Reusable Embeddings

Learning Objectives

  1. Learn how to use a pre-trained TF Hub text modules to generate sentence vectors
  2. Learn how to incorporate a pre-trained TF-Hub module into a Keras model
  3. Learn how to deploy and use a text model on CAIP

Introduction

In this notebook, we will implement text models to recognize the probable source (Github, Tech-Crunch, or The New-York Times) of the titles we have in the title dataset.

First, we will load and pre-process the texts and labels so that they are suitable to be fed to sequential Keras models with first layer being TF-hub pre-trained modules. Thanks to this first layer, we won't need to tokenize and integerize the text before passing it to our models. The pre-trained layer will take care of that for us, and consume directly raw text. However, we will still have to one-hot-encode each of the 3 classes into a 3 dimensional basis vector.

Then we will build, train and compare simple DNN models starting with different pre-trained TF-Hub layers.


In [ ]:
import os

from google.cloud import bigquery
import pandas as pd

In [ ]:
%load_ext google.cloud.bigquery

Replace the variable values in the cell below:


In [ ]:
PROJECT = "cloud-training-demos"  # Replace with your PROJECT
BUCKET = PROJECT  # defaults to PROJECT
REGION = "us-central1"  # Replace with your REGION
SEED = 0

Create a Dataset from BigQuery

Hacker news headlines are available as a BigQuery public dataset. The dataset contains all headlines from the sites inception in October 2006 until October 2015.

Here is a sample of the dataset:


In [ ]:
%%bigquery --project $PROJECT

SELECT
    url, title, score
FROM
    `bigquery-public-data.hacker_news.stories`
WHERE
    LENGTH(title) > 10
    AND score > 10
    AND LENGTH(url) > 0
LIMIT 10

Let's do some regular expression parsing in BigQuery to get the source of the newspaper article from the URL. For example, if the url is http://mobile.nytimes.com/...., I want to be left with nytimes


In [ ]:
%%bigquery --project $PROJECT

SELECT
    ARRAY_REVERSE(SPLIT(REGEXP_EXTRACT(url, '.*://(.[^/]+)/'), '.'))[OFFSET(1)] AS source,
    COUNT(title) AS num_articles
FROM
    `bigquery-public-data.hacker_news.stories`
WHERE
    REGEXP_CONTAINS(REGEXP_EXTRACT(url, '.*://(.[^/]+)/'), '.com$')
    AND LENGTH(title) > 10
GROUP BY
    source
ORDER BY num_articles DESC
  LIMIT 100

Now that we have good parsing of the URL to get the source, let's put together a dataset of source and titles. This will be our labeled dataset for machine learning.


In [ ]:
regex = '.*://(.[^/]+)/'


sub_query = """
SELECT
    title,
    ARRAY_REVERSE(SPLIT(REGEXP_EXTRACT(url, '{0}'), '.'))[OFFSET(1)] AS source
    
FROM
    `bigquery-public-data.hacker_news.stories`
WHERE
    REGEXP_CONTAINS(REGEXP_EXTRACT(url, '{0}'), '.com$')
    AND LENGTH(title) > 10
""".format(regex)


query = """
SELECT 
    LOWER(REGEXP_REPLACE(title, '[^a-zA-Z0-9 $.-]', ' ')) AS title,
    source
FROM
  ({sub_query})
WHERE (source = 'github' OR source = 'nytimes' OR source = 'techcrunch')
""".format(sub_query=sub_query)

print(query)

For ML training, we usually need to split our dataset into training and evaluation datasets (and perhaps an independent test dataset if we are going to do model or feature selection based on the evaluation dataset). AutoML however figures out on its own how to create these splits, so we won't need to do that here.


In [ ]:
bq = bigquery.Client(project=PROJECT)
title_dataset = bq.query(query).to_dataframe()
title_dataset.head()

AutoML for text classification requires that

  • the dataset be in csv form with
  • the first column being the texts to classify or a GCS path to the text
  • the last colum to be the text labels

The dataset we pulled from BiqQuery satisfies these requirements.


In [ ]:
print("The full dataset contains {n} titles".format(n=len(title_dataset)))

Let's make sure we have roughly the same number of labels for each of our three labels:


In [ ]:
title_dataset.source.value_counts()

Finally we will save our data, which is currently in-memory, to disk.

We will create a csv file containing the full dataset and another containing only 1000 articles for development.

Note: It may take a long time to train AutoML on the full dataset, so we recommend to use the sample dataset for the purpose of learning the tool.


