日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

简明代码介绍类激活图CAM, GradCAM, GradCAM++

發布時間:2025/3/8 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 简明代码介绍类激活图CAM, GradCAM, GradCAM++ 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

??類激活圖(class activation map, CAM)能夠顯示輸入圖像各區域對于分類神經網絡指定類別提供信息的多少,可以幫助我們更好的理解神經網絡的工作過程。關于CAM網上講的有不少,我這里重點給出CAM的簡明代碼實現,并討論一下各種情況下CAM的效果。CAM已經發展出了好幾種方法,有CAM, GradCAM, GradCAM++, SmoothGradCAM++, ScoreCAM, SSCAM, ISCAM等。這里只介紹前三種,其他暫時不打算學。網上給出的代碼要么完整而太長,要么太短而不完整,都不易快速理解,我這里給出完整又簡短的寫法,比較好理解。如果對其中涉及到的hook鉤子方法不太熟悉,可以看我另一篇博客專門介紹鉤子方法。

一、CAM

??CAM僅適用于網絡結構是最后一個卷積層+GAP(即global average pooling,全局平均池化)+fc的情況。這種情況時,最后一個卷積層輸出的特征圖feature_map(在下面代碼中我們命名為hook_a)經過GAP池化處理后會從C×H×W變成C×1×1,這樣最后的全連接層fc的輸入節點數就是C,所以fc的權重weights就是C × num_classes的。如果我們把weights中每個類別的向量和最后卷積層的特征圖相乘后再相加,就可以得到一個H×W尺寸的圖,這個圖成為熱圖heatmap,它反映了feature_map中每個像素對最后分類的重要程度。而卷積神經網絡始終只是進行縮放而沒有旋轉變換,所以hook_a和原輸入圖片的坐標位置是對應的,因此可以resize后和原圖片疊加起來用于顯示原圖中每個區域對指定類的貢獻程度。
??好吧,用人類語言解釋這個事太費勁了,估計大家沒怎么看懂,show you the code:

import numpy as np import torch.nn.functional as F import torchvision.models as models from torchvision.transforms.functional import normalize, resize, to_tensor, to_pil_image import matplotlib.pyplot as plt from matplotlib import cm from PIL import Imagenet = models.resnet18(pretrained=True).cuda()hook_a = None def _hook_a(module,inp,out):global hook_ahook_a = outsubmodule_dict = dict(net.named_modules()) target_layer = submodule_dict['layer4'] hook1 = target_layer.register_forward_hook(_hook_a)img_path = 'images/border_collie2.jpg' img = Image.open(img_path, mode='r').convert('RGB') img_tensor = normalize(to_tensor(resize(img, (224, 224))),[0.485, 0.456, 0.406], [0.229, 0.224, 0.225]).cuda()scores = net(img_tensor.unsqueeze(0)) hook1.remove() class_idx = 232 # class 232 corresponding to the border collie weights = net.fc.weight.data[class_idx,:] cam = (weights.view(*weights.shape, 1, 1) * hook_a.squeeze(0)).sum(0) cam = F.relu(cam) cam.sub_(cam.flatten(start_dim=-2).min(-1).values.unsqueeze(-1).unsqueeze(-1)) cam.div_(cam.flatten(start_dim=-2).max(-1).values.unsqueeze(-1).unsqueeze(-1)) cam = cam.data.cpu().numpy()heatmap = to_pil_image(cam, mode='F') overlay = heatmap.resize(img.size, resample=Image.BICUBIC) cmap = cm.get_cmap('jet') overlay = (255 * cmap(np.asarray(overlay) ** 2)[:, :, :3]).astype(np.uint8) alpha = .7 result = (alpha * np.asarray(img) + (1 - alpha) * overlay).astype(np.uint8) plt.imshow(result)

圖1.原圖

圖2. CAM法輸出結果

??我們可以看出來,ResNet18并沒有關注到圖中所有的邊牧,它只關注了最像的一個,這就足以讓它做出分類判斷了。

二、GradCAM

??與CAM相比,GradCAM不再使用全連接層的權重,而是巧妙計算了指定類輸出得分相對于卷積層特征圖的梯度,不再要求最后一個卷積層和全連接層之間必須有GAP層,適用范圍更廣。

