天天看點

第五章 深度學習用于計算機視覺(三)

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,卷積神經網絡可視化。

繼續閱讀