にほんごのれんしゅう

日本語として伝えるための訓練を兼ねたテクログ

新しく買ったWindows 10でneologd等の自然言語処理環境を構築する (+ XGBoost)

新しく買ったWindows 10でneologd等の自然言語処理環境を構築する (+ XGBoost)

はじめに

f:id:catindog:20170319214656p:plain

 自然言語処理機械学習とSE的なことを仕事としているのですが、現在務めている会社ではWindowsの利用を強く推奨されることがあります。これは、コンプラインスの関係でセキュリティ管理者が複数のOSを管理するコストは高いからWindowsに限定することが多いという理由に起因しています。
 この文章を書いている環境はUbuntu LinuxLibreofficeを用いています。文字コードUTF-8だと自然言語処理の観点で何かと都合がよく、MeCabなどの形態素解析エンジンもUTF-8ですし、Python3も内部の処理系がUTF-8で、GolangのRuneや、C++も文字処理粒度はUTF-8のものが増えています。
 こういう関係もあって、sjisをハックするなど、あまり技術的にこったことをしたくないというモチベーションのもと、Windows形態素解析と、みんな大好きXGBoostを導入するまでの一連の流れを示したいと思います。
 ツール属性の攻撃という言葉があって、これが案外馬鹿にできないというか、かなりの攻撃力を誇るので、できるだけ覚えるコスト下げたいという思いもあります。
 (この方法は2017年3月段階での、私がいじくってなんとか動かした方法ですので、正しく理解するには公式ドキュメントも注意深く参照することをおすすめします)

計画

1. Windows 10の持ち運べるノートパソコンであるLG gramの購入と、アップグレード
2. OSをNVMeに移動する
3. 日本語が処理できるようにcmdやcmderの代わりにputty, teratermやcygwind sshを利用する
4. mecabとneologdの導入
5. Anacondaとjupyter notebookの構築
6. XGBoostの導入

1. Windows 10の持ち運べるノートパソコンであるLG gramの購入と、アップグレード

 本当はMacBook Proがほしいのだが、2017年3月、もっとも高いメモリのモデルでも16GByteまでしかなく、加えて8GByte → 16GByteに変更するのに20000円もかかる。自分で取り替えられる場合、12000円程度ですむ。
 MacBookには、NVMeモデルがない。NVMeはSSDより高速な主記憶装置で、ディスクに大量にアクセスする操作が発生することが多い機械学習ではNVMeであるだけで、かなり処理時間が短縮できることがある。スワップ領域等を作成しておくと、メモリがあふれた時にもマシンが処理落ちして使い物にならなくなる時間が短縮できて大変よい。
 CPUは早いほうがいいが、ノートパソコンで重い計算をさせることは私は少ないのでCore i3でいいかなって感じです。

 LG gramの2017年のモデルが1キロを下回る軽さで、メモリを追加することも、NVMeに換装することもできます。96000円程度で秋葉原ツクモで10%還元で購入できました。そのポイントを利用してNVMeとDDR4の16GByteのメモリを購入しました。NVMeがsamsung 512GBのモデルで30000円程度,DDR4の16GByteで12000円程度でした。合計で、120000円程度です。
f:id:catindog:20170319212304p:plain

図1. ツクモで買ってきました

 全く文脈を無視しますが、ぼくは小林銅蟲先生を尊敬していまして、料理ブログよく見てるので、あのノリ羨ましいなって思います。
f:id:catindog:20170319212424p:plain

図2. PCの箱とNVMeとメモリとツクモちゃんのクリアファイルです
f:id:catindog:20170319212602p:plain
図3. 開けたときの様子、プラスチックで昔のMacBookを連想させます。ぼくは樹脂のほうが好きです
f:id:catindog:20170319212717p:plain
図4. 背面の様子です。開けていく
f:id:catindog:20170319212804p:plain
図5. 一キロ切っているモデルでメモリと記憶域交換できるのは本当にめずらしいです。よさを感じる
f:id:catindog:20170319212852p:plain
図6. メモリスロットとPCI-Expressです。
f:id:catindog:20170319212937p:plain
図7. 刺しました。(SSDからUSBディスクにOSを退避させた後です)
f:id:catindog:20170319213041p:plain
図8. メモリ20GByte認識して、NVMeの速度出ているようです

OSをNVMeに移動する

 これが一番めんどくさかったです。コントロールパネルからリカバリーディスクの作成を選んで、32GのUSBフラッシュメモリにOSのリカバリディスクを作成しました。
 かなり早いUSBフラッシュメモリを用いたのに全く速度がでない。。。4時間ぐらいかかりました。これはMicorsoftの怠慢では。途中で寝てしまいました。
f:id:catindog:20170319213224p:plain

