text: # -*- coding: utf-8 -*-
  """TF2.0 Serving.ipynb
  
  Automatically generated by Colaboratory.
  
  Original file is located at
      https://colab.research.google.com/drive/1GTTc6mReYhGfYxSRCbtK4Y-zko_YzEXa
  """
  
  # Commented out IPython magic to ensure Python compatibility.
  # Install TensorFlow
  import json
  import tempfile
  from tensorflow.keras.models import Model
  from tensorflow.keras.layers import Input, Conv2D, Dense, Flatten, Dropout
  import subprocess
  import os
  import matplotlib.pyplot as plt
  import numpy as np
  import requests
  import tensorflow as tf
  print(tf.__version__)
  
  # By the way, what is a server / service / API?
  
  # Best way to learn is by example
  # Here is a service that simply returns your IP address in a JSON
  
  r = requests.get('https://api.ipify.org?format=json')
  j = r.json()
  print(j)
  
  # Our Tensorflow model server is the same, except what it does is much more
  # complex - it returns the predictions from a ML model!
  
  # More imports
  
  
  # Load in the data
  fashion_mnist = tf.keras.datasets.fashion_mnist
  
  (x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()
  x_train, x_test = x_train / 255.0, x_test / 255.0
  print("x_train.shape:", x_train.shape)
  print("x_test.shape:", x_test.shape)
  
  # the data is only 2D!
  # convolution expects height x width x color
  x_train = np.expand_dims(x_train, -1)
  x_test = np.expand_dims(x_test, -1)
  print(x_train.shape)
  
  # number of classes
  K = len(set(y_train))
  print("number of classes:", K)
  
  # Build the model using the functional API
  i = Input(shape=x_train[0].shape)
  x = Conv2D(32, (3, 3), strides=2, activation='relu')(i)
  x = Conv2D(64, (3, 3), strides=2, activation='relu')(x)
  x = Conv2D(128, (3, 3), strides=2, activation='relu')(x)
  x = Flatten()(x)
  x = Dropout(0.2)(x)
  x = Dense(512, activation='relu')(x)
  x = Dropout(0.2)(x)
  x = Dense(K, activation='softmax')(x)
  
  model = Model(i, x)
  model.summary()
  
  # Compile and fit
  # Note: make sure you are using the GPU for this!
  model.compile(optimizer='adam',
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])
  r = model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=15)
  
  # Save the model to a temporary directory
  
  MODEL_DIR = tempfile.gettempdir()
  version = 1
  export_path = os.path.join(MODEL_DIR, str(version))
  print('export_path = {}\n'.format(export_path))
  if os.path.isdir(export_path):
      print('\nAlready saved a model, cleaning up\n')
      !rm - r {export_path}
  
  tf.saved_model.save(model, export_path)
  
  print('\nSaved model:')
  !ls - l {export_path}
  
  !saved_model_cli show - -dir {export_path} - -all
  
  # 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" | tee / etc/apt/sources.list.d/tensorflow-serving.list & & \
      curl https: // storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | apt-key add -
  !apt update
  
  !apt-get install tensorflow-model-server
  
  os.environ["MODEL_DIR"] = MODEL_DIR
  
  # Commented out IPython magic to ensure Python compatibility.
  # %%bash --bg
  # nohup tensorflow_model_server \
  #   --rest_api_port=8501 \
  #   --model_name=fashion_model \
  #   --model_base_path="${MODEL_DIR}" >server.log 2>&1
  
  !tail server.log
  
  # Label mapping
  labels = '''T-shirt/top
  Trouser
  Pullover
  Dress
  Coat
  Sandal
  Shirt
  Sneaker
  Bag
  Ankle boot'''.split("\n")
  
  
  def show(idx, title):
      plt.figure()
      plt.imshow(x_test[idx].reshape(28, 28), cmap='gray')
      plt.axis('off')
      plt.title('\n\n{}'.format(title), fontdict={'size': 16})
  
  
  i = np.random.randint(0, len(x_test))
  show(i, labels[y_test[i]])
  
  # Format some data to pass to the server
  # {
  #   "signature_name": "serving_default",
  #   "instances": [ an N x H x W x C list ],
  # }
  
  data = json.dumps({"signature_name": "serving_default", "instances": x_test[0:3].tolist()})
  print(data)
  
  headers = {"content-type": "application/json"}
  r = requests.post('http://localhost:8501/v1/models/fashion_model:predict', data=data, headers=headers)
  j = r.json()
  print(j.keys())
  print(j)
  
  # It looks like a 2-D array, let's check its shape
  pred = np.array(j['predictions'])
  print(pred.shape)
  
  # This is the N x K output array from the model
  # pred[n,k] is the probability that we believe the nth sample belongs to the kth class
  
  # Get the predicted classes
  pred = pred.argmax(axis=1)
  
  # Map them back to strings
  pred = [labels[i] for i in pred]
  print(pred)
  
  # Get the true labels
  actual = [labels[i] for i in y_test[:3]]
  print(actual)
  
  for i in range(0, 3):
      show(i, f"True: {actual[i]}, Predicted: {pred[i]}")
  
  # Allows you to select a model by version
  
  headers = {"content-type": "application/json"}
  r = requests.post('http://localhost:8501/v1/models/fashion_model/versions/1:predict', data=data, headers=headers)
  j = r.json()
  pred = np.array(j['predictions'])
  pred = pred.argmax(axis=1)
  pred = [labels[i] for i in pred]
  for i in range(0, 3):
      show(i, f"True: {actual[i]}, Predicted: {pred[i]}")
  
  # Let's make a new model version
  # Build the model using the functional API
  i = Input(shape=x_train[0].shape)
  x = Conv2D(32, (3, 3), strides=2, activation='relu')(i)
  x = Flatten()(x)
  x = Dense(K, activation='softmax')(x)
  
  model2 = Model(i, x)
  model2.summary()
  
  # Compile and fit
  # Note: make sure you are using the GPU for this!
  model2.compile(optimizer='adam',
                 loss='sparse_categorical_crossentropy',
                 metrics=['accuracy'])
  r = model2.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=15)
  
  # Save version 2 of the model
  
  version = 2
  export_path = os.path.join(MODEL_DIR, str(version))
  print('export_path = {}\n'.format(export_path))
  if os.path.isdir(export_path):
      print('\nAlready saved a model, cleaning up\n')
      !rm - r {export_path}
  
  tf.saved_model.save(model2, export_path)
  
  print('\nSaved model:')
  !ls - l {export_path}
  
  # Will Tensorflow serving know about the new model without restarting?
  
  headers = {"content-type": "application/json"}
  r = requests.post('http://localhost:8501/v1/models/fashion_model/versions/2:predict', data=data, headers=headers)
  j = r.json()
  pred = np.array(j['predictions'])
  pred = pred.argmax(axis=1)
  pred = [labels[i] for i in pred]
  for i in range(0, 3):
      show(i, f"True: {actual[i]}, Predicted: {pred[i]}")
  
  # What if we use a version number that does not exist?
  headers = {"content-type": "application/json"}
  r = requests.post('http://localhost:8501/v1/models/fashion_model/versions/3:predict', data=data, headers=headers)
  j = r.json()
  print(j)
  