缩减CSV样本量后Val_loss出现NaN,求适配原示例的调整方案
解决Keras时间序列天气预报示例val_loss为NaN的问题
针对你把10分钟间隔数据改成小时级后出现的val_loss=NaN、提示“val_loss did not improve from inf”的问题,核心问题大概率出在归一化逻辑不匹配、样本生成错误或数据异常上,以下是具体的修复方案:
1. 重新适配归一化流程,避免数据泄露
原代码的归一化统计量(均值、标准差)是基于10分钟间隔的全量数据计算的,改成小时级后必须重新计算,还要排除未使用的3列:
# 假设你的数据框是df,新增的3列是col_a、col_b、col_c # 只保留原示例用到的特征列,排除新增列 used_features = [col for col in df.columns if col not in ['col_a', 'col_b', 'col_c']] df_used = df[used_features] # 按8:2分割训练/验证集,用训练集单独计算归一化统计量(关键!防止数据泄露) train_split = int(0.8 * len(df_used)) train_data = df_used.iloc[:train_split] mean = train_data.mean(axis=0) std = train_data.std(axis=0) # 对全量数据做归一化 df_normalized = (df_used - mean) / std
重点:绝对不能用全量数据计算均值/std,否则验证集的信息会提前泄露给模型,同时新增列哪怕不用也要排除,避免它们的异常值干扰归一化结果。
2. 修复时间序列样本生成逻辑
原step=6对应10分钟间隔下的1小时步长,改成小时级后step=1是对的,但要调整窗口长度(look_back):
原示例的look_back可能是24*6=144(对应24小时的10分钟数据),现在要改成24(对应24小时的小时级数据),否则窗口过长会导致样本无效:
import numpy as np def create_dataset(X, y, look_back=24, step=1): Xs, ys = [], [] # 从look_back索引开始遍历,避免越界 for i in range(look_back, len(X), step): Xs.append(X.iloc[i-look_back:i].values) ys.append(y.iloc[i].values) return np.array(Xs), np.array(ys) # 假设目标变量是'temp'(原示例的预报目标) y = df_normalized['temp'] X = df_normalized.drop('temp', axis=1) # 生成训练/验证样本 X_train, y_train = create_dataset(X.iloc[:train_split], y.iloc[:train_split], look_back=24, step=1) X_val, y_val = create_dataset(X.iloc[train_split:], y.iloc[train_split:], look_back=24, step=1)
生成后先验证样本数:print(len(X_train), len(X_val)),确保验证集样本数不为0,且没有空样本。
3. 清理数据中的异常值和缺失值
筛选整点数据后可能存在缺失段,或者新增列带进来的异常值影响了主特征:
# 检查主特征列的缺失值 print(df_used.isna().sum()) # 优先用前后值填充缺失值,避免删除过多样本 df_used = df_used.fillna(method='ffill').fillna(method='bfill') # 过滤极端值(Z-score超过3的视为异常) from scipy import stats z_scores = stats.zscore(df_used) abs_z_scores = np.abs(z_scores) filtered_entries = (abs_z_scores < 3).all(axis=1) df_used = df_used[filtered_entries]
如果某小时数据完全缺失,填充后再生成样本,避免模型读取空数据导致损失计算出现NaN。
4. 调整模型训练参数,避免梯度爆炸
如果前面的步骤都做完还是有问题,可能是原学习率太高导致梯度爆炸,尝试以下调整:
- 降低学习率,改用更稳定的损失函数:
from tensorflow.keras.optimizers import Adam model.compile(optimizer=Adam(learning_rate=0.0001), loss='mae')
MAE损失比MSE对极端值更宽容,先用来验证模型是否能正常收敛,收敛后再换回MSE。
- 添加梯度裁剪回调,防止梯度溢出:
from tensorflow.keras.callbacks import Callback class GradientClipping(Callback): def on_train_batch_end(self, batch, logs=None): for layer in self.model.layers: weights = layer.get_weights() # 把权重裁剪到[-1,1]区间,避免梯度爆炸 weights = [np.clip(w, -1.0, 1.0) for w in weights] layer.set_weights(weights) # 训练时加入回调 model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=50, batch_size=16, callbacks=[GradientClipping()])
验证步骤
- 检查归一化后的数据:
print(df_normalized.describe()),确保均值接近0,标准差接近1,没有inf或NaN。 - 查看生成的样本:
print(X_train[0], y_train[0]),确认是有效的时间序列片段,没有空值。 - 先跑1个epoch的小批量训练(batch_size=8),观察训练loss是否正常,再逐步增大epochs。
内容的提问来源于stack exchange,提问作者Larosen
相关产品推荐
相关产品推荐

