훅
훅은 패키징 코드 중간에 원하는 코드를 삽입할 수 있는 기능입니다.
정방향 전파 후 모델 가중치 변경, 실시간 매개변수 업데이트 확인 등
취향에 따라 원하는 모델을 일부 변경할 수 있습니다.
후크는 크게 텐서에 적용되는 후크와 모듈에 적용되는 후크로 나눌 수 있습니다.
텐서
텐서에는 전방 후크가 없으며 후방 후크만 사용할 수 있습니다.
import torch
tensor = torch.rand(1, requires_grad=True)
def tensor_hook(grad):
pass
tensor.register_hook(tensor_hook)
# 🦆 tensor는 backward hook만 있어요!
tensor._backward_hooks
기준 치수
모듈에 적용되는 후크에는 forward_pre_hook, forward_hook 및 full_backward_hook가 포함됩니다.
다른 backward_hook 및 state_dict_hook가 있지만
Backward_hook는 누락된 기능이고 state_dict_hook는 load_state_dict 함수에서 사용하지만 사용자가 사용하지 않는 기능입니다.
모델에 등록된 Hook은 __dict__를 통해 확인할 수 있습니다.
일반적인 실행 순서를 이해하지 못해서 찾아봤습니다.
forward_pre_hook 앞으로forward_hook뒤로full_backward_hook
의 순서대로 간다
Hook 실행 순서를 자세히 알고 싶다면 nn.Module의 소스 코드를 읽어보세요.
https://github.com/pytorch/pytorch/blob/master/torch/nn/modules/module.py
함수는 __call__에 의해 실행되기 때문에 _call_impl이라는 함수를 보면
코드에서 후크의 순서를 이해할 수 있습니다.
forward_pre_hook
정방향 실행 전에 실행되는 후크입니다.
import torch
from torch import nn
# Add 모델을 수정하지 마세요!
class Add(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x1, x2):
output = torch.add(x1, x2)
return output
# 모델 생성
add = Add()
# TODO: 답을 x1, x2, output 순서로 list에 차례차례 넣으세요!
answer = ()
# TODO : pre_hook를 이용해서 x1, x2 값을 알아내 answer에 저장하세요
def pre_hook(module, input):
answer.append(input(0))
answer.append(input(1))
# return input(0), input(1)
# 아래 코드는 수정하실 필요가 없습니다!
x1 = torch.rand(1)
x2 = torch.rand(1)
add.register_forward_pre_hook(pre_hook)
output = add(x1, x2)
pre_hook(module, input)의 입력값으로 forward 함수를 통해 받은 x1, x2가 튜플 형태로 저장된다.
여기서 모델의 입력은 반환 값으로 수정할 수 있습니다.
pre_hook를 등록하려면 모델을 빌드한 후 등록 기능을 사용하십시오.
앞으로 후크
포워드 이후에 실행되는 훅입니다.
import torch
from torch import nn
# Add 모델을 수정하지 마세요!
class Add(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x1, x2):
output = torch.add(x1, x2)
return output
# 모델 생성
add = Add()
# TODO : hook를 이용해서 전파되는 output 값에 5를 더해보세요!
def hook(module, input, output):
return output + 5
add.register_forward_hook(hook)
# 아래 코드는 수정하실 필요가 없습니다!
x1 = torch.rand(1)
x2 = torch.rand(1)
output = add(x1, x2)
Hook(모듈, 입력, 출력)은 튜플 형태로 전방에서 받은 인자 값을 입력으로 하고 전방 전파의 결과 값을 출력으로 갖는다.
모델에서는 결과를 저장하기 위해 먼저 forward를 실행하고, forward_hook에서 반환 값이 있으면 forward_hook에서 반환 값으로 결과를 수정합니다.
따라서 hook에서 입력과 출력을 모두 변경할 수 있지만 변경된 입력은 포워더에 적용되지 않습니다.
_call_impl에서 내용을 확인할 수 있습니다.
full_backward_hook
이 후크는 입력에 대한 그래디언트가 계산될 때마다 호출됩니다.
import torch
from torch import nn
from torch.nn.parameter import Parameter
# Model 모델을 수정하지 마세요!
class Model(nn.Module):
def __init__(self):
super().__init__()
self.W = Parameter(torch.Tensor((5)))
def forward(self, x1, x2):
output = x1 * x2
output = output * self.W
return output
# 모델 생성
model = Model()
# TODO: 답을 x1.grad, x2.grad, output.grad 순서로 list에 차례차례 넣으세요!
answer = ()
# TODO : hook를 이용해서 x1.grad, x2.grad, output.grad 값을 알아내 answer에 저장하세요
def module_hook(module, grad_input, grad_output):
answer.append(grad_input(0))
answer.append(grad_input(1))
answer.append(grad_output(0))
model.register_full_backward_hook(module_hook)
# 아래 코드는 수정하실 필요가 없습니다!
x1 = torch.rand(1, requires_grad=True)
x2 = torch.rand(1, requires_grad=True)
output = model(x1, x2)
output.retain_grad()
output.backward()
module_hook에(모듈, grad_input, grad_output) 정도_입력이전에 얻은 factor 값에 대한 그래디언트를 튜플 형태로 저장합니다.
grade_output정방향 전파 출력에 대한 기울기를 포함하므로 1이 되고 튜플로 저장됩니다.
grad_output 또는 입력 및 출력은 편집할 수 없습니다.
grad_input 을 사용하는 대신 새 그래디언트를 반환 값으로 지정할수있다.
그러나 디버깅 이외의 상황에서는 grad_input의 의미가 왜곡되므로 권장하지 않습니다.
import torch
from torch import nn
from torch.nn.parameter import Parameter
class Model(nn.Module):
def __init__(self):
super().__init__()
self.W = Parameter(torch.Tensor((5)))
def forward(self, x1, x2):
output = x1 * x2
output = output * self.W
return output
# 모델 생성
model = Model()
# TODO : hook를 이용해서 module의 gradient 출력의 합이 1이 되도록 하세요!
# ex) (1.5, 0.5) -> (0.75, 0.25)
def module_hook(module, grad_input, grad_output):
grad_input = tuple(map(lambda x: x/sum(grad_input), grad_input))
return grad_input
model.register_full_backward_hook(module_hook)
# 아래 코드는 수정하실 필요가 없습니다!
x1 = torch.rand(1, requires_grad=True)
x2 = torch.rand(1, requires_grad=True)
output = model(x1, x2)
output.backward()
# x1.grad + x2.grad == 1
full_backward_hook에 의해 합이 1로 표준화된 grad_input을 반환하면 x1과 x2의 그래디언트 합이 1이 됩니다.
full_backward_hook에서는 입력(forward factor)과 출력 기울기 값만 알 수 있으며,
모델 내 텐서의 기울기 값은 알 수 없습니다.
모델 내 매개변수의 기울기를 알고 싶다면 텐서 후크를 사용할 수 있습니다.
import torch
from torch import nn
from torch.nn.parameter import Parameter
class Model(nn.Module):
def __init__(self):
super().__init__()
self.W = Parameter(torch.Tensor((5)))
def forward(self, x1, x2):
output = x1 * x2
output = output * self.W
return output
# 모델 생성
model = Model()
# TODO : hook를 이용해서 W의 gradient 값을 알아내 answer에 저장하세요
def tensor_hook(grad):
print(grad)
model.W.register_hook(tensor_hook)
# 아래 코드는 수정하실 필요가 없습니다!
x1 = torch.rand(1, requires_grad=True)
x2 = torch.rand(1, requires_grad=True)
output = model(x1, x2)
output.backward()