ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

机器学习学习器分类与实战:监督与无监督算法选型指南

机器学习学习器分类与实战:监督与无监督算法选型指南

1. 从一堆模型里挑花眼说起:ML学习器的分类逻辑

刚接触机器学习那会儿,我最头疼的不是数学公式,而是sklearn里那一长串模型名字。分类的、回归的、聚类的混在一起,文档翻来翻去也不知道该用哪个。后来项目做多了才慢慢摸出门道:学习器不是按名字分的,是按“你手里有什么数据、想解决什么问题”分的。这个分类逻辑搞清楚了,选模型就是查表的事。

机器学习里的“学习器”说白了就是一个从数据里找规律的函数。给它一批样本,它输出一个能对新样本做判断的规则。按照训练信号的不同,常用的学习器可以分成四大类:监督学习、无监督学习、半监督学习和强化学习。日常工作中百分之八十的场景落在前两类,所以这篇主要围绕监督和无监督展开,把每类里最常用的几个学习器拆开讲,配上可以直接跑的Python代码。

这篇文章适合谁看?如果你刚学完Python基础,想上手sklearn但不知道从哪开始,或者你已经会调几个模型但说不清楚它们之间的区别和适用边界,那这篇就是写给你的。我会按“原理一句话说清、代码直接能跑、参数怎么调、坑在哪里”这个节奏来,每个学习器都给出完整的实现和调参经验。

提示:文中所有代码基于scikit-learn 1.3以上版本,Python 3.9+。不同版本API可能有细微差异,遇到报错先检查版本。

2. 监督学习学习器:有标签数据的主力选手

监督学习的核心特征是训练数据里既有输入特征X,也有对应的标签y。学习器的任务就是找到X到y的映射关系。根据y的类型不同,又分为分类问题(y是离散类别)和回归问题(y是连续数值)。这两类问题用的学习器有重叠但不完全一样,下面分别拆解。

2.1 线性模型:最容易被低估的基线

很多人一上来就上神经网络,结果发现线性回归调半天还不如老老实实用逻辑回归。线性模型的价值在于它是所有复杂模型的基线,如果线性模型效果已经很好了,说明问题本身不复杂,没必要上重型武器。

线性回归的数学形式很直白:y = w1x1 + w2x2 + ... + wnxn + b。训练过程就是找一组w和b,让预测值和真实值的平方误差最小。这个“最小二乘”的解法有解析解,但sklearn用的是梯度下降或SVD分解,数值上更稳定。

from sklearn.linear_model import LinearRegression, Ridge, Lasso from sklearn.datasets import make_regression from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score # 生成模拟数据 X, y = make_regression(n_samples=500, n_features=10, noise=15, random_state=42) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 普通线性回归 lr = LinearRegression() lr.fit(X_train, y_train) y_pred = lr.predict(X_test) print(f"线性回归 R2: {r2_score(y_test, y_pred):.4f}") print(f"线性回归 RMSE: {mean_squared_error(y_test, y_pred, squared=False):.4f}") # Ridge回归(L2正则) ridge = Ridge(alpha=1.0) ridge.fit(X_train, y_train) print(f"Ridge R2: {r2_score(y_test, ridge.predict(X_test)):.4f}") # Lasso回归(L1正则) lasso = Lasso(alpha=0.1) lasso.fit(X_train, y_train) print(f"Lasso R2: {r2_score(y_test, lasso.predict(X_test)):.4f}") print(f"Lasso非零系数个数: {sum(lasso.coef_ != 0)}")

Ridge和Lasso的区别值得单独说。Ridge加的是L2正则,会让系数整体变小但不为零;Lasso加的是L1正则,会把不重要的特征系数直接压到零,相当于自动做了特征选择。我试过在一个有200个特征但实际只有15个有用的数据集上跑,Lasso直接把185个无关特征的系数归零了,模型可解释性一下子提上来。

调参经验:alpha是正则强度的关键参数。alpha越大,正则越强,模型越保守。我一般从0.01、0.1、1、10、100这几个值里用交叉验证挑。如果alpha调到很大R2还很高,说明特征本身信噪比高;如果alpha稍微大一点R2就崩,说明模型过拟合严重,需要更多数据或更强正则。

逻辑回归虽然名字里有“回归”,但它是分类器。它在线性输出外面套了一个sigmoid函数,把输出压到0到1之间,表示属于正类的概率。多分类场景下用softmax扩展。逻辑回归的训练目标是最小化交叉熵损失,没有解析解,用梯度下降迭代求解。

