如何修正倾向于Class 0的时序LSTM二分类模型?
序列二分类模型偏差问题
我正在基于15个连续序列(含1个非连续但仍属序列的特征)做二分类预测,数据集共933k条数据,每条含15个特征。实际类别分布为Class 0占~45.09%,Class 1占~54.91%,存在轻微不平衡。目前模型始终偏向输出Class 0,训练后Recall持续下降,Loss、Accuracy、Precision略有上升但最终趋近54%,明显是模型在适应数据集偏差而非学习有效特征。
常规的欠采样/过采样方法因LSTM依赖连续滞后数据集,随机增减样本不可行。我已尝试以下优化手段:
- 调整学习率
- 增减模型复杂度
- 添加输出层偏置初始化(bias_initializer)
- 加入Dropout层
- 调整时间步长
- 移除数据平坦区
- 小数据集过拟合测试(1000轮后准确率达55%-75%)
当前期望:测试集与验证集准确率达到55%以上,预测的类别分布落在40-60区间内(接近均衡)。
模型代码
data = pd.read_csv('data.csv') X_train, y_train, X_cv, y_cv, X_test, y_test, init_bias = data_formatting.transform(data=data) output_bias = tf.keras.initializers.Constant(init_bias) model1 = Sequential([ InputLayer(batch_input_shape=(64, 60, 15)), LSTM(units=64, return_sequences=True, stateful=True), tf.keras.layers.BatchNormalization(), tf.keras.layers.Dropout(0.2), LSTM(units=64, return_sequences=True, stateful=True), tf.keras.layers.BatchNormalization(), tf.keras.layers.Dropout(0.2), LSTM(units=64, stateful=True), tf.keras.layers.BatchNormalization(), tf.keras.layers.Dropout(0.2), Dense(units=32, activation='relu'), tf.keras.layers.BatchNormalization(), Dense(units=1, activation='sigmoid', bias_initializer=output_bias) ]) model1.summary() cp = ModelCheckpoint('Model_NewF_3/', save_best_only=True) model1.compile(loss=BinaryCrossentropy(), optimizer=AdamW(learning_rate=0.000001), metrics=['accuracy', tf.keras.metrics.Precision(), tf.keras.metrics.Recall()]) model1.fit(X_train, y_train, validation_data=(X_cv, y_cv), epochs=10, batch_size=64, callbacks=[cp])
混淆矩阵情况
- 训练集:Class 0预测数量远高于实际占比,Class 1被大量误判为Class 0
- 测试集:同样呈现Class 0预测偏向,Class 1召回率极低
优化方向建议
1. 类别加权损失函数
计算类别权重,让模型对被低估的Class 1给予更高损失权重,避免偏向多数类(此处模型反向偏向,本质是对Class 1学习不足):
import numpy as np # 计算类别权重:n_samples/(n_classes*class_counts) class_weight = { 0: len(y_train)/(2 * np.sum(y_train == 0)), 1: len(y_train)/(2 * np.sum(y_train == 1)) } # 训练时传入参数 model1.fit(..., class_weight=class_weight)
也可直接在BinaryCrossentropy中设置weight参数,或自定义加权损失函数。
2. 序列感知的重采样
不破坏序列连续性的前提下调整数据分布:
- 提取包含高占比Class 1的连续序列块,复制这些块补充到训练集
- 裁剪掉连续的、纯Class 0的长序列块,减少冗余的偏向性数据
3. 修正Stateful LSTM训练逻辑
Stateful LSTM要求每个batch的样本是连续的序列片段,且需手动重置状态:
# 每个epoch结束后重置状态 for epoch in range(10): model1.fit(X_train, y_train, ...) model1.reset_states()
同时确保数据集划分是按连续序列切割,而非随机打乱,否则stateful的优势无法发挥。
4. 分类阈值优化
训练完成后,在验证集上搜索最优分类阈值(默认0.5可能不适用):
from sklearn.metrics import f1_score y_pred_proba = model1.predict(X_cv) thresholds = np.linspace(0.3, 0.7, 41) best_thresh, best_f1 = 0.5, 0 for thresh in thresholds: y_pred = (y_pred_proba > thresh).astype(int) current_f1 = f1_score(y_cv, y_pred) if current_f1 > best_f1: best_f1 = current_f1 best_thresh = thresh print(f"最优阈值: {best_thresh}, 对应F1分数: {best_f1}")
用最优阈值替代0.5,可平衡预测的类别分布。
5. 增强序列特征捕捉
- 尝试双向LSTM(
tf.keras.layers.Bidirectional(LSTM(...))),捕捉序列前后的关联信息 - 添加Attention层,让模型自动关注对分类关键的时间步特征
- 提取序列的时间统计特征(滑动窗口均值、方差、趋势),补充到原有特征中
6. 调整训练策略
- 增加训练轮数,配合
EarlyStopping回调(监控验证集Recall或F1),避免过拟合同时保证充分训练 - 尝试更大的学习率(当前1e-6过小,可逐步调整到1e-4~1e-5范围),提升模型收敛速度
内容的提问来源于stack exchange,提问作者Didlex
相关产品推荐
相关产品推荐

