卷積神經(jīng)網(wǎng)絡(Convolutional Neural Network, CNN)是一種前饋神經(jīng)網(wǎng)絡岖赋,它的人工神經(jīng)元可以響應一部分覆蓋范圍內(nèi)的周圍單元,對于大型圖像處理有出色表現(xiàn)赘来。通過卷積旭贬、池化瘩将、激活等操作的配合吟税,卷積神經(jīng)網(wǎng)絡能夠較好的學習到空間上關(guān)聯(lián)的特征凹耙。以VGG16為例,我們將利用Keras觀察CNN到底在學些什么肠仪,它是如何理解我們送入的訓練圖片的肖抱。
github:https://github.com/xiaochus/VisualizationCNN
參考:Keras,deep-learning-with-python-notebooks
主要的四種可視化模式:
1. 卷積核輸出的可視化异旧,即可視化卷積操作后的結(jié)果意述,幫助理解卷積核的作用。
2. 卷積核的可視化吮蛹,對卷積核本身進行可視化荤崇,對卷積核學習到的行為進行解釋。
3. 類激活圖可視化潮针,通過熱度圖术荤,了解圖像分類問題中圖像哪些部分起到了關(guān)鍵作用,同時可以定位圖像中物體的位置然低。
4. 特征可視化喜每,與第一種方法類似,但是輸出的不再是卷積層的激活值雳攘,而是使用反卷積與反池化來可視化輸入圖像的激活特征。
環(huán)境
- Python 3.6
- Keras 2.2.2
- Tensorflow-gpu 1.8.0
- OpenCV 3.4
卷積輸出可視化
卷積輸出可視化可以通過將不同的卷積層設置為輸出層來實現(xiàn)枫笛,如下所示:
def conv_output(model, layer_name, img):
"""Get the output of conv layer.
Args:
model: keras model.
layer_name: name of layer in the model.
img: processed input image.
Returns:
intermediate_output: feature map.
"""
# this is the placeholder for the input images
input_img = model.input
try:
# this is the placeholder for the conv output
out_conv = model.get_layer(layer_name).output
except:
raise Exception('Not layer named {}!'.format(layer_name))
# get the intermediate layer model
intermediate_layer_model = Model(inputs=input_img, outputs=out_conv)
# get the output of intermediate layer model
intermediate_output = intermediate_layer_model.predict(img)
return intermediate_output[0]
我們對四個block下不同的卷積層輸出進行了可視化處理吨灭,可視化結(jié)果如下所示⌒糖桑可以看出淺層的卷積層獲得的特征數(shù)量還比較多喧兄,特征數(shù)據(jù)與原始的圖像數(shù)據(jù)很接近。隨著層數(shù)越深啊楚,得到的有效特征越來越少吠冤,特征也變得越來越抽象。
卷積核可視化
解釋CNN模型的另一個簡單方法是顯示每個卷積核響應的視覺模式, 卷積核可視化通過輸入空間中的梯度上升來完成恭理。
卷積核可視化的過程:我們要定義一個損失函數(shù)拯辙,這個損失函數(shù)將用于最大化某個指定濾波器的激活值。以該函數(shù)為優(yōu)化目標優(yōu)化后颜价,后我們將使用隨機梯度下降來調(diào)整輸入圖像的值涯保,以便最大化該激活值。
def conv_filter(model, layer_name, img):
"""Get the filter of conv layer.
Args:
model: keras model.
layer_name: name of layer in the model.
img: processed input image.
Returns:
filters.
"""
# this is the placeholder for the input images
input_img = model.input
# get the symbolic outputs of each "key" layer (we gave them unique names).
layer_dict = dict([(layer.name, layer) for layer in model.layers[1:]])
try:
layer_output = layer_dict[layer_name].output
except:
raise Exception('Not layer named {}!'.format(layer_name))
kept_filters = []
for i in range(layer_output.shape[-1]):
loss = K.mean(layer_output[:, :, :, i])
# compute the gradient of the input picture with this loss
grads = K.gradients(loss, input_img)[0]
# normalization trick: we normalize the gradient
grads = utils.normalize(grads)
# this function returns the loss and grads given the input picture
iterate = K.function([input_img], [loss, grads])
# step size for gradient ascent
step = 1.
# run gradient ascent for 20 steps
fimg = img.copy()
for j in range(40):
loss_value, grads_value = iterate([fimg])
fimg += grads_value * step
# decode the resulting input image
fimg = utils.deprocess_image(fimg[0])
kept_filters.append((fimg, loss_value))
# sort filter result
kept_filters.sort(key=lambda x: x[1], reverse=True)
return np.array([f[0] for f in kept_filters])
可視化結(jié)果如下所示周伦∠Υ海可以看出最淺層的卷積核傾向于學習點、顏色等基礎(chǔ)特征专挪;接下來開始學習到線段及志、邊緣等特征片排。層數(shù)越深,學習到的特征就越具體越抽象速侈。
類激活圖可視化
類激活圖(CAM)可視化率寡,包括在輸入圖像上產(chǎn)生類激活的熱圖。 類激活熱圖是與特定輸出類相關(guān)聯(lián)的分數(shù)的2D網(wǎng)格锌畸,針對任何輸入圖像中的每個位置計算勇劣,指示每個位置相對于所考慮的類的重要程度。例如潭枣,給定一個圖像輸入我們的“貓與狗”之一比默,類激活圖可視化允許我們?yōu)轭悺柏垺鄙蔁釄D,指示圖像的貓狀不同部分是如何盆犁,同樣對于“狗”類命咐,表示圖像的狗狀不同部分。換句話時候谐岁,假設最后一層卷積層有512個卷積核醋奠,我想了解這512個卷積核對該圖片是”貓”分別投了幾票。投票越多的卷積核伊佃,就越確信圖片是“貓”窜司,因為它們提取到的特征趨向貓的特征。
CAM的原理:它包括在給定輸入圖像的情況下獲取卷積層的輸出特征圖航揉,并通過目標類相對于通道的梯度對該特征圖中的每個通道進行加權(quán)塞祈。簡單的講,我們獲取到最后一個卷積層的卷積輸出以及目標類別神經(jīng)元相對于每一個通道的梯度帅涂,使用這個梯度對卷積核的每一個通道進行加權(quán)處理议薪,最后對通道求均值并且對重要度進行歸一化處理。
def output_heatmap(model, last_conv_layer, img):
"""Get the heatmap for image.
Args:
model: keras model.
last_conv_layer: name of last conv layer in the model.
img: processed input image.
Returns:
heatmap: heatmap.
"""
# predict the image class
preds = model.predict(img)
# find the class index
index = np.argmax(preds[0])
# This is the entry in the prediction vector
target_output = model.output[:, index]
# get the last conv layer
last_conv_layer = model.get_layer(last_conv_layer)
# compute the gradient of the output feature map with this target class
grads = K.gradients(target_output, last_conv_layer.output)[0]
# mean the gradient over a specific feature map channel
pooled_grads = K.mean(grads, axis=(0, 1, 2))
# this function returns the output of last_conv_layer and grads
# given the input picture
iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])
pooled_grads_value, conv_layer_output_value = iterate([img])
# We multiply each channel in the feature map array
# by "how important this channel is" with regard to the target class
for i in range(conv_layer_output_value.shape[-1]):
conv_layer_output_value[:, :, i] *= pooled_grads_value[i]
# The channel-wise mean of the resulting feature map
# is our heatmap of class activation
heatmap = np.mean(conv_layer_output_value, axis=-1)
heatmap = np.maximum(heatmap, 0)
heatmap /= np.max(heatmap)
return heatmap
可視化結(jié)果如下所示媳友∷挂椋可以看出對于這張貓的圖片,頭部耳朵貢獻了最重要的特征醇锚,其次就是爪子哼御,最后是身體部分。
特征可視化
特征可視化主要使用目標卷積層的輸出作為輸入搂抒,利用反卷積艇搀、反池化、反激活等方法得到反向操作的結(jié)果求晶,用以驗證顯示各層提取到的特征圖派撕。由于Keras還沒有提供反池化的方法誓军,這里提供一些相關(guān)的論文與工具作為參考逸月。
paper:
Visualizing and Understanding Convolutional Networks
Understanding Neural Networks Through Deep Visualization
tools:
DeepVis Toolbox
tf_cnnvis
網(wǎng)絡的整個過程,從右邊開始:輸入圖片→卷積→Relu→最大池化→得到結(jié)果特征圖→反池化→Relu→反卷積辟宗。
對于反卷積過程,這里不用進行訓練而是采用卷積過程轉(zhuǎn)置后的濾波器(參數(shù)一樣吝秕,把參數(shù)矩陣水平和垂直方向翻轉(zhuǎn)了一下)泊脐。