Search

Pytorch Workflow with Regression

대분류
인공지능/데이터
소분류
ML/DL 정리 노트
유형
딥 러닝
부유형
Introduction Pytorch
최종 편집 일시
2024/10/27 15:18
생성 일시
2024/10/17 00:21
14 more properties

Pytorch Workflow

import torch from torch import nn # nn contains all of PyTorch's building blocks for neural networks import matplotlib.pyplot as plt plt.ion() # Check PyTorch version > torch.__version__ 2.4.0+cu121
Python
복사

Part 1: 데이터를 숫자로 변환 (Turn data into numbers)

첫 번째 단계는 다양한 형태의 입력 데이터를 모델이 처리할 수 있는 숫자 형태로 변환하는 과정이다.
입력 데이터는 다음과 같은 형태로 들어올 수 있다:
이미지 데이터: 음식 사진, 또는 다른 시각적 정보
텍스트 데이터: 트윗, 뉴스 기사 등의 문장
음성 데이터: 음성 명령, 대화 등
이러한 데이터는 각각 고유한 방식으로 **숫자 데이터(numerical encoding)**로 변환된다.
예를 들어:
이미지의 경우, 픽셀값을 숫자로 표현할 수 있으며, 각 픽셀은 특정한 색상 값을 가진다.
텍스트의 경우, 단어 또는 문장을 벡터화(벡터 임베딩)하여 숫자로 변환한다. 각 단어는 고유한 숫자 벡터로 변환되며, 이는 단어 간 유사성을 반영한다.
음성 데이터는 진폭(amplitude) 값이나 주파수(frequency) 값으로 변환되어 수치 데이터로 표현된다.
그림에서는 이러한 입력 데이터가 숫자 배열(예: [[116, 78, 15], [117, 43, 96], [125, 87, 23]])로 변환되고 있음을 보여준다.
이 단계에서 중요한 것은, 모델이 학습할 수 있도록 모든 입력이 숫자로 인코딩된다는 점이다.

Part 2: 패턴을 학습하는 모델 구축 (Build model to learn patterns in numbers)

두 번째 단계는 딥러닝 모델이 숫자 데이터를 입력받아 패턴을 학습하는 과정이다. 그림의 중앙에는 신경망(neural network) 구조가 보이며, 이 네트워크는 데이터를 처리하고 학습한다.
이 과정에서 모델은 다음과 같은 작업을 수행한다:
표현 학습(representation learning): 입력된 숫자 데이터를 바탕으로 패턴을 찾고, 중요한 특징(feature)을 추출한다. 이는 가중치(weights) 및 매개변수(parameters)를 학습하는 과정을 포함한다.
숫자 간의 관계 학습: 다양한 레이어(layer)를 거치며, 각 데이터가 가지는 의미 있는 패턴을 파악하게 된다. 예를 들어, 음식 사진을 통해 음식의 종류를 학습하거나, 텍스트를 통해 스팸 여부를 판별하는 과정을 학습한다.
그림에서 출력되는 Representation outputs는 모델이 입력 데이터를 바탕으로 학습한 패턴을 바탕으로 숫자 벡터로 출력한 결과이다.
예시로 [(0.983, 0.004, 0.013), (0.110, 0.889, 0.001), (0.023, 0.027, 0.985)] 같은 벡터가 표현된다.

Outputs (최종 출력)

최종적으로 모델은 학습된 패턴을 바탕으로 결과값을 출력한다. 예를 들어:
이미지 데이터의 경우, "라면", "스파게티"와 같은 음식 종류를 분류할 수 있다.
텍스트 데이터의 경우, 스팸 메시지를 분류하여 "스팸 아님(Not Spam)"이라고 판단할 수 있다.
음성 데이터의 경우, "Hey Siri, what’s the weather today?"와 같은 명령을 이해하여 날씨 정보를 제공하는 것처럼, 모델이 적절한 응답을 반환한다.

1.데이터 준비

데이터 생성

선형 회귀식: y=weightX+biasy=weight∗X+bias
# Create *known* parameters weight = 0.7 bias = 0.3
Python
복사
# Create data start = 0 end = 1 step = 0.02 # torch.arange(start, end, step) -> [0, 0.02, 0.04, 0.06 ... ] -> (50,) # (50,).unsqueeze(dim=1) -> (50,1) X = torch.arange(start, end, step).unsqueeze(dim=1) > X.shape torch.Size([50, 1])
Python
복사
y = weight * X + bias > y.shape torch.Size([50, 1])
Python
복사
> X[:5], y[:5] # X는 변환 전, y는 변환 후 (tensor([[0.0000], [0.0200], [0.0400], [0.0600], [0.0800]]), tensor([[0.3000], [0.3140], [0.3280], [0.3420], [0.3560]]))
Python
복사

