pokenote
SScott

「あなたの配分、丸見えです!」ダメージから相手の配分を予想する方法 [ベイズ推定]

こんばんは (断定)


突然ですが、攻撃を受けたり攻撃されたりしたとき、ダメージの数値から相手の配分を考えることってありますよね。

その際、ダメ計を行ってざっくり予想するという方法でも十分実用的だと思いますが、

この「ダメージから相手の配分を予想する行為」をしっかりやると、どこまで特定できるようになるか気になりませんか?

私は気になったので、いろいろと考えてみました。

考えた結果、ダメージの乱数から思いがけない情報を取り出せることに気づいたので、ここで紹介します。



[注意]

この記事は下記の動画の内容に基づいて制作しました。(動画投稿者は私です)

1. 逆ダメ計とは

HPの減り方から相手の配分を予想することを、ダメージ計算の反対なのでこの記事では逆ダメ計と呼ぶことにします。逆ダメ計を極めると何ができるか、先に一つ例を示します。

敵のアーマーガアのHPが、試合の中で下記の画像のように変化したとします。

画像のように変化して、最終的に12%になったとして、このアーマーガアは次のステルスロックで落ちるでしょうか?

答えは「落ちる」です。

補足すると、ステルスロックはアーマーガアに対して 1/8 つまり 12.5%入ります。

HPの%表示は切り捨てのため、12%という表示の中身は12.0%〜12.99...%までありえます。

そのため、この数字だけからは落ちるかどうか判断できないように思えます。

しかし、それまでのHP表示の変化を記録していれば、この個体は「確実に落ちる」と判定できます。さらに、同じ12%の表示でも、「確実に耐える」と判定できる個体や、61%の確率で落ちるとしかわからない個体も存在します。

同じHPの%表示でも、その個体が辿ってきたHPの推移が異なれば、得られる情報も違うということです。

この仕組みを、相手の火力を推定する場合と相手の耐久を推定する場合に分けて解説し、

最後に自動で行うツールを紹介します。

2. 配分の候補を絞っていく方法

ここでは、ナイーブな方法からスタートしてみます。

自分のカバルドン(H32 B2 D32 B補正あり)が、相手のガブリアスのじしんで69ダメージを受けたとします。

自分のHPは実数値で見えるので、受けたダメージの量は正確に計測できます。

ここで、ダメージ計算を総当たりで行うと「カバルドンに69ダメージを与えられるガブリアスの配分」の一覧が作れます。

これは攻撃実数値と火力アイテムの有無を軸に取った表です。

バツマークがついているマスが、脱落した候補になります。

さらに次のじしんで76ダメージを受けたら、69と76の両方を出せる配分だけを残します。
3発目以降も同様に照合していけば、消去法で候補は絞ることができます。

2.1 消去法方式の問題点

ここで、この方法について2つの問題点を提示したいと思います。

一つは、同じダメージの数値を2回引いてしまったとき、候補を絞るための情報が得られないことです。69、76ダメージの後にもう一度 69 ダメージが来ても、候補は1つも減らせません。

「もう一度じしんで69ダメージを引いた」という情報から何かを取り出せないでしょうか?

もう一つは、残った候補に優劣がつかないことです。数十個の候補が残ったとして、それらが全て同じくらい正解に近いとは考えにくく、本命とそれ以外を区別する情報が欲しいところです。

この2つの課題は、同じ方法で解決できます。鍵はダメージの計算式に含まれている乱数です。

3. ダメージの乱数に隠された情報

ダメージ計算式には乱数の項があり、0.85〜1.00の16通りが等確率で選ばれます。
ダメ計ツールが表示する「63~75」のようなダメージ幅の正体がこの乱数です。

ここで、16個の乱数それぞれのダメージを並べた一覧(ダメージテーブル)を見ると、計算結果が切り捨てで丸められるため、異なる乱数が同じダメージになっている箇所があります。

これを利用します。

ガブリアスの配分の候補を2つ考えます。

一つ目は ガブリアス(A32 無補正)です

二つ目はガブリアス(A4 無補正) です

どちらも69ダメージを出せる、つまり消去法では区別がつかない2候補です。

しかしダメージテーブルの中身は異なり、下記のようになっています

