lesson6 part6 CNN

卷积神经网络(jupyter笔记pets_more章节 training a model)

我们要学习卷积神经网络,我们先来创建一个卷积神经网络。你知道怎样创建它们,我先创建一个,然后用fit_one_cycle()训练它,解冻它。然后,我要创建一个更大的352 x 352的数据集,再训练它,然后保存它。

gc.collect()
learn = create_cnn(data, models.resnet34, metrics=error_rate, bn_final=True)
learn.fit_one_cycle(3, slice(1e-2), pct_start=0.8)
Total time: 00:52
epoch  train_loss  valid_loss  error_rate
1      2.413196    1.091087    0.191475    (00:18)
2      1.397552    0.331309    0.081867    (00:17)
3      0.889401    0.269724    0.068336    (00:17)
learn.unfreeze()
learn.fit_one_cycle(2, max_lr=slice(1e-6,1e-3), pct_start=0.8)
Total time: 00:44
epoch  train_loss  valid_loss  error_rate
1      0.695697    0.286645    0.064276    (00:22)
2      0.636241    0.295290    0.066982    (00:21)
data = get_data(352,bs)
learn.data = data
learn.fit_one_cycle(2, max_lr=slice(1e-6,1e-4))
Total time: 01:32
epoch  train_loss  valid_loss  error_rate
1      0.626780    0.264292    0.056834    (00:47)
2      0.585733    0.261575    0.048038    (00:45)
learn.save('352')

我们得到了一个CNN。现在我们要弄明白这里面做了什么。要弄明白的方式就是学习怎样生成这个图片:


image.png

这是一个热度图(heat map)。这个图片显示了当CNN判断这张图片是什么时,它关注图片里的哪个部分。我们会从头做这个热力图。

现在我们处在课程的这样一个阶段,我假设你们到达了这个阶段,并且还处在这个阶段,就是你们对深入这些细节很有兴趣,并且对深入它们已经做好了准备。我们要学习怎样创建这个热度图,不用fastai库里的东西。我们会用PyTorch里的纯粹的张量计算,我们用这些来真正理解它是怎样运行的。

提醒下,这不是高深的东西,但很多内容大家从未听过,不要期待一次就能弄明白,而是要听课,进入notebook,做些尝试,测试结果。通过研究张量形状和输入输出,来加深你的理解,然后回去再听一遍。试几次,你就会明白了。其实就是很多新概念,因为我们还没有试过用纯的PyTorch的标准算法来做一些东西。

CNN

让我们开始学习卷积神经网络。奇怪的是在课程快结束的时候才学习卷积,这是很少见的。但是你仔细想想, 知道batch norm怎样工作、dropout怎样工作、卷积怎样工作,不如知道如何将它们组合起来、如何用它们完成具体的任务。以及如何将任务完成地更好。现在我们学习到一定程度,希望可以生成热力图。尽管我们把这个方法直接添加到了我们的库里,你可以运行一个函数就能生成。但是你做的越多,你越想用不同的方式来做,又或者在你的研究领域,你会想我能否做得略微不同。你目前的经验,让你知道怎样靠自己做更多的事情。这意味着你需要理解背后的原理。

背后的原理是我们在创建一个像这样的神经网络:


image.png

但在三个黄色矩阵中,我们不是要做矩阵乘法,而是做卷积。卷积就是一类矩阵乘法,但其有自己的特性。

image.png

你一定要访问下这个网站http://setosa.io/ev/image-kernels/ev代表可视化解释explain visually),我们借用这个美妙的动画,它实际上是一个JavaScript程序,你可以自己尝试,这个动画展示了卷积是怎么回事。当我们移动这个小红方框时,你可以看到卷积是怎么计算的。这是一幅图像,黑白或者灰度图像,每一个3×3的小方格,都是可以移动的红色区域。显示了图像中3×3的一小块区域。这里使用的是像素。在fastai库中,像素值在0到1之间。在这个例子里,像素值在0到255之间。这里有9个像素值。这个区域很白。这里的像素值就会很高。

随着我们移动光标,这个9数字的值会变,同时能看到的底色也在变。上面还有另外9个值。 下面的方格下的×1,X2和这里的1,2,1是对应的。现在你能看到, 当我们移动红色小方格,这些数字会改变,然后我们把这些数字乘以对应的数字,这里我们开始用到一些术语。

