페이지

2022년 8월 24일 수요일

18.5 with 문을 활용한 모드 전환

 파이썬에는 with라고 하는, 후처리를 자동으로 수행하고자 할 때 사용할 수 있는 구문이 있습니다. 대표적인 예는 파일의 open과 close입니다. 예를 들어 with문 없이 파일에 무언가를 쓰려면 다음처럼 작성해야 합니다.

f = open('sample.txt''w')
f.write('hello world!')
f.close()

보다시피 open()으로 파일을 열고, 무언가를 쓰고, close()로 파일을 닫습니다. 이때 매번 close()하기란 귀찮기도 하거니와 깜빡하고 잊어버릴 때도 있습니다. with문은 이런 실수를 막아줍니다.

with open('sample.txt''w'as f:
  f.write('hello world!')

이 코드에서는 with블록에 들어갈 때 파일이 열립니다. with블록 안에서 파일은 계속 열린 상태고 블록을 빠져나올때(사용자에게 보이지 않는 곳에서) 자동으로 닫힙니다. 이와 같이 with문을 사용하는 것으로 'with 블록에 들어갈 때의 처리(후처리)'를 자동으로 할 수 있습니다.

이런한 with문의 원리를 이용하여 '역전파 비활성 모드'로 전화하려 합니다. 구체적으로 다음과 같은 식으로 사용하려 합니다(using_config 메서드의 구현은 조금 뒤에 설명합니다.).


with using_config('enable_backprop'False):
  x = Variable(np.array(2.0)
  y = square(x)

이와 같이 with using_config('enable_backprop', False): 안에서만 '역전파 비활성 모드'가 됩니다. 그리고 with블록을 벗어나면 일반 모드, 즉 '역전파 활성 모드'로 돌아갑니다.


'역전파 비활성 모드'로 일시적으로 전환하는 방법은 실전에서 자주 사용됩니다. 예컨대 신경망 학습에서는 모델 평가를 (학습 도중에)하기 위해 기울기가 필요 없는 모드를 사용하는 일이 자주 발생합니다.


그럼 with문을 사용한 모드 전환을 구현해볼까요? contextlib모듈을 사용하면 가장 쉽게 구현할 수 있습니다. 우선 contextlib모듈 사용법을 설명하겠습니다.

import contextlib

@contextlib.contextmanager
def config_test():
  print('start')      # 전처리
  try:
    yield
  finally:
    print('done')     # 후처리

with config_test():
  print('process...')


start process... done


앞의 코드처럼 @contextlib.contextmanager 데코레이터를 달면 문맥(context)을 판단하는 함수가 만들어집니다. 그리고 이 함수 안에서 yield전에는 전처리 로직은, yield 다음에는 후처리 로직을 작성합니다. 그러면 with config_test(): 형태의 구문을 사용할 수 있습니다. 이 구문을 사용하면 with블록 안으로 들어갈 때 전처리가 실행되고 블록 범위를 빠져나올 때 후처리가 실행됩니다.


with 블록 안에서예외가 발생할 수 있고, 발생한 예외는 yield를 실행하는 코드로 전달 됩니다. 따라서 yield는 try/finaliy로 감싸야 합니다.


이상을 바탕으로 using_config 함수를 다음과 같이 구현할 수 있습니다.

import contextlib

@contextlib.contextmanager
def using_config(namevalue):
  old_value = getattr(Config, name)
  setattr(Config, name, value)
  try:
    yield
  finally:
    setattr(Config, name, old_value)

using_config(name, value)의 인수 중 name은 타입이 str이며, 사용할 Config속성의 이름(클래스 속성 이름)을 가리킵니다. 그리고 nmae을 getattr 함수에 넘겨 Config클래스에서 커내옵니다. 그런 다음 setattr함수를 사용하여 새로운 값을 설정합니다.

이제 with 블록에 들어갈 때 name으로 지정한 Config 크래스 속성이 value로 설정됩니다. 그리고 with블록에 빠져나오면서 원래 값(old_value)으로 복원됩니다. 그럼 using_config 함수를 실제로 사용해보죠.

with using_config('enable_backprop'False):
  x = Variable(np.array(2.0))
  y = square(x)

이와 같이 역전파가 필요 없는 경우에는 with 블록에서 순전파 코드만 실행합니다. 이제 불플요한 계산을 생략하고 메모리를 절약할 수 있습니다. 그러나 with using_config('enable_backprop', False): 라는 긴 코드를 매번 적어주기는 귀찮은 일이니 다음과 같이 no_grad라는 편의 함수를 준비했습니다.

def no_grad():
  return using_config('enable_backprop'False)

with no_grad():
  x = Variable(np.array(2.0))
  y = square(x)

no_grad 함수는 단순히 using_conig('enable_backprop', False)를 호출하는 코드를 return으로 돌려줄 뿐입니다. 이제 기울기가 필요 없을 때는 no_grad함수를 호출하면 됩니다.

이상으로 이번 단계를 마칩니다. 앞으로는 기울기 계산이 필요 없을 때, 즉 단순히 순전파 계산만 필요할 때는 방금 구현한 '모드 전환'을 사용하겠 습니다.


댓글 없음: