目录
2.4.2 tf.keras.layers.Concatenate
2.6.1 tf.keras.models.load_model
一、神经网络基础
1.1 反向传播训练算法
使用有效技术自动计算梯度下降,针对每个模型参数计算网络误差的梯度,大致流程如下:
- 一次处理一个小批量并多次遍历整个训练集,每次遍历称为一个轮次
- 每个轮次中,将小批量传入输入层,后将其传入隐藏层,并将隐藏层的输出传入下一个隐藏层直到输出,保留中间结果 (前向通路)
- 使用一种损失函数来测量网络误差
- 应用链式法则,计算每个输出连接对错误的贡献程度
- 再次使用链式法则测量下面层的误差贡献直到输入层,即向后传播误差梯度 (后向)
- 执行梯度下降
其中需要随机初始化所有隐藏层的连接权重,流行的激活函数有以下几种
- 逻辑函数:
- 双曲正切函数:
- 线性整流函数:
1.2 回归MLP
回归任务的输入神经元的个数与输出维度相同,如果要保证输出始终为正,则需要在输出层中使用 ReLU 激活函数,或者使用该函数的平滑变体 softplus 激活函数, ;如果要保证预测值落在给定范围内,则应使用逻辑函数或者双曲正切函数作为输出层的激活函数。
训练使用的损失函数通常为均方误差,如果输入存在较多的离群值,则应使用平均绝对误差,或者使用 Huber 损失。
超参数 | 典型值 |
---|---|
输入神经元数量 | 每个输入特征一个 |
隐藏层数量 | 通常为1到5 |
每个隐藏层的神经元数量 | 通常为10到100 |
输出神经元数量 | 等于预测维度 |
隐藏层的激活函数 | ReLU |
输出层的激活函数 | 无、ReLU/softplus、逻辑、tanh |
损失函数 | MSE、MAE/Huber |
1.3 分类MLP
对于二分类问题,只需要一个输出神经元,将输出结果分为正负类。
对于多标签二分类问题,如同时预测邮件是否是垃圾邮件和是否是紧急邮件,则需要两个输出神经元,即为每个正类设置一个输出神经元。
对于多分类问题,则输出神经元的个数等于类的个数,并使用 softmax 激活函数作为输出层的激活函数来保证将概率限制在 0 到 1 之间并且加起来等于 1 。
超参数 | 二进制分类 | 多标签二进制分类 | 多类分类 |
---|---|---|---|
输入层和隐藏层 | 同回归MLP | ||
输出神经元个数 | 1 | 每个标签1个 | 每个类1个 |
输出层的激活函数 | 逻辑 | 逻辑 | softmax |
损失函数 | 交叉熵 |
二、利用 tf.keras 搭建神经网络
2.1 加载数据
Keras 里提供了用以加载数据的常见数据集如 MNIST、 Fashion MNIST等,以 Fashion MNIST 为例:
import tensorflow as tf
from tensorflow import keras
# 加载数据
fashion_mnist = keras.datasets.fashion_mnist
(X_train, y_train), (X_test, y_test) = fashion_mnist.load_data()
首先查看训练集的形状:
可以看出训练集有 60000 张图片,每张图片用一个 28*28 的数组存储。注意该训练集并未划分出验证集,所以接下来对训练集进行归一化处理并划分出验证集:
X_valid, X_train = X_train[:5000] / 255.0, X_train[5000:] / 255.0
y_valid, y_train = y_train[:5000], y_train[5000:]
2.2 顺序API创建分类模型
建立顺序模型的流程如下:
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28]))
model.add(keras.layers.Dense(300, activation="relu"))
model.add(keras.layers.Dense(100, activation="relu"))
model.add(keras.layers.Dense(10, activation="softmax"))
如上所建立的模型中,有一个输入层,两个隐藏层和一个输出层,两个隐藏层均采用 relu 激活函数,由于是一个多分类问题,并且共有十种类别,所以输出层的神经元个数为 10 个,且采用 softmax 激活函数 。上述编码方式的等效编码方式是:
# 仅作参考
model = keras.Sequential([
# keras.layers.Conv2D(64, (3, 3), activation='relu', input_shape=(28, 28, 1)), # 卷积层
# keras.layers.MaxPooling2D(2, 2), # MaxPooling 取最大特征值
# keras.layers.Conv2D(64, (3, 3), activation='relu'), # 卷积层
# keras.layers.MaxPooling2D(2, 2),
keras.layers.Flatten(input_shape=(28, 28)), # 输入层,将28*28的输入展平
keras.layers.Dense(128, activation=tf.nn.relu), # 中间层
keras.layers.Dense(10, activation=tf.nn.softmax) # 输出层,10个类别
])
可以通过 model.summary() 来展示模型概要。
可以通过索引或者 get_layer() 获取层。
可以通过 get_weights 和 set_weights 访问各层的参数。
可以看到各个神经元的偏置均被初始化为0 。
然后调用 compile() 方法来指定损失函数和要使用的优化器。
model.compile(
loss="sparse_categorical_crossentropy",
optimizer="sgd",
metrics=["accuracy"])
在多分类下,各类互斥,所以使用 sparse_categorical_crossentropy 作为损失函数,使用 softmax 作为输出层的激活函数;若为二分类,则应在输出层使用 sigmoid 即逻辑作为激活函数,使用 binary_crossentropy 作为损失函数。其中采用 sgd 即简单随机梯度下降作为优化器,学习率默认为 0.01,但通常通过
tf.keras.optimizers.SGD(learning_rate=0.01)
来设置学习率。
配置好模型后,就可以开始拟合。
history = model.fit(X_train, y_train,
epochs=100,
validation_data=(X_valid, y_valid),
callbacks=[checkpoint, early_callback, tensorboard_cb])
如果训练集不平衡,可以通过 fit() 方法中的 class_weight 参数来调整;如果需要每个实例的权重,可以设置 sample_weight 参数;若两个参数都提供,则训练中会将其相乘。
返回的 history 对象包括训练参数 history.params,训练轮次列表 history.epoch,且有很重要的以字典形式存储的损失指标等 history.history,可以通过 pandas 创建 DataFrame 对象并调用 plot() 方法绘制学习曲线,代码如下。
import pandas as pd
import matplotlib.pyplot as plt
pd.DataFrame(history.history).plot(figsize=(8, 5))
plt.grid(True)
plt.gca().set_ylim(0, 1)
plt.show()
得到的绘制结果如图所示。
训练完成后,可以输入测试集来评估其泛化误差。
model.evaluate(X_test, y_test)
最后可以使用模型的 predict() 方法来进行预测。接下来将对上述流程涉及到的方法进行简单的汇总。
2.2.1 tf.keras.Sequential
用来创建一个顺序模型。
tf.keras.Sequential(
layers=None, name=None
)
参数 | 注释 |
---|---|
layers | 需要添加到模型中的层列表 |
name | 模型的可选名 |
属性 | 注释 |
---|---|
layers | 存储该神经网络模型中的各层 |
metrics_names | 返回所有输出的模型显示标签 |
方法名 | 参数 | 参数解释 | 方法用途 |
---|---|---|---|
add | layer | 一层神经元 | 向模型中添加一层神经元 |
summary | 其它 | 见 tf.keras.Sequential | TensorFlow Core v2.6.0 | 模型摘要 |
compile | optimizer | 优化器名称,见 tf.keras.Optimizers | 对训练模型的配置 |
loss | 损失函数,为字符串或 tf.kears.losses.Loss 实例,见 tf.keras.losses | ||
metrics | 模型训练和测试期间的评估指标,可以为字符串或者 tf.kears.metrics.Metric 实例,见tf.kears.metrics,注意赋值方式应为 metrics=['accuracy'],应为多输出模型赋予多个指标 | ||
其它 | 见 tf.keras.Sequential | TensorFlow Core v2.6.0 | ||
fit | x | 输入数据,可以为 Numpy 数组、Tensorflow 张量等 | 训练模型 |
y | 标签,可以为 Numpy 数组、Tensorflow 张量等 | ||
为一个整数,用以指定每次梯度更新的样本数,默认为32;如有 6W 个样本,将 batch_size 设置为32,则每个轮次训练 60000/32 个样本 | |||
为整数,指定训练几次模型 | |||
callbacks | 训练期间的回调列表,可用于提前终止对模型的训练等操作,值为 tf.keras.callbacks.Callbacks 实例列表 | ||
validation_split | 介于 0 和 1 之间的浮点数。要用作验证数据的训练数据的一部分。该模型将把这部分训练数据分开,不会对其进行训练,并将在每个时期结束时评估损失和此数据的任何模型指标。 | ||
validation_data | 训练集,值为一个二元的 Numpy 或者 Tensorflow 张量元组,(x_val, y_val) | ||
其它 | 见 tf.keras.Sequential | TensorFlow Core v2.6.0 | ||
evaluate | x, y | 同上 | 对模型输入测试集后测试模型的性能,并返回一个 history 对象来记录每次训练的指标 |
其它 | 见 tf.keras.Sequential | TensorFlow Core v2.6.0 | ||
get_layer | index | 索引 | 返回一层神经元 |
name | 图层名 | ||
load_weights | filepath | 要加载的权重文件路径 | 从 TensorFlow 或 HDF5 权重文件加载所有层权重。 |
by_name | 为 True 则仅当权重共享相同名称时才加载,否则只有当网络拓扑相同时才加载 | ||
skip_mismatch | 是否跳过权重数量不匹配或权重形状不匹配的层的加载 | ||
options | tf.train.CheckpointOption | ||
predict | x | 同上 | 预测 |
其它 | tf.keras.Sequential | TensorFlow Core v2.6.0 | ||
save | filepath | 存储路径 | 存储模型 |
其它 | 见 tf.keras.Sequential | TensorFlow Core v2.6.0 | ||
save_weights | filepath | 存储路径 | 存储权重 |
其它方法 | 见 tf.keras.Sequential | TensorFlow Core v2.6.0 |
2.2.2 tf.keras.layers.Flatten
tf.keras.layers.Flatten(
data_format=None, **kwargs
)
创建模型中的一层,仅用于展平输入,即将输入转化为一维数组,如例子中指定 input_shape = [28, 28]
2.2.3 tf.keras.layers.Dense()
tf.keras.layers.Dense(
units, activation=None, use_bias=True,
kernel_initializer='glorot_uniform',
bias_initializer='zeros', kernel_regularizer=None,
bias_regularizer=None, activity_regularizer=None, kernel_constraint=None,
bias_constraint=None, **kwargs
)
创建模型中的一层神经元,参数表如下:
参数 | 注释 |
---|---|
units | 该层神经元的维度 |
activation | 激活函数 |
其它 | 见 tf.keras.layers.Dense | TensorFlow Core v2.6.0 |
2.3 顺序API创建回归模型
在上一节介绍了顺序 API 创建分类模型的问题,本节则要介绍顺序 API 创建回归模型的问题,相比于分类模型,对于单标签的输出模型中输出神经元仅只有一个,且输出神经元中不适用激活函数,模型采用的损失函数为均方误差。代码参考如下:
model = keras.Sequential([
keras.layers.Dense(30, activation='relu', input_shape=X_train.shape[1:]),
keras.layers.Dense(1)
])
model.compile(loss='mean_squared_error', optimizer='sgd')
history = model.fit(X_train, y_train, epochs=20, validation=(X_valid, y_valid))
2.4 函数式API创建复杂模型
非顺序神经网络的一个代表为“宽深”神经网络,它可将所有或部分输入直接连接至输出层,该架构可使神经网络能够学习深度模式(通过深度路径)或者简单规则(通过短路径)。
该方式建立神经网络的流程参考如下:
# 首先创建 Input 对象,需要规定 shape 和 dtype
input_ = keras.Input(shape=X_train.shape[1:])
# 创建一个有30个神经元的使用 relu 作为激活函数的隐藏层1,并通过 (input_) 指出该层的输入是 input_
hidden1 = keras.layers.Dense(30, activation='relu')(input_)
# 创建一个同上的隐藏层,并指定该层的输入是 hidden1 的输出
hiddden2 = keras.layers.Dense(30, activation='relu')(hidden1)
# 创建一个连接层,并指定该层的输入是 input_ 和 hidden2 的输出
concat = keras.layers.Concatenate()([input_, hiddden2])
# 创建输出层并指定该层的输入是 concat 的输出
output = keras.layers.Dense(1)(concat)
# 构建模型
model = keras.Model(inputs=[input_], outputs=output)
可以看出代码中以类似函数的方式将各层连接在一起,在 keras.Model() 中的参数中,可以通过向 inputs 参数传入一个列表来处理多输出问题,如:
model = keras.Model(inputs=[inpu_A, input_B], outputs=output)
若存在多输入问题,则调用 fit() 、evaluate() 等方法时都应传入多个矩阵。
同样可以向 outputs 参数传递一个列表来处理多输出:
model = keras.Model(inputs=[input_A, input_B], outputs=[output_A, output_B])
在遇到以下问题时可能遇到多输出:
- 在图片中定位和分类主要物体,这既是回归又是分类
- 可能有基于同一数据的多个独立任务,但多数情况下为每个任务训练一个模型会有更好的结果
- 正则化技术减少过拟合。
以下代码来构建一个正则化示例:
input_A = keras.Input(shape=[5], name="wide_input")
input_A = keras.Input(shape=[65], name="deepwide_input")
hidden1 = keras.layers.Dense(30, activation='relu')(input_B)
hiddden2 = keras.layers.Dense(30, activation='relu')(hidden1)
concat = keras.layers.Concatenate()([input_A, hiddden2])
output = keras.layers.Dense(1)(concat)
aux_output = keras.layers.Dense(1, name="aux_output")(hidden2)
model = keras.Model(inputs=[input_A, input_B], outputs=[output, axu_output])
model.compile(loss=['mse', 'mse'], loss_weights=[0.9, 0.1], optimizer='sgd')
history = model.fit([X_train_A, X_train_B], [y_train, y_train], epochs=20, validation_data=([X_valid_A, X_valid_B], [y_valid, y_valid]))
total_loss, main_loss, axu_loss = model.evaluate([X_test_A, X_test_B], [y_test, y_test])
可以看到,针对每个输出,在 compile 函数中以列表形式配置了一个损失函数,并且通过 loss_weights 参数来设置损失权重,因为更关系主要输出而不是用于正则化的辅助输出,所以给主要输出设置更高的损失权重。本小节涉及到的一些方法如下:
2.4.1 tf.keras.Input
用来实例化 tf 张量
tf.keras.Input(
shape=None, batch_size=None, name=None, dtype=None, sparse=None, tensor=None,
ragged=None, type_spec=None, **kwargs
)
参数 | 注释 |
---|---|
shape | 形状元组(整数),不包括批量大小。 |
其它 | 见 tf.keras.Input | TensorFlow Core v2.6.0 |
2.4.2 tf.keras.layers.Concatenate
用来连接输入列表的层。它接受一个张量列表作为输入,除了串联轴外,所有形状都相同,并返回一个单一的张量,它是所有输入的串联。
tf.keras.layers.Concatenate(
axis=-1, **kwargs
)
2.4.3 tf.keras.Model
tf.keras.Model(
*args, **kwargs
)
参数 | 注释 |
---|---|
inputs | 模型的输入 |
outputs | 模型的输出 |
name | 模型的名字 |
该方法返回一个模型,可参考 2.2.1 节的 model 方法来使用。
2.5 子类API创建动态模型
顺序式和函数式都是声明式的,即先声明确定使用的层,整个模型是一个静态的图,这样有助于保存、克隆、共享和查看分析其结构。可以通过继承 Model 类,在子类的构造函数中创建所需的层并通过 call 方法来执行计算,这就是子类 API。参考代码如下:
class WideAndDeepModel(keras.Model):
def __init__(self, units=30, activation="relu", **kwargs):
super().__init__(**kwargs)
self.hidden1 = keras.layers.Dense(units, activation=activation)
self.hidden2 = keras.layers.Dense(units, activation=activation)
self.main_output = keras.layers.Dense(1)
self.aux_output = keras.layers.Dense(1)
def call(self, inputs):
input_A, input_B = inputs
hidden1 = self.hidden1(input_B)
hidden2 = self.hidden2(hidden1)
concat = keras.layers.concatenate([input_A, hidden2])
main_output = self.main_output(concat)
aux_output = self.aux_output(hidden2)
return main_output, aux_output
但是这样做会导致模型的架构隐藏在 call 方法中,无法保存和克隆。
2.6 保存和还原模型
使用顺序 API 或者函数式 API 可以非常简单保存训练好的模型, keras 使用 HFD5 格式来保存模型的结构和参数、优化器等。代码参考如下:
# 保存训练好的模型
model.save('./model1.h5')
# 加载模型
model = keras.models.load_model('./model1.h5')
若使用子类方式,则只能通过 save_weights 和 load_weights 来保存和读取参数。
本小节涉及到的 API 如下:
2.6.1 tf.keras.models.load_model
tf.keras.models.load_model(
filepath, custom_objects=None, compile=True, options=None
)
参数 | 注释 |
---|---|
filepath | 字符串或
|
其它 | 见 tf.keras.models.load_model | TensorFlow Core v2.6.0 |
2.7 回调函数
注意到在前面模型构建后使用 fit 函数拟合过程中,对 callbacks 参数进行了赋值。该参数可以指定模型在训练开始和结束时、轮次开始和结束时将调用的对象列表。
会给 callbacks 参数赋予一个列表,列表中的值为回调对象,可以同时结合使用多个回调,目前涉及到的回调对象如下。
2.7.1 定期保存模型的检查点
通过 tf.keras.callbacks.ModelCheckpoint 实现以某个频率保存 Keras 模型或模型权重。
tf.keras.callbacks.ModelCheckpoint(
filepath, monitor='val_loss', verbose=0, save_best_only=False,
save_weights_only=False, mode='auto', save_freq='epoch',
options=None, **kwargs
)
参数 | 注释 |
---|---|
filepath | 保存模型的文件路径 |
monitor | 要监控的指标名称,具体设置见链接 |
save_best_only | 当模型最佳时才保存 |
其它 | 见 tf.keras.callbacks.ModelCheckpoint | TensorFlow Core v2.6.0 |
使用示例如下:
checkpoint = keras.callbacks.ModelCheckpoint("./model1.h5", save_best_only=True)
2.7.2 为防止过拟合,提前停止训练
通过 tf.keras.callbacks.EarlyStopping 回调,如果多个轮次在验证集上没有明显进展则会中断训练,并选择回滚至最佳模型。
tf.keras.callbacks.EarlyStopping(
monitor='val_loss', min_delta=0, patience=0, verbose=0,
mode='auto', baseline=None, restore_best_weights=False
)
参数 | 注释 |
---|---|
patience | 设置多少轮次没有改进后就停止训练 |
restore_best_weights | 是否回滚到最佳模型 |
其它 | 见 tf.keras.callbacks.EarlyStopping | TensorFlow Core v2.6.0 |
2.7.3 自定义回调
可以通过继承 tf.keras.callbacks.Callback 类来编写自定义的回调,并通过实现 on_train_begin、on_train_end、on_epoch_begin、on_epoch_end、on_batch_begin、on_batch_end 等函数来自定义回调调用时期,并可以通过实现 on_predict_begin 等函数实现预测,详细见 tf.keras.callbacks.Callback | TensorFlow Core v2.6.0 ,代码示例如下:
class MyCallback(tf.keras.callbacks.Callback):
def on_train_end(self, logs=None):
pass
三、神经网络参数优化概述
Hyperopt | 用于优化各种复杂的搜索空间(学习率的实数值和层数的离散值等) |
Hyperas、kopt或 Talos | 用于优化 Keras 的超参数 |
Keras Tuner | Google 针对 Keras 提供的易于使用的超参数优化库,带有可视化分析和托管服务 |
skopt | 通用优化库 |
Spearmint | 贝叶斯优化库 |
Hyperband | 快速超参数调整库 |
Sklearn-Deap | 基于进化算法的超参数优化库 |
隐藏层数量 | 对于复杂问题,深层网络可以使用更少的神经元对更复杂的函数进行吉纳摩,因而效率较高 |
---|---|
每个隐藏层神经元的数量 | 通常调整成金字塔形状,因为低层特征可以合并成更少的高层特征; 可以选择逐渐增加神经元的数量,也可以选择一个比实际需要的层和神经元更多的模型然后使用提前停止或正则化技术等防止过拟合; 不过通常增加隐藏层的数量会有更好的收益 |
学习率 | 一般而言,最佳学习率约为最大学习率的一半; 从非常低的学习率(1e-5)开始,逐渐增加到非常大的值如10 |
优化器 | 选择比普通的小批量梯度下降更好的优化器 |
批量大小 | 大批量可能会导致训练不稳定,模型泛化能力差; 一种思路是首先选择小批量(2-32),从而在短时间内获得更好额模型; 另一种思路是使用大批量处理并慢慢增加学习率,如果训练不稳定则换用小批量。 |