図9. 遅すぎる
 終わったら、NVMeに取り替えてF10を押しながら起動して、USBから起動して復元を行います。(有料のソフトもいくつかみたけど、覚えるのつらすぎ)

3. 日本語が処理できるようにcmdやcmderの代わりにputty, teratermやcygwind sshを利用する

 Bash on WidowsというWindows上にUbuntuが動作する仕組みがあるのですが、コマンドプロンプトや、よくおすすめされるcmderというソフトは、日本語入力やファイルが壊滅的にダメで、これが原因でほんとつらい思いをしました。
 よく私が使う回避策は、Bash on Windowsssh-serverを起動して、teraterm等てアクセスする方法です。面倒ですが今のところこれが一番安定して、日本語が扱えます。

3.1 開発者モードを、コントロールパネルから有効にする
3.2 Windowsの機能の有効化または無効化 → Windows Subsystem for Linux (Beta) → チェックを入れて再起動
3.3 services.mscコマンドプロンプトに入力 -> SSH Server Broker,SSH Server Proxyの2つのサービスを無効にする
3.3 cmdを立ち上げる → bashと入力 → Linuxのインストール
3.4 Bash on Windowsに入る → sudo apt get install openssh-serverでインストール
/etc/ssh/sshd_configを編集

$ sudo vim /etc/ssh/sshd_config
変更→ UsePrivilegeSeparation no
変更→ PasswordAuthentication yes
コメントアウト → #Hostkey /etc/ssh/ssh_host_dsa/key
コメントアウト → #Hostkey /etc/ssh/ssh_host_ecdsa/key
コメントアウト → #Hostkey /etc/ssh/ssh_host_ed25519/key

コマンド実行(これをやらないとつながらない)

$ sudo ssh-keygen -t rsa -N '' -f ssh_host_rsa_key

sshdを起動

$ sudo /etc/init.d/ssh start

ここまでできれば、teratermcygwinsshで好きなように操作することができます。日本語もオッケーです。

4. mecabとneologdの導入

 これもUbuntuのインストール手順に従えば、問題ないです。標準のUbuntuLinuxにはデフォルト入ってないパッケージもあるので適宜入れていきます。

mecabソフトウェア各種インストール

$ sudo apt install mecab libmecab-dev mecab-ipadic
$ sudo apt install mecab-ipadic-utf8
$ sudo apt install python-mecab
$ sudo apt install python3-pip
$ sudo pip3 install mecab-python3
$ git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
$ cd mecab-ipadic-neologd
$ ./bin/install-mecab-ipadic-neologd -n

mecabのデフォルト辞書をneologdに変更する

$ sudo vim /etc/mecabrc
変更 → dicdir = /usr/lib/mecab/dic/mecab-ipadic-neologd

動作確認(FGOは標準辞書にない)

$ echo "Fate/Grand Order" | mecab
Fate/Grand Order        名詞,固有名詞,一般,*,*,*,Fate/Grand Order,フェイトグランドオーダー, フェイトグランドオーダー
EOS

5. Anacondaとjupyter notebookの構築

最初Anacondaを使わずに入れようと思ったけど、ubuntuのwheelが通じないことながあって、Anacondaでインストールするのが楽でした。

Anacondaのパッケージを取得

$ wget https://repo.continuum.io/archive/Anaconda3-4.3.1-Linux-x86_64.sh

インストール(sudoするとパーミッションが面倒なのでしない)

$ sh Anaconda3-4.3.1-Linux-x86_64.sh
(ひらすらyesで進めていく)

私の環境ではpython3.6のバイナリがAnacondaのPythonですので、Python3.6で実行できるようになります。

$ which python3.6
/home/{yourname}/anaconda3/bin/python3.6

なお、ipythonのkernelが不安定なので、以下のパッチを当てる必要があります。

conda install -c jzuhone zeromq=4.1.dev0

jupyter notebookのパスワードを作成します

In [1]: from notebook.auth import passwd
In [2]: passwd()
Enter password:
Verify password:
Out[2]: 'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed' <-これをコピーしておきます

jupyter notebookにパスワードを設定

$ vim ~/.jupyter/jupyter_notebook_config.py
コピーした内容を貼り付け
c.NotebookApp.password = u'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'

ついでにjupyterで形態素解析ができるようにする

$ sudo ipython -m pip install mecab-python3

これでClient側のWindowsからwebブラウザ等でjupyter notebookが利用可能です。

例でスクリーンショットを載せます。
f:id:catindog:20170319214656p:plain

図10. jupyter notebook上で最新の形態素解析ができました

6. XGBoostの導入

 自分はlibsvm形式で直接xgboostのバイナリに放り込むことが多いのですが、やりかは何でもいいと思います。

$ git clone --recursive https://github.com/dmlc/xgboost
$ cd xgboost
$ make -j4
$ sudo ln -s /home/{yourname}/xgboost/xgboost /usr/local/bin/xgboost

