にほんごのれんしゅう

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

RNNで関数近似

RNNで関数近似

DeepLearningの一種であるRNNで、任意の関数を学習させて、十分にある関数の挙動が観測できたとき、未知の任意の点で予想できることを示したいと思います。

始める前に諸々調査したこと

ディープラーニングで任意の連続する関数を再現可能です。

しかし、ディープラーニングは無限やかなり大きな値を扱うことは難しく、適切にフィットしない問題があります。無限大に発散しない(発散してもいいけど、目的とする値をなにか閉域に限定して変換する操作を行う)必要があります

もっと有機的で人間的な特徴を学習と予想を行う問題として、sketch-rnnというrnnで最初の一部だけを描き、学習した内容で残りを予想するという問題設定もあって面白くユニークです

図1. 関数近似もスケッチの特徴を学び学習するのも一緒な気がします

耳だけ描くと、残りの部分が自動的に機械学習の予想結果により描かれます

様々な関数をDeepLearingのRNNで近似する

RNNはn-1からn-mまでの情報を特徴量にnの系列でのデータを予想することができます。
この時、連続であることが望ましく、無限小、無限大を取らないような、一定の再帰性があると収束しやすいです。(発散する関数は予想が大きくなるに近づくにつれ、難しくなります)

 

図2. アステロイド(うまくいく)

 

図3. 二次関数(うまくいかない)

数式で表現する

ディープラーニングのRNNのEncoder-Decoderの一つの機能の粒度を関数として表現すると、このように表現することができると思います。

ディープラーニングで適切なF1, F2, F3を決定することで、次の系列のX,Yが予想可能になります

ネットワークで表現する

ディープラーニングのネットワークではこのように表せるように思います

図4. 作成したネットワーク(RNNのAutoEncoderモデルを参考に作成)

このネットワークは特に媒介変数がないと表現できないようなネットワークについて高い力を発揮しそうだと考えました。

実験

サイクロイド

サイクロイドはこのような図形になます

 

媒介変数をもちいた表現はこのようになります

 

学習

$ python3 20-train-cicloid.py --train

評価

trainで使用しなかったデータを使用して予想

$ python3 20-train-cicloid.py --predict

 

図6. 予想したサイクロイド

ステロイド

ステロイドはこのように表現され、一定の大きさよりは大きくなりません

 

図7. アステロイド

媒介変数をもちいた表現はこのようになります

 

学習

$ python3 20-train-asteroid.py --train

評価

trainで使用しなかったデータを使用して予想します

$ python3 20-train-asteroid.py --predict

 

図8. 予想したアステロイド(たまにyが0にドロップする)

対数らせん

対数らせんはこのように表現されます。無限に大きくなるので、媒介変数thetaを一定の大きさでストップします

 

図9. 対数らせん

媒介変数を用いた表現はこのようになります

 

対数らせんの図

図10. 予想すした対数らせん(微妙に歪んでいる)

コード説明

Githubからどなたでも確認することができます
10-prepare.py
上記の例で示した図を描くような数字の系列のデータセットを出力します
以下のファイルが生成されます

サイクロイド.pkl
アステロイド.pkl
カージオイド.pkl
対数らせん.pkl
リサージュ.pkl
インボリュート.pkl

20-train-asteroid.py
ステロイドを学習&予想します
20-train-kardio.py
カージオイドを学習&予想します
20-train-cicloid.py
サークロイドを学習&予想します
20-train-spiral.py
対数らせんを学習&予想します

まとめ

  • いろいろな媒介変数でないと表現が難しい関数をy = f(x)でない、系列予想の問題設定とすることで簡単に予想することができそうだとわかりました
  • とにかく時系列でもなんでも連続した数字として表現できるのであれば、(モデルが十分に大きければ)予想が可能そうです
  • 仮定や解析プロセスを挟まずに学習&予想することができるので、楽です

参考文献

Microsoft Bingのランキングエンジンをxgboostでシミュレート

Microsoft Bingのランキングエンジンをシミュレートし、ランキングを学習します

目的

inspector(検査官、監査人、検閲官、視学、警視正、警部補)

ランキングアルゴリズムは日々進化しています。Googleサーチエンジン200以上の特徴量を用いたり色々しています。

これらはGoogleでないと手に入らない特徴量も多数存在しており、容易に、ユーザが最適化できるものではなかったりします

