Я начал свой путь в машинном обучении в прошлом году с этого фантастического курса по машинному обучению от Стэнфордского университета на Coursera (2,5 миллиона записались на данный момент и 113 000 + 4,9/5 отзывов! Только что вау! Если вы только начинаете свой путь в машинном обучении, я очень рекомендую этот базовый курс.

Один из способов, который я нашел очень эффективным в изучении ML, — это вернуться к предыдущим задачам по программированию и решить их с помощью новых навыков и фреймворков. В этом посте я буду описывать исследование этого задания на основе Keras, а также упражнение по добавлению ваших собственных рукописных образцов и просмотру этой работы. Нет большей радости, чем учиться на практике, так что давайте углубимся.

Примечание. Я использую colab (сокращение от collaboratory — среда блокнота Jupyter, для использования которой не требуется настройка. Блокнот colab для всего кода в этой статье можно найти здесь.

Часть 1: Загрузка и анализ обучающих данных:

Следите в коллабе здесь.

ex3data1.mat представляет собой обучающий набор рукописных цифр (20px X 20px), полученный из MNIST. В ex3data1.mat есть 5000 обучающих примеров, где каждый обучающий пример представляет собой изображение цифры в градациях серого размером 20 на 20 пикселей. Каждый пиксель представлен числом с плавающей запятой, указывающим интенсивность оттенков серого в этом месте. Сетка пикселей 20 на 20 разворачивается в 400-мерный вектор. Каждый из этих обучающих примеров становится отдельной строкой в ​​нашей матрице данных X. Вторая часть обучающей выборки — это 5000-мерный вектор y, который содержит метки для обучающей выборки.

data = spio.loadmat('ex3data1.mat', squeeze_me=True)
#Images
X_train = data['X']
print(X_train.shape)
#Labels
Y_train = data['y']
print(Y_train.shape)
#Note that 0 is encoded as 10
print(Y_train)
(5000, 400)
(5000,)
[10 10 10 ...  9  9  9]

Каждое изображение в X разворачивается в массив форм (1, 400) из массива форм (20, 20). Чтобы прочитать его правильно, нам нужно будет транспонировать массив изображений после изменения его формы обратно в (20, 20).

Например. скажем, изображение 4x4:

a b c d
a b c d
a b c d
a b c d

развертывание его в массив формы (1, 400) приводит к:

aaaabbbbccccdddd

преобразование его в (4,4) приводит к:

a a a a
b b b b
c c c c
d d d d

и, наконец, выполнив транспонирование, мы получаем изображение обратно: -

a b c d
a b c d
a b c d
a b c d

Давайте попробуем этот пример ниже: -

a = np.array([['a', 'b', 'c', 'd'],
              ['a', 'b', 'c', 'd'],
              ['a', 'b', 'c', 'd'],
              ['a', 'b', 'c', 'd']])
print(a)
# unroll image
b = np.transpose(a).reshape(1,4*4)
print(b)
# re-create image
print(np.transpose(b.reshape(4, 4)))
[['a' 'b' 'c' 'd']
 ['a' 'b' 'c' 'd']
 ['a' 'b' 'c' 'd']
 ['a' 'b' 'c' 'd']]
[['a' 'a' 'a' 'a' 'b' 'b' 'b' 'b' 'c' 'c' 'c' 'c' 'd' 'd' 'd' 'd']] 
[['a' 'b' 'c' 'd'] 
 ['a' 'b' 'c' 'd'] 
 ['a' 'b' 'c' 'd'] 
 ['a' 'b' 'c' 'd']]

Давайте посмотрим на первое изображение в X_train:-
Обратите внимание, что цифра 0 соответствует метке 10.

# let's take the 1st image in the training set
image = np.transpose(X_train[0].reshape(20,20))
plt.figure(figsize = (1,1))
plt.imshow(image, cmap='gray')
print(Y_train[0])

Давайте попробуем построить подмножество случайно перемешанных изображений:

width, height, columns, rows = 20, 20, 10, 10
fig=plt.figure(figsize=(rows, columns))
# sample and shuffle 10% of the training data
image_samples = pd.DataFrame(X_train).sample(frac=0.1)
for i in range(0, rows*columns):
  image = np.transpose(image_samples[i:i+1].values.reshape(width, height))
  fig.add_subplot(rows, columns, i+1)
  plt.imshow(image, cmap='gray')
plt.show()

Далее мы горячим кодированием метки разделяем на 10 классов.

# one-hot encode the target (0 is encoded as 10 in original dataset)
Y_train = np.where(Y_train==10,0,Y_train)
Y_train_one_hot = keras.utils.to_categorical(Y_train, 10)
print(Y_train_one_hot.shape)
print(Y_train[0],' --> ', Y_train_one_hot[0])
print(Y_train[1],' --> ', Y_train_one_hot[1])
print(Y_train[2],' --> ', Y_train_one_hot[2])
(5000, 10)
0  -->  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
0  -->  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
0  -->  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

Давайте перезагрузим данные, перемешаем, а затем разделим их на обучающий и проверочный наборы (мы будем использовать разделение 80:20).

def reload_with_split(display_test_image=False):
  # Reload data
  data = spio.loadmat('ex3data1.mat', squeeze_me=True)
  #Images
  X_train = data['X']
  print(X_train.shape)
#Labels
  Y_train = data['y']
  print(Y_train.shape)
# We will need to shuffle this data before splitting as it's sorted by labels
  s = np.arange(0, len(X_train), 1)
  np.random.shuffle(s)
  X_train_shuffled, Y_train_shuffled = X_train[s], Y_train[s]
#Training data: Images
  X_train = X_train_shuffled[:4000]
  print(X_train.shape)
  #Training data: Labels
  Y_train = Y_train_shuffled[:4000]
  print(Y_train.shape)
X_val = X_train_shuffled[4000:]
  print(X_val.shape)
Y_val = Y_train_shuffled[4000:]
  print(Y_val.shape)
#Convert label for 0 to 0 (from 10)
  Y_train = np.where(Y_train==10,0,Y_train)
  #One-hot encode the labels.
  Y_train_one_hot = keras.utils.to_categorical(Y_train, 10)
  print(Y_train_one_hot.shape)
#Same for validation labels
  Y_val = np.where(Y_val==10,0,Y_val)
  Y_val_one_hot = keras.utils.to_categorical(Y_val, 10)
  print(Y_val_one_hot.shape)
if display_test_image:
    # let's take the first image in the validation set (this should be different
    # every time you run this cell).
    image = np.transpose(X_val[0].reshape(20,20))
    plt.figure(figsize = (1,1))
    plt.imshow(image, cmap='gray')
    print(Y_val[0])
  
  return [X_train, Y_train_one_hot, X_val, Y_val_one_hot]
X_train, Y_train_one_hot, X_val, Y_val_one_hot = reload_with_split()

(5000, 400)
(5000,)
(4000, 400)
(4000,)
(1000, 400)
(1000,)
(4000, 10)
(1000, 10)
5

Часть 2: Обучите глубокую нейронную сеть для классификации:

Следите в коллабе здесь.

В этой статье перечислены все тесты производительности для DNN и CNN. Для DNN попробуем 6-слойную NN 400–2500–2000–1500–1000–500–10, как подробно описано в этой статье. Мы будем использовать ReLu между плотными слоями и активацию Softmax для создания окончательных вероятностей.

from keras.layers import Dense, Activation
from keras.models import Sequential
model_dnn = Sequential([
    Dense(400, input_shape=(400,)),
    Activation('relu'),
    Dense(2500),
    Activation('relu'),
    Dense(2000),
    Activation('relu'),
    Dense(1500),
    Activation('relu'),
    Dense(1000),
    Activation('relu'),
    Dense(500),
    Activation('relu'),
    Dense(10),
    Activation('softmax')
    ])
model_dnn.summary()
reload_with_split()
model_dnn.compile(
    loss='categorical_crossentropy',
    optimizer='adam',
    metrics=['accuracy']
)
model_dnn.fit(
    X_train, 
    Y_train_one_hot,
    batch_size=512, 
    epochs=100,
    validation_data=(X_val, Y_val_one_hot),
    verbose=1
)

Разве с Керасом не все так просто :). Узнайте больше о Keras здесь.

После обучения для 100 эпох мы получаем точность 100 % (переобучение) и точность проверки ~94 %. Давайте посмотрим, как наша модель работает с реальными данными.

Часть 3. Создание собственного тестового рукописного набора данных:

Напишите цифры от 0 до 9 на листе бумаги и сфотографируйте это. Затем вы можете создать отдельные кадры для каждой из цифр, которые вы написали, и назвать их по порядку. В моем случае я назвал их 1.png, 2.png и так далее…

Давайте проверим нашу DNN на наших рукописных данных:

import cv2
#Assuming images are stored as 1.png, 2.png and so on
def test_dnn_model():
  num_px=20
  for i in range(1,10):
    image = cv2.imread(str(i) + '.png', 0)
    image_rs = cv2.resize(image,(num_px,num_px))
    # training images are grayscale and normalized
    image_rs = (255-image_rs)/255
    image_rs = np.transpose(image_rs).reshape(1,400,)
my_predicted_number = model_dnn.predict(image_rs,batch_size=1, 
                                        verbose=False)
    confidence_array = np.argsort(-my_predicted_number[0])
    print('Real: '+ str(i) +', Predicted number:' +
          str(confidence_array[0]) + ' Also likely: ' +
          str(confidence_array[1]) + ' or ' + str(confidence_array[2]))
def display_handwritten_images():
  width, height, columns, rows = 20, 20, 3, 3
  fig=plt.figure(figsize=(rows, columns))
for i in range(1, rows*columns+1):
    image = cv2.imread(str(i) + '.png', 0)
    image_rs = cv2.resize(image,(width,height))
    # training images are grayscale and normalized
    image = (255-image_rs)/255
    fig.add_subplot(rows, columns, i)
    plt.imshow(image,cmap='gray')
  plt.show()
    
test_dnn_model()
display_handwritten_images()
Real: 1, Predicted number:2 Also likely: 3 or 8
Real: 2, Predicted number:2 Also likely: 3 or 8
Real: 3, Predicted number:3 Also likely: 5 or 9
Real: 4, Predicted number:2 Also likely: 3 or 8
Real: 5, Predicted number:5 Also likely: 8 or 3
Real: 6, Predicted number:5 Also likely: 8 or 0
Real: 7, Predicted number:2 Also likely: 3 or 8
Real: 8, Predicted number:2 Also likely: 3 or 8
Real: 9, Predicted number:2 Also likely: 3 or 8

← Вот как выглядят наши рукописные изображения.

Итак, как мы справились с нашим рукописным набором данных с только что обученной DNN — 30% точность (40%, если мы рассматриваем 2-ю и 3-ю наивысшие вероятности). УРА!!! Что здесь случилось ? (Добро пожаловать в реальный мир :-)).

  1. Статистическое отклонение набора данных для обучения/проверки и новые данные. Рукописные изображения, которые у меня есть, взяты из совершенно другого статистического набора. Модели реального мира требуют постоянного сбора новых данных и обучения для поддержания точности.
  2. Наша точность проверки при разделении 80:20 составляет всего 94 %, что довольно мало (мы также подгоняли к нашим обучающим данным в DNN).

