Ein neuronales Netzwerk aufbauen: Wie man das Gehirn nicht bricht

Hallo Habr!

In diesem kurzen Artikel erzähle ich Ihnen von zwei Fallstricken, mit denen man leicht kollidieren und leicht umgehen kann.

Es wird darum gehen, ein triviales neuronales Netzwerk auf Keras zu erstellen, mit dem wir das arithmetische Mittel zweier Zahlen vorhersagen.

Es scheint, dass das einfacher sein könnte. Und wirklich nichts Kompliziertes, aber es gibt Nuancen.

Für wen das Thema interessant ist, willkommen unter dem Schnitt, es wird keine langen langweiligen Beschreibungen geben, nur einen kurzen Code und Kommentare dazu.

Die Lösung sieht ungefähr so ​​aus:

import numpy as np from keras.layers import Input, Dense, Lambda from keras.models import Model import keras.backend as K #   def train_iterator(batch_size=64): x = np.zeros((batch_size, 2)) while True: for i in range(batch_size): x[i][0] = np.random.randint(0, 100) x[i][1] = np.random.randint(0, 100) x_mean = (x[::,0] + x[::,1]) / 2 x_mean_ex = np.expand_dims(x_mean, -1) yield [x], [x_mean_ex] #  def create_model(): x = Input(name = 'x', shape=(2,)) x_mean = Dense(1)(x) model = Model(inputs=x, outputs=x_mean) return model #    model = create_model() model.compile(loss=['mse'], optimizer = 'rmsprop') model.fit_generator(train_iterator(), steps_per_epoch = 1000, epochs = 100, verbose = 1) #  x, x_mean = next(train_iterator(1)) print(x, x_mean, model.predict(x)) 

Ich versuche zu lernen ... aber es kommt nichts dabei heraus. Und hier an diesem Ort können Sie Tänze mit einem Tamburin arrangieren und viel Zeit verlieren.

 Epoch 1/100 1000/1000 [==============================] - 2s 2ms/step - loss: 1044.0806 Epoch 2/100 1000/1000 [==============================] - 2s 2ms/step - loss: 713.5198 Epoch 3/100 1000/1000 [==============================] - 3s 3ms/step - loss: 708.1110 ... Epoch 98/100 1000/1000 [==============================] - 2s 2ms/step - loss: 415.0479 Epoch 99/100 1000/1000 [==============================] - 2s 2ms/step - loss: 416.6932 Epoch 100/100 1000/1000 [==============================] - 2s 2ms/step - loss: 417.2400 [array([[73., 57.]])] [array([[65.]])] [[49.650894]] 

49 wurde vorhergesagt, was weit von 65 ist.

Aber sobald wir den Generator ein wenig erneuern, funktioniert alles sofort.

 def train_iterator_1(batch_size=64): x = np.zeros((batch_size, 2)) x_mean = np.zeros((batch_size,)) while True: for i in range(batch_size): x[i][0] = np.random.randint(0, 100) x[i][1] = np.random.randint(0, 100) x_mean[::] = (x[::,0] + x[::,1]) / 2 x_mean_ex = np.expand_dims(x_mean, -1) yield [x], [x_mean_ex] 

Und es ist klar, dass das Netzwerk bereits in der dritten Ära buchstäblich konvergiert.

 Epoch 1/5 1000/1000 [==============================] - 2s 2ms/step - loss: 648.9184 Epoch 2/5 1000/1000 [==============================] - 2s 2ms/step - loss: 0.0177 Epoch 3/5 1000/1000 [==============================] - 2s 2ms/step - loss: 0.0030 

Der Hauptunterschied besteht darin, dass im ersten Fall das x_mean-Objekt jedes Mal im Speicher erstellt wird und im zweiten Fall beim Erstellen des Generators angezeigt und dann nur wiederverwendet wird.

Wir verstehen weiter, ob in diesem Generator alles korrekt ist. Es stellt sich heraus, dass nicht wirklich.
Das folgende Beispiel zeigt, dass etwas schief geht.
 def train_iterator(batch_size=1): x = np.zeros((batch_size, 2)) while True: for i in range(batch_size): x[i][0] = np.random.randint(0, 100) x[i][1] = np.random.randint(0, 100) x_mean = (x[::,0] + x[::,1]) / 2 yield x, x_mean it = train_iterator() print(next(it), next(it)) 

(array([[44., 2.]]), array([10.])) (array([[44., 2.]]), array([23.]))

