当前位置: 首页 > news >正文

Python股票价格预测实战包:随机森林多版本代码+全市场行情数据+逐行中文注释

本文还有配套的精品资源,点击获取

简介:直接运行就能上手的股票价格预测练习项目,用scikit-learn里的随机森林回归(RFR)建模,提供三个不同颗粒度的训练脚本:main_RFR.py(单只股票)、main_RFR_all.py(全市场统一特征)、main_RFR_all_2.py(增强版特征组合)。配套真实整理好的A股历史行情CSV文件data_all.csv,get_data.py辅助你本地补充或清洗数据。所有代码都带清晰中文注释,从数据加载、缺失值处理、技术指标构造(如MA、RSI、MACD等基础特征)、标准化、交叉验证到模型评估(MAE、RMSE、R²)全部分步写明。requirements.txt和packages.txt双清单锁定依赖版本,PyCharm、VS Code等主流IDE开箱导入即用,.gitignore和配置文件已预置,适合课程设计、毕业课题或自学机器学习落地流程。

1. 这不是“预测股价”,而是教你用随机森林理解市场波动的底层逻辑

你点开这个标题,大概率是被“股票预测”四个字吸引来的——但我要先泼一盆冷水:没有任何一个负责任的从业者会告诉你,这套代码能帮你稳定赚钱。它真正的价值,远比“猜明天涨跌”深刻得多:它是一套完整、可触摸、可调试的“市场行为解剖工具包”。我带过十几届金融工程和计算机专业的毕业设计,每年都有学生拿着K线图问:“老师,模型说下周要涨,我能信吗?”我的回答永远是:“先别急着下单,先把main_RFR.py里第87行的feature_importances_打印出来,告诉我,到底是哪个指标在驱动这个‘涨’的判断?是5日均线的斜率?还是RSI从30反弹到45的幅度?还是成交量突然放大2.3倍?”——这才是这个项目最硬核的部分。

它用scikit-learn里的随机森林回归(RFR)作为载体,不是因为RFR是“最强算法”,而是因为它像一台透明的X光机:你能清晰看到每个技术指标(MA、RSI、MACD、布林带宽度、换手率变化率……)对最终价格变动的贡献权重;你能亲手调整滑动窗口长度、改变特征组合方式、对比不同交叉验证策略对模型稳定性的影响;你甚至能故意把某只股票的收盘价列删掉,看看模型在“只看量、不看价”的情况下,还能不能捕捉到资金流向的蛛丝马迹。这种“可解释性+可干预性”,恰恰是LSTM或Transformer这类黑箱模型最难提供的。

关键词里反复出现的“随机森林”“Python机器学习”“sklearn”,不是堆砌术语,而是锚定了整个项目的实操坐标系:它不讲抽象理论,所有操作都在pandas DataFrame里发生,所有模型都在sklearn的fit()和predict()方法中完成,所有评估指标都来自sklearn.metrics的现成函数。你不需要懂决策树的Gini不纯度公式,但你要明白,为什么把max_depth设为12比设为5更抗过拟合,为什么用TimeSeriesSplit做交叉验证比用普通的KFold更符合金融数据的时间序列特性。配套的data_all.csv不是随便抓取的行情快照,而是经过清洗的A股全市场日频数据(含沪深主板、创业板、科创板),字段覆盖了open/high/low/close/volume/amount,时间跨度足够跑通一个完整的回测周期。而三个主脚本——main_RFR.py、main_RFR_all.py、main_RFR_all_2.py——本质上是你在不同建模阶段的“思维快照”:从单只股票的精细化调参(适合课程作业),到全市场统一特征的批量训练(适合毕业设计),再到引入滞后项与交互特征的增强版(适合想深挖的同学)。这不是一个“运行就出结果”的魔法盒子,而是一张标好了每条岔路、每个路标、每处坑洼的实操地图。如果你刚学完《机器学习实战》前五章,或者正在为毕业设计发愁找不到落地项目,又或者想摆脱“调包侠”身份真正理解特征工程怎么影响模型输出——那这个包,就是为你量身定做的第一块真实业务场景垫脚石。

2. 项目整体设计思路拆解:为什么选随机森林?为什么是这三个脚本?

2.1 随机森林不是“万能钥匙”,而是最适合初学者建立直觉的“教学型模型”

很多人一上来就想用LSTM或XGBoost做股票预测,这就像学游泳先跳进深水区。而随机森林(RFR)之所以成为这个项目的基石,核心在于它完美平衡了可理解性、鲁棒性与工程友好性三大维度,而这三点对入门者至关重要。

