
你大概用过不少健康管理类的App——输入年龄、性别、BMI、血压再填几项血液指标它就能给出一个风险评分或健康提示。这个评分背后往往是一个训练好的机器学习模型在实时计算。但模型在Jupyter Notebook里跑通和让它变成成千上万用户都能调用的服务中间隔着一条不小的鸿沟。今天我们就以“乳腺肿瘤风险筛查”这个具体场景为例选这个是因为scikit-learn库中自带了真实的数据集手把手走一遍从模型训练到API封装再到Docker容器化、最终发布上线的完整流程。读完希望你能把自己的模型变成一个随时可调用的Web服务。一、先规划好项目结构动手之前先想清楚项目怎么组织。一个清晰的结构能让后续开发、调试和部署都顺心很多。我们创建一个名为health-risk-api的文件夹里面分成几个部分src目录放FastAPI应用代码artifacts目录存放训练好的模型文件根目录下放训练脚本、依赖清单和Dockerfile。具体长这样health-risk-api/ ├── src/ │ ├── __init__.py │ └── api.py # FastAPI 应用 ├── artifacts/ │ └── breast_cancer_model.joblib # 训练好的模型 ├── build_model.py # 模型训练脚本 ├── requirements.txt # Python 依赖 └── Dockerfile # 容器配置这个结构不复杂但把训练、服务、依赖和容器配置都分开了后面每一步该做什么一目了然。二、准备开发环境接下来需要一台装有Python 3.9和Docker的机器。Python用来训练模型和写APIDocker用来打包运行环境。为了避免污染系统环境我们先建一个虚拟环境再安装必要的库scikit-learn、pandas、fastapi和uvicorn。python -m venv health-env source health-env/bin/activate # Windows 用 health-env\Scripts\activate pip install scikit-learn pandas fastapi uvicorn joblib这几行命令执行完开发环境就齐了。这里多装了一个joblib后面保存模型会用到它比pickle更适合存储scikit-learn模型。三、训练一个肿瘤风险分类模型我们直接使用scikit-learn内置的乳腺癌数据集。它包含569条样本每条有30个细胞核特征半径、纹理、周长、面积等目标是把肿瘤分为恶性或良性。这个数据集规模适中特征丰富用来演示部署流程再合适不过。我们选逻辑回归作为分类器因为它训练快、可解释性好而且输出概率值方便我们包装成“风险评分”。在build_model.py中我们这样写import joblib from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, roc_auc_score from sklearn.pipeline import Pipeline # 加载数据 data load_breast_cancer() X, y data.data, data.target # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 构建包含标准化和逻辑回归的管道 pipe Pipeline([ (scaler, StandardScaler()), (clf, LogisticRegression(max_iter1000, random_state42)) ]) pipe.fit(X_train, y_train) # 评估 y_pred pipe.predict(X_test) y_prob pipe.predict_proba(X_test)[:, 1] print(f准确率: {accuracy_score(y_test, y_pred):.4f}) print(fAUC: {roc_auc_score(y_test, y_prob):.4f}) # 保存整个管道 joblib.dump(pipe, artifacts/breast_cancer_model.joblib)运行这个脚本你会在artifacts目录下得到breast_cancer_model.joblib。$ python build_model.py 准确率: 0.9825 AUC: 0.9954这个文件里既包含标准化器也包含分类器后面API直接加载它就能完成端到端的预测。四、用FastAPI把模型包装成API模型有了但总不能要求每个用户都装Python和scikit-learn吧这时候FastAPI就派上用场了。它轻量、性能好还能自动生成交互式文档。我们在src/api.py里做三件事定义输入数据格式、加载模型、提供预测接口。先用Pydantic定义输入结构。乳腺癌数据集一共有30个特征分别是10个均值特征、10个误差特征和10个最差值特征。Pydantic的模型字段顺序很重要因为我们在后面拼装numpy数组时必须严格按训练时的特征顺序来否则模型会用错数据预测结果就完全不可信了。from pydantic import BaseModel class TumorFeatures(BaseModel): # 10 个均值特征 mean_radius: float mean_texture: float mean_perimeter: float mean_area: float mean_smoothness: float mean_compactness: float mean_concavity: float mean_concave_points: float mean_symmetry: float mean_fractal_dimension: float # 10 个误差特征 radius_error: float texture_error: float perimeter_error: float area_error: float smoothness_error: float compactness_error: float concavity_error: float concave_points_error: float symmetry_error: float fractal_dimension_error: float # 10 个最差特征 worst_radius: float worst_texture: float worst_perimeter: float worst_area: float worst_smoothness: float worst_compactness: float worst_concavity: float worst_concave_points: float worst_symmetry: float worst_fractal_dimension: float接着初始化FastAPI应用并用lifespan在启动时加载模型。这样比在模块顶层直接加载更规范也方便后面做测试。import joblib import numpy as np from contextlib import asynccontextmanager from fastapi import FastAPI ml_model {} asynccontextmanager async def lifespan(app: FastAPI): ml_model[pipe] joblib.load(artifacts/breast_cancer_model.joblib) yield ml_model.clear() app FastAPI(title乳腺肿瘤风险预测 API, lifespanlifespan)然后写预测端点。用户POST过来一组特征我们按顺序转成numpy数组调用模型推理返回恶性概率和风险等级app.post(/v1/predict) def predict_risk(features: TumorFeatures): input_array np.array([[ features.mean_radius, features.mean_texture, features.mean_perimeter, features.mean_area, features.mean_smoothness, features.mean_compactness, features.mean_concavity, features.mean_concave_points, features.mean_symmetry, features.mean_fractal_dimension, features.radius_error, features.texture_error, features.perimeter_error, features.area_error, features.smoothness_error, features.compactness_error, features.concavity_error, features.concave_points_error, features.symmetry_error, features.fractal_dimension_error, features.worst_radius, features.worst_texture, features.worst_perimeter, features.worst_area, features.worst_smoothness, features.worst_compactness, features.worst_concavity, features.worst_concave_points, features.worst_symmetry, features.worst_fractal_dimension ]]) prob ml_model[pipe].predict_proba(input_array)[0, 1] risk_level 高风险 if prob 0.7 else (中风险 if prob 0.3 else 低风险) return { malignancy_probability: round(float(prob), 4), risk_level: risk_level }再补一个根路径的健康检查方便确认服务是否活着app.get(/) def health_check(): return {status: ok, service: 乳腺肿瘤风险预测}五、在本地把服务跑起来代码写好了先在本地验证一下。用uvicorn启动开发服务器uvicorn src.api:app --reload --port 8000看到“Uvicorn running on http://127.0.0.1:8000”就说明服务起来了。打开浏览器访问http://localhost:8000/docsFastAPI会自动生成Swagger文档你可以直接在里面点“Try it out”发请求。当然也可以用curlcurl -X POST http://localhost:8000/v1/predict \ -H Content-Type: application/json \ -d { mean_radius: 17.99, mean_texture: 10.38, mean_perimeter: 122.8, mean_area: 1001.0, mean_smoothness: 0.1184, mean_compactness: 0.2776, mean_concavity: 0.3001, mean_concave_points: 0.1471, mean_symmetry: 0.2419, mean_fractal_dimension: 0.07871, radius_error: 1.095, texture_error: 0.9053, perimeter_error: 8.589, area_error: 153.4, smoothness_error: 0.006399, compactness_error: 0.04904, concavity_error: 0.05373, concave_points_error: 0.01587, symmetry_error: 0.03003, fractal_dimension_error: 0.006193, worst_radius: 25.38, worst_texture: 17.33, worst_perimeter: 184.6, worst_area: 2019.0, worst_smoothness: 0.1622, worst_compactness: 0.6656, worst_concavity: 0.7119, worst_concave_points: 0.2654, worst_symmetry: 0.4601, worst_fractal_dimension: 0.1189 }如果返回类似{malignancy_probability: 0.9823, risk_level: 高风险}说明本地服务已经通了。顺便说一句这条样本恰好是数据集里标签为恶性的第一条所以高风险的结果也是符合预期的——这也能反过来验证你的API没有把特征拼错。六、用Docker把服务装进容器本地跑通只是第一步。真实部署时目标服务器可能没有Python环境或者版本不一致。Docker能把应用和所有依赖打包成一个镜像到哪都能跑。我们先写requirements.txt把版本固定下来保证环境一致fastapi0.115.12 uvicorn0.34.2 scikit-learn1.6.1 pandas2.2.3 numpy2.2.6 joblib1.4.2然后写Dockerfile。基于官方python:3.12-slim镜像设置工作目录为/code复制依赖并安装再把应用代码和模型文件复制进去暴露8000端口最后用uvicorn启动FROM python:3.12-slim WORKDIR /code COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY src/ ./src/ COPY artifacts/ ./artifacts/ EXPOSE 8000 CMD [uvicorn, src.api:app, --host, 0.0.0.0, --port, 8000]在项目根目录执行构建docker build -t health-risk-api .构建完成后运行容器docker run -d -p 8000:8000 health-risk-api现在无论在哪台机器上只要装了Docker拉取这个镜像就能得到一模一样的服务。七、发布到Docker Hub镜像在本地构建好了怎么分享给其他人或者部署到云服务器最直接的办法是推到Docker Hub。先登录docker login然后给镜像打上你的用户名和版本标签再推送docker tag health-risk-api your-username/health-risk-api:v1.0 docker tag health-risk-api your-username/health-risk-api:latest docker push your-username/health-risk-api:v1.0 docker push your-username/health-risk-api:latest推送完成后任何一台机器都可以用docker pull your-username/health-risk-api:v1.0拉取然后docker run启动。之后如果你想把服务部署到阿里云、腾讯云或者AWS ECS也只需要基于这个镜像配置即可。八、还能继续做什么走到这一步你已经拥有了一个可用的模型API。但如果要上生产还可以考虑几件事加一层认证比如API Key防止接口被滥用接入日志和监控观察预测延迟和请求量如果业务需要一次性预测多条记录可以再加一个批量预测端点。这些都可以在现有FastAPI应用上逐步扩展。从训练一个模型到把它变成Docker镜像并推送到仓库整个过程其实并不神秘。关键是把每一步拆开先让本地跑通再考虑环境一致性和分发。你完全可以把今天这套流程套用到自己的模型上——不管是预测房价、销量还是设备故障思路都是一样的。