上節實現完的精確度和loss繪製圖如下。從圖中可以看出,訓練精度和驗證精度相差很大,模型在驗證集上僅達到70%左右的精度。 本節研究其問題,並嘗試提高模型的整體性能。
過擬合
在上面的圖中,訓練精度隨着時間線性增加,而驗證精度在訓練過程中停滯在70%左右。此外,訓練和驗證準確性之間的差異是顯而易見的——這是過擬合的標誌。 當訓練樣本數量較少時,模型有時會從訓練樣本中的噪聲或不需要的細節中學習,從而對新樣本的模型性能產生負面影響。這種現象被稱爲過擬合。這意味着該模型在新的數據集上將很難推廣。 在訓練過程中有多種方法來對抗過度訓練。在本案例中將使用數據擴充,並在我們的模型中添加dropout。
數據擴充
過擬合通常發生在有少量訓練樣本的時候。解決這個問題的一種方法是擴充數據集,使其具有足夠數量的訓練示例。數據擴充採用從現有訓練樣本中產生更多訓練數據的方法,通過使用產生可信圖像的隨機變換來擴充樣本。目標是模型在訓練中永遠不會看到完全相同的圖片兩次。這有助於將模型展示給數據的更多方面,並更好地進行概括。
實現:使用在tf.keras中的ImageDataGenerator類實現這一點。將不同的轉換傳遞給數據集,它將在訓練過程中負責應用它。
擴充和可視化數據
首先對數據集應用隨機水平翻轉增強,並查看轉換後各個圖像的外觀。
(1)應用水平翻轉
將horizontal_flip作爲參數傳遞給ImageDataGenerator類,並將其設置爲“True”以應用此增強。
代碼實現:
# 該函數將圖像繪製成1行5列的網格形式,圖像放置在每一列中。
def plotImages(images_arr):
fig, axes = plt.subplots(1, 5, figsize=(20, 20))
axes = axes.flatten()
for img, ax in zip(images_arr, axes):
ax.imshow(img)
ax.axis('off')
plt.tight_layout()
plt.show()
# 將horizontal_flip作爲參數傳遞給ImageDataGenerator類,並將其設置爲“True”以應用此增強。
image_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. / 255, horizontal_flip=True)
# 在爲訓練和驗證圖像定義生成器之後,flow_from_directory方法從磁盤加載圖像,應用重新縮放,並將圖像調整到所需的尺寸。
train_data_gen = image_gen.flow_from_directory(batch_size=batch_size,
directory=train_dir,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH))
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
plotImages(augmented_images)
實現效果:
(2)隨機旋轉圖像
觀察一個不同的叫做旋轉的增強,並隨機地將45度旋轉應用到訓練例子中。
代碼實現:
# 將rotation_range作爲參數傳遞給ImageDataGenerator類,並將其設置爲45。
image_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=45)
# 在爲訓練和驗證圖像定義生成器之後,flow_from_directory方法從磁盤加載圖像,應用重新縮放,並將圖像調整到所需的尺寸。
train_data_gen = image_gen.flow_from_directory(batch_size=batch_size,
directory=train_dir,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH))
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
plotImages(augmented_images)
實現效果:
(3)應用縮放增量
對數據集應用縮放增強功能,將圖像隨機放大50%。
代碼實現:
# zoom_range from 0 - 1 where 1 = 100%.
image_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. / 255, zoom_range=0.5)
# 在爲訓練和驗證圖像定義生成器之後,flow_from_directory方法從磁盤加載圖像,應用重新縮放,並將圖像調整到所需的尺寸。
train_data_gen = image_gen.flow_from_directory(batch_size=batch_size,
directory=train_dir,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH))
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
plotImages(augmented_images)
實現效果:
把它們組合在一起
應用所有先前的增強。在此對訓練圖像應用了重新縮放、45度旋轉、寬度移動、高度移動、水平翻轉和縮放增強。
代碼實現:
image_gen_train = tf.keras.preprocessing.image.ImageDataGenerator(
rescale=1. / 255,
rotation_range=45,
width_shift_range=.15,
height_shift_range=.15,
horizontal_flip=True,
zoom_range=0.5
)
# 在爲訓練和驗證圖像定義生成器之後,flow_from_directory方法從磁盤加載圖像,應用重新縮放,並將圖像調整到所需的尺寸。
train_data_gen = image_gen_train.flow_from_directory(batch_size=batch_size,
directory=train_dir,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH),
class_mode='binary')
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
plotImages(augmented_images)
實現效果:
創建驗證集數據生成器
通常,只對訓練示例應用數據擴充。在這種情況下,只需重新縮放驗證圖像,並使用ImageDataGenerator將它們轉換爲批處理。
# 創建驗證集數據生成器
image_gen_val = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. / 255)
val_data_gen = image_gen_val.flow_from_directory(batch_size=batch_size,
directory=validation_dir,
target_size=(IMG_HEIGHT, IMG_WIDTH),
class_mode='binary')
Dropout
另一種減少過擬合的技術是在網絡中引入Dropout。這是一種正則化形式,它強制網絡中的權重只取小值,這使得權重值的分佈更加規則,並且網絡可以減少對小訓練樣本的過度擬合。
當將Dropout應用到一個層時,它會在訓練過程中從所應用的層中隨機丟棄(設置爲零)數量的輸出單位。Dropout取一個分數作爲它的輸入值,形式如0.1、0.2、0.4等。這意味着從應用層隨機放棄10%、20%或40%的輸出單位。 當將0.1 D應用於某一層時,它會在每個訓練週期中隨機殺死10%的輸出單位。 用這個新的三維特性創建一個網絡體系結構,並將其應用於不同的卷積和完全連接的層。
創建一個有Dropouts的新網絡
在這裏,您將Dropout應用於第一個和最後一個最大池層。在每個訓練期間,應用Dropout將隨機設置20%的神經元爲零。這有助於避免過度擬合訓練數據集。
# 創建包含Dropouts的模型
model_new = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(16, 3, padding='same', activation='relu',
input_shape=(IMG_HEIGHT, IMG_WIDTH, 3)),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dense(1)
])
編譯模型:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 150, 150, 16) 448
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 75, 75, 16) 0
_________________________________________________________________
dropout (Dropout) (None, 75, 75, 16) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 75, 75, 32) 4640
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 37, 37, 32) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 37, 37, 64) 18496
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 18, 18, 64) 0
_________________________________________________________________
dropout_1 (Dropout) (None, 18, 18, 64) 0
_________________________________________________________________
flatten (Flatten) (None, 20736) 0
_________________________________________________________________
dense (Dense) (None, 512) 10617344
_________________________________________________________________
dense_1 (Dense) (None, 1) 513
=================================================================
Total params: 10,641,441
Trainable params: 10,641,441
Non-trainable params: 0
_________________________________________________________________
訓練模型
在成功地將數據擴充引入訓練樣例並向網絡中添加Dropout後,訓練此新網絡:
history = model_new.fit_generator(
train_data_gen,
steps_per_epoch=total_train // batch_size,
epochs=epochs,
validation_data=val_data_gen,
validation_steps=total_val // batch_size
)
可視化模型
想象一下訓練後的新模型,你會發現過度擬合明顯比以前少了。在對模型進行更多時期的訓練後,精確度應該會提高。
如以下是將epochs = 15修改成epochs = 30的效果,其他效果自己嘗試:
完整代碼如下:
import tensorflow as tf
import os
import numpy as np
import matplotlib.pyplot as plt
"""
本教程遵循一個基本的機器學習工作流程:
-檢查和理解數據
-建立輸入管道
-建立模型
-訓練模型
-測試模型
-改進模型並重復該過程
"""
base_dir = './dataset/'
train_dir = os.path.join(base_dir, 'train/')
validation_dir = os.path.join(base_dir, 'validation/')
train_cats_dir = os.path.join(train_dir, 'cats') # directory with our training cat pictures
train_dogs_dir = os.path.join(train_dir, 'dogs') # directory with our training dog pictures
validation_cats_dir = os.path.join(validation_dir, 'cats') # directory with our validation cat pictures
validation_dogs_dir = os.path.join(validation_dir, 'dogs') # directory with our validation dog pictures
"""
爲方便起見,設置預處理數據集和訓練網絡時要使用的變量。
"""
batch_size = 128
epochs = 30
IMG_HEIGHT = 150
IMG_WIDTH = 150
num_cats_tr = len(os.listdir(train_cats_dir)) # total training cat images: 1000
num_dogs_tr = len(os.listdir(train_dogs_dir)) # total training dog images: 1000
num_cats_val = len(os.listdir(validation_cats_dir)) # total validation cat images: 500
num_dogs_val = len(os.listdir(validation_dogs_dir)) # total validation dog images: 500
total_train = num_cats_tr + num_dogs_tr # Total training images: 2000
total_val = num_cats_val + num_dogs_val # Total validation images: 1000
# 該函數將圖像繪製成1行5列的網格形式,圖像放置在每一列中。
def plotImages(images_arr):
fig, axes = plt.subplots(1, 5, figsize=(20, 20))
axes = axes.flatten()
for img, ax in zip(images_arr, axes):
ax.imshow(img)
ax.axis('off')
plt.tight_layout()
plt.show()
"""
數據準備
將圖像格式化成經過適當預處理的浮點張量,然後輸入網絡:
- 從磁盤讀取圖像。
- 解碼這些圖像的內容,並根據它們的RGB內容將其轉換成適當的網格格式。
- 把它們轉換成浮點張量。
- 將張量從0到255之間的值重新縮放到0到1之間的值,因爲神經網絡更喜歡處理小的輸入值。
幸運的是,所有這些任務都可以用tf.keras提供的ImageDataGenerator類來完成。
它可以從磁盤讀取圖像,並將它們預處理成適當的張量。它還將設置發生器,將這些圖像轉換成一批張量——這對訓練網絡很有幫助。
"""
image_gen_train = tf.keras.preprocessing.image.ImageDataGenerator(
rescale=1. / 255,
rotation_range=45,
width_shift_range=.15,
height_shift_range=.15,
horizontal_flip=True,
zoom_range=0.5
)
# 在爲訓練和驗證圖像定義生成器之後,flow_from_directory方法從磁盤加載圖像,應用重新縮放,並將圖像調整到所需的尺寸。
train_data_gen = image_gen_train.flow_from_directory(batch_size=batch_size,
directory=train_dir,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH),
class_mode='binary')
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
plotImages(augmented_images)
# 創建驗證集數據生成器
image_gen_val = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. / 255)
val_data_gen = image_gen_val.flow_from_directory(batch_size=batch_size,
directory=validation_dir,
target_size=(IMG_HEIGHT, IMG_WIDTH),
class_mode='binary')
# 創建模型
model_new = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(16, 3, padding='same', activation='relu',
input_shape=(IMG_HEIGHT, IMG_WIDTH, 3)),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dense(1)
])
#
# # 編譯模型
# # 這邊選擇ADAM優化器和二進制交叉熵損失函數。要查看每個訓練時期的訓練和驗證準確性,請傳遞metrics參數。
model_new.compile(optimizer='adam',
loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
metrics=['accuracy'])
model_new.summary()
# 在成功地將數據擴充引入訓練樣例並向網絡中添加Dropout後,訓練此新網絡:
history = model_new.fit_generator(
train_data_gen,
steps_per_epoch=total_train // batch_size,
epochs=epochs,
validation_data=val_data_gen,
validation_steps=total_val // batch_size
)
# 可視化模型
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs_range = range(epochs)
plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()