Blog.

Tensorflow Guida Italiano: primi passi con Tensorflow


Autore
Andrea Provino
Data
Tempo di lettura
13 minuti
Categoria
Guide, machine-learning

tensorflow-wallpaper-machine-learning-data-science-deep-learning-tensorflow-guida-italiano-primi-passi-con-tesnorflow-italia

Tensorflow guida italiano intende essere una guida in italiano per muovere i primi passi con Tensorflow, una libreria per il Machine Learning e il Deep Learning giunta alla versione di Tensorflow 2.0.

In Tensorflow 2: how to start? abbiamo seguito un esempio semplice, riproducendo una rete neurale artificiale per la conversione delle temperature.

Puoi trovare questo Tensorflow Tutorial in italiano qui.

Udacity ha invece sviluppato un corso tensorflow gratuito che puoi trovare qui.

Ok dovrebbe bastare.

Bentornato!

Possiamo finalmente iniziare a macinare informazioni utili, dopo aver accuratamente studiato un’introduzione SEO-friendly ricca di keywords per i crawler di Google, i bot usati nell’indicizzazione delle pagine web (Qui una spiegazione dettagliata).

Caro il mio marinaio, è giunto il momento di salpare alla volta di tensori, reti neurali, grafi e operazioni di derivazione.

Questa è la Tensorflow Guida in Italiano. Iniziamo!

Tensorflow Guida Italiano

First thing first: where can I code?

Tensorflow è piattaforma Open Source per Python, che consente lo sviluppo di sistemi di machine learning. Esiste anche una versione Web, chiamata Tensorflow.js, che per il momento non consideriamo.

Per iniziare a muovere i primi passi con Tensorflow hai bisogno di un ambiente Python configurato: in alternativa puoi avviare un documento Google Colab Online, che contiene tutto il necessario per programmare fin da subito.

In tal caso, ho realizzato per te questa breve guida che ti insegna a prendere dimestichezza con i Jupyter Notebook di Google Colab.

Prima cosa da fare: importare la versione più recente di Tensorflow.

Il 27 Marzo 2020 è stato compiuto lo switching della default version della libreria, passata dalla 1.x alla 2.x, rendendo superfluo andare a specificare quale versione importare.

Ci basta quindi scrivere:

# import Tensorflow
import tensorflow as tf

# for numerical computation and plotting
import numpy as np
from matplotlib import pyplot as plt

Ora, devi sapere una cosa.

Tensorflow è una parola composta da Tensor e flow

La prima indica un tensore, vale a dire una struttura dati generale definita da un singolo spazio vettoriale, di cui vettori, matrici, funzioni lineari ed endomorfismi costituiscono casi particolari.

La seconda indica il flusso che viene seguito per il calcolo di operazione complesse.

Tensorflow si configura quindi come un framework per la definizione e il calcolo di operazioni che coinvolgono tensori, rappresentazioni generiche di vettori e matrici a dimensioni maggiori.

Internamente, Tensorflow rappresenta i tensor come array n-dimensionali di datatypes base (int, string, etc..)

Esistono alcuni tensori particolari, tra cui:

tf.Variable(
 initial_value=None, trainable=None, validate_shape=True, caching_device=None,
    name=None, variable_def=None, dtype=None, import_scope=None, constraint=None,
    synchronization=tf.VariableSynchronization.AUTO,
    aggregation=tf.compat.v1.VariableAggregation.NONE, shape=None
)
tf.constant(
   value, dtype=None, shape=None, name='Const'
)

A eccezione del tf.Variable, i tensori sono immutabili.

Shape and Rank

Devi sapere che ogni elemento in un tensor condivide poi il data type, che è sempre noto, con quello degli altri elementi.

La shape di un tensor (vale a dire il numero di dimensioni e la lunghezza di ogni dimensione) può invece essere anche parzialmente nota.

Questo perché la maggior parte delle operazioni produce tensori di dimensioni completamente note (full-known) se quelle degli input sono altrettanto conosciute. In altri casi è possibile determinare la shape finale solo al termine dell’esecuzione dei grafi.

Un grafo è la rappresentazione, per mezzo di nodi, di operazioni eseguite sui tensori.

Il rank di un tensor è invece il numero di dimensioni (n-dimenions), e può essere considerato l’ordine di grandezza del tensore.

Per maggiori info, la documentazione è ricca di spunti.

Facciamo comunque qualche esempio:

