인공지능 in 네부캠 AI 4기/파이토치

Custom Model 연습

제로타이 2022. 10. 2. 14:58

과제에 나온 것들을 연습해보고, 직접 구현해보기. 그리고 추가적으로 몇가지 연습을 더해서 파이토치랑 더 친해지기

이건 당연히 해야..

torch.tensor, torch.Tensor

기본적으로 전자는 함수, 후자는 클래스이다.

tmp = [1,2,3]
A = torch.Tensor(tmp)
B = torch.tensor(tmp)
print(A, B)
print(type(A), type(B))

A = torch.Tensor()
B = torch.tensor()

A = torch.Tensor(tmp, requires_grad=True)
B = torch.tensor(tmp, requires_grad=True)

Tensor는 받는 인자가 없더라도 빈 텐서가 생성된다. 또한 자동으로 미분의 대상이 되기 때문에 requires_grad를 인자로 받지 않는다. 또한 Tensor는 int형의 배열을 받더라도 float으로 저장한다. tensor는 정반대.

Tensor doc

tensor는 항상 데이터를 복제한다.

torch.add 등의 연산 함수

코드로 가져오는 게 귀찮은..

torch.add는 함수. 하지만 텐서 객체에 메소드로도 있다.

뒤에 _를 붙이면 in-place된다

torch.gather 등의 차원을 다루는 함수

dim이 고차원이면 무조건 해당 차원의 첫번째 원소만을 이용하는 듯하다. 차원을 맞춰줘야 사용할 수 있다.

3차원에서 대각선의 위치에 있는 값들을 빼낼 때 사용하는 방법. 웬만해서 dim은 최하위 차원으로 두면 되는 듯하다.

chunk

여기서부터는 앞 인자는 내가 몇 개로 나누고 싶은 지를 나타낸다(보장되지 않는다. 보장하고 싶다면 tensor_split을 사용하라). 뒤는 dim으로 내가 싹둑할 차원을 나타낸다. 

random 관련

randn은 표준정규분포를 따르는 값을 내준다. 일반적인 rand보다는 확률적으로 정규분포 모양을 그리나봄.

einsum

Einsum 사용하기 | Yeongmin’s Blog (baekyeongmin.github.io)

 

Einsum 사용하기

Torch나 Tensorflow로 짜여진 코드들을 보다보면 einsum() 연산이 포함되어 있는 경우를 볼 수 있습니다. 아주 가끔 보이는 방법이라 보일때마다 해석하는 법을 찾아보고는 했는데, 이번에 살펴보았던

baekyeongmin.github.io

아주 좋은 글이 있어서 인용. 아인슈타인 합을 줄여서 쓴 것인데, 행렬 연산이 어떻게 될 것인지 문자열로 인자를 넣고, 그 후에 연산할 배열들을 넣는 방식으로 작동한다. 간단한 예시를 들자면 2차원 행렬의 행렬곱을 할 때,

$\Large A_{ik} \times B_{kj} = C_{ij}$

일텐데, 이때 이렇게 진행될 행렬의 연산은 'ik,kj->ij'라고 표시를 하는 것이다. 만약 내적으로 표현하고 싶다면? 'ik,ik->' 뭐 이런 식으로. 이 방법을 잘 알아두면 확실히 복잡한 행렬 연산을 편하게 표현할 수 있을 것으로 보인다. 또한 코드 공유를 할 때도 이러한 표기 덕에 쉽게 이해할 수 있지 않을까.

linalg

선형대수학과 관련된 함수들

l2 노름

보다시피 공식문서에서는 노름을 구할 때 벡터와 행렬을 구분하는 함수를 쓸 것을 권장하고 있다.

다른 놈들도 보려고는 했는데.. 선형대수학의 내용이 너무 많이 등장해서 쉽게 손을 댈 수 없었다. 이러면 어쩔 수 없다. 

nn

이것은 파이토치의 블럭을 쌓는 과정을 돕기 위해 존재하는 기본적인 블럭 모듈이다. 이걸 잘 다루는 게 실질적인 파이토치 숙달의 핵심이라고 생각한다.

nn.linear

조금 만져보니까 감이 오는데, 이것도 결국 모듈이다. 결국 모듈들이 이미 차곡차곡 쌓인 방식으로 구현되어있고 빌트인으로 제공되는 함수 따위의 것들도 결국은 다 클래스로 된 모듈.

what is grad_fn

