Pytorch vs. TensorFlow – troviamo le differenze

Articolo in lingua originale di Kirill Dubovikov

In questo post voglio esaminare alcune delle somiglianze e differenze chiave tra due framework popolari di deep learning: Pytorch e Tensorflow. Perché questi due e non altri? Esistono molti frameework per il deep learning e molti di questi sono strumenti validi, ho scelto questi due solo perché ero interessato a confrontarli in modo specifico.

Origini

TensorFlow è sviluppato da Google Brain e utilizzato attivamente da Google sia per la ricerca che per la produzione. Il suo predecessore closed-source si chiama DistBelief.

PyTorch è un cugino del framework Torch basato su lua, sviluppato e utilizzato da Facebook. Tuttavia, PyTorch non è un semplice insieme di wrapper per supportare un linguaggio popolare, ma è stato riscritto e adattato per essere veloce e per dare l’impressione di essere nativo.

Il modo migliore per confrontare due framework è scrivere codice con entrambi. Ho scritto un notebook jupyter per questo post e potete trovarlo qui. Tutto il codice sarà fornito anche nel post.

Per prima cosa scriviamo in entrambi i framework il codice di un semplice approssimatore per la seguente funzione:

Proveremo a trovare il parametro non-noto phi dato un campione di dati x e una funzione f(x). Certo, l’uso della discesa stocastica del gradiente per questo è eccessivo e la soluzione analitica può essere trovata facilmente, ma questo problema servirà al nostro scopo come semplice esempio.

Risolviamolo prima con Pytorch:

import torch
from torch.autograd import Variable
import numpy as np

def rmse(y, y_hat):
"""Compute root mean squared error"""
return torch.sqrt(torch.mean((y - y_hat).pow(2).sum()))

def forward(x, e):
"""Forward pass for our fuction"""
return x.pow(e.repeat(x.size(0)))

# Let's define some settings
n = 100 # number of examples
learning_rate = 5e-6
target_exp = 2.0 # real value of the exponent will try to find

# Model definition
x = Variable(torch.rand(n) * 10, requires_grad=False)

# Model parameter and it's true value
exp = Variable(torch.FloatTensor([target_exp]), requires_grad=False)
exp_hat = Variable(torch.FloatTensor([4]), requires_grad=True) # just some starting value, could be random as well
y = forward(x, exp)

# a couple of buffers to hold parameter and loss history
loss_history = []
exp_history = []

# Training loop
for i in range(0, 200):
print("Iteration %d" % i)

# Compute current estimate
y_hat = forward(x, exp_hat)

# Calculate loss function
loss = rmse(y, y_hat)

# Do some recordings for plots
loss_history.append(loss.data[0])
exp_history.append(y_hat.data[0])

# Compute gradients
loss.backward()

print("loss = %s" % loss.data[0])
print("exp = %s" % exp_hat.data[0])

# Update model parameters
exp_hat.data -= learning_rate * exp_hat.grad.data
exp_hat.grad.data.zero_()

Se avete un po’ di esperienza con i framework di deep learning, avrete notato che stiamo implementando la discesa del gradiente a mano. Non è molto comodo, eh? Fortunatamente, PyTorch dispone del modulo optimize, che contiene le implementazioni degli algoritmi di ottimizzazione più diffusi, come RMSProp o Adam. Utilizzeremo SGD con momentum.

import torch
from torch.autograd import Variable
import numpy as np

def rmse(y, y_hat):
"""Compute root mean squared error"""
return torch.sqrt(torch.mean((y - y_hat).pow(2)))

def forward(x, e):
"""Forward pass for our fuction"""
return x.pow(e.repeat(x.size(0)))

# Let's define some settings
n = 1000 # number of examples
learning_rate = 5e-10

# Model definition
x = Variable(torch.rand(n) * 10, requires_grad=False)
y = forward(x, exp)

# Model parameters
exp = Variable(torch.FloatTensor([2.0]), requires_grad=False)
exp_hat = Variable(torch.FloatTensor([4]), requires_grad=True)

# Optimizer (NEW)
opt = torch.optim.SGD([exp_hat], lr=learning_rate, momentum=0.9)

loss_history = []
exp_history = []

# Training loop
for i in range(0, 10000):
opt.zero_grad()
print("Iteration %d" % i)

