ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [딥러닝] pytorch로 CNN 구현하기
    [Study] Data Science/머신러닝&딥러닝 2022. 7. 30. 13:43

     

    1. 합성곱 연산

     

     본격적으로 들어가기에 앞서, CNN의 기본이 되는 연산인 'Convolution'에 대해 살펴보고자 한다. Convolution(합성곱) 연산은 이미지 위에서 stride 값만큼 filter(또는 kernel)을 이동시키면서, 겹쳐지는 부분의 각 원소들을 곱한 후 모두 더한 값을 출력으로 하는 연산이다. 

     

    출처 : https://wikidocs.net/64066

     

     이러한 합성곱 연산은 이미지 분류를 하는데 있어, 기계가 자칫 다른 값으로 판단할 뻔한 값을 합성곱을 통해 이미지들의 특징을 추출해서 같은 값으로 잘 분류해내는데 도움을 준다. 아래와 같은 이미지가 주어졌다고 할 때, 사람의 경우 동일한 알파벳 Y로 판단할 수 있지만, 기계가 보기에는 다른 값으로 판단하기 쉽다.

     

    특히 각각의 이미지가 동일한 값을 나타내고 있는지를 확인하기 위해 다층퍼셉트론을 적용한다면, 기계가 이미지를 1차원 벡터로 변환하기 때문에 이미지의 공간적인 정보가 사라져 더욱 같은 값으로 분류해내기 어려울 것이다. 따라서 이미지들의 형변환 없이 공간적 정보를 보존하고, local feature들을 찾아서 매칭할 수 있도록 하는 것이 합성곱 연산이다. 또한 위와 같은 의미를 비추어본다면, 합성곱 연산에서 커널(Kernel)은 INPUT 이미지에 대한 가중치라고 볼 수 있다. 

    출처: https://wikidocs.net/64066

       

     그렇다면 이미지가 다수의 채널을 가졌을 경우로 확장하여 생각해보자. 다수의 채널을 가진 이미지에 대해 합성곱 연산을 할 때 주의할 점은 Input 이미지의 채널 수와 Kernel의 채널 수가 동일해야 한다는 점이다. 각 채널마다 합성곱을 수행한 후, 각 채널에서 나온 결과를 합하여 최종 feature map을 도출하는데 활용한다.

     

     

     

     

    2. CNN(Convolution Neural Network)이란

     

    CNN은 크게 합성곱층(Convolution layer)풀링층(Pooling layer), 그리고 최종적인 출력을 도출하는 역할의 완전연결층(fully-connected layer)로 이루어져있다. 이 계층들을 조합하여 CNN이 형성되는 것이다. 

     

    1) 합성곱층 

    아래의 구조와 같이 Input 이미지가 주어졌을 때 커널과의 합성곱 연산을 수행한 후,  ② 활성화함수에 그 결과를 넣어 비선형성을 추가한다.

     

    2) 풀링층

    풀링은 필수적인 절차는 아니지만, 합성곱층에서 추출한 feature map을 Pooling을 통해 유의미한 정보를 압축한다. Pooling의 주요 방법으로는 max pooling, average pooling 등이 있다. Pooling 계층은 학습해야할 매개변수가 없다는 점이며, 입력 데이터가 조금 변하더라도 풀링의 결과는 잘 변하지 않기 때문에 강건하다는 특징을 지니고 있다. 또한 feature map의 크기를 줄임으로써 계산복잡도 또한 낮추어 오버피팅을 방지할 수 있다는 장점도 있다.

     

    이렇게 합성곱층과 풀링층을 반복하여 통과할 수록 의미있는 정보 위주로 남게 되어, 저차원의 특성에서 고차원의 특성으로 나아갈 것이다. 

     

    출처 : 성균관대학교 이지형 교수님 강의 교안

     

     3) 완전연결층

    합성곱층과 풀링층이 feature extraction을 위한 절차였다면, Dense Layer라고도 불리우는 완전연결층에서는 최종적으로 추출된 특성을 기반으로 분류를 해내야 한다. 완전연결층도 두 가지의 세부단계로 나눌 수 있다. 

    • Flatten Layer : 최종 이미지를 1차원의 배열 형태(flatten)로 만든 후, 분류해야할 각 클래스의 가중치를 곱한다. 
    • Softmax Layer : softmax 등의 활성화 함수에 가중치를 곱한 값을 넣어 클래스를 구분한다. 

     

     

     

    3. Pytorch로 기본 CNN 구현하기 

    아래 코드는 <모두를 위한 딥러닝 Pytorch> CNN편을 참고하였으며, MNIST 데이터셋을 활용한다.

     

    먼저 torchvision에서 dataset을 가져오기 위해 아래와 같이 import한다. torchvision은 유명한 데이터셋들, 이미 구현된 유명한 모델들이 보관된 패키지라고 볼 수 있다.

    import torch
    import torchvision.datasets as dsets
    import torchvision.transforms as transforms
    import torch.nn.init

    본 코드는 CPU대신 GPU를 사용하도록 하기 위한 코드이며, GPU가 동작가능할 때 활용되게 된다. 

    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    
    # for reproducibility
    torch.manual_seed(777)
    if device == 'cuda':
        torch.cuda.manual_seed_all(777)

    학습률, 배치 사이즈 등 기본 파라미터를 설정한다.

    # parameters
    learning_rate = 0.001
    training_epochs = 15
    batch_size = 100

    dset.MNIST를 사용하여 데이터를 다운로드 하는 과정이다. 먼저 root라는 파라미터를 통해 MNIST 데이터를 다운받을 경로를 지정해준다. train 파라미터에는 True를 주면 MNIST의 훈련 데이터를, False를 주면 테스트 데이터를 리턴받는다고 볼 수 있다. 마지막으로, transform은 데이터를 파이토치의 텐서로 변환해주는 역할을 한다.

    # MNIST dataset
    mnist_train = dsets.MNIST(root='MNIST_data/',
                              train=True,
                              transform=transforms.ToTensor(),
                              download=True)
    
    mnist_test = dsets.MNIST(root='MNIST_data/',
                             train=False,
                             transform=transforms.ToTensor(),
                             download=True)

    데이터셋을 만들었다면,  torch.utils.data.DataLoader를 통해 데이터를 불러온다. 데이터를 불러오는 역할이므로, 일반적으로 모델의 훈련이 시작되기 전에 위치한다. DataLoader에 dataset 파라미터를 활용해 사용할 데이터셋을 담고, batch size를 지정한 후, 매 epoch마다 mini batch를 셔플할 것인지 등을 설정한다. 

    # dataset loader
    data_loader = torch.utils.data.DataLoader(dataset=mnist_train,
                                              batch_size=batch_size,
                                              shuffle=True,
                                              drop_last=True)

    data_loader라는 변수를 통해 받은 이미지의 shape을 확인하고 싶다면, next와 iter를 사용하여 다음과 같은 코드를 실행시킨다. 위에서 batch_size를 100으로 지정했기 때문에, 이미지의 shape은 torch.Size([100, 1, 28, 28]이 나오는 것을 확인할 수 있다.

    image, label = next(iter(data_loader))
    image.shape, label.shape

    앞서 살펴본 CNN의 기본개념에서와 같이, Input 이미지가 주어졌을 때 먼저 합성곱 연산을 수행한 후 활성화함수인 ReLU를 거쳐 비선형성을 추가해주는 합성곱층을 통과한다. 이후 풀링층을 거쳐 합성곱층에서 도출한 feature map 중 유의미한 정보를 위주로 압축한다고 볼 수 있다. 

     

    이러한 과정을 하나의 레이어(self.layer1)로 본다면, 같은 구조의 레이어가 (self.layer2) 한 번 더 얹혀진 형태로 파악할 수 있다. 그리고 최종적으로는 fully connected layer를 붙인다. 

     

    첫 번째 레이어(self.layer1)에서 torch.nn.Conv2d(1,32, kernel_size=3, stride=1, padding=1) 은 입력채널의 사이즈는 1, 출력채널의 사이즈는 32, kernel(또는 filter) 크기는 3, padding과 stride는 각 1임을 의미한다.

    # CNN Model (2 conv layers)
    class CNN(torch.nn.Module):
    
        def __init__(self):
            super(CNN, self).__init__()
            # L1 ImgIn shape=(?, 28, 28, 1)
            #    Conv     -> (?, 28, 28, 32)
            #    Pool     -> (?, 14, 14, 32)
            self.layer1 = torch.nn.Sequential(
                torch.nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1),
                torch.nn.ReLU(),
                torch.nn.MaxPool2d(kernel_size=2, stride=2))
            # L2 ImgIn shape=(?, 14, 14, 32)
            #    Conv      ->(?, 14, 14, 64)
            #    Pool      ->(?, 7, 7, 64)
            self.layer2 = torch.nn.Sequential(
                torch.nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
                torch.nn.ReLU(),
                torch.nn.MaxPool2d(kernel_size=2, stride=2))
            # Final FC 7x7x64 inputs -> 10 outputs
            self.fc = torch.nn.Linear(7 * 7 * 64, 10, bias=True)
            torch.nn.init.xavier_uniform_(self.fc.weight)
    
        def forward(self, x):
            out = self.layer1(x)
            out = self.layer2(out)
            out = out.view(out.size(0), -1)   # Flatten them for FC
            out = self.fc(out)
            return out

    아래와 같이 클래스를 선언함으로써 CNN 모델을 정의하는 과정이라고 볼 수 있다. 

    # instantiate CNN model
    model = CNN().to(device)

    비용함수와 Optimizer를 정의한다. CE에는 소프트맥스가 내재되어 있다. 

    # define cost/loss & optimizer
    criterion = torch.nn.CrossEntropyLoss().to(device)    # Softmax is internally computed.
    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

    최종적으로 학습과 테스트를 진행한다.  

    # train my model
    total_batch = len(data_loader)
    print('Learning started. It takes sometime.')
    for epoch in range(training_epochs):
        avg_cost = 0
    
        for X, Y in data_loader:
            # image is already size of (28x28), no reshape
            # label is not one-hot encoded
            X = X.to(device)
            Y = Y.to(device)
    
            optimizer.zero_grad()
            hypothesis = model(X)
            cost = criterion(hypothesis, Y)
            cost.backward()
            optimizer.step()
    
            avg_cost += cost / total_batch
    
        print('[Epoch: {:>4}] cost = {:>.9}'.format(epoch + 1, avg_cost))
    
    print('Learning Finished!')
    # Test model and check accuracy
    with torch.no_grad():
        X_test = mnist_test.test_data.view(len(mnist_test), 1, 28, 28).float().to(device)
        Y_test = mnist_test.test_labels.to(device)
    
        prediction = model(X_test)
        correct_prediction = torch.argmax(prediction, 1) == Y_test
        accuracy = correct_prediction.float().mean()
        print('Accuracy:', accuracy.item())
Designed by Tistory.