개발자식

[Pytorch] Pytorch Module, Parameter, Backward 본문

AI/Pytorch

[Pytorch] Pytorch Module, Parameter, Backward

밍츠 2022. 10. 2. 03:41

딥러닝은 수많은 레이어(블록) 반복의 연속이다!

 

torch.nn.Module

- 딥러닝을 구성하는 Layer의 base 클래스

- input, output, forward, backward 그리고 학습의 대상이 되는 parameter(tensor)도 정의된다.

 

torch.nn.Parameter

- Tensor 객체의 상속 객체

- nn.Module 내에 attribute가 될 때는 required_grad = True로 지정되어 학습 대상이 되는 Tensor이다.

- low-level이 아니라면 우리가 직접 지정할 일은 잘 없다. (대부분 정해져 있음)

 

만약에 Module 내에 attribute를 Parameter 말고 Tensor로 선언한다면?

-. parameters()로 파라미터를 확인하면 나오지 않는다.

- 왜냐하면 파라미터로 출력되는 경우는 미분 대상이기 때문이다.

- Parameter로 선언하면 requires_grad=True로 .parameters()로 미분 대상인 값을 확인할 수 있고 이는 역전파의 대상이다.

 

Parameter로 선언하고 .parameters()로 출력한 결과 코드

class MyLiner(nn.Module):
    def __init__(self, in_features, out_features, bias=True):
        super().__init__()
        self.in_features = in_features
        self.out_features = out_features
        
        self.weights = nn.Parameter(
                torch.randn(in_features, out_features))
        
        self.bias = nn.Parameter(torch.randn(out_features))

    def forward(self, x : Tensor):
        return x @ self.weights + self.bias

layer = MyLiner(7, 12)

for value in layer.parameters():
    print(value)
Output:
Parameter containing:
tensor([[ 0.2482,  0.2727,  0.6945,  0.1879,  0.4732,  2.2693, -1.6261, -0.7074,
         -0.8284, -0.4581,  0.5989,  0.6506],
        [-0.3134,  0.5870, -0.1706, -0.7035, -0.0758, -0.7304,  0.3122,  0.5325,
          0.1195, -2.4368, -0.1422,  0.0280],
        [-0.0051, -0.6986, -0.4618, -0.8123, -2.0547, -2.7787, -0.9899,  0.4777,
          0.6875, -0.3546,  1.2172,  0.9975],
        [ 1.1294, -1.9423,  0.7216,  0.2683,  1.7940, -1.0777, -0.5499,  0.0216,
          1.0361,  0.9964,  0.8483,  0.6719],
        [-0.4104, -1.1598, -1.1437, -0.4701,  0.7079, -0.2966, -1.2883, -1.2595,
          0.8861,  0.2785, -1.4302, -1.0315],
        [-0.9925,  0.3052,  0.9983, -2.7115,  0.0303,  2.2076, -1.3721,  0.5701,
          0.1194, -0.7236, -0.6762, -1.3057],
        [-1.2586,  0.6245, -0.8562,  0.2581,  1.6552, -0.5766, -0.8893, -1.1381,
         -0.5901, -0.0711,  0.1816,  0.6767]], requires_grad=True)
Parameter containing:
tensor([-0.1515,  0.5543, -0.0473,  0.4265, -2.3464, -0.4731, -0.5312,  0.7327,
         0.2549,  0.3927,  1.1567, -0.7312], requires_grad=True)

- requires_grad = True 가 같이 출력되는 것을 알 수 있고,

- 위와 같은 코드에서 Parameters가 아닌 Tensor를 쓰면 print문이 출력되지 않는다.

 

Backward 코드

