(본 포스팅은 PyTorch로 시작하는 딥 러닝 입문 교재를 참고하여 작성하였습니다.)
선형회귀를 구현하기에 앞서 아래 지식이 선행되면 이해하기 더 쉽습니다 :)
- 가설 (Hypothesis)
- 손실 함수 (= 목적 함수 = 비용 함수) ; loss function = objective function = cost function
- 경사 하강법 (Gradient Descent)
공부시간과 시험 성적에 대한 데이터가 아래 테이블처럼 나와있다고 가정해보겠습니다.
Study Hours (x) | Result (y) |
1 | 20 |
2 | 40 |
3 | 60 |
4 | ? |
1시간 공부 --> 20점
2시간 공부 --> 40점
3시간 공부 --> 60점
4시간 공부 --> ??점
선형회귀를 이용하여 시험 성적을 예측한다면, 아래의 패턴을 학습하여 4시간 공부했을 때 시험 성적이 얼마나 나올 것인지를 예측 값을 제공합니다.
가설 수립
머신러닝에서 식을 세울 때, 이 식을 가설(hypothesis)라고 합니다.
이 식은 가설에 입각한 임의의 추측된 식일수도 있으며, 경험적으로 알고 있는 식일 수도 있습니다.
따라서, 이 '가설'은 아니라고 판단되면 계속해서 수정되는 식으로 볼 수 있습니다.
선형 회귀(Linear Regression)에서의 가설은 이미 널리 알려져있습니다.
학습 데이터와 가장 잘 맞는 선형 식인데요. 아래처럼 직선의 방정식을 따른 식으로 구성할 수 있습니다.
y=Wx+by=Wx+b
이때, W를 가중치(Weight)이라고 하며, b를 편향(bias)이라고 합니다.
비용 함수 (Cost Function)
비용 함수에 대해 간략하게 설명을 하자면, 비용 함수는 가설(Hypothesis)로부터 도출된 예측값이 실제값(y)으로부터 얼마나 떨어져 있는지를 알려주는 역할을 합니다.
비용함수의 값이 작을수록 데이터를 잘 설명한다고 볼 수 있습니다. 따라서, 모델 학습의 목적은 이 비용함수를 줄이는 데 의의가 있습니다.
아래 예제를 보겠습니다.

어떤 라인이 데이터를 가장 잘 설명한다고 볼 수 있을까요?
검정색 직선이 4개의 점을 가장 그럴듯하게 지나간다고 볼 수 있습니다.
하지만 수학에서는 이런 느낌이라는 표현을 사용하는 것은 의미가 없습니다.
어떤 직선이 가장 적절한 직선인지는 수학적 근거에 입각해서 표현할 수 있습니다. 여기서 비용 함수에 근간이 되는 오차(error)라는 개념이 필요합니다.

위 그림의 주황색 선은 점으로 표시된 실제값 4개에 대한 예측값입니다.
이 실제값과 예측값의 차이, 즉 오차(error)는 빨간색 선의 길이만큼으로 크기가 표현되고 있습니다.
이 오차들의 합을 최소화 한다면, 예측값과 실제값의 차이가 적어지겠죠.
극단적인 예로, 개개인의 오차가 모두 0이라면, 예측값 = 실제값이 된다고 볼 수 있습니다.
이때, 고민해야할 부분이 나옵니다.
각 데이터 포인트마다의 오차가 -1, 10, -7, -2 라면 어떨까요? 오차의 총합은 0이 될 것입니다.
이런 방식으로 총 오차를 구하고, 확인한다면 우리는 많은 정보의 손실을 경험해야합니다.
이때 활용할 수 있는 방법중 하나는 오차의 제곱합(Sum of Squared Errors) 입니다.
각각의 오차를 제곱하면 음의 값은 더이상 등장하지 않겠죠. 따라서 합산하였을 때, 0이 되는 경우는 각각의 오차가 0인 경우 하나뿐입니다.
(물론, 오차의 제곱합을 구한다면 outlier에 큰 영향을 받는다는 단점도 있습니다)
우리는 비용함수 (또는 손실함수)로 오차의 제곱합을 데이터 포인트 개수 n만큼으로 나누어, 평균 제곱 오차를 사용할 것입니다.
최종 수식은 다음과 같으며, 자세한 보충 설명은 PyTorch로 시작하는 딥 러닝 입문 참고 바랍니다.
cost(W, b)=1nn∑i=1[y(i)−H(x(i))]cost(W, b)=1nn∑i=1[y(i)−H(x(i))]
위 비용함수를 최소화 시킨다면, 선형회귀를 통해 주어진 훈련 데이터를 가장 잘 나타내는 직선을 구할 수 있다고 알아두시면 됩니다.
옵티마이저 - 경사하강법 (Gradient Descent)
앞서 설명한 비용함수를 작게 만들기 위해서라면 우리는 H(x)에 집중할 필요가 있습니다.
H(x) 식의 요소들(가중치 W 와 편향 b)을 수정하면, 실제값과 비슷한 예측값을 만들 수 있겠죠.
이렇게 W와 b를 최적으로 고쳐나가는 방식을 최적화 알고리즘이라고 부릅니다.
Optimizer를 통해 최적화시킬 수 있습니다.
W와 b 어떻게 최적화할 수 있을까요?
위의 비용함수가 최소화 되는 지점을 찾는다면, 해당 알고리즘(본 예제에서는 선형회귀)에서 최적화 시켰다고 할 수 있을 것 같습니다.
최소화는 미분을 통해 구할 수 있습니다.
아래 그래프를 보겠습니다.

