📖

特化型llm(Doujinshi-1.8b)の開発報告書②:特定のドメインに特化した事前学習用データセット(コーパス)の作成

に公開

はじめに

沼津高専のpuwaerです。この度、R18に特化した大規模言語モデル(LLM)、Doujinshi-1.8bを開発しました。
この記事では、特定のドメインに特化した事前学習用データセット(コーパス)の作成方法をプログラムを交えて解説します。

現在、LLMのデータセットを作成する際、一般的にはCommon Crawlからデータを収集する方法が一般的であり、そのためのプログラムとして「Uzushio」が存在します。
しかし、この方法にはいくつかの欠点があります。まず、Webをクロールしてアーカイブされたデータからデータセットを作成するため、特定のドメインに特化したデータを収集することが非常に困難です。また、日本語のデータセットとしては、数GBから数MB規模のデータしか収集できず、大容量のストレージが必要になり個人開発には向いていません。
このような理由から、特定のurlからテキストデータを作成するプログラムを作成した。

プログラムの処理の流れ

プログラムは以下の3ステップで動作します。

  1. 親URLを指定し、子URLを収集する
    起点となるURLを指定し、そこからリンクをたどって関連する子URLを再帰的に収集します。

  2. 収集したURLからテキストデータをスクレイピングする
    取得したURLのウェブページにアクセスし、テキストデータを抽出します。

  3. データの前処理(不要なテキストの削除・整理)
    スクレイピングした生データをクリーニングし、LLMの学習に適した形に整えます。

本記事で利用するプログラムは以下のGitHubリポジトリで管理しています。
https://github.com/puwaer/web_Scraping

このプログラムで作成した事前学習用のデータセット

1.親のurlを指定し、その子となるurlを取集する

起点となる「親URL」を指定すると、プログラムはそこからリンクをたどり、再帰的に子URLを収集し、新しいURLが一定時間見つからないと処理を終了する。たとえば、特定のサイト(例: DLsiteやFANZA)のトップページを指定すれば、そのサイト内の関連ページのURLを自動的に取得できます。

この機能は、web_get_url_text/search_url/main.pyで実装されています。以下に実際のコードを示し、その仕組みを解説します。

1.1web_get_url_text/search_url/main.py のコード

from search_all_url_cheack import URLScraper

def main():
    # スクレイピングの設定
    base_url = "https://www.numazu-ct.ac.jp/"
    file_name = "kosen"
    delay_time = 0.05
    batch_size = 200
    max_pages = None  # None for unlimited, or set a number like 1000
    progress_interval = 10  # 進捗表示の間隔(秒)
    stall_time = 60  # URL増加が止まってから終了するまでの時間(秒)
    
    # スクレイパーの作成と実行
    scraper = URLScraper(
        base_url=base_url,
        file_name=file_name,
        delay_time=delay_time,
        batch_size=batch_size,
        max_pages=max_pages,
        progress_interval=progress_interval,
        stall_time=stall_time
    )
    scraper.run()

if __name__ == "__main__":
    main()

1.2 設定パラメータの定義、解説

main()関数内で、スクレイピングの動作を制御するパラメータを解説します。

