影象分類之:經典機器學習 Battle 深度學習
本文寫於2018.08.31, 生日前夕。
前段時間,有個朋友和我提到,自己最近正打算用機器來判別圖片中的場景是古鎮還是園林,所以我這一期特地寫了一篇文章,來描述影象的分類演算法。由於最近工作略忙,所以文章斷斷續續寫了好久,終於在自己生日前夕完成,希望可以有所幫助,這樣我就可以安心回家吃蛋糕了。
用機器來做圖片分類簡單來講就是給機器輸入一張圖片,機器會輸出這幅圖片裡面的內容。機器學習中提供了很多對資料分類的演算法,像KNN(最近鄰)、Adaboost、Naive Bayes(樸素貝葉斯)、SVM(支援向量機)、ANN(人工神經網路)等以及最近幾年興起的CNN(卷積神經網路)。
在這篇文章的實作部分,我會挑出KNN、SVM、ANN來實現,使用的是使用經典的scikit-learn(sklearn)
程式設計的環境需求:
-
系統:windows / linux
-
直譯器:python 3.6
-
依賴庫:numpy、opencv-python 3、tensorflow、keras、scikit-learn
資料集選擇:
由於這幾天無法穿越到蘇州去採集大量園林和古鎮的圖片,而且本文還會去實驗多分類的情況,但是既然同是圖片的分類問題,我決定去網上搜集一些和風景相關的資料集。
突然有一天,在我逛GitHub的時候,它就這樣出現了,
在我的世界裡
帶給我驚喜
情不自已
為此,我給作者寫了一封信:
作者在我晚上吃飯的時候給了回覆:
這。。。。。。。
看來這條路沒走順,不如去看看一些知名的資料集裡有沒有自己需要的東西吧,不過有承諾在先,我還是會標註上資料集作者的GitHub地址:(https://github.com/yuweiming70/Landscape-Dataset),畢竟他送了我這麼多精美桌布。
牛津大學的17 Category Flower Dataset
(http://www.robots.ox.ac.uk/~vgg/data/flowers/17/index.html)很漂亮,看起來就是我在尋找的資料集(沒錯,我判斷一個東西是不是自己需要的標準就是漂不漂亮)。這個資料集總共17種花,每種花有80張圖片,整個資料集有1360張圖片,為了既達到實驗的目的又不在訓練上耗費太多的時間,我在同一種演算法上選取了前兩種花和前四種花做對比實驗:
由於SVM和ANN的原理會佔用太多的篇幅,並且這篇文章的主要目的是為了講解程式碼實現,所以這裡只介紹下機器學習中最簡單的KNN分類器:
KNN是資料探勘分類技術中最簡單的方法之一,k最近鄰,從名字大致就可以看出它的含義,就是找出K個離自己最近(相似)的資料,在這K個找到的資料中,看看那個類別最多,那麼就認為自己是屬於哪一個類別。
關於相似度度量的方法有很多,常見的有歐氏距離、曼哈頓距離、切比雪夫距離(切比雪夫兄跨界實在太多)、漢明距離、餘弦相似度(夾角餘弦值)等等。
KNN用到的是:歐氏距離(L2):
在資料(向量)只有二維的情況下,兩個資料之間的歐氏距離就是兩個點在二維座標系下的直線距離,用初中一年級的數學公式就可以算出來:
當資料推廣到多維的時候,兩個資料之間的歐式距離就變成了:
距離越小表示兩個向量相似度越大。
KNN有著實現方法簡單、無需訓練的優點,但是由於每次分類都要計算和所有資料之間的相似度,所以當資料維度很大或者資料數量很大的時候,計算會很耗時。
實驗(KNN、SVM、ANN):
現在我要使用sklearn中的KNeighborsClassifier( KNN )、SVC( SVM )、MLPClassifier( 多層感知機分類器:ANN ),來實現這三個演算法,由於sklearn中的演算法模型高度統一化,所以三個程式可以寫在同一個例子中,只是在建立分類器模型的時候略有不同:
引入KNeighborsClassifier、SVC、MLPClassifier模組:
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.neural_network import MLPClassifier
引入訓練樣本分割函式:
from sklearn.model_selection import train_test_split
引入numpy 和 opencv:
import cv2
import numpy as np
讀取影象函式,返回影象列表和標籤列表:
IMAGE_SIZE = 100
def resize_without_deformation(image, size = (IMAGE_SIZE, IMAGE_SIZE)):
height, width, _ = image.shape
longest_edge = max(height, width)
top, bottom, left, right = 0, 0, 0, 0
if height < longest_edge:
height_diff = longest_edge - height
top = int(height_diff / 2)
bottom = height_diff - top
elif width < longest_edge:
width_diff = longest_edge - width
left = int(width_diff / 2)
right = width_diff - left
image_with_border = cv2.copyMakeBorder(image, top , bottom, left, right, cv2.BORDER_CONSTANT, value=[0, 0, 0])
resized_image = cv2.resize(image_with_border, size)
return resized_image
def read_image(size = None):
data_x, data_y = [], []
#for i in range(1, 1361):
for i in range(1, 241):
try:
im = cv2.imread('17flowers/image_%s.jpg' % str(i).zfill(4))
if size is None:
size = (IMAGE_SIZE, IMAGE_SIZE)
im = resize_without_deformation(im, size)
im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
data_x.append(np.asarray(im, dtype = np.int8))
data_y.append(str(int((i-1)/80.0)))
except IOError as e:
print(e)
except:
print('Unknown Error!')
return data_x, data_y
raw_images, raw_labels = read_image(size = (IMAGE_SIZE, IMAGE_SIZE))
raw_images, raw_labels = np.asarray(raw_images, dtype = np.float32), np.asarray(raw_labels, dtype = np.int32)
由於這三種分類器只接受一維向量的輸入,所以將圖片拍扁:
raw_images = raw_images.reshape((-1, IMAGE_SIZE * IMAGE_SIZE))
分割訓練集和測試集(訓練集:測試集 = 8 : 2):
train_images, test_images, train_labels, test_labels = train_test_split(raw_images, raw_labels,
test_size = 0.2)
圖片資料歸一化:
train_images /= 255.0
test_images /= 255.0
建立分類器模型:
classifier_model = KNeighborsClassifier(n_neighbors = 7)
'''
classifier_model = SVC(C = 1.0,
kernel = 'rbf',
max_iter = 10000,
class_weight = 'balanced')
'''
'''
classifier_model = MLPClassifier(hidden_layer_sizes=(20, 100, 70), activation = 'relu',
solver = 'sgd', batch_size = 5,
learning_rate_init = 0.001, max_iter = 1000,
alpha=1e-4, tol=1e-4,
random_state=1, shuffle = True,
momentum = 0.8)
'''
訓練:
classifier_model.fit(train_images, train_labels)
計算準確率:
accuracy = classifier_model.score(test_images, test_labels)
print('Accuracy: %s' % str(accuracy))
實驗了幾次,計算準確率平均值大致得到:
-
KNN: 73.8%
-
SVM: 78.5%
-
ANN: 78.9%
上面只是2分類的情況,現在取4種花來訓練,得到準確率:
-
KNN: 40.1%
-
SVM: 40.6%
-
ANN: 41.5%
並且ANN在訓練集上的正確率表現為100%, 很明顯,已經過擬合,即模型已經呈現了記憶效應。
實驗(CNN):
現在來試下卷積神經網路(由於上篇文章已經講解過卷積神經網路的構建過程,這篇文章就不再贅述):
引入相關模組:
import keras
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dense, Dropout, Flatten
from keras.optimizers import SGD
from keras.utils import np_utils
from sklearn.model_selection import train_test_split
import cv2
import numpy as np
讀取影象函式,返回影象列表和標籤列表:
IMAGE_SIZE = 100
def resize_without_deformation(image, size = (IMAGE_SIZE, IMAGE_SIZE)):
height, width, _ = image.shape
longest_edge = max(height, width)
top, bottom, left, right = 0, 0, 0, 0
if height < longest_edge:
height_diff = longest_edge - height
top = int(height_diff / 2)
bottom = height_diff - top
elif width < longest_edge:
width_diff = longest_edge - width
left = int(width_diff / 2)
right = width_diff - left
image_with_border = cv2.copyMakeBorder(image, top , bottom, left, right, cv2.BORDER_CONSTANT, value=[0, 0, 0])
resized_image = cv2.resize(image_with_border, size)
return resized_image
def read_image(size = None):
data_x, data_y = [], []
#for i in range(1, 1361):
for i in range(1, 161):
try:
im = cv2.imread('17flowers/image_%s.jpg' % str(i).zfill(4))
#im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
if size is None:
size = (IMAGE_SIZE, IMAGE_SIZE)
im = resize_without_deformation(im, size)
data_x.append(np.asarray(im, dtype = np.int8))
data_y.append(str(int((i-1)/80.0)))
except IOError as e:
print(e)
except:
print('Unknown Error!')
return data_x, data_y
raw_images, raw_labels = read_image(size = (IMAGE_SIZE, IMAGE_SIZE))
raw_images, raw_labels = np.asarray(raw_images, dtype = np.float32), np.asarray(raw_labels, dtype = np.int32)
One-Hot編碼:
ont_hot_labels = np_utils.to_categorical(raw_labels)
分割訓練集和測試集(訓練集:測試集 = 8 : 2):
train_images, test_images, train_labels, test_labels = train_test_split(raw_images, ont_hot_labels,
test_size = 0.2)
圖片資料歸一化:
train_images /= 255.0
test_images /= 255.0
構建CNN:
採用了VGG19結構的CNN:
深度很深,訓練時間很長,特別耗記憶體和處理器(訓練的時候記得在電腦下面墊冰塊):
image_classification_model = keras.Sequential()
image_classification_model.add(Conv2D(64,(3,3),strides=(1,1),input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(64,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(MaxPooling2D(pool_size=(2,2)))
image_classification_model.add(Conv2D(128,(3,2),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(128,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(MaxPooling2D(pool_size=(2,2)))
image_classification_model.add(Conv2D(256,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(256,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(256,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(MaxPooling2D(pool_size=(2,2)))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(MaxPooling2D(pool_size=(2,2)))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(MaxPooling2D(pool_size=(2,2)))
image_classification_model.add(Flatten())
image_classification_model.add(Dense(4096,activation='relu'))
image_classification_model.add(Dropout(0.5))
image_classification_model.add(Dense(4096,activation='relu'))
image_classification_model.add(Dropout(0.5))
image_classification_model.add(Dense(len(ont_hot_labels[0]),activation='softmax'))
image_classification_model.summary()
如果想要快速訓練,也可以使用下面的簡化模型,效果也還不錯:
image_classification_model = keras.Sequential()
image_classification_model.add(Conv2D(32, 3, 3, border_mode='valid',
subsample = (1, 1),
dim_ordering = 'tf',
input_shape = (IMAGE_SIZE, IMAGE_SIZE, 3),
activation='relu'))
image_classification_model.add(Conv2D(32, 3, 3,border_mode='valid',
subsample = (1, 1),
dim_ordering = 'tf',
activation = 'relu'))
image_classification_model.add(MaxPooling2D(pool_size=(2, 2)))
image_classification_model.add(Dropout(0.25))
image_classification_model.add(Conv2D(64, 3, 3, border_mode='valid',
subsample = (1, 1),
dim_ordering = 'tf',
activation = 'relu'))
image_classification_model.add(Conv2D(64, 3, 3, border_mode='valid',
subsample = (1, 1),
dim_ordering = 'tf',
activation = 'relu'))
image_classification_model.add(MaxPooling2D(pool_size=(2, 2)))
image_classification_model.add(Dropout(0.25))
image_classification_model.add(Flatten())
image_classification_model.add(Dense(512, activation = 'relu'))
image_classification_model.add(Dropout(0.5))
image_classification_model.add(Dense(len(ont_hot_labels[0]), activation = 'sigmoid'))
image_classification_model.summary()
設定SGD優化器並編譯模型:
learning_rate = 0.01
decay = 1e-6
momentum = 0.9
nesterov = True
sgd_optimizer = SGD(lr = learning_rate, decay = decay,
momentum = momentum, nesterov = nesterov)
image_classification_model.compile(loss = 'categorical_crossentropy',
optimizer = sgd_optimizer,
metrics = ['accuracy'])
訓練,這裡只訓練30次:
batch_size = 20
epochs = 30
image_classification_model.fit(train_images, train_labels,
epochs = epochs,
batch_size = batch_size,
shuffle = True,
validation_data = (test_images, test_labels))
看看這個模型在測試集上的表現:
score = image_classification_model.evaluate(test_images, test_labels, verbose=0)
print("%s: %.2f%%" % (image_classification_model.metrics_names[1], score[1] * 100))
準確率96.88%
再試下四種花的情況,在測試集上正確率為 70%,在訓練集上正確率為 99.6%,雖然也過擬合,但是比三種經典分類器效果要好很多。
這種感覺就好像CNN把經典機器學習分類器的臉按在地上瘋狂的摩擦,不放潤滑油的那種。
不過反思一下,如果在進SVM、KNN、ANN之前,可以做一些特徵提取,效果應該會更好一些。畢竟CNN訓練和執行起來實在是太耗硬體了。
這是我的微信公眾號二維碼:
歡迎關注! 這是我的微訊號二維碼,掃一掃可以和我交流: