簡單的識別貓狗的模型

從Google下載貓狗訓練集與驗證集的zip壓縮包,提取到項目目錄下。這個文件夾裏面包含訓練(train)和驗證(validation)數據集的子目錄,而且每個子目錄都包含貓和狗的子目錄。

image.png

可以直接在它這個目錄下創建一個python文件,就叫貓和狗(cats_and_dogs.py),然後配置好訓練集、驗證集的目錄。

base_dir = '../cats_and_dogs_filtered'
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')
# 用於訓練的狗圖目錄。
train_dogs_dir = os.path.join(train_dir, 'dogs')

# 用於驗證的貓圖目錄。
validation_cats_dir = os.path.join(validation_dir, 'cats')
# 用於驗證的狗圖目錄。
validation_dogs_dir = os.path.join(validation_dir, 'dogs')

現在可以看看貓(cats)和狗(dogs)在訓練(train)和驗證(validation)目錄中的文件命名是怎麼約定的。

train_cat_fnames = os.listdir(train_cats_dir)
print('貓的訓練、驗證目錄中的文件命名約定:\n%s' % (train_cat_fnames[:10]))

train_dog_fnames = os.listdir(train_dogs_dir)
train_dog_fnames.sort()
print('狗的訓練、驗證目錄中的文件命名約定:\n%s' % (train_dog_fnames[:10]))

'''
貓的訓練、驗證目錄中的文件命名約定:
['cat.952.jpg', 'cat.946.jpg', 'cat.6.jpg', 'cat.749.jpg', 'cat.991.jpg', 'cat.985.jpg', 'cat.775.jpg', 'cat.761.jpg', 'cat.588.jpg', 'cat.239.jpg']
狗的訓練、驗證目錄中的文件命名約定:
['dog.0.jpg', 'dog.1.jpg', 'dog.10.jpg', 'dog.100.jpg', 'dog.101.jpg', 'dog.102.jpg', 'dog.103.jpg', 'dog.104.jpg', 'dog.105.jpg', 'dog.106.jpg']
'''

還可以找出訓練和驗證目錄中貓和狗圖像的總數。

print('總共用於訓練的貓圖像:', len(os.listdir(train_cats_dir)))
print('總共用於訓練的狗圖像:', len(os.listdir(train_dogs_dir)))
print('總共用於驗證的貓圖像:', len(os.listdir(validation_cats_dir)))
print('總共用於驗證的狗圖像:', len(os.listdir(validation_dogs_dir)))

'''
總共用於訓練的貓圖像: 1000
總共用於訓練的狗圖像: 1000
總共用於驗證的貓圖像: 500
總共用於驗證的狗圖像: 500
'''

現在知道了,有1,000個貓狗的訓練圖片,還有500個貓狗的驗證圖片。現在可以再看幾張圖片,以便提前地瞭解一下貓狗數據集裏都是些什麼圖片。

import matplotlib.pyplot as plt
import matplotlib.image as mpimg

# 輸出圖表的參數,將以4x4的配置輸出貓狗數據集的部分圖片。
nrows = 4
ncols = 4

# 迭代圖像的當前索引。
pic_index = 0

# 設置matplotlib(Python的2D繪圖庫)圖,並將其設置爲適合4x4圖片大小。
fig = plt.gcf()
fig.set_size_inches(ncols * 4, nrows * 4)

pic_index += 8
next_cat_pix = [os.path.join(train_cats_dir, fname)
                for fname in train_cat_fnames[pic_index-8:pic_index]]
next_dog_pix = [os.path.join(train_dogs_dir, fname)
                for fname in train_dog_fnames[pic_index-8:pic_index]]

for i, img_path in enumerate(next_cat_pix+next_dog_pix):
  # 設置子圖,子圖的索引從1開始。
  sp = plt.subplot(nrows, ncols, i + 1)
  # 不顯示軸(或者說網格線)。
  sp.axis('Off')
  img = mpimg.imread(img_path)
  plt.imshow(img)

plt.show()

輸出的實際效果如下圖所示,每次重新運行都會查看到新的一批圖片。

image.png

