前言
本文主要使用一维的卷积神经网络完成时序数据的分类任务,使用的数据主要来自于 UCR/UEA archive 的 FordA 数据集。
数据准备
下面的代码主要是列出系统上的物理 GPU 设备,然后为每个 GPU 设备设置显存增长方式,保证按需使用 GPU ,而不是直接占满 GPU ,这样能避免不必要的显存浪费,本次任务使用大约在 3G 左右。
gpus = tf.config.experimental.list_physical_devices(device_type='GPU')
for gpu in gpus:
tf.config.experimental.set_memory_growth(gpu, True)
下面代码主要是为了完成数据的加载和预处理工作,以便准备好任务所需的时间序列数据,后续可以用于模型的训练和测试。具体如下:
reader
函数从 filename 中加载数据,将每一行用 '\t' 分割开,然后将所有行的第一列作为标签 y ,并将类型转为 int ,将所有行的除第一列的所有列作为输入 x ,返回 x 和 y 。- 分别使用
reader
函数从两个不同的 URL 加载训练集和测试集数据,并将训练集和测试集的特征和标签分别赋给x_train
、y_train
以及x_test
、y_test
。FordA
来自于在标准操作环境下,汽车引擎所发出的声音。通过汽车引擎声音来判断引擎是否存在问题,也就是一个二分类问题。第一列是类别, -1 表示有问题,1 表示没问题,后面的所有列表示的是输入的 500 维特征。训练集 3601 个, 测试集 1320 个。 - 通过使用
reshape
方法,将训练集和测试集的特征数据从二维数组重塑为三维数组,因为要使用Conv1D
深度学习模型,所以必须得有的输入要求。具体来说,这将数据的形状从(样本数, 特征数)
更改为(样本数, 特征数, 1)
,其中1
表示数据的通道数, 处理时间任务通常需要将数据变换成这种形式。 num_classes
用于分类任务的输出层设置。这里就是一个二分类也就是 2 。- 通过使用
np.random.permutation
和索引idx
,对训练集特征数据和标签数据进行同样的随机洗牌,以提高训练模型的泛化能力。 - 将训练集和测试集的标签中的
-1
替换为0
,便于进行模型的计算。 input_shape
:定义了模型输入数据的形状。
def reader(filename):
data = np.loadtxt(filename, delimiter='\t')
y = data[:, 0]
x = data[:, 1:]
return x, y.astype(int)
x_train, y_train = reader("https://raw.githubusercontent.com/hfawaz/cd-diagram/master/FordA/FordA_TRAIN.tsv")
x_test, y_test = reader("https://raw.githubusercontent.com/hfawaz/cd-diagram/master/FordA/FordA_TEST.tsv")
x_train = x_train.reshape((x_train.shape[0], x_train.shape[1], 1))
x_test = x_test.reshape((x_test.shape[0], x_test.shape[1], 1))
num_classes = len(np.unique(y_train))
idx = np.random.permutation(len(x_train))
x_train, y_train = x_train[idx], y_train[idx]
y_train[y_train == -1] = 0
y_test[y_test == -1] = 0
input_shape = x_train.shape[1:]
模型搭建
这里主要是进行模型结构的搭建,使用 Keras 建立一个卷积神经网络,该模型通常用于处理一维数据的分类问题。而我们这里的时间序列数据就是一维数据,以下是代码的主要含义:
-
Input
层定义了模型的输入层。input_shape
已经提前计算出来了,就是[500, 1]
。 -
接下来是三个完全一样的卷积层,每个卷积层都有
Conv1D
、BatchNormalization
、ReLU
:- 每个卷积层输出 128 个特征图。
- 每个卷积层之后都跟着 BatchNormalization 层,用于 batch 标准化,这有助于稳定训练过程,并加速模型的收敛。
- 最后是 ReLU 激活函数,用于引入非线性特征变化。
-
GlobalAveragePooling1D
用于将卷积层的输出特征图转换成全局平均池化的形式,减少维度并保留重要的特征。在时间序列问题中,这有助于降低序列长度,以便最后的全连接层可以更好地进行分类。 -
Dense
是全连接层,也是输出层,使用 softmax 激活函数,以便进行多类别分类。
model = keras.Sequential([
keras.layers.Input(shape=input_shape),
keras.layers.Conv1D(128, 3, padding='same'),
keras.layers.BatchNormalization(),
keras.layers.ReLU(),
keras.layers.Conv1D(128, 3, padding='same'),
keras.layers.BatchNormalization(),
keras.layers.ReLU(),
keras.layers.Conv1D(128, 3, padding='same'),
keras.layers.BatchNormalization(),
keras.layers.ReLU(),
keras.layers.GlobalAveragePooling1D(),
keras.layers.Dense(num_classes, activation='softmax')
])
模型编译和训练
这里主要是完成模型的编译和训练,同时使用了一些回调函数来监视和控制训练过程。具体如下:
-
callbacks列表
定义了一组回调函数,它们在训练期间根据一些条件触发特定的操作。ModelCheckpoint
:用于保存模型的回调函数,可以保存在验证集上 val_loss 性能最好的模型。ReduceLROnPlateau
:学习率衰减回调函数。当验证集上的 val_loss 在经过 patience 个 epoch 后仍没有改进,该回调函数将以 factor 倍率减小学习率以帮助模型更好地收敛。EarlyStopping
:早停止回调函数。当验证集上的 val_loss 在 patience 个 epoch 后仍没有改进,可以终止训练过程,以防止过拟合。
-
编译模型
:用于指定模型的优化器、损失函数和评估指标。optimizer='adam'
:使用 Adam 优化器进行模型参数更新。loss=keras.losses.SparseCategoricalCrossentropy()
:使用稀疏分类交叉熵作为损失函数,适用于多类别分类任务。metrics=[keras.metrics.SparseCategoricalAccuracy()]
:使用稀疏分类准确率作为模型的性能指标。
-
模型训练
:训练过程中 batch_size 为 128 ,将 20% 的数据作为验证集,最多训练 500 轮。
callbacks = [
keras.callbacks.ModelCheckpoint('best_model.h5', save_best_only=True, monitor="val_loss"),
keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=20, min_lr=0.0001),
keras.callbacks.EarlyStopping(monitor='val_loss', patience=50, verbose=1)
]
model.compile(optimizer='adam', loss=keras.losses.SparseCategoricalCrossentropy(),
metrics=[keras.metrics.SparseCategoricalAccuracy()])
history = model.fit(x_train, y_train, batch_size=128, epochs=500, callbacks=callbacks, validation_split=0.2, verbose=1)
结果打印,可以看出来随着训练的进行验证集的准确率子不断提升,损失在不断降低,同时学习率也在不断降低,最后在 352 轮 epoch 后停止训练:
Epoch 1/500
23/23 [==============================] - 2s 16ms/step - loss: 0.5937 - sparse_categorical_accuracy: 0.6622 - val_loss: 0.7124 - val_sparse_categorical_accuracy: 0.4688 - lr: 0.0010
Epoch 2/500
23/23 [==============================] - 0s 9ms/step - loss: 0.4759 - sparse_categorical_accuracy: 0.7663 - val_loss: 0.7992 - val_sparse_categorical_accuracy: 0.4688 - lr: 0.0010
...
Epoch 162/500
23/23 [==============================] - 0s 9ms/step - loss: 0.0961 - sparse_categorical_accuracy: 0.9663 - val_loss: 0.2214 - val_sparse_categorical_accuracy: 0.9085 - lr: 5.0000e-04
Epoch 352/500
23/23 [==============================] - 0s 9ms/step - loss: 0.0421 - sparse_categorical_accuracy: 0.9892 - val_loss: 0.1041 - val_sparse_categorical_accuracy: 0.9639 - lr: 1.0000e-04
Epoch 352: early stopping
模型评估
这里用于加载之前训练好的模型,对测试数据进行评估,并绘制训练过程中的准确率变化趋势图。
model = keras.models.load_model('best_model.h5')
test_loss, test_acc = model.evaluate(x_test, y_test)
print("测试集准确率", test_acc)
print("测试集损失值", test_loss)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure()
plt.plot(history.history["sparse_categorical_accuracy"])
plt.plot(history.history["val_sparse_categorical_accuracy"])
plt.title("准确率变化趋势")
plt.ylabel("准确率", fontsize='large')
plt.xlabel('轮数', fontsize='large')
plt.legend(['训练过程', '验证过程'], loc='best')
plt.show()
plt.close()
结果打印,可以从图中看出,训练过程是符合预期的,没有发生过拟合现象:
测试集准确率 0.9681817889213562
测试集损失值 0.09469714760780334