Давайте попробуем обучить сверточную нейронную сеть и посмотрим, сможем ли мы добиться большего успеха:

Часть 4. Обучите модель классификации с помощью сверточной нейронной сети (CNN).

Следите в коллабе здесь.

from keras.layers import Conv2D, MaxPooling2D, Flatten, Dropout
from keras.models import Sequential
model_cnn = Sequential([
      Conv2D(32, (3,3), activation='relu', input_shape=(20,20,1)),
      Conv2D(64, (3,3), activation='relu'),
      MaxPooling2D(2,2),
      Dropout(0.25),
      Flatten(),
      Dense(128, activation='relu'),
      Dropout(0.25),
      Dense(10, activation='softmax')
    ])
model_cnn.summary()
reload_with_split()
model_cnn.compile(
    loss='categorical_crossentropy',
    optimizer='adadelta',
    metrics=['accuracy']
)
model_cnn.fit(
    X_train.reshape(4000,20,20,1), 
    Y_train_one_hot,
    batch_size=1024, 
    epochs=40,
    validation_data=(X_val.reshape(1000,20,20,1), Y_val_one_hot),
    verbose=1
)

Обучение этой модели приводит к повышению точности проверки:

Output:
Train on 4000 samples, validate on 1000 samples Epoch 1/40 4000/4000 [==============================] - 7s 2ms/step - loss: 2.1836 - acc: 0.2563 - val_loss: 1.8490 - val_acc: 0.6260
...
Epoch 40/40 4000/4000 [==============================] - 0s 50us/step - loss: 0.0758 - acc: 0.9742 - val_loss: 0.1211 - val_acc: 0.9650

