딥러닝 모델을 제작할 때, 우리는 여러 Parameter를 가지고 있는 하나의 층을 겹겹이 쌓아 하나의 모델을 만들게 된다.
이때, 쌓는 방법과 층의 종류 등에 따라서 모델의 성능이 결정된다.
Pytorch는 우리가 좀 더 모델을 편하게 만들 수 있도록 도와주기 위해
torch.nn.Module
이라는 라이브러리를 제공한다.
nn.Module
위와 같이
torch.nn.Module
에는 다양한 함수들이 존재한다.모델을 만드는 과정을, 정말 간단하게 요약하면 다음과 같다.
1.torch.nn.Module
상속
2.__init__()
으로 클래스 초기화
3.forward()
함수 재정의즉, 위의 3과정은 반드시 해주어야 한다.
forward 작동원리
Python에는 Magic Method라고 불리는 Python 내부적으로 이미 구현된 함수가 존재한다.
(이 함수들은 언더바로 감싸져 있고__init__()
이 대표적인 예이다.)
__call__()
은 호출자 함수로, 이미 생성된 객체를 호출할 때 실행되는 함수이다.이때,
nn.Module
에서는 이__call__()
함수에forward
를 호출하도록 재정의 해 놓았기 때문에 객체를 호출할 때,foward
가 실행되게 된다.
층을 쌓기 전, 주의해야 할 부분이 있다.
다음 두 코드를 비교해 보자.
- Parameter
class Linear(nn.Module): def __init__(self, in_feat, out_feat): super().__init__() self.W = Parameter(torch.ones(out_feat, in_feat)) self.b = Parameter(torch.ones(1, out_feat))
- Tensor
class Linear(nn.Module): def __init__(self, in_feat, out_feat): super().__init__() self.W = torch.ones(out_feat, in_feat) self.b = torch.ones(1, out_feat)
위의 두 코드는 Parameter인지 Tensor인지의 차이가 있다.
먼저 Tensor로 생성하면, 손실함수를 구해 값을 Update하고자 할 때, gradient를 계산하는 함수가 존재하지 않아 이 과정이 불가능하다.
반면에 Parameter로 생성할 경우에는 gradient를 계산하는 함수가 존재하여 이 과정이 가능하다.
앞으로는 대부분
linear
나Conv2D
와 같은 내부 모델을 사용하기 때문에 이 Parameter를 직접 설정하는 경우는 거의 없다.
- nn.Module.parameters()
모듈내의 auto_grad기능을 가진 변수들의 위치를 반환하는
nn.Module
의 parameters와는 역할이 다르다는 것을 유의하자.
- torch.nn.Sequential
- 여러 Module들을 하나로 묶어 순차적으로 실행하고자 할 때 사용
class Test(nn.Module): def __init__(self): super().__init__() self.fc = nn.Sequential( nn.Linear(~), nn.MaxPool1d(~) ) def forward(self, x): x = self.fc(x) return x
- torch.nn.ModuleList
- 여러 Module들을 하나의 변수에 묶어 보관하고자 할 때 사용
- 일반 list에 보관하는 방법과는 다르게 보관하는 Module을 확인 할 수 있다.
class Test(nn.Module): def __init__(self): super().__init__() self.fc = nn.ModuleList([ nn.Linear(~), nn.MaxPool1d(~) ]) def forward(self, x): x = self.fc[0](x) x = self.fc[1](x) return x
- torch.nn.ModuleDict
- 여러 Module들을 하나의 변수에 묶어 보관하고자 할 때 사용
- MouleList와 비슷하나, dict형태라는 차이점이 있다.
class Test(nn.Module): def __init__(self): super().__init__() self.fc = nn.ModuleDict({ 'one' : nn.Linear(~), 'two' : nn.MaxPool1d(~) }) def forward(self, x): x = self.fc['one'](x) x = self.fc['two'](x) return x
2번에 따라 딥러닝 모델을 만들고 nn.Module의 여러 기능들, Container등을 활용해 우리가 원하는 Model을 구성하자.
위의 코드처럼 학습은 다음의 과정을 Epoch만큼 반복하며 진행된다.
먼저 위에서 제작한 딥러닝 모델(Class)를 사용해 객체를 생성한다.
그 후에 해당 객체를 호출할 때마다 정해진 Layer를 지나 우리가 원하는 예측값(Output)을 얻는다.
위의 Forward과정에서 얻은 예측값과 실제 정답값을 비교하여 손실함수를 구한다.
위에서 구한 손실함수를 이용해 각 Parameter들의 gradient를 얻는다.
그리고 이 gradient를 활용해 parameter들을 update한다.
아직까지는 우리가 모델을 직접 만들어 사용했기 때문에 이 부분이 잘 이해가 되지 않을 순 있다.
하지만 앞으로 우리는 대부분 다른 사람들의 모델을 참조하여 우리의 모델을 학습시킬일이 훨씬 많다.
이 경우 어떻게 모델을 분석해 볼 수 있을지 알아보자
모델의 Layer의 구성요소 출력
(nn.Module객체).named_modules()
- 모델에 속한 모든 Submodule 출력
for name, module in model.named_modules(): print(f"[ Name ] : {name}\n[ Module ]\n{module}") print("-" * 30)
(nn.Module객체).named_children()
- 모델의 바로 아래 단계의 Submodule만 출력
for name, child in model.named_children(): print(f"[ Name ] : {name}\n[ Children ]\n{child}") print("-" * 30)
(nn.Module객체).named_parameters()
- 모델에 존재하는 parameters들을 출력
for name, parameter in model.named_parameters(): print(f"[ Name ] : {name}\n[ Parameter ]\n{parameter}") print("-" * 30)
모델의 구성요소 가져오기
(nn.Module객체).get_submodule("~")
- parameter로 주어진 이름의 Submodule을 가져온다.
submodule = model.get_submodule("ab.a")
(nn.Module객체).get_parameter("~")
- parameter로 주어진 이름의 parameter를 가져온다.
parameter = model.get_parameter("ab.b.W1")
모듈의 extra출력 설정하기
- 다음과 같이 해당 모듈 안에서
extra_repr(self)
함수를 재정의 해줄 경우 extra출력을 설정할 수 있다.![]()
객체의 call(), 즉 객체를 호출하기 전과, 객체의 호출이 끝난 후 항상 실행되어야 할 함수를 설정하는 방법이다.
nn.Module을 상속받았을 경우 이 hook함수가 주어지는데 이는 다음과 같다.
register_forward_pre_hook(hook)
- 객체 호출 전 실행되는 함수.
hook(module, input)
의 prototype을 가지는 함수를 인자로 가진다.
register_forward_hook(hook)
- 객체 호출 후 실행되는 함수
hook(module, input, output)
의 prototype을 가지는 함수를 인자로 가진다.
register_full_backward_hook(hook)
- 모델의 backward작업 후 실행되는 함수
hook(module, grad_input, grad_output)
의 prototype을 가지는 함수를 인자로 가진다.
- 모델의 Module들에 Postoreder순서로 임의의 함수를 적용하는 함수로, 주로 가중치 초기화에 사용한다.
def weight_initialization(module): module_name = module.__class__.__name__ if module_name[:8] == "Function": module.W.data.fill_(1.0) returned_module = model.apply(weight_initialization)
torch.nn.Linear
: linear parameter를 가지는 layer를 만들고 싶을 때 사용
torch.nn.lazyLinear
: input의 크기를 정하지 않고, output의 크기만 정하고 싶을 때 사용
: 즉, 첫번째 forward에서 임의로 초기화 하고 그 후에 nn.Linear와 같이 동작한다.
pytorch에서는 다음과 같은 특징을 갖는다.