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 环境构建与训练/推理一致性链路 环境定义(YAML) Conda:python=3.10 / numpy=1.24 pip:mlflow==2.9.2 / 私有wheel Docker上下文:wheels/、requirements.txt 镜像构建与注册 基础镜像 + Dockerfile 层 解析依赖 → 构建 → 推送ACR 注册为 churn-env:1 / :2 作业引用 训练作业:churn-env:1 推理部署:churn-env:1 禁止:推理用 latest 漂移 训练/推理一致性检查点 ① Python与关键包版本一致 pandas / numpy / scikit-learn 固定版本 ② 特征工程代码同源 同一wheel同时进入训练与推理环境 ③ 系统库与CUDA匹配 基础镜像CUDA版本与推理硬件一致 ④ 环境版本写入模型元数据 模型注册时记录 churn-env:1

实操:注册环境并提交训练作业

在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上下文携带私有文件,用自定义基础镜像解决系统级依赖,用版本号锁定训练与推理的一致性。把环境当作模型资产的一部分来管理,而不是一次性配置,才能真正实现可复现的模型工程化部署。