上面的矩阵,叫做核。卷积核。我们将会取图像每个3×3的部分,然后做逐元素相乘,用鼠标点到的9个像素值,和我们核中的9个数相对应。当我们把每个对应的元素都相乘,然后把它们相加求和,这就是右边显示出来的。 随着这里的红色方格移动至此,你会发现这边也有一个小红格。因为这个小红格里面的数字就是下面9个元素与核进行逐元素相乘,再求和的结果。因此这个图像的大小比原始图像少一圈。你能看到,右图有一个黑边,因为在边缘位置,3×3的核不能溢出边缘,所以核的中心能走到最远的地方,就是距离边缘的一个像素的位置。

我们为什么这么做呢?或许你已经发现了什么。这张脸有些地方(右图)显得有一些白,勾勒出了脸部轮廓水平的边缘。怎么实现呢?其实就是,这9个像素值与核,进行逐元素相乘,求和,并把对应结果在这里拼在一起。那为什么 能画出这种白边轮廓呢?你可以试想一下。我们来看这里(左图的头发),


image.png

v
如果我们只看这一小块, 它上面的点都很白,所以它们都有比较高的值,上面的这些数字乘以1,2,1,能生成大的数字。中间的数字都是0,所以不用关心这些。下面这些数字都是小数字、因为它们都接近0.所以它们都没有什么作用。因此这个部分最终会是白色的像素。同时在下方另一侧(发际线这里),像素色浅,将会产生很多负值。区域上方像素是黑色的,像素值很小,再乘上1,2,1依旧很小,没有什么影响,所以在这里我们将会得到很负的数字。这里我们用3×3的矩阵,与核进行逐元素的相乘,在相加求和,产生一个输出值。这个过程我们称之为卷积。这就是卷积了。你可能看着很熟悉。因为我们之前几节课里,看过Zelier和Fergus的文章,那篇文章里面我们看到不同的层,并且我们可视化地看到权重的作用是什么。还记得吗?第一层参数基本上具备找到对角边和梯度的特征,卷积的作用就是这样。每一层都是一个卷积。所以第一层(核矩阵中的值)能做的就差不多是这样。 但很棒的是,下一层可以用前一层作为输入,它可以把通道结合,一个卷积的输出叫做一个通道,所以它可以用一个通道找到顶边,用另一个通道找到左边,然后下一层可以将这两个通道整合成输入数据,从中提炼出具备识别左上角边沿的能力。这就是我们Zeiler和Fergus这篇可视化文章所了解到的。

让我们再从其它角度看下这个。我们看下这一篇Matt Kleinsmith的精彩文章,他是我们这个课程第一年的学生。他写了这个作为他的课程项目的一部分。

image.png

这里是我们的图片(右图中间列)。它是一个3x3的图片(中间列),我们的核是一个2x2的核(左边列)。我们要做的是把这个核应用在这个图片的左上角上部分。让左边2×2的核与中间列相应颜色的小格相乘,绿色对应绿色,红色对应红色。把它们加在一起,生成输出里的左上角的值。也就是说:
image.png

b 是一个bias,这没什么影响。它只是一个普通的bias。你可以看到每个输出像素是不同线性方程的计算结果。可以看到这四个权重被来回移动,因为它们是我们的卷积核。

这是另一种理解卷积的方式,它是一个经典的神经网络的视角,P是把每一个输入,乘以对应权重,然后把除了灰色的所有结果加起来。


image.png

我需要将灰色参数都被设置为0,因为P只和A、B、D、E有联系。


image.png

换句话说,神经网络表示的是矩阵乘法,所以我们可以用一个矩阵乘法表示它。这里是将3×3小图的像素展平为一维向量。这是一个矩阵和向量相乘再加上偏置。这里的许多元素,都被我们设置为0,所以你可以看到,这里是0,0,0,0。


image.png

相应的这里也是0 0 0 0 0 (C F G H J 位置)。


image.png

换句话说,卷积就是一次矩阵乘法,在这之中,发生了两件事情,一是部分元素一直都为0,二是那些具有相同颜色的元素已有相同的权重,因此当存在具有相同权重的多个元素时,这就叫权重栓连(weight tying)。很明显,我们可以用矩阵乘法实现卷积,但是我们不会这么做,因为这很慢。在实际中,我们的程序库,有专门的的卷积函数可以用。它们基本上做的就是这个,
image.png

这个,


image.png

也就是这组等式,


image.png

也就是和矩阵乘法一样,


image.png