現在準備從頭開始構建小型Convnet(開源的卷積神經網絡代碼),並期望能達到72%的準確率。我們要處理的圖片是150x150的彩色圖片,編寫代碼,將堆疊3個卷積層(convolution)、修正線性激活函數(relu)和最大池化層(maxpooling)模塊。

我們的卷積層(convolution)過濾器大小爲3x3,我們的最大池化層(maxpooling)過濾器爲2x2。

  • 第一個卷積層(convolution):設置16個3x3大小的過濾器,即識別16種輪廓及邊緣,最後通過修正線性激活函數(relu)形成一個3x3x16的立方體。
  • 第二個卷積層(convolution):設置32個3x3大小的過濾器,即識別32種輪廓及邊緣,最後通過修正線性激活函數(relu)形成一個3x3x32的立方體。
  • 第三個卷積層(convolution):設置64個3x3大小的過濾器,即識別64種輪廓及邊緣,最後通過修正線性激活函數(relu)形成一個3x3x64的立方體。

每一個卷積層(convolution)成形之後,都會追加一個最大池化層(maxpooling),通過池化操作來降低卷積層輸出的特徵向量,同時改善結果,使其不易出現過擬合。

注意:這是一種廣泛使用的配置,並且已知可以很好地用於圖像分類。

此外,由於我們只有相對較少的訓練樣本(1,000張),僅使用三個卷積模塊就可以保持模型較小,從而降低過度擬合(overfitting)的風險。過擬合(overfitting)的意思是:對見過的數據,分類效果極好;而對沒見過的數據,表現很糟糕。

from tensorflow import keras

# 輸入特徵圖是150x150x3,其中150x150用於圖像像素,3用於三個顏色通道(R、G和B)。
img_input = keras.layers.Input(shape=(150, 150, 3))

# 第一個卷積層提取3x3x16的特徵,使用修正線性激活函數(`relu`),然後是具有2x2大小的最大池化層。
x = keras.layers.Conv2D(16, 3, activation='relu')(img_input)
x = keras.layers.MaxPooling2D(2)(x)

# 第二個卷積層提取3x3x32的特徵,使用修正線性激活函數(`relu`),然後是具有2x2大小的最大池化層。
x = keras.layers.Conv2D(32, 3, activation='relu')(x)
x = keras.layers.MaxPooling2D(2)(x)

# 第三個卷積層提取3x3x64的特徵,使用修正線性激活函數(`relu`),然後是具有2x2大小的最大池化層。
x = keras.layers.Conv2D(64, 3, activation='relu')(x)
x = keras.layers.MaxPooling2D(2)(x)

然後是最重要一步,創建兩個全連接層(dense),因爲我們正面臨着兩個分類問題,即二元分類問題,我們將以sigmoid函數(一個常用的神經網絡激勵函數)激活我們的網絡,以便我們的網絡輸出將是0~1之間的單個標量,當前編碼的概率圖像是一維(而不是零維)。

# 將特徵圖展平爲一維數據(`1-dim`)張量,以便添加全連接層。
x = keras.layers.Flatten()(x)

# 使用修正線性激活函數(`relu`)和512個隱藏單元(或神經元)創建全連接層。
x = keras.layers.Dense(512, activation='relu')(x)

# 使用單個節點(或神經元)和`sigmoid`激活函數創建輸出層。
output = keras.layers.Dense(1, activation='sigmoid')(x)

# 創建模型:
# input = 輸入特徵映射
# output = 輸入特徵映射 + 堆疊卷積層/最大池化層數 + 全連接層
# 全連接層 + `sigmoid`輸出層
model = keras.Model(img_input, output)

最後,可以總結一下整個模型的框架,欣賞一下我們創建的卷積神經網絡模型。

model.summary()

'''
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 150, 150, 3)       0         
_________________________________________________________________
conv2d (Conv2D)              (None, 148, 148, 16)      448       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 74, 74, 16)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 72, 72, 32)        4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 36, 36, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 34, 34, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 17, 17, 64)        0         
_________________________________________________________________
flatten (Flatten)            (None, 18496)             0         
_________________________________________________________________
dense (Dense)                (None, 512)               9470464   
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 513       
=================================================================
Total params: 9,494,561
Trainable params: 9,494,561
Non-trainable params: 0
_________________________________________________________________
'''

