데이터사이언스/PyTorch

PyTorch 1 - 텐서

Johnny Yoon 2019. 7. 9. 00:27
728x90
반응형

 

PyTorch 기본

먼저 PyTorch의 주요 패키지들을 가져온다.

  • torch는 PyTorch의 최상위 패키지이고, Numpy와 비슷하지만, Tensor라는 N차원벡터를 GPU위에서 다룰 수 있는 패키지이다.
  • torch.autograd는 경사하강법 및 미분을 자동으로 해주는 패키지이다.
  • torch.nn은 여러 많이 사용되는 신경망들의 구현체를 가지고있는 패키지이다.
  • torch.optim은 SGD나 Adam을 비롯한 여러가지 최적화 함수들을 가지고있는 패키지이다.
 

PyTorch 텐서

위에서 언급한 것 처럼 텐서는 N차원벡터들을 의미한다. 아래는 PyTorch에서 텐서를 어떻게 사용하는지 보여주는 코드이다.

In [2]:
import torch
from torch import autograd, nn, optim
import torch.nn.functional as F
import numpy as np
In [3]:
N = 5
# 5 x 10사이즈의 2차원 텐서에 랜덤한 Float값들을 넣어 만든다
x = torch.randn(N, 10).type(torch.FloatTensor)
x
Out[3]:
tensor([[-0.6349, -0.2247, -0.4449,  0.6491, -0.5728, -0.1650, -0.4292, -0.9084,
         -0.0368,  0.4138],
        [ 0.8501,  1.4085,  0.2601, -0.1743, -0.2548, -1.3606, -0.7319,  1.2837,
         -0.6363,  0.5006],
        [-1.2243, -0.1258,  0.7474,  0.4825,  0.0774,  0.6134,  0.7328,  0.5594,
          0.0478,  1.1863],
        [-0.0347, -0.1537,  0.3539, -1.2959, -0.7198,  0.7109,  1.2373,  0.7423,
         -1.9033,  0.2780],
        [-0.1157,  1.0360,  1.0076, -1.1989,  1.4506, -0.3820,  2.2638,  0.4915,
         -1.4914, -2.0551]])
In [6]:
# view함수를 사용해 2차원의 행들을 하나로 합친다
x.view(1, -1)
Out[6]:
torch.Size([1, 50])
In [8]:
# 기존의 텐서 x와 차원이 바뀐 텐서의 shape을 비교해본다
x.shape, x.view(1, -1).shape
Out[8]:
(torch.Size([5, 10]), torch.Size([1, 50]))
 

PyTorch Autograd

앞서 이야기했듯이 autograd패키지는 경사하강법 즉 미분을 자동으로 해주는 패키지이다. 각 파라미터들의 에러(오차)값을 구하려면 미분을 해야하기 때문이다.

이것을 텐서에 적용하려면 requires_grad 파라미터를 True로 설정해주면 된다.

In [19]:
x = torch.tensor([1., 2., 3., 4., 5., 6.], requires_grad=True)
x.grad
In [20]:
# 딥러닝에서는 보통 텐서에 활성함수를 사용하지만, 여기서는 단순 계산
L = (2*x+1).sum()
# 역전파를 사용해 텐서안의 값들(딥러닝에선 파라미터)을 갱신해준다
L.backward()
Out[20]:
tensor(48., grad_fn=<SumBackward0>)
 

앞서 grad를 출력하면 아무것도 나오지 않았지만, 아래 다시 grad를 출력하면 갱신된 값들을 가진 텐서를 볼 수 있을것이다.

In [22]:
x.grad
Out[22]:
tensor([2., 2., 2., 2., 2., 2.])
 

PyTorch nn

nn모듈에는 딥러닝에서 많이 사용하는 신경망 모델들, 즉 레이어들과 활성함수 (비용함수)들이 들어있다.

아래의 예제는 N x 5 행렬을 N x 3 행렬로 변환하는 선형레이어 예제이다.

In [27]:
D = 5
M = 3
linear_map = nn.Linear(in_features=D, out_features=M)
# 랜덤으로 할당된 파라미터 값들을 출력해준다
[p for p in linear_map.parameters()]
Out[27]:
[Parameter containing:
 tensor([[-1.3423e-01,  1.2878e-02, -8.2049e-02,  3.9390e-01,  3.8989e-01],
         [-4.3276e-01, -2.2667e-01, -2.1788e-01, -3.8605e-01,  3.1620e-01],
         [ 4.4465e-01, -4.2811e-04, -1.3444e-01,  1.7238e-01, -3.3498e-02]],
        requires_grad=True), Parameter containing:
 tensor([-0.0959, -0.0040, -0.3548], requires_grad=True)]
 

