解決問題 Convolutional Neural Networks (1)

教學目標

初步了解如何透過 AWS 雲端服務解決圖片識別分類應用的問題。

重點概念

首先人工智慧有許多應用皆是進行圖片識別為基礎,然而針對圖片分類的教學皆是以 MNIST 或 CIFAR-10 為主,其中資料皆是已經準備好,而我們僅需要透過一個函數就能夠把所有資料皆載入進行,所以相對簡單適合初學者進行概念的學習。但是若在實務上若我們要採用特定的圖片進行分類時,要如何從頭開始呢?此時我們就能夠透過 AWS 深度學習映像檔產生的雲端機器進行圖片識別,並且撰寫 Python 程式以 Keras 搭配 Tensorflow 後端引擎進行模型訓練,當模型訓練完成之後,我們就能夠針對任何一張圖片進行識別。

接著我們透過 MobaXterm 工具登入至雲端機器之後,主要有五個步驟,分別為:

  1. 啟動 TensorFlow 環境。
  2. 安裝更新套件與確認版本。
  3. 將檔案分類至資料夾中。
  4. 訓練圖片識別模型。
  5. 預測圖片識別分類。

啟動 TensorFlow 環境

1
$ source activate tensorflow_p36

安裝更新套與確認版本

1
2
3
4
5
6
7
8
9
10
$ pip install tensorflow
$ pip install keras
$ pip install opencv-python
$ pip install imutils
$ pip install sklearn
$ python
$ import tensorflow as tf
$ tf.__version__
$ import keras
$ keras.__version__

將檔案分類至資料夾中

1
2
3
4
5
6
7
8
9
10
11
from shutil import copyfile
import csv
import os.path
train_data = []
with open('/home/ubuntu/ai/train.csv') as csvfile:
readCSV = csv.reader(csvfile, delimiter=',')
for row in readCSV:
train_data.append(row)
print(row[0],row[1])
if os.path.isfile('/home/ubuntu/ai/test/' + row[0] + '.png') :
copyfile('/home/ubuntu/ai/test/' + row[0] + '.png', '/home/ubuntu/ai/train/' + row[1] + '/' + row[0] + '.png')

訓練圖片識別模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dense
from keras import backend as K
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import Adam
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import img_to_array
from keras.utils import to_categorical
from imutils import paths
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import argparse
import random
import cv2
import os
import sys

matplotlib.use("Agg")
sys.path.append('..')

EPOCHS = 3
INIT_LR = 1e-3
BS = 32
CLASS_NUM = 62
norm_size = 64

class LeNet:
@staticmethod
def build(width, height, depth, classes):
model = Sequential()
inputShape = (height, width, depth)
if K.image_data_format() == "channels_first": #for tensorflow
inputShape = (depth, height, width)
model.add(Conv2D(20, (5, 5),padding="same",input_shape=inputShape))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Conv2D(50, (5, 5), padding="same"))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Flatten())
model.add(Dense(500))
model.add(Activation("relu"))
model.add(Dense(classes))
model.add(Activation("softmax"))
return model

def load_data(path):
print("[資訊] 載入圖片...")
data = []
labels = []
imagePaths = sorted(list(paths.list_images(path)))
random.seed(42)
random.shuffle(imagePaths)
for imagePath in imagePaths:
image = cv2.imread(imagePath)
image = cv2.resize(image, (norm_size, norm_size))
image = img_to_array(image)
data.append(image)
label = int(imagePath.split(os.path.sep)[-2])
labels.append(label)
data = np.array(data, dtype="float") / 255.0
labels = np.array(labels)
labels = to_categorical(labels, num_classes=CLASS_NUM)
return data,labels

def train(aug,trainX,trainY,testX,testY):
model = LeNet.build(width=norm_size, height=norm_size, depth=3, classes=CLASS_NUM)
opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])

print("[資訊] 訓練模型...")
H = model.fit_generator(aug.flow(trainX, trainY, batch_size=BS),
validation_data=(testX, testY), steps_per_epoch=len(trainX),
epochs=EPOCHS, verbose=1)

print("[資訊] 儲存模型...")
model.save('image_classification.model')

model = LeNet.build(width=norm_size, height=norm_size, depth=3, classes=CLASS_NUM)
opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])

print("[資訊] 訓練模型...")
H = model.fit_generator(aug.flow(trainX, trainY, batch_size=BS),
validation_data=(testX, testY), steps_per_epoch=len(trainX),
epochs=EPOCHS, verbose=1)
print("[資訊] 儲存模型...")
model.save('image_classification.model')

