Data_Analysis_Track_33/Python

Python_Deeplearning_pytorch_02(pytorch_linear_regression)

lsc99 2023. 10. 16. 19:04

LinearRegression from Scratch

구현할 것

  • 공부시간과 성적간의 관계를 모델링한다.
        - 머신러닝 모델(모형)이란 수집한 데이터를 기반으로 입력값(Feature)와 출력값(Target)간의 관계를 하나의 공식으로 정의한 함수이다. 그 공식을 찾는 과정을 모델링이라고 한다.
        - 이 예제에서는 공부한 시간으로 점수를 예측하는 모델을 정의한다.
        - 입력값과 출력값 간의 관계를 정의할 수있는 다양한 함수(공식)이 있다. 여기에서는 딥러닝과 관계가 있는 Linear Regression 을 사용해본다.
  • 우리가 수집한 공부시간과 점수 데이터를 바탕으로 둘 간의 관계를 식으로 정의 할 수 있으면 내가 몇시간 공부하면 점수를 얼마 받을 수 있는지 예측할 수 있게 된다.
  • 수집한 데이터를 기반으로 앞으로 예측할 수있는 모형을 만드는 것이 머신러닝 모델링이다.

입력데이터 : 공부시간, 출력데이터 : 성적

 

학습(훈련) 데이터셋 만들기

  • 모델을 학습시키기 위한 데이터셋을 구성한다.
  • 입력데이터와 출력데이터를 따로 행렬로 구성한다.
  • 같은 데이터 포인트의 입력, 출력 데이터를 같은 index에 정의한다.

모델링

모델 정의

  • Feature와 Target간의 관계를 수식으로 정의한다.
  • 여기서는 공부시간(Feature)와 점수(Target)간의 관계를 정의하는데 선형회귀(Linear Regression) 모델을 가설로 세우고 모델링을 한다.
        - 많은 머신러닝 연구자들이 다양한 종류의 데이터에 관계를 예측할 수 있는 여러 알고리즘을 연구했다.
        - 선형회귀 모델은 입력데이터와 출력데이터가 선형관계(비례 또는 반비례 관계)일때 좋은 성능을 나타낸다.
      

 

가설

  • 아직은 이 식이 맞는지 틀린지는 알 수없기 때문에 이 식을 가설(hypothesis) 라고 한다.
  • 가설을 세우고 모델링을 한 뒤 검증을 해서 좋은 예측결과를 내면 그 가설을 최종 결과 모델로 결정한다. 예측결과가 좋지 않을 경우 새로운 가설로 모델링을 한다.

 

선형회귀 (Linear Regression)

  • Feature들의 가중합을 이용해 Target을 추정한다.
  • Feature에 곱해지는 가중치(weight)들은 각 Feature가 Target에 얼마나 영향을 주는지 영향도가 된다.
        - 음수일 경우는 target값을 줄이고 양수일 경우는 target값을 늘린다.
        - 가중치가 0에 가까울 수록 target에 영향을 주지 않는 feature이고 0에서 멀수록 target에 많은 영향을 준다.
  • 모델 학습과정에서 가장 적절한 Feature의 가중치를 찾아야 한다.

 

경사하강법을 이용한 최적화

 

train data 정의

import torch

# 훈련(train) 데이터 정의
## 입력데이터 변수명: X, 출력데이터 변수명: y
X_train = torch.tensor([[1], 
                        [2],
                        [3]
                       ], dtype=torch.float32)
y_train = torch.tensor([[20], 
                        [40],
                        [60]
                       ], dtype=torch.float32)

 

X_train, y_train의 shape

print(X_train.shape, y_train.shape)
#  [3, 1] -> [데이터개수, 개별데이터의  shape] 
#         => 3개의 데이터. 각데이터는 1개의 값을 구성된 1차원배열

 

모델 정의

- weight : 가중치

- bias : 편향

##### 모델 정의
torch.manual_seed(0)
### weight(가중치)와 bias(편향)를 정의
weight = torch.randn(1, 1, requires_grad=True)   # (1: 입력 값의 개수, 1: 출력 값의 개수)
bias = torch.randn(1, requires_grad=True)

print("초기 파라미터")
print("weight:", weight)
print("bias", bias)

def linear_model(X):
    pred = X @ weight + bias
    return pred

 

pred -> predict를 의미

X_train 데이터를 model에 적용시켜 예측값 확인

### 예측
pred_train = linear_model(X_train)
pred_train

 

오차함수(mes_loss_fn) 정의