上面輸出中的“Output Shape”列顯示特徵圖的大小如何在每個連續圖層中演變。很明顯,由於沒有設置填充(padding),卷積層(conv)會將特徵映射的大小減少一點,並且每個最大池化層(max_pooling)將特徵映射減半。

接下來,我們要配置模型訓練的規範,我們將使用交叉熵損失函數(binary_crossentropy)訓練我們的模型,因爲這是一個二進制分類問題,我們的最終激活函數是一個sigmoid函數。

我們將使用學習率爲0.001的rmsprop(一種常用的深度學習優化算法)優化器。同時,在訓練期間,我們想監控分類準確性。

注意:在當前情況下,使用rmsprop優化算法優於隨機梯度下降(SGD)算法,因爲rmsprop會自動調整學習速率。

model.compile(loss='binary_crossentropy',
              optimizer=keras.optimizers.RMSprop(lr=0.001),
              metrics=['acc'])

現在,讓我們設置數據生成器,它將讀取源文件夾中的圖片,將它們轉換爲float32的張量,並將它們附帶上標籤提供給我們的網絡。這樣的話,我們將有一個用於訓練圖像的生成器和一個用於驗證圖像的生成器,生成器將生產20個尺寸爲150x150的圖像及其標籤(二進制)。

通常進入神經網絡的數據應該以某種方式進行標準化,以使其更適合網絡處理。(將原始像素直接輸入網絡的情況並不常見)在這裏,我們通過將像素值歸一化爲0~1範圍來預處理圖像(最初所有值都在0~255範圍內)。

在Keras(用Python編寫的高級神經網絡API)中,可以使用rescale參數通過keras.preprocessing.image.ImageDataGenerator類完成此操作。此類允許我們通過.flow(data, labels).flow_from_directory(directory)實例化增強圖像批次(及其標籤)的生成器。然後,這些生成器可以與接受數據生成器作爲輸入的Keras模型方法一起使用:fit_generatorevaluate_generatorpredict_generator

# 對所有圖像按照指定的尺度因子,進行放大或縮小,設置值在0~1之間,通常爲1./255。
train_datagen = keras.preprocessing.image.ImageDataGenerator(rescale=1./255)
test_datagen = keras.preprocessing.image.ImageDataGenerator(rescale=1./255)

# 使用train_datagen生成器分批輪流訓練20張圖像。
train_generator = train_datagen.flow_from_directory(
        # 這是訓練圖像的源目錄。
        train_dir,
       # 所有圖像將調整爲150x150大小。
        target_size=(150, 150),
        batch_size=20,
        # 由於我們使用binary_crossentropy損失算法,我們需要二進制標籤。
        class_mode='binary')

# 使用test_datagen生成器批量生成20個流程驗證圖像。
validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

現在讓我們訓練所有的圖像,一共2000個,分15個時期,並驗證所有驗證圖像,一個1000個。

# 開始訓練。
history = model.fit_generator(
      train_generator,
      # 2000張圖片 = 批量大小 * 步進。
      steps_per_epoch=100,
      epochs=15,
      validation_data=validation_generator,
      # 1000張圖片 = 批量大小 * 步進。
      validation_steps=50,
      verbose=2)

我們可以做一個有意思的事情,就是可視化在訓練時如何模型是怎麼變化的,讓我們從訓練集中選擇一個隨機的貓或狗圖像,然後生成一個圖形,其中每一行是圖層的輸出,並且該行中的每個圖像都是該輸出特徵圖中的特定濾鏡。

import numpy as np
import random

# 定義一個新的模型,它將圖像作爲輸入,並在第一個模型之後輸出前一個模型中所有圖層的中間表示。
successive_outputs = [layer.output for layer in model.layers[1:]]
visualization_model = keras.Model(img_input, successive_outputs)

# 從訓練集中準備一隻貓或狗的隨機輸入圖像。
cat_img_files = [os.path.join(train_cats_dir, f) for f in train_cat_fnames]
dog_img_files = [os.path.join(train_dogs_dir, f) for f in train_dog_fnames]
img_path = random.choice(cat_img_files + dog_img_files)

# 這是PIL圖像。
img = keras.preprocessing.image.load_img(img_path, target_size=(150, 150))
# Numpy數組形狀(150,150,3)。
x = keras.preprocessing.image.img_to_array(img)
# Numpy數組形狀(1,150,150,3)。
x = x.reshape((1,) + x.shape)

