VGG16內置於Keras,可以通過keras.applications模塊中導入。
--------------------------------------------------------將VGG16 卷積實例化:-------------------------------------------------------------------------------------------------------------------------------------
1 from keras.applications import VGG16 2 3 conv_base = VGG16(weights = 'imagenet',#指定模型初始化的權重檢查點 4 include_top = False, 5 input_shape = (150,150,30))
weights:指定模型初始化的權重檢查點、
include_top: 指定模型最后是否包含密集連接分類器。默認情況下,這個密集連接分類器對應於ImageNet的100個類別。如果打算使用自己的密集連接分類器,可以不適用它,置為False。
input_shape: 是輸入到網絡中的圖像張量的形狀。這個參數完全是可選的,如果不傳入這個參數,那么網絡能夠處理任意形狀的輸入。
--------------------------------------------------------查看VGG詳細架構:conv_base.summary()----------------------------------------------------------------------------------------------------------

最后一特征圖形狀為(4,4,512),我們將在這個特征上添加一個密集連接分類器,有兩種方式:
| 方法一:在你的數據集上運行卷積基,將輸出保存為numpy數組,然后用這個數據做輸入,輸入到獨立的密集連接分類器中。這種方法速度快,計算代價低,因為對於每個輸入圖像只需運行一次卷積基,而卷積基是日前流程中計算代價最高的。但這種方法不允許使用數據增強。 |
方法二:在頂部添加Dense層來擴展已有模型,並在輸入數據上端到端地運行整個模型。這樣你可以使用數據增強,因為每個輸入圖像進入模型時都會經過卷積基。但這種方法的計算代價比第一種要高很多。 |
|
#方法一:不使用數據增強的快速特征提取 import os import numpy as np from keras.preprocessing.image import ImageDataGenerator base_dir = 'cats_dogs_images' 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)#將所有圖像乘以1/255縮放 batch_size = 20 def extract_features(directory,sample_count): features = np.zeros(shape=(sample_count,4,4,512)) labels = np.zeros(shape=(sample_count)) # 通過.flow或.flow_from_directory(directory)方法實例化一個針對圖像batch的生成器,這些生成器 # 可以被用作keras模型相關方法的輸入,如fit_generator,evaluate_generator和predict_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) features[i * batch_size:(i+1) * batch_size] = features_batch 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(validataion_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,(2000,4*4*512)) test_features = np.reshape(test_features,(2000,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) )
|
#在卷積基上添加一個密集連接分類器 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()
在編譯和訓練模型之前,一定要“凍結”卷積基。 凍結☞ 指一個或多個層在訓練過程中保持其權重不變 如果不這么做,那么卷積基之前學到的表示將會在訓練過程中被修改。因為其上添加的Dense層是隨機初始化的,所以非常大的權重更新將會在網絡中傳播,對之前學到的表示造成很大破壞。 在keras中,凍結網絡的方法是將其trainable屬性設置為False eg. conv_base.trainable = False
#使用凍結的卷積基端到端地訓練模型
|
#繪制損失曲線和精度曲線 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('Traing 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('Traing and validation loss') plt.legend() plt.show()
|
|
|
|
電腦帶不起來,慢死,這邊就不截圖了。 驗證精度約為96% |
--------------------------------------------------------微調模型--------------------------------------------------------------------------------------------------------------------------------------------------------------
另外一種廣泛使用的模型復用方法是模型微調,與特征提取互為補充。
對於用於特征提取的凍結的模型基,微調是指將其頂部的幾層“解凍”,並將這解凍的幾層和新增加的部分聯合訓練。之所以叫作微調,是因為他只是略微調整了所復用模型中更加抽象的表示,
以便讓這些表示與手頭的問題更加相關。
凍結VGG16的卷積基是為了能夠在上面訓練一個隨機初始化的分類器。同理,只有上面的分類器訓練好了,才能微調卷積基的頂部幾層。如果分類器沒有訓練好,那么訓練期間通過網絡傳播的
誤差信號會特別大,微調的幾層之前學到的表示都會被破壞。因此,微調網絡的步驟如下:
(1)在已經訓練好的基網絡上添加自定義網絡
(2)凍結基網絡
(3)訓練所添加的部分
(4)解凍基網絡的一些層
(5)聯合訓練解凍的這些層和添加的部分
# 凍結直到某一層的所有層 #僅微調卷積基的最后的兩三層 conv_base.trainable = True set_trainable = False for layer in conv_base.layers[:-1]: if layer.name == 'block5_conv1': set_trainable = True if set_trainable: print(layer) set_trainable = True else: set_trainable = False |
|
#微調模型 model.compile(loss='binary_crossentropy', optimizer = optimizers.RMSprop(lr=1e-5), metrics = ['acc']) history = model.fit_generator( train_generator, steps_per_epoch=10, epochs = 5, validation_data = validation_generator, validation_steps = 50 ) |
|
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) |
得到了97%的測試精度
|
#使曲線變得平滑 def smooth_curve(points,factor = 0.2): 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
|
為什么不微調更多層?為什么不微調整個卷積基?當然可以這么做,但需要先考慮以下幾點:
(1)卷積基中更靠底部的層編碼的是更加通用的可復用特征,而更靠頂部的層編碼的是更專業化的特征。微調這些更專業化的特征更加有用,因為它們需要在你的新問題上改變用途。
微調更靠底部的層,得到的匯報會更少
(2)訓練的參數越多,過擬合的風險越大。卷積基有1500萬個參數,所以在你的小型數據集上訓練這么多參數是有風險的。
--------------------------------------------------------卷積神經網絡的可視化------------------------------------------------------------------------------------------------------------------------------------------------------------------------
三種最容易理解也最有用的方法:
(1)可視化卷積神經網絡的中間輸出(中間激活):有助於理解卷積神經網絡連續的層如何對輸入進行變換,也有助於初步了解神經網絡每個過濾器的含義
(2)可視化卷積神經網絡的過濾器:有助於精確理解卷積神經網絡中每個過濾器容易接受的視覺模式或視覺概念
(3)可視化圖像中類激活的熱力圖:有助於理解圖像的哪個部分被識別為屬於哪個類別,從而可以定位圖像中的物體
| 可視化卷積神經網絡的中間輸出(中間激活) | 可視化卷積神經網絡的過濾器 |
| from keras.models import load_model model = load_model('cats_and_dogs_small_2.h5') model.summary() |
#為過濾器的可視化定義損失張量 from keras.applications import VGG16 from keras import backend as K model = VGG16(weights = 'imagenet',include_top = False) def generate_pattern(layer_name,filter_index,size=150): layer_output = model.get_layer(layer_name).output loss = K.mean(layer_output[:,:,:,filter_index]) #獲取損失相對於輸入的梯度 grads = K.gradients(loss,model.input)[0] #梯度標准化 grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)#1e-5防止不小心除以0 #給定numpy輸入值,得到numpy輸出值 iterate = K.function([model.input],[loss,grads]) #通過隨機梯度下降讓損失最大化 input_img_data = np.random.random((1,size,size,3))*20+128. step = 1. for i in range(40): loss_value,grads_value = iterate([input_img_data]) input_img_data += grads_value * step img = input_img_data[0] return deprocess_image(img) |
#預處理單張圖像 img_path = 'cats_and_dogs_small/test/cats/cat.1501.jpg' from keras.preprocessing import image import numpy as np img = image.load_img(img_path,target_size=(150,150)) img_tensor = image.img_to_array(img) # img_tensor = np.expand_dims(img_tensor,axis=0) img_tensor = img_tensor.reshape((1,)+img_tensor.shape) img_tensor /= 255. print(img_tensor.shape)
|
#將張量轉化為有效圖像的實用函數 def deprocess_image(x): x -= x.mean() x /= (x.std() + 1e-5) x *= 0.1 x += 0.5 x = np.clip(x,0,1) x *= 255 x = np.clip(x,0,255).astype('uint8') return x |
|
#顯示測試圖像 |
plt.imshow(generate_pattern('block3_conv1',0)) |
|
|
#用一個輸入張量和一個輸出張量列表將模型實例化 from keras import models layer_outputs = [layer.output for layer in model.layers[:8]] activation_model = models.Model(inputs=model.input,outputs=layer_outputs)#創建一個模型,給定模型輸入,可以返回這些輸出 #這個模型有一個輸入和8個輸出,即每層激活對於一個輸出
|
#生成某一層中所有過濾器響應模式組成的網絡 def create_vision(layer_name): size = 64 margin = 5 results = np.zeros((8 * size + 7 * margin , 8 * size + 7*margin ,3)) for i in range(8): for j in range(8): filter_img = generate_pattern(layer_name, i + (j * 8), size = size) horizontal_start = i * size + i * margin horizontal_end = horizontal_start + size vertical_start = j*size + j * margin vertical_end = vertical_start + size results[horizontal_start:horizontal_end, vertical_start:vertical_end, :] = filter_img plt.figure(figsize=(20, 20)) plt.imshow(results.astype('uint8')) #不知為何deprocess_image無效,使得results矩陣並不是uint8格式,故需要轉換否則不顯示
|
#以預測模式運行模型 activations = activation_model.predict(img_tensor) #返回8個numpy數組組成的列表,每個層激活對應一個numpy數組 first_layer_activation = activations[3] #first_layer_activation.shape--> 32個通道 (1, 148, 148, 32) import matplotlib.pyplot as plt plt.matshow(first_layer_activation[0,:,:,15],cmap='viridis') |
layer_name = 'block1_conv1'
|
|
layer_name = 'block4_conv1'
|
#將每個中間激活的所有通道可視化 layer_names = [] for layer in model.layers[:8]: layer_names.append(layer.name)#層的名字 images_per_row = 16 for layer_name,layer_activation in zip(layer_names,activations): n_features = layer_activation.shape[-1] #[32, 32, 64, 64, 128, 128, 128, 128] 特征個數 size = layer_activation.shape[1] #特征圖的形狀為(1,size,size,n_features) n_cols = n_features // images_per_row display_grid = np.zeros((size*n_cols,images_per_row*size)) for col in range(n_cols): for row in range(images_per_row): channel_image = layer_activation[0,:,:,col*images_per_row + row] channel_image -= channel_image.mean() channel_image /= channel_image.std() channel_image *= 64 channel_image += 128 channel_image = np.clip(channel_image,0,255).astype('uint8') display_grid[col*size:(col+1)*size,row*size:(row+1)*size]=channel_image scale = 1./size plt.figure(figsize=(scale*display_grid.shape[1], scale*display_grid.shape[0])) plt.title(layer_name) plt.grid(False) plt.imshow(display_grid,aspect='auto',cmap='viridis') |
|
|
可視化圖像中類激活的熱力圖
|
|
| from keras.applications import VGG16 model = VGG16(weights = 'imagenet',include_top = True) |
|
#為VGG16模型預處理一張輸入圖像 from keras.preprocessing import image from keras.applications.vgg16 import preprocess_input,decode_predictions import numpy as np img_path = 'E:/軟件/nxf_software/pythonht/nxf_practice/keras/大象.jpg' img = image.load_img(img_path,target_size=(224,224))#讀取圖像並調整大小 x = image.img_to_array(img) # ==> array(150,150,3) plt.figure() implot = plt.imshow(image.array_to_img(x)) plt.show() |
|
x = x.reshape((1,)+x.shape) # ==> array(1,150,150,3) x = preprocess_input(x)#對批量進行預處理(按通道進行顏色標准化) preds = model.predict(x) decode_predictions(preds,top=3)[0]
|
|
1 #為了展示圖像中哪些部分最像非洲象,我們使用Grad-CAM算法 2 3 african_elephant_output = model.output[:386] #預測向量中的'非洲象'元素 4 5 last_conv_layer = model.get_layer('block5_conv3')#VGG16的最后一個卷積層 6 7 grads = K.gradients(african_elephant_output,last_conv_layer)[0] 8 9 iterate = K.function([model.input],[pooled_grads,last_conv_layer.output[0]]) 10 11 pooled_grads_value,conv_layer_output_value = iterate([x]) 12 13 for i in range(512): 14 conv_layer_output_value[:,:,i] *= pooled_grads_value[i] 15 16 headmap = np.mean(conv_layer_output_value,axis = -1)
|
|
| heatmap = np.maximum(heatmap,0) headmap /= np.max(heatmap) plt.matshow(heatmap) |
|
1 #將熱力圖與原始圖像疊加 2 import cv2 3 4 img = cv2.imread(img_path) #用cv2加載原始圖像 5 6 heatmap = cv2.resize(heatmap,(img.shape[1],img.shape[0]))#將熱力圖的大小調整為與原始圖像相同 7 8 heatmap = np.uint8(255 * heatmap)#將熱力圖轉換為RGB格式 9 10 heatmap = cv2.applyColorMap(heatmap,cv2.COLORMAP_JET)#將熱力圖應用於原始圖像 11 12 superimposed_img = heatmap * 0.4 + img #0.4是熱力圖強度因子 13 14 cv2.imwrite('E:/軟件/nxf_software/pythonht/nxf_practice/keras/大象1.jpg',superimposed_img) #將圖像保存到硬盤
|
|
這種可視化方法回答了兩個重要問題
|
|









