2018년 9월 15일 토요일

Deep Learning with Sequence Data and text (순차적 데이터와 텍스트의 딥러닝 PyTorch) (5)

시작하기 전에


여기 글의 예제는 PyTorch 로 되어 있습니다.
본문의 내용은 Deep Learning with PyTorch (Vishnu Subramanian) 책 내용으로 구성 되어있으며, 해당 내용은 Chapter 6 의 내용으로 구성되어 있습니다.


Generate batches of vectors (벡터의 배치(일괄) 작업 생성)

Torchtext는 BucketIterator를 제공합니다. 모든 텍스트 작업을 일괄로 처리하고 단어를 인덱스 숫자로 변환 하는것을 돕습니다. 이때 batch_size, device(CPU or GPU), shuffle(순서를 섞을때 사용합니다.)과 같은 유용한 인자를 제공합니다. 다음은 어떻게 iterator를 생성하는지 예제 코드를 보여줍니다. iterator(반복자)란 java, c++의 stl 같은 언어에 있는 동일한 개념입니다. 해당 기능은 배열이나 그와 유사한 자료 구조의 내부의 요소를 순회(traversing)하기 위한 객체입니다. 다음은 train 데이터셋을 위해서 배치를 생성한 예제입니다.

train_iter = BucketIterator(train_data, batch_size=1, device=None, shuffle=False)

그리고 생성된 iterator는 파이썬 내장 함수 iter, next를 이용해 사용이 가능합니다. 먼저 iter는 객체의 __iter__ 메서드를 호출해주고, next는 객체의 __next__ 메서드를 호출해줍니다. 아래는 Iterator 사용의 단순한 예제입니다.

>>> iter = iter(range(3))
>>> next(iter)
0
>>> next(iter)
1
>>> next(iter)
2
>>> next(iter)
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    next(it)
StopIteration

next는 다음 인스턴스를 참조 할 수 있게 해줍니다.
따라서 다음 예제는 첫번째의 train 데이터 셋을 가져오게 됩니다. (위 예제에서 0을 가져온것과 같습니다.)

batch = next(iter(train_iter))
print(batch.text)

앞에서 언급한 단어의 인덱스 숫자로 변환 한다는 의미는 다음 예제를 보면 쉽게 이해 할 수 있습니다.

from torchtext.data import Field, Iterator, TabularDataset, BucketIterator
import torch.nn as nn
import torch.optim as optim
import torch
import torch.nn.functional as F

is_cuda = False

if torch.cuda.is_available():
    is_cuda=True

TEXT = Field(sequential=True,
             use_vocab=True,
             lower=True, 
             batch_first=True,fix_length=20)  
LABEL = Field(sequential=False,
              use_vocab=False,
              batch_first=True)

train_data = TabularDataset(path='./qtrain.tsv', skip_header=True, format='tsv', fields=[('text', TEXT), ('label', LABEL)])

TEXT.build_vocab(train_data, min_freq = 1)

print(len(train_data))
print(train_data[0].text)

train_iter = BucketIterator(train_data, batch_size=4, device=None, shuffle=False)
batch = next(iter(train_iter))
print(batch.text)

