简介:面向机器学习入门与课程实践者,这套预测系统汇总围绕贝叶斯网络、马尔科夫模型、线性回归、岭回归、多项式回归、决策树回归与深度神经网络七类方法展开,适合用来对比不同模型在分类、回归和时间序列预测中的适用边界。压缩包共12个文件、约1.3MB,以6个Python脚本为核心,配合xlsx与csv数据、ui界面文件、docx说明和md文档,既可运行示例,也便于修改参数做课程实验。已有136人学习下载,内容涵盖从概率图模型、无记忆状态转移到正则化回归和树模型、深度网络预测的完整示例,能帮助读者理解误差最小化、L2正则、非线性拟合与反向传播等关键环节,并据此完成模型选型、结果对比和实验报告整理。
1. 七类预测模型放在一个包里:先解决“同口径对比”而不是逐个数算法
拿到一份名为“机器学习预测系统汇总”的压缩包,目录里同时躺着贝叶斯网络、马尔科夫模型、线性回归、岭回归、多项式回归、决策树回归和深度神经网络预测,最容易犯的错是逐个打开脚本跑一遍,看到 loss 降了、R² 上去了就以为摸清了。真正决定这份资源能不能用的,不是模型数量,而是这些模型有没有共用同一套数据契约和误差口径;否则你拿线性回归的随机切分结果去和马尔科夫模型的时间顺序结果比,结论基本是反的。这个包适合手上有一批结构化时序或表格数据、需要快速拉起多个基线并做同口径对比的人;不适合指望直接拿现成模型上线的人,因为特征工程、切分和评估这三层仍然要自己补齐。我的建议是把它当成一套预测系统的骨架,先统一入口,再逐个模型验证边界。
2. 统一数据入口与评估口径:三张表把线性回归到深度神经网络的误差拉到同一把尺子
2.1 目录约定与数据契约
多模型汇总包最常见的问题不是模型写错,而是每个模型子目录里读数据的方式不一样:有的脚本把目标列写死成target,有的写死成y;有的默认第一列是时间,有的默认最后一列是标签。直接逐个跑,表面上看每个脚本都能出结果,实际上你已经在用不同的列做预测。我的做法是先补一个数据契约,把目标列、时间列、类别特征、数值特征固定下来,任何模型脚本都从同一个契约对象取列。这样后面接线性回归还是深度神经网络,输入列是一致的。
from dataclasses import dataclass, field from typing import List, Optional import pandas as pd @dataclass class DataContract: target_col: str time_col: Optional[str] = None feature_cols: List[str] = field(default_factory=list) categorical_cols: List[str] = field(default_factory=list) numeric_cols: List[str] = field(default_factory=list) drop_cols: List[str] = field(default_factory=list) def load(self, path: str) -> pd.DataFrame: df = pd.read_csv(path) # 统一列名前后空格,避免不同模型脚本读同一份数据时列名对不上 df.columns = [c.strip() for c in df.columns] missing = [c for c in [self.target_col] + self.feature_cols if c not in df.columns] if missing: raise ValueError(f"缺少字段: {missing}") return df contract = DataContract( target_col="y", time_col="dt", feature_cols=["x1", "x2", "city"], categorical_cols=["city"], numeric_cols=["x1", "x2"], )这段代码的逻辑是把数据入口收口到一个对象里:加载时统一列名,缺少目标列或特征列直接抛错,避免某个模型脚本默默用错列还继续训练。参数上,target_col改成实际标签列;time_col没有时间维度就置None;categorical_cols和numeric_cols要按数据实际类型填,后面的预处理 Pipeline 依赖这两个列表。失败时先看报错里缺哪个字段,再回头检查 CSV 表头是不是带了空格或大小写不一致。
2.2 特征工程与时间切分:防止未来信息泄漏
预测系统汇总包里最容易翻车的地方,是把标准化和编码放在切分之前做。全量数据先fit一遍StandardScaler,验证集的信息已经漏进训练过程,线性回归和岭回归的验证分数会好看得不真实。正确做法是把缺失值填充、标准化、独热编码全部塞进Pipeline,只在训练折上fit。如果数据带时间列,切分也要按时间顺序来,不能随手train_test_split(shuffle=True);时序预测里随机切分等于让模型偷看未来。
from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.preprocessing import OneHotEncoder, StandardScaler from sklearn.impute import SimpleImputer from sklearn.model_selection import TimeSeriesSplit preprocess = ColumnTransformer( transformers=[ ("num", Pipeline([ ("imputer", SimpleImputer(strategy="median")), ("scaler", StandardScaler()), ]), contract.numeric_cols), ("cat", Pipeline([ ("imputer", SimpleImputer(strategy="most_frequent")), ("onehot", OneHotEncoder(handle_unknown="ignore")), ]), contract.categorical_cols), ], remainder="drop", ) tscv = TimeSeriesSplit(n_splits=5, test_size=20, gap=1) # gap=1 表示训练集和验证集之间空一行,避免相邻时间点泄漏这段代码的逻辑是把预处理和模型训练绑在同一个 Pipeline 里,交叉验证时每一折都重新拟合预处理器。参数上,n_splits按数据量改,test_size是每次验证窗口长度,gap用来切断相邻时间点;没有时间列就用KFold,但要固定random_state,并且不要在时序数据上随手 shuffle。失败时先看验证分数是不是异常高,再检查标准化和编码有没有在切分前做过。
提示:时间序列数据不要先全量标准化再切分,这个顺序错了,后面模型对比基本失效。
2.3 统一评估函数与结果表
七类模型的输出尺度不完全一样:线性回归和岭回归输出连续值,贝叶斯网络和马尔科夫模型可能输出离散状态或概率,决策树回归和深度神经网络输出连续值。如果评估时有人用 RMSE、有人用 MAE、有人只报 R²,最后的结果表没法横向比。我一般会固定一个评估函数,同时输出 MAE、RMSE、R² 和 MAPE,但排名只认一个主指标。回归任务里,RMSE 对异常值更敏感,MAE 更稳,R² 看的是相对均值的解释比例,MAPE 只在目标非零且量纲有意义时用。
import numpy as np from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score def evaluate_regression(y_true, y_pred, name: str) -> dict: y_true = np.asarray(y_true).ravel() y_pred = np.asarray(y_pred).ravel() mae = mean_absolute_error(y_true, y_pred) rmse = np.sqrt(mean_squared_error(y_true, y_pred)) r2 = r2_score(y_true, y_pred) # 只对非零真值计算 MAPE,避免除零把指标打成 inf mask = y_true != 0 mape = np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100 if mask.any() else np.nan return {"model": name, "MAE": mae, "RMSE": rmse, "R2": r2, "MAPE": mape}这段代码的逻辑是把所有模型输出拉平成一维数组再算指标,避免某个模型返回(n,1)、另一个返回(n,)时广播出错。参数上,name写模型名,方便后面拼结果表;如果目标有负数或接近零,MAPE 不要拿来排名。结果表建议直接落盘成 CSV,每次跑完追加一行,不要只在 notebook 里看。
| 指标 | 适合看什么 | 容易误判的场景 |
|---|---|---|
| MAE | 平均绝对误差,业务解释直接 | 大误差被平均掉 |
| RMSE | 放大大误差 | 异常值多时波动大 |
| R² | 相对均值的解释比例 | 不同分布的数据集之间不可直接比 |
| MAPE | 相对误差 | 目标接近 0 或含负数 |
3. 线性模型族落地:线性回归、岭回归、多项式回归的系数检查与正则路径
3.1 线性回归:最小二乘的基线不是“随便跑跑”
线性回归在这个汇总包里通常是最先跑通的模型,但它不是用来凑数的。它的价值在于给后面所有非线性模型一个可解释的基线:如果决策树和深度神经网络的验证误差没有明显低于线性回归,说明特征本身的信息量不够,或者切分口径有问题。线性回归跑完一定要看系数,不看系数就等于把模型当黑匣子用。系数方向要和业务常识对得上,系数量级要和特征量纲一起看;独热编码后的特征名要能对上原始类别,否则解释会错位。
from sklearn.linear_model import LinearRegression from sklearn.pipeline import Pipeline import pandas as pd def fit_linear(train_df, valid_df, contract): X_train = train_df[contract.feature_cols] y_train = train_df[contract.target_col] X_valid = valid_df[contract.feature_cols] y_valid = valid_df[contract.target_col] model = Pipeline([ ("prep", preprocess), ("reg", LinearRegression()), ]) model.fit(X_train, y_train) pred = model.predict(X_valid) coef = pd.Series( model.named_steps["reg"].coef_, index=model.named_steps["prep"].get_feature_names_out() ).sort_values(key=abs, ascending=False) return model, pred, coef这段代码的逻辑是让线性回归走同一套预处理 Pipeline,训练完把系数和展开后的特征名对齐。参数上,fit_intercept默认True,如果特征已经中心化可以设False;coef排序用绝对值,先看影响最大的几个特征。失败时先看系数符号和业务常识是否冲突,再查共线性、异常值和编码方式;如果某个类别特征的系数大得离谱,很可能是该类样本太少。
3.2 岭回归:正则强度与标准化必须绑在一起
岭回归不是“线性回归加一个 alpha”这么简单。alpha 的选择跟特征尺度强相关,如果预处理里没有标准化,alpha 会被量纲带偏:量纲大的特征被压得很狠,量纲小的特征几乎不受约束。我的习惯是把标准化和RidgeCV绑在同一个 Pipeline 里,让交叉验证在标准化后的特征上选 alpha。alpha 的候选范围先从1e-3到1e3扫一遍,看最优值落在哪个区间,再决定要不要扩。
from sklearn.linear_model import RidgeCV import numpy as np alphas = np.logspace(-3, 3, 13) ridge = Pipeline([ ("prep", preprocess), ("reg", RidgeCV(alphas=alphas, cv=5, scoring="neg_root_mean_squared_error")), ]) ridge.fit(X_train, y_train) print("alpha:", ridge.named_steps["reg"].alpha_)这段代码的逻辑是用RidgeCV在训练集内部交叉验证选 alpha,预处理器跟着每一折重新拟合。参数上,alphas是候选正则强度,样本少时cv从 5 降到 3;scoring用负 RMSE,和前面的主指标保持一致。失败时先看 alpha 是不是顶到候选边界,顶到就扩大alphas范围;如果岭回归系数和线性回归几乎一样,说明 alpha 太小或者标准化没进 Pipeline。
3.3 多项式回归:阶数不是越高越好
多项式回归本质上是特征扩展后再做线性回归,不是独立算法。它的坑在于阶数一高,交互项数量爆炸,特征一多就过拟合。比如 20 个特征做 3 阶多项式,展开后的维度很容易把样本量压垮。我的做法是先用degree=2试交互,再看验证曲线;如果验证误差随阶数先降后升,取拐点,不要取训练误差最低的点。多项式回归后面如果要加正则,直接接Ridge,不要用普通最小二乘硬扛。
from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression from sklearn.model_selection import validation_curve poly_pipe = Pipeline([ ("prep", preprocess), ("poly", PolynomialFeatures(include_bias=False)), ("reg", LinearRegression()), ]) degrees = [1, 2, 3, 4] train_scores, valid_scores = validation_curve( poly_pipe, X_train, y_train, param_name="poly__degree", param_range=degrees, cv=tscv, scoring="neg_root_mean_squared_error" )这段代码的逻辑是用同一个时间切分器tscv扫不同阶数,训练分数和验证分数一起看。参数上,degree先从 1 到 3 试,特征多时只开交互不升全阶;include_bias=False是因为后面线性回归自带截距。失败时看验证分数是不是在某个阶数后明显变差,同时训练分数接近完美,这就是典型过拟合;减阶数或把最后的LinearRegression换成Ridge。
4. 概率状态与非线性模型:贝叶斯网络、马尔科夫模型、决策树回归、深度神经网络预测的接口对齐
4.1 贝叶斯网络:离散化、结构先验与条件概率表
贝叶斯网络在这个汇总包里通常不是拿来和线性回归拼 RMSE 的,它更适合做带结构先验的概率推断:哪些变量影响哪些变量,条件概率表长什么样。落地时第一个坑是离散化:连续变量分箱边界如果在全量数据上定,验证集信息会漏进去;第二个坑是父节点组合太细,某些组合样本为零,条件概率表直接空掉。我的做法是先在训练集上定分箱边界,再统计条件概率表,对稀疏组合做合并或平滑。
import pandas as pd def conditional_prob(df, child, parents): # 统计父节点组合下子节点取值的频率,得到条件概率表 grouped = df.groupby(parents + [child]).size().rename("n").reset_index() grouped["p"] = grouped["n"] / grouped.groupby(parents)["n"].transform("sum") return grouped # 示例:迟到概率受天气和路况影响 cpt = conditional_prob(df, child="late", parents=["weather", "traffic"]) def predict_bn(row, cpt, parents, child): sub = cpt for p in parents: sub = sub[sub[p] == row[p]] return sub.sort_values("p", ascending=False).iloc[0][child]这段代码的逻辑是不学习网络结构,只校验固定结构的条件概率表;资源包里如果带结构,先用业务先验确认边方向,再算 CPT。参数上,parents是父节点列表,child是预测目标;离散化分箱边界要在训练集上定。失败时先看某个父节点组合是不是没有样本,CPT 为空就合并类别或加平滑,不要直接拿空表去预测。
4.2 马尔科夫模型:状态划分比转移矩阵更关键
马尔科夫模型的代码通常很短,难的是状态怎么划。状态划得太粗,转移矩阵没有区分度;划得太细,每个状态样本太少,矩阵大量为零,预测永远落在少数状态。我的习惯是先按业务含义划 3 到 5 个状态,统计每个状态的样本量和转移频次,再决定要不要合并。转移矩阵要加拉普拉斯平滑,不然零概率会把整条路径掐断。
import numpy as np def transition_matrix(states, n_states, alpha=1.0): # states 是整数编码后的状态序列 counts = np.zeros((n_states, n_states)) for a, b in zip(states[:-1], states[1:]): counts[a, b] += 1 counts += alpha # 拉普拉斯平滑,避免零概率 return counts / counts.sum(axis=1, keepdims=True) P = transition_matrix(states, n_states=3, alpha=1.0) # 下一步状态分布 = 当前状态向量 @ P next_dist = np.eye(3)[current_state] @ P这段代码的逻辑是统计相邻状态转移频次,加平滑后按行归一化。参数上,alpha是平滑系数,状态少、样本少时用1.0;n_states是状态数,不要为了精细把状态切得太碎。失败时看矩阵某一行是不是接近均匀分布,如果是,说明该状态样本太少或状态定义太细,先合并状态再重跑。
4.3 决策树回归:深度、叶节点样本数与特征重要性
决策树回归不需要标准化,但它对深度和叶节点样本数很敏感。max_depth=None让树完全长开,训练 RMSE 可以接近 0,验证 RMSE 却可能翻倍。我的习惯是先固定min_samples_leaf,再扫max_depth;min_samples_leaf是防止小样本叶节点的第一道闸。特征重要性可以看,但不要把它当因果解释,尤其是特征之间有相关性的时候。
from sklearn.tree import DecisionTreeRegressor from sklearn.model_selection import GridSearchCV tree = DecisionTreeRegressor(random_state=42) param_grid = { "max_depth": [3, 5, 8, None], "min_samples_leaf": [5, 10, 20], "min_samples_split": [2, 10], } search = GridSearchCV(tree, param_grid, cv=tscv, scoring="neg_root_mean_squared_error") search.fit(X_train, y_train) print(search.best_params_)这段代码的逻辑是用同一个时间切分器做网格搜索,避免不同参数用不同切分导致结果不可比。参数上,max_depth控制复杂度,min_samples_leaf防止小样本叶节点,min_samples_split控制分裂门槛;random_state固定后特征重要性才可复现。失败时先看训练 RMSE 和验证 RMSE 的差距,差距大就加min_samples_leaf或降max_depth。
4.4 深度神经网络预测:标准化、早停与随机种子
深度神经网络预测在这个包里通常是最容易被神化的部分,但它在表格数据上不一定打得过树模型。落地时三个参数最要命:输入标准化、早停和随机种子。输入不标准化,loss 不降是常态;早停的验证集如果和最终测试集混在一起,评估结果会虚高;随机种子不固定,每次跑出来的对比没有意义。我的做法是先把 MLP 当基线跑,隐藏层从小开始,确认 loss 能稳定下降再加深。
from sklearn.neural_network import MLPRegressor mlp = Pipeline([ ("prep", preprocess), ("reg", MLPRegressor( hidden_layer_sizes=(64, 32), activation="relu", learning_rate_init=1e-3, alpha=1e-4, early_stopping=True, validation_fraction=0.15, n_iter_no_change=20, random_state=42, max_iter=1000, )), ]) mlp.fit(X_train, y_train)这段代码的逻辑是把标准化放进 Pipeline,MLP 只看标准化后的特征;early_stopping从训练集里再切一部分做早停,不碰最终验证集。参数上,hidden_layer_sizes先小后大,learning_rate_init从1e-3试,alpha是 L2 正则,random_state固定用于对比。失败时先查标准化和标签尺度,再看 loss 曲线是不是震荡;每次结果差异大就固定种子并做多次平均。
4.5 输出对齐与模型融合
七类模型的输出对齐是汇总包落地时最琐碎的一步。线性回归、岭回归、多项式回归、决策树回归、深度神经网络都输出连续值,贝叶斯网络和马尔科夫模型可能输出状态或概率,拼结果表时不能直接concat。我的做法是统一用验证集的 index 拼宽表,每个模型一列,再算简单平均作为融合基线。融合不是越多越好,误差相关性高的模型堆在一起,提升很有限。
import pandas as pd pred_frames = [] for name, pred in [("linear", pred_linear), ("ridge", pred_ridge), ("tree", pred_tree), ("mlp", pred_mlp)]: pred_frames.append(pd.DataFrame({"model": name, "pred": pred}, index=valid_df.index)) pred_long = pd.concat(pred_frames) pred_wide = pred_long.pivot(columns="model", values="pred") result = valid_df[[contract.target_col]].join(pred_wide) result["ensemble"] = pred_wide.mean(axis=1)这段代码的逻辑是用同一个valid_df.index对齐所有模型预测,避免行错位。参数上,ensemble这里用简单平均,只是基线;如果模型误差相关性低,再考虑加权。失败时先看结果表行数对不对,行数多了通常是某个模型预测时reset_index过。
5. 多模型汇总包避坑排查:五条血泪经验从数据泄漏到模型加载
5.1 现象:验证集 R² 高得离谱,线上预测却一塌糊涂
现象是离线验证 R² 到 0.95 以上,换一批数据预测直接崩。原因多数不是模型太强,而是预处理在切分前做了全量fit,或者时间序列随机切分让未来信息漏进训练集。解决方式是把所有预处理塞进 Pipeline,时序数据用TimeSeriesSplit并留gap,每次评估只让训练折拟合预处理器。检查时把验证集单独拿出来,重新fit一次预处理器再预测,如果分数掉很多,基本就是泄漏。
5.2 现象:岭回归系数和线性回归几乎一样
现象是加了岭回归,系数和普通最小二乘差不多,alpha 看起来没起作用。原因通常是 alpha 太小,或者特征没有标准化,正则强度被量纲带偏。解决方式是把StandardScaler放进 Pipeline,alphas从1e-3扫到1e3,看最优 alpha 落在哪;如果顶到边界就扩范围。另一个检查点是特征共线性,共线性严重时岭回归才会明显压系数,不严重时两者接近是正常的。
5.3 现象:马尔科夫转移矩阵大量 0,预测永远落在少数状态
现象是转移矩阵稀疏,预测路径很快收敛到一两个状态。原因是状态划得太细,或者样本量不够,某些状态只出现几次。解决方式是先合并状态,把业务含义相近的类别并在一起,再统计转移频次;对稀疏矩阵加拉普拉斯平滑,alpha从1.0开始试。检查时看每个状态的样本量和出边数量,出边太少的状态不要单独保留。
5.4 现象:神经网络 loss 不降或每次结果差异很大
现象是 MLP 训练 loss 长期不降,或者每次跑出来的验证误差差很多。原因通常是输入没标准化、学习率太大、早停验证集切得不对,或者随机种子没固定。解决方式是把标准化放进 Pipeline,learning_rate_init从1e-3往下试,early_stopping=True并固定random_state。检查时先跑一个小网络,确认 loss 能稳定下降,再加深层数;不要一上来就堆隐藏层。
5.5 现象:模型文件加载后预测值量纲不对
现象是训练时 RMSE 正常,加载模型后预测值整体偏大或偏小。原因多数是只保存了模型,没保存预处理器,或者保存了预处理器但加载时列顺序变了。解决方式是把预处理和模型一起保存,加载后先用训练集的一小批样本做一次预测校验,确认输出量纲和训练时一致。检查时对比加载前后同一批样本的预测值,差得远就先查scaler和特征列顺序。
6. 进阶验证:滚动回测、残差诊断与上线前的一张验收表
6.1 滚动回测:按时间窗口重训
离线交叉验证只能说明模型在历史切分上的表现,真要上线还得做滚动回测:按时间窗口反复重训、预测下一段,记录每折的 RMSE 和方差。下面这段代码把前面统一的评估函数接进滚动回测,clone保证每折重新初始化模型,gap切断相邻时间点。
from sklearn.base import clone def rolling_validate(model, df, contract, n_splits=5, test_size=20): scores = [] for train_idx, valid_idx in TimeSeriesSplit(n_splits=n_splits, test_size=test_size, gap=1).split(df): train = df.iloc[train_idx] valid = df.iloc[valid_idx] m = clone(model) m.fit(train[contract.feature_cols], train[contract.target_col]) pred = m.predict(valid[contract.feature_cols]) scores.append(evaluate_regression(valid[contract.target_col], pred, name="rolling")) return pd.DataFrame(scores)参数上,test_size按业务预测周期定,n_splits数据少时降到 3;结果看 RMSE 的均值和方差,不只看平均。
6.2 残差与分群误差检查
滚动回测通过后,还要看残差有没有系统性偏移。把验证集按类别特征或时间段分群,分别算 MAE,如果某个群体的误差明显高于整体,说明模型在这个群体上欠拟合。常见原因是类别特征编码后样本太少,或者时间跨度上分布漂移。
6.3 上线前的一张验收表
| 检查项 | 通过标准 | 不通过先查 |
|---|---|---|
| 数据切分 | 时间顺序切分,gap>0 | 是否 shuffle |
| 预处理 | 只在训练集 fit | 是否全量标准化 |
| 指标 | RMSE/MAE 同口径对比 | 是否混用不同切分 |
| 残差 | 分群误差无系统性偏移 | 是否漏掉类别特征 |
| 模型加载 | 预测值与训练量纲一致 | 是否漏保存 scaler |
从那以后我每次拆这种多模型汇总包,都会先跑一遍滚动回测和残差分群,再决定哪个模型进候选;不先把口径钉死,模型越多越容易给自己制造错觉。希望帮到你。
本文还有配套的精品资源,点击获取