1. 程式人生 > >keras系列︱影象多分類訓練與利用bottleneck features進行微調(三)

keras系列︱影象多分類訓練與利用bottleneck features進行微調(三)

不得不說,這深度學習框架更新太快了尤其到了Keras2.0版本,快到Keras中文版好多都是錯的,快到官方文件也有舊的沒更新,前路坑太多。
到發文為止,已經有theano/tensorflow/CNTK支援keras,雖然說tensorflow造勢很多,但是筆者認為接下來Keras才是正道。
筆者先學的caffe,從使用來看,比caffe簡單超級多,非常好用,特別是重新訓練一個模型,但是呢,在fine-tuning的時候,遇到了很多問題,對新手比較棘手。

訓練、訓練主要就”練“嘛,所以堆幾個案例就知道怎麼做了。
.

20181112更新
由於筆者使用的版本都比較老,可能很多demo都跑不通,筆者這邊推薦評論區的一位小夥伴提供的比較新的,跑通的code:

【火爐煉AI】深度學習005-簡單幾行Keras程式碼解決二分類

.

Keras系列:

.

一、CIFAR10 小圖片分類示例(Sequential式)

要訓練模型,首先得知道資料長啥樣。先來看看經典的cifar10是如何進行訓練的。
示例中CIFAR10採用的是Sequential式來編譯網路結構。

from __future__ import print_function
import keras
from keras.datasets import cifar10
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D

batch_size = 32
num_classes = 10
epochs = 200
data_augmentation = True

# 資料載入
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

# 多分類標籤生成
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

# 網路結構配置
model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same',
                 input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes))
model.add(Activation('softmax'))

# 訓練引數設定
# initiate RMSprop optimizer
opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)

# Let's train the model using RMSprop
model.compile(loss='categorical_crossentropy',
              optimizer=opt,
              metrics=['accuracy'])

# 生成訓練資料
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

if not data_augmentation:
    print('Not using data augmentation.')
    model.fit(x_train, y_train,
              batch_size=batch_size,
              epochs=epochs,
              validation_data=(x_test, y_test),
              shuffle=True)
else:
    print('Using real-time data augmentation.')
    # This will do preprocessing and realtime data augmentation:
    datagen = ImageDataGenerator(
        featurewise_center=False,  # set input mean to 0 over the dataset
        samplewise_center=False,  # set each sample mean to 0
        featurewise_std_normalization=False,  # divide inputs by std of the dataset
        samplewise_std_normalization=False,  # divide each input by its std
        zca_whitening=False,  # apply ZCA whitening
        rotation_range=0,  # randomly rotate images in the range (degrees, 0 to 180)
        width_shift_range=0.1,  # randomly shift images horizontally (fraction of total width)
        height_shift_range=0.1,  # randomly shift images vertically (fraction of total height)
        horizontal_flip=True,  # randomly flip images
        vertical_flip=False)  # randomly flip images

    # Compute quantities required for feature-wise normalization
    # (std, mean, and principal components if ZCA whitening is applied).
    datagen.fit(x_train)

# fit訓練
    # Fit the model on the batches generated by datagen.flow().
    model.fit_generator(datagen.flow(x_train, y_train,
                                     batch_size=batch_size),
                        steps_per_epoch=x_train.shape[0] // batch_size,
                        epochs=epochs,
                        validation_data=(x_test, y_test))

就像caffe裡面需要把資料編譯成LMDB一樣,keras也要資料服從其格式。來看看cifar10的資料格式:
.

1、載入資料

(x_train, y_train), (x_test, y_test) = cifar10.load_data()

這句用來網路上載入資料,跟之前application之中,pre-model一樣,有時間需要不斷的網上下載,所以等你下載完了,可以自己改一樣地址,讓其讀取本地檔案。
x_train格式例如(100,100,100,3),100張格式為1001003的影象集;y_train格式為(100,)

.

##2、多分類標籤指定keras格式
keras對多分類的標籤需要一種固定格式,所以需要按照以下的方式進行轉換,num_classes為分類數量,假設此時有5類:

y_train = keras.utils.to_categorical(y_train, num_classes)

最終輸出的格式應該是(100,5)
.

3、圖片預處理生成器ImageDataGenerator

datagen = ImageDataGenerator() 
datagen.fit(x_train)

生成器初始化datagen ,然後datagen.fit,計算依賴於資料的變換所需要的統計資訊
.

4、最終訓練格式-batch

把資料按照每個batch進行劃分,這樣就可以送到模型進行訓練了。比caffe中要LMDB快很多。

datagen.flow(x_train, y_train, batch_size=batch_size)

接收numpy陣列和標籤為引數,生成經過資料提升或標準化後的batch資料,並在一個無限迴圈中不斷的返回batch資料。

.

.

二、官方改編——多分類簡易網路結構(Sequential式)

1、資料來源與下載

官方文件是貓狗二分類,此時變成一個5分類,由於追求效率,從網上找來一個很小的資料集。來源於部落格:
Caffe學習系列(12):訓練和測試自己的圖片
資料描述:
共有500張圖片,分為大巴車、恐龍、大象、鮮花和馬五個類,每個類100張。
下載地址:http://pan.baidu.com/s/1nuqlTnN
編號分別以3,4,5,6,7開頭,各為一類。我從其中每類選出20張作為測試,其餘80張作為訓練。因此最終訓練圖片400張,測試圖片100張,共5類。如下圖:
這裡寫圖片描述
.

2、 載入與模型網路構建

很坑的是Keras中文文件本節還沒有及時更新,還需要看原版的網站。譬如keras中文文件是Convolution2D,但是現在是conv2D所以有點坑。

# 載入與模型網路構建
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense

model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=(150, 150,3)))
# filter大小3*3,數量32個,原始影象大小3,150,150
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())  # this converts our 3D feature maps to 1D feature vectors
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(5))   #                               matt,幾個分類就要有幾個dense
model.add(Activation('softmax'))#                     matt,多分類

二分類與多分類在前面的結構上都沒有問題,就是需要改一下最後的全連線層,因為此時有5分類,所以需要Dense(5),同時啟用函式是softmax,如果是二分類就是dense(2)+sigmoid(啟用函式)。

同時出現了以下的報錯:

報錯1:model.add(Convolution2D(32, 3, 3, input_shape=(3, 150, 150)))
ValueError: Negative dimension size caused by subtracting 3 from 1 for 'conv2d_6/convolution' (op: 'Conv2D') with input shapes: [?,1,148,32], [3,3,32,32].

報錯2:model.add(MaxPooling2D(pool_size=(2, 2)))
ValueError: Negative dimension size caused by subtracting 2 from 1 for 'max_pooling2d_11/MaxPool' (op: 'MaxPool') with input shapes: [?,1,148,32].

原因:
input_shape=(3,150, 150)是theano的寫法,而tensorflow需要寫出:(150,150,3);
需要修改Input_size。也就是"channels_last”和"channels_first"資料格式的問題。
.

3、設定訓練引數

# 二分類
#model.compile(loss='binary_crossentropy',
#              optimizer='rmsprop',
#              metrics=['accuracy'])

# 多分類
model.compile(loss='categorical_crossentropy',                                 # matt,多分類,不是binary_crossentropy
              optimizer='rmsprop',
              metrics=['accuracy'])
# 優化器rmsprop:除學習率可調整外,建議保持優化器的其他預設引數不變

二分類的引數與多分類的引數設定有些區別。

.
##4、影象預處理
然後我們開始準備資料,使用.flow_from_directory()來從我們的jpgs圖片中直接產生資料和標籤。
其中值得留意的是:

  • ImageDataGenerator:用以生成一個batch的影象資料,支援實時資料提升。訓練時該函式會無限生成資料,直到達到規定的epoch次數為止。
  • flow_from_directory(directory):
    以資料夾路徑為引數,生成經過資料提升/歸一化後的資料,在一個無限迴圈中無限產生batch資料

