データサイエンスや機械学習のプロジェクトにおいて、結果の再現性を確保することは極めて重要な要素です。
Pythonの数値計算ライブラリであるNumPyを使用する際、乱数生成の結果を固定するために「シード(種)」を設定することが一般的です。
かつては「np.random.seed」という関数が広く使われてきましたが、現在のNumPyでは新しい仕組みである「Generator」の使用が強く推奨されています。
本記事では、2026年現在の標準となっているdefault_rngを用いた、モダンで安全な乱数シードの固定方法について詳しく解説します。
なぜ乱数のシードを固定する必要があるのか
コンピュータで生成される乱数は、完全な偶然によって生み出される「真の乱数」ではなく、特定の計算式に基づいた「擬似乱数」です。
擬似乱数は初期値となる「シード」が決まれば、その後に生成される数値の順序がすべて決定されるという特性を持っています。
データ分析においてシードを固定しない場合、プログラムを実行するたびに異なる結果が得られてしまい、モデルの比較やデバッグが困難になります。
再現性を担保することは、研究の信頼性やチーム開発の効率を維持するために不可欠なプロセスです。
NumPyにおける乱数生成の変遷と現状
NumPyの乱数生成システムは、バージョン1.17を境に大きな転換期を迎えました。
それ以前は、グローバルな状態を管理する「Legacy Random Generation」が主流であり、np.random.seedやnp.random.randが多用されていました。
しかし、これらの古いAPIは、複数のライブラリが同時に使用された際に予期せぬ副作用を引き起こすリスクがありました。
現在のNumPyでは、より高速で統計的性質に優れた「PCG64」などのアルゴリズムを採用した新しいGenerator APIの使用が推奨されています。
従来のnp.random.seedが推奨されない理由
np.random.seedはNumPy全体のグローバルな乱数状態を書き換えてしまいます。
これにより、インポートした外部ライブラリが内部で乱数を使用している場合、その挙動を意図せず変化させてしまう可能性があります。
また、マルチスレッド環境での並列計算において、グローバルな状態管理は競合やパフォーマンス低下の原因となります。
最新のベストプラクティスでは、明示的にGeneratorオブジェクトを生成し、それを局所的に利用することが求められています。
推奨されるGenerator(default_rng)の使い方
現在の推奨される方法は、numpy.random.default_rngを呼び出してGeneratorインスタンスを作成することです。
この関数に整数を渡すことで、特定のシード値に基づいた乱数生成器を初期化できます。
基本的な実装方法
まずは、基本的なシードの固定方法を確認しましょう。
import numpy as np
# シード値を指定してGeneratorを作成する
# 42という数値は慣習的に使われることが多いですが、任意の値で構いません
rng = np.random.default_rng(seed=42)
# 0以上1未満の乱数を3つ生成する
random_values = rng.random(3)
print(random_values)
[0.77395605 0.43887844 0.85859792]
このように、rngというオブジェクトを介して乱数を生成します。
再度同じシード値「42」で初期化すれば、全く同じ実行結果が得られます。
各種乱数の生成例(整数・浮動小数点数)
Generatorオブジェクトは、様々な分布や形式の乱数を生成するメソッドを持っています。
整数を生成する場合はintegersメソッドを、標準正規分布に従う値を生成する場合はstandard_normalメソッドを使用します。
import numpy as np
rng = np.random.default_rng(seed=12345)
# 1から10までの整数(10を含まない)を5つ生成
integers = rng.integers(low=1, high=10, size=5)
print(f"整数乱数: {integers}")
# 標準正規分布(平均0, 標準偏差1)に従う乱数を3つ生成
normal_dist = rng.standard_normal(3)
print(f"標準正規分布: {normal_dist}")
# 配列の要素をランダムにシャッフルする
arr = np.array([1, 2, 3, 4, 5])
rng.shuffle(arr)
print(f"シャッフル後: {arr}")
整数乱数: [3 3 6 1 4]
標準正規分布: [-1.43026368 -0.26477124 -1.45524949]
シャッフル後: [4 1 5 2 3]
従来のnp.random.randintなどとはメソッド名が異なる場合があるため、移行の際は注意が必要です。
実践的なシード固定のテクニック
大規模なプログラムを開発する際には、単にスクリプトの冒頭でシードを固定するだけでは不十分な場合があります。
コードの再利用性やテストのしやすさを考慮した、高度な管理方法を学びましょう。
関数やクラス内でのGeneratorの共有
関数の中で乱数を使用する場合、外部からGeneratorオブジェクトを受け取る設計にすることが推奨されます。
これにより、関数の外部で管理されているシードの状態を維持したまま、関数内部で乱数を生成できます。
import numpy as np
def generate_sample_data(size, rng=None):
# rngが渡されない場合は、新しくGeneratorを作成する
if rng is None:
rng = np.random.default_rng()
return rng.random(size)
# 特定のシードを持つGeneratorを作成
my_rng = np.random.default_rng(seed=2026)
# 関数にGeneratorを渡して実行
data = generate_sample_data(5, rng=my_rng)
print(data)
この手法を用いることで、グローバルな状態に依存することなく、特定のシードから派生する乱数列をプログラム全体で一貫して扱うことが可能になります。
並列処理におけるシード管理(SeedSequence)
マルチコアCPUを利用した並列計算において、各プロセスに同じシードを渡してしまうと、すべてのプロセスで全く同じ乱数列が生成されるという問題が発生します。
これを避けるために、NumPyはSeedSequenceという仕組みを提供しています。
SeedSequenceを使用すると、一つの親シードから、互いに独立した複数の子シード(子Generator)を安全に生成できます。
import numpy as np
# 親となるSeedSequenceを作成
ss = np.random.SeedSequence(12345)
# 子シードを3つ生成
child_seeds = ss.spawn(3)
# それぞれの子シードからGeneratorを作成
generators = [np.random.default_rng(s) for s in child_seeds]
for i, g in enumerate(generators):
print(f"Generator {i}: {g.random()}")
このように、並列処理を行う各プロセスに対して一意のGeneratorを割り当てることが、現代的な数値計算の鉄則です。
従来手法と最新手法の比較
新旧の乱数生成方法の違いを理解するために、主要なポイントを表にまとめました。
| 特徴 | 従来手法 (np.random.seed) | 最新手法 (default_rng) |
|---|---|---|
| 管理方式 | グローバルな状態管理 | 明示的なインスタンス管理 |
| 推奨アルゴリズム | Mersenne Twister (MT19937) | PCG64 / PCG64DXSM |
| 並列計算の安全性 | 低い(重複のリスクあり) | 高い(SeedSequenceによる分離) |
| 実行速度 | 普通 | 高速 |
かつての標準であったMersenne Twisterも優れたアルゴリズムですが、PCGファミリーはよりメモリ効率が良く、統計的な欠陥も少ないことが知られています。
新規にコードを書く場合だけでなく、既存のコードをメンテナンスする際も、可能な限りGeneratorベースへのリファクタリングを検討すべきです。
Generator使用時の注意点
シードを固定していても、NumPyのバージョンが異なると、同じシード値から生成される乱数列が変化する可能性がある点に注意してください。
将来にわたって完全な同一性を保証したい場合は、NumPyのバージョンを固定(固定バージョンのライブラリを使用)することが推奨されます。
また、default_rngにシードを渡さない場合は、OSの乱数源(/dev/urandomなど)からエントロピーを取得して初期化されます。
本番環境で予測不可能な乱数が必要な場合は、シードを固定せずに利用し、実験や検証の段階では必ずシードを指定するように使い分けましょう。
まとめ
NumPyで乱数シードを固定する方法は、従来のnp.random.seedから、default_rngによるGeneratorオブジェクトの管理へと進化しました。
モダンな実装では、特定の整数をシードとしてGeneratorを作成し、そのインスタンスを通じて各メソッドを呼び出します。
この方法は、グローバルな状態を汚染せず、マルチスレッド環境や並列処理においても安全かつ高速に動作します。
再現性は科学的な分析の基盤であり、適切なシード固定の知識を持つことはデータサイエンティストにとって不可欠なスキルです。
今後NumPyを利用する際は、今回紹介したGenerator(PCG64)を積極的に活用し、堅牢で信頼性の高いコードを目指してください。