ここで、69 ダメージの個数を数えてみると次のようなことがわかります

  • ガブリアス(A32 無補正):16個のうち2個が69ダメージ → 69が出る確率は 2/16

  • ガブリアス(A4 無補正): 16個のうち1個が69ダメージ → 69が出る確率は 1/16

このように出現する個数が違うならば、確率も変わります。

ここで「69ダメージが出た」という事実をバトルで見たとき、ガブリアス (A32 無補正) のほうが、正解 (相手のガブの真の配分) に近い気がしてきませんか?

これは体感だけでなく、理論上もガブリアス(A32 無補正)のほうが正解に近いと考えてよいです。

これはベイズの定理と呼ばれる統計学の定理から導かれます。

4. ベイズの定理

ベイズの定理について説明します。

ここからは数学的な内容が続きますが、なんとかついてきてほしいです!

4.1 条件付き確率

条件付き確率について導入します。

事象 X に対して、X が発生する確率を P(X) と表記することにします。

ここで、事象 X と Y があったとき、 P(X | Y) と書いたら

「Y が発生したという前提のもと、X が発生する確率」とします。

ポケモンの例で考えます。

X を 「じゃれつくが命中する」、Y を「じゃれつくが急所に当たる」とします。

このとき、各確率は次のようになります。

(急所率を1/24 = 0.042 とします)

P(X) = 0.9
P(Y) = 0.9 * 0.042 (じゃれつくが当たって、かつ急所に当たる確率なので)
P(X | Y) = 1 (「急所に当たったとき、じゃれつくが当たっている確率」なので 100% です。)
P(Y | X) = 0.042

このように、条件を付けて確率を考えることを、条件付き確率と呼び

P(確率の対象になる事象 | 前提となる事象)

と表記します。

4.2 ベイズの定理

ベイズの定理とは、次のようなものです

P(X | Y) := P(Y | X) × P(X) / P(Y)

証明については、良質な解説が多数存在するため省かせてください。

P(Y) で割っている部分は、確率の総和を 1 にするために調整しているだけなので、これを取り除いて、

P(X | Y) ∝ P(Y | X) × P(X)

この式について注目してみます。(∝ は、「比例する」という意味です)

先ほどのガブリアスとカバルドンの例に当てはめて、考えてみます。

適当なガブリアスの配分 x を思い浮かべてください。
事象 X を「相手のガブリアスの配分が x である」、事象 Y を「じしんでダメージ69を観測」とし、
さきほどの式に代入すると

P(相手のガブリアスの配分が x である | じしんでダメージ69を観測) 
∝ P(じしんでダメージ69を観測 | 相手のガブリアスの配分が x である)
   × P(相手のガブリアスの配分が x である)

と、このように書けます。

ここで左辺の、

P(相手のガブリアスの配分が x である | じしんでダメージ69を観測) 

というのは、「ダメージ69を見たうえで、相手のガブリアスの配分が x である」確率です。

x を固定せずに、すべての配分についてこの確率を求めると、

これは「ダメージを観測して、相手のガブリアスの配分を推測しようとする行為」そのものです。

配分ごとに「この配分の確率はこのくらいかな?」と点数をつけていることを想像してみてください。

つまり、逆ダメ計を行っていることになります。

続いて

P(じしんでダメージ69を観測 | 相手のガブリアスの配分が x である)

について考えます。

これは、相手のガブリアスの配分が x であるとき、ダメージ 69 が出る確率を計算すればいいです。

つまり、先ほどの例で考えた、「16個のうち○○個が69ダメージ」というのを求めればよいので、

ダメージ計算を行って、当てはまるダメージを数えるだけです。

この、「正解が x であることを仮定したときに、観測(ダメージ)が発生する確率」を尤度と呼びます。

最後の

P(相手のガブリアスの配分が x である)

は、この観測を見る前に「相手のガブリアスの配分が x である」確率です。

これは事前確率と呼ばれます。

我々はダメージの観測なんぞを見ずとも、流行しているガブリアスの型や相手の6匹の並びから

「この型のガブリアスの線が濃厚だなぁ」みたいな予想をすることがあると思います。

事前確率とは、このような「予測者が観測前から持っている知識」のことを指します。

これは主観的なもので OK です。

また更新後の確率(左辺)

P(相手のガブリアスの配分が x である | ダメージ69を観測) 

を、事前確率と対比して事後確率と呼びます。

5. 推定のデモンストレーション

