Introduzione a Datasets ed Estimators di TensorFlow

Pubblicato dal TensorFlow Team
TensorFlow 1.3 introduce due nuove importanti funzionalità.

  • Datasets: un modo completamente nuovo di creare pipeline di input (ossia la lettura dei dati nel tuo programma).
  • Estimators: un metodo ad alto livello per creare modelli TensorFlow. Gli stimatori includono modelli predefiniti per le attività più comuni di machine learning ma possono anche essere utilizzati per creare modelli personalizzati.

Di seguito vedremo come rientrano nell’architettura TensorFlow. Insieme offrono un metodo semplice per creare modelli TensorFlow e alimentarli con dati.

Esempio di modello

Per mostrarti queste funzionalità creeremo un modello corredato di frammenti di codice salienti. Il codice completo è disponibile qui, comprese le istruzioni per ottenere i file di addestramento e di test. È da notare che il codice è stato scritto per dimostrare come Datasets e Estimators operano a livello funzionale e non per ottimizzarne le prestazioni.

Il modello addestrato classifica i fiori di Iris in base a quattro caratteristiche botaniche (lunghezza e larghezza dei sepali, lunghezza e larghezza dei petali). In tal modo durante l’inferenza puoi fornire valori per queste quattro caratteristiche e il modello indovinerà di quale fiore si tratta tra queste belle tre varietà.

Da sinistra a destra: Iris setosa (Radomil, CC BY-SA 3.0), Iris versicolor (Dlanglois, CC BY-SA 3.0) e Iris virginica (Frank Mayfield, CC BY-SA 2.0).

Prepariamo un Deep Neural Network Classifier con la struttura sottostante. Tutti i valori di input e output saranno float32 e la somma dei valori di output sarà pari a 1 (cerchiamo di prevedere la probabilità di ogni tipo di Iris).

Ad esempio, un risultato di output potrebbe essere: 0,05 per Iris Setosa, 0,9 per Iris Versicolor, 0,05 per Iris Virginica, dandoci il 90% di probabilità che sia l’Iris Versicolor.

Bene! Ora che abbiamo definito il modello, pensiamo a come utilizzare Datasets ed Estimators per addestrarlo e fare delle previsioni.

Introduzione a Datasets

Datasets è un nuovo modo per creare pipeline di input nei modelli TensorFlow. Questa API non solo è molto più performante di feed_dict o delle pipeline basate su code, ma è anche più pulita e facile da utilizzare. Anche se Datasets risiede ancora in tf.contrib.data a 1.3, prevediamo di spostarla a core a 1.4, e ora è giunto il momento di provarla.

Ad alto livello, Datasets consiste in alcune classi.

Eccole:

  • Dataset: classe di base contenente metodi per creare e trasformare set di dati. Consente inoltre di inizializzare un set di dati dai dati in memoria o da un generatore Python.
  • TextLineDataset: legge righe dai file di testo.
  • TFRecordDataset: legge record dai file TFRecord.
  • FixedLengthRecordDataset: legge record di dimensioni fisse dai file binari.
  • Iterator: fornisce un metodo per accedere all’elemento di un set di dati alla volta.

Il set di dati

Innanzitutto vediamo il set di dati che useremo per alimentare il nostro modello. Leggeremo i dati da un file CSV in cui ogni riga contiene cinque valori: i quattro valori di input più l’etichetta.

L’etichetta sarà:

  • 0 per Iris Setosa
  • 1 per Iris Versicolor
  • 2 per Iris Virginica

Rappresentazione del set di dati

Per descrivere il nostro set di dati creiamo subito l’elenco delle caratteristiche:


feature_names = [
'SepalLength',
'SepalWidth',
'PetalLength',
'PetalWidth']

Quando addestriamo il nostro modello, abbiamo bisogno di una funzione che legga il file di input e restituisca i dati delle caratteristiche e delle etichette. Estimators richiede di creare una funzione del seguente formato:


def input_fn():
......
return ({ 'SepalLength':[values], ...., 'PetalWidth':[values] },
[IrisFlowerType])

Il valore restituito deve essere una tupla a due elementi strutturata come segue: :

  • Il primo elemento deve essere un dict in cui ogni funzione di input è una chiave e quindi un elenco di valori per il batch di addestramento.
  • Il secondo elemento è un elenco di etichette per il batch di addestramento.

Poiché viene restituito un batch di funzioni di input e di etichette di addestramento, tutti gli elenchi nell'istruzione return dovranno avere lunghezze uguali. Dal punto di vista tecnico ogni volta che abbiamo parlato di "elenco", ci riferivamo a un tensore TensorFlow 1-d.

Per abilitare il semplice riuso di input_fn dobbiamo aggiungere degli argomenti. Ciò consente di creare funzioni di input con impostazioni diverse. Gli argomenti sono piuttosto semplici:

  • file_path: il file da leggere.
  • perform_shuffle: indica se l'ordine del record debba essere randomizzato.
  • repeat_count: il numero di volte per l'iterazione dei record nel set di dati. Ad esempio, se specifichiamo 1, ogni record verrà letto una volta. Se specifichiamo None, l'iterazione proseguirà per sempre.

Ecco come possiamo implementare questa funzione utilizzando l'API Dataset. Successivamente eseguiremo il wrapping in una "funzione di input" adatta al feed del modello del nostro Estimator:


def my_input_fn(file_path, perform_shuffle=False, repeat_count=1):
def decode_csv(line):
parsed_line = tf.decode_csv(line, [[0.], [0.], [0.], [0.], [0]])
label = parsed_line[-1:] # Last element is the label
del parsed_line[-1] # Delete last element
features = parsed_line # Everything (but last element) are the features
d = dict(zip(feature_names, features)), label
return d

dataset = (tf.contrib.data.TextLineDataset(file_path) # Read text file
.skip(1) # Skip header row
.map(decode_csv)) # Transform each elem by applying decode_csv fn
if perform_shuffle:
# Randomizes input using a window of 256 elements (read into memory)
dataset = dataset.shuffle(buffer_size=256)
dataset = dataset.repeat(repeat_count) # Repeats dataset this # times
dataset = dataset.batch(32) # Batch size to use
iterator = dataset.make_one_shot_iterator()
batch_features, batch_labels = iterator.get_next()
return batch_features, batch_labels

Nota quanto segue: :

  • TextLineDataset: l'API Dataset eseguirà gran parte della gestione della memoria se si utilizzano i suoi set di dati basati su file. Ad esempio, puoi leggere file di set di dati molto più grandi della memoria o leggere più file specificando un elenco come argomento.
  • shuffle: legge i record buffer_size, poi li mette in ordine casuale (randomizza).
  • map: chiama la funzione decode_csv con ogni elemento del set di dati come argomento (giacché utilizziamo TextLineDataset, ogni elemento sarà una riga di testo CSV). Quindi applica decode_csv a ognuna delle righe.
  • decode_csv: divide ogni riga in campi fornendo valori predefiniti, se necessario. Poi restituisce un dict con le chiavi di campo e i valori di campo. La funzione di mappatura aggiorna ogni elemento (riga) nel set di dati con il dict.

Questa era l'introduzione a Datasets! Ora divertiamoci a utilizzare la funzione per stampare il primo batch:


next_batch = my_input_fn(FILE, True) # Will return 32 random elements

# Now let's try it out, retrieving and printing one batch of data.
# Although this code looks strange, you don't need to understand
# the details.
with tf.Session() as sess:
first_batch = sess.run(next_batch)
print(first_batch)

# Output
({'SepalLength': array([ 5.4000001, ...], dtype=float32),
'PetalWidth': array([ 0.40000001, ...], dtype=float32),
...
},
[array([[2], ...], dtype=int32) # Labels
)

È tutto ciò che ci serve dall'API Dataset per implementare il nostro modello. Datasets offre molte altre funzionalità perciò ti preghiamo di leggere la parte finale di questo post dove indichiamo ulteriori risorse.

Introduzione a Estimators

Estimators è un'API di alto livello che riduce gran parte del codice della boilerplate che dovevi scrivere in precedenza durante l'addestramento del modello TensorFlow. Estimators è anche molto flessibile e consente di ignorare il comportamento predefinito se hai specifici requisiti per il tuo modello.

Ci sono due modi per creare un modello utilizzando Estimators.

  • Stimatore predefinito: è uno stimatore già pronto che serve a generare un tipo specifico di modello. In questo post usiamo lo stimatore predefinito DNNClassifier.
  • Stimatore (classe base): ti fornisce il controllo totale sulla creazione del modello utilizzando una funzione model_fn ma affronteremo questo argomento in un altro post.

Ecco il diagramma di classe per Estimators.

Ci auguriamo di poter aggiungere altri stimatori predefiniti nelle versioni future.

Come puoi notare, tutti gli stimatori fanno uso di input_fn che fornisce loro i dati di input. Nel nostro caso riuseremo my_input_fn, già definito a questo scopo.

Il seguente codice crea l'istanza dello stimatore che cerca di indovinare la varietà di Iris:


# Create the feature_columns, which specifies the input to our model.
# All our input features are numeric, so use numeric_column for each one.
feature_columns = [tf.feature_column.numeric_column(k) for k in feature_names]

# Create a deep neural network regression classifier.
# Use the DNNClassifier pre-made estimator
classifier = tf.estimator.DNNClassifier(
feature_columns=feature_columns, # The input features to our model
hidden_units=[10, 10], # Two layers, each with 10 neurons
n_classes=3,
model_dir=PATH) # Path to where checkpoints etc are stored

Abbiamo creato uno stimatore che possiamo iniziare ad addestrare.

Addestramento del modello

L'addestramento avviene utilizzando una singola riga di codice di TensorFlow:


# Train our model, use the previously function my_input_fn
# Input to training is a file with training example
# Stop training after 8 iterations of train data (epochs)
classifier.train(
input_fn=lambda: my_input_fn(FILE_TRAIN, True, 8))

Aspetta un attimo... che cos'è questa roba "lambda: my_input_fn(FILE_TRAIN, True, 8)"? È così che colleghiamo Datasets con Estimators! Estimators ha bisogno di dati per addestrare, valutare e fare previsioni, e usa la input_fn per recuperare i dati. Estimators richiede una input_fn senza argomenti, quindi creiamo una funzione senza argomenti usando lambda che chiama input_fn con gli argomenti desiderati: file_path, shuffle setting, e repeat_count. Nel nostro caso utilizziamo my_input_fn, con i seguenti passaggi:

  • FILE_TRAIN, che è il file dei dati di addestramento.
  • True, che dice allo stimatore di mettere i dati in ordine casuale.
  • 8, che indica lo stimatore e ripete il set di dati 8 volte.

Valutazione del modello addestrato

Ora che abbiamo un modello addestrato, come possiamo valutarne le prestazioni? Per fortuna ogni stimatore è dotato di un metodo evaluate:


# Evaluate our model using the examples contained in FILE_TEST
# Return value will contain evaluation_metrics such as: loss & average_loss
evaluate_result = estimator.evaluate(
input_fn=lambda: my_input_fn(FILE_TEST, False, 4)
print("Evaluation results")
for key in evaluate_result:
print(" {}, was: {}".format(key, evaluate_result[key]))

Nel nostro caso otteniamo una precisione del ~93%. Naturalmente ci sono diversi modi per raggiungere una precisione persino maggiore. Un metodo è eseguire semplicemente il programma più volte. Poiché lo stato del modello rimane invariato (nel model_dir=PATH sopra), il modello migliorerà in base al numero delle iterazioni di addestramento effettuate fino a divenire stazionario. Un altro modo è regolare il numero di livelli nascosti o il numero di nodi in ciascun livello nascosto. Sentiti libero di sperimentare ma tieni presente che quando apporti una modifica, devi rimuovere la directory specificata in model_dir=PATH giacché stai cambiando la struttura del DNNClassifier.

Previsioni basate sul modello addestrato

Fatto! Ora abbiamo un modello addestrato e, se siamo soddisfatti dei risultati della valutazione, possiamo indovinare la varietà di Iris in base all'input. Nel caso dell'addestramento e della valutazione, indoviniamo utilizzando una singola chiamata di funzione:


# Predict the type of some Iris flowers.
# Let's predict the examples in FILE_TEST, repeat only once.
predict_results = classifier.predict(
input_fn=lambda: my_input_fn(FILE_TEST, False, 1))
print("Predictions on test file")
for prediction in predict_results:
# Will print the predicted class, i.e: 0, 1, or 2 if the prediction
# is Iris Sentosa, Vericolor, Virginica, respectively.
print prediction["class_ids"][0]

Previsioni basate sui dati in memoria

Il codice precedente utilizza FILE_TEST per fare previsioni sui dati archiviati in un file, ma come possiamo farle sui dati che si trovano in altre origini come, ad esempio, nella memoria? Come puoi intuire, di fatto questo processo non richiede cambiamenti alla nostra chiamata predict, ma dobbiamo comunque configurare l'API Dataset per utilizzare una struttura di memoria come segue:


# Let create a memory dataset for prediction.
# We've taken the first 3 examples in FILE_TEST.
prediction_input = [[5.9, 3.0, 4.2, 1.5], # -> 1, Iris Versicolor
[6.9, 3.1, 5.4, 2.1], # -> 2, Iris Virginica
[5.1, 3.3, 1.7, 0.5]] # -> 0, Iris Sentosa
def new_input_fn():
def decode(x):
x = tf.split(x, 4) # Need to split into our 4 features
# When predicting, we don't need (or have) any labels
return dict(zip(feature_names, x)) # Then build a dict from them

# The from_tensor_slices function will use a memory structure as input
dataset = tf.contrib.data.Dataset.from_tensor_slices(prediction_input)
dataset = dataset.map(decode)
iterator = dataset.make_one_shot_iterator()
next_feature_batch = iterator.get_next()
return next_feature_batch, None # In prediction, we have no labels

# Predict all our prediction_input
predict_results = classifier.predict(input_fn=new_input_fn)

# Print results
print("Predictions on memory data")
for idx, prediction in enumerate(predict_results):
type = prediction["class_ids"][0] # Get the predicted class (index)
if type == 0:
print("I think: {}, is Iris Sentosa".format(prediction_input[idx]))
elif type == 1:
print("I think: {}, is Iris Versicolor".format(prediction_input[idx]))
else:
print("I think: {}, is Iris Virginica".format(prediction_input[idx])

Dataset.from_tensor_slides() è progettato per piccoli set di dati che rientrano nella memoria. Utilizzando il TextLineDataset come nel caso dell'addestramento e della valutazione, puoi avere file grandi quanto vuoi a patto che la memoria possa gestire il buffer in ordine casuale e le dimensioni dei batch.

In omaggio

Gli stimatori predefiniti come DNNClassifier offrono molti vantaggi, perché oltre a essere facili da usare, forniscono metriche di valutazione integrate e creano riepiloghi visualizzabili su TensorBoard. Per vedere questi report, avvia TensorBoard dalla riga di comando nel seguente modo:


# Replace PATH with the actual path passed as model_dir argument when the
# DNNRegressor estimator was created.
tensorboard --logdir=PATH

I diagrammi che seguono mostrano alcuni dei dati forniti da TensorBoard.

Sintesi

In questo post abbiamo parlato di Datasets ed Estimators. Sono API importanti per definire i flussi di dati di input e creare modelli, quindi vale la pena investire del tempo per conoscerle meglio!

Per ulteriori dettagli consulta le seguenti risorse:

Ma non finisce qui. A breve pubblicheremo altri post sul funzionamento di queste API, quindi rimani sintonizzato!

A presto e buona programmazione con TensorFlow!