Компьютерное зрение для глубокого обучения Python (средний уровень)

глубокое обучение Python

Это пятый день моего участия в августовском испытании обновлений, подробности о мероприятии:Испытание августовского обновления

Эта статья продолжает предыдущую статью "Компьютерное зрение для глубокого обучения Python (часть 1)".

Использование предварительно обученных сверточных нейронных сетей

5.3 Using a pretrained convnet

Для небольших наборов данных изображений эффективным методом, который мы обычно используем, является использование предварительно обученной сети для построения модели глубокого обучения. Предварительно обученная сеть — это сеть, предварительно обученная на большом наборе данных (обычно обученная на задаче классификации больших изображений). Если данные, используемые в наборе для предварительного обучения, достаточно велики, а модель достаточно обобщена, пространственная иерархия, изученная сетью для предварительного обучения, может быть эффективно использована в качестве общей модели для отражения реального визуального мира и, таким образом, может быть использована. для множества различных задач компьютерного зрения, даже если новая задача вообще не имеет ничего общего с исходной задачей.

Например, мы можем решить проблему классификации кошек и собак с помощью сети, предварительно обученной на ImageNet (в этом наборе данных 1,4 миллиона изображений, 1000 различных категорий, в основном животные и различные предметы быта). Мы будем использовать архитектуру VGG16.

Существует два способа использования предварительно обученной сети:Извлечение признаков(извлечение признаков) иТочная настройка модели(тонкая настройка).

Извлечение признаков

Извлечение признаков заключается в использовании представлений, изученных предыдущей сетью, для извлечения необходимых признаков из новых образцов, ввода нового классификатора и обучения с нуля.

В предыдущем примере сверточной нейронной сети мы знаем, что модель, которую мы используем для классификации изображений, можно разделить на две части:

  • Сверточная база: предыдущие слои свертки и объединения;
  • Классификатор: последний слой плотной связи;

Итак, для сверточной нейронной сетиИзвлечение признаковОн заключается в извлечении сверточной базы ранее обученной сети, вводе новых данных для запуска, а затем использовании ее вывода для обучения нового классификатора:

保持卷积基不变,改变分类器

Обратите внимание, что повторно используются только сверточные базы, а не классификаторы. База свертки используется для извлечения признаков, которые могут быть одинаковыми, но поскольку классификация каждой задачи различна, следует использовать разные классификаторы, а положение признаков в некоторых задачах полезно, а карту признаков мы переносим в Dense Эти признаки положения теряются при добавлении слоя, поэтому не все задачи просто классифицируются Dense, поэтому без мозгов классификатор применять не стоит.

В предварительно обученной сети общность представления признаков, которые можно извлечь, зависит от ее глубины. Меньшее количество слоев является более общим (например, цвет изображения, края, текстуры и т. д.), а большее количество слоев будет содержать более абстрактную информацию (например, имеет кошачьи глаза и т. д.). Таким образом, если исходная проблема, решаемая сетью предварительного обучения, слишком далека от проблемы, с которой мы имеем дело в настоящее время, то это только потому, что следует использовать первые несколько слоев, а не всю сверточную базу.

Теперь для практики воспользуемся предварительно обученной на ImageNet моделью VGG16 для решения задачи классификации кошек и собак, базу свертки оставим без изменений и изменим классификатор.

Модель VGG16 встроена в Keras, просто используйте ее напрямую:

from tensorflow.keras.applications import VGG16

conv_base = VGG16(weights='imagenet',        # 指定模型初始化的权重检查点
                  include_top=False,         # 是否包含最后的密集连接层分类器
                  input_shape=(150, 150, 3)) # 输入的形状,不传的话能处理任意形状的输入

Скачать модель можно отсюда:GitHub.com/ Very OL T/…

Если вы позволите ему загружаться медленно, вы можете рассмотреть возможность загрузки и установки вручную. Обратитесь к исходному коду vgg16:/usr/local/lib/python3.7/site-packages/keras_applications/vgg16.py, и обнаружил, что он вызывает get_file для получения модели:

