lesson 7 part1 Resnet

欢迎来到第七课!这是课程第一部分的最后一课。这节课内容比较多。不要担心。这是因为,我想让你们在课程第二部分开始之前有足够的事情做。今天讲的一些东西,我不会讲太多细节。只会指出一些东西。我会说,我们今天不会讲它。然后,在课程第二部分,会学到这些内容的细节。今天会很快地学很多东西。你可能要多看几遍,做些实验,来完全理解。我们特意这样做,给你们一些东西,让你们在接下来一两个月里能过得充实有趣。


image.png

首先,我要展示一个很酷的东西,它是几个学生做的。Reshama和Nidhin开发了一个Android和iOS app,在这个Reshma在论坛发的文章 里可以看到,他们有一个怎样创建可以真正发布到Google Play Store和Apple App Stroe的Android和iOS app的demo,这很酷。这是我知道的App Store里的第一个使用fastai的东西。非常感谢Reshama,她为fast.ai社区和机器学习社区,和女性机器学习社区 做出了贡献。她做了很多了不起的事情:提供了大量了不起的文档和教程,组织社区等等。谢谢你,Reshama。祝贺这个app发布。

MNIST CNN【2:04】

可以看到,今天我们有很多notebook。第一个要学的notebook是lesson7-resnet-mnist.ipynb。我想看一些我们上周讲过的关于卷积和卷积神经网络的东西,然后基于上述知识差不多是从头开始构建更加先进的深度学习框架。我说的从头,不是重新实现一遍先前学过的notebook,不过我会借助一下先前保存的PyTorch数据。我们使用MNIST数据集。URLs.MNIST有完整的MNIST数据集,我们经常只用它的一个子集。

image.png

这里有一个训练集文件夹和一个测试集文件夹。当我读入数据集的时候,我要演示一些data blocks API的细节,你看到过它们是怎样用的。通常,我们通常由于用了data blocks API会把所有的函数调用代码放在一个cell里,,但现在让我们来一个cell一个cell运行。

image.png

先说是 ITemList 的类型 。这里是一个存放image的item list。然后是从哪里得到文件名list呢?这里,我们这里以递归的方式遍历文件夹, 这是文件的所在位置(path参数)。

你可以传入参数,最终函数参数会传递到pillow模块中,因为pillow或者PIL实际上管理着这些图片。这种情况下,它们是黑白的,不是RGB,所以你要用Pillow的convert_mode='L'。在这个Python Imaging Library的文档里可以看到它的convert modes的更多细节。convert_mode='L'这一个参数代表是MNIST的灰度图。

il.items[0]
PosixPath('/home/jhoward/.fastai/data/mnist_png/training/8/56315.png')

ItemList具有items的属性,而这个item特性是由你设定的,这里你的设置就是一列文件的名称,这就是从文件夹获得的(文件名称)。

defaults.cmap='binary'

通常,你展示的图片都是彩色的。这里,我们希望用binary color map(二值图)。在fastai里,你可以设置默认的color map。参考matplotlib文档,可以查看更多关于cmp和color map(色标图)的信息。defaults.cmap='binary' 会设置fastai的默认color map。

il
ImageItemList (70000 items)
[Image (1, 28, 28), Image (1, 28, 28), Image (1, 28, 28), Image (1, 28, 28), Image (1, 28, 28)]...
Path: /home/jhoward/.fastai/data/mnist_png

我们的image item list包含70,000个条目,这是一堆1x28x28的图片。记住PyTorch把channel放在第一个维度。它们是一个通道的28x28。你可能会想,为什么不是只有28x28的矩阵,而是1x28x28的张量。因为这样更简单。所有Conv2d里的东西和其它的东西都是处理秩是3的张量的,所以你要用一个值是1的维度放在前面。当fastai读到只有一个通道的图片时,它会自动为你做这个。

image.png

这个.items属性包含了可以用来创建图像的信息,这里它是文件名,如果你直接给item list后面加上索引([0]),你可以得到一个实际的 image对象。
image.png

image对象有一个show方法,这就是那张图片。

你得到一个ImageItemList后,你把它分成训练集和验证集。你通常需要验证集。如果你不需要,你可以用.no_split()方法,创建一个空的验证集。调用函数时,你必须设定如何分割初始数据,其中一个选项就是no_split()