데이터 분리

# Create train/test split train_split = int(0.8 * len(X)) # 80% of data used for training set, 20% for testing X_train, y_train = X[:train_split], y[:train_split] X_test, y_test = X[train_split:], y[train_split:] > len(X_train), len(y_train), len(X_test), len(y_test) (40, 40, 10, 10)
Python
복사

데이터 확인

def plot_predictions( train_data=X_train, train_labels=y_train, test_data=X_test, test_labels=y_test, predictions=None): """ Plots training data, test data and compares predictions. """ plt.figure(figsize=(10, 7)) # Plot training data in blue plt.scatter(train_data, train_labels, c="b", s=4, label="Training data") # Plot test data in green plt.scatter(test_data, test_labels, c="g", s=4, label="Testing data") if predictions is not None: # Plot the predictions in red (predictions were made on the test data) plt.scatter(test_data, predictions, c="r", s=4, label="Predictions") # Show the legend plt.legend(prop={"size": 14}); plot_predictions();
Python
복사

2.모델링 정의

모델 정의

# 선형 회귀 모델 클래스를 생성 class LinearRegressionModel(nn.Module): # <- PyTorch에서 거의 모든 것은 nn.Module을 상속받아 만듦 # (신경망의 기본 블록이라고 생각할 수 있음) def __init__(self): # super().__init__() -> 부모 클래스를 인스턴스화 super().__init__() # 가중치(weights)를 초기화 -> 랜덤 값으로 생성 self.weights = nn.Parameter(torch.randn(1, # <- 학습이 진행되면서 조정될 가중치를 무작위 값으로 설정 dtype=torch.float), # <- PyTorch는 기본적으로 float32 형식을 사용 requires_grad=True) # <- 이 값을 경사하강법(gradient descent)으로 업데이트할 수 있는가? # 편향(bias)을 초기화 -> 랜덤 값으로 생성 self.bias = nn.Parameter(torch.randn(1, # <- 학습이 진행되면서 조정될 편향을 무작위 값으로 설정 dtype=torch.float), # <- PyTorch는 기본적으로 float32 형식을 사용 requires_grad=True) # <- 이 값을 경사하강법으로 업데이트할 수 있는가? # forward는 모델에서 계산 과정을 정의 def forward(self, x: torch.Tensor) -> torch.Tensor: # <- "x"는 입력 데이터 (예: 훈련 또는 테스트 데이터의 특징) # 선형 회귀 공식 pred = self.weights * x + self.bias # <- 이것은 선형 회귀 공식 (y = m*x + b) return pred # 선형 회귀 모델의 예측값을 반환
Python
복사
# 생성한 nn.Module 서브클래스 내의 nn.Parameter들을 확인한다 > list(model_0.parameters()) [Parameter containing: tensor([0.3367], requires_grad=True), Parameter containing: tensor([0.1288], requires_grad=True)] # List named parameters > model_0.state_dict() OrderedDict([('weights', tensor([0.3367])), ('bias', tensor([0.1288]))])
Python
복사

학습하기 전 모델 예측

# Make predictions with model # with torch.no_grad()와 의미가 같다!!! # -> 모델의 파라미터가 변경되는 것을 방지한다. with torch.inference_mode(): y_preds = model_0(X_test)
Python
복사
# Check the predictions > print(f"Number of testing samples: {len(y_test)}") > print(f"Number of predictions made: {len(y_preds)}") Number of testing samples: 10 Number of predictions made: 10 > plot_predictions(predictions=y_preds)
Python
복사
아래 그래프와 같이 모델 학습을 하기 전에는 예측과 살제가 많이 차이나는 것을 확인할 수 있다.

3.모델 학습

아래 그래프와 같이 실제값과 예측값의 차이를 줄이는 방향으로 모델이 학습을 하게 된다.

Loss function & Optimizer

Loss function: 실제값과 예측값의 차이를 계산하는 함수
Optimizer: 모델이 학습하는 방법

Loss function

