keras_卷積神經網絡_貓狗分類案例(二)
Python深度學習
參考:https://blog.csdn.net/xiewenrui1996/article/details/104032476/
**5.3 使用預訓練的卷積神經網絡
5.3.1 特征提取
代碼清單 5-16 将 VGG16 卷積基執行個體化
-
不使用資料增強的快速特征提取
代碼清單 5-17 使用預訓練的卷積基提取特征
代碼清單 5-18 定義并訓練密集連接配接分類器
代碼清單 5-19 繪制結果
-
使用資料增強的特征提取
代碼清單 5-20 在卷積基上添加一個密集連接配接分類器
代碼清單 5-21 利用當機的卷積基端到端地訓練模型
5.3.2 微調模型**
微調網絡的步驟如下。
(1) 在已經訓練好的基網絡(base network)上添加自定義網絡。
(2) 當機基網絡。
(3) 訓練所添加的部分。
(4) 解凍基網絡的一些層。
(5) 聯合訓練解凍的這些層和添加的部分。
你在做特征提取時已經完成了前三個步驟。我們繼續進行第四步:先解凍 conv_base,然
後當機其中的部分層。
代碼清單 5-22 當機直到某一層的所有層
代碼清單 5-23 微調模型
代碼清單 5-24 使曲線變得平滑
下面是你應該從以上兩節的練習中學到的要點。
‰ 卷積神經網絡是用于計算機視覺任務的最佳機器學習模型。即使在非常小的資料集上也
可以從頭開始訓練一個卷積神經網絡,而且得到的結果還不錯。
‰ 在小型資料集上的主要問題是過拟合。在處理圖像資料時,資料增強是一種降低過拟合
的強大方法。
‰ 利用特征提取,可以很容易将現有的卷積神經網絡複用于新的資料集。對于小型圖像數
據集,這是一種很有價值的方法。
‰ 作為特征提取的補充,你還可以使用微調,将現有模型之前學到的一些資料表示應用于
新問題。這種方法可以進一步提高模型性能。
現在你已經擁有一套可靠的工具來處理圖像分類問題,特别是對于小型資料集
import keras
keras.__version__
# 将 VGG16 卷積基執行個體化
from keras.applications import VGG16
conv_base = VGG16(weights='imagenet',
include_top=False,
input_shape=(150, 150, 3))
# weights 指定模型初始化的權重檢查點。
# include_top 指定模型最後是否包含密集連接配接分類器。預設情況下,這個密集連接配接分類器對應于 ImageNet 的 1000 個類别。因為我們打算使用自己的密集連接配接分類器(隻有 兩個類别:cat 和 dog),是以不需要包含它。
# input_shape 是輸入到網絡中的圖像張量的形狀。這個參數完全是可選的,如果不傳入這個參數,那麼網絡能夠處理任意形狀的輸入。 VGG16 卷積基的詳細架構如下所示。它和你已經熟悉的簡單磁碟區積神經網絡很相似。
conv_base.summary()
_________________________________________________________________
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
注意,這些生成器在循環中不斷
# 生成資料,是以你必須在讀取完
# 所有圖像後終止循環
# 最後的特征圖形狀為 (4, 4, 512)。我們将在這個特征上添加一個密集連接配接分類器。
# 接下來,下一步有兩種方法可供選擇。
# • 在你的資料集上運作卷積基,将輸出儲存成硬碟中的 Numpy 數組,然後用這個資料作
# 為輸入,輸入到獨立的密集連接配接分類器中(與本書第一部分介紹的分類器類似)。這種
# 方法速度快,計算代價低,因為對于每個輸入圖像隻需運作一次卷積基,而卷積基是目
# 前流程中計算代價最高的。但出于同樣的原因,這種方法不允許你使用資料增強。
# • 在頂部添加 Dense 層來擴充已有模型(即 conv_base),并在輸入資料上端到端地運作
# 整個模型。這樣你可以使用資料增強,因為每個輸入圖像進入模型時都會經過卷積基。
# 但出于同樣的原因,這種方法的計算代價比第一種要高很多。5.3 使用預訓練的卷積神經網絡 119
# 1 2 3 4 5 6 7 8 9
# 這兩種方法我們都會介紹。首先來看第一種方法的代碼:儲存你的資料在 conv_base 中的
# 輸出,然後将這些輸出作為輸入用于新模型
# 1. 不使用資料增強的快速特征提取
# 首先,運作 ImageDataGenerator 執行個體,将圖像及其标簽提取為 Numpy 數組。我們需要
# 調用 conv_base 模型的 predict 方法來從這些圖像中提取特征。
# 代碼清單 5-17 使用預訓練的卷積基提取特征
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
base_dir = '/Users/fchollet/Downloads/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):
features = np.zeros(shape=(sample_count, 4, 4, 512))
labels = np.zeros(shape=(sample_count))
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)
features[i * batch_size : (i + 1) * batch_size] = features_batch
labels[i * batch_size : (i + 1) * batch_size] = labels_batch
i += 1
if i * batch_size >= sample_count:
# Note that since generators yield data indefinitely in a loop,
# we must `break` after every image has been seen once.
# 注意,這些生成器在循環中不斷
# 生成資料,是以你必須在讀取完
# 所有圖像後終止循環
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)
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
The extracted features are currently of shape (samples, 4, 4, 512). We will feed them to a densely-connected classifier, so first we must flatten them to (samples, 8192):
現在你可以定義你的密集連接配接分類器(注意要使用 dropout 正則化),并在剛剛儲存的資料
# 和标簽上訓練這個分類器
# 目前,提取的特征形狀為 (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))
# 現在你可以定義你的密集連接配接分類器(注意要使用 dropout 正則化),并在剛剛儲存的資料
# 和标簽上訓練這個分類器
At this point, we can define our densely-connected classifier (note the use of dropout for regularization), and train it on the data and labels that we just recorded:
訓練速度非常快,因為你隻需處理兩個 Dense 層。即使在 CPU 上運作,每輪的時間也不
# 到一秒鐘。
# 我們來看一下訓練期間的損失曲線和精度曲線
# 定義并訓練密集連接配接分類器
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))
# 訓練速度非常快,因為你隻需處理兩個 Dense 層。即使在 CPU 上運作,每輪的時間也不
# 到一秒鐘。
# 我們來看一下訓練期間的損失曲線和精度曲線
Train on 2000 samples, validate on 1000 samples
Epoch 1/30
2000/2000 [==============================] - 1s - loss: 0.6253 - acc: 0.6455 - val_loss: 0.4526 - val_acc: 0.8300
Epoch 2/30
2000/2000 [==============================] - 0s - loss: 0.4490 - acc: 0.7965 - val_loss: 0.3784 - val_acc: 0.8450
Epoch 3/30
2000/2000 [==============================] - 0s - loss: 0.3670 - acc: 0.8490 - val_loss: 0.3327 - val_acc: 0.8660
Epoch 4/30
2000/2000 [==============================] - 0s - loss: 0.3176 - acc: 0.8705 - val_loss: 0.3115 - val_acc: 0.88
# 代碼清單 5-19 繪制結果
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(len(acc))
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()
# 2. 使用資料增強的特征提取
# 下面我們來看一下特征提取的第二種方法,它的速度更慢,計算代價更高,但在訓練期間
# 可以使用資料增強。這種方法就是:擴充 conv_base 模型,然後在輸入資料上端到端地運作模型。
# 注意 本方法計算代價很高,隻在有 GPU 的情況下才能嘗試運作。它在 CPU 上是絕對難以運
# 行的。如果你無法在 GPU 上運作代碼,那麼就采用第一種方法
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'))
This is what our model looks like now:
model.summary()
_________________________________________________________________
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
_________________________________________________________________
As you can see, the convolutional base of VGG16 has 14,714,688 parameters, which is very large. The classifier we are adding on top has 2 million parameters.
Before we compile and train our model, a very important thing to do is to freeze the convolutional base. "Freezing" a layer or set of layers means preventing their weights from getting updated during training. If we don't do this, then the representations that were previously learned by the convolutional base would get modified during training. Since the Dense layers on top are randomly initialized, very large weight updates would be propagated through the network, effectively destroying the representations previously learned.
In Keras, freezing a network is done by setting its trainable attribute to False:
如你所見, VGG16 的卷積基有 14 714 688 個參數,非常多。在其上添加的分類器有 200 萬
# 個參數。
# 在編譯和訓練模型之前,一定要“當機”卷積基。 當機(freeze)一個或多個層是指在訓練
# 過程中保持其權重不變。如果不這麼做,那麼卷積基之前學到的表示将會在訓練過程中被修改。
# 因為其上添加的 Dense 層是随機初始化的,是以非常大的權重更新将會在網絡中傳播,對之前
# 學到的表示造成很大破壞。
# 在 Keras 中,當機網絡的方法是将其 trainable 屬性設為 False
# 如你所見, VGG16 的卷積基有 14 714 688 個參數,非常多。在其上添加的分類器有 200 萬
# 個參數。
# 在編譯和訓練模型之前,一定要“當機”卷積基。 當機(freeze)一個或多個層是指在訓練
# 過程中保持其權重不變。如果不這麼做,那麼卷積基之前學到的表示将會在訓練過程中被修改。
# 因為其上添加的 Dense 層是随機初始化的,是以非常大的權重更新将會在網絡中傳播,對之前
# 學到的表示造成很大破壞。
# 在 Keras 中,當機網絡的方法是将其 trainable 屬性設為 False
print('This is the number of trainable weights '
'before freezing the conv base:', len(model.trainable_weights))
This is the number of trainable weights before freezing the conv base: 30
conv_base.trainable = False
print('This is the number of trainable weights '
'after freezing the conv base:', len(model.trainable_weights))
This is the number of trainable weights after freezing the conv base: 4
With this setup, only the weights from the two Dense layers that we added will be trained. That's a total of four weight tensors: two per layer (the main weight matrix and the bias vector). Note that in order for these changes to take effect, we must first compile the model. If you ever modify weight trainability after compilation, you should then re-compile the model, or these changes would be ignored.
Now we can start training our model, with the same data augmentation configuration that we used in our previous example:
# 如此設定之後,隻有添加的兩個 Dense 層的權重才會被訓練。總共有 4 個權重張量,每層
# 2 個(主權重矩陣和偏置向量)。注意,為了讓這些修改生效,你必須先編譯模型。如果在編譯
# 之後修改了權重的 trainable 屬性,那麼應該重新編譯模型,否則這些修改将被忽略。5.3 使用預訓練的卷積神經網絡 123
# 1 2 3 4 5 6 7 8 9
# 現在你可以開始訓練模型了,使用和前一個例子相同的資料增強設定。
# 代碼清單 5-21 利用當機的卷積基端到端地訓練模型
from keras.preprocessing.image import ImageDataGenerator
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')
# Note that the validation data should not be augmented!
# 注意,不能增強驗證資料
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
# This is the target directory
train_dir,
# All images will be resized to 150x150
target_size=(150, 150),
batch_size=20,
# Since we use binary_crossentropy loss, we need binary labels
# 因為使用了binary_crossentropy損失,是以需要用二進制标簽
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,
verbose=2)
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
Epoch 1/30
74s - loss: 0.4465 - acc: 0.7810 - val_loss: 0.2056 - val_acc: 0.9120
Epoch 2/30
72s - loss: 0.2738 - acc: 0.8905 - val_loss: 0.1239 - val_acc: 0.9550
Epoch 3/30
72s - loss: 0.2088 - acc: 0.9145 - val_loss: 0.1194 - val_acc: 0.9560
我們來再次繪制結果(見圖 5-17 和圖 5-18)。如你所見,驗證精度約為 96%。這比從頭開
# 始訓練的小型卷積神經網絡要好得多。
# 我們來再次繪制結果(見圖 5-17 和圖 5-18)。如你所見,驗證精度約為 96%。這比從頭開
# 始訓練的小型卷積神經網絡要好得多。
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
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()
As a reminder, this is what our convolutional base looks like:
# 前面說過,當機 VGG16 的卷積基是為了能夠在上面訓練一個随機初始化的分類器。同理,
# 隻有上面的分類器已經訓練好了,才能微調卷積基的頂部幾層。如果分類器沒有訓練好,那麼126 第 5 章 深度學習用于計算機視覺
# 訓練期間通過網絡傳播的誤差信号會特别大,微調的幾層之前學到的表示都會被破壞。是以,
# 微調網絡的步驟如下。
# (1) 在已經訓練好的基網絡(base network)上添加自定義網絡。
# (2) 當機基網絡。
# (3) 訓練所添加的部分。
# (4) 解凍基網絡的一些層。
# (5) 聯合訓練解凍的這些層和添加的部分。
# 你在做特征提取時已經完成了前三個步驟。我們繼續進行第四步:先解凍 conv_base,然
# 後當機其中的部分層。
# 提醒一下,卷積基的架構如下所示。
conv_base.summary()
_________________________________________________________________
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
_________________________________________________________________
# 我們将微調最後三個卷積層,也就是說,直到 block4_pool 的所有層都應該被當機,而
# block5_conv1、 block5_conv2 和 block5_conv3 三層應該是可訓練的。
# 為什麼不微調更多層?為什麼不微調整個卷積基?你當然可以這麼做,但需要考慮以下幾點。
# • 卷積基中更靠底部的層編碼的是更加通用的可複用特征,而更靠頂部的層編碼的是更專
# 業化的特征。微調這些更專業化的特征更加有用,因為它們需要在你的新問題上改變用
# 途。微調更靠底部的層,得到的回報會更少。
# • 訓練的參數越多,過拟合的風險越大。卷積基有 1500 萬個參數,是以在你的小型資料
# 集上訓練這麼多參數是有風險的。
# 是以,在這種情況下,一個好政策是僅微調卷積基最後的兩三層。我們從上一個例子結束
# 的地方開始,繼續實作此方法。
# 代碼清單 5-22 當機直到某一層的所有層
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
Now we can start fine-tuning our network. We will do this with the RMSprop optimizer, using a very low learning rate. The reason for using a low learning rate is that we want to limit the magnitude of the modifications we make to the representations of the 3 layers that we are fine-tuning. Updates that are too large may harm these representations.
Now let's proceed with fine-tuning:
現在你可以開始微調網絡。我們将使用學習率非常小的 RMSProp 優化器來實作。之是以讓
# 學習率很小,是因為對于微調的三層表示,我們希望其變化範圍不要太大。太大的權重更新可
# 能會破壞這些表示。
# 代碼清單 5-23 微調模型
# 現在你可以開始微調網絡。我們将使用學習率非常小的 RMSProp 優化器來實作。之是以讓
# 學習率很小,是因為對于微調的三層表示,我們希望其變化範圍不要太大。太大的權重更新可
# 能會破壞這些表示。
# 代碼清單 5-23 微調模型
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)
Epoch 1/100
100/100 [==============================] - 32s - loss: 0.0215 - acc: 0.9935 - val_loss: 0.0980 - val_acc: 0.9720
Epoch 2/100
100/100 [==============================] - 32s - loss: 0.0131 - acc: 0.9960 - val_loss: 0.1247 - val_acc: 0.9700
Epoch 3/100
100/100 [==============================] - 32s - loss: 0.0140 - acc: 0.9940 - val_loss: 0.1044 - val_acc: 0.9790
model.save('cats_and_dogs_small_4.h5')
Let's plot our results using the same plotting code as before:
# 和前面一樣的繪圖代碼來繪制結果
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
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()
# 這些曲線看起來包含噪聲。為了讓圖像更具可讀性,你可以将每個損失和精度都替換為指數
# 移動平均值,進而讓曲線變得平滑。下面用一個簡單的實用函數來實作(見圖 5-22 和圖 5-23)。
# 代碼清單 5-24 使曲線變得平滑
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()
我們得到了 97% 的測試精度。在關于這個資料集的原始 Kaggle 競賽中,這個結果是最佳
# 結果之一。但利用現代深度學習技術,你隻用一小部分訓練資料(約 10%)就得到了這個結果。
# 訓練 20 000 個樣本與訓練 2000 個樣本是有很大差别的!
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)
# 驗證精度曲線變得更清楚。可以看到,精度值提高了 1%,從約 96% 提高到 97% 以上。
# 注意,從損失曲線上看不出與之前相比有任何真正的提高(實際上還在變差)。你可能感到
# 奇怪,如果損失沒有降低,那麼精度怎麼能保持穩定或提高呢?答案很簡單:圖中展示的是逐
# 點(pointwise)損失值的平均值,但影響精度的是損失值的分布,而不是平均值,因為精度是
# 模型預測的類别機率的二進制門檻值。即使從平均損失中無法看出,但模型也仍然可能在改進。
# 現在,你可以在測試資料上最終評估這個模型。
# 我們得到了 97% 的測試精度。在關于這個資料集的原始 Kaggle 競賽中,這個結果是最佳
# 結果之一。但利用現代深度學習技術,你隻用一小部分訓練資料(約 10%)就得到了這個結果。
# 訓練 20 000 個樣本與訓練 2000 個樣本是有很大差别的!
Found 1000 images belonging to 2 classes.
test acc: 0.967999992371
Here we get a test accuracy of 97%. In the original Kaggle competition around this dataset, this would have been one of the top results. However, using modern deep learning techniques, we managed to reach this result using only a very small fraction of the training data available (about 10%). There is a huge difference between being able to train on 20,000 samples compared to 2,000 samples!