Transferir aprendizaje: cómo entrenar rápidamente una red neuronal en sus datos

El aprendizaje automático se está volviendo más accesible, hay más oportunidades para aplicar esta tecnología utilizando "componentes estándar". Por ejemplo, Transfer Learning le permite utilizar la experiencia adquirida en la resolución de un problema para resolver otro problema similar. La red neuronal se entrena primero en una gran cantidad de datos, luego en el conjunto objetivo.

Reconocimiento de alimentos

En este artículo, le diré cómo usar el método Transfer Learning utilizando el ejemplo de reconocimiento de imágenes con alimentos. Hablaré sobre otras herramientas de aprendizaje automático en el taller de Aprendizaje automático y Redes neuronales para desarrolladores .

Si nos enfrentamos a la tarea de reconocimiento de imágenes, puede utilizar el servicio listo para usar. Sin embargo, si necesita entrenar el modelo en su propio conjunto de datos, deberá hacerlo usted mismo.

Para tareas típicas como la clasificación de imágenes, puede usar la arquitectura preparada (AlexNet, VGG, Inception, ResNet, etc.) y entrenar la red neuronal en sus datos. Ya existen implementaciones de tales redes que usan varios marcos, por lo que en esta etapa puede usar una de ellas como una caja negra, sin profundizar en el principio de su funcionamiento.

Sin embargo, las redes neuronales profundas exigen grandes cantidades de datos para la convergencia del aprendizaje. Y a menudo en nuestra tarea particular no hay suficientes datos para entrenar adecuadamente todas las capas de la red neuronal. Transferir aprendizaje resuelve este problema.

Transferir el aprendizaje para la clasificación de imágenes


Las redes neuronales que se usan para la clasificación generalmente contienen N neuronas de salida en la última capa, donde N es el número de clases. Tal vector de salida se trata como un conjunto de probabilidades de pertenecer a una clase. En nuestra tarea de reconocer imágenes de alimentos, la cantidad de clases puede diferir de la del conjunto de datos original. En este caso, tendremos que tirar por completo esta última capa y poner una nueva, con el número correcto de neuronas de salida

Transferencia de aprendizaje

A menudo, al final de las redes de clasificación, se utiliza una capa totalmente conectada. Como reemplazamos esta capa, el uso de pesos pre-entrenados para ella no funcionará. Tendrás que entrenarlo desde cero, inicializando sus pesos con valores aleatorios. Cargamos pesos para todas las demás capas desde una instantánea pre-entrenada.

Hay varias estrategias para seguir entrenando el modelo. Usaremos lo siguiente: entrenaremos a toda la red de extremo a extremo (de extremo a extremo ), y no fijaremos pesos pre-entrenados para permitir que se ajusten un poco y se ajusten a nuestros datos. Este proceso se llama ajuste fino .

Componentes estructurales


Para resolver el problema, necesitamos los siguientes componentes:

  1. Descripción del modelo de red neuronal.
  2. Canal de aprendizaje
  3. Tubería de interferencia
  4. Pesas pre-entrenadas para este modelo
  5. Datos para entrenamiento y validación

Componentes

En nuestro ejemplo, tomaré los componentes (1), (2) y (3) de mi propio repositorio , que contiene el código más liviano; puede resolverlo fácilmente si lo desea. Nuestro ejemplo se implementará en el popular marco TensorFlow . Los pesos pre-entrenados (4) adecuados para el marco seleccionado se pueden encontrar si corresponden a una de las arquitecturas clásicas. Como conjunto de datos (5) para demostración, tomaré Food-101 .

Modelo


