вступление

Привет, друзья, я Яшвардхан Кукреджа, и я разработчик полного стека для Android. Я начал машинное обучение около 5 месяцев назад и начал прилично владеть им. Всякий раз, когда я начинал с какой-то технической области, такой как Android, серверная часть или обработка изображений, я сразу переходил к проекту, а затем извлекал уроки из этого проекта. Но на этот раз, изучая машинное обучение, я выбрал более традиционный подход, сначала пройдя некоторые курсы, но затем, после нескольких месяцев нескольких курсов, у меня появилось несколько идей об основных проектах машинного обучения, которые я хотел реализовать. Я начал с них, но потом понял, что курсы мало помогли, когда дело дошло до обработки и возни с реальными данными, поэтому я снова переключил свое внимание на обучение на основе проектов, и что ж, это первый проект машинного обучения, который я делал.

Теперь я знаю, что многие источники в Интернете, книги и тому подобное, использовали распознавание цифр в качестве основного примера для объяснения вещей, но то, что они не сделали (многие из них), - это распознавание цифр. на реальных изображениях, содержащих рукописные цифры. Может показаться простым применить модель распознавания цифр непосредственно к реальному изображению, но это не так. Перед применением этой модели к реальному изображению нам необходимо предварительно обработать изображение. По сути, нам нужно извлекать и обрезать цифры с изображения и некоторые другие вещи, которые я объясню в этой статье, чтобы правильно распознавать реальные рукописные цифры.

Давай нырнем

Итак, теперь, прежде всего, давайте возьмем набор данных MNIST из 70 000 рукописных цифр и обучим на нем CNN (сверточную нейронную сеть) для распознавания цифр.

Небольшая вещь, я предпочитаю, чтобы вы, ребята, имели базовые концептуальные знания о том, как работает CNN, чтобы вы оставались со мной, когда в этом проекте появляются различные термины.

Это видео Сираджа Раваль прекрасно объясняет работу CNN.

Вернуться к проекту

Итак, для реализации CNN и всей связанной с этим предварительной обработки мы будем использовать keras. Это очень простая библиотека нейронной сети, переплетенная с тензорным потоком .

Во-первых, давайте импортируем все модули, которые будут использоваться в нашем проекте.

import cv2
import numpy as np
from keras.datasets import mnist
from keras.layers import Dense, Flatten
from keras.layers.convolutional import Conv2D
from keras.models import Sequential
from keras.utils import to_categorical
import matplotlib.pyplot as plt

Я объясню использование каждого из вышеперечисленных модулей по мере продвижения проекта.

Итак, во-первых, загрузим наш набор данных. Keras автоматически предоставляет множество наборов данных, одним из которых является набор данных с рукописными цифрами.

Итак, здесь используется «from keras.datasets import mnist».

Давайте инициализируем набор данных и разделим их на набор для обучения и тестирования.

(X_train, y_train), (X_test, y_test) = mnist.load_data()

Обучающий набор состоит из 60 000 изображений, а тестовый набор состоит из остальных 10 000 изображений, что очень хорошо подходит для обучения.

Теперь, чтобы получить представление о том, как выглядит изображение в наборе данных, давайте отобразим его с помощью matplotlib. Здесь используется «импортировать matplotlib.pyplot как plt».

plt.imshow(X_train[0], cmap="gray")
plt.show()
print (y_train[0])

Первые две строки кода отображают изображение, а последняя строка печатает значение цифры, соответствующей этому изображению.

Ясно, что это изображение цифры «5», следовательно, последняя строка выводит 5.

Давай начнем

1). Предварительная обработка данных - изменение формы

Это самая важная часть обучения модели. Мы должны убедиться, что наша модель смотрит на набор данных так, как она хочет, чтобы модель не обучалась беспорядочно.

Нам нужно изменить форму входных данных набора данных (X_train и X_test) в соответствии с формой, которую наша модель ожидает при обучении модели. Первое число - это количество изображений (X_train - ›60000, X_test -› 10000). Затем следует форма каждого изображения, то есть (28, 28). Последняя цифра 1 означает, что изображение в оттенках серого.

