🙄

SudachiをつかったBERTの事前学習モデルの構築をしてみようとおもったら(いまやってる途中)

2022/02/17に公開

これは個人的な作業記録的なものと、もし今後同じような志を持った人の参考となるようにするために可能な限り作業の詳細を残しています。

そもそもSudachiとは

そもそもSudachiってなにてきなひとはこんなマニアックなタイトルの記事のところに来ないでしょうがもしも気になる方は僕の友人が、形態素解析器Sudachiをpythonファイルで使ってみた的な記事を書いているので的に参照してみるのがいいとお思います。

なんで事前学習モデルなんか作ろうと思ったのか?

地方国立大の理学部に在籍していて、ちょうど卒業研究終わりなどで、計算リソースがほぼ独占状態でつかえること
計算リソースも一応国立大というだけあってBERTの事前学習に耐えられるだけありそう
研究室配属前でわりと好きなことができて、時間がある期間ということ

という感じにべつに絶対やり遂げようとおもってるわけでもないのですが、なにより「ないからなんとなくつくってみようかな」てきな興味本位ではじめました。

手順

手順はすべてTraining Sudachi BERT Modelsに書いてあります。
いろいろと古いものも玉石混合だったようですが、Slack上で聞くと丁寧に対応していただける開発者の方がいて古いものやエラーは一掃されたように思えます。
まだやってる途中なのでわかりません!

以下は上記のドキュメントプラス個人的な環境のものなど入れてます。

環境構築
必要なパッケージのインストール

環境構築

環境構築は、
1.SudachiTraをGithubからClone
2.Dockerコンテナの準備
で行います。

SudachiTraをGithubからClone

ディレクトリないと始まらないので、Training Sudachi BERT Models set-upのはじめにある

bash
git clone --recursive https://github.com/WorksApplications/SudachiTra/
pip install -U sudachitra

のコマンドを適当なターミナルで実行します。

Dockerの準備

環境は、NGCプラットフォームのTensorflowコンテナを利用しました。
 Dockerfileとdocker-compose.ymlは以下のような感じです。

SudachiTra/Dockerfile
FROM nvcr.io/nvidia/tensorflow:22.01-tf2-py3 AS tensorflow
RUN mkdir /nlp
WORKDIR /nlp
SudachiTra/docker-compose.yml
version: '3'

services:
sudachi:
  build: 
    context: .
    dockerfile: Dockerfile
    target: tensorflow
  tty: true
  deploy:
    resources:
      reservations:
        devices:
          - driver: nvidia
            count: all
            capabilities: [gpu]
  volumes:
    - .:/nlp
  working_dir: /nlp

コンテナの起動

コンテナを起動します。SudachiTraのディレクトリで以下のコマンドを使ってコンテナを立ち上げます。

SudachiTra/
# SudachiTra/ のディレクトリで以下のコマンドを実行する
docker-compose up -d

コンテナ内には、

SudachiTra/
# SudachiTra/ のディレクトリで以下のコマンドを実行する
# docker-compose up -dの実行後に
docker-compose exec sudachi /bin/bash

で入ることができます。

必要なパッケージのインストール

コンテナ内で、Training Sudachi BERT Models set-upにある

nlp/
# 上記と同じdocker-compose.ymlであれば以下のcdコマンドで移動した先で下記のコマンドを実行するとちょうどいい感じになります。
# cd SudachiTra/
pip install -r requirements.txt
pip install -r pretraining/bert/requirements.txt
pip install -r pretraining/bert/models/official/requirements.txt

# めんどくさいので、
# pip install -r requirements.txt && pip install -r pretraining/bert/requirements.txt && pip install -r pretraining/bert/models/official/requirements.txt
# で実行するといいと思います。

のコマンドを実行します。
上記のdocker-compose.ymlなら/nlp/SudachiTra/ディレクトリで上記のコマンドを実行すれば大丈夫です。それぞれの環境に合わせて実行してください。

以下全てコンテナ内で進めていきます

データセットのダンロード

Quick Start の 1.download-wiki40bをダウンロードします。

お恥ずかしながらwiki40bという存在を知らずに今まで過ごしていました。wiki40bは日本語の場合、2.19GB
のデータセットからなります。

74万5392件の訓練(train)データ用
4万1576件の精度検証(validation)データ用
4万1268件のテスト(test)データ用
合計82万8236件(2.19GB)
公式ドキュメントの日本語の部分参照

それではダウンロードしていきます。

nlp/
# 下記のコマンドをコンテナ内で実行
$ cd pretraining/bert/
# It may take several hours.
$ ./run_prepare_dataset.sh

このファイルは、Quick Startによると、

BERTの事前トレーニングのためにはデータをドキュメント単位に分割して準備する必要があリます。そこで。run_prepare_dataset.shスクリプトを実行すると、wiki40bのダウンロードと処理が行われます。
データセットを準備するためのスクリプトの内容は次のとおりです。

データのダウンロード : wiki40bがdatasets/corpusディレクトリにダウンロードされます。
文のセグメンテーション ; コーパステキストは別々の文に処理されます。
ドキュメントのセグメンテーション : ドキュメントに分割されたコーパステキスト。

処理されたデータは/datasets/corpus_splitted_by_paragraphに保存されます。
コーパスファイルは合計で約4.0GBです。

実行結果

745391 Elapsed Time: 13:04:37

real	786m2.272s
user	787m12.685s
sys	0m56.138s

--------------------------------
41575 Elapsed Time: 0:35:05

real	35m16.749s
user	35m18.906s
sys	0m5.971s

--------------------------------
41267 Elapsed Time: 0:35:00

real	35m11.358s
user	35m13.632s
sys	0m5.666s

前処理

ダウンロードしたコーパスの一部には学習に大きな影響を与えるには不適切(短or長すぎる)文章があります。そのようなドキュメントを除外するために以下を実行します。

pretraining/bert/
python preprocess_dataset.py  \
-i ./datasets/corpus_splitted_by_paragraph/ja_wiki40b_train.paragraph.txt \
-o ./datasets/corpus_splitted_by_paragraph/ja_wiki40b_train.preprocessed.paragraph.txt \
--sentence_filter_names email url sequence_length \
--document_filter_names short_document script ng_words \
--sentence_normalizer_names citation whitespace \
--document_normalizer_names concat_short_sentence

実行結果

100% (1869817 of 1869817)  Elapsed Time: 0:04:46 Time:  0:04:46

Vocablaryの構築

sudachiTraのドキュメントにあるようにWordPieceを使用してサブワードを取得し、TokenizersでWordPieceの実装されているものを使いました。

WordPiece

pretraining/bert/
python train_wordpiece_tokenizer.py \
--input_file datasets/corpus_splitted_by_paragraph/ja_wiki40b_train.preprocessed.paragraph.txt \
--do_nfkc \
--vocab_size 32000 \
--limit_alphabet 5000 \
--dict_type core \
--split_mode C \
--word_form_type normalized \
--output_dir _tokenizers/ja_wiki40b/wordpiece/train_CoreDic_normalized_unit-C \
--config_name config.json \
--vocab_prefix wordpiece

実行結果

[03:30:17] Pre-processing files (1528 Mo)               100%
[00:00:01] Tokenize words  1215394  /  1215394
[00:00:01] Count pairs     1215394  /  1215394
[00:00:06] Compute merges  22320    /    22320

Character

sudachiTraのドキュメントにあるようにwordPieceにあるようなトークン化されたCharacterによって作成された語彙から文字のみを抽出することで、語彙を取得できます。

pretraining/bert/
OUTPUT_DIR="tokenizers/ja_wiki40b/character/train_CoreDic_normalized_unit-C"
mkdir -p $OUTPUT_DIR
head -n 5005 _tokenizers/ja_wiki40b/wordpiece/train_CoreDic_normalized_unit-C/wordpiece-vocab.txt > $OUTPUT_DIR/vocab.txt

POS Substitution(part-of-speech substitution : 品詞置換)

