🔥알림🔥
① 테디노트 유튜브 - 구경하러 가기!
② LangChain 한국어 튜토리얼 바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs) 바로가기 🙌

7 분 소요

torchtext는 pytorch 모델에 주입하기 위한 텍스트 데이터셋을 구성하기 편하게 만들어 주는 데이터 로더(Data Loader) 입니다. torchtext 를 활용하여 CSV, TSV, JSON 등의 정형 데이터셋을 쉽게 로드하도록 도와주는 TabularDataset 클래스의 활용 방법과 제공해주는 토크나이저(tokenizer) 워드 벡터(Word Vector) 를 적용하는 방법에 대하여 알아보겠습니다.

튜토리얼의 끝 부분에는 Pandas 의 DataFrame을 Data Loader 로 쉽게 변환하는 방법도 알아보도록 하겠습니다.

예제 코드는 아래에서 확인할 수 있습니다.

예제 코드

torchtext 튜토리얼

샘플 데이터셋 다운로드

import urllib

url = 'https://storage.googleapis.com/download.tensorflow.org/data/bbc-text.csv'
urllib.request.urlretrieve(url, 'bbc-text.csv')
('bbc-text.csv', <http.client.HTTPMessage at 0x7fef4303e940>)

Pandas로 데이터 로드 및 출력

import pandas as pd

df = pd.read_csv('bbc-text.csv')
df
category text
0 tech tv future in the hands of viewers with home th...
1 business worldcom boss left books alone former worldc...
2 sport tigers wary of farrell gamble leicester say ...
3 sport yeading face newcastle in fa cup premiership s...
4 entertainment ocean s twelve raids box office ocean s twelve...
... ... ...
2220 business cars pull down us retail figures us retail sal...
2221 politics kilroy unveils immigration policy ex-chatshow ...
2222 entertainment rem announce new glasgow concert us band rem h...
2223 politics how political squabbles snowball it s become c...
2224 sport souness delight at euro progress boss graeme s...

2225 rows × 2 columns

토크나이저 생성

from torchtext.data.utils import get_tokenizer

tokenizer의 타입으로는 basic_english, spacy, moses, toktok, revtok, subword 이 있습니다.

다만, 이 중 몇개의 타입은 추가 패키지가 설치되어야 정상 동작합니다.

tokenizer = get_tokenizer('basic_english', language='en')
tokenizer("I'd like to learn torchtext")
['i', "'", 'd', 'like', 'to', 'learn', 'torchtext']

토큰 타입을 지정하면 그에 맞는 tokenizer를 반환하는 함수를 생성한 뒤 원하는 타입을 지정하여 tokenizer를 생성할 수 있습니다.

def generate_tokenizer(tokenizer_type, language='en'):
    return get_tokenizer(tokenizer_type, language=language)

basic_english를 적용한 경우

tokenizer = generate_tokenizer('basic_english')
tokenizer("I'd like to learn torchtext")
['i', "'", 'd', 'like', 'to', 'learn', 'torchtext']

toktok을 적용한 경우

tokenizer = generate_tokenizer('toktok')
tokenizer("I'd like to learn torchtext")
['I', "'", 'd', 'like', 'to', 'learn', 'torchtext']
from nltk.tokenize import word_tokenize

word_tokenize("I'd like to learn torchtext")
['I', "'d", 'like', 'to', 'learn', 'torchtext']

필드(Field) 정의

from torchtext.legacy import data

torchtext.legacy.data.Field

  • Field 클래스는 Tensor로 변환하기 위한 지침과 함께 데이터 유형을 정의합니다.

  • Field 객체는 vocab 개체를 보유합니다.

  • Field 객체는 토큰화 방법, 생성할 Tensor 종류와 같이 데이터 유형을 수치화하는 역할을 수행합니다.

TEXT = data.Field(sequential=True,    # 순서를 반영
                  tokenize=tokenizer, # tokenizer 지정
                  fix_length=120,     # 한 문장의 최대 길이 지정
                  lower=True,         # 소문자 화
                  batch_first=True)   # batch 를 가장 먼저 출력


LABEL = data.Field(sequential=False)

fields 변수에 dictionary를 생성합니다.

  • key: 읽어 들여올 파일의 열 이름을 지정합니다.

  • value: (문자열, data.Field) 형식으로 지정합니다. 여기서 지정한 문자열이 나중에 생성된 data의 변수 이름으로 생성됩니다.

(참고) fields에 [('text', TEXT), ('label', LABEL)] 와 같이 생성하는 경우도 있습니다. 컬러명 변경이 필요하지 않은 경우는 List(tuple(컬럼명, 변수))로 생성할 수 있습니다.

fields = {
    'text': ('text', TEXT), 
    'category': ('label', LABEL)
}

데이터셋 로드 및 분할

TabularDataset 클래스는 정형 데이터파일로부터 직접 데이터를 읽을 때 유용합니다.

지원하는 파일 형식은 CSV, JSON, TSV 을 지원합니다.