from sklearn.linear_model import LogisticRegression from sklearn.datasets import make_classification from sklearn.metrics import classification_report, confusion_matrix X_clf, y_clf = make_classification(n_samples=1000, n_features=20, n_informative=10, n_redundant=5, random_state=42) X_train_c, X_test_c, y_train_c, y_test_c = train_test_split(X_clf, y_clf, test_size=0.2, random_state=42) log_reg = LogisticRegression(C=1.0, max_iter=1000, solver='lbfgs') log_reg.fit(X_train_c, y_train_c) y_pred_c = log_reg.predict(X_test_c) print(classification_report(y_test_c, y_pred_c))

逻辑回归的C参数和Ridge的alpha正好相反:C越小正则越强。这个容易搞混,我一开始也记反过。记法就是“C是惩罚的倒数”,C大惩罚小,C小惩罚大。

2.2 树模型:表格数据的王者

如果你问我表格数据用什么模型最稳,我会毫不犹豫说树模型。决策树、随机森林、梯度提升树这一家子在结构化数据上的表现,至今没有被神经网络全面超越。树模型的优势在于:不需要特征缩放、能自动处理非线性关系、对缺失值有一定容忍度、可解释性好。

决策树是最基础的树模型。它通过递归地选择最优特征和切分点,把特征空间划分成若干个区域,每个区域给一个预测值。分类树用基尼系数或信息熵选切分点,回归树用均方误差。

from sklearn.tree import DecisionTreeClassifier, export_text from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.metrics import accuracy_score # 单棵决策树 dt = DecisionTreeClassifier(max_depth=5, min_samples_split=10, min_samples_leaf=5, random_state=42) dt.fit(X_train_c, y_train_c) print(f"决策树准确率: {accuracy_score(y_test_c, dt.predict(X_test_c)):.4f}") # 随机森林 rf = RandomForestClassifier(n_estimators=200, max_depth=10, min_samples_leaf=3, n_jobs=-1, random_state=42) rf.fit(X_train_c, y_train_c) print(f"随机森林准确率: {accuracy_score(y_test_c, rf.predict(X_test_c)):.4f}") # 梯度提升树 gbdt = GradientBoostingClassifier(n_estimators=200, learning_rate=0.05, max_depth=4, subsample=0.8, random_state=42) gbdt.fit(X_train_c, y_train_c) print(f"GBDT准确率: {accuracy_score(y_test_c, gbdt.predict(X_test_c)):.4f}")

决策树最大的问题是容易过拟合。一棵不加限制的树可以把训练集每个样本都分对,但测试集上惨不忍睹。控制过拟合的手段有:限制树的最大深度、限制叶子节点最少样本数、限制分裂所需最少样本数、剪枝。我一般先用max_depth=5试,如果欠拟合再逐步加深。

随机森林用bagging思想,在训练集上做有放回抽样,训练多棵决策树,然后投票或取平均。它比单棵树稳得多,而且几乎不需要调参就能有不错的效果。n_estimators一般设100到500之间,再多收益递减。max_features控制每棵树分裂时考虑的特征数,分类问题默认是sqrt(n_features),回归问题默认是n_features。

梯度提升树用boosting思想,每棵树拟合前面所有树的残差,逐步逼近真实值。它的效果通常比随机森林好,但更容易过拟合,需要更仔细地调参。learning_rate和n_estimators是一对需要联合调节的参数:learning_rate小就需要更多棵树,learning_rate大就需要更少棵树。我一般从learning_rate=0.1、n_estimators=100开始,如果欠拟合就增加n_estimators或提高learning_rate。

注意:树模型不需要标准化,但如果你用Pipeline把树模型和其他模型串在一起,标准化步骤会作用到树模型上,虽然不影响效果但浪费计算。建议分开处理。

2.3 支持向量机:小样本高维场景的利器

SVM的核心思想是找一个超平面,让两类样本之间的间隔最大化。对于线性不可分的数据,通过核函数把数据映射到高维空间,在高维空间里找线性超平面。常用的核函数有线性核、多项式核、RBF核和sigmoid核。

from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline # SVM对特征缩放敏感,必须标准化 svm_pipe = Pipeline([ ('scaler', StandardScaler()), ('svm', SVC(C=1.0, kernel='rbf', gamma='scale', probability=True)) ]) svm_pipe.fit(X_train_c, y_train_c) print(f"SVM准确率: {accuracy_score(y_test_c, svm_pipe.predict(X_test_c)):.4f}")