Попробуем классифицировать наши рукописные изображения:

def test_cnn_model():
  num_px=20
  for i in range(1,10):
    image = cv2.imread(str(i) + '.png', 0)
    image_rs = cv2.resize(image,(num_px,num_px))
    # training images are grayscale and normalized
    image_rs = (255-image_rs)/255
    image_rs = np.transpose(image_rs).reshape(1,20,20).reshape(1, 20,20, 1)
my_predicted_number = model_cnn.predict(image_rs,batch_size=1, 
                                        verbose=False)
    confidence_array = np.argsort(-my_predicted_number[0])
    print('Real: '+ str(i) +', Predicted number:' +
          str(confidence_array[0]) + ' Also likely: ' +
          str(confidence_array[1]) + ' or ' + str(confidence_array[2]))
    
    
test_cnn_model()
Real: 1, Predicted number:1 Also likely: 8 or 6
Real: 2, Predicted number:2 Also likely: 3 or 8
Real: 3, Predicted number:3 Also likely: 5 or 2
Real: 4, Predicted number:4 Also likely: 9 or 8
Real: 5, Predicted number:5 Also likely: 3 or 8
Real: 6, Predicted number:5 Also likely: 0 or 6
Real: 7, Predicted number:3 Also likely: 7 or 2
Real: 8, Predicted number:8 Also likely: 5 or 3
Real: 9, Predicted number:4 Also likely: 9 or 8

