Deep learning with Python 學習筆記(2)


本節介紹基於Keras的CNN

卷積神經網絡接收形狀為 (image_height, image_width, image_channels)的輸入張量(不包括批量維度),寬度和高度兩個維度的尺寸通常會隨着網絡加深而變小。通道數量由傳入 Conv2D 層的第一個參數所控制

用卷積神經網絡對 MNIST 數字進行分類Demo

from keras import layers
from keras import models
from keras.datasets import mnist
from keras.utils import to_categorical


def set_model():
    model = models.Sequential()
    model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    # 需要將 3D 輸出展平為 1D,將(3, 3, 64)輸出展平為(576, )
    model.add(layers.Flatten())
    model.add(layers.Dense(64, activation='relu'))
    model.add(layers.Dense(10, activation='softmax'))
    # 查看模型各層狀態
    model.summary()
    return model


(train_images, train_labels), (test_images, test_labels) = mnist.load_data(path='/home/fan/dataset/mnist.npz')
train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

model = set_model()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=5, batch_size=64)
test_loss, test_acc = model.evaluate(test_images, test_labels)
print(test_acc)

運行之后,顯示正確率為0.9921,而之前使用的密集連接網絡的正確率為0.9794,提高了0.0127

密集連接層和卷積層的根本區別在於, Dense 層從輸入特征空間中學到的是全局模式,如果模式出現在新的位置,它只能重新學習這個模式,而卷積層學到的是局部模式,可以在任何位置進行匹配
學習局部模式使得CNN具有以下性質:

  1. 卷積神經網絡學到的模式具有平移不變性(translation invariant)

卷積神經網絡在圖像右下角學到某個模式之后,它可以在任何地方識別這個模式,比如左上角
對於密集連接網絡來說,如果模式出現在新的位置,它只能重新學習這個模式

  1. 卷積神經網絡可以學到模式的空間層次結構(spatial hierarchies of patterns)

第一個卷積層將學習較小的局部模式(比如邊緣),第二個卷積層將學習由第一層特征組成的更大的模式,以此類推。這使得卷積神經網絡可以有效地學習越來越復雜、越來越抽象的視覺概念(因為視覺世界從根本上具有空間層次結構)

對於包含兩個空間軸(高度和寬度)和一個深度軸(也叫通道軸)的 3D 張量,其卷積也叫特征圖(feature map)。卷積運算從輸入特征圖中提取圖塊,並對所有這些圖塊應用相同的變換,生成輸出特征圖(output feature map)。該輸出特征圖仍是一個 3D 張量,具有寬度和高度,其深度可以任意取值,因為輸出深度是層的參數,深度軸的不同通道不再像 RGB 輸入那樣代表特定顏色,而是代表過濾器(filter)。過濾器對輸入數據的某一方面進行編碼

上例中,模型定義了

model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))

該卷積層接收一個大小為 28 * 28 * 1 的特征圖,輸出一個 26 * 26 * 32 的特征圖(26 = (28 -3) / 1 + 1),該26 * 26 * 32是過濾器對輸入的響應圖(response map),表示這個過濾器模式在輸入中不同位置的響應。這也是特征圖這一術語的含義: 深度軸的每個維度都是一個特征(或過濾器),而 2D 張量 output[:, :, n]是這個過濾器在輸入上的響應的二維空間圖(map)

卷積由以下兩個關鍵參數所定義

  1. 從輸入中提取的圖塊尺寸: 這些圖塊的大小通常是 3×3 或 5×5
  2. 輸出特征圖的深度:卷積所計算的過濾器的數量
    對於 Keras 的 Conv2D 層,這些參數都是向層傳入的前幾個參數: Conv2D(output_depth, (window_height, window_width))

卷積的工作原理
在 3D 輸入特征圖上滑動(slide)這些 3×3 或 5×5 的窗口,在每個可能的位置停止並提取周圍特征的 3D 圖塊[形狀為 (window_height, window_width, input_depth) ]。然后每個 3D 圖塊與學到的同一個權重矩陣[叫作卷積核(convolution kernel)]做張量積,轉換成形狀為 (output_depth,) 的 1D 向量。然后對所有這些向量進行空間重組,使其轉換為形狀為 (height, width, output_depth) 的 3D 輸出特征圖。輸出特征圖中的每個空間位置都對應於輸入特征圖中的相同位置

卷積計算

可見,當特征圖通過卷積核之后,特征圖的尺寸變小,具體變化為

\[outputSize = \frac {(inputSize - ConvSize + 2*padding)}{stride} \]

其中,outputSize 為輸出尺寸,inputSize 為輸入尺寸,ConvSize為卷積核尺寸,padding 為填充,stride 為步幅

對於 Conv2D 層,可以通過 padding 參數來設置填充,這個參數有兩個取值: "valid" 表示不使用填充(只使用有效的窗口位置);"same" 表示“填充后輸出的寬度和高度與輸入相同”。padding 參數的默認值為 "valid"

最大池化通常使用 2×2 的窗口和步幅 2,其目的是將特征圖下采樣 2 倍。與此相對的是,卷積通常使用 3×3 窗口和步幅 1
通過池化,我們可以減少參數數量,防止過擬合,同時可以使得之后的卷積相對於之前的獲得更大的視野,從而更好地學習特征的空間層級結構

