400GB程度の清掃済み日本語コーパスを作るまでのメモ書き



はじめに

最近は大規模言語モデルを作っています。

来週から始まる学習に向け、400GBほどの日本語コーパスをチームで作成しました。今後、きちんとした作業記録を書ければと思いますので、直近では、かなり立て込んでおりますので、備忘録も含めて、構築経緯などを軽くメモしていきます。

スライド、コード、データなど

スライドはこちら

コードはこちら(工事中の箇所がちょくちょくあります)

データは、プロジェクト内で共有中です。slackなどで個別にお問い合わせいただければ、共有できると思います。

概要

データ

  • CommonCrawlから収集した複数のsnapshot

    • 合計6個 (一部はwarc, 残りはwet)

  • 既存の日本語コーパス (mc4, oscar, cc100, shisa, 日本語2010)

  • ドメイン特化の種々のコーパス

処理時間

  • データ収集: 数日以上

    • CommonCrawlはGCPのインスタンスなどで対応

  • クリーニング: 3日程度

    • ただし後述の通り、CommonCrawlは20%程度しか処理できなかった

    • 処理速度に改善の余地大

  • 重複削除: 1日

  • コーパス化: 1日

  • トークナイズ: 1日

用いたマシン

  • データ処理

    • 64コア、256GB RAM、8TB程度程度のストレージ

      • もっと並列数やRAMがあっても良かった

      • CommonCrawlのdedupには8TBでも足りない疑惑

  • CommonCrawl収集

    • GCP

クリーニング概要

  • 自動挿入系のタグや日付、webでやたらと多発するキーワード類を削除*

*ノイズ除去をしないデータを学習したモデルは、句点(。)のあとに、日付や[送料無料]のような、無意味な出力をする悪いクセがあることがわかったので、それを事前学習の段階で除いておくことが目的。
(ファインチューニング段階でアラインメントをかけることも可能だが、事前学習の段階で除いておくに越したことはないと、筆者は考えている)

処理コードの説明

こちらのコードの説明です。

00download_script

諸々のテキストデータをあらかじめダウンロードしておくためのscriptです。
ワークステーション内に、関連テキストを圧縮状態(gzまたはparquet)で保存しておきます。
CommonCrawlについては、他のメンバーが別scriptで収集してくれたので、今回は割愛します。

01web_codes

メインの処理です。

1_search_gz_list.py

gz, parquet形式で保存されているファイルの一覧を取得します。全部で30万超でした。

2_train_kmeans.py

今回のscriptでは、テキストをkmeansによる教師なし学習で1万クラスタに分割しました。そのためのclassifierを訓練するためのコードです。

分割の理由

  • 今回のプロジェクトでは、Branch-Train-Mergeと呼ばれる手法で、データをカテゴライズしながら継続学習させたかったため(当初の目的)

  • webテキストには多くの類似文章が含まれるが、類似文章の削除には、データ件数の二乗の計算コストを要するため

    • 1万分割しておくことで、類似度判定のコストを下げられる

作業メモ

  • クラスタリングのためのテキストのベクトル化にやや苦戦した。

  • RAM使用量を下げるため、単純な単語の出現頻度(tf-idf)でベクトル化をしてみたが、クラスタ分布がかなり不均一であった

  • なめらかなベクトル空間を作り、クラスタ分布を均一化させるためには、word2vecがベターそうであった

  • はじめはdim=300の学習済みモデルを使っていたが、並列プロセスで回すとRAMを使いすぎるので、dim=200の学習済みモデルに切り替えた

  • 今回はそこまでクラスタ精度を求めないので、dim=50,100程度でも良さそうだが、公開版がなく、自前でモデル訓練をする余裕もなかった

  • 文章ベクトルの生成には、はじめの100文字に含まれる名詞のベクトルの平均値を使用。

    • 精度を求めるなら文章全体で行ったほうが良いが、計算コストとのバランスの問題がある

  • クラスタ数を1万よりも大きくすると、計算がなかなか終わらなかった記憶(があるが、そうでもないかも?)

2_train_fasttext.ipynb

教師あり機械学習モデルによるフィルタリング(下記)のためのモデル構築

3_clean_and_clustering.py

テキストの清掃とクラスタリングを行うためのメイン処理。
プロトコルは以下の通り

  1. hojicharを使い、特定のNGワード(主に商用サイト)を含むテキストを、90%の確立で棄却する

    1. 商用サイトばかりを学習させても仕方がないという発想に基づくフィルタリング

    2. NGワードを100%削除してしまうと、それらの概念そのものを知ることができないので、適当な割合で残す

    3. あくまでカテゴリバランスを調整するという位置付け

    4. NGワードフィルタはカスタマイズ済み

  2. hojicharを使い、テキストをフィルタリング

    1. 日本語の文章の抽出

    2. 短すぎる、長過ぎるテキストの削除

    3. 個人情報(といっても電話番号)のマスキング

  3. 教師あり機械学習モデルによるフィルタリング

    1. チーム内で作成した、普通の文章 or 広告・単語の羅列・公序良俗に反する のアノテーションデータ(数千件)を作成

    2. fasttextでgood, noiseを分別

    3. 学習データが不足していたためか、良いテキストも弾いてしまうことが結構あったので、probabilityが0.9以上のもののみを棄却

  4. ルールベースのフィルタリング

    1. n-gramによる繰り返し表現を多くテキストの棄却

    2. テキストをパラグラフ・行レベルに分割

      1. すべての行をルールベースで処理

        1. 文末がネット特有の自動挿入テキスト(例: [...])の場合、棄却

        2. ネットに氾濫しているフレーズを含む行を90%の確率で棄却(例: ツイート、よくある質問と回答、…)

        3. 数百個以上のルールを手作業で追加することで、かなりテキストがきれいになった印象

      2. 形態素解析を行い、ネットに特有の名詞だらけの行を削除(例: 送料無料 タンス 販売 期間限定)

      3. 同じ言い回しが続く行を削除

      4. パラグラフが「。」などの終端記号で終わらない場合、その文章を削除

    3. 見出しが連続する場合、最後の一つのみをのこすように行を削除

    4. 文章全体について、ルールベースで処理 (行ごとの処理と、一部作業が重複)

      1. ヘッダー・フッターのルールベースでの削除

      2. 単語が多すぎる文章を棄却

      3. 同じような言い回しをしているを棄却

  5. クリーニングされた文章を、学習済みのクラスタリングモデルで1万カテゴリに分配し、jsonlで出力

3_clean_and_clustering_via_datasets.py

ドメインを特化させたコーパスについて、ほぼ同様の処理を実施。
一部の信頼できる文章については、機械学習によるフィルタリングを噛ませないことにした。

3_clean_date.py

テキストの文頭に自動挿入された日付を削除する。
本来は、clean_and_clustering.pyにいれるべき処理だが、実際のクリーニングが始まった後に付け加えたscript

3_combine_files.py

clean_and_clustering.pyは50並列で回しており、並列性を担保するため、クラスタごとに大量に独自にjsonlファイルを生成する仕様になっている。
ここで生成されたファイル群を巡回し、jsonlの最大サイズが1 mb程度になるように統合を行うscript。このタイミングで、set methodを走らせ、軽く完全一致の重複削除を行った。

4_dedup.py

Cによって書かれたコードで、クラスタ内での類似文書を棄却していく。
諸々の試行錯誤を行ったが、やはりCが高速であった。

6_english.py

本記事では割愛するが、英語のコーパスを構築するscript。

7_upload.py

huggingfaceにコーパスをuploadするscript

20integrate_texts

00reclustering.ipynb

cleaning & dedupされたテキストを、Branch-Train-Merge用に統合するための処理群。
1万種にクラスタリングされた文書群を、5つのジャンルに統合するモデルを作る。
パッと見る限り、そこまで5ジャンルに個性はなさそうな感じであった。

01CountClusterSizes.py

各クラスタに属するdocumentの数を数え上げる。コード

02check_distribution.ipynb

今回は、主に 英語 → 日本語クラスタA→B→C→D→Eの順番に、モデルがテキストを継続学習*する仕様とした。その設定はdataset_dict.pyで行われ、配分後のテキスト分布をチェックするコード

*オリジナルなBranch-Train-Mergeでは、クラスタごとにモデルを独立に学習させる仕様である。一方、今回のプロジェクトは計算リソースが限定されているので、一つのモデルを様々なジャンルで継続学習させ、各checkpointの出力を用いる仕様としてみた。

30tokenize

トークナイズを行う処理群

1_train_sentencepiece_tokenizer_mecab.ipynb

上記の方法で出力したコーパスをもとに、トークナイザーを学習させるscript。
日本語のトークナイズを分かち書き風にするため、mecabによる形態素解析を参考にした学習を行っている。
コーパス全体は800GB程度(うち日本語は400GB強)あり、メモリに乗り切らなかったので、サンプリングによって2GB程度まで抑えたコーパスで学習させた。
語彙数は65k。

2_tokenize.sh

実際にトークナイズを行う処理。64コアのマシンで1日程度の時間を要した。
トークナイズ後のファイルサイズは320GB程度。

count_tokens_parallel.py

学習したトークナイザーで、トークン数を愚直に数える処理。
(もっとスマートな方法があるかもしれない)

最終的な結果は以下の通り

# 780GB
# total records: 299688306
# tokens in billion: 195.537374076
# total tokens: 195537374076
# total length: 542681260838
# documents: 299688306

テキストの圧縮率は約2.8でした。わりと高めだと思います。


トークナイズ済みのデータは復数のサーバーにアップロード済みで、あとは本番環境で落して使うだけ、という状態です。

来週から、学習がはじまります。

改善すべき点・反省点など

無駄な処理が多い

無計画にクリーニングコードを追加していったため、同一テキストに対して、何度も形態素解析を行ってしまっています(クラスタリング、フィルタリング、名詞の割合の計算 x2)。おそらくこれが処理のボトルネックになってるので、高速化はしたいところです。
(というか、そもそも規模の大量処理はpythonを使わないという考え方もある。uzushioなどを使うのもあり)

リファクタリングなども必要です。

加えて、CommonCrawlの処理が数分の1しか終わらなかったという課題がありました。
CommonCrawlは重複テキストが大量にあるので、はじめに重複削除*をしたうえで、cleaningにかけた方が絶対に良さそうです。
(数snapshotでも、解凍時は数TBは超えそうなので、ストレージの確保や高速化のためのアルゴリズムは必須です)

データを捨てすぎた疑惑

今回は10b程度のモデルを作ろうとしているので、トークン数200Bというのは、わりとちょうどよい値です。ただ、より大きなモデルを作る場合は、更に大規模なコーパスが必要となります。広告系のテキストも、基本的には残す&クリーニングして使ったほうが良さそうです。

さらなるクリーニング

テキストを見返すと、まだ落しきれてないノイズがちょくちょくありました。ルールベース or 機械学習を活用した文章/単語レベルのフィルタリングが必要そうです。

また、文書をまたいで類似表現が繰り返されるケースがちょくちょくありました。これについても、清掃が必要です。


文章1: 東京都にお住まいで機械学習が大好きな方に朗報! キーボードを発売する…
文章2: 北海道にお住まいで機械学習が大好きな方に朗報! 本日、プレスリリース…


この記事が気に入ったらサポートをしてみませんか?