잇연

[sesac LLM] day39-240227 딥러닝 선형회귀모델, 파이토치 사용 본문

SESAC LLM 개발자 강의/DL 딥러닝, LLM

[sesac LLM] day39-240227 딥러닝 선형회귀모델, 파이토치 사용

잇연 2024. 2. 27. 09:27

머신러닝 VS 딥러닝:

머신러닝 :

  • 장점 : 보다 긴 역사를 가지고 있다. 많은 구현체, 검증된 방법론들이 있다. 다양한 요건에 대한 방법론이 존재한다. 작은 데이터로 빠른 성능에 도달하기도 한다.
  • 단점 : 특정분야에서 딥러닝보다 정확도가 떨어진다. 손이 많이가고 피처가 매우 많을 때 현실적이지 않을 수 있다.

딥러닝 :

  • 장점 : 특정분야에서 드라마틱한 성능 향상을 할 수 있다. feature selection 에 큰 공을 들이지 않더라도 모델이 end to end로 학습하고 선별하는 경우가 많다.
  • 단점 : 상당히 많은 라벨 데이터가 필요할 때가 많다. 작은 양의 데이터로는 Under fit되는 경우가 ML의 경우보다 더 많다. GPU 및 Big Data Scale Computing 환경 등, 고사양 Big Data Scale Computing 환경 등 고사양의 Compute 자원을 필요로 한다.

 

파이토치 사용법

 

- 파이토치를 활용한 사칙연산

import torch
A = torch.Tensor([4])
B = torch.Tensor([2])
C = torch.Tensor([1])
D = torch.Tensor([2])
E = torch.Tensor([5])

# 1줄에 torch함수 하나씩만 사용
out1 = torch.mul(A, B) # out1은 A와 B의 원소곱이다. 즉, 4 * 2 = 8이다.
out2 = torch.add(C,D) # out2는 C와 D의 원소합이다. 즉, 1 + 2 = 3이다.
out3 = torch.sub(out1, out2) # out3은 out1에서 out2를 빼는 연산이다. 8 - 3 = 5이다.

output = torch.sub(out3, E) #output은 out3에서 E를 빼는 연산이다. 5 - 5 = 0이다.

print("result = {}".format(output)) # 최종 결과인 output의 값은 0이며, 이를 출력한다.

 

 

 

파이토치를 활용하여 선형회귀분석

실습1 tensor_linear_regression

1. 데이터 선언, 기본 파라미터 정의

## data 선언
x_data = torch.FloatTensor([[1.],[2.],[3.],[4.]])
y_data = torch.FloatTensor([[1.],[3.],[5.],[7.]])

x_data는 학습데이터고 y_data는 정답데이터이다. 

# 평균 0, 분산 1의 파라미터의 정규분포로부터 값을 가져옴.
# 학습을 통해 업데이트가 되어 변화되는 모델의 파라미터인 w,b를 의미한다.
W = torch.nn.Parameter(torch.normal(mean=0, std=1, size=(1,1)))
b = torch.nn.Parameter(torch.normal(mean=0, std=1, size=(1,1)))

# 정규분포 사용하는이유 : 정규분포를 사용하여 가중치를 초기하하는 것은 신경망의 학습을 안정적이고 효율적으로 하기 위한 방법
# 특정 뉴런이 너무 큰 값이나 너무 작은 값을 가지는 것을 방지

lr = torch.tensor(0.0001) #러닝레이트
print("W : ", W) #가중치
print("b : ", b) #편향
print('lr : ', lr) #러닝레이트
#출력결과
W :  Parameter containing:
tensor([[2.5482]], requires_grad=True)
b :  Parameter containing:
tensor([[-0.2635]], requires_grad=True)
lr :  tensor(1.0000e-04)

2. 모델 학습

for i in range(2000):  ## 에폭, 학습을 2000번 반복한다.
    total_error = 0

    for j in range(len(x_data)): ## 배치 1, 각 데이터 포인트에 대해 가중치와 편향을 업데이트한다.
        ## data * weight
        WX =torch.matmul(x_data[j], W) #행렬곱
        # (1, 1) * (1, 1)

        ## bias add, 예측값 계산
        y_hat = torch.add(WX, b)

        ## 정답인 Y와 출력값의 error 계산, 오차 계산
        error = torch.subtract(y_data[j], y_hat) ## (true - prediction)

        ## 경사하강법으로 W와 b 업데이트(파라미터 업데이트)
        ## 도함수 구하기
        diff_W = torch.matmul(error, x_data[j]) # error * x
        diff_b = error
        ##----------------------------여기까지 순전파
        ##  업데이트할 만큼 러닝레이트 곱
        diff_W = torch.multiply(lr, diff_W)
        diff_b = torch.multiply(lr, diff_b) # lr * (error)

        ## w, b 업데이트
        W = torch.add(W, diff_W) # w + lr * x * (error)
        b = torch.add(b, diff_b) # b + lr * (error)
        #######

        ## 토탈 에러. 에러를 누적시켜 전체 에폭의 에러를 계산
        visual_error = torch.square(error)
        total_error = total_error + visual_error

    ## 모든 데이터에 따른 error 값
    print("epoch: ", i, "error : ", total_error/len(x_data))

