クイックノート

ちょっとした発見・アイデアから知識の発掘を

【R】複数の評価軸でランキングを作る【keras】

統計データを使ってランキングを作る時、
複数の尺度があると、
こっちの尺度では、Aの方がランクが高いけど、
別の尺度だと、Bの方がランクが高いなど、
ランクが逆転することがあります。

大まかには、同じような傾向を拾っていても、
微妙な数値の違いでランクが上下してしまうとき、
どっちの評価軸を使えばいいのかという悩ましい問題を抱えます。

いっそのこと、どの軸も使ってやれとなったりもするのですが、
よくやる方法だと、軸に重みを振ってスコアを合計して、
合計スコアでランク付けするというものですが、
重みをどうするのかという問題にぶつかります。

さて、この似たような尺度なのに、
ランクが変わってしまう時、
どのようにランキングを作るのが良いのでしょうか。

一つの考え方としては、
どの軸を使ったランキングとも
そんなに順位が変わらないランキングを作るという方法が考えられます。

ということで、この方針のもとディープラーニングを使って、
複数の軸をそれなりに混ぜたランキングを作ってみましょう。

ランクを学ぶ

さて、ディープラーニングでランキングを作りたいのですが、
そもそも、どうやって、
ディープラーニングにランキングを学ばせれば良いのでしょうか。

単純には、ランク付けをする対象A,B,C,D,...に対して、
それぞれが、何位かを教えれば良いと考えられますが、
順位をただの数字として教えると、
数字の大小関係も教えないと、
1位と2位は近いけど、
1位と10位は遠いなどの関係が反映されません。

ランキングに重要なのは、順序関係ということですね。

ということで、順序関係を教えることで、
ランキングを学ぶことにしましょう。

R でランキングを学習する

それでは実際にランキングを学習させてみましょう。

学習の手順は、
順序関係の教師データを生成して、
それをモデルにフィッティングするという流れになります。

教師データの生成

AとBについて、 A \gt B を判定する人工知能を作れば、
その結果からランキングを作ることができそうです。

ということで、手持ちのデータから、
ディープラーニングの学習用の順序データを生成してみましょう。

持っているデータの全てのペアA,Bについて、
key属性で比較して、Aの方が大きければTRUE
そうでなければFALSEとなるように、データを生成します。

make_data = function(data,key){
  n = dim(data)[1]
  s = apply(data,2,max)
  tmp = apply(expand.grid(1:n,1:n),1,function(x){
    A = data[x[1],] / s
    B = data[x[2],] / s
    list(A=A,B=B,y=A[key] > B[key])
  })
  
  x = t(sapply(tmp,function(x){unlist(c(x$A,x$B))}))
  y = sapply(tmp,function(x){x$y})
  return(list(x=x,y=y))
}

これで、データフレームdataないの全てのデータのペアについて、
key属性で比較した時の結果をyに、
データのペアをxとして、
教師データの作成が行えます。

モデルの生成

モデルはこれでないといけないというものはありませんが、
とりあえず、以下のようなモデルを生成してみました。

make_model = function(n_feat){
  model = keras_model_sequential()
  
  model %>%
    layer_dense(units=64,activation="relu",input_shape = n_feat*2) %>%
    layer_dropout(rate=0.6) %>%
    layer_dense(units=128,activation="relu") %>% 
    layer_dropout(rate=0.4) %>% 
    layer_dense(units=32,activation="relu") %>%
    layer_dropout(rate=0.2) %>%
    layer_dense(units=1,activation="sigmoid")
  
  model %>% compile(loss="binary_crossentropy",
                    optimizer = optimizer_adam(),
                    metrics = "accuracy")
}

n_featはデータフレーム中の各データの属性の数です。

このモデルで、順序関係がちゃんと学べるかどうかは後程テストします。

モデルを学習する

後は、教師データを基に学習を行うだけです。

本題は、複数の評価軸でのランキング生成なので、
軸aを使った場合の順序関係と、
軸bを使った場合の順序関係といったように、
それぞれの軸で異なった順序関係をまとめて教師データとして渡します。

train_full = function(model,data,keys,epochs){
  train_data = lapply(keys,function(key){
    make_data(data,key)
  })
  train_x = abind::abind(lapply(train_data,function(x){x$x}),along=1)
  train_y = abind::abind(lapply(train_data,function(x){x$y}),along=1)
  
  model %>% fit(train_x,train_y,epochs=epochs)
}

keysはランキングを作る際に利用したい全ての評価軸の属性名です。
それぞれの属性について、make_dataを実行して、
生成された教師データをマージして、学習に渡しています。

ランキングの生成

上の学習を行うことで、順序関係を学んだニューラルネットワークが生成されます。
つまり、AとBのペアを入力すると、どちらがより順位が上かを教えてくれます。

最終的な目的は、ランキングを作ることなので、
この順序関係をランキングに翻訳する操作が必要です。

やることは簡単で、全てのペアについて、順序を比較して、
大きいと判定されることの多いデータほど上位に持ってくればオッケーです。