今回は、今まで例で扱ってきたカバルドンに対して、相手の配分未知のガブリアスが攻撃してきたとします。

このカバルドンに対して、ガブリアスが

  1. げきりん で 81 ダメージ

  2. じしん で 69 ダメージ

  3. げきりん で 93 ダメージ

  4. げきりん で 87 ダメージ

  5. じしん で 79 ダメージ

与えてきたとします。

まず、事前分布 P(ガブリアスの配分が x である) について、考えてみます。

今回はありうる配分 × 持ち物の候補に対して、等しい確率を割り振ります。

すべてのセルに0.52%が事前確率として割り振られています。

ここに、げきりんで81ダメージを与えられたとして確率を更新してみます

事前分布に、「げきりん 81ダメージ」で計算した尤度関数を掛け算しました。

そもそものダメージテーブルに81がない候補からは、確率が消えます。(尤度が 0 /16 になるためです)

残った候補でも、1.56% と 0.78% で2倍の差がつきます。

これは、81を1個含む配分と2個含む配分の違いが表現されています。

続いて、じしんの69ダメージですが、

更新された確率がこちらになります。りゅうのキバの候補が消えました。

続いて、げきりんの93ダメージの観測ですが、ここでやわらかいすなの候補も脱落します。

続いてげきりん87ダメージ、候補の脱落はありませんが、微妙に重みが変わっています。

最後にじしん79ダメージです。A175 ~ 178 の候補が脱落しました。

これで、ありうる候補は5パターンまで絞ることができました。

これで観測が終わったとして、あとは主観的な事前確率次第ですが、

最も確率の高い一団にいる、火力系の持ち物を持ってないA32無補正の線が濃厚なのではないか、と考えることができるのではないでしょうか?(一番多そうですし)

長々説明してきましたが、これが相手の火力を推定するための逆ダメ計です。

6. 相手の耐久を推定する逆ダメ計

ここまでは、受けたダメージを自分のHPバーから実数値で正確に把握できていました。

今度はこちらが攻撃する側、つまり相手の耐久(H・B・D)を推定する場合を考えてみましょう。前半との大きな違いは、相手のHPが%表示で、しかも切り捨て表示されていることです。

「何ダメージ入ったか」を正確に観測できないため、難易度が上がります。

なお本記事では、%表示の仕様を「小数点以下切り捨て、ただし0%になる場合のみ1%表示」として扱います。この仕様については、ポケモンソルジャー様の検証動画を参考にしています。

6.1 尤度の計算が難しい例

自分のA32無補正ガブリアスのじしんで、相手のカバルドンの表示が50%から21%になったとします。「H215 B164 D110」という配分に対する尤度はどのように計算すればよいでしょうか?

50%と表示される実際のHPは108109の2通りです。
HPが109なら、ダメージ次第で21%表示というのはありえます。
一方、内部HPが108なら、最低乱数の63ダメージでも表示は20%まで落ちるため、この観測は起こりません。

つまり、この候補の尤度関数は、 108 の場合 0/16 (つまりゼロ)、109 の場合 1/16 と、2通りあることになってしまいます。

困りました。

6.2 内部のHP分布を持つ

ここから、相手のH・B・Dの実数値の組み合わせをHBD候補と呼ぶことにします。

HBD候補ごとに、「この候補が正解だった場合、考えられる内部のHPの値」を重み付きの分布で持ち、観測のたびに更新します。

具体例で確認します。

H215のカバルドン候補が、じしんで100%→70%になったとします。

70%表示になるダメージを逆算すると63か64です。

ダメージテーブルを見ると、63を出す乱数は1個、64を出す乱数は2個あります。よってこの候補の内部のHPは、

  • HP 152 (= 215 - 63) 重み 0.33333...

  • HP 151 (= 215 - 64) 重み 0.66666...

という分布になります。
同じ70%という表示でも、151のほうが152より2倍、確率が高いという情報まで持つことができます。
ゲームが切り捨てた小数点以下を、こちらが勝手に拾って保管している格好です。

次の観測(仮に適当な技で70%→50%)が来たら、分布の各内部HPから16通りの乱数をすべて試し、50%表示と一致する遷移だけを残します。
この例では、151から43ダメージ(乱数1個)、152から43または44ダメージ(乱数2個)が一致する、とします。

このとき、次のように更新します。


