Word-level language modeling with RNN

1) 데이터 클래스 준비

import os
import torch

class Dictionary(object):
    def __init__(self):
        self.token2id = {}
        self.id2token = []

    def add_word(self, word):
        if word not in self.token2id:
            self.id2token.append(word)
            self.token2id[word] = len(self.id2token) - 1
        return self.token2id[word]

    def __len__(self):
        return len(self.id2token)

class Corpus(object):
    def __init__(self, path):
        self.dictionary = Dictionary()
        self.train = self.tokenize(os.path.join(path, 'train.txt'))
        self.valid = self.tokenize(os.path.join(path, 'valid.txt'))
        self.test = self.tokenize(os.path.join(path, 'test.txt'))

    def tokenize(self, path):
        """Tokenizes a text file."""
        assert os.path.exists(path)
        # Add words to the dictionary
        with open(path, 'r', encoding="utf-8") as f:
            for line in f:
                words = line.split() + ['<eos>']
                for word in words:
                    self.dictionary.add_word(word)

        # Tokenize file content
        with open(path, 'r', encoding="utf-8") as f:
            idss = []
            for line in f:
                words = line.split() + ['<eos>']
                ids = []
                for word in words:
                    ids.append(self.dictionary.token2id[word])
                idss.append(torch.tensor(ids).type(torch.int64))
            ids = torch.cat(idss)

        return ids

제공된 wikitext-2 파일은 사전에 토큰화가 되어 공백으로 모든 토큰이 나누어져 있습니다. 따라서 단순히 공백 기준으로 토큰화를 진행하면 됩니다.

# corpus 확인
path = './wikitext'
corpus = Corpus(path)

print(corpus.train.size())
print(corpus.valid.size())
print(corpus.test.size())

Untitled

전처리를 완료한 데이터의 전체 개수 확인

2) 배치화(batchify)

<aside> 💡 BPTT 배치화 함수

</aside>

def bptt_batchify(data: torch.Tensor, batch_size: int, sequence_length: int):
    ### YOUR CODE HERE   
    ### 방법 1
    # 배치화하는 데이터 총 개수 = num_sample * batch_size * sequence_length
    # batch_size * sequence_length 배수에 맞게 자른 뒤 view로 shape 조절
    batches: torch.Tensor = None
    num_sample = len(data) // (batch_size * sequence_length)
    end_idx = num_sample * batch_size * sequence_length
    data = data[:end_idx]

    # batches = data.view(-1, batch_size, sequence_length) # 아래와 비슷한 거 같지만 이렇게하면 안됨
    batches = torch.transpose(data.view(batch_size, -1, sequence_length), 0, 1)       

    ### 정답 코드
    length = data.numel() // (batch_size * sequence_length) \\
                           * (batch_size * sequence_length)
    batches = data[:length].reshape(batch_size, -1, sequence_length).transpose(0, 1)

    return batches

Untitled

배치화 함수를 단계별로 그리면 위의 그림과 같다.

  1. 길이가 24인 1차원 형태의 데이터에 배치화(batch_size=4)를 수행한다. [, 24] → [4, 6]

  2. 배치화를 적용해도 하나의 샘플이 너무 길면 RNN이 역전파하는데 어려움이 있으므로 각 배치를 sequence_length로 나눈다. [4, 6] → [4, 3, 2]

  3. RNN은 빨간색 화살표 방향으로 데이터를 입력해 학습하기 때문에 **[num_sample, batch_size, sequence_length]**로 reshape 한다. [4, 3, 2] → [3, 4, 2]