<commit_before>{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introduction\n",
    "[Chainer](http://chainer.org/) とはニューラルネットの実装を簡単にしたフレームワークです。\n",
    "\n",
    "* 今回は言語の分野でニューラルネットを適用してみました。\n",
    "\n",
    "![](./pictures/Chainer.jpg)\n",
    "\n",
    "* 今回は言語モデルを作成していただきます。\n",
    "\n",
    "\n",
    "言語モデルとはある単語が来たときに次の単語に何が来やすいかを予測するものです。\n",
    "\n",
    "言語モデルにはいくつか種類があるのでここでも紹介しておきます。\n",
    "\n",
    "* n-グラム言語モデル\n",
    " * 単語の数を単純に数え挙げて作成されるモデル。考え方としてはデータにおけるある単語の頻度に近い\n",
    "* ニューラル言語モデル\n",
    " * 単語の辞書ベクトルを潜在空間ベクトルに落とし込み、ニューラルネットで次の文字を学習させる手法\n",
    "\n",
    "* リカレントニューラル言語モデル\n",
    " * 基本的なアルゴリズムはニューラル言語モデルと同一だが過去に使用した単語を入力に加えることによって文脈を考慮した言語モデルの学習が可能となる。ニューラル言語モデルとは異なり、より古い情報も取得可能\n",
    "\n",
    "以下では、このChainerを利用しデータを準備するところから実際に言語モデルを構築し学習・評価を行うまでの手順を解説します。\n",
    "\n",
    "1. [各種ライブラリ導入](#各種ライブラリ導入) \n",
    "2. [初期設定](#初期設定) \n",
    "3. [データ入力](#データ入力)\n",
    "4. [リカレントニューラル言語モデル設定](#リカレントニューラル言語モデル設定) \n",
    "5. [学習を始める前の設定](#学習を始める前の設定)\n",
    "6. [パラメータ更新方法（確率的勾配法）](#パラメータ更新方法（確率的勾配法）)\n",
    "7. [言語の予測](#言語の予測)\n",
    "\n",
    "もしGPUを使用したい方は、以下にまとめてあるのでご参考ください。\n",
    "\n",
    "[Chainer を用いてリカレントニューラル言語モデル作成のサンプルコードを解説してみた](http://qiita.com/GushiSnow/private/b34da4962dd930d1487a)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 各種ライブラリ導入"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Chainerの言語処理では多数のライブラリを導入します。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import time\n",
    "import math\n",
    "import sys\n",
    "import pickle\n",
    "import copy\n",
    "import os\n",
    "\n",
    "import numpy as np\n",
    "from chainer import cuda, Variable, FunctionSet, optimizers\n",
    "import chainer.functions as F"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`導入するライブラリの代表例は下記です。\n",
    "\n",
    "* `numpy`: 行列計算などの複雑な計算を行なうライブラリ\n",
    "* `chainer`: Chainerの導入\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 初期設定"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 学習回数、ユニット数、確率的勾配法に使用するデータの数、学習に使用する文字列の長さ、勾配法で使用する敷居値、学習データの格納場所、モデルの出力場所を設定しています。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "#-------------Explain7 in the Qiita-------------\n",
    "n_epochs    = 10\n",
    "n_units     = 128\n",
    "batchsize   = 50\n",
    "bprop_len   = 50\n",
    "grad_clip   = 5\n",
    "data_dir = \"data_hands_on\"\n",
    "checkpoint_dir = \"cv\"\n",
    "#-------------Explain7 in the Qiita-------------"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## データ入力"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "学習用にダウンロードしたファイルをプログラムに読ませる処理を関数化しています\n",
    "\n",
    "* 学習データをバイナリ形式で読み込んでいます。\n",
    "* 文字データを確保するための行列を定義しています。\n",
    "* データを単語をキー、長さを値とした辞書データにして行列データセットに登録しています。\n",
    "\n",
    "学習データ、単語の長さ、語彙数を取得しています。\n",
    "上記をそれぞれ行列データとして保持しています。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# input data\n",
    "#-------------Explain1 in the Qiita-------------\n",
    "def load_data():\n",
    "    vocab = {}\n",
    "    print ('%s/linux_source.c'% data_dir)\n",
    "    words = open('%s/linux_source.c' % data_dir, 'rb').read()\n",
    "    words = list(words)\n",
    "    dataset = np.ndarray((len(words),), dtype=np.int32)\n",
    "    for i, word in enumerate(words):\n",
    "        if word not in vocab:\n",
    "            vocab[word] = len(vocab)\n",
    "        dataset[i] = vocab[word]\n",
    "    print('corpus length:', len(words))\n",
    "    print('vocab size:', len(vocab))\n",
    "    return dataset, words, vocab\n",
    "#-------------Explain1 in the Qiita-------------\n",
    "\n",
    "if not os.path.exists(checkpoint_dir):\n",
    "    os.mkdir(checkpoint_dir)\n",
    "    \n",
    "train_data, words, vocab = load_data()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## リカレントニューラル言語モデル設定"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "RNNLM(リカレントニューラル言語モデルの設定を行っています）\n",
    "\n",
    "* EmbedIDで行列変換を行い、疎なベクトルを密なベクトルに変換しています。\n",
    "* 出力が4倍の理由は入力層、出力層、忘却層、前回の出力をLSTMでは入力に使用するためです。\n",
    "* 隠れ層に前回保持した隠れ層の状態を入力することによってLSTMを実現しています。\n",
    "* ドロップアウトにより過学習するのを抑えています。\n",
    "* 予測を行なうメソッドも実装しており、入力されたデータ、状態を元に次の文字列と状態を返すような関数になっています。\n",
    "* モデルの初期化を行なう関数もここで定義しています。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "class CharRNN(FunctionSet):\n",
    "\n",
    "#-------------Explain2 in the Qiita-------------\n",
    "    def __init__(self, n_vocab, n_units):\n",
    "        super(CharRNN, self).__init__(\n",
    "            embed = F.EmbedID(n_vocab, n_units),\n",
    "            l1_x = F.Linear(n_units, 4*n_units),\n",
    "            l1_h = F.Linear(n_units, 4*n_units),\n",
    "            l2_h = F.Linear(n_units, 4*n_units),\n",
    "            l2_x = F.Linear(n_units, 4*n_units),\n",
    "            l3   = F.Linear(n_units, n_vocab),\n",
    "        )\n",
    "        for param in self.parameters:\n",
    "            param[:] = np.random.uniform(-0.08, 0.08, param.shape)\n",
    "\n",
    "    def forward_one_step(self, x_data, y_data, state, train=True, dropout_ratio=0.5):\n",
    "        x = Variable(x_data, volatile=not train)\n",
    "        t = Variable(y_data, volatile=not train)\n",
    "\n",
    "        h0      = self.embed(x)\n",
    "        h1_in   = self.l1_x(F.dropout(h0, ratio=dropout_ratio, train=train)) + self.l1_h(state['h1'])\n",
    "        c1, h1  = F.lstm(state['c1'], h1_in)\n",
    "        h2_in   = self.l2_x(F.dropout(h1, ratio=dropout_ratio, train=train)) + self.l2_h(state['h2'])\n",
    "        c2, h2  = F.lstm(state['c2'], h2_in)\n",
    "        y       = self.l3(F.dropout(h2, ratio=dropout_ratio, train=train))\n",
    "        state   = {'c1': c1, 'h1': h1, 'c2': c2, 'h2': h2}\n",
    "\n",
    "        return state, F.softmax_cross_entropy(y, t)\n",
    "#-------------Explain2 in the Qiita-------------\n",
    "\n",
    "    def predict(self, x_data, state):\n",
    "        x = Variable(x_data, volatile=True)\n",
    "\n",
    "        h0      = self.embed(x)\n",
    "        h1_in   = self.l1_x(h0) + self.l1_h(state['h1'])\n",
    "        c1, h1  = F.lstm(state['c1'], h1_in)\n",
    "        h2_in   = self.l2_x(h1) + self.l2_h(state['h2'])\n",
    "        c2, h2  = F.lstm(state['c2'], h2_in)\n",
    "        y       = self.l3(h2)\n",
    "        state   = {'c1': c1, 'h1': h1, 'c2': c2, 'h2': h2}\n",
    "\n",
    "        return state, F.softmax(y)\n",
    "\n",
    "def make_initial_state(n_units, batchsize=50, train=True):\n",
    "    return {name: Variable(np.zeros((batchsize, n_units), dtype=np.float32),\n",
    "            volatile=not train)\n",
    "            for name in ('c1', 'h1', 'c2', 'h2')}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "RNNLM(リカレントニューラル言語モデルの設定を行っています）\n",
    "\n",
    "* 作成したリカレントニューラル言語モデルを導入しています。\n",
    "* 最適化の手法はRMSpropを使用\n",
    "http://qiita.com/skitaoka/items/e6afbe238cd69c899b2a\n",
    "* 初期のパラメータを-0.1〜0.1の間で与えています。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Prepare RNNLM model\n",
    "model = CharRNN(len(vocab), n_units)\n",
    "\n",
    "optimizer = optimizers.RMSprop(lr=2e-3, alpha=0.95, eps=1e-8)\n",
    "optimizer.setup(model.collect_parameters())\n",
    "\n",
    "print(\"*-----------------------------------model----------------------------------*\")\n",
    "print(dir(model))\n",
    "print(\"*----------------------------------embed-----------------------------------*\")\n",
    "print(dir(model.embed))\n",
    "print(\"*--------------------------------model l1_x--------------------------------*\")\n",
    "print(dir(model.l1_x.W))\n",
    "print(model.l1_x.label)\n",
    "print(model.l1_x.parameter_names)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 学習を始める前の設定"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 学習データのサイズを取得 \n",
    "* ジャンプの幅を設定（順次学習しない）\n",
    "* パープレキシティを0で初期化 \n",
    "* 最初の時間情報を取得 \n",
    "* 初期状態を現在の状態に付与 \n",
    "* 状態の初期化 \n",
    "* 損失を0で初期化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "whole_len    = train_data.shape[0]\n",
    "jump         = whole_len / batchsize\n",
    "epoch        = 0\n",
    "start_at     = time.time()\n",
    "cur_at       = start_at\n",
    "state        = make_initial_state(n_units, batchsize=batchsize)\n",
    "accum_loss   = Variable(np.zeros((), dtype=np.float32))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## パラメータ更新方法（確率的勾配法）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 確率的勾配法を用いて学習している。\n",
    "* 一定のデータを選択し損失計算をしながらパラメータ更新をしている。\n",
    "* 逐次尤度の計算も行っている。\n",
    "\n",
    "* 適宜学習データのパープレキシティも計算している\n",
    "\n",
    "* バックプロパゲーションでパラメータを更新する。\n",
    "* [truncate](http://kiyukuta.github.io/2013/12/09/mlac2013_day9_recurrent_neural_network_language_model.html#recurrent-neural-network)はどれだけ過去の履歴を見るかを表している。\n",
    "* optimizer.clip_gradsの部分でL2正則化をかけている。\n",
    "* 過学習を抑えるために学習効率を徐々に下げている。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "for i in range(int(jump * n_epochs)):\n",
    "    #-------------Explain3 in the Qiita-------------\n",
    "    x_batch = np.array([train_data[(jump * j + i) % whole_len]\n",
    "                        for j in range(batchsize)])\n",
    "    y_batch = np.array([train_data[(jump * j + i + 1) % whole_len]\n",
    "                        for j in range(batchsize)])\n",
    "\n",
    "    state, loss_i = model.forward_one_step(x_batch, y_batch, state, dropout_ratio=0.5)\n",
    "    accum_loss   += loss_i\n",
    "\n",
    "    if (i + 1) % bprop_len == 0:  # Run truncated BPTT\n",
    "        now = time.time()\n",
    "        print('{}/{}, train_loss = {}, time = {:.2f}'.format((i+1)/bprop_len, jump, accum_loss.data / bprop_len, now-cur_at))\n",
    "        cur_at = now\n",
    "\n",
    "        optimizer.zero_grads()\n",
    "        accum_loss.backward()\n",
    "        accum_loss.unchain_backward()  # truncate\n",
    "        accum_loss = Variable(np.zeros((), dtype=np.float32))\n",
    "\n",
    "        optimizer.clip_grads(grad_clip)\n",
    "        optimizer.update()\n",
    "\n",
    "    if (i + 1) % 10000 == 0:    \n",
    "        fn = ('%s/charrnn_epoch_%.2f.chainermodel' % (checkpoint_dir, float(i)/jump))\n",
    "        pickle.dump(copy.deepcopy(model).to_cpu(), open(fn, 'wb'))\n",
    "\n",
    "    if (i + 1) % jump == 0:\n",
    "        epoch += 1\n",
    "\n",
    "        if epoch >= 10:\n",
    "            optimizer.lr *= 0.97\n",
    "            print('decayed learning rate by a factor {} to {}'.format(0.97, optimizer.lr))\n",
    "    #-------------Explain3 in the Qiita-------------\n",
    "\n",
    "    sys.stdout.flush()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 言語の予測"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 学習したデータを再度入力\n",
    "* 入力データを辞書として保持\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "np.random.seed(123)\n",
    "\n",
    "# load vocabulary\n",
    "vocab = {}\n",
    "#-------------Explain4 in the Qiita-------------\n",
    "def load_predict_data(filename):\n",
    "    global vocab, n_vocab\n",
    "    #words = open(filename).read().replace('\\n', '<eos>').strip().split()\n",
    "    words = open(filename).read().strip().split()\n",
    "    dataset = np.ndarray((len(words),), dtype=np.int32)\n",
    "    for i, word in enumerate(words):\n",
    "        if word not in vocab:\n",
    "            vocab[word] = len(vocab)\n",
    "        dataset[i] = vocab[word]\n",
    "    return dataset\n",
    "\n",
    "train_data = load_predict_data('data_hands_on/linux_source.c')\n",
    "\n",
    "ivocab = {}\n",
    "ivocab = {v:k for k, v in vocab.items()}\n",
    "#-------------Explain4 in the Qiita-------------\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "* 学習したモデルを取得\n",
    "* モデルからユニット数を取得\n",
    "* 最初の空文字を設定"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# load model\n",
    "#-------------Explain5 in the Qiita-------------\n",
    "model = pickle.load(open(\"cv/charrnn_epoch_9.45.chainermodel\", 'rb'))\n",
    "#-------------Explain5 in the Qiita-------------\n",
    "n_units = model.embed.W.shape[1]\n",
    "\n",
    "# initialize generator\n",
    "state = make_initial_state(n_units, batchsize=1, train=False)\n",
    "\n",
    "prev_char = np.array([0], dtype=np.int32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 学習したモデルを利用して文字の予測を行なう。\n",
    "* 予測で出力された文字と状態を次の入力に使用する。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "for i in range(1000):\n",
    "    #-------------Explain6 in the Qiita-------------\n",
    "    state, prob = model.predict(prev_char, state)\n",
    "\n",
    "    index = np.argmax(cuda.to_cpu(prob.data))\n",
    "    sys.stdout.write(ivocab[index] + \" \")\n",
    "\n",
    "    prev_char = np.array([index], dtype=np.int32)\n",
    "    #-------------Explain6 in the Qiita-------------\n",
    "\n",
    "print"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }<commit_msg>chainer hands on<commit_after>{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introduction\n",
    "[Chainer](http://chainer.org/) とはニューラルネットの実装を簡単にしたフレームワークです。\n",
    "\n",
    "* 今回は言語の分野でニューラルネットを適用してみました。\n",
    "\n",
    "![](./pictures/Chainer.jpg)\n",
    "\n",
    "* 今回は言語モデルを作成していただきます。\n",
    "\n",
    "\n",
    "言語モデルとはある単語が来たときに次の単語に何が来やすいかを予測するものです。\n",
    "\n",
    "言語モデルにはいくつか種類があるのでここでも紹介しておきます。\n",
    "\n",
    "* n-グラム言語モデル\n",
    " * 単語の数を単純に数え挙げて作成されるモデル。考え方としてはデータにおけるある単語の頻度に近い\n",
    "* ニューラル言語モデル\n",
    " * 単語の辞書ベクトルを潜在空間ベクトルに落とし込み、ニューラルネットで次の文字を学習させる手法\n",
    "\n",
    "* リカレントニューラル言語モデル\n",
    " * 基本的なアルゴリズムはニューラル言語モデルと同一だが過去に使用した単語を入力に加えることによって文脈を考慮した言語モデルの学習が可能となる。ニューラル言語モデルとは異なり、より古い情報も取得可能\n",
    "\n",
    "以下では、このChainerを利用しデータを準備するところから実際に言語モデルを構築し学習・評価を行うまでの手順を解説します。\n",
    "\n",
    "1. [各種ライブラリ導入](#各種ライブラリ導入) \n",
    "2. [初期設定](#初期設定) \n",
    "3. [データ入力](#データ入力)\n",
    "4. [リカレントニューラル言語モデル設定](#リカレントニューラル言語モデル設定) \n",
    "5. [学習を始める前の設定](#学習を始める前の設定)\n",
    "6. [パラメータ更新方法（確率的勾配法）](#パラメータ更新方法（確率的勾配法）)\n",
    "7. [言語の予測](#言語の予測)\n",
    "\n",
    "もしGPUを使用したい方は、以下にまとめてあるのでご参考ください。\n",
    "\n",
    "[Chainer を用いてリカレントニューラル言語モデル作成のサンプルコードを解説してみた](http://qiita.com/GushiSnow/private/b34da4962dd930d1487a)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 各種ライブラリ導入"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Chainerの言語処理では多数のライブラリを導入します。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import time\n",
    "import math\n",
    "import sys\n",
    "import pickle\n",
    "import copy\n",
    "import os\n",
    "\n",
    "import numpy as np\n",
    "from chainer import cuda, Variable, FunctionSet, optimizers\n",
    "import chainer.functions as F"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`導入するライブラリの代表例は下記です。\n",
    "\n",
    "* `numpy`: 行列計算などの複雑な計算を行なうライブラリ\n",
    "* `chainer`: Chainerの導入\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 初期設定"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 学習回数、ユニット数、確率的勾配法に使用するデータの数、学習に使用する文字列の長さ、勾配法で使用する敷居値、学習データの格納場所、モデルの出力場所を設定しています。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "#-------------Explain7 in the Qiita-------------\n",
    "n_epochs    = 10\n",
    "n_units     = 128\n",
    "batchsize   = 50\n",
    "bprop_len   = 50\n",
    "grad_clip   = 5\n",
    "data_dir = \"data_hands_on\"\n",
    "checkpoint_dir = \"cv\"\n",
    "#-------------Explain7 in the Qiita-------------"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## データ入力"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "学習用にダウンロードしたファイルをプログラムに読ませる処理を関数化しています\n",
    "\n",
    "* 学習データをバイナリ形式で読み込んでいます。\n",
    "* 文字データを確保するための行列を定義しています。\n",
    "* データを単語をキー、長さを値とした辞書データにして行列データセットに登録しています。\n",
    "\n",
    "学習データ、単語の長さ、語彙数を取得しています。\n",
    "上記をそれぞれ行列データとして保持しています。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# input data\n",
    "#-------------Explain1 in the Qiita-------------\n",
    "def load_data():\n",
    "    vocab = {}\n",
    "    print ('%s/linux_source.c'% data_dir)\n",
    "    words = open('%s/linux_source.c' % data_dir, 'rb').read()\n",
    "    words = list(words)\n",
    "    dataset = np.ndarray((len(words),), dtype=np.int32)\n",
    "    for i, word in enumerate(words):\n",
    "        if word not in vocab:\n",
    "            vocab[word] = len(vocab)\n",
    "        dataset[i] = vocab[word]\n",
    "    print('corpus length:', len(words))\n",
    "    print('vocab size:', len(vocab))\n",
    "    return dataset, words, vocab\n",
    "#-------------Explain1 in the Qiita-------------\n",
    "\n",
    "if not os.path.exists(checkpoint_dir):\n",
    "    os.mkdir(checkpoint_dir)\n",
    "    \n",
    "train_data, words, vocab = load_data()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## リカレントニューラル言語モデル設定"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "RNNLM(リカレントニューラル言語モデルの設定を行っています）\n",
    "\n",
    "* EmbedIDで行列変換を行い、疎なベクトルを密なベクトルに変換しています。\n",
    "* 出力が4倍の理由は入力層、出力層、忘却層、前回の出力をLSTMでは入力に使用するためです。\n",
    "* 隠れ層に前回保持した隠れ層の状態を入力することによってLSTMを実現しています。\n",
    "* ドロップアウトにより過学習するのを抑えています。\n",
    "* 予測を行なうメソッドも実装しており、入力されたデータ、状態を元に次の文字列と状態を返すような関数になっています。\n",
    "* モデルの初期化を行なう関数もここで定義しています。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "class CharRNN(FunctionSet):\n",
    "\n",
    "#-------------Explain2 in the Qiita-------------\n",
    "    def __init__(self, n_vocab, n_units):\n",
    "        super(CharRNN, self).__init__(\n",
    "            embed = F.EmbedID(n_vocab, n_units),\n",
    "            l1_x = F.Linear(n_units, 4*n_units),\n",
    "            l1_h = F.Linear(n_units, 4*n_units),\n",
    "            l2_h = F.Linear(n_units, 4*n_units),\n",
    "            l2_x = F.Linear(n_units, 4*n_units),\n",
    "            l3   = F.Linear(n_units, n_vocab),\n",
    "        )\n",
    "        for param in self.parameters:\n",
    "            param[:] = np.random.uniform(-0.08, 0.08, param.shape)\n",
    "\n",
    "    def forward_one_step(self, x_data, y_data, state, train=True, dropout_ratio=0.5):\n",
    "        x = Variable(x_data, volatile=not train)\n",
    "        t = Variable(y_data, volatile=not train)\n",
    "\n",
    "        h0      = self.embed(x)\n",
    "        h1_in   = self.l1_x(F.dropout(h0, ratio=dropout_ratio, train=train)) + self.l1_h(state['h1'])\n",
    "        c1, h1  = F.lstm(state['c1'], h1_in)\n",
    "        h2_in   = self.l2_x(F.dropout(h1, ratio=dropout_ratio, train=train)) + self.l2_h(state['h2'])\n",
    "        c2, h2  = F.lstm(state['c2'], h2_in)\n",
    "        y       = self.l3(F.dropout(h2, ratio=dropout_ratio, train=train))\n",
    "        state   = {'c1': c1, 'h1': h1, 'c2': c2, 'h2': h2}\n",
    "\n",
    "        return state, F.softmax_cross_entropy(y, t)\n",
    "#-------------Explain2 in the Qiita-------------\n",
    "\n",
    "    def predict(self, x_data, state):\n",
    "        x = Variable(x_data, volatile=True)\n",
    "\n",
    "        h0      = self.embed(x)\n",
    "        h1_in   = self.l1_x(h0) + self.l1_h(state['h1'])\n",
    "        c1, h1  = F.lstm(state['c1'], h1_in)\n",
    "        h2_in   = self.l2_x(h1) + self.l2_h(state['h2'])\n",
    "        c2, h2  = F.lstm(state['c2'], h2_in)\n",
    "        y       = self.l3(h2)\n",
    "        state   = {'c1': c1, 'h1': h1, 'c2': c2, 'h2': h2}\n",
    "\n",
    "        return state, F.softmax(y)\n",
    "\n",
    "def make_initial_state(n_units, batchsize=50, train=True):\n",
    "    return {name: Variable(np.zeros((batchsize, n_units), dtype=np.float32),\n",
    "            volatile=not train)\n",
    "            for name in ('c1', 'h1', 'c2', 'h2')}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "RNNLM(リカレントニューラル言語モデルの設定を行っています）\n",
    "\n",
    "* 作成したリカレントニューラル言語モデルを導入しています。\n",
    "* 最適化の手法はRMSpropを使用\n",
    "http://qiita.com/skitaoka/items/e6afbe238cd69c899b2a\n",
    "* 初期のパラメータを-0.1〜0.1の間で与えています。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Prepare RNNLM model\n",
    "model = CharRNN(len(vocab), n_units)\n",
    "\n",
    "optimizer = optimizers.RMSprop(lr=2e-3, alpha=0.95, eps=1e-8)\n",
    "optimizer.setup(model.collect_parameters())\n",
    "\n",
    "#-------------Explain3 in the Qiita-------------\n",
    "print(\"*-----------------------------------model----------------------------------*\")\n",
    "print(dir(model))\n",
    "print(\"*----------------------------------embed-----------------------------------*\")\n",
    "print(dir(model.embed))\n",
    "print(\"*--------------------------------model l1_x--------------------------------*\")\n",
    "print(dir(model.l1_x.W))\n",
    "print(model.l1_x.label)\n",
    "#-------------Explain3 in the Qiita-------------"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 学習を始める前の設定"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 学習データのサイズを取得 \n",
    "* ジャンプの幅を設定（順次学習しない）\n",
    "* パープレキシティを0で初期化 \n",
    "* 最初の時間情報を取得 \n",
    "* 初期状態を現在の状態に付与 \n",
    "* 状態の初期化 \n",
    "* 損失を0で初期化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "whole_len    = train_data.shape[0]\n",
    "jump         = whole_len / batchsize\n",
    "epoch        = 0\n",
    "start_at     = time.time()\n",
    "cur_at       = start_at\n",
    "state        = make_initial_state(n_units, batchsize=batchsize)\n",
    "accum_loss   = Variable(np.zeros((), dtype=np.float32))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## パラメータ更新方法（確率的勾配法）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 確率的勾配法を用いて学習している。\n",
    "* 一定のデータを選択し損失計算をしながらパラメータ更新をしている。\n",
    "* 逐次尤度の計算も行っている。\n",
    "\n",
    "* 適宜学習データのパープレキシティも計算している\n",
    "\n",
    "* バックプロパゲーションでパラメータを更新する。\n",
    "* [truncate](http://kiyukuta.github.io/2013/12/09/mlac2013_day9_recurrent_neural_network_language_model.html#recurrent-neural-network)はどれだけ過去の履歴を見るかを表している。\n",
    "* optimizer.clip_gradsの部分でL2正則化をかけている。\n",
    "* 過学習を抑えるために学習効率を徐々に下げている。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "for i in range(int(jump * n_epochs)):\n",
    "    #-------------Explain4 in the Qiita-------------\n",
    "    x_batch = np.array([train_data[(jump * j + i) % whole_len]\n",
    "                        for j in range(batchsize)])\n",
    "    y_batch = np.array([train_data[(jump * j + i + 1) % whole_len]\n",
    "                        for j in range(batchsize)])\n",
    "\n",
    "    state, loss_i = model.forward_one_step(x_batch, y_batch, state, dropout_ratio=0.5)\n",
    "    accum_loss   += loss_i\n",
    "\n",
    "    if (i + 1) % bprop_len == 0:  # Run truncated BPTT\n",
    "        now = time.time()\n",
    "        print('{}/{}, train_loss = {}, time = {:.2f}'.format((i+1)/bprop_len, jump, accum_loss.data / bprop_len, now-cur_at))\n",
    "        cur_at = now\n",
    "\n",
    "        optimizer.zero_grads()\n",
    "        accum_loss.backward()\n",
    "        accum_loss.unchain_backward()  # truncate\n",
    "        accum_loss = Variable(np.zeros((), dtype=np.float32))\n",
    "\n",
    "        optimizer.clip_grads(grad_clip)\n",
    "        optimizer.update()\n",
    "\n",
    "    if (i + 1) % 10000 == 0:    \n",
    "        fn = ('%s/charrnn_epoch_%.2f.chainermodel' % (checkpoint_dir, float(i)/jump))\n",
    "        pickle.dump(copy.deepcopy(model).to_cpu(), open(fn, 'wb'))\n",
    "\n",
    "    if (i + 1) % jump == 0:\n",
    "        epoch += 1\n",
    "\n",
    "        if epoch >= 10:\n",
    "            optimizer.lr *= 0.97\n",
    "            print('decayed learning rate by a factor {} to {}'.format(0.97, optimizer.lr))\n",
    "    #-------------Explain4 in the Qiita-------------\n",
    "\n",
    "    sys.stdout.flush()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 言語の予測"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 学習したデータを再度入力\n",
    "* 入力データを辞書として保持\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "np.random.seed(123)\n",
    "\n",
    "# load vocabulary\n",
    "vocab = {}\n",
    "#-------------Explain5 in the Qiita-------------\n",
    "def load_predict_data(filename):\n",
    "    global vocab, n_vocab\n",
    "    #words = open(filename).read().replace('\\n', '<eos>').strip().split()\n",
    "    words = open(filename).read().strip().split()\n",
    "    dataset = np.ndarray((len(words),), dtype=np.int32)\n",
    "    for i, word in enumerate(words):\n",
    "        if word not in vocab:\n",
    "            vocab[word] = len(vocab)\n",
    "        dataset[i] = vocab[word]\n",
    "    return dataset\n",
    "\n",
    "train_data = load_predict_data('data_hands_on/linux_source.c')\n",
    "\n",
    "ivocab = {}\n",
    "ivocab = {v:k for k, v in vocab.items()}\n",
    "#-------------Explain5 in the Qiita-------------\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "* 学習したモデルを取得\n",
    "* モデルからユニット数を取得\n",
    "* 最初の空文字を設定"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# load model\n",
    "#-------------Explain6 in the Qiita-------------\n",
    "model = pickle.load(open(\"cv/charrnn_epoch_9.45.chainermodel\", 'rb'))\n",
    "#-------------Explain6 in the Qiita-------------\n",
    "n_units = model.embed.W.shape[1]\n",
    "\n",
    "# initialize generator\n",
    "state = make_initial_state(n_units, batchsize=1, train=False)\n",
    "\n",
    "prev_char = np.array([0], dtype=np.int32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 学習したモデルを利用して文字の予測を行なう。\n",
    "* 予測で出力された文字と状態を次の入力に使用する。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "for i in range(1000):\n",
    "    #-------------Explain7 in the Qiita-------------\n",
    "    state, prob = model.predict(prev_char, state)\n",
    "\n",
    "    index = np.argmax(cuda.to_cpu(prob.data))\n",
    "    sys.stdout.write(ivocab[index] + \" \")\n",
    "\n",
    "    prev_char = np.array([index], dtype=np.int32)\n",
    "    #-------------Explain7 in the Qiita-------------\n",
    "\n",
    "print"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
{'fraction_non_alphanumeric': 0.24318073600435344, 'fraction_numerical': 0.015135024828242976, 'mean_word_length': 2.088874881815317, 'pattern_counts': {'":': 258, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7136799', 'n_tokens_mistral': 14465, 'n_tokens_neox': 14068, 'n_words': 1671}