# src/train.pyimport os
import pickle
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.impute import SimpleImputer
from sklearn.metrics import(
accuracy_score,
classification_report,
confusion_matrix,
roc_auc_score,)from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
defload_data():
data_path ="data/train.csv"
df = pd.read_csv(data_path,low_memory=False)print("Data loaded successfully!")return df
defpreprocess_data(df):# Drop unnecessary columns
df = df.drop(columns=["ID","Customer_ID","SSN","Name","Month"])# Drop rows with missing values
df = df.dropna()# Convert data types# Convert the 'Age' column to numeric, setting errors='coerce' to handle non-numeric values
df["Age"]= pd.to_numeric(df["Age"], errors="coerce")# Filter the DataFrame to include only rows where 'Age' is between 1 and 60
df = df[(df["Age"]>=1)&(df["Age"]<=60)]
df["Annual_Income"]= pd.to_numeric(df["Annual_Income"], errors="coerce")
df["Monthly_Inhand_Salary"]= pd.to_numeric(
df["Monthly_Inhand_Salary"], errors="coerce")# Separate features and target
X = df.drop("Credit_Score", axis=1)
y = df["Credit_Score"]print("Data preprocessed successfully!")return X, y
defencode_data(X):# Identify categorical and numerical features
categorical_features =["Occupation","Credit_Mix","Payment_of_Min_Amount","Payment_Behaviour","Type_of_Loan",]
numerical_features = X.select_dtypes(include=["int64","float64"]).columns.tolist()# Define preprocessing steps
numerical_transformer = SimpleImputer(strategy="median")
categorical_transformer = Pipeline(
steps=[("imputer", SimpleImputer(strategy="most_frequent")),("onehot", OneHotEncoder(handle_unknown="ignore")),])
preprocessor = ColumnTransformer(
transformers=[("num", numerical_transformer, numerical_features),("cat", categorical_transformer, categorical_features),])return preprocessor
defsplit_data(X, y):
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42)return X_train, X_test, y_train, y_test
deftrain_model(X_train, y_train, preprocessor):# Create a pipeline with preprocessing and model
clf = Pipeline(
steps=[("preprocessor", preprocessor),("classifier", RandomForestClassifier(n_estimators=100)),])# Train the model
clf.fit(X_train, y_train)# Return the trained modelreturn clf
defevaluate_model(clf, X_test, y_test):# Predict and evaluate
y_pred = clf.predict(X_test)
acc = accuracy_score(y_test, y_pred)
report = classification_report(y_test, y_pred, labels=["Poor","Standard","Good"])
cm = confusion_matrix(y_test, y_pred, labels=["Poor","Standard","Good"])# Calculate AUC score
y_test_encoded = y_test.replace({"Poor":0,"Standard":1,"Good":2})
y_pred_proba = clf.predict_proba(X_test)
auc_score = roc_auc_score(y_test_encoded, y_pred_proba, multi_class="ovr")# Print metricsprint("Model Evaluation Metrics:")print(f"Accuracy: {acc}")print(f"AUC Score: {auc_score}")print("Classification Report:")print(report)# Plot confusion matrix
plt.figure(figsize=(8,6))
plt.imshow(cm, interpolation="nearest", cmap=plt.cm.Blues)
plt.title("Confusion Matrix")
plt.colorbar()
tick_marks = np.arange(3)
plt.xticks(tick_marks,["Poor","Standard","Good"], rotation=45)
plt.yticks(tick_marks,["Poor","Standard","Good"])
plt.ylabel("True label")
plt.xlabel("Predicted label")
plt.tight_layout()
cm_path = os.path.join("model","confusion_matrix.png")
plt.savefig(cm_path)print(f"Confusion matrix saved to {cm_path}")defsave_model(clf):
model_dir ="model"
os.makedirs(model_dir, exist_ok=True)
model_path = os.path.join(model_dir,"model.pkl")# Save the trained modelwithopen(model_path,"wb")as f:
pickle.dump(clf, f)print(f"Model saved to {model_path}")defmain():# Execute steps
df = load_data()
X, y = preprocess_data(df)
preprocessor = encode_data(X)
X_train, X_test, y_train, y_test = split_data(X, y)
clf = train_model(X_train, y_train, preprocessor)
evaluate_model(clf, X_test, y_test)
save_model(clf)if __name__ =="__main__":
main()技术支持
模型服务脚本将通过模型文件加载已保存的模型管道,创建一个接收用户字典列表的 POST 请求函数,将其转换为 DataFrame,输入模型生成预测并返回预测标签。我们使用 FastAPI 作为 API 框架,仅需少量代码即可提供模型服务。
src/app.py:
# src/app.pyimport pickle
from fastapi import FastAPI
from pydantic import BaseModel
import pandas as pd
import os
# Load the trained model
model_path = os.path.join("model","model.pkl")withopen(model_path,"rb")as f:
model = pickle.load(f)
app = FastAPI()classInputData(BaseModel):
data:list# List of dictionaries representing feature values@app.post("/predict")defpredict(input_data: InputData):# Convert input data to DataFrame
X_new = pd.DataFrame(input_data.data)# Ensure the columns match the training data
prediction = model.predict(X_new)# Return predictionsreturn{"prediction": prediction.tolist()}技术支持
# Dockerfile
FROM python:3.9-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir-r requirements.txt
# Copy the application code
COPY ./src/./src/
COPY ./data/./data/# Ensure the model directory exists and is copied
RUN mkdir -p model
# Run the training script during the build
RUN python src/train.py
# Expose the port for the API
EXPOSE 8000# Run the FastAPI app
CMD ["uvicorn","src.app:app","--host","0.0.0.0","--port","8000"]技术支持
INFO: Started server process [1]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: 10.88.0.1:36886 - "POST /predict HTTP/1.1"200 OK技术支持
IT 领域的每位开发者和运维工程师都熟悉使用 Docker 在本地或云端构建与部署应用。不过,作为开发者或机器学习运维工程师,您可能正在寻求优化资源、提升安全性并改进系统集成的方案。Podman 提供了一个值得关注的替代选择。它是一款免费、开源的工具,可替代 Docker 和 Docker Desktop。
在本教程中,我们将介绍 Podman 是什么、Podman 与 Docker 的差异,以及如何安装和使用 Podman。此外,我们还将讲解如何使用 Dockerfile 和 Podman 命令在本地训练、评估和部署机器学习模型。
图片来源:作者
什么是 Podman?
Podman 是一款开源的容器管理工具,旨在为开发者和机器学习工程师提供无缝且安全的使用体验。与 Docker 不同,Podman 采用无守护进程(daemonless)架构,支持以无特权(rootless)方式运行容器,从而提升安全性和灵活性。这一关键特性使其无需 root 权限即可运行容器,降低了潜在风险。
Podman 完全兼容 OCI(开放容器倡议)标准,确保使用 Podman 创建的容器与镜像可以轻松集成到其他符合 OCI 的工具与平台中,如 runc、Buildah 和 Skopeo。此外,Podman 支持创建与管理 pod,即共享同一网络命名空间的一组容器,类似于 Kubernetes 的 pod。
使用 Podman 的一大优势在于,其体验与 Docker 相当接近。命令行界面与 Docker 类似,您也可以从 Docker Hub 拉取镜像。这种相似性让熟悉 Docker 的用户能够轻松迁移,同时还提供满足容器化应用开发与部署不断演进需求的高级特性。
Podman 只是 MLOps 工具库中的一个工具。想全面了解 MLOps 生态中的各类工具,请阅读博文《2025 年必知的 25 款 MLOps 工具》。
Docker 与 Podman 对比
Docker 和 Podman 是两款知名的容器管理工具,各自拥有不同的特性与能力。以下对比将帮助您了解它们的差异,并协助您选择更契合需求的工具。
Docker
Podman
架构
Docker 采用客户端-服务器架构,依赖名为 dockerd 的守护进程。
Podman 无需守护进程,采用 fork-exec 模型,提升了安全性与简洁性。
安全性
Docker 默认以 root 身份运行容器,可能带来安全风险。
Podman 默认支持无特权容器,降低安全风险。
镜像管理
Docker 使用其自有工具构建并管理容器镜像。
Podman 借助 Buildah 构建镜像,并可运行来自 Docker 注册表的镜像。
兼容性
Docker 使用广泛,并与众多 CI/CD 工具集成。
Podman 提供与 Docker 兼容的 CLI,便于在不改动工作流的情况下迁移。
容器编排
Docker 支持 Docker Swarm 与 Kubernetes 进行编排。
Podman 不支持 Docker Swarm,但可通过 pod 与 Kubernetes 协同工作。
平台支持
Docker 可原生运行于 Linux、macOS 和 Windows(配合 WSL)。
Podman 同样支持 Linux、macOS 和 Windows(配合 WSL)。
性能
Docker 以高效的资源管理与快速部署闻名。
Podman 整体性能相当,且通常具有更快的启动时间。
使用场景
适合需要成熟工具与集成的项目。
适合优先关注安全与轻量化的环境,非常适用于大规模部署。
在 Docker 与 Podman 之间进行选择,主要取决于项目的具体需求,尤其是安全性、兼容性与编排方面。
对于既有的 CI/CD 流水线与完善的容器管理,Docker 仍是强有力的选项;而对强调安全与无特权操作的环境,Podman 则提供了一个安全、轻量的替代方案,同时具备更快的启动速度,适合大规模部署。
查看我们的入门文章 《面向数据科学的 Docker》,其中包含示例代码与案例。
安装 Podman
首先,请前往官方网站下载并安装 Podman Desktop 安装包。
来源:Podman
安装过程简洁迅速。几分钟内,您将进入入门界面,并被询问是否安装可选扩展。
如果您的 Windows 未安装 WSL,它将自动进行安装。
接下来,设置 Podman machine。
与 Docker 相比,您无需单独设置 machine。然而,在 Podman 中,您可以同时管理多个运行不同容器的 machine,从而实现更佳的资源管理。
我们的 machine 已启动并运行,随时可以创建镜像并运行容器。
为验证 Podman 是否正常工作,我们将从 quay.io 拉取一个示例镜像并执行容器。
Podman machine 已成功拉取镜像并运行容器,同时输出了日志。
使用 Podman 构建 MLOps 项目
在本次 MLOps 项目中,我们将通过 Dockerfile 和 Podman 实现模型的自动化训练与评估,并对模型进行服务化。流程与 Docker 类似,但我们将使用 Podman CLI 构建镜像并运行容器。
如果您对相关概念还不熟悉,可通过完成MLOps 概念课程学习基础知识。
1. 搭建机器学习项目
要搭建机器学习项目,我们需要创建训练与服务脚本,并准备 requirements.txt 文件用于安装所需的 Python 包。
训练脚本将加载信用评分分类数据集,进行处理与编码并训练模型,同时完成模型评估。最后,我们将使用 pickle 格式保存预处理与训练管道及模型。
src/train.py:
模型服务脚本将通过模型文件加载已保存的模型管道,创建一个接收用户字典列表的 POST 请求函数,将其转换为 DataFrame,输入模型生成预测并返回预测标签。我们使用 FastAPI 作为 API 框架,仅需少量代码即可提供模型服务。
src/app.py:
我们需要创建一个
requirements.txt文件,包含运行上述脚本所需的全部 Python 包。该文件将用于在 Docker 容器中搭建运行环境,确保我们可以顺利执行 Python 脚本。requirements.txt:
2. 创建 Dockerfile
创建一个“Dockerfile”,并添加以下代码。
该 Dockerfile 将执行以下步骤:
本地工作区应按如下结构组织:
data文件夹models文件夹src文件夹requirements.txt与Dockerfile其余文件为自动化与 Git 操作的辅助组件。
3. 使用 Podman 构建 Docker 镜像
构建 Docker 镜像很简单:只需在 Dockerfile 所在的当前目录,使用
build命令并指定镜像名称。构建工具会按顺序执行 Dockerfile 中的所有指令,从环境搭建到提供机器学习应用服务。
我们也可以在日志中看到模型评估结果。模型准确率为 75%,ROC AUC 得分为 0.89,属于中等水平。
如果您打开 Podman Desktop,并点击“Images”标签页,您会看到您的
mlops_app镜像已成功创建。查看《面向数据科学的 Docker》速查表,学习所有相关的 Docker 命令。只需将首个命令
docker替换为podman即可。4. 使用 Podman 运行 Docker 容器
我们将使用
run命令,从mlops-app镜像以分离模式(-d)启动一个名为 "mlops_container" 的容器,并将容器的8000端口映射到宿主机的8000端口。这样即可从容器外部访问 FastAPI 应用。要查看 "mlops_container" 的所有日志,请使用
logs命令。输出:
您也可以打开 Podman Desktop,点击“Containers”标签页查看正在运行的容器。
在 Podman Desktop 中查看日志:点击“mlops_container”,然后选择“Terminal”标签页。
5. 测试 ML 推理服务
我们现在通过访问交互式 Swagger UI 进行测试:http://localhost:8000/docs。Swagger UI 提供了友好的界面,便于您探索所有可用的 API 端点。
我们也可以在终端中使用 CURL 命令测试 API。
FastAPI 服务器运行正常,成功处理了用户输入并返回准确的预测结果。
6. 停止并移除容器
体验完 API 后,我们将使用
stop命令停止容器。同时也可以使用
rm命令移除容器,释放系统资源。移除前必须先停止容器。7. 移除镜像
要移除名为 "mlops-app" 的本地容器镜像,我们将使用
rmi命令。如果您在运行上述代码或编写 Dockerfile 时遇到问题,请查看 GitHub 仓库kingabzpro/mlops-with-podman。其中包含使用指南和在您本地执行代码所需的全部文件。
您学习的下一步是尝试用 Podman 构建10 个 Docker 项目创意(从入门到高级),以加深您对 Podman 生态的理解。
结语
在某些用例下,Podman 是 Docker 的有力替代,但许多开发者仍偏好 Docker Desktop 与 CLI,这主要得益于 Docker 广泛的集成与友好的工具链。
不过,对于简单直接的 MLOps 项目,工程师可能会选择 Podman,相较于 Docker Desktop,其更为轻量且易于设置。
在本教程中,我们通过与 Docker 的对比,介绍了流行的容器管理工具 Podman,并演示了如何安装 Podman Desktop。同时我们带您完成一个基于 Podman 的 MLOps 项目,涵盖创建 Dockerfile、构建镜像与运行容器。Podman 上手简单,如果您已熟悉 Docker,将会体会到其平滑的迁移体验。
参加MLOps 部署与全生命周期课程,深入了解现代 MLOps 框架,包括机器学习模型的生命周期与部署。