본문 바로가기

책/파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습

06 임베딩 (3) CNN

CNN의 이론적인 내용은 예전에 다룬적이 있어서 여기서는 파이토치에서 CNN을 사용하는 방법에 대해서만 다룬다.

 

2023.09.21 - [책/밑바닥부터 시작하는 딥러닝] - 7장 CNN (1)

 

합성곱 신경망(Convolutional Neural Network, CNN)

 

합성곱 계층 클래스

conv = torch.nn.Conv2d(
    in_channels,
    out_channels,
    kernel_size,
    stride=1,
    padding=0,
    dilation=1,
    groups=1,
    bias=True,
    padding_mode='zeros'
)
  • groups : 입력 채널을 그룹으로 나누는데 사용되는 매개변수. groups가 1인 경우에는 전체 입력 채널에 대한 컨볼루션을 수행하지만, groups가 입력 채널수로 설정된 경우, 각 입력 채널을 독립적으로 처리하게 된다. 따라서 입력 채널은 groups의 배수여야 한다.
  • padding_mode : reflect는 가장자리를 거울처럼 반사해 값을 할당한다. 이는 주로 경계 효과를 줄이고 모델이 입력 데이터의 가장자리에 대해 더 나은 특징을 학습할 수 있게 한다.

합성곱 모델

import torch
from torch import nn


class CNN(nn.Module):
    def __init__(self):
        super().__init__()

        self.conv1 = nn.Sequential(
            nn.Conv2d(
                in_channels=3, out_channels=16, kernel_size=3, stride=2, padding=1
            ),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.conv2 = nn.Sequential(
            nn.Conv2d(
                in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1
            ),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.fc = nn.Linear(32 * 32 * 32, 10)

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = torch.flatten(x)
        x = self.fc(x)
        return x

 

모델 실습

CNN을 사용하여 이전 포스팅 처럼 문장의 긍/부정 분류 모델을 학습해보자.

 

자연어 처리를 위한 CNN은 1D 합성곱을 사용한다. 1D 합성곱에서도 문장은 토큰화, 패딩, 임베딩 층을 거쳐서 시퀀스 형태로 입력된다.

 

'wait for the video and don't rent it'이라는 문장이 있을 때 이 문장은 다음과 같은 행렬로 변활될 것이다.

  • n : 문장의 길이
  • k : 임베딩 차원

 

1D 합성곱 연산에서 커널의 너비는 임베딩 벡터의 차원과 동일하게 설정된다. 그렇기 때문에 1D 합성곱 연산에서는 커널의 높이만으로 해당 커널의 크기라고 간주한다. 가령 커널의 크기가 2인 경우에는 아래 그림과 같은 커널이 사용된다.

 

해당 문장에 크기가 2인 커널을 사용하여 1D 합성곱 연산을 수행하면 8차원의 벡터를 얻게된다.

 

1D 합성곱 연산과 자연어 처리 관점에서 커널의 크기가 달라진다는 것은 참고하는 단어의 묶음 크기가 달라진다. 이는 참고하는 n-gram이 달라진다고 볼 수 있다.

 

이미지 처리에서 그랬듯이, 1D 합성곱 층에서도 다음에는 풀링 층을 추가하게 된다.

 

다음은 여러개의 커널로 문장 분류를 위한 신경망의 구조를 나타낸다.

 

6개의 커널을 사용하여 맥스 풀링을 한 후에는 6개의 스칼라 값을 얻게 되는데, 일반적으로 이를 모두 연결하여 하나의 벡터로 만들어준다. 그런다음 이를 뉴런이 2개인 출력층에 완전 연결시키므로써 텍스트 분류를 수행할 수 있다.

 

데이터 구성부터 학습까지 이전 포스팅과 같으므로 모델 클래스만 살펴보겠다.

 

합성곱 기반 문장 분류 모델 정의

class SentenceClassifier(nn.Module):
    def __init__(self, pretrained_embedding, filter_sizes, max_length, dropout=0.5):
        super().__init__()

        self.embedding = nn.Embedding.from_pretrained(
            torch.tensor(pretrained_embedding, dtype=torch.float32)
        )
        embedding_dim = self.embedding.weight.shape[1]

        conv = []
        for size in filter_sizes:
            conv.append(
                nn.Sequential(
                    nn.Conv1d(
                        in_channels=embedding_dim,
                        out_channels=1,
                        kernel_size=size
                    ),
                    nn.ReLU(),
                    nn.MaxPool1d(kernel_size=max_length-size-1),
                )
            )
        self.conv_filters = nn.ModuleList(conv)

        output_size = len(filter_sizes)
        self.pre_classifier = nn.Linear(output_size, output_size)
        self.dropout = nn.Dropout(dropout)
        self.classifier = nn.Linear(output_size, 1)

    def forward(self, inputs):
        embeddings = self.embedding(inputs)
        embeddings = embeddings.permute(0, 2, 1)

        conv_outputs = [conv(embeddings) for conv in self.conv_filters]
        concat_outputs = torch.cat([conv.squeeze(-1) for conv in conv_outputs], dim=1)

        logits = self.pre_classifier(concat_outputs)
        logits = self.dropout(logits)
        logits = self.classifier(logits)
        return logits
  • forward 메서드에서 embeddings를 permute하는 이유는 컨볼루션 레이어에 입력으로 전달하기 전에 데이터의 차원을 조정하는 것이다. PyTorch의 nn.Conv1d 레이어는 입력의 차원 순서를 (batch_size, in_channels, sequence_length)로 기대한다. 그러나 일반적으로 자연어 처리에서 사용되는 임베딩 레이어의 출력은 (batch_size, sequence_length, embedding_dim) 형태이다.
  • 따라서 embeddings.permute(0, 2, 1)를 사용하여 두 번째(sequence_length)와 세 번째(embedding_dim) 차원을 교환하여 모델이 올바른 형식의 입력을 받을 수 있게 한다.
  • conv_outputs는 리스트로 각 원소는 [배치 크기, 채널수, 출력 길이]이다.
  • concat_outputs : [배치 크기, 총 채널 수]