Hay muchas publicaciones en la red sobre la interpretación de una red neuronal particular y la importancia y contribución de ciertos puntos al aprendizaje. Hay mucho trabajo sobre la búsqueda de bigotes, colas y otras partes y su importancia y significado. Ahora no reemplazaré a los bibliotecarios y haré una lista. Solo hablaré de mi experimento.
Todo comenzó con un excelente informe en video “Cómo piensan los robots. Interpretación de modelos ML ” , revisado con el consejo de una persona inteligente y, como cualquier negocio sensato, planteó muchas preguntas. Por ejemplo: - ¿Qué tan únicos son los puntos clave del conjunto de datos?
U otra pregunta: - hay muchos artículos en la red sobre cómo cambiar un punto de la imagen puede distorsionar significativamente la predicción de la red. Permítanme recordarles que en este artículo solo estamos considerando problemas de clasificación. ¿Qué tan singular es este insidioso punto? ¿Existen tales puntos en la secuencia natural de MNIST y si se encuentran y se descartan, la precisión de entrenamiento de la red neuronal será mayor?
El autor, siguiendo su método tradicional de deshacerse de todo lo innecesario, decidió no interferir con el grupo y eligió una forma simple, confiable y efectiva de estudiar las preguntas planteadas:
como problema experimental, ejemplo de preparación, elija el conocido MNIST ( yann.lecun.com/exdb/mnist ) y su clasificación.
Como red experimental, elegí la clásica, recomendada para principiantes, una red ejemplar del equipo
KERAS github.com/keras-team/keras/blob/master/examples/mnist_cnn.py
Y el estudio en sí decidió ser muy simple.
Entrenemos la red desde KERAS con un criterio de parada como la ausencia de un aumento en la precisión en la secuencia de prueba, es decir. Enseñe a la red hasta que test_accuracy sea significativamente mayor que validation_accuracy y validation_accuracy no mejore durante 15 épocas. En otras palabras, la red dejó de aprender y comenzó la reentrenamiento.
A partir del conjunto de datos MNIST, crearemos 324 nuevos conjuntos de datos descartando grupos de puntos y la misma red nos enseñará exactamente en las mismas condiciones con los mismos pesos iniciales.
Empecemos, creo que es correcto y correcto diseñar todo el código, desde la primera hasta la última línea. Incluso si los lectores lo han visto, obviamente, muchas veces.
Cargamos las bibliotecas y cargamos el conjunto de datos mnist, si aún no se ha cargado.
Luego lo convertimos al formato 'float32' y lo normalizamos al rango 0. - 1.
Se acabó la preparación.
'''Trains a simple convnet on the MNIST dataset.
Gets to 99.25% test accuracy after 12 epochs
(there is still a lot of margin for parameter tuning).
16 seconds per epoch on a GRID K520 GPU.
'''
from __future__ import print_function
import keras
from keras.datasets import mnist
from keras.models import Sequential, load_model
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
from keras.optimizers import *
from keras.callbacks import EarlyStopping
import numpy as np
import os
num_classes = 10
# input image dimensions
img_rows, img_cols = 28, 28
# the data, shuffled and split between train and test sets
(x_train, y_train), (x_test, y_test) = mnist.load_data()
if K.image_data_format() == 'channels_first':
x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
input_shape = (1, img_rows, img_cols)
else:
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= np.max(x_train)
x_test /= np.max(x_test)
XX_test = np.copy(x_test)
XX_train = np.copy(x_train)
YY_test = np.copy(y_test)
YY_train = np.copy(y_train)
print('x_train shape:', XX_train.shape)
print('x_test shape:', XX_test.shape)
Recordemos en las variables el nombre de los archivos y pesos del modelo, así como la precisión y pérdida de nuestra red. Esto no está en el código fuente, pero es necesario para el experimento.
f_model = "./data/mnist_cnn_model.h5"
f_weights = "./data/mnist_cnn_weights.h5"
accu_f = 'accuracy'
loss_f = 'binary_crossentropy'
La red en sí es exactamente la misma que en
github.com/keras-team/keras/blob/master/examples/mnist_cnn.py .
Guarde la red y las escalas en disco. Realizaremos todos nuestros intentos de entrenamiento con los mismos pesos iniciales:
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
activation='relu',
input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))
model.compile(loss=[loss_f], optimizer=Adam(lr=1e-4), metrics=[accu_f])
model.summary()
model.save_weights(f_weights)
model.save(f_model)
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 26, 26, 32) 320
_________________________________________________________________
conv2d_1 (Conv2D) (None, 24, 24, 64) 18496
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 12, 12, 64) 0
_________________________________________________________________
dropout (Dropout) (None, 12, 12, 64) 0
_________________________________________________________________
flatten (Flatten) (None, 9216) 0
_________________________________________________________________
dense (Dense) (None, 128) 1179776
_________________________________________________________________
dropout_1 (Dropout) (None, 128) 0
_________________________________________________________________
dense_1 (Dense) (None, 10) 1290
=================================================================
Total params: 1,199,882
Trainable params: 1,199,882
Non-trainable params: 0
_________________________________________________________________
Comencemos a entrenar en el mnist original para obtener una eficiencia básica de referencia.
x_test = np.copy(XX_test)
x_train = np.copy(XX_train)
s0 = 0
if os.path.isfile(f_model):
model = load_model(f_model)
model.load_weights(f_weights, by_name=False)
step = 0
while True:
fit = model.fit(x_train, y_train,
batch_size=batch_size,
epochs=1,
verbose=0,
validation_data=(x_test, y_test)
)
current_accu = fit.history[accu_f][0]
current_loss = fit.history['loss'][0]
val_accu = fit.history['val_'+accu_f][0]
val_loss = fit.history['val_loss'][0]
print("\x1b[2K","accuracy {0:12.10f} loss {1:12.10f} step {2:5d} val_accu {3:12.10f} val_loss {4:12.10f} ".\
format(current_accu, current_loss, step, val_accu, val_loss), end="\r")
step += 1
if val_accu > max_accu:
s0 = 0
max_accu = val_accu
else:
s0 += 1
if current_accu * 0.995 > val_accu and s0 > 15:
break
else:
print("model not found ")
accuracy 0.9967333078 loss 0.0019656278 step 405 val_accu 0.9916999936 val_loss 0.0054226643
Ahora comencemos el experimento principal. Tomamos para entrenamiento de la secuencia original las 60,000 imágenes marcadas, y en ellas ponemos a cero todo excepto el cuadrado de 9x9. Consigamos 324 secuencias experimentales y comparemos el resultado del entrenamiento de la red en ellas con el entrenamiento de la secuencia original. Entrenamos la misma red con los mismos pesos iniciales.
batch_size = 5000
s0 = 0
max_accu = 0.
for i in range(28 - 9):
for j in range(28 - 9):
print("\ni= ", i, " j= ",j)
x_test = np.copy(XX_test)
x_train = np.copy(XX_train)
x_train[:,:i,:j,:] = 0.
x_test [:,:i,:j,:] = 0.
x_train[:,i+9:,j+9:,:] = 0.
x_test [:,i+9:,j+9:,:] = 0.
if os.path.isfile(f_model):
model = load_model(f_model)
model.load_weights(f_weights, by_name=False)
else:
print("model not found ")
break
step = 0
while True:
fit = model.fit(x_train, y_train,
batch_size=batch_size,
epochs=1,
verbose=0,
validation_data=(x_test, y_test)
)
current_accu = fit.history[accu_f][0]
current_loss = fit.history['loss'][0]
val_accu = fit.history['val_'+accu_f][0]
val_loss = fit.history['val_loss'][0]
print("\x1b[2K","accuracy {0:12.10f} loss {1:12.10f} step {2:5d} val_accu {3:12.10f} val_loss {4:12.10f} ".\
format(current_accu, current_loss, step, val_accu, val_loss), end="\r")
step += 1
if val_accu > max_accu:
s0 = 0
max_accu = val_accu
else:
s0 += 1
if current_accu * 0.995 > val_accu and s0 > 15:
break
No tiene sentido publicar los 324 resultados aquí, si alguien está interesado, puedo enviarlo personalmente. El cálculo tarda varios días, si alguien quiere repetirlo.
Al final resultó que, la red en un recorte de 9x9 puede aprender como peor, lo cual es obvio, pero también mejor, lo cual no es en absoluto obvio.
Por ejemplo:
i = 0 j = 14
precisión 0.9972333312 pérdida 0.0017946947 paso 450 val_accu 0.9922000170 val_loss 0.0054322388
i = 18, j = 1
precisión 0.9973166585 pérdida 0.0019487827 paso 415 val_accu 0.9922000170 val_loss 0.0053000450 Desechamos las
imágenes con la calidad del aprendizaje 9x y los números escritos a mano. ¡El reconocimiento está mejorando con nosotros!
También está claro que existe más de un área especial para mejorar la calidad de la red. Y no dos, estos dos se dan como ejemplo.
El resultado de este experimento y conclusiones preliminares.
- Cualquier conjunto de datos naturales, no creo que LeCune distorsionara deliberadamente algo, contiene no solo puntos que son esenciales para el aprendizaje, sino también puntos que interfieren con el aprendizaje. La tarea de encontrar puntos "dañinos" se vuelve urgente, existen, aunque no sean visibles.
- Puede apilar y combinar no solo a lo largo del conjunto de datos, seleccionando imágenes en grupos, sino también a lo largo, seleccionando áreas de imágenes para dividir y luego como de costumbre. En este caso, este enfoque mejora la calidad de la capacitación y se espera que, en una tarea similar, el uso de este tipo de apilamiento agregue calidad. Y en el mismo kaggle.com, algunas diezmilésimas a veces (casi siempre) le permiten aumentar significativamente su autoridad y calificación.
Gracias por su atención.