# Compute current estimate
y_hat = forward(x, exp_hat)

# Calculate loss function
loss = rmse(y, y_hat)

# Do some recordings for plots
loss_history.append(loss.data[0])
exp_history.append(y_hat.data[0])

# Update model parameters
loss.backward()
opt.step()

print("loss = %s" % loss.data[0])
print("exp = %s" % exp_hat.data[0])
Funzione di perdita e plot dell’esponente da PyTorch

Come si può vedere, abbiamo dedotto rapidamente il vero esponente dai dati di addestramento. E ora proseguiamo con TensorFlow:

import tensorflow as tf

def rmse(y, y_hat):
"""Compute root mean squared error"""
return tf.sqrt(tf.reduce_mean(tf.square((y - y_hat))))

def forward(x, e):
"""Forward pass for our fuction"""
# tensorflow has automatic broadcasting 
# so we do not need to reshape e manually
return tf.pow(x, e)

n = 100 # number of examples
learning_rate = 5e-6

# Placeholders for data
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)

# Model parameters
exp = tf.constant(2.0)
exp_hat = tf.Variable(4.0, name='exp_hat')

# Model definition
y_hat = forward(x, exp_hat)

# Optimizer
loss = rmse(y, y_hat)
opt = tf.train.GradientDescentOptimizer(learning_rate)

# We will run this operation to perform a single training step,
# e.g. opt.step() in Pytorch.
# Execution of this operation will also update model parameters
train_op = opt.minimize(loss)

# Let's generate some training data
x_train = np.random.rand(n) + 10
y_train = x_train ** 2

loss_history = []
exp_history = []

# First, we need to create a Tensorflow session object
with tf.Session() as sess:

# Initialize all defined variables
tf.global_variables_initializer().run()

# Training loop
for i in range(0, 500):
print("Iteration %d" % i)
# Run a single trainig step
curr_loss, curr_exp, _ = sess.run([loss, exp_hat, train_op], feed_dict={x: x_train, y: y_train})

print("loss = %s" % curr_loss)
print("exp = %s" % curr_exp)

# Do some recordings for plots
loss_history.append(curr_loss)
exp_history.append(curr_exp)
Funzione di perdita ed plot dell’esponente da TensorFlow

Come si può vedere, anche l’implementazione in TensorFlow funziona (sorprendentemente 🙃). Ci sono volute più iterazioni per recuperare l’esponente, ma sono sicuro che la causa sia da ricercare nel fatto che non ho giocherellato abbastanza con i parametri dell’ottimizzatore per ottenere risultati comparabili.

Ora siamo pronti a esplorare alcune differenze.

 

Differenza #0 - adozione

Attualmente, TensorFlow è considerato uno strumento indispensabile da molti ricercatori e professionisti del settore. Il framework è ben documentato e, se la documentazione non è sufficiente, su Internet sono disponibili molti tutorial estremamente ben scritti. È possibile trovare centinaia di modelli implementati e addestrati su github, a partire da qui.

PyTorch è relativamente nuovo rispetto ai suoi concorrenti, ma sta rapidamente prendendo piede. Anche la documentazione e i tutorial ufficiali sono interessanti. PyTorch include anche diverse implementazioni delle architetture di computer vision più diffuse, che sono facilissime da usare.

Differenza #1 - definizione dei grafi statica e dinamica

Entrambi i framework operano sui tensori e considerano qualsiasi modello come un grafo aciclico diretto (DAG), ma differiscono drasticamente su come è possibile definirli.

Tensorflow segue il detto ‘data as code and code is data’ (dati come codice e codice come dati). In TensorFlow si definisce staticamente il grafo prima che il modello possa essere eseguito. Tutte le comunicazioni con il mondo esterno avvengono tramite gli oggetti tf.Session e tf.Placeholder, che sono tensori che verranno sostituiti da dati esterni in fase di esecuzione.

In PyTorch le cose sono molto più imperative e dinamiche: si possono definire, modificare ed eseguire i nodi man mano che si procede, senza interfacce di sessione o segnaposto speciali. Nel complesso, il framework è più strettamente integrato con il linguaggio Python e il più delle volte sembra più nativo. Quando si scrive in TensorFlow, a volte si ha la sensazione che il modello sia dietro a un muro di mattoni con diversi piccoli buchi per comunicare. In ogni caso, si tratta ancora di una questione di gusti, più o meno.

