今日も窓辺でプログラム

外資系企業勤めのエンジニアが勉強した内容をまとめておくブログ

SentencePieceを使用してRNN言語モデルを学習させてみる

はじめに

前回の記事ではpytorchのサンプルコードを参考にし、Wikipediaの記事の一部に対してRNN言語モデルを学習させてみました。
RNN言語モデルのpytorch実装をWikipediaの記事で学習させてみる - 今日も窓辺でプログラム

その際にWikipediaの記事を単語に分割するための技術として、MeCab + neologd辞書を使用していました。
Wikipediaの日本語記事を全行を、分かち書きしてforループで回す - 今日も窓辺でプログラム

最近出てきた文章を単語分割する手法に、wordpiecesと呼ばれている技術があります。その実装であるSentencePieceがGoogleから公開されています。
技術的な解説などは、SentencePieceの開発者の方(MeCabの開発者でもあります)が公開しているQiitaの記事で詳しく説明されているので、ぜひ読んでみてください。
Sentencepiece : ニューラル言語処理向けトークナイザ - Qiita

SentencePieceを使用すると、少し長い文字列でも出現頻度が高ければ1つの単語IDが割り当てられるようです。逆に、人間から見ると一つの単語でも、その単語の出現頻度が低い場合は単語をさらに分割した小さい単位に対して単語IDを割り当てていきます。

この記事では、前回学習したRNN言語モデルの単語分割部分をMeCabからSentencePieceに置き換えるとどのような結果になるか試してみたいと思います。

使用したソースコード

GitHubに今回使用したJupyter Notebookを公開しています。前回と共通のコードも多く記事中ではすべてを紹介していないので、必要に応じて参照してください。
github.com

SentencePieceのインストール

GitHubで公開されているReadMeに沿ってコマンドをたたくだけでインストールは完了です。
Pythonバインディングも公開されていて、こちらに関してはpipで一発インストールできます。

pip install sentencepiece

Wikipediaの一部記事を切り出す

前回までは、MeCabで分かち書きした状態のものをテキストファイルとして保存していたのですが、SentencePieceの学習には分かち書きされていない生テキストが必要です。
下記コードを使って、24k, 3k, 3k文を学習用、検証用、テスト用に取り出します。
前回までの記事ではこのwiki_sentences()というジェネレータは1文ではなく1行(改行区切り)を返すようになっていましたが、今回は改行と"。"で区切った文を返すように変更を加えました

from WikiIterator import wiki_sentences
# データを用意する。学習用、検証用、テスト用にそれぞれ24k, 3k, 3k文。
train_size = 24000
valid_size = 3000
test_size = 3000

gen = wiki_sentences()
for filename, size in zip(
    ['sp/wiki_train.txt', 'sp/wiki_valid.txt', 'sp/wiki_test.txt'],
    [train_size, valid_size, test_size]):
    
    i = 0
    output = ''
    for text in gen:
        output += text + '\n'
        i += 1
        if i >= size:
            with open(filename,  'w', encoding='utf-8') as f:
                f.write(output)
            break

SentencePieceのモデルを学習

トレーニングデータでモデルの学習

学習はコマンド1回で終わります。

spm_train --input=sp/wiki_train.txt --model_prefix=sp/sp --vocab_size=8000

今回はWikipediaから持ってきた24000文を、語彙数8000に落とし込むことにしました。(8000, 16000, 32000あたりの語彙数がよくつかわれるようです)

どのように分割されたか見てみる

SentencePieceでトレーニングデータがどのように分割されたか見てみましょう。

import sentencepiece as spm
sp = spm.SentencePieceProcessor()
sp.Load('sp/sp.model')

i = 0
for text in wiki_sentences():
    print(' '.join(sp.EncodeAsPieces(text)))
    i += 1
    if i >= 5: break