Como modelo, utilizamos la clásica red neuronal VGG (más precisamente, VGG19 ). A pesar de algunas desventajas, este modelo demuestra una calidad bastante alta. Además, es fácil de analizar. En TensorFlow Slim, la descripción del modelo parece bastante compacta:

 import tensorflow as tf import tensorflow.contrib.slim as slim def vgg_19(inputs, num_classes, is_training, scope='vgg_19', weight_decay=0.0005): with slim.arg_scope([slim.conv2d], activation_fn=tf.nn.relu, weights_regularizer=slim.l2_regularizer(weight_decay), biases_initializer=tf.zeros_initializer(), padding='SAME'): with tf.variable_scope(scope, 'vgg_19', [inputs]): net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1') net = slim.max_pool2d(net, [2, 2], scope='pool1') net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2') net = slim.max_pool2d(net, [2, 2], scope='pool2') net = slim.repeat(net, 4, slim.conv2d, 256, [3, 3], scope='conv3') net = slim.max_pool2d(net, [2, 2], scope='pool3') net = slim.repeat(net, 4, slim.conv2d, 512, [3, 3], scope='conv4') net = slim.max_pool2d(net, [2, 2], scope='pool4') net = slim.repeat(net, 4, slim.conv2d, 512, [3, 3], scope='conv5') net = slim.max_pool2d(net, [2, 2], scope='pool5') # Use conv2d instead of fully_connected layers net = slim.conv2d(net, 4096, [7, 7], padding='VALID', scope='fc6') net = slim.dropout(net, 0.5, is_training=is_training, scope='drop6') net = slim.conv2d(net, 4096, [1, 1], scope='fc7') net = slim.dropout(net, 0.5, is_training=is_training, scope='drop7') net = slim.conv2d(net, num_classes, [1, 1], scope='fc8', activation_fn=None) net = tf.squeeze(net, [1, 2], name='fc8/squeezed') return net 

Los pesos para VGG19, entrenados en ImageNet y compatibles con TensorFlow, se descargan del repositorio en GitHub desde la sección Modelos pre-entrenados .

 mkdir data && cd data wget http://download.tensorflow.org/models/vgg_19_2016_08_28.tar.gz tar -xzf vgg_19_2016_08_28.tar.gz 

Datacet


Como muestra de capacitación y validación, utilizaremos el conjunto de datos público Food-101 , que contiene más de 100 mil imágenes de alimentos, divididas en 101 categorías.

Conjunto de datos Food-101

Descargue y descomprima el conjunto de datos:

 cd data wget http://data.vision.ee.ethz.ch/cvl/food-101.tar.gz tar -xzf food-101.tar.gz 

La canalización de datos en nuestra capacitación está diseñada para que del conjunto de datos necesitemos analizar lo siguiente:

  1. Lista de clases (categorías)
  2. Tutorial: una lista de rutas a imágenes y una lista de respuestas correctas
  3. Conjunto de validación: lista de rutas a imágenes y lista de respuestas correctas

Si es su conjunto de datos, entonces para el entrenamiento y la validación necesita romper los conjuntos usted mismo. Food-101 ya tiene esa partición, y esta información se almacena en el meta directorio.

 DATASET_ROOT = 'data/food-101/' train_data, val_data, classes = data.food101(DATASET_ROOT) num_classes = len(classes) 

Todas las funciones auxiliares responsables del procesamiento de datos se mueven a un archivo data.py separado:

data.py
 from os.path import join as opj import tensorflow as tf def parse_ds_subset(img_root, list_fpath, classes): ''' Parse a meta file with image paths and labels -> img_root: path to the root of image folders -> list_fpath: path to the file with the list (eg train.txt) -> classes: list of class names <- (list_of_img_paths, integer_labels) ''' fpaths = [] labels = [] with open(list_fpath, 'r') as f: for line in f: class_name, image_id = line.strip().split('/') fpaths.append(opj(img_root, class_name, image_id+'.jpg')) labels.append(classes.index(class_name)) return fpaths, labels def food101(dataset_root): ''' Get lists of train and validation examples for Food-101 dataset -> dataset_root: root of the Food-101 dataset <- ((train_fpaths, train_labels), (val_fpaths, val_labels), classes) ''' img_root = opj(dataset_root, 'images') train_list_fpath = opj(dataset_root, 'meta', 'train.txt') test_list_fpath = opj(dataset_root, 'meta', 'test.txt') classes_list_fpath = opj(dataset_root, 'meta', 'classes.txt') with open(classes_list_fpath, 'r') as f: classes = [line.strip() for line in f] train_data = parse_ds_subset(img_root, train_list_fpath, classes) val_data = parse_ds_subset(img_root, test_list_fpath, classes) return train_data, val_data, classes def imread_and_crop(fpath, inp_size, margin=0, random_crop=False): ''' Construct TF graph for image preparation: Read the file, crop and resize -> fpath: path to the JPEG image file (TF node) -> inp_size: size of the network input (eg 224) -> margin: cropping margin -> random_crop: perform random crop or central crop <- prepared image (TF node) ''' data = tf.read_file(fpath) img = tf.image.decode_jpeg(data, channels=3) img = tf.image.convert_image_dtype(img, dtype=tf.float32) shape = tf.shape(img) crop_size = tf.minimum(shape[0], shape[1]) - 2 * margin if random_crop: img = tf.random_crop(img, (crop_size, crop_size, 3)) else: # central crop ho = (shape[0] - crop_size) // 2 wo = (shape[0] - crop_size) // 2 img = img[ho:ho+crop_size, wo:wo+crop_size, :] img = tf.image.resize_images(img, (inp_size, inp_size), method=tf.image.ResizeMethod.AREA) return img def train_dataset(data, batch_size, epochs, inp_size, margin): ''' Prepare training data pipeline -> data: (list_of_img_paths, integer_labels) -> batch_size: training batch size -> epochs: number of training epochs -> inp_size: size of the network input (eg 224) -> margin: cropping margin <- (dataset, number_of_train_iterations) ''' num_examples = len(data[0]) iters = (epochs * num_examples) // batch_size def fpath_to_image(fpath, label): img = imread_and_crop(fpath, inp_size, margin, random_crop=True) return img, label dataset = tf.data.Dataset.from_tensor_slices(data) dataset = dataset.shuffle(buffer_size=num_examples) dataset = dataset.map(fpath_to_image) dataset = dataset.repeat(epochs) dataset = dataset.batch(batch_size, drop_remainder=True) return dataset, iters def val_dataset(data, batch_size, inp_size): ''' Prepare validation data pipeline -> data: (list_of_img_paths, integer_labels) -> batch_size: validation batch size -> inp_size: size of the network input (eg 224) <- (dataset, number_of_val_iterations) ''' num_examples = len(data[0]) iters = num_examples // batch_size def fpath_to_image(fpath, label): img = imread_and_crop(fpath, inp_size, 0, random_crop=False) return img, label dataset = tf.data.Dataset.from_tensor_slices(data) dataset = dataset.map(fpath_to_image) dataset = dataset.batch(batch_size, drop_remainder=True) return dataset, iters 


Entrenamiento modelo


El código de entrenamiento modelo consta de los siguientes pasos:

  1. Construcción de tuberías de datos de validación / tren
  2. Construcción de trenes / gráficos de validación (redes)
  3. Adjunto de la función de clasificación de pérdidas ( pérdida de entropía cruzada ) sobre el gráfico del tren
  4. El código necesario para calcular la precisión de las predicciones en la muestra de validación durante el entrenamiento
  5. Lógica para cargar escalas pre-entrenadas desde una instantánea
  6. Creación de diversas estructuras para la formación.
  7. El ciclo de aprendizaje en sí (optimización iterativa)

La última capa del gráfico se construye con el número requerido de neuronas y se excluye de la lista de parámetros cargados de la instantánea pre-entrenada.