해주면 

epoch:  0 error :  tensor([[4.8042]], grad_fn=<DivBackward0>)
epoch:  1 error :  tensor([[4.7725]], grad_fn=<DivBackward0>)
epoch:  2 error :  tensor([[4.7410]], grad_fn=<DivBackward0>)
epoch:  3 error :  tensor([[4.7096]], grad_fn=<DivBackward0>)
epoch:  4 error :  tensor([[4.6785]], grad_fn=<DivBackward0>)
epoch:  5 error :  tensor([[4.6476]], grad_fn=<DivBackward0>)
epoch:  6 error :  tensor([[4.6169]], grad_fn=<DivBackward0>)
epoch:  7 error :  tensor([[4.5864]], grad_fn=<DivBackward0>)
epoch:  8 error :  tensor([[4.5562]], grad_fn=<DivBackward0>)
epoch:  9 error :  tensor([[4.5261]], grad_fn=<DivBackward0>)
epoch:  10 error :  tensor([[4.4962]], grad_fn=<DivBackward0>)
epoch:  11 error :  tensor([[4.4665]], grad_fn=<DivBackward0>)
epoch:  12 error :  tensor([[4.4370]], grad_fn=<DivBackward0>)
epoch:  13 error :  tensor([[4.4077]], grad_fn=<DivBackward0>)
epoch:  14 error :  tensor([[4.3786]], grad_fn=<DivBackward0>)
epoch:  15 error :  tensor([[4.3497]], grad_fn=<DivBackward0>)
epoch:  16 error :  tensor([[4.3210]], grad_fn=<DivBackward0>)
epoch:  17 error :  tensor([[4.2925]], grad_fn=<DivBackward0>)
epoch:  18 error :  tensor([[4.2641]], grad_fn=<DivBackward0>)
epoch:  19 error :  tensor([[4.2360]], grad_fn=<DivBackward0>)
epoch:  20 error :  tensor([[4.2080]], grad_fn=<DivBackward0>)
epoch:  21 error :  tensor([[4.1803]], grad_fn=<DivBackward0>)
epoch:  22 error :  tensor([[4.1527]], grad_fn=<DivBackward0>)
epoch:  23 error :  tensor([[4.1253]], grad_fn=<DivBackward0>)
epoch:  24 error :  tensor([[4.0981]], grad_fn=<DivBackward0>)
...
epoch:  1996 error :  tensor([[0.0320]], grad_fn=<DivBackward0>)
epoch:  1997 error :  tensor([[0.0320]], grad_fn=<DivBackward0>)
epoch:  1998 error :  tensor([[0.0320]], grad_fn=<DivBackward0>)
epoch:  1999 error :  tensor([[0.0320]], grad_fn=<DivBackward0>)

이렇게 결과가 나온다.

 

3. W(가중치),b(편향) 확인

print("W : ", W)
print("b :", b)

 

4. 테스트 데이터로 예측을 해보면 다음과 같다.

# inference
torch.add(torch.multiply(3, W), b)

모델은 Wx+b 인 선형모델인 것이고

W는 학습을 통해서 최적의 W를 찾아냈고, 이 상태에서 x를 테스트 데이터를 넣었을 때 y햇 값이 예측의 결과이다.

위에서 wx+b 연산으로 학습시켰으면 error가 최소화될 때의 w로 업데이트가 되어있다. 그 상태에서 x입력을 3으로 넣었을 때, 예측값이 몇인지 확인해주면된다.

이것을 인퍼런스 모델이라고 한다.

 

하지만, nn.Parameters 귀찮다. 그리고 선형모델..이것도 귀찮다. torch 기능을 사용해서 편리하게 코드를 짜보자. 입력사이즈와 출력사이즈만 입력하면 알아서 선형모델을 짜주는 방법을 알아보자.

실습2 tensor_linear_regression_model (모델사용)

순전파 부분 모델 짜는 방법은 nn.Linear로 와핑이 되어있어서 다음과 같이 짜줄 수 있다.

linear = torch.nn.Linear(1,1, bias=True) # input_size, output_size
model = torch.nn.Sequential(linear)

이렇게 해줄 수 있다. WX 뿐만아니라 bias = True 하여 WX+b까지 학습해줄 수 있다. linear모델을 nn.Sequential안에 변수를 넣어주었는데, 모델이 여러개일 때 순차적으로  학습해주라는 뜻이다.

 

criterion은 예측값과 정닶값을 정의해 놓은것이다.

optimizer는 SGD라는 것이 있는데 경사하강법 방법이다. 업데이트 방법을 지정한 것이다.

criterion = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)

이 부분은

    	## 정답인 Y와 출력값의 error 계산, 오차 계산
        error = torch.subtract(y_data[j], y_hat) ## (true - prediction)

        ## 경사하강법으로 W와 b 업데이트(파라미터 업데이트)
        ## 도함수 구하기
        diff_W = torch.matmul(error, x_data[j]) # error * x
        diff_b = error