サンプルのアガリクスの有効判定みたいなのをときます。

$ cd demo/binary_classification
$ python mapfeat.py
$ python mknfold.py agaricus.txt 1
$ xgboost mushroom.conf

いろいろ入れられそうですが、この辺にしておきます。
再度になりますが、自分はQiitaとか見てて結構ハマって、よくわかんないなぁとか思っていたのですが、公式ドキュメントで解決することが多かったです。私の今回の方法も、ひと月後には通用しないものになっているかもしれません。
公式ドキュメントを正しく理解することが一番の近道かもしれません。
(何か間違い等がありましたら、ツイッターでお知らせいただけると幸いです。)

alternative illustration2vec(高次元タグ予想器)について

alternative illustration2vec(高次元タグ予想器)について

f:id:catindog:20170311231233p:plain

図1. 予想結果のサンプル

はじめに

今回はillustration2vecを去年10月に知り、実装法を模索していたが、Kerasでの転移学習と、目的関数を調整することで同様の結果が得られるのではないかという仮説に基づいて、検証実験を行った。

illustration2vecのような画像のベクトル化技術に関してはアプローチは複数用意されており、どのような方法がデファクトかつ、もっとも精度が良いのかわかっていない。

以下、私が考えた3つの方法を記す。

  • 1. VGG16などの学習済みモデルの出力部分のみを独自ネットワークの入力にすることで、タグ予想問題に切り替える
  • 2. 上記のアプローチをとるが、入力に途中のネットワークのレイヤのベクトルも入力に加える
  • 3. キャラクタ判別問題などにタスクを切り替えて、タスク完了後ネットワークを学習させないようにフリーズして、途中のレイヤの出力、ボトルネック層の出力などを素性にしたlogistic regressionにて学習する

f:id:catindog:20170311231843p:plain

図2. 今回用いた転移学習の例

1,2は転移学習とよばれる学習済みのモデルを利用して再度学習する方法であるが、ネットワークの出力に近い部分を取り外して任意のネットワークに置換することで別の問題をとくことが可能になる。
この時、よく調整された評価用のVGG16やResNetなどは卓越した特徴量抽出機能を備えている。下手に独自の学習で悪影響を及ぼすより、フリーズと呼ばれる学習を反映しない状態に変換して特徴量抽出器のような使い方をすることができる。

タグの確率表現としての妥当性や多数でのパラメータでの多様性を考えると3が最も良いように見える。これはディープラーニングと既存の実績のある判別問題のアプローチを合成したハイブリットなアプローチである。

先行研究

オリジナルペーパ、やってる概要はつかめたのだが、学習用の実装が見当たらず、妄想であれこれいけるんじゃないかと検証していた

タグ予想という名前でプロダクトを出しており、githubで中間層のデータを参照するようなコードの断片を見つけて、自分の考えがそんなに間違いでないことを知る
なお、400次元に出力を限定しているがGPUのメモリを考えれば5000次元ぐらい行けるのになぜ?

実験

  • GPUの性能の関係でVGG16を改造することに決定
  • 1,2のアプローチの後、性能が出なければ、3のアプローチを導入(1がうまく行ったため行っていない)
  • http://danbooru.donmai.us/ というサイトから、scraperを回して画像とタグ情報を139万枚取得。評価者に馴染み深いドメインである必要があったため、艦これのタグが入っているものを優先。
  • オプショナルな情報として、アダルトスコアも習得
  • 学習時間の関係から5万枚を学習に使用した
  • VGG16を用いるため、150*150*3の画像が入力となる
  • 出力次元には特に制限を設ける必要性を感じなかったために4096次元にした
  • 多くの例ではVGG16の15層までをフリーズするが、性能試験を行ったところ、12層ぐらいのフリーズの方が今回のタスクでは性能が向上した
  • Dropoutは性能が改悪することがあったため、BN(BatchNormalization)で対応した
  • Activation関数に関しては、linearで出力した後Sigmoidをかけている(意味ない可能性ある)
  • 評価関数はbinary_crossentropyを用いている
  • optimizerはadam
  • 評価いはニコニコ静画の画像をもって行うものとする(学習データに含まれていない必要があるため、最新の投稿を用いる)

ネットワーク

  • (2に関して結果から言うと、どうしてもロス率の下がりが悪く、計算リソースの関係で諦めた)
  • 1のネットワークに関しての図は、図2を用いた。
  • Kerasでのモデル定義のコードを記す
def build_model():
  input_tensor = Input(shape=(150, 150, 3))
  vgg16_model = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)
  dense  = Flatten()( \
             Dense(2048, activation='relu')( \
               BN()( \
                 vgg16_model.layers[-1].output ) ) )
  result = Activation('sigmoid')(\
             Activation('linear')( \
               Dense(4096)(\
                 dense) ) )
  model = Model(input=vgg16_model.input, output=result)
  for i in range(len(model.layers)):
    print(i, model.layers[i])
  for layer in model.layers[:12]: # default 15
    layer.trainable = False
  model.compile(loss='binary_crossentropy', optimizer='adam')
  return model

