Preparar dados para criar um modelo

Saiba como usar o ML.NET para preparar dados para construção de modelo ou processamento adicional.

Dados geralmente não estão limpos e são esparsos. Os algoritmos de aprendizado de máquina do ML.NET esperam que a entrada ou os recursos estejam em um único vetor numérico. Da mesma forma, o valor a ser previsto (rótulo), especialmente no caso de dados categóricos, deve ser codificado. Portanto, uma das metas da preparação de dados é obter os dados no formato esperado por algoritmos do ML.NET.

Dividir os dados em conjuntos de treinamento e teste

A seção a seguir descreve problemas comuns ao treinar um modelo conhecido como sobreajuste e subajuste. Dividir seus dados e validar seus modelos usando um conjunto mantido pode ajudar você a identificar e atenuar esses problemas.

Sobreajuste e subajuste

Sobreajuste e subajuste são os dois problemas mais comuns que você encontra ao treinar um modelo. O subajuste significa que o treinador selecionado não é capaz de ajustar o conjunto de dados de treinamento e geralmente resulta em uma perda alta durante o treinamento e pontuação/métrica baixa no conjunto de dados de teste. Para resolver isso, você precisa selecionar um modelo mais poderoso ou executar mais engenharia de recursos. O sobreajuste é o oposto e acontece quando o modelo aprende bem demais os dados de treinamento. Isso geralmente resulta em baixa métrica de perda durante o treinamento, mas alta perda no conjunto de dados de teste.

Uma boa analogia para esses conceitos é estudar para uma prova. Digamos que você saiba as perguntas e respostas com antecedência. Depois de estudar, você faz a prova e recebe uma pontuação perfeita. Ótimas notícias! No entanto, quando você recebe a prova novamente com as perguntas reorganizadas e com textos ligeiramente diferentes, você recebe uma pontuação mais baixa. Isso sugere que você memorizou as respostas e não aprendeu os conceitos em que estava sendo testado. Esse é um exemplo de sobreajuste. O subajuste é o oposto, e os materiais de estudo que lhe foram dados não representam com precisão aquilo em que você é avaliado na prova. Como resultado, você recorre a adivinhar as respostas, pois não tem conhecimento suficiente para responder corretamente.

Dividir os dados

Carregue os seguintes dados de entrada em um IDataView chamado data:

var homeDataList = new HomeData[]
{
    new()
    {
        NumberOfBedrooms = 1f,
        Price = 100_000f
    },
    new()
    {
        NumberOfBedrooms = 2f,
        Price = 300_000f
    },
    new()
    {
        NumberOfBedrooms = 6f,
        Price = 600_000f
    },
    new()
    {
        NumberOfBedrooms = 3f,
        Price = 300_000f
    },
    new()
    {
        NumberOfBedrooms = 2f,
        Price = 200_000f
    }
};

Para dividir dados em conjuntos de treinamento/teste, use o método TrainTestSplit(IDataView, Double, String, Nullable<Int32>).

// Apply filter
TrainTestData dataSplit = mlContext.Data.TrainTestSplit(data, testFraction: 0.2);

O parâmetro testFraction é usado para usar 0,2 ou 20% do conjunto de dados para teste. Os 80% restantes são usados para treinamento.

O resultado é DataOperationsCatalog.TrainTestData com dois IDataViews que você pode acessar por meio de TrainSet e TestSet.

Filtrar dados

Às vezes, nem todos os dados em um conjunto de dados são relevantes para análise. Uma abordagem para remover dados irrelevantes é a filtragem. O DataOperationsCatalog contém um conjunto de operações do filtro que recebem um IDataView que contém todos os dados e o retorno de um IDataView contendo apenas o pontos de interesse dos dados. É importante observar que, como operações de filtro não são um IEstimator ou ITransformer como aquelas no TransformsCatalog, elas não podem ser incluídas como parte de um pipeline de preparação de dados EstimatorChain ou TransformerChain.

Carregue os seguintes dados de entrada em um IDataView chamado data:

HomeData[] homeDataList = new HomeData[]
{
    new ()
    {
        NumberOfBedrooms=1f,
        Price=100000f
    },
    new ()
    {
        NumberOfBedrooms=2f,
        Price=300000f
    },
    new ()
    {
        NumberOfBedrooms=6f,
        Price=600000f
    }
};

Para filtrar dados com base no valor de uma coluna, use o método FilterRowsByColumn.

// Apply filter
IDataView filteredData = mlContext.Data.FilterRowsByColumn(data, "Price", lowerBound: 200000, upperBound: 1000000);

O exemplo acima usa linhas no conjunto de dados com um preço entre 200.000 e 1.000.000. O resultado da aplicação desse filtro retornaria apenas as duas últimas linhas nos dados e excluiria a primeira linha, pois seu preço é 100.000 e não está no intervalo especificado.

Substituir valores ausentes

Valores ausentes são uma ocorrência comum em conjuntos de dados. Uma abordagem para lidar com valores ausentes é substituí-los pelo valor padrão para o tipo fornecido, se houver, ou outro valor significativo, como o valor médio dos dados.

Carregue os seguintes dados de entrada em um IDataView chamado data:

HomeData[] homeDataList = new HomeData[]
{
    new ()
    {
        NumberOfBedrooms=1f,
        Price=100000f
    },
    new ()
    {
        NumberOfBedrooms=2f,
        Price=300000f
    },
    new ()
    {
        NumberOfBedrooms=6f,
        Price=float.NaN
    }
};

Observe que o último elemento em nossa lista tem um valor ausente para Price. Para substituir os valores ausentes na coluna Price, use o método ReplaceMissingValues para preencher esse valor ausente.

Importante

ReplaceMissingValue funciona somente com os dados numéricos.

// Define replacement estimator
var replacementEstimator = mlContext.Transforms.ReplaceMissingValues("Price", replacementMode: MissingValueReplacingEstimator.ReplacementMode.Mean);

// Fit data to estimator
// Fitting generates a transformer that applies the operations of defined by estimator
ITransformer replacementTransformer = replacementEstimator.Fit(data);

// Transform data
IDataView transformedData = replacementTransformer.Transform(data);

O ML.NET dá suporte a vários modos de substituição. O exemplo acima usa o modo de substituição Mean que preenche o valor ausente com o valor da média da coluna. O resultado da substituição preenche a propriedade Price para o último elemento em nossos dados com 200.000, pois é a média de 300.000 e de 100.000.

Usar normalizadores

A normalização é uma técnica de pré-processamento de dados usada para escalar recursos para estarem no mesmo intervalo, geralmente entre 0 e 1, a fim de que sejam processados com mais precisão por um algoritmo de aprendizado de máquina. Por exemplo, os intervalos de idade e renda variam significativamente com idades geralmente no intervalo de 0 a 100 e a renda geralmente no intervalo de zero a milhares. Acesse a página de transformações para uma lista mais detalhada e uma descrição de transformações de normalização.

Normalização de Mín-Máx

Carregue os seguintes dados de entrada em um IDataView chamado data:

HomeData[] homeDataList = new HomeData[]
{
    new ()
    {
        NumberOfBedrooms = 2f,
        Price = 200000f
    },
    new ()
    {
        NumberOfBedrooms = 1f,
        Price = 100000f
    }
};

A normalização pode ser aplicada a colunas com valores numéricos únicos e também vetores. Normalize os dados na coluna Price usando a normalização Mín-Máx com o método NormalizeMinMax.

// Define min-max estimator
var minMaxEstimator = mlContext.Transforms.NormalizeMinMax("Price");

// Fit data to estimator
// Fitting generates a transformer that applies the operations of defined by estimator
ITransformer minMaxTransformer = minMaxEstimator.Fit(data);

// Transform data
IDataView transformedData = minMaxTransformer.Transform(data);

Os valores de preço original [200000,100000] são convertidos em [ 1, 0.5 ] usando a fórmula de normalização MinMax que gera valores de saída no intervalo de 0 a 1.

Compartimentalização

Compartimentalização converte valores contínuos em uma representação discreta da entrada. Por exemplo, suponha que um de seus recursos seja idade. Em vez de usar o valor real de idade, a compartimentalização cria intervalos para esse valor. 0-18 poderia ser um compartimento, outro poderia ser 19-35 e assim por diante.

Carregue os seguintes dados de entrada em um IDataView chamado data:

HomeData[] homeDataList = new HomeData[]
{
    new ()
    {
        NumberOfBedrooms=1f,
        Price=100000f
    },
    new ()
    {
        NumberOfBedrooms=2f,
        Price=300000f
    },
    new ()
    {
        NumberOfBedrooms=6f,
        Price=600000f
    }
};

Normalize os dados em compartimentos usando o método NormalizeBinning. O parâmetro maximumBinCount permite que você especifique o número de compartimentos necessários para classificar seus dados. Neste exemplo, os dados serão colocados em dois compartimentos.

// Define binning estimator
var binningEstimator = mlContext.Transforms.NormalizeBinning("Price", maximumBinCount: 2);

// Fit data to estimator
// Fitting generates a transformer that applies the operations of defined by estimator
var binningTransformer = binningEstimator.Fit(data);

// Transform Data
IDataView transformedData = binningTransformer.Transform(data);

O resultado de compartimentalização cria limites de compartimento de [0,200000,Infinity]. Portanto os compartimentos resultantes são [0,1,1], pois a primeira observação está entre 0 a 200.000 e os outros são maiores que 200.000, mas menores que infinito.

Trabalhar com os dados categóricos

Um dos tipos de dados mais comuns são os dados categóricos. Os dados categóricos têm um número finito de categorias. Por exemplo, os estados dos EUA ou uma lista dos tipos de animais encontrados em um conjunto de imagens. Se os dados categóricos forem recursos ou rótulos, eles deverão ser mapeados em um valor numérico para serem usados na geração de um modelo de machine learning. Há diversas maneiras de trabalhar com dados categóricos no ML.NET, dependendo do problema a ser solucionado.

Mapeamento de valor de chave

No ML.NET, uma chave é um valor inteiro que representa uma categoria. O mapeamento de valor de chave é usado frequentemente para mapear rótulos de cadeia de caracteres em valores inteiros exclusivos para treinamento e, em seguida, novamente para seus valores de cadeia de caracteres quando o modelo é usado para fazer uma previsão.

As transformações usadas para realizar o mapeamento de valor de chave são MapValueToKey e MapKeyToValue.

MapValueToKey adiciona um dicionário de mapeamentos no modelo, para que MapKeyToValue possa realizar a transformação reversa durante uma previsão.

Codificação one-hot

Uma codificação one-hot mapeia um conjunto finito de valores em inteiros cuja representação binária tem um único valor 1 em posições únicas na cadeia de caracteres. Uma codificação one-hot pode ser a melhor escolha quando não há ordenação implícita dos dados categóricos. A tabela a seguir mostra um exemplo com CEPs como valores brutos.

Valor bruto Valor codificado com a codificação one-hot
98052 00...01
98100 00...10
... ...
98109 10...00

A transformação para converter dados categóricos em números codificados com a codificação one-hot é OneHotEncoding.

Hash

O hashing é outra maneira de converter dados categóricos em números. Uma função hash mapeia dados de um tamanho arbitrário (uma cadeia de caracteres de texto, por exemplo) para um número com um intervalo fixo. O hashing pode ser uma maneira rápida e eficiente de vetorizar recursos. Um exemplo notável de hashing no aprendizado de máquina é a filtragem de spam de email, em que cada palavra no email tem hash aplicado e é adicionada a um grande vetor de recursos, em vez de manter um dicionário de palavras conhecidas. O uso de hashing dessa maneira evita o problema de evasão da filtragem de spam malicioso pelo uso de palavras que não estão no dicionário.

O ML.NET fornece transformação de hash para executar hashing em textos, datas e dados numéricos. Assim como o mapeamento de chave de valor, as saídas da transformação de hash são tipos de chave.

Trabalhar com os dados de texto

Assim como os dados categóricos, os dados de texto precisam ser transformados em recursos numéricos antes de serem usados para compilar um modelo de machine learning. Acesse a página de transformações para uma lista mais detalhada e uma descrição de transformações de texto.

Usando dados, como os dados abaixo, que foram carregados em um IDataView:

ReviewData[] reviews = new ReviewData[]
{
    new ReviewData
    {
        Description="This is a good product",
        Rating=4.7f
    },
    new ReviewData
    {
        Description="This is a bad product",
        Rating=2.3f
    }
};

O ML.NET fornece a transformação FeaturizeText que utiliza o valor da cadeia de caracteres de um texto e cria um conjunto de recursos com base nesse texto, aplicando uma série de transformações individuais.

// Define text transform estimator
var textEstimator  = mlContext.Transforms.Text.FeaturizeText("Description");

// Fit data to estimator
// Fitting generates a transformer that applies the operations of defined by estimator
ITransformer textTransformer = textEstimator.Fit(data);

// Transform data
IDataView transformedData = textTransformer.Transform(data);

A transformação resultante converte os valores de texto na coluna Description em um vetor numérico semelhante à seguinte saída:

[ 0.2041241, 0.2041241, 0.2041241, 0.4082483, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0, 0, 0, 0, 0.4472136, 0.4472136, 0.4472136, 0.4472136, 0.4472136, 0 ]

As transformações que compõem FeaturizeText também podem ser aplicadas individualmente para um maior controle de granularidade sobre a geração de recursos.

// Define text transform estimator
var textEstimator = mlContext.Transforms.Text.NormalizeText("Description")
    .Append(mlContext.Transforms.Text.TokenizeIntoWords("Description"))
    .Append(mlContext.Transforms.Text.RemoveDefaultStopWords("Description"))
    .Append(mlContext.Transforms.Conversion.MapValueToKey("Description"))
    .Append(mlContext.Transforms.Text.ProduceNgrams("Description"))
    .Append(mlContext.Transforms.NormalizeLpNorm("Description"));

textEstimator contém um subconjunto de operações realizadas pelo método FeaturizeText. O benefício de um pipeline mais complexo é a visibilidade e o controle sobre as transformações aplicadas aos dados.

Usando a primeira entrada como um exemplo, a seguir está uma descrição detalhada dos resultados produzidos pelas etapas de transformação definidas pelo textEstimator:

Texto original: este é um bom produto

Transformação Descrição Resultado
1. NormalizeText Converte todas as letras em minúsculas por padrão este é um bom produto
2. TokenizeWords Divide a cadeia de caracteres em palavras individuais ["este", "é", "um","bom","produto"]
3. RemoveDefaultStopWords Remove palavras irrelevantes, como está e um. ["bom","produto"]
4. MapValueToKey Mapeia os valores para chaves (categorias) com base nos dados de entrada [1,2]
5. ProduceNGrams Transforma o texto em sequência de palavras consecutivas [1,1,1,0,0]
6. NormalizeLpNorm Escala as entradas por sua lp-norm [ 0.577350529, 0.577350529, 0.577350529, 0, 0 ]