결과
4
['an', 'american', 'in', 'paris', 'is', 'a', 'wonderful', 'musical', 'about', 'an', 'american', 'painter', 'living', 'in', 'paris', 'for', 'inspiration.', 'he', 'meets', 'a', 'rich', 'woman', 'who', 'admires', 'his', 'paintings', 'on', 'the', 'street', 'and', 'she', 'believes', 'she', 'can', 'get', 'his', 'work', 'to', 'be', 'even', 'more', 'popular', 'to', 'the', 'public,', 'e.g.', 'in', 'a', 'museum.', 'golden', 'globe', 'nominated', 'gene', 'kelly', 'as', 'the', 'artist', 'jerry', 'mulligan', 'is', 'just', 'perfect', 'at', 'both', 'singing', 'and', 'especially', 'dancing.', 'he', 'also', 'meets', 'the', 'main', 'girl', 'lise', 'bouvier', '(leslie', 'caron)', 'who', 'is', 'engaged', 'to', 'his', 'best', 'friend.', 'he', "can't", 'help', 'his', 'feelings', 'for', 'this', 'girl,', 'even', 'after', 'he', 'finds', 'out', 'who', 'she', 'is', 'engaged', 'to.', 'filled', 'with', 'nice', 'romance', 'and', 'wonderful', 'song', 'and', 'dance,', 'this', 'is', 'a', 'very', 'good', 'musical', 'film.', 'it', 'may', 'drag', 'slightly', 'with', 'his', 'dancing', 'dream', 'sequence,', 'i.e.', 'the', 'american', 'in', 'paris', 'ballet,', 'but', 'there', 'is', 'a', 'good', 'happy', 'ending.', 'it', 'won', 'the', 'oscars', 'for', 'best', 'art', 'direction-set', 'decoration,', 'best', 'cinematography,', 'best', 'costume', 'design,', 'best', 'music,', 'scoring', 'of', 'a', 'musical', 'picture,', 'best', 'writing,', 'story', 'and', 'screenplay', 'and', 'best', 'picture,', 'and', 'it', 'was', 'nominated', 'for', 'best', 'director', 'for', 'vincente', 'minnelli', 'and', 'best', 'film', 'editing,', 'it', 'was', 'nominated', 'the', 'bafta', 'for', 'best', 'film', 'from', 'any', 'source,', 'and', 'it', 'won', 'the', 'golden', 'globe', 'for', 'best', 'motion', 'picture', '-', 'musical/comedy,', 'and', 'it', 'was', 'nominated', 'for', 'best', 'director', 'for', 'vincente', 'millenni', "(liza's", 'father).', 'gene', 'kelly', 'was', 'number', '66', 'on', 'the', '100', 'movie', 'stars,', 'and', 'he', 'was', 'number', '15', 'on', '100', 'years,', '100', 'stars', '-', 'men,', '"i', 'got', 'rhythm"', 'was', 'number', '32', 'on', '100', 'years,', '100', 'songs,', 'the', 'film', 'was', 'number', '9', 'on', '100', 'years', 'of', 'musicals,', 'it', 'was', 'number', '39', 'on', '100', 'years,', '100', 'passions,', 'it', 'was', 'number', '68', 'on', '100', 'years,', '100', 'movies,', 'and', 'it', 'was', 'number', '58', 'on', 'the', '100', 'greatest', 'musicals.', 'very', 'good!']
tensor([[  31,   44,    9,   48,    7,    4,  102,   36,   51,   31,
           44,  382,  321,    9,   48,   11,  296,   26,   76,    4],
        [ 291,   94,   89,  519,    6,   56,   39,    3,  128,   18,
           15,   27,    8,  298,    6,  458,    2,  103,  261,   20],
        [  15,  140,   23,  469,  353,   33,  532,  387,  488,  142,
          207,    5,  266,  279,  208,    5,   79,   55,  336,  112],
        [   2,  153,   98,    5,  401,   77,    7,  249,   85,   18,
            2,   86,    3,   65,   46,   21,   24,   25,  468,    3]])