这就是规定。首先创建item list,然后决定怎样划分。这个例子里,我们按照文件夹名称来划分。

image.png

MNIST的验证集文件夹是testing。在fastai里,我用和Kaggle一样的做法。训练集是你用来训练模型的数据集,验证集(validation set)的输出是有标签的,用于验证你的模型是否能够正常运行,测试集(test set)没有公开的标签。你用它作为输入进行输出的预测,或者你可以把它们发送至竞赛平台,或者把它们发送给第三方,由他们进行标注或测试等等。所以,尽管你数据集里的文件夹叫testing,但并不意味着文件夹里的数据就是测试集(test set)。
image.png

这里有标签,所以这是验证集。

如果你想一次性预测很多组数据,与其一个一个地来做,不如使用fastai里的test=来设置,来表示这里输入的数据是没有标签的,我只用它们做输出推断。

sd
ItemLists;

Train: ImageItemList (60000 items)
[Image (1, 28, 28), Image (1, 28, 28), Image (1, 28, 28), Image (1, 28, 28), Image (1, 28, 28)]...
Path: /home/jhoward/.fastai/data/mnist_png;

Valid: ImageItemList (10000 items)
[Image (1, 28, 28), Image (1, 28, 28), Image (1, 28, 28), Image (1, 28, 28), Image (1, 28, 28)]...
Path: /home/jhoward/.fastai/data/mnist_png;

Test: None
可以看到,我划分后的数据是一个训练集和一个验证集。

(path/'training').ls()
[PosixPath('/home/jhoward/.fastai/data/mnist_png/training/8'),
 PosixPath('/home/jhoward/.fastai/data/mnist_png/training/5'),
 PosixPath('/home/jhoward/.fastai/data/mnist_png/training/2'),
 PosixPath('/home/jhoward/.fastai/data/mnist_png/training/3'),
 PosixPath('/home/jhoward/.fastai/data/mnist_png/training/9'),
 PosixPath('/home/jhoward/.fastai/data/mnist_png/training/6'),
 PosixPath('/home/jhoward/.fastai/data/mnist_png/training/1'),
 PosixPath('/home/jhoward/.fastai/data/mnist_png/training/4'),
 PosixPath('/home/jhoward/.fastai/data/mnist_png/training/7'),
 PosixPath('/home/jhoward/.fastai/data/mnist_png/training/0')]

在训练集里,每个类别都有一个文件夹。

ll = sd.label_from_folder()

我们取这个划分的数据,调用label_from_folder()
首先你创建一个MNIST数据集,然后划分它,然后标注它。

image.png

现在你可以看到,数据分成了输入x和输出y两块, Y就是类别对象。类别对象基本就是一个分类。

x,y = ll.train[0]

如果你对一个label list后面加上索引,


image.png

你会得到一个自变量x和因变量y。


image.png

这里,x是可以用show画出来的图片对象,y是可以打印出来的类别对象.这里输出的y的值是8.

image.png

下一个我们可以做的事情是添加变换(transform)。这里,我们不使用普通的get_transforms函数,因为我们正在做数字识别,用于识别的数字不应该左右翻转,这会改变数字的含义。你也不能旋转太多,这也会改变含义。因为这些图片很小,又经过了变焦处理,这导致这些图片非常模糊,不易辨认,因此通常对这类手写数字的小图片,你可以直接在原图上使用随机填充,因此这里我用了随机填充指令rand_pad()函数。这个指令里,对输入执行了两种变换,分别是填充和随机剪裁(random crop),因此你需要在rand_pad前面加上*. 对list里的数据进行两种变换,这就是我们调用的变换。这里的空数组([])指的是验证集的变换,验证集的数据没有进行变换。现在我们得到变换后有标注的list,我们就可以设置批量样本数,然后调用数据堆(databunch),加上.normalize()函数后缀进行归一化。这里我们不使用预训练的模型,因此就没有理由在这里加上imagenet_stats.如果你调用normalize而不传stats参数,程序会随机抓取每个批量的样本,然后根据批量数据决定使用哪一种归一化的stats方法,这是个好方法,如果没有预训练模型的话。现在我们已经有了数据堆了,这个数据堆里含有x,y组成的数据集,
image.png

我们之前已经见过了。有趣的是,训练集中的数据已经做过数据增强了,因为你对数据进行过了变换,plot.mult()是一个fastai的函数,
image.png

