Распознавание объектов на изображениях из набора данных CIFAR-10

Mамкин Data Scientist

In [1]:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Conv2D, MaxPooling2D, Flatten, Dropout, BatchNormalization
from tensorflow.keras import utils
from tensorflow.keras.preprocessing import image
from tensorflow.keras.datasets import cifar10
import numpy as np
import matplotlib.pyplot as plt
from scipy.misc import toimage
import pandas as pd
%matplotlib inline 
In [2]:
import warnings
warnings.filterwarnings("ignore")
In [3]:
# Размер мини-выборки
BATCH_SIZE = 128
# Количество классов изображений
NB_CLASSES = 10
# Названия классов из набора данных CIFAR-10
classes=['самолет', 'автомобиль', 'птица', 'кот', 'олень', 'собака', 'лягушка', 'лошадь', 'корабль', 'грузовик']
In [4]:
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

Просматриваем примеры данных

Данные для обучения

In [5]:
n = 9546
plt.imshow(toimage(x_train[n]))
plt.show()
class_number = y_train[n][0]
print("Номер класса:", class_number)
print("Тип объекта:", classes[class_number])
Номер класса: 4
Тип объекта: олень
In [6]:
x_train[n]
Out[6]:
array([[[212, 211, 208],
        [213, 213, 204],
        [211, 211, 203],
        ...,
        [171, 178, 197],
        [168, 177, 183],
        [147, 167, 134]],

       [[194, 195, 197],
        [188, 190, 194],
        [183, 188, 193],
        ...,
        [163, 171, 165],
        [171, 174, 185],
        [153, 162, 154]],

       [[179, 184, 200],
        [170, 177, 200],
        [158, 165, 188],
        ...,
        [150, 166, 143],
        [167, 178, 168],
        [163, 175, 163]],

       ...,

       [[124, 146,  67],
        [120, 148,  53],
        [100, 118,  53],
        ...,
        [138, 151,  91],
        [132, 149,  86],
        [121, 141,  78]],

       [[126, 165,  47],
        [132, 161,  65],
        [128, 136,  84],
        ...,
        [126, 164,  59],
        [117, 157,  57],
        [103, 149,  51]],

       [[140, 179,  50],
        [136, 169,  62],
        [142, 158,  81],
        ...,
        [122, 168,  41],
        [112, 162,  38],
        [ 98, 154,  33]]], dtype=uint8)

Данные для тестирования

In [7]:
n = 100
plt.imshow(toimage(x_test[n]))
plt.show()
In [8]:
x_test[n]
Out[8]:
array([[[118, 133, 167],
        [118, 133, 166],
        [120, 135, 168],
        ...,
        [ 48,  71,  65],
        [ 32,  52,  37],
        [ 29,  48,  30]],

       [[116, 131, 165],
        [116, 132, 164],
        [118, 134, 166],
        ...,
        [ 42,  65,  51],
        [ 25,  38,  25],
        [ 22,  37,  24]],

       [[126, 137, 168],
        [125, 137, 167],
        [126, 140, 170],
        ...,
        [ 26,  41,  27],
        [ 28,  46,  30],
        [ 28,  55,  34]],

       ...,

       [[171, 171, 161],
        [166, 164, 159],
        [161, 161, 153],
        ...,
        [183, 178, 177],
        [180, 175, 174],
        [176, 172, 169]],

       [[162, 165, 154],
        [171, 165, 159],
        [161, 156, 149],
        ...,
        [174, 171, 170],
        [176, 173, 171],
        [172, 168, 162]],

       [[141, 149, 132],
        [161, 155, 149],
        [161, 153, 148],
        ...,
        [181, 180, 178],
        [182, 181, 179],
        [185, 180, 178]]], dtype=uint8)

Предварительная обработка данных

Преобразуем правильные ответы в формат One-hot-encoding

In [9]:
y_train = utils.to_categorical(y_train, NB_CLASSES)

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

