Это пятый день моего участия в августовском испытании обновлений, подробности о мероприятии:Испытание августовского обновления
Эта статья продолжает предыдущую статью "Компьютерное зрение для глубокого обучения 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
Просто хорошо.
Также обратите внимание, добавлять ли топ (финальный классификатор), размер загружаемой модели все равно сильно отличается:
Короче говоря, в конце вы получите модель 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)
, сзади мы должны подключить собственный классификатор. Есть два способа:
- Запустим наш текущий набор данных на этой сверточной базе, затем поместим результат в массив Numpy, сохраним его на диск, а затем используем этот массив в качестве входных данных и бросим его в плотно связанную сеть для обучения. Этот метод относительно прост, и ему нужно только вычислить часть базы свертки, которая потребляет больше всего. Однако этот подход не может использовать увеличение данных.
- Расширение 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()
Видно, что эффект все еще очень хороший, точность близка к 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
Тонкая настройка, тонкая настройка модели
(Я предпочитаю термин «тонкая настройка», но «тонкая настройка» не имеет внутреннего вкуса.)
Дополнения для тонкой настройки извлекают признаки и дополнительно оптимизируют модель. Что делает тонкая настройка, так это помещает верхние (обратные) слои сверточной базыоттепель, обучите размороженные слои вместе с вновь добавленной частью (полностью связанный классификатор). Этот подход немного корректирует высокоуровневые абстрактные представления в предварительно обученной модели (т. е. те, что близки к верхнему уровню), чтобы сделать их более подходящими для рассматриваемой проблемы.
Обратите внимание, что окончательный полносвязный классификатор должен быть обучен перед переходом к блоку Conv в верхней части базы Fine-tune convolution, иначе результаты предварительного обучения будут полностью уничтожены.
Итак, тонкая настройка должна выполнять следующие шаги:
- Добавляем нашу собственную сеть (типа классификатора) в уже обученную сеть (базовую сеть, базовую сеть);
- Заморозить базовую сеть;
- Приучите себя добавлять эту часть;
- Разморозить некоторые слои базовой сети;
- Совместная подготовка талого слоя и собственной части;
Первые три шага аналогичны извлечению признаков, поэтому мы начнем с четвертого шага. Сначала взгляните на наш сверточный базис 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:
Я не знаю, почему то, что я сделал, так отличается от книги. Я перечитывал ее снова и снова и не нашел никаких проблем. Даже если я использую блокнот, данный автором для запуска, я не знать, где проблема. все равно? Быть по сему.
Наконец, давайте посмотрим на результаты на тестовом наборе:
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%, извините.