DeepLearning/Pytorch

[Pytorch] 실험 재현(reproducibility)을 위한 실험 환경 randomness 제어하기

yooj_lee 2021. 11. 17. 19:22
300x250

실험을 여러번 했을 때 똑같은 결과를 보장하는 것을 '재현성(reproducibility)' 라고 한다.
파이토치에서 실험 재현성을 보장하기 위해서 해주어야 하는 사전 절차는 다음과 같다.

 

reproducibility

실험 재현성을 위해서 제어해야할 randomness는 다음과 같다.

1. torch의 randomness

2. torch.cuda의 randomness
- multi-gpu를 사용할 경우에는 manual_seed_all로 제어해줘야 함
- pytorch document에 의하면 torch.manual_seed로 모든 device에서의 randomness를 제어 가능한 것으로 나오지만 다른 코드를 더 찾아보니 이 부분도 제어해주기에 추가

3. torch.backends.cudnn (benchmark, deterministic)
- cuDNN convolution 연산이 새로운 사이즈 파라미터 세트와 함께 호출될 때, 여러 개의 convolution 연산을 수행해서 가장 빠른 convolution 연산을 찾는 과정이 동반될 수 있음. (이후 corresponding set of size parameters에서는 탐색된 가장 빠른 conv 연산 알고리즘을 벤치마킹하게 됨)
- 이러한 벤치마킹 과정 속에서 발생하는 noise 등으로 인해서 동일한 머신에서 연산을 수행한다고 하더라도 이후 실행할 때에 다른 알고리즘을 benchmarking하게 될 수 있음.
- 이러한 부분을 방지하기 위해 benchmarking을 막아주는 옵션이 필요한데, 이게 torch.backends.cudnn.benchmark 옵션과 연관.
- torch.backends.cudnn.deterministic의 경우에는 파이토치 내의 nondeterministic한 연산(torch.bmm과 같은 연산)들을 deterministic한 연산으로 대체하도록 하는 옵션임 (가능할 경우에만, 불가능하다면 에러를 리턴하게 됨).

4. np.random.seed

- numpy에 의존하는 라이브러리를 하나라도 사용한다면 numpy의 random seed를 고정해줘야함.

5. python random seed

- custom operator를 사용할 수도 있으므로 python random seed도 고정해주는 것이 안전.

6. dataloader

- dataloader의 경우에는 single core cpu를 사용하는 경우에는 상관이 없지만 cpu의 num workers를 여러 개로 지정하여 멀티 프로세스 데이터 로딩을 해주는 경우에는 randomness를 제어해줘야 함.
- worker_init_fn()generator를 사용해서 randomness를 제어해줌.

 

code

random seed를 고정해주는 코드는 아래와 같음.

def fix_seed(random_seed):
    """
    fix seed to control any randomness from a code 
    (enable stability of the experiments' results.)
    """
    torch.manual_seed(random_seed)
    torch.cuda.manual_seed(random_seed)
    torch.cuda.manual_seed_all(random_seed)  # if use multi-GPU
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False
    np.random.seed(random_seed)
    random.seed(random_seed)

아래는 dataloader에서 num_worker를 여러 개 지정하여 멀티 프로세싱할 경우

def seed_worker(worker_id):
    worker_seed = torch.initial_seed() % 2**32
    numpy.random.seed(worker_seed)
    random.seed(worker_seed)

g = torch.Generator()
g.manual_seed(0)

DataLoader(
    train_dataset,
    batch_size=batch_size,
    num_workers=num_workers,
    worker_init_fn=seed_worker,
    generator=g,
)

 

References

  1. https://pytorch.org/docs/stable/notes/randomness.html
  2. https://hoya012.github.io/blog/reproducible_pytorch/
300x250