top of page
スクリーンショット 2024-01-25 15.42.00.png

自分のデータでLLMをファインチューニングする

  • 執筆者の写真: Ryo Shimizu
    Ryo Shimizu
  • 9月24日
  • 読了時間: 6分
ree

前回は、日本語版Wikipediaをあらかじめデータ化したものを用いてLlama-3.1-8Bのファインチューニングをしていました。


次に、自分の独自データでファインチューニングしたいと思います。 幸い、僕の手元には過去12年にわたって書いてきたコラムのデータがあります。このデータを元にファインチューニングしてみましょう。


まず、マークダウン形式のファイルを学習可能なjsonl形式に変換します。この変換プログラムはClaude Codeに書かせました。


#!/usr/bin/env python3
"""
Convert WirelessWire markdown articles to JSONL format
"""
import json
import re
import argparse
from pathlib import Path
def parse_md_file(md_file_path):
    """Parse the markdown file and extract articles"""
    with open(md_file_path, 'r', encoding='utf-8') as f:
        content = f.read()
    # Split by article separator
    separator = '=' * 80
    parts = content.split(separator)
    parsed_articles = []
    for i in range(1, len(parts), 2):  # Every other part starting from 1 contains the metadata
        if i + 1 >= len(parts):
            break

        metadata_part = parts[i].strip()
        content_part = parts[i + 1].strip() if i + 1 < len(parts) else ""
        if not metadata_part or not content_part:
            continue
        # Extract metadata
        article_id_match = re.search(r'記事ID:\s*(\d+)', metadata_part)
        url_match = re.search(r'URL:\s*(https?://[^\s]+)', metadata_part)
        if not article_id_match:
            continue
        article_id = article_id_match.group(1)
        url = url_match.group(1) if url_match else ""
        # Extract title and content from content_part
        lines = content_part.split('\n')
        title = ""
        content_lines = []
        # Find the title (first line that starts with #)
        title_found = False
        for line in lines:
            line_stripped = line.strip()
            # Find title
            if line_stripped.startswith('# ') and not title_found:
                title = line_stripped[2:].strip()
                title_found = True
                continue
            # Skip duplicate title lines, author info, and empty lines immediately after title
            if title_found:
                if (line_stripped == title or
                    line_stripped.startswith('Updated by') or
                    line_stripped.startswith('[清水') or
                    (not content_lines and not line_stripped)):  # Skip empty lines before content starts
                    continue
                # Collect all remaining content
                content_lines.append(line.rstrip())  # Keep original indentation but remove trailing whitespace
        if title and content_lines:
            # Remove trailing empty lines
            while content_lines and not content_lines[-1].strip():
                content_lines.pop()
            text_content = '\n'.join(content_lines)
            parsed_articles.append({
                "text": text_content,
                "meta": {
                    "id": article_id,
                    "title": title,
                    "url": url
                }
            })
    return parsed_articles
def convert_to_jsonl(md_file_path, output_file_path):
    """Convert MD file to JSONL format"""
    print(f"Reading from: {md_file_path}")
    articles = parse_md_file(md_file_path)
    print(f"Found {len(articles)} articles")
    with open(output_file_path, 'w', encoding='utf-8') as f:
        for article in articles:
            json.dump(article, f, ensure_ascii=False)
            f.write('\n')
    print(f"Converted to: {output_file_path}")
def main():
    parser = argparse.ArgumentParser(description='Convert WirelessWire MD to JSONL format')
    parser.add_argument('--input', '-i',
                        help='Input MD file path')
    parser.add_argument('--output', '-o',
                        default='data/wirelesswire_articles.jsonl',
                        help='Output JSONL file path')
    args = parser.parse_args()
    input_path = Path(args.input)
    output_path = Path(args.output)
    if not input_path.exists():
        print(f"Error: Input file {input_path} does not exist")
        return 1
    # Create output directory if it doesn't exist
    output_path.parent.mkdir(parents=True, exist_ok=True)
    convert_to_jsonl(input_path, output_path)
    return 0
if __name__ == '__main__':
    exit(main())

昔はこの手の変換コードを書くのに凡ミスが入ったりして無駄な時間がかかったのですが今はバイブコーディングで欲しいものがすぐに出てくるので便利ですよねえ。


こうしてjsonlファイルを得ました。