Неплохо! Мы получили точность около 60% (90% с учетом 2-го и 3-го наивысших вероятностей). Давайте посмотрим, что мы сделали не так:

6 › Очень близко к 5 с 3-й догадкой, классифицирующей это как 6. Возможно, здесь нам нужно больше обучающих данных. То же самое для 7.

9 › Наш образ несколько ущербен. Изменение размера и преобразование в оттенки серого явно выбили из верхней части 9. Это действительно 4 в изображении!

Давайте попробуем получить больше обучающих данных и посмотрим, что у нас получится. В следующем разделе мы будем тренироваться со всем набором данных MNIST и посмотрим, сможем ли мы повысить точность.

Часть 5. Обучение с полным набором данных MNIST:

Следите в коллабе здесь. Скопировано дословно из здесь.

from __future__ import print_function
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
batch_size = 128
num_classes = 10
epochs = 10 # we get to reasonable accuracy (~98% train/val) with 3 epochs
# input image dimensions
img_rows, img_cols = 28, 28
# the data, split between train and test sets
(x_train, y_train), (x_test, y_test) = mnist.load_data()
if K.image_data_format() == 'channels_first':
    x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
    x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
    input_shape = (1, img_rows, img_cols)
else:
    x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
    x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
    input_shape = (img_rows, img_cols, 1)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')
# convert class vectors to binary class matrices
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
model_mnist_full = Sequential()
model_mnist_full.add(Conv2D(32, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=input_shape))
model_mnist_full.add(Conv2D(64, (3, 3), activation='relu'))
model_mnist_full.add(MaxPooling2D(pool_size=(2, 2)))
model_mnist_full.add(Dropout(0.25))
model_mnist_full.add(Flatten())
model_mnist_full.add(Dense(128, activation='relu'))
model_mnist_full.add(Dropout(0.5))
model_mnist_full.add(Dense(num_classes, activation='softmax'))
model_mnist_full.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adadelta(),
              metrics=['accuracy'])
model_mnist_full.fit(x_train, y_train,
          batch_size=batch_size,
          epochs=epochs,
          verbose=1,
          validation_data=(x_test, y_test))
score = model_mnist_full.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

Мы получаем точность около 99% как на обучающих, так и на проверочных наборах! Давайте посмотрим, как мы работаем с нашими данными почерка:

def test_model_mnist_full():
  num_px=28 # model is trained with 28x28 image size
  for i in range(1,10):
    image = cv2.imread(str(i) + '.png', 0)
    image_rs = cv2.resize(image,(num_px,num_px))
    # training images are grayscale and normalized
    image_rs = (255-image_rs)/255.0
    image_rs = image_rs.reshape(1,28,28).reshape(1, 28,28, 1)
my_predicted_number = model_mnist_full.predict(image_rs,batch_size=1, 
                                        verbose=False)
    confidence_array = np.argsort(-my_predicted_number[0])
    print('Real: '+ str(i) +', Predicted number:' +
          str(confidence_array[0]) + ' Also likely: ' +
          str(confidence_array[1]) + ' or ' + str(confidence_array[2]))
    
test_model_mnist_full()
Real: 1, Predicted number:1 Also likely: 8 or 6
Real: 2, Predicted number:2 Also likely: 1 or 8
Real: 3, Predicted number:3 Also likely: 5 or 8
Real: 4, Predicted number:4 Also likely: 9 or 8
Real: 5, Predicted number:5 Also likely: 3 or 8
Real: 6, Predicted number:6 Also likely: 5 or 8
Real: 7, Predicted number:7 Also likely: 2 or 3
Real: 8, Predicted number:8 Also likely: 2 or 3
Real: 9, Predicted number:4 Also likely: 9 or 8

Вуаля, точность 90%! (100% с учетом вторичного ответа и проблемы с нашим 9).

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

Учиться весело!