# create 1-d Tensors from vectors and lists
sports = tf.constant(["Tennis", "Basketball"], tf.string)
numbers = tf.constant([3.141592, 1.414213, 2.71821], tf.float64)

print("`sports` is a {}-d Tensor with shape: {}".format(tf.rank(sports).numpy(), tf.shape(sports)))
print("`numbers` is a {}-d Tensor with shape: {}".format(tf.rank(numbers).numpy(), tf.shape(numbers)))

# Output:
#`sports` is a 1-d Tensor with shape: [2]
#`numbers` is a 1-d Tensor with shape: [3]

Perfetto.

Abbiamo preso dimestichezza con gli elementi base, i tensori, ora è arrivato il momento di passare all rappresentazione di tensor dalle dimensioni maggiori.

In applicazioni reali, come vedremo, avremo bisogno anche di 4-d Tensor, con i quali rappresentare ad esempio immagini in task di image preprocessing e computer vision.

Consideriamo dunque un tensor pronto a gestire 10 immagini quadrate a colori di 256px nello spazio RGB: 10 x 256*256*3.

'''Define a 4-d Tensor.'''
# Use tf.zeros to initialize a 4-d Tensor of zeros with size 10 x 256 x 256 x 3.
# You can think of this as 10 images where each image is RGB 256 x 256.

#  Creates a tensor with all elements set to zero.
images = tf.constant(tf.zeros((10,256,256,3), tf.int32, "The name of the operation"))

assert isinstance(images, tf.Tensor), "matrix must be a tf Tensor object"
assert tf.rank(images).numpy() == 4, "matrix must be of rank 4"
assert tf.shape(images).numpy().tolist() == [10, 256, 256, 3], "matrix is incorrect shape"

Computations | Tensorflow Guida Italiano alle operazioni

Per comprendere il flow dei dati e delle operazioni in Tensorflow, possiamo servirci dei grafi, rappresentazioni convenienti di computazioni.

Un grafo, o graph, e sarà quindi costituito da tensori, che gestiranno i dati, e dalle operazioni compiuti su di essi.

Vediamo una semplice operazione di somma: due tensori costanti e una sommatoria.

alt text
# Create the nodes in the graph, and initialize values
a = tf.constant(15)
b = tf.constant(61)

# Add them!
c1 = tf.add(a,b)
c2 = a + b # TensorFlow overrides the "+" operation so that it is able to act on Tensors
print(c1)
print(c2)

# Output:
# tf.Tensor(76, shape=(), dtype=int32)
# tf.Tensor(76, shape=(), dtype=int32)

Ora consideriamo un esempio più complesso:

Tensorflow Guida Italiano computational graph

Andiamo ora a costruire una funzione che riproduca le operazioni rappresentate nel grafo:

'''Defining Tensor computations''''

# Construct a simple computation function
def func(a,b):
  c = tf.add(a,b)
  d = tf.subtract(b,1)
  e = tf.multiply(c,d)
  return e

Quindi calcoliamo il risultato:

# Consider example values for a,b
a, b = 1.5, 2.5
# Execute the computation
e_out = func(a,b)
print(e_out)

# Output:
# tf.Tensor(6.0, shape=(), dtype=float32)

L’ouput è un semplice scalare, privo di dimensoni.

Tensorflow Guida Italiano alle Neural Network

Ovviamente, possiamo andare definire delle reti neurali artificiali ed eseguire operazioni complesse grazie a loro.

Devi sapere che Tensorflow usa una high-level API chiamata Keras, di cui abbiamo parlato in passato, che fornisce una potente e intuitiva interfaccia per creare e allenare reti neurali profonde, o Deep Neural Network

Nella nostra splendida introduzione al machine learning, che ti consiglio caldamente di leggere, così come in quella sulle reti neurali artificiali, che trovi invece qui, abbiamo descritto il perceptron.

Per semplicità, lo rappresenteremo graficamente come segue:

Tensorflow Guida Italiano single neuron perceptron

I tensor possono essere organizzati in Layer.

Secondo la documentazione ufficiale, un layery (a livello di tensorflow) è un classe che implementa operazioni comuni per le reti neurali quali convoluzione, gestione dei weight, dei loss e degli aggiornmenti generici.

Usiamo la struttura sottostante per definire un Layer e implementare il perceptron:

### Defining a network Layer ###

# n_output_nodes: number of output nodes
# input_shape: shape of the input
# x: input to the layer

