Novembro de 2018

Volume 33 - Número 11

Execução de teste - Introdução à biblioteca do ML.NET

Por James McCaffrey

James McCaffreyA biblioteca do ML.NET é uma coleção de software livre de aprendizado de máquina (ML) que pode ser usada diretamente em aplicativos .NET. A maioria das bibliotecas de ML, como o Keras, TensorFlow, CNTK e PyTorch, é escrita em Python e chamam rotinas de C++ de nível baixo. No entanto, se você usar uma biblioteca baseada em Python, não será tão fácil para um aplicativo .NET acessar um modelo de ML treinado. Felizmente, a biblioteca do ML.NET se integra perfeitamente aos aplicativos .NET.

A melhor maneira de saber o rumo que este artigo tomará é examinar o programa de demonstração na Figura 1. A demonstração cria um modelo de ML que prevê se um paciente vai morrer ou sobreviver com base em idade, sexo e pontuação em um teste renal clínico. Como há somente dois resultados possíveis, morrer ou sobreviver, isso é um problema de classificação binária.

Programa de demonstração do ML.NET em ação
Figura 1 Programa de demonstração do ML.NET em ação

Nos bastidores, o programa de demonstração usa a biblioteca do ML.NET para criar e treinar um modelo de regressão logística. Enquanto escrevo este artigo, a biblioteca do ML.NET ainda está em modo de visualização e, portanto, algumas das informações apresentadas aqui podem ter sido alteradas no momento em que você está lendo este artigo.

A demonstração usa um conjunto de dados de treinamento com 30 itens. Depois de treinado, o modelo foi aplicado aos dados de origem e atingiu 66,67% de precisão (20 corretos e 10 incorretos). A demonstração é concluída usando o modelo treinado para prever o resultado de um homem de 50 anos com uma pontuação 4.80 no teste renal — a previsão é que o paciente sobreviverá.

Este artigo pressupõe que você tenha habilidades de programação intermediárias ou melhores com C#, mas não pressupõe que você tenha familiaridade com a biblioteca ML.NET. O código completo e os dados para o programa de demonstração são apresentados neste artigo e também estão disponíveis no download do arquivo que acompanha este artigo.

O programa de demonstração

Para criar o programa de demonstração, iniciei o Visual Studio 2017. A biblioteca do ML.NET funcionará com a Community Edition gratuita ou com qualquer uma das edições comerciais do Visual Studio 2017. A documentação do ML.NET não declara explicitamente que o Visual Studio 2017 é necessário, mas não consegui fazer o programa de demonstração funcionar com o Visual Studio 2015. Criei um novo aplicativo de console em C# e dei-lhe o nome de Kidney. A biblioteca do ML.NET funcionará com um .NET clássico ou um tipo de aplicativo do .NET Core.

Após o código de modelo ser carregado, eu cliquei com o botão direito do mouse no arquivo Program.cs na janela Gerenciador de Soluções e o renomeei como KidneyProgram.cs, permitindo que o Visual Studio renomeasse automaticamente a classe Program para mim. Em seguida, na janela do Gerenciador de Soluções, cliquei com o botão direito do mouse no projeto Kidney e selecionei a opção Pacotes NuGet. Na janela do NuGet, selecionei a guia Procurar e, em seguida, inseri “ML.NET” no campo de pesquisa. A biblioteca do ML.NET está hospedada no pacote Microsoft.ML. Selecionei o pacote e cliquei no botão Instalar. Depois de alguns segundos, o Visual Studio respondeu com uma mensagem “Microsoft.ML 0.3.0 instalado com êxito para Kidney”.

Neste ponto, fiz um Compilar | Recompilar Solução e obtive uma mensagem de erro “compatível somente com arquiteturas x64”. Na janela do Gerenciador de Soluções, cliquei com o botão direito do mouse no projeto Kidney e selecionei a entrada Propriedades. Na janela Propriedades, selecionei a guia Compilar à esquerda e alterei, em seguida, a entrada de destino da plataforma de “Qualquer CPU” para “x64”. Também me assegurei de que tinha como destino a versão 4.7 do .NET Framework. Com versões anteriores, eu obtinha um erro relacionado a uma das dependências de biblioteca de matemática e tinha que editar manualmente o arquivo .csproj global. Que chato. Em seguida, fiz Compilar | Recompilar Solução e obtive êxito. Ao trabalhar com bibliotecas de modo de visualização, como ML.NET, você deve esperar falhas deste tipo como sendo a regra em vez da exceção.

