Текст переведен сThe Keras Blog. Цель перевода в основном состоит в том, чтобы замедлить ваше собственное обучение, возможно, это поможет и другим. Далее идет перевод.
В этом руководстве мы покажем несколько простых, но эффективных методов, позволяющих создавать мощные модели классификации изображений с очень небольшим количеством обучающих выборок — от нескольких сотен до нескольких тысяч изображений на класс для распознавания.
Мы будем использовать следующий метод:
- Обучите небольшую сеть с нуля в качестве эталона для сравнения
- Функция узкого места с использованием предварительно обученной сети
- Точная настройка последних нескольких слоев предварительно обученной сети
Это подводит нас к следующим функциям Keras:
-
fit_generator
Используйте генераторы данных Python для обучения моделей Keras -
ImageDataGenerator
улучшение изображения в реальном времени - Замораживание слоев и тонкая настройка модели
- ...разное
Примечание. Все примеры были обновлены до Keras 2.0 API 14 марта 2017 г. Для запуска этих кодов требуется номер версии Keras больше или равный 2.0.0.
Наша установка: всего 2000 образцов (по 1000 на класс)
Начнем со следующих настроек:
- На машине установлены Keras, SciPy, PIL. Вы также можете использовать его, если у вас есть видеокарта NVIDIA (требуется установка cuDNN), но это не обязательно, так как мы имеем дело с очень небольшим количеством изображений.
- Каталог обучающих наборов и каталог проверочных наборов, оба содержат подкаталоги, упорядоченные по категориям, и эти подкаталоги содержат изображения .png или .jpg:
data/
train/
dogs/
dog001.jpg
dog002.jpg
...
cats/
cat001.jpg
cat002.jpg
...
validation/
dogs/
dog001.jpg
dog002.jpg
...
cats/
cat001.jpg
cat002.jpg
...
Для того, чтобы получить от сотен до тысяч фотографий в интересующих вас категориях, вы можете использоватьFlickr APIЗагрузите изображения определенных тегов, его протокол более дружелюбен.
В нашем примере используются два типа изображений, которые происходят изKaggle: 1000 кошек и 1000 собак (хотя исходный набор данных содержит 12 500 кошек и 12 500 собак, мы используем только 1000 лучших изображений каждого класса). Затем 400 изображений каждой категории берутся в качестве проверочного набора для оценки модели.
Это оставляет очень мало изображений для изучения, что непросто для задачи классификации изображений. Это сложная проблема машинного обучения, но также и очень реалистичная: в реальном мире также дорого или даже невозможно получать только небольшие наборы данных (например, медицинское обучение). Получение максимальной отдачи от ограниченных данных является важным навыком для квалифицированного специалиста по данным.
Насколько сложна эта проблема? Спустя более двух лет после того, как Kaggle начал свой конкурс по классификации кошек и собак (всего 25 000 изображений), появились следующие комментарии:
В неофициальном опросе много лет назад эксперты по компьютерной графике утверждали, что классификатор с точностью выше 60 процентов был бы почти невозможен без значительных достижений в современных технологиях. Для справки, классификатор с точностью 60% будет12-image HIPВероятность угадывания увеличилась с 1/4096 до 1/459. В текущем материале считается, что показатель правильной классификации машин по этой задаче может достигать 80%.
В результатах соревнований лучшие игроки используют современные технологии глубокого обучения, а точность может достигать 98%. В нашем случае мы ограничиваемся только 8% набора данных, задача будет сложнее.
Роль глубокого обучения в проблемах с небольшими данными
Я часто слышу утверждение, что «глубокое обучение полезно только в том случае, если у вас есть огромные объемы данных». Хотя это утверждение не совсем неверно, оно несколько вводит в заблуждение. Конечно, глубокое обучение имеет возможность автоматически извлекать признаки из данных, что обычно возможно только при большом количестве доступных данных, особенно при высокой размерности входных выборок, таких как изображения. Однако сверточные нейронные сети — один из важных алгоритмов глубокого обучения — являются лучшими моделями для большинства проблем восприятия, таких как классификация изображений, даже когда данных для обучения мало. Обучение сверточной нейронной сети с нуля на небольшом наборе данных изображений может по-прежнему достигать приличных результатов без ручного проектирования. Сверточные нейронные сети достаточно хороши, чтобы стать подходящим инструментом для такого рода задач.
Но если сделать еще один шаг вперед, модели глубокого обучения, естественно, подходят для изменяющихся сценариев приложений: как вы увидите в этой статье, вы можете взять модель классификации изображений или распознавания речи, обученную на большом наборе данных, и применить ее к совершенно другому набору данных. проблема. Особенно в области компьютерной графики многие предварительно обученные модели (обычно в наборе данных ImageNet) общедоступны для загрузки, и вы можете использовать их для создания мощных моделей изображений на очень небольшом количестве данных.
Предварительная обработка данных и увеличение данных
Чтобы лучше использовать ограниченные выборки, вам нужно улучшить изображение с помощью серии случайных преобразований, чтобы модель больше не использовала одно и то же изображение. Это предотвращает переоснащение, и модель лучше обобщается.
В Керасе это можно сделать черезkeras.preprocessing.image.ImageDataGenerator
класс для реализации. Этот класс предоставляет следующие функции:
- Установить случайное преобразование и нормализацию изображений во время обучения
- пройти через
.flow(data, labels)
илиflow_from_directory(directory)
для создания экземпляра генератора пакетных улучшенных изображений. Затем эти генераторы можно использовать какfit_generator
, ``evaluate_generator和
Входные данные для методов модели Keras, таких как predict_generator`.
Пример выглядит следующим образом:
from keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
rescale=1./255,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')
Вот лишь некоторые из настраиваемых элементов (подробнее см.Документация. Давайте посмотрим на значение вышеуказанных настраиваемых элементов:
-
rotation_range
Это значение в градусах от 0 до 180, указывающее диапазон поворота изображения. -
width_shift
иheight_shift
это процент ширины и длины, представляющий диапазон перевода изображения -
rescale
Указывает множитель, который следует применить к изображению перед другой обработкой. Наше исходное изображение состоит из значений RGB от 0 до 255. Эта модель значений слишком велика для обработки (при обычных скоростях обучения), поэтому значения необходимо масштабировать от 0 до 1 с коэффициентом масштабирования 1/ 255. между -
shear_range
Указывает случайное применениеошибка -
zoom_range
Представляет случайное масштабирование изображения. -
horizontal_flip
Представляет случайное переворачивание изображения по горизонтали, когда нет предположения о горизонтальной асимметрии (например, изображения реального мира). -
fill_mode
Указывает стратегию заполнения для новых пикселей после поворота или горизонтального/вертикального перемещения.
Теперь мы используем этот инструмент для создания некоторых изображений во временной папке и интуитивно чувствуем эффект этих стратегий улучшения Для отображения изображений мы не используем здесь числовые коэффициенты масштабирования.
from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img
datagen = ImageDataGenerator(
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')
img = load_img('data/train/cats/cat.0.jpg') #这是一个 PIL 图片
x = img_to_array(img) # 这是一个 Numpy 数组,形状是 (3, 150, 150)
x = x.reshape((1,) + x.shape) # 这是一个 Numpy 数组,形状是 (1, 3, 150, 150)
# 下面的 .flow() 命令生成一批随机变换的图片
# 然后保存到 `preview/` 目录
i = 0
for batch in datagen.flow(x, batch_size=1,
save_to_dir='preview', save_prefix='cat', save_format='jpeg'):
i += 1
if i > 20:
break # 否则生成器会一直循环下去
Ниже приведено изображение стратегии увеличения данных.
Обучение небольшой сверточной сети с нуля: точность 80 % в 40 строках кода
Сверточные сети являются подходящим инструментом для решения задач классификации изображений, и мы сначала пытаемся обучить их в качестве основы. Поскольку выборок мало, первая проблема — переобучение. Переобучение происходит, когда модель использует слишком мало выборок для обобщения новых данных, то есть модель начинает использовать некоторые нерелевантные функции для прогнозирования. Например, человек видел только 3 изображения лесорубов и 3 изображения моряков.На этих изображениях только один лесоруб носит шляпу.Вы можете подумать, что ношение шляпы характерно для лесорубов, а не для моряков, так что это было бы плохой классификатор дровосека/моряка.
Расширение данных — один из способов преодоления переобучения, но одного расширения данных недостаточно, поскольку расширенные выборки сильно коррелированы. Ключом к преодолению переобучения является энтропийная емкость модели — сколько информации модель может хранить. Чем больше информации может хранить модель, тем больше она может быть точной, она может использовать больше функций, но рискует сохранить нерелевантные функции. Когда модель может хранить очень мало информации, она может сосредоточиться на характерных чертах, обнаруженных в данных, которые с большей вероятностью будут иметь отношение к реальной проблеме и лучше обобщать.
В этом примере мы используем небольшую свёрточную сеть всего с несколькими слоями и несколькими фильтрами, используя как увеличение данных, так и отсев. Выпадение также уменьшает переобучение, оно не позволяет слою дважды видеть один и тот же шаблон и, таким образом, играет роль, аналогичную дополнению данных (что можно понять, поскольку как дополнение данных, так и выпадение могут нарушать случайные корреляции в данных).
Приведенный ниже код является нашей первой моделью, простой трехслойной сверточной сетью с активаторами ReLU, за каждым сверточным слоем следует слой максимального объединения. Это очень похоже по структуре на модель классификации изображений, опубликованную LeCun в 1990-х годах, за исключением того, что активатор ReLU в то время не использовался.
Полный код этого эксперимента можно найти по адресуздесьоказаться.
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=(3, 150, 150)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
#目前为止模型的输出维度 (height, width, features)
Кроме того, используются два полносвязных слоя. Последний слой представляет собой единую единицу, использующую сигмовидный активатор, который очень подходит для задач бинарной классификации, используйте соответствующийbinary_crossentropy
для обучения модели.
model.add(Flatten()) # 3维数据特征转化为1维
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(1))
model.add(Activation('sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='rmsprop',
metrics=['accuracy'])
Теперь, чтобы подготовить данные, используйте.flow_from_directory()
Создавайте пакеты данных изображения и соответствующие метки в соответствии с изображениями в формате jpg и соответствующими каталогами.
batch_size = 16
# 用于训练的图片增强配置
train_datagen = ImageDataGenerator(
rescale=1./255,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True)
# 用于测试的图片增强配置:只设定rescale
test_datagen = ImageDataGenerator(rescale=1./255)
# 生成器从'data/train' 文件夹中读取数据, 可以无限生成增强的图片数据
train_generator = train_datagen.flow_from_directory(
'data/train', # 目标文件夹
target_size=(150, 150), # 所有图片尺寸重设为 150x150
batch_size=batch_size,
class_mode='binary') # binary_crossentropy 损失函数需要二值标签
# 一个类似的生成器,用于验证集数据
validation_generator = test_datagen.flow_from_directory(
'data/validation',
target_size=(150, 150),
batch_size=batch_size,
class_mode='binary')
Теперь генератор можно использовать для обучения модели. Каждый раунд обучения занимает 20~30 секунд на GPU и 300~400 секунд на CPU. Так что, если он не торопится, работает на процессоре.
model.fit_generator(
train_generator,
steps_per_epoch=2000 // batch_size,
epochs=50,
validation_data=validation_generator,
validation_steps=800 // batch_size)
model.save_weights('first_try.h5') # 记得在训练后和训练中适时保存模型
После 50 раундов обучения точность проверочного набора составляет около 0,79–0,81 (50 раз задаются произвольно — поскольку модель очень маленькая и используется сильный отсев, ее нелегко переобучить). Если бы Kaggle впервые выпустил это соревнование, наши результаты были бы уже лучшими — и использовали только 8% данных, и не оптимизировали структуру сети и гиперпараметры. Фактически, эта модель входит в сотню лучших в конкурсе Kaggle (всего 215 участников). Таким образом, по оценкам, по меньшей мере 115 участников не использовали глубокое обучение.
Функция узкого места с использованием предварительно обученной сети: точность 90% за одну минуту
Более элегантный подход — использовать предварительно обученные сети на больших наборах данных. Эти сети научились функциям, которые полезны во многих задачах компьютерной графики, и могут использоваться для достижения более высокой точности, чем модели, которые полагаются только на данные, доступные для самой задачи.
Мы используем модель VGG16, предварительно обученную на наборе данных ImageNet, о чем говорилось ранее в этом блоге. Поскольку 1000 категорий ImageNet включают несколько категорий «кошки» (персидские, сиамские и т. д.) и множество категорий «собак», эта модель изучила множество функций, имеющих отношение к нашей задаче классификации. Фактически, необходимо только записать прогнозное значение данных softmax после модели, чтобы решить проблему классификации кошек и собак, без использования узких мест. Однако, чтобы сделать метод более широко применимым, включая те классификации, которых нет в ImageNet, мы по-прежнему используем узкие места.
Ниже представлена структура VGG16:
Наша стратегия такова: использовать только сверточную часть этой модели, т.е. все слои до полного соединения. Затем запустите модель на наших данных, используя два пустых массива для записи всех выходных данных (например, узкое место VGG16: последний слой активации перед полносвязным слоем). Затем на основе этих записей обучается небольшая полносвязная сеть.
Причина, по которой мы записываем эти функции в автономном режиме, а не добавляем обучение полностью подключенному слою непосредственно поверх замороженных сверточных слоев, заключается в том, что это более эффективно с точки зрения вычислений. Выполнение модели VGG16 требует много времени, особенно при использовании ЦП, и мы хотим вычислить ее только один раз. Обратите внимание, однако, что увеличение данных не может использоваться таким образом.
допустимыйздесьНайдите полный код изGithubПолучите предварительно подготовленные веса. Как создается и загружается модель, здесь не обсуждается — это обсуждалось во многих примерах Keras. Но посмотрите, как использовать генератор изображений для записи узких мест:
batch_size = 16
generator = datagen.flow_from_directory(
'data/train',
target_size=(150, 150),
batch_size=batch_size,
class_mode=None, # 这个生成器不产生标签
shuffle=False) # 保持数据顺序,先是1000只猫,然后是1000条狗
# 给定一个生成器yields批量的numpy数据,predict_generator会返回模型的输出
bottleneck_features_train = model.predict_generator(generator, 2000)
# 保存这些输出到 numpy数组
np.save(open('bottleneck_features_train.npy', 'w'), bottleneck_features_train)
generator = datagen.flow_from_directory(
'data/validation',
target_size=(150, 150),
batch_size=batch_size,
class_mode=None,
shuffle=False)
bottleneck_features_validation = model.predict_generator(generator, 800)
np.save(open('bottleneck_features_validation.npy', 'w'), bottleneck_features_validation)
Затем используйте сохраненные данные для обучения небольшой полностью подключенной сети:
train_data = np.load(open('bottleneck_features_train.npy'))
# 由于特征是按猫狗的顺序产生的,所有构造标签很简单
train_labels = np.array([0] * 1000 + [1] * 1000)
validation_data = np.load(open('bottleneck_features_validation.npy'))
validation_labels = np.array([0] * 400 + [1] * 400)
model = Sequential()
model.add(Flatten(input_shape=train_data.shape[1:]))
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
model.fit(train_data, train_labels,
epochs=50,
batch_size=batch_size,
validation_data=(validation_data, validation_labels))
model.save_weights('bottleneck_fc_model.h5')
Так как сеть маленькая, обучение на CPU тоже быстрое (1 эпоха в секунду):
Train on 2000 samples, validate on 800 samples
Epoch 1/50
2000/2000 [==============================] - 1s - loss: 0.8932 - acc: 0.7345 - val_loss: 0.2664 - val_acc: 0.8862
Epoch 2/50
2000/2000 [==============================] - 1s - loss: 0.3556 - acc: 0.8460 - val_loss: 0.4704 - val_acc: 0.7725
...
Epoch 47/50
2000/2000 [==============================] - 1s - loss: 0.0063 - acc: 0.9990 - val_loss: 0.8230 - val_acc: 0.9125
Epoch 48/50
2000/2000 [==============================] - 1s - loss: 0.0144 - acc: 0.9960 - val_loss: 0.8204 - val_acc: 0.9075
Epoch 49/50
2000/2000 [==============================] - 1s - loss: 0.0102 - acc: 0.9960 - val_loss: 0.8334 - val_acc: 0.9038
Epoch 50/50
2000/2000 [==============================] - 1s - loss: 0.0040 - acc: 0.9985 - val_loss: 0.8556 - val_acc: 0.9075
Точность достигает 0,90~0,91, что весьма неплохо. Это связано с тем, что базовая модель была обучена на выборочных данных, помеченных как кошки и собаки.
Точная настройка последних нескольких слоев предварительно обученной сети
Чтобы продолжить улучшение предыдущих результатов, мы можем настроить окончательный сверточный модуль модели VGG16, а также классификатор. Тонкая настройка означает, начиная с обученной сети и переобучая ее на новом наборе данных с очень небольшими обновлениями веса, Этот пример можно разделить на 3 этапа:
- Инициализируйте сверточную часть сети VGG16 для загрузки весов.
- На этой основе добавляем полносвязную сеть, которую мы обучили ранее, и загружаем веса
- Заморозить слои перед последним модулем свертки
Уведомление:
- Для тонкой настройки модели все слои начинаются с обученных весов: например, нельзя добавить полносвязный слой, инициализированный случайными значениями, к предварительно обученному сверточному слою. Потому что большие обновления случайно инициализированных весов могут повредить веса сверточных слоев. Итак, в этом примере мы обучаем классификатор с полносвязным слоем, прежде чем заставить его работать со сверточным слоем.
- Чтобы предотвратить переобучение, мы настраиваем только последний сверточный слой, а не всю сеть, поскольку вся сеть имеет очень большую энтропийную емкость и, следовательно, склонна к переобучению. Функции, изученные базовой сверточной сетью, являются более базовыми функциями, а не такими абстрактными, как функции высокого уровня, поэтому разумно исправить функции первых нескольких слоев (более основные функции) и обучить только последний слой.
- Тонкая настройка должна использовать небольшую скорость обучения и, как правило, использовать оптимизатор SGD, а не такие адаптивные оптимизаторы, как RMSProp. Это сделано для того, чтобы обновления веса имели очень маленькую величину, не нанося ущерба функциям, которые сеть уже изучила.
допустимыйздесьПолучите полный пробный код.
После инициализации весов загрузки сети VGG16 добавьте полносвязный слой, который мы обучили ранее:
# 在卷积层上增加分类器
top_model = Sequential()
top_model.add(Flatten(input_shape=model.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(1, activation='sigmoid'))
# 注意为了微调训练,全连接分类器也应该训练过
# classifier, including the top classifier,
# in order to successfully do fine-tuning
top_model.load_weights(top_model_weights_path)
# 添加到卷积层之上
model.add(top_model)
Наконец, начните тонкую настройку модели с небольшой скоростью обучения:
batch_size = 16
# 准备数据增强
train_datagen = ImageDataGenerator(
rescale=1./255,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
train_data_dir,
target_size=(img_height, img_width),
batch_size=batch_size,
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_data_dir,
target_size=(img_height, img_width),
batch_size=batch_size,
class_mode='binary')
# 微调模型
model.fit_generator(
train_generator,
steps_per_epoch=nb_train_samples // batch_size,
epochs=epochs,
validation_data=validation_generator,
validation_steps=nb_validation_samples // batch_size)
После 50 циклов обучения этот метод позволяет достичь точности 0,94. отличное улучшение!
Можно попытаться добиться точности 0,95 следующим методом:
- Более агрессивное увеличение данных
- более агрессивный отсев
- Используйте регуляризацию L1 и L2 (также известную как «распад веса»)
- Еще один уровень тонкой настройки (при использовании большей регуляризации)
Эта статья заканчивается здесь! Напомним, что код для каждой части эксперимента выглядит следующим образом:
Если у вас есть какие-либо комментарии или предложения по темам, обсуждаемым в этой статье, пожалуйста, перейдите наTwitter.