이 부분과 같다.

 

그리고 for문을 돌린다.

파이토치는 동적신경망이기 때문에 제로그레드로 초기화를 시켜줘야한다. 

for step in range(2000):  # 에폭
    optimizer.zero_grad() # 동적신경망이므로 초기화 해야함.
    y_hat = model(x_data)

    # loss function
    loss = criterion(y_hat, y_data)
    loss.backward()
    optimizer.step()

    print("epoch: ", step, "error : ", loss.item())

for문 순서 1) 초기화, 2) 모델선언해서 예측값 y햇 구하기 3) y햇과 정답 y간의 오차구하기 4) loss.backward는 도함수로 업데이트하는 과정. 백프로파게이션. 5) optimizer.step()은 기울기(gradient)업데이트 한다.

텐서플로에서는 이걸model.fit으로 해준다.

 

# 0. 필요라이브러리 import
import torch
import torch.nn as nn

# 1. data 선언
x_data = torch.FloatTensor([[1.],[2.],[3.],[4.]])
y_data = torch.FloatTensor([[1.],[3.],[5.],[7.]])

# 2. 1-layer perceptron 만들기
linear = torch.nn.Linear(1,1, bias=True) # input_size, output_size
model = torch.nn.Sequential(linear)

# 2-2.Subclass로 model정의
class LinearRegression(nn.Module):
    def __init__(self):
        super(LinearRegression, self).__init__()
        self.linear = nn.Linear(1, 1)
    def forward(self, x):
        return self.linear(x)
        
model = LinearRegression()

# 3.학습 방식 설정 : loss & optimizer 정의
# cost & optimizer
criterion = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)

# 4. 모델 학습
for step in range(2000):  # 에폭
    optimizer.zero_grad() # 동적신경망이므로 초기화 해야함.
    y_hat = model(x_data)

    # loss function
    loss = criterion(y_hat, y_data)
    loss.backward()
    optimizer.step()

    print("epoch: ", step, "error : ", loss.item())
    
# 4-2. 모델 요약
from torchsummary import summary

summary(model, (1,1))

# 5. 모델평가
model.eval()
with torch.no_grad():
    predict = model(x_data)
    predict = predict.cpu().data.numpy()
    print('train:', x_data)
    print('predict:', predict)

 

 

 

--------------------

그리고,, 배치까지 고려를 해보자.

Dataloader를 활용.

x_data y_data만든다음에 TensorDataset으로 묶는다. 그러면 텐서형태로 변환이 되고 x_data와 y_data가 쌍으로 묶이게된다. 그러면 데이터로더를 불러올 수 있다. Dataset을 묶은 것을 배치사이지를 다른 곳에서 지정해줄 수 있다

batch_size=3
data_loader = DataLoader(dataset = dataset, batch_size = batch_size, shuffle =True)

에폭은 모든 데이터를 모델 돌린 전체 과정 한사이클을 의미

배치는 지정한 데이터 크기대로 모델을 돌린 한 사이클..

위에서 입력데이터 크기가 5고, 배치사이즈가 3이면, 배치크기대로 3을 일단 돌리고 업데이트 시킨다음에 남은 2를 돌리고 업데이트 시킨다. 이 과정을 모두 수행하면 에폭1이되는 것. 배치크기가 3이니까 두번째도 크기 3대로 돌려야할 것 같지만 모자른대로 돌아간다. 입력데이터가 10이었다면, 3, 3, 3, 1 이런식이되는것

 

------------------------

지금까지 데이터 하나를 가지고 하나를 출력했다.이제 두개이상 넣었을 때 하나를 출력하는 것을 배울 것이다.

예) 수업시간 2시간, 야자시간 1시간 => 성적 81이런식으로

wx+wx+b = y 를 따른다.

이렇게 했을때 입력이 2개씩 들어가기 때문에, input size는 2, output size는 1이다. 

nn.Linear(input_size,output_size, bias=True) 를 nn.Linear(2,1)로 해주면된다.

-----

방금 내용을 바탕으로 실습

실습3 tensor_multi_regression_model

import torch
import torch.nn as nn

 

1. 데이터 불러오기

# data 선언
x_data = torch.FloatTensor([[2.,0.,7.], [6.,4.,2.], [5.,2.,4.],[8.,4.,1]])
y_data = torch.FloatTensor([[75], [95], [91], [97]])
test_data = torch.FloatTensor([[5.,5.,5.]])

3개의 데이터로 하나의 출력값을 예측해야함을 알 수 있다.

 

2. 모델 정의

그러므로 input사이즈는 3, output사이즈는 1로 지정해준다.

input_size = 3
output_size = 1
# 1-layer perceptron 만들기
linear = nn.Linear(input_size,output_size)
model = nn.Sequential(linear)

 

그 뒤는 위와 같아서 생략

 

다음 수업 내용  

선형회귀모델을 배웠다. 다음 시간에는 classification,분류모델을 배울 것이다. 

분류모델을 선형회귀모델처럼하면 제대로 분류가 잘안되기 때문에 추가 과정을 거친다.