본문 바로가기
머신러닝, 딥러닝/파이토치

[파이토치 스터디] 경사하강법 구현, Class 사용하기

by 장찐 2022. 3. 1.

📚 선형회귀로 경사하강법 살펴보기 

 

https://wikidocs.net/60754

• 경사하강법은 위와 같은 cost function을 미분해서 기울기(gradient)가 가장 심한 지점을 구해서, 비용함수의 최저 지점으로 이동하는 방향을 찾아내는 알고리즘이다. 

 

📌 변수 선언 

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

x_train = torch.FloatTensor([[1], [2], [3], [4], [5], [6]])
y_train = torch.FloatTensor([[2], [4], [6], [7], [11], [14]])

 

📌 가중치, 편향 초기화 

#%% 가중치, 편향 초기화 
W = torch.zeros(1, requires_grad=True)  
b = torch.zeros(1, requires_grad=True)

 선형회귀 식을 만들기 위해서 기울기와 절편에 대한 가중치를 초기화한다. requires_grad=True는 해당 변수에 gradient 값을 저장되고, 학습을 통해서 값이 계속 업데이트 된다는 것을 의미한다. 

 

 

📌 경사하강법을 통한 학습 실시 

#경사 하강법 구현 
optimizer = optim.SGD([W, b], lr=0.01)

epochs = 20000

for epoch in range(epochs + 1):
    
    H = x_train * W + b  #가설 설정 
    cost = torch.mean((H - y_train ) ** 2) #비용함수 선언 
    
    optimizer.zero_grad()  #경사 0으로 초기화
    cost.backward()  #비용함수 미분해서 gradient 계산 
    optimizer.step()  #W,b 업데이트
    
    if epoch % 100 == 0:
           print('Epoch {:4d}/{} W: {:.3f}, b: {:.3f} Cost: {:.6f}'.format(
                epoch, epochs, W.item(), b.item(), cost.item()))

for 반복문을 통해서 에폭마다 학습을 진행하면서 경사하강을 진행한다. 

 

라인11 : 기울기를 0으로 초기화한다. 각 에폭을 반복할 때 마다 기울기를 초기화해야 새로운 가중치화 편향에 대한 기울기를 구할 수 있다. 

 

라인12 : 새로운 W, b에 대한 gradient(기울기)를 계산한다. 

 

라인13 : 설정한 옵티마이저에 step함수를 이용해서 새로 계산한 기울기에 learning rate를 곱해서 새로운 값으로 업데이트 한다. 

 

 

🏷️ zero_grad() 사용 이유 

w = torch.tensor(2.0, requires_grad=True)

nb_epochs = 20
for epoch in range(nb_epochs + 1):
    z = 2*w
    z.backward()
    print(w.grad)

 파이토치는 미분을 얻은 gradient를 이전에 계산된 gradient에 누적시킨다. 위에서 w 값을 출력해보면 계속해서 누적이 되는 것을 확인할 수 있다. 따라서 for 루프를 돌릴 때, zero_grad()를 통해서 미분값을 계속 0으로 초기화해야 한다. 

 

 

🏷️자동미분(Autograd) 

w = torch.tensor(2.0, requires_grad=True)

y = w**2
z = 2*y + 5

z.backward()  

w.grad  #미분값 출력

 파이토치에서는 경사하강법 수행 과정에서 자동 미분을 실시하는 autograd 기능을 지원한다. 

 2w2+5 라는 식에 대해서 backward()로 미분을 실시해서 gradient를 계산하면, w에 대한 미분값(4w)이 자동으로 w.grad에 8로 저장되어 있다. 

 

 


📚 행렬 연산 형태로 구현하기

 

위에서는 각 변수와 가중치를 하나씩 선언했다. 하지만 변수가 많아질 경우 일일이 설정하기에는 한계가 있다. 따라서 행렬 곱셈을 이용해서 연산 과정을 단순화 할 수 있다. 

 

https://wikidocs.net/54841

H(x)로 표현된 다중회귀식을 아래와 같이 행렬 연산으로 간단하게 표현할 수 있다. 

 

 행:5, 변수:3 개인 데이터에 대한 행렬 연산은 위와 같이 나타낼 수 있다. 

 

📌 모델 정의 및 학습 

x_train  =  torch.FloatTensor([[73,  80,  75], 
                               [93,  88,  93], 
                               [89,  91,  80], 
                               [96,  98,  100],   
                               [73,  66,  70]])

y_train  =  torch.FloatTensor([[152],  [185],  [180],  [196],  [142]])

print(x_train.shape)
print(y_train.shape)