import random
from torchtext.legacy.data import TabularDataset

SEED = 123

dataset = TabularDataset(path='bbc-text.csv',  # 파일의 경로
                         format='CSV',         # 형식 지정
                         fields=fields,        # 이전에 생성한 field 지정
                         skip_header=True    # 첫 번째 행은 컬러명이므로 skip
                        )        

이전에 생성한 dataset 변수로 train / test 데이터셋을 분할 합니다.

train_data, test_data = dataset.split(split_ratio=0.8,               # 분할 비율
                                      stratified=True,               # stratify 여부
                                      strata_field='label',          # stratify 대상 컬럼명
                                      random_state=random.seed(SEED) # 시드
                                     )
# 생성된 train / test 데이터셋의 크기를 출력 합니다.
len(train_data), len(test_data)
(1781, 444)

단어 사전 생성

TEXT.build_vocab(train_data, 
                 max_size=1000,             # 최대 vocab_size 지정 (미지정시 전체 단어사전 개수 대입)
                 min_freq=5,                # 최소 빈도 단어수 지정
                 vectors='glove.6B.100d')   # 워드임베딩 vector 지정, None으로 지정시 vector 사용 안함

LABEL.build_vocab(train_data)
NUM_VOCABS = len(TEXT.vocab.stoi)
NUM_VOCABS
1002
TEXT.vocab.freqs.most_common(10)
[('the', 41674),
 ('to', 19644),
 ('of', 15674),
 ('and', 14621),
 ('a', 14327),
 ('in', 13995),
 ('s', 7126),
 ('for', 7054),
 ('is', 6535),
 ('that', 6329)]

TEXT.vocab.stoi는 문자열을 index로, TEXT.vocab.itos는 index를 문자열로 변환합니다.

TEXT.vocab.stoi
defaultdict({'<unk>': 0,
             '<pad>': 1,
             'the': 2,
             'to': 3,
             'of': 4,
             'and': 5,
             ...
             'dems': 995,
             'laws': 996,
             'rival': 997,
             'story': 998,
             'watch': 999,
             ...})
# string to index
print(TEXT.vocab.stoi['this'])
print(TEXT.vocab.stoi['pretty'])
print(TEXT.vocab.stoi['original'])

print('==='*10)

# index to string
print(TEXT.vocab.itos[14])
print(TEXT.vocab.itos[194])
print(TEXT.vocab.itos[237])
37
0
849
==============================
was
end
record

버킷 이터레이터 생성

  • BucketIterator 의 주된 역할은 데이터셋에 대한 배치 구성입니다.
import torch

device = torch.device('cuda:1' if torch.cuda.is_available() else 'cpu')

BATCH_SIZE = 32

train_iterator, test_iterator = data.BucketIterator.splits(
    (train_data, test_data),     # dataset
    sort=False,
    repeat=False,
    batch_size=BATCH_SIZE,       # 배치사이즈
    device=device)               # device 지정

1개의 배치를 추출합니다.

# 1개의 batch 추출
sample_data = next(iter(train_iterator))

text 의 shape 를 확인합니다.

# batch_size, sequence_length
sample_data.text.shape
torch.Size([32, 120])
len(sample_data.text)
32
sample_data.label.size(0)
32

label 의 shape 를 확인합니다.

# batch_size
sample_data.label.shape
torch.Size([32])
# label을 출력합니다.
sample_data.label
tensor([5, 1, 2, 4, 1, 4, 5, 2, 2, 4, 1, 2, 5, 3, 1, 3, 4, 4, 1, 4, 3, 3, 2, 1,
        3, 5, 2, 4, 1, 5, 3, 5], device='cuda:1')

아래에서 확인할 수 있듯이 <unk> 토큰 때문에 카테고리의 개수가 5개임에도 불구하고 index는 0번부터 5번까지 맵핑되어 있습니다.

LABEL.vocab.stoi
defaultdict({'<unk>': 0,
             'sport': 1,
             'business': 2,
             'politics': 3,
             'tech': 4,
             'entertainment': 5})

따라서, 0번을 무시해주기 위해서는 배치 학습시 다음과 같이 처리해 줄 수 있습니다.

1을 subtract 해줌으로써 0~4번 index로 조정해 주는 것입니다.

sample_data.label.sub_(1)
tensor([4, 0, 1, 3, 0, 3, 4, 1, 1, 3, 0, 1, 4, 2, 0, 2, 3, 3, 0, 3, 2, 2, 1, 0,
        2, 4, 1, 3, 0, 4, 2, 4], device='cuda:1')

데이터프레임(DataFrame) 커스텀 데이터셋 클래스

torchtext.legacy.data.Dataset을 확장하여 DataFrame을 바로 BucketIterator로 변환할 수 있습니다.

import pandas as pd
from sklearn.model_selection import train_test_split

SEED = 123

# 데이터프레임을 로드 합니다.
df = pd.read_csv('bbc-text.csv')

