오늘은 회귀 문제에서 loss function으로 가장 널리 사용되는 Mean Squared Error loss function에 대해서 살펴보고 pytorch에서 구체적으로 어떻게 돌아가는지 수식과 함께 살펴보고자 한다. MSE loss function을 살펴보고 분류 문제에서의 Cross entropy loss을 살펴볼 것이다. pytorch에서 모델을 정의하고, prediction, loss, grad을 구하고, 이를 기반으로 gradient descent을 하는 것은 잘 구현되어 있지만 그 내부에서 왜 이런 grad가 나왔는지 개인적으로 알아보고 싶었기 때문에 글을 적어본다.
Simple Linear Regression
우선, 문제를 심플하게 바라보기 위해 변수가 1개인 단변량 회귀 문제를 생각해보자. notation을 정의해보자.
- $n$: 데이터의 개수
- $w$: 가중치, weight
- $b$: bias
- $y_i = wx_i + b$: true $y$가 정의되는 식. 관측되는 데이터가 이런 관계 (여기서는 linear model)를 가진다고 가정하는 것이다.
- $y_i = w x_i + b + \epsilon_i$: 현실 세계에서 데이터가 관측되는 형태. true model과 정확한 $y_i$가 예측되는 것이 아니라 noise인 $\epsilon_i$과 함께 관측된다. 이러한 모델 세팅에서 관측된 $(x_i, y_i)$에 기반하여 $w, b$을 추정하는 것이다.
통계학에서는 error 항을 $\epsilon_i \sim N( \mu, \sigma^2)$와 같이 정규분포로 가정하여 likelihood을 최대로 하는 파라미터를 구하고 이후에 표준편차에 근거하여 추론을 진행한다. 일반적인 딥러닝 문제에서는 분포 가정을 하지 않고 gradient descent을 통해 파라미터를 반복적으로 학습하여 파라미터가 수렴하면 학습을 종료한다. 즉, 딥러닝에서는 파라미터에 대한 추론을 하지 않는데 이 점이 통계학과 가장 큰 차이점을 보이는 지점이다. 분포 가정을 통해서 파라미터에 대한 추론을 진행하는 통계학은 파라미터의 신뢰도를 조사할 수 있지만 파라미터의 수가 많아지거나 모델을 비선형으로 가정하면 적절한 분포를 가정하여 표준편차를 유도하기가 쉽지 않다. 반면, 딥러닝은 분포 가정을 하지 않기 때문에 파라미터의 신뢰도에 대한 개념을 가설검정 등으로 전개하지 못하지만, 대신 비선형 모델 등 데이터간의 복잡한 관계를 설정할 수 있다.
이제 MSE Loss을 정의해보자.
\[ MSE = \dfrac{1}{n} \sum^n_{i=1} \epsilon_i^2 = \dfrac{1}{n} \sum^n_{i=1} ( y_i - ( w x_i + b ) )^2 \]
MSE는 Mean Squared Error이다. 여기서 Error는 $y_i - (w x_i + b)$으로 정의된다. 관측되는 데이터인 $y_i$와 가정한 모형 $w x_i + b$에 기반한 예측값과의 차이이다. 이제 모델 파라미터인 $w,b$에 대한 gradient을 구해보자.
\[ \dfrac{\partial MSE}{\partial w} = \dfrac{1}{n} \sum -2x_i (y_i - (w x_i + b)) \]
\[ \dfrac{\partial MSE}{\partial b} = \dfrac{1}{n} \sum -2 (y_i - (w x_i + b)) \]
gradient을 구했으니, 어떤 optimizer을 사용할지에 따라서 gradient을 사용하여 파라미터를 업데이트한다. 여기서는 논의를 간단하게 하기 위해 단순하게 learning rate을 곱하여 업데이트 하는 방식으로 생각해보면, 아래와 같이 파라미터가 업데이트된다.
\[ w = w - \text{learning rate} \times \dfrac{\partial MSE}{\partial w} \]
\[ b = b - \text{learning rate} \times \dfrac{\partial MSE}{ \partial b} \]
pytorch 코드로 살펴보자.
import torch
from torch import nn
torch.manual_seed(1)
x = torch.rand(100).unsqueeze(dim=1)
y = 3*x + 1 + torch.randn(100).unsqueeze(dim=1) / 5
model = nn.Linear(in_features=1, out_features=1, bias=True)
model.weight, model.bias
"""
(Parameter containing:
tensor([[0.7392]], requires_grad=True),
Parameter containing:
tensor([-0.7216], requires_grad=True))
"""
true model을 $y = 3x + 1$로 가정하고 100개의 데이터를 랜덤하게 생성한다. 단변량 선형회귀 문제이므로 `in_features=1`로 하여 `nn.Linear` 선형 모델을 만든다. weight와 bias를 출력하면 위와 같다.
선형 회귀 문제이므로 loss function으로 `nn.MSELoss`을 정의 하고 loss을 계산한다.
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(params=model.parameters(), lr=0.01)
prediction = model(x)
loss = criterion(input=prediction, target=y)
model.weight.grad, model.bias.grad
"""
(None, None)
"""
아직 `loss.backward()`을 하지 않았기 때문에 각 파라미터의 gradient가 계산되지 않았다. 이제 gradient을 계산해보자.
optimizer.zero_grad()
loss.backward()
model.weight.grad, model.bias.grad
"""
(tensor([[-3.2217]]), tensor([-5.7002]))
"""
gradient가 계산되었다. pytorch에서 `loss.backward()`을 통해 gradient을 계산해줬는데, 사실 위에서 유도한 weight, bias에 대한 gradient을 pytorch가 계산해준 것이다. weight, bias 식을 다시 살펴보자.
\[ \dfrac{\partial MSE}{\partial w} = \dfrac{1}{n} \sum -2x_i (y_i - (w x_i + b)) \]
\[ \dfrac{\partial MSE}{\partial b} = \dfrac{1}{n} \sum -2 (y_i - (w x_i + b)) \]
이를 코드로 간단하게 구현해보면, `loss.backward()`와 동일한 결과가 나옴을 확인할 수 있다.
print( (-2 * x * (y - (model.weight*x + model.bias))).sum()/10 )
"""
tensor(-32.2166, grad_fn=<DivBackward0>)
"""
print( (-2 * (y - (model.weight*x + model.bias))).sum()/10 )
"""
tensor(-57.0018, grad_fn=<DivBackward0>)
"""
Multiple Linear Regression
이제 다변량인 상황을 가정해보자.
\[ y_i = w_1 x_{1i} + \cdots + w_k x_{ki} + b + \epsilon_i \]
이제 MSE는 아래와 같이 정의된다.
\[ MSE = \dfrac{1}{n} \sum^n_{i=1} ( y_i - (w_1 x_{1i} + \cdots + w_k x_{ki}) - b )^2 \]
이제 파라미터는 $w_1, \cdots, w_k, b$이다. $w_k$와 $b$에 대한 gradient을 구해보자.
\[ \dfrac{\partial MSE}{\partial w_k} = \dfrac{1}{n} \sum^n_{i=1} -2 x_{ki} (y_i - (w_1 x_{1i} + \cdots + w_k x_{ki}) - b) \]
\[ \dfrac{\partial MSE}{\partial b} = \dfrac{1}{n} \sum^n_{i=1} -2 (y_i - (w_1 x_{1i} + \cdots + w_k x_{ki}) - b) \]
pytorch 코드로 살펴보자. 변수가 10개인 데이터를 100개 생성하여 `nn.Linear`을 통해 적합하는 상황이다.
import torch
from torch import nn
from torch import nn
k = 10
n = 100
model = nn.Linear(in_features=k, out_features=1, bias=True)
w = torch.rand((k,1), requires_grad=True)
x = torch.rand((n,k))
b = torch.rand(1, requires_grad=True)
y = torch.matmul(x,w) + b + torch.randn(n).unsqueeze(dim=1) / 5
print(w.shape, x.shape, b.shape, y.shape)
"""
(torch.Size([10, 1]),
torch.Size([100, 10]),
torch.Size([1]),
torch.Size([100, 1]))
"""
print(model.weight, model.bias)
"""
(Parameter containing:
tensor([[-0.1172, 0.2788, -0.2409, 0.2869, -0.2487, -0.2228, 0.1546, -0.2272,
-0.0724, 0.2301]], requires_grad=True),
Parameter containing:
tensor([0.2505], requires_grad=True))
"""
$n=100, k=10$이기 때문에 $X$의 차원은 $(100,10)$이고 $W$의 차원은 $(10,1)$이다. 똑같이 MSE loss를 정의하고 loss을 계산해본다.
prediction = model(x)
criterion = nn.MSELoss()
loss = criterion(input=prediction, target=y)
print(model.weight.grad, model.bias.grad)
"""
(None, None)
"""
아직 `loss.backward()`을 하지 않았기 때문에 gradient가 계산되지 않았다. 이제 gradient을 계산해보자.
loss.backward()
print(model.weight.grad, model.bias.grad)
"""
(tensor([[-2.8346, -2.8203, -3.1048, -3.0553, -3.0539, -3.0568, -2.7976, -2.7694,
-3.0549, -2.5928]]),
tensor([-5.6705]))
"""
수식과 비교하기 위해 $w_1$의 gradient을 수식으로 계산하여 구해보자.
tmp = []
for i in range(n):
tmp.append( -2 * x[i,0] * (y[i] - (torch.matmul(model.weight, x[i,:]) + model.bias)) )
print(sum(tmp)/n)
"""
tensor([-2.8346], grad_fn=<DivBackward0>)
"""
값이 일치함을 확인할 수 있다.
'ML&DL > Pytorch' 카테고리의 다른 글
[Pytorch] nn.CrossEntropyLoss와 nn.NLLLoss (0) | 2024.08.30 |
---|
댓글