본문 바로가기
NLP

[NLP] 자연어처리 토큰화작업 2, 하위 단어 토큰화

by 단깅수 2024. 3. 13.
728x90

하위 단어 토큰화

자연어 처리에서 형태소 분석은 중요한 전처리 과정 중 하나입니다. 컴퓨터가 자연어를 인간이 이해하는 방식과 비슷하게 처리할 수 있도록 하기 위해서는 형태소 단위의 토큰화가 효과적인 방법입니다.

 

그러나 언어는 시간이 지남에 따라 변화하고 새로운 단어나 표현이 등장하며 더 이상 사용되지 않는 단어나 표현도 생깁니다. 현대의 일상 언어에서는 맞춤법이나 띄어쓰기가 엄격하게 지켜지지 않는 경우가 많고 형태소 분석기의 취약점인 신조어나 고유어 등이 빈번하게 생겨납니다. 외래어, 띄어쓰기 오류, 오탈자 등이 있는 문장을 기존 형태소 분석기로 토큰화하면 다음과 같은 결과가 나타납니다.

  • 원문 : 시보리도 짱짱하고 허리도 어벙하지 않고 조아효
  • 결과 : ['시', '보리', '도', '짱짱하고', '허리', '도', '어', '벙하지', '않고', '조', '아', '효']

형태소 분석기 결과를 보면 '시보리'라는 외래어를 인식하지 못하고 '시'와 '보리'로 나뉜 것을 볼 수 있습니다. '어벙하다'라는 표현도 '어'라는 토큰과 '벙하지' 토큰으로 나뉘었습니다.

 

현대 자연어 처리에서는 신조어의 발생, 오탈자, 축약어 등을 고려해야 하기 때문에 분석할 단어의 양이 많아져 어려움을 겪습니다. 이를 해결하기 위한 방법 중 하나로 하위 단어 토큰화(Subword Tokenization)이 있습니다.

 

하위 단어 토큰화란 하나의 단어가 빈번하게 사용되는 하위 단어(Subword)의 조합으로 나누어서 토큰화하는 방법입니다. 예를 들어 'Reinforcement'라는 단어는 길이가 비교적 길어 처리가 어려울 수 있지만 하위 단어 토큰화를 적용한다면 'Rein', 'force', 'ment' 등으로 나눠 처리할 수 있습니다.

 

하위 단어 토큰화를 적용하면 단어의 길이를 줄일 수 있어서 처리 속도가 빨라질 뿐만 아니라, OOV 문제, 신조어, 은어, 고유어 등으로 인한 문제를 완화할 수 있습니다. 방법으로는 바이트 페어 인코딩, 워드피스, 유니그램 모델 등이 있습니다.

 

바이트 페어 인코딩

바이트 페어 인코딩(Byte Pair Encoding, BPE)이란 다이그램 코딩(Digram Coding)이라고도 하며 하위 단어 토큰화의 한 종류입니다. 텍스트 데이터에서 가장 빈번하게 등장하는 글자 쌍의 조합을 찾아 부호화하는 압축 알고리즘으로 초기에는 데이터 압축을 위해 개발됐으나, 자연어 처리 분야에서 하위 단어 토큰화를 위한 방법으로 사용됩니다.

  • 빈도 사전 : ('low', 5), ('lower', 2), ('newest', 6), ('widest', 3)
  • 어휘 사전 : ['low', 'lower', 'newest', 'widest']

 바이트 페어 인코딩은 입력 데이터에서 가장 많이 등장한 글자의 빈도수를 측정하고, 가장 빈도수가 높은 글자 쌍을 탐색해 치환해가면서 어휘 사전에 추가합니다. 빈도 사전을 보면 말뭉치에 각각이 5번, 2번, 6번, 3번 등장했음을 알 수 있습니다. 빈도 사전을 바이트 페어 인코딩으로 재구성한다면 아래와 같습니다.

  • 빈도 사전 : (l, o, w, 5), (l, o, w, e, r, 2), (n, e, w, e, s, t, 6), (w, i, d, e, s, t, 3)
  • 어휘 사전 : [d, e, i, l, n, o, r, s, t, w]