#### 오차함수 정의 -> 모델이 추론한 값과 정답사이의 차이를 계산 하는 함수.
# 평균 제곱 오차 (Mean Squared Error : MSE)
def mse_loss_fn(pred:"예측값", y:"정답"):
    return torch.mean((pred - y)**2)

 

오차함수를 이용한 오차 확인

loss = mse_loss_fn(pred_train, y_train)
print('오차:', loss)

 

grad 계산

- grad -> gradient(경사)

#### weight 와 bias에 대한 gradient(경사) 계산
loss.backward()

 

weight와 bias에 대한 grad

print("weight의 grad::", weight.grad)
print("bias의 grad:", bias.grad)

 

경사하강법 연산으로 인해 weight(가중치)의 값이 커졌다. -> target에 더 많은 영향을 주도록 최적화되었다.

### 최적화 -> 경사하강법 ==> 파이토치의 함수를 사용.
optimizer = torch.optim.SGD([weight, bias],  #최적화할 대상 => requires_grad=True
                            lr=0.0001  # 학습률
                           )
## 경사하강법 연산.
print("update전 weight:", weight)
optimizer.step()
print("update후 weight:", weight)
optimizer.zero_grad()

 

업데이트 이전과 이후의 loss값의 차이를 확인

- 업데이트 이후 loss(오차)의 값이 작아졌다.

#### 업데이트후 로스계산
## 추론
pred = linear_model(X_train)
## 오차 계산
loss2 = mse_loss_fn(pred, y_train)
print("이전:", loss)
print("업데이트후", loss2)

 

학습

  • 1. 모델을 이용해 추정한다.
       - pred = model(input)
  • 2. loss를 계산한다.
       - loss = loss_fn(pred, target)
  • 3. 계산된 loss를 파라미터에 대해 미분하여 계산한 gradient 값을 각 파라미터에 저장한다.
       - loss.backward()
  • 4. optimizer를 이용해 파라미터를 update한다.
       - optimizer.step()  
  • 5. 파라미터의 gradient(미분값)을 0으로 초기화한다.
       - optimizer.zero_grad()
    - 위의 단계를 반복한다.   

 

학습 코드

STEPS = 500 # 파라미터(WEIGHT, BIAS)를 업데이트할 횟수.
for _ in range(STEPS):
    # 1. 추론(예측)
    pred = linear_model(X_train)
    # 2. 오차 계산
    loss = mse_loss_fn(pred, y_train)
    # 3. weight와 bias에대한 gradient를 계산.
    loss.backward()
    # 4. 최적화 -> optimizer를 이용. ==> 경사하강법
    optimizer.step()
    # 5. 계산된 gradient값을 초기화
    optimizer.zero_grad()

 

### weight, bias 값 
print("weight:", weight)
print("bias:", bias)

 

오차 계산

### 오차 계산
pred3 = linear_model(X_train)
loss3 = mse_loss_fn(pred3, y_train)
print(loss3)

다중 입력, 다중 출력

  • 다중입력: Feature가 여러개인 경우
  • 다중출력: Output 결과가 여러개인 경우

다음 가상 데이터를 이용해 사과와 오렌지 수확량을 예측하는 선형회귀 모델을 정의한다.  

가상 데이터

사과수확량  = w11 * 온도 + w12 * 강수량 + w13 * 습도 + b1
오렌지수확량 = w21 * 온도 + w22 * 강수량 + w23 *습도 + b2

 

  • 온도, 강수량, 습도 값이 사과와, 오렌지 수확량에 어느정도 영향을 주는지 가중치를 찾는다.
        - 모델은 사과의 수확량, 오렌지의 수확량 두개의 예측결과를 출력해야 한다.
        - 사과에 대해 예측하기 위한 weight 3개와 오렌지에 대해 예측하기 위한 weight 3개 이렇게 두 묶음, 총 6개의 weight를 정의하고 학습을 통해 가장 적당한 값을 찾는다.
            - 이 묶음을 딥러닝에서는 Node, Unit, Neuron 이라고 한다.
  • 목적은 우리가 수집한 train 데이터셋을 이용해 정확한 예측을 위한 weight와 bias를 찾는 것이다.

 

Training Data

  • Train data는 feature와 target를 각각 따로 2개의 행렬로 구성한다.
  • Feature의 행은 관측치(개별 데이터)를 열을 Feature(특성, 변수)를 표현한다.
  • Target은 모델이 예측할 대상으로 행은 개별 관측치, 열은 각 항목에 대한 정답으로 이 예제에서는 사과수확량과 오렌지 수확량 값을 가진다.

 