class OurDenseLayer(tf.keras.layers.Layer):
  def __init__(self, n_output_nodes):
    super(OurDenseLayer, self).__init__()
    self.n_output_nodes = n_output_nodes

  def build(self, input_shape):
    d = int(input_shape[-1])
    # Define and initialize parameters: a weight matrix W and bias b
    # Note that parameter initialization is random!
    self.W = self.add_weight("weight", shape=[d, self.n_output_nodes]) # note the dimensionality
    self.b = self.add_weight("bias", shape=[1, self.n_output_nodes]) # note the dimensionality

  def call(self, x):
    '''TODO: define the operation for z (hint: use tf.matmul)'''
    z = tf.matmul(x,self.W) + self.b

    '''TODO: define the operation for out (hint: use tf.sigmoid)'''
    y = tf.sigmoid(z)
    return y

# Since layer parameters are initialized randomly, we will set a random seed for reproducibility
tf.random.set_seed(1)
layer = OurDenseLayer(3)
layer.build((1,2))
x_input = tf.constant([[1,2.]], shape=(1,2))
y = layer.call(x_input)

# test the output!
print(y.numpy())
mdl.lab1.test_custom_dense_layer_output(y)

# Output:
# [[0.26978594 0.45750412 0.66536945]]
# [PASS] test_custom_dense_layer_output
# True

Per convenienza, Tensorflow ha definito una serie di Layer comuni risparmiandocene in questo modo la codifica da zero.

Un esempio è il Dense Layer. (doc)

Per organizzare più layer, possiamo impiegare un modello di Keras chiamato Sequential (doc). Con questa API possiamo facilmente impilare più livelli e definire la nostra neural network.

''' Defining a neural network using the Sequential API '''

# Import relevant packages
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense

# Define the number of outputs
n_output_nodes = 3

# First define the model 
model = Sequential()

'''Define a dense (fully connected) layer to compute z'''
# Remember: dense layers are defined by the parameters W and b!
# You can read more about the initialization of W and b in the TF documentation :) 
# https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dense?version=stable
dense_layer = Dense(n_output_nodes)

# Add the dense layer to the model
model.add(dense_layer)

Ora possiamo testare la nostra rete usando un piccolo input:

# Test model with example input
x_input = tf.constant([[1,2.]], shape=(1,2))

'''feed input into the model and predict the output!'''
model_output = model.predict(x_input)
print(model_output)

# Output:
# [[ 0.24415088  0.6485772  -1.9461871 ]]

In generale, useremo la Layer class per definire blocchi computazionali interni e la Model class per definre il modello esterno: l’oggetto da allenare.

In una variazione della ResNet, di cui abbiamo parlato qui, la ResNet50 ci sono diverse sottoclassi di modelli ResNet e una solo Model Class finale, quella che chiamiamo rete o modello.

Guida Tensorflow Italiano alle Subclass: ecco le sottoclassi

Per rendere la nostra Tensorflow Guida Italiano, degna di nota non possiamo non parlare delle sottoclassi.

Usando le sottoclassi possiamo creare diverse classi per il nostro modello, e poi definire il forwardpass attraverso la rete con la funzione call.

Le sottoclassi ci permettono di definire custom layer, custom training loops, custom activation function e custom model.

Ora ricreiamo la rete neurale precedente sostituendo le sottoclassi al Sequential Model

''' Defining a model using subclassing '''

from tensorflow.keras import Model
from tensorflow.keras.layers import Dense

class SubclassModel(tf.keras.Model):

  # In __init__, we define the Model's layers
  def __init__(self, n_output_nodes):
    super(SubclassModel, self).__init__()
    '''TODO: Our model consists of a single Dense layer. Define this layer.''' 
    self.dense_layer = Dense(n_output_nodes)

  # In the call function, we define the Model's forward pass.
  def call(self, inputs):
    return self.dense_layer(inputs)
  

Ora testiamolo!

n_output_nodes = 3
model = SubclassModel(n_output_nodes)

x_input = tf.constant([[1,2.]], shape=(1,2))

print(model.call(x_input))

# Output:
# Tf.Tensor([[ 0.62118787 -0.08692831  1.6387595 ]], shape=(1, 3), dtype=float32)

Le sottoclassi estendono le nostra capacità garantendoci una flessibilità pressoché infinita.

In questo potremmo ad esempio condizionare il ritorno della funzione call, modificando il comportamento della rete durante il training e l’inferenza.

