☰
随机森林Python实现:从三行代码到可交付模型
2026/11/6 10:23:53 网站建设 项目流程

简介:面向希望动手理解随机森林算法原理与 Python 实现的学习者,这份资料以可运行代码加小型数据集的方式,帮助读者从零完成分类实验并观察模型训练与预测流程。压缩包共 4 个文件,包含 2 个 py 脚本、1 个 csv 数据集和 1 个 txt 阅读说明,整体约 34KB,体积轻便,适合课堂演示、自学复现与代码对照。已有 15746 人学习下载,说明其在入门实践场景中具有一定参考价值。资料内提供两份实现代码,一份来自网络,一份为作者整理编写,均配有详尽中文注释且已调试通过;结合 sonar 数据集可完成数据加载、特征处理、模型构建与结果评估,也便于比较不同写法、理解参数调整对准确率的影响。编程环境为 Python2.7,调参空间保留较多,适合继续改进。

1. 随机森林 Python 实现:三行代码和一份能交付的实现差在哪

很多人第一次写随机森林 Python 实现,套路就是三行:RandomForestClassifier()、fit、predict,跑完看到准确率 0.95 就以为收工。真把这套代码搬到自己的表格数据上,第一版结果往往很难看——不是报错,就是指标虚高、换个随机种子就大幅波动。这份代码包解决的正是这段落差:它把数据加载、训练切分、参数配置、评估和特征重要性拆成独立脚本,每个文件只干一件事,改一处不牵连别处。适合两类人:刚学完集成学习、想找一份能跑通的骨架的;以及手上有一份结构化 CSV、想快速拿到一个可解释基线模型的。下面按「文件怎么摆、参数怎么定、指标怎么看、坑在哪」的顺序拆开。

2. 拆包看结构:数据、特征、训练脚本怎么摆

拿到一份带数据集的随机森林代码,第一件事不是运行main.py,而是先确认它把「数据」和「模型」的边界划在哪里。边界模糊的代码包,通常用两周就会变成一团面——想换一份数据要先改训练脚本,想调参又要回头动加载逻辑。我一般会花十分钟先把目录和数据字段过一遍,再决定是直接用还是先重构。

2.1 目录与字段:动手前先确认这四件事

一份结构清晰的实现,通常是这种形态:data/放原始 CSV,src/放加载与训练脚本,outputs/放模型文件和指标报告,根目录一个run.py串流程。这个划分不是形式主义,它的实际价值在于:数据换了只动data/,参数换了只动配置,产物永远落在outputs/,不会出现「上次那个模型文件到底存哪了」这种问题。

动代码之前,我固定会确认四件事,缺一件后面就要返工:

  • 目标列是哪一个,它的取值是 0/1 二分类、多分类,还是连续值(连续值要走RandomForestRegressor,别拿分类器硬套)
  • 特征列里有几列是 ID、时间戳、姓名这类「高基数但无信息」的字段,这些必须在入模前剔除
  • 类别型特征有多少个,取值基数大不大,基数超过几十的类别列直接 one-hot 会把特征维度撑爆
  • 缺失值是NaN、空字符串,还是-1、999这种占位符

第三点最容易出问题。随机森林对特征尺度不敏感,但对特征维度和噪声很敏感,一列有 500 个取值的城市字段做 one-hot,等于往 500 个几乎全零的维度里塞噪声,重要性排序会被彻底搅乱。基数值超过 20 的类别列,我一般直接丢掉或者手工合并成十几组,而不是硬编码。

2.2 加载与切分:一行 stratify 省掉后面一堆麻烦

加载脚本看着简单,但它是后面所有指标的地基。下面这份是我在多个项目里反复用的写法,改动量小、容错够用:

# src/data_loader.py import pandas as pd from sklearn.model_selection import train_test_split FEATURE_COLS = ["age", "income", "tenure", "n_tickets", "avg_session"] TARGET_COL = "churn" def load_dataset(path: str): df = pd.read_csv(path) # 列名统一小写去空格,避免 "Age" 和 "age" 同时存在的低级错误 df.columns = [c.strip().lower() for c in df.columns] # 目标列如果是字符串标签,统一映射成 0/1,sklearn 对字符串标签支持有限 if df[TARGET_COL].dtype == object: df[TARGET_COL] = df[TARGET_COL].map({"yes": 1, "no": 0}) X = df[FEATURE_COLS].copy() y = df[TARGET_COL].astype(int) return X, y if __name__ == "__main__": X, y = load_dataset("data/customer_sample.csv") X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, # 留 20% 做最终评估,全程不参与调参 random_state=42, # 固定切分,保证每次跑出来的测试集是同一批 stratify=y # 按标签比例分层,正样本少的时候必加 ) print(X_train.shape, X_test.shape, y_train.mean(), y_test.mean())

几个参数值得单独说。stratify=y是防翻车的第一道闸:如果正样本只占 3%,随机切分有可能测试集里只剩个位数正样本,评估结果的方差大到没有参考价值,分层之后训练集和测试集的正样本比例被强制对齐。random_state不是「为了好看」,而是让你在两周后能复现同一批数据划分——模型指标对不上时,第一个要排除的就是切分变了。test_size=0.2是常规比例,样本量低于 2000 行时我会改成 0.25 甚至 0.3,宁可训练数据少一点,也要保证测试集里每类都有足够的样本支撑指标计算。

最后那行print里的y_train.mean()是我个人的习惯:训练集和测试集的正样本率应该几乎相等,差超过 0.01 就说明分层没生效或者标签里有脏值。

2.3 特征预处理:不用标准化,但这三件事必须做

随机森林基于特征阈值切分,对量纲不敏感,所以标准化、归一化这一步可以省掉——这也是它在表格数据上比逻辑回归、SVM 省事的地方。但省掉标准化不等于省掉预处理,下面三件事不做,模型要么报错,要么悄悄给你一个虚高的分数。

第一件是缺失值。RandomForestClassifier从早版本起就不接受NaN,直接喂进去会抛ValueError: Input contains NaN。常见做法是数值列填中位数、类别列填众数,或者干脆加一列缺失标记再填补,让模型自己学「缺失」这件事有没有信息量。我一般先看缺失率:低于 5% 的列直接填中位数,高于 40% 的列先问一句这列还值不值得留。

第二件是类别编码。随机森林不能直接吃字符串,LabelEncoder把类别映射成 0、1、2 会让树误以为这是有序的,Sex=female变成 1、male变成 0,这个顺序是人造的。基数小(10 以内)的类别列用 one-hot 最稳;基数大的列用目标编码更容易保住信息,但目标编码必须在训练集内部做,不然就是标签泄漏。

第三件是常量列和近常量列。某一列 99.8% 都是同一个值,它对划分几乎没有贡献,但会占一个特征重要性名额,稀释真正的强特征。我通常在加载后加一句df = df.loc[:, df.nunique() > 1]把常量列清掉,近常量的列则看业务判断。

# src/preprocess.py import pandas as pd from sklearn.impute import SimpleImputer def basic_clean(df: pd.DataFrame, num_cols, cat_cols): # 1) 常量列直接删 df = df.loc[:, df.nunique() > 1].copy() # 2) 数值列中位数填补 num_imp = SimpleImputer(strategy="median") df[num_cols] = num_imp.fit_transform(df[num_cols]) # 3) 类别列众数填补 + one-hot,drop_first 去掉共线的那一列 for c in cat_cols: df[c] = df[c].fillna(df[c].mode()[0]) df = pd.get_dummies(df, columns=cat_cols, drop_first=True) return df

这段代码里SimpleImputer必须fit在训练集上、transform到测试集上,直接对全量数据fit_transform是最典型的标签泄漏。drop_first=True的作用是去掉 one-hot 产生的冗余列,对随机森林来说不是必需(树模型不怕共线),但能少几个特征维度,训练快一点。

3. 训练主链路:从单棵树到森林的参数怎么定

预处理做完,就进入真正决定效果的一段。随机森林的参数不算多,但每一个都有明确的适用区间,拍脑袋填默认值跑出来的模型,通常既不是最好也不是最差,只是「看起来能用」。这一章按我实际的调参顺序写:先立对照组,再定森林规模,最后看输出。

3.1 先跑一棵决策树当对照组

跳过单棵树直接上森林,是我见过最常见的方法论缺失。随机森林的本质是「很多棵带随机性的树投票」,如果单棵树的效果和森林差不多,说明数据本身的信号就那么多,继续加树只是浪费算力;如果森林比单棵树好一大截,才说明 bagging 和特征随机性起了作用,这时候调max_features才有意义。

