[PyTorch] 기본 & 구조

확률론과 통계학

우선 지난 번에 정리하지 못한 확률론과 통계학 부분 강의에 대한 정리.

확률론

딥러닝은 확률론 기반의 머신러닝 이론.
회귀분석에서 손실함수로 사용되는 L2노름은 예측오차의 분산을 최소화하는 방향으로 학습하도록 유도한다.

이산형확률분포와 연속형확률분포. 원래 확률분포와 상관없이 결합분포는 다를 수 있다

주변확률분포(P(X))보다는 조건부확률분포가 통계적 관계를 모델링하기 좋다.

연속확률분포의 기대값은 주어진 함수에 확률밀도함수를 곱한 후 적분, 이산확률분포의 기대값은 주어진 함수에 확률질량함수를 곱한 후 급수를 사용한다.

로지스틱 회귀에서 사용된 선형모델과 소프트맥스 함수의 결합은 데이터에서 추출된 패턴을 기반으로 확률을 해석하는 데에 사용된다.

확률분포를 모를 때는 몬테카를로 샘플링으로 기대값을 계산한다. 이산형이든 연속형이든 상관 없다. 독립적으로 샘플링을 해야만 한다. 독립추출이라는 전제 하에 대수의 법칙에 따라 목표하는 기대값을 얻을 수 있다.

통계학

통계학 맛보기

데이터가 특정 확률분포를 따른다고 가정하면 모수적 방법론.

표집분포는 통계량의 확률분포. 표본들의 분포가 아니다! 표본평균과 표본분포를 말한다. n이 커질수록 정규분포….중심극한정리.

최대가능도 추정법은 가장 가능성이 높은 모수를 추정하는 방법. 이 가능도 함수는 모수를 따르는 분포가 x를 관찰할 확률이 아닌 가능성을 뜻하는 것이다. 즉 대소비교가 가능한 값.

데이터 집합이 독립 추출일 때는 로그가능도를 최적화. 로그를 사용한다면 가능도의 곱셈을 로그가능도의 덧셈으로 계산 가능. 따라서 시간복잡도를 획기적으로 줄여준다. 단, 경사하강법을 사용하는 손실함수는 음의 로그가능도 사용.


PyTorch 기본

  • numpy 기반이라 사용 방식이 상당히 유사하다.
  • AutoGrad를 이용해서 자동 미분을 지원한다.
  • 딥러닝을 지원하는 함수와 모델이 다양하다.

프레임워크를 공부하는 것이 딥러닝을 공부하는 것이다 파이토치와 텐서플로우가 메인스트림!

케라스는 wrapper. 케라스 자체가 딥러닝을 지원하는 건 아니라 내부에서 파이토치나 텐서플로우 같은 걸 지원함.

사실상 텐서플로우랑 케라스랑 합쳐짐. 텐서플로우랑 파이토치의 가장 큰 차이점은 그래프 표현 방식이다.

텐서플로우는 정적 그래프(define and run)를 그린다. 그래프를 먼저 코드로 정의한 후에 실행 시점에 데이터를 넣어준다.

반면에 파이토치는 동적 그래프라서 먼저 연산의 과정을 그래프로 그린다. 실행하면서 그래프 생성. 파이토치 방식이 디버깅하기 더 쉽다고 한다.



Tensor

앞서 말했듯이 numpy와 상당히 유사하다.

1
2
3
4
import numpy as np
n_array = np.arange(10).reshape(2, 5)
print(n_array)
print("ndim :", n_array.ndim, "shape :", n_array.shape)

위와 아래 코드의 출력 결과는 같다.

1
2
3
4
import torch
t_array = torch.FloatTensor(n_array)
print(t_array)
print("ndim :", t_array.ndim, "shape :", t_array.shape)

리스트나 ndarray로도 Tensor 생성이 가능하다.

1
2
3
4
5
6
7
8
9
10
data = [[1, 3], [5, 7]]

l_tensor = torch.tensor(data)
l_tensor


nd_array = np.array(data)
a_tensor = torch.from_numpy(nd_array)
a_tensor


아무튼 numpy처럼 작동한다.

이렇게

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data = [[1, 2, 3],[4, 5, 6], [7, 8, 9]]
tensored = torch.tensor(data)

tensored[1:] # 이렇게 슬라이싱 가능
tensored[:2, 1:]

tensored.flatten() # numpy에서처럼 이렇게 쫙 펴는 것도 가능

torch.ones_like(tensored) # 마찬가지로 이렇게 1로 채워넣는 것도 똑같다

tensored.numpy()

tensored.shape

tensored.dtype


Tensor handling

  • view : tensor의 shape를 변환한다. reshape와 유사하다.
  • squeeze : 차원의 개수가 1인 차원을 삭제한다.
  • unsqueeze : 차원의 개수가 1인 차원을 추가한다.

먼저 view와 reshape의 차이를 간단히 말하자면 깊은 복사(reshape)와 얕은 복사(view)의 차이이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 만약에 충분히 큰 크기를 가진 data라는 tensor가 있다면
data.view([-1, 3])
data.reshape([-1, 3])
# 전자와 후자의 결과값은 같다.

# 하지만..! 아래의 코드를 실행시키면 차이가 보일 것이다.

a = torch.zeros(3, 2)
b = a.view(2, 3)
a.fill_(1)

a = torch.zeros(3, 2)
b = a.t().reshape(6)
a.fill_(1)

contiguity 차이가 있는 것인데, reshape는 새로 메모리를 사용하는 반면에 view는 기존의 메모리를 공유한다.

squeeze와 unsqueeze

squeeze와 unsqueeze


Tensor operation

PyTorch에는 dot, mm, matmul이라는 함수가 있다.

행렬 곱셈 연산은 mm을 사용한다. dot이 아니다. dot은 벡터행렬 내적에, mm은 행렬 곱셈에 쓰인다. matmul은 mm처럼 행렬 곱셈 기능을 가지지만 broadcasting을 지원한다는 장점이 있다.

1
2
3
4
5
6
7
a = torch.rand(5, 2, 3)
b = torch.rand(5)
a.mm(b)

a = torch.rand(5, 2, 3)
b = torch.rand(3)
a.matmul(b)


Autograd

PyTorch는 자동 미분을 지원한다. backward 함수를 사용하면 된다.

예를 들어,
y = w ** 2, z = 10y + 25
라면

1
2
3
4
5
6
w = torch.tensor(2.0, requires_grad = True)
y = w**2
z = 10*y + 2

z.backward()
w.grad

편미분이라면 어떨까?
Q = 3a^3 - b^2
이때 Q를 편미분한다면 다음과 같이 할 수 있겠다.
dQ/da = 9a^2 dQ/db = -2b

1
2
3
4
5
6
7
8
9
a = torch.tensor([2., 3.], requires_grad=True)  
b = torch.tensor([6., 4.], requires_grad=True)  
Q = 3*a**3 - b**2

external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)

a.grad
b.grad

PyTorch 구조와 모델

torch.nn.Module

nn.Module class는 PyTorch에서 모델을 만드는 데에 꼭 필요한 존재인데, 모델의 뼈대(container)라고 생각하면 될 듯하다. 다시 말하자면 모델은 layer를 레고블록을 쌓듯이 뭉텅이로 쌓아 만든 것이다.

  • 딥러닝을 구성하는 Layer의 base class
  • Input, Output, Forward, Backward 정의
  • 학습의 대상이 되는 parameter(tensor) 정의


모듈을 관리할 때 쓰는 자료형

  • torch.nn.Secuential : Module을 하나로 묶어서 순차적으로 실행시킬 때 사용한다.
  • torch.nn.ModuleList : Module을 리스트에 저장해서 인덱싱으로 필요한 것을 찾는다. 다만, 리스트의 크기가 커진다면 비효율적이지 않겠는가.
  • torch.nn.ModuleDict : Module을 딕셔너리 형태로 저장한 다음에 키값으로 필요한 것을 찾는다.

학습 방법

torch.nn.Parameter

  • Tensor 객체의 상속 객체이자 학습에 필요한 매개변수를 가지고 있다가 전달하는 존재이다.
  • nn.Module 내에 attribute가 될 때는 required_grad = True 로 지정되어 학습 대상이 되는 Tensor
  • 다만 직접 설정할 일은 거의 없으며 대부분의 layer에 weights 값들이 설정되어 있음
1
2
3
4
5
6
7
8
9
10
class MyLiner(nn.Module):
    def __init__(self, in_features, out_features, bias=True):
        super().__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.weights = nn.Parameter(torch.randn(in_features, out_features))
        self.bias = nn.Parameter(torch.randn(out_features))

    def forward(self, x : Tensor):
        return x @ self.weights + self.bia

위의 코드에서 클래스 MyLiner는 nn.Module을 상속한다.

_init_ 에서 ‘in_features’와 ‘out_features’를 초기화 시키고 있는데, 이는 각각 input의 차원과 output의 차원을 설정한다.
그리고 ‘self.weights’ 그리고 ‘self.bias’를 nn.Parameter를 통해 파라미터로써 생성해주었다.

nn.Parameter로 생성하면 미분의 대상이 되기 때문에 나중에 출력이 가능하다. 반면에 그냥 Tensor로 생성하면 미분의 대상이 아니게 된다.


Backward

  • layer에 있는 parameter들의 미분을 수행
  • Forward의 결과값(model의 output..즉, 예측치)과 실제값 간의 차이(loss)에 대해 미분을 수행하는데, 여기서 나온 결과값으로 parameter를 갱신한다.

optimizer.zero_grad() -> 학습을 할 때 gradient 값이 업데이트 되는데, optimizer는 이전의 gradient 값이 현재 학습에 영향을 주지 않도록 초기화 하는 것.

backward

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for epoch in range(epochs):

# 앞서 말한대로 기울기 초기화.
optimizer.zero_grad()

# 모델으로부터 결과값을 얻는 것. 모델 예측.
outputs = model(inputs)

# loss 계산.
loss = criterion(outputs, labels)
print(loss)

# 파라미터 기울기값 계산
loss.backward()

# 파라미터 갱신
optimizer.step()


named_childeren, named_modules, named_buffers

  • named_children : 한 단계 아래의 sub module까지만 보여준다.
  • named_modules : 자신에게 속하는 모든 sub module들을 보여준다.
  • named_buffers : 자신에게 속하는 모든 buffer들을 보여준다.

get_submodules

원하는 특정 서브 모듈을 가져올 때 쓴다

PyTorch hook

‘self.hooks’에 등록된 함수가 있으면 실행한다.

  • Module 적용 hook
    • forward hook : ‘forward()’로 실행한다. forward_pre_hook는 forward 실행 전에 호출되는 반면에 forward_hook은 실행 후에 호출된다.
    • forward pre hook
    • full backward hook
    • backward hook : module과 tensor 둘 다에 적용 가능하다. 다만, module 단위의 backward hook은 module 기준의 input 및 output gradient 값만 가져오기 때문에 tensor의 gradient값은 알 수 없다.
  • Tensor 적용 hook
    • backward hook

apply

모델 내의 하위 module에 일괄적으로 custom function을 적용하고 싶을 때 사용한다.
가중치 초기화(Weight Initialization)에 많이 사용된다.

gather

gather

https://stackoverflow.com/questions/50999977/what-does-the-gather-function-do-in-pytorch-in-layman-terms

아시겠어요?