In [ ]:
DATADIR = './data/'

if not os.path.exists(DATADIR):
    os.makedirs(DATADIR)

In [ ]:
FULL_DATASET_NAME = 'titles_full.csv'
FULL_DATASET_PATH = os.path.join(DATADIR, FULL_DATASET_NAME)

# Let's shuffle the data before writing it to disk.
title_dataset = title_dataset.sample(n=len(title_dataset))

title_dataset.to_csv(
    FULL_DATASET_PATH, header=False, index=False, encoding='utf-8')

Now let's sample 1000 articles from the full dataset and make sure we have enough examples for each label in our sample dataset (see here for further details on how to prepare data for AutoML).


In [ ]:
sample_title_dataset = title_dataset.sample(n=1000)
sample_title_dataset.source.value_counts()

Let's write the sample datatset to disk.


In [ ]:
SAMPLE_DATASET_NAME = 'titles_sample.csv'
SAMPLE_DATASET_PATH = os.path.join(DATADIR, SAMPLE_DATASET_NAME)

sample_title_dataset.to_csv(
    SAMPLE_DATASET_PATH, header=False, index=False, encoding='utf-8')

In [ ]:
sample_title_dataset.head()

In [1]:
import datetime
import os
import shutil

import pandas as pd
import tensorflow as tf
from tensorflow.keras.callbacks import TensorBoard, EarlyStopping
from tensorflow_hub import KerasLayer
from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.utils import to_categorical


print(tf.__version__)


2.1.0

In [2]:
%matplotlib inline

Let's start by specifying where the information about the trained models will be saved as well as where our dataset is located:


In [3]:
MODEL_DIR = "./text_models"
DATA_DIR = "./data"

Loading the dataset

As in the previous labs, our dataset consists of titles of articles along with the label indicating from which source these articles have been taken from (GitHub, Tech-Crunch, or the New-York Times):


In [4]:
ls ./data/


titles_full.csv    titles_sample.csv

In [5]:
DATASET_NAME = "titles_full.csv"
TITLE_SAMPLE_PATH = os.path.join(DATA_DIR, DATASET_NAME)
COLUMNS = ['title', 'source']

titles_df = pd.read_csv(TITLE_SAMPLE_PATH, header=None, names=COLUMNS)
titles_df.head()


Out[5]:
title source
0 code reviews good idea bad idea github
1 brainfuck on rails github
2 abundance without attachment nytimes
3 finally a startup visa that works techcrunch
4 unraveling the key to a cold virus s effective... nytimes

Let's look again at the number of examples per label to make sure we have a well-balanced dataset:


In [6]:
titles_df.source.value_counts()


Out[6]:
github        36525
techcrunch    30891
nytimes       28787
Name: source, dtype: int64

Preparing the labels

In this lab, we will use pre-trained TF-Hub embeddings modules for english for the first layer of our models. One immediate advantage of doing so is that the TF-Hub embedding module will take care for us of processing the raw text. This also means that our model will be able to consume text directly instead of sequences of integers representing the words.

However, as before, we still need to preprocess the labels into one-hot-encoded vectors:


In [7]:
CLASSES = {
    'github': 0,
    'nytimes': 1,
    'techcrunch': 2
}
N_CLASSES = len(CLASSES)

In [8]:
def encode_labels(sources):
    classes = [CLASSES[source] for source in sources]
    one_hots = to_categorical(classes, num_classes=N_CLASSES)
    return one_hots

In [9]:
encode_labels(titles_df.source[:4])


Out[9]:
array([[1., 0., 0.],
       [1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]], dtype=float32)

Preparing the train/test splits

Let's split our data into train and test splits:


In [10]:
N_TRAIN = int(len(titles_df) * 0.95)

titles_train, sources_train = (
    titles_df.title[:N_TRAIN], titles_df.source[:N_TRAIN])

titles_valid, sources_valid = (
    titles_df.title[N_TRAIN:], titles_df.source[N_TRAIN:])

To be on the safe side, we verify that the train and test splits have roughly the same number of examples per class.

Since it is the case, accuracy will be a good metric to use to measure the performance of our models.


In [11]:
sources_train.value_counts()


Out[11]:
github        34710
techcrunch    29284
nytimes       27398
Name: source, dtype: int64

In [12]:
sources_valid.value_counts()


Out[12]:
github        1815
techcrunch    1607
nytimes       1389
Name: source, dtype: int64

