[AITech] 20220126 - Custom Model 개발하기
학습 내용 정리
Custom Model 개발하기
Basic Operations
-
torch.tensor와 torch.Tensor의 차이
-
텐서 인덱싱
-
index_select(tensor, axis, indices: 1D tensor)
- axis(dim)은 이동하는 방향이 아니라 선택 후보의 방향이다!
A = torch.Tensor([[1, 2], [3, 4]]) indices = torch.tensor([0]) output = torch.squeeze(torch.index_select(A, 1, indices)) print(output) # tensor([1., 3.]) -
numpy style indexing
-
-
해당 인덱스의 값 모으기
-
gather(tensor, axis, indices_tensor: index-value tensor)
- 예
A = torch.Tensor([[1,2], [3,4]])
torch.gather(A, dim=0, index=torch.tensor([[0,1], [1,1]])) 이라면,
각 인덱스에 대해 차례로
[0][0] => A[index[0][0]][0] = A[0][0] = 1
[0][1] => A[index[0][1]][1] = A[1][1] = 4
[1][0] => A[index[1][0]][0] = A[1][0] = 3
[1][1] => A[index[1][1]][1] = A[1][1] = 4
즉, dim에 해당하는 차원의 인덱스 값은 index로 전달된 텐서의 값으로 대체되고, 나머지 인덱스 값은 index의 위치값을 그대로 유지합니다.
-
-
자료구조 확인
- is_tensor, is_storage, is_complex, …
- numel(tensor) :원소의 개수 반환
-
Tensor 함수
- Creating
- from_numpy(ndarray)
- zeros(*shape)
- zeros_like(tensor)
- full(shape, value)
- Tensor.expand(*size)
- 셀프 broadcasting을 시켜주는 함수로 볼 수 있음.
- Indexing, Slicing, Joining, Mutating
- Random sampling
- normal(mean: tensor, std: tensor)/ normal(mean: float, std: float, size)
- mean, std를 tensor 타입으로 전달할 경우 output tensor의 각 원소의 mean, std를 각각 지정
- mean, std를 float 타입으로 전달할 경우 output tensor의 전체 원소의 mean, std를 지정하고 size를 전달
- rand(*size)
- 0~1 사이 랜덤한 값을 uniform distribution으로 생성
- randint(low, high, size)
- low~high 사이 랜덤한 정수 값을 생성
- randn(*size)
- mean=0, std=1 인 정규분포를 따르는 랜덤한 값을 생성
- normal(mean: tensor, std: tensor)/ normal(mean: float, std: float, size)
- Math operations - Pointwise ops
- abs(tensor)
- add(input: tensor, other: tensor or number, alpha: number)
- addcdiv(input, tensor1, tensor2, value)
- addcmul(input, tensor1, tensor2, value)
- Reduction ops
- Math operations - Comparison ops
- allclose(input, other, rtol, atol)
- argsort(input, dim, descending=False)
- eq(input, other), equal(input, other)
- elementwise equality: tensor VS tensor equality: bool
- Math operations - Other ops
- einsum(equation, *operands)
- einstein summation의 약자
- atleast_1d(input: tensor or a list of tensors), atleast_2d(input: tensor or a list of tensors), atleast_3d(input: tensor or a list of tensors)
- broadcast_tensors(*tensors), broadcast_to(input: tensor, shape)
- einsum(equation, *operands)
- Math operations - BLAS and LAPACK ops
- 선형대수학과 연관된 함수
- BLAS: Basic Linear Algebra Subprograms
- LAPACK: Linear Algebra PACKage
- 현재는 torch.linalg 모듈의 함수들을 사용할 것이 권장됨
- Creating
-
- 선형대수학 관련 함수들을 모아놓은 모듈
-
- 딥러닝 모델을 만들기 위한 Basic Building Block들을 미리 만들어 놓은 모듈
- Linear와 LazyLinear의 차이는?
- LazyLinear는 아직 in_features가 결정되지 않은 Linear layer입니다. 최초 실행 시 in_features가 결정되면서 Linear와 동일하게 동작합니다.
nn.Module
파이토치의 nn.Module 클래스는 여러 기능들을 한 곳에 모아놓는 상자의 역할을 합니다.
nn.Module 이라는 상자는 또 다른 nn.Module 상자를 포함할 수도 있으며, 어떻게 사용하느냐에 따라 다른 의미를 가집니다.
nn.Module이라는 상자에기능들을 가득 모아놓은 경우basic building blocknn.Module이라는 상자에basic building block인nn.Module들을 가득 모아놓은 경우딥러닝 모델nn.Module이라는 상자에딥러닝 모델인nn.Module들을 가득 모아놓은 경우더욱 큰 딥러닝 모델
-
Containers
-
nn.Module 블록들을 묶어서 관리하는 클래스들
-
- 순차적으로 forward를 실행할 때 사용
class Add(nn.Module): def __init__(self, value): super().__init__() self.value = value def forward(self, x): return x + self.value # y = x + 3 + 2 + 5 from collections import OrderedDict calculator = nn.Sequential(OrderedDict([ ('plus1', Add(3)), ('plus2', Add(2)), ('plus3', Add(5)) ])) x = torch.tensor([1]) output = calculator(x) -
- 모듈들을 모아놓고 사용하고 싶은 모듈만 선택해서 사용
- 인덱스로 관리
class Add(nn.Module): def __init__(self, value): super().__init__() self.value = value def forward(self, x): return x + self.value class Calculator(nn.Module): def __init__(self): super().__init__() self.add_list = nn.ModuleList([Add(2), Add(3), Add(5)]) def forward(self, x): # y = ((x + 3) + 2) + 5 x = self.add_list[1](x) x = self.add_list[0](x) x = self.add_list[2](x) return x x = torch.tensor([1]) calculator = Calculator() output = calculator(x) -
- key 값을 이용해 모듈들을 관리할 때 사용
class Add(nn.Module): def __init__(self, value): super().__init__() self.value = value def forward(self, x): return x + self.value class Calculator(nn.Module): def __init__(self): super().__init__() self.add_dict = nn.ModuleDict({'add2': Add(2), 'add3': Add(3), 'add5': Add(5)}) def forward(self, x): # y = ((x + 3) + 2) + 5 x = self.add_dict['add3'](x) x = self.add_dict['add2'](x) x = self.add_dict['add5'](x) return x x = torch.tensor([1]) calculator = Calculator() output = calculator(x)
-
-
Parameters: nn.parameter.Parameter
- nn.parameter.Parameter(data, requires_grad=True)
- self.register_parameter(name, tensor): 모듈에 새로운 파라미터 등록
class Linear(nn.Module): def __init__(self, in_features, out_features): super().__init__() self.W = Parameter(torch.ones(out_features, in_features)) self.b = Parameter(torch.ones(out_features)) def forward(self, x): output = torch.addmm(self.b, x, self.W.T) return output-
Tensor 대신 Parameter를 사용하는 이유!!
-
Tensor는 모델 저장 시 저장되지 않는다!
-
Gradient가 계산되지 않는다!
-
혹시 갱신되지는 않지만 저장하고 싶은 Tensor 값이 있다면, buffer에 tensor를 등록한다!
- self.register_buffer(name, tensor, persistent=True)
- 위에서 self는 nn.Module을 상속받은 커스텀 모델 클래스
-
nn.Module 분석하기
-
SubModule
- SubModule 표시하기: named_modules() VS named_children()
- named_modules(): 자신에 속하는 전체 하위 모듈을 표시
- named_children(): 하나 아래 단계의 하위 모듈까지만 표시
- 이름 없이 그냥 모듈만 가져올 경우 modules(), children() 사용
- SubModule 가져오기: get_submodule(target_name)
- SubModule 표시하기: named_modules() VS named_children()
-
Parameter
- Parameter 표시하기: parameters(), named_parameters()
- Parameter 가져오기: get_parameter(target_name)
-
Buffer
- Buffer 표시하기: buffers(), named_buffers()
- Buffer 가져오기: get_buffer(target_name)
-
같은 레벨(함수 레벨, basic block 레벨 등)에서는 서로의 참조를 허용하지 않는다!!!
class Function_C(nn.Module): def __init__(self): super().__init__() self.register_buffer('duck', torch.Tensor([7]), persistent=True) def forward(self, x): x = x * self.duck return x class Function_D(nn.Module): def __init__(self): super().__init__() self.W1 = Parameter(torch.Tensor([3])) self.W2 = Parameter(torch.Tensor([5])) # self.c = Function_C() def forward(self, x): x = x + self.W1 x = Function_C().forward(x) # self.c(x) x = x / self.W2 return x -
모듈의 이름(이를 repr이라 함)을 재설정 해주고 싶다면, 해당 모듈 내에서
extra_repr(self): return ' '을 오버라이딩 해준다. -
- Docstring은 함수 또는 클래스의 맨 위에 해당 함수/클래스에 대한 정보(파라미터, 반환값 등)를 적시하는 것으로, 코멘트(주석)와는 다르다.
__doc__프로퍼티로 module에 대한 docstring을 볼 수 있다.help(module)로 module에 대한 더 자세한 설명(메서드, 프로퍼티 등)을 볼 수 있다.- Documentation이 없는 모델이라면 Docstring을 Documentation처럼 여기고 꼼꼼히 보아야 한다.
def string_reverse(str1): ''' Returns the reversed String. Parameters: str1 (str):The string which is to be reversed. Returns: reverse(str1):The string which gets reversed. ''' reverse_str1 = '' i = len(str1) while i > 0: reverse_str1 += str1[i - 1] i = i- 1 return reverse_str1
nn.Module 더 알아보기
-
hook
- hook은 패키지화된 다른 코드에서 다른 프로그래머가 custom 코드를 중간에 실행시킬 수 있도록 만들어놓은 인터페이스입니다.
- 프로그램의 실행 로직을 분석하거나,
- 프로그램에 추가적인 기능을 제공하고 싶을 때
- 사용합니다.
- 기본적으로 hook은 아래와 같이 동작합니다.
class Package(object): """프로그램 A와 B를 묶어놓은 패키지 코드""" def __init__(self): self.programs = [program_A, program_B] self.hooks = [] def __call__(self, x): for program in self.programs: x = program(x) # Package를 사용하는 사람이 자신만의 custom program을 # 등록할 수 있도록 미리 만들어놓은 인터페이스 hook if self.hooks: for hook in self.hooks: output = hook(x) # return 값이 있는 hook의 경우에만 x를 업데이트 한다 if output: x = output return x- hook을 어디에 심어놓을 지는 package를 설계하는 설계자에게 달려있습니다.
- Tensor의 hook
- Tensor의 hook에는
tensor._backward_hooks만이 존재하고, 등록은 tensor.register_hook(hook)을 사용하여 할 수 있습니다.
- Tensor의 hook에는
- Module의 hook
- register_forward_pre_hook(hook)
- register_forward_hook(hook)
- register_full_backward_hook(hook)
- module의
__dict__attribute에서 parameter, hook 등을 모두 볼 수 있습니다.- backward_hooks의 경우 full_backward_hooks의 전신으로 현재 deprecated 상태이고, state_dict_hooks의 경우 모듈이 내부적으로 사용하는 hook입니다.
- PyTorch hooks 사용 사례 보기
- gradient의 값의 변화를 시각화
- gradient값이 특정 임곗값을 넘으면 gradient exploding 경고 알림
- 특정 tensor의 gradient 값이 너무 커지거나 작아지는 현상이 관측되면 해당 tensor 한정으로 gradient clipping
- hook은 패키지화된 다른 코드에서 다른 프로그래머가 custom 코드를 중간에 실행시킬 수 있도록 만들어놓은 인터페이스입니다.
-
apply -> applied module
-
모델에 custom 함수를 적용시켜 그 하위 모듈들에도 모두 적용되도록 하고 싶을 때 사용합니다.
-
apply를 통해서 적용하는 함수는 module을 입력으로 받으며, 모델의 모든 module들을 순차적으로 입력받아 처리합니다.
-
주로 가중치 초기화에 많이 사용됩니다.
-
apply는 Postorder Traversal 방식(후위탐색)으로 함수를 module에 적용합니다.

-
How to initialize weights in PyTorch?
def init_weights(m): if isinstance(m, nn.Linear): torch.nn.init.xavier_uniform(m.weight) m.bias.data.fill_(0.01) net = nn.Sequential(nn.Linear(2, 2), nn.Linear(2, 2)) net.apply(init_weights)
-
Leave a comment