weights_path = keras_utils.get_file(
    'vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5',
    WEIGHTS_PATH_NO_TOP,
    cache_subdir='models',
    file_hash='6d6bbae143d832006294945121d1f1fc')

Смотрите здесь он из подкаталогаmodelsпрочитать модель в, и этоkeras_utils.get_fileэто/usr/local/lib/python3.7/site-packages/tensorflow_core/python/keras/utils/data_utils.pyФункция get_file около строки 150 написана в комментариях к документации, а вещи помещены в дефолтные~/.kerasэтого каталога.

Короче говоря, это скачать модель и поставить ее в~/.keras/modelsПросто хорошо.

Также обратите внимание, добавлять ли топ (финальный классификатор), размер загружаемой модели все равно сильно отличается:

不同配置的VGG16预训练模型的大小

Короче говоря, в конце вы получите модель conv_base, которая проста для понимания и использует то, что мы узнали ранее:

conv_base.summary()
Model: "vgg16"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
...  (篇幅限制,中间具体信息省略了)
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0
_________________________________________________________________

Обратите внимание, что передний вход настроен так, как мы хотим.(150, 150, 3), окончательный вывод(4, 4, 512), сзади мы должны подключить собственный классификатор. Есть два способа:

  1. Запустим наш текущий набор данных на этой сверточной базе, затем поместим результат в массив Numpy, сохраним его на диск, а затем используем этот массив в качестве входных данных и бросим его в плотно связанную сеть для обучения. Этот метод относительно прост, и ему нужно только вычислить часть базы свертки, которая потребляет больше всего. Однако этот подход не может использовать увеличение данных.
  2. Расширение conv_base, добавление к нему слоя Dense, а затем запуск всей сети от начала до конца на входных данных может использовать увеличение данных, но это требует больших вычислительных ресурсов.

Сначала делаем первый.

Быстрое извлечение признаков без увеличения данных

В общем, этот подход сохраняет выходные данные наших данных, проходящих через conv_base, а затем передает эти выходные данные в новую модель в качестве входных данных.

Здесь мы по-прежнему используем ImageDataGenerator для извлечения изображений и меток в массивы Numpy. Затем вызовите метод прогнозирования conv_base, чтобы извлечь функции из предварительно обученной модели.

# 使用预训练的卷积基提取特征

import os
import numpy as np
from tensorflow.keras.preprocessing.image import ImageDataGenerator

base_dir = '/Volumes/WD/Files/dataset/dogs-vs-cats/cats_and_dogs_small'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')

datagen = ImageDataGenerator(rescale=1./255)
batch_size = 20

def extract_features(directory, sample_count):
    features = np.zeros(shape=(sample_count, 4, 4, 512))    # 这个要符合之前 conv_base.summary() 最后一层的输出的形状
    labels = np.zeros(shape=(sample_count))
    generator = datagen.flow_from_directory(
        directory,
        target_size=(150, 150),
        batch_size=batch_size,
        class_mode='binary')
    i = 0
    for inputs_batch, labels_batch in generator:    # 一批一批加数据
        features_batch = conv_base.predict(inputs_batch)
        features[i * batch_size : (i + 1) * batch_size] = features_batch
        labels[i * batch_size : (i + 1) * batch_size] = labels_batch
        i += 1
        if i * batch_size >= sample_count:    # generator 会生成无限的数据哦,break 要自己控制
            break
    return features, labels

train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)

вывод:

Found 2000 images belonging to 2 classes.

Found 1000 images belonging to 2 classes.

Found 1000 images belonging to 2 classes.

После этого мы собираемся подключить классификатор слоя плотных соединений, поэтому давайте сначала сгладим тензор:

train_features = np.reshape(train_features, (2000, 4 * 4 * 512))
validation_features = np.reshape(validation_features, (1000, 4 * 4 * 512))
test_features = np.reshape(test_features, (1000, 4 * 4 * 512))