Train feature data

# Input (temp, rainfall, humidity) : (5, 3)
inputs = torch.tensor([[73, 67, 43], 
                   [91, 88, 64], 
                   [87, 134, 58], 
                   [102, 43, 37], 
                   [69, 96, 70]], dtype=torch.float32)

 

Train targets data

# Targets: 생산량 - (apples, oranges) - (5, 2)
targets = torch.tensor([[56, 70], 
                    [81, 101], 
                    [119, 133], 
                    [22, 37], 
                    [103, 119]], dtype=torch.float32)

 

Linear Regression Model (from scratch)

weight와 bias

  • weight 는 각 feature에 곱해지는 가중치로 target 값에 얼마나 영향을 주는지를 나타낸다. 직선의 방정식에서 기울기이다.
  • bias는 각 feature와 weight간의 가중합에 더해주는 값으로 모든 feature가 0일 경우 target의 값을 나타낸다. 직선의 방정식에서 절편이다.
  • weight와 bias는 각각 random 값을 초기값으로 가지는 matrix로 정의한다.
  • weight의 shape: (2, 3)
  • bias의 shape: (2, ) 

 

import torch
torch.manual_seed(0)

 

weight(기울기), bias(절편) 생성

# weight와 bias를 생성
# 파라미터 -> 학습을 통해 찾아야 하는 값 -> requires_grad = True
weights = torch.randn(3, 2, requires_grad=True)
# [3, 2] -> [input feature개수, output개수] -> [[온도w, 강수량w, 습도w], [사과, 오렌지]]
bias = torch.randn(2, requires_grad=True) # [2] ->: [output개수 -> 사과, 오렌지]

 

weight, bias 정보 확인

print(weights.shape, bias.shape)
print(weights)
print(bias)

 

Linear Regression model

  • 모델은 weights `w`와 inputs `x`의 내적(dot product)한 값에 bias `b`를 더하는 함수이다. 

함수

 

함수 정의

def model(X):
    return X @ weights + bias

 

pred : 예측값

model : 위 코드에서 정의한 함수

inputs : Train feature data

# inputs.shape
pred = model(inputs)

 

결과값 확인

print(pred.shape)
pred

 

Loss Function

  • 모델이 예측한 값과 정답간의 차이를 비교하는 메소드. 

 

Loss function 선언

def mse_loss_fn(preds, targets):
    squared_error = (preds - targets) ** 2
    return torch.mean(squared_error)

 

예측값(pred)와 실제값(targets)의 차이 확인

loss = mse_loss_fn(pred, targets) # (2, ) -> 0: 사과생산량 오차, 1: 오렌지생산량 오차
loss

 

Gradients 계산

  • loss에 대한 weight와 bias의 gradients (미분계수)를 계산한다. Pytorch의 자동미분을 이용한다. (graident를 구하려는 tensor는 requires_grad=True로 설정한다.)

 

loss에 대한 weight와 bias의 gradients (미분계수)를 계산

loss.backward()

print(weights)
print(weights.grad)

print(bias)
print(bias.grad)

 

모델 최적화

  • gradient decent 알고리즘을 이용해 loss를 줄여 모델의 추론 성능을 높인다. 이를 위해 좋은 성능을 낼 수 있도록 경사하강법(gradient decent) 을 이용해 weight와 bias를 update한다. 

    1. 추론하기
    2. loss 계산하기
    3. weight와 bias에 대한 gradient계산하기
    4. 계산된 gradient에 비례한 값을 학습률을 곱해 작게 만든 뒤 wegith에서 빼서 조정한다.
    5. gradient를 0으로 초기화

 

step, lr, optimizer

# step(파라미터 업데이트)수 지정
STEPS = 100
# 학습률 (Learning Rate)
LR = 0.0001 #1e-4
# Optimizer 생성
optimizer = torch.optim.SGD([weights, bias], lr=LR) # 경사하강법처리. gradient값 초기화.

 

10번의 업데이트마다 오차를 출력

- 초반의 10번의 텀마다 오차가 크게 줄어드는 것을 확인할 수 있다.

