こんにちは。技術部平山です。
今回は、ゲームにおけるA/Bテスト について論じます。
「論じます」で始めたことで察しがつくかとも思いますが、今回はブログではありません。 媒体はブログですが、ブログの容量ではない代物になっております。3.5万字(115KB)超えです。 ゲームにおけるA/Bテストについて、実施の方法や問題点、 倫理的側面に至るまで幅広く書き連ねてみました。 読んで欲しいのはどちらかと言えば同僚なのですが、 そういう時にはまず社外に出してしまった方が良いものですので、 ブログにしてしまいます。
比較的同業の方が読むことを想定しているため、 図表を用いてわかりやすくすることはしておりません。
- これを書いた人間は何者か
- 技術的な問題の前に
- A/Bテストが可能である条件
- A/Bテストの手続きを概観する
- 振り分け
- データの収集
- データ蓄積と集計
- テストの評価
- アップデートに関する問題
- まとめ
- 付録
これを書いた人間は何者か
今回は通常の技術記事と異なり、「どんな仕事をした人間が書いているのか」 が重要ですので、最初に書いておきます。
平山は元々ゲームセンター用ゲーム、家庭用ゲーム機向けゲーム、スマホソーシャルゲームの開発に、 合計18年くらいエンジニアとして参加していた人間です。 その後2019年からは広告収入モデルのカジュアルゲーム(ハイパーカジュアルと呼ばれることもある) の開発に参加して現在に至ります。1年ほど前までは、オリジナル作者がある程度作った後の開発を受け継いで、 改善を行う仕事をしていました。関わった製品は、以下のような感じです。
「何をいじったらもっと遊ばれるようになるか?」を考え、改良を実装し、 この記事で扱うA/Bテストを行うことを繰り返していました。 改良はフレームレート改善などの技術的側面もありますが、大半はゲーム内容に関するもので、 例えばPark Masterでは一部のステージ(レベル)のデザインも行っています。 オリジナル作者から受け取る時期はまちまちで、ほぼほぼ完成した後に「さらなる改善」を行ったものもありますし、 プロトタイプに近い状態で受け継いだ物もあります。
その後は、自分で考えたゲームを作る仕事に移行し、全世界配布に至ったのは以下の2つです。
これらに関しても、プロトタイプができた後にやることは同じで、 「A/Bテストを利用した改善」となります。
したがって、「ゲームにおけるA/Bテスト」とは言いつつも、 具体的な経験としてあるのは「広告収入モデルのカジュアルゲーム」のみです。 コンシューマゲームやソーシャルゲームでいかにA/Bテストを行うか、 といった話題については、想像はできても、経験はありません。
また、これらの製品で直接課金が含まれるものはごくわずかですので、 直接課金についての知見もあまりありません。 ただし、若干の経験はありますので、多少はそういった内容も含まれてきます。
技術的な問題の前に
医学の世界に、臨床試験 というものがあります。
新しい薬や治療法が本当に効くのか、を確かめる手続きです。 薬の場合、ランダムに薬を飲むグループと薬を飲まないグループに患者を割り振って、 飲んだ群が「いい感じ」だと「科学的な手続きで」示されれば、 「この薬は効く」と言っていいことになります。 効くと証明されていない薬が広く使われていた迷信万歳な世界が終わったのは、 この臨床試験という手続きのおかげです。
「科学的な手続きで」というのが特に大事で、 雑に言えば「何かした時に結果が変わるかどうかは、しなかった時と比べないとわからない」、 そして、「新しく変えたこと以外は同じままにしておけ」ということです。 「私はこの方法で受験に受かりました」って宣伝する人がいたとして、 「その方法を使わなかったらどうなったのか」を試さないと説得力ないですよね? また、料理のレシピを改良する時に、加熱の時間と温度を一緒に変えると、 どっちの影響かわかんなくなっちゃうので、一個だけ変えようね、みたいなことです。
さて臨床試験の場合、薬を飲む人と飲まない人を用意する必要があります。 そしてさらに、患者が「自分がどっちなのか知らない状態」にしないといけません。 「薬を飲むか飲まないかが自分でもわからない」って意味わからないですよね? でも人は不思議なことに、「何か薬飲んでる」と思うだけで 謎の力を発揮して病気を治してしまうことがあり、 ぶっちゃけ薬の中身が単なる小麦粉でも治療効果が出てしまったりします。 なので、本物の薬と、見掛けソックリの偽物の薬(プラセボ)を用意して、 患者には区別つかないようにするわけです。
さらに、医師も、ある患者が本物の薬を飲む患者か、 偽物を飲む患者かを知らないようにしないといけません。 知っていると、患者と話す時に態度に出てしまったりして、 患者さんにバレてしまったりするからです。 コッソリしゃべっちゃう人もいるかもしれませんしね (二重盲検法)。
このように、「他の条件を同じにする」というのは相当大変なことで、 これが科学的な手続きというものなわけですが1、ちょっと「ん?」と思うことはありませんか。
まず「飲む人と飲まない人を作る。しかも本人は知らない」って言いましたけど、 そんなことしていいんですか?
薬を作った人は「これで病気が治って人が幸せになる」 と思って薬を作っているでしょうし、 動物実験などを経て、行けるという確信めいたものがあるからこそ、 人に飲ませる試験をするわけです。なのに、半分の人には薬を渡さないというのは、 アリなのでしょうか? 実際問題アリではないので、すでに薬が存在する病気であれば「現在わかっている一番いい薬」と 「それよりいいかもしれない新しい薬」でのテストになるでしょうし、 そういうテストに参加することの承諾は取ってからやるわけですが、 それでもスッキリしない感じは残ります。
客観的に、あるいは科学的に言えば、 作った薬が効くと思っているのは薬の開発者の思いこみかもしれず、 どんなに大層な理屈があったとしても、実験してみなければわかりません。 そして実験の結果、「その薬が効いた」と言うためには、 「その薬を与えなかった」人達との比較が必要です。 臨床試験は人類を進歩させてきた大変偉大な手続きだと思います。
でもそれはそれとして、スッキリはしないんですよね。
ゲームにおいても構図は全く同じ
ゲームにおけるA/Bテストも、題材的に深刻さでは比較になりませんが、 臨床試験とだいたい同じことをします。 ある改善がお客さんをより幸せにするかを知りたい時には、 改造が入ったゲームを配るお客さんと、 入っていないゲームを配るお客さんに振り分けます。 その上で、お客さんの遊び方に差が出れば、 「この仕様によって違いが出た」と言えます。
ゲーム開発者も所詮は人間であり、勝手な思い込みから自由ではいられません。 入れるべきではなかった仕様をノリで入れてしまって、 ゲームを台無しにすることは歴史上珍しいことではなかったでしょう。 しかし、「A/Bテストができる条件」が揃っている場合、 そうした悲劇の危険を著しく下げることができます。
しかし、薬の場合と同じ問題をはらんでいることは明らかです。
改善をした開発者は、「これ絶対おもろい」 と思って改善をすることが多いでしょうし、 その確信があるからこそ、客に出して遊んでもらう決断をするわけです。 にも関わらず、それよりも「つまらない」 と思っている旧バージョンをお客さんに出すことは、アリなのでしょうか?
「いや、この改善イマイチ自信がないんだよ」という場合もあるでしょうけど、 それはそれで問題です。「おまえ、自信がないもの客に出す気?」
そういうわけで、ゲームのA/Bテストというものにおいては、 手法云々の前に、客に物を出す人間の姿勢が問われることになります。
A/Bテストが可能である条件
さて臨床試験と同じ構図であるということから考えると、 ゲームでA/Bテストが可能な条件というものがある程度想像できるかと思います。
結論から言って、多くのゲームではA/Bテストができません。 多少はできるかもしれませんが、あまり大規模にはできないことが多いでしょう。
まず、アップデートが可能であることが必要です。 何かA/Bテストをして「こうした方が良い」とわかっても、 それを反映することができないなら意味がありません。 続編に反映する、ということはできなくもないとは思いますけど、 やりませんよね。 ですので、まあ普通に考えて、アップデートの手間が小さいゲーム開発でしか 適用できず、つまりほぼほぼスマホということになります。
もう一つは、お客さん同士がつながっている状況ではできることが極端に減る、 ということです。 例えばゲーム内にネットワークでコミュニケーションする機能が入っている場合、 お客さんによってあまりに挙動が違うと会話の中でそれが際立ってきてしまいます。 「左上のボタン押して」と言われた時に、言われたお客さんの画面では そのボタンが右下にあったら、どうでしょう? ゲーム内にコミュニケーション機能がなくても、twitterで会話していたら何かが噛み合わない、 といったことも起こり得ます。
そして、これは原理的な制約ではありませんが、 お客さんの数が多く、ゲームの仕様が小さいことは、 事実上必要な条件です。そうでないと、後述する手続きで支障を来します。
「広告収入モデルなカジュアルゲーム」なるものはこの条件と相性が良く、 事実上、ゲームが初めてA/Bテスト可能になった分野なのではないでしょうか。 それ以前から行われている事例があったのかどうかについては、私は知識がありません。
A/Bテストの手続きを概観する
ここからA/Bテストの実際の話に入っていきましょう。 より具体的な例で考えてみます。
今スマホのレースゲームを開発していて、 もう製品は配布しているとしましょう。 現在は、起動したら何も押さなくても車が走り始める仕様です。 しかし、さすがにこれでは唐突なので、起動したらタイトル画面を映し、 STARTボタンをタップすると始まる方がいいに違いない、と思いました。 そこで、A/Bテストをすることにします。
A/Bテストでまず必要なのは、「あるお客さんには旧挙動、別のあるお客さんには新挙動」 というように割り振る機能です。 起動する度に変わってもいいなら実装は簡単ですが、 さすがにそれではまずいでしょう。 「このお客さんはどっち?」というのを何らかの手段で覚えておいて、 再起動しても同じ挙動を保つべきです。 また、旧バージョンですでにゲームを始めていたお客さんが アップデートした時には、旧バージョンの挙動のままが良いでしょう。 そのあたりの実装とテストがそれなりに問題になります。 カジュアルゲームだからといってあまりテキトーな実装をしていると いずれ事故が起こります。
そして、割り振ったお客さんが遊んだ結果どうだったか、 がわかる必要があります。お客さんが開発者の目の前で遊んでくれていれば 「いきなり始まったせいで慌てちゃって、車をぶつけてる!」 といったようなことがすぐわかるのですが、 お客さんを開発者が直接見られるようなテストはそうそうできるものではありません。 お客さんの反応をtwitterで調べる、というのでは取れる情報には限りがありますよね。 そこで、アプリにデータ収集の機能を持たせて、どこかのサーバに送信させ、 開発者の手に届くようにする必要があります。 サーバは自分で用意してもいいでしょうし、 何らかのサービスを利用してもいいでしょう。 そういったインフラ面の整備が必要です。
あとは「何をデータとして送るか」を考える必要もあります。 そしてそれを決めるには「どちらのグループが良いのかを、何によって判断するか」 を考えねばなりません。 例えば、「プレイ時間が長い方が良い」と決めるならば、 プレイ時間をそれぞれのお客さんの端末から開発者に送ってくるように アプリに送信コードを仕込むことを検討します。 そしてもちろんのこと、「この端末はタイトル画面が出る方に割り振られたのか、 そうでないのか」も送ります。これがないと集計ができません。 「IDがXXXXのお客さんはタイトル画面が出るグループで、今までのプレイ時間は1300秒」 みたいな情報を、適切な頻度でサーバに送信することになります。
次に、送ったデータを蓄積し、分析するためのインフラの整備が必要です。 自前でサーバを用意してデータベースに入れてSQL (データ処理用のプログラミング言語っぽいもの)を書いて解析するのか、 そういうことをやってくれる既存サービスを利用するのか。選択肢は様々です。
次に評価です。データの集計を行って「何かしらの評価値」を算出し、 それが高い方が勝ち、というのが基本でしょう。 そのためには、評価値を何にするのか、そもそもいつ集計するのか、 人数はどれくらい必要なのか、評価値はどれくらい差がついたらいいのか、 といったことを決めなくてはいけません。 例えば、タイトル画面の有無について各5人づつ割り振って、 アップデート後3時間経った所でデータを集計し、 プレイ時間の平均値が高い方を「優れている」と判定する、というような決め方はどうでしょう? 5人で決めていいのか?3時間で決めていいのか? その5人の平均値が高かったら本当にそれでいいのか? といったことが検討すべき問題です。 このあたりについては、統計学の助けを借りた方が、 科学的手続きとしても、精神の健康のためにも良いと思います。
そして実は、「何をA/Bテストするか、そもそもするか」もかなり重要です。 今見てきたように、A/Bテストは結構な手間を要します。 あらゆる変更について全部A/Bテストしていたら開発が凄まじく遅くなってしまいます。 それこそ、ボタン一つにしても、色や場所、大きさなどで 試そうと思えば無限に試せます。それ、本当にテストするんですか? そんなにあなたは自分が良いと思うものに自信がないんですか?
科学的には「自信があろうがなかろうが人間は当てにならない」わけですが、 今我々はゲームを作っているわけです。ゲームですよ? 開発者の美学をお客さんにさらけ出して「どうだ!」ってやる行為ですよ(個人的な意見)。 それなのに、そんなことまでお客さんに聞いちゃうんですか?
このあたりは、最初に書いた倫理の問題とも関わってきますし、 そもそも「ゲームにおけるA/Bテストの限界」というものもあります。 「ある人に設定Aのゲームをやらせ、タイムスリップしてやる前に戻り、設定Bのゲームをやらせる」 ということをしない限り、「遊ぶ人が違っている」わけですから厳密には同じ条件になりません。 人数で個人差を薄めようとしても限度があります。 何万人のデータを取ったとしても、 ある時に出た結果が未来永劫通用するという保証は全くないのです。 臨床試験であれば「人体は人体だし、似たようなものだろう」 とある程度納得できますが、 ゲームの場合、人の趣味は千差万別で時代によっても変化します。 下手をしたら、「平日はこっちがいいが土日はあっちがいい」 「一ヶ月前はこっちだったが、今はあっち」みたいなことだってあるでしょうし、実際あるでしょう。 つまり、ゲームにおけるA/Bテストの限界をわきまえる必要があるということです。
まとめると、
- 振り分け
- データ収集
- 集計
- 評価
- A/Bテスト自体の妥当性とコスト
といった要素があるわけですね。 以後、これらの点について述べていきます。
振り分け
では振り分けの方法から具体的な話を始めましょう。 選択肢は大きく分ければ2つです。
- 振り分けをアプリ内部でやる
- 振り分けをアプリの外からもらう
前者は、初回起動時に乱数を振って、振り分けます。 例えば切り換える実装が2種類あるなら、 0か1が出る乱数で決めれば良いでしょう。
後者は、UnityやFirebase等のサービスを使って、 SDK経由で割り振りをやってもらいます。 FirebaseならRemote Config、 Unity社提供であればRemote Settings ですね。
アプリ内振り分けの場合
現在私が関わっている製品は全部アプリ内振り分けです。 過去にはFirebase Remote Configを使っていた時期もありますし、 さらに前にはUnityのRemote Settingsを使っていた時期もありますが、 今はどちらもやめてしまいました。
自前のいい所は、簡単なことです。 初回起動時に乱数を振って割り振り、 決まった割り振りをセーブデータに書いておきます。
二回目起動時以降はセーブデータから割り振りを取得して、 挙動を決めます。セーブデータが小さければ、IOは同期呼び出しで問題なく、 したがって待ち時間を考慮する必要もなく実装は簡単です。
初回起動かどうかの判定は、セーブデータの有無で判定すればいいでしょう。 お客さんが意図的にセーブデータを消した場合には、 初回でなくても初回と判定されますが、レアケースということで許容していいと思います。 こうすれば、androidであればアプリのストレージ削除で 割り振りをしなおすことができてテストもはかどります。
Firebase Remote Configを使う
Firebaseを使う利点は、セーブデータに割り振りを保存しないという選択肢があることです。 起動する度にSDKがサーバに問い合わせて割り振り情報をもらいます。 セーブデータに書き込むと、お客さんがセーブデータを改変する可能性が出てきますが、 そういう問題がなくなります。
そしてもっと大きい利点は、アプリをアップデートしなくても あるお客さんの割り振りを変えられることです。 Firebaseのwebサイトで、「全員旧挙動に戻す」「全員新挙動に移行」 といった操作を行えます。
つまり、A/Bテストが決着したら、良いとわかった挙動を全員に適用することが webの操作だけでできます。
平山がFirebase Remote Configをやめた理由
このように利点は結構あるのですが、結局使うのをやめてしまいました。
まず最初の利点である「セーブデータに書かなくて済む」ですが、 これは「ネットがつながっていて通信が十分速ければ」という条件がつきます。 起動時にネットが切れていればサーバからは割り振りが来ません。 ローカルにキャッシュデータがあってそこから読めることもあるでしょうが、 あらゆる場合に大丈夫なのかはよくわかりません。 結局、念のためにセーブデータに書きこんでおくことになります。
FirebaseはSDKの初期化に秒単位の時間を要しますが、 それが終わるまで割り振りが読めません。 起動直後に欲しい時には困ります。 先の例の「タイトル画面があるかどうか」はいい例です。 1秒だって待ちたくありません。 過去には、BGMの有無がわかるまで再生を遅延する処理が必要になったりもして、 なかなか厄介でした。 ですので、「起動直後から割り振りが必要な時はアプリ内割り振りにし、 それ以外はFirebaseから届き次第反映させる」という面倒な実装をしていたこともあります。 これもそれなりに面倒です。
また、そもそも初回起動時にネットがつながっていないと、 割り振りが行われません。 割り振りが来なかった時のデフォルト値を決めておくことになりますが、 起動時にネットがつながっていない人はそこそこいるので、 デフォルト値の方が数が多くなります。 そして、起動時にネットが切れている場合のかなりの部分は、 お客さんがネットを意図的に切っている場合と思われますが、 そういったお客さんは行動パターンが結構違うので、デフォルト値を取るグループのデータがそれによって歪んだりします。 やむをえず別に「この人は割り振りが届かなかった」というデータを 送信して集計を別にしたりしていました。
次の利点である「アプリをアップデートせずに割り振りを変えられる」も、 うれしいことばかりではありません。 「全世界の既存のお客さん全員に影響を与えられる」というのはリスクでもあるからです。 変な割り振り情報を後から全世界に送信できるとしたら、 それで全世界のゲームの挙動をバグらせる可能性があります。 Firebaseから変な値が来てもバグらないという確信を持てる実装をしないと いけないですし、そもそも変な値を送らないように相当な緊張感を持って 設定をする必要があります。被害を縮小するために「バージョン何々以降にだけ配る」 のような条件を詳細に設定して配布範囲を狭めたりもするわけですが、これがまた煩雑で、 神経を使います。
一方、アプリ側で割り振りを決めていれば、仮に何かバグがあっても 被害はそのビルドバージョンに限定されますから、 被害の最大規模は10倍も20倍も違ってきます。
アプリをアップデートしないと挙動を変えられないという欠点は、 アプリをアップデートすればいいのであまり問題になりません。 2年半くらい前の私は「アプリのアップデートは大変なコストがかかる行為なので、極力アップデートなしで できることを多く確保しておかねばならない」と思っていました。 それは大抵の場合は正しいでしょう。 しかし、ほとんど個人開発に近い開発規模で、 ダウンロード容量も大きくないアプリであるならば、 アップデートのコストも許容でき、 それによってお客さんに強いるダウンロード容量も許してもらえる範囲に収められるでしょう。 現在のgoogleのストアはできるだけ差分だけをダウンロードするように 工夫してくれています。
なお、UnityのRemote Settingsについても、だいたい同じことが言えます。
データの収集
次にデータを収集する所について考えます。 収集の問題は二つあり、
- 何を収集するか
- どこにどうやって送るか
に分けられます。まずは何を収集するかから考えてみましょう。
何を収集するか
何が欲しいかは、何で評価したいかによります。 しかし、どんなゲームであれとりあえず送っておいて損のないデータ、 というものはある程度あるでしょう。 製品ごとに何を収集するかを考えるのも手間です。
だいたい以下のようなものは、それほど製品固有ではなく、 共通ライブラリに機能を持っておいても良さそうに思えます。
- 総プレイ時間
- 起動回数
- レベル(=ステージ/面)制であればそのプレイ数、あるいはクリア数
- レベルの所要時間
- レベルごとの失敗回数
- 広告収入モデルであれば、広告発生の回数
- (あれば)課金額
- (あれば)広告報酬額
- フレームレート情報
- 不具合情報
- A/Bテストの振り分け情報
- お客さん固有のID
- アプリのバージョン
総プレイ時間や起動回数
総プレイ時間は、ゲームの面白さを計る上では、 たぶん一番汎用的な指標です。 面白いゲームは長くやるし、つまらないと短いでしょう。 二つのゲームのどちらが面白いかを比べる時に、 プレイ時間が長い方が面白い、と言うのは、完全に正確ではないにしても、 雑な評価としては便利です。納得感があります。 もちろん、素晴らしく濃厚な時間を10分過ごして満足してゲームをやめるケースもありますし、 個人的にはそういうゲームも好きなのですが、 おそらく稀なケースだと思います。商業的には不利ですしね。
プレイ時間の取り方にはいくつかやり方がありますが、 スマホの場合「ゲーム終了」の瞬間を認識できる保証もないので、 開始時間と終了時間の差を取るやり方は合いません。 毎フレーム前のフレームの時刻との差分をセーブデータに加算していくのが 単純な気はします。Unityであれば Time.deltaTimeですね。 ストレージへの保存は重い処理になりうるので、毎フレームは行わず、 動きがカクッとしても気にならないタイミングまで待つと良いでしょう。 レベルの終了/開始時、何かUIのボタンを押した時、などがあると思います。
また、スマホの場合他のアプリに移ってバックグラウンドになったりすると、 「次のフレームでいきなり1時間経ってた」 みたいなことが起こります。 UnityのTime.deltaTimeには最大値があって、サスペンドした時にはその最大値が入るので、 deltaTimeを加算していく分にはそういったケアがなくても、それなりな値にはなりますが、 unscaledDeltaTime を使うなどして実時間で加算していく場合は、 サスペンドと復帰の時刻を測っておいて差し引く必要があります。 UnityであればOnApplicationPauseを利用しましょう。
もう一つ、起動回数も結構重要な情報です。 プレイ時間は起動して放置していれば長くなるし、 ロード時間が長くて待たされるゲームでも長くなる可能性がありますが、 「起動する」という行為は意思が必要なので、 愛されている度合いを測る指標にはしやすいでしょう。 プレイ時間は少ないが毎日起動して少しづつ遊ばれている、 というようなこともわかります。
レベルごとの情報
レベル制のゲームであれば、あるお客さんが何面まで進めたのか、 その面に何秒かかったのか、何回失敗したのか、などは取れて損はない情報でしょう。 A/Bテストの評価には直接使わないとしても、 後で述べるように安全な判断をするためには余計な情報が多少必要です。
あるレベルを開始した時と終了した時にデータを送信し、 終了時には、何秒かかったか、何回失敗したか、などを一緒に送ると良いと思います。 「5面がやけに時間かかってる」 というような情報は、ゲームの改善に役立ちます。 もし面倒であれば、「レベル終了」 を送信するだけでも「平均何面遊んでくれるのか」がわかり有益です。
お金周り
課金アイテムがあるゲームであれば、買ったアイテムやその値段も 収集しておくといいでしょう。A/Bテストの評価をお金でやるならば、 「どちらのグループがよりお金を払っていただけるのか」 がわかる必要があり、絶対に必要な情報です。
同様に、広告収入モデルなのであれば、SDKから報酬額が取れる ケースがありますので(ApplovinのMax SDKは取れる)、 これも送ると良いでしょう。そうすれば、 グループごとに広告報酬がどうなっているかがわかります。
あとは、広告が発生した回数も使える情報です。 バナーなのか全画面広告なのかなど、種別もわかるようにしましょう。
フレームレートや不具合
「敵の数を5倍にしたら面白くなるかを試す」 というA/Bテストを行う場合を考えてみましょう。
自分で遊んでると明らかに面白いのに、 実際にお客さんに出してみるとプレイ時間が奮わない、 なんてことはあります。
そういう時に、ゲームが面白くないと判断されたのか、 それとも、お客さんの機械で快適に動かなかったのか、 は真っ先に確認すべきことです。
そういうリスクがあるので個人的には開発者はそこそこ性能のショボい マシンで普段自分のゲームを遊んでおくべきだと思っているのですが、 普段使いが高級品のiPhoneな方も多いでしょう(iPhoneが高級品だと気づいていない人も多い印象)。 ですので、実際にお客さんの所でどれくらい滑らかに動いているのかは 重要な情報なわけです。 上述したレベル関連の情報で、終了時に平均フレームレートも つけて収集しておくと良いと思います。
なお、これは個人的な感覚でしかありませんが、 フレームレート(FPS)の10パーセンタイル値(高い順に並べて、下から人数の1/10の所にいる人のfps)が、 20を切っていたら、そのゲームは少々動作が重すぎると感じます。 10人に1人は20fpsも出ていない、ということで、もしアクション的なゲームであれば 遊ぶのに支障を来します。 スピード感のあるゲームであれば30は欲しい所です。 fpsは大抵60以上にはならず、半分以上の人は60fps出てしまうので、平均を取ると40あるいは50に なりますが、パーセンタイルで見ると「ガクガクで遊べない人が10%もいる」 というようなことがはっきりとわかります。平均以外の指標で判断すべき数字は多くあります。
なお、フレームレートをレベルごとに取るのをおすすめしたのは、 「このレベルは重い」というようなことがわかるようにするためです。 「最高に面白いレベルを足したぞ!」と思っても、 お客さんの機械ではムチャクチャ重くてゲームにならないかもしれません。
あとは不具合ですね。Firebase Crashlytics などのツールを使っていれば null参照例外やら何やらがどれくらい起きているのかは収集できるのですが、 「A/Bテストのグループごとにバグり具合が違うか」がわかりません。 「何か仕様を足したら、そのためにバグった」というケースもあるでしょうから、 グループごとに不具合数を集計できた方が良いはずです。 なので、例外の類も送ってしまいましょう。 Unityであれば Application.logMessageReceivedを使えばいいでしょう。 ちょっと凝ればコールスタック情報を足してデバグに役立てることもできます。
お客さんIDと振り分け、バージョン
最後に、集計に必要なデータがあります。 お客さんのIDと、グループの振り分け情報です。
レベルをクリアする度にサーバに「クリアしました!」と送る、 という時、「誰が」がないと困りますよね。 「一人当たり何レベル遊ばれている」を計算するためにも、 人ごとのIDのようなものが必要です。
このためのIDは、Firebaseであれば自動で振られるので何もしなくていいですし、 Adjustを使用していればSDKからIDを取得できます。 もし自前でやるならば自分でIDを発行する必要があるでしょう。 サーバに発行してもらうのか、何らかの手段でアプリ内で発行するのかはわかりませんが、 いずれにせよ少々面倒でしょう。
そして、もちろん「A/Bテストのどのグループか」が必要です。 「誰がどのグループに属しているのか」がわからないと、 グループごとに集計できません。 Firebaseを使う場合は、UserPropertyという概念があって、 そこに振り分け情報を入れておけば全ての送信情報に 自動でそれが付加されます。 例えば「レベルをクリアした」という情報には、 「それはどのIDのお客さんで、どのグループに割り振られているのか」 が勝手につくわけです。Firebaseを使わない場合、 例えばUnityAnalyticsであればそのあたりのことを考える必要はないでしょうし、 自前でサーバのデータベースに溜める場合にはテーブルをよろしく設計する必要があります。
最後にアプリのバージョンです。 後述するように些細な改良はいちいちA/Bテストしていられないので、 「いろいろ入ってるこのバージョンが旧バージョンに全体として勝てばいい」 となることは頻繁にあります。 その際はバージョンごとに集計するので、バージョン情報が必要です。 ただしこれはFirebaseを使えば勝手に送ってくれます。
もし、同じバージョン番号でありながらビルドが違う、 というようなものを比較したい場合にはビルド番号も入れる必要があるでしょう。 Unityの関数では取れませんので、ビルド番号をテキストファイルに書いたものをビルドに入れて 読むと良いと思います。でも、そういう時はバージョン番号を変えた方がいいと思います。 私は今動かしているアプリがどのビルドか確認するために入れています。 インストールするビルドを間違えたままテストしていた、 という事故はそう珍しいものではありませんからね。 秘密のコマンドを入れるとビルド番号が表示される仕組みが入っています。
その他
「どちらのグループが良いか」とは関係ない情報も、 必要に応じて送ることがあります。 よくあるのは、「正しく動作していることを確認するための情報」です。
例えば「タイトル画面が出るか出ないか」でA/Bテストを行ってデータが出てきたとして、 「本当にこっちのグループではタイトル画面が出ているのか?」 と疑わしく思うことはあります。 もちろんアップデートを公開する前には自分でテストするわけですが、 テストすれば大丈夫というものではないのは、皆さんご存知のことでしょう。
そこで、「タイトル画面が出た時に何かデータを送信しておく」 というような処理を入れておくと、 「タイトル画面が出るグループの人でちゃんとタイトル画面が出ている数」 や「タイトル画面が出ないグループの人なのにタイトル画面が出ている数」 などを調べることができます。これによって「確かに動いているな」と安心することができるわけです。
いつもこのような追加データが必要なわけではありませんが、 有用なことがあります。
収集の手段
自前のサーバに送るか、収集用の既存サービスを使うか、で大きく分かれます。
自前でやるのは自前でできる人だけでしょうから、問題点はよく理解しているでしょう。 私はサーバは素人ですし、ここでは触れません。ただ、自前のサーバに送ることが許される製品かどうか、 という問題は考えた方がいいと思います。 アプリがデータを送信する度に自社で用意したサーバにアクセスする場合、 そのサーバの運用費がかかりますから。
私は過去Unity AnalyticsとFirebaseを使いましたが、 今はFirebaseに落ちついています。Firebaseだけでは大したことはできませんが、 後述するようにBigQueryというものを併用することでSQLを利用でき、 できることが広がります。導入はFirebaseの方が面倒です。
Firebaseでデータを送る
Firebase Analyticsでデータを送るのは簡単です。 初期化して、何か送りたくなる度にデータを送る関数を呼びます。 例えばレベルが始まったら送り、レベルが終わったらFPS情報や所要時間情報をつけて送り、 広告報酬がもらえたら送り、といった具合です。
ただ、面倒なのが初期化です。 初期化が終わるまで送れないので、例えば起動直後に起こった 何かを送りたい場合は、初期化が終わるまで待つ必要があります。 ラッパークラスにキューを持たせ、 外からは初期化完了を気にしなくていいようにすると良いでしょう。
また、振り分け情報はUserProperyとして送っておくと、 以後のイベント全てに振り分け情報がついて集計が簡単になります。 Firebase Remote Configを使っている場合など、 起動後すぐには振り分け情報が手に入らない場合もあります。 その意味でも送信したい情報はキューに溜めておいて、 振り分けが来てからキューを消化するようにすると良いと思います。 私も過去はそうしていましたが、Remote Configは使わなくなったので この問題はなくなりました。
それともう一つ、総プレイ時間のように「連続的に増えていく数字」は いつ送るかが問題になります。頻度が高いほど、アプリがいきなり落ちた時に 失われる情報が減りますが、あまり送信しすぎると負荷も高くなるし、 サーバに溜まる情報が無駄に増えます。 例えば10分に一回送る場合、9分やった所でアプリが落ちると、 その9分の情報は失われます。どれくらいを許容できるか、ということです。
私はレベル開始とレベル終了のイベントの追加パラメータとして 総プレイ時間を送っています。途中でやめてアプリを落とすとそこまでのプレイ時間は失われますが、 「そういうものだ」とわかっているので問題にはなりません。
データ蓄積と集計
データを集めて送信する所は終わったので、 今度はそれを溜めて集計する所の話です。
自前のサーバとデータベースで収集、蓄積をする場合は、 SQLを書いて集計できる環境の整備も自前でやるのでしょう。 うちでも多少は試しましたが、 実戦投入には至っていないので、ここでは扱いません。
Unity Analytics
まずUnity Analyticsを使った場合ですが、 すでに記憶が曖昧ですし、2年以上前のことですので 確たることは言えません。
とりあえず「何面まで進んだ人がこれくらいいる」 みたいなことはグラフで見られた気がしますし、 A/BテストもRemote Settingsと組み合わせて行うことができたような気がします。 Unityで作っているゲームの場合、 余計なものを自前で入れなくても導入できて楽でしょう。
ただ、「自分でSQLを書けない」というのは、私にとってはかなり不便です。 例えば「12/23に限って、メキシコの人で、このアイテムを取った上で8面で失敗した率が知りたい」 みたいな時は、たぶんSQLを書けないと辛いと思います。 また後述する「どちらが良いかの判定」の話では、 判定に必要な数字を計算する必要があり、これもSQLが書けないとたぶん得られません。
データをCSVなどの形でダウンロードすることはできそうな気もしますが、 経験上、溜まるデータは数百ギガバイト以上、ヒットすればテラバイト量になりますから、 ダウンロードして自前のデータベースに置いてからSQL、というのはちょっと大変な気がします。
Firebase単体
Firebaseに送ったデータは、Firebaseのwebで いろいろと見られます。 あるイベントを何回送ってたかとか、どこの国にお客さんが多いかとか、 そういうことはグラフィカルに見えます。
また、Firebase Remote Configを併用することで、 「今回のA/Bテストはこっちのグループが勝っている確率が85%です」 みたいな数字を出してくれます。
こちらも、やはりSQLが書けないのは痛いですし、 A/Bテストの判定にしても「こっちのグループが85%の確率で良い」と言われても、 それが何に基いた数字なのかよくわかりません。 実際、後述するような手続きで自前でやるのとは違う数字が出てきます。 おそらく ベイズ統計 か何かを使っているのでしょう(想像)。 なにせgoogleのやることですから先進的なのだとは思いますが、 「よくわからない」というのは若干マイナスです。
そして何より、「何で評価するか」を選ぶ自由度が足りないのが困ります。 こちらで指定した評価値での評価はたぶんできません。 例えばですが「全画面広告の回数と、バナー広告の回数の1/10を足したものが、 どっちのグループでいいのか知りたい」みたいな場合、 その願いはFirebase単体では無理であるように見えます。
もしかしたらうまく使いこなせば行けるのかもしれませんが、 「そもそもFirebase Remote Configを使いたくない」 となるとお手上げになります。 すでに述べたように、私はRemote Configはあんまり使いたくありません。 起動時の待ちが面倒くさいのと、 事故が起こった時の理論上の最大ダメージが大きいからです。
Firebase + BigQuery
FirebaseはBigQueryと連携させると、 溜まったデータに対してSQLを実行することができるようになります。 SQLが叩ければ、そこにあるデータを処理し放題です。
今ちょうど触っている製品のSQLプログラムを見てみたのですが、
- バージョンとA/Bテストのグループごとに、広告回数、報酬額、プレイ時間平均値、プレイ時間中央値、平均レベルクリア数、FPSの10パーセンタイル、例外の発生数、あたりを表示する
- お客さんの国の比率を出す
- インストール数の推移を日毎に出す
- 広告回数や報酬額のヒストグラムを出す
- 「xレベルまでやってくれた人がy%います」を並べて、人が次第にゲームをやめてしまう様子をグラフにする
- 広告報酬が種類や時間でどう変わるかを見る
- それぞれのレベルの所要時間やフレームレート、失敗回数を比べる
等々を用意してあり、好きな時にそれを実行してデータを見ることができます。 やろうと思えば、定期的にSQLを実行してその結果を Google SpreadSheetに貼りつけ、グラフも更新する、 というようなこともできます。
SQLはそう難しい言語ではないし、使う機能だけ覚えればいいですから、 学ぶのにかかる手間はそれほどでもないように感じます。 Unityでアプリを作っているようなエンジニアがSQLを習得できないということは 少々考えにくい気もします。
ただ、うちの会社はサーバ系エンジニアがたくさんいて、 社内にSQLがわかる人が何十人もいますから、 わからなくなったらすぐに聞けます。結構恵まれた環境です。 firebase+BigQueryという選択肢もサーバの人から教えてもらったものです。 サーバの人があまりいない環境であれば、 この選択をするには少々思い切りが必要になるかもしれません。 実際私はこの仕事を始めるまでSQLもBigQueryも知りませんでした。
...それに考えてみれば、うちのチームにおいても、気軽にSQLを書いている人間は あんまり多くない気もします。 もしかしたら、私が思っているよりもみんなSQLを書きたくないのかもしれません。 Unity AnalyticsのようにSQLを書かずに済む何かを使った方が 総合的に幸せになる可能性は否定できませんね。
なお、Firebaseを入れるのはそこそこ面倒くさいので、 データの収集は何か別のものを使って、 そこからBigQueryにデータを移す、というようなことができると、 もしかしたら良いのかもしれません。現状そういったことは試みてもいないので できるかどうかはわかりませんけれども。
テストの評価
さて、データが溜まり、集計するための道具の話も終わりました。 話の核心である評価の問題に行きましょう。
実はこの評価が一番厄介なのですが、 厄介であると気づかずにやっていてもそんなに問題ないかもしれない、 という点でも厄介なので、そのへんにも触れて行きます。
評価値
まず問題は、「何で評価するか」です。
我々はお客さんに愛される、面白いゲームを作るべく仕事をしているわけですが、 「何がどうなったら面白い」と言えるんでしょうね? 個人的には面白さは「感動の強さ×時間」 のような感じで数値化できそうな気はしていますが、 「感動の強さ」のようなものをどうやってアプリで測定するのか? と問われると全く思いつきません。 単純な総プレイ時間で見るのもそれほど悪くはないのでしょうが、 本当にそれで大丈夫かは不安があります。
それに、なんだかんだ言いつつも、我々は会社でゲームを作っていますので、 ゲーム作りを続けられるだけの利益を確保せねなばなりません。 ある改造をして、開発者的には「すごく面白くなった!」と思っても、 それで利益が減ってしまうのであれば、採用することはできません。 「すごい面白くなるけど儲け減るから!」ってプロデューサに言って、 「いいよ」って言ってくれるならいいんですけどね。
というわけで、少々の頭の悪さは感じますが、 「利益が大きい=お客さんが喜んでいる=面白いものを作った」 と考えることにしましょう。 完璧ではないにしてもそこそこは正しいはずです。 とりわけ、広告収益モデルの無料配布ゲームにおいては、 つまらなければすぐやめればいいわけで、 ダメな改造を入れればお客さんは離れるはずです。 何か邪悪な改造によってお金を儲けようとしても、 それでお客さんが嫌になればやめてしまいますから、 その意味では問題は少ないと言えます。 逆に、最初に5000円などのお金を頂く買い切り形式のゲームであったり、 アイテムを購入できるゲームであったりすると、 問題は複雑になってくるかもしれませんね。
というわけで、とりあえずは「頂けるお金が多いグループが勝ち」 としてA/Bテストを行う路線で考えてみます。
頂けるお金の量で評価する
ある仕様が入っているグループと、入っていないグループ、 どちらの収入が大きいか? というのはシンプルな問いですね。
課金を入れて直接お金をいただくゲームであれば、 その課金額をFirebaseで収集して、BigQueryで集計し、 一人当たりの課金額をグループごとに計算します。 それが良いグループが勝ちです。 広告収入モデルについても、広告報酬が取れるSDKであれば 同じことをやれば済みます。
しかし、「じゃあちょっとでも大きかったらそれでいいの?」 と言われれば違いますよね? それに、その判定は「いつ」すればいいのでしょうか? アップデートの配布を開始して1日後ですか?7日後ですか? 最終的に知りたいのは、 「十分時間が経った時にどっちがいいのか」 ですから、待てば待つほど正確なデータで判断できます。 しかし、待てば待つほど開発は遅れます。 今テストしていることと関係ない次の改善を 始めることはできるでしょうが、 結果待ちでソワソワする期間が長いのは嫌ですよね。 その改造がイケるのか、ダメなのかがわからないと、 次にやることも決まらないことは多いでしょう。 だからできるだけ早く決めてしまいたいわけです。
ではどうしたらいいでしょうか。
「統計学的に有意な差がある」
今、サイコロが2個あるとしましょう。 A,Bとします。
それぞれで1回づつ振った時、Aが6で、Bが1でした。 この2個のサイコロは6つの面が等確率で出ない不正なサイコロでしょうか? それとも正常なサイコロでしょうか?
こういう問いに対して統計学はそれなりな答えを出してくれます。 そこで、A/Bテストの問題を、これと同じ形の問いに変えれば、 統計学が使えることになります。やってみましょう。
「2つのグループから出てきた平均値は、Aグループは6、Bグループは1でした。 この2つのグループは全然違う性質でしょうか? それとも、同じ性質でしょうか?」
「グループが同じ性質」というのは、グループの中の人が皆同じということではなく、 例えば「10円の人が90%、100円の人が1%」というような、 お金を払ってくれる量の分布が同じだ、ということです。 2グループでその分布が同じなのであれば、同じ平均になりやすいでしょう。 しかし、たまたま平均がズレることもあります。 統計学は、「平均がこれくらいズレているなら同じ性質じゃないかもよ?」 ということを教えてくれます。
さて、サイコロの例に戻ります。 1回振って6が出る確率は、サイコロがまともであれば1/6です。 1が出る確率も、1/6です。 サイコロAで6が出て、サイコロBで1が出る確率は1/36ですが、 これ自体は意味がありません。サイコロAで3が出て、サイコロBで3が出る確率も1/36ですから。 そこで差に注目します。 差は6-1で5で、他に5になるケースはありませんから、確率は1/36です。 結構なレアかな?という感じですね。 例えば、4以上の差になる確率は、(6,1)(6,2)(5,1)で3通りありますから、1/12と3倍になります。 1/12くらいならたまたまかもしれないが、1/36はちょっと怪しい、 というような判断はできますよね? もちろん、1/36でもたまたまかもしれない、と言うことはできます。 「どれくらいの確率なら怪しい」かは、テストをする人が自分で決められますし、決めます。
A/Bテストにおいても、「グループAの平均とグループBの平均が5も離れる確率は何%」 ということが統計学を使うと計算できるので、 「いや、それは偶然じゃないだろ」あるいは「偶然かもな」というように判断します。 この「それは偶然じゃないだろ」を「統計学的に有意差がある」 と言ったりしますが、我々は別に論文を書くわけではなく学者さんと話すわけでもないので、 そういう言葉を使う必要はありません。我々は所詮ゲーム開発者ですから、 変に「ぼく統計学ができます」的な顔をする必要はありません(反省)。
ここのロジックは若干ややしいのでもう一度整理しましょう。
まず、2つのグループは同じ分布だと仮定します。 「イカサマでないサイコロと仮定する」と同じです。 そして、「仮にそうだとすると平均がこんなに違う確率はどれくらいか?」 というのを統計学という道具を使って計算します。 そして、それが高いか低いかを見て、偶然か偶然じゃないかを自分で判断します。 偶然じゃない、と判断したならば、それはつまり、 2つのグループは同じ分布ではなかったのだ、 という話になるわけです。サイコロはイカサマだった、ということですね。 少々まわりくどいですが、 これで「こっちのグループが良い」と言えることになります。
統計学で確率を計算する
統計学で確率を計算する方法はたくさんあります。 そしてそれは、元々のグループがどんな分布かによります。
一番運が良いのは、元々のグループが正規分布 という形の分布の時です。 平均値くらいの人がたくさんいて、 上に外れたり下に外れたりする人はどんどん減っていきます。
受験の記憶がある方は偏差値 というのを覚えていると思いますが、 あれはテストの点数が「だいたい正規分布」することを利用して、 自分が上から何%くらいの順位にいるかを粗く想像できる便利な数字です。 平均の人は偏差値50で、上に半分下に半分います。 偏差値60の人は上から15%、偏差値70の人は上から2%、偏差値80の人は上から0.1%くらいの所にいます。
もし、ゲームでお客さんから頂くお金が正規分布をしていれば、 よく知られた正規分布専用のやり方であるt検定 を使って確率を計算できます。
計算に必要な数字は、グループごとの人数と、平均値と、 分散です。 SQLでそれらをグループごとに出しておけばt検定できます。 t検定はweb上でもでき、自分で実装する必要はありません。 2群の平均の差のt検定 - 高精度計算サイト などで簡単に計算できます。 分散がだいたい同じか違うかで使う手法が異なりますので、 基本「等分散仮定」を「仮定できない(Welch)」にチェックを入れておくと良いでしょう (自分で実装するならwelchの方法にしましょう)。
「有意水準」というのが「何%以下なら偶然じゃないと考えるか」で、 よく5%が使われますが、これは自分で決めましょう。 実行すると、「平均が等しいという帰無仮説 棄却される」 みたいな出力が出てきます。なんかよくわからない言葉ですが、 「平均は同じだと仮定したがそうじゃない」つまり「平均値に差がある」という意味です。
「棄却されない」と言われた時
では、「棄却されない」って言われたらどうすればいいのでしょうか?
「この改造は意味がなかった」という話にして終わりにしてもいいですし、 改良してリベンジしたってかまいませんが、 もうちょっと待ってみてもいいでしょう。
基本、t検定は人数が多くなるほど、わずかな平均値の差でも「差がある」 と出やすくなります。理由はなんとなくわかりますよね? サイコロ1個だったら5の差が出る確率は1/36もありますけど、 サイコロ2個だと確率は一気に下がります。 2回振って両方6、2回振って両方1でないと差は5になりませんから、なんと1/1296です。 そういうわけで、数が増えるほど大きな差になる確率は減っていくわけです。
スマホゲームのアップデートであれば、待てば待つほど インストールするお客さんの数は増えるでしょうから、 それだけ小さな差でも検出できるようになります。 逆に言えば、「棄却されない」と言われた時には、 人数が少なすぎた、と解釈することもできます。
なお、もう一つの要素として、分散が大きいほど「棄却される」は出にくくなります。 分散はバラつき具合を意味していて、 例えばコインの裏に1、表に2と書いてあるものをサイコロのように使うよりも、 普通のサイコロの方が出る数字の範囲が大きいのでバラつきます。1から2と、1から6ですから。 そうすると分散も大きくなります。 バラつきが大きくなれば、平均値がブレる確率が上がるのは当然です。 なので、バラつきが大きなデータで検定すると、なかなか決着(棄却される、と言われることを私はよくこう呼ぶ)しにくくなります。 このことは後でまた出てきます。
正規分布?
さて、さっき大事なことをブッ飛ばして話を進めてしまいました。 「お客さんからもらえるお金が正規分布するなら、t検定が使える」 と言いましたよね? 本当に正規分布なんでしょうか?
これは製品によると思うので、「正規分布である」とも「正規分布でない」 とも言えないのですが、 私は正規分布になるのを見たことはありません。 たぶん正規分布になることはないと思います。
何がどうだったら正規分布なのか? というのはあんまり簡単ではないのですが、
- 左右だいたい対称
- 平均付近の人が一番多い
- 平均から離れるほど人が減る
というのを満たしていれば、正規分布に似てると まあ言っていい気がします。 しかし、私が見た範囲の広告収益モデルのゲーム製品においては、
- 左右はひどく非対称
- 平均あたりの人が多いわけではなく、0あたりの人が一番多い
- 平均からでなく、0から離れるほど人が減る
といった感じです。全然正規分布じゃありません。
なので、何かt検定とは別の、より近い分布向けの手法を使えばいいと思うわけですが、 困ったことに見つかりませんでした。
しかし、結論から言えば、たぶんt検定で行けます。
もっと実際の分布に似ている分布(指数分布や対数正規分布)を 仮定して、ランダムサンプリングで確率を計算するプログラムを書いてみたところ、 結局t検定が出すのと同じような数字になったからです。 数十回以上両方のツールで確率を計算してみて、大きく食い違ったことはありませんでした。 せっかく作ったので私自身は自作の確率計算プログラムを使っていますが、 たぶんt検定でOKです。保証はしませんが。
中央極限定理 によって「数が無限なら正規分布でなくてもいい」は保証されますが、 「実際どれくらいの数あれば大丈夫か」は実際やってみないとわかりません。 しかし、「t検定の頑健性」などで検索してみると2、 「どうも数が数千を超えてくると大丈夫っぽい」気がしてきます。 「広告収入モデルなカジュアルゲーム」のお客さんの数は万単位ですからね。 しかしもし数十人とか数百人だと分布の形の差が問題になる可能性はあります。 ご自分の用途に合わせて確認をした方が良いかと思います。
そのへん、真面目にやる必要あるの?
なんか急激に話が学問じみてきて、頭が痒くなってきました。 ゲーム作ってるのに、そんな話は本当に必要なの?と思いませんか?
実のところ、このあたりの話をどこまで厳密にやっても、 ゲームのA/Bテストである限り根本的な限界があります。 世の中の流行りが変わったらテスト結果はゴミであり、 出た結果は「その時はそうだった」でしかありません。 台風が来たとか、似たもっと面白いゲームが出たとか、 そういう条件でお客さんの行動がガラっと変わるかもしれません。
それに、「たまたまSQL実行して出てきた数字で、こっちのグループが上だったから、こっちの勝ち!」 という雑なやり方をしても、50%以上の確率で正しい決断ができます。 だったらそれでいいじゃないか、という考え方はありえます。
むしろ、「データが大切」と言いながら、 「何回もSQLを実行してたまたま自分が望んでいた結果が出た時に決断する」 ということを無意識にやってしまう方が問題です。これをやってしまえばt検定も役に立ちません。 「お客さん全員では今回の改造負けちゃったけど、人数の多いインドの人だけで見てみたら勝ってる。これは 本当は勝っているに違いない」みたいな判断を避けるのは人間には難しいのです。 「せっかく入れた改造を勝たせたい」と思わない人はいませんからね。 いいんです。面白いと思ったものをお客さんに出すのが本来の姿ですから。 科学的手続き(キリッ)とか言っても、時間ばかりかかって意味ないかもしれませんよ?
そのへんを考えると、この手のややこしい理屈をガン無視するのも否定されるべきではないと思います。 まあ、そもそもA/Bテストしなくていいんじゃないかという問題も生じてきますが...
分散の問題
さて、話を台無しにするような脱線をしましたが、 また細かい話に戻しましょう。t検定の話でしたね。
実は正規分布かどうかなんかよりも、もっと重大な問題があります。 それは、分散があまりにも大きい時です。 プレイ時間で言えば、すぐやめちゃう人から、ムチャクチャ遊んでくれる人まで 幅広くいれば、分散は大きくなります。
分散が大きいと、t検定はなかなか「差がある」と言ってくれません。 理由はすでに述べた通りです。
しかし、たぶん現実のお金のデータの分散は相当大きくなるのが普通です。 だって考えてください。「基本無料で、400円で広告が出なくなります」 という時、買う人はたぶん少数派ですから、 平均は0に近いです。分散は平均との差の二乗ですから、 400-0の二乗みたいなものが足されて分散は大きくなります。
広告収入モデルのカジュアルゲームでも状況は同じか、 下手をするともっと悪いかもしれません。普通全世界にリリースするので、 いろんな国のお客さんがいます。 そして、大抵の場合広告報酬額は、 経済力のある国ほど高いのです。 お客さんの国籍によって何十倍も違うこともあります。 このため、分散はムチャクチャ大きくなります。 平均が16で、分散が1000、みたいなケースは珍しくありません。 分散は平方根を取ると「標準偏差」という名前の数字になりまして、 正規分布の場合は、「平均+標準偏差」よりも低い人が 70%くらいになります。 今平均が3で、標準偏差が10となると、 「もし正規分布だったら」13以下の人が70%ということですが、 これ、平均値を考えるとムチャクチャ広いですよね? 平均の4倍とか高い数字より上に、まだ30%いるわけです。 そもそも、お金の額はマイナスにならないので、 左右対称ではありえません。絶対に正規分布じゃないな、というのがよくわかります。
私の印象としては、広告報酬額でt検定をして決着させるには、 ムチャクチャいい改善をするか、 十万人単位のお客さんが必要になります。 アプリがまだ育っておらず、そんなにたくさんのお客さんがいない時には、 「ちょっと良くなった」程度の改善だと全く決着がつかず、 よって、「t検定で決着がついたら採用していい」 というルールでやっていると永遠に採用できなくなります。
対策はいろいろあるので、それぞれで考えていただければと思いますが、 「差があるとみなす確率を甘くする」はあんまり筋がいいとは思えません。 「5%以下なら差があるとする」ところを「20%以下ならOK」 とすれば、もちろん決着はつきやすくなりますが、それ、 「1/5の確率で間違い」ですよ?さすがに甘すぎやしませんか? もういっそA/Bテストなんてやめたらいいんじゃないですかね?
まっとうな対策は、とりあえず2つくらいあると思います。 1つは、「個別にテストしない」。 もう1つは、「別のもっと分散が小さな数字でテストする」です。
個別にテストしない
微妙な差しか出ない改善についていちいちテストをしない、 というのは、一つの有力な手だと思います。A/Bテストって面倒くさいですし。
「ボタンのデザインを良くして、処理落ちしてたので直して、操作性がイマイチだったので良くして...」 のように複数の変更をテストなしでバンバンアプリに入れます。 入れるんですが、何のテストもしないでそれを採用するわけではありません。 変更がたくさん入ったビルドと、入ってない前のビルドで比較すればいいのです。
幸い、Androidのストアでは、2つのバージョンを同時に配布することができます。 現行バージョンの1.0を50%、新バージョンの1.1を50%、というような設定が可能です。 新バージョンがバグってないか不安なら、最初は新バージョンを5%だけ出す、 ということも可能です。
そして、A/Bテストのグループ分けを「バージョン」にします。 Firebaseであればバージョン番号は勝手に収集されますから、 A/Bテストの振り分けや送信を実装する必要もありません。
改造がいろいろ入っていれば、それだけ差は出やすくなります。 個別にどの改造が良かったのかはわかりませんが、 総合的に良くなったならOKでしょう。 そのビルドを100%公開して、旧バージョンの配布を停止します。
これを繰り返してアプリを育てていけばいいわけです。 そして育った後で、「以前入れたあの改造、本当に良かったのかな」 と不安に思うのであれば、それを個別にテストすれば良いでしょう。 後になってテストし直せるような項目は ゲームの根幹に関わらない些細な事でしょうから、 「こんなことをA/Bテストするのは人として許されるのだろうか」的な 葛藤はそれほどなくて済むと思います。 例えばUIのボタンの位置や色、大きさ、 あるいは演出の速さ、といったものがそういった例になるでしょう。
なお、これは2つのバージョンを同時に配布できないと成立しない手法です。 スマホ市場であればAndroidのみとなります。 iOSはアップデートすると旧バージョンが即座に止まって 新バージョンだけになってしまうのです。
そもそも、2バージョン並行で出せないというのは困ったことで、 新バージョンがバグった時のダメージが大きいので慎重にならざるを得ません。 iOSのアップデートはそもそも危険です。審査等々の手間もあります。 私の場合、全力でゲームを良くしている時期には、 1,2日に一度の頻度でアップデートをしますが、 iOSにはAndroidで大きな進展が見られた時にのみアップデートします。 月1回やるかな?くらいです。
また、これは余談ですが、 AndroidとiOSでは内容がほぼ同じであっても バージョン番号を変えた方が便利です。 BigQueryでバージョン別で集計した際に、 あるバージョンにiOSとAndroidが混ざっていると、 お客さんの性質が全然違うために分離せねばならず手間だからです。 iPhoneは高級品ですので、iOSのお客さんは経済力が全然違います。 そのため行動も広告報酬も全然違ってしまうのです。
それともう一つ。私は、あるバージョンを100%配布にする際には、 新バージョン全体が旧バージョン全体に対してt検定で勝利することを条件にしています(例外はあります)。 いじったつもりがない所をいじっていることもありますし、 A/Bテスト不能な変更、例えばSDKのアップデートなどもありますから、 何か事故って悪化するのを避けるためです。 t検定で決着しない程度の差のアップデートを100%にする価値は薄いですし、 「実は14日後にバグが発動する」みたいな、長期間見てないと発覚しない問題が 入っていたら嫌ですよね。なので、A/Bテストを繰り返して勝利する改善がいくつか溜まり、 バージョン全体として現行バージョンに明確に勝つまでは、 旧バージョンの配布を止めないようにしています。 最新のMannequin Downhillの場合、 現在までに60回ビルドを出していますが、 うち100%配布に至ったバージョンは1/4程度だと思います。
より分散が小さな数字
「お金の額を評価値に使うと分散が大きくて困る問題」の話でしたね。 もう一つの対処としては、別の評価値を使う方法があります。
お金がバラつく理由は、課金においては「お金を出す人と出さない人の差が激しい」、 広告収入モデルにおいては「国によって全然違う」といったものであり、 国による差が小さく、だいたい収入とリンクしてるであろう数字を使えば、分散は小さくなるはずです。
例えば、プレイ時間を使う手があります。 プレイ時間は0の人はそんなに多くないでしょう。 多少は遊んでくれるはずです。 また、普通の人の何十倍も遊んでくださるようなお客さんは、 たぶんそんなに多くはありません。 結果、そんなにバラつかないはずです。
しかし、プレイ時間とお金の額は本当にリンクしているのか? という疑問は生じますね。 また、異常値が入りやすい欠点もあります。 「ちょっと机の上に置いて1時間放置しちゃった」 みたいなことが起こると、簡単に「異常なほど高い値」が混入します。 時間で画面がオフにならない設定の人もいますよね? 人数が多ければ異常値の影響も薄まりますが、 今は人数を多くできないからこういう話をしているのです。 例えば、グループあたり100人で判断したい時、 どちらかに一人でも「1時間放置した人」が入れば、 そちらのグループの平均値は3600/100=36秒も大きくなってしまいます。 分散も大きくなるので多少はt検定で決着しにくくはなりますが、 それだけ平均が大きくなると誤って「こっちのグループがいいね!」 となってしまうこともありそうです。
あとは、レベルクリア数、も同じ目的で使えるかもしれません。 こちらもだいたいプレイ時間と相関するはずで、 そんなに無茶な数字は出ないでしょうし、 放置しても増えないでしょうから異常値も入りにくいです。 しかし、レベルの改造をするテストだったらどうでしょう? 「テンポが悪いので各レベルの所要時間を短くしてみた」 という改造の場合、新しい方は当然クリアレベル数は増えるでしょう。 それは本当に良いのでしょうか?プレイ時間は逆に減るかもしれませんよ?
広告収入モデルの場合、「全画面広告が出た回数」 というような選択肢もあります。 広告収入モデルですから、広告を見てもらえた回数が多い方が 利益が大きいだろうと仮定するのは、だいたい合ってそうです。 これもレベルクリア数と同様、放置しても異常値にならず、 だいたいプレイ時間と相関し、 レベルを改造したことの影響は受けにくそうです。 実際私は、これをベースにしたものをメインの評価値として使っています。
ただし、これも問題があります。 「広告出しまくったら評価値が高くなるけど、それで本当にいいのか?」 という問題、そして、 「広告が増えれば本当に利益は増えるのか?」という疑念です。 問題がややこしいので付録で後述しますが、この問題があるので、広告回数を評価値にするには それなりに注意が必要です。 しかし、注意さえすれば悪くない指標であるように思います。 また、各広告でいくらもらえたのかがSDKから取れない場合には、 これを評価値にする以外にやりようがない、ということもあるでしょう。 実は我々も過去はそうでした。2年前には報酬額は取れなかった気がします。
もう一つ、使えそうな値として、起動回数というのもあります。 何日も遊んでもらえる愛されるゲームであれば、 起動回数は当然増えます。 ゲームの出来を評価する指標として「k日後に何%の人がまだゲームを続けているか」 が使われることがありますが、それを評価値として使うと、 例えば「3日後の残存率が高くなるように改造していったら、 7日後の残存率はかえって下がった」というようなことも起こるでしょうし、 何日での残存率を使うか悩むことにもなります。 起動回数で評価すれば、こういう問題は発生せず、話はシンプルになります。
ただ、起動回数と面白さがどれだけ相関するかは物によります。 「つい徹夜しちゃったよ」みたいなことが増えても、起動回数は増えないし、 下手するとそれで満足してゲームを離れてしまい、 起動回数が減ることだってあるかもしれません。 この指標が適切かどうかは、収益モデルやゲームの性質によるでしょう。 そして、起動回数で差がつくには最低数日、 おそらくは週単位の時間が必要なため、 かなり長く遊ばれる、開発規模の大きなゲームが適するように思います。 我々がやっている広告収入モデルのカジュアルゲームだと、 そこまでのボリュームはないので、起動回数で明確な差がつくような 改善は滅多にできません。
t検定でOKならそれでいいのか?
さて、検定して「棄却される」(=たまたまじゃない)と出れば、それでオーケーと言えるのでしょうか。
答えは、「自分で決めろ」です。
そもそもt検定で出てくるのは確率であって、 「5%の確率だから、たまたまじゃない」と言われたとしても、 そういうことが20回あれば、うち1回はたまたまなわけです。5%=1/20ですから。 そしてすでに見たように、t検定は元々正規分布するもののためのもので、 おそらくゲームのA/Bテストで評価値として使う数字は正規分布から程遠いですから、 出てきた確率がどれくらい信頼できるかは場合によります。
さらに、検定の手続きがどうこう言う前に、元のデータの問題もあります。 A/Bテストの実装が入ったアップデートを出した時刻から データを収集し始め、例えば3日後にBigQueryでSQLを実行してt検定し、 「たまたまじゃない」と判定できたとしましょう。
ここで、このA/Bテストが「インストール後3日経つとプレゼントがもらえる仕様」 に関するものだったとしたらどうでしょう。
絶対ウソですよね、この結果。
もし本当に仕様通りに実装できているのであれば、たぶん結果は「たまたま」です。 そして私なら、逆に「仕様通りに実装できていないこと」を疑います。
こんな極端な例はそんなにないでしょうけれども、 ゲームの改造には「始めてすぐ差が現れるもの」から「しばらくやった人に対して差が出るもの」 までいろいろあります。わかりやすい例としては、 「序盤のレベルを簡単にした」というのがあります。 ゲーム開発者はつい不必要に難しく作ってしまいがちですので、 後からこういう直しを行うことはあります。 これによって、おそらくインストール後あまり時間が経っていない段階での成績は良くなります。 従来なら難しくて投げてしまったお客さんを繋ぎ留められるからです。 しかし、さらに時間が経った後にどうなるかは必ずしもわかりません。 元々難しいと思っていなかったお客さんは物足りなくなってやめてしまうかもしれません。 また、多少難しい要素であったとしても、それを理解することが ゲームの面白さを味わうことに必要であれば、 それを序盤で登場させて理解させることは長く遊んでもらうのに必要なことです。 安直に「難しいみたいだから削ろう」としてしまうと、 面白さも一緒に削れてしまいます。
つまり、ある改造がある時点で良さそうだったとしても、 さらに時間が経過した時にも良いかはわからないのです。 本来知りたいのは「時間が十分経過した時点での数字」ですが、 待てば待つほど開発スピードが遅くなりますから、そうそう待てません。 どれくらい待てばいいかは、ゲームの性質、お客さんの性質、 どんな改造か、等々に依存します。
この手の仕事を続けていれば、 「このゲーム、3日で出た結果がひっくり返ることはまずないな」 みたいな感覚は育ってくると思いますが、 感覚が当てにならないからt検定のようなことをしているわけで、 「どれくらい感覚を当てにするかも感覚次第」 という困った状況になります。 しかしそれが現実であり、向き合うしかありません。
サブ指標の活用
「判断の確からしさ」を補強するテクニックとしては、 「サブ指標」を使うことがあります。 例えば「全画面広告表示回数」を評価値として使っていても、 プレイ時間や起動回数も見ておくということです。 普通に考えて、ゲームが面白くなったのであれば、 プレイ時間や起動回数は増えるはずです。広告表示回数は 面白くなった結果として増えるわけですから、これらは一緒に上がります。 しかし、これらが逆の方向に動いていれば、何かおかしいか、 あるいはまだデータが足りないことが疑われます。
「何かおかしい」と思った時は 今回の改造の内容と照らし合わせて考えると良いでしょう。 例えば、レベルとレベルの間で広告が出るゲームにおいて、 レベルの所要時間を短くしてテンポを良くする改造をした場合、 実質的には広告が出る頻度は上がることが多いでしょう。 そうするとプレイ時間は下がる可能性が高くなります。 それをオーケーとするかは自分で決めるしかありません。
もう一つ考えておくべきは、 「本当は勝ってないものを勝っているとしてしまった時にどうなるか」 です。例えばUIボタンの位置に関するテストの場合、 後でテストをやり直して元に戻すのはそんなに大変ではありません。 しかし、「キャラクターカスタマイズ機能を足した」 というような改造の場合、「実は悪化してた」 とわかっても、もはや削ることは容易ではありません。 このように「判断が間違っていた時に取り返しがつくかどうか」 によっても判断の慎重さを変えることになるでしょう。
アップデートに関する問題
A/Bテストは、アプリのアップデートによって行われます。 まずテストが入ったアプリを配布し、 データを収集し、結果が出たら 結果を反映したアップデートをさらに行い、配布します。
このアップデートに関わる問題が結構あります。 ここではそれに触れておきましょう。
基本、テストの対象とするのは、 テストが入ったバージョンで初めて遊ぶお客さんだけにするのが無難です。 「1面の作りを改良した」みたいなテストだと、 これから1面を遊ぶお客さんにしか関係がありませんよね。 また、操作の変更のようなものだと、急に変わるのはマズイです。 それに、すでに遊んでいてゲームが進んでいるお客さんの データが混入すると、集計や評価が難しくなりがちです。 進行状態はさまざまですから、データがバラつきが 大きくなってしまいます。
しかしだからといって、「前のバージョンから遊んでいるお客さん」 がアップデートした時のことを考えなくていいわけではありません。 アップデートしたら動かなくなった、というのは困るので動作テストは必要です。 すでに遊んでいたお客さんはアップデートしても 挙動が変わらないのが良いわけですが、 動作テストはそこそこ面倒です。 そして、「アップデートしたのに何も変わってないじゃん」 と思うお客さんがいるようなアップデートは許されるのか? という倫理的な問題もあります。 お客さんに対しては「バグを直しました」 としか言えないケースは多々あることでしょう。
さらに、「A/Bテストした新仕様がダメだとわかった」時にどうするかも問題です。 仕様を完全に削除した場合、 「その仕様で遊んでいたお客さんが次のアップデートをすると、 急に挙動が変わる」ということが起こります。 ですから、できれば実装は入れっぱなしにしておいて 挙動が変わらないようにした方がいいわけですが、 実装を一切削除しないままA/Bテストを繰り返せば、 実装は膨れ上がってソースコードの管理が困難になります。
あるいはダウンロード容量が増え、 ダウンロードしてもらえるお客さんが減ることにもなりかねません。 一般に、アプリのダウンロード容量が大きいほど インストールの成功率は低く、 それは即座に、我々が広告を出してお客さんに製品を 認知してもらう効率が下がることを意味します。 この世界の言葉で言えば「CPI(Cost Per Install)が上がる」ということです。 多少ならともかく、あまり肥大化するのは歓迎できません。 お客さんにも不利益を与えます。
そういうわけで、どこかでダメだった仕様の実装を削除しなければなりませんが、 いつやるのかが問題です。 あまり引っぱると、実装の肥大化がひどくなり、 また、ありうるテストの組み合わせが増えて リリース前の動作テストのコストが増えます。 あるいは、コスト的に動作テストを全部できないとなれば、 お客さんの所で事故が起こる確率が上がります。 かといってさっさと削除してしまうと、 急に挙動が変わってしまうお客さんの数が増えます。
このあたりは、「どれくらい長く遊ばれる製品なのか」 によって匙加減が違ってくるでしょう。 ボリュームが小さく、数日遊んでもらえばオーケーな製品であれば、 例えば2週間経ったら実装を削ってもほとんど問題ないかもしれません。 しかし、強く愛してくれるお客さんがいるなら、もっと待つべきかもしれません。 それは製品によりますし、作り手の良心によっても変わってきます。
また、A/Bテストしたのがどのような改造かによっても違ってきます。 「明らかにデザインがかっこよくなったので、前から遊んでる人にも適用していい」 「レベルを後ろに増やす変更だから、むしろ前から遊んでいる人にも配布して楽しんでほしい」 「操作がそもそも根本的に違うから絶対に挙動が変わるのは許せない」 等々、さまざまなことがあるでしょう。
特にややこしい例として、「スクリーンショットを撮って ストレージに保存して、好きな時に眺められる機能を足した」 みたいなケースがあります。 この機能を足すテストをしてみたものの評判が悪いからやめよう、 となった場合、この機能が急に消えてしまうのはかなり問題です。 「パワーアップアイテムをゲーム内のお金で買えるようにした」 とかも同じで、お客さんが何かしら手に入れたもの、 ゲーム内で作成したもの、が消えるのは、ちょっと次元が違います。 しかし、えてしてこういった凝った改造は実装規模が大きく、 残っていると延々と大きなコストがかかり続けます。 動作テストを怠ると、 どこかのアプデでバグって動かなくなっていても気づかない、 といった事故も起こるでしょう。 こういったケースでは、 「とにかく勝てるまで改造を続ける」と決意して、 その機能を磨き上げていくことも過去にはありました。 最初に機能を足した時には出来が悪くてA/Bテストで負けることは 結構あるわけですが、そこであきらめずに改善を続けて、 最終的に勝てるのであれば問題は解消します。 Park Masterにはレベル選択機能があり、 クリアした時のスクリーンショットが並びますが、 この機能はそういった例です。最初は出来が悪く勝てませんでしたが、 後から削るのは問題がありすぎました。 そういった場合には、A/Bテストの判定を甘くして、 「互角以上ならヨシ」としてしまうことも選択肢に入ってくるでしょう。
セーブデータの仕様を変更、拡張するような変更も危険です。 ダメだったとわかって戻した時に何が起こるかは、 相当慎重に確認しなければなりません。 お客さんの所に「仕様が変更されたセーブデータ」が すでにある状態で、実装が巻き戻るからです。 ぶっちゃけ、「セーブデータをいじる改造は実装を消せない」 と覚悟してしまった方がいいと、私は思います。
まとめ
ひどい量になってしまいました。そして結論はありません。 今までA/Bテストをしてきて、これからもしていくであろう私が、 「A/Bテストについて何か書こう」と思った時に 脳内からダラダラと出てきたことを書き連ねたものです。
とにかく大事なことは、「ゲームを良くするためにやっている」 という根本を忘れないことです。A/Bテストは、その工程における 不確実性を減らすための道具にすぎません。 私は、「A/Bテストは自分が入れたい仕様を入れる許可を取る手続き」 だと思っています。 普通ならプロデューサなりリーダーなりに許可を取って自分がやりたいことを ゲームに入れますが、うちのようなカジュアルゲーム開発だと個人開発です。 許可を取る対象はお客さんとし、その手続きがA/Bテスト、ということになります。 これを「お客さんに何が好きかを尋ねるアンケート」 のように考えると、「そもそも俺は何を作りたいんだ」 みたいな迷路に迷いこみます。
何回か書きましたが、ゲームのA/Bテストは科学の香りこそするものの、 科学ではありません。来週同じテストをしたらひっくり返るかもしれず、 普遍的な知見など得られないのです。 自分の独り善がりさを認識することは多く学びは大きいのですが、 「作りたい、面白いと思うものをお客さんに出す」 という建前はある程度大事にしたいものです。私が古いだけかもしれませんが。
なお、本題から逸れすぎたり、詳細すぎたりするので 本文から外した話題について、後ろに付録をつけておきます。 付録を含めると、まだ長くなります。本当にすみません。
付録
Firebase中国問題
Firebaseは導入が結構面倒なので 入れずに済むなら入れたくないのですが、 もっと大きな問題があります。 中国で使えない恐れがあることです。 googleのサービスですから、中国のネットワーク規制に ひっかかってアクセスできない可能性があります。 現在使えるのか使えないのか、何が使えるのかは不明です。 しかし、動作が不確実なものを動かすのはリスクなので、 動作国が中国であると認識された時は(現在はMaxSDKから国情報を取っている)、 Firebaseの初期化を行わず、ダミー処理を通すようにしています。
つまり、世界でも有数の市場である中国のお客さんのデータは取れません。 これは惜しいことです。UnityAnalyticsなら大丈夫なのか、 みたいなことはわかりませんが、他に方法があるなら検討して良いと思います。 Adjustでデータを収集して蓄積と集計を自前化しようと 試みたことがあったのも、最大の理由はここにあります。
ちなみに、Firebaseを入れると何が面倒かというと、 FirebaseのSDKには結構バグがあって結構な頻度でトラブる印象があるのと、 DL容量がかなり大きくなるのと、社内手続きがそこそこあるのと、 Unityにインポートするファイルの中に100MB超えのものがあって、 これを削除しないとgitHubにpush失敗して面倒くさいことになることです。
広告収入モデルにおける評価値
直接課金しているゲームであれば、 課金額が大きいグループの方が利益が大きいのは まあ確かでしょう。私は直接課金主体の製品をやったことがないので推測でしかありませんが。
しかし、広告収入の場合は話が怪しくなってきます。
A/Bテストは、各グループが独立で互いに影響しないことが重要です。 グループAに加えた変更が、グループBで出てくる数字に影響するようなことがもしありうると、 A/Bテストの前提条件が成り立たなくなります。
そのために「お客さん同士がやりとりしない」という前提条件を置いたわけですが、 広告収入の場合、それが満たされてもグループをまたいだ影響がある可能性があります。
広告報酬がいくらになるかは我々とは関係ない場所で決まっているので、 仕組みは推測するしかないのですが、 根本的な話として、我々のゲームに出た広告が成果を上げたから、我々は広告報酬をもらえるわけですよね? 「成果」というのは、お客さんが広告をクリックした上で、 何かアプリをインストールしたり、何かを買ったり、といったものでしょう。 昔のテレビ広告は成果を測定できなかったでしょうが、 今は「この広告を見た人がインストールしました」みたいな情報が 広告媒体業者のサーバに蓄積されますので、成果によって報酬額が決まっているであろうと 推測するのは自然です。 うちのゲームに広告を出した人が払う広告費、つまりうちの収入になる原資には、 「うちのゲームで過去出た広告を経由してどれくらいの成果があったか」 が強く影響していると想像できます。
この話で言えば、「ゲームを改良した結果、広告経由で何かをインストールしたり物を買ったりする数が増えた」 となれば、報酬額は増えるでしょう。問題は広告の表示回数ではなく、成果の額です。 広告表示回数が増えても、誰もそれをクリックしなければ、 広告一回あたりの報酬額が後から減らされて利益自体は元と変わらない、ということになるかもしれません。 現在は広告が表示された瞬間に報酬が得られているようですが、 ここでもらう報酬の額は、それまでの成果から計算されたものであろうと推測されます。 無駄な広告、つまりクリックされない広告が増えれば、後から一回あたりの広告報酬が減らされて 辻褄を合わせるのでしょう。
つまり、仮に報酬額を評価値としてA/Bテストを繰り返し、 改良を重ねたとしても、それで実際の利益が増える保証はありません。 そもそも「ゲームが面白くなると報酬額が増える」は保証されていない、ということでもあります。
実際上の問題はさらに深刻です。すでに述べた通り、報酬額は通常評価値としては使えません。 分散が大きいために相当な人数が必要だからです。 広告回数ベースの評価値でやることになるのですが、 「広告回数が増えれば報酬が増える」という保証はありません。評価値として妥当であるという根拠は ますます弱くなっていきます。
実際、お客さんのデータを追った所、 「1回目よりも2回目が、2回目よりも3回目の広告報酬が安い。その後もどんどん安くなる」 という現象が見られました。どのような場合でもそうかは今後の調査が必要ですが、 「この人クリックしてくれないから額下げよう」 という判断が個人単位で行われていれば、そういうこともあるでしょう。 そうなると、広告回数の平均値を評価値として使う方法はかなりマズいことになります。
例として、広告回数が2回の人が2人いるグループと、1回の人と3回の人がいるグループで 考えてみましょう。仮に「3回目の広告報酬はすごく安い」とすると、 どちらも平均は2回ですが、2回の人が2人いるグループの方が報酬額は高いはずです。 結果、グループによって広告一回あたりの平均報酬額は異なってしまい、 「広告回数の平均値が高い方が良い」という評価はそもそも破綻します。
こう考えていくと、「そもそも何を評価値としてA/Bテストをすべきか」 は相当に難しい問題であることになりますし、 「そもそも我々は何のためにA/Bテストをしているのか」 という問いにまで発展します。
非常に極端な仮定の話ですが、 「ムチャクチャたくさん広告を出した結果、プレイ時間は減り、起動回数も減ったが、 うんざりして広告から他のアプリをインストールして遊び始める人が続出したためか、 広告報酬が高くなって利益が増えた」 などということだって論理的にはありえないことではありません。
何を評価値としてA/Bテストを行うかは、未だにホットな問題であると言えます。 いい方法を知っている方は是非教えてください(twitter)。
Adjust+自前サーバでデータを収集するという試み
Firebaseは導入がそこそこ面倒くさいですし、 すでに触れたように中国でデータが取れません。
そこで、すでに述べた「Adjustでeventを送る機能」 に付随した「その時についでに自社サーバにもデータを送れる機能」 を利用して、自前でデータを収集することを考えました。
サーバの人に頼んで少しだけ試してみたのですが、現状不安な点が2つあります。
一つはお金です。広告収入モデルのカジュアルゲームを全世界に出すと、 大半は新興国でのダウンロードです。そういう国では一人あたりの利益が0.1円くらいでも おかしくなく、サーバ費は一人あたり 0.01円でも高いな、という感じになります。 データベース用に機械を借りる費用みたいなものも込みで、 さらにその1/10くらいであってほしいものです。 果たして自前で用意したサーバはそれくらいのコストで運用できるものなのでしょうか。
そしてサーバの能力も問題です。収集されるデータは平気で一日20GBとかを超えてくるので、 それに耐えるストレージ、データベースも必要ですし、 集計する時にはSQLで待たされる時間も問題になります。 BigQueryなら1分待たされることは稀ですが、 自前で用意したものは、数百GBを読み込む時にその性能を出せるものでしょうか。 Redashという「ブラウザでSQLを書いてなんかできる環境」を用意してもらいましたが、 まだ育っていない製品の集計でも分単位の待ち時間になっていたので、 もしあの速度のままであれば実用は辛そうな雰囲気です。
Adjustから自社サーバ、自社サーバからBigQuery、という経路で、 中間のFirebaseだけを削除する方法も取れるかもしれませんが、まだ検討もしていません。
BigQueryのお値段
外のサービスに頼ると気になるのがお金です。 私はお金のことはあまり気にしていないのでわかりませんが、 弊社には1億ダウンロード級のアプリが2つありまして、 それに使っていて問題になっていないのだから、たぶん大丈夫なのでは?と思います。 「月3万円以上使ったらメールが飛んでくる」ような設定にしていたと思いますが、 飛んできたことはない気がします。
ただし、Park Masterの時には毎回元データ(Firebaseから来たままのデータ) を処理するSQLを実行していたら結構危ないことになったので、 毎日自動実行機能で必要なデータだけを抽出したテーブルを別に作っていました。 BigQueryはSQLを実行する時に「このSQLはxxGBのデータを読みます」 というような表示が出るのですが、 ヒットしたゲームで何の工夫もしないと、あっというまに500GBとか1TBとかの 桁になっていきます。それくらいの桁になるとお金も結構かかってくるようです。
なお、データが溜まっている量に応じてかかるお金もありますので、 一定時間経ったテーブルは消えるように設定しておくと良いでしょう。 我々がやっている広告収入モデルのカジュアルゲームであれば、 90日分も取ってあればそれ以上昔のデータを見る必要はほとんどないと思います。 長期的な推移を残したい場合は、 必要なデータだけを抽出するSQLを自動で毎日走らせて、 小さなテーブルに追記していく、というような手法があると思います。 ある日のインストール数、ある日に始めたお客さんが最終的に何面遊んでくれたか、 といったことの時間推移が長期で残っていると、後々良いかもしれません。
何年も経ってから、アプリリリース時からの推移をグラフにして見てみると、 なかなか感動があるかもしれませんね。まあ我々もまだ「何年も経つ」 という経験はできていないし、正直毎日それどころではないのですが。
AIにA/Bテストの運用を任せるという未来
過去、GameTuneというものを試したことがあります。 GameTuneは当時ベータ公開で、今は全然違っているかもしれないので、 それ自体の話はしません。 ただ、その時に思ったことがあります。A/Bテストの未来の形です。
A/Bテストによって、十分な人数がいれば、 「こっちの方がいい」と言えるわけですが、 負けた方の仕様が好きなお客さんはきっといますよね? にも関わらず、勝った方の仕様を全体に適用するのは、 なんというか、雑です。 もっときめ細かく仕様を選べるようにした方がいいかもしれません。 昔のファイナルファンタジーでUIの色を変える機能があったりしましたけど、 確かにUIだったらそういう自由度があったっていいでしょう?
しかし、そういう「個人によって好み違いそうな要素」を全部カスタマイズ可能にすると、 それはそれで厄介です。まず仕様が大きくなります。実装もテストもコストが増します。 そして、「そんな選択肢があること自体が邪魔」なお客さんにとってはゲームを邪魔する要素になります。 「振動のon/offの切り替えボタンを画面に置いたらA/Bテストで負けた」という経験もありますから、 私は画面に要素を足すことには慎重です。 選べる、ということは良いこととは限らないのです。 Mannequin Downhillにおいても、1面ではリトライボタンや「Level 5」のような表示を消して、 全くUI表示がない状態にしています。初めての方にはその方が集中できるだろうと私は思いますし、 それがA/Bテストで勝ったからそうなっているわけです。
そういえば最近は普通にある「難易度選択」も、「Easyを選ぶのは負けた気がするから選びたくない。 でもNormalはムズすぎてやめちゃった」というお客さん絶対いますよね。 でももし、そういうお客さんを何らかの手段で識別して、しれっとEasy難易度にできたら、 もしかしたらハッピーになるかもしれません。
そして、AIはそれを可能にする可能性があります。 いろんな属性データから、「このお客さんはEasyがいいぞ」 とAIが言ってくれて、実装内で難易度を切り換える。 それを単にアプリ内で実装するだけなら昔でもできましたが、 行動データの収集と評価値での評価を合わせてやることで、 「この属性のお客さんにEasyを出す方が良い確率は95%」 みたいなことをAIが勝手に判定し、実際Easyを出してみてどうだったかを見て、 それを他のお客さんの次の判断に活かしていく。そういうことができる 時代になってきたということです。
しかも、その仕掛けを動かしっぱなしにすれば、時代とともにお客さんの趣味が変わってきても 自動で対応できます。ある時点のテスト結果で「えいや」と決めて、そのまま放置しておいたら お客さんの趣味が変わって合わなくなる、ということが普通のA/Bテストでは起こりますが、 この問題が解決するはずです。
ただし、いつ実用になるかは私には何とも言えません。 お値段の問題もあります。それによって増える利益より安いお値段で使えないといけません。 また、結局は「数字の調整」に使うのが一番良いと思うので、 連続値の調整をやってくれるような機能がないと使いにくいでしょう。 「Easy,Normal,Hardから選んでくれます」くらいだと、 Easy,Normal,Hardの3バージョンを実装としては用意しないといけないのでそこそこ大変です。 どちらか言えば、「敵のHPを94%に落とす」みたいな調整を期待したいのですが、 「用意した選択肢から選ぶ」というインターフェイスだと 50%から1%刻みで100段階用意してAIに選んでもらう、みたいなことになります。 しかし、選択肢が多いと判断がうまく動かないでしょう。 A/Bテストのグループの数を増やすほど、グループあたりの人数が減るので、 t検定で決着がつきにくくなります。それと同じことが起こると思います。
ここ数年のAIの進化はすごいので、 もしかしたら今は実用になっているのかもしれませんね。 どなたか知見をお持ちの方は教えてください。
iOSのお客さんとAndroidのお客さん
A/Bテストは基本Androidでやる、と上に書きました。 しかし、iOSのお客さんとAndroidのお客さんに大きな差があるのであれば、 Androidで出た結果をiOSにも適用するのは雑すぎます。
とはいえ、これを言い始めると、アメリカの人に限った結果と、 メキシコの人に限った結果は違うでしょうし、 端末の性能がいいお客さんと、端末の性能が良くないお客さんでも結果が違うでしょう。
細かな条件の差によって挙動を微妙にカスタマイズすることで お客さんの満足度を上げることはできるかもしれませんが、 問題は「手間がかかること」です。 分析の手間もかかるし、実装自体手間だし、テストの手間もかかります。 どれくらいなら許容できるか?という話になるわけですね。
とりあえず、iOSとAndroidで挙動を変えるのは、 まあ2種類ですし、どちらのアップデートをする時にもそれぞれの実機で テストしますから、余計な手間はそんなに増えません。 テストによってAndroidとiOSで結果が食い違うようであれば、 検討してみてもいいかもしれません。
以下には、iOSとAndroidで特に違いが出る点を書いておきます
フレームレート
Androidで10パーセンタイル値が20fpsくらいの製品でも、 iOSでの10パーセンタイル値は50fpsは超えるのが普通です。 性能が全然違います。Androidでも高い機械は速いんですが、 遅い機械の比率も高いです。
ですので、Androidの低性能端末用に描画解像度を落としていると、 おそらくiOSでは落としすぎになります。 そこで、私が関わった製品は大抵自動でフレームレートを測定して 解像度や影の綺麗さ、アンチエイリアスなどを調整していますが、 初回起動時にはしばらくボケた感じにはなり、完璧ではありません。
なお、昔は先にiOSでプロトタイプを作っていて、 iOSなら平気で動くゲームでもAndroidに持っていくと フレームレートが辛いことになる例がありました。 GPU側であれば解像度で吸収もできるのですが、CPU側だと打つ手がありません。 しかし今はAndroidで先に開発するので、こういうことは起こりにくくなっています。 逆に、iOSならもっと豪華にできる、という例は多いでしょう。
振動機能(バイブレーション)
新し目のiPhoneは「コツン」って感じの感触が出る高度な振動機能を積んでいます。 ニンテンドースイッチについてる奴と似た感じです。
しかしAndroidの機械はバラバラで、安い機械は「ブブブブブ」という 昔ながらの奴しかありません。
なので、ゲームの臨場感を高めようと振動を入れても、 Androidではほとんど効果が出ない印象があります。 たぶん、振動がある方がいい機械もたくさんあるんですが、 ない方がいい機械がそれを相殺するくらいたくさんあるので、 テストとしては差が出ないのでしょう。 それにそもそも、「どの機種でいい感じになるように調整するのか」 という問題もあって、それほど完成度を上げられません。
ですので、振動のテストはiOSでもやっておいて、 iOSだけに絞って結果を見てみると良いかもしれません。 iOSは大半の機械でいい振動が動くので、効くならはっきり出るはずです。 それで勝つなら、iOSだけ振動onというのも良いかと思います。
Firebase→BigQueryのデータ転送について注意
アプリが育ってくると、FirebaseからBigQueryへのデータ転送でトラブルが起こることがあるので、 ちょっとだけ書いておきます。
エクスポート設定で「ストリーミング」を有効にしておくと、 データがどんどんBigQueryに来るので、自分の端末でテストして数分後には そのデータが見られたりして便利です。 このデータは名前に"intraday"が入ったテンポラリのテーブルに格納されます。
そして「毎日」という設定も有効にしておくと、 このテンポラリのテーブルが、もう変化しない固定したテーブルに移されます。 この移すのが問題でして、容量制限にひっかかるとデータが消えてしまうのです。 中途半端にデータが入ったテーブルができて、元のテンポラリのテーブルは消えます。
お金を払えばこの制限を緩和できるようなのですが、うちは払っていません。 そのかわりに、エクスポート設定の「毎日」をオフにして、 テンポラリのテーブルのままにしています。
テンポラリのテーブルは重複があったり、抜けがあったり、 時系列順でなかったりしますので、SQLでdistinctをつけて重複を削除し、 また、多少抜けているかもしれないという覚悟でいれば問題はない印象です。
- 実際には無作為化二重盲検試験でない臨床試験もあるし、無作為化二重盲検試験でなければ科学的ではない、というわけでもない。↩
- Annual Review of Public Health Vol. 23:151-169 とか。↩