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(二)_第1张图片

第二层改造的效果图:

左边是改造之前的 ,右边是改造之后。

可以看到前面说的 因步长太大而出现的网格化无效卷积核(左图点开放大之后明显看到网格化的一块黑一块白,第二块和最后一排的第三块),在改造之后都得到了解决。
CNN基础论文 精读+复现----ZFnet(二)_第2张图片

遮挡敏感度

4.2节中有个遮挡敏感度的方法 也给了一张图 放到了论文第七页。

说明一下这张图:

第一列就是输入的图像,可以看到输入的图像里有一个灰色的小方块,这个灰色的小方块就是遮挡的部分(训练时灰色小方块会不断移动)。

第二列是经过整个网络都输出的特征部位,某一块被遮住时,其图片的feature map 越红说明越大,蓝色说明越小,通俗的讲 即越红说明特征越无关,越蓝说明特征越相关。

第三列就是通过反卷积操作之后的映射图,其中黑框的部分表示不经任何遮挡所得到的映射图(最大的feature map对应图),其余三个是从原始数据中找到另外三个同样使得最大的feature map然后反卷积映射回原始空间的图。

第四列就是遮挡不同部位时识别出来的概率,比如第一行,遮挡住狗脸图片显示蓝色,说明基本识别不出来是狗了。

第五列看左上角的小分类,颜色对应可以看出来识别成什么东西了。
CNN基础论文 精读+复现----ZFnet(二)_第3张图片

图像的局部相关性分析

这小节提到一个问题就是现存的深度学习模型没有显示的定义出图片中各部分的关系。

之后给了一张图:

这里有五种狗,然后每一列就是 遮左眼 右眼 嘴巴 耳朵 后面四列是随机遮。

作者通过海明距离等一堆计算得到一个结论: 狗的眼睛、鼻子是被隐式地定义在了模型中,且再次验证了之前的结论,网络层数越深特征不变性越强(浅层关注空间信息,深层关注语义)。
CNN基础论文 精读+复现----ZFnet(二)_第4张图片

第6页

第六页里有一张图:
简单说一下,可以在下标看到,这里是5个层的演变。每一行就是训练的 feature map ,每一列就是训练的轮次。

图中的元素和上面的一样,都是反卷积映射到输入空间后可视化出来的

仔细看原论文的图可以发现,在第一层第二层的收敛速度是比较快的。

CNN基础论文 精读+复现----ZFnet(二)_第5张图片

可以看第一层和第二层的横向,收敛速度是比较快的,CNN基础论文 精读+复现----ZFnet(二)_第6张图片

到了第四层,有个明显突变的地方,这里就代表 使该卷积核的Feature Map中的最大数值改变了。

CNN基础论文 精读+复现----ZFnet(二)_第7张图片

得到两个结论:

  • 浅层收敛快。
  • 深层收敛慢。

原文又给了一个图:

这张图第一行 为平移,第二行缩放,第三行旋转。
后面网格第一列是第1层后,第二列是第7层后,第三列是正确的概率。

总结下来就两点:

  • 输入的变换对底层影响显著,对高层影响较小。
  • 整体网络对平移和缩放不太敏感,对旋转敏感,除非旋转到了对称位置(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(二)_第8张图片

总结

这篇论文可以说是神经网络可视化的鼻祖,我觉得最大的贡献就是打破了神经网络过程的黑箱了吧。使用 反池化 -> 反激活 ->反卷积 的操作重现了原始输入图,进而改进之前黑箱的Alex网络。还有使用敏感性遮挡的方法对模型进行评估等。

刚开始还没接触这篇论文的时候,看到简介我还想着反卷积这个东西没听说过应该很难把,学完了觉得 还好吧(也可能我没学精哈哈)。

可视化代码部分还有一点问题,就是 hook+tensorboard 我也是刚学这个所以很多地方还没搞明白,等后面学会了再回来完善。

后面应该会搞一下NiN或者GoogleNet。

完整代码:

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()



你可能感兴趣的:(个人笔记,论文精读+复现,深度学习,人工智能,python)