深度学习最佳实践

518 阅读8分钟
  • 本文首发自公众号:RAIS,欢迎关注。

最佳实践,顾名思义,就是做某事的最佳方法,当然,这里的最佳一定是绝大多数情况,但又不是百分百的情况,我们不必纠结这个问题,我们需要记住的是下面这些方法在深度学习实践中是非常好的做法。

回调机制

如果你看到这里,我有理由认为你是一个懂得程序设计懂得编程的有一定开发经验的程序员,既然如此,你一定对回调不陌生,回调类似于一种观察者的设计模式,我交给你一个任务去执行,交代结束我就继续去做自己的工作了,你执行结束了,不管结果是好是坏,你都要把结果告诉我,这就是回调的含义。

在我们之前的各种深度学习的例子中,总有一个参数是 epochs,含义是网络模型循环迭代的次数,一开始的时候,我们都会给出一个不小的值,让网络训练然后调整参数,这个 epochs 在调整参数的过程中总要保证模型要处于过拟合状态,只有这样,我们才能知道在什么情况下网络达到最优。在前面的大多数例子中这是合理的也是可行的,因为我们的网络训练的很快,数据集也没有大到在可接受时间内无法完成的情况,因此这并不影响什么,但是也有一些例外,例如前文提到的循环神经网络,训练的时间可能就有点让你等得不耐烦了,这个问题凸显出来了,我们需要解决这个问题。

我们查看训练过程中的数据,找到哪里开始过拟合,那我们就把循环迭代的次数设置成那个临界点,重新去训练网络,那解决上诉问题的思路是否可以改为我们训练的网络,自己去判断什么时候开始过拟合(或者数据指标无显著变化),然后自动停下来,自然,回调是处理这种问题最好的方法了,现在各种深度学习框架几乎都提供了这样的方法,具体怎么做呢?我们还是以 Keras 为例,这里给出三个常用的例子,不同框架有一些细微差别,使用时请关注:

# 定义需要哪些 callback
callbacks_list = [
    # 当所监控的数据不再增长,停止训练
    keras.callbacks.EarlyStopping(
        monitor='acc',
        patience=1,
    ),
    # 每轮训练都进行一次模型参数的保存,
    # monitor 表示如果监控数据没有改善(自动推断,还可用 mode 参数控制),不进行保存
    # save_best_only=True 时:
    #   val_loss: 自动推断 min
    #   val_acc: 自动推断 max
    keras.callbacks.ModelCheckpoint(
        filepath='point_model.h5',
        monitor='val_loss',
        save_best_only=True,
    ),
    # 当评价的指标没有提升时,减小学习率
    keras.callbacks. ReduceLROnPlateau(
        monitor='val_loss' 
        factor=0.1,    # 减小 10 倍学习率,2 或者 10 倍是常用值
        patience=10,   # 10 次迭代没有改善
    )
    # 当然,还可以自定义一些,keras.callbacks.Callback 的子类
]
model.fit(callbacks = callbacks_list)

TensorBoard

对于 TensorBoard,我们在前文 神经网络手写数字识别 中提到过,当时我们说我们知道网络训练出来一个模型,但却不知道网络中具体是怎么运行的,所以需要 TensorBoard 进行可视化查看,可以在浏览器中查看,类似于这样:

image

image

代码实现与上面的 Callback 几乎一样:

from keras.callbacks.tensorboard_v2 import TensorBoard
callback_list = [
        keras.callbacks.TensorBoard(
            log_dir='./logs',
            histogram_freq=1,
            embeddings_freq=1,
        )
    ]
model.fit(train_images, train_labels, epochs=5, batch_size=128, callbacks=callback_list)
# shell 中:tensorboard --logdir=logs
# 浏览器:http://localhost:6006/

相信现在你再看以前的数据导出的内容,会有不同的感觉,这不是一篇专门讲解 TensorBoard 的文章,我建议你回去看看,会有不同的。当然,这里还有一个小插曲,最新的正式版的 Keras 和 TensorFlow 不兼容,使用这个回调时会报错,已经 Fix,相关 GitHub Issues 在 这里,2 月 20 号更改的,相信不久后的新版本会发布这个 Fix。

接下来我们讨论一下如何提高深度学习性能相关内容。

采用高级设计模式

设计模式往往是普通程序设计的最佳实践中的方法之一,在深度学习中,这里的设计模式有些不同。这里的设计模式包括三种:残差连接、批标准化和深度可分离卷积。

残差连接

image

采用 上文 所述的函数式编程的方法,将前面层训练的输出作为后面某些层的输入,解决梯度消失和表示瓶颈的问题。

