数学建模算法实战:从线性规划到遗传算法的代码实现与场景应用

发布时间:2026/8/29 15:55:47
数学建模算法实战:从线性规划到遗传算法的代码实现与场景应用 1. 从“知道”到“会用”数学建模算法的实战分水岭很多同学在接触数学建模时都会陷入一个怪圈教材和论文里看过的算法一大堆什么线性规划、遗传算法、神经网络名字都耳熟能详甚至还能背出几个公式。但一到自己动手解决一个具体问题比如预测下个月的销量、规划物流路线、或者分析用户行为数据立刻就懵了——我该用哪个算法这些算法到底是怎么从一行行代码变成实际解决方案的这个从“知道”到“会用”的鸿沟恰恰是决定建模成败的关键。我自己带学生打比赛、做项目这么多年发现大家最缺的不是算法理论而是场景映射能力和代码落地能力。前者帮你从纷繁的问题描述中精准地抓住核心并匹配上最合适的“工具”后者则确保这个“工具”能被你实实在在地用起来跑出结果而不是停留在PPT里。网上很多资料要么过于理论像教科书要么只给个代码片段不说清楚前因后果和怎么调参。结果就是大家学了一堆“屠龙术”真遇到问题却连只鸡都杀不了。所以这个系列我不想再重复那些定义和公式咱们就来点硬的。聚焦几个在数学建模竞赛像国赛、美赛和实际数据分析项目中出场率最高、也最容易让人困惑的算法。每一篇我们都死死扣住两个核心第一这个算法最适合解决什么类型的问题看到什么样的描述词你就应该立刻想到它第二拿到一个问题后如何从零开始用代码把它实现出来包括数据怎么处理、参数怎么设置、结果怎么分析我会结合我踩过的坑和成功的案例把每一步的逻辑都掰开揉碎讲清楚。咱们的目标就一个让你下次再遇到问题能条件反射般地选出算法并能有条不紊地把它实现出来。2. 线性规划与整数规划资源分配的“铁算盘”当你看到问题描述里出现“最大化利润”、“最小化成本”、“在…约束条件下”、“资源有限”、“分配方案”这些关键词时你的第一反应就应该跳到线性规划Linear Programming, LP或其兄弟整数规划Integer Programming, IP。这是优化领域最经典、最基础也最实用的工具没有之一。它就像一把“铁算盘”能帮你在一堆限制条件中算出那个“最优”的分配或生产方案。2.1 核心思想与适用场景解剖线性规划的核心思想其实特别直观我们有一个想要最大化比如利润或最小化比如成本的目标这个目标可以表示为一组决策变量的线性组合比如总利润 5*产品A产量 8*产品B产量。同时我们做事不可能随心所欲会面临各种限制比如原材料不够、机器工时有限、市场需求有上限这些限制同样可以表示为决策变量的线性不等式或等式比如2*产品A产量 4*产品B产量 100表示原材料消耗不超过100公斤。线性规划要做的就是在满足所有这些线性约束的“可行域”里找到让目标函数达到最优的那个点。它的适用场景极其广泛生产计划多种产品共享有限的人力、设备、原材料如何安排生产比例使总利润最高运输调度多个仓库、多个销售点运输成本不同如何调货使总运费最低营养配餐多种食物营养成分和价格不同如何搭配在满足每日营养需求的前提下成本最低投资组合多种投资渠道预期回报和风险不同如何分配资金在可接受风险下最大化收益而整数规划是线性规划的升级版额外要求全部或部分决策变量必须取整数值。这听起来只是个小变化但实际意义巨大。因为现实中很多东西是不能分割的比如你不可能雇佣0.5个人、派出3.5辆车、或者建半座工厂。当问题涉及“是否选择”0或1称为0-1规划或者“不可分割物品的数量”时就必须用整数规划。例如物流中心的选址选或不选某个地点、生产线的开关启动或关闭、旅行商问题每个城市恰好访问一次等。注意整数规划的计算复杂度远高于线性规划。线性规划有单纯形法等高效算法能在多项式时间内求解大规模问题。但整数规划是NP-hard问题问题规模稍大求解时间就可能指数级增长。在实际应用中如果决策变量值很大比如生产数万台有时可以先用线性规划求解然后对结果取整作为近似解。但对于关键决策如选址必须严格使用整数规划求解器。2.2 代码实现从问题描述到Python求解理论懂了怎么变成代码这里我们以Python为例因为它有强大且易用的优化库如PuLP和ortools。PuLP更贴近建模语言易于理解ortools是谷歌出品求解器性能强劲。我们先用PuLP上手因为它最能体现“从问题到模型”的翻译过程。场景设定一家工厂生产两种产品A和B。生产一件A产品利润5元耗时2小时消耗原料3公斤生产一件B产品利润8元耗时4小时消耗原料2公斤。工厂每周可用工时为100小时原料为120公斤。且由于市场原因产品A的产量不能超过30件。问每周应如何安排生产计划才能使总利润最大第一步定义问题识别三要素决策变量产品A的产量x_A产品B的产量x_B。目标函数最大化总利润Maximize Z 5*x_A 8*x_B。约束条件工时约束2*x_A 4*x_B 100原料约束3*x_A 2*x_B 120市场约束x_A 30非负约束x_A 0, x_B 0(产量不能为负)第二步使用PuLP建模与求解# 导入PuLP库 from pulp import LpProblem, LpVariable, LpMaximize, LpStatus, value # 1. 创建问题实例指定问题名称和优化方向最大化 prob LpProblem(Factory_Production_Planning, LpMaximize) # 2. 定义决策变量lowBound指定下界非负 x_A LpVariable(Product_A, lowBound0, catContinuous) # 连续变量 x_B LpVariable(Product_B, lowBound0, catContinuous) # 3. 构建目标函数 prob 5 * x_A 8 * x_B, Total_Profit # 4. 添加约束条件 prob 2 * x_A 4 * x_B 100, Labor_Hours_Constraint prob 3 * x_A 2 * x_B 120, Raw_Material_Constraint prob x_A 30, Market_Demand_Constraint_A # 5. 求解问题 prob.solve() # 6. 打印求解状态和结果 print(求解状态:, LpStatus[prob.status]) print(最优生产计划) print(f 产品A产量: {value(x_A):.2f} 件) print(f 产品B产量: {value(x_B):.2f} 件) print(f 最大总利润: {value(prob.objective):.2f} 元)运行这段代码你会得到类似产品A产量: 20.00 件 产品B产量: 15.00 件 最大总利润: 220.00 元的结果。PuLP默认使用CBC求解器对于中小规模线性规划问题足够好用。第三步升级为整数规划现在假设产品A和B必须以“箱”为单位生产每箱10件即产量必须是10的整数倍。我们需要引入整数变量。修改很简单# 将变量定义为整数类型 x_A LpVariable(Product_A, lowBound0, catInteger) x_B LpVariable(Product_B, lowBound0, catInteger) # 添加必须是10的倍数的约束或者直接定义决策变量为箱数每箱利润和消耗乘以10 # 方法定义新的决策变量 y_A, y_B 为箱数且为整数 y_A LpVariable(Box_A, lowBound0, catInteger) y_B LpVariable(Box_B, lowBound0, catInteger) # 那么实际产量 x_A 10 * y_A, x_B 10 * y_B # 目标函数变为Maximize 5*10*y_A 8*10*y_B 50*y_A 80*y_B # 约束条件中的 x_A, x_B 也相应替换 prob 2*10*y_A 4*10*y_B 100, Labor_Hours_Constraint prob 3*10*y_A 2*10*y_B 120, Raw_Material_Constraint prob 10*y_A 30, Market_Demand_Constraint_A prob 50*y_A 80*y_B, Total_Profit求解后你会得到一个整数解。这里你会发现由于整数约束最优利润很可能比放松整数约束的线性规划解要低这个利润的差值被称为“整数约束的成本”。实操心得变量命名要清晰像x_A,y_A这样的命名在模型复杂后能救命千万别用x1, x2。先跑线性松弛对于整数规划问题可以先去掉整数约束用线性规划求解。这个解提供了目标函数值的上界对于最大化问题。你的整数规划最优解不可能比这个值更好。这可以帮助你评估求解器的结果质量。关注求解状态PuLP的prob.status可能返回Optimal找到最优解、Infeasible无解约束矛盾、Unbounded目标值无限大通常模型有误。一定要检查状态而不是直接相信输出的数字。大规模问题换求解器如果问题规模很大变量成千上万PuLP默认的CBC可能变慢。可以尝试安装更专业的商业求解器如Gurobi、CPLEX的API如果有学术许可或者使用ortools它的求解效率通常更高。3. 图论与网络优化连接万物的“关系网”如果说线性规划擅长处理“资源分配”那么图论Graph Theory就是处理“关系”与“连接”的天然语言。当你看到问题描述中出现“路径”、“最短”、“最少”、“连通”、“网络”、“节点”、“边”这些词时图论算法就该登场了。它把实体抽象成“点”把实体间的关系抽象成“边”从而将复杂的网络结构问题转化为可计算的形式。3.1 核心模型与典型问题映射图论的应用场景比想象中更贴近生活最短路径问题地图导航Dijkstra算法、网络路由。关键词“最快到达”、“最低成本通行”。最小生成树问题电网布局、通信网络建设、水管铺设。关键词“用最少的材料连接所有点”。最大流问题交通流量分析、管道输送容量、网络带宽分配。关键词“从A到B的最大输送能力”。旅行商问题快递员派件、机床钻孔顺序、电路板布线。关键词“访问所有点一次并回到起点总距离最短”。这是著名的NP难问题匹配问题任务分配、求职招聘、婚姻匹配。关键词“一对一配对使得总体满意度最高”。对于数学建模你不需要成为图论专家但必须能识别出这些问题属于图论范畴并知道该调用哪个“经典算法”来解决。其中最短路径和最小生成树是基础中的基础必须掌握。3.2 代码实战使用NetworkX解决最短路径与最小连通Python中NetworkX库是处理图论问题的瑞士军刀。它创建、操作、分析图结构非常方便并且内置了大量经典算法。场景一城市间最短路径Dijkstra算法假设有5个城市它们之间的公路距离如下表所示单位公里。求从城市A到城市E的最短路径及距离。起点终点距离AB4AC2BC1BD5CB1CD8CE10DE2ED2注意这里C到B有边B到C也有边距离相同这是一个有向图。如果所有边都是双向且权重相同则是无向图。import networkx as nx # 创建一个有向图 G nx.DiGraph() # 添加带权重的边 (起点 终点 权重) edges [ (A, B, 4), (A, C, 2), (B, C, 1), (B, D, 5), (C, B, 1), (C, D, 8), (C, E, 10), (D, E, 2), (E, D, 2) ] G.add_weighted_edges_from(edges) # 使用Dijkstra算法计算从A到E的最短路径 shortest_path_length, shortest_path nx.single_source_dijkstra(G, sourceA, targetE) print(f从A到E的最短路径是: {shortest_path}) print(f最短距离是: {shortest_path_length} 公里) # 如果想看所有节点到A的最短距离 # distances nx.single_source_dijkstra_path_length(G, sourceA) # print(所有节点到A的最短距离:, distances)运行结果会显示最短路径为[A, C, B, D, E]距离为10公里。NetworkX的single_source_dijkstra函数一次性返回了最短距离和路径节点列表非常方便。场景二建设成本最低的通信网络最小生成树Prim算法要在6个基站之间铺设光纤使所有基站连通任意两个基站可以间接连通且光纤总长度最短。各基站间可能的铺设距离如下单位公里。这是一个无向图问题。基站1基站2距离AB6AC1AD5BC5BE3CD5CE6CF4DF2EF6import networkx as nx # 创建一个无向图 G nx.Graph() edges [ (A, B, 6), (A, C, 1), (A, D, 5), (B, C, 5), (B, E, 3), (C, D, 5), (C, E, 6), (C, F, 4), (D, F, 2), (E, F, 6) ] G.add_weighted_edges_from(edges) # 计算最小生成树 (使用Prim算法) # minimum_spanning_tree 返回一个图对象 mst nx.minimum_spanning_tree(G, algorithmprim) print(最小生成树中包含的边连接方案:) for edge in mst.edges(dataTrue): print(f {edge[0]} -- {edge[1]} (长度: {edge[2][weight]}公里)) total_length sum(edge[2][weight] for edge in mst.edges(dataTrue)) print(f\n光纤网络总长度: {total_length} 公里) # 可视化需要matplotlib import matplotlib.pyplot as plt pos nx.spring_layout(G) # 布局算法 nx.draw(G, pos, with_labelsTrue, node_colorlightblue, edge_colorgray) nx.draw_networkx_edges(mst, pos, edge_colorred, width2) nx.draw_networkx_edge_labels(G, pos, edge_labelsnx.get_edge_attributes(G, weight)) plt.title(通信网络与最小生成树(红色边)) plt.show()这段代码会输出最小生成树包含的边例如A-C,C-F,F-D,C-B,B-E总长度可能是15公里。红色的边就是在最终方案中需要铺设光纤的路线。实操心得有向图 vs 无向图这是建模的第一步也是最容易出错的一步。公路如果是单行道就是有向图光纤连接必然是双向的是无向图。用nx.DiGraph()和nx.Graph()区分。权重代表什么边的权重可以是距离、时间、成本、流量容量等。根据问题目标选择。求“最短”通常用Dijkstra求“最小连通成本”用最小生成树。算法选择NetworkX封装得很好通常你只需要调用nx.shortest_path或nx.minimum_spanning_tree。但对于超大规模图节点数10万可能需要考虑更高效的实现如使用graph-tool库。可视化辅助对于节点数不多的情况50用matplotlib画一下图能极大帮助你理解网络结构和验证算法结果是否正确。一图胜千言。4. 启发式算法当精确计算无能为力时的“智慧搜索”前面讲的线性规划和图论中的一些基础问题都有成熟的多项式时间算法能求出精确最优解。但现实世界中很多优化问题复杂到令人绝望比如旅行商问题TSP、车辆路径问题VRP、大规模排班问题。它们的解空间随着变量增加呈指数级爆炸即使用世界上最快的超级计算机也无法在宇宙寿命内求出精确解。这时我们就需要启发式算法。启发式算法顾名思义就是提供一种“启发式”的、经验性的搜索策略它不保证找到数学上的最优解但能在可接受的时间内找到一个非常非常好的、接近最优的可行解。对于很多实际应用“足够好速度快”远比“最优但算不出来”有价值。4.1 遗传算法物竞天择的优化策略遗传算法Genetic Algorithm, GA是启发式算法中最著名、最仿生的一种。它模拟达尔文的生物进化论一个种群一组候选解通过选择优胜劣汰、交叉基因重组、变异基因突变等操作一代代演化最终产生适应度目标函数值更高的个体更好的解。适用场景问题解可以编码成一条“染色体”如一个序列或向量并且很难用传统解析方法求解。典型例子包括组合优化TSP旅行商问题、背包问题。参数调优机器学习模型超参数优化、神经网络结构搜索。调度问题生产作业调度、航班机组排班。看到以下特征可考虑遗传算法解空间巨大且离散。目标函数不规则多峰有很多局部最优解。对解的精度要求不是绝对的数学最优而是实用最优。4.2 代码实战用遗传算法求解旅行商问题我们以经典的旅行商问题为例一个快递员要访问N个城市假设N10每个城市坐标随机生成最后回到起点。求访问所有城市一次的最短路径。我们将使用DEAP这个强大的进化计算框架它比手写遗传算法更规范、功能更全。import random import numpy as np import matplotlib.pyplot as plt from deap import base, creator, tools, algorithms # 1. 定义问题随机生成10个城市的坐标 num_cities 10 cities {i: (random.uniform(0, 100), random.uniform(0, 100)) for i in range(num_cities)} # 计算城市间距离矩阵 distance_matrix np.zeros((num_cities, num_cities)) for i in range(num_cities): for j in range(num_cities): if i ! j: dx cities[i][0] - cities[j][0] dy cities[i][1] - cities[j][1] distance_matrix[i][j] np.sqrt(dx*dx dy*dy) # 2. 定义遗传算法的个体和适应度 # 个体一个城市的访问顺序排列排列编码例如 [0, 3, 1, 9, 2, 5, 4, 7, 8, 6] # 适应度总路径长度的倒数因为DEAP默认是最小化我们想要最小化距离所以用倒数使其最大化 creator.create(FitnessMin, base.Fitness, weights(-1.0,)) # 权重为负表示最小化 creator.create(Individual, list, fitnesscreator.FitnessMin) toolbox base.Toolbox() # 定义属性生成器一个0到9的随机排列 toolbox.register(indices, random.sample, range(num_cities), num_cities) # 用生成器初始化个体 toolbox.register(individual, tools.initIterate, creator.Individual, toolbox.indices) # 定义种群 toolbox.register(population, tools.initRepeat, list, toolbox.individual) # 3. 定义评估函数计算路径总长度 def eval_tsp(individual): # 个体是一个城市索引的列表 total_distance 0 # 计算从最后一个城市回到第一个城市的距离形成闭环 for i in range(num_cities): start_city individual[i] end_city individual[(i 1) % num_cities] # 取模实现闭环 total_distance distance_matrix[start_city][end_city] return (total_distance,) # 返回元组适应度可以是多目标的 toolbox.register(evaluate, eval_tsp) # 4. 定义遗传算子 # 交叉算子有序交叉OX适合排列编码能保留父代顺序信息 toolbox.register(mate, tools.cxOrdered) # 变异算子随机交换两个城市的位置交换变异 toolbox.register(mutate, tools.mutShuffleIndexes, indpb0.05) # 每个位置有5%概率发生交换 # 选择算子锦标赛选择 toolbox.register(select, tools.selTournament, tournsize3) # 5. 运行遗传算法主流程 def main(): random.seed(42) # 设置随机种子保证结果可复现 pop toolbox.population(n300) # 种群大小300 hof tools.HallOfFame(1) # 保留历代最优个体 stats tools.Statistics(lambda ind: ind.fitness.values) stats.register(avg, np.mean) stats.register(min, np.min) # 运行算法迭代200代 pop, log algorithms.eaSimple(pop, toolbox, cxpb0.7, mutpb0.2, ngen200, statsstats, halloffamehof, verboseTrue) return pop, log, hof if __name__ __main__: pop, log, hof main() best_ind hof[0] print(f\n找到的最优路径顺序: {best_ind}) print(f最短路径长度: {best_ind.fitness.values[0]:.2f}) # 可视化最优路径 best_route best_ind [best_ind[0]] # 加上起点形成闭环 x_coords [cities[i][0] for i in best_route] y_coords [cities[i][1] for i in best_route] plt.figure(figsize(10, 5)) plt.subplot(1, 2, 1) for i, (x, y) in cities.items(): plt.plot(x, y, o) plt.text(x, y, str(i)) plt.plot(x_coords, y_coords, r-) plt.title(f最优路径 (长度: {best_ind.fitness.values[0]:.2f})) plt.xlabel(X) plt.ylabel(Y) # 绘制进化过程 plt.subplot(1, 2, 2) gen log.select(gen) min_fit log.select(min) avg_fit log.select(avg) plt.plot(gen, min_fit, b-, label最短距离) plt.plot(gen, avg_fit, r-, label平均距离) plt.xlabel(代数) plt.ylabel(路径长度) plt.title(进化过程) plt.legend() plt.tight_layout() plt.show()代码解读与实操心得编码是关键TSP问题最自然的编码是“排列编码”即一个城市序列。其他问题可能需要二进制编码、实数编码等。编码方式直接影响后续交叉、变异算子的设计。算子选择对于排列编码不能使用简单的单点交叉会破坏排列的合法性产生重复或缺失城市。有序交叉OX或部分映射交叉PMX是标准选择。变异常用交换或逆序。参数调优cxpb交叉概率通常0.6-0.9、mutpb变异概率通常0.01-0.1、pop_size种群大小通常50-500、ngen迭代代数都需要根据问题调整。没有银弹需要多次实验。一个经验是增大种群和代数通常能提升解的质量但计算时间也增加。早熟收敛如果算法很快比如50代就停滞不前可能陷入了局部最优。可以尝试增加变异概率、使用更激进的选择压力、或者引入“精英保留”策略HallOfFame确保最优解不丢失。结果分析遗传算法每次运行结果可能不同随机性。好的实践是独立运行算法多次比如30次记录最优解和平均解评估算法的稳定性和解的质量。我们代码中设置了随机种子是为了结果可复现便于调试。不要神化GA遗传算法能给出很好的近似解但无法证明是最优的。对于小规模TSPN20可以用精确算法如动态规划验证GA的结果。对于大规模问题GA的结果通常可以作为有效的上界/下界参考。5. 时间序列预测从历史看未来的“水晶球”在数学建模中预测类问题占了相当大的比重尤其是经济、金融、气象、电力负荷、销量预测等领域。时间序列预测就是基于变量自身的历史数据来预测其未来值。当你拿到一组按时间顺序排列的数据并需要预测接下来一段时间的发展趋势时时间序列模型就是你的核心工具。5.1 模型选择指南从简单到复杂时间序列模型众多选择哪一个取决于你数据的特征数据趋势明显长期来看持续上升或下降。可以考虑Holt-Winters 指数平滑能同时处理趋势和季节性或ARIMA 模型更通用但参数解释复杂。数据有季节性以固定周期如一年、一月、一周重复出现波动。季节性分解STL或季节性ARIMASARIMA是首选。数据波动大非线性传统线性模型拟合不好。可以尝试ProphetFacebook开源对缺失值和趋势变化鲁棒性强或LSTM神经网络能捕捉复杂长期依赖但需要大量数据且调参复杂。数据量小想快速出结果简单移动平均或指数平滑是不错的基线模型。对于数学建模竞赛SARIMA和Prophet是两个非常值得掌握的利器。SARIMA理论扎实能体现建模者的统计功底Prophet则像“黑盒”但效果往往不错且能提供直观的成分分解。5.2 代码实战用Prophet进行销量预测我们假设有一家商店过去3年的每日销量数据并预测未来90天的销量。Prophet对数据格式有固定要求两列分别命名为ds(日期时间) 和y(数值)。import pandas as pd import numpy as np from prophet import Prophet import matplotlib.pyplot as plt # 1. 生成模拟数据实际中从文件读取如 pd.read_csv np.random.seed(42) dates pd.date_range(start2021-01-01, end2023-12-31, freqD) # 构造一个包含趋势、季节性和噪声的序列 trend np.linspace(100, 200, len(dates)) # 线性上升趋势 # 年度季节性以365天为周期 yearly_seasonality 50 * np.sin(2 * np.pi * (dates.dayofyear / 365.25)) # 周季节性以7天为周期 weekly_seasonality 20 * np.sin(2 * np.pi * (dates.dayofweek / 7)) # 噪声 noise np.random.normal(0, 10, len(dates)) y trend yearly_seasonality weekly_seasonality noise # 确保没有负数销量不为负 y np.maximum(y, 0) df pd.DataFrame({ds: dates, y: y}) # 2. 创建并拟合Prophet模型 # 默认情况下Prophet会自动检测年度和星期季节性。 model Prophet( yearly_seasonalityTrue, # 开启年度季节性 weekly_seasonalityTrue, # 开启周季节性 daily_seasonalityFalse, # 日数据但预测日销量通常不开日季节性 seasonality_modeadditive # 加法模型趋势和季节性效应相加。如果季节性波动随趋势增大可设为multiplicative ) # 如果数据中有明显的假期效应可以在这里添加 # model.add_country_holidays(country_nameCN) # 例如添加中国节假日 model.fit(df) # 3. 构建未来时间框架并进行预测 future model.make_future_dataframe(periods90) # 预测未来90天 forecast model.predict(future) # 4. 查看预测结果的关键列 print(forecast[[ds, yhat, yhat_lower, yhat_upper]].tail()) # yhat: 预测值 # yhat_lower, yhat_upper: 预测区间的下界和上界默认80%置信区间 # 5. 绘制预测结果 fig1 model.plot(forecast) plt.title(销量预测) plt.xlabel(日期) plt.ylabel(销量) # 图中黑点历史数据。蓝色线预测值。浅蓝色区域置信区间。 # 6. 绘制时间序列的成分分解趋势、周季节性、年季节性 fig2 model.plot_components(forecast) plt.show() # 7. 可选性能评估在历史数据上计算误差 from sklearn.metrics import mean_absolute_error, mean_squared_error # 获取历史数据部分的预测值 historical_forecast forecast.loc[forecast[ds] df[ds].max()] mae mean_absolute_error(df[y], historical_forecast[yhat]) rmse np.sqrt(mean_squared_error(df[y], historical_forecast[yhat])) print(f\n模型在历史数据上的表现:) print(f 平均绝对误差 (MAE): {mae:.2f}) print(f 均方根误差 (RMSE): {rmse:.2f})Prophet实战心得数据质量Prophet对缺失值不敏感但异常值会影响趋势判断。建模前最好先进行简单的异常值检测和处理。季节性与假期yearly_seasonality和weekly_seasonality默认是autoProphet会根据数据自动判断是否包含。如果你确信有可以显式设为True。添加假期效应是Prophet的一大优势对于促销日、法定节假日等对销量有显著影响的日子一定要通过add_country_holidays或自定义假期列表加入模型。趋势变化点Prophet会自动检测时间序列中的趋势变化点例如产品突然变得畅销。你可以通过model.changepoints查看这些点。如果觉得自动检测不准也可以手动指定。不确定性区间预测结果中的yhat_lower和yhat_upper非常重要它们量化了预测的不确定性。在撰写论文时除了给出点预测一定要汇报这个区间这比只给一个数字严谨得多。模型评估永远不要在用于训练的数据上评估模型并沾沾自喜。应该将历史数据分为训练集和测试集例如用前80%的数据训练预测后20%并与真实值比较用测试集上的MAE、RMSE等指标来客观评估模型泛化能力。上面的示例代码只是在训练集上评估仅用于演示。与ARIMA对比Prophet优点在于全自动、对缺失值和趋势变化稳健、能方便地加入假期因素、结果可解释性强成分分解图。缺点在于它是一个加性模型对于乘性季节性的数据可能不适用且其底层是一个可加性回归模型对于特别复杂或高频率的数据可能不如LSTM等深度模型。ARIMA则需要更多的统计知识来识别模型参数 (p,d,q)但更灵活。在比赛中如果时间允许可以同时尝试两者对比结果。