[AITech] 20220126 - Custom Model 개발하기

5 minute read


학습 내용 정리

Custom Model 개발하기

Basic Operations


nn.Module

파이토치의 nn.Module 클래스는 여러 기능들을 한 곳에 모아놓는 상자의 역할을 합니다.

nn.Module 이라는 상자는 또 다른 nn.Module 상자를 포함할 수도 있으며, 어떻게 사용하느냐에 따라 다른 의미를 가집니다.

  • nn.Module이라는 상자에 기능들을 가득 모아놓은 경우 basic building block
  • nn.Module이라는 상자에 basic building blocknn.Module들을 가득 모아놓은 경우 딥러닝 모델
  • nn.Module이라는 상자에 딥러닝 모델nn.Module들을 가득 모아놓은 경우 더욱 큰 딥러닝 모델


  • Containers

    • nn.Module 블록들을 묶어서 관리하는 클래스들

    • nn.Sequential

      • 순차적으로 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)
      
    • nn.ModuleList

      • 모듈들을 모아놓고 사용하고 싶은 모듈만 선택해서 사용
      • 인덱스로 관리
      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)
      
    • nn.ModuleDict

      • 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

    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를 등록한다!

nn.Module 분석하기

  • SubModule

    • SubModule 표시하기: named_modules() VS named_children()
      • named_modules(): 자신에 속하는 전체 하위 모듈을 표시
      • named_children(): 하나 아래 단계의 하위 모듈까지만 표시
      • 이름 없이 그냥 모듈만 가져올 경우 modules(), children() 사용
    • SubModule 가져오기: get_submodule(target_name)
  • 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

    • 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)을 사용하여 할 수 있습니다.
    • Module의 hook
    • PyTorch hooks 사용 사례 보기
      • gradient의 값의 변화를 시각화
      • gradient값이 특정 임곗값을 넘으면 gradient exploding 경고 알림
      • 특정 tensor의 gradient 값이 너무 커지거나 작아지는 현상이 관측되면 해당 tensor 한정으로 gradient clipping
  • apply -> applied module

    • 모델에 custom 함수를 적용시켜 그 하위 모듈들에도 모두 적용되도록 하고 싶을 때 사용합니다.

    • apply를 통해서 적용하는 함수는 module을 입력으로 받으며, 모델의 모든 module들을 순차적으로 입력받아 처리합니다.

    • 주로 가중치 초기화에 많이 사용됩니다.

    • apply는 Postorder Traversal 방식(후위탐색)으로 함수를 module에 적용합니다.

      image-20220126215452221

    • 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