Hacer una red neuronal: cómo no romper el cerebro

Hola Habr!

En este breve artículo, le contaré sobre dos trampas con las que es fácil colisionar y sobre las que es fácil sortear.

Se tratará de crear una red neuronal trivial en Keras, con la cual predeciremos la media aritmética de dos números.

Parece que eso podría ser más fácil. Y realmente, nada complicado, pero hay matices.

Para quien el tema es interesante, bienvenido bajo el corte, no habrá descripciones largas y aburridas, solo un código corto y comentarios al respecto.

La solución se parece a esto:

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)) 

Tratando de aprender ... pero no sale nada de eso. Y aquí, en este lugar, puedes organizar bailes con una pandereta y perder mucho tiempo.

 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]] 

Se predijo 49, que está lejos de ser 65.

Pero en cuanto rehacemos un poco el generador, todo comienza a funcionar de inmediato.

 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] 

Y está claro que, literalmente, en la tercera era, la red está convergiendo.

 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 

La principal diferencia es que en el primer caso, el objeto x_mean se crea en la memoria cada vez, y en el segundo caso, aparece cuando se crea el generador y luego solo se reutiliza.

Entendemos aún más si todo es correcto en este generador. Resulta que no realmente.
El siguiente ejemplo muestra que algo va mal.
 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.]))

El valor promedio en la primera llamada del iterador no coincide con los números en función de los cuales se calcula. De hecho, el valor promedio se calculó correctamente, pero porque la matriz se pasó por referencia, la segunda vez que se llamó al iterador, los valores en la matriz se sobrescribieron y la función print () devolvió lo que estaba en la matriz, y no lo que esperábamos.

Hay dos formas de arreglar esto. Ambos son costosos pero correctos.
1. Mueva la creación de la variable x dentro del ciclo while para que se cree una matriz en cada rendimiento.
 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. Devuelva una copia de la matriz.
 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]))


Ahora todo está bien. Adelante

¿Se necesita hacer expand_dims? Intentemos eliminar esta línea y el nuevo código será así:

 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] 

Todo aprende bien, aunque los datos devueltos tienen una forma diferente.

Por ejemplo, había [[49.]], y se convirtió en [49.], pero dentro de Keras esto, aparentemente, se reduce correctamente a la dimensión deseada.

Entonces, sabemos cómo debería ser el generador de datos correcto, ahora juguemos con la función lambda y veamos el comportamiento de expand_dims allí.

No vamos a predecir nada, solo consideramos el valor correcto dentro de lambda.

El código es el siguiente:

 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 

Comenzamos y vemos que todo está bien:

 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 

Ahora intentemos modificar ligeramente nuestra función lambda y eliminar expand_dims.

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

Al compilar el modelo, no aparecieron errores en la dimensión, pero el resultado ya es diferente, la pérdida se considera incomprensible. Así que aquí debe hacerse expand_dims, nada sucederá automáticamente.

 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 

Y si observa el resultado de predicción () devuelto, puede ver que la dimensión es incorrecta, el resultado es [46.] y esperado [[46.]].

Algo asi. Gracias a todos los que lo leyeron. Y tenga cuidado con los detalles, el efecto de ellos puede ser significativo.

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


All Articles