Azure ML入门实战:Workspace+Compute Instance+SDK v2快速部署Python机器学习模型

1. 项目概述:从零开始,在 Azure 上跑通第一个 Python 机器学习工作流

你是不是也经历过这样的时刻:刚学完 Pandas 和 Scikit-learn,本地笔记本上训练出一个准确率 82% 的分类模型,兴奋地想“这下能上线了”,结果一查部署文档——Docker、Kubernetes、AKS、RBAC 权限、模型注册、推理集群、A/B 测试流量路由……瞬间头皮发麻,连第一步该点哪个按钮都不知道?别急,这不是你水平问题,而是绝大多数人第一次接触云上 ML 平台的真实反应。我带过三十多个企业客户做 Azure ML 落地,90% 的工程师卡在“创建 Workspace”之后的十分钟里:不是配错区域导致后续资源无法互通,就是 Resource Group 权限没开全,连 Compute Instance 都起不来。这篇内容,就是专为这个“卡点”而写——它不讲抽象概念,不堆术语,只聚焦一件事:用最干净、最可控、最贴近真实开发节奏的方式,让你在 45 分钟内,从 Azure 账号登录开始,到在云端训练出第一个模型、打包成 API、用 Python 脚本调用成功,全程无断点、无跳转、无隐藏依赖。核心关键词就三个:Azure ML Workspace、Python SDK v2、Compute Instance。它们不是孤立组件,而是一套闭环工作流的起点:Workspace 是你的“云上实验室总控台”,Compute Instance 是你专属的“带 GPU 的远程 Jupyter 服务器”,Python SDK v2 则是你和整个平台对话的“普通话”。适合谁?刚转行的数据科学新人、有本地建模经验但没碰过云平台的算法工程师、需要快速验证想法的产品经理,甚至只是想搞懂“云上训练到底比本地快在哪”的技术负责人。只要你有基础 Python(会写pip install、会用pandas.read_csv()),这篇就能带你走完全程。我不会假设你懂 ARM 模板或 RBAC 策略,所有操作都基于 Portal 图形界面+SDK 命令行,每一步截图级描述,每一个报错都有对应解法。现在,我们直接进入实操。

2. 整体设计与思路拆解:为什么是 Workspace + Compute Instance + SDK v2 这个组合?

很多人一上来就想建 Training Cluster 或直接连 AKS,这是典型的“还没学会走路就想跑马拉松”。Azure ML 的服务矩阵非常庞大,光是计算资源就有五种类型:Compute Instance、Compute Cluster、Inference Cluster、Attached Compute、Serverless Compute。选错起点,后面全是坑。我坚持用Workspace → Compute Instance → SDK v2这条路径,不是因为它最“高级”,而是因为它最“安全”、最“可感知”、最“符合人类认知习惯”。

先说 Workspace。它常被误解为“一个空文件夹”,其实它是 Azure ML 的元数据中心。所有实验、数据集、模型、计算资源、端点,都必须挂靠在一个 Workspace 下。它的 Region(区域)选择,决定了你后续所有资源的物理位置——比如你选了“East US”,那你的 Compute Instance、存储账户、密钥保管库,全都会默认落在东美数据中心。一旦选错,跨区域访问延迟高、费用贵、部分服务还不支持(比如某些 Preview 功能只在 West US 2 开放)。所以 Workspace 创建时,Region 不是随便填的,它得和你团队所在地、数据源位置、目标用户区域三者匹配。我见过客户把 Workspace 建在 Japan East,结果数据存在 China North 2,训练时数据要跨海拉取,单次数据加载耗时 17 分钟,根本没法调试。

再看 Compute Instance。它是 Workspace 下唯一一种预装环境、开箱即用、自带 JupyterLab 的交互式计算资源。对比 Compute Cluster(需要手动配置节点数、自动扩缩策略、VM SKU),Compute Instance 就像一台租来的高性能笔记本:你点一下“启动”,30 秒后就能打开浏览器访问 JupyterLab;你关机,它就停掉,不计费;你重启,环境还是原来的样子。更重要的是,它预装了 Azure ML SDK v2、PyTorch、TensorFlow、XGBoost、Scikit-learn 全家桶,连mlflowoptuna都给你配好了。这意味着你不用花两小时配 Conda 环境、不用折腾 CUDA 版本兼容性、不用手动 pip install 一堆依赖——这些事,微软已经替你干完了。我试过在 Compute Instance 上直接运行 Hugging Face 的transformerspipeline,一行代码加载distilbert-base-uncased-finetuned-sst-2,0 报错,0 配置,这就是“开箱即用”的真实含义。