Tuttavia, questi approcci differiscono non solo dal punto di vista dell’ingegneria del software: esistono diverse architetture di reti neurali dinamiche che possono trarre vantaggio dall’approccio dinamico.

Ricordiamo le RNN: con i grafi statici, la lunghezza della sequenza di ingresso rimane costante. Ciò significa che se si sviluppa un modello di sentiment analysis per le frasi in inglese è necessario fissare la lunghezza della frase a un valore massimo e riempire di zeri tutte le sequenze più piccole. Non è molto comodo, eh. Inoltre, i problemi aumentano nel campo delle RNN ricorsive e delle RNN ad albero.

Differenza #2 - debugging

Poiché il grafico di calcolo in PyTorch è definito in fase di esecuzione, è possibile utilizzare i nostri strumenti di debug preferiti di Python, come pdb, ipdb, il debugger di PyCharm o il caro e vecchio print.

Questo non è il caso di TensorFlow. È possibile utilizzare uno strumento speciale chiamato tfdbg che consente di valutare le espressioni di tensorflow in fase di esecuzione e di esplorare tutti i tensori e le operazioni nell’ambito della sessione. Naturalmente, non sarà possibile eseguire il debug di alcun codice python con questo strumento, quindi sarà necessario utilizzare pdb separatamente.

 

Differenza #3 - visualizzazione

Tensorboard è fantastico quando si tratta di visualizzazione 😎. Questo strumento viene fornito con TensorFlow ed è molto utile per il debug e il confronto di diversi addestramenti. Ad esempio, pensate di aver addestrato un modello, poi aggiustate alcuni iperparametri e lanciate un nuovo training. Entrambe le esecuzioni possono essere mostrate in Tensorboard contemporaneamente in modo da sottolineare possibili differenze.

Tensorboard può:

  • Visualizzare il grafico del modello
  • Plottare variabili scalari
  • Visualizzare distribuzioni e istogrammi
  • Visualizzare immagini
  • Visualizzare le incorporazioni
  • Riprodurre l’audio

Tensorboard può visualizzare vari riassunti che possono essere raccolti con il modulo tf.summary. Definire operazioni di sintesi per il nostro esponente giocattolo e utilizzeremo tf.summary.FileWriter per salvarle sul disco.

import tensorflow as tf
import numpy as np

def rmse(y, y_hat):
"""Compute root mean squared error"""
return tf.sqrt(tf.reduce_mean(tf.square((y - y_hat))))

def forward(x, e):
"""Forward pass for our fuction"""
# tensorflow has automatic broadcasting 
# so we do not need to reshape e manually
return tf.pow(x, e)

n = 100 # number of examples
learning_rate = 5e-6

# Placeholders for data
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)

# Model parameters
exp = tf.constant(2.0)
exp_hat = tf.Variable(4.0, name='exp_hat')

# Model definition
y_hat = forward(x, exp_hat)

# Optimizer
loss = rmse(y, y_hat)
opt = tf.train.GradientDescentOptimizer(learning_rate)

# Summaries (NEW)
loss_summary = tf.summary.scalar("loss", loss)
exp_summary = tf.summary.scalar("exp", exp_hat)
all_summaries = tf.summary.merge_all()

# We will run this operation to perform a single training step,
# e.g. opt.step() in Pytorch.
# Execution of this operation will also update model parameters
train_op = opt.minimize(loss)

# Let's generate some training data
x_train = np.random.rand(n) + 10
y_train = x_train ** 2

loss_history = []
exp_history = []

# First, we need to create a Tensorflow session object
with tf.Session() as sess:

# Initialize all defined variables
tf.global_variables_initializer().run()

summary_writer = tf.summary.FileWriter('./tensorboard', sess.graph)

# Training loop
for i in range(0, 500):
print("Iteration %d" % i)
# Run a single trainig step
summaries, curr_loss, curr_exp, _ = sess.run([all_summaries, loss, exp_hat, train_op], feed_dict={x: x_train, y: y_train})

print("loss = %s" % curr_loss)
print("exp = %s" % curr_exp)