像我们讨论过的,我们需要考虑padding,因为如果你有一个3x3的核,和一个3x3的图片,这只能生成一个像素的输出。这个3x3的内积核只能做一次卷积。因此,如果我们希望输出多于一个像素,需要使用另一种方法叫填充(padding),就是在图像周边添加额外的像素,大多数程序库的做法是在图像周边加一层圈0,即在图像周边添加一圈0,因此对于3×3的内核,在每一边之外都要添加一个0,一旦你这样添加之后,就可以移动这个3×3的内核穿过图像,得到和输入一样尺寸的输出。正如我们提到的,在fastai中,我们通常不一定使用zero填充,我们可能会用reflectioni填充,尽管对于这种简单的卷积,我们还是用zero填充,因为对于一幅大图来说,这么小的区域不是很重要。用那种填充方式没多大的区别。这就是卷积的概念。


image.png

一个卷积神经网络如果只能找到边缘,那就不是很有意义,所以我们必须更进一步。如果我们有一个输入,它可能是标准的红蓝绿图像,那么我们可以创建3×3的内核,就像这样,然后我们可以把它用在所有不同的像素上。但是你可以仔细想一下我们用的不再是2维的输入,我们现在是3维的输入,一个三阶张量,所以我们很可能不想把一样的核的值用在红蓝绿三个通道上,因为如果我们正在建立以绿色青蛙的检测器,相较于蓝色激活,我们希望绿色能得到更多激活。如果我们试着建立合适的特征让我们找到梯度,去区分从绿色到蓝色的变化,那么每个色彩通道的核,都需要设定不同的值,因此我们需要建一个3×3×3的核。


image.png

这仍然是我们的核。我们仍然会从高度和宽度上做一些变化,但是相较于做9个值的逐个元素相乘,我们要做27个值的逐个元素相乘,3×3×3.我们仍将它们加起来变成一个数字,所以当我们用这个立方体(核)作用在这里,或者像这样,这样将会填充到后面,是吧。当我们做这部分的卷积的时候,仍然只会得到一个数字,因为我们对27个核的值做了逐元素的乘法,并把他们全部加总了,所以我们可以使用填白后的单元。


image.png

作为输入,所以我们的输入是1,2,3,4,5(一组)×5,所以我们最终会得到一个输出,也是5×5的。 但是现在输入的三原色是三通道的,输出只有一个通道,现在我们对一个通道无法做太多的分析,因为现在我们只能找到上边缘。


image.png

我们怎么能找到侧边和梯度还有长期留白的部分?所以,我们需要另建一个核,对输入卷积进行转换,并且建立另外一个5×5的输出。

image.png

然后我们可以把这两个输出矩阵信息堆叠起来,就有了另外一个维度。我们可以大量重复这一步骤,堆叠输出矩阵,最终我们会得到另外一个三阶张量输出。
image.png

这就是实践操作中的流程。在实践中,我们的输入是H(高)乘W(宽)乘3(三原色/通道数) ,我们让大量卷积作用在上面,核的数量我们可以选择,然后我们得到一个输出,高度×宽度×我们所拥有的核的数量。
image.png

通常,我们在开始时会使用16个核,所以现在我们有16个通道,这16个通道,代表这个像素上不同的要素,比如有多少左边缘,有多少上边缘,有多少蓝色到红色的梯度渐变,在这个2709个像素的RGB图片中,你可以继续做同样的事情。你可以继续建设一组核,通过它们又会得到另一组3阶张量。同样也是图片的高度×宽度×通道数,可能也是16。
image.png

我们真正想做的是,随着进入到更深的网络,我们希望有越来越多的通道,我们希望得到越来越丰富的特征,所以就像我们看到Zeiler和Fergus的论文里面写的用了4或5层,我们得到了眼球检测器和羽毛检测器,和其他检测器, 所以你真的需要很多通道。为了避免耗尽内存,时不时的我们建立这样的卷积层,不去遍历3×3的所有子集,而是一次跳两步。所以我们会以(2,2)为中心的3×3矩阵开始,直接依次跳到以(2,4)、(2,6)、(2,8)为中心的矩阵,这个叫步长(stride)为2的卷积(Stride 2 convolution)。其他部分和步长为1的完全是一样的。仍然是一组核。我们只是每次跳两步,我们每隔一个像素跳一个,所以输出就变成了H/2乘以W/2,所以输出就变成了H/2乘以W/2,所以当我们这么做的时候,我们通常会创建两倍的核,所以现在我们对每个点有32个激活,这就是现代卷及网络的大致样子。

image.png

我们可以看到,如果我们分析我们的宠物数据集,建立CNN(卷积神经网络),我们看一下这只猫,如果我们运行

