ARTICLE DETAIL

资讯详情

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

基于LLM修正的时间序列销量预测:从ARIMA到大模型实战

基于LLM修正的时间序列销量预测:从ARIMA到大模型实战 简介这套Python大模型汽车销量预测项目代码包面向数据分析与机器学习学习者、新能源汽车市场研究人员提供从数据采集、清洗、存储到建模与可视化的完整实现。项目融合网络爬虫与GBDT、ARIMA等模型涉及requests、BeautifulSoup、Pandas、Scikit-learn等常用库并通过ECharts完成交互式可视化适合希望掌握真实业务预测流程的读者也便于二次开发。包内共14个文件以8个Python脚本为核心分别对应爬虫、数据分析、模型构建、可视化等模块另含依赖清单、说明文档、示例数据集、前端页面模板及TODO清单整包仅27KB结构紧凑易于通读。目前已有79人学习资源按analysis、crawler、models等目录清晰划分便于按模块对照理解从爬虫采集到模型评估再到可视化展示可快速复现新能源汽车销量预测的研究思路也可作为课程设计或竞赛项目的参考框架。1. 选大模型做汽车销量预测最直接的动机是“拐点”预测总打脸先交代一下背景。上季度我接到一个汽车品牌的销量预测任务数据范围覆盖三年多的月度销量、经销商库存、促销费用、车型换代节点还有大量新闻稿、行业报告和社交媒体讨论。最初我用的是传统套路ARIMA做基线XGBoost加特征工程前几期效果还算能看但一到“新车型上市”“补贴政策落地”这种拐点月份预测值几乎必然偏离实际值30%以上。原因不难理解——传统时序模型本质上是在用历史规律外推一旦市场进入它没见过的新状态它没有任何认知能力判断“新车型上市是利好还是利空”。所以我把目光转向大模型。我的思路不是让LLM直接取代统计模型而是让它承担**“事件解读员”**的角色把最近的新闻、政策、促销活动、竞品动态喂给模型让它在统计模型预测值的基础上给出修正。说白了传统模型负责算“如果没有突发情况销量大概是多少”大模型负责判断“这些突发情况会带来多少增量或减量”。这套思路跑通之后效果比我预想的好。季度预测的MAPE从18%降到9%出头拐点月份的单期偏差缩小了一半多。这篇文章把完整的项目代码、Prompt设计思路和踩坑过程拆开讲适合已经会基础Python、需要一个可落地方案的读者参考。项目不算复杂但里面有几个细节不亲自跑一遍大概率会卡住。2. 数据准备汽车销量数据比想象中更“脏”这步直接决定上限先说清楚大模型再聪明喂给它的数据是垃圾输出也好不到哪去。汽车销量预测的数据准备有三块结构化销量数据、外部事件文本、以及把这两者拼起来的“对齐工程”。2.1 需要整理的字段和来源我不建议一上来就追求面面俱到核心字段就这几类销量序列按月、按车型或品牌聚合的销量数量至少要有24个月以上历史太短模型没什么可学的。促销与价格信号平均折扣率、促销活动天数、金融贴息政策起止时间这些在汽车行业对销量的影响权重极高。供给端信号经销商库存深度、工厂排产计划、芯片或零部件供应状态。2023年前后的“缺芯”就是一个典型的供给冲击传统模型很难捕捉。外部文本新车上市公告、召回公告、补贴政策、行业销量快报以及主流汽车论坛、社交平台的热议话题。文本不需要多每个月挑36条有信息量的就行。数据仓库一般能直接导出前两类后两类需要手动收集。刚开始我也觉得很麻烦后来简化成“每月固定收集官方公告行业协会月报摘要”成本可控收益明显。2.2 清洗和特征构造的几个关键动作在做特征工程时有这么几个点不处理会连环翻车第一节假日挤兑问题。汽车销量有典型的季末冲量效应——3月、6月、9月、12月厂商为了报表数据好看会给经销商压库导致销量脉冲。我按自然月做聚合时专门增加了一个month_end_flag字段标记当月是否处在季末月份。第二滞后特征别贪多。XGBoost时代我习惯堆lag_1到lag_12但这套东西喂给LLM反而会让上下文变得冗长影响模型对关键信息的注意力。我最后只保留了lag_1、lag_3、lag_6和同比变化率效果最好。第三把表格转成句子。这是整个项目里最容易被忽略的一步。大模型不擅长直接“读”二维表格尤其当数值不带单位、没有语义上下文时它很难理解数字背后的经营含义。我构造了一个build_context_text()函数把近12个月的销量、库存、折扣率翻译成一段带语义的文本比如“2024年6月品牌A销量为12600辆同比下降4.2%环比上升12.0%库存深度为1.4个月处于季节性高位”。模型读这种文本比读JSON数组靠谱得多。2.3 文本清洗的坑官方公告不等于有效信号刚开始我直接把新闻标题塞给大模型结果发现噪音巨大。比如某个月份某车型因为OTA升级上了热搜标题看得很热闹但对当月销量几乎没有影响。后来我做了两个修正一是要求文本消息必须带有“影响方向”和“影响强度”这两个显式标签从源头筛选二是对输入文本做长度截断单条不超过300字每个月不超过6条宁缺毋滥。下面是构造上下文的核心代码import pandas as pd from datetime import datetime def build_context_text( sales_df: pd.DataFrame, events: list[dict], target_month: str ) - str: 把结构化销量数据和外部事件文本拼成一段适合喂给LLM的上下文。 sales_df: 按月聚合的销量、库存、折扣率数据 events: [{date, title, impact_direction, impact_level, summary}] target_month: 要预测的目标月份格式YYYY-MM # 截取目标月之前的12个月作为历史窗口 hist_df sales_df[sales_df[month] target_month].tail(12) lines [] for _, row in hist_df.iterrows(): line ( f{row[month]}{row[model_name]}销量为{row[sales]}辆 f同比增长{row[yoy_growth]}%环比增长{row[mom_growth]}% f库存深度{row[inventory_months]}个月 f平均折扣率{row[discount_rate]}% ) lines.append(line) context \n.join(lines) context \n\n# 外部事件信息\n for ev in events: context ( f- [{ev[month]}] {ev[title]} f影响方向{ev[impact_direction]} f影响强度{ev[impact_level]} f摘要{ev[summary]}\n ) return context这段代码不复杂但target_month之前12个月这个窗口长度是我反复试出来的太短模型看不到季节性太长上下文里塞满无关信息反而稀释了关键事件的影响权重。3. 技术选型ARIMA、GBDT和大模型各自的角色如何分工做这行的人容易走两个极端要么觉得大模型无所不能想用一张API把所有预测都解决要么觉得大模型不可靠碰都不碰。我的观点是把这三类模型放在正确的层次上各自干擅长的事比争论谁更强重要得多。3.1 三类模型的定位差异模型强项弱项在本项目里的角色ARIMA/Prophet周期、趋势、季节性的捕捉稳定且可解释对事件冲击、新变量无感知提供基线预测值XGBoost/LightGBM能融合多维结构化特征拟合能力强对文本、语义信息束手无策可另做一路预测用于交叉验证大模型能理解文本事件、市场情绪、政策含义数值精度差爱“编”数据负责事件驱动的修正项这里关键不是“哪个最好”而是“每一层都别越界”。我让ARIMA老老实实算季节性和趋势外推让大模型做残差修正这样即使大模型给了一个夸张的修正幅度统计模型打过底整体预测也不会飞出天际。3.2 大模型的选择API调用还是本地部署这个项目里我采用的是通过API调用一个开源大模型的服务接口方式好处是代码侵入性小、不需要管GPU资源跑一次预测十几秒出结果。如果你出于数据隐私或成本考虑也可以改成本地部署方案比如用Ollama拉起一个量化版模型接口协议基本都是OpenAI兼容的改一个base_url就能切换下面代码里会给出具体注释。有人说大模型幻觉太多不适合做严肃预测。我的做法是从架构上不给它幻觉的机会——它只输出一个修正系数而不是让它自由发挥写一段分析。修正系数的范围被我限制在0.7到1.3之间超出范围就视为无效输出回退到统计模型的结果。这样即使模型抽风它也影响不了大局。import json from openai import OpenAI client OpenAI( api_keyyour-api-key, # 换成你自己的Key base_urlhttps://your-llm-api-endpoint/v1, # 换成服务商地址 ) def ask_llm_correction(context_text: str, base_forecast: float) - float: 让大模型基于历史销量、事件文本和基线预测值输出一个修正系数。 返回修正后的销量预测值。 prompt f 你是资深汽车行业市场分析师。请根据以下历史销量、外部事件和基线预测判断基线预测是否合理。 你的任务只有一个输出一个修正系数表示你对该月份销量相对于基线预测的看法。 规则 1. 修正系数范围0.7到1.3。1.0表示认同基线预测。 2. 只输出JSON不要输出任何解释不要输出markdown代码块。 3. JSON格式{correction_factor: 1.05} 历史销量和外部事件 {context_text} 基线预测值{base_forecast}辆 请输出修正系数 try: resp client.chat.completions.create( modelyour-model-name, messages[{role: user, content: prompt}], temperature0.2, max_tokens100, response_format{type: json_object}, ) content resp.choices[0].message.content.strip() # 防御如果模型在JSON外裹了json标签剥离掉 if content.startswith(): content content.replace(json, ).replace(, ).strip() factor json.loads(content)[correction_factor] factor max(0.7, min(1.3, factor)) # 硬性限制修正幅度 return factor * base_forecast except Exception as e: print(fLLM调用失败回退基线预测值{e}) return base_forecast3.3 为什么修正系数比直接生成预测值好直接让模型输出“预测销量是多少”模型倾向于给出一个绝对值哪怕它根本不知道这个品牌的量级。但修正系数是相对值模型只需要判断“这个基线高还是低、偏了多少”任务难度大幅降低准确性自然上来了。这是我尝试了几版Prompt之后最大的心得——别让模型做数值计算让它做方向判断。这个思路在工程上还有一个好处即使未来统计基线的算法换了比如从ARIMA换成Prophet或者干脆用GradientBoosting大模型修正层完全不用改输入输出接口不变。项目扩展性一下子就好了很多。4. 核心代码完整预测流程从数据到结果的串联方式这一节给出一个能直接跑的最小完整项目。实际项目中我封装成了几个类但核心逻辑全在这里看懂这段串联剩下都是体力活。4.1 用ARIMA跑出基线预测这里用了statsmodels库选阶方式用简单的AIC网格搜索没有做太复杂的调参。月度数据天然存在季节性所以order(1,1,1)seasonal_order(1,1,1,12)作为起点基本够用。import pandas as pd from statsmodels.tsa.statespace.sarimax import SARIMAX def run_baseline_forecast(sales_df: pd.DataFrame, target_month: str) - float: 基于历史销量序列用SARIMA预测目标月份的基线销量。 返回预测值Float。 # 需要至少24个月历史数据 hist_df sales_df[sales_df[month] target_month] ts hist_df.set_index(month)[sales].astype(float) try: # 网格搜索AIC最小阶数减少手工调参 best_aic float(inf) best_order None for p in range(0, 3): for q in range(0, 3): for ps in range(0, 2): for qs in range(0, 2): try: model SARIMAX( ts, order(p, 1, q), seasonal_order(ps, 1, qs, 12), enforce_stationarityFalse, enforce_invertibilityFalse, ).fit(dispFalse) except Exception: continue if model.aic best_aic: best_aic model.aic best_order (p, 1, q, ps, 1, qs) p, _, q, ps, _, qs best_order final_model SARIMAX( ts, order(p, 1, q), seasonal_order(ps, 1, qs, 12), ).fit(dispFalse) forecast final_model.forecast(steps1).iloc[0] return max(0, forecast) # 销量不可能为负 except Exception as e: print(fSARIMA模型拟合失败改用最近三个月均值{e}) return float(ts.tail(3).mean())这段代码里最值得说的反而是那个try-except回退逻辑。实际跑的时候我遇到过数据量不够导致SARIMA直接抛矩阵奇异异常的情况如果没有这层兜底整个批处理流程会中断。销量预测项目通常要跑多个车型、多个品牌的预测一天跑几百个模型任何一个抛异常都会打断流水线。所以生产环境的预测代码异常兜底和模型本身同样重要。4.2 完整流程串联从历史数据到最终预测值正常情况下流程是这样读历史数据 → 整理外部事件文本 → SARIMA出基线 → 构造上下文文本 → 大模型出修正系数 → 计算最终值 → 可视化对比。完整代码如下def predict_month_sales( sales_df: pd.DataFrame, events: list[dict], target_month: str ) - dict: 完整预测流程返回基线预测、LLM修正后的最终预测以及关键中间信息。 # 1. 基线预测 base_value run_baseline_forecast(sales_df, target_month) # 2. 构造上下文文本 context_text build_context_text(sales_df, events, target_month) # 3. LLM修正 final_value ask_llm_correction(context_text, base_value) # 4. 返回结果 return { target_month: target_month, base_forecast: round(base_value, 0), llm_corrected_forecast: round(final_value, 0), correction_ratio: round(final_value / base_value, 3) if base_value else None, } # 使用示例 if __name__ __main__: # 模拟一份销量数据真实场景应读CSV或数据库 sales_df pd.read_csv(monthly_sales.csv, parse_dates[month]) events [ { month: 2025-01, title: 国家推出汽车以旧换新补贴政策, impact_direction: positive, impact_level: high, summary: 对符合条件的老旧车辆置换给予1-2万元补贴预计刺激一季度销量。, }, ] result predict_month_sales(sales_df, events, target_month2025-02) print(result)整个主流程控制在60行以内我觉得这是比较合理的代码量。如果代码超过200行大概率是做了太多“锦上添花”的事对预测准确率没有帮助。4.3 另一个可选方案用本地大模型替代API如果你不想把销量数据发到外部API用Ollama跑本地模型也很方便。流程几乎不变只改一个客户端初始化from openai import OpenAI client OpenAI( base_urlhttp://localhost:11434/v1, # Ollama 默认服务地址 api_keyollama, # Ollama 本地服务不带鉴权占位即可 )提示词完全复用只是model参数要改成你本地pull下来的模型名比如qwen3:8b或者llama3.1:8b。实测下来本地7B~8B量化模型在“判断事件影响方向”这件事上表现不输大型API模型但在“修正系数数值精度”上会逊色一些所以建议把温度调低到0.1并保留修正系数的上限下限。如果只是做月度预测这种低频任务本地部署还多了一个好处没有token费用试Prompt不用心疼钱。5. 实测效果对比和踩坑记录比你预想中更容易翻车的地方项目上线后我对过去12个月的历史月份做了回测同时对比了三种策略纯SARIMA、纯XGBoost、SARIMALLM修正。5.1 回测效果对比策略整体MAPE拐点月份MAPE最大单期偏差纯SARIMA18.3%31.5%43.2%纯XGBoost含促销、库存等特征16.8%27.9%38.6%SARIMA LLM修正9.6%12.7%21.4%这个结果符合我对这个项目的预期定位LLM修正对普通月份的帮助并不大但对拐点月份——也就是外部事件比较密集的月份——效果极其显著。看整体MAPE可能只是从18%降到9%看着“也就那样”但拐点月份的误差从30%以上压到13%以内对一个需要做备货和排产的团队来说差距是“断货”和“压库存”的区别。5.2 三个坑预计每个新手都会踩到坑一模型对时间的感知是盲区。有一次我把上下文文本喂给LLM里面只写“去年销量为12000辆”模型根本判断不了“去年”是哪一年导致它把季度性完全搞错。后来所有时间信息一律用具体月份比如“2024-06”不用任何相对时间词。坑二温度参数一定要压低。默认的温度是0.7或1.0在大模型做文本创作时没问题但在做数值判断时0.7摄氏度的“发散”会导致同一份输入反复调用得到完全不同的修正系数。我实测过温度从1.0降到0.2修正系数的标准差从0.09降到了0.02稳定性肉眼可见地提升。坑三外部事件文本的时效性过滤。我把前几个月的新闻忘删了导致某品牌已经涨过的销量又被重复计入模型给了一个偏高的修正系数。解决办法很简单每个月只保留当月和上个月的事件三个月前的直接不喂给模型。5.3 我对这套方案适用边界的一些体会这套“统计模型基线大模型残差修正”的架构最适合的是那些外部环境变化频繁、传统模型经常失效的场景除了汽车销量消费电子、家电、药品集采后的市场都类似。但反过来如果你预测的是高度稳定的产品线比如某品牌经典车型月销量常年波动在5%以内那加一层LLM只会引入更多方差没必要。再提醒一句LLM调用是异步的如果预测任务很多建议用异步调用或者并发批处理否则几百个车型逐个调API等的时间会让人崩溃。我后来用asyncio把并发量提到20路整个批处理时间缩小到原来的十分之一。最后说说我个人的一点体会。做这个项目之前我一度觉得大模型在严肃数值预测领域是“花架子”但跑通之后我改变了看法。它的价值不在于算得准而在于它把文本、新闻、政策这类原本只能靠人工经验判断的信号变成了可量化、可复现的输入。只要给它划好边界让它做它擅长的方向判断不做它不擅长的数值计算这套组合拳的可靠度非常高。后面我打算把行业研报摘要也做成自动抓取和摘要进一步减少手动整理事件文本的工作量。本文还有配套的精品资源点击获取
返回列表