結果

良い結果になった。
以下に三つの例を載せる。
(画像の著作権ニコニコ静画及び絵を書いた人に帰属します)

f:id:catindog:20170311232415p:plain

図3.複数人数を把握することも可能

f:id:catindog:20170311232526p:plain

図4.楽器などの特殊な状況に対応できるか不安だったが正しく認識しているようだ

f:id:catindog:20170311232631p:plain

図5.トップに載せいている電ちゃんと同一(電ちゃんかわいい)

コード

今回は試行錯誤がたくさんあり、コードに微調整の後が大量に見て取れるかもしれない。赦してほしい。
github.com
git cloneする。

git clone https://github.com/GINK03/alt-i2v

画像のデータが必要な方はスクレイピング
(通信速度と頻度に関しては十分注意してください)
bautfulsoupとよばれるモジュールが必要です。

python3 danbooru_datasetgenerator.py --mode=scrape

ダウンロードが完了したら、タグ情報の前処理

python3 alt_i2v.py --maeshori 

高速に画像を処理するために、画像をベクトル化してKVSに格納する
numpy, pillow, msgpack, msgpack-numpy, plyvel+leveldbが必要です

python3 alt_i2v.py --build

トレイン

python3 alt_i2v.py --train

タグ情報を予想してみる

python3 alt_i2v.py --pred foojpg bar.jpg

結論

  • タグの予想は意外と簡単にできるし、精度も悪くない。
  • 今回作成したネットワークを用いることで類似画像検索や、その画像が何を示しているのかを概念的に把握することができる。これはSingle Shot DetectionやYolo V2とは異なったアプローチで状況の把握が可能になることを示している
  • 別に400次元に出力を限定する意味を感じなかったが正しかったようだ。RNNやってれば10000次元に到達することなんてザラだし。

謝辞

- 小林さんちのメイドラゴンのED 「イシュカン・コミュニケーション
「なんでルールはきゅうくつ?胸がしまっちゃうね、下等で愚かな価値観。だめって決定する、倫理なんていりませんよ」の流れ最高ですね。

教師なしRNNによる連続した不完全情報(主にテキスト)の補完

教師なしRNNによる連続した不完全情報(主にテキスト)の補完

背景

Google(や、過去の私のブログの投稿)などで低解像度の映像を高解像度化する方法は、GANを使うことでわりとうまくいきました。
テキストにも同じような問題が適応できるのかって思うことがあります。RNNはすでに記述されている文字をヒントとして次の文字を予想することができます。
その特性を利用して、情報が失われたテキストから復元を試みる例を示します。

(また、戦時中の戦艦に乗っていた搭乗員の劣化した手記から情報を復元したいという思いがあります。手記の海水に沈み腐敗が進んでいたり、筆記が乱れていて解析が困難であったりという点を補完できれば良いと思います。彼らの思い、可能な限り現代に蘇らせたいと思います。)

先行研究

  • 今回は見当たらない(ほんとに存在しないんだったら、論文出したい)

手法

  • RNNで欠落した連続する情報を周辺の単語分布から、情報を復元しようとした場合、どの単語が尤もらしいかどうか予想するタスクに置き換える
  • 一般的なテキスト生成タスクでは、生成タスクの素性とするのは、それまでの系で、決定された文字列である。これを前方までに拡張する。なお、この時、欠落した情報を加工することなく、そのまま学習データとして利用するので、教師なし学習である。
  • 最も尤度が高い欠落情報から埋めていき、再帰的に尤度がたいところを探索することで、より確度の高い復元を試みる

f:id:catindog:20170305175458p:plain

図1. イメージ図

  • 数式も載せようと思いましたけど、Kerasからじゃ数式レベルの粒度で見えないから意味ないんだよね...[3]
  • キャラクタ数多すぎる問題が日本語では起きるので、fastTextでキャラクタを256次元に圧縮したものを用いてます。

実験

  • Keras(TensorFlowをバックエンドとする)
  • 日本の戦時中の手記[1]と、新約聖書[2]を利用する
  • 全体の10%、20%、30%をランダムに情報を欠損させた場合、未知の文章に対しての復元能力を定性的に確認する
  • 全体のドキュメントの8割を学習に、2割を評価に用いる
  • GRUを用いたRNNでunit数は1024
  • BatchNormalizationの論文の基づく、パラメータで導入
  • Dropoutを0.5のパラメータで導入
  • OptimizerにAdamを利用
  • 全結合層はsoftmaxで単語の素性数を出力
  • 目的関数にcategorial cross entropyを利用
  • epoch 160