# Do some recordings for plots
loss_history.append(curr_loss)
exp_history.append(curr_exp)

summary_writer.add_summary(summaries, i)

Per lanciare Tensorboard eseguite tensorboard –logdir=./tensorboard. Questo strumento è davvero conveniente da usare sulle istanze cloud poiché si tratta di una webapp.

Il concorrente di Tensorboard dal lato di PyTorch è visdom. Non è altrettanto completo, ma è un po’ più comodo da usare. Inoltre, esistono integrazioni con Tensorboard. Inoltre, è possibile utilizzare gli strumenti di plottaggio standard – matplotlib e seaborn.

 

Differenza #4 - Distribuzione

Se iniziamo a parlare di distribuzione, TensorFlow è un chiaro vincitore per ora: ha TensorFlow Serving che è un framework per distribuire i modelli su un server gRPC specializzato. È supportato anche in versione mobile.

Quando torneremo a PyTorch, potremmo usare Flask o un’altra alternativa per creare un’API REST in cima al modello. Questo può essere fatto anche con i modelli TensorFlow, se gRPC non è adatto al vostro caso d’uso. Tuttavia, TensorFlow Serving potrebbe essere un’opzione migliore se le prestazioni sono un problema.

Tensorflow supporta anche l’addestramento distribuito, che per ora manca a PyTorch.

Differenza #5 - Parallelismo tra i dati

Una delle caratteristiche principali che distinguono PyTorch da TensorFlow è il parallelismo dichiarativo dei dati: si può usare torch.nn.DataParallel per avvolgere qualsiasi modulo e questo sarà (quasi magicamente) parallelizzato su una dimensione batch. In questo modo è possibile sfruttare più GPU quasi senza sforzo.

D’altra parte, TensorFlow consente di regolare con precisione ogni operazione da eseguire su un dispositivo specifico. Tuttavia, la definizione del parallelismo è molto più manuale e richiede un’attenta riflessione. Si consideri il codice che implementa qualcosa come DataParallel in TensorFlow:

def make_parallel(fn, num_gpus, **kwargs):
in_splits = {}
for k, v in kwargs.items():
in_splits[k] = tf.split(v, num_gpus)out_split = []
for i in range(num_gpus):
with tf.device(tf.DeviceSpec(device_type="GPU", device_index=i)):
with tf.variable_scope(tf.get_variable_scope(), reuse=tf.AUTO_REUSE):
out_split.append(fn(**{k : v[i] for k, v in in_splits.items()}))return tf.concat(out_split, axis=0)def model(a, b):
return a + bc = make_parallel(model, 2, a=a, b=b)

Detto questo, quando si usa TensorFlow si può ottenere tutto ciò che si può fare in PyTorch, ma con uno sforzo maggiore (si ha più controllo però).

Inoltre, vale la pena notare che entrambi i framework supportano l’esecuzione distribuita e forniscono interfacce di alto livello per la definizione dei cluster.

 

Differenza #6 - Un framework o una libreria

Costruiamo una CNN per la classificazione di cifre scritte a mano, Ora PyTorch inizierà veramente a sembrare un framework. Ricordiamo che un framework di programmazione ci fornisce astrazioni utili in un certo dominio e un modo conveniente di usarle per risolvere problemi concreti. Questa è l’essenza che separa un framework da una libreria.

Qui introduciamo il modulo datasets, che contiene i wrapper per i datasets più diffusi, utilizzati per il benchmark delle architetture di deep learning. Inoltre nn.Module viene utilizzato per costruire un classificatore di rete neurale convoluzionale personalizzato. nn.Module è un blocco di costruzione che PyTorch ci fornisce per creare architetture di deep learning complesse. Nel pacchetto torch.nn sono disponibili numerosi moduli pronti all’uso che possiamo utilizzare come base per il nostro modello. Si noti come PyTorch utilizzi un approccio orientato agli oggetti per definire i blocchi di base e fornirci alcuni “binari” su cui muoverci, offrendo al contempo la possibilità di estendere le funzionalità tramite sottoclassi.

Ecco una versione leggermente modificata di https://github.com/pytorch/examples/blob/master/mnist/main.py:

import numpy as np
import tensorflow as tf

from tensorflow.contrib import learn
from tensorflow.contrib.learn.python.learn.estimators import model_fn as model_fn_lib

tf.logging.set_verbosity(tf.logging.INFO)


def cnn_model_fn(features, labels, mode):
"""Model function for CNN."""
# Input Layer
# Reshape X to 4-D tensor: [batch_size, width, height, channels]
# MNIST images are 28x28 pixels, and have one color channel
input_layer = tf.reshape(features, [-1, 28, 28, 1])

# Convolutional Layer #1
# Computes 32 features using a 5x5 filter with ReLU activation.
# Padding is added to preserve width and height.
# Input Tensor Shape: [batch_size, 28, 28, 1]
# Output Tensor Shape: [batch_size, 28, 28, 32]
conv1 = tf.layers.conv2d(
inputs=input_layer,
filters=32,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu)

# Pooling Layer #1
# First max pooling layer with a 2x2 filter and stride of 2
# Input Tensor Shape: [batch_size, 28, 28, 32]
# Output Tensor Shape: [batch_size, 14, 14, 32]
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)

# Convolutional Layer #2
# Computes 64 features using a 5x5 filter.
# Padding is added to preserve width and height.
# Input Tensor Shape: [batch_size, 14, 14, 32]
# Output Tensor Shape: [batch_size, 14, 14, 64]
conv2 = tf.layers.conv2d(
inputs=pool1,
filters=64,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu)

# Pooling Layer #2
# Second max pooling layer with a 2x2 filter and stride of 2
# Input Tensor Shape: [batch_size, 14, 14, 64]
# Output Tensor Shape: [batch_size, 7, 7, 64]
pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)

# Flatten tensor into a batch of vectors
# Input Tensor Shape: [batch_size, 7, 7, 64]
# Output Tensor Shape: [batch_size, 7 * 7 * 64]
pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])

# Dense Layer
# Densely connected layer with 1024 neurons
# Input Tensor Shape: [batch_size, 7 * 7 * 64]
# Output Tensor Shape: [batch_size, 1024]
dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)

# Add dropout operation; 0.6 probability that element will be kept
dropout = tf.layers.dropout(
inputs=dense, rate=0.4, training=mode == learn.ModeKeys.TRAIN)

# Logits layer
# Input Tensor Shape: [batch_size, 1024]
# Output Tensor Shape: [batch_size, 10]
logits = tf.layers.dense(inputs=dropout, units=10)

loss = None
train_op = None

# Calculate Loss (for both TRAIN and EVAL modes)
if mode != learn.ModeKeys.INFER:
onehot_labels = tf.one_hot(indices=tf.cast(labels, tf.int32), depth=10)
loss = tf.losses.softmax_cross_entropy(
onehot_labels=onehot_labels, logits=logits)

# Configure the Training Op (for TRAIN mode)
if mode == learn.ModeKeys.TRAIN:
train_op = tf.contrib.layers.optimize_loss(
loss=loss,
global_step=tf.contrib.framework.get_global_step(),
learning_rate=0.001,
optimizer="SGD")

# Generate Predictions
predictions = {
"classes": tf.argmax(
input=logits, axis=1),
"probabilities": tf.nn.softmax(
logits, name="softmax_tensor")
}

# Return a ModelFnOps object
return model_fn_lib.ModelFnOps(
mode=mode, predictions=predictions, loss=loss, train_op=train_op)


# Load training and eval data
mnist = learn.datasets.load_dataset("mnist")
train_data = mnist.train.images # Returns np.array
train_labels = np.asarray(mnist.train.labels, dtype=np.int32)
eval_data = mnist.test.images # Returns np.array
eval_labels = np.asarray(mnist.test.labels, dtype=np.int32)

# Create the Estimator
mnist_classifier = learn.Estimator(
model_fn=cnn_model_fn, model_dir="/tmp/mnist_convnet_model")

# Set up logging for predictions
# Log the values in the "Softmax" tensor with label "probabilities"
tensors_to_log = {"probabilities": "softmax_tensor"}
logging_hook = tf.train.LoggingTensorHook(
tensors=tensors_to_log, every_n_iter=50)

# Train the model
mnist_classifier.fit(
x=train_data,
y=train_labels,
batch_size=100,
steps=20000,
monitors=[logging_hook])

