과학 분야의 연산을 위한 포괄적인 프레임워크
But 연산 그래프나 딥러닝, 변화도(gradient)에 대해서는 알지 못함
GPU를 사용하여 수치 연산을 가속화할 수 없음
따라서, Numpy는 선형 연산에는 유용하지만, Neural Network 연산에 활용하기에는 다소 부족
import numpy as np
import math
# 무작위로 입력과 출력 데이터를 생성합니다
x = np.linspace(-math.pi, math.pi, 2000)
y = np.sin(x)
# 무작위로 가중치를 초기화합니다
a = np.random.randn()
b = np.random.randn()
c = np.random.randn()
d = np.random.randn()
learning_rate = 1e-6
for t in range(2000):
# 순전파 단계: 예측값 y를 계산합니다
# y = a + b x + c x^2 + d x^3
y_pred = a + b * x + c * x ** 2 + d * x ** 3
# 손실(loss)을 계산하고 출력합니다
loss = np.square(y_pred - y).sum()
if t % 100 == 99:
print(t, loss)
# 손실에 따른 a, b, c, d의 변화도(gradient)를 계산하고 역전파합니다.
grad_y_pred = 2.0 * (y_pred - y)
grad_a = grad_y_pred.sum()
grad_b = (grad_y_pred * x).sum()
grad_c = (grad_y_pred * x ** 2).sum()
grad_d = (grad_y_pred * x ** 3).sum()
# 가중치를 갱신합니다.
a -= learning_rate * grad_a
b -= learning_rate * grad_b
c -= learning_rate * grad_c
d -= learning_rate * grad_d
print(f'Result: y = {a} + {b} x + {c} x^2 + {d} x^3')
반면 PyTorch의 가장 핵심적인 개념인 텐서(Tensor)는 개념적으로 NumPy 배열과 동일
계산 그래프와 그래디언트를 추적할 수 있을 뿐만 아니라 과학적 계산을 위한 일반적인 도구로도 유용
NumPy와는 다르게, GPU를 사용하여 수치 연산을 가속할 수 있음
# -*- coding: utf-8 -*-
import torch
import math
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # GPU에서 실행하려면 이 주석을 제거하세요
# 무작위로 입력과 출력 데이터를 생성합니다
x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
y = torch.sin(x)
# 무작위로 가중치를 초기화합니다
a = torch.randn((), device=device, dtype=dtype)
b = torch.randn((), device=device, dtype=dtype)
c = torch.randn((), device=device, dtype=dtype)
d = torch.randn((), device=device, dtype=dtype)
learning_rate = 1e-6
for t in range(2000):
# 순전파 단계: 예측값 y를 계산합니다
y_pred = a + b * x + c * x ** 2 + d * x ** 3
# 손실(loss)을 계산하고 출력합니다
loss = (y_pred - y).pow(2).sum().item() # MSE loss
if t % 100 == 99:
print(t, loss)
# 손실에 따른 a, b, c, d의 변화도(gradient)를 계산하고 역전파합니다.
grad_y_pred = 2.0 * (y_pred - y)
grad_a = grad_y_pred.sum()
grad_b = (grad_y_pred * x).sum()
grad_c = (grad_y_pred * x ** 2).sum()
grad_d = (grad_y_pred * x ** 3).sum()
# 가중치를 갱신합니다.
a -= learning_rate * grad_a
b -= learning_rate * grad_b
c -= learning_rate * grad_c
d -= learning_rate * grad_d
print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3')
torch.linspace(start, end, steps(tensor size))
: linearly spaced vectors;
원하는 시작점과 끝점에 해당하는 구역에 대해 동일한 간격으로 특정 갯수만큼의 벡터를 생성
torch.randn()
: 평균이 0이고 표준 편차가 1인 값으로 이루어진 tensor 반환
MSE = (y_pred - y)^2
grad_y_pred = d(MSE)/d(y_pred - y) = 2 * (y_pred - y)
자동 미분을 사용하여 신경망의 역전파 단계 연산을 자동화할 수 있는 기능 제공
Autograd를 사용하면, 신경망의 순전파 단계에서 연산 그래프를 정의하게 된다;
노드(Node) --> 텐서
에지(Edge) --> Input 텐서에서 Output 텐서를 출력하는 함수
이 그래프를 통해 역전파를 하게 되면 변화도를 쉽게 계산할 수 있다.
# -*- coding: utf-8 -*-
import torch
import math
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # GPU에서 실행하려면 이 주석을 제거하세요
# 입력값과 출력값을 갖는 텐서들을 생성합니다.
# requires_grad=False가 기본값으로 설정되어 역전파 단계 중에 이 텐서들에 대한 변화도를
# 계산할 필요가 없음을 나타냅니다.
x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
y = torch.sin(x)
# 가중치를 갖는 임의의 텐서를 생성합니다. 3차 다항식이므로 4개의 가중치가 필요합니다:
# y = a + b x + c x^2 + d x^3
# requires_grad=True로 설정하여 역전파 단계 중에 이 텐서들에 대한 변화도를 계산할 필요가
# 있음을 나타냅니다.
a = torch.randn((), device=device, dtype=dtype, requires_grad=True)
b = torch.randn((), device=device, dtype=dtype, requires_grad=True)
c = torch.randn((), device=device, dtype=dtype, requires_grad=True)
d = torch.randn((), device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6
for t in range(2000):
# 순전파 단계: 텐서들 간의 연산을 사용하여 예측값 y를 계산합니다.
y_pred = a + b * x + c * x ** 2 + d * x ** 3
# 텐서들간의 연산을 사용하여 손실(loss)을 계산하고 출력합니다.
# 이 때 손실은 (1,) shape을 갖는 텐서입니다.
# loss.item() 으로 손실이 갖고 있는 스칼라 값을 가져올 수 있습니다.
loss = (y_pred - y).pow(2).sum()
if t % 100 == 99:
print(t, loss.item())
# autograd 를 사용하여 역전파 단계를 계산합니다. 이는 requires_grad=True를 갖는
# 모든 텐서들에 대한 손실의 변화도를 계산합니다.
# 이후 a.grad와 b.grad, c.grad, d.grad는 각각 a, b, c, d에 대한 손실의 변화도를
# 갖는 텐서가 됩니다.
loss.backward()
# 경사하강법(gradient descent)을 사용하여 가중치를 직접 갱신합니다.
# torch.no_grad()로 감싸는 이유는, 가중치들이 requires_grad=True 지만
# autograd에서는 이를 추적하지 않을 것이기 때문입니다.
with torch.no_grad():
a -= learning_rate * a.grad
b -= learning_rate * b.grad
c -= learning_rate * c.grad
d -= learning_rate * d.grad
# 가중치 갱신 후에는 변화도를 직접 0으로 만듭니다.
a.grad = None
b.grad = None
c.grad = None
d.grad = None
print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3')
PyTorch에서 torch.autograd.Function 의 하위클래스(subclass)를 정의하고 forward 와 backward 함수를 구현함으로써 사용자 정의 autograd 연산자를 손쉽게 정의할 수 있다.
➡️ () : 3차 르장드르 다항식
# -*- coding: utf-8 -*-
import torch
import math
class LegendrePolynomial3(torch.autograd.Function):
"""
torch.autograd.Function을 상속받아 사용자 정의 autograd Function을 구현하고,
텐서 연산을 하는 순전파 단계와 역전파 단계를 구현해보겠습니다.
"""
@staticmethod
def forward(ctx, input):
"""
순전파 단계에서는 입력을 갖는 텐서를 받아 출력을 갖는 텐서를 반환합니다.
ctx는 컨텍스트 객체(context object)로 역전파 연산을 위한 정보 저장에 사용합니다.
ctx.save_for_backward 메소드를 사용하여 역전파 단계에서 사용할 어떤 객체도
저장(cache)해 둘 수 있습니다.
"""
ctx.save_for_backward(input)
return 0.5 * (5 * input ** 3 - 3 * input)
@staticmethod
def backward(ctx, grad_output):
"""
역전파 단계에서는 출력에 대한 손실(loss)의 변화도(gradient)를 갖는 텐서를 받고,
입력에 대한 손실의 변화도를 계산해야 합니다.
"""
input, = ctx.saved_tensors
return grad_output * 1.5 * (5 * input ** 2 - 1)
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # GPU에서 실행하려면 이 주석을 제거하세요
# 입력값과 출력값을 갖는 텐서들을 생성합니다.
# requires_grad=False가 기본값으로 설정되어 역전파 단계 중에 이 텐서들에 대한 변화도를 계산할
# 필요가 없음을 나타냅니다.
x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
y = torch.sin(x)
# 가중치를 갖는 임의의 텐서를 생성합니다. 3차 다항식이므로 4개의 가중치가 필요합니다:
# y = a + b * P3(c + d * x)
# 이 가중치들이 수렴(convergence)하기 위해서는 정답으로부터 너무 멀리 떨어지지 않은 값으로
# 초기화가 되어야 합니다.
# requires_grad=True로 설정하여 역전파 단계 중에 이 텐서들에 대한 변화도를 계산할 필요가
# 있음을 나타냅니다.
a = torch.full((), 0.0, device=device, dtype=dtype, requires_grad=True)
b = torch.full((), -1.0, device=device, dtype=dtype, requires_grad=True)
c = torch.full((), 0.0, device=device, dtype=dtype, requires_grad=True)
d = torch.full((), 0.3, device=device, dtype=dtype, requires_grad=True)
learning_rate = 5e-6
for t in range(2000):
# 사용자 정의 Function을 적용하기 위해 Function.apply 메소드를 사용합니다.
# 여기에 'P3'라고 이름을 붙였습니다.
P3 = LegendrePolynomial3.apply
# 순전파 단계: 연산을 하여 예측값 y를 계산합니다;
# 사용자 정의 autograd 연산을 사용하여 P3를 계산합니다.
y_pred = a + b * P3(c + d * x)
# 손실을 계산하고 출력합니다.
loss = (y_pred - y).pow(2).sum()
if t % 100 == 99:
print(t, loss.item())
# autograd를 사용하여 역전파 단계를 계산합니다.
loss.backward()
# 경사하강법(gradient descent)을 사용하여 가중치를 갱신합니다.
with torch.no_grad():
a -= learning_rate * a.grad
b -= learning_rate * b.grad
c -= learning_rate * c.grad
d -= learning_rate * d.grad
# 가중치 갱신 후에는 변화도를 직접 0으로 만듭니다.
a.grad = None
b.grad = None
c.grad = None
d.grad = None
print(f'Result: y = {a.item()} + {b.item()} * P3({c.item()} + {d.item()} x)')
torch.full(tensor_size, value)
: value값으로 size를 채우고 tensor를 반환
계산 그래프와 autograd는 자동으로 미분 계산을 취하기 위해 매우 편리하지만, 대규모의 신경망을 갖출 경우 autograd 만으로는 부족
➡️ Pytorch nn 모듈로 Neural Network를 구성할 수 있다.
# -*- coding: utf-8 -*-
import torch
import math
# 입력값과 출력값을 갖는 텐서들을 생성합니다.
x = torch.linspace(-math.pi, math.pi, 2000)
y = torch.sin(x)
# 이 예제에서, 출력 y는 (x, x^2, x^3)의 선형 함수이므로, 선형 계층 신경망으로 간주할 수 있습니다.
# (x, x^2, x^3)를 위한 텐서를 준비합니다.
p = torch.tensor([1, 2, 3])
xx = x.unsqueeze(-1).pow(p)
# 위 코드에서, x.unsqueeze(-1)은 (2000, 1)의 shape을, p는 (3,)의 shape을 가지므로,
# 이 경우 브로드캐스트(broadcast)가 적용되어 (2000, 3)의 shape을 갖는 텐서를 얻습니다.
# nn 패키지를 사용하여 모델을 순차적 계층(sequence of layers)으로 정의합니다.
# nn.Sequential은 다른 Module을 포함하는 Module로, 포함되는 Module들을 순차적으로 적용하여
# 출력을 생성합니다. 각각의 Linear Module은 선형 함수(linear function)를 사용하여 입력으로부터
# 출력을 계산하고, 내부 Tensor에 가중치와 편향을 저장합니다.
# Flatten 계층은 선형 계층의 출력을 `y` 의 shape과 맞도록(match) 1D 텐서로 폅니다(flatten).
model = torch.nn.Sequential(
torch.nn.Linear(3, 1),
torch.nn.Flatten(0, 1)
)
# 또한 nn 패키지에는 주로 사용되는 손실 함수(loss function)들에 대한 정의도 포함되어 있습니다;
# 여기에서는 평균 제곱 오차(MSE; Mean Squared Error)를 손실 함수로 사용하겠습니다.
loss_fn = torch.nn.MSELoss(reduction='sum')
learning_rate = 1e-6
for t in range(2000):
# 순전파 단계: x를 모델에 전달하여 예측값 y를 계산합니다. Module 객체는 __call__ 연산자를
# 덮어써서(override) 함수처럼 호출할 수 있도록 합니다. 이렇게 함으로써 입력 데이터의 텐서를 Module에 전달하여
# 출력 데이터의 텐서를 생성합니다.
y_pred = model(xx)
# 손실을 계산하고 출력합니다. 예측한 y와 정답인 y를 갖는 텐서들을 전달하고,
# 손실 함수는 손실(loss)을 갖는 텐서를 반환합니다.
loss = loss_fn(y_pred, y)
if t % 100 == 99:
print(t, loss.item())
# 역전파 단계를 실행하기 전에 변화도(gradient)를 0으로 만듭니다.
model.zero_grad()
# 역전파 단계: 모델의 학습 가능한 모든 매개변수에 대해 손실의 변화도를 계산합니다.
# 내부적으로 각 Module의 매개변수는 requires_grad=True일 때 텐서에 저장되므로,
# 아래 호출은 모델의 모든 학습 가능한 매개변수의 변화도를 계산하게 됩니다.
loss.backward()
# 경사하강법을 사용하여 가중치를 갱신합니다.
# 각 매개변수는 텐서이므로, 이전에 했던 것처럼 변화도에 접근할 수 있습니다.
with torch.no_grad():
for param in model.parameters():
param -= learning_rate * param.grad
# list의 첫번째 항목에 접근하는 것처럼 `model` 의 첫번째 계층(layer)에 접근할 수 있습니다.
linear_layer = model[0]
# 선형 계층에서, 매개변수는 `weights` 와 `bias` 로 저장됩니다.
print(f'Result: y = {linear_layer.bias.item()} + {linear_layer.weight[:, 0].item()} x + {linear_layer.weight[:, 1].item()} x^2 + {linear_layer.weight[:, 2].item()} x^3')
# optim 패키지를 사용하여 모델의 가중치를 갱신할 optimizer를 정의합니다.
# 여기서는 RMSprop을 사용하겠습니다; optim 패키지는 다른 다양한 최적화 알고리즘을 포함하고 있습니다.
# RMSprop 생성자의 첫번째 인자는 어떤 텐서가 갱신되어야 하는지를 알려줍니다.
learning_rate = 1e-3
optimizer = torch.optim.RMSprop(model.parameters(), lr=learning_rate)
for t in range(2000):
# 순전파 단계: 모델에 x를 전달하여 예측값 y를 계산합니다.
y_pred = model(xx)
# 손실을 계산하고 출력합니다.
loss = loss_fn(y_pred, y)
if t % 100 == 99:
print(t, loss.item())
# 역전파 단계 전에, optimizer 객체를 사용하여 (모델의 학습 가능한 가중치인) 갱신할
# 변수들에 대한 모든 변화도(gradient)를 0으로 만듭니다. 이렇게 하는 이유는 기본적으로
# .backward()를 호출할 때마다 변화도가 버퍼(buffer)에 (덮어쓰지 않고) 누적되기
# 때문입니다. 더 자세한 내용은 torch.autograd.backward에 대한 문서를 참조하세요.
optimizer.zero_grad()
# 역전파 단계: 모델의 매개변수들에 대한 손실의 변화도를 계산합니다.
loss.backward()
# optimizer의 step 함수를 호출하면 매개변수가 갱신됩니다.
optimizer.step()
# -*- coding: utf-8 -*-
import torch
import math
class Polynomial3(torch.nn.Module):
def __init__(self):
"""
생성자에서 4개의 매개변수를 생성(instantiate)하고, 멤버 변수로 지정합니다.
"""
super().__init__()
self.a = torch.nn.Parameter(torch.randn(()))
self.b = torch.nn.Parameter(torch.randn(()))
self.c = torch.nn.Parameter(torch.randn(()))
self.d = torch.nn.Parameter(torch.randn(()))
def forward(self, x):
"""
순전파 함수에서는 입력 데이터의 텐서를 받고 출력 데이터의 텐서를 반환해야 합니다.
텐서들 간의 임의의 연산뿐만 아니라, 생성자에서 정의한 Module을 사용할 수 있습니다.
"""
return self.a + self.b * x + self.c * x ** 2 + self.d * x ** 3
def string(self):
"""
Python의 다른 클래스(class)처럼, PyTorch 모듈을 사용해서 사용자 정의 메소드를 정의할 수 있습니다.
"""
return f'y = {self.a.item()} + {self.b.item()} x + {self.c.item()} x^2 + {self.d.item()} x^3'
# 입력값과 출력값을 갖는 텐서들을 생성합니다.
x = torch.linspace(-math.pi, math.pi, 2000)
y = torch.sin(x)
# 위에서 정의한 클래스로 모델을 생성합니다.
model = Polynomial3()
# 손실 함수와 optimizer를 생성합니다. SGD 생성자에 model.paramaters()를 호출해주면
# 모델의 멤버 학습 가능한 (torch.nn.Parameter로 정의된) 매개변수들이 포함됩니다.
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-6)
for t in range(2000):
# 순전파 단계: 모델에 x를 전달하여 예측값 y를 계산합니다.
y_pred = model(x)
# 손실을 계산하고 출력합니다.
loss = criterion(y_pred, y)
if t % 100 == 99:
print(t, loss.item())
# 변화도를 0으로 만들고, 역전파 단계를 수행하고, 가중치를 갱신합니다.
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f'Result: {model.string()}')
동일한 가중치를 여러 번 재사용하여 4차와 5차를 계산하는 3-5차 다항식이라는 매우 이상한 모델을 구현
일반적인 Python 제어 흐름을 사용하여 반복(loop)을 구현할 수 있으며, 매개변수를 여러번 재사용하여 가중치 공유를 구현할 수 있습니다.
import random
import torch
import math
class DynamicNet(torch.nn.Module):
def __init__(self):
"""
생성자에서 5개의 매개변수를 생성(instantiate)하고 멤버 변수로 지정합니다.
"""
super().__init__()
self.a = torch.nn.Parameter(torch.randn(()))
self.b = torch.nn.Parameter(torch.randn(()))
self.c = torch.nn.Parameter(torch.randn(()))
self.d = torch.nn.Parameter(torch.randn(()))
self.e = torch.nn.Parameter(torch.randn(()))
def forward(self, x):
"""
모델의 순전파 단계에서는 무작위로 4, 5 중 하나를 선택한 뒤 매개변수 e를 재사용하여
이 차수들의의 기여도(contribution)를 계산합니다.
각 순전파 단계는 동적 연산 그래프를 구성하기 때문에, 모델의 순전파 단계를 정의할 때
반복문이나 조건문과 같은 일반적인 Python 제어-흐름 연산자를 사용할 수 있습니다.
여기에서 연산 그래프를 정의할 때 동일한 매개변수를 여러번 사용하는 것이 완벽히 안전하다는
것을 알 수 있습니다.
"""
y = self.a + self.b * x + self.c * x ** 2 + self.d * x ** 3
for exp in range(4, random.randint(4, 6)):
y = y + self.e * x ** exp
return y
출처 : PyTorch Tutorials https://tutorials.pytorch.kr/beginner/pytorch_with_examples.html