0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python・NumPy・PyTorchの乱数seedをまとめて固定する

0
Posted at

機械学習やシミュレーションでは、同じコードを実行しているのに結果が毎回変わることがあります。

原因の一つが、Python・NumPy・PyTorchで別々の乱数生成器が使われていることです。

そこで、各ライブラリのseedをまとめて固定する関数を作ります。

実行環境

  • Python 3
  • NumPy
  • PyTorch
pip install numpy torch

seedをまとめて固定する関数

import random

import numpy as np
import torch


def seed_everything(
    seed: int = 42,
    deterministic: bool = False,
) -> np.random.Generator:
    """Python・NumPy・PyTorchの乱数seedを固定する。"""

    # Python標準ライブラリ
    random.seed(seed)

    # NumPyの従来API
    np.random.seed(seed)

    # PyTorch
    torch.manual_seed(seed)

    # CUDAを使用する場合
    if torch.cuda.is_available():
        torch.cuda.manual_seed(seed)
        torch.cuda.manual_seed_all(seed)

    # 再現性をさらに優先する場合
    if deterministic:
        torch.use_deterministic_algorithms(True, warn_only=True)

        if torch.backends.cudnn.is_available():
            torch.backends.cudnn.benchmark = False
            torch.backends.cudnn.deterministic = True

    # NumPyの新しいGenerator API
    return np.random.default_rng(seed)

使用例です。

def generate_values(seed: int) -> dict[str, float]:
    rng = seed_everything(seed)

    return {
        "Python": random.random(),
        "NumPy従来API": np.random.rand(),
        "NumPy Generator": rng.random(),
        "PyTorch": torch.rand(1).item(),
    }


first = generate_values(42)
second = generate_values(42)

print(first)
print(second)
print(first == second)

実行結果の例です。

{'Python': 0.6394267984578837, ...}
{'Python': 0.6394267984578837, ...}
True

同じseedを指定したため、2回とも同じ乱数列が生成されます。

np.random.seed()とdefault_rng()の両方を使う理由

NumPyには、大きく分けて2種類の乱数生成方法があります。

従来の書き方です。

np.random.seed(42)
value = np.random.rand()

現在推奨されているGeneratorを使う書き方です。

rng = np.random.default_rng(42)
value = rng.random()

既存コードや外部ライブラリが従来APIを使っている可能性があるため、今回は両方に対応しています。

新しくコードを書く場合は、乱数生成器を明示的に渡せるdefault_rng()のほうが扱いやすいです。

def create_noise(
    rng: np.random.Generator,
    size: int,
) -> np.ndarray:
    return rng.normal(size=size)


rng = np.random.default_rng(42)
noise = create_noise(rng, size=5)

print(noise)

完全に同じ結果になるとは限らない

seedを固定しても、次の条件が変わると結果が一致しない場合があります。

  • Pythonやライブラリのバージョン
  • CPUとGPUの違い
  • GPUの種類
  • 並列処理の実行順序
  • 非決定的なアルゴリズムの使用

そのため、実験結果を保存するときはseedだけでなく、ライブラリのバージョンや実行環境も記録しておくと安全です。

import platform

print("Python:", platform.python_version())
print("NumPy:", np.__version__)
print("PyTorch:", torch.__version__)
print("CUDA:", torch.version.cuda)

まとめ

複数のライブラリを使う実験では、一つのseedだけを設定したつもりでも、別の乱数生成器が固定されていないことがあります。

最初にseed_everything()を呼び出す形にしておけば、seedの設定漏れを減らせます。

rng = seed_everything(42)

小規模な検証コードでも、あとから結果を再確認できるよう、seedを明示しておくことをおすすめします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?