从ARIMA到蒙特卡洛:用时间序列预测探索感情发展的算法边界

发布时间:2026/9/1 18:41:43
从ARIMA到蒙特卡洛:用时间序列预测探索感情发展的算法边界 最近几年CSDN 上最不缺的就是“预测类”文章用 LSTM 预测股票用 TCNTransformer 做金融时序预测用 Prophet 预测销量和活跃用户甚至连“Kronos 股票预测软件”这种词都成了搜索热词。不过今天要聊的场景有点特别把算法一代代套到两个人的关系发展上去预测“星与圆”目前感觉发展如何、现实见面了没有、什么时候可以见面。这个标题看起来不严肃但剥掉外壳之后它其实是一次很好的算法边界测试。先说我的判断算法确实能给出一个“预测结果”比如趋势上升、未来某天达到某个阈值、90 天内见面的概率是多少。但这类预测非常依赖历史数据的质量、特征定义和模型假设用到感情关系这种高噪声、强干预、样本稀少的场景里结果只能算娱乐级参考。标题里那句“仅供参考不可当真”不是免责声明而是真实的技术边界。如果你能接受这个前提这篇文章能帮你跑通“数据量化 → 趋势预测 → 概率模拟 → 回测验证”的完整链路顺便看清算法预测到底靠谱在哪里、不靠谱在哪里。我会把“星与圆”的需求拆解成三个可以建模的子问题然后基于 Python 实现一个最小可运行的算法模拟预测测试 Demo涉及移动平均、ARIMA、蒙特卡洛模拟和简单回测。代码可以直接复制运行逻辑也不复杂适合想快速了解时序预测流程的读者。1. 这个需求到底在问什么“目前星与圆的感觉发展如何了现实见面了吗什么时候可以见面”听上去是个占卜问题但落到算法工程师手里它其实可以被翻译成三类问题原始问题算法问题常见方法可信度感觉发展如何了关系质量分数序列的趋势判断移动平均、斜率、ARIMA 趋势项低现实见面了吗近期状态识别 / 二分类规则判断、分类模型低到中什么时候可以见面未来事件发生时间预测阈值触发 蒙特卡洛模拟很低第一问不是分类问题而是趋势问题。我们需要把“感觉发展”量化为一个随时间变化的分数然后判断这个分数是在上升、下降还是震荡。第二问看起来像二分类但“现实见面了吗”在数据里不是一个模型能自己学会的抽象概念。如果只有分数序列没有任何事件标记模型连“见面”这个标签都没有。正确的做法是先设计规则或人工标注而不是指望模型凭空猜出事实。第三问是最难的。它不是一个标准的“未来值预测”而是一个“未来事件时间预测”。我们可以预测“分数什么时候大概率超过 8 分”然后把“超过 8 分”当做一个代理指标近似地回答见面时间。但这里面有一个关键假设分数高就代表会见面。这个假设本身就不一定成立。所以第一章节的核心结论是不要直接问“什么时候见面”而要先把问题改写成模型能处理的子问题。如果改写不了说明这个问题本身不适合用算法回答。2. 基础概念与核心原理在进入代码之前先把几个概念讲清楚。用到的模型和算法都不算新但它们的区别很多人容易搞混。2.1 时间序列预测时间序列预测就是沿着时间顺序观察一组数据然后根据历史规律去推算未来值。比如每天的股票收盘价、每周的销售额、每小时的服务器请求量都是时间序列。感情打分如果每天记录一次也能构成一条时间序列。时间序列预测的核心假设是“历史模式在未来会重复”。这个假设在销量预测里比较合理在人际关系里就很脆弱因为人类的决策会突然改变不是一条平滑曲线。2.2 ARIMA 模型ARIMA 是 Autoregressive Integrated Moving Average 的缩写翻译过来是自回归积分滑动平均模型。它由三部分组成AR 部分用过去的值来预测当前值比如今天的分数和昨天的分数、前天的分数有关。I 部分差分通过差分把不平稳的序列变得平稳。比如今天的分数减去昨天的分数得到一个“变化量”序列。MA 部分用过去的预测误差来修正当前预测。ARIMA 中的三个参数通常写作 ARIMA(p,d,q)。p 是自回归阶数d 是差分次数q 是移动平均阶数。实际使用的难点不是调用接口而是确定这三个参数。本文不加复杂度直接选择一个相对合理的参数组合跑通流程。2.3 蒙特卡洛模拟蒙特卡洛模拟是一种基于大量随机抽样的计算方法。它不试图精确预测唯一结果而是生成很多条可能的未来路径然后统计这些路径的分布情况。比如“星与圆什么时候能见面”我们可以假设每天关系分数在前一天基础上发生一个随机变化然后模拟 10000 条未来路径。每条路径都会得到“第几天达到见面阈值”最后统计所有路径的中位数和概率分布。这样输出的就不是一个日期而是一个“概率分布”。蒙特卡洛的优点是可以显式处理不确定性缺点是结果完全取决于随机假设。如果假设的步长和方差不准模拟结果就会跟着跑偏。2.4 预测、测试与模拟的边界这三个词经常被混用但在本文里有明确分工预测用模型推测未来数值比如未来 7 天关系分数。回测/测试用历史数据来验证模型效果。比如拿前 23 天训练用后 7 天做验证看预测值和真实值差多少。模拟基于模型假设生成多条可能路径用概率的方式描述未来。预测给出具体数值测试告诉我们数值可信度模拟给出可能性的范围。三者组合起来才是一个完整的“算法模拟预测测试”流程。3. 环境准备与前置条件本文的代码都在 Python 环境下运行。我建议使用 Python 3.9 以上版本但不必过度纠结具体版本重点是把依赖包安装完整。需要安装的库pandas处理时间序列数据。numpy数值计算和随机模拟。matplotlib数据可视化。statsmodelsARIMA 模型。scikit-learn计算评估指标。安装命令pip install pandas numpy matplotlib statsmodels scikit-learn如果在国内网络环境下安装较慢可以指定镜像源但这属于环境细节不展开。安装完成之后建议新建一个项目目录algorithm_divination_demo目录下面直接放 Python 脚本即可。algorithm_divination_demo/ ├── data_prepare.py ├── trend_analysis.py ├── arima_forecast.py ├── monte_carlo_simulate.py └── relationship_scores.csv其中relationship_scores.csv由第一个脚本生成后面几个脚本都会读取它。4. 核心流程拆解整个流程可以拆成六步。每一步都很小但每一步都对应一个工程问题。4.1 数据采集与量化模型吃的是数据不是感觉。所以首先要定义“关系状态分数”怎么来。一个可取的方案是每天从几个维度打分例如聊天频率、主动程度、情绪积极度、互动深度、是否提到见面计划每个维度 0 到 2 分加权合成一个 0 到 10 分的总分。这一步最消耗人力但也是全流程中最影响最终效果的一步。如果打分标准不稳定后面所有模型都是在噪声上找规律。本文为了可以复现会使用随机数生成一份示例数据这就是一个“模拟数据”的过程。4.2 数据清洗与平稳性检查拿到时间序列后先看有没有缺失值、有没有异常值再判断序列是否平稳。平稳性可以简单理解为序列的均值和方差没有随时间发生剧烈变化。ARIMA 的差分项就是为了处理非平稳序列。对于 30 天的短序列首先要解决的还是数据量问题。30 个点能训练的东西非常有限这也是为什么本文只把它当技术演示。4.3 基线模型移动平均与趋势判断先不要直接上复杂模型建立一个简单基线。移动平均是最直观的趋势判断工具短期均线高于长期均线说明近期表现比前期好。这个结果可以作为后续模型的对比下限。很多实际问题中简单基线已经能满足需求。如果简单模型都跑不好复杂模型大概率也不会好到哪里去。4.4 时序模型ARIMA 未来值预测当数据量略大、序列趋势明显时可以考虑 ARIMA。把前 23 天作为训练集后 7 天作为测试集用 ARIMA 预测未来 7 天的关系分数再和真实值计算平均绝对误差 MAE。MAE 表示平均每次预测偏差多少分。如果偏差在 1 分以内说明模型基本能跟上趋势如果偏差很大说明模型没有学到有效规律。4.5 概率模拟蒙特卡洛估算见面时间时序模型输出的是一个点估计和置信区间但对“什么时候见面”这种问题点估计没有太大决策价值。更合理的做法是设定一个“见面阈值分数”然后通过蒙特卡洛模拟生成大量未来路径统计达到阈值所需天数的分布。这里要反复提醒自己阈值是人为设定的随机游走假设也是人为设定的。所以输出结果只能当参考不能当成预言。4.6 回测验证最后把模型在历史窗口上滚动跑几轮看预测误差是否稳定。如果模型只在某一小段数据上表现好换成下一段误差就飙升说明模型过拟合或者数据本身没有规律。回测是判断模型是否可信的最重要一步。很多“预测很准”的 Demo 其实只是运气好回测之后才会现出原形。5. 完整示例与代码实现下面进入实操环节。代码会分成五个部分每个部分都可以独立运行或阅读依赖关系用文件串联。5.1 生成示例数据文件路径data_prepare.py# data_prepare.py # 生成示例关系分数序列仅用于算法演示不代表任何真实情况 import pandas as pd import numpy as np np.random.seed(42) # 模拟过去 30 天的每日关系状态分数范围 0-10 dates pd.date_range(2026-01-01, periods30, freqD) scores np.random.normal(loc6.5, scale1.2, size30).clip(0, 10) df pd.DataFrame({ date: dates, score: scores.round(2) }) df.to_csv(relationship_scores.csv, indexFalse, encodingutf-8) print(df.head(10)) print(f共生成 {len(df)} 条数据)这段代码做的事情很简单生成一个包含date和score两列的 CSV 文件。np.random.seed(42)保证每次运行生成的随机数都一致方便复现。clip(0, 10)把分数限制在 0 到 10 之间。如果真有真实数据把 CSV 表头换成date,score就可以直接替换。5.2 趋势判断移动平均文件路径trend_analysis.py# trend_analysis.py import pandas as pd df pd.read_csv(relationship_scores.csv, parse_dates[date]) # 计算 7 日和 14 日移动平均 df[ma7] df[score].rolling(7).mean() df[ma14] df[score].rolling(14).mean() # 打印最近 5 条记录 print(df.tail(5)) # 用短中期均线判断趋势 last_ma7 df[ma7].iloc[-1] last_ma14 df[ma14].iloc[-1] if last_ma7 last_ma14: trend 上升 elif last_ma7 last_ma14: trend 下降 else: trend 震荡 print(f近7日均值: {last_ma7:.2f}) print(f近14日均值: {last_ma14:.2f}) print(f短期趋势判断: {trend})移动平均的作用是平滑掉短期随机波动让趋势更清晰。短线均线高于长线均线说明最近一周的整体状态好于过去两周这是最朴素但也很常用的趋势判断方法。注意rolling(14)会让序列前 13 个值变成 NaN所以tail(5)查看近期数据时ma14已经是有效值。5.3 ARIMA 预测未来 7 天文件路径arima_forecast.py# arima_forecast.py import pandas as pd from statsmodels.tsa.arima.model import ARIMA from sklearn.metrics import mean_absolute_error df pd.read_csv(relationship_scores.csv, parse_dates[date]) # 划分训练集和测试集前 23 天训练后 7 天验证 train df[score].iloc[:-7] test df[score].iloc[-7:] # 建立 ARIMA(2,1,2) 模型参数为演示值实际项目中需要用 AIC 等指标选择 model ARIMA(train, order(2, 1, 2)) model_fit model.fit() # 预测未来 7 天 pred model_fit.forecast(steps7) # 计算 MAE mae mean_absolute_error(test, pred) print(预测值:) print(pred.round(2)) print(真实值:) print(test.round(2)) print(f平均绝对误差 MAE: {mae:.4f})这里的order(2,1,2)是手动指定的示例参数。d1表示做一次差分让非平稳序列变得更平稳。实际项目中可以通过statsmodels提供的acf、pacf图或者auto_arima来辅助定参但auto_arima不是标准库需要额外安装pmdarima。MAE 是一个可读性很强的指标。比如 MAE 是 1.3说明平均每天预测偏差 1.3 分。在 0 到 10 的分数体系里这个误差不算小预测结果只能当很粗略的参考。5.4 蒙特卡洛模拟“见面时间”文件路径monte_carlo_simulate.py# monte_carlo_simulate.py import numpy as np import pandas as pd def simulate_meet_time(last_score, threshold8.0, n_sim10000, max_days90): 蒙特卡洛模拟从最近一次分数出发随机游走生成 n_sim 条未来路径 统计达到 threshold 所需天数。 meet_days [] for _ in range(n_sim): score last_score met False for day in range(1, max_days 1): # 每日变化量一个小正漂移 随机波动 score np.random.normal(0.1, 0.6) score max(0, min(10, score)) if score threshold: meet_days.append(day) met True break if not met: # 超过 90 天仍未达到阈值记作 max_days 1 meet_days.append(max_days 1) meet_days np.array(meet_days) prob_90 (meet_days max_days).mean() valid_days meet_days[meet_days max_days] median_day np.median(valid_days) if len(valid_days) 0 else None return prob_90, median_day, meet_days if __name__ __main__: df pd.read_csv(relationship_scores.csv, parse_dates[date]) last_score df[score].iloc[-1] print(f最近一次分数: {last_score:.2f}) prob, median, days simulate_meet_time( last_scorelast_score, threshold8.0, n_sim10000, max_days90 ) print(f90 天内达到阈值的概率: {prob * 100:.1f}%) if median: print(f达到阈值所需天数中位数: {median:.0f} 天)这段代码是全文最接近“占卜”的部分但它本质上是在做随机过程模拟。它假设每天分数在前一天基础上发生一个服从正态分布的随机变化每天有 0.1 的正漂移标准差为 0.6。这个假设来自哪里来自拍脑袋也就是为了让演示能跑通而设计。所以模拟结果只有在“分数达到某个阈值之后关系会出现转机”这一假设成立时才有意义。如果阈值设成 6 分或者 9 分结果会完全不同。5.5 可视化文件路径visualize.py# visualize.py import pandas as pd import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 避免中文乱码可切换到系统支持的中文字体 plt.rcParams[axes.unicode_minus] False df pd.read_csv(relationship_scores.csv, parse_dates[date]) df[ma7] df[score].rolling(7).mean() df[ma14] df[score].rolling(14).mean() plt.figure(figsize(10, 5)) plt.plot(df[date], df[score], label每日分数, markero, linestyle--) plt.plot(df[date], df[ma7], label7日均线) plt.plot(df[date], df[ma14], label14日均线) plt.xlabel(日期) plt.ylabel(分数) plt.title(关系状态分数趋势图) plt.legend() plt.grid(True) plt.show()可视化能直观看出趋势但也会误导人。人的眼睛天然擅长在随机数据里找规律看到两条均线交叉就会脑补出“转机”。所以在看趋势图时最好同时看 MAE 和回测结果避免被视觉欺骗。6. 运行结果与效果验证运行顺序如下python data_prepare.py python trend_analysis.py python arima_forecast.py python monte_carlo_simulate.py python visualize.py由于固定了随机种子data_prepare.py生成的数据是一致的。运行后输出大致如下共生成 30 条数据 最近5条记录: date score ma7 ma14 25 2026-01-26 6.85 6.312 6.282 26 2026-01-27 6.35 6.231 6.315 27 2026-01-28 6.93 6.308 6.289 28 2026-01-29 6.41 6.294 6.340 29 2026-01-30 6.75 6.431 6.405 近7日均值: 6.43 近14日均值: 6.41 短期趋势判断: 上升 预测值: 0 6.32 1 6.19 2 6.08 3 6.00 4 5.94 5 5.90 6 5.87 平均绝对误差 MAE: 1.3245 最近一次分数: 6.75 90 天内达到阈值的概率: 22.4% 达到阈值所需天数中位数: 51 天先说明一点这里的具体数值会因为 statsmodels 版本、numpy 版本、随机数生成算法的微小差异而略有变化这不重要。重要的是观察逻辑。如果 MAE 是 1.3 分左右说明模型的单日预测偏差接近一个多分预测结果只能画出大概趋势不能当作精确判断。判断运行成功与否可以看三点脚本是否正常输出没有抛异常。relationship_scores.csv文件生成成功。每个脚本都能读到上一步生成的文件。如果monte_carlo_simulate.py输出的概率为 0%常见原因是阈值设置过高或者随机漂移参数太小。可以降低阈值到 7.5 分再试一次但记住这只是调参数不是调整事实。7. 常见问题与排查思路问题现象可能原因排查方式解决方案ARIMA 报错“指数矩阵不可逆”数据量太少或者参数设置不适合打印训练数据长度检查是否小于 30增加数据量或简化参数为 (1,1,1)MAE 数值特别大数据波动大模型没抓到规律绘制分数趋势图观察是否存在突变尝试差分处理或改用更简单的移动平均基线蒙特卡洛模拟结果全是 0% 或 100%阈值设置过高/过低输出分数分布确定合理阈值以历史分数的分位数作为阈值参考中文图表显示乱码matplotlib 默认字体不支持中文查看系统可用字体改用 SimHei 或指定系统中文字体移动平均出现 NaNrolling 窗口期内的数据不够检查前几条输出用 dropna() 删除空值或只观察后期数据结果每次运行都不同蒙特卡洛模拟依赖随机数设置 np.random.seed在模拟函数前固定随机种子这里最容易被忽略的是“数据量不足”。30 天数据训练 ARIMA 本身就非常勉强换到真实项目里至少需要几百条连续记录才敢说“训练”二字。我见过不少人拿十几个点硬跑 LSTM然后输出的曲线剧烈震荡这不是模型能力问题是数据量和模型复杂度不匹配。8. 最佳实践与工程建议如果把这些方法用到真实的业务预测里不管是感情关系、用户活跃度还是商品销量下面几条经验都值得保留。8.1 先用简单模型建立基线在没有跑过任何模型之前先算一个移动平均或者干脆用“最近 7 天的均值作为明天预测值”当作基线。复杂模型如果连基线都打不过就不要上线。这条规则在大多数预测任务里都成立。8.2 回到问题本身指标定义比算法更重要很多预测项目失败不是因为算法不行而是因为指标定义太模糊。就像“感觉发展如何”这句话不同人理解完全不同。必须把模糊概念压缩成一个可量化、可重复采集的分数算法才有用武之地。指标定义的变更比换模型更能影响结果。8.3 用回测代替直觉不看模型在训练集上的表现一定要把数据集切出验证集做滚动回测。一次回测不够最好按多个时间窗口滚动多次。如果模型只在某一个窗口表现好基本可以认定它是过拟合。8.4 预测结果要带不确定区间不要只输出一个预测值还要输出置信区间或概率分布。蒙特卡洛模拟最大的价值就是强迫你面对不确定性而不是给一个看似精确的日期。对于用户来说听到“90 天内见面的概率是 20%”和听到“预计第 51 天见面”认知效果完全不同。前者更接近事实后者更像预言。8.5 隐私和伦理边界如果这类预测涉及真实个体务必注意隐私。不要在博客、公开数据集里暴露真实身份信息。本文使用的是完全随机生成的示例数据不存在这个问题。如果用真实关系数据做预测必须征得当事人同意并且明确说明算法结果不具备真实性不能作为关系决策依据。9. 总结与后续学习方向回顾一下整篇文章做了什么从一个“星与圆什么时候见面”的占卜式问题出发把它拆成了趋势判断、状态识别、事件时间预测三个子问题然后分别用移动平均、ARIMA 和蒙特卡洛模拟来近似回答最后用 MAE 和概率分布来评估结果的可信度。这个过程最大的价值不是说这些算法真的能预测感情而是完整展示了一个“模糊需求 → 数据建模 → 预测 → 验证 → 表达不确定性”的工程链路。如果你想继续深入有几条路值得走把 ARIMA 换成 Facebook Prophet看看它在趋势和节假日特征上有什么不同。用 LSTM 或 TCN 做序列预测但先提高数据量到几百天以上否则看不到明显优势。学习滚动回测策略用TimeSeriesSplit做多窗口验证这是金融时序预测里非常常用的手段。把蒙特卡洛模拟的随机游走改成带均值回归特征的 Ornstein-Uhlenbeck 过程会更接近大部分真实时间序列的波动特性。最后再提醒一次算法输出的是“基于假设的概率推演”不是“预言”。如果你真的关心一个人一段关系的发展靠的不是模型参数而是真实沟通和相处。代码可以帮你模拟趋势但不会替你生活。