(Pytorch 기본) 훅(Forward Hook, Forward Hook, Backward Hook)

훅은 패키징 코드 중간에 원하는 코드를 삽입할 수 있는 기능입니다.

정방향 전파 후 모델 가중치 변경, 실시간 매개변수 업데이트 확인 등

취향에 따라 원하는 모델을 일부 변경할 수 있습니다.

후크는 크게 텐서에 적용되는 후크와 모듈에 적용되는 후크로 나눌 수 있습니다.

텐서

텐서에는 전방 후크가 없으며 후방 후크만 사용할 수 있습니다.

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

GitHub – pytorch/pytorch: 강력한 GPU 가속 기능을 갖춘 Python의 Tensor 및 동적 신경망

강력한 GPU 가속 기능을 갖춘 Python의 텐서 및 동적 신경망 – GitHub – pytorch/pytorch: 강력한 GPU 가속 기능을 갖춘 Python의 텐서 및 동적 신경망

github.com

함수는 __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()