train_file_path = '/home/ubuntu/ai/train/'
test_file_path = '/home/ubuntu/ai/validation/'
trainX,trainY = load_data(train_file_path)
testX,testY = load_data(test_file_path)
aug = ImageDataGenerator(rotation_range=30, width_shift_range=0.1,
height_shift_range=0.1, shear_range=0.2, zoom_range=0.2,
horizontal_flip=True, fill_mode="nearest")
train(aug,trainX,trainY,testX,testY);

預測圖片識別分類

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from keras.preprocessing.image import img_to_array
from keras.models import load_model
import numpy as np
import imutils
import cv2
norm_size = 64
def predict():
model = load_model('image_classification.model')
predict_result=[]
for i in range(1,20000):
img_path = '/home/ubuntu/ai/'+str(i)+'.png'
image = cv2.imread(img_path)
print("[資訊] 預測圖片" + img_path)
image = cv2.resize(image, (norm_size, norm_size))
image = image.astype("float") / 255.0
image = img_to_array(image)
image = np.expand_dims(image, axis=0)
result = model.predict(image)[0]
proba = np.max(result)
label = str(np.where(result==proba)[0])
label = "{}: {:.2f}%".format(label, proba * 100)
predict_result.append([i,label[1]])
import pandas as pd
df = pd.DataFrame(predict_result)
df.to_csv("/home/ubuntu/ai/result.csv")

predict()

再來本篇主要採用 LeNet 經典深度學習的卷積神經網路,早在 1998 年就被提出,主要用於手寫數字的識別,已經有完整的卷積神經網路的基本框架構,包括卷積、激活、池化和全連接等元件,但是在 1998 年之後,深度學習並沒有太多的突破,直到 2012 年資料量的快速成長、運算速度的提升,以及更多有關卷積神經網路,像是 AlexNet,才開啟深度學習的全新時代,進行快速發展的階段。

最後我們僅需要透過雲端服務,採用「p2.xlarge」等級的 EC2 雲端主機,就能夠在十五分鐘內透過上述五個步驟針對 64×64 的二萬張圖片以 LeNet 經典深度學習的卷積神經網路進行模型訓練,以及再針對二萬張圖片進行識別預測,輕鬆達到分類率為 97% 的結果。但若是要達到分類率為 99% 的精準度,我們就必須再花時間深入了解更多有關卷積神經網路的應用,像是 AlexNet、VGG、GoogLeNet、ResNet、SENet、…等。此外我們測試時建議可以先在本機電腦安裝 Anaconda 啟動 Jupyter Notebook 網站工具進行資料處理和模型建置與測試,待確認無誤之後,再部署至雲端主機進行快速的運算。

預測圖片識別結果