train_datagen = ImageDataGenerator(
        rescale=1./255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        '/.../train', 
        target_size=(150, 150),  # all images will be resized to 150x150
        batch_size=32,
        class_mode='categorical')                               # matt,多分類

validation_generator = test_datagen.flow_from_directory(
        '/.../validation',
        target_size=(150, 150),
        batch_size=32,
        class_mode='categorical')                             # matt,多分類
# class_mode='binary'

這一步驟是資料準備階段,會比較慢,同時多分類,需要設定class_mode為“categorical”。flow_from_directory是計算資料的一些屬性值,之後再訓練階段直接丟進去這些生成器。
.

5、訓練階段

model.fit_generator(
        train_generator,
        samples_per_epoch=2000,
        nb_epoch=50,
        validation_data=validation_generator,
        nb_val_samples=800)
# samples_per_epoch,相當於每個epoch資料量峰值,每個epoch以經過模型的樣本數達到samples_per_epoch時,記一個epoch結束
model.save_weights('/.../first_try_animal5.h5')  

最後的結果示範:

Epoch 48/50
62/62 [==============================] - 39s - loss: 0.0464 - acc: 0.9929 - val_loss: 0.3916 - val_acc: 0.9601
Epoch 49/50
62/62 [==============================] - 38s - loss: 0.0565 - acc: 0.9914 - val_loss: 0.6423 - val_acc: 0.9500
Epoch 50/50
62/62 [==============================] - 38s - loss: 0.0429 - acc: 0.9960 - val_loss: 0.4238 - val_acc: 0.9599
<keras.callbacks.History object at 0x7f049fc6f090>

.
##6、出現的問題
問題一:loss為負數
原因:如果出現loss為負,是因為之前多分類的標籤哪些設定不對,現在是5分類的,寫成了2分類之後導致了Loss為負數,形如下面

Epoch 43/50
62/62 [==============================] - 39s - loss: -16.0148 - acc: 0.1921 - val_loss: -15.9440 - val_acc: 0.1998
Epoch 44/50
61/62 [============================>.] - ETA: 0s - loss: -15.8525 - acc: 0.2049Segmentation fault (core dumped)

.

.

三、fine-tuning方式一:使用預訓練網路的bottleneck特徵

本節主要來源於:面向小資料集構建影象分類模型
當然,keras中文版裡面漏洞一大堆… 沒有跟著版本更新,導致很多內容都是不對的,哎…

先看VGG-16的網路結構如下:
圖片

本節主要是通過已經訓練好的模型,把bottleneck特徵抽取出來,然後滾到下一個“小”模型裡面,也就是全連線層。
實施步驟為:

  • 1、把訓練好的模型的權重拿來,model;
  • 2、執行,提取bottleneck feature(網路在全連線之前的最後一層啟用的feature
    map,卷積-全連線層之間),單獨拿出來,並儲存
  • 3、bottleneck層資料,之後 + dense全連線層,進行fine-tuning
    .
    ##1、匯入預訓練權重與網路框架
    這裡keras中文文件是錯誤的,要看現在的原作者的部落格
WEIGHTS_PATH = '/home/ubuntu/keras/animal5/vgg16_weights_tf_dim_ordering_tf_kernels.h5'
WEIGHTS_PATH_NO_TOP = '/home/ubuntu/keras/animal5/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5'

from keras.applications.vgg16_matt import VGG16
model = VGG16(include_top=False, weights='imagenet')

其中WEIGHTS_PATH_NO_TOP 就是去掉了全連線層,可以用他直接提取bottleneck的特徵,感謝原作者。
.

2、提取圖片的bottleneck特徵

需要步驟:

  • 載入圖片;
  • 灌入pre-model的權重;
  • 得到bottleneck feature
#如何提取bottleneck feature
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense

# (1)載入圖片
# 影象生成器初始化
from keras.preprocessing.image import ImageDataGenerator
import numpy as np
datagen = ImageDataGenerator(rescale=1./255)

# 訓練集影象生成器
generator = datagen.flow_from_directory(
        '/home/ubuntu/keras/animal5/train',
        target_size=(150, 150),
        batch_size=32,
        class_mode=None,
        shuffle=False) 