In [12]:
# Создаем последовательную модель
model = Sequential()
# Слой пакетной нормализации
model.add(BatchNormalization(input_shape=(32, 32, 3)))
# Первый сверточный слой
model.add(Conv2D(32, (3, 3), padding='same', activation='relu'))
# Второй сверточный слой
model.add(Conv2D(32, (3, 3), activation='relu', padding='same'))
# Первый слой подвыборки
model.add(MaxPooling2D(pool_size=(2, 2)))
# Слой регуляризации Dropout
model.add(Dropout(0.25))

# Слой пакетной нормализации
model.add(BatchNormalization())
# Третий сверточный слой
model.add(Conv2D(64, (3, 3), padding='same', activation='relu'))
# Четвертый сверточный слой
model.add(Conv2D(64, (3, 3), padding='same', activation='relu'))
# Второй слой подвыборки
model.add(MaxPooling2D(pool_size=(2, 2)))
# Слой регуляризации Dropout
model.add(Dropout(0.25))
# Слой преобразования данных из 2D представления в плоское
model.add(Flatten())
# Полносвязный слой для классификации
model.add(Dense(512, activation='relu'))
# Слой регуляризации Dropout
model.add(Dropout(0.5))
# Выходной полносвязный слой
model.add(Dense(NB_CLASSES, activation='softmax'))

Компилируем сеть

In [13]:
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])

print(model.summary())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
batch_normalization_4 (Batch (None, 32, 32, 3)         12        
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 32, 32, 32)        896       
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 32, 32, 32)        9248      
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 16, 16, 32)        0         
_________________________________________________________________
dropout_6 (Dropout)          (None, 16, 16, 32)        0         
_________________________________________________________________
batch_normalization_5 (Batch (None, 16, 16, 32)        128       
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 16, 16, 64)        18496     
_________________________________________________________________
conv2d_11 (Conv2D)           (None, 16, 16, 64)        36928     
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 8, 8, 64)          0         
_________________________________________________________________
dropout_7 (Dropout)          (None, 8, 8, 64)          0         
_________________________________________________________________
flatten_2 (Flatten)          (None, 4096)              0         
_________________________________________________________________
dense_2 (Dense)              (None, 512)               2097664   
_________________________________________________________________
dropout_8 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_3 (Dense)              (None, 10)                5130      
=================================================================
Total params: 2,168,502
Trainable params: 2,168,432
Non-trainable params: 70
_________________________________________________________________
None

Обучаем нейронную сеть

In [14]:
history = model.fit(x_train, 
                    y_train, 
                    batch_size=BATCH_SIZE, 
                    epochs=25,
                    validation_split=0.2,
                    verbose=1)
Train on 40000 samples, validate on 10000 samples
Epoch 1/25
40000/40000 [==============================] - 141s 4ms/step - loss: 1.6467 - acc: 0.4026 - val_loss: 1.3068 - val_acc: 0.5595
Epoch 2/25
40000/40000 [==============================] - 594s 15ms/step - loss: 1.1845 - acc: 0.5794 - val_loss: 1.0206 - val_acc: 0.6458
Epoch 3/25
40000/40000 [==============================] - 141s 4ms/step - loss: 1.0280 - acc: 0.6362 - val_loss: 0.9086 - val_acc: 0.6760
Epoch 4/25
40000/40000 [==============================] - 140s 4ms/step - loss: 0.9399 - acc: 0.6673 - val_loss: 0.8566 - val_acc: 0.6946
Epoch 5/25
40000/40000 [==============================] - 142s 4ms/step - loss: 0.8500 - acc: 0.7026 - val_loss: 0.8323 - val_acc: 0.7110
Epoch 6/25
40000/40000 [==============================] - 148s 4ms/step - loss: 0.7861 - acc: 0.7229 - val_loss: 0.7707 - val_acc: 0.7343
Epoch 7/25
40000/40000 [==============================] - 149s 4ms/step - loss: 0.7378 - acc: 0.7396 - val_loss: 0.7010 - val_acc: 0.7553
Epoch 8/25
40000/40000 [==============================] - 146s 4ms/step - loss: 0.6852 - acc: 0.7575 - val_loss: 0.7026 - val_acc: 0.7566
Epoch 9/25
40000/40000 [==============================] - 144s 4ms/step - loss: 0.6551 - acc: 0.7686 - val_loss: 0.6972 - val_acc: 0.7600
Epoch 10/25
40000/40000 [==============================] - 152s 4ms/step - loss: 0.6019 - acc: 0.7875 - val_loss: 0.7017 - val_acc: 0.7640
Epoch 11/25
40000/40000 [==============================] - 163s 4ms/step - loss: 0.5699 - acc: 0.7962 - val_loss: 0.6637 - val_acc: 0.7760
Epoch 12/25
40000/40000 [==============================] - 166s 4ms/step - loss: 0.5336 - acc: 0.8111 - val_loss: 0.6659 - val_acc: 0.7745
Epoch 13/25
40000/40000 [==============================] - 164s 4ms/step - loss: 0.4998 - acc: 0.8221 - val_loss: 0.6820 - val_acc: 0.7735
Epoch 14/25
40000/40000 [==============================] - 156s 4ms/step - loss: 0.4821 - acc: 0.8310 - val_loss: 0.6734 - val_acc: 0.7759
Epoch 15/25
40000/40000 [==============================] - 154s 4ms/step - loss: 0.4512 - acc: 0.8414 - val_loss: 0.6710 - val_acc: 0.7854
Epoch 16/25
40000/40000 [==============================] - 155s 4ms/step - loss: 0.4305 - acc: 0.8493 - val_loss: 0.6933 - val_acc: 0.7776
Epoch 17/25
40000/40000 [==============================] - 157s 4ms/step - loss: 0.4071 - acc: 0.8566 - val_loss: 0.6802 - val_acc: 0.7883
Epoch 18/25
40000/40000 [==============================] - 155s 4ms/step - loss: 0.3857 - acc: 0.8622 - val_loss: 0.7009 - val_acc: 0.7772
Epoch 19/25
40000/40000 [==============================] - 156s 4ms/step - loss: 0.3665 - acc: 0.8703 - val_loss: 0.6900 - val_acc: 0.7843
Epoch 20/25
40000/40000 [==============================] - 156s 4ms/step - loss: 0.3509 - acc: 0.8749 - val_loss: 0.6808 - val_acc: 0.7939
Epoch 21/25
40000/40000 [==============================] - 156s 4ms/step - loss: 0.3343 - acc: 0.8817 - val_loss: 0.6977 - val_acc: 0.7847
Epoch 22/25
40000/40000 [==============================] - 159s 4ms/step - loss: 0.3301 - acc: 0.8819 - val_loss: 0.6645 - val_acc: 0.7935
Epoch 23/25
40000/40000 [==============================] - 158s 4ms/step - loss: 0.3088 - acc: 0.8915 - val_loss: 0.7027 - val_acc: 0.7921
Epoch 24/25
40000/40000 [==============================] - 155s 4ms/step - loss: 0.3018 - acc: 0.8940 - val_loss: 0.7160 - val_acc: 0.7907
Epoch 25/25
40000/40000 [==============================] - 157s 4ms/step - loss: 0.2922 - acc: 0.8968 - val_loss: 0.7067 - val_acc: 0.7916

Визуализация качества обучения

In [15]:
plt.plot(history.history['acc'], 
         label='Доля верных ответов на обучающем наборе')
plt.plot(history.history['val_acc'], 
         label='Доля верных ответов на проверочном наборе')
plt.xlabel('Эпоха обучения')
plt.ylabel('Доля верных ответов')
plt.legend()
plt.show()

