Machine Learning für Softwareentwickler. Paolo Perrotta

Чтение книги онлайн.

Читать онлайн книгу Machine Learning für Softwareentwickler - Paolo Perrotta страница 16

Автор:
Серия:
Издательство:
Machine Learning für Softwareentwickler - Paolo Perrotta

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

train() besteht darin, die markierte Stelle unten in dieser Kurve zu finden, also den Wert von w, der zu einem minimalen Verlust führt. Bei diesem w nähert sich unser Modell den Datenpunkten am besten an.

      Stellen Sie sich diese Kurve als ein Tal vor, an dessen Hang irgendwo eine Wanderin steht, die zu ihrem Lager an der gekennzeichneten Stelle unterwegs ist. Allerdings ist es so dunkel, dass sie nur den Boden in unmittelbarer Nähe ihrer Füße sehen kann. Um das Lager zu finden, kann sie sich eines ganz einfachen Verfahrens bedienen: Sie geht immer in die Richtung des steilsten Abstiegs. Sofern es in dem Gelände keine Löcher oder Klippen gibt – was bei unserer Verlustfunktion nicht der Fall ist –, führt jeder Schritt die Wanderin näher an ihr Lager heran.

      Um dieses Prinzip in Code umsetzen zu können, müssen wir die Steigung der Verlustkurve bestimmen. Ein Maß dafür ist der Gradient. Vereinbarungsgemäß ist der Gradient an einem gegebenen Punkt der Kurve ein »Pfeil«, der bergauf weist.

image

      Um den Gradienten zu bestimmen, verwenden wir ein mathematisches Werkzeug: die Ableitung des Verlusts nach dem Gewicht, geschrieben ∂L/∂w. Formal ausgedrückt, bestimmt die Ableitung an einem gegebenen Punkt, wie stark sich L an diesem Punkt bei kleinen Abweichungen von w ändert. Was geschieht mit dem Verlust, wenn wir das Gewicht ein winziges bisschen erhöhen? In dem vorstehenden Diagramm ist die Ableitung negativ, da der Verlust abnimmt. Bei positiver Ableitung dagegen steigt der Verlust. Am Minimum der Kurve, also an dem mit dem Kreuz markierten Punkt, ist die Kurve eben und die Ableitung damit null.

      Beachten Sie, dass unsere Wanderin in die dem Gradienten entgegengesetzte Richtung gehen muss, um das Minimum zu erreichen. An einem Punkt mit negativer Ableitung wie in dem Bild muss sie sich also in positiver Richtung bewegen. Ihre Schrittweite muss proportional zum Betrag der Ableitung sein. Ist die Ableitung betragsmäßig groß, verläuft die Kurve steil. Das Lager ist dann noch weit entfernt. Daher kann die Wanderin vertrauensvoll große Schritte machen. Wenn sie sich dem Lager nähert, wird die Ableitung jedoch kleiner und damit auch ihre Schrittweite.

      Dieser Algorithmus ist das Gradientenverfahren oder Verfahren des steilsten Abstiegs. Zu seiner Implementierung ist ein bisschen Mathematik gefordert.

      Als Erstes übersetzen wir unsere Formel für den mittleren quadratischen Fehler in die gute, alte mathematische Schreibweise:

image

      Falls Ihnen diese Schreibweise nicht bekannt vorkommt: Das Symbol S ist das Summenzeichen, und das m steht für die Anzahl der Beispiele. Diese Formel bedeutet: »Summiere die quadrierten Fehler aller Beispiele von Beispiel 1 bis Beispiel m und dividiere das Ergebnis durch die Anzahl der Beispiele.«

      Bei den verschiedenen x und y handelt es sich um die Eingabevariablen und Labels, also um Konstanten. Auch m ist konstant, da sich die Anzahl der Beispiele nicht ändert. Da wir b vorübergehend auf 0 gesetzt haben, ist auch dieser Wert konstant. Wir werden b in Kürze wieder benutzen, aber vorläufig ist w der einzige Wert in der Formel, der sich ändert.

      Nun müssen wir Betrag und Richtung des Gradienten bestimmen, also die Ableitung von L nach w. Wenn Sie sich noch an die Analysis in der Oberstufe erinnern, können Sie die Ableitung selbst berechnen. Wenn nicht, ist das aber auch kein Beinbruch. Jemand anderes hat die Arbeit schon für uns erledigt:

image

      Die Ableitung des Verlusts sieht ähnlich aus wie der Verlust selbst, allerdings ist die Quadrierung verloren gegangen. Außerdem wird jeder Summand mit x und das Endergebnis mit 2 multipliziert. In diese Formel können wir nun beliebige Werte für w eingeben und erhalten den Gradienten an diesem Punkt als Ergebnis.

      Im Code sieht diese Formel wie folgt aus. Auch hier ist b wieder auf 0 fixiert.

       03_gradient/gradient_descent_without_bias.py

      def gradient(X, Y, w):

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

      Mit der Formel für den Gradienten können wir nun train() so umschreiben, dass die Funktion das Gradientenverfahren anwendet.

      Mit den Änderungen für das Gradientenverfahren sieht train() wie folgt aus:

       03_gradient/gradient_descent_without_bias.py

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

      w = 0

      for i in range(iterations):

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

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

      return w

      Diese Version von train() ist viel knapper als die vorherige. Bei Anwendung des Gradientenverfahrens brauchen wir keine if-Anweisungen mehr. Wir müssen lediglich w initialisieren und dann wiederholt in die dem Gradienten entgegengesetzte Richtung gehen (da der Gradient aufwärts zeigt, wir uns aber abwärts bewegen wollen). Der Hyperparameter lr ist immer noch da, gibt jetzt aber an, wie groß jeder einzelne Schritt im Verhältnis zum Gradienten sein soll.

      Außerdem müssen wir entscheiden, wann wir aufhören wollen. Die alte Version von train() endete, wenn die Höchstzahl der Iterationen erreicht oder es nicht mehr möglich war, den Verlust weiter zu verringern. Beim Gradientenverfahren dagegen kann der Verlust theoretisch immer kleiner werden und sich in immer winzigeren Schritten dem Minimum nähern, ohne es jemals zu erreichen. Wann sollten wir diesen Vorgang abbrechen?

      Wir können aufhören, wenn der Gradient ausreichend klein geworden ist, da das bedeutet, dass wir dem Minimum schon sehr nahe gekommen sind. Der vorstehende Code dagegen verfolgt einen weniger raffinierten Ansatz: Wenn Sie train() aufrufen, geben Sie an, wie viele Iterationen die Funktion durchlaufen soll. Mehr Iterationen bedeuten einen geringeren Verlust, aber da der Verlust in immer geringem Maße sinkt, ist irgendwann ein Punkt erreicht, an dem eine größere Präzision den Aufwand nicht mehr lohnt.

      Weiter hinten in diesem Buch (in Kapitel 15, »Entwicklung«) erfahren Sie, wie Sie sinnvolle Werte für Hyperparameter wie iterations und lr auswählen. Vorläufig habe ich einfach verschiedene Werte ausprobiert und mich für diejenigen entschieden, die zu einem ausreichend geringen Verlust und genügender Genauigkeit führten:

      X, Y = np.loadtxt("pizza.txt", skiprows=1, unpack=True)

      w = train(X, Y, iterations=100, lr=0.001)

      print("\nw=%.10f" % w)

      Bei der Ausführung dieses Codes ergibt sich Folgendes:

      Iteration 0 => Loss: 812.8666666667

      Iteration 1 => Loss: 304.3630879787

      Iteration 2 => Loss: 143.5265791020

      ...

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