# 컬럼명은 text / label 로 변경합니다
df = df.rename(columns={'category': 'label'})
df
label text
0 tech tv future in the hands of viewers with home th...
1 business worldcom boss left books alone former worldc...
2 sport tigers wary of farrell gamble leicester say ...
3 sport yeading face newcastle in fa cup premiership s...
4 entertainment ocean s twelve raids box office ocean s twelve...
... ... ...
2220 business cars pull down us retail figures us retail sal...
2221 politics kilroy unveils immigration policy ex-chatshow ...
2222 entertainment rem announce new glasgow concert us band rem h...
2223 politics how political squabbles snowball it s become c...
2224 sport souness delight at euro progress boss graeme s...

2225 rows × 2 columns

# train / validation 을 분할 합니다.
train_df, val_df = train_test_split(df, test_size=0.2, random_state=SEED)
# train DataFrame
train_df.head()
label text
1983 sport officials respond in court row australian tenn...
878 tech slow start to speedy net services faster broad...
94 politics amnesty chief laments war failure the lack of ...
1808 sport dal maso in to replace bergamasco david dal ma...
1742 tech technology gets the creative bug the hi-tech a...
# validation DataFrame
val_df.head()
label text
717 politics child access laws shake-up parents who refuse ...
798 entertainment fry set for role in hitchhiker s actor stephen...
1330 business palestinian economy in decline despite a short...
18 business japanese banking battle at an end japan s sumi...
1391 business manufacturing recovery slowing uk manufactur...
# 필요한 모듈 import
import torch
from torchtext.legacy import data
from torchtext.data.utils import get_tokenizer

# device 설정
device = torch.device('cuda:1' if torch.cuda.is_available() else 'cpu')
print(device)
cuda:1

torchtext.legacy.data.Dataset을 상속하여 데이터프레임을 로드할 수 있습니다.

class DataFrameDataset(data.Dataset):

    def __init__(self, df, fields, is_test=False, **kwargs):
        examples = []
        for i, row in df.iterrows():
            # text, label 컬럼명은 필요시 변경하여 사용합니다
            label = row['label'] if not is_test else None
            text = row['text'] 
            examples.append(data.Example.fromlist([text, label], fields))

        super().__init__(examples, fields, **kwargs)

    @staticmethod
    def sort_key(ex):
        return len(ex.text)

    @classmethod
    def splits(cls, fields, train_df, val_df=None, test_df=None, **kwargs):
        train_data, val_data, test_data = (None, None, None)
        data_field = fields

        if train_df is not None:
            train_data = cls(train_df.copy(), data_field, **kwargs)
        if val_df is not None:
            val_data = cls(val_df.copy(), data_field, **kwargs)
        if test_df is not None:
            test_data = cls(test_df.copy(), data_field, False, **kwargs)

        return tuple(d for d in (train_data, val_data, test_data) if d is not None)
# 토크나이저 정의 (다른 토크나이저로 대체 가능)
tokenizer = get_tokenizer('basic_english')

앞선 내용과 마찬가지로 Field를 구성합니다.

TEXT = data.Field(sequential=True,    # 순서를 반영
                  tokenize=tokenizer, # tokenizer 지정
                  fix_length=120,     # 한 문장의 최대 길이 지정
                  lower=True,         # 소문자화
                  batch_first=True)   # batch 를 가장 먼저 출력


LABEL = data.Field(sequential=False)

# fiels 변수에 List(tuple(컬럼명, 변수)) 형식으로 구성 후 대입
fields = [('text', TEXT), ('label', LABEL)]
# DataFrame의 Splits로 데이터셋 분할
train_ds, val_ds = DataFrameDataset.splits(fields, train_df=train_df, val_df=val_df)
# 단어 사전 생성
TEXT.build_vocab(train_ds, 
                 max_size=1000,             # 최대 vocab_size 지정 (미지정시 전체 단어사전 개수 대입)
                 min_freq=5,                # 최소 빈도 단어수 지정
                 vectors='glove.6B.100d')   # 워드임베딩 vector 지정, None으로 지정시 vector 사용 안함

LABEL.build_vocab(train_ds)
# 단어 사전 개수 출력
NUM_VOCABS = len(TEXT.vocab)
NUM_VOCABS
# 개수 1000 + <unk> + <pad> : 총 1002개
1002

BucketIterator를 생성합니다.

BATCH_SIZE = 32

train_iterator, test_iterator = data.BucketIterator.splits(
    (train_ds, val_ds), 
    batch_size=BATCH_SIZE,
    sort_within_batch=True,
    device=device)
# 1개 배치 추출
sample_data = next(iter(train_iterator))
# text shape 출력 (batch_size, sequence_length)
sample_data.text.shape
torch.Size([32, 120])
# label 출력 (batch)
sample_data.label
tensor([1, 2, 4, 4, 3, 4, 5, 4, 5, 1, 2, 1, 2, 2, 5, 5, 2, 5, 5, 2, 5, 1, 1, 2,
        5, 5, 1, 3, 2, 3, 3, 5], device='cuda:1')

댓글남기기