简介基于长短期记忆网络LSTM的城市人口预测MATLAB实现面向需要处理时间序列预测的本科生、研究生及科研人员提供从数据输入、模型训练到误差评估的完整代码与配套人口数据。资源包共三十八个文件、约五百五十三KB以MATLAB脚本、结果图像及数据表格为主包含主程序、误差指标计算与拟合优度R方计算等脚本以及多幅预测结果对比图并附有说明图像便于对照理解LSTM在人口预测中的建模流程与精度验证方法。目前已有五百二十五人学习下载代码注释清晰可直接运行或扩展至其他时序预测场景适用面广。若遇到运行问题或需要创新修改可私信或扫码联系作者协助适合本科及以上人群用于课程设计、毕业设计或算法对比参考。1. 城市人口预测为什么选LSTM不只看数字还要看「惯性」城市人口预测是个典型的时间序列问题。过去我们常用回归、ARIMA这类统计模型它们能抓住线性趋势但对人口变动里那种「过去几年的增长惯性会延续、但又不是简单重复」的非线性特征拟合能力很有限。LSTM长短期记忆网络作为循环神经网络的一种核心优势在于它能从历史序列里自己学出「该记多久、该忘什么」不需要你手工设计滞后阶数。对于城市人口这种受经济、政策、迁移多重因素叠加的数据LSTM往往比传统模型更稳。这篇笔记面向两类人一类是做城市规划、社会统计相关工作的从业者手上有人口历史数据但没用过深度学习方法另一类是正在做课程设计或入门项目的研究者需要一份从数据处理到模型评估都能跑通的完整方案。你会看到我完整走一遍数据清洗、序列化、模型搭建、训练和预测的流程也会看到哪些地方看着简单、实际最容易翻车。读完你至少能独立复现一版可用的LSTM城市人口预测并知道怎么判断结果靠不靠谱。2. 人口数据先处理成什么样子清洗、归一化与滑动窗口2.1 拿到一份人口数据后我做的第一件事不是建模很多人习惯拿到数据直接喂给模型结果发现训练过程像玄学loss死活降不下去。其实问题往往不在模型而在数据形态。城市人口数据最常见的存储格式是CSV每一行是一个年份或月份列包括总人口、出生率、死亡率、迁入迁出等字段。第一步是检查缺失值和异常值这一步不要跳过。import pandas as pd import numpy as np # 读取数据假设文件名为 population.csv df pd.read_csv(population.csv, encodingutf-8) print(df.head()) print(df.info()) # 检查缺失值 print(df.isnull().sum()) # 对数值列做插值填充人口数据一般用线性插值即可 df[total_population] df[total_population].interpolate(methodlinear) # 检查异常值例如人口突然翻倍或减半需要人工核实 df[pct_change] df[total_population].pct_change() outliers df[abs(df[pct_change]) 0.1] print(outliers)这段代码的逻辑先用info()看每列类型和非空数量再用interpolate填充缺失的人口中年份数据最后用环比变化率标出异常年份。对于年度人口数据相邻年份变化超过10%基本可以判定为异常或口径调整需要查统计公报或备注列确认。插值填充适合少量缺失如果连续缺失三年以上建议放弃那一段否则模型会学到一段假序列。2.2 为什么一定要做归一化LSTM对数值尺度极其敏感LSTM内部用的是 sigmoid 和 tanh 激活函数它们的输出范围都在 (-1, 1) 或 (0, 1) 之间。如果输入数据是几十万量级的人口数梯度很容易饱和训练要么极慢要么直接不收敛。我一般用 MinMaxScaler 把所有特征缩放到 0~1 之间训练完再逆变换回真实人口数。from sklearn.preprocessing import MinMaxScaler # 选择要用的特征列这里用总人口 出生率 死亡率作为输入特征 feature_cols [total_population, birth_rate, death_rate] data df[feature_cols].values scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(data)注意fit_transform是对全量数据做的。如果是在真实项目中更规范的做法是先切分出测试集再用训练集的统计量 transform 测试集避免测试集信息泄漏。但对于人口预测这种自回归型任务后面会细说。这里还有一个常见误区如果把测试集一起放进 scaler预测得到的数值会被测试集本身的分布「校正」看起来效果很好实际部署时一换新数据就现原形。这一点在第五章还会提到。2.3 构造滑动窗口LSTM不是随便把一整列数据丢进去LSTM 的输入要求是三维张量(样本数, 时间步长, 特征数)。时间步长lookback window就是你用过去多少年的数据来预测下一年。这个参数直接决定模型能看到的「记忆长度」需要根据数据量来设。def create_sequences(data, lookback): X, y [], [] for i in range(len(data) - lookback): X.append(data[i:i lookback]) y.append(data[i lookback, 0]) # 预测目标是总人口列 return np.array(X), np.array(y) lookback 5 # 用过去5年的数据预测下一年 X, y create_sequences(scaled_data, lookback) print(X.shape, y.shape)这里我把预测目标设为data[i lookback, 0]也就是总人口那一列。输出形状如果是(年份数-5, 5, 3)说明每次输入是一个 5×3 的矩阵5 个时间步、3 个特征。lookback的选择要考虑数据长度如果你总共只有 30 年数据设lookback10会丢掉 10 个样本训练数据直接少三分之一。我建议以总数据量的 15%~20% 为参考30 年数据用 5 年左右比较安全。还要注意窗口长度不是越大越好太长的窗口会把很久之前、已经失效的人口政策影响也塞进去模型反而被噪声干扰。3. 用Keras搭建LSTM城市人口预测模型网络结构怎么定3.1 单层还是多层先从最简结构开始LSTM 模型的结构看着简单但层数、神经元数、dropout 比例都会影响最终效果。我先给一套稳妥的基线结构一个 LSTM 隐藏层 一个全连接输出层。不要一上来就堆三层 LSTM数据量小几十到几百个样本的时候深层网络只会过拟合。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam model Sequential([ LSTM(units50, return_sequencesFalse, input_shape(lookback, feature_dim)), Dropout(0.2), Dense(1) ]) model.compile(optimizerAdam(learning_rate0.001), lossmse) model.summary()这里的units50是 LSTM 隐藏状态维度return_sequencesFalse表示只返回最后一个时间步的输出因为我们的任务是单步预测。Dropout(0.2)在 LSTM 层之后加用来缓解过拟合。Dense(1)输出一个标量作为预测人口数。feature_dim在第 2 章的例子中是 3如果只用了人口序列一个字段那就是 1。学习率 0.001 是 Adam 优化器的常见起点后面可以按 loss 曲线微调。3.2 什么时候需要堆叠 LSTM 层一个判断基准如果你发现单层 LSTM 在训练集上的 loss 都降不下来说明模型容量不够这时候再加一层。叠层时要注意除最后一层 LSTM 外其余层的return_sequencesTrue因为层与层之间需要传递完整的时间步序列。model Sequential([ LSTM(50, return_sequencesTrue, input_shape(lookback, feature_dim)), Dropout(0.2), LSTM(50, return_sequencesFalse), Dropout(0.2), Dense(1) ])判断要不要叠层的一个指标是训练集 loss 与验证集 loss 的差距。如果训练集 loss 已经很低但验证集还在高位说明是过拟合加层解决不了问题应该减神经元数或者加大 dropout。反过来如果两边的 loss 都很高才考虑加层。城市人口数据通常只有几十到几百条我遇到的情况大多是单层够用、双层过拟合。所以不建议盲目模仿别人模型里的复杂结构。3.3 损失函数为什么选 MSE评估口径决定损失选择预测人口数本质上是一个回归问题最直接的损失函数是均方误差MSE。它会放大大误差样本的惩罚——人口预测差 10 万和差 1 万的惩罚比例是 100:1这符合实际需求我们更不希望出现重大偏差。如果你只关心预测值和真实值的相对误差可以考虑 MAPE平均绝对百分比误差但 MSE 作为训练损失更容易稳定收敛。LSTM 的权重初始化、dropout 位置这些细节在实际运行中差异很大。比如 dropout 如果加在 LSTM 层内部通过recurrent_dropout参数会改变 LSTM 的记忆状态更新方式训练速度明显变慢但某些数据集上效果更好。我的习惯是先用Dropout层等验证集效果不理想时再尝试recurrent_dropout。4. 训练与评估怎么判断模型真的学会了「人口惯性」4.1 训练集和测试集的划分方式时序数据不能随机打乱很多人在划分数据集时习惯用train_test_split的默认随机模式这是在时间序列预测里最隐蔽的坑。随机打乱会把未来几年的数据混进训练集模型提前看到了「答案」验证集上表现当然好但换到真实预测就把脸丢光。时序数据必须按顺序切分。train_size int(len(X) * 0.8) X_train, X_test X[:train_size], X[train_size:] y_train, y_test y[:train_size], y[train_size:] print(f训练集样本数: {len(X_train)}, 测试集样本数: {len(X_test)})按时间顺序切分后训练集是前 80% 的年份窗口测试集是最后 20% 的年份。这样测试集模拟的就是「用过去预测未来」的真实场景。要注意切分后测试集样本数可能就剩五六个预测误差的波动会比较大这是数据量小带来的固有限制不是模型出了问题。我一般会在评估时同时看单点误差和整体趋势形状而不是只盯一个指标。4.2 训练过程中要盯的三个信号loss曲线、过拟合、早停训练 LSTM 不像训练线性模型那样跑完就行。你需要在训练过程中观察 loss 曲线的下降趋势判断学习率是否合适、模型是否过拟合。from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping( monitorval_loss, patience20, restore_best_weightsTrue ) history model.fit( X_train, y_train, validation_data(X_test, y_test), epochs300, batch_size16, callbacks[early_stop], verbose1 )patience20表示验证集 loss 连续 20 个 epoch 不下降就停止训练。restore_best_weightsTrue会在停止时回滚到验证集 loss 最低的权重也就是「后悔药」。如果你的训练集 loss 在下降但验证集 loss 一路上升说明过拟合已经发生早停会帮你切在拐点附近。batch_size对于几十条样本的数据16 是一个相对安全的取值如果样本特别少可以直接整批训练。4.3 评估预测效果的反直觉经验看拐点比看数值更重要训练完之后把预测结果逆变换回真实人口量级和真实值对比。不要只算一个 RMSE 就结束一定要画曲线看形状。可以说 99% 的 LSTM 人口预测 Demo 都有同一个问题趋势预测出来了但拐点处总是滞后。人口数据里某个年份可能因为行政区划调整或统计口径变化出现突然的上升或下降LSTM 对这类突变基本无能为力。如果你发现预测曲线在拐点处比真实数据晚一年这是正常现象学习到的本质上是一个「平滑后的惯性」不是模型的 bug。5. 避坑指南LSTM人口预测的4个常见问题5.1 归一化后堆叠了奇怪的「台阶状」预测曲线现象预测结果在逆变换后变成一段段的水平线或台阶完全没有人口曲线该有的平滑度。原因是窗口预测的一步步迭代误差累积每一次预测值被当作下一次输入后误差被逐渐放大曲线开始失真。解决如果做多步预测预测未来多年不要用纯滚动预测改用「直接多步输出」——让模型一次输出未来多个年份的人口值代价是最后一层改成Dense(forecast_horizon)损失函数不变。这样每一步的误差不会递归放大曲线会平滑很多。5.2 训练集 loss 降得很好验证集体积翻车现象验证集 loss 比训练集高出一个量级预测曲线训练段可以未来段直接漂移。这种状况多半是数据分割时不切实际或者模型容量过大。解决先查数据划分是否时序有序再把 LSTMunits降到 30 左右同时提高 dropout 到 0.3最后把特征精简到只保留总人口和迁移人口这类强相关字段。城市人口预测的场景里弱特征加多了反而是噪声源。5.3 lookback 窗口设置不当带来的数据量折损现象30 年数据lookback设成 10训练集只剩 16 个样本模型毫无泛化能力。很多初学者为了「让模型看更多历史」把窗口设得很大忽略了样本数量。解决先算一笔账样本数 总年份 − lookback − 测试集年份。保证训练样本数不低于 20 才谈得上训练 LSTM。如果数据量实在不够优先考虑用更短的lookback3或者干脆换用结构更简单的 GRU。5.4 测试集的信息「泄漏」进 scaler现象验证集上 RMSE 很低但一部署到新数据上预测结果明显偏离像是模型被谁动了手脚。原因我在 2.2 里埋了个伏笔——如果你对全量数据做fit_transform测试集的取值范围已经参与了归一化参数的估计。这就等于考试前先看了答案验证集上的指标是被污染的。解决严格按时间切分后只对训练集scaler.fit_transform再用同一个 scaler 对测试集做transform。有些项目里连验证集也单独做 scaler那同样会泄之前就直接transform就好。6. 从预测一年到预测N年多步预测的两种落地做法单步预测能用的场景有限实际项目里大多要的是未来 5 年甚至 10 年的人口趋势。最后写一下我常用的两种多步预测方案以及各自的坑。第一种是滚动预测把每次的预测结果拼到输入序列末尾继续预测下一年。代码上就是循环调用model.predict每次更新输入窗口。这种方式最简单但误差随预测步长线性累积预测 5 年以上基本只配看趋势数值不值得较真。第二种是直接多步输出改网络尾部结构把Dense(1)换成Dense(steps)其中steps是你想预测的未来年数比如 5。同时y的构造也需要改每个样本的标签从单个值变成一个 5 维向量。这样一步到位输出全部未来值误差不会递归放大。代价是训练难度提高需要更多样本才能拟合得稳。我的建议30 年以上的数据可以尝试直接多步如果只有 20 年左右滚动预测是更稳妥的选择。还有一个容易被忽略的小习惯预测之后对结果做一次「合理性检查」比如人口不可能为负、单年变化率不应超过某个经验阈值。这类约束在统计模型中天然成立但 LSTM 这类纯数据驱动模型完全不知道这些常识需要用代码把边界钳制住。另外如果你想处理更复杂的城市人口场景——比如包含区县级数据、多城市对比迁移趋势——可以考虑把模型升级为双向 LSTM 或注意力机制。但那是数据量大到一定程度之后的事了。对大多数城市人口预测需求单层 LSTM 合理的数据处理已经能拿到可信度足够的结果。我自己在这类项目上吃过亏当初为了效果强行上双向 LSTM 加注意力最后在 40 个样本的数据上训练出一堆幻觉回退到简洁的单层结构反而稳定。希望这些经验能帮你在做类似项目时少走一段弯路。本文还有配套的精品资源点击获取 SEO 优化官网定制响应式建站教育培训建站