PyTorch를 활용한 선형회귀

선형회귀는 주어진 데이터(x값과 y값을 가진 점들)에 가장 잘 맞는 선을 구하는 문제이다.

In [215]:
lin = lambda a, b, x : a * x + b
# 가짜 데이터를 생성한다
def gen_fake_data(n, a, b):
    x = np.random.uniform(0, 1, n)
    y = lin(a, b, x) + 0.1 * np.random.normal(0, 3, n)
    return x, y

x, y = gen_fake_data(50, 3., 8.)

# 생성된 데이터를 5개씩만 출력해본다
x[:5], y[:5]
Out[215]:
(array([0.83506544, 0.46013089, 0.42934005, 0.71619762, 0.59925999]),
 array([10.61613902,  9.68312179,  8.60728252, 10.21721379, 10.05744442]))
In [216]:
import matplotlib.pyplot as plt
# matplotlib를 사용해 데이터를 그려본다
# 가짜 데이터가 선형회귀에 알맞게 잘 분포되어 있는것을 볼 수 있다
%matplotlib inline
plt.scatter(x,y, s=8)
plt.xlabel("x")
plt.ylabel("y")
Out[216]:
Text(0, 0.5, 'y')
 
 

선형회귀 문제에서는 데이터에 맞는 직선 a * x + b의 a와 b를 데이터를 기반으로 구하는것이 핵심이다. 이를 위해서 에러함수인 Mean Squared Error함수를 정의하겠다.
만약 우리가 a = 10, b = 5라고 가정하고 실제 y데이터와 비교해주면 다음과 같이 에러를 리턴해준다. (데이터 자체가 랜덤생성값이기 때문에 위에서 데이터를 다시 정의해주면 값이 바뀐다.)

In [217]:
mse = lambda y_hat, y: ((y_hat - y) ** 2).mean()
# 데이터의 x값을 사용해 10x + 5의 직선으로 y 데이터를 예측한다.
y_hat = lin(10, 5, x)
mse(y_hat, y)
Out[217]:
4.750511937151509
In [218]:
MSELoss = lambda  a, b, x, y: mse(lin(a, b, x), y)
MSELoss(10, 5, x, y)
Out[218]:
4.750511937151509
 

PyTorch 경사하강법

MSELoss는 x와 y의 함수가 아니라 a와 b의 함수이다. 그 이유는 x와 y는 이미 정해져있는 데이터셋이기 때문이다. 따라서 우리는 MSELoss함수를 최소화하는 a와 b값을 찾아야한다.

경사하강법은 이 오차함수를 최소화 하는 알고리즘이다. 경사하강법은 어떠한 파라미터값(가중치)으로 시작해 오차함수를 최소화 하는 파라미터값으로 반복을 통해 조금씩 이동하게 하는 알고리즘이다.

아래는 이것이 구현된 PyTorch 코드이다.

In [256]:
# 앞서 정의한 함수로 가짜 데이터를 만들어낸다
x_orig, y_orig = gen_fake_data(10000, 3., 8.)
x_orig.shape, y_orig.shape
Out[256]:
((10000,), (10000,))
In [257]:
from sklearn.model_selection import train_test_split

# 데이터를 Train과 Test로 나눠준다
x_train, x_test, y_train, y_test = train_test_split(x_orig, y_orig, test_size=0.3, random_state=42)

# 데이터를 텐서로 만들어준다
x_train = torch.tensor(x_train)
y_train = torch.tensor(y_train)
x_test = torch.tensor(x_test)
y_test = torch.tensor(y_test)
x_train, y_train, x_test, y_test
Out[257]:
(tensor([0.8449, 0.8602, 0.8374,  ..., 0.5944, 0.9657, 0.4592],
        dtype=torch.float64),
 tensor([10.7715, 10.5279, 10.2740,  ...,  9.3949, 11.4890,  9.5719],
        dtype=torch.float64),
 tensor([0.0653, 0.9017, 0.9175,  ..., 0.0902, 0.6124, 0.0507],
        dtype=torch.float64),
 tensor([ 8.6670, 10.6629, 11.1680,  ...,  8.3220,  9.5689,  8.3043],
        dtype=torch.float64))
