当前位置: 首页 > 科技观察

前端工程师掌握这18个技巧,在浏览器里玩转深度学习

时间:2023-03-20 18:42:13 科技观察

TensorFlow.jsTensorFlow发布后。我发现某些模型在浏览器中运行良好。感觉TensorFlow.js让我们的前端工作变得很火爆。虽然浏览器也可以运行深度学习模型,但这些模型毕竟不是为了在浏览器中运行而设计的,所以很多限制和挑战随之而来。以目标检测为例,别说实时检测,要保持一定的帧率可能都非常困难。更不用说几百兆的机型给用户的浏览器和带宽(手机端)带来的压力了。但只要我们遵循一定的原则,用CNN和TensorFlow.js在浏览器中训练出像样的深度学习模型不是梦。从下图可以看出,我训练的模型大小都控制在2MB以下,最小的只有3KB。你可能会有一个疑问:你脑死亡了吗?你想用浏览器训练模型吗?是的,用自己的电脑、服务器、集群或云来训练深度学习模型绝对是正确的方式,但并不是每个人都有钱去使用NVIDIAGTX1080Ti或TitanX(尤其是在显卡集体涨价之后).这时候在浏览器中训练深度学习模型的优势就体现出来了。借助WebGL和TensorFLow.js,我可以轻松地在计算机上使用AMDGPU训练深度学习模型。对于目标识别的问题,为了安全起见,通常建议大家使用一些现成的架构,比如YOLO、SSD、残差网络ResNet或者MobileNet,但是个人认为如果完全照搬,训练在浏览器上效果肯定不好。在浏览器上训练要求模型小,速度快,越容易训练越好。下面我们从模型架构、训练和调试等方面来看一下如何实现这三点。模型架构1.控制模型大小控制模型的大小很重要。如果模型架构太大太复杂,训练和运行的速度会变慢,从浏览器加载模型的速度也会变慢。控制模型的大小说起来容易,难的是要在精度和模型大小之间取得平衡。如果准确率达不到要求,模型再小也没用。2.使用深度可分离的卷积运算不同于标准的卷积运算。深度可分离卷积首先对每个通道进行一次卷积操作,然后进行1X1的跨通道卷积。这样做的好处是可以大大减少参数的数量,因此模型的运行速度会大大提高,资源消耗和训练速度也会有所提高。深度可分离卷积运算的过程如下图所示:MobileNet和Xception都使用了深度可分离卷积,在MobileNet和PoseNet的TensorFlow.js版本中也可以看到深度可分离卷积。虽然depthwiseseparableconvolution对模型精度的影响还有争议,但从我个人的经验来看,用它在浏览器中训练模型是绝对正确的。对于第一层,我建议使用标准的conv2d操作来维护特征提取后通道之间的关系。因为第一层一般参数很少,所以对性能影响不大。其他卷积层都可以使用depthwiseseparableconvolutions。例如,这里我们使用两个过滤器。这里tf.separableConv2d使用的卷积核结构分别是[3,3,32,1]和[1,1,32,64]。3.使用skipconnections和denseblocks随着网络层数的增加,出现梯度消失问题的可能性也会增加。梯度消失会导致损失函数下降太慢导致训练时间过长或完全失败。ResNet和DenseNet中使用的跳过连接避免了这个问题。简单来说,skipconnection就是将一些层的输出直接传递给网络深处的隐藏层作为输入,跳过激活函数,如下图:这样就避免了激活带来的梯度消失问题函数和链的推导,我们还可以根据需求增加网络的层数。显然,skipconnection的一个隐含要求就是被连接的两层的输出和输入格式必须能够对应。如果要使用残差网络,那么必须保证两层的filters和padding个数相同,stride为1(但必须有其他方式保证format对应)。一开始我模仿了residualnetwork的思想,每隔一层就加了一个skipconnection(如下图)。然而,我发现密集块效果更好,模型收敛速度比添加跳过连接快得多。下面我们来看一下具体的代码。这里的密集块有四个深度可分离的卷积层。在第一层中,我将步幅设置为2以更改输入的大小。4.选择ReLU作为激活函数。如果在浏览器中训练深度网络,不用看激活函数,直接选择ReLU即可。主要原因是渐变消失了。但是您可以尝试ReLU的不同变体,例如与MobileNet一起使用的ReLU-6(y=min(max(x,0),6)):训练过程5。优化器选择Adam。这也是我的亲身经历。.在使用SGD之前,它经常陷入局部最小值或发生梯度爆炸。我建议你一开始将学习率设置为0.001,然后其他参数使用默认值:6.动态调整学习率。一般来说,当损失函数不再下降时,我们就应该停止训练,因为再训练会过拟合。但是,如果我们发现损失函数上下波动,则可以降低学习率,使损失函数变小。在下面的示例中,我们可以看到学习率最初设置为0.01,然后从周期32开始振荡(黄线)。这里,通过将学习率更改为0.001(蓝线),损失函数为减少约0.3。7.权重初始化原则我个人喜欢设置bias为0,权重使用传统的正态分布。我一般使用Glorot正态分布初始化方法:8.打乱数据集的顺序是老生常谈。在TensorFlow.js中我们可以使用tf.utils.shuffle来实现。9.保存模型js可以使用FileSaver.js实现模型的保存(或下载)。比如下面的代码可以保存模型的所有权重:保存成什么格式由你决定,但是FileSaver.js只保存,所以这里需要使用JSON.strinfify把blob转成字符串:debugging10.保证预处理虽然后处理和后处理的正确性是废话,但“垃圾数据垃圾结果”真是一句至理名言。标记要标注正确,每一层的输入输出也要保持一致。尤其是对图片做过一些前处理和后处理的,更需要小心。有时这些小问题很难发现。所以,虽然费点功夫,但磨刀不误砍柴工。11、自定义损失函数TensorFlow.js提供了很多现成的损失函数供大家使用,一般来说够用了,不建议大家自己写。如果真的要自己写,请务必先测试测试。12.尝试拟合数据子集后,我建议模型定义好后,先挑十几二十张图片试试看损失函数是否收敛。***能够将结果可视化,让模型是否具备成功的潜力一目了然。这样做我们还可以尽早发现模型和预处理中的一些低级错误。这其实就是11条说的test测试损失函数性能13,内存泄漏我就不知道了。你知道吗TensorFlow.js不会自动为你执行垃圾回收。tensor占用的内存必须通过调用tensor.dispose()手动释放。如果忘记回收,内存泄漏迟早会发生。很容易判断是否存在内存泄漏。大家每次迭代输出tf.memory(),看看张量的数量。如果它不继续增加,则表示没有泄漏。14.调整画布的大小,而不是张量。在调用TF之前将画布转换为张量。从像素,调整画布大小,否则你会很快用完GPU内存。到下面的代码。(注意以下说法只在tfjs-core当前状态下有效,我目前使用的是tfjs-core0.12.14版本)15.谨慎选择batchsize。每个batch选择多少个样本,也就是batchsize显然取决于我们用的是什么GPU和网络结构,所以大家***尝试不同的batchsize看哪个最快。我通常从1开始,有时我发现增加batchsize对训练效率没有帮助。16.善用IndexedDB。我们训练的数据集有时会很大,因为都是图片。如果每次都下载,效率肯定很低。存储最好使用IndexedDB。IndexedDB实际上是一个嵌入在浏览器中的本地数据库,任何数据都可以以键值对的形式存储。读取和保存数据也可以只用几行代码来完成。17.异步返回损失函数值如果想实时监控损失函数值,可以使用如下代码自己计算然后异步返回:需要注意的是,如果损失函数值存储在每段训练后的一个文件,这样的代码就会出问题。因为现在损失函数的值是异步返回的,所以我们必须等待最后的承诺返回才能保存它。不过我一般是在第一节结束后暴力等待10秒来保存:18.权重的量化为了达到小而快的目的,在模型训练完成后,我们应该对权重进行量化,以压缩模型。权重量化不仅可以减小模型的大小,还有助于提高模型的速度,而且几乎百利而无一害。这一步使得模型小而快,非常适合我们在浏览器中训练深度学习模型。在浏览器中训练深度学习模型的十八个技巧(实际是十七个技巧)总结在这里。希望大家看完这篇文章都能有所收获。