Keras API
目前为止,介绍的神经网络模型都是通过Sequential模型来实现的。Sequential模型假设神经网络模型只有一个输入一个输出,而且模型的网络层是线性堆叠在一起的。
这是一个经过验证的假设;配置非常普遍,到目前为止已经能够使用Sequential模型类覆盖许多任务和实际应用程序。但在许多情况下,这套假设过于僵化。一些网络模型需要几个独立的输入,其他需要多个输出,并且一些网络在层之间具有内部分支,使得它们看起来像层的图形而不是线性堆叠层。
例如,某些任务需要多模式输入:它们合并来自不同输入源的数据,使用不同类型的神经层处理每种类型的数据。想象一下深度学习模型试图使用以下输入来预测二手服装的最可能的市场价格:用户提供的元数据(例如商品的品牌,使用时长等),用户提供的文字说明和项目图片。如果只有可用的元数据,可以对其进行一次热编码,并使用密集连接的网络来预测价格。如果只有可用的文本说明,则可以使用RNN或1D convnet。如果只有图片,则可以使用2D convnet。但是怎么能同时使用这三个呢?一种方法是训练三个单独的模型,然后对它们的预测进行加权平均。但这可能不是最理想的,因为模型提取的信息可能是冗余的。更好的方法是通过使用可以同时查看所有可用输入模态的模型来共同学习更准确的数据模型:具有三个输入分支的模型。
同样,某些任务需要预测输入数据的多个目标属性。鉴于小说或短篇小说的文本,可能希望按类型(例如浪漫或惊悚)自动对其进行分类,同时可以预测它的大致日期。可以训练两个独立的模型:一个用于分类,一个用于与预测时间。但由于这些属性在统计上并不独立,因此可以通过学习同时预测类型和日期来构建更好的模型。这样的联合模型将具有两个输出。
另外,许多最近开发的神经架构需要非线性网络拓扑:构造为有向非循环图的网络。当仅使用Keras中的Sequential模型类时,多输入模型,多输出模型和类图模型这三个重要的用例是不可能实现的。但是Keras还有另一种更通用和灵活的方式:function API。
Function API介绍
在function API中,可以直接操作张量,并将图层用作使用张量和返回张量的函数。
from keras import Input,layers
input_tensor = Input(shape=(32,))#输入张量
dense = layers.Dense(32,activation='relu')#网络层:函数形式
output_tensor = dense(input_tensor)#网络层对输入张量操作,返回运行结果张量
Sequential和Function API对比:
from keras.models import Sequential,Model
from keras import layers
from keras import Input
seq_model = Sequential()
seq_model.add(layers.Dense(32, activation='relu', input_shape=(64,)))
seq_model.add(layers.Dense(32, activation='relu'))
seq_model.add(layers.Dense(10, activation='softmax'))
input_tensor = Input(shape=(64,))
x = layers.Dense(32, activation='relu')(input_tensor)
x = layers.Dense(32, activation='relu')(x)
output_tensor = layers.Dense(10, activation='softmax')(x)
model = Model(input_tensor, output_tensor)#Model类将输入张量和输出张量转换为模型。
看起来有点神奇的唯一部分是仅使用输入张量和输出张量实例化Model对象。Keras检索从input_tensor到output_tensor的所有参与层,将它们组合成一个类似图形的数据结构---一个模型。当然,它工作的原因是output_tensor是通过重复转换input_tensor获得的。如果尝试从不相关的输入和输出构建模型,则会出现RuntimeError异常。
多输入模型
Function API可用于构建具有多个输入的模型。通常,此类模型在某些时候使用可以组合多个张量的图层合并它们的不同输入分支:通过添加,连接等操作。通常通过Keras合并操作完成,例如keras.layers.add,keras.layers.concatenate等。一个多输入模型的一个非常简单的例子:问答模型。
典型的问答模型有两个输入:自然语言问题和提供用于回答问题的信息文本片段(例如新闻文章)。然后,模型必须产生答案:在最简单的设置中,这是通过softmax在某些预定义词汇表上获得的单字答案。
Function API实现两个输入的问答模型
from keras.models import Model
from keras import layers
from keras import Input
text_vocabulary_size = 10000
question_vocabulary_size = 10000
answer_vocabulary_size = 500
text_input = Input(shape=(None,), dtype='int32', name='text')
embedded_text = layers.Embedding(64, text_vocabulary_size)(text_input)
encoded_text = layers.LSTM(32)(embedded_text)
question_input = Input(shape=(None,),dtype='int32',name='question')
embedded_question = layers.Embedding(32, question_vocabulary_size)(question_input)
encoded_question = layers.LSTM(16)(embedded_question)
concatenated = layers.concatenate([encoded_text, encoded_question],axis=-1)#两个输入处理结果联系起来
answer = layers.Dense(answer_vocabulary_size,activation='softmax')(concatenated)
model = Model([text_input, question_input], answer)#模型声明
model.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['acc'])#compile阶段
现在,如何训练这个双输入模型?有两种方法:可以为模型提供Numpy数组列表作为输入,或者可以为其提供将输入名称映射到Numpy数组的字典。当然,只有在为输入命名时,后一个选项才可用。
送入数据训练
import numpy as np
num_samples = 1000
maxlen = 100
text = np.random.randint(1,text_vocabulary_size,size=(num_samples,maxlen))#训练数据
question = np.random.randint(1,question_vocabulary_size,size=(num_samples,maxlen))
answers = np.random.randint(0,1,size=(num_samples,answer_vocabulary_size))#标签
model.fit([text,question],answers,epochs=10,batch_size=128)
model.fit({'text':text,'question':question},answers,epochs=10,batch_size=128)
多输出模型
以同样的方式,可以使用Function API来构建具有多个输出的模型。一个简单的例子是试图同时预测数据的不同属性的网络模型,例如从一个匿名人员那里获取一系列社交媒体帖子作为输入的网络,并试图预测该人的属性,例如年龄,性别和收入水平等。
Function API 3个输出模型
from keras.models import Model
from keras import layers
from keras import Input
vocabulary_size = 50000
num_income_groups = 10
posts_input = Input(shape=(None,), dtype='int32', name='posts')
embedded_posts = layers.Embedding(256, vocabulary_size)(posts_input)
x = layers.Conv1D(128, 5, activation='relu')(embedded_posts)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.GlobalMaxPooling1D()(x)
x = layers.Dense(128, activation='relu')(x)
age_prediction = layers.Dense(1, name='age')(x)#输出定义:年龄
income_prediction = layers.Dense(num_income_groups,activation='softmax',name='income')(x)#收入
gender_prediction = layers.Dense(1, activation='sigmoid', name='gender')(x)#性别
model = Model(posts_input,[age_prediction, income_prediction, gender_prediction])#列表定义输出内容
重要的是,训练这样的模型需要能够为网络的不同输出指定不同的损失函数:例如,年龄预测是标量回归任务,但性别预测是二元分类任务,需要不同的训练过程。但由于梯度下降要求最小化一个标量,因此必须将这些损失合并为单个值才能训练模型。结合不同损失的最简单方法是将它们全部加起来。在Keras中,可以在编译中使用列表或损失字典来为不同的输出指定不同的优化函数;所产生的损失值总计为全局损失,在训练期间最小化。
多输出模型训练
model.compile(optimizer='rmsprop',loss=['mse','categorical_crossentropy', 'binary_crossentropy'])
model.compile(optimizer='rmsprop',loss={'age': 'mse','income': 'categorical_crossentropy','gender': 'binary_crossentropy'})
请注意,非常不平衡的损失贡献将导致模型表示优先针对具有最大个体损失的任务进行优化,而牺牲其他任务。为了解决这个问题,可以为损失值分配不同程度的重要性。如果损失值取值范围不同,这种方法尤其有用。例如,用于年龄回归任务的均方误差(MSE)损失通常取约3-5的值,而用于性别分类任务的交叉熵损失可低至0.1。在这种情况下,为了平衡不同损失的贡献,可以为交叉线损失指定10的权重,并为MSE损失指定0.25的权重。
多输出模型:权衡损失函数
model.compile(optimizer='rmsprop',loss=['mse', 'categorical_crossentropy', 'binary_crossentropy'],
loss_weights=[0.25, 1., 10.])
model.compile(optimizer='rmsprop',loss={'age': 'mse','income': 'categorical_crossentropy',
'gender': 'binary_crossentropy'},loss_weights={'age': 0.25,'income': 1.,'gender': 10.})
与多输入模型的情况一样,可以通过数组列表或通过数组字典将Numpy数据传递给模型进行训练。
多输出模型训练
model.fit(posts, [age_targets, income_targets, gender_targets],epochs=10, batch_size=64)
model.fit(posts, {'age': age_targets,'income': income_targets,'gender': gender_targets},
epochs=10, batch_size=64)
有向非循环图
使用Function API,不仅可以构建具有多个输入和多个输出的模型,还可以实现具有复杂内部拓扑的网络。Keras中允许神经网络层是任意有向无环图。“非循环”很重要:这些图不能有循环。张量x不可能成为生成x的其中一个层的输入。允许的唯一处理循环(即循环连接)是循环层内部的循环。
几个常见的神经网络组件被实现为图形。两个值得注意的是Inception模块和残差连接。为了更好地理解function API如何用于构建图层图,看一下如何在Keras中实现它们。
Inception 模块
Inception是卷积神经网络的一种流行的网络架构。它由一堆模块组成,这些模块本身看起来像小型独立网络,分成几个并行分支。Inception模块的最基本形式有三到四个分支,以1×1卷积开始,然后是3×3卷积,最后是结果特征的串联。此设置有助于网络分别学习空间特征和通道特征,这比联合学习它们更有效。更复杂的Inception模块版本也是可能的,通常涉及池化操作,不同的空间卷积大小(例如,在某些分支上为5×5而不是3×3),以及没有空间卷积的分支(仅1×1)卷积)。
Function API实现Inception模块
from keras import layers
branch_a = layers.Conv2D(128, 1,activation='relu', strides=2)(x)#x 4D 张量
branch_b = layers.Conv2D(128, 1, activation='relu')(x)
branch_b = layers.Conv2D(128, 3, activation='relu', strides=2)(branch_b)
branch_c = layers.AveragePooling2D(3, strides=2)(x)
branch_c = layers.Conv2D(128, 3, activation='relu')(branch_c)
branch_d = layers.Conv2D(128, 1, activation='relu')(x)
branch_d = layers.Conv2D(128, 3, activation='relu')(branch_d)
branch_d = layers.Conv2D(128, 3, activation='relu', strides=2)(branch_d)
output = layers.concatenate([branch_a, branch_b, branch_c, branch_d], axis=-1)
完整的Inception V3架构在Keras中可用,keras.applications.inception_v3.InceptionV3,包括在ImageNet数据集上预先训练的权重系数。作为Keras应用程序模块的一部分提供的另一个密切相关的模型是Xception。Xception是一个由Inception启发的convnet架构。它采用了将通道和空间特征的学习分离到其逻辑极值的想法,并用深度可分离卷积替换初始模块,该卷积由深度卷积(每个输入通道单独处理的空间卷积)跟随通过逐点卷积(1×1卷积),其中空间特征和通道特征完全分离。Xception与Inception V3具有大致相同数量的参数,但由于更有效地使用模型参数,它在ImageNet以及其他大型数据集上显示出更好的运行时性能和更高的准确性。
Residual残差连接
残差连接是许多2015年后网络架构中常见的类似图形的网络组件,如Xception。通常,将残余连接添加到任何具有10层以上的模型可能是有益的。
残差连接包括使较早层的输出可用作后续层的输入,从而有效地在顺序网络中创建快捷方式。不是将其连接到后来的激活值上,而是将较早的输出与后面的激活值相加,后者假定两个激活值的大小形状相同。如果它们的大小不同,则可以使用线性变换将较早的激活值重新整形为目标形状(例如,没有激活函数的全连接层,或者对于卷积特征映射,没有激活函数的1×1卷积)。
from keras import layers
x = ...
y = layers.Conv2D(128, 3, activation='relu', padding='same')(x)
y = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
y = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
y = layers.add([y, x])
网络层权重共享
Function API的一个更重要的特性是能够多次重用层实例。当调用图层实例两次时,不是为每个调用实例化一个新图层,而是在每次调用时重复使用相同的权重。这允许构建具有共享分支的模型---几个分支都具有相同的知识并执行相同的操作。也就是说,它们共享相同的特征表示并同时为不同的输入集学习这些特征表示。
Function API实现
from keras import layers
from keras import Input
from keras.models import Model
lstm = layers.LSTM(32)
left_input = Input(shape=(None, 128))
left_output = lstm(left_input)
right_input = Input(shape=(None, 128))
right_output = lstm(right_input)#lstm重用两次
merged = layers.concatenate([left_output, right_output], axis=-1)
predictions = layers.Dense(1, activation='sigmoid')(merged)
model = Model([left_input, right_input], predictions)
model.fit([left_data, right_data], targets)
模型作为网络层
在Function API中,可以将模型视为“更大的图层”,这意味着可以在输入张量上调用模型并检索输出张量:
y = model(x)
如果模型有多个输入和输出:
y1,y2 = model([x1,x2])
当调用模型实例时,将重用模型的权重--与调用图层实例时的情况完全相同。调用实例,无论是层实例还是模型实例,都将重用实例的现有特征表示。
通过重用模型实例可以构建的一个简单实用示例是使用双摄像头作为其输入的视觉模型:两个并行摄像头,相距几厘米。这样的模型可以感知深度,这在许多应用中是有用的。在合并两个输入之前,不需要两个独立的模型来从左相机和右相机中提取视觉特征。这种低级处理可以在两个输入之间共享:即,通过使用相同权重的层来完成,从而共享相同的表示。以下是在Keras实现Siamese视觉模型(共享卷积基础)的方法:
from keras import layers
from keras import applications
from keras import Input
xception_base = applications.Xception(weights=None,include_top=False)
left_input = Input(shape=(250, 250, 3))
right_input = Input(shape=(250, 250, 3))
left_features = xception_base(left_input)
right_input = xception_base(right_input)
merged_features = layers.concatenate([left_features, right_input], axis=-1)
使用Keras回调和TensorBoard检查和监控深度学习模型
训练过程中使用回调
在训练模型时,有很多事情从一开始就无法预测。特别是,无法确定需要多少个epochs才能获得最佳验证损失。到目前为止,这些例子都是训练足够的epochs,让模型过度拟合;使用第一次运行来估计适当的训练eopchs,然后最终使用这个最佳数字从头开始新的训练运行。这种方法效率低。
处理此问题的更好方法是在测量验证损失不再改善时停止训练。这可以使用Keras回调函数来实现。回调callback是一个对象(实现特定方法的类实例),它在调用fit中传递给模型,并且在训练期间由模型在各个点调用。它可以访问有关模型状态及其性能的所有可用数据,并且可以执行操作:中断训练,保存模型,加载不同的权重或以其他方式更改模型的状态。
使用callbacks的几种方法:
- Model checkpointing:在训练期间在不同点保存模型的当前权重;
- 提前停止early stopping:当验证损失不再改进时,中断训练(保存训练期间获得的最佳模型);
- 在训练期间动态调整某些参数的值:如学习率;
- 在训练期间记录训练和验证指标,或者可视化模型在更新时学习的特征表示:Keras进度条就是一种callback。
keras.callbacks模块包含许多内置callbacks对象。
keras.callbacks.ModelCheckpoint
keras.callbacks.EarlyStopping
keras.callbacks.LearningRateScheduler
keras.callbacks.ReduceLROnPlateau
keras.callbacks.CSVLogger
...
ModelCheckPoint和EarlyStopping callbacks
一旦监测的目标指标在固定数量的epochs中停止改进,就可以使用EarlyStopping回调来中断训练过程。例如,这个回调允许在开始过度拟合时立即中断训练,从而避免以较少epochs重新训练模型。这个回调通常与ModelCheckpoint结合使用,它允许在训练期间不断保存模型(并且,可选地,仅保存当前最佳模型:在训练时期结束时获得最佳性能的模型版本) :
import keras
#通过模型的fit函数的callbacks参数传递callbacks类列表
callbacks_list = [
keras.callbacks.EarlyStopping(monitor='acc',patience=1,),
keras.callbacks.ModelCheckpoint(filepath='my_model.h5',monitor='val_loss',save_best_only=True,)
]
model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])
model.fit(x, y,epochs=10,batch_size=32,callbacks=callbacks_list,validation_data=(x_val, y_val))
ReduceLROnPlateau callbacks
当验证损失已经停止改进时,可以使用此回调来降低学习速率。降低或提高学习率是在训练期间摆脱局部最小值的有效策略。
callbacks_list = [
keras.callbacks.ReduceLROnPlateau(monitor='val_loss',factor=0.1,patience=10,)]#监测val_loss,10倍率减少,如果10个epochs后loss都没有减少,学习率减小
model.fit(x, y,epochs=10,batch_size=32,callbacks=callbacks_list,validation_data=(x_val, y_val))
TensorBoard:TensorFlow可视化工具
要进行良好的研究或开发好的模型,需要在实验过程中对模型内部发生的事情进行丰富,频繁的反馈。这就是运行实验的重点:获取有关模型执行情况的信息。结果改善是一个迭代过程:从一个想法开始,并将其表达为一个实验,试图验证或使想法无效。运行此实验并处理它生成的信息。这激发了下一个想法。能够运行的这个循环的迭代次数越多,最终的想法就越精确。Keras能在最短的时间内从创意到实验,快速GPU可以从实验到结果的时间尽可能短。TensorBoard可以用来观察实验结果变换。
TensorBoard,一个基于浏览器的可视化工具,与TensorFlow一起打包。请注意,当将Keras与TensorFlow后端一起使用时,它能适用于Keras框架。
TensorBoard的主要目的是有助于在训练期间直观地监控模型内部的所有内容。TensorBoard可以在浏览器中访问,有几个简洁的功能:
- 可视化训练过程中的监测指标;
- 可视化模型架构;
- 可视化激活函数和梯度值的直方图;
- Exploring embeddings in 3D.
模型优化
如果只需要一些可行的东西,那么使用模型的默认值即可满足要求。但如果要求更高,必须采用其他的技巧对模型进行调整。
高级架构模式
更复杂的设计模式:残差连接、规范化normalization和深度可分离卷积depthwise separate convolution。当构建高性能深度网络时,这些模式尤为重要,而且它们也常见于许多其他类型模型的体系结构中。
Batch Normalization
归一化是一种广泛的方法类别,旨在使机器学习模型看到的不同样本彼此更相似,这有助于模型学习和概括新数据。最常见的数据归一化形式:通过从数据中减去平均值将数据居中于0,并通过将数据除以其标准差得到单位标准差。实际上,这假设数据遵循正态(或高斯)分布,并确保此分布居中并缩放到单位方差:
normalized_data = (data - np.mean(data, axis=...)) / np.std(data, axis=...)
之前的示例在将数据输入模型之前对数据进行标准化处理。但是,在网络运行地每次转换之后,数据规范化层成为一个新的问题:也没有理由预期先验数据会出现高斯分布的情况(即使进入全连接或Conv2D网络的数据具有0均值和单位方差)。
批量标准化Batch Normalization是一个网络层(Keras中的BatchNormalization),即使平均值和方差在训练期间随时间变化,它也可以自适应地标准化数据。批量归一化的主要影响是它有助于梯度传播,因此允许更深的网络。一些非常深的网络只有在包含多个BatchNormalization层时才能被训练。
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())#全连接层后
BatchNormalization图层采用axis参数,该参数指定应规范化的特征轴。参数默认为-1,即输入张量中的最后一个轴。
使用Dense层,Conv1D层,RNN层和Conv2D层并且data_format设置为“channels_last”时。但是在将data_format设置为“channels_first”的Conv2D层中,特征轴是轴1;因此,BatchNormalization中的axis参数应设置为1。
深度可分离卷积depthwise separate convolution
如果有一个层可以用作Conv2D的替代品,这将使模型更轻量(可训练的重量参数更少)和更快速(更少的浮点运算)并使任务结果提升几个百分点?这正是深度可分离卷积层的作用(SeparableConv2D)。在通过逐点卷积(1×1卷积)混合输出通道之前,该层独立地在其输入的每个通道上执行空间卷积。这相当于将空间特征的学习与通道特征的学习分开,如果假设输入中的空间位置高度相关,但不同的通道是相当独立的,这就很有意义。它需要的参数明显更少,计算量更少,因此可以实现更小,更快的模型。并且因为它是执行卷积的更具代表性的有效方式,所以它倾向于使用更少的数据来学习更好的表示,从而产生性能更好的模型。
当在有限的数据上从头开始训练小型模型时,这些优势变得尤为重要。例如,在小型数据集上为图像分类任务(softmax分类分类)构建轻量级,深度可分离的卷积网络:
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'))
model.compile(optimizer='rmsprop', loss='categorical_crossentropy')
超参数优化
在构建深度学习模型时,必须做出许多看似随意的决策:应该堆叠多少层?每层应该有多少个单位或卷积核?使用relu作为激活函数,还是使用其他的激活函数?在给定图层后使用BatchNormalization?等等。这些体系结构级参数称为超参数,以将它们与模型的参数[通过反向传播进行训练]区分开来。
在实践中,经验丰富的机器学习工程师和研究人员随着时间的推移不断积累经验,了解在这些选择中哪些有效,哪些无效。
但是没有正式的规则。如果你想达到给定任务可以达到的最优解,必须调整超参数值。可以通过手动调整它们并重复重新训练模型来改进选择----这就是机器学习工程师和研究人员花费大部分时间做的事情。但是,作为一个人类,你不应该整天摆弄超级参数---这最好留给机器[???]。
优化超参数的过程通常如下所示:
- 选择一组超参数;
- 构建相应的模型;
- 使其适合于训练数据,并测量验证数据集上的表现;
- 选择下一组超参数;
- 重复;
- 最后,测量测试数据的性能。
此过程的关键是使用的算法,在给定各种超参数集,通过验证集上的监测指标选择要评估的下一组超参数。可能有许多不同的技术:贝叶斯优化,遗传算法,简单随机搜索等。
在大规模进行自动超参数优化时要记住的一个重要问题是验证集上模型过拟合。因为基于使用验证数据计算的信号更新超参数,所以可以有效地对验证数据进行训练,因此它们会快速过拟合验证数据。
模型集成
另一种在处理任务中获得最佳结果的强大技术是模型集成。集成包括将一组不同模型的预测汇集在一起,以产生更好的预测结果。
集成依赖于假设,独立训练的不同优秀模型可能偏爱于某种特定的特征:每个模型都会查看数据的略微不同的方面来进行预测,获得“真相”的一部分但不是全部。如盲人摸象,盲人本质上是机器学习模型,试图通过自己的假设(由模型的独特架构和独特的随机权重初始化提供)从各自的角度理解训练数据的多样性。他们每个人都获得了数据真实性的一部分,但不是全部真相。通过将它们的视角汇集在一起,可以获得更准确的数据描述。大象是各个部分的组合:没有任何一个盲人得到正确结果,但是,一起采访,他们可以说出一个相当准确的大象形象。
以分类为例。汇集一组分类器预测最简单方法是模型预测时,平均预测结果:
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)
只有当分类器预测效果大致相当时,才有效。如果其中一个明显比其他分类器差,那么最终的预测可能不如该群体的最佳分类器。
集成工作的关键是分类器的多样性。 Diversity is strength.如果所有的盲人都只触摸了大象的躯干,他们会同意大象像蛇一样,他们会永远不知道大象的真面目。多样化是使集成工作的原因。在机器学习方面,如果所有模型都以相同的方式偏向某种特征,那么整体将保持同样的认知。如果模型以不同的方式学习,则偏差将相互抵消,并且整体将更加稳健和更准确。
出于这个原因,应该尽可能集合尽可能好的不同类别模型。这通常意味着使用非常不同的架构甚至不同类别的机器学习方法。一个不值得做的方法是从不同的随机初始化中独立训练多次相同的网络模型。如果模型之间的唯一区别在于它们的随机初始化以及训练数据的顺序,那么模型是低多样性的,并且仅比任何单个模型结果稍好一点。
一种有效的集成方法是使用基于树的方法(例如随机森林或梯度提升树)和深度神经网络的集合。值得注意的是,整体中的一个模型起源于与其他模型不同的方法(它是一个正规化的贪婪森林)并且得分明显低于其他模型。不出所料,它在整体中被分配了一个小重量。但令我们惊讶的是,事实证明它可以通过一个很大的因素来改善整体效果,因为它与其他模型完全不同:它提供了其他模型无法访问的信息。这恰恰是整合的重点。这不是关于你最好的模型有多好;这是关于你的候选模型集的多样性。
最近,在实践中非常成功的一种基本集成风格是使用类别广泛而深度的模型,将深度学习与浅层学习相结合。这些模型包括联合训练深度神经网络和大型线性模型。一系列不同模型的联合训练是实现模型集成的另一种选择。