教師なしRNNによる連続した不完全情報(主にテキスト)の補完
教師なしRNNによる連続した不完全情報(主にテキスト)の補完
背景
Google(や、過去の私のブログの投稿)などで低解像度の映像を高解像度化する方法は、GANを使うことでわりとうまくいきました。
テキストにも同じような問題が適応できるのかって思うことがあります。RNNはすでに記述されている文字をヒントとして次の文字を予想することができます。
その特性を利用して、情報が失われたテキストから復元を試みる例を示します。
(また、戦時中の戦艦に乗っていた搭乗員の劣化した手記から情報を復元したいという思いがあります。手記の海水に沈み腐敗が進んでいたり、筆記が乱れていて解析が困難であったりという点を補完できれば良いと思います。彼らの思い、可能な限り現代に蘇らせたいと思います。)
先行研究
- 今回は見当たらない(ほんとに存在しないんだったら、論文出したい)
手法
- RNNで欠落した連続する情報を周辺の単語分布から、情報を復元しようとした場合、どの単語が尤もらしいかどうか予想するタスクに置き換える
- 一般的なテキスト生成タスクでは、生成タスクの素性とするのは、それまでの系で、決定された文字列である。これを前方までに拡張する。なお、この時、欠落した情報を加工することなく、そのまま学習データとして利用するので、教師なし学習である。
- 最も尤度が高い欠落情報から埋めていき、再帰的に尤度がたいところを探索することで、より確度の高い復元を試みる
- 数式も載せようと思いましたけど、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艇による移乗がは結変苦労したと言います。「谷風」が知る由もありませんでしたが、「谷風」の死闘はこの移乗作業を間接的に助けたことになったと評価されています。もしドーントレスが「谷風」を攻撃せずにそのまま、そしていたら、主隊に南雲機動部隊は移乗作業中に攻撃を受けていた可能性もあると言われているからです。
感想
コード
コード全体は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
参考文献
[1] 駆逐艦に見るミッドウェイ海戦 http://www.warbirds.jp/truth/midway.html
[2] 新約聖書 http://bible.salterrae.net/kougo/pdf/
[3] RNNの数式的説明: https://micin.jp/feed/developer/articles/rnn000
近況
なんとか生きています。進捗が発生させられないくてツライ。
艦これのセリフ分類を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で以前行った人の話を聞くと精度はでるらしい)
- ネットワーク図を記す
実際に使用したネットワークより簡略化している
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が必要です)
$ git clone https://github.com/GINK03/keras-cnn-text-classify $ cd keras-cnn-text-classify $ python3 model.py --train --all (ビルトインしているデータ・セットでGTX 1080で30分ほどかかります) # 確率を計算します。これは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を作った。
サーバに立ち上げておかなきゃいけないものなので、いつまで公開しているかわからないが、遊んでほしい。
コミケの製作者側は、このセリフをこの子に言わせたらどうなんだろう?とかってあると思うけど、そういうときに役に立つかも。
(不定期に止めたりアップデートしたりする予定です)
@deep_shigureです。よろしくお願いします
twitter.com
(すぐ落ちるので、そのときはほんとすみません。だれか管理しませんか)
一人あたりの発言が少なく、データセットの量が足りずに、結構苦労しています。だれかpull requestとかforkとかしてよりすごくしてくれたら嬉しいです。(メールは気付かないことがあるけど、twitterはよくみているので知らせてください)
参考
[1]
たぶん台詞判定でもこういう手法が精度出るんだろうけど、判定結果について、なんで? って聞かれると「ニューラルネットの気持ちになるですよ…」としか言いようがない予感https://t.co/PiDWJYcWqk
— imas_cg集計 (@shuukei_imas_cg) 2017年2月23日
[2]
speakerdeck.com
YoRHa No.2 Type Bを作りたかった
はじめに
ドーモ。国家深層学習術師デス。(多数のミームが重なり合い、汚染が観測できるはずです)
この前、Domain Transfer Network(DTN)というものがFacebookの機械学習関連の研究所から発表され、直接的な対応関係がなくても、対応したドメインに変化させることができるということが実証されました。
実を言うと私には、いくつか夢があり、一つ結婚すること、二つJKになること、三つ目は生殖以外の方法で知能を錬金することです。
物質的な改造によるJK化は無理だと諦めているのですが、DTNで自分のビジュアルをJKに変換し、AR/VRに投影することで実現可能なのではって考えています。AR/VRに投影することでは物質として本質が変化することはありませんが、認知レベルでは詐称可能です。クオリア(その人の認識の非常に深いところ)を攻略するにはAR/VRで十分であると感じています。
JKになるべくタイに行かなくてよかった。オレはディープラーニングでJKになってやる。
DTNもしばらくいじっていたんですが、うまく収束させるのが難しく、また今度ゆっくりやっていきたいと思います。
蛇足がひどかったですが、三つ目の夢である、人工生命の生成に関して、Facebookが非常に興味深い研究を行っています。文脈を考慮できるチャットボットのような存在を研究しています。
意識の在り処
Wikipediaの中国語の部屋を読んでもらえばわかるかと思うのですが、何らか同じように感じ、同じような感覚質を所有し、自分が考える心が宿っているかなどは、プロトコルが成立して、会話などが成り立っていればあたかも心を持った存在であると、詐称し続けることが可能になります[1]
これは哲学的ゾンビの問題にも波及する大いなる命題です。あなたの好きなあの子は、本当に自分と同じく思考し、感じる人間ですか?あなたの脳が見続けている夢ではありませんか?[2]
哲学的ゾンビか、真に意識を持った存在かは、観測側から証明不能なので、意識をもったような存在の生成は機械で代替可能であると考えています。
Googleの研究所ではSeq2Seqを用いた応答ボットは、チャットでのヘルプデスクの会話ログを学習させたことで最も人間らしいと判断されたのでした。そこで課題とされたのは、文脈に応じた会話ができない問題でした。以下は例ですが、文脈が簡単に破綻してしまいます。
質問> あなたの職業はなんですか? 機械> 医者です 質問> さぞ儲けていらっしゃるのでしょうね 機械> わたしは投資機関で働いていますが、投機に失敗してしまい赤字です
医者と言ったのに直後の文脈で理論的な破綻が起きています。このようなことがSeq2Seqでは簡単に起きやすいです。
AttentionやConditional Vector(出力の方向性を指示するベクトル)を入れることで解決できることでもあります。(要は前の文脈を内在した表現を入力に受け付けることになるから)
このように機械学習で生成したモデルは、人間から見るといささか不自然に見え、意識が宿っていないように見えます。
ですが、この文脈に応じた会話ができないという、言語を理解していないように見える側面を、攻略しようとしているのがFacebookの研究の本質的なところなのかなと感じています。
bAbiデータ・セットについて
Facebookが作成した会話を記したダイアログで、bAbi(ベビーと多分発音する)という名が記すとおり、知能などをテストする指標としてなるべく作成されました。
既存のLSTMなどでも比較的うまく動くものも多いですが、既存の手法では、うまくいかないものを、どのようなネットワークしたら意味を取れるような動作をするのかという点を競い合っています。
Facebookが公開したネットワークはMemNNとよばれ、文脈を記憶するような作用があります。文脈を覚えることで、もっともらしい箇所を参照して質問に対して適切な解を導いたりします。
データ・セットには様々な指標が入っており、以下のような構成になっています。
1 Basic factoid QA with single supporting fact WhereIsActor 2 Factoid QA with two supporting facts WhereIsObject 3 Factoid QA with three supporting facts WhereWasObject 4 Two argument relations: subject vs. object IsDir 5 Three argument relations WhoWhatGave 6 Yes/No questions IsActorThere 7 Counting Counting 8 Lists/Sets Listing ...
1のサンプルについて具体例を記しておくと、このようなものになります。
INPUT: メアリーはトイレに移動しました。 INPUT: ヨハネは廊下に行った。 Query: メアリーはどこですか? Anser: バスルーム (これは非常に短い応答だが、実際にはもっと複雑)
文脈に応じた回答が求められるデータセットでは単純にSeq2Seqのような前の特徴量との対応関係を自動で構築する以上のことを求められていることがわかるかと思います。
英語の限定されたモデルでやっても面白くないと思ったので、邦訳した簡単なデータセットを下部のgithubのURLの場所においておきます。(別途パース用のスクリプトを動作させる必要があります)
MemNNについて
MemNNはFacebookの研究所で考案されたネットワークで以下の4つの諸要素から構成されています。
I: (input feature map) convert incoming data to the internal feature representation. G: (generalization) update memories given new input. O: produce new output (in feature representation space) given the memories. R: (response) convert output O into a response seen by the outside world.
IGORとは、映画フランケンシュタインの教授の助手であり、このあからさまな名称はやはり人工生命の文脈が背後にあること意図しているものであると思われます。
説明資料の図をそのまま記す。
Matching functionというものがあって、質問と状況説明ペアのベクトルを合成して、参照できるようにしてあります。
答えが直接的な表現でないこともあり、2nd hopと呼ばれる二回目のMatching functionもあります
Match(Q:Where is the footbal?, A:John picked up the football) 2nd hop-> Match( [Q:Where is the footbal?, A:John picked up the football], A:John is in the playground)
ネットワークの説明
この図が分かりやすかった。
下記の図のように実装ているのですが、図や文章だけでは私もよくわからなかったので、コードと合わせて参照することをおすすめします。
Kerasでのモデル
# この入力は状況説明文 input_encoder_m = Sequential() input_encoder_m.add(Embedding(input_dim=vocab_size, output_dim=64, input_length=story_maxlen)) input_encoder_m.add(Dropout(0.3)) # この入力は質問文 question_encoder = Sequential() question_encoder.add(Embedding(input_dim=vocab_size, output_dim=64, input_length=query_maxlen)) question_encoder.add(Dropout(0.3)) # 積で合成 match = Sequential() match.add(Merge([input_encoder_m, question_encoder], mode='dot', dot_axes=[2, 2])) match.add(Activation('softmax')) # この入力は状況説明文 input_encoder_c = Sequential() input_encoder_c.add(Embedding(input_dim=vocab_size, output_dim=query_maxlen, input_length=story_maxlen)) input_encoder_c.add(Dropout(0.3)) response = Sequential() response.add(Merge([match, input_encoder_c], mode='sum')) response.add(Permute((2, 1))) # output: (samples, query_maxlen, story_maxlen) # Σで合成 answer = Sequential() answer.add(Merge([response, question_encoder], mode='concat', concat_axis=-1)) answer.add(LSTM(32)) answer.add(Dropout(0.3)) answer.add(Dense(vocab_size)) answer.add(Activation('softmax')) answer.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
具体例
Kerasのsampleコードにもあるのですが、何故か学習のスクリプトだけ用意してあって、どのように実行するのかプログラムが書いてありませんが、自分で実装するのもそう難しいことではありませんでした。
一応、githubでcloneすればそのまま使える感じにしておきます。(Python3とKerasが必要です)
$ git clone https://github.com/GINK03/keras-babi-memnn.git $ cd keras-babi-memnn データセットをシリアライズしてMemNNが読める形に変換します $ python3 kizunekotom.py
またネットワーク構成をみると、まだユニット数を増やしたり、いろいろな条件を付け加える余地がある感じでしたので、任意の新規提案が色々載せられる段階であると思います。
学習タスク(デフォルトepoch200、ネットワークはModel.pyを参照のこと)
$ python3 babi_memnn.py --train Using TensorFlow backend. Vocab size: 45 unique words Story max length: 86 words Query max length: 7 words Number of training stories: 9995 Number of test stories: 993 - (...だいたい30分くらいで200epochの学習完了) ボキャブラリ数40くらいで、Validation精度が93%程度(英語では95%超えてたけど、翻訳が怪しいのでしょうがない)
予想タスク
$ python3 babi_memnn.py --pred サンドラは庭に行った。ジョンは台所に行きました。メアリーはトイレに行った。メアリーは寝室に行った。メアリーはオフィスに旅をした。ダニエルはトイレに行った。 Q:メアリーはどこですか? ANS:オフィス
github
Python3とgithubがインストールされていれば、git cloneして、下記のコマンドを叩くだけで使用可能だと思われます。
https://github.com/GINK03/keras-babi-memnn
$ python3 babi_memnn.py --train
bAbiデータ・セットの次にあるもの
チャットボットを超えた何らかの意識を有しているように見えるネットワークがくる、と思います。ただMemNNも相当アドホックなものな感じなので、しばらく何がベストなのか探索的な研究は続くと思います。
短い期間でえらい勢いでスコアが伸びてるので、期待して良さそうですね[5]
参考
[1] 中国語の部屋
[2] 哲学的ゾンビ
[3] Facebook MemNN
[4] Keras Example: 何故か記憶素子を持たないはずのRNNの方がパフォーマンスが優れている例
[5] https://arxiv.org/pdf/1502.05698.pdf:tile=TOWARDS AI-COMPLETE QUESTION ANSWERING : A SET OF PREREQUISITE TOY TASKS(いろんな種類のMemNNが評価されている)
おまけ:「機械学習」と「AI・人工知能」というワードの意味は異なる?
ちょっとだけ言っておこうと思うことがありまして、Google検索でニュース記事のライターの方とか、専門誌のライターの方が検索して引っかかってくれることを少々期待しています。
「機械学習」と「AI」と「人工知能」の使い分けはどうされていますか?私が所属している機関では、営業向けの人はAIということが多く、現場の人は機械学習ということが多いです。
機械学習は非常に工学と科学の双方を用いて、理論や仮説にもとづいてモデルを構築し、エンピリカルに成果を出すものなのかなって思います。それで、エンジニアは偉い人にこれこれこういうことをやっていると説明するんですが、偉い人の知識体系には、私がやっている体験の実感がないためか、AIと翻訳することがあってズレを感じる次第です。
理解が違えば、用法も違うので意味が異なってきてしまいます。単純な言い換えにはならないんじゃないかと思っていたのですが、定量的にこれで行けるんじゃないかと思った方法があるので、ご紹介します。
1.意味が近しいや同じってどういうことか → 意味をベクトル空間に射影することができれば、近しいベクトル同士が意味が似ているといえる 2.ベクトル空間に射影するには → fastTextやword2vecなどのword enbeddingが意味が近いものが同じようなベクトルになることが幾つかの論文で示されている 3.しかし、人間には200次元ものベクトルの近さをイメージできない → PCAなどの次元圧縮方法で可視レベルに落とし込める
こんな仮説(プロセス?)が成立できました。
Newsサイトの記事2月1~15日までの記事を集めると500MByteを超えるデータになりました。テキストとしては十分大きいです。
fastTextを今回用いembeddingしました。sub word分割が強力に働きすぎるということだったので、最小のcharのngramを5文字と限定するチューニングのみ行いました。
sklearnのPCAで2dimに圧縮して、プロットするとこのような図が得られました。
右上の方の「機械学習」と「AI」と「人工知能」を確認してください。「AI」と「人工知能」は非常に近いので言い換え可能そうですが、「機械学習」はちょっと距離がありますよね。単純に機械学習の言い換えとしてのAIはきついんじゃないですかね。
こういうことがあるということだけ知ってもらいたいと思いました。
ウィキペディア日本語版 tf-idfのidf辞書の公開
nora(野良)-idf-dic
モチベーション
フォーマット
- idfはjsonのdict型(ハッシュマップとも言います)です。
idf = { term1: weight1, term2:weight2, ... }
このようなフォーマットになっており、単語とidfの重みがペアになって格納されています。
以下は実際にmecab等をインストールして、ゼロからidf辞書を作成する例なので、idf辞書を利用するのみなら参照する必要はありません。
プロジェクトの取得取得と、周辺ソフトウェアウェアのインストール
LevelDB(kvs)のインストール
(Ubuntu 16.04以上を想定しています)
$ git clone https://github.com/google/leveldb.git $ cd leveldb $ make $ cd include $ sudo cp -r leveldb $ sudo cp -r leveldb/ /usr/local/include/ $ cd .. $ cd out-shared $ sudo cp lib* /usr/local/lib/ $ sudo ldconfig $ cd ~
$ sudo apt install mecab libmecab-dev mecab-ipadic $ sudo apt install mecab-ipadic-utf8
$ git clone https://github.com/GINK03/tiny-japanese-wikipedia-tfidf-dic-generator $ sudo pip3 install mecab-python3 $ sudo pip3 install plyvel
NeoLogdのインストール、及び辞書の書き換え
$ cd ~ $ git clone https://github.com/neologd/mecab-ipadic-neologd.git $ cd mecab-ipadic-neologd/ $ ./bin/install-mecab-ipadic-neologd [install-mecab-ipadic-NEologd] : Do you want to install mecab-ipadic-NEologd? Type yes or no. >yes $ sudo vi /etc/mecabrc (元)dicdir = /var/lib/mecab/dic/debian -> (変更後)dicdir = /usr/lib/mecab/dic/mecab-ipadic-neologd
Neologdのテスト
$ echo "Fate/Grand Order" | mecab Fate/Grand Order 名詞,固有名詞,一般,*,*,*,Fate/Grand Order,フェイトグランドオーダー,フェイトグランドオー ダー EOS
動作確認
$ cd ~ $ cd tiny-japanese-wikipedia-tfidf-dic-generator $ python3 nora-idf-dic.py (何も表示されなけばOK)
Wikipediaのダンプ情報の取得
Wikipediaのスナップショットと呼ばれる情報を取得し、展開します。
$ wget https://dumps.wikimedia.org/jawiki/20170201/jawiki-20170201-pages-articles-multistream.xml.bz2 $ bunzip2 jawiki-20170201-pages-articles-multistream.xml.bz2
idf辞書を構築します。
$ python3 nora-idf-dic.py --wakati (...60分ほど待ちます) $ ls title_context.ldb(このディレクトリがあればOK) $ python3 nora-idf-dic.py --build (...3分ほど待ちます) $ ls words_idf.json words_idf.jsonls
tf-idfでベクトル化する
具体例を記しておきます。
$ echo "あなた狩りごっこがあまり好きじゃないけものなんだね" | python3 nora-idf-dic.py --check {'ね': 4.926646596986834, 'ない': 2.042401886218362, 'だ': 2.8119346405476735, 'が': 1.2142350698667934, 'じゃ': 6.054326132384362, 'あなた': 5.476151075317936, 'ごっこ': 8.627077870130083, 'ん': 3.364157726200682, '狩り': 7.11635016692977, '好き': 4.97306829447642, 'けもの': 9.584680272531994, 'あまり': 5.093448481495583, 'な': 1.6713533531785785}
keyを数値としてindexを振っていけば、libsvmやXGBoostやLightGBMで入力可能なフォーマットになります。
別にこのスクリプト経由で読み出すのではなく、jsonファイルだけ読み込んで、好きなように使っていただいて構いません。
コード
ライセンス・その他
- Text of Creative Commons Attribution-ShareAlike 3.0 Unported Licenseというライセンスに準拠
- Wikipediaを参照してください