Recurrent-Neural-Networks / classification.ipynb
classification.ipynb
Raw
{
 "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
}