Затем нужно сделать плотносвязный классификатор или использовать регуляризацию отсева:

from tensorflow.keras import models
from tensorflow.keras import layers
from tensorflow.keras import optimizers

model = models.Sequential()
model.add(layers.Dense(256, activation='relu', input_dim=4 * 4 * 512))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
              loss='binary_crossentropy',
              metrics=['acc'])

history = model.fit(train_features, train_labels,
                    epochs=30,
                    batch_size=20,
                    validation_data=(validation_features, validation_labels))
Train on 2000 samples, validate on 1000 samples
Epoch 1/30
2000/2000 [==============================] - 3s 1ms/sample - loss: 0.5905 - acc: 0.6840 - val_loss: 0.4347 - val_acc: 0.8430
......
Epoch 30/30
2000/2000 [==============================] - 1s 687us/sample - loss: 0.0889 - acc: 0.9715 - val_loss: 0.2401 - val_acc: 0.9010

Глядя на результаты, нарисуйте кривую тренировочного процесса:

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

plt.plot(epochs, acc, 'bo-', label='Training acc')
plt.plot(epochs, val_acc, 'rs-', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo-', label='Training loss')
plt.plot(epochs, val_loss, 'rs-', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

png

png

Видно, что эффект все еще очень хороший, точность близка к 90%. Но проблемы все же есть, потому что улучшения данных нет, переоснащение по-прежнему серьезно, и оно в основном переоснащение с самого начала. Для такого небольшого набора данных изображений это, как правило, невозможно без улучшения данных.

Извлечение признаков с увеличением данных

Второй способ — расширить conv_base, добавить после него слой Dense и запустить всю сеть сквозным образом на входных данных.

Этот метод рассчитывает стоимость! довольно часто! высокий! , в основном может работать только с GPU. Это используется без графического процессора.

В Keras мы можем добавить модель к модели Sequential точно так же, как добавляя слои:

# 在卷积基的基础上添加密集连接分类器

from tensorflow.keras import models
from tensorflow.keras import layers

model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

Посмотрите, как выглядит модель:

model.summary()

вывод:

Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
vgg16 (Model)                (None, 4, 4, 512)         14714688  
_________________________________________________________________
flatten (Flatten)            (None, 8192)              0         
_________________________________________________________________
dense_2 (Dense)              (None, 256)               2097408   
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 257       
=================================================================
Total params: 16,812,353
Trainable params: 16,812,353
Non-trainable params: 0
_________________________________________________________________

Обратите внимание, что при использовании этого метода использования предварительно обученной модели обязательнозамороженный сверточный базис, то есть сказать сети не обновлять параметры сверточной базы в процессе обучения! Это очень важно, если этого не сделать, то предобученная модель будет уничтожена, в конце концов это равносильно обучению с нуля, что потеряет смысл использования предобучения.

# 冻结卷积基
conv_base.trainable = False

После этого при обучении модели будут постоянно обновляться только веса плотного слоя:

model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
vgg16 (Model)                (None, 4, 4, 512)         14714688  
_________________________________________________________________
flatten (Flatten)            (None, 8192)              0         
_________________________________________________________________
dense_2 (Dense)              (None, 256)               2097408   
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 257       
=================================================================
Total params: 16,812,353
Trainable params: 2,097,665
Non-trainable params: 14,714,688
_________________________________________________________________

Далее вы можете выполнить аугментацию данных и обучить модель:

# 利用冻结的卷积基端到端地训练模型

from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras import optimizers

train_datagen = ImageDataGenerator(
    rescale=1./255,
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    fill_mode='nearest')

test_datagen = ImageDataGenerator(rescale=1./255)    # 注意 test 不增强

train_generator = train_datagen.flow_from_directory(
    train_dir,
    target_size=(150, 150),
    batch_size=20,
    class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
    validation_dir,
    target_size=(150, 150),
    batch_size=20,
    class_mode='binary')

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=2e-5),
              metrics=['acc'])

