저는 오늘 PyTorch를 사용해 보다가, requires_grad=False임에도 불구하고 출력에 0과 2가 섞이는 것을 확인했습니다. 입력은 모두 1이었고, Linear 레이어의 가중치는 단위 행렬이었습니다. 저는 inference_mode()를 사용하여 Dropout까지도 중지시킬 수 있을 것이라고 생각했었죠.
일반적인 평가(evaluation) 및 추론 시에는 model.eval()과 torch.inference_mode()를 모두 사용해야 합니다. 단순히 기울기 기록을 하지 않도록 설정하는 것만으로는 학습 과정에서 사용된 Dropout이 남아있게 됩니다.
저는 macOS, CPython 3.12.13, PyTorch 2.12.0의 CPU 환경에서 실행했습니다. 외부 통신은 필요하지 않습니다.
from contextlib import nullcontext
import torch
torch.manual_seed(0)
...
열은 순서대로 평가 모드, 추론 모드, 기울기 필요 여부, 출력 값을 나타냅니다.
False False True [0.0, 2.0]
False True False [0.0, 2.0]
True False True [1.0]
...
2번째 줄에서는 기울기 기록은 중지되었습니다. 그럼에도 불구하고 Dropout이 작동합니다. 3번째 줄에서는 반대로 Dropout이 중지되었고, Linear 레이어의 파라미터를 사용하는 계산은 기울기 기록 대상이 됩니다.
Linear 레이어를 사이에 넣은 이유는 가중치에 대한 기울기도 확인하고 싶었기 때문입니다. Dropout만 사용하면 평가 시 입력값을 그대로 반환하며, 입력값의 requires_grad를 상속받습니다.
eval()은 train(False)를 호출하여 하위 모듈에도 재귀적으로 training=False를 설정합니다. 동일한 버전의 Module 구현체로 확인했습니다.
nn.Dropout은 해당 self.training 상태를 함수형 Dropout에 전달합니다. 학습 시에는 확률 0.5로 요소를 0으로 만들고, 나머지는 1 / (1 - 0.5)배 합니다. 그래서 이번 출력은 0과 2가 나온 것입니다. 평가 시에는 입력값을 그대로 통과시키므로, 단위 행렬을 거친 1이 남게 됩니다.
반면, inference_mode()는 자동 미분 기록 및 관리에 필요한 수고를 줄이기 위한 설정입니다. 모듈의 training 상태는 변경하지 않습니다. 같은 버전의 설명에도 다음과 같은 주의사항이 있습니다.
does NOT automatically set the model to evaluation mode.
이 두 가지는 별개로 설정해야 합니다.
학습 후에는 model.eval()로 전환하고, forward 함수를 torch.inference_mode() 안에서 호출합니다. 이것이 마지막 조합입니다. requires_grad=False만 테스트하는 경우, 2번째 줄도 통과시켜 버립니다.
seed 고정 역시 평가 모드 확인에는 사용하지 않습니다. 매번 seed를 되돌리면 학습 모드에서도 동일한 마스크를 적용할 수 있습니다. 출력값의 일치만으로는 Dropout이 중지되었는지 알 수 없습니다. 이번에는 값이 1인 상태로 통과하는 것도 확실하게 했습니다. 0과 2가 모두 나오는 assert는 이 seed와 크기에서의 재현을 위한 것입니다.
불확실성 추정(Uncertainty Estimation)에서는 추론 중에도 Dropout을 사용하는 설계가 있습니다. eval()조차도 모델 전체의 완전한 재현성을 보장하지 않습니다.
일반적인 추론이라면, 먼저 평가 모드를 설정하고 기울기 기록을 중지해야 합니다. 평가 시 동작이 달라지는 레이어는 그 출력값도 테스트해야 합니다. 모델이나 래퍼(wrapper)를 업데이트할 때도 실행해 본다면, 설정 누락을 잡아낼 수 있습니다. 추론의 수용 조건에 requires_grad와 레이어의 동작 두 가지를 모두 포함시켜야 합니다.