見出し画像

Retro-gaming and so on

確率変数の話

オカルトって好き?

一般的には科学、ってのは「隠されたモノを暴き出す」事だと思われている。
そもそも「オカルト」とは、語源的には「隠されたモノ」だ。従って「隠されたモノを暴き出して法則性を見出す」科学とは、まさしく対極的なモノだ、と一般的に思われてはいる。
しかしながら、数式数式、って出てくる「科学と思われてるモノ」がオカルトを日の光の下へと全部晒しだしている、ってのは実は誤解だ。
世の中には「科学のフリをして」実は内情的には「オカルト」を含んでるモノもある。
代表的なトコでは統計学、がある。
統計学では確率分布とか確率密度関数、と呼ばれるモノが頻出するが、実はこれ自体が「オカルト」を内包してるんだ。
信じられない?でも「事実」だ。これら確率分布/確率密度関数、っつーのは、「形式的には」数学的関数の見た目を持ってるが、実は根底にはオカルトを含んでる。

一番有名な確率密度関数に、例えば性器正規分布がある。一時期、ベルのカタチに似てる、ってぇんでベル・カーブと言う通称が流行った事がある(※1)。


ベルの例


ベル・カーブならぬベルマーク

正規分布は概形から言うと、なるほど、確かにベルに似てる。


な?
ベル・カーブは割に色んなトコで見かけるグラフだ。
ただし、これを描画する数式の見た目が厳しいんで、統計初学者泣かせでもある。
こんな数式だ。


ビビんな、ビビんな。ここでこれ出したのは「如何にも関数のように見える」から示しただけだ。実際はこいつは関数じゃない、って言いたかっただけだ。
関数じゃないって?うん。こいつは確率密度関数の代表格なんだけど、確率密度関数は数学で言う関数じゃないんだ。上の方に書いただろ?描画する数式だって。
そう、こいつは描画する為だけに存在する数式で、実は関数としては機能していない。と言うのも、性質的には、xが決まったからf(x)が決まる、と言うような事を表してるんじゃねぇんだ。
実際は、横軸のxは「何らかの結果」を表している。「何らか」って何だろ?それはツキ、とか運、とか「神の見えざる手」とか、あるいは「山村貞子」かもしんない。要するに「何だか分からないもの」が結果xを作っている。
だから言っただろ?「オカルト」だって(笑)。何だか分からんものが作り出した「結果」がxで、何度もそれを試して積み上げた結果が縦軸のdensityになっている。
これは、さっきも書いたけど、数学で言う関数みたいにxを与えたから結果f(x)が決まる、と言うような関係じゃない。むしろ、運、と言う「人知を超えたトコの」結果がxなんだ。

x = 運(オカルト)の結果

考えてみたらそうだろ?例えばサイコロを何度も振ったら次のような結果になるかもしんない。


でも別に横軸のValueが縦軸のprobabilityを決定してるわけじゃない。要するに直接は関係ないんだ・・・ただし、多数の試行をした際に「こうなるだろうな」と言うのを図として定式化したのが確率分布、あるいは確率密度関数と呼ばれるモノだ。
つまり、何度も繰り返すけど、確率分布/確率密度関数は別に数学的な関数・・・例えば y = xとかみたいに、xが決まったら即時yが決まる、と言うような関係性を表すモノではない。
そしてあくまでxは「運の」結果だ。
確率分布、ないしは確率密度関数で言う横軸、ここではxとしたが、それを確率変数、と呼ぶ。ただし、この変数は関数の変数を意味しない。単に「運の」結果を確率変数、って呼んでるだけだ。
もう一度言うが、確率分布ないしは確率密度関数は、基本的には図式化する為のモノであって、別に「確率を生み出す構造」を記述してるわけじゃない。「確率を生み出す構造」は人知を超えたトコにある。言わばオカルト、だ。オカルトの結果な以上、例えば貴方がここでサイコロを振って1が出たとしても「どうして?」と言う問に答えは返ってこない。何故なら「どうして」は「オカルトだから」と言うのが答えになる。その時点で1が出るのは全く理由が分からない。
つまり、厳密に言うと、統計学は科学ではない。オカルトの結果を積み上げた結果、図式化して「無限試行すればこんな画になるだろう」と言ってるだけで、物理学とか化学とかが「現象と現象との因果関係」を言うのとは全く次元の違う事を言ってる。根底的には「分からん」ってトコからスタートして、そのまま来てるわけだ。あるのは仮定に次ぐ仮定しかねぇわけだな。
意外だったか(笑)?
しかし、統計で扱う「確率」なんつーのは数学でも何でもないし、「オカルトを観察した結果」でしかないんだ(※2)。

