1. Introduzione
In questo codelab, addestrerai un modello per fare previsioni partendo da dati numerici che descrivono un insieme di auto.
Questo esercizio mostrerà i passaggi comuni per l'addestramento di molti tipi diversi di modelli, ma utilizzerà un set di dati ridotto e un modello semplice (superficie). L'obiettivo principale è aiutarti ad acquisire familiarità con la terminologia, i concetti e la sintassi di base relativi all'addestramento dei modelli con TensorFlow.js e fungere da punto di riferimento per ulteriori approfondimenti e approfondimenti.
Poiché stiamo addestrando un modello per prevedere i numeri continui, questa attività viene a volte definita attività di regressione. Addestreremo il modello mostrandogli molti esempi di input insieme all'output corretto. In questo caso si parla di apprendimento supervisionato.
Cosa creerai
Creerai una pagina web che utilizza TensorFlow.js per addestrare un modello nel browser. Dato "Cavalli della potenza" per un'auto, il modello imparerà a prevedere "Miglia per gallone" (MPG).
Per farlo:
- Carica i dati e preparali per l'addestramento.
- Definire l'architettura del modello.
- Addestra il modello e monitorane le prestazioni durante l'addestramento.
- Valuta il modello addestrato facendo alcune previsioni.
Cosa imparerai a fare
- Best practice per la preparazione dei dati per il machine learning, tra cui shuffling e normalizzazione.
- Sintassi TensorFlow.js per creare modelli utilizzando l'API tf.layers.
- Come monitorare l'addestramento nel browser utilizzando la libreria tfjs-vis.
Che cosa ti serve
- Una versione recente di Chrome o di un altro browser moderno.
- Un editor di testo in esecuzione localmente sul tuo computer o sul web tramite un editor di testo come Codepen o Glitch.
- Conoscenza di HTML, CSS, JavaScript e Chrome DevTools (o gli strumenti di sviluppo del browser che preferisci).
- Una comprensione concettuale di alto livello delle reti neurali. Se hai bisogno di una presentazione o di un ripasso, guarda questo video di 3blue1brown o questo video sul deep learning in JavaScript di Ashi Krishnan.
2. Configurazione
Crea una pagina HTML e includi il codice JavaScript
Copia il seguente codice in un file HTML denominato
index.html
<!DOCTYPE html>
<html>
<head>
<title>TensorFlow.js Tutorial</title>
<!-- Import TensorFlow.js -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.0.0/dist/tf.min.js"></script>
<!-- Import tfjs-vis -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis@1.0.2/dist/tfjs-vis.umd.min.js"></script>
</head>
<body>
<!-- Import the main script file -->
<script src="script.js"></script>
</body>
</html>
Crea il file JavaScript per il codice.
- Nella stessa cartella del file HTML indicato sopra, crea un file denominato script.js e inserisci al suo interno il codice seguente.
console.log('Hello TensorFlow');
Mettiti alla prova
Ora che hai creato i file HTML e JavaScript, provali. Apri il file index.html nel browser e apri la console devtools.
Se tutto funziona correttamente, dovrebbero essere create e disponibili due variabili globali nella console DevTools:
tf
è un riferimento alla libreria TensorFlow.jstfvis
è un riferimento alla libreria tfjs-vis
Apri gli strumenti per sviluppatori del browser. Dovresti vedere il messaggio Hello TensorFlow
nell'output della console. In questo caso, puoi andare al passaggio successivo.
3. Carica, formatta e visualizza i dati di input
Come primo passaggio, devi caricare, formattare e visualizzare i dati su cui vogliamo addestrare il modello.
Caricheremo le "auto" da un file JSON che abbiamo ospitato per te. Contiene molte caratteristiche diverse per ogni auto. Per questo tutorial, vogliamo estrarre solo dati relativi a potenza e miglia per gallone.
Aggiungi il seguente codice al tuo
script.js
file
/**
* Get the car data reduced to just the variables we are interested
* and cleaned of missing data.
*/
async function getData() {
const carsDataResponse = await fetch('https://storage.googleapis.com/tfjs-tutorials/carsData.json');
const carsData = await carsDataResponse.json();
const cleaned = carsData.map(car => ({
mpg: car.Miles_per_Gallon,
horsepower: car.Horsepower,
}))
.filter(car => (car.mpg != null && car.horsepower != null));
return cleaned;
}
Verranno rimosse anche tutte le voci per cui non sono definite miglia per gallone o potenza in cavalli. Tracciamo anche questi dati in un grafico a dispersione per vedere come appaiono.
Aggiungi il seguente codice in fondo al tuo
script.js
file.
async function run() {
// Load and plot the original input data that we are going to train on.
const data = await getData();
const values = data.map(d => ({
x: d.horsepower,
y: d.mpg,
}));
tfvis.render.scatterplot(
{name: 'Horsepower v MPG'},
{values},
{
xLabel: 'Horsepower',
yLabel: 'MPG',
height: 300
}
);
// More code will be added below
}
document.addEventListener('DOMContentLoaded', run);
Quando aggiorni la pagina. Sul lato sinistro della pagina dovresti vedere un riquadro con un grafico a dispersione dei dati. L'annuncio dovrebbe avere un aspetto simile a questo.
Questo riquadro è noto come "visore" e viene fornito da tfjs-vis. Consente di mostrare facilmente le visualizzazioni.
In genere, quando si lavora con i dati, è una buona idea trovare modi per esaminarli e pulirli se necessario. In questo caso, abbiamo dovuto rimuovere da carsData
alcune voci che non contenevano tutti i campi obbligatori. La visualizzazione dei dati può darci un'idea dell'esistenza di una struttura dei dati che il modello può apprendere.
Dal grafico sopra si può vedere che c'è una correlazione negativa tra potenza e MPG, cioè man mano che la potenza aumenta, le auto generalmente ottengono meno miglia per gallone.
Concettualizzare l'attività
I nostri dati di input ora avranno il seguente aspetto.
...
{
"mpg":15,
"horsepower":165,
},
{
"mpg":18,
"horsepower":150,
},
{
"mpg":16,
"horsepower":150,
},
...
Il nostro obiettivo è addestrare un modello che preveda un numero, cavalli vapore e impari a prevedere un numero, miglia per gallone. Ricorda questa mappatura uno a uno, poiché sarà importante per la prossima sezione.
Forniremo questi esempi, potenza e MPG, a una rete neurale che apprende da questi esempi una formula (o funzione) per prevedere il consumo di energia (MPG) dato la potenza dei cavalli. Questo apprendimento basato su esempi per i quali abbiamo le risposte corrette è chiamato apprendimento supervisionato.
4. Definire l'architettura del modello
In questa sezione scriveremo il codice per descrivere l'architettura del modello. L'architettura del modello è semplicemente un modo sofisticato per dire "quali funzioni verranno eseguite dal modello durante l'esecuzione" o, in alternativa, "quale algoritmo utilizzerà il modello per calcolare le risposte".
I modelli di ML sono algoritmi che prendono un input e producono un output. Quando si utilizzano le reti neurali, l'algoritmo è un insieme di strati di neuroni con "ponderazioni" (numeri) che ne regolano l'output. Il processo di addestramento apprende i valori ideali per quelle ponderazioni.
Aggiungi la seguente funzione al tuo
script.js
per definire l'architettura del modello.
function createModel() {
// Create a sequential model
const model = tf.sequential();
// Add a single input layer
model.add(tf.layers.dense({inputShape: [1], units: 1, useBias: true}));
// Add an output layer
model.add(tf.layers.dense({units: 1, useBias: true}));
return model;
}
Questo è uno dei modelli più semplici che possiamo definire in tensorflow.js. Scomponiamo un po' ogni riga.
Creare un'istanza del modello
const model = tf.sequential();
Viene così creata un'istanza di un oggetto tf.Model
. Questo modello è sequential
perché i suoi input passano direttamente al suo output. Altri tipi di modelli possono avere rami o anche più input e output, ma in molti casi i modelli saranno sequenziali. I modelli sequenziali hanno inoltre un'API più facile da usare.
Aggiungi livelli
model.add(tf.layers.dense({inputShape: [1], units: 1, useBias: true}));
Viene così aggiunto un livello di input alla nostra rete, che è automaticamente connessa a uno strato dense
con un'unità nascosta. Uno strato dense
è un tipo di strato che moltiplica i propri input per una matrice (chiamata ponderazioni) e poi aggiunge al risultato un numero (detto bias). Poiché questo è il primo livello della rete, dobbiamo definire il nostro inputShape
. Il valore inputShape
è [1]
perché il nostro input è il numero 1
(la potenza di una determinata auto).
units
imposta le dimensioni della matrice dei pesi nel livello. Impostandolo su 1, indichiamo che ci sarà 1 peso per ciascuna delle caratteristiche di input dei dati.
model.add(tf.layers.dense({units: 1}));
Il codice riportato sopra crea il nostro livello di output. Abbiamo impostato units
su 1
perché vogliamo restituire 1
numero.
Crea un'istanza
Aggiungi il seguente codice al
run
che abbiamo definito in precedenza.
// Create the model
const model = createModel();
tfvis.show.modelSummary({name: 'Model Summary'}, model);
Verrà creata un'istanza del modello e verrà visualizzato un riepilogo dei livelli sulla pagina web.
5. Prepara i dati per l'addestramento
Per sfruttare i vantaggi prestazionali di TensorFlow.js che rendono pratico l'addestramento dei modelli di machine learning, dobbiamo convertire i dati in tensori. Eseguiremo inoltre varie trasformazioni sui nostri dati, che rappresentano delle best practice, vale a dire lo shuffling e la normalizzazione.
Aggiungi il seguente codice al tuo
script.js
file
/**
* Convert the input data to tensors that we can use for machine
* learning. We will also do the important best practices of _shuffling_
* the data and _normalizing_ the data
* MPG on the y-axis.
*/
function convertToTensor(data) {
// Wrapping these calculations in a tidy will dispose any
// intermediate tensors.
return tf.tidy(() => {
// Step 1. Shuffle the data
tf.util.shuffle(data);
// Step 2. Convert data to Tensor
const inputs = data.map(d => d.horsepower)
const labels = data.map(d => d.mpg);
const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]);
const labelTensor = tf.tensor2d(labels, [labels.length, 1]);
//Step 3. Normalize the data to the range 0 - 1 using min-max scaling
const inputMax = inputTensor.max();
const inputMin = inputTensor.min();
const labelMax = labelTensor.max();
const labelMin = labelTensor.min();
const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));
const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin));
return {
inputs: normalizedInputs,
labels: normalizedLabels,
// Return the min/max bounds so we can use them later.
inputMax,
inputMin,
labelMax,
labelMin,
}
});
}
Analizziamo cosa sta succedendo qui.
Riproduzione casuale dei dati
// Step 1. Shuffle the data
tf.util.shuffle(data);
Qui mettiamo in modo casuale l'ordine degli esempi da inserire nell'algoritmo di addestramento. Lo shuffling è importante perché in genere durante l'addestramento il set di dati viene suddiviso in sottoinsiemi più piccoli, chiamati batch, su cui viene addestrato il modello. Lo shuffling consente a ogni batch di avere una varietà di dati da tutta la distribuzione dei dati. In questo modo, aiutiamo il modello a:
- Non apprendere informazioni che dipendono puramente dall'ordine in cui sono stati inseriti i dati.
- Non essere sensibile alla struttura nei sottogruppi (ad esempio, se vede auto ad alta potenza solo per la prima metà dell'addestramento, potrebbe apprendere una relazione che non si applica al resto del set di dati).
Converti in tensori
// Step 2. Convert data to Tensor
const inputs = data.map(d => d.horsepower)
const labels = data.map(d => d.mpg);
const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]);
const labelTensor = tf.tensor2d(labels, [labels.length, 1]);
Qui creiamo due array, uno per gli esempi di input (le voci di potenza) e un altro per i valori di output veri (che sono note come etichette nel machine learning).
Quindi convertiamo ogni array di dati in un tensore 2D. Il tensore avrà la forma di [num_examples, num_features_per_example]
. Qui abbiamo inputs.length
esempi e ogni esempio ha 1
caratteristica di input (la potenza).
Normalizza i dati
//Step 3. Normalize the data to the range 0 - 1 using min-max scaling
const inputMax = inputTensor.max();
const inputMin = inputTensor.min();
const labelMax = labelTensor.max();
const labelMin = labelTensor.min();
const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));
const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin));
Ora seguiamo un'altra best practice per l'addestramento del machine learning. Normalizziamo i dati. Qui normalizziamo i dati nell'intervallo numerico 0-1
utilizzando la scalabilità min-max. La normalizzazione è importante perché i componenti interni di molti modelli di machine learning che creerai con tensorflow.js sono progettati per funzionare con numeri non troppo grandi. Intervalli comuni per normalizzare i dati per includere 0 to 1
o -1 to 1
. Avrai più successo nell'addestramento dei tuoi modelli se prendi l'abitudine di normalizzare i dati entro un intervallo ragionevole.
Restituisci i dati e i limiti di normalizzazione
return {
inputs: normalizedInputs,
labels: normalizedLabels,
// Return the min/max bounds so we can use them later.
inputMax,
inputMin,
labelMax,
labelMin,
}
Vogliamo mantenere i valori utilizzati per la normalizzazione durante l'addestramento, in modo da poter denormalizzare gli output per riportarli alla scala originale e per consentirci di normalizzare i dati di input futuri allo stesso modo.
6. Addestra il modello
Dopo aver creato l'istanza del modello e aver rappresentato i dati come tensori, abbiamo a disposizione tutto ciò per avviare il processo di addestramento.
Copia la seguente funzione nel tuo
script.js
file.
async function trainModel(model, inputs, labels) {
// Prepare the model for training.
model.compile({
optimizer: tf.train.adam(),
loss: tf.losses.meanSquaredError,
metrics: ['mse'],
});
const batchSize = 32;
const epochs = 50;
return await model.fit(inputs, labels, {
batchSize,
epochs,
shuffle: true,
callbacks: tfvis.show.fitCallbacks(
{ name: 'Training Performance' },
['loss', 'mse'],
{ height: 200, callbacks: ['onEpochEnd'] }
)
});
}
Analizziamo tutti gli aspetti nel dettaglio.
Preparati per la formazione
// Prepare the model for training.
model.compile({
optimizer: tf.train.adam(),
loss: tf.losses.meanSquaredError,
metrics: ['mse'],
});
Dobbiamo "compilare" il modello prima di addestrarlo. Per farlo, dobbiamo specificare alcune cose molto importanti:
optimizer
: si tratta dell'algoritmo che gestirà gli aggiornamenti del modello per quanto riguarda gli esempi. In TensorFlow.js sono disponibili molti ottimizzatori. Qui abbiamo scelto l'ottimizzatore di Adam perché è abbastanza efficace nella pratica e non richiede alcuna configurazione.loss
: questa è una funzione che comunica al modello il suo rendimento nell'apprendimento di ogni batch (sottoinsiemi di dati) mostrato. In questo caso utilizziamomeanSquaredError
per confrontare le previsioni fatte dal modello con i valori veri.
const batchSize = 32;
const epochs = 50;
Poi scegliamo un valore batchSize e alcune epoche:
batchSize
si riferisce alla dimensione dei sottoinsiemi di dati che il modello vedrà a ogni iterazione dell'addestramento. Le dimensioni comuni dei batch tendono a essere comprese tra 32 e 512. Non esiste una dimensione del batch ideale per tutti i problemi e non rientra nell'ambito di questo tutorial descrivere le motivazioni matematiche di vari batch di dimensioni.epochs
si riferisce al numero di volte in cui il modello esamina l'intero set di dati da te fornito. Qui useremo 50 iterazioni attraverso il set di dati.
Avvia il giro del treno
return await model.fit(inputs, labels, {
batchSize,
epochs,
callbacks: tfvis.show.fitCallbacks(
{ name: 'Training Performance' },
['loss', 'mse'],
{ height: 200, callbacks: ['onEpochEnd'] }
)
});
model.fit
è la funzione che chiamiamo per avviare il loop di addestramento. Si tratta di una funzione asincrona, quindi restituiamo la promessa che ci offre, in modo che il chiamante possa determinare quando l'addestramento è completato.
Per monitorare l'avanzamento dell'addestramento, passiamo alcuni callback a model.fit
. Usiamo tfvis.show.fitCallbacks
per generare funzioni che tracciano i grafici per la "perdita" e "mse" specificata in precedenza.
Riassumi tutto
Ora dobbiamo chiamare le funzioni che abbiamo definito dalla funzione run
.
Aggiungi il seguente codice in fondo al tuo
run
funzione.
// Convert the data to a form we can use for training.
const tensorData = convertToTensor(data);
const {inputs, labels} = tensorData;
// Train the model
await trainModel(model, inputs, labels);
console.log('Done Training');
Dopo aver aggiornato la pagina, dopo qualche secondo dovresti vedere i seguenti grafici che si aggiornano.
Vengono creati dai callback che abbiamo creato in precedenza. Visualizzano la perdita e l'MSE, calcolati in media sull'intero set di dati, alla fine di ogni epoca.
Durante l'addestramento di un modello, vogliamo vedere che la perdita diminuisce. In questo caso, poiché la nostra metrica è una misura dell'errore, vogliamo che anche la metrica scenda.
7. Fai previsioni
Ora che il modello è addestrato, occorre fare alcune previsioni. Valutiamo il modello vedendo cosa prevede per un intervallo uniforme di numeri da bassa ad alta potenza.
Aggiungi la seguente funzione al file script.js
function testModel(model, inputData, normalizationData) {
const {inputMax, inputMin, labelMin, labelMax} = normalizationData;
// Generate predictions for a uniform range of numbers between 0 and 1;
// We un-normalize the data by doing the inverse of the min-max scaling
// that we did earlier.
const [xs, preds] = tf.tidy(() => {
const xsNorm = tf.linspace(0, 1, 100);
const predictions = model.predict(xsNorm.reshape([100, 1]));
const unNormXs = xsNorm
.mul(inputMax.sub(inputMin))
.add(inputMin);
const unNormPreds = predictions
.mul(labelMax.sub(labelMin))
.add(labelMin);
// Un-normalize the data
return [unNormXs.dataSync(), unNormPreds.dataSync()];
});
const predictedPoints = Array.from(xs).map((val, i) => {
return {x: val, y: preds[i]}
});
const originalPoints = inputData.map(d => ({
x: d.horsepower, y: d.mpg,
}));
tfvis.render.scatterplot(
{name: 'Model Predictions vs Original Data'},
{values: [originalPoints, predictedPoints], series: ['original', 'predicted']},
{
xLabel: 'Horsepower',
yLabel: 'MPG',
height: 300
}
);
}
Alcuni aspetti da notare nella funzione riportata sopra.
const xsNorm = tf.linspace(0, 1, 100);
const predictions = model.predict(xsNorm.reshape([100, 1]));
Generiamo 100 nuovi "esempi" da fornire al modello. Model.predict è il modo in cui inserisci questi esempi nel modello. Tieni presente che devono avere una forma simile ([num_examples, num_features_per_example]
) a quella dell'addestramento.
// Un-normalize the data
const unNormXs = xsNorm
.mul(inputMax.sub(inputMin))
.add(inputMin);
const unNormPreds = predictions
.mul(labelMax.sub(labelMin))
.add(labelMin);
Per riportare i dati all'intervallo originale (anziché 0-1), utilizziamo i valori calcolati durante la normalizzazione, ma invertiamo semplicemente le operazioni.
return [unNormXs.dataSync(), unNormPreds.dataSync()];
.dataSync()
è un metodo che possiamo utilizzare per ottenere un typedarray
dei valori archiviati in un tensore. Ciò ci consente di elaborare questi valori in JavaScript. Si tratta di una versione sincrona del metodo .data()
, generalmente preferibile.
Infine, utilizziamo tfjs-vis per tracciare i dati originali e le previsioni del modello.
Aggiungi il seguente codice al tuo
run
funzione.
// Make some predictions using the model and compare them to the
// original data
testModel(model, data, tensorData);
Aggiorna la pagina. Al termine dell'addestramento del modello, dovresti vedere una schermata simile alla seguente.
Complimenti! Hai appena addestrato un semplice modello di machine learning. Attualmente esegue quella che è nota come regressione lineare, che cerca di adattare una linea alla tendenza presente nei dati di input.
8. Concetti principali
I passaggi per addestrare un modello di machine learning includono:
Formula la tua attività:
- È un problema di regressione o di classificazione?
- È possibile farlo con l'apprendimento supervisionato o non supervisionato?
- Qual è la forma dei dati di input? Come dovrebbero essere i dati di output?
Prepara i dati:
- Pulisci i tuoi dati e, se possibile, ispezionali manualmente per verificare la presenza di pattern
- Distribuisci i dati in modo casuale prima di utilizzarli per l'addestramento
- Normalizza i dati entro un intervallo ragionevole per la rete neurale. Di solito 0-1 o -1-1 sono buoni intervalli per i dati numerici.
- Converti i dati in tensori
Crea ed esegui il modello:
- Definisci il tuo modello utilizzando
tf.sequential
otf.model
e poi aggiungi strati utilizzandotf.layers.*
- Scegli un ottimizzatore ( adam di solito è adatto) e parametri come la dimensione del batch e il numero di epoche.
- Scegli una funzione di perdita appropriata per il problema e una metrica di accuratezza per valutare i progressi.
meanSquaredError
è una funzione di perdita comune per problemi di regressione. - Monitora l'addestramento per capire se la perdita sta diminuendo
valuta il modello
- Scegli una metrica di valutazione per il tuo modello che puoi monitorare durante l'addestramento. Dopo l'addestramento, prova a fare alcune previsioni di test per avere un'idea della qualità delle previsioni.
9. Vantaggi aggiuntivi: cose da provare
- Sperimenta la modifica del numero di epoche. Di quante epoche hai bisogno prima che il grafico si appiattisca?
- Prova ad aumentare il numero di unità nello strato nascosto.
- Prova ad aggiungere altri strati nascosti tra il primo strato nascosto e il livello di output finale. Il codice degli strati aggiuntivi dovrebbe avere un aspetto simile al seguente.
model.add(tf.layers.dense({units: 50, activation: 'sigmoid'}));
La novità più importante di questi strati nascosti è che introducono una funzione di attivazione non lineare, in questo caso l'attivazione sigmoid. Per scoprire di più sulle funzioni di attivazione, consulta questo articolo.
Vedi se riesci a far sì che il modello produca un output come nell'immagine seguente.