RM-BLOG

IT系技術職のおっさんがIT技術とかライブとか日常とか雑多に語るブログです。* 本ブログに書かれている内容は個人の意見・感想であり、特定の組織に属するものではありません。/All opinions are my own.*

ストレイテナーと確率10(トリビュートアルバムの正解確率)

ストレイテナー20周年企画のトリビュートアルバム「PAUSE」には、
過去の12の楽曲に対しそれぞれ(テナー本人を含め)12のアーティストが参加する。
公式サイトではこれの組み合わせを当てる参加型クイズ企画が開催されている(2017年9月末まで)。
テナーファンのみなさんは過去の経験やアーティスト間の関係性等により皆予想をたてており、
実際そうした経験則や知識に基づく予想により、闇雲に選ぶよりは正解に近い組み合わせを導けるのは事実であろう。

しかしこれを「同様に確からしい」確率モデルとして評価した場合に、
正解である1つの組み合わせを導ける確率はいかほどのものか?を調査してみたくなった。
(やはり同様に仕事に飽きたので暇つぶしである。夜間はこういうことしてるほうが頭がさえるぜ。)
これは下記のツイートに基づきちょっとだけマジになって本格的に取り組んでみようと思い至ったことによる。



※ちなみに先に訂正しておくと、
 このツイートでは「全組み合わせ数=11!=39916800」と書いているが、
 よく見たらトリビュート曲数は12だったので、そもそも母数の捉え方に誤りがある。
 よって全組み合わせ数は12!=479001600(4億7千9百万とんで1600)が正解である(やべええええええ)

【「ストレイテナーと確率」シリーズリンク集】
ストレイテナーと確率
ストレイテナーと確率2
ストレイテナーと確率3
ストレイテナーと確率4
ストレイテナーと確率5
ストレイテナーと確率6
ストレイテナーと確率7
ストレイテナーと確率8
ストレイテナーと確率9



 


先に正解を出しちまったが、12の曲のどれにどのアーティストが当てはまるか?の全組み合わせ数は12!=479001600である。
このため、この中に正解の組み合わせが1通りだけあると仮定すると、
その正解確率は1/12!=2.09×10-9である。
要するに約5億分の1の確率ということになる。

ちなみにこのNEVERまとめを見る限りだと、
ジャンボ宝くじ1等の当選確率がわずか1000万分の1(もはや「わずか」と言いたくなるレベル)らしいので、
テナートリビュートの正解組み合わせを完全ランダムで予想した場合に比べればはるかに高確率といえるだろう。
単純な確率の数値の比較だけでいえば、
「テナートリビュートを正解させるのはジャンボ宝くじ1等当選するより約50倍も難しい」
言い換えればこれを当選させられるほどの運の持ち主なら、宝くじ1等を47.9回(約50回)当選させられる。
うわああああ大金持ちだあああああああああああ!!!!!




で、もし、理論値の予想の通りなら、
モンテカルロ法によるシミュレーションを行えば、(結構な回数の試行が必要だが)理論値に近い数字を導出できるはずである。
よって例にもれずちょっとしたシミュレーション用のプログラムを作って試してみようと思う。
作成したプログラムはこれ。

import java.math.*;
import java.util.*;
import java.text.*;
import java.io.*;

public class StraightenerTributeShuffleResolve {

	private static final DateFormat yyyyMMddHHmmssSSS = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
	private static final int LOG_POINT = 1000000;
	private static final long END_ROUND = 10000000000L;
	
	private static final String[] ARRAY_SONGS = new String[] {
		"Farewell Dear Deadman"
		,"KILLER TUNE"
		,"Melodic Storm"
		,"REMINDER"
		,"ROCKSTEADY"
		,"SAD AND BEAUTIFUL WORLD"
		,"SENSELESS STORY TELLER SONY"
		,"SIX DAY WONDER"
		,"TRAVELING GARGOYLE"
		,"シーグラス"
		,"シンクロ"
		,"冬の太陽"
	};
	private static final String[] ARRAY_ARTISTS = new String[] {
		"ACIDMAN"
		,"ASIAN KUNG-FU GENERATION"
		,"9mm Parabellum Bullet"
		,"go!go!vanillas"
		,"THE BACK HORN"
		,"the pillows"
		,"SPECIAL OTHERS"
		,"back number"
		,"My Hair is Bad"
		,"MONOEYES"
		,"majiko"
		,"ストレイテナー"
	};
	private static final String[] ARRAY_ANSWER = new String[] {
		"SENSELESS STORY TELLER SONY-ASIAN KUNG-FU GENERATION"
		,"TRAVELING GARGOYLE-ストレイテナー"
		,"KILLER TUNE-9mm Parabellum Bullet"
		,"冬の太陽-THE BACK HORN"
		,"シーグラス-back number"
		,"ROCKSTEADY-MONOEYES"
		,"SIX DAY WONDER-ACIDMAN"
		,"Farewell Dear Deadman-the pillows"
		,"SAD AND BEAUTIFUL WORLD-majiko"
		,"REMINDER-My Hair is Bad"
		,"シンクロ-go!go!vanillas"
		,"Melodic Storm-SPECIAL OTHERS"
	};
	private static final int LIST_LENGTH = ARRAY_SONGS.length;
		
