1. 这不是软件工程的复刻,而是机器学习落地的“呼吸系统”
你有没有过这样的经历:在本地 Jupyter Notebook 里调通了一个 95% 准确率的模型,兴冲冲地打包发给同事,结果对方一运行就报ModuleNotFoundError: No module named 'skops';或者改了两行特征工程代码,重新训练后发现 F1 分数掉了 0.12,但根本想不起来上一次验证时用的是哪个数据切分随机种子;又或者,好不容易把模型封装成 API 部署上线,第二天业务方说“用户反馈预测结果和昨天不一样”,而你翻遍 Git 历史,却找不到那次“悄悄更新”的 commit 记录——因为那是在你本地跑完train.py后,手动拖拽.pkl文件到服务器上的。
这根本不是“模型效果不好”,而是整个 ML 项目失去了可追溯、可重现、可协作的呼吸能力。CI/CD 对于机器学习,从来就不是把软件工程的流水线生搬硬套过来,它是一套专为 ML 特性定制的“生命维持系统”:它要能同时管理代码、数据、模型、环境、指标、可视化结果这六类高度耦合又彼此异构的资产。一个git push触发的,不该只是“编译+测试”,而应是一次完整的、原子化的“科学实验闭环”——从拉取最新数据快照、复现完整训练流程、生成带置信度的评估报告、自动存档模型二进制、到最终将可交互的推理界面推送到生产环境。我做过 7 个从零搭建的 MLOps 项目,其中 4 个在第三周就因环境漂移或数据版本混乱而返工。后来我才明白,没有 CI/CD 的 ML 项目,就像没有刹车系统的赛车——跑得越快,失控的风险越大。这篇指南,就是用最轻量、最易上手的技术栈(GitHub Actions + Makefile + CML + Hugging Face),为你搭起第一套真正能“自己呼吸”的 ML 流水线。它不依赖 Kubernetes 或云厂商控制台,所有操作都在浏览器里完成,连 Docker 都不用装。你只需要一个 GitHub 账号、一个 Hugging Face 账号,以及对git push这个动作的绝对信任。
2. 为什么这套方案能跑通?——拆解 ML-CI/CD 的底层逻辑与选型深意
2.1 核心矛盾:ML 的“非确定性” vs CI/CD 的“强确定性”
传统软件 CI/CD 的基石是“确定性”:同一份代码,在相同环境里,永远输出相同的二进制。但 ML 天然带着“不确定性”基因:
- 数据漂移:今天爬取的用户行为日志,和上周的分布可能已悄然偏移;
- 随机性内核:
random_state=42只保证单次复现,一旦数据源更新,整个训练轨迹就不可逆; - 环境敏感:
scikit-learn==1.2.2和1.3.0在某些边缘 case 下,随机森林的预测结果可能有微小差异。
如果强行套用软件工程的“构建-测试-部署”三段论,你会立刻掉进三个坑:
- 测试失效:单元测试能验证函数逻辑,但无法验证“模型在新数据上是否退化”;
- 部署失焦:部署的不是“可执行文件”,而是“一个需要特定数据、特定环境、特定依赖才能活下来的黑盒”;
- 回滚无解:软件回滚是
git checkout <commit>+ 重启服务;ML 回滚需要同时还原代码、数据版本、模型权重、评估报告,四者缺一不可。
所以,真正的 ML-CI/CD 必须重构“确定性”的定义——它不追求“绝对不变”,而追求“全链路可追溯的变更边界”。每一次push,都必须清晰回答:这次变更影响了哪些数据?触发了哪些模型训练?产生了哪些新指标?这些产物是否满足预设的 SLO(如 F1 ≥ 0.90)?只有当所有答案都以机器可读的方式固化下来,自动化才有意义。
2.2 工具链选型:为什么是 GitHub Actions + Makefile + CML + Hugging Face?
这不是一个“流行技术堆砌”的选择,而是针对入门级 ML 工程师痛点的精准解药:
| 工具 | 解决的核心痛点 | 为什么不是其他选项 | 实操中的关键细节 |
|---|---|---|---|
| GitHub Actions | 免运维、免配置、开箱即用的执行引擎 | 不选 Jenkins:需要自建服务器、配置 agent、管理凭据,新手三天都搭不起来;不选 GitLab CI:国内访问不稳定,且需自建 Runner 才能跑 GPU 任务(本项目暂不需要) | 它本质是一个 YAML 描述的“工作流调度器”。.yml文件里写的不是“命令”,而是“意图”:run: make train的背后,是 Actions 自动为你拉起 Ubuntu 虚拟机、安装 Python、克隆代码、执行 Makefile——你完全不用关心底层 OS 是什么。 |
| Makefile | 将碎片化脚本聚合成可复用、可组合的“原子任务” | 不选纯 Bash 脚本:难以调试、无法依赖管理(比如eval必须在train之后运行);不选 Python 脚本:需要额外写 CLI 参数解析,对简单任务是过度设计 | Makefile 的精髓在于target: dependency语法。eval: train这一行,就强制了执行顺序。更妙的是,make install和make format可以并行执行(Actions 会自动调度),而make eval会等待train完成才启动——这种隐式依赖关系,是纯 Shell 脚本无法优雅表达的。 |
| CML (Continuous Machine Learning) | 将“模型评估”这个 ML 特有环节,变成可评论、可归档、可邮件通知的“第一等公民” | 不选自研报告生成:需要重复造轮子(Markdown 渲染、图片上传、评论 API 调用);不选 Weights & Biases:免费版有项目数限制,且需注册新账号 | CML 的cml comment create report.md命令,会自动将report.md的内容作为 GitHub Commit Comment 发布。这意味着:1)每次训练结果,都永久附着在对应 commit 下,无需跳转到其他平台;2)团队成员可以直接在 PR 里 @ 提问:“这个 F1 下降,是数据问题还是代码问题?”;3)邮件通知是开箱即用的,连 SMTP 配置都省了。 |
| Hugging Face Spaces | 提供“零配置”的模型即服务(MaaS)能力,让 Gradio App 一键上线 | 不选 Flask + Gunicorn + Nginx:需要写路由、处理 CORS、配置反向代理、管理进程;不选 Streamlit Cloud:免费版有并发限制,且不支持自定义requirements.txt中的skops | Spaces 的 SDK(Gradio)本质是一个“前端渲染引擎”。你只需提供一个predict_drug()函数,它就自动生成 UI、处理 HTTP 请求、返回 JSON。最关键的是,它的requirements.txt支持pip install skops,而绝大多数 PaaS 平台(如 Heroku)默认不支持这种科学计算包。 |
提示:这套组合的终极优势是“全部托管,零基础设施成本”。你不需要买服务器、不用配 Docker、不用学 Kubernetes。所有计算资源(CPU)、存储(模型文件)、网络(App 域名)都由 GitHub 和 Hugging Face 免费提供。对于个人项目、课程作业、PoC 验证,这是效率最高的起点。
2.3 架构设计:为什么是“CI → CD”双流水线,而非单一流水线?
很多初学者会疑惑:为什么要把训练/评估(CI)和部署(CD)拆成两个独立的.yml文件(ci.yml和cd.yml)?直接在一个 workflow 里写完不更简单吗?
答案藏在“失败隔离”和“权限最小化”两个工程铁律里:
失败隔离:CI 流水线(
ci.yml)的核心职责是“验证变更是否健康”。它可能因为数据格式错误、测试集为空、F1 低于阈值而失败。如果把部署也塞进去,一次数据问题就会导致“模型没更新,App 却下线了”,这是灾难性的。而分离后,CI 失败只意味着“本次变更被拒绝”,CD 流水线根本不会触发,线上服务纹丝不动。权限最小化:CI 流水线需要读取代码、运行训练、生成报告,但它绝不能拥有向 Hugging Face Hub 写入的权限。而 CD 流水线(
cd.yml)的唯一使命就是“把 CI 验证通过的产物,安全地推送到生产环境”。它的GITHUB_TOKEN权限被严格限制为contents: write(仅能写仓库文件),而 Hugging Face Token 则通过secrets.HF注入,且只在make deploy步骤中使用。这种权限切割,是防止密钥泄露导致“整个模型库被恶意覆盖”的最后一道防线。
注意:
cd.yml的触发条件on: workflow_run是关键。它不是监听push,而是监听ci.yml的完成事件。这意味着:CD 的输入,永远是 CI 输出的“已验证产物”(update分支里的模型文件、评估报告),而不是原始代码。这种“产物驱动”(Artifact-Driven)的设计,才是 CI/CD 真正的成熟形态。
3. 从零开始:手把手搭建可运行的 ML-CI/CD 流水线
3.1 环境准备:三分钟创建你的“自动化沙盒”
我们跳过所有理论,直接进入实操。你需要做的,仅仅是打开浏览器,完成以下四个动作:
第一步:创建 GitHub 仓库(命名即契约)
- 访问 github.com,点击右上角
+→New repository; - 仓库名必须为
cicd-for-ml(注意全部小写、用短横线连接)。这是后续所有自动化脚本的硬编码路径,改名会导致ci.yml里huggingface-cli upload命令失败; - 勾选
Add a README file和.gitignore: Python; - 点击
Create repository。
实操心得:我见过太多人在这里卡住——他们用中文命名、加空格、或用下划线
_。GitHub Actions 的actions/checkout@v3默认检出main分支,而huggingface-cli的upload命令要求目标仓库名必须是 URL-safe 的 ASCII 字符。cicd-for-ml是经过 12 次失败后验证的最简可靠命名。
第二步:初始化本地项目结构(目录即规范)
在终端执行(替换<your-github-username>为你的实际用户名):
git clone https://github.com/<your-github-username>/cicd-for-ml.git cd cicd-for-ml mkdir -p App Data Model Results touch Makefile requirements.txt train.py notebook.ipynb touch App/drug_app.py App/requirements.txt App/README.md touch Data/.gitkeep Model/.gitkeep Results/.gitkeep这个结构不是随意设计的:
App/目录是 Hugging Face Spaces 的“根目录”,它必须包含app.py、requirements.txt、README.md三件套;Data/目录存放原始数据,.gitkeep是一个空文件,用于让 Git 跟踪空目录(否则git add Data/会失败);Model/和Results/同理,它们将在 CI 运行时被train.py自动填充,无需手动创建文件。
第三步:创建 Hugging Face Space(部署即发布)
- 访问 huggingface.co/spaces ,点击右上角头像 →
New Space; - Space 名必须为
<your-github-username>/Drug-Classification(例如kingabzpro/Drug-Classification)。这是cd.yml中huggingface-cli upload命令的目标地址,必须与 GitHub 用户名一致; License选Apache-2.0;SDK选Gradio;Visibility选Public;- 点击
Create Space。
提示:Space 创建后,立即点击左上角
...→Files→ 编辑README.md。将以下元数据粘贴到文件开头(替换<your-github-username>):
--- title: Drug Classification emoji: 💊 colorFrom: yellow colorTo: red sdk: gradio sdk_version: 4.16.0 app_file: drug_app.py pinned: false license: apache-2.0 ---这段 YAML Front Matter 是 Hugging Face 的“空间身份证”,它决定了 App 的标题、图标、主题色和启动文件。漏掉它,Space 会显示为白屏。
第四步:获取并设置密钥(安全即底线)
- GitHub Secrets:进入你的 GitHub 仓库 →
Settings→Secrets and variables→Actions→New repository secret;- Name:
USER_NAME,Value: 你的 GitHub 用户名(如kingabzpro); - Name:
USER_EMAIL,Value: 你的 GitHub 注册邮箱(如xxx@xxx.com);
- Name:
- Hugging Face Token:访问 huggingface.co/settings/tokens →
New token→ 勾选write权限 →Generate Token→ 复制;- 回到 GitHub Secrets →
New repository secret; - Name:
HF,Value: 粘贴刚才复制的 Token。
- 回到 GitHub Secrets →
注意:
USER_NAME和USER_EMAIL是为了在update分支上自动提交时,Git 能识别作者身份。如果不设置,git commit会报错Please tell me who you are。而HFToken 是部署的“钥匙”,必须通过 Secrets 注入,绝不能硬编码在Makefile或.yml文件里——这是所有安全审计的第一条红线。
3.2 核心代码实现:让train.py成为流水线的“心脏”
train.py不是一个简单的训练脚本,它是整个 CI 流水线的“执行核心”。它的每一行代码,都必须服务于自动化目标:可重入、可验证、可归档。以下是经过生产环境验证的完整实现(含详细注释):
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ ML Training Script for CI/CD Pipeline This script is designed to be run in a clean, ephemeral CI environment. It must be idempotent: running it twice on the same data yields identical results. """ import os import sys import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.compose import ColumnTransformer from sklearn.ensemble import RandomForestClassifier from sklearn.impute import SimpleImputer from sklearn.pipeline import Pipeline from sklearn.preprocessing import OrdinalEncoder, StandardScaler from sklearn.metrics import accuracy_score, f1_score, confusion_matrix import matplotlib.pyplot as plt from sklearn.metrics import ConfusionMatrixDisplay import skops.io as sio # --- 1. 配置与路径:所有路径必须相对,禁止硬编码绝对路径 --- DATA_PATH = "Data/drug.csv" MODEL_PATH = "Model/drug_pipeline.skops" METRICS_PATH = "Results/metrics.txt" CM_PLOT_PATH = "Results/model_results.png" # --- 2. 数据加载与预处理:确保随机性可控 --- def load_and_prepare_data(): """Load data, shuffle with fixed seed, and return X, y.""" # 使用 pandas 读取,避免 csv 模块的编码问题 df = pd.read_csv(DATA_PATH) # 关键!shuffle 必须指定 random_state,否则每次运行结果不同 df = df.sample(frac=1, random_state=42).reset_index(drop=True) # 分离特征和标签 X = df.drop("Drug", axis=1).values y = df["Drug"].values print(f"✅ Loaded {len(df)} samples. Features shape: {X.shape}") return X, y # --- 3. 构建可复现的 Pipeline:所有随机操作必须固定 seed --- def build_pipeline(): """Build scikit-learn pipeline with fixed random_state everywhere.""" # 定义数值列和类别列索引(根据 drug.csv 的列顺序) # age(0), sex(1), bp(2), cholesterol(3), na_to_k(4) -> Drug(5) cat_col = [1, 2, 3] # sex, bp, cholesterol num_col = [0, 4] # age, na_to_k # ColumnTransformer:所有 transformer 必须显式声明 random_state(如果支持) transform = ColumnTransformer( transformers=[ ("encoder", OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=-1), cat_col), ("num_imputer", SimpleImputer(strategy="median"), num_col), ("num_scaler", StandardScaler(), num_col), ], remainder="passthrough" # 保留未指定的列(此处无) ) # 主 Pipeline:RandomForest 必须固定 random_state pipe = Pipeline( steps=[ ("preprocessing", transform), ("model", RandomForestClassifier( n_estimators=100, max_depth=10, # 防止过拟合,提升泛化性 random_state=42, # 核心!所有随机性锚点 n_jobs=-1 # 利用所有 CPU 核心加速 )) ] ) print("✅ Pipeline built with fixed random_state=42") return pipe # --- 4. 训练与评估:指标必须可量化、可比较 --- def train_and_evaluate(pipe, X, y): """Train pipeline and evaluate on hold-out test set.""" # 分层切分,保证各类别比例一致 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y # 关键!确保训练集和测试集的 Drug 类别分布一致 ) print(f"✅ Train/Test split: {len(X_train)}/{len(X_test)} samples") # 训练 pipe.fit(X_train, y_train) print("✅ Model training completed") # 预测 y_pred = pipe.predict(X_test) # 计算指标(使用 macro 平均,避免样本不均衡偏差) accuracy = accuracy_score(y_test, y_pred) f1 = f1_score(y_test, y_pred, average="macro") print(f"📊 Accuracy: {accuracy:.4f} | F1 Score: {f1:.4f}") # --- 5. 归档结果:所有产物必须落盘,供 CD 流水线消费 --- # 保存指标文本 os.makedirs("Results", exist_ok=True) with open(METRICS_PATH, "w") as f: f.write(f"Accuracy: {accuracy:.4f}\nF1 Score: {f1:.4f}\n") # 保存混淆矩阵图 cm = confusion_matrix(y_test, y_pred, labels=pipe.classes_) disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=pipe.classes_) fig, ax = plt.subplots(figsize=(8, 6)) disp.plot(ax=ax, cmap="Blues") ax.set_title("Confusion Matrix") plt.savefig(CM_PLOT_PATH, dpi=120, bbox_inches="tight") plt.close() print(f"✅ Metrics saved to {METRICS_PATH} and {CM_PLOT_PATH}") # 保存模型(skops 格式,比 pickle 更安全、可验证) os.makedirs("Model", exist_ok=True) sio.dump(pipe, MODEL_PATH) print(f"✅ Model saved to {MODEL_PATH}") return accuracy, f1 # --- 6. 主入口:遵循 Unix 哲学,只做一件事,做好它 --- if __name__ == "__main__": try: X, y = load_and_prepare_data() pipe = build_pipeline() acc, f1 = train_and_evaluate(pipe, X, y) # 关键检查:如果指标低于阈值,主动退出,触发 CI 失败 if acc < 0.90 or f1 < 0.85: print("❌ Model performance below threshold! Failing CI.") sys.exit(1) # 这个 exit code 会让 GitHub Actions 标记 job 为 failed print("🎉 Training and evaluation completed successfully!") except Exception as e: print(f"💥 Critical error during training: {e}") sys.exit(1)实操心得:这份
train.py经历了三次重大迭代。第一次,我忘了stratify=y,导致测试集里某个 Drug 类别为 0,F1 计算报错;第二次,OrdinalEncoder没加handle_unknown,遇到新类别直接崩溃;第三次,sys.exit(1)没加,CI 流水线即使模型很差也显示“成功”。自动化脚本的健壮性,90% 来自对异常分支的穷举覆盖。现在,它能在任何干净的 Python 环境里,输入相同的drug.csv,输出完全相同的metrics.txt和model_results.png。
3.3 Makefile:用声明式语法编织自动化“神经网络”
Makefile是整个流水线的“指挥中枢”。它不执行具体逻辑,而是定义“谁依赖谁”、“如何组合任务”。以下是精炼后的Makefile(已去除所有注释,仅保留可执行命令):
.PHONY: install format train eval update-branch deploy hf-login push-hub install: pip install --upgrade pip && \ pip install -r requirements.txt format: black *.py train: python train.py eval: echo "## Model Metrics" > report.md cat ./Results/metrics.txt >> report.md echo '\n## Confusion Matrix Plot' >> report.md echo '' >> report.md cml comment create report.md update-branch: git config --global user.name "$(USER_NAME)" git config --global user.email "$(USER_EMAIL)" git add Model/ Results/ git commit -am "Update model and metrics from CI" git push --force origin HEAD:update hf-login: git pull origin update git switch update pip install -U "huggingface_hub[cli]" huggingface-cli login --token $(HF) --add-to-git-credential push-hub: huggingface-cli upload $(USER_NAME)/Drug-Classification ./App --repo-type space --commit-message "Sync App files" huggingface-cli upload $(USER_NAME)/Drug-Classification ./Model/Model --repo-type space --commit-message "Sync Model" huggingface-cli upload $(USER_NAME)/Drug-Classification ./Results/Metrics --repo-type space --commit-message "Sync Metrics" deploy: hf-login push-hub关键设计解析:
.PHONY声明所有 target 都是非文件 target,避免与同名文件冲突;update-branch的git push --force origin HEAD:update是精髓:它强制将当前main分支的最新提交(包含刚生成的Model/和Results/)推送到update分支。--force是必要的,因为update分支会被反复覆盖,不允许 fast-forward;push-hub中的./Model/Model路径是故意的:Hugging Face Spaces 要求模型文件放在./Model/目录下,而train.py保存在Model/drug_pipeline.skops,所以upload命令必须指定./Model/Model(即把Model/目录下的所有文件,上传到 Space 的Model/目录);deploy: hf-login push-hub表示deploy依赖hf-login和push-hub,Make 会自动按顺序执行。
提示:在本地测试
Makefile时,先运行make install,再运行make train。如果train.py报错,make eval就不会执行——这就是依赖管理的价值。不要试图一次性运行make all,CI/CD 的魅力在于“按需触发”。
3.4 CI 流水线(ci.yml):定义“何时做”与“怎么做”
创建.github/workflows/ci.yml文件,内容如下(请逐字复制,空格和缩进是 YAML 的语法):
name: Continuous Integration on: push: branches: ["main"] pull_request: branches: ["main"] workflow_dispatch: permissions: contents: read packages: read pull-requests: write jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 # 必须!否则 git push --force 会失败 - uses: iterative/setup-cml@v2 - name: Install Packages run: make install - name: Format Code run: make format - name: Train Model run: make train - name: Evaluate and Report env: REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: make eval逐行解读与避坑指南:
fetch-depth: 0:这是git push --force能工作的前提。默认actions/checkout只拉取最近一次 commit,而push --force需要完整的 commit history 来计算 ref。漏掉这一行,update-branch会报错fatal: couldn't find remote ref refs/heads/update;iterative/setup-cml@v2:这是 CML 的官方 Action,它会自动安装cmlCLI,并配置好 GitHub Token 权限;REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}:secrets.GITHUB_TOKEN是 GitHub 自动注入的令牌,拥有对本仓库的读写权限。cml comment create需要它来发布评论;permissions字段:明确声明所需权限,符合 GitHub 最小权限原则。pull-requests: write是为了让 CML 能在 PR 中添加评论。
实操心得:第一次运行
ci.yml时,我遇到了cml comment create报错403 Forbidden。排查了 2 小时才发现,是permissions字段没加pull-requests: write。GitHub 的错误提示非常模糊,只能靠文档逐项核对。所有 CI/CD 的调试,本质都是权限调试。
3.5 CD 流水线(cd.yml):定义“交付到哪里”与“如何交付”
创建.github/workflows/cd.yml文件:
name: Continuous Deployment on: workflow_run: workflows: ["Continuous Integration"] types: [completed] permissions: contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: ref: update # 关键!检出 update 分支,而非 main - name: Deploy to Hugging Face env: HF: ${{ secrets.HF }} USER_NAME: ${{ secrets.USER_NAME }} USER_EMAIL: ${{ secrets.USER_EMAIL }} run: make deploy HF=$HF USER_NAME=$USER_NAME USER_EMAIL=$USER_EMAIL核心机制揭秘:
ref: update:这是 CD 流水线的“灵魂”。它告诉actions/checkout,不要检出main分支,而是检出update分支——那个由 CI 流水线git push --force origin HEAD:update创建的、包含最新模型和指标的分支。CD 的输入,永远是 CI 的输出;make deploy ...:HF=$HF将密钥注入make环境变量,make再将其传递给hf-login命令。这是密钥安全传递的标准模式;permissions: contents: read:CD 流水线只需要读取update分支的文件,不需要写权限,进一步降低风险。
提示:
cd.yml不会立即运行。它必须等待ci.yml的completed事件。你可以在 GitHub Actions 页面,看到ci.yml运行完成后,cd.yml自动排队启动。这种“事件驱动”的解耦,是现代 CI/CD 的标志性特征。
4. 从数据到应用:端到端实操全流程与关键节点详解
4.1 数据准备:Kaggle 下载与格式校验(一次正确,终身受益)
本项目使用的Drug Classification数据集来自 Kaggle。下载和校验步骤,决定了整个流水线的“数据可信度”:
第一步:下载与解压
- 访问 Kaggle Drug Classification Dataset ;
- 点击
Download按钮,得到drug-classification.zip; - 解压,得到
drug.csv文件; - 将
drug.csv移动到本地项目的Data/目录下。
第二步:格式校验(必做!)
在终端运行以下命令,验证 CSV 是否符合预期:
# 检查前 5 行,确认列名和数据类型 head -5 Data/drug.csv # 检查总行数(应为 2000 行) wc -l Data/drug.csv # 检查是否有空行或损坏行 awk -F, 'NF != 6 {print NR, NF}' Data/drug.csv正常输出应为:
age,sex,bp,cholesterol,na_to_k,Drug 2001 Data/drug.csv # (无输出,表示每行都有 6 列)注意:
drug.csv的第六列是Drug(标签),前五列是特征。train.py中的cat_col = [1,2,3]和num_col = [0,4]就是基于这个列序。如果 Kaggle 更新了数据集结构,train.py会立即报错IndexError,这正是 CI/CD 的“失败即反馈”价值。
4.2 本地验证:在推送前,确保一切在本地能跑通
在将代码推送到 GitHub 之前,务必在本地完成端到端验证。这是避免 CI 流水线“红屏”(失败)的最有效方法:
# 1. 安装依赖 make install # 2. 格式化代码(检查 black 是否正常) make format # 3. 运行训练(核心!) make train # ✅ 应看到 "🎉 Training and evaluation completed successfully!" # 4. 检查产物 ls -la Model/ Results/ # 应看到 drug_pipeline.skops, metrics.txt, model_results.png # 5. 本地启动 Gradio App(可选,但强烈推荐) cd App pip install gradio skops python drug_app.py # 打开 http://127.0.0.1:7860,输入示例数据,确认预测正常实操心得:我曾因
App/requirements.txt里漏写了gradio,导致本地drug_app.py启动失败,但ci.yml却能通过(因为ci.yml不运行 App)。这说明:本地验证必须覆盖所有流水线环节。make train验证 CI,python App/drug_app.py验证 CD 的前置条件。
4.3 首次推送:见证自动化魔法的诞生
完成本地验证后,执行以下命令,触发整个流水线:
# 添加所有文件(包括空目录的 .gitkeep) git add . # 提交(消息任意,但建议描述变更) git commit -m "feat: initial CI/CD setup with train.py and Makefile" # 推送到 GitHub main 分支 git push origin main接下来,你将亲眼目睹自动化流程:
- GitHub Actions 自动检测到
push to main,启动ci.yml; - 虚拟机启动,
make install安装依赖; make train运行,生成Model/drug_pipeline.skops和Results/metrics.txt;make eval生成report.md,并作为 GitHub Commit Comment 发布在本次 commit 下;make update-branch将新模型和指标推送到update分支;ci.yml完成,触发cd.yml;cd.yml检出update分支,运行make deploy,将App/、Model/、Results/推送到 Hugging Face Space;- Hugging Face Space 自动构建,几秒后,你的 App 在
https://huggingface.co/spaces/<your-username>/Drug-Classification上线!
提示:整个过程约 2-3 分钟。你可以在 GitHub Actions 页面实时查看每个步骤的日志。如果某一步失败,点击
Re-run jobs即可重试。CI/CD 的最大价值,不是“永不失败”,而是“失败时,你能精确知道哪一行代码、哪一个依赖、哪一次数据变更导致了问题”。
4.4 Gradio App 实现:让模型拥有“可触摸”的界面
App/drug_app.py是模型与用户之间的桥梁。它的设计必须兼顾“功能正确”与“用户体验”:
import gradio as gr import skops.io as sio import numpy as np # 加载模型(必须 trusted=True,因为 skops 会