Ad esempio, supponiamo di voler ritornare l’input senza alcuna perturbazione in alcune circostanze. Definiamo un argomento booleano nella funzione call che controlli questo comportamento:

''' Defining a model using subclassing and specifying custom behavior '''

from tensorflow.keras import Model
from tensorflow.keras.layers import Dense

class IdentityModel(tf.keras.Model):

  # As before, in __init__ we define the Model's layers
  # Since our desired behavior involves the forward pass, this part is unchanged
  def __init__(self, n_output_nodes):
    super(IdentityModel, self).__init__()
    self.dense_layer = tf.keras.layers.Dense(n_output_nodes, activation='sigmoid')

  '''TODO: Implement the behavior where the network outputs the input, unchanged, 
      under control of the isidentity argument.'''
  def call(self, inputs, isidentity=False):
    x = self.dense_layer(inputs)   
    '''TODO: Implement identity behavior'''
    return inputs if isidentity else x
 

Quindi eseguiamo il codice e gustiamoci il risultato:

n_output_nodes = 3
model = IdentityModel(n_output_nodes)

x_input = tf.constant([[1,2.]], shape=(1,2))
'''TODO: pass the input into the model and call with and without the input identity option.'''
out_activate = model.call(x_input)
out_identity = model.call(x_input,isidentity=True)

print("Network output with activation: {}; network identity output: {}".format(out_activate.numpy(), out_identity.numpy()))
 
# Output:
# Network output with activation: [[0.11969814 0.43011418 0.3474862 ]]; network identity output: [[1. 2.]]

Ora che sappiamo come definire nuovi Layer e costruire neural network in Tensorflow usando sia la Sequential API che la Subclass API è arrivato il momento d’implementare l’algoritmo di backpropagtion.

Guida Tensorflow per l’Automatic Differentiation

Un concetto chiave nel processo di training di una neural network, è quello di calcolo dell’errore compiuto nella previsione, e della sua retropropagazione ai layer precedenti.

Devi sapere che questi sono concetti fondamentali.

Sono veloce perché ne abbiamo ampiamente parlato nel post dedicato alla teoria delle reti neurali artificiali, che trovi qui.

Per il momento sappi che a livello matematico, l’allenamento di una rete neurale richiede che vengano eseguite operazioni computazionali, più o meno complesse, come il calcolo differenziale di una funzione.

La differenziazione automatica ci viene in aiuto.

Per Automatic Differentiation intendiamo allora un insieme di tecniche per calcolare la derivata di una funzione attraverso la Regola della Catena(chain rule).

Per mezzo di operazioni elementari (addizione, sottrazione etc…) e funzioni basilari (exp, log, etc…), consente di determinare una qualsiasi derivata, in modo preciso ed efficiente.

Questo insieme di tecniche è alla base dell’algoritmo di backpropagation.

Tensorflow Gradient Tape

Per ottimizzare la ricerca del minimo globale (global optima) della funzione di costo (objective function) esistono diversi algoritmi di ottimizzazione (trattai qui), di cui la discesa del gradiente (gradient descent) ne è il più comune. (come abbiamo visto qui).

Per il calcolo del gradiente in Tensorflow, abbiamo bisogno di un record delle operazioni compiute: tf.GradientTape (doc) corre in soccorso.

Quando il flusso è compiuto in avanti, espressione italiana aberrante per indicare il forward-pass o feed-forward system, tutte le operazioni sono ricordate su un tape.

Poi, il gradiente è calcolato svolgendo all’indietro il tape delle operazioni.

Ogni singolo tf.GradientTape può quindi calcolare un solo gradiente, in alternativa possiamo creare un persistent gradient tape e calcolare più gradienti.

Definiamo una semplice funzione quadratica e calcoliamone il gradiente (la derivata):

''' Gradient computation with GradientTape '''

# y = x^2
# Example: x = 3.0
x = tf.Variable(3.0)

# Initiate the gradient tape
with tf.GradientTape() as tape:
  # Define the function
  y = x * x
# Access the gradient -- derivative of y with respect to x
dy_dx = tape.gradient(y, x)

assert dy_dx.numpy() == 6.0

Nel prossimo blocco di codice useremo lo SGD (qui la spiegazione) per calcolare il minimo della objective function:

[latexpage]
$$ L=(x-x_f)^2 $$

xf è la variabile per il valore che vogliamo ottimizzare; L rappresenta la nostra funzione costo che intendiamo minimizzare.

Calcoliamo il risultato usandoGradientTape, benché sia possibile in questo caso risolverlo analiticamente, per preparare la strada a lavori futuri.

