수달이네 기술 블로그

1. 단순 선형 회귀(손실함수, 최적화, 경사하강법, 학습률) 본문

AI공부/딥러닝

1. 단순 선형 회귀(손실함수, 최적화, 경사하강법, 학습률)

슬픈 수달이 2026. 1. 27. 16:16

선형회귀 분석(Linear Regression)

주어진 데이터에서 독립 변수(X)와 종속 변수(Y)간의 관계를 직선(다차원 상의 평면)으로 설명하고, 새로운 종속변수의 출력을 예측한다.

  • Y = wX + b (w = 기울기, b = 절편)
  • 경사하강법(Gradient Descent): 최적의 w와 b를 찾기 위해 비용함수를 최소화한다.

단항 선형 회귀

하나의 독립변수(X)로 하나의 종속변수(Y)를 예측하는 통계/머신러닝 기법

  • 둘 사이의 관계를 직선으로 나타낸다.
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
torch.manual_seed(42)

x_train = torch.FloatTensor([[1],[2],[3]])
y_train = torch.FloatTensor([[2],[4],[6]])

print(x_train, x_train.shape)
print(y_train, y_train.shape)
  • 데이터를 독립변수 > 1,2,3 / 종속변수 > 2,4,6을 넣어 간단한 데이터셋(선형)을 만들었을 때.

model = nn.Linear(1,1) #기울기, bias를 랜덤으로 설정
print(model)

# Linear(in_features=1, out_features=1, bias=True)
  • nn.Linear(1,1): 입력차원1, 출력차원1 인 선형회귀를 생성
    • 여기서 기울기(weight), 편향(bias)는 랜덤설정됨.
y_pred = model(x_train)
print(y_pred)
print(list(model.parameters()))

# tensor([[0.1479],
#         [1.0295],
#         [1.9110]], grad_fn=<AddmmBackward0>)
# [Parameter containing:
# tensor([[0.8815]], requires_grad=True), Parameter containing:
# tensor([-0.7336], requires_grad=True)]
  • model(x_train)model에 x값을 넣었을때 나온 예측값
  • 기울기(w): tensor([[0.8815]], requires_grad=True)
  • 편향(b): tensor([-0.7336], requires_grad=True)]

$$ y=0.8815\cdot x-0.7336$$

  • 위와 같은 식을 학습중이다.
print(0.8815*1 + -0.7336)
print(0.8815*2 + -0.7336)
print(0.8815*3 + -0.7336)

# 0.14789999999999992
# 1.0293999999999999
# 1.9108999999999998
  • 따라서 위 공식에 독립변수 1,2,3을 넣으면 위에서 나온 예측값과 같음을 확인 가능하다.
  • 그러나 위의 기울기와 절편은 아무 의미 없는 값이고, 학습을 하여 예측값과 실제 값 간의 관계를 파악해 의미있는 기울기와 절편을 알아내야한다.

손실함수

머신러닝, 딥러닝 모델이 예측한 값과 실제 값 사이의 차이(오차)를 수치적으로 나타낸 함수

  • 해당 차이를 최소화 해야 의미있는 모델의 값을 알아낼 수 있다.
  • 손실함수가 반환한 값을 역전파(Backpropagation)하여 모델의 가중치, 편향을 조정한다.

주로 사용되는 손실함수

  • MSE(Mean Squared Error, 평균 제곱 오차): 회귀문제에서 주로 사용
    • 예측값 - 실제값의 제곱(절대값)의 평균
    $$ MSE=\frac{1}{n}\sum _{i=1}^n(y_i-\hat {y}_i)^2 $$
    • n = 데이터 개수, y_i = 실제값, y_i(hat) = 예측값
  • RMSE(Root MSE)
  • $$ RMSE=\sqrt{\frac{1}{n}\sum _{i=1}^n(y_i-\hat {y}_i)^2} $$
  • MAE는 미분하기 좋지 않기 때문에 자주 사용하지 않음.
  • CE(Cross-Entropy Loss, 교차 엔트로피 손실): 분류문제에서 주로 사용
    • n: 데이터 개수, pi: 실제 분포, qi:모델이 예측한 분포4
  • $$ H(p,q)=-\sum _{i=1}^np_i\cdot \log (q_i) $$

구현

loss = nn.MSELoss()(y_pred, y_train) 
# 객체를 만들어 해당 객체를 함수처럼 사용
# 파이썬 매직 메서드의 __CALL__의 문법과 같다.
# ex) MSE = MSELoss()
#     MSE(y_pred, y_train) 이것처럼 구현하는 것과 동일
loss

# tensor(9.6581, grad_fn=<MseLossBackward0>)
  • torch의 MSELoss()를 이용하여 손실값을 구한다.(위는 MSE)

최적화

주어진 목표를 달성하기 위해 최상의 해결책을 찾는 과정

  • 머신러닝/ 딥러닝에선 모델이 예측한 값과 실제 값 간의 오차를 최소화 하는 것을 목표로 한다.
    • 파라미터(가중치, 편향)을 조정해 손실 함수 값을 점점 더 작게 만듦.
    • 경사하강법등으로 최저점, 최적점 을 찾아냄.
  • 손실을 줄이는 것 뿐 아니라, 학습 속도, 안정성, 과적합 방지등의 요소를 고려해야 한다.