矢印で表している確率の更新を、遷移 と呼ぶことにします。(統計の用語ではありません。)

遷移に書いてある数式は「 一致した乱数の個数/16 × 遷移前の内部HPの重み」です、
これを遷移の重みとします。

この遷移の重みは、2つのことに使えます。

1つ目は、HBD候補の尤度です。一致した遷移の重みをすべて足すと、そのHBD候補で今回の観測がどれくらい起こりやすいかが分かります。

あとは火力の推定と同じようにベイズの定理を使った確率の更新を行うだけです。

2つ目は、次の内部HPの分布です。

一致した遷移の行き先(攻撃後の内部HP)ごとに重みを足し合わせると、それがそのまま更新後の分布になります。

つまり、1回の観測で「HBD候補そのものの事後確率」「その候補の内部HPの分布」の両方が更新されます。

これが、耐久方向の逆ダメ計のポイントです。

6.3 物理と特殊をまとめて扱う理由

物理技はHPとB、特殊技はHPとDが関係するため、
物理耐久と特殊耐久を分けて推定したくなります。

実際、前半の火力推定では物理と特殊を切り分けて扱っていました。

しかし耐久方向では、HBD候補としてH・B・Dを1セットとしてまとめて持っています。

理由は内部HPの履歴です。

たとえば、物理技で100%→67%、続けて特殊技で67%→40%になったとき、2回目の更新の起点である「67%のHPの中身」は、1回目の物理技が作った分布です。

物理技も特殊技も回復技も、相手のHPは1本の同じ流れの上で起きているため、分けて持つと内部HPの分布が追跡できなくなります。

まとめて持つことで、すべての観測を同じHP推移としてつなげて扱えます。

6.4 回復技と固定ダメージ

この「内部のHP分布を持つ」という考え方は回復技にも使えます。

なまけるや自己再生は最大HPの半分を回復する技ですが、HBD候補が決まっていれば最大HPも決まるため、回復量も確定します。

乱数が絡まないので、各内部HPに回復量を足して%表示と合うかを照合するだけで、合わない候補を消せます

乱数のブレがないぶん、回復技はHPの候補を絞るうえでかなり強い情報になります。

ステルスロックや砂ダメなどの固定ダメージ、やどりぎのたね、がむしゃら、いたみ分けも同様の考え方で情報を取得できます。

冒頭のアーマーガアの例題は、この仕組みで計算しています。

内部HP分布を追跡していれば、12%という表示の中身が分かっているため、分布の全てがステロ圏内なら確実に落ちる、全て圏外なら確実に耐える、混在していれば重みを集計して「61%で落ちる」と判断できます。

(この61%のような中途半端な値は、はあくまで主観的な事前分布によって左右される値のため、「環境を考慮せず、あくまで場に出てからの観測のみを考慮すると」という注釈がつきます。)

7. ツールの紹介

ここまでの計算を対戦中に手で行うのは現実的ではないので、自動で行うツールを作りました。
ブラウザで動作します。

リンクは下記です。

なお、使い方の説明については、動画で見たほうがわかりやすいと思いますので、冒頭で貼った動画の後編 (14:16~) をご覧ください

7.1 ツールの課題

対戦の1ターン45秒制限の中で入力を続けるのはかなり大変で、製作者の私も毎回の対戦では使えていません。

「作った本人が使いこなせていないツール」という字面の悪さは自覚しています。

操作速度を上げるためのUI/UXの改善は続けますが「HPの推移を入力し続ける」行為そのものが時間のかかる作業だと感じているため、この部分が本質的に改善されるかは難しいところです。

(入力を自動化するとか、そういう方向でないと本質的な改善は難しいと思います)

なので、いきなり実践ではなく、まずはリプレイを見ながら試合を振り返る用途で使うことを推奨します。

また、一部の技は逆ダメ計のやり方(尤度関数の計算)が難しく、

現時点では実装できていません。代表例は、イカサマです。

よい方法を思いついた方は、ぜひどこかで教えてください。

8. おわりに

いかがでしたか?

今回は、ダメージの変動から尤度関数を求め、それによって相手の配分の推定を逆ダメ計する方法を説明しました。

面白いと思っていただければ幸いです。

逆ダメ計の詳細やツールの使い方など、質問していただければ答えますので、
Xや動画のコメント欄にぜひ送ってください!