图像分类(二) 全面解读复现ZFNet

网络详解

前言:ZF网络是2013年提出的,网上有很多关于它的介绍和讲解,但是很多内容讲的不太好(个人感觉),于是花时间收集了一些资料,整理了一些比较好的文章,从头到尾把ZFNet说了一遍。

ZFNet简介

1.1 为什么起名ZFnetwork

​ ILSVRC 2013获胜者是来自Matthew Zeiler和Rob Fergus的卷积网络。它被称为ZFNet(Zeiler&Fergus Net的简称)。这是对AlexNet的改进,通过调整架构超参数,特别是通过扩展中间卷积层的大小。

​ ZFNet是用人的名字命名的,那他到底出自哪篇论文呢?它出自Matthew D.Zeiler 和 Rob Fergus (纽约大学)2013年撰写的论文: 《Visualizing and Understanding Convolutional Networks》,转讲解“卷积神经网络的理解和可视化”

1.2 ILSVRC历届冠军网络介绍以及CNN的大致演化过程

img

img

1.3 ZFNet最大研究意义

ZFNet是在AlexNet基础上进行了一些细节的改动,网络结构上并没有太大的突破,只是在卷积核和步幅上面做了一些改动。

按照以前的观点,一个卷积神经网络的好坏我们只能通过不断地训练去判断,我也没有办法知道每一次卷积、每一次池化、每一次经过激活函数到底发生了什么,也不知道神经网络为什么取得了如此好的效果,那么只能靠不停的实验来寻找更好的模型。ZFNet所做的工作不少,但是最核心的其实就是一个。

**核心意义:**通过使用可视化技术揭示了神经网络各层到底在干什么,起到了什么作用。一旦知道了这些,如何调整我们的神经网络,往什么方向优化,就有了较好的依据。

主要工作:

(1)使用一个多层的反卷积网络来可视化训练过程中特征的演化及发现潜在的问题;

(2)同时根据遮挡图像局部对分类结果的影响来探讨对分类任务而言到底那部分输入信息更重要。

​ 总而言之,《Visualizing and Understanding Convolutional Networks》,可以说是CNN领域可视化理解的开山之作,这篇文献告诉我们CNN的每一层到底学习到了什么特征,然后作者通过可视化进行调整网络,提高了精度。最近两年深层的卷积神经网络,进展非常惊人,在计算机视觉方面,识别精度不断的突破,CVPR上的关于CNN的文献一大堆。然而很多学者都不明白,为什么通过某种调参、改动网络结构等,精度会提高。可能某一天,我们搞CNN某个项目任务的时候,你调整了某个参数,结果精度飙升,但如果别人问你,为什么这样调参精度会飙升呢,你所设计的CNN到底学习到了什么牛逼的特征?

​ 这篇文献的目的,就是要通过特征可视化,告诉我们如何通过可视化的角度,查看你的精度确实提高了,你设计CNN学习到的特征确实比较牛逼。

1.4 论文研究的贡献
(1)特征可视化
使用反卷积、反池化、反激活函数去反可视化feature map ,通过feature map可以看出,特征分层次体系结构 ,以及我们可以知道前面的层学习的是物理轮廓、边缘、颜色、纹理等特征,后面的层学习的是和类别相关的抽象特征。这一个非常重要的结论,我们后面很多的微调手段都是基于此理论成果。再次强调这个结论:

**结论一:**CNN网络前面的层学习的是物理轮廓、边缘、颜色、纹理等特征,后面的层学习的是和类别相关的抽象特征

**结论二:**CNN学习到的特征具有平移和缩放不变性,但是,没有旋转不变性

(2)特征提取的通用性
作者通过实验说明了,将使用ImageNet2012数据集训练得到的CNN,在保持前面七层不变的情况,只在小数据集上面重新训练softmax层,通过这样的实验,说明了使用ImageNet2012数据集训练得到的CNN的特征提取功能就有通用性。

**结论三:**CNN网络的特征提取具有通用性,这是后面微调的理论支持

(3)对于遮挡的敏感性
通过遮挡,找出了决定图像类别的关键部位当,在输入图片中遮挡了学习到的特征时,分类效果会很差。同时根据遮挡图像局部对分类结果的影响来探讨对分类任务而言到底那部分输入信息更重要。

(4)特征的层次分析
作者通过实验证明了,不同深度的网络层学习到的特征对于分类的作用,说明了深度结构确实能够获得比浅层结构更好的效果。 通过实验,说明了深度增加时,网络可以学习到更具区分的特征。 底层特征在迭代次数比较少时就能收敛,高层需要的迭代次数比较多 越是高层的特征,其用来做分类的性能越好

(5)对于CNN结构的改进

(6)特征结构选择

作者通过可视化AlexNet第一层和第二层的特征,发现比较大的stride和卷积核提取的特征不理想,所以作者将第一层的卷积核从1111减小到77,将stride从4减小到2,实验说明,这样有助于分类性能的提升。

​ 这个改进方案是建立在(1)的基础之上的,上述结论中的(2)、(3)、(4)、(5)、(6)均是建立在(1)的基础之上的,因为我们知道了网络各个层到底发生了什么,自然也就知道了优化的方向。
​ 通过特征可视化可以知道,Krizhevsky的CNN结构学习到的第一层特征只对于高频和低频信息有了收敛,但是对于中层信息却还没有收敛;同时,第二层特征出现了混叠失真,这个主要是因为第一个卷积层的层的步长设置为4引起的,为了解决这个问题,作者不仅将第一层的卷积核的大小设置为7*7,同时,也将步长设置为2(对AlexNet的改进)

ZFNet所涉及的几个核心原理

其实上面所罗列出来的5个贡献,不管是对于遮挡的敏感性、特征的层析分析、还是特征提取的通用性,都是建立在“特征可视化”的基础之上的,因此特征可视化就是论文的核心所在了,要想能够看到每一个卷积层之后,到底提取到了原始图像什么样的特征,则需要经过几个核心步骤,反池化、反激活、反卷积。反卷积可视化以各层得到的特征图作为输入,进行反卷积,得到反卷积结果,用以验证显示各层提取到的特征图。举个例子:假如你想要查看Alexnet 的conv5提取到了什么东西,我们就用conv5的特征图后面接一个反卷积网络,然后通过:反池化、反激活、反卷积,这样的一个过程,把本来一张1313大小的特征图(conv5大小为1313),放大回去,最后得到一张与原始输入图片一样大小的图片(227*227)。

下面就来看一下实现feature map可视化的几个核心思想。

2.1 反池化过程
我们知道,池化是不可逆的过程,然而我们可以通过记录池化过程中,最大激活值得坐标位置。然后在反池化的时候,只把池化过程中最大激活值所在的位置坐标的值激活,其它的值置为0,当然这个过程只是一种近似,因为我们在池化的过程中,除了最大值所在的位置,其它的值也并不是为0的。刚好最近几天看到文献:《Stacked What-Where Auto-encoders》,里面有个反卷积示意图画的比较好,所有就截下图,用这篇文献的示意图进行讲解:

img
以上面的图片为例,上面的图片中左边表示pooling过程,右边表示unpooling过程。假设我们pooling块的大小是33,采用max pooling后,我们可以得到一个输出神经元其激活值为9,pooling是一个下采样的过程,本来是33大小,经过pooling后,就变成了11大小的图片了。而upooling刚好与pooling过程相反,它是一个上采样的过程,是pooling的一个反向运算,当我们由一个神经元要扩展到33个神经元的时候,我们需要借助于pooling过程中,记录下最大值所在的位置坐标(0,1),然后在unpooling过程的时候,就把(0,1)这个像素点的位置填上去,其它的神经元激活值全部为0。

再来一个例子:在max pooling的时候,我们不仅要得到最大值,同时还要记录下最大值得坐标(-1,-1),然后再unpooling的时候,就直接把(-1-1)这个点的值填上去,其它的激活值全部为0。

当然了,实际上在池化之前,除了(0,1)位置上的那个9以外,其他地方的数字并不是0,所以反池化并没有真正意义上的完全还原,只是一种近似而已。

2.2 反激活

我们在Alexnet中,relu函数是用于保证每层输出的激活值都是正数,因此对于反向过程,我们同样需要保证每层的特征图为正值,也就是说这个反激活过程和激活过程没有什么差别,都是直接采用relu函数。这里需要;了解什么是relu激活函数,当然这也只是一种近似,只是保证了每一个经过激活函数的输出值始终为正值。

2. 3 反卷积

​ 对于反卷积过程,采用卷积过程转置后的滤波器(参数一样,只不过把参数矩阵水平和垂直方向翻转了一下),反卷积实际上应该叫卷积转置

​ 这里我打算下一篇博客仔细讲一下卷积转置。最后可视化网络结构如下:

img

​ 网络的整个过程,从右边开始:输入图片-》卷积-》Relu-》最大池化-》得到结果特征图-》反池化-》Relu-》反卷积。到了这边,可以说我们的算法已经学习完毕了,其实很好理解,我们可以用两个过程来描述:

**过程一:**特征提取过程,输入图像-》卷积-》Relu激活-》最大化池化-》feature map

**过程二:**特征还原过程,feature map-》反池化-》反Relu激活-》反卷积-》可视化(原始)图像

2.4 ZFNet网络的结构

上面2.3中的图片并不是ZFNet网络的结构,上面的2.3是进行特征可视化的步骤图,特征可视化的目的就是为了改进原来的网络,适当调整原来的CNN网络(论文中是适当调整AlexNet)。原来的ZFNet是下图所示:

img

从上面可以看出,ZFNet相较于AlexNet的改进主要在两点:

(1)第一个卷积层的大小从11*11改为了7*7,步长从4改为了2.

​ 作出这样调整的依据就是通过特征图可视化来实现的,作者通过可视化AlexNet第一层和第二层的特征,发现比较大的stride和卷积核提取的特征不理想,所以作者将第一层的卷积核从1111减小到77,实验说明,这样有助于分类性能的提升。另外通过特征可视化可以知道,Krizhevsky的CNN结构学习到的第一层特征只对于高频和低频信息有了收敛,但是对于中层信息却还没有收敛;同时,第二层特征出现了混叠失真,这个主要是因为第一个卷积层的层的步长设置为4引起的,为了解决这个问题,作者不仅将第一层的卷积核的大小设置为7*7,同时,也将步长设置为2(对AlexNet的改进)。

(2)将3、4、5层卷积核的数量由384、384、256调整为512、1024、512,

**总结:**从上面的这个网络改进过程可以发现,虽然在设计卷积核的大小、步长、卷积核的数量等方面都是认为自己设置的,依然还带有一定的盲目性,需要多次试验寻求最佳的设计,但是相较于之前已经有了一些进步,因为我至少可以通过feature map的可视化分析一下大致的原因,找寻一个大致的优化方向,是该调整大一点还是小一点呢?虽然不能很快确定,但是有了一定的参考依据。

原始论文中的feature map可视化结果分析

我们现在来看一下原始论文是如何对可视化的特征图进行分析,进而找出网络调整的依据的。

3.1 特征可视化

​ 每个特征单独投影到像素空间揭露了不同的结构能刺激不同的一个给定的特征图,因此展示了它对于变形的输入内在的不变性。下图即在一个已经训练好的网络中可视化后的图。

img

由上图可以看到第二层应对角落和其他边缘或者颜色的结合;

第三层有更加复杂的不变性,捕捉到了相似的纹理;

第四层显示了特定类间显著的差异性;

第五层显示了有显著构成变化的整个物体。

​ 总的来说,通过CNN学习后,我们学习到的特征,是具有辨别性的特征,比如要我们区分人脸和狗头,那么通过CNN学习后,背景部位的激活度基本很少,我们通过可视化就可以看到我们提取到的特征忽视了背景,而是把关键的信息给提取出来了。从layer 1、layer 2学习到的特征基本上是颜色、边缘等低层特征;layer 3则开始稍微变得复杂,学习到的是纹理特征,比如上面的一些网格纹理;layer 4学习到的则是比较有区别性的特征,比如狗头;layer 5学习到的则是完整的,具有辨别性关键特征。

3.2 特征演变过程

外表突然的变化导致图像中的一个变换即产生了最强烈的激活。模型的底层在少数几个epoches就能收敛聚集,然而上层在一个相当多的epoches(40-50)之后才能有所变化,这显示了让模型完全训练到完全收敛的必要性。可以由下图看到颜色对比度都逐步增强。作者给我们显示了,在网络训练过程中,每一层学习到的特征是怎么变化的,上面每一整张图片是网络的某一层特征图,然后每一行有8个小图片,分别表示网络epochs次数为:1、2、5、10、20、30、40、64的特征图:

img

结果:(1)仔细看每一层,在迭代的过程中的变化,出现了sudden jumps;(2)从层与层之间做比较,我们可以看到,低层在训练的过程中基本没啥变化,比较容易收敛,高层的特征学习则变化很大。这解释了低层网络的从训练开始,基本上没有太大的变化,因为梯度弥散嘛。(3)从高层网络conv5的变化过程,我们可以看到,刚开始几次的迭代,基本变化不是很大,但是到了40~50的迭代的时候,变化很大,因此我们以后在训练网络的时候,不要着急看结果,看结果需要保证网络收敛。

**总结:**卷积神经网络前面的及各层很快就收敛了,二上层收敛较慢,因此我们以后在训练网络的时候,不要着急看结果,看结果需要保证网络收敛。

3.3特征不变性

​ 一般来说,小的变化对于模型的第一层都有非常大的影响,但对于最高层的影响却几乎没有。对于图像的平移、尺度、旋转的变化来说,网络的输出对于平移和尺度变化都是稳定的,但却不具有旋转不变性,除非目标图像时旋转对称的。下图为分别对平移,尺度,旋转做的分析图。

img
上图按行顺序分别为对5类图像进行不同程度的垂直方向上的平移、尺度变换、旋转对输出结果影响的分析图。按列顺序分别为原始变换图像,第一层中原始图片和变换后的图片的欧氏距离,第7层中原始图片和变换后的图片的欧氏距离,变换后图片被正确分类的概率图。

​ 可视化不仅能够看到一个训练完的模型的内部操作,而且还能帮助选择好的网络结构。

3.4 ZFNet与AlexNet的对比

​ 前面说了ZFNet的结构与AlexNet基本一致,只不过做了稍微的更改,改变了第一层卷积核的大小和步幅,现在来看一下这个第一层卷积层改变前后,特征到底发生了什么变化。在稍微更改了第一层之后,分类性能提升了,下图为两个网络结构可视化特征图。

img(a)为没有经过裁剪的图片经过第一个卷积层后的特征可视化图,注意到有一个特征全白,

(b)为AlexNet中第一个卷积层特征可视化图,

(c)为ZFNet中第一个卷积层可视化图,可以看到相比前面有更多的独特的特征以及更少的无意义的特征,如第3列的第3到6行,

(d)为AlexNet中第二个卷积层特征可视化图,

(e)为ZFNet中的第二个卷积层特征可视化图,可以看到(e)中的特征更加干净,清晰,保留了更多的第一层和第二层中的信息。

3.5 遮挡图像来判断网络是否知道对象在原图中的位置

可能看到现在,我们一直存在一个疑问,那就是,整个模型它自己清楚目标在图像中的具体位置吗?可以用实验来证明一下,即用一个灰色小方块来挡住图像中的目标,然后再观测输出的分类情况来分析,如下图:

img

上图为对三个不同的测试图片中的不同位置用灰色小方块进行掩盖后,观测分类输出的改变情况。

第一列(a)为原始测试图片,

第二列(b)为某个区域被灰色小方块掩盖后的网络结构中第五层的特征图,

第三列(c)为将第五层的特征图投影到输入图像的可视化图像,第一行表明最强烈的特征表现在狗狗的面部区域,(d)为正确分类概率的图,(e)为最有可能的标签。
  上述结果表明,如果图像中的目标被遮挡,那么被正确分类的概率会显著降低,这表明这种可视化与激发特征图的图像结构式真正对应上的。即大概能知道位置。

3.6 图像的一致性分析

​ 为了更进一步探究在深度模型中如何对同一类物体的不同图像进行一致性分析。对五张小狗(同一类)的不同图片的不同区域进行掩盖,然后进行分析观察探究深度模型是对一类物体的那部分进行一致性分析的过程。如下图

img

​ 上图中按列的顺序都是依次遮挡住左眼,右眼,鼻子等对于每幅图像i,计算:img,其中imgimg各自代表第l层中原始和被遮挡的图像,然后计算成对的图像

img

汉明距离,即汉明距离是一个概念,它表示两个(相同长度)字对应位不同的数量,我们以d(x,y)表示两个字x,y之间的汉明距离。对两个字符串进行异或运算,并统计结果为1的个数,那么这个数就是汉明距离。所以得到的值越小,则有更好的一致性。下图对第5层和第7层的特征图中为2,3,4列以及其他随机遮挡情况进行分析的Delta的得分情况,得分越低代表有更好的一致性。

img

​ 由上图可以观察到,在第5层随机遮挡的情况比其他眼睛鼻子被遮挡的情况一致性分析较差,而第7层中,这四类却都差不多,那是因为底层判别的是一类物体共有的部分,而高层判别的是类别中不同的品种这种更高维的部分了

四、

总结

提出了一种可视化方法——核心**“三反”**;

发现学习到的特征远不是无法解释的,而是特征间存在层次性,层数越深,特征不变性越强,类别的判别能力越强;

通过可视化模型中间层,在alexnet基础上进一步提升了分类效果;

遮挡实验表明分类时模型和局部块的特征高度相关;

模型的深度很关键;

预训练模型可以在其他数据集上fine-tuning得到很好的结果。

复现

代码实现

作者对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:

在这里插入图片描述

总结

这篇论文可以说是神经网络可视化的鼻祖,我觉得最大的贡献就是打破了神经网络过程的黑箱了吧。使用 反池化 -> 反激活 ->反卷积 的操作重现了原始输入图,进而改进之前黑箱的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()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/162679.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Cloud Alibaba微服务组件-Nacos-配置中心

Nacos做注册中心是以serviceName做基本管理单元,而作为配置中心则是以dataId为基本管理单元,dataId也就是配置文件名 使用 配置中心架构图 多个配置的优先级 配置动态更新 客户端 ConfigService 输出: 通过调用Nacos服务端的“获取配置”接口…

ubuntu20.04.1网络图标突然消失,无法上网

故障:打开虚拟机进入Ubuntu系统后,打开火狐浏览器,发现无法连接网络。 解决办法:因为刚接触Linux系统,就在网上找各种资料,试了各种办法无果,最后发现有可能网络配置文件被更改。 打开控制台输…

Qt6版使用Qt5中的类遇到的问题解决方案

如果有需要请关注下面微信公众号,会有更多收获! 1.QLinkedList 是 Qt 中的一个双向链表类。它提供了高效的插入和删除操作,尤其是在中间插入和删除元素时,比 QVector 更加优秀。下面是使用 QLinkedList 的一些基本方法&#xff1a…

腾讯云服务器新用户优惠有哪些?腾讯云服务器新人优惠整理汇总

你们是否曾经幻想过拥有一台属于自己的服务器,却因为价格而望而却步呢?今天,我要告诉你一个好消息——腾讯云服务器现在针对新用户推出了一系列的优惠政策,让你可以用超低的价格购买到性能强大的服务器! 首先&#xf…

【数字人】7、GeneFace++ | 使用声音驱动的面部运动系数作为 condition 来指导 NeRF 重建说话头

文章目录 一、背景二、相关工作2.1 唇形同步的 audio-to-motion2.2 真实人像渲染 三、方法3.1 对 GeneFace 的继承3.2 GeneFace 的结构3.2.1 Pitch-Aware Audio-to-Motion Transform3.2.2 Landmark Locally Linear Embedding3.2.3 Instant Motion-to-Video Rendering 四、效果 …

《循环双向链表》(带哨兵位的头节点)

目录 ​编辑 前言: 关于双向循环带头链表: 模拟实现双向循环带头链表: 1.typedef数据类型 2.打印链表 3.初始化链表: 4.创建节点 5.尾插 6.头插 7.尾删 8.头删 9.寻找节点 10.在节点前插入 11.删除指定节点 单链表和双链表的区别…

【实用技巧】更改ArduinoIDE默认库文件位置,解放C盘,将Arduino15中的库文件移动到其他磁盘

本文主要介绍更改Arduino IDE (含2.0以上版本)默认库文件位置的方法。 原创文章,转载请注明出处: 【实用技巧】解放系统盘,更改ArduinoIDE默认库文件位置,将Arduino15中的库文件移动到其他磁盘-CSDN博客文…

2D槽道流

之前看槽道流时,一直无法在二维槽道流里计算出湍流状态,后来了解到二维槽道流需要额外添加随机扰动,但是这个体积力的植入方式一直不知道。而且看稳定性分析中的OS方程的推导,也是基于2d的NS方程,至今还是很疑惑这个问…

保姆级 | Nginx编译安装

0x00 前言 Nginx 是一个 HTTP 和反向代理服务器, 邮件代理服务器, 和通用 TCP/UDP 代理服务器, 最初由伊戈尔西索耶夫(Igor Sysoev)撰写。采用编译安装可以根据自身需要自定义配置,让服务器有更高的安全性和…

智能配电系统解决方案

智能配电系统解决方案是一种集成了先进技术和智能化功能的配电系统,它能够提高电力系统的效率、可靠性和安全性。力安科技智能配电系统解决方案依托电易云-智慧电力物联网,具体实施的方案如下: 智能化设备和传感器:采用智能化的开…

基于PI+重复控制的并网逆变系统谐波抑制策略模型

微❤关注“电气仔推送”获得资料(专享优惠) PI重复控制简介: 重复控制这一新型控制理论最早于出现日本学术界,其目的是为了用于解决质子加速器跟踪精度的问题。Yamamoto Y 等人提出了重复控制数学基础的内模原理,在控…

PS学习笔记——图层

文章目录 图层面板图层类型新建图层新建方式图层颜色 操作图层修改图层名称选中图层隐藏图层调整图层顺序复制图层 图层面板 按F7可打开/关闭图层面板 该面板就是图层面板了 对所有图层进行筛选的按钮,第一个搜索框可以选择按什么方式进行筛选,支持&am…

x程无忧sign逆向分析

x程无忧sign逆向分析: 详情页sign: 详情页网站: import base64 # 解码 result base64.b64decode(aHR0cHM6Ly9qb2JzLjUxam9iLmNvbS9ndWFuZ3pob3UvMTUxODU1MTYyLmh0bWw/cz1zb3Vfc291X3NvdWxiJnQ9MF8wJnJlcT0zODQ4NGQxMzc2Zjc4MDY2M2Y1MGY2Y…

ZHUTI主提2024春夏 聆听「宁静的声音」

将自然艺术触达生活 生活与艺术实践活动 ZHUTI主提2024春夏艺术活动「宁静的声音」,将自然艺术真实的触达生活为核心,将原野聚会、黑胶音乐、插花、咖啡、食物、舞蹈、服装等艺术与生活的元素组合在这场芦苇荡中,用一场兼具无穷畅想和独特审…

如何快速本地搭建悟空CRM结合内网穿透工具高效远程办公

🌈个人主页:聆风吟 🔥系列专栏:数据结构、Cpolar杂谈 🔖少年有梦不应止于心动,更要付诸行动。 文章目录 📋前言一. 无需公网IP,使用cpolar实现悟空CRM远程访问二. 通过公网来访问公司…

【Java】ArrayList和LinkedList使用不当,性能差距会如此之大!

文章目录 前言源码分析ArrayList基本属性初始化新增元素删除元素遍历元素 LinkedList实现类基本属性节点查询新增元素删除元素遍历元素 分析测试 前言 在面试的时候,经常会被问到几个问题: ArrayList和LinkedList的区别,相信大部分朋友都能回…

Intellij Idea屏蔽日志/过滤日志

一、安装插件 Grep Console 二、设置关键词,过滤日志 关键词的前后加上 .* 符号,类似: .*关键词.*设置后 ,点击 Apply 即可过滤日志。

LeetCode704.二分查找及二分法

每日一题:LeetCode704.二分查找 LeetCode704.二分查找知识点:二分法解题代码 LeetCode704.二分查找 问题描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中…

VSG-001

VulkanSceneGraph (VSG), is a modern, cross platform, high performance scene graph library built upon Vulkan VSG 是一个基于vulkan的现代的、跨平台的高性能场景管理库 VSg特性: 使用C17作为c规范编码,支持 CppCoreGuidelines支持 FOSS Best P…

大师学SwiftUI第16章 - UIKit框架集成

其它相关内容请见​​虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记​​ SwiftUI是一套新框架,因此并没有包含我们构建专业应用所需的所有工具。这意味着我们会需要求助于UIKit(移动设备)和AppKit(Mac电脑)等原…