POS Substitutionは、品詞タグを使用して語彙のサイズを縮小する方法です。
 POS Substitution、サブワードトークナイザーを使用する代わりに、低頻度の単語が品詞タグに置き換えられます。
 最後に、トレーニングコーパスに表示されない品詞タグのみが不明な単語として扱われます。

pretraining/bert/
python train_pos_substitution_tokenizer.py \
--input_file datasets/corpus_splitted_by_paragraph/ja_wiki40b_train.preprocessed.paragraph.txt \
--token_size 32000 \
--limit_character 5000 \
--dict_type core \
--split_mode C \
--word_form_type normalized \
--output_file _tokenizers/ja_wiki40b/pos_substitution/train_CoreDic_normalized_unit-C/vocab.txt 

事前学習用のデータセットの作成

データセットを複数のファイルに分割することがsudaxhiTraのドキュメントで推奨されているのでしたがって分割します。
 アップデートされたsudachiTraのドキュメントでは追加でプログラムに関する説明と実行に必要なリソースが示されました。
 この分割用のプログラムはTensorFlow ModelGardenに基づいているということです。

公式ドキュメント
It consumes about 10 GB or more of memory to create the data for pre-training from this one file.

と大量にメモリが消費されることが記載されていますが、本当に信じられないくらい消費します

例にしたがって、ファイルあたりの文数(--line_per_file)は700,000に設定して、実行すると最大で見た限り89GBくらい使ってました。
ただ、環境が68GBだったので、MAX_PROCSを6に設定しています。

pretraining/bert/
# wiki40bを複数のファイルに分割
python split_dataset.py \
--input_filedatasets / corpus_splitted_by_paragraph / ja_wiki40b_train.preprocessed.paragraph.txt \
--line_per_file 700000

TRAIN_FILE_NUM = ` finddatasets / corpus_splitted_by_paragraph -type f | grep -E " ja_wiki40b_train.preprocessed.paragraph [0-9] +。txt "  | wc -l `
nlp/pretrainig/bert/
cd ../../ 
MAX_PROCS = 8 
mkdir datasets_for_pretraining
export PYTHONPATH="$PYTHONPATH:/nlp/pretraining/bert/models"

seq -f %20g 1 ${TRAIN_FILE_NUM} | xargs -L 1 -I {} -P ${MAX_PROCS} python3 models/official/nlp/data/create_pretraining_data.py \
--input_file datasets/corpus_splitted_by_paragraph/ja_wiki40b_train.preprocessed.paragraph{}.txt \
--output_file datasets_for_pretraining/pretraining_train_{}.tf_record \
--do_nfkc \
--vocab_file _tokenizers/ja_wiki40b/wordpiece/train_CoreDic_normalized_unit-C/wordpiece-vocab.txt \
--tokenizer_type wordpiece \
--word_form_type normalized \
--split_mode C \
--sudachi_dic_type core \
--do_whole_word_mask \
--max_seq_length 512 \
--max_predictions_per_seq 80 \
--dupe_factor 10

実行結果
 とんでもなく長いので省略しますが、割と時間がかかります。

学習

やっと学習です!

GPU4枚使用する場合です。
 
 sudachiTraの新しいドキュメントに関しては、LAMBが利用できるので、nvidiaのDockerコンテナが推奨されていますが、tensorflowで事項してしまったので、先にそちらを記載します。

/nlp/pretraining/bert/
pwd
# /nlp/pretraining/bert/
cd models/
# export BERT_PREP_WORKING_DIR="/workspace/bert/data"

WORK_DIR="../../../pretraining/bert"; python official/nlp/bert/run_pretraining.py \
--input_files="$WORK_DIR/datasets_for_pretraining/pretraining_*_*.tf_record" --model_dir="$WORK_DIR/bert_small/" \
--bert_config_file="$WORK_DIR/bert_small/bert_small_config.json" \
--max_seq_length=512 \
--max_predictions_per_seq=80 \
--train_batch_size=1 \
--learning_rate=1e-4 \
--num_train_epochs=100 \
--num_steps_per_epoch=10000 \
--optimizer_type=adamw \
--warmup_steps=10000 \
--num_gpus=4
GitHubで編集を提案

Discussion