问题

Resnet到底在解决一个什么问题呢?

回答
要理解 ResNet 到底解决了什么问题,咱们得先聊聊它诞生之前,深度学习在图像识别领域遇到的一些“瓶颈”和“尴尬”。

故事的开端:层层叠叠的神经网络与“性能迷思”

话说在 ResNet 出现之前,大家对神经网络的看法是这样的:层数越多,性能越好。 就像一个孩子,学的东西越多,懂得越多,也就越聪明。所以,研究人员们就想方设法地加深网络,一层接一层地往上堆。

从 LeNet、AlexNet 到 VGG,可以看到一个明显的趋势:网络越来越深了。 VGG16、VGG19 这些模型,动辄十几层甚至二十几层,在 ImageNet 这样的图像识别大赛上取得了非常亮眼的成绩,一度成为了主流。大家觉得,只要把网络堆够深,就能解决更复杂的图像识别问题。

然而,事情并没有那么简单。

问题的出现:梯度消失/爆炸的“绊脚石”

当网络层数不断加深时,一个非常棘手的问题就暴露出来了:梯度消失(Vanishing Gradients) 和 梯度爆炸(Exploding Gradients)。

简单来说,训练神经网络的过程就像是在调整一个巨大的函数,我们通过“反向传播”来计算每个参数对最终结果的影响(也就是梯度),然后根据这个梯度来更新参数,让模型越来越“准”。

梯度消失: 想象一下你在一长串数字上做乘法,如果有很多小于1的数,乘来乘去,最后的数字就会变得越来越小,小到几乎可以忽略不计。在反向传播中,梯度在经过很多层激活函数(比如 Sigmoid、Tanh)和权重矩阵时,也会被不断“缩小”,当它传到最前面的几层时,就可能变得非常小,以至于模型的前面几层几乎学不到东西,或者学习效率极其低下。
梯度爆炸: 反过来,如果有很多大于1的数,乘来乘去,数字就会变得越来越大,失控。在神经网络中,这会导致梯度非常大,参数更新幅度过大,模型在训练过程中不稳定,甚至无法收敛。

虽然通过 权重初始化、激活函数选择(比如 ReLU) 以及 Batch Normalization 等技术,在一定程度上缓解了梯度消失/爆炸的问题,使得训练更深的网络成为可能,但是这种“堆叠”式的加深,到了一定程度,性能反而会出现饱和,甚至下降!

深度网络的“退步”现象:性能的“天花板”

这就是 ResNet 出现之前大家遇到的一个令人沮丧的现象:理论上更深的网络应该能表达更复杂的函数,性能应该更好,但实际上,一旦网络堆到一定深度(比如 20 层以上),你再往上加层,识别准确率反而会下降。

这就像你给一个学生加了很多课程,本来想让他学更多知识,结果他却因为知识点太多、消化不过来,反而学得不如以前好。

这到底是怎么回事?是网络太深了模型“过拟合”了吗?倒也不是。研究人员发现,即使是在训练集上,更深的网络也出现了性能下降的现象,这说明问题不仅仅在于泛化能力,而是网络本身在学习过程中遇到了障碍。

ResNet 的“革命”:短路连接的智慧

这时候,ResNet 就应运而生了,它提出的核心思想,可以说是对“堆叠”式加深网络的一种“反思”和“优化”。它试图解决的就是:如何有效训练非常非常深的神经网络,并且在加深网络的过程中,不损失性能,甚至能进一步提升性能。

ResNet 的“救星”就是它最核心的创新——残差块(Residual Block)。

想象一下,你有一个很复杂的任务,直接去学习这个任务的“最终输出”,可能很难。但如果我告诉你,你只需要学习这个任务相对于某个“基准”的“差值”(也就是“残差”),是不是就容易多了?

在神经网络中,ResNet 就是这么做的。一个标准的 ResNet 残差块,包含一个“恒等映射”(Identity Mapping),也就是一个“跳过连接”(Skip Connection)。这个跳过连接直接将输入信号绕过中间的几层非线性变换(比如卷积层和激活层),然后加到这些中间层的输出上。

公式可以简单表示为:$H(x) = F(x) + x$

其中:
$x$ 是输入。
$F(x)$ 是中间几层(通常是两层或三层卷积)学习到的“残差”部分。
$H(x)$ 是整个残差块的输出。