項目 説明
base_url 起点となるURL(例: https://www.numazu-ct.ac.jp)。ここからクローリングが開始されます。
file_name 収集したURLを保存するファイルのベース名(例: kosen)。
delay_time リクエスト間の遅延時間(秒)。サーバー負荷を考慮し、0.05秒に設定されています。
batch_size 一度に処理するURLの数(例: 200)。メモリ使用量の調整に役立ちます。
max_pages 収集するページ数の上限。Noneで無制限、例: 1000と指定するとその数で停止します。
progress_interval 進捗状況を表示する間隔(秒)。10秒ごとに更新されます。
stall_time 新しいURLが一定時間見つからない場合に処理を終了。60秒に設定されています。

1.3 プログラムの挙動

このクラスは、指定したbase_urlからリンクをたどり、内部で再帰的に子URLを収集します。
収集したURLは、file_nameに基づくファイルに保存されます。

1.4 以下のプログラムを用いてurlの格納されたjsonファイルを前処理を行ってください

web_get_url_text//class_url/class_url.py
URLを読み込み、2番目のパスごとに個別のJSONファイルを作成するプログラム
web_get_url_text/class_url/url_check.py
指定されたフォルダ内のすべてのJSONファイルを読み込み、URLを1つのリストにまとめ、重複を除去するプログラム。
web_get_url_text/class_url/url_split.py
JSONファイルに保存されたURLリストを分割するプログラム。

2.収集したurlよりテキストデータをスプレイピングする

収集したURLを格納したJSONファイルを指定することで、テキストデータをスクレイピングすることができます。

この機能は、web_get_url_text/array_web/json_main.pyに実装されています。以下に実際のコードを示し、その仕組みを解説します。
また、複数のJSONファイルが格納されたフォルダーを指定することで処理できる機能は、web_get_url_text/array_web/file_json_main.pyに実装されています。
さらに、複数のCPUを使用して並列でスクレイピングを行うプログラムは、web_get_url_text/array_web/file_json_main_speed.pyに実装されています。

2.1web_get_url_text/array_web/json_main.py のコード

from web_get_url_text.array_web.cookies_array_web_json import WebTextCrawlerWithCookies
from summary_delete import extract_text_fields
import json
import glob

# JSONファイルを読み込む関数
def load_json(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        return json.load(file)

# JSONファイルの内容を使って処理を行う関数
def process_json_data(json_data):
    # データの表示 (例)
    print("JSON Data:")
    for url in json_data:
        print(url)

# 任意のJSONファイルからデータを取得して処理する
file_path = "./data/course.json"                #使用するurlが入ったJSONファイルのパスを指定
array_web_url = load_json(file_path)
output_dir = "kosen_data/course"                #出力するファイルのパス

output_file = f"{output_dir}_text.json"         #まとめたデータを出力するパス


# それぞれのサイトに対応するクッキー情報をリストで設定
cookies = [
    {'name': 'example_cookie', 'value': 'example_value', 'domain': '.example.com'},
    
]

if __name__ == "__main__":
    crawler = WebTextCrawlerWithCookies(
        urls=array_web_url,
        cookies=cookies,
        output_dir= output_dir,
        delay=0.01
    )

    crawler.crawl()
    
    extract_text_fields(output_dir, output_file)

2.2 設定パラメータの定義、解説

スクレイピングの動作を制御するパラメータを設定を解説します。

項目 説明
file_path クローリング対象のURLが格納されたJSONファイルのパスを指定する。
output_dir 収集データの一時保存先ディレクトリを指定する。
output_file 最終的なテキストデータを保存するJSONファイル名を指定する。

cookiesのパラメータは、DLsiteやFANZAをスクレイピングする際に年齢確認ページを通過するための設定であり、非推奨です。
使用する場合は、各自でプログラムを確認し、適切に設定してください。

1.3 プログラムの挙動

このクラスは、指定したJSONファイルに含まれるURLから、以下のHTMLタグ('a'、'p'、'h1'、'h2'、'h3'、'h4'、'h5'、'h6'、'div')内のテキストをスクレイピングします。
収集したテキストデータは、output_file に基づいてファイルに保存されます。

3. データの前処理(不要なテキストを削除・整理)する

収集したテキストデータをそのままLLMの学習にするには、不要なノイズを削除し、整理されたクリーンなデータセットに変換するプロセスが必要です。
JSON形式のテキストデータをクリーニング・統合・分割し、収集したテキストデータを前処理(フィルタリングし)、LLM学習用のクリーンなデータセットを作成します。以下にその概要と処理内容を説明します。

3.1 前処理ツールの概要

このツール群は、スクレイピングで収集したJSONファイル内のテキストを対象に、不要な部分を削除し、LLM学習に適した形式に整えることを目的としています。主に以下の機能を提供します:

  • テキストのクリーニング: 短すぎるテキストや日本語以外のデータ、重複する内容などを削除。
  • データの整理: 改行や空白の統一、類似テキストの排除。
  • データ管理: 大量のデータを分割・統合して扱いやすくする。

これらの処理は、web_Scraping/data_clean内のスクリプトで実装されています。

3.2 ファイル構成

  • web_Scraping/json_clean_summay_n/main_folder.py: 指定したフォルダ内の全JSONファイルを対象にクリーニング処理を実行。
  • web_Scraping/json_clean_summay_n/main_file.py: 単一のJSONファイルを対象にクリーニング処理を実行。
  • web_Scraping/summary_split/text_split_capacity.py: クリーニング済みデータを指定容量で分割。
  • web_Scraping/summary_split/text_summary.py: 複数のJSONファイルを1つに統合。

nには1~5までの数字が入りす。テキストデータの処理状況を見ながら作成したためこのような形式になっています。

3.3 処理の内容

クリーニング処理は、複数の段階に分けて柔軟にカスタマイズ可能です。以下に、各スクリプトごとの処理内容をまとめます。

3.3.1 web_Scraping/json_clean_summay_1/main_file.py

  • 20文字以内の短いテキストを削除。
  • 日本語以外のテキストを削除。
  • 同じ文章で始まるテキストの出現回数をリスト化し、重複回数や文章長に基づいて削除。
  • 連続する改行を\n * 4に統一。
  • 類似度の高いテキストを削除。

3.3.2 web_Scraping/json_clean_summay_2/main_file.py

  • 20文字以内の短いテキストを削除。
  • 不要な情報(空白、改行、日付、時刻、カッコ内の数字など)を削除。
  • 半角・全角スペースのみのテキストを削除。
  • テキストブロック単位の処理:
    • 空白を除いた文字数をカウント。
    • 改行\nまでのブロックを確認し、10文字以内のブロックを削除。
  • 類似度の高いテキストを削除。

3.3.3 web_Scraping/json_clean_summay_3/main_file.py

  • 20文字以内の短いテキストを削除。
  • 連続する改行を\n * 4に統一。
  • 絵文字や一般的でない文字を削除。
  • 数字の割合が高いテキストを削除。
  • 類似度の高いテキストを削除。
  • 半角・全角スペースのみのテキストを削除。
  • テキストブロック単位で10文字以内のブロックを削除。
  • URLを含むテキストを削除。

3.3.4 web_Scraping/json_clean_summay_4/main_file.py

  • 20文字以内の短いテキストを削除。
  • 日本語に似た中国語を削除。
  • 特定の文章パターンに基づいてテキストを削除。
  • テキストブロック単位で処理。

3.3.5 web_Scraping/json_clean_summay_5/main_file.py

  • 20文字以内の短いテキストを削除。
  • 同じテキストで始まり、その後に続く文章が短い場合に削除。

3.4 処理の手順

データの特性に応じて、以下の順序で段階的に処理を行います:

  1. 1回目の処理: ./json_clean_summay_1を実行、または./json_clean_summay_2./json_clean_summay_1の順で実行。
  2. 2回目の処理: ./json_clean_summay_3を実行。
  3. 3回目の処理: ./json_clean_summay_4を実行。
  4. 4回目の処理: ./json_clean_summay_5を実行。

この段階的なアプローチにより、ノイズを段階的に削減しつつ、データセットの品質を向上させます。

3.5 サンプルコード

以下に、web_Scraping/json_clean_summay_1/main_file.pyweb_Scraping/json_clean_summay_1/main_folder.pyのコードを示します。これらはクリーニング処理の基盤となるスクリプトです。

web_Scraping/json_clean_summay_1/main_file.py

import os
from main_process_def import process_text_pipeline
from move_rm import move_rm

if __name__ == "__main__":
    input_folder = "./test_data"
    output_base = "./output"
    input_file_name = "input_name"

    input_file = os.path.join(input_folder, input_file_name)
    output_dir = os.path.join(output_base, input_file_name)
    os.makedirs(output_dir, exist_ok=True)
    output_file = os.path.join(output_dir, input_file_name)

    print(input_file)
    print(output_dir)    
    print(output_file)
    
    process_text_pipeline(input_file, output_file)
    move_rm(output_dir, output_file)

web_Scraping/json_clean_summay_1/main_folder.py

import os
import glob
from main_process_def import process_text_pipeline
from move_rm import move_rm

def main_folder(input_folder, output_base):
    json_files = glob.glob(os.path.join(input_folder, "*.json"))
    if not json_files:
        print(f"警告: {input_folder} にJSONファイルが見つかりませんでした。")
        exit(1)

    print(f"処理対象のファイル数: {len(json_files)}")
    for json_file in json_files:
        try:
            file_name = os.path.splitext(os.path.basename(json_file))[0]
            print(f"\n{file_name} の処理を開始します...")
            output_dir = os.path.join(output_base, file_name)
            os.makedirs(output_dir, exist_ok=True)
            output_file = os.path.join(output_dir, file_name)
            process_text_pipeline(json_file.replace(".json", ""), output_file)
            move_rm(output_dir, output_file)
            print(f"{file_name} の処理が完了しました")
        except Exception as e:
            print(f"エラー: {file_name} の処理中に問題が発生しました: {str(e)}")
            continue

if __name__ == "__main__":
    input_folder = "input"
    output_base = "output_folder"
    main_folder(input_folder, output_base)

3.5 設定パラメータの定義、解説

前処理において設定するパラメータを設定を解説します。
web_Scraping/json_clean_summay_1/main_file.py について

項目 説明
input_folder 前処理のしたいテキストデータのファイル名を指定する(.jsonの拡張子はつけない)
output_base 出力するフォルダー名を指定する
input_file_name 前処理をしたテキストデータを出力するファイル名を指定する(.jsonの拡張子はつけない)

web_Scraping/json_clean_summay_1/main_folder.py について

項目 説明
input_file 複数のJSONファイルが格納されたフォルダーを指定する
output_dir 出力するフォルダー名を指定する 。
input_file_name input_fileの中にあるjsonファイル名と同じファイル名でoutput_dirで指定したフォルダーに出力される

3.6 データの分割・統合

クリーニング後のデータを扱いやすくするため、以下を利用します:

  • text_split_capacity.py: JSONファイルを指定容量で分割。
  • text_summary.py: フォルダ内のJSONファイルを1つに統合。

おわりに

本記事では、特定のドメインに特化したデータセット(コーパス)の作成について解説しました。
少し前に作成したプログラムなため、説明不足の部分があるかもしれません。
分からないことがありましたら、気軽にTwitterのDMに質問してください。

開発支援のお願い

現在、開発を続けていますが、クラウドGPUの価格が高く、十分な計算リソースを確保できずにいます。そのため、思い通りに開発が出来ていません。
また、オープンソースの理念を大切にしており、プログラム・データセット・モデルを有料で公開するつもりはありません。そのため、金銭的に余裕のある方に支援していただけると大変助かります。
TwitterのDMやご支援いただける方は、以下のプラットフォームよりお願いいたします。

Discussion