評価

定性的な評価では概ね良好な結果を示した。
欠落する情報が50%を超えるとRNN特有の再帰構造があちこちに観測され、微妙にわからなくなってくるが、10%程度ならほぼ正しく、30%程度ならぎりぎり読めるようにみえる。


10%欠落した例1:聖書

驚き恐れて、この獣に従い、そればりか、こ■獣に■威を授けた竜を拝むようになっ■。彼らはま■獣をも拝■で、■これほど偉大な者を見たことがな■。だれ獣と戦■て■てる■ろうか」と言った。■の獣は高ぶり、神を汚す言■を吐き■ご■わずかな期間だけ活動■ることが許されで、その■は、神に対し■汚しごとを言い始■た。神とその御使いと、そこ■いる神の民をののしった。その■はクリスチャ戦い■挑んで打ち負かす力が与え■■、国民、部族、種族、■族を支配する力が与えられた。地上に生■ている人で、キリス命の書に、その名前が書き■されていない者は皆■彼を拝むようにな■。■■こと■何を意味しているのか考えてみなさい。クリスチャンは迫害によっ■■え■れて行く。そのことが自分の身に■った時にも■あわてずに心の備えを■ていなければ な。迫害する者がクリスチャンを殺して■、彼ら自身も■ず殺されることになる■だから、クリスチ■ンはす■てを■の御手にて耐え忍ぶことができる■私はまた■もう一頭の獣が地から■って来るのを見た。■■は、おそらく偽預言者のことであろうの獣には、子羊のような二本の角があって、■のように、すごみを■び■ものの言い方をした。この■は、先の獣が持っていらゆる権威を、その獣の前で行使し、地に■む人■に、無理矢理■致■的な傷の治った獣を拝ませた。また、人々■前で燃える火を天から降■せる■さらに、地上の人々を惑わし、致命的な傷を受けながらも■き■った先の獣■像を造るよ地上に住む■■に命じた。偽預言■は魔術的なやり方で、その■の像がものを言っているよ■に見せ■■、その獣の像を拝まを皆殺させた。ま■、あらゆる種類、あらゆる階層の人々の右の■■額に、印を■けさせた。その印の■■てい■い者は、だを■■たり売■たりできないようにし■■まった。その印■、その獣の名前であり、その名前はま■数字でも表された。その■こうと■るなら、天か■の知恵が必■で■る。■の数字の意味の解ける人は、解いてみるが■い。その数字は、■る特定のしており、六百六十六である。これは反キリストである	

10%補完した例1:聖書

驚き恐れて、この獣に従い、そればかりか、この獣に権威を授けた竜を拝むようになった。彼らはまた獣をも拝んで、、これほど偉大な者を見たことがない。だれがこの獣と戦っていてるだろうか」と言った。この獣は高ぶり、神を汚す言葉を吐き、ごさわずかな期間だけ活動することが許された。そこで、その人は、神に対して汚しごとを言い始めた。神とその御使いと、そこにいる神の民をののしった。その獣はクリスチャンに戦いを挑んで打ち負かす力が与えられ、国民、部族、種族、民族を支配する力が与えられた。地上に生きている人で、キリストの命の書に、その名前が書き記されていない者は皆、彼を拝むようになる。このことは何を意味しているのか、よく考えてみなさい。クリスチャンは迫害によって与えられて行く。そのことが自分の身になった時にも、あわてずに心の備えをしていなければならない。迫害する者がクリスチャンを殺しても、彼ら自身も必ず殺されることになる。だから、クリスチャンはすべてを神の御手にゆだねて耐え忍ぶことができる。私はまた、もう一頭の獣が地から帰って来るのを見た。それは、おそらく偽預言者のことであろう。この獣には、子羊のような二本の角があって、そのように、すごみを滅びるものの言い方をした。この男は、先の獣が持っているあらゆる権威を、その獣の前で行使し、地にやむ人々に、無理矢理の致命的な傷の治った先の獣を拝ませた。また、人々の前で燃える火を天から降らせる。さらに、地上の人々を惑わし、致命的な傷を受けながらもべきまった先の獣の像を造るように、地上に住むの中に命じた。偽預言者は魔術的なやり方で、その方の像がものを言っているように見せまし、その獣の像を拝まない者を皆殺させた。また、あらゆる種類、あらゆる階層の人々の右の手そ額に、印を付けさせた。その印の振っていない者は、だれも物を持ったり売ったりできないようにしてしまった。その印は、その獣の名前であり、その名前はまた数字でも表された。その数字をそこうとするなら、天からの知恵が必要である。その数字の意味の解ける人は、解いてみるがない。その数字は、ある特定の人間を指しており、六百六十六である。これは反キリストである

30%欠損した例2:ミッドウェー海戦手記

