如何使用 ML.NET 自动化机器 (AutoML) 学习 API

本文介绍如何使用 ML.NET 自动化 ML (AutoML API)。

可以在 dotnet/machinelearning-samples 存储库中找到 AutoML API 的示例。

安装

若要使用 AutoML API,请在要引用它的 .NET 项目中安装 Microsoft.ML.AutoML NuGet 包。

注意

本指南使用 0.20.0 及更高版本的 Microsoft.ML.AutoML NuGet 包。 尽管早期版本中的示例和代码仍然有效,但我们强烈建议在新项目中使用此版本引入的 API。

有关安装 NuGet 包的详细信息,请参阅以下指南:

快速启动

AutoML 提供了几个默认值,用于快速训练机器学习模型。 在本部分,你将了解如何:

  • 加载数据
  • 定义管道
  • 配置试验
  • 运行试验
  • 使用最佳模型进行预测

定义问题

假定一个存储在名为 taxi-fare-train.csv 的逗号分隔文件中的数据集,如下所示:

vendor_id rate_code passenger_count trip_time_in_secs trip_distance payment_type fare_amount
CMT 1 1 1271 3.8 CRD 17.5
CMT 1 1 474 1.5 CRD 8
CMT 1 1 637 1.4 CRD 8.5

加载数据

首先初始化 MLContextMLContext 是所有 ML.NET 操作的起点。 初始化 mlContext 创建了新的 ML.NET 环境,可以在模型创建工作流对象之间共享。 从概念上讲,它与实体框架中的 DBContext 类似。

然后,若要加载数据,请使用 InferColumns 方法。

// Initialize MLContext
MLContext ctx = new MLContext();

// Define data path
var dataPath = Path.GetFullPath(@"..\..\..\..\Data\taxi-fare-train.csv");

// Infer column information
ColumnInferenceResults columnInference =
    ctx.Auto().InferColumns(dataPath, labelColumnName: "fare_amount", groupColumns: false);

InferColumns 从数据集加载几行。 然后,它会检查数据,并尝试根据每列的内容猜测或推断其数据类型。

默认行为是将相同类型的列分组为特征向量或包含每个单独列的元素的数组。 将 groupColumns 设置为 false 会取代此默认行为,并且仅执行列推理而不对列进行分组。 通过分隔列,可以在单个列级别(而不是列分组)预处理数据时应用不同的数据转换。

InferColumns 的结果是一个 ColumnInferenceResults 对象,其中包含创建 TextLoader 所需的选项以及列信息。

对于 taxi-fare-train.csv 中的示例数据集,列信息可能如下所示:

  • LabelColumnName:fare_amount
  • CategoricalColumnNames:vendor_id、payment_type
  • NumericColumnNames:rate_code、passenger_count、trip_time_in_secs、trip_distance

获得列信息后,请使用 ColumnInferenceResults 定义的 TextLoader.Options 创建 TextLoader,以将数据加载到 IDataView 中。

// Create text loader
TextLoader loader = ctx.Data.CreateTextLoader(columnInference.TextLoaderOptions);

// Load data into IDataView
IDataView data = loader.Load(dataPath);

通常最佳做法是将数据集拆分为训练集和验证集。 使用 TrainTestSplit 将数据集拆分为 80% 训练集和 20% 验证集。

TrainTestData trainValidationData = ctx.Data.TrainTestSplit(data, testFraction: 0.2);

定义管道

管道定义用于训练模型的数据处理步骤和机器学习管道。

SweepablePipeline pipeline =
    ctx.Auto().Featurizer(data, columnInformation: columnInference.ColumnInformation)
        .Append(ctx.Auto().Regression(labelColumnName: columnInference.ColumnInformation.LabelColumnName));

SweepablePipelineSweepableEstimator 的集合。 SweepableEstimator 是具有 SearchSpace 的 ML.NET Estimator

Featurizer 是一种便捷 API,可基于提供的列信息生成数据处理可扫描估算器的可扫描管道。 Featurizer 不是从头开始生成管道,而是自动执行数据预处理步骤。 有关 ML.NET 支持的转换的详细信息,请参阅数据转换指南

