{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Music Generation\n", "\n", "> In this post, We will take a hands-on-lab of Music Generation. This tutorial is the software lab, which is part of \"introduction to deep learning 2021\" offered from MIT 6.S191.\n", "\n", "- toc: true \n", "- badges: true\n", "- comments: true\n", "- author: Chanseok Kang\n", "- categories: [Python, Tensorflow, MIT]\n", "- image: images/lstm_inference.png" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Copyright Information\n", "\n", "Copyright 2021 MIT 6.S191 Introduction to Deep Learning. All Rights Reserved.\n", "\n", "Licensed under the MIT License. You may not use this file except in compliance\n", "with the License. Use and/or modification of this code outside of 6.S191 must\n", "reference:\n", "\n", "© MIT 6.S191: Introduction to Deep Learning\n", "http://introtodeeplearning.com" ] }, { "cell_type": "markdown", "metadata": { "id": "edLrpksDJfDg" }, "source": [ "## Music Generation with RNNs\n", "\n", "In this portion of the lab, we will explore building a Recurrent Neural Network (RNN) for music generation. We will train a model to learn the patterns in raw sheet music in [ABC notation](https://en.wikipedia.org/wiki/ABC_notation) and then use this model to generate new music. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "cellView": "form", "id": "dq6HedFjckGr" }, "outputs": [], "source": [ "import tensorflow as tf \n", "import numpy as np\n", "import os\n", "import time\n", "import regex as re\n", "import subprocess\n", "import urllib\n", "import functools\n", "from IPython import display as ipythondisplay\n", "from tqdm import tqdm\n", "import matplotlib.pyplot as plt\n", "\n", "# Check that we are using a GPU, if not switch runtimes\n", "# using Runtime > Change Runtime Type > GPU\n", "assert len(tf.config.list_physical_devices('GPU')) > 0" ] }, { "cell_type": "markdown", "metadata": { "id": "ynLs1RCduz_O" }, "source": [ "## Dataset\n", "\n", "![Let's Dance!](http://33.media.tumblr.com/3d223954ad0a77f4e98a7b87136aa395/tumblr_nlct5lFVbF1qhu7oio1_500.gif)\n", "\n", "We've gathered a dataset of thousands of Irish folk songs, represented in the ABC notation. Let's download the dataset and inspect it: " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Found 817 songs in text\n", "\n", "Example song: \n", "X:1\n", "T:Alexander's\n", "Z: id:dc-hornpipe-1\n", "M:C|\n", "L:1/8\n", "K:D Major\n", "(3ABc|dAFA DFAd|fdcd FAdf|gfge fefd|(3efe (3dcB A2 (3ABc|!\n", "dAFA DFAd|fdcd FAdf|gfge fefd|(3efe dc d2:|!\n", "AG|FAdA FAdA|GBdB GBdB|Acec Acec|dfaf gecA|!\n", "FAdA FAdA|GBdB GBdB|Aceg fefd|(3efe dc d2:|!\n" ] } ], "source": [ "cwd = os.getcwd()\n", "\n", "def extract_song_snippet(text):\n", " pattern = '(^|\\n\\n)(.*?)\\n\\n'\n", " search_results = re.findall(pattern, text, overlapped=True, flags=re.DOTALL)\n", " songs = [song[1] for song in search_results]\n", " print(\"Found {} songs in text\".format(len(songs)))\n", " return songs\n", "\n", "songs = []\n", "\n", "with open(os.path.join(cwd, 'dataset', 'irish.abc'), 'r') as f:\n", " text = f.read()\n", " songs = extract_song_snippet(text)\n", " \n", "# Print one of the songs to inspect it in greater detail!\n", "example_song = songs[0]\n", "print(\"\\nExample song: \")\n", "print(example_song)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can easily convert a song in ABC notation to an audio waveform and play it back. Be patient for this conversion to run, it can take some time.\n", "\n", "> Note: Actually, original notebook is for google Colab, and it can reproduce it on linux environment. But if you can install `timidity` and `abc2midi` on windows, it can be worked on windows. I`ve done this on windows jupyter environment" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def save_song_to_abc(song, filename=\"tmp\"):\n", " save_name = \"{}.abc\".format(filename)\n", " with open(save_name, \"w\") as f:\n", " f.write(song)\n", " return filename\n", "\n", "def abc2wav(abc_file):\n", " suf = abc_file.rstrip('.abc')\n", " cmd = \"abc2midi {} -o {}\".format(abc_file, suf + \".mid\")\n", " os.system(cmd)\n", " cmd = \"timidity {}.mid -Ow {}.wav\".format(suf, suf)\n", " return os.system(cmd) \n", "\n", "def play_wav(wav_file):\n", " return ipythondisplay.Audio(wav_file)\n", "\n", "def play_song(song):\n", " basename = save_song_to_abc(song)\n", " ret = abc2wav(basename + '.abc')\n", " if ret == 0: #did not suceed\n", " return play_wav(basename+'.wav')\n", " return None" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "play_song(example_song)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One important thing to think about is that this notation of music does not simply contain information on the notes being played, but additionally there is meta information such as the song title, key, and tempo. How does the number of different characters that are present in the text file impact the complexity of the learning problem? This will become important soon, when we generate a numerical representation for the text data." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "There are 83 unique characters in the dataset\n" ] } ], "source": [ "# Join our list of song strings into a single string containing all songs\n", "songs_joined = \"\\n\\n\".join(songs) \n", "\n", "# Find all unique characters in the joined string\n", "vocab = sorted(set(songs_joined))\n", "print(\"There are\", len(vocab), \"unique characters in the dataset\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Process the dataset for the learning task\n", "\n", "Let's take a step back and consider our prediction task. We're trying to train a RNN model to learn patterns in ABC music, and then use this model to generate (i.e., predict) a new piece of music based on this learned information. \n", "\n", "Breaking this down, what we're really asking the model is: given a character, or a sequence of characters, what is the most probable next character? We'll train the model to perform this task. \n", "\n", "To achieve this, we will input a sequence of characters to the model, and train the model to predict the output, that is, the following character at each time step. RNNs maintain an internal state that depends on previously seen elements, so information about all characters seen up until a given moment will be taken into account in generating the prediction." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Vectorize the text\n", "\n", "Before we begin training our RNN model, we'll need to create a numerical representation of our text-based dataset. To do this, we'll generate two lookup tables: one that maps characters to numbers, and a second that maps numbers back to characters. Recall that we just identified the unique characters present in the text." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "### Define numerical representation of text ###\n", "\n", "# Create a mapping from character to unique index.\n", "# For example, to get the index of the character \"d\", \n", "# we can evaluate `char2idx[\"d\"]`. \n", "char2idx = {u:i for i, u in enumerate(vocab)}\n", "\n", "# Create a mapping from indices to characters. This is\n", "# the inverse of char2idx and allows us to convert back\n", "# from unique index to the character in our vocabulary.\n", "idx2char = np.array(vocab)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This gives us an integer representation for each character. Observe that the unique characters (i.e., our vocabulary) in the text are mapped as indices from 0 to `len(unique)`. Let's take a peek at this numerical representation of our dataset:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "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", " '-' : 9,\n", " '.' : 10,\n", " '/' : 11,\n", " '0' : 12,\n", " '1' : 13,\n", " '2' : 14,\n", " '3' : 15,\n", " '4' : 16,\n", " '5' : 17,\n", " '6' : 18,\n", " '7' : 19,\n", " ...\n", "}\n" ] } ], "source": [ "print('{')\n", "for char,_ in zip(char2idx, range(20)):\n", " print(' {:4s}: {:3d},'.format(repr(char), char2idx[char]))\n", "print(' ...\\n}')" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "### Vectorize the songs string ###\n", "\n", "'''TODO: Write a function to convert the all songs string to a vectorized\n", " (i.e., numeric) representation. Use the appropriate mapping\n", " above to convert from vocab characters to the corresponding indices.\n", "\n", " NOTE: the output of the `vectorize_string` function \n", " should be a np.array with `N` elements, where `N` is\n", " the number of characters in the input string\n", "'''\n", "\n", "def vectorize_string(string):\n", " vectorized_list = np.array([char2idx[s] for s in string])\n", " return vectorized_list\n", "\n", "vectorized_songs = vectorize_string(songs_joined)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also look at how the first part of the text is mapped to an integer representation:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'X:1\\nT:Alex' ---- characters mapped to int ----> [49 22 13 0 45 22 26 67 60 79]\n" ] } ], "source": [ "print ('{} ---- characters mapped to int ----> {}'.format(repr(songs_joined[:10]), vectorized_songs[:10]))\n", "# check that vectorized_songs is a numpy array\n", "assert isinstance(vectorized_songs, np.ndarray), \"returned result should be a numpy array\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create training examples and targets\n", "\n", "Our next step is to actually divide the text into example sequences that we'll use during training. Each input sequence that we feed into our RNN will contain `seq_length` characters from the text. We'll also need to define a target sequence for each input sequence, which will be used in training the RNN to predict the next character. For each input, the corresponding target will contain the same length of text, except shifted one character to the right.\n", "\n", "To do this, we'll break the text into chunks of `seq_length+1`. Suppose `seq_length` is 4 and our text is \"Hello\". Then, our input sequence is \"Hell\" and the target sequence is \"ello\".\n", "\n", "The batch method will then let us convert this stream of character indices to sequences of the desired size." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def test_batch_func_types(func, args):\n", " ret = func(*args)\n", " assert len(ret) == 2, \"[FAIL] get_batch must return two arguments (input and label)\"\n", " assert type(ret[0]) == np.ndarray, \"[FAIL] test_batch_func_types: x is not np.array\"\n", " assert type(ret[1]) == np.ndarray, \"[FAIL] test_batch_func_types: y is not np.array\"\n", " print(\"[PASS] test_batch_func_types\")\n", " return True\n", "\n", "def test_batch_func_shapes(func, args):\n", " dataset, seq_length, batch_size = args\n", " x, y = func(*args)\n", " correct = (batch_size, seq_length)\n", " assert x.shape == correct, \"[FAIL] test_batch_func_shapes: x {} is not correct shape {}\".format(x.shape, correct)\n", " assert y.shape == correct, \"[FAIL] test_batch_func_shapes: y {} is not correct shape {}\".format(y.shape, correct)\n", " print(\"[PASS] test_batch_func_shapes\")\n", " return True\n", "\n", "def test_batch_func_next_step(func, args):\n", " x, y = func(*args)\n", " assert (x[:,1:] == y[:,:-1]).all(), \"[FAIL] test_batch_func_next_step: x_{t} must equal y_{t-1} for all t\"\n", " print(\"[PASS] test_batch_func_next_step\")\n", " return True" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[PASS] test_batch_func_types\n", "[PASS] test_batch_func_shapes\n", "[PASS] test_batch_func_next_step\n", "======\n", "[PASS] passed all tests!\n" ] } ], "source": [ "### Batch definition to create training examples ###\n", "\n", "def get_batch(vectorized_songs, seq_length, batch_size):\n", " # the length of the vectorized songs string\n", " n = vectorized_songs.shape[0] - 1\n", " # randomly choose the starting indices for the examples in the training batch\n", " idx = np.random.choice(n-seq_length, batch_size)\n", "\n", " '''TODO: construct a list of input sequences for the training batch'''\n", " input_batch = [vectorized_songs[i:i+seq_length] for i in idx]\n", " '''TODO: construct a list of output sequences for the training batch'''\n", " output_batch = [vectorized_songs[i+1: i+1+seq_length] for i in idx]\n", "\n", " # x_batch, y_batch provide the true inputs and targets for network training\n", " x_batch = np.reshape(input_batch, [batch_size, seq_length])\n", " y_batch = np.reshape(output_batch, [batch_size, seq_length])\n", " return x_batch, y_batch\n", "\n", "\n", "# Perform some simple tests to make sure your batch function is working properly! \n", "test_args = (vectorized_songs, 10, 2)\n", "if not test_batch_func_types(get_batch, test_args) or \\\n", " not test_batch_func_shapes(get_batch, test_args) or \\\n", " not test_batch_func_next_step(get_batch, test_args): \n", " print(\"======\\n[FAIL] could not pass tests\")\n", "else: \n", " print(\"======\\n[PASS] passed all tests!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For each of these vectors, each index is processed at a single time step. So, for the input at time step 0, the model receives the index for the first character in the sequence, and tries to predict the index of the next character. At the next timestep, it does the same thing, but the RNN considers the information from the previous step, i.e., its updated state, in addition to the current input.\n", "\n", "We can make this concrete by taking a look at how this works over the first several characters in our text:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Step 0\n", " input: 82 ('|')\n", " expected output: 62 ('g')\n", "Step 1\n", " input: 62 ('g')\n", " expected output: 60 ('e')\n", "Step 2\n", " input: 60 ('e')\n", " expected output: 56 ('a')\n", "Step 3\n", " input: 56 ('a')\n", " expected output: 62 ('g')\n", "Step 4\n", " input: 62 ('g')\n", " expected output: 1 (' ')\n" ] } ], "source": [ "x_batch, y_batch = get_batch(vectorized_songs, seq_length=5, batch_size=1)\n", "\n", "for i, (input_idx, target_idx) in enumerate(zip(np.squeeze(x_batch), np.squeeze(y_batch))):\n", " print(\"Step {:3d}\".format(i))\n", " print(\" input: {} ({:s})\".format(input_idx, repr(idx2char[input_idx])))\n", " print(\" expected output: {} ({:s})\".format(target_idx, repr(idx2char[target_idx])))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Recurrent Neural Network (RNN) model\n", "\n", "Now we're ready to define and train a RNN model on our ABC music dataset, and then use that trained model to generate a new song. We'll train our RNN using batches of song snippets from our dataset, which we generated in the previous section.\n", "\n", "The model is based off the LSTM architecture, where we use a state vector to maintain information about the temporal relationships between consecutive characters. The final output of the LSTM is then fed into a fully connected [`Dense`](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dense) layer where we'll output a softmax over each character in the vocabulary, and then sample from this distribution to predict the next character. \n", "\n", "As we introduced in the first portion of this lab, we'll be using the Keras API, specifically, [`tf.keras.Sequential`](https://www.tensorflow.org/api_docs/python/tf/keras/models/Sequential), to define the model. Three layers are used to define the model:\n", "\n", "* [`tf.keras.layers.Embedding`](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Embedding): This is the input layer, consisting of a trainable lookup table that maps 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): Our LSTM network, with size `units=rnn_units`. \n", "* [`tf.keras.layers.Dense`](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dense): The output layer, with `vocab_size` outputs.\n", "\n", "\n", "![rnn_unrolled](image/lstm_unrolled-01-01.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Define the RNN model\n", "\n", "Now, we will define a function that we will use to actually build the model." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "def LSTM(rnn_units): \n", " return tf.keras.layers.LSTM(\n", " rnn_units, \n", " return_sequences=True, \n", " recurrent_initializer='glorot_uniform',\n", " recurrent_activation='sigmoid',\n", " stateful=True,\n", " )" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "### Defining the RNN Model ###\n", "\n", "def build_model(vocab_size, embedding_dim, rnn_units, batch_size):\n", " model = tf.keras.Sequential([\n", " # Layer 1: Embedding layer to transform indices into dense vectors \n", " # of a fixed embedding size\n", " tf.keras.layers.Embedding(vocab_size, embedding_dim, batch_input_shape=[batch_size, None]),\n", "\n", " # Layer 2: LSTM with `rnn_units` number of units. \n", " LSTM(rnn_units),\n", "\n", " # Layer 3: Dense (fully-connected) layer that transforms the LSTM output\n", " # into the vocabulary size. \n", " tf.keras.layers.Dense(units=vocab_size)\n", " ])\n", "\n", " return model\n", "\n", "# Build a simple model with default hyperparameters. You will get the \n", "# chance to change these later.\n", "model = build_model(len(vocab), embedding_dim=256, rnn_units=1024, batch_size=32)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Test out the RNN model\n", "\n", "It's always a good idea to run a few simple checks on our model to see that it behaves as expected. \n", "\n", "First, we can use the `Model.summary` function to print out a summary of our model's internal workings. Here we can check the layers in the model, the shape of the output of each of the layers, the batch size, etc." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Model: \"sequential\"\n", "_________________________________________________________________\n", "Layer (type) Output Shape Param # \n", "=================================================================\n", "embedding (Embedding) (32, None, 256) 21248 \n", "_________________________________________________________________\n", "lstm (LSTM) (32, None, 1024) 5246976 \n", "_________________________________________________________________\n", "dense (Dense) (32, None, 83) 85075 \n", "=================================================================\n", "Total params: 5,353,299\n", "Trainable params: 5,353,299\n", "Non-trainable params: 0\n", "_________________________________________________________________\n" ] } ], "source": [ "model.summary()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also quickly check the dimensionality of our output, using a sequence length of 100. Note that the model can be run on inputs of any length." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Input shape: (32, 100) # (batch_size, sequence_length)\n", "Prediction shape: (32, 100, 83) # (batch_size, sequence_length, vocab_size)\n" ] } ], "source": [ "x, y = get_batch(vectorized_songs, seq_length=100, batch_size=32)\n", "pred = model(x)\n", "print(\"Input shape: \", x.shape, \" # (batch_size, sequence_length)\")\n", "print(\"Prediction shape: \", pred.shape, \"# (batch_size, sequence_length, vocab_size)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Predictions from the untrained model\n", "\n", "Let's take a look at what our untrained model is predicting.\n", "\n", "To get actual predictions from the model, we sample from the output distribution, which is defined by a `softmax` over our character vocabulary. This will give us actual character indices. This means we are using a [categorical distribution](https://en.wikipedia.org/wiki/Categorical_distribution) to sample over the example prediction. This gives a prediction of the next character (specifically its index) at each timestep.\n", "\n", "Note here that we sample from this probability distribution, as opposed to simply taking the `argmax`, which can cause the model to get stuck in a loop.\n", "\n", "Let's try this sampling out for the first example in the batch." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([39, 59, 60, 51, 63, 49, 28, 49, 39, 72, 59, 22, 3, 64, 36, 5, 30,\n", " 24, 19, 59, 24, 17, 71, 54, 17, 1, 31, 63, 49, 23, 25, 28, 3, 16,\n", " 53, 7, 74, 1, 43, 8, 76, 20, 39, 69, 41, 53, 2, 71, 66, 13, 41,\n", " 62, 72, 53, 9, 75, 5, 50, 13, 49, 40, 68, 27, 69, 52, 50, 65, 15,\n", " 44, 30, 20, 24, 77, 35, 9, 51, 11, 26, 32, 60, 26, 80, 51, 17, 11,\n", " 35, 19, 10, 42, 34, 66, 72, 59, 63, 70, 31, 42, 14, 34, 61],\n", " dtype=int64)" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sampled_indices = tf.random.categorical(pred[0], num_samples=1)\n", "sampled_indices = tf.squeeze(sampled_indices,axis=-1).numpy()\n", "sampled_indices" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now decode these to see the text predicted by the untrained model:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Input: \n", " '|def def|GFG AFD|!\\ndef def|def ecA|GAB cAF|GFG AFD:|!\\nDdd Dcc|DBB Dcc|Ddd Dcc|GFG AFD|!\\nDdd Dcc|DBB '\n", "\n", "Next Char Predictions: \n", " 'NdeZhXCXNqd:\"iK\\'E=7d=5p^5 FhX<>C\"4])s R,u8NnP]!pk1Pgq]-t\\'Y1XOmBn[Yj3SE8=vJ-Z/AGeAyZ5/J7.QIkqdhoFQ2If'\n" ] } ], "source": [ "print(\"Input: \\n\", repr(\"\".join(idx2char[x[0]])))\n", "print()\n", "print(\"Next Char Predictions: \\n\", repr(\"\".join(idx2char[sampled_indices])))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, the text predicted by the untrained model is pretty nonsensical! How can we do better? We can train the network!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Training the model: loss and training operations\n", "\n", "Now it's time to train the model!\n", "\n", "At this point, we can think of our next character prediction problem as a standard classification problem. Given the previous state of the RNN, as well as the input at a given time step, we want to predict the class of the next character -- that is, to actually predict the next character. \n", "\n", "To train our model on this classification task, we can use a form of the `crossentropy` loss (negative log likelihood loss). Specifically, we will use the [`sparse_categorical_crossentropy`](https://www.tensorflow.org/api_docs/python/tf/keras/losses/sparse_categorical_crossentropy) loss, as it utilizes integer targets for categorical classification tasks. We will want to compute the loss using the true targets -- the `labels` -- and the predicted targets -- the `logits`.\n", "\n", "Let's first compute the loss using our example predictions from the untrained model: " ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Prediction shape: (32, 100, 83) # (batch_size, sequence_length, vocab_size)\n", "scalar_loss: 4.4188814\n" ] } ], "source": [ "### Defining the loss function ###\n", "\n", "'''TODO: define the loss function to compute and return the loss between\n", " the true labels and predictions (logits). Set the argument from_logits=True.'''\n", "def compute_loss(labels, logits):\n", " loss = tf.keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True)\n", " return loss\n", "\n", "'''TODO: compute the loss using the true next characters from the example batch \n", " and the predictions from the untrained model several cells above'''\n", "example_batch_loss = compute_loss(y, pred) \n", "\n", "print(\"Prediction shape: \", pred.shape, \" # (batch_size, sequence_length, vocab_size)\") \n", "print(\"scalar_loss: \", example_batch_loss.numpy().mean())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's start by defining some hyperparameters for training the model. To start, we have provided some reasonable values for some of the parameters. It is up to you to use what we've learned in class to help optimize the parameter selection here!" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "### Hyperparameter setting and optimization ###\n", "\n", "# Optimization parameters:\n", "num_training_iterations = 2000 # Increase this to train longer\n", "batch_size = 4 # Experiment between 1 and 64\n", "seq_length = 100 # Experiment between 50 and 500\n", "learning_rate = 5e-3 # Experiment between 1e-5 and 1e-1\n", "\n", "# Model parameters: \n", "vocab_size = len(vocab)\n", "embedding_dim = 256 \n", "rnn_units = 1024 # Experiment between 1 and 2048\n", "\n", "# Checkpoint location: \n", "checkpoint_dir = './training_checkpoints'\n", "checkpoint_prefix = os.path.join(checkpoint_dir, \"my_ckpt\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we are ready to define our training operation -- the optimizer and duration of training -- and use this function to train the model. You will experiment with the choice of optimizer and the duration for which you train your models, and see how these changes affect the network's output. Some optimizers you may like to try are [`Adam`](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam?version=stable) and [`Adagrad`](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adagrad?version=stable).\n", "\n", "First, we will instantiate a new model and an optimizer. Then, we will use the [`tf.GradientTape`](https://www.tensorflow.org/api_docs/python/tf/GradientTape) method to perform the backpropagation operations. \n", "\n", "We will also generate a print-out of the model's progress through training, which will help us easily visualize whether or not we are minimizing the loss." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "class PeriodicPlotter:\n", " def __init__(self, sec, xlabel='', ylabel='', scale=None):\n", "\n", " self.xlabel = xlabel\n", " self.ylabel = ylabel\n", " self.sec = sec\n", " self.scale = scale\n", "\n", " self.tic = time.time()\n", "\n", " def plot(self, data):\n", " if time.time() - self.tic > self.sec:\n", " plt.cla()\n", "\n", " if self.scale is None:\n", " plt.plot(data)\n", " elif self.scale == 'semilogx':\n", " plt.semilogx(data)\n", " elif self.scale == 'semilogy':\n", " plt.semilogy(data)\n", " elif self.scale == 'loglog':\n", " plt.loglog(data)\n", " else:\n", " raise ValueError(\"unrecognized parameter scale {}\".format(self.scale))\n", "\n", " plt.xlabel(self.xlabel); plt.ylabel(self.ylabel)\n", " ipythondisplay.clear_output(wait=True)\n", " ipythondisplay.display(plt.gcf())\n", "\n", " self.tic = time.time()" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████████████████████████████████████████████████████████████████████████| 2000/2000 [00:46<00:00, 42.67it/s]\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXgAAAEGCAYAAABvtY4XAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjMsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+AADFEAAAgAElEQVR4nO3dd3wUdfoH8M+TAiF0ktBL6L2HDlJEiiDcWbCe9eSnchb0VOygnod6enY9UFBRLIi9A1KkE5TeS4BQAwECBFK/vz9mZjNbs5vs7Cazn/frlReb2cnMs5PwzHe+VZRSICIi+4kKdwBERGQNJngiIptigicisikmeCIim2KCJyKyqZhwB2CWmJiokpOTwx0GEVG5sXbt2uNKqSRP75WpBJ+cnIzU1NRwh0FEVG6IyD5v71laRSMiNUTkCxHZJiJbRaSPlecjIqIiVpfgXwXws1LqShGpACDe4vMREZHOsgQvItUAXATgZgBQSuUCyLXqfERE5MzKKppmADIAzBSRP0XkXRGp7LqTiIwXkVQRSc3IyLAwHCKiyGJlgo8B0A3A20qprgDOAZjkupNSappSKkUplZKU5LEhmIiISsDKBJ8OIF0ptUr//gtoCZ+IiELAsgSvlDoC4ICItNY3XQxgi1XnIyIiZ1b3orkbwMd6D5o9AG6x4iQ7j55B5rlc9GqWYMXhiYjKJUsTvFJqHYAUK88BAJf8dwkAIG3qKKtPRURUbnAuGiIim2KCJyKyKSZ4IiKbYoInIrIpJngiIptigicisikmeCIim2KCJyKyKSZ4IiKbYoInIrIpJngiIptigicisikmeCIim2KCJyKyKSZ4IiKbYoInIrIpJngiIptigicisikmeCIim2KCJyKyKSZ4IiKbYoInIrIpJngiIptigicisikmeCIim2KCJyKyKSZ4IiKbYoInIrIpJngiIpuKsfLgIpIG4AyAAgD5SqkUK89HRERFLE3wusFKqeMhOA8REZmwioaIyKasTvAKwK8islZExlt8LiIiMrG6iqafUuqQiNQGME9Etimllph30BP/eABo3LixxeEQEUUOS0vwSqlD+r/HAHwFoKeHfaYppVKUUilJSUlWhkNEFFEsS/AiUllEqhqvAQwDsMmq8xERkTMrq2jqAPhKRIzzzFZK/Wzh+YiIyMSyBK+U2gOgs1XHJyIi39hNkojIppjgiYhsigmeiMimmOCJiGyKCZ6IyKaY4ImIbIoJnojIppjgiYhsigmeiMimbJXglVLhDoGIqMywVYIvKGSCJyIy2CrB5zPBExE52CrB5xUUhjsEIqIyw1YJPr+AJXgiIoOtEnxeIUvwREQGeyV4luCJiBxsleDnbT4S7hCIiMoMWyX4C/msoiEiMtgqwTeqGR/uEIiIygxbJfh8NrISETnYKsEXcqoCIiIHWyV49oMnIipiqwTPuWiIiIrYK8GzioaIyMFeCZ4leCIiB1sleNbBExEVsUWCX/DAQAAswRMRmdkiwTepFQ8R4GxOfrhDISIqM2yR4GOio1ArvgIyzuaEOxQiojLDFgkeAOJio5GTx5GsREQGyxO8iESLyJ8i8r2V54mNFk5VQERkEooS/L0Atlp9kpjoKPaiISIysTTBi0hDAKMAvGvleQAgJkq4JisRkYnVJfhXADwEwPLMGxsdhXx2kyQicrAswYvIaADHlFJri9lvvIikikhqRkZGic8XE80SPBGRmZUl+H4AxohIGoBPAQwRkY9cd1JKTVNKpSilUpKSkkp8stgo1sETEZlZluCVUo8opRoqpZIBXAPgN6XUDVadL4a9aIiInNimH3x0lCCPJXgiIoeQJHil1CKl1GgrzxEbHYX0k9n4dfMRK09DRFRu2KYEHxMlOH42F+Nn+WzTJSKKGLZJ8LHRtvkoRERBYZusGBMtjteF7A9PRGSjBB9V9FEKuXQfEZF9EnysqQTPtVmJiGyU4HNNo1iZ34mIbJTgtx8543jNOWmIiGyU4ONiox2vZy7dG8ZIiIjKBtsk+EqmBL/n+LkwRkJEVDbYJsHHxbIXDRGRmY0SfFEJnvmdiMhGCb5CNEvwRERmtknw0VFF/eCZ34mI/EzwItJcRCrqrweJyD0iUsPa0AJjTvAswRMR+V+CnwugQERaAHgPQFMAsy2LqgSY4ImInPmb4AuVUvkA/grgFaXURAD1rAsrcOYEX696pTBGQkRUNvib4PNE5FoANwH4Xt8Wa01IJRMlRQm+SUJ8GCMhIiob/E3wtwDoA+BfSqm9ItIUgNsC2uE0YXAL9G2eAAAo4FQFRET+JXil1Bal1D1KqU9EpCaAqkqpqRbHFpCkqhXx3k09AACvzt8Z5miIiMLP3140i0SkmojUArAewEwRedna0AJn1MOfycnH3LXpYY6GiCi8/K2iqa6UygJwOYCZSqnuAIZaF1bJxJgaWh+Ysz6MkRARhZ+/CT5GROoBGIeiRtYyJ8qU4ImIIp2/Cf5pAL8A2K2UWiMizQCwopuIqAyL8WcnpdQcAHNM3+8BcIVVQRERUen528jaUES+EpFjInJUROaKSEOrgyMiopLzt4pmJoBvAdQH0ADAd/q2Mu1Udm64QyAiCht/E3ySUmqmUipf/3ofQJKFcQXFlkNZ4Q6BiChs/E3wx0XkBhGJ1r9uAHDCysBK6ua+yY7X7yzZE75AiIjCzN8Efyu0LpJHABwGcCW06QvKnMlj2jteL9mRgW/WHQxjNERE4ePvVAX7lVJjlFJJSqnaSqm/QBv0VOb9tPFIuEMgIgqL0qzodL+vN0UkTkRWi8h6EdksIlNKca4SE459IqIIVZoEX1zqzAEwRCnVGUAXACNEpHcpzue3+tXjHK+59gcRRarSJHifqVNpzurfxupfIUm3X03o53h9Ljc/FKckIipzfI5kFZEz8JyUBUCxyyaJSDSAtQBaAHhTKbXKwz7jAYwHgMaNG/sRcvHqVCsqwQvraIgoQvkswSulqiqlqnn4qqqUKnaaA6VUgVKqC4CGAHqKSAcP+0xTSqUopVKSkoLftb6Qi38QUYQqTRWN35RSpwAsAjAiFOcz4wLcRBSpLEvwIpIkIjX015WgzR+/zarzecPl+4goUvk1m2QJ1QPwgV4PHwXgc6VUyOeSX7U3E2cu5KFqXJlaI5yIyHKWleCVUhuUUl2VUp2UUh2UUk9bdS5P+rVIcLyezikLiCgChaQOPhyiTL1nWElDRJHItgk+2rR83/ncgjBGQkQUHrZN8OYS/LtL9yK/oDCM0RARhV5EJHgAyGdvGiKKMLZN8NEun4z94Yko0tg2wbMET0SRzrYJ/rb+TZ2+55QFRBRpbJvgOzWs4fQ9R7QSUaSxbYJ3nUSSCZ6IIo1tE3xMlOCSdnUc30/9eRtumrEap8/nQbHBlYgigG0TvIhg+o0pju+//OMgFu/IQOcpv2LGsrTwBUZEFCK2TfCGJ0a3c9v262YuxE1E9mf7BJ9QuYLbNlbQEFEksH2CN89JY2AdPBFFAtsn+Jx89zlomN+JKBLYPsF7WnKb+Z2IIoHtE3zbetXctuVxZkkiigC2T/Dt6ldDbLRzOX5D+ukwRUNEFDq2T/AAULtqnNu2XA9180REdhIRCT7zXK7bNq7yRER2FxEJ3tNc8Nl5+WGIhIgodCIiwXvqKpnNEjwR2VxEJHhPWEVDRHYXEQn+p3sHuG07l1NURaOUwou/bMPWw1mhDIuIyFIRkeA99YXPzisqwZ/PK8CbC3dj3DsrQhkWEZGlIiLBA0D7+s5J/nxuAZRSOJZ1AaKPd83hACgispGISfCzbuvl9P3qvZm4//P16PncAqw7cAoA120lInuJCXcAoVLLZdrg95enOV5vP6LVvecXKpzLyUflihFzWYjIxiKmBO/L5O+2OF73fm5BGCMhIgoeJngXZ3Ly0eqxn7DpIOerIaLyzbIELyKNRGShiGwVkc0icq9V5wq23IJCjH59acjOl19QiKe+2YQjpy+E7JxEZH9WluDzATyglGoLoDeACSLivkBqGZYfol41y3efwAcr9mHSlxtCcj4iigyWJXil1GGl1B/66zMAtgJoYNX5/DGmc/2A9n970W63bYWFCjfPXI3lu49j3YFTyM4NfE6blXtO4FR20QRoxlw57MRDRMEUkjp4EUkG0BXAKg/vjReRVBFJzcjIsDSO167tGtD+B0+dd9t26nweFm3PwHXTV+Evby7DxM/WBXTMC3kFuGbaStzy/hrHNuZ1IrKC5QleRKoAmAvgPqWU21wASqlpSqkUpVRKUlKS1eEEJLegEEoprN6biZd/3Q7AfQnAjQEuHmKU1rcdPuP2nqflBYmISsrSDt8iEgstuX+slPrSynNZ4cs/DmLfiWys3XcSAPDab7uw5MHBTvuIMC0TUdlkZS8aAfAegK1KqZetOk+gZt7SA9f3auz3/kZyN0z+bnOpzu9hanrW0RCRJaysoukH4G8AhojIOv3rUgvP55fBrWvj6h6NSvzzv2075vR9lIcrqJTCt+sPeVwW0Fcu58MAEQWTZVU0SqmlKKPVylFBzKTi4SPO23IU93zyJ+4e0gIPDGvt9J6n1aXyOMkZEVkgIkeyFgSxP2JufiFaPvYjvt9wyLHNWAP2WFaO2/7KQy4fP2stgDJ6NySicisiE3x+YfBKzBlnc5BXoDD1p22ObQV6Kd1j9Q0r3IkoRCIywefma0m2WWLlUh8rNlord6efPI8Xft6GwkLlmHbYtSroXE4+zusLjZzP45KBRGStiJwX1yjBB2Na4NioKFyAdry3Fu3GJ6v3o3ezBABAdJRzgm//1C9IcJm22MzfLpcnzuYg/eR5dG5Uo9h9N6SfQof61REVxQogokgTkSX4JrW0kvvoTvVKfawzOc5TFZzMzsNPm44A0ErwBzKzsXhHBo5laROJnTiX63aMQP31reUY++Yyp21KKby/bK/T6Nu1+05izBvL8NaiXaU+JxGVPxGZ4BsnxGPj5GG4qW+y23ut61QN2nnWpGViwAsLcdOM1ejpYZ75p03z0AO+G1kzz+UiedIPWLrzOPZnZru9v/lQFiZ/twWTv9X66Z+5kIflu44DAFbtzSz5hyCicisiq2gAoGpcrKN7ogjwwS090bd5AmKio5A86YegnGPzIbeZGZzMWLbXad75E+dyUVCo3Kp2AK2qBQBueM9tOh8AQNaFPO3f89q/101fhY36sX/feTzw4Imo3IvIErwhJkrQsUF1vHFtN1zUKgkx0aG/HKvTikrX6w6cQp9/L8Dp83k4oydsQ7F9b/QdjGr8jSVcsCTzXC6Ux+G2JbftSBYusFGZypFHv9qIR7/aGO4wSi2iE7yI4Lu7+2NUEOrig+XYmRx0nvIrevxrvvMbHnLu9CV7sDvjrNPbngZeAdrc9kezihYU2XzoNH7YcBinsnNx0QsLsfVwFg6dOo9uz8zDO4v3BOOjANBuGCNe+R1tnvg56DcOIqvMXrUfs1ftD3cYpRbRCb4su5CnVR/tzjiLE2dzPPaf/9ePW3HxS4sBFA3e8tYR5+V5O9DruQU4dkZL8qNeW4oJs//A/Z+vx/7MbLw6f6fjBvDzpsNB+Qzbj5zBfabplI+dcR/4FU6r92aWaD7/cNt6OMvjVNZErpjgPejUsHq4QwAApJ/MxsUvLcbAFxfh1vdTve53IDPbMbjqbE4+Tpx1T6Qr9pxw7NvysR8d2425dbLzChCrV1HlFhTdTF5fsBPfrDtYovhv+2ANluwomuPfdQTxawt24pPV4SklHc26gHH/W4F/zlkf8M9+nnoAh0+HL8GOfPV39Jv6W9jObzWlFF6ZvyOs19gumOA9mHVrL8frW/s1DVsc/Z9fCEBL2r6cy81HgZ6UN6SfRvdn57vtU71SLADg9Pk85BW4Pw3k5hc4euAUmhLxS/N24N5PA1vUxOD6NOGa4F+etwOPfGl9Peeslfuw1KWh+Zx+Tbd6mJfflxNnc/DQFxtw+4feb7hmD3+xIWiN9pFi25EzeGX+Tvxj9p/hDqXcY4L3oHp8rOO1vxOBpTSpaVU4xcrLV8VOv7Bou1aS/mXTUY/vr9yTiVR9amSjF89PG52ragoLFX7edATdn5mHWSv34UJeAWYs3YsLeQVYk5aJdQdO4ceNh7Fo+zHk5hfiQKZzCcyogk8/mY05qQc8xjFz2V5M+PgPt+0HMrPx3fpDTtvW7st0jC/w5YmvN3ntfRSobUe0G0LWeeeb7t8/SMWIV5YAABZuP4b0k1pX1s+8fE5fCgqVWyN7ebB6byYWbT9W/I4+ZOfmI0Ovyjufy4b50orYbpL+cp3yt261OBzxkFT6Nk9A+snzHt+z2mVvLPV7X38Szha9jte4KQBAp8m/IOtCUVJ74utNeOLrTQCAp7/f4naM1Y9d7LbNqEa6+n8rvdYhT9HHBrzpsv2yN5biVHYeLutcHwWFCudy83HF2yuQULkC1j5xSbGfCQD2Hj+Hz9YcwMMjimb49Nbwm19QiPxChbjYaKft17+r3Sgq6ds3HzqNRrXiMX9r0Y3zlpnacow7nh3p2JZXUOioAivOlO8248MV+7Dj2ZGoEBNYGWzF7hP4dcsRXNuzMapUjEH9GpUC+vnSGPe/FQCAtKmjSnyM0a8txZ7j5wCUz+mzCwoVvt9wCJd1ql8mRo+zBF+M/xvYzOn7i1oletyvQClUjLXP5XSt4zUnd398utr9RlJQqDBvy1G35D5FX0TF19PSqWytRHvmQh6aP/ojOk3+FYA2dsC8gLkvt32wBu8s3o2mj/yIoS8vdnv/181HkDzpBxzIzMbtH6aizRM/I3nSD1ijd2XddqRoXIORfEa9ttQRiyvz2gGzVuzzK0ZAW0kMAL7+M/C2j2unr8TMZWkY9t8l6BvCevpgTXltJHfAPcErpTB9yR6cPl92n24+WrkP9366Dp+uCfzJzQr2yUgWaZZUxen7nk0TPO5XqDzP9V6elWbmy5fn7XDbNvTlxR7rrmcuS8OqPSecGlyNAWAv/LwN101f6djeaYp7Mu3y9Dy/YsrJK0pCRnOA+RN+rTcmr08/hYWmp5dfNh3Bn/tPYsQrv5t+XgXU7fPp77d4XL9308HTbjcoI689NHeD2/5r0pxHJW8/csatfcEsJz+41RxXvr0cA174zTGwzjB/i+eqv9Jw7fK7bNcJ/OvHrXjqm02lOu6FvALLbhLH9Q4Ox00dHY6fzXG7XqHCBB+gGpViPW4vVApBnIW4TMj30BhrlaunrcRq05QKo19fisU7MvDWot1YvvuEY7u3nHogMxvD/rvY0Q3UE08Jed+JbBw8dR6zV+13LM/oWi0nArenjkIFLN1V3AhhhaqmCe1cu2Tm5hdi9OtLHesBGMzzG+3JOIu04+dwNicfM5buxUcrnZ8Ehr+yxGf7glGNFiyp+07iQOZ5t6eWfD/XWBj9+u8eE/Rna/bjnk+cG1VdS/DGDKzeOh0s3HbMr543V09bic4eCgrBYIS8wXQzT3l2fth6PbEO3osruzf0OGSoYS3PdZrKhiX4HA9LDlrppEtJ9qYZq/3+2ak/bcOOo2fxn1+24+mxHRx15+aE6K2a6dEvN2KxqTvn/Z87d53cn5mNxgnOU0vvOnYWf3vPPb6Ve4puRqv2Zjola9fZQo1xB1sPZ2Hh9mOoFV/BbYbQIfo4hy6NamDdgVMe4/fl89R0XNa5Pga0TMKJszmoVbkCvl1/CIu2Z2B4+zoY0cHzIL/Rr/+OVrWr4uWru3g99rGsC+j53AI8MbodEqt4nyXVbNPBLGw6mIUpYzs4bX94bvG9qYz/X67X8eS5XOw8dha3vL8GiVUq4O4hLVGvehyGta8LQOsskJ1bgL92bYBFO45hvX4dL3phIX66dwCiowR5BYWoGheLa6atQP8WnqthzVbsPoHmtSujdtU45zf02MxtMgBwJsAqzmBhgvfiP1d1dtu2+rGLUbtqHJY+PBhKaQN3rnh7OQBgSJvaJeov/sKVnfDQF+6P4mVB8SXU4DKX1AP1g97j5/PUdHyemo60qaOw8+gZPG4qwXor+RXXDfWXzUfxy2b/qiCumVZUnTRzWZrTe966jQqKGmZfuLKTx+OWJLkbPl65H82SqqDf1N+QnBCPtBNaD5+v/jzotUHUSMS+Erwxgd7MZXvx4PDWXvcrKdcCVtE6C9r1qBkfiyYJldH1maIquuNnc/GU3t13zh190LVRDdyp98p6YI77jXvTwdOY8t0WbDmchbSpo7ByTyZW7il6ktx6OAtfrE3H46PaOt1Yrp2+Eo1qVcLvDw3xGbM3Sik89e1mDG1bBwNaJvo9VXigmOD9MG/iRTifV+C4WzesGQ8Ajh4z3ZvURO9mCfCnnaljg+qoER+LapVi8cOGw47eGGVRqBuzgvkAtHTncVSJ8+/P21i0JVTeWbwbzZOqoFmS9lRgrt0I5GZvflr46s90LNvl+QaZV1CIw3oVk5HcA7Hp4GlcN30lFjwwyOP7It6fXk9n5zl1OzbMWLoX1/RshPgKvn9HOfkFWLbrOLo0qolXF+wEoHXj/Ys+XbavHjtXvbPC57G14xdiy2HvkwKOfFVrd7lrUHMkVKkIoOjG7NoNeP+JbEeMxck6n48PV+zDhyv24eVxnXF5t4Z+/VygmOD90NLLFMJGHbXRb9xbo9tP9w5Ai9pVUGDqdnfnR2udfpaC62jWBVSqEO/XvuYSm5UEwJsLd+HFX7YD0LrcAsU/QXhjflqY+Jn3Ebm5BYVOvVO86TT5FwxvXxfX9WrstH3akj3IupDvPj+SyaFT7m0f87Ycxe0fpmLOHX3QI7mW03tPf79F+xrbHjf2SfZ4zPXpp3HHrLVYuD0DCZUrONZSCGZp19zFN8PHVBpnc/IdCd7ccL0h/RQ6NdSq1f48cNKvc17IK8CJc0Xn+nnTEcsSPBtZS8EotUTrf3CupZiZt/TA81d0RNt61RAbHeXUp7pbY21gVONa8Zh1W0/89sBAx3vzJl5kdegON/ZpErJzhVJ2XgGueLv4ElwoPf71Jry3dK/j+1CNmfh953GvTwavzt+JXce0CeuyLuRjztp0/PWt5U77+NO2ZNy0zFL1Hj+pad4T35PfbPZ5XKM3k3mhnOggJnjjswPAMx7GcxgGvrjIMSOquTfW56ZxJf62wf3tvVWOthWg5Dd4f7AEXwrGo5pRCncdij+4dW2vP/v3AU0xtF0dNPWwLqy3JwYAuKhVktP8LqX19NgO+LCYPtpvXtcNE2a7jy4ty4LdeyQYjFGwZcl/5+/Af+fvwN5/X+rx/WunrURmMauQuVZVzFy2F1sOZWHO2nQARSXe5o/+6PazAByjfv1l1UPvqWKqJD9YnobbBzRz6/K4UB/v4NqL7unvtmDP8bNwtcblhmdl5wwm+FJI1B/ZOuqTkwXyexIRt+Q+8+YeSPaxEPiTo9vh1v5N/Zrb5OVxnTFtyR6fSeWWfsnFHqdTw+oY1ake1qc3w7QlwZtGmMqWpo94Tr4r9gTe8D3FZaWyV+bvxL0Xt3QrABk+DnBa3l0ZRUnzgIfVzUrqdDED5vILtYbRWaaeWR+t3I+PVnqOf8ayvW7blu8ObccFVtGUQrv61fD1hH544JJWAIruxIEOLzcMblPbLek3MA01v7W/94nPKsZEoXezonrOv3Rp4HgSGNw6yW3/cSkNce/FLX3Gk9KkJj4d3xsAynRjMJV9z/241et7by/aHdCxNh0sahQd8MLCEsfkqkmC98IVAHy8cp9Tci+J66a7j1mwsnc1E3wpdWlUw7ESlNFVbM2jQ7HmsaGlOu6AlomYOLQVnv1LB6/7mHsQbH92JCYMbgEA6N8iEVFRgoTKWt9kT08FL1zZGTXiKzjifuO6rph7Z1/T8Ubgizv7Ono5mP8GB7Qsvp+wldrUrerX0weVHdN/dy/NljU1PfT2MTt0unRtJs96qeO/kFdg2WI4rKIJopv7NcXNQZpeeNZtvYrfqRgPj2iD5kmVcUPvJo4+2Y9d2hZt61Vz2s+4MZhVjHEusd/Upwnmrk3HW9d3w/vL00odm7dJ2/zx4a09kZ1b4NbP3JvbBzR1JJixXepj/YFTJeouSPb2QQDzBZXEu0s93+TWp5/Gy/N24IFhwR9LwBK8jbTTE/fNfZMBAJUqRONvfZKdupXdflEz9C9BCTyhSkUsmzQEnRvVwJSx7fHk6HaO9zwtkNKraS23bWYL/zkIm6cMxwh9tGGgkhMr46PbeqGRl5HFZuZZHCtER6FP8+I//wtXdnKq8iKyklWTkzHB20hClYpImzoKQ9vVsfQ81eJindoDJo1sg6SqFbH+yWGObcY9Jb5C0ZNA2tRR+Oquvph9ey9UqhCNyhVj0DjBva/6kgcHY9E/B3k9f0296ql/y0SPw8pdE7P54fdalz7e3vRtnoBPx/dB3+aeJ5fzZe6dfQL+GYps2RZ1lWSCLwd+f2gwfrp3QKmOcVV3/wZSvHBFJ9wxsLlf+86beBHm3tkXfZsnYs1jQ51GLEbpGX76jSkY1bEehrbVbjpdG9dEX1MJ2nWgV9rUUWicEI/kxMrY++9LseTBwQCAJgnx2P7sCKRNHeVUIp88pj1+vGcAGtcqulF8cntvp2NendII0VGCL+7og26Na2J4++JvgEaVqDFOwHyjMnx5V1+PQ/S7N6lleh2+hWB84fi6suWcRYubWJbgRWSGiBwTkbLXIbmcaVQr3qne/NZ+TTGqkzZJ1NcT+uGdG7oXe4wXr+rs10IM43o0wqSRbfyKq2Wdql4TWJRp8Neb13fDuzeleNxvwuAWuH2A53YLEUHjhHhsnjIc8yYOdGsXALS2gnb1q+GbCf3QI7kmZt3W022kY3JiZex+7lKk6KMpB7Wu7dTve/kk5/lEgKKnBKMn0uQx7R09mga1TsLEoa3QrXFN3KRXhwHazWD6jdrnnP13rQ3F1zQIEwZrN1JfPZTeuaFbqRbQ8Gb5JPcFWUKFN5fQsbIE/z6AERYeP2I9eVk7vHldNwBaL54RHUpWj22VKCmqoiluFtkqFWPw2Kh2PvepXDGm2K6nNStXwJw7+mJAS61L6Jd3aT2CqnuZ3tl8E6hfoxK+/Uc/vGSaYK6KPs1v86Qq2DxlOMalNEKUHsLkyzhB0lkAAA49SURBVNrj3qEtnfYDtEFjl+jVY63rajcGb8PwAeDB4W2w7ZkReOv6bm7vGU8MxmyPyzzchLzx52mtbvU4r+/1axF4tVQgXGeSNPv5vtI9qQZqzh19HG1WBqu7BL93Uwrm3BGaajzLErxSagmA0EzyQWXGOzd0x28PDHKU4P3t/nVjnyZu/9FKo1vjmph8WTt8dVff4ncG0KlhDVzRvSG+vKsvlj482Om9ynoS9zYlxc19k92eooz2kEs7ep6O10gicbHRblMQ3jOkBVIfH4rNU4Y7tjWoUQkLHhjo1JXVm44Nq3ss9Ze2umja37TPOKZz/VIdZ6ReILm0o3PBZGCrJLSpWw3Nk7z3RzcGFwZLj+RamDymvdO2QKtDn7+iI/7uY4yKq8oVY9zm5rFK2OvgRWS8iKSKSGpGRvCG4FN4jOhQF8mJlR2P4f527316bAe3/2ildXO/pm4rchWnW+OajtlCXRlrbLom+Mlj2vt8ipp9ey+nUvpV3Rti1m09Hd8bi4KMaF8XEwY3x90Xt0R8hRjHjcXQPKkKurrMF2/4/u7+jnYOb9fc9eZw1yCtimjiUG2g3oTBzZ3WkQWAb//RD8snDUHa1FEY1r4u0qaOwmvXdnXa5/KuDbzG5Ort67shUb/5vXV9d6cb0cybewDwPQFf6uOex5f428bkTY/koptffMXASvAjO9bD46PboYKfa+4aN9q4ECzxGfYEr5SappRKUUqlJCW5j7ik8umpy9pjaNs66FOCXiihUrViYMNAjBJ8oMuP9m2eiEs71sP7t/TA93f3x4tXdXa0BwDaf/gXr+yEl8Z1xoPD2/hcnFtEK0F/5DJOonlSFbSuq93MjKeDT8f3xvNXdAQAjO7k/iTx0Ig2SJs6CjUra9VYtavGuVWFdWpYw+PC3cZMmMsmDcGUsdqN+RnToLz/XNUZ7es7j7f4ekI/jPTyRFOvepzjBmo8/Q1tW9vvAYP+zMr61GXtnBrjzcZfVNSxwDwp4NcT+uHVa7q4jR0BgFZ1tOtdLU67fsN8NN6be3YZv995Ewe6/R6DjQOdyBLJiZW9NqyWFYseHBTQnPeTx7THE19vQhMPXTv9McjL5HMigqtSGvl1DBFxK0EDWmPu3UNaolpcLC7vppWoezdLQO9mCbi8W0OfMzBe36sJKsVGO6asNZ4AzE8ZroyEWlioUDUu1lES/3XzEfy+8zhio8WtsbuLl6ePzVOGOyVoI8HfN7QVqupz+vdploCZt2gl/OWThiBKBNm5+U6zMhrqVovD/Ze0ws5jZ5B2Ihvz9PVib+nXFPO3HsV+ff4acwN4ZVMvqXhTgu/SqAa6NKqBsV0aoPdzCxyD867p0QiPjGzrtArZS+M645FL2zqW54uvEI1svXfM//6W4rZMYKNa8Wjk5YYTLEzwFLESqlR0zPHtj34tEvGbj/75oTb3zj7IOp+PFrWrICY6CjHRwP956OJqfiL44o4+qFPNuYE1Osr5BnMqW7vpeWugBoDXru2KNxfuQj2XxlpfP+ONa1XU46Pb4qEvNqB5UhXExUa7tSd4eqIw30tiogXjehR9HvPkfK3qVMWyXSdwx8DmuLZn0T4dTIP1Yrw8QX1/T3+MeX0pDp2+gMFtaqN6fKxT1+CKMdFoUKMS2tevhlqVK2DWbb2QdSEPFWOiPPYACwXLEryIfAJgEIBEEUkH8JRS6j2rzkcUacz97f2V4kfj3j0Xt8DEz9ajZW3v01Z3b1ITM/Q6czOj+t8ohadNHYXkST9gYCv/q1/7Nk/E0of97zUEAB0b1MAn0EaDemqDMKqUHhnZFpe0rYO+LgPkjGoWcw+aZi6NvYlVKmLK2A64/cNUdGjgPnrb8MM9RY20xnEB4LcHBjr1ujIMap1UbG+zkhKrJrkpiZSUFJWamhruMIiohO76eC1+3HgEb1zXFaM7ab1tcvILEBMVZcnqZUbpfOe/RmLTwdP461vLcc+QFrjfNK/LsawLqFQhGlXjfD9dnM7OQ3S0oErFGKSfzEa1SrFOCbqsEpG1SimP9aGsoiGioHlydHtUjIl29OgB3Ceus0JMlKBr45rY9swIt94stat57/NvZq5u8daTqrxhgieioKlbPQ7/vbpLyM9rNOjGcd0CJ2HvJklERNZgCZ6Iyq1v/9EP69NPhzuMMosJnojKrU4Na6BTQ8/964lVNEREtsUET0RkU0zwREQ2xQRPRGRTTPBERDbFBE9EZFNM8ERENsUET0RkU2VqNkkRyQCwr4Q/ngjgeBDDCRbGFRjGFZiyGhdQdmOzW1xNlFIe52MuUwm+NEQk1duUmeHEuALDuAJTVuMCym5skRQXq2iIiGyKCZ6IyKbslOCnhTsALxhXYBhXYMpqXEDZjS1i4rJNHTwRETmzUwmeiIhMmOCJiGyq3Cd4ERkhIttFZJeITArxuRuJyEIR2Soim0XkXn37ZBE5KCLr9K9LTT/ziB7rdhEZbmFsaSKyUT9/qr6tlojME5Gd+r819e0iIq/pcW0QkW4WxtXadF3WiUiWiNwXjmsmIjNE5JiIbDJtC/gaichN+v47ReQmi+J6UUS26ef+SkRq6NuTReS86bq9Y/qZ7vrfwC49drEgroB/b8H+P+slrs9MMaWJyDp9eyivl7f8ELq/MaVUuf0CEA1gN4BmACoAWA+gXQjPXw9AN/11VQA7ALQDMBnAPz3s306PsSKApnrs0RbFlgYg0WXbCwAm6a8nAXhef30pgJ8ACIDeAFaF8Pd3BECTcFwzABcB6AZgU0mvEYBaAPbo/9bUX9e0IK5hAGL018+b4ko27+dynNUA+ugx/wRgpAVxBfR7s+L/rKe4XN5/CcCTYbhe3vJDyP7GynsJvieAXUqpPUqpXACfAhgbqpMrpQ4rpf7QX58BsBVAAx8/MhbAp0qpHKXUXgC7oH2GUBkL4AP99QcA/mLa/qHSrARQQ0TqhSCeiwHsVkr5Gr1s2TVTSi0BkOnhfIFco+EA5imlMpVSJwHMAzAi2HEppX5VSuXr364E0NDXMfTYqimlVigtS3xo+ixBi8sHb7+3oP+f9RWXXgofB+ATX8ew6Hp5yw8h+xsr7wm+AYADpu/T4TvBWkZEkgF0BbBK3/QP/TFrhvEIhtDGqwD8KiJrRWS8vq2OUuowoP3xAagdhrjMroHzf7xwXzMg8GsUjmt3K7SSnqGpiPwpIotFZIC+rYEeSyjiCuT3FurrNQDAUaXUTtO2kF8vl/wQsr+x8p7gPdWRhbzfp4hUATAXwH1KqSwAbwNoDqALgMPQHhGB0MbbTynVDcBIABNE5CIf+4b8OopIBQBjAMzRN5WFa+aLtzhCGp+IPAYgH8DH+qbDABorpboCuB/AbBGpFsK4Av29hfr3eS2cCxEhv14e8oPXXb3EUOLYynuCTwfQyPR9QwCHQhmAiMRC++V9rJT6EgCUUkeVUgVKqUIA01FUpRCyeJVSh/R/jwH4So/hqFH1ov97LNRxmYwE8IdS6qgeZ9ivmS7QaxSy+PTGtdEArterEaBXgZzQX6+FVr/dSo/LXI1jSVwl+L2F8nrFALgcwGemeEN6vTzlB4Twb6y8J/g1AFqKSFO9RHgNgG9DdXK9fu89AFuVUi+btpvrr/8KwGjd/xbANSJSUUSaAmgJrWEn2HFVFpGqxmtoDXSb9PMbLfA3AfjGFNeNeit+bwCnjUdICzmVrMJ9zUwCvUa/ABgmIjX16olh+ragEpERAB4GMEYplW3aniQi0frrZtCuzx49tjMi0lv/O73R9FmCGVegv7dQ/p8dCmCbUspR9RLK6+UtPyCUf2OlaSUuC1/QWp53QLsTPxbic/eH9qi0AcA6/etSALMAbNS3fwugnulnHtNj3Y5SttL7iKsZtN4J6wFsNq4LgAQACwDs1P+tpW8XAG/qcW0EkGLxdYsHcAJAddO2kF8zaDeYwwDyoJWSbivJNYJWJ75L/7rForh2QauHNf7O3tH3vUL/Ha8H8AeAy0zHSYGWcHcDeAP6yPUgxxXw7y3Y/2c9xaVvfx/AHS77hvJ6ecsPIfsb41QFREQ2Vd6raIiIyAsmeCIim2KCJyKyKSZ4IiKbYoInIrIpJniyDRE5q/+bLCLXBfnYj7p8vzyYxyeyAhM82VEygIASvDH4xQenBK+U6htgTEQhxwRPdjQVwADR5vueKCLRos2nvkafFOv/AEBEBok2X/dsaANLICJf6xO0bTYmaRORqQAq6cf7WN9mPC2IfuxNos0lfrXp2ItE5AvR5nH/WB/ZCBGZKiJb9Fj+E/KrQxEjJtwBEFlgErQ5ykcDgJ6oTyuleohIRQDLRORXfd+eADoobUpbALhVKZUpIpUArBGRuUqpSSLyD6VUFw/nuhzaRFudASTqP7NEf68rgPbQ5g1ZBqCfiGyBNqS/jVJKib5wB5EVWIKnSDAM2hwf66BN15oAbQ4SAFhtSu4AcI+IrIc253oj037e9AfwidIm3DoKYDGAHqZjpyttIq510KqOsgBcAPCuiFwOINvDMYmCggmeIoEAuFsp1UX/aqqUMkrw5xw7iQyCNkFVH6VUZwB/Aojz49je5JheF0BbkSkf2lPDXGgLPfwc0CchCgATPNnRGWhLpBl+AXCnPnUrRKSVPsumq+oATiqlskWkDbRl0wx5xs+7WALgar2ePwna8nFeZ7vU5wavrpT6EcB90Kp3iCzBOniyow0A8vWqlvcBvAqteuQPvaEzA56XY/sZwB0isgHaDIgrTe9NA7BBRP5QSl1v2v4VtHU810ObOfAhpdQR/QbhSVUA34hIHLTS/8SSfUSi4nE2SSIim2IVDRGRTTHBExHZFBM8EZFNMcETEdkUEzwRkU0xwRMR2RQTPBGRTf0/tyIuAcusWgcAAAAASUVORK5CYII=\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "### Define optimizer and training operation ###\n", "\n", "'''TODO: instantiate a new model for training using the `build_model`\n", " function and the hyperparameters created above.'''\n", "model = build_model(vocab_size, embedding_dim, rnn_units, batch_size)\n", "\n", "'''TODO: instantiate an optimizer with its learning rate.\n", " Checkout the tensorflow website for a list of supported optimizers.\n", " https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/\n", " Try using the Adam optimizer to start.'''\n", "optimizer = tf.keras.optimizers.Adam(learning_rate)\n", "\n", "@tf.function\n", "def train_step(x, y): \n", " # Use tf.GradientTape()\n", " with tf.GradientTape() as tape:\n", " \n", " '''TODO: feed the current input into the model and generate predictions'''\n", " y_hat = model(x)\n", " \n", " '''TODO: compute the loss!'''\n", " loss = compute_loss(y, y_hat)\n", "\n", " # Now, compute the gradients \n", " '''TODO: complete the function call for gradient computation. \n", " Remember that we want the gradient of the loss with respect all \n", " of the model parameters. \n", " HINT: use `model.trainable_variables` to get a list of all model\n", " parameters.'''\n", " grads = tape.gradient(loss, model.trainable_variables)\n", " \n", " # Apply the gradients to the optimizer so it can update the model accordingly\n", " optimizer.apply_gradients(zip(grads, model.trainable_variables))\n", " return loss\n", "\n", "##################\n", "# Begin training!#\n", "##################\n", "\n", "history = []\n", "plotter = PeriodicPlotter(sec=2, xlabel='Iterations', ylabel='Loss')\n", "if hasattr(tqdm, '_instances'): tqdm._instances.clear() # clear if it exists\n", "\n", "for iter in tqdm(range(num_training_iterations)):\n", "\n", " # Grab a batch and propagate it through the network\n", " x_batch, y_batch = get_batch(vectorized_songs, seq_length, batch_size)\n", " loss = train_step(x_batch, y_batch)\n", "\n", " # Update the progress bar\n", " history.append(loss.numpy().mean())\n", " plotter.plot(history)\n", "\n", " # Update the model with the changed weights!\n", " if iter % 100 == 0: \n", " model.save_weights(checkpoint_prefix)\n", " \n", "# Save the trained model and the weights\n", "model.save_weights(checkpoint_prefix)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generate music using the RNN model\n", "\n", "Now, we can use our trained RNN model to generate some music! When generating music, we'll have to feed the model some sort of seed to get it started (because it can't predict anything without something to start with!).\n", "\n", "Once we have a generated seed, we can then iteratively predict each successive character (remember, we are using the ABC representation for our music) using our trained RNN. More specifically, recall that our RNN outputs a `softmax` over possible successive characters. For inference, we iteratively sample from these distributions, and then use our samples to encode a generated song in the ABC format.\n", "\n", "Then, all we have to do is write it to a file and listen!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Restore the latest checkpoint\n", "\n", "To keep this inference step simple, we will use a batch size of 1. Because of how the RNN state is passed from timestep to timestep, the model will only be able to accept a fixed batch size once it is built. \n", "\n", "To run the model with a different `batch_size`, we'll need to rebuild the model and restore the weights from the latest checkpoint, i.e., the weights after the last checkpoint during training:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Model: \"sequential_2\"\n", "_________________________________________________________________\n", "Layer (type) Output Shape Param # \n", "=================================================================\n", "embedding_2 (Embedding) (1, None, 256) 21248 \n", "_________________________________________________________________\n", "lstm_2 (LSTM) (1, None, 1024) 5246976 \n", "_________________________________________________________________\n", "dense_2 (Dense) (1, None, 83) 85075 \n", "=================================================================\n", "Total params: 5,353,299\n", "Trainable params: 5,353,299\n", "Non-trainable params: 0\n", "_________________________________________________________________\n" ] } ], "source": [ "model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=1)\n", "\n", "# Restore the model weights for the last checkpoint after training\n", "model.load_weights(tf.train.latest_checkpoint(checkpoint_dir))\n", "model.build(tf.TensorShape([1, None]))\n", "\n", "model.summary()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that we have fed in a fixed `batch_size` of 1 for inference." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The prediction procedure\n", "\n", "Now, we're ready to write the code to generate text in the ABC music format:\n", "\n", "* Initialize a \"seed\" start string and the RNN state, and set the number of characters we want to generate.\n", "\n", "* Use the start string and the RNN state to obtain the probability distribution over the next predicted character.\n", "\n", "* Sample from multinomial distribution to calculate the index of the predicted character. This predicted character is then used as the next input to the model.\n", "\n", "* At each time step, the updated RNN state is fed back into the model, so that it now has more context in making the next prediction. After predicting the next character, the updated RNN states are again fed back into the model, which is how it learns sequence dependencies in the data, as it gets more information from the previous predictions.\n", "\n", "![LSTM inference](image/lstm_inference.png)\n", "\n", "Complete and experiment with this code block (as well as some of the aspects of network definition and training!), and see how the model performs. How do songs generated after training with a small number of epochs compare to those generated after a longer duration of training?" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "### Prediction of a generated song ###\n", "\n", "def generate_text(model, start_string, generation_length=1000):\n", " # Evaluation step (generating ABC text using the learned RNN model)\n", "\n", " '''TODO: convert the start string to numbers (vectorize)'''\n", " input_eval = [char2idx[s] for s in start_string]\n", " input_eval = tf.expand_dims(input_eval, 0)\n", "\n", " # Empty string to store our results\n", " text_generated = []\n", "\n", " # Here batch size == 1\n", " model.reset_states()\n", " tqdm._instances.clear()\n", "\n", " for i in tqdm(range(generation_length)):\n", " predictions = model(input_eval)\n", " \n", " # Remove the batch dimension\n", " predictions = tf.squeeze(predictions, 0)\n", " \n", " '''TODO: use a multinomial distribution to sample'''\n", " predicted_id = tf.random.categorical(predictions, num_samples=1)[-1,0].numpy()\n", " \n", " # Pass the prediction along with the previous hidden state\n", " # as the next inputs to the model\n", " input_eval = tf.expand_dims([predicted_id], 0)\n", " \n", " '''TODO: add the predicted character to the generated text!'''\n", " # Hint: consider what format the prediction is in vs. the output\n", " text_generated.append(idx2char[predicted_id])\n", " \n", " return (start_string + ''.join(text_generated))" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "100%|█████████████████████████████████████████████████████████████████████████████| 1000/1000 [00:03<00:00, 269.83it/s]\n" ] } ], "source": [ "'''TODO: Use the model and the function defined above to generate ABC format text of length 1000!\n", " As you may notice, ABC files start with \"X\" - this may be a good start string.'''\n", "generated_text = generate_text(model, start_string=\"X\", generation_length=1000) # TODO" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Play back the generated music!\n", "\n", "We can now call a function to convert the ABC format text to an audio file, and then play that back to check out our generated music! Try training longer if the resulting song is not long enough, or re-generating the song!" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Found 3 songs in text\n", "Generated song 0\n" ] }, { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Generated song 1\n" ] }, { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Generated song 2\n" ] }, { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "generated_songs = extract_song_snippet(generated_text)\n", "\n", "for i, song in enumerate(generated_songs): \n", " # Synthesize the waveform from a song\n", " waveform = play_song(song)\n", "\n", " # If its a valid song (correct syntax), lets play it! \n", " if waveform:\n", " print(\"Generated song\", i)\n", " ipythondisplay.display(waveform)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> Note: Sometimes, the model cannot generate valid song. In that case, try to re-train the model and extract it." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Experiment and **get awarded for the best songs**!\n", "\n", "Congrats on making your first sequence model in TensorFlow! It's a pretty big accomplishment, and hopefully you have some sweet tunes to show for it.\n", "\n", "Consider how you may improve your model and what seems to be most important in terms of performance. Here are some ideas to get you started:\n", "\n", "* How does the number of training epochs affect the performance?\n", "* What if you alter or augment the dataset? \n", "* Does the choice of start string significantly affect the result? \n", "\n", "Try to optimize your model and submit your best song! **MIT students and affiliates will be eligible for prizes during the IAP offering**. To enter the competition, MIT students and affiliates should upload the following to the course Canvas:\n", "\n", "* a recording of your song;\n", "* iPython notebook with the code you used to generate the song;\n", "* a description and/or diagram of the architecture and hyperparameters you used -- if there are any additional or interesting modifications you made to the template code, please include these in your description.\n", "\n", "You can also tweet us at [@MITDeepLearning](https://twitter.com/MITDeepLearning) a copy of the song! See this example song generated by a previous 6.S191 student (credit Ana Heart): song from May 20, 2020.\n", "\n", "\n", "Have fun and happy listening!\n", "\n", "![Let's Dance!](http://33.media.tumblr.com/3d223954ad0a77f4e98a7b87136aa395/tumblr_nlct5lFVbF1qhu7oio1_500.gif)" ] } ], "metadata": { "accelerator": "GPU", "colab": { "collapsed_sections": [], "name": "PyTorch_Tutorial.ipynb", "provenance": [] }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "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.7.6" } }, "nbformat": 4, "nbformat_minor": 4 }