巨大な戦艦■らばこれら■■■を解決■■るので、■に■■司令・有賀大佐が移乗を強く■申したとも■え■れています。■嵐」■野■」か■「■■」へ■「萩風■■■風雲■から■■■」へ、「巻雲■から「■奥」■、そ■■「濱風」は■山城■へ、「磯風」は甲標■■艦「千代■■■■■艇による■乗■は■変苦労し■と言います■■谷風」が知る由もありません■し ■■闘はこの移乗■業を間接的に助け ■になっ■■評■■れ■いま■。もしドー■■レスが「谷風」を攻撃せずにそのまま■■して■■ら、主隊■■雲■動部隊■受け■いた可能■もあると言われているからです。

30%欠損を復元した例2:ミッドウェー海戦手記

巨大な戦艦ならばこれらの問題を解決でするので、特に四駆司令・有賀大佐が移乗を強く具申したとも伝えられています。「嵐」「野分」から「榛名」へ、「萩風」「舞風」から「長門」に「「風雲」から、「嵐」へ、「巻雲」から「浦奥」へ、そして「濱風」は「山城」へ、「磯風」は甲標上母艦「千代1時3。前日はうこりが大きく、6艇による移乗がは結変苦労したと言います。「谷風」が知る由もありませんでしたが、「谷風」の死闘はこの移乗作業を間接的に助けたことになったと評価されています。もしドーントレスが「谷風」を攻撃せずにそのまま、そしていたら、主隊に南雲機動部隊は移乗作業中に攻撃を受けていた可能性もあると言われているからです。

感想

  • 今回はヒエラルキカルなんとかベイズの理論の勉強中に思いついたので、実際にはベイズでもできるとは思う。実装難易度だったり他の人の感想を見てると、ディープラーニングのほうが簡単ではないかと思ったりした。
  • 多くの点でRNNとベイズとの類型が見られるので、何か興味深い習性があるのではないか

コード

コード全体はgithubにおいておきます。多分そのまま利用できるかと思います。
github.com

git cloneしてファイルを取得

$git clone https://github.com/GINK03/keras-unsupervised-rnn-negentropy.git

トレインの方法です。デフォルトで全体の8割を使用します。デフォルトではmidwayデータセットを用います。適宜コードを変えてください。

$python3 rnn_negentropy.py --train

評価の方法です。testcast.pyに必要な評価対象文章を追加してください。結果はresult.txtファイルへ出力されます。

$python3 rnn_negentropy.py --eval

今回も結構な分量になったので、モデルだけ。

def build_model(maxlen=None, feats=None):
  print('Build model...')
  situations  = 4
  vector_size = 256
  model_text = Sequential()
  model_text.add(Reshape( (maxlen, vector_size, ), input_shape=(maxlen, vector_size)))
  model_text.add(GRU(int(128*8)))
  model_text.add(BN(epsilon=0.001, mode=0, axis=-1, momentum=0.99, weights=None, beta_init='zero', gamma_init='one',
 gamma_regularizer=None, beta_regularizer=None) )
  model_text.add(Dropout(0.5))
  final_model = Sequential()
  final_model.add(model_text)
  final_model.add(Dense(len(feats)))
  final_model.add(Activation('softmax') )
  optimizer = Adam()
  final_model.compile(loss='categorical_crossentropy', optimizer=optimizer)
  return final_model

近況

なんとか生きています。進捗が発生させられないくてツライ。

艦これのセリフ分類をCNNでやる

(2017/2/24追記. いろいろ試したんですが、objective functionをcategorial cross entropyからpoissonに変更し, softmaxの出力をlogを取ることで、急峻なスパイクを抑えることができある程度改善しました )

艦これのセリフ分類をCNNでやる

 幾つかの基礎と、業務で使用できるかどうかの調査した結果、CNNでのテキスト分類が最近評判が良いことがわかった。
 RNNが負けると言われていた分野は分類とか識別の部分で、テキストの生成や連続系ではまだ、RNNが有利であると思う。

 ディープラーニング以前のアルゴリズムは変数の重要度に対しての解釈をする方法が、ある程度ノウハウが蓄積されており、変数の係数や、決定時の出現する変数の頻度でそれっぽく解釈はできたが、ディープラーニングは中身がうかがい知れない事が多い。しかし、性能は高いとされている[1]

 何にせよ、CNNでのテキスト分類は簡単にできるので、情報系が志す人はやっておいた方がいいと思う。

注:また、偶然であるが、RettyさんのCNNによるテキスト分類とネタ的にかぶってしまった。同じchar粒度だし[2]。
twitterのボットとソースコードが見えるのが違う点だと思います)

CNNでのテキスト分類

