{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<strong>プロダクト開発演習</strong><br>\n",
    "<strong>テーマ: ライブドアニュースコーパスの文章分類 (LSTMとCNNの比較)</strong><br>\n",
    "行ったこと: ライブドアニュースコーパスの文章分類を2つのモデル(LSTM・CNN)を用いて精度比較し、高精度なモデルを選定した。<br>\n",
    "参考としたサイト:<br>\n",
    "<ul>\n",
    "<li>LSTMについて</li>\n",
    "https://qiita.com/m__k/items/841950a57a0d7ff05506<br>\n",
    "https://qiita.com/m__k/items/db1a81bb06607d5b0ec5\n",
    "<li>CNNについて</li>\n",
    "https://qiita.com/m__k/items/6c39cfe7dfa99102fa8e<br>\n",
    "https://arxiv.org/pdf/1510.03820.pdf\n",
    " </ul>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "●LSTM用データセットの準備"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['movie-enter', 'it-life-hack', 'kaden-channel', 'topic-news', 'livedoor-homme', 'peachy', 'sports-watch', 'dokujo-tsushin', 'smax']\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from glob import glob\n",
    "import pandas as pd\n",
    "import linecache\n",
    "\n",
    "categories = [name for name in os.listdir('text') if os.path.isdir(\"text/\" +name)]\n",
    "print(categories)\n",
    "\n",
    "datasets = pd.DataFrame(columns=[\"title\", \"category\"])\n",
    "for cat in categories:\n",
    "    path = \"text/\" + cat + \"/*.txt\"\n",
    "    files = glob(path)\n",
    "    for text_name in files:\n",
    "        title = linecache.getline(text_name, 3)\n",
    "        s = pd.Series([title, cat], index=datasets.columns)\n",
    "        datasets = datasets.append(s, ignore_index=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "●LSTMを用いた文章分類モデルの作成"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import MeCab\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import MeCab\n",
    "import re\n",
    "\n",
    "tagger = MeCab.Tagger(\"-Owakati\")\n",
    "\n",
    "def make_wakati(sentence):\n",
    "    sentence = tagger.parse(sentence)\n",
    "    sentence = re.sub(r'[0-9０-９a-zA-Zａ-ｚＡ-Ｚ]+', \" \", sentence)\n",
    "    sentence = re.sub(r'[\\．_－―─！＠＃＄％＾＆\\-‐|\\\\＊\\“（）＿■×+α※÷⇒—●★☆〇◎◆▼◇△□(：〜～＋=)／*&^%$#@!~`){}［］…\\[\\]\\\"\\'\\”\\’:;<>?＜＞〔〕〈〉？、。・,\\./『』【】「」→←○《》≪≫\\n\\u3000]+', \"\", sentence)\n",
    "    wakati = sentence.split(\" \")\n",
    "    wakati = list(filter((\"\").__ne__, wakati))\n",
    "    return wakati"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "vocab size :  12944\n"
     ]
    }
   ],
   "source": [
    "word2index = {}\n",
    "# 系列を揃えるためのパディング文字列<pad>を追加\n",
    "# パディング文字列のIDは0とする\n",
    "word2index.update({\"<pad>\":0})\n",
    "\n",
    "for title in datasets[\"title\"]:\n",
    "    wakati = make_wakati(title)\n",
    "    for word in wakati:\n",
    "        if word in word2index: continue\n",
    "        word2index[word] = len(word2index)\n",
    "print(\"vocab size : \", len(word2index))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "import random\n",
    "from sklearn.utils import shuffle\n",
    "\n",
    "cat2index = {}\n",
    "for cat in categories:\n",
    "    if cat in cat2index: continue\n",
    "    cat2index[cat] = len(cat2index)\n",
    "\n",
    "def sentence2index(sentence):\n",
    "    wakati = make_wakati(sentence)\n",
    "    return [word2index[w] for w in wakati]\n",
    "\n",
    "def category2index(cat):\n",
    "    return [cat2index[cat]]\n",
    "\n",
    "index_datasets_title_tmp = []\n",
    "index_datasets_category = []\n",
    "\n",
    "# 系列の長さの最大値を取得。この長さに他の系列の長さをあわせる\n",
    "max_len = 0\n",
    "for title, category in zip(datasets[\"title\"], datasets[\"category\"]):\n",
    "  index_title = sentence2index(title)\n",
    "  index_category = category2index(category)\n",
    "  index_datasets_title_tmp.append(index_title)\n",
    "  index_datasets_category.append(index_category)\n",
    "  if max_len < len(index_title):\n",
    "    max_len = len(index_title)\n",
    "\n",
    "# 系列の長さを揃えるために短い系列にパディングを追加\n",
    "# 後ろパディングだと正しく学習できなかったので、前パディング\n",
    "index_datasets_title = []\n",
    "for title in index_datasets_title_tmp:\n",
    "  for i in range(max_len - len(title)):\n",
    "    title.insert(0, 0) # 前パディング\n",
    "#     title.append(0)　# 後ろパディング\n",
    "  index_datasets_title.append(title)\n",
    "\n",
    "train_x, test_x, train_y, test_y = train_test_split(index_datasets_title, index_datasets_category, train_size=0.7)\n",
    "\n",
    "# データをバッチでまとめるための関数\n",
    "def train2batch(title, category, batch_size=100):\n",
    "  title_batch = []\n",
    "  category_batch = []\n",
    "  title_shuffle, category_shuffle = shuffle(title, category)\n",
    "  for i in range(0, len(title), batch_size):\n",
    "    title_batch.append(title_shuffle[i:i+batch_size])\n",
    "    category_batch.append(category_shuffle[i:i+batch_size])\n",
    "  return title_batch, category_batch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "\n",
    "class LSTMClassifier(nn.Module):\n",
    "    def __init__(self, embedding_dim, hidden_dim, vocab_size, tagset_size, layer_number):\n",
    "        super(LSTMClassifier, self).__init__()\n",
    "        self.hidden_dim = hidden_dim\n",
    "        # <pad>の単語IDが0なので、padding_idx=0としている\n",
    "        self.word_embeddings = nn.Embedding(vocab_size, embedding_dim, padding_idx=0)\n",
    "        # batch_first=Trueが大事！\n",
    "        self.lstm = nn.LSTM(input_size=embedding_dim, hidden_size=hidden_dim, num_layers=layer_number, batch_first=True)\n",
    "        self.hidden2tag = nn.Linear(hidden_dim, tagset_size)\n",
    "        self.softmax = nn.LogSoftmax()\n",
    "\n",
    "    def forward(self, sentence):\n",
    "        embeds = self.word_embeddings(sentence)\n",
    "        #embeds.size() = (batch_size × len(sentence) × embedding_dim)\n",
    "        _, lstm_out = self.lstm(embeds)\n",
    "        # lstm_out[0].size() = (1 × batch_size × hidden_dim)\n",
    "        tag_space = self.hidden2tag(lstm_out[0][2])\n",
    "        # tag_space.size() = (1 × batch_size × tagset_size)\n",
    "\n",
    "        # (batch_size × tagset_size)にするためにsqueeze()する\n",
    "        tag_scores = self.softmax(tag_space.squeeze())\n",
    "        # tag_scores.size() = (batch_size × tagset_size)\n",
    "\n",
    "        return tag_scores\n",
    "\n",
    "EMBEDDING_DIM = 200\n",
    "HIDDEN_DIM = 128\n",
    "VOCAB_SIZE = len(word2index)\n",
    "TAG_SIZE = len(categories)\n",
    "LAYER_NUMBER = 3\n",
    "model = LSTMClassifier(EMBEDDING_DIM, HIDDEN_DIM, VOCAB_SIZE, TAG_SIZE, LAYER_NUMBER)\n",
    "loss_function = nn.NLLLoss()\n",
    "optimizer = optim.Adam(model.parameters(), lr=0.001)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "<ipython-input-6-81593e8a8fc4>:26: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.\n",
      "  tag_scores = self.softmax(tag_space.squeeze())\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 0 \t train loss:  101.45796 \t train acc:  0.28654 \t test loss: 36.7009 \t test acc: 0.41739\n",
      "process time:  19.0 [s]\n",
      "epoch 1 \t train loss:  64.87202 \t train acc:  0.56288 \t test loss: 27.30444 \t test acc: 0.56826\n",
      "process time:  18.0 [s]\n",
      "epoch 2 \t train loss:  40.17011 \t train acc:  0.73115 \t test loss: 24.36324 \t test acc: 0.62435\n",
      "process time:  19.0 [s]\n",
      "epoch 3 \t train loss:  23.96207 \t train acc:  0.84827 \t test loss: 24.78822 \t test acc: 0.6413\n",
      "process time:  18.0 [s]\n",
      "epoch 4 \t train loss:  13.94799 \t train acc:  0.91269 \t test loss: 25.57045 \t test acc: 0.6513\n",
      "process time:  18.0 [s]\n",
      "epoch 5 \t train loss:  6.92676 \t train acc:  0.95962 \t test loss: 27.38291 \t test acc: 0.65957\n",
      "process time:  18.0 [s]\n",
      "epoch 6 \t train loss:  4.59388 \t train acc:  0.96962 \t test loss: 28.50567 \t test acc: 0.65696\n",
      "process time:  18.0 [s]\n",
      "epoch 7 \t train loss:  2.26921 \t train acc:  0.98327 \t test loss: 30.072 \t test acc: 0.66\n",
      "process time:  18.0 [s]\n",
      "epoch 8 \t train loss:  1.30224 \t train acc:  0.98788 \t test loss: 31.47531 \t test acc: 0.66174\n",
      "process time:  18.0 [s]\n",
      "epoch 9 \t train loss:  0.84991 \t train acc:  0.98962 \t test loss: 31.78433 \t test acc: 0.6613\n",
      "process time:  18.0 [s]\n",
      "epoch 10 \t train loss:  0.70333 \t train acc:  0.99 \t test loss: 32.47073 \t test acc: 0.65957\n",
      "process time:  18.0 [s]\n",
      "epoch 11 \t train loss:  0.54112 \t train acc:  0.99058 \t test loss: 33.76472 \t test acc: 0.66478\n",
      "process time:  18.0 [s]\n",
      "epoch 12 \t train loss:  0.39043 \t train acc:  0.99096 \t test loss: 34.48341 \t test acc: 0.66696\n",
      "process time:  18.0 [s]\n",
      "epoch 13 \t train loss:  0.3547 \t train acc:  0.99115 \t test loss: 35.85314 \t test acc: 0.66565\n",
      "process time:  18.0 [s]\n",
      "epoch 14 \t train loss:  0.33564 \t train acc:  0.99096 \t test loss: 34.98831 \t test acc: 0.66435\n",
      "process time:  18.0 [s]\n",
      "epoch 15 \t train loss:  0.31613 \t train acc:  0.99115 \t test loss: 35.73679 \t test acc: 0.66043\n",
      "process time:  18.0 [s]\n",
      "epoch 16 \t train loss:  0.27396 \t train acc:  0.99115 \t test loss: 35.56435 \t test acc: 0.66565\n",
      "process time:  18.0 [s]\n",
      "epoch 17 \t train loss:  0.25152 \t train acc:  0.99115 \t test loss: 36.12127 \t test acc: 0.6613\n",
      "process time:  18.0 [s]\n",
      "epoch 18 \t train loss:  0.24455 \t train acc:  0.99115 \t test loss: 36.90332 \t test acc: 0.66217\n",
      "process time:  18.0 [s]\n",
      "epoch 19 \t train loss:  0.23821 \t train acc:  0.99115 \t test loss: 38.2615 \t test acc: 0.66174\n",
      "process time:  18.0 [s]\n",
      "done.\n"
     ]
    }
   ],
   "source": [
    "lstm_train_accuracy_list = []\n",
    "lstm_test_accuracy_list = []\n",
    "\n",
    "maxepoch = 20\n",
    "for epoch in range(maxepoch):\n",
    "    start_time = time.process_time()\n",
    "    \n",
    "    temp_train_acc = 0\n",
    "    train_loss = 0\n",
    "    train_acc = 0\n",
    "    title_batch, category_batch = train2batch(train_x, train_y)\n",
    "    for i in range(len(title_batch)):\n",
    "        batch_loss = 0\n",
    "\n",
    "        model.zero_grad()\n",
    "\n",
    "        title_tensor = torch.tensor(title_batch[i])\n",
    "        # category_tensor.size() = (batch_size × 1)なので、squeeze()\n",
    "        category_tensor = torch.tensor(category_batch[i]).squeeze()\n",
    "\n",
    "        out = model(title_tensor)\n",
    "\n",
    "        batch_loss = loss_function(out, category_tensor)\n",
    "        _, preds = torch.max(out, 1)\n",
    "        batch_loss.backward()\n",
    "        optimizer.step()\n",
    "\n",
    "        train_loss += batch_loss.item()\n",
    "        temp_train_acc += torch.sum(preds==category_tensor).item()\n",
    "    train_acc = temp_train_acc / (len(title_batch)*100)\n",
    "\n",
    "    temp_test_acc = 0\n",
    "    test_loss = 0\n",
    "    test_acc = 0\n",
    "    test_num = len(test_x)\n",
    "    a = 0\n",
    "    with torch.no_grad():\n",
    "        title_batch, category_batch = train2batch(test_x, test_y)\n",
    "        \n",
    "        for i in range(len(title_batch)):\n",
    "            title_tensor = torch.tensor(title_batch[i])\n",
    "            category_tensor = torch.tensor(category_batch[i]).squeeze()\n",
    "            \n",
    "            out = model(title_tensor)\n",
    "            batch_loss=loss_function(out, category_tensor)\n",
    "            _, preds = torch.max(out, 1)\n",
    "            \n",
    "            test_loss += batch_loss.item()\n",
    "            temp_test_acc += torch.sum(preds==category_tensor).item()\n",
    "    test_acc = temp_test_acc / (len(title_batch)*100)\n",
    "    print(\"epoch\", epoch, \"\\t\" , \"train loss: \", round(train_loss, 5), \"\\t\" , \"train acc: \", round(train_acc, 5), \"\\t\" , \"test loss:\", round(test_loss, 5), \"\\t\" , \"test acc:\", round(test_acc, 5))\n",
    "    \n",
    "    lstm_train_accuracy_list.append(train_acc)\n",
    "    lstm_test_accuracy_list.append(test_acc)\n",
    "    \n",
    "    end_time = time.process_time()\n",
    "    elapsed_time = end_time - start_time\n",
    "    print(\"process time: \", round(elapsed_time, 0), \"[s]\")\n",
    "    \n",
    "print(\"done.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "plt.plot(range(maxepoch), lstm_train_accuracy_list)\n",
    "plt.plot(range(maxepoch), lstm_test_accuracy_list)\n",
    "plt.legend([\"Train_accuracy\", \"Test_accuracy\"])\n",
    "plt.xlabel(\"#epoch\")\n",
    "plt.ylabel(\"Accuracy\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "●CNN用データデータセットの準備"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "paths = list(Path('text').iterdir())\n",
    "labels = []\n",
    "texts = []\n",
    "\n",
    "for path in paths:    \n",
    "    for filepath in path.glob('*.txt'):\n",
    "        if not filepath.name == 'LICENSE.txt':\n",
    "            with open(filepath) as f:\n",
    "                next(f)\n",
    "                next(f)\n",
    "                text = f.read().replace('\\u3000','').replace('\\n','')\n",
    "            \n",
    "            texts.append(text)\n",
    "            labels.append(path.name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "news_df = pd.DataFrame({\n",
    "    'body': texts,\n",
    "    'category': labels\n",
    "})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "●CNNを用いた文章分類モデルの作成"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "import fugashi\n",
    "from torchtext.legacy import data\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "tagger = fugashi.Tagger(\"-Owakati\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_wakati(text):\n",
    "    text = tagger.parse(text)\n",
    "    wakati = text.split(\" \")\n",
    "    wakati = list(filter((\"\").__ne__, wakati))\n",
    "    return wakati"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train size (4419, 2)\n",
      "validation size (1474, 2)\n",
      "test size (1474, 2)\n"
     ]
    }
   ],
   "source": [
    "# カテゴリーをidに変換\n",
    "categories = news_df[\"category\"].unique().tolist()\n",
    "news_df[\"category_id\"] = news_df[\"category\"].map(lambda x: categories.index(x))\n",
    "\n",
    "# 元データを学習、検証、テストの3つに分割\n",
    "train_val_df, test_df = train_test_split(news_df[[\"body\", \"category_id\"]], train_size=0.8)\n",
    "train_df, val_df = train_test_split(train_val_df, train_size=0.75)\n",
    "\n",
    "print(\"train size\", train_df.shape)\n",
    "print(\"validation size\", val_df.shape)\n",
    "print(\"test size\", test_df.shape)\n",
    "\n",
    "# torchtext用にtsvファイルで保存\n",
    "train_df.to_csv(\"train.tsv\", sep=\"\\t\", index=False, header=None)\n",
    "val_df.to_csv(\"val.tsv\", sep=\"\\t\", index=False, header=None)\n",
    "test_df.to_csv(\"test.tsv\", sep=\"\\t\", index=False, header=None)\n",
    "\n",
    "TEXT = data.Field(sequential=True, tokenize=make_wakati, lower=False, batch_first=True, pad_token=\"<pad>\")\n",
    "LABEL = data.Field(sequential=False, use_vocab=False)\n",
    "\n",
    "train_data, val_data, test_data = data.TabularDataset.splits(path=\"/Users/xxxx/Desktop/\", train=\"train.tsv\", validation=\"val.tsv\", test=\"test.tsv\", format=\"tsv\", fields=[(\"Text\", TEXT), (\"Label\", LABEL)])\n",
    "\n",
    "# vocabulary生成\n",
    "# 学習データだけでvocabを作成します。\n",
    "TEXT.build_vocab(train_data, min_freq=1)\n",
    "\n",
    "BATCH_SIZE = 64\n",
    "train_loader = data.Iterator(train_data, batch_size=BATCH_SIZE, train=True)\n",
    "val_loader = data.Iterator(val_data, batch_size=BATCH_SIZE, train=False, sort=False)\n",
    "test_loader = data.Iterator(test_data, batch_size=BATCH_SIZE, train=False, sort=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Net(nn.Module):\n",
    "    def __init__(self, vocab_size, embedding_dim):\n",
    "        super(Net, self).__init__()\n",
    "        #単語分散表現はランダムベクトルを使う\n",
    "        self.embeddings = nn.Embedding(vocab_size, embedding_dim, padding_idx=TEXT.vocab.stoi[\"<pad>\"])\n",
    "        self.conv1 = nn.Conv2d(1, 2, kernel_size=(2, embedding_dim))\n",
    "        self.conv2 = nn.Conv2d(1, 2, kernel_size=(3, embedding_dim))\n",
    "        self.conv3 = nn.Conv2d(1, 2, kernel_size=(4, embedding_dim))\n",
    "        \n",
    "        #3つ畳み込み処理でそれぞれ2次元のベクトルが生成されるので、それらを全て結合して6次元のベクトルとなる\n",
    "        #liveddorのカテゴリは9つなので、アウトプットサイズは9を指定\n",
    "        self.linear = nn.Linear(6, 9)\n",
    "        \n",
    "    def forward(self, input_ids):\n",
    "        #(i) 文章の行列を取得\n",
    "        out = self.embeddings(input_ids)\n",
    "        # チャネル数1を挿入\n",
    "        out = out.unsqueeze(1)\n",
    "        \n",
    "        #(ii) 畳み込んでreluに通す\n",
    "        out1 = F.relu(self.conv1(out))\n",
    "        out2 = F.relu(self.conv2(out))\n",
    "        out3 = F.relu(self.conv3(out))\n",
    "        \n",
    "        #(iii) poolingして、各特徴マップの最大要素を取得\n",
    "        out1 = F.max_pool2d(out1, kernel_size=(out1.size()[2], 1))\n",
    "        out2 = F.max_pool2d(out2, kernel_size=(out2.size()[2], 1))\n",
    "        out3 = F.max_pool2d(out3, kernel_size=(out3.size()[2], 1))\n",
    "        \n",
    "        #(iv)viewして次元を整える\n",
    "        out1 = out1.view(-1, 2)\n",
    "        out2 = out2.view(-1, 2)\n",
    "        out3 = out3.view(-1, 2)\n",
    "        \n",
    "        #(v)全部結合して1本のベクトルにする\n",
    "        out = torch.cat([out1, out2, out3], dim=1)\n",
    "        \n",
    "        #(vi)全結合層で9つのカテゴリー分類できるように変換\n",
    "        out = self.linear(out)\n",
    "        \n",
    "        return out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 0 \ttrain loss 148.5192 \ttrain accuracy 0.2347 \tval loss 43.8903 \tval accuracy 0.4043\n",
      "time: 362.094066\n",
      "epoch 1 \ttrain loss 107.3851 \ttrain accuracy 0.535 \tval loss 31.9789 \tval accuracy 0.5936\n",
      "time: 358.88564099999985\n",
      "epoch 2 \ttrain loss 79.805 \ttrain accuracy 0.6766 \tval loss 24.9609 \tval accuracy 0.6913\n",
      "time: 352.265895\n",
      "epoch 3 \ttrain loss 64.3195 \ttrain accuracy 0.7461 \tval loss 21.1068 \tval accuracy 0.7252\n",
      "time: 366.32607200000007\n",
      "epoch 4 \ttrain loss 53.5912 \ttrain accuracy 0.7905 \tval loss 18.155 \tval accuracy 0.7612\n",
      "time: 362.6128030000002\n",
      "epoch 5 \ttrain loss 43.6401 \ttrain accuracy 0.8228 \tval loss 16.4422 \tval accuracy 0.7802\n",
      "time: 366.38400500000034\n",
      "epoch 6 \ttrain loss 39.1445 \ttrain accuracy 0.8592 \tval loss 14.9904 \tval accuracy 0.8005\n",
      "time: 356.82478200000014\n",
      "epoch 7 \ttrain loss 32.4194 \ttrain accuracy 0.8823 \tval loss 14.2734 \tval accuracy 0.8094\n",
      "time: 363.46790199999987\n",
      "epoch 8 \ttrain loss 28.7035 \ttrain accuracy 0.9016 \tval loss 13.5009 \tval accuracy 0.8202\n",
      "time: 358.15388299999995\n",
      "epoch 9 \ttrain loss 24.9321 \ttrain accuracy 0.9145 \tval loss 12.9508 \tval accuracy 0.8209\n",
      "time: 362.11623599999984\n",
      "epoch 10 \ttrain loss 20.6999 \ttrain accuracy 0.9335 \tval loss 12.581 \tval accuracy 0.8263\n",
      "time: 363.47333400000025\n",
      "epoch 11 \ttrain loss 18.2341 \ttrain accuracy 0.9443 \tval loss 12.122 \tval accuracy 0.8236\n",
      "time: 368.4358520000005\n",
      "epoch 12 \ttrain loss 16.1649 \ttrain accuracy 0.9525 \tval loss 11.8146 \tval accuracy 0.8412\n",
      "time: 360.1432559999994\n",
      "epoch 13 \ttrain loss 14.2326 \ttrain accuracy 0.9613 \tval loss 11.598 \tval accuracy 0.8412\n",
      "time: 374.934569\n",
      "epoch 14 \ttrain loss 12.0825 \ttrain accuracy 0.969 \tval loss 11.3908 \tval accuracy 0.8474\n",
      "time: 360.7733240000007\n",
      "epoch 15 \ttrain loss 10.4957 \ttrain accuracy 0.9737 \tval loss 11.2721 \tval accuracy 0.8487\n",
      "time: 366.32139500000085\n",
      "epoch 16 \ttrain loss 9.0818 \ttrain accuracy 0.981 \tval loss 11.2058 \tval accuracy 0.8548\n",
      "time: 362.1448909999999\n",
      "epoch 17 \ttrain loss 8.1702 \ttrain accuracy 0.981 \tval loss 11.1899 \tval accuracy 0.8535\n",
      "time: 361.6823430000004\n",
      "epoch 18 \ttrain loss 7.3026 \ttrain accuracy 0.9844 \tval loss 11.198 \tval accuracy 0.8507\n",
      "time: 359.6158379999997\n",
      "epoch 19 \ttrain loss 6.7623 \ttrain accuracy 0.9869 \tval loss 11.2555 \tval accuracy 0.8507\n",
      "time: 368.5266160000001\n"
     ]
    }
   ],
   "source": [
    "import torch.optim as optim\n",
    "\n",
    "VOCAB_SIZE = len(TEXT.vocab.stoi)\n",
    "EMBEDDING_DIM = 200\n",
    "\n",
    "net = Net(VOCAB_SIZE, EMBEDDING_DIM)\n",
    "\n",
    "loss_function = nn.CrossEntropyLoss()\n",
    "optimizer = optim.Adam(net.parameters(), lr=0.001)\n",
    "\n",
    "# 損失、精度を格納する配列を準備\n",
    "train_loss = []\n",
    "val_loss = []\n",
    "train_accuracy = []\n",
    "val_accuracy = []\n",
    "\n",
    "# グラフ反映用のリストを準備\n",
    "cnn_train_accuracy_list = []\n",
    "cnn_test_accuracy_list = []\n",
    "\n",
    "maxepoch = 20\n",
    "for epoch in range(maxepoch):\n",
    "    start_time = time.process_time()\n",
    "    \n",
    "    #学習\n",
    "    _train_loss = 0.0\n",
    "    _train_acc = 0.0\n",
    "    net.train()\n",
    "    for batch in train_loader:\n",
    "        inputs = batch.Text\n",
    "        y = batch.Label\n",
    "        optimizer.zero_grad()\n",
    "        out = net(inputs)\n",
    "        loss = loss_function(out, y)\n",
    "        _, preds = torch.max(out, 1)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        _train_loss += loss.item()\n",
    "        _train_acc += torch.sum(preds == y).item()\n",
    "    train_loss.append(_train_loss)\n",
    "    train_epoch_acc = _train_acc / len(train_loader.dataset)\n",
    "    train_accuracy.append(train_epoch_acc)\n",
    "    \n",
    "    #検証\n",
    "    _val_loss = 0.0\n",
    "    _val_acc = 0.0\n",
    "    net.eval()\n",
    "    with torch.no_grad():\n",
    "        for batch in val_loader:\n",
    "            inputs = batch.Text\n",
    "            y = batch.Label\n",
    "            out = net(inputs)\n",
    "            loss = loss_function(out, y)\n",
    "            _, preds = torch.max(out, 1)\n",
    "            _val_loss += loss.item()\n",
    "            _val_acc += torch.sum(preds == y).item()\n",
    "    val_loss.append(_val_loss)\n",
    "    val_epoch_acc = _val_acc / len(val_loader.dataset)\n",
    "    val_accuracy.append(val_epoch_acc)\n",
    "    \n",
    "    print(\"epoch\", epoch,\n",
    "         \"\\ttrain loss\", round(_train_loss, 4), \"\\ttrain accuracy\", round(train_epoch_acc, 4),\n",
    "         \"\\tval loss\", round(_val_loss, 4), \"\\tval accuracy\", round(val_epoch_acc, 4))\n",
    "    cnn_train_accuracy_list.append(round(train_epoch_acc, 4))\n",
    "    cnn_test_accuracy_list.append(round(val_epoch_acc, 4))\n",
    "    \n",
    "    end_time = time.process_time()\n",
    "    elapsed_time = end_time - start_time\n",
    "    print(\"time:\", elapsed_time)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "plt.plot(range(maxepoch), cnn_train_accuracy_list)\n",
    "plt.plot(range(maxepoch), cnn_test_accuracy_list)\n",
    "plt.legend([\"Train_accuracy\", \"Test_accuracy\"])\n",
    "plt.xlabel(\"#epoch\")\n",
    "plt.ylabel(\"Accuracy\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "●2つのモデルの精度比較"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "plt.plot(range(maxepoch), lstm_test_accuracy_list)\n",
    "plt.plot(range(maxepoch), cnn_test_accuracy_list)\n",
    "plt.legend([\"LSTM\", \"CNN\"])\n",
    "plt.xlabel(\"#epoch\")\n",
    "plt.ylabel(\"Accuracy\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "結果: 20epochが完了し、 LSTM: 0.66, CNN: 　0.85の精度となった。そのため、今回作成したモデルではCNNの方が良い精度となった。<br>\n",
    "考察:\n",
    "<ul>　\n",
    "<li>両モデルの学習データで、LSTM用のdatasets[\"title\"]とCNN用のnews_df[\"body\"]を比較すると、CNNの方がデータが大きい。その分学習時間を要するが、より深い学習ができたのではないかと考える</li>\n",
    "<li>両モデルについて、サイトを参考に作成し、ハイパーパラメータのチューニングは特に行っていないため、チューニングすることで精度の向上を図ることができると考える。\n",
    "LSTMの方が、調整できるパラメータが多いので、今回の精度0.66から伸び代は大きくあると考えており、特に単語の埋め込み次元をを増やすことでベクトルでの表現力が向上し精度もアップすると考える。</li>\n",
    "<li>作成したモデルは、dropout等の正則化は利用していない。そのため、正則化することで精度の向上を期待できる。</li>\n",
    "</ul>\n",
    "感想: LSTMよりCNNの方が、コードを理解しやすく、精度も高かった。学習時間はCNNの方が多く要したが、GPUを利用すれば問題ないように思えた。そのため、初心者が同様のタスクを行おうとするならば、CNNを選んだ方が良いように思った。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>title</th>\n",
       "      <th>category</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>【DVDエンター！】誘拐犯に育てられた女が目にした真実は、孤独か幸福か\\n</td>\n",
       "      <td>movie-enter</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>藤原竜也、中学生とともにロケット打ち上げに成功\\n</td>\n",
       "      <td>movie-enter</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>『戦火の馬』ロイヤル・プレミアにウィリアム王子＆キャサリン妃が出席\\n</td>\n",
       "      <td>movie-enter</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>香里奈、女子高生100人のガチンコ質問に回答「ラーメンも食べる」\\n</td>\n",
       "      <td>movie-enter</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>ユージの前に立ちはだかったJOY「僕はAKBの高橋みなみを守る」\\n</td>\n",
       "      <td>movie-enter</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                   title     category\n",
       "0  【DVDエンター！】誘拐犯に育てられた女が目にした真実は、孤独か幸福か\\n  movie-enter\n",
       "1              藤原竜也、中学生とともにロケット打ち上げに成功\\n  movie-enter\n",
       "2    『戦火の馬』ロイヤル・プレミアにウィリアム王子＆キャサリン妃が出席\\n  movie-enter\n",
       "3     香里奈、女子高生100人のガチンコ質問に回答「ラーメンも食べる」\\n  movie-enter\n",
       "4     ユージの前に立ちはだかったJOY「僕はAKBの高橋みなみを守る」\\n  movie-enter"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "datasets.head() # 参考 datasetsの中身"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>body</th>\n",
       "      <th>category</th>\n",
       "      <th>category_id</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>【DVDエンター！】誘拐犯に育てられた女が目にした真実は、孤独か幸福か2005年11月から翌...</td>\n",
       "      <td>movie-enter</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>藤原竜也、中学生とともにロケット打ち上げに成功「アンテナを張りながら生活をしていけばいい」2...</td>\n",
       "      <td>movie-enter</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>『戦火の馬』ロイヤル・プレミアにウィリアム王子＆キャサリン妃が出席3月2日より全国ロードショ...</td>\n",
       "      <td>movie-enter</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>香里奈、女子高生100人のガチンコ質問に回答「ラーメンも食べる」女優の香里奈が18日、都内で...</td>\n",
       "      <td>movie-enter</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>ユージの前に立ちはだかったJOY「僕はAKBの高橋みなみを守る」5日、東京・千代田区の内幸町...</td>\n",
       "      <td>movie-enter</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                body     category  category_id\n",
       "0  【DVDエンター！】誘拐犯に育てられた女が目にした真実は、孤独か幸福か2005年11月から翌...  movie-enter            0\n",
       "1  藤原竜也、中学生とともにロケット打ち上げに成功「アンテナを張りながら生活をしていけばいい」2...  movie-enter            0\n",
       "2  『戦火の馬』ロイヤル・プレミアにウィリアム王子＆キャサリン妃が出席3月2日より全国ロードショ...  movie-enter            0\n",
       "3  香里奈、女子高生100人のガチンコ質問に回答「ラーメンも食べる」女優の香里奈が18日、都内で...  movie-enter            0\n",
       "4  ユージの前に立ちはだかったJOY「僕はAKBの高橋みなみを守る」5日、東京・千代田区の内幸町...  movie-enter            0"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "news_df.head() #参考 news_dfの中身"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "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.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