#가중치, 편향 정의 
W = torch.zeros((3, 1), requires_grad=True)  #변수가 3개이므로 가중치도 3개 
b = torch.zeros(1, requires_grad=True)


#경사하강법
optimizer = optim.SGD([W, b], lr=1e-5)

epochs = 50

for epoch in range(epochs +1):
    H = x_train.matmul(W) + b  #행렬 연산으로 간단하게 표현 
    cost = torch.mean((H-y_train) ** 2)
    
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()
    
    print('Epoch {:4d}/{} hypothesis: {} Cost: {:.6f}'.format(
        epoch, epochs, H.squeeze().detach(), cost.item()
    ))

 예제 다중회귀식에 대해서 경사하강법을 실시하는 과정을 나타내면 위와 같다. 

 

라인13: 변수가 3개이므로 이에 해당하는 가중치도 3개로 설정한다. 

라인23: 다중회귀식을 matmul 함수로 간단하게 한 줄로 표현할 수 있다. 

 

 


 

📚 파이토치의 함수 이용하기 

 

 파이토치에는 여러 알고리즘에 대한 함수가 구현되어 있어서 이를 불러와서 간단하게 사용할 수 있다. 다중선형회귀의 경우 torch.nn에서 nn.Linear()를 사용해서 구현할 수 있다. 

 

📌 모델 정의 및 학습 

import torch
import torch.nn as nn
import torch.nn.functional as F

# 데이터 생성 
x_train = torch.FloatTensor([[1], [2], [3]])
y_train = torch.FloatTensor([[2], [4], [6]])

# 모델을 선언 및 초기화 
model = nn.Linear(1,1)

#모델 파라미터 출력
print(list(model.parameters()))

#옵티마이저 설정 
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

#학습 : 경사하강법 
epochs = 3000
for epoch in range(epochs+1):
    prediction = model(x_train)  #H(x)계산, 클래스에 그냥 입력하면 됨 
    
    cost = F.mse_loss(prediction, y_train) #cost 계산 
    
    #cost로 H(x) 개선 
    optimizer.zero_grad()  #gradient=0 초기
    cost.backward()  # 비용함수 미분해서 gradient 계산 
    optimizer.step()  # 파라미터 업데이트 
    
    if epoch % 100 == 0:
    # 100번마다 로그 출력
      print('Epoch {:4d}/{} Cost: {:.6f}'.format(
          epoch, epochs, cost.item()))

 

라인10 : 이 예제는 단순 선형 회귀이므로 input_dim=3, output_dim=1 으로 설정한다. 

라인13 : model.parameters() 를 통해서 

첫 번째가 기울기, 두 번째가 편향이다. 경사하강으로 업데이트 되는 변수이므로 requires_grad=True로 표시된다. 

 

라인21 : prediction 함수로 행렬 연산에서 matmul이나 직접 연산을 입력하는 과정을 대체할 수 있음 

 

 

📌 새로운 변수 예측 

new_var =  torch.FloatTensor([[73, 80, 75]]) 
pred_y = model(new_var) 
pred_y

위와 같이 새로운 데이터를 입력해서 예측값을 산출할 수도 있다. 

 

 


📚  Class 사용하기 

 파이토치에서는 대부분 클래스를 이용해서 모델을 구현한다.

📌모델 정의 

class LinearRegressionModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(1,1)
        
    def forward(self, x):
        return self.linear(x) 

model = LinearRegressionModel()

 nn.Module을 상속받는다.

 

 __init__()에서 모델의 구조와 동작을 정의한다. 여기서 정의한 생성자는 파이썬 객체가 생성될 때 마다 자동으로 호출된다. super() 함수를 부르면 nn.Module 클래스의 속성을 가지고 초기화된다. 

 

 forward()는 모델이 학습데이터를 입력받아서 순방향으로 연산을 진행시키는 함수이고, 객체를 호출하면 자동으로 실행된다. 모델에 입력된 x로부터 y를 얻는 것을 forward 연산이라고 한다. 

 

 

📌모델 학습 

x_train = torch.FloatTensor([[1], [2], [3]])
y_train = torch.FloatTensor([[2], [4], [6]])

optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

epochs = 20000
for epoch in range(epochs+1):
    
    prediction = model(x_train)
    cost = F.mse_loss(prediction, y_train)
    
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()
    
    if epoch % 100 == 0:
        print('epoch {:4d}/{} Cost :{:.6f}' .format(epoch, epochs, cost.item()))

학습 코드는 동일하다. 

 


 

📚  Reference

 PyTorch로 시작하는 딥러닝 입문, 유원준 외, 2022, https://wikidocs.net/book/2788

 

댓글