# src/baseline_tree.py from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import f1_score tree = DecisionTreeClassifier( max_depth=5, # 先限深,不加限制的树一定过拟合 min_samples_leaf=20, # 叶子最少 20 个样本,防止切出只含几个点的碎片叶子 random_state=42 ) tree.fit(X_train, y_train) pred_tree = tree.predict(X_test) print("单树 F1:", round(f1_score(y_test, pred_tree), 4))

max_depth=5和min_samples_leaf=20这两个值不是随便写的:限深控制模型复杂度,叶子最小样本数控制切分粒度,两者一起用能把单棵树压到一个「能看出趋势但不过拟合」的状态。这一步的产出不是最终模型,而是一个基准分数——后面森林跑出来如果连这个数都超不过,问题多半在数据或特征,不在模型。

3.2 n_estimators、max_depth、max_features 的取值区间

森林这一层的参数,我按影响从大到小排:n_estimators决定树的数量,max_depth决定单棵树的复杂度,max_features决定每次分裂时候选特征的多少,min_samples_leaf和min_samples_split控制叶子粒度,class_weight处理不平衡。

参数作用常用区间调大的后果
n_estimators树的数量100 起,常用 300~500指标上升后趋平,训练时间和内存线性增长
max_depth单棵树最大深度5~20,或不限太深必过拟合,太浅欠拟合
max_features每次分裂的候选特征数分类 sqrt(n),回归 n/3越大单树越强、树间相关性越高
min_samples_leaf叶子最小样本数1~50,样本少时调大越大模型越平滑,偏差上升
class_weight类别权重None 或 balancedbalanced 提升少数类召回,牺牲部分精度

n_estimators是最不需要纠结的一个:从 100 加到 300,指标通常有明显提升;300 加到 500 提升已经很微弱;再往上基本是拿电费换小数点后第三位。我的习惯是先固定 300 跑通全流程,等参数和特征都稳定了,再单独扫一次 100/300/500/800,画一条曲线确认拐点在哪。

max_features是随机森林之所以「随机」的关键。分类任务默认sqrt(n_features),回归默认1.0(也就是用全部特征,这一点常被误会)。它直接控制树与树之间的相关性:取值越小,单棵树越弱但彼此越独立,集成后的方差越低;取值越大,单棵树越强但树之间越像,退化成一个普通的 bagging。特征数在 20 到 200 之间时,我一般在sqrt(n)附近做一个小范围扫描,而不是死守默认值。

# src/train_rf.py from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report rf = RandomForestClassifier( n_estimators=300, # 树的数量,先给一个够用的值 max_depth=12, # 限制深度,控制单树复杂度 max_features="sqrt", # 分类任务默认,特征维度高时可显式写死数值 min_samples_leaf=3, # 叶子最小样本,缓解过拟合 class_weight="balanced", # 正样本少时开启,代价是精度可能略降 oob_score=True, # 打开袋外评估,白拿一个验证分数 n_jobs=-1, # 用满所有核,注意内存 random_state=42 ) rf.fit(X_train, y_train) pred = rf.predict(X_test) print("OOB:", round(rf.oob_score_, 4)) print(classification_report(y_test, pred, digits=4))

oob_score=True是随机森林相对其他模型的一个白拿的福利,每个样本在每棵树里约有三分之一概率没被抽到,这些「袋外样本」可以当验证集用,不需要额外切分。n_jobs=-1会占满所有 CPU 核,小数据上感觉不到,数据量上去之后内存峰值会翻几倍,这一点后面避坑章会单独说。

3.3 训练结束先看这三组输出

模型fit完,很多人直接看accuracy就结束了。我一般固定看三组:OOB 分数和测试集分数的差距、分类报告里各类的 precision/recall、以及正负样本预测概率的分布。

OOB 和测试集分数如果差得很多(比如 OOB 0.88、测试集 0.72),说明模型在袋外样本上表现好但在真正的新数据上掉下来,通常是训练集和测试集的分布不一致,或者测试集太小导致指标抖动。两者接近,才说明这个分数是可以信的。

分类报告里,二分类任务重点看少数类的 recall。准确率在类别不平衡时是彻底的误导指标:正样本占 5% 时,全预测成负类也有 95% 的准确率。class_weight="balanced"会把少数类的权重按比例放大,代价是整体准确率可能降一两个点,但少数类的召回通常能提十几个点——哪个更重要取决于业务,不是取决于数字好看。

