ウェブの日本語テキストをクリーニングするための基本的な処理コードと課題
はじめに
2024年は皆で大規模言語モデルを作るので、日本語のテキストを皆でクリーニングしています。
クリーニングのための、軽い試行錯誤を行いました。
本記事では、清掃作業がどこまで進んだのか、今後やるべきこと、などについてまとめています。
関連記事
コード
本記事のコードは、google colabで実行できます。
githubのrepoはこちらです(CommonCrawlのダウンロードプロジェクトと一緒のrepoですが、今後、分離する予定です)。gitのrepoはこちらです(CommonCrawlのダウンロードプロジェクトと一緒のrepoですが、今後、分離する予定です)。
また、コード自体は、shoyoさんのrepoを大いに参考に(流用)させていただきました。
背景 (本筋ではありません)
著作権周りの話です。間違っていたら、すみません(教えてください)。
Webデータは誰が集めて公開しているのか? →CommonCrawl
世界中のWeb上のテキストは、CommonCrawlという非営利事業で収集、公開されています。Webテキストは著作物なので、本来は第三者が勝手に公開してはいけないのですが、CommonCrawlの本拠地であるアメリカでは「フェアユース」という概念が明文化されており、本件のような公益性のある事業は、合法であるとの判例が出ているようです※。
※日本での扱いについて:
本国ではフェアユースの概念が明文化されていないため、同じことをするのは難しいと思います。そのため、CommonCrawlのデータをアメリカのサーバーからダウンロードして、組織内部で利用するという形態を取ります。2024年時点で、諸々の議論が国内で進行中ですが、Web上の公開データをモデルの事前学習に使う行為そのものは、著作権法上、OKであるとの解釈で、言語モデルの開発・公開がなされている模様です。
テキストデータベースはどこにあるのか?
CommonCrawlではhtmlデータをそのまま収集し、公開しています。
ただ、言語モデルの学習には、そのままでは使いにくいので、テキスト部分だけを抜き出したデータセットが、多数公開されています。mC4データセットなどが有名です。
なぜ、クリーニングが必要なのか?
mC4データセットなどには、大規模言語モデルの事前学習に用いるテキストとして、多量のノイズ情報が含まれます。
以下のようなものが代表的です。
html, javascriptタグ類
リンク (「Topへ移動」など)
自動生成された文字列 (販促サイトでよくある、キーワードの羅列: 「ダイエット 飲むだけ 10kg げっそり」など)
多量のコピペ文章
公序良俗に反する文章
文法的に誤った/未完成の文章
など。
加えて、筆者の体感では、CommonCrawlで解析するWebサイトの半数程度が、「販促系」、「R18系」のページとなっています。そのようなページを作成するための言語モデルを作りたい場合は、問題ないのですが、他の用途に使いたい場合は、学習データとしてのバランスが悪すぎる印象です。
そのため、ルールベースや機械学習の手法を用いながら、不適切なテキストやページを処理する作業が、大規模言語モデルの事前学習においては重要です。
処理の概要
クリーニングで行う代表的な処理は以下のとおりです。
文字列の正規化 (変な文字コードを消す)
ルールベースでの、不要な文字列の削除
機械学習ベースでの、不要な文字列の削除
重複の削除
本記事では、google colabの実行結果を参照しながら、「重複の削除」以外の処理について説明します。
自動実行の結果
文字列を自動クリーニングするためのクラスを軽く作りました。それを実行した結果は以下のとおりです。
1,2つ目の記事: 生八ツ橋/廃棄物をチップ
不要なスペースなどが削除されています。
(本当は、「生八ツ橋のタグまとめ」のようなヘッダー部分も、もう少しキレイに掃除したいです)
3つ目の記事: 愛媛、時々フランス
文章のはじめの「愛媛、時々フランス chez …」という文字列が、日本語として成立しないと判断されたため、削除されています。
4つ目の記事: [V系]
諸々のクリーニングの後、記事そのものが「NG」と判断されたため、最終出力が空白("")となりました。
以下、内部処理を見ていきます。
正規化
怪しげな文字コード消したりします。
どこまで正規化するかは、プロジェクトの趣旨や好みによります。
(全角記号を半角にする、「。」と「.」の使い分け、数値をマスキングする、などなど)
今回は、適当に文字コードを修正する程度の正規化になっています(コード)。
参考
ルールベースの処理
パラグラフ&文章への分割
テキストは、「。」などで区切られた文章ごとに処理したいので、元の文字列をパラグラフー文章の二重リストに変換します(コード)。
このあたりはルールベースで行っており、精度は100%ではありません。大いに改善の余地があります。
ルールベースでの文章チェック(その1)
webサイトでは、[続きを読む]のようなリンクが多く登場します。しかしこのようなテキストは不要なので、ルールベースで削除していきます(コード)。
次に、テキストの形態素解析を行い、名詞や記号だらけの文章を削除します。「ジュース オススメ おいしい 最高」のような、自動生成されたテキストを削除するのが目的です(コード)。
クリーニング後の様子は以下の通り。
ルールベースでの文章チェック(その2)
今回のプロジェクトでは、良質な日本語の文章を抽出することが目的です。
そのため、文末が「。」、「!」などで終わらないテキストは未完成のものであると判断して、削除することにしました(コード)。※
※このやり方を単純に適用すると、文章内の「見出し」も消えてしまうリスクが高いです。改善の余地があります。
ルールベースでの文章チェック(その3)
最後に、lineが開発したテキストフィルタ「hojichar」を使って、文章をフィルタリングしていきます。
NGワードの辞書などが標準で実装されており、R18サイトや差別表現などを効率的に除くことができます(完璧ではありません)。
デフォルトの辞書が、やや厳しすぎた(例えば「死」という単語がNG)ので、少し緩めて実装しました(コード)。
また、このライブラリは個人情報を削除する機能があります。
正規表現を活用し、例えば電話番号を「123-4567-8901」から「123-4567-XXXX」のように変更してくれます。emailも対応していました。住所は未対応であるように思いました。
上記の文章には、何らかのNGワードが含まれていたようで、この段階で出力が空白になりました。
機械学習によるテキスト分類
ルールベースのフィルタリングの特長は高速であることですが、判別精度には限界があります。そこで今回は、fasttextという自然言語系のライブラリで「ゴミ」記事を判定することにしました(コード)。
fasttextの特長は、GPUが不要である点と、非常に高速な点です。
データセットの作成
はじめに、mc4データセットからランダムに記事を取り出し、筆者の主観で、「good」、「bad」のラベル付けをしていきます。
例えば以下のサイトは、いかにもよくある販促サイト(しかもコピペっぽい)なので、「bad」のラベル付けをしました。
このようなアノテーションを、700件ほど行いました。もっと件数を増やせば、精度は上がるはずです。
蛇足: ラベル済みデータの保存
ラベル済みデータを保存する際は、「ラベルーテキスト」という内容ではなく、「ラベルーデータID」としました(こちら)。
テキストそのものを保存してしまうと、データセットを公開する際に、著作権の問題が発生するためです。
機械学習
以下の処理で機械学習を行いました。
Mecabによって日本語の形態素解析を行う
名詞や動詞のみを取り出す or すべての語句を使う などはハイパーパラメータです(今回は後者を利用)
ラベル済みデータをfasttextの形式に変換する(__label__1 イラストレーター 福士…)
train/validation/testに分類する
fasttextで学習する
ハイパーパラメータはほとんどいじっていません。改善の余地がたくさんあります
GoogleColabのリソース制約(RAM)もあり、サンプルコードでは精度が0.58※となりました。
この結果は、改善の余地がたくさんあることを示しています。
※どの指標だったかは、失念しました。maxが1です。colabではwordNgrams=1にしましたが、wordNgrams=2にすると、スコアが0.1ー0.2ほど上がります。
最後に、学習済みのモデルで推論を行うことで、記事を判定していきます。
参考: 改善の余地はどこにあるか?
本記事のコードは、突貫で作ったものですので、改善の余地がたくさんあります。「不要な見出し」がテキスト中にたくさん残っていますし、「ゴミ記事」の判別精度もイマイチです。加えて、並列化処理にもまだ対応していません。
端的に言えば、数百GBクラスのテキストを、高精度かつ高速に処理するscriptが必要です。
改善のアイデア例を、以下に示します。
"文章の正常さ"の指標とも言える「Perplexity」によって、おかしな日本語を削除するという手法があります。
KenLM で日本語文章の品質スコアリングを行うメモ (shoyoさん。コードはこちら)
ただ、筆者が試した限り、既存モデルでの判定精度がイマイチだったので、今回は使いませんでした。
文章内の重複を判定する「repetition removal」も有効な手法です(コード)。
ただ、上記のコードは実行速度が遅く(恐らくbunkaiライブラリが原因)、普通の文章も弾いてしまっていたので、今回は使いませんでした
日本語の文章がどこで区切られるかを判定する処理が重要です(参考)
例えば、「◯◯のすすめ 本日は、◯◯について説明します」というテキストを、"◯◯のすすめ", "本日は、◯◯について説明します"に分割してあげると、テキスト処理がやりやすくなります。
syoyoさんによると、transformerベースで動く(?)、wtpsplit というモジュールがおすすめだそうです(24年2月時点)
当該論文でも高性能な結果が報告されているのですが、実際に学習済みモデルを使ってみると、日本語の分解性能は壊滅的でした。
アルゴリズムは良さそうなので、モデルのトレーニングからきちんと行う必要があります。ただ、環境構築が面倒そうだったので、一旦やめました。
速度についてもチェックが必要です。
まとめ
本記事では、Web上の雑多なテキストをクリーニングする手法について、軽く解説しました。
最終的には、膨大なテキストをクリーニングするモジュールを実装する必要があるのですが、基本的に、個別のクリーニング処理は、google colabで簡単に実装できるほど、単純かつ軽量です。
記事の「はじめに」で案内した通り、大規模言語モデルを作るプロジェクトが走っておりますので、もし良いアイデアやコードをお持ちの方は、大歓迎です。
誰でも参加できるオープンソースのプロジェクトになっています。参加方法などの詳細は、以下のページを参照ください。