首先看可理解性。RFR由上百棵决策树组成,每棵树的分裂逻辑都基于某个特征的阈值判断(比如“如果RSI < 35,则向左子树走”)。sklearn提供feature_importances_属性,能直接告诉你:在全部树的分裂中,“RSI”这个特征被用来做判断的总次数占所有特征分裂次数的百分比。我实测过data_all.csv里贵州茅台的数据,发现“5日收盘价均值与20日均值的差值”这一特征重要性常年排前三——这背后反映的是短期趋势与中期趋势的背离强度,是技术分析里“金叉死叉”的量化表达。这种直观的因果链条,是神经网络的权重矩阵永远无法提供的。你可以一边跑main_RFR.py,一边修改features_list = [‘ma5’, ‘rsi’, ‘macd_diff’]这行代码,删掉rsi再跑一次,立刻看到R²下降0.07,从而真切体会到RSI在当前模型中的实际价值。

其次是鲁棒性。股票数据充满噪声:财报暴雷导致单日暴跌、政策利好引发脉冲式上涨、流动性枯竭造成的异常低成交量……这些都会让线性模型瞬间失效。而RFR通过bagging(自助采样)和feature subsampling(特征随机子集)天然具备抗噪能力。举个例子:在main_RFR_all.py里,我们用全市场3000+只股票的数据训练一个统一模型。其中某只ST股因退市风险连续跌停,它的价格序列会严重拖累整体MSE。但RFR在构建每棵树时,只随机抽取约2/3的样本(bootstrap sample),且每次分裂只考虑sqrt(n_features)个特征,这就保证了绝大多数树不会被这一个极端样本主导。实测下来,在剔除ST股后,模型在测试集上的RMSE仅下降1.2%,而普通线性回归则波动超过8%。这种“不把鸡蛋放在一个篮子里”的哲学,正是金融建模的第一课。

最后是工程友好性。sklearn的RFR接口极度简洁:初始化、fit、predict三步走,没有复杂的超参数需要实时调优。对比XGBoost,你不需要纠结learning_rate、gamma、min_child_weight这些概念;对比LSTM,你不用处理序列填充、状态保持、梯度消失等底层问题。requirements.txt里锁定的scikit-learn==1.3.0版本,确保你在PyCharm里右键Run,不会因为版本兼容问题卡在import sklearn.ensemble那一行。这种“所见即所得”的确定性,对正在赶DDL的学生或自学新手而言,节省的不是几小时调试时间,而是避免因环境问题产生的挫败感——毕竟,第一次成功跑出R²=0.62的那个下午,带来的信心远胜于读十页公式推导。

2.2 三个脚本的本质:对应三种建模认知阶段的“思维脚手架”

这三个.py文件绝非简单复制粘贴的产物,它们是按认知难度递进设计的“思维阶梯”。

main_RFR.py 是“单点突破”模式,目标是让你彻底吃透一只股票的建模全流程。它默认加载贵州茅台(600519)的数据,所有操作都围绕这只股票展开:从读取CSV、构造10个基础技术指标(MA5/MA10/MA20、RSI(14)、MACD(12,26,9)、布林带上轨/中轨/下轨、ATR(14)、换手率),到处理缺失值(用前向填充而非插值,因为价格序列具有强时间依赖性),再到标准化(用StandardScaler而非MinMaxScaler,因为金融数据常有长尾分布,后者会压缩异常值影响)。最关键的细节在第124行:它使用TimeSeriesSplit进行5折交叉验证,且每折的训练集严格早于测试集——这是防止未来信息泄露的生命线。很多初学者用普通KFold,结果模型在测试集上R²高达0.85,一到实盘就归零,根源就在这里。这个脚本就像一辆手动挡教练车,离合、油门、档位都暴露在外,逼你亲手感受每个操作的物理反馈。

main_RFR_all.py 是“横向扩展”模式,解决“如何把单只股票的经验规模化”。它不再针对个股,而是遍历data_all.csv里所有股票代码,对每只股票独立构造相同的特征集,然后将全部样本合并成一个巨型DataFrame。这里有个精妙的设计:特征构造函数create_features()被封装成独立模块,确保3000只股票用完全一致的逻辑计算MA、RSI等指标。但挑战随之而来——不同股票的上市时间、停牌历史、数据完整性差异巨大。脚本在第98行做了智能过滤:自动剔除上市不足1年的股票(避免冷启动问题),并用np.nanmean()计算全市场特征均值时,自动忽略各股的NaN值。更关键的是,它引入了“行业哑变量”:从股票代码映射到申万一级行业(如“食品饮料”“电力设备”),再用pd.get_dummies()转为独热编码。这样模型就能学到“同行业股票对同一宏观事件(如消费刺激政策)的响应相似性”。这已经触及了多因子模型的雏形,但实现起来只需增加12行代码。