''' Function minimization with automatic differentiation and SGD '''

# Initialize a random value for our initial x
x = tf.Variable([tf.random.normal([1])])
print("Initializing x={}".format(x.numpy()))

learning_rate = 1e-2 # learning rate for SGD
history = []
epochs = 300
# Define the target value
x_f = 4

# We will run SGD for a number of iterations. At each iteration, we compute the loss, 
#   compute the derivative of the loss with respect to x, and perform the SGD update.
for i in range(epochs):
  with tf.GradientTape() as tape:
    '''TODO: define the loss as described above'''
    loss = (x - x_f)*(x - x_f)

  # loss minimization using gradient tape
  grad = tape.gradient(loss, x) # compute the derivative of the loss with respect to x
  new_x = x - learning_rate*grad # sgd update
  x.assign(new_x) # update the value of x
  history.append(x.numpy()[0])

# Plot the evolution of x as we optimize towards x_f!
plt.plot(history)
plt.plot([0, epochs],[x,x_f])
plt.legend(('Predicted', 'True'))
plt.xlabel('Iteration')
plt.ylabel('x value')

# Output:
# Initializing x=[[-0.92759573]]
# Text(0, 0.5, 'x value')
esempio-gradienttape-italia-tesnorflow-simple-example-italino-guida-machine-learning-tensroflow-deep-learnng-neurl-network-italiano-esempio

Per sfruttare appieno le potenzialità dell’algoritmo di retropropagazione dell’errore, tracciamo i flussi in avanti (forward pass) sul tape, usiamo l’informazione per determinare il gradiente e infine questi gradienti per l’ottimizzazione attraverso il SGD.

Primi passi con tensorflow: il tuo primo progetto

Ora puoi finalmente cimentarti in qualcosa di concreto.

In questa Guida Tensorflow hai visto gli elementi fondamentali di questo framework.

Seguimi allora in quello che sarà il tuo primo progetto!

Andremo a realizzare una piccola rete neurale, e poi una più complessa rete neurale convoluzionale, per classificare alcune immagini.

TensorFlow MNIST Tutorial con CNN, la guida in italiano step by step per creare una CNN!

Tensorflow Guida: domande frequenti sui primi passi!

Ho pensato di raccogliere per te alcune domande e relative risposte sull’argomento: penso le troverai utili!

Per il momento è tutto!

Per aspera, ad astra.

Un caldo abbraccio, Andrea.

Taggeddeep learningguidamachine learningneural networktensorflow


Ultimi post

Patricia Merkle Trie

Il Practical Algorithm To Retrieve Information Coded In Alphanumeric Merkle Trie, o Patricia Merkle Trie è una struttura dati chiave-valore usatada Ethereum e particolarmente efficiente per il salvataggio e la verifica dell’integrità dell’informazione. In questo post ne studieremo le caratteristiche. Prima di procedere, ci conviene ripassare l’introduzione al Merkle Tree nella quale abbiamo chiarito il […]

Andrea Provino
ethereum-patricia-merkle-tree
Tree Data Structure: cos’è un Merkle Tree

Un Merkle Tree è una struttura dati efficiente per verificare che un dato appartenga a un insieme esteso di elementi. È comunemente impiegato nelle Peer to Peer network in cui la generazione efficiente di prove (proof) contribuisce alla scalabilità della rete. Capire i vantaggi di questa struttura ci tornerà utile nel nostro percorso di esplorazione […]

Andrea Provino
merkle-tree-cover
UTXO: come funziona il modello Unspent Transaction Outputs

Per tenere traccia dei bilanci utente, la blockchain di Bitcoin sfrutta un modello di contabilità definito UTXO o Unspent Transaction Outputs. In questo articolo ne esaminiamo le caratteristiche. Ogni blockchain è dotata di un sistema di contabilità, un meccanismo attraverso cui tenere traccia dei bilanci di ciascun utente. I due grandi modelli di riferimento nel […]

Andrea Provino
bitcoin-utxo
Cos’è Ethereum

Possiamo definire Ethereum come una macchina a stati distribuita che traccia le transizioni di un archivio dati general-purpose (i.e. una memoria in grado di registrare qualsiasi dato esprimibile come coppia di chiave e valore o key-value) all’interno della Ethereum Blockchain. È arrivato il momento di esplorare uno dei progetti tecnologici più innovativi e interessanti degli […]

Andrea Provino
ethereum