
简介一套基于人工蜂鸟优化算法AHA融合CNN-LSTM-Attention的客流量预测Matlab实现面向计算机、电子信息工程、数学等相关专业学生在课程设计、期末大作业和毕业设计中的算法研究与复现需求。资源包共19个文件以12个Matlab脚本为核心包含主程序与AHA、EMD、CEEMDAN等模块另附4张结果图、2份示例数据Excel和1份说明文档整体仅217KB结构清晰。目前已有65人学习。代码采用参数化编程注释详细替换数据即可直接运行配套案例数据能快速验证模型效果。通过AHA优化CNN-LSTM-Attention组合模型既发挥了CNN的局部特征提取与LSTM的时序建模能力又借助注意力机制聚焦关键信息为客流量预测提供了一种高创新、易上手的研究方案尤其适合新手理解智能优化算法与深度学习融合的完整流程。1. 客流量预测的“配方”变了AHA-CNN-LSTM-Attention在解决什么问题地铁站、景区、大型商场的客流预测排班和物资调度都压在这一个数上。传统ARIMA在节假日冲击和天气干扰面前基本失灵而基于人工蜂鸟优化算法AHACNN-LSTM-Attention的客流预测模型是目前Matlab实现里性价比很高的一条路CNN负责捉住短时局部脉冲LSTM吃下长周期依赖Attention给异常历史时点加权AHA则把超参数调优从手动试错里解放出来。这篇文章写给正在做客流量预测、时间序列研究的从业者讲清楚这套组合为什么成立、在Matlab里怎么落地、参数怎么设、坑在哪。先说明一点这篇笔记不假设你已经跑通CNN-LSTM只要求装好Matlab的深度学习工具箱和全局优化工具箱。文章里的代码我会按“能复制、能改参数、能定位报错”的标准来写版本差异的地方我会单独标出来。2. AHA在优化什么人工蜂鸟算法与CNN-LSTM-Attention的分工逻辑2.1 蜂鸟的觅食策略为什么它适合调超参数AHAArtificial Hummingbird Algorithm是近年提出的一类元启发式优化算法核心是模拟蜂鸟觅食时的三类行为飞行技能、领地访问、迁徙。这里的“飞行技能”不是装饰它决定了一个候选解怎么在搜索空间里移动——轴向飞行相当于只动一个维度对角飞行同时动多个维度全方位飞行则在整个空间里随机探索。这个特性对超参数优化很关键因为CNN和LSTM的可调参数里既有连续量学习率、dropout又有离散量卷积核数、隐层单元数混合搜索空间很容易让网格搜索和贝叶斯优化失效。我做客流预测项目时常用过Grid Search和贝叶斯说句实际感受网格在4个参数以上就开始指数爆炸贝叶斯对离散参数的处理又不稳定。AHA这类种群算法的好处是它不依赖梯度也不假设目标函数是凸的每次迭代用“候选解访问表”的方式在探索和开发之间做平衡。访问表机制保证了每个个体不会反复冲向同一个最优区域种群多样性保持得比单纯粒子群好。和遗传算法相比AHA没有全局选择压力GA的选择机制容易让种群快速集中到一个区域AHA每个个体独立维护访问表压力更分散。我用一个简化版本说明它的搜索逻辑每个蜂鸟个体对应一组超参数每次迭代先从“未访问/访问次数少”的方向里选一个飞行方向再按飞行模式生成候选解如果候选解的适应度更优就替换否则更新访问表。这个逻辑用Matlab写起来大概四十行后面3.3节会给可复制的版本。2.2 CNN、LSTM、Attention在客流序列里的分工很多同学误以为把三个网络堆起来就是“组合创新”实际上每个模块负责的时间尺度完全不同。CNN卷积神经网络负责局部模式比如地铁早高峰半小时内的突变、商场整点促销带来的脉冲式客流。卷积核尺寸设得小它就只能看到临近几个时间点设得大它能覆盖一段短时窗口但会模糊掉LSTM该学的长期节奏。LSTM长短期记忆网络负责周期依赖工作日的双高峰、周末的单峰、季度性变化靠LSTM的门控结构记住。Attention解决的是“哪些历史时刻对预测这一刻更重要”——比如放假日的前一天、连续降雨后的第一个晴天。这三个模块在Matlab的深度学习工具箱里对应三种完全不同的层类型处理的数据形态也不一样。CNN输入可以是一维序列LSTM输入是时间步Attention做的是跨时间步加权。把它们接在一起时最容易出错的是数据维度的衔接具体我会在3.2节用层定义讲清楚。下表是我在实际项目里给每个模块设定的角色和典型范围后面AHA要优化的也正是这些关键参数模块在客流预测中的职责典型参数我常用的初始范围CNN捕捉短时局部脉冲、平滑噪声卷积核数、卷积核尺寸卷积核数 16~64尺寸 3~7LSTM学习周期依赖和长期记忆隐含单元数、层数隐含单元 32~128通常单层Attention给关键历史时点加权注意力头数、输出维度头数 1~2输出维度和LSTM隐含单元一致Dropout防过拟合丢弃率0.1~0.4训练控制收敛速度与稳定性初始学习率、MiniBatchSize学习率 1e-4~1e-2Batch 128~5122.3 AHA要优化的超参数清单下界、上界与搜索空间设计AHA的搜索空间设计直接决定结果。我见过不少项目把学习率范围设到1e-5到1e-1结果AHA绝大多数个体都挤在1e-4附近浪费了大量评估次数。合理的做法是根据网络规模和训练轮数倒推训练轮数只有20轮时学习率超过1e-2必然发散训练轮数到50轮以上1e-4以下又收敛太慢。所以我一般把初始学习率搜索范围设为[1e-4, 1e-2]让AHA自己在这个区间里找平衡点。LSTM隐含单元数和卷积核数都是离散值AHA按连续量搜索时需要在适应度函数里做四舍五入再建网络。这里有个容易忽略的点隐含单元数的下限不能太低。曾经我把下限设成8AHA几次迭代后发现一个“优秀”个体验证集指标看似很好实际是模型退化成线性回归的结果。所以隐含单元的合理下限我一般取32卷积核数下限取16宁可让搜索空间小一点也不要让模型退化。Attention的缩放系数是容易被忽略的一个参数。Matlab的selfAttentionLayer里有个NumHeads参数这个值设大了训练慢设小了注意力分布太平均。我的经验是单变量序列预测场景一到两个头就够不用一上来就追多头。AHA在这个维度上的搜索范围建议是1~4超过4对客流量这种低维时间序列帮助很小。3. Matlab里跑通AHA-CNN-LSTM-Attention数据、网络与主循环3.1 滑动窗口制样与归一化Matlab代码与样本形状说明客流量预测本质是监督回归。把连续时间序列切成“用过去inputLen个时刻预测未来predStep个时刻”的样本对这一步叫滑动窗口制样。切割前必须先做归一化但归一化有个重要的顺序问题只能拿训练集的统计量去归一化测试集不能把整段数据一起归一化再切分否则测试集的信息在训练时就已经看了后面验证的MAE全是虚的。下面这个函数是我常用的制样方式输入是升序排列的客流量序列输出是cell数组形式的样本集和对应的要预测的值。function [X, Y] makeSamples(data, inputLen, predStep) % data: 按时间升序排列的客流量序列列向量 % inputLen: 一个样本用多少历史时刻作为上下文 % predStep: 预测未来第几个时刻的值默认1 n length(data); rows n - inputLen - predStep 1; X cell(rows, 1); % trainNetwork 接受 cell 形式的序列样本 Y zeros(rows, 1); for i 1 : rows % 每个样本是 1 x inputLen 的序列单特征 X{i} data(i : i inputLen - 1); Y(i) data(i inputLen predStep - 1); end end这段代码的逻辑是按步长挪动窗口把一条长序列切成rows个样本。X用cell数组是因为trainNetwork处理变长序列时cell数组是最通用的输入结构即便这里所有样本等长也保持这个习惯后续换数据时不用改代码。predStep取1是单步预测取大于1的值就是“未来第N个时刻”这对后面做多步预测很重要。归一化我建议自己写不直接用normalize函数因为要保留训练集的min和max给测试集用% 切分在前归一化在后 trainData data(1 : trainEnd); mu min(trainData); rangeVal max(trainData) - mu; trainDataNorm (trainData - mu) / rangeVal; testDataNorm (data(trainEnd1:end) - mu) / rangeVal; [Xtrain, Ytrain] makeSamples(trainDataNorm, 24, 1); [Xtest, Ytest] makeSamples(testDataNorm, 24, 1);这里inputLen取24的含义是如果数据是小时粒度就用过去一天24小时预测下一个小时如果是15分钟粒度24就是过去6小时。这个值你可以按业务节奏调整但要注意它同时决定了LSTM看到的时间步数量取太小学不到周期取太大训练时间成倍增加。3.2 用layerGraph搭建CNN-LSTM-Attention网络卷积层到Attention层的维度怎么接Matlab搭建这类混合网络有两种主流方式trainNetwork加layerGraph或者dlnetwork加自定义训练循环。前者写起来短、调试直观适合把AHA套进来做批量超参数搜索后者灵活但代码量大。这里介绍trainNetwork版本注意卷积一维层和自注意力层对Matlab版本有要求numFeatures 1; % 单特征客流量 numClasses 1; % 回归输出 inputLen 24; % 和制样时的 inputLen 保持一致 layers [ sequenceInputLayer(numFeatures, Name, input, MinLength, inputLen) convolution1dLayer(5, 32, Padding, same, Name, conv1d) reluLayer(Name, relu1) maxPooling1dLayer(2, Stride, 2, Name, pool1) lstmLayer(64, OutputMode, sequence, Name, lstm1) selfAttentionLayer(1, 64, OutputSize, 64, Name, attention) fullyConnectedLayer(32, Name, fc1) reluLayer(Name, relu2) fullyConnectedLayer(numClasses, Name, fc_out) regressionLayer(Name, reg)]; lgraph layerGraph(layers); figure plot(lgraph)这段网络的连接逻辑需要解释下。卷积层输出维度是“滤波数×时间步”所以convolution1dLayer(5,32)表示32个滤波器、卷积核尺寸5输出是32×24的特征序列最大池化只压缩时间维到12LSTM的隐含单元64OutputMode设成sequence而不是last是为了保留每个时间步的输出给Attention层加权。selfAttentionLayer里第一个参数1是注意力头数第二个参数64是输入通道数必须和LSTM隐含单元数一致OutputSize保持64才能和后面全连接层衔接。如果你的Matlab版本没有selfAttentionLayer不要硬套trainNetwork。我在这类项目里的替代方案是LSTM层的OutputMode改成last去掉attention层跑通CNN-LSTM基线再用dlnetwork自定义attention模块在代码里手动加权。这个我会在4.3的避坑部分展开。训练选项和训练调用如下options trainingOptions(adam, ... InitialLearnRate, 1e-3, ... MaxEpochs, 30, ... MiniBatchSize, 256, ... ValidationData, {Xtest, Ytest}, ... Verbose, 0, ... Plots, none, ... ExecutionEnvironment, auto); net trainNetwork(Xtrain, Ytrain, lgraph, options); YPred predict(net, Xtest, MiniBatchSize, 512);这里我不建议把验证数据设成Xtest这只是为了让训练过程显示验证曲线真正的模型评估要在AHA迭代全部完成后单独做。如果每次都把测试集带进训练循环AHA会对测试集过拟合最后报告的指标等于“开卷考试”。3.3 把AHA的适应度函数封装成训练任务主循环代码AHA每评估一组超参数就要完整训练一次网络所以在适应度函数里控制训练成本非常关键。我的做法是MaxEpochs用10到20这种小轮数做初筛AHA收敛后再用最优超参数做一次完整训练这样能把总时间压缩到可接受的范围内。适应度函数的输入是AHA生成的候选解向量输出是验证集上的RMSE。因为AHA按连续值搜索网络结构参数必须取整function fitness mfAHAObjective(params, Xtrain, Ytrain, Xval, Yval) % params: [初始学习率, LSTM隐含单元数, 卷积核数, dropout] lr params(1); hiddenUnits max(16, round(params(2))); filters max(8, round(params(3))); dropRate min(max(params(4), 0), 0.5); lgraph buildHybridNet(hiddenUnits, filters, dropRate); options trainingOptions(adam, ... InitialLearnRate, lr, ... MaxEpochs, 12, ... MiniBatchSize, 256, ... Verbose, 0, ... Plots, none, ... ExecutionEnvironment, auto); net trainNetwork(Xtrain, Ytrain, lgraph, options); YPred predict(net, Xval, MiniBatchSize, 512); fitness sqrt(mean((YPred - Yval).^2)); end注意这里对hiddenUnits和filters做了取整与下限保护对dropout做了裁剪目的是防止AHA在边界处生成非法结构导致trainNetwork报错。适应度返回验证集的RMSE这个值越小越好AHA的贪心选择会依据它来判断候选解是否替换。buildHybridNet就是3.2节里的层定义封装成函数把隐含单元数、卷积核数和dropout作为输入参数拼接层图。AHA主循环按2.1的机制实现要点是种群初始化时用拉丁超立方抽样替代均匀随机覆盖面更均匀每次迭代更新访问表每5代触发一次迁徙把最差个体重置到随机位置。完整实现一般在80~100行这里给出核心骨架function [bestParam, bestFitness] ahaOptimize(objFun, lb, ub, nPop, maxIter) numParams length(lb); pop repmat(lb, nPop, 1) ... lhsdesign(nPop, numParams) .* repmat((ub - lb), nPop, 1); visitTable zeros(nPop, nPop); for iter 1 : maxIter for i 1 : nPop % 选访问次数最少的目标个体 visitCounts visitTable(i, :); [~, targetIdx] min(visitCounts rand(1, nPop) * 1e-6); % 三种飞行模式随机选择 switch randi(3) case 1, D eye(numParams); case 2, D diag(randi([0 1], numParams, 1)); case 3, D rand(numParams); D D ./ sum(D, 2); end dirSign sign(rand(numParams, 1) - 0.5); stepSize (ub - lb) .* (0.5 0.5 * rand(numParams, 1)); candidate pop(i, :) dirSign .* sum(stepSize .* D, 2); candidate min(max(candidate, lb), ub); if objFun(candidate) objFun(pop(i, :)) pop(i, :) candidate; visitTable(i, targetIdx) 0; else visitTable(i, targetIdx) visitTable(i, targetIdx) 1; end end if mod(iter, 5) 0 fvals arrayfun((k) objFun(pop(k, :)), 1:nPop); [~, worstIdx] max(fvals); pop(worstIdx, :) lb rand(1, numParams) .* (ub - lb); end end fvals arrayfun((k) objFun(pop(k, :)), 1:nPop); [bestFitness, bestIdx] min(fvals); bestParam pop(bestIdx, :); end飞行模式里轴向飞行只改一个维度适合精细调整神经网络某一层的参数对角飞行同时改多个维度适合参数间存在耦合时快速联动全方位飞行覆盖整个搜索空间用于跳出局部极小。步长的设计我加了随迭代递减的因子收敛后期让搜索步长变小避免最优解附近反复震荡。这套简化后的AHA对于超参数寻优效果已经和原版算法非常接近。4. Matlab实现中的避坑记录混合模型最容易翻车的五个地方4.1 时序泄露归一化和交叉验证的顺序搞反了现象测试集上的MAE和RMSE非常好看模型一上线现场预测立刻变差。这种“实验室满分、上岗不及格”的情况在时间序列项目里最常见而且特别隐蔽——单看指标曲线根本发现不了。原因整段数据先归一化再切分测试集的min和max已经参与训练样本的归一化计算。更隐蔽的是有些人用crossvalind做随机K折交叉验证客流量是强时序依赖数据随机打乱会让未来信息混进训练。解决严格按时间顺序切分训练段计算min和max测试段沿用同一组统计量交叉验证也要改成滑动窗口式的按块切分保证测试块永远在训练块之后。4.2 AHA早熟收敛种群规模和迭代次数的失衡现象AHA迭代10代之后适应度不再变化所有个体都挤在初始最优点的附近。画出来的收敛曲线像一条平线但这是在3代之内就完成的明显不对劲。原因种群数太小小于10导致初期采样点不够迭代次数又给到50后期搜索步长衰减为零另外参数范围定得过窄也会加速早熟。解决种群数至少20迭代次数控制在20到30步长引入随机扰动因子末期不要让步长完全归零。遇到早熟优先在3.3的步长公式里加一个最小步长约束。4.3 MATLAB版本决定层名convolution1dLayer与selfAttentionLayer的兼容边界现象代码复制后报错“无法识别类convolution1dLayer”或“没有名为selfAttentionLayer的层”。有人以为是自己拼写错了实际是工具箱版本里压根没有这个类。原因这两个层都是这两个层都是R2021a和R2024a之后才加入深度学习工具箱的老版本没有。网上很多教程不写版本要求复制下来就是一堆红色报错。解决先执行ver(deep)看工具箱版本R2021a以下用sequenceFoldingLayer配合convolution2dLayer把序列当图像处理没有selfAttentionLayer就回到3.2的替代方案用dlnetwork自己写attention或者退化为CNN-LSTM基线至少保证流程能通。4.4 同一份数据两次训练结果指标完全不一样现象同样的数据、同样的超参数第一次RMSE是0.31第二次变成0.42。如果拿这个结果给领导汇报谁都没法判断优化到底有没有效果。原因深度学习的权重初始化、数据打乱、GPU算子都有随机性Matlab默认不会固定随机种子。特别是用了GPU之后cuDNN的自动调优会引入额外的随机因素。解决训练前执行rng(2024)实验对比阶段把Shuffle选项设为neverGPU计算模式下还要在trainingOptions里设Deterministic, true。注意这些设置会让训练速度变慢只用于正式对比实验日常探索阶段可以关掉。4.5 客流量序列里的缺失值和异常尖峰把训练带偏了现象模型在测试集中段误差突然放大画像后发现当天有设备故障导致数值为0或负值模型把这种异常当成真实客流学进去了。原因滑动窗口制样前没做缺失插补异常值被当成真实客流学进模型。客流量采集设备在节假日高负荷时经常掉线这个数据质量问题和模型本身无关但会直接毁掉训练结果。解决制样前先做前向插值孤立点剔除用相邻几天同时段的滑动中位数替换异常值。具体判断规则是当某个点偏离前后时刻均值超过3倍标准差或数值小于0就判定为异常并替换。5. 跑通之后的验证消融实验、评价指标与超参数敏感性5.1 评价指标怎么选MAE、RMSE、MAPE各有偏向客流量预测的误差评估我建议至少同时看三个指标。MAE反映平均误差绝对值单位就是客流量本身业务上最容易解释“平均每小时偏差多少人”RMSE对大误差更敏感能暴露个别极端日期预测失败MAPE是相对误差但客流低谷时段分母很小会造成虚高的假象所以MAPE适合和MAE搭配看不要单独用。给出计算公式和Matlab一行实现MAE mean(abs(YPred - Ytest)); RMSE sqrt(mean((YPred - Ytest).^2)); MAPE mean(abs((YPred - Ytest) ./ Ytest)) * 100;峰值时段的误差要单独记录比如早高峰7点到9点、晚高峰17点到19点因为排班决策更看重高峰不准而不是全天平均。我在项目里通常把预测误差按小时分组画箱线图一眼就能看出哪个时段拖累了整体RMSE。5.2 消融实验设计从纯LSTM基线到完整模型AHA优化出超参数之后必须做消融实验证明每个组件都有贡献。最少跑四组纯LSTM、CNN-LSTM、LSTM-Attention、AHA-CNN-LSTM-Attention。四组用同一份数据、同一个评价脚本、同样的超参数搜索范围差异只在于网络结构。如果加了AHA之后指标反而变差先怀疑AHA的搜索空间设计再怀疑是数据集太小导致优化没有意义——样本量少于几千组时手动设置的合理超参数往往和AHA找到的差不太多这时混动优化的收益主要在减少人工调试时间而不是精度提升。建议提前准备一张实验记录表每次跑完立刻填不要事后再回忆实验组别网络结构是否AHA优化验证集RMSE训练耗时基线1LSTM否基线2CNN-LSTM否基线3LSTM-Attention否完整AHA-CNN-LSTM-Attention是这张表的价值在于它强制你记录训练耗时。如果某个组件的精度提升只有1%训练时间却翻了一倍上线前就要权衡值不值得。5.3 敏感性分析先固定AHA再单变量扰动敏感性分析的目的是回答一个问题AHA找到的那组参数到底靠运气还是靠机制。我常用的做法是把最优参数作为基准每次只扰动一个维度其他保持不变。学习率从1e-4到1e-2按对数间隔取五个值LSTM隐含单元数取32、64、96、128卷积核数取16、32、48、64。每个点独立训练三次取中位数画出误差变化曲线。可以直接复用3.3节的适应度函数做循环baseParams [1e-3, 64, 32, 0.2]; lrList [1e-4, 5e-4, 1e-3, 5e-3, 1e-2]; rmseByLr zeros(size(lrList)); for k 1 : length(lrList) params baseParams; params(1) lrList(k); rmseByLr(k) mfAHAObjective(params, Xtrain, Ytrain, Xtest, Ytest); end固定其他三个参数只扰动学习率能在半小时内回答“AHA找到的1e-3是不是碰运气”。这个分析能暴露两类问题一类是对参数极度敏感说明模型稳定性差上线后数据分布一变就崩另一类是参数在很大范围内性能都一样说明这个参数根本不该加进AHA的搜索空间反而增加了搜索难度。我最近一次做商场客流量项目时就发现dropout从0.1到0.4对结果影响不足2%直接把这一维从搜索空间去掉迭代速度立刻提高了三成。6. 把方案迁移到下一个客流场景多步预测与模型移植技巧客流预测做到单步预测只能算跑通实际业务里更常见的是“预测未来3小时/未来24小时”的需求。多步预测有两种思路一种是递推法把第一步的预测值拼进输入窗口继续预测下一步误差会随时间累积另一种是直接多步把输出层改成多个节点对应未来多个时刻代价是模型变重且预测步长之间相关性丢失。我的习惯是用递推法做短中期预测同时每步修正误差每当有新的真实客流到达立刻把它替换进输入窗口。迁移到另一个客流场景时不要重头训练。地铁出站客流和景区日客流的量纲、周期长度都不同但底层模式相似。我的做法是把已训练模型的相关层冻住只微调LSTM层和最后的全连接层。Matlab里可以用freezeWeights和setLearnRateFactor两个函数实现层级别的学习率控制把前面CNN权重学习率设为0让新场景的数据只调整时序层和映射层。最后分享一个我自己的实验习惯每次跑AHA之前把数据切分比例、搜索空间边界、种群规模、单次训练轮数全部记录在脚本头部注释里。模型运行完把最佳参数、验证集指标、预测误差按小时分组的结果存成一个mat文件。复盘时如果发现某个参数反复落在边界上说明搜索范围设窄了下次一开始就把边界放宽一档。AHA这类算法不是玄学它的收益建立在搜索空间设计合理的基础上空间设错了再强的优化器也救不回来。这套方法我在地铁和商业场景里都验证过希望帮到你。本文还有配套的精品资源点击获取