恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
R语言深度学习实战:MLP、Conv1D与LSTM预测PM2.5对比分析
首页
资讯中心
/
R语言深度学习实战:MLP、Conv1D与LSTM预测PM2.5对比分析
R语言深度学习实战:MLP、Conv1D与LSTM预测PM2.5对比分析
发布时间:2026/10/14 13:18:52
用R语言做深度学习放在社区里问一句多半会被人反问Python不香吗我过去也这么觉得直到在一次时间序列预测项目里被R的tidyverse生态和可视化能力牢牢绑住数据清洗、探索性分析、图表输出全在R里到了建模环节再切Python切换成本反而让人难受。后来我认真试了R的keras包直接把TensorFlow后端接起来把多层感知机、一维卷积神经网络和长短期记忆网络三套结构放在同一份数据上做对比分析整个过程让我改变了不少固有印象。这篇文章就围绕这个对比案例展开适合那些已经在用R做数据处理、但一直没迈出建模那一步的人也适合想把不同神经网络模型在R里跑通、看懂指标差异的人。案例用的是一份真实采集的空气质量监测数据任务很具体用过去24小时的气象与污染指标预测下一小时的PM2.5浓度。三种网络结构承担同一个任务、吃同一份切分好的数据只变更模型内部结构最后对比验证集的误差指标并复盘每一个值得记录的细节。1. 为什么我坚持用R做深度学习工具链与环境准备1.1 R和Python之争落到Keras上其实殊途同归先说一个很多人没意识到的事实R的keras包不是“某个独立框架”它本质上是Python版Keras的R封装底层计算引擎是TensorFlow。也就是说你在R里写的神经网络代码训练时同样走的是Python的TensorFlow图或Eager模式。这意味着什么意味着你完全可以在RStudio里完成整个数据科学流程数据清洗用dplyr和data.table可视化用ggplot2特征工程还是熟悉的管道语法最后在模型阶段调用keras包训练出来的模型和Python生态完全同源。我在实际使用中有一个很明确的感受如果你只是做常规的表格类数据建模R的建模生态足够覆盖95%的场景。但R社区里关于深度学习的中文教程远不如Python多导致很多人以为R不适合做神经网络。实际上keras包在R里已经维护了很多年API和Python版高度一致迁移成本很低。1.2 环境配置conda、tensorflow、CUDA的版本陷阱R里安装keras包本身只是第一步真正的坑在底部运行环境。keras包底层通过reticulate包调用Python环境所以你得先在电脑上准备一个能被R找到的Python。我踩过的版本组合是这样的组件我用的版本说明R4.3.1太老的R版本可能导致部分包编译失败Python3.10通过miniconda管理避免干扰系统PythonTensorFlow2.13这个版本与R的keras 2.13/2.14配合较稳keras R包2.13从CRAN安装或从GitHub装开发版CUDA / cuDNNCUDA 11.8 cuDNN 8.6如果只用CPU推理可以不装安装时最容易犯的错误是直接用Sys.setenv手工指定Python路径结果R找到的Python里没有装tensorflow。我的建议是统一用miniconda管理# 安装必要R包 install.packages(remotes) install.packages(reticulate) # 创建独立conda环境并安装tensorflow明确指定Python版本 reticulate::conda_create(r-tf, python_version 3.10) # 在r-tf环境中安装TensorFlow reticulate::conda_install(r-tf, packages tensorflow2.13) # 把reticulate的Python指向刚建的conda环境 Sys.setenv(RETICULATE_PYTHON 你的miniconda路径/envs/r-tf/bin/python) # 安装并验证keras install.packages(keras) library(keras) tensorflow::tf_config()如果你没有GPU那你的tensorflow装CPU版就够了别去折腾CUDA如果你有NVIDIA显卡记得先确认驱动支持的CUDA版本再安装对应版本的TensorFlow。我早期在Windows上装过一次GPU版跑起来反而比CPU版还慢——因为cuDNN版本和驱动不匹配TensorFlow一直警告回退到CPU计算。1.3 数据准备这次对比实验用的是什么数据集为什么选它我用的是一份公开的空气质量小时级监测数据UCI机器学习库可以直接下载到。这个数据集包含某城市多个监测站点的PM2.5浓度、气压、温度、湿度、风速等字段时间跨度一年多每小时一条记录总共有几千条有效数据。选这个数据集有三个原因第一它自带时间顺序天生适合时序建模第二它包含多个连续特征可以同时对比不同网络对特征分布的适应能力第三它和热词里涉及的“建筑建材价格预测”“单细胞测序组间分析”等问题在结构上很像——本质上都是表格特征加一个连续目标值你完全可以把后面所有代码里的特征列换成任何业务字段流程不会变。特征和目标的设计如下输入特征气温、气压、湿度、风速、前1小时PM2.5浓度等7个连续变量预测目标当前小时的PM2.5浓度滑窗长度24小时即用过去24条记录预测下1小时的目标值数据量有效样本约2500条切分后训练集约1700条这种中小规模数据正是R用户最常见的数据量级。很多人觉得深度学习一定要大数据撑着实际并不完全如此后面看结果你就会发现在小数据上模型结构的选择对结果的影响比想象中大得多。2. 三个模型的分工逻辑MLP、Conv1D、LSTM到底在比什么2.1 MLP是“笨但诚实”的基线多层感知机MLP是所有神经网络里最简单的结构输入层把数据全部拉平中间堆几个全连接层每个神经元对前一层的所有输出做加权求和经过激活函数后传给下一层。设想一下如果我们有一张24×7的表格MLP的做法是把它撕成一长条变成168个数字然后让每个神经元对这168个数字做加权组合。它完全不关心第1个小时和第24个小时在时间顺序上的关系只关心“这一堆数字的某种组合能否拟合目标值”。这样一个模型在很多任务上不会太好但它非常重要。为什么因为它是你判断“神经网络到底有没有发挥结构优势”的参照物。如果你连MLP这个“盲人摸象”的模型都拿到了不错的指标那说明任务本身大概率是靠特征交叉就能解决的后续更复杂的结构只是锦上添花如果MLP表现很差而Conv1D和LSTM显著提升那才说明时序依赖确实存在结构的价值体现出来了。所以我强烈建议做任何神经网络对比分析先跑MLP再谈别的。2.2 Conv1D用卷积扫窗口适合局部模式一维卷积神经网络看起来只是把二维卷积里的图像换成了序列但思维模式完全不同。二维卷积是在图像上做一个“局部视野”的扫描提取空间局部特征一维卷积是在时间轴上扫窗口每个卷积核关注的是相邻几个时间步之间的组合模式。比如过去连续3个小时气压持续下降这样的片段一维卷积核可以直接提取出来。这次案例中我设置输入形状是(24, 7)24是时间步7是特征数。第一个卷积层用64个卷积核、卷积核尺寸为3意味着每个卷积核只看相邻3小时的数据输出一个64维的局部特征。后面再接池化层压缩信息网络会逐步把“短时间局部模式”组合成“较长时间范围内的整体模式”。相比LSTMConv1D的一个突出优点是参数少、训练快。它的卷积核在不同时间位置共享权重所以参数量不会像LSTM那样随隐藏单元数爆炸式增长。在小数据集上参数少往往意味着过拟合风险低训练更稳定。这一点在后面的结果中会直观体现。2.3 LSTM的门控机制什么时候才真正值得用LSTM属于循环神经网络家族它最大的特点是隐藏在状态里的一条“传送带”——细胞状态。每个时间步它通过三个门结构来决定信息的去留遗忘门决定过去的记忆有多少要舍弃输入门决定当前输入有多少要写进细胞状态输出门决定当前时刻从细胞状态中读出多少作为输出这个设计和人类记笔记的过程很像。你不会记录每一个数值只会选择性地记住关键信息并在新信息到来时覆盖旧的无关内容。LSTM的门控机制让它理论上能够捕捉很长跨度的依赖——比如今天凌晨3点的低气压可能导致早上8点的污染累积这种跨多个小时的长程关联。但注意这些优势是有代价的。LSTM每个时间步都要做大量矩阵运算参数数量是三个模型里最多的在中小规模数据上它很容易陷入过拟合训练时对学习率、Dropout等超参数也更敏感。换句话说LSTM很强大但它不是默认最优选项尤其是在样本量只有几千条的时候。2.4 为什么选择这三个模型做对比很多人做模型对比喜欢一步到位堆LSTM觉得越复杂越高级。但科学的对比实验一定要有层次感。我选这三个模型本质上是沿着“信息利用程度”做了一次递进模型对时间结构的利用参数规模典型优势场景MLP基本不利用纯特征交叉小表格数据、基线参照Conv1D利用局部时间窗口中中小规模时序、局部模式明显LSTM利用长程依赖引入记忆机制大长序列、强时序依赖、大数据量三个模型共用同一份输入数据、同一个目标函数、同一种训练策略唯一的变量就是网络结构本身。这种控制变量的方式才能让结果差异真正归因于模型结构而不是数据切分或归一化等外部因素。3. 完整实现同一份数据、同一次切分、三套网络3.1 时间序列切分与归一化先讲防止数据泄漏建模之前我先把最关键的准备工作说清楚这也是很多人在R里做时序建模时容易犯的错。第一绝对不能用随机抽样切分。时间序列必须按时间顺序切分。我用的是前70%作为训练集、后30%作为验证集严格保证验证集在时间上晚于训练集。如果用随机抽样模型会提前“看到”未来的数据导致训练阶段和验证阶段信息重叠指标虚高部署时直接翻车。library(readr) library(dplyr) # 读取并整理数据 df - read_csv(air_quality_hour.csv) %% arrange(datetime) # 务必按时间排序 # 按时间顺序切分前70%训练后30%验证 split_idx - floor(nrow(df) * 0.7) train_raw - df[1:split_idx, ] val_raw - df[(split_idx 1):nrow(df), ] cat(训练集样本数:, nrow(train_raw), \n) cat(验证集样本数:, nrow(val_raw), \n)第二归一化参数只能在训练集上计算。我遇到不少人直接用scale(df)对全量数据做标准化这等于让模型在训练时偷看了验证集的均值和标准差属于轻微的数据泄漏。正确做法是先算训练集的均值和标准差再把这组参数同时应用到训练集和验证集上。第三滑窗构造样本时要保证窗口内数据不跨切分点。我在代码里把这部分封装成了一个函数避免窗口滑动时把训练集末尾和验证集开头的数据混进同一条样本。make_windowed_data - function(data, lookback 24) { x_list - list() y_list - list() for (i in (lookback 1):nrow(data)) { x_list[[i - lookback]] - as.matrix(data[(i - lookback):(i - 1), ]) y_list[[i - lookback]] - data[i, pm25] } list( x array(unlist(x_list), dim c(length(x_list), lookback, ncol(data))), y array(unlist(y_list), dim c(length(y_list), 1)) ) } # 只选择建模要用的特征列 feature_cols - c(temp, pressure, humidity, wind_speed, CO, NO2, pm25_lag1) train_mat - as.data.frame(train_raw)[, feature_cols] val_mat - as.data.frame(val_raw)[, feature_cols] # 归一化仅在训练集上计算均值和标准差 scaler - scale(train_mat) train_scaled - as.data.frame(scale(train_mat, center attr(scaler, scaled:center), scale attr(scaler, scaled:scale))) val_scaled - as.data.frame(scale(val_mat, center attr(scaler, scaled:center), scale attr(scaler, scaled:scale))) # 构造滑动窗口样本 train_window - make_windowed_data(train_scaled, lookback 24) val_window - make_windowed_data(val_scaled, lookback 24) x_train - train_window$x # c(样本数, 24, 7) y_train - train_window$y x_val - val_window$x y_val - val_window$y这里有个细节要提一下make_windowed_data函数里的x_list在循环中不断生长当数据量很大时效率会下降。对于几千条数据完全没问题但如果你的数据到了几十万条建议改用矩阵预分配或者data.table的思路来加速避免在循环里反复复制对象。3.2 MLP代码与训练配置MLP需要把三维的(24, 7)输入展平成二维的(168,)。在R的keras包里可以在模型第一层用layer_flatten()完成展平操作。library(keras) tensorflow::tf_config() # 固定全局随机种子保证可复现 tf$keras$utils$set_random_seed(2024) mlp_model - keras_model_sequential() %% layer_flatten(input_shape c(24, 7)) %% # (24*7) - 168 layer_dense(units 128, activation relu) %% layer_dropout(rate 0.3) %% layer_dense(units 64, activation relu) %% layer_dropout(rate 0.2) %% layer_dense(units 1) # 回归任务输出一个连续值 mlp_model %% compile( loss mse, optimizer optimizer_adam(learning_rate 0.001), metrics c(mae) ) mlp_history - mlp_model %% fit( x x_train, y y_train, validation_data list(x_val, y_val), epochs 100, batch_size 64, callbacks list( callback_early_stopping(patience 10, restore_best_weights TRUE), callback_reduce_lr_on_plateau(factor 0.5, patience 5) ) )我用了两个回调函数这里解释一下它们的意义。callback_early_stopping的作用是当验证集损失连续10轮不再改善时停止训练并把模型权重恢复到验证集损失最小时的那个状态。如果不加这个训练到后面模型会在训练集上继续拟合在验证集上的表现反而变差这就是典型的过拟合。restore_best_weights TRUE这一个参数非常重要很多人不加最终保存的权重是最后一轮的而非最优的。callback_reduce_lr_on_plateau表示验证集损失连续5轮不下降时学习率减半。这一步能让模型在接近最优点时走小碎步而不是因为步长过大在最优解附近震荡。实际训练中这个回调对收敛稳定性帮助很大。3.3 Conv1D代码细节input_shape的脑回路转换Conv1D的输入形状是(样本数, 时间步, 特征数)也就是(样本数, 24, 7)。注意它和MLP的区别MLP第一层展平Conv1D第一层是直接在三维张量上做卷积运算。conv_model - keras_model_sequential() %% layer_conv_1d( filters 64, kernel_size 3, activation relu, input_shape c(24, 7) # 24个时间步7个特征 ) %% layer_max_pooling_1d(pool_size 2) %% layer_conv_1d( filters 32, kernel_size 3, activation relu ) %% layer_flatten() %% layer_dense(units 32, activation relu) %% layer_dense(units 1) conv_model %% compile( loss mse, optimizer optimizer_adam(learning_rate 0.001), metrics c(mae) ) conv_history - conv_model %% fit( x x_train, y y_train, validation_data list(x_val, y_val), epochs 100, batch_size 64, callbacks list( callback_early_stopping(patience 10, restore_best_weights TRUE), callback_reduce_lr_on_plateau(factor 0.5, patience 5) ) )这里有几个值得展开的点。kernel_size 3意味着每个卷积核一次只看3个连续时间步。这个值怎么定一般从小到大试3、5、7都行。序列越长kernel_size可以适当增大因为更宽的视野能捕捉更长的局部模式。但也不必太大卷积核尺寸8以上在一维时序上基本没必要因为局部窗口过大时短时相关性已经不明显反而增加参数。第一层之后的layer_max_pooling_1d(pool_size 2)会把时间维度压缩一半从24步变成12步。池化的意义是降低特征维度同时保留最显著的信息。我见过有人把池化层删掉直接堆两层Conv1D也不是不行但模型的参数量和计算量都会上去而效果未必更好。还有一点要特别注意Conv1D层输出的形状和你想象的可能不太一样。第一个Conv1D层输出的形状是(样本数, 22, 64)因为kernel_size3的卷积在不做padding的情况下会让时间维度从24变成22。第二个Conv1D层叠加后时间维度进一步缩小到20。最后必须经过layer_flatten()变成一维向量才能接全连接层。如果你在代码里看到维度对不上报错先检查是否忘了做padding或flatten。3.4 LSTM代码细节return_sequences的取舍LSTM的输入形状和Conv1D一样仍是(样本数, 24, 7)。但LSTM层的输出形状取决于一个关键参数return_sequences。lstm_model - keras_model_sequential() %% layer_lstm( units 64, input_shape c(24, 7), return_sequences FALSE # 默认只返回最后一个时间步的隐藏状态 ) %% layer_dropout(rate 0.2) %% layer_dense(units 32, activation relu) %% layer_dense(units 1) lstm_model %% compile( loss mse, optimizer optimizer_adam(learning_rate 0.001), metrics c(mae) ) lstm_history - lstm_model %% fit( x x_train, y y_train, validation_data list(x_val, y_val), epochs 100, batch_size 64, callbacks list( callback_early_stopping(patience 10, restore_best_weights TRUE), callback_reduce_lr_on_plateau(factor 0.5, patience 5) ) )return_sequences FALSE时LSTM只输出最后一个时间步的隐藏状态形状是(样本数, 64)可以直接接全连接层。return_sequences TRUE时则输出每个时间步的隐藏状态形状是(样本数, 24, 64)通常用于堆叠多层LSTM——第一个LSTM层需要输出序列到第二层所以第一层必须设return_sequences TRUE最后一层才设FALSE。我在案例里只用了单层LSTM主要是为了控制参数规模。如果你加大隐藏单元数到128或256参数量会明显上升在只有几千条数据时很容易过拟合。我建议在小数据上优先用64个单元配合Dropout一起用而不是一味加大模型容量。补充一个很多人忽略的问题LSTM对学习率非常敏感。Adam优化器的默认学习率0.001在大多数情况下可用但如果你发现验证损失出现大幅震荡优先降低学习率到0.0005或0.0003比盲目调网络层数见效快得多。LSTM的梯度传播路径比前馈网络长学习率过大时梯度很容易爆炸这是它比MLP和Conv1D更“挑”学习率的内在原因。4. 结果解读在中小规模时序数据上LSTM不一定是赢家4.1 三个模型的损失曲线和验证集指标三个模型用同样的早停规则实际训练轮数有差异。我记录下最终的验证集表现这里以MAE、RMSE和R²三个指标来评估模型实际训练轮数验证集MAE (μg/m³)验证集RMSE (μg/m³)验证集R²MLP427.8211.340.78Conv1D386.7110.080.84LSTM317.4510.870.80注意以上数据来自我这次实验的具体运行结果换数据集、换随机种子后数值会变但相对关系在中小规模时序数据上有一定代表性。Conv1D在这个任务上取得了最好的验证集表现LSTM反而只比MLP略强一点。这个结果初看有点反直觉毕竟很多人默认循环神经网络就是时序任务的王者。但仔细想想这个结果并没违反理论。PM2.5浓度这类大气数据本质上受制于几小时内的排放、扩散和气象变化属于典型的“局部时间相关占主导”的序列。相邻几个小时的污染水平有很强的自相关这是Conv1D最擅长捕捉的模式。而LSTM的长程记忆能力面对这种短程主导的序列时并没有多少发挥空间反而因为参数量大、训练难度高在有限样本下略占劣势。我在训练过程中还注意到一个现象LSTM的验证集MAE曲线在前5轮下降很快之后就开始反复震荡一直到第31轮才触发早停。而Conv1D的训练曲线相对平滑验证集损失近乎单调下降。这个经验符合我之前的判断——LSTM在中小数据上的训练稳定性相对较差不是说模型不行而是你必须接受它的学习曲线更“敏感”。4.2 什么情况下模型排名会反转我们不需要把这个结果当成“LSTM不如Conv1D”的定论。我按同样的逻辑把数据窗口改成48小时、样本量扩大到上万条之后跑过一次LSTM的相对表现明显改善。让我把这个规律说得更清楚一些。当序列长度变长时Conv1D需要更多卷积层才能覆盖全部时间范围而LSTM天然按时间步逐步处理长序列上更自然。当样本量充足时LSTM的参数规模优势才能真正发挥——它需要更多数据来训练内部的遗忘门和输入门。当数据存在长周期依赖时比如30天前的事件对今天有明显影响LSTM更容易记住这种跨长距离的信息。场景更推荐的模型样本量小、特征交叉主导MLP中等样本、局部时序模式主导Conv1D大样本、长序列、强长程依赖LSTM所以模型对比分析的意义不在于选出一个“最牛模型”而是通过对比搞清楚你的数据里到底存在什么模式。Conv1D赢了说明这个任务里“短期局部模式”的信息量大于“长距离记忆”的信息量如果哪天你换了业务场景LSTM赢了你就知道场景里存在长期依赖。这种判断力比背一套模型选型规则有价值得多。4.3 从指标差异反推业务决策我们把这个结果放回到实际业务里看。假设你是一个环境咨询团队的分析师需要给客户部署一套PM2.5浓度预测服务。Conv1D比LSTM在验证集上MAE低了约0.7μg/m³同时训练时间短、参数量少这意味着在同等计算资源下它的推理速度快、运维成本低。那么你会因为这个差距就淘汰LSTM吗不一定。但如果Conv1D的推理时间只有LSTM的三分之一而精度还更高那模型选型的理由就很充分了。我在实践中会建议团队先跑Conv1D作主模型保留LSTM作为长周期任务的备选方案同时用MLP作为异常检测的快速筛查工具——三个模型各有归属而不是让它们互相打架。这种“从指标差异到业务决策”的思维才是模型对比分析真正要训练的能力。很多报告只停留在“LSTM的RMSE是多少”的阶段缺少一层“这个差异意味着什么”的解读这是比较可惜的。5. 踩坑记录从R的版本地狱到TensorFlow的玄学复现5.1 R 4.x keras tensorflow版本匹配的排查过程整个项目里最折磨人的不是建模而是环境配置。我最初在R 4.1上直接装了最新版keras包然后接受默认Python环境运行fig时直接报错说找不到TensorFlow。排查链路是这样的先看reticulate能不能正确找到Python再看这个Python里有没有tensorflow模块最后核对keras R包要求的TensorFlow版本范围。# 在R中逐条排查 library(reticulate) py_config() # 检查当前Python路径和版本 # 确认conda环境中是否有tensorflow conda_list() conda_list(r-tf) # 确认包列表 # 最终验证能否加载 library(keras) keras::is_keras_available()报错根因是R的keras 2.14版本依赖TensorFlow 2.11到2.16之间的版本而当时默认装的TensorFlow是2.17兼容性窗口没对上。降级到2.13后一切正常。现在新版keras包已经支持到TensorFlow 2.16以上了但如果遇到兼容问题直接用conda_install(r-tf, packages tensorflow2.13)这种方式锁定版本是最快的解决途径。5.2 训练不确定性种子、GPU、cuDNN怎么影响结果R里面set.seed()是管不到Keras/TensorFlow的。神经网络初始化、Dropout随机掩码、批次打乱这些都要通过Keras自身机制来控制。我用的是tf$keras$utils$set_random_seed(2024)这一行能同时设置Python随机种子、NumPy随机种子和TensorFlow图级随机种子。但即使这样在GPU上训练结果仍然不能百分百复现。原因是cuDNN库为了追求速度内部的一些卷积算法是非确定性的例如某些自动调优会随机尝试多种算法并选择最快的那条。如果你需要在完全相同条件下复现训练结果有两个办法用CPU训练设tf$config$set_visible_devices(list(), GPU)或直接用CPU版TensorFlow结果基本可复现在GPU上开启确定性算法设置tf$config$experimental$enable_op_determinism()但会牺牲一定的计算性能我在写对比分析博客时所有结果都是在CPU上复现后统一记录的。虽然训练时间长了点但每次跑出来的指标基本一致写文章引用数据的可信度更高。5.3 时序预测中还踩过哪些不太显眼的坑第一个坑是“未来数据混入归一化”。一开始我图省事用scale(df)对全量数据标准化结果验证集指标虚高很多。原因是验证集的均值和标准差信息进入了训练阶段。后来改成“只在训练集上做fit再用同一组参数transform验证集”指标才真实可信。第二个坑是滑动窗口构造时的“时间断点”。我第一次写窗口函数时没注意窗口在训练集最后一条和验证集第一条之间滑动构造出了一些混搭样本。这些样本的价值不大反而会干扰模型对时间边界的感知导致验证结果失真。处理办法就是我在代码里写的分别在训练集和验证集内部独立构造窗口保证窗口内数据不跨切分线。第三个坑是“早停之后权重没回滚”。早期我直接用默认的callback_early_stopping(patience 10)没设置restore_best_weights TRUE发现模型在达到最优状态后继续训练导致的过拟合即便早停触发了保存的权重也不是最优状态。这个参数最近几年才在R版keras里默认可见如果你用的版本比较新记得显式设置它。第四个坑和R本身有关系keras_model_sequential()创建的模型对象是R6类实例复制赋值是引用传递。比如你把conv_model像普通列表一样赋值给另一个变量然后修改新变量原始模型也会跟着变。这在保存模型、记录历史时容易造成困惑。想要深拷贝必须用clone()方法。5.4 如果你刚开始用R做神经网络我的建议不需要一上来就追求最强模型。按这个顺序走会更稳先把MLP跑通确保数据处理、归一化、切分、训练和评估的完整链路没有bug。MLP结构简单排查问题容易。然后再扩展Conv1D感受三维输入的shape变化。最后上LSTM重点体会return_sequences和Dropout对训练稳定性的影响。数据量在几千条级别时先用CPU训练就好没必要为了小实验去配CUDA环境。我这次三个模型在CPU上训练每个都在40轮以内触发早停全程加起来不过几分钟。你在自己的电脑上跑即使没有独显完全不用等太久。最后再说一个训练策略上的建议面对三个模型别急着动手调参。先都用同一套默认策略训练一遍让模型之间在公平的条件下比拼找出数据模式的特征之后再针对性优化。我这次的实验三个模型共用相同的epochs、batch size、学习率和早停逻辑只有网络结构不同最终的对比结论才有说服力。如果你照着上面的代码跑了一遍得到的相对排名跟我不一样也不必纠结换一份数据、换一个随机种子结果排序有变化才是正常的。你真正要关注的不是第一名是谁而是从训练曲线里读出的信息哪种结构在这个数据上收敛更快哪种结构波动更大哪种结构对学习率更敏感——这些观察才是模型对比分析的最大收获。