机器学习算法入门:基础概念详解——一个双休不加班的国企程序员的深夜自白

写码的老王
2025-12-19 04:59
阅读 484

上周五晚上十点半,我正瘫在出租屋的懒人沙发上刷B站,突然收到组长微信:“小张,下周三有个新需求评审,要搞个用户行为预测模型,你先看看能不能用点机器学习的东西。”

我一口冰可乐差点喷出来——咱们这可是做政务系统的国企啊!平时连 Redis 都只敢用作缓存,突然整上 AI 了?但转念一想,领导既然开口了,大概率是上面KPI压力大,又或者隔壁组炫耀他们“智能化转型”成功了。
毕竟去年双11,我们还在帮某市人社局做社保卡补办系统,前端页面还是 Bootstrap 3 的复古风……

不过吐槽归吐槽,作为坐标上海、住公司步行15分钟、坚持双休不加班(划重点)的佛系程序员,这种“半强制性技术升级”其实也不赖——至少简历能多一行“具备机器学习项目落地经验”。再说了,谁让我偏偏喜欢深夜写代码呢?安静、没人@你改需求、连测试都下班了,效率拉满。

于是,我就着泡面和《孤勇者》的BGM,开始从零搭建这个所谓的“用户行为预测模块”。本文不是什么高深论文,就是记录我踩坑、调参、被 sklearn 折磨、最后勉强跑通的全过程。如果你也像我一样,主业是 CRUD 工程师,副业被迫 AI 化,那这篇可能有点参考价值。


为啥要在 Springboot 里塞机器学习?

先说清楚业务场景:我们要预测用户是否会完成一次在线办事流程(比如提交材料、确认信息)。原始数据来自 Kafka,字段包括用户 ID、停留时长、点击次数、是否中途退出等,标签是 completed: 0/1

乍一看,这不是典型的二分类问题吗?逻辑回归、随机森林随便上啊!
但问题来了:我们的后端是 Springboot + MyBatis 的经典组合,部署在 K8s 集群上(别问,问就是“云原生战略”),运维老哥明确表示:“别整 Python 服务,Java 堆栈必须闭环!”

行吧,那就用 Java 实现 ML 模型。
我试过 Weka,也试过 Tribuo(Oracle 出的那个冷门库),最后还是选了 DJL (Deep Java Library) —— 虽然名字带 Deep,但它支持 Sklearn 导出的 PMML 模型,也能加载 ONNX,关键是和 Springboot 集成贼顺。

💡 开发心得:别一上来就搞 TensorFlow Serving 或 Flask API 封装模型。在国企,稳定性和可维护性 > 技术炫酷度。能用 Java 跑的,绝不用 Python 再起一个服务——否则运维会拿拖鞋追着你打。


机器学习不是魔法,是“特征工程+调参”的苦力活

很多人以为 ML 就是 model.fit(X, y) 然后坐等 accuracy 飞升。现实是:80% 的时间在清洗数据,15% 在调参,5% 在写代码

我的原始数据长这样:

user_id page_views avg_dwell_sec clicks exit_before_submit completed
u123 5 120 20 1 0
u456 8 300 35 0 1

看起来很干净?天真!
实际上,avg_dwell_sec 有负数(前端埋点 bug)、clicks 超过 10000(机器人刷的)、还有 30% 的样本缺失 exit_before_submit 字段。

第一步:数据清洗 + 特征工程

  • 过滤 dwell_time < 0 或 > 3600 的异常值
  • 对 clicks 做 log 变换(缓解长尾分布)
  • 用中位数填充缺失的 exit 字段
  • 新增特征:clicks_per_minute = clicks / (dwell_sec / 60)
# 这部分我用 Python 做(本地 Jupyter),因为 pandas 太香了
import pandas as pd
import numpy as np

df = pd.read_csv('user_behavior.csv')
df = df[(df['avg_dwell_sec'] > 0) & (df['avg_dwell_sec'] < 3600)]
df['clicks_log'] = np.log1p(df['clicks'])
df['exit_before_submit'].fillna(df['exit_before_submit'].median(), inplace=True)
df['clicks_per_min'] = df['clicks'] / (df['avg_dwell_sec'] / 60 + 1e-5)

🤯 血泪教训:别在 Java 里做复杂数据预处理!用 Python 清洗+训练,导出模型,Java 只负责推理。这样分工明确,还能避免在 Springboot 里写 for 循环算均值这种反人类操作。


模型选型:简单有效才是王道

我试了三种算法:

算法 训练时间 AUC 是否支持 PMML 导出 在线推理延迟
逻辑回归 2s 0.78 <5ms
随机森林 45s 0.82 ✅(需 skl2pmml) ~15ms
XGBoost 120s 0.84 ❌(需转 ONNX) ~25ms

