Python 3.13までに学んでおきたい疑似乱数の話

ヘビーなPythonユーザなら把握していると思うが,次バージョンでGIL解消のオプションが入る。現時点ではリリース時に複数のバイナリが出てくるのか実行時オプションになるのか実際に出てみないことには分からない。一番可能性が高いのは現状アルファ版と同じって期待だろう。

 

peps.python.org

 

で,当然並行処理関係でPythonを避けていた問題が一気に解消することになる。Web系はもちろん,機械学習分野においても苦心の策が無用になることも多くあるだろう。

その辺は横においておいて,今日は疑似乱数の話をメモしておく。

乱数と疑似乱数の差が分からない人は一旦他で学んできた方がいい。

 

簡単に言うと乱数は文字通りランダムな数なので予測も再現も不可能なものである。しかしながら計算機で扱う乱数はほぼ全て疑似乱数であり,何らかの計算で得られる数値でしかない。そのためアルゴリズムと初期値が分かれば100%再現可能である。これが本件の背景。

 

GILと何が関係あるかと言えば並行処理である。疑似乱数は生成アルゴリズムに基づき数列を作るがその数列を取り出す順序が異なれば再現しない。これは並行処理で複数のスレッド(プロセスでも軽量スレッドでも)から呼び出される疑似乱数では問題となる。

では,どうして対応するかと言えば例えばGoogleが管理している並列計算ライブラリのJAXがどうしているか見てみよう。

GitHub - google/jax: Composable transformations of Python+NumPy programs: differentiate, vectorize, JIT to GPU/TPU, and more

pmapを使うサンプルコードを見ると

keys = random.split(random.PRNGKey(0), 8)
mats = pmap(lambda key: random.normal(key, (5000, 6000)))(keys)

このようなコードがあり,乱数のキーを生成し並行処理するプロセス毎にキーを渡している。これにより乱数の種(seed)が各プロセス毎に固定される。

 

では,JAX以外でどうすればいいか。

Python標準のrandomならrandomクラスを継承した乱数生成器を並行処理するスレッド数用意すればいい。

numpyならGeneratorをスレッド数用意すればいい。

Random Generator — NumPy v1.26 Manual

ということになる。

親で生成して渡してもいいし,seed値だけ渡して子スレッドで生成してもseedが再現できれば再現性は問題ないだろう。

もちろんnumpyを使うのがお勧めである。

 

もちろん再現性気にしない勢はお好きにどうぞ。

未対応な高級ライブラリ勢は3.13リリース前後ですぐに対応するものと思う。