# 重新縮放1/255。
x /= 255

# 通過神經網絡運行我們的圖像,從而獲得該圖像的所有中間表示。
successive_feature_maps = visualization_model.predict(x)

# 這些是圖層的名稱,因此可以將它們作爲我們圖表的一部分。
layer_names = [layer.name for layer in model.layers]

# 現在展示一下。
for layer_name, feature_map in zip(layer_names, successive_feature_maps):
  if len(feature_map.shape) == 4:
    # 只需對conv/maxpool(卷積/最大化池)圖層執行此操作,而不是全連接層。
    # 特徵圖中的要素數量。
    n_features = feature_map.shape[-1]
    # 特徵圖具有形狀(1,size,size,n_features)。
    size = feature_map.shape[1]
    # 我們將在此矩陣中平鋪圖像。
    display_grid = np.zeros((size, size * n_features))
    for i in range(n_features):
      # 對特徵該進行後處理,使其在視覺上更加美觀。
      x = feature_map[0, :, :, I]
      x -= x.mean()
      x /= x.std()
      x *= 64
      x += 128
      x = np.clip(x, 0, 255).astype('uint8')
      # 我們將每個過濾器平鋪到這個大的水平網格中。
      display_grid[:, i * size : (i + 1) * size] = x
    # 顯示網格。
    scale = 20. / n_features
    plt.figure(figsize=(scale * n_features, scale))
    plt.title(layer_name)
    plt.grid(False)
    plt.imshow(display_grid, aspect='auto', cmap='viridis')
    # 顯示圖像。
    plt.show()

當上面的代碼運行完之前,會依次顯示下面的幾張圖片。

Figure_1.png

Figure_2.png

Figure_3.png

Figure_4.png

Figure_5.png

Figure_6.png

正如上面所顯示的,我們看到了圖像從原始像素變爲越來越抽象和緊湊的展示,下游的展示開始突出顯示神經網絡關注的內容,並且它們顯示越來越少的特徵被“激活”;大多數都設置爲零,這被稱爲“稀疏性”,稀疏性是深度學習的關鍵特徵。

這些展示關於圖像的原始像素的信息越來越少,但是關於圖像類別的信息越來越精細,我們可以將convnet(卷積神經或一般的深層網絡)視爲信息蒸餾管道。

接下來我們可以評估一下模型的準確性和損失,繪製訓練期間收集的訓練/驗證準確性和損失。

# 檢索每個訓練時期的訓練和驗證數據集的準確度結果列表。
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)
plt.plot(epochs, val_acc)
plt.title('Training and validation accuracy')

plt.figure()

# 繪製每個時期的訓練和驗證損失。
plt.plot(epochs, loss)
plt.plot(epochs, val_loss)
plt.title('Training and validation loss')

plt.show()

上面的代碼運行結束前,會顯示下面圖片。

Figure_7.png

Figure_8.png

正如上面圖片所展示的,我們的模型明顯過度擬合了,我們的訓練準確度(藍線)接近100%,而我們的驗證準確度(橙線)停滯在70%上下。我們的驗證損失在僅僅5個時期後達到最小值,這是因爲我們的訓練樣本數量相對較少(2000個)。

因此,過度擬合應成爲我們的首要關注點,當太少特徵樣本的模型,不推廣到新數據的模式時,即當模型開始使用不相關的特徵進行預測時,就會發生過度擬合。例如,如果我們作爲人類,只能看到三個伐木工人的圖像,以及三個水手的圖像,其中只有一個戴帽子的人是伐木工人,你可能會開始認爲戴着帽子是一名伐木工人而不是水手的標誌。

然後我們就會做出一個非常糟糕的伐木工人/水手分類器,過度擬合是機器學習中的核心問題:假設我們將模型的參數擬合到給定的數據集,我們如何確保模型適用於以前從未見過的數據?我們如何避免學習特定於訓練數據的內容?

最後清理一下數據,運行以下代碼以終止內核並釋放內存資源。

os.kill(os.getpid(), signal.SIGKILL)

通過以上內容,就建立了一個簡單的識別貓狗的模型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章