Preparare i dati per la creazione di un modello

Informazioni su come usare ML.NET per preparare i dati per un'ulteriore elaborazione o creazione di un modello.

I dati sono spesso sparsi e non puliti. Gli algoritmi di Machine Learning di ML.NET prevedono input o caratteristiche in un singolo vettore numerico. Analogamente, il valore da stimare (etichetta), soprattutto quando si tratta di dati categorici, deve essere codificato. Uno degli obiettivi della preparazione dei dati, pertanto, è conferire ai dati il formato previsto dagli algoritmi di ML.NET.

Suddividere i dati in set di training e test

Nella sezione seguente vengono descritti i problemi comuni durante il training di un modello noto come overfitting e underfitting. La suddivisione dei dati e la convalida dei modelli usando un set risolto consente di identificare e attenuare questi problemi.

Overfitting e underfitting

L'overfitting e l'underfitting sono i due problemi più comuni riscontrati durante il training di un modello. L'underfitting indica che il formatore selezionato non è in grado di adattarsi al set di dati di training e in genere comporta una perdita elevata durante il training e un punteggio/metrica basso nel set di dati di test. Per risolvere questo problema, è necessario selezionare un modello più potente o eseguire altre attività di progettazione delle funzionalità. L'overfitting è l'opposto, che si verifica quando il modello apprende i dati di training troppo bene. Ciò comporta in genere una metrica a bassa perdita durante il training, ma una perdita elevata nel set di dati di test.

Una buona analogia per questi concetti è lo studio per un esame. Si supponga di conoscere le domande e le risposte in anticipo. Dopo aver studiato, si fa il test e si ottiene un punteggio perfetto. Grande novità! Tuttavia, quando si ripropone l'esame con le domande riordinate e con una formulazione leggermente diversa si ottiene un punteggio inferiore. Ciò suggerisce che si sono memorizzate le risposte e di non si sono effettivamente appresi i concetti su cui si è stati esaminati. Questo è un esempio di overfitting. L'underfitting è l'opposto, dove i materiali di studio forniti non rappresentano in modo accurato ciò che viene valutato per l'esame. Di conseguenza, si ricorre a indovinare le risposte perché non si dispone di conoscenze sufficienti per rispondere correttamente.

Dividere i dati

Prendere i dati di input seguenti e caricarli in un IDataView denominato 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
    }
};

Per suddividere i dati in set di training/test, usare il metodo TrainTestSplit(IDataView, Double, String, Nullable<Int32>).

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

Il parametro testFraction viene usato per accettare lo 0,2 o il 20% del set di dati per il test. L'80% rimanente viene utilizzato per il training.

Il risultato è DataOperationsCatalog.TrainTestData costituito da due IDataView a cui è possibile accedere tramite TrainSet e TestSet.

Filtro dei dati

In alcuni casi, non tutti i dati in un set di dati sono rilevanti per l'analisi. Un modo per rimuovere i dati irrilevanti da un set di dati consiste nell'applicare dei filtri. La classe DataOperationsCatalog contiene un set di operazioni di filtro che esaminano un'interfaccia IDataView che contiene tutti i dati e restituiscono un'interfaccia IDataView contenente solo i punti dati di interesse. È importante notare che, poiché non sono di tipo IEstimator e neppure ITransformer come quelle disponibili nella classe TransformsCatalog, le operazioni di filtro non possono essere incluse in una pipeline di preparazione dei dati EstimatorChain o TransformerChain.

Prendere i dati di input seguenti e caricarli in un IDataView denominato data:

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

Per filtrare i dati in base al valore di una colonna, usare il metodo FilterRowsByColumn.

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

L'esempio precedente considera le righe del set di dati con un prezzo compreso tra 200000 e 1000000. Il risultato che si otterrebbe applicando questo filtro sarebbe la restituzione solo delle ultime due righe di dati e l'esclusione della prima riga perché il prezzo è 100000 e pertanto non rientra nell'intervallo specificato.

Sostituire valori mancanti

I valori mancanti sono un evento comune nei set di dati. Un modo per gestire i valori mancanti consiste nel sostituirli con il valore predefinito, qualora esistesse per il tipo specificato, oppure con un altro valore significativo, ad esempio il valore medio dei dati.

Prendere i dati di input seguenti e caricarli in un IDataView denominato data:

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

Si noti che l'ultimo elemento nell'elenco presenta un valore mancante per Price. Per sostituire i valori mancanti nella colonna Price, inserire valore mancante usando il metodo ReplaceMissingValues.

Importante

ReplaceMissingValue funziona solo con dati numerici.

// 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);

ML.NET supporta varie modalità sostituzione. L'esempio precedente usa la modalità sostituzione Mean che inserisce il valore medio della colonna per sopperire al valore mancante. Il risultato della sostituzione completa la proprietà Price dell'ultimo elemento di dati con il valore 200.000 perché è la media fra 100.000 e 300.000.

Usare i normalizzatori

La normalizzazione è una tecnica di pre-elaborazione dei dati usata per ridimensionare le funzionalità nello stesso intervallo, in genere compreso tra 0 e 1, in modo che possano essere elaborate in modo più accurato da un algoritmo di Machine Learning. Gli intervalli di età e reddito, ad esempio, variano in modo significativo perché l'età è generalmente compresa nell'intervallo da 0 a 100 e il reddito nell'intervallo da 0 a diverse migliaia. Visitare la pagina Trasformazioni dati per un elenco e una descrizione più dettagliati delle trasformazioni di normalizzazione.

Normalizzazione min-max

Prendere i dati di input seguenti e caricarli in un IDataView denominato data:

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

È possibile applicare la normalizzazione alle colonne con valori numerici singoli, nonché vettori. Normalizzare i dati nella colonna Price usando la normalizzazione min-max con il metodo 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);

I valori di prezzo originali [200000,100000] vengono convertiti in [ 1, 0.5 ] usando la formula di normalizzazione MinMax che genera valori di output nell'intervallo 0-1.

Binning

Il processo di binning converte valori continui in una rappresentazione discreta dell'input. Si supponga, ad esempio, che una delle caratteristiche sia l'età. Invece di usare il valore effettivo dell'età, per tale valore il processo di binning crea intervalli. 0-18 potrebbe essere un intervallo, 19-35 potrebbe essere un altro intervallo e così via.

Prendere i dati di input seguenti e caricarli in un IDataView denominato data:

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

Normalizzare i dati in contenitori usando il metodo NormalizeBinning. Il parametro maximumBinCount consente di specificare il numero di contenitori necessari per classificare i dati. In questo esempio i dati verranno inseriti in due contenitori.

// 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);

Il risultato del processo di binning è la creazione di limiti per i contenitori di [0,200000,Infinity]. Di conseguenza, i contenitori risultanti sono [0,1,1] perché la prima osservazione prende in considerazione i valori tra 0 e 200000 e gli altri sono maggiori di 200000 ma inferiori a infinito.

Usare dati di categorie

Uno dei tipi di dati più comuni è costituito da dati categorici. I dati categorici hanno un numero finito di categorie. Ad esempio, gli stati degli Stati Uniti o un elenco dei tipi di animali trovati in un set di immagini. Indipendentemente dal fatto che i dati categorici siano caratteristiche o etichette, devono essere mappati a un valore numerico in modo che possano essere usati per generare un modello di Machine Learning. Esistono diversi modi per usare i dati categorici in ML.NET, a seconda del problema che si sta risolvendo.

Mapping dei valori chiave

In ML.NET una chiave è un valore intero che rappresenta una categoria. Il mapping dei valori chiave viene spesso usato per eseguire il mapping delle etichette di stringa in valori interi univoci per il training, per poi tornare ai valori stringa quando il modello viene usato per eseguire una stima.

Le trasformazioni usate per eseguire il mapping dei valori della chiave sono MapValueToKey e MapKeyToValue.

MapValueToKey aggiunge un dizionario di mapping nel modello, in modo che MapKeyToValue possa eseguire la trasformazione inversa durante l'esecuzione di una stima.

Codifica ad accesso frequente

Una codifica ad accesso frequente accetta un set finito di valori e ne esegue il mapping a numeri interi la cui rappresentazione binaria ha un singolo valore 1 in posizioni univoche nella stringa. Una codifica ad accesso frequente può essere la scelta migliore se non esiste un ordinamento implicito dei dati categorici. Nella tabella seguente viene illustrato un esempio con i codici postali come valori non elaborati.

Valore grezzo Un valore codificato ad accesso frequente
98052 00...01
98100 00...10
... ...
98109 10...00

La trasformazione per convertire i dati categorici in numeri con codifica ad accesso frequente è OneHotEncoding.

Hashing

L'hashing è un altro modo per convertire i dati categorici in numeri. Una funzione hash esegue il mapping dei dati di una dimensione arbitraria (ad esempio una stringa di testo) a un numero con un intervallo fisso. L'hashing può essere un modo rapido ed efficiente in termini di spazio per vettorizzare le funzionalità. Un esempio notevole di hashing in Machine Learning è il filtro della posta indesiderata in cui, invece di mantenere un dizionario di parole note, ogni parola nel messaggio di posta elettronica viene sottoposta ad hashing e aggiunta a un vettore di funzionalità di grandi dimensioni. L'uso dell'hashing in questo modo evita il problema del filtro della posta indesiderata dannoso tramite l'uso di parole che non sono nel dizionario.

ML.NET fornisce la trasformazione Hash per eseguire l'hashing su testo, date e dati numerici. Analogamente al mapping di chiavi valore, gli output della trasformazione hash sono tipi di chiave.

Usare dati di testo

Come i dati categorici, i dati di testo devono essere trasformati in funzionalità numeriche prima di essere usati per creare un modello di Machine Learning. Visitare la pagina Trasformazioni dati per un elenco e una descrizione più dettagliati delle trasformazioni del testo.

Usando dati analoghi ai seguenti caricati in un'interfaccia 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
    }
};

ML.NET fornisce la trasformazione FeaturizeText che accetta il valore stringa di un testo e crea un set di caratteristiche dal testo applicando una serie di singole trasformazioni.

// 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);

La trasformazione risultante converte i valori di testo nella colonna Description in un vettore numerico simile all'output seguente:

[ 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 ]

Le trasformazioni che costituiscono FeaturizeText possono essere applicate singolarmente anche per il controllo granulare più fine sulla generazione delle funzionalità.

// 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 contiene un sottoinsieme di operazioni eseguite tramite il metodo FeaturizeText. Il vantaggio che offre l'uso di una pipeline più complessa è la visibilità e il controllo sulle trasformazioni applicate ai dati.

Usando la prima voce come esempio, la descrizione dettagliata dei risultati generati dai passaggi di trasformazione definiti da textEstimator sarebbe:

Testo originale: questo è un buon prodotto

Trasformazione Descrizione Risultato
1. NormalizeText Converte tutte le lettere in minuscolo per impostazione predefinita this is a good product
2. TokenizeWords Suddivide la stringa in singole parole ["this","is","a","good","product"]
3. RemoveDefaultStopWords Rimuove le parole non significative, ad esempio is e a. ["good","product"]
4. MapValueToKey Esegue il mapping dei valori su chiavi (categorie) in base ai dati di input [1,2]
5. ProduceNGrams Trasforma il testo in una sequenza di parole consecutive [1,1,1,0,0]
6. NormalizeLpNorm Scala gli input in base alla relativa lp-norm [ 0.577350529, 0.577350529, 0.577350529, 0, 0 ]