构建Keras模型的不同方法
序贯模型
最简单常用的模型 ,本质上是个列表
两种方法来构建模型
但是要注意,此时模型还没有权重, 必须调用build 并给定输入形状时 模型才具有权重
直接调用weights 会报错
summary函数可以显示模型的内容,这对调试很有用
还可以指定每层layer的名称
这个None,3 大家理解成 是一个 任意样本数量,每个样本有3个特征 即可
提前声明模型的输入形状时,可以在没有build的情况下 直接观察模型
函数式api
序贯模型 只能表示 具有单一输入和单一输出的模型。按顺序逐层处理,但大部分时候,我们会碰到 多输入模型和多输出模型(预测数据的不同方面)。 或具有非线上拓扑结构的模型。
可以把前面的例子 用函数式 来实现一遍 可以体会胰腺癌
假设我们有如下需求
构建一个ai系统,按优先级对客户支持工单进行排序,并将工单转给相应的部门。 这个模型 有3个输入:
- 工单标题(文本输入)
- 工单的文本正文(文本输入)
- 用户添加的标签(分类输入,假定为 one-shot编码)
可以将 文本输入编码 为由1和0组成的数组,数组大小为voc_size,
模型有2个输出;
工单的优先级分数,sigmoid输出 应处理工单的部门,对所有部门做softmax
python
voc_size = 10000
num_tags = 100
num_depart = 4
# 定义模型的输入
title = keras.Input(shape=(voc_size,), name="title")
text_body = keras.Input(shape=(voc_size,), name="text_body")
tags = keras.Input(shape=(num_tags,), name="tags")
# 通过拼接将输入特征组合成张量features
features = layers.Concatenate()([title, text_body, tags])
features = layers.Dense(64, activation="relu")(features)
# 定义模型的输出
priority = layers.Dense(1, activation="sigmoid", name="priority")(features)
department = layers.Dense(num_depart,activation='softmax',name="department")(features)
# 利用中间层 将输入特征 重组为更丰富的表示
model = keras.Model(inputs=[title,text_body,tags],outputs=[priority,department])
model.summary()
用随机数据来训练这个模型:
python
import numpy as np
num_samples = 1280
title_data = np.random.randint(0, 2, size=(num_samples, voc_size))
text_body_data = np.random.randint(0, 2, size=(num_samples, voc_size))
tags_data = np.random.randint(0, 2, size=(num_samples, num_tags))
priority_data = np.random.random(size=(num_samples, 1))
department_data = np.random.randint(0, 2, size=(num_samples, num_depart))
model.compile(optimizer="rmsprop", loss=["mean_squared_error", "categorical_crossentropy"],
metrics=[["mean_absolute_error"], ["accuracy"]])
model.fit([title_data, text_body_data, tags_data], [priority_data, department_data], epochs=1)
model.evaluate([title_data, text_body_data, tags_data], [priority_data, department_data])
priority_preds, department_preds = model.predict([title_data, text_body_data, tags_data])
用字典的方式来训练模型的写法 更加易读
python
import numpy as np
num_samples = 1280
title_data = np.random.randint(0, 2, size=(num_samples, voc_size))
text_body_data = np.random.randint(0, 2, size=(num_samples, voc_size))
tags_data = np.random.randint(0, 2, size=(num_samples, num_tags))
priority_data = np.random.random(size=(num_samples, 1))
department_data = np.random.randint(0, 2, size=(num_samples, num_depart))
model.compile(optimizer="rmsprop", loss={"priority": "mean_squared_error", "department": "categorical_crossentropy"},
metrics={"priority": ["mean_squared_error"], "department": ["accuracy"]})
model.fit([title_data, text_body_data, tags_data], [priority_data, department_data], epochs=1)
model.fit({"title": title_data, "text_body": text_body_data, "tags": tags_data},
{"priority": priority_data, "department": department_data}, epochs=1)
model.evaluate([title_data, text_body_data, tags_data], [priority_data, department_data])
priority_preds, department_preds = model.predict({"title": title_data, "text_body": text_body_data, "tags": tags_data})
我们还可以获取层的连接方式 并把他用图的形式展示出来
python
priority_preds, department_preds = model.predict(
{"title": title_data, "text_body": text_body_data, "tags": tags_data})
keras.utils.plot_model(model, "tickets.png", show_shapes=True)
可以看出来这个是None,也就是说 这个模型接受任意大小的批量
模型子类化
这个比较复杂,而且本书中也强调了 后面的例子都是函数式api,所以模型子类化的写法 这里就跳过了,有需要的自行百度关键字查询写法即可
回顾一下 模型的标准写法
python
import keras
from tensorflow.keras.datasets import mnist
from tensorflow.keras import layers
def get_mnist_model():
inputs = keras.Input(shape=(28 * 28,))
features = layers.Dense(512, activation='relu')(inputs)
features = layers.Dropout(0.5)(features)
outputs = layers.Dense(10, activation='softmax')(features)
model = keras.Model(inputs=inputs, outputs=outputs)
return model
if __name__ == '__main__':
(images, labels), (test_images, test_labels) = mnist.load_data()
images = images.reshape((60000, 28 * 28)).astype('float32') / 255
test_images = test_images.reshape((10000, 28 * 28)).astype('float32') / 255
# 区分下训练集和验证集
train_images, val_images = images[10000:], images[:10000]
train_labels, val_labels = labels[10000:], labels[:10000]
model = get_mnist_model()
# 编译模型
model.compile(optimizer='rmsprop', loss="sparse_categorical_crossentropy", metrics=["accuracy"])
# fit训练模型,提供验证数据 来监控模型在 没有见过的数据中的性能 也就是泛化的性能
model.fit(train_images, train_labels, epochs=3, validation_data=(val_images, val_labels))
test_metrics = model.evaluate(test_images, test_labels)
predictions = model.predict(test_images)
编写自定义指标
python
class RootMeanSquaredError(keras.metrics.Metric):
# 在构造函数中定义状态变量
def __init__(self, name="rmse", **kwargs):
super().__init__(name=name, **kwargs)
self.mse_sum = self.add_weight(name="mse_sum", initializer="zeros")
self.total_samples = self.add_weight(name="total_samples", initializer="zeros", dtype="int32")
# 在update_state方法中 实现状态更新逻辑
# 为了匹配mnist模型吗,需要分类预测值和整数标签
# y_true 一个数据批量对应的标签, y_pred 对应的模型预测值,最后一个参数可以忽略
def update_state(self, y_true, y_pred, sample_weight=None):
y_true = tf.one_hot(y_true, depth=tf.shape(y_pred)[1])
mse = tf.reduce_sum(tf.square(y_true - y_pred))
self.mse_sum.assign_add(mse)
num_samples = tf.shape(y_pred)[0]
self.total_samples.assign_add(num_samples)
# 返回指标的当前值
def result(self):
return tf.sqrt(self.mse_sum / tf.cast(self.total_samples,tf.float32))
# 重置指标状态,而不需要重新实例化,
# 这样相同的指标对象 可以在不同的训练轮次中使用
def reset_states(self):
self.mse_sum.assign(0.)
self.total_samples.assign_add(0)
python
# 编译模型
model.compile(optimizer='rmsprop', loss="sparse_categorical_crossentropy", metrics=["accuracy",RootMeanSquaredError()])
看下执行结果
回调函数
模型在开始训练 就是调用fit函数以后,一旦开始无法结束,我们可以在这个fit的过程中 利用一些回调函数 来做一些事情,这在某些场景下会非常好用
有很多回调函数可以利用,这里介绍其中集中常用的
EarlyStopping
此回调函数用于 发现验证损失不再改善时,停止训练。
python
callback_list = [
# 如果精度在两轮内都不再改善,则终端训练
keras.callbacks.EarlyStopping(
monitor='val_accuracy', patience=2,
),
# 在每轮过后保存当前权重
# 参数的含义是 只有当val_loss改善时,才会覆盖模型文件,这样就可以一直保存训练过程中的最佳模型
keras.callbacks.ModelCheckpoint(filepath="checkpoint_path.keras", monitor="val_loss", save_best_only=True, )]
model = get_mnist_model()
# 编译模型
model.compile(optimizer='rmsprop', loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
# fit训练模型,提供验证数据 来监控模型在 没有见过的数据中的性能 也就是泛化的性能
model.fit(train_images, train_labels, epochs=10, validation_data=(val_images, val_labels), callbacks=callback_list)
test_metrics = model.evaluate(test_images, test_labels)
predictions = model.predict(test_images)
编写自定义回调函数
python
# 自定义回调函数
# 参数logs 代表 前一个批量,前一个轮次,或者前一次训练的信息
# 这个回调函数 训练过程中 保存每个批量损失值组成的列表,还在每轮结束时保存这些损失值组成的图
class LossHistory(keras.callbacks.Callback):
# 在训练开始时被调用
def on_train_begin(self, logs):
self.per_batch_losses = []
# 在处理每个批量之后被调用
def on_batch_end(self, batch, logs):
self.per_batch_losses.append(logs.get("loss"))
# 在每轮结束时被调用
def on_epoch_end(self, epoch, logs):
plt.clf()
plt.plot(range(len(self.per_batch_losses)), self.per_batch_losses, label="Training loss for each batch")
plt.xlabel(f"Batch (epoch={epoch})")
plt.ylabel("Loss")
plt.legend()
plt.savefig(f"plot_at_epoch_{epoch}")
self.per_batch_losses = []
TensorBoard
官方提供的监控模型的最佳方式
python
tensorboard = keras.callbacks.TensorBoard(
log_dir="./logs",
)
# fit训练模型,提供验证数据 来监控模型在 没有见过的数据中的性能 也就是泛化的性能
model.fit(train_images, train_labels, epochs=10, validation_data=(val_images, val_labels), callbacks=[tensorboard])
然后输入命令:
tensorboard --logdir logs
然后点击这个网址即可
总结
构建模型有 序贯模型,函数式api 以及模型子类化。 多数情况下 我们使用前2者 要训练和评估模型 最简单的方式时fit和evaluate 除了fit,还可以完全从头开始编写自定义的训练循环,不过这是对研究全新的训练算法 才有用的,对于我们这种api 调用的,可以忽略这种技巧