多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

日常项目机器学习实战:分类、回归、聚类与文本处理

日常项目机器学习实战:分类、回归、聚类与文本处理 1. 从一个真实需求说起为什么日常项目需要机器学习很多开发者第一次接触机器学习脑子里浮现的都是论文、数学公式和跑在服务器集群上的大模型。但我在实际项目里发现真正高频的需求往往特别朴素一张 Excel 表里几百行数据想预测下个月的销量一批用户行为日志想自动分出几类人群一堆客服对话想快速判断情绪是正面还是负面。这些场景用传统的 if-else 写规则写到第三十条就开始互相打架维护成本高得离谱。机器学习在这个位置的价值就体现出来了。它不需要你从零训练一个 GPT而是用几十行 Python 代码把“人肉总结规律”这件事交给算法。我把它理解成日常开发里的“第二把锤子”第一把是写业务逻辑第二把是让程序自己从数据里找逻辑。这篇文章就是围绕这个思路展开的我会把日常项目中最常见的几类机器学习应用拆开讲包括分类、回归、聚类和简单的文本处理每一类都给出可复现的代码和踩坑记录。适合谁看如果你会写 Python用过 pandas 和 numpy但每次看到 sklearn 的文档就头大或者跑完模型不知道结果靠不靠谱那这篇内容就是为你准备的。我不会堆数学推导重点放在“这个参数为什么这么设”“这个结果能不能信”“线上怎么用”这些实际问题上。整套内容基于我过去几年在多个中小型项目里的实践代码都是能直接抄去改的。2. 日常项目里机器学习的整体设计思路2.1 先判断问题类型再谈模型选型我见过太多人一上来就问“用哪个模型最好”这其实是个伪命题。正确的顺序是先搞清楚你的问题属于哪一类再倒推工具。日常项目里 90% 的需求可以归到下面四类我整理了一张对照表你可以直接对号入座。问题类型典型场景输出形式常用算法二分类判断邮件是否垃圾、用户是否流失0 或 1逻辑回归、随机森林多分类新闻分类、故障类型识别类别标签随机森林、SVM回归预测销量、房价、耗时连续数值线性回归、梯度提升聚类用户分群、异常检测分组编号K-Means、DBSCAN判断方法很简单看你的标签列。有标签且只有两个值就是二分类多个值就是多分类是连续数字就是回归完全没有标签就是聚类。这个判断决定了后面所有步骤选错了后面全白做。2.2 为什么我优先推荐树模型而不是神经网络在中小规模数据几千到几十万行的日常项目里我几乎总是先上树模型尤其是随机森林和梯度提升树。原因有三个都是实战里踩出来的。第一树模型对特征缩放不敏感。神经网络要求你把所有特征归一化到相近范围否则梯度下降会很难收敛。但树模型是按特征值切分节点数值大小不影响切分逻辑省掉了一整套预处理工作。第二树模型能直接输出特征重要性这对业务方解释“为什么这个用户被判定为流失”特别关键神经网络在这方面基本是黑盒。第三调参成本低。随机森林大部分时候用默认参数就能跑出不错的结果而神经网络的学习率、层数、batch size 随便一个设错就训练失败。当然树模型不是万能的。如果数据量到了百万级以上或者涉及图像、语音这类高维非结构化数据那还是得用深度学习。但日常项目里先把树模型跑通拿到一个基线结果再考虑要不要上更复杂的方案这个顺序能帮你省下大量时间。2.3 数据质量决定上限模型只是逼近上限这句话我在每个项目里都会跟团队强调一遍。我做过一个用户流失预测前后换了五种模型准确率始终卡在 72% 左右上不去。后来花了两天时间检查数据发现“最近登录时间”这一列有大量空值而空值本身恰恰是流失的强信号但被填充逻辑给抹掉了。修正之后同一个逻辑回归模型直接跳到 85%。所以我的工作流里数据清洗和特征工程占的时间通常超过 60%建模和调参加起来不到 40%。新手容易反过来把大量精力花在调参上结果提升零点几个百分点而数据里一个明显的坑没填损失十几个点。后面我会专门用一节讲数据检查的清单。3. 核心细节解析与实操要点3.1 环境准备最小依赖集日常项目不需要装一堆东西。我常用的组合就四个库装多了反而容易版本冲突。pip install pandas scikit-learn numpy matplotlibpandas 负责数据读写和清洗scikit-learn 提供模型和评估工具numpy 做数值计算matplotlib 画图看分布。版本上我建议 pandas 用 2.xscikit-learn 用 1.3 以上这两个版本对空值和类别特征的处理更友好。如果你要用梯度提升可以额外装 lightgbm 或 xgboost但初期用 sklearn 自带的 GradientBoostingClassifier 就够了。注意不要在一个环境里同时装多个深度学习框架和 sklearn 的老版本我遇到过 numpy 版本被覆盖导致 sklearn 直接报错的情况。用虚拟环境隔离这是基本纪律。3.2 数据检查清单建模前必须过的五道关在写任何模型代码之前我会固定跑一遍下面这个检查流程。这五步能拦掉大部分低级错误。import pandas as pd df pd.read_csv(data.csv) # 1. 看形状和类型 print(df.shape) print(df.dtypes) # 2. 看缺失值比例 missing df.isnull().sum() / len(df) print(missing[missing 0].sort_values(ascendingFalse)) # 3. 看标签分布分类问题 print(df[label].value_counts(normalizeTrue)) # 4. 看数值列的基本统计 print(df.describe()) # 5. 看类别列的取值数量 for col in df.select_dtypes(includeobject).columns: print(col, df[col].nunique())第一步看形状和类型重点检查有没有本该是数字的列被读成了字符串比如“1,234”这种带逗号的金额。第二步看缺失值超过 30% 缺失的列要慎重可能直接删掉比填充更好。第三步看标签分布如果某一类占比超过 95%说明样本极度不平衡后面评估不能用准确率。第四步看数值范围发现最大值是 999999 这种明显异常值要标记出来。第五步看类别列的取值数量如果一个列有几百个不同取值直接做独热编码会炸维度需要先做合并或目标编码。3.3 特征处理数值、类别、时间三类分开对待特征处理是决定模型效果的关键环节我按数据类型分三类处理每类都有固定的套路。数值特征的处理相对简单。大部分树模型不需要归一化但如果用逻辑回归或 SVM就必须做标准化。我通常用 StandardScaler把均值变 0、方差变 1。对于长尾分布的数值比如收入、点击量我会先做 log 变换再标准化这样能让分布更接近正态模型更容易学到规律。from sklearn.preprocessing import StandardScaler import numpy as np df[income_log] np.log1p(df[income]) scaler StandardScaler() df[income_scaled] scaler.fit_transform(df[[income_log]])类别特征要分情况。取值少的比如性别、城市等级直接用独热编码pandas 的 get_dummies 一行搞定。取值多的比如商品 ID、用户 ID不能用独热维度会爆炸我一般用目标编码也就是用该类别的标签均值来替换原始值。但目标编码有个坑必须用交叉验证的方式计算否则会数据泄露训练集上效果好得离谱测试集一塌糊涂。时间特征最容易被忽略。很多人拿到时间戳直接扔掉其实里面信息量很大。我会从时间戳里拆出年、月、日、星期几、是否周末、是否节假日这几个特征。特别是“星期几”和“是否周末”在用户行为预测里往往是强特征。比如一个电商项目里我发现“是否发薪日前后三天”这个特征的重要性排到了前三。3.4 训练集测试集划分时间序列不能用随机划分这是新手最容易犯的错误之一。如果你的数据有时间顺序比如按天记录的销量绝对不能随机划分训练集和测试集。因为随机划分会让模型在训练时“看到未来”测试结果虚高上线后直接崩盘。正确做法是按时间切分用前面的数据训练后面的数据测试。比如用 1 月到 9 月的数据训练10 月到 12 月的数据测试。如果数据量不够可以用时间序列交叉验证sklearn 的 TimeSeriesSplit 就是干这个的。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_idx, test_idx in tscv.split(df): train, test df.iloc[train_idx], df.iloc[test_idx] # 在 train 上训练在 test 上评估对于没有时间顺序的数据比如用户画像随机划分是可以的但要注意分层抽样保证训练集和测试集的标签比例一致。用 train_test_split 的 stratify 参数就能做到。4. 实操过程与核心环节实现4.1 二分类实战用户流失预测完整流程我拿一个模拟的用户流失场景来走完整流程。数据包含用户 ID、最近登录间隔天数、月均使用次数、付费金额、注册时长、是否流失六个字段。目标是用前五个特征预测“是否流失”。第一步读数据并做基础清洗。import pandas as pd from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, roc_auc_score df pd.read_csv(churn.csv) df df.dropna(subset[是否流失]) df[是否流失] df[是否流失].astype(int) X df[[最近登录间隔天数, 月均使用次数, 付费金额, 注册时长]] y df[是否流失]第二步划分数据。这里没有明显时间顺序用分层抽样。X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy )第三步训练模型并评估。model RandomForestClassifier( n_estimators200, max_depth8, min_samples_leaf5, random_state42, class_weightbalanced ) model.fit(X_train, y_train) y_pred model.predict(X_test) y_prob model.predict_proba(X_test)[:, 1] print(classification_report(y_test, y_pred)) print(AUC:, roc_auc_score(y_test, y_prob))这里有几个参数我解释一下为什么这么设。n_estimators200 是树的数量太少容易欠拟合太多训练慢且收益递减200 在中小数据上是个平衡点。max_depth8 限制树深防止过拟合因为流失数据里噪声多树太深会记住噪声。min_samples_leaf5 保证每个叶子节点至少有 5 个样本避免模型对个别样本过度敏感。class_weightbalanced 是因为流失用户通常只占少数不加这个参数模型会倾向于全预测为“不流失”准确率看着高但召回率极低。第四步看特征重要性这是跟业务方沟通的关键。import matplotlib.pyplot as plt importances pd.Series( model.feature_importances_, indexX.columns ).sort_values(ascendingFalse) print(importances)实测下来“最近登录间隔天数”通常排第一这符合直觉一个用户越久没登录流失概率越高。但有时候“注册时长”会排到第二说明老用户反而更容易流失这个发现就能推动业务方去做老用户召回活动。4.2 回归实战销量预测与误差分析回归问题的流程和分类类似区别在评估指标和损失函数。我用一个模拟的日销量预测场景特征包括前一天销量、星期几、是否促销、温度。from sklearn.ensemble import GradientBoostingRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np df pd.read_csv(sales.csv) df[星期几] pd.to_datetime(df[日期]).dt.dayofweek df[是否周末] (df[星期几] 5).astype(int) features [前一天销量, 星期几, 是否周末, 是否促销, 温度] X df[features] y df[销量] split int(len(df) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] model GradientBoostingRegressor( n_estimators300, learning_rate0.05, max_depth4, random_state42 ) model.fit(X_train, y_train) y_pred model.predict(X_test) mae mean_absolute_error(y_test, y_pred) rmse np.sqrt(mean_squared_error(y_test, y_pred)) print(fMAE: {mae:.2f}, RMSE: {rmse:.2f})回归里我重点看两个指标。MAE 是平均绝对误差直接告诉你预测值平均偏离真实值多少业务方最容易理解。RMSE 是均方根误差对大误差惩罚更重如果 RMSE 远大于 MAE说明存在个别预测得特别离谱的样本需要去排查这些异常点。这里 learning_rate0.05 配合 n_estimators300 是梯度提升的经典组合。学习率低意味着每棵树只贡献一点点需要更多树来补偿这样泛化能力更好。如果学习率设成 0.3树的数量就得降到 100 左右否则容易过拟合。max_depth4 比分类任务里的 8 更浅因为回归任务对噪声更敏感树太深会把噪声也拟合进去。4.3 聚类实战用户分群不预设标签聚类和前面两类最大的区别是没有标签你需要自己判断分几组、每组代表什么。我用 K-Means 做一个模拟的用户分群特征包括月消费、使用频次、活跃天数。from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler df pd.read_csv(users.csv) features [月消费, 使用频次, 活跃天数] X StandardScaler().fit_transform(df[features]) inertias [] for k in range(2, 11): km KMeans(n_clustersk, random_state42, n_init10) km.fit(X) inertias.append(km.inertia_) for k, inertia in zip(range(2, 11), inertias): print(fk{k}, inertia{inertia:.2f})选几个组是聚类里最主观的一步。我用肘部法则看 inertia 下降速度在哪里明显变缓那个点就是合适的 k。但肘部法则不是万能的有时候曲线很平滑看不出明显拐点。这时候我会结合业务判断比如运营团队说“我们最多能针对三类人群做不同策略”那就定 k3然后看这三类各自的特征均值给它们起名字比如“高价值活跃”“低频高消费”“低价值流失边缘”。km KMeans(n_clusters3, random_state42, n_init10) df[群体] km.fit_predict(X) print(df.groupby(群体)[features].mean())聚类结果一定要做业务解读否则就是一堆没有意义的数字。我通常会输出每组的特征均值然后跟业务方一起讨论这组人该怎么运营。这个过程比算法本身更重要。4.4 文本分类入门客服对话情绪判断文本处理在日常项目里越来越常见。我用一个模拟的客服对话数据集判断每条对话是“正面”还是“负面”。这里不涉及深度学习用 TF-IDF 加逻辑回归就能拿到不错的效果。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline df pd.read_csv(feedback.csv) X_train, X_test, y_train, y_test train_test_split( df[文本], df[情绪], test_size0.2, random_state42, stratifydf[情绪] ) pipeline Pipeline([ (tfidf, TfidfVectorizer(max_features5000, ngram_range(1, 2))), (clf, LogisticRegression(max_iter1000, class_weightbalanced)) ]) pipeline.fit(X_train, y_train) print(classification_report(y_test, pipeline.predict(X_test)))TfidfVectorizer 的 max_features5000 表示只保留词频最高的 5000 个词或词组防止维度爆炸。ngram_range(1, 2) 表示同时考虑单个词和相邻两个词的组合比如“不 满意”和“不满意”是两回事二元组能捕捉这种否定结构。逻辑回归的 max_iter 默认是 100文本数据维度高经常不收敛调到 1000 基本能解决。这个方案在几千条数据上通常能到 80% 以上的准确率对于快速验证需求足够了。如果效果不够再考虑上预训练模型但那是另一个量级的成本。5. 常见问题与排查技巧实录5.1 模型效果差先查数据再查模型模型效果不达预期时我的排查顺序是固定的先看数据再看特征最后才动模型。下面这张表是我整理的常见症状和对应原因。症状可能原因排查方法训练集准确率 99%测试集 60%过拟合减少树深、增加正则、检查数据泄露训练集和测试集都差欠拟合或特征无效增加特征、换更复杂模型、检查标签准确率高但召回率极低样本不平衡加 class_weight、调整阈值、重采样线上效果远差于离线数据分布不一致对比线上线下特征分布、检查时间泄露每次训练结果波动大数据量太小或随机性固定随机种子、增加数据、交叉验证数据泄露是最隐蔽也最致命的问题。我遇到过一次特征里有个“是否已投诉”字段而投诉和流失高度相关但这个字段在预测时根本拿不到因为用户还没流失怎么会有投诉记录。这种特征必须删掉否则离线指标好看上线完全没用。5.2 类别不平衡的三种处理方式流失预测、欺诈检测这类场景正样本往往只占 1% 到 5%。这时候模型会倾向于全预测为负样本准确率看着有 95%但一个正样本都没抓到。我常用的处理方式有三种按优先级排列。第一种是调整类别权重也就是 class_weightbalanced让模型对少数类样本的误差惩罚更重。这是最简单的方式一行参数搞定大部分时候效果就不错。第二种是调整预测阈值模型输出的是概率默认 0.5 以上判为正类你可以把阈值降到 0.3牺牲精确率换召回率。具体降到多少要看业务能接受多少误报。第三种是重采样对少数类过采样或对多数类欠采样。我一般用 SMOTE 做过度采样但要注意只能在训练集上做测试集必须保持原始分布否则评估结果失真。from imblearn.over_sampling import SMOTE smote SMOTE(random_state42) X_train_res, y_train_res smote.fit_resample(X_train, y_train)5.3 特征重要性怎么看才不误导特征重要性是树模型的一大优势但直接看默认输出容易被误导。有两个坑要注意。第一个坑是相关性高的特征会互相稀释重要性。比如“月消费”和“年消费”高度相关模型可能把重要性平分给两者看起来都不重要实际上这个维度很重要。解决办法是先把高度相关的特征合并或删掉一个再做重要性分析。第二个坑是默认的特征重要性基于不纯度减少对取值多的特征有偏好。比如用户 ID 这种高基数特征即使没有预测价值也可能排到前面。更可靠的方式是用 permutation importance它通过随机打乱某个特征的值来看模型效果下降多少更接近真实贡献。from sklearn.inspection import permutation_importance result permutation_importance( model, X_test, y_test, n_repeats10, random_state42 ) for i, col in enumerate(X.columns): print(col, result.importances_mean[i])5.4 模型上线后的监控要点模型训练完不是终点上线后效果会随着数据分布变化而衰减这叫模型漂移。我一般监控三个指标。第一个是输入特征的分布。比如“月均使用次数”的均值如果从 20 次降到 10 次说明用户行为变了模型可能不再适用。第二个是预测结果的分布。如果模型突然把 80% 的用户都判为流失肯定有问题。第三个是业务指标。模型只是手段最终要看流失率有没有真的下降。我习惯每周跑一次离线评估用最新数据重新算一遍指标跟上线时对比偏差超过 10% 就触发重新训练。提示模型版本一定要管理起来每次训练记录数据版本、参数、评估指标。我见过团队换了模型但没记录出问题后完全无法回滚只能从头再来。6. 我踩过的坑和几条实在建议第一个坑是盲目追求复杂模型。我早期做过一个文本分类上来就用当时最火的深度模型调了两周效果还不如同事用 TF-IDF 加朴素贝叶斯跑出来的基线。后来才明白数据量和问题难度不匹配时简单模型反而更稳。现在我的习惯是先用最简单的方法跑一个基线记录指标然后每次只改一个变量看提升多少这样每一步的收益都清清楚楚。第二个坑是忽略业务含义。有次做一个预测模型AUC 到了 0.92我特别得意结果业务方看了一眼说“这个特征我们上线时拿不到”。那一刻我才意识到技术指标再好如果特征在预测时不可用整个模型就是废的。从那以后我每次选特征都会先问一句“这个字段在预测时刻能拿到吗”。第三个坑是不做交叉验证。单次划分训练集测试集结果波动可能很大尤其是数据量小的时候。我现在的习惯是至少做 5 折交叉验证看均值和标准差。如果标准差很大说明模型不稳定需要更多数据或更简单的模型。最后分享一个实用技巧把整个流程写成一个函数或脚本从读数据到输出评估报告一键跑完。这样每次有新数据或新想法改一个参数就能重新跑效率比手动一步步操作高得多。我现在的模板大概 200 行代码覆盖了数据检查、特征处理、模型训练、评估和特征重要性输出新项目直接复制过去改字段名就能用。这个模板本身也在不断迭代每次遇到新问题就补一条检查规则进去用久了就成了自己的工具箱。
返回列表