文系と理系の交差点

文系と理系の交差点

文系と理系を行ったりきたりして生活しているエンジニアブログ

言語処理でよく使う前処理まとめ -tokenize, subword-

f:id:Cardinal_Moon:20200919174338p:plain

言語処理を行うときの基本として,現在は文章を単語などの何らかの単位に区切り(トークナイズ (tokenize) して),それらをベクトルに落とし込んでモデルで処理することが多いです.

f:id:Cardinal_Moon:20200919174757p:plain

今回はトークナイズ(単語を区切ること)にフォーカスして,それをでどの様に書くかを簡単にご紹介します.言語は主に python を使用します.

目次

トークナイズ

そもそもトークナイズとは,単語をトークンという単位に区切ることを指しますが,この区切り方は様々あります.よく使われるのは単語や形態素です.

さらに後ほど説明するサブワード (subword) といって,単語をさらに細かく区切った表現をトークンとして扱うことや,1文字を1トークンとして分割すること(character 分割)などもあります.

ここでは,単語・形態素分割する場合について書いていきます.

python ではライブラリが豊富に準備されているため,ほとんどの場合,ライブラリを用いて行います.今回は日本語と英語を例にトークナイズしてみます.

日本語の場合

ライブラリとしては MeCab を用います.一応,公式リンクはこちら

MeCab は条件付き確率場 (CRF: Conditional Random Fields) に基づく形態素解析エンジンです.CRF については今回解説しませんが,次に挙げる参考書にその説明が掲載されています.

インストールは pip コマンドで可能です(リンク).ビルドの方法などは,公式リンクを参照ください.

$ pip install mecab-python3

MeCab は辞書を用いてトークナイズを行いますが,pip でインストールした場合,デフォルトで mecab-ipadic になるそうです.
※変更する場合は別途設定が必要

使い方はシンプルで,インスタンスを生成して,実際にトークナイズするだけです.”-Owakati” の部分で分かち書きトークン分割をする)ことを指定しています.

# in python code
import MeCab
makati = MeCab.Tagger(“-Owakati”)
wakati.parse(“pythonが大好きです”).split()
# output:
# [‘python, ‘が’, ‘大好き’, ‘です’]

他にもオプションがあり,例えば "-Ochasen" を指定すると,

# in python code
import MeCab
chasen = MeCab.Tagger("-Ochasen")
chasen.parse("pythonが大好きです").split()
# output:
# python python  python 名詞-固有名詞-組織
# が   ガ    が   助詞-格助詞-一般
# 大好き ダイスキ 大好き 名詞-形容動詞語幹
# です  デス   です  助動詞 特殊・デス 基本形
# EOS

の様に,形態素解析した結果が見られます.

日本語のトークナイザとしては,他にも Juman, Janome などのトークナイザが python ライブラリとして準備されています.
※今後追記するかは未定です

英語の場合

ライブラリとしては mosestokenizer を利用します(リンク).

こちらも pip コマンドでインストール可能です.

$ pip install mosestokenizer

python で使うときは,次の様に書きます.

# in python code
from mosestokenizer import *
tokenizer = MosesTokenizer('en')
token_list = tokenizer('I have a pen.')
# output:
# ['I', 'have', 'a', 'pen', '.']

因みに,mosestokenizer はヨーロッパ系の言語で幅広く利用することができ,ドイツ語やロシア語などにも適用できます.
scripts/share/nonbreaking_prefixes
に定義ファイルが存在すれば利用可能です.
定義ファイルがない場合は,次の様な Warning が出ます.

tokenizer = MosesTokenizer('ja')
# output:
# WARNING: No known abbreviations for language ‘ja’, attempting fall-back to English version..

shell script から利用するときは次の様に書きます.

$ cat {text_file} | moses-tokenizer en > {output_file}

英語 (en) の箇所をその他の言語に書き換えれば,ドイツ語 (de) やロシア語 (ru) などでも使用可能です.

サブワード (Subword)

近年の言語処理においては,トークナイズを行う際に,単語よりさらに小さい単位に分割することで,未知語を減らしたり,vocabulary を節約したりすることで,モデルの性能を底上げすることが多いです.

