十四欺劳、計(jì)算機(jī)視覺中的神經(jīng)網(wǎng)絡(luò):可視化卷積網(wǎng)絡(luò)所學(xué)到的東西


文章代碼來源:《deep learning on keras》唧取,非常好的一本書,大家如果英語好划提,推薦直接閱讀該書枫弟,如果時間不夠,可以看看此系列文章鹏往,文章為我自己翻譯的內(nèi)容加上自己的一些思考淡诗,水平有限,多有不足伊履,請多指正韩容,翻譯版權(quán)所有,若有轉(zhuǎn)載唐瀑,請先聯(lián)系本人群凶。
個人方向?yàn)閿?shù)值計(jì)算,日后會向深度學(xué)習(xí)和計(jì)算問題的融合方面靠近哄辣,若有相近專業(yè)人士请梢,歡迎聯(lián)系。


系列文章:
一力穗、搭建屬于你的第一個神經(jīng)網(wǎng)絡(luò)
二毅弧、訓(xùn)練完的網(wǎng)絡(luò)去哪里找
三、【keras實(shí)戰(zhàn)】波士頓房價預(yù)測
四当窗、keras的function API
五够坐、keras callbacks使用
六、機(jī)器學(xué)習(xí)基礎(chǔ)Ⅰ:機(jī)器學(xué)習(xí)的四個標(biāo)簽
七、機(jī)器學(xué)習(xí)基礎(chǔ)Ⅱ:評估機(jī)器學(xué)習(xí)模型
八咆霜、機(jī)器學(xué)習(xí)基礎(chǔ)Ⅲ:數(shù)據(jù)預(yù)處理邓馒、特征工程和特征學(xué)習(xí)
九、機(jī)器學(xué)習(xí)基礎(chǔ)Ⅳ:過擬合和欠擬合
十蛾坯、機(jī)器學(xué)習(xí)基礎(chǔ)Ⅴ:機(jī)器學(xué)習(xí)的一般流程十一光酣、計(jì)算機(jī)視覺中的深度學(xué)習(xí):卷積神經(jīng)網(wǎng)絡(luò)介紹
十二、計(jì)算機(jī)視覺中的深度學(xué)習(xí):從零開始訓(xùn)練卷積網(wǎng)絡(luò)
十三脉课、計(jì)算機(jī)視覺中的深度學(xué)習(xí):使用預(yù)訓(xùn)練網(wǎng)絡(luò)
十四救军、計(jì)算機(jī)視覺中的神經(jīng)網(wǎng)絡(luò):可視化卷積網(wǎng)絡(luò)所學(xué)到的東西


常常說深度學(xué)習(xí)模型是“黑盒”,學(xué)習(xí)表示很難提取表示成人類可以讀懂的形式倘零。盡管這對于特定類型的深度學(xué)習(xí)模型是對的唱遭,但那時對于卷積網(wǎng)絡(luò)一定是錯的。我們從卷積網(wǎng)絡(luò)學(xué)到的表示都是高度可視化的呈驶,很大一部分是因?yàn)樗鼈儽硎境闪丝梢暬母拍羁皆蟆W詮?013以來,各種各樣的技術(shù)都被提出用來可視化和揭示這些表示袖瞻。我們不會調(diào)查所有的司致,但我們會涵蓋三種最容易接觸最有用的:

  • 可視化卷積網(wǎng)絡(luò)中間輸出(中間激活)這對于理解卷積網(wǎng)絡(luò)層如何變換它們的輸入,以及了解每一個卷積網(wǎng)絡(luò)濾波器的意義聋迎。
  • 可視化卷積網(wǎng)絡(luò)濾波器脂矫。這對于正確理解每個濾波器的視覺圖案和在卷積網(wǎng)絡(luò)中濾波器接收到的內(nèi)容。
  • 可視化每一幅圖像的分類激活值的熱力圖霉晕。這對于理解圖像的哪一個部分對于分類起的作用最大庭再,這也允許局部化圖像中的物體。

在第一種模式——激活值可視化——我們將會使用我們從零訓(xùn)練的小的卷積網(wǎng)絡(luò)(cat vs. dog)分類問題牺堰。在接下來的兩種方法拄轻,我們將會使用VGG16模型。

可視化中間的激活值

可視化中間的激活值包含展示通過不同卷積和池化層以后的輸出萌焰,給一個特定的輸入(這層輸出叫做“激活值”哺眯,激活函數(shù)的輸出值)這給了一個視角來看一個輸入是如何分解到不同的網(wǎng)絡(luò)學(xué)到的濾波器的。這些我們想要可視化的特征有三個維度:長扒俯、高、深度一疯。每一個通道編碼了相互獨(dú)立的特征撼玄,所以合適的可視化這些特征的方法是通過單獨(dú)畫出每一個通道里面的內(nèi)容,作為二維圖像墩邀,讓我們開始加載之前訓(xùn)練過的模型吧:

>>> from keras.models import load_model
>>> model = load_model('cats_and_dogs_small_2.h5')
>>> model.summary() # As a reminder.
________________________________________________________________
Layer (type) Output Shape Param #
================================================================
conv2d_5 (Conv2D) (None, 148, 148, 32) 896
________________________________________________________________
maxpooling2d_5 (MaxPooling2D) (None, 74, 74, 32) 0
________________________________________________________________
conv2d_6 (Conv2D) (None, 72, 72, 64) 18496
________________________________________________________________
maxpooling2d_6 (MaxPooling2D) (None, 36, 36, 64) 0
________________________________________________________________
conv2d_7 (Conv2D) (None, 34, 34, 128) 73856
________________________________________________________________
maxpooling2d_7 (MaxPooling2D) (None, 17, 17, 128) 0
________________________________________________________________
conv2d_8 (Conv2D) (None, 15, 15, 128) 147584
________________________________________________________________
maxpooling2d_8 (MaxPooling2D) (None, 7, 7, 128) 0
________________________________________________________________
flatten_2 (Flatten) (None, 6272) 0
________________________________________________________________
dropout_1 (Dropout) (None, 6272) 0
________________________________________________________________
dense_3 (Dense) (None, 512) 3211776
________________________________________________________________
dense_4 (Dense) (None, 1) 513
================================================================
Total params: 3,453,121
Trainable params: 3,453,121
Non-trainable params: 0

接下來我們要在網(wǎng)絡(luò)上用的貓的圖像是在網(wǎng)絡(luò)上沒有訓(xùn)練過的:
先預(yù)處理圖像:

img_path = '/Users/fchollet/Downloads/cats_and_dogs_small/test/cats/cat.1700.jpg'
# We preprocess the image into a 4D tensor
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)
# Remember that the model was trained on inputs
# that were preprocessed in the following way:
img_tensor /= 255.
# Its shape is (1, 150, 150, 3)
print(img_tensor.shape)

展示我們的圖像:

import matplotlib.pyplot as plt
plt.imshow(img_tensor[0])
Our test cat picture

為了提取我們想要看到的特征掌猛,我們將會建立一個keras模型以圖像批作為輸入,輸出所有卷積和池化層的激活值。我們將會使用 Keras class模型來做到這一點(diǎn)荔茬。一個模型的實(shí)例用了兩個參數(shù):一個輸入張量废膘,一個輸出張量。結(jié)果的類別是一個keras模型慕蔚,就和你熟知的sequential模型類似的丐黄,將特定輸入映射到特定輸出。讓這二者有區(qū)別的是我們現(xiàn)在要用的模型可以有多個輸出孔飒,不像sequential灌闺。想要了解更多有關(guān)Model class的信息,可以看書的第七章第一部分坏瞄。

from keras import models
# Extracts the outputs of the top 8 layers:
layer_outputs = [layer.output for layer in model.layers[:8]]
# Creates a model that will return these outputs, given the model input:
activation_model = models.Model(inputs=model.input, outputs=layer_outputs)

當(dāng)喂進(jìn)去輸入圖像時桂对,模型返回原始模型的層的激活值。這是你第一次在本書遇到多輸出的模型:直到現(xiàn)在鸠匀,你所看到的模型都是一個輸入一個輸出蕉斜。一般的情況,一個模型能有任意多輸入和輸出缀棍。這里的有一個輸入宅此,五個輸出,每一層輸出一個層激活值睦柴。

# This will return a list of 5 Numpy arrays:
# one array per layer activation
activations = activation_model.predict(img_tensor)

舉個例子诽凌,這就是我們貓圖像輸入卷積層第一層的激活值:

>>> first_layer_activation = activations[0]
>>> print(first_layer_activation.shape)
(1, 148, 148, 32)

這是一個148\times 148的特征,有著32個通道坦敌。讓我們看一下第四個通道:

import matplotlib.pyplot as plt
plt.matshow(first_layer_activation[0, :, :, 4], cmap='viridis')
4th channel of the activation of the first layer on our test cat picture

這個通道看起來編碼了對角邊緣探測器侣诵。讓我們試一下第七個通道——但注意你自己的通道或許是多樣的,因?yàn)榫矸e層選到的濾波器是不確定的狱窘。

plt.matshow(first_layer_activation[0, :, :, 7], cmap='viridis')
7th of the activation of the first layer on our test cat picture

這看起來就像是“亮綠點(diǎn)”探測器杜顺,對于編碼貓眼很有用。在這一點(diǎn)上蘸炸,讓我們畫出整個網(wǎng)絡(luò)的可視化激活值躬络。我們將會提取和畫出五個激活圖中的每一個通道,然后我們將會把這些結(jié)果堆在一個大的圖像張量中搭儒,通道挨著堆穷当。

# These are the names of the layers, so can have them as part of our plot
layer_names = []
for layer in model.layers[:8]:
 layer_names.append(layer.name)
images_per_row = 16
# Now let's display our feature maps
for layer_name, layer_activation in zip(layer_names, activations):
 # This is the number of features in the feature map
 n_features = layer_activation.shape[-1]
 # The feature map has shape (1, size, size, n_features)
 size = layer_activation.shape[1]
 # We will tile the activation channels in this matrix
 n_cols = n_features // images_per_row
 display_grid = np.zeros((size * n_cols, images_per_row * size))
# We'll tile each filter into this big horizontal grid
 for col in range(n_cols):
 for row in range(images_per_row):
 channel_image = layer_activation[0,
 :, :,
 col * images_per_row + row]
 # Post-process the feature to make it visually palatable
 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
 # Display the grid
 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')
Every channel of every layer activation on our test cat picture

有些需要注意的:

  • 第一層表現(xiàn)得像各種邊緣探測器的集合,在那個階段淹禾,激活值仍然是保留了幾乎原始圖像的所有信息馁菜。
  • 更高一層,激活值就變得進(jìn)一步抽象铃岔,更少的視覺理解汪疮。它們開始編碼更高一層的內(nèi)容,諸如“貓耳”和“貓眼”。更高級的表示得到的圖像的視覺內(nèi)容更少了智嚷,并得到了更多關(guān)于圖像類別的信息卖丸。
  • 隨著層的加深,激活值的稀疏性也在增加:在第一層盏道,所有的濾波器都被輸入圖像激活了稍浆,但在接下來的層里面越來越多的濾波器變空了。這意味著濾波器編碼的圖案不是在輸入圖像中找到的摇天。

我們剛剛已經(jīng)論證了一個非常重要普遍的深度神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)表示的特點(diǎn):層提取到的特征隨著層數(shù)增加而變得更抽象粹湃。層的激活值會攜帶越來越少的有關(guān)特定輸入的信息,隨著層的增加泉坐,會攜帶越來越多有關(guān)目標(biāo)的信息(在我們的例子中为鳄,圖像類別是貓、狗)腕让。一個深度神經(jīng)網(wǎng)絡(luò)有效的工作就像一個信息蒸餾管道孤钦,輸入數(shù)據(jù)向量,在我們的例子中是輸入了RGB圖纯丸,就會反復(fù)轉(zhuǎn)換使得無關(guān)信息被濾出去(例如圖像特定的視覺外觀)有用的信息就會被放大和提煉(例如圖像類別)偏形。
人類和動物感知世界的方式與之類似:在觀察了一個場景幾秒以后,一個人類能夠記住物體的抽象但無法記住物體具體的表現(xiàn)觉鼻。實(shí)際上俊扭,如果讓你現(xiàn)在從記憶中畫一個單車,你很可能都無法畫出細(xì)節(jié)坠陈,盡管大致正確萨惑,雖然你一生中見過上千輛單車。現(xiàn)在馬上試一下:效果屬實(shí)仇矾。你的大腦學(xué)習(xí)了輸入圖像的完全抽象庸蔼,把它轉(zhuǎn)化成更高級的視覺內(nèi)容,完全過濾掉不相關(guān)的視覺細(xì)節(jié)贮匕,讓記住我們周圍的東西的實(shí)際的樣子非常的難姐仅。


Left: attempts to draw a bicycle from memory. Right: what a schematic bicycle should look like.

可視化卷積網(wǎng)絡(luò)濾波器

另一個簡單的事情是監(jiān)視濾波器從卷積網(wǎng)絡(luò)里學(xué)到的東西,并把它用可視化的方式展現(xiàn)出來刻盐。這能通過輸入空間里的gradient ascent做到:使用gradient descent來評估輸入圖像的卷積網(wǎng)絡(luò)以最大化特定濾波器的反饋掏膏,從一個空白輸入圖像開始。最終輸入圖像的結(jié)果是對于選擇的濾波器具有最大響應(yīng)的敦锌。
這個過程很簡單:我們將會建立一個損失函數(shù)來最大化在給定卷積層中濾波器的值憋肖,我們會使用隨機(jī)梯度下降來調(diào)節(jié)輸入圖像的值從而最大化激活值侣颂。舉個例子,這里給出了VGG16網(wǎng)絡(luò)中的"block3_conv1"的濾波器0的激活值損失呵萨。

from keras.applications import VGG16
from keras import backend as K
model = VGG16(weights='imagenet',
 include_top=False)
layer_name = 'block3_conv1'
filter_index = 0
layer_output = model.get_layer(layer_name).output
loss = K.mean(layer_output[:, :, :, filter_index])

為了應(yīng)用梯度下降法,我們將會需要遵循模型輸入的損失下降伶丐。為了做到這一點(diǎn)悼做,我們需要用gradients函數(shù)來打包keras的backend模型:

# The call to `gradients` returns a list of tensors (of size 1 in this case)
# hence we only keep the first element -- which is a tensor.
grads = K.gradients(loss, model.input)[0]

一個不明顯的把戲來使用梯度下降處理光滑是標(biāo)準(zhǔn)化梯度張量,通過除以它的L2模(張量中的平方和均值的平方根)這保證輸入圖像更新總是在一個相同的范圍哗魂。

# We add 1e-5 before dividing so as to avoid accidentally dividing by 0.
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)

現(xiàn)在我們需要一個方法來在給定輸入圖像時肛走,計(jì)算損失張量的值以及梯度張量。我們能夠定義一個keras 的backend函數(shù)來做到:iterate是一個函數(shù)录别,拿進(jìn)去一個數(shù)組張量返回兩個數(shù)組張量:損失值和梯度值朽色。

iterate = K.function([model.input], [loss, grads])
# Let's test it:
import numpy as np
loss_value, grads_value = iterate([np.zeros((1, 150, 150, 3))])

此時我們就可以使用Python里面的loop來做隨機(jī)梯度下降了。

# We start from a gray image with some noise
input_img_data = np.random.random((1, 150, 150, 3)) * 20 + 128.
# Run gradient ascent for 40 steps
step = 1. # this is the magnitude of each gradient update
for i in range(40):
 # Compute the loss value and gradient value
 loss_value, grads_value = iterate([input_img_data])
 # Here we adjust the input image in the direction that maximizes the loss
 input_img_data += grads_value * step

圖像張量的結(jié)果將會是浮點(diǎn)張量的形狀(1,150,150,3)值在[0.255]之間组题。因此我們需要發(fā)布張量的流程來將其轉(zhuǎn)化為可視化圖像葫男。我們通過以下實(shí)用函數(shù)來做到:

def deprocess_image(x):
 # normalize tensor: center on 0., ensure std is 0.1
 x -= x.mean()
 x /= (x.std() + 1e-5)
 x *= 0.1
 # clip to [0, 1]
 x += 0.5
 x = np.clip(x, 0, 1)
 # convert to RGB array
 x *= 255
 x = np.clip(x, 0, 255).astype('uint8')
 return x

現(xiàn)在我們有了所有的不見,讓我們把它們放在一起崔列,放進(jìn)Python函數(shù)就像放進(jìn)層的名字和濾波器指標(biāo)梢褐,會返回一個有效圖像張量,其代表著最大化特定濾波器的激活值赵讯。

def generate_pattern(layer_name, filter_index, size=150):
 # Build a loss function that maximizes the activation
 # of the nth filter of the layer considered.
 layer_output = model.get_layer(layer_name).output
 loss = K.mean(layer_output[:, :, :, filter_index])
 # Compute the gradient of the input picture wrt this loss
 grads = K.gradients(loss, model.input)[0]
 # Normalization trick: we normalize the gradient
 grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
 # This function returns the loss and grads given the input picture
 iterate = K.function([model.input], [loss, grads])
 # We start from a gray image with some noise
 input_img_data = np.random.random((1, size, size, 3)) * 20 + 128.
 # Run gradient ascent for 40 steps
 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)

可視化block3_conv1的濾波器0:

>>> plt.imshow(generate_pattern('block3_conv1', 0))

Pattern that the 0th channel in layer block3_conv1 maximally responds to

看起來就像是波爾卡圓點(diǎn)圖盈咳。
現(xiàn)在是比較有趣的部分:我們能從每一層的單獨(dú)濾波器開始可視化。簡單來說边翼,我們將只看到每一層的前64個濾波器鱼响,將會只看到第一層的每個卷積塊(block1_conv1,block2_conv1,block3_conv1,block4_conv1,block5_conv1)我們將輸出排列在
8\times 8
64\times 64
大小的濾波器圖案,通過一些在每一個濾波器圖案周圍加黑邊组底。

layer_name = 'block1_conv1'
size = 64
margin = 5
# This a empty (black) image where we will store our results.
results = np.zeros((8 * size + 7 * margin, 8 * size + 7 * margin, 3))
for i in range(8): # iterate over the rows of our results grid
 for j in range(8): # iterate over the columns of our results grid
 # Generate the pattern for filter `i + (j * 8)` in `layer_name`
 filter_img = generate_pattern(layer_name, i + (j * 8), size=size)
 # Put the result in the square `(i, j)` of the results grid
 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
# Display the results grid
plt.figure(figsize=(20, 20))
plt.imshow(results)
Filter patterns for layer block1_conv1

Filter patterns for layer block2_conv1

Filter patterns for layer block3_conv1

Filter patterns for layer block4_conv1

這些濾波器可視化告訴我們卷積網(wǎng)絡(luò)層是如何看這個世界的:每一層都簡單的學(xué)習(xí)一類濾波器丈积,使得他們的輸入能夠被表示成濾波器的組合。這個和傅里葉分解信號成一個余弦函數(shù)庫很類似斤寇。這些卷積網(wǎng)絡(luò)濾波器庫中的濾波器當(dāng)我們的層升高時變得更加復(fù)雜和精致:

  • 第一層濾波器block1_conv1編碼一些簡單的邊緣和顏色桶癣,或者某些情況下編碼彩色邊緣。
  • block2_conv1的濾波器將邊緣和顏色組合起來編碼簡單的紋理
  • 在更高層的濾波器開始尋找在自然圖像中類似的紋理:羽毛娘锁,眼睛牙寞,葉子等等。

分類激活的熱力圖可視化

