モンテカルロ法による円周率の推定(その4 PRNG)
- モンテカルロ法による円周率の推定(その1)
- モンテカルロ法による円周率の推定(その2 CLI)
- モンテカルロ法による円周率の推定(その3 Gaussian)
- モンテカルロ法による円周率の推定(その4 PRNG) ← イマココ
「モンテカルロ法による円周率の推定」もひととおり終わったので,今回は擬似乱数生成器(pseudo random number generator)の話。
math/rand の擬似乱数生成器
Go 言語では math/rand
パッケージで擬似乱数を取り扱えることは「その1」で紹介した通り。
math/rand
パッケージに実装されている擬似乱数生成器はラグ付フィボナッチ法(Lagged Fibonacci Generator)のバリエーションらしい。
ラグ付フィボナッチ法は線形合同法(後述)を改善することを目的としたものでフィボナッチ数の生成法を元にしている。
ラグ付フィボナッチ法は以下の式で表される。
ラグ付フィボナッチ法は $ * $ 演算子によってバリエーションがあるが math/rand
パッケージの実装では加算を使うため “Additive Lagged Fibonacci Generator” ということらしい。
ソースコードで言うとこの部分かな。
// Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
func (rng *rngSource) Int63() int64 {
rng.tap--
if rng.tap < 0 {
rng.tap += _LEN
}
rng.feed--
if rng.feed < 0 {
rng.feed += _LEN
}
x := (rng.vec[rng.feed] + rng.vec[rng.tap]) & _MASK
rng.vec[rng.feed] = x
return x
}
擬似乱数生成器のバリエーション
math/rand
パッケージでは以下の interface を持つ別の擬似乱数生成器を使うことができる。
// A Source represents a source of uniformly-distributed
// pseudo-random int64 values in the range [0, 1<<63).
type Source interface {
Int63() int64
Seed(seed int64)
}
以下に2つほど紹介する。
線形合同法
線形合同法(Linear Congruential Generator)は昔の擬似乱数ライブラリでよく使われていたアルゴリズムで,以下の式で表される。
定数 $A$ および $B$ の与え方により幾つかバリエーションがある。
線形合同法のメリットは実装サイズが小さく計算量も少ない点だろうか。 一方デメリットとしては,多次元で疎に分布する性質があり,周期も小さいため乱数を大量に発生させる必要がある科学技術シミュレーションなどには向かないと言われている。 このためメモリサイズが限られるマイクロ・コントローラのようなものでもない限り線形合同法が使われることはなくなった。
Go 言語でわざわざ線形合同法を実装しているパッケージは少ないのだが,たとえば以下のパッケージがある1。
Mersenne Twister
Mersenne Twister とは松本眞・西村拓士両氏によって1996年に発表された擬似乱数生成アルゴリズムである。 他の擬似乱数生成アルゴリズムと比べて以下の特徴があるそうだ。 (「Mersenne Twister とは?」より)
- 従来の様々な生成法の欠点を考慮して設計されています。
- 従来にない長周期,高次元均等分布を持ちます。(周期が $2^{19937}-1$ で2、623次元超立方体の中に 均等に分布することが証明されています。)
- 生成速度がかなり速い。(処理系にもよりますが、パイプライン処理やキャッシュメモリ のあるシステムでは、Cの標準ライブラリの
rand()
より高速なこと もあります。なお、開発当時には cokus 版はrand()
より4倍程度高速でしたが、その後 ANSI-C のrand()
が LCG 法から lagged-fibonacci に 変更されたこともあり、2002年現在 rand と MT の速度差はあまりありません。) - メモリ効率が良い。(32ビット以上のマシン用に設計された
mt19937.c
は、 624ワードのワーキングメモリを消費するだけです。 1ワードは32ビット長とします。
ちなみに Mersenne Twister のオリジナル・コードは BSD ライセンスで提供されている。
公式のリポジトリには C/C++ による実装のみのようだが, Go 言語で実装している人もいるようである。
- seehuhn/mt19937: An implementation of Takuji Nishimura’s and Makoto Matsumoto’s Mersenne Twister pseudo random number generator in Go.
- nutterts/randgen: Pseudo Random Number Generators implementing the Go(lang) math/rand.Source Interface
- farces/mt19937_64: Mersenne Twister (int64) for Go
- cuixin/goalg: golang algorithms
- cpmech/gosl: Go scientific library : SFMT や TinyMT に対応。オリジナルのコードを cgo で結合しているのでクロス環境では注意
擬似乱数生成器を組み込む
では,先ほど紹介した擬似乱数生成器を今回のコードに組み込んでみることにしよう。 こんな感じ。
package gencmplx
import (
"math/rand"
"github.com/davidminor/gorand/lcg"
"github.com/seehuhn/mt19937"
)
//RNGs is kind of RNG
type RNGs int
const (
NULL RNGs = iota
GO
LCG
MT
)
//NewRndSource returns Source of random numbers
func NewRndSource(rng RNGs, seed int64) rand.Source {
switch rng {
case LCG:
return lcg.NewLcg64(uint64(seed))
case MT:
mt := mt19937.New()
mt.Seed(seed)
return mt
default:
return rand.NewSource(seed)
}
}
gencmplx.NewRndSource()
関数で rand.Source
オブジェクトを生成する3。
これを「その1」で作った gencmplx.New()
関数に渡せばよい。
CLI は以下のように調整してみた。
$ go run main.go estmt --help
Estimate of Pi with Monte Carlo method.
Usage:
pi estmt [flags]
Flags:
-e, --ecount int Count of estimate (default 100)
Global Flags:
-p, --pcount int Count of points (default 10000)
-r, --rsource string Source of RNG (GO/LCG/MT) (default "GO")
で,それぞれの擬似乱数生成器で評価を行ってみようと思ったのだが,今回のケースに限ってはあまり違いが出ないようである。
まずは線形合同法の場合。
$ go run main.go estmt -e 10000 -p 100000 -r LCG > estmt100k-lcg.dat
random number generator: LCG
minimum value: 3.12204
maximum value: 3.16224
average value: 3.14164
standard deviation: 0.00524 (68.3%)
次は Mersenne Twister の場合。
$ go run main.go estmt -e 10000 -p 100000 -r MT > estmt100k-mt.dat
random number generator: MT
minimum value: 3.12380
maximum value: 3.16140
average value: 3.14165
standard deviation: 0.00517 (67.8%)
もっと多次元だったりすると変わってくるのかなぁ。
暗号技術用途の乱数生成器
Go 言語では暗号技術用途の乱数として crypto/rand
パッケージが用意されている。
これは math/rand
とは互換性がない。
具体的には,UNIX 系のプラットフォームでは乱数生成に /dev/urandom
デバイスを参照している4。
また Windows プラットフォームでは CryptoAPI 2.0 の CryptGenRandom
関数を使っている5。
そもそも暗号技術用途の乱数生成器は科学技術シミュレーションやゲームで使う擬似乱数生成器とは要件が異なる。
- RFC 4086 - Randomness Requirements for Security (IPA による日本語訳)
- NIST Special Publication 800-90A Revision 1: Recommendation for Random Number Generation Using Deterministic Random Bit Generators
暗号技術用途の乱数生成器は,暗号分野においては中核技術のひとつであるが,一度に大量の乱数を生成させる必要のある科学技術シミュレーションなどの用途には向かない。 上手く使い分けてほしい。
ブックマーク
- Mersenne Twister に関する覚え書き
- PCG, A Family of Better Random Number Generators | PCG, A Better Random Number Generator
- /dev/randomではなく/dev/urandomを使うべき理由? | マイナビニュース
- 与えられた重みに従ってランダムに値を返す「Weighted Random Selection」をGoで実装する! - Qiita
- ある範囲に収まる乱数を得るために剰余(モジュロ)演算を書くとき、レビューするときに意識すること
参考図書
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。
- 暗号技術入門 第3版 秘密の国のアリス
- 結城 浩 (著)
- SBクリエイティブ 2015-08-25 (Release 2015-09-17)
- Kindle版
- B015643CPE (ASIN)
- 評価
SHA-3 や Bitcoin/Blockchain など新しい知見や技術要素を大幅追加。暗号技術を使うだけならこれ1冊でとりあえず無問題。
-
ただし
github.com/davidminor/gorand
/lcg
には不具合があってInt63()
関数で負の値を出力する場合がある。とりあえず fork 版のgithub.com/spiegel-im-spiegel/gorand
/lcg
で修正している。 Pull request も出したけど,古いコードだし,もうメンテしてないかなぁ。 ↩︎ -
$2^{19937}-1$ はメルセンヌ素数で Mersenne Twister の名前の由来になっている。 Mersenne Twister では周期サイズごとに複数の実装があるが, $2^{19937}-1$ がポピュラーな実装として広く使われているようだ。 ↩︎
-
Go 言語におけるオブジェクトの多態性については「Go 言語における「オブジェクト」」を参考にどうぞ。 ↩︎
-
/dev/urandom
はハードウェア・デバイスから十分なエントロピー源が得られない場合は内部で疑似乱数生成器を使用する。このため一時は/dev/urandom
の脆弱性が疑われたが,現時点では事実上は問題ないとされている。一方で,スマートデバイスのような場合はハードウェア・デバイスからのエントロピー源だけでは外部から推測され易いため,性能のよい疑似乱数生成器を組み合わせるほうが有効になる場合もあるようだ。 ↩︎ -
CryptGenRandom
関数の内部実装は公開されていないが,やはりキーボードやマウス等のデバイスの挙動をエントロピー源とし, NIST の SP800-90 勧告に従った実装をしているようである。余談だが SP800-90 は乱数生成の一部のアルゴリズムで脆弱性が発見され(これがまた NSA 絡みだったものだから大騒ぎになった),現在は修正版の SP800-90A Revision 1が発行されている。(参考:擬似乱数生成アルゴリズム Dual_EC_DRBG について) ↩︎