今回は有名な BPE, SentencePiece の2つの利用方法について書いていきます.

BPE (Bite Pair Encoding)

BPE の基本的な考え方は単語をさらに細かく分割していくとき,頻出する文字列の組み合わせからその分割方法を学習するというものです.

元論文はこちら

f:id:Cardinal_Moon:20200919174739p:plain

BPEの学習をする時,始めは1文字単位から出発し,上の例にみれられる様に,頻出する文字同士を結合させていきます.
上記の例の場合,"r" が文末 ("") にきていることが多いため,"r" + "" を学習し,次に "l" + "o" という結合を学習します.
これを指定した operation 数だけ繰り返します.

これが BPE の基本的なアルゴリズムです.
十分なデータで学習すると,英語でいうところの "ing", "er", "ly" など,見たことのある表現がうまく分割されるはずです.

ライブラリとしては,subword-nmt が有名です(論文の著者の GitHub です).

こちらも pip でインストールすることが可能です.

$ pip install subword-nmt

使い方としては,単語ごとにトークナイズしたのち,BPE を適用します.

コマンドラインから使用します.

# トークナイズ
$ cat {text_file} | moses-tokenizer en > {tok_file}
# 学習して
$ subword-nmt learn-bpe -s {num_operations} < {tok_file} > {codes_file}
# 適用する
$ subword-nmt apply-bpe -c {codes_file} < {applied_file} > {out_file}
# vocab ファイルを作成する時
$ subword-nmt get-vocab --input {out_file} --output {vocab_file}
# BPE から,通常の文字列にもどす時
$ cat {encoded_file} | sed -r 's/(@@ )|(@@ ?$)//g' > {detok_file}

{num_operations} の数,繰り返しサブワードの分割を学習していきますが,これは正確に vocabulary の数と一致しません.最終的にこのアルゴリズムを適用して得られたトークンの集合を vocaburary として扱うことになります.ご注意ください.

BPE に限った話ではありませんが,言語処理を行う際は train データを用いてサブワード (Subword) を学習し,validation, test のファイルにも適用することが通例です.

このライブラリのより詳しい使い方などは,先ほど紹介した著者 GitHub をご参照ください.

SentencePiece

先ほど紹介した BPE のアルゴリズムは,一度単語に分割してから適用する必要がありました.これ自体も手間になります.そして日本語や中国語など,スペース区切りがない言語,もっと言うとどこまでが1単語かを決めることが難しい様な言語では,単語分割の正確さにも多少の疑問が残ります.

この問題を解決するために,SentencePiece は生み出されたそうです.
詳しくは,元論文・著者の Qiita を参照ください.

基本的なアイデアは,スペースも文字として扱うことで,全て􏰁言語におい て,文を文字列とみなすという考え方です.これにより単語分割することなく,BPEや言語モデルに基づく教師なし分割を学習することを可能にしました.

f:id:Cardinal_Moon:20200912131923p:plain

こちらも,python のライブラリとして準備されています.(GitHubリンク

インストールは,こちらも pip で可能です.

$ pip install sentencepiece

python での使用方法は次のとおりです.

# in python code
import sentencepiece as spm # インポート
spm.SentencePieceTrainer.Train(“--input=data --model_prefix=sp_model \
--vocab_size=16000 --character_coverage=1.0) # training
sp = spm.SentencePieceProcessor() # インスタンス準備
sp.Load(sp_model) # train したモデルをロード
sp_list = sp.EncodeAsPieces(item) # 学習結果を適用
decoded_text = sp.DecodePieces(sp_list) # デコード

コマンドラインで書くと,次の様になります.

$ spm_train --input={input} --model_prefix={model_name} --vocab_size=16000 --character_coverage=1.0
$ spm_encode --model={model_file} --output_format=piece < {input_file} > {output_file}
$ spm_decode --model={model_file} --input_format=piece < {input_file} > {output_file}

character_coverage は,日本語などの vocabulary が多い言語で 0.9995 が良いと,先ほどのGitHub で紹介されています.

参考

URL

図書

元論文

2020/9/6 時点参照