Now let's create the features and labels we will feed our models with:


In [13]:
X_train, Y_train = titles_train.values, encode_labels(sources_train)
X_valid, Y_valid = titles_valid.values, encode_labels(sources_valid)

In [14]:
X_train[:3]


Out[14]:
array(['code reviews  good idea  bad idea ', 'brainfuck on rails',
       'abundance without attachment'], dtype=object)

In [15]:
Y_train[:3]


Out[15]:
array([[1., 0., 0.],
       [1., 0., 0.],
       [0., 1., 0.]], dtype=float32)

NNLM Model

We will first try a word embedding pre-trained using a Neural Probabilistic Language Model. TF-Hub has a 50-dimensional one called nnlm-en-dim50-with-normalization, which also normalizes the vectors produced.

Once loaded from its url, the TF-hub module can be used as a normal Keras layer in a sequential or functional model. Since we have enough data to fine-tune the parameters of the pre-trained embedding itself, we will set trainable=True in the KerasLayer that loads the pre-trained embedding:


In [16]:
# TODO 1
NNLM = "https://tfhub.dev/google/nnlm-en-dim50/2"

nnlm_module = KerasLayer(
    NNLM, output_shape=[50], input_shape=[], dtype=tf.string, trainable=True)

Note that this TF-Hub embedding produces a single 50-dimensional vector when passed a sentence:


In [17]:
# TODO 1
nnlm_module(tf.constant(["The dog is happy to see people in the street."]))


Out[17]:
<tf.Tensor: shape=(1, 50), dtype=float32, numpy=
array([[ 0.19331802,  0.05893906,  0.15330684,  0.2505918 ,  0.19369544,
         0.03578748,  0.07387847, -0.10962156, -0.11377034,  0.07172022,
         0.12458669, -0.02289705, -0.18177685, -0.07084437, -0.00225849,
        -0.36875236,  0.05772953, -0.14222091,  0.08765972, -0.14068899,
        -0.07005888, -0.20634466,  0.07220475,  0.04258814,  0.0955702 ,
         0.19424029, -0.42492998, -0.00706906, -0.02095   , -0.05055764,
        -0.18988201, -0.02841404,  0.13222624, -0.01459922, -0.31255388,
        -0.09577855,  0.05469003, -0.13858607,  0.01141668, -0.12352604,
        -0.07250367, -0.11605677, -0.06976165,  0.14313601, -0.15183711,
        -0.06836402,  0.03054246, -0.13259597, -0.14599673,  0.05094011]],
      dtype=float32)>

Swivel Model

Then we will try a word embedding obtained using Swivel, an algorithm that essentially factorizes word co-occurrence matrices to create the words embeddings. TF-Hub hosts the pretrained gnews-swivel-20dim-with-oov 20-dimensional Swivel module.


In [18]:
# TODO 1
SWIVEL = "https://tfhub.dev/google/tf2-preview/gnews-swivel-20dim-with-oov/1"
swivel_module = KerasLayer(
    SWIVEL, output_shape=[20], input_shape=[], dtype=tf.string, trainable=True)

Similarly as the previous pre-trained embedding, it outputs a single vector when passed a sentence:


In [19]:
# TODO 1
swivel_module(tf.constant(["The dog is happy to see people in the street."]))


Out[19]:
<tf.Tensor: shape=(1, 20), dtype=float32, numpy=
array([[ 0.9967701 , -0.3100155 ,  0.5889897 , -0.16765082, -0.6171738 ,
        -1.1586996 , -0.8619045 ,  0.7281645 ,  0.32575002,  0.4754492 ,
        -0.9272241 ,  0.41090095, -0.75389475, -0.31525993, -1.8918804 ,
         0.6423996 ,  0.6801622 , -0.1335669 , -1.0017993 , -0.11908641]],
      dtype=float32)>

Building the models

Let's write a function that

  • takes as input an instance of a KerasLayer (i.e. the swivel_module or the nnlm_module we constructed above) as well as the name of the model (say swivel or nnlm)
  • returns a compiled Keras sequential model starting with this pre-trained TF-hub layer, adding one or more dense relu layers to it, and ending with a softmax layer giving the probability of each of the classes:

In [20]:
def build_model(hub_module, name):
    model = Sequential([
        hub_module, # TODO 2
        Dense(16, activation='relu'),
        Dense(N_CLASSES, activation='softmax')
    ], name=name)

    model.compile(
        optimizer='adam',
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )
    return model

