IMDB를 통한 자연어 처리 학습 DB 분석

시계열 데이터는 공간 데이터와 마찬가지로 그 의미가 매우 큽니다. 이미 존재하는 많은 데이터는 시간에 따라 축척된다는 점에서 볼때 거의 대부분의 데이터는 시계열성을 갖습니다. 더욱이 공간 데이터와 시계열 데이터가 융합되었을때 그 의미는 더욱 강화됩니다. 시계열 데이터로는 단어들의 순서에 따라 그 의미가 달라지는 문장과 같은 자연어 등이 있습니다. 딥러닝에서 신경망 모델도 중요하지만, 신경망이 유효한 기능 작용을 할 수 있게 만드는 훈련에서 활용되는 데이터셋 중 시계열 데이터를 실제 신경망에 주입시키기 위한 내부적인 구조를 정리해 봅니다.

RNN 신경망에서 사용되는 시계열 데이터를 손쉽게 읽어오기 위해서 PyTorch에서는 torchtext 패키지를 활용합니다. 자연어에 대한 데이터로써 IMDB가 있는데, 이 데이터셋은 영화 리뷰에 대한 텍스트 문장 데이터와 이 리뷰가 긍정적인지 부정적인지를 나타내는 레이블 데이터로 구성됩니다. 텍스트 문장 데이터는 각 영화 리뷰에 대해 하나의 파일로, 레이블 데이터는 폴더명으로 정해집니다.

먼저 아래의 코드는 IMDB를 다운로드 받고, 2개의 데이터셋인 훈련 데이터셋과 시험 데이터셋으로 구성하라는 코드입니다.

import torch
from torchtext import data, datasets

TEXT = data.Field(sequential=True, batch_first=True, lower=True)
LABEL = data.Field(sequential=False, batch_first=True)

trainset, testset = datasets.IMDB.splits(TEXT, LABEL)

위의 코드가 실행되면, trainset과 testset이라는 dataset 객체가 생성되는데요. 이 dataset의 구성은 다음과 같이 구성됩니다.

이제 이 데이터셋에 단어 사전을 구성해야 하는데, 이를 위한 코드는 다음과 같습니다.

TEXT.build_vocab(trainset, min_freq=10)
LABEL.build_vocab(trainset)

TEXT 필드의 경우 문장중 최소 10번 이상 출현한 단어에 대해서만 사전에 추가하라는 의미에서 min_freq 인자에 10을 지정하고 있습니다. 결국 데이터셋에 다음과 같이 구성됩니다.

이제 이 데이터셋을 이용해 RNN 신경망의 소중한 입력 데이터를 입력할 수 있게 됩니다. 이를 위해 검증 데이터셋까지 구성하고, 배치단위로 입력 데이터를 구성할 수 있도록 다음 코드가 필요할 것입니다.

trainset, valset = trainset.split(split_ratio=0.8)
train_iter, val_iter, test_iter = data.BucketIterator.splits((trainset, valset, testset), batch_size=24, shuffle=True, repeat=False)

GeoAI를 이용한 건물과 비닐하우스에 대한 Detection

GeoAI는 공간정보과학(Geospatial Science; Spatial Data Science)과 인공지능(Artificial Intelligence)의 합성어이며, 공간 빅데이터(Spatial Big Data)로부터 유의미한 정보를 도출하기 위해 인공지능 기술(A.I.: Machine Learning, Deep Learning)과 고성능 컴퓨터를 활용하는 분야로써, 이 글은 항공영상으로부터 탐지하고자 하는 객체를 자동으로 획득하는 GeoAI의 한가지 기술 개발을 위해 직접 학습 DB를 구축하고, 실제 딥러닝 학습을 통한 도출된 모델을 이용하여 테스트한 내용을 소개합니다.

항공영상으로부터 탐지하고자 하는 객체는 건물과 비닐하우스로 정하고, 이 두가지에 대한 학습 DB인 레이블 데이터를 구축하고, 구축된 학습 데이터를 이용해 Detection에 대한 신경망 모델인 Faster R-CNN을 학습시켰습니다. 구축 건수는 건물와 비닐하우스에 대해 각각 6000개, 2000개이고, 레이블 데이터 구축 툴은 아래의 글에서 소개한 GeoAI Labeling Tool입니다.

GeoAI Labeling Tool 소개

총 35 Epoch만큼 학습된 모델을 이용해 항공영상을 테스트했으며, 그 중 테스트한 순서대로 3개정도의 이미지만을 언급하면 다음과 같습니다. 참고로 Class1은 건물, Class2는 비닐하우스입니다.