id = 0
x,y = data.valid_ds[idx]
x.show()
data.valid_ds.y[idx]

抓取第一个(index是0),然后运行x.show()然后我们输出y图像,显然这只猫的品种是缅因猫,


image.png

一周前我完全不知道,世界上有种动物叫缅因猫,在花了一周时间研究这只缅因猫之后,我现在非常熟悉这只缅因猫。所以我们运行

learn.summary()

记住我们的输入要求是352×352像素,


image.png

一般来说第一个卷积层的步长是2,所以第一层之后成了176×176,这是运行learn.summary的结果,会输出所有层的维数176×176【will print out for you the output shape up to every layer 176×176】,第一个卷积层有64个激活函数。


image.png

输入learn.model我们可以看到
image.png

这是一个Con2d有3个输入通道,64个输出通道,步长是2,好的。有趣的是开始的时候它的核是7×7的,所以几乎所有其他的卷积都是3×3,全是3×3。由于在第二部分我们说的原因,我们经常在开始的时候用更大的核。如果用了更大的核,你需要更多的padding,所以我们必须用核数除2取整(比如7//2 = 3)来padding,这样我们就不会遗漏任何信息。现在我们有64个通道,因为步长是2,现在是176×176。随着推演,你会时不时看到,我们的网格会减半,到88×88,再到44×44,所以这就是Conv2d,然后我们这么做的时候,通常会给通道加倍,


image.png

数据经过其他几个卷积层,你能看到也经过了Batchnorm2d和ReLU,这都是很通用的操作。然后我们又复原操作了一遍。另外一个步长是2的卷积,又加倍了通道。现在是512个通道11×11的网络。这就是我们神经网络主体结束的地方。最终我们是512个通道11×11的网络。
image.png

手工卷积

好的,在这个点上,我们可以开始做热力图了。我们试着完成它,在此之前,我想让你看看,你怎么做手动的卷积。因为它挺有趣的。我们就从这张缅因猫的图片开始。我们创建了一个卷积核,


image.png

这个有一个右边缘和底边的正值,里面都是负值,我觉得它代表有一个右底边。好的,这是我的张量,现在一个复杂的问题是,3×3的核不能用于这个目的,因为我还需要另外两个维度一起使用。首先,我需要第三个维度,用于表征怎么把红、绿、蓝结合起来。所以我要做的是用.expand这个函数。这是我刚才建的3×3矩阵,我再加个3到开始的地方。expand的作用是建立一个3×3×3的张量,方法是基础张量(调用这个方法的张量)复制3次。我想说的并不是完全的复制,只是“假装”复制,它们三个矩阵都引用着同一块内存,所以这是一种能节约存储空间的复制方式。这里就是那3个复制品。


image.png

我这么做的原因就是我想要对红绿蓝同样对待和处理,正如我正给你展示的一样。然后我们需要再多一个轴,因为我们其实并不是要完全分开的单独的核, 就像我们之前在这里画的多个内核。


image.png

我们实际上要用的是用一个四维的张量,最前面的一个参数是对每个单独核组合方式的说明。

在这里,我要创建一个核,为了做卷积,我还要在前面加这个参数(expand1,3,3,3)中的1)。现在你可以看到k.shape是[1, 3, 3, 3]:


image.png
k.shape
torch.Size([1, 3, 3, 3])
image.png

这是一个3x3的核(后两个参数)。有3个这样的核(第二个参数),这就是我们最终获得的核(第1个参数)。要理解这些高维的向量会花一些时间,因为我们不习惯写4D的张量,但是这样想就好了,4D的张量就是一组叠在一起的3D的张量。
这就是我们的4D张量,然后你可以调用conv2d,传入一些图片,我要用的图片是验证集里的第一部分以及核。


image.png

还有一个技巧, 在Pytorch里面,所有的工作都是基于mini-batch的,而不是单独的一张图片,所以在这里,我们必须创建一个只有1个图片的小批量数据集,我们的原始图片时3个通道乘352×352,即高×宽。


image.png

记住,Pytorch是通道数×高×宽,我们需要创建一个4维的张量,第一个维度的值是1。这表示mini-batch的大小是1. 因为这种小批次就是pytorch所需要的格式,你可以用pytorch或者用Numpy完成。就是你可以通过none去索引数组或者张量。这样就会在那个数据里产生一个新的维度,t是我所选择的3×352×352的图片,T[none]是一个4维的张量。也就是含一张图像(1×3×352×352)小批次。现在我可以运行Conv2d了,这是输出的猫,也就是缅因猫。