# 驗證集影象生成器
generator = datagen.flow_from_directory(
        '/home/ubuntu/keras/animal5/validation',
        target_size=(150, 150),
        batch_size=32,
        class_mode=None,
        shuffle=False)

#(2)灌入pre-model的權重
model.load_weights('/.../vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5')

#(3)得到bottleneck feature
bottleneck_features_train = model.predict_generator(generator, 500)
# 核心,steps是生成器要返回資料的輪數,每個epoch含有500張圖片,與model.fit(samples_per_epoch)相對
np.save(open('bottleneck_features_train.npy', 'w'), bottleneck_features_train)

bottleneck_features_validation = model.predict_generator(generator, 100)
# 與model.fit(nb_val_samples)相對,一個epoch有800張圖片,驗證集
np.save(open('bottleneck_features_validation.npy', 'w'), bottleneck_features_validation)

注意

  • class_mode,此時為預測場景,製作資料階段,不用設定標籤,因為此時是按照順序產生;而在train_generator資料訓練之前的資料準備,則需要設定標籤
  • shuffle,此時為預測場景,製作資料集,不用打亂;但是在model.fit過程中需要打亂,表示是否在訓練過程中每個epoch前隨機打亂輸入樣本的順序。

.

3、 fine-tuning - "小"網路

主要步驟:

  • (1)匯入bottleneck_features資料;
  • (2)設定標籤,並規範成Keras預設格式;
  • (3)寫“小網路”的網路結構
  • (4)設定引數並訓練
# (1)匯入bottleneck_features資料
train_data = np.load(open('bottleneck_features_train.npy'))
# the features were saved in order, so recreating the labels is easy
train_labels = np.array([0] * 100 + [1] * 100 + [2] * 100 + [3] * 100 + [4] * 96)  # matt,打標籤

validation_data = np.load(open('bottleneck_features_validation.npy'))
validation_labels = np.array([0] * 20 + [1] * 20 + [2] * 20 + [3] * 20 + [4] * 16)  # matt,打標籤

# (2)設定標籤,並規範成Keras預設格式
train_labels = keras.utils.to_categorical(train_labels, 5)
validation_labels = keras.utils.to_categorical(validation_labels, 5)

# (3)寫“小網路”的網路結構
model = Sequential()
#train_data.shape[1:]
model.add(Flatten(input_shape=(4,4,512)))# 4*4*512
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
#model.add(Dense(1, activation='sigmoid'))  # 二分類
model.add(Dense(5, activation='softmax'))  # matt,多分類
#model.add(Dense(1))
#model.add(Dense(5)) 
#model.add(Activation('softmax'))

# (4)設定引數並訓練
model.compile(loss='categorical_crossentropy',   
# matt,多分類,不是binary_crossentropy
              optimizer='rmsprop',
              metrics=['accuracy'])

model.fit(train_data, train_labels,
          nb_epoch=50, batch_size=16,
          validation_data=(validation_data, validation_labels))
model.save_weights('bottleneck_fc_model.h5')

因為特徵的size很小,模型在CPU上跑的也會很快,大概1s一個epoch。

#正確的結果:
Epoch 48/50
496/496 [==============================] - 0s - loss: 0.3071 - acc: 0.7762 - val_loss: 4.9337 - val_acc: 0.3229
Epoch 49/50
496/496 [==============================] - 0s - loss: 0.2881 - acc: 0.8004 - val_loss: 4.3143 - val_acc: 0.3750
Epoch 50/50
496/496 [==============================] - 0s - loss: 0.3119 - acc: 0.7984 - val_loss: 4.4788 - val_acc: 0.5625
<keras.callbacks.History object at 0x7f25d4456e10>

.

4、遇到的問題

(1)Flatten層——最難處理的層
其中在配置網路中,我發現Flatten是最容易出現問題的Layer了。非常多的問題,是因為輸給這個層的格式不對。譬如報錯:

語句:model.add(Flatten(input_shape=train_data.shape[1:]))
ValueError: Input 0 is incompatible with layer flatten_5: expected min_ndim=3, found ndim=2