Featurizer 输出是单个列,其中包含一个数字特征向量,表示每个列的转换数据。 然后,此特征向量将用作用于训练机器学习模型的算法的输入。

如果需要对数据预处理进行精细控制,可以使用每个单独的预处理步骤创建管道。 有关详细信息,请参阅准备建模的数据指南。

提示

FeaturizerColumnInferenceResults 配合使用,可以最大程度地提高 AutoML 的效用。

AutoML 提供了一个可扫描管道用于训练,其中包含用于以下机器学习任务的默认训练程序和搜索空间配置:

对于出租车费预测问题,由于目标是预测数值,因此请使用 Regression。 有关选择任务的详细信息,请参阅 ML.NET 中的机器学习任务

配置试验

首先,创建一个 AutoML 试验。 AutoMLExperimentTrialResult 的集合。

AutoMLExperiment experiment = ctx.Auto().CreateExperiment();

创建试验后,请使用它提供的扩展方法来配置不同的设置。

experiment
    .SetPipeline(pipeline)
    .SetRegressionMetric(RegressionMetric.RSquared, labelColumn: columnInference.ColumnInformation.LabelColumnName)
    .SetTrainingTimeInSeconds(60)
    .SetDataset(trainValidationData);

在本示例中,你可以:

  • 通过调用 SetPipeline 将可扫描管道设置为在试验期间运行。
  • 通过调用 SetRegressionMetric 来选择 RSquared 作为训练期间要优化的指标。 有关评估指标详细信息,请参阅使用指标评估 ML.NET 模型指南。
  • 通过调用 SetTrainingTimeInSeconds 将训练的时间设置为 60 秒。 确定训练时长的一个很好的启发式方法是数据的大小。 通常,较大的数据集需要更长的训练时间。 有关详细信息,请参阅训练时间指南。
  • 通过调用 SetDataset 提供要使用的训练和验证数据集。

定义试验后,需要某种方法来跟踪其进度。 跟踪进度的最快方法是修改 MLContext 中的 Log 事件。

// Log experiment trials
ctx.Log += (_, e) => {
    if (e.Source.Equals("AutoMLExperiment"))
    {
        Console.WriteLine(e.RawMessage);
    }
};

运行试验

定义试验后,请使用 RunAsync 方法启动试验。

TrialResult experimentResults = await experiment.RunAsync();

训练时间到期后,结果为 TrialResult,表示在训练期间找到的最佳模型。

此时,可以保存模型或使用它进行预测。 有关如何使用 ML.NET 模型的详细信息,请参阅以下指南:

修改列推理结果

由于 InferColumns 仅加载数据的子集,因此可能未捕获用于推断列的示例外部包含的边缘情况,并且为列设置了错误的数据类型。 可以更新 ColumnInformation 的属性,以考虑到列推理结果不正确的情况。

例如,在出租车费数据集中,rate_code 列中的数据是一个数字。 但是,该数值表示一个类别。 默认情况下,调用 InferColumns 会将 rate_code 放置在 NumericColumnNames 属性中,而不是 CategoricalColumnNames 中。 由于这些属性是 .NET 集合,因此可以使用标准操作在集合中添加和删除项。

可以执行以下操作来更新 rate_codeColumnInformation

columnInference.ColumnInformation.NumericColumnNames.Remove("rate_code");
columnInference.ColumnInformation.CategoricalColumnNames.Add("rate_code");

排除训练程序

默认情况下,AutoML 会在训练过程中尝试多个训练程序,以查看哪一个最适合你的数据。 但是,在整个训练过程中,你可能会发现某些训练程序会占用过多的计算资源,或者没有提供良好的评估指标。 可以选择从训练过程中排除训练程序。 使用哪些训练程序取决于任务。 有关 ML.NET 中受支持的训练程序的列表,请参阅 ML.NET 中的机器学习任务指南。

例如,在出租车费回归场景中,若要排除 LightGBM 算法,请将 useLgbm 参数设置为 false

ctx.Auto().Regression(labelColumnName: columnInference.ColumnInformation.LabelColumnName, useLgbm:false)

在其他任务(如二分类和多类分类)中排除训练程序的过程采用相同的方式。

自定义可扫描估算器