我们可以调用一些函数显示图片,这么些行列的每一行每一列,这里的函数就可以抓取图片了,从训练集中抓取第一张图片,
image.png

由于你每次从训练集中抓取数据,都要从硬盘上加载,而在加载时都会同步对图片进行变换。有时候人们就会问,你的原始图片经过了多少次变换?这个问题的回答是无穷多次。我们每次从数据集中抓取一组数据,就会同时对数据进行一次随机变换,每个人得到的结果都会有一点不同。请看这里,
image.png

如果数字8的图显示很多次,数字‘8’所在的位置都会有一些不同,因为我们对图片做了随机填充。我们可以从数据堆中抓取出一个批次的数据,数据堆是有数据加载器(data loader)的,data loader让你每次抓取一个batch的数据,
image.png

那么现在你已经抓取了x batch和y batch,批次数×通道数×行数×列数。所有的fastai数据堆都有show_batch()函数以合理的方式展示数据内容。

image.png

以上就是简单的介绍,用data block API加载数据。

Basic CNN with batchnorm

让我们开始构造一个简单的CNN卷积网络,输入是28×28。我喜欢这样定义函数,
image.png

当我构建模型架构的函数时,我不厌其烦做的就是每次重新定义架构函数,因为我不想调用和之前相同的函数变量,因为我会忘记细节容易犯错,这里我的卷积网络kerne_size =3, stride=2 padding =1,让我们创建一个运用到这些参数的简单函数。一个卷积网络,这里的strde参数值是每步略去一个像素,stride =2,它每次跳跃2个格子,这意味着我们每次做卷积运算时,得到的网络尺寸会减半,我这里打上了备注(#)

model = nn.Sequential(
    conv(1, 8), # 14
    nn.BatchNorm2d(8),
    nn.ReLU(),
    conv(8, 16), # 7
    nn.BatchNorm2d(16),
    nn.ReLU(),
    conv(16, 32), # 4
    nn.BatchNorm2d(32),
    nn.ReLU(),
    conv(32, 16), # 2
    nn.BatchNorm2d(16),
    nn.ReLU(),
    conv(16, 10), # 1
    nn.BatchNorm2d(10),
    Flatten()     # remove (1,1) grid
)

‘#‘后面显示了每次运算后新的grid size是什么。在第一次卷积,输入是一个通道,因为输入是灰度图,只有一个通道,输出的通道数目就看你的需求了。你总是可以决定,要设置多少个过滤器,不管是不是一个全连接层,全连接层的情况下就是相乘矩阵的宽度,或者在2D卷积的情况下,对应的就是过滤器的数目。这里我设定为8,stride = 2,28×28的图像输入,变成了具有8个通道的14×14的特征图,因此我们就得到了8×14×14的张量激活值。然后做BatchNormalization,再然后做ReLu。

下一个卷积层的输入过滤器数量,必须等于上一个卷积层的过滤器数量。


image.png

我们可以持续增加通道的数量,因为stride =2,会减少图片的2D尺寸/网格尺寸,注意这边的grid size从7降到了4,因为你对16×7×7的图像做了stride运算,就会得到Math.celling(7/2)=4.

数据再经过BatchNorm ReLU Conv(卷积层)就变成2×2了,数据再经过BatchNorm ReLU卷积层就变成1×1了。这些计算过后,现在的输出尺寸就是10×1×1了,能理解吧。现在我们的网格大小就是一了。输出的数据的尺寸不是一个长度为10的向量,而是一个10×1×1的三阶张量。不过损失函数的输入一般是个向量而非三阶张量,那你可以对三阶张量调动Flatten()函数,flatten()函数所做的就是移除任何单位轴,得到的结果就是一个长度为10的向量。这就是我们想要的。

image.png

这就是创建出的卷积神经网络。我们把以上步骤变成learner学习器,把data和mdel作为输入参数,还有可选的参数metrics,损失函数使用的还是先前的CrossEntropyloss。

learn = Learner(data, model, loss_func = nn.CrossEntropyLoss(), metrics=accuracy)
learn.summary()
================================================================================
Layer (type)               Output Shape         Param #   
================================================================================
Conv2d                    [128, 8, 14, 14]     80                  
________________________________________________________________________________
BatchNorm2d               [128, 8, 14, 14]     16                  
________________________________________________________________________________
ReLU                      [128, 8, 14, 14]     0                   
________________________________________________________________________________
Conv2d                    [128, 16, 7, 7]      1168                
________________________________________________________________________________
BatchNorm2d               [128, 16, 7, 7]      32                  
________________________________________________________________________________
ReLU                      [128, 16, 7, 7]      0                   
________________________________________________________________________________
Conv2d                    [128, 32, 4, 4]      4640                
________________________________________________________________________________
BatchNorm2d               [128, 32, 4, 4]      64                  
________________________________________________________________________________
ReLU                      [128, 32, 4, 4]      0                   
________________________________________________________________________________
Conv2d                    [128, 16, 2, 2]      4624                
________________________________________________________________________________
BatchNorm2d               [128, 16, 2, 2]      32                  
________________________________________________________________________________
ReLU                      [128, 16, 2, 2]      0                   
________________________________________________________________________________
Conv2d                    [128, 10, 1, 1]      1450                
________________________________________________________________________________
BatchNorm2d               [128, 10, 1, 1]      20                  
________________________________________________________________________________
Lambda                    [128, 10]            0                   
________________________________________________________________________________
Total params:  12126

现在我们可以调动learn.summary()函数,可以验证输入1×28×28-->第一个卷积层-->8×14×14-->第二个卷积层-->16×7×7,依次32×4×4,16×2×2,10×1×1,flatten过后的输出层,被称为lambda.
你可以看到10×1×1,现在变成了孤零零的10,批次里的每个输入,对应的输出就是一个长度为10的向量。所以整个mini-batch的输出就是128×10的矩阵。

我们浏览一遍就是为了确认里面没有问题,我们可以取到我们之前创建的X的mini batch,把它放到GPU,直接调用model。

xb = xb.cuda()
model(xb).shape

Out[49]  : torch.Size([128, 10])

每一个PyTorch 模块,我们都可以把它当作一个函数,它返回了一个128x10的结果,和我们预期的一样。

这就是我们怎样直接得到预测值的 。调用lr_find,fit_one_cycle()就好了,我们的CNN得到了98.6%的准确率。

learn.lr_find(end_lr=100)
learn.recorder.plot()
image.png
image.png

这是从头训练的,当然,没有用预训练的结果。我们构建了我们自己的网络架构。这是你能想到的最简单架构。用了18秒训练。这就是怎样创建一个很准确的数字识别程序,很简单。

重构(Refactor) 15:42

我们把这个重构一下。不再总是写conv、batch norm、ReLU,fastai里已经有conv_layer()函数,可以让你创建conv、batch norm、ReLU的组合。


image.png

conv_layer函数还有很多其它的选项可供选择调整,不过基础版本就是我展示的这样。因此,我们可以这样重构它:

def conv2(ni,nf): return conv_layer(ni,nf,stride=2)
model = nn.Sequential(
    conv2(1, 8),   # 14
    conv2(8, 16),  # 7
    conv2(16, 32), # 4
    conv2(32, 16), # 2
    conv2(16, 10), # 1
    Flatten()      # remove (1,1) grid
)
learn = Learner(data, model, loss_func = nn.CrossEntropyLoss(), metrics=accuracy)
learn.fit_one_cycle(10, max_lr=0.1)

Total time: 00:53


image

和之前的神经网络一样。让我们再多训练一点,精确度能高达99.1%。如果在训练一分多钟,这很酷。

ResNet-ish 16:24

怎样能提升它呢?我们要创建一个更深的网络,要创建更深的网络一个很简单的方法是,在每个stride = 2的conv后添加一个stride = 1的conv。

image.png

因为步长是1的conv不会改变特征图的大小,你可以随意加。但有一个问题,是这个论文指出的,这是一个非常非常有影响力的论文,叫Deep Residual Learning for Image Recognition ,作者是微软研究院的Kaiming He和他的同事。

他们说,让我们来看看训练错误率。先不考虑其他指标(比如验证误差,测试误差等),只看看在CIFAR-10数据集上训练的网络的训练错误率。我们尝试用一个20层的网络,每层是3x3的卷积层,就像我刚刚给你们看的网络一样,只是没有batch norm。他们在训练集上训练了一个20层的网络和一个56层的网络。

这个56层的网络有更多的参数。中网络中有很多步长是1的卷积层。这个有更多参数的网络应该会过拟合得更严重,对吧?因此你会期望这个56层的网络的训练错误率很快收敛到0附近,但这没有发生。它比这个20层规模的网络误差还大。

当看到奇怪的事情发生时,真正优秀的研究者不会说“噢,不,它没有效果”,他们会说“这很有意思”。Kaiming He说“这很有意思,发生了什么?”。他说“我不知道,但我知道,我可以拿这个56层的网络,生成一个新的版本,跟之前的版本本质上一样的,让它效果至少和这个20层的神经网络一样好,他的方法是:


image

每2次卷积层,把这两个卷积的输入和这两个卷积的结果加到一起。“换句话说,不再是用:


image

而是用:
image

(这里与原论文表述的不太一致)

这样设定的话,他56层的卷积神经网络就有意义了,因为,他的理论证明了训练结果应该至少与20层的版本结果相当。因为你总是可以设定C2和C1的权重为0,对于20层以后更深的卷积层可以这么做。因为输入值x的输出值在20层后仍然是x。


image.png

所以这个如你所见(箭头所指)的地方被称为identity Connection(一致链接)。前后是一致的,没有发生什么变化,也被称为Skip Connection(跳跃连接)。
上述就是理论逻辑,是论文中提到的直觉解释。创建的这个模型,性能可以媲美20层的神经网络。也许这个神经网络包含28个隐藏层,实际上你可以跳过里面的很多卷积层。接下来发生了什么呢?他赢了那年的ImageNet比赛。他轻松地赢了比赛。事实上,直到今天,我们还在使用它。我们去年在ImageNet训练速度上破了记录。ResNet是革命性的。

ResBlock Trick 20::36

任何时候,如果你喜欢做点研究,这里有个技巧,任何时候你发现某个模型,无论是做医学图像分割,还是生成式对抗网络,或者其他目的,只要是几年前提出的,就有可能忘记将Resnet的思路考虑在内,这就是我们常说的ResBlock。


image.png

因此,如果用一系列ResBlock代替卷积路径,你几乎总能得到更好更快的结果,这是个小妙招。

可视化神经网络的损失空间 Visualizing the Loss Landscape of Neural Nets 26:16

Rachel、David、Sylvain和我刚刚参加NeurIPS会议回来,在那里,我们看到了一个新的展示,他们提出怎样可视化神经网络的损失平面,这很酷。这是一个了不起的论文,现在在看这个课的人,会理解这篇论文里最重要的概念。你可以现在阅读它。你不用全部理解它,但我敢肯定你会发现它很有意义。


图二

如果你画一个x和y的图,这是权重空间的两个投影,z是损失度。当在权重空间中移动时,一个56层的没有skip connection的神经网络,损失平面非常凹凸不平。


image.png

所以这里没有太多进展,因为被限制在这些沟壑中。但同样拥有identity connection的网络的损失空间是这样的(右侧图)。2015年Kaiming He怎么就认识到图一所示的情况不应该发生,并且有方法可以解决,然后花了三年时间,人们才知道为什么这能解决它。这让我回忆起几周前我们讲的batch norm,人们有时会在事情发生过后一段时间,才意识到是什么起了作用。
class ResBlock(nn.Module):
    def __init__(self, nf):
        super().__init__()
        self.conv1 = conv_layer(nf,nf)
        self.conv2 = conv_layer(nf,nf)
        
    def forward(self, x): return x + self.conv2(self.conv1(x))

在代码里,按照刚刚讲过的,我们创建了一个ResBlock。(这里的是实现也与论文不一致,但论坛里有按照论文实现这个的人说没什么区别;老师自己说先relu再batchnormlization效果稍好一点。)

image.png

我们创建了一个nn.Module,创建两个conv_layer,(记住,一个conv_layer是Conv2d BatchNorm、ReLU、又一个conv_layer:Conv2d ReLU BatchNorm),我们创建了两个这样的东西,然后代入forward里,我们运行conv1(x),然后对它运行conv2,然后添加x
image.png

这是fastai里的res_block函数,你可以直接调用res_block,传入参数,比如需要多少个过滤器。

image.png

这是我在notebook里定义的ResBlock,用这个ResBlock,我们可以用其中一个,我才复制了之前的卷积神经网络(CNN)除了最后一个之conv2,我在每个con2d后面添加了一个res_block,这样现在的层数是之前的三倍,因此这个网络可以做更多的计算。但不会更难优化。

然后让我们再重构一次。因为我用了conv2 res_block很多次,我们来把它们提取到一个mini的sequential模型里,这就把它重构成了这样:


image.png

如果你在尝试新的架构,持续重构可以让你更少出错。很少人这样做。你看到的大多数研究人员写的代码都很粗陋难看,因为不做重构错误重重,所以不要这样做。你们都是coder,养成好习惯,工作更轻松。

好了,这是ResNet架构。像之前一样做lr_findfit一会儿,得到了99.54%。

learn = Learner(data, model, loss_func = nn.CrossEntropyLoss(), metrics=accuracy)
learn.lr_find(end_lr=100)
learn.recorder.plot()
image
learn.fit_one_cycle(12, max_lr=0.05)
image

这很有意义,因为我们是用一个从头开始构建模型架,也是从头开始训练模型,我还没有在其他地方见过这种架构,完全是首创。就0.45%这个误差而言,基本是三四年前这个数据集的最佳成绩(state of the art)。


image

现在MNIST被认为是一个非常简单的数据集,我不能说“哇,我们打破了一些记录”这样的话。有人得到了更低的错误率。但我要说,ResNet的现在依然非常实用,这是我们在快速训练ImageNet中所使用的全部东西。还有一个原因是,它很流行,因此其作者花了大量时间来优化它的库,因此使训练更为迅速。某些新式风格架构的模型,使用separable(可分离卷积)或group convolutions(分组卷积),通常在实践中并不能十分快速地训练。

image

如果你看看fastai里res_block的定义,你会看到它和这里的有点不同,


image.png

这是因为我创建了一个叫MergeLayer的东西。MergeLayer就是,我们先暂时跳过这个dense不讲它。在forward()定义这里返回(x+x.orig)。你可以看到一些ResNet风格的。什么是x.orig?如果你创建一个叫做SequentialEX的专门的sequential模型,这里的意思是fastai序列扩展。它就像一个普通的sequential模型,但我们把输入保存在x.orig。所以这个SequentialEx(conv_layer(), conv_layer(), MergeLayer())和jupyter中的(ResBlock)效果是一样的。你可以用SequentialEx和MergeLayer轻松创建你自己的ResNet block代码。

当你创建MergeLayer时,你可以选择设置dense=True,这会发生什么?如果你这样做,它不会运行x+x.orig,它会运行cat([x,x.orig])。


image.png

换句话说,


image.png

与其在跳跃连接里做加法,它做了个拼接运算,这很有意思。因为,当输入进入你的ResBlock,而你使用了拼接而不是加法,这就不能再称为ResBlock了,而是要称为DenseBlock。这种网络结构也不再称为ResNet,而是它叫DenseNet。

DenseNet是在ResNet提出一年后提出的,如果你阅读DenseNet论文,你会发现它看上去非常复杂和不同,但实际在代码上是一样的。 只不过这里的+换成了cat(concatenate)方法。


image.png

如果输入进入DenseBlock,在这里经过几次卷积,


image

然后你得到了输出,然后得到identity connection,记住,它不是用加法,它用了concat,这里是通道轴,它会变得略大一点。然后,再经过下一个dense block,最后,我们像以前一样,得到卷积的结果,但这次的identity block比较大。只是这次变大了。

可以看到,由于使用dense block,它变得越来越大,有意思的是,确切的输入其实在这儿。


image.png

因此,不管无论你的网络有多少层,原始输入像素都在identity block里。原始的第一层特征也是,原始的第二层特征也是。可以想象,DenseNet很耗内存。但有方法可以处理这个。就是时不时的,执行一次常规卷积来压缩通道。但它们仍会耗费内存。不过用的参数更少。因此它们往往在小数据集上效果很好。此外由于它(DenseNet)可以使原始输入像素在路径上传播,因此在图像数据方面效果很好,因为在做图像分割时你会需要,重建图片分辨率,原始像素在这里是非常有用的。

这就是Resnet的内容,还有一个重要原因就是,除了Resnets本身很棒之外,促使我们介绍ResNets是因为这些跳跃连接,在其他地方也很有用,ResNets显得格外有用,在于其不同的方式来设计模型架构,用于图像分割。

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

推荐阅读更多精彩内容