나는 사실 눈으로만 읽으면 굉장히 집중이 안된다.. 그래서 직접 기록하면서 정독을 하려고 한다.
이 글은 파이토치 공식 튜토리얼에서 가져온 예제다. 더 정확한 내용을 보시려면 여기를 참조
pytorch는 2가지의 주요한 특징이 있음.
1. Numpy와 유사하지만 GPU 상에서 실행가능한 N차원의 tensor
2. 신경망을 구성하고 학습하는 과정에서 자동 미분.
완전히 연결된 ReLU 신경망을 예제로 사용할것입니다. fully connected?
이 신경망은 하나의 은닉층을 갖고 있으며, 신경망의 출력과 정답 사이의 유클리드 거리를 최소화 하는 식으로 경사하강법을 사용하여 무작위 데이터를 맞추도록 학습할 것입니다.
Tensor (Numpy)
pytorch를 소개하기 전에 먼저 numpy를 사용하여 신경망을 구성해보자.
numpy는 n차원 배열 객체와 함께 이러한 배열들을 조작하기 위한 다양한 함수를 제공 numpy는 과학적 분야의 연산을 위한 포괄적인 프레임워크임. 넘파이는 연산 그래나 딥러닝, 변화도에 대해서는 알지 못하지만 넘파이 연상을 사용하여 순전파 단계와 역전파단계를 직접 구현하여 2계층을 갖는 신경망이 무작위의 데이터를 맞추도록 할 수 있다.
import numpy as np
# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10
# 무작위의 입력과 출력 데이터를 생성합니다.
x = np.random.randn(N, D_in) # (64,1000)
y = np.random.randn(N, D_out) # (64,10)
# 무작위로 가중치를 초기화합니다.
w1 = np.random.randn(D_in, H) # (1000,100)
w2 = np.random.randn(H, D_out) # (100,10)
learning_rate = 1e-6
for t in range(500):
# 순전파 단계: 예측값 y를 계산합니다.
h = x.dot(w1) # 내적. 64,100
h_relu = np.maximum(h,0) # 음수 제거?
y_pred = h_relu.dot(w2) # 내적. 64,10
# 손실(loss)을 계산하고 출력합니다.
loss = np.square(y_pred - y).sum() # 차이의 제곱의 합
print(t, loss)
# 손실에 따른 w1, w2의 변화도를 계산하고 역전파합니다.
grad_y_pred = 2.0 * (y_pred - y) # 2배?
grad_w2 = h_relu.T.dot(grad_y_pred) # 100,64 * 64,10 => 100,10
grad_h_relu = grad_y_pred.dot(w2.T) # 64,100
grad_h = grad_h_relu.copy()
grad_h[h < 0] = 0
grad_w1 = x.T.dot(grad_h)
# 가중치를 갱신합니다.
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
Tensor(pytorch)
넘파이는 훌령한 프레임워크지만 gpu를 사용하여 수치연산을 가속화할수는 없다. gpu경우 50배 또는 그 이상 속도 향상이 있기때문에 넘파이는 딥러닝에 적합하지 않다.
pytorch의 tensor는 넘파이와 동일함. 하지만 gpu를 활용하여 연산을 가속화 할수 있다. 넘파이 자료형을 새로운 자료형으로 캐스팅해주기만 하면 된다. 아래 예시를 보자.
import torch
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # GPU에서 실행하려면 이 주석을 제거하세요.
# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10
# 무작위의 입력과 출력 데이터를 생성합니다.
x = torch.randn(N, D_in, device=device, dtype=dtype) # 64,1000
y = torch.randn(N, D_out, device=device, dtype=dtype) # 64,10
# 무작위로 가중치를 초기화합니다.
w1 = torch.randn(D_in, H, device=device, dtype=dtype) # 1000,100
w2 = torch.randn(H, D_out, device=device, dtype=dtype) # 100,10
learning_rate = 1e-6
for t in range(500):
# 순전파 단계: 예측값 y를 계산합니다.
h = x.mm(w1) # dot product = mm(matrix multiplication) 64,100
h_relu = h.clamp(min=0) # 집게?, np.maximum 과 같음.
y_pred = h_relu.mm(w2) # 64,10
# 손실(loss)을 계산하고 출력합니다.
loss = (y_pred - y).pow(2).sum().item()
if t % 100 == 99:
print(t, loss)
# 손실에 따른 w1, w2의 변화도를 계산하고 역전파합니다.
grad_y_pred = 2.0 * (y_pred - y) # 오차에 대해서 2배?
grad_w2 = h_relu.t().mm(grad_y_pred)
grad_h_relu = grad_y_pred.mm(w2.t())
grad_h = grad_h_relu.clone()
grad_h[h < 0] = 0
grad_w1 = x.t().mm(grad_h)
# 경사하강법(gradient descent)를 사용하여 가중치를 갱신합니다.
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
넘파이와 거의 비슷한것을 볼수 있다! 속도가 더 빠르다면 이게 훨씬 좋을것.
Autograd
위의 예제들에서 우리는 신경망의 순전파 단계와 역전파단계를 직접 구현하였다. 작은 2계층 신경망에서 역전파단계를 직접 구현하는것은 큰일이 아니지만 대규모의 복잡한 신경망에서는 매우 아슬아슬한 일일것.
다행히 자동미분을 사용하여 신경망에서 역전파단계의 연산을 자동화할수 있다. Pytorch의 autograd 채키지는 정확히 이런 기능을 제공함. autograd 를 사용할때 신경망의 순전파단계는 연산그래프를 정의하게 됨.
이 그래프의 노드는 tensor, 엣지는 입력 tensor로 부터 출력 tensor를 만들어 내는 함수가 됨.
autograd를 사용하면 더이상 역전파단계를 직접 구현할 필요가 없어짐.
역전파를 직접 구현할 필요가 없으므로 중간값에 대한 참조를 가질 필요가 없음.
import torch
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # GPU에서 실행하려면 이 주석을 제거하세요.
# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10
# 입력과 출력을 저장하기 위해 무작위 값을 갖는 Tensor를 생성합니다.
# requires_grad=False로 설정하여 역전파 중에 이 Tensor들에 대한 변화도를 계산할
# 필요가 없음을 나타냅니다.
# (requres_grad의 기본값이 False이므로 아래 코드에는 이를 반영하지 않았습니다.)
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
# 가중치를 저장하기 위해 무작위 값을 갖는 Tensor를 생성합니다.
# requires_grad=True로 설정하여 역전파 중에 이 Tensor들에 대한
# 변화도를 계산할 필요가 있음을 나타냅니다.
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
# 순전파 단계: Tensor 연산을 사용하여 예상되는 y 값을 계산합니다. 이는 Tensor를
# 사용한 순전파 단계와 완전히 동일하지만, 역전파 단계를 별도로 구현하지 않아도
# 되므로 중간값들에 대한 참조(reference)를 갖고 있을 필요가 없습니다.
y_pred = x.mm(w1).clamp(min=0).mm(w2)
# h = x.mm(w1)
# h_relu = h.clamp(min=0)
# y_pred = h_relu.mm(w2) ==> 한번에 클리어
# Tensor 연산을 사용하여 손실을 계산하고 출력합니다.
# loss는 (1,) 형태의 Tensor이며, loss.item()은 loss의 스칼라 값입니다.
loss = (y_pred - y).pow(2).sum()
if t % 100 == 99:
print(t, loss.item())
# autograd를 사용하여 역전파 단계를 계산합니다. 이는 requires_grad=True를
# 갖는 모든 Tensor에 대해 손실의 변화도를 계산합니다. 이후 w1.grad와 w2.grad는
# w1과 w2 각각에 대한 손실의 변화도를 갖는 Tensor가 됩니다.
loss.backward()
# 아래의 과정이 다 들어가있음
# grad_y_pred = 2.0 * (y_pred - y)
# grad_w2 = h_relu.T.dot(grad_y_pred)
# grad_h_relu = grad_y_pred.dot(w2.T)
# grad_h = grad_h_relu.copy()
# grad_h[h < 0] = 0
# grad_w1 = x.T.dot(grad_h)
# 경사하강법(gradient descent)을 사용하여 가중치를 수동으로 갱신합니다.
# torch.no_grad()로 감싸는 이유는 가중치들이 requires_grad=True이지만
# autograd에서는 이를 추적할 필요가 없기 때문입니다. -> 가중치의 변화가 기록되지 않도록 하기 위함.
# 다른 방법은 weight.data 및 weight.grad.data를 조작하는 방법입니다.
# tensor.data가 tensor의 저장공간을 공유하기는 하지만, 이력을
# 추적하지 않는다는 것을 기억하십시오.
# 또한, 이를 위해 torch.optim.SGD 를 사용할 수도 있습니다.
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
# 가중치 갱신 후에는 수동으로 변화도를 0으로 만듭니다.
w1.grad.zero_()
w2.grad.zero_()
여기까지 봤을때, 입력 x가 있고 출력 y가 있을때 x에 가중치 w1,w2를 곱하여 y를 예측한 y_pred가 y와 얼마나 다른가를 loss를 계산하고 이를 다시 역전파를 거쳐 w1,w2를 수정하여 다시 y와 얼마나 다른지를 반복하는 과정이 계속된다. 이것이 학습과정이다.
가장 기본적인 dnn 과정임.
내일은 복습하고 autograd 함수정의하기부터 !
'Bigdata, AI > NLP' 카테고리의 다른 글
예제로 배우는 파이토치3. 마지막. (0) | 2020.08.25 |
---|---|
예제로 배우는 파이토치2 (0) | 2020.08.25 |
파이토치 텐서보드 에러 ModuleNotFoundError: No module named 'torch.utils.tensorboard' (1) | 2020.07.24 |
mecab 설치하기 (0) | 2020.07.20 |
멀티 gpu 사용하기 (0) | 2020.07.20 |
댓글