SVM有两个关键参数:C和gamma。C控制对误分类的惩罚力度,C大表示不允许误分类,容易过拟合;C小表示允许一些误分类,泛化能力更强。gamma控制RBF核的“影响半径”,gamma大表示每个样本的影响范围小,容易过拟合;gamma小表示影响范围大,容易欠拟合。

我个人的调参顺序是:先固定gamma='scale',在[0.1, 1, 10, 100]里调C;找到最优C后,再在[0.001, 0.01, 0.1, 1]里调gamma。SVM在样本量小于一万、特征维度高的场景下表现很好,但样本量大了训练时间会爆炸,因为它的时间复杂度是O(n²)到O(n³)。

2.4 K近邻:最直观的懒惰学习器

KNN的思路简单到不需要解释:一个新样本的类别由离它最近的K个邻居投票决定。它没有显式的训练过程,所有计算都发生在预测阶段,所以叫“懒惰学习”。

from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier(n_neighbors=5, weights='distance', metric='minkowski', p=2) knn.fit(X_train_c, y_train_c) print(f"KNN准确率: {accuracy_score(y_test_c, knn.predict(X_test_c)):.4f}")

KNN的K值选择很关键。K太小,模型对噪声敏感,容易过拟合;K太大,模型过于平滑,容易欠拟合。我一般从sqrt(n_samples)附近开始试,然后上下调整。weights='distance'表示距离近的邻居投票权重更大,通常比'uniform'效果好。

KNN最大的问题是预测慢,因为每次预测都要计算新样本到所有训练样本的距离。如果训练集有十万条,每次预测都要算十万次距离,线上服务根本扛不住。所以KNN适合小数据集或者离线批量预测的场景。

3. 无监督学习学习器:没有标签也能挖出结构

无监督学习的训练数据只有X没有y,目标是发现数据内部的结构。最常见的两类任务是聚类和降维。聚类把相似的样本分到一组,降维把高维数据压缩到低维同时保留主要信息。

3.1 K-Means聚类:快而简单的分组工具

K-Means的目标是把n个样本分成k个簇,使得簇内样本尽量相似,簇间样本尽量不同。算法流程是:随机选k个初始中心点,把每个样本分配到最近的中心点,重新计算每个簇的中心点,重复直到中心点不再变化。

from sklearn.cluster import KMeans from sklearn.datasets import make_blobs from sklearn.metrics import silhouette_score import numpy as np X_blob, y_blob = make_blobs(n_samples=1000, centers=4, n_features=2, random_state=42) # 用肘部法确定k值 inertias = [] silhouettes = [] K_range = range(2, 11) for k in K_range: km = KMeans(n_clusters=k, n_init=10, random_state=42) km.fit(X_blob) inertias.append(km.inertia_) silhouettes.append(silhouette_score(X_blob, km.labels_)) # 打印结果辅助判断 for k, inertia, sil in zip(K_range, inertias, silhouettes): print(f"k={k}, 惯性={inertia:.2f}, 轮廓系数={sil:.4f}")

K-Means有两个坑我踩过。第一个是初始中心点的选择,随机初始化可能收敛到局部最优。sklearn默认用k-means++初始化,比纯随机好很多,但n_init参数控制重启次数,我一般设10以上。第二个是K-Means假设簇是球形的,如果数据是长条形或环形,K-Means会分错。这时候要换DBSCAN或谱聚类。

确定k值的方法,肘部法看inertia下降的拐点,轮廓系数看峰值。但这两个指标都不是绝对的,最终还是要结合业务解释。我做过一个用户分群的项目,轮廓系数在k=3时最高,但业务方认为分成5群更有运营价值,最后用了k=5。

3.2 主成分分析:降维保留主要信息

PCA通过线性变换把原始特征投影到方差最大的方向上。第一个主成分是方差最大的方向,第二个主成分是与第一个正交且方差最大的方向,以此类推。PCA常用于可视化、去噪和特征压缩。

from sklearn.decomposition import PCA from sklearn.preprocessing import StandardScaler # PCA前必须标准化 scaler = StandardScaler() X_scaled = scaler.fit_transform(X_clf) pca = PCA(n_components=0.95) # 保留95%方差 X_pca = pca.fit_transform(X_scaled) print(f"原始特征数: {X_clf.shape[1]}, 降维后特征数: {X_pca.shape[1]}") print(f"各主成分方差解释比: {pca.explained_variance_ratio_}") print(f"累计方差解释比: {np.cumsum(pca.explained_variance_ratio_)}")