for epoch in range(epochs):
    ...
    ...
    ...
    # 1) optimizer 초기화
    optimizer.zero_grad()

    # 2) 모델에 입력값을 넣어 출력물 저장
    outputs = model(inputs)

    # 3) 출력값과 실제 labels값을 사용해 손실함수 값 계산
    loss = criterion(outputs, labels)

    # 4) 손실함수를 사용해 backward(역전파) 진행
    loss.backward()

    # 5) optimizer를 이용해 가중치 업데이트
    optimizer.step()

- 위 5단계 스텝은 자주 쓰이는 코드이니 기억하자

 

Backward from the scratch 

- from the scratch : 처음부터, 바닥부터

- 실제 backward는 Module 단계에서 직접 지정이 가능하지만 할 필요가 없다. autograd가 해주기 때문에

- 직접 하는 방법은 Module에서 backward와 optimizer 오버라이딩한다.

- 이는 사용자가 직접 미분 수식을 써야 하는 부담이 있다.

- 쓸 일은 없으나 순서는 이해할 필요가 있다.

 

위 내용을 토대로 Linear Regression을 구현해보자

- y = w * x + b에서 딥러닝을 통해 w, b를 찾아보자

- 정답 : y = 2 * x + 1

 

학습 데이터 생성

import numpy as np

x_values = [i for i in range(11)]
x_train = np.array(x_values, dtype=np.float32)
x_train = x_train.reshape(-1, 1)

y_values = [2*i + 1 for i in x_values]
y_train = np.array(y_values, dtype=np.float32)
y_train = y_train.reshape(-1, 1)

 

LinearRegression 클래스 생성

import torch
from torch.autograd import Variable
class LinearRegression(torch.nn.Module):
    def __init__(self, inputSize, outputSize):
        super(LinearRegression, self).__init__()
        self.linear = torch.nn.Linear(inputSize, outputSize) #LinearRegression와 같은 기능 구현 해줌

    def forward(self, x):
        out = self.linear(x)
        return out

모델 생성

inputDim = 1        
outputDim = 1       
learningRate = 0.01 
epochs = 100

model = LinearRegression(inputDim, outputDim)

#gpu 사용이 가능한지 확인
if torch.cuda.is_available():
    model.cuda()

 

metric & optimizer 설정

criterion = torch.nn.MSELoss() 
optimizer = torch.optim.SGD(model.parameters(), lr=learningRate)

학습

for epoch in range(epochs):
	#inputs, labels을 Variable 형태로 바꾼다.
    if torch.cuda.is_available():
        inputs = Variable(torch.from_numpy(x_train).cuda())
        labels = Variable(torch.from_numpy(y_train).cuda())
    else:
        inputs = Variable(torch.from_numpy(x_train))
        labels = Variable(torch.from_numpy(y_train))

    #optimizer 초기화
    optimizer.zero_grad()

    #model에 inputs값을 넣은 결과 값 저장
    outputs = model(inputs)

    #손실함수 계산
    loss = criterion(outputs, labels)
    #print(loss)
    
    #손실함수를 이용해 역전파 계산
    loss.backward() 

    #가중치(파라미터) 업데이트
    optimizer.step()

    #print('epoch {}, loss {}'.format(epoch, loss.item()))

위에서 구한 파라미터를 적용한 예측값

with torch.no_grad(): 
    if torch.cuda.is_available():
        predicted = model(Variable(torch.from_numpy(x_train).cuda())).cpu().data.numpy()
    else:
        predicted = model(Variable(torch.from_numpy(x_train))).data.numpy()
    print(predicted)

- no_grad() 함수는 gradient 계산 context를 비활성화해준다.

- 추론과 validation 할 때 사용

for p in model.parameters():
    if p.requires_grad: #requires_grad 미분이 되는 값
         print(p.name, p.data)
Output:
None tensor([[2.0672]])
None tensor([0.5331])

 

파이토치에서 어떻게 layer를 만들고 파라미터를 업데이트 하는지 알았다.

코드를 직접 구현하는 것은 아직 어렵지만 어떤 역할을 수행하는지는 감이 오는 것 같다..ㅎ

Comments