Let's also wrap the training code into a train_and_evaluate function that

  • takes as input the training and validation data, as well as the compiled model itself, and the batch_size
  • trains the compiled model for 100 epochs at most, and does early-stopping when the validation loss is no longer decreasing
  • returns an history object, which will help us to plot the learning curves

In [21]:
def train_and_evaluate(train_data, val_data, model, batch_size=5000):
    X_train, Y_train = train_data

    tf.random.set_seed(33)

    model_dir = os.path.join(MODEL_DIR, model.name)
    if tf.io.gfile.exists(model_dir):
        tf.io.gfile.rmtree(model_dir)

    history = model.fit(
        X_train, Y_train,
        epochs=100,
        batch_size=batch_size,
        validation_data=val_data,
        callbacks=[EarlyStopping(), TensorBoard(model_dir)],
    )
    return history

Training NNLM


In [22]:
data = (X_train, Y_train)
val_data = (X_valid, Y_valid)

In [23]:
nnlm_model = build_model(nnlm_module, 'nnlm')
nnlm_history = train_and_evaluate(data, val_data, nnlm_model)


Train on 91392 samples, validate on 4811 samples
Epoch 1/100
91392/91392 [==============================] - 13s 138us/sample - loss: 1.0636 - accuracy: 0.4781 - val_loss: 1.0152 - val_accuracy: 0.6096
Epoch 2/100
91392/91392 [==============================] - 12s 129us/sample - loss: 0.9620 - accuracy: 0.6753 - val_loss: 0.8947 - val_accuracy: 0.7246
Epoch 3/100
91392/91392 [==============================] - 12s 132us/sample - loss: 0.8159 - accuracy: 0.7539 - val_loss: 0.7376 - val_accuracy: 0.7703
Epoch 4/100
91392/91392 [==============================] - 12s 134us/sample - loss: 0.6517 - accuracy: 0.7981 - val_loss: 0.5933 - val_accuracy: 0.7996
Epoch 5/100
91392/91392 [==============================] - 12s 134us/sample - loss: 0.5175 - accuracy: 0.8258 - val_loss: 0.4967 - val_accuracy: 0.8158
Epoch 6/100
91392/91392 [==============================] - 12s 127us/sample - loss: 0.4290 - accuracy: 0.8476 - val_loss: 0.4432 - val_accuracy: 0.8271
Epoch 7/100
91392/91392 [==============================] - 12s 131us/sample - loss: 0.3724 - accuracy: 0.8649 - val_loss: 0.4134 - val_accuracy: 0.8354
Epoch 8/100
91392/91392 [==============================] - 12s 133us/sample - loss: 0.3334 - accuracy: 0.8783 - val_loss: 0.3975 - val_accuracy: 0.8397
Epoch 9/100
91392/91392 [==============================] - 12s 126us/sample - loss: 0.3044 - accuracy: 0.8894 - val_loss: 0.3887 - val_accuracy: 0.8427
Epoch 10/100
91392/91392 [==============================] - 12s 128us/sample - loss: 0.2815 - accuracy: 0.8977 - val_loss: 0.3857 - val_accuracy: 0.8406
Epoch 11/100
91392/91392 [==============================] - 12s 134us/sample - loss: 0.2627 - accuracy: 0.9052 - val_loss: 0.3853 - val_accuracy: 0.8439
Epoch 12/100
91392/91392 [==============================] - 12s 134us/sample - loss: 0.2469 - accuracy: 0.9119 - val_loss: 0.3877 - val_accuracy: 0.8447

In [24]:
history = nnlm_history
pd.DataFrame(history.history)[['loss', 'val_loss']].plot()
pd.DataFrame(history.history)[['accuracy', 'val_accuracy']].plot()


Out[24]:
<matplotlib.axes._subplots.AxesSubplot at 0x14a524828>

Training Swivel


In [25]:
swivel_model = build_model(swivel_module, name='swivel')

In [26]:
swivel_history = train_and_evaluate(data, val_data, swivel_model)


