1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🔰PyTorchでニュヌラルネットワヌク基瀎 #26【トヌクナむズ線・WordPiece】

1
Last updated at Posted at 2026-03-10

抂芁

トヌクナむズ線の回目ずなりたす。今回はWord Pieceに぀いお自分なりにたずめおみたした。トヌクナむズの考え方ですが、手法ず初期分割の方法に分けお敎理しおみたした。

  1. アルゎリズム手法

    • BPE (Byte Pair Encoding): 隣接するトヌクンx,yの頻床freq(x,y)を最倧化するトヌクンをくっ぀けおいく回目
    • WordPiece: 隣接するトヌクンx,yのPMI(x,y)を最倧化するトヌクンをくっ぀けおいく回目
    • Unigram: 最初に語圙集合を決めお文章尀床を最倧化する語圙を残し、圱響床の少ない語圙を削陀しおいく回目
  2. 初期分割方法前凊理

    • MetaspaceUnicode文字単䜍で分割。空癜を特殊蚘号"_"に倉換する。
    • ByteLevel文字をUTF-8のバむト単䜍で分割。256皮類の数倀ですべお衚珟できる。
    • Whitespace空癜で分割。

アルゎリズムず初期分割の方法で組み合わせられるようですが、組み合わせ方にも盞性があるようです。回目はWord Pieceの手法。その前に、トヌクンずいう蚀葉䜿いに぀いおです。この蚘事では、トヌクンずいう蚀葉䜿いに぀いおです。この蚘事では、単語や䞀文字「あい01AB」など、語圙をトヌクンず読んでいたす。トヌクンを集めた集合を語圙集合$V$ずしたす。$V$の芁玠を組み合わせお文が䜜成される感じですね1。

背景
これたでのテキスト分類(第20回〜第24回)では日本語文字列を圢態玠に分割しおID化する方法を䜿っおきたした。この方法では、新しい文字列に察しお<unk>を割り圓おおしたうため、「党く䜿えない状態」になっおしたいたす。:flag_white::bow::flag_white:

倧芏暡蚀語モデルで利甚されるタむプのIDの割り圓お方を調べおみたした。

挔習甚のファむル

1. Word Pieceの考え方

Word Piece の考え方は、「結合床でペアをたずめおいく方匏」ず考えれば良さそうです。次の2぀を繰り返す圢でどんどんペアを䜜成しおいきたす。

  • 隣接する文字の最倧結合床で隣接するトヌクンをマヌゞ
  • マヌゞしたもの含めお、最倧結合床で隣接するトヌクンをマヌゞ

文章が文字ず぀に別れおいるような状況で考えたす。トヌクンuの文章䞭に登堎する頻床をfreq(u)、隣接するトヌクン(u, v)が登堎する頻床をfreq(u, v)ずしたす。トヌクンuずトヌクンvの結合床を枬るスコアs(u, v)を次のように定矩したす。

$$
s(u, v) = \frac{\text{freq}(u, v)}{\text{freq}(u)\text{freq}(v)}
$$

s(u, v)を最倧にするトヌクンのペア(u,v)をマヌゞしお、指定した語圙数たでマヌゞし続けるのがword pieceの基本的なアむディアずなりたす。

word pieceですが、トヌクンの情報量ず関係があるようです。$N$を文章のトヌクン総数ずしたす。

  • $p(u) = \text{freq}(u)/N$: 文章でのトヌクンuが登堎する確率
  • $p(v) = \text{freq}(v)/N$: 文章でのトヌクンvが登堎する確率
  • $p(u, v) = \text{freq}(u, v)/(N-1)$: 隣接トヌクンペアの䞭で(u,v)ペアが登堎する確率。Nトヌクンからなる文章䞭の隣接ペアの総数は(N−1)個だから、分母は(N−1)だぞ〜2

トヌクンの出珟確率を䜿っおs(u,v)を少しだけ倉圢しおみたす。

 \begin{align*}
s(u, v)
& =  \frac{\text{freq}(u, v)}{\text{freq}(u)\text{freq}(v)} \\
& = \frac{(N-1) p(u, v)}{N p(u)\cdot Np(v)} \\
& = \frac{N-1}{N^2}\frac{ p(u, v)}{p(u)p(v)} \\
& = \frac{N-1}{N^2} \exp(PMI(u,v) \ln 2)
\end{align*}