这样做有什么好处呢?

1. 解决了梯度消失问题: 通过这个“跳过连接”,梯度在反向传播时可以绕过中间层,直接传播到前面的层。这意味着,即使中间层学到的残差 $F(x)$ 非常接近于零(就像一个无效的层),但因为有 $x$ 这个恒等映射的存在,梯度至少不会因为中间层的“消失”而完全消失。它至少能保证信息能够顺利地传递下去。
2. 更容易学习恒等映射: 如果一个网络层完全不需要做任何变化(即理想情况下,增加的层是多余的),那么它只需要学习一个恒等映射。通过残差连接,这变得非常容易。模型只需要让 $F(x)$ 逼近于零就可以了,而这比直接让几层网络逼近一个恒等映射(即让每一层的输出都等于输入)要容易得多。这相当于给网络增加了一个“保险丝”,如果中间的层学不好,就“回退”到原始输入的状态,保证了性能不下降。
3. 实现了更深网络的训练: 正是因为解决了梯度消失和更容易学习恒等映射的问题,ResNet 使得训练几百层甚至上千层的深度网络成为可能,并且在这些超深网络上取得了比浅层网络更好的性能。

所以, ResNet 到底解决了什么问题?

ResNet 核心解决的问题是:如何有效地训练非常非常深的神经网络,并且克服了传统深度网络在加深过程中出现的“性能退化”问题(即网络层数增加到一定程度后,准确率反而下降)。它通过引入“残差连接”或“跳过连接”,使得梯度能够更顺畅地反向传播,也让网络更容易学习到有意义的特征,从而能够构建并训练出比以往深得多的模型,极大地推动了深度学习在图像识别等领域的进步。

可以说,ResNet 的出现,让深度学习的“深度”优势得以真正释放,打破了之前性能的“天花板”,为后续更复杂的深度学习模型(如 DenseNet, EfficientNet 等)奠定了坚实的基础。它不仅仅是一个技术上的突破,更是一种对深度学习模型设计思路的革新。

网友意见

user avatar

看了这个问题之后我思考了很久,于是写出了这篇专栏,现在贴过来当答案。

首先是跟着论文的思路走,了解作者提出resnet的“心路历程”,最后也有些个人整理的理解。

ps:欢迎关注我的专栏,这段时间我会持续更新,并且在更完约莫十几篇论文阅读后,会再写一些模型实现/代码方面的理解。


一、引言:为什么会有ResNet? Why ResNet?

神经网络叠的越深,则学习出的效果就一定会越好吗?

答案无疑是否定的,人们发现当模型层数增加到某种程度,模型的效果将会不升反降。也就是说,深度模型发生了退化(degradation)情况。

那么,为什么会出现这种情况?

1. 过拟合? Overfitting?

首先印入脑海的就是Andrew Ng机器学习公开课[1]的过拟合问题


在这个多项式回归问题中,左边的模型是欠拟合(under fit)的此时有很高的偏差(high bias),中间的拟合比较成功,而右边则是典型的过拟合(overfit),此时由于模型过于复杂,导致了高方差(high variance)。

然而,很明显当前CNN面临的效果退化不是因为过拟合,因为过拟合的现象是"高方差,低偏差",即测试误差大而训练误差小。但实际上,深层CNN的训练误差和测试误差都很大。

2. 梯度爆炸/消失? Gradient Exploding/Vanishing?

除此之外,最受人认可的原因就是“梯度爆炸/消失(弥散)”了。为了理解什么是梯度弥散,首先回顾一下反向传播的知识。

假设我们现在需要计算一个函数 , , ,在时的梯度,那么首先可以做出如下所示的计算图。

将 , ,带入,其中,令 ,一步步计算,很容易就能得出 。

这就是前向传播(计算图上部分绿色打印字体与蓝色手写字体),即:

前向传播是从输入一步步向前计算输出,而反向传播则是从输出反向一点点推出输入的梯度(计算图下红色的部分)。

注:这里的反向传播假设输出端接受之前回传的梯度为1(也可以是输出对输出求导=1)

观察上述反向传播,不难发现,在输出端梯度的模值,经过回传扩大了3~4倍。

这是由于反向传播结果的数值大小不止取决于求导的式子,很大程度上也取决于输入的模值。当计算图每次输入的模值都大于1,那么经过很多层回传,梯度将不可避免地呈几何倍数增长(每次都变成3~4倍,重复上万次,想象一下310000有多大……),直到Nan。这就是梯度爆炸现象。

