23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
자연어 문장을 컴퓨터가 쉽게 이해하게 만들기 위해서는 다양한 전처리 과정을 거쳐야합니다.
그 중 하나로 문장을 토큰 단위로 쪼개서 처리하는 토크나이징 기법이 있습니다.
오늘은 SentencePiece를 활용하여 한국어 텍스트를 효과적으로 토크나이징 하는 방법을 소개합니다.
SentencePiece는 Google에서 2018년도에 공개한 오픈소스 라이브러리로, 다양한 자연어처리 태스크에서 널리 사용되고 있습니다.
최근에는 Huggingface에서 공개한 Tokenizers도 자주 사용되고 있지만 오늘은 Sentencepiece에 대한 내용을 주로 다루도록 하겠습니다.
데이터 전처리 | 예시 |
|---|---|
텍스트 데이터를 모델이 이해하는 벡터로 바꾸려면 다양한 전처리 과정을 거치게 됩니다. 오늘 다루는 주제는 이중에서도 가장 앞단에 있는 텍스트를 토큰 단위로 쪼개는(Split) 전처리 과정을 다룹니다. 오른쪽 그림은 |
pyenv를 통해 sentencepiece를 설치할 환경을 구성합니다
작성시점 기준 비교적 최신인 3.11.x 버전을 설치해줍니다
설치는 pyenv 기준으로 진행하겠습니다
$ pyenv install -list
Available versions:
....
3.11.1
3.12.0a4
3.12-dev
activepython-3.6.0
anaconda-1.4.0
$ pyenv install 3.11.1
python-build: use openssl@1.1 from homebrew
python-build: use readline from homebrew
Downloading Python-3.11.1.tar.xz...
-> https://www.python.org/ftp/python/3.11.1/Python-3.11.1.tar.xz
Installing Python-3.11.1...
python-build: use readline from homebrew
Installed Python-3.11.1 to /Users/사용자계정명/.pyenv/versions/3.11.1 pyenv로 파이썬 가상환경을 구성 후 필요한 패키지를 설치합니다
$ pyenv virtualenv 3.11.1 nlp
$ pyenv activate nlp
$(nlp) pip install sentencepiece
$(nlp) pip install datasets
$(nlp) pip install transformersSentencePiece는 subword 토크나이저로 단어를 subword 단위로 구성하여 학습합니다
주어진 corpus를 subword 단위로 구성하여 subword의 빈도수를 계산해서 높은 빈도수를 가진 subword를 병합하여 모델을 완성합니다
subword의 패턴을 파악하기 위해서는 corpus가 필요합니다
학습할 corpus는 주로 wiki 데이터 또는 모델을 활용하고자 하는 도메인에서 일부를 샘플링해서 구축하지만 실습의 편의를 고려하여 nsmc (naver sentiment movie corpus)를 사용하겠습니다
nsmc 데이터는 huggingface dataset hub에서도 다운받을 수 있습니다
from datasets import load_dataset
import os
data_dir = './data'
dataset = load_dataset('nsmc')
os.makedirs(data_dir, exist_ok=True)
for split_key in dataset.keys():
doc_path = f"{data_dir}/{split_key}.txt"
with open(doc_path, 'w') as f:
for doc in dataset[split_key]['document']:
f.write(doc+'\n')데이터를 다운받으면 아래와 같이 학습에 사용할 데이터가 구축됩니다
.
├── README.md
├── data
├── test.txt
└── train.txt$ head -n 5 train.txt
아 더빙.. 진짜 짜증나네요 목소리
흠...포스터보고 초딩영화줄....오버연기조차 가볍지 않구나
너무재밓었다그래서보는것을추천한다
교도소 이야기구먼 ..솔직히 재미는 없다..평점 조정
사이몬페그의 익살스런 연기가 돋보였던 영화!스파이더맨에서 늙어보이기만 했던 커스틴 던스트가 너무나도 이뻐보였다데이터가 구축되었으면 sentencepiece 라이브러리를 활용해서 토크나이저를 학습해봅니다
본 예제에서는 T5 모델을 위한 CBPE(char level BPE) 토크나이저를 학습하겠습니다
BPE는 Byte-Pair Encoding의 약자로 1994년에 제안된 데이터 압축 알고리즘입니다.
자연어처리에서는 char-level(CBPE) or byte-level(BBPE)에서 시작해서 점차적으로 vocab을 만들어내는 Bottom-up 방식으로 학습하게 됩니다
자연어 처리에서는 기계번역 태스크에서 적용되기 시작했습니다
vocab size는 special_tokens (pad, bos, eos, unk, ....) 7개 + additional_special_tokens (T5를 위한 <extra_id_XX> 토큰) 100개 + subwords 토큰 31,900개로 구성되어 총 32,000 의 크기를 갖게 셋팅했습니다.
vocab size는 하이퍼파라미터로 정해진 값은 없습니다 (보통은 10,000~52,000 사이지만 모델의 크기에 따라 다름)
import sentencepiece as spm
from pathlib import Path
data_dir = './data'
paths = [str(x) for x in Path(data_dir).glob("*.txt")]
corpus = ",".join(paths)
prefix = "t5-sp-bpe-nsmc"
vocab_size = 31900-7
spm.SentencePieceTrainer.train(
f"--input={corpus} --model_prefix={prefix} --vocab_size={vocab_size + 7}" +
" --model_type=bpe" +
" --max_sentence_length=999999" + # 문장 최대 길이 (너무 길면 에러발생)
" --pad_id=0 --pad_piece=<pad>" + # pad (0)
" --unk_id=1 --unk_piece=<unk>" + # unknown (1)
" --bos_id=2 --bos_piece=<s>" + # begin of sequence (2)
" --eos_id=3 --eos_piece=</s>" + # end of sequence (3)
" --user_defined_symbols=<sep>,<cls>,<mask>") # 사용자 정의 토큰sentencepiece 학습로그
...
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29600 all=641749 active=32848 piece=틈없이
bpe_model_trainer.cc(167) LOG(INFO) Updating active symbols. max_freq=11 min_freq=5
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29620 all=641915 active=32245 piece=...0
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29640 all=641935 active=32265 piece=▁out
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29660 all=641945 active=32275 piece=▁같더라
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29680 all=641952 active=32282 piece=▁공연을
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29700 all=641961 active=32291 piece=▁기억할
bpe_model_trainer.cc(167) LOG(INFO) Updating active symbols. max_freq=11 min_freq=5
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29720 all=641995 active=32128 piece=▁나이든
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29740 all=642015 active=32148 piece=▁누나가
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29760 all=642043 active=32176 piece=▁돌려도
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29780 all=642071 active=32204 piece=▁로코물
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29800 all=642089 active=32222 piece=▁머랄까
bpe_model_trainer.cc(167) LOG(INFO) Updating active symbols. max_freq=11 min_freq=5
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29820 all=642124 active=32140 piece=▁미친짓
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29840 all=642148 active=32164 piece=▁범죄가
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29860 all=642164 active=32180 piece=▁봣지만
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29880 all=642172 active=32188 piece=▁사고의
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29900 all=642198 active=32214 piece=▁서로가
bpe_model_trainer.cc(167) LOG(INFO) Updating active symbols. max_freq=11 min_freq=5
trainer_interface.cc(685) LOG(INFO) Saving model: t5-sp-bpe-nsmc.model
trainer_interface.cc(697) LOG(INFO) Saving vocabs: t5-sp-bpe-nsmc.vocabsentencepiece의 학습이 끝나면 아래와 같이 학습된 파일이 만들어집니다
├── README.md
├── data
│ ├── test.txt
│ └── train.txt
├── t5-sp-bpe-nsmc.model
├── t5-sp-bpe-nsmc.vocab
└── train.py학습된 토크나이저 모델파일을 sentencepiece 라이브러리를 통해 다시 로딩해서 쓸 수 있지만, 사용성을 위해 huggingface의 T5Tokenizer로 랩핑해서 사용하겠습니다
save_pretrained()함수를 사용하면 최근 많이 사용되는 huggingface의 토크나이저의 포멧으로 저장되어 재사용 할 수 있습니다
huggingface 클래스를 사용할때 실제 필요한 파일은 t5-sp-bpe-nsmc.model 하나만 있으면 됩니다
from transformers import T5Tokenizer
tokenizer = T5Tokenizer(vocab_file="t5-sp-bpe-nsmc.model")
tokenizer.save_pretrained("t5-tokenizer-bpe-nsmc")huggingface에서 사용하는 토크나이저로 저장된 모습
├── README.md
├── data
│ ├── test.txt
│ └── train.txt
├── t5-sp-bpe-nsmc.model
├── t5-sp-bpe-nsmc.vocab
├── t5-tokenizer-bpe-nsmc
│ ├── special_tokens_map.json
│ ├── spiece.model
│ └── tokenizer_config.json
└── train.py이제 학습한 토크나이저를 테스트해보겠습니다
from transformers import T5Tokenizer
tokenizer = T5Tokenizer(vocab_file="t5-sp-bpe.model")
tokenizer.save_pretrained("t5-tokenizer-bpe-nsmc")
lines = [
"`DEVOCEAN`은 SK그룹의 대표 개발자 커뮤니티이자🧑",
"내/외부 개발자 간 소통과 성장을 위한 플랫폼을 상징합니다.👋",
"`Developers`' Ocean 개발자들을 위한 영감의 바다🙏",
"`Devotion` 헌신,몰두,전념💯",
"`Technology for Everyone` 모두를 위한 기술👍"
]
for line in lines:
tokens = tokenizer.tokenize(line)
inputs = tokenizer(line)
decoded_sequence = tokenizer.decode(inputs['input_ids'])
print(line) # 입력 데이터
print(tokens) # subword로 토큰화된 데이터
print(decoded_sequence) # subword토큰화된 데이터 -> token id -> 복원된데이터
print()실행 결과 입력 데이터들이 대부분 subword로 잘 분절되어 쪼개지고 다시 decoding 했을때 복원된걸 확인할 수 있습니다
하지만 위 결과에서 한가지 문제가 있습니다
학습 corpus에 없었던 🧑👋🙏💯👍와 같은 토큰들은 decoding시에
unk(Unknown Token)토큰으로 복원되게 됩니다🧑👋🙏💯👍와 같은 특수문자 또는 외국어등은 학습 corpus에 등장하는 비율이 낮아 토크나이저에서 decoding시에
unk토큰으로 복원될 가능성이 있습니다
이러한 문제를 해결하기 위해 등장한것이 BBPE (Byte-level BPE)입니다 (GPT 계열의 모델이 사용함)
subword 학습을 char level이 아닌 byte level 단위로 수행하는 것입니다
이러한 방법을 사용했을때는 사람이 육안으로 토큰들을 확인하는건 불편하지만, OOV(Out-Of-Vocabulary)를 줄일 수 있다는 장점이 있습니다
sentencepice에서는 공식적으로 BBPE를 지원하지 않지만, 비슷한 효과를 내는 " --byte_fallback=true" 옵션이 있습니다
" --byte_fallback=true" 옵션은 unk 토큰을 만났을때 해당 토큰을 utf-8 byte level로 쪼개서 처리해줍니다 (참고)
" --byte_fallback=true" 옵션을 적용해서 재학습후 평가해보겠습니다
import sentencepiece as spm
from pathlib import Path
paths = [str(x) for x in Path(data_dir).glob("*.txt")]
corpus = ",".join(paths)
prefix = "t5-sp-bpe-nsmc-byte-fallback"
vocab_size = 31900-7
spm.SentencePieceTrainer.train(
f"--input={corpus} --model_prefix={prefix} --vocab_size={vocab_size + 7}" +
" --model_type=bpe" +
" --max_sentence_length=999999" + # 문장 최대 길이 -> 이게 너무 길면 에러발생함
" --pad_id=0 --pad_piece=<pad>" + # pad (0)
" --unk_id=1 --unk_piece=<unk>" + # unknown (1)
" --bos_id=2 --bos_piece=<s>" + # begin of sequence (2)
" --eos_id=3 --eos_piece=</s>" + # end of sequence (3)
" --byte_fallback=true" + # add byte_fallback for unk tokens
" --user_defined_symbols=<sep>,<cls>,<mask>") # 사용자 정의 토큰실행 로그
bpe_model_trainer.cc(167) LOG(INFO) Updating active symbols. max_freq=11 min_freq=5
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29820 all=642124 active=32140 piece=▁미친짓
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29840 all=642148 active=32164 piece=▁범죄가
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29860 all=642164 active=32180 piece=▁봣지만
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29880 all=642172 active=32188 piece=▁사고의
bpe_model_trainer.cc(258) LOG(INFO) Added: freq=11 size=29900 all=642198 active=32214 piece=▁서로가
bpe_model_trainer.cc(167) LOG(INFO) Updating active symbols. max_freq=11 min_freq=5
trainer_interface.cc(685) LOG(INFO) Saving model: t5-sp-bpe-nsmc-byte-fallback.model
trainer_interface.cc(697) LOG(INFO) Saving vocabs: t5-sp-bpe-nsmc-byte-fallback.vocab학습한 모델 로딩 후 테스트
from transformers import T5Tokenizer
tokenizer = T5Tokenizer(vocab_file="t5-sp-bpe-nsmc-byte-fallback.model")
tokenizer.save_pretrained("t5-tokenizer-bpe-nsmc-byte-fallback")
lines = [
"`DEVOCEAN`은 SK그룹의 대표 개발자 커뮤니티이자🧑",
"내/외부 개발자 간 소통과 성장을 위한 플랫폼을 상징합니다.👋",
"`Developers`' Ocean 개발자들을 위한 영감의 바다🙏",
"`Devotion` 헌신,몰두,전념💯",
"`Technology for Everyone` 모두를 위한 기술👍"
]
for line in lines:
tokens = tokenizer.tokenize(line)
inputs = tokenizer(line)
decoded_sequence = tokenizer.decode(inputs['input_ids'])
print(line)
print(tokens)
print(decoded_sequence)
print()기존과 달리 🧑👋🙏💯👍와 같은 토큰들이 제대로 복원된 것을 확인할 수 있습니다
오늘은 자연어처리에서 가장 많이 쓰이는 라이브러리중 하나인 SentencePiece와 Huggingface의 transformers를 통해 토크나이저를 학습하고 OOV 문제를 해결하는 방법을 다뤘습니다.
가독성 있는 토큰화된 결과와 OOV 이슈를 해소하기 원한다면 SentencePiece CBPE + byte_fallback=true 옵션을 고려해보실 수 있을 것 같습니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.