Publicado por el equipo de TensorFlow
En TensorFlow 1.3 se introducen dos funciones importantes que te recomendamos probar:
- Conjuntos de datos: una forma completamente nueva de crear procesos de entrada (es decir, lectura de datos para tu programa).
- Estimadores: un método de alto nivel para crear modelos de TensorFlow. Los estimadores incluyen modelos preconfigurados para tareas comunes de aprendizaje automático, pero también puedes usarlos para crear tus propios modelos personalizados.
A continuación, te mostramos cómo se adecuan a la arquitectura de TensorFlow. Juntos ofrecen una forma sencilla de crear modelos de TensorFlow y enviarles datos:
Nuestro modelo de ejemplo
Para explorar estas funciones, compilaremos un modelo y te mostraremos fragmentos de código importantes. El código completo está disponible
aquí y se incluyen instrucciones para obtener los archivos de capacitación y prueba. Ten en cuenta que el código se escribió para demostrar la funcionalidad de los conjuntos de datos y los estimadores, pero no se optimizó para brindar el máximo rendimiento.
El modelo entrenado categoriza las flores de iris en función de cuatro características botánicas (longitud del
sépalo, ancho del sépalo, longitud del
pétalo y ancho del pétalo). Por lo tanto, durante la inferencia, puedes proporcionar valores para esas cuatro características y el modelo predecirá que la flor corresponde a una de las tres hermosas variedades siguientes:
De izquierda a derecha:
Iris setosa (por
Radomil, CC BY-SA 3.0),
iris versicolor (por
Dlanglois, CC BY-SA 3.0) e
iris virginica(por
Frank Mayfield, CC BY-SA 2.0).
Prepararemos un clasificador de red neuronal profunda con la estructura que se indica a continuación. Todos los valores de entrada y salida serán
float32
, y la suma de los valores de salida será igual a 1 (ya que se predice la probabilidad para cada tipo de iris individual):
Por ejemplo, un resultado de salida podría ser 0,05 para iris setosa, 0,9 para iris versicolor y 0,05 para iris virginica, lo cual indica un 90% de probabilidad de que se trate de una iris versicolor.
¡Muy bien! Ahora que definimos el modelo, veamos cómo podemos usar conjuntos de datos y estimadores para prepararlo y realizar predicciones.
Introducción a los conjuntos de datos
Los conjuntos de datos son una nueva manera de crear procesos de entrada para modelos de TensorFlow. Esta API ofrece un rendimiento muy superior al que se obtiene usando feed_dict o los procesos basados en cola, y es más limpia y fácil de usar. Si bien la Datasets API todavía reside en tf.contrib.data en la versión 1.3, prevemos que pase a ser central en la versión 1.4, por lo que este es el mejor momento para probarla.
A grandes rasgos, Datasets API está compuesta por las siguientes clases:
Donde:
- Dataset: clase básica que contiene los métodos para crear y transformar conjuntos de datos. También te permite inicializar un conjunto de datos a partir de datos en la memoria, o de un generador Python.
- TextLineDataset: lee líneas de archivos de texto.
- TFRecordDataset: lee registros de archivos TFRecord.
- FixedLengthRecordDataset: lee registros de tamaño fijo de archivos binarios.
- Iterator: proporciona una forma de acceder a un elemento del conjunto de datos a la vez.
Nuestro conjunto de datos
Para comenzar, observemos el conjunto de datos que emplearemos en nuestro modelo. Leeremos datos de un archivo CSV, donde cada fila contendrá cinco valores (los cuatro valores de entrada y la etiqueta):
La etiqueta será la siguiente:
- 0 para iris setosa;
- 1 para versicolor;
- 2 para virginica.
Representación de nuestro conjunto de datos
Para describir nuestro conjunto de datos, primero crearemos una lista de nuestras funciones:
feature_names = [
'SepalLength',
'SepalWidth',
'PetalLength',
'PetalWidth']
Cuando preparemos nuestro modelo, necesitaremos una función que lea el archivo de entrada y muestre los datos de la función y la etiqueta. La Estimators API exige la creación una función con el siguiente formato:
def input_fn():
...<code>...
return ({ 'SepalLength':[values], ..<etc>.., 'PetalWidth':[values] },
[IrisFlowerType])
El valor que se muestra debe ser una tupla de dos elementos organizada de la siguiente manera:
- El primero debe ser un elemento dict en el que cada función de entrada sea una clave y luego una lista de valores para el lote de preparación.
- El segundo elemento es una lista de etiquetas para el lote de preparación.
Debido a que mostraremos un lote de funciones de entrada y etiquetas de preparación, todas las listas de la instrucción return tendrán la misma extensión. En términos técnicos, siempre que mencionamos una “lista” en realidad nos referimos a un tensor TensorFlow 1-d.
Para permitir la reutilización simple de
input_fn
, le agregaremos algunos argumentos. Esto nos permite compilar funciones de entrada con diferentes configuraciones. Los argumentos son bastante simples:
file_path
: indica el archivo de datos que se leerá.
perform_shuffle
: indica si se debe aleatorizar el orden de los registros.
repeat_count
: indica la cantidad de veces que se deben iterar los registros del conjunto de datos. Si se especifica 1, por ejemplo, cada registro se leerá una vez. Si se especifica None, la iteración continuará sin detenerse.
Aquí te mostramos la manera de implementar esta función usando la Dataset API. Integraremos esto en una “función de entrada” que resulte adecuada cuando agreguemos datos a nuestro modelo de estimador, más adelante:
def my_input_fn(file_path, perform_shuffle=False, repeat_count=1):
def decode_csv(line):
parsed_line = tf.decode_csv(line, [[0.], [0.], [0.], [0.], [0]])
label = parsed_line[-1:] # Last element is the label
del parsed_line[-1] # Delete last element
features = parsed_line # Everything (but last element) are the features
d = dict(zip(feature_names, features)), label
return d
dataset = (tf.contrib.data.TextLineDataset(file_path) # Read text file
.skip(1) # Skip header row
.map(decode_csv)) # Transform each elem by applying decode_csv fn
if perform_shuffle:
# Randomizes input using a window of 256 elements (read into memory)
dataset = dataset.shuffle(buffer_size=256)
dataset = dataset.repeat(repeat_count) # Repeats dataset this # times
dataset = dataset.batch(32) # Batch size to use
iterator = dataset.make_one_shot_iterator()
batch_features, batch_labels = iterator.get_next()
return batch_features, batch_labels
Ten en cuenta lo siguiente:
TextLineDataset
: La Dataset API realizará por ti un enorme trabajo de administración de memoria cuando uses sus conjuntos de datos basados en archivos. Por ejemplo, puedes leer archivos del conjunto de datos mucho más grandes que la memoria o leer varios archivos especificando una lista como argumento.
shuffle
: lee registros buffer_size y luego cambia (aleatoriza) el orden de estos.
map
: llama a la función decode_csv
con cada elemento del conjunto de datos como argumento (debido a que usaremos TextLineDataset, cada elemento será una línea de texto CSV). Luego, aplicaremos decode_csv
a cada una de las líneas.
decode_csv
: divide cada línea en campos y proporciona valores predeterminados si es necesario. Luego muestra un dict con las claves y los valores de campo. La función de mapa actualiza cada elemento (línea) del conjunto de datos con el dict.
Esta fue una introducción a los conjuntos de datos. Solo por diversión, ahora podemos usar esta función para imprimir el primer lote:
next_batch = my_input_fn(FILE, True) # Will return 32 random elements
# Now let's try it out, retrieving and printing one batch of data.
# Although this code looks strange, you don't need to understand
# the details.
with tf.Session() as sess:
first_batch = sess.run(next_batch)
print(first_batch)
# Output
({'SepalLength': array([ 5.4000001, ...<repeat to 32 elems>], dtype=float32),
'PetalWidth': array([ 0.40000001, ...<repeat to 32 elems>], dtype=float32),
...
},
[array([[2], ...<repeat to 32 elems>], dtype=int32) # Labels
)
Eso es todo lo que necesitamos de la Dataset API para implementar nuestro modelo. No obstante, los conjuntos de datos ofrecen muchas más capacidades. Observa la sección final de esta publicación, donde hemos recopilado más recursos.
Introducción a los estimadores
La Estimators API es una API de alto nivel que reduce notablemente el código estándar que antes necesitabas escribir cuando preparabas un modelo de TensorFlow. Los estimadores también son muy flexibles y te permiten anular el comportamiento predeterminado si tu modelo tiene requisitos específicos.
Existen dos formas de compilar tu modelo usando la Estimators API:
- Estimadores preconfigurados: son estimadores predefinidos, creados para generar un tipo de modelo específico. En esta entrada de blog, usaremos el estimador preconfigurado DNNClassifier.
- Estimator (clase básica): te permite controlar por completo el método de creación de tu modelo usando una función model_fn. En otra entrada del blog trataremos la manera de hacer esto.
A continuación, te mostramos el diagrama de clases para los estimadores:
Prevemos agregar más estimadores preconfigurados en futuras versiones.
Como puedes ver, todos los estimadores usan
input_fn
, que proporciona datos de entrada al estimador. En nuestro caso, reutilizaremos la función
my_input_fn
que definimos para este fin.
El siguiente código crea una instancia del estimador que predice el tipo de flor iris:
# Create the feature_columns, which specifies the input to our model.
# All our input features are numeric, so use numeric_column for each one.
feature_columns = [tf.feature_column.numeric_column(k) for k in feature_names]
# Create a deep neural network regression classifier.
# Use the DNNClassifier pre-made estimator
classifier = tf.estimator.DNNClassifier(
feature_columns=feature_columns, # The input features to our model
hidden_units=[10, 10], # Two layers, each with 10 neurons
n_classes=3,
model_dir=PATH) # Path to where checkpoints etc are stored
Ahora tenemos un estimador que podemos empezar a preparar.
Preparación del modelo
La preparación se realiza usando una sola línea de código de TensorFlow:
# Train our model, use the previously function my_input_fn
# Input to training is a file with training example
# Stop training after 8 iterations of train data (epochs)
classifier.train(
input_fn=lambda: my_input_fn(FILE_TRAIN, True, 8))
Aguarda... ¿Qué es “
lambda:
my_input_fn(FILE_TRAIN, True, 8)
”? Es el espacio en el que vinculamos conjuntos de datos con estimadores. Los estimadores necesitan datos para la preparación, evaluación y predicción, y usan
input_fn
para obtener los datos. Los estimadores requieren una función
input_fn
sin argumentos, por lo que crearemos una función sin argumentos usando
lambda
, que llama a nuestra función input_fn con los argumentos deseados:
file_path, shuffle
setting,
y
repeat_count
. En nuestro caso, usamos nuestra función
my_input_fn,
y le pasamos lo siguiente:
FILE_TRAIN
, que es el archivo de datos de preparación.
True
, que indica al estimador cambiar el orden de los datos.
8
, que indica al estimador reproducir y repetir el conjunto de datos 8 veces.
Evaluación del modelo preparado
Ya tenemos un modelo preparado. ¿Cómo podemos evaluar su funcionamiento? Por suerte, todos los estimadores tienen un método
evaluate
:
# Evaluate our model using the examples contained in FILE_TEST
# Return value will contain evaluation_metrics such as: loss & average_loss
evaluate_result = estimator.evaluate(
input_fn=lambda: my_input_fn(FILE_TEST, False, 4)
print("Evaluation results")
for key in evaluate_result:
print(" {}, was: {}".format(key, evaluate_result[key]))
En nuestro caso, alcanzamos una precisión que ronda el 93%. Por supuesto, existen varias formas de mejorar esa precisión. Una consiste simplemente en ejecutar el programa de forma repetida. Debido a que el estado del modelo es persistente (en
model_dir=PATH
más arriba), este mejorará a medida que lo prepares con más iteraciones, hasta estabilizarse. Otra forma consiste en ajustar la cantidad de capas ocultas o de nodos de cada capa oculta. Haz experimentos con esto libremente, pero ten en cuenta que al realizar un cambio debes eliminar el directorio especificado en
model_dir=PATH
, ya que modificarás la estructura de
DNNClassifier
.
Realización de predicciones con el modelo preparado
¡Eso es todo! Ya tenemos un modelo preparado, y si los resultados de la evaluación son satisfactorios, podemos usarla para predecir una flor de iris en función de algunos datos. Tanto en la preparación como en la evaluación, realizamos predicciones usando una llamada a una función individual:
# Predict the type of some Iris flowers.
# Let's predict the examples in FILE_TEST, repeat only once.
predict_results = classifier.predict(
input_fn=lambda: my_input_fn(FILE_TEST, False, 1))
print("Predictions on test file")
for prediction in predict_results:
# Will print the predicted class, i.e: 0, 1, or 2 if the prediction
# is Iris Sentosa, Vericolor, Virginica, respectively.
print prediction["class_ids"][0]
Realización de predicciones sobre datos de la memoria
En el código anterior se especificó
FILE_TEST
para realizar predicciones sobre datos guardados en un archivo, pero ¿cómo podríamos realizar predicciones en función de datos que residen en otras fuentes, como la memoria? Como podrás suponer, para esto no es necesario modificar nuestra llamada a
predict
. En lugar de ello, configuraremos la Dataset API para usar una estructura de memoria como se muestra a continuación:
# Let create a memory dataset for prediction.
# We've taken the first 3 examples in FILE_TEST.
prediction_input = [[5.9, 3.0, 4.2, 1.5], # -> 1, Iris Versicolor
[6.9, 3.1, 5.4, 2.1], # -> 2, Iris Virginica
[5.1, 3.3, 1.7, 0.5]] # -> 0, Iris Sentosa
def new_input_fn():
def decode(x):
x = tf.split(x, 4) # Need to split into our 4 features
# When predicting, we don't need (or have) any labels
return dict(zip(feature_names, x)) # Then build a dict from them
# The from_tensor_slices function will use a memory structure as input
dataset = tf.contrib.data.Dataset.from_tensor_slices(prediction_input)
dataset = dataset.map(decode)
iterator = dataset.make_one_shot_iterator()
next_feature_batch = iterator.get_next()
return next_feature_batch, None # In prediction, we have no labels
# Predict all our prediction_input
predict_results = classifier.predict(input_fn=new_input_fn)
# Print results
print("Predictions on memory data")
for idx, prediction in enumerate(predict_results):
type = prediction["class_ids"][0] # Get the predicted class (index)
if type == 0:
print("I think: {}, is Iris Sentosa".format(prediction_input[idx]))
elif type == 1:
print("I think: {}, is Iris Versicolor".format(prediction_input[idx]))
else:
print("I think: {}, is Iris Virginica".format(prediction_input[idx])
Dataset.from_tensor_slides()
se designa para conjuntos de datos pequeños que entran en la memoria. Cuando usas
TextLineDataset
, como lo hicimos para el entrenamiento y la evaluación, puedes tener archivos arbitrariamente grandes siempre que tu memoria pueda administrar el búfer de orden aleatorio y los tamaños de los lotes.
Hay más
Usar un estimador preconfigurado como DNNClassifier aporta mucho valor. Además de ser fáciles de usar, los estimadores preconfigurados proporcionan métricas de evaluación integradas y crean resúmenes que puedes ver en TensorBoard. Para ver este informe, inicia TensorBoard desde tu línea de comandos de la siguiente manera:
# Replace PATH with the actual path passed as model_dir argument when the
# DNNRegressor estimator was created.
tensorboard --logdir=PATH
En los siguientes diagramas se muestran algunos de los datos que proporcionará TensorBoard:
Resumen
En esta entrada de blog, exploramos conjuntos de datos y estimadores. Son API importantes para definir el flujo de datos de entrada y crear modelos, por lo que vale la pena dedicar tiempo a aprenderlas.
Para obtener más información, asegúrate de observar lo siguiente:
Esto no termina aquí. En poco tiempo, publicaremos más entradas de blog en las que describiremos cómo funcionan esas API. ¡Permanece atento!
Hasta entonces, ¡feliz codificación con TensorFlow!