## Checking out the shapes involved in dataset
print ("Shape of X_train: {}".format(X_train.shape))
print ("Shape of y_train: {}".format(y_train.shape))
print ("Shape of X_test: {}".format(X_test.shape))
print ("Shape of y_test: {}".format(y_test.shape))

Вывод приведенного выше фрагмента

Shape of X_train: (60000, 28, 28)
Shape of y_train: (60000,)
Shape of X_test: (10000, 28, 28)
Shape of y_test: (10000,)

Итак, давайте изменим набор данных в соответствии с нашей моделью.

X_train = X_train.reshape(60000, 28, 28, 1)
X_test = X_test.reshape(10000, 28, 28, 1)

Теперь давайте посмотрим, как выглядят обновленные X_train и X_test.

print ("Shape of X_train: {}".format(X_train.shape))
print ("Shape of y_train: {}".format(y_train.shape))
print ("Shape of X_test: {}".format(X_test.shape))
print ("Shape of y_test: {}".format(y_test.shape))
#### Output
Shape of X_train: (60000, 28, 28, 1)
Shape of y_train: (60000,)
Shape of X_test: (10000, 28, 28, 1)
Shape of y_test: (10000,)

2). Быстрое кодирование

Последний уровень нашей модели CNN будет содержать 10 узлов, каждый из которых соответствует соответствующей цифре (первый узел - ›0, второй узел -› 1 и так далее). Итак, когда мы загрузим изображение в модель, модель вернет вероятности этой цифры в соответствии с каждым узлом. Таким образом, в конце предсказанная цифра будет соответствовать узлу с наибольшей вероятностью. Например, если у первого узла самая высокая вероятность, то прогнозируемая цифра равна 0.

Для всей этой операции модель ожидает, что каждая из меток будет в форме массива из 10 элементов, в котором только один из элементов = 1 (элемент / узел с наибольшей вероятностью), а остальные = 0. Итак, для что нам нужно горячее кодирование наших переменных.

Например, если изображение имеет номер 6, то метка вместо = 6 будет иметь значение 1 в столбце 7 и 0 в остальных столбцах, например [0,0,0,0,0, 0,1,0,0].

Здесь используется «from keras.utils import to_categorical»

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

Это будет делать то, что я проиллюстрировал выше.

3). Построим модель

Мы будем использовать тип модели Последовательная. Здесь используется «from keras.models import Sequential».
Последовательный - это самый простой способ создания модели в Keras. Это позволяет строить модель слой за слоем.
add () функция используется для добавления последовательных слоев.

Первые 2 слоя - это слои Conv2D. Это сверточные слои, которые будут работать с нашими входными изображениями, которые рассматриваются как 2D-матрицы.

Здесь я использую 32 узла на первом уровне и 64 узла на втором уровне. Эти числа могут быть соответственно скорректированы в зависимости от размера набора данных. В этом случае 32 и 64 работают нормально.

Размер ядра - это размер матрицы фильтра для нашей свертки. Итак, размер ядра 3 означает, что будет использоваться матрица фильтров 3x3.

Активация - это функция активации для слоя. Функция активации, используемая здесь для первых двух уровней, - это ReLU, или выпрямленная линейная активация. Эта функция выводит 0, если ввод является отрицательным числом, и выводит тот же ввод, если ввод является положительным числом. Проще говоря, ReLU- ›max (0, вход). Эта функция активации известна тем, что хорошо работает с точки зрения скорости и вывода в нейронных сетях.

Здесь используется «from keras.layers import Dense, Flatten» и «from keras.layers.convolutional import Conv2D»

## Declare the model
model = Sequential()

## Declare the layers
layer_1 = Conv2D(32, kernel_size=3, activation=’relu’, input_shape=(28, 28, 1))
layer_2 = Conv2D(64, kernel_size=3, activation=’relu’)
layer_3 = Flatten()
layer_4 = Dense(10, activation=’softmax’)

## Add the layers to the model
model.add(layer_1)
model.add(layer_2)
model.add(layer_3)
model.add(layer_4)

