添加特征/加深神经网络后MSE上升的原因及优化方案求助
比特币5分钟价格预测模型异常:复杂模型效果劣于简单模型的原因及优化方案
我用PyTorch构建了预测5分钟后比特币价格的神经网络,实验中发现反常现象:增加输入特征或加深网络结构时,模型MSE反而显著上升:
- 单特征单层线性模型:训练集MSE≈2150,测试集≈2050
- 三特征单层线性模型:训练/测试集MSE均上升约100
- 全12特征深层神经网络:MSE升至≈20000,加入Dropout后效果更差
后续实现CLR(循环学习率)算法调整学习率,损失曲线更稳定,但最优模型仍是最简单的单特征线性模型。求该现象的原因及进一步优化建议。
现象原因分析
- 特征质量问题:新增的特征可能和目标变量(5分钟后比特币价格)相关性极低,甚至是噪声特征。线性模型中加入无关特征会引入额外方差,导致MSE上升;深层网络则会过度拟合这些噪声特征,使得泛化能力急剧下降。
- 特征未做标准化/归一化:从代码看,所有特征直接输入模型,没有做标准化处理。不同特征的量纲差异会导致模型训练时权重更新失衡,尤其是深层网络,容易出现梯度消失/爆炸,导致模型无法收敛到最优解。
- 深层网络结构过度复杂:你的深层网络有7层全连接层,参数数量远超过任务所需,而比特币价格短期预测本身具有很强的随机性,数据中的有效信号有限,复杂模型会过度拟合训练数据中的噪声,导致测试集MSE暴增。
- Dropout使用不当:Dropout的作用是抑制过拟合,但如果模型本身还没学到有效特征就加Dropout,会进一步破坏模型的学习能力,导致性能下降。你设置的Dropout概率(0.05)过低,对抑制过拟合作用有限,反而增加了训练的随机性。
优化方案
1. 特征工程优化
- 特征筛选:计算每个特征与目标变量
dq的皮尔逊相关系数,只保留相关性较高(比如绝对值>0.1)的特征;也可以用互信息、方差膨胀因子(VIF)剔除冗余/无关特征。 - 特征标准化:对所有输入特征做Z-score标准化(
(X - mean) / std)或Min-Max归一化,确保特征量纲一致,加速模型收敛。 - 构造有效特征:基于现有特征构造时间序列相关特征,比如:
- 过去N个时间段的价格均值/波动率
- 价格的差分(当前价-前一时间段价格)
- 技术指标(如RSI、MACD、布林带等)
2. 模型结构调整
- 简化深层网络:减少全连接层的数量和神经元个数,比如先尝试2-3层全连接层(如12→64→32→1),逐步增加复杂度,观察性能变化。
- 改用时间序列专用模型:比特币价格是时间序列数据,全连接网络无法捕捉时序依赖,可尝试:
- LSTM/GRU:专门处理时序数据的循环神经网络,能捕捉价格的时序关联
- Transformer(如Temporal Fusion Transformer):适合多变量时序预测,能处理长期依赖和特征交互
- 尝试线性模型变种:比如带正则化的线性模型(
Ridge/Lasso),在单特征线性模型基础上加入L2/L1正则,进一步提升泛化能力。
3. 训练策略优化
- 正则化调整:如果用深层网络,可尝试增大Dropout概率(比如0.2-0.5),或加入L2正则(在优化器中设置
weight_decay=1e-4),抑制过拟合。 - 学习率调优:CLR已经让损失曲线稳定,但可以尝试更小的初始学习率(比如1e-4),或用学习率衰减(如StepLR、ReduceLROnPlateau),让模型更平缓地收敛。
- 数据增强:对时序数据做简单增强,比如加入少量高斯噪声,或对特征做随机缩放,提升模型的泛化能力。
- 交叉验证:改用时间序列交叉验证(而非随机划分train/test),避免数据泄露,更准确评估模型的泛化能力。
4. 损失函数与评估指标
- 比特币价格预测是回归任务,除了MSE,可尝试MAE(平均绝对误差)作为损失函数,对异常值更鲁棒;同时用MAPE(平均绝对百分比误差)评估模型的相对误差,更贴合实际业务场景。
数据预处理代码
df.head(2) X = df.drop('dq', axis=1) y = df['dq'] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) X_train = torch.from_numpy(X_train.values.astype('float32')) X_test = torch.from_numpy(X_test.values.astype('float32')) y_train = torch.from_numpy(y_train.values.astype('float32')).reshape(-1, 1) y_test = torch.from_numpy(y_test.values.astype('float32')).reshape(-1, 1) device = torch.device('mps')
DataFrame示例(前2行)
单特征单层模型训练代码
class MyModel(nn.Module): def __init__(self): super(MyModel, self).__init__() self.fc = nn.Linear(1, 1) # Linear layer def forward(self, x): return self.fc(x) model = MyModel() early_stopping = EarlyStopping(patience=5, verbose=True) criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters()) # Prepare the data X_train_avg = ((X_train[:, 1:2] + X_train[:, 1:2]) / 2.0) X_test_avg = ((X_test[:, 1:2] + X_test[:, 1:2]) / 2.0) model = model.to(device) X_train_avg = X_train_avg.to(device) y_train = y_train.to(device) X_test_avg = X_test_avg.to(device) y_test = y_test.to(device) train_data = TensorDataset(X_train_avg, y_train) test_data = TensorDataset(X_test_avg, y_test) train_loader = DataLoader(train_data, batch_size=32, shuffle=True, pin_memory=True) test_loader = DataLoader(test_data, batch_size=32) num_epochs = 40 for epoch in range(num_epochs): model.train() total_loss = 0 total_batches = len(train_loader) pbar = tqdm(train_loader) for batch_idx, (inputs, targets) in enumerate(pbar): optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() total_loss = total_loss + loss.item() if (batch_idx + 1) % 100 == 0: pbar.set_description(f'Epoch {epoch+1}/{num_epochs}, Loss: {total_loss/batch_idx:.4f}') if batch_idx == total_batches - 1: model.eval() with torch.no_grad(): test_loss = sum(criterion(model(inputs), targets) for inputs, targets in test_loader) test_loss /= len(test_loader) pbar.set_description(f'Epoch {epoch+1}/{num_epochs}, Loss: {total_loss/(1+batch_idx):.4f}, TestLoss: {test_loss:.4f}') early_stopping(test_loss, model) if early_stopping.early_stop: print("Early stopping") break
单特征模型损失:训练集约2150,测试集约2050
三特征单层模型代码
class MyModel(nn.Module): def __init__(self): super(MyModel, self).__init__() self.fc = nn.Linear(3, 1) # Linear layer def forward(self, x): return self.fc(x) model = MyModel() early_stopping = EarlyStopping(patience=5, verbose=True) criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters()) # Prepare the data X_train_avg = (X_train[:, 1::4] / 2.0 + X_train[:, 3::4] / 2.0) X_test_avg = (X_test[:, 1::4] / 2.0 + X_test[:, 3::4] / 2.0)
三特征模型损失:较单特征模型上升约100
全特征深层模型代码
class MyModel(nn.Module): def __init__(self, input_dim): super(MyModel, self).__init__() self.layers = nn.Sequential( nn.Linear(input_dim, 256), nn.ReLU(), # nn.Dropout(p=0.05), nn.Linear(256, 128), nn.ReLU(), # nn.Dropout(p=0.05), nn.Linear(128, 64), nn.ReLU(), # nn.Dropout(p=0.05), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 16), nn.ReLU(), nn.Linear(16, 1) ) def forward(self, x): return self.layers(x) model = MyModel(12) early_stopping = EarlyStopping(patience=20, verbose=True) criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters()) model = model.to(device) X_train = X_train.to(device) y_train = y_train.to(device) X_test = X_test.to(device) y_test = y_test.to(device)
深层模型损失:约20000,加入Dropout后更糟
内容的提问来源于stack exchange,提问作者Schmied
相关产品推荐
相关产品推荐

