AI 개발 공부 공간

AI, 머신러닝, 딥러닝, Python & PyTorch, 실전 프로젝트

딥러닝/딥러닝: 기초 개념

Pytorch의 텐서 연산을 활용하여 단순 선형 회귀 모델 구현

qordnswnd123 2025. 1. 16. 10:43

1.선형 변환과 편향(bias)의 역할

선형 변환은 다음과 같은 수학적 표현으로 정의됩니다.

Y = XW + b

  • X는 입력 데이터를 나타내는 텐서,
  • W는 레이어의 가중치를 나타내는 행렬,
  • b는 편향을 나타내는 벡터,
  • Y는 연산 결과로 얻어지는 출력 데이터 텐서입니다.

이전에는 주로 가중치의 적용, 즉 XW(입력값 x 가중치) 를 통해 예측을 수행했습니다.
이번에는 선형 변환의 또 다른 중요한 구성 요소인 편향 b의 역할에 주목하겠습니다.
편향은 모델의 출력에 더해져, 입력 데이터가 0일지라도 모델이 활성화되는 데 기여하는 파라미터입니다.
이는 뉴런의 활성화 조건을 결정하며 신경망의 출력을 조절하는 데 중요한 역할을 합니다.

편향의 주요 역할은 다음과 같습니다.

  • 결정 경계 조정: 편향은 결정 경계를 조정하여 모델이 더 유연하게 데이터를 분류할 수 있도록 합니다.
    예를 들어, 편향이 없으면 결정 경계가 항상 원점을 통과해야 하지만, 편향을 추가함으로써 이 경계를 이동시킬 수 있습니다.
  • 활성화 함수의 오프셋 조정: 신경망에서 편향은 활성화 함수의 임계값을 조정합니다.
    이는 뉴런이 입력에 대해 얼마나 민감하게 반응할지를 결정하는 데 중요합니다.

선형 회귀의 목적 = 가장 잘 맞는 직선을 찾는 것 = 가장 잘 맞는 직선을 찾도록 W(가중치)와 b(편향)를 찾는것 입니다.


2. 기본 텐서 연산을 사용하여 선형 회귀 모델 구현

1) 데이터 준비

import torch
import torch.optim as optim

x_train = torch.tensor([[1], [2], [3]], dtype=torch.float)
y_train = torch.tensor([[3],[6],[9]], dtype=torch.float)

print(x_train)
print(y_train)

 

tensor([[1.],
        [2.],
        [3.]])
tensor([[3.],
        [6.],
        [9.]])

 

2) 가중치와 편향 초기화

W = torch.zeros(1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)

print(f"가중치:",W)
print(f"편향:",b)
가중치: tensor([0.], requires_grad=True)
편향: tensor([0.], requires_grad=True)

 

※ 가중치와 편향을 0으로 시작하는 이유

가중치와 편향을 0으로 함으로써, 우리의 모델은 시작할 때 아무런 선입견 없이, 데이터에서 패턴을 찾기 시작할 수 있습니다.

이 방법은 특히 간단한 모델에서 잘 작동하는데, 선형 회귀 같은 경우를 생각해보면, 데이터 속 패턴을 조금씩 학습하면서 가중치와 편향을 적절하게 조정해 나가게 됩니다.

하지만 가중치와 편향을 시작하는 방법은 이것만이 전부가 아닙니다.

예에서 시작하는 것 외에도, 우리는 숫자를 무작위로 정해서 시작할 수도 있고, 좀 더 복잡한 Xavier 초기화나 He 초기화 같은 방법을 쓸 수도 있습니다.

이런 다양한 시작 방법은 컴퓨터가 복잡한 문제를 풀 때 더 많은 가능성을 탐색하게 하고, 학습 과정의 질을 원활하게 만들어줍니다.

학습 과정에서 모델 데이터 속에서 최적의 가중치와 편향, 즉 오차를 최소화하는 값을 찾아가는 여정을 시작합니다.

