GA-LSTM预测代码调参实战:遗传算法优化超参数与PyTorch实现

发布时间:2026/10/9 3:41:17
GA-LSTM预测代码调参实战:遗传算法优化超参数与PyTorch实现 简介这份资源是面向时间序列预测学习者与算法实践者的GA-LSTM预测模型Python实现包针对传统LSTM易过拟合、欠拟合及参数调优困难的问题用遗传算法对LSTM权重进行全局寻优可应用于股票价格、天气等复杂序列预测场景。压缩包共5个文件约9KB以2个py源码文件为核心分别承载遗传算法优化与LSTM网络构建逻辑另含2个pyc编译缓存与1个txt说明文档结构精简、便于快速阅读与二次修改。目前已有390人学习下载。代码覆盖数据预处理、模型定义、遗传算法适应度评估与选择交叉变异、训练迭代及预测评估等环节读者可据此理解GA与LSTM结合的完整流程掌握用MSE、MAE等指标衡量预测性能的方法并在此基础上迁移到自己的序列建模任务中。1. GA-LSTM 预测代码到底在优化什么从一组跑崩的超参说起很多人第一次拿到 GA-LSTM 遗传算法优化的 LSTM 预测代码会以为它是个更高级的模型结构其实不是。它优化的是 LSTM 的超参数——时间窗长度、隐藏层神经元数、学习率、批大小、训练轮数。这些参数手动调要么靠经验要么靠网格搜索参数一多组合就爆炸。遗传算法做的事就是把这些超参数编码成一条“染色体”用选择、交叉、变异去迭代搜索让验证集上的预测误差最小。适合谁手上有时间序列预测任务、已经跑通过基础 LSTM、但被调参折磨到怀疑人生的从业者。这篇笔记就按“先讲清 GA 和 LSTM 怎么接、再给可复现的 Python 实现、最后说清踩坑点”的顺序展开代码可以直接抄参数可以按你的数据改。2. GA 和 LSTM 怎么接染色体编码与适应度函数的设计2.1 为什么不是所有超参都值得丢给遗传算法遗传算法的搜索开销和染色体长度直接相关。染色体越长搜索空间越大收敛越慢。我一般只把对预测精度影响最大的 4 个参数放进染色体时间窗长度window_size、LSTM 隐藏层单元数hidden_size、学习率lr、批大小batch_size。训练轮数epochs不放进搜索因为每评估一次染色体都要完整训练一轮轮数再翻倍时间成本直接失控。常见做法是固定一个偏大的epochs配合早停策略让模型自己决定什么时候停。染色体编码用实数编码每个基因对应一个超参范围自己定。比如window_size取 5 到 60 的整数hidden_size取 16 到 256 的整数lr取 1e-4 到 1e-2 的对数均匀分布batch_size取 16 到 128 的 2 的幂次。编码方式决定了搜索的粒度粒度太细遗传算法退化成随机搜索粒度太粗容易错过好解。适应度函数是 GA 和 LSTM 之间的桥梁。每次评估一条染色体就用它解码出的超参构建一个 LSTM在训练集上训练在验证集上算一个误差指标比如 RMSE 或 MAE。误差越小适应度越高。这里有个血泪经验验证集必须和训练集在时间上隔开不能随机打乱否则时间序列的时序依赖被破坏适应度会虚高最后选出来的参数在真实预测时翻车。2.2 用 DEAP 搭一个最小可跑的 GA 搜索框架DEAP 是 Python 里比较轻量的遗传算法库不用自己手写选择、交叉、变异。下面这段代码搭了一个最小框架染色体是 4 个浮点数适应度函数先用一个模拟的 LSTM 评估函数占位后面再替换成真实训练。import random from deap import base, creator, tools, algorithms # 定义适应度最大化和个体类型 creator.create(FitnessMax, base.Fitness, weights(1.0,)) creator.create(Individual, list, fitnesscreator.FitnessMax) # 超参范围window_size, hidden_size, lr, batch_size BOUNDS [(5, 60), (16, 256), (1e-4, 1e-2), (16, 128)] def decode(individual): 把染色体解码成可用的超参 window int(round(individual[0])) hidden int(round(individual[1])) lr float(individual[2]) batch int(2 ** round(individual[3] ** 0.5)) # 映射到2的幂次附近 batch max(16, min(128, batch)) return window, hidden, lr, batch def evaluate(individual): 适应度函数这里先用模拟值后面替换成真实LSTM训练 window, hidden, lr, batch decode(individual) # 模拟假设存在一个最优组合越接近越好 score -(abs(window - 30) / 30 abs(hidden - 128) / 128 abs(lr - 1e-3) / 1e-3 abs(batch - 64) / 64) return (score,) toolbox base.Toolbox() toolbox.register(attr_float, random.uniform, 0, 1) toolbox.register(individual, tools.initRepeat, creator.Individual, toolbox.attr_float, n4) toolbox.register(population, tools.initRepeat, list, toolbox.individual) toolbox.register(evaluate, evaluate) toolbox.register(mate, tools.cxBlend, alpha0.5) toolbox.register(mutate, tools.mutGaussian, mu0, sigma0.2, indpb0.2) toolbox.register(select, tools.selTournament, tournsize3) def main(): pop toolbox.population(n20) hof tools.HallOfFame(1) stats tools.Statistics(lambda ind: ind.fitness.values) stats.register(max, max) algorithms.eaSimple(pop, toolbox, cxpb0.6, mutpb0.3, ngen10, statsstats, halloffamehof, verboseTrue) best hof[0] print(最优染色体:, best) print(解码超参:, decode(best)) if __name__ __main__: main()逻辑说明decode函数把 0 到 1 之间的浮点基因映射到实际超参范围其中batch_size做了幂次映射因为批大小通常取 2 的幂。evaluate是占位函数真实使用时要把 LSTM 训练和验证误差算进去。cxBlend是混合交叉mutGaussian是高斯变异selTournament是锦标赛选择这三个算子是时间序列超参搜索里比较稳的组合。参数说明种群大小n20适合快速验证实际跑建议 30 到 50交叉概率cxpb0.6、变异概率mutpb0.3是常用起点变异概率别超过 0.4否则搜索退化成随机游走。提示DEAP 的eaSimple每代都会打印统计信息第一次跑先把ngen设小一点比如 5 到 10确认流程通了再加大。3. 把 LSTM 训练塞进适应度函数PyTorch 实现与数据窗口构造3.1 时间序列滑窗构造与归一化顺序LSTM 预测代码里最容易翻车的地方不是模型结构而是数据窗口的构造顺序。时间序列要先做差分或去趋势再做归一化最后滑窗。如果先滑窗再归一化训练集和验证集的统计量会混在一起造成信息泄漏。我一般用MinMaxScaler在训练集上拟合然后 transform 验证集和测试集绝不 fit 验证集。滑窗函数要保证输入X和标签y的时序对齐。假设原始序列是series窗口长度是window那么第i个样本的输入是series[i:iwindow]标签是series[iwindow]。下面这个函数返回三维数组形状是(样本数, 窗口长度, 特征数)可以直接喂给 LSTM。import numpy as np from sklearn.preprocessing import MinMaxScaler def make_windows(series, window): 把一维序列转成LSTM需要的滑窗样本 X, y [], [] for i in range(len(series) - window): X.append(series[i:iwindow]) y.append(series[iwindow]) X np.array(X).reshape(-1, window, 1) y np.array(y) return X, y def prepare_data(series, window, train_ratio0.8): 先划分训练/验证再分别归一化和滑窗 n len(series) train_end int(n * train_ratio) train_series series[:train_end] val_series series[train_end - window:] # 验证集要包含窗口回溯部分 scaler MinMaxScaler() train_scaled scaler.fit_transform(train_series.reshape(-1, 1)).flatten() val_scaled scaler.transform(val_series.reshape(-1, 1)).flatten() X_train, y_train make_windows(train_scaled, window) X_val, y_val make_windows(val_scaled, window) return X_train, y_train, X_val, y_val, scaler逻辑说明prepare_data先按时间顺序切分训练集和验证集验证集往前多取window个点保证第一个验证样本的输入窗口完整。归一化只在训练集上 fit验证集用同一个 scaler transform。参数说明train_ratio0.8是常见起点如果数据量少可以调到 0.7window由 GA 搜索决定这里作为入参传入。3.2 用 PyTorch 写一个可被 GA 调用的 LSTM 训练函数下面这个函数接收 GA 解码出的超参完成一次完整的 LSTM 训练和验证返回验证集 RMSE。它会被注册到 DEAP 的toolbox.evaluate里替换掉之前的模拟函数。import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset import numpy as np class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers1): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.lstm(x) out self.fc(out[:, -1, :]) # 取最后一个时间步 return out.squeeze(-1) def train_and_eval(X_train, y_train, X_val, y_val, hidden_size, lr, batch_size, epochs50, patience5): 训练LSTM并返回验证集RMSE device torch.device(cuda if torch.cuda.is_available() else cpu) model LSTMPredictor(hidden_sizehidden_size).to(device) optimizer torch.optim.Adam(model.parameters(), lrlr) criterion nn.MSELoss() train_ds TensorDataset(torch.FloatTensor(X_train), torch.FloatTensor(y_train)) train_loader DataLoader(train_ds, batch_sizebatch_size, shuffleFalse) val_X torch.FloatTensor(X_val).to(device) val_y torch.FloatTensor(y_val).to(device) best_rmse float(inf) wait 0 for epoch in range(epochs): model.train() for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() optimizer.step() model.eval() with torch.no_grad(): pred_val model(val_X) rmse torch.sqrt(criterion(pred_val, val_y)).item() if rmse best_rmse: best_rmse rmse wait 0 else: wait 1 if wait patience: break # 早停 return best_rmse逻辑说明LSTMPredictor只取 LSTM 最后一个时间步的输出接全连接层适合单步预测。train_and_eval里shuffleFalse是故意的时间序列训练集内部也不建议打乱否则相邻 batch 之间的时序连续性被破坏。早停patience5防止过拟合同时控制 GA 评估的时间成本。参数说明epochs50配合早停实际训练轮数通常远小于 50num_layers1是默认值如果欠拟合可以加到 2但 GA 搜索里不建议把层数也放进去搜索空间会太大。3.3 把真实评估函数接回 GA 主循环现在把train_and_eval包进evaluateGA 每评估一条染色体就完整跑一次 LSTM 训练。注意数据准备只做一次放在 GA 外面避免每条染色体都重复归一化和滑窗。# 假设 series 是你的原始时间序列 # X_train, y_train, X_val, y_val, scaler prepare_data(series, window30) def evaluate_real(individual): window, hidden, lr, batch decode(individual) # 注意window 变了滑窗数据也要重新构造 X_tr, y_tr, X_va, y_va, _ prepare_data(series, window) rmse train_and_eval(X_tr, y_tr, X_va, y_va, hidden_sizehidden, lrlr, batch_sizebatch) return (-rmse,) # DEAP 最大化适应度所以取负 toolbox.register(evaluate, evaluate_real)逻辑说明因为window是 GA 搜索的一部分每条染色体解码出的窗口长度不同滑窗数据必须重新构造。这是 GA-LSTM 比固定窗口 LSTM 慢的主要原因。参数说明如果数据量很大可以在 GA 外面预先算好几个候选窗口的滑窗数据用字典缓存evaluate_real里按window查表能省不少时间。4. 避坑与排查GA-LSTM 预测代码最常见的 5 个翻车点4.1 现象GA 收敛到一组超参但测试集误差远大于验证集原因验证集和测试集的时间划分有问题或者归一化时用了全量数据的统计量。时间序列里验证集和测试集必须严格在训练集之后不能有重叠。归一化 scaler 只能在训练集上 fit。解决检查prepare_data里的train_ratio切分点确认验证集和测试集没有用到未来信息。把 scaler 的fit和transform分开写验证集和测试集只 transform。4.2 现象GA 每代最优适应度几乎不变搜索像在随机游走原因变异概率太高或者染色体编码范围太宽导致好基因被破坏。另一个常见原因是适应度函数噪声太大LSTM 每次训练结果波动大GA 分不清哪条染色体真的好。解决把mutpb降到 0.1 到 0.2cxpb保持在 0.5 到 0.7。适应度噪声大的话每条染色体评估 2 到 3 次取平均或者固定随机种子。PyTorch 里可以用torch.manual_seed和np.random.seed固定。4.3 现象训练 loss 正常下降但验证 RMSE 一直很大原因LSTM 隐藏层单元数太多模型过拟合或者学习率太大验证集上的预测值震荡。GA 搜索出的hidden_size和lr组合可能偏向训练集。解决在适应度函数里加正则项比如对hidden_size做惩罚防止 GA 一味追求大模型。学习率搜索范围上限别超过 1e-2下限别低于 1e-5。4.4 现象GA 跑了几代就报内存不足原因每条染色体都重新构造滑窗数据X_train数组反复分配释放Python 垃圾回收跟不上。数据量大时尤其明显。解决用字典缓存不同window对应的滑窗数据evaluate_real里先查缓存。另外把batch_size的下限设大一点比如 32减少 DataLoader 的迭代次数。4.5 现象最优超参在训练集上表现很好换一批数据就崩原因GA 过拟合了验证集。遗传算法本身也是一种搜索搜索次数多了验证集就被“记住”了。这是 GA-LSTM 最隐蔽的坑。解决留一个独立的测试集GA 搜索结束后只在测试集上跑一次不参与任何选择。如果测试集误差和验证集差很多说明搜索过拟合减少 GA 代数或增大种群多样性。5. 让 GA-LSTM 真正可用的两个进阶技巧5.1 用并行评估把 GA 搜索时间压下来GA-LSTM 最大的成本是每条染色体都要训练一次 LSTM。种群 30、代数 20就是 600 次训练。单次训练 10 秒总共 100 分钟。DEAP 支持并行评估用multiprocessing把种群评估分发到多个进程。关键是把toolbox.register(map, pool.map)加进去注意 LSTM 训练函数里不要有全局状态数据用只读方式传入。import multiprocessing from deap import base pool multiprocessing.Pool(processes4) toolbox.register(map, pool.map) # 后续 eaSimple 会自动用 pool.map 并行评估逻辑说明pool.map会把种群里的个体分发到 4 个进程每个进程独立跑evaluate_real。参数说明进程数别超过 CPU 核心数GPU 训练的话每个进程都会占显存进程数要按显存大小调。如果显存不够改成 2 个进程或者用 CPU 训练。5.2 用验证集误差曲线判断 GA 是否真的在收敛不要只看 GA 打印的最优适应度那个值受随机性影响大。我一般把每代的最优验证 RMSE 和平均验证 RMSE 都存下来画一条曲线。如果最优曲线震荡但平均曲线在下降说明种群在探索还有希望如果两条曲线都平了说明收敛了再跑下去只是浪费时间。下面这段代码把统计信息存成列表跑完画图。import matplotlib.pyplot as plt history {gen: [], best: [], avg: []} def record(stats): gen len(history[gen]) history[gen].append(gen) history[best].append(stats[max]) history[avg].append(stats[avg]) # 在 eaSimple 的 stats 里注册 record或者手动在每代后调用 # 跑完后画图 plt.plot(history[gen], history[best], labelbest) plt.plot(history[gen], history[avg], labelavg) plt.xlabel(generation) plt.ylabel(fitness (negative RMSE)) plt.legend() plt.savefig(ga_convergence.png)逻辑说明best曲线反映当前找到的最好解avg曲线反映种群整体水平。两条曲线都平了就可以停。参数说明如果best还在降但avg平了说明种群多样性不够可以适当提高变异概率。如果avg比best差很多说明种群分化大可以增加选择压力。我自己跑 GA-LSTM 的习惯是先用小种群、少代数跑通流程确认数据窗口和归一化没问题再加大搜索规模。每次改数据或者改预测目标都要重新检查验证集划分别偷懒复用上一次的切分。这个方向值不值得做如果你的 LSTM 调参时间超过两天GA 搜索半天到一天能给出一个稳定可用的超参组合那就值得。如果数据量小到手动试几组就能找到好参数GA 反而是过度工程。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询