如何快速高效的训练ResNet,各种奇技淫巧(八):一大波技巧

2024-06-21 09:18

本文主要是介绍如何快速高效的训练ResNet,各种奇技淫巧(八):一大波技巧,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

点击上方“AI公园”,关注公众号,选择加“星标“或“置顶”


作者:David Page

编译:ronghuaiyang

导读

这个系列介绍了如何在CIFAR10上高效的训练ResNet,这是第八篇,给大家总结了一大波的技巧,这些技巧同样可以用到提升准确率上。

在本系列的最后一篇文章中,我们绕了一圈,加快了我们的单gpu训练,与多gpu竞争。我们推出了一系列标准和不太标准的技巧,通过增加测试时间将训练时间缩短到34秒或26秒。

到目前为止我们已经占领的地盘包括,超参数调优,权值衰减,batch norm,我们的CIFAR10训练到94%的测试准确率的位置在DAWNBench排行榜上已经下滑了5个位置。

640?wx_fmt=jpeg

前六个都使用了9层resnet,它们是我们在之前的blog中开发的网络的近亲。第一个是来自Kakao Brain的4-GPU实现,它在37秒内完成。同样的单gpu版本以68s排在第三,比去年的单gpu版本明显提高了7s,尽管仔细检查显示这些提交使用的是测试时间增强(TTA)。我们将在文章的最后讨论这种方法的有效性(我们的结论是,任何合理的限制都应该基于总推断成本,这里使用的轻度TTA的形式以及轻量级的网络将在这方面进行传递)。请注意,我们之前的提交,允许相同的TTA,将在没有进一步更改的情况下,在19个epoch的训练策略上实现了60秒的时间。

在文章的末尾,我们的单gpu实现轻松地超越了顶级多gpu,以34秒的时间夺回了梦寐以求的DAWNBench皇冠,并在系列开始时实现了10倍于单gpu最先进水平的改进!使用与Kakao Brain提交的相同的TTA,这个数字下降到26s。我们通过积累一系列小的改进(通常是0.1-0.3%的绝对测试精度)来达到这些时间,这些改进可以用更短的训练时间来交换。这些改进是基于一组标准和不那么标准的技巧。

我们的主要武器是统计显著性。一次训练的测试准确度的标准偏差约为0.15%,在两次训练之间进行比较时,我们需要将其乘以√2。这比我们正在测量的许多影响都要大。考虑到训练时间很快就会降到一分钟以下,我们可以进行10-100次的实验,以确保改进是真实的,这使我们能够取得一致的进展。

尖锐的实验结果对于推进这个领域是至关重要的,但是如果基线调整得不好或者运行的次数太少,实验验证就没有什么价值。今天这篇文章的主要目标是提供一个良好的调优的基线,在此基础上测试新技术,允许在一个GPU上在几分钟内完成统计上显著数量的训练运行。我们在文章结束时确认,如果训练进程收敛一致,训练速度的改进将转化为最终准确性的改进。

GPU上的预处理 (70s)

我们从一些代码优化的实际问题开始。我们早期DAWNBench提交的日志显示,在数据预处理上浪费了3秒,这将计入训练时间。回想一下,我们在训练前对数据集进行标准化、转置和填充,以避免在每个epochs重复工作。

我们可以做得更好,通过传输数据到GPU,在那里预处理,然后传输回CPU随机数据增加和批处理。将整个数据集(uint8格式)移动到GPU只需微不足道的40毫秒,而在GPU上完成预处理则更快,大约只需15毫秒。大部分时间花在将预处理数据集传输回CPU上,这需要近半秒的时间。这比我们之前的3s有了很大的改进,但似乎也有点浪费,因为数据需要在批量处理和增强之后再次传输到GPU,在每一个训练步骤都会导致进一步的延迟。我们可以通过在GPU上做数据增强来消除这个开销吗?

答案是肯定的,但这需要一点小心。如果我们天真地将增强应用于单个训练示例,比如在CPU上,我们会导致启动多个GPU内核来处理每个项的大量开销。我们可以通过对一组示例应用相同的增强来避免这种情况,并且我们可以通过预先打乱数据来保持随机性。

例如,考虑对CIFAR10图像应用8×8 cutout增强。在一张32×32的图像中有625个可能的8×8个裁剪区域,因此我们可以通过打乱数据集并将其分成625个组,每个组对应一个可能的裁剪区域,从而实现随机增强。如果我们选择大小均匀的组,这与为每个示例随机选择不同(这会导致组大小不规则),但已经足够接近了。作为进一步的优化,如果用于增加的组的数量变得过大,我们可以考虑将其限制在一个合理的限制范围内——比如每个epoch随机选择200个组。

