въведение

Здравейте приятели, аз съм Яшвардхан Кукреджа и съм разработчик на Android с пълен стек. Започнах машинно обучение преди около 5 месеца и започнах да го овладявам прилично. Всеки път, когато започвах с някаква технологична област като android, back end или обработка на изображения, скачах директно в проекта и след това научавах неща от този проект. Но този път, докато изучавах ML, избрах по-конвенционален подход за провеждане на някои от курсовете в началото, но след това, след няколко месеца на куп курсове, получих някои идеи за основни ML проекти, които исках да направя. Започнах с тях, но след това разбрах, че курсовете не помагат много, когато става дума за работа и работа с реални данни, така че промених фокуса си обратно към обучение, базирано на проекти, и добре, това е първият ML проект, който Направих.

Знам, че много източници в интернет, книги и какво ли още не са използвали разпознаването на цифри като основен пример за обяснение на нещата, но това, което не са направили (много от тях), е разпознаването на цифри върху реални изображения, съдържащи ръкописни цифри. Може да изглежда лесно да се приложи моделът на разпознаване на цифри директно върху реално изображение, но това не е вярно. Преди да приложим този модел върху реално изображение, трябва да го обработим предварително. По принцип трябва да извлечем и изрежем цифрите от изображението и някои други неща, които ще обясня в тази статия, за да можем правилно да разпознаваме ръкописни цифри в реалния живот.

Нека се потопим

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

Само нещо малко, бих предпочел да имате основни концептуални познания за това как работи CNN, така че да останете с мен, когато се появят различни терминологии в този проект.

Това видео от Siraj Raval обяснява работата на CNN по невероятен начин.

Обратно към проекта

Така че за внедряването на CNN и цялата предварителна обработка, свързана с него, ще използвамеkeras. Това е много проста библиотека за невронни мрежи, работеща върху tensorflow.

Първо, нека импортираме всички модули, които ще се използват в нашия проект

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 автоматично предоставя много набори от данни, в които един от тях е наборът от данни с mnist ръкописни цифри.

И така, тук идва използването на „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 -› 60 000, X_test -› 10 000). След това идва формата на всяко изображение, т.е. (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). Да изградим модела

Типът модел, който ще използваме, е Последователен. Тук идва използването на „от keras.models import Sequential“.
Sequential е най-лесният начин за изграждане на модел в Keras. Позволява изграждането на модела слой по слой.
Функцията add() се използва за добавяне на последователни слоеве.

Първите 2 слоя са Conv2D слоеве. Това са слоеве на навиване, които ще работят с нашите входни изображения, които се разглеждат като 2D матрици.

Тук използвам 32 възела в първия слой и 64 възела във втория слой. Тези числа могат да бъдат съответно коригирани в зависимост от размера на набора от данни. В този случай 32 и 64 изглежда работят добре.

Размерът на ядрото е размерът на филтърната матрица за нашата конволюция. И така, размер на ядрото 3 означава, че ще се използва филтърна матрица 3x3.

Активиране е функцията за активиране на слоя. Функцията за активиране, използвана тук за първите 2 слоя, е ReLU или Rectified Linear Activation. Тази функция извежда 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 служи като връзка между навити и плътни слоеве.
  • „Плътен“ е типът слой, който се използва за изходен слой. Dense е стандартен тип слой, който се използва в много случаи за невронни мрежи.
  • Ще имаме 10 възела в нашия изходен слой, по един за всеки възможен резултат (0–9)
  • Функцията за активиране е „softmax“. Softmax прави сумата на изхода tp 1, така че изходът съдържа серия от вероятности.
  • Моделът ще предскаже този с най-голяма вероятност.

4). Компилиране на модела

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

  • Оптимизатор – Той контролира скоростта на обучение. Ще използваме оптимизатор „adam“. Това е много добър оптимизатор, тъй като използва предимствата както на Stochastic gradient, така и на RMSprop оптимизаторите.
  • Функция за загуба — Ще използваме функция за загуба „categorical_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.

Надявам се, че сте разбрали всичко за тази статия. Все пак, ако се сблъскате с някакъв проблем/съмнение относно тази статия или живот XD, чувствайте се напълно свободни да се свържете с мен.

Имейл — [email protected]

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

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