Código de entrenamiento modelo
 import numpy as np import tensorflow as tf import tensorflow.contrib.slim as slim tf.logging.set_verbosity(tf.logging.INFO) import model import data ########################################################### ### Settings ########################################################### INPUT_SIZE = 224 RANDOM_CROP_MARGIN = 10 TRAIN_EPOCHS = 20 TRAIN_BATCH_SIZE = 64 VAL_BATCH_SIZE = 128 LR_START = 0.001 LR_END = LR_START / 1e4 MOMENTUM = 0.9 VGG_PRETRAINED_CKPT = 'data/vgg_19.ckpt' CHECKPOINT_DIR = 'checkpoints/vgg19_food' LOG_LOSS_EVERY = 10 CALC_ACC_EVERY = 500 ########################################################### ### Build training and validation data pipelines ########################################################### train_ds, train_iters = data.train_dataset(train_data, TRAIN_BATCH_SIZE, TRAIN_EPOCHS, INPUT_SIZE, RANDOM_CROP_MARGIN) train_ds_iterator = train_ds.make_one_shot_iterator() train_x, train_y = train_ds_iterator.get_next() val_ds, val_iters = data.val_dataset(val_data, VAL_BATCH_SIZE, INPUT_SIZE) val_ds_iterator = val_ds.make_initializable_iterator() val_x, val_y = val_ds_iterator.get_next() ########################################################### ### Construct training and validation graphs ########################################################### with tf.variable_scope('', reuse=tf.AUTO_REUSE): train_logits = model.vgg_19(train_x, num_classes, is_training=True) val_logits = model.vgg_19(val_x, num_classes, is_training=False) ########################################################### ### Construct training loss ########################################################### loss = tf.losses.sparse_softmax_cross_entropy( labels=train_y, logits=train_logits) tf.summary.scalar('loss', loss) ########################################################### ### Construct validation accuracy ### and related functions ########################################################### def calc_accuracy(sess, val_logits, val_y, val_iters): acc_total = 0.0 acc_denom = 0 for i in range(val_iters): logits, y = sess.run((val_logits, val_y)) y_pred = np.argmax(logits, axis=1) correct = np.count_nonzero(y == y_pred) acc_denom += y_pred.shape[0] acc_total += float(correct) tf.logging.info('Validating batch [{} / {}] correct = {}'.format( i, val_iters, correct)) acc_total /= acc_denom return acc_total def accuracy_summary(sess, acc_value, iteration): acc_summary = tf.Summary() acc_summary.value.add(tag="accuracy", simple_value=acc_value) sess._hooks[1]._summary_writer.add_summary(acc_summary, iteration) ########################################################### ### Define set of VGG variables to restore ### Create the Restorer ### Define init callback (used by monitored session) ########################################################### vars_to_restore = tf.contrib.framework.get_variables_to_restore( exclude=['vgg_19/fc8']) vgg_restorer = tf.train.Saver(vars_to_restore) def init_fn(scaffold, sess): vgg_restorer.restore(sess, VGG_PRETRAINED_CKPT) ########################################################### ### Create various training structures ########################################################### global_step = tf.train.get_or_create_global_step() lr = tf.train.polynomial_decay(LR_START, global_step, train_iters, LR_END) tf.summary.scalar('learning_rate', lr) optimizer = tf.train.MomentumOptimizer(learning_rate=lr, momentum=MOMENTUM) training_op = slim.learning.create_train_op( loss, optimizer, global_step=global_step) scaffold = tf.train.Scaffold(init_fn=init_fn) ########################################################### ### Create monitored session ### Run training loop ########################################################### with tf.train.MonitoredTrainingSession(checkpoint_dir=CHECKPOINT_DIR, save_checkpoint_secs=600, save_summaries_steps=30, scaffold=scaffold) as sess: start_iter = sess.run(global_step) for iteration in range(start_iter, train_iters): # Gradient Descent loss_value = sess.run(training_op) # Loss logging if iteration % LOG_LOSS_EVERY == 0: tf.logging.info('[{} / {}] Loss = {}'.format( iteration, train_iters, loss_value)) # Accuracy logging if iteration % CALC_ACC_EVERY == 0: sess.run(val_ds_iterator.initializer) acc_value = calc_accuracy(sess, val_logits, val_y, val_iters) accuracy_summary(sess, acc_value, iteration) tf.logging.info('[{} / {}] Validation accuracy = {}'.format( iteration, train_iters, acc_value)) 