main_RFR_all_2.py 是“纵深挖掘”模式,聚焦于特征工程的升维。它在main_RFR_all.py基础上,增加了三类高阶特征:一是滞后项(Lag Features),比如price_lag1(昨日收盘价)、volume_lag5(5日前成交量),捕捉价格惯性;二是滚动统计量(Rolling Statistics),如过去20日收益率的标准差(volatility_20)、成交量的滚动均值比(volume_ma5/volume_ma20),刻画波动率与量能变化;三是交互特征(Interaction Features),典型如rsi * macd_diff,试图捕捉“超卖状态下MACD金叉”的复合信号。这些特征并非凭空想象,而是源于经典技术分析著作《期货市场技术分析》里的“多重确认原则”。脚本第156行特意注释:“此处交互特征需谨慎,过多会导致维度灾难,建议先用SelectKBest筛选Top20”。这提醒你:特征工程不是堆砌,而是有理论依据的减法艺术。

这三个脚本共同构成了一条清晰的学习路径:从理解单只股票的“树木”,到把握全市场的“森林”,再到洞察市场机制的“土壤层”。你不必一次性跑完全部,完全可以先搞定main_RFR.py,再带着问题去读另外两个——这才是项目设计的真正用心所在。

3. 核心细节解析与实操要点:从数据清洗到特征构造的魔鬼细节

3.1 data_all.csv的真相:它不是“原始数据”,而是经过三次清洗的“教学友好型数据集”

很多同学拿到data_all.csv第一反应是:“哇,全市场数据,直接用!”——这恰恰是最大的陷阱。这份CSV表面平静,内里暗藏玄机。我来带你一层层揭开它的清洗逻辑,因为理解数据怎么来的,比知道怎么用它更重要

首先明确一点:data_all.csv不是从Wind或Tushare实时爬取的原始快照,而是基于2018-2023年A股日频行情,经过三轮人工校验的产物。第一轮是基础字段对齐。原始数据源包含上交所、深交所、北交所三套独立系统,字段命名混乱:上交所用“CLOSE”表示收盘价,深交所用“close”,北交所用“last_price”;成交量单位也不统一(上交所是“手”,深交所是“股”,北交所是“股”但需除以100)。脚本get_data.py的第42行执行了标准化映射:

# get_data.py 第42行 raw_cols = {'CLOSE': 'close', 'close': 'close', 'last_price': 'close', 'VOLUME': 'volume', 'volume': 'volume', 'deal_amount': 'amount'} df = df.rename(columns=raw_cols)

这确保了无论来源,最终CSV里只有标准的close/volume/amount字段。

第二轮是异常值熔断。A股存在大量“乌龙指”事件:2020年某券商系统故障导致某股票1分钟内成交上万手,价格瞬间偏离30%。这类数据若直接进入模型,会严重扭曲特征分布。我们在清洗时设置了三重熔断机制:一是单日涨跌幅熔断(第68行),剔除当日涨跌幅>15%的记录(排除ST股及新股首日);二是量价背离熔断(第75行),当volume > 前5日均值5 且 close变化率 < 1%时,标记为可疑,人工复核后剔除;三是空值连续性熔断*(第82行),对任意股票,若连续10个交易日的close为空,则整段数据截断——因为这通常意味着长期停牌,后续复牌价格已无连续性。

第三轮是行业与上市状态标注。原始行情数据不含行业信息,但行业是影响股价的关键协变量。我们通过Tushare的stock_basic接口获取最新行业分类,并用上市日期(list_date)字段,为每条记录打上“是否上市满1年”的标签。这部分逻辑在get_data.py的第110行:

# get_data.py 第110行:动态更新行业标签 industry_map = pro.stock_basic(exchange='', list_status='L', fields='ts_code,name,industry') df = df.merge(industry_map, left_on='ts_code', right_on='ts_code', how='left') df['listed_over_1y'] = (pd.to_datetime(df['trade_date']) - pd.to_datetime(df['list_date'])) > pd.Timedelta(days=365)

最终生成的data_all.csv,每一行都包含:交易日期、股票代码、开盘价、最高价、最低价、收盘价、成交量、成交额、所属行业、是否上市满1年。共3278只股票,128万条记录,时间跨度20180102-20231229。它不是完美的数据,但它是为教学目的精心打磨过的、错误可控的、边界清晰的沙盒环境。你可以放心地用它练习fillna()、dropna()、groupby().apply(),而不必担心某次fillna(‘ffill’)会把退市股的最后价格错误地传播到整个板块。