卷積神經網絡主要由 Conv2D 層(使用 relu 激活)和MaxPooling2D 層交替堆疊構成,當要處理更大的圖像和更復雜的問題時,需要相應的增大網絡,即可以再增加一個 Conv2D + MaxPooling2D 的組合。這既可以增大網絡容量,也可以進一步減小特征圖的尺寸,使其在連接 Flatten 層時尺寸不會太大

在向網絡中輸入數據時,我們首先需要將數據進行預處理,將其格式化為浮點數張量,JPEG數據處理步驟如下

  1. 讀取圖像
  2. 將JPEG文件解碼為RGB像素網絡
  3. 將像素網絡轉換為浮點數張量
  4. 將像素值縮放到[0, 1]區間

當數據量較大時,我們可以采用生成器的方式將數據依次喂給網絡來進行擬合
Keras包含ImageDataGenerator 類,可以快速創建 Python 生成器,能夠將硬盤上的圖像文件自動轉換為預處理好的張量批量
讓模型對數據擬合

model.fit_generator(train_generator, steps_per_epoch=100, epochs=30, 
                    validation_data=validation_generator, validation_steps=50)

第一個參數為數據生成器,第二個參數表示從生成器中抽取 steps_per_epoch 個批量后(即運行了steps_per_epoch 次梯度下降),擬合過程將進入下一個輪次,第三個參數為驗證數據,如果其為一個數據生成器的話,需要指定validation_steps參數,來說明需要從驗證生成器中抽取多少個批次用於評估

Keras保存模型

model.save('\*\*\*.h5')

一個使用CNN的貓狗分類Demo
數據集下載
此處為了快速得到結果,使用貓狗各1000個圖像訓練,各500個驗證,各500個測試

from keras import layers
from keras import models
from keras import optimizers
import os
import matplotlib.pyplot as plt


def get_model():
    # 貓狗二分類
    model = models.Sequential()
    model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(128, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(128, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Flatten())
    model.add(layers.Dense(512, activation='relu'))
    model.add(layers.Dense(1, activation='sigmoid'))
    # 顯示模型各層信息
    model.summary()
    model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])
    return model


from keras.preprocessing.image import ImageDataGenerator


def data_preprocess(train_dir, validation_dir):
    # Python生成器會不斷循環目標文件夾中的圖像,從而會不停地生成批量
    # 將圖像乘1/255縮放
    train_datagen = ImageDataGenerator(rescale=1. / 255)
    test_datagen = ImageDataGenerator(rescale=1. / 255)
    # 將所有文件調整為150 * 150
    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')
    return train_generator, validation_generator


base_dir = '/home/fan/dataset/dogVScat/testDogVSCat'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
train_generator, validation_generator = data_preprocess(train_dir, validation_dir)
model = get_model()
history = model.fit_generator(train_generator, steps_per_epoch=100, epochs=30, validation_data=validation_generator, validation_steps=50)


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()

實驗結果
loss: 0.0304 - acc: 0.9925 - val_loss: 1.2209 - val_acc: 0.7010

從如上結果可以看出,我們的網絡過擬合了,可以使用數據增強的方式來防止過擬合

數據增強是從現有的訓練樣本中生成更多的訓練數據,其方法是利用多種能夠生成可信圖像的隨機變換來增加(augment)樣本。其目標是,模型在訓練時不會兩次查看完全相同的圖像。這讓模型能夠觀察到數據的更多內容,從而具有更好的泛化能力
在 Keras 中,這可以通過對 ImageDataGenerator 實例讀取的圖像執行多次隨機變換來實現
Demo

from keras.preprocessing.image import ImageDataGenerator  
datagen = ImageDataGenerator(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')

其中

rotation_range 是角度值(在 0~180 范圍內),表示圖像隨機旋轉的角度范圍
width_shift 和 height_shift 是圖像在水平或垂直方向上平移的范圍(相對於總寬度或總高度的比例)
shear_range 是隨機錯切變換的角度
zoom_range 是圖像隨機縮放的范圍
horizontal_flip 是隨機將一半圖像水平翻轉
fill_mode 是用於填充新創建像素的方法,這些新像素可能來自於旋轉或寬度 / 高度平移

使用數據增強的方法增加數據

from keras.preprocessing import image
import matplotlib.pyplot as plt
from keras.preprocessing.image import ImageDataGenerator
import numpy as np


img_path = '/home/fan/dataset/dogVScat/testDogVSCat/train/dogs/dog.77.jpg'
# 加載圖片並調整尺寸
img = np.asarray(image.load_img(img_path, target_size=(150, 150)))
datagen = ImageDataGenerator(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')
plt.imshow(img)
plt.title('original img')
plt.show()


img = img.reshape((1, ) + img.shape)
i = 0
for item in datagen.flow(img, batch_size=1):
    item = image.array_to_img(item[0])
    plt.subplot(2, 2, i+1)
    plt.imshow(item)
    i += 1
    plt.title('generated img ' + str(i))
    if i % 4 == 0:
        break
plt.show()

結果如下

為了繼續降低過擬合,可以再向網絡中添加dropout。Keras向網絡中添加dropout

model.add(layers.Dropout(0.5))

通過使用數據增強,正則化以及調節網絡參數可以在一定程度上提高精度,但是因為數據較少,想要進一步提高精度就需要使用預訓練的模型

Deep learning with Python 學習筆記(3)
Deep learning with Python 學習筆記(1)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM