Recentemente ho armeggiato con modelli di deep learning in Tensorflow e di conseguenza sono stato introdotto alla gestione dei dati come tensori.
Essendo un ingegnere dei dati che lavora tutto il giorno su tabelle che posso facilmente suddividere, tagliare e visualizzare, non avevo assolutamente alcuna intuizione riguardo al lavoro con i tensori e mi sembrava di imbattermi costantemente negli stessi errori che, soprattutto all'inizio, andavano ben oltre la mia testa.
Tuttavia, l'immersione in profondità mi ha insegnato molto sui tensori e su TensorFlow e volevo consolidare qui questi apprendimenti da utilizzare come riferimento.
Se hai un errore, una soluzione o un suggerimento per il debug preferito, lascia un commento!
Prima di approfondire gli errori stessi, volevo documentare alcuni dei frammenti e dei pezzi di codice leggeri e semplici che ho trovato utili nel debug. (Anche se va detto per motivi legali che ovviamente effettuiamo sempre il debug con funzionalità di debug ufficiali e mai solo con dozzine di dichiarazioni stampate 🙂)
Vedere all'interno dei nostri set di dati Tensorflow
Prima di tutto, guardando i nostri dati reali. Quando stampiamo un Dataframe o SELECT * in SQL, vediamo i dati! Quando stampiamo un set di dati tensoriale vediamo…
<_TensorSliceDataset element_spec=(TensorSpec(shape=(2, 3), dtype=tf.int32, name=None), TensorSpec(shape=(1, 1), dtype=tf.int32, name=None))>
Queste sono tutte informazioni abbastanza utili, ma non ci aiutano a capire cosa sta realmente accadendo nei nostri dati.
Per stampare un singolo tensore all'interno del grafico di esecuzione possiamo sfruttare tf.print. Questo articolo è un meraviglioso approfondimento su tf.print che consiglio vivamente se prevedi di usarlo spesso: Utilizzo di tf.Print() in TensorFlow
Ma quando lavoriamo con i set di dati Tensorflow durante lo sviluppo, a volte abbiamo bisogno di vedere pochi valori alla volta. Per questo possiamo scorrere e stampare singoli dati in questo modo:
# Generate dummy 2D data
np.random.seed(42)
num_samples = 100
num_features = 5
X_data = np.random.rand(num_samples, num_features).astype(np.float32)
y_data = 2 * X_data(:, 0) + 3 * X_data(:, 1) - 1.5 * X_data(:, 2) + 0.5 * X_data(:, 3) + np.random.randn(num_samples)# Turn it into a Tensorflow Dataset
dataset = tf.data.Dataset.from_tensor_slices((X_data, y_data))
# Print the first 10 rows
for i, (features, label) in enumerate(dataset.take(10)):
print(f"Row {i + 1}: Features - {features.numpy()}, Label - {label.numpy()}")
Possiamo anche usare skip per arrivare a un indice specifico:
mini_dataset = dataset.skip(100).take(20)
for i, (features, label) in enumerate(mini_dataset):
print(f"Row {i + 1}: Features - {features.numpy()}, Label - {label.numpy()}")
Conoscere le specifiche dei nostri tensori
Quando lavoriamo con i tensori dobbiamo anche conoscere la loro forma, rango, dimensione e tipo di dati (se parte di quel vocabolario non mi è familiare, come lo era per me inizialmente, non preoccuparti, ci torneremo più tardi articolo). Ad ogni modo, di seguito sono riportate alcune righe di codice per raccogliere queste informazioni:
# Create a sample tensor
sample_tensor = tf.constant(((1, 2, 3), (4, 5, 6)))# Get the size of the tensor (total number of elements)
tensor_size = tf.size(sample_tensor).numpy()
# Get the rank of the tensor
tensor_rank = tf.rank(sample_tensor).numpy()
# Get the shape of the tensor
tensor_shape = sample_tensor.shape
# Get the dimensions of the tensor
tensor_dimensions = sample_tensor.shape.as_list()
# Print the results
print("Tensor Size:", tensor_size)
print("Tensor Rank:", tensor_rank)
print("Tensor Shape:", tensor_shape)
print("Tensor Dimensions:", tensor_dimensions)
Gli output di cui sopra:
Tensor Size: 6
Tensor Rank: 2
Tensor Shape: (2, 3)
Tensor Dimensions: (2, 3)
Aumentare model.summary()
Infine, è sempre utile poter vedere come i dati si muovono attraverso un modello e come cambia la forma negli input e negli output tra i livelli. La fonte di molti errori sarà una mancata corrispondenza tra queste forme di input e output previste e la forma di un dato tensore.
modello.summary() ovviamente porta a termine il lavoro, ma possiamo integrare tali informazioni con il seguente snippet, che aggiunge un po' più di contesto con input e output del modello e del livello:
print("###################Input Shape and Datatype#####################")
(print(i.shape, i.dtype) for i in model.inputs)
print("###################Output Shape and Datatype#####################")
(print(o.shape, o.dtype) for o in model.outputs)
print("###################Layer Input Shape and Datatype#####################")
(print(l.name, l.input, l.dtype) for l in model.layers)
Quindi saltiamo in alcuni errori!
Rango
ValueError: la forma deve essere di rango x ma è di rango y….
Ok, prima di tutto, cos'è un rango? Il rango è solo l'unità di dimensionalità che usiamo per descrivere i tensori. Un tensore di rango 0 è un valore scalare; un tensore di rango uno è un vettore; un rango due è una matrice, e così via per tutte le strutture n dimensionali.
Prendiamo ad esempio un tensore a 5 dimensioni.
rank_5_tensor = tf.constant((((((1, 2), (3, 4)), ((5, 6), (7, 8))), (((9, 10), (11, 12)), ((13, 14), (15, 16)))),
((((17, 18), (19, 20)), ((21, 22), (23, 24))), (((25, 26), (27, 28)), ((29, 30), (31, 32))))))
print("\nRank 5 Tensor:", rank_5_tensor.shape)
Rank 5 Tensor: (2, 2, 2, 2, 2)
Il codice sopra mostra che ciascuna dimensione delle cinque ha una dimensione pari a due. Se volessimo indicizzarlo, potremmo farlo lungo uno qualsiasi di questi assi. Per arrivare all'ultimo elemento, 32, eseguiremmo qualcosa del tipo:
rank_5_tensor.numpy()(1)(1)(1)(1)(1)
IL documentazione ufficiale del tensore ha alcune visualizzazioni davvero utili per renderlo un po' più comprensibile.
Torniamo all'errore: sta solo segnalando che il tensore fornito ha una dimensione diversa da quella prevista per una particolare funzione. Ad esempio, se l'errore dichiara che “La forma deve essere di rango 1 ma è di rango 0…” significa che stiamo fornendo un valore scalare e si aspetta un tensore 1-D.
Prendiamo l'esempio qui sotto dove stiamo cercando di moltiplicare i tensori insieme con il metodo matmul.
import tensorflow as tf
import numpy as np
# Create a TensorFlow dataset with random matrices
num_samples = 5
matrix_size = 3
dataset = tf.data.Dataset.from_tensor_slices(np.random.rand(num_samples, matrix_size, matrix_size))
mul = (1,2,3,4,5,6)# Define a function that uses tf.matmul
def matmul_function(matrix):
return tf.matmul(matrix, mul)
# Apply the matmul_function to the dataset using map
result_dataset = dataset.map(matmul_function)
Se diamo uno sguardo al documentazionematmul si aspetta almeno un tensore di rango 2, quindi moltiplicando la matrice per (1,2,3,4,5,6), che è solo un array, aumenterà questo errore.
ValueError: Shape must be rank 2 but is rank 1 for '{{node MatMul}} = MatMul(T=DT_DOUBLE, transpose_a=false, transpose_b=false)(args_0, MatMul/b)' with input shapes: (3,3), (2).
Un ottimo primo passo per questo errore è immergersi nella documentazione e capire cosa sta cercando la funzione che stai utilizzando (ecco un bell'elenco delle funzioni disponibili sui tensori: raw_ops.
Quindi utilizza il metodo di classificazione per determinare cosa stiamo effettivamente fornendo.
print(tf.rank(mul))
tf.Tensor(1, shape=(), dtype=int32)
Per quanto riguarda le correzioni, tf.reshape è spesso una buona opzione con cui iniziare. Prendiamoci un breve momento per parlare un po' di tf.reshape, poiché sarà un compagno fedele durante il nostro viaggio in Tensorflow: tf.reshape(tensore, forma, nome=Nessuno)
Reshape accetta semplicemente il tensore che vogliamo rimodellare e un altro tensore contenente quella che vogliamo che sia la forma dell'output. Ad esempio, rimodelliamo il nostro input di moltiplicazione:
mul = (1,2,3,4,5,6)
tf.reshape(mul, (3, 2)).numpy()
array(((1, 2),
(3, 4),
(5, 6)), dtype=int32)
La nostra variabile si trasformerà in un tensore (3,2) (3 righe, 2 colonne). Una breve nota, tf.reshape(t, (3, -1)).numpy() produrrà la stessa cosa perché -1 dice a Tensorflow di calcolare la dimensione della dimensione in modo tale che la dimensione totale rimanga costante. Il numero di elementi nel tensore della forma è il rango.
Una volta creato un tensore con il rango corretto, la nostra moltiplicazione funzionerà perfettamente!
Forma
ValueError: l'input del layer non è compatibile con il layer….
Avere una comprensione intuitiva della forma del tensore e del modo in cui interagisce e cambia tra i livelli del modello ha reso la vita con il deep learning molto più semplice
Innanzitutto, togliamo di mezzo i termini di base: la forma di un tensore si riferisce al numero di elementi lungo ciascuna dimensione, o asse del tensore. Ad esempio, un tensore 2D con 3 righe e 4 colonne ha una forma (3, 4).
Quindi cosa può andare storto con la forma? Sono felice che tu abbia chiesto un bel po' di cose!
Innanzitutto la forma e il rango dei dati di addestramento devono corrispondere alla forma di input prevista dal livello di input. Diamo un'occhiata ad un esempio, una CNN di base:
import tensorflow as tf
from tensorflow.keras import layers, models# Create a function to generate sample data
def generate_sample_data(num_samples=100):
for _ in range(num_samples):
features = tf.random.normal(shape=(64, 64, 3))
labels = tf.one_hot(tf.random.uniform(shape=(), maxval=10, dtype=tf.int32), depth=10)
yield features, labels
# Create a TensorFlow dataset using the generator function
sample_dataset = tf.data.Dataset.from_generator(generate_sample_data, output_signature=(tf.TensorSpec(shape=(64, 64, 3), dtype=tf.float32), tf.TensorSpec(shape=(10,), dtype=tf.float32)))
# Create a CNN model with an input layer expecting (128, 128, 3)
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(128, 128, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
# Compile the model
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=('accuracy'))
# Fit the model using the dataset
model.fit(sample_dataset.batch(32).repeat(), epochs=5, steps_per_epoch=100, validation_steps=20)
Il tentativo di eseguire il codice sopra comporterà:
ValueError: Input 0 of layer "sequential_5" is incompatible with the layer: expected shape=(None, 128, 128, 3), found shape=(None, 64, 64, 3)
Questo perché il nostro modello si aspetta che il tensore di input abbia la forma (128, 128, 3) e i nostri dati generati sono (64, 64, 3).
In una situazione come questa, il nostro buon amico, reshape, o un'altra funzione di Tensorflow, resize, può aiutare. Se, come nel caso precedente, stiamo lavorando con immagini, possiamo semplicemente eseguire il ridimensionamento o modificare le aspettative dell'input del nostro modello:
def resize_image(image, label):
resized_image = tf.image.resize(image, size=target_shape)
return resized_image, label# Apply the resize function to the entire dataset
resized_dataset = sample_dataset.map(resize_image)
In questo contesto, è utile sapere qualcosa su come i tipi comuni di modelli e livelli di modello si aspettano input di forme diverse, quindi facciamo una piccola deviazione.
Le reti neurali profonde di strati densi accettano tensori monodimensionali (o bidimensionali, a seconda se si include la dimensione del batch, ma parleremo della dimensione del batch tra poco) del formato (feature_size, ) dove feature_size è il numero di funzioni in ciascun campione.
Le reti neurali convoluzionali acquisiscono dati che rappresentano immagini, utilizzando tensori tridimensionali di (larghezza, altezza, canali) dove i canali sono la combinazione di colori, 1 per la scala di grigi e 3 per RBG.
E infine, le reti neurali ricorrenti come gli LTSM assumono 2 dimensioni (fasi temporali, feature_size)
Ma torniamo agli errori! Un altro comune colpevole degli errori di forma di Tensorflow ha a che fare con il modo in cui la forma cambia mentre i dati passano attraverso i livelli del modello. Come accennato in precedenza, livelli diversi assumono forme di input diverse e possono anche rimodellare l'output.
Tornando al nostro esempio della CNN dall'alto, analizziamolo di nuovo, vedendo cosa succede quando rimuoviamo il livello Appiattisci. Se proviamo a eseguire il codice vedremo
ValueError: Shapes (None, 10) and (None, 28, 28, 10) are incompatible
È qui che la stampa di tutte le forme di input e output del nostro modello insieme alle forme di dati risulta utile per aiutarci a individuare i punti in cui si verifica una mancata corrispondenza.
model.summary() ce lo mostrerà
Layer (type) Output Shape Param #
=================================================================
conv2d_15 (Conv2D) (None, 126, 126, 32) 896
max_pooling2d_10 (MaxPooli (None, 63, 63, 32) 0
ng2D)
conv2d_16 (Conv2D) (None, 61, 61, 64) 18496
max_pooling2d_11 (MaxPooling2D) (None, 30, 30, 64) 0
conv2d_17 (Conv2D) (None, 28, 28, 64) 36928
flatten_5 (Flatten) (None, 50176) 0
dense_13 (Dense) (None, 64) 3211328
dense_14 (Dense) (None, 10) 650
=================================================================
Total params: 3268298 (12.47 MB)
Trainable params: 3268298 (12.47 MB)
Non-trainable params: 0 (0.00 Byte)
E la nostra ulteriore diagnostica lo rivelerà
###################Input Shape and Datatype#####################
(None, 128, 128, 3) <dtype: 'float32'>
###################Output Shape and Datatype#####################
(None, 10) <dtype: 'float32'>
###################Layer Input Shape and Datatype#####################
conv2d_15 KerasTensor(type_spec=TensorSpec(shape=(None, 128, 128, 3), dtype=tf.float32, name='conv2d_15_input'), name='conv2d_15_input', description="created by layer 'conv2d_15_input'") float32
max_pooling2d_10 KerasTensor(type_spec=TensorSpec(shape=(None, 126, 126, 32), dtype=tf.float32, name=None), name='conv2d_15/Relu:0', description="created by layer 'conv2d_15'") float32
conv2d_16 KerasTensor(type_spec=TensorSpec(shape=(None, 63, 63, 32), dtype=tf.float32, name=None), name='max_pooling2d_10/MaxPool:0', description="created by layer 'max_pooling2d_10'") float32
max_pooling2d_11 KerasTensor(type_spec=TensorSpec(shape=(None, 61, 61, 64), dtype=tf.float32, name=None), name='conv2d_16/Relu:0', description="created by layer 'conv2d_16'") float32
conv2d_17 KerasTensor(type_spec=TensorSpec(shape=(None, 30, 30, 64), dtype=tf.float32, name=None), name='max_pooling2d_11/MaxPool:0', description="created by layer 'max_pooling2d_11'") float32
flatten_5 KerasTensor(type_spec=TensorSpec(shape=(None, 28, 28, 64), dtype=tf.float32, name=None), name='conv2d_17/Relu:0', description="created by layer 'conv2d_17'") float32
dense_13 KerasTensor(type_spec=TensorSpec(shape=(None, 50176), dtype=tf.float32, name=None), name='flatten_5/Reshape:0', description="created by layer 'flatten_5'") float32
dense_14 KerasTensor(type_spec=TensorSpec(shape=(None, 64), dtype=tf.float32, name=None), name='dense_13/Relu:0', description="created by layer 'dense_13'") float32
L'output è molto, ma possiamo vedere che il livello dense_13 sta cercando l'input della forma (None, 50176). Tuttavia, output del livello conv2d_17 (None, 28, 28, 64)
I livelli appiattiti trasformano l'output multidimensionale dei livelli precedenti in un vettore unidimensionale (piatto) previsto dal livello Dense.
I livelli Conv2d e Max Pooling modificano i dati di input anche in altri modi interessanti, ma questi non rientrano nell'ambito di questo articolo. Per un fantastico resoconto dai un'occhiata a: Guida definitiva alla forma di input e alla complessità del modello nelle reti neurali
Ma per quanto riguarda la dimensione del lotto?! Non ho dimenticato!
Se rompiamo il nostro codice ancora una volta rimuovendo .batch(32) dal set di dati in model.fit otterremo l'errore:
ValueError: Input 0 of layer "sequential_10" is incompatible with the layer: expected shape=(None, 128, 128, 3), found shape=(128, 128, 3)
Questo perché la prima dimensione dell'input di un livello è riservata alla dimensione del batch o al numero di campioni su cui vogliamo che il modello elabori alla volta. Per una lettura approfondita e approfondita Differenza tra lotto ed epoca.
La dimensione batch per impostazione predefinita è Nessuna prima dell'adattamento, come possiamo vedere nell'output di riepilogo del modello, e il nostro modello si aspetta che la impostiamo altrove, a seconda di come ottimizziamo l'iperparametro. Possiamo anche forzarlo nel nostro livello di input utilizzando batch_input_size invece di input_size, ma ciò diminuisce la nostra flessibilità nel testare valori diversi.
Tipo
TypeError: impossibile convertire l'oggetto di tipo in Tensor. Tipo di oggetto non supportato
Infine, parliamo un po' di alcune specifiche del tipo di dati in Tensor.
L'errore sopra riportato è un altro che, se sei abituato a lavorare in sistemi di database con tabelle costruite da tutti i tipi di dati, può essere un po' sconcertante, ma è uno dei più semplici da diagnosticare e correggere, sebbene esistano errori un paio di cause comuni a cui prestare attenzione.
Il problema principale è che, sebbene i tensori supportino una varietà di tipi di datiquando convertiamo un array NumPy in tensori (un flusso comune nel deep learning), i tipi di dati devono essere float. Lo script seguente inizializza un esempio inventato di dataframe con None e con punti dati stringa. Esaminiamo alcuni problemi e soluzioni per questo esempio:
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential
data = (
(None, 0.2, '0.3'),
(0.1, None, '0.3'),
(0.1, 0.2, '0.3'),
)
X_train = pd.DataFrame(data=data, columns=("x1", "x2", "x3"))
y_train = pd.DataFrame(data=(1, 0, 1), columns=("y"))# Create a TensorFlow dataset
train_dataset = tf.data.Dataset.from_tensor_slices((X_train.to_numpy(), y_train.to_numpy()))
# Define the model
model = Sequential()
model.add(Dense(1, input_dim=X_train.shape(1), activation='sigmoid'))
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=('accuracy'))
# Fit the model using the TensorFlow dataset
model.fit(train_dataset.batch(3), epochs=3)
L'esecuzione di questo codice ci segnalerà che:
ValueError: Failed to convert a NumPy array to a Tensor (Unsupported object type float).
Il problema più ovvio è che stai inviando un array NumPy che contiene un tipo non float, un oggetto. Se disponi di una colonna effettiva di dati categorici, esistono molti modi per convertirla in dati numerici (codifica One shot, ecc.), ma ciò non rientra nell'ambito di questa discussione.
Possiamo determinarlo se eseguiamo print(X_train.dtypes), che ci dirà cosa c'è nel nostro dataframe che non piace a Tensorflow.
x1 float64
x2 float64
x3 object
dtype: object
Se ci imbattiamo in punti dati non float, la riga seguente risolverà magicamente tutti i nostri problemi:
X_train = np.asarray(X_train).astype('float32')
Un'altra cosa da verificare è se hai None o np.nan ovunque.
Per scoprirlo possiamo utilizzare poche righe di codice come:
null_mask = X_train.isnull().any(axis=1)
null_rows = X_train(null_mask)
print(null_rows)
Il che ci dice che abbiamo valori nulli sulle righe 0 e 1:
x1 x2 x3
0 NaN 0.2 0.3
1 0.1 NaN 0.3
Se è così, e ciò è previsto/intenzionale, dobbiamo sostituire tali valori con un'alternativa accettabile. Fillna può aiutarci qui.
X_train.fillna(value=0, inplace=True)
Con queste modifiche al codice seguente, il nostro array NumPy verrà convertito con successo in un set di dati tensore e potremo addestrare il nostro modello!
Spesso trovo che imparo di più su una particolare tecnologia quando devo risolvere gli errori e spero che questo sia stato utile anche a te!
Se hai suggerimenti e trucchi interessanti o errori divertenti di Tensorflow, condividili!
Fonte: towardsdatascience.com