虽然 XGBoost 效果最好,但导出 ONNX 后在 DJL 加载有点玄学(有时内存爆炸),而且我们 QPS 不高(峰值也就 200),没必要上重型武器。

最终选了 随机森林 —— AUC 足够用,PMML 标准支持好,Java 解析稳如老狗。

# 训练并导出 PMML
from sklearn.ensemble import RandomForestClassifier
from sklearn2pmml import sklearn2pmml, PMMLPipeline

pipeline = PMMLPipeline([
    ("classifier", RandomForestClassifier(n_estimators=100, random_state=42))
])
pipeline.fit(X_train, y_train)

sklearn2pmml(pipeline, "user_behavior_rf.pmml", with_repr=True)

把 PMML 模型塞进 Springboot

现在重点来了:怎么让 Java 读 PMML 并预测?

我用的是 JPMML-Evaluator,一个轻量级 PMML 解析库。

<!-- pom.xml -->
<dependency>
    <groupId>org.jpmml</groupId>
    <artifactId>jpmml-evaluator</artifactId>
    <version>1.6.4</version>
</dependency>

然后写个 Spring Bean 加载模型:

@Component
public class MlModelService {

    private Evaluator evaluator;

    @PostConstruct
    public void loadModel() throws Exception {
        try (InputStream is = getClass().getResourceAsStream("/models/user_behavior_rf.pmml")) {
            PMML pmml = PMMLUtil.unmarshal(is);
            this.evaluator = new LoadingModelEvaluatorBuilder()
                .load(pmml)
                .build();
        }
    }

    public double predict(Map<String, Object> inputFeatures) {
        Map<String, ?> arguments = new LinkedHashMap<>();
        // 注意:字段名必须和 PMML 里一致!
        arguments.put("page_views", inputFeatures.get("pageViews"));
        arguments.put("avg_dwell_sec", inputFeatures.get("avgDwellSec"));
        arguments.put("clicks_log", Math.log1p((Double) inputFeatures.get("clicks")));
        arguments.put("exit_before_submit", inputFeatures.get("exitBeforeSubmit"));
        arguments.put("clicks_per_min", /* 计算逻辑 */);

        Map<String, ?> results = evaluator.evaluate(arguments);
        // PMML 输出一般是 "probability(1)" 表示正类概率
        return (Double) results.get("probability(1)");
    }
}

⚠️ 巨坑预警

  1. PMML 里的字段名区分大小写,且不能有下划线(除非训练时指定);
  2. 所有输入必须是 Map<String, Object>,数值类型要匹配(Integer vs Double 会报错);
  3. 别把模型文件放 resources 下直接 new File(),要用 getResourceAsStream,否则 K8s 打包后找不到!

线上效果 & 性能压测

模型上线前,我用 JMeter 模拟了 500 并发请求,结果如下:

指标 数值
平均响应时间 12ms
P99 28ms
CPU 占用(单 Pod) +15%
内存增量 ~80MB

完全在可接受范围内。更关键的是——业务方居然说“效果不错”
原来之前用规则引擎(if-else 堆到 200 行)的准确率只有 65%,现在提升到 80%+,产品经理当场表示“这波 AI 转型成功了”,还请我们组喝了奶茶(虽然是蜜雪冰城)。


给同行的几点真心话(附简历技巧)

作为一个白天写 Springboot、晚上啃《统计学习方法》的国企码农,我想说:

  1. 别被“AI 工程师”头衔吓到。大多数业务场景根本不需要深度学习,传统 ML 完全够用。
  2. 简历上写“使用机器学习优化业务指标”比“熟悉 TensorFlow”更有说服力。HR 和面试官更关心你解决了什么问题,而不是用了多 fancy 的框架。
  3. 在 Java 体系里做 ML,优先考虑模型导出+本地推理。微服务拆分越多,运维越头疼,尤其在国企。
  4. 特征工程 > 模型选择。我见过太多人花一周调 XGBoost 参数,却不愿花一天思考“用户停留时间是否该分段”。

最后,分享一句我在 K8s dashboard 上贴的便签:

“模型再牛,不如需求不变;算法再快,不如周末双休。”

——毕竟,我们写代码是为了更好地生活,不是为了给老板的 PPT 增加“科技感”。

(完)


P.S. 如果你也在国企/传统行业搞 ML,欢迎交流。我整理了一份《Springboot 集成 PMML 模型避坑指南》,包含完整的 Maven 配置、字段映射工具类、以及如何用 Actuator 监控模型加载状态。需要的话评论区喊一声,我抽空放到 GitHub 上。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