history = model.fit_generator(
    train_generator,
    steps_per_epoch=100,
    epochs=30,
    validation_data=validation_generator,
    validation_steps=50)

P.S. Этот работает на моем процессоре, один раунд занимает около 15 минут, 30 раундов, и я сдался. Я запустил это на Kaggle, и один раунд занял всего 30 секунд ?:

Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
Epoch 1/30
100/100 [==============================] - 30s 300ms/step - loss: 0.5984 - acc: 0.6855 - val_loss: 0.4592 - val_acc: 0.8250
......
Epoch 30/30
100/100 [==============================] - 30s 302ms/step - loss: 0.2693 - acc: 0.8900 - val_loss: 0.2401 - val_acc: 0.9070

运行结果图

Тонкая настройка, тонкая настройка модели

(Я предпочитаю термин «тонкая настройка», но «тонкая настройка» не имеет внутреннего вкуса.)

Дополнения для тонкой настройки извлекают признаки и дополнительно оптимизируют модель. Что делает тонкая настройка, так это помещает верхние (обратные) слои сверточной базыоттепель, обучите размороженные слои вместе с вновь добавленной частью (полностью связанный классификатор). Этот подход немного корректирует высокоуровневые абстрактные представления в предварительно обученной модели (т. е. те, что близки к верхнему уровню), чтобы сделать их более подходящими для рассматриваемой проблемы.

Fine-tuning 示意图,微调了 VGG16 的最后一个卷积块

Обратите внимание, что окончательный полносвязный классификатор должен быть обучен перед переходом к блоку Conv в верхней части базы Fine-tune convolution, иначе результаты предварительного обучения будут полностью уничтожены.

Итак, тонкая настройка должна выполнять следующие шаги:

  1. Добавляем нашу собственную сеть (типа классификатора) в уже обученную сеть (базовую сеть, базовую сеть);
  2. Заморозить базовую сеть;
  3. Приучите себя добавлять эту часть;
  4. Разморозить некоторые слои базовой сети;
  5. Совместная подготовка талого слоя и собственной части;

Первые три шага аналогичны извлечению признаков, поэтому мы начнем с четвертого шага. Сначала взгляните на наш сверточный базис VGG16:

conv_base.summary()
Model: "vgg16"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 150, 150, 3)]     0         
_________________________________________________________________

...  (篇幅限制,中间具体信息省略了)
_________________________________________________________________

block5_pool (MaxPooling2D)   (None, 4, 4, 512)         0         
=================================================================
Total params: 14,714,688
Trainable params: 0
Non-trainable params: 14,714,688
_________________________________________________________________

Мы разморозим block5_conv1, block5_conv2 и block5_conv3, чтобы завершить Fine-turning:

# 冻结直到某一层的所有层

conv_base.trainable = True

set_trainable = False
for layer in conv_base.layers:
    if layer.name == 'block5_conv1':
        set_trainable = True
    if set_trainable:
        layer.trainable = True
    else:
        layer.trainable = False
        
# 微调模型

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-5),    # 这里用的学习率(lr)很小,是希望让微调的三层表示变化范围不要太大
              metrics=['acc'])

history = model.fit_generator(
    train_generator,
    steps_per_epoch=100,
    epochs=100,
    validation_data=validation_generator,
    validation_steps=50)

Это также работает на Kaggle:

在 Kaggle 训练的最后几轮输出

训练历史图线

Я не знаю, почему то, что я сделал, так отличается от книги. Я перечитывал ее снова и снова и не нашел никаких проблем. Даже если я использую блокнот, данный автором для запуска, я не знать, где проблема. все равно? Быть по сему.

Наконец, давайте посмотрим на результаты на тестовом наборе:

test_generator = test_datagen.flow_from_directory(
        test_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')
test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print('test acc:', test_acc)

Результаты в книге точны на 97%, у меня менее 95%, извините.