【Pytorch】 Pytorch 튜토리얼 3 - pytorch로 딥러닝하기

(Pytorch) Pytorch 튜토리얼 3 - pytorch로

NEURAL NETWORKS 구성

"""
# nn 패키지를 사용한 신경망 생성

신경망의 학습 과정
1. 학습 가능한 매개변수(또는 가중치(weight))를 갖는 신경망을 정의합니다.
2. 데이터셋(dataset)을 이용해 아래의 과정을 반복한다.
3. 입력을 신경망에서 전파(process == forward)합니다.
4. 손실을 계산합니다.
5. 변화도(gradient)를 신경망의 매개변수들에 역으로 전파 -> d_Loss / d_parametar 를 구한다. 
6. 신경망의 가중치를 갱신합니다. weight = weight - learning_rate * gradient
"""
pass
import torch
import torch.nn as nn
import torch.nn.functional as F 
# F를 import하는 이유 : Implements forward and backward definitions of an autograd operation. 
class Net(nn.Module): # nn.Module 클래스를 상속하여 Net 클래스를 생성한다.
    def __init__(self): # para 없음
        super(Net,self).__init__()  # child class에서 parent class의 __init__ 내용들을 사용하고 싶다.
        self.conv1 = nn.Conv2d(1,6,kernel_size = 3)
        self.conv2 = nn.Conv2d(6,16,kernel_size = 3)
        
        self.relu = nn.ReLU()
        self.maxP1 = nn.MaxPool2d(kernel_size=2,stride=2)

        self.fc1 = nn.Linear(16*6*6, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x): # para에 x가 들어간다!
        '''[홈페이지 방법]
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        print(x.size()) # 1*6*15*15
        '''
        x = self.maxP1(self.relu(self.conv1(x)))
        # print(x.size()) # masP1의 stride가 1일 때. -> 1*6*29*29. // stride가 2일 때. -> 1*6*15*15 
        
        x = F.max_pool2d(F.relu(self.conv2(x)), 2) # maxpool은 정사각형이기 때문에 2만 넣어도 (2,2)로 인식한다.
        x = x.view(-1, self.num_flat_features(x))  # torch.Size([1, 16, 6, 6])  -> num_flat_feature를 하고 난 후의 x의 torch.Size([1, 576])

        # x = F.relu(self.fc1(x)) [홈페이지 방법]
        # print("num_flat_feature를 하고 난 후의 x의" , x.shape)
        x = self.relu(self.fc1(x)) 
        x = self.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension (3차원이면 channel, 4차원이면 이미지 갯수)
        num_features = 1
        for s in size:
            num_features *= s   # 예를 들어 tensor sief (120, 6, 6) 라면, 6*6 = 36 을 return 한다.
        return num_features  

net = Net()
print(net)  # __init__ 에 정의한 내용이 나타난다. forward 아님! 

"""
forward 함수만 정의하고 나면, (변화도를 계산하는) backward 함수는 autograd 를 사용하여 자동으로 정의된다. 
모델의 학습가능한 매개변수들은 nn.Module 클래스의 parameters 매소드를 통해 확인할 수 있다.
"""
pass
Net(
  (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
  (relu): ReLU()
  (maxP1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (fc1): Linear(in_features=576, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)
params = list(net.parameters())
print(len(params))       # 10개에는 conv filter의 parameter, FC의 parameter, bias 등이 포함되어 있다. 자세한 건 나중에 더 자세히 알아보기
print(params[0].size())  # conv1's .weight
10
torch.Size([6, 1, 3, 3])
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

"""
*** NOTE 중요  ***
굳이 1*1*32*32의 이미지를 넣는 이유 : 
torch.nn 은 미니-배치(mini-batch)만 지원합니다. torch.nn 패키지 전체는 하나의 샘플이 아닌, 샘플들의 미니-배치만을 입력으로 받습니다.
만약 하나의 샘플만 있다면, input.unsqueeze(0) 을 사용해서 가짜 차원을 추가합니다.
"""
pass
num_flat_feature를 하기 전의 x의 torch.Size([1, 16, 6, 6])
num_flat_feature를 하고 난 후의 x의 torch.Size([1, 576])
tensor([[ 0.0136,  0.0084,  0.1542, -0.0920, -0.0778,  0.0385, -0.0898,  0.0524,
         -0.0445,  0.0961]], grad_fn=<AddmmBackward>)
net.zero_grad()  # 1. Sets gradients of all model parameters to zero.
out.backward(torch.randn(1, 10)) # # 2. Backpropa. 1*10 백터를 정규분포에서 랜덤한 값을 가져와 생성한 tensor.
# print(input.grad) -> None 이라고 나온다. 아직은 뭔지 모르겠으니 좀 더 공부해보자.


Loss Function

https://pytorch.org/docs/stable/nn.html#loss-functions

여기에 존재하는 다양한 loss functions 중에서 하나를 사용해보자.

MSEloss는 가장 단순한 mean-squared error Loss이다. (평균제곱오차)

input = torch.randn(1, 1, 32, 32)
out = net(input)  # 예측값
target = torch.randn(10)   # 정답값
# target = target.unsqueeze(1) -> tensor.size([10,1])
target = target.view(1,-1)
print("out의 사이즈 : {}  ==(같아야 한다) target의 사이즈 {}".format(out.size(), target.size()))

criterion = nn.MSELoss() # 이 loss function을 기준으로 삼겠다.
loss = criterion(out, target)
print("{} type, loss : {}".format(type(loss),loss))
out의 사이즈 : torch.Size([1, 10])  ==(같아야 한다) target의 사이즈 torch.Size([1, 10])
<class 'torch.Tensor'> type, loss : 1.5556150674819946

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d

  -> view -> linear -> relu -> linear -> relu -> linear

  -> MSELoss

  -> loss
# 다음과 같은 방법으로, 최근에 한 마지막 연산들을 찾아갈 수 있다.
# 아래의 grad_fn 과 next_function은 torch document에도 없고 여기에만 있다... 
print(loss.grad_fn)  # MSELoss  // # 사용자가 마지막으로 이 변수를 만들 때 사용하는 Function을 참조한다. (기억해 놓는다.)
print(loss.grad_fn.next_functions[0][0])  # Linear
print(loss.grad_fn.next_functions[0][0].next_functions)  # ReLU
<MseLossBackward object at 0x000001915F1C0A88>
<AddmmBackward object at 0x000001915F1C0C88>
((<AccumulateGrad object at 0x000001915F1C0A88>, 0), (<ReluBackward0 object at 0x000001915F1C83C8>, 0), (<TBackward object at 0x000001915F1C8908>, 0))

오차(error)를 역전파하기 위해서는 loss.backward() 만 해주면 된다.

과거의 gradient를 없애는 작업이 필요한데( .zero_grad() ), 그렇지 않으면 gradient가 기존의 것과 누적되기 때문이다.
***

각 파라미터(가중치, 편향)에 대한 Loss 미분값을 알아내는 방밥

# 각 파라미터(가중치, 편향)에 대한 Loss 미분값을 알아내는 방밥

net.zero_grad()     # zeroes the gradient buffers of all parameters

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad) # 이렇게 method를 사용하는 것도, 

loss.backward()

print('\nconv1.bias.grad after backward') # d_loss / d_bias_at-conv1
print(net.conv1.bias.grad)

print("\n bias 들의 실제 값 = ", net.conv1.bias)
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])

