Scala(JVM) で機械学習(NLP, Deep Learning, Neural Net, etc)
BERT 周りの知識
tokenize
(日本語向け)BERT では, 分かち書き(word segmentation) + subword 分割した後でトークナイズ処理をする. 分かち書き + subword 分割 の部分は pre-tokenize と呼ぶことがある.
入力テキストを subword まで分割して辞書(語彙)に含まれる tokenid にマッピングする処理をトークナイズと呼ぶ. subword 分割とトークナイズ処理は model の vocabulary に依存する.
vocabularyファイルがサブワード分割の学習の結果生成され*5、サブワード分割を行う際に必要になる。
BERT派生のモデルでは従来の単語分かち書きだけでなく、さらに細かい分かち書き単位であるサブワード分割という処理が適用される。 モデル的にはこの処理は必須というわけではないものの、報告されているタスク性能のほとんどがサブワード分割を入れた上での性能値なので、特別な理由がなければ入れておきたい。(https://tech.mntsq.co.jp/entry/2021/02/26/120013)
tokenize の一連の処理は以下のように整理できる
Encode: 文字列 → 事前学習モデル入力可能なEncodingオブジェクト
- Normalizer: 文字列正規化の前処理(Unicode正規化や小文字化など)
- PreTokenizer: 文字列→基本トークン(単語)の変換
- Model: 基本トークン→トークン(サブワード)の変換;学習済みvocabularyが必要な箇所
- PostProcessor: 最終的なEncodingデータを生成する後処理(BERTの特殊トークンの追加など)
word segmentation | subwording | link | |
---|---|---|---|
cl-tohoku/bert-base-japanese-whole-word-masking | mecab | wordpiece | https://huggingface.co/cl-tohoku/bert-base-japanese-whole-word-masking |
cl-tohoku/bert-japanese | mecab | wordpiece | https://github.com/cl-tohoku/bert-japanese |
gpt2-japanese | bpe | https://github.com/tanreinama/gpt2-japanese/blob/master/report/models.md |
BERT のモデルにも tokenizer がはいっているが、ユニコード正規化が cjk を意識していなかったり、漢字の分割の単位が小さくなりすぎるので mecab など日本語の形態素解析ライブラリで分割する必要がある.
外国語のモデルのトークナイザをそのまま日本語に使うのは避けた方がいい.
日本語に bert-base-cased の tokenizer を利用すると多くの単語が [UNK]
になる.
tokenizer の違いがその後のタスクに影響するか調べたレポート?研究発表?
tokenType
一文に論理的に複数の文章が含まれているときにそれを識別するための id. 通常はすべて同じ値でいいが、質問文と回答などを扱うときは tokenType を 0, 1 などと指定する.
vocab size
モデルに含まれる語彙のサイズ. 30500 とか 32000 とかベースのモデルに近い値がはいっていることが多い.
一般的には、Transformerモデルが50,000を超える語彙数を持つことはほとんどありません。
ベースのモデルによっては vocab size を変更すると動かなくなるものがあるらしいのであまり変えない方が良さそう.
JVM のライブラリ
sentencepiece は djl の実装がある.
scikit learn や w2v も onnx に変換できるらしい. 一通り試してみたい.
極めてシンプルな torch モデル -> ONNX の構築
色々みてると input embeddings と contextualized word embeddings に区別があるらしい.
これ1単語だけ突っ込んだ場合も contextualized word embeddings は変わるのかしら.
生成した特徴量を inspect したり欲しいものを引っこ抜いたりするのに使える道具あれこれ
近傍探索ライブラリ
Tokenizer について
base | tokenizer | subwording | example |
---|---|---|---|
BERT | MeCab | WordPiece? | cl-tohoku bert japanese |
DistilBERT | MeCab | Sentencepiece | LINE DistilBERT |
RoBERTa | Juman++ | Unigram | waseda RoBERTa |
T5 | sentencepiece |
sentencepiece は unigram や bpe の拡張がある... :thinking:
huggingface のモデルの場合はほとんど sentencepiece + unigram?
テキストの前処理
入力は最低限以下の5段階.
- 生のテキスト
- 正規化済みテキスト: https://github.com/neologd/mecab-ipadic-neologd/wiki/Regexp.ja
- 分かち書き済みテキスト: MeCab, Sudachi, Juman
- 分かち書き済みテキスト + subwording: BPE, Wordpiece, Sentencepiece は 2 と 3 をカバーする?
- 語彙 -> id へのマッピング: tokenizer.encode
(sentencepiece などの)トークナイザーの語彙の訓練が必要なケースもある.
BPEとsentencepiece について
BPE は文章を「単語」に分割した後で適用するので単語分割の精度に影響を受ける可能性がある.
sentencepiece は日本語のように単語の境界がスペースで決定されない言語でも影響を受けにくい.
sentencepiece の保存と tokenizer の json 形式への書き換え
cl-tohoku系列: JVM なら sudachi で分かち書きして djl の wordpiece でサブワード分割すればトークナイザーの代わりに使えそう...? cl-tohoku の vocab
tokenizer の encode/decode さえ JVM でできればあとは onnx runtime に突っ込むだけ.
文書分類などの基本的なタスクなら一応 djl でもできそう.
HuggingFaceTokenizer.newInstance("sentence-transformers/msmarco-distilbert-dot-v5")
model = AutoModelForCausalLM.from_pretrained(...); model.generate(...)
に対応するタスクは 2023/09 時点では実装中っぽい.
input_tkn_ids = tknz.tokenize(text)
out_tkn_ids = model.generate(input_ids, ...)
tknz.decode(out_tkn_ids)
// => human-readable text
テキスト生成時の探索. ビームサーチとか.
sentencepiece の開発者の記事
sentencepiece のモチベーションについて書かれている記事
8000 とか 32000 とか、結構研究者の肌感なのかしらね.
ggml や llama2 がかなり依存が薄く&ビルドがシンプルに作られているのでそれにバインディングするのが良さそう.
実際、Scala x SlinC でのバインディング例がある.
djl を挟んでもいいが ggml や llama のラッパーを書いてもいいかも.
WASM も意識してるっぽい.
spacy, ginza の関係性やアーキテクチャ
Oracle の Tribuo という Deep Learning 向けライブラリも開発中っぽい.
//> using dep "ai.djl.huggingface:tokenizers:0.28.0"
val t = HuggingFaceTokenizer.newInstance("sentence-transformers/paraphrase-xlm-r-multilingual-v1")
val encoding = t.encode("これはテストです")