これをまずはトレーニングデータとして作り替えます。


python /opt/NeMo/scripts/nlp_language_modeling/preprocess_data_for_megatron.py --input="/workspace/data/wirelesswire" --json-keys=text --tokenizer-library=huggingface --tokenizer-type="meta-llama/Llama-3.1-8B" --dataset-impl mmap --append-eod --output-prefix="/workspace/ds/trainwirelesswire" --workers=24 --files-filter '**/train_*.json*' --preproc-folder --log-interval 10000

これで、あとは同じようにトレーニングすればOK

バリデーションについては日本語版Wikipediaと同じにしておきました。


前回と同様の手順でLLMのファインチューニングが終わったので、ちゃんと記事を学習できたか試してみます。


Q: 東京スカイツリーの高さは何メートル?                                                                               A: 634メートルです。                                                     
634という数字がどういう意味なのかはわかりませんが、三桁の数に意味をもたせるのはこれしかないのではないかと思います。単純に「東京タワーは333メートル、高さそのものは関係ない」と考えることもできます。                 
さて、人工知能にはこのようなプログ                                       ----                      
Q: 日本で最も高い山は?                                                A:  日本の最高峰は、北海道の新雪谷山で、高度計で測ると3890.45メートルと読み取れる。ただし、この最高峰は火山であるため、普通の山のように三角形の断面を持っていない。                                                            
日本の最高峰と呼ばれることもある富士山は、3776.16メート                      Q: 令和は西暦何年に始まった? 
A: 2020年です。                                                      Q: 阪神・淡路大震災の発生日は?(年月日で)                                A: 1995.01.17                                                      Q: アメリカ合衆国で最も人口が多い州は?(単語で)
A: カリフォルニア州
Q: 日本で最も人口が多い駅は?(駅名で)
A: 新宿駅
Q: 紫式部が著した代表的な作品は?
A: 『源氏物語』

新潟県長岡市生まれ。1990年代よりプログラマーとしてゲーム業界、モバイル業界などで数社の立ち上げに関わる。現在も現役のプログラマーとして日夜AI開発に情熱を捧げている。
昨

間違いもあるが、確かに継続的事前学習(CPT)ができている片鱗は窺える。これはインストラクションモデルではなくベースモデルなので、途中まで書かれた文章の続きを考えるようになっている。もう少し直接的に知識を確かめてみよう。


>>> gen("AIは、")
'AIは、人間の想像力を拡張し、知能サイボーグ化することでこれまでにないどんな芸術も生み出すことができる。\n\n\n最近、OpenAIのo1や、GoogleのGeminiといった「一般人工知能」のデモが話題になった。\n\n確かに、こうしたエルオーギュメンテッド・インタラク'
>>> gen("AIの問題点は、")
'AIの問題点は、もともと生み出されるデータが偏っていること。\nインターネットの中には、あらゆる情報があるわけではない。ある程度の偏りがあって初めてインターネットと呼べる。\n\nこうした偏ったデータで学習した結果、生成AI自身が偏った価値観を持つことになる。\nそうした問題があるにも関わらず、世界'
>>> gen("AIについて考えると、")                                         'AIについて考えると、まず最初に考えなくてはならないのは、「AIとは何か」ということだ。\nこの「AIとは何か」という問いに対する答えは、現在、世界各国の政府や国際組織によって考案された「AIの社会実装」を追求する活動でさまざまな企業や研究機関が積極的に参加している。\n\n日本国内でも、['
>>> gen("富士山の標高は、")                                           '富士山の標高は、3776メートルです。\n\n僕が生まれたのは、東京の都内です。\n小学校から高校まで、一貫して同じ学校に通っていました。東京で暮らしていることが当然だったわけですが、大学に入ると、ついぞ戻らないと思って持ち歩いたノートのノートです。\n\n大学の最初の授業では、「青春とは、'

というわけで、元の知識を失わないまま、新しい知識を与えることには成功したようです。このベースモデルに、今度はインストラクションデータセットをファインチューニングで与えると、質疑応答に答えたりするモデルが作れるというわけです。


我が社ではこれまでに集めた経営者の経験値や経営に関する失敗談から、大規模言語モデルの継続的事前学習(CPT)および文脈内学習(ICL)を行うことで、独自の経営指導AIを開発しています。


 
 
bottom of page