	public static void main(String[] args) {
		execMain();
	}
	
	private static void execMain(){
		printLog("★開始");
		long round = 0L;
		int answerCount = 0;
		while(true) {
			List<String> songs = new ArrayList<String>();
			List<String> artists = new ArrayList<String>();
			for (int i=0; i < LIST_LENGTH; i++) {
				songs.add(ARRAY_SONGS[i]);
				artists.add(ARRAY_ARTISTS[i]);
			}
			
			String[] randomChoise = new String[LIST_LENGTH];
			for (int j=0; j < LIST_LENGTH; j++) {
				int songsIdx = (int)(Math.random() * songs.size());
				int artistsIdx = (int)(Math.random() * artists.size());
				String song = songs.get(songsIdx);
				String artist = artists.get(artistsIdx);
				
				randomChoise[j] = song + "-" + artist;
				songs.remove(songsIdx);
				artists.remove(artistsIdx);
			}
			if (isAnswer(randomChoise)) {
				answerCount++;
				printLog("正解の組み合わせに遭遇!!うおおおおおおおおおおおお!!!よかったね!!!!");
			}
			round++;
			if (round % LOG_POINT == 0) {
				printLog(round + "回ループ完了...");
			}
			if (END_ROUND > 0 && round > END_ROUND) {
				break;
			}
		}
		printLog("★終了、全ループ回数="+round + "中、正解遭遇回数=" + answerCount + "、正解率=" + ((double)answerCount/(double)round));
	}
	
	private static boolean isAnswer(String[] randomChoise) {
		List<String> randomChoiseList = new ArrayList<String>();
		for (int i=0; i < randomChoise.length; i++) {
			randomChoiseList.add(randomChoise[i]);
		}
		List<String> answerList = new ArrayList<String>();
		for (int j=0; j < ARRAY_ANSWER.length; j++) {
			answerList.add(ARRAY_ANSWER[j]);
		}
		for (Iterator<String> it = randomChoiseList.iterator(); it.hasNext(); ) {
			String randomCoiseString = it.next();
			if (!answerList.contains(randomCoiseString)) {
				return false;
			}
		}
		return true;
	}
	
	private static void printLog(Object msg) {
		StringBuilder sb = new StringBuilder();
		sb.append(yyyyMMddHHmmssSSS.format(new Date()));
		sb.append(" ");
		if (msg != null) {
			sb.append(msg.toString());
		}
		
		System.out.println(sb.toString());
	}

}


理論値は「約5億分の1」という超低確率=つまり「5億回に1回だけ正解するレベル」なので、
普通に考えて試行回数は最低でも5億回は必要になる。
しかし、このテの検証にあたって試行数と理論値を一致させるのは検証にならないためもっと増やす。
ちょうどキリがいいので10億にしたいところだが、
理論値ベースでもその中で正解するのは2回あるかないか。若干心もとない。
なので、思い切って試行数を一旦100億回として設定する。
5億回に1回当たるということは、平均して100億回に20回あたる計算になるはずだ。
なお、100億という数値はint型の限界を超えているため、long型として定義する。

あらかじめ定義している文字列の配列(要素数12)から乱数を使って位置を特定し、
曲及びアーティストの組み合わせをつくって検査用の内部リストに詰め込んで、
これまたあらかじめ定義している「正解」にあたる組み合わせと照合して、
全件一致していたら「正解」とするプログラム。
検査文字列はなんでもいいんだが一応”曲名+"-"+アーティスト名”とした。
ランダムで抽出した曲とアーティストの組み合わせを↑に則って連結して文字列にし、正解かどうかを照合する。

