Esta guía entrena un modelo de red neuronal para clasificar imágenes de ropa, como zapatillas y camisetas , guarda el modelo entrenado y luego lo muestra con TensorFlow Serving . La atención se centra en TensorFlow Serving, en lugar del modelado y el entrenamiento en TensorFlow, por lo que para obtener un ejemplo completo que se centra en el modelado y el entrenamiento, consulte el ejemplo de Clasificación básica .
Esta guía usa tf.keras , una API de alto nivel para crear y entrenar modelos en TensorFlow.
import sys
# Confirm that we're using Python 3
assert sys.version_info.major is 3, 'Oops, not running Python 3. Use Runtime > Change runtime type'
# TensorFlow and tf.keras
print("Installing dependencies for Colab environment")
!pip install -Uq grpcio==1.26.0
import tensorflow as tf
from tensorflow import keras
# Helper libraries
import numpy as np
import matplotlib.pyplot as plt
import os
import subprocess
print('TensorFlow version: {}'.format(tf.__version__))
Crea tu modelo
Importar el conjunto de datos Fashion MNIST
Esta guía utiliza el conjunto de datos Fashion MNIST que contiene 70.000 imágenes en escala de grises en 10 categorías. Las imágenes muestran prendas de vestir individuales a baja resolución (28 por 28 píxeles), como se ve aquí:
![]() |
Figura 1. Muestras de moda-MNIST (por Zalando, licencia del MIT). |
Fashion MNIST está pensado como un reemplazo directo del clásico conjunto de datos MNIST , que a menudo se usa como el "Hola, mundo" de los programas de aprendizaje automático para la visión por computadora. Puede acceder a Fashion MNIST directamente desde TensorFlow, solo importe y cargue los datos.
fashion_mnist = keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
# scale the values to 0.0 to 1.0
train_images = train_images / 255.0
test_images = test_images / 255.0
# reshape for feeding into the model
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1)
test_images = test_images.reshape(test_images.shape[0], 28, 28, 1)
class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',
'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
print('\ntrain_images.shape: {}, of {}'.format(train_images.shape, train_images.dtype))
print('test_images.shape: {}, of {}'.format(test_images.shape, test_images.dtype))
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz 32768/29515 [=================================] - 0s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz 26427392/26421880 [==============================] - 0s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz 8192/5148 [===============================================] - 0s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz 4423680/4422102 [==============================] - 0s 0us/step train_images.shape: (60000, 28, 28, 1), of float64 test_images.shape: (10000, 28, 28, 1), of float64
Entrena y evalúa tu modelo
Usemos la CNN más simple posible, ya que no estamos enfocados en la parte del modelado.
model = keras.Sequential([
keras.layers.Conv2D(input_shape=(28,28,1), filters=8, kernel_size=3,
strides=2, activation='relu', name='Conv1'),
keras.layers.Flatten(),
keras.layers.Dense(10, name='Dense')
])
model.summary()
testing = False
epochs = 5
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=[keras.metrics.SparseCategoricalAccuracy()])
model.fit(train_images, train_labels, epochs=epochs)
test_loss, test_acc = model.evaluate(test_images, test_labels)
print('\nTest accuracy: {}'.format(test_acc))
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= Conv1 (Conv2D) (None, 13, 13, 8) 80 _________________________________________________________________ flatten (Flatten) (None, 1352) 0 _________________________________________________________________ Dense (Dense) (None, 10) 13530 ================================================================= Total params: 13,610 Trainable params: 13,610 Non-trainable params: 0 _________________________________________________________________ Epoch 1/5 1875/1875 [==============================] - 13s 2ms/step - loss: 0.7546 - sparse_categorical_accuracy: 0.7457 Epoch 2/5 1875/1875 [==============================] - 3s 2ms/step - loss: 0.4254 - sparse_categorical_accuracy: 0.8521 Epoch 3/5 1875/1875 [==============================] - 3s 2ms/step - loss: 0.3812 - sparse_categorical_accuracy: 0.8668 Epoch 4/5 1875/1875 [==============================] - 3s 2ms/step - loss: 0.3557 - sparse_categorical_accuracy: 0.8770 Epoch 5/5 1875/1875 [==============================] - 3s 2ms/step - loss: 0.3415 - sparse_categorical_accuracy: 0.8795 313/313 [==============================] - 1s 2ms/step - loss: 0.3699 - sparse_categorical_accuracy: 0.8694 Test accuracy: 0.8694000244140625
Guarda tu modelo
Para cargar nuestro modelo entrenado en TensorFlow Serving, primero debemos guardarlo en formato SavedModel . Esto creará un archivo protobuf en una jerarquía de directorios bien definida e incluirá un número de versión. TensorFlow Serving nos permite seleccionar qué versión de un modelo o "servible" queremos usar cuando hacemos solicitudes de inferencia. Cada versión se exportará a un subdirectorio diferente en la ruta indicada.
# Fetch the Keras session and save the model
# The signature definition is defined by the input and output tensors,
# and stored with the default serving key
import tempfile
MODEL_DIR = tempfile.gettempdir()
version = 1
export_path = os.path.join(MODEL_DIR, str(version))
print('export_path = {}\n'.format(export_path))
tf.keras.models.save_model(
model,
export_path,
overwrite=True,
include_optimizer=True,
save_format=None,
signatures=None,
options=None
)
print('\nSaved model:')
!ls -l {export_path}
export_path = /tmp/1 INFO:tensorflow:Assets written to: /tmp/1/assets Saved model: total 88 drwxr-xr-x 2 kbuilder kbuilder 4096 Mar 9 10:10 assets -rw-rw-r-- 1 kbuilder kbuilder 78123 Mar 9 10:10 saved_model.pb drwxr-xr-x 2 kbuilder kbuilder 4096 Mar 9 10:10 variables
Examina tu modelo guardado
Usaremos la utilidad de línea de comando saved_model_cli
para ver MetaGraphDefs (los modelos) y SignatureDefs (los métodos que puede llamar) en nuestro SavedModel. Consulta esta discusión sobre la CLI del modelo guardado en la Guía de TensorFlow.
saved_model_cli show --dir {export_path} --all
2021-03-09 10:10:12.685464: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.11.0 MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs: signature_def['__saved_model_init_op']: The given SavedModel SignatureDef contains the following input(s): The given SavedModel SignatureDef contains the following output(s): outputs['__saved_model_init_op'] tensor_info: dtype: DT_INVALID shape: unknown_rank name: NoOp Method name is: signature_def['serving_default']: The given SavedModel SignatureDef contains the following input(s): inputs['Conv1_input'] tensor_info: dtype: DT_FLOAT shape: (-1, 28, 28, 1) name: serving_default_Conv1_input:0 The given SavedModel SignatureDef contains the following output(s): outputs['Dense'] tensor_info: dtype: DT_FLOAT shape: (-1, 10) name: StatefulPartitionedCall:0 Method name is: tensorflow/serving/predict Defined Functions: Function Name: '__call__' Option #1 Callable with: Argument #1 Conv1_input: TensorSpec(shape=(None, 28, 28, 1), dtype=tf.float32, name='Conv1_input') Argument #2 DType: bool Value: False Argument #3 DType: NoneType Value: None Option #2 Callable with: Argument #1 inputs: TensorSpec(shape=(None, 28, 28, 1), dtype=tf.float32, name='inputs') Argument #2 DType: bool Value: False Argument #3 DType: NoneType Value: None Option #3 Callable with: Argument #1 inputs: TensorSpec(shape=(None, 28, 28, 1), dtype=tf.float32, name='inputs') Argument #2 DType: bool Value: True Argument #3 DType: NoneType Value: None Option #4 Callable with: Argument #1 Conv1_input: TensorSpec(shape=(None, 28, 28, 1), dtype=tf.float32, name='Conv1_input') Argument #2 DType: bool Value: True Argument #3 DType: NoneType Value: None Function Name: '_default_save_signature' Option #1 Callable with: Argument #1 Conv1_input: TensorSpec(shape=(None, 28, 28, 1), dtype=tf.float32, name='Conv1_input') Function Name: 'call_and_return_all_conditional_losses' Option #1 Callable with: Argument #1 inputs: TensorSpec(shape=(None, 28, 28, 1), dtype=tf.float32, name='inputs') Argument #2 DType: bool Value: False Argument #3 DType: NoneType Value: None Option #2 Callable with: Argument #1 Conv1_input: TensorSpec(shape=(None, 28, 28, 1), dtype=tf.float32, name='Conv1_input') Argument #2 DType: bool Value: True Argument #3 DType: NoneType Value: None Option #3 Callable with: Argument #1 Conv1_input: TensorSpec(shape=(None, 28, 28, 1), dtype=tf.float32, name='Conv1_input') Argument #2 DType: bool Value: False Argument #3 DType: NoneType Value: None Option #4 Callable with: Argument #1 inputs: TensorSpec(shape=(None, 28, 28, 1), dtype=tf.float32, name='inputs') Argument #2 DType: bool Value: True Argument #3 DType: NoneType Value: None
¡Eso nos dice mucho sobre nuestro modelo! En este caso, acabamos de entrenar nuestro modelo, por lo que ya conocemos las entradas y salidas, pero si no lo supiéramos, esta sería información importante. No nos dice todo, como el hecho de que se trata de datos de imagen en escala de grises, por ejemplo, pero es un gran comienzo.
Sirva su modelo con TensorFlow Serving
Agrega el URI de distribución de TensorFlow Serving como fuente del paquete:
Nos estamos preparando para instalar TensorFlow Serving con Aptitude, ya que este Colab se ejecuta en un entorno Debian. tensorflow-model-server
el tensorflow-model-server
a la lista de paquetes que conoce Aptitude. Tenga en cuenta que estamos ejecutando como root.
import sys
# We need sudo prefix if not on a Google Colab.
if 'google.colab' not in sys.modules:
SUDO_IF_NEEDED = 'sudo'
else:
SUDO_IF_NEEDED = ''
# This is the same as you would do from your command line, but without the [arch=amd64], and no sudo
# You would instead do:
# echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | sudo tee /etc/apt/sources.list.d/tensorflow-serving.list && \
# curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | sudo apt-key add -
!echo "deb http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | {SUDO_IF_NEEDED} tee /etc/apt/sources.list.d/tensorflow-serving.list && \
curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | {SUDO_IF_NEEDED} apt-key add -
!{SUDO_IF_NEEDED} apt update
deb http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 2943 100 2943 0 0 15822 0 --:--:-- --:--:-- --:--:-- 15822 OK Hit:1 http://asia-east1.gce.archive.ubuntu.com/ubuntu bionic InRelease Hit:2 http://asia-east1.gce.archive.ubuntu.com/ubuntu bionic-updates InRelease Hit:3 http://asia-east1.gce.archive.ubuntu.com/ubuntu bionic-backports InRelease Hit:4 http://packages.cloud.google.com/apt google-cloud-logging-wheezy InRelease Get:5 https://packages.cloud.google.com/apt eip-cloud-bionic InRelease [5419 B] Hit:6 https://nvidia.github.io/libnvidia-container/stable/ubuntu18.04/amd64 InRelease Hit:7 https://nvidia.github.io/nvidia-container-runtime/ubuntu18.04/amd64 InRelease Get:8 https://nvidia.github.io/nvidia-docker/ubuntu18.04/amd64 InRelease [1129 B] Get:9 http://storage.googleapis.com/tensorflow-serving-apt stable InRelease [3012 B] Hit:10 http://archive.canonical.com/ubuntu bionic InRelease Hit:11 http://security.ubuntu.com/ubuntu bionic-security InRelease Get:12 http://storage.googleapis.com/tensorflow-serving-apt stable/tensorflow-model-server amd64 Packages [340 B] Get:13 http://storage.googleapis.com/tensorflow-serving-apt stable/tensorflow-model-server-universal amd64 Packages [348 B] Fetched 10.2 kB in 1s (7051 B/s) 114 packages can be upgraded. Run 'apt list --upgradable' to see them.
Instalar publicación de TensorFlow
Esto es todo lo que necesita: ¡una línea de comando!
{SUDO_IF_NEEDED} apt-get install tensorflow-model-server
The following packages were automatically installed and are no longer required: adwaita-icon-theme ca-certificates-java dconf-gsettings-backend dconf-service default-jre default-jre-headless dkms fonts-dejavu-extra freeglut3 freeglut3-dev g++-6 glib-networking glib-networking-common glib-networking-services gsettings-desktop-schemas gtk-update-icon-cache hicolor-icon-theme humanity-icon-theme java-common libaccinj64-9.1 libasound2 libasound2-data libasyncns0 libatk-bridge2.0-0 libatk-wrapper-java libatk-wrapper-java-jni libatk1.0-0 libatk1.0-data libatspi2.0-0 libavahi-client3 libavahi-common-data libavahi-common3 libcairo-gobject2 libcolord2 libcroco3 libcudart9.1 libcufft9.1 libcufftw9.1 libcups2 libcurand9.1 libcusolver9.1 libcusparse9.1 libdconf1 libdrm-amdgpu1 libdrm-dev libdrm-intel1 libdrm-nouveau2 libdrm-radeon1 libegl-mesa0 libegl1 libegl1-mesa libepoxy0 libflac8 libfontenc1 libgbm1 libgdk-pixbuf2.0-0 libgdk-pixbuf2.0-common libgif7 libgl1 libgl1-mesa-dev libgl1-mesa-dri libglapi-mesa libgles1 libgles2 libglu1-mesa libglu1-mesa-dev libglvnd-core-dev libglvnd-dev libglvnd0 libglx-mesa0 libglx0 libgtk-3-0 libgtk-3-common libgtk2.0-0 libgtk2.0-common libice-dev libjansson4 libjson-glib-1.0-0 libjson-glib-1.0-common liblcms2-2 libllvm9 libnppc9.1 libnppial9.1 libnppicc9.1 libnppicom9.1 libnppidei9.1 libnppif9.1 libnppig9.1 libnppim9.1 libnppist9.1 libnppisu9.1 libnppitc9.1 libnpps9.1 libnvrtc9.1 libnvtoolsext1 libnvvm3 libogg0 libopengl0 libpciaccess0 libpcsclite1 libproxy1v5 libpthread-stubs0-dev libpulse0 librest-0.7-0 librsvg2-2 librsvg2-common libsensors4 libsm-dev libsndfile1 libsoup-gnome2.4-1 libsoup2.4-1 libstdc++-6-dev libthrust-dev libvdpau-dev libvdpau1 libvorbis0a libvorbisenc2 libwayland-client0 libwayland-cursor0 libwayland-egl1 libwayland-server0 libx11-dev libx11-xcb-dev libx11-xcb1 libxau-dev libxcb-dri2-0 libxcb-dri2-0-dev libxcb-dri3-0 libxcb-dri3-dev libxcb-glx0 libxcb-glx0-dev libxcb-present-dev libxcb-present0 libxcb-randr0 libxcb-randr0-dev libxcb-render0-dev libxcb-shape0 libxcb-shape0-dev libxcb-sync-dev libxcb-sync1 libxcb-xfixes0 libxcb-xfixes0-dev libxcb1-dev libxcomposite1 libxcursor1 libxdamage-dev libxdamage1 libxdmcp-dev libxext-dev libxfixes-dev libxfixes3 libxfont2 libxft2 libxi-dev libxi6 libxinerama1 libxkbcommon0 libxkbfile1 libxmu-dev libxmu-headers libxnvctrl0 libxrandr2 libxshmfence-dev libxshmfence1 libxt-dev libxtst6 libxv1 libxxf86dga1 libxxf86vm-dev libxxf86vm1 linux-gcp-5.3-headers-5.3.0-1030 linux-gcp-headers-5.0.0-1026 linux-headers-5.3.0-1030-gcp linux-image-5.3.0-1030-gcp linux-modules-5.3.0-1030-gcp linux-modules-extra-5.3.0-1030-gcp mesa-common-dev ocl-icd-libopencl1 ocl-icd-opencl-dev opencl-c-headers openjdk-11-jre openjdk-11-jre-headless openjdk-8-jre openjdk-8-jre-headless pkg-config policykit-1-gnome python3-xkit screen-resolution-extra ubuntu-mono x11-utils x11-xkb-utils x11proto-core-dev x11proto-damage-dev x11proto-dev x11proto-fixes-dev x11proto-input-dev x11proto-xext-dev x11proto-xf86vidmode-dev xorg-sgml-doctools xserver-common xserver-xorg-core-hwe-18.04 xtrans-dev Use 'sudo apt autoremove' to remove them. The following NEW packages will be installed: tensorflow-model-server 0 upgraded, 1 newly installed, 0 to remove and 114 not upgraded. Need to get 223 MB of archives. After this operation, 0 B of additional disk space will be used. Get:1 http://storage.googleapis.com/tensorflow-serving-apt stable/tensorflow-model-server amd64 tensorflow-model-server all 2.4.1 [223 MB] Fetched 223 MB in 6s (40.3 MB/s) Selecting previously unselected package tensorflow-model-server. (Reading database ... 242337 files and directories currently installed.) Preparing to unpack .../tensorflow-model-server_2.4.1_all.deb ... Unpacking tensorflow-model-server (2.4.1) ... Setting up tensorflow-model-server (2.4.1) ...
Comience a ejecutar TensorFlow Serving
Aquí es donde comenzamos a ejecutar TensorFlow Serving y cargamos nuestro modelo. Después de que se cargue, podemos comenzar a realizar solicitudes de inferencia usando REST. Hay algunos parámetros importantes:
-
rest_api_port
: el puerto que usará para las solicitudes REST. -
model_name
: lo usará en la URL de las solicitudes REST. Puede ser cualquier cosa. -
model_base_path
: esta es la ruta al directorio donde ha guardado su modelo.
os.environ["MODEL_DIR"] = MODEL_DIR
nohup tensorflow_model_server \
--rest_api_port=8501 \
--model_name=fashion_model \
--model_base_path="${MODEL_DIR}" >server.log 2>&1
tail server.log
Realice una solicitud a su modelo en TensorFlow Serving
Primero, echemos un vistazo a un ejemplo aleatorio de nuestros datos de prueba.
def show(idx, title):
plt.figure()
plt.imshow(test_images[idx].reshape(28,28))
plt.axis('off')
plt.title('\n\n{}'.format(title), fontdict={'size': 16})
import random
rando = random.randint(0,len(test_images)-1)
show(rando, 'An Example Image: {}'.format(class_names[test_labels[rando]]))
Ok, eso parece interesante. ¿Qué tan difícil es para ti reconocerlo? Ahora creemos el objeto JSON para un lote de tres solicitudes de inferencia y veamos qué tan bien reconoce nuestro modelo las cosas:
import json
data = json.dumps({"signature_name": "serving_default", "instances": test_images[0:3].tolist()})
print('Data: {} ... {}'.format(data[:50], data[len(data)-52:]))
Data: {"signature_name": "serving_default", "instances": ... [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0]]]]}
Realizar solicitudes de REST
Versión más nueva del servidor
Enviaremos una solicitud de predicción como POST al punto final REST de nuestro servidor y le pasaremos tres ejemplos. Le pediremos a nuestro servidor que nos proporcione la última versión de nuestro servidor sin especificar una versión en particular.
!pip install -q requests
import requests
headers = {"content-type": "application/json"}
json_response = requests.post('http://localhost:8501/v1/models/fashion_model:predict', data=data, headers=headers)
predictions = json.loads(json_response.text)['predictions']
show(0, 'The model thought this was a {} (class {}), and it was actually a {} (class {})'.format(
class_names[np.argmax(predictions[0])], np.argmax(predictions[0]), class_names[test_labels[0]], test_labels[0]))
Una versión particular del servidor
Ahora especifiquemos una versión particular de nuestro servidor. Como solo tenemos uno, seleccionemos la versión 1. También veremos los tres resultados.
headers = {"content-type": "application/json"}
json_response = requests.post('http://localhost:8501/v1/models/fashion_model/versions/1:predict', data=data, headers=headers)
predictions = json.loads(json_response.text)['predictions']
for i in range(0,3):
show(i, 'The model thought this was a {} (class {}), and it was actually a {} (class {})'.format(
class_names[np.argmax(predictions[i])], np.argmax(predictions[i]), class_names[test_labels[i]], test_labels[i]))