In [258]:
# 파라미터값을 0과 1사이의 랜덤한 값으로 초기화 하고,
#  경사하강법(자동미분)을 적용하는 텐서를 만든다
a, b = np.random.randn(1), np.random.randn(1)
a = torch.tensor(a, requires_grad=True)
b = torch.tensor(b, requires_grad=True)
a, b
Out[258]:
(tensor([2.5059], dtype=torch.float64, requires_grad=True),
 tensor([-0.7658], dtype=torch.float64, requires_grad=True))
 

학습률(learning rate)는 경사하강법이 한번에 얼마만큼 변하는지를 결정한다. 아래의 코드에서는 학습률을 0.003으로 설정해 주었다.

In [259]:
learning_rate = 1e-3
for t in range(10000):
    # 먼저 초기값의 a와b를 통해 y를 예측한다
    loss = MSELoss(a, b, x_train, y_train)
    if t % 1000 == 0:
        print(loss.item())
        
    # 오차 역전파 미분을 시행한다.
    # requires_grad=True로 설정했기 때문에 자동미분을 수행한다
    loss.backward()
    
    # 경사와 학습률을 곱한 만큼 a와b를 갱신한다
    a.data -= learning_rate * a.grad.data
    b.data -= learning_rate * b.grad.data
    
    #경사를 다시 0으로 설정해준다
    a.grad.data.zero_()
    b.grad.data.zero_()
print(a, b)    
 
81.19151451245165
1.2859032677157982
0.6241921504447668
0.49776983645012335
0.40310662098173067
0.3304423781467055
0.274653234622166
0.23182014830241987
0.19893430003309234
0.17368561707705904
tensor([3.8634], dtype=torch.float64, requires_grad=True) tensor([7.5319], dtype=torch.float64, requires_grad=True)
 

테스트 데이터를 사용해 모델을 평가해본다. 평가는 학습때와 같이 MSE Loss를 이용한다. loss의 결과를 확인하면 학습때의 loss와 얼추 비슷하게 나오는 것을 확인할 수 있다.

In [260]:
a.grad.data.zero_()
b.grad.data.zero_()
loss = MSELoss(a, b, x_test, y_test)
loss.item()
Out[260]:
0.15668705603706762
 

PyTorch nn을 사용한 경사하강법

위의 코드를 PyTorch의 nn패키지를 사용해 조금 더 간결하게 작성해보겠다.

In [265]:
# 입력 차원 1개 출력 차원 1개의 선형 모델이다
nn.Linear(1, 1)
Out[265]:
Linear(in_features=1, out_features=1, bias=True)
In [266]:
# 이를 감싸주는 클래스를 작성한다
# forward함수는 모델이 호출될 때 자동으로 불리게 되어있다
# Sequential을 사용한 케라스 스타일의 작성법도 있지만,
# 복잡한 모델을 만들때는 클래스형식을 많이 쓰기 때문에 작성하지 않는다
class LinearRegression(nn.Module):
    def __init__(self):
        super().__init__()
        self.lin = nn.Linear(1, 1)
        
    def forward(self, x):
        x = self.lin(x)
        return x
    
model = LinearRegression()
In [267]:
print([p for p in model.parameters()])
 
[Parameter containing:
tensor([[0.7837]], requires_grad=True), Parameter containing:
tensor([0.7221], requires_grad=True)]
In [268]:
from sklearn.model_selection import train_test_split

x, y = gen_fake_data(10000, 3., 8.)

# 데이터를 Train과 Test로 나눠준다
X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=42)

x_train = torch.tensor(X_train).float()
y_train = torch.tensor(y_train).float()
x_test = torch.tensor(X_test).float()
y_test = torch.tensor(y_test).float()
x_train, y_train, x_test, y_test
Out[268]:
(tensor([0.6328, 0.8097, 0.7832,  ..., 0.2560, 0.7778, 0.3601]),
 tensor([ 9.7916,  9.9869, 10.5888,  ...,  8.2958, 10.5546,  9.2445]),
 tensor([0.2546, 0.4270, 0.9966,  ..., 0.4156, 0.3343, 0.6906]),
 tensor([ 8.7774,  9.1672, 11.0231,  ...,  9.2628,  8.7739,  9.7556]))
 