於是要改成(4,4,512),這樣寫(512,4,4)也不對!

(2)標籤格式問題
model.fit之後報錯:

ValueError: Error when checking target: expected dense_2 to have shape (None, 5) but got array with shape (500, 1)

標籤格式沒有設定,特別是多分類會遇見這樣的問題。需要keras.utils.to_categorical()

train_labels = keras.utils.to_categorical(train_labels, 5)

.

.

四、fine-tuning方式二:要調整權重

Keras中文文件+原作者文件這個部分都沒有寫對!

先來看看整個結構。
![][)

fine-tune分三個步驟:

  • 搭建vgg-16並載入權重,將之前定義的全連線網路加在模型的頂部,並載入權重
  • 凍結vgg16網路的一部分引數
  • 模型訓練

注意:

  • 1、fine-tune,所有的層都應該以訓練好的權重為初始值,例如,你不能將隨機初始的全連線放在預訓練的卷積層之上,這是因為由隨機權重產生的大梯度將會破壞卷積層預訓練的權重。
  • 2、選擇只fine-tune最後的卷積塊,而不是整個網路,這是為了防止過擬合。整個網路具有巨大的熵容量,因此具有很高的過擬合傾向。由底層卷積模組學習到的特徵更加一般,更加不具有抽象性,因此我們要保持前兩個卷積塊(學習一般特徵)不動,只fine-tune後面的卷積塊(學習特別的特徵)
  • 3、fine-tune應該在很低的學習率下進行,通常使用SGD優化而不是其他自適應學習率的優化演算法,如RMSProp。這是為了保證更新的幅度保持在較低的程度,以免毀壞預訓練的特徵。
    .

1、步驟一:搭建vgg-16並載入權重

1.1 Keras文件結果

先看看Keras中文文件是這樣的:

from keras import applications
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras.models import Sequential
from keras.layers import Dropout, Flatten, Dense

# 網路結構
top_model = Sequential()
#top_model.add(Flatten(input_shape=model.output_shape[1:]))
top_model.add(Flatten(input_shape=(4,4,512)))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
#top_model.add(Dense(1, activation='sigmoid'))
top_model.add(Dense(5, activation='softmax')) 
# 載入權重
top_model.load_weights(top_model_weights_path)

model.add(top_model)

中文文件是用Sequential式寫的,但是沒有找到對的權重:top_model_weights_path,如果不正確的權重檔案會報錯:

ValueError: You are trying to load a weight file containing 16 layers into a model with 2 layers.

同時也沒有交代model是什麼。

1.2 原作者新改

當然看原作者程式碼知道了這裡的model就是VGG16的。所以原作者改成:

# 載入Model權重 + 網路
from keras.applications.vgg16_matt import VGG16
model = VGG16(weights='imagenet', include_top=False)

# “小網路”結構
top_model = Sequential()
top_model.add(Flatten(input_shape=model.output_shape[1:]))
# top_model.add(Flatten(input_shape=(4,4,512)))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(5, activation='softmax')) 

# 加權重
top_model.load_weights(top_model_weights_path)

# 兩個網路整合
model.add(top_model)

這裡又出現一個問題就是,原作者是用application中的VGG16來做的,那麼VGG16原來的是Model式的,現在model.add的是Sequential,相容不起來,報錯:

# AttributeError: 'Model' object has no attribute 'add'

於是參考了VGG16原來網路中的結構自己寫了:

from keras import applications
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras.models import Sequential
from keras.layers import Dropout, Flatten, Dense

# 載入Model權重 + 網路
from keras.applications.vgg16_matt import VGG16
model = VGG16(weights='imagenet', include_top=False)

# 新加層
x = model.output
# 最有問題的層:flatten層
x = Flatten(name='flatten')(x)
# 嘗試一:x = Flatten()(x)
# 嘗試二:x = GlobalAveragePooling2D()(x)
# 嘗試三:from keras.layers import Reshape
#x = Reshape((4,4, 512))(x) # TypeError: long() argument must be a string or a number, not 'NoneType'