当然反过来,如果我们每个阶段输入的模恒小于1,那么梯度也将不可避免地呈几何倍数下降(比如每次都变成原来的三分之一,重复一万次就是3-10000),直到0。这就是梯度消失现象。值得一提的是,由于人为的参数设置,梯度更倾向于消失而不是爆炸。

由于至今神经网络都以反向传播为参数更新的基础,所以梯度消失问题听起来很有道理。然而,事实也并非如此,至少不止如此。

我们现在无论用Pytorch还是Tensorflow,都会自然而然地加上Bacth Normalization(简称BN),而BN的作用本质上也是控制每层输入的模值,因此梯度的爆炸/消失现象理应在很早就被解决了(至少解决了大半)。

不是过拟合,也不是梯度消失,这就很尴尬了……CNN没有遇到我们熟知的两个老大难问题,却还是随着模型的加深而导致效果退化。无需任何数学论证,我们都会觉得这不符合常理。等等,不符合常理……

3. 为什么模型退化不符合常理?

按理说,当我们堆叠一个模型时,理所当然的会认为效果会越堆越好。因为,假设一个比较浅的网络已经可以达到不错的效果,那么即使之后堆上去的网络什么也不做,模型的效果也不会变差

然而事实上,这却是问题所在。“什么都不做”恰好是当前神经网络最难做到的东西之一。

MobileNet V2的论文[2]也提到过类似的现象,由于非线性激活函数Relu的存在,每次输入到输出的过程都几乎是不可逆的(信息损失)。我们很难从输出反推回完整的输入。

也许赋予神经网络无限可能性的“非线性”让神经网络模型走得太远,却也让它忘记了为什么出发(想想还挺哲学)。这也使得特征随着层层前向传播得到完整保留(什么也不做)的可能性都微乎其微。

用学术点的话说,这种神经网络丢失的“不忘初心”/“什么都不做”的品质叫做恒等映射(identity mapping)

因此,可以认为Residual Learning的初衷,其实是让模型的内部结构至少有恒等映射的能力。以保证在堆叠网络的过程中,网络至少不会因为继续堆叠而产生退化!

二、深度残差学习 Deep Residual Learning

1. 残差学习 Residual Learning

前面分析得出,如果深层网络后面的层都是是恒等映射,那么模型就可以转化为一个浅层网络。那现在的问题就是如何得到恒等映射了。

事实上,已有的神经网络很难拟合潜在的恒等映射函数H(x) = x。

但如果把网络设计为H(x) = F(x) + x,即直接把恒等映射作为网络的一部分。就可以把问题转化为学习一个残差函数F(x) = H(x) - x.

只要F(x)=0,就构成了一个恒等映射H(x) = x。 而且,拟合残差至少比拟合恒等映射容易得多。

于是,就有了论文[3]中的Residual block结构

图中右侧的曲线叫做跳接(shortcut connection),通过跳接在激活函数前,将上一层(或几层)之前的输出与本层计算的输出相加,将求和的结果输入到激活函数中做为本层的输出。

用数学语言描述,假设Residual Block的输入为 ,则输出 等于:

其中 是我们学习的目标,即输出输入的残差 。以上图为例,残差部分是中间有一个Relu激活的双层权重,即:

其中 指代Relu,而 指代两层权重。


顺带一提,这里一个Block中必须至少含有两个层,否则就会出现很滑稽的情况:

显然这样加了和没加差不多……

2.网络结构与维度问题

论文中原始的ResNet34与VGG的结构如上图所示,可以看到即使是当年号称“Very Deep”的VGG,和最基础的Resnet在深度上相比都是个弟弟。

可能有好奇心宝宝发现了,跳接的曲线中大部分是实现,但也有少部分虚线。这些虚线的代表这些Block前后的维度不一致,因为去掉残差结构的Plain网络还是参照了VGG经典的设计思路:每隔x层,空间上/2(下采样)但深度翻倍。

也就是说,维度不一致体现在两个层面

  • 空间上不一致
  • 深度上不一致

空间上不一致很简单,只需要在跳接的部分给输入x加上一个线性映射 ,即:

而对于深度上的不一致,则有两种解决办法,一种是在跳接过程中加一个1*1的卷积层进行升维,另一种则是直接简单粗暴地补零。事实证明两种方法都行得通。