n_components可以设成整数(保留几个主成分)或小数(保留多少比例的方差)。我一般先设0.95看降到几维,如果降维后维度还是很高,说明原始特征之间相关性不强,PCA帮助有限。

PCA的局限是它是线性降维,对非线性结构无能为力。如果数据在低维空间是弯曲的流形,PCA会破坏结构。这时候可以用t-SNE或UMAP做可视化,但这两个主要用于可视化而不是特征压缩,因为它们的变换不可逆且计算量大。

3.3 DBSCAN:能发现任意形状簇的密度聚类

DBSCAN基于密度定义簇:核心点是周围eps半径内至少有min_samples个点的点,边界点在核心点的邻域内但不是核心点,噪声点既不是核心点也不是边界点。簇由核心点和它们邻域内的边界点组成。

from sklearn.cluster import DBSCAN from sklearn.datasets import make_moons X_moon, y_moon = make_moons(n_samples=500, noise=0.05, random_state=42) dbscan = DBSCAN(eps=0.3, min_samples=5) labels = dbscan.fit_predict(X_moon) n_clusters = len(set(labels)) - (1 if -1 in labels else 0) n_noise = list(labels).count(-1) print(f"簇数量: {n_clusters}, 噪声点数量: {n_noise}")

DBSCAN的eps和min_samples需要根据数据分布调。eps太小会把所有点都当成噪声,eps太大会把所有点合并成一个簇。我一般先用k距离图找拐点来确定eps:计算每个点到第k近邻的距离,排序后画图,拐点位置就是合适的eps。

DBSCAN的优势是不需要预先指定簇数量,能发现任意形状的簇,还能识别噪声点。缺点是当数据密度不均匀时,一组参数很难同时适应高密度和低密度区域。这种场景下可以用HDBSCAN,它是DBSCAN的层次化改进版,对密度变化更鲁棒。

4. 模型评估与选择:别只看准确率

模型训完了,怎么知道它好不好?准确率是最直观的指标,但在类别不平衡的场景下会骗人。比如一个二分类问题,正样本占1%,模型把所有样本都预测为负类,准确率也有99%,但这个模型毫无价值。

4.1 分类问题的评估指标矩阵

分类问题常用的指标有精确率、召回率、F1分数、AUC-ROC。精确率是预测为正的样本中真正为正的比例,召回率是真正为正的样本中被预测出来的比例。F1是精确率和召回率的调和平均。AUC-ROC衡量模型对正负样本的排序能力,不受阈值影响。

from sklearn.metrics import (precision_score, recall_score, f1_score, roc_auc_score, roc_curve, precision_recall_curve) y_prob = log_reg.predict_proba(X_test_c)[:, 1] y_pred = log_reg.predict(X_test_c) print(f"精确率: {precision_score(y_test_c, y_pred):.4f}") print(f"召回率: {recall_score(y_test_c, y_pred):.4f}") print(f"F1: {f1_score(y_test_c, y_pred):.4f}") print(f"AUC-ROC: {roc_auc_score(y_test_c, y_prob):.4f}")

选择哪个指标取决于业务场景。如果误报代价高(比如垃圾邮件过滤,把正常邮件判成垃圾很严重),优先看精确率;如果漏报代价高(比如疾病筛查,漏掉一个病例很严重),优先看召回率。大多数场景下用F1或AUC-ROC做综合评估。

4.2 交叉验证与超参数搜索

单次划分训练集和测试集的结果有随机性,交叉验证能给出更稳定的评估。K折交叉验证把数据分成K份,每次用K-1份训练、1份验证,重复K次取平均。

from sklearn.model_selection import cross_val_score, GridSearchCV, RandomizedSearchCV from scipy.stats import randint, uniform # 交叉验证 cv_scores = cross_val_score(rf, X_train_c, y_train_c, cv=5, scoring='f1') print(f"5折交叉验证F1: {cv_scores.mean():.4f} (+/- {cv_scores.std():.4f})") # 网格搜索 param_grid = { 'n_estimators': [100, 200, 300], 'max_depth': [5, 10, 15, None], 'min_samples_leaf': [1, 3, 5] } grid_search = GridSearchCV(RandomForestClassifier(random_state=42), param_grid, cv=5, scoring='f1', n_jobs=-1) grid_search.fit(X_train_c, y_train_c) print(f"最佳参数: {grid_search.best_params_}") print(f"最佳F1: {grid_search.best_score_:.4f}") # 随机搜索(参数空间大时更高效) param_dist = { 'n_estimators': randint(100, 500), 'max_depth': randint(3, 20), 'min_samples_leaf': randint(1, 10) } random_search = RandomizedSearchCV(RandomForestClassifier(random_state=42), param_dist, n_iter=50, cv=5, scoring='f1', n_jobs=-1, random_state=42) random_search.fit(X_train_c, y_train_c) print(f"随机搜索最佳参数: {random_search.best_params_}")