第三组是预测概率的分布。把rf.predict_proba(X_test)[:, 1]拉出来看直方图,如果大量样本挤在 0.45 到 0.55 之间,说明模型对这批样本几乎没有区分度,只是被 0.5 的阈值硬切成了两类。这种情况要么加特征,要么改用predict_proba输出概率交给下游做分层决策,而不是死守 0.5。

4. 评估与可解释性:OOB、混淆矩阵和特征重要性

随机森林最大的卖点是「效果稳 + 能解释」,但这两点都得靠正确的评估方式兜住。评估做错,模型再准也是自欺;解释做错,特征重要性排序会把业务方带进沟里。这一章讲三件在交付前必须做的事。

4.1 OOB 分数:不用验证集也能估泛化

袋外评估的原理不复杂:自助采样(bootstrap)时每个样本被抽中的概率约 63.2%,剩下约 36.8% 没进训练集,这些样本就是这棵树的「袋外样本」。森林里每棵树都对属于自己袋外的样本做一次预测,把所有树的袋外预测聚合起来,就得到一个不依赖额外切分的泛化估计。

它的价值在于省数据。样本量只有几千行的时候,再切一个验证集出来会很心疼,OOB 让你在只用训练集的情况下就能判断模型有没有过拟合。但 OOB 有两个边界要知道:一是它随n_estimators增大而趋于稳定,树太少(比如 50 棵以下)时 OOB 波动很大;二是它不能替代测试集,因为 OOB 用的仍然是训练集的数据分布,对分布漂移毫无察觉。

# src/evaluate.py import numpy as np import pandas as pd from sklearn.metrics import confusion_matrix, roc_auc_score proba = rf.predict_proba(X_test)[:, 1] pred = (proba >= 0.5).astype(int) cm = confusion_matrix(y_test, pred) print(pd.DataFrame(cm, index=["真实-负", "真实-正"], columns=["预测-负", "预测-正"])) print("AUC:", round(roc_auc_score(y_test, proba), 4)) # 概率分布分桶,看模型是不是在 0.5 附近瞎猜 bins = pd.cut(proba, bins=[0, 0.2, 0.4, 0.5, 0.6, 0.8, 1.0]) print(bins.value_counts().sort_index())

roc_auc_score用的是概率而不是硬标签,它衡量的是「随机取一个正样本和一个负样本,正样本得分更高的概率」,对阈值不敏感,是比准确率更可靠的排序质量指标。后面那段分桶统计是我自己加的,纯粹为了肉眼判断模型有没有区分度,跑一次几秒钟,能省掉很多无效的调参。

4.2 混淆矩阵与分类报告:准确率骗人的时候

混淆矩阵要读的是四个格子背后的业务含义,不是四个数字。假阳性(预测为正、实际为负)和假阴性(预测为负、实际为正)的代价在绝大多数场景里并不对称:风控场景漏掉一个坏用户的代价远高于误拦一个好人,医疗筛查漏诊的代价远高于误报。这种情况下,阈值应该往少数类那边偏,而不是停在 0.5。

一个实操细节:调整阈值之后,分类报告要重新算,不能拿默认阈值的结果去汇报。我一般的做法是扫一遍 0.1 到 0.9 的阈值,把每个阈值下的 precision、recall、F1 列成一张小表,交给业务方选一个他们能接受的工作点,而不是我替他们拍板。

classification_report里的macro avg和weighted avg也要分清:macro是各类指标的直接平均,少数类的表现会被同等权重计入;weighted按样本数加权,少数类的影响被稀释。类别不平衡时看macro,看整体表现时看weighted,两个数差很多就说明模型在偏科。

4.3 特征重要性:impurity 和 permutation 要对着看

rf.feature_importances_用的是不纯度下降(mean decrease in impurity),计算快,但有一个公认的偏差:它偏爱取值多的连续特征和高基数类别特征,因为这类特征有更多的切分点可以选择,天然容易带来更大的不纯度下降。直接用这个排序去汇报,很容易得出「用户 ID 最重要」这种荒谬结论。

我的固定做法是两种重要性对着看:先看 impurity 重要性拿到一个初筛,再用置换重要性(permutation importance)在测试集上交叉验证。置换重要性的逻辑更朴素也更可信:把某一列的值随机打乱,看模型指标掉多少,掉得多说明这列重要,几乎不掉说明这列可有可无。它慢一些,但结论更接近真实。

# src/importance.py import numpy as np import pandas as pd from sklearn.inspection import permutation_importance imp = pd.Series(rf.feature_importances_, index=X_train.columns) perm = permutation_importance( rf, X_test, y_test, n_repeats=10, # 每列重复打乱 10 次,取均值降方差 random_state=42, scoring="f1" # 用和业务一致的指标,别默认 accuracy ) perm_s = pd.Series(perm.importances_mean, index=X_test.columns) compare = pd.DataFrame({"impurity": imp, "permutation": perm_s}) compare["rank_gap"] = compare["impurity"].rank(ascending=False) - \ compare["permutation"].rank(ascending=False) print(compare.sort_values("permutation", ascending=False).head(15))

n_repeats=10是精度和耗时的折中,重复次数越多均值的方差越小,10 次在中等规模数据上通常够用。scoring一定要显式指定成和业务指标一致的那个,默认用 accuracy 在不平衡数据上会得出完全错误的排序。最后那列rank_gap是我自己加的:两种重要性排名差距大的特征,通常就是高基数或强噪声的嫌疑对象,值得单独看一眼原始分布。

5. 避坑与排查:五条实打实踩过的记录

下面这五条,是我在把随机森林从「能跑」推到「敢交付」的过程中反复撞上的。每条按现象、原因、解决写,不展开理论。

5.1 训练集 0.99、测试集 0.62

现象:训练完打印训练集准确率 0.99,测试集只有 0.62,两个数放在一起像两个模型。

原因:树的深度完全没限制,min_samples_leaf默认是 1,每棵树都能一路切到每个叶子只剩一个样本,训练集被完整记住。随机森林虽然有 bagging 平均,但单树过拟合到极致时,平均也救不回来。

解决:先给max_depth设一个 10 到 15 的上限,同时把min_samples_leaf提到 3 到 20 之间,然后固定n_estimators=300重跑。如果训练测试差距还是很大,再回头查特征里有没有泄漏列(比如和目标列强相关的衍生字段)。

5.2 特征重要性第一名是一列 ID

现象:feature_importances_排出来,第一位是一个看着就应该是标识符的列,业务方当场质疑整个模型。

原因:这一列的基数极高(几乎每行一个取值),不纯度重要性对这类特征有天生的偏爱,树可以用它切出大量碎片化的分支,每次切分都能带来一点不纯度下降,累积起来就排到了第一。

解决:入模前把所有 ID、时间戳、流水号类字段全部剔除;如果业务上确实需要保留,就改用置换重要性做最终排序。判断标准很简单——这列乱序打乱之后指标几乎不变,那它本来就不该在模型里。

5.3 n_jobs=-1 之后内存占用立刻升高

现象:小数据上跑得好好的脚本,换到几万行、上百个特征的数据上,进程被杀,日志里只留下一句 MemoryError。

原因:n_jobs=-1会为每个 CPU 核复制一份训练数据,核数乘以数据体积就是内存峰值。数据本身在内存里占 2G,16 核跑起来峰值能到 20G 以上,普通机器和容器环境都扛不住。

解决:把n_jobs显式设成一个具体值(4 或 8),用top或容器监控看一眼内存曲线再定。另外可以在训练前把浮点特征降成 float32,内存直接减半,精度损失在随机森林这种阈值切分模型上基本看不出来。

5.4 类别特征直接丢进去报错

现象:ValueError: could not convert string to float,报错行指向fit。

原因:数据里还有字符串列没编码,pandas读进来是object类型,sklearn 不接受。

解决:入模前跑一句X_train.dtypes[X_train.dtypes == object]把字符串列揪出来,逐列决定是 one-hot 还是丢弃。这一步最好固化到加载脚本里,而不是每次训练前手动查一遍。另外要确认训练集和测试集的列完全一致——one-hot 之后测试集少一列是高频翻车点,用X_test = X_test.reindex(columns=X_train.columns, fill_value=0)兜一句。

5.5 random_state 不写,两次结果对不上

现象:同一个人、同一份数据、同一份代码,跑两次指标差两个点,争论到底是数据变了还是模型不行。

原因:RandomForestClassifier不设random_state时,每次的 bootstrap 采样和特征子集选择都不同,结果自然有波动。数据量越小,波动越大。