わかりやすいものでは、ドメイン以内にコンテンツが十分に存在し、それがある程度参照されるものであれば、以前あったようにWelqさんのようにコンテンツの内容の是非によらず、ランクアップしてしまうような問題もございました。

意図しない作用をもたらすから、狙ってはいけないなどということはなく、SEOはビジネスにおいて極めて重要な課題です。  

SEOでどの要素(サイト規模?テキスト数?キーワードの作り?コンテンツの内容?)などどれにどの程度注力すればいいのか判明したら大変なビジネスインパクトがあります。

ここでは、クリエイティブのキーワード(title, meta, body)で何がどの程度重要か、BingさんやGoogleさんなどを教師データとしたときに、機械学習アルゴリズムでサイトを、どの程度有益なのか定量化してみようと思います。  

お題

Rankingエンジンのランクの傾向を、検索クエリ結果から、ランキンされたサイトの自然言語的特徴から、獲得しようという意図です。

これを行うにあたって1つの制約があります。

1. ランキングエンジンのリバースエンジニアリングのような行為は認められるのか

  1. に関して述べると、この行為は、例えばGoogleやBingなどのサーチエンジンの競合を作るという意図がない、広域に解釈すれば、ビジネスでなければよいと言えそうである。例えば今回は、自己研究に基づくので問題がない。  

Microsft Bingのランキングエンジンのクエリ(neologdに内在している辞書を利用)することによって、膨大なダイバシティの検索結果が得られます。
検索結果のランキングを見ることにより、同等のランキングエンジンが作成可能であると期待できます

ランキングアルゴリズム一覧

読むべき論文

よく見る評価指標

目的関数

ranksvmフォーマット

資料が全くなく、調査して理解するまで結構かかりました。
ranksvmフォーマットはgroup fileというのが別途必要になっている

bingのデータクローン

  • neologdで一般単語をとりだす
  • bingでクエリを作成して、ひたすら大量に集める
  • 1位=4, 2位=3, 3位=2, 4位=1で、単一ページでないドメイントップのサイトをランキングする  
  • 言語処理的にtitle, meta, bodyの自然言語でランキングしてみる

(本当はこれ+DeepLearningでやってもいい)

オペレーション

Githubで公開しています(問題等があればtwitterにておしらせくださいませ)

Operation 1. ランククエリ生成

neologdなどからnoun(名詞)を取り出して、それを検索クエリ群にする

import glob
import pickle
import json 
nouns = []
for name in glob.glob('mecab-ipadic-neologd/build/*/*.csv'):
   f = open(name)
   for line in f:
     ents = line.strip().split(',')
     if '名詞' not in ents:
       continue
     term = ents[0]
     nouns.append(term)
open('nouns.json', 'w').write( json.dumps(nouns, indent=2, ensure_ascii=False) )

Operation 2. bingをスクレイピング

$ python3 scrape.py

Operation 3. フルドメインが入っているリンクをパース

$ python3 scan_pair.py

Operation 4. bingの結果からフルドメインスクレイピング

$ python3 pair_scrape.py

Operation 5. フルドメインのHTMLをパース

$ python3 10-parse-htmls.py

Operation 6. 分かち書きして特徴量化

$ python3 20-make-vector.py

Operation 7. 疎行列で表現してranksvm形式のファイルを出力

$ python3 30-index.py

学習

OP7を実行すると学習可能なファイル群が出力されます
(xgboostのバイナリがlibcなどの互換がなくて実行できない場合は、xgboostを再コンパイルしてください)

$ cd rank
$ ./xgb mq_train.conf 

map(mean average precision)の略で、平均精度です。pairwiseで評価すると、mapでの評価になります。他の関数のndcgなどはうまく動作しません。なぜ?

1000roundでの精度はこの程度です

[20:36:05] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 936 extra nodes, 1252 pruned nodes, max_depth=36                                                                                 
[20:36:05] [999]        test-map:0.721291        

学習が完了すると、rank.modelというファイルが出力されます

予想

rank.modelをもちいて未知のクエリに対してランキングすることができます

$ ./xgb mq_predict.conf 
[20:51:20] 144775x162216 matrix with 3899318 entries loaded from test.data
[20:51:20] start prediction...
[20:51:49] writing prediction to pred.txt

pred.txtの中にランキングされたファイルが記されています。