Después de comenzar el entrenamiento, puede ver su progreso utilizando la utilidad TensorBoard, que viene incluida con TensorFlow y sirve para visualizar varias métricas y otros parámetros.

 tensorboard --logdir checkpoints/ 

Al final del entrenamiento en TensorBoard, vemos una imagen casi perfecta: una disminución en la pérdida de trenes y un aumento en la precisión de validación

TensorBoard pérdida y precisión

Como resultado, obtenemos la instantánea guardada en checkpoints/vgg19_food de checkpoints/vgg19_food , que usaremos durante la prueba de nuestro modelo ( inferencia ).

Prueba de modelo


Ahora prueba nuestro modelo. Para hacer esto:

  1. Construimos un nuevo gráfico diseñado específicamente para inferencia ( is_training=False )
  2. Cargue pesas entrenadas desde una instantánea
  3. Descargue y procese previamente la imagen de prueba de entrada.
  4. Conduzcamos la imagen a través de la red neuronal y obtengamos la predicción

inferencia.py
 import sys import numpy as np import imageio from skimage.transform import resize import tensorflow as tf import model ########################################################### ### Settings ########################################################### CLASSES_FPATH = 'data/food-101/meta/labels.txt' INP_SIZE = 224 # Input will be cropped and resized CHECKPOINT_DIR = 'checkpoints/vgg19_food' IMG_FPATH = 'data/food-101/images/bruschetta/3564471.jpg' ########################################################### ### Get all class names ########################################################### with open(CLASSES_FPATH, 'r') as f: classes = [line.strip() for line in f] num_classes = len(classes) ########################################################### ### Construct inference graph ########################################################### x = tf.placeholder(tf.float32, (1, INP_SIZE, INP_SIZE, 3), name='inputs') logits = model.vgg_19(x, num_classes, is_training=False) ########################################################### ### Create TF session and restore from a snapshot ########################################################### sess = tf.Session() snapshot_fpath = tf.train.latest_checkpoint(CHECKPOINT_DIR) restorer = tf.train.Saver() restorer.restore(sess, snapshot_fpath) ########################################################### ### Load and prepare input image ########################################################### def crop_and_resize(img, input_size): crop_size = min(img.shape[0], img.shape[1]) ho = (img.shape[0] - crop_size) // 2 wo = (img.shape[0] - crop_size) // 2 img = img[ho:ho+crop_size, wo:wo+crop_size, :] img = resize(img, (input_size, input_size), order=3, mode='reflect', anti_aliasing=True, preserve_range=True) return img img = imageio.imread(IMG_FPATH) img = img.astype(np.float32) img = crop_and_resize(img, INP_SIZE) img = img[None, ...] ########################################################### ### Run inference ########################################################### out = sess.run(logits, feed_dict={x:img}) pred_class = classes[np.argmax(out)] print('Input: {}'.format(IMG_FPATH)) print('Prediction: {}'.format(pred_class)) 


Inferencia

Todo el código, incluidos los recursos para crear y ejecutar un contenedor Docker con todas las versiones necesarias de las bibliotecas, se encuentra en este repositorio ; en el momento de leer el artículo, el código en el repositorio puede tener actualizaciones.

En el taller "Aprendizaje automático y redes neuronales para desarrolladores" analizaré otras tareas del aprendizaje automático y los estudiantes presentarán sus proyectos al final de la sesión intensiva.

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


All Articles