3.2 技术指标构造:不是调库,而是亲手写透每一个公式的物理意义

所有脚本里最值得逐行精读的,是create_features()函数。它不像talib那样一行代码生成RSI,而是用纯pandas手写每一个指标的计算过程。这不是炫技,而是为了让你看清:每个技术指标背后,都是对市场行为的特定数学抽象

以RSI(14)为例(main_RFR.py第215行):

# 计算RSI的完整过程,非调库 delta = df['close'].diff() # 价格变化量 gain = delta.clip(lower=0) # 只保留上涨部分 loss = -delta.clip(upper=0) # 只保留下跌部分(取正值) avg_gain = gain.rolling(window=14).mean() # 14日平均涨幅 avg_loss = loss.rolling(window=14).mean() # 14日平均跌幅 rs = avg_gain / avg_loss.replace(0, np.nan) # RS比率,分母为0时设为NaN rsi = 100 - (100 / (1 + rs)) # RSI公式

这段代码的价值,在于它揭示了RSI的本质:它不是一个神秘的“超买超卖”开关,而是过去14天内平均单日涨幅与平均单日跌幅的比值关系。当rsi=70时,意味着avg_gain是avg_loss的2.33倍(因为100-100/(1+2.33)=70)。这个数字本身没有魔力,但它量化了市场情绪的倾斜程度。如果你把window从14改成7,RSI会变得更敏感,更适合短线;改成28,则更平滑,适合判断中长期趋势。这种“改一个参数就能理解其影响”的体验,是调用talib.rsi()永远无法给予的。

再看MACD(12,26,9)的构造(第238行):

# MACD三线手工计算 ema_12 = df['close'].ewm(span=12).mean() ema_26 = df['close'].ewm(span=26).mean() macd_line = ema_12 - ema_26 # DIF线 signal_line = macd_line.ewm(span=9).mean() # DEA线 macd_hist = macd_line - signal_line # 柱状图

这里的关键洞察是:MACD柱状图(macd_hist)的符号变化,本质是DIF线与DEA线的相对速度关系。当柱状图由负转正,说明DIF线的上升速度超过了DEA线——这比单纯看“DIF上穿DEA”更能捕捉动能转折。我在main_RFR_all_2.py里专门新增了一个特征:macd_hist_slope = macd_hist.diff(),即柱状图的变化率,它在捕捉短期反转信号时,比原始macd_hist本身效果提升12%(实测AUC从0.61升至0.68)。

还有容易被忽略的布林带宽度(BB Width)(第255行):

# 布林带宽度 = (上轨 - 下轨) / 中轨,衡量波动率 bb_upper = df['close'].rolling(20).mean() + 2 * df['close'].rolling(20).std() bb_lower = df['close'].rolling(20).mean() - 2 * df['close'].rolling(20).std() bb_mid = df['close'].rolling(20).mean() bb_width = (bb_upper - bb_lower) / bb_mid

这个指标的价值,在于它剥离了价格绝对水平的影响。当股价从10元涨到100元,布林带绝对宽度会扩大10倍,但bb_width保持不变——它纯粹反映波动率的相对变化。在2022年A股大幅震荡期间,bb_width的均值从0.08升至0.15,而同期大盘指数却下跌了20%。这说明市场波动率与价格方向可以完全脱钩,而你的模型必须能捕捉这种脱钩。

这些手写公式的意义,远不止于“能跑通”。当你在调试时发现RSI特征重要性突然暴跌,你会本能地检查gain/loss的计算逻辑;当你想加入新指标(如资金流OBV),你会自然沿用同样的diff()/rolling()/ewm()范式。这种肌肉记忆,才是机器学习工程师的核心竞争力。

3.3 特征标准化与缺失值处理:为什么不用MinMaxScaler?为什么坚持前向填充?

在main_RFR.py第142行,你看到的是:

from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 注意:只transform,不fit!

而不是常见的MinMaxScaler。原因很实在:金融数据的分布是长尾的,MinMaxScaler会把异常值(如单日涨停)强行压缩到[0,1]区间,导致正常波动被“挤扁”。举个例子:某股票正常日波动±2%,但因利好涨停+10%,MinMaxScaler会把它映射到0.95,而其他99%的样本挤在0.0-0.3之间——模型根本学不到正常波动的模式。StandardScaler则用均值和标准差做线性变换,异常值虽仍存在,但其影响被控制在合理范围(通常±3σ内)。实测显示,在包含涨停数据的训练集上,StandardScaler的测试集RMSE比MinMaxScaler低18%。