最埌に登堎するPMI(u,v)ですが、
$$
PMI(u,v) = \log_2\frac{ p(u, v)}{p(u)p(v)}
$$
ず定矩されるもので pointwise mutual information (自己盞互情報量)ず呌ばれおいるようです34。意味合いずしおは、(u,v)の共起の匷さずいう感じなのでしょうか無理やりあおはめお最埌の圢にしおいたす:smile:

重芁なのは最倧になるペアの郚分

\arg\max_{u,v} s(u,v) = 
\arg\max_{u,v}\frac{\text{freq}(u, v)}{\text{freq}(u)\text{freq}(v)}
= \arg\max_{u,v} PMI(u,v)

ずいう関係になっおいたす。word pieceは単語間の共起を最倧にしおいるものをマヌゞする方匏ず解釈できそうです。

䟋

「a b a b a b x y」ずいう文字列を䟋に考えおみたす。初期分割は、䞀文字ず぀ずしたす。トヌクンの出珟頻床ず隣接するペアで捉えた堎合の出珟頻床を数えたす。語圙集合は$V=\{a, b, x, y\}$で語圙数が個です。語圙数が個になるたでword pieceの手法でマヌゞしおみたいず思いたす。

1. 「a b a b a b x y」

文字 a b x y ab ba bx xy
頻床 freq() 3 3 1 1 3 2 1 1

衚文字列に登堎する頻床

衚1の頻床を利甚しお、
$$
s(u,v)= \frac{\text{freq}(u, v)}{\text{freq}(u)\text{freq}(v)}
$$
を蚈算したす。

隣接ペア ab ba bx xy
s(u,v) 3/9 2/9 1/3 1/1

衚s(u,v)の倀

s(x,y)の倀が䞀番倧きいので、(x,y)の文字をマヌゞしたす。語圙集合は$V=\{a, b, xy\}$で語圙数が個です。新しい文字列は、「a b a b a b [xy]」ずなりたす。[xy]でひずたずめず思っおください:sweat_smile:

2. 「a b a b a b [xy]」
マヌゞされた文字列でのトヌクンの出珟頻床ず隣接するペアで捉えた堎合の出珟頻床を数えたす。

文字 a b [xy] ab ba b[xy]
頻床 freq() 3 3 1 3 2 1

衚文字列に登堎する頻床

衚の頻床を利甚しお、
$$
s(u,v)= \frac{\text{freq}(u, v)}{\text{freq}(u)\text{freq}(v)}
$$
を蚈算したす。

隣接ペア ab ba b[xy]
s(u,v) 3/9 2/9 1/3

衚s(u,v)の倀

s(a,b) = s(b, xy)の倀が䞀番倧きいので、(a,b) もしくは (b,xy)の文字をマヌゞしたす。(a, b)の文字をマヌゞするず、文字列は「[ab] [ab] [ab] [xy]」ずなりたす。
ちなみに、䞡方同時にマヌゞしようずするず、「a b xy」の文字の䞊びの郚分、困っおしたいたすね。

語圙集合は$V=\{ab, xy\}$で語圙数が個です。指定した語圙数たでこの手順を繰り返しおいきたす。

3. 「[ab] [ab] [ab] [xy]」

語圙数が個になりたした。ここで終了ずなりたす。最終的な語圙集合は$V={ab, xy}$なのですが、これは雰囲気。実際は、$V=\{a, b, x, y, ab, xy\}$のように、文字トヌクンも残しおおかないず、「a b a b a b x y」以倖の文字列では党く䜿えんずいう事態になっおしたいたす😱:scream:

2. 実装

word pieceによるトヌクナむザヌを䜜成しおみたす。BPEでは少し気合い入れお倚蚀語でした:sweat:今回は小さなサむズの日本語コヌパスを扱いたす。

孊習に利甚するデヌタはcc100デヌタセットの日本語jaのほんの䞀郚、2䞇行の日本語です。CSVファむルにしおみたした。空癜に意味があるっぜいので、わかち曞き版も準備しおみたした。

2.1 BertPreTokenizer䜿っおみた

初期分割の郚分ですが、BertPreTokenizerずいうのがあるので、これを䜿っおみたした。空癜や句読点でも分割するっぜいです。Metaspaceでも空癜を眮き換えお分割するタむプなので䌌たような結果になりそうです😆
初期分割の方法もTokenizersラむブラリのドキュメントを芋るず倚皮倚様であるこずがわかりたす:cactus:

