K8s Lab 把当前仓库文档整理成一个可阅读的网页站点

Repository Reading Site

ML 全流程 — 从数据到上线

不是模拟。每一步都在 K8s 集群上真实运行。 --- 传统编程:人写规则 → 程序执行 机器学习:数据写规则 → 程序学习 sklearn 内置,20640 条加州房屋数据(1990 年人口普查): | 特征 | 含义 | 示例值 | 为什么重要 | |------|------|--------|-----------| | MedInc | 街区中位数

Markdownml-platform/docs/ml-pipeline.md2026年4月9日 15:30

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) 惩罚大偏差
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   │ │
│  └──────────────┘    └──────────────┘   └───────────┘ │
└──────────────────────────────────────────────────────┘