경사하강법

머신러닝, 딥러닝 모델이 최적의 가중치를 찾기 위해 손실함수를 최적화 하는 방법

  • 높은 지점에서 가장 낮은 지점(최솟값)을 찾아가는 과정
  1. 손실함수의 기울기를 계산(편미분) > 현재 지점에서 손실이 가장 빠르게 감소하는 방향
  2. 기울기를 보고 조정한다.

  • Initial Weight(초기화된 기울기): 처음이므로 cost가 매우 적다.
    • 편미분을 통해 기울기를 파악한다.
    • 기울기가 양의 방향이므로 - 를 해주어 최적값을 찾는다.
    • 반대로 기울기가 음의 방향이면 +를 해준다.
    • 기울기의 변화가 너무 작으면 오래걸리고, 너무 크면 오히려 발산할 수도 있다.

경사하강법의 방법들

  • 모든 데이터 하나하나의 오차를 다 더해서 계산할 수 있지만, 너무 많은 계산이 필요할 수 있다.
    • 오차 구하고,조정하고를 반복해야하므로 따라서 굳이 이렇게 모든 데이터셋을 사용할 필요가 있는가?
  • 아래는 데이터셋을 전체를 쓰는가(배치), 하나만 랜덤으로 쓰는가(확률적), 작은 데이터 묶음을 쓰는가(Mini-Batch)에 따라 다른 경사하강법의 방법들이다.

학습률

머신러닝, 딥러닝 모델에서 가중치를 얼마나 조정할지를 결정하는 하이퍼 파라미터

  • 위에서 말한 기울기의 변화가 학습률임.

가중치

$$ w_{t+1}=w_t-\eta \cdot \frac{\partial L}{\partial w_t} $$

  • w: 가중치, n(eta): 학습률, L: 손실함수

ex) 가중치= 2.0, 학습률: 0.1, 기울기: 4.0 > 2.0 - 0.1 * 4.0 = 1.6

다음 가중치 = 1.6

편향

$$ b_{t+1}=b_t-\eta \cdot \frac{\partial L}{\partial w_t} $$

  • b: 편향
list(model.parameters())
# [Parameter containing:
#  tensor([[0.8815]], requires_grad=True),
#  Parameter containing:
#  tensor([-0.7336], requires_grad=True)]
optimizer = optim.SGD(model.parameters(), lr = 0.01)
  • 해당 파라미터를 관리하라고 optimizer에 넣어줌(SGD를 사용함)
  • 학습률 > 0.01
optimizer.zero_grad()
# 기울기 값을 일단 초기화
loss.backward()
#loss값의 기울기를 구해준다
optimizer.step()

zero_grad(): 이후 기울기 값을 초기화 하는데 계산식이 누적되는 것을 막아준다.

backward(): 역전파로 기울기를 구한다.

step(): 업데이트

print(list(model.parameters()))

# [Parameter containing:
# tensor([[0.7645]], requires_grad=True), 
# Parameter containing:
# tensor([0.8300], requires_grad=True)]

위같은 가중치가

list(model.parameters())

# [Parameter containing:
#  tensor([[0.8466]], requires_grad=True),
#  Parameter containing:
#  tensor([0.8628], requires_grad=True)]

위와 같이 조정되었다.(이처럼 기울기가 최적점과 가까워진것을 학습이라한다.)

  • 0.7645, 0.8300 > 0.8466, 0.8628

epoch(반복)

epochs = 1000

for epoch in range(epochs + 1):
    y_pred = model(x_train) # 현재 모델의 예측값
    loss = nn.MSELoss()(y_pred, y_train) #로스값
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if epoch % 100 == 0:
        print(f'Epoch: {epoch}/{epochs} Loss: {loss: .6f}')

# Epoch: 0/1000 Loss:  2.971597
# Epoch: 100/1000 Loss:  0.116835
# Epoch: 200/1000 Loss:  0.072197
# Epoch: 300/1000 Loss:  0.044613
# Epoch: 400/1000 Loss:  0.027568
# Epoch: 500/1000 Loss:  0.017035
# Epoch: 600/1000 Loss:  0.010527
# Epoch: 700/1000 Loss:  0.006505
# Epoch: 800/1000 Loss:  0.004020
# Epoch: 900/1000 Loss:  0.002484
# Epoch: 1000/1000 Loss:  0.001535
  • 이걸 여러 번 학습해주어 로스값을 줄인다.
  • 한번 사이클을 돌때마다 모델의 예측값과 해당 예측값으로 로스값을 뽑아 기울기를 조정한다.
x_test = torch.FloatTensor([[5]])
y_pred = model(x_test)
y_pred
# tensor([[9.8762]], grad_fn=<AddmmBackward0>)

5를 넣어보면 모델은 9.8762를 예상한다.