Der Durchschnittswert im ersten Iteratoraufruf stimmt nicht mit den Zahlen überein, auf deren Grundlage er berechnet wird. Tatsächlich wurde der Durchschnittswert korrekt berechnet, aber weil Das Array wurde als Referenz übergeben, beim zweiten Aufruf des Iterators wurden die Werte im Array überschrieben, und die Funktion print () gab zurück, was sich im Array befand und nicht das, was wir erwartet hatten.

Es gibt zwei Möglichkeiten, dies zu beheben. Beides ist teuer, aber richtig.
1. Verschieben Sie die Erstellung der Variablen x in die while-Schleife, sodass bei jeder Ausbeute ein Array erstellt wird.
 def train_iterator_1(batch_size=1): while True: x = np.zeros((batch_size, 2)) for i in range(batch_size): x[i][0] = np.random.randint(0, 100) x[i][1] = np.random.randint(0, 100) x_mean = (x[::,0] + x[::,1]) / 2 yield x, x_mean it_1 = train_iterator_1() print(next(it_1), next(it_1)) 

(array([[82., 4.]]), array([43.])) (array([[77., 34.]]), array([55.5]))


2. Geben Sie eine Kopie des Arrays zurück.
 def train_iterator_2(batch_size=1): x = np.zeros((batch_size, 2)) while True: x = np.zeros((batch_size, 2)) for i in range(batch_size): x[i][0] = np.random.randint(0, 100) x[i][1] = np.random.randint(0, 100) x_mean = (x[::,0] + x[::,1]) / 2 yield np.copy(x), x_mean it_2 = train_iterator_2() print(next(it_2), next(it_2)) 

(array([[63., 31.]]), array([47.])) (array([[94., 25.]]), array([59.5]))


Jetzt ist alles in Ordnung. Mach weiter.

Muss expand_dims durchgeführt werden? Versuchen wir, diese Zeile zu entfernen, und der neue Code sieht folgendermaßen aus:

 def train_iterator(batch_size=64): while True: x = np.zeros((batch_size, 2)) for i in range(batch_size): x[i][0] = np.random.randint(0, 100) x[i][1] = np.random.randint(0, 100) x_mean = (x[::,0] + x[::,1]) / 2 yield [x], [x_mean] 

Alles lernt gut, obwohl die zurückgegebenen Daten eine andere Form haben.

Zum Beispiel war es [[49.]] und es wurde [49.], aber innerhalb von Keras wird dies anscheinend korrekt auf die gewünschte Dimension reduziert.

Wir wissen also, wie der richtige Datengenerator aussehen sollte. Lassen Sie uns nun mit der Lambda-Funktion herumspielen und das Verhalten von expand_dims dort betrachten.

Wir werden nichts vorhersagen, wir betrachten nur den korrekten Wert innerhalb von Lambda.

Der Code lautet wie folgt:

 def calc_mean(x): res = (x[::,0] + x[::,1]) / 2 res = K.expand_dims(res, -1) return res def create_model(): x = Input(name = 'x', shape=(2,)) x_mean = Lambda(lambda x: calc_mean(x), output_shape=(1,))(x) model = Model(inputs=x, outputs=x_mean) return model 

Wir fangen an und sehen, dass alles in Ordnung ist:

 Epoch 1/5 100/100 [==============================] - 0s 3ms/step - loss: 0.0000e+00 Epoch 2/5 100/100 [==============================] - 0s 2ms/step - loss: 0.0000e+00 Epoch 3/5 100/100 [==============================] - 0s 3ms/step - loss: 0.0000e+00 

Versuchen wir nun, unsere Lambda-Funktion leicht zu ändern und expand_dims zu entfernen.

 def calc_mean(x): res = (x[::,0] + x[::,1]) / 2 return res 

Beim Kompilieren des Modells traten keine Fehler in der Dimension auf, aber das Ergebnis ist bereits anders, der Verlust wird als unverständlich angesehen, wie. Hier muss also expand_dims gemacht werden, nichts wird automatisch passieren.

 Epoch 1/5 100/100 [==============================] - 0s 3ms/step - loss: 871.6299 Epoch 2/5 100/100 [==============================] - 0s 3ms/step - loss: 830.2568 Epoch 3/5 100/100 [==============================] - 0s 2ms/step - loss: 830.8041 

Wenn Sie sich das zurückgegebene Ergebnis von prognost () ansehen, können Sie feststellen, dass die Dimension falsch ist, die Ausgabe [46.] ist und erwartet wird [[46.]].

Irgendwie so. Vielen Dank an alle, die es gelesen haben. Und seien Sie vorsichtig im Detail, die Auswirkungen können erheblich sein.

Source: https://habr.com/ru/post/de439038/


All Articles