天天看點

CNN基礎論文 精讀+複現----ZFnet(二)

文章目錄

  • ​​第5頁​​
  • ​​對Alex的改造​​
  • ​​遮擋敏感度​​
  • ​​圖像的局部相關性分析​​
  • ​​第6頁​​
  • ​​8-10頁​​
  • ​​代碼實作​​
  • ​​feature map可視化​​
  • ​​總結​​
  • ​​完整代碼:​​

上一篇: ​​CNN基礎論文 精讀+複現----ZFnet(一)​​

第5頁

對Alex的改造

這裡的第四章介紹了一些作者對Alex的改造過程,作者可視化了Alex的1,2層,發現有一些卷積核有極高和極低的資訊混合,沒有中頻資訊,第二層發現一些卷積核 因為步長太長出現一些混淆的網格特征,這些都稱為無效卷積核。

是以作者将Alex裡的11 * 11 的卷積核變成了 7 * 7 的。步長 從4降低到2.

第一層改造 實作效果作者給在論文第7頁。

上面是改造前的Alex,下面是改造之後的,可以看出來改造之前的卷積核裡有很多無效卷積核(基本一塊灰或着一大塊的純色,沒有有效的特征)。而改造之後明顯大幅改善了這個問題(比如第三列的那幾個)。

CNN基礎論文 精讀+複現----ZFnet(二)

第二層改造的效果圖:

左邊是改造之前的 ,右邊是改造之後。

可以看到前面說的 因步長太大而出現的網格化無效卷積核(左圖點開放大之後明顯看到網格化的一塊黑一塊白,第二塊和最後一排的第三塊),在改造之後都得到了解決。

CNN基礎論文 精讀+複現----ZFnet(二)

遮擋敏感度

4.2節中有個遮擋敏感度的方法 也給了一張圖 放到了論文第七頁。

說明一下這張圖:

第一列就是輸入的圖像,可以看到輸入的圖像裡有一個灰色的小方塊,這個灰色的小方塊就是遮擋的部分(訓練時灰色小方塊會不斷移動)。

第二列是經過整個網絡都輸出的特征部位,某一塊被遮住時,其圖檔的feature map 越紅說明越大,藍色說明越小,通俗的講 即越紅說明特征越無關,越藍說明特征越相關。

第三列就是通過反卷積操作之後的映射圖,其中黑框的部分表示不經任何遮擋所得到的映射圖(最大的feature map對應圖),其餘三個是從原始資料中找到另外三個同樣使得最大的feature map然後反卷積映射回原始空間的圖。

第四列就是遮擋不同部位時識别出來的機率,比如第一行,遮擋住狗臉圖檔顯示藍色,說明基本識别不出來是狗了。

第五列看左上角的小分類,顔色對應可以看出來識别成什麼東西了。

CNN基礎論文 精讀+複現----ZFnet(二)

圖像的局部相關性分析

這小節提到一個問題就是現存的深度學習模型沒有顯示的定義出圖檔中各部分的關系。

之後給了一張圖:

這裡有五種狗,然後每一列就是 遮左眼 右眼 嘴巴 耳朵 後面四列是随機遮。

作者通過海明距離等一堆計算得到一個結論: 狗的眼睛、鼻子是被隐式地定義在了模型中,且再次驗證了之前的結論,網絡層數越深特征不變性越強(淺層關注空間資訊,深層關注語義)。

CNN基礎論文 精讀+複現----ZFnet(二)

第6頁

第六頁裡有一張圖:

簡單說一下,可以在下标看到,這裡是5個層的演變。每一行就是訓練的 feature map ,每一列就是訓練的輪次。

圖中的元素和上面的一樣,都是反卷積映射到輸入空間後可視化出來的

仔細看原論文的圖可以發現,在第一層第二層的收斂速度是比較快的。

CNN基礎論文 精讀+複現----ZFnet(二)

可以看第一層和第二層的橫向,收斂速度是比較快的,

CNN基礎論文 精讀+複現----ZFnet(二)

到了第四層,有個明顯突變的地方,這裡就代表 使該卷積核的Feature Map中的最大數值改變了。

CNN基礎論文 精讀+複現----ZFnet(二)

得到兩個結論:

  • 淺層收斂快。
  • 深層收斂慢。

原文又給了一個圖:

這張圖第一行 為平移,第二行縮放,第三行旋轉。

後面網格第一列是第1層後,第二列是第7層後,第三列是正确的機率。

CNN基礎論文 精讀+複現----ZFnet(二)

總結下來就兩點:

  • 輸入的變換對底層影響顯著,對高層影響較小。
  • 整體網絡對平移和縮放不太敏感,對旋轉敏感,除非旋轉到了對稱位置(90°C)。

這也驗證了開始的結論,底層關注空間資訊,高層關注語義資訊。

8-10頁

