Python深度学习(让模型性能发挥到极致)--学习笔记(十七)

7.3 让模型性能发挥到极致

  • 如果你只是想要让模型具有不错的性能,那么盲目地尝试网络架构足以达到目的。本节中,将提供一套用于构建最先进深度学习模型的必备技术的快速指南,从而让模型由“具有不错的性能”上升到“性能卓越且能够赢得机器学习竞赛”。

7.3.1 高级架构模式

  • 7.1.4节详细介绍过一种重要的设计模式-残差连接。还有另外两种设计模式:标准化和深度可分离卷积。这些模式在构建高性能深度深度卷积神经网络时特别重要,但在其他许多类型的架构中也很常见。
1.批标准化
  • 标准化(normalization)是一大类方法,用于让机器学习模型看到的不同样本彼此之间更加相似,这有助于模型的学习与对新数据的泛化。最常见的数据标准化形式就是将数据减去其平均值使其中心为0,然后将数据除以其标准差使其标准差为1。实际上,这种做法假设数据服从正态分布(也叫高斯分布),并确保让该分布的中心为0,同时缩放到方差为1。
normalized_data = (data - np.mean(data, axis=...))/np.std(data, axis=...)
  • 前面的示例都是在将数据输入模型之前对数据做标准化。但在网络的每一次变换之后都应该考虑数据标准化。即使输入Dense或Conv2D网络的数据均值为0、方差为1,也没有理由假定网络输出的数据也是这样。
  • 批标准化(bacth normalization)是Ioffe和Szegedy在2015年提出的一种层的类型(在Keras中是BatchNormalization),即使在训练过程中均值和方差随时间发生变化,它也可以适应性地将数据标准化。批标准化的工作原理是,训练过程中在内部保存已读取每批数据均值和方差的指数移动平均值。批标准化的主要效果是,它有助于梯度传播(这一点和残差连接很像),因此允许更深的网络。对于有些特别深的网络,只有包含多个BatchNormalization层时才能进行训练。例如,BatchNormalization广泛用于Keras内置的许多高级卷积神经网络架构,比如ResNet50、Inception V3和Xception。
  • BatchNormalization层通常在卷积层或密集连接层之后使用
conv_model.add(layers.Conv2D(32, 3, activation='relu'))
conv_model.add(layers.BathcNormalization())

dense_model.add(layers.Dense(32, activation='relu'))
dense_model.add(layers.BatchNormalization())
  • BatchNormalization层接收一个axis参数,它指定应该对哪个特征轴做标准。这个参数的默认值是-1,即输入张量的最后一个轴。对于Dense层、Conv1D层、RNN层和将data_format设为"channels_last"(通道在后)的Conv2D层,这个默认值都是正确的。但有少数人使用将data_format设为"channels_first"(通道在前)的Conv2D层,这时特征轴是编号为1的轴,因此BacthNormalization的axis参数应该相应地设为1。
2.深度可分离卷积
  • 如果告诉你,有一个层可以替代Conv2D,并可以让模型更加轻量(即更少的可训练权重参数)、速度更快(即更少的浮点数运算),还可以让任务性能提高几个百分点,这正是深度可分离卷积(depthwise separable convolution)层(SeparableConv2D)的作用。这个层对输入的每个通道分别执行空间卷积,然后通过逐点卷积(1x1卷积)将输出通道混合。这相当于将空间特征学习和通道特征学习分开,如果你假设输入中的空间位置高度相关,但不同的通道之间相对独立,那么这么做是很有意义的。它需要的参数要少很多,计算量也更小,因此可以得到更小、更快的模型。因为它是一种执行卷积更高效的方法,所以往往能够使用更少的数据学到更好的表数,从而得到性能更好的模型。
  • 如果只用有限的数据从头开始训练小型模型,这些优点就变得尤为重要。
from keras.models import Sequential, Model
from keras import layers

height = 64
width = 64
channels = 3
num_classes = 10

model = Sequential()
model.add(layers.SeparableConv2D(32, 3, activation='relu', input_shape=(height, width, channels)))
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(num_classes, activation='softmax'))

print(model.summary())

model.compile(optimizer='rmsprop', loss='categorical_crossentropy')
  • 对于规模更大的模型,深度可分离卷积是Xception架构的基础,Xception是一个高性能的卷积神经网络,内置于Keras。

7.3.2 超参数优化

  • 构建深度学习模型时,你必须做出做出许多看似随意的决定:应该堆叠多少层?每层应该包含多少个单元或过滤器?激活应该使用relu还是其它函数?在某一层之后是否应该使用BatchNormalization?应该使用多大的dropout比率?等。这些在架构层面的参数叫作超参数(hyperparameter),以便将其与模型参数区分开来,后者通过反向传播进行训练。
  • 在实践中,经验丰富的机器学习工程师和研究人员会培养出直觉,能够判断上述选择那些可行、哪些不可行。也就说,他们学会了调节超参数的技巧。但是调节超参数并没有正式成文的规则。如果你想要在某项任务上达到最佳性能,那么就不能满足于一个容易犯错的人随意做出的选择。即使你拥有很好的直觉,最初的选择也几乎不可能是最优的。你可以手动调节你的选择、重新训练模型,如此不停重复来改进你的选择,这也是机器学习工程师和研究人员大部分时间都在做的事情。但是,整天调节超参数不应该是人类的工作,最好留给机器去做。
  • 因此,你需要制定一个原则,系统性地自动探索可能的决策空间。你需要搜索架构空间,并根据经验找到性能最佳的架构。这正是超参数自动优化领域的内容。
  • 超参数优化的过程通常如下所示:(1)选择一组超参数(自动选择);(2)构建相应的模型;(3)将模型在训练数据上拟合,并衡量其在验证数据上的最终性能;(4)选择要尝试的下一组超参数(自动选择);(5)重复上述过程;(6)最后,衡量模型在测试数据上的性能。
  • 这个过程的关键在于,给定许多组超参数,使用验证性能的历史来选择下一组需要评估的超参数算法。有多种不同的技术可供选择:贝叶斯优化、遗传算法、简单随机搜索等。
  • 训练模型权重相对简单:在小批量数据上计算损失函数,然后用方向传播算法让权重向正确的方向移动。与此相反,更新超参数则非常具有挑战性:1、计算反馈信号(这组超参数在这个任务上述会否得到一个高性能的模型)的计算代价可能非常高,它需要在数据集上创建一个模型从头开始训练。2、超参数空间通常由许多离散的决定组成,因而既不是连续的,也不是可微的。因此,你通过不能再超参数空间中做梯度下降。相反,你必须依赖不使用梯度的优化方法,而这些方法的效率比梯度下降要低很多。

