{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "JL3ZPJPgC4wC"
},
"source": [
"# Shakespeare Text Generation (using RNN LSTM)"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "eoWGtkL8PPJ3"
},
"source": [
"> - 🤖 See [full list of Machine Learning Experiments](https://github.com/trekhleb/machine-learning-experiments) on **GitHub**
\n",
"> - ▶️ **Interactive Demo**: [try this model and other machine learning experiments in action](https://trekhleb.github.io/machine-learning-experiments/)"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "bui0MyTjv1Mp"
},
"source": [
"## Experiment overview\n",
"\n",
"In this experiment we will use character-based [Recurrent Neural Network](https://en.wikipedia.org/wiki/Recurrent_neural_network) (RNN) to generate a Shakespeare's-like text based on the Shakespeare dataset from [The Unreasonable Effectiveness of Recurrent Neural Networks](http://karpathy.github.io/2015/05/21/rnn-effectiveness/) blog post.\n",
"\n",
"For this experiment we will use [Tensorflow v2](https://www.tensorflow.org/) with its [Keras API](https://www.tensorflow.org/guide/keras).\n",
"\n",
"![text_generation_shakespeare_rnn.jpg](../../demos/src/images/text_generation_shakespeare_rnn.jpg)"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "3XxEZuRNIzHH"
},
"source": [
"_Inspired by [Text generation with an RNN](https://www.tensorflow.org/tutorials/text/text_generation)_"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "IDJctDhhEDTD"
},
"source": [
"## Import dependencies"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"colab_type": "code",
"executionInfo": {
"elapsed": 1347,
"status": "ok",
"timestamp": 1584519798294,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "Z2xZnxncD2Ep",
"outputId": "9e110fb9-a6d1-4bd6-818c-2198114e3133"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"TensorFlow 2.x selected.\n"
]
}
],
"source": [
"# Selecting Tensorflow version v2 (the command is relevant for Colab only).\n",
"# %tensorflow_version 2.x"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 68
},
"colab_type": "code",
"executionInfo": {
"elapsed": 2811,
"status": "ok",
"timestamp": 1584519800384,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "SpueB6zADYgE",
"outputId": "3f4e9997-8ce8-4ef8-f8f9-320c9a44e03b"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Python version: 3.6.9\n",
"Tensorflow version: 2.1.0\n",
"Keras version: 2.2.4-tf\n"
]
}
],
"source": [
"import tensorflow as tf\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import platform\n",
"import time\n",
"import pathlib\n",
"import os\n",
"\n",
"print('Python version:', platform.python_version())\n",
"print('Tensorflow version:', tf.__version__)\n",
"print('Keras version:', tf.keras.__version__)"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "ciI_JnnNEGCw"
},
"source": [
"## Download the dataset"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 68
},
"colab_type": "code",
"executionInfo": {
"elapsed": 2533,
"status": "ok",
"timestamp": 1584519800396,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "Vk2HldshEOph",
"outputId": "45342704-eb1c-4863-a4e4-8a94d9cc764b"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt\n",
"1122304/1115394 [==============================] - 0s 0us/step\n",
"/content/tmp/datasets/shakespeare.txt\n"
]
}
],
"source": [
"cache_dir = './tmp'\n",
"dataset_file_name = 'shakespeare.txt'\n",
"dataset_file_origin = 'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt'\n",
"\n",
"dataset_file_path = tf.keras.utils.get_file(\n",
" fname=dataset_file_name,\n",
" origin=dataset_file_origin,\n",
" cache_dir=pathlib.Path(cache_dir).absolute()\n",
")\n",
"\n",
"print(dataset_file_path)"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "eKTy6YS5Gx-g"
},
"source": [
"## Analyze the dataset"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"colab_type": "code",
"executionInfo": {
"elapsed": 2199,
"status": "ok",
"timestamp": 1584519800398,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "CzfX7hK8G0bn",
"outputId": "dbcd9c2e-967d-404f-ebd4-387ee0b629d7"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Length of text: 1115394 characters\n"
]
}
],
"source": [
"# Reading the database file.\n",
"text = open(dataset_file_path, mode='r').read()\n",
"\n",
"print('Length of text: {} characters'.format(len(text)))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 272
},
"colab_type": "code",
"executionInfo": {
"elapsed": 1774,
"status": "ok",
"timestamp": 1584519800402,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "nknOAOuoH-Av",
"outputId": "7030d3f8-c6a8-43e0-81a0-c1e87d677314"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"First Citizen:\n",
"Before we proceed any further, hear me speak.\n",
"\n",
"All:\n",
"Speak, speak.\n",
"\n",
"First Citizen:\n",
"You are all resolved rather to die than to famish?\n",
"\n",
"All:\n",
"Resolved. resolved.\n",
"\n",
"First Citizen:\n",
"First, you know Caius Marcius is chief enemy to the people.\n",
"\n"
]
}
],
"source": [
"# Take a look at the first 250 characters in text.\n",
"print(text[:250])"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 71
},
"colab_type": "code",
"executionInfo": {
"elapsed": 1590,
"status": "ok",
"timestamp": 1584519800403,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "VPXkB5bOITqi",
"outputId": "69fdb374-4b35-442d-a934-7017245ea18e"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"65 unique characters\n",
"vocab: ['\\n', ' ', '!', '$', '&', \"'\", ',', '-', '.', '3', ':', ';', '?', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']\n"
]
}
],
"source": [
"# The unique characters in the file\n",
"vocab = sorted(set(text))\n",
"\n",
"print('{} unique characters'.format(len(vocab)))\n",
"print('vocab:', vocab)"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "GqpuKh9HMNnf"
},
"source": [
"## Process the dataset"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "6dj4e-AGMaV4"
},
"source": [
"### Vectorize the text\n",
"\n",
"Before feeding the text to our RNN we need to convert the text from a sequence of characters to a sequence of numbers. To do so we will detect all unique characters in the text, form a vocabulary out of it and replace each character with its index in the vocabulary."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 408
},
"colab_type": "code",
"executionInfo": {
"elapsed": 1510,
"status": "ok",
"timestamp": 1584519800417,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "xFFpuXfGMPq2",
"outputId": "530395b0-62e2-41e5-c0bf-63b2ca9da7cf"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" '\\n': 0,\n",
" ' ' : 1,\n",
" '!' : 2,\n",
" '$' : 3,\n",
" '&' : 4,\n",
" \"'\" : 5,\n",
" ',' : 6,\n",
" '-' : 7,\n",
" '.' : 8,\n",
" '3' : 9,\n",
" ':' : 10,\n",
" ';' : 11,\n",
" '?' : 12,\n",
" 'A' : 13,\n",
" 'B' : 14,\n",
" 'C' : 15,\n",
" 'D' : 16,\n",
" 'E' : 17,\n",
" 'F' : 18,\n",
" 'G' : 19,\n",
" ...\n",
"}\n"
]
}
],
"source": [
"# Map characters to their indices in vocabulary.\n",
"char2index = {char: index for index, char in enumerate(vocab)}\n",
"\n",
"print('{')\n",
"for char, _ in zip(char2index, range(20)):\n",
" print(' {:4s}: {:3d},'.format(repr(char), char2index[char]))\n",
"print(' ...\\n}')"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 85
},
"colab_type": "code",
"executionInfo": {
"elapsed": 1466,
"status": "ok",
"timestamp": 1584519800417,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "lQB33zI7NkRo",
"outputId": "ef5bc93f-7bad-437f-f70a-9eb53d895613"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['\\n' ' ' '!' '$' '&' \"'\" ',' '-' '.' '3' ':' ';' '?' 'A' 'B' 'C' 'D' 'E'\n",
" 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W'\n",
" 'X' 'Y' 'Z' 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o'\n",
" 'p' 'q' 'r' 's' 't' 'u' 'v' 'w' 'x' 'y' 'z']\n"
]
}
],
"source": [
"# Map character indices to characters from vacabulary.\n",
"index2char = np.array(vocab)\n",
"print(index2char)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 51
},
"colab_type": "code",
"executionInfo": {
"elapsed": 1367,
"status": "ok",
"timestamp": 1584519800418,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "DXUAlYmvN_Rj",
"outputId": "ef3412c4-68e0-4362-e093-feba628b7079"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"text_as_int length: 1115394\n",
"'First Citizen:\\n' --> array([18, 47, 56, 57, 58, 1, 15, 47, 58, 47, 64, 43, 52, 10, 0])\n"
]
}
],
"source": [
"# Convert chars in text to indices.\n",
"text_as_int = np.array([char2index[char] for char in text])\n",
"\n",
"print('text_as_int length: {}'.format(len(text_as_int)))\n",
"print('{} --> {}'.format(repr(text[:15]), repr(text_as_int[:15])))"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "CHv5HhUuTQYS"
},
"source": [
"## Create training sequences"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"colab_type": "code",
"executionInfo": {
"elapsed": 1227,
"status": "ok",
"timestamp": 1584519800418,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "9tHT8IXGTTtZ",
"outputId": "489b7e07-e898-49bc-e099-a9e7c4791c8c"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"examples_per_epoch: 11043\n"
]
}
],
"source": [
"# The maximum length sentence we want for a single input in characters.\n",
"sequence_length = 100\n",
"examples_per_epoch = len(text) // (sequence_length + 1)\n",
"\n",
"print('examples_per_epoch:', examples_per_epoch)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 102
},
"colab_type": "code",
"executionInfo": {
"elapsed": 1331,
"status": "ok",
"timestamp": 1584519800779,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "Pf5tMMDnUP3q",
"outputId": "f8e191f6-df0b-426b-9905-af62be4d605c"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"F\n",
"i\n",
"r\n",
"s\n",
"t\n"
]
}
],
"source": [
"# Create training dataset.\n",
"char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)\n",
"\n",
"for char in char_dataset.take(5):\n",
" print(index2char[char.numpy()])"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 156
},
"colab_type": "code",
"executionInfo": {
"elapsed": 3082,
"status": "ok",
"timestamp": 1584519802697,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "Ap71VjB2Vuct",
"outputId": "0c05de4a-d66c-4f20-83a7-e584d853e98d"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Sequences count: 11043\n",
"\n",
"'First Citizen:\\nBefore we proceed any further, hear me speak.\\n\\nAll:\\nSpeak, speak.\\n\\nFirst Citizen:\\nYou '\n",
"'are all resolved rather to die than to famish?\\n\\nAll:\\nResolved. resolved.\\n\\nFirst Citizen:\\nFirst, you k'\n",
"\"now Caius Marcius is chief enemy to the people.\\n\\nAll:\\nWe know't, we know't.\\n\\nFirst Citizen:\\nLet us ki\"\n",
"\"ll him, and we'll have corn at our own price.\\nIs't a verdict?\\n\\nAll:\\nNo more talking on't; let it be d\"\n",
"'one: away, away!\\n\\nSecond Citizen:\\nOne word, good citizens.\\n\\nFirst Citizen:\\nWe are accounted poor citi'\n"
]
}
],
"source": [
"# Generate batched sequences out of the char_dataset.\n",
"sequences = char_dataset.batch(sequence_length + 1, drop_remainder=True)\n",
"\n",
"# Sequences size is the same as examples_per_epoch.\n",
"print('Sequences count: {}'.format(len(list(sequences.as_numpy_iterator()))));\n",
"print()\n",
"\n",
"# Sequences examples.\n",
"for item in sequences.take(5):\n",
" print(repr(''.join(index2char[item.numpy()])))"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "Y8spPCfe-iTn"
},
"outputs": [],
"source": [
"# sequences shape:\n",
"# - 11043 sequences\n",
"# - Each sequence of length 101\n",
"#\n",
"#\n",
"# 101 101 101\n",
"# [(.....) (.....) ... (.....)]\n",
"#\n",
"# <---------- 11043 ----------->"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "HdcrcUs4Xxso"
},
"source": [
"For each sequence, duplicate and shift it to form the input and target text. For example, say `sequence_length` is `4` and our text is `Hello`. The input sequence would be `Hell`, and the target sequence `ello`."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "9fxvXsP0XFDh"
},
"outputs": [],
"source": [
"def split_input_target(chunk):\n",
" input_text = chunk[:-1]\n",
" target_text = chunk[1:]\n",
" return input_text, target_text"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"colab_type": "code",
"executionInfo": {
"elapsed": 6915,
"status": "ok",
"timestamp": 1584519807164,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "454rWIQYXXRY",
"outputId": "de3a70e1-49e8-4f68-d988-77d1856a2e60"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"dataset size: 11043\n"
]
}
],
"source": [
"dataset = sequences.map(split_input_target)\n",
"\n",
"# Dataset size is the same as examples_per_epoch.\n",
"# But each element of a sequence is now has length of `sequence_length`\n",
"# and not `sequence_length + 1`.\n",
"print('dataset size: {}'.format(len(list(dataset.as_numpy_iterator()))))"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 122
},
"colab_type": "code",
"executionInfo": {
"elapsed": 6548,
"status": "ok",
"timestamp": 1584519807165,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "Kuoh4tCdYCck",
"outputId": "b03368eb-4c92-4662-95fe-d64f6bec308d"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Input sequence size: 100\n",
"Target sequence size: 100\n",
"\n",
"Input: 'First Citizen:\\nBefore we proceed any further, hear me speak.\\n\\nAll:\\nSpeak, speak.\\n\\nFirst Citizen:\\nYou'\n",
"Target: 'irst Citizen:\\nBefore we proceed any further, hear me speak.\\n\\nAll:\\nSpeak, speak.\\n\\nFirst Citizen:\\nYou '\n"
]
}
],
"source": [
"for input_example, target_example in dataset.take(1):\n",
" print('Input sequence size:', repr(len(input_example.numpy())))\n",
" print('Target sequence size:', repr(len(target_example.numpy())))\n",
" print()\n",
" print('Input:', repr(''.join(index2char[input_example.numpy()])))\n",
" print('Target:', repr(''.join(index2char[target_example.numpy()])))"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "cp0tl0sN807l"
},
"outputs": [],
"source": [
"# dataset shape:\n",
"# - 11043 sequences\n",
"# - Each sequence is a tuple of 2 sub-sequences of length 100 (input_text and target_text)\n",
"#\n",
"#\n",
"# 100 100 100\n",
"# /(.....)\\ /(.....)\\ ... /(.....)\\ <-- input_text\n",
"# \\(.....)/ \\(.....)/ \\(.....)/ <-- target_text\n",
"#\n",
"# <----------- 11043 ------------->"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "BDYHEJ0pY1ai"
},
"source": [
"Each index of these vectors are processed as one time step. For the input at time step 0, the model receives the index for \"F\" and trys to predict the index for \"i\" as the next character. At the next timestep, it does the same thing but the RNN considers the previous step context in addition to the current input character."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 272
},
"colab_type": "code",
"executionInfo": {
"elapsed": 6469,
"status": "ok",
"timestamp": 1584519807584,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "C-0zpv53Y2o4",
"outputId": "05fb1543-308c-42f9-d2ee-003c2454772b"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Step 0\n",
" input: 18 ('F')\n",
" expected output: 47 ('i')\n",
"Step 1\n",
" input: 47 ('i')\n",
" expected output: 56 ('r')\n",
"Step 2\n",
" input: 56 ('r')\n",
" expected output: 57 ('s')\n",
"Step 3\n",
" input: 57 ('s')\n",
" expected output: 58 ('t')\n",
"Step 4\n",
" input: 58 ('t')\n",
" expected output: 1 (' ')\n"
]
}
],
"source": [
"for i, (input_idx, target_idx) in enumerate(zip(input_example[:5], target_example[:5])):\n",
" print('Step {:2d}'.format(i))\n",
" print(' input: {} ({:s})'.format(input_idx, repr(index2char[input_idx])))\n",
" print(' expected output: {} ({:s})'.format(target_idx, repr(index2char[target_idx])))"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "1iDlp40lC5YB"
},
"source": [
"## Split training sequences into batches\n",
"\n",
"We used `tf.data` to split the text into manageable sequences. But before feeding this data into the model, we need to shuffle the data and pack it into batches."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"colab_type": "code",
"executionInfo": {
"elapsed": 6256,
"status": "ok",
"timestamp": 1584519807585,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "eDq-wa5EC3wW",
"outputId": "d13224e8-062a-4df8-c232-04f60d572e8d"
},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 19,
"metadata": {
"tags": []
},
"output_type": "execute_result"
}
],
"source": [
"# Batch size.\n",
"BATCH_SIZE = 64\n",
"\n",
"# Buffer size to shuffle the dataset (TF data is designed to work\n",
"# with possibly infinite sequences, so it doesn't attempt to shuffle\n",
"# the entire sequence in memory. Instead, it maintains a buffer in\n",
"# which it shuffles elements).\n",
"BUFFER_SIZE = 10000\n",
"\n",
"dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)\n",
"\n",
"dataset"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"colab_type": "code",
"executionInfo": {
"elapsed": 8229,
"status": "ok",
"timestamp": 1584519809742,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "1x4puZiiOlyl",
"outputId": "1debcd12-46ba-4b5f-aa56-a162fbe76f1a"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Batched dataset size: 172\n"
]
}
],
"source": [
"print('Batched dataset size: {}'.format(len(list(dataset.as_numpy_iterator()))))"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 306
},
"colab_type": "code",
"executionInfo": {
"elapsed": 9749,
"status": "ok",
"timestamp": 1584519811500,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "b_kYgvQGBO0U",
"outputId": "2dfda8dc-a501-4e5f-8835-6c87b8f77051"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1st batch: input_text: tf.Tensor(\n",
"[[39 58 59 ... 57 58 53]\n",
" [47 52 1 ... 1 63 53]\n",
" [53 61 1 ... 46 57 6]\n",
" ...\n",
" [ 1 57 43 ... 52 42 8]\n",
" [58 46 43 ... 43 1 58]\n",
" [56 43 47 ... 53 59 1]], shape=(64, 100), dtype=int64)\n",
"\n",
"1st batch: target_text: tf.Tensor(\n",
"[[58 59 56 ... 58 53 41]\n",
" [52 1 63 ... 63 53 59]\n",
" [61 1 50 ... 57 6 1]\n",
" ...\n",
" [57 43 43 ... 42 8 0]\n",
" [46 43 43 ... 1 58 53]\n",
" [43 47 45 ... 59 1 61]], shape=(64, 100), dtype=int64)\n"
]
}
],
"source": [
"for input_text, target_text in dataset.take(1):\n",
" print('1st batch: input_text:', input_text)\n",
" print()\n",
" print('1st batch: target_text:', target_text)"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "UkDCH15v_2I6"
},
"outputs": [],
"source": [
"# dataset shape:\n",
"# - 172 batches\n",
"# - 64 sequences per batch\n",
"# - Each sequence is a tuple of 2 sub-sequences of length 100 (input_text and target_text)\n",
"#\n",
"#\n",
"# 100 100 100 100 100 100\n",
"# |/(.....)\\ /(.....)\\ ... /(.....)\\| ... |/(.....)\\ /(.....)\\ ... /(.....)\\| <-- input_text\n",
"# |\\(.....)/ \\(.....)/ \\(.....)/| ... |\\(.....)/ \\(.....)/ \\(.....)/| <-- target_text\n",
"#\n",
"# <------------- 64 ----------------> <------------- 64 ---------------->\n",
"#\n",
"# <--------------------------------- 172 ----------------------------------->"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "ghB-VwLlD-Oz"
},
"source": [
"## Build the model\n",
"\n",
"Use [tf.keras.Sequential](https://www.tensorflow.org/api_docs/python/tf/keras/Sequential) to define the model. For this simple example three layers are used to define our model:\n",
"\n",
"- [tf.keras.layers.Embedding](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Embedding): The input layer. A trainable lookup table that will map the numbers of each character to a vector with `embedding_dim` dimensions;\n",
"- [tf.keras.layers.LSTM](https://www.tensorflow.org/api_docs/python/tf/keras/layers/LSTM): A type of RNN with size units=rnn_units (You can also use a GRU layer here.)\n",
"- [tf.keras.layers.Dense](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dense): The output layer, with vocab_size outputs."
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 425
},
"colab_type": "code",
"executionInfo": {
"elapsed": 9280,
"status": "ok",
"timestamp": 1584519811503,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "0cg8DlO3QjuT",
"outputId": "21a9daf0-1aca-4923-958e-317293023462"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"tmp_input_array shape: (2, 8)\n",
"tmp_input_array:\n",
"[[9 0 2 1 4 6 8 8]\n",
" [3 7 9 1 0 4 8 6]]\n",
"\n",
"tmp_output_array shape: (2, 8, 5)\n",
"tmp_output_array:\n",
"[[[-0.01348125 0.04649678 0.03382711 0.03225223 0.00077888]\n",
" [ 0.01548834 0.02294052 -0.03144439 -0.02458044 -0.01775809]\n",
" [-0.01713194 0.02779663 0.02824989 -0.01959893 -0.01335952]\n",
" [-0.00085108 -0.04381631 0.04934248 0.01773458 -0.0059096 ]\n",
" [-0.04360831 0.02180418 0.04860454 -0.04669809 0.0012654 ]\n",
" [-0.04296128 0.00703833 -0.01924504 -0.03515936 -0.00462629]\n",
" [ 0.03757649 0.01776652 0.0135636 -0.00759625 0.01923021]\n",
" [ 0.03757649 0.01776652 0.0135636 -0.00759625 0.01923021]]\n",
"\n",
" [[-0.00344205 -0.04512044 -0.04339879 -0.04655287 0.04151651]\n",
" [-0.01463156 -0.04065967 -0.03041167 -0.03633127 -0.04055873]\n",
" [-0.01348125 0.04649678 0.03382711 0.03225223 0.00077888]\n",
" [-0.00085108 -0.04381631 0.04934248 0.01773458 -0.0059096 ]\n",
" [ 0.01548834 0.02294052 -0.03144439 -0.02458044 -0.01775809]\n",
" [-0.04360831 0.02180418 0.04860454 -0.04669809 0.0012654 ]\n",
" [ 0.03757649 0.01776652 0.0135636 -0.00759625 0.01923021]\n",
" [-0.04296128 0.00703833 -0.01924504 -0.03515936 -0.00462629]]]\n"
]
}
],
"source": [
"# Let's do a quick detour and see how Embeding layer works.\n",
"# It takes several char indices sequences (batch) as an input.\n",
"# It encodes every character of every sequence to a vector of tmp_embeding_size length.\n",
"tmp_vocab_size = 10\n",
"tmp_embeding_size = 5\n",
"tmp_input_length = 8\n",
"tmp_batch_size = 2\n",
"\n",
"tmp_model = tf.keras.models.Sequential()\n",
"tmp_model.add(tf.keras.layers.Embedding(\n",
" input_dim=tmp_vocab_size,\n",
" output_dim=tmp_embeding_size,\n",
" input_length=tmp_input_length\n",
"))\n",
"# The model will take as input an integer matrix of size (batch, input_length).\n",
"# The largest integer (i.e. word index) in the input should be no larger than 9 (tmp_vocab_size).\n",
"# Now model.output_shape == (None, 10, 64), where None is the batch dimension.\n",
"tmp_input_array = np.random.randint(\n",
" low=0,\n",
" high=tmp_vocab_size,\n",
" size=(tmp_batch_size, tmp_input_length)\n",
")\n",
"tmp_model.compile('rmsprop', 'mse')\n",
"tmp_output_array = tmp_model.predict(tmp_input_array)\n",
"\n",
"print('tmp_input_array shape:', tmp_input_array.shape)\n",
"print('tmp_input_array:')\n",
"print(tmp_input_array)\n",
"print()\n",
"print('tmp_output_array shape:', tmp_output_array.shape)\n",
"print('tmp_output_array:')\n",
"print(tmp_output_array)"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "I7ZuvZHBD_pS"
},
"outputs": [],
"source": [
"# Length of the vocabulary in chars.\n",
"vocab_size = len(vocab)\n",
"\n",
"# The embedding dimension.\n",
"embedding_dim = 256\n",
"\n",
"# Number of RNN units.\n",
"rnn_units = 1024"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "-sojdDCAICWO"
},
"outputs": [],
"source": [
"def build_model(vocab_size, embedding_dim, rnn_units, batch_size):\n",
" model = tf.keras.models.Sequential()\n",
"\n",
" model.add(tf.keras.layers.Embedding(\n",
" input_dim=vocab_size,\n",
" output_dim=embedding_dim,\n",
" batch_input_shape=[batch_size, None]\n",
" ))\n",
"\n",
" model.add(tf.keras.layers.LSTM(\n",
" units=rnn_units,\n",
" return_sequences=True,\n",
" stateful=True,\n",
" recurrent_initializer=tf.keras.initializers.GlorotNormal()\n",
" ))\n",
"\n",
" model.add(tf.keras.layers.Dense(vocab_size))\n",
" \n",
" return model"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "XoPwxyAPEg6z"
},
"outputs": [],
"source": [
"model = build_model(vocab_size, embedding_dim, rnn_units, BATCH_SIZE)"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 255
},
"colab_type": "code",
"executionInfo": {
"elapsed": 8897,
"status": "ok",
"timestamp": 1584519812083,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "iLnlZFgU55bQ",
"outputId": "eeece968-3a2a-4652-a0b7-d8f6944cdbc2"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"sequential_1\"\n",
"_________________________________________________________________\n",
"Layer (type) Output Shape Param # \n",
"=================================================================\n",
"embedding_1 (Embedding) (64, None, 256) 16640 \n",
"_________________________________________________________________\n",
"lstm (LSTM) (64, None, 1024) 5246976 \n",
"_________________________________________________________________\n",
"dense (Dense) (64, None, 65) 66625 \n",
"=================================================================\n",
"Total params: 5,330,241\n",
"Trainable params: 5,330,241\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"model.summary()"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 422
},
"colab_type": "code",
"executionInfo": {
"elapsed": 8704,
"status": "ok",
"timestamp": 1584519812084,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "CcaO_rO_8-GH",
"outputId": "21830f00-dc89-417a-81d8-8988354235f4"
},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
""
]
},
"execution_count": 28,
"metadata": {
"tags": []
},
"output_type": "execute_result"
}
],
"source": [
"tf.keras.utils.plot_model(\n",
" model,\n",
" show_shapes=True,\n",
" show_layer_names=True,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "JpL9idwZV6fL"
},
"source": [
"For each character the model looks up the embedding, runs the GRU one timestep with the embedding as input, and applies the dense layer to generate logits predicting the log-likelihood of the next character:\n",
"\n",
"![Model architecture](https://www.tensorflow.org/tutorials/text/images/text_generation_training.png)\n",
"\n",
"Image source: [Text generation with an RNN](https://www.tensorflow.org/tutorials/text/text_generation) notebook."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "Npruiy2RAPkt"
},
"source": [
"## Try the model"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"colab_type": "code",
"executionInfo": {
"elapsed": 11144,
"status": "ok",
"timestamp": 1584519814917,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "E4DCLA0GASL1",
"outputId": "eba67358-d6b9-4b42-a2f1-dc6ed3e0708c"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(64, 100, 65) # (batch_size, sequence_length, vocab_size)\n"
]
}
],
"source": [
"for input_example_batch, target_example_batch in dataset.take(1):\n",
" example_batch_predictions = model(input_example_batch)\n",
" print(example_batch_predictions.shape, \"# (batch_size, sequence_length, vocab_size)\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "MWebJXU9CEPd"
},
"source": [
"To get actual predictions from the model we need to sample from the output distribution, to get actual character indices. This distribution is defined by the logits over the character vocabulary."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 340
},
"colab_type": "code",
"executionInfo": {
"elapsed": 11020,
"status": "ok",
"timestamp": 1584519814919,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "Y4Jgo-iECFWI",
"outputId": "a59cb406-3813-4fb0-8297-6862a73f2499"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Prediction for the 1st letter of the batch 1st sequense:\n",
"tf.Tensor(\n",
"[-5.5658985e-03 -5.6167855e-03 2.3333444e-03 -5.4010577e-03\n",
" -1.2658490e-03 -2.0685978e-03 -1.7119508e-03 -1.9059415e-03\n",
" -1.8099162e-03 1.8549486e-03 -6.1538210e-03 2.1649192e-03\n",
" 5.9574037e-03 3.1515316e-03 -3.3867492e-03 6.0820174e-03\n",
" 2.0375988e-03 1.1445739e-03 1.3204901e-03 -2.5559508e-03\n",
" -6.7244383e-04 2.0627938e-03 -5.1490301e-03 5.0887186e-03\n",
" -2.1955089e-03 -5.3793588e-03 3.5228319e-03 -1.3459967e-03\n",
" 1.5698730e-03 -3.6733383e-03 -2.6844436e-04 1.2893401e-03\n",
" -2.9499468e-03 -2.0524827e-03 4.9254433e-03 -1.4868007e-03\n",
" -3.2468680e-03 -3.1228375e-03 1.4175450e-03 2.6028175e-03\n",
" -1.9702244e-04 -1.5901095e-04 -5.2650859e-05 -2.0922027e-03\n",
" -1.1370353e-03 -1.3396480e-03 2.7639134e-04 2.6140050e-03\n",
" 3.0784088e-04 -7.5585191e-04 2.4966525e-03 1.0233605e-03\n",
" 1.4258339e-03 -1.6010429e-04 5.1720534e-03 9.4948718e-03\n",
" -4.6309843e-03 5.5265515e-03 -1.2462666e-04 -1.3864412e-03\n",
" -2.6601439e-03 2.0305226e-03 1.0555825e-03 1.6011465e-03\n",
" 2.9543908e-03], shape=(65,), dtype=float32)\n"
]
}
],
"source": [
"print('Prediction for the 1st letter of the batch 1st sequense:')\n",
"print(example_batch_predictions[0, 0])"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"colab_type": "code",
"executionInfo": {
"elapsed": 10898,
"status": "ok",
"timestamp": 1584519814922,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "0dOr0MwFHlRb",
"outputId": "f8dc2264-a19a-4887-f964-9086a95b14a4"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"tf.Tensor([[2 2 2 1 2]], shape=(1, 5), dtype=int64)\n"
]
}
],
"source": [
"# Quick overview of how tf.random.categorical() works.\n",
"\n",
"# logits is 2-D Tensor with shape [batch_size, num_classes].\n",
"# Each slice [i, :] represents the unnormalized log-probabilities for all classes.\n",
"# In the example below we say that the probability for class \"0\" is low but the\n",
"# probability for class \"2\" is much higher.\n",
"tmp_logits = [\n",
" [-0.95, 0, 0.95],\n",
"];\n",
"\n",
"# Let's generate 5 samples. Each sample is a class index. Class probabilities \n",
"# are being taken into account (we expect to see more samples of class \"2\").\n",
"tmp_samples = tf.random.categorical(\n",
" logits=tmp_logits,\n",
" num_samples=5\n",
")\n",
"\n",
"print(tmp_samples)"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"colab_type": "code",
"executionInfo": {
"elapsed": 10502,
"status": "ok",
"timestamp": 1584519814922,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "JPzr0r4zCgS3",
"outputId": "23e7516b-1753-402a-bc61-8a8988e49f18"
},
"outputs": [
{
"data": {
"text/plain": [
"TensorShape([100, 1])"
]
},
"execution_count": 32,
"metadata": {
"tags": []
},
"output_type": "execute_result"
}
],
"source": [
"sampled_indices = tf.random.categorical(\n",
" logits=example_batch_predictions[0],\n",
" num_samples=1\n",
")\n",
"\n",
"sampled_indices.shape"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"colab_type": "code",
"executionInfo": {
"elapsed": 10276,
"status": "ok",
"timestamp": 1584519814923,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "YaA7DclID8dz",
"outputId": "08a3c58b-e331-4f91-8e3f-be2649a85f48"
},
"outputs": [
{
"data": {
"text/plain": [
"(100,)"
]
},
"execution_count": 33,
"metadata": {
"tags": []
},
"output_type": "execute_result"
}
],
"source": [
"sampled_indices = tf.squeeze(\n",
" input=sampled_indices,\n",
" axis=-1\n",
").numpy()\n",
"\n",
"sampled_indices.shape"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 119
},
"colab_type": "code",
"executionInfo": {
"elapsed": 10212,
"status": "ok",
"timestamp": 1584519814924,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "_ubGQ0gVENhB",
"outputId": "4ab910cf-06e6-4fd1-c751-746d79d895d0"
},
"outputs": [
{
"data": {
"text/plain": [
"array([62, 61, 40, 20, 4, 44, 17, 43, 28, 18, 50, 48, 0, 11, 58, 17, 41,\n",
" 22, 62, 46, 12, 24, 25, 54, 10, 6, 18, 5, 4, 23, 55, 51, 38, 9,\n",
" 50, 42, 42, 9, 49, 4, 48, 54, 59, 4, 30, 7, 20, 4, 53, 27, 21,\n",
" 5, 16, 27, 25, 47, 21, 7, 24, 25, 46, 21, 6, 56, 1, 21, 24, 28,\n",
" 55, 8, 23, 0, 45, 29, 4, 29, 5, 49, 25, 45, 3, 4, 22, 11, 13,\n",
" 30, 43, 0, 62, 0, 48, 29, 33, 58, 46, 51, 12, 59, 55, 62])"
]
},
"execution_count": 34,
"metadata": {
"tags": []
},
"output_type": "execute_result"
}
],
"source": [
"sampled_indices"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 122
},
"colab_type": "code",
"executionInfo": {
"elapsed": 9992,
"status": "ok",
"timestamp": 1584519814925,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "Gi9HOzw9EajS",
"outputId": "aa7dc69f-63ad-4273-b2df-e6cc8eada7cd"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Input:\n",
" 'is service.\\n\\nBAPTISTA:\\nA thousand thanks, Signior Gremio.\\nWelcome, good Cambio.\\nBut, gentle sir, met'\n",
"\n",
"Next char prediction:\n",
" \"xwbH&fEePFlj\\n;tEcJxh?LMp:,F'&KqmZ3ldd3k&jpu&R-H&oOI'DOMiI-LMhI,r ILPq.K\\ngQ&Q'kMg$&J;ARe\\nx\\njQUthm?uqx\"\n"
]
}
],
"source": [
"print('Input:\\n', repr(''.join(index2char[input_example_batch[0]])))\n",
"print()\n",
"print('Next char prediction:\\n', repr(''.join(index2char[sampled_indices])))"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 272
},
"colab_type": "code",
"executionInfo": {
"elapsed": 9825,
"status": "ok",
"timestamp": 1584519814926,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "b87e0lsYMTsv",
"outputId": "75c7e1bf-b2d5-4f15-cba7-0ae5f8765327"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Prediction 0\n",
" input: 47 ('i')\n",
" next predicted: 1 ('x')\n",
"Prediction 1\n",
" input: 57 ('s')\n",
" next predicted: 1 ('w')\n",
"Prediction 2\n",
" input: 1 (' ')\n",
" next predicted: 1 ('b')\n",
"Prediction 3\n",
" input: 57 ('s')\n",
" next predicted: 1 ('H')\n",
"Prediction 4\n",
" input: 43 ('e')\n",
" next predicted: 1 ('&')\n"
]
}
],
"source": [
"for i, (input_idx, sample_idx) in enumerate(zip(input_example_batch[0][:5], sampled_indices[:5])):\n",
" print('Prediction {:2d}'.format(i))\n",
" print(' input: {} ({:s})'.format(input_idx, repr(index2char[input_idx])))\n",
" print(' next predicted: {} ({:s})'.format(target_idx, repr(index2char[sample_idx])))"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "LqcBufKEE_p6"
},
"source": [
"## Train the model\n",
"\n",
"At this point the problem can be treated as a standard classification problem. Given the previous RNN state, and the input this time step, predict the class of the next character."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "l4s0-PvrFub5"
},
"source": [
"### Attach an optimizer, and a loss function"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 51
},
"colab_type": "code",
"executionInfo": {
"elapsed": 9644,
"status": "ok",
"timestamp": 1584519814927,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "UOEUUm6JE95a",
"outputId": "ba94797c-e3c6-4d71-ac44-2cf76f7b2843"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Prediction shape: (64, 100, 65) # (batch_size, sequence_length, vocab_size)\n",
"scalar_loss: 4.17424\n"
]
}
],
"source": [
"# An objective function.\n",
"# The function is any callable with the signature scalar_loss = fn(y_true, y_pred).\n",
"def loss(labels, logits):\n",
" return tf.keras.losses.sparse_categorical_crossentropy(\n",
" y_true=labels,\n",
" y_pred=logits,\n",
" from_logits=True\n",
" )\n",
"\n",
"example_batch_loss = loss(target_example_batch, example_batch_predictions)\n",
"\n",
"print(\"Prediction shape: \", example_batch_predictions.shape, \" # (batch_size, sequence_length, vocab_size)\")\n",
"print(\"scalar_loss: \", example_batch_loss.numpy().mean())"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "SXhJsB6eFgrJ"
},
"outputs": [],
"source": [
"adam_optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)\n",
"model.compile(\n",
" optimizer=adam_optimizer,\n",
" loss=loss\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "MK3Cf-xZFwL4"
},
"source": [
"### Configure checkpoints"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "LUhXnHPJFy5q"
},
"outputs": [],
"source": [
"# Directory where the checkpoints will be saved.\n",
"checkpoint_dir = 'tmp/checkpoints'\n",
"os.makedirs(checkpoint_dir, exist_ok=True)\n",
"\n",
"# Name of the checkpoint files\n",
"checkpoint_prefix = os.path.join(checkpoint_dir, 'ckpt_{epoch}')\n",
"\n",
"checkpoint_callback=tf.keras.callbacks.ModelCheckpoint(\n",
" filepath=checkpoint_prefix,\n",
" save_weights_only=True\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "oFg9MFJoGZWf"
},
"source": [
"### Execute the training"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "AVk-pARPGaja"
},
"outputs": [],
"source": [
"EPOCHS=40"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 1000
},
"colab_type": "code",
"executionInfo": {
"elapsed": 1197604,
"status": "ok",
"timestamp": 1584521003714,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "y0rveBdAGeEz",
"outputId": "2449548c-5ca4-4340-df15-9cdf1714dcbc"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Train for 172 steps\n",
"Epoch 1/40\n",
"172/172 [==============================] - 31s 179ms/step - loss: 2.5607\n",
"Epoch 2/40\n",
"172/172 [==============================] - 29s 171ms/step - loss: 1.8654\n",
"Epoch 3/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 1.6191\n",
"Epoch 4/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 1.4893\n",
"Epoch 5/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 1.4108\n",
"Epoch 6/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 1.3550\n",
"Epoch 7/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 1.3101\n",
"Epoch 8/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 1.2705\n",
"Epoch 9/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 1.2335\n",
"Epoch 10/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 1.1980\n",
"Epoch 11/40\n",
"172/172 [==============================] - 29s 171ms/step - loss: 1.1610\n",
"Epoch 12/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 1.1230\n",
"Epoch 13/40\n",
"172/172 [==============================] - 30s 174ms/step - loss: 1.0843\n",
"Epoch 14/40\n",
"172/172 [==============================] - 30s 174ms/step - loss: 1.0446\n",
"Epoch 15/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 1.0031\n",
"Epoch 16/40\n",
"172/172 [==============================] - 30s 174ms/step - loss: 0.9617\n",
"Epoch 17/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 0.9211\n",
"Epoch 18/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 0.8810\n",
"Epoch 19/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 0.8413\n",
"Epoch 20/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.8018\n",
"Epoch 21/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.7692\n",
"Epoch 22/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.7346\n",
"Epoch 23/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 0.7056\n",
"Epoch 24/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.6777\n",
"Epoch 25/40\n",
"172/172 [==============================] - 30s 174ms/step - loss: 0.6528\n",
"Epoch 26/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.6282\n",
"Epoch 27/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 0.6077\n",
"Epoch 28/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.5897\n",
"Epoch 29/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 0.5735\n",
"Epoch 30/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.5566\n",
"Epoch 31/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.5447\n",
"Epoch 32/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.5318\n",
"Epoch 33/40\n",
"172/172 [==============================] - 30s 174ms/step - loss: 0.5198\n",
"Epoch 34/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.5103\n",
"Epoch 35/40\n",
"172/172 [==============================] - 30s 172ms/step - loss: 0.4999\n",
"Epoch 36/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.4930\n",
"Epoch 37/40\n",
"172/172 [==============================] - 30s 174ms/step - loss: 0.4858\n",
"Epoch 38/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.4782\n",
"Epoch 39/40\n",
"172/172 [==============================] - 30s 173ms/step - loss: 0.4719\n",
"Epoch 40/40\n",
"172/172 [==============================] - 29s 170ms/step - loss: 0.4672\n"
]
}
],
"source": [
"history = model.fit(\n",
" x=dataset,\n",
" epochs=EPOCHS,\n",
" callbacks=[\n",
" checkpoint_callback\n",
" ]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "mLdnOyvzMggJ"
},
"outputs": [],
"source": [
"def render_training_history(training_history):\n",
" loss = training_history.history['loss']\n",
" plt.title('Loss')\n",
" plt.xlabel('Epoch')\n",
" plt.ylabel('Loss')\n",
" plt.plot(loss, label='Training set')\n",
" plt.legend()\n",
" plt.grid(linestyle='--', linewidth=1, alpha=0.5)\n",
" plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 295
},
"colab_type": "code",
"executionInfo": {
"elapsed": 1197218,
"status": "ok",
"timestamp": 1584521003724,
"user": {
"displayName": "Oleksii Trekhleb",
"photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GiiA4aUKCbFho88Jd0WWMoAqQUt3jbuCtfNYpHVOA=s64",
"userId": "03172675069638383074"
},
"user_tz": -60
},
"id": "4Ghveem_OQBV",
"outputId": "f4fcc0aa-4509-4330-a88b-1f5863c354d2"
},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"