모델 성능 검증을 위해 소량의 데이터와 작은 반복의 학습했으므로, 높은 정확도를 기대한 것은 아니지만, 소량의 학습 DB임에도 상당수의 건물(Class1) 또는 비닐하우스(Class2)에 대해서 종류별로 검출이 되는 것을 볼 수 있습니다. 하지만 3번째 이미지를 보시면, 좌상단 부분의 건물 몇개가 검출되지 않는 결과를 볼 수 있습니다.

해서, 3번 이미지 중 검출되지 않는 부분만을 짤라내어 다시 검출을 시도해 보면 다음처럼 검출되는 것을 볼 수 있었습니다.

실제 프로젝트에서는 더 많은 수의 학습 데이터를 구축하고, 이를 다시 데이터 증강기법을 이용한 보강을 통해 더욱 높은 포퍼먼스를 얻을 수 있습니다. 또한 Faster R-CNN 보다는 항공영상에서의 건물 등과 같은 객체 탐지를 위한 더 나은 모델을 활용하는 것이 옳을 것입니다. 즉, Faster R-CNN처럼 먼저 영역(Region)을 추출하고, 이 영역에 대한 분류(Classification)을 수행하는 방식이 아닌, 픽셀 단위로 바로 분류 하는 모델이 정확성과 업무의 응용성이 더 높을 것입니다. 물론 Faster R-CNN의 장점은 정확한 학습 DB를 빠르게 구축할 수 있다는 장점이 있으므로 적용하고자 하는 분야에 따라 신경망 모델을 결정해야 할 것입니다. 아울러 학습 DB 구축에 대해 덧붙이자면 건물이나 비닐하우스 등은 지역과 계절에 따라 그 형상 및 색감이 다르므로 계절별로, 또 보다 더 넓은 지역을 범위로 해야 합니다.

다중분류를 위한 대표적인 손실함수, torch.nn.CrossEntropyLoss

딥러닝의 많은 이론 중 가장 중요한 부분이 손실함수와 역전파입니다. PyTorch에서는 다양한 손실함수를 제공하는데, 그 중 torch.nn.CrossEntropyLoss는 다중 분류에 사용됩니다. torch.nn.CrossEntropyLoss는 nn.LogSoftmax와 nn.NLLLoss의 연산의 조합입니다. nn.LogSoftmax는 신경망 말단의 결과 값들을 확률개념으로 해석하기 위한 Softmax 함수의 결과에 log 값을 취한 연산이고, nn.NLLLoss는 nn.LogSoftmax의 log 결과값에 대한 교차 엔트로피 손실 연산(Cross Entropy Loss|Error)입니다.

Softmax와 교차 엔트로피 손실 연산에 대한 각각의 설명은 아래와 같습니다.

활성화 함수(Activation Function)

손실함수(Loss Function)

참고로 nn.NLLLoss의 구현 코드는 아래와 같습니다.

import torch

def NLLLoss(logs, targets):
    out = torch.zeros_like(targets, dtype=torch.float)
    for i in range(len(targets)):
        out[i] = logs[i][targets[i]]
    return -out.sum()/len(out)

물론 PyTorch에서도 torch.nn.NLLLoss를 통해 위와 동일한 기능을 제공합니다. 결과적으로 Softmax의 Log 결과를 Cross Entropy Loss 값의 결과를 얻기 위해 3가지 방식이 존재하는데, 아래와 같습니다.

x = torch.Tensor([[0.8982, 0.805, 0.6393, 0.9983, 0.5731, 0.0469, 0.556, 0.1476, 0.8404, 0.5544]])
y = torch.LongTensor([1])

# Case 1
cross_entropy_loss = torch.nn.CrossEntropyLoss()
print(cross_entropy_loss(x, y)) # tensor(2.1438)

# Case 2
log_softmax = torch.nn.LogSoftmax(dim=1)
x_log = log_softmax(x)
print(NLLLoss(x_log, y)) # tensor(2.1438)

# Case 3
nll_loss = torch.nn.NLLLoss()
print(nll_loss(x_log, y)) # tensor(2.1438)

위의 세가지 방식 중 torch.nn.CrossEntropyLoss처럼 연산을 한번에 처리하는 것이 수식이 간소화되어 역전파가 더 안정적으로 이루지므로 실제 사용에 권장됩니다.