회귀 작업 (Regression Tasks)
이 손실 함수는 예측 값과 실제 값의 차이를 제곱하여 평균을 낸 값이다.
오차가 클수록 큰 패널티를 부과하며, 회귀 문제에서 가장 흔히 사용된다.
공식: MSE=1ni=1n(yiyi^)2\text{MSE} = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y_i})^2
예측 값과 실제 값 사이의 차이를 절대값으로 구해 평균을 낸 값이다.
오차에 대한 패널티가 MSE보다 덜 극단적이다.
공식: MAE=1ni=1nyiyi^\text{MAE} = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y_i}|
이진 분류 작업 (Binary Classification Tasks)
nn.BCELoss (Binary Cross Entropy Loss)
이 손실 함수는 이진 분류 문제에서 사용되며, 예측된 확률과 실제 클래스 값(0 또는 1) 간의 차이를 측정한다.
주로 시그모이드 함수와 함께 사용되어 예측 값을 0과 1 사이의 확률로 변환한 후 손실을 계산한다.
공식: BCE=1ni=1n[yilog(yi^)+(1yi)log(1yi^)]\text{BCE} = -\frac{1}{n} \sum_{i=1}^{n} [y_i \log(\hat{y_i}) + (1 - y_i) \log(1 - \hat{y_i})]
nn.Sigmoidnn.BCELoss를 결합한 형태
따로 시그모이드 함수를 적용할 필요 없이 바로 사용 가능하다.
내부적으로 시그모이드를 적용한 후 BCE 손실을 계산한다.
이 방식은 수치적으로 더 안정적이며, 따로 시그모이드를 적용하는 것보다 성능이 좋다.
다중 클래스 분류 작업 (Multi-class Classification Tasks)
nn.CrossEntropyLoss (교차 엔트로피 손실)
nn.LogSoftmaxnn.NLLLoss(Negative Log Likelihood Loss)를 결합한 형태
LogSoftmax는 각 클래스의 예측 확률을 구하고, NLLLoss는 예측 확률과 실제 클래스 간의 차이를 기반으로 손실을 계산한다.
공식: CrossEntropy=i=1Cyilog(pi^)\text{CrossEntropy} = -\sum_{i=1}^{C} y_i \log(\hat{p_i})
C : 클래스의 수
pi^\hat{p_i} : 모델이 예측한 각 클래스에 대한 확률
# Create the loss function loss_fn = nn.L1Loss() # MAE loss is same as L1Loss >> L2Loss(Mean Squared Error): nn.MSELoss
Python
복사
자주 사용하는 Optimizations
# 옵티마이저를 생성한다 optimizer = torch.optim.SGD(params=model_0.parameters(), # 최적화할 대상 모델의 파라미터들 lr=0.01) # 학습률 # 학습률이 높을수록 옵티마이저가 파라미터를 더 많이 변경하지만 안정성이 떨어진다. # 낮을수록 변경이 적어져 시간이 더 걸릴 수 있다
Python
복사

학습

학습전 준비물
데이터: features, target
train datatest data
lose function
Optimization
model

Training loop

1.
Forward Pass (순방향 전파)
역할
모델이 입력 데이터를 받아 forward() 메서드를 통해 계산을 수행한다.
이를 통해 모델은 입력 데이터를 기반으로 예측값을 생성한다.
설명
이 단계에서는 모델이 학습 데이터를 통과시키면서 예측 값을 계산한다.
이 계산은 모델이 학습할 특징(feature)과 현재까지의 파라미터(weights, bias)를 기반으로 이루어진다.
코드 예시: model(x_train)
x_train은 훈련 데이터를 의미하며, 모델은 이 데이터를 사용해 예측 값을 만든다.
2.
Calculate the Loss (손실 계산)
역할
모델의 예측값과 실제 정답(레이블)을 비교하여 손실을 계산한다.
이 손실 값은 모델이 얼마나 잘못 예측했는지를 나타낸다.
설명
손실 함수는 예측 값과 실제 값 사이의 차이를 측정한다.
회귀 작업에서는 주로 MSE, 분류 작업에서는 CrossEntropy 등이 사용된다.
코드 예시: loss = loss_fn(y_pred, y_train)
y_pred는 모델이 예측한 값이고, y_train은 실제 값(정답 레이블)이다.
loss_fn은 손실 함수(예: MSELoss, CrossEntropyLoss 등)를 의미한다.
3.
Zero Gradients (기울기 초기화)
역할
매번 학습 스텝을 시작할 때, 이전 스텝에서 계산된 기울기(gradient)를 0으로 초기화한다.
기울기는 자동으로 축적되기 때문에, 이를 초기화하지 않으면 잘못된 값이 누적될 수 있다.
설명
경사 하강법(gradient descent)을 사용해 모델을 업데이트할 때, 매번 새로운 기울기를 계산할 수 있도록 기존의 기울기 값을 0으로 설정한다.
코드 예시: optimizer.zero_grad()
옵티마이저(optimizer)가 모든 파라미터의 기울기를 0으로 초기화한다.
4.
Perform Backpropagation (역전파 수행)
역할
손실 함수를 기준으로 모든 학습 가능한 파라미터에 대한 기울기를 계산한다.
이를 통해 파라미터가 어느 방향으로 얼마나 업데이트되어야 하는지 결정한다.
설명
역전파(backpropagation)는 손실에 대한 각 파라미터의 기울기를 계산하는 과정이다.
PyTorch는 이를 자동으로 처리하며, 이를 통해 가중치와 편향을 업데이트할 수 있는 정보가 제공된다.
코드 예시: loss.backward()
backward() 함수는 손실 함수의 값이 각 파라미터에 미치는 영향을 계산한다.
5.
Update the Optimizer (옵티마이저 업데이트)
역할
앞서 계산된 기울기를 기반으로 파라미터를 업데이트한다.
이는 경사 하강법 등의 최적화 기법을 통해 이루어진다.
설명
옵티마이저는 모델의 파라미터(weights, bias)를 업데이트하여 손실을 줄이는 방향으로 모델을 학습시킨다.
가장 일반적으로 사용되는 옵티마이저는 Adam 또는 SGD이다.
코드 예시: optimizer.step()
옵티마이저는 계산된 기울기를 바탕으로 모델의 파라미터를 업데이트한다.