Train on 91392 samples, validate on 4811 samples
Epoch 1/100
91392/91392 [==============================] - 1s 8us/sample - loss: 1.1817 - accuracy: 0.3474 - val_loss: 1.1063 - val_accuracy: 0.4051
Epoch 2/100
91392/91392 [==============================] - 0s 4us/sample - loss: 1.0600 - accuracy: 0.4482 - val_loss: 1.0193 - val_accuracy: 0.4986
Epoch 3/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.9859 - accuracy: 0.5219 - val_loss: 0.9590 - val_accuracy: 0.5450
Epoch 4/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.9283 - accuracy: 0.5715 - val_loss: 0.9068 - val_accuracy: 0.5832
Epoch 5/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.8760 - accuracy: 0.6087 - val_loss: 0.8557 - val_accuracy: 0.6159
Epoch 6/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.8239 - accuracy: 0.6416 - val_loss: 0.8044 - val_accuracy: 0.6512
Epoch 7/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.7719 - accuracy: 0.6685 - val_loss: 0.7544 - val_accuracy: 0.6782
Epoch 8/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.7219 - accuracy: 0.6925 - val_loss: 0.7082 - val_accuracy: 0.6972
Epoch 9/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.6762 - accuracy: 0.7139 - val_loss: 0.6678 - val_accuracy: 0.7127
Epoch 10/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.6366 - accuracy: 0.7317 - val_loss: 0.6349 - val_accuracy: 0.7238
Epoch 11/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.6034 - accuracy: 0.7455 - val_loss: 0.6079 - val_accuracy: 0.7354
Epoch 12/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.5756 - accuracy: 0.7576 - val_loss: 0.5858 - val_accuracy: 0.7475
Epoch 13/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.5520 - accuracy: 0.7681 - val_loss: 0.5677 - val_accuracy: 0.7547
Epoch 14/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.5316 - accuracy: 0.7778 - val_loss: 0.5516 - val_accuracy: 0.7633
Epoch 15/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.5138 - accuracy: 0.7859 - val_loss: 0.5383 - val_accuracy: 0.7707
Epoch 16/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.4981 - accuracy: 0.7930 - val_loss: 0.5268 - val_accuracy: 0.7739
Epoch 17/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.4840 - accuracy: 0.7993 - val_loss: 0.5163 - val_accuracy: 0.7788
Epoch 18/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.4713 - accuracy: 0.8051 - val_loss: 0.5074 - val_accuracy: 0.7818
Epoch 19/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.4598 - accuracy: 0.8105 - val_loss: 0.4999 - val_accuracy: 0.7853
Epoch 20/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.4494 - accuracy: 0.8147 - val_loss: 0.4935 - val_accuracy: 0.7892
Epoch 21/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.4398 - accuracy: 0.8193 - val_loss: 0.4876 - val_accuracy: 0.7911
Epoch 22/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.4311 - accuracy: 0.8234 - val_loss: 0.4828 - val_accuracy: 0.7961
Epoch 23/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.4230 - accuracy: 0.8275 - val_loss: 0.4785 - val_accuracy: 0.7969
Epoch 24/100
91392/91392 [==============================] - 0s 5us/sample - loss: 0.4155 - accuracy: 0.8307 - val_loss: 0.4749 - val_accuracy: 0.8007
Epoch 25/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.4086 - accuracy: 0.8339 - val_loss: 0.4715 - val_accuracy: 0.8021
Epoch 26/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.4022 - accuracy: 0.8369 - val_loss: 0.4688 - val_accuracy: 0.8050
Epoch 27/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.3962 - accuracy: 0.8397 - val_loss: 0.4666 - val_accuracy: 0.8059
Epoch 28/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.3906 - accuracy: 0.8422 - val_loss: 0.4650 - val_accuracy: 0.8077
Epoch 29/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.3854 - accuracy: 0.8447 - val_loss: 0.4632 - val_accuracy: 0.8081
Epoch 30/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.3808 - accuracy: 0.8466 - val_loss: 0.4620 - val_accuracy: 0.8084
Epoch 31/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.3762 - accuracy: 0.8488 - val_loss: 0.4615 - val_accuracy: 0.8088
Epoch 32/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.3719 - accuracy: 0.8506 - val_loss: 0.4607 - val_accuracy: 0.8094
Epoch 33/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.3679 - accuracy: 0.8523 - val_loss: 0.4604 - val_accuracy: 0.8102
Epoch 34/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.3642 - accuracy: 0.8540 - val_loss: 0.4600 - val_accuracy: 0.8111
Epoch 35/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.3606 - accuracy: 0.8554 - val_loss: 0.4595 - val_accuracy: 0.8102
Epoch 36/100
91392/91392 [==============================] - 0s 4us/sample - loss: 0.3573 - accuracy: 0.8573 - val_loss: 0.4597 - val_accuracy: 0.8119