我們還將介紹一些可視化技術(shù)莫秆,對于理解給定圖像的哪一部分會導(dǎo)致卷積網(wǎng)絡(luò)作出最終的分類決定间雀。這對于調(diào)試卷積網(wǎng)絡(luò)決定的錯誤非常管用,特別是在分類錯誤的情況下镊屎。這也將允許你找到圖像中的特定物體惹挟。
這種一般的分類方法叫做“分類激活圖”CAM可視化,是通過在輸入圖像上畫分類激活值的熱力圖得到的缝驳。一個分類激活值的熱力圖是一個二維的和特定輸出類別相關(guān)的分?jǐn)?shù)连锯,計(jì)算每一個輸入圖像的位置归苍,指示著每一個位置對于分類結(jié)果的重要程度。例如运怖,給一個圖像進(jìn)我們的"cat vs. dog"卷積網(wǎng)絡(luò)拼弃,分類激活圖允許我們生成一幅關(guān)于貓的熱力圖,指示貓樣子的圖在不同的地方是什么樣子摇展,類似的可以畫出狗的吻氧。
我們用的具體的實(shí)現(xiàn)過程在Grad-CAM中詳細(xì)描述。其實(shí)很簡單:衡量每一個特征對于分類的權(quán)重咏连,最后畫出來盯孙。
我們將使用預(yù)訓(xùn)練過的VGG16網(wǎng)絡(luò)來討論這個技術(shù)。

from keras.applications.vgg16 import VGG16
# Note that we are including the densely-connected classifier on top;
# all previous times, we were discarding it.
model = VGG16(weights='imagenet')

讓我們考慮下面這幅有著兩只非洲象的圖片祟滴,或許是一個母親和它的幼崽漫步在大草原:

Our test picture of African elephants

讓我們將圖像轉(zhuǎn)化成VGG16模型能讀得懂的數(shù)據(jù):這個模型在大小為
224 \times 224
的圖像上訓(xùn)練振惰,預(yù)處理的幾個很少的規(guī)則都打包在了keras.applications.vgg16.preprocess_input。所以我們需要加載圖像踱启,并把大小resize到
224\times 224
轉(zhuǎn)化成float32的張量报账,并使用那些預(yù)處理規(guī)則。

from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input, decode_predictions
import numpy as np
# The local path to our target image
img_path = '/Users/fchollet/Downloads/creative_commons_elephant.jpg'
# `img` is a PIL image of size 224x224
img = image.load_img(img_path, target_size=(224, 224))
# `x` is a float32 Numpy array of shape (224, 224, 3)
x = image.img_to_array(img)
# We add a dimension to transform our array into a "batch"
# of size (1, 224, 224, 3)
x = np.expand_dims(x, axis=0)
# Finally we preprocess the batch
# (this does channel-wise color normalization)
x = preprocess_input(x)

我們可以在預(yù)訓(xùn)練網(wǎng)絡(luò)上允許圖片埠偿,并把預(yù)測向量返回為人類可閱讀的形式:

>>> preds = model.predict(x)
>>> print('Predicted:', decode_predictions(preds, top=3)[0])
Predicted:', [(u'n02504458', u'African_elephant', 0.92546833),
(u'n01871265', u'tusker', 0.070257246),
(u'n02504013', u'Indian_elephant', 0.0042589349)]

前三類關(guān)于該圖像的預(yù)測為:

  • 非洲象(92.5%)
  • 長牙象(7%)
  • 印度象(0.4%)

因此我們的網(wǎng)絡(luò)識別圖像時包含了一個待定數(shù)量的非洲象透罢。預(yù)測向量 的最大激活值在“非洲象類”,指標(biāo)是386.

>>> np.argmax(preds[0])
386

為了可視化我們圖像的哪一部份最像“非洲象”冠蒋,讓我們設(shè)置一個Grad-CAM過程:

# This is the "african elephant" entry in the prediction vector
african_elephant_output = model.output[:, 386]
# The is the output feature map of the `block5_conv3` layer,
# the last convolutional layer in VGG16
last_conv_layer = model.get_layer('block5_conv3')
# This is the gradient of the "african elephant" class with regard to
# the output feature map of `block5_conv3`
grads = K.gradients(african_elephant_output, last_conv_layer.output)[0]
# This is a vector of shape (512,), where each entry
# is the mean intensity of the gradient over a specific feature map channel
pooled_grads = K.mean(grads, axis=(0, 1, 2))
# This function allows us to access the values of the quantities we just defined:
# `pooled_grads` and the output feature map of `block5_conv3`,
# given a sample image
iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])
# These are the values of these two quantities, as Numpy arrays,
# given our sample image of two elephants
pooled_grads_value, conv_layer_output_value = iterate([x])
# We multiply each channel in the feature map array
# by "how important this channel is" with regard to the elephant class
for i in range(512):
 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)