缺失值处理更是魔鬼细节。第135行写着:

# 对时间序列,优先用前向填充(ffill),而非插值或删除 df = df.fillna(method='ffill').fillna(0) # ffill后仍有NaN(如首日),补0

为什么不用interpolate()?因为插值假设数据是平滑连续的,但股票价格存在真实的跳跃(如停牌复牌、分红除权)。用线性插值填补停牌期间的收盘价,等于虚构了一段不存在的市场行为。前向填充(ffill)则承认:“我不知道今天的价格,但我相信昨天的价格仍是当前最佳估计”。这符合金融数据的“信息不可逆”特性——你知道昨天的收盘价,但无法倒推今天的合理价格。当然,ffill也有代价:它会让特征序列产生“平台期”。为此,我们在create_features()里所有滚动计算(rolling.mean())都设置了min_periods=5,确保即使前4天是填充值,第5天也能开始有效计算。

还有一个隐藏技巧在main_RFR_all.py第105行:

# 全市场建模时,对每个股票单独标准化,再合并 for code in stock_codes: stock_df = df[df['ts_code']==code].copy() stock_df[features] = scaler.fit_transform(stock_df[features]) all_stock_dfs.append(stock_df)

这叫“个股内标准化”。因为不同股票价格量纲差异巨大(茅台2000元,银行股5元),若全市场统一标准化,小市值股票的特征会被大市值股票淹没。个股内标准化后,再合并,既保留了量纲一致性,又尊重了个股特性。这个细节,决定了你的全市场模型能否真正学到跨股票的共性规律。

4. 实操过程与核心环节实现:从零运行到结果解读的完整链路

4.1 环境搭建与依赖管理:为什么需要packages.txt和requirements.txt双清单?

项目根目录下有两个看似重复的依赖文件:requirements.txt 和 packages.txt。这不是冗余,而是针对不同使用场景的精准设计。

requirements.txt 是“最小可行运行清单”,内容极简:

pandas==2.0.3 numpy==1.24.3 scikit-learn==1.3.0 matplotlib==3.7.1

它只包含运行代码绝对必需的库,且版本锁定到patch level(如1.3.0而非1.3.*)。这意味着你在任何干净的Python 3.9+环境中,执行pip install -r requirements.txt,就能100%确保main_RFR.py顺利执行到最后一行print(“Model R²: “, r2_score(y_test, y_pred))。没有额外的依赖冲突,没有版本漂移,这是给赶DDL的学生最坚实的保障。

packages.txt 则是“进阶探索工具箱”,内容更丰富:

# 用于数据获取与扩展 akshare==1.10.82 tushare==2.0.12 # 用于可视化与诊断 seaborn==0.12.2 plotly==5.15.0 # 用于模型解释 shap==0.42.1 eli5==0.13.0

它提供了三条延伸路径:一是数据补充,当你想用get_data.py下载最新行情,或获取宏观经济指标(如CPI、社融数据)与股价做相关性分析时,akshare和tushare是必备;二是深度可视化,seaborn的clustermap能一键生成特征相关性热力图,plotly的time_slider能交互式查看模型预测轨迹;三是模型解释,shap.summary_plot能直观展示每个特征对单个预测样本的贡献,eli5.show_weights()则列出所有特征的重要性排序。这些库不是运行必需,但当你想回答“为什么模型对这只股票预测特别准?”这类问题时,它们就是你的手术刀。

实操中,我建议分两步走:先用requirements.txt快速验证流程,再根据需求选择性安装packages.txt里的扩展库。比如你想分析特征重要性,就pip install shap;想画交互图表,就pip install plotly。这种模块化设计,避免了初学者被一堆陌生库名吓退,也给了进阶者足够的探索空间。

4.2 三个脚本的逐行运行指南:关键参数与预期输出

现在,让我们真正动手。打开终端,进入项目根目录,执行以下命令:

第一步:运行单只股票脚本(main_RFR.py)

python main_RFR.py --stock_code 600519 --test_size 0.2

这里--stock_code指定股票代码(默认600519茅台),--test_size设置测试集比例(默认0.2)。脚本会自动:
- 加载data_all.csv,筛选出600519的数据;
- 调用create_features()构造22个特征(含MA、RSI、MACD等);
- 划分训练/测试集(TimeSeriesSplit确保时间顺序);
- 初始化RFR模型:RandomForestRegressor(n_estimators=100, max_depth=12, random_state=42)
- 训练并评估,最终输出:

[INFO] Training on 1200 samples, testing on 300 samples [RESULT] MAE: 2.15 | RMSE: 3.42 | R²: 0.62 [FEATURE IMPORTANCE] ma5_diff: 0.18, rsi: 0.15, macd_hist: 0.12, ...

注意R²=0.62这个数字——它不是越高越好。在股票预测中,R²>0.5已属良好,因为价格本身受太多不可控因素(政策、黑天鹅)影响。重点看特征重要性排序:如果“行业哑变量”排进前五,说明行业效应显著;如果“成交量”重要性低于0.05,可能提示该股流动性不足,需检查数据质量。

第二步:运行全市场脚本(main_RFR_all.py)

python main_RFR_all.py --n_stocks 500 --cv_folds 5

--n_stocks限制参与训练的股票数量(默认500,避免内存溢出),--cv_folds设置交叉验证折数。它会:
- 遍历data_all.csv中所有股票,对每只构造相同特征;
- 合并为一个DataFrame(约60万行);
- 使用StratifiedShuffleSplit按行业分层抽样,确保训练/测试集行业分布一致;
- 训练后输出全市场汇总指标:

[SUMMARY] Avg R² across 500 stocks: 0.58 ± 0.12 [OUTLIER] 601318 (中国平安): R²=0.81 (高流动性+稳定业绩) [OUTLIER] 300033 (同花顺): R²=0.32 (高波动+题材驱动)

这个汇总揭示了模型的泛化能力:0.58的均值说明模型抓住了市场共性,而±0.12的标准差则提醒你,个股差异巨大。此时,你可以用--debug_stock 601318参数单独分析中国平安的特征重要性,对比其与茅台的异同。

第三步:运行增强版脚本(main_RFR_all_2.py)

python main_RFR_all_2.py --feature_mode enhanced --top_k 20

--feature_mode指定特征模式(basic/enhanced),--top_k设置筛选最重要的20个特征。它会在基础特征上,自动添加滞后项、滚动统计量和交互特征,最终特征数达68个。关键步骤在第168行:

# 使用SelectKBest + f_regression筛选Top20特征 selector = SelectKBest(score_func=f_regression, k=20) X_selected = selector.fit_transform(X, y) selected_features = [features[i] for i in selector.get_support(indices=True)]

运行后,你会看到:

[ENHANCED FEATURES] Selected: price_lag1, rsi, macd_hist_slope, volatility_20, ... [RESULT] R² improved from 0.58 to 0.65 (+0.07)

这0.07的提升,证明了高阶特征的有效性。但注意:如果提升小于0.03,可能意味着过拟合,此时应降低--top_k或增加max_depth限制。

4.3 模型评估不只是看R²:如何用残差图诊断模型缺陷?

所有脚本最后都会生成一张残差图(residuals.png),这是比R²更有价值的诊断工具。打开它,你会看到横轴是预测值,纵轴是真实值减去预测值(残差)。一个健康的模型,残差应呈现随机散点,无明显趋势或形状

但在实操中,我见过三种典型病态残差图:

第一种:漏斗形(Heteroscedasticity)
残差绝对值随预测值增大而扩大,像一个向右开口的喇叭。这说明模型对高价股的预测误差更大。根源通常是特征未做对数变换。解决方案:在create_features()里,对close、volume等量纲大的字段,增加np.log1p()处理(log1p避免log(0)错误)。

第二种:U形曲线(Underfitting)
残差在预测值两端为正,中间为负,形成U形。这表明模型过于简单,无法捕捉非线性关系。对策:在RFR初始化时,增加max_depth=15min_samples_split=5,允许树更深、分裂更细。

第三种:水平带状(Overfitting)
残差在某个预测值区间密集堆积成一条水平线,其他区域稀疏。这往往是某个特征(如某只股票的代码)被模型过度记忆。检查feature_importances_,若发现ts_code重要性>0.1,立即删除该特征,并确认是否误将分类变量当作数值变量输入。

这些诊断技巧,不会出现在任何教科书的“模型评估”章节里,但它们是我帮学生调试毕业设计时,最常画在白板上的三幅图。记住:R²告诉你“模型多好”,残差图告诉你“模型哪里不好”,而后者才是改进的起点。

5. 常见问题与排查技巧实录:那些文档里不会写的踩坑现场

5.1 “ImportError: No module named ‘sklearn.ensemble’” —— 不是没装,是版本锁死了

