Training a ML.NET Model with Azure ML

Model Builder is a great starting point when you are training your first ML.NET model on your own machine. The problem shows up later, once your dataset keeps growing and you want to retrain regularly without babysitting the process by hand every time. At that point you want the training itself to run somewhere else, on a schedule or a trigger, not on your laptop.

This walkthrough sets up exactly that: an Azure Machine Learning pipeline that trains an ML.NET model using the ML.NET CLI inside a Docker container, runnable manually through the Azure CLI or automatically from an Azure DevOps pipeline.

Getting the Dataset into Azure ML

Before any training pipeline can run, Azure ML needs to know about your data. This is done through an Azure Machine Learning Dataset, which tracks a specific file or set of files, including versioning if you upload updated data later. In the Azure Machine Learning Studio, under Datasets, create a new dataset and choose File as the type.

Basic info step when creating a new file dataset in Azure Machine Learning Studio.
Basic info step when creating a new file dataset in Azure Machine Learning Studio.

The upload step matters more than it looks. Upload into the default workspaceblobstore and note the exact file name, since the training pipeline will reference this path later, and a mismatch here is one of the more common reasons a first run fails with a file-not-found error rather than a training error.

Choosing the datastore and uploading the data file for the new dataset.
Choosing the datastore and uploading the data file for the new dataset.

Once the upload finishes and the dataset is created, open it and check the Explore tab to confirm the preview actually looks like your data, not a truncated or malformed upload. Also note down the dataset name and version number, both show up on the dataset’s own page, since the training YAML needs both values exactly as they appear here.

Working Out the ML.NET CLI Command

Before wiring anything into Azure, work out the actual ML.NET CLI command you would run locally for this training scenario. For a regression problem, that means specifying the dataset and the label column at minimum.

ML.NET CLI command help for the regression training scenario.
ML.NET CLI command help for the regression training scenario.
mlnet regression --dataset <YOUR_DATA_FILE_NAME> --label-col <YOUR_LABEL> --output outputs --log-file-path outputs/logs --verbosity q

Text classification and recommendation scenarios are supported the same way for tabular data, so this same pattern extends beyond regression if that is what your model needs. The –verbosity q flag is worth keeping even outside Azure, since some of the CLI’s richer console output does not play well once it is running inside a Linux container rather than an interactive terminal.

Building the Training Container

The actual training runs inside a container that has the ML.NET CLI installed on top of the .NET SDK image. Create a file literally named Dockerfile, with no extension, in a new folder for this experiment.

FROM mcr.microsoft.com/dotnet/sdk:6.0
RUN dotnet tool install -g microsoft.mlnet-linux-x64
ENV PATH="$PATH:/root/.dotnet/tools"

This keeps the training environment reproducible and independent of whatever happens to be installed on the machine that eventually kicks off the job, whether that is your laptop or a build agent.

Defining the Azure ML Training Job

Next to the Dockerfile, create an AzureTrain.yml file describing the job for Azure ML’s command job schema. The dataset reference uses the name and version noted earlier, and the command uses {inputs.data} as a placeholder for wherever Azure ML actually downloads the file on the compute node, since that path is not something you control or need to hardcode.

inputs:
  data:
    dataset: azureml:<DATASET_NAME>:<VERSION>
    mode: download
experiment_name: mldotnet-training
code:
  local_path: .
command: mlnet regression --dataset {inputs.data}/<YOUR_DATA_FILE_NAME> --label-col <YOUR_LABEL_COLUMN> --output outputs --log-file-path outputs/logs --verbosity q
compute: azureml:<YOUR-COMPUTE-NAME>
environment:
  build:
    local_path: .
    dockerfile_path: Dockerfile

The compute value needs to match an existing compute cluster in your workspace, visible under Computes, Compute clusters. If you skip creating a compute cluster first and just point this at a name that does not exist, the job submission itself will fail before training even starts, so it is worth confirming the cluster exists and is running before debugging anything else.

Running the Job Manually First

Before wiring this into a pipeline, run it once by hand using the Azure CLI with the ML extension installed. This step alone catches most configuration mistakes, wrong dataset version, wrong compute name, missing permissions, before you add the extra complexity of a CI/CD trigger on top.

az configure --defaults group=<YOUR_RESOURCE_GROUP> workspace=<YOUR_WORKSPACE>
az ml job create --file AzureTrain.yml

Once submitted, check progress under Experiments, mldotnet-training in Azure Machine Learning Studio. A completed run produces the trained model and example inference code under Outputs and Logs, in the outputs folder, exactly where the CLI command told it to write.

Automating Retraining with Azure DevOps

Once the manual run works, wiring this into Azure DevOps for scheduled or trigger-based retraining is mostly a matter of repeating the same CLI calls inside pipeline tasks, using a service connection instead of your own personal login.

Create the service connection first, in Azure DevOps under Project Settings, Pipelines, Service connections, as an Azure Resource Manager connection scoped to the Machine Learning Workspace resource group. Then reference that connection name in the pipeline.

variables:
  ml-ws-connection: 'aml-ws'
  ml-ws: '<YOUR_VALUE>'
  ml-rg: '<YOUR_VALUE>'
 
trigger:
  <YOUR_TRIGGER>
 
pool:
  vmImage: ubuntu-latest
 
steps:
- task: AzureCLI@2
  displayName: 'Install AML CLI (azureml-v2-preview)'
  inputs:
    azureSubscription: $(ml-ws-connection)
    scriptType: 'bash'
    scriptLocation: inlineScript
    inlineScript: 'az extension add -n ml'
 
- task: AzureCLI@2
  displayName: 'Setup default config values'
  inputs:
    azureSubscription: $(ml-ws-connection)
    scriptType: 'bash'
    scriptLocation: inlineScript
    inlineScript: 'az configure --defaults group=$(ml-rg) workspace=$(ml-ws)'
 
- task: AzureCLI@2
  displayName: 'Create training job'
  inputs:
    azureSubscription: $(ml-ws-connection)
    scriptType: 'bash'
    scriptLocation: inlineScript
    inlineScript: 'az ml job create --file <YOUR_PATH>/AzureTrain.yml'

The trigger section is where this becomes genuinely useful, since it accepts the usual Azure DevOps trigger types, a schedule, or a trigger tied to changes in the data or code path. Runs kicked off by the pipeline’s service principal show up under the same Experiments view, just toggle off ‘View only my runs’ to see them alongside runs you started manually.

Taking This Further

What is described here gets you a repeatable training job, but it stops short of an actual MLOps setup. The natural next step is registering each successful run’s output as a versioned model in the Azure ML model registry, rather than leaving trained models sitting in the outputs folder of individual runs. That registry step is what lets a separate deployment pipeline pick up ‘the latest approved model’ cleanly instead of reaching into a specific run’s output folder by hand.

It is also worth deciding upfront whether every pipeline run should auto-register a new model version, or whether registration should be a manual approval step after reviewing training metrics. For anything customer-facing, I would lean towards an approval gate rather than fully automatic registration, since a retrain job succeeding is not the same as the resulting model being an improvement over what is already deployed.

Leave a Reply

Discover more from Behind the Stack

Subscribe now to keep reading and get access to the full archive.

Continue reading