ちなみに、1万回程度ならそこまで深く考えないが、100億回回すとなると、
1ループにかける時間を出来るだけ短くする工夫をしないと、そもそも終わらなくて検証にならないなんてことも考えられる。
このため「乱数を使って抽出する」ところと「それが正解か」ACIDMANみたいだな…あれは「今透明か」か)という判定については
少なくとも目に見える無駄をなくすようロジック化した。
たとえば、「乱数を使ってランダム要素を抽出した後、それを検証用のリストに詰め込む」ところについては、
(1)取り出す⇒(2)内部リストにいるかどうかチェック⇒(3)いなければadd、いればもう一度(1)に戻る
というロジック化が比較的簡単に思いつくものだが、
こうすると検証用の内部リストを生成するために(1)~(3)を無駄に何度も繰り返す可能性があり、タイムロスになる。
このため、ループの先頭で、検証の母体となるワーク用のリスト変数に曲ないしアーティストを全要素移しておいて、
ランダム抽出する度ワーク用のリスト変数からremoveするように実装した。
また、「それが正解か」の判定に関しては、
お互いの要素をListに詰め込んで、僅かながらも早くするためIteratorによる全走査を行い、
1つでも違う組み合わせが見つかった時点でfalseとする等の考慮もしている。(まあこの辺はPGの常識の範疇か)

ただし、いきなり100億回の試行に挑むのはいささか無謀である。
プログラムが正常動作する保証もないので、まずは母数の少ないケースを作り出して実験してみることにした。
「曲」と「アーティスト」の候補のうち、以下4つだけを選定して、母数4のリストで挑んでみる。
(この4つを選んだことには全く意味はない。公式サイトの紹介順序の上から順に4つ、という程度である)

曲アーティスト備考(どうでもいいコメント)

Farewell Dear Deadman ACIDMAN う~ん…?なんか想像がつかないな。個人的に。
(これが正解だったらどうしようw)
KILLER TUNE ASIAN KUNG-FU GENERATION SOFTツアーかなんかでテナーのメンバーが言ってたが、
ゴッチはサビ部分を「きっらはぁ~ん」って歌うんだそうだ。
聴いてみたいw
Melodic Storm 9mm Parabellum Bullet 9mmはこういうさわやか系がそこまで合わないような。。。
と思ってたがなんか菅原さんの声のメロストは脳内再生余裕!
REMINDER go!go!vanillas 悪くないかもしれん。
しかしバニラズはポップな印象が強い分、
トリビュートのラインナップでピッタシ来る曲が
見当たらない。。

面倒なので変数定義部分を以下のように変えて挑む。

	private static final String[] ARRAY_SONGS = new String[] {
		"Farewell Dear Deadman"
		,"KILLER TUNE"
		,"Melodic Storm"
		,"REMINDER"
	};
	private static final String[] ARRAY_ARTISTS = new String[] {
		"ACIDMAN"
		,"ASIAN KUNG-FU GENERATION"
		,"9mm Parabellum Bullet"
		,"go!go!vanillas"
	};
	private static final String[] ARRAY_ANSWER = new String[] {
		"Farewell Dear Deadman-ACIDMAN"
		,"KILLER TUNE-ASIAN KUNG-FU GENERATION"
		,"Melodic Storm-9mm Parabellum Bullet"
		,"REMINDER-go!go!vanillas"
	};


母数4の場合の全組み合わせ数は4!=24なので、正解1つを当てる確率は1/24≒0.04166…である。
この程度なら100回の試行でも4回はあたる計算なので、大分実験しやすい。
試行回数を10万回として実験してみた結果は以下の通り。

★終了、全ループ回数=100000中、正解遭遇回数=4188、正解率=0.04188


シミュレーション結果が0.04123958760412396なので、理論値の0.04166…と近い。
まあ、まともに動作してると見ていいだろう。

では、12曲のトリビュート組み合わせに100億回の試行で挑戦する!!




実験にあたっては、仮でもいいので、とりあえず1つ、「正解の組み合わせ」を設定しておく必要がある。
なんでもいいのだが、とりあえず、某フォロワーさんがツイートしていた組み合わせを使わせてもらう。
すなわち

曲アーティスト備考(どうでもいいコメント)

