神經(jīng)網(wǎng)絡(luò)可視化——CAM方法

姓名:成杰? ? ?學(xué)號:21021210653? ? 學(xué)院:電子工程學(xué)院

轉(zhuǎn)自:https://zhuanlan.zhihu.com/p/509645247

【嵌牛導(dǎo)讀】

CAM(Class Activation Mapping):將CNN在分類時使用的分類依據(jù)(圖中對應(yīng)的類別特征)在原圖的位置進(jìn)行可視化,并繪制成熱圖驹针,以此作為物體定位的依據(jù)的方法烘挫。

【嵌牛鼻子】

神經(jīng)網(wǎng)絡(luò)、可視化

【嵌牛提問】

CAM方法的原理柬甥?

【嵌牛正文】?

1. 整體思路

CNN網(wǎng)絡(luò)做分類時之所以丟失了物體的位置信息饮六,是因?yàn)榫W(wǎng)絡(luò)末端使用了全連接層,通過使用GAP替代全連接層苛蒲,從而使卷積網(wǎng)絡(luò)的定位能力能延續(xù)到網(wǎng)絡(luò)的最后一層卤橄,故保持經(jīng)典網(wǎng)絡(luò)(如VGGnet窟扑、Alexnet和GoogleNet)的卷積部分,只在輸出層前(用于分類的softmax)將全連接層替換為GAP橘霎,并將它們作為得出分類的特征。通過這樣的連接結(jié)構(gòu)阔蛉,可以把圖片中重要的區(qū)域用輸出層權(quán)重映射回卷積層特征的方式標(biāo)記出來,這種方法稱為類別激活映射或類別激活圖损搬。

2. 全局平均池化層(GAP)和全局最大池化層(GMP)對比

GAP和GMP都是全局池化的方法嵌灰,也有學(xué)者在做弱監(jiān)督物體定位時采用了這兩種方法沽瞭,而文章之所以選擇GAP有以下兩個原因:

(1) GMP希望網(wǎng)絡(luò)更關(guān)注物體的1個discriminaltive part,更關(guān)注物體的邊緣識別豌鹤,取最大的部分,而GAP則更希望網(wǎng)絡(luò)識別物體的整個范圍灵临。在求平均值時儒溉,GAP可以綜合并找到所有discriminaltive part來得到最大激活赶诊,對于低激活的區(qū)域就會減少特定輸出舔痪,即GAP相對于GMP來說識別這個物體辨別性區(qū)域的損失更小。

(2) GMP由于只取了區(qū)域最大值滋捶,所以其他低分的區(qū)域?qū)ψ罱K分類的得分都不會有影響,GMP的分類性能和GAP相當(dāng)巡扇,但GAP的定位能力強(qiáng)于GMP。

3. 原理

CAM激活圖由兩部分加權(quán)構(gòu)成:原圖+特征圖刀闷。其中特征圖是通過:最后全連接層的參數(shù)(權(quán)重矩陣W)與最后輸出的特征圖集合對應(yīng)相乘再相加(重疊)而形成,即顯示模型是依據(jù)特征圖進(jìn)行決策分類的筒扒。


(1)輸入:訓(xùn)練集或測試集圖片澄步,大小為[224,224,3]

(2) VGG16預(yù)訓(xùn)練卷積層最后一層的特征圖大小為7*7村缸,共512張仇箱,所以大小為[7,7,512]

(3)經(jīng)過全局平均池化(GAP)剂桥,512張?zhí)卣鲌D被降維為長度512的特征向量

(4)最后特征向量與權(quán)重矩陣W點(diǎn)積,再經(jīng)過softmax函數(shù)壓縮為[0,1]區(qū)間內(nèi)的概率

由于采用遷移學(xué)習(xí)的方式斟薇,卷積層凍結(jié)固定,所以對于同一張圖片袱箱,卷積層的輸出特征始終不變,模型的分類概率只隨著全連接層的權(quán)重矩陣W發(fā)生變化筐咧,這就是模型的學(xué)習(xí)更新過程。