7.3.3 模型集成

  • 想要在一项任务上获得最佳结果,另一种强大的技术是模型集成(model ensembling)。集成是指将一系列不同模型的预测结果汇集到一起,从而得到更好的预测结果。观察机器学习竞赛,特别是Kaggle上的竞赛,你会发现优胜者都是将很多模型集成在一起,它必然可以打败任何单个模型,无论这个模型的表现多么好。
  • 集成依赖于这样的假设,即对于独立训练的不同良好模型,它们表现良好可能是因为不同的原因:每个模型都从略有不同的角度观察数据来做出预测,得到了“真相”的一部分,但不是全部真相。
  • 想要将一组分类器的预测结果汇集在一起(即分类器集成(ensemble the classifiers)),最简单的方法就是将它们的预测结果取平均值作为预测结果。
# 使用4个不同的模型来计算初始预测
preds_a = model_a.predict(x_val)
preds_b = model_b.predict(x_val)
preds_c = model_c.predict(x_val)
preds_d = model_d.predict(x_val)

final_preds = 0.25 * (preds_a + preds_b + preds_c + preds_d)
  • 只有这组分类器中每一个的性能差不多一样好时,这组方法才奏效。如果其中一个分类器的性能比其他的差很多,那么最终预测结果可能不如这一组中的最佳分类器那么好。
  • 将分类器集成有一个更聪明的做法,即加权平均,其权重在验证数据上学习得到。通常来说,更好的分类器被赋予更大的权重,而较差的分类器则被赋予较小的权重。为了找到一组好的集成权重,你可以使用随机搜索或简单的优化算法。
preds_a = model_a.predict(x_val)
preds_b = model_b.predict(x_val)
preds_c = model_c.predict(x_val)
preds_d = model_d.predict(x_val)

# 假设(0.5, 0.25, 0.1, 0.15)这些权重是根据经验学到的。
final_preds = 0.5 * preds_a + 0.25 * preds_b + 0.1 * preds_c + 0.15 * preds_d
  • 还有许多其他变体,比如你可以对预测结果先取指数再做平均。一般来说,简单的加权平均,其权重在验证数据上进行最优化,这是一个很强大的基准方法。
  • 想要保证集成方法有效,关键在于这组分类器的多样性(diversity)。多样性就是力量。如果所有盲人都只摸到大象的鼻子,那么他们会一致认为大象像蛇,并且永远不会知道大象的真实模样。是多样性让集成方法能够取得良好效果。用机器学习的术语来说,如果所有模型的偏差都在同一个方向上,那么集成也会保留同样的偏差。如果各个模型的偏差在不同方向上,那么这些偏差会批次抵消,集成结果会更加稳定、更加准确。
  • 因此,集成的模型应该尽可能好,同时尽可能不同。这通常意味着使用非常不同的架构,甚至使用不同类型的机器学习方法。有一件事情基本上是不值得做的,就是对相同的网络,使用不同的随机初始化多次独立训练,然后集成。如果模型之间的唯一区别是随机初始化和训练数据的读取顺序,那么集成的多样性很小,与单一模型相比只会有微小的改进。
  • 有一种方法在实践中非常有效,就是将基于树的方法(比如随机森林或梯度提升树)和深度神经网络进行集成。集成不在于你的最佳模型有多好,而在于候选模型集合的多样性。
  • 近年来,一种在实践中非常成功的基本集成方法是宽且深(wide and deep)的模型类型,它结合了深度学习与浅层学习。这种模型联合训练一个深度神经网络和一个大型的线性模型。对多种模型联合训练,是实现模型集成的另一种选择。

7.3.4 小结

  • 构建高性能的卷积神经网络时,你需要使用残差连接、批标准化和深度可分离卷积。未来,无论是一维、二维还是三维应用,深度可分离卷积很可能会完全取代普通卷积,因为它的表示效率更高。
  • 构建深度网络需要选择许多参数和架构,这些选择共同决定了模型的性能。与其将这些选择建立在直觉或随机性之上,不如系统性地搜索超参数空间,以找到最佳选择。目前,这个搜索过程的计算代价还很高,使用的工具也不是很好。但Hyperopt和Hyperas这两个库可能会对你有所帮助。进行超参数优化时,一定要小心验证集过拟合!
  • 想要在机器学习竞赛中获胜,或者想要在某项任务上获得最佳结果,只能通过多个模型的集成来实现。利用加权平均(权重已经过优化)进行集成通常已经能够取得足够好的效果。请记住,多样性就是力量。将非常相似的模型集成基本上是没有意义的。最好的集成方法是将尽可能不同的一组模型集成(这组模型还需要具有尽可能高的预测能力)。

你可能感兴趣的:(深度学习,神经网络,深度学习)