Запускаем распознавание объектов из тестового набора данных

In [16]:
predictions = model.predict(x_test)
In [17]:
predictions[:5]
Out[17]:
array([[2.9571234e-05, 8.0892880e-07, 9.5847399e-06, 9.7014654e-01,
        1.4077837e-06, 2.9393781e-02, 4.1020452e-04, 2.9882341e-07,
        6.6073085e-06, 1.1430212e-06],
       [8.1208855e-06, 4.6199297e-05, 6.9120680e-14, 5.1800310e-14,
        2.3204661e-17, 2.1886395e-19, 1.2176156e-15, 2.3180318e-18,
        9.9994552e-01, 1.2804072e-07],
       [2.1835592e-02, 1.5953025e-02, 2.2225197e-05, 3.6393416e-05,
        1.3504969e-05, 1.0174772e-06, 9.1049587e-06, 3.3243439e-05,
        9.5161033e-01, 1.0485613e-02],
       [7.0882440e-01, 1.6649980e-02, 1.5385650e-04, 3.6394289e-05,
        8.1008733e-07, 6.3760055e-08, 1.1289445e-05, 5.3089548e-07,
        2.7061358e-01, 3.7090238e-03],
       [9.2423641e-14, 9.8756520e-14, 2.5091893e-05, 5.3204512e-05,
        1.5450222e-03, 2.3040835e-11, 9.9837661e-01, 1.7479547e-13,
        1.5520566e-12, 1.4345740e-13]], dtype=float32)

Преобразуем результаты распознавания из формата one hot encoding в цифры

In [18]:
predictions = np.argmax(predictions, axis=1)
In [19]:
predictions[:5]
Out[19]:
array([3, 8, 8, 0, 6])

Проверем результаты распознавания

In [21]:
n = 1
plt.imshow(toimage(x_test[n]))
class_number = predictions[n]
print("Номер класса:", class_number)
print("Тип объекта:", classes[class_number])
plt.show()
Номер класса: 8
Тип объекта: корабль

Готовим файл с решением для Kaggle

In [22]:
out = np.column_stack((range(1, predictions.shape[0]+1), predictions))
In [23]:
out[:5]
Out[23]:
array([[1, 3],
       [2, 8],
       [3, 8],
       [4, 0],
       [5, 6]])

Записываем результаты в файл

In [24]:
np.savetxt('submission.csv', out, header="Id,Category", 
            comments="", fmt="%d,%d")
In [25]:
!head submission.csv
Id,Category
1,3
2,8
3,8
4,0
5,6
6,6
7,1
8,4
9,3

Отправляем решение на соревнование

In [ ]:
!kaggle competitions submit -c Название соревнования (например digit-recognizer) -m "Baseline submition" -f submission.csv

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

  1. Используйте шаблон ноутбука, чтобы отправить решение на соревнование MNIST. https://www.kaggle.com/c/digit-recognizer
  2. Попробуйте изменить нейронную сеть, чтобы улучшить качество решения:
    • Изменяйте архитектуру сети (сочетание и количество слоев свертки и подвыборки, полносвязных слоев)
    • Изменяйте количество и размер фильтров в сверточных слоях
    • Изменяйте количество нейронов в полносвязных слоях
    • Изменяйте параметры Dropout
    • Изменяйте количество эпох обучения
    • Изменяйте размер мини-выборки (batch_size)
  3. Во время обучения следите, чтобы не возникло переобучения.
  4. После подбора лучших гиперпараметров, обучите сеть еще раз на полном объеме данных без разделения на обучающий и проверочный наборы.
  5. Составьте отчет, который включает:
    • Место в соревновании, которое вам удалось достичь
    • Описание архитектуры нейронной сети и гиперпараметров обучения
    • Ссылку на ноутбук с кодом обучения
    • График качества обучения на обучающем и проверочном наборах данных

Решение --> @BoykoAA