5.3 Using a pre-trained convnet 使用預訓練的網絡
預訓練網絡
- 使用預訓練網絡是一種非常高效的小圖像資料集的深度學習方法;
- 預訓練網絡是一個儲存好的網絡,之前已經在大型資料集上訓練好。
- 使用預訓練網絡的兩種方法: 1、特征提取 2、微調模型
特征提取
使用之前網絡學習到的表示來從新樣本中提取出有趣的特征,然後将這些特征輸入一個新的分類器,從頭開始訓練。
卷積基:一系列池化層和卷積層
特征提取就是取出之前訓練好的網絡的卷積基,在上面運作新資料,然後在輸出上面訓練一個新的分類器。
卷積基的模型是非常通用的,但是分類器是隻針對某個訓練集的。
模型的底部往往是具有通用特征的,而上層是各異性比較強的。是以特征提取隻使用模型的前幾層做特征提取,而不是整個卷積基。
#将VGG16卷積基執行個體化
from keras.applications import VGG16
conv_base = VGG16(weights='imagenet', #指定模型初始化的權重檢查點
include_top=False, # 指定模型是否包含密集連接配接分類器
input_shape=(150, 150, 3)) #輸入到網絡的張量形狀,不傳參數可以進行任意形狀的輸入
在colab中運作時,上面的寫法是會報錯的。報錯是:cannot import name 'VGG16' from 'keras.applications'。原因應該是keras版本問題,解決方案是将from keras.applications import VGG16 改為from keras.applications.vgg16 import VGG16。上面框中的代碼是在本地jupyter notebook中可以運作的。
下面我門首先看看剛剛定義的模型conv_base的結構是怎麼樣的。
conv_base.summary()
結果如下: Model: "vgg16" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) (None, 150, 150, 3) 0 _________________________________________________________________ block1_conv1 (Conv2D) (None, 150, 150, 64) 1792 _________________________________________________________________ block1_conv2 (Conv2D) (None, 150, 150, 64) 36928 _________________________________________________________________ block1_pool (MaxPooling2D) (None, 75, 75, 64) 0 _________________________________________________________________ block2_conv1 (Conv2D) (None, 75, 75, 128) 73856 _________________________________________________________________ block2_conv2 (Conv2D) (None, 75, 75, 128) 147584 _________________________________________________________________ block2_pool (MaxPooling2D) (None, 37, 37, 128) 0 _________________________________________________________________ block3_conv1 (Conv2D) (None, 37, 37, 256) 295168 _________________________________________________________________ block3_conv2 (Conv2D) (None, 37, 37, 256) 590080 _________________________________________________________________ block3_conv3 (Conv2D) (None, 37, 37, 256) 590080 _________________________________________________________________ block3_pool (MaxPooling2D) (None, 18, 18, 256) 0 _________________________________________________________________ block4_conv1 (Conv2D) (None, 18, 18, 512) 1180160 _________________________________________________________________ block4_conv2 (Conv2D) (None, 18, 18, 512) 2359808 _________________________________________________________________ block4_conv3 (Conv2D) (None, 18, 18, 512) 2359808 _________________________________________________________________ block4_pool (MaxPooling2D) (None, 9, 9, 512) 0 _________________________________________________________________ block5_conv1 (Conv2D) (None, 9, 9, 512) 2359808 _________________________________________________________________ block5_conv2 (Conv2D) (None, 9, 9, 512) 2359808 _________________________________________________________________ block5_conv3 (Conv2D) (None, 9, 9, 512) 2359808 _________________________________________________________________ block5_pool (MaxPooling2D) (None, 4, 4, 512) 0 ================================================================= Total params: 14,714,688 Trainable params: 14,714,688 Non-trainable params: 0 _________________________________________________________________
下一步工作是在得到的特征(None, 4, 4, 512)上增加一個密集連接配接分類器,共有兩種方法:
1、在資料集上運作卷積基,将輸出儲存為硬碟中存儲的numpy數組,然後用這個資料作為輸入,輸入到獨立的密集連接配接分類器中,不可以使用資料增強,但是速度很快。
2、在頂部添加Dense層擴充模型,可以使用資料增強,但是計算代價大。
方法1:
# 使用預訓練的卷積基提取特征
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
base_dir = 'D:/ALL_code\Anaconda/Deep_Learning_Study/chapter5/kaggle_original_data/cats_and_dogs_small'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')
datagen = ImageDataGenerator(rescale=1./255)
batch_size = 20
def extract_features(directory, sample_count): # directory檔案夾目錄,sample_count是資料量
features =np.zeros(shape=(sample_count, 4, 4, 512)) #開辟一個四維的零矩陣(四維張量空間),大小就是足夠存放這2000個圖像的空間
labels = np.zeros(shape=(sample_count))
#generator是一個存放處理過的圖像資料的容器
generator = datagen.flow_from_directory(
directory, #圖像存放目錄
target_size=(150, 150), #目标大小
batch_size=batch_size, #一次處理批量的多少
class_mode='binary') #分類模式:當然是二進制
i = 0
#使用循環做處理過的圖像經過模型預測後的結果
for inputs_batch, labels_batch in generator:
features_batch = conv_base.predict(inputs_batch) #将generator中的資料經過VGG16模型預測
features[i * batch_size : (i + 1) * batch_size] = features_batch #将預測後的結果存放在剛剛開辟的空間中
#隻對第0軸進行切片,是因為features是一個四維張量,而這裡的資料也是四維的,是以對第0軸進行切片就可以了,其他的會自己存儲。
#就像在一個箱子裡放入正方體一樣,隻考慮一邊就可以了,其他的邊會自動配置設定。
labels[i * batch_size : (i + 1) * batch_size] = labels_batch
i +=1
if i * batch_size >= sample_count:
break #生成器在循環中是連續不斷的,必須設定斷點
return features, labels
train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)
#目前提取的特征形狀為(samples, 4, 4, 512)我們要将其輸入到密集連接配接分類器中,是以首先要将其形狀展平為(samples, 8192)
train_features = np.reshape(train_features, (2000, 4 * 4 * 512))
validation_features = np.reshape(validation_features, (1000, 4 * 4 * 512))
test_features = np.reshape(test_features, (1000, 4 * 4 * 512))
得到這個結果,說明特征提取完畢:
#定義并訓練密集連接配接分類器
from keras import models
from keras import layers
from keras import optimizers
model = models.Sequential()
model.add(layers.Dense(256, activation='relu', input_dim=4 * 4 * 512))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
loss='binary_crossentropy',
metrics=['acc'])
history = model.fit(train_features, train_labels, epochs=30, batch_size=20, validation_data=(validation_features, validation_labels))
這個過程因為隻添加了兩個密集連接配接層,是以運作速度非常快,在CPU上面每一個epoch也隻2秒鐘,下面繪制一下損失曲線和驗證曲線
# 繪制結果
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()
繪制結果如下:
驗證精度達到了90%,比上一節中從頭開始訓練的結果好很多。從圖中可以看出,雖然Dropout比率相當大,但是模型幾乎從一開始就過拟合。這是因為這裡沒有使用資料增強,資料增強對于降低過拟合非常重要。
方法二:使用資料增強的特征提取
擴充conv_base模型,然後在輸入資料上端到端的運作模型,可以使用資料增強。
模型的行為和層類似,是以可以向Sequential模型中添加一個模型(conv_base),就像添加一個層一樣。
這邊代碼在colab上運作,注意在colab上運作需要進行google drive的挂載
#在卷積基上添加一個密集連接配接分類器
from keras import models
from keras import layers
model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
>>>model.summary()Model: "sequential_2" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= vgg16 (Model) (None, 4, 4, 512) 14714688 _________________________________________________________________ flatten_1 (Flatten) (None, 8192) 0 _________________________________________________________________ dense_3 (Dense) (None, 256) 2097408 _________________________________________________________________ dense_4 (Dense) (None, 1) 257 ================================================================= Total params: 16,812,353 Trainable params: 16,812,353 Non-trainable params: 0 _________________________________________________________________
VGG16的卷積基有 14 714 688個參數,在編譯和訓練模型之前一定要"當機"卷積基。當機一個或多個層是指在訓練過程中保持其權重不變。如果不這麼做,那麼卷積基之前學到的表示将會在訓練過程中被修改。因為其上添加的Dense層使随機初始化的,是以非常大的權重更新将會在網絡中傳播,對之前學到的表示造成很大的破壞。這個model中當機VGG的參數,當機14714688這麼多
在keras中,當機網絡的方法是将其trainable屬性更改為False
print('This is the number of trainable weights'
'before freezing the conv base:', len(model.trainable_weights))
conv_base.trainable = False
print('This is the number of trainable weights'
'after freezing the conv base:', len(model.trainable_weights))
運作結果如下:
當機前為什麼是估30個trainable weight,是因為不僅要估計一個W矩陣,還有一個截距項bias,是以每一層都要估計兩個trainable weight,共有15層。
>>>model.summary()
很顯然可以看出,模型中vgg16的部分被當機了:
下面對當機後的模型開始訓練:
#利用當機的卷積基端到端地訓練模型
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
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,
fill_mode='nearest')
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')
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=2e-5),
metrics=['acc'])
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=30,
validation_data=validation_generator,
validation_steps=50)
今天在Google Colab上面運作的時候,發現了一個bug,在compile的時候,optimizer竟然報錯了,具體原因也沒有查到,看樣子應該還是版本問題:Error module 'keras.optimizers' has no attribute 'RMSprop'。具體解決方案:在keras前面加上tensorflow,即from tensorflow.keras import optimizers。
儲存模型:
model.save('cats_and_dogs_small_3.h5')
# 繪制結果
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()
繪制結果如下:
微調模型
當機VGG16的卷積基是為了能夠在上面訓練一個随機初始化的分類器。隻有上面的分類器訓練好了才能微調卷積基的頂部幾層。如果分類器沒有訓練好,那麼訓練期間通過網絡傳播的誤差信号會特别大,微調的幾層之前學到的表示都會受到破壞。
微調網絡步驟如下:
(1)在已經訓練好的基網絡(base network)上添加自定義網絡
(2)當機基網絡
(3)訓練所添加的部分
(4)解凍基網絡的一些層
(5)聯合訓練解凍的這些層和添加的部分。
前面三個步驟在特征提取時做完了
為什麼不微調最後三個卷積層?為什麼不微調整個卷積基?需要考慮的問題:
卷積基中更多靠底部的層編碼的更加通用的可複用特征,而更可靠頂部的層編碼的是更專業化的特征。微調這些更專業化的特征更加有用。微調更靠底部的層,得到的回報會更少。
訓練集上的參數越多,過拟合的風險越大。
#當機直到某一層的所有層
conv_base.trainable = True
set_trainable = False
for layer in conv_base.layers:
if layer.name == 'block5_conv1':
set_trainable = True
if set_trainable :
layer.trainable = True
else:
layer.trainable = False
#微調模型
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-5),
metrics=['acc'])
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_4.h5')
#儲存模型
# 繪制結果
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()
繪制結果如下:
這些曲線看起來很緊湊。為了使它們更易讀,我們可以用這些量的指數移動平均值替換每一個損失和精度。下面是一個簡單的實用程式函數:
#使曲線變得平滑
def smooth_curve(points, factor=0.8):
smoothed_points = []
for point in points:
if smoothed_points:
previous = smoothed_points[-1]
smoothed_points.append(previous * factor + point * (1 - factor))
else:
smoothed_points.append(point)
return smoothed_points
plt.plot(epochs, smooth_curve(acc), 'bo', label='Smoothed training acc')
plt.plot(epochs, smooth_curve(val_acc), 'b', label='Smoothed validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs,
smooth_curve(loss), 'bo', label='Smoothed training loss')
plt.plot(epochs,
smooth_curve(val_loss), 'b', label='Smoothed validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
結果如下:
這些曲線看起來更清晰、更穩定。我們看到一個不錯的1%的絕對改善。 請注意,損失曲線沒有顯示出任何真正的改善(事實上,它正在惡化)。你可能想知道,如果損失不減少,精确度如何提高?答案很簡單:我們顯示的是逐點損失值的平均值,但實際影響準确性的是損失值的分布,而不是它們的平均值,因為準确性是模型預測的類機率的二進制門檻值化的結果。即使這沒有反映在平均損失中,模型可能仍然在改進。 我們現在可以根據測試資料最終評估此模型:
#在測試集上評估這個模型
test_generator = test_datagen.flow_from_directory(
test_dir,
target_size=(150, 150),
batch_size=20,
class_mode='binary')
test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print('test acc: ', test_acc)
#CPU運作很快,由于colab挂載雲端硬碟,所有的檔案都在雲端,通路也要從雲通路,是以運作速度很慢
運作結果如下:
上面結果是Colab上面跑出來的,下面是我用jupyter跑出來的:
其實有些不太懂,代碼完全一緻 ,使用資料集完全一緻,為什麼跑出來的結果和繪制的曲線會有差别。jupyter notebook使用起來比colab順手,但是跑的時候感覺前面的訓練會對下面的資料有影響,會發生這些詭異的事情。
下一篇整理5.4,卷積神經網絡可視化。