fast.ai 深度学习笔记(六)(2)https://developer.aliyun.com/article/1482697
TrainPhase [2:01]
fastai 库的另一个非常酷的贡献是一个新的训练阶段 API。我将做一件我以前从未做过的事情,那就是我将展示别人的笔记本。之前我没有这样做的原因是因为我没有喜欢到足够好的笔记本,认为值得展示,但 Sylvain 在这里做得非常出色,不仅创建了这个新 API,还创建了一个描述它是什么以及如何工作等等的精美笔记本。背景是,正如大家所知,我们一直在努力更快地训练网络,部分原因是作为这个 Dawn bench 竞赛的一部分,还有一个下周您将了解的原因。我上周在论坛上提到,如果我们有一个更容易尝试不同的学习率调度等的方法,那对我们的实验将非常方便,我提出了我心目中的 API,如果有人能写出来那将非常酷,因为我现在要睡觉了,明天我有点需要它。Sylvain 在论坛上回复说,听起来是一个不错的挑战,24 小时后,它就完成了,而且效果非常酷。我想带您了解一下,因为它将使您能够研究以前没有人尝试过的东西。
这被称为 TrainPhase API,最简单的方法是展示它的示例。这是一个迭代学习率图表,你应该很熟悉。我们在学习率为 0.01 的情况下训练一段时间,然后在学习率为 0.001 的情况下训练一段时间。我实际上想创建一个非常类似于学习率图表的东西,因为大多数训练 ImageNet 的人都使用这种分阶段的方法,而这实际上并不是 fastai 内置的,因为我们通常不建议这样做。但为了复制现有的论文,我想以同样的方式做。因此,与其写一系列不同学习率的 fit、fit、fit 调用,不如能够说在这个学习率下训练 n 个周期,然后在那个学习率下训练 m 个周期。
这就是你如何做到的:
phases = [ TrainingPhase(epochs=1, opt_fn=optim.SGD, lr = 1e-2), TrainingPhase(epochs=2, opt_fn=optim.SGD, lr = 1e-3) ]
一个阶段是一个具有特定优化器参数的训练期,phases
由许多训练阶段对象组成。一个训练阶段对象说明要训练多少个周期,要使用什么优化函数,以及其他我们将看到的东西。在这里,你会看到你刚刚在那张图上看到的两个训练阶段。所以现在,不再调用learn.fit
,而是说:
learn.fit_opt_sched(phases)
换句话说,learn.fit
与一个具有这些阶段的优化器调度器。大多数传递的参数都可以像往常一样传递给 fit 函数,所以大多数通常的参数都可以正常工作。一般来说,我们只需使用这些训练阶段,你会看到它以一种通常的方式适应。然后当你说plot_lr
时,你会看到上面的图表。它不仅绘制学习率,还绘制动量,并且对于每个阶段,它告诉你使用了什么优化器。你可以关闭优化器的打印(show_text=False
),你可以关闭动量的打印(show_moms=False
),你还可以做其他一些小事情,比如一个训练阶段可以有一个lr_decay
参数:
phases = [ TrainingPhase(epochs=1, opt_fn=optim.SGD, lr = 1e-2), TrainingPhase( epochs=1, opt_fn=optim.SGD, lr = (1e-2,1e-3), lr_decay=DecayType.LINEAR ), TrainingPhase(epochs=1, opt_fn=optim.SGD, lr = 1e-3) ]
这里有一个固定的学习率,然后是线性衰减的学习率,然后是放弃这个图像的固定学习率:
lr_i = start_lr + (end_lr - start_lr) * i/n
这可能是一个很好的训练方式,因为我们知道在高学习率下,你可以更好地探索,在低学习率下,你可以更好地微调。逐渐在两者之间滑动可能更好。所以我认为这实际上不是一个坏方法。
你可以使用其他衰减类型,比如余弦:
phases = [ TrainingPhase(epochs=1, opt_fn=optim.SGD, lr = 1e-2), TrainingPhase( epochs=1, opt_fn=optim.SGD, lr =(1e-2,1e-3), lr_decay=DecayType.COSINE ), TrainingPhase(epochs=1, opt_fn=optim.SGD, lr = 1e-3) ]
这可能更有意义,作为一个真正有用的学习率退火形状。
lr_i = end_lr + (start_lr - end_lr)/2 * (1 + np.cos(i * np.pi)/n)
指数,这是一个非常流行的方法:
lr_i = start_lr * (end_lr/start_lr)**(i/n)
多项式并不是非常流行,但实际上在文献中比其他任何方法都要好,但似乎已经被大多数人忽视了。所以多项式是值得注意的。Sylvain 已经为每个曲线给出了公式。因此,使用多项式,你可以选择使用哪个多项式。我相信 p 为 0.9 的多项式是我看到的效果非常好的一个 - FYI。
lr_i = end_lr + (start_lr - end_lr) * (1 - i/n) ** p
如果在 LR 衰减时不提供学习率的元组,那么它将一直衰减到零。如你所见,你可以愉快地从不同的点开始下一个周期。
phases = [ TrainingPhase(epochs=1, opt_fn=optim.SGD, lr = 1e-2), TrainingPhase( epochs=1, opt_fn=optim.SGD, lr = 1e-2, lr_decay=DecayType.COSINE ), TrainingPhase(epochs=1, opt_fn=optim.SGD, lr = 1e-3) ]
SGDR
所以酷的是,现在我们可以仅仅使用这些训练阶段来复制所有我们现有的计划。这里有一个名为phases_sgdr
的函数,它使用新的训练阶段 API 来进行 SGDR。
def phases_sgdr(lr, opt_fn, num_cycle,cycle_len,cycle_mult): phases = [TrainingPhase(epochs = cycle_len/ 20, opt_fn=opt_fn, lr=lr/100), TrainingPhase(epochs = cycle_len * 19/20, opt_fn=opt_fn, lr=lr, lr_decay=DecayType.COSINE)] for i in range(1,num_cycle): phases.append(TrainingPhase(epochs=cycle_len* (cycle_mult**i), opt_fn=opt_fn, lr=lr, lr_decay=DecayType.COSINE)) return phases
所以你可以看到,如果他按照这个计划运行,这就是它的样子:
他甚至做了我训练时使用非常低的学习率一小段时间然后突然增加并进行几个周期的小技巧,而且这些周期的长度在增加[8:05]。而且这一切都在一个函数中完成。
1cycle
现在我们可以用一个小函数来实现新的 1cycle。
def phases_1cycle(cycle_len,lr,div,pct,max_mom,min_mom): tri_cyc = (1-pct/100) * cycle_len return [TrainingPhase(epochs=tri_cyc/2, opt_fn=optim.SGD, lr=(lr/div,lr), lr_decay=DecayType.LINEAR, momentum=(max_mom,min_mom), momentum_decay=DecayType.LINEAR), TrainingPhase(epochs=tri_cyc/2, opt_fn=optim.SGD, lr=(lr,lr/div), lr_decay=DecayType.LINEAR, momentum=(min_mom,max_mom), momentum_decay=DecayType.LINEAR), TrainingPhase(epochs=cycle_len-tri_cyc, opt_fn=optim.SGD, lr=(lr/div,lr/(100*div)), lr_decay=DecayType.LINEAR, momentum=max_mom)]
所以如果我们符合这个,我们会得到这个三角形,然后是一个稍微平坦的部分,动量是一个很酷的东西 - 动量有一个动量衰减。在第三个训练阶段,我们有一个固定的动量。所以它同时处理动量和学习率。
区分学习率+ 1cycle
我还没有尝试过的一件事,但我认为会非常有趣的是使用区分学习率和 1cycle 的组合。还没有人尝试过。这将非常有趣。我遇到的唯一一篇使用区分学习率的论文使用了一种称为 LARS 的东西。它被用来通过查看每层的梯度和均值之间的比率并使用该比率自动更改每层的学习率来训练 ImageNet,从而使用非常大的批量大小。他们发现他们可以使用更大的批量大小。这是我看到这种方法使用的唯一其他地方,但是您可以尝试结合区分学习率和不同有趣的调度尝试很多有趣的事情。
您自己的 LR 查找器
现在您可以编写不同类型的 LR finder,特别是因为现在有这个stop_div
参数,基本上意味着当损失变得太糟糕时,它将停止训练。
添加的一个有用功能是plot
函数中的linear
参数。如果您在学习率查找器中使用线性调度而不是指数调度,这是一个好主意,如果您调整到大致正确的区域,那么您可以使用线性来找到确切的区域。然后您可能希望使用线性比例来绘制它。因此,您现在也可以将 linear 传递给 plot。
您可以在每个阶段更改优化器。这比您想象的更重要,因为实际上针对 ImageNet 在非常大的批量大小上快速训练的当前最先进技术实际上是从 RMSProp 开始的,然后他们在第二部分切换到 SGD。因此,这可能是一个有趣的实验,因为至少有一篇论文现在已经表明这样可以很好地工作。再次强调,这是一个尚未被充分认识的问题。
更改数据
然后我发现最有趣的部分是您可以更改您的数据。为什么我们要更改我们的数据?因为您还记得第 1 和第 2 课,您可以在开始时使用小图像,然后稍后使用更大的图像。理论上,您可以使用这种方法更快地训练第一部分,然后记住,如果您将高度减半并将宽度减半,则每层的激活数量就会减少四分之一,因此速度可能会更快。它甚至可能泛化得更好。因此,您现在可以创建几种不同大小,例如,他有 28 和 32 大小的图像。这是 CIFAR10,所以您可以做的事情有限。然后,如果您在调用fit_opt_sched
时在data_list
参数中传入数据数组,它将在每个阶段使用不同的数据集。
data1 = get_data(28,batch_size) data2 = get_data(32,batch_size)learn = ConvLearner.from_model_data(ShallowConvNet(), data1)phases = [TrainingPhase(epochs=1, opt_fn=optim.Adam, lr=1e-2, lr_decay=DecayType.COSINE), TrainingPhase(epochs=2, opt_fn=optim.Adam, lr=1e-2, lr_decay=DecayType.COSINE)]learn.fit_opt_sched(phases, data_list=[data1,data2])
这真的很酷,因为我们现在可以像在 DAWN bench 条目中那样使用它,并查看当我们实际上用很少的代码增加大小时会发生什么。那么当我们这样做时会发生什么?答案在 DAWN bench 上对 ImageNet 的训练中。
你可以看到,谷歌用半小时在一组 TPU 上赢得了比赛。最好的非 TPU 集群结果是 fast.ai + 学生在不到 3 小时内击败了拥有 128 台计算机的英特尔,而我们只用了一台计算机。我们还击败了在 TPU 上运行的谷歌,所以使用这种方法,我们已经证明了:
- 最快的 GPU 结果
- 最快的单机结果
- 最快的公开可用基础设施结果
这些 TPU 机架,除非你是谷歌,否则无法使用。而且成本很低(72.54 美元),这个英特尔的成本是 1200 美元的计算成本——他们甚至没有写在这里,但如果你同时使用 128 台计算机,每台有 36 个核心,每台有 140G,那就是你得到的结果,与我们的单个 AWS 实例相比。所以这在我们可以做的事情方面是一种突破。我们可以在一个公开可用的机器上训练 ImageNet,这个成本是 72 美元,顺便说一句,实际上是 25 美元,因为我们使用了一个 spot 实例。我们的学生 Andrew Shaw 建立了整个系统,让我们可以同时运行一堆 spot 实例实验,并且几乎自动化,但 DAWN bench 没有引用我们使用的实际数字。所以实际上是 25 美元,而不是 72 美元。所以这个 data_list 的想法非常重要和有帮助。
CIFAR10 结果
我们的 CIFAR10 结果现在也正式发布了,你可能还记得之前最好的结果是一个多小时。这里的诀窍是使用 1cycle,所以 Sylvain 的训练阶段 API 中的所有东西实际上都是我们用来获得这些顶级结果的东西。另一位 fast.ai 学生 bkj 采用了这个方法,并做了自己的版本,他采用了一个 Resnet18,并在顶部添加了我们学到的 concat pooling,并使用了 Leslie Smith 的 1cycle,所以他上了排行榜。所以前三名都是 fast.ai 的学生,这太棒了。
CIFAR10 成本结果
成本也是一样的——前三名,你可以看到,Paperspace。Brett 在 Paperspace 上运行,得到了最便宜的结果,略胜于 bkj。
所以我认为你可以看到,目前训练更快、更便宜的有趣机会很多都是关于学习率退火、尺寸退火,以及在不同时间使用不同参数进行训练,我仍然认为大家只是触及了表面。我认为我们可以做得更快、更便宜。这对于资源受限的环境中的人们非常有帮助,基本上除了谷歌,也许还有 Facebook。
架构也很有趣,上周我们看了一下简化版本的 darknet 架构。但有一个架构我们还没有谈到,那就是理解 Inception 网络所必需的。Inception 网络实际上非常有趣,因为他们使用了一些技巧使得事情更加高效。我们目前没有使用这些技巧,我觉得也许我们应该尝试一下。最有趣、最成功的 Inception 网络是他们的 Inception-ResNet-v2 网络,其中大部分块看起来像这样:
它看起来很像标准的 ResNet 块,因为有一个恒等连接,还有一个卷积路径,我们把它们加在一起。但实际上并不完全是这样。首先,中间的卷积路径是一个 1x1 卷积,值得思考一下 1x1 卷积实际上是什么。
1x1 卷积
1x1 卷积简单地说,对于输入中的每个网格单元,您基本上有一个向量。1 乘 1 乘滤波器数量的张量基本上是一个向量。对于输入中的每个网格单元,您只需与该张量进行点积。然后,当然,对于我们正在创建的 192 个激活之一,它将是这些向量之一。因此,基本上对网格单元(1,1)进行 192 个点积,然后对网格单元(1,2)或(1,3)等进行 192 个点积。因此,您将得到与输入具有相同网格大小和输出中的 192 个通道的内容。因此,这是一种非常好的方法,可以减少或增加输入的维度,而不改变网格大小。这通常是我们使用 1x1 卷积的方式。在这里,我们有一个 1x1 卷积和另一个 1x1 卷积,然后将它们相加。然后有第三个路径,这第三个路径没有被添加。虽然没有明确提到,但这第三个路径是被连接的。有一种形式的 ResNet 基本上与 ResNet 相同,但我们不使用加号,而是使用连接。这被称为 DenseNet。这只是一个使用连接而不是加法的 ResNet。这是一个有趣的方法,因为这样,身份路径实际上被复制。因此,您可以一直保持这种流动,因此正如我们将在下周看到的那样,这对于分割等需要保留原始像素、第一层像素和第二层像素不变的情况非常有用。
连接而不是添加分支是一件非常有用的事情,我们正在连接中间分支和右侧分支。最右侧的分支正在做一些有趣的事情,首先是 1x1 卷积,然后是 1x7,然后是 7x1。那里发生了什么?所以,那里发生的事情基本上是我们真正想要做的是 7x7 卷积。我们想要做 7x7 卷积的原因是,如果有多个路径(每个路径具有不同的内核大小),那么它可以查看图像的不同部分。最初的 Inception 网络将 1x1、3x3、5x5、7x7 连接在一起或类似的东西。因此,如果我们可以有一个 7x7 滤波器,那么我们可以一次查看图像的很多部分并创建一个非常丰富的表示。因此,Inception 网络的干部,即 Inception 网络的前几层实际上也使用了这种 7x7 卷积,因为您从这个 224x224x3 开始,希望将其转换为 112x112x64。通过使用 7x7 卷积,您可以在每个输出中获得大量信息以获得这些 64 个滤波器。但问题是 7x7 卷积是很费力的。您需要将 49 个内核值乘以每个通道的每个输入像素的 49 个输入。因此,计算量很大。您可能可以在第一层中使用它(也许可以),实际上,ResNet 的第一个卷积就是 7x7 卷积。
但对于《盗梦空间》来说并非如此。它们不使用 7x7 卷积,而是使用 1x7 接着 7x1。因此,基本思想是 Inception 网络或其所有不同版本的基本思想是有许多不同的卷积宽度的独立路径。在这种情况下,概念上的想法是中间路径是 1x1 卷积宽度,右侧路径将是 7 卷积宽度,因此它们正在查看不同数量的数据,然后将它们组合在一起。但我们不希望在整个网络中都使用 7x7 卷积,因为这太耗费计算资源了。
但是如果你考虑一下[23:18],如果我们有一些输入进来,我们有一些我们想要的大滤波器,但它太大了无法处理。我们能做什么?让我们做 5x5。我们可以创建两个滤波器 —— 一个是 1x5,一个是 5x1。我们将前一层的激活传递给 1x5。我们从中取出激活,然后通过 5x1 传递,最后得到一些结果。现在另一端出来了什么?与其将其视为首先我们取激活,然后通过 1x5,然后通过 5x1,不如一起考虑这两个操作,看看一个 5x1 点积和一个 1x5 点积一起做会发生什么?实际上,你可以取一个 1x5 和 5x1,它们的外积将给你一个 5x5。现在你不能通过取这个积来创建任何可能的 5x5 矩阵,但是你可以创建很多 5x5 矩阵。所以这里的基本思想是当你考虑操作的顺序时(如果你对这里的理论更感兴趣,你应该查看 Rachel 的数值线性代数课程,这基本上是关于这个的整个课程)。但从概念上来说,很多时候你想要做的计算实际上比整个 5x5 卷积更简单。在线性代数中我们经常使用的术语是有一些低秩近似。换句话说,1x5 和 5x1 结合在一起 —— 那个 5x5 矩阵几乎和你理想情况下应该计算的 5x5 矩阵一样好。所以在实践中这往往是情况 —— 因为现实世界的本质是现实世界往往比随机性更具结构性。
酷的地方是[26:16],如果我们用 1x7 和 7x1 替换我们的 7x7 卷积,对于每个单元格,它有 14 个输入通道乘以输出通道的点积要做,而 7x7 卷积则有 49 个要做。所以速度会快得多,我们希望它的效果几乎一样好。从定义上来说,它肯定捕捉到了尽可能多的信息宽度。
如果你对这方面的知识感兴趣,特别是在深度学习领域,你可以搜索分解卷积。这个想法是 3 年或 4 年前提出的。它可能已经存在更长时间了,但那是我第一次看到它的时候。结果表明它的效果非常好,Inception 网络广泛使用它。他们实际上在他们的干部中使用它。我们之前谈过,我们倾向于添加-on —— 我们倾向于说这是主干,例如我们有 ResNet34。这是主干,其中包含所有的卷积,然后我们可以添加一个自定义头部,通常是最大池化或全连接层。更好的做法是谈论主干包含两个部分:一个是干部,另一个是主干。原因是进来的东西只有 3 个通道,所以我们希望有一系列操作将其扩展为更丰富的东西 —— 通常是 64 个通道之类的东西。
在 ResNet 中,干部非常简单。它是一个 7x7 步幅 2 卷积,后面跟着一个步幅 2 最大池(如果我记得正确的话)。Inception 有一个更复杂的干部,其中包括多个路径的组合和连接,包括因子化卷积(1x7 和 7x1)。我很感兴趣的是,如果你在 Inception 干部上堆叠一个标准的 ResNet 会发生什么。我认为这将是一个非常有趣的尝试,因为 Inception 干部是一个非常精心设计的东西,以及如何将 3 通道输入转换为更丰富的东西似乎非常重要。而所有这些工作似乎都被抛弃了。我们喜欢 ResNet,它的效果非常好。但是如果我们在 Inception 干部上放置一个密集的网络骨干呢?或者如果我们用标准 ResNet 中的 1x7 和 7x1 因子化卷积替换 7x7 卷积呢?有很多事情我们可以尝试,我认为这将是非常有趣的。所以这是关于潜在研究方向的一些想法。
这就是我小小一堆随机东西部分的内容[29:51]。稍微接近这个实际主题的是图像增强。我将简要谈一下一篇新论文,因为它与我刚刚讨论的内容和我们接下来要讨论的内容有很大联系。这是一篇关于渐进式 GAN 的论文,来自 Nvidia:渐进增长的 GANs 用于提高质量、稳定性和变化。渐进式 GANs 采用了逐渐增加图像大小的想法。这是我所知道的唯一另一个人们实际上逐渐增加图像大小的方向。令我惊讶的是,这篇论文实际上非常受欢迎,知名度很高,而且受欢迎,但是人们还没有将逐渐增加图像大小的基本思想应用到其他地方,这显示了你可以在深度学习研究社区中期望找到的创造力水平。
他们真的回到了 4x4 GAN 开始[31:47]。实际上,他们试图复制 4x4 像素,然后是 8x8(上面左上角的那些)。这是 CelebA 数据集,所以我们试图重新创建名人的图片。然后他们去 16x16,32,64,128,然后 256。他们做的一个非常聪明的事情是,随着尺寸的增加,他们还向网络添加更多层。这有点说得通,因为如果你在做更多的 ResNet 类型的事情,那么你应该能够在每个网格单元大小输出一些有意义的东西,所以你应该能够在其上叠加东西。当他们这样做时,他们做了另一个聪明的事情,他们添加了一个跳过连接,并逐渐改变线性插值参数,使其越来越远离旧的 4x4 网络,朝向新的 8x8 网络。一旦完全移动到新网络,他们就会丢弃那个额外的连接。细节并不太重要,但它使用了我们谈论过的基本思想,逐渐增加图像大小和跳过连接。这是一篇很棒的论文,因为这是一种罕见的情况,好的工程师实际上构建了一些以非常明智的方式工作的东西。现在这并不奇怪,这实际上来自 Nvidia 自己。Nvidia 并不发表很多论文,有趣的是,当他们这样做时,他们构建了一些非常实用和明智的东西。所以我认为这是一篇很棒的论文,如果你想整合我们学到的许多不同的东西,而且没有太多的重新实现,所以这是一个有趣的项目,也许你可以继续研究并找到其他东西。
接下来会发生什么[33:45]。我们最终会升级到 1024x1024,你会看到图像不仅分辨率更高,而且质量更好。所以我要看看你能否猜出以下哪一个是假的:
它们全都是假的。这是下一个阶段。你一直往上走,然后突然爆炸。所以 GANs 和其他东西变得疯狂,你们中的一些人可能在这周看到了这个[34:16]。这个视频刚刚发布,是巴拉克·奥巴马的演讲,让我们来看一下:
正如你所看到的,他们使用这种技术来实际移动奥巴马的脸,就像乔丹·皮尔的脸在移动一样。你现在基本上拥有了所有需要的技术。这是一个好主意吗?
fast.ai 深度学习笔记(六)(4)https://developer.aliyun.com/article/1482700