さて、確率分布、ないしは確率密度関数、と言われるモノの「正体」をこうやってまずは把握してもらって、だ(※3)。
コンピュータで何とかこの「オカルト」を再現したい、と。つまり「確率を生成する何か」を実装しようとする。
以前、PythonによるXorshiftの実装例を紹介したが。この「何とかしてオカルトのようなモノを再現したい」と言った熱意の結果を疑似乱数と呼ぶ。
"疑似"ってことはホンモノじゃない、って事だ。乱数は定義上、「オカルトの中身」が分からん以上、結果がどうなるか分からん。しかし、疑似乱数は、「パッと見分からん数列」になっているが、数列である以上、実は規則性があるわけだ。
一般に、疑似乱数生成器、と言う数列は周期性がある。初めに出した数値に「戻ってくる」んだよな。
例えばC言語実装に良く用いられる線形合同法なんかは、場合によってはWikipediaで解説されてる通り、3つ数値を出したら戻ってくるケースがある。
反面、「最高峰の疑似乱数生成器」と言われる日本で開発されたメルセンヌ・ツイスタ(Pythonで使われてる乱数生成器実装)だとパターンを繰り返すまでに43154247973881626480552355163379198390539350432267115051652505414033306801376580911304513629318584665545269938257648835317902217334584413909528269154609168019007875343741396296801920114486480902661414318443276980300066728104984095451588176077132969843762134621790396391341285205627619600513106646376648615994236675486537480241964350295935168662363909047948347692313978301377820785712419054474332844529183172973242310888265081321626469451077707812282829444775022680488057820028764659399164766265200900561495800344054353690389862894061792872011120833614808447482913547328367277879565648307846909116945866230169702401260240187028746650033445774570315431292996025187780790119375902863171084149642473378986267503308961374905766340905289572290016038000571630875191373979555047468154333253474991046248132504516341796551470575481459200859472614836213875557116864445789750886277996487304308450484223420629266518556024339339190844368921018424844677042727664601852914925277280922697538426770257333928954401205465895610347658855386633902546289962132643282425748035786233580608154696546932563833327670769899439774888526687278527451002963059146963875715425735534475979734463100678367393327402149930968778296741391514599602374213629898720611431410402147238998090962818915890645693934483330994169632295877995848993366747014871763494805549996163051541225403465297007721146231355704081493098663065733677191172853987095748167816256084212823380168625334586431254034670806135273543270714478876861861983320777280644806691125713197262581763151313596429547763576367837019349835178462144294960757190918054625114143666384189433852576452289347652454631535740468786228945885654608562058042468987372436921445092315377698407168198376538237748614196207041548106379365123192817999006621766467167113471632715481795877005382694393400403061700457691135349187874888923429349340145170571716181125795888889277495426977149914549623916394014822985025331651511431278802009056808456506818877266609831636883884905621822262933986548645669080672191704740408891349835685662428063231198520436826329415290752972798343429446509992206368781367154091702655772727391329424277529349082600585884766523150957417077831910016168475685658673192860882070179760307269849987354836042371734660257694347235506301744118874141292438958141549100609752216882230887611431996472330842380137110927449483557815037586849644585749917772869926744218369621137675101083278543794081749094091043084096774144708436324279476892056200427227961638669149805489831121244676399931955371484012886360748706479568669048574782855217054740113945929622177502575565811067452201448981991968635965361551681273982740760138899638820318776303668762730157584640042798880691862640268612686180883874939573818125022279689930267446255773959542469831637863000171279227151406034129902181570659650532600775823677398182129087394449859182749999007223592423334567850671186568839186747704960016277540625331440619019129983789914712515365200336057993508601678807687568562377857095255541304902927192220184172502357124449911870210642694565061384919373474324503966267799038402386781686809962015879090586549423504699190743519551043722544515740967829084336025938225780730880273855261551972044075620326780624448803490998232161231687794715613405793249545509528052518010123087258778974115817048245588971438596754408081313438375502988726739523375296641615501406091607983229239827240614783252892479716519936989519187808681221191641747710902480633491091704827441228281186632445907145787138351234842261380074621914004818152386666043133344875067903582838283562688083236575482068479639546383819532174522502682372441363275765875609119783653298312066708217149316773564340379289724393986744139891855416612295739356668612658271234696438377122838998040199739078061443675415671078463404673702403777653478173367084844734702056866636158138003692253382209909466469591930161626097920508742175670306505139542860750806159835357541032147095084278461056701367739794932024202998707731017692582046210702212514120429322530431789616267047776115123597935404147084870985465426502772057300900333847905334250604119503030001704002887892941404603345869926367501355094942750552591581639980523190679610784993580896683299297681262442314008657033421868094551740506448829039207316711307695131892296593509018623094810557519560305240787163809219164433754514863301000915916985856242176563624771328981678548246297376249530251360363412768366456175077031977457534912806433176539995994343308118470147158712816149394421276614228262909950055746981053206610001560295784656616193252269412026831159508949671513845195883217147982748879261851417819979034417285598607727220866677680426090308754823803345446566305619241308374452754668143015487710877728011086004325892262259413968285283497045571062757701421761565262725153407407625405149931989494459106414660534305378576709862520049864880961144869258603473714363659194013962706366851389299692869491805172556818508298824954954815796063169517658741420159798754273428026723452481263569157307213153739781041627653715078598504154797287663122946711348158529418816432825044466692781137474494898385064375787507376496345148625306383391555145690087891955315994462944493235248817599907119135755933382121706191477185054936632211157222920331148502487563303118018805685073569841580518118710778653953571296014372940865270407021924383167290323231567912289419486240594039074452321678019381871219092155460768444573578559513613304242206151356457513937270939009707237827101245853837678338161023397586854894230696091540249987907453461311923963852950754758058205625956600817743007191746812655955021747670922460866747744520875607859062334750627098328593480067789456169602494392813763495657599847485773553990957557313200809040830036446492219409934096948730547494301216165686750735749555882340303989874672975455060957736921559195480815514035915707129930057027117286252843197413312307617886797506784260195436760305990340708481464607278955495487742140753570621217198252192978869786916734625618430175454903864111585429504569920905636741539030968041471個の数を出す。
つまり、周期が長いんで、どこを切り取っても「充分乱数に見える」って事だ。
そう、疑似乱数は、生成する数列の「周期が長ければ長い程」良いんだ(※4)。
もう一つ、コンピュータ上でのやり様では、疑似乱数を生成する数列に与える初期値をガンガン変える、と言う手を打つ。良くあるパターンでは、初期値に時刻を使うわけだな。初期値が2022年11月30日と2022年12月1日の2つだと、なるほど、生成される乱数列は違うモノとなるだろう。そうすれば、たとえ「規則/数学的に数列が生成されようと」僕らはフツーは気づかない、っちゅーこっちゃ。

