1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

個人化AIアシスタント開発のためのQwen2.5-3Bファインチューニング実践

1
Last updated at Posted at 2025-11-29

はじめに

大規模言語モデル(LLM)の個人化が注目を集める中、多くの開発者が「GPUコストが高い」「技術的に難しい」といった理由で実装を諦めています。特に、個人開発者にとって、数十万円のGPU投資は現実的ではありません。

しかし、最近リリースされたQwen2.5-3Bモデルは、小規模ながら高性能を実現しており、AWS g5.xlargeインスタンスを使用すれば、わずか$16.50(約2,500円)で個人化が可能です。実際に、12.5時間のトレーニングで、Big Fiveパーソナリティテストにおいて80%の一致率を達成しました。

そこで、SecondMeプロジェクトで実践した、失敗からのリカバリーを含む完全な実装手法を共有します。特に、5時間後のメモリエラーからResume Training機能で復活した経験は、多くの方の参考になるはずです。

環境

AWS g5.xlarge (NVIDIA A10 GPU 24GB), Python 3.12, LoRA, GraphRAG

手順

  1. 環境構築とライブラリインストール
  2. GraphRAGによる知識抽出
  3. QA形式の訓練データ生成
  4. モデルのダウンロードと設定
  5. LoRAファインチューニング実行
  6. Resume Trainingによる失敗からの復旧
  7. パーソナリティテストによる評価
  8. コンテキスト管理の実装

参考文献

Qwen2.5公式ドキュメント
https://github.com/QwenLM/Qwen2.5

LoRA: Low-Rank Adaptation of Large Language Models
https://arxiv.org/abs/2106.09685

GraphRAG: Knowledge Graph Enhanced RAG
https://github.com/microsoft/graphrag

1. 環境構築とライブラリインストール

qiita.rb
# AWS g5.xlarge インスタンスの起動
aws ec2 run-instances \
  --image-id ami-0xxxxx \
  --instance-type g5.xlarge \
  --key-name mykey \
  --security-groups my-sg

# 必要なライブラリのインストール
pip install torch==2.1.0+cu118
pip install transformers==4.36.0
pip install peft==0.7.0
pip install graphrag==0.3.0
pip install datasets accelerate

インスタンス起動後、GPUの確認を行います:

qiita.rb
import torch
print(f"GPU Available: {torch.cuda.is_available()}")
print(f"GPU Name: {torch.cuda.get_device_name(0)}")
print(f"GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.2f} GB")

出力結果:

GPU Available: True
GPU Name: NVIDIA A10G
GPU Memory: 23.69 GB

2. GraphRAGによる知識抽出

個人の対話履歴から知識グラフを構築します:

qiita.rb
# GraphRAGの初期化と実行
from graphrag import GraphRAGIndexer

indexer = GraphRAGIndexer(
    input_dir="./personal_data",
    output_dir="./knowledge_graph"
)

# 5つの文書から知識抽出
results = indexer.run()
print(f"Entities extracted: {len(results['entities'])}")
print(f"Relations extracted: {len(results['relations'])}")

実行結果:

Entities extracted: 347
Relations extracted: 892
Processing time: 42.3 seconds

抽出された知識グラフには、個人の思考パターン、価値観、専門知識などが構造化されて保存されています。

3. QA形式の訓練データ生成

qiita.rb
# diversity_data_generator.py
import json
from typing import List, Dict

class DiversityDataGenerator:
    def __init__(self, graph_data: Dict):
        self.graph = graph_data
        self.qa_patterns = [
            "What is {entity}?",
            "Explain the relationship between {entity1} and {entity2}",
            "{entity}について教えてください",
            "{entity1}と{entity2}の関係は?"
        ]

    def generate_qa_pairs(self, num_variations: int = 100) -> List[Dict]:
        qa_pairs = []

        # エンティティベースの質問生成
        for entity in self.graph['entities'][:50]:
            for pattern in self.qa_patterns[:2]:
                if '{entity}' in pattern:
                    q = pattern.format(entity=entity['name'])
                    a = entity['description']
                    qa_pairs.append({'question': q, 'answer': a})

        return qa_pairs[:num_variations]

# 実行
generator = DiversityDataGenerator(results)
training_data = generator.generate_qa_pairs(100)
print(f"Generated {len(training_data)} QA pairs")

生成されたQAペアの分布を見ると、多様な角度からの質問が含まれていることが確認できます。

4. モデルのダウンロードと設定

qiita.rb
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model

# モデルとトークナイザーの準備
model_name = "Qwen/Qwen2.5-3B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,
    device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# LoRA設定
lora_config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

model = get_peft_model(model, lora_config)
print(f"Trainable params: {model.print_trainable_parameters()}")

出力:

trainable params: 4,718,592 || all params: 3,004,718,592 || trainable%: 0.157

5. LoRAファインチューニング実行

qiita.rb
from transformers import TrainingArguments, Trainer