SENSELESS STORY TELLER SONY ASIAN KUNG-FU GENERATION ゴッチが選んだ曲をホリエ氏に伝えたら
「お、おう」みたいな反応されたということで、ネット上では
「トリビュートのラインナップで「お、おう」ってなるのこれしかねえ!」
という説が広く流布している。
ゴッチは「アっ君の歌いまわしが難しい」というツイートしていたが
これそこまで難しいかな…?(うたったことないからわからんがw)
TRAVELING GARGOYLE ストレイテナー トリビュートのラインナップと、本人たちが参加するという情報を見たとき、
これは俺もそう思った(もしくはREMINDER)。
記念すべきアニバーサリーにデビュー曲を演るという素敵な演出。
2013年武道館公演の一発目を思い出す。
音源で聴いたら泣いてしまいそうだ…
KILLER TUNE 9mm Parabellum Bullet 確かになんとなく「っぽい」感じはする。
black market blues的なノリというか。
英語詞のイメージがあまりないけどなあ。
冬の太陽 THE BACK HORN 「曲名がバクホンっぽい」という点で同意したいw
でも曲調的にはこの曲個人的にACIDMANっぽさがあるんだよなあ。
最初のイントロ部分大木さんの声で歌ってるの
脳内再生余裕なんですが。
シーグラス back number back numberは個人的になぜか
「魚」「海」みたいなイメージが強い
(「逃した魚」とか「fish」のせいかな)
なのでなんとなくピンとくる組み合わせ。
ROCKSTEADY MONOEYES Sky Jamboree 2017で細見さんが乱入して歌ったという。
まあスカジャン前から「細見さんはロクステ」という説が広まっていたが。
そういうのもあってこれも定説感が強い。
SIX DAY WONDER ACIDMAN 俺はよく知らないが、
ACIDMANは6日。他のバンドに譲るとは思えない」
という説が非常に強いようだ。
確かにこれも大木さんの声での脳内再生余裕。
ピアノのあたりどうするのかなーって気はするが。
Farewell Dear Deadman the pillows 個人的に、pillowsのイメージが、
なんかすごく「ハッピー」な感じなので、
その意味では合ってるような気はする。
SAD AND BEAUTIFUL WORLD majiko 本人が過去、バンドでカバーしたとか。
そういった背景からこの組み合わせも定説感は強い。
REMINDER My Hair is Bad マイヘアが昔バンドでカバーしたことがあるとか。
同様の理由で定説感が強い組み合わせ。
シンクロ go!go!vanillas これはなんとなくイメージがわかない。
というかバニラズは全体的にポップな印象が強いので
個人的にこのラインナップどれもなんとなくしっくりこない。
インディーズ時代の「NON TITLE」とか、
「MAD PIANIST」とか、そんな感じのイメージ。
どれが正解かなあ?
Melodic Storm SPECIAL OTHERS スペアザは本当にわからない。
そもそもトリビュートのラインナップに
テナー唯一のインスト「SNOOZE」がない
というのも
難しさの一つになってるが、
仮に「SNOOZE」があったとしてもスペアザっぽくはないし。
ただ激しめロックな曲よりは、
こういう少し爽やか系があってるというのはなんとなくわかる。

である。

さあ、いざ実験!




と思ったが2億回試行するのにも約40分かかる始末。
つまり100億回はその50倍、約40分×50=2000分≒33.33…時間
まじかよ……ドラクエ5全クリできるぜ………

もう少しスペックのいいマシンならもっと早く回るんだろうが、会社の貧弱マシンではこれが限界だった。。
まあ33時間くらいなら流しっぱなしでもいいかあ~と思ったが
さすがに結果が出るまで33時間待つというのも現実的ではない(どんなに遅いバッチだって日次だろ!というSE的感覚だ)ので
残念だが試行回数を見直すことにする。
会社のマシンのスペック向上は常日頃から文句を言ってはいるが一向に改善されない。
メーカー系なのだからもっと現場に目を向けてほしいところだ。
(会社のマシンでテナートリビュート予想してるやつが言えたことではないがw)