Течение модели

  • Первый слой принимает форму, здесь 28, 28, 1, где 1 означает оттенки серого.
  • Между слоями Conv2D и плотным слоем находится слой «Flatten». Flatten служит связующим звеном между сверточными и плотными слоями.
  • «Плотный» - это тип слоя, который используется для выходного слоя. Плотный - это стандартный тип слоя, который во многих случаях используется для нейронных сетей.
  • У нас будет 10 узлов в нашем выходном слое, по одному для каждого возможного результата (0–9).
  • Функция активации - softmax. Softmax суммирует выходные данные tp 1, так что выходные данные содержат ряд вероятностей.
  • Модель предскажет тот, который с наибольшей вероятностью.

4). Составление модели

Компиляция модели принимает три параметра:

  • Оптимизатор - контролирует скорость обучения. Мы будем использовать оптимизатор adam. Это очень хороший оптимизатор, поскольку он использует преимущества оптимизаторов как Stochastic gradient, так и RMSprop.
  • Функция потерь. Мы будем использовать функцию потерь "category_crossentropy". Это наиболее распространенный выбор для классификации. Чем ниже балл, тем лучше производительность.
  • Метрики. Чтобы упростить интерпретацию, мы будем использовать метрику «точность», чтобы увидеть оценку точности на проверочном наборе во время обучения модели.
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

5). Обучение модели

Обучим вышеуказанную модель с заданными характеристиками.

Итак, модель будет тренироваться на (X_train, y_train) и будет проверена на (X_test, y_test).

Я устанавливаю количество эпох равным 3. Вы можете увеличить количество эпох для большей точности, но здесь даже с 3 эпохами достигается замечательная точность.

1 эпоха - ›Одна итерация / цикл набора данных во всей нейронной сети

model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=3)

Выход

Train on 60000 samples, validate on 10000 samples
Epoch 1/3
60000/60000 [==============================] - 87s 1ms/step - loss: 1.4928 - acc: 0.8784 - val_loss: 0.0714 - val_acc: 0.9774
Epoch 2/3
60000/60000 [==============================] - 60s 1ms/step - loss: 0.0637 - acc: 0.9813 - val_loss: 0.0730 - val_acc: 0.9789
Epoch 3/3
60000/60000 [==============================] - 61s 1ms/step - loss: 0.0456 - acc: 0.9859 - val_loss: 0.0709 - val_acc: 0.9792

Мы видим, что наша модель достигла замечательной точности проверки 97,92%.

6). Прогнозирование и тестирование текущего набора данных

Здесь я собираюсь использовать второе изображение в наборе данных в качестве примера для тестирования обученной модели.

example = X_train[1]
prediction = model.predict(example.reshape(1, 28, 28, 1))
## First output
print ("Prediction (Softmax) from the neural network:\n\n {}".format(prediction))
## Second output
hard_maxed_prediction = np.zeros(prediction.shape)
hard_maxed_prediction[0][np.argmax(prediction)] = 1
print ("\n\nHard-maxed form of the prediction: \n\n {}".format(hard_maxed_prediction))
## Third output
print ("\n\n--------- Prediction --------- \n\n")
plt.imshow(example.reshape(28, 28), cmap="gray")
plt.show()
print("\n\nFinal Output: {}".format(np.argmax(prediction)))

Не волнуйтесь, XD. Это очень просто. Здесь я представляю прогноз несколькими способами.

  • Первый вывод - выводит список «softmaxed», состоящий из 10 вероятностей введенной цифры. Наивысшая вероятность будет соответствовать предсказанной цифре.
Prediction (Softmax) from the neural network:   [[1.0000000e+00 4.4261627e-16 4.0853871e-09 5.0804031e-12 5.4154770e-14   1.8538372e-12 1.7931812e-09 1.0223739e-11 6.2451244e-10 8.4184970e-09]]
  • Второй результат - я преобразовал этот «softmaxed» список в форму, в которой я заменил все элементы на 0, ожидая наивысшей вероятности, который я заменил на 1.
