새소식

딥러닝/자연어 처리

NLP_Preprocessing : 6)Batchify with torchtext

  • -

Preprocessing Workflow

1. 코퍼스 수집 : 구입,외주,크롤링

2. 정제 : Task에 따른 노이즈 제거, 인코딩 변환

(3. 레이블링 : Task에 따른 문장 or 단어마다 labeling 수행)

4. Tokenization : 형태소 분석기 활용하여 분절 수행

(5.  Subword Segmentation : 단어보다 더 작은 의미 추가 분절 수행)

6. Batchify : 사전 생성 및 word2index 맵핑 수행, 효율화를 위한 전/후처리

 


1~5번 과정에서 전처리를 진행하였다. 전처리가 다 된 후에는 이제 모델에 넣기 위한 처리가 필요하다.

  1. Split Train/Valid/Test data set
  2. 단어를 index로 mapping하기 위해 train set을 기반으로 어휘 사전 생성
    : 빈도 순으로 단어 사전을 정렬한다.
  3. 길이로 Sorting
    : Sequence의 차원의 크기는 미니 배치내의 가장 긴 문장에 의해 결정이 되는데,
      만약 가장 긴 문장은 100단어이고 어떤 문장은 16단어로 구성되어 있다면 그 어떤 문장은 84 timestep이 <pad>로 채워지게     
    된다. 그러면 학습할 때 84 timestep동안 놀게된다.
      → 짧은 문장끼리 미니배치를 만들고, 긴 문장끼리 미니 배치를 만들면 training effficiency를 높일 수 있을 것이다!
           길이로 sorting을 하고 미니배치 순서 자체를 shuffling해서 넣어준다.
  4. integer로 변환 (각 token들을 사전을 활용하여 str → index 맵핑)
  5. Batchify and pad
  6. Shuffle

 

TorchText는 NLP 또는 텍스트와 관련된 기계학습 또는 딥러닝을 수행하기 위한 데이터를 읽고 전처리 하는 코드를 모아놓은 라이브러리이다.

from torchtext import data ## torchtext==0.8.0

step 1: "Define Fields"

self.label = data.Field(Sequential=False,
			use_vocab=True,
            		unk_token=None,
                    	)
self.test = data.Field(use_vocab=True,
			batch_first=True,
            		include_lengths=False,
                    	eos_token='<EOS>' if use_eos else None
                    	)

step 2: "Define Dataset with Fields"