圖3. GradCAM法核心公式

??
??用論文中這個公式來解釋,其中 AijkA^{k}_{ij}Aijk?表示卷積層的輸出特征圖,k是通道數,i,j是像素數,ycy^{c}yc表示第c類的輸出向量,?yc?Aijk\frac{\partial y^{c}}{\partial A^{k}_{ij}}?Aijk??yc?表示的就是第c類對特征圖的梯度。對此梯度按每通道求平均,也可以得到一個k維向量αkc\alpha ^{c}_{k}αkc?, 就是代碼中的weights,用它來取代CAM中的weights也可以進行計算。對于適用了末層卷積層和全連接層之間使用了GAP的情況下,用GradCAM法和CAM法計算的結果是相同的。下面還是給出完整代碼:

import numpy as np import torch.nn.functional as F import torchvision.models as models from torchvision.transforms.functional import normalize, resize, to_tensor, to_pil_image import matplotlib.pyplot as plt from matplotlib import cm from PIL import Imagenet = models.resnet18(pretrained=True).cuda()hook_a = None def _hook_a(module,inp,out):global hook_ahook_a = outhook_g = None def _hook_g(module,inp,out):global hook_ghook_g = out[0]submodule_dict = dict(net.named_modules()) target_layer = submodule_dict['layer4']hook1 = target_layer.register_forward_hook(_hook_a) hook2 = target_layer.register_backward_hook(_hook_g)img_path = 'images/border_collie3.jpg' img = Image.open(img_path, mode='r').convert('RGB') img_tensor = normalize(to_tensor(resize(img, (224, 224))),[0.485, 0.456, 0.406], [0.229, 0.224, 0.225]).cuda()scores = net(img_tensor.unsqueeze(0)) class_idx = 232 # class 232 corresponding to the border collie loss = scores[:,class_idx].sum() loss.backward() hook1.remove() hook2.remove()weights = hook_g.squeeze(0).mean(dim=(1,2)) cam = (weights.view(*weights.shape, 1, 1) * hook_a.squeeze(0)).sum(0) cam = F.relu(cam) cam.sub_(cam.flatten(start_dim=-2).min(-1).values.unsqueeze(-1).unsqueeze(-1)) cam.div_(cam.flatten(start_dim=-2).max(-1).values.unsqueeze(-1).unsqueeze(-1)) cam = cam.data.cpu().numpy()heatmap = to_pil_image(cam, mode='F') overlay = heatmap.resize(img.size, resample=Image.BICUBIC) cmap = cm.get_cmap('jet') overlay = (255 * cmap(np.asarray(overlay) ** 2)[:, :, :3]).astype(np.uint8) alpha = .7 result = (alpha * np.asarray(img) + (1 - alpha) * overlay).astype(np.uint8) plt.imshow(result)

??注意再GradCAM中使用的loss就是最后輸出的指定類的得分,使用這個loss反向傳播,而這里提取的梯度也不是常規情況下我們關注的權重的梯度,而是特征圖的梯度。
??理論上GradCAM法可以用任意卷積層的特征圖和特征圖的梯度計算這個cam圖:

圖4. GradCAM法使用ResNet18不同層畫出的cam圖

??但是從各層畫出的結果中可以看出,只有最后層畫出的cam圖有意義,這個原因我還沒有細想。

三、GradCAM++

??GradCAM++為了效果更好,引入了二階梯度和三階梯度,在代碼中計算的時候為了方便,分別用一階梯度的平方和三次方來替代。代碼和GradCAM基本相同,只需要把計算weights的一句替換為下面代碼即可:

##weights = hook_g.squeeze(0).mean(dim=(1,2)) grad_2 = hook_g.pow(2) grad_3 = grad_2 * hook_g denom = 2 * grad_2 + (grad_3 * hook_a).sum(dim=(2, 3), keepdim=True) nan_mask = grad_2 > 0 grad_2[nan_mask].div_(denom[nan_mask]) weights = grad_2.squeeze_(0).mul_(torch.relu(hook_g.squeeze(0))).sum(dim=(1, 2))

圖5. GradCAM++法輸出結果

總結

以上是生活随笔為你收集整理的简明代码介绍类激活图CAM, GradCAM, GradCAM++的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。