解决:random_state在切分、模型、置换重要性三个地方全部显式写死。同时要建立一个认知——固定种子是为了「可复现地调试」,不是为了「掩盖方差」。真正判断模型稳不稳,得跑 5 折交叉验证看均值和标准差,单次结果没有任何说服力。

6. 进阶:把小网格搜索和交叉验证串成一条能复现的流程

参数和评估都跑通之后,最后一步是把「调参」这件事变成一条可复现的流水线。我见过太多人调参是手动改数字、跑一次、记一下、再改,跑了二十次之后完全说不清哪个组合对应哪个分数,血泪经验。正确做法是把搜索空间、交叉验证和结果写入文件一次性写完,让机器去跑。

6.1 网格范围怎么框,才不浪费机时

网格搜索的耗时是各参数取值个数的乘积乘以折数再乘以单次训练时间,参数一多就爆炸。我的做法是先做单参数扫描,确认每个参数的拐点在哪,再在拐点附近做小网格。

# src/tune.py import json import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV, StratifiedKFold param_grid = { "n_estimators": [200, 300, 500], "max_depth": [8, 12, 16], "min_samples_leaf": [1, 3, 8], "max_features": ["sqrt", 0.3] } cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) gs = GridSearchCV( estimator=RandomForestClassifier(class_weight="balanced", n_jobs=4, random_state=42), param_grid=param_grid, scoring="f1", # 和业务指标对齐,别用默认 accuracy cv=cv, n_jobs=1, # 外层不再并行,避免嵌套并行把内存打满 verbose=1, return_train_score=True # 保留训练分数,用来看过拟合程度 ) gs.fit(X_train, y_train) print("best:", gs.best_params_, round(gs.best_score_, 4)) cvdf = pd.DataFrame(gs.cv_results_) cvdf.to_csv("outputs/grid_results.csv", index=False) with open("outputs/best_params.json", "w") as f: json.dump(gs.best_params_, f, indent=2)

这个网格一共 3×3×3×2=54 组,5 折就是 270 次训练,300 棵树的量级下在普通笔记本上大概几十分钟,属于可接受的区间。n_jobs=1这个设置容易引起争议:内层模型已经用 4 个核并行建树,外层搜索再开并行会形成嵌套,内存峰值成倍上涨,还不如老老实实串行跑。return_train_score=True的用处是拿训练分数和验证分数对比,如果某组参数训练 0.99、验证 0.7,那组参数直接淘汰,不用等它排到第一才发现过拟合。

选参数时不要只盯best_score_。我的习惯是看cv_results_里前五名的均值和标准差,选那个「均值高、标准差小」的组合,而不是均值最高但标准差大的。差 0.005 的均值换 0.03 的标准差下降,在真实数据上通常是划算的。

6.2 把最优参数、模型和评估报告一起保存到文件

调参调完就结束的流程,两周后必然返工。交付前的最后一步是把最优参数、训练好的模型、测试集指标、特征重要性一起落盘,形成一份能对得上的记录。

# src/finalize.py import joblib from sklearn.metrics import classification_report best = gs.best_estimator_ # 已经是全量训练集上 refit 过的模型 joblib.dump(best, "outputs/rf_model.joblib") # 保存整模型,下游直接 load 即可 report = classification_report(y_test, best.predict(X_test), output_dict=True) pd.DataFrame(report).T.to_csv("outputs/test_report.csv") fi = pd.Series(best.feature_importances_, index=X_train.columns).sort_values(ascending=False) fi.to_csv("outputs/feature_importance.csv", header=["importance"])

GridSearchCV在找到最优参数后默认会用全量训练数据重新fit一次,best_estimator_拿到的就是这个 refit 后的模型,不需要再手动训一遍。joblib比pickle更适合保存带大量 numpy 数组的 sklearn 模型,读写更快。三份产出放在一起,任何人拿到这个目录,都能回答「用了哪些参数、测试集多少分、哪些特征在起作用」这三个问题。

我现在的固定习惯是:任何一次要拿去汇报的模型,必须先跑完这条流水线,参数、分数、重要性三份文件缺一不可,缺了就不许进汇报材料。吃过一次亏——半年前的一个基线模型,特征重要性截图还在,参数和随机种子找不到了,只能推倒重跑,白丢两天。所以别嫌这一步麻烦,它是唯一能让你在两个月后还说得清模型是怎么来的东西。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询