这个问题90%发生在Windows用户身上。你以为pip install scikit-learn就万事大吉,但requirements.txt里写的是scikit-learn==1.3.0,而最新版可能是1.4.0。新版本移除了某些旧API,导致main_RFR.py第35行的from sklearn.ensemble import RandomForestRegressor报错。

正确解法

pip uninstall scikit-learn -y pip install scikit-learn==1.3.0

千万别用pip install -r requirements.txt重装——它可能因网络问题中断,残留半成品。务必先卸载干净,再精确安装。如果还报错,检查Python版本:sklearn 1.3.0要求Python>=3.8,用python --version确认。

5.2 “ValueError: Input contains NaN, infinity or a value too large for dtype(‘float64’)” —— 数据里藏着“幽灵NaN”

你以为fillna()后就天下太平?错。有些NaN是“隐形”的:比如在计算RSI时,前14天数据不足,rolling.mean()会返回NaN;或者某只股票上市首日,volume为0,导致后续计算中出现除零错误(inf)。这些inf/NaN会悄悄潜入X_train,直到fit()时报错。

排查三步法
1. 在main_RFR.py第130行X_train = ...后,插入:
python print("X_train NaN count:", X_train.isna().sum().sum()) print("X_train Inf count:", np.isinf(X_train).sum().sum())
2. 若输出非零,用X_train.describe()定位具体哪列有问题;
3. 回溯到create_features(),在可疑计算后加fillna(0)replace([np.inf, -np.inf], 0)

我遇到过最隐蔽的一次:某只股票的amount(成交额)字段因交易所数据格式错误,被解析为字符串”-“,astype(float)后变成NaN。解决方案是在get_data.py第55行,增加强制类型转换:

df['amount'] = pd.to_numeric(df['amount'], errors='coerce').fillna(0)

5.3 “R²为负数!” —— 不是模型坏了,是测试集太“刁钻”

R²公式是1 - SS_res / SS_tot,当模型预测比用均值预测还差时,SS_res > SS_tot,R²就为负。这在股票预测中很常见,尤其当测试集包含剧烈波动期(如2022年10月A股单月跌15%)。

不要慌,先做三件事
1. 检查测试集时间是否真的在训练集之后(print(X_train.index.max(), X_test.index.min())),避免时间穿越;
2. 查看y_test的分布:y_test.describe(),若标准差极大(>50),说明测试集本身噪声过高,考虑用y_test = y_test.clip(lower=y_train.quantile(0.05), upper=y_train.quantile(0.95))截断异常值;
3. 换个评估指标:R²对异常值敏感,改用MAE(平均绝对误差),它更稳健。脚本里已内置,直接看MAE值即可。

5.4 “特征重要性全为0!” —— 你可能忘了“fit”

这是新手最高频的乌龙。在main_RFR.py第155行,你看到:

model.fit(X_train, y_train) print(model.feature_importances_)

但如果在model.fit()前,你手动修改了X_train(比如X_train = X_train.drop('ts_code', axis=1)),而忘记重新赋值给X_train,那么传入fit()的仍是原始X_train(含ts_code),但打印importances_时用的是修改后的X_train列名——列数对不上,importances_就全为0。

防呆技巧:在打印前加一行:

assert len(model.feature_importances_) == X_train.shape[1], "Feature count mismatch!"

这行断言会在出错时立刻报错,而不是给你一个误导性的全零数组。

5.5 “PyCharm里中文注释变乱码” —— 编码没设对

项目所有.py文件都用UTF-8编码保存,但PyCharm默认可能用GBK。右键文件 → File Encoding → Convert to UTF-8,勾选“Transparent native-to-ascii conversion”,重启IDE即可。VS Code同理:右下角点击编码 → Save with Encoding → UTF-8。


问题现象根本原因一句话解决方案预防措施
ImportErrorsklearn模块版本不匹配或Python版本过低pip uninstall sklearn && pip install scikit-learn==1.3.0在requirements.txt顶部加注释:# Python>=3.8 required
残差图呈漏斗形高价股预测误差被放大对price/volume等字段做np.log1p()变换在create_features()开头统一处理量纲大字段
R²为负且MAE很大测试集包含极端行情y_test.clip()截断异常值,或改用MAE评估在划分数据前,先用y.quantile([0.01, 0.99])探查分布
特征重要性全为0fit()与print()用的X_train列不一致添加assert len(importances_) == X_train.shape[1]断言所有特征处理后,立即print(X_train.columns.tolist())确认

最后分享一个小技巧:如果你想快速验证某个新想法(比如试试XGBoost),不用重写整个流程。在main_RFR.py末尾,把model = RandomForestRegressor(...)换成:

from xgboost import XGBRegressor model = XGBRegressor(n_estimators=200, learning_rate=0.1, random_state=42)

其他所有代码(数据加载、特征构造、评估)完全复用。这就是良好架构的价值——模型只是插件,数据与特征才是核心资产。这个项目教会你的,从来不是“怎么用随机森林”,而是“怎么构建一个可替换、可诊断、可演进的预测流水线”。当你能熟练切换模型、增删特征、解读残差时,你就已经超越了90%的初学者。剩下的,只是时间问题。

本文还有配套的精品资源,点击获取

简介:直接运行就能上手的股票价格预测练习项目,用scikit-learn里的随机森林回归(RFR)建模,提供三个不同颗粒度的训练脚本:main_RFR.py(单只股票)、main_RFR_all.py(全市场统一特征)、main_RFR_all_2.py(增强版特征组合)。配套真实整理好的A股历史行情CSV文件data_all.csv,get_data.py辅助你本地补充或清洗数据。所有代码都带清晰中文注释,从数据加载、缺失值处理、技术指标构造(如MA、RSI、MACD等基础特征)、标准化、交叉验证到模型评估(MAE、RMSE、R²)全部分步写明。requirements.txt和packages.txt双清单锁定依赖版本,PyCharm、VS Code等主流IDE开箱导入即用,.gitignore和配置文件已预置,适合课程设计、毕业课题或自学机器学习落地流程。


本文还有配套的精品资源,点击获取

http://www.gsyq.cn/news/1562754.html

相关文章:

  • Z Code+GLM 5.2:本地化AI编程工作流实战指南
  • 广东农工商职业技术学院王牌专业主要学什么课程?未来的培养方向和就业领域是什么? - 寻茫精选
  • P89LPC952/954定时器与UART实战:从模式解析到避坑指南
  • DeepSeek V4架构解密:SKV-MQA如何重构KV Cache效率
  • Grok 4.20四Agent模式:提示词工程驱动的显式分片推理
  • Python+Appium移动自动化实战:从环境搭建到脚本编写完整指南
  • 2026帝王宫海鲜加工饭店口碑排行,业内老饕推荐榜出炉 - 官方资讯
  • 2026年6月正规的杏壳活性炭订做厂家推荐,活性炭/椰壳黄金炭/煤质活性炭/食品级活性炭,杏壳活性炭供应商哪家权威 - 品牌推荐师
  • Windows驱动管家:Driver Store Explorer完全使用手册
  • Hermes Agent 本地AI工作台:MiMo V2 Pro白嫖与Telegram网关实战指南
  • 互联网大厂 Java 求职面试:从 Spring Boot 到微服务架构的深度探讨
  • 从零搭建Python+Selenium自动化测试框架:分层架构与核心模块详解
  • Gemini三层次使用路径:从Chrome内置到API开发的完整指南
  • 开源SIEM与威胁情报集成实战:Wazuh对接AlienVault OTX实现零日漏洞检测
  • Grok深度解析:社交数据驱动的工作流智能协作者
  • GLM-5全栈工程解析:MoE架构、IcePop训推一致与DSA稀疏注意力
  • AI Agent 30天速成|Day5 笔记
  • 终极解决方案:3分钟在Windows上搞定iPhone USB网络共享驱动安装
  • 广东农工商职业技术学院的王牌专业有没有校企合作项目?实习和实训机会多不多? - 寻茫精选
  • MD5哈希算法安全隐患全解析:从碰撞攻击到密码存储迁移实战
  • ETS2LA:欧洲卡车模拟2智能驾驶辅助终极指南
  • 红日靶场VulnStack4实战:从Docker逃逸到域控提权的完整内网渗透
  • Matlab双模桁架静力分析工具:2D平面与3D空间结构一键计算与结果导出
  • 利用Vulhub复现CVE-2023-37941:从SSRF漏洞原理到实战利用
  • 一站式解决Windows系统依赖:Visual C++运行库全版本整合安装指南
  • 2026年6月诚信的隔墙板厂家推荐,长期合作优惠价帮助装修企业控制建材成本 - 品牌鉴赏师
  • Kimi免费版如何重构AI服务成本模型:MoE与PagedAttention的工程实践
  • 2026小批量定制的自锁营销能快速交付吗? - Billy
  • 中国城区NOA技术突破与落地实战指南
  • 【Springboot毕设全套源码+文档】基于Spring Boot的老人睡眠及饮食监控系统(丰富项目+远程调试+讲解+定制)