その操作を下の関数で実行します。

pred_ranking = function(model,data){
  n = dim(data)[1]
  s = apply(data,2,max)
  id_pair = expand.grid(1:n,1:n)
  tmp_x = apply(id_pair,1,function(x){
    A = data[x[1],] / s
    B = data[x[2],] / s
    c(A,B)
  })
  tmp_x  = t(sapply(tmp_x,function(x){unlist(x)}))
  gt = model %>% predict(tmp_x)
  
  comp_df = cbind(id_pair,gt)
  score = sapply(1:n,function(i){
    sum(comp_df[comp_df[,1] == i,3] > 0.5)
  })
  
  data[order(score,decreasing = T),]
}

学習済みのモデルmodelと、元のデータフレームdataを渡すことで、
学習された順序関係通りに並び替えたデータフレームが返ってきます。

テスト実行

モデルは雑に決めましたが、
本当にそのモデルで、順序関係を学習できるかどうかについて、
簡単に調べておきます。

方法

過去記事で、趣味に関するツイートのログデータを集めたものがあるので、
ディープラーニングで、
一日当たりのツイート数の多い順に並び替えてもらいましょう。

https://clean-copy-of-onenote.hatenablog.com/entry/hoby_rankingclean-copy-of-onenote.hatenablog.com

一個の軸でランキングを作るので、
正解と比較してみることができあます。

結果

下のグラフは、学習された順位通りに横に並べて、
それぞれの1日当たりのツイート数をプロットしたものです。

f:id:u874072e:20181105150040p:plain:w400

黒丸がディープラーニングで学習した順序、
赤線が正しい順序で並べたものです。

グラフから、ほぼ正しい順序で並び替えれていることが分かります。

モデルはこれで十分そうですね。

実行例(ツイート上の趣味ランキング)

それでは、本題の複数の評価軸を使ってランキングを作ってみましょう。

過去の記事で収集した趣味に関するツイートの、
1日当たりツイート数、いいね数、リツイート数を測定したデータを使って、
この3つの軸で趣味のランキングを作ります。

https://clean-copy-of-onenote.hatenablog.com/entry/hoby_rankingclean-copy-of-onenote.hatenablog.com

実行コード

上で定義した関数を使って、
次のコードを実行することで、
順序の学習、順位による並び変えを行います。

変数xは並び替えの対象となるデータフレームです。

model = make_model(dim(x)[2])
train_full(model,x,c("tw","fav","rt"),epochs = 100)
ranked = pred_ranking(model,x)

このコードではデータフレーム中の
tw,fav,rtの三つの属性を使ってランキングを生成しています。
並び替えられた結果はrankedになります。

とりあえず100エポックの学習をしたところ、
下のような学習過程になりました。

f:id:u874072e:20181106091831p:plain

まだ、ロスが下がってきていますが、
精度がそこまで変わらないので、この辺で終了しておきます。

この時の精度は、3つの軸での順位関係にどれだけマッチしているかで、
85%ならそこそこいい感じでしょうか。

結果

それでは、ツイート数、いいね数、リツイート数の3つをまとめてみた
ツイート上での趣味のランキング結果を発表します。

順位 趣味
1 剣道
2 囲碁
3 歌舞伎
4 工場見学
5 紅茶
6 SNS
7 韓流
8 イラスト
9 心理学
10 サバイバルゲーム

意外なのは、工場見学が上位に来ていることでしょうか。
あまり、身の回りで工場見学に行くと聞くことはないイメージですが、
工場見学は「ツイッター映え」するということでしょうか。

各評価軸の値

上のランキングで気になるのは、
このランキングで、複数の評価軸を総合したいい感じのランキングが得られているかです。

ということで、ランク順に各評価軸の値をプロットしてみましょう。

f:id:u874072e:20181106101650p:plain

見やすさのために、各評価軸は最大値が1となるように正規化しています。

いいねの数とリツイート数はほぼ順位通りに並んでいるのに比べて、
ツイート数は順位の入れ替えがそれなりにあることが分かります。

いいねとリツイートについては、どちらも、他者からのリアクション数なので、
似たような傾向があり、まとめやすい評価軸と言えます。

一方で、ツイート数は、その人が発信したいかどうかの一人称的な軸なので、
上の二つの軸とは少し異なった傾向を指した軸だと言えます。

なるべく、順位の入れ替えが少なくなるように学習が進むので、
統合しやすいものの順位をなるべくそのままにしてまとめ、
ちょっと外れた軸はそこそこ大胆に順位を変えるという結果になったのでしょう。

このことから、似たような評価軸を統合してランキングを作るという意味では、
この方法は有効に働きそうです。

まとめ

複数の評価軸でのランキングをAIに作らせるということをやってみました。

非常にシンプルな方法で学習させましたが、
そこそこ良さそうなランキングが得られているのが、
現在のディープラーニングのすごさの一端を感じさせてくれます。

プライバシーポリシー