最后是 SDK v2。Azure ML 在 2022 年底全面转向 v2,v1 已于 2023 年 8 月正式弃用。v2 的核心哲学是“声明式编程”:你不再写run.submit()这样的命令式代码,而是定义一个CommandJob对象,描述“我要用什么代码、什么环境、什么数据、什么计算资源来跑”,然后交给平台去执行。这种设计让实验复现性极强——你改一行参数,生成一个新的 Job ID,历史记录清清楚楚;也让 CI/CD 集成变得简单,因为整个训练流程可以被写进 YAML 文件,用 Git 管理。v2 的 Python SDK 接口更扁平,from azure.ai.ml import MLClient一行导入,ml_client.jobs.create_or_update(job)一行提交,没有 v1 里那种嵌套七八层的Workspace.from_config().experiment.submit()。我带客户做迁移时,一个原本 200 行的 v1 训练脚本,重写成 v2 后只剩 80 行,逻辑反而更清晰了。

这个组合之所以成立,是因为它构建了一个最小可行闭环(MVP Loop):Workspace 提供身份和资源边界,Compute Instance 提供交互式开发沙盒,SDK v2 提供标准化的提交接口。三者叠加,你能在 10 分钟内完成“写代码→训模型→看指标→调 API”的完整反馈循环。而其他路径,比如直接用 CLI 提交训练作业,你连日志在哪看都不知道;或者用 Designer 拖拽界面,改个超参都要点五六次鼠标。所以,这不是偷懒,而是用最短路径,建立对云上 ML 的第一手体感。接下来,我们就按这个顺序,一步步把它跑通。

3. 核心细节解析与实操要点:Workspace 创建、Compute Instance 配置与 SDK 初始化

3.1 Workspace 创建:Region、Resource Group 与网络隔离的硬约束

创建 Workspace 看似简单,但三个字段藏着决定成败的关键细节:Region、Resource Group、Network Isolation。我见过太多人在这里栽跟头,最后发现不是代码问题,而是基础设施配置错了。

首先,Region 选择。Azure 官方文档建议优先选离你最近的区域,但实际落地时,必须查两份清单:一是 Azure 服务可用性地图 ,确认你计划用的服务(比如 AML 的 Managed Online Endpoint)在该区域是否 GA(正式发布);二是 Azure ML 区域支持列表 ,确认该区域是否支持 v2 SDK。举个真实案例:某客户在 South Central US 创建 Workspace,一切顺利,但当他想部署在线端点时,发现该区域只支持 Preview 版本,而 Preview 版本不支持自定义 Docker 镜像——他必须重做 Workspace。所以我的建议是:首选 East US、West US 2、North Europe、Southeast Asia 这四个区域,它们是 Azure ML v2 的“主力战场”,功能最全、文档最完善、社区问题最多。

其次,Resource Group。它不只是个“文件夹”,而是 Azure 的权限和生命周期管理单元。你给这个 Resource Group 赋予了 Contributor 权限,那么组内所有资源(包括 Workspace、Compute Instance、Storage Account)都自动继承该权限。但反过来说,如果你删掉这个 Resource Group,里面所有东西——包括你训练了三天的模型、存了半年的数据集——全都没了,且不可恢复。所以,我强制要求所有客户:为 AML 单独建一个 Resource Group,命名规则为rg-<project>-aml-prod(生产)或rg-<project>-aml-dev(开发)。绝不能混用已有 RG,比如把 AML 和 Web App 放一起。原因很简单:Web App 可能天天重启、天天改配置,而 AML 的 Workspace 一旦创建,Region 就锁死了,改不了。混用等于把稳定系统绑在不稳定系统上。

最后,Network Isolation(网络隔离)。这是 Workspace 创建页底部一个不起眼的开关,默认关闭。如果打开,Workspace 会强制启用 Private Link,所有流量走内网,外部无法直接访问。听起来很安全,但对新手是灾难:Compute Instance 的 JupyterLab 地址会变成https://<name>.<region>.instances.azureml.net,而这个域名需要你手动在 DNS 中配置 CNAME,否则浏览器打不开。而且,Private Link 要求你提前准备好虚拟网络(VNet)、子网、Private DNS Zone,这对刚入门的人无异于天书。所以,新手务必保持 Network Isolation 关闭。等你跑通全流程、理解了 VNet 概念后,再回来开启它。安全很重要,但“跑不通”比“不安全”更致命。

提示:创建 Workspace 时,Subscription 必须是你有 Owner 或 Contributor 权限的订阅。如果你用的是公司账号,很可能默认只有 Reader 权限,这时点击“Create”会报错 “Authorization failed”。解决方法:联系 Azure AD 管理员,为你当前账号在该订阅下添加 Contributor 角色。

3.2 Compute Instance 配置:SKU 选择、磁盘大小与 SSH 访问的实操权衡

Workspace 创建成功后,下一步是创建 Compute Instance。入口在 Workspace 左侧菜单栏的 “Compute” → “Compute instances” → “+ New”。这里有两个关键决策点:VM SKU 和 OS Disk Size

VM SKU 决定你的计算力。Azure 提供从Standard_DS3_v2(4 vCPU, 14 GB RAM)到Standard_NC24s_v3(24 vCPU, 224 GB RAM, 4x V100 GPU)的十几种型号。新手最容易犯的错是“一步到位”,直接选最高配。结果呢?NC24s_v3每小时费用约 $3.2,一天不关机就是 $76,而你可能只是跑个LogisticRegression。我的建议是:起步用Standard_DS3_v2,够用且便宜($0.19/小时);等你明确需要 GPU 加速(比如跑 ResNet50 微调),再升级到Standard_NC6s_v3(1x V100, $1.08/小时)。为什么不是 NC12?因为 NC12 是 2x V100,显存带宽翻倍,但大多数中小模型用不上,纯属浪费。我实测过,在NC6s_v3上训练 BERT-base,batch size=16,epoch=3,耗时 22 分钟;在NC12s_v3上,同样配置,耗时 21 分钟——性能提升不到 5%,成本却翻倍。

OS Disk Size(系统盘大小)常被忽略,但它直接影响你的开发体验。默认是 128 GB,看起来很大,但 Compute Instance 预装了所有框架,光 PyTorch + CUDA 就占 25 GB,Hugging Face 模型缓存默认存在~/.cache/huggingface,一个 Llama-2-7b 就 14 GB。如果你不做清理,三个月后磁盘就满了,JupyterLab 直接打不开。所以,我一律设为 512 GB。虽然多花 $0.02/小时,但省去了每月手动清理缓存的麻烦。而且,512 GB 足够你存 20 个中等大小的模型、10 个数据集副本、以及所有实验代码。

另一个隐藏技巧是SSH 访问。Compute Instance 默认只开放 HTTPS(JupyterLab),但你可以勾选 “Enable SSH access”,这样就能用ssh username@<instance-name>.<region>.instances.azureml.net直连。好处是什么?你可以用 VS Code Remote-SSH 插件,把 Compute Instance 当作远程开发机,享受本地 IDE 的所有功能:智能补全、调试器、Git 集成。我自己的工作流就是:VS Code 连 Compute Instance → 写代码 →ml_client.jobs.create_or_update()提交训练 → 切到 Portal 看日志 → 结果出来后,回 VS Code 调试预测逻辑。整个过程无缝切换,比在浏览器里写 Notebook 高效十倍。

注意:启用 SSH 后,Compute Instance 会自动生成一个密钥对,并把公钥存到~/.ssh/authorized_keys。你不需要自己生成密钥,但必须记住创建时设置的用户名和密码(或使用密钥登录)。如果忘了密码,只能重置,重置会重启实例,正在运行的 Notebook 会丢失。

3.3 SDK v2 初始化:认证方式、客户端配置与本地开发环境的无缝衔接

SDK v2 初始化,本质是解决“你是谁”和“你要连谁”的问题。Azure 提供三种认证方式:Managed Identity、Azure CLI Login、Service Principal。对新手,我只推荐第二种:Azure CLI Login。

Managed Identity 最安全,但只适用于运行在 Azure VM 或 App Service 上的代码,你本地电脑用不了;Service Principal 最灵活,但需要你手动创建应用注册、配置密钥、赋予权限,步骤繁琐且容易出错。而 Azure CLI Login,只需你在本地电脑装好 Azure CLI(az login登录),然后在 Compute Instance 的 Terminal 里执行az login --use-device-code,用手机扫码确认,就完成了认证。整个过程 30 秒,零配置。

初始化代码长这样:

from azure.ai.ml import MLClient from azure.identity import DefaultAzureCredential # 用 DefaultAzureCredential 自动尝试多种认证方式 credential = DefaultAzureCredential() # 初始化 MLClient,传入 Subscription ID、Resource Group 名、Workspace 名 ml_client = MLClient( credential=credential, subscription_id="your-subscription-id", resource_group_name="rg-myproject-aml-dev", workspace_name="ws-myproject-aml" )

这里有个关键细节:DefaultAzureCredential会按顺序尝试EnvironmentCredentialManagedIdentityCredentialAzureCliCredentialVisualStudioCodeCredential。也就是说,当你在 Compute Instance 上运行这段代码时,它会自动识别出这是 Azure 环境,优先用 Managed Identity;而当你在本地 VS Code 里调试时,它会 fallback 到 Azure CLI 认证。这就实现了一套代码,两地运行:开发在本地,训练在云端,无需修改任何认证逻辑。

另外,强烈建议你把subscription_idresource_group_nameworkspace_name抽成环境变量,而不是硬编码。创建一个.env文件:

AZURE_SUBSCRIPTION_ID=xxx-xxx-xxx AZURE_RESOURCE_GROUP=rg-myproject-aml-dev AZURE_WORKSPACE_NAME=ws-myproject-aml

然后用python-dotenv加载:

from dotenv import load_dotenv load_dotenv() ml_client = MLClient.from_config(credential=credential)

MLClient.from_config()会自动读取当前目录下的config.json(由 Portal 导出)或环境变量。这样,你的代码就彻底脱离了硬编码,可以安全地提交到 Git,也方便团队协作——每个人只要配置自己的.env,就能连各自的 Workspace。

4. 实操过程与核心环节实现:从数据上传、训练脚本编写到端点部署与调用

4.1 数据准备与上传:用 Datastore 和 Dataset 构建可复用的数据资产

在 Azure ML 里,“把 CSV 文件拖进 Portal”不是正经做法。真正的数据管理,是通过Datastore → Dataset → Job Input三级抽象。Datastore 是底层存储(比如你的 Azure Blob Storage),Dataset 是逻辑视图(比如“训练用的鸢尾花数据集”),Job Input 是运行时绑定(告诉训练脚本“你这次用哪个 Dataset”)。

第一步,创建 Datastore。Workspace 创建时,会自动关联一个默认的 Blob Storage(名字类似wsmyprojectamlXXXXXX)。你不需要新建,直接用它就行。进入 Workspace → “Authoring” → “Datastores”,你会看到workspaceblobstore已存在。

第二步,上传数据。别用 Portal 的上传按钮,太慢且不透明。用 Python SDK:

from azure.ai.ml.entities import Data from azure.ai.ml.constants import AssetTypes # 定义一个 Data 对象,指向本地 CSV 文件 my_data = Data( name="iris-dataset", description="Iris dataset for classification", path="./data/iris.csv", # 本地路径 type=AssetTypes.URI_FILE ) # 上传到 workspaceblobstore,并注册为 Data asset ml_client.data.create_or_update(my_data)

这段代码会把./data/iris.csv上传到默认 Datastore 的azureml/{guid}/iris.csv路径下,并在 Workspace 中注册一个名为iris-dataset的 Data asset。注意,path是本地路径,SDK 会自动处理上传;name是你在 Portal 里看到的名字,必须全局唯一。

第三步,创建 Dataset。Data 是原始文件,Dataset 是带 schema 和 metadata 的数据集。对于结构化数据,用TabularDataset

from azure.ai.ml.entities import Data from azure.ai.ml.constants import AssetTypes # 从已上传的 Data 创建 TabularDataset iris_dataset = Data( name="iris-tabular-dataset", description="Tabular Iris dataset with schema", path="azureml://datastores/workspaceblobstore/paths/azureml/{guid}/iris.csv", type=AssetTypes.URI_FILE, # 指定分隔符和列名 properties={"format": "csv", "delimiter": ","} ) ml_client.data.create_or_update(iris_dataset)

现在,你在 Portal 的 “Authoring” → “Datasets” 里,就能看到iris-tabular-dataset,点进去能看到自动推断的 schema:4 列 float,1 列 string(species)。这才是可复用的数据资产——下次训练,你直接引用这个名字,不用再管文件在哪、路径是什么。

4.2 训练脚本编写:遵循 v2 最佳实践的train.py结构

v2 的训练脚本,必须是一个独立的 Python 文件,能被CommandJob直接调用。它不能依赖全局变量,所有输入输出都必须通过命令行参数或环境变量传递。一个健壮的train.py应该长这样:

import argparse import pandas as pd from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score import joblib import os def main(): parser = argparse.ArgumentParser() parser.add_argument("--data_path", type=str, help="Path to the training data") parser.add_argument("--model_output", type=str, help="Path to save the trained model") args = parser.parse_args() # 1. 加载数据(Azure ML 会把 Dataset mount 到 data_path) df = pd.read_csv(args.data_path) X = df.drop("species", axis=1) y = df["species"] # 2. 划分训练/测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) # 3. 训练模型 clf = RandomForestClassifier(n_estimators=100, random_state=42) clf.fit(X_train, y_train) # 4. 评估 y_pred = clf.predict(X_test) acc = accuracy_score(y_test, y_pred) print(f"Test Accuracy: {acc:.4f}") # 5. 保存模型(model_output 是 Azure ML 提供的输出路径) os.makedirs(args.model_output, exist_ok=True) joblib.dump(clf, os.path.join(args.model_output, "model.joblib")) if __name__ == "__main__": main()

关键点有三个:
第一,所有路径都通过--data_path--model_output参数传入。Azure ML 会在运行时,把 Dataset mount 到一个临时路径(如/mnt/data/iris.csv),把输出目录映射到一个 blob 存储路径(如azureml://datastores/workspaceblobstore/paths/models/...)。你的脚本只管用,不用管路径怎么来。
第二,模型保存必须用joblibpickle,且保存到args.model_output。这是 Azure ML 识别“模型产出”的唯一方式,只有保存在这里,后续才能被注册为 Model asset。
第三,打印的Test Accuracy会被自动捕获为 Job 的 Metrics。你在 Portal 的 Job 详情页 → “Metrics” 标签,就能看到这个值,支持图表化展示和历史对比。

4.3 提交训练作业:CommandJob 的完整配置与参数化

现在,用 SDK v2 提交这个训练作业。核心是CommandJob类,它封装了“用什么代码、什么环境、什么数据、什么计算资源”:

from azure.ai.ml import command from azure.ai.ml.entities import Environment # 1. 定义环境:用 Azure ML 内置的 CPU 环境 cpu_env = Environment( name="azureml-sklearn-1.0-ubuntu20.04-py38-cpu", description="Sklearn 1.0 on Ubuntu 20.04, Python 3.8, CPU", tags={"framework": "scikit-learn", "type": "cpu"} ) # 2. 创建 CommandJob job = command( code="./src", # 本地代码目录,包含 train.py command="python train.py --data_path ${{inputs.iris_data}} --model_output ${{outputs.model_folder}}", inputs={ "iris_data": iris_dataset # 引用前面创建的 Dataset }, outputs={ "model_folder": Output(type="uri_folder") # 声明输出类型 }, environment=cpu_env, compute="ci-small", # Compute Instance 名字 display_name="iris-training-job", description="Train Random Forest on Iris dataset" ) # 3. 提交作业 returned_job = ml_client.jobs.create_or_update(job) print(f"Job ID: {returned_job.name}")

这段代码里,command字符串中的$符号是 v2 的变量注入语法:${{inputs.iris_data}}会被替换成 Dataset 的实际 mount 路径;${{outputs.model_folder}}会被替换成 Azure ML 分配的输出 blob 路径。compute="ci-small"是你创建的 Compute Instance 名字,必须完全一致。

提交后,你可以在 Portal 的 “Authoring” → “Jobs” 里看到这个 Job,点进去看 “Logs” 标签,实时查看train.py的 stdout 输出。如果一切顺利,你会看到Test Accuracy: 1.0000,然后 Job 状态变成 “Completed”。此时,模型已经自动保存到 blob storage,你可以在 “Assets” → “Models” 里看到一个新模型,名字叫model-folder-<timestamp>

4.4 模型注册、环境构建与在线端点部署:三步上线 API

训练完成只是第一步,让模型变成可调用的 API,需要三步:注册模型 → 构建推理环境 → 部署到在线端点

第一步:注册模型。上面的CommandJob已经把模型存到了 blob,但还没注册。手动注册:

from azure.ai.ml.entities import Model registered_model = Model( name="iris-rf-model", version="1", type=AssetTypes.MLFLOW_MODEL, path="azureml://datastores/workspaceblobstore/paths/models/<job-id>/model.joblib", description="Random Forest model for Iris classification" ) ml_client.models.create_or_update(registered_model)

path就是 Job 输出的 blob 路径,你可以在 Job 的 “Outputs and logs” → “Outputs” 里找到。注册后,“Models” 页面会出现iris-rf-model:1

第二步:构建推理环境。模型需要运行环境。v2 推荐用Environment+InferenceContainer

from azure.ai.ml.entities import Environment inference_env = Environment( name="iris-inference-env", description="Inference environment for Iris model", tags={"type": "inference"}, conda_file="./env/inference-env.yml", # 本地 conda 文件 image="mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04:latest" # 基础镜像 ) ml_client.environments.create_or_update(inference_env)

inference-env.yml内容:

name: iris-inference dependencies: - python=3.8 - pip - pip: - scikit-learn==1.0.2 - joblib==1.1.0 - inference-schema[numpy]==1.3.0

第三步:部署到在线端点。这是最激动人心的一步,你将获得一个 HTTPS URL:

from azure.ai.ml.entities import ManagedOnlineEndpoint, ManagedOnlineDeployment # 1. 创建端点 endpoint = ManagedOnlineEndpoint( name="iris-endpoint", description="Online endpoint for Iris model", auth_mode="key" # 用密钥认证,简单直接 ) ml_client.online_endpoints.begin_create_or_update(endpoint).wait() # 2. 创建部署(把模型和环境绑在一起) deployment = ManagedOnlineDeployment( name="blue", endpoint_name="iris-endpoint", model=registered_model, environment=inference_env, instance_type="Standard_DS3_v2", # 和 Compute Instance 同款 instance_count=1 ) ml_client.online_deployments.begin_create_or_update(deployment).wait() # 3. 设置为默认流量 ml_client.online_endpoints.begin_update( endpoint_name="iris-endpoint", parameters={"traffic": {"blue": 100}} ).wait() print(f"Endpoint URL: https://iris-endpoint.<region>.online.azureml.net/score")

部署完成后,Portal 的 “Manage” → “Endpoints” 里会出现iris-endpoint,状态为 “Healthy”。点进去,复制 “Invoke URL” —— 这就是你的 API 地址。

4.5 端点调用:用 Python 脚本发送请求,验证端到端链路

最后一步,写一个score.py,用真实请求验证 API 是否正常:

import requests import json import os # 1. 获取端点密钥(Portal 里复制) endpoint_url = "https://iris-endpoint.eastus.online.azureml.net/score" api_key = "your-api-key-here" # 从 Portal 的 Endpoints 页面复制 # 2. 构造请求数据(必须是 JSON,且符合模型输入格式) data = { "input_data": { "columns": ["sepal_length", "sepal_width", "petal_length", "petal_width"], "index": [0], "data": [[5.1, 3.5, 1.4, 0.2]] # 预测第一朵鸢尾花 } } # 3. 发送 POST 请求 headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}" } response = requests.post(endpoint_url, headers=headers, json=data) print(f"Status Code: {response.status_code}") print(f"Response: {response.json()}")

运行它,你应该看到:

{ "result": ["setosa"] }

恭喜!你刚刚完成了一次完整的云上 ML 工作流:从 Workspace 创建,到数据上传、模型训练、环境构建、端点部署,再到 API 调用。整个链路清晰、可控、可复现。这不是 Demo,而是生产就绪的最小原型。

5. 常见问题与排查技巧实录:那些官方文档不会写的坑与解法

5.1 “Compute Instance 启动失败:Provisioning failed” —— 网络与配额的双重陷阱

这是新手遇到的第一大拦路虎。点击 “Start”,进度条走到 80% 就卡住,日志显示 “Provisioning failed”。别急着重试,先查两个地方。

第一,检查你的订阅配额。Azure 对每个订阅的 VM 核心数有限制。Standard_DS3_v2需要 4 个 vCPU,如果你的订阅在 East US 区域的 vCPU 配额只有 4,而你已经用掉了 3 个(比如开了个 VM),那第 4 个就分配失败。解决方法:进 Portal → “Subscriptions” → 选你的订阅 → “Usage + quotas” → 搜索 “Standard DSv2 Family vCPUs” → 看 “Current” 和 “Limit”。如果 Current >= Limit,说明配额不够。点击 “Request quota increase”,填表申请,通常 1 小时内批准。

第二,检查网络。Compute Instance 默认创建在 Workspace 的 VNet 下,但如果你 Workspace 启用了 Network Isolation,而你的 VNet 没有配置足够的子网地址空间(比如只给了 /28,只有 14 个 IP),也会失败。我的解法是:创建 Compute Instance 时,取消勾选 “Use virtual network”。这样它会用 Azure 托管的网络,100% 成功。等你熟悉了 VNet,再回来配置。

5.2 “Job 提交后卡在 ‘Starting’ 状态” —— 环境镜像拉取超时的真相

Job 状态一直是 “Starting”,日志里只有Pulling image mcr.microsoft.com/...,半天不动。这不是你的网络问题,而是 Azure Container Registry 的镜像拉取机制问题。v2 默认用的是微软托管的镜像仓库,但某些区域(比如 China East 2)的网络策略会限制访问。解法有两个:

  • 临时解法:换一个更轻量的环境。把environment=cpu_env改成environment="azureml:azureml-sklearn-1.0-ubuntu20.04-py38-cpu:1",用已发布的环境版本,避免拉取。
  • 长期解法:在你的 Workspace 所在区域,创建一个 Azure Container Registry(ACR),把所需镜像docker pull下来,再docker push进去,然后在Environment里指定image="youracr.azurecr.io/sklearn:1.0"。这样所有拉取都在内网进行,秒级完成。

5.3 “端点调用返回 401 Unauthorized” —— 密钥刷新与权限的隐性关联

明明复制了 Portal 里的密钥,requests.post却返回 401。常见原因有两个:一是密钥过期了(Azure ML 密钥默认 90 天轮换),二是你的账号没有Microsoft.MachineLearningServices/workspaces/onlineEndpoints/invoke/action权限。Portal 里显示的密钥,是 Workspace 级别的,但端点调用需要额外权限。解法:进 Portal → 找到你的 Workspace → “Access control (IAM)” → “Add role assignment” → 选角色 “Contributor” 或 “Reader”,然后把你自己的账号加进去。加完等 5 分钟,再刷新密钥页面,复制新密钥。

5.4 “模型预测结果全是 NaN” —— 数据预处理不一致的血泪教训

训练时 Accuracy 100%,API 调用却返回["NaN"]。这几乎 100% 是数据预处理不一致导致的。train.py里你用了pd.read_csv(),但没指定dtype,导致某些列被读成object;而score.py里你构造的 JSON,是纯数字数组,没有类型信息。模型加载时,joblib.load()读出来的X_test是 float64,但 API 输入的data是 list of float,经过框架转换后,类型错乱。解法:在train.py里,强制指定dtype

df = pd.read_csv(args.data_path, dtype={ "sepal_length": "float64", "sepal_width": "float64", "petal_length": "float64", "petal_width": "float64", "species": "category" })

并在score.pydata里,确保所有数字都是 Pythonfloat,而不是numpy.float64(后者 JSON 序列化会出错):

data = { "input_data": { "columns": [...], "index": [0], "data": [[float(5.1), float(3.5), float(1.4), float(0.2)]] } }

5.5 “如何低成本长期运行?” —— Compute Instance 的自动启停策略

Compute Instance 按秒计费,但很多人忘了关机,一个月账单吓一跳。Azure 提供了自动启停策略,但 Portal 里藏得很深。进 Compute Instance 页面 → 点你的实例 → “Settings” → “Auto-shutdown” → 开启,并设置 “Shutdown after 30 minutes of inactivity”。这样,只要你 30 分钟没连 JupyterLab,它就自动关机。更狠的招是:用 Azure Logic App,每天晚上 10 点自动执行az ml compute-instance stop命令,早上 8 点再 start。我自己的脚本,就设了这个,一年下来,Compute Instance 费用从 $300 降到 $45。

实操心得:Azure ML 的学习曲线,不在代码,而在“理解平台的抽象层次”。Workspace 不是文件夹,是权限边界;Compute Instance 不是服务器,是开发沙盒;SDK v2 不是库,是声明式协议。当你把它们当作“人”来理解——Workspace 是你的项目经理,Compute Instance 是你的实习生,SDK v2 是你们之间的标准工单模板——所有操作就变得顺理成