18 de Diciembre de 2017 · 20 min de lectura
En el turtorial anterior (Tutorial I) hemos introducido algunos conceptos básicos sobre el uso de la librería de TensorFlow para la implementación de modelos para realizar procesos de aprendizaje automático (machine learning). En el Tutorial I, creamos un modelo capaz de reconocer dígitos escritos a mano usando el conjunto de datos MNIST. Pudimos verificar que la precisión del modelo en la clasificación alcanzó un 91%.
En este tutorial implementaremos una Red Neural Convolucional en TensorFlow, y veremos que éste modelo más complejos es capaz de reconocer los dígitos escrito a mano con una precisión de clasificación de aproximadamente un 99%.
El objetivo de éste tutorial, es mediante el ejemplo empleado en el Tutorial I usar la librería de TensorFlow para crear y entrenar redes neuronales.
Si no se está familiarizado con los conceptos, características y el funcionamiento de las Redes Neuronales Convolucionales (Convolutional Neural Netwoks CNNs), antes de seguir con éste tutorial, recomendamos que revisen nuestro artículo Use of Convolutional Neural Network for Image Classification.
Como se explica en nuestro artículo Use of Convolutional Neural Network for Image Classification, las redes Convolucionales funcionan moviendo pequeños filtros a través de la imagen de entrada. Esto significa que los filtros se reutilizan para reconocer patrones en toda la imagen de entrada. La introducción de las capas convolucionales en las redes neuronales permiten que éstas sean mucho más poderosas que las redes neuronales tradicionales.
En la figura que se presenta a continuación muestra aproximadamente cómo fluyen los datos por la la red neuronal convolucional que se implementa en éste tutorial.
Vamos a describir un poco lo que representa éste diagrama. Como se observa, la imagen de entrada (un 7 con una resolución de 28x28) se procesa en la primera capa convolucional utilizando los pesos de los filtros (16). Esto da como resultado 16 nuevas imágenes, una para cada filtro en la capa convolucional. Luego las imágenes se procesan de manera tal, que la resolución de éstas se reduce de 28x28 a 14x14 (ver Use of Convolutional Neural Network for Image Classification para saber como son éstos procesos).
Estas 16 imágenes más pequeñas se procesan en la segunda capa convolucional. En esta segunda capa necesitamos un filtro para cada uno de estos 16 canales proveniente de la primera capa convolucional, y necesitamos ponderar cada filtro para cada canal de salida de esta capa, es decir 6x6=36, que corresponden al número de canales de salida. Puesto que tenemos 36 canales de salida, tendremos un total de 16x36 = 576 filtros en la segunda capa convolucional. Luego, las imágenes resultantes se procesan de nuevo para reducir la resolución a 7x7 píxeles (ver [Use of Convolucional Neural Network for Image Classification] para más detalles) .
La salida de la segunda capa convolucional es de 36 imágenes de 7x7 píxeles cada una. Estos se transforman a un único vector de longitud 7 x 7 x 36 = 1764, que se utiliza como la entrada a una capa completamente conectada con 128 neuronas. Esto alimenta a otra capa totalmente conectada con 10 neuronas, una para cada una de las clases, que se utiliza para determinar qué número se representa en la imagen, es decir determinar a que clase pertenece la imagen de entrada.
Los filtros convolucionales inicialmente se eligen al azar, por lo que la clasificación se realiza aleatoriamente. El error entre la clase pronosticada y la salida deseada (la imagen verdadera) se mide empleando una función de coste, que en nuestro caso usaremos la entropía cruzada (cross-entropy) como función de coste.
Luego, el optimizador propaga automáticamente este error a través de la red convolucional usando la regla de diferenciación y actualiza los pesos de filtro para mejorar el error de clasificación. Esto se hace de forma iterativa miles de veces hasta que el error de clasificación sea lo suficientemente bajo. Este proceso de optimización es lo que se conoce como proceso de aprendizaje o entrenamiento de la red. El objetivo de este proceso es, optimizar el valor de los pesos en los filtros convolucionales así cómo los pesos en las conexiones entre las neuronas. Estos pesos en las imágenes filtradas y las imágenes intermedias particulares, son los resultados durante el entrenamiento de la CNNs.
La figura a continuación muestra cuatro copias de la imagen de entrada (el 7), para que podamos ver claramente cómo se mueve el filtro a diferentes posiciones de la imagen. Para cada posición del filtro, los puntos verdes corresponde al resultado en la imagen de salida luego de haber pasado el filtro sobre la imagen de entrada (ver Use of Convolutional Neural Network for Image Classification para mas detalles).
El filtro de color rojo significa que el filtro tiene una reacción positiva a los píxeles negros en la imagen de entrada, mientras que los píxeles azules significa que el filtro tiene una reacción negativa a los píxeles negros. En este ejemplo, se aprecia como el filtro reconoce la línea horizontal del dígito 7.
El tamaño de paso para mover el filtro a través de la entrada se llama zancada ó stride. Hay un stride para mover el filtro horizontalmente (eje x) y otra para moverse verticalmente (eje y).
Por convención, el stride se establece en 1 en ambas direcciones, lo que significa que el filtro comienza en la esquina superior izquierda de la imagen de entrada y se mueve 1 píxel a la derecha en cada paso, sin embargo esta restricción puede ser modificada dependiendo de la tarea a resolver. Cuando el filtro alcanza el final de la imagen a la derecha, el filtro se mueve hacia el lado izquierdo y 1 píxel hacia abajo de la imagen. Esto continúa hasta que el filtro haya alcanzado la esquina inferior derecha de la imagen de entrada y se haya generado toda la imagen de salida.
Cuando el filtro llega al final del lado derecho así como también a la parte inferior de la imagen de entrada, se puede rellenar con ceros (píxeles blancos). Esto hace que la imagen de salida tenga exactamente la misma dimensión que la imagen de entrada.
Luego, la salida de la convolución se puede pasar a través de una denominada Unidad lineal rectificada (ReLU), que simplemente asegura que la salida sea positiva porque los valores negativos se establecen en cero.
Finalmente, el resultado mediante el denominado max-pooling procesamos la imagen con imágenes más pequeñas de 2x2 píxeles y solo conserva el mayor de esos píxeles. Esto reduce a la mitad la resolución de la imagen de entrada
Tenga en cuenta que la segunda capa convolucional es más complicada porque requiere 16 canales de entrada. Queremos un filtro separado para cada canal de entrada, por lo que necesitamos 16 filtros en lugar de uno solo. Además, queremos 36 canales de salida de la segunda capa convolucional, por lo que en total necesitamos 16 x 36 = 576 filtros para la segunda capa convolucional.
Pasemos ahora a configurar nuestra grafo en TensorFlow para generar la red convolucional del Flowchart discutido antes.
Primero cargaremos algunas librería
%matplotlib inline
import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np
from sklearn.metrics import confusion_matrix
El conjunto de datos MNIST es de aproximadamente 12 MB y se descargará automáticamente si no se encuentra en la ruta dada.
# Load Data.....
from tensorflow.examples.tutorials.mnist import input_data
data = input_data.read_data_sets("data/MNIST/", one_hot=True)
Extracting data/MNIST/train-images-idx3-ubyte.gz
Extracting data/MNIST/train-labels-idx1-ubyte.gz
Extracting data/MNIST/t10k-images-idx3-ubyte.gz
Extracting data/MNIST/t10k-labels-idx1-ubyte.gz
Verificamos los datos
print("Size of:")
print("- Training-set:\t\t{}".format(len(data.train.labels)))
print("- Test-set:\t\t{}".format(len(data.test.labels)))
print("- Validation-set:\t{}".format(len(data.validation.labels)))
Size of:
Training-set: 55000
Test-set: 10000
Validation-set: 5000
Como se observa, ahora tenemos tres sub conjunto de datos, uno de entrenamiento, uno de test y otro de validación.
El conjunto de datos se ha cargado con la codificación denominada One-Hot. Esto significa que las etiquetas se han convertido de un solo número a un vector cuya longitud es igual a la cantidad de clases posibles. Todos los elementos del vector son cero excepto el elemento i enésimo que toma el valor uno; y significa que la clase es i.
Por ejemplo, las etiquetas codificadas de One-Hot para las primeras 5 imágenes en el conjunto de prueba son:
data.test.labels[0:5, :]
se obtiene:
array([[ 0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
[ 0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.]])
Como se observa, tenemos cinco vectores donde cada componete tiene valores cero excepto en la posición de la componete que identifica la clase, cuyo valor es 1.
Como necesitamos las clases como números únicos para las comparaciones y medidas de rendimiento, procedemos a convertir estos vectores codificados como One-Hot a un solo número tomando el índice del elemento más alto. Tenga en cuenta que la palabra 'clase' es una palabra clave utilizada en Python, por lo que necesitamos usar el nombre 'cls' en su lugar.
Para codificar estos vectores a números:
data.test.cls = np.array([label.argmax() for label in data.test.labels])
Ahora podemos ver la clase para las primeras cinco imágenes en el conjunto de pruebas.
print (data.test.cls[0:5])
se obtiene
array([7, 2, 1, 0, 4, 1])
Comparemos estos con los vectores codificados One-Hot de arriba. Por ejemplo, la clase para la primera imagen es 7, que corresponde a un vector codificado One-Hot donde todos los elementos son cero excepto el elemento con índice 7.
El siguiente paso es definir algunas variables que se usaran en el código. Estas variables y sus valores constantes nos permitirá tener un código más limpio y fácil de leer.
El primer paso es definir un conjunto de variables para dar formato a las dimensiones de nuestras imágenes:
# We know that MNIST images are 28 pixels in each dimension.
img_size = 28
# Images are stored in one-dimensional arrays of this length.
img_size_flat = img_size * img_size
# Tuple with height and width of images used to reshape arrays.
img_shape = (img_size, img_size)
# Number of colour channels for the images: 1 channel for gray-scale.
num_channels = 1
# Number of classes, one class for each of 10 digits.
num_classes = 10
Ahora vamos a definir un conjunto de variables par la configuración de la red neuronal convolucional.
# Convolutional Layer 1.
filter_size1 = 5 # Convolution filters are 5 x 5 pixels.
num_filters1 = 16 # There are 16 of these filters.
# Convolutional Layer 2.
filter_size2 = 5 # Convolution filters are 5 x 5 pixels.
num_filters2 = 36 # There are 36 of these filters.
# Fully-connected layer.
fc_size = 128 # Number of neurons in fully-connected layer.
Como se observa, hemos definido el conjunto de variables para definir las propiedades de cada capa de nuestra red neuronal que usaremos luego para crear nuestro grafo en TensorFlow.
Ahora crearemos una función que es utilizada para trazar 9 imágenes en una cuadrícula de 3x3 y escribir las clases verdaderas y predichas debajo de cada imagen.
def plot_images(images, cls_true, cls_pred=None):
assert len(images) == len(cls_true) == 9
# Create figure with 3x3 sub-plots.
fig, axes = plt.subplots(3, 3)
fig.subplots_adjust(hspace=0.5, wspace=0.5)
for i, ax in enumerate(axes.flat):
# Plot image.
ax.imshow(images[i].reshape(img_shape), cmap='binary')
# Show true and predicted classes.
if cls_pred is None:
xlabel = "True: {0}".format(cls_true[i])
else:
xlabel = "True: {0}, Pred: {1}".format(cls_true[i], cls_pred[i])
ax.set_xlabel(xlabel)
# Remove ticks from the plot.
ax.set_xticks([])
ax.set_yticks([])
Dibujemos algunas imágenes para ver si los datos son correctos
# Get the first images from the test-set.
images = data.test.images[0:9]
# Get the true classes for those images.
cls_true = data.test.cls[0:9]
# Plot the images and labels using our helper-function above.
plot_images(images=images, cls_true=cls_true)
El siguiente paso es crear unas funciones que nos permite dar valor a los pesos y a los sesgos (bias), paro los filtros usados en las capas convolucionales.
def new_weights(shape):
return tf.Variable(tf.truncated_normal(shape, stddev=0.05))
def new_biases(length):
return tf.Variable(tf.constant(0.05, shape=[length]))
Vamos a ecribir una función que nos permita crea una nueva capa convolucional en el grafo computacional para TensorFlow.
Se supone que la entrada es un tensor de 4 dim con las siguientes dimensiones:
Tenga en cuenta que los canales de entrada pueden ser canales de color, o pueden ser canales de filtro si la entrada se produce a partir de una capa convolucional anterior.
La salida es otro tensor de 4-dim. Con las siguientes dimensiones:
Pasemos a definir esta función:
def new_conv_layer(input, # The previous layer.
num_input_channels, # Num. channels in prev. layer.
filter_size, # Width and height of each filter.
num_filters, # Number of filters.
use_pooling=True): # Use 2x2 max-pooling.
# Shape of the filter-weights for the convolution.
# This format is determined by the TensorFlow API.
shape = [filter_size, filter_size, num_input_channels, num_filters]
# Create new weights aka. filters with the given shape.
weights = new_weights(shape=shape)
# Create new biases, one for each filter.
biases = new_biases(length=num_filters)
# Create the TensorFlow operation for convolution.
# Note the strides are set to 1 in all dimensions.
# The first and last stride must always be 1,
# because the first is for the image-number and
# the last is for the input-channel.
# But e.g. strides=[1, 2, 2, 1] would mean that the filter
# is moved 2 pixels across the x- and y-axis of the image.
# The padding is set to 'SAME' which means the input image
# is padded with zeroes so the size of the output is the same.
layer = tf.nn.conv2d(input=input,
filter=weights,
strides=[1, 1, 1, 1],
padding='SAME')
# Add the biases to the results of the convolution.
# A bias-value is added to each filter-channel.
layer += biases
# Use pooling to down-sample the image resolution?
if use_pooling:
# This is 2x2 max-pooling, which means that we
# consider 2x2 windows and select the largest value
# in each window. Then we move 2 pixels to the next window.
layer = tf.nn.max_pool(value=layer,
ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1],
padding='SAME')
# Rectified Linear Unit (ReLU).
# It calculates max(x, 0) for each input pixel x.
# This adds some non-linearity to the formula and allows us
# to learn more complicated functions.
layer = tf.nn.relu(layer)
# Note that ReLU is normally executed before the pooling,
# but since relu(max_pool(x)) == max_pool(relu(x)) we can
# save 75% of the relu-operations by max-pooling first.
# We return both the resulting layer and the filter-weights
# because we will plot the weights later.
return layer, weights
con esta función podemos crear ahora un capa convolucional cada vez que la necesitemos agregar en nuestro grafo.
Una capa convolucional produce un tensor de salida con 4 dimensiones. Ya que hemos de agregar capas de neuronas completamente conectadas después de las capas de convolución, debemos reducir la dimensionalidad del tensor de 4-dimensional a 2-dimensional para que ésta se pueda usarse como entrada a la capa totalmente conectada.
Creamos la función flatten_layer()
def flatten_layer(layer):
# Get the shape of the input layer.
layer_shape = layer.get_shape()
# The shape of the input layer is assumed to be:
# layer_shape == [num_images, img_height, img_width, num_channels]
# The number of features is: img_height * img_width * num_channels
# We can use a function from TensorFlow to calculate this.
num_features = layer_shape[1:4].num_elements()
# Reshape the layer to [num_images, num_features].
# Note that we just set the size of the second dimension
# to num_features and the size of the first dimension to -1
# which means the size in that dimension is calculated
# so the total size of the tensor is unchanged from the reshaping.
layer_flat = tf.reshape(layer, [-1, num_features])
# The shape of the flattened layer is now:
# [num_images, img_height * img_width * num_channels]
# Return both the flattened layer and the number of features.
return layer_flat, num_features
Esta función crea una nueva capa completamente conectada en el gráfo para TensorFlow. Se supone que la entrada es un tensor de forma 2-dim [shape_image, num_inputs]. La salida es un tensor de forma 2-dim [Num_images, num_outputs].
def new_fc_layer(input, # The previous layer.
num_inputs, # Num. inputs from prev. layer.
num_outputs, # Num. outputs.
use_relu=True): # Use Rectified Linear Unit (ReLU)?
# Create new weights and biases.
weights = new_weights(shape=[num_inputs, num_outputs])
biases = new_biases(length=num_outputs)
# Calculate the layer as the matrix multiplication of
# the input and weights, and then add the bias-values.
layer = tf.matmul(input, weights) + biases
# Use ReLU?
if use_relu:
layer = tf.nn.relu(layer)
return layer
Creamos las variables de marcador de posición como hicimos en el Tutorial I
x = tf.placeholder(tf.float32, shape=[None, img_size_flat], name='x')
x_image = tf.reshape(x, [-1, img_size, img_size, num_channels])
y_true = tf.placeholder(tf.float32, shape=[None, num_classes], name='y_true')
y_true = tf.placeholder(tf.float32, shape=[None, num_classes], name='y_true')
y_true_cls = tf.argmax(y_true, dimension=1)
A continuación vamos a crear las capas de convlució.
La primera capa convolucional toma x_image como entrada y crea num_filters1 filtros diferentes, cada uno con ancho y alto igual a filter_size1. Luego reducimos el tamaño de la imagen a la mitad usando 2x2 max-pooling.
layer_conv1, weights_conv1 = \
new_conv_layer(input=x_image,
num_input_channels=num_channels,
filter_size=filter_size1,
num_filters=num_filters1,
use_pooling=True)
Verificamos la forma del tensor que sale por la capa convolucional. Debe ser (?, 14, 14, 16) lo que significa que hay un número arbitrario de imágenes (?), Cada imagen tiene 14 píxeles de ancho y 14 píxeles de alto, y hay 16 canales diferentes, un canal para cada uno de los filtros (ver Flowchart discutido anteriormente).
print(layer_conv1)
Tensor("Relu:0", shape=(?, 14, 14, 16), dtype=float32)
Convolucional Layer 2
Cree la segunda capa convolucional, que toma como entrada la salida de la primera capa convolucional
layer_conv2, weights_conv2 = \
new_conv_layer(input=layer_conv1,
num_input_channels=num_filters1,
filter_size=filter_size2,
num_filters=num_filters2,
use_pooling=True
verificamos que todo marche bien
print (layer_conv2)
Tensor("Relu_1:0", shape=(?, 7, 7, 36), dtype=float32)
OK, según nuestro FLowchart
Las capas convolucionales sacan tensores de 4 dim. Ahora deseamos utilizarlos como entrada en una red totalmente conectada, lo que requiere que los tensores se transformen o se aplanen en tensores de 2-dim.
layer_flat, num_features = flatten_layer(layer_conv2)
Verificamos que los tensores ahora tengan la forma (?, 1764), lo que significa que hay un número arbitrario de imágenes que se han aplanado en vectores de longitud 1764 cada uno. Tenga en cuenta que 1764 = 7 x 7 x 36.
print(layer_flat)
nos da
Tensor("Reshape_1:0", shape=(?, 1764), dtype=float32)
print(num_features)
1764
Ahora creamos las dos últimas capas de nuestro grafos.
Agregamos una capa completamente conectada a la red. La entrada de esta capa, es la capa aplanada de la convolución anterior. La cantidad de neuronas o nodos en la capa completamente conectada es fc_size=128. Luego usamos la función ReLU para la transformación no-lenial que nos ayudara durante el aprendizaje.
layer_fc1 = new_fc_layer(input=layer_flat,
num_inputs=num_features,
num_outputs=fc_size,
use_relu=True)
print(layer_fc1)
Tensor("Relu_2:0", shape=(?, 128), dtype=float32)
Agregamos la otra capa completamente conectada que produzca vectores de longitud 10 para determinar a cuál de las 10 clases pertenece la imagen de entrada. Note que la función ReLU no se usa en esta capa.
layer_fc2 = new_fc_layer(input=layer_fc1,
num_inputs=fc_size,
num_outputs=num_classes,
use_relu=False)
prin(layer_fc2)
Tensor("add_3:0", shape=(?, 10), dtype=float32)
La segunda capa totalmente conectada estima qué tan probable es que la imagen de entrada pertenezca a cada una de las 10 clases.
y_pred = tf.nn.softmax(layer_fc2)
El número de clase es el índice del elemento más grande.
y_pred_cls = tf.argmax(y_pred, dimension=1)
Del mismo modo que en el Tutorial I, creamos la función de coste a optimizar, el método de optimización y las medidas de desempeño (ver Tutorial I para más detalles)
entonces:
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=layer_fc2,labels=y_true)
cost = tf.reduce_mean(cross_entropy)
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4).minimize(cost)
correct_prediction = tf.equal(y_pred_cls, y_true_cls)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
Una vez que se ha creado el grafo de TensorFlow, tenemos que crear una sesión de TensorFlow que se utiliza para ejecutarlo.
session = tf.Session()
Las variables para pesos y sesgos deben inicializarse antes de comenzar a optimizarlas.
session.run(tf.global_variables_initializer())
creamos la función de iteración para el proceso de optimización:
train_batch_size = 64
# Counter for total number of iterations performed so far.
total_iterations = 0
def optimize(num_iterations):
# Ensure we update the global variable rather than a local copy.
global total_iterations
# Start-time used for printing time-usage below.
start_time = time.time()
for i in range(total_iterations,
total_iterations + num_iterations):
# Get a batch of training examples.
# x_batch now holds a batch of images and
# y_true_batch are the true labels for those images.
x_batch, y_true_batch = data.train.next_batch(train_batch_size)
# Put the batch into a dict with the proper names
# for placeholder variables in the TensorFlow graph.
feed_dict_train = {x: x_batch,
y_true: y_true_batch}
# Run the optimizer using this batch of training data.
# TensorFlow assigns the variables in feed_dict_train
# to the placeholder variables and then runs the optimizer.
session.run(optimizer, feed_dict=feed_dict_train)
# Print status every 100 iterations.
if i % 100 == 0:
# Calculate the accuracy on the training-set.
acc = session.run(accuracy, feed_dict=feed_dict_train)
# Message for printing.
msg = "Optimization Iteration: {0:>6}, Training Accuracy: {1:>6.1%}"
# Print it.
print(msg.format(i + 1, acc))
# Update the total number of iterations performed.
total_iterations += num_iterations
# Ending time.
end_time = time.time()
# Difference between start and end-times.
time_dif = end_time - start_time
# Print the time-usage.
print("Time usage: " + str(timedelta(seconds=int(round(time_dif)))))
Ahora crearemos algunas funciones para el monitoreo del comportamiento del modelo.
def plot_example_errors(cls_pred, correct):
# This function is called from print_test_accuracy() below.
# cls_pred is an array of the predicted class-number for
# all images in the test-set.
# correct is a boolean array whether the predicted class
# is equal to the true class for each image in the test-set.
# Negate the boolean array.
incorrect = (correct == False)
# Get the images from the test-set that have been
# incorrectly classified.
images = data.test.images[incorrect]
# Get the predicted classes for those images.
cls_pred = cls_pred[incorrect]
# Get the true classes for those images.
cls_true = data.test.cls[incorrect]
# Plot the first 9 images.
plot_images(images=images[0:9],
cls_true=cls_true[0:9],
cls_pred=cls_pred[0:9])
def plot_confusion_matrix(cls_pred):
# This is called from print_test_accuracy() below.
# cls_pred is an array of the predicted class-number for
# all images in the test-set.
# Get the true classifications for the test-set.
cls_true = data.test.cls
# Get the confusion matrix using sklearn.
cm = confusion_matrix(y_true=cls_true,
y_pred=cls_pred)
# Print the confusion matrix as text.
print(cm)
# Plot the confusion matrix as an image.
plt.matshow(cm)
# Make various adjustments to the plot.
plt.colorbar()
tick_marks = np.arange(num_classes)
plt.xticks(tick_marks, range(num_classes))
plt.yticks(tick_marks, range(num_classes))
plt.xlabel('Predicted')
plt.ylabel('True')
# Ensure the plot is shown correctly with multiple plots
# in a single Notebook cell.
plt.show()
# Split the test-set into smaller batches of this size.
test_batch_size = 256
def print_test_accuracy(show_example_errors=False,
show_confusion_matrix=False):
# Number of images in the test-set.
num_test = len(data.test.images)
# Allocate an array for the predicted classes which
# will be calculated in batches and filled into this array.
cls_pred = np.zeros(shape=num_test, dtype=np.int)
# Now calculate the predicted classes for the batches.
# We will just iterate through all the batches.
# There might be a more clever and Pythonic way of doing this.
# The starting index for the next batch is denoted i.
i = 0
while i < num_test:
# The ending index for the next batch is denoted j.
j = min(i + test_batch_size, num_test)
# Get the images from the test-set between index i and j.
images = data.test.images[i:j, :]
# Get the associated labels.
labels = data.test.labels[i:j, :]
# Create a feed-dict with these images and labels.
feed_dict = {x: images,
y_true: labels}
# Calculate the predicted class using TensorFlow.
cls_pred[i:j] = session.run(y_pred_cls, feed_dict=feed_dict)
# Set the start-index for the next batch to the
# end-index of the current batch.
i = j
# Convenience variable for the true class-numbers of the test-set.
cls_true = data.test.cls
# Create a boolean array whether each image is correctly classified.
correct = (cls_true == cls_pred)
# Calculate the number of correctly classified images.
# When summing a boolean array, False means 0 and True means 1.
correct_sum = correct.sum()
# Classification accuracy is the number of correctly classified
# images divided by the total number of images in the test-set.
acc = float(correct_sum) / num_test
# Print the accuracy.
msg = "Accuracy on Test-Set: {0:.1%} ({1} / {2})"
print(msg.format(acc, correct_sum, num_test))
# Plot some examples of mis-classifications, if desired.
if show_example_errors:
print("Example errors:")
plot_example_errors(cls_pred=cls_pred, correct=correct)
# Plot the confusion matrix, if desired.
if show_confusion_matrix:
print("Confusion Matrix:")
plot_confusion_matrix(cls_pred=cls_pred)
Miremos la precisión antes de cualquier optimización.
print_test_accuracy()
optenemos
Accuracy on Test-Set: 10.5% (1048 / 10000)
Como vemos, la precisión del modelo es muy baja. Tiene sentido, puesto que no hemos optimizado ningún parámetro.
Mirémos ahora depuès de una iteración en el proceso de optimización:
optimize(num_iterations=1)
Optimization Iteration: 1, Training Accuracy: 12.5% Time usage: 0:00:00
Veamos que ocurre
print_test_accuracy()
como vemos como el modelo mejora un poco su precisión.
Rendimiento después de 100 iteraciones de optimización
optimize(num_iterations=100) # We already performed 1 iteration above.
Optimization Iteration: 101, Training Accuracy: 71.9% Time usage: 0:00:09
print_test_accuracy()
Accuracy on Test-Set: 66.1% (6608 / 10000)
Después de 100 iteraciones de optimización, el modelo ha mejorado significativamente su precisión de clasificación.
Rendimiento después de 1000 iteraciones de optimización
optimize(num_iterations=900) # We performed 100 iterations above.
Optimization Iteration: 101, Training Accuracy: 71.9%
Optimization Iteration: 201, Training Accuracy: 79.7%
Optimization Iteration: 301, Training Accuracy: 71.9%
Optimization Iteration: 401, Training Accuracy: 81.2%
Optimization Iteration: 501, Training Accuracy: 85.9%
Optimization Iteration: 601, Training Accuracy: 95.3%
Optimization Iteration: 701, Training Accuracy: 90.6%
Optimization Iteration: 801, Training Accuracy: 90.6%
Optimization Iteration: 901, Training Accuracy: 96.9%
Time usage: 0:02:01
print_test_accuracy()
Accuracy on Test-Set: 92.6% (9262 / 10000)
Después de 1000 iteraciones de optimización, el modelo ha incrementado enormemente su precisión en el conjunto de pruebas a más del 90%
Rendimiento después de 10.000 iteraciones de optimización
optimize(num_iterations=9000)
Optimization Iteration: 9601, Training Accuracy: 98.4%
Optimization Iteration: 9701, Training Accuracy: 96.9%
Optimization Iteration: 9801, Training Accuracy: 100.0%
Optimization Iteration: 9901, Training Accuracy: 98.4%
Time usage: 0:20:36
print_test_accuracy(show_confusion_matrix=True)
Accuracy on Test-Set: 66.1% (6608 / 10000) Confusion Matrix:
[[ 633 0 54 134 1 3 139 3 13 0]
[ 0 1073 0 42 13 0 4 0 3 0]
[ 1 6 557 327 7 0 116 15 3 0]
[ 0 3 14 972 1 0 7 11 0 2]
[ 0 21 11 68 766 0 47 8 2 59]
[ 0 24 6 550 56 135 75 7 31 8]
[ 0 24 43 22 4 1 858 0 6 0]
[ 0 23 7 58 10 0 0 894 0 36]
[ 0 44 1 688 16 6 44 14 154 7]
[ 2 21 16 111 148 0 2 143 0 566]]
Después de 10,000 iteraciones de optimización, el modelo tiene una precisión de clasificación en el conjunto de prueba de aproximadamente el 99%. Esta vez note que hemos incluido la matriz de confusión como parámetro dentro de la función print_test_accuracy, para tener más información a cerca del rendimiento del modelo.
Como hemos visto en éste tutorial, una Red Neural Convolucional funciona mucho mejor al reconocer los dígitos escritos a mano, que el modelo de regresión lineal implementado en el Tutorial I. La red Convolucional obtiene una precisión de clasificación de aproximadamente el 99% en comparación con solo el 91% para el modelo lineal simple.
Sin embargo, la Red convolucional también es mucho más complicada de implementar, y no es obvio mirar los pesos de filtro por qué funciona y por qué a veces falla.