pytorch中網路特徵圖(feture map)、卷積核權重、卷積核最匹配樣本、類別啟用圖(Class Activation Map/CAM)、網路結構的視覺化方法

tensor_zhang發表於2021-07-23

0,視覺化的重要性:

深度學習很多方向所謂改進模型、改進網路都是在按照人的主觀思想在改進,常常在說模型的本質是提取特徵,但並不知道它提取了什麼特徵、哪些區域對於識別真正起作用、也不知道網路是根據什麼得出了分類結果。為了增強結果的可解釋性,需要給出模型的一些視覺化圖來證明模型或新methods對於任務的作用,這一點不僅能增加新模型或新methods可信度;還可以根據視覺化某個網路的結果分析其不足之處,從而提出新的改進方法。(寫論文還可以用來湊字數、湊工作量:)

無特殊說明,本文中所用的網路是:

torchvision.models.resnet50(pretrained=True)

所用的圖片為
案例16C1

1,特徵圖(feture map)

  • 概念
    特徵圖是一個在深度學習的研究過程中經常會遇到的概念,簡單來說就是對輸入進行一次計算處理後的輸出,通過對特徵圖的視覺化可以看出輸入樣本在網路中的變化情況。
  • 視覺化方法:
    • 直接視覺化:直接讓輸入資料經過各層網路,獲取各層網路處理後的輸出,然後繪製想要展示的特徵圖即可。
    • 反摺積網路(deconvnet)From 'Visualizing and Understanding Convolutional Networks':對一個訓練好的神經網路中任意一層feature map經過反摺積網路後重構出畫素空間,主要操作是
      • Unpooling/反池化:將最大值放到原位置,而其他位置直接置零。
      • Rectification:同樣使用Relu作為啟用函式。
      • Filtering/反摺積:使用原網路的卷積核的轉置作為卷積核,對Rectification後的輸出進行卷積。
    • 導向反向傳播(Guided-backpropagation)From 'Striving for simplicity: The all convolutional net':其與反摺積網路的區別在於對ReLU的處理方式,在反摺積網路中使用ReLU處理梯度,只回傳梯度大於0的位置;而在普通反向傳播中只回傳feature map中大於0的位置;在導向反向傳播中結合這兩者,只回傳輸入和梯度都大於0的位置。
  • 例子
'''方法1,直接視覺化'''
import torch
import torchvision
import cv2
from PIL import Image
import torchvision.models as models
import torch.nn as nn
from matplotlib import pyplot as plt
import math

'''1,載入訓練模型'''
resnet50 = models.resnet50(pretrained=True)
print(resnet50)

'''2,提取CNN層,非必須'''
conv_layers = []
model_weights = []
model_children = list(models.resnet50().children())
counter = 0

for i in range(len(model_children)):
    if type(model_children[i]) == nn.Conv2d:
        counter += 1
        model_weights.append(model_children[i].weight)
        conv_layers.append(model_children[i])
    elif type(model_children[i]) == nn.Sequential:
        for j in range(len(model_children[i])):
            for child in model_children[i][j].children():
                if type(child) == nn.Conv2d:
                    counter += 1
                    model_weights.append(child.weight)
                    conv_layers.append(child)

'''3,讀取資料'''
img = cv2.cvtColor(cv2.imread('data.jpg'), cv2.COLOR_BGR2RGB)
img = torchvision.transforms.Compose([
    torchvision.transforms.ToPILImage(),
    torchvision.transforms.Resize((1050, 1680)),
    torchvision.transforms.ToTensor(),
    torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])(img).unsqueeze(0)

'''4,特徵對映,這裡繪製第一層卷積的feature map'''
featuremaps = [conv_layers[0](img)]
plt.figure(1)
for i in range(64):
    plt.subplot(8, 8, i + 1)
    plt.axis('off')
    plt.imshow(featuremaps[0][0, i, :, :].detach(), cmap='gray')
plt.show()
案例16C1

2,卷積核權重

  • 概念
    卷積核的計算可以看作是在計算相似度,二維卷積核本身可以看作是一副縮圖,一維卷積核本身也可以看作是一段一維訊號,卷積核權重的視覺化就是將這些資訊視覺化。(雖然這些資訊幾乎都是雜亂無章的,沒什麼用:)
  • 視覺化方法
    取出某個想要視覺化的卷積核權重,然後繪圖即可。
  • 例子
    for i in range(64):
        plt.subplot(8, 8, i+1)
        plt.axis('off')
        plt.imshow(model_weights[0][i][0, :, :].detach(), cmap='gray')
    plt.show()
    
    案例16C1