plt.matshow(heatmap)
African elephant class activation heatmap over our test picture

最后羽圃,我們使用OpenCV來生成一幅原圖和熱力圖的疊加:

import cv2
# We use cv2 to load the original image
img = cv2.imread(img_path)
# We resize the heatmap to have the same size as the original image
heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))
# We convert the heatmap to RGB
heatmap = np.uint8(255 * heatmap)
# We apply the heatmap to the original image
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
# 0.4 here is a heatmap intensity factor
superimposed_img = heatmap * 0.4 + img
# Save the image to disk
cv2.imwrite('/Users/fchollet/Downloads/elephant_cam.jpg', superimposed_img)
Superimposing the class activation heatmap with the original picture

這項(xiàng)技術(shù)回答了兩個重要的問題:

  • 為什么網(wǎng)絡(luò)認(rèn)為圖像包含了一個非洲象?
  • 非洲象在圖像中的位置抖剿?

特別的朽寞,我們可以看到非洲象寶寶的耳朵被強(qiáng)烈激活:這或許是網(wǎng)絡(luò)認(rèn)為的非洲象和印第安象的區(qū)別。

總結(jié):計(jì)算機(jī)視覺中的深度學(xué)習(xí)

這里有一些你需要從本章打包帶走的東西:

  • 卷積網(wǎng)絡(luò)是一個解決圖像分類問題的最好工具斩郎。
  • 它們通過學(xué)習(xí)模式化的層次以及在視覺世界的表示來工作
  • 它們學(xué)習(xí)到的表示很容易觀察到——它們和黑盒相反

此外脑融,你應(yīng)當(dāng)選擇一些實(shí)用的技巧:

  • 你能夠從零訓(xùn)練你的卷積網(wǎng)絡(luò)解決圖像分類問題。
  • 你理解如何使用可視化數(shù)據(jù)增加來對抗過擬合
  • 你知道如何使用預(yù)訓(xùn)練的卷積網(wǎng)絡(luò)來做特征提取和調(diào)參
  • 你能夠生成你的網(wǎng)絡(luò)學(xué)習(xí)到的濾波器的可視化缩宜,就像分類激活的熱力圖那樣肘迎。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锻煌,隨后出現(xiàn)的幾起案子妓布,更是在濱河造成了極大的恐慌,老刑警劉巖宋梧,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匣沼,死亡現(xiàn)場離奇詭異,居然都是意外死亡捂龄,警方通過查閱死者的電腦和手機(jī)释涛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門加叁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人枢贿,你說我怎么就攤上這事殉农。” “怎么了局荚?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長愈污。 經(jīng)常有香客問我耀态,道長,這世上最難降的妖魔是什么暂雹? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任首装,我火速辦了婚禮,結(jié)果婚禮上杭跪,老公的妹妹穿的比我還像新娘仙逻。我一直安慰自己,他們只是感情好涧尿,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布系奉。 她就那樣靜靜地躺著,像睡著了一般姑廉。 火紅的嫁衣襯著肌膚如雪缺亮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天桥言,我揣著相機(jī)與錄音萌踱,去河邊找鬼。 笑死号阿,一個胖子當(dāng)著我的面吹牛并鸵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扔涧,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼园担,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扰柠?” 一聲冷哼從身側(cè)響起粉铐,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卤档,沒想到半個月后蝙泼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡劝枣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年汤踏,在試婚紗的時候發(fā)現(xiàn)自己被綠了织鲸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡溪胶,死狀恐怖搂擦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哗脖,我是刑警寧澤瀑踢,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站才避,受9級特大地震影響橱夭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桑逝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一棘劣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧楞遏,春花似錦茬暇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拘荡,卻和暖如春臼节,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背珊皿。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工网缝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蟋定。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓粉臊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親驶兜。 傳聞我的和親對象是個殘疾皇子扼仲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容