Transfer Learning: So trainieren Sie schnell ein neuronales Netzwerk mit Ihren Daten

Maschinelles Lernen wird immer zugänglicher, es gibt mehr Möglichkeiten, diese Technologie mithilfe von „Standardkomponenten“ anzuwenden. Mit Transfer Learning können Sie beispielsweise die Erfahrungen bei der Lösung eines Problems nutzen, um ein anderes, ähnliches Problem zu lösen. Das neuronale Netzwerk wird zuerst mit einer großen Datenmenge und dann mit dem Zielsatz trainiert.

Lebensmittelerkennung

In diesem Artikel werde ich Ihnen am Beispiel der Erkennung von Bildern mit Lebensmitteln die Verwendung der Transfer-Lernmethode erläutern. Ich werde auf dem Workshop " Maschinelles Lernen und Neuronale Netze für Entwickler" über andere Tools für maschinelles Lernen sprechen.

Wenn wir vor der Aufgabe der Bilderkennung stehen, können Sie den vorgefertigten Service nutzen. Wenn Sie das Modell jedoch anhand Ihres eigenen Datensatzes trainieren müssen, müssen Sie dies selbst tun.

Für typische Aufgaben wie die Bildklassifizierung können Sie die vorgefertigte Architektur (AlexNet, VGG, Inception, ResNet usw.) verwenden und das neuronale Netzwerk auf Ihre Daten trainieren. Es gibt bereits Implementierungen solcher Netzwerke unter Verwendung verschiedener Frameworks. In diesem Stadium können Sie eines davon als Black Box verwenden, ohne sich eingehend mit dessen Funktionsprinzip zu befassen.

Tiefe neuronale Netze erfordern jedoch große Datenmengen für die Konvergenz des Lernens. Und oft gibt es in unserer speziellen Aufgabe nicht genügend Daten, um alle Schichten des neuronalen Netzwerks richtig zu trainieren. Transfer Learning löst dieses Problem.

Transferlernen zur Bildklassifizierung


Die neuronalen Netze, die zur Klassifizierung verwendet werden, enthalten normalerweise N Ausgangsneuronen in der letzten Schicht, wobei N die Anzahl der Klassen ist. Ein solcher Ausgabevektor wird als eine Menge von Wahrscheinlichkeiten der Zugehörigkeit zu einer Klasse behandelt. Bei unserer Aufgabe, Lebensmittelbilder zu erkennen, kann die Anzahl der Klassen von der im Originaldatensatz abweichen. In diesem Fall müssen wir diese letzte Schicht vollständig wegwerfen und eine neue mit der richtigen Anzahl von Ausgangsneuronen einfügen

Lernen übertragen

Oft wird am Ende von Klassifizierungsnetzwerken eine vollständig verbundene Schicht verwendet. Da wir diese Schicht ersetzt haben, funktioniert es nicht, vorab trainierte Gewichte zu verwenden. Sie müssen ihn von Grund auf neu trainieren und seine Gewichte mit zufälligen Werten initialisieren. Wir laden Gewichte für alle anderen Ebenen aus einem vorab trainierten Schnappschuss.

Es gibt verschiedene Strategien zur Weiterbildung des Modells. Wir werden Folgendes verwenden: Wir werden das gesamte Netzwerk von Ende zu Ende ( Ende zu Ende ) trainieren und die vorab trainierten Gewichte nicht korrigieren, damit sie sich ein wenig anpassen und sich an unsere Daten anpassen können. Dieser Vorgang wird als Feinabstimmung bezeichnet .

Strukturelle Komponenten


Um das Problem zu lösen, benötigen wir die folgenden Komponenten:

  1. Beschreibung des neuronalen Netzwerkmodells
  2. Lernpipeline
  3. Interferenzpipeline
  4. Vorgeübte Gewichte für dieses Modell
  5. Daten für Training und Validierung

Komponenten

In unserem Beispiel nehme ich die Komponenten (1), (2) und (3) aus meinem eigenen Repository , das den leichtesten Code enthält - Sie können es leicht herausfinden, wenn Sie möchten. Unser Beispiel wird auf dem beliebten TensorFlow- Framework implementiert. Vorgeübte Gewichte (4), die für das ausgewählte Gerüst geeignet sind, können gefunden werden, wenn sie einer der klassischen Architekturen entsprechen. Als Datensatz (5) zur Demonstration werde ich Food-101 nehmen .

