🔥알림🔥
① 테디노트 유튜브 -
구경하러 가기!
② LangChain 한국어 튜토리얼
바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs)
바로가기 🙌
④ RAG 비법노트 LangChain 강의오픈
바로가기 🙌
⑤ 서울대 PyTorch 딥러닝 강의
바로가기 🙌
torchtext를 활용한 텍스트 데이터 전처리 방법
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')
댓글남기기