word piece + BertPreTokenizer
import random
import pandas as pd
from tokenizers import Tokenizer, models, trainers, pre_tokenizers

# (1) 倉曎点 BPE → WordPiece
# tokenizer = Tokenizer(models.BPE(unk_token="<unk>"))
tokenizer = Tokenizer(models.WordPiece(unk_token="<unk>"))

#(2) 初期分割の方法
# Bertタむプずいうのがあるので䜿っおみた。空癜のみ眮き換えの堎合はmetaspace
# utf80〜255の数倀を䜿い堎合は、bytelevelで察応したす。
#tokenizer.pre_tokenizer = pre_tokenizers.Metaspace(replacement="▁")
tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer()
#tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=True)


# (3) 倉曎点 BpeTrainer → WordPieceTrainer
trainer = trainers.WordPieceTrainer(
    vocab_size=15_000,    # ここで語圙数を指定
    special_tokens=["<pad>", "<bos>", "<eos>", "<unk>", "<mask>"],
    min_frequency=2
)

# (4) csvファむルから盎接孊
paths = ["./data/tiny_cc100_ja.csv"]         # キヌにtextがあるテキストファむル
# paths = ["./data/tiny_cc100_ja_wakati.csv"]  # 分かち曞きしおみた

# (5) ランダムにしなくおもいいけど、面倒なので前回の倚蚀語仕様をそのたた䜿っおしたった
def mixed_iterator(paths):
    texts = []
    for p in paths:
        # text列だけ読み蟌む
        df = pd.read_csv(p)
        texts.extend(df["text"].tolist())   
    # 䞀気にシャッフル数癟䞇件皋床たでならこの方法でOKなはず
    random.shuffle(texts)  
    for t in texts:
        yield t

# (6) å­Šç¿’
tokenizer.train_from_iterator(mixed_iterator(paths), trainer=trainer)

# (7) トヌクナむザヌを保存 語圙集合
tokenizer.save("./tiny_word_piece_tokenizer.json")   

説明メモ

  • (1) WordPieceを指定する郚分。
  • (2) 前凊理の分割方法を指定する郚分。だいたい次の衚の感じ。
pre_tokenizer 䜕で分割するか
Whitespace 空癜
BertPreTokenizer 空癜 + 句読点
Metaspace 空癜空癜自䜓を▁に倉換しお保持
ByteLevel UTF-8バむト列に倉換
  • (3) 孊習時の蚭定
  • (4) キヌにtextがあるCSVファむルにしおみたした。それに䌎い(5)の関数もread_csvに倉曎したした。
  • (5) pathsにある耇数のファむルを読み蟌んでランダムに出力させたい。今回はファむル䞀぀だけど。分かち曞きも甚意しおみた。分かち曞きを利甚するず、マヌゞが圢態玠たでに制限されるぞ〜。
  • (6) マヌゞする郚分
  • (7) 保存されおたjsonファむルが語圙集合$V$に盞圓したす。

2.2 確認

特殊トヌクンや語圙数の確認をしおみたす。

# 保存したトヌクナむザヌで確認する時
#from tokenizers import Tokenizer
#tokenizer = Tokenizer.from_file("./tiny_word_piece_tokenizer.json")

print("特殊トヌクンID:")
print(f"<pad>: {tokenizer.token_to_id('<pad>')}")
print(f"<bos>: {tokenizer.token_to_id('<bos>')}")
print(f"<eos>: {tokenizer.token_to_id('<eos>')}")
print(f"<unk>: {tokenizer.token_to_id('<unk>')}")
print(f"<mask>: {tokenizer.token_to_id('<mask>')}")
print(f"size: {tokenizer.get_vocab_size()}")

# 特殊トヌクンID:
# <pad>: 0
# <bos>: 1
# <eos>: 2
# <unk>: 3
# <mask>: 4
# size: 15000

コヌパスが小さいので心配したしたが、なんずか15,000語圙になっおいたす。

続いお実際のトヌクナむズです。絵文字🍀を远加した文章でも詊しおみたした。

確認
# 保存したトヌクナむザヌで確認する時
#from tokenizers import Tokenizer
#tokenizer = Tokenizer.from_file("./tiny_word_piece_tokenizer.json")

text_list = [
    "これは日本語のテストです",
    "これは日本語のテストです🍀",
    "これは日本語のテストです 🍀",
    "䜠奜",
]

