Reports

Twitter: @mtknnktm.

未知の非線形な交互作用をマルチレベルモデリングでモデル化してみる

はじめに

なんか変なタイトルですが、この記事では次のような現象について考えます。

y \sim Normal(E(x_1, x_2), \sigma)
E(x_1, x_2) = \beta_0 + \beta_1 x_1 + \beta_2 (x_1) x_2

これはぱっと見は通常の重回帰モデルですが、よく見るとx_2の回帰係数\beta_2x_1の関数になっています。本記事ではこういったx_1, x_2 間の未知の(非線形かもしれない)交互作用について考えることを目的とします。

どういった状況でこんなよくわからないモデルを考えないといけないのでしょうか?
例えば、とあるシステムの開発プロジェクトの開発にかかる工数を分析するモデルだとして、目的変数 y が開発期間、説明変数 x_1 が開発規模(人数)、x_2が開発者のコミュ力だとしましょう(※ 単なる例なので現象の妥当性は気にしてはいけません)。

小規模(x_1が小)ならばコミュニケーションを取る相手が少なく文脈も常に共有しているため、コミュ力x_2の影響は小さいと考えられます。つまり、\beta_2(x_1)は小さくなります。
一方で規模が大きくなる(x_1が大きくなる)ほどコミュニケーションを取る相手が多く、かつ、文脈も共有されていないことが増えるため、コミュ力x_2の影響は非常に大きくなると考えられます。つまり、\beta_2(x_1)は非常に大きいと考えられます。

従って、\beta_2(x_1)は線形関数ではなく、例えば\beta_2(x_1)=\exp(a x_1)みたいな感じでx_1について指数的に増大したりしそうです。また、N次の多項式で表される極大値や極小値があるような複雑な形をしているかもしれません。ここでの目的はコミュ力x_2の開発期間への影響が規模によってどのように変わってくるか? (つまり\beta_2(x_1)の形)について知ることです。

モデル

では、上記の\beta_2(x_1)が未知の関数だとして、どうにかして\beta_2(x_1)の形を推定する方法を考えます。
この記事では\beta_2(x_1)x_1で適当に区切って、その区間内を線形関数で近似することを考えます。
すなわち、
E(x_1, x_2, c) = \beta_0 + \beta_1 x_1 + (\beta_{21c} (x_1 - m_c) + \beta_{20c}) x_2
という形で近似することを考えます。ここでcx_1が所属する区間、m_cはその区間の中央の値を表します。この場合、\beta_{20c}, \beta_{21c}は区間数L個分だけ推定する必要があります。x_1で階層分けをしたマルチレベルモデリングと言ってもいいと思います。

例えば、\beta_2(x_1) = (x_1 - 2.5) ^2で、区間数L=4のときは[x_2]の回帰係数である関数\beta_2は次のように近似されるイメージです。
f:id:swarm_of_trials:20150320005934p:plain:h200
二次曲線が4つの線形関数で近似されていることがわかります。

実験

それでは実際に未知の非線形関数beta_2(x_1)を線形関数で近似できるかやってみましょう。
ここでは\beta_2(x_1) = (x_1 - 2.5) ^2/5x_1 \in [0, 5]とし、\beta_0=1, \beta_1=2, \sigma=0.1とします。

以下にこのモデルを表すStanのコードを示します。

data {
  int<lower=0> N;   //データの数
  int<lower=0> C;   //区間の数
  real<lower=0> m; //x_1の最大値(5)

  int c[N];         //データが所属する区間id(1〜C)
  vector[N] x1; //説明変数x_1
  vector[N] x2; //説明変数x_2
  vector[N] y;   //目的変数
}
parameters {
  real beta0;
  real beta1;
  vector[C] beta2_1;
  vector[C] beta2_0;
  real s;
}
model {
  for(i in 1:N)
    y[i]~normal(beta0 + beta1 * x1[i] + (beta2_1[c[i]] * (x1[i] - m*(c[i]-0.5)/C) + beta2_0[c[i]]) * x2[i], s);  
}
generated quantities{
  vector[N] log_lik;  //対数尤度。WAICを求めるため
  vector[N] pred;     //予測値
  
  for (i in 1:N){
    log_lik[i] <- normal_log(y[i], beta0 + beta1 * x1[i] + (beta2_1[c[i]] * (x1[i] - m*(c[i]-0.5)/C) + beta2_0[c[i]]) * x2[i], s);
    pred[i] <- beta0 + beta1 * x1[i] + (beta2_1[c[i]] * (x1[i] - m*(c[i]-0.5)/C) + beta2_0[c[i]]) * x2[i];
  }
}

以下にテスト用のデータを生成して上記のStanを実行するRのコードを示します。ここでは区間数を5としています。※ 2015/03/23 使用ライブラリが書いてなかったので追記しました。

library(ggplot2)
library(dplyr)
library(rstan)
library(matrixStats)

#パラメータ
N <- 1000
beta1 <- 2
beta0 <- 1

#データ生成
d <- tbl_df(data.frame(x1=runif(N, 0, 5), x2=runif(N, 1, 5)))
beta2 <- (d$x1 - 5/2)^2 / 5
d <- d %>% mutate(y=rnorm(N, beta0 + beta1 * x1 + beta2 * x2, 0.1))

#区間作成
L <- 5
d <- d %>% mutate(cls=ceiling(x1 / (5/L)))

dat <- list(y=d$y, x1=d$x1, x2=d$x2, c=d$cls, N=N, C=L, m=5)

#MCMC
fit <- stan(file='ml.stan', data=dat, iter=3000)

上記のコードを実行して、結果を見てみましょう。Rhatは1.00付近でいい感じだったのでMCMCは収束したと判断します。

まず、モデルが正しい結果を推定できているか否か? について見てます。以下を実行することで予測値(pred)と実際の値(y)の散布図を書きます。

d %>% mutate(pred=colMeans(extract(fit, 'pred')$pred)) %>% ggplot(aes(y, pred)) + geom_point()

以下が、その結果です。モデルはフィットしてそうであることがわかります。
f:id:swarm_of_trials:20150320115104p:plain

それでは、真の関数\beta_2(x_1) = (x_1 - 2.5) ^2/5と求めた\beta_{21c} (x_1- m_c) +  \beta_{20c}を比較してみましょう。\beta_{21c}, \beta_{20c}は各区間ごとに中央値で評価しています。
f:id:swarm_of_trials:20150320115435p:plain
黒い線が真の関数、カラフルな直線が各区間で求めた線形関数です。二次関数が線形関数である程度近似されていることがわかります。

それでは区間数Lを変えてどんな感じになるか見てみましょう。
以下に区間数L=1, 2, 3, 4, 5, 10, 15, 20, 100としたときの(x_1, \beta_{21c} (x_1- m_c) +  \beta_{20c})を示します。
L=1では\beta_2(x_1)の特徴が全く表現できていないこと、Lが大きくなるに伴い、\beta_2(x_1)によくフィットしたことがわかります。ただし、L=100のように区間数が大きくなりすぎると過学習が発生してしまうこともわかります。
f:id:swarm_of_trials:20150320134512p:plain

以下に区間数LとそれぞれのモデルのWAICの値を示します。Lは1〜20と100について求めました。L=1から6までは急激に減少し、その後は似たりよったりで、L=100になるとL=6〜20よりも値が大きくなりました。ここから、L\in[6, 100)の間にWAICを最小にする値があることがわかります。そのため、WAICによってLを適切に決めることでモデルを決めることができそうです。
ただし、ここでのWAICが支持するモデルは必ずしも解釈しやすいとは限らないので、結果を見て適当にLを決めてしまってもいいんじゃないかなと思います(今回の件で言えばL=5ぐらいでも十分に傾向を見て取ることができます)。

f:id:swarm_of_trials:20150320134601p:plain:h250

ちなみにWAICは以下のようにして求める事ができます(多分合ってるはず(´・ω・`))。

log_lik <- extract(fit, 'log_lik')$log_lik
lppd <- sum(log(colMeans(exp(log_lik))))
p_waic <- sum(colVars(log_lik))
(waic.pp <- -2*lppd + 2*p_waic)

まとめ

ちょっと無理矢理感がありますがやってみたらできました。

データサイエンティスト養成読本 [ビッグデータ時代のビジネスを支えるデータ分析力が身につく! ] (Software Design plus)

データサイエンティスト養成読本 [ビッグデータ時代のビジネスを支えるデータ分析力が身につく! ] (Software Design plus)

ベイズ統計モデリング (統計ライブラリー)

ベイズ統計モデリング (統計ライブラリー)


稀にしか発生しない事象は何回観測すればいい?

低確率で発生する事象は、その稀にしか発生しないという性質上、それの発生確率を正確に知ろうとすると非常に多くの試行回数が必要です。低確率で発生しつつ、かつ、その発生確率を正確に知ることが重要なものとして、例えば、オンライン広告のクリック率(CTR)やコンバージョン率(CVR)などがあり、発生確率の正確な推定が重要な事が稀に良くあります。

例えば、事象の発生確率p=0.05のときにn回試行したときの平均を以下に示します。5回実行して試行回数nが少ないとばらつきが大きい(平均値があてにならない)ことがよくわかると思います。また、1000回ほど試行しても収束しきってないことがわかります。

f:id:swarm_of_trials:20150202230121p:plain

では、一体どのぐらいの試行回数nの時にどのぐらいの信頼性で真の事象発生確率pを推定できるのでしょうか? (どのぐらいのnであれば上図の各実行のばらつきが小さくなり、安心して評価できる事象発生確率pの推定値になるのでしょうか?)
簡単に計算できそうなのでやってみました。

確率pで1が発生し、確率1-pで0が発生するベルヌーイ試行を考えます。このとき、それの期待値はp、分散はp(1-p)になります(ベルヌーイ分布 - Wikipedia)。この試行をn回繰り返したとき、その期待値はE=p、分散はV=p(1-p)/nとなります(第12講:大数の法則・中心極限定理 (.pdf))。

よって事象発生確率がpの時に、ある信頼区間(S)が誤差e(比率)に収まるためには試行回数n

  • S \sqrt{V} = S \sqrt{p(1-p)/n} \leq ep

となります。これをnについて解くと

  • n \geq (\frac{1}{p}-1) (\frac{S}{e})^2

となり、試行回数nはこれを満たす必要があります。

例えば真の事象発生確率がp=0.05であるときに、95%信頼区間(S=1.96)が誤差10%に収まるためには、

  • n \geq (\frac{1}{p}-1) (\frac{S}{e})^2 = (\frac{1}{0.05}-1) (\frac{1.96}{0.1})^2 = 7299.04

となるので、7300回の試行が必要であることがわかります。そして、より正確な計測のために誤差eを減らそうとすると、neの二乗に反比例するため非常に多くの試行nが必要になることがわかります。

また、それにもかかわず、試行回数がn=200ぐらいしかなかった場合の誤差eは上式をeについて解いて代入すると

  • e = \sqrt{\frac{1}{200}(\frac{1}{0.05}-1) 1.96^2} = 0.604

となり、60%の誤差がありうるような状態でしか事象発生確率を把握できない事がわかります。

オンライン広告の分野では可能な限り少ないインプレッション(試行回数n)で正確にCTRやCVRを把握し、より効果的な広告を提供していくことが重要ですので、少ないインプレッションで正確にCTR/CVRを予測するための研究がなされています(Predicting Clicks: Estimating the Click-Through Rate for New Ads, WWW, 2007 / 日本語解説記事)。

サンプルサイズの決め方 (統計ライブラリー)

サンプルサイズの決め方 (統計ライブラリー)

アドテクノロジー プロフェッショナル養成読本 ~デジタルマーケティング時代の広告効果を最適化! (Software Design plus)

アドテクノロジー プロフェッショナル養成読本 ~デジタルマーケティング時代の広告効果を最適化! (Software Design plus)


複数階層間相互作用におけるタイムスケールの影響

概要

  • 多段創発について考えたくて、とりあえず、多段階の階層は創発されてる前提で階層間の相互作用について簡単なモデルを作ってどんなことが起こりうるのか調べてみた。
  • 特に階層間のタイムスケールの差異に着目してモデル構築・分析をした。下の階層はロトカ・ヴォルテラ方程式の亜種、上の階層はレプリケーター方程式を使った。
  • タイムスケールの差異によってモデルの振る舞いは大きな影響を受け、分岐現象が内在しうることがわかった。

はじめに

なんか大仰なタイトルになってしまいましたが、先日参加した研究会で多段創発に関する議論が有り、刺激を受けたのでやってみました。なんとなく数学とか物理っぽいことを書いてますが、数学とか物理はもともと苦手な上にすっかり忘れてしまっているので間違ってたら優しく教えて下さい (;・∀・)

創発とはミクロレベルの要素の相互作用から「ミクロな要素の持つ性質からは想像できないような」マクロな現象が出現する現象のことを言います。例えば、脳細胞が集まって知能という現象が発生したり、人間が集まって社会という現象が発生したり、動物や人間が生存競争をして進化という現象が発生したりなどです。多段創発とはこれが多段階に発生することです。例えば、脳細胞の集団から人間の知能が創発し、その知能を持った人間の集団から社会が創発というのは二段階の創発と言えるでしょう。

このように多段創発は至るところで発生しており、また重要なトピックでもあります(例えば、*1)。というよりは世界は多段階の創発によって成り立っていると言っていいでしょう。しかし、多くの研究は複数の階層を扱わず1段階の階層のマクロ(社会とか)とミクロ(社会に対する人間とか)を扱うことが多いです(たぶん)。なぜならば多段階の創発について扱うことは結構大変であり、また大抵の場合、ナンセンスだからです*2

大変な理由としては、必要な最小のミクロな要素が膨大な数になるからというのが挙げられます。一般に創発には非常に多くのミクロな要素が相互作用することを必要とします*3。「ミクロな要素からマクロな現象の発生」が多段階で発生する場合、一番下の階層で発生したマクロな現象は、その一つ上の階層で発生するマクロな現象から見た「ミクロな要素」になるだけの十分な数が発生する必要があるからです。

また、多段階創発の場合、観測に必要な時間も非常に長くなります。一般に、ミクロな要素のダイナミクスのタイムスケールは短く、マクロな現象のタイムスケールは長くなります。例えば、生態系のマクロな振る舞い(進化)とそれを構成する生物の生涯の場合、前者は数万年以上のタイムスケールを考えることがほとんどだと思いますが、後者はせいぜい数十年のタイムスケールです。これが多段階になると最もマクロな階層は非常に長いタイムスケールで観測しなければなりません。

つまり、創発の研究で最もよく使われる手法であるシミュレーションで何とかしようとする場合、要素数も時間もとんでもないことになり、計算機パワーが足りません(社会を構成する人間の知能を構成する脳細胞をシミュレーションすることを考えるとぞっとしますね)。また、数ヶ月から数年ぐらいのタイムスケールの社会現象の創発について考えるときには、数時間から数日ぐらいのタイムスケールの人間の思考について考えれば十分で、数ミリ秒といったタイムスケールの脳細胞のダイナミクスについて考えることはナンセンスでしょう。

では、現実の世界は多段階の階層なのに、1階層の創発しか扱わない研究の場合、どのように考えて1階層だけで現象を扱っているのでしょうか? 例として、進化ゲーム理論を題材として、進化について考えるときに生物の生涯(いつ何をどうしてしたか?とか)はどう扱われるのか? について考えてみましょう。結論としては、生物の個々体は、「どのぐらいの確率で生き延び、どの程度の数の子孫を残すか」の期待値のみで評価され、それぞれの生涯は気にされません。つまり、生存・繁殖に関わる「特徴」とそれの「生存・繁殖能力の期待値」だけで評価されます。なぜなら、進化について考えたい時のほとんどは、どんな特徴が生態系に広がり、どんな特徴が絶滅するかに最も興味があるからです。それを構成する生物の1個体1個体の生涯に興味はありません。このように「最も知りたいマクロな現象」に焦点を当て、それの一段階下の「可能な限りシンプルに表現されたミクロな要素」を考えます。ミクロな要素の内部のダイナミクスとか面倒ですし、大抵の場合、興味もないので考えません。

このようにタイムスケールの短いミクロな要素のダイナミクスを平均で評価することで、ミクロな要素のダイナミクスをモデルから消し去り、マクロな現象のダイナミクスを観測できるようにモデルを簡略化(近似)するテクニックを縮約する*4とか断熱的に近似する*5とか言います。これによってモデルは非常にシンプルで扱いやすくなり、場合によってはシミュレーションではなく数理的に何とかできたりするようになります。

縮約したモデルでマクロな現象が十分に表現できていれば、ミクロレベルの要素はそれ以上細かく見る必要がないと言えます。この場合はただ一つの階層だけ考えばよく、多段階の創発について考える必要はありません。

では、いつも一つの階層について考えればいいのでしょうか? 縮約はミクロレベルのダイナミクスがマクロレベルのダイナミクスよりも十分に速い時に可能です。その縮約という近似が効かなくなった時にどんなことが起きるか? 両者のダイナミクスのタイムスケールがそんなに違わない時に、マクロレベルのダイナミクスは大して変わらないのか、それとも全く異なることが起きるのか? について検討します。この辺りについては「縮約法の破綻」などと言われ、いろいろ研究されているようです(なので本来はこういうことする前にサーベイするべきなのですが…)。

それについて考えるために本記事ではタイムスケールの差が縮約という近似に与える影響についてどんなことが起こりうるのか調べます。創発させて階層を作るのは上記の通りとても大変なので、階層は存在していると仮定してタイムスケールの違う階層間の相互作用に焦点を当てます。

モデル

本記事では個体からなるグループ、グループからなる生態系という二階層の個体数動力学モデルを考えます。ただし、階層間のタイムスケールの差の影響について知ることが目的なので、モデルに生態学的な妥当性は求めません。

グループi = 1 \dots N内の個体のダイナミクス\dot{x}, \dot{y}はリミットサイクルを持つ以下のロトカヴォルテラ方程式の亜種(詳細はロトカ・ヴォルテラ方程式の「もう一つの安定なロトカ・ヴォルテラ方程式,リミットサイクル」を参照)を使って記述します。

\dot{x_i}=rx_i(1-\frac{x_i}{K}) - \frac{ax_i}{1+h_i x_i}y_i(1)
\dot{y_i}=\frac{bx_i y_i}{1+h_i x_i} - cy_i(2)

これは被食者(例えばうさぎ)の数x_iは自然に増殖するが捕食者によって食べられ減り、捕食者(例えばきつね)の数y_iは被食者を捕食できないと減るが捕食できればそれに応じて増加するという生態系のモデルです。ただし、被食者の増殖には環境収容力Kによる制限がかかり、捕食者の増殖には捕食速度 a/h_i による制限が掛かります。ここで捕食速度のパラメータ h_iにも添字iが付いており、被食者・捕食者の数x_i, y_iだけでなく、グループの環境パラメータh_iもグループごとに違うということに注意してください。どのように決まるかは後述します。

そのグループiからなる生態系のダイナミクスは以下のレプリケーター方程式を使って記述します。

\dot{z_i}=\alpha z_i (f_i - \bar{f})(3)
\bar{f}=\sum_i f_i z_i(4)
\sum_i z_i = 1(5)

これは、平均より適応度が高い個体(ここではグループi)は全体における比率z_iが増加し、そうでない個体(ここではグループi)の比率z_iは減少するという進化ゲーム理論などで使用されるモデルです。詳細はレプリケーター方程式 - Tokiwiki 2.0を参照してください。\alphaz_iの「式1, 2に対する相対的な変化の早さ」を調整するパラメータで、値が小さいほどz_iダイナミクスのタイムスケールはx_i, y_iよりも長いということになります。

ここで、グループiの適応度f_i
f_i=\mathrm{e}^{-x_i}(6)、
すなわち、グループiの適応度は被食者数x_iが増加すると指数的に減少するとします。なんだかよくわかりませんがこうします。

また上記で示したグループiの環境パラメータh_i
h_i=0.1 N z_i(7)、
すなわち、グループのサイズ(の全体の比率)z_iが増えれば増えるほど、捕食者の捕食速度a/h_iは遅くなるとします。こちらもなんだかよくわかりませんがそうします。

こうすると、被食者x_iが増えるほどグループサイズz_iは増加し、その結果、捕食者の捕食速度a/h_iは遅くなり、被食者x_iは増え、捕食者y_iは増加しやすくなるというモデルになり、なにか面白そうな振る舞いをしてくれそうです。また、振動子(各グループのロトカヴォルテラ方程式)がレプリケーター方程式によって大域的に結合している結合振動子系と考えることもできそうです。

結果と考察

上記のモデルをオイラー法(dt=0.1)で1000000ステップの数値計算をし、最後の一割のステップを評価しました。各パラメータはr=0.18, K=30, a=0.02, b=0.0504, c=0.25, N=10と設定しました。

まず、ダイナミクスの早さの差異(\alpha)の影響について見てみます。
f:id:swarm_of_trials:20150102181530p:plain
f:id:swarm_of_trials:20150102214302p:plain

  • 図1. \alphaによるz_iエントロピーに関する分岐図(上)とそれの\alpha <= 0.93における拡大図(下)

図1は\alphaを0.01から1まで0.01刻みで変えた時のz_iエントロピー\sum_i z_i \mathrm{log}_2 z_i)の軌道です。マクロなダイナミクスz_1, z_2, \dots, z_Nについて一次元の値で観測したかったためエントロピーを秩序変数として使用しています。また連続値で振動していたため極値のみをプロットしています。0.01 <= \alpha<=0.93のときは非常に小さな範囲で振動し、0.94<=\alphaのとき広い範囲で振動を示しました。すなわち、階層のダイナミクスのタイムスケールの差異の大小によって全く異なった振る舞いをし、それは分岐によって分けられました。また、図では読み取りにくいですが、\alphaが大きくなるほど、z_iエントロピーの最小値は0に漸近しました。以降、0.01 <= \alpha<=0.93小振動相0.94 <= \alpha大振動相と呼びます。

次にx_i, y_iダイナミクスについて見てみましょう。まず、小振動相について分析します。図2に\alpha = 0.93の各グループの被食者数x_1 \dots x_N、図3に各グループサイズz_1 \dots z_Nの軌道を示します。

f:id:swarm_of_trials:20150102220602p:plain

  • 図2. 各グループの被食者数x_1 \dots x_Nの軌道(\alpha = 0.93

f:id:swarm_of_trials:20150102211340p:plain

  • 図3. 各グループサイズz_1 \dots z_Nの軌道(\alpha = 0.93


被食者数x_1 \dots x_NN/2ずつの2つのクラスタに別れ、クラスタ内では同期して振動しました。図2, 3では重なっていますが、ひとつのように見える曲線は5つの軌道が重なっています。そして、その2つのクラスタは逆位相で同期して振動しました。グループのの集団サイズz_1 \dots z_Nも同様に2つのクラスタに別れ、クラスタ内では同期して振動、クラスタ同士は逆位相で同期して振動しました。ただし、集団サイズは\pm 0.003程度の非常に狭い範囲で振動しています。すなわち、グループサイズはほぼ均等になり、そのグループの中では被食者は同位相、または逆位相で同期して振動していました(捕食者も同様)。なお、Nが2で割り切れない条件(N=11)でも同様の実験を実施したが、その場合はN=5, 6の2つのクラスタにわかれた以外は同様の振る舞いを示した。

次に大振動相について分析します。図4に\alpha = 0.94の各グループの被食者数x_1 \dots x_N、図5に各グループの集団サイズz_1 \dots z_Nの軌道を示します。

f:id:swarm_of_trials:20150102220609p:plain

  • 図4. 各グループの被食者数x_1 \dots x_Nの軌道(\alpha = 0.94

f:id:swarm_of_trials:20150102211354p:plain

  • 図5. 各グループの集団サイズz_1 \dots z_Nの軌道(\alpha = 0.94

被食者数x_1 \dots x_Nは2つのグループのみ振動し、それらは逆位相で同期しました(図4)。それ以外のグループは絶滅しない状態(x_i > 0)で収束しました。そして、グループのの集団サイズz_1 \dots z_Nも同様に2つのグループのみ振動し、それらは逆の位相で同期しました(図5)。それ以外のグループのサイズは0に漸近しました。すなわち、グループは2つを残して絶滅(グループサイズが0)になり、残った2つのグループの中では被食者は逆位相で同期して振動していました(捕食者も同様)。

以下に小振動相\alpha=0.93と大振動相\alpha= 0.94から1つずつグループを抜き出し、被食者と捕食者の軌道を示します(\alpha=0.94についてはz_i>0の非絶滅グループを抜き出してます)。

f:id:swarm_of_trials:20150102211359p:plain

  • 図6. \alpha = 0.93から抜き出したグループのx, y\alpha = 0.94から抜き出したグループのx, yの軌道

小振動相の場合に比べて、大振動相の場合はx, yは各振動で0に漸近し、絶滅しかけるという極端な振る舞いを示していることがわかります。また、小振動相の場合はグループレベルの相互作用(式3, 4, 5)がない場合に類似した軌道を示しています(ロトカ・ヴォルテラ方程式の「もう一つの安定なロトカ・ヴォルテラ方程式,リミットサイクル」を参照のこと)。

小振動相ではグループサイズはほぼ均等にな状態で安定するため、その状態では捕食速度a/h_iはほぼ一定になります。したがってグループレベルの相互作用がない場合と類似した振る舞いを示したのだと考えられます。グループレベルのダイナミクスが、個体レベルのダイナミクスよりも遅いため、個体レベルのダイナミクスを考えるときにh_iが受ける影響が小さかったためだと考えられます。

一方で、大振動相では被食者x_iが増加するとグループサイズz_iが減少し、捕食速度a/h_iも連動して変わり増加するため、被食者x_iと捕食速度a/h_iの両者が高い状態となり、捕食者y_iが急激に増加します。それによって被食者x_iは急激に減少するため、捕食者y_iもそれに伴い減少します。被食者x_iが激減するため、グループサイズz_iが増加し、捕食速度a/h_iが減少します。それによって捕食者y_iはさらに減少します。捕食速度a/h_iが遅く捕食者y_iが少ないという状態となり、被食者x_iが増加し最初の状態に戻ります。

興味深いのはこの2つの全く異なる振る舞いが\alphaという変化の相対的な早さを表すパラメータを変えるだけで現れ、また、その境界はなだらかでなく、\alpha=0.94で分岐によって明確に分けられたことです。

まとめ

本記事では、複数階層間の相互作用におけるタイムスケールの差異の影響について知るために、2つの階層が明示的に存在する系を考えて、階層のダイナミクスのタイムスケールの差異を変えてどんなことが起きうるのか? について簡単なモデルを作って調べてみました。

その結果、タイムスケールの差異によって、ミクロレベルもマクロレベルも全く異なる振る舞いを示しました。また、そこには明確なしきい値が存在しました。

この結果は、ダイナミクスのタイムスケールが十分に異なるときは、縮約やそれに近い考え方を使って一つの階層のミクロ・マクロ相互作用に焦点を当てて分析することが可能だが、そうではない場合、縮約という近似は破綻し、全く異なる振る舞いが発現する可能性がある。それは分岐によって明確に相が別れうるということを示します。

小振動相・大振動相共に引き込みによる同期が発生しており、それが何故どのように起こっているのか? は結合振動子として扱えそうな気がしないでもないのですが、振動子系は勉強不足(というかほとんど知らない)のため全然手を付けれていません。その辺りを今後勉強しながらやってみたいと思います。とりあえず、学生の頃買ってずっと積みっぱなしの蔵本先生のリズム現象の世界 *4 を読まないと(;・∀・)



参考文献

Google Spreadsheet, Google Drive, Google Apps Scriptでレポーティングの簡単自動化

はじめに

Webサービスを運営する上で、サービスは現在どういう状態なのか? サービスを改修したことで何が変わったか? を知ることは非常に重要です。弊社ではHawkeyeという内製のレポーティング・分析システムを開発し、日々のPDCAに活かしています(参考: Amebaソシャゲ分析事例のご紹介)。

見るべきKPIや行うべき分析を定型化し、それに最適なデータとUIを設計し、システム化することは重要ですが、一方でそれをするには設計・開発に時間がかかってしまいます。また、実際に運用してみないと本当に有用なレポート・分析であるかどうか判断が付きにくい場合もありますし、運用しながら実情に合わせていろいろ変えたい場合もあるかと思います。そのような場合、とりあえず手動で集計して何日か様子を見てから判断という方法も取れますが、手動は面倒です。

そこで、本記事ではそれを補間するために Google Spreadsheet, Google Drive, Google Apps Scriptを利用した簡単な自動レポーティングの作成方法を紹介します。弊社ではGoogle Appsを導入しており、全員が利用可能なので非常に手軽に利用することができます(スピード経営の足かせになるな!:ID統合にインフラ強化、サイバーエージェントが挑んだ情報システム改革 (1/2) - ITmedia エンタープライズ)。また、導入していなくても通常のGoogleのアカウントでも可能です。

やること

本記事では

  • Google Drive にあるデイリーで追加されるTSVファイルを読み込んで、Google Spreadsheet に貼り付け、グラフを作成(または更新)する

ことを自動的に実施するために必要な Google Apps Scriptの使い方を紹介します。

ただし、本記事では Google Drive にファイルを配置するところは扱わないので、その部分についてこちらをご覧ください。curlコマンドで簡単にできます。

完成イメージ

f:id:swarm_of_trials:20141211145509j:plain

Google Appsを使うメリット・デメリット

Google Apps上でレポーティングすることは何が嬉しいかについて考えてみます。

  • 閲覧・編集権限に関する制御やアクセス負荷などに関することは、Google Appsが全てやってくれるので気にしなくても良い。
    • 特にレポートは売上など重要な情報を扱うことが多いため、閲覧権限を簡単に制御できる点は重要です。
  • ブラウザで閲覧可能なので閲覧者にとってアクセスが容易。
    • レポーティングシステムは閲覧者に毎日継続して見てもらい、問題点把握・施策の効果確認をしてもらうこと目的です。したがって閲覧者がアクセスしにくいのは致命的です。
  • 使い慣れたスプレッドシート形式でレポートが提供されるので、閲覧者がレポート上のデータを使って、即座に自分で分析することができる。
    • レポートの対象サービスについて最もよく理解しているのは閲覧者(≒サービスの運営者)です。レポート作成者とは異なった観点で手軽に分析できることはサービスやそのためのレポートの品質を上げることに役立つはずです。
  • Google Appsが上から下までひと通りの機能を提供してくれてるので自動化が簡単。
    • とはいえ手間がかかったら大変なので簡単なのは大事です。Google Apps ScriptはJavaScriptとほぼ同じなので学習コストもあまり高くありません(特に私は元フロントエンドエンジニアなので非常に楽でした)。

一方で、凝った動的なUIや分析などを提供することは難しいので、そういったものが必要な場合はTableauやShinyといった専用のツールを使ったほうがいいかと思います。

やってみる

Google Apps Script の開発環境の準備

Google Spreadsheet のファイルを新規作成し、メニューから [ツール] → [スクリプトエディタ] を押下します。するとエディタが起動し、「コード.gs」というファイルにプログラムを書き込むことができるようになります。この「コード.gs」にプログラムを書いていきます。

Google Driveからファイルを読み込む

まずGoogle Apps ScriptでGoogle Driveからファイルを読み込みます。ファイルにはHadoopで集計した結果やR/Pythonで分析した結果が書き込まれていることを想定しています。

//fname: 読み込むファイル名
function loadData(fname){
  //Spreadsheetとデータの置いてあるフォルダを取得
  //FolderIDはGoogle Driveで [共有] する際に表示される [共有URL] のGetパラメータのidから知ることができます。
  //当然、Google DriveのAPIからも知ることができるので、そこも含めて自動化したい場合はそこから取得したほうがいいでしょう。
  var folder = DriveApp.getFolderById('xxxxxxxxxxxxxxxxxxxxxxx');
  
  //TSVデータファイルの取得
  var files = folder.getFilesByName(fname)

  //folder以下には同じファイルはひとつしかないようにするので
  if(!files.hasNext()) throw(fname + ': not found');

  var file = files.next();
  var fileBlob = file.getBlob();
  
  //TSVをパースして二次元配列に変換
  d = Utilities.parseCsv(fileBlob.getDataAsString(), '\t')
  
  return d;
}

Google Apps ScriptはJDBCが使える(JDBC - Google Apps Script — Google Developers)ので、データベースに接続してデータを読み込むこともできそうです(未検証)。データベースを使えば動的なスライシングなどもある程度実現できると思うので、そのうち試してみたいと思います。

シートを選択(または新規作成)する

次にデータを書き込む場所(シート)を確保しましょう。

//sname: 対象のシート名
function getSheet(sname){
  //対象となるシート名は一個しかないと仮定して、シートの一覧を取得してその中から名前が一致するものを探す
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
  var sheets = spreadsheet.getSheets();
  var sheet = underscoreGS._filter(sheets, function(s){
    return s.getName() == sname;
  })[0];
  
  if(sheet){
    //対象のシートが存在している時はそれを使う
    return sheet;
  }else{
    //対象のシートが存在していない時はシートを作成しカラム名を設定する
    var s = spreadsheet.insertSheet(sname);
    s.getRange(1,1, 1, 5).setValues([['dt', 'DAU', 'ARPU', 'ARPPU', '課金率']]);
    return s;
}

シートにデータを書き込む

それでは読み込んだデータをシートに書き込んでみましょう。

//sheet: 書き込み対象のシート
//data: 書き込むデータ(2次元配列)
//c, r: 書き込む場所
function putData(sheet, data){
  var range = sheet.getRange(r, c, data[0].length, data.length);
  range.setValues(data);
}

グラフを新規作成する

いよいよシートに書き込んだデータを使ってグラフを作ります。

//sheet: グラフ作成対象のシート
function createChart(sheet){
  //グラフ範囲を求める
  var dataRange = sheet.getDataRange();
  var height = dataRange.getHeight();

  //グラフ作成
  var dau = sheet.newChart()
                    .setChartType(Charts.ChartType.LINE)
                    .addRange(sheet.getRange('A1:A' + height)) //グラフに使うデータ(X軸)
                    .addRange(sheet.getRange('B1:B' + height))  //グラフに使うデータ(Y軸)
                    .setPosition(1, 7, 0, 0)                   //グラフを配置する座標
                    .setOption('title', 'DAU')
                    .setOption('hAxis.title', '日')
                    .setOption('vAxis.title', 'ユーザ数')
                    .setOption('useFirstColumnAsDomain', true)//最初のカラムをX軸として使うことを指定
                    .setOption('vAxis.viewWindow.min', 0)
                    .setOption('height', 280)
                    .setOption('width', 480)
                    .build();

  //シートに貼り付け
  sheet.insertChart(dau);
}

グラフを更新する

すでに存在するグラフの場合、元になるデータの入っているセル群の下に1行追加すると自動的にグラフも更新されます。便利。

定期実行する

ここまでで、データを読み込んで、書き込むシートを作って、データをシートに書き込んで、それを使ってグラフを生成する、というレポート作成に最低限の機能を紹介してきました。
それを定期実行します。

まず以下の様な処理を開始する関数を定義します。

function main(){
  var data = loadData('20140101.tsv');
  var sheet = getSheet('DAU_201401');
  
  putData(sheet, data);
  createChart(sheet);
}

次にその関数を駆動するトリガーを設定します。

  • メニューから [リソース] → [現在のプロジェクトのトリガー] を押下します。すると今のプロジェクトで作成したトリガーが表示されます。
  • リンク [新しいトリガーを追加](または [トリガーが設定されていません。今すぐ追加するにはここをクリックしてください。] )を押下するとトリガーが新規作成されるのでトリガーの設定をします。

トリガーの内容を、例えば以下のように設定すると、「main関数を毎日 午前8時〜9時に実行する」というトリガーを作成することができます。
f:id:swarm_of_trials:20141211160447p:plain

※ トリガーに [スプレッドシートから] → [フォーム送信時] というものがあります。これはスプレッドシートに紐づく Google フォームのリクエストに応じで関数が駆動されるというものです。弊社の環境だとちょっと試した範囲ではできなかった(権限とかの問題?)のですが、普通のGoogleのアカウントの方だとできたので、このトリガーが可能な環境ではフォーム送信のリクエストを真似て分析バッチなどからリクエストを送ることで、よりいい感じの自動化ができるようになるかもしれません。

まとめ

というわけで、Google Spreadsheet, Google Drive, Google Apps Script を使って簡単にレポーティングを自動化する方法を紹介しました。Google Apps Script は調べてみると結構いろんなことができたりするのでいろいろやってみましょう。


LASSOでpixivのイラスト閲覧数に対する「萌え要素」効果を分析してみた

概要

f:id:swarm_of_trials:20141016011640p:plain:right:h100

  • 目的: 人を惹きつける萌え要素を知りたい
  • 方法: pixivのタグと閲覧数(PV)データを使って、人気のある(PVが上がりやすい)萌え要素タグを調べた
  • 結論: 残念な美人こと足柄さんはPVの女王。ギャップ萌えとエロ要因がPVに効きそう。

(※: 画像はゆるこた様の "ゆっくりかんむす" の足柄さんを使用しています: 【素材配布】ゆっくりかんむすで第六駆逐隊とみんな! / ゆるこた さんのイラスト - ニコニコ静画 (イラスト)

はじめに

例えば眼鏡やネコミミ、メイドといった萌え要素はキャラクターの人気を左右する大きな要因の一つであり、どのような萌え要素を持ったキャラクターを採用するか? はゲームやアニメ、漫画・イラスト・動画、広告などのコンテンツにおいて重要だと考えられます。

ところが萌え要素は非常に多様であり、見る人の趣向や萌え要素の組み合わせによってその意味や効果は大きく異ると考えられます(参考: 萌え要素・属性の一覧とは (モエヨウソゾクセイノイチランとは) [単語記事] - ニコニコ大百科)。

例えば以下は眼鏡メイド眼鏡白衣のイラストについてGoogleで画像検索した結果です。両者ともに眼鏡が基軸となる要素ですが、それと組み合わさる衣装(メイド・白衣)によって「萌え」の表現が大きく異なるため、眼鏡という萌え要素の意味もそれを好む人も全然異なるであろうことが想定できます。

f:id:swarm_of_trials:20141002114116p:plain:h250 f:id:swarm_of_trials:20141002114257p:plain:h250

  • 図1. 眼鏡メイドと眼鏡白衣のGoogle画像検索結果

このように、萌え要素は様々なコンテンツにとって重要であるものの、その多様性から非常に複雑です。

本記事では、どのような萌え要素萌え要素の組み合わせが人を惹きつける力を持つか? について知ることを目的とします。萌え要素単品ではなく組み合わせについても考えるのは、上記のように組み合わせによってそれらが表現する「萌え」が異なるからです。本記事ではそれをするためにpixiv(イラストコミュニケーションサービス[pixiv(ピクシブ)])のデータを使うことを考えます。イラストのPV(閲覧数)が高いほど人を惹きつける力が強いイラストであると考え、pixivの各イラストについたタグからイラストの萌え要素を抽出し、それがPVにどのような影響をおよぼすか? について分析します。本記事ではその影響度合いを萌え指数と呼びます。また、本記事で対象とするのは各場面で規制されない非エロな萌え要素を対象とします(境界線は難しいですが非エロ〜微エロまでぐらいを対象としたいところ)。

これについて知ることで

  • 萌えやフェチなどのヒトの趣向に関する知見が得られたり
  • 人気の出やすい萌えキャラクター要素について知れたり
  • 訴求力の高いバナー広告に盛り込むべき要素を知れたり

することができるんじゃないかなぁと思います。

※ もちろん、人を惹きつけやすい萌え要素突っ込んだらそれでいいかというとそんなわけはなく、物語や世界観、他のキャラクターとの組み合わせ、対象とする人の属性など様々な要因が重要になってくるので、各領域(ゲームとか広告とか)に対する深い理解が必要だと思います。

データ

本記事では萌え要素ニコニコ大百科萌え要素の一覧(萌え要素・属性の一覧とは (モエヨウソゾクセイノイチランとは) [単語記事] - ニコニコ大百科)をベースとして、明らかにエロ要素であるタグを除外し、略語(女子高生→JKとか)などを追加して使用しました。この萌え要素それぞれについてpixivで検索を掛けて、非R-18のイラストにつけられたタグとPV(閲覧数)と絵師IDを取得しました。そのため分析対象のイラストはpixivのイラスト全体からすると少々偏りが有るかもしれません。

次に取得したタグから萌え要素一覧に該当するもののみ抽出し、各イラストごとに (萌え要素タグリスト, PV) という組み合わせのデータを作成しました。またこの際にある程度の名寄せ(おねいさん、おねえさん、お姉さん→お姉さん など)と分解(ドヤ乳 → ドヤ顔・おっぱい など)を行いました。

対象としたタグは、タグ(とタグの組み合わせ)の出現頻度が25件以上のみのタグとしました。50件以上出現する場合は全体から50件ランダムサンプリングしています(説明変数とデータ数を減らしたかったので)。対象としたイラストデータは上記で抽出したタグを1件以上含むものとします。

予備調査

まずはPVの時間的な推移を見てみましょう。
f:id:swarm_of_trials:20141001232411p:plain:h350

  • 図2. PVの時間推移。横軸が経過日数で縦軸がPVです。各線はそれぞれ25%ile、50%ile(中央値)、75%ileを表します。

図2を見ると対数っぽく推移していることがわかります。これは投稿直後は人の目に触れやすいが時間が経過すると人の目に触れる機会が減っていくからだと考えられます(無料会員だとイラストは投稿順でのみ閲覧可能なので)。本記事では投稿から2週間〜 のデータを使って萌え指数の分析に使うことにします。

次にPVの分布を見てみましょう。
f:id:swarm_of_trials:20141015192911p:plainf:id:swarm_of_trials:20141015192917p:plain

  • 図3. PVの分布。左は通常スケール、右はX軸について片対数スケールのグラフ。

図3を見るとちょっと歪んでますが対数正規分布っぽくなっているのがわかります。そういった分布は左の図を見るとわかるように大抵のイラストはPVはそんなに伸びないが、極稀にPVがとんでもなく伸びるイラストが存在することを表します。こういったロングテール的な性質は、例えば

  • (人気のある)フォロワーの多い絵師さんはPVが増えるのでランキングなどでの露出が増える
  • さらにフォロワーが増え、より露出が増える

といったポジティブフィードバックループによって発生すると考えられます。

モデル

イラストのPVに対する萌え要素タグの効果を分析するために次のモデルを考えます。
PVは萌え要素だけでなく、投稿してからの経過時間(PVは累積するので)と絵師さんの人気にも大きく依存していると考えられます。というか、基本的には萌え要素よりも絵師さんの人気の方が影響が大きいと思われます。そのため、PVに関する萌え指数の中にそれらが入ってしまうのを防ぐために、モデルに経過時間と絵師人気も組み込んでいます。

結果、モデルは以下のようになりました。これはイラストiのPV(PV_i)を経過秒数 s_i 、絵師さんの平均PV u_i 、付けられている萌え要素タグ \vec{t}_i で説明しようとするモデルです。
 
{ \displaystyle
\log(PV_i) \sim {\rm Normal(\lambda_i)} \\
\lambda_i = \beta_0 + \beta_1 \ln(\ln(s_i)) + \beta_{2i} u_i + \sum_{j \in tags} \beta_{3j} t_{ij} + \sum_{j < k; j, k \in tags} \beta_{4jk} t_{ij} t_{ik}
}
 
イラストiのPV PV_i対数正規分布に従うとします。それを説明する変数はそれぞれ以下を表します。

  • s_i はイラストiがアップロードされてからの経過秒数を表します。大雑把にはイラストのPVは、無視した投稿直後を除いて経過秒数に対して対数成長をしていたので、\ln(\ln(s_i)) としています。
  • u_iはイラストiを描いた絵師さんの平均的なPVを表す整数の変数です。
  • t_{ij}はイラストiにタグjが付いている時に1、そうでなければ0になる変数です。右辺第4項( \sum_{j \in tags} \beta_{3j} t_{ij} )はタグの単体の効果を表します(例えば眼鏡)。右辺第5項( \sum_{j < k; j, k \in tags} \beta_{4jk} t_{ij} t_{ik})は2つのタグの組み合わせの効果(交互作用)を表します(例えば眼鏡 x 白衣)。萌え要素タグや萌え要素タグの組み合わせが人を惹きつける効果があるほど、\beta_{3j}\beta_{4jk} が大きくなります。タグの交互作用は2つのタグのみに限定しています(それ以上にすると説明変数が多くなりすぎて処理が終わらなかったので…)。この記事では\beta_{3j}, \beta_{4jk}の大きさを萌え指数とします。

このモデルをRのパッケージ glmnet を使って一般化線形モデルで LASSO しました。LASSO については LASSO and Ridge regression - iAnalysis 〜おとうさんの解析日記〜 でやさしく説明されているのでそちらをご参照ください。ざっくり言うと説明変数がやたらと多くても大丈夫で、重要な説明変数を勝手に選択をしてくれるありがたい一般化線形モデルです。本記事では上記のようにタグの交互作用をモデルに組みこんでおり、説明変数が非常に多くなるため LASSO を使いました。

分析結果

それでは分析結果を見て行きましょう。まず、前述のモデルでどの程度データにフィットしたか見てみます。
f:id:swarm_of_trials:20141015201228p:plain

  • 図4. イラストPVの実績値と予測値の相関

なんかだいたい良さそうな気がします。

ではいよいよ萌え指数を見てみましょう。表1に萌え指数の高いタグとタグの組み合わせのトップ10を示します(本記事の最後にトップ50を示します)。萌え指数が高いことは、対象のタグが付いているイラストは同じ絵師さんでも他のイラストに比べてPVが高い傾向にあったということを表します。

順位 萌え指数 PV25%ile PV50%ile PV75%ile イラスト数 タグ
1 0.29 3,279 8,233 26,665 51 残念な美人
2 0.28 500 1,358 7,159 107 レイプ目
3 0.24 841 2,140 5,194 108 ボテ腹
4 0.21 228 413 490 40 お姉さん アンドロイド
5 0.20 1,183 2,164 4,074 380 巨大娘
6 0.19 1,879 6,475 17,999 50 ビッチ 処女
7 0.17 653 2,745 10,978 49 幼妻
8 0.17 1,015 1,614 8,885 38 団地妻
9 0.17 407 1,381 4,144 83 巨乳 百合
10 0.17 400 1,322 3,078 51 巨乳 敵女

 

順位 萌え指数 PV25%ile PV50%ile PV75%ile イラスト数 タグ
1 -0.38 322 345 402 27 サムライ スーツ
2 -0.21 117 144 357 22 浴衣 高校生
3 -0.18 150 250 338 62 ツンデレ 高校生
4 -0.18 81 110 144 26 お嬢様 人形
5 -0.16 382 672 855 53 ニーソ ハイレグ
6 -0.16 89 145 240 60 猫耳 高校生
7 -0.15 139 205 331 26 ロリ 中二病
8 -0.15 180 243 370 30 犬耳 魔法少女
9 -0.15 86 139 256 51 コスプレ 猫耳
10 -0.14 342 1,779 4,181 32 看板娘 腋

男性向け・女性向けイラストの選別は特にしてないのですがほとんどが男性向けの萌え要素になりました。これは元にしたニコニコ大百科萌え要素リストが男性向けに偏っていたのが原因だと考えられます。

この20件だけ見ると、フェティッシュなタグの方が萌え指数が高く、あざとい*1タグの方が萌え指数が低いように見えなくもないような気がします*2

次に萌え指数が高いタグの傾向をつかむために、萌え指数の高さトップ10のタグを持つイラストのうち、描いた絵師さんの平均的なPVとイラストのPVの比(萌え比PV_i/u_i)が大きかったイラストを見てみます。

萌え指数 No.1: 残念な美人

栄えある最高萌え指数を叩きだしたのは残念な美人です。以下にPVが5000以上で萌え比が大きかったイラスト10点の情報を示します(以下、他の萌え要素についても同様)。

順位 タイトル タグ
1 足柄ノ戦イ(14.02.22) 漫画 艦これ 足柄 戦艦ル級 空母ヲ級 飢えた狼 戦術的敗北 艦これ1000users入り 今日も深海は平和です 残念な美人
2 艦これ】私は全艦娘たちの中で足柄さんが一番好きです(*´Д`) 艦これ 足柄 妙高 羽黒 那智 妙高型 飢えた狼 残念な美人 4コマで分かる妙高
3 【図書館組】もしかしたら、こんな事もあったかもしれない 新・世界樹の迷宮 図書館組 残念な美人 だがそれがいい ラクーナ・シェルドン
4 死守 うごイラ 艦隊これくしょん 赤城 残念な美人 一航戦の埃 焼肉奉行 鉄壁の右手 多重次元屈折現象 艦これ500users入り
5 提督から話があるらしい 艦これ 加賀 正妻空母 五航戦いじめ 翔鶴/赤城/青葉 残念な美人 提督LOVE 銀髪の子かわいそう 艦これ500users入り お局
6 超秘封公開。 漫画 東方 秘封倶楽部 宇佐見蓮子 マエリベリー・ハーン 残念な美人 酷いパンモロ いつもの米泥棒 こっちみんな これはひどい
7 【図解】将美さん 将美さん オリジナル 八重歯 振袖 メガネ 残念美人 残念な美人
8 女性提督 【夏コミCG集表紙】 女性提督 艦これ 松竜提督 三笠刀 黒髪ロング 変態という名の淑女 軍服 艦これ10000users入り 残念な美人 女性提督マジ天使
9 くちうつし 艦隊これくしょん 艦これ 不知火さんこっちです これ絶対当ててるよね 違う、そうじゃない 正妻空母 翔鶴 ショタ提督 残念な美人 艦これ100users入り
10 【リク消化】ドラミングアマゾン ドラゴンズクラウン ビッグボイン 筋肉娘 ボインチョップ アマゾン ソーサレス 残念な美人 胸囲の格差社会 腹筋

足柄さんのワンツーフィニッシュ。上記以外では、アイマス音無小鳥、東方のアリス・マーガトロイド八雲紫八雲藍といった大人な美人系のキャラのイラストが入っていました。

残念な美人とは、美人なのにもかからわず、なんだか残念な行為をしてしまうという萌え要素です(残念な美人とは (ザンネンナビジンとは) [単語記事] - ニコニコ大百科)。そういったギャップが閲覧者を惹きつけたのではないかと考えられます。

萌え指数 No.2: レイプ目

順位 タイトル タグ
1 おもらし ショタ 失禁 おもらし 薬漬け 八重歯 レイプ目 交通安全ポスター
2 えっちごっこ。 オリジナル 女の子 制服 セーラー服 女子高生 百合 レイプ目 オリジナル100users入り 百合100users入り
3 雨はいつか病むさ 版権 艦これ 艦隊これくしょん 時雨 時雨改二 ア艦これ ヤンデレ 厚塗り なにこれ怖い レイプ目
4 スマプリ4コマ16~18 スマイルプリキュア 4コマ漫画 ウルフルン キュアハッピー 星空育代 黄瀬ちはる 青木れいか レイプ目 星空博司 黄瀬やおい
5 育代さん、娘の前で… スマイルプリキュア! 星空博司 星空育代 星空みゆき 早く薄い本を描く作業に戻るんだ レイプ目 リモコンバイブ みゆきのトラウマ 声我慢 キュアアンハッピー
6 愛しあう二人はいつも一緒 東方 物部布都 などと供述しており ヤンデレ レイプ目
7 六花ちゃん若いねぇ ドキドキ!プリキュア 菱川六花 相田マナ マナりつ 事後 レイプ目 変態六花さん 早く薄い本を描く作業に戻るんだ 百合 ドキプリ100users入り
8 おしっこの匂いってね 犬走椛 マーキング レイプ目 守れない、この絵がおー 攻めのレイプ目 見える上にかけてもらえる 人を呪わば穴二つ ヤンデレ 東方Project100users入り 因がおー報
9 ケモえもん24 ケモえもん ドラえもんパロ 4コマ漫画 しずくちゃんマジブラックw レイプ目 むしろ逆レイプ目
10 はぁ、このたくあん・・・ 蘇我屠自古 たくあん食べろ 目が死んでいる とじこのたくあん 何があった レイプ目 事後 大根 東方 いともたやすく行われるえげつない行為

R-18のタグ付きイラストは分析対象から外したのですが、タグを見た感じエロイラストが多いようです。R-18タグを除いたため、R-18のついていないエロ系のイラストに付与されがちな萌え要素の萌え指数が高くなったのだと思われます。またマニアックなフェチを表すようなタグとの共起が多いように見えます。

萌え指数 No.3: ボテ腹

順位 タイトル タグ
1 ボテ腹の描き分け オリジナル ボテ腹 膨腹 大食い 講座
2 えいじ★こんとろーら【年齢調節器】 その6 挿絵 膨乳 爆乳 おっぱい 年齢調節器 水着 ビキニ 急成長 膨腹 肥満化 ボテ腹
3 らくがき らくがき 小学生 ロリ JK ボテ腹 妊婦 母娘丼 ロリニティ
4 早く外に出たいんだってさ 東方 博麗霊夢 ヤンデレ 妊婦 横乳 乳巫女 なんかエロい ボテ腹 東方Project100users入り
5 親子逆転物語 -産まれる前へ還る- 親子逆転物語 若返り 急成長 吸収 遺伝子 妊婦 ボテ腹 爆乳 極上の乳
6 えいじ★こんとろーら【年齢調節器】 その4 挿絵2 おっぱい 年齢調節器 膨体 セーラー服 表紙 膨腹 ボテ腹 妊娠 妊婦 おなか
7 艦これ健全 レ級 艦これ 初春 叢雲 妙高 KENZEN 戦艦レ級 正の字 ボテ腹
8 淫夢迷路~熟女とJKを犯し征服せよ~ パイズリ 中出し フェラ アナル おっぱい ボテ腹 尻 ぶっかけ 触手
9 えいじ★こんとろーら【年齢調節器】 その6 挿絵追加 水着 ロリ 年齢調節器 膨腹 膨体 おなか ボテ腹 妊娠 妊婦 ロリ妊婦
10 かす子いじってたらすごいことになった(その1) R-17 3Dカスタム少女 おっぱい 爆乳 ボテ腹 膨腹 水着 爆腹 妊婦

こちらの萌え指数が高くなっていた理由もエロでしょうか… "ボテ腹" も相当マニアックですが、同時にマニアックなタグとの共起が多いように見えます(爆腹って…)。付与されているタグをざっと見た感じ、ロリキャラ的なキャラクターが妊娠している状態に対する萌え(?)のような気がしないでもないです。これもギャップ萌えなのでしょうか…?

萌え指数 No.4: お姉さん アンドロイド

こちらのタグはPVが5000を超えるものはありませんでした。多くのイラストはほとんど一名の絵師さんがシリーズ物的な感じで描いていました。シリーズ物に固定閲覧者がついたため、その絵師さんの平均的なPVよりもそのシリーズのイラストのPVが高くなったため萌え指数が高くなったのだと考えられます。したがって、これは「お姉さん アンドロイド」という要素の萌え指数が高かったというよりは、「シリーズ物は強い」と解釈したほうがいいでしょう。

萌え指数 No.5: 巨大娘

順位 タイトル タグ
1 ちょ、ちょっとどこ入ってんのよ! オリジナル 看板娘 成瀬紅葉 シュリンカー 巨乳 極上の乳 巨大娘 全身パイズリ
2 艦載機は着艦して! 漫画 艦隊これくしょん 翔鶴 艦これ 巨大娘 艦これ100users入り 1/1艦娘
3 くっ苦しい…! 成瀬紅葉 看板娘 シュリンカー パジャマ 巨乳 巨大娘 全身パイズリ
4 どーん 漫画 スキ美 おっぱい 巨大娘 超乳 人間パイズリ希望 神々の谷間 極上の乳 シリーズ化希望
5 うずまきスカート オリジナル ハイセンス 宇宙ヤバイ パンツ丸見え 見えそうで見えてる 銀河系 女の子 巨大娘 パンモロ オリジナル10000users入り
6 女体盛り 外 ちびもと ガリバー旅行記 刺身 健全な女体盛り 小びもと おかず 足プルプルしてる 巨大娘
7 1129の日だったので。 漫画 ぽっちゃり 巨大娘 妖怪 手洗い鬼 御霊神 SSBBW 肥満化 髑髏坊 ジャイアンパンチ
8 シホとその兄の喧嘩風景(兄は高層ビル屋上にいる) オリジナル 水着 巨大娘 ロリ巨乳 ロリ 背景てっきとー! 巨乳 ツインテール 幼女 膨乳
9 巨大会長 角谷杏 巨大娘 ガールズ&パンツァー 勝てる気がしない 尻
10 C83の新刊 巨大娘 足裏 同人 セイバー saber fate FATE/ZERO Fate/EXTRA C83 裸足

またしても解釈の難しいタグの萌え指数が高くなりました… 多くは微エロ系のイラストのように見えます。非日常的なシチュエーション? が効果的だったのでしょうか。

萌え指数 No.6: ビッチ 処女

順位 タイトル タグ
1 鈴谷におまかせー! 艦これ 艦隊これくしょん 鈴谷 艦これかわいい 誘い受け 前から見えるお尻 憲兵さんこっちです 艦これ1000users入り いいぞもっとやれ 処女ビッチ
2 艦娘に 「ラムネ飲みたい」 って言ってみた 漫画 艦隊これくしょん ラムネ 鈴谷 このあと滅茶苦茶シリーズ 提督LOVE 処女ビッチ 鈴谷(艦隊これくしょん) 艦これ100users入り なにこれ超かわいい
3 いいオークの日(の続き) 漫画 エルフ オーク エロフ 処女ビッチ オークさんは紳士 シリーズ化希望 いいオークにも程がある はいていない いいオーク
4 脳内鎮守府ショート劇場まとめ2 漫画 加賀 艦これ 正妻空母 鈴谷/霧島/比叡/金剛/榛名/赤城 処女ビッチ 艦これ1000users入り リア充轟沈しろ 脳内鎮守府劇場 一航戦の埃
5 艦これらくがきまとめ 艦これ 鈴谷 伊401 処女ビッチ 飛龍(艦隊これくしょん) 憲兵さんこっちです 榛名 ラウ・ル・クルーゼ 間宮 艦これ500users入り
6 いいオークの日(それから) 漫画 またお前か オークのオーク いいオーク 処女ビッチ YES枕 M字開脚 YES!YES!YES! エルフのエルフ illust/43946203
7 まちがってない。 やはり俺の青春ラブコメはまちがっている。 処女ビッチ 由比ヶ浜結衣 黒ハイソックス 比企谷八幡 八結 俺ガイル100users入り パンチラ
8 【俺ガイル】やっはろー。。 アニメ 俺ガイル 由比ケ浜結衣 やはり俺の青春ラブコメは間違っている。 やはり俺の青春ラブコメはまちがっている。 由比ヶ浜結衣 俺ガイル1000users入り やっはろー 処女ビッチ 素足
9 処女ビッチちゃん やはり俺の青春ラブコメはまちがっている。 由比ヶ浜結衣 巨乳 俺ガイル 俺ガイル1000users入り 処女ビッチ
10 で、保健体育のどこがわからないの? やはり俺の青春ラブコメはまちがっている。 川なんとかさん 川崎沙希 川崎大志 処女ビッチ

処女ビッチとは「いかにも遊んでそうなギャルっぽい派手な見た目・ファッション(短いスカート・着崩した制服・脱色した髪など)だが、実はまともに男と付き合ったことがない女性キャラ」を指す萌え要素です(処女ビッチとは (ショジョビッチとは) [単語記事] - ニコニコ大百科より)。こちらもギャップ萌えの一種と言えます。このタグの性質上、非R-18の微エロ的なイラストが多そうなので、わりと本記事の目的にかなった萌え要素なのかなと思います。

萌え指数 No.7: 幼妻

順位 タイトル タグ
1 づほ 瑞鳳 艦隊これくしょん 艦これ 艦これかわいい 提督LOVE 艦これ1000users入り 幼妻 良妻軽母 祥鳳 瑞鳳に定評のあるアマノ
2 吹雪ちゃん 漫画 艦これ 吹雪 愛を感じる 愛しか感じない 愛妻 幼妻 主人公 提督LOVE 艦これ500users入り
3 山手殿(寒松院) 戦国大戦イラストコンテスト3_オリジナル武将 オリジナル 女の子 幼妻 山手殿 オリジナル250users入り
4 ダメよ、しっかり食べないと! 艦これ 雷(艦隊これくしょん) 雷ちゃんマジ天使 提督LOVE 艦隊これくしょん 私服艦娘 ケッコンカッコカリ 幼妻 艦これ100users入り ダメ提督製造機
5 電ちゃん成長記録 ~赤ちゃんから主婦になるまで~ 艦これ 艦隊これくしょん まるで成長していない 幼妻 愛がなければ書けない 電ちゃんマジ天使 童顔 人妻 妄想成長
6 艦これ2コマ劇場その49『電は知っている』 艦これ 艦隊これくしょん 電 左手薬指の存在感 提督LOVE 日刊ゆうじこうじ 幼妻 ロリコンカッコガチ 艦これ100users入り だが教えない
7 雷カレーを召し上がれ♪ 艦これ 艦隊これくしょん 雷(艦隊これくしょん) 今日も鎮守府は平和です 第六駆逐隊 ダメ提督製造機 雷ちゃんマジ天使 幼妻 艦これ500users入り
8 私の作った玉子焼き、食べる? 艦これ 艦隊これくしょん 瑞鳳 食べるー! 幼妻 あ~ん 艦これ100users入り 良妻軽母 提督LOVE
9 最後に オリジナル 虫籠のカガステル メイキング希望! 幼妻 オリジナル500users入り エメラルドグリーン
10 第六駆逐隊と日替わり生活+夜 艦これ 何この天使達 幼妻 土日出勤 ダメ提督製造機 艦これ1000users入り 騎乗位

幼いのに奥さん的という意味でこちらもギャップ萌え的な萌え要素だと思われます(幼妻 (おさなづま)とは【ピクシブ百科事典】)。付与されたタグを見る感じ非エロほのぼの系のイラストが多いように見えます。それにしても艦これ多いですね。

萌え指数 No.8: 団地妻

順位 タイトル タグ
1 洗濯物と東條さん ラブライブ! 東條希 若奥さん のぞえり パンスト 団地妻 乳袋 solo 良妻賢母
2 オフの龍田 龍田 艦これ 艦隊これくしょん おっぱい 巨乳 たてセタ 眼鏡 酒飲んだ 団地妻 私服艦娘
3 ハピネスチャージプリキュア! 第3話 攻めるママ 漫画 ハピネスチャージプリキュア! 愛乃めぐみ 愛乃かおり 相楽誠司 相楽真央 人妻 団地妻 誠司君逃げて!超逃げて!! ハピプリ100users入り
4 まほうのエプロンにクラリーチェ参戦 アルカナハート クラリーチェ ハローキティとまほうのエプロン なにこれエロい 母性は原初のエロス むちむち ポニーテール ジーンズ 団地妻
5 若々しい奥さんですね ハピネスチャージプリキュア! 愛乃かおり 人妻は絶対に見逃さない西公太朗 定期的に人妻分を補給しなければ死んでしまう西公太朗 人妻 若妻 仕事が早い 団地妻 新たな人妻ターゲット ハピプリ100users入り
6 バレてしまった… ハピネスチャージプリキュア! 愛乃かおり 人妻 定期的に人妻分を補給しなければ死んでしまう西公太朗 ママキュア 早く薄い本を描く作業に戻るんだ 胸元 ハピプリ100users入り 団地妻
7 かおりさんとの約束だよ! ハピネスチャージプリキュア! 愛乃かおり たくし上げ 定期的に人妻分を補給しなければ死んでしまう西公太朗 人妻 はいてない ハピネス注入 団地妻 ハピプリ100users入り ニニンがシノブ伝
8 玄関開けると ハピネスチャージプリキュア! 愛乃かおり 定期的に人妻分を補給しなければ死んでしまう西公太朗 なんの支度かは言ってない 人妻チャージ西公太郎! ママキュア 団地妻 早く薄い本を描く作業に戻るんだ 差分希望 ハピプリ100users入り

タグを見た感じ微エロ的なイラストが多いように見えます。萌え要素というよりかはフェチ要素のような気がしないでもないです(団地妻とは (ダンチヅマとは) [単語記事] - ニコニコ大百科)。

萌え指数 No.9: 巨乳 百合

順位 タイトル タグ
1 无頼の女帝 サーヤ 尻神様 翠星のガルガンティア 美巨乳 リジット メガネっ娘 ラケージ 水着 百合 巨乳
2 小鞠先輩にちゅっちゅ!! のんのんびより 百合 一条蛍 越谷小鞠 コマちゃん ちっちゃくないよ! キス 小学生 巨乳 ほたこま
3 【C84新刊】咲-saki-嶺上の百合 石戸 霞&神代 小蒔 神代小蒔 石戸霞 C84 咲-Saki- 乳合わせ 永水女子 百合 咲100users入り 爆乳
4 媚薬リングで電マぶるぶる 爆乳 アヘ顔 潮吹き 電マ キャットファイト 女子プロレス レズ 網タイツ ふともも ロリ
5 エロレスのあれこれ~1~ 漫画 背後から胸揉み 巨乳 レズ バトルファック キャットファイト 百合 ロリ むちむち ふともも
6 違いは細部に... ふたなり アヘ顔 ブルマ リョナ レズ 体操服 女子プロレス 機械姦 爆乳 首輪
7 性闘エロレス 4発売! ふたなり アヘ顔 リョナ レズ 機械姦 爆乳 女子プロレス 体操服 ブルマ 首輪
8 双子S嬢 オリジナル 創作 女の子 巨乳 女王様 百合 土下座したくなる一枚 Tバック
9 マイクロビキニ オリジナル おっぱい マイクロビキニ 角娘 ナイスバディ もっと暑くなるべき 微エロ 巨乳 レズ
10 うさぎさん×2 オリジナル バニーガール 赤バニー 背後から乳揉み 巨乳 言い値で払おう 百合 網タイツ 背後から胸揉み

エロっすかね。

萌え指数 No.10: 巨乳 敵女

こちらもPVが5000を超えるイラストは分析対象のイラストにはありませんでした。ざっと付与されたタグを見た感じ、微エロ(ドロンジョ様的な)なイラストが多いように見えました。

まとめ

  • 萌え指数が高かった微/非エロ萌え要素は「残念な美人、巨大娘、処女ビッチ、幼妻、団地妻」。巨大娘・団地妻以外はギャップ萌え的な要素を持っているように見えました。
  • エロイラストはやはり強かった(ただしいろんな規制に引っかかりやすい)。スタンダードなエロ要素タグではなく、妙にマニアックなタグの萌え指数が高くなったことは興味深い点だと思います。
  • シリーズ物は固定閲覧者がつくのでPVが伸びやすいのかも。

結論

というわけで皆さん、明日からバナーやサムネは足柄さんにしましょう。このブログのサムネも足柄さんです。

課題

  • 萌え要素・エロ要素が多様、かつ、ハイレベルなものが多く解釈が難しい。識者による考察が期待される。
  • 以下を対応して純粋な萌え要素効果を見たい。
    • あからさまなエロ要素はもっとちゃんと除く。
    • 絵師さんの人気の変化に対応する。
    • シリーズ物効果を除く。

マンガでわかる統計学 回帰分析編

マンガでわかる統計学 回帰分析編


付録

萌え指数の高い / 低い萌え要素トップ100を以下に示します。

順位 萌え指数 PV25%ile PV50%ile PV75%ile イラスト数 タグ
1 0.29 3,279 8,233 26,665 51 残念な美人
2 0.28 500 1,358 7,159 107 レイプ目
3 0.24 841 2,140 5,194 108 ボテ腹
4 0.21 228 413 490 40 お姉さん アンドロイド
5 0.20 1,183 2,164 4,074 380 巨大娘
6 0.19 1,879 6,475 17,999 50 ビッチ 処女
7 0.17 653 2,745 10,978 49 幼妻
8 0.17 1,015 1,614 8,885 38 団地妻
9 0.17 407 1,381 4,144 83 巨乳 百合
10 0.17 400 1,322 3,078 51 巨乳 敵女
11 0.16 689 913 2,364 28 ニーソ 素足
12 0.15 1,115 4,187 12,132 259 目がハート
13 0.15 815 3,309 13,208 3008 おしり
14 0.15 550 1,310 3,449 53 大食い
15 0.15 2,264 6,956 16,789 562 前から見えるお尻
16 0.13 256 515 2,207 43 ブーツ 眼鏡
17 0.13 2,390 10,364 17,301 44 小柄 巨乳
18 0.13 10,892 17,709 30,419 29 おしり つけてない
19 0.13 334 870 1,964 104 パイロットスーツ
20 0.13 235 2,784 4,104 41 ケモノ 軍服
21 0.13 482 897 2,770 22 チャイナドレス 谷間
22 0.13 915 1,781 3,321 209 競パン
23 0.13 173 338 763 805 ヤンデレ
24 0.13 261 375 577 199 アンドロイド 女の子
25 0.12 149 193 279 28 人外 緑髪
26 0.12 401 575 1,181 45 バニーガール ポニーテール
27 0.12 999 1,867 4,345 53 腋毛
28 0.12 928 1,323 2,110 93 ふともも ぽっちゃり
29 0.12 235 324 1,402 14 下着 谷間
30 0.12 91 228 752 44 軍服 黒髪
31 0.12 261 728 4,290 192 ビッチ
32 0.12 839 1,566 2,978 23 ババア 眼鏡
33 0.12 2,617 4,538 4,874 22 オッドアイ バニーガール
34 0.12 704 1,260 4,765 155 衣装チェンジ
35 0.12 87 151 262 28 猫耳 赤毛
36 0.12 708 2,110 6,679 76 隠れ巨乳
37 0.11 178 258 450 31 ゾンビ ナース
38 0.11 721 1,707 4,967 361 黒スト
39 0.11 173 279 989 47 チャイナドレス 黒髪
40 0.11 306 734 2,429 2503 百合
41 0.11 154 262 510 59 ショタ 女の子
42 0.11 410 608 921 52 アンドロイド メイド
43 0.11 272 535 878 96 制服 小学生
44 0.11 698 1,378 4,129 277 人妻
45 0.11 340 2,577 7,004 58 アホの子
46 0.11 248 1,594 10,083 126 処女
47 0.11 683 1,176 2,142 97 女の子 競泳水着
48 0.10 518 1,289 2,127 43 ぽっちゃり ババア
49 0.10 457 1,061 3,277 25 巨乳 白衣
50 0.10 887 2,024 4,860 32 下乳 制服

 

順位 萌え指数 PV25%ile PV50%ile PV75%ile イラスト数 タグ
1 -0.38 322 345 402 27 サムライ スーツ
2 -0.21 117 144 357 22 浴衣 高校生
3 -0.18 150 250 338 62 ツンデレ 高校生
4 -0.18 81 110 144 26 お嬢様 人形
5 -0.16 382 672 855 53 ニーソ ハイレグ
6 -0.16 89 145 240 60 猫耳 高校生
7 -0.15 139 205 331 26 ロリ 中二病
8 -0.15 180 243 370 30 犬耳 魔法少女
9 -0.15 86 139 256 51 コスプレ 猫耳
10 -0.14 342 1,779 4,181 32 看板娘 腋
11 -0.13 153 323 677 57 獣耳 眼鏡
12 -0.13 753 1,082 1,424 57 ぽっちゃり 男の娘
13 -0.13 376 1,092 1,787 44 ハイヒール 巨乳
14 -0.12 161 235 520 60 おでこ 女の子
15 -0.12 374 581 1,322 60 おしり ケモノ
16 -0.11 309 404 828 36 制服 男装
17 -0.11 83 135 223 130 お兄さん
18 -0.11 133 301 537 42 制服 犬耳
19 -0.10 76 127 192 35 パーカー 茶髪
20 -0.10 177 235 656 24 パジャマ 黒髪
21 -0.10 159 249 457 57 ツインテール ミニスカート
22 -0.10 300 637 1,224 36 ふともも 獣耳
23 -0.10 137 151 180 36 宇宙人 緑髪
24 -0.10 80 112 181 25 バカ 高校生
25 -0.10 98 116 319 26 刺青 黒髪
26 -0.10 96 174 263 54 タレ目
27 -0.10 139 207 255 86 お姉さん 高校生
28 -0.09 134 189 249 28 かぶりもの 着ぐるみ
29 -0.09 152 315 729 64 サンタ服
30 -0.09 96 167 295 554 帽子
31 -0.09 162 349 1,860 27 角 黒髪
32 -0.09 127 273 500 49 巫女 猫耳
33 -0.09 152 239 555 130 体操着 高校生
34 -0.09 98 186 321 26 単眼 猫耳
35 -0.09 172 408 706 40 そばかす 眼鏡
36 -0.09 116 208 625 30 ツインテール 性転換
37 -0.09 184 240 377 128 ポニーテール ロリ
38 -0.09 140 261 447 51 アイドル ツインテール
39 -0.09 80 161 335 35 悪魔 赤毛
40 -0.09 104 156 246 35 スーツ 金髪
41 -0.08 89 152 385 26 姉 美人
42 -0.08 972 1,378 2,271 28 おっぱい 幼馴染
43 -0.08 54 61 75 67 お嬢様 高校生
44 -0.08 107 139 245 24 姫 金髪
45 -0.08 125 174 365 27 青髪 高校生
46 -0.08 119 217 533 958 マフラー
47 -0.08 99 130 191 77 擬人化 高校生
48 -0.08 123 187 396 41 女の子 幼馴染
49 -0.08 98 164 357 58 小悪魔 悪魔
50 -0.08 74 110 189 81 スーツ ネクタイ


*1:そういえば、"あざとい" という萌え要素は上記のニコニコ大百科にはなかったので萌え要素リストには入れてないことにこの時点で気づきました。"残念な美人" の萌え指数が高かったことを考えるとわりといい線いきそうなのですが…

*2:"サムライ スーツ" はほとんど一人の絵師さんによるものでした。その絵師さんは最近は "サムライ スーツ" タグの付いたイラストを描いていない、かつ、徐々にその絵師さんの全体的なPVが上がっているため、過去のイラストによくついていたタグの萌え指数が低くなってしまっていたと考えられます。これの解消にはモデルに絵師さんの人気の変化を組み入れることが必要だと思います。

「居心地のいいソーシャル」なソーシャルゲームとはどんな環境なのか? ちょっと考察してみた

はじめに

ソーシャルゲームはその名の通りプレイヤー同士が社会的なやりとり(競争や協調)を楽しむゲームです。例えば、貴重な報酬を獲得するための競争や、グループ内で協力して目的を達成するギルドバトル・レイドなどの枠組みが主だったかと思います。一方でそういった社会的なしがらみに疲れてしまい、ソーシャルゲームを嫌がる人も増え、ソーシャル要素の弱いゲームが主流になりゲームの形態は大きく変わりました。さらに最近では直接の友人との協力プレイのゲーム(モンストや白猫など)がヒットするなど新たなソーシャルの形態が出てくるなど、ゲームにおける "ソーシャル" 要素はめまぐるしく変化しています。

ではなぜソーシャル要素はこんなにも変化したのでしょうか?

ゲームでもSNSでも現実でも、他人と競争したり、仲間と協力したり、他愛のないコミュニケーションをとったり基本的にはソーシャルは楽しい要素です。しかし、ゲームでもSNSでも現実でも、競争やコミュニケーションなどに時々疲れてしまうこともあるかと思います。それでは、"ちょうどいい" ソーシャル度合いみたいなものはあるのでしょうか? ソーシャルゲームのソーシャル疲れというテーマについて人の社会的な振る舞いの代表例である「協調行動」の観点から考察してみたいと思います。

なぜソーシャルは疲れるのか?

これには複数の要因があると考えられます。例えば「競争」や「社会的しがらみ」だと思います。ここでは「社会的しがらみ」によるソーシャル疲れに焦点を当ててみます。ここで考える社会的なしがらみとは、「仲間が今がんばってるのだからがんばらないと」と思ったり、「仲間が◯◯してくれたから◯◯をお返ししないと」と思うなどして人の行動が制限されることだと考えます。

ソーシャルゲームではプレイヤーは前述のように互いに協調しあってゲームをプレイしています。こういった協調行動において,協調的な人とそうでない利己的な人がやりとりをすると、協調的な人が一方的に協力する(搾取される)だけになってしまい、やがて協力的な人はいなくってしまう(協調的でなくなったり排除されたりする)ため、協調行動は成立しにくいと考えられます【Axelrod, 2006】。ところが、人は現実世界やソーシャルゲームの社会でお互いに協力しあっています。したがって,人は相互に協調し合う関係を維持する心理的なメカニズムを持っているはずです。

そういったメカニズムには、

  • 「自分に協調してくれそうな(将来、相手からの利益が見込める)相手にだけ協調」する互恵性
  • 「自分に協調してくれそうかどうかを間接的に予想(他人同士のやりとりを見るとか)」する評判
  • 「利己的な相手に罰を与えて利己的な振る舞いを抑制」する懲罰

などがあります【長谷川, 2000】。またソーシャルゲーム内でも互恵性や懲罰といったメカニズムが機能している可能性も示されています【高野, 2014】。

これらは他人の利己的な振る舞いを抑制する効果があるため、協調行動を促進することができますが、同時にソーシャル疲れの原因になりうるのではないかと私は考えています。例えば、協調してくれた相手に協調し返さないと、「あいつは協調的じゃないから関わるのやめよう」という悪い評判が立ち、協調してもらえなくなる可能性があるため、そのグループでうまくやるにはちゃんと協調し返さなければなりません。しかし、現実には十分に協調しかえすことがリソース(時間やお金)の問題で難しく、無理をしなければならない(自分持つリソースの許容範囲以上のコストを支払わなければならない)こともあるでしょう。こういったことがソーシャル疲れにつながるのではないかと考えてます。

ソーシャル疲れは緩和可能か?

前節では互恵性・評判・懲罰のメカニズムが協調行動を促進し、また、それがソーシャル疲れにつながっているのではないか? と述べました。そういったことがソーシャルゲームの主流が協調を主軸においたタイプからソーシャル要素が弱いタイプに推移した要因の一つではないかと考えています。しかし、お互いに協調して目標を達成するようなことはとても楽しい要素であり、協調し合うという要素はゲーム中にあったほうが楽しいだろうなぁとも思っています。

実は互恵性・評判・懲罰がなくても協調行動は成立可能であることがHauertら【Hauert, 2002】によって理論的に示されています。Hauertらは、協調しかしない個体(Cooperator)、搾取しようとする個体(Defecter)、社会的なやりとりをしない個体(Loner)の3種類の戦略(社会的相互作用のタイプ)について考え、利得の多い戦略を持つ個体が増えるという集団(つまり選択淘汰によって進化する集団、または、各個体が利得の高い行動を学習して戦略を変える集団)という非常にシンプルな集団を考えました。ここで、Cooperator同士が相互作用した結果の利得は、Lonerよりも大きく、また、Cooperator/DefectorがDefectorと相互作用した時の利得は、Lonerよりも小さいとします。また、ここで考えている個体は個体識別能力や記憶能力すら持たず、ただひたすら自分の戦略の通りに協調したり裏切ったり参加しなかったりするという非常にシンプルな個体です。

この集団をシミュレーションした結果は以下です。

  • Loner戦略の利得が比較的大きいと、CooperatorはDefectorに搾取されてDefectorが多くなるもののDefector同士の相互作用は利得が低いので、その後にLonerが増加し、Lonerのみになってしまいます。
  • Loner戦略の利得が小さめであるときに、この3種類の戦略は共存し増減するような振動を続けます。つまり、協調行動が部分的に成立します。


この結果からソーシャルゲームに対して示唆するところを考えると、ソーシャルゲームにおいても、社会的なやりとりに参加しないという選択肢があり、参加しなかった場合の利益が「協調し合うより小さく、搾取されるよりは大きい」かつ、その利益が大きすぎない場合に、「社会的なしがらみ」がなくても協調行動は成立可能であることです(そうでないとみんな単独でゲームするだけ)。したがって、協調し合うことのインセンティブ・他人を搾取することのインセンティブが、単独でゲームをすることとバランスがとれていれば、ソーシャルにプレイしたり(協調し合うとか)、一人でプレイしたりといったプレイスタイル(戦略)を気軽に変える事ができ、協調がある程度成立しつつ互恵性・評判・懲罰の3つのメカニズムの必要のない環境を作ることができると考えられます。人は互恵性・評判・懲罰が可能な能力を持っているので、そういった環境でも社会的しがらみを完全に失くすことは難しいと思われますが、緩和は可能なのではないかと考えています(それについてHauertらのモデルを拡張して検討してみても面白いかも)。

まとめ

ソーシャルゲームにおけるソーシャル疲れについて、協調行動を促進するためのメカニズム(互恵性・評判・懲罰)が要因の一端ではないかと考え、それを軽減しつつ協調的な環境を用意するにはどうしたらいいか? について考んがえてみました。

上記のように社会的な場面に参加しなくても大きく得も損をしないゲームデザインをすることで、互恵性・評判・懲罰などの社会的しがらみの要因になりうるメカニズムを使わなくてもいいように設計することが、ソーシャル疲れしにくく、かつ、協調的な振る舞いが成立している「居心地のいい」ソーシャルゲームのデザインにつなげることができるんじゃないかなぁと思います。

参考文献

  • Axelrod, R., "The Evolution of Cooperation: Revised Edition", Basic Books, 2006.
  • Hauert, C, De Monte, S, Hofbauer, J, Sigmund, K, "Volunteering as Red Queen mechanism for cooperation in public goods games", Science, vol. 296, pp.1129-1132, 2002.
  • 長谷川 寿一, 長谷川 真理子, "進化と人間行動", 東京大学出版会, 2000.
  • 高野雅典, 和田計也, 福田一郎, "ソーシャルゲームにおける互恵的利他主義に基づく協調行動", 第8回ネットワークが創発する知能研究会(JWEIN2014), 2014.


Shiny-Serverをたった1行の変更でマルチプロセス化する方法

最近、R界隈でShinyが熱いですね。
Shinyって何かというと、Rだけで簡単にGUIの可視化・分析環境を作れるRのフレームワークです(Shiny)。
もともとRの持つ可視化・分析の手軽さはそのままに、GUIも手軽に作れるという特徴があり、Rの豊富なライブラリや手軽な分析環境を分析ツールの開発にも使えるようになりました。

Shinyというライブラリは基本的にはスタンドアローンで利用するツールなのですが、開発したツールのバージョンアップ・データの配布とかを考えたらWebアプリ化できるととても便利です。
実はそのようなプロダクトも用意されていて、それをShiny-Serverと言います。ざっくり、Rで動いているShinyの前にnode.jsで作ったサーバを立てて、そいつがリクエストをさばく感じです。

先日の Japan.R でも @wdkz さんがShiny-ServerについてLTしてたようです。

ただ、このスライドにもある通り複数ユーザが同時に利用すると1つのRのプロセスを共有するため非常に低速になるという重大な欠点があります。

f:id:swarm_of_trials:20131211160835p:plain
(@wdkz さんスライドp. 10より)

んで、それの対策として考えられるのは以下の2つです(@wdkz さんスライドp. 11より)。

  1. Shiny-Server+Rを(複数のサーバで)プロセス起動 させて、その前にロードバランサーを立てて負荷分散する
  2. Shiny-Serverの後ろに複数Rプロセス起動させる

@wdkzさんのスライドでは1の方法について解説しています。
これによって同時に快適に利用できるユーザ数を増やすとともに、既存のShiny-Serverでは認証ができないという問題もある程度解決できていい感じです(分析ツールでは特定の人にしか見せられない情報を表示しないといけないので、組織でShiny-Serverを使う場合には認証ができることはとても重要だと思います)。

でも、この方法だと Rプロセスの数(= 同時に快適に利用できるユーザ数)の数だけShiny-Serverも立ち上げないといけないのでちょっと大変 & nonblockingなnode.jsのいいところを全く活かすことができ無くてJavaScript好きな私としてはちょっと (´・ω・`) です(node.js部分は大した処理をしないので実用的にはそこまで問題じゃないと思います)。

とか思いながらスライド見てると…
f:id:swarm_of_trials:20131211181840p:plain:w350

!?

なんか指名されたので、やってみましょう。

そのためにはShiny-Serverのソースコード(node.js)を変更する必要があります。v0.3.6で試しましたが、ざっと見た感じ現時点での最新版のv0.4.0でもいけそうです。

このブログのタイトルにある通り書き換えるのはたったの1行です。
書き換えるファイルはこちら(GitHub: shiny-server/app-spec.js at master · rstudio/shiny-server · GitHub)。

  • 【Shiny-Serverのインストールディレクトリ】/lib/worker/app-spec.js

普通にインストールすると /usr/lib/node_modules/shiny-server あたりにShiny-Serverはインストールされるかと思います。

書き換える内容は24行目〜30行目ぐらいの以下の部分です。

 this.getKey = function() {
    return this.appDir + "\n" +
      this.runAs + "\n" +
      this.prefix + "\n" +
      this.logDir + "\n" +
      JSON.stringify(this.settings) + 
      Math.random(); //乱数を追加!
  };

Shiny-Serverを起動して、重い処理をするShinyアプリに複数のブラウザ/タブからアクセスしてみてください。
topコマンドなどでCPU使用率を見てみると、これまで複数のブラウザ/タブに対してRのプロセスが1つしかなかったのに対して、変更後は複数のRプロセスが立ち上がり、並列に処理をしているのがわかると思います。

なんで、乱数を1つ追加しただけでマルチプロセス化できたの?

まず、ShinyとShiny-Serverがやっていることについて見て行きましょう。

  • Shiny単体で利用する場合

f:id:swarm_of_trials:20131211173140p:plain:w250

  • Shiny-Serverで利用する場合

f:id:swarm_of_trials:20131211173138p:plain:w400

Shinyはローカルで利用することを前提としていて、上記のようにユーザのブラウザとShinyが1対1でWebSocketで通信して、ユーザの操作をRが知ったり、Rの処理結果をユーザの画面に表示したりしています。Shinyの起動はRコンソールからrunApp関数を叩くことで実行します。

Shiny-Serverの場合は、まずユーザがShiny-Serverのアプリにアクセスして(①)、Shiny-Serverがアクセスされたアプリに対応するR(Shiny)プロセスを起動します(②)。そして、プロセスが起動したらShiny-Serverはブラウザ・ShinyそれぞれとWebSocket通信を確立します(③・④)。あとはShiny-Serverはブラウザから来たものをそのままShinyに渡し、Shinyから来たものをブラウザに渡してるだけです。このとき裏で動いているShinyは単体で利用している場合と全く同じです。

それでキモはShiny-Serverの②のところなんですが、Shiny-ServerはShinyプロセス一つ一つにキーを割り当てて、同じキーに対応するプロセスが存在する場合は、新たにプロセスを起動せずに再利用します。つまり、複数のブラウザからのアクセスに対して同一のキーを生成するのであれば、それらブラウザのリクエストは同じR(Shiny)プロセスによって処理されることになります。要するに重いです。

んで、そのキーを生成している所が上でも挙げた

  • 【Shiny-Serverのインストールディレクトリ】/lib/worker/app-spec.js

の getKey関数

 this.getKey = function() {
    return this.appDir + "\n" +
      this.runAs + "\n" +
      this.prefix + "\n" +
      this.logDir + "\n" +
      JSON.stringify(this.settings);
  };

です。
この関数を見てみると、アプリケーションのディレクトリやログのディレクトリ、設定内容などなどからキーを生成している事がわかります。Shinyではアプリケーションのディレクトリ = アプリ名なので、基本的にはアプリ(+設定)ごとにR(Shiny)プロセスを立ち上げています。
なので、このキーを毎回変わるようにすれば、ブラウザのアクセスごとにR(Shiny)プロセスが起動するので複数のユーザがプロセスを共有してしまい、だれか1人の重い処理が走っている間は待たないといけないといった状況を回避できます。

それで一番最初に戻ると、とりあえず一番簡単な方法として、キーの生成時に乱数つけてしまえばいいんじゃね? ってことです。

ただし、この方法だと同時接続数 = Rプロセスの数になってしまうので、本格的に運用する場合はキーの生成方法をちょっと工夫して最大プロセス数を制御するなどやったほうがいいかと思います(私はそうしました)。
なんでかというと、各リクエストの処理高速化のために、大きいデータをShiny起動時にメモリ読み込むような作りする場合とかもあると思うのですが、そうするとプロセスが増えるたびにメモリ使用量もどんどん増えていくからです。

ちなみに、一旦、ブラウザとShinyのWebSocket通信が確立してしまえば、間に立ってるShiny-Serverはデータを受け渡すだけで、キーが接続確立後に使われることはありません(画面のボタンを押すたびにプロセスが立ち上がったりしない)し、ブラウザとShiny-ServerのWebSocketの接続が切れると対応するR(Shiny)プロセスも落ちるようになってるので、ゴミが残る心配もありません(※ ただコードに手を入れる前からたまにゴミプロセスが残ることがあります)。

ただ、Shinyはconfファイルで色々設定できるのですが、上記の変更をしたうえで凝った設定をすると動かなくなるかもしれないです(未検証なので)。

まとめ

  • キーの生成時にひと工夫加えることで複数のリクエストを並列で捌くことができる!やった!
  • Shiny-Serverのソースコードはとてもシンプルなので読みやすい!いじりやすい!

RとRubyによるデータ解析入門

RとRubyによるデータ解析入門

データサイエンティスト養成読本 R活用編 【ビジネスデータ分析の現場で役立つ知識が満載! 】 (Software Design plus)

データサイエンティスト養成読本 R活用編 【ビジネスデータ分析の現場で役立つ知識が満載! 】 (Software Design plus)

注意事項

  • 本記事の内容について私は一切保証はしません。エライことになっても責任も負いません。
  • Shiny-Serverのライセンスは AGPLv3 なので、ソースコードを変更してサービスを公開する場合は注意して下さい。