- 標準的なCNNでのテキスト分類を用いて、艦これのキャラクタのセリフを分類する
- 艦これのキャラクタの発言がそもそもMeCabなどで形態素解析するのに不適な語彙がおおい(ex:はわわ~、ぱんぱかぱーん、造語等)
 なので、形態素解析を必要としない単語粒度のCNNでの分類を行った(twitterで以前行った人の話を聞くと精度はでるらしい)
- ネットワーク図を記す
f:id:catindog:20170223231442p:plain

図1. ネットワーク図

実際に使用したネットワークより簡略化している
1. Embeddingと呼ばれる文字情報もベクトル化を行う
2. 1~6文字を連結した状態で畳み込みを行う
3. Pooling層をとおしてそれぞれの単語の長さの粒度の出力層をConcat(連結)してDense(全結合層)に入力する
4. 今回は、複数のキャラクタがいるため、Softmaxと呼ばれる方法でマルチクラスに対応する
5. objective functionをpoissonにする
6. logsoftmaxがないので、softmaxの出力値のlogを取る

コード

KerasというTensorFlowを再利用する形で利用するディープラーニングフレームワークがあるのだが、短く簡潔にかけるのでChainerとともによく使う。
今回はKerasで実装した。
コードの全体の説明は長くなってしまうので、ネットワークの構成だけ示す。アドホックなところは、色々とネットワークの構成やパラメータを変えて、分類能が高い構成を探すためである(このネットワークで決まりで、もういじらないなら抽象化してコードを短くできるけど、多分これからもネットワークをいじるので)。

def build_model(sequence_length=None, filter_sizes=None, embedding_dim=None, vocabulary_size=None, num_filters=None, drop=None, idx_name=None):
  inputs = Input(shape=(sequence_length,), dtype='int32')
  embedding = Embedding(output_dim=embedding_dim, input_dim=vocabulary_size, input_length=sequence_length)(inputs)
  reshape = Reshape((sequence_length,embedding_dim,1))(embedding)
  conv_0   = Convolution2D(num_filters, filter_sizes[0], embedding_dim, border_mode='valid', init='normal', activation='relu', dim_ordering='tf')(reshape)
  pad_0    = ZeroPadding2D((1,1))(conv_0)
  conv_0_1 = Convolution2D(512, filter_sizes[0], 3, border_mode='valid', init='normal', activation='relu', dim_ordering='tf')(pad_0)
  conv_1 = Convolution2D(num_filters, filter_sizes[1], embedding_dim, border_mode='valid', init='normal', activation='relu', dim_ordering='tf')(reshape)
  pad_1  = ZeroPadding2D((1,1))(conv_1)
  conv_1_1 = Convolution2D(512, filter_sizes[1], 3, border_mode='valid', init='normal', activation='relu', dim_ordering='tf')(pad_1)
  conv_2 = Convolution2D(num_filters, filter_sizes[2], embedding_dim, border_mode='valid', init='normal', activation='relu', dim_ordering='tf')(reshape)
  pad_2  = ZeroPadding2D((1,1))(conv_1)
  conv_2_1 = Convolution2D(512, filter_sizes[2], 3, border_mode='valid', init='normal', activation='relu', dim_ordering='tf')(pad_1)
  conv_3 = Convolution2D(num_filters, filter_sizes[3], embedding_dim, border_mode='valid', init='normal', activation='relu', dim_ordering='tf')(reshape)
  conv_4 = Convolution2D(num_filters, filter_sizes[4], embedding_dim, border_mode='valid', init='normal', activation='relu', dim_ordering='tf')(reshape)
  maxpool_0   = MaxPooling2D(pool_size=(sequence_length - filter_sizes[0] + 1, 1), strides=(1,1), border_mode='valid', dim_ordering='tf')(conv_0)
  maxpool_0_1 = MaxPooling2D(pool_size=(sequence_length - filter_sizes[0] + 1, 1), strides=(1,1), border_mode='valid', dim_ordering='tf')(conv_0_1)
  maxpool_1   = MaxPooling2D(pool_size=(sequence_length - filter_sizes[1] + 1, 1), strides=(1,1), border_mode='valid', dim_ordering='tf')(conv_1)
  maxpool_1_1 = MaxPooling2D(pool_size=(sequence_length - filter_sizes[1] + 0, 1), strides=(1,1), border_mode='valid', dim_ordering='tf')(conv_1_1)
  maxpool_2 = MaxPooling2D(pool_size=(sequence_length - filter_sizes[2] + 1, 1), strides=(1,1), border_mode='valid', dim_ordering='tf')(conv_2)
  maxpool_2_1 = MaxPooling2D(pool_size=(sequence_length - filter_sizes[2]  - 0, 1), strides=(1,1), border_mode='valid', dim_ordering='tf')(conv_2_1)
  maxpool_3 = MaxPooling2D(pool_size=(sequence_length - filter_sizes[3] + -3, 1), strides=(1,1), border_mode='valid', dim_ordering='tf')(conv_2)
  maxpool_4 = MaxPooling2D(pool_size=(sequence_length - filter_sizes[4] + -2, 1), strides=(1,1), border_mode='valid', dim_ordering='tf')(conv_2)
  merged_tensor = merge([maxpool_0, maxpool_0_1, maxpool_1, maxpool_1_1, maxpool_2, maxpool_2_1, maxpool_3, maxpool_4], mode='concat', concat_axis=1)
  flatten = Flatten()(merged_tensor)
  dropout = Dropout(drop)(flatten)
  output = Dense(output_dim=len(idx_name), activation='softmax')(dropout)
  adam = Adam()
  model = Model(input=inputs, output=output)
  model.compile(optimizer=adam, loss='poisson', metrics=['accuracy'])
  return model

全体のコードはgithubにおいてあってそのままダウンロードで使えるようにしておきます。
(Python3とTensorFlowバックエンドのKerasが必要です)

github.com

$ git clone https://github.com/GINK03/keras-cnn-text-classify
$ cd keras-cnn-text-classify
$ python3 model.py --train --all
(ビルトインしているデータ・セットでGTX 108030分ほどかかります)

# 確率を計算します。これはkerasのMC(マルコフ連鎖モンテカルロ法) searchのプラクティスから抜き出した方法で計算しています
# 最大値を100%として可能性を提示していますが、現実世界の確率と解釈は同じではありません
$ echo "大丈夫、きっと僕は君のことを忘れない" | python3 model.py --pred
時雨 100%
日向 75%
レーベレヒト・マース 27%
初月 27%
武蔵 11%
若葉 11%
能代 4%
ハルナ 3%
利根 1%
木曽 1%
$ echo "あのあの司令官さん、どうしたのですか" | python3 model.py --pred
電 100%
春雨 26%
吹雪 22%
沖波 16%
雪風 11%
霰 9%
高波 6%
春風 2%
青葉 1%
菊月 1%
$ $ echo "そんなんじゃだめよー" | python3 model.py --pred
敷波 100%
雷 61%
望月 55%
江風 47%
鈴谷 41%
時津風 26%
島風 15%
瑞鶴 15%
北上 5%
睦月 1%
$ echo "もうちゃんとレディとして扱ってよね" | python3 model.py --pred
暁 100%
熊野 38%
夕張 37%
愛宕 32%
天津風 21%
能代 6%
最上 6%
加古 3%
金剛 1%
瑞鶴 1%
$ echo "フリーダム響だよ" | python3 model.py --pred
響 100%
江風 55%
木曽 23%
加古 18%
敷波 16%
時雨 12%
イ168 8%
イ26 1%
深雪 1%
涼風 1%
$ echo "おっそーい" | python3 model.py --pred
島風 100%
時津風 43%
イ26 32%
U-511(呂500) 31%
瑞鶴 8%
望月 6%
江風 1%
敷波 1%
涼風 1%
初風 1%
$ echo "司令、なにやってんの~、ねぇってばー" | python3 model.py --pred
黒潮 100%
陽炎 67%
時津風 49%
イ26 15%
酒匂 9%
青葉 8%
雷 8%
文月 1%
比叡 1%
敷波 1%

パラメータと精度確認

学習の様子を見ていると、過学習に存外早く陥ってしまうことがわかった。
epoch 100までやったが、epoch 10~20あたりがちょうど良さそうであった。

Train Acc: 97%, Validation Acc: 56%
(56%というのは会話文では、他のアルゴリズムとくらべても悪くないものだと思う)

パラメータ

embedding次元:256
filterサイズ: 1, 2, 3, 4, 5
filter数:512
dropout:0.5
epoch: 10
batch: 30
Optimizer: Adam(学習率等はデフォルト)
objective function: poisson

せっかくできたのでbotにした

昔使ってたアカウントをボットに変更して、リプを送ると、リプの内容が艦むすでいうと誰の発言になるのか、確率(のようなもの)を表現するbotを作った。
サーバに立ち上げておかなきゃいけないものなので、いつまで公開しているかわからないが、遊んでほしい。
コミケの製作者側は、このセリフをこの子に言わせたらどうなんだろう?とかってあると思うけど、そういうときに役に立つかも。
(不定期に止めたりアップデートしたりする予定です)

f:id:catindog:20170223231923p:plain

図2. Deep時雨の様子(めっちゃ不安定です)

@deep_shigureです。よろしくお願いします
twitter.com
(すぐ落ちるので、そのときはほんとすみません。だれか管理しませんか)

一人あたりの発言が少なく、データセットの量が足りずに、結構苦労しています。だれかpull requestとかforkとかしてよりすごくしてくれたら嬉しいです。(メールは気付かないことがあるけど、twitterはよくみているので知らせてください)

参考

[1]


[2]
speakerdeck.com