Применение сети VGG16 для распознавания котов и собак

In [1]:
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.preprocessing import image
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten, Dropout
from tensorflow.python.keras.optimizers import Adam
from tensorflow.python.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.applications.vgg16 import preprocess_input
import numpy as np
import matplotlib.pyplot as plt
from scipy.misc import toimage
%matplotlib inline 

Смотрим примеры картинок

In [2]:
img_path = 'test/0.jpg'
img = image.load_img(img_path, target_size=(224, 224))
plt.imshow(img)
plt.show()

Создаем генератор изображений

In [3]:
# Каталог с данными для обучения
train_dir = 'train'
# Размеры изображения
img_width, img_height = 224, 224
# Размерность тензора на основе изображения для входных данных в нейронную сеть
# backend Tensorflow, channels_last
input_shape = (img_width, img_height, 3)
# Размер мини-выборки
batch_size = 50
# Количество элементов данных для обучения
nb_train_samples = 5000

Генератор изображений создается на основе класса ImageDataGenerator с дополнением данных.

In [4]:
datagen = ImageDataGenerator(rescale=1. / 255,
                                  rotation_range=20,
                                  width_shift_range=0.1,
                                  height_shift_range=0.1,
                                  zoom_range=0.2,
                                  horizontal_flip=True,
                                  fill_mode='nearest')

Проверяем генератор

In [5]:
x = image.img_to_array(img)
x = x.reshape((1,) + x.shape)
i = 0
for batch in datagen.flow(x, batch_size=1):
    plt.figure(i)
    imgplot = plt.imshow(image.array_to_img(batch[0]))
    i += 1
    if i % 4 == 0:
        break
plt.show()

Генератор данных для обучения на основе изображений из каталога

In [6]:
train_generator = datagen.flow_from_directory(
    train_dir,
    target_size=(img_width, img_height),
    batch_size=batch_size,
    class_mode='binary')
Found 5000 images belonging to 2 classes.

Смотрим номера классов

In [7]:
train_generator.class_indices
Out[7]:
{'cats': 0, 'dogs': 1}

Загружаем предварительно обученную нейронную сеть VGG16

In [8]:
vgg16_net = VGG16(weights='imagenet', 
                  include_top=False, 
                  input_shape=input_shape)
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5
58892288/58889256 [==============================] - 72s 1us/step

"Замораживаем" веса предварительно обученной нейронной сети VGG16

In [9]:
vgg16_net.trainable = False
In [10]:
vgg16_net.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 224, 224, 3)       0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 224, 224, 64)      36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 112, 112, 64)      0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 112, 112, 128)     73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 112, 112, 128)     147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 56, 56, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 56, 56, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 28, 28, 256)       0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 28, 28, 512)       1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 14, 14, 512)       0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 7, 7, 512)         0         
=================================================================
Total params: 14,714,688
Trainable params: 0
Non-trainable params: 14,714,688
_________________________________________________________________

Создаем составную нейронную сеть на основе VGG16

In [11]:
model = Sequential()
# Добавляем в модель сеть VGG16 вместо слоя
model.add(vgg16_net)
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1, activation='sigmoid'))
In [12]:
model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
vgg16 (Model)                (None, 7, 7, 512)         14714688  
_________________________________________________________________
flatten (Flatten)            (None, 25088)             0         
_________________________________________________________________
dense (Dense)                (None, 256)               6422784   
_________________________________________________________________
dropout (Dropout)            (None, 256)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 257       
=================================================================
Total params: 21,137,729
Trainable params: 6,423,041
Non-trainable params: 14,714,688
_________________________________________________________________

Компилируем составную нейронную сеть

In [13]:
model.compile(loss='binary_crossentropy',
              optimizer=Adam(lr=1e-5), 
              metrics=['accuracy'])

Обучаем сеть с помощью генераторов

In [14]:
## Для получения нормального результата поменять количество эпох
model.fit_generator(
    train_generator,
    steps_per_epoch=nb_train_samples // batch_size,
    epochs=1,
    verbose=1)
Epoch 1/1
100/100 [==============================] - 1295s 13s/step - loss: 0.6006 - acc: 0.6694
Out[14]:
<tensorflow.python.keras.callbacks.History at 0x127e599b0>

Распознаем данные

In [15]:
# Каталог с данными для распознавания
test_dir = 'predict'
# Количество элементов данных для распознавания
nb_test_samples = 1000

Данные для распознавания должны находится в подкаталоге. Поэтому копируем каталог test в каталог predict

In [16]:
!mkdir predict
In [17]:
!mv test predict
In [18]:
test_datagen = ImageDataGenerator(rescale=1./255)
In [19]:
test_generator = datagen.flow_from_directory(
    test_dir,
    target_size=(img_width, img_height),
    batch_size=batch_size,
    shuffle=False,
    class_mode='binary')
Found 1000 images belonging to 1 classes.
In [20]:
test_generator.reset()
In [21]:
predictions = model.predict_generator(test_generator, steps = nb_test_samples // batch_size)
In [22]:
predictions[:5]
Out[22]:
array([[0.24155661],
       [0.8666218 ],
       [0.73840755],
       [0.27485466],
       [0.65299493]], dtype=float32)
In [23]:
filenames_with_dir = test_generator.filenames
filenames_with_dir[:5]
Out[23]:
['test/0.jpg', 'test/1.jpg', 'test/10.jpg', 'test/100.jpg', 'test/101.jpg']
In [24]:
filenames = []
for filename in filenames_with_dir:
    filenames.append(filename.replace('test/',''))
filenames[:5]
Out[24]:
['0.jpg', '1.jpg', '10.jpg', '100.jpg', '101.jpg']
In [25]:
threshold = 0.5
In [26]:
binary_predictions = []
for prediction in predictions:
    if prediction >= threshold:
        binary_predictions.append(1)
    else:
        binary_predictions.append(0)      
In [27]:
binary_predictions[:10]
Out[27]:
[0, 1, 1, 0, 1, 0, 0, 1, 1, 0]
In [28]:
out = np.column_stack((filenames, binary_predictions))
In [29]:
out[:5]
Out[29]:
array([['0.jpg', '0'],
       ['1.jpg', '1'],
       ['10.jpg', '1'],
       ['100.jpg', '0'],
       ['101.jpg', '1']], dtype='<U21')

Домашнее задание

  1. Попытайтесь улучшить решение:
    • Изменяйте часть сети, отвечающей за классификацию: количество нейронов в полносвязном слое (попробуйте варианты 32, 128, 256, 512, 1024) и параметры Dropout (0.25, 0.5, 0.6, 0.8).
    • Добавьте еще один полносвязный слой перед выходным слоем (количество нейронов 32, 128, 256).
    • Изменяйте размер мини-выборки, попробуйте варианты 25, 50, 100, 125.
    • Изменяйте параметр скорости обучения, который задается в аргументе lr при компиляции сети (optimizer=Adam(lr=1e-5)). Попробуйте значения 1e-3, 1e-4, 1e-5 и 1e-6.
    • Изменяйте количество шагов на каждой эпохе обучения. В примере количество шагов равно числу изображений деленному на размер мини-выборки. Попробуйте уменьшить это количество вдвое, а также увеличить в два или 4 раза.
    • Изменяйте параметры дополнения данных (документация https://keras.io/preprocessing/image/ ).
    • Попробйуте включить тонкую настройку нейронной сети: разморозить несколько слоев VGG16 и дообучить их. Попробуйте два варианта тонкой настройки: разморозка слоев начиная с block5_conv1 и block4_conv1.
    • Замените сеть VGG16 на Inception, или любую другую из https://keras.io/applications/.