5.2 在小型資料集上從頭開始訓練一個卷積神經網絡
- 資料集是Kaggle中的dogs_vs_cats是一個典型的圖像分類問題
- 可能需要使用GPU,GPU如果沒有可以使用Google Colab中的免費GPU。
Kaggle中的這個資料集中一共有25000張圖檔,其中貓狗各有12500張。資料集中已經分好了測試資料和訓練資料。當然現在機器學習算法發展迅速,我們可以僅僅使用4000張貓狗的圖檔(2000張貓,2000張狗)使得訓練結果就非常好。我們将2000張圖像用于訓練,1000張用于驗證,1000張用于測試。(當然資料集越大訓練結果越好,但是這裡研究的是小資料集,因為後面還會介紹資料增強等内容,是以小資料集再合适不過了)
首先要進行資料的下載下傳工作,直接從kaggle下載下傳就可以了https://www.kaggle.com/c/dogs-vs-cats/data。
檔案夾中已經分好了測試集和訓練集。kaggle賬号注冊很簡單,但是要下載下傳資料集需要驗證你的手機号,這一步有時候并不太容易做到。那麼就去網上找找吧,應該很容易找到,CSDN也有。
這是資料集中的内容,将檔案解壓到你電腦的相應位址就可以在jupyter notebook上進行學習了。但是jupyter notebook使用的是本地的CPU(如果你沒有GPU的話,有就當我沒說),訓練速度比較慢。
要使用Google Colab首先要挂載Google Drive ,操作方法可以參考這個博文https://blog.csdn.net/qq_43028656/article/details/119849938。
我們要使用4000張圖檔,下面進行資料的整理:
import os,shutil
original_dataset_dir = 'D:/ALL_code\Anaconda/Deep_Learning_Study/chapter5/kaggle_original_data/train' #設定原始資料的路徑
base_dir = 'D:/ALL_code/Anaconda/Deep_Learning_Study/chapter5/kaggle_original_data/cats_and_dogs_small'
os.mkdir(base_dir) #由于隻使用資料中的一部分 兩千張cat兩千張dog,是以在檔案夾中建立一個較小的檔案集合用以存放部分資料
train_dir = os.path.join(base_dir, 'train')
#将train和前面的路徑連接配接起來
os.mkdir(train_dir)
#建立檔案夾
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)
# 分别對應劃分後的訓練集驗證集和測試集
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)
#在train檔案夾裡建立cats和dogs的檔案夾,存儲用于訓練的圖檔
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)
#在validation檔案夾裡建立兩個檔案夾,cats和dogs,存儲用于驗證的圖檔
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)
#在test中建立兩個檔案夾,用于存取測試用的圖檔
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_cats_dir, fname)
shutil.copyfile(src, dst)
#将前1000張貓的圖檔複制到train_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_cats_dir, fname)
shutil.copyfile(src, dst)
# 将接下來500張貓的圖像複制到validation_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_cats_dir, fname)
shutil.copyfile(src, dst)
# 将接下來500張貓的圖像複制到test_cats_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_dogs_dir, fname)
shutil.copyfile(src, dst)
#将前1000張狗的圖檔複制到train_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_dogs_dir, fname)
shutil.copyfile(src, dst)
# 将接下來500張狗的圖像複制到validation_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_dogs_dir, fname)
shutil.copyfile(src, dst)
# 将接下來500張狗的圖像複制到test_dogs_dir
# 自己的硬碟大概運作45s
完成後将會看到檔案夾裡面的内容是這樣的,每個檔案夾裡都有cat和dog檔案夾,裡面分别存放着貓和狗的圖檔。到這裡我們需要用到的資料就整理好了。
接下來是模型建構和配置,資料預處理以及訓練:
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
#第一個conv2d要進行參數計算是896 896=(9*3+1)*32 9是(3,3)的濾鏡資料量,原市資料(150, 150, 3)通道數為3 是以要9*3 +1是截距項
#一共有32層 是以×32
#配置用于訓練的模型
from keras import optimizers
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])
資料預處理
将資料輸入神經網絡之前,應該将資料格式化為胫骨哦預處理的浮點數張量
步驟如下:
- 讀取圖像檔案
- 将JPEG檔案解碼為RGB像素網格
- 将這些像素網格轉換為浮點數張量
- 将像素值(0~255範圍内)縮放到[0,1]區間
keras擁有自動完成這些步驟的工具
圖像輔助子產品: keras.preprocessing.image包含ImageDataGenerator類,可以快速建立python生成器
from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
train_dir,
target_size=(150, 150),
batch_size=20,
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=20,
class_mode='binary')
運作後有如下輸出,說明我們需要使用的資料已經預處理完成
Found 2000 images belonging to 2 classes. Found 1000 images belonging to 2 classes.
#使用批量生成器拟合模型
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=30,
validation_data=validation_generator,
validation_steps=50)
model.save('cats_and_dogs_small_1.h5')#儲存模型
這個過程在我自己的CPU上跑一個epoch大概需要55s,在雲端的GPU上跑一個epoch隻需要13s, 但是第一個epoch比較慢(我跑了1305s)具體原因我也沒有找到,但是整體來說還是GPU跑得快。
#繪制損失曲線和精度曲線
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title("Training and Validation accuracy")
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss,'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
繪制結果如下:
從這些圖像可以看出過拟合的特征,訓練精度随着時間線性增加,直到接近100%,而驗證精度則一直停留在70%~72%。驗證損失在第5輪以後就達到了最小值,然後保持不變,而訓練損失則一直線性下降直到0。深度學習處理圖像時的幾乎都會使用到資料增強來解決過拟合。
資料增強 ( data augmentation)
- 計算機視覺領域經常使用的處理過拟合的方法
- 實作:利用多種能夠生成可信圖像的随機變換來增加樣本
- keras中使用ImageDataGenerator執行個體讀取的圖像執行多次随機變換來實作
original_dataset_dir = 'D:/ALL_code/Anaconda/Deep_Learning_Study/chapter5/kaggle_original_data'
base_dir = 'D:/ALL_code/Anaconda/Deep_Learning_Study/chapter5/kaggle_original_data/cats_and_dogs_small'
# os.mkdir(base_dir)
train_dir = 'D:/ALL_code/Anaconda/Deep_Learning_Study/chapter5/kaggle_original_data/cats_and_dogs_small/train'
validation_dir = 'D:/ALL_code/Anaconda/Deep_Learning_Study/chapter5/kaggle_original_data/cats_and_dogs_small/validation'
test_dir = 'D:/ALL_code/Anaconda/Deep_Learning_Study/chapter5/kaggle_original_data/cats_and_dogs_small/test'
from keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(
rotation_range=40, #角度值,表示圖像随機旋轉的角度範圍
width_shift_range=0.2, #水準方向平移的範圍
height_shift_range=0.2,
shear_range=0.2, # 随機錯切變換的角度
zoom_range=0.2, # 随機縮放的範圍
horizontal_flip=True, # 随機将一半圖像水準翻轉
)# 用于填充新建立像素的方法
使用了資料增強以後還不能完全消除過拟合,由于資料來自于少量的原市圖像,是以網絡看到的輸入仍然是高度相關的。為了進一步降低過拟合,向模型中添加一個Dropout層,添加到密集連接配接分類器之前。
#定義一個包含dropout的新卷積神經網絡
from keras import layers
from keras import models
from keras import optimizers
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Dropout(0.5))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])
訓練模型:
#使用資料增強生成器訓練卷積神經網絡
train_datagen = ImageDataGenerator(
rescale=1./255,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True, )
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
train_dir,
target_size=(150, 150),
batch_size=32,
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=32,
class_mode='binary')
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=100,
validation_data=validation_generator,
validation_steps=50)
model.save('cats_and_dogs_small_2.h5')
這一過程在CPU上面一個epoch要持續90s左右,100個epoch跑了兩個半小時,在GPU上隻跑了半個小時。注意:發現一個問題,在GPU上跑的時候設定batch_size=32,會報錯,說訓練的範圍查過了資料的範圍,由于隻有2000個訓練資料,設定32個epoch會超出2000的範圍,是以我訓練的時候将32改回了原來的20。奇怪的是在本地CPU上跑的時候并沒有報這個錯誤,查資料也沒查出個是以然。。。
#繪制損失曲線和精度曲線
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title("Training and Validation accuracy")
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss,'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
繪制結果如下:
現在得到的精度達到了84%左右,比未正則的模型提高了15%(相對比例),通過進一步的正則以及調節網絡參數可以使結果更好,接下來的内容會繼續講述。