x = Dense(256, activation='relu', name='fc1')(x)
x = Dropout(0.5)(x)
predictions = Dense(5, activation='softmax')(x)

from keras.models import Model

vgg_model = Model(input=model.input, output=predictions)

其中又是遇到了Flatten()層的問題,而且做了很多嘗試,這一個層的意思是把VGG16網路結構+權重的model資料輸出格式輸入給Flatten()進行降維,但是!
model.output輸出的格式是:(?,?,?,512)
那麼肯定會報錯:

ValueError: The shape of the input to "Flatten" is not fully defined (got (None, None, 512). Make sure to pass a complete "input_shape" or "batch_input_shape" argument to the first layer in your model.

(1)其中原作者VGG16程式碼中是這麼處理Flatten層的:

x = Flatten(name='flatten')(x)
x = Reshape((4,4, 512))(x)

也沒成功,應該是自己不太會如果寫這個層。

(3)嘗試直接加了個GlobalAveragePooling2D層之後:

x = GlobalAveragePooling2D()(x)
x = Dense(256, activation='relu', name='fc1')(x)
x = Dropout(0.5)(x)
predictions = Dense(5, activation='softmax')(x)

可以執行,但是,fit的結果是:

Epoch 1/50
31/31 [==============================] - 10s - loss: 0.5575 - acc: 0.7730 - val_loss: 0.5191 - val_acc: 0.8000
Epoch 2/50
31/31 [==============================] - 9s - loss: 0.5548 - acc: 0.7760 - val_loss: 0.5256 - val_acc: 0.8000
...
Epoch 49/50
31/31 [==============================] - 9s - loss: 0.5602 - acc: 0.7730 - val_loss: 0.5285 - val_acc: 0.8000
Epoch 50/50
31/31 [==============================] - 9s - loss: 0.5583 - acc: 0.7780 - val_loss: 0.5220 - val_acc: 0.8000
<keras.callbacks.History object at 0x7fb90410fb10>

內容結果總是一樣的,所以還是不對,這塊還沒有解決。。

延伸:評論區網友柚9527給出自己的解決:

top_model = Sequential()  
top_model.add(Flatten(input_shape=base_model.output_shape[1:]))  # model.output_shape[1:])  
top_model.add(Dense(256, activation='relu',kernel_regularizer=l2(0.001),))  
top_model.add(Dropout(0.8))  
top_model.add(Dense(1, activation='sigmoid'))  
# 載入上一模型的權重  
top_model.load_weights(top_model_weights_path)  
  
model = Model(inputs=base_model.input, outputs=top_model(base_model.output))  

.

2、凍結vgg16網路的一部分引數

然後將最後一個卷積塊前的卷積層引數凍結:

for layer in vgg_model.layers[:25]:
    layer.trainable = False

# compile the model with a SGD/momentum optimizer


vgg_model.compile(loss='binary_crossentropy',
              optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
              metrics=['accuracy'])

.

3、模型訓練

然後以很低的學習率進行訓練:

# 準備資料
train_data_dir = '/.../train'
validation_data_dir = '/.../validation'
img_width, img_height = 150, 150
nb_train_samples = 500
nb_validation_samples = 100
epochs = 50
batch_size = 16

# 圖片預處理生成器
train_datagen = ImageDataGenerator(
        rescale=1./255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

test_datagen = ImageDataGenerator(rescale=1./255)

# 圖片generator
train_generator = train_datagen.flow_from_directory(
        train_data_dir,
        target_size=(img_height, img_width),
        batch_size=32,
        class_mode='categorical')

validation_generator = test_datagen.flow_from_directory(
        validation_data_dir,
        target_size=(img_height, img_width),
        batch_size=32,
        class_mode='categorical')

# 訓練
vgg_model.fit_generator(
        train_generator,
        steps_per_epoch=nb_train_samples // batch_size,
        epochs=epochs,
        validation_data=validation_generator,
        validation_steps=nb_validation_samples // batch_size)

如果在之前的網路結構可以正常載入的話,後面都是沒有問題的,可以直接執行。