ランクの値と、実際のランクはこのように表現され、やはり相関など何も考えなくても単純にランキングが上になりがちな構成という物がありそうです
(予想ランク@左、実測ランク@右)

1.05754 4
-0.578957 2
0.386893 0
0.511651 4
0.683687 2
-0.800342 0
0.642033 4
0.35129 2
0.74472 0
1.39248 4
0.454038 2
0.221512 0
...

単純なSVMなどを利用すると、簡単に配信システムで計算できますが、勾配ブーストのようなランキングはどうなんですかね。決定木なのでC++のファイルなどに変換させるのが良いと思います

xgboost, lightgbmランキング意外と便利

ランキング学習はレコメンドエンジンとも深く結びついた技術でマネタイズのコアをなしうる重要なテクノロジーです

AIで自動判別でなんかすごいのを作る前の前哨戦で、レコメンドはぜひとも検討に値するテクノロジーです  

おまけ

勾配ブーストによる特徴量選択の結果を見ることができます

強力に非線形化されているので、これ単独で見ることにあんまり意味はないのですが、どんな特徴量が選ばれやすいのかは視覚的に確認できます

例えばランキングに影響する単語群はtitleとkeywordの重要度が高いのはなるほどという感じなのですが、車なども強いことがわかります

title:インポートファッションアイテム 1989114     
title:Sledge 680939                              
title:バンキング 465049                          
title:西善 314689                                
title:メイプルストーリー 188914                  
keyword: ベローズ の 専門 メーカー  180548       
title:ラセン 153568                              
title:DV 86233                                   
keyword:建築 資材  68659                         
title:あぜ 68124                                 
body:none 60321                                  
keyword: クロス ロード  51767                    
title:Shade 46926                                
title:食通 44635                                 
body:, 43729                                     
keyword:競艇  38174                              
keyword: 常滑  32940                             
title:多摩丘陵 29434                             
title:cherry 25848                               
body:MAZDA 25152                                 
title:の 23459                                   
title:エビス 23234                               
title:本田技研工業 23085                         
body:Jewelry 22530                               
title:・ 21902     
...

brand-transition(ブランドの変遷)

brand-transition(ブランドの変遷)

SVMで極性評価した単語をもちいて、声優のポジティブネスをniconicoニュースコーパスを利用して、時系列的に表現します

時系列的な変遷を分析することで、バズの検知や、話題、炎上などをシステム的にキャッチアップしやすくなります

もともとの起草

これは、もともともブランドの毀損などを評価しうるものとして、prophetなどの系列予想システムと連結させることで、急激なブランド毀損(炎上)などを検知して、炎上に対して適切な対応をとることにより、ブランドイメージを守ろうとするものでした(これは個人研究でやっていたのですが、3ヶ月ほど前にいろいろなところに提案しましたが、どこにも引き取ってもらえてないので、いくつか再スクラッチして自分の所有しているデータセットに変換し、公開ナレッジ化します)  

Amazon, RakutenなどのレビューをコーパスSVMなどで大規模極性辞書を作成する

もともとブランドのネガティブさ、ポジティブさというのはどのように評価すればいいのかわかりません。

この時、SVMなどをレビュー情報(星の数)などを教師データに、極性を重んじた学習の分割が可能になります  

星の数を1,2,3個をネガティブとして、5個をポジティブとすると、非常にそれらしい結果が得ることができます。

学習した結果のウェイト

negative度が高いもの

売り手 -4.393270938125656
ガッカリ -3.947897992103171
がっかり -3.648103587594949
残念 -3.130127382290406
致命 -3.112032623756082
期待はずれ -3.083929660401355
返品 -2.948516584384151
灰皿 -2.87787780959521
3つ -2.783511547585251
駄作 -2.771137686778778
物足りなかっ -2.663694159049179
最悪 -2.655338531812268
うーん -2.499197140760456
二度と -2.480062770645911
意味不明 -2.420151065200296
いまいち -2.419859090879487
失せ -2.41635935742869
つまらない -2.404018144393537
単調 -2.368271765218191
堀江 -2.34675269334997
つまらなかっ -2.316511096716675
タートル -2.288030642964168
イマイチ -2.27754859957102
目新しい -2.269002906892812
まずい -2.23071763111786
欠ける -2.222289341852733
...

positive度が高いもの