image.png

好的,你自己可以这样去尝试下使用卷积。

生成热度图

记住,我之前提到的,我们是从红-蓝-绿(组成的图片)输入值开始的。然后通过了一堆卷积层 ,我们用竖线来代表卷积层, 通过激活函数输出。


image.png

我们会有越来越多的通道,并且图片越来越小(高×宽),最终得到在summary那一段里看的结果,也就是11×11×512的输出,


image.png

data.c代表了我们有多少类别,我们可以看到,


image.png

模型最终有37个特征。


image.png

这意味着,我们获得了图片对应37个类别猫和狗的各自概率,也就是一个长度为37的向量,也就是我们最终需要的输出,我们将把这个输出跟我们的one-hot矩阵作比较。one-hot编码将在缅因猫的地方标着1,我们需要把11× 11×512的矩阵变成37个值的向量。那么方法就是我们对每个11×11的面来取均值,这里我们对第一个面取均值,其结果将为一个数字,然后我们会对512个面中的第二个面取均值,我们又得到一个值。我们将对每一个面都这么做,这样会得到一个长度为512的向量。现在我们需要做的仅仅是把这个向量乘以一个512×37的矩阵。这样我们机会得到一个长度是37的输出量。这里给每个面取均值的这步,被称为average pooling(均值池化)
![image.png](https://upload-images.jianshu.io/upload_images/17391828-252acdcafad 96dcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

让我们回头看下我们的模型。这里是我们最终的512个值。这里有一个concat pooling。我们在part2会讨论。现在我们只关注均值池化。concat pooling是fastai的特有的,其他库都只能做average pool。


image.png

AvgPool2d的输出大小是1,这就是AvgPool2d的大小为1的输出。


image.png

这里又是一个fastai特有的东西,这里我们实际有两层,通常其他库只有一个线性层,这个线性层输入为512,输出为37。


image.png

那意味着什么呢?这儿的小盒子区域,也就是我们希望缅因猫的值是1的地方。我们必须在对应小盒子的地方有一个比较高的数值。这里的损失值比较小。为了能在这里得到一个高的值,而我们是通过矩阵相乘, 也就是说它代表了512个元素值经过权重变换的线性组合。


image.png

所以如果我们能自信地说,这就是一个缅因猫,也只需要将输入加权求和,而这些输入实际上代表这一些有意义的特征。比如毛是不是松软,鼻子是什么颜色,腿有多长,耳朵有多尖等,以及其他可以使用的特征。比如如果你想识别一个斗牛犬,你也同样会用512个输入,只是权重不同而已。因为这就是矩阵乘法的本质。就是加权求和。不同的输出会使用不同权重求和。


image.png

我们知道,可能会有成百甚至上千的卷积层,但最终以输出11×11的面来对应每一个特征。比如这一小块。


image.png

可能就表明了它像尖耳朵的程度,或者它的毛发蓬松的程度。或者是不是更像一条长腿,或者像一个鲜红鼻子的程度。这就是这些层需要代表的特征。每个面都代表一个不同的特征。 这些特征输出我们可以理解为不同的特征。

所以,我们真正想知道的并不是每个11×11面的均值是什么,经过这11×11的面的输出,我们真正想知道的是这11×11的面的每个点代表着什么,我们如果不对这11×11个点取均值,而是对512个通道来去均值。如果我们用512个通道均值,我们将会得到一个11×11的矩阵。每一个区域,每个点将是那个区域活跃程度的平均值。当图片逐渐弄清楚是一只缅因猫时,每个格点对应的就是这个区域包含的缅因猫成分值。在11×11的区域里,这实际上就是我们制作热度图的方法。我想最简单的方法可能还是逆向操作。

这是我们的热度图,而它是由平均激活值组成的。这用了一些matplotlib和fastai代码。fastai用来显示图片,Matplotlib用来做热度图。


image.png

我们在这里输入平均激活值,hm就是热力图。Alpha=0.6是增强透明度。而extent是将11×11的张量扩展成352×352。Interpolation用的是bilinear interpolatiions(双线性插值),这样不会成像为块状不均匀。而用不同的色差cmap来标亮。这就是matplotlib。不是很重要。关键的是这里的平均激活值,就是我们想要的11×11张量。这里写了,平均激活值尺寸avg_acts.shape是11×11。


image.png

为了到这一步,我们对512×11×11的第0维取均值,

avg_acts = acts.mean(0)
avg_acts.shape
torch.Size([11, 11])

也就是我们刚刚说的在pytorch中的第一个维度就是通道维度,


image.png

所以对第0维做均值处理,可以将512×11×11张量转化为目标的11×11张量。


image.png

image.png

所以激活值acts包含了我们要做平均处理的激活值,那acts从哪里来呢?他们来自Pytorch的hook函数。Hook是一个高级酷炫的Pytorch功能,能让你只用名称注册,就可以连接到Pytorch内部,并且运行任意python代码。这真的是一个很棒很有用的功能。

因为你知道,通常我们通过pytorch模块,运行forward时,我们得到一系列输出,但我们知道在计算过程中,这些结果是在计算这个(512×11×11的面),我要做的是,连接hook到forward,然后让pytorch 在计算中间部分的时候,把它们保存下来。这是什么呢?这就是模型卷积部分的结果。 模型的卷积部分基本上就是在均值池(average pool)之前的所有层(除了输入层)。


image.png

回顾一下迁移学习, 我们切掉模型卷积部分,之后的所有东西,并换上我们自己的层。对于fastai,模型的原始卷积部分,永远是模型结构的首位。确切来说,它常被调用到这里。我调用了我的模型,并且命令为m。你可以看到,m的内容有很多,但至少在fastai里,M[0]总是模型的卷积部分,这里,我们建立了一个Resnet34,resnet34主要的部分就是,也就是我们用来与训练的部分,都在m[0]里。所以基本上,这是Resnet34的全部内容。最后得到的就是512×11×11的激活值。换句话说,我们要做的是调取m[0],再用hook链接它的输出,这样做非常有用,而且fastai有一些功能可以用来帮你完成这个步骤。就是用hook_output().之后把m[0]的输出传入到你想要的链接pytorch的模块中。

image.png

你最想链接的就是模型的卷积部分,也就是m[0]或者learn.model[0],所以我们给hook一个名字,不用担心这部分的代码,我们下周会学到。
image.png

这里连接了m[0]输出,我们现在需要运行forward pass正向传递,提醒一下,在pytorch里,要计算一些东西,需要用到正向传递(forward pass),你只需要将模型当做一个函数,我们给函数输入一个小批次,我们已经有了一个缅因猫图片,但我们不能把它直接传入到模型中,它需要被归一化处理,再转化为一个小批次,放进GPU.在fastai中有一个东西叫做数据堆,是data中的一个功能,你可以执行data.one_item()函数,来建立一个小批次。给你们一个小作业,试着构建一个小批次,但不用dat.one_item().确保你学会了怎么归一化处理数据。如果你们想试试的话。但这就是你如何创建小批次。它只含一个样本,然后我们再将数据用.cuda()传入gpu上,这样就可以将图像传进模型,得到预测值。其实我并不关心这个预测值,因为预测这件事情,并不是我想要的, 所以实际上我不会对预测值做任何事情,我关心的是我刚刚建立的hook。需要注意的是,当你在pytorch用hook链接什么的时候,这意味着每一次运行模型时,假设你在链接输出结果,Pytorch会存储所以输出结果,当你得到你要的结果以后,你需要移除hook,要不然的话,如果你再用这个模型,它会继续不断链接输出结果并存储,这回导致速度变慢,内存紧张。所以我们建立了这个东西,python称之为上下文管理器,你可以 用任何hook作为上下文管理器,在with代码块的最后,它会删除那个hook。
image.png

我们现在有了hook,就是fastai hooks,总给你一些东西叫做,或者说output hooks总会给你一些东西叫做.stored,这里存贮着你用hook链接的内容,也就是我们的激活值。


image.png

所以我们做了一个正向传播,在hook模型卷积部分的输出之后,我们调取了它存储的内容,检查了数据尺寸是我们预料的512×11×11,然后我们对通道轴取均值,得到11×11张量。


image.png

然后我们就得到了我们要的图片。我知道这段内容信息量极大,但如果你花时间反复看这两部分:卷积核部分以及heatmap部分,就像去运行这些代码,尝试改变一点代码试试举一反三,重要的是记住检查他的shape。


image.png

你可能发现了,当我在向你们展示着写notebooks的时候,我经常输出shape来检查。当你查看shape的时候,你要看看有几个维度,也就是张量的阶数,以及每一个维度里面的值是多少。试着想象为什么是这样的 ,试着回头看看输出的总结,看看模型各个层的设置, 以及回头看一看,想一想,我们画的图。在想想这背后的原理都是怎样的。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容