[PyTorch] Linear Regression 구현 및 디버깅

조유솔·2024년 8월 11일
0
post-thumbnail

구현을 위한 코드를 하나하나 뜯어보자

데이터 출처: https://www.kaggle.com/datasets/abhishek14398/salary-dataset-simple-linear-regression




1. 데이터 불러와서 특징/목적변수 분리

!kaggle datasets download -d abhishek14398/salary-dataset-simple-linear-regression
!unzip salary-dataset-simple-linear-regression.zip

import torch

import pandas as pd
data = pd.read_csv("Salary_dataset.csv", sep = ",", header = 0)

x = data.iloc[:, 1].values 
t = data.iloc[:, 2].values 
  • 캐글에서 데이터 다운받기: !kaggle datasets download -d 이게 명령어다. 뒤에 유저명/데이터명 넣으면 된다.그리고 다운로드된 파일을 unzip 후 사용해주면된다.

  • header

pd.read_csv(…., header = 0) 에서 header = 0 이란 ‘인덱스 0의 행을 데이터프레임의 열 이름으로 사용’ 이라는 의미

  • values

x = data.iloc[:, 1].values 에서 values 속성은 Pandas DataFrame이나 Series에서 데이터를 NumPy 배열 형태로 반환하는 역할을 한다




2. 스케일링(데이터 표준화)

from sklearn.preprocessing import StandardScaler 

scaler_x = StandardScaler() 
x_scaled = scaler_x.fit_transform(x.reshape(-1, 1)) 

scaler_t = StandardScaler()
t_scaled = scaler_t.fit_transform(t.reshape(-1, 1)) 

x_tensor = torch.tensor(x_scaled, dtype=torch.float32).view(-1, 1) 
t_tensor = torch.tensor(t_scaled, dtype=torch.float32).view(-1, 1)

앞서 이론 포스팅에서 한 번 다룬 바 있는 코드이다.
라이브러리 불러오기 → 스케일러 객체 생성 → 2차원 특징변수 배열 주입해 표준화 → 목적 변수에도 같은 방식 적용 → 2차원 텐서로 변경한다.




3. 모델 클래스, 인스턴스 정의

import torch.nn as nn 

class LinearRegressionModel(nn.Module): 
    def __init__(self):  
        super(LinearRegressionModel, self).__init__() 
        self.linear = nn.Linear(1, 1)
        
    def forward(self, x_tensor): 
        y = self.linear(x_tensor) 
        return y
        
model = LinearRegressionModel()
  • torch에서 nn(신경망)을 불러온 다음 LR을 위한 클래스를 정의하고, 모델 인스턴스를 생성한다.
  • 이 때 자식클래스인 LinearRegressionModel은 부모 클래스인 nn.Module을 상속받는다(=자식 클래스가 부모 클래스의 기능을 자동으로 가지는 것. 추가적인 기능을 정의하거나 기존 기능을 확장할 수 있음)
  • def init(생성자)은 해당 클래스를 통해 객체를 처음 생성할 때 해야할 일을 정의하며, 처음 생성하는 순간 딱 한번 실행된다.
    • 이 클래스는 super를 통해 부모 모듈인 nn.Module의 생성자를 그대로 받아온다.
    • self.linear라는 클래스 속성에 입력과 출력의 차원이 모두 1인 선형 회귀 모델을 정의한다
  • forward에 순전파 메서드를 정의한다.
    • 해당 메서드는 텐서 형태의 특성 변수를 입력으로 받는다.
    • 앞서 정의한 선형 레이어(linear)를 통해 계산한 예측값을 반환한다.




4. GPU 설정

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

model.to(device) 

x_tensor = x_tensor.to(device) 
t_tensor = t_tensor.to(device)
  • cuda의 사용가능성을 체크하고, cuda와 cpu 중 사용할 device를 정의한다
  • GPU에 전송해야하는 것은 다음과 같다.
    • 생성한 모델 인스턴스
    • 입력 변수 데이터
    • 레이블 변수 데이터



5. Optimizer, Loss Function 정의

import torch.optim as optim

loss_function = nn.MSELoss() # 옵티마이저 정의
optimizer = optim.SGD(model.parameters(), lr = 0.01)  # 학습률을 적절히 설정
  • optimizer를 설정하기 위해 torch.optim을 불러온 다음,
  • 선형 회귀이므로 Loss function과 Optimizer를 각각 MSE, SGD로 설정하였다.
    • 이때, optim.SGD는 SGD의 알고리즘을 사용하며, model.parameters()를 통해 현재 모델의 파라미터를 전달받고 학습률 0.01을 전달받아 optimizer객체를 세팅한다.




6. 모델 학습

num_epochs = 1000  
loss_list = []  