train, valid = data.TabularDataset.splits(path='',
					  train=train_fn,
                        		  validation=valid_fn,
                                          format='tsv',
                                          fields=[('label', self.label,
                                          ('text', self.text)]
                                          )

step 3: "Get DataLoaders from Datasets"

self.train_loader, self.valid_loader = data.BucketIterator.splits((train,valid),
							      batch_size=batch_size,
                                                              device='cuda:%d' % device if device >=0 else 'cpu'
                                                              shuffle=shuffle,
                                                              sort_key=lambda x: len(x.text),
                                                              sort_within_batch=True,
                                                              )

step 4: "Make Vocab dictionary"

 

self.label.build_vocab(train)
self.text.build_vocab(train, max_size=max_vocab, min_freq=min_freq)

 


torchtext == 0.8.0을 사용하였다.

from torchtext import data


class DataLoader(object):
    """
    Data loader class to load text file using torchtext library.
    """

    def __init__(
        self,
        train_fn,
        batch_size=64,
        valid_ratio=0.2,
        device=-1,
        max_vocab=999999,
        min_freq=1,
        use_eos=False,
        shuffle=True,
    ):
        """
        DataLoader initialization.
        :param train_fn: Train-set filename
        :param batch_size: Batchify data fot certain batch size.
        :param device: Device-id to load data (-1 for CPU)
        :param max_vocab: Maximum vocabulary size
        :param min_freq: Minimum frequency for loaded word.
        :param use_eos: If it is True, put <EOS> after every end of sentence.
        :param shuffle: If it is True, random shuffle the input data.
        """
        super().__init__()

        # field 정의
        # positive/negative , text
        self.label = data.Field(sequential=False, use_vocab=True, unk_token=None)
        self.text = data.Field(
            use_vocab=True,
            batch_first=True,
            include_lengths=False,
            eos_token="<EOS>" if use_eos else None,
        )

        # two columns will be delimited by TAB.
        # TabularDataset to load two columns in the input file.
        # Files consist of two columns: label field and text field.
        train, valid = data.TabularDataset(
            path=train_fn,
            format="tsv",
            fields=[
                ("label", self.label),
                ("text", self.text),
            ],
        ).split(split_ratio=(1 - valid_ratio))


        # train loadee and valid loader
        # We sort input sentences by length, to group similar lengths.
        self.train_loader, self.valid_loader = data.BucketIterator.splits(
            (train, valid),
            batch_size=batch_size,
            device="cuda:%d" % device if device >= 0 else "cpu",
            shuffle=shuffle,
            sort_key=lambda x: len(x.text),
            sort_within_batch=True,
        )

        # making mapping table between words and indice.
        self.label.build_vocab(train)
        self.text.build_vocab(train, max_size=max_vocab, min_freq=min_freq)


review.sorted.uniq.refined.tok.tsv를 shuffle 한 tsv 파일을 만든다.

shuf < review.sorted.uniq.refined.tok.tsv > review.sorted.uniq.refined.tok.shuf.tsv
korea@LAPTOP-J8PRNVSI MINGW64 ~/OneDrive/바탕 화면/NLP study/preprocessing/5_batchify
$ head -n 2 ./review.sorted.uniq.refined.tok.shuf.tsv
negative        렌치 가 다 찌그려 지 는 거 파 시 나요 너무 하 네요 
negative        먼지 가 너무 붙 어요 . . 
(mecab_venv) 
korea@LAPTOP-J8PRNVSI MINGW64 ~/OneDrive/바탕 화면/NLP study/preprocessing/5_batchify
$ head -n 2 ./review.sorted.uniq.refined.tok.tsv
negative        ! 
negative        ! 다 녹 아서 왓 어요 . . 짜증
(mecab_venv)

train은 62943개, test는 10000개로 데이터를 분리한다.

korea@LAPTOP-J8PRNVSI MINGW64 ~/OneDrive/바탕 화면/NLP study/preprocessing/5_batchify
$ head -n 62943 ./review.sorted.uniq.refined.tok.shuf.tsv > review.sorted.uniq.refined.tok.shuf.train.tsv
(mecab_venv) 
korea@LAPTOP-J8PRNVSI MINGW64 ~/OneDrive/바탕 화면/NLP study/preprocessing/5_batchify
$ tail -n 10000 ./review.sorted.uniq.refined.tok.shuf.tsv > review.sorted.uniq.refined.tok.shuf.test.tsv
(mecab_venv)

 

앞에서 정의한 DataLoader을 이용하여 train loader와 valid loader을 만든다.

loaders = DataLoader(
    train_fn='./review.sorted.uniq.refined.tok.shuf.train.tsv',
    batch_size=256,
    valid_ratio=.2,
    device=-1,
    max_vocab=999999,
    min_freq=5,
)

만든 loader을 통해 train sample의 개수, valid sample 개수를 확인할 수 있다.

print("|train|=%d" % len(loaders.train_loader.dataset))
print("|valid|=%d" % len(loaders.valid_loader.dataset))
|train|=50354 |valid|=12589

 

text vocab은 8690개의 단어로 이루어짐을 확인할 수 있다.

print("|vocab|=%d" % len(loaders.text.vocab))
print("|label|=%d" % len(loaders.label.vocab))
|vocab|=8690 |label|=2

 

미니배치를 하나만 받아오기 위해 iter로 받아온다. 90개의 단어로 이루어져있음을 알 수 있다.

data = next(iter(loaders.train_loader))

print(data.text.shape)
print(data.label.shape)
torch.Size([256, 90]) torch.Size([256])

 

미니배치의 마지막 문장을 확인해보면,

print(data.text[-1])
tensor([ 256, 341, 254, 43, 8, 7, 37, 2704, 6, 4889, 43, 84, 55, 77, 3, 92, 29, 37, 133, 11, 549, 733, 8562, 12, 159, 52, 286, 50, 42, 13, 4633, 4215, 8562, 12, 38, 186, 351, 1980, 5, 7, 37, 559, 1269, 6, 2101, 43, 577, 61, 55, 80, 5253, 559, 5, 64, 482, 21, 17, 37, 251, 575, 12, 266, 58, 61, 790, 151, 57, 67, 329, 234, 99, 61, 52, 64, 321, 29, 37, 335, 5, 83, 37, 1, 1, 1, 1, 1, 1, 1, 1, 1])

 

tensor형태의 데이터를 text로 복원하였다.

x = data.text[-1]
line = []
for x_i in x:
    line += [loaders.text.vocab.itos[x_i]]
    
print(' '.join(line))
와 기대 이상 으로 좋 네요 ! 볼트 도 여분 으로 더 주 시 고 감사 합니다 ! 제 가 타 브랜드 철봉 은 써 보 진 않 았 는데 멜 킨 철봉 은 가격 대비 매우 훌륭 하 네요 ! 조립 방법 도 동영상 으로 설명 해 주 어서 따라서 조립 하 니 금방 했 어요 ! 내 구성 은 계속 사용 해 봐야 알 겠 지만 일단 몇 번 해 보 니 튼튼 합니다 ! 번창 하 세요 !
728x90
Contents