Hard-maxed form of the prediction: 

 [[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
  • Третий выход - отображает тестовое изображение и соответствующую ему прогнозируемую цифру.

Итак, мы получили правильный результат. Что ж, модель вроде бы готова к жестким боям.

Самое интересное: запуск нашей модели на реальном изображении рукописных цифр.

Итак, мы собираемся использовать изображение ниже, чтобы протестировать нашу модель на

1). Самая важная часть - предварительная обработка реального изображения.

Вот шаги для предварительной обработки изображения:

  • Преобразуйте это изображение в оттенки серого
  • Сделайте бинаризацию (порог) изображения в оттенках серого таким образом, чтобы только цифры на изображении были белыми, а остальные - черными.
  • Используя преобразованное в двоичное изображение изображение, найдите на нем контуры. Здесь контуры предоставят нам отдельные цифры на изображении.
  • Теперь у нас есть цифры. Но мы должны изменить его дальше таким образом, чтобы он стал намного больше похож на изображения, присутствующие в наборе обучающих данных.
  • Теперь посмотрим на изображение в наборе данных. Мы можем сделать вывод, что изображение должно иметь форму (28, 28), оно должно содержать цифру белого цвета и черный фон, а цифра на изображении не растягивается до границ, вместо этого вокруг цифры в каждом с четырех сторон имеется 5-пиксельная область (отступ) черного цвета. (Вы поймете это полностью, если посмотрите на любое изображение из набора данных).
  • Итак, теперь, чтобы изменить наше изображение, мы изменим его размер до (18,18)
  • Затем мы добавим заполнение нулями (черный цвет) по 5 пикселей в каждом направлении (сверху, снизу, слева, справа).
  • Итак, окончательное изображение с заполнением будет иметь размер (5 + 18 + 5, 5 + 18 + 5) = (28, 28), что мы и хотели.
image = cv2.imread('./test_image.jpg')
grey = cv2.cvtColor(image.copy(), cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(grey.copy(), 75, 255, cv2.THRESH_BINARY_INV)
_, contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
preprocessed_digits = []
for c in contours:
    x,y,w,h = cv2.boundingRect(c)
    
    # Creating a rectangle around the digit in the original image (for displaying the digits fetched via contours)
    cv2.rectangle(image, (x,y), (x+w, y+h), color=(0, 255, 0), thickness=2)
    
    # Cropping out the digit from the image corresponding to the current contours in the for loop
    digit = thresh[y:y+h, x:x+w]
    
    # Resizing that digit to (18, 18)
    resized_digit = cv2.resize(digit, (18,18))
    
    # Padding the digit with 5 pixels of black color (zeros) in each side to finally produce the image of (28, 28)
    padded_digit = np.pad(resized_digit, ((5,5),(5,5)), "constant", constant_values=0)
    
    # Adding the preprocessed digit to the list of preprocessed digits
    preprocessed_digits.append(padded_digit)
print("\n\n\n----------------Contoured Image--------------------")
plt.imshow(image, cmap="gray")
plt.show()
    
inp = np.array(preprocessed_digits)

Выход

2). Давай предскажем

Итак, я буду распечатывать выходные данные для каждой из обработанных цифр, как это было сделано для примера на шаге 6).

for digit in preprocessed_digits:
    prediction = model.predict(digit.reshape(1, 28, 28, 1))  
    
    print ("\n\n---------------------------------------\n\n")
    print ("=========PREDICTION============ \n\n")
    plt.imshow(digit.reshape(28, 28), cmap="gray")
    plt.show()
    print("\n\nFinal Output: {}".format(np.argmax(prediction)))
    
    print ("\nPrediction (Softmax) from the neural network:\n\n {}".format(prediction))
    
    hard_maxed_prediction = np.zeros(prediction.shape)
    hard_maxed_prediction[0][np.argmax(prediction)] = 1
    print ("\n\nHard-maxed form of the prediction: \n\n {}".format(hard_maxed_prediction))
    print ("\n\n---------------------------------------\n\n")

Окончательный результат

и так до…

Конец примечания

Спасибо, что добрались до этого места. Если вам понравилась эта статья, дайте мне несколько хлопков XD.

Надеюсь, вы все поняли в этой статье. Тем не менее, если у вас возникнут какие-либо проблемы / сомнения относительно этой статьи или life XD, не стесняйтесь обращаться ко мне.

Электронная почта - [email protected]

Github - https://github.com/yashvardhan-kukreja

LinkedIn - https://www.linkedin.com/in/yashvardhan-kukreja-607b24142/