Repository Reading Site
ML 全流程 — 从数据到上线
不是模拟。每一步都在 K8s 集群上真实运行。 --- 传统编程:人写规则 → 程序执行 机器学习:数据写规则 → 程序学习 sklearn 内置,20640 条加州房屋数据(1990 年人口普查): | 特征 | 含义 | 示例值 | 为什么重要 | |------|------|--------|-----------| | MedInc | 街区中位数
ML 全流程 — 从数据到上线
这套系统做了什么?
真实数据 → 真实训练 → 真实模型 → 真实预测 → 真实监控
↑ ↑ ↑ ↑ ↑
California sklearn ONNX/JSON Go HTTP Prometheus
Housing 3种算法 NFS存储 0.3ms延迟 实时指标
20640条 K8s CRD
不是模拟。每一步都在 K8s 集群上真实运行。
第一章:数据 — 一切的起点
什么是机器学习?
传统编程:人写规则 → 程序执行
if income > 100000 and location == "SF":
price = 500000
机器学习:数据写规则 → 程序学习
给模型 20000 条 (特征, 房价) 数据
模型自己找出: 收入、位置、房龄 如何影响房价
我们用的数据集:California Housing
sklearn 内置,20640 条加州房屋数据(1990 年人口普查):
| 特征 | 含义 | 示例值 | 为什么重要 |
|---|---|---|---|
| MedInc | 街区中位数收入(万美元) | 3.5 | 收入越高的区域房价越高 |
| HouseAge | 房屋中位数年龄 | 25 | 新房 vs 老房 |
| AveRooms | 户均房间数 | 5.2 | 房子大小 |
| AveBedrms | 户均卧室数 | 1.1 | 卧室占比 |
| Population | 街区人口 | 1200 | 人口密度 |
| AveOccup | 户均居住人数 | 3.0 | 拥挤程度 |
| Latitude | 纬度 | 37.5 | 地理位置(越北越贵?不一定) |
| Longitude | 经度 | -122.0 | 地理位置(沿海 vs 内陆) |
目标变量(Target): 房价中位数,单位 $100,000。值 2.5 = $250,000。
为什么选这个数据集?
| 候选 | 为什么选/不选 |
|---|---|
| California Housing ✓ | 20640 条够大、8 特征够复杂、CPU 秒级训练、真实数据 |
| Iris | 150 条太小,什么都学不到 |
| Boston Housing | 已被 sklearn 弃用(含种族相关变量,有伦理争议) |
| MNIST | 图像数据,CPU 训练慢,不是我们的场景 |
第二章:特征工程 — 让数据适合模型
训练集/测试集分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# 80% 用来训练 (16512 条)
# 20% 用来测试 (4128 条)
为什么要分? 防止"作弊"。如果用全部数据训练又用全部数据评估,模型可能只是"背答案"(过拟合),遇到新数据就不行。测试集模拟"从未见过的数据"。
标准化(StandardScaler)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X) # 每个特征: (x - 均值) / 标准差
为什么要标准化?
原始数据范围差异巨大:
- MedInc: 0.5 ~ 15 (收入)
- Population: 3 ~ 35682 (人口)
如果不标准化,Population 的数值大几千倍,线性回归会认为它"更重要"——这是错的,仅仅是单位不同。
标准化后所有特征都在 [-3, 3] 范围内,模型公平对待每个特征。
重要细节: fit_transform(X_train) 是用训练集算均值和标准差,transform(X_test) 是用训练集的参数转换测试集。不能用测试集的统计量——那等于"偷看答案"。
第三章:模型训练 — 三种算法对决
1. 线性回归(LinearRegression)— 基线
y = w₁·MedInc + w₂·HouseAge + ... + w₈·Longitude + b
就是一条多维直线。简单、快速、可解释,但假设特征和目标是线性关系。
我们的结果: R² = 0.576(57.6% 的房价变化被解释)
2. 随机森林(RandomForest)— 集成
100 棵决策树,每棵用不同的数据子集训练,最终取平均。
树1: 如果 MedInc > 5 且 Latitude < 38 → 预测 3.2
树2: 如果 AveRooms > 5 且 HouseAge < 20 → 预测 3.5
...
最终预测 = (3.2 + 3.5 + ... ) / 100
Bagging(装袋): 每棵树看不同的数据,减少过拟合。
我们的结果: R² = 0.774
3. 梯度提升(GradientBoosting)— 冠军
也是多棵树,但依次训练。每棵新树专门纠正前面树的错误:
树1预测: 3.0 (实际 3.5, 误差 +0.5)
树2专门学习误差: 预测误差 0.3 (还差 0.2)
树3再学剩余误差: 预测误差 0.15
...
最终 = 3.0 + 0.3 + 0.15 + ... ≈ 3.5
Boosting(提升): 每一步针对性地改进。
我们的结果: R² = 0.811(最优!)
评估指标
| 指标 | 公式 | 我们的值 | 含义 |
|---|---|---|---|
| MAE | 平均|预测-实际| | 0.335 ($33,500) | 平均偏差 3.3 万美元 |
| RMSE | √(平均(预测-实际)²) | 0.498 ($49,800) | 惩罚大偏差 |
| R² | 1 - 残差方差/总方差 | 0.811 | 81.1% 房价变化被解释 |
第四章:模型导出 — ONNX 与 JSON
为什么不直接用 Python 做推理?
| Python 推理 | Go 推理 | |
|---|---|---|
| 内存占用 | 50-100MB(numpy/sklearn) | 5-10MB |
| 并发 | GIL 限制,需多进程 | goroutine 原生并发 |
| 启动时间 | 2-5 秒(import 库) | 50ms |
| 部署 | Python 环境 + 依赖 | 单个二进制文件 |
生产中的标准做法: Python 训练 → ONNX/TorchScript 导出 → Go/C++/Rust 推理。
我们的导出格式
ONNX(Open Neural Network Exchange): 跨平台模型序列化格式。Python 训练的模型可以在任何 ONNX Runtime(Go/C++/Java/C#)中运行。
JSON 参数: 对于线性回归,模型就是 weights 和 intercept。直接存 JSON,Go 纯代码实现预测数学:
// model_params.json
{
"weights": [0.8296, 0.1179, -0.2654, ...], // 8个权重
"intercept": 2.0719 // 截距
}
// scaler_params.json
{
"mean": [3.8706, 28.6394, ...], // 8个均值
"std": [1.8998, 12.5856, ...] // 8个标准差
}
Go 预测代码核心(6 行):
// 标准化
for i, x := range features {
scaled[i] = (x - scaler.Mean[i]) / scaler.Std[i]
}
// 预测: y = Σ(xi * wi) + b
prediction := model.Intercept
for i, x := range scaled {
prediction += x * model.Weights[i]
}
第五章:K8s 部署 — Operator 模式
为什么用 CRD + Operator?
传统方式:手动创建 Deployment + Service + HPA + ConfigMap...
Operator 方式:
apiVersion: ml.k8s-lab.io/v1alpha1
kind: MLModel
metadata:
name: housing-model-v1
spec:
modelPath: /models/housing-model-v1.onnx
version: "v1"
replicas: 2
一个 CR 搞定。Operator 自动创建 Deployment + Service,自动管理生命周期。
我们的 MLModel CRD
kubectl get mlmodel -n ml-platform
NAME VERSION TYPE PHASE REPLICAS READY ENDPOINT
housing-model-v1 v1 GradientBoosting Serving 2 2 housing-model-v1-svc.ml-platform.svc:8080
Operator 做了什么?
1. Watch 到 MLModel CR 创建
2. 创建 Deployment: 2 副本推理容器,挂载 NFS 模型文件
3. 创建 Service: ClusterIP,端口 8080
4. 持续监控: Deployment Ready 后更新 MLModel 状态为 "Serving"
5. 级联删除: 删 MLModel → 自动删 Deployment + Service
第六章:真实预测 — 验证结果
测试 1: 旧金山湾区高收入区
输入: {"features": [8.3, 20, 6.0, 1.0, 1500, 3.0, 37.78, -122.42]}
输出: {"prediction": 4.1596, "price_usd": "$415,957", "latency_ms": 0.392}
MedInc=8.3(高收入),旧金山坐标 → 预测 $416K。合理!
测试 2: 洛杉矶内陆低收入区
输入: {"features": [2.5, 35, 4.5, 1.2, 3000, 4.5, 34.05, -118.25]}
输出: {"prediction": 1.7937, "price_usd": "$179,366", "latency_ms": 0.176}
MedInc=2.5(低收入),LA 内陆 → 预测 $179K。合理!
推理性能
- 延迟: 0.2-0.4ms(纯 Go + 简单线性运算)
- 内存: Pod 约 5MB
- 并发: Go 原生支持数千并发请求
第七章:行业对比 — 我们造了什么轮子?
| 我们的组件 | 行业工具 | 区别 |
|---|---|---|
| train.py | MLflow / Kubeflow Pipelines | MLflow 管理实验、模型版本;我们手写理解本质 |
| MLModel CRD | KServe InferenceService | KServe 支持 TF/PyTorch/ONNX 自动推理;我们手写 Operator |
| Go inference | Triton / TF Serving | Triton 支持多框架、动态 batching;我们理解核心逻辑 |
| NFS 模型存储 | S3 / Model Registry | 生产用对象存储;我们用 NFS 简化 |
学习价值: 理解了这些组件在做什么之后,使用 KServe/MLflow 等工具时就不是"黑盒调用",而是知道底层在干什么。
完整架构图
┌──────────────────────────────────────────────────────┐
│ ML Platform │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ Training Job │ │ MLModel CRD │ │ Inference │ │
│ │ (Python) │───→│ (K8s 资源) │──→│ (Go) │ │
│ │ sklearn │ │ version,path │ │ HTTP API │ │
│ │ 3种算法 │ │ replicas │ │ 0.3ms │ │
│ └──────┬───────┘ └──────┬───────┘ └─────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ NFS Storage │ │ Go Operator │ │Prometheus │ │
│ │ model files │ │ Reconcile │ │ 指标采集 │ │
│ │ JSON params │ │ Deploy+Svc │ │ Grafana │ │
│ └──────────────┘ └──────────────┘ └───────────┘ │
└──────────────────────────────────────────────────────┘