In [27]:
history = swivel_history
pd.DataFrame(history.history)[['loss', 'val_loss']].plot()
pd.DataFrame(history.history)[['accuracy', 'val_accuracy']].plot()


Out[27]:
<matplotlib.axes._subplots.AxesSubplot at 0x148ac9b00>

Comparing the models

Swivel trains faster but achieves a lower validation accuracy, and requires more epochs to train on.

At last, let's compare all the models we have trained at once using TensorBoard in order to choose the one that overfits the less for the same performance level.

Running the following command will launch TensorBoard on port 6006. This will block the notebook execution, so you'll have to interrupt that cell first before you can run other cells.


In [ ]:
!tensorboard --logdir $MODEL_DIR --port 6006

Deploying the model

The first step is to serialize one of our trained Keras model as a SavedModel:


In [42]:
OUTPUT_DIR = "./savedmodels"
shutil.rmtree(OUTPUT_DIR, ignore_errors=True)

EXPORT_PATH = os.path.join(OUTPUT_DIR, 'swivel')
os.environ['EXPORT_PATH'] = EXPORT_PATH

shutil.rmtree(EXPORT_PATH, ignore_errors=True)

tf.saved_model.save(swivel_model, EXPORT_PATH)


INFO:tensorflow:Assets written to: ./savedmodels/swivel/assets
INFO:tensorflow:Assets written to: ./savedmodels/swivel/assets

Then we can deploy the model using the gcloud CLI as before:


In [44]:
%%bash

# TODO 5

PROJECT=# TODO: Change this to your PROJECT
BUCKET=${PROJECT}
REGION=us-east1
MODEL_NAME=title_model
VERSION_NAME=swivel
EXPORT_PATH=$EXPORT_PATH

if [[ $(gcloud ai-platform models list --format='value(name)' | grep $MODEL_NAME) ]]; then
    echo "$MODEL_NAME already exists"
else
    echo "Creating $MODEL_NAME"
    gcloud ai-platform models create --regions=$REGION $MODEL_NAME
fi

if [[ $(gcloud ai-platform versions list --model $MODEL_NAME --format='value(name)' | grep $VERSION_NAME) ]]; then
    echo "Deleting already existing $MODEL_NAME:$VERSION_NAME ... "
    echo yes | gcloud ai-platform versions delete --model=$MODEL_NAME $VERSION_NAME
    echo "Please run this cell again if you don't see a Creating message ... "
    sleep 2
fi

echo "Creating $MODEL_NAME:$VERSION_NAME"
gcloud beta ai-platform versions create \
  --model=$MODEL_NAME $VERSION_NAME \
  --framework=tensorflow \
  --python-version=3.7 \
  --runtime-version=1.15 \
  --origin=$EXPORT_PATH \
  --staging-bucket=gs://$BUCKET \
  --machine-type n1-standard-4


title_model already exists
Creating title_model:swivel
Creating version (this might take a few minutes)......
.......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................done.

Before we try our deployed model, let's inspect its signature to know what to send to the deployed API:


In [79]:
!saved_model_cli show \
 --tag_set serve \
 --signature_def serving_default \
 --dir {EXPORT_PATH}
!find {EXPORT_PATH}


The given SavedModel SignatureDef contains the following input(s):
  inputs['keras_layer_1_input'] tensor_info:
      dtype: DT_STRING
      shape: (-1)
      name: serving_default_keras_layer_1_input:0
The given SavedModel SignatureDef contains the following output(s):
  outputs['dense_3'] tensor_info:
      dtype: DT_FLOAT
      shape: (-1, 3)
      name: StatefulPartitionedCall_2:0
Method name is: tensorflow/serving/predict
./savedmodels/swivel
./savedmodels/swivel/variables
./savedmodels/swivel/variables/variables.data-00000-of-00001
./savedmodels/swivel/variables/variables.index
./savedmodels/swivel/saved_model.pb
./savedmodels/swivel/assets
./savedmodels/swivel/assets/tokens.txt

Let's go ahead and hit our model:


In [76]:
%%writefile input.json
{"keras_layer_1_input": "hello"}


Overwriting input.json

In [78]:
!gcloud ai-platform predict \
  --model title_model \
  --json-instances input.json \
  --version swivel


DENSE_3
[0.8266409635543823, 0.062128521502017975, 0.11123056709766388]

Bonus

Try to beat the best model by modifying the model architecture, changing the TF-Hub embedding, and tweaking the training parameters.

Copyright 2019 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License