さて、この疑似乱数を生成する「確率生成装置」だけど、一応疑似乱数≒乱数として考えてみた時、得られる確率分布を一様分布と呼び、生成された乱数を一様乱数、と呼ぶ。



うん、それは樋口一葉だ(謎

例えばサイコロをプログラミングしよう、とすればRacketならこうなるだろう。

(define (die) (random 1 6))

Pythonだと次のようになり、

from random import randint
def die(): return randint(1, 6)

JavaScriptだとこんなカンジだろう(※5)。

function die() { return Math.floor(Math.random() * 6) + 1; }

これらは皆、1〜6の確率変数を生成し、出てくる結果を描画すると一様分布になる。要するに関数dieは1〜6の確率変数を生成する一様乱数の一種だな。
これが一番プログラミングに於いてお世話になるシステムだろう。
そして割に何でもかんでも一様乱数に頼っている。
しかしそれでホンマエエのか?

基本攻撃ダメージ=(攻撃力-敵の守備力÷2)*乱数値(54~197)/256

うん、これは確かに一様乱数を使った典型的なプログラムで、しかもこれで攻撃が失敗したり、あるいは「かいしんのいちげき」が出るわけだよな。
でもちと待ってくれ、とか思わない?攻撃で与えるダメージに「平均値」があるんだったら、プログラムとして考えた際に、例えば「正規分布に従う」(※6)ダメージでもエエんちゃうか、とか。何もサイコロっぽい計算式じゃなくても良いんじゃないか、とか。
まぁ、ある種ドラクエはオリジンであるTRPGっぽい計算になってる、って言えばなってはいる。しかし、せっかくのCRPGだ。何も計算式がサイコロを利用したようなモノじゃなくって、もっと別のイカした乱数を扱っても良かないか、と。
実はゲームプログラミングに於いては長い間、この「サイコロスタイル」、言い換えれば一様乱数の利用しかなかったんだわ(※7)。一つはプログラミングする側の「確率」に対する知識が少なかった事。もう一つはなんだかんだ言ってプログラミング言語側に「一様乱数」以外の乱数が提供されてなかった、からだよな(※8)。
後者は結構理由としては大きい。やっぱり「ビルトインライブラリ」が無い状態で色々やろうとするのは面倒臭い。
ここでは疑似乱数である一様乱数から、他の確率分布/確率密度関数に従う確率変数の作り方を紹介しよう。
ここで、例えば指数分布、と言う確率密度関数を考える。


描画の為の数式、つまり確率密度関数は以下の通りだ。



別にビビらんでもエエだろ。プログラムするなら

;;; Racket
(define (f x λ) (* λ * (exp (- (* λ x)))))

### Python
import math
def f(x, λ): return λ * math.exp(- λ * x)

// JavaScript
function f(x, λ) {return λ * Math.exp(- λ * x);}

みたいなモンだ。
ただし、何度も言うが、確率分布/確率密度関数は「描画の為の数式」であって、指数分布に従う確率変数、つまり、指数乱数を生成する為のモノじゃない。
ここでトリックを使う。確率分布/確率密度関数に付随した累積分布関数、と言われるものがある。それを利用する。

累積分布関数、とは数学的に言うと、確率分布を順次加算、あるいは確率密度関数を積分したものだ。
積分、と聞いてもビビらないでいい。大体有名な確率分布/確率密度関数に於いての累積分布関数はWikipediaに載っている(笑)。自分で計算せんでも、「累積分布」をキーワードにして調べてみればいいだけ、だ(笑)。
しかも、確率は全部足せば1だ、ってのは小学生でも知っている。確率密度関数も定義域で全域に渡って積分すれば1になる。と言うよりも総和が1になる量を確率、としている、と言うのがホントのトコだ。そして総和が1になるようなブツは誰でも勝手に作れるんだよ。
確率分布/確率密度関数は統計学でいくつも出てきて、またこれを暗記せなアカンのか、と悩む統計初学者が多いんだけど、実のトコ言うと、それらは現実で良く見かけるブツとして代表例で出てきてるだけ、であって、定義を良く良く見てみると、自分で勝手に作っても別に怒られる類のモンじゃないんだよな。っつーか、総和が1になるようなブツなんざどうとでも作れるだろ(笑)?だから気楽に構えてていいんだ。
さて、指数分布の累積分布関数はこうだ。


数学に自信がある人は指数分布の確率密度関数をxで不定積分すればいい。自信が無い人はWikipediaから答えを引っこ抜いてくればいい(笑)。
ちと見づらいし、左辺を敢えてxと置いて、今までxだったものをyとして次のように書き換える。


そしてyに付いて整理する。


この右辺をプログラムするんだが、ここで、xを「0〜1」を生じる一様乱数とする。
つまり、次のようなプログラムにする。

;;; Racket
(define (expovariate λ) (- (/ (log (- 1 (random))) λ)))

### Python
import math, random
def expovariate(λ): return - math.log(1 - random.uniform(0, 1)) / λ

// JavaScript
function expovariate(λ) {return - Math.log(1 - Math.random()) / λ;}

これが「指数乱数」を発生する関数となる。結果はそんなに難しくねぇだろ?
一体何がどうなって、どういう仕組みで指数乱数が形成出来たのか。
もう一回指数分布の累積分布関数を見てみよう。



さっき、確率分布の総和や確率密度関数の定義域における積分は1になる、って話をした。そうすると、縦軸は0からxまでの和を意味していて、数学的に言うと値域は0〜1の範囲に制限されてる事が分かるだろうか。
0〜1、だ。つまり0〜1の数を発生させる「一様乱数」が取り得る値と対応してるのが分かるだろう。
言い換えると、横軸のxを縦軸に取る数値の「関数」として考えてみよう、ってのが仕組みだ。縦軸の中の値が一様乱数の結果として出てきたのなら対応するxを調べれば、それが欲しい確率分布/確率密度に従う確率変数になるだろう、ってのがそのトリックなんだよ。



累積分布関数と一様乱数を使えば、ほぼ全ての「任意の」確率分布/確率密度関数に従う乱数を生成する事が可能だ。
まぁ、モダンな言語処理系には色んな乱数が備わってるが、仮にそういうのが無かった場合でも、性器正規乱数以外は簡単に作れるんだよ(※9)、って事を紹介したかっただけ、だ。
貴方の使ってるプログラミング言語に、豊富な乱数が備わって無く、自作しなきゃならない場合は是非とも活用して欲しい。思ったより簡単に様々な乱数を作れる事が分かる筈だ。

※1: 「ベル・カーブ」と言う呼称が日本で流行ったのは、1994年にアメリカで出版された「The Bell Curve」と言うベストセラーな書籍が原因。
流行った割には邦訳が何故か無かった。マスコミ関係者がテレビで発言した際に一躍広まった愛称だ。

※2: ちなみに「確率論」と言うのは数学の一分野ではある。
ややこしいんだけど、数学上では確率論は「測度論」と言う分野の一分野だ。
いや、ホンマややこしく思うかもしれないが、これは平たく言うと「測量に関する計算法」を扱う分野で、確率も「計算対象」として扱われてる、と言う事だ。
「それが何か?」って思うかもしんないが、これは数学上の概念を読み解く鍵になってる。
つまり、数学は根底的には「計算」を扱う学問なんだけど、「意味」は扱わないんだよ。
大雑把に言うと、数学では全く「意味」を扱わない。つまり、我々が思うだろう「確率」と数学者が扱う「確率」は必ずしも同じじゃないんだ。後者は「計算対象」としてだけ数学者の興味を引いてるだけであって、じゃあそれが現実的に一体何を指すのか、と言うのは数学者の意識の外にあるんだ。それが事実なんだわ。
ある対象がどんな意味を持つのか、と言うのは数学の範疇外なんだ。はい、これホント。
従って、「確率とは何か?」と言うのは数学の範疇外の話で、要するに我々の側には「解釈の余地」ってのがある、って事だ。言い換えると「意味」を付け足すのは我々の勝手、なのである。
この話がピンと来ない人もいるだろうが、例えば1 + 1 = 2っつーのは数学なんだけど、1円 + 1円 = 2円、と「解釈」するのは数学の外なんだわ。数学がやってるのは円だろうとcmだろうと成り立つ「抽象的な枠組み」を決める事なんだけど、実際に円なのかcmなのか、ってのを考えるのは「数学の外」にいる僕らが決めていい事なんだ。
少なくとも、「計算方法とその定義」と言う枠組みだけ決めてあとは知らん、ってのが現代の数学のやり方で、こういうのを公理主義と言う。ルールは決めるからあとは勝手にしてくれ、ってのが現代の数学のスタンスで、それもあって

「確率の計算上の定義と計算法は決めるけど、確率の"意味"に対しては問わない」

と言う状況になってるわけ。
なお、確率には現在主流の「客観的確率」と言う「解釈」に始まって、主観確率、論理的確率、等、色々な「解釈」の流派が存在している。
そしてそれらは「どれがホント」ってワケじゃあないんだ。その「解釈」には哲学要素が加わってくる、ってのがホントのトコだ。

なお、こういう話を聞くと「数学は役に立つか?」って問はバカバカしくなるだろ?
数学自体は役に立たない。役に立てるか立てないか、ってのはむしろ「数学の外」にいる僕らの役目なんだ。

※3: 一般に、サイコロを振った結果、つまり飛び飛びの値が形成する「図」を「確率分布」、正規分布のように連続量で形成された図を「確率密度関数」と呼ぶ。

※4: Racketで使われている疑似乱数生成器はMRG32k3a法に拠る。

※5: ちなみに、あまり知られてないが、サイコロはdieが単数形であり、一般でダイス(dice) = サイコロ、と思われてるが、それは複数形だ。日本語化したブツでは珍しく、複数形が日本語になっている。

※6: 結果がある確率分布ないしは確率密度関数に見えるようになる事を「✕✕分布に従う」と表現する。例えば、サイコロを振り続けた結果でグラフを作った際に、一様分布に見えるような結果になった場合、「サイコロを振った結果は一様分布に従う」と表現する。

※7: ゼビウスの開発者として知られる遠藤雅伸氏は安直なサイコロ型の確率の乱用に対して、常々警告を放っている。 => 参考: 確率の罠―ドロップ確率1%=100回倒せば手に入る?
ローカル環境でのゲームだと問題がなかった事が、大規模なネットゲーム、特にガチャ絡みだと、圧倒的に「運が無い」人々が具現化し、アンチになってしまう。
明らかに理由は確率システム自体に内在してて、アンチは「単純な確率の捉え方」に対するツケとなって出て来てるわけだ。
このように、実は乱数を操っていても、プログラマは一般に、数学的思考に実は弱い。

※8: 最近では違う。Pythonは特に、Battery Includedと呼ばれるだけあって、一様乱数以外にも多くの乱数を備えている
また、Racketも、math/distributionモジュールで豊富な乱数を扱う事が出来る。

※9: 正規分布に従う確率変数、いわゆる正規乱数を作成するのが難しいのは、単純に正規分布をフツーに積分して累積分布関数を計算するのがムズいから、だ。
こればっかは処理系が用意しても良い乱数だと思う。
ただ、近似で良ければロジスティック分布を用いる、ってのは手だろう。これはカタチが極めて正規分布に良く似てて、しかも積分なんかでの扱いもラクなんで、近似値としては良く使われる確率密度関数だ。
もちろん、計算に自信があれば自分で手計算で積分しても良いが、Wikipedia辺りから解をくすねて来るのを妨げる事もない。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「プログラミング」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事