参考記事をみてCommand R+とかGPT-4oの日本語性能の進化はtokenizerのvocabによるものなのかと思った。
comammd R+は255k。
gpt3.5とgpt4は"cl100k_base"に対して、gpt4oは"o200k_base"に増えている。
Llama3もtokenizerのvocabサイズ128kで、Llama2の32kから増えている。
これらの発表時期も比較的近い。
2024年04月04日…CohereForAI Command R+
2024年04月19日…Meta Llama3
2024年05月13日…OpenAI GPT-4o
変換token長さ比較:
command R+は主要10言語 (英語、フランス語、スペイン語、イタリア語、ドイツ語、ポルトガル語、日本語、韓国語、アラビア語、中国語) に優れている。とあるのでおそらく日本語のvocabは15k~20k程度であろう。
ELYZAはllama2の32kでこれに日本語のvocabを追加して45kにしているので日本語のvocabは13k程度。
vocabを横軸、羅生門の変換token長さを縦軸にすると以下になる。
日本語vocabが多いほど変換長が短くなる。
図の左下に固まるELYZA、llm-jp、JapaneseBERT、japanese-gpt-neox、japanese-stablelmは日本語のvocabを集中的に持っている。
Llama3は日本語vocabの割合が多めに見える。またはGPT4oは多言語の対応を目指し、日本語の比重は相対的に少なく見えるのかもしれない。
import tiktoken
text = """
羅生門
芥川龍之介
ある日の暮方の事である。一人の下人が、羅生門の下で雨やみを待っていた。...
下人の行方は、誰も知らない。
(大正四年九月)
"""
print("芥川龍之介 羅生門")
for encode_name in ["o200k_base", "cl100k_base", "p50k_base", "r50k_base"]:
tokenizer = tiktoken.get_encoding(encode_name)
input_ids = tokenizer.encode(text)
print(encode_name, len(input_ids))
-------------------------------------------------------------
芥川龍之介 羅生門
o200k_base 4913
cl100k_base 6531
p50k_base 8188
r50k_base 8188
huggingfaceにloginしないとダウンロード出来ない場合、オリジナルのモデルの代わりに量子化モデルなどのログインしないでも使用できるtokenizerを用いた。(tokenizerに必要なデータはtokenizer.jsonとかだけなので)
from transformers import AutoTokenizer
model_list = ["parsawar/Llama2_quantize_4bit",
"nvidia/Llama3-ChatQA-1.5-70B",
"elyza/ELYZA-japanese-Llama-2-7b-fast-instruct",
"llm-jp/llm-jp-13b-v1.0", "CohereForAI/aya-101",
"alpindale/c4ai-command-r-plus-GPTQ",
"microsoft/Phi-3-medium-128k-instruct",
"google/byT5-large",
"google/mt5-large",
"google/siglip-base-patch16-256-multilingual",
"Metin/gemma-2b-tr",
"4bit/japanese-stablelm-instruct-alpha-7b",
"tohoku-nlp/bert-base-japanese-v3",
"rinna/japanese-gpt-neox-3.6b"]
text = """
羅生門
芥川龍之介
ある日の暮方の事である。一人の下人が、羅生門の下で雨やみを待っていた。...
下人の行方は、誰も知らない。
(大正四年九月)
"""
print("芥川龍之介 羅生門")
for model_name in model_list:
tokenizer = AutoTokenizer.from_pretrained(model_name)
input_ids = tokenizer(text)['input_ids']
print(tokenizer.name_or_path, len(input_ids), tokenizer.vocab_size)
-------------------------------------------------------------
芥川龍之介 羅生門
parsawar/Llama2_quantize_4bit 7054 32000
nvidia/Llama3-ChatQA-1.5-70B 4412 128000
elyza/ELYZA-japanese-Llama-2-7b-fast-instruct 4137 45043
llm-jp/llm-jp-13b-v1.0 4310 50570
CohereForAI/aya-101 3526 250100
alpindale/c4ai-command-r-plus-GPTQ 4017 255000
microsoft/Phi-3-medium-128k-instruct 7053 32000
google/byT5-large 17285 256
google/mt5-large 3526 250100
google/siglip-base-patch16-256-multilingual 3534 250100
Metin/gemma-2b-tr 3719 256000
4bit/japanese-stablelm-instruct-alpha-7b 3309 65535
tohoku-nlp/bert-base-japanese-v3 4564 32768
rinna/japanese-gpt-neox-3.6b 3777 32000
分割結果
実際のtokenizerごとの日本語の分割結果を一部示す。
print(tokenizer.convert_ids_to_tokens(input_ids))
parsawar/Llama2_quantize_4bit 7054 32000
[... '▁', '<0x0A>', '羅', '生', '門', '<0x0A>', '<0xE8>', '<0x8A>', '<0xA5>', '川', '龍', '之', '介', '<0x0A>', '\u3000', 'あ', 'る', '日', 'の', '<0xE6>', '<0x9A>', '<0xAE>', '方', 'の', '事', 'で', 'あ', 'る', '。', '一', '人', 'の', '下', '人', 'が', '、', '羅', '生', '門', 'の', '下', 'で', '<0xE9>', '<0x9B>', '<0xA8>', 'や', 'み', 'を', '<0xE5>', '<0xBE>', '<0x85>', 'っ', 'て', 'い', 'た', '。', '<0x0A>', '\u3000', '<0xE5>', '<0xBA>', '<0x83>', 'い', '門', 'の', '下', 'に', 'は', '、', 'こ', 'の', ' 男', 'の', 'ほ', 'か', 'に', '<0xE8>', '<0xAA>', '<0xB0>', 'も', 'い', 'な', 'い', '。', 'た', 'だ', '、', '所', '々', '<0xE4>', '<0xB8>', '<0xB9>', '<0xE5>', '<0xA1>', '<0x97>', 'の', '<0xE5>', '<0x89>', '<0xA5>', 'げ', 'た', '、', '大', 'き', 'な', '<0xE5>', '<0x86>', '<0x86>', '<0xE6>', '<0x9F>', '<0xB1>', 'に', '、', '<0xE8>', '<0x9F>', '<0x8B>', '<0xE8>', '<0x9F>', '<0x80>', 'が', '一', '<0xE5>', '<0x8C>', '<0xB9>', 'と', 'ま', 'っ', 'て', 'い', 'る', '。', '羅', '生', '門', 'が', '、', '朱', '<0xE9>', '<0x9B>', '<0x80>', '大', '路', 'に', 'あ', 'る', '以', '上', 'は', '、', 'こ', 'の', '男', 'の', 'ほ', 'か', 'に', 'も', '、', '<0xE9>', '<0x9B>', '<0xA8>', 'や', 'み', 'を', 'す', 'る', '市', '女', '<0xE7>', '<0xAC>', '<0xA0>', 'や', '<0xE6>', '<0x8F>', '<0x89>', '<0xE7>', '<0x83>', '<0x8F>', '<0xE5>', '<0xB8>', '<0xBD>', '子', 'が', '、', 'も', 'う', '二', '三', '人', 'は', 'あ', 'り', 'そ', 'う', 'な', 'も', 'の', 'で', 'あ', 'る', '。', 'そ', 'れ', 'が', '、', 'こ', 'の', '男', 'の', 'ほ', 'か', 'に', 'は', '<0xE8>', '<0xAA>', '<0xB0>', 'も', 'い', 'な', 'い', '。',...]
elyza/ELYZA-japanese-Llama-2-7b-fast-instruct 4137 45043
[..., '▁', '<0x0A>', '羅', '生', '門', '<0x0A>', '芥', '川', '龍', '之', '介', '<0x0A>', '\u3000', 'ある', '日の', '暮', '方の', '事', 'である', '。', '一', '人の', '下', '人が', '、', '羅', '生', '門', 'の', '下', 'で', '雨', 'や', 'み', 'を', '待', 'っていた', '。', '<0x0A>', '\u3000', '広', 'い', '門', 'の', '下', 'には', '、', 'この', '男', 'の', 'ほ', 'かに', '誰も', 'いない', '。', 'ただ', '、', '所', '々', '丹', '塗', 'の', '剥', 'げ', 'た', '、', '大きな', '円', '柱', 'に', '、', '蟋', '蟀', 'が一', '匹', 'と', 'ま', 'っている', '。', '羅', '生', '門', 'が', '、', '朱', '雀', '大', '路', 'にある', '以上', 'は', '、', 'この', '男', 'の', 'ほか', 'にも', '、', '雨', 'や', 'み', 'をする', '市', '女', '笠', 'や', '揉', '烏', '帽', '子', 'が', '、', 'もう', '二', '三', '人は', 'あり', 'そうな', 'もので', 'ある', '。', 'それが', '、', 'こ の', '男', 'の', 'ほか', 'には', '誰も', 'いない', '。',...]
llm-jp/llm-jp-13b-v1.0 4310 50570
['▁', '\n', '羅', '生', '門', '\n', '芥川', '龍之介', '\n', '\u3000', 'ある', '日', 'の', '暮', '方', 'の', '事', 'で', 'ある', '。', '一人', 'の下', '人', 'が', '、', '羅', '生', '門', 'の下', 'で', '雨', 'や', 'み', 'を', '待', 'って', 'いた', '。', '\n', '\u3000', '広い', '門', 'の下', 'には', '、', 'この', '男', 'の', 'ほか', 'に', '誰', 'も', 'い', 'ない', '。', 'ただ', '、', '所', '々', '丹', '塗', 'の', '剥', 'げ', 'た', '、', '大きな', '円', '柱', 'に', '、', '<0xE8>', '<0x9F>', '<0x8B>', '<0xE8>', '<0x9F>', '<0x80>', 'が', '一', '匹', 'と', 'まっ', 'ている', '。', '羅', '生', '門', 'が', '、', '朱', '雀', '大路', 'に', 'ある', '以上', 'は', '、', 'この', '男', 'の', 'ほか', 'にも', '、', '雨', 'や', 'み', 'を', 'する', '市', '女', '笠', 'や', '揉', '烏', '帽子', 'が', '、', 'もう', '二', '三', '人', 'は', 'あり', 'そう', 'な', 'もの', 'で', 'ある', '。', 'それ', 'が', '、', 'この', '男', 'の', 'ほか', 'には', '誰', 'も', 'い', 'ない', '。'...]
CohereForAI/aya-101 3526 250100
['▁', '羅', '生', '門', '▁', '芥', '川', '龍', '之', '介', '▁', 'ある日', 'の', '暮', '方の', '事', 'である', '。', '一人の', '下', '人が', '、', '羅', '生', '門', 'の下', 'で', '雨', 'や', 'みを', '待', 'っていた', '。', '▁', '広い', '門', 'の下に', 'は', '、', 'この', '男', 'のほか', 'に', '誰も', 'いない', '。', 'ただ', '、', '所', '々', '丹', '塗', 'の', '剥', 'げた', '、', '大きな', '円', '柱', 'に', '、', '蟋', '蟀', 'が一', '匹', 'と', 'まっている', '。', '羅', '生', '門', 'が', '、', '朱', '雀', '大', '路', 'にある', '以上', 'は', '、', 'この', '男', 'のほか', 'にも', '、', '雨', 'や', 'み', 'をする', '市', '女', '笠', 'や', '揉', '烏', '帽子', 'が', '、', 'もう', '二', '三', '人は', 'あり', 'そうな', 'ものである', '。', 'それが', '、', 'この', '男', 'のほか', 'には', '誰も', 'いない', '。',...]
Metin/gemma-2b-tr 3719 256000
['', '\n', '羅', '生', '門', '\n', '芥', '川', '龍', '之', '介', '\n', '\u3000', 'ある', '日の', '暮', '方の', '事', 'である', '。', '一', '人の', '下', '人が', '、', '羅', '生', '門', 'の下', 'で', '雨', 'や', 'みを', '待', 'っていた', '。', '\n', '\u3000', '広い', '門', 'の下', 'には', '、', 'この', '男', 'の', 'ほ', 'かに', '誰も', 'いない', '。', ' ただ', '、', '所', '々', '丹', '塗', 'の', '剥', 'げた', '、', '大きな', '円', '柱', 'に', '、', '蟋', '蟀', 'が一', '匹', 'と', 'まっている', '。', '羅', '生', '門', 'が', '、', '朱', '雀', '大', '路', 'にある', '以上', 'は', '、', 'この', '男', 'の', 'ほか', 'にも', '、', '雨', 'や', 'み', 'をする', '市', '女', '笠', 'や', '揉', '烏', '帽子', 'が', '、', 'もう', '二', '三', '人は', 'あり', 'そうな', 'ものである', '。', 'それが', '、', 'この', '男', 'の', 'ほか', 'には', '誰も', 'いない', '。'...]
4bit/japanese-stablelm-instruct-alpha-7b 3309 65535
['<|startoftext|>', '▁', '<0x0A>', '羅', '生', '門', '<0x0A>', '芥', '川', '龍', '之介', '<0x0A>', '\u3000', 'ある', '日の', '暮', '方の', '事', 'である', '。', '一人の', '下', '人が', '、', '羅', '生', '門', 'の下で', '雨', 'や', 'みを', '待っていた', '。', '<0x0A>', '\u3000', '広い', '門', 'の下', 'には', '、', 'この', '男の', 'ほかに', '誰もいない', '。', 'ただ', '、', '所', '々', '丹', '塗', 'の', '剥', 'げた', '、', '大きな', '円', '柱', 'に', '、', '蟋', '蟀', 'が一', '匹', 'と', 'まっている', '。', '羅', '生', '門', 'が', '、', '朱', '雀', '大', '路', 'にある', '以上は', '、', 'この', '男の', 'ほか', 'にも', '、', '雨', 'や', 'み', 'をする', '市', '女', '笠', 'や', '揉', '烏', '帽', '子が', '、', 'もう', '二', '三人は', 'あり', 'そうな', 'ものである', '。', 'それが', '、', 'この', '男の', 'ほか', 'には', '誰もいない', '。'...]
tohoku-nlp/bert-base-japanese-v3 4564 32768
['[CLS]', '羅', '##生', '門', '芥川', '龍', '##之介', 'ある', '日', 'の', '暮', '##方', 'の', '事', 'で', 'ある', '。', '一人', 'の', '下', '##人', 'が', '、', '羅', '##生', '門', 'の', '下', 'で', '雨', '##や', '##み', 'を', '待っ', 'て', 'い', 'た', '。', '広い', '門', 'の', '下', 'に', 'は', '、', 'この', '男', 'の', 'ほか', 'に', '誰', 'も', 'い', 'ない', '。', 'ただ', '、', '所', '##々', '丹', '##塗', 'の', '剥', '##げ', 'た', '、', '大きな', '円', '##柱', 'に', '、', '蟋', '##蟀', 'が', '一', '匹', 'と', '##まっ', 'て', 'いる', '。', '羅', '##生', '門', 'が', '、', '朱', '##雀', '大', '##路', 'に', 'ある', '以上', 'は', '、', 'この', '男', 'の', 'ほか', 'に', 'も', '、', '雨', '##や', '##み', 'を', 'する', '市', '女', '##笠', 'や', '揉', '烏', '##帽', '##子', 'が', '、', 'もう', '二', '三', '人', 'は', 'あり', 'そう', 'な', 'もの', 'で', 'ある', '。', 'それ', 'が', '、', 'この', '男', 'の', 'ほか', 'に', 'は', '誰', 'も', 'い', 'ない', '。', ...]
rinna/japanese-gpt-neox-3.6b 3777 32000
['▁', '羅', '生', '門', '▁', '芥', '川', '龍', '之介', '▁', 'ある日', 'の', '暮', '方の', '事', 'である', '。', '一人', 'の下', '人が', '、', '羅', '生', '門', 'の下で', '雨', 'や', 'み', 'を', '待', 'っていた', '。', '▁', '広い', '門', 'の下に', 'は', '、', 'この', '男', 'のほかに', '誰も', 'いない', '。', 'ただ', '、', '所', '々', '丹', '塗', 'の', '剥', 'げ', 'た', '、', '大きな', '円', '柱', 'に', '、', '[UNK]', 'が', '一', '匹', 'と', 'まっている', '。', '羅', '生', '門', 'が', '、', '朱雀', '大路', 'にある', '以上', 'は', '、', 'この', '男', 'のほかに', 'も', '、', '雨', 'や', 'み', 'をする', '市', '女', '笠', 'や', '揉', '烏', '帽子', 'が', '、', 'もう', '二', '三人', 'は', 'あり', 'そう', 'な', 'ものである', '。', 'それが', '、', 'この', '男', 'のほかに', 'は', '誰も', 'いない', '。'...]
s_list = []
for input_id in input_ids:
s_list.append(tokenizer.decode([input_id]))
print(s_list)
nvidia/Llama3-ChatQA-1.5-70B 4412 128000
['\n', '羅', '生', '門', '\n', '�', '�', '川', '龍', '之', '介', '\n', '\u3000', 'ある', '日の', '暮', '方', 'の', '事', 'である', '。一', '人の', '下', '人が', '、', ' 羅', '生', '門', 'の', '下', 'で', '雨', 'や', 'み', 'を', '待', 'っていた', '。\n', '\u3000', '広', 'い', '門', 'の', '下', 'には', '、この', '男', 'の', 'ほ', 'かに', ' 誰', 'も', 'い', 'ない', '。', 'ただ', '、', '所', '々', '丹', '�', '�', 'の', '�', '�', 'げ', 'た', '、大', 'きな', '円', '柱', 'に', '、', '�', '�', '�', '�', ' が', '一', '匹', 'と', 'ま', 'っている', '。', '羅', '生', '門', 'が', '、', '朱', '�', '�', '大', '路', 'にある', '以上', 'は', '、この', '男', 'の', 'ほ', 'か', 'にも', '、', '雨', 'や', 'み', 'をする', '市', '女', '�', '�', 'や', '�', '�', '�', '�', '帽', '子', 'が', '、', 'もう', '二', '三', '人は', 'あり', 'そうな', 'もの', 'である', '。それ', 'が', '、この', '男', 'の', 'ほ', 'か', 'には', '誰', 'も', 'い', 'ない', '。\n', ...]
alpindale/c4ai-command-r-plus-GPTQ 4017 255000
['', '\n', '羅', '生', '門', '\n', '芥', '川', '龍', '之介', '\n', '\u3000', 'ある', '日の', '暮', '方の', '事', 'である', '。', '一', '人の', '下', '人が', '、', '羅', '生', '門', 'の下で', '雨', 'や', 'みを', '待', 'っていた', '。', '\n', '\u3000', '広い', '門', 'の下', 'には', '、', 'この', '男', 'のほか', 'に', '誰', 'も', ' いない', '。', 'ただ', '、', '所', '々', '丹', '塗', 'の', '剥', 'げた', '、', '大きな', '円', '柱', 'に', '、', '�', '�', '�', '�', 'が', '一', '匹', 'と', 'ま', 'っ ている', '。', '羅', '生', '門', 'が', '、', '朱', '雀', '大', '路', 'にある', '以上', 'は', '、', 'この', '男', 'のほか', 'にも', '、', '雨', 'や', 'み', 'をする', '市', '女', '笠', 'や', '�', '�', '烏', '帽', '子が', '、', 'もう', '二', '三', '人は', 'あり', 'そう', 'な', 'ものである', '。', 'それが', '、', 'この', '男', 'のほか', 'には', '誰', 'も', 'いない', '。',...]
s_list = []
s0 = "".encode('utf-8')
for s in tokenizer.decode_tokens_bytes(input_ids):
try:
s_list.append(s.decode('utf-8'))
except:
s2 = s0 + s
try:
s_list.append(s2.decode('utf-8') + '(2)')
except:
pass
s0 = s
print(s_list)
cl100k_base 6531
['\n', '羅(2)', '生', '門(2)', '\n', '芥(2)', '川(2)', '龍(2)', '之', '介', '\n', '\u3000', 'あ', 'る', '日', 'の', '暮(2)', '方', 'の', '事', 'で', 'あ', 'る', '。', '一', '人', 'の', '下', '人', 'が', '、', '羅(2)', '生', '門(2)', 'の', '下', 'で', '雨(2)', 'や', 'み', 'を', '待', 'って', 'い', 'た', '。\n', '\u3000', '広(2)', 'い', '門(2)', 'の', '下', 'に', 'は', '、', 'この', '男', 'の', 'ほ(2)', 'か', 'に', '誰(2)', 'も', 'い', 'な', 'い', '。', 'ただ', '、', '所', '々(2)', '丹(2)', '塗(2)', 'の', '剥(2)', 'げ(2)', 'た', '、', '大', 'き', 'な', '円', '柱(2)', 'に', '、', 'が', '一', '匹(2)', 'と', 'ま', 'って', 'い', 'る', '。', '羅(2)', '生', '門(2)', 'が', '、', '朱(2)', '雀(2)', '大', '路', 'に', 'あ', 'る', '以上', 'は', '、', 'この', '男', 'の', 'ほ(2)', 'か', 'に', 'も', '、', '雨(2)', 'や', 'み', 'を', 'する', '市', '女', '笠(2)', 'や', '揉(2)', '帽(2)', '子', 'が', '、', 'も', 'う', '二', '三', '人', 'は', 'あり', 'そ', 'う', 'な', 'も', 'の', 'で', 'あ', 'る', '。', 'それ', 'が', '、', 'この', ' 男', 'の', 'ほ(2)', 'か', 'に', 'は', '誰(2)', 'も', 'い', 'な', 'い', '。\n',...]
o200k_base 4913
['\n', '羅', '生', '門', '\n', '芥(2)', '川', '龍', '之', '介', '\n', '\u3000', 'ある', '日の', '暮', '方', 'の', '事', 'で', 'ある', '。一', '人', 'の', '下', '人', 'が', '、', '羅', '生', '門', 'の', '下', 'で', '雨', 'や', 'み', 'を', '待', 'って', 'いた', '。\n', '\u3000', '広', 'い', '門', 'の', '下', 'には', '、この', '男', 'の', 'ほ', 'か', 'に', '誰', 'も', 'い', 'ない', '。ただ', '、', '所', '々', '丹', '塗(2)', 'の', '剥(2)', 'げ', 'た', '、大', 'き', 'な', '円', '柱', 'に', '、', '蟋(2)', '蟀(2)', 'が', '一', '匹', 'と', 'ま', 'って', 'いる', '。', '羅', '生', '門', 'が', '、', '朱', '雀', '大', '路', 'に', 'ある', '以上', 'は', '、この', '男', 'の', 'ほ', 'か', ' にも', '、', '雨', 'や', 'み', 'を', 'する', '市', '女', '笠(2)', 'や', '揉', '烏(2)', '帽', '子', 'が', '、', 'もう', '二', '三', '人', 'は', 'あり', 'そう', 'な', 'も', 'ので', 'ある', '。それ', 'が', '、この', '男', 'の', 'ほ', 'か', 'には', '誰', 'も', 'い', 'ない', '。\n',...]
英語ならtokenizerで単語単位で区切れるが、日本語は日本語tokenizerでも完全には単語ごとには区切れない。もっともSentencePieceによれば単語区切りよりもBPE、正確にはUnigramの対数尤度を元にした分割手法の方が良いらしい。
まとめ
Llama3、GPT4o、Command R+などの日本語のtoken変換効率を調べた。
変換token長だけならjapanese-stablelmが最短だった。次点でmT5。
テキスト圧縮
羅生門テキストの日本語のバイト型テキストの理論圧縮比率を確認する。
1byte(256通りデータ)の情報データで何バイトまで圧縮可能か。
2byte(65,536通りデータ)ならば更に1/2まで圧縮可能。
2.25byte(256k通りデータ)ならば更に8/18まで圧縮可能。
これはvocabが66kでtokenの出現頻度が等しいとしても2500token長が限度という事である。
vocabが256kでtokenの出現頻度が等しいとしても2200token長が限度。
japanese-stablelmがvocabが66kでtoken長が3309なのである程度限度に近い。
むしろ、既にgzipと圧縮比率がほぼ等しい。
一方、BPEはマルチバイト型テキストの辞書型圧縮の一種とみなせる。
import zpaq
import bz2
import gzip
import lzma
print('text_char_length=', len(text))
text = text.encode("utf-8")
print('text_byte_length=', len(text))
text_compress_zpaq = zpaq.compress(text)
text_compress_bz2 = bz2.compress(text)
text_compress_gzip = gzip.compress(text)
text_compress_lzma = lzma.compress(text)
print('text_compress_zpaq=', len(text_compress_zpaq))
print('text_compress_bz2=', len(text_compress_bz2))
print('text_compress_gzip=', len(text_compress_gzip))
print('text_compress_lzma=', len(text_compress_lzma))
------------------------------------------------------------------
text_char_length= 5820
text_byte_length= 17284
text_compress_zpaq= 5041
text_compress_bz2= 5388
text_compress_gzip= 6351
text_compress_lzma= 6048
vocab中に平仮名2文字を含む数
「ああ」~「んん」の内、tokenizerで平仮名2文字を1tokenに変換できる数。
各モデルによって大きく違う。日本語の場合、マイナーな漢字を収録するのにvocabを使うより、ひらがなの組み合わせにvocabを使う方がtoken長は変わるかもしれない。
import tiktoken
from transformers import AutoTokenizer
japanese_list = ["あ","い","う","え","お",
"か","き","く","け","こ",
"さ","し","す","せ","そ",
"た","ち","つ","て","と",
"な","に","ぬ","ね","の",
"は","ひ","ふ","へ","ほ",
"ま","み","む","め","も",
"や","ゆ","よ",
"ら","り","る","れ","ろ",
"わ","を","ん"]
for encode_name in ["o200k_base", "cl100k_base", "p50k_base", "r50k_base"]:
tokenizer = tiktoken.get_encoding(encode_name)
count = 0
for t1 in japanese_list:
for t2 in japanese_list:
text = t1 + t2
input_ids = tokenizer.encode(text)
if len(input_ids)==1:
count += 1
print(encode_name, count)
model_list = ["parsawar/Llama2_quantize_4bit",
"nvidia/Llama3-ChatQA-1.5-70B",
"elyza/ELYZA-japanese-Llama-2-7b-fast-instruct",
"llm-jp/llm-jp-13b-v1.0", "CohereForAI/aya-101",
"alpindale/c4ai-command-r-plus-GPTQ",
"microsoft/Phi-3-medium-128k-instruct",
"google/byT5-large",
"google/mt5-large",
"Metin/gemma-2b-tr",
"4bit/japanese-stablelm-instruct-alpha-7b",
"google/siglip-base-patch16-256-multilingual",
"tohoku-nlp/bert-base-japanese-v3",
"rinna/japanese-gpt-neox-3.6b"]
for model_name in model_list:
tokenizer = AutoTokenizer.from_pretrained(model_name)
notext_ids = tokenizer("ポ")['input_ids']
count = 0
for t1 in japanese_list:
for t2 in japanese_list:
text = "ポ" + t1 + t2
input_ids = tokenizer(text)['input_ids']
if len(input_ids)-len(notext_ids)==1:
count += 1
print(model_name, count)
------------------------------------------------------
o200k_base 92
cl100k_base 19
p50k_base 0
r50k_base 0
parsawar/Llama2_quantize_4bit 0
nvidia/Llama3-ChatQA-1.5-70B 166
elyza/ELYZA-japanese-Llama-2-7b-fast-instruct 221
llm-jp/llm-jp-13b-v1.0 435
CohereForAI/aya-101 455
alpindale/c4ai-command-r-plus-GPTQ 310
microsoft/Phi-3-medium-128k-instruct 0
google/byT5-large 0
google/mt5-large 455
Metin/gemma-2b-tr 460
4bit/japanese-stablelm-instruct-alpha-7b 696
google/siglip-base-patch16-256-multilingual 455
tohoku-nlp/bert-base-japanese-v3 298
rinna/japanese-gpt-neox-3.6b 399
考察
変換token長が短い方が意味を理解するのは容易になる筈である。
一方で、vocabサイズを増やせば性能が上がるという単純な話でもないらしい。
例えば同じ分類問題でもCIFAR-10よりCIFAR-100やImageNet1000の精度が低いように、分類問題においては分類クラス数が大きくなるほどその問題は難しくなる。どうもvocabサイズが増えるとこれと同じことが起こるらしい。
複数の言語を介する多言語モデルと、複数のテキスト、画像、動画、音声を介するマルチモーダルモデルとは何か関連するのだろうか?仮にマルチモーダルモデルの入力タイプ(テキスト、画像、音声)ごとにencoderが分かれているなら、多言語モデルも入力、出力言語ごとに異なるencoder、decoderを使い分ければ他言語vocabを保持しなくていい分、日本語vocabサイズはもっと増やせるのではと思った。