昨年12月27日に京都大学言語メディア研究室から、日本語GPT-2モデルgpt2-large-japanese-charが発表された。モデル名の末尾がcharなので、たぶん単文字トークナイザを使っているようだ。ちょっと試してみよう。
>>> from transformers import AutoTokenizer
>>> tkz=AutoTokenizer.from_pretrained("ku-nlp/gpt2-large-japanese-char")
>>> print(tkz.convert_ids_to_tokens(tkz("国境の長いトンネルを抜けると雪国であった。","夜の底が白くなった。")["input_ids"]))
['åĽ½', 'å¢ĥ', 'ãģ®', 'éķ·', 'ãģĦ', 'ãĥĪ', 'ãĥ³', 'ãĥį', 'ãĥ«', 'ãĤĴ', 'æĬľ', 'ãģij', 'ãĤĭ', 'ãģ¨', 'éĽª', 'åĽ½', 'ãģ§', 'ãģĤ', 'ãģ£', 'ãģŁ', 'ãĢĤ', 'å¤ľ', 'ãģ®', 'åºķ', 'ãģĮ', 'çĻ½', 'ãģı', 'ãģª', 'ãģ£', 'ãģŁ', 'ãĢĤ']
う、「国」が「åĽ½」ということは、もしや
>>> text="国境の長いトンネルを抜けると雪国であった。夜の底が白くなった。"
>>> x=tkz(text,return_offsets_mapping=True)
>>> for i,(s,e) in zip(x["input_ids"],x["offset_mapping"]):
... print(text[s:e],text[s:e].encode("utf-8"),ascii(tkz.convert_ids_to_tokens(i)))
...
国 b'\xe5\x9b\xbd' '\xe5\u013d\xbd'
境 b'\xe5\xa2\x83' '\xe5\xa2\u0125'
の b'\xe3\x81\xae' '\xe3\u0123\xae'
長 b'\xe9\x95\xb7' '\xe9\u0137\xb7'
い b'\xe3\x81\x84' '\xe3\u0123\u0126'
ト b'\xe3\x83\x88' '\xe3\u0125\u012a'
ン b'\xe3\x83\xb3' '\xe3\u0125\xb3'
ネ b'\xe3\x83\x8d' '\xe3\u0125\u012f'
ル b'\xe3\x83\xab' '\xe3\u0125\xab'
を b'\xe3\x82\x92' '\xe3\u0124\u0134'
抜 b'\xe6\x8a\x9c' '\xe6\u012c\u013e'
け b'\xe3\x81\x91' '\xe3\u0123\u0133'
る b'\xe3\x82\x8b' '\xe3\u0124\u012d'
と b'\xe3\x81\xa8' '\xe3\u0123\xa8'
雪 b'\xe9\x9b\xaa' '\xe9\u013d\xaa'
国 b'\xe5\x9b\xbd' '\xe5\u013d\xbd'
で b'\xe3\x81\xa7' '\xe3\u0123\xa7'
あ b'\xe3\x81\x82' '\xe3\u0123\u0124'
っ b'\xe3\x81\xa3' '\xe3\u0123\xa3'
た b'\xe3\x81\x9f' '\xe3\u0123\u0141'
。 b'\xe3\x80\x82' '\xe3\u0122\u0124'
夜 b'\xe5\xa4\x9c' '\xe5\xa4\u013e'
の b'\xe3\x81\xae' '\xe3\u0123\xae'
底 b'\xe5\xba\x95' '\xe5\xba\u0137'
が b'\xe3\x81\x8c' '\xe3\u0123\u012e'
白 b'\xe7\x99\xbd' '\xe7\u013b\xbd'
く b'\xe3\x81\x8f' '\xe3\u0123\u0131'
な b'\xe3\x81\xaa' '\xe3\u0123\xaa'
っ b'\xe3\x81\xa3' '\xe3\u0123\xa3'
た b'\xe3\x81\x9f' '\xe3\u0123\u0141'
。 b'\xe3\x80\x82' '\xe3\u0122\u0124'
ううう、UTF-8のByte-Pair Encodingがナマで使われてる(ただし0x00~0x20・0x7f~0x9fはU+0100~U+0120・U+0121~U+0141へマッピング)。decode
は実装されてるかしら。
>>> print([tkz.decode(t) for t in x["input_ids"]])
['国', '境', 'の', '長', 'い', 'ト', 'ン', 'ネ', 'ル', 'を', '抜', 'け', 'る', 'と', '雪', '国', 'で', 'あ', 'っ', 'た', '。', '夜', 'の', '底', 'が', '白', 'く', 'な', 'っ', 'た', '。']
decode
が準備されてるので、とりあえず何とかなりそうだ。ただ、常用漢字表2136字は、どの程度サポートしているのだろう。
>>> import urllib.request
>>> with urllib.request.urlopen("https://www.unicode.org/wg2/iso10646/edition6/data/JapaneseCoreKanji.txt") as r:
... k=[chr(int(t,16)) for t in r.read().decode().strip().split("\n") if not t.startswith("#")]
...
>>> for i,j in zip(k,tkz(k)["input_ids"]):
... if len(j)>1:
... print(i,tkz.convert_ids_to_tokens(j))
...
剝 ['åī', 'Ŀ']
塡 ['å¡', '¡']
頰 ['éł', '°']
𠮟 ['ð', 'ł', '®', 'Ł']
「剝」「塡」「頰」「𠮟」の4字がサポートされておらず、いずれも複数のトークンに泣き別れとなっている。うーん、「剝」「塡」「頰」は2トークンなので、merges
を追加すれば何とかなりそうだけど、「𠮟」を救うのは無理かなあ。それともReplace("𠮟","叱")
をnormalizer
に入れるべきかなあ。