100億回には若干無理があるとわかったので、とりあえず回数を見直して10億回にしてみる。
本当はもっと大きい値にしたいのだが、スペックとの兼ね合い及び処理時間を鑑み、
一旦この数値にさせていただく(特に「スペック」という点で値を制限しなければならないのは残念でならない…)
ただし、もともとの確率が低すぎる(5億分の1)ため、
10億回回しても、理論値ベースの想定正解数はわずか2回である。
運が悪いと1回も正解しない可能性もある。
そういうのもあって、10億回程度では正直、精度の高い実験にはならないだろう。
その意味では最早理論値ベースの最小試行回数5億回でも大した差がないとは思っている。
しかし2億回回すのに約40分=5億回回すのにも約100分(1時間40分)かかるうえ、
言ってるとおり5億回はあくまで「理論値ベースの最小試行回数」だから
当然5億回回しても1回正解するかどうかはわからない。
まあ、ちょっとした時間で実験しきれるほどのものではないことはわかったのと、
どうせやるなら「1時間40分かけて実験して1回も当らなかった」なんて残念な結末を迎えたくはないし、
結果を得られるまで流しっぱなしにして放置する前提でやってみることにする。
とりあえず流してみた。

2017/09/08 11:31:15.423 ★開始
2017/09/08 11:33:18.216 10000000回ループ完了...
2017/09/08 11:35:21.351 20000000回ループ完了...
2017/09/08 11:37:25.664 30000000回ループ完了...
2017/09/08 11:39:27.557 40000000回ループ完了...
2017/09/08 11:41:30.443 50000000回ループ完了...
2017/09/08 11:43:33.837 60000000回ループ完了...
2017/09/08 11:45:36.848 70000000回ループ完了...
2017/09/08 11:47:39.093 80000000回ループ完了...
2017/09/08 11:49:41.890 90000000回ループ完了...
2017/09/08 11:51:53.212 100000000回ループ完了...
…(以後ダラダラログが続くので省略)…
2017/09/08 13:04:24.984 450000000回ループ完了...
2017/09/08 13:06:03.425 正解の組み合わせに遭遇!!うおおおおおおおおおおおお!!!よかったね!!!!
2017/09/08 13:06:30.795 460000000回ループ完了...
…(以後ダラダラログが続くので省略)…
2017/09/08 14:27:08.865 850000000回ループ完了...
2017/09/08 14:27:36.048 正解の組み合わせに遭遇!!うおおおおおおおおおおおお!!!よかったね!!!!
2017/09/08 14:29:12.806 860000000回ループ完了...
2017/09/08 14:29:55.084 正解の組み合わせに遭遇!!うおおおおおおおおおおおお!!!よかったね!!!!
2017/09/08 14:31:15.750 870000000回ループ完了...
…(以後ダラダラログが続くので省略)…
2017/09/08 14:57:59.973 ★終了、全ループ回数=1000000000
2017/09/08 14:57:59.974 全問正解数=3、正解率=3.0E-9


こうなりました。

終結果は3回正解という、
10億回にしては「当たった」ほう(理論値ベースだと2回正解が関の山)だった。
正解率は3.0×10-9という値であり、
理論値は2.09×10-9なのでそれよりは高い値になっていることがその証拠。
10億回では2回当たる程度の計算なので、想定では2.0×10-9を狙っていたが、
3回当たったので正解率が想定より高い数値になった。
(このあたりが「精度の高い実験になっていない」ことを裏付けている)

記録を見た感じの考察では、
理論値的には「5億回に1回当たるレベル」と言っているが、
正確には分母は12!で「4億7千万」くらいなので、
その点において1回目の正解を迎えたのが
4億5千万~4億6千万回目の間だというのはほぼ理論値通りと言えるだろう。
このペースで「正解」してくれたら文句なかったのだが、
8億5千万~8億7千万回の間で立て続けに2回「正解」したのがイレギュラーだった
(運が悪いとも運がいいともいえる。言い得て妙である)

しかしいずれにしてもこれを通じて得られる結果がせいぜい2や3の正解数では
精度の高い実験・検証が出来ているとは到底思えない。
もっと巨大な試行回数(100億回程度は確実に必要と言える)を相手にしないと実験にすらならない。
高スペックなマシンを探し出しても再チャレンジしてもいいが、一旦この辺で区切りとしよう。




ところで、テナーのトリビュートクイズ参加企画は「6問以上正解者」という個別の条件がもうけられている。
全問正解だけを狙って実験するのもいいが、
それよりは直感的にもはるかに高確率であろう「6問以上正解」の確率を狙って実験したほうが、
もう少しマシな実験結果が得られるのではないかと予想できる。
次は「6問以上正解」の確率を狙ってみよう。