简介:面向机器学习初学者与项目实践者的预测模型汇总包,围绕贝叶斯网络、马尔科夫模型、线性回归、岭回归、多项式回归、决策树回归及深度神经网络七类方法,提供从概率图建模、序列状态转移到回归拟合与神经网络预测的对照学习素材,适合课程实验、毕业设计选题和预测类项目预研。压缩包共12个文件,约1.3MB,以6个Python脚本为核心,配套xlsx与csv数据集、ui界面文件及docx、md说明文档,便于按模型运行、调整参数并观察结果差异。已有137人学习下载。读者可结合代码与数据理解最小二乘拟合、L2正则化、多项式特征扩展、决策树分裂规则、贝叶斯网络条件依赖和马尔科夫无记忆假设,并对比深度神经网络在复杂特征表示上的表现;说明文档与界面文件也有助于梳理目录结构及交互调用流程,为模型选择、可解释性评估与泛化能力比较提供参考。
1. 七个模型放进一个仓库:这套机器学习预测系统汇总适合谁
上周有个做设备运维的开发者找我看需求:手上有三年的工况表和故障记录,想同时回答三个问题——明天这台设备的振动值大概多少、下个月会不会进入异常状态、哪几个传感器组合最容易一起报警。他试过只拿线性回归硬套,结果误差大得没法看;换成深度神经网络又变成黑匣子,领导问一句“为什么是这个数”就答不上来。这类场景其实不该押注单一模型,而应该横向摆开对比。这套机器学习预测系统汇总把贝叶斯网络、马尔科夫模型、线性回归、岭回归、多项式回归、决策树回归、深度神经网络预测七个模块收在同一个工程里,共用数据管线和评估口径,适合想一次跑通多种预测路线、再做取舍的开发者。下面按目录结构、数据管线、模型实现、避坑、二次开发的顺序拆。
2. 工程骨架与数据管线:一份 CSV 怎么喂给七个模型
汇总类项目最怕的不是模型写不出来,而是七个脚本各读各的数据、各算各的指标,最后没人敢拿结果做决策。我第一次拆这类包的时候就踩过:线性回归用的是随机切分,马尔科夫用的是按时间切分,两边 RMSE 压根不可比,白跑一晚上。所以第 2 章先把骨架和数据口径讲清楚,后面六个模型的代码才有讨论价值。
2.1 目录结构与模块边界
这类汇总项目一般不会上微服务,常见的做法是单仓库 + 脚本入口,靠目录把职责切开。下面是我整理后的结构,也是我拿到类似压缩包后第一件事会去核对的东西:
ml-forecast-kit/ ├── data/ │ ├── raw/ # 原始 CSV,只读,任何清洗都不回写 │ └── processed/ # 清洗后的特征表,可随时删掉重跑 ├── configs/ │ └── default.yaml # 字段名、目标列、切分比例、随机种子 ├── src/ │ ├── data_loader.py # 读表、缺失值处理、切分 │ ├── preprocess.py # 标准化、分箱、序列构造 │ ├── evaluate.py # 统一指标:MAE / RMSE / R2 / 准确率 │ ├── registry.py # 模型注册表,按名字取构建函数 │ └── models/ │ ├── linear.py # 线性回归 │ ├── ridge.py # 岭回归 + 多项式特征 │ ├── tree.py # 决策树回归 │ ├── bayes_net.py # 贝叶斯网络 │ ├── markov.py # 马尔科夫链 │ └── dnn.py # 深度神经网络 ├── scripts/ │ ├── train_all.py # 一把跑完七个模型,输出对比表 │ └── predict.py # 单条 / 批量预测入口 └── outputs/ ├── models/ # joblib、pt 权重 └── reports/ # 每个模型的指标 JSON这个结构的关键不是好看,而是data/raw只读。汇总项目里最容易被改坏的就是原始表:有人为了省事直接在原 CSV 上补缺失值、改字段名,后面想复现某个结果就再也回不去了。我一般会把processed/整个目录加进忽略列表,需要时从raw/重新生成,成本只有几十秒。
configs/default.yaml承担的是“七个模型共用一套参数口径”的职责。字段名、目标列、切分方式、随机种子都放这里,模型脚本里不出现硬编码列名。常见做法是把target_col、time_col、test_size、seed这几项固定下来,任何模型想改切分方式,必须显式传参,避免悄悄换了评估基准。
2.2 数据管线:缺失值、切分与防泄漏的标准化
七个模型里,回归类模型吃标准化后的特征,树模型不吃也不受影响,贝叶斯网络必须先把连续变量分箱成离散状态。所以管线要拆成“公共部分”和“模型专属部分”。公共部分只做读表、去重、目标缺失行剔除和切分:
# src/data_loader.py import pandas as pd from sklearn.model_selection import train_test_split def load_dataset(path, target_col, feature_cols, test_size=0.2, seed=42): df = pd.read_csv(path) # 目标值缺失的样本没有监督信号,直接丢弃,不做插补 df = df.dropna(subset=[target_col]) X = df[feature_cols].copy() y = df[target_col].copy() # 固定 random_state,保证七个模型拿到完全相同的训练集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=test_size, random_state=seed, shuffle=True ) return X_train, X_test, y_train, y_test参数说明里最值得盯的是random_state。汇总项目里七个模型如果各自用默认随机种子,对比表上的差异有一部分其实来自切分噪声,而不是模型能力。shuffle=True适用于横截面数据;如果数据带明显时间顺序、目标是预测未来,就要换成按时间切分,这一步在配置文件里做成开关,比在每个模型里写if更省心。
标准化的正确位置是在训练集上fit、在测试集上transform,并且要包进Pipeline,让交叉验证的每一折都重新拟合。常见的错误写法是对整张表先做StandardScaler().fit_transform(X)再切分,这样测试集的均值和方差已经泄漏进训练过程,线下分数会虚高。
# src/preprocess.py from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.impute import SimpleImputer def numeric_pipeline(estimator): # 顺序不能反:先补缺失,再标准化,最后才是模型 return Pipeline([ ("imputer", SimpleImputer(strategy="median")), ("scaler", StandardScaler()), ("model", estimator), ])SimpleImputer用中位数而不是均值,是因为工况类数据里常有极端值,均值会被拉偏。这个管线只给线性、岭回归和 DNN 用;决策树回归直接吃原始数值和缺失值即可,硬套标准化不会出错但多一层开销,也容易让人误以为树模型依赖特征尺度。
2.3 统一训练与评估接口:一张对比表怎么来的
七个模型的构建方式差异很大:sklearn 的模型是fit/predict,PyTorch 要写训练循环,贝叶斯网络是“学参数 + 推断”,马尔科夫干脆不训练。想统一,就得在它们外面包一层相同签名。我一般用一个极简注册表:
# src/registry.py MODEL_REGISTRY = {} def register(name): def wrapper(builder): MODEL_REGISTRY[name] = builder return builder return wrapper @register("linear") def build_linear(params): from sklearn.linear_model import LinearRegression return numeric_pipeline(LinearRegression()) @register("ridge") def build_ridge(params): from sklearn.linear_model import Ridge return numeric_pipeline(Ridge(alpha=params.get("alpha", 1.0)))注册表的价值在于train_all.py不需要知道每个模型的具体类,只按名字遍历即可:
# scripts/train_all.py 片段 from src.registry import MODEL_REGISTRY from src.evaluate import regression_metrics results = {} for name, builder in MODEL_REGISTRY.items(): model = builder(params.get(name, {})) model.fit(X_train, y_train) pred = model.predict(X_test) results[name] = regression_metrics(y_test, pred)regression_metrics统一输出 MAE、RMSE、R2 三个数,写进outputs/reports/*.json。这里有个细节值得强调:所有模型必须用同一个X_test和y_test,否则对比表就是拼凑出来的。我在train_all.py里会把测试集的哈希值打印出来,七个模型跑完核对一遍,这个习惯帮我挡过好几次“某人偷偷改了切分比例”的事故。
3. 监督模型层拆解:线性回归、岭回归、多项式回归、决策树回归与 DNN 的同台对比
第 3 章是这份资源的主体。五个监督模型放在一起,最容易出现的误解是“越复杂的模型效果越好”,实际跑下来经常相反:小样本上线性回归稳稳压住 DNN,特征共线时岭回归比普通最小二乘稳得多,而多项式回归一旦阶数放开,训练集分数漂亮、测试集直接崩。下面按从简到繁的顺序拆,每个模型都给关键代码和参数取舍。
3.1 线性回归:基线怎么搭才不误导
线性回归的作用不是拿冠军,而是给其他六个模型提供一个“解释得了的下限”。如果 DNN 跑出来还不如线性回归,问题多半在数据或训练过程,而不是模型不够深。构建代码很短,重点在系数解释和残差检查:
# src/models/linear.py import numpy as np def fit_linear(pipe, X_train, y_train): pipe.fit(X_train, y_train) model = pipe.named_steps["model"] coefs = dict(zip(X_train.columns, model.coef_)) # 按绝对值排序,快速看出哪些特征在主导预测 top = sorted(coefs.items(), key=lambda kv: abs(kv[1]), reverse=True)[:5] return pipe, {"intercept": float(model.intercept_), "top_coef": top}coef_是在标准化之后算出来的,所以系数绝对值可以直接横向比较:数值越大,该特征对预测值的边际影响越强。但要注意,标准化后的系数不再对应原始量纲,业务方问“温度每升高一度影响多少”时,得把系数除以该特征的标准差还原回去。这个换算我第一次就漏了,汇报时数字对不上,被追问了半天。
残差检查比 R2 更有信息量。把残差对预测值画散点,如果出现喇叭口(误差随预测值增大而增大),说明目标做过对数变换会更合适;如果残差呈明显曲线,说明线性假设不成立,该上多项式或者树模型了。这一步不写进自动化脚本,但每次换数据集我都会手动看一遍。
3.2 岭回归与多项式回归:正则化强度和阶数的取舍
岭回归是在最小二乘的损失上加了 L2 惩罚,作用是把共线性强、方差大的系数往零压。它在代码上只是换一个类,但alpha的选取必须用交叉验证,不能拍脑袋:
# src/models/ridge.py from sklearn.linear_model import RidgeCV from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler, PolynomialFeatures def build_ridge_cv(alphas=(0.01, 0.1, 1.0, 10.0, 100.0)): return Pipeline([ ("scaler", StandardScaler()), ("model", RidgeCV(alphas=alphas, cv=5, scoring="neg_mean_squared_error")), ])RidgeCV会在训练集内部做 5 折交叉验证,选出让验证误差最小的alpha,训练完可以通过named_steps["model"].alpha_读回来。常见做法是把候选网格按数量级铺开,从 0.01 到 100 覆盖大部分场景;如果最优值落在网格边界上,说明范围不够,要往外扩。岭回归不会把系数压到恰好为零,想做特征筛选得换 Lasso,这是两件事。
多项式回归本质是“先造特征、再做带正则的线性回归”。单独用PolynomialFeatures加普通线性回归,几乎必然过拟合,所以实际写法是多项式特征 + 岭回归:
def build_poly_ridge(degree=2, alpha=1.0): return Pipeline([ ("poly", PolynomialFeatures(degree=degree, include_bias=False)), ("scaler", StandardScaler()), # 多项式项量纲差异大,必须重新标准化 ("model", Ridge(alpha=alpha)), ])degree=2时,10 个原始特征会扩到 65 个(含交互项);degree=3会到 285 个。特征数是组合增长,不是线性增长,所以阶数一定要卡死,我一般默认 2,只有在残差图明确显示二次曲线拟合不足时才试 3。include_bias=False是因为后面接的是带截距的 Ridge,重复加常数列没有意义。另外,多项式项之间量纲差距极大(平方项和一次项不是一个量级),标准化必须放在PolynomialFeatures之后,顺序反了等于没做。
3.3 决策树回归:非线性关系的快速验证
决策树回归的最大优势是“不需要特征工程也能抓住非线性和交互”,缺点是单棵树方差大。在汇总项目里它的定位是快速验证:如果树模型明显好于线性回归,说明数据里存在线性模型抓不住的结构,值得往集成模型或 DNN 上投入。
# src/models/tree.py from sklearn.tree import DecisionTreeRegressor def build_tree(max_depth=6, min_samples_leaf=5, seed=42): return DecisionTreeRegressor( max_depth=max_depth, # 限制深度,防止把训练集背下来 min_samples_leaf=min_samples_leaf, # 叶子最少样本数,抑制噪声切分 random_state=seed, )这两个参数是控制过拟合的主要旋钮。max_depth不设限时,树会一直分到每个叶子只有一个样本,训练集 R2 接近 1、测试集一塌糊涂。min_samples_leaf比max_depth更稳健,因为它直接约束每个叶子代表的样本量,小样本场景我一般从 5 到 20 之间试。树模型对特征尺度不敏感,所以不需要标准化,这一点和前面的回归族不同,混在一个管线里容易出错,这也是第 5 章要专门讲的坑之一。
用树模型还有一件事值得做:把feature_importances_打出来,和线性回归的标准化系数对照。如果两边排序差异很大,通常意味着存在非线性关系或者强交互,这时候再考虑多项式特征或者 DNN 才有依据。
3.4 深度神经网络预测:小样本下怎么不翻车
DNN 模块是这份资源里最容易劝退人的部分,因为它对数据量、标准化和训练轮次都敏感。汇总项目里常见的做法是用一个浅层 MLP,两到三层隐藏层,而不是堆很深的网络:
# src/models/dnn.py import torch import torch.nn as nn class MLPRegressor(nn.Module): def __init__(self, n_features, hidden=(64, 32), dropout=0.2): super().__init__() layers, prev = [], n_features for h in hidden: layers += [nn.Linear(prev, h), nn.ReLU(), nn.Dropout(dropout)] prev = h layers.append(nn.Linear(prev, 1)) # 回归输出单值 self.net = nn.Sequential(*layers) def forward(self, x): return self.net(x).squeeze(-1)训练循环里必须有三样东西:早停、学习率调度、固定随机种子。早停按验证集损失判断,连续若干轮不下降就停:
def train_dnn(model, train_loader, val_loader, epochs=200, lr=1e-3, patience=15): optimizer = torch.optim.Adam(model.parameters(), lr=lr) loss_fn = nn.MSELoss() best, wait = float("inf"), 0 for epoch in range(epochs): model.train() for xb, yb in train_loader: optimizer.zero_grad() loss = loss_fn(model(xb), yb) loss.backward() optimizer.step() model.eval() with torch.no_grad(): val_loss = sum(loss_fn(model(xb), yb).item() for xb, yb in val_loader) if val_loss < best - 1e-6: best, wait = val_loss, 0 else: wait += 1 if wait >= patience: break # 验证损失不再下降,提前结束 return modelhidden=(64, 32)是经验起点,特征维度只有几十时不要往 256 以上堆;dropout=0.2对小样本足够,数据量上千后再考虑调低。学习率1e-3配 Adam 是稳妥组合,如果 loss 前期震荡明显,先降到1e-4而不是换优化器。最重要的一条:输入必须先标准化,否则 ReLU 层很容易在早期就大面积失活,表现为 loss 几乎不降——这不是玄学,是尺度问题。
五个模型跑完,对比表大概是这样:
| 模型 | 是否需要标准化 | 关键超参 | 小样本表现 | 可解释性 |
|---|---|---|---|---|
| 线性回归 | 是 | 无 | 稳定 | 强,系数可直接解释 |
| 岭回归 | 是 | alpha | 稳定,共线时更优 | 强 |
| 多项式回归 | 是(特征展开后) | degree、alpha | 易过拟合 | 中等 |
| 决策树回归 | 否 | max_depth、min_samples_leaf | 方差大 | 中等,可看特征重要性 |
| 深度神经网络 | 是 | 层数、dropout、lr | 小样本易过拟合 | 弱 |
4. 概率与序列建模:贝叶斯网络的推断链路和马尔科夫模型的转移矩阵
前五个模型处理的都是“给一组特征,输出一个数值”。但真实预测需求里还有两类问题不适合这么建模:一是“哪些因素组合会导致故障”,这是概率图问题;二是“系统会从当前状态转移到哪个状态”,这是序列问题。这两块是这份汇总里最容易被跳过、但落地价值很高的部分。
4.1 贝叶斯网络:先定结构,再让数据填条件概率
贝叶斯网络由两部分组成:有向无环图(结构)和每个节点的条件概率表(参数)。结构可以靠领域知识画,参数用数据学。汇总项目常见的做法是用pgmpy实现,代码骨架如下:
# src/models/bayes_net.py import pandas as pd from pgmpy.models import BayesianNetwork from pgmpy.estimators import MaximumLikelihoodEstimator from pgmpy.inference import VariableElimination def build_bayes_net(df_discrete: pd.DataFrame): # 结构:温度 -> 振动,负载 -> 振动,振动 -> 故障 edges = [("temperature", "vibration"), ("load", "vibration"), ("vibration", "failure")] model = BayesianNetwork(edges) # 用最大似然估计条件概率表 model.fit(df_discrete, estimator=MaximumLikelihoodEstimator) return model, VariableElimination(model)edges定义的是因果假设,不是相关性,这一点必须和业务方对齐:箭头方向画反了,推断结果会完全不同。MaximumLikelihoodEstimator按频数统计条件概率,样本量小时会出现大量零概率,导致推断时某些组合直接为 0,常见做法是加平滑或者用贝叶斯估计器。
推断阶段的写法是“给定证据、问后验”:
def predict_failure(infer, evidence: dict): # 例:已知温度和振动状态,求故障概率分布 result = infer.query(variables=["failure"], evidence=evidence) return result.values # 对应 failure 各状态的取值概率evidence里的键必须和训练时的列名一致,取值必须是离散状态标签,传连续值会直接报错。所以贝叶斯网络前面一定要接一步分箱,把振动值这类连续变量切成“低/中/高”。分箱边界必须在训练集上确定,然后原样应用到测试集,否则就是数据泄漏,这个坑在第 5 章展开。
需要说明的是,不同版本的pgmpy类名有变动,部分版本里BayesianNetwork已改名为DiscreteBayesianNetwork。拿到压缩包先跑一遍import,报类不存在就按提示改类名,逻辑本身不变。
4.2 马尔科夫模型:转移矩阵怎么算、怎么用
马尔科夫模型假设下一状态只依赖当前状态。工程上最实用的形式是一阶离散马尔科夫链,核心就是一个状态转移矩阵。不需要复杂依赖,用 numpy 就能算清楚:
# src/models/markov.py import numpy as np def build_transition(states, n_states=3, smooth=1e-6): """states: 长度为 T 的状态序列,取值 0..n_states-1""" counts = np.zeros((n_states, n_states)) for cur, nxt in zip(states[:-1], states[1:]): counts[cur, nxt] += 1 counts += smooth # 拉普拉斯平滑,避免整行概率为 0 return counts / counts.sum(axis=1, keepdims=True) def forecast(trans, current_state, steps=1): # 多步预测等价于转移矩阵的幂 return np.linalg.matrix_power(trans, steps)[current_state]build_transition统计的是相邻时刻的状态对,smooth用于处理某些状态从未出现转移的情况,不加平滑时整行和为 0,做矩阵幂会得到 NaN。forecast返回的是steps步之后处于各状态的概率分布,取argmax就是最可能状态。
这套方法有两个前提必须检查:状态划分要一致,马尔科夫性要大致成立。状态划分一般用分位数或者业务阈值,把连续指标切成 3 到 5 档;状态太多会导致每个转移的样本量不足,矩阵稀疏得没法看。马尔科夫性可以用卡方检验粗略验证,但更实用的办法是看预测命中率——如果两步预测的准确率断崖式下跌,说明序列里的长期依赖比较强,一阶链不够用,得考虑高阶或者加特征。
4.3 两类模型的组合用法
在实际项目里,这两个模型经常不是二选一。常见组合是:用马尔科夫链预测未来若干步的状态分布,把“未来状态概率”作为特征喂给贝叶斯网络或者回归模型,用来预测具体数值。这样既有序列信息,又有可解释的概率输出。我在一个能耗预测的场景里这么试过,比单独用回归的 RMSE 低了约一成,代价是多维护一套状态划分逻辑,值不值得要看业务对精度的要求。
5. 避坑与排查:七个模型同仓时最容易翻车的五个地方
汇总项目的好处是对比方便,坏处是一个环节出错,七个模型的结果一起失真。下面五条是我在拆这类包时反复遇到的,按“现象 → 原因 → 解决”写,能直接对照排查。
5.1 线下分数高得离谱,上线直接掉一半
现象:交叉验证 R2 到 0.95,换一批新数据掉到 0.4 以下,模型像是换了一个。
原因:标准化或者分箱在切分之前做了全量拟合。最常见的是先StandardScaler().fit_transform(X)再train_test_split,测试集的均值方差泄漏进训练过程;贝叶斯网络的分箱边界如果也用全量数据算,同样的问题。
解决:把所有依赖统计量的步骤包进Pipeline,只在训练折上fit;分箱边界单独存成配置,测试阶段直接复用。检查办法很简单:把切分后的训练集和测试集分别算一遍均值,如果测试集均值显著影响 scaler 参数,说明哪里漏了。
5.2 树模型和线性模型混用一条管线,结果对不上
现象:同一个特征表,线性回归和决策树回归的预测分布差异极大,树模型的预测值全挤在几个离散档位上。
原因:把带StandardScaler的管线直接套给了决策树,或者反过来,把树模型塞进了需要标准化的位置。树模型不关心尺度,但如果你在树前面加了PolynomialFeatures,等于人为造了一堆冗余切分点,树会优先选这些组合特征,行为变得难以解释。
解决:按模型类型分两条管线,回归族走“补缺失 → 标准化 → 模型”,树模型走“补缺失 → 模型”。在注册表里显式区分,别图省事共用一个numeric_pipeline。
5.3 多项式阶数放开,训练秒完但内存爆了
现象:degree调到 3 或 4 之后,特征数从几十涨到几千,训练时间翻十倍,机器开始吃满内存。
原因:PolynomialFeatures生成的是所有单项式和交互项,特征数随维度组合增长。10 个特征degree=3是 285 维,20 个特征就是 1770 维,再加上标准化会复制一份矩阵。
解决:阶数默认锁死在 2,需要更高阶时先做特征筛选,把入模特征压到 10 个以内;或者改用include_bias=False配合Ridge,靠正则化压住无效项。跑之前先打印poly.n_output_features_确认维度,这个数字比任何经验判断都可靠。
5.4 DNN 的 loss 不下降,换了几组参数都没用
现象:训练几十轮,loss 停在初始值附近小幅震荡,预测值几乎等于目标均值。
原因:输入没有标准化,特征量纲差异大,ReLU 层大面积失活;或者学习率过大导致参数在最优解附近来回跳;也可能是目标值范围过大,MSE 量级吓人。
解决:先确认输入已经过StandardScaler;学习率从1e-3往下试到1e-4;目标值做标准化或者对数变换。排查时先拿 100 条样本做一次过拟合测试——如果模型连 100 条都拟合不了,说明是代码或数据问题,不是调参问题。
5.5 马尔科夫预测出现 NaN 或者概率全为 0
现象:matrix_power之后结果里出现 NaN,或者某状态的概率整行为 0。
原因:某些状态在训练序列里从未作为起点出现过,转移矩阵对应行全为 0,归一化时除以 0;或者状态编码不连续,n_states设小了,索引越界被静默截断。
解决:统计时加平滑项,归一化前检查每行和是否大于 0;状态编码用LabelEncoder统一映射到 0 到 n-1 的连续整数,并把映射表存下来,预测阶段复用同一张表。别在训练和预测时各写一套映射逻辑,这是最容易出错的地方。
6. 二次开发:把 train_all.py 改成能对外提供预测的模块
跑通七个模型只是第一步,真正要用起来,得把它们从脚本变成能被调用的预测模块。我的做法是先抽一个统一的predict入口,模型名和特征字典传进去,返回预测值,中间不关心底层是 sklearn 还是 PyTorch:
# scripts/predict.py import joblib import numpy as np def load_bundle(path): # 统一存成 dict:pipeline + 特征顺序 + 模型名 + 元信息 return joblib.load(path) def predict_one(bundle, features: dict) -> float: # 按训练时的特征顺序取值,缺字段直接报错而不是静默补 0 row = [features[name] for name in bundle["feature_names"]] X = np.asarray([row], dtype=float) return float(bundle["pipeline"].predict(X)[0])这个封装有三个必须保留的点。第一,feature_names要跟模型一起序列化,不能靠调用方猜顺序,字段错位是线上预测最常见的低级故障。第二,缺失字段直接抛异常,不做默认值填充,因为预测阶段的缺失和训练阶段的缺失含义不同,默默补中位数会让结果看起来正常但完全错误。第三,joblib只适合 sklearn 管线,DNN 要单独用torch.save存权重和输入维度,加载时重建网络结构,别指望一个序列化格式通吃。
模型选择上,我一般不在代码里写死,而是在配置里指定一个active_model,并保留一个降级链:主模型不可用时退到岭回归,再退到线性回归。汇总项目里模型多,这个降级成本很低,但能避免单点故障。
验证改造是否成功,我固定跑三个检查:同一份输入连续预测两次结果一致(排除随机性)、用训练集前 100 条重放预测与离线结果误差小于 1e-6(排除管线错位)、故意漏一个字段确认会报错(排除静默填充)。这三步加起来不到一分钟,但帮我挡过两次特征顺序被改的事故——那次是有人调整了 CSV 列顺序,脚本没报错,预测值整体偏移,靠重放检查才定位到。
从那以后我每次改完特征表,都强制把这三个检查走一遍再提交,哪怕只改了一个列名。项目拆到最后你会发现,模型本身很少出问题,出问题的永远是数据和接口的衔接处。希望这套拆法能帮你在自己的数据上少走几段弯路。
本文还有配套的精品资源,点击获取