本章包括以下内容:
使用函数式 API,你可以直接操作张量,也可以把层当作函数来使用,接收张量并返回张量(因此得名函数式 API)。
from keras import Input, layers
#一个张量
input_tensor = Input(shape=(32,))
#一个层是一个函数
dense = layers.Dense(32, activation='relu')
#可以在一个张量上调用一个层,它会返回一个张量
output_tensor = dense(input_tensor)
我们首先来看一个最简单的示例,并列展示一个简单的 Sequential 模型以及对应的函数式 API 实现。
from keras.models import Sequential, Model
from keras import layers
from keras import Input
#前面学过的 Sequential 模型
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'))
#对应的函数式 API 实现
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 = Model(input_tensor, output_tensor)
model.summary()#查看模型
#编译模型
model.compile(optimizer='rmsprop', loss='categorical_crossentropy')
import numpy as np
#生成用于训练的虚构Numpy 数据
x_train = np.random.random((1000, 64))
y_train = np.random.random((1000, 10))
#训练 10 轮模型
model.fit(x_train, y_train, epochs=10, batch_size=128)
#评估模型
score = model.evaluate(x_train, y_train)
函数式 API 可用于构建具有多个输入的模型。通常情况下,这种模型会在某一时刻用一个可以组合多个张量的层将不同的输入分支合并,张量组合方式可能是相加、连接等。这通常利用 Keras 的合并运算来实现,比如 keras.layers.add、keras.layers.concatenate 等。
用函数式 API 实现双输入问答模型
#代码清单 7-1 用函数式 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')
#将输入嵌入长度为 64 的向量
embedded_text = layers.Embedding(text_vocabulary_size, 64)(text_input)
#利用 LSTM 将向量编码为单个向量
encoded_text = layers.LSTM(32)(embedded_text)
#对问题进行相同的处理(使用不同的层实例)
question_input = Input(shape=(None,),dtype='int32',name='question')
embedded_question = layers.Embedding(question_vocabulary_size, 32)(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.summary()
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['acc'])
将数据输入到多输入模型中
import numpy as np
import keras
num_samples = 1000
max_length = 100
#生成虚构的 Numpy数据
text = np.random.randint(1, text_vocabulary_size,size=(num_samples, max_length))
question = np.random.randint(1, question_vocabulary_size,size=(num_samples, max_length))
answers = np.random.randint(answer_vocabulary_size, size=(num_samples))
#回答是 one-hot 编码的,不是整数
answers = keras.utils.to_categorical(answers, 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)
利用相同的方法,我们还可以使用函数式 API 来构建具有多个输出(或多头)的模型。一个简单的例子就是一个网络试图同时预测数据的不同性质,比如一个网络,输入某个匿名人士的一系列社交媒体发帖,然后尝试预测那个人的属性,比如年龄、性别和收入水平。
用函数式 API 实现一个三输出模型
from keras import layers
from keras import Input
from keras.models import Model
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])
model.summary()
#代码清单 7-4 多输出模型的编译选项:多重损失
model.compile(optimizer='rmsprop',
loss=['mse', 'categorical_crossentropy', 'binary_crossentropy'])
model.compile(optimizer='rmsprop',
loss={'age': 'mse',
'income': 'categorical_crossentropy',
'gender': 'binary_crossentropy'})
多输出模型的编译选项:损失加权
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.})
将数据输入到多输出模型中
#假设 age_targets、income_targets 和gender_targets 都是 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)
我们已经知道,卷积能够在输入张量的每一个方块周围提取空间图块,并对所有图块
应用相同的变换。极端情况是提取的图块只包含一个方块。这时卷积运算等价于让每个方块向量经过一个 Dense 层:它计算得到的特征能够将输入张量通道中的信息混合在一起,但不会将跨空间的信息混合在一起(因为它一次只查看一个方块)。这种 1×1 卷积[也叫作逐点卷积(pointwise convolution)]是 Inception 模块的特色,它有助于区分开通道特征学习和空间特征学习。如果你假设每个通道在跨越空间时是高度自相关的,但不同的通道之间可能并不高度相关,那么这种做法是很合理的。
使用函数式 API 可以实现上图中的模块,其代码如下所示。这个例子假设我们有一个四维输入张量 x。
from keras import backend as K
K.set_image_dim_ordering('tf')
from keras import Input, layers
x = Input(shape=(None,None,None), dtype='float')
branch_a = layers.Conv2D(128, 1,activation='relu', strides=2)(x)
#每个分支都有相同的步幅值(2),这对于保持所有分支输出具有相同的尺寸是很有必要的,这样你才能将它们连接在一起
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 数据集上预训练得到的权重。与其密切相关的另一个模型是 Xception,a 它也是 Keras 的 applications 模块的一部分。
如果特征图的尺寸相同,在 Keras 中实现残差连接的方法如下,用的是恒等残差连接(identity residual connection)。这个例子假设我们有一个四维输入张量 x。
from keras import layers
x = ...
# 对 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)
# 将原始 x 与输出特征相加
y = layers.add([y, x])
如果特征图的尺寸不同,实现残差连接的方法如下,用的是线性残差连接(linear residual connection)。同样,假设我们有一个四维输入张量 x。
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.MaxPooling2D(2, strides=2)(y)
#使用 1×1 卷积,将原始 x 张量线性下采样为与 y 具有相同的形状
residual = layers.Conv2D(128, 1, strides=2, padding='same')(x)
#将残差张量与输出特征相加
y = layers.add([y, residual])
函数式 API 还有一个重要特性,那就是能够多次重复使用一个层实例。如果你对一个层实例调用两次,而不是每次调用都实例化一个新层,那么每次调用可以重复使用相同的权重。这样你可以构建具有共享分支的模型,即几个分支全都共享相同的知识并执行相同的运算。也就是说,这些分支共享相同的表示,并同时对不同的输入集合学习这些表示。我们将其称为连体 LSTM(Siamese LSTM)或共享LSTM(shared LSTM)模型。
使用 Keras 函数式 API 中的层共享(层重复使用)可以实现这样的模型,其代码如下所示。
from keras import layers
from keras import Input
from keras.models import Model
#将一个 LSTM 层实例化一次
lstm = layers.LSTM(32)
#构建模型的左分支:输入是长度128 的向量组成的变长序列
left_input = Input(shape=(None, 128))
left_output = lstm(left_input)
#构建模型的右分支:如果调用已有的层实例,那么就会重复使用它的权重
right_input = Input(shape=(None, 128))
right_output = lstm(right_input)
#在上面构建一个分类器
merged = layers.concatenate([left_output, right_output], axis=-1)
predictions = layers.Dense(1, activation='sigmoid')(merged)
#将模型实例化并训练:训练这种模型时,基于两个输入对 LSTM层的权重进行更新
model = Model([left_input, right_input], predictions)
model.summary()
model.fit([left_data, right_data], targets)
自然地,一个层实例可能被多次重复使用,它可以被调用任意多次,每次都重复使用一组相同的权重。
重要的是,在函数式 API 中,可以像使用层一样使用模型。实际上,你可以将模型看作“更大的层”。Sequential 类和 Model 类都是如此。这意味着你可以在一个输入张量上调用模型,并得到一个输出张量。
y = model(x)
如果模型具有多个输入张量和多个输出张量,那么应该用张量列表来调用模型。
y1, y2 = model([x1, x2])
在调用模型实例时,就是在重复使用模型的权重,正如在调用层实例时,就是在重复使用层的权重。调用一个实例,无论是层实例还是模型实例,都会重复使用这个实例已经学到的表示,这很直观。
from keras import layers
from keras import applications
from keras import Input
#图像处理基础模型是Xception 网络(只包括卷积基)
xception_base = applications.Xception(weights=None,include_top=False)
#输入是 250×250 的 RGB 图像
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回调函数来实现。回调函数(callback)是在调用 fit 时传入模型的一个对象(即实现特定方法的类实例),它在训练过程中的不同时间点都会被模型调用。它可以访问关于模型状态与性能的所有可用数据,还可以采取行动:中断训练、保存模型、加载一组不同的权重或改变模型的状态。
回调函数的一些用法示例如下所示。
keras.callbacks 模块包含许多内置的回调函数,下面列出了其中一些,但还有很多没有列出来。
keras.callbacks.ModelCheckpoint
keras.callbacks.EarlyStopping
keras.callbacks.LearningRateScheduler
keras.callbacks.ReduceLROnPlateau
keras.callbacks.CSVLogger
下面介绍其中几个回调函数,让你了解如何使用它们:ModelCheckpoint、EarlyStopping和 ReduceLROnPlateau。
如果监控的目标指标在设定的轮数内不再改善,可以用 EarlyStopping 回调函数来中断训练。比如,这个回调函数可以在刚开始过拟合的时候就中断训练,从而避免用更少的轮次重新训练模型。这个回调函数通常与 ModelCheckpoint 结合使用,后者可以在训练过程中持续不断地保存模型(你也可以选择只保存目前的最佳模型,即一轮结束后具有最佳性能的模型)。
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28,1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.summary()
from keras.datasets import mnist
from keras.utils import to_categorical
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images.reshape((60000, 28, 28,1))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000,28, 28,1))
test_images = test_images.astype('float32') / 255
#在 MNIST 图像上训练卷积神经网络
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
import keras
#通过 fit 的 callbacks 参数将回调函数传入模型中,这个参数接收一个回调函数的列表。你可以传入任意个数的回调函数
callbacks_list = [
keras.callbacks.EarlyStopping( #如果不再改善,就中断训练
monitor='acc', #监控模型的验证精度
patience=1,#如果精度在多于一轮的时间(即两轮)内不再改善,中断训练
),
keras.callbacks.ModelCheckpoint( #在每轮过后保存当前权重
filepath='my_model.h5', #标模型文件的保存路径
monitor='val_loss',
save_best_only=True,
#这两个参数的含义是,如果 val_loss 没有改善,那么不需要覆盖模型文件。这就可以始终保存在训练过程中见到的最佳模型
)
]
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc']) #你监控精度,所以它应该是模型指标的一部分
model.fit(train_images, train_labels, epochs=5, batch_size=64,callbacks=callbacks_list,validation_data=(test_images, test_labels))
#注意,由于回调函数要监控验证损失和验证精度,所以在调用 fit 时需要传入 validation_data(验证数据)
如果验证损失不再改善,你可以使用这个回调函数来降低学习率。在训练过程中如果出现了损失平台(loss plateau),那么增大或减小学习率都是跳出局部最小值的有效策略。下面这个示例使用了 ReduceLROnPlateau 回调函数。
callbacks_list = [
keras.callbacks.ReduceLROnPlateau(
monitor='val_loss',#监控模型的验证损失
factor=0.1,#触发时将学习率除以 10
patience=10, #如果验证损失在 10 轮内都没有改善,那么就触发这个回调函数
)
]
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc']) #你监控精度,所以它应该是模型指标的一部分
model.fit(train_images, train_labels, epochs=5, batch_size=64,callbacks=callbacks_list,validation_data=(test_images, test_labels))
如果你需要在训练过程中采取特定行动,而这项行动又没有包含在内置回调函数中,那么可以编写你自己的回调函数。回调函数的实现方式是创建 keras.callbacks.Callback 类的子类。然后你可以实现下面这些方法(从名称中即可看出这些方法的作用),它们分别在训练过程中的不同时间点被调用。
这些方法被调用时都有一个 logs 参数,这个参数是一个字典,里面包含前一个批量、前一个轮次或前一次训练的信息,即训练指标和验证指标等。此外,回调函数还可以访问下列属性。
下面是一个自定义回调函数的简单示例,它可以在每轮结束后将模型每层的激活保存到硬盘(格式为 Numpy 数组),这个激活是对验证集的第一个样本计算得到的。
import keras
import numpy as np
class ActivationLogger(keras.callbacks.Callback):
#在训练之前由父模型调用,告诉回调函数是哪个模型在调用它
def set_model(self, model):
self.model = model
layer_outputs = [layer.output for layer in model.layers]
#模型实例,返回每层的激活
self.activations_model = keras.models.Model(model.input,layer_outputs)
def on_epoch_end(self, epoch, logs=None):
if self.validation_data is None:
raise RuntimeError('Requires validation_data.')
#获取验证数据的第一个输入样本
validation_sample = self.validation_data[0][0:1]
activations = self.activations_model.predict(validation_sample)
#将数组保存到硬盘
f = open('activations_at_epoch_' + str(epoch) + '.npz', 'w')
np.savez(f, activations)
f.close()
关于回调函数你只需要知道这么多,其他的都是技术细节,很容易就能查到。现在,你已经可以在训练过程中对一个 Keras 模型执行任何类型的日志记录或预定程序的干预。
本节将介绍 TensorBoard,一个内置于 TensorFlow 中的基于浏览器的可视化工具。注意,只有当 Keras 使用 TensorFlow 后端时,这一方法才能用于 Keras 模型。
TensorBoard 的主要用途是,在训练过程中帮助你以可视化的方法监控模型内部发生的一切。如果你监控了除模型最终损失之外的更多信息,那么可以更清楚地了解模型做了什么、没做什么,并且能够更快地取得进展。TensorBoard 具有下列巧妙的功能,都在浏览器中实现。
我们用一个简单的例子来演示这些功能:在 IMDB 情感分析任务上训练一个一维卷积神经网络。
使用了 TensorBoard 的文本分类模型
import keras
from keras import layers
from keras.datasets import imdb
from keras.preprocessing import sequence
#作为特征的单词个数
max_features = 2000
#在这么多单词之后截断文本(这些单词都属于前 max_features个最常见的单词)
max_len = 500
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
x_train = sequence.pad_sequences(x_train, maxlen=max_len)
x_test = sequence.pad_sequences(x_test, maxlen=max_len)
model = keras.models.Sequential()
model.add(layers.Embedding(max_features, 128,input_length=max_len,name='embed'))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.MaxPooling1D(5))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(1))
model.summary()
model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])
为 TensorBoard 日志文件创建一个目录
my_log_dir
使用一个 TensorBoard 回调函数来训练模型
import numpy as np
callbacks = [
keras.callbacks.TensorBoard(
log_dir='my_log_dir', #日志文件将被写入这个位置
histogram_freq=1, #每一轮之后记录激活直方图
embeddings_freq=1, #每一轮之后记录嵌入数据
embeddings_data = np.arange(0, max_len).reshape((1, max_len)),
)
]
history = model.fit(x_train, y_train,
epochs=20,
batch_size=128,
validation_split=0.2,
callbacks=callbacks)
启动
tensorboard --logdir=my_log_dir
然后可以用浏览器打开 http://localhost:6006
注意,Keras 还提供了另一种更简洁的方法——keras.utils.plot_model 函数,它可以
将模型绘制为层组成的图,而不是 TensorFlow 运算组成的图。使用这个函数需要安装 Python 的pydot 库和 pydot-ng 库,还需要安装 graphviz 库。我们来快速看一下。
from keras.utils import plot_model
plot_model(model, to_file='./data/model.png')
你还可以选择在层组成的图中显示形状信息。下面这个例子使用 plot_model 函数及
show_shapes 选项将模型拓扑结构可视化.
from keras.utils import plot_model
plot_model(model, show_shapes=True, to_file='./data/model.png')
标准化(normalization)是一大类方法,用于让机器学习模型看到的不同样本彼此之间更加相似,这有助于模型的学习与对新数据的泛化。最常见的数据标准化形式就是你已经在本书中多次见到的那种形式:将数据减去其平均值使其中心为 0,然后将数据除以其标准差使其标准差为 1。实际上,这种做法假设数据服从正态分布(也叫高斯分布),并确保让该分布的中心为 0,同时缩放到方差为 1。
normalized_data = (data - np.mean(data, axis=...)) / np.std(data, axis=...)
批标准化(batch 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.BatchNormalization())
dense_model.add(layers.Dense(32, activation='relu'))
#在 Dense 层之后使用
dense_model.add(layers.BatchNormalization())
BatchNormalization 层接收一个 axis 参数,它指定应该对哪个特征轴做标准化。这
个参数的默认值是 -1,即输入张量的最后一个轴。对于 Dense 层、Conv1D 层、RNN 层和将data_format 设为 “channels_last”(通道在后)的 Conv2D 层,这个默认值都是正确的。但有少数人使用将 data_format 设为 “channels_first”(通道在前)的 Conv2D 层,这时特征轴是编号为 1 的轴,因此 BatchNormalization 的 axis 参数应该相应地设为 1。
这个层对输入的每个通道分别执行空间卷积,然后通过逐点卷积(1×1 卷积)将输出通道混合.
如果只用有限的数据从头开始训练小型模型,这些优点就变得尤为重要。例如,下面这个示例是在小型数据集上构建一个轻量的深度可分离卷积神经网络,用于图像分类任务(softmax多分类)。
from keras import backend as K
K.set_image_dim_ordering('tf')
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')
model.summary()
通常情况下,随机搜索(随机选择需要评估的超参数,并重复这一过程)就是最好的
解决方案,虽然这也是最简单的解决方案。但我发现有一种工具确实比随机搜索更好,它就是Hyperopt。它是一个用于超参数优化的 Python 库,其内部使用 Parzen 估计器的树来预测哪组超参数可能会得到好的结果。另一个叫作 Hyperas 的库将 Hyperopt 与 Keras 模型集成在一起。
我们以分类问题为例。想要将一组分类器的预测结果汇集在一起[即分类器集成(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)
# 这个新的预测数组应该比任何一个初始预测都更加准确
将分类器集成有一个更聪明的做法,即加权平均,其权重在验证数据上学习得到。通常来说,更好的分类器被赋予更大的权重,而较差的分类器则被赋予较小的权重。为了找到一组好的集成权重,你可以使用随机搜索或简单的优化算法(比如 Nelder-Mead 方法)。
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.5 * preds_a + 0.25 * preds_b + 0.1 * preds_c + 0.15 * preds_d
# 假设 (0.5, 0.25, 0.1, 0.15)这些权重是根据经验学到的
下一节:Python深度学习-Keras-文本生成