我们的基本实现相当简单,只需要大约35行代码(没有任何Pytorch dataloader)。下面是这4张图片的两个随机放大,以显示它的实际效果:

640?wx_fmt=jpeg

更重要的是,它的速度很快,只需不到400毫秒就可以遍历24个周期的训练数据,并应用随机裁剪、水平翻转和裁剪数据增强、随机打乱和批处理。这比将数据集一次传输到CPU所花费的时间还少!此外,由于我们不再与GPU竞争CPU预处理队列,我们可以完全停止担心数据加载,即使训练变得更快。

注意:我们这样做的依赖是数据集足够小,存储和操作可以作为一个整体在GPU内存中实现,但更复杂的实现将解决这个—或者可以切换到一个工业强度的解决方案如(Nvidia DALI)。

如果我们用新的GPU数据处理重新运行网络并从我们的DAWNBench提交训练,训练时间将下降到70秒以下,使我们在排行榜上上升两位!

混合精度训练

在我们最初DAWNBench提交和在上面测试的代码中,我们简单把模型转换为float16,并没有在意没有所谓的细节(混合精度训练,虽然我们用了一个基本的“损失缩放”,把batch中所有的损失加起来,而不是平均。实现适当的混合精度训练是很简单的,但是这增加了大约一秒钟的总训练时间,我们发现它对最终的精度没有影响,所以我们下面没有继续做它。

去掉最大池化(64s)

把最大池化与一个单调递增的激活函数(如ReLU)交换。先使用池化应该更高效。好的编译器也可能做到这点,但是现在让我们手动切换顺序。这是一个典型的conv-pool块:

640?wx_fmt=png

交换之后:

640?wx_fmt=png

切换顺序将在24个epochs的训练时间内进一步减少3秒,而网络的计算完全没有变化!也许我们应该尝试一些更激进的方法,把最大池化移动到batch norm之前。这将获得进一步的效率提高,但会改变网络,所以我们需要测试训练的效果。

640?wx_fmt=png

结果对测试准确度有一个小的负面影响,与我们的基线94.1%相比,测试准确度变成了94.0%(平均50次)。更积极的是,这大大减少了5秒的训练时间。我们可以通过在训练中增加额外的时间来恢复以前的准确性。这是这篇文章中唯一一次我们选择了一个导致更糟的准确性的“改进”!一个更高效的网络带来的5s的好处,超过了额外训练时间带来的2.5秒的损失。净效应使我们的时间达到64秒,在排行榜上名列第三。

标签平滑(59s)

标签平滑是一种在分类问题中提高神经网络训练速度和泛化能力的成熟技巧。它包括混合独热目标概率与均匀分布在类标签内的交叉熵损失。这有助于稳定的产生分布,并防止网络做出过度自信的预测,而这种预测可能会抑制进一步的训练。让我们试一试,标签平滑参数0.2已经是非常粗的手工优化了,但结果对一系列选择不是太敏感。

640?wx_fmt=png

测试精度提高到94.2%(平均运行50次)。我们可以通过减少epoch的数量来换取训练时间。根据经验,每提高0.1%的测试准确度,我们就减少一个训练周期,这是大致跟踪了额外训练周期的收益得到的。我们减少了热身阶段——在此期间,学习率线性增长——与整个epochs的数量成比例。

23个epochs的正确率为94.1%,训练时间低于1分钟!

CELU激活函数(52s)

我们希望通过使用一个光滑的激活函数来帮助优化过程,而不是使用在原点处不连续的ReLU。这也可能有助于泛化,因为平滑的函数能表达的函数类别较少——在较大的平滑极限下,我们恢复了线性网络。

除此之外,我们对ReLU很满意,所以我们将选择一个简单的平滑选项。我们选择的是连续可微指数线性单元或CELU激活函数,因为它是平滑的(不像ELU),而且PyTorch的实现比其他的Softplus激活要快。除了平滑之外,CELU还对ReLU使用了x-和y-shift,如下所示,但是考虑到我们使用的batch norm,这些无关紧要。

640?wx_fmt=png

我们手动调整平滑参数值α为0.075,注意,这是远低于默认值为1。这样达到了94.3%的测试精度(平均50次),有了显著的提高,从而进一步减少了3个epochs的训练,20个epochs的时间为52秒,达到了94.1%的准确性。

Ghost batch norm (46s)

Batch norm似乎在batch size为32左右时工作得最好。原因可能与batch统计中的噪声有关,特别是在中间大小的batch的有益调节效果和小batch的过量噪声之间的平衡。

我们的batch size为512,如果不严重影响训练时间,我们无法减少它们,但是我们可以将batch norm单独应用于训练batch的子集。这种技术称为“ghost” batch norm,通常在分布式设置中使用,但在单个节点上使用大量batch norm时同样有用。在PyTorch中没有直接支持它,但是我们可以很容易地自己实现它。

这对epoch测试的20个epochs的准确率(94.2%)提供了一个提升。随着训练时间变得越来越短,偶尔提高学习率来弥补这一不足是有帮助的。如果将最大学习率提高50%,则可以在18个epochs和46秒的训练时间内达到94.1%的正确率。

冻结batch norm的缩放(43s)

Batch norm对每个通道的均值和方差进行标准化,但随后是可学习的缩放和偏差。我们的Batch norm层由(平滑的)ReLUs继承,因此可学习的偏差可以允许网络优化每个通道的稀疏程度。另一方面,如果通道尺度有很大变化,这可能会减少有效通道的数量,并带来瓶颈。让我们来看看这些参数在训练中的动态变化:

640?wx_fmt=png

640?wx_fmt=png

图中发生了很多事情,但有一件事很突出,那就是缩放参数并没有做太多的学习,在很大程度上是在权值衰减的控制下进化的。让我们试着把它们冻结在一个恒定的值1/4——大概是它们在训练中点的平均值。最后一层的可学习的缩放尺度要大一些,但是我们可以调整网络输出的规模来进行补偿。

实际上我们可以固定batch norm的缩放参数为1,或者我们也可以通过因子4调节CELU的α,通过因子42和1/42来调节学习率和权值衰减。我们更喜欢这样做,因为它使通道尺度对偏差的学习率动态的影响更加明显。

18个epochs的测试精度提高到了94.2%。有趣的是,如果我们没有提高batch norm偏差的学习率,我们将获得一个相当低的准确率。这表明,可学习的bias确实在做一些有用的事情——要么学习适当程度的稀疏性,要么只是添加正则化噪声。实际上,我们可以通过将偏差的学习率提高4倍,并将权值衰减除以相应的因子,来略微改善这种情况。

最后,我们可以使用增加的精度,把训练减少到17个epochs。新的测试精度为94.1%,最重要的是我们以43秒的速度超过了BaiduNet9P的8个gpu,排名第二!

输入块的白化 (36s)

batch norm在控制单个通道的分布方面做得很好,但不能处理通道与像素之间的协方差。在内部层控制协方差,使用batch norm的“白化”版本,可能会有帮助,但需要额外的计算和比较复杂的实现工作。我们将重点放在输入层的简单问题上。

消除输入相关性的经典方法是执行全局PCA(或ZCA)白化。我们提出了一种基于patch的方法,它不受图像大小的限制,更符合卷积网络的结构。我们将PCA白化应用于3×3的块输入,作为初始的3×3卷积与固定(非学习)权重。接下来是一个可学习的1×1卷积。这一层的27个输入通道是原3×3×3输入patch的转换版本,其协方差矩阵近似为恒等式,这将使优化变得更容易。

首先绘制输入数据的3×3个patch的协方差矩阵的主特征向量。方括号中的数字是对应特征值的平方根,表示沿着这些方向的相对变化幅度,我们用两个符号绘制特征向量来说明变化的方向。正如我们所预料的,局部亮度的变化占主导地位。

640?wx_fmt=png

现在我们用一个固定的3×3白化卷积替换网络的第一个3×3卷积,使上述特征patch的尺度相等,然后是一个可学习的1×1卷积,看看对训练的效果。

17个epochs的测试准确度跃升至94.4%,令训练时间进一步缩短2个epochs。15个epochs带来的测试精度为94.1%时间是39秒,接近4 gpu,测试时间增强帮助了我们!如果我们进一步增加最大学习率的~50%,并减少cutout增强,从8×8变为5×5,以弥补额外的高学习率带来的正则化,我们可以删除一个epoch,在36s内达到测试精度达到94.1%,我们勉强进入排行榜榜首!

指数移动平均(34s)

高学习率是快速训练的必要条件,因为它允许随机梯度下降在有限的时间内在参数空间中通过必要的距离。另一方面,学习率需要在训练结束时进行退火,以便在参数空间中沿着更陡峭和更嘈杂的方向进行优化。参数平均方法允许以更高的速度继续训练,同时通过多次迭代进行平均,可以沿着有噪声或振荡的方向接近最小值。

我们将研究参数的指数移动平均,这是一种标准方法。出于效率的原因,我们每5个批次更新一次移动平均,因为我们发现更频繁的更新并不能改善情况。我们需要选择一个新的学习率策略,在培训练接近尾声时提高学习率,并为移动平均提供动力。学习率的一个简单的选择是坚持我们一直使用的分段线性策略,比过去的两个epochs的固定低学习率要好,我们选择0.99的momentum ,这样平均大约发生在时间尺度的最后一个epoch上。

测试精度提高到94.3%,允许我们进一步削减epochs。13个epoch训练达到94.1%的测试精度,训练时间低于34s,比系列开始时的单gpu水平提高了10倍!

测试时间增强(26s)

假设你希望网络在对输入进行水平翻转的时候,对图像输出相同的分类。一种可能性是,我们现在一直在使用的,是向网络提供大量的数据,可能通过保存左右翻转的标签来扩充,并希望网络最终通过大量的训练来学习不变性。

第二种方法是同时给网络输入图像和水平翻转后的图像,并通过对两个版本的网络输出进行平均来达成一致,从而保证不变性。这种非常明智的方法被称为测试时间增强。

在训练时,我们仍然向网络呈现每个图像的单一版本——可能会受到随机翻转的影响,作为数据增强,以便在不同的训练阶段呈现不同的版本。另一种方法是,在训练时使用与测试时相同的程序,并将每个图像及其镜像显示出来。在这种情况下,我们可以通过将网络分成两个相同的分支来改变网络,其中一个分支可以看到翻转后的图像,然后在最后合并。通过这一视角,原始训练可以被看作是一个权重固定的随机训练过程,即两个分支网络,其中每个训练示例都有一个分支被“删除”。

这种dropout-training的观点清楚地表明,如果不允许引入TTA的话,基准测试的尝试会有很多困难。从这个角度看,我们刚刚介绍了一个更大的网络,我们有一个有效的随机训练方法。另一方面,如果我们不限制我们在测试时的工作量,那么就会出现一些明显的退化解决方案,其中训练所需的时间与存储数据集所需的时间一样少!

这些参数不仅与人工基准测试相关,而且与终端用户用例有关。在一些应用中,分类精度是所有的要求,在这种情况下,TTA绝对应该被使用。在其他情况下,推理时间也是一种约束,明智的做法是在这种约束下最大化准确性。这可能也是一个很好的训练基准测试方法。

在目前的案例中,Kakao Brain团队应用了这里描述的TTA的简单形式——在推断时呈现图像及其左右镜像,从而使计算量加倍。当然,对于其他对称(如平移对称、亮度/颜色的变化等)来说,TTA的更广泛形式是可能的,但这将付出更高的计算成本。

现在,由于这些都是基于一个计算能力较轻的9层ResNet, 包括TTA在内的总推断时间可能比在比赛早期阶段进入的100多个层网络中的一些要低得多!根据我们上面的讨论,任何限制这种方法的合理规则都应该基于推理时间约束,而不是任意的实现,因此从这个角度来看,我们应该接受这种方法。

让我们看看TTA带来了哪些改进。为了与当前DAWNBench提交的一致,我们将限制自己只使用水平翻转TTA,因为这似乎是准确性和推理成本之间的最佳点。在我们现有的网络和13个epoch训练设置下,TTA的测试精度提高到94.6%,这是我们今天研究的最大的单个提升效果。

如果我们去掉剩余的cutout增强——在如此短的训练时间中,这是一个障碍——我们可以将训练减少到10个epochs,并在26秒内实现TTA测试精度94.1% !

训练到收敛

这里有一个简单的实验来研究我们所收集的训练速度的提高如果训练到最后收敛的话是否也转化为模型的最终精度的提高。我们有充分的理由相信这应该是这样的情况因为我们今天使用的许多技术最初本来就是作为提高ImageNet收敛精度的技术而提出的!如果同样的技术可以将CIFAR10上的训练时间提高到94%的精度,同样也可以提高ImageNet上的收敛精度,那么这就为后一个问题的研究提供了一种相当有效的方法!

不像之前的实验,这是个非常粗的实验,我们留到未来去把这个实验做的更仔细。我们选择一个固定的学习率策略,学习率较低,适合较长时间的训练,并将cutout增强的数量增加到12×12 patch,使训练时间更长,而不会过拟合。我们将恢复上面提到的其他超参数,并对基线网络和最终网络进行24到100个不同epochs的训练。最后我们要打破所有的规则,每个实验只运行5次!结果如下:

640?wx_fmt=png

640?wx_fmt=png

尽管缺少针对较长时间运行的最终训练设置的各种额外超参数的调优,但即使在训练的100个epochs和近似收敛的情况下,它似乎也能保持对基线的领先。我们的9层ResNet在80个epochs的最终TTA准确度是96.1%,尽管我们从未优化过任何训练精度高于94%的东西!我们可以通过适当的超参数优化得到更高的结果。

在大约70个epochs和3分钟的总训练时间内,准确率达到96%,回答了一个我已经被问过好几次的问题,这些人(也许是正确的)认为DAWNBench的94%的门槛太低了。请注意,我们几乎没有尝试优化96%的时间,我们预计它会大幅下降。

最后的一点想法

在这个项目中工作是非常有趣的,探索神经网络训练的动态,并扩展其他人的工作,将训练时间带到一个可以进行快速实验的水平。我希望读者能在他们的工作中发现这一点,并相信通过进一步的算法开发,训练时间还有很长的路要走(如果你喜欢的话,也可以提高精确度!)

在本系列文章的开头,我曾半开玩笑地说,如果我们能够实现100%的计算效率,那么训练应该花费40s。如果我发现这个目标在本系列的末尾被超越了,而计算效率却没有比以前好多少,我会感到惊讶的!在这方面还有很大的改进空间。

640?wx_fmt=png

—END—

英文原文:https://myrtle.ai/how-to-train-your-resnet-8-bag-of-tricks/

640?wx_fmt=jpeg

请长按或扫描二维码关注本公众号

喜欢的话,请给我个好看吧640?wx_fmt=gif

这篇关于如何快速高效的训练ResNet,各种奇技淫巧(八):一大波技巧的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1080815

相关文章

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

高效+灵活,万博智云全球发布AWS无代理跨云容灾方案!

摘要 近日,万博智云推出了基于AWS的无代理跨云容灾解决方案,并与拉丁美洲,中东,亚洲的合作伙伴面向全球开展了联合发布。这一方案以AWS应用环境为基础,将HyperBDR平台的高效、灵活和成本效益优势与无代理功能相结合,为全球企业带来实现了更便捷、经济的数据保护。 一、全球联合发布 9月2日,万博智云CEO Michael Wong在线上平台发布AWS无代理跨云容灾解决方案的阐述视频,介绍了

电脑桌面文件删除了怎么找回来?别急,快速恢复攻略在此

在日常使用电脑的过程中,我们经常会遇到这样的情况:一不小心,桌面上的某个重要文件被删除了。这时,大多数人可能会感到惊慌失措,不知所措。 其实,不必过于担心,因为有很多方法可以帮助我们找回被删除的桌面文件。下面,就让我们一起来了解一下这些恢复桌面文件的方法吧。 一、使用撤销操作 如果我们刚刚删除了桌面上的文件,并且还没有进行其他操作,那么可以尝试使用撤销操作来恢复文件。在键盘上同时按下“C

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

滚雪球学Java(87):Java事务处理:JDBC的ACID属性与实战技巧!真有两下子!

咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE啦,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~ 🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!! 环境说明:Windows 10

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

hdu 4565 推倒公式+矩阵快速幂

题意 求下式的值: Sn=⌈ (a+b√)n⌉%m S_n = \lceil\ (a + \sqrt{b}) ^ n \rceil\% m 其中: 0<a,m<215 0< a, m < 2^{15} 0<b,n<231 0 < b, n < 2^{31} (a−1)2<b<a2 (a-1)^2< b < a^2 解析 令: An=(a+b√)n A_n = (a +

v0.dev快速开发

探索v0.dev:次世代开发者之利器 今之技艺日新月异,开发者之工具亦随之进步不辍。v0.dev者,新兴之开发者利器也,迅速引起众多开发者之瞩目。本文将引汝探究v0.dev之基本功能与优势,助汝速速上手,提升开发之效率。 何谓v0.dev? v0.dev者,现代化之开发者工具也,旨在简化并加速软件开发之过程。其集多种功能于一体,助开发者高效编写、测试及部署代码。无论汝为前端开发者、后端开发者