嗨,各位好久不見啦!
最近因為 系上學長要求(誤),開始學習遷移式學習(transfer learning)
順便記錄過程。目前也還在學習中,所以有誤或有更專業方法的話,歡迎各位不吝嗇給予反饋與建議!是說本來想說鐵人比賽結束就不用了,不過發現自己的文章有人追蹤訂閱,所以就還是寫寫東西好了。
遷移學習就是把已經訓練過的模型參數遷移到新的模型,幫助新模型的訓練,加快新模型的訓練的效率,因為不需要從零開始學習,當然網路上可以找到更詳細的介紹,這裡就不細說明囉。
import warnings
warnings.filterwarnings('ignore')
如標題所示,這裡我主要是用keras
而不是tensorflow
,keras是已經被包裝過的深度學習套件,適合新手(我)使用!當然除了深度學習套件以外還有基本的Data Science會用到的套件(在我的鐵人文章有介紹過)。
import numpy as np
import keras
from keras import backend as K
from keras.models import Sequential
from keras.layers import Activation
from keras.layers.core import Dense, Flatten
from keras.optimizers import Adam
from keras.metrics import categorical_crossentropy
from keras.preprocessing.image import ImageDataGenerator
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import *
from sklearn.metrics import confusion_matrix
from keras.models import load_model
import itertools
import matplotlib.pyplot as plt
from PIL import Image
分別有訓練train
與測試test
和驗證valid
資料集,裡面分別放入貓
與狗
的資料夾,裡面再放入貓和狗的圖片檔。
.
├── transfer-learning-with-keras.ipynb
├── test/
│ ├── cats/
│ └── dogs/
├── train/
│ ├── cats/
│ └── dogs/
└── valid/
├── cats/
└── dogs/
train_path = './train'
valid_path = './valid'
test_path = './test'
ImageDataGenerator
顧名思義就是用來產生圖片資料的:
用以生成一個批次的圖像數據。訓練時該函數會無限生成數據,直到達到規定的epoch
次數為止。
train_batches = ImageDataGenerator().flow_from_directory(train_path, target_size=(224,224), classes=['dogs', 'cats'], batch_size=10)
valid_batches = ImageDataGenerator().flow_from_directory(valid_path, target_size=(224,224), classes=['dogs', 'cats'], batch_size=4)
test_batches = ImageDataGenerator().flow_from_directory(test_path, target_size=(224,224), classes=['dogs', 'cats'], batch_size=10)
flow_from_directory()
:
以文件夾路徑為參數,生成經過數據提升/歸一化後的數據,在一個無限循環中無限制生產批次資料。
classes
:
是可選參數,為子文件夾的列表,如上我們分別為['dogs','cats']的分類,默認為無。若未提供,則該類別列表將從目錄下的子文件夾名稱/結構自動推斷。每一個子文件夾都會被認為是一個新的類!
target_size=(224,224)
:
整數元組默認為(256,256),圖像將被調整大小成該尺寸,因為我基於VGG16模型,所以這裡設定為(224,224)。
輸出:
Found 40 images belonging to 2 classes.
Found 10 images belonging to 2 classes.
Found 10 images belonging to 2 classes.
可以來看一下大小:
print(train_batches.image_shape)
# (224, 224, 3)
引入keras內VGG16模型,若為第一次引入使用,則需要稍等待下載:
vgg16_model = keras.applications.vgg16.VGG16()
model = Sequential()
model.summary()
可以看到:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
Total params: 0
Trainable params: 0
Non-trainable params: 0
_________________________________________________________________
接著我們將VGG16的layer加到我們的model內:
for layer in vgg16_model.layers:
model.add(layer)
model.summary()
可以看到結果:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, 224, 224, 3) 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, 224, 224, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, 224, 224, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, 112, 112, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, 112, 112, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, 112, 112, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, 56, 56, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, 56, 56, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, 28, 28, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, 28, 28, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, 14, 14, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_pool (MaxPooling2D) (None, 7, 7, 512) 0
_________________________________________________________________
flatten (Flatten) (None, 25088) 0
_________________________________________________________________
fc1 (Dense) (None, 4096) 102764544
_________________________________________________________________
fc2 (Dense) (None, 4096) 16781312
_________________________________________________________________
predictions (Dense) (None, 1000) 4097000
=================================================================
Total params: 138,357,544
Trainable params: 138,357,544
Non-trainable params: 0
_________________________________________________________________
將頂層predictions
拿掉,基本上遷移式學習不只是刪掉原本輸出,不過因為資料類型的關係這裡只把最後一層刪掉重新訓練。
model.layers.pop()
因為VGG16原本有1000個分類,但這裡我們要改成2種分類:
改變之前layer的trainable
參數為False
,因為我們現在只需要訓練最後一層。
for layer in model.layers:
layer.trainable = False
最後加上最後一層:
可以看到這裡我們是改成2
,因為我們只要0
跟1
,分別代表狗
跟貓
就行了:
model.add(Dense(2, activation='softmax'))
Layer (type) Output Shape Param #
=================================================================
input_2 (InputLayer) (None, 224, 224, 3) 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, 224, 224, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, 224, 224, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, 112, 112, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, 112, 112, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, 112, 112, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, 56, 56, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, 56, 56, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, 28, 28, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, 28, 28, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, 14, 14, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_pool (MaxPooling2D) (None, 7, 7, 512) 0
_________________________________________________________________
flatten (Flatten) (None, 25088) 0
_________________________________________________________________
fc1 (Dense) (None, 4096) 102764544
_________________________________________________________________
fc2 (Dense) (None, 4096) 16781312
_________________________________________________________________
dense_5 (Dense) (None, 2) 2002
=================================================================
Total params: 134,262,546
Trainable params: 2,002
Non-trainable params: 134,260,544
_________________________________________________________________
model.compile(Adam(lr=.00002122), loss='categorical_crossentropy', metrics=['accuracy'])
設定裡面的參數,包含訓練的以及驗證的資料集,epochs
設定為10次,因為資料量小,所以steps_per_epoch
和validation_steps
我不需要設定太大,這邊就依照你的資料量大小改變囉!
model.fit_generator(train_batches, steps_per_epoch=10, validation_data=valid_batches, validation_steps=4, epochs=10, verbose=2)
等待完成:
Epoch 1/10 - 6s - loss: 0.6749 - acc: 0.7756 - val_loss: 0.6456 - val_acc: 1.0000
Epoch 2/10 - 2s - loss: 0.6679 - acc: 0.7856 - val_loss: 0.6674 - val_acc: 0.7143
Epoch 3/10 - 2s - loss: 0.6586 - acc: 0.8672 - val_loss: 0.6757 - val_acc: 0.7143
Epoch 4/10 - 2s - loss: 0.6547 - acc: 0.8980 - val_loss: 0.6460 - val_acc: 1.0000
Epoch 5/10 - 2s - loss: 0.6461 - acc: 0.9898 - val_loss: 0.6455 - val_acc: 1.0000
Epoch 6/10 - 2s - loss: 0.6443 - acc: 1.0000 - val_loss: 0.6441 - val_acc: 1.0000
Epoch 7/10 - 2s - loss: 0.6443 - acc: 1.0000 - val_loss: 0.6475 - val_acc: 1.0000
Epoch 8/10 - 2s - loss: 0.6442 - acc: 1.0000 - val_loss: 0.6512 - val_acc: 0.9286
Epoch 9/10 - 2s - loss: 0.6440 - acc: 1.0000 - val_loss: 0.6583 - val_acc: 0.8571
Epoch 10/10 - 2s - loss: 0.6445 - acc: 1.0000 - val_loss: 0.6511 - val_acc: 0.9286
因為資料量不多,所以非常的快,可以看到acc準確度到100%了,非常開心。並不是每次訓練都會非常的快,所以記得在最後面都要寫上儲存的方法把模型存起來才行啊!
model.save("cat-dog-model-base-VGG16.h5")
好的,那這就是今天的介紹!非常感謝大家的收看!(好久沒有寫文章了。)
關於圖片處理:
keras Image preprocessing
關於預訓練模型:
keras Applications
請問老師為甚麼要加這一行,不加會影響訓練嗎?
for layer in model.layers:
layer.trainable = False