權(quán)重矩陣W可以理解為對長度為512的特征向量的加權(quán)残炮,畢竟特征向量是由特征圖全局平局池化GAP所得。歸根到底是對特征圖集合的加權(quán)苞冯,所以利用特征圖集合與權(quán)重矩陣相乘司忱,再重疊為一張?zhí)卣鲌D,就可以模擬模型分類過程中,是依據(jù)哪部分區(qū)域做出判斷的老翘。

假設(shè)初始模型在剛開始訓(xùn)練時,利用某個特征圖作為判斷的依據(jù)卫键,并計(jì)算正確率莉炉,此時損失值loss很大钓账,則在反向傳播過程中,權(quán)重矩陣W會不斷更新絮宁,在損失函數(shù)約束下梆暮,找到有效的特征圖作為判斷的依據(jù),那么當(dāng)loss小到一定程度绍昂,或預(yù)測的準(zhǔn)確率上升到一定程度啦粹,那么此時的模型便學(xué)會了判別,有了正確分類的能力唠椭。



計(jì)算方法如下圖所示敌厘。對于一個CNN模型,對其最后一個feature map做全局平均池化(GAP)計(jì)算各通道均值活合,然后通過全連接層等映射到class score,找出argmax隆判,計(jì)算最大的那一類的輸出相對于最后一個feature map的梯度,再把這個梯度可視化到原圖上即可照筑。直觀來說冬念,就是看一下網(wǎng)絡(luò)抽取到的高層特征的哪部分對最終的classifier影響更大。


GAP操作后輸出最后一個卷積層每個單元feature-map的平均值,之后再接一個softmax層用于分類色乾,而該層的所有參數(shù)作為權(quán)重wck澎办,對前方的GAP得到的feature-map做加權(quán)總和得到最后的輸出县貌,即CAM輸出楞泼。此時CAM的輸出尺寸和feature-map大小一致,故需要通過上采樣方式還原疊加到原圖中。

CAM的可視化是通過fk激活值實(shí)現(xiàn)的,激活值越大的地方說明該區(qū)域越有可能屬于對應(yīng)某個分類,通過改變圖像尺寸,將激活圖還原成原圖大小的圖片校套,即可得到該分類對應(yīng)在原圖的位置价脾,加權(quán)越多的區(qū)域顏色亮度越大,在通過設(shè)置閾值即可畫出覆蓋該區(qū)域的BBox笛匙,從而得到物體在圖片中的定位邊框侨把。


該方法的缺點(diǎn)是只能適用于最后一層特征圖和全連接之間是GAP操作。如果不是妹孙,就需要用戶修改網(wǎng)絡(luò)并重新訓(xùn)練(或 fine-tune)秋柄。修改網(wǎng)絡(luò)全連接為GAP形式,利用GAP層與全連接的權(quán)重作為特征融合權(quán)重蠢正,對特征圖進(jìn)行線性融合獲取CAM骇笔。

CAM 的實(shí)現(xiàn)依賴于全局平均池化層,通過全局平均池化得到 feature map 每一個通道的權(quán)重嚣崭,然后線性加權(quán)求和得到網(wǎng)絡(luò)關(guān)注區(qū)域的熱力圖笨触。因此對于很多網(wǎng)絡(luò)都不能直接使用,需要把網(wǎng)絡(luò)后面的全連接層改為全局平均池化雹舀。CAM雖然簡單芦劣,但是它要求修改原模型的結(jié)構(gòu),導(dǎo)致需要重新訓(xùn)練該模型说榆,這大大限制了它的使用場景虚吟。如果模型已經(jīng)上線了寸认,或著訓(xùn)練的成本非常高,我們幾乎是不可能為了它重新訓(xùn)練的串慰。

4. 代碼實(shí)現(xiàn)

fromPILimportImagefromtorchvisionimportmodels,transformsfromtorch.autogradimportVariablefromtorch.nnimportfunctionalasFimportnumpyasnpimportcv2importjsonLABELS_URL='https://s3.amazonaws.com/outcome-blog/imagenet/labels.json'# 下載label# 使用本地的圖片和下載到本地的labels.json文件LABELS_PATH="labels.json"# networks such as googlenet, resnet, densenet already use global average pooling at the end, so CAM could be used directly.model_id=2# 選擇使用的網(wǎng)絡(luò)ifmodel_id==1:net=models.squeezenet1_1(pretrained=True)finalconv_name='features'# this is the last conv layer of the networkelifmodel_id==2:net=models.resnet18(pretrained=True)finalconv_name='layer4'elifmodel_id==3:net=models.densenet161(pretrained=True)finalconv_name='features'net.eval()print(net)# 獲取特定層的feature map# hook the feature extractorfeatures_blobs=[]defhook_feature(module,input,output):# input是注冊層的輸入 output是注冊層的輸出print("hook input",input[0].shape)features_blobs.append(output.data.cpu().numpy())# 對layer4層注冊偏塞,把layer4層的輸出append到features里面net._modules.get(finalconv_name).register_forward_hook(hook_feature)# 注冊到finalconv_name,如果執(zhí)行net()的時候,# 會對注冊的鉤子也執(zhí)行邦鲫,這里就是執(zhí)行了 layer4()print(net._modules)# 這里是利用鉤子函數(shù)來獲取最后的feature map灸叼,# _modules.get()方法會返回指定層的網(wǎng)絡(luò)結(jié)構(gòu),即layer4的結(jié)構(gòu)掂碱,# 然后使用register_forward_hook在前向傳播的過程中為layer4注冊hook函數(shù)怜姿,# 接下來會在前向傳播的過程中截取layer4的輸入和輸出,# 在hook_feature函數(shù)中實(shí)現(xiàn)對于輸入和輸出的操作疼燥。# 得到softmax weightparams=list(net.parameters())# 將參數(shù)變換為列表 按照weights bias 排列 池化無參數(shù)weight_softmax=np.squeeze(params[-2].data.numpy())# 提取softmax 層的參數(shù) (weights,-1是bias)# 打印一下網(wǎng)絡(luò)的參數(shù)蚁堤,可以得到網(wǎng)絡(luò)的最后兩個參數(shù)是fc.weight和fc.biasforname,datainnet.named_parameters():print(name,":",data.size())# 生成CAM圖的函數(shù)醉者,完成權(quán)重和feature相乘操作,最后resize成上采樣defreturnCAM(feature_conv,weight_softmax,class_idx):# generate the class activation maps upsample to 256x256size_upsample=(256,256)bz,nc,h,w=feature_conv.shape# 獲取feature_conv特征的尺寸output_cam=[]# class_idx為預(yù)測分值較大的類別的數(shù)字表示的數(shù)組披诗,一張圖片中有N類物體則數(shù)組中N個元素foridxinclass_idx:# weight_softmax中預(yù)測為第idx類的參數(shù)w乘以feature_map(為了相乘撬即,故reshape了map的形狀)# w1*c1 + w2*c2+ .. -> (w1,w2..) * (c1,c2..)^T -> (w1,w2...)*((c11,c12,c13..),(c21,c22,c23..))# weight_softmax[idx]的含義:我們之前取到的fc.weight是一個(1000,512)的矩陣,# 因?yàn)閷τ贗mageNet來說是一個1000分類的問題呈队,那么weight_softmax的第i行就代表第i類的權(quán)重剥槐,# 傳入的第三個參數(shù)class_idx是一個列表,里面有若干值,# 代表我們想得到對于一張圖片來說被分成不同的類映射回原圖分別是原圖的哪一部分在起作用宪摧。# 對于最后的特征圖做一個reshape粒竖,feature_conv.reshape((nc, h*w)將特征圖變成每一行代表原特征圖的一個通道,# 然后對于權(quán)重和reshape之后的圖像做一個矩陣乘法就完成了對于feature map每個加權(quán)通道的求和几于。cam=weight_softmax[idx].dot(feature_conv.reshape((nc,h*w)))# 把原來的相乘再相加轉(zhuǎn)化為矩陣# 將feature_map的形狀reshape回去cam=cam.reshape(h,w)# 歸一化操作(最小的值為0蕊苗,最大的為1)cam=cam-np.min(cam)cam_img=cam/np.max(cam)# 轉(zhuǎn)換為圖片的255的數(shù)據(jù)cam_img=np.uint8(255*cam_img)# resize 圖片尺寸與輸入圖片一致output_cam.append(cv2.resize(cam_img,size_upsample))returnoutput_cam# 數(shù)據(jù)處理,先縮放尺寸到(256*256)沿彭,再變換數(shù)據(jù)類型為tensor,最后normalizenormalize=transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])preprocess=transforms.Compose([transforms.Resize((256,256)),transforms.ToTensor(),normalize])img_pil=Image.open('2103.jpg')img_tensor=preprocess(img_pil)# 處理圖片為Variable數(shù)據(jù)img_variable=Variable(img_tensor.unsqueeze(0))# 將圖片輸入網(wǎng)絡(luò)得到預(yù)測類別分值logit=net(img_variable)# 使用本地的 LABELS_PATHwithopen(LABELS_PATH)asf:data=json.load(f).items()classes={int(key):valuefor(key,value)indata}# 使用softmax打分h_x=F.softmax(logit,dim=1).data.squeeze()# 分類分值# 對分類的預(yù)測類別分值排序朽砰,輸出預(yù)測值和在列表中的位置probs,idx=h_x.sort(0,True)# 轉(zhuǎn)換數(shù)據(jù)類型probs=probs.numpy()idx=idx.numpy()# output the predictionforiinrange(0,5):print('{:.3f} -> {}'.format(probs[i],classes[idx[i]]))# generate class activation mapping for the top1 predictionCAMs=returnCAM(features_blobs[0],weight_softmax,[idx[0]])# render the CAM and outputprint('output CAM.jpg for the top1 prediction: %s'%classes[idx[0]])img=cv2.imread('2103.jpg')height,width,_=img.shapeheatmap=cv2.applyColorMap(cv2.resize(CAMs[0],(width,height)),cv2.COLORMAP_JET)result=heatmap*0.3+img*0.5cv2.imwrite('CAM.jpg',result)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市喉刘,隨后出現(xiàn)的幾起案子瞧柔,更是在濱河造成了極大的恐慌,老刑警劉巖睦裳,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件造锅,死亡現(xiàn)場離奇詭異,居然都是意外死亡推沸,警方通過查閱死者的電腦和手機(jī)备绽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門券坞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肺素,你說我怎么就攤上這事恨锚。” “怎么了倍靡?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵猴伶,是天一觀的道長。 經(jīng)常有香客問我塌西,道長他挎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任捡需,我火速辦了婚禮办桨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘站辉。我一直安慰自己呢撞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布饰剥。 她就那樣靜靜地躺著殊霞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汰蓉。 梳的紋絲不亂的頭發(fā)上绷蹲,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機(jī)與錄音顾孽,去河邊找鬼祝钢。 笑死,一個胖子當(dāng)著我的面吹牛岩齿,可吹牛的內(nèi)容都是我干的太颤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼盹沈,長吁一口氣:“原來是場噩夢啊……” “哼龄章!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乞封,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤做裙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后肃晚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锚贱,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年关串,在試婚紗的時候發(fā)現(xiàn)自己被綠了拧廊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片监徘。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖吧碾,靈堂內(nèi)的尸體忽然破棺而出凰盔,到底是詐尸還是另有隱情,我是刑警寧澤倦春,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布户敬,位于F島的核電站,受9級特大地震影響睁本,放射性物質(zhì)發(fā)生泄漏尿庐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一呢堰、第九天 我趴在偏房一處隱蔽的房頂上張望抄瑟。 院中可真熱鬧,春花似錦枉疼、人聲如沸锐借。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至严卖,卻和暖如春席舍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哮笆。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工来颤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人稠肘。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓福铅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親项阴。 傳聞我的和親對象是個殘疾皇子滑黔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355