W의 크기에 따라 Cost의 값이 변하는 것을 볼 수 있습니다.
너무 작아도 안되고, 너무 커서도 안된다는 것을 확인할 수 있습니다.
경사하강법(Gradient Descent)는 기울기가 하강하는 그림으로 설명할 수 있습니다.
점점 가파른 기울기가 최적의 값을 찾아나가면서 하강하는 모습을 보여줍니다.
그리고 우리는 기울기 값이 0인 지점은 볼록함수의 경우 값이 최소화 된 지점임을 알 수 있습니다.


자세한 내용은 아래 더보기를 펼쳐주세요.
PyTorch로 시작하는 딥 러닝 - 선형회귀 파트 발췌
"
위의 그림에서 초록색 선은 W가 임의의 값을 가지게 되는 네 가지의 경우에 대해서, 그래프 상으로 접선의 기울기를 보여줍니다. 주목할 것은 맨 아래의 볼록한 부분으로 갈수록 접선의 기울기가 점차 작아진다는 점입니다. 그리고 맨 아래의 볼록한 부분에서는 결국 접선의 기울기가 0이 됩니다. 그래프 상으로는 초록색 화살표가 수평이 되는 지점입니다.
즉, cost가 최소화가 되는 지점은 접선의 기울기가 0이 되는 지점이며, 또한 미분값이 0이 되는 지점입니다. 경사 하강법의 아이디어는 비용 함수(Cost function)를 미분하여 현재 W에서의 접선의 기울기를 구하고, 접선의 기울기가 낮은 방향으로 W의 값을 변경하는 작업을 반복하는 것에 있습니다.
이 반복 작업에는 현재 W에 접선의 기울기를 구해 특정 숫자 α를 곱한 값을 빼서 새로운 W로 사용하는 식이 사용됩니다.
기울기=∂cost(W)∂W
기울기가 음수일 때와 양수일 때 어떻게 W 값이 조정되는지 보겠습니다.


위의 그림은 학습률 \(\alpha\)가 지나치게 높은 값을 가질 때, 접선의 기울기가 0이 되는 W를 찾아가는 것이 아니라 W의 값이 발산하는 상황을 보여줍니다. 반대로 학습률 \(\alpha\)가 지나치게 낮은 값을 가지면 학습 속도가 느려지므로 적당한 \(\alpha\)의 값을 찾아내는 것도 중요합니다.
"
PyTorch로 선형회귀 구현하기
# torch 로딩
import torch
import torch.optim as optim # optimizer 호출용
# train 데이터 생성
X_train = torch.FloatTensor([[1],
[2],
[3]]) # 3개의 행 - 입력 데이터: 공부한 시간
y_train = torch.FloatTensor([[2], [4], [6]]) # 3개의 행 - 출력 데이터: 성적
다음으로는 가중치와 편향을 초기화 하겠습니다.
가중치와 편향을 0으로 초기화하고 학습을 통해 값이 변경되는 변수인지 명시해주어야 합니다. 이 부분은 requires_grad 인자 값을 True 전달하여 반영할 수 있습니다.
# 가중치 W를 0으로 초기화 & 학습을 통해 값이 변경되는 변수인지 명시
W = torch.zeros(1, requires_grad=True)
# 편향도 0으로 초기화 & 학습을 통해 값이 변경되는 변수임을 명시
b = torch.zeros(1, requires_grad=True)
print(W) # 출력물: tensor([0.], requires_grad=True)
print(b) # 출력물: tensor([0.], requires_grad=True)
위 코드에 따랐을 때, 현재 식은 y = 0x + 0 입니다
이대로 예측을 진행하면 가설은 0을 예측하게 되겠죠. 아직 적절한 W와 b가 아닙니다.
다음은 가설을 세우고 비용함수, 옵티마이저까지 구현해보겠습니다.
# 가설 세우기
hypothesis = x_train * W + b
print(hypothesis)
# Cost function (MSE)
cost = torch.mean((hypothesis - y_train) ** 2)
print(cost) # 출력물: tensor(18.6667, grad_fn=<MeanBackward0>)
# 경사하강법
# Stochastic Gradient Descent 활용 - learning rate = 0.01
optimizer = optim.SGD([W, b], lr=0.01)
이제 epoch을 설정하고, 학습시켜보겠습니다.
nb_epochs = 2000
for epoch in range(nb_epochs + 1):
# H(x)
hypothesis = X_train * W + b
# cost
cost = torch.mean((y_train - hypothesis) ** 2)
# cost로 H(x) 개선
optimizer.zero_grad()
cost.backward()
optimizer.step()
if epoch % 100 == 0:
print('Epoch {:4d}/{} W: {:.3f}, b: {:.3f} Cost: {:.6f}'.format(
epoch, nb_epochs, W.item(), b.item(), cost.item()))
cost로 H(X)를 개선하는 부분에 대한 코드 설명은 PyTorch로 시작하는 딥 러닝을 인용했습니다.
"optimizer.zero_grad()를 실행하므로서 미분을 통해 얻은 기울기를 0으로 초기화합니다.
기울기를 초기화해야만 새로운 가중치 편향에 대해서 새로운 기울기를 구할 수 있습니다.
그 다음 cost.backward() 함수를 호출하면 가중치 W와 편향 b에 대한 기울기가 계산됩니다.
그 다음 경사 하강법 최적화 함수 opimizer의 .step() 함수를 호출하여 인수로 들어갔던 W와 b에서 리턴되는 변수들의 기울기에 학습률(learining rate) 0.01을 곱하여 빼줌으로서 업데이트합니다."