Modell


Als Modell verwenden wir das klassische neuronale VGG- Netzwerk (genauer gesagt VGG19 ). Trotz einiger Nachteile weist dieses Modell eine relativ hohe Qualität auf. Darüber hinaus ist es einfach zu analysieren. Bei TensorFlow Slim sieht die Modellbeschreibung recht kompakt aus:

 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 

Die auf ImageNet trainierten und mit TensorFlow kompatiblen Gewichte für VGG19 werden aus dem Repository auf GitHub im Abschnitt Vorgefertigte Modelle heruntergeladen.

 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


Als Trainings- und Validierungsbeispiel verwenden wir den öffentlichen Food-101- Datensatz, der mehr als 100.000 Lebensmittelbilder enthält und in 101 Kategorien unterteilt ist.

Food-101-Datensatz

Laden Sie den Datensatz herunter und entpacken Sie ihn:

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

Die Datenpipeline in unserem Training ist so konzipiert, dass wir aus dem Datensatz Folgendes analysieren müssen:

  1. Liste der Klassen (Kategorien)
  2. Tutorial: Eine Liste der Pfade zu Bildern und eine Liste der richtigen Antworten
  3. Validierungssatz: Liste der Pfade zu Bildern und Liste der richtigen Antworten

Wenn Ihr Datensatz, dann müssen Sie für Zug und Validierung die Sätze selbst brechen. Food-101 hat bereits eine solche Partition, und diese Informationen werden im meta Verzeichnis gespeichert.

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

Alle für die Datenverarbeitung verantwortlichen Hilfsfunktionen werden in eine separate Datei data.py :

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 


Modelltraining


Der Modell-Trainingscode besteht aus folgenden Schritten:

  1. Bau von Zug- / Validierungsdaten -Pipelines
  2. Erstellen von Zug- / Validierungsgraphen (Netzwerken)
  3. Anhängen der Klassifizierungsfunktion von Verlusten ( Kreuzentropieverlust ) über Zuggraph
  4. Der Code, der benötigt wird, um die Genauigkeit der Vorhersagen auf der Validierungsprobe während des Trainings zu berechnen
  5. Logik zum Laden vorab trainierter Waagen aus einem Schnappschuss
  6. Schaffung verschiedener Strukturen für das Training
  7. Der Lernzyklus selbst (iterative Optimierung)

Die letzte Ebene des Diagramms besteht aus der erforderlichen Anzahl von Neuronen und wird aus der Liste der Parameter ausgeschlossen, die aus dem vorab trainierten Schnappschuss geladen wurden.

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


Nach dem Start des Trainings können Sie den Fortschritt mithilfe des TensorBoard-Dienstprogramms anzeigen, das im Lieferumfang von TensorFlow enthalten ist und zur Visualisierung verschiedener Metriken und anderer Parameter dient.

 tensorboard --logdir checkpoints/ 

Am Ende des Trainings bei TensorBoard sehen wir ein nahezu perfektes Bild: eine Verringerung des Zugverlusts und eine Erhöhung der Validierungsgenauigkeit

TensorBoard Verlust und Genauigkeit

Als Ergebnis erhalten wir den gespeicherten Snapshot in checkpoints/vgg19_food , den wir beim Testen unseres Modells verwenden ( Inferenz ).

Modellprüfung


Testen Sie jetzt unser Modell. Dafür:

  1. Wir konstruieren einen neuen Graphen, der speziell für die Inferenz entwickelt wurde ( is_training=False ).
  2. Laden Sie trainierte Gewichte aus einem Schnappschuss
  3. Laden Sie das eingegebene Testbild herunter und verarbeiten Sie es vor.
  4. Lassen Sie uns das Bild durch das neuronale Netzwerk fahren und die Vorhersage erhalten

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


Folgerung

Der gesamte Code, einschließlich der Ressourcen zum Erstellen und Ausführen eines Docker-Containers mit allen erforderlichen Versionen von Bibliotheken, befindet sich in diesem Repository. Zum Zeitpunkt des Lesens des Artikels enthält der Code im Repository möglicherweise Aktualisierungen.

Beim Workshop „Maschinelles Lernen und Neuronale Netze für Entwickler“ werde ich andere Aufgaben des maschinellen Lernens analysieren und die Schüler werden ihre Projekte am Ende der intensiven Sitzung vorstellen.

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


All Articles