conv1.bias.grad after backward
tensor([ 0.0211, -0.0160,  0.0067, -0.0190, -0.0056,  0.0125])

ias 들의 실제 값 =  Parameter containing:
tensor([-0.2747, -0.2196, -0.2772, -0.1524,  0.2187,  0.0836],
       requires_grad=True)

위에서 net.conv1.bias 는 그저 tensor이다. (https://pytorch.org/docs/stable/nn.html#conv2d 에서 확인가능 conv2d.weight도 사용 가능.)

그리고 grad는 torch.autograd.grad 를 이용한 것이다. (https://pytorch.org/docs/stable/autograd.html 에서 확인가능)

grad 함수 : xxx.grad에서 d_? / d_xxx 값을 tensor형태로 뱉어 준다.


가중치 갱신

확률적 경사하강법(SGD; Stochastic Gradient Descent)

# 아주 단순하게 가중치 갱신하는 방법
learning_rate = 0.01
# for를 돌아서, 내가 가진 모든 layers의 파라메터를 모두, 한번 갱신한다.
for i, f in enumerate( net.parameters() ):   
    print("{}번째 parameters 갱신 완료: {}".format(i+1, f.size()))
    f.data.sub_(f.grad.data * learning_rate)
     # 굳이 data method를 사용하는 이유는 모르겠다.
     # https://pytorch.org/docs/stable/tensors.html?highlight=sub_#torch.Tensor.sub_
    
# net.parameters를 이용해도 되고, 위에 처럼 net.conv1을 이용해도 좋다. print를 통해 알아보자.
print("\n", type(list(net.parameters())[0]), '\n', 
            type(list(net.parameters())[0].grad), '\n',
            type(list(net.parameters())[0].grad.data), '\n',
            list(net.parameters())[1].grad, '\n',
            list(net.parameters())[1].grad.data  )
1번째 parameters 갱신 완료: torch.Size([6, 1, 3, 3])
2번째 parameters 갱신 완료: torch.Size([6])
3번째 parameters 갱신 완료: torch.Size([16, 6, 3, 3])
4번째 parameters 갱신 완료: torch.Size([16])
5번째 parameters 갱신 완료: torch.Size([120, 576])
6번째 parameters 갱신 완료: torch.Size([120])
7번째 parameters 갱신 완료: torch.Size([84, 120])
8번째 parameters 갱신 완료: torch.Size([84])
9번째 parameters 갱신 완료: torch.Size([10, 84])
10번째 parameters 갱신 완료: torch.Size([10])

 <class 'torch.nn.parameter.Parameter'> 
 <class 'torch.Tensor'> 
 <class 'torch.Tensor'> 
 tensor([ 0.0211, -0.0160,  0.0067, -0.0190, -0.0056,  0.0125]) 
 tensor([ 0.0211, -0.0160,  0.0067, -0.0190, -0.0056,  0.0125])
# Optimizer를 사용해서 갱신하는 방법. https://pytorch.org/docs/stable/optim.html#algorithms -> 여기에 있는 가중치 갱신 알고리즘 사용할 것.
# 위의 과정이 optimizer.stop() 이라는 것 하나로 그대로 잘 동작한다.
import torch.optim as optim

# Optimizer를 생성합니다.
optimizer = optim.SGD(net.parameters(), lr=0.01)

# 학습 과정(training loop)에서는 다음과 같습니다:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update. 다양한 종류의 optimizer 들은 모두 자신만의 step method가 구현되어 있다.




© All rights reserved By Junha Song.