grad_fn은 미분이 되기 이전의 연산 정보를 담고있는 인자이다. 이것을 이용해서 미분이 진행된다고 한다. linear를 보면 addmm이라 써있는데 아주 정직하게 선형회귀의 식을 생각해보면 될 듯하다. 참고로 미분할 때 미분할 녀석은 output이 벡터 형태여야 한다고 한다.

nn.Identity

별난 놈이다. 일단 모듈로 보이는데.. 그냥 똑같은 놈을 만들어주는 모듈인 듯하다. What is the use of nn.Identity? - PyTorch Forums

 

What is the use of nn.Identity?

CLASS torch.nn. Identity ( *args , **kwargs )[SOURCE] A placeholder identity operator that is argument-insensitive. Parameters args – any argument (unused) kwargs – any keyword argument (unused) Examples: m = nn.Identity(54, unused_argument1=0.1, unuse

discuss.pytorch.org

쓰는 용도는 다양한 듯하다. 완전히 복사를 해주는 놈이니까 변수명을 간단하게 하기 위해 사용한다던가, 하는 식.

nn.Module

이게 정말 많이 쓰게 되는 모듈.

기본적인 예시

클래스를 많이 사용해본 적 없으니 계속 써가면서 친해져야 한다. 처음에는 이 작동 방식이 너무나 이해되지 않았는데, forward는 부모 클래스로 nn.module을 둘 경우 해당 클래스에 인자를 넣으면 바로 실행되는 메소드이다.  backward는 미분을 자동으로 행하는 메소드. 처음에 이게 얼마나 어려웠던지!
왜 super().__init__()이라는 과정이 필요한가? 일단 우리 클래스가 부모 클래스의 기능을 사용할 수 있기 위함이 있을 것이고, 또 부모 노드의 __setattr__를 호출하기 위함이라는 이야기가 있다.

 def __init__(self) -> None:
        """
        Initializes internal Module state, shared by both nn.Module and ScriptModule.
        """
        torch._C._log_api_usage_once("python.nn_module")

        self.training = True
        self._parameters: Dict[str, Optional[Parameter]] = OrderedDict()
        self._buffers: Dict[str, Optional[Tensor]] = OrderedDict()
        self._non_persistent_buffers_set: Set[str] = set()
        self._backward_hooks: Dict[int, Callable] = OrderedDict()
        self._is_full_backward_hook = None
        self._forward_hooks: Dict[int, Callable] = OrderedDict()
        self._forward_pre_hooks: Dict[int, Callable] = OrderedDict()
        self._state_dict_hooks: Dict[int, Callable] = OrderedDict()
        self._load_state_dict_pre_hooks: Dict[int, Callable] = OrderedDict()
        self._load_state_dict_post_hooks: Dict[int, Callable] = OrderedDict()
        self._modules: Dict[str, Optional['Module']] = OrderedDict()
        
def __setattr__(self, name: str, value: Union[Tensor, 'Module']) -> None:
        def remove_from(*dicts_or_sets):
            for d in dicts_or_sets:
                if name in d:
                    if isinstance(d, dict):
                        del d[name]
                    else:
                        d.discard(name)

        params = self.__dict__.get('_parameters')
        if isinstance(value, Parameter):
            if params is None:
                raise AttributeError(
                    "cannot assign parameters before Module.__init__() call")
            remove_from(self.__dict__, self._buffers, self._modules, self._non_persistent_buffers_set)
            self.register_parameter(name, value)
        elif params is not None and name in params:
            if value is not None:
                raise TypeError("cannot assign '{}' as parameter '{}' "
                                "(torch.nn.Parameter or None expected)"
                                .format(torch.typename(value), name))
            self.register_parameter(name, value)
        else:
            modules = self.__dict__.get('_modules')
            if isinstance(value, Module):
                if modules is None:
                    raise AttributeError(
                        "cannot assign module before Module.__init__() call")
                remove_from(self.__dict__, self._parameters, self._buffers, self._non_persistent_buffers_set)
                modules[name] = value
            elif modules is not None and name in modules:
                if value is not None:
                    raise TypeError("cannot assign '{}' as child module '{}' "
                                    "(torch.nn.Module or None expected)"
                                    .format(torch.typename(value), name))
                modules[name] = value
            else:
                buffers = self.__dict__.get('_buffers')
                if buffers is not None and name in buffers:
                    if value is not None and not isinstance(value, torch.Tensor):
                        raise TypeError("cannot assign '{}' as buffer '{}' "
                                        "(torch.Tensor or None expected)"
                                        .format(torch.typename(value), name))
                    buffers[name] = value
                else:
                    object.__setattr__(self, name, value)

그럼 한번 nn.Module의 __init__뭐하는 놈인지를 보자. 당장 봤을 때는, 속성 선언하는 것밖에는 안 보이는데, setattr를 보니까 뭔가 init 관련된 것이 보이기는 한다. 
이걸 정확히 파악하려면 일단 매직 메소드에 대한 파악도 필요할 것 같다.

what is setattr

! 이런 뜻이었다..! 즉, 내 클래스에서 init을 할 때 setattr가 발동된다는 것. 아직 완벽하게는 이해를 하지 못 했지만, 부모 클래스로 결국 뭔갈 지정해놓은 이상, 자식 클래스에서 속성을 선언할 때 부모에 있는 속성이 필요시된다는 것 아닐까? 그래서 부모의 setattr로까지 가서 문제를 일으킨다는 것.

As per the example above, an __init__() call to the parent class must be made before assignment on the child.

doc에서도 정확하게 명시를 해주고 있다. 

nn.Sequantial

 모듈을 연속적으로 만들고 싶을 때 사용한다. 확실치는 않지만, 모듈을 적을 때 인자로 적는 것은 가능하지만 ,이후에 이 Sequantial을 쓸 때 인자를 전달하는 것은 불가능한 것 같다. 종속된 하위 모듈들의 forward에 인자를 전달하는 게 불가능하다는 것. Add의 forward는 위에 예시와 같이 2개가 필요한데 그것을 전달할 방법은 없어보였다.

forward에 인자가 많댄다

하지만 다른 예시들을 보면 인자가 하나 들어가는 것은 가능한 것 같다. 근데 Add은 인자가 두개 필요한 걸 뭐 어쩌라고.. 하나 줄이면 또 하나 줄였다고 입을 턴다. 에라이 관둬라 관둬..

Sequantial은 순서가 정해져있지만, Modulelist나 Moduledict를 사용하면 인덱싱이 가능해져서 내가 원하는 순서로 작동하게 코드를 짤 수도 있다. 잘 쓰일지는 모르겠다.

nn의 모듈을 사용하면 모듈이 해당 모듈의 하위로 들어간지의 여부를 판별할 수 있게 된다.

parameter

parameter

파라미터를 등록하면 해당 변수를 앞으로 미분할 대상이라는 것을 확실히 할 수 있다. 보다시피 파라미터 관련 메소드를 토해 접근할 수도 있게 되고, 미분의 대상으로 설정된다. nn에 구현된 layer를 쓰게 된다면 이걸 직접 쓸 일은 없을 것이라 한다.

buffer도 사용해보기

Parameter처럼 쓸 수는 없고, register를 사용해 버퍼를 만들 수 있다. 참고로 register로 파라미터를 만드려면 아무 값이나 넣을 수는 없고 이미 올려진 파라미터만 사용할 수 있는 듯하다.

register는 객체 생성 이후 바깥에서도 사용할 수 있다. 이전 예시에서 봤듯이 그냥 buffers()나 named_(이건 해당 변수의 이름까지 같이 반환)를 쓰면 제네레이터가 생성된다. 그걸 활용해  print를 해보았다.

직접 파라미터나 버퍼를 만질 일은 없을 이유에 대한 예시. 이미 만들어진 layer를 많이 쓰게 될 것이고, 그런 것들은 알아서 다 지정이 되어있다.

__dict__를 통해 가지고 있는 온갖 정보들을 확인할 수 있다. 아래에 보면 hook이 있는데, 이것을 통해 내가 해당 모듈이 작동될 때 그것에 대해 작동하고 싶은 함수를 우겨넣을 수 있다. 이 함수는 결과값에도 변화를 줄 수 있다. 

이외

apply

내가 만든 함수를 모듈에 전체 적용할 수 있는 기능. apply! 

'인공지능 in 네부캠 AI 4기 > 파이토치' 카테고리의 다른 글

PyTorch Troubleshooting  (0) 2022.10.01
Hyperparameter Tuning  (0) 2022.10.01
Multi-GPU 학습  (0) 2022.09.30
Monitoring tools for PyTorch  (0) 2022.09.29
모델 불러오기  (0) 2022.09.29