▁ 悔返
▁ 悔返 ( く い か え し ) とは 、 中世 日本 において 、 和 与 ・ 寄 進 などの 財 産 処分 を 行 って 所有 権 の 移動 が行われた 後に 元 の 所有 者 あるいは その 子孫 らが その 行為 を 否定 して 取り 戻 す 行為 。
▁ 平 安 時代の 公 家 法 においては い か なる 場合 でも 悔返 は 認められ ていなかった とする の が 通 説 とされている が 、 教 令 違反 や 不 孝 など 律 令 法 における 廃 嫡 に 相 応 する ような 罪 の 事実 が 子孫 に あ れば 、「 後 状 有効 説 」 によって 悔返 は 認められ た とする 異 説 ( 長 又 高 夫 ら ) もある 。
▁ 鎌倉 時代 に入る と 、 公 家 法 において も 子孫 に対する 悔返 が 場合 によって は 認められる よう になっていた が 、 その 場合 において も 親 の 保護 下 に ない 既 婚 女子 への 譲 与 分 の 悔返 の 禁止 などの 厳しい 制 約 が設けられ 、 特に 本人 もしくは 和 与 を受けた 子孫 が 第三 者 に 譲 った 場合 ( 他 人 和 与 ) には い か なる 場合 でも 悔返 は 認められ なかった 。
▁これ に対して 武 家 法 においては 惣 領 の 親 権 を より 重要 視 する 立場 から 、 寺社 への 寄 進 を 除 いて 広く 認められ た 。

各文の先頭についている▁は、文の区切りを意味する記号です。
すぐに見て取れるMeCabとの違いの1つは、複数単語からなるトークンが存在していることです。例えば、「認められていなかった」は「認められ」と「ていなかった」に、「通説とされている」が「通説」と「とされている」に分割されています。

逆に、武家法などは、「武」「家」「法」という3つのトークンに分割されています。今回使用した学習データに、「武家」や「武家法」という文字列の出現が少なかったために、このように1文字単位で分割されているようです。

言語モデルを学習させる

あとは前回のコードのコーパスを読み込む部分を少し書き換えると、言語モデルが学習できます。
コード全体はGitHubにあげてあるので、そちらを参考にしてください。

学習は、40エポックを超えたあたりで学習率が小さく、ロスもほぼ変わらなくなったので打ち切りました。

-----------------------------------------------------------------------------------------
| epoch  41 |   200/  965 batches | lr 0.02 | ms/batch 26.30 | loss  4.80 | ppl   121.32
| epoch  41 |   400/  965 batches | lr 0.02 | ms/batch 25.81 | loss  4.69 | ppl   108.77
| epoch  41 |   600/  965 batches | lr 0.02 | ms/batch 26.12 | loss  4.69 | ppl   108.73
| epoch  41 |   800/  965 batches | lr 0.02 | ms/batch 26.16 | loss  4.67 | ppl   106.48
-----------------------------------------------------------------------------------------
| end of epoch  41 | time: 26.58s | valid loss  5.12 | valid ppl   167.45
-----------------------------------------------------------------------------------------

トレーニングデータ等の量が少し多くなっているので、直接比較はできないですが、参考までに前回の学習結果と比較してみると、
語彙数が少なく抑えられているのでロスやパープレキシティが前回より小さくなっています。

テストデータでも評価をしたところ、

=========================================================================================
| End of training | test loss  5.11 | test ppl   165.51
=========================================================================================

という前回より小さいロスになりました。

文章を生成してみる

これまた前回と同じ方法で文章を生成してみました。文としては不完全なものが多いですが、ところどころそれなりに日本語っぽいものが生成されているようにも見えます。。

エ ヨ ピン リン の 三 つの 隈 壮 地 の 異 起 が 出 い られていた 。
北 関東 の
運動星団 が 受け る 麟 、 南アフリカ で 、 変 改訂 に 興味 は 過 激 な なく だった 。
の 3 つの 分類 されている 。
シー レ ・ スクエア ( 採 り 山 )
能 線
その 長 が 含まれている 。
FC サ ロ キ ガ イン 
( The 
B ur t is 
L i ch e y 」 とは 、 植物 としても 使われ ていた 公開 の 山 本 の 項 基 礎 調査 である 。
は サー ヴィ ュ ー によって 集中 する 動き が 、 航海 の 不 意 に 知 された もので 、 また 、 セ チ 州 ジ パ ヨ を 6 月 29 日に 公式 ジョ リ ・ ヨ ック よりも ものである 。
一 杯 の 近 する ことになる 。


関連記事

www.madopro.net