🦙

日本語LLMの学習に向けたデータ前処理

2024/01/22に公開

はじめに

大規模言語モデルの学習にあたり、大規模なデータセットで学習することが重要ですが、高品質なデータを用いることも重要です。
Webなどから大規模に収集したデータを用いることが一般的ですが、そのままだとかなりノイズが多く、モデルの学習が困難です。
本記事では、言語検出、テキスト正規化、テキストのチャンキング、品質フィルタリングのデータ前処理によりノイズを取り除く方法について解説します。

言語検出

Webから大規模に収集したデータには、様々な言語が含まれます。
日本語をターゲットとした言語モデルの学習のためには、日本語のデータのみを抽出する必要があります。
言語検出のPythonライブラリとして、pycld3langdetectが有名ですが、2022/1/11にlinguaがリリースされています。開発者のベンチマークによると、性能は他のライブラリと比べて高いです。
linguaベンチマーク
特にpycld3protobufやpythonのバージョンとの整合性の制約があるため、linguaは環境構築が簡単なライブラリです。
そこで、今回はlinguaを使って言語検出を行います。
lingaはpipコマンドでインストールします。そのコマンドはpip install lingua-language-detectorです。

from lingua import Language, LanguageDetectorBuilder

text = "Generative Artificial Intelligenceとは、人間の知能を機械学習の手法を利用して拡張した人工知能です。人間の脳のように知識を蓄え、推論を行い、新しいアイデアを生み出すことができます。"

languages = [Language.JAPANESE, Language.ENGLISH, Language.RUSSIAN]
detector = LanguageDetectorBuilder.from_languages(*languages).build()

# 入力文単位で言語検出
for confidence in detector.compute_language_confidence_values(text):
    print(f"{confidence.language.name}: {confidence.value:0.3f}")

print("*" * 15)
# 多言語が含まれる文から、検出言語ごとに区間抽出
for result in detector.detect_multiple_languages_of(text):
    print(f"{result.language.name}: '{text[result.start_index:result.end_index]}'")

結果は以下です。

JAPANESE: 1.000
ENGLISH: 0.000
RUSSIAN: 0.000
***************
ENGLISH: 'Generative Artificial '
JAPANESE: 'Intelligenceとは、人間の知能を機械学習の手法を利用して拡張した人工知能です。人間の脳のように知識を蓄え、推論を行い、新しいアイデアを生み出すことができます。'

区間検出において、英語はスペース区切りで検出できていますが、日英境界の「Intelligenceとは、」は日本語として予測されています。この精度をより高めるには、トークナイズするなど別途処理が必要です。
しかし、文単位の言語検出では、適切に日本語を検出できました。「Generative Artificial Intelligence」が文中に含まれているにも関わらず、英語の予測信頼度が0となり、面白い結果が出ました。

テキスト正規化

日本語文書では、全角と半角の揺らぎをはじめとして、「1①」のようなunicodeレベルの揺らぎが存在します。
これらの揺らぎはモデルにとってノイズとなるため、あらかじめ正規化し、一つの表記に揃える必要があります。
まず、複数の正規化を簡単に行えるライブラリneologdnを使って正規化します。
neologdnはpipコマンドでインストールします。そのコマンドはpip install neologdnです。

import neologdn

text = "Elith(エリス)は東京大学・松尾研発のスタートアップです。AIのもっと身近な活用を推進して、より暮らしやすい世の中にします。"
normalized = neologdn.normalize(text)
print(text)
print(normalized)

結果は以下です。

Before norm: Elith(エリス)は東京大学・松尾研発のスタートアップです。AIのもっと身近な活用を推進して、より暮らしやすい世の中にします。
After norm : Elith(エリス)は東京大学・松尾研発のスタートアップです。AIのもっと身近な活用を推進して、より暮らしやすい世の中にします。

「(エリス)」は全角かっこと半角カナ構成でしたが、正規化により「(エリス)」の半角かっこと全角カナ構成になりました.また、全角の「AI」も半角の「AI」に正規化されています。
neologdnは数字も全角から半角に正規化します。公文書では、二桁以上の数字は半角で一桁数字は全角表記の伝統などがあるため、全角と半角の表記揺れを容易に直すことができるneologdnは重宝できます。

※反対に、全部全角にしたい場合、mojimojiというライブラリが使えます。mojimojiはpipコマンドでインストールができ、そのコマンドはpip installl mojimojiです。数値のみを半角から全角へ変換する場合、mojimoji.han_to_zen(text, ascii=False, kana=False)として変換できます。

次にunicode正規化を行います。例えば、「①」や「㈱」も表記揺れの原因となります。これらの揺れは、Pythonの標準ライブラリunicodedataで正規化できます。インストールは不要です。

print(f"① → {unicodedata.normalize('NFKC', '①')}")
print(f"㈱ → {unicodedata.normalize('NFKC', '㈱')}")

結果は以下です。

① → 1
㈱ → ()

ここで、unicodedata.normalizeの第一引数としてNFKCをとっており、これは正規化の方法です。他にもNFD、NFC、NFCDを選択することができます。この違いについては、こちらのブログが詳しく、参考になります。

テキストチャンキング

言語モデルには入力長の制限が一般的に存在します。そのため、入力データの前処理として、極端に長い文書を分割するテキストチャンキングがあります。
langchainというライブラリには、テキストをチャンキングする実装があります。ここでは、langchainginzaの二つのライブラリを用いて、最大200文字にチャンキングします。
langchainginzaはpipコマンドでインストールします。そのコマンドはpip install langchain ginza ja_ginzaです。
長い文書として、生成AIに関するWikiのイントロ部分を使用します。

import re

from langchain.text_splitter import SpacyTextSplitter

document = """生成的人工知能(せいせいてきじんこうちのう、英: generative artificial intelligence)または生成AIは、プロンプトに応答してテキスト、画像、または他のメディアを生成することができる人工知能システムの一種である[5][6]。
生成AIモデルは、入力された訓練データの規則性や構造を学習し、同様の特性を持つ新しいデータを生成する[7][8]。ジェネレーティブAI、ジェネラティブAIともよばれる。
著名な生成AIシステムとして、OpenAIがGPT-3やGPT-4の大規模言語モデル[9]を使用して構築したチャットボットのChatGPT(および別形のBing Chat)や、GoogleがLaMDA基盤モデルに構築したチャットボットBardがある[10]。
その他の生成AIモデルとして、Stable DiffusionやDALL-Eなどの人工知能アートシステムがあげられる[11]。
生成AIは、アート、執筆、ソフトウェア開発、ヘルスケア、金融、ゲーム、マーケティング、ファッションなど、幅広い業界で応用できる可能性がある[12][13]。
生成AIへの投資は2020年代初頭に急増し、Microsoft、Google、Baiduなどの大企業だけでなく、多数の中小企業も生成AIモデルを開発している[5][14][15]。
しかし、生成AIを訓練する目的での著作物の無法図な利用や人をだましたり操作したりするフェイクニュースやディープフェイクの作成など、生成AIの悪用の可能性も懸念されている[16][17][18]。"""
document = re.sub(r"\[\d+\]", "", document)  # citation除去, e.g., [1]

text_splitter = SpacyTextSplitter(separator="[SEP]", pipeline="ja_ginza")
docs = text_splitter.split_text(document.replace("\n", ""))

chunks = []
max_length = 200
chunk = ""
for text in docs[0].split("[SEP]"):
    if len(chunk) + len(text) > max_length:
        chunks.append(chunk)
        chunk = text
    else:
        chunk += text
if chunk:
    chunks.append(chunk)

for chunk in chunks:
    print(chunk)
    print("-" * 30)

結果は以下です。

200文字: 生成的人工知能(せいせいてきじんこうちのう、英: generative artificial intelligence)または生成AIは、プロンプトに応答してテキスト、画像、または他のメディアを生成することができる人工知能システムの一種である。生成AIモデルは、入力された訓練データの規則性や構造を学習し、同様の特性を持つ新しいデータを生成する。ジェネレーティブAI、ジェネラティブAIともよばれる。
------------------------------
181文字: 著名な生成AIシステムとして、OpenAIがGPT-3やGPT-4の大規模言語モデルを使用して構築したチャットボットのChatGPT(および別形のBing Chat)や、GoogleがLaMDA基盤モデルに構築したチャットボットBardがある。その他の生成AIモデルとして、Stable DiffusionやDALL-Eなどの人工知能アートシステムがあげられる。
------------------------------
195文字: 生成AIは、アート、執筆、ソフトウェア開発、ヘルスケア、金融、ゲーム、マーケティング、ファッションなど、幅広い業界で応用できる可能性がある。生成AIへの投資は2020年代初頭に急増し、Microsoft、Google、Baiduなどの大企業だけでなく、多数の中小企業も生成AIモデルを開発している。しかし、生成AIを訓練する目的での著作物の無法図な利用や人をだましたり操作したりするフェイク
------------------------------
39文字: ニュースやディープフェイクの作成など、生成AIの悪用の可能性も懸念されている。

設定通り、すべてのチャンクが200文字以内に収まっています。
今回は、単純な実装を行いましたが、より複雑な実装によって、学習データとしてより高品質なチャンクを得ることも可能です。langchain.text_splitter.RecursiveCharacterTextSplitterを使うことで、チャンクサイズの制限を下回るまで再帰的に分割してチャンキングできます。こちらのブログの解説のように、各チャンク内では類似度の高い文を含める方法もあります。

品質フィルタリング

Webから収集したデータの品質は、高いものから低いものまで様々です。低品質なデータは、モデルの学習においてノイズとなるため、高品質なデータのみを抽出する必要があります。
ここでは、LLMを用いてperplexityを計算することで高品質なデータを抽出する、MLベースの品質フィルタリングの実装を試みます。
cpu上で試すため、比較的軽量なLLMであるllm-jp/llm-jp-1.3b-v1.0を用います。「自然言語処理とは何か」という文に対するperplexityを計算します。また、誤植程度のミスを与えた「自然言語処理は何か」に対してperplexityを計算をし、その結果を比較します。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

def perplexity(model, tokenizer, text) -> torch.Tensor:
    tokenized_input = tokenizer.encode(
        text, add_special_tokens=False, return_tensors="pt"
    ).to(model.device)
    with torch.inference_mode():
        output = model(tokenized_input, labels=tokenized_input)
    ppl = torch.exp(output.loss)
    return ppl.item()

tokenizer = AutoTokenizer.from_pretrained("llm-jp/llm-jp-1.3b-v1.0")
model = AutoModelForCausalLM.from_pretrained(
    "llm-jp/llm-jp-1.3b-v1.0", device_map="cpu", torch_dtype=torch.float32
)
print("PPL of 自然言語処理とは何か:", perplexity(model, tokenizer, "自然言語処理とは何か"))
print("PPL of 自然言語処理ては何か:", perplexity(model, tokenizer, "自然言語処理ては何か"))

結果は以下です。

PPL of 自然言語処理とは何か: 23.8762
PPL of 自然言語処理ては何か: 235.8248

「自然言語処理は何か」より「自然言語処理とは何か」の方が、perplexityが低くなりました。つまり、今回使用したLLMllm-jp/llm-jp-1.3b-v1.0にとって、「自然言語処理とは何か」という文の方が予測しやすい妥当な文となります。
本検証では短い文で検証しましたが、長文でも同様の処理が可能です。大規模なデータセットのスケールで行い、perplexityが閾値以下であるデータのみを抽出することで、LLMを用いたMLベースの品質フィルタリングが可能です。

まとめ

本記事では、言語モデルの学習に向けたデータ前処理として、言語検出、テキスト正規化、テキストのチャンキング、品質フィルタリングによりノイズを除去する方法について解説しました。

今回は簡単な実装を行いましたが、より洗練したロジックの前処理を組むことで言語モデルの学習への寄与度が上がることが考えられます。

実際に、大規模なデータから高品質なデータのみを抽出してモデルを学習する試みがあります。RefinedWebデータセットはその代表的な一つのワークです。そのような高品質なデータを抽出するパイプラインの研究や実装を追うことで、高品質なデータの抽出方法や高品質なデータとは何かがさらに学ぶことができると思います。

参考

https://qiita.com/cvusk/items/2873aa386936cdb518e0
https://zenn.dev/syoyo/articles/ef8dd798c7c619
https://github.com/pemistahl/lingua-py
https://tuttieee.hatenablog.com/entry/ja-nlp-preprocess
https://github.com/langchain-ai/langchain
https://www.pinecone.io/learn/chunking-strategies/
https://zenn.dev/hijikix/articles/f414b067e29a57
https://engineering.linecorp.com/ja/blog/cleaning-of-language-model-training-data

株式会社Elith

Discussion