批标准化

批标准化是标准化的一种,我们在前文见过的标准化的方法是:让所有数据减去其平均值,然后分别除以其标准差,使其平均值为 0,标准差为 1,是不是想起了标准正态分布(计算机是固化的数学),对,就是那样!

批标准化的做法就是卷积神经网络在训练过程中,每一层的输入都保持相同的分布,这么做效果怎么样,非常好,具体的原理又是一篇论文,日后再聊,但我们知道的是训练的速度明显加快,收敛过程也明显加快,增加分类效果,同时因为对学习率要求不高,因此调参也更容易了,总之就是用起来很爽。

这里要指出,尤其对于一些比较深的网络,批标准化有助于梯度传播。对应的代码在 Keras 是 BatchNormalization,在卷积层或密集层之后使用,代码奉上:

conv_model.add(layers.Conv2D(32, 3, activation='relu'))
conv_model.add(layers.BatchNormalization())
​
dense_model.add(layers.Dense(32, activation='relu'))
​dense_model.add(layers.BatchNormalization())

深度可分离卷积

深度可分离卷积(SeparableConv2D)层的做法是将输入分别执行空间卷积,然后逐点卷积将输出通道混合,在一些情况下可以代替 Conv2D 层,由于他的参数更少,浮点运算更少,因此也有更好的效率,是一个轻量的模型。

image

下面是一个图像分类的代码的例子:

model = Sequential()
model.add(layers.SeparableConv2D(32, 3, activation='relu', input_shape=(64, 64, 3,)))
model.add(layers.SeparableConv2D(64, 3, activation='relu'))
model.add(layers.MaxPooling2D(2))
model.add(layers.SeparableConv2D(64, 3, activation='relu'))
model.add(layers.SeparableConv2D(128, 3, activation='relu'))
model.add(layers.MaxPooling2D(2))
model.add(layers.SeparableConv2D(64, 3, activation='relu'))
model.add(layers.SeparableConv2D(128, 3, activation='relu'))
# 极大的减少运算量
model.add(layers.GlobalAveragePooling2D())
model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.compile(optimizer='rmsprop', loss='categorical_crossentropy')

超参数优化

我们在构建网络模型的时候有很多时候都是凭感觉做出的决定:堆叠多少层,用什么激活函数,是否需要批标准化等,这些参数叫做超参数。平时我们所说的参数指的是在网络训练过程中,内部节点模型的参数,与这个参数没什么关系。

我们知道参数的调节太复杂太庞大了,因此我们人类采用让机器去调节,根据反馈信号等作为调节依据,那同样的,这样的超参数是不是也可以考虑交给机器呢,毕竟让程序员去做这样的事情,程序员一定会写代码自动化的完成这个工作。所以结论就是无论如何都需要交给机器去做。

但是这种优化参数的方法并没有什么特别好的特别优秀的统一方法,大致的过程就是读入(或自动生成)一组参数进行模型的构建和训练,保存下来结果,再次用另一组参数模型训练,比较哪个结果好,留下好的结果,如此重复,最终一定程度的时候得到的参数就认为是最好的参数。注意这种超参数的调节其实也是对验证集的训练,也要考虑过拟合,这一点需要注意。

当然,这里的超参数的调节方法有一定的算法,典型的就是贝叶斯优化算法,不一定有效,情况太复杂了,这只是一个方法,这是个大课题,日后再聊。

模型集成

针对这个问题,一般是在竞赛或者是生产环境中有效的,在研究学习上的作用有限,这一点需要注意。那什么叫模型集成呢,就是我对同样的数据集,用不同的网络模型方法进行训练,得到的结果都还不错,把他们加权结合在一起,所得到的的结果就可能比每一种都要好。

怎么解释这个问题呢?举一个不是那么准确的例子,图片分类,有的模型更关注线条,有的模型关注颜色,把他们结合在一起的模型就既关注了线条又关注了颜色,因此最终的结果就会更好一些,换一句话说,就是不同的模型关注数据的不同方面,更加全方位立体式的分析数据,当然得到的效果要好一些了。当然,这也要求所集成的模型都还不错,否则有那么一个捣乱的,你这个集成的模型也就废了,这很好理解。

总结

深度学习是个大课题,最佳实践也不仅仅这么多,这里给出的是大概率有效的成熟和不成熟的方法,有些方法还处于发展阶段,但本来神经网络中间训练过程的各种细节就很难说清楚了,因此只有有效的才是最好的,成熟不成熟都是值得讨论的,本篇就到这里。

  • 本文首发自公众号:RAIS,欢迎关注。