お買い得 1.987645060840108
最高傑作 1.990702627997584
別格 2.011164421643425
目からウロコ 2.014549379784212
これ程 2.019119980229243
杞憂 2.026934292869131
戻れ 2.052378639149482
オジさん 2.061753308187434
痛快 2.061960603545156
欠か 2.087942969787786
叩け 2.08933 2.130187229517098
pop 2.1307765382222862
病みつき 2.142924963400594
憎め 2.160722430925575
洋楽 2.169574554504112
強いて 2.224321728820268
バイブル 2.229368722596254
絶妙 2.245596441611436
従う 2.254309352677592
五つ 2.255604793487576
待ち遠しい 2.25806614954727
逸品 2.265821501069568
最高 2.289466171874273
傑作 2.293924930835376
サウンドトラック 2.295214513832636
是非 2.311342110589227
半信半疑 2.39707658523505
ガル2.661651342883942922
R&B 2.6851915989280470909009
一括 2.730196185564076
リッツ 2.766706018272894
大正解 2.874333983897774
手放せ 2.948887203551322
必読 3.012749694663035
セレクトショップ 3.050995942436786
5つ 3.081741024496506
秀逸 3.229761176207314
...

このようにそれらしい結果が得られます

データセットDropboxで公開していますので必要ならばご利用ください(商用利用は法的に望ましくないです)。

ある名詞(人名)が含まれているドキュメントが含まれているドキュメントのネガポジをその語の含まれている文脈の意味とする

仮説ベースで、ヒューリスティックにうまくいく方法ですが、このようにすることで、ドキュメントの極性と、ドキュメントに含まれる単語の紐付けを行うことができます  

 

図1.

時系列的にドキュメントを並べて、ある人名のポジティブ度をSVMで作成した重みを利用して時系列順に並べる

niconicoニュースのコーパスは、毎日、ある程度更新されており、ライセンスの関係かわかりませんか、結構昔に遡れます(新聞社は多くの場合、過去に遡ることができません)  

そのため、コーパスを日時の粒度でまとめて、並べ直して、その時のniconicoニュースのコーパスを利用すると、下記の図にすることができます。  

SVMでレビュー情報を学習した単語に付与される重み情報から、ドキュメントのスコアを算出します(強調的に状態が見えるようにいくつかヒューリスティックにしてあります)  

 

図2.

実験結果

100日以上、話題にされている声優でかつ、私がわかる声優さんが含まれるニュース記事の極性を判定して、sigmoidにかける前の重みで、重みのリストを手動でいくつか編集した物を用いて、日付順で極性のスコアの変遷を見ていきます。

声優を題材としたのは、単純に、niconicoニュースがオタクよりのコンテンツが強いからであって、それ以上の他意はございません  

図3. 早見沙織の時系列データ

図4. 水瀬いのりの時系列データ

図5. 日笠陽子の時系列データ

単純に日々のニュースでポジティブに紹介されたようなことがあると、グラフ的に跳ねる現象になり、なんらかの異常値検知システムにて自動化もできそうです

コードとオペレーション  

niconico newsなどのコーパスは各自でご用意ください

(スクレイパーもありますが、1secのsleepを入れていますが、そもそもどの程度許容されるものなのかよくわかっていません)

レビュー情報をパース  

$ python3 10-scan-reviews.py

liblinear(sigmoid)で学習したウェイトを分析して、単語の重み付きファイルを構築する  

$ python3 20-scan-weights.py 

niconico newsを時系列で処理する  

$ python3 30-news-time-series.py  

声優名が含まれるコーパスを重み付き単語ファイルで評価

$ python3 40-actor-time-series-polarity.py

各々の声優名のデータを取り出す

$ python3 50-data-pick.py 

結果

このように、時系列データして扱うと、ところどころ、ポジティブさみたいなものが跳ねるような事象を観測することができて、プラスの意味での炎上(ただの話題ですが)を観測したりアラートを流すことができたりしそうです。
声優統計みたいな感じになりましたが、他意はございません。niconicoニュースとオタク関連の記事の相性がよかったので題材をこのように定義しました。

ポアンカレエンベッディング

ポアンカレエンベッディング

Euclid空間にエンベッディングするようなword2vecは意味の上下関係が明示的に記されません。(情報としたあったとしても僅かでしょう)