처음에 0으로 모든 가중치와 편향을 설정하는 것은 단지 출발점일 뿐이라고 볼 수 있습니다.

학습이 진행될수록, 모델은 데이터와 잘 맞는 최적의 가중치와 편향을 찾아가게 됩니다.

이 여정을 통해, 모델은 처음에는 몰랐던 많은 것들을 배우면서, 점차 데이터 속 숨은 패턴을 파악하고, 예측을 더 정확하게 할 수 있게 됩니다.

 

※ requires_grad=True의 의미와 중요성

requires_grad=True라는 설정은 우리가 컴퓨터에게 "이 가중치와 편향을 학습하는 동안 계속 업데이트해야 해"라고 말하는 것과 같습니다.

파이토치라는 도구를 사용할 때, 이 설정을 켜면, 컴퓨터가 학습을 하면서 어떤 부분들을 더 개선해야 할지 스스로 알아낼 수 있게 도와줍니다.

즉, requires_grad=True 설정만으로도, 컴퓨터는 손실함수의 결과 점수를 바탕으로, 어떻게 하면 더 나은 점수를 얻을 수 있을지를 계산하고, 그에 따라 가중치와 편향을 조금씩 바꿔가며 학습해 나갑니다.

3) 순전파 과정

pred = x_train * W + b

4) 오차계산

loss_ = torch.mean((pred - y_train) ** 2)

5) 역전파 과정

PyTorch의 .backward() 메서드는 손실 loss_에 대한 기울기를 자동으로 계산하고, 이를 텐서와 관련된 모든 파라미터(가중치와 편향)에 대해 역전파합니다.

loss_.backward()

6) 가중치 업데이트

.step() 메서드는 계산된 기울기(미분값)를 사용하여 각 파라미터(W, b)를 업데이트합니다.

optimizer.step()

7) 반복 학습

한 번의 업데이트로 모든 것이 완벽해지지는 않습니다.

우리의 목표는 '가장 잘 맞는 직선', 즉 최적의 가중치와 편향을 찾는 것입니다.

이를 위해서는 위의 과정을 여러 번 반복 해야 합니다.

이러한 반복 학습 과정을 에포크(epoch) 라고 합니다.

각 에포크는 전체 데이터셋에 대한 하나의 학습 사이클 을 의미하며, 각 사이클마다 손실을 조금씩 줄여가는 것 이 목표입니다.

에폭(epoch)은 전체 데이터 세트에 대해 학습을 한 번 수행하는 것을 의미합니다.

여기서는 10번의 에폭 동안 모델을 학습시키기 위해 for 반복문을 사용합니다.

for epoch in range(10):
    pred = x_train * W + b                   # 순전파 과정
    loss = torch.mean((pred - y_train) ** 2) # 오차 계산
    loss.backward()                          # 역전파 과정
    optimizer.step()                         # 가중치 업데이트

    print(f'Epoch {epoch+1}, Loss: {loss.item()}')

3. 옵티마이저 설정: 확률적 경사 하강법(Stochastic Gradient Descent, SGD)

결론적으로, 확률적 경사 하강법(SGD)은 경사 하강법(GD)과 비교하여 계산 비용이 낮은 장점을 가지고 있습니다.
이는 한 번의 반복에서 하나의 데이터 포인트를 무작위로 선택하여 손실 함수의 기울기를 계산하고, 이를 사용하여 가중치를 업데이트하기 때문입니다.

optimizer = optim.SGD([W, b], lr=0.01)

4. 가중치와 편향 최적화를 위한 선형 회귀 학습 과정

# 전체 에폭 수를 정의합니다.
epochs = 1000

for epoch in range(epochs):
    # 순전파: 모델을 통해 예측값을 계산합니다.
    pred = x_train * W + b
    
    # 손실 함수를 계산합니다.
    loss = torch.mean((pred - y_train) ** 2)
    
    # 역전파: 손실 함수의 기울기(gradient)를 계산합니다.
    optimizer.zero_grad()  # 기울기를 0으로 초기화
    loss.backward()  # 역전파 수행
    
    # 옵티마이저를 사용하여 파라미터를 업데이트합니다.
    optimizer.step()
    
    # 일정 에폭마다 학습 상태를 출력합니다.
    if (epoch+1) % 100 == 0:
        print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}, W: {W.item():.3f}, b: {b.item():.3f}')
Epoch [100/1000], Loss: 0.1089, W: 2.618, b: 0.869
Epoch [200/1000], Loss: 0.0673, W: 2.699, b: 0.683
Epoch [300/1000], Loss: 0.0416, W: 2.764, b: 0.537
Epoch [400/1000], Loss: 0.0257, W: 2.814, b: 0.422
Epoch [500/1000], Loss: 0.0159, W: 2.854, b: 0.332
Epoch [600/1000], Loss: 0.0098, W: 2.885, b: 0.261
Epoch [700/1000], Loss: 0.0061, W: 2.910, b: 0.205
Epoch [800/1000], Loss: 0.0037, W: 2.929, b: 0.161
Epoch [900/1000], Loss: 0.0023, W: 2.944, b: 0.127
Epoch [1000/1000], Loss: 0.0014, W: 2.956, b: 0.100

※ 초기화를 하는 이유

PyTorch에서는 기울기를 계산할 때마다 .backward()를 호출하면 기울기가 누적되기 때문에, 매 반복(iteration)마다 기울기를 0으로 초기화해줘야 합니다.

만약 이 초기화 과정을 생략하면, 기울기가 계속 누적되어 잘못된 방향으로 파라미터를 업데이트하게 되며, 이는 모델 학습에 부정적인 영향을 미치게 됩니다.

올바른 학습을 위해서는 각 반복마다 독립적인 기울기를 계산하여 파라미터를 업데이트해야 합니다. optimizer.zero_grad()를 호출함으로써, 우리는 각 반복에서 새로운 기울기 계산을 시작하게 됩니다.

이렇게 하지 않으면, 이전 반복에서 계산된 기울기가 누적되고 새로운 기울기에 영향을 미치게 됩니다.

이는 이전에 계산한 다음 파라미터에 영향을 미치지 않도록 하여, 왜곡되지 않고 독립적인 학습이 이루어질 수 있도록 합니다.

이 과정은 특히 복잡한 모델과 대규모 데이터를 사용할 때 중요하며, 이는 모델의 성능을 최적화하고, 오버피팅(과적합)이나 언더피팅(과소적합) 같은 문제를 방지하는 데 도움을 줍니다.

따라서, optimizer.zero_grad() 호출은 모델을 학습시키는 과정에서 필수적인 단계로 간주됩니다.


5. 모델을 사용한 예측

test_x = torch.tensor([[10]], dtype=torch.float)

with torch.no_grad():
    pred_y = test_x * W + b
    print("훈련 후 입력이 10일 때의 예측값 :", pred_y.item())
훈련 후 입력이 10일 때의 예측값 : 29.661359786987305

※ 예측 수행 전 설정

with torch.no_grad(): 구문은 PyTorch에게 자동 미분 기능을 비활성화하도록 지시합니다.

이는 추론(inference) 또는 예측 수행 시에는 모델 파라미터의 기울기를 계산할 필요가 없기 때문에 사용합니다.

이렇게 하면 메모리 사용량을 줄이고 연산 속도를 향상시킬 수 있습니다.

※ 직접 계산을 통한 예측

pred_y = test_x * W + b는 준비된 입력 데이터 test_x에 학습된 모델의 가중치(W)와 편향(b)을 직접 적용하여 예측값 pred_y를 계산합니다.

이 방식은 모델의 내부 forward 메소드를 사용하지 않고, 명시적으로 가중치와 편향을 사용하여 예측값을 구하는 접근 방법입니다.

특히 선형 회귀와 같이 모델의 계산이 단순한 경우에 유용하게 사용될 수 있습니다.