for i in range(STEPS):
    # 추론
    preds = model(inputs)
    # 오차계산
    loss = mse_loss_fn(preds, targets)
    # gradient 계산
    loss.backward()
    # 파라미터(weights, bias) 값들 업데이트
    optimizer.step()
    # 파라미터의 grad값 초기화
    optimizer.zero_grad()
    # 10 step당 loss를 출력
    if i % 10 == 0 or i == (STEPS - 1):
        print(f"Step: {i} - loss: {loss.item()} ") # tensor객체.item() : scalar나 원소가 1개인 tensor의 값을 추출(파이썬 타입 값)

 

weights와 bias 파라미터값의 차이 확인(위의 최적화 코드실행 전/후로 한번씩 실행하여 차이를 확인한다.)

print("학습전 또는 후의 파라미터") # 위의 모델 최적화 코드 실행 전, 후로 한번씩 실행 -> 결과확인
print(weights)
print(bias)

 

예측결과값 pred_value

pred_value = model(inputs)

 

예측결과값 pred_value와 실제값 targets의 값 차이 확인

- 최적화 코드를 반복 실행할 때마다 차이가 크게 줄어든다.

print(pred_value)
targets

pytorch built-in 모델을 사용해 Linear Regression 구현

 

똑같이 필요한 import와 inputs데이터, tragets데이터 준비

import torch
import torch.nn as nn # 모델들을 제공하는 모듈

 

inputs = torch.tensor([[73, 67, 43], 
                   [91, 88, 64], 
                   [87, 134, 58], 
                   [102, 43, 37], 
                   [69, 96, 70]], dtype=torch.float32)

 

targets = torch.tensor([[56, 70], 
                    [81, 101], 
                    [119, 133], 
                    [22, 37], 
                    [103, 119]], dtype=torch.float32)

 

nn.Linear

  • Pytorch는 nn.Linear 클래스를 통해 Linear Regression 모델을 제공한다. 
  • nn.Linear에 입력 feature의 개수와 출력 값의 개수를 지정하면 random 값으로 초기화한 weight와 bias들을 생성해 모델을 구성한다.

 

nn.Linear()함수를 통해 weight(가중치), bias(편향) 생성 및 조회

# Linear(input feature개수, output개수) # 모델 정의
model = nn.Linear(3, 2)
# weight와 bias를 조회
print(model.weight)
print(model.bias)

 

Optimizer와 Loss 함수 정의

  • torch.optim 모듈에 다양한 Optimizer 클래스가 구현되있다. 그 중에서 Adam를 사용한다.
  • torch.nn 또는 torch.nn.functional 모듈에 다양한 Loss 함수가 제공된다. 이중 mse_loss() 를 사용한다.

 

nn.MSELoss() -> 객체

torch.nn.functional.mse_loss() -> 함수

# Loss 함수 정의
# nn.MSELoss()객체 사용 or torch.nn.functional.mse_loss()함수 사용
loss_fn = nn.MSELoss()

 

optimizer 정의

# optimizer
optimizer = torch.optim.SGD(model.parameters(), lr = 0.0001) # ([최적화대상-모델의 파라미터], lr = 학습률)

 

학습전 추론

# 학습전에 추론 -> 오차 계산
# 추론
pred = model(inputs)
pred

 

학습전 추론 결과와 실제값의 오차 계산

# 오차 계산
loss = loss_fn(pred, targets) # (모델예측결과, 정답)
loss

 

Model Train

  • 주어진 epoch 만큼 학습하는 fit  함수를 정의한다.

 

train 함수 코드

def fit(num_epochs:"몇번 반복할지", model:"학습시킬모델", loss_fn:"오차함수", optim:"옵티마이저", inputs:"입력데이터", targets:"출력데이터"):
    for epoch in range(num_epochs):
        # 1. 추론
        pred = model(inputs)
        # 2. 오차 계산
        loss = loss_fn(pred, targets)
        # 3. gradient 계산
        loss.backward()
        # 4. parameter update
        optim.step()
        # 5. 계산된 gradient값 초기화
        optim.zero_grad()
        # loss(결과) 출력
        if epoch % 10 == 0 or epoch == (num_epochs-1):
            print(f"{epoch}/{num_epochs}: train loss: {loss.item():.5f}")

 

경사하강법의 최적화 코드와 비슷하게 초반에 오차가 크게 줄어드는 것을 확인할 수 있다.

fit(100, model, loss_fn, optimizer, inputs, targets)

 

학습후의 모델 추론 결과 및 실제값 확인 -> 차이가 크지 않다.

# 학습후 모델 추론 결과 확인
p = model(inputs)
print(p)
print(targets)

 

학습후 모델 추론 결과와 실제값의 차이 확인

l = loss_fn(p, targets)
l.item()