Machine Learning für Softwareentwickler. Paolo Perrotta

Machine Learning für Softwareentwickler - Paolo Perrotta


Скачать книгу
X mit w beschreiben:

image

      Allerdings hat X nicht nur eine Zeile, sondern eine Zeile pro Beispiel – es ist eine (30, 3)-Matrix. Wenn wir sie mit w multiplizieren – einer (3, 1)-Matrix –, erhalten wir eine (30, 1)-Matrix mit einer Zeile pro Beispiel und einer einzigen Spalte, die die Vorsagen für diese Beispiele enthält. Mit einer einzigen Matrizenmultiplikation erhalten wir also auf einen Streich die Vorhersagen für sämtliche Beispiele.

      Um die Vorhersagefunktion für mehrere Eingabevariablen umzuschreiben, müssen wir also lediglich X und w multiplizieren, wobei uns die NumPy-Funktion matmul() zu Hilfe kommt:

      def predict(X, w):

      return np.matmul(X, w) <

      Es hat eine Weile gedauert, um zu dieser winzigen Funktion zu kommen, und dabei haben wir die Sache noch vereinfacht, indem wir den Bias vorläufig ignoriert haben. Der Aufwand hat sich aber schon gelohnt: predict() implementiert jetzt das Modell der multiplen linearen Regression in einer einzigen, kurzen Codezeile.

      Kommen wir nun zur Funktion loss(). Zur Berechnung des Verlusts hatten wir den mittleren quadratischen Fehler herangezogen:

      def loss(X, Y, w):

      return np.average((predict(X, w) - Y) ** 2)

      Auch habe ich wieder den Bias b herausgenommen, damit wir uns für den Anfang mit einem Problem weniger herumschlagen müssen. Der Bias wird aber in Kürze wieder zurückkehren. Schauen wir uns zunächst aber an, wie wir diese abgespeckte Version von loss() an mehrdimensionale Eingaben anpassen.

      Ich weiß noch, wie frustrierend Matrizenoperationen für mich waren, als ich meine ersten ML-Programme schrieb. Insbesondere schienen die Dimensionen der Matrizen nie zueinanderzupassen. Mit der Zeit lernte ich jedoch, dass diese Dimensionen in Wirklichkeit eine gute Sache waren, denn wenn ich sie sorgfältig im Auge behielt, halfen sie mir sogar dabei, meinen Code zu schreiben. Auch hier wollen wir die Dimensionen der Matrizen als Richtschnur für unseren Code nutzen.

      Fangen wir bei den Labels an. Im vorstehenden Code haben wir zwei Matrizen für Labels: Y mit der Grundwahrheit der Datenmenge sowie die von predict() berechnete Matrix y_hat (Y-Dach!). Beide sind nun (m, 1)-Matrizen, haben also eine Zeile pro Beispiel, aber nur eine einzige Spalte. Da wir 30 Beispiele haben, sind es in unserem Fall (30, 1)-Matrizen. Wenn wir Y von y_hat subtrahieren, prüft NumPy zunächst, ob beide Matrizen die gleiche Größe haben, und subtrahiert dann jedes Element von Y vom entsprechenden Element von y_hat. Das Ergebnis ist ebenfalls eine (30, 1)-Matrix.

      Anschließend wollen wir alle Elemente in dieser Matrix quadrieren. Da Matrizen als NumPy-Arrays implementiert sind, können wir eine Besonderheit von NumPy nutzen, die als Broadcasting bezeichnet wird. Sie kam sogar schon in früheren Kapiteln zum Tragen. Wenn wir eine arithmetische Operation auf ein NumPy-Array anwenden, dann wird sie wie von einem Rundfunksender an alle Elemente des Arrays übertragen. Das heißt also, wenn wir die gesamte Matrix quadrieren, wendet NumPy diese Operation pflichteifrig auf alle Elemente der Matrix an. Auch das Ergebnis dieses Vorgangs ist wieder eine (30, 1)-Matrix.

      Schließlich rufen wir noch average() auf, um den Durchschnitt aller Elemente in der Matrix zu bilden. Diese Funktion gibt einen einzelnen Skalar zurück:

      a_number = loss(X, Y, w)

      a_number.shape # => ()

      Das leere Klammernpaar ist die NumPy-Schreibweise für: »Dies ist ein Skalar, hat also keine Dimensionen.«

      Fazit: Wir müssen die Berechnung des Verlusts überhaupt nicht ändern. Unsere Code für den mittleren quadratischen Fehler funktioniert bei mehreren Eingabevariablen genauso wie bei einer einzigen Variablen.

      Jetzt bleibt nur noch eines zu tun: Wir müssen den Verlustgradienten auf mehrere Variablen erweitern. Ich gehe hier gleich in die Vollen und zeige Ihnen die Matrizenversion der Funktion gradient():

      def gradient(X, Y, w):

      return 2 * np.matmul(X.T, (predict(X, w) - Y)) / X.shape[0]

      X.T bedeutet »X transponiert« (siehe »Matrizen transponieren« auf Seite 65).

      Siehe »Of Gradients and Matrices« auf ProgML.

      Es hat mich einiges an Zeit gekostet, um von der alten Version von gradient() zu dieser zu kommen. Ausnahmsweise werde ich hier nicht auf die Einzelheiten der Funktion eingehen, weil sie einfach zu viel Platz einnehmen würden. Wenn Sie neugierig sind, können Sie auf ProgML2, der Begleitwebsite zu diesem Buch, mehr darüber erfahren. Sie können sich auch selbst vergewissern, dass sich diese neue Version von gradient() genauso verhält wie die alte, aber mehrere Eingabevariablen entgegennimmt.

      Prüfen wir, ob wir alle Einzelteile zusammenhaben:

       Wir haben den Code zur Aufbereitung der Daten geschrieben.

       Wir haben predict() angepasst.

       Wir haben festgestellt, dass wir loss() nicht anpassen müssen.

       Wir haben gradient() angepasst.

      Es ist alles erledigt. Damit können wir nun unser ML-Programm wie folgt umschreiben:

       04_hyperspace/multiple_regression_without_bias.py

       import numpy as np

      def predict(X, w):

      return np.matmul(X, w) <

      def loss(X, Y, w):

      return np.average((predict(X, w) - Y) ** 2)

      def gradient(X, Y, w):

      return 2 * np.matmul(X.T, (predict(X, w) - Y)) / X.shape[0] <

      def train(X, Y, iterations, lr):

      w = np.zeros((X.shape[1], 1)) <

      for i in range(iterations):

      print("Iteration %4d => Loss: %.20f" % (i, loss(X, Y, w)))

      w -= gradient(X, Y, w) * lr

      return w

      x1, x2, x3, y = np.loadtxt("pizza_3_vars.txt", skiprows=1, unpack=True) <

      X = np.column_stack((x1, x2, x3)) <

      Y = y.reshape(-1, 1) <

      w = train(X, Y, iterations=100000, lr=0.001) <

      Wir haben eine ganze Reihe von Seiten gebraucht, um fertig zu werden, und doch ist der Code dem aus dem vorherigen Kapitel ziemlich ähnlich. Abgesehen von dem Teil, in dem wir die Daten laden und aufbereiten, mussten wir nur drei Zeilen ändern. Die Funktionen sind jetzt allgemein nutzbar: Sie können nicht nur Robertos Datenmenge mit drei Variablen verarbeiten, sondern mit Eingabevariablen beliebiger Anzahl umgehen.

      Wenn wir dieses Programm ausführen, erhalten wir folgende Ausgabe:

      Iteration 0 => Loss: 1333.56666666666660603369

      Iteration 1 => Loss: 151.14311361881479456315

      Iteration 2 => Loss: 64.99460808656147037254

      ...

      Iteration 99999 => Loss: 6.89576133146784187034

      Der Verlust


Скачать книгу