ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 파이토치(PyTorch)-3.Neural Network 구현하기
    Deep Learning/밑바닥부터 시작하는 딥러닝 2020. 2. 21. 17:24

    3. Neural Net 구현 with PyTorch

     

    [모두를 위한 cs231n] Lecture 8 - Part4. PyTorch Framework

    PyTorch Framework PyTorch Framework에 대한 모든 것을 알아보겠습니다 안녕하세요 Steve-Lee입니다. Lecture 8 Part 3의 주제는 PyTorch Frame work입니다. PyTorch는 Face book에서 개발한 Deep Learning Fram..

    deepinsight.tistory.com

    신경망은 torch.nn 패키지를 사용하여 생성할 수 있다.

    지금까지 autograd를 살펴봤다.

    nn은 모델을 정의하고 미분하는 데 autograd를 사용한다.

    nn.Module은 계층(Layer)과 output을 반환하는 forward(input) 메서드를 포함하고 있다.

    😄 우리가 이전 장에서 배웠던 autograd(자동미분) 패키지가 nn 모듈과 만나서 어떻게 활용되는지 알아보자

    😄 그리고 nn패키지도 샅샅히 파헤쳐보자!(Torch Package를 낱낱이 파헤치고왔다! 이제 학습만 잘 하면 된다)

    오늘의 예제는 숫자 이미지를 분류하는 신경망이다

    LeNet-5 Architecture as Published in the original paper

    reference:

    https://medium.com/@pechyonkin/key-deep-learning-architectures-lenet-5-6fc3c59e6f4

    위에 보이는 LeNet은 간단한 순전파 네트워크(Feed-forward network)이다.

    입력(input)을 받아 여러 계층에 차례로 전달한 후, 최종 출력(output)을 제공한다.

    신경망의 일반적인 학습과정은 다음과 같다.

    • 학습 가능한 매개 변수(또는 가중치(weight)를 갖는 신경망을 정의한다.

    • 데이터셋(dataset)입력을 반복한다.

    • 입력을 신경망에서 전파(process)한다.

    • 손실(loss: 출력이 정답으로부터 얼마나 떨어져 있는지)을 계산한다.

    • 변화도(gradient)를 신경망의 매개변수들에 역으로 전파한다. (Backpropagation)

    • 신경망의 가중치를 갱신한다. 일반적으로 다음과 같은 간단한 규칙을 사용한다.

      업데이트된 가중치(weight) = 가중치(weight) - 학습률(learning rate) * 변화도(gradient)

    W1 = W0 - η*(df/dx)

    #1. 신경망 정의하기

    이제 본격적으로 신경망을 정의해보도록 하자

    🚨주석의 Question(Q)을 주목해보자!!

    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    
    class Net(nn.Module): # nn.Module 모든 신경망 모듈의 기본이 되는 클래스
                          # 각 층과 함수 등 신경망의 구성요소를 이 클래스 안에서 정의한다.
                          # nn.Module은 모든 신경망 모듈의 기본이 되는 클래스로 레이어, 함수등을 정의하는구나!
    
        def __init__(self):  # 초기화 함수   
            super(Net, self).__init__()
            # 1 input image channel, 6 output channels, 3x3 square convolution kernel
            # (kernel과 filter는 같다) 즉, filter size는 3x3
            self.conv1 = nn.Conv2d(1, 6, 3)  # 입력 채널 수, 출력 채널 수, 필터의 크기
            self.conv2 = nn.Conv2d(6, 16, 3) # 마찬가지 입력 채널 수, 출력 채널 수, 필터의 크기    
    
            # an affine operation: y = Wx + b
            # input_image의 dimension을 6x6이라고 가정하자
            self.fc1 = nn.Linear(16 * 6 * 6, 120)  # Q1. 매개변수 값은 어떻게 계산해야할까?
                                                   # 6*6 from image dimension? -20.02.20.Thur pm 2:00-
                                                   # image dimension은 어떻게 구하지? -20.02.20.Thur pm 8:05-
                                                   # https://discuss.pytorch.org/t/linear-layer-input-neurons-number-calculation-after-conv2d/28659
                                                   # Set the number of in_features for the first linear layer to (outputchanel * size of image)
                                                   # output_chanel_num * height * width
            self.fc2 = nn.Linear(120, 84)
            self.fc3 = nn.Linear(84, 10)
    
        def forward(self, x):
            # Max pooling over a (2, 2) window
            x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
            # If the size is a square you can only specify a single number
            x = F.max_pool2d(F.relu(self.conv2(x)), 2)
            x = x.view(-1, self.num_flat_features(x))
            x = F.relu(self.fc1(x))
            x = F.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
            num_features = 1
            for s in size:
                num_features *= s
            return num_features
    
    # Affine Operation에서 Linear 계층의 매개변수는 어떻게 정해지는건가? -20.02.20.Thur pm 7:49
    # 여전히 미스테리다(20.02.21.Fri.pm 12:22)... Torch Doc을 뒤져보자
    # nn.Linear()함수의 첫 벗째 인자에는 input sample의 size가 들어간다.
    # 두 번째 인자에는 output sample의 size가 들어간다.
    # clas:: torch.nn.Linear(in_features, out_features, bias=True)
    # 본 예제에서는 첫 인자로 16 * 6 * 6의 값을 인자로 받는데 
    # 이는 Conv2 계층을 지난 출력 채널의 수가 16, input_image의 dimension이 6*6이기 때문이다.
    # 정리하면 self1.fc1 = nn.Linear(16 * 6 * 6, 120)이 된다.
    
    net = Net()
    print(net)
    Net(
      (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
      (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
      (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)
    )

    forward함수만 정의하고 나면, (변화도를 계산하는) backward함수는 autograd를 사용하여 자동으로 정의된다. forward함수에서는 어떠한 Tensor 연산을 사용해도 된다.

    모델의 학습 가능한 매개변수들은 net.parameters()에 의해 반환된다.

    params = list(net.parameters())
    print(len(params))
    print(params[0].size())  # conv1's.weight
    10
    torch.Size([6, 1, 3, 3])

    임의의 32x32 입력값을 넣어보자.

    Note

    이 신경망(LeNet)의 예상되는 입력 크기는 32x32이다. 이 신경망에 MNIST 데이터셋을 사용하기 위해서는, 데이터셋의 이미지 크기를 32x32로 변경해야 한다.

    input = torch.randn(1, 1, 32, 32)
    out = net(input)
    print(out)
    tensor([[-0.0811, -0.1157, -0.0431, -0.0696,  0.1271,  0.0314, -0.0562,  0.0463,
              0.0287, -0.1436]], grad_fn=<AddmmBackward>)

    모든 매개변수의 변화도 버퍼(gradient buffer)를 0으로 설정하고, 무작위 값으로 역전파를 한다.

    net.zero_grad()
    out.backward(torch.randn(1, 10))

    계속 진행하기 전에, 지금까지 살펴봤던 것들을 다시 한번 요약해보자.

    요약

    • torch.Tensor - backward()와 같은 autograd 연산을 지원하는 다차원 배열이다. 또한 tensor에 대한 변화도(gradient)를 갖고 있다.

    • nn.Module - 신경망 모듈. 매개변수를 캡슐화(encapsulation)하는 간편한 방법으로, GPU로 이동, 내보내기(exporting), 불러오기(loading)등의 작업을 위한 헬퍼(helper)를 제공한다.

    • nn.Parameter - Tensor의 한 종류로, Module에 속성으로 할당될 때 자동으로 매개변수로 등록된다.

    • autograd.Function - autograd 연산의 전방향과 역방향정의를 구현한다. 모든 Tensor연산은 하나 이상의 Function노드를 생성하며, 각 노드는 Tensor를 생성하고 이력(history)을 부호화 하는 함수들과 연결하고 있다.

    👉🏻 모든 Tensor 연산은 적어도 하나의 Function node를 생성하는데 Function Node는 텐서를 생성한 함수와 연결된다.

    👉🏻 autograd package를 사용해서 직접 확인해본다면 보다 직관적으로 이해할 수 있게 된다.

    지금까지 배운 내용은 다음과 같다

    • 신경망을 정의하는 것
    • 입력을 처리하고 backward를 호출하는 것

    더 살펴볼내용들은 다음과 같다

    • 손실을 계산하는 것
    • 신경망의 가중치를 갱신하는 것

    #2. 손실 함수(Loss Function)

    👨🏻‍🏫 손실함수란

    손실 함수는(output, target)을 한 쌍(pair)의 입력으로 받아, 출력(output)이 정답(target)으로부터 얼마나 멀리 떨어져 있는지 추정하는 값을 계산한다.

    nn패키지에는 여러가지의 손실함수들이 존재한다. 간단한 손실 함수로는 출력과 대상간의 평균제곱오차(mean-sqaured error)를 계산하는 nn.MSEloss가 있다.

    MSE 예시

    input.shape
    torch.Size([1, 1, 32, 32])
    out = net(input)
    out
    tensor([[ 0.0120,  0.0656,  0.0773, -0.0016, -0.0734, -0.1500, -0.0617,  0.1941,
             -0.0681,  0.0739]], grad_fn=<AddmmBackward>)
    out.shape
    torch.Size([1, 10])
    # example of MSE
    output = net(input)
    target = torch.randn(10)  # a dummy target, for example
                              # 예제를 위한 더미 타겟값 할당
    target = target.view(1, -1)  # make it the same shape as output
                                 # out의 shape이 1,10이었으니 target의 shape을 맞춰준다.
    criterion = nn.MSELoss()
    
    loss = criterion(output, target)
    print('loss:', loss)
    loss: tensor(0.9448, grad_fn=<MseLossBackward>)

    이제 .grad_fn속성을 사용하여 loss를 역방향에서 따라가다보면, 이러한 모습의 연산 그래프를 볼 수 있다.

    input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
    -> view -> linear -> relu -> linear -> relu -> linear
    -> MSELoss
    -> loss

    따라서 loss.backward()를 실행할 때, 전체 그래프는 손실(loss)에 대하여 미분되며, 그래프 내의 requires_grad=True인 모든 Tensor는 변화도(gradient)가 누적된 .gradTensor를 갖게 된다.

    print(loss.grad_fn)  # MSE Loss
    print(loss.grad_fn.next_functions[0][0])  # Linear
    print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU
    <MseLossBackward object at 0x0000021CB08FDA48>
    <AddmmBackward object at 0x0000021CB0925AC8>
    <AccumulateGrad object at 0x0000021CAF2617C8>

    #3. 역전파(Backpropagation)

    오차(Error)를 역전파하기 위해서는 loss.backward()만 해주면 된다. 기존 변화도를 없애는 작업이 필요한데, 그렇지 않으면 변화도가 기존의 것에 누적되기 때문이다.

    이제 loss.backward()를 호출하여 역전파 전과 후에 conv1의 bias gradient를 살펴보자

    net.zero_grad()  # zeroes the gradient buffers of all parameters
    
    print('conv1.bias.grad before backward')
    print(net.conv1.bias.grad)
    
    loss.backward()
    
    print('conv1.bias.grad before backward')
    print(net.conv1.bias.grad)
    conv1.bias.grad before backward
    tensor([0., 0., 0., 0., 0., 0.])
    conv1.bias.grad before backward
    tensor([ 0.0018, -0.0029,  0.0151,  0.0080, -0.0060, -0.0012])

    #4. 가중치 갱신

    실제로 많이 사용되는 가장 단순한 갱신 규칙은 확률적 경사하강법(SGD: Stochastic Gradient Descent)이다.
    가중치(weight) = 가중치(weight) - 학습률(learning rate) * 변화도(gradient)

    learning_rate = 0.01
    for f in net.parameters():
        f.data.sub_(f.grad.data * learning_rate)

    신경망을 구성할 때, SGD, Nesterov-SGD, Adam, RMSProp 등과 같은 다양한 갱신 규칠을 사용하고 싶을 때가 있다. 이를 위해서 torch.optim라는 작은 패키지에 이러한 방법들을 모두 구현해두었다. 사용법은 매우 간단하다.

    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

    Reference

    ## Future Work

    1. Review

    2. Back to PyTorch Introduction Book

    20.02.21.Sat pm 10:53

     

    Related Posts

    • PyTorch Autograd 패키지
    • 모두를 위한 cs231n
    • Code - Github
     

    파이토치(PyTorch)-2. Autograd패키지

    2. Autograd-자동미분 https://github.com/Steve-YJ HesseyInsight/deep-learning-from-scratch-studying This repository contains a series of attempts and failures to implement deep learning from scratch...

    deepinsight.tistory.com

     

    모두를 위한 cs231n (feat. 모두의 딥러닝 & cs231n)

    cs231n 시작합니다! 안녕하세요. Steve-Lee입니다. 작년 2학기 빅데이터 연합동아리 활동을 하면서 동기, 후배들과 함께 공부했었던 cs231n을 다시 시작하려고 합니다. 제가 공부하면서 느꼈던 점들과

    deepinsight.tistory.com

     

    Steve-YJ/deep-learning-from-scratch-studying

    This repository contains a series of attempts and failures to implement deep learning from scratch. - Steve-YJ/deep-learning-from-scratch-studying

    github.com

    댓글 0

Designed by Tistory.