빈도 사전을 기준으로 가장 자주 등장한 글자 쌍을 찾습니다. 위 빈도 사전의 예시를 보면 es와 t쌍이 6 + 3 = 9번으로 가장 많이 등장한 걸 확인할 수 있습니다. 이를 반영하면 아래와 같습니다.

  • 빈도 사전 : (l, o, w, 5), (l, o, w, e, r, 2), (n, e, w, est, 6), (w, i, d, est, 3)
  • 어휘 사전 : [d, e, i, l, n, o, r, s, t, w, es, est]

이러한 과정을 총 10번 정도 반복했다고 가정하면 아래와 같습니다.

  • 빈도 사전 : (low, 5), (low, e, r, 2), (n, e, w, est, 6), (w, i, d, est, 3)
  • 어휘 사전 : [d, e, i, l, n, o, r, s, t, w, es, est, lo, low, ne, new, newest, wi, wid, widest]

BPE 알고리즘을 사용해 말뭉치에서 자주 등장하는 글자 쌍을 찾아 어휘 사전을 구축했습니다. 따라서 newer 와 같은 기존 말뭉치에 등장하지 않았던 새로운 단어가 입력되더라도 OOV로 처리되지 않고 기존 어휘 사전을 참고해 new e r로 토큰화할 수 있습니다.

 

센텐스피스 및 코포라

센텐스피스(Sentencepiece) 라이브러리는 구글에서 개발한 오픈소스 하위 단어 토크나이저 라이브러리입니다. 바이트 페어 인코딩과 유사한 알고리즘을 사용해 입력 데이터를 토큰화하고 단어 사전을 생성합니다. 또한 워드피스, 유니코드 기반의 다양한 알고리즘을 지원하며 사용자가 직접 설정할 수 있는 하이퍼파라미터들을 제공해 세밀한 토크나이징 기능을 제공합니다.

 

코포라(Korpora) 라이브러리는 국립국어원이나 AI Hub에서 제공하는 말뭉치 데이터를 쉽게 사용할 수 있게 제공하는 오픈소스 라이브러리입니다. 파이썬에서 쉽게 사용할 수 있게 API가 제공됩니다.

 

  • 센텐스피스, 코포라 라이브러리 설치
!pip install sentencepiece Korpora

 

  • 청와대 청원 데이터 다운로드
    • 코포라 라이브러리는 말뭉치 불러오기를 통해 말뭉치 데이터 다운로드 가능
    • corpus는 총 433,631개의 청원 데이터가 저장되어 있으며, train 속성으로 학습 데이터 가져오기 가능
from Korpora import Korpora

corpus = Korpora.load("korean_petitions")
dataset = corpus.train
petition = dataset[0]

print("청원 시작일:", petition.begin)
print("청원 종료일:", petition.end)
print("청원 동의 수:", petition.num_agree)
print("청원 범주:", petition.category)
print("청원 제목:", petition.title)
print("청원 본문:", petition.text[:30])

 

  • 학습 데이터세트 생성
from Korpora import Korpora

corpus = Korpora.load("korean_petitions")
petitions = corpus.get_all_texts()
with open ("../datasets/corpus.txt", "w", encoding = "utf-8") as f:
  for petition in petitions:
    f.write(petition + "\n")

 

  • 토크나이저 모델 학습
    • 센텐스피스 라이브러리의 토크나이저 모델 학습
from sentencepiece import SentencePieceTrainer

SentencePieceTrainer.Train(
    "--input=../datasets/corpus.txt\
    --model_prefix = petition_bpe\
    --vocab_size = 8000 model_type = bpe"
)

 

  • 바이트 페어 인코딩 토큰화
from sentencepiece import SentencePieceProcessor

tokenizer = SentencePieceProcessor()
tokenizer.load("petition_bpe.model")

sentence = "안녕하세요, 토크나이저가 잘 학습되었군요!"
sentences = ["이렇게 입력값을 리스트로 받아서", "쉽게 토크나이저를 사용할 수 있습니다"]

tokenized_sentence = tokenizer.encode_as_pieces(sentence)
tokenized_sentences = tokenizer.encode_as_pieces(sentences)

print('단일 문장 토큰화 :', tokenized_sentence)
print('여러 문장 토큰화 :', tokenized_sentences)

encoded_sentence = tokenizer.encode_as_ids(sentence)
encoded_sentences = tokenizer.encode_as_ids(sentences)
print("단일 문장 정수 인코딩 :", encoded_sentence)
print("여러 문장 정수 인코딩 :", encoded_sentences)

decode_ids = tokenizer.decode_ids(encoded_sentence)
decode_pieces = tokenizer.decode_pieces(encoded_sentences)
print("정수 인코딩에서 문장 변환 :", decode_ids)
print("하위 단어 토큰에서 문장 변환 :", decode_pieces)

 

  • 어휘 사전 불러오기
from sentencepiece import SentencePieceProcessor

tokenizer = SentencePieceProcessor()
tokenizer.load('petition_bpe.model')

vocab = {idx : tokenizer.id_to_piece(idx) for idx in range(tokenizer.get_piece_size())}
print(list(vocab.items())[:5])
print("vocab size :", len(vocab))

토크나이저스

토크나이저스 라이브러리의 워드피스 API를 이용하면 쉽고 빠르게 토크나이저를 구현하고 학습할 수 있습니다. 허깅 페이스의 토크나이저스(Tokenizers) 라이브러리를 사용해서 실습을 진행해보겠습니다.

 

  • 허깅 페이스 토크나이저스 라이브러리 설치
!pip install tokenizers

 

토크나이저스 라이브러리는 정규화(Normalization)사전 토큰화(Pre-tokenization)를 제공합니다.

 

정규화는 일관된 형식으로 텍스트를 표준화하고 모호한 경우를 방지하기 위해 일부 문자를 대체하거나 제거하는 등의 작업을 수행합니다. 불필요한 공백 제거, 대소문자 변환, 유니코드 정규화, 구두점 처리, 특수 문자 처리 등을 제공합니다.

 

사전 토큰화는 입력 문장을 토큰화하기 전에 단어와 같은 작은 단위로 나누는 기능을 제공합니다. 공백 혹은 구두점을 기준으로 입력 문장을 나눠 텍스트 데이터를 효율적으로 처리하고 모델의 성능을 향상시킬 수 있습니다.

 

  • 워드피스 토크나이저 학습
    • 토크나이저스 라이브러리의 토크나이저로 워드피스 모델 불러오기
    • NFD 유니코드 정규화, 소문자 변환 등을 사용
from tokenizers import Tokenizer
from tokenizers.models import WordPiece
from tokenizers.normalizers import Sequence, NFD, Lowercase
from tokenizers.pre_tokenizers import Whitespace

tokenizer = Tokenizer(WordPiece())
tokenizer.normalizer = Sequence([NFD(), Lowercase()])
tokenizer.pre_tokenizer = Whitespace()

tokenizer.train(['../datasets/corpus.txt'])
tokenizer.save('../models/petition_wordpiece.json')

 

  • 워드피스 토큰화
from tokenizers import Tokenizer
from tokenizers.decoders import WordPiece as WordPieceDecoder

tokenizer = Tokenizer.from_file('../models/petition_wordpiece.json')
tokenizer.decoder = WordPieceDecoder()

sentence = "안녕하세요, 토크나이저가 잘 학습되었군요!"
sentences = ["이렇게 입력값을 리스트로 받아서", "쉽게 토크나이저를 사용할 수 있습니다"]

encoded_sentence = tokenizer.encode(sentence)
encoded_sentences = tokenizer.encode_batch(sentences)

print("인코더 형식 :", type(encoded_sentence))

print('단일 문장 토큰화 :', encoded_sentence.tokens)
print('여러 문장 토큰화 :', [enc.tokens for enc in encoded_sentences])

print("단일 문장 정수 인코딩 :", encoded_sentence.ids)
print("여러 문장 정수 인코딩 :", [enc.ids for enc in encoded_sentences])

print("정수 인코딩에서 문장 변환 :", tokenizer.decode(encoded_sentence.ids))

 

이번 포스팅에서는 바이트 페어 인코딩과 워드피스라는 두 가지 알고리즘에 대해서 알아보았습니다. 최근 연구 동향은 더 큰 말뭉치를 사용해 모델을 학습하고 OOV의 위험을 줄이기 위해 하위 단어 토큰화를 활용합니다.

 

이 외에도 유니코드 단위가 아닌 바이트 단위에서 토큰화하는 바이트 수준 바이트 페어 인코딩(Byte-level Byte-Pair-Encoding, BBPE)이나 크기가 큰 어휘 사전에서 덜 필요한 토큰을 제거하며 학습하는 유니그램(Unigram) 등이 있습니다.

728x90