网格搜索适合参数少、每个参数候选值少的情况。如果参数多、候选值多,网格搜索的组合数会爆炸,这时候用随机搜索,在参数空间里随机采样,通常用更少的计算量就能找到接近最优的参数。

提示:交叉验证的scoring参数要跟业务指标对齐。如果业务关心召回率,就设scoring='recall',不要默认用准确率。

5. 实操中的常见问题与排查技巧

模型跑起来只是第一步,实际项目中会遇到各种意料之外的问题。这一节整理我踩过的坑和排查思路,按问题类型组织成速查表。

5.1 数据层面的问题

特征尺度不一致导致模型不收敛。线性模型、SVM、KNN、神经网络都对特征尺度敏感。如果一个特征范围是0到1,另一个是0到10000,梯度下降会走之字形,收敛很慢甚至不收敛。解决办法是用StandardScaler或MinMaxScaler统一尺度。树模型不需要这个步骤。

类别不平衡导致模型偏向多数类。如果正负样本比例是1比100,模型会把所有样本预测为多数类来降低损失。解决办法有:调整class_weight='balanced'、对少数类过采样、对多数类欠采样、用SMOTE生成合成样本。我一般先试class_weight,不行再上采样。

缺失值处理不当引入偏差。sklearn的大多数模型不接受缺失值。简单的做法是均值/中位数填充,但会低估方差。更好的做法是用KNNImputer或IterativeImputer,利用其他特征的信息来填充。如果缺失比例超过50%,考虑直接删掉这个特征。

5.2 模型训练层面的问题

过拟合:训练集表现好,测试集表现差。判断标准是训练集和验证集的指标差距大。解决办法:增加正则化强度、减少模型复杂度、增加训练数据、用早停(对梯度提升树)。我一般先画学习曲线,看是数据量不够还是模型太复杂。

欠拟合:训练集和测试集表现都差。说明模型容量不够。解决办法:增加模型复杂度、增加特征、减少正则化强度。如果线性模型欠拟合,试试多项式特征或换树模型。

训练时间过长。树模型可以设n_jobs=-1并行;SVM在大样本上很慢,考虑换线性SVM或降维;神经网络用GPU加速。如果数据量太大,先用小样本调参,确定参数范围后再全量训练。

5.3 常见问题速查表

问题现象可能原因排查方法解决方案
训练集准确率远高于测试集过拟合画学习曲线增加正则、减少复杂度、增加数据
训练集和测试集都差欠拟合检查特征信息量增加特征、提高模型复杂度
模型不收敛特征尺度不一致检查特征范围标准化/归一化
预测结果偏向多数类类别不平衡看混淆矩阵class_weight、采样、SMOTE
交叉验证方差大数据量少或分布不均看每折得分增加数据、分层抽样
特征重要性全是零特征无区分度或正则过强检查特征与标签相关性换特征、降低正则强度

5.4 几个容易被忽略的实操细节

随机种子要固定。模型初始化、数据划分、采样都有随机性,不固定种子结果不可复现。我一般在项目开始时设一个全局随机种子,所有涉及随机的地方都用它。

Pipeline能避免数据泄露。如果在划分训练测试集之前做了标准化,测试集的信息就泄露到了训练过程中。正确做法是把标准化和模型封装进Pipeline,在交叉验证内部做标准化。

from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression pipe = Pipeline([ ('scaler', StandardScaler()), ('clf', LogisticRegression()) ]) # 交叉验证时标准化只在训练折上fit scores = cross_val_score(pipe, X_clf, y_clf, cv=5)

保存模型用joblib而不是pickle。joblib对numpy数组的序列化效率更高,模型文件更小,加载更快。

import joblib joblib.dump(rf, 'random_forest_model.joblib') loaded_model = joblib.load('random_forest_model.joblib')

特征重要性只能看不能全信。树模型给出的特征重要性是基于分裂增益的,对于高基数特征(取值多的类别特征)会高估。更可靠的方法是用permutation importance,打乱某个特征的值看模型性能下降多少。