第五章,就是結果分析了。

5.1小節 總結下來幾點:

  • 僅去掉網絡中第六、七層(FC層),錯誤率會稍微減少。對之後網絡改進的影響有:将FC層變為GAP層,可以提升網絡性能的同時減少參數量防止過拟合。
  • 僅去掉兩個中間的卷積層,對網絡性能影響不大。
  • 将上述兩個實驗中的層都去掉,網絡性能變差,得出結論:網絡深度對性能影響是正向的。
  • 改變FC層中神經元的個數,對網絡性能影響不大。
  • 增加卷積層中卷積核個數,使得網絡性能變好,但是這會擴大FC層中的參數量最終導緻過拟合。

5.2說了一下他的 泛化 遷移學習能力很不錯(全部層不變,隻變化最後的softmax分類層) 。

即用imagenet資料集訓練,然後改變softmax層,再換資料集進行驗證(這裡不再次訓練,隻用iamgenet訓練好的資料進行驗證),發現效果很好(泛化能力)。

最後用5折交叉驗證進行評估。

關于5折交叉驗證:

将資料劃分為(大緻)相等的5部分,使用第1折作為測試集,其他折(2-5)作為訓練集,得到一個精度,依次,使用第2折作為測試集,其他折(1、3、4、5)作為訓練集,共得到5個精度,取平均值即得到模型精度,這樣得到的模型精度更準确。

代碼實作

作者對Alex的改造:

第一層卷積核大小由11×11→7×7,第一、二層卷積核步長改變為2。

是以直接拿過來Alex那套代碼過來改一改前兩層就行了。

原來的Alex:

nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4,padding=2),
nn.ReLU(True),
nn.LocalResponseNorm(size=96,alpha=0.0001,beta=0.75,k=2),
nn.MaxPool2d(kernel_size=3,stride=2),


nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=1,padding=2),
nn.ReLU(True),
nn.LocalResponseNorm(size=256,alpha=0.0001,beta=0.75,k=2),
nn.MaxPool2d(kernel_size=3,stride=2),      

改為:

nn.Conv2d(in_channels=3, out_channels=96, kernel_size=7, stride=2,padding=0),
nn.ReLU(True),
nn.MaxPool2d(kernel_size=3,stride=2),


nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=2,padding=1),
nn.ReLU(True),
nn.MaxPool2d(kernel_size=3,stride=2),      

其他代碼不變。

可以看到我去掉了LRN層,VGG不是把LRN層推翻了嘛,是以我這裡省事直接就給她去掉了。實際上ZFnet論文裡并沒有說明對LRN的處理(也有可能說了我沒看到?懶得回去翻了),如果完全性複現的話,應該加上LRN層的把。

feature map可視化

使用改造之後的Alex代碼 再加上中間層的 feature map可視化就可以了。

這裡是使用了 pytorch 的hook鈎子函數 使用tensorboard顯示。

def hook_func(module, input):
    x = input[0][0]
    # print(x.shape)
    x = x.unsqueeze(1)
    # print(x.shape)
    global i
    image_batch = torchvision.utils.make_grid(x, padding=4)  # 将若幹張圖檔拼成一張圖, padding在這裡是這些圖之間的間隔
    image_batch = image_batch.numpy().transpose(1, 2, 0) # C H W ->  H W C
    writer.add_image("feature_map", image_batch, i, dataformats='HWC')
    # image_batch y軸資料   i是X軸資料
    i += 1      

​image_batch = image_batch.numpy().transpose(1, 2, 0)​

轉換成 numpy類型,次元變化。 C H W -> H W C。

簡單解釋一下 原來的 C為0 H為1 W為2。現在使用transpose函數 變成了1 2 0 自然就是 H W C了。

然後再訓練的時候加上下面的代碼:

writer = SummaryWriter("./logs")
        for name, m in model.named_modules():
            if isinstance(m, torch.nn.Conv2d):
                m.register_forward_pre_hook(hook_func)      

即 隻寫入卷積層的 feature map。

我用minist資料集跑了一邊,

可以看到中間層提出來的 feature map:

CNN基礎論文 精讀+複現----ZFnet(二)

總結

這篇論文可以說是神經網絡可視化的鼻祖,我覺得最大的貢獻就是打破了神經網絡過程的黑箱了吧。使用 反池化 -> 反激活 ->反卷積 的操作重制了原始輸入圖,進而改進之前黑箱的Alex網絡。還有使用敏感性遮擋的方法對模型進行評估等。

剛開始還沒接觸這篇論文的時候,看到簡介我還想着反卷積這個東西沒聽說過應該很難把,學完了覺得 還好吧(也可能我沒學精哈哈)。

完整代碼:

import torch
import torchvision
from torch import optim
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.utils.tensorboard import SummaryWriter
import cv2



batch_size = 4
transform = transforms.Compose([
    transforms.Resize(224),
    transforms.ToTensor (),
    transforms.Normalize((0.4915, 0.4823, 0.4468,), (1.0, 1.0, 1.0)),
])

train_dataset = datasets.CIFAR10(root='../data/', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size)
test_dataset = datasets.CIFAR10(root='../data/', train=False, download=True, transform=transform)
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)

print("訓練集長度",len(train_dataset))
print("測試集長度",len(test_dataset))

# 模型類設計

class ZFnet(nn.Module):
    def __init__(self):
        super(ZFnet, self).__init__()
        self.mode1 = nn.Sequential(

            nn.Conv2d(in_channels=3, out_channels=96, kernel_size=7, stride=2,padding=0),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=3,stride=2),


            nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=2,padding=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=3,stride=2),

            nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, stride=1,padding=1),
            nn.ReLU(True),
            nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1,padding=1),
            nn.ReLU(True),
            nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, stride=1,padding=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=3,stride=2),

            nn.Flatten(),
            nn.Linear(in_features=6*6*256, out_features=2048),
            nn.ReLU(True),
            nn.Dropout2d(p=0.5),
            nn.Linear(in_features=2048, out_features=2048),
            nn.ReLU(True),
            nn.Dropout2d(p=0.5),
            nn.Linear(in_features=2048, out_features=1000),

        )

    def forward(self, input):

        x = self.mode1(input)
        return x
# 鈎子函數,可視化Feature map
def hook_func(module, input):
    x = input[0][0]
    # print(x.shape)
    x = x.unsqueeze(1)
    # print(x.shape)
    global i
    image_batch = torchvision.utils.make_grid(x, padding=4)  # 将若幹張圖檔拼成一張圖, padding在這裡是這些圖之間的間隔
    image_batch = image_batch.numpy().transpose(1, 2, 0) # C H W ->  H W C
    writer.add_image("feature_map", image_batch, i, dataformats='HWC')
    # image_batch y軸資料   i是X軸資料
    i += 1



model = ZFnet().cuda()
# 損失函數
criterion = torch.nn.CrossEntropyLoss().cuda()
# 優化器
optimizer = optim.SGD(model.parameters(),lr=0.01,weight_decay=0.0005,momentum=0.9)


def train(epoch):
    runing_loss = 0.0
    i = 1
    for i, data in enumerate(train_loader):
        x, y = data
        x, y = x.cuda(), y.cuda()
        i +=1
        if i % 10 == 0:
            print("運作中,目前運作次數:",i)
        # 清零 正向傳播  損失函數  反向傳播 更新
        optimizer.zero_grad()
        y_pre = model(x)
        loss = criterion(y_pre, y)
        loss.backward()
        optimizer.step()
        runing_loss += loss.item()
    # 每輪訓練一共訓練1W個樣本,這裡的runing_loss是1W個樣本的總損失值,要看每一個樣本的平均損失值, 記得除10000

    print("這是第 %d輪訓練,目前損失值 %.5f" % (epoch + 1, runing_loss / 782))

    return runing_loss / 782

def test(epoch):
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            x, y = data
            x, y = x.cuda(), y.cuda()
            pre_y = model(x)
            # 這裡拿到的預測值 每一行都對應10個分類,這10個分類都有對應的機率,
            # 我們要拿到最大的那個機率和其對應的下标。

            j, pre_y = torch.max(pre_y.data, dim=1)  # dim = 1 列是第0個次元,行是第1個次元

            total += y.size(0)  # 統計方向0上的元素個數 即樣本個數

            correct += (pre_y == y).sum().item()  # 張量之間的比較運算
    print("第%d輪測試結束,目前正确率:%d %%" % (epoch + 1, correct / total * 100))
    return correct / total * 100
if __name__ == '__main__':
    writer = SummaryWriter("./logs")
    plt_epoch = []
    loss_ll = []
    corr = []
    for epoch in range(1):
        plt_epoch.append(epoch+1) # 友善繪圖
        loss_ll.append(train(epoch)) # 記錄每一次的訓練損失值 友善繪圖
        corr.append(test(epoch)) # 記錄每一次的正确率

        for name, m in model.named_modules():
            if isinstance(m, torch.nn.Conv2d):
                m.register_forward_pre_hook(hook_func)


    plt.rcParams['font.sans-serif'] = ['KaiTi']
    plt.figure(figsize=(12,6))
    plt.subplot(1,2,1)
    plt.title("訓練模型")
    plt.plot(plt_epoch,loss_ll)
    plt.xlabel("循環次數")
    plt.ylabel("損失值loss")


    plt.subplot(1,2,2)
    plt.title("測試模型")
    plt.plot(plt_epoch,corr)
    plt.xlabel("循環次數")
    plt.ylabel("正确率")
    plt.show()      

繼續閱讀