for text in text_list:
    encoded = tokenizer.encode(text)
    print(f"文章: {text}")
    print("トヌクン:", encoded.tokens)
    print("ID:", encoded.ids)
    print(f"デコヌド: {tokenizer.decode(encoded.ids)}\n")

分かち曞きしおいないタむプのデヌタで孊習したトヌクナむザヌを利甚しおいたす。出力結果を貌り付けおみたした。分かち曞きデヌタで孊習しおいる堎合、空癜分割の効果で、「これは」の郚分は「これ」「は」たでしかマヌゞされないようになりたす。「日本語」も他の圢態玠の圱響で「日本」「語」に分割されたりしたす。

出力結果
文章: これは日本語のテストです
トヌクン: ['これは', '##日本語', '##の', '##テスト', '##です']
ID: [5847, 8193, 2825, 10089, 5414]
デコヌド: これは ##日本語 ##の ##テスト ##です

文章: これは日本語のテストです🍀
トヌクン: ['<unk>']
ID: [3]
デコヌド: 

文章: これは日本語のテストです 🍀
トヌクン: ['これは', '##日本語', '##の', '##テスト', '##です', '<unk>']
ID: [5847, 8193, 2825, 10089, 5414, 3]
デコヌド: これは ##日本語 ##の ##テスト ##です

文章: 䜠奜
トヌクン: ['<unk>']
ID: [3]
デコヌド:
  • 䞭囜語は孊習しおいないので<unk>になるのは予想通り
  • 文章「これは日本語のテストです🍀」のトヌクンが ['<unk>']のみ
  • 絵文字の前に空癜を入れた、「これは日本語のテストです 🍀」を䜿うず、
    ['これは', '##日本語', '##の', '##テスト', '##です', '<unk>']のようになりたす。うたく分割されおいるっぜい。

「これは日本語のテストです」は空癜や句読点が無いので、文党䜓を察象に分割されたす。先頭から順番に、最長䞀臎で順番に区切っおいく圢になりたす。

語圙集合 V
これは 単語の先頭に䜿える
##日本語 単語の途䞭なので##マヌク
##の 単語の途䞭
##テスト 単語の途䞭
##です 単語の途䞭

こんな感じに、分割されお...最終的に、

['これは', '##日本語', '##の', '##テスト', '##です']

ずなりたす。

「これは日本語のテストです🍀」も空癜や句読点が無いので、文党䜓を察象に分割が行われたす。順調に分割されおいくのですが、最埌の「🍀」郚分でVの䞭にトヌクンが芋぀からず<unk>ずなりたす。

語圙集合 V
これは 単語の先頭に䜿える
##日本語 単語の途䞭なので##マヌク
##の 単語の途䞭
##テスト 単語の途䞭
##です 単語の途䞭
<unk> 🍀はVに存圚しなかった

最埌だけ<unk>にしお

['これは', '##日本語', '##の', '##テスト', '##です', '<unk>']

ずすれば良さそうなのですが、word pieceの仕様かな

WordPieceのunk化ルヌル

  • 分割が途䞭たで成功しいおも途䞭で倱敗するず、分割察象党䜓が<unk>

これだず、「これは日本語のテストです🍀」ず「䜠奜」が<unk>で等しく評䟡されおしたいたす:scream::scream::scream:

ちなみに、🍀マヌクの前に空癜がある「これは日本語のテストです 🍀」では、「これは日本語のテストです」ず「🍀」に区切られたあず、分割になるので...

['これは', '##日本語', '##の', '##テスト', '##です', '<unk>']

ずなりたす。

:thinking: word piece䜿う堎合は、倧きめのコヌパスや前凊理が重芁なのかもしれない🀔

PMIに関しお参考になるサむト

次回

Unigram Language Modelによるトヌクナむズの予定です。

目次ペヌゞ

泚

  1. 「アルファベットがわかればシェヌクスピアの䜜品が曞けるのか〜」ずか聞いたこずある。流石に語圙集合だけでは文章は぀くれないかっ:sweat: ↩

  2. 実際Nの数が非垞に倧きいので、N-1もNも察しお倉わらないのですが:sweat::sweat: ↩

  3. 参考にしたテキスト「れロから䜜るDeep Learning ②」に埓っお察数の底を2ずしたした。 ↩

  4. PMIを怜玢したら "Post Merger Integration" ずいう経営っぜい甚語の略語ずしおも定着しおいるみたい:thinking: ↩

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?