廿TT

譬如水怙牛過窓櫺 頭角四蹄都過了 因甚麼尾巴過不得

[RStan]項目反応理論の応用でフリースタイルダンジョン登場ラッパーの強さをランキングしてみた

分析対象

フリースタイルダンジョンはフリースタイル(即興)のラップバトルで、チャレンジャーがモンスターと呼ばれる強豪ラッパーを勝ち抜き、賞金獲得することを目指すテレビ番組です。

データは、
フリースタイルダンジョン 結果 勝敗 全試合 - 戯言
からもらいました。

成形したデータは以下に置いておきます。

FSD.txt · GitHub

Score1 がチャレンジャーのスコア、Score2 がモンスターのスコアです。

ぼくは Web の知識がなさすぎてスクレイピングできないので、エディタでちまちま置換して成形しました。

だれか rvest の使い方を教えてください。

目的

ラッパーの強さを表す素朴な方法として、スコアの平均を出すことが考えられます。

しかし、これだと強い相手と当たった場合も、弱い相手と当たった場合も、スコアを同じ重みで評価することになります。

また、順序尺度のデータを単純に平均するのはちょっと不安です。

そこで対戦相手の強さを踏まえ、スコアを順序尺度のまま扱って評価するモデルを考えました。

項目反応理論という分野では、なんらかのテストに対して、被験者の能力パラメータと問題の難易度パラメータを両方推定することを行います。

今回提案するモデルは項目反応理論のモデルを少し変形したもので、能力パラメータがチャレンジャーの能力パラメータに、難易度パラメータがモンスターの能力パラメータに対応します。

順序ロジスティックモデル

以降、単にスコアという場合、チャレンジャー側のスコアを指すことにします。

勝敗は 5 人の審査員が決めるので、スコアは 0 から 5 点です。

ラッパーはそれぞれバトルのスキルパラメータ \eta_i (i=1.\ldots,n) を持つものとします。

チャレンジャーはロジスティック分布にしたがってパフォーマンスを発揮し、それがスコアとして現れると考えます。

このときロジスティック分布の平均は、チャレンジャーのスキルパラメータから、立ち塞がるモンスターのスキルパラメータを引いたものになるとします。

f:id:abrahamcow:20170422151936p:plain

パフォーマンスが閾値を超えるとチャレンジャーにスコアが入ります。

0 から 5 の評価なので閾値も 5 つあります。

この閾値c_1,\ldots,c_5 とします。

例えば、スコアが 3 だとしたら、チャレンジャーが発揮したパフォーマンスは、c_3 からc_4 の間だったということになります。

パフォーマンスそのものは観測されないので、尤度は区間打ち切りデータ(離散時間データからの指数分布のパラメータの最尤推定 - 廿TT)を考えているのと同じことになります。

このスコアの分布を離散型の確率関数として捉えてやると順序ロジスティック分布と呼ばれるものになります。

さてモデルには未知パラメータ、スキル  \eta_i どうしの引き算が入っており、このままでは識別不能です。

たとえば、敗者のスキルが 1 で勝者のスキルが 2 だとすれば引き算すれば 1 です。平行移動して敗者のスキルが 2 で勝者のスキルが 3 だとしても引き算すれば 1 で、どちらにしても結果はかわりません。

そこで、事前分布を入れることで基準を決めてやります。

今回は各選手のスキルパラメータはそれぞれ独立に、平均 0、分散 1 の正規分布にしたがうことにしました。

これはかなり強い仮定に見えるかもしれませんが、標準化された能力パラメータを求めているのだと考えれば、大きく一般性を失うものではありません。

事前分布を多少変えても、スキルの相対的比較という観点で矛盾した結果はそうそうでないと思います(本当のところはやってみないとわかりませんが)。

RStan

Stan の順序ロジスティック分布は最小値が 1 の分布なので、transformed data ブロックではスコアに 1 たして一個ずらしています。

そこは実装上都合のいいようにしているだけで、本質的ではありません。

parameters ブロックでは閾値 c_k が小さい順に並ぶように ordered 型で宣言して、制約を入れています。

// FSD.stan
data{
  int<lower=1> n;
  int<lower=1> m;
  int<lower=1,upper=n> m_id[m];
  int<lower=1,upper=n> c_id[m];
  int<lower=0,upper=5> score[m];
}
transformed data{
 int<lower=1,upper=6> K[m];
 for(i in 1:m){
  K[i] = score[i] + 1; 
 }
}
parameters{
  real eta[n];
  ordered[5] c;
}
model{
  eta ~ normal(0,1);
  for(i in 1:m){
   K[i] ~ ordered_logistic(eta[c_id[i]]-eta[m_id[i]],c); 
  }
}

R から Stan をこのように操作します。

library(dplyr)
FSDdat <- read.table("FSD.txt",stringsAsFactors = FALSE)
name <-unique(c(FSDdat$charanger,FSDdat$monster))
FSDdat <-FSDdat %>% 
  mutate(c_id = sapply(charanger,function(x)which(x == name)),
         m_id = sapply(monster,function(x)which(x == name)))
n <- length(name)
m <- nrow(FSDdat)
dat4stan <- list(n=n,m=m,m_id=FSDdat$m_id,c_id=FSDdat$c_id,score=FSDdat$Score1)
library(rstan)
rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())
FSDmodel <-stan_model("FSD.stan")
FSDfit <-sampling(FSDmodel,dat4stan)

結果のとりだし

\eta_i の推定値(事後期待値)と95%ベイズ信頼区間をデータフレームに格納します。

etahat <-get_posterior_mean(FSDfit,pars="eta")[,"mean-all chains"]