# Configure the accuracy metric for evaluation
metrics = {
"accuracy":
learn.MetricSpec(
metric_fn=tf.metrics.accuracy, prediction_key="classes"),
}

# Evaluate the model and print results
eval_results = mnist_classifier.evaluate(
x=eval_data, y=eval_labels, metrics=metrics)
print(eval_results)

TensorFlow è molto più simile a una libreria che a un framework: tutte le operazioni sono di basso livello ed è necessario scrivere molto codice di base anche quando non lo si desidera (definiamo i bias e i pesi ancora e ancora…)

Con il passare del tempo è nato un intero ecosistema di wrapper di alto livello attorno a TensorFlow. Ognuno di essi mira a semplificare il modo in cui si lavora con la libreria. Molti di essi si trovano attualmente nel modulo tensorflow.contrib (che non è considerato un’API stabile) e alcuni hanno iniziato a migrare nel repository principale (vedere tf.layers).

Quindi, si ha molta libertà su come utilizzare TensorFlow e su quale framework sia più adatto al compito da svolgere: TFLearn, tf.contrib.learn, Sonnet, Keras, il semplice tf.layers, ecc. A dire il vero, Keras meriterebbe un altro post, ma al momento non rientra nello scopo di questo confronto.

Qui useremo tf.layers e tf.contrib.learn per costruire la nostra CNN. Il codice segue il tutorial ufficiale su tf.layers:

import numpy as np
import tensorflow as tf

from tensorflow.contrib import learn
from tensorflow.contrib.learn.python.learn.estimators import model_fn as model_fn_lib

tf.logging.set_verbosity(tf.logging.INFO)


def cnn_model_fn(features, labels, mode):
"""Model function for CNN."""
# Input Layer
# Reshape X to 4-D tensor: [batch_size, width, height, channels]
# MNIST images are 28x28 pixels, and have one color channel
input_layer = tf.reshape(features, [-1, 28, 28, 1])

# Convolutional Layer #1
# Computes 32 features using a 5x5 filter with ReLU activation.
# Padding is added to preserve width and height.
# Input Tensor Shape: [batch_size, 28, 28, 1]
# Output Tensor Shape: [batch_size, 28, 28, 32]
conv1 = tf.layers.conv2d(
inputs=input_layer,
filters=32,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu)

# Pooling Layer #1
# First max pooling layer with a 2x2 filter and stride of 2
# Input Tensor Shape: [batch_size, 28, 28, 32]
# Output Tensor Shape: [batch_size, 14, 14, 32]
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)

# Convolutional Layer #2
# Computes 64 features using a 5x5 filter.
# Padding is added to preserve width and height.
# Input Tensor Shape: [batch_size, 14, 14, 32]
# Output Tensor Shape: [batch_size, 14, 14, 64]
conv2 = tf.layers.conv2d(
inputs=pool1,
filters=64,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu)

# Pooling Layer #2
# Second max pooling layer with a 2x2 filter and stride of 2
# Input Tensor Shape: [batch_size, 14, 14, 64]
# Output Tensor Shape: [batch_size, 7, 7, 64]
pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)

# Flatten tensor into a batch of vectors
# Input Tensor Shape: [batch_size, 7, 7, 64]
# Output Tensor Shape: [batch_size, 7 * 7 * 64]
pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])

# Dense Layer
# Densely connected layer with 1024 neurons
# Input Tensor Shape: [batch_size, 7 * 7 * 64]
# Output Tensor Shape: [batch_size, 1024]
dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)

# Add dropout operation; 0.6 probability that element will be kept
dropout = tf.layers.dropout(
inputs=dense, rate=0.4, training=mode == learn.ModeKeys.TRAIN)

# Logits layer
# Input Tensor Shape: [batch_size, 1024]
# Output Tensor Shape: [batch_size, 10]
logits = tf.layers.dense(inputs=dropout, units=10)

loss = None
train_op = None

# Calculate Loss (for both TRAIN and EVAL modes)
if mode != learn.ModeKeys.INFER:
onehot_labels = tf.one_hot(indices=tf.cast(labels, tf.int32), depth=10)
loss = tf.losses.softmax_cross_entropy(
onehot_labels=onehot_labels, logits=logits)

