日本語tokenizerを学習する
tokenizerを学習させてhuggingface hubにuploadするまでを行う
作成したtokenizerはここ
tokenizerはsentencepieceのunigramを使う
学習にはhuggingfaceのtokenizer ライブラリを使う
sentencepieceのunigramを、transformerのtokenizerに変換する方法がなさそうだったので、
PreTrainedTokenizerを継承してhuggingface hubから使えるようにした。
スマートなやり方あれば教えてください。
学習
tokenizerを学習させるモチベーション
- byte levelだと漢字が分割されるが、漢字1文字で1IDのほうが良さそう
- 語彙数はそこそこのサイズがあったほうが良さそう
https://www.anlp.jp/proceedings/annual_meeting/2022/pdf_dir/A1-2.pdf
データセットはwikinewsのja/en、青空文庫として以下を使わせて頂く
学習はreadmeをそのまま
tokenizerをロードして、train_from_iteratorで学習する。
vocab_sizeは35000、後々更に大きいサイズを試してみたい
from tokenizers import Tokenizer, models, normalizers, pre_tokenizers, trainers
tokenizer = Tokenizer(models.Unigram())
tokenizer.normalizer = normalizers.NFKC()
tokenizer.pre_tokenizer = pre_tokenizers.UnicodeScripts()
tokenizer.decoder = decoders.ByteLevel()
trainer = trainers.UnigramTrainer(
vocab_size=35000,
show_progress=True,
special_tokens=["<PAD>", "<BOS>", "<EOS>", "<UNK>", "<MASK>"],
unk_token="<UNK>"
)
def ds_yielder():
// datasetのload
tokenizer.train_from_iterator(ds_yielder(), trainer=trainer)
tokenizer.save('./tokenizer.json')
学習コードのサンプルは以下
novelAIのtokenizerとの比較
token count: 20
['それは', '九月', '初', '旬', 'のある', '蒸', 'し', '暑い', '晩', 'のことであった', '。', '私は', '、', 'D', '坂の', '大', '通り', 'の中', '程', 'にある']
novelAI
token count: 22
['それは', '九', '月', '初', '旬', 'のある', '蒸', 'し', '暑い', '晩', 'のこと', 'であった', '。', '私は', '、', 'D', '坂', 'の大', '通り', 'の中', '程', 'にある']
学習したもの
token count: 11
['九月', '、', '十月', '、', '十一月', 'は', '月', 'が', '綺麗', 'だ', '。']
novelAI
token count: 12
['九', '月', '、', '十', '月', '、', '十一', '月は', '月が', '綺麗', 'だ', '。']
['九月']と['九', '月']のように月が1つのトークンとして分割できている。
upload
AutoTokenizer.from_pretrainedから呼び出すことを想定
最初にhuggingfaceのUIからtokenizer用のrepositoryを作成しておく。
upload方法は色々あるが、ファイルサイズが小さいので今回はgitclientを使う
huggingface.tokenizer
をtransfomer.AutoTokenizer.from_pretrained
から呼び出せるように、PreTrainedTokenizer
を継承したclassとしてsentencepiece_ja.py
を作成する。
あとは、get_vocab
、vocab_size
、_tokenize
、_convert_token_to_id
、_convert_id_to_token
のあたりを実装しておく。
class SentencePieceJA(PreTrainedTokenizer):
def __init__(self, model_path = "./tokenizer.json", **kwargs):
super().__init__(**kwargs)
from tokenizers import Tokenizer
self._tokenizer = Tokenizer.from_file(model_path)
以下の用にsave_vocabulary
を実装しておくと、tokenizer.save_pretrained('./sample')
でuploadに必要なファイルが出力できる。
def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None) -> Tuple[str]:
index = 0
if os.path.isdir(save_directory):
vocab_file = os.path.join(
save_directory, (filename_prefix + "-" if filename_prefix else "") + 'vocab.txt'
)
else:
vocab_file = (filename_prefix + "-" if filename_prefix else "") + save_directory
with open(vocab_file, "w", encoding="utf-8") as writer:
for token, token_index in sorted(self.get_vocab().items(), key=lambda kv: kv[1]):
if index != token_index:
index = token_index
writer.write(token + "\n")
index += 1
return (vocab_file,)
tokenizer_config.jsonに以下を追加することで、tokenizerのload時にsentencepiece_ja.py
を使うようになる。
"auto_map": {
"AutoTokenizer": ["","sentencepiece_ja.SentencePieceJA"]
}
auto_mapあたりの挙動はこの辺を確認
最終的にuploadするファイルは以下となる
- tokenizer.json
- special_tokens_map.json
- tokenizer_config.json
- vocab.txt
- sentencepiece_ja.py
これらをgit push
Discussion
tokenizer.json
が出来上がっているのなら、PreTrainedTokenizer
から組み上げるよりもDebertaV2TokenizerFast
あたりを借りた方が、auto_mapも不要で楽だと思うのです。よければお試しあれ。