4. Azure机器学习环境与依赖管理
在Azure机器学习中,“环境”不是一台机器,而是一份可复现的运行时契约:它规定了训练或推理作业启动时使用什么基础镜像、安装哪些Python包、以什么顺序解析依赖、由谁负责构建镜像,以及这份定义如何被版本化并绑定到具体作业。环境管理做得好,训练与推理才能共享同一套依赖语义;做得不好,最常见的现象就是“本地能跑、提交到计算集群报ModuleNotFoundError”或“训练精度正常、部署后预处理结果偏移”。
环境的三层构成:Conda、pip与Docker上下文
Azure ML环境定义通常由三部分组成。第一层是Conda依赖,用于声明Python版本、conda渠道包以及pip安装项;第二层是pip依赖,适合安装PyPI上的包或私有源包;第三层是Docker上下文,即构建镜像时随环境一起上传的本地文件,例如requirements.txt、私有wheel、配置文件或初始化脚本。三者关系可以理解为:Conda先建立解释器与基础科学计算栈,pip补齐Conda渠道没有的包,Docker上下文则把“无法从公共源直接获取”的内容带进构建过程。
一个典型的环境YAML如下,它同时声明了Conda与pip依赖:
name: churn-train-env
channels:
- conda-forge
dependencies:
- python=3.10
- numpy=1.24
- pandas=2.0
- scikit-learn=1.3
- pip
- pip:
- azureml-mlflow==1.53.0
- mlflow==2.9.2
- ./wheels/feature_engine-0.1.0-py3-none-any.whl
这里有两个关键点。其一,python=3.10与numpy=1.24使用Conda渠道,能获得预编译的二进制包,减少编译失败;其二,私有wheel通过相对路径./wheels/...引用,因此必须把wheels目录放入Docker上下文,否则构建时找不到文件。Docker上下文不是“可选附件”,而是依赖解析的一部分。
自定义基础镜像:何时需要,如何写
当默认基础镜像无法满足系统级依赖时,就需要自定义基础镜像。例如训练需要CUDA 11.8、cuDNN 8,或需要libgomp、libssl等系统库,仅靠pip无法解决。此时可基于Azure ML提供的mcr.microsoft.com/azureml/openmpi4.1.0-cuda11.8-cudnn8-ubuntu22.04编写Dockerfile:
FROM mcr.microsoft.com/azureml/openmpi4.1.0-cuda11.8-cudnn8-ubuntu22.04
RUN apt-get update && apt-get install -y libgomp1 libssl-dev && rm -rf /var/lib/apt/lists/*
COPY requirements.txt /tmp/requirements.txt
RUN pip install --no-cache-dir -r /tmp/requirements.txt
自定义基础镜像的收益是系统级依赖可控,代价是构建时间变长、镜像层变大。实践中应把“变化频率低”的系统库放在Dockerfile前部,“变化频率高”的Python包放在后部,以利用层缓存。若只是增加几个pip包,优先使用Conda+pip环境定义,而不是重建基础镜像。
环境版本化与训练/推理一致性
环境每次注册都会生成新版本,例如churn-env:1、churn-env:2。版本化不是形式,而是可追溯性的基础:训练作业应记录所用环境名称与版本,模型注册时也应把环境作为元数据一并登记。推理部署时,若直接使用“最新版”环境,可能因依赖漂移导致预处理逻辑变化。更稳妥的做法是:训练与推理引用同一个环境版本,或在推理环境中显式固定与训练一致的包版本。
一致性保障的核心是“锁定关键依赖”。例如训练时使用pandas=2.0,推理环境就不能写成pandas>=1.5,否则可能因默认行为变化导致特征列顺序或缺失值处理不一致。对于自定义特征工程代码,应将其打包为wheel并同时放入训练与推理环境,避免“训练用A版本、推理用B版本”。
实操:注册环境并提交训练作业
在Azure ML SDK v2中,环境注册与作业提交可以写成如下形式。假设当前目录包含env.yml、wheels/和train.py:
from azure.ai.ml import MLClient, command, Input
from azure.ai.ml.entities import Environment
from azure.identity import DefaultAzureCredential
ml_client = MLClient.from_config(DefaultAzureCredential())
env = Environment(
name="churn-env",
version="1",
conda_file="./env.yml",
image="mcr.microsoft.com/azureml/openmpi4.1.0-cuda11.8-cudnn8-ubuntu22.04",
description="Churn training env with pinned pandas/numpy"
)
ml_client.environments.create_or_update(env)
job = command(
code="./src",
command="python train.py --data ${{inputs.data}}",
inputs={"data": Input(type="uri_folder", path="./data")},
environment="azureml:churn-env:1",
compute="gpu-cluster"
)
ml_client.jobs.create_or_update(job)
提交后,可在作业详情页的“环境”标签查看实际解析出的包版本。若构建失败,常见原因是Docker上下文缺少文件、pip源不可达或Conda与pip包冲突。此时应优先查看构建日志中“Collecting”与“Solving environment”两段,判断是网络问题还是版本约束冲突。
环境版本化与回滚策略
环境版本一旦注册就不应修改,而应新建版本。例如发现churn-env:1中mlflow版本与推理服务不兼容,应创建churn-env:2并更新推理部署,而不是覆盖:1。这样历史训练作业仍可复现,模型注册表中的环境引用也不会失效。回滚时只需把推理部署重新指向旧版本环境,并确认模型文件与旧环境兼容。
churn-train-cuda118-py310,避免仅用env1、env2导致后期无法判断差异。latest标签部署推理环境。即使训练时使用了固定版本,推理端若引用latest,后续环境更新会静默改变运行时,导致线上预测结果漂移。总结来看,Azure ML环境管理的核心动作是:用Conda+pip声明依赖,用Docker上下文携带私有文件,用自定义基础镜像解决系统级依赖,用版本号锁定训练与推理的一致性。把环境当作模型资产的一部分来管理,而不是一次性配置,才能真正实现可复现的模型工程化部署。