TabularDataset 는 tsv파일로 부터 train 데이터를 읽어오게 됩니다.  TEXT.build_vocab는 어휘을 만드는 동작입니다. 이 내용들은 앞에서 모두 설명하였기 때문에 생략하고, 다음 동작부터 설명을 하도록 하겠습니다. train_data가 모두 몇개 들어있는지 출력합니다. 여기에서는 이전 예제와 같이 4개를 넣었습니다. 따라서 4가 출력되고 train_data[0].text 는 첫번째 데이터의 text들이 출력됩니다. 결과 내용에서 ['an', 'american',... 이런식으로 나오는 부분입니다.
print(len(train_data))
print(train_data[0].text)
앞에서 학습한 BucketIterator의 경우 batch_size를 4로 설정하였습니다. 입력데이터도 4개이기때문에 한번에 로딩이 될것입니다.
train_iter = BucketIterator(train_data, batch_size=4, device=None, shuffle=False)
그리고 첫번째 항목을 반복자를 이용해 읽어오기 위해서는 다음과 같이 표현이 가능합니다.
batch = next(iter(train_iter))
이 값은 4*20 tensor가 됩니다. 앞의 4는 batch size가 되고 20은 전체 text데이터의 최대 크기를 나타냅니다. 여기에서는 fix_length=20 로 제한 하였기 때문에 20이 이 됩니다.
batch의 결과를 출력해보면 text형태로 들어있는것이 아니라 index로 변형된 숫자가 들어 있습니다. 이 의미가 "단어의 인덱스 숫자로 변환 한다"라는 의미입니다. 실제 Deep learning을 하기 위해서는 숫자 형태로 들어가야하기 때문입니다.

enumerate iterator

앞에서는 iterator next를 이용하여 한번만 데이터를 로딩하는 예제였습니다. 일반적인 train에서는 iterator를 이용하여 반복적으로 데이터를 읽어서 훈련을 하게 됩니다. 이때 enumerate 를 이용하게 됩니다.
다음과 같은 형태를 사용하게 되는데, train_iter.repeat = False를 하지 않으면, 무한히 루프를 돌게 됩니다.
for batch_idx , batch in enumerate(train_iter):

from torchtext.data import Field, Iterator, TabularDataset, BucketIterator
import torch.nn as nn
import torch.optim as optim
import torch
import torch.nn.functional as F

is_cuda = False

if torch.cuda.is_available():
    is_cuda=True

TEXT = Field(sequential=True,
             use_vocab=True,
             lower=True, 
             batch_first=True,fix_length=20)  
LABEL = Field(sequential=False,
              use_vocab=False,
              batch_first=True)

train_data = TabularDataset(path='./qtrain.tsv', skip_header=True, format='tsv', fields=[('text', TEXT), ('label', LABEL)])

TEXT.build_vocab(train_data, min_freq = 1)

train_iter = BucketIterator(train_data, batch_size=1, device=None, shuffle=False)

print(len(train_iter))
train_iter.repeat = False

for batch_idx , batch in enumerate(train_iter):
 print("index:", batch_idx, batch, batch.text)

전체 train data가 4개 이고 batch_size=1이라서 아래와 같은 결과가 나옵니다.
4
index: 0
[torchtext.data.batch.Batch of size 1]
        [.text]:[torch.LongTensor of size 1x20]
        [.label]:[torch.LongTensor of size 1] tensor([[  31,   44,    9,   48,    7,    4,  102,   36,   51,   31,
           44,  382,  321,    9,   48,   11,  296,   26,   76,    4]])
index: 1
[torchtext.data.batch.Batch of size 1]
        [.text]:[torch.LongTensor of size 1x20]
        [.label]:[torch.LongTensor of size 1] tensor([[ 291,   94,   89,  519,    6,   56,   39,    3,  128,   18,
           15,   27,    8,  298,    6,  458,    2,  103,  261,   20]])
index: 2
[torchtext.data.batch.Batch of size 1]
        [.text]:[torch.LongTensor of size 1x20]
        [.label]:[torch.LongTensor of size 1] tensor([[  15,  140,   23,  469,  353,   33,  532,  387,  488,  142,
          207,    5,  266,  279,  208,    5,   79,   55,  336,  112]])
index: 3
[torchtext.data.batch.Batch of size 1]
        [.text]:[torch.LongTensor of size 1x20]
        [.label]:[torch.LongTensor of size 1] tensor([[   2,  153,   98,    5,  401,   77,    7,  249,   85,   18,
            2,   86,    3,   65,   46,   21,   24,   25,  468,    3]])

이번에는 batch_size=2로 변경하였습니다.
train_iter 크기가 2로가 되며 tensor 크기가 2*20크기가 됩니다.
2
index: 0
[torchtext.data.batch.Batch of size 2]
        [.text]:[torch.LongTensor of size 2x20]
        [.label]:[torch.LongTensor of size 2] tensor([[  31,   44,    9,   48,    7,    4,  102,   36,   51,   31,
           44,  382,  321,    9,   48,   11,  296,   26,   76,    4],
        [ 291,   94,   89,  519,    6,   56,   39,    3,  128,   18,
           15,   27,    8,  298,    6,  458,    2,  103,  261,   20]])
index: 1
[torchtext.data.batch.Batch of size 2]
        [.text]:[torch.LongTensor of size 2x20]
        [.label]:[torch.LongTensor of size 2] tensor([[  15,  140,   23,  469,  353,   33,  532,  387,  488,  142,
          207,    5,  266,  279,  208,    5,   79,   55,  336,  112],
        [   2,  153,   98,    5,  401,   77,    7,  249,   85,   18,
            2,   86,    3,   65,   46,   21,   24,   25,  468,    3]])

이번에는 batch_size=3으로 변경하였습니다. 마지막의 tensor 크기가 줄어듭니다.
2
index: 0
[torchtext.data.batch.Batch of size 3]
        [.text]:[torch.LongTensor of size 3x20]
        [.label]:[torch.LongTensor of size 3] tensor([[  31,   44,    9,   48,    7,    4,  102,   36,   51,   31,
           44,  382,  321,    9,   48,   11,  296,   26,   76,    4],
        [ 291,   94,   89,  519,    6,   56,   39,    3,  128,   18,
           15,   27,    8,  298,    6,  458,    2,  103,  261,   20],
        [  15,  140,   23,  469,  353,   33,  532,  387,  488,  142,
          207,    5,  266,  279,  208,    5,   79,   55,  336,  112]])
index: 1
[torchtext.data.batch.Batch of size 1]
        [.text]:[torch.LongTensor of size 1x20]
        [.label]:[torch.LongTensor of size 1] tensor([[   2,  153,   98,    5,  401,   77,    7,  249,   85,   18,
            2,   86,    3,   65,   46,   21,   24,   25,  468,    3]])

BucketIterator와 Iterator차이

BucketIterator는 Iterator 클래스를 상속받아서 만든 클래스로 쓰임에는 큰차이가 없습니다. 다만  sort될때  batch_size * 100 크기의 pool을 만들어 두고 그곳에서 가져오는 개념이 다릅니다. 궁금하신 분들은 아래 소스 참고 하시기 바랍니다.
https://github.com/pytorch/text/blob/master/torchtext/data/iterator.py


댓글 없음:

댓글 쓰기