アダマール積が便利な話

ツイッターに書こうとしたら長すぎたからこっちに書く。
アダマール積なんて難しそうな言葉をタイトルにが使ったけどそんなに仰々しい文章でもない。ただ行列計算に落とし込む方式を考えついたのを自慢したかったのと、今後これを実装するためにメモ代わりに使いたい、それだけの文。

サンブレイクのスキルは独特

サンブレイクのスキルは、巧撃や狂竜症のように特定条件下でだけ発動するスキルがかなり多い。
困ったことに、そういうスキルの発動率は各々の立ち回りやモンスターとの相性、他スキルに左右される部分が大きいから、「このスキルはだいたいこれぐらいの強さ!」とは一概に言えない。だからスキルの実質的な強さは計算機で決めず、あらかじめ決まっている効果だけ計算機に格納して、その発動率を各々に入力させて期待値に反映させようとしている。以下はその実装をどうするかの話。

行列計算に落とし込む話

もしかしたら誰でも思いつく方法なのかもしれないけれど、pythonに慣れてない俺にとってはめちゃめちゃ目新しい方法だったから、俺と同じようにどうしても最強装備が作りたい人々(いるのか?)のためにここに書いておく。
期待値計算をするときにかなり便利な概念だから、強い装備を作りたいと思っているなら、行列計算の概念だけでも知ってからブラウザバックしてほしい。というささやかな願いがあるが、まあ時間は有限なので飽きたらさっさとブラウザバックしてシコって寝てください。

下のコードは例。スキルと確率は適当。pythonのnumpyライブラリを使って、ndarray型として行列を表現している。

import numpy as np

# 巧撃スキルの発動率行列
# 巧撃スキル発動が70%、非発動が30%
umageki_array = np.array([[0.3, 0.7]])

# 死中に活スキルの発動率行列
# 死中に活スキル発動が20%、非発動が80%
shichu_array = np.array([[0.8, 0.2]])

行列とは言っても1行2列なので数字が2つ並んでいるだけ。それぞれの数字はそのスキルが発動している割合と発動していない割合を表していて、足すと1になる。
ここからが重要で、行数と列数が一致する行列は行列どうしでかけ算(通常の積ではなくアダマール積:各成分ごとのかけ算)ができる。
が、このままかけ算をしても意味のある行列にはならないから、死中に活スキルを転置(行と列を入れ替えること)して計算する。このとき、行数と列数が一致しなくなるけど、行数/列数が1の場合、かける相手の行列と同じ行数/列数と見なされる仕様があるから、かけ算ができる。このnumpyライブラリでは、行列の後に.Tとつけるだけで転置行列になる。

# 死中に活スキルの転置行列
print(shichu_array.T)

-----実行結果-----
[[0.8]
 [0.2]]

そして、巧撃スキルと死中に活スキルをかけ算(アダマール積)するとこうなる。

# 巧撃スキルの行列と死中に活スキルの行列をかけ算
mul_array = np.multiply(
    umageki_array, shichu_array.T
    )
print(mul_array)

-----実行結果-----
[[0.24 0.56]
 [0.06 0.14]]

この2行2列の行列の数字はこういう意味。
0.24 巧撃も死中に活も発動しない割合
0.56 巧撃が発動し、死中に活が発動しない割合
0.06 巧撃が発動せず、死中に活が発動する割合
0.14 巧撃も死中に活も発動している割合
こんなふうに、転置してかけ算をすることでスキルの発動割合を行列で表現できるようになる。

さらにこの行列を1次元に変換すれば、これまでと同様にして他のスキルも追加できる。

# 行列を1次元化する
reshaped_array = mul_array.reshape(-1)
print(reshaped_array)

-----実行結果-----
[0.24 0.56 0.06 0.14]



# 逆襲スキルの発動率行列
# 逆襲スキル発動が10%、非発動が90%
gyakushu_array = np.array([[0.9, 0.1]])

mul_array2 = np.multiply(
    reshaped_array, gyakushu_array.T
    )

print(mul_array2)

-----実行結果-----
[[0.216 0.504 0.054 0.126]
 [0.024 0.056 0.006 0.014]]

ここまでは発動率について行列を作った。
で、ここが最も重要で、同様にして作った攻撃力の行列とかけ算して、行列をすべて足し合わせれば、期待値を計算できる

ここでは上の期待値行列をもとに、攻撃力330、巧撃Lv3(+30)、死中に活Lv3(+20)、逆襲Lv3(+25)で期待値を出す。
実際にはこれらスキルによる強化は変数に入れて計算するけど、わかりにくくなるから値だけ入ったリストを用意した。

# 発動率行列
rate_array =
    [[0.216, 0.504, 0.054, 0.126],
     [0.024, 0.056, 0.006, 0.014]]

# 攻撃力行列
# [[なし, 巧撃, 死中に活, 巧撃+死中],
#  [逆襲, 巧撃+逆襲, 死中+逆襲, 巧撃+死中+逆襲]]
atk_array =
    [[330, 360, 350, 380],
     [355, 385, 375, 405]]

# 期待値行列
ex_dmg_array = np.multiply(rate_array, atk_array)
print(ex_dmg_array)

# 行列の和を求める
ex_dmg = np.sum(ex_dmg_array)
print(ex_dmg)

-----実行結果-----
[[71.28 181.44 18.9  47.88]
 [ 8.52  21.56  2.25  5.67]]

357.5

このように、行列のアダマール積を使って期待値を計算できた。

おわりに

こんな感じでめちゃくちゃ便利な概念だからさっさと実装しようと思う。会心もちょっと面倒だけど応用して計算できる(ただし一度行列から出さないといけないっぽい)。
当たり前なことを偉ぶって書いた感じがしたから出すかどうか迷ったこと、普通の行列の積(手計算だとめっちゃ計算が面倒くさいやつ)とアダマール積を勘違いしていたことを記して〆にします。おやすみなさい。


この記事が気に入ったらサポートをしてみませんか?