1. 코퍼스 수집¶
pass
2. 정제¶
- 원하는 업무와 문제에 따라, 또는 응용 분야에 따라 필요한 정제의 수준이나 깊이가 다름
- ex) 음성인식: 사람의 음성을 그대로 받아적어야 하므로 괄호 또는 별표와 같은 특수 문자들을 포함해서는 안됨. 전화번호나 이메일주소, 신용카드 번호와 같은 개인정보나 민감한 정보들은 제거하거나 변조해서 모델링해야 할 수도 있음
전각문자 제거¶
- 대부분의 중국어와 일본어 문서, 그리고 일부 한국어 문서의 숫자, 영자, 기호가 전각문자일 때가 있음. 이러한 경우 일반적으로 사용되는 반각문자로 변환해주는 작업이 필요함
대소문자 통일¶
- 일부 영어 코퍼스에서는 약자 등에서의 대소문자 표현이 않을 때가 있음. 예를 들어 New York City의 줄임말(약자)인 NYC의 경우 다음과 같이 다양하게 표현가능(NYC/nyc/N.Y.C/N.Y.C.)
- 이러한 다양한 표현의 일원화는 하나의 의미를 지니는 여러 단어를 하나의 형태로 통일해 희소성을 줄이는 효과를 기대할 수 있음.
- 하지만 다양한 단어들을 비슷한 값의 벡터로 나타낼 수 있게 되면서, 대소문자 통일과 같은 문제를 해결할 필요성이 줄어듦
정규표현식을 사용한 정제¶
- 크롤링을 통해 얻어낸 데이터의 경우 특수문자, 기호등에 의해 노이즈가 섞일때가 많음. 이러한 노이즈들을 효율적으로 감지하고 없애려면 정규표현식이 필요함
In [16]:
import re
text = '''abc123def456ghi'''
pattern = '([a-z])[0-9]+([a-z])'
to = '띠용'
y = re.sub(pattern, to, text)
y
Out[16]:
3. 문장 단위 분절¶
- 여러 문장이 한 라인에 있거나, 한 문장이 여러 라인에 걸쳐 있는 경우에는 문장 단위의 분절이 필요함.
- 단순히 마침표만을 기준으로 문장 단위 분절을 수행하면 소수점 등 여러가지 문제에 마주칠 수 있음.
- 따라서 직접 분절해주는 모듈을 만들거나 nltk 등에서 제공하는 분절 모듈을 이용하기를 권장.
- 물론 이경우에도 완벽하지 못하며 일부 추가적인 전/후처리가 필요할 수도 있음.
In [22]:
text = '자연어처리는 인공지능의 한 줄기 입니다. 시퀀스 투 시퀀스의 등장 이후로 딥러닝을 활용한 자연어처리는 새로운 전기를 맞이하게 되었습니다. 문장을 받아 단순히 수치로 나타내던 시절을 넘어, 원하는대로 문장을 만들어낼 수 있게 된 것 입니다. 이에따라 이전까지 큰 변화가 없었던 자연어처리 분야의 연구는 폭발적으로 늘어나기 시작하여, 곧 기계번역 시스템은 신경망에 의해 정복 당하였습니다. 또한, attention 기법의 고도화로 전이학습이 발전하면서, QA 문제도 사람보다 정확한 수준이 되었습니다.'
In [25]:
import sys, fileinput, re
from nltk.tokenize import sent_tokenize
if text.strip() != "":
text = re.sub(r'([a-z])\.([A-Z])', r'\1. \2', text.strip())
sentences = sent_tokenize(text.strip())
for s in sentences:
if s != "":
result = sys.stdout.write(s + "\n")
result
4. 분절¶
- 한국어의 경우 띄워쓰기가 이미 되어 있어도 제각각인 경우가 많기 때문에, 정규화를 해주는 의미로 다시 한 번 표준화된 띄어쓰기를 적용하는 과정이 필요함.
- 또한, 교착어로써 접사를 어근에서 분리해주는 역할도 하므로 희소성 문제를 해소하기도 함.
- 영어의 경우 기본적으로 띄어쓰기가 있고, 대부분의 경우 규칙을 매우 잘 따르고 있음. 다만 언어 모델을 더 용이하게 구성할 수 있또록 일불 처리를 더 해주면 좋음. nltk를 사용하여 이러한 전처리를 수행함
- 한국어의 경우 Mecab, KoNLPy 등이 사용
5. 병렬 코퍼스 정렬¶
- 대부분의 병렬 코퍼스들은 여러 문장 단위로 정렬됨. 예를 들어, 영자 신문에서 크롤링한 영문 뉴스 기사는 한글 뉴스 기사에 매핑되지만, 문서와 문서 단위의 매핑일 뿐 문장 대 문장에 관한 정렬을 이루어져있지 않음. 이런 경우, 각각의 문장에 대해 정렬해주어야 함
- 그 과정에서 일부 불필요한 문장들을 걸러내야 하고, 문장 간 정렬이 잘 맞지 않는 경우 정렬을 재정비하거나 아예 걸러내야 함.
5.1 병렬 코퍼스 제작 프로세스 개요: 정렬을 수행하기 위한 전체 과정 요약¶
- 소스 언어와 타겟 언어 사이의 단어사전을 준비
- 만약 준비된 단어 사전이 없다면 다음 작업을 수행함. 이미 단어 사전을 가지고 있는 경우 7번으로
- 각 언어에 대해서 코퍼스를 수집 및 정제
- 각 언어에 대해서 단어 임베딩 벡터를 구함.
- MUSE를 통해서 단어 레벨 번역기를 훈련
- 훈련된 단어 레벨 번역기를 통해 두 언어 사이의 단어 사전 생성
- 만들어진 단어 사전을 넣어
Champollion
을 통해 기존에 수집된 다중 언어 코퍼스를 정렬 - 각 언어에 대해서 단어 사전을 적용하기 위해 알맞은 수준의 분절을 수행
- 각 언어에 대해서 정제를 수행
- Champollion을 사용하여 병렬 코퍼스를 생성
5.2 사전 생성¶
- 페이스북의 MUSE는 병렬 코퍼스가 없는 상황에서 사전을 구축하는 방법과 코드를 제공함
- 비지도학습
5.3 CTK를 활용한 정렬¶
- CTK는 이중 언어 코퍼스의 문장 정렬을 수행하는 오픈소스
6. 서브워드 분절¶
- BPE 알고리즘을 통한 서브워드 단위 분절은 현재 필수 전처리 방법으로 자리잡음.
- 서브워드 분절 기법은 기본적으로 '단어는 의미를 가진 더 작은 서브워드들의 조합으로 이루어진다'는 가정하에 적용되는 알고리즘
- 적절한 서브워드를 발견하여 해당 단위로 쪼개주면 어휘 수를 줄일 수 있고 희소성을 효과적으로 줄일 수 있음
- 희소성 감소 외에도, UNK(unknown) 토큰에 대한 효율적인 대처도 가능
- 서브워드 단위 분절을 통해 신조어나 오타와 같은 UNK에 대해 서브워드 단위나 문자 단위로 쪼개줌으로써 기존 훈련 데이터에서 보았던 토큰들의 조합으로 만들수 있음.
- 즉, UNK 자체를 없앰으로써 효율적으로 UNK에 대처할 수 있고, 자연어 처리 알고리즘의 결과물 품질을 향상시킬 수 있음.
- 구글의 SentencePiece 모듈이 속도가 빠름
토치텍스트¶
- 머신러닝이나 딥러닝을 수행하는 데이터를 읽고 전처리하는 코드를 모아둔 라이브러리
- 보통 Field라는 클래스를 통해 우리가 ㅇ릭고자 하는 텍스트 파일 내의 필드를 먼저 정의함
- 텍스트 파일 내에서 탭을 사용하여 필드(또는 열)를 구분하는 방식을 자연어 처리 분야의 입력에서 가장 많이 사용
- 정의된 각 필드를 Dataset 클래스를 통해 읽어들임
- 읽어들인 코퍼스는 미리 주어진 미니배치 크기에 따라서 나뉠 수 있도록 이터레이터에 들어감
- 미니배치를 구성하는 과정에서 미니배치 내의 문장의 길이가 다를 경우에는 필요에 따라 문장의 앞 뒤에 패딩(PAD)을 삽입함
- 이 패딩은 추후 소개할 BOS, EOS와 함께 하나의 단어 또는 토큰과 같은 취급을 받음
- 이후에 훈련 코퍼스에 대해 어휘 사전을 만들어 각 단어(토큰)를 숫자로 매핑하는 작업을 수행함
In [44]:
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=.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__()
# Define field of the input file.
# The input file consists of two fields.
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,
)
# Those defined two columns will be delimited by TAB.
# Thus, we use TabularDataset to load two columns in the input file.
# We would have two separate input file: train_fn, valid_fn
# 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))
# Those loaded dataset would be feeded into each iterator:
# train iterator and valid iterator.
# 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,
)
# At last, we make a vocabulary for label and text field.
# It is 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)
한 라인이 텍스트로만 채워져 있을 때¶
- 주로 언어모델을 훈련 시키는 상황에서 쓸 수 있음
LanguageModelDataset
을 통해 미리 정의된 필드를 텍스트 파일에서 읽어들임- 이 때 각 문장의 길이에 따라 정렬을 통해 비슷한 길이의 문장끼리 미니배치를 만들어줌
- 이 작업을 통해서 매우 상이한 길이의 문장들이 하나의 미니배치에 묶여 훈련 시간에서 손해보는 것을 방지
In [46]:
from torchtext import data, datasets
PAD, BOS, EOS = 1, 2, 3
class DataLoader():
def __init__(self,
train_fn,
valid_fn,
batch_size=64,
device='cpu',
max_vocab=99999999,
max_length=255,
fix_length=None,
use_bos=True,
use_eos=True,
shuffle=True
):
super(DataLoader, self).__init__()
self.text = data.Field(sequential=True,
use_vocab=True,
batch_first=True,
include_lengths=True,
fix_length=fix_length,
init_token='<BOS>' if use_bos else None,
eos_token='<EOS>' if use_eos else None
)
train = LanguageModelDataset(path=train_fn,
fields=[('text', self.text)],
max_length=max_length
)
valid = LanguageModelDataset(path=valid_fn,
fields=[('text', self.text)],
max_length=max_length
)
self.train_iter = data.BucketIterator(train,
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
)
self.valid_iter = data.BucketIterator(valid,
batch_size=batch_size,
device='cuda:%d' % device if device >= 0 else 'cpu',
shuffle=False,
sort_key=lambda x: -len(x.text),
sort_within_batch=True
)
self.text.build_vocab(train, max_size=max_vocab)
class LanguageModelDataset(data.Dataset):
def __init__(self, path, fields, max_length=None, **kwargs):
if not isinstance(fields[0], (tuple, list)):
fields = [('text', fields[0])]
examples = []
with open(path) as f:
for line in f:
line = line.strip()
if max_length and max_length < len(line.split()):
continue
if line != '':
examples.append(data.Example.fromlist(
[line], fields))
super(LanguageModelDataset, self).__init__(examples, fields, **kwargs)
병렬 코퍼스 읽기¶
- 텍스트로만 채워진 두 개의 파일을 동시에 입력 데이터로 읽어 들이는 코드
- 이 때 두 파일의 코퍼스는 병렬 데이터로 취급되어 같은 라인끼리 매핑되어야 하므로, 같은 라인수로 채워져 있어야 함
- 주로 기계번역이나 요약 등에 사용할 수 있음
- 탭을 사용하여 하나의 파일에서 두개의 열에 각 언어의 문장을 표현하는 것도 한 가지 방법
- 그렇다면 앞의
TabularDataset
클래스를 이용하면 됨 - 그리고 앞 서 소개함
LanguageModelDataset
과 마찬가지로 길이에 따라서 미니배치를 구성함
In [47]:
from torchtext import data, datasets
PAD, BOS, EOS = 1, 2, 3
class DataLoader():
def __init__(self,
train_fn=None,
valid_fn=None,
exts=None,
batch_size=64,
device='cpu',
max_vocab=99999999,
max_length=255,
fix_length=None,
use_bos=True,
use_eos=True,
shuffle=True,
dsl=False
):
super(DataLoader, self).__init__()
self.src = data.Field(
sequential=True,
use_vocab=True,
batch_first=True,
include_lengths=True,
fix_length=fix_length,
init_token='<BOS>' if dsl else None,
eos_token='<EOS>' if dsl else None,
)
self.tgt = data.Field(
sequential=True,
use_vocab=True,
batch_first=True,
include_lengths=True,
fix_length=fix_length,
init_token='<BOS>' if use_bos else None,
eos_token='<EOS>' if use_eos else None,
)
if train_fn is not None and valid_fn is not None and exts is not None:
train = TranslationDataset(
path=train_fn,
exts=exts,
fields=[('src', self.src), ('tgt', self.tgt)],
max_length=max_length
)
valid = TranslationDataset(
path=valid_fn,
exts=exts,
fields=[('src', self.src), ('tgt', self.tgt)],
max_length=max_length,
)
self.train_iter = data.BucketIterator(
train,
batch_size=batch_size,
device='cuda:%d' % device if device >= 0 else 'cpu',
shuffle=shuffle,
sort_key=lambda x: len(x.tgt) + (max_length * len(x.src)),
sort_within_batch=True,
)
self.valid_iter = data.BucketIterator(
valid,
batch_size=batch_size,
device='cuda:%d' % device if device >= 0 else 'cpu',
shuffle=False,
sort_key=lambda x: len(x.tgt) + (max_length * len(x.src)),
sort_within_batch=True,
)
self.src.build_vocab(train, max_size=max_vocab)
self.tgt.build_vocab(train, max_size=max_vocab)
def load_vocab(self, src_vocab, tgt_vocab):
self.src.vocab = src_vocab
self.tgt.vocab = tgt_vocab
class TranslationDataset(data.Dataset):
"""Defines a dataset for machine translation."""
@staticmethod
def sort_key(ex):
return data.interleave_keys(len(ex.src), len(ex.trg))
def __init__(self, path, exts, fields, max_length=None, **kwargs):
"""Create a TranslationDataset given paths and fields.
Arguments:
path: Common prefix of paths to the data files for both languages.
exts: A tuple containing the extension to path for each language.
fields: A tuple containing the fields that will be used for data
in each language.
Remaining keyword arguments: Passed to the constructor of
data.Dataset.
"""
if not isinstance(fields[0], (tuple, list)):
fields = [('src', fields[0]), ('trg', fields[1])]
if not path.endswith('.'):
path += '.'
src_path, trg_path = tuple(os.path.expanduser(path + x) for x in exts)
examples = []
with open(src_path, encoding='utf-8') as src_file, open(trg_path, encoding='utf-8') as trg_file:
for src_line, trg_line in zip(src_file, trg_file):
src_line, trg_line = src_line.strip(), trg_line.strip()
if max_length and max_length < max(len(src_line.split()), len(trg_line.split())):
continue
if src_line != '' and trg_line != '':
examples += [data.Example.fromlist([src_line, trg_line], fields)]
super().__init__(examples, fields, **kwargs)
In [ ]:
'자연어, 비전' 카테고리의 다른 글
카카오톡 대화 내용으로 개인별 워드클라우드(wordcloud) 그리기 (1) | 2022.06.22 |
---|---|
transformer 구현 및 설명 (0) | 2021.06.03 |
시퀀스 모델링 (0) | 2020.11.10 |
워드 임베딩 (0) | 2020.11.10 |
단어 유사도 정리 (0) | 2020.11.09 |