{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "executionInfo": { "elapsed": 2729, "status": "ok", "timestamp": 1606199385550, "user": { "displayName": "Punit Jha", "photoUrl": "", "userId": "07885534541681120711" }, "user_tz": 360 }, "id": "MstYuFxFgO2i", "outputId": "24bc8595-0f0f-4393-accc-ca80734bc61f" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: Unidecode in /usr/local/lib/python3.6/dist-packages (1.1.1)\n" ] } ], "source": [ "\n", "!pip install Unidecode\n", "import os\n", "import time\n", "import math\n", "import glob\n", "import string\n", "import random \n", "\n", "import torch\n", "import torch.nn as nn\n", "\n", "from rnn.helpers import time_since\n", "\n", "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "executionInfo": { "elapsed": 512, "status": "ok", "timestamp": 1606199291446, "user": { "displayName": "Punit Jha", "photoUrl": "", "userId": "07885534541681120711" }, "user_tz": 360 }, "id": "lZxpgLA1gRB_", "outputId": "14a17cc6-98c1-43bf-d8ab-bc264a9fa6c6" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Drive already mounted at /content/gdrive; to attempt to forcibly remount, call drive.mount(\"/content/gdrive\", force_remount=True).\n" ] } ], "source": [ "from google.colab import drive\n", "drive.mount('/content/gdrive')" ] }, { "cell_type": "markdown", "metadata": { "id": "f0ESsWZrg7IC" }, "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "executionInfo": { "elapsed": 453, "status": "ok", "timestamp": 1606199416702, "user": { "displayName": "Punit Jha", "photoUrl": "", "userId": "07885534541681120711" }, "user_tz": 360 }, "id": "0_iJSZUmhBpA", "outputId": "69b85f3b-4aff-49b4-9aef-3bef19109f55" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/content/gdrive/My Drive/DL_stuff/assignment_4_part_2\n" ] } ], "source": [ "import os\n", "os.chdir(\"gdrive/My Drive/DL_stuff/assignment_4_part_2\")\n", "#os.chdir(\"./assignment1\")\n", "!pwd" ] }, { "cell_type": "markdown", "metadata": { "id": "trXrIsXRg0qj" }, "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "xBipcqNOgO2j" }, "outputs": [], "source": [ "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")" ] }, { "cell_type": "markdown", "metadata": { "id": "ENk4RHvzgO2j" }, "source": [ "# Language recognition with an RNN\n", "\n", "If you've ever used an online translator you've probably seen a feature that automatically detects the input language. While this might be easy to do if you input unicode characters that are unique to one or a small group of languages (like \"你好\" or \"γεια σας\"), this problem is more challenging if the input only uses the available ASCII characters. In this case, something like \"těší mě\" would beome \"tesi me\" in the ascii form. This is a more challenging problem in which the language must be recognized purely by the pattern of characters rather than unique unicode characters.\n", "\n", "We will train an RNN to solve this problem for a small set of languages thta can be converted to romanized ASCII form. For training data it would be ideal to have a large and varied dataset in different language styles. However, it is easy to find copies of the Bible which is a large text translated to different languages but in the same easily parsable format, so we will use 20 different copies of the Bible as training data. Using the same book for all of the different languages will hopefully prevent minor overfitting that might arise if we used different books for each language (fitting to common characteristics of the individual books rather than the language)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "executionInfo": { "elapsed": 296, "status": "ok", "timestamp": 1606199422488, "user": { "displayName": "Punit Jha", "photoUrl": "", "userId": "07885534541681120711" }, "user_tz": 360 }, "id": "sqKfT6OsgO2j", "outputId": "825eb54b-703c-49a8-a216-50d20f544733" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "tesi me\n" ] } ], "source": [ "from unidecode import unidecode as unicodeToAscii\n", "\n", "all_characters = string.printable\n", "n_letters = len(all_characters)\n", "\n", "print(unicodeToAscii('těší mě'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "OpLuYurVgO2k" }, "outputs": [], "source": [ "# Read a file and split into lines\n", "def readFile(filename):\n", " data = open(filename, encoding='utf-8').read().strip()\n", " return unicodeToAscii(data)\n", "\n", "def get_category_data(data_path):\n", " # Build the category_data dictionary, a list of names per language\n", " category_data = {}\n", " all_categories = []\n", " for filename in glob.glob(data_path):\n", " category = os.path.splitext(os.path.basename(filename))[0].split('_')[0]\n", " all_categories.append(category)\n", " data = readFile(filename)\n", " category_data[category] = data\n", " \n", " return category_data, all_categories" ] }, { "cell_type": "markdown", "metadata": { "id": "1pDlqlHAgO2k" }, "source": [ "The original text is split into two parts, train and test, so that we can make sure that the model is not simply memorizing the train data." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "executionInfo": { "elapsed": 19100, "status": "ok", "timestamp": 1606199325048, "user": { "displayName": "Punit Jha", "photoUrl": "", "userId": "07885534541681120711" }, "user_tz": 360 }, "id": "U8wWiiM0gO2k", "outputId": "360ff599-3ba2-4e13-8271-abd52f30af53" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "20\n", "['finnish', 'german', 'xhosa', 'esperanto', 'czech', 'spanish', 'vietnamese', 'danish', 'turkish', 'albanian', 'maori', 'norwegian', 'portuguese', 'italian', 'swedish', 'romanian', 'french', 'hungarian', 'lithuanian', 'english']\n" ] } ], "source": [ "train_data_path = 'language_data/train/*_train.txt'\n", "test_data_path = 'language_data/test/*_test.txt'\n", "\n", "train_category_data, all_categories = get_category_data(train_data_path)\n", "test_category_data, test_all_categories = get_category_data(test_data_path)\n", "\n", "n_languages = len(all_categories)\n", "\n", "print(len(all_categories))\n", "print(all_categories)" ] }, { "cell_type": "markdown", "metadata": { "id": "VVZz6IMCgO2k" }, "source": [ "# Data processing" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "7vEZXQLCgO2k" }, "outputs": [], "source": [ "def categoryFromOutput(output):\n", " top_n, top_i = output.topk(1, dim=1)\n", " category_i = top_i[:, 0]\n", " return category_i\n", "\n", "# Turn string into long tensor\n", "def stringToTensor(string):\n", " tensor = torch.zeros(len(string), requires_grad=True).long()\n", " for c in range(len(string)):\n", " tensor[c] = all_characters.index(string[c])\n", " return tensor\n", "\n", "def load_random_batch(text, chunk_len, batch_size):\n", " input_data = torch.zeros(batch_size, chunk_len).long().to(device)\n", " target = torch.zeros(batch_size, 1).long().to(device)\n", " input_text = []\n", " for i in range(batch_size):\n", " category = all_categories[random.randint(0, len(all_categories) - 1)]\n", " line_start = random.randint(0, len(text[category])-chunk_len)\n", " category_tensor = torch.tensor([all_categories.index(category)], dtype=torch.long)\n", " line = text[category][line_start:line_start+chunk_len]\n", " input_text.append(line)\n", " input_data[i] = stringToTensor(line)\n", " target[i] = category_tensor\n", " return input_data, target, input_text" ] }, { "cell_type": "markdown", "metadata": { "id": "gJ7nNfKfgO2k" }, "source": [ "Implement Model\n", "====================\n", "\n", "For this classification task, we can use the same model we implement for the generation task which is located in `rnn/model.py`. See the `MP4_P2_generation.ipynb` notebook for more instructions. In this case each output vector of our RNN will have the dimension of the number of possible languages (i.e. `n_languages`). We will use this vector to predict a distribution over the languages.\n", "\n", "In the generation task, we used the output of the RNN at every time step to predict the next letter and our loss included the output from each of these predictions. However, in this task we use the output of the RNN at the end of the sequence to predict the language, so our loss function will use only the predicted output from the last time step.\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "XlmgprNygO2k" }, "source": [ "# Train RNN" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Frl7EXregO2k" }, "outputs": [], "source": [ "from rnn.model import RNN" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "LVREt5HogO2k" }, "outputs": [], "source": [ "# chunk_len = 50\n", "\n", "# BATCH_SIZE = 100\n", "# n_epochs = 2000\n", "# hidden_size = 100\n", "# n_layers = 1\n", "# learning_rate = 0.01\n", "# model_type = 'rnn'\n", "\n", "# criterion = nn.CrossEntropyLoss()\n", "# rnn = RNN(n_letters, hidden_size, n_languages, model_type=model_type, n_layers=n_layers).to(device)\n", "\n", "chunk_len = 50\n", "\n", "BATCH_SIZE = 100\n", "#n_epochs = 1000\n", "hidden_size = 600#100 #300\n", "n_layers = 2 #1\n", "learning_rate =0.001 #0.001 #0.01 #0.05\n", "#model_type = 'rnn'\n", "model_type = 'lstm'\n", "criterion = nn.CrossEntropyLoss()\n", "rnn = RNN(n_letters, hidden_size, n_languages, model_type=model_type, n_layers=n_layers).to(device)\n", "optimizer = torch.optim.Adam(rnn.parameters(), lr=learning_rate)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "vF1tNHmb1V7T" }, "outputs": [], "source": [ "rnn.load_state_dict(torch.load(\"./classification_model.pth\"))\n", "optimizer.load_state_dict(torch.load(\"./classification_optimizer.pth\"))" ] }, { "cell_type": "markdown", "metadata": { "id": "94H6JrZUgO2k" }, "source": [ "**TODO:** Fill in the train function. You should initialize a hidden layer representation using your RNN's `init_hidden` function, set the model gradients to zero, and loop over each time step (character) in the input tensor. For each time step compute the output of the of the RNN and the next hidden layer representation. The cross entropy loss should be computed over the last RNN output scores from the end of the sequence and the target classification tensor. Lastly, call backward on the loss and take an optimizer step." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "W2jYGIgzgO2k" }, "outputs": [], "source": [ "def train(rnn, target_tensor, data_tensor, optimizer, criterion, batch_size=BATCH_SIZE):\n", " \"\"\"\n", " Inputs:\n", " - rnn: model\n", " - target_target: target character data tensor of shape (batch_size, 1)\n", " - data_tensor: input character data tensor of shape (batch_size, chunk_len)\n", " - optimizer: rnn model optimizer\n", " - criterion: loss function\n", " - batch_size: data batch size\n", " \n", " Returns:\n", " - output: output from RNN from end of sequence \n", " - loss: computed loss value as python float\n", " \n", " \"\"\"\n", " \n", " #output, loss =0,0\n", " \n", " output, loss = 0, 0\n", " batch_size=data_tensor.shape[0]\n", " chunk_size=data_tensor.shape[1]\n", " rnn_hidden = rnn.init_hidden(batch_size, device=device) # initialize a hidden layer representation using your RNN's init_hidden function\n", " rnn.zero_grad() # set the model gradients to zero\n", " \n", " for x in range(chunk_size): # loop over each time step (character) in the input tensor.\n", " output,rnn_hidden=rnn(data_tensor[:,x], rnn_hidden ) #each time step compute the output of the of the RNN\n", " #print(\"output of rnn\",rnn_out.size())\n", " #rnn_out_new=rnn_out.view(batch_size, -1)\n", " #print(\"output of resized rnn\",rnn_out_new.size())\n", " \n", " \n", " new_target_tensor=target_tensor.squeeze() #piazza hint\n", " #print(\"after squeeze\",new_target_tensor.size())\n", " #print(\"output size\",output.size())\n", " loss+=criterion(output,new_target_tensor) #piazza hint\n", "\n", " loss.backward() #call backward on the averaged loss \n", " optimizer.step() # take an optimizer step.\n", " \n", " ####################################\n", " # YOUR CODE HERE #\n", " ####################################\n", " \n", " \n", " ########## END ##########\n", "\n", " return output, loss\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "m0Kq43nDgO2k" }, "outputs": [], "source": [ "def evaluate(rnn, data_tensor, seq_len=chunk_len, batch_size=BATCH_SIZE):\n", " with torch.no_grad():\n", " data_tensor = data_tensor.to(device)\n", " hidden = rnn.init_hidden(batch_size, device=device)\n", " for i in range(seq_len):\n", " output, hidden = rnn(data_tensor[:,i], hidden)\n", " \n", " return output\n", " \n", "def eval_test(rnn, category_tensor, data_tensor):\n", " with torch.no_grad():\n", " output = evaluate(rnn, data_tensor)\n", " batch_size=data_tensor.shape[0] # this was initially needed\n", " output= output.view(batch_size, -1) # this was initially needed\n", " loss = criterion(output, category_tensor.squeeze())\n", " return output, loss.item()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "executionInfo": { "elapsed": 311089, "status": "ok", "timestamp": 1606200527476, "user": { "displayName": "Punit Jha", "photoUrl": "", "userId": "07885534541681120711" }, "user_tz": 360 }, "id": "D0Ou_uGogO2k", "outputId": "c0c15d8a-c35b-4072-a1a5-3c1deb71f70c", "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "50 2% (0m 7s) 0.1092 0.0262 ce que vous mangerez de tout ce qui est dans les e / french ✓\n", "Train accuracy: 0.9838\n", "100 5% (0m 15s) 0.0063 0.0046 lesegul, a sajat leanyaikat pedig oda adtak azok f / hungarian ✓\n", "Train accuracy: 0.989\n", "150 7% (0m 23s) 0.0037 0.0034 rasten skall bara nagot av tjurens blod in i uppen / swedish ✓\n", "Train accuracy: 0.9882\n", "200 10% (0m 31s) 0.0011 0.0010 trente ans, et engendra Heber. Et Shelach, apres / french ✓\n", "Train accuracy: 0.9918\n", "250 12% (0m 39s) 0.0182 0.0084 layan, adam olduren gibidir,<br />Davar kurban ede / turkish ✓\n", "Train accuracy: 0.9856\n", "300 15% (0m 46s) 0.0726 0.0344 ua; vi truoc mat cac tho xay cat, chung no co choc / vietnamese ✓\n", "Train accuracy: 0.9888\n", "350 17% (0m 54s) 0.0022 0.0021 uchu, sedici na miste, kde pred tim lezelo Jezisov / czech ✓\n", "Train accuracy: 0.9902\n", "400 20% (1m 2s) 0.0170 0.0102 Ar, det halvtredsindstyvende, vaere eder; I ma ikk / danish ✓\n", "Train accuracy: 0.987\n", "450 22% (1m 9s) 0.0311 0.0188 t calcati in picioare la poarta, si nimeni nu i sc / romanian ✓\n", "Train accuracy: 0.99\n", "500 25% (1m 17s) 0.0091 0.0067 melis dar turejo kita zmona, kuri buvo vardu Atara / lithuanian ✓\n", "Train accuracy: 0.9876\n", "550 27% (1m 25s) 0.0121 0.0050 oni sa andej e ketej brenda vathes sepse Malkami p / albanian ✓\n", "Train accuracy: 0.9898\n", "600 30% (1m 33s) 0.0282 0.0097 kur ai do te shfaqet? Ai eshte si zjarri i shkrire / albanian ✓\n", "Train accuracy: 0.9908\n", "650 32% (1m 40s) 0.0570 0.0066 anlarini sundu. Getirdigi armaganlar sunlardi: 130 / turkish ✓\n", "Train accuracy: 0.9872\n", "700 35% (1m 48s) 0.0472 0.0158 ong the nations, and despised among men. As for th / english ✓\n", "Train accuracy: 0.988\n", "750 37% (1m 56s) 0.0030 0.0025 Es korulfogtak ot, hogy legyozzek. Akkor felkialt / hungarian ✓\n", "Train accuracy: 0.9904\n", "800 40% (2m 4s) 0.0262 0.0066 uotas i chaldeju rankas\". Viespats tare Jeremijui: / lithuanian ✓\n", "Train accuracy: 0.989\n", "850 42% (2m 11s) 0.0723 0.0417 mahen und das Gute zu erwahlen weiss. Denn ehe der / german ✓\n", "Train accuracy: 0.9904\n", "900 45% (2m 19s) 0.0092 0.0069 kusxis kun sia patro; kaj li ne sciis, kiam sxi k / esperanto ✓\n", "Train accuracy: 0.9902\n", "950 47% (2m 27s) 0.0165 0.0078 og han tok bolig i landet Midian og bodde ved en b / norwegian ✓\n", "Train accuracy: 0.9892\n", "1000 50% (2m 35s) 0.0589 0.0083 erto. Y volvieron y vinieron a Emmisphat, que es C / spanish ✓\n", "Train accuracy: 0.9926\n", "1050 52% (2m 42s) 0.0147 0.0059 r eder. Og alle dyr pa jorden og alle fugler under / norwegian ✓\n", "Train accuracy: 0.9916\n", "1100 55% (2m 50s) 0.0590 0.0169 a. Dijome entonces: Hijo del hombre, ?no ves lo qu / spanish ✓\n", "Train accuracy: 0.9884\n", "1150 57% (2m 58s) 0.0226 0.0072 estas publikulino, cxar sxi kovris sian vizagxon. / esperanto ✓\n", "Train accuracy: 0.9896\n", "1200 60% (3m 6s) 0.0110 0.0011 diz o Senhor, mas me provocastes ira com a obra / portuguese ✓\n", "Train accuracy: 0.9882\n", "1250 62% (3m 13s) 0.0406 0.0037 ki ahau. A ka korerotia e ia ki tona papa ratou ko / maori ✓\n", "Train accuracy: 0.9892\n", "1300 65% (3m 21s) 0.0181 0.0048 ma ikke vanhellige din Datter ved at lade hende b / danish ✓\n", "Train accuracy: 0.9878\n", "1350 67% (3m 29s) 0.0072 0.0046 reddish-white plague; it is leprosy breaking out i / english ✓\n", "Train accuracy: 0.9914\n", "1400 70% (3m 36s) 0.0030 0.0028 Yehova, babuye elowo endleleni yakhe embi; ngokub / xhosa ✓\n", "Train accuracy: 0.9922\n", "1450 72% (3m 44s) 0.0679 0.0446 ondis, dirante: Fratoj, auxskultu min; Simeon rak / esperanto ✓\n", "Train accuracy: 0.9928\n", "1500 75% (3m 52s) 0.0406 0.0169 dig. Du skal ikke lane ham penger mot rente og ikk / norwegian ✓\n", "Train accuracy: 0.9864\n", "1550 77% (4m 0s) 0.0061 0.0033 , ze mu zelezna sekera spadla do vody. Vykrikl a z / czech ✓\n", "Train accuracy: 0.989\n", "1600 80% (4m 7s) 0.0992 0.0121 ehabeam. Men da kom HERRENs Ord til den Guds Mand / danish ✓\n", "Train accuracy: 0.9904\n", "1650 82% (4m 15s) 0.0141 0.0047 n Geschlecht, das seinen Vater verflucht und seine / german ✓\n", "Train accuracy: 0.9892\n", "1700 85% (4m 23s) 0.0557 0.0097 Ginath: so Tibni died, and Omri reigned. In the th / english ✓\n", "Train accuracy: 0.9922\n", "1750 87% (4m 31s) 0.0244 0.0118 v meste vidim nasili a svary, ve dne v noci po hr / czech ✓\n", "Train accuracy: 0.9888\n", "1800 90% (4m 38s) 0.0467 0.0173 len de las cavernas en que se habian escondido. Y / spanish ✓\n", "Train accuracy: 0.9914\n", "1850 92% (4m 46s) 0.0073 0.0034 stermis per glavo cxion, kio estis en la urbo, la / esperanto ✓\n", "Train accuracy: 0.9912\n", "1900 95% (4m 54s) 0.0236 0.0067 beslendikten sonra acikta birakildi. Firavunun kiz / turkish ✓\n", "Train accuracy: 0.9906\n", "1950 97% (5m 2s) 0.0859 0.0312 nguoi, khong tin Ngai va khong nghe theo tieng Nga / vietnamese ✓\n", "Train accuracy: 0.9912\n", "2000 100% (5m 10s) 0.0080 0.0031 Son, og salvede og hyldede ham til Bonge i hans F / danish ✓\n", "Train accuracy: 0.9922\n" ] } ], "source": [ "n_iters = 2000 #2000 #100000\n", "print_every = 50\n", "plot_every = 50\n", "\n", "\n", "# Keep track of losses for plotting\n", "current_loss = 0\n", "current_test_loss = 0\n", "all_losses = []\n", "all_test_losses = []\n", "\n", "start = time.time()\n", "\n", "\n", "\n", "\n", "number_correct = 0\n", "for iter in range(1, n_iters + 1):\n", " input_data, target_category, text_data = load_random_batch(train_category_data, chunk_len, BATCH_SIZE)\n", " output, loss = train(rnn, target_category, input_data, optimizer, criterion)\n", " current_loss += loss\n", " \n", " _, test_loss = eval_test(rnn, target_category, input_data)\n", " current_test_loss += test_loss\n", " \n", " guess_i = categoryFromOutput(output)\n", " number_correct += (target_category.squeeze()==guess_i.squeeze()).long().sum()\n", " \n", " # Print iter number, loss, name and guess\n", " if iter % print_every == 0:\n", " sample_idx = 0\n", " guess = all_categories[guess_i[sample_idx]]\n", " \n", " category = all_categories[int(target_category[sample_idx])]\n", " \n", " correct = '✓' if guess == category else '✗ (%s)' % category\n", " print('%d %d%% (%s) %.4f %.4f %s / %s %s' % (iter, iter / n_iters * 100, time_since(start), loss, test_loss, text_data[sample_idx], guess, correct))\n", " print('Train accuracy: {}'.format(float(number_correct)/float(print_every*BATCH_SIZE)))\n", " number_correct = 0\n", " \n", " # Add current loss avg to list of losses\n", " if iter % plot_every == 0:\n", " all_losses.append(current_loss / plot_every)\n", " current_loss = 0\n", " all_test_losses.append(current_test_loss / plot_every)\n", " current_test_loss = 0" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Sz3PWOjc056c" }, "outputs": [], "source": [ "torch.save(rnn.state_dict(), \"./classification_model_final.pth\")\n", "torch.save(optimizer.state_dict(), \"./classification_optimizer_final.pth\")" ] }, { "cell_type": "markdown", "metadata": { "id": "OnK-Uq1NgO2l" }, "source": [ "Plot loss functions\n", "--------------------\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 284 }, "executionInfo": { "elapsed": 461, "status": "ok", "timestamp": 1606200549613, "user": { "displayName": "Punit Jha", "photoUrl": "", "userId": "07885534541681120711" }, "user_tz": 360 }, "id": "oTQOYu39gO2l", "outputId": "02f60de4-6bda-45e7-8661-8277fcf17177", "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "[<matplotlib.lines.Line2D at 0x7f2c285e9940>]" ] }, "execution_count": 50, "metadata": { "tags": [] }, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": { "needs_background": "light", "tags": [] }, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "import matplotlib.ticker as ticker\n", "\n", "plt.figure()\n", "plt.plot(all_losses, color='b')\n", "plt.plot(all_test_losses, color='r')\n", "\n", "#############################################\n", "# Explanation of the plot below\n", "#############################################\n", "# This is the training and testing loss plot after the initial run of 2000 iterations when\n", "#the accuracy was already around 98 percent. The initial training loss graph is presented in\n", "#the report\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "jerM8oS5gO2m" }, "source": [ "Evaluate results\n", "-------------------\n", "\n", "We now vizualize the performance of our model by creating a confusion matrix. The ground truth languages of samples are represented by rows in the matrix while the predicted languages are represented by columns.\n", "\n", "In this evaluation we consider sequences of variable sizes rather than the fixed length sequences we used for training." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 339 }, "executionInfo": { "elapsed": 12832, "status": "ok", "timestamp": 1606200598676, "user": { "displayName": "Punit Jha", "photoUrl": "", "userId": "07885534541681120711" }, "user_tz": 360 }, "id": "TqzSFm3hgO2m", "outputId": "ab0c22a7-cabc-493e-eb78-d4d9ebb46e27" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Test accuracy: 0.903\n" ] }, { "data": { "image/png": "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\n", "text/plain": [ "<Figure size 432x288 with 2 Axes>" ] }, "metadata": { "needs_background": "light", "tags": [] }, "output_type": "display_data" } ], "source": [ "eval_batch_size = 1 # needs to be set to 1 for evaluating different sequence lengths\n", "\n", "# Keep track of correct guesses in a confusion matrix\n", "confusion = torch.zeros(n_languages, n_languages)\n", "n_confusion = 1000\n", "num_correct = 0\n", "total = 0\n", "\n", "for i in range(n_confusion):\n", " eval_chunk_len = random.randint(10, 50) # in evaluation we will look at sequences of variable sizes\n", " input_data, target_category, text_data = load_random_batch(test_category_data, chunk_len=eval_chunk_len, batch_size=eval_batch_size)\n", " output = evaluate(rnn, input_data, seq_len=eval_chunk_len, batch_size=eval_batch_size)\n", " \n", " guess_i = categoryFromOutput(output)\n", " category_i = [int(target_category[idx]) for idx in range(len(target_category))]\n", " for j in range(eval_batch_size):\n", " category = all_categories[category_i[j]] \n", " confusion[category_i[j]][guess_i[j]] += 1\n", " num_correct += int(guess_i[j]==category_i[j])\n", " total += 1\n", "\n", "print('Test accuracy: ', float(num_correct)/float(n_confusion*eval_batch_size))\n", "\n", "# Normalize by dividing every row by its sum\n", "for i in range(n_languages):\n", " confusion[i] = confusion[i] / confusion[i].sum()\n", "\n", "# Set up plot\n", "fig = plt.figure()\n", "ax = fig.add_subplot(111)\n", "cax = ax.matshow(confusion.numpy())\n", "fig.colorbar(cax)\n", "\n", "# Set up axes\n", "ax.set_xticklabels([''] + all_categories, rotation=90)\n", "ax.set_yticklabels([''] + all_categories)\n", "\n", "# Force label at every tick\n", "ax.xaxis.set_major_locator(ticker.MultipleLocator(1))\n", "ax.yaxis.set_major_locator(ticker.MultipleLocator(1))\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "id": "MenUgxZAgO2m" }, "source": [ "You can pick out bright spots off the main axis that show which\n", "languages it guesses incorrectly.\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "Xju4rPWqgO2m" }, "source": [ "Run on User Input\n", "---------------------\n", "\n", "Now you can test your model on your own input. \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "executionInfo": { "elapsed": 313, "status": "ok", "timestamp": 1606200621242, "user": { "displayName": "Punit Jha", "photoUrl": "", "userId": "07885534541681120711" }, "user_tz": 360 }, "id": "Rb4R2JNlgO2m", "outputId": "d60ef7aa-ec94-4c3e-8b1b-58ef657a8416" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "> This is a phrase to test the model on user input\n", "(10.56) english\n", "(2.76) danish\n", "(0.74) norwegian\n", "(0.23) spanish\n", "(-0.15) hungarian\n" ] } ], "source": [ "def predict(input_line, n_predictions=5):\n", " print('\\n> %s' % input_line)\n", " with torch.no_grad():\n", " input_data = stringToTensor(input_line).long().unsqueeze(0).to(device)\n", " output = evaluate(rnn, input_data, seq_len=len(input_line), batch_size=1)\n", "\n", " # Get top N categories\n", " topv, topi = output.topk(n_predictions, dim=1)\n", " predictions = []\n", "\n", " for i in range(n_predictions):\n", " topv.shape\n", " topi.shape\n", " value = topv[0][i].item()\n", " category_index = topi[0][i].item()\n", " print('(%.2f) %s' % (value, all_categories[category_index]))\n", " predictions.append([value, all_categories[category_index]])\n", "\n", "predict('This is a phrase to test the model on user input')\n" ] }, { "cell_type": "markdown", "metadata": { "id": "DhrHtSztgO2m" }, "source": [ "# Output Kaggle submission file\n", "\n", "Once you have found a good set of hyperparameters submit the output of your model on the Kaggle test file." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "_dz2MzlwgO2m" }, "outputs": [], "source": [ "### DO NOT CHANGE KAGGLE SUBMISSION CODE ####\n", "import csv\n", "\n", "kaggle_test_file_path = 'language_data/kaggle_rnn_language_classification_test.txt'\n", "with open(kaggle_test_file_path, 'r') as f:\n", " lines = f.readlines()\n", "\n", "output_rows = []\n", "for i, line in enumerate(lines):\n", " sample = line.rstrip()\n", " sample_chunk_len = len(sample)\n", " input_data = stringToTensor(sample).unsqueeze(0)\n", " output = evaluate(rnn, input_data, seq_len=sample_chunk_len, batch_size=1)\n", " guess_i = categoryFromOutput(output)\n", " output_rows.append((str(i+1), all_categories[guess_i]))\n", "\n", "submission_file_path = 'kaggle_rnn_submission_new.txt'\n", "with open(submission_file_path, 'w') as f:\n", " output_rows = [('id', 'category')] + output_rows\n", " writer = csv.writer(f)\n", " writer.writerows(output_rows)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Trb9Qzp-gO2m" }, "outputs": [], "source": [] } ], "metadata": { "accelerator": "GPU", "colab": { "collapsed_sections": [], "name": "MP4_P2_classification_new.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": 1 }