如果要对可扫描管道中包含的估算器选项进行更精细的自定义,需要:

  1. 初始化搜索空间
  2. 使用搜索空间定义自定义工厂
  3. 创建可扫描估算器
  4. 将可扫描估算器添加到可扫描管道

AutoML 为以下机器学习任务中的训练程序提供了一组预配置的搜索空间:

在本例中,使用的搜索空间用于 SdcaRegressionTrainer。 使用 SdcaOption 初始化搜索空间。

var sdcaSearchSpace = new SearchSpace<SdcaOption>();

然后,使用搜索空间定义自定义工厂方法以创建 SdcaRegressionTrainer。 在本例中,L1RegularizationL2Regularization 的值都设置为默认值以外的值。 对于 L1Regularization,设置的值由调谐器在每次试用期间确定。 每次试用将 L2Regularization 固定为硬编码值。 在每次试用期间,自定义工厂的输出是具有已配置超参数的 SdcaRegressionTrainer

// Use the search space to define a custom factory to create an SdcaRegressionTrainer
var sdcaFactory = (MLContext ctx, SdcaOption param) =>
{
    var sdcaOption = new SdcaRegressionTrainer.Options();
    sdcaOption.L1Regularization = param.L1Regularization;
    sdcaOption.L2Regularization = 0.02f;

    sdcaOption.LabelColumnName = columnInference.ColumnInformation.LabelColumnName;

    return ctx.Regression.Trainers.Sdca(sdcaOption);
};

可扫描估算器是估算器与搜索空间的组合。 定义搜索空间并使用它创建自定义工厂方法以生成训练程序后,请使用 CreateSweepableEstimator 方法创建新的可扫描估算器。

// Define Sdca sweepable estimator (SdcaRegressionTrainer + SdcaOption search space)
var sdcaSweepableEstimator = ctx.Auto().CreateSweepableEstimator(sdcaFactory, sdcaSearchSpace);

若要在试验中使用可扫描估算器,请将其添加到可扫描管道。

SweepablePipeline pipeline =
    ctx.Auto().Featurizer(data, columnInformation: columnInference.ColumnInformation)
        .Append(sdcaSweepableEstimator);

由于可扫描管道是可扫描估算器的集合,因此可以根据需要配置和自定义任意数量的可扫描估算器。

自定义搜索空间

在某些情况下,除了自定义试验中使用的可扫描估算器之外,还需要控制搜索空间范围。 若要执行此操作,可以使用键访问搜索空间属性。 在本例中,L1Regularization 参数为 float。 因此,若要自定义搜索范围,请使用 UniformSingleOption

sdcaSearchSpace["L1Regularization"] = new UniformSingleOption(min: 0.01f, max: 2.0f, logBase: false, defaultValue: 0.01f);

根据要设置的超参数的数据类型,可以从以下选项中进行选择:

搜索空间也可以包含嵌套搜索空间。

var searchSpace = new SearchSpace();
searchSpace["SingleOption"] = new UniformSingleOption(min:-10f, max:10f, defaultValue=0f) 
var nestedSearchSpace = new SearchSpace();
nestedSearchSpace["IntOption"] = new UniformIntOption(min:-10, max:10, defaultValue=0);
searchSpace["Nest"] = nestedSearchSpace;

自定义搜索范围的另一个选项是扩展搜索范围。 例如,SdcaOption 仅提供 L1RegularizationL2Regularization 参数。 但是,SdcaRegressionTrainer 具有更多可以设置的参数,例如 BiasLearningRate

若要扩展搜索空间,请创建继承自 SdcaOption 的新类,例如 SdcaExtendedOption

public class SdcaExtendedOption : SdcaOption
{
    [Range(0.10f, 1f, 0.01f)]
    public float BiasLearningRate {get;set;}   
}

若要指定搜索空间范围,请使用等效于 Microsoft.ML.SearchSpace.OptionRangeAttribute

然后,无论在哪个位置使用搜索空间,都引用 SdcaExtendedOption 而不是 SdcaOption

例如,初始化搜索空间时,可以执行以下操作:

var sdcaSearchSpace = new SearchSpace<SdcaExtendedOption>();

创建自己的试用运行程序