est <-data_frame(name=name,etahat=etahat,
                 lower=summary(FSDfit,pars="eta")$summary[,"2.5%"],
                 upper=summary(FSDfit,pars="eta")$summary[,"97.5%"]) %>% 
  arrange(desc(etahat))

事後期待値で見て、バトルスキル上位10名のラッパーは以下のようになりました。

library(cowplot)
theme_set(theme_cowplot(font_family = "HiraKakuProN-W3"))

p1<-ggplot(est[1:10,],aes(x=reorder(name,etahat),y=etahat,ymin=lower,ymax=upper))+
  geom_pointrange()+
  geom_hline(yintercept = 0,linetype=2)+
  coord_flip()+
  xlab("")
print(p1)

f:id:abrahamcow:20170422024726p:plain

丸が事後期待値、棒が95%ベイズ信頼区間を表します。

GADORO が上位に来てるのはちょっと意外でしたが、ぼくは GADORO 好きなので嬉しい。

また R-指定を一発クリティカルで倒した崇勲のスキルがR-指定より下に推定されています。

これは対戦回数が少ない場合、事前分布の平均方向に推定値が縮む(シュリンクする)ためだと考えられます。

この性質は「オーバーフィッティングを避けている」と捉えることもできるし、「事前分布の影響が強く出てしまっている」と捉えることもできます。

モンスターの強さランキングは次のようになりました。

monster_rank <- dplyr::filter(est,name %in%
                                c("般若","R指定","漢","サイプレス上野","T-PABLOW","DOTAMA","CHICOCARLITO"))

p2<-ggplot(monster_rank,aes(x=reorder(name,etahat),y=etahat,ymin=lower,ymax=upper))+
  geom_pointrange()+
  geom_hline(yintercept = 0,linetype=2)+
  coord_flip()+
  xlab("")
print(p2)

f:id:abrahamcow:20170422024806p:plain

今のところ一度も負けていない般若が、トップに位置しています。

ただし、対戦回数が少ないため、ベイズ信頼区間の幅は広めになっています。

R-指定はベイズ信頼区間の下限が 0 から明らかに離れており、平均的なラッパーより強い、ということがかなり確信をもっていえます。

この結果は背景知識(ぼくの実感)と一致しています。 R-指定が強いことは多くの人が認めており、たとえば hitode909 氏も「R-指定っていう人がうまくて聴いててたのしい」(土曜日 - hitode909の日記)と言及されています。

全ラッパーの推定値を以下の表にまとめました。

name etahat lower upper
般若 1.49 -0.05 2.99
R指定 1.43 0.43 2.42
崇勲 1.26 -0.04 2.57
GADORO 1.16 0.04 2.28
焚巻 1.15 0.00 2.28
スナフキン 1.03 -0.27 2.30
掌幻 1.01 0.04 1.98
HIDADDY 0.93 -0.32 2.15
龍道 0.92 -0.23 2.05
Mr.Q 0.90 -0.62 2.41
押忍マン 0.75 -0.54 2.11
KOPERU 0.72 -0.42 1.84
GASHIMA 0.66 -0.54 1.83
DOTAMA 0.64 -0.24 1.55
ACE 0.55 -0.93 2.00
TKda黒ぶち 0.25 -0.83 1.33
CIMA 0.23 -0.74 1.19
USU 0.20 -1.18 1.60
CHICOCARLITO 0.08 -0.79 0.95
NONKEY 0.04 -1.40 1.52
DragonOne 0.01 -1.15 1.24
T-PABLOW -0.05 -0.92 0.81
言×THEANSWER -0.11 -1.55 1.28
SALVADOR -0.16 -1.50 1.16
-0.18 -1.10 0.75
D.D.S -0.23 -1.51 1.04
サイプレス上野 -0.26 -1.12 0.61
LEONa.k.a.獅子 -0.26 -1.89 1.38
MC☆ニガリa.k.a赤い稲妻 -0.40 -1.70 0.86
RYOTA -0.41 -1.81 0.99
GOMESS -0.42 -1.90 1.05
ENEMY -0.44 -2.24 1.34
RACK -0.52 -1.99 1.01
田中光 -0.56 -2.17 1.04
MCKUREI -0.60 -2.14 1.00
BALAa.k.aSHIBAKEN -0.63 -2.11 0.90
Lick-G -0.65 -1.90 0.64
輪入道 -0.67 -2.22 0.96
はなび -0.67 -2.22 0.92
REIDAM -0.70 -2.41 1.06
HELLBELL -0.71 -2.52 1.13
HIDE -0.71 -2.50 1.13
TAROSOUL -0.71 -2.47 1.09
句潤 -0.72 -2.48 1.05
YZERR -0.86 -2.39 0.81
MEGA-G -0.88 -2.48 0.73
KissShot -0.88 -2.47 0.71
黄猿 -0.92 -2.06 0.27
PONY -1.20 -2.84 0.43

open problem


1) 野球に関する分析、

のような形で、バトルMCの勝負ムラを推定できないでしょうか?

2) 対戦相手ごとの相性みたいなものを表現できないでしょうか?

3) 新ルールのチーム戦になって以降はどのようなモデルが考えられるでしょうか?

4) 普通、MCバトルでは後攻のほうが有利とされているので、後攻についての係数をいれてしまってもいいかもしれません。これは簡単な拡張です。

5) T-PABLOW は、はじめのころは正直ピンと来なかったんだけど、最近はかなりかっこいいと思う。能力の時間変化を表現できないでしょうか?

アマゾンアフィリエイトのコーナー

StanとRでベイズ統計モデリング (Wonderful R)

StanとRでベイズ統計モデリング (Wonderful R)

助演男優賞

助演男優賞

たりないふたり

たりないふたり

abrahamcow.hatenablog.com