nn의 모델을 사용해 예측을 하는 것은 다음과 같이 한다:

In [269]:
# x의 형식을 모델에 적용하기 좋게 바꿔준다
x_train_unsqueezed = torch.unsqueeze(x_train, 1)
y_hat = model(x_train_unsqueezed)
y_hat
Out[269]:
tensor([[1.2180],
        [1.3567],
        [1.3359],
        ...,
        [0.9227],
        [1.3316],
        [1.0043]], grad_fn=<AddmmBackward>)
 

앞서 정의했던 반복문보다 조금 짧은것을 볼 수 있다. 주석을 보고 같은 부분을 비교해보자. 결과값을 보면 앞서 했던 경사하강법과 성능이 비슷한 것을 알 수 있다.

In [270]:
learning_rate = 1e-3
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
    
for t in range(10000):
    model.train()
    y_hat = model(x_train_unsqueezed)
    # 먼저 초기값의 a와b를 통해 y를 예측한다
    loss = F.mse_loss(y_hat, y_train.unsqueeze(1))
    if t % 1000 == 0: 
        print(loss.item())
        
    # 오차 역전파 미분을 시행한다.
    # requires_grad=True로 설정했기 때문에 자동미분을 수행한다
    loss.backward()

    # 경사와 학습률을 곱한 만큼 a와b를 갱신한다
    optimizer.step()
    
    #경사를 다시 0으로 설정해준다
    optimizer.zero_grad()
 
70.94881439208984
0.6404173374176025
0.18173591792583466
0.1592845320701599
0.14400039613246918
0.13221736252307892
0.12312369793653488
0.11610571295022964
0.11068933457136154
0.10650947690010071
In [271]:
print([p for p in model.parameters()])
 
[Parameter containing:
tensor([[3.3594]], requires_grad=True), Parameter containing:
tensor([7.8071], requires_grad=True)]
 

이제 모델을 평가해보자. loss함수를 사용해 평가해보면 위의 마지막 loss와 비슷한 결과를 얻은것을 볼 수 있다.

In [274]:
x_test_unsqueezed = torch.unsqueeze(x_test, 1)

model.eval()
with torch.no_grad():
    outputs = model(x_test_unsqueezed)
    loss = F.mse_loss(outputs, y_test_tensor.unsqueeze(1))
    print(loss.item())
        
 
tensor(0.0957)
 

아래는 단순한 경사하강법 대신 Adam이라는 최적화함수를 사용하고, 학습률 0.1을 적용한 결과이다. 이전 결과보다 빨리 수렴하는것을 볼 수 있다.

In [276]:
model = LinearRegression()

X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=42)

x_train = torch.tensor(X_train).float()
y_train = torch.tensor(y_train).float()
x_test = torch.tensor(X_test).float()
y_test = torch.tensor(y_test).float()
x_train, y_train, x_test, y_test

x_train_unsquezzed = torch.unsqueeze(x_train, 1)

learning_rate = 0.1
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
    
for t in range(10000):
    y_hat = model(x_train_unsquezzed)
    loss = F.mse_loss(y_hat, y_train.unsqueeze(1))
    if t % 1000 == 0: 
        print(loss.item())
        
    loss.backward()
    optimizer.step()

    optimizer.zero_grad()
 
103.46098327636719
0.09240294247865677
0.09237399697303772
0.09237399697303772
0.09237399697303772
0.09237399697303772
0.09237399697303772
0.09237399697303772
0.09237399697303772
0.09237399697303772
In [277]:
print([p for p in model.parameters()])
 
[Parameter containing:
tensor([[2.9980]], requires_grad=True), Parameter containing:
tensor([8.0015], requires_grad=True)]
In [278]:
x_test_unsqueezed = torch.unsqueeze(x_test, 1)

model.eval()
with torch.no_grad():
    outputs = model(x_test_unsqueezed)
    loss = F.mse_loss(outputs, y_test_tensor.unsqueeze(1))
    print(loss.item())
 
0.0860409215092659

 

참고자료

아래의 원본 소스를 기반으로 번역 및 개인 의견을 덧붙인 코드입니다.

https://github.com/yanneta/pytorch-tutorials

728x90
반응형