尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

数学建模竞赛中BP神经网络与CNN的PyTorch实战应用

数学建模竞赛中BP神经网络与CNN的PyTorch实战应用 1. 项目概述当数学建模遇上深度学习那年研究生数学建模D题的赛题我至今记忆犹新。它本质上是一个典型的、数据驱动的复杂系统分析与预测问题通常涉及高维、非线性、时序或空间关联的数据。对于习惯了传统统计模型和优化算法的建模者来说这类题目既是挑战也是拥抱新工具的绝佳机会。题目要求我们基于给定的数据集构建模型来预测、分类或识别某种模式。这正是深度学习的“主场”。我选择的解题核心是BP神经网络Backpropagation Neural Network和卷积神经网络Convolutional Neural Network, CNN并用PyTorch框架实现。这不是为了炫技而是基于问题特性的务实选择。BP神经网络作为最经典的多层前馈网络是处理表格数据、进行非线性回归和分类的“万能近似器”基础。而CNN凭借其局部连接和权值共享的特性天生擅长从具有网格拓扑结构的数据如图像、时序信号、甚至某些经过重构的表格数据中提取空间或局部特征。将两者结合或对比使用能让我们从不同维度“攻击”问题。这篇分享我会详细拆解当年解题的全过程从对题目的理解与数据预处理到两种网络模型的设计、PyTorch实现、训练调优再到最终的模型集成与结果分析。我会重点分享那些在标准教程里不会写的“坑”和“技巧”比如如何将抽象的数学建模问题“翻译”成神经网络能理解的输入输出格式如何在有限的比赛时间内进行高效的模型调试以及如何写出既清晰又高效的PyTorch代码。无论你是正在备战数模的研究生还是刚接触PyTorch和深度学习想找个实战项目练手的朋友希望这篇基于真实赛题复盘的经验能给你带来实实在在的启发。2. 解题思路与模型选型分析面对数学建模赛题第一步永远不是急着写代码而是彻底理解问题并将问题“映射”到合适的数学模型上。D题通常不会直接说“请用神经网络解题”它可能描述一个物理过程、一个经济现象或一个社会系统的观测数据。我们的任务就是透过现象看本质识别出这属于哪类机器学习任务。2.1 问题抽象与任务定义首先我们需要明确几个关键点输入是什么赛题提供的数据集。可能是纯数值表格每行一个样本每列一个特征也可能是图像、时间序列或者是它们的混合。需要仔细阅读数据说明理解每个字段的物理意义。输出是什么题目要求预测的目标变量。可能是连续值回归问题如预测房价、销量也可能是离散类别分类问题如判断设备故障类型、用户信用等级或者是复杂的结构化输出如时间序列预测、多标签分类。D题往往要求预测未来某个时刻的状态或对样本进行分级评价。数据有何特性数据是否存在缺失、异常特征之间量纲差异是否巨大是否存在明显的时序或空间相关性这些特性直接决定了我们后续的预处理步骤和模型选择。以我曾处理过的一个类似D题为例题目提供了某地区历史气象数据温度、湿度、气压等和电力负荷数据要求预测未来24小时的负荷曲线。这显然是一个多变量时间序列回归预测问题。输入是过去一段时间窗口内的多变量序列输出是未来一段时间窗口的单变量序列。2.2 为什么选择BP神经网络和CNN基于问题抽象我们来分析模型选型的逻辑BP神经网络全连接网络的适用场景核心优势强大的非线性拟合能力。只要网络足够深、足够宽理论上可以逼近任何连续函数。这对于挖掘高维特征间复杂的、非线性的相互作用规律非常有效。在本类问题中的应用当我们的特征是基于领域知识构建的、彼此独立的“手工特征”时BP网络是首选。例如在上述负荷预测问题中如果我们已经计算出了过去24小时的平均负荷、最高负荷、最低负荷、负荷波动方差等统计特征作为输入那么这些特征之间的关系就适合用全连接网络来学习。选型理由实现简单调参思路相对成熟是检验问题是否具有非线性可学习性的“基线模型”。如果连一个精心调参的BP网络都学不好那可能意味着特征工程没到位或者数据本身噪声太大。卷积神经网络CNN的适用场景核心优势自动提取局部特征并具有平移不变性。通过卷积核在输入数据上滑动它能高效地捕捉局部模式如边缘、纹理、周期信号中的特定波形。在本类问题中的“创造性”应用对于时间序列数据我们可以将其视为一个一维“图像”通道数等于特征维数长度等于时间步长。一维CNN能自动学习序列中的局部时间模式例如负荷在每天早高峰的上升模式。对于某些空间分布数据如不同监测站点的数据甚至可以重构为二维网格使用二维CNN。这是将CNN应用于非图像数据的精髓也是解题的亮点之一。选型理由相比全连接网络CNN的参数更少训练更快且更不容易过拟合因为它通过权值共享隐式地引入了“局部性”先验知识。对于具有明显局部相关性的数据如时序、空间数据CNN往往能取得比纯BP网络更好的效果。最终策略在实际解题中我通常会采用“双模型对比验证”或“特征融合”的策略。即分别用BP网络处理全局统计特征和CNN处理原始序列的局部特征进行建模对比其性能。有时还会将BP网络提取的高层抽象特征与CNN提取的局部特征进行拼接再输入到一个更小的融合网络中进行最终预测这常常能提升模型的鲁棒性和精度。2.3 PyTorch框架的优势在紧张的比赛环境中框架的选择至关重要。我选择PyTorch而非其他框架如TensorFlow主要基于以下几点实战考量动态计算图Eager Execution这使得调试变得极其直观。你可以像写普通Python代码一样逐行执行随时用print()或调试器查看张量的形状和值。在模型结构探索和快速原型验证阶段这能节省大量时间。Pythonic的API设计PyTorch的代码风格非常贴近Python学习曲线相对平缓。自定义网络层、损失函数、训练循环都非常灵活直观这对于需要快速实现创新想法的数学建模比赛来说是个巨大优势。强大的生态系统torch.nn模块提供了丰富的网络层torch.optim提供了各种优化器torch.utils.data的Dataset和DataLoader让数据加载和批处理变得异常简单。这些都能让我们专注于模型和问题本身而不是底层实现。社区与资源PyTorch拥有活跃的社区遇到任何问题几乎都能快速找到解决方案或参考代码这在分秒必争的比赛中是宝贵的“外援”。注意在比赛开始前确保你的编程环境Anaconda PyTorch CUDA if available已经搭建并测试无误。不要在比赛期间才折腾环境那会打乱节奏、影响心态。3. 数据预处理与特征工程实战模型决定上限特征决定下限。在深度学习时代特征工程的重要性并未降低而是演变为如何更好地将领域知识融入数据表示以及如何为神经网络准备“可口”的输入。3.1 数据清洗与探索性分析拿到数据后我首先会用Pandas进行加载和初步探索。import pandas as pd import numpy as np import matplotlib.pyplot as plt # 加载数据 data pd.read_csv(competition_data.csv) print(data.info()) # 查看数据概览有无缺失类型 print(data.describe()) # 查看数值分布 print(data.head())关键检查点缺失值处理对于时间序列常用前后插值法data.fillna(methodffill).fillna(methodbfill)。对于其他数据可根据情况用均值、中位数或基于其他特征的预测值填充。如果缺失太多考虑是否直接删除该特征或样本。异常值检测通过箱线图或describe()查看数据的上下限。对于明显脱离物理意义或统计分布的异常点需要谨慎处理。在负荷预测中一个远高于历史极值的负荷点可能是抄表错误可以用前后时刻的平均值或滑动窗口的中位数替代。重复值检查data.duplicated().sum()直接删除重复行。3.2 特征构造为BP网络和CNN准备“食材”这是将原始数据转化为模型输入的关键一步思路因模型而异。为BP网络构造特征BP网络喜欢固定长度的、有明确意义的特征向量。我们需要从原始数据中提炼出这样的特征。对于时序数据采用滑动窗口法。假设我们要用过去T个时间步的数据预测未来K步。对于每个时间点t我们构造一个特征向量包含从t-T1到t时刻的所有特征值。此外可以加入一些统计特征如该窗口内的均值、方差、最大值、最小值、斜率等这些特征往往具有很强的预测能力。示例代码构造滑动窗口特征def create_sliding_windows(data, window_size, forecast_horizon): X, y [], [] for i in range(len(data) - window_size - forecast_horizon 1): X.append(data[i:iwindow_size]) # 输入窗口 y.append(data[iwindow_size : iwindow_sizeforecast_horizon, target_column_idx]) # 输出目标 return np.array(X), np.array(y)领域特征结合题目背景。在负荷预测中“是否节假日”、“小时”、“星期几”是非常强的特征需要进行独热编码One-Hot Encoding或周期性编码如将小时转换为sin(2π*hour/24), cos(2π*hour/24)。为CNN构造特征CNN的输入通常保持原始数据的局部结构。对于一维时序数据我们直接使用滑动窗口得到的原始序列片段作为输入。关键在于输入张量的形状。形状定义在PyTorch中CNN的输入通常是四维张量(batch_size, channels, height, width)。对于一维CNN我们常用三维张量(batch_size, channels, length)。batch_size: 批大小。channels: 类比图像的RGB通道。在多元时间序列中每个特征变量就是一个通道。例如有温度、湿度、气压3个特征那么channels3。length: 时间序列窗口的长度。数据重构我们需要将(num_samples, window_size, num_features)的数组转换为(num_samples, num_features, window_size)。# 假设 X_bp 形状为 (1000, 24, 5) - 1000个样本窗口长度245个特征 X_cnn np.transpose(X_bp, (0, 2, 1)) # 转置后形状为 (1000, 5, 24)3.3 数据标准化与数据集划分神经网络对输入数据的尺度非常敏感。我们必须进行标准化。方法最常用的是Z-score标准化即减去均值除以标准差。切记必须用训练集的均值和标准差去标准化验证集和测试集避免数据泄露。from sklearn.preprocessing import StandardScaler scaler_X StandardScaler() scaler_y StandardScaler() # 假设 train_X 形状为 (n_samples, n_features) 对于BP网络或展平后处理 # 对于CNN通常对每个特征通道单独标准化 train_X_original_shape train_X.shape # 重塑为 (n_samples, n_features) 或按通道处理 train_X_reshaped train_X.reshape(-1, train_X.shape[-1]) scaler_X.fit(train_X_reshaped) train_X_scaled scaler_X.transform(train_X_reshaped).reshape(train_X_original_shape) # 对标签y也进行标准化回归问题 scaler_y.fit(train_y) train_y_scaled scaler_y.transform(train_y)数据集划分对于时间序列数据不能随机打乱划分必须按时间顺序划分防止未来信息泄露。通常按比例如前70%训练中间15%验证最后15%测试。实操心得特征工程和数据预处理会占用整个项目60%以上的时间但其效果直接决定了模型的性能天花板。多花时间在这里进行探索和实验比如尝试不同的窗口大小、不同的统计特征组合、不同的编码方式并用一个简单的线性模型或浅层网络快速验证其特征有效性远比盲目堆叠复杂的网络层更有效率。4. PyTorch模型构建详解数据准备就绪后我们进入核心环节用PyTorch定义我们的BP网络和CNN模型。我会采用面向对象的方式定义清晰且易于扩展的nn.Module子类。4.1 BP神经网络模型实现BP网络的核心是全连接层nn.Linear。设计的关键在于确定层数、每层的神经元数量以及激活函数的选择。import torch import torch.nn as nn import torch.nn.functional as F class BPNet(nn.Module): 一个简单的多层BP神经网络。 假设输入特征维度input_size 输出维度output_size (对于回归是1对于多分类是类别数) def __init__(self, input_size, hidden_sizes, output_size, dropout_rate0.2): super(BPNet, self).__init__() # 动态构建隐藏层 layers [] prev_size input_size for i, hidden_size in enumerate(hidden_sizes): layers.append(nn.Linear(prev_size, hidden_size)) layers.append(nn.BatchNorm1d(hidden_size)) # 批归一化加速收敛 layers.append(nn.ReLU()) # 激活函数 layers.append(nn.Dropout(dropout_rate)) # Dropout防止过拟合 prev_size hidden_size # 输出层 layers.append(nn.Linear(prev_size, output_size)) # 将层序列包装成 Sequential self.network nn.Sequential(*layers) def forward(self, x): # x 的形状: (batch_size, input_size) # 如果输入是三维的如来自滑动窗口需要先展平 if x.dim() 2: x x.view(x.size(0), -1) # 展平为 (batch_size, input_size) return self.network(x)关键参数与设计选择hidden_sizes: 例如[128, 64, 32]代表三个隐藏层神经元数依次递减。这是一个经验性的设计通常从大到小像漏斗一样逐步提炼信息。可以从一个适中的规模如[64]开始根据验证集效果增加深度或宽度。BatchNorm1d: 批归一化层。它通过对每个批次的数据进行归一化使得网络每一层的输入分布相对稳定极大地加速了训练过程并允许使用更高的学习率。这是现代深度网络几乎必备的组件。Dropout: 在训练时随机“关闭”一部分神经元是一种简单有效的正则化手段能减少神经元之间的复杂共适应关系防止过拟合。dropout_rate通常设置在0.2到0.5之间。激活函数ReLU及其变种如LeakyReLU是目前最常用的因为它们能缓解梯度消失问题计算简单。输出层的激活函数取决于任务回归任务通常不用激活函数或使用线性激活二分类用Sigmoid多分类用Softmax通常与nn.CrossEntropyLoss结合其内部已包含Softmax。4.2 一维卷积神经网络模型实现对于时间序列我们使用一维卷积nn.Conv1d。class TimeSeriesCNN(nn.Module): 用于时间序列预测的一维CNN。 输入形状: (batch_size, num_channels, sequence_length) 输出形状: (batch_size, output_size) def __init__(self, input_channels, seq_length, output_size, conv_channels[32, 64], kernel_sizes[3, 3], fc_sizes[128], dropout_rate0.2): super(TimeSeriesCNN, self).__init__() self.conv_layers nn.ModuleList() prev_channels input_channels # 构建卷积层块 for i, (out_channels, kernel_size) in enumerate(zip(conv_channels, kernel_sizes)): conv_block nn.Sequential( nn.Conv1d(in_channelsprev_channels, out_channelsout_channels, kernel_sizekernel_size, paddingsame), # paddingsame保持长度不变 nn.BatchNorm1d(out_channels), nn.ReLU(), nn.Dropout(dropout_rate), nn.MaxPool1d(kernel_size2, stride2) # 池化层下采样 ) self.conv_layers.append(conv_block) prev_channels out_channels # 计算经过池化后的序列长度 seq_length seq_length // 2 # 展平后的特征维度 self.flattened_size prev_channels * seq_length # 全连接层相当于BP网络的后半部分 self.fc_layers nn.ModuleList() prev_size self.flattened_size for fc_size in fc_sizes: self.fc_layers.append(nn.Sequential( nn.Linear(prev_size, fc_size), nn.BatchNorm1d(fc_size), nn.ReLU(), nn.Dropout(dropout_rate) )) prev_size fc_size # 输出层 self.output_layer nn.Linear(prev_size, output_size) def forward(self, x): # x 形状: (batch, channels, length) for conv_block in self.conv_layers: x conv_block(x) # 展平 x x.view(x.size(0), -1) for fc_block in self.fc_layers: x fc_block(x) x self.output_layer(x) return x关键参数与设计选择input_channels: 对应特征数量。例如温度、湿度、气压三个特征此处为3。conv_channels和kernel_sizes: 定义了卷积层的深度和卷积核大小。[32, 64]和[3,3]意味着两个卷积层分别有32和64个滤波器卷积核宽度均为3。较小的核如3或5适合捕捉短时局部模式。paddingsame: 这是PyTorch 1.9引入的便捷参数自动计算填充以保证输入输出长度一致。在早期版本中需要手动计算padding(kernel_size-1)//2。MaxPool1d: 最大池化层进行下采样逐步减少序列长度扩大感受野同时提供一定的平移不变性。stride2和kernel_size2是最常见的配置每次将长度减半。卷积层后的全连接层卷积层提取的是局部特征我们需要通过全连接层将这些特征组合起来进行最终的回归或分类决策。4.3 模型初始化与检查定义好模型后务必进行初始化并检查输入输出形状是否匹配。# 假设输入数据形状 batch_size 32 # 对于BP网络假设特征已展平 bp_input_size 24 * 5 # 窗口长度24 * 特征数5 bp_model BPNet(input_sizebp_input_size, hidden_sizes[128, 64], output_size1) # 回归任务输出1 # 对于CNN cnn_model TimeSeriesCNN(input_channels5, seq_length24, output_size1, conv_channels[32, 64]) # 创建随机输入数据检查形状 bp_dummy_input torch.randn(batch_size, 24, 5) # 模拟一个批次的数据 cnn_dummy_input torch.randn(batch_size, 5, 24) # 注意通道维在前 print(BP网络输入形状:, bp_dummy_input.shape) bp_output bp_model(bp_dummy_input) print(BP网络输出形状:, bp_output.shape) print(\nCNN输入形状:, cnn_dummy_input.shape) cnn_output cnn_model(cnn_dummy_input) print(CNN输出形状:, cnn_output.shape)这个步骤能提前发现网络结构设计中的维度错误避免在训练时出现令人困惑的报错。注意事项在比赛时间有限的情况下不要一开始就设计非常深的网络。从一个简单的3-5层网络开始快速验证数据流和训练循环的正确性。模型复杂度应该与数据量和任务难度相匹配小数据上过深的网络极易过拟合。5. 模型训练、验证与调优全流程模型定义好后接下来就是训练的核心循环。这部分代码的健壮性和效率直接影响实验迭代速度。5.1 数据加载与训练循环搭建PyTorch的DataLoader是管理批数据、打乱、并行加载的利器。from torch.utils.data import DataLoader, TensorDataset # 将numpy数组转换为PyTorch张量 train_X_tensor torch.FloatTensor(train_X_scaled) train_y_tensor torch.FloatTensor(train_y_scaled).view(-1, 1) # 确保标签是二维 (n_samples, 1) val_X_tensor torch.FloatTensor(val_X_scaled) val_y_tensor torch.FloatTensor(val_y_scaled).view(-1, 1) # 创建Dataset和DataLoader train_dataset TensorDataset(train_X_tensor, train_y_tensor) val_dataset TensorDataset(val_X_tensor, val_y_tensor) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) # 训练集需要打乱 val_loader DataLoader(val_dataset, batch_size64, shuffleFalse) # 验证集不需要打乱 # 定义设备 device torch.device(cuda if torch.cuda.is_available() else cpu) print(fUsing device: {device}) model BPNet(...).to(device) # 或 cnn_model.to(device) # 定义损失函数和优化器 criterion nn.MSELoss() # 回归任务用均方误差损失 # criterion nn.CrossEntropyLoss() # 分类任务用交叉熵损失 optimizer torch.optim.Adam(model.parameters(), lr0.001) # Adam是默认的首选优化器 scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, modemin, factor0.5, patience10) # 学习率调度器训练循环模板num_epochs 200 train_losses, val_losses [], [] for epoch in range(num_epochs): # 训练阶段 model.train() running_train_loss 0.0 for batch_X, batch_y in train_loader: batch_X, batch_y batch_X.to(device), batch_y.to(device) optimizer.zero_grad() # 清零梯度 outputs model(batch_X) # 前向传播 loss criterion(outputs, batch_y) # 计算损失 loss.backward() # 反向传播 optimizer.step() # 更新参数 running_train_loss loss.item() * batch_X.size(0) epoch_train_loss running_train_loss / len(train_loader.dataset) train_losses.append(epoch_train_loss) # 验证阶段 model.eval() running_val_loss 0.0 with torch.no_grad(): # 禁用梯度计算节省内存和计算 for batch_X, batch_y in val_loader: batch_X, batch_y batch_X.to(device), batch_y.to(device) outputs model(batch_X) loss criterion(outputs, batch_y) running_val_loss loss.item() * batch_X.size(0) epoch_val_loss running_val_loss / len(val_loader.dataset) val_losses.append(epoch_val_loss) scheduler.step(epoch_val_loss) # 根据验证损失调整学习率 # 打印日志 if (epoch 1) % 20 0: print(fEpoch [{epoch1}/{num_epochs}], Train Loss: {epoch_train_loss:.4f}, Val Loss: {epoch_val_loss:.4f}, LR: {optimizer.param_groups[0][lr]:.6f})5.2 核心超参数调优策略在有限的时间内系统性地调参至关重要。我通常会遵循以下优先级学习率Learning Rate, LR这是最重要的参数。可以从0.001Adam的默认值开始。如果训练损失不下降尝试增大如0.01如果训练过程震荡剧烈或损失变成NaN尝试减小如0.0001。使用ReduceLROnPlateau调度器能自动在验证损失停滞时降低LR非常实用。批大小Batch Size在GPU内存允许范围内较大的批大小如64, 128能使梯度估计更稳定可能有助于收敛。但有时小批量如16, 32能带来更好的泛化性能。可以尝试调整。网络结构深度/宽度如果模型欠拟合训练集和验证集损失都高可以尝试增加层数深度或每层神经元数宽度。Dropout率如果模型过拟合训练损失低验证损失高可以适当增加dropout_rate如从0.2调到0.5。优化器Adam在大多数情况下表现良好且无需太多调参。如果效果不佳可以尝试AdamW修正了权重衰减或传统的SGD with momentum后者有时能找到更尖锐的最小值但需要仔细调整LR和动量参数。权重初始化PyTorch默认的初始化通常工作良好。对于深层网络可以尝试nn.init.kaiming_normal_针对ReLU激活函数优化。高效的调参方法手动依次调整效率低。如果条件允许可以使用网格搜索Grid Search或随机搜索Random Search对少数几个关键参数如LR、隐藏层大小、dropout率进行组合实验。更高级的可以使用Optuna或Ray Tune等自动调参库。但在数模比赛中手动基于经验的迭代通常更可控。5.3 训练监控与早停为了防止过拟合和节省时间早停Early Stopping是必备技巧。best_val_loss float(inf) patience 30 # 容忍验证损失不下降的轮数 patience_counter 0 best_model_state None for epoch in range(num_epochs): # ... 训练和验证代码 ... # 在每个epoch验证后 if epoch_val_loss best_val_loss: best_val_loss epoch_val_loss best_model_state model.state_dict().copy() # 深拷贝保存最佳状态 patience_counter 0 # 可以在这里保存模型 checkpoint # torch.save({model_state_dict: model.state_dict(), ...}, best_model.pth) else: patience_counter 1 if patience_counter patience: print(fEarly stopping triggered at epoch {epoch1}) break # 训练结束后加载最佳模型 model.load_state_dict(best_model_state)同时绘制训练和验证损失曲线直观判断模型状态。plt.figure(figsize(10, 5)) plt.plot(train_losses, labelTraining Loss) plt.plot(val_losses, labelValidation Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.legend() plt.title(Training and Validation Loss Curve) plt.grid(True) plt.show()理想的曲线是两条线都平稳下降并最终接近。如果训练损失持续下降而验证损失上升就是典型的过拟合。实操心得在比赛环境中我通常会为BP和CNN模型各准备一个基础的训练脚本并开启TensorBoard或简单的日志记录实时监控损失。第一个能成功跑通并开始下降的模型哪怕很简单也能给你巨大的信心。不要追求第一个epoch就完美先让它“动起来”。6. 结果分析、模型集成与论文写作要点模型训练完成后工作只完成了一半。如何科学地评估模型并将结果有效地呈现在论文中是取得好成绩的关键。6.1 模型评估与反标准化训练时我们对数据进行了标准化预测结果也是标准化后的值。为了计算有物理意义的误差指标需要进行反标准化。# 预测验证集或测试集 model.eval() all_predictions [] all_targets [] with torch.no_grad(): for batch_X, batch_y in val_loader: batch_X batch_X.to(device) outputs model(batch_X) all_predictions.append(outputs.cpu().numpy()) all_targets.append(batch_y.cpu().numpy()) predictions_scaled np.vstack(all_predictions) targets_scaled np.vstack(all_targets) # 反标准化 predictions scaler_y.inverse_transform(predictions_scaled) targets scaler_y.inverse_transform(targets_scaled) # 计算评估指标 from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score mae mean_absolute_error(targets, predictions) rmse np.sqrt(mean_squared_error(targets, predictions)) r2 r2_score(targets, predictions) print(fMAE: {mae:.2f}) print(fRMSE: {rmse:.2f}) print(fR² Score: {r2:.4f})MAE平均绝对误差直观与目标值单位一致。RMSE均方根误差对大的误差惩罚更重更常用。R²决定系数表示模型对数据波动的解释能力越接近1越好。绘制预测值与真实值的对比散点图或时序对比图能直观展示模型性能。plt.figure(figsize(10, 6)) plt.scatter(targets, predictions, alpha0.5) plt.plot([targets.min(), targets.max()], [targets.min(), targets.max()], r--, lw2) # 对角线 plt.xlabel(True Values) plt.ylabel(Predictions) plt.title(True vs Predicted Values) plt.grid(True) plt.show()6.2 模型集成策略单一模型可能不稳定或存在偏差。集成学习能有效提升模型的鲁棒性和预测精度。在比赛中时间允许的话可以尝试简单平均法训练多个同构但不同随机种子初始化的模型如5个BP网络5个CNN对它们的预测结果取平均。加权平均法根据每个模型在验证集上的表现如RMSE的倒数分配权重性能好的模型权重高。Stacking将多个基模型BP CNN的预测结果作为新的特征训练一个元模型如线性回归或简单的神经网络进行最终预测。这种方法潜力最大但也更复杂容易过拟合需要谨慎使用。示例代码简单平均集成def ensemble_predict(models, data_loader, device): 多个模型预测并取平均 all_model_preds [] for model in models: model.eval() predictions [] with torch.no_grad(): for batch_X, _ in data_loader: # 注意这里不需要标签 batch_X batch_X.to(device) outputs model(batch_X) predictions.append(outputs.cpu().numpy()) preds np.vstack(predictions) all_model_preds.append(preds) # 沿第一个轴模型轴取平均 ensemble_pred np.mean(np.array(all_model_preds), axis0) return ensemble_pred6.3 论文写作与可复现性数学建模论文的核心是清晰地将你的工作传达给评委。模型部分不要只贴代码。要用流程图如数据预处理流程、网络结构图和公式如损失函数、卷积操作公式清晰地描述你的BP网络和CNN模型。说明每层的作用、参数数量、激活函数选择理由。结果部分用表格对比不同模型BP, CNN, 集成模型在验证集和测试集上的评估指标MAE, RMSE, R²。用图表展示预测曲线与真实曲线的对比。对误差进行分析模型在哪些样本或时间段预测误差较大可能的原因是什么如节假日突变、数据噪声。可复现性在附录或提供的代码中必须注明所有关键超参数学习率、批大小、网络结构、随机种子等。使用requirements.txt文件列出所有依赖库及其版本。良好的代码注释和结构也是加分项。创新与总结强调你将CNN创造性应用于时间序列分析的思路对比传统BP网络的优势如更少的参数、更好的局部特征提取能力。总结模型的优缺点并提出可能的改进方向如引入注意力机制、使用更复杂的循环神经网络如LSTM/GRU进行对比。最后的心得研究生数模比赛不仅是技术的比拼更是项目管理、团队协作和快速学习能力的考验。在有限的时间里先完成后完美。搭建一个端到端、可运行的基础管道数据读取-预处理-模型-训练-评估比一开始就追求一个复杂完美的模型更重要。在此基础上通过迭代改进数据、模型和超参逐步提升分数。保持代码模块化方便快速替换和实验。最后相信你的模型但更要相信你对问题的分析和理解。祝你在比赛中取得佳绩
返回列表