基於 Keras 的貓狗分類識別
更新:
本文代碼github連接:https://github.com/Sdamu/Keras_pratice
本篇主要實現利用 Keras 來實現 Kaggle 的經典比賽 ——貓狗識別,目的是熟悉 Keras 的基本用法與使用環境,爲後期利用 Keras 和 TensorFlow 實現更多的深度學習網絡打下基礎。
本篇的主要參考來自DeepLearn ,並在這個實現基礎上,增加了一些實現,如 Tensorboard 可視化訓練過程,使用 matplotlib 可視化訓練過程,保存加載模型,使用全部的訓練集等操作,後期我會同步到我的 github 上。
1. 使用自己搭建的 CNN 實現貓狗分類
1.1 數據集
可以直接從 這裏 進行下載數據,下載好後放在工程文件夾下,針對訓練數據和測試數據,分別命名爲 train
和 test
文件夾,用來存儲訓練數據和測試數據。
可以看到,訓練數據有 25000 張圖片,其中貓狗圖片各佔二分之一,測試數據有 12500 張。
1.2 圖片讀入
對於原博客 DeepLearn 直接利用 opencv 進行讀取,然後存放在內存中,這是一種直接將所有圖片讀到內存中的方法,但是當文件數量很大的時候,就會發現內存會發生溢出的現象,因此當文件數量很大的時候個人並不推薦這種方法,而是使用 keras 中的函數進行讀取。下面首先給出將所有圖片讀入到內存中的代碼:
resize = 224
def load_data():
imgs = os.listdir("./train/")
train_data = np.empty((5000, resize, resize, 3), dtype="int32")
train_label = np.empty((5000, ), dtype="int32")
test_data = np.empty((5000, resize, resize, 3), dtype="int32")
test_label = np.empty((5000, ), dtype="int32")
print("load training data")
for i in trange(5000):
if i % 2:
train_data[i] = cv2.resize(cv2.imread('./train/' + 'dog.' + str(i) + '.jpg'), (resize, resize))
train_label[i] = 1
else:
train_data[i] = cv2.resize(cv2.imread('./train/' + 'cat.' + str(i) + '.jpg'), (resize, resize))
train_label[i] = 0
print("\nload testing data")
for i in trange(5000, 10000):
if i % 2:
test_data[i-5000] = cv2.resize(cv2.imread('./train/' + 'dog.' + str(i) + '.jpg'), (resize, resize))
test_label[i-5000] = 1
else:
test_data[i-5000] = cv2.resize(cv2.imread('./train/' + 'cat.' + str(i) + '.jpg'), (resize, resize))
test_label[i-5000] = 0
return train_data, train_label, test_data, test_label
train_data, train_label, test_data, test_label = load_data()
train_data, test_data = train_data.astype('float32'), test_data.astype('float32')
train_data,test_data = train_data/255.0, test_data/255.0
# 變爲 one-hot 向量
train_label = keras.utils.to_categorical(train_label,2)
test_label = keras.utils.to_categorical(test_label,2)
從上面的代碼種可以看出,因爲需要將所有圖片都讀入到內存中,因此並未使用所有的圖片進行訓練,而僅僅使用了一小部分圖片。
當我們想利用全部圖片進行訓練時,會發現這種方法並不能滿足我們的要求,因此考慮使用 keras 中的內置函數 .flow_from_directory
詳見 keras,但是在使用這種方法前,我們需要重新整理一下我們數據集的文件夾結構,具體如下所示:
在整理好數據集後,我們利用 keras 中的 ImageDataGenerator
類的相關操作進行數據的整理和生成,這部分代碼如下所示:
from keras.preprocessing.image import ImageDataGenerator
# 訓練樣本目錄和測試樣本目錄
train_dir = './data/train/'
test_dir = './data/validation/'
# 對訓練圖像進行數據增強
train_pic_gen = ImageDataGenerator(rescale=1./255,
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.5,
horizontal_flip=True,
fill_mode='nearest')
# 對測試圖像進行數據增強
test_pic_gen = ImageDataGenerator(rescale=1./255)
# 利用 .flow_from_directory 函數生成訓練數據
train_flow = train_pic_gen.flow_from_directory(train_dir,
target_size=(224,224),
batch_size=64,
class_mode='categorical')
# 利用 .flow_from_directory 函數生成測試數據
test_flow = test_pic_gen.flow_from_directory(test_dir,
target_size=(224,224),
batch_size=64,
class_mode='categorical')
print(train_flow.class_indices)
利用最後的輸出我們可以看到程序根據我們的目錄已經自動將訓練文件分爲了兩個類別,即 cat :0
和 dog:1
,具體輸出如下所示:
Using TensorFlow backend.
Found 22500 images belonging to 2 classes.
Found 2500 images belonging to 2 classes.
{'cat': 0, 'dog': 1}
1.3 搭建網絡
在完成了數據的讀取任務後,需要進行的是搭建網絡,這裏只搭建一個簡單的 CNN 就結構,因此使用 Keras 中的 Sequential
即可完成任務。具體代碼如下所示:
model = Sequential()
# level1
model.add(Conv2D(filters=96,kernel_size=(11,11),
strides=(4,4),padding='valid',
input_shape=(resize,resize,3),
activation='relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(3,3),
strides=(2,2),
padding='valid'))
# level_2
model.add(Conv2D(filters=256,kernel_size=(5,5),
strides=(1,1),padding='same',
activation='relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(3,3),
strides=(2,2),
padding='valid'))
# layer_3
model.add(Conv2D(filters=384,kernel_size=(3,3),
strides=(1,1),padding='same',
activation='relu'))
model.add(BatchNormalization())
model.add(Conv2D(filters=384,kernel_size=(3,3),
strides=(1,1),padding='same',
activation='relu'))
model.add(BatchNormalization())
model.add(Conv2D(filters=356,kernel_size=(3,3),
activation='relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(3,3),
strides=(2,2),padding='valid'))
# layer_4
model.add(Flatten())
model.add(Dense(4096,activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(4096,activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1000,activation='relu'))
model.add(Dropout(0.5))
# output layer
model.add(Dense(2))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy',
optimizer='sgd',
metrics=['accuracy'])
1.4 訓練數據
在網絡搭建完成後,需要將數據餵給網絡進行訓練,這裏直接使用 keras 中的 fit_generator
即可完成相關工作,具體代碼如下所示:
his = model.fit_generator(data_move.train_flow,
steps_per_epoch=100,
epochs=50,
verbose=1,
validation_data=data_move.test_flow,
validation_steps=500,
callbacks=[tbCallBack])
這裏簡單介紹一下這個函數,即 fit_generator
, 這部分內容參考博客 fit_generator(). 函數定義如下所示:
fit_generator(self, generator, steps_per_epoch=None, epochs=1, verbose=1, callbacks=None, validation_data=None, validation_steps=None, class_weight=None, max_queue_size=10, workers=1, use_multiprocessing=False, shuffle=True, initial_epoch=0)
這個函數的作用是:通過Python generator產生一批批的數據用於訓練模型。generator可以和模型並行運行,例如,可以使用CPU生成批數據同時在GPU上訓練模型。
參數:
- generator:一個generator或Sequence實例,爲了避免在使用multiprocessing時直接複製數據。
- steps_per_epoch:從generator產生的步驟的總數(樣本批次總數)。通常情況下,應該等於數據集的樣本數量除以批量的大小。
- epochs:整數,在數據集上迭代的總數。
- works:在使用基於進程的線程時,最多需要啓動的進程數量。
- use_multiprocessing:布爾值。當爲True時,使用基於基於過程的線程。
爲了可以更加形象的可視化我們的訓練過程,因此引入 keras 中的 history 參數,利用這個參數結合 matplotlib 可以很方便的畫出 loss 和 accuracy 的迭代曲線,只需要添加很少的代碼即可完成這部分工作:
plt.plot(his.history['acc'])
plt.plot(his.history['val_acc'])
plt.title('model_accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
plt.plot(his.history['loss'])
plt.plot(his.history['val_loss'])
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
除了利用 matplotlib 進行可視化外,因爲我按裝的 Keras 是基於 Tensorflow 的,因此還可以調用 TensorBoard 進行做圖,具體來說,只需要在 fit_generator
參數種令 callbacks 參數指向 Tensorboard 即可。
2. 利用訓練好的模型進行遷移學習
除了利用我們自己搭建的網絡進行訓練學習外,我們還可以使用 keras 中已經訓練好的權重進行遷移學習,這裏以 VGG16 爲例,數據的讀取方法和上面介紹的大致相同,因此不再進行贅述,這裏直接給出相關的代碼:
import keras
import matplotlib.pyplot as plt
from keras.layers import Dense,Dropout
from keras.layers import GlobalAveragePooling2D
from keras.applications.vgg16 import VGG16
from keras.models import Model
import data_move
resize = 224
tbCallBack = keras.callbacks.TensorBoard(log_dir='./logs/1',
histogram_freq= 0,
write_graph=True,
write_images=True)
base_model = VGG16(weights='imagenet', include_top=False, pooling=None,
input_shape=(resize, resize, 3), classes = 2)
for layer in base_model.layers:
layer.trainable = False
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(64, activation='relu')(x)
x = Dropout(0.5)(x)
predictions = Dense(2, activation='sigmoid')(x)
model = Model(inputs=base_model.input, outputs=predictions)
model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'],)
his = model.fit_generator(data_move.train_flow,
steps_per_epoch=100,
epochs=50,
verbose=1,
validation_data=data_move.test_flow,
validation_steps=500,
callbacks=[tbCallBack])
plt.plot(his.history['acc'])
plt.plot(his.history['val_acc'])
plt.title('model_accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
plt.plot(his.history['loss'])
plt.plot(his.history['val_loss'])
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
model.save('./weights/catdogs_model.h5')
其中,data_move.py 也就是上面提到的讀入數據的部分,下面給出完整代碼:
from keras.preprocessing.image import ImageDataGenerator
train_dir = './data/train/'
test_dir = './data/validation/'
train_pic_gen = ImageDataGenerator(rescale=1./255,
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.5,
horizontal_flip=True,
fill_mode='nearest')
test_pic_gen = ImageDataGenerator(rescale=1./255)
train_flow = train_pic_gen.flow_from_directory(train_dir,
target_size=(224,224),
batch_size=64,
class_mode='categorical')
test_flow = test_pic_gen.flow_from_directory(test_dir,
target_size=(224,224),
batch_size=64,
class_mode='categorical')
print(train_flow.class_indices)
3. 測試
在訓練完成後,我們可以利用 keras 的 predict
函數,給出一張圖片,我們對其進行相應處理後,直接利用 predict
進行預測。具體代碼如下所示:
model.load_weights('./weights/catdogs_model.h5')
test_image = cv2.resize(cv2.imread('./43.jpg'),(224,224))
test_image = np.asarray(test_image.astype("float32"))
test_image = test_image/255.
test_image = test_image.reshape((1,224,224,3))
preds = model.predict(test_image)
if preds.argmax() == 0:
print("cat")
else:
print("dog")
通過上面的簡單程序,就可以很方便的識別出我們提供的圖片屬於貓或狗。
4.代碼
全部代碼將會上傳到我的 github,後期會進行更新。