默认情况下,AutoML 支持二分类、多类分类和回归。 但是,ML.NET 支持更多情况,例如:

  • 建议
  • 预测
  • 排名
  • 图像分类
  • 文本分类
  • 句子相似性

对于没有预配置的搜索空间和可扫描估算器的情况,你可以自行创建,并使用试用运行程序为该情况启用 AutoML。

例如,给定的餐馆评论数据如下所示:

哇...喜欢这个地方。

1

酥皮不行。

0

需要使用 TextClassificationTrainer 训练程序来分析情绪,其中 0 为负面评论,1 为正面评论。 但是,缺少 ctx.Auto().TextClassification() 配置。

若要将 AutoML 与文本分类训练程序配合使用,必须:

  1. 创建自己的搜索空间。

    // Define TextClassification search space
    public class TCOption
    {
        [Range(64, 128, 32)]
        public int BatchSize { get; set; }
    }
    

    在这种情况下,AutoML 将搜索 BatchSize 超参数的不同配置。

  2. 创建可扫描估算器并将其添加到管道。

    // Initialize search space
    var tcSearchSpace = new SearchSpace<TCOption>();
    
    // Create factory for Text Classification trainer
    var tcFactory = (MLContext ctx, TCOption param) =>
    {
        return ctx.MulticlassClassification.Trainers.TextClassification(
            sentence1ColumnName: textColumnName,
            batchSize:param.BatchSize);
    };
    
    // Create text classification sweepable estimator
    var tcEstimator = 
        ctx.Auto().CreateSweepableEstimator(tcFactory, tcSearchSpace);
    
    // Define text classification pipeline
    var pipeline =
        ctx.Transforms.Conversion.MapValueToKey(columnInference.ColumnInformation.LabelColumnName)
            .Append(tcEstimator);
    

    在本例中,TCOption 搜索空间和自定义 TextClassificationTrainer 工厂用于创建可扫描估算器。

  3. 创建自定义试用运行程序

    若要创建自定义试用运行程序,请实现 ITrialRunner

    public class TCRunner : ITrialRunner
    {
        private readonly MLContext _context;
        private readonly TrainTestData _data;
        private readonly IDataView _trainDataset;
        private readonly IDataView _evaluateDataset;
        private readonly SweepablePipeline _pipeline;
        private readonly string _labelColumnName;
        private readonly MulticlassClassificationMetric _metric;
    
        public TCRunner(
            MLContext context, 
            TrainTestData data, 
            SweepablePipeline pipeline,
            string labelColumnName = "Label", 
            MulticlassClassificationMetric metric = MulticlassClassificationMetric.MicroAccuracy)
        {
            _context = context;
            _data = data;
            _trainDataset = data.TrainSet;
            _evaluateDataset = data.TestSet;
            _labelColumnName = labelColumnName;
            _pipeline = pipeline;
            _metric = metric;
        }
    
        public void Dispose()
        {
            return;
        }
    
        // Run trial asynchronously
        public Task<TrialResult> RunAsync(TrialSettings settings, CancellationToken ct)
        {
            try
            {
                return Task.Run(() => Run(settings));
            }
            catch (Exception ex) when (ct.IsCancellationRequested)
            {
                throw new OperationCanceledException(ex.Message, ex.InnerException);
            }
            catch (Exception)
            {
                throw;
            }
        }
    
        // Helper function to define trial run logic
        private TrialResult Run(TrialSettings settings)
        {
            try
            {
                // Initialize stop watch to measure time
                var stopWatch = new Stopwatch();
                stopWatch.Start();
    
                // Get pipeline parameters
                var parameter = settings.Parameter["_pipeline_"];
    
                // Use parameters to build pipeline
                var pipeline = _pipeline.BuildFromOption(_context, parameter);
    
                // Train model
                var model = pipeline.Fit(_trainDataset);
    
                // Evaluate the model
                var predictions = model.Transform(_evaluateDataset);
    
                // Get metrics
                var evaluationMetrics = _context.MulticlassClassification.Evaluate(predictions, labelColumnName: _labelColumnName);
                var chosenMetric = GetMetric(evaluationMetrics);
    
                return new TrialResult()
                {
                    Metric = chosenMetric,
                    Model = model,
                    TrialSettings = settings,
                    DurationInMilliseconds = stopWatch.ElapsedMilliseconds
                };
            }
            catch (Exception)
            {
                return new TrialResult()
                {
                    Metric = double.MinValue,
                    Model = null,
                    TrialSettings = settings,
                    DurationInMilliseconds = 0,
                };
            }
        }
    
        // Helper function to choose metric used by experiment
        private double GetMetric(MulticlassClassificationMetrics metric)
        {
            return _metric switch
            {
                MulticlassClassificationMetric.MacroAccuracy => metric.MacroAccuracy,
                MulticlassClassificationMetric.MicroAccuracy => metric.MicroAccuracy,
                MulticlassClassificationMetric.LogLoss => metric.LogLoss,
                MulticlassClassificationMetric.LogLossReduction => metric.LogLossReduction,
                MulticlassClassificationMetric.TopKAccuracy => metric.TopKAccuracy,
                _ => throw new NotImplementedException(),
            };
        }
    }
    

    本例中的 TCRunner 实现:

    • 提取为本次试用选择的超参数
    • 使用超参数创建 ML.NET 管道
    • 使用 ML.NET 管道训练模型
    • 评估模型
    • 返回一个 TrialResult 对象,其中包含本次试用的信息
  4. 初始化自定义试用运行程序

    var tcRunner = new TCRunner(context: ctx, data: trainValidationData, pipeline: pipeline);
    
  5. 创建并配置试验。 使用 SetTrialRunner 扩展方法将自定义试用运行程序添加到试验。

    AutoMLExperiment experiment = ctx.Auto().CreateExperiment();
    
    // Configure AutoML experiment
    experiment
        .SetPipeline(pipeline)
        .SetMulticlassClassificationMetric(MulticlassClassificationMetric.MicroAccuracy, labelColumn: columnInference.ColumnInformation.LabelColumnName)
        .SetTrainingTimeInSeconds(120)
        .SetDataset(trainValidationData)
        .SetTrialRunner(tcRunner);
    
  6. 运行试验

    var tcCts = new CancellationTokenSource();
    TrialResult textClassificationExperimentResults = await experiment.RunAsync(tcCts.Token);
    

选择其他调谐器

AutoML 支持各种优化算法来循环访问搜索空间,以搜索最佳超参数。 默认情况下,它使用 Eci 成本节俭调谐器。 利用试验扩展方法,可以选择最适合你的场景的其他调谐器。

使用以下方法设置调谐器:

例如,若要使用网格搜索调谐器,代码可能如下所示:

experiment.SetGridSearchTuner();

配置试验监视

监视试验进度的最快方法是从 MLContext 定义 Log 事件。 但是,Log 事件会输出 AutoML 在每次试用期间生成的日志的原始转储。 由于大量未格式化的信息,这很困难。

若要获得更受控的监视体验,请使用 IMonitor 界面实现类。

public class AutoMLMonitor : IMonitor
{
    private readonly SweepablePipeline _pipeline;

    public AutoMLMonitor(SweepablePipeline pipeline)
    {
        _pipeline = pipeline;
    }

    public IEnumerable<TrialResult> GetCompletedTrials() => _completedTrials;

    public void ReportBestTrial(TrialResult result)
    {
        return;
    }

    public void ReportCompletedTrial(TrialResult result)
    {
        var trialId = result.TrialSettings.TrialId;
        var timeToTrain = result.DurationInMilliseconds;
        var pipeline = _pipeline.ToString(result.TrialSettings.Parameter);
        Console.WriteLine($"Trial {trialId} finished training in {timeToTrain}ms with pipeline {pipeline}");
    }

    public void ReportFailTrial(TrialSettings settings, Exception exception = null)
    {
        if (exception.Message.Contains("Operation was canceled."))
        {
            Console.WriteLine($"{settings.TrialId} cancelled. Time budget exceeded.");
        }
        Console.WriteLine($"{settings.TrialId} failed with exception {exception.Message}");
    }

    public void ReportRunningTrial(TrialSettings setting)
    {
        return;
    }
}

IMonitor 界面有四个生命周期事件:

提示

尽管不是必需的,但请将 SweepablePipeline 包含在监视器中,以便可以使用 TrialSettingsParameter 属性检查为试用生成的管道。

在本例中,仅实现 ReportCompletedTrialReportFailTrial

实现监视器后,请使用 SetMonitor 将其设置为试验配置的一部分。

var monitor = new AutoMLMonitor(pipeline);
experiment.SetMonitor(monitor);

然后,运行试验:

var cts = new CancellationTokenSource();
TrialResult experimentResults = await experiment.RunAsync(cts.Token);

使用此实现运行试验时,输出应如下所示:

Trial 0 finished training in 5835ms with pipeline ReplaceMissingValues=>OneHotEncoding=>Concatenate=>FastForestRegression
Trial 1 finished training in 15080ms with pipeline ReplaceMissingValues=>OneHotEncoding=>Concatenate=>SdcaRegression
Trial 2 finished training in 3941ms with pipeline ReplaceMissingValues=>OneHotHashEncoding=>Concatenate=>FastTreeRegression

永久试用

默认情况下,AutoML 仅存储最佳模型的 TrialResult。 但是,如果要保留每次试用,可以从监视器内部执行此操作。

在监视器中:

  1. 为已完成的试用定义属性以及用于访问它们的方法。

    private readonly List<TrialResult> _completedTrials;
    
    public IEnumerable<TrialResult> GetCompletedTrials() => _completedTrials;
    
  2. 在构造函数中初始化它

    public AutoMLMonitor(SweepablePipeline pipeline)
    {
        //...
        _completedTrials = new List<TrialResult>();
        //...
    }
    
  3. 将每个试用结果追加到 ReportCompletedTrial 生命周期方法中。

    public void ReportCompletedTrial(TrialResult result)
    {
        //...
        _completedTrials.Add(result);
    }
    
  4. 训练完成后,可以通过调用 GetCompletedTrials 来访问所有已完成的试用

    var completedTrials = monitor.GetCompletedTrials();
    

此时,可以对已完成的试用集合执行其他处理。 例如,可以选择 AutoML 选择的模型以外的模型,将试用结果记录到数据库,或者从任何已完成的试用重新生成管道。

取消试验

异步运行试验时,请确保完全终止进程。 若要执行此操作,请使用 CancellationToken

警告

取消试验不会保存任何中间输出。 请设置检查点以保存中间输出。

var cts = new CancellationTokenSource();
TrialResult experimentResults = await experiment.RunAsync(cts.Token);

设置检查点

检查点提供了一种在出现提前终止或错误时保存训练过程中的中间输出的方法。 若要设置检查点,请使用 SetCheckpoint 扩展方法并提供用于存储中间输出的目录。

var checkpointPath = Path.Join(Directory.GetCurrentDirectory(), "automl");
experiment.SetCheckpoint(checkpointPath);

确定特征重要性

随着机器学习被引入日常生活的更多方面(例如医疗保健),理解机器学习模型为何做出其决策变得至关重要。 排列特征重要性 (PFI) 是一种用于解释分类、设置优先级和回归模型的技术。 概括而言,其工作原理是一次随机为整个数据集随机抽取数据的一个特征,并计算关注性能指标的下降程度。 变化越大,特征就越重要。 有关 PFI 的详细信息,请参阅使用排列特征重要性解释模型预测

注意

计算 PFI 可能是一项耗时的操作。 计算所需的时间与特征列数成正比。 特征越多,运行 PFI 所需的时间就越长。

若要使用 AutoML 确定特征重要性,请执行以下操作:

  1. 获取最佳模型。

    var bestModel = expResult.Model;
    
  2. 将模型应用于数据集。

    var transformedData = bestModel.Transform(trainValidationData.TrainSet);
    
  3. 使用 PermutationFeatureImportance 计算特征重要性

    在此情况下,任务是回归,但相同的概念适用于其他任务,如设置优先级和分类。

    var pfiResults = 
        mlContext.Regression.PermutationFeatureImportance(bestModel, transformedData, permutationCount:3);
    
  4. 按评估指标的更改对特征重要性进行排序。

    var featureImportance = 
        pfiResults.Select(x => Tuple.Create(x.Key, x.Value.Regression.RSquared))
            .OrderByDescending(x => x.Item2);