# Configure the Training Op (for TRAIN mode)
if mode == learn.ModeKeys.TRAIN:
train_op = tf.contrib.layers.optimize_loss(
loss=loss,
global_step=tf.contrib.framework.get_global_step(),
learning_rate=0.001,
optimizer="SGD")

# Generate Predictions
predictions = {
"classes": tf.argmax(
input=logits, axis=1),
"probabilities": tf.nn.softmax(
logits, name="softmax_tensor")
}

# Return a ModelFnOps object
return model_fn_lib.ModelFnOps(
mode=mode, predictions=predictions, loss=loss, train_op=train_op)


# Load training and eval data
mnist = learn.datasets.load_dataset("mnist")
train_data = mnist.train.images # Returns np.array
train_labels = np.asarray(mnist.train.labels, dtype=np.int32)
eval_data = mnist.test.images # Returns np.array
eval_labels = np.asarray(mnist.test.labels, dtype=np.int32)

# Create the Estimator
mnist_classifier = learn.Estimator(
model_fn=cnn_model_fn, model_dir="/tmp/mnist_convnet_model")

# Set up logging for predictions
# Log the values in the "Softmax" tensor with label "probabilities"
tensors_to_log = {"probabilities": "softmax_tensor"}
logging_hook = tf.train.LoggingTensorHook(
tensors=tensors_to_log, every_n_iter=50)

# Train the model
mnist_classifier.fit(
x=train_data,
y=train_labels,
batch_size=100,
steps=20000,
monitors=[logging_hook])

# Configure the accuracy metric for evaluation
metrics = {
"accuracy":
learn.MetricSpec(
metric_fn=tf.metrics.accuracy, prediction_key="classes"),
}

# Evaluate the model and print results
eval_results = mnist_classifier.evaluate(
x=eval_data, y=eval_labels, metrics=metrics)
print(eval_results)

Quindi, sia TensorFlow che PyTorch forniscono astrazioni utili per ridurre la quantità di codice e accelerare lo sviluppo dei modelli. La differenza principale tra i due è che PyTorch può sembrare più ” pythonico” e ha un approccio orientato agli oggetti, mentre TensorFlow ha diverse opzioni tra le quali si può scegliere.

Personalmente, ritengo che PyTorch sia più chiaro e facile da sviluppare. Il modulo torch.nn.Module offre la possibilità di definire moduli riutilizzabili in modo OOP e trovo questo approccio molto flessibile e potente. In seguito è possibile comporre tutti i tipi di moduli tramite torch.nn.Sequential (ciao Keras ✋🏻). Inoltre, tutti i moduli incorporati sono in forma funzionale, il che può essere molto comodo. Nel complesso, tutte le parti dell’API funzionano bene insieme.

Naturalmente, è possibile scrivere codice molto pulito in TensorFlow, ma ci vogliono più abilità e prove ed errori prima di riuscirci. Quando si passa a framework di livello superiore come Keras o TFLearn, preparatevi a perdere almeno una parte della flessibilità offerta da TensorFlow.

 

Conclusione

TensorFlow è una libreria di deep learning molto potente e matura, con forti capacità di visualizzazione e diverse opzioni da utilizzare per lo sviluppo di modelli di alto livello. Ha opzioni di distribuzione pronte per la produzione e supporto per le piattaforme mobili. TensorFlow è una buona opzione nei casi in cui si volesse:

  • Sviluppare modelli per la produzione
  • Sviluppare modelli che devono essere distribuiti su piattaforme mobili
  • Avere un buon supporto da parte della comunità e una documentazione completa
  • Avere risorse di apprendimento ricche e in varie forme (TensorFlow ha un intero MOOC)
  • Utilizzare Tensorboard
  • Utilizzare l’addestramento di modelli distribuiti su larga scala

PyTorch è ancora un framework giovane che sta prendendo piede rapidamente. Potreste trovarlo adatto a voi se:

  •  Fate ricerca o i vostri requisiti non funzionali per la produzione non sono molto impegnativi.
  • Volete una migliore esperienza di sviluppo e debug
  • Amate tutto ciò che è Pythonic

Se avete tempo, il consiglio migliore è di provarli entrambi e vedere quale si adatta meglio alle vostre esigenze.

Share:

Contenuti
Torna in alto