torch.nn.CrossEntropyLoss를 이용하여 손실값을 구하는 것에 초점을 맞춰보면.. 먼저 torch.nn.CrossEntropyLoss의 수식은 다음과 같습니다.

    $$loss(x,class)=-\log\biggl(\frac{\exp(x[class])}{\sum_{j}{\exp(x[j])}}\biggr)=-x[class]+\log\biggl(\sum_{j}{\exp(x[j])}}\biggr)$$

위의 수식을 살펴보면 앞서 언급한 것처럼 Softmax와 Log처리 및 Cross Entropy Loss 연산의 조합이라는 것을 알수 있습니다.

torch.nn.CrossEntropyLoss를 코드를 통해 설명하면… 예를들어 신경망 말단에서 총 10개의 값(앞서 언급한 x값)이 출력되었고, 실제 레이블 값(앞서 언급한 y 또는 class)은 1일때에 손실값을 구하는 코드는 아래와 같습니다.

import torch
import torch.nn as nn
import numpy as np

output = torch.Tensor([[0.8982, 0.805, 0.6393, 0.9983, 0.5731, 0.0469, 0.556, 0.1476, 0.8404, 0.5544]])
target = torch.LongTensor([1])

criterion = nn.CrossEntropyLoss()
loss = criterion(output, target)
print(loss) # tensor(2.1438)

참고로 위의 코드에서 사용된 nn.CrossEntropyLoss의 수식을 알고 있으므로 nn.CrossEntropyLoss을 사용하지 않고 직접 손실값을 계산한다면 다음과 같습니다.

import torch
import torch.nn as nn
import numpy as np

output = [0.8982, 0.805, 0.6393, 0.9983, 0.5731, 0.0469, 0.556, 0.1476, 0.8404, 0.5544]
target = [1]
loss = np.log(sum(np.exp(output))) - output[target[0]]
print(loss) # 2.143818427948945

손실값이 필요할 때는 신경망의 학습인데, 학습에서 데이터는 GPU 자원을 최대한 활용하기 위해 배치 단위로 처리됩니다. 즉, 앞서 언급한 것처럼 1개 단위가 아닌 2개 이상의 데이터가 한꺼번에 들어온다는 것입니다. 이에 대한 처리에 대한 예는 다음 코드와 같습니다.

import torch
import torch.nn as nn
import numpy as np

output = torch.Tensor(
    [
        [0.8982, 0.805, 0.6393, 0.9983, 0.5731, 0.0469, 0.556, 0.1476, 0.8404, 0.5544],
        [0.9457, 0.0195, 0.9846, 0.3231, 0.1605, 0.3143, 0.9508, 0.2762, 0.7276, 0.4332]
    ]
)

target = torch.LongTensor([1, 5])

criterion = nn.CrossEntropyLoss()
loss = criterion(output, target)
print(loss) # tensor(2.3519)

위의 코드를 nn.CrossEntropyLoss()를 사용하지 않고 계산한다면 다음 코드와 같구요.

import torch
import torch.nn as nn
import numpy as np

output = [0.8982, 0.805, 0.6393, 0.9983, 0.5731, 0.0469, 0.556, 0.1476, 0.8404, 0.5544]
target = [1]
loss1 = np.log(sum(np.exp(output))) - output[target[0]]

output = [0.9457, 0.0195, 0.9846, 0.3231, 0.1605, 0.3143, 0.9508, 0.2762, 0.7276, 0.4332]
target = [5]
loss2 = np.log(sum(np.exp(output))) - output[target[0]]

print((loss1 + loss2)/2) # 2.351937720511233

즉, 배치 처리에 대한 손실값은 배치를 구성하는 각 데이터의 손실값들의 평균이라는 점을 확인할 수 있습니다.

RNN 텐서 차원수

딥러닝의 모델을 이해하기 위해서는 모델의 각 레이어 사이를 흘러다니는 데이터인 텐서의 차원을 정확이 이해하고 파악해야 합니다. 아래는 순환신경망인 기본 RNN에 대한 모델과 레이어에 입력되고 출력되는 텐서에 대한 그림입니다. 모델의 구성 레이어는 사각형으로, 텐서는 원으로 표시하였습니다.

위의 그림에서 언급된 텐서(Tensor)의 의미를 명시하면, W는 가중치, b는 편향, N은 미니배치 구성수, V는 시퀀스 구성수, D는 시퀀스 구성 요소의 분산 표현 벡터 차원수, H는 은닉 벡터 차원수입니다.