注:深度上和空间上维度的不一致是分开处理的,但很多人将两者混为一谈(包括目前某乎一些高赞文章),这导致了一些人在模型的实现上感到困惑(比如当年的我)。

3. torchvision中的官方实现

事实上论文中的ResNet并不是最常用的,我们可以在Torchvision的模型库中找到一些很不错的例子,这里拿Resnet18为例:

运行代码:

       import torchvision model = torchvision.models.resnet18(pretrained=False) #我们不下载预训练权重 print(model)     

得到输出:

       ResNet(   (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)   (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)   (relu): ReLU(inplace)   (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)   (layer1): Sequential(     (0): BasicBlock(       (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       (relu): ReLU(inplace)       (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)     )     (1): BasicBlock(       (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       (relu): ReLU(inplace)       (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)     )   )   (layer2): Sequential(     (0): BasicBlock(       (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)       (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       (relu): ReLU(inplace)       (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       (downsample): Sequential(         (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)         (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       )     )     (1): BasicBlock(       (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       (relu): ReLU(inplace)       (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)     )   )   (layer3): Sequential(     (0): BasicBlock(       (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)       (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       (relu): ReLU(inplace)       (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       (downsample): Sequential(         (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)         (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       )     )     (1): BasicBlock(       (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       (relu): ReLU(inplace)       (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)     )   )   (layer4): Sequential(     (0): BasicBlock(       (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)       (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       (relu): ReLU(inplace)       (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       (downsample): Sequential(         (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)         (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       )     )     (1): BasicBlock(       (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)       (relu): ReLU(inplace)       (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)       (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)     )   )   (avgpool): AvgPool2d(kernel_size=7, stride=1, padding=0)   (fc): Linear(in_features=512, out_features=1000, bias=True) )     

薰风说 Thinkings

上述的内容是我以自己的角度思考作者提出ResNet的心路历程,我比作者蔡很多,所以难免出现思考不全的地方。

ResNet是如此简洁高效,以至于模型提出后还有无数论文讨论“ResNet到底解决了什么问题(The Shattered Gradients Problem: If resnets are the answer, then what is the question?)”[4]

论文[4]认为,即使BN过后梯度的模稳定在了正常范围内,但梯度的相关性实际上是随着层数增加持续衰减的。而经过证明,ResNet可以有效减少这种相关性的衰减。

对于 层的网络来说,没有残差表示的Plain Net梯度相关性的衰减在 ,而ResNet的衰减却只有 。这也验证了ResNet论文本身的观点,网络训练难度随着层数增长的速度不是线性,而至少是多项式等级的增长(如果该论文属实,则可能是指数级增长的)


而对于“梯度弥散”观点来说,在输出引入一个输入x的恒等映射,则梯度也会对应地引入一个常数1,这样的网络的确不容易出现梯度值异常,在某种意义上,起到了稳定梯度的作用。

除此之外,shortcut类似的方法也并不是第一次提出,之前就有“Highway Networks”。可以只管理解为,以往参数要得到梯度,需要快递员将梯度一层一层中转到参数手中(就像我取个快递,都显示要从“上海市”发往“闵行分拣中心”,闵大荒日常被踢出上海籍)。而跳接实际上给梯度开了一条“高速公路”(取快递可以直接用无人机空投到我手里了),效率自然大幅提高,不过这只是个比较想当然的理由。


上面的理解很多论文都讲过,但我个人最喜欢下面两个理解。

第一个已经由Feature Pyramid Network[5]提出了,那就是跳连接相加可以实现不同分辨率特征的组合,因为浅层容易有高分辨率但是低级语义的特征,而深层的特征有高级语义,但分辨率就很低了。

第二个理解则是说,引入跳接实际上让模型自身有了更加“灵活”的结构,即在训练过程本身,模型可以选择在每一个部分是“更多进行卷积与非线性变换”还是“更多倾向于什么都不做”,抑或是将两者结合。模型在训练便可以自适应本身的结构,这听起来是多么酷的一件事啊!

有的人也许会纳闷,我们已经知道一个模型的来龙去脉了,那么在一个客观上已经十分优秀的模型,强加那么多主观的个人判断有意思吗?

然而笔者还是相信,更多角度的思考有助于我们发现现有模型的不足,以及值得改进的点。比如我最喜欢的两个理解就可以引申出这样的问题“虽然跳接可以结合不同分辨率,但ResNet显然没有充分利用这个优点,因为每个shortcut顶多跨越一种分辨率(大部分还不会发生跨越)”。

那么“如果用跳接组合更多分辨率的特征,模型的效果会不会更好?”这就是DenseNet回答我们的问题了。

参考文献

[1]coursera.org/learn/mach
[2]Sandler M, Howard A, Zhu M, et al. MobileNetV2: Inverted Residuals and Linear Bottlenecks[J]. 2018.

[3]He K, Zhang X, Ren S, et al. Deep Residual Learning for Image Recognition[J]. 2015.

[4]Balduzzi D , Frean M , Leary L , et al. The Shattered Gradients Problem: If resnets are the answer, then what is the question?[J]. 2017.

[5]Lin T Y , Dollár, Piotr, Girshick R , et al. Feature Pyramid Networks for Object Detection[J]. 2016.


安利时间,我一直致力于结合感性的人文艺术思考和理性的数学分析。并梦想着帮助大家更好的理解计算机视觉/人工智能/深度学习有关的种种问题。
若你同样想听我用如上讲故事的口吻介绍人工智能的干货知识,欢迎关注我的知乎专栏:

另外,在开学之后,我会继续在上海交大坚持把自己喜欢的音乐录制成视频,权当安利自己爱听的歌,如果你想听一听的话,不妨试试我的最新作:勾指起誓的竹笛版!



类似的话题

  • 回答
    要理解 ResNet 到底解决了什么问题,咱们得先聊聊它诞生之前,深度学习在图像识别领域遇到的一些“瓶颈”和“尴尬”。故事的开端:层层叠叠的神经网络与“性能迷思”话说在 ResNet 出现之前,大家对神经网络的看法是这样的:层数越多,性能越好。 就像一个孩子,学的东西越多,懂得越多,也就越聪明。所以.............
  • 回答
    关于ResNet是否仅仅是一个“深度学习的trick”,我认为这是一个值得深入探讨的问题。简单地将其归类为“trick”未免过于轻率,因为它触及了深度学习领域一个非常核心且影响深远的技术突破。与其说是trick,不如说它是一种精妙的结构设计,巧妙地解决了深度神经网络训练中的一个根本性难题,从而极大地.............
  • 回答
    好的,我们来详细解析一下 ResNet(残差网络)中的核心概念——残差块(Residual Block),以及它里面的 F(x) 究竟长什么样子。首先,要理解 F(x) 在 ResNet 中的意义,我们需要回顾一下残差网络要解决的问题以及它的基本思想。为什么需要残差网络?在深度学习早期,人们普遍认为.............
  • 回答
    何凯明博士的ResNet论文被引用超过10万次,这一数字在全球学术界都是一个极其辉煌的成就,可以毫不夸张地说,这是深度学习领域最具影响力的论文之一。评价其学术贡献和地位,需要从多个维度进行细致的分析。一、 ResNet的突破性贡献及其深远影响要理解ResNet的价值,首先要回到它诞生的时代背景。在2.............
  • 回答
    timm作者发布ResNet新基准:ResNet50提至80.4,这对后续研究会带来哪些影响?timm库作者Ross Wightman在ImageNet数据集上发布了ResNet50的新基准,将准确率提升至80.4%。这个消息在计算机视觉领域引起了不小的关注,尤其是对于那些仍然依赖ResNet系列作.............
  • 回答
    FAIR(Facebook AI Research)团队最新推出的 RegNet 确实是计算机视觉领域一个非常重要的进展。它并非一个全新的模型架构,而是对神经网络架构搜索 (NAS) 的一个重新思考和系统性研究,旨在发现一组在效率和性能之间取得良好权衡的“规则化”网络家族。要评价 RegNet,我们.............
  • 回答
    说起《开端》这部剧,大家肯定都不陌生。这部悬疑剧播出后火遍全网,很多人都被它烧脑的剧情和出乎意料的结局吸引。不过,除了剧情本身,《开端》的英文名“Reset”也着实引起了不少人的好奇。这个词用得真是绝妙,它不仅点明了剧集的核心设定,更能引发观众对时间、选择和命运的深层思考。“Reset”这个词,直译.............
  • 回答
    .......

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有