6.4 CAM視覺化與hook函數使用
前文說到,在本章第二節介紹CNN的視覺化時我們知道,深度學習模型仍是一個黑箱,大家想盡辦法對其進行視覺化,本節就介紹一個實用的分析方法CAM(Class activation mapping,類啟動圖),如下圖所示:
以上圖為例,CAM方法是探究模型將圖片分為“Dog”的依據是什麼,即圖片中哪些區域支撐了模型將圖片判別為"Dog"。
其背後的原理是將網路中間的特徵圖進行加權並視覺化,而權重的得來就有多種方法,不同的方法就有了不同的CAM演算法,如CAM、Grad CAM和Grad CAM++。
CAM:《2016-CAM-Learning Deep Features for Discriminative Localization》
Grad-CAM:《2017-Grad-CAM Visual Explanations from Deep Networks via Gradient-based Localization》
Grad-CAM++:《2018-Grad-CAM++ Generalized Gradient-based Visual Explanations for Deep Convolutional Networks》
本文將介紹其演變過程,並用手寫代碼實現Grad CAM,同時借助強大的github倉庫,實現多種熱力圖對比,如下圖所示:
CAM
論文名稱:《Learning Deep Features for Discriminative Localization》
原理解釋:CAM方法需要將CNN模型修改後重新訓練,修改的目的就是為了獲得加權權重,具體方式如下圖所示:
將最後一層特徵圖之後的層移除,並接入一個全連接層用於分類,全連接層的輸入是特徵圖的全域池化後的特徵向量。
最終熱力圖通過分類類別神經元所對應的權重w,乘以最後一層特徵圖,再求和即可。
背後邏輯有兩個:
1、最後一層特徵圖是原圖經過一系列卷積後保留下來的特徵,其與原圖的空間關係一一對應,空間關係指左上角對應左上角,右下角對應原圖右下角,等等。
2、特徵圖對於分類類別(如上圖的AustralianTerrier)的貢獻程度與全連接層權重密切相關
因此,只需要利用全連接層的權重乘以對應通道,即可得到熱力圖。
Grad CAM
論文名稱:《Grad-CAM Visual Explanations from Deep Networks via Gradient-based Localization》
CAM的思路非常巧妙,但缺點很明顯,它需要對模型進行修改,並且重新訓練,不適用於大多數場景。
為此,研究者提出額Grad CAM,可以對模型直接進行觀察,無需改動模型。
Grad CAM的思路也非常巧妙,在CAM思路中有兩個要素:
1、特徵圖
2、特徵圖對應指定類別的權重
特徵圖很容易獲得,但特徵圖的重要性(加權權重)應該如何尋找?
Grad CAM給出了不一樣的答案,Grad CAM利用梯度求導獲得特徵圖的重要性權重。
原理分析:
假設最後一層feature maps有10個,那麼如何尋找10個權值用來指示這些feature maps對某一類別的重要性呢?
CAM是通過對feature maps進行GAP,然後採用該類別與這個10個GAP後的神經元的連接權值作為權值;
而Grad-CAM採用的則是梯度,是該類別對於這10個feature maps的求取梯度。
注意:最終求取的梯度是一個110的向量,即每個feature map對應一個標量,而對feature maps求取梯度是一個矩陣,作者是通過對*矩陣求均值得到的標量。
對於類別c,特徵圖的權值 a_k^c 計算公式如下:
c表示第c類,k表示第k個特徵圖,Z表示特徵圖圖元點總數,i表示行,j表示列,A表示特徵圖。
公式可以分兩部分看,最右邊 gradients via backprop,即對類別c求得特徵圖的梯度,是一個二維的矩陣;
再通過左邊的global average pooling,對二維的梯度矩陣求平均值,得到第k個特徵圖對於第c類的權值。
示意圖如下:
最終的熱力圖通過以下公式進行求取:
與CAM不同的是,Grad-CAM在加權求和後還加上了ReLU函數,計算公式如下:
之所以加上ReLU,是因為在這裡只關注正的梯度值,而不關注負的。
最後將Discriminative Localization Map直接resize至原圖大小即可,如最後一層feature maps是14*14的,原圖是224*224,則將14*14的圖縮放到224*224即可。
Grad-CAM++
論文名稱:《Grad-CAM++ Generalized Gradient-based Visual Explanations for Deep Convolutional Networks》
事物總是在不斷的發展,Grad CAM還存在以下缺點:
- 當圖片出現多個同類物體時,無法定位;(1-3列)
- 單物體圖片中,定位錯誤。(4-6列)
並且, Grad-CAM中一個特徵圖對應的權重是對特徵圖的梯度求平均,即認為特徵圖的梯度(2維資料)上的每個元素同等重要。
而Grad-CAM++則認為特徵圖的梯度上的每個元素重要程度應當不一樣,因此對Grad CAM進行了改進。
Grad-CAM++ 熱力圖的權重計算通過以下公式:
CAM系列對比
CAM、Grad-CAM和Grad-CAM++的區別如下圖所示:
關於CAM還有很多反覆運算改進,可參考這個repo
repo中對數十個CAM進行了實現,建議使用。
本節將用代碼手動的實現Grad CAM,並通過演算法的實現來學習nn.module中的hook函數使用。
nn.module中的hook函數
在CAM系列演算法中知道,需要利用中間層的特徵圖,可nn.module並不會返回、保留中間層的特徵圖。這時,就要用到nn.module中的hook函數,把中間層特徵圖給拿出來。
什麼是hook函數?
pytorch中的hook是一個非常有意思的概念,hook意為鉤、掛鉤、魚鉤。 引用知乎用戶“馬索萌”對hook的解釋:“(hook)相當於外掛程式。可以實現一些額外的功能,而又不用修改主體代碼。把這些額外功能實現了掛在主代碼上,所以叫鉤子,很形象。” 簡單講,就是不修改主體,而實現額外功能。對應到在pytorch中,主體就是forward和backward,而額外的功能就是對模型的變數進行操作
pytorch提供的hook
1. torch.Tensor.register_hook
功能:註冊一個反向傳播hook函數,這個函數是Tensor類裡的,當計算tensor的梯度時自動執行。
形式: hook(grad) -> Tensor or None ,其中grad就是這個tensor的梯度。
返回值:a handle that can be used to remove the added hook by calling handle.remove()
案例請看配套代碼
2. torch.nn.Module.register_forward_hook
功能:Module前向傳播中的hook,module在前向傳播後,自動調用hook函數。
形式:hook(module, input, output) -> None。注意不能修改input和output 返回值
其中,module是當前網路層,input是網路層的輸入資料, output是網路層的輸出資料
應用場景:如用於提取特徵圖
案例請看配套代碼
3. torch.nn.Module.register_forward_pre_hook
功能:執行forward()之前調用hook函數。
形式:hook(module, input) -> None or modified input
應用場景舉例:暫時沒碰到過,希望讀者朋友補充register_forward_pre_hook相關應用場景。
registerforwardprehook與forwardhook一樣,是在module.__call中註冊的,與forward_hook不同的是,其在module執行forward之前就運行了,具體可看module.__call中的代碼,第一行就是執行forward_pre_hook的相關操作。
4. torch.nn.Module.register_full_backward_hook
功能:Module反向傳播中的hook,每次計算module的梯度後,自動調用hook函數。
形式:hook(module, grad_input, grad_output) -> tuple(Tensor) or None
注意事項:當module有多個輸入或輸出時,grad_input和grad_output是一個tuple。
返回值:a handle that can be used to remove the added hook by calling handle.remove()
應用場景舉例:例如提取特徵圖的梯度
Grad CAM 手動實現
下面就利用 register_forward_hook 和 register_full_backward_hook 來實現Grad CAM
詳情請看配套代碼
整體思路如下:
- 對模型最後一個卷積層進行hook函數註冊,兩個hook分別記錄特徵圖於梯度
def backward_hook(module, grad_in, grad_out):
grad_block.append(grad_out[0].detach())
def farward_hook(module, input, output):
fmap_block.append(output)
------------------------------------------------------------
# 註冊hook
resnet_50.layer4[-1].register_forward_hook(farward_hook)
resnet_50.layer4[-1].register_full_backward_hook(backward_hook)
Copy
- 獲取類別loss,類別loss為分類類別最大的那個神經元的值,具體由comp_class_vec函數實現
if not index:
index = np.argmax(ouput_vec.cpu().data.numpy())
else:
index = np.array(index)
index = index[np.newaxis, np.newaxis]
index = torch.from_numpy(index)
one_hot = torch.zeros(1, 1000).scatter_(1, index, 1)
one_hot.requires_grad = True
class_vec = torch.sum(one_hot * ouput_vec) # one_hot = 11.8605
Copy
- 執行backward,得到梯度
- 通過gen_cam()函數得到CAM圖
- 將CAM與原圖進行融合視覺化,如下圖所示
CAM 系列演算法統一實現
CAM自2016年提出以來,已經有多種改進,並可運用於圖像分割和目標檢測,詳細的CAM演算法參見倉庫。
pytorch-grad-cam提供了豐富的演算法及簡單的介面應用,下面就以resnet50為例,繪製6種CAM演算法的熱力圖,效果如下圖所示。
代碼就不剖析了,grad-cam的介面已經非常清晰。請運行代碼,查看結果如下圖所示:
小結
CAM系列演算法對理解深度卷積神經網路非常有説明,建議仔細學習本節內容並進行拓展。
通過CAM分析:
- 可診斷模型是否學到真正特徵
- 可通過熱力圖資訊做對應的資料增強(如對非啟動區域進行隨機擦除和Cutout處理),類似YOLOv4中的CutMix資料增強方法。
- 還可以將熱力圖作為語義分割的弱監督標籤進行訓練分割模型,可參考《Tell Me Where to Look: Guided Attention Inference Network》
留言列表