for epoch in range(num_epochs):
    y = model(x_tensor) 
    loss = loss_function(y, t_tensor)  

    optimizer.zero_grad() 
    loss.backward()  
    optimizer.step()  

    loss_list.append(loss.item())

    if (epoch+1) % 100 == 0: 
        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item()}')

        for name, param in model.named_parameters():
            print(f'{name}: {param.data}')
         
  • num_epochs에 에폭 수를 1000으로 지정하면 for 문이 0부터 999번까지 실행된다.
  • loss_list 나중에 그래프를 그리려고 손실값을 저장하는 리스트이다.
  • for 문에서 에포크수만큼 학습이 이루어진다.
    • model(): 설명 변수 텐서를 입력, 예측 변수값 계산
    • loss_function(,): 예측값과 실제값 입력, 손실값 계산
    • zero_grad(): 이전 단계에 계산된 gradient를 0으로 초기화(누적없이 새로 계산하기 위함)
    • backward():현재 loss에 대한 gradient를 계산
    • step(): 계산된 gradient를 사용해 parameter update
    • model.named_parameters(): 모델의 파라미터 이름과 그 값을 반환(각 파라미터의 이름과 값을 출력하기 때문에 학습과정 중 파라미터가 어떻게 변화하고 있는지 확인 가능하다. 디버깅에 유용)



7. 테스트 데이터에 적용하기

import numpy as np

def predict_test_data(test_data): 
    test_scaled = scaler_x.transform(test_data.reshape(-1, 1))
    test_tensor = torch.tensor(test_scaled, dtype=torch.float32).view(-1, 1).to(device) 

    model.eval()  
    with torch.no_grad():
        predictions_scaled = model(test_tensor)

    predictions = scaler_t.inverse_transform(predictions_scaled.cpu().numpy())
    return predictions

test_experience = np.array([1.0, 2.0, 7.0])  
predicted_salaries = predict_test(test_experience)
  • predict_test_data: 테스트 데이터를 입력받아 예측값 반환하는 함수
    • transform() 표준화할 데이터를 입력받고 표준화를 진행해 반환한다. 앞서 학습용 데이터에 fit된 scaler를 사용해 테스트 데이터를 표준화한다.
    • torch.tensor().view(-1,1).to(device): 표준화를 위해 넘파이 배열 형태로 두었던 데이터를 다시 텐서로 변환한다.
    • model.eval(): 모델을 평가모드로 전환한다.
      • 이때, 평가모드란?
        학습 모드(model.train())에서는 일부 레이어가 학습을 돕기 위해 랜덤하게 동작한다. 하지만 PyTorch에서 model.eval()은 Dropout이나 배치 정규화(각 배치에 맞게 평균과 분산을 계산하여 표준화하는 것)같은 레이어가 랜덤하게 동작하는 것이 비활성화된다.
    • with torch.no_grad(): 이 블록 내에서는 gradient계산을 비활성화. 학습이 아니라 단순히 모델의 출력을 계산하려는 경우에 메모리 및 시간 비용을 줄일 수 있음
    • inverse_transform: 표준화를 해제하여 예측값을 원래 스케일로 돌려놓는다




8. 다 잘 했는데, RuntimeError가 발생한다면?

PyTorch에서 텐서(Tensor)와 모델의 연산은 같은 디바이스에서 이루어져야 한다. 모델과 입력 데이터 중 하나는 CUDA, 하나는 CPU에 할당되어 있다면 연산이 불가능하다(CPU와 GPU가 메모리를 따로 관리하기 때문)!

모델, 옵티마이저, 데이터가 모두 같은 메모리를 사용하도록 해야한다!
: 일반적으로 옵티마이저는 모델의 파라미터를 추적하므로 모델과 같은 디바이스에 저장된다고 한다. 데이터와 모델의 디바이스에 신경쓰면 되겠다.

디바이스 확인, 어떻게?

# 모델이 어느 디바이스에 있는지 확인
print(model.device)

# 특정 텐서가 어느 디바이스에 있는지 확인
print(x_tensor.device)

device속성을 통해 확인 가능하다.



9. 이해 안되었던 부분

self.linear라는 속성에 모델이 들어간다. 메서드에 들어갈 줄 알았는데, 왜 속성에 들어가지?

속성에 모델이 들어가는 이유: self.linear에 회귀 모델(선형 계층)을 속성으로 정의하는 것은, 모델의 구조를 설정하고 이 구조를 여러 번 사용할 수 있게 하기 위함.

w,b의 초기값은 어떻게 정해지는 거지?

W, b의 초기값: 초기값은 nn.Linear 계층을 정의할 때 PyTorch가 자동으로 무작위로 설정!

0개의 댓글