ランダムだが再現できる
モンテカルロ・シミュレーション、ブートストラップ、多くの検定は乱数を必要としますが、誰も再現できない結果は価値がありません。デバッグも、モデルの2つのバージョンの比較も、同僚に見せることもできないからです。コンピュータが作る数は本当のランダムではありません。擬似乱数生成器は、シードと呼ばれる初期値から、乱数の抽出のように振る舞う長い列を作り出す決定論的なアルゴリズムです。同じシードなら、いつでもどのマシンでも同じ列になります。
import numpy as np
rng = np.random.default_rng(42) # a Generator created from the seed 42
print(rng.random(3).round(4).tolist()) # [0.774, 0.4389, 0.8586]
rng = np.random.default_rng(42) # the same seed again: the same numbers
print(rng.random(3).round(4).tolist()) # [0.774, 0.4389, 0.8586]
rng = np.random.default_rng(43) # another seed: other numbers
print(rng.random(3).round(4).tolist()) # [0.6523, 0.0438, 0.02]グローバルな状態ではなくGeneratorを使う
古いコードは np.random.seed(42) を1回呼び、あとはどこでも np.random.normal(...) を呼びます。すべてのコードが隠れたグローバルな生成器を1つ共有します。ライブラリや新しく数を引く行が増えると、他のすべての列がずれ、モデルと無関係な理由で結果が変わります。現代的な方法は np.random.default_rng(seed) で Generator オブジェクトを作り、明示的に受け渡すことです。random・normal・choice・integers などのメソッドがすべての抽出を行い、2つの生成器が干渉することはありません。
def simulate_returns(seed, n, mu, sigma):
rng = np.random.default_rng(seed) # a private generator, not the global one
return rng.normal(mu, sigma, size=n)
first = simulate_returns(7, 5, 0.0005, 0.01)
second = simulate_returns(7, 5, 0.0005, 0.01)
print(first.round(5).tolist()) # [0.00051, 0.00349, -0.00224, -0.00841, -0.00405]
print(np.array_equal(first, second)) # True repeatableリサンプリングと独立ストリーム
ブートストラップは履歴を再抽出して、統計量がどれだけ変わりえたかを調べます。手持ちのリターンと同じ数を復元抽出で引きます:rng.choice(returns, size=len(returns), replace=True)。同じ日が2回出ることも、一度も出ないこともあります。多くのシミュレーションを並列実行するとき、各プロセスにシード1、2、3を与えるのは、近いシードが独立とは限らないため危険です。np.random.SeedSequence(seed).spawn(k) は重ならないよう設計された k 個の子系列を作るので、それぞれから default_rng(child) を作りましょう。
data = np.array([1, 2, 3, 4, 5])
rng = np.random.default_rng(1)
print(rng.choice(data, size=len(data), replace=True).tolist()) # [3, 3, 4, 5, 1] 3 appears twice, 2 not at all
# Three independent streams from one seed, for three parallel simulations
children = np.random.SeedSequence(2026).spawn(3)
streams = [np.random.default_rng(child) for child in children]
print([stream.random(2).round(4).tolist() for stream in streams])
# [[0.3827, 0.0204], [0.6711, 0.158], [0.5508, 0.2339]]rng = np.random.default_rng(42)
print(rng.integers(1, 7, size=8).tolist()) # [1, 5, 4, 3, 3, 6, 1, 5] eight dice rolls, repeatable
rng = np.random.default_rng(42)
print(rng.uniform(-0.01, 0.01, 3).round(5).tolist()) # [0.00548, -0.00122, 0.00717]rng = np.random.default_rng(seed):同じシードなら常に同じ数。メソッドrandom・normal・choice・integersを使う- 隠れたグローバル状態
np.random.seedではなく、生成器を受け渡す choice(..., replace=True)はブートストラップ。SeedSequence(seed).spawn(k)はk個の独立ストリーム。使ったシードを記録する
次は分布からのサンプリングです。一様乱数を正規・指数・離散の抽出に変換します。
ラボ:採点される内容
make_rngで再現可能な乱数生成器を作る25点simulate_returnsで再現可能なリターンを生成する25点bootstrap_returnsで履歴をリサンプリングする25点independent_streamsで1つのシードを独立なストリームに分ける25点