ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

共享单车预测与调度毕设源码:BP神经网络与蚁群算法实战

共享单车预测与调度毕设源码:BP神经网络与蚁群算法实战 简介这份毕业设计资源面向计算机、数据科学相关专业的学生与深度学习入门者聚焦共享单车需求量预测与调度路径优化这一典型城市交通问题。方案以神经网络为核心将单车需求量与时间段、地理画像等特征建立关联实现不同区域的需求预测并借助蚁群算法规划最优调度路径形成从数据预处理到结果评估的完整闭环。压缩包共16个文件以11个Python脚本为主体覆盖地理哈希解码、区域划分、POI决策、需求统计、BP神经网络建模、误差计算与蚁群算法等模块另含4个npy数据文件用于存放训练与测试数组以及1份README说明文档整体约545KB结构清晰、便于按流程复现。目前已有387人学习下载适合作为毕业设计参考或课程项目模板读者可据此掌握特征工程、模型训练与启发式调度算法的结合思路并借鉴其模块划分与排错经验。1. 从一份毕设源码说起共享单车预测与调度到底怎么落地共享单车的潮汐现象是调度员每天都要面对的难题早高峰地铁口的车被骑空晚高峰写字楼下的车堆成山。这份毕业设计源码用两条技术线解决这个问题——BP 神经网络做需求量预测蚁群算法做调度路径规划。整个项目用 Python 写成从 geohash 解码、区域划分、POI 特征融合到训练数据生成、模型训练、误差计算再到蚁群算法求解调度路径是一条完整的链路。它适合正在做深度学习或智能优化方向毕设的学生也适合想了解「预测调度」闭环怎么用代码串起来的工程师。源码包里的文件按编号排列从 1 到 11跟着编号走就能把整条流水线跑通。2. 数据预处理流水线从 geohash 到区域需求矩阵2.1 geohash 解码与区域划分的逻辑共享单车的原始数据通常带经纬度但直接拿经纬度做区域统计粒度太细噪声大。这个项目用 geohash 做空间聚合把连续坐标映射成网格编码再按网格统计需求量。1.geohash-decode.py负责把原始数据里的 geohash 字符串还原成经纬度2.division area.py按 geohash 前缀把城市切成若干区域。geohash 的好处是前缀相同的点在地理上相邻切区域时不用做复杂的空间聚类。# 1.geohash-decode.py 核心逻辑示意 import geohash def decode_geohash(gh): 将 geohash 字符串解码为经纬度 lat, lon geohash.decode(gh) return lat, lon # 批量处理原始数据中的 geohash 列 raw_data[lat], raw_data[lon] zip(*raw_data[geohash].map(decode_geohash))这段代码的关键在geohash.decode它返回的是网格中心点的经纬度。参数上要注意 geohash 精度精度 6 大约对应 1.2km×0.6km 的网格精度 7 大约 150m×150m。精度选太高每个区域样本太少神经网络学不到规律选太低区域太大调度失去意义。我一般先用精度 6 跑一遍看区域需求量分布如果某些区域日均需求低于 5 单就降到精度 5。2.2 POI 特征融合与区域画像构建4.area-dicision-with-poi.py是整条流水线里容易被忽略但很关键的一步。它把区域和 POI兴趣点数据关联起来给每个区域打上「地铁站」「写字楼」「住宅区」「商圈」等标签。神经网络如果只吃时间段和区域编号学到的只是时间规律加上 POI 特征后才能捕捉「为什么这个区域早高峰需求大」——因为它是地铁口。# 4.area-dicision-with-poi.py 区域-POI 关联示意 area_poi_dict {} for area_id, area_polygon in areas.items(): poi_count {metro: 0, office: 0, residential: 0, mall: 0} for poi in poi_list: if point_in_polygon(poi[location], area_polygon): poi_count[poi[type]] 1 area_poi_dict[area_id] poi_countpoint_in_polygon判断 POI 是否落在区域内常见做法是用射线法或直接调shapely库。POI 类型映射需要根据实际数据调整不同城市 POI 分类体系不一样。这一步的输出area_poi_dict会作为特征拼接到后续的训练数据里。如果 POI 数据缺失可以退而求其次用区域历史需求均值做特征但预测精度会打折扣。2.3 多表合并与需求统计3.merge-two-sheets.py把骑行记录表和区域表按区域 ID 合并5.demands-statistics.py按「区域时间段」做聚合统计算出每个区域在每个时间片的需求量。时间片粒度通常取 1 小时太细会导致样本稀疏太粗则失去调度指导意义。# 5.demands-statistics.py 按区域和时间片统计需求 demand_matrix df.groupby([area_id, time_slot]).size().reset_index(namedemand) # 补全缺失的时间片需求量填 0 full_index pd.MultiIndex.from_product( [demand_matrix[area_id].unique(), range(24)], names[area_id, time_slot] ) demand_matrix demand_matrix.set_index([area_id, time_slot]).reindex(full_index, fill_value0).reset_index()groupby后size()统计的是每个区域每个时间片的骑行订单数。reindex补全缺失组合很重要——如果某个区域某个小时没有订单不补 0 的话神经网络会误以为这个时间片不存在。补全后得到的是完整的「区域×时间片」需求矩阵这是后续 BP 神经网络的标签来源。注意需求统计时要去掉异常订单比如骑行时长超过 4 小时的可能是未还车和骑行时长低于 1 分钟的可能是误开锁这些噪声会拉偏预测。3. BP 神经网络预测需求量结构、训练与误差评估3.1 网络结构设计与输入特征组织9.BP Neural Networks.py是整个项目的预测核心。输入层维度由特征数量决定时间段one-hot 24 维、区域 POI 特征4 维、历史需求滞后特征前 1-3 个时间片3 维总共约 31 维。隐层用两层每层 64 个神经元激活函数用 ReLU输出层 1 个神经元做回归不加激活函数。# 9.BP Neural Networks.py 网络定义示意 import torch import torch.nn as nn class BikeDemandNet(nn.Module): def __init__(self, input_dim): super().__init__() self.net nn.Sequential( nn.Linear(input_dim, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 1) ) def forward(self, x): return self.net(x)选 BP 而不是 LSTM 或 Transformer是因为这个场景的输入特征已经是结构化向量时间依赖通过滞后特征显式引入不需要网络自己去学序列模式。隐层 64 是经验值区域数量多、数据量大时可以加到 128但要注意过拟合。损失函数用 MSE优化器用 Adam学习率 1e-3。3.2 训练数据生成与划分策略7.generate-test-data.py和8.save-test train-data.py负责把需求矩阵切成训练集和测试集。这里有个容易翻车的地方不能随机打乱后划分因为同一区域相邻时间片的样本高度相关随机划分会导致信息泄漏测试集精度虚高。# 8.save-test train-data.py 按时间划分训练测试集 split_time int(24 * 0.8) # 前 80% 时间片做训练 train_data demand_matrix[demand_matrix[time_slot] split_time] test_data demand_matrix[demand_matrix[time_slot] split_time] np.save(input_arr.npy, train_data[feature_cols].values) np.save(output_arr.npy, train_data[demand].values)按时间切分的逻辑是用前 19 个小时的数据训练预测后 5 个小时。这更接近真实调度场景——你只能用历史数据预测未来。input_arr.npy和output_arr.npy是训练集的输入输出test_input_arr.npy和test_output_arr.npy是测试集。保存成 npy 格式方便后续直接np.load读取不用每次重新跑预处理。3.3 误差计算与模型评估10.calculate-average-error.py算的是平均绝对误差MAE和平均绝对百分比误差MAPE。MAE 直接反映预测值偏离真实值多少单MAPE 反映偏离比例。两个指标一起看MAE 小但 MAPE 大说明低需求区域预测不准MAPE 小但 MAE 大说明高需求区域误差绝对值大。# 10.calculate-average-error.py 误差计算 pred model(test_input) mae np.mean(np.abs(pred - test_output)) mape np.mean(np.abs((pred - test_output) / (test_output 1e-6))) * 100 print(fMAE: {mae:.2f}, MAPE: {mape:.2f}%)1e-6是防止除零。实际跑下来MAE 在 2-5 单之间算正常MAPE 在 15%-30% 之间。如果 MAPE 超过 50%检查一下是不是某些区域需求量接近 0 导致百分比爆炸这种情况可以只对需求量大于阈值的区域算 MAPE。提示训练时用torch.save(model.state_dict(), model.pth)保存权重方便复现。不要只保存整个模型对象换环境容易加载失败。4. 蚁群算法调度路径规划参数、实现与调优4.1 蚁群算法求解调度路径的建模方式11.Ant Colony Algorithm.py解决的是已知若干区域的需求量和当前车辆分布如何规划一辆调度车的最优路径使得满足需求的同时总行驶距离最短。这是一个带容量约束的车辆路径问题CVRP。蚁群算法的思路是让多只「蚂蚁」各自构建路径路径短的蚂蚁释放更多信息素后续蚂蚁倾向于走信息素浓的边。# 11.Ant Colony Algorithm.py 信息素更新核心 def update_pheromone(pheromone, ant_paths, distances, rho0.5, Q100): pheromone * (1 - rho) # 挥发 for path, dist in zip(ant_paths, distances): for i in range(len(path) - 1): pheromone[path[i]][path[i1]] Q / dist return pheromonerho是挥发系数取 0.5 意味着每轮信息素减半。Q是信息素增量系数影响收敛速度。Q/dist让短路径获得更多信息素。参数组合需要根据区域数量和距离量级调整区域多、距离大时Q要相应增大否则信息素差异太小蚂蚁选择趋于随机。4.2 关键参数设置与收敛判断蚁群算法的参数比神经网络更敏感几个核心参数直接决定能不能收敛到可用解。参数含义常用范围调参建议alpha信息素重要程度1~3越大越依赖历史经验容易早熟beta启发式信息重要程度2~5越大越贪心容易陷入局部最优rho信息素挥发系数0.3~0.7太小收敛慢太大丢失多样性Q信息素强度50~200根据距离量级调整ant_num蚂蚁数量20~50区域数量的 1~2 倍max_iter最大迭代次数100~500看收敛曲线平坦后可停# 参数配置示例 alpha, beta, rho, Q 1.5, 3.0, 0.5, 100 ant_num, max_iter 30, 200 best_path, best_dist None, float(inf) for it in range(max_iter): paths, dists [], [] for ant in range(ant_num): path, dist build_ant_path(pheromone, distance_matrix, alpha, beta) paths.append(path); dists.append(dist) pheromone update_pheromone(pheromone, paths, dists, rho, Q) if min(dists) best_dist: best_dist, best_path min(dists), paths[dists.index(min(dists))]收敛判断看best_dist随迭代的变化曲线。如果 50 代内没下降基本可以停。build_ant_path里要做容量约束判断车辆载重有限不能一次满足所有区域需要多辆车或多次往返。4.3 调度结果输出与可视化验证6.generate-final-sheet.py把预测结果和调度路径整合成最终输出表包含每个区域的需求量、分配车辆、到达时间、装载量。验证调度方案是否合理可以看几个指标总行驶距离、车辆满载率、需求满足率。# 6.generate-final-sheet.py 输出调度方案 final_sheet [] for route in best_routes: for i, area in enumerate(route): final_sheet.append({ vehicle_id: route[vehicle_id], sequence: i, area_id: area, predicted_demand: demand_pred[area], load: route[loads][i] }) pd.DataFrame(final_sheet).to_csv(dispatch_plan.csv, indexFalse)输出 CSV 后可以用 QGIS 或 folium 画路径图直观检查有没有绕路、回头路。如果发现某辆车路径交叉严重说明距离矩阵计算有问题检查一下是不是用了直线距离而实际路网距离差异大。注意蚁群算法每次运行结果可能不同因为初始信息素和蚂蚁选择有随机性。建议固定随机种子np.random.seed(42)方便复现和对比不同参数的效果。5. 避坑与常见问题排查5.1 数据预处理阶段的三个坑现象geohash 解码后大量点落在城市外。原因原始数据里混入了测试订单或 GPS 漂移点。解决解码后按城市边界过滤超出范围的直接丢弃不要试图修正。现象需求矩阵大量为 0神经网络预测全是 0。原因区域划分太细每个区域样本太少。解决降低 geohash 精度或合并相邻区域保证每个区域日均需求至少 10 单。现象POI 关联后某些区域特征全为 0。原因POI 数据覆盖不全或坐标系不一致。解决检查 POI 和区域是否用同一坐标系WGS84 vs GCJ02不一致先转换。5.2 神经网络训练阶段的翻车记录现象训练 loss 下降但测试 MAE 很高。原因随机划分导致信息泄漏或者过拟合。解决按时间切分加 Dropout 或 L2 正则早停。现象预测值全是均值附近没有波动。原因输入特征没做归一化或者学习率太小。解决对需求量做 Min-Max 归一化学习率调到 1e-3 或 1e-2 试。现象GPU 上训练正常CPU 上加载报错。原因保存时 tensor 在 GPU 上。解决保存前.cpu()加载时map_locationcpu。5.3 蚁群算法调度的常见异常现象算法不收敛best_dist 一直波动。原因alpha 太大导致早熟或 rho 太小导致信息素累积过多。解决降低 alpha 到 1增大 rho 到 0.5-0.7。现象路径满足不了所有区域需求。原因车辆容量约束太紧或区域需求预测偏高。解决增加车辆数或放宽容量检查预测需求量是否合理。现象每次运行结果差异很大。原因随机种子没固定。解决np.random.seed(42)和random.seed(42)都加上。6. 进阶技巧把预测误差反馈到调度鲁棒性里跑通整条流水线后真正影响落地效果的是预测误差怎么传导到调度环节。我一般会做一件事不只用预测值做调度而是用预测值的置信区间做鲁棒调度。具体做法是在 BP 网络输出层同时输出均值和方差用高斯负对数似然做损失这样每个区域的预测结果带一个不确定性估计。# 输出均值和方差的双头网络 class BikeDemandNetWithUncertainty(nn.Module): def __init__(self, input_dim): super().__init__() self.shared nn.Sequential( nn.Linear(input_dim, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU() ) self.mean_head nn.Linear(64, 1) self.var_head nn.Linear(64, 1) def forward(self, x): h self.shared(x) mean self.mean_head(h) var torch.exp(self.var_head(h)) 1e-6 return mean, var # 高斯负对数似然损失 def gaussian_nll(mean, var, target): return torch.mean(0.5 * torch.log(var) 0.5 * ((target - mean) ** 2) / var)训练完后每个区域的需求量是一个分布。调度时用「均值 1 倍标准差」作为需求上界这样即使预测偏低调度车辆也有余量。代价是总行驶距离会增加但需求满足率会明显提升。实际跑下来MAE 只增加 0.5 单左右但缺车区域数量能减少三成。另一个技巧是蚁群算法的信息素初始化。不要用均匀分布而是用「需求量/距离」做初始信息素让蚂蚁第一轮就倾向于高需求近区域。这样收敛代数能从 200 降到 80 左右。# 基于需求密度的信息素初始化 pheromone np.zeros((n_areas, n_areas)) for i in range(n_areas): for j in range(n_areas): if i ! j: pheromone[i][j] demand_pred[i] / (distance_matrix[i][j] 1e-6) pheromone pheromone / pheromone.max() # 归一化这两个改动加起来整条流水线的调度方案从「能跑」变成「敢用」。从那以后我每次做预测调度的项目都会强制走一遍不确定性量化和信息素热启动不再直接用点预测值去驱动调度。希望帮到你。本文还有配套的精品资源点击获取
返回列表