Testing Loop

학습 실행(with 코드)

# 재현성을 위해 랜덤 시드를 설정한다 torch.manual_seed(42) # 에폭 수를 설정한다 (모델이 학습 데이터를 몇 번 반복해서 학습할지 결정한다) epochs = 100 # 손실 값을 추적하기 위해 빈 리스트를 생성한다 train_loss_values = [] test_loss_values = [] epoch_count = [] for epoch in range(epochs): ######################################################################### ### 학습 루프 ######################################################################### # 모델을 학습 모드로 전환한다 (모델의 기본 상태이다) model_0.train() # 1. 학습 데이터에 대해 순전파를 수행한다 (forward() 메서드를 사용한다) y_pred = model_0(X_train) # print(y_pred) # 2. 손실을 계산한다 (모델의 예측과 실제 값의 차이를 측정한다) loss = loss_fn(y_pred, y_train) # 3. 옵티마이저의 기울기를 초기화한다 optimizer.zero_grad() # 4. 손실에 대해 역전파를 수행한다 loss.backward() # 5. 옵티마이저를 업데이트한다 optimizer.step() ######################################################################### ### 테스트 루프 ######################################################################### # 모델을 평가 모드로 전환한다 model_0.eval() with torch.inference_mode(): # 1. 테스트 데이터에 대해 순전파를 수행한다 test_pred = model_0(X_test) # 2. 테스트 데이터에 대한 손실을 계산한다 test_loss = loss_fn(test_pred, y_test.type(torch.float)) # 예측값은 torch.float 데이터 타입으로 나오므로, 비교는 동일한 타입의 텐서로 수행해야 한다 # 진행 상황을 출력한다 if epoch % 10 == 0: epoch_count.append(epoch) # loss.detach() -> loss가 포함된 텐서를 현재 디바이스에서 분리한다 # loss.detach().numpy() -> torch 텐서를 numpy 배열로 변환한다 train_loss_values.append(loss.detach().numpy()) test_loss_values.append(test_loss.detach().numpy()) print(f"Epoch: {epoch} | MAE Train Loss: {loss} | MAE Test Loss: {test_loss} ")
Python
복사

학습 결과

# Plot the loss curves plt.plot(epoch_count, train_loss_values, label="Train loss") plt.plot(epoch_count, test_loss_values, label="Test loss") plt.title("Training and test loss curves") plt.ylabel("Loss") plt.xlabel("Epochs") plt.legend();
Python
복사
# Find our model's learned parameters print("The model learned the following values for weights and bias:") print(model_0.state_dict()) print("\nAnd the original values for weights and bias are:") print(f"weights: {weight}, bias: {bias}")
Python
복사
The model learned the following values for weights and bias: OrderedDict([('weights', tensor([0.5784])), ('bias', tensor([0.3513]))]) And the original values for weights and bias are: weights: 0.7, bias: 0.3
Python
복사

4.모델 평가