Os dados de demonstração

Depois de criar o esqueleto do programa de demonstração, a próxima etapa era criar o arquivo de dados de treinamento. Os dados são apresentados na Figura 2. Na janela do Gerenciador de Soluções, cliquei com o botão direito do mouse no projeto Kidney e selecionei Adicionar | Novo Item. Na janela de diálogo de novo item, selecionei o tipo de arquivo de texto e o chamei de KidneyData.txt. Se você estiver acompanhando isto, copie os dados da Figura 2 e cole-os na janela do editor, tomando cuidado para não ter qualquer linha em branco extra à direita.

Figura 2 Dados de Kidney

48, +1, 4.40, survive
60, -1, 7.89, die
51, -1, 3.48, survive
66, -1, 8.41, die
40, +1, 3.05, survive
44, +1, 4.56, survive
80, -1, 6.91, die
52, -1, 5.69, survive
56, -1, 4.01, survive
55, -1, 4.48, survive
72, +1, 5.97, survive
57, -1, 6.71, die
50, -1, 6.40, survive
80, -1, 6.67, die
69, +1, 5.79, survive
39, -1, 5.42, survive
68, -1, 7.61, die
47, +1, 3.24, survive
45, +1, 4.29, survive
79, +1, 7.44, die
44, -1, 2.55, survive
52, +1, 3.71, survive
55, +1, 5.56, die
76, -1, 7.80, die
51, -1, 5.94, survive
46, +1, 5.52, survive
48, -1, 3.25, survive
58, +1, 4.71, survive
44, +1, 2.52, survive
68, -1, 8.38, die

O conjunto de dados de 30 itens é artificial e deve ser em grande parte autoexplicativo. O campo sexo é codificado como masculino = -1 e feminino = + 1. Como os dados têm três dimensões (idade, sexo, pontuação de teste), não é possível exibi-los em um grafo bidimensional. Mas você pode ter uma boa ideia da estrutura dos dados examinando o grafo de idade e a pontuação de teste renal na Figura 3. O gráfico sugere que os dados podem ser linearmente separáveis. 

Dados de Kidney
Figura 3 Dados de Kidney

O código do programa

O código de demonstração completo, com algumas pequenas edições para economizar espaço, é apresentado na Figura 4. Na parte superior da janela do Editor, removi todas as referências de namespace e as substitui por aquelas mostradas na listagem de código. Os diversos namespaces Microsoft.ML abrigam toda a funcionalidade do ML.NET. O namespace System.Threading.Tasks é necessário para salvar ou carregar um modelo do ML.NET treinado no arquivo.

Figura 4 Programa de exemplo do ML.NET

using System;
using Microsoft.ML;
using Microsoft.ML.Data;
using Microsoft.ML.Runtime.Api;
using Microsoft.ML.Trainers;
using Microsoft.ML.Transforms;
using Microsoft.ML.Models;
using System.Threading.Tasks;
namespace Kidney
{
  class KidneyProgram
  {
    public class KidneyData
    {
      [Column(ordinal: "0", name: "Age")]
      public float Age;
      [Column(ordinal: "1", name: "Sex")]
      public float Sex;
      [Column(ordinal: "2", name: "Kidney")]
      public float Kidney;
      [Column(ordinal: "3", name: "Label")]
      public string Label;
    }
    public class KidneyPrediction
    {
      [ColumnName("PredictedLabel")]
      public string PredictedLabels;
    }
    static void Main(string[] args)
    {
      Console.WriteLine("ML.NET (v0.3.0 preview) demo run");
      Console.WriteLine("Survival based on age, sex, kidney");
      var pipeline = new LearningPipeline();
      string dataPath = "..\\..\\KidneyData.txt";
      pipeline.Add(new TextLoader(dataPath).
        CreateFrom<KidneyData>(separator: ','));
      pipeline.Add(new Dictionarizer("Label"));
      pipeline.Add(new ColumnConcatenator("Features", "Age",
        "Sex", "Kidney"));
      pipeline.Add(new Logistic​Regression​Binary​Classifier());
      pipeline.Add(new
        PredictedLabelColumnOriginalValueConverter()
        { PredictedLabelColumn = "PredictedLabel" });
      Console.WriteLine("\nStarting training \n");
      var model = pipeline.Train<KidneyData,
        KidneyPrediction>();
      Console.WriteLine("\nTraining complete \n");
      string ModelPath = "..\\..\\KidneyModel.zip";
      Task.Run(async () =>
      {
        await model.WriteAsync(ModelPath);
      }).GetAwaiter().GetResult();
      var testData = new TextLoader(dataPath).
        CreateFrom<KidneyData>(separator: ',');
      var evaluator = new BinaryClassificationEvaluator();
      var metrics = evaluator.Evaluate(model, testData);
      double acc = metrics.Accuracy * 100;
      Console.WriteLine("Model accuracy = " +
        acc.ToString("F2") + "%");
      Console.WriteLine("Predict 50-year male, kidney 4.80:");
      KidneyData newPatient = new KidneyData()
        { Age = 50f, Sex = -1f, Kidney = 4.80f };
      KidneyPrediction prediction = model.Predict(newPatient);
      string result = prediction.PredictedLabels;
      Console.WriteLine("Prediction = " + result);
      Console.WriteLine("\nEnd ML.NET demo");
      Console.ReadLine();
    } // Main
  } // Program
} // ns

O programa de demonstração define uma classe chamada KidneyData, aninhada na classe principal do programa, que define a estrutura interna dos dados de treinamento. Por exemplo, a primeira coluna é:

[Column(ordinal: "0", name: "Age")]
public float Age;

Observe que o campo de idade é declarado como do tipo float em vez do tipo double. No ML, o tipo float é o tipo de numérico padrão porque o aumento na precisão obtido do uso do tipo double quase nunca vale a penalidade de desempenho e de memória resultante. O valor a ser previsto deve usar o nome “Label”, mas os nomes de campo de previsão podem ser o que você quiser.

O programa de demonstração define uma classe aninhada chamada KidneyPrediction para manter as previsões do modelo:

public class KidneyPrediction
{
  [ColumnName("PredictedLabel")]
  public string PredictedLabels;
}

O nome da coluna “PredictedLabel” é necessário mas, conforme mostrado, o identificador de cadeia de caracteres associada não precisa corresponder.

Criação e treinamento do modelo

O programa de demonstração cria um modelo de ML usando estas sete instruções:

var pipeline = new LearningPipeline();
string dataPath = "..\\..\\KidneyData.txt";
pipeline.Add(new TextLoader(dataPath).
  CreateFrom<KidneyData>(separator: ','));
pipeline.Add(new Dictionarizer("Label"));
pipeline.Add(new ColumnConcatenator("Features", "Age", "Sex", "Kidney"));
pipeline.Add(new Logistic​Regression​Binary​Classifier());
pipeline.Add(new PredictedLabelColumnOriginalValueConverter()
  { PredictedLabelColumn = "PredictedLabel" });

Você pode pensar no objeto de pipeline como um modelo de ML não treinado mais os dados necessários para treinar o modelo. Lembre-se de que os valores a serem previstos no arquivo de dados são “survive” ou “die”. Como os modelos de ML só entendem valores numéricos, a classe de nome estranho Dictionarizer é usada para codificar duas cadeias de caracteres como 0 ou 1. O construtor ColumnConcatenator combina as três variáveis de previsão em uma agregação e o uso de um parâmetro de resultado de cadeia de caracteres com o nome “Features” é obrigatório.

Há muitas técnicas de ML diferentes que você pode usar para um problema de classificação binária. Posso usar a regressão logística no programa de demonstração para manter as ideias principais tão claras quanto possível, porque essa é, indiscutivelmente, a forma mais simples e mais básica de ML. Outros algoritmos de classificação binária com suporte do ML.NET incluem AveragedPerceptronBinaryClassifier, FastForestBinaryClassifier e LightGbmClassifier.

O programa de demonstração treina e salva o modelo usando estas instruções:

var model = pipeline.Train<KidneyData, KidneyPrediction>();
string ModelPath = "..\\..\\KidneyModel.zip";
Task.Run(async () =>
{
  await model.WriteAsync(ModelPath);
}).GetAwaiter().GetResult();

O método Train da biblioteca do ML.NET é muito sofisticado. Se consultar novamente a captura de tela da Figura 1, você poderá ver que Train realiza a normalização automática de variáveis de previsão, o que as dimensiona de forma que os valores de previsão grandes, como a renda anual de uma pessoa, não engulam os valores pequenos, como a quantidade de filhos de uma pessoa. O método Train também usa regularização, que é uma técnica avançada para melhorar a precisão de um modelo. Em resumo, o ML.NET faz todos os tipos de processamento avançado, sem que seja necessário configurar explicitamente os valores de parâmetro.

Como salvar e avaliar o modelo

Depois que o modelo tiver sido treinado, ele será salvo em disco da seguinte forma:

string ModelPath = "..\\..\\KidneyModel.zip";
Task.Run(async () =>
{
  await model.WriteAsync(ModelPath);
}).GetAwaiter().GetResult();

Como o método WriteAsync é assíncrono, não é tão fácil chamá-lo. A abordagem que eu prefiro é a técnica de wrapper mostrada. A falta de um método não assíncrono para salvar um modelo do ML.NET é bastante surpreendente, mesmo para uma biblioteca que esteja no modo de visualização.

O programa de demonstração faz a suposição de que o programa executável está dois diretórios abaixo do diretório raiz do projeto. Em um sistema de produção, convém verificar se o diretório de destino existe. Normalmente, em um projeto do ML.NET, eu gosto de criar um subdiretório de dados e um subdiretório de modelos fora da pasta raiz do projeto (Kidney, neste exemplo) e salvar meus dados e modelos nesses diretórios.

O modelo é avaliado com estas instruções:

var testData = new TextLoader(dataPath).
  CreateFrom<KidneyData>(separator: ',');
var evaluator = new BinaryClassificationEvaluator();
var metrics = evaluator.Evaluate(model, testData);
double acc = metrics.Accuracy * 100;
Console.WriteLine("Model accuracy = " +
  acc.ToString("F2") + "%");

Na maioria dos cenários de ML, você teria dois arquivos de dados — um conjunto usado somente para treinamento e um segundo conjunto de dados de teste usado somente para avaliação do modelo. Para simplificar, o programa de demonstração reutiliza o arquivo de dados de 30 itens único para avaliação de modelo.

O método Evaluate retorna um objeto com várias métricas, incluindo perda de log, precisão, cancelamento, pontuação F1 e assim por diante. O objeto de retorno também tem um objeto ConfusionMatrix legal que pode ser usado para exibir contagens, como o número de pacientes que se esperava que fossem sobreviver, mas que na verdade morreram.

Como usar o modelo treinado

O programa de demonstração mostra como usar o modelo treinado para fazer uma previsão:

Console.WriteLine("Predict 50-year male kidney = 4.80:");
KidneyData newPatient = new KidneyData()
  { Age = 50f, Sex = -1f, Kidney = 4.80f };
KidneyPrediction prediction = model.Predict(newPatient);
string result = prediction.PredictedLabels;
Console.WriteLine("Prediction = " + result);

Observe que os literais numéricos para pontuação renal, idade e sexo usam o modificador “f”, porque o modelo está esperando valores do tipo float. Neste exemplo, o modelo treinado estava disponível porque o programa acabou o treinamento. Se você quisesse fazer uma previsão de um programa diferente, seria necessário carregar o modelo treinado usando o método ReadAsync ao longo das linhas de:

PredictionModel<KidneyData,
  KidneyPrediction> model = null;
Task.Run(async () =>
{
  model2 = await PredictionModel.ReadAsync
  <KidneyData, KidneyPrediction>(ModelPath);
}).GetAwaiter().GetResult();

Conclusão

Embora a biblioteca do ML.NET seja nova, suas origens remontam a muitos anos atrás. Logo após o lançamento do Microsoft .NET Framework em 2002, a Microsoft Research iniciou um projeto chamado “navegação e pesquisa de mineração de texto”, ou TMSN, para permitir que os desenvolvedores de software incluíssem o código de ML em produtos e tecnologias Microsoft. O projeto foi muito bem-sucedido e, ao longo dos anos. aumentou em tamanho e uso interno na Microsoft. Em meados de 2011, a biblioteca foi renomeada para “o código de aprendizado” (TLC). O TLC é amplamente usado dentro da Microsoft e está atualmente na versão 3.9. A biblioteca do ML.NET é um produto direto do TLC, com recursos específicos da Microsoft removidos. 


Dr. James McCaffreytrabalha para a Microsoft Research em Redmond, Washington. Ele trabalhou em vários produtos da Microsoft, incluindo Internet Explorer e Bing. Entre em contato com o Dr. McCaffrey pelo email jamccaff@microsoft.com.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Chris Lee e Ricky Loynd


Discuta esse artigo no fórum do MSDN Magazine