{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Generative networks\n",
    "\n",
    "Recurrent Neural Networks (RNNs) and their gated cell variants such as Long Short Term Memory Cells (LSTMs) and Gated Recurrent Units (GRUs) provided a mechanism for language modeling, i.e. they can learn word ordering and provide predictions for next word in a sequence. This allows us to use RNNs for **generative tasks**, such as ordinary text generation, machine translation, and even image captioning.\n",
    "\n",
    "In RNN architecture we discussed in the previous unit, each RNN unit produced next next hidden state as an output. However, we can also add another output to each recurrent unit, which would allow us to output a **sequence** (which is equal in length to the original sequence). Moreover, we can use RNN units that do not accept an input at each step, and just take some initial state vector, and then produce a sequence of outputs.\n",
    "\n",
    "In this notebook, we will focus on simple generative models that help us generate text. For simplicity, let's build **character-level network**, which generates text letter by letter. During training, we need to take some text corpus, and split it into letter sequences. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "from tensorflow import keras\n",
    "import tensorflow_datasets as tfds\n",
    "import numpy as np\n",
    "\n",
    "ds_train, ds_test = tfds.load('ag_news_subset').values()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Building character vocabulary\n",
    "\n",
    "To build character-level generative network, we need to split text into individual characters instead of words. `TextVectorization` layer that we have been using before cannot do that, so we have to options:\n",
    "\n",
    "* Manually load text and do tokenization 'by hand', as in [this official Keras example](https://keras.io/examples/generative/lstm_character_level_text_generation/)\n",
    "* Use `Tokenizer` class for character-level tokenization.\n",
    "\n",
    "We will go with the second option. `Tokenizer` can also be used to tokenize into words, so one should be able to switch from char-level to word-level tokenization quite easily.\n",
    "\n",
    "To do character-level tokenization, we need to pass `char_level=True` parameter:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def extract_text(x):\n",
    "    return x['title']+' '+x['description']\n",
    "\n",
    "def tupelize(x):\n",
    "    return (extract_text(x),x['label'])\n",
    "\n",
    "tokenizer = keras.preprocessing.text.Tokenizer(char_level=True,lower=False)\n",
    "tokenizer.fit_on_texts([x['title'].numpy().decode('utf-8') for x in ds_train])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We also want to use one special token to denote **end of sequence**, which we will call `<eos>`. Let's add it manually to the vocabulary:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "eos_token = len(tokenizer.word_index)+1\n",
    "tokenizer.word_index['<eos>'] = eos_token\n",
    "\n",
    "vocab_size = eos_token + 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, to encode text into sequences of numbers, we can use:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[[48, 2, 10, 10, 5, 44, 1, 25, 5, 8, 10, 13, 78]]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tokenizer.texts_to_sequences(['Hello, world!'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Training a generative RNN to generate titles\n",
    "\n",
    "The way we will train RNN to generate news titles is the following. On each step, we will take one title, which will be fed into an RNN, and for each input character we will ask the network to generate next output character:\n",
    "\n",
    "![Image showing an example RNN generation of the word 'HELLO'.](./images/rnn-generate.png)\n",
    "\n",
    "For the last character of our sequence, we will ask the network to generate `<eos>` token.\n",
    "\n",
    "The main difference between generative RNN that we are using here is that we will take an output from each step of the RNN, and not just from the final cell. This can be achieved by specifying `return_sequences` parameter to the RNN cell.\n",
    "\n",
    "Thus, during the training, an input to the network would be a sequence of encoded characters of some length, and an output would be a sequence of the same length, but shifted by one element and terminated by `<eos>`. Minibatch will consist of several such sequences, and we would need to use **padding** to align all sequences.\n",
    "\n",
    "Let's create functions that will transform the dataset for us. Because we want to pad sequences on minibatch level, we will first batch the dataset by calling `.batch()`, and then `map` it in order to do transformation. So, the transformation function will take a whole minibatch as a parameter:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def title_batch(x):\n",
    "    x = [t.numpy().decode('utf-8') for t in x]\n",
    "    z = tokenizer.texts_to_sequences(x)\n",
    "    z = tf.keras.preprocessing.sequence.pad_sequences(z)\n",
    "    return tf.one_hot(z,vocab_size), tf.one_hot(tf.concat([z[:,1:],tf.constant(eos_token,shape=(len(z),1))],axis=1),vocab_size)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A few important things that we do here:\n",
    "* We first extract the actual text from the string tensor\n",
    "* `text_to_sequences` converts the list of strings into a list of integer tensors\n",
    "* `pad_sequences` pads those tensors to their maximum length\n",
    "* We finally one-hot encode all the characters, and also do the shifting and `<eos>` appending. We will soon see why we need one-hot-encoded characters\n",
    "\n",
    "However, this function is **Pythonic**, i.e. it cannot be automatically translated into Tensorflow computational graph. We will get errors if we try to use this function directly in the `Dataset.map` function. We need to enclose this Pythonic call by using `py_function` wrapper: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def title_batch_fn(x):\n",
    "    x = x['title']\n",
    "    a,b = tf.py_function(title_batch,inp=[x],Tout=(tf.float32,tf.float32))\n",
    "    return a,b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **Note**: Differentiating between Pythonic and Tensorflow transformation functions may seem a little too complex, and you may be questioning why we do not transform the dataset using standard Python functions before passing it to `fit`. While this definitely can be done, using `Dataset.map` has a huge advantage, because data transformation pipeline is executed using Tensorflow computational graph, which takes advantage of GPU computations, and minimized the need to pass data between CPU/GPU.\n",
    "\n",
    "Now we can build our generator network and start training. It can be based on any recurrent cell which we discussed in the previous unit (simple, LSTM or GRU). In our example we will use LSTM.\n",
    "\n",
    "Because the network takes characters as input, and vocabulary size is pretty small, we do not need embedding layer, one-hot-encoded input can directly go into LSTM cell. Output layer would be a `Dense` classifier that will convert LSTM output into one-hot-encoded token numbers.\n",
    "\n",
    "In addition, since we are dealing with variable-length sequences, we can use `Masking` layer to create a mask that will ignore padded part of the string. This is not strictly needed, because we are not very much interested in everything that goes beyond `<eos>` token, but we will use it for the sake of getting some experience with this layer type. `input_shape` would be `(None, vocab_size)`, where `None` indicates the sequence of variable length, and output shape is `(None,vocab_size)` as well, as you can see from the `summary`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "masking (Masking)            (None, None, 84)          0         \n",
      "_________________________________________________________________\n",
      "lstm (LSTM)                  (None, None, 128)         109056    \n",
      "_________________________________________________________________\n",
      "dense (Dense)                (None, None, 84)          10836     \n",
      "=================================================================\n",
      "Total params: 119,892\n",
      "Trainable params: 119,892\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n",
      "15000/15000 [==============================] - 229s 15ms/step - loss: 1.5385\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.callbacks.History at 0x7fa40c1245e0>"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Masking(input_shape=(None,vocab_size)),\n",
    "    keras.layers.LSTM(128,return_sequences=True),\n",
    "    keras.layers.Dense(vocab_size,activation='softmax')\n",
    "])\n",
    "\n",
    "model.summary()\n",
    "model.compile(loss='categorical_crossentropy')\n",
    "\n",
    "model.fit(ds_train.batch(8).map(title_batch_fn))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Generating output\n",
    "\n",
    "Now that we have trained the model, we want to use it to generate some output. First of all, we need a way to decode text represented by a sequence of token numbers. To do this, we could use `tokenizer.sequences_to_texts` function; however, it does not work well with character-level tokenization. Therefore we will take a dictionary of tokens from the tokenizer (called `word_index`), build a reverse map, and write our own decoding function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "reverse_map = {val:key for key, val in tokenizer.word_index.items()}\n",
    "\n",
    "def decode(x):\n",
    "    return ''.join([reverse_map[t] for t in x])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, let's do generation. We will start with some string `start`, encode it into a sequence `inp`, and then on each step we will call our network to infer the next character. \n",
    "\n",
    "Output of the network `out` is a vector of `vocab_size` elements representing probablities of each token, and we can find the most probably token number by using `argmax`. We then append this character to the generated list of tokens, and proceed with generation. This process of generating one character is repeated `size` times to generate required number of characters, and we terminate early when `eos_token` is encountered."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Today #39;s lead to strike for the strike for the strike for the strike (AFP)'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def generate(model,size=100,start='Today '):\n",
    "        inp = tokenizer.texts_to_sequences([start])[0]\n",
    "        chars = inp\n",
    "        for i in range(size):\n",
    "            out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
    "            nc = tf.argmax(out)\n",
    "            if nc==eos_token:\n",
    "                break\n",
    "            chars.append(nc.numpy())\n",
    "            inp = inp+[nc]\n",
    "        return decode(chars)\n",
    "    \n",
    "generate(model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sampling output during training \n",
    "\n",
    "Because we do not have any useful metrics such as *accuracy*, the only way we can see that our model is getting better is by **sampling** generated string during training. To do it, we will use **callbacks**, i.e. functions that we can pass to the `fit` function, and that will be called periodically during training. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/3\n",
      "15000/15000 [==============================] - 226s 15ms/step - loss: 1.2703\n",
      "Today #39;s a lead in the company for the strike\n",
      "Epoch 2/3\n",
      "15000/15000 [==============================] - 227s 15ms/step - loss: 1.2057\n",
      "Today #39;s the Market Service on Security Start (AP)\n",
      "Epoch 3/3\n",
      "15000/15000 [==============================] - 226s 15ms/step - loss: 1.1752\n",
      "Today #39;s a line on the strike to start for the start\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.callbacks.History at 0x7fa40c74e3d0>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sampling_callback = keras.callbacks.LambdaCallback(\n",
    "  on_epoch_end = lambda batch, logs: print(generate(model))\n",
    ")\n",
    "\n",
    "model.fit(ds_train.batch(8).map(title_batch_fn),callbacks=[sampling_callback],epochs=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This example already generates some pretty good text, but it can be further improved in several ways:\n",
    "* **More text**. We have only used titles for our task, but you may want to experiment with full text. Remember that RNNs are not too great with handling long sequences, so it makes sense either to split them into shorted sentences, or to always train on a fixed sequence length of some predefined value `num_chars` (say, 256). You may try to change the example above into such architecture, using [official Keras tutorial](https://keras.io/examples/generative/lstm_character_level_text_generation/) as an inspiration.\n",
    "* **Multilayer LSTM**. It makes sense to try 2 or 3 layers of LSTM cells. As we mentioned in the previous unit, each layer of LSTM extracts certain patterns from text, and in case of character-level generator we can expect lower LSTM level to be responsible for extracting syllables, and higher levels - for words and word combinations. This can be simply implemented by passing number-of-layers parameter to LSTM constructor.\n",
    "* You may also want to experiment with **GRU units** and see which ones perform better, and with **different hidden layer sizes**. Too large hidden layer may result in overfitting (e.g. network will learn exact text), and smaller size might not produce good result."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Soft text generation and temperature\n",
    "\n",
    "In the previous definition of `generate`, we were always taking the character with highest probability as the next character in generated text. This resulted in the fact that the text often \"cycled\" between the same character sequences again and again, like in this example:\n",
    "```\n",
    "today of the second the company and a second the company ...\n",
    "```\n",
    "\n",
    "However, if we look at the probability distribution for the next character, it could be that the difference between a few highest probabilities is not huge, e.g. one character can have probability 0.2, another - 0.19, etc. For example, when looking for the next character in the sequence '*play*', next character can equally well be either space, or **e** (as in the word *player*).\n",
    "\n",
    "This leads us to the conclusion that it is not always \"fair\" to select the character with higher probability, because choosing the second highest might still lead us to meaningful text. It is more wise to **sample** characters from the probability distribution given by the network output.\n",
    "\n",
    "This sampling can be done using `np.multinomial` function that implements so-called **multinomial distribution**. A function that implements this **soft** text generation is defined below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "--- Temperature = 0.3\n",
      "Today #39;s strike #39; to start at the store return\n",
      "On Sunday PO to Be Data Profit Up (Reuters)\n",
      "Moscow, SP wins straight to the Microsoft #39;s control of the space start\n",
      "President olding of the blast start for the strike to pay &lt;b&gt;...&lt;/b&gt;\n",
      "Little red riding hood ficed to the spam countered in European &lt;b&gt;...&lt;/b&gt;\n",
      "\n",
      "--- Temperature = 0.8\n",
      "Today countie strikes ryder missile faces food market blut\n",
      "On Sunday collores lose-toppy of sale of Bullment in &lt;b&gt;...&lt;/b&gt;\n",
      "Moscow, IBM Diffeiting in Afghan Software Hotels (Reuters)\n",
      "President Ol Luster for Profit Peaced Raised (AP)\n",
      "Little red riding hood dace on depart talks #39; bank up\n",
      "\n",
      "--- Temperature = 1.0\n",
      "Today wits House buiting debate fixes #39; supervice stake again\n",
      "On Sunday arling digital poaching In for level\n",
      "Moscow, DS Up 7, Top Proble Protest Caprey Mamarian Strike\n",
      "President teps help of roubler stepted lessabul-Dhalitics (AFP)\n",
      "Little red riding hood signs on cash in Carter-youb\n",
      "\n",
      "--- Temperature = 1.3\n",
      "Today wits flawer ro, pSIA figat's co DroftwavesIs Talo up\n",
      "On Sunday hround elitwing wint EU Powerburlinetien\n",
      "Moscow, Bazz #39;s sentries olymen winnelds' next for Olympite Huc?\n",
      "President lost securitys from power Elections in Smiltrials\n",
      "Little red riding hood vides profit, exponituity, profitmainalist-at said listers\n",
      "\n",
      "--- Temperature = 1.8\n",
      "Today #39;It: He deat: N.KA Asside\n",
      "On Sunday i arry Par aldeup patient Wo stele1\n"
     ]
    },
    {
     "ename": "KeyError",
     "evalue": "0",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mKeyError\u001b[0m                                  Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m     18\u001b[0m     \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"\\n--- Temperature = {i}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     19\u001b[0m     \u001b[0;32mfor\u001b[0m \u001b[0mj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m         \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgenerate_soft\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0msize\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m300\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwords\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mj\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mtemperature\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36mgenerate_soft\u001b[0;34m(model, size, start, temperature)\u001b[0m\n\u001b[1;32m     11\u001b[0m             \u001b[0mchars\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     12\u001b[0m             \u001b[0minp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0minp\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchars\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     14\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     15\u001b[0m \u001b[0mwords\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m'Today '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'On Sunday '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Moscow, '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'President '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Little red riding hood '\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36mdecode\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m     \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36m<listcomp>\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m     \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mKeyError\u001b[0m: 0"
     ]
    }
   ],
   "source": [
    "def generate_soft(model,size=100,start='Today ',temperature=1.0):\n",
    "        inp = tokenizer.texts_to_sequences([start])[0]\n",
    "        chars = inp\n",
    "        for i in range(size):\n",
    "            out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
    "            probs = tf.exp(tf.math.log(out)/temperature).numpy().astype(np.float64)\n",
    "            probs = probs/np.sum(probs)\n",
    "            nc = np.argmax(np.random.multinomial(1,probs,1))\n",
    "            if nc==eos_token:\n",
    "                break\n",
    "            chars.append(nc)\n",
    "            inp = inp+[nc]\n",
    "        return decode(chars)\n",
    "\n",
    "words = ['Today ','On Sunday ','Moscow, ','President ','Little red riding hood ']\n",
    "    \n",
    "for i in [0.3,0.8,1.0,1.3,1.8]:\n",
    "    print(f\"\\n--- Temperature = {i}\")\n",
    "    for j in range(5):\n",
    "        print(generate_soft(model,size=300,start=words[j],temperature=i))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We have introduced one more parameter called **temperature**, which is used to indicate how hard we should stick to the highest probability. If temperature is 1.0, we do fair multinomial sampling, and when temperature goes to infinity - all probabilities become equal, and we randomly select next character. In the example below we can observe that the text becomes meaningless when we increase the temperature too much, and it resembles \"cycled\" hard-generated text when it becomes closer to 0. "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py38_tensorflow",
   "language": "python",
   "name": "conda-env-py38_tensorflow-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