# 1. 모델을 평가 모드로 전환한다 model_0.eval() # 2. 추론 모드 컨텍스트 매니저를 설정한다 with torch.inference_mode(): # 3. 모델과 데이터가 동일한 디바이스에서 계산되도록 한다 # 현재는 디바이스에 독립적인 코드를 설정하지 않았으므로, 데이터와 모델은 기본적으로 CPU에 있다 # model_0.to(device) # X_test = X_test.to(device) y_preds = model_0(X_test) > y_preds[:5] tensor([[0.8141], [0.8256], [0.8372], [0.8488], [0.8603]]) > plot_predictions(predictions=y_preds)
Python
복사

5.모델 저장

주요 메서드

torch.save

역할: 모델, 텐서, 또는 기타 파이썬 객체(예: 딕셔너리 등)를 디스크에 저장한다. 이 메서드는 파이썬의 pickle 유틸리티를 사용해 객체를 직렬화하여 저장한다.
설명: 모델을 학습한 후, 나중에 사용할 수 있도록 모델을 파일로 저장할 때 사용된다. 이때 텐서, 모델, 또는 딕셔너리 형태의 객체 모두 저장 가능하다.

torch.load

역할: **pickle*을 사용해 저장된 파이썬 객체(예: 모델, 텐서, 딕셔너리 등)를 디스크에서 불러온다. 불러온 객체를 메모리에 로드하며, 어느 디바이스(CPU, GPU)로 로드할지도 설정할 수 있다.
설명: 모델을 저장한 후, 이를 다시 불러와서 추론 또는 추가 학습을 할 때 사용된다. 또한, 저장된 텐서나 딕셔너리도 동일한 방식으로 불러올 수 있다.

torch.nn.Module.load_state_dict

역할: 모델의 파라미터 딕셔너리를 불러온다. 이 파라미터 딕셔너리는 모델의 가중치와 편향을 저장하는데 사용되며, 저장된 state_dict() 객체를 이용해 모델을 복원할 수 있다.
설명: state_dict()는 PyTorch 모델의 모든 학습 가능한 파라미터(가중치 및 편향 등)를 저장한 딕셔너리 객체이다. 이 메서드는 저장된 파라미터를 불러와서 모델을 동일한 상태로 복원하는 데 사용된다.

save model

from pathlib import Path # 1. 모델 디렉토리를 생성한다 MODEL_PATH = Path("models") MODEL_PATH.mkdir(parents=True, exist_ok=True) # mkdir: 디렉토리를 생성한다 # 2. 모델 저장 경로를 생성한다 MODEL_NAME = "01_pytorch_workflow_model_0.pth" MODEL_SAVE_PATH = MODEL_PATH / MODEL_NAME # 3. 모델의 상태 사전을 저장한다 print(f"모델을 다음 경로에 저장한다: {MODEL_SAVE_PATH}") torch.save(obj=model_0.state_dict(), # state_dict()만 저장하면 모델이 학습한 파라미터들만 저장된다 f=MODEL_SAVE_PATH)
Python
복사
> model_0.state_dict() OrderedDict([('weights', tensor([0.5784])), ('bias', tensor([0.3513]))])
Python
복사

load model

# Instantiate a new instance of our model (this will be instantiated with random weights) # 학습하지 않은 모델 생성 loaded_model_0 = LinearRegressionModel() # 학습하지 않은 모델의 파라미터 확인 > loaded_model_0.state_dict() OrderedDict([('weights', tensor([0.3367])), ('bias', tensor([0.1288]))])
Python
복사
# Load the state_dict of our saved model (this will update the new instance of our model with trained weights) # 이미 학습된 모델의 파라미터 적용 loaded_model_0.load_state_dict(torch.load(f=MODEL_SAVE_PATH)) # 이미 학습된 모델 파라미터 적용 확인 > loaded_model_0.state_dict() OrderedDict([('weights', tensor([0.5784])), ('bias', tensor([0.3513]))])
Python
복사
# 1. 로드한 모델을 평가 모드로 전환한다 loaded_model_0.eval() # 2. 추론 모드 컨텍스트 매니저를 사용하여 예측을 수행한다 with torch.inference_mode(): loaded_model_preds = loaded_model_0(X_test) # 로드한 모델로 테스트 데이터에 대해 순전파를 수행한다 # Compare previous model predictions with loaded model predictions (these should be the same) > y_preds == loaded_model_preds tensor([[True], [True], [True], [True], [True], [True], [True], [True], [True], [True]])
Python
복사