from sklearn.inspection import permutation_importance perm_imp = permutation_importance(rf, X_test_c, y_test_c, n_repeats=10, random_state=42) for i in perm_imp.importances_mean.argsort()[::-1][:5]: print(f"特征{i}: {perm_imp.importances_mean[i]:.4f} +/- {perm_imp.importances_std[i]:.4f}")

6. 学习器选型的实战决策路径

说了这么多学习器,实际项目中怎么选?我总结了一个决策路径,按顺序问自己几个问题,基本能定位到合适的模型范围。

第一个问题:有标签吗?有标签走监督学习,没标签走无监督学习。有标签但标签很少,考虑半监督学习或先用无监督做特征再少量标签微调。

第二个问题:标签是离散还是连续?离散走分类,连续走回归。有序离散(比如评分1到5)可以当分类也可以当回归,我一般先试分类,因为分类对异常值更鲁棒。

第三个问题:数据量多大?样本量小于一千,优先SVM或逻辑回归,树模型容易过拟合。样本量一千到十万,随机森林和梯度提升树是首选。样本量超过十万,考虑线性模型或LightGBM这类高效实现。

第四个问题:需要可解释性吗?需要的话用线性模型或单棵决策树。不需要的话随机森林和梯度提升树效果更好。如果业务方要求解释每个预测的原因,逻辑回归的系数和决策树的规则是最容易说清楚的。

第五个问题:特征维度高吗?维度高样本少,用SVM或带正则的线性模型。维度低样本多,树模型和KNN都可以。

这套路径不是绝对的,但能帮你快速缩小范围。实际项目中我通常会同时跑三四个模型做对比,用交叉验证选最优的。有时候简单模型的效果和复杂模型差不多,那就选简单的,维护成本低。

提示:不要一上来就调参。先用默认参数跑一遍所有候选模型,看哪个方向有潜力,再针对性地调。我见过太多人在一个明显不合适的模型上调了半天参数,不如换个模型来得快。

7. 从sklearn到生产:模型部署的轻量方案

模型在notebook里跑通只是完成了百分之三十的工作,剩下百分之七十是怎么让它稳定地在生产环境里提供服务。这里不展开完整的MLOps,只说几个轻量级的落地方式。

最简单的方式是用Flask或FastAPI把模型包成一个HTTP接口。模型在服务启动时加载到内存,请求来了直接调predict。这种方式适合QPS不高的内部服务。

from fastapi import FastAPI from pydantic import BaseModel import joblib import numpy as np app = FastAPI() model = joblib.load('random_forest_model.joblib') class PredictRequest(BaseModel): features: list @app.post("/predict") def predict(req: PredictRequest): X = np.array(req.features).reshape(1, -1) pred = model.predict(X)[0] prob = model.predict_proba(X)[0].max() return {"prediction": int(pred), "confidence": float(prob)}

如果QPS高,可以用ONNX Runtime或Treelite把模型转成更高效的格式。树模型转ONNX后推理速度能提升几倍。但转换过程可能遇到算子不支持的问题,需要测试验证转换前后的输出一致。

模型版本管理用MLflow或DVC,每次训练记录参数、指标和模型文件。线上出问题时能快速回滚到上一个版本。我经历过一次模型更新后效果下降,因为没有版本管理,花了半天才找到旧模型文件。

监控方面,至少记录每天的预测分布和实际标签分布,发现漂移及时告警。特征漂移比模型本身更容易出问题,因为数据分布会随业务变化。

8. 我个人的一些经验体会

做了这么多项目,最大的体会是:数据和特征决定了模型效果的上限,模型和算法只是逼近这个上限。我见过太多团队花大量时间调模型,但特征工程做得粗糙,最后效果还不如别人简单模型加好特征。

另一个体会是简单模型往往被低估。逻辑回归在文本分类、点击率预测等场景下至今是工业界的主力,因为它的训练和推理都快,可解释性好,而且效果并不差。随机森林在表格数据上经常打败深度学习模型。选模型要看场景,不要盲目追新。

还有一点是评估指标要和业务对齐。技术指标好看不等于业务价值高。我做过一个推荐项目,AUC提升了两个点,但线上点击率没变化,后来发现是推荐位置和展示策略的问题,跟模型关系不大。做模型之前先想清楚它怎么影响业务指标,比调参重要得多。

最后,代码要可复现。固定随机种子、记录数据版本、保存模型文件、写清楚环境依赖。这些工程细节看起来不起眼,但决定了你的工作能不能被别人复用,也决定了你自己三个月后能不能复现当时的结果。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进