3,卷積核最匹配樣本

  • 概念
    正如2中提到的,卷積核的計算可以看作是在計算相似度,那麼在一批樣本中經過卷積核計算以後的相似度值肯定有高有底,那麼相似度越高也就越滿足這個卷積核的“口味”,這也意味著該樣本與該卷積核越匹配。那麼,有沒有方法能夠超越有限的訓練樣本,生成與特定卷積核最匹配的樣本呢?答案是肯定的。一個可行的思路是:隨機初始化生成一個樣本(指的是對樣本的各個資料點隨機取值,而不是在資料集中隨機選一個樣本),然後經過前向傳播到該卷積核;我們希望這個隨機生成的樣本在經過這一層卷積核時響應值能儘可能的大(響應值比較大的樣本是這個卷積核比較認可的,是與識別任務更相關的);我們要做的就是不斷調整樣本各個資料點的值,直到響應值足夠大,我們就可以認為此時的樣本就是這個卷積核所認可的,從而達到生成卷積核最匹配樣本的目的。
  • 視覺化方法
    設計一個損失函式(例如:經過變換後的響應值),使用梯度上升,更新資料點的值,使響應值最大。
  • 例子
    下圖展示的是pytorch中的resnet50中的layer1的最後一次卷積操作中的256個卷積核中的0,10,...,150這16個卷積核的最匹配樣本。優化過程中用了兩種方法,結果如圖:
    方法1
    案例16C1
    方法2,可以明顯看出方法2的結果區別性更大
    案例16C1

4,類別啟用圖(Class Activation Map/CAM)

  • 概念
    特徵圖視覺化、卷積核權重視覺化、卷積核最匹配樣本這些方法更多是用於分析模型在某一層學習到的東西;但是對於不同的類,我們又如何知道模型是根據哪些資訊進行識別的?是否能將這些資訊在原始資料上表示出來(比如說熱力圖)?答案依舊是肯定的。這個方法主要是CAM系列,目前有CAM, Grad-CAM, Grad-CAM++。其中CAM需要特定的結構-GAP,但大部分現有的模型沒有這個結構,想要使用該方法便需要修改原模型結構,並重新訓練,因此適用範圍有限。針對CAM的缺陷,有了之後Grad-CAM的提出。
  • 視覺化方法
    Grad-CAM的最大特點就是不再需要修改現有的模型結構,也不需要重新訓練,可以直接在原模型上視覺化。Grad-CAM對於想要視覺化的類別C,使最後輸出的類別C的概率值通過反向傳播到最後一層feature maps,得到類別C對該feature maps的每個畫素的梯度值;對每個畫素的梯度值取全域性平均池化,即可得到對feature maps的加權係數alpha;接下來對特徵圖加權求和,使用ReLU進行修正,再進行上取樣。使用ReLU的原因是對於那些負值,可認為與識別類別C無關,這些負值可能是與其他類別有關,而正值才是對識別C有正面影響的。
案例16C1

計算梯度及全域性平均池化:

\[\alpha_k^c=\overbrace{\frac{1}{Z}\displaystyle{\sum_i\sum_j}}^{global~average~pooling}\underbrace{\frac{\partial{y^c}}{\partial{A^k_{ij}}}}_{grdients~via~backprop} \]

加權:

\[L^c_{Grad-CAM}=ReLU\underbrace{\left(\displaystyle\sum_k\alpha^c_kA^k\right)}_{linear~combination} \]

  • 例子
    1,由藍到紅,越紅代表關注度越高,對於類別分配的結果影響越大,這個例子將圖片識別為n02909870 水桶,從關注的區域也可以看出預測是失敗的,沒有關注到有效的資訊
    案例16C1
    這個預訓練模型眼裡好像不是水桶就是鉤子。。。

2,從網上找了一個模型,效果就挺好的
案例16C1案例16C1案例16C1案例16C1

案例16C1案例16C1案例16C1案例16C1

案例16C1案例16C1案例16C1案例16C1

識別青蛙的結果很有意思,這意味著網路關注的重點不在於青蛙,反而在於青蛙周圍的環境,而且網路的預測結果還是正確的!
案例16C1案例16C1案例16C1案例16C1

5,網路結構的視覺化

  • 視覺化方法
    用tensorboard直接繪製就行,注意兩點
    • 路徑不要有中文
    • pytorch版本在1.3.0及以上(低版本不顯示)
  • 例子
from torch.utils.tensorboard import SummaryWriter
import torch
import torch.nn as nn
import torch.nn.functional as F

'''
1,初始化writer
'''
writer = SummaryWriter('runs/resnet50')    # 指定寫入檔案的位置

'''
2,載入模型
'''
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(6 * 12 * 12, 120)
        self.fc2 = nn.Linear(120, 84)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = x.view(-1, 6 * 12 * 12)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        return x
net = Net()

'''
3,新增網路結構
'''
writer.add_graph(net, torch.randn(1, 1, 28, 28))
writer.close()
案例16C1

相關文章