(註:此結果主要是將 Epoch 設為 50,但是實際測試的預測結果準確率還是 97%,僅供參考。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
Epoch 1/50
20000/20000 [==============================] - 687s 34ms/step - loss: 0.1871 - acc: 0.9121 - val_loss: 0.0693 - val_acc: 0.9674
Epoch 2/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0871 - acc: 0.9590 - val_loss: 0.0589 - val_acc: 0.9681
Epoch 3/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0699 - acc: 0.9671 - val_loss: 0.0504 - val_acc: 0.9738
Epoch 4/50
20000/20000 [==============================] - 682s 34ms/step - loss: 0.0609 - acc: 0.9712 - val_loss: 0.0353 - val_acc: 0.9839
Epoch 5/50
20000/20000 [==============================] - 682s 34ms/step - loss: 0.0545 - acc: 0.9743 - val_loss: 0.0328 - val_acc: 0.9842
Epoch 6/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0501 - acc: 0.9768 - val_loss: 0.0317 - val_acc: 0.9850
Epoch 7/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0461 - acc: 0.9786 - val_loss: 0.0304 - val_acc: 0.9849
Epoch 8/50
20000/20000 [==============================] - 679s 34ms/step - loss: 0.0436 - acc: 0.9798 - val_loss: 0.0288 - val_acc: 0.9866
Epoch 9/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0407 - acc: 0.9815 - val_loss: 0.0320 - val_acc: 0.9847
Epoch 10/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0392 - acc: 0.9823 - val_loss: 0.0327 - val_acc: 0.9843
Epoch 11/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0371 - acc: 0.9834 - val_loss: 0.0303 - val_acc: 0.9873
Epoch 12/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0351 - acc: 0.9842 - val_loss: 0.0253 - val_acc: 0.9888
Epoch 13/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0340 - acc: 0.9848 - val_loss: 0.0165 - val_acc: 0.9928
Epoch 14/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0327 - acc: 0.9855 - val_loss: 0.0258 - val_acc: 0.9886
Epoch 15/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0317 - acc: 0.9860 - val_loss: 0.0200 - val_acc: 0.9908
Epoch 16/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0305 - acc: 0.9869 - val_loss: 0.0184 - val_acc: 0.9921
Epoch 17/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0292 - acc: 0.9874 - val_loss: 0.0238 - val_acc: 0.9899
Epoch 18/50
20000/20000 [==============================] - 678s 34ms/step - loss: 0.0282 - acc: 0.9876 - val_loss: 0.0197 - val_acc: 0.9921
Epoch 19/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0274 - acc: 0.9882 - val_loss: 0.0130 - val_acc: 0.9950
Epoch 20/50
20000/20000 [==============================] - 682s 34ms/step - loss: 0.0264 - acc: 0.9886 - val_loss: 0.0166 - val_acc: 0.9930
Epoch 21/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0263 - acc: 0.9888 - val_loss: 0.0166 - val_acc: 0.9937
Epoch 22/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0254 - acc: 0.9891 - val_loss: 0.0136 - val_acc: 0.9946
Epoch 23/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0246 - acc: 0.9896 - val_loss: 0.0124 - val_acc: 0.9950
Epoch 24/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0239 - acc: 0.9899 - val_loss: 0.0137 - val_acc: 0.9944
Epoch 25/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0237 - acc: 0.9901 - val_loss: 0.0130 - val_acc: 0.9947
Epoch 26/50
20000/20000 [==============================] - 682s 34ms/step - loss: 0.0226 - acc: 0.9905 - val_loss: 0.0175 - val_acc: 0.9925
Epoch 27/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0223 - acc: 0.9908 - val_loss: 0.0104 - val_acc: 0.9962
Epoch 28/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0222 - acc: 0.9907 - val_loss: 0.0171 - val_acc: 0.9931
Epoch 29/50
20000/20000 [==============================] - 682s 34ms/step - loss: 0.0214 - acc: 0.9912 - val_loss: 0.0104 - val_acc: 0.9969
Epoch 30/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0212 - acc: 0.9912 - val_loss: 0.0100 - val_acc: 0.9958
Epoch 31/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0208 - acc: 0.9914 - val_loss: 0.0117 - val_acc: 0.9952
Epoch 32/50
20000/20000 [==============================] - 679s 34ms/step - loss: 0.0201 - acc: 0.9917 - val_loss: 0.0120 - val_acc: 0.9953
Epoch 33/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0204 - acc: 0.9916 - val_loss: 0.0149 - val_acc: 0.9946
Epoch 34/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0194 - acc: 0.9920 - val_loss: 0.0107 - val_acc: 0.9958
Epoch 35/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0192 - acc: 0.9920 - val_loss: 0.0089 - val_acc: 0.9964
Epoch 36/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0190 - acc: 0.9922 - val_loss: 0.0095 - val_acc: 0.9964
Epoch 37/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0187 - acc: 0.9924 - val_loss: 0.0100 - val_acc: 0.9961
Epoch 38/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0184 - acc: 0.9926 - val_loss: 0.0149 - val_acc: 0.9950
Epoch 39/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0181 - acc: 0.9928 - val_loss: 0.0104 - val_acc: 0.9958
Epoch 40/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0178 - acc: 0.9927 - val_loss: 0.0139 - val_acc: 0.9940
Epoch 41/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0179 - acc: 0.9927 - val_loss: 0.0093 - val_acc: 0.9959
Epoch 42/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0171 - acc: 0.9931 - val_loss: 0.0093 - val_acc: 0.9964
Epoch 43/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0171 - acc: 0.9932 - val_loss: 0.0127 - val_acc: 0.9946
Epoch 44/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0167 - acc: 0.9933 - val_loss: 0.0083 - val_acc: 0.9964
Epoch 45/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0170 - acc: 0.9932 - val_loss: 0.0081 - val_acc: 0.9971
Epoch 46/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0164 - acc: 0.9934 - val_loss: 0.0079 - val_acc: 0.9971
Epoch 47/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0159 - acc: 0.9936 - val_loss: 0.0095 - val_acc: 0.9967
Epoch 48/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0162 - acc: 0.9935 - val_loss: 0.0089 - val_acc: 0.9962
Epoch 49/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0158 - acc: 0.9938 - val_loss: 0.0108 - val_acc: 0.9960
Epoch 50/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0156 - acc: 0.9938 - val_loss: 0.0075 - val_acc: 0.9969

相關資源