Sentence Piece

Installation

$ pip install --upgrade sentencepiece datasets transformers

Data

dataset = load_dataset("nsmc")

data_path = Path('/tmp/sentencepiece-data')
os.makedirs(data_path, exist_ok=True)

for key in dataset.keys():
    file_path = data_path / f'{key}.txt'
    print(file_path)
    with open(file_path, 'wt') as f:
        for line in dataset[key]['document']:
            f.write(f'{line}\n')

아래와 같이 파일이 생성됩니다.

아 더빙.. 진짜 짜증나네요 목소리
흠...포스터보고 초딩영화줄....오버연기조차 가볍지 않구나
너무재밓었다그래서보는것을추천한다
교도소 이야기구먼 ..솔직히 재미는 없다..평점 조정
사이몬페그의 익살스런 연기가 돋보였던 영화!스파이더맨에서 늙어보이기만 했던 커스틴 던스트가 너무나도 이뻐보였다
막 걸음마 뗀 3세부터 초등학교 1학년생인 8살용영화.ㅋㅋㅋ...별반개도 아까움.
원작의 긴장감을 제대로 살려내지못했다.

Train

Option Description
–input , (comma) 로 구분되는 파일 위치를 넣으면 됩니다. (ex. “A.txt,B.txt,C.txt”).
또한 SentencePiece는 NFKC 로 normalize 하기 때문에 따로 tokenizer, normalizer 또는 preprocessor 를 돌릴필요가 없습니다.
–model_prefix model name prefix
–vocab_size e.g. 8000, 16000 or 32000
–character_coverage default is 0.9995
–model_type unigram (default), bpe, char, word (word 사용시 pretokenized 되 있어야 합니다.)
–num_threads unigram 에서만 작동합니다.
–byte_fallback=True 이거 키면 모르는 단어 보면 byte token 으로 변경합니다.

Jupyter Notebook 에서 실행시키면, logging이 보이지 않습니다.
따라서 python 파일 따로 만들어서 실행시키는 것을 추천합니다. (notebook 에서는 심지어 멈추기 까지 합니다.)

학습시간은 대충 1분도 안걸립니다.

import sentencepiece as spm

corpus = ",".join([str(x) for x in data_path.glob("*.txt")])
vocab_size = 31900
model_prefix = "sp-bpt-nsmc"

spm.SentencePieceTrainer.train(
    input=corpus,
    model_prefix=model_prefix,
    vocab_size=vocab_size,
    model_type="bpe",
    max_sentence_length=500000,
    character_coverage=1.0,
    pad_id=0,
    unk_id=1,
    bos_id=2,
    eos_id=3,
    pad_piece="<pad>",
    unk_piece="<unk>",
    bos_piece="<s>",
    eos_piece="</s>",
    user_defined_symbols="<sep>,<cls>,<mask>",
    byte_fallback=True,
    num_threads=16
)

또는 다음과 같이 실행합니다.

spm.SentencePieceTrainer.train(
    f"--input={corpus} "
    f"--model_prefix={model_prefix} "
    f"--vocab_size={vocab_size} "
    f"--model_type=bpe "
    f"--max_sentence_length=500000 "
    " --character_coverage=1.0 "
    " --pad_id=0 --pad_piece=<pad> "
    " --unk_id=1 --unk_piece=<unk> "  # Unknown
    " --bos_id=2 --bos_piece=<s> "  # begin of sequence
    " --eos_id=3 --eos_piece=</s> "  # end of sequence
    " --user_defined_symbols=<sep>,<cls>,<mask> "
    " --byte_fallback=true "
    " --num_threads=16 "
)

Encoding & Decoding

학습이 모두 끝났으면 불러와서 사용하면 됩니다.
아래처럼 모델을 불러오면 됩니다.

import sentencepiece as spm

sp = spm.SentencePieceProcessor()
sp.load('sp-bpt-nsmc.model')
example_sentence = '홍콩반점 짬뽕은 맛있다 하지만 오끽궁보다 못하다'

# Encoding
sp.encode(example_sentence)
sp.encode_as_ids(example_sentence)
# [1786, 28852, 28749, 7137, 28719, 1278, 646, 515, 77, 30267, 29258, 114, 4830]

# EOS & BOS 넣기
# bos: 문장의 시작 토큰 (bos=2)
# eos: 문장의 끝 토근 (eos=3)
sp.encode(example_sentence, add_bos=True, add_eos=True)
# [2, 1786, 28852, 28749, 7137, 28719, 1278, 646, 515, 77, 30267, 29258, 114, 4830, 3]

# Tokenizing
sp.encode_as_pieces(example_sentence)
# ['▁홍콩', '반', '점', '▁짬뽕', '은', '▁맛', '있다', '▁하지만', '▁오', '끽', '꾝', '보다는', '▁못하다']


# Decoding
encoded = [1786, 28852, 28749, 7137, 28719, 1278, 646, 515, 77, 30267, 29258, 114, 4830]
sp.decode(encoded)
sp.decode_ids(encoded)
# '홍콩반점 짬뽕은 맛있다 하지만 오끽궁보다 못하다'


Token Information

# Vocab Size
vocab_size = sp.get_piece_size()  # 31900

# List of Tokens and ID
for i in range(sp.get_piece_size()):
    print(f'{i:05}: {sp.id_to_piece(i)}')

    if i > 100:
        break

# 00000: <pad>
# 00001: <unk>
# 00002: <s>
# 00003: </s>
# 00004: <sep>
# 00005: <cls>
# 00006: <mask>
# 00007: ..
# 00008: 영화
# 00009: ▁영화
# 00010: ▁이
# 00011: ▁아