ポアンカレボールという双曲幾何学空間に埋め込むことで、効率的に意味(や木構造)の上位関係をとらえることができます[1]

理解

 ポアンカレボールはこのような、外周部に行くほど密になる球みたいなものなのです。

図1. ハニカム構造のPoincare Ball(Wikipediaより)

ポアンカレボールでは外に行くほど情報が密になり、空間が広がっているともとらえます。

数式で表現するとこのようになって、

gEというユークリッド距離がxが1に近づけば無限に大きくなることがわかります。

このポアンカレボール上にある二点間の距離はこのように表現され、単純なユークリッド距離ではないことが見て取れます。

この距離関数に基づいて損失関数L(Θ)を定義します。

これをSGDやFBリサーチの論文ではRiemannianSGDというオプティマイザを利用して最適化しています

双極幾何学空間に埋め込むと、情報が何らかの上下関係を持っており、木構造で表現できるとき、ルートノード(つまり抽象度が高い)方が真ん中にきて、枝葉に近いほど、外周部に行く傾向があるとのことで、これはポアンカレ空間に木の幹と枝を配置しようと試みるとき、幹が真ん中にきて枝が外周に来るとおさまりがいいのは、直感的に理解できると思います。

なんか以前やったDeep Graph Convolutionに似ているなと思っていたら、Abejaさんのブログでも同様の記述を発見しました[2]  

何でもいいので、優秀なエンベッティングは距離空間を適宜定義することでその空間の形に応じた特性を獲得できそうということでもあり、ポアンカレボールは意味的、木構造的な関係を獲得できるし、別の距離空間ではそのようなものが定義できるのだと思います。

以下の実験のコード

実験

pythonの言語処理ライブラリであるgensimですでにpoincare embeddingが実装されています。

可視化の方法もjupyterを用いて簡単にできるので、なんかの情報のペアで木構造を持つと仮定できるデータセットがあれば、学習することができます。

poincare embeddingの論文では、動物名と動物の所属する種類などのペアのデータセットを利用して、poincareに埋め込んで評価しています。

(ライオン, ネコ科)
(ネコ科, 哺乳類)
...

こんなどちらが上位の概念化は順序がむちゃくちゃでいいので、ペアのデータセットがたくさん作成します

データセット

旧日本海軍の艦種のいくつかのデータセットを用いてやってみましょう。

軍艦を発信点にして、大分する艦種と、艦種と艦名のペアです。

軍艦    駆逐艦
軍艦    戦艦
軍艦    巡洋艦
軍艦    空母
駆逐艦  陽炎型
駆逐艦  朝潮型
駆逐艦  吹雪型
駆逐艦  白露型
朝潮型  朝潮
朝潮型  大潮
朝潮型  満潮
...

可視化

gensimでの実装は、jupyterとplotlyを用いてこのようにすることで、簡単に可視化できます
(おそらくFacebook ResearchのPyTorchでの実装は同じ方法は通用しませんが、こっちの方が本来性能はよさそうです)

import plotly
import gensim.viz.poincare

import pickle
model = pickle.loads(open('model.pkl','rb').read())
relations = pickle.loads(open('relations.pkl', 'rb').read() )

plotly.offline.init_notebook_mode(connected=False)
prefecutre_map = gensim.viz.poincare.poincare_2d_visualization(model=model,
                                                               tree=relations,
                                                               figure_title="艦種",
                                                               show_node_labels=model.kv.vocab.keys())
plotly.offline.iplot(prefecutre_map)

評価

ポアンカレボール上に配置するときに、乱択アルゴリズムでサンプリングするらしいので、小さいデータセットではきれいな円状に配置されませんが、おおむねそれらしい結果が得られています。(割り切ってランダムネスの影響を許容するといいでしょう)

朝潮型が変なところに来ています。初期値のラムダムネスにより変なところにきているようです

なかなかgemsinで実行するとうまくいきませんね。(最適化アルゴリズムSGDなどの乱択がはいるので仕方がないですが。。。)

より、細やかに丁寧に距離函数を再定義できそうな実装系としてFacebook社のPytorchのSGDオプティマイザと距離函数を双曲線距離に定義した物を使うと、learning rateを最初に大きく取って少なくしていくなどの戦略が取れるので、より細やかに綺麗に学習が収束しそうです[3]  

Facebook ResearchによるPoincare Embedding

参考文献