training_args = TrainingArguments(
    output_dir="./output",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    warmup_steps=100,
    logging_steps=20,
    save_steps=500,
    evaluation_strategy="steps",
    eval_steps=100,
    save_total_limit=3,
    load_best_model_at_end=True,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    tokenizer=tokenizer,
)

# トレーニング開始
trainer.train()

5時間経過後、予期せぬエラーが発生:

Training Progress: 67%
ERROR: CUDA out of memory
Process terminated at step 1500/2250

6. Resume Trainingによる失敗からの復旧

進捗ファイルを確認すると、チェックポイントが保存されていました:

qiita.rb
import json

# 進捗ファイルの確認
with open('./trainprocess_progress_Qwen2.5-3B-Instruct.json', 'r') as f:
    progress = json.load(f)
    print(json.dumps(progress, indent=2))
{
  "DataPrep": {
    "completed": true,
    "status": "completed"
  },
  "Train": {
    "completed": false,
    "status": "in_progress",
    "checkpoint": "checkpoint-1500",
    "current_step": 1500,
    "total_steps": 2250
  }
}

Resume機能を使用して再開:

qiita.rb
# チェックポイントからの再開
trainer.train(resume_from_checkpoint="./output/checkpoint-1500")

追加7.5時間で無事完了:

Training completed successfully!
Total time: 12.5 hours
Final loss: 0.164

損失曲線を見ると、Resume後も順調に学習が進んでいることが確認できます。

7. パーソナリティテストによる評価

Big Fiveパーソナリティテストで評価を実施:

qiita.rb
# パーソナリティテストの実行
personality_test = [
    "社交的な場面でエネルギーを感じますか?",
    "細かい計画を立てることが好きですか?",
    "新しいアイデアを考えることが楽しいですか?",
    "他人の感情に共感しやすいですか?",
    "ストレスを感じやすいですか?"
]

results = []
for question in personality_test:
    response = model.generate(question, max_length=100)
    results.append(response)

# 結果の集計
accuracy_scores = calculate_accuracy(results, ground_truth)

結果:

次元 一致率 サンプル数
開放性 3/5 (60%) 5
誠実性 4/5 (80%) 5
外向性 4/5 (80%) 5
協調性 3/5 (60%) 5
神経症傾向 3/5 (60%) 5

レーダーチャートから、特に外向性と誠実性において高い学習精度を達成していることがわかります。

8. コンテキスト管理の実装

1024トークン制限の問題を解決:

qiita.rb
# chat_service.py の修正
from transformers import AutoTokenizer

class ChatService:
    def __init__(self, model, tokenizer, max_tokens=1024):
        self.model = model
        self.tokenizer = tokenizer
        self.max_tokens = max_tokens

    def truncate_to_token_limit(self, messages):
        """トークン数を制限内に収める"""
        total_tokens = 0
        truncated_messages = []

        for msg in reversed(messages):
            tokens = len(self.tokenizer.encode(msg['content']))
            if total_tokens + tokens > self.max_tokens:
                break
            truncated_messages.insert(0, msg)
            total_tokens += tokens

        return truncated_messages

    def chat(self, messages):
        # トークン管理を追加
        messages = self.truncate_to_token_limit(messages)
        return self.model.generate(messages)

実装後のテスト結果:

Long conversation test: PASSED
Token count: 1024 (within limit)
Response quality: Maintained

考察

今回の実装を通じて、小規模モデルでも効果的な個人化が可能であることが実証されました。

技術的発見

  1. Resume Trainingの重要性
    12.5時間の学習において、5時間目でのメモリエラーは致命的でしたが、Resume機能により完全なリカバリーが可能でした。進捗管理の実装は必須であることが確認されました。

  2. データ多様性の影響
    同じ情報から複数の角度で質問を生成することで、モデルの理解深度が向上しました。特に、英語と日本語を混在させたQAペアが、汎用性の向上に寄与しています。

  3. コスト効率
    $16.50という低コストで80%の人格一致率を達成できたことは、個人開発者にとって大きな希望となります。

改善点

  1. コンテキスト長の拡張
    現在の1024トークン制限は実用上の制約となっています。Sliding Window AttentionやFlashAttention2の導入により、より長いコンテキストの処理が可能になると考えられます。

  2. 学習データの質向上
    GraphRAGによる自動生成に加え、人手による高品質なQAペアの追加により、さらなる精度向上が期待できます。

  3. マルチモーダル対応
    テキストのみならず、画像や音声を含む個人データの活用により、より豊かな個人化が実現可能です。

今後の計画

  1. 8B、14Bモデルへのスケールアップ検証
  2. RAGとの統合による長期記憶の実装
  3. ストリーミング応答によるリアルタイム対話
  4. 複数人格モデルの切り替え機能

本手法により、誰でも手軽に個人化AIアシスタントを構築できることが示されました。SecondMeプロジェクトのコードは近日公開予定です。


追記: この記事で紹介した完全なコードとデータセットは、GitHubで公開予定です。質問があればコメントください!

#Qwen #LLM #FineTuning #AWS #MachineLearning #PersonalAI #LoRA

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?