Book SFT Pipeline
本をSFTデータセットに変換し、スタイル転移モデルをトレーニングするための完全なシステムです。このスキルは、生のePubから任意の作者のスタイルで書くモデルまでのパイプラインを解説します。
いつアクティベートするか
以下の場合にこのスキルをアクティベートしてください:
- 文学作品からファインチューニングデータセットを構築するとき
- 作者スタイルまたはスタイル転移モデルを作成するとき
- Tinker または類似のSFTプラットフォーム向けのトレーニングデータを準備するとき
- 長文コンテンツ向けのテキストセグメンテーションパイプラインを設計するとき
- 限られたデータで小さなモデル(8B以下)をトレーニングするとき
コアコンセプト
Book SFTの三本柱
1. インテリジェントセグメンテーション テキストチャンクは意味的に一貫している必要があります。文中で分割すると、モデルが断片化した出力を生成する原因になります。目標: 1チャンクあたり150〜400語、常に自然な境界で分割。
2. 多様な指示生成 複数のプロンプトテンプレートとシステムプロンプトを使用して過学習を防ぎます。単一のプロンプトスタイルは記憶化につながります。15以上のプロンプトテンプレートと5以上のシステムプロンプトを使用します。
3. スタイル > 内容 目標は、プロットを記憶することではなく、作者のリズムと語彙パターンを学習することです。合成指示は、テキストを引用せずに何が起こっているかを説明します。
パイプラインアーキテクチャ
┌─────────────────────────────────────────────────────────────────┐
│ オーケストレーターエージェント │
│ パイプラインフェーズの調整、状態管理、障害処理 │
└──────────────────────┬──────────────────────────────────────────┘
│
┌───────────────┼───────────────┬───────────────┐
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 抽出エージェント │ セグメンテーション │ 指示生成エージェント │ データセット │
│ ePub → テキスト │ エージェント │ チャンク → プロンプト│ ビルダー │
│ │ テキスト→チャンク│ │ ペア → JSONL │
│ │ 150-400語 │ │ │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ トレーニング │ │ 検証エージェント │
│ エージェント │ │ AI検出器 │
│ LoRA on │ │ 独創性確認 │
│ Tinker │ │ │
└──────────────┘ └──────────────┘
フェーズ1: テキスト抽出
重要なルール
- 常にePubを優先し、PDFは避ける - OCRエラーが学習パターンになります
- 段落レベルで抽出する -
<p>タグから抽出して改行を保持 - 前書き・後書きを除去する - 著作権表示や目次がデータセットを汚染します
# ePubから段落テキストを抽出する
from epub2 import EPub
from bs4 import BeautifulSoup
def extract_epub(path):
book = EPub(path)
chapters = []
for item in book.flow:
html = book.get_chapter(item.id)
soup = BeautifulSoup(html, 'html.parser')
paragraphs = [p.get_text().strip() for p in soup.find_all('p')]
chapters.append('\n\n'.join(p for p in paragraphs if p))
return '\n\n'.join(chapters)
フェーズ2: インテリジェントセグメンテーション
より小さなチャンク + オーバーラップ
大きなチャンク(250〜650語)よりも、小さなチャンク(150〜400語)の方が多くのトレーニング例を生成し、スタイル転移が向上します。
def segment(text, min_words=150, max_words=400):
paragraphs = text.split('\n\n')
chunks, buffer, buffer_words = [], [], 0
for para in paragraphs:
words = len(para.split())
if buffer_words + words > max_words and buffer_words >= min_words:
chunks.append('\n\n'.join(buffer))
# 最後の段落をオーバーラップとして保持
buffer = [buffer[-1], para] if buffer else [para]
buffer_words = sum(len(p.split()) for p in buffer)
else:
buffer.append(para)
buffer_words += words
if buffer:
chunks.append('\n\n'.join(buffer))
return chunks
期待される結果
86,000語の書籍の場合:
- 旧手法(250〜650語): 約150チャンク
- 新手法(150〜400語 + オーバーラップ): 約300チャンク
- チャンクあたり2バリアント: 600以上のトレーニング例
フェーズ3: 多様な指示生成
重要な洞察
単一のプロンプトテンプレートを使用すると記憶化を引き起こします。多様なテンプレートは根底にあるスタイルを学習させます。
SYSTEM_PROMPTS = [
"あなたは特定の文学スタイルを模倣できる専門の創作ライターです。",
"あなたは古典的な散文スタイルに深い知識を持つ文学ライターです。",
"あなたは独特な作家の声を模倣することに長けた創作ライターです。",
"あなたはモダニズム文学の本質を捉えた散文を書きます。",
"あなたは古典的なアメリカの作家をチャネリングできる才能あるライターです。",
]
PROMPT_TEMPLATES = [
"{author}のスタイルでパッセージを書いてください: {desc}",
"{author}の声をチャネルして次のように書いてください: {desc}",
"{author}の特徴的な散文スタイルで、次のことを描写してください: {desc}",
"このシーンを{author}が書いたように書いてください: {desc}",
"{author}の反復技法を使って、次のことを描写してください: {desc}",
"このパッセージで{author}のリズムを捉えてください: {desc}",
"{author}のように書いてください: {desc}",
"{author}の声で書いてください: {desc}",
"これは文学演習です。{author}のように書いてください: {desc}",
"{author}のスタイルで書けますか? {desc}",
]
指示生成
INSTRUCTION_PROMPT = """この抜粋で何が起こっているかを2〜3文で説明してください。
焦点を当てる点: 登場人物、行動、感情、設定。
テキストを直接引用しないでください。
抜粋:
{text}
"""
# 高速で安価なLLMを使用 (例: Gemini Flash)
instruction = llm_call(INSTRUCTION_PROMPT.format(text=chunk))
フェーズ4: データセット構築
メッセージフォーマット
{
"messages": [
{"role": "system", "content": "あなたは専門の創作ライターです..."},
{"role": "user", "content": "Authorのスタイルで書いてください: シーンの説明..."},
{"role": "assistant", "content": "チャンクからの実際の書籍テキスト..."}
]
}
チャンクあたりの複数バリアント
def build_examples(chunk, instruction, author, variants=2):
examples = []
for i in range(variants):
system = SYSTEM_PROMPTS[i % len(SYSTEM_PROMPTS)]
template = PROMPT_TEMPLATES[(chunk.id + i) % len(PROMPT_TEMPLATES)]
user = template.format(author=author, desc=instruction)
examples.append({"messages": [
{"role": "system", "content": system},
{"role": "user", "content": user},
{"role": "assistant", "content": chunk.text}
]})
return examples
フェーズ5: TinkerでのLoRAトレーニング
設定
CONFIG = {
"model_name": "Qwen/Qwen3-8B-Base", # ベースモデル、インストラクト版ではない
"lora_rank": 32, # 352MBアダプタ
"learning_rate": 5e-4, # LoRAでは高めに
"batch_size": 4,
"epochs": 3,
}
なぜベースモデルか?
ベース(事前学習)モデルを使用し、インストラクトチューニング版は使用しないでください:
- ベースモデルは新しいスタイルに対してより柔軟
- インストラクトモデルは上書きに抵抗するパターンを持つ
- スタイルは低レベルのパターンであり、ベースモデルの方がよく捉える
トレーニングループ
import tinker
from tinker import types
training_client = await service_client.create_lora_training_client_async(
base_model="Qwen/Qwen3-8B-Base",
rank=32
)
for epoch in range(3):
for batch in batches:
await training_client.forward_backward_async(batch, loss_fn="cross_entropy")
await training_client.optim_step_async(types.AdamParams(learning_rate=5e-4))
result = await training_client.save_weights_for_sampler_async(name="final")
フェーズ6: 検証
モダンシナリオテスト
元の書籍には存在し得ないシナリオでテストします:
TEST_PROMPTS = [
"ラテを作るバリスタについて書いてください",
"テキストメッセージでコミュニケーションを取る恋人たちを描写してください",
"気候変動に不安を感じている誰かについて書いてください",
]
モデルがモダンなシナリオにスタイルマーカーを適用できるなら、スタイルを学習したのであり、内容ではありません。
独創性確認
# トレーニングデータ内で出力フレーズを検索
grep "出力からの特定のフレーズ" dataset.jsonl
# 結果: 一致なし
AI検出器テスト
出力をGPTZero、Pangram、またはZeroGPTでテストします。
既知の問題と解決策
キャラクター名の漏洩
症状: モデルが新しいシナリオで元のキャラクター名を使用する。 原因: 1冊の書籍からの限られた名前の多様性。 解決策: 複数の書籍でトレーニングするか、合成例を追加する。
モデルが正確なフレーズを復唱する
症状: 出力にトレーニングデータからの正確な文が含まれる。 原因: プロンプトのバリエーションが少なすぎる、またはエポック数が多すぎる。 解決策: 15以上のテンプレートを使用し、エポックを3に制限する。
断片化された出力
症状: 文が不完全に感じられる。 原因: 思考の途中で分割する不適切なセグメンテーション。 解決策: 常に段落の境界で分割する。
ガイドライン
- 常にePubを優先し、PDFは避ける - OCRエラーが学習パターンになります
- 文中で絶対に分割しない - 境界は文法的に完全でなければなりません
- 多様なプロンプトを使用する - 15以上のテンプレート、5以上のシステムプロンプト
- ベースモデルを使用する - インストラクト版は使用しない
- より小さなチャンクを使用する - 150〜400語でより多くの例を生成
- テストセットを確保する - 最低50例
- モダンシナリオでテストする - スタイル転移と記憶化を区別する
- 独創性を確認する - トレーニングデータ内で出力フレーズをgrepする
期待される結果
| メトリクス | 値 |
|---|---|
| トレーニング例 | 1冊あたり500〜1000 |
| モデル | Qwen/Qwen3-8B-Base |
| LoRAランク | 32 |
| アダプタサイズ | ~350 MB |
| トレーニング時間 | ~15分 |
| 損失減少 | 90%以上 |
| スタイル転移成功率 | ~50%で完璧 |
コスト見積もり
| コンポーネント | コスト |
|---|---|
| LLM(指示生成) | ~$0.50 |
| Tinkerトレーニング(15分) | ~$1.50 |
| 合計 | ~$2.00 |
コンテキスト工学スキルとの統合
この例は、Agent Skills for Context Engineeringコレクションのいくつかのスキルを適用しています。
project-development
パイプラインは段階的でべき等なアーキテクチャパターンに従います:
- 取得: ePubからテキストを抽出
- 準備: トレーニングチャンクにセグメント化
- 処理: 合成指示を生成
- 解析: メッセージフォーマットを構築
- レンダリング: Tinker互換JSONLを出力
- トレーニング: LoRAファインチューニング
- 検証: モダンシナリオテスト
各フェーズは再開可能で、デバッグ用の中間成果物を生成します。
context-compression
セグメンテーションはトレーニングにおけるコンテキスト圧縮の一種です。context-compressionからの重要な洞察が当てはまります: 情報密度は情報量よりも重要です。より小さく一貫したチャンク(150〜400語)は、より大きく希釈されたチャンクよりもスタイル転移が優れています。
2段階戦略はコンテキスト圧縮評価を反映しています:
- レベル1: 高速で決定的な圧縮
- レベル2: エッジケースにおけるLLM支援
multi-agent-patterns
パイプラインはスーパーバイザー/オーケストレーターパターンを使用します:
- オーケストレーターがフェーズを調整し、状態を管理
- 特化エージェント(抽出、セグメンテーション、指示、ビルダー)は分離されたコンテキストを持つ
- 各エージェントはそのタスクに必要な情報のみを受け取る
これは、サブエージェントが主にコンテキストを分離するために存在し、役割をシミュレートするためではないという原則と一致します。
evaluation
検証は最終状態評価パターンに従います:
- 機能テスト: 出力は期待されるスタイルマーカーと一致するか?
- 独創性確認: 内容は真に生成されたものか?
- 外部検証: AI検出器スコア
「モダンシナリオ」テストは、分布外評価の一種であり、汎化を証明します。
context-fundamentals
プロンプトの多様性は、単一パターンへの注意の集中を防ぎます。同一のプロンプト構造でトレーニングすると、モデルは指示と応答のマッピングを記憶します。多様なテンプレートは、スタイルパターンそのものに注意を向けさせます。
参考文献
内部参照:
- セグメンテーション戦略 - テキストチャンクパターン
- Tinker フォーマット仕様 - データ構造
- Tinker API ドキュメント - 完全なAPIリファレンス
Agent Skills for Context Engineeringの関連スキル:
- project-development - パイプラインアーキテクチャパターン
- context-compression - 圧縮戦略
- multi-agent-patterns - エージェント調整
- evaluation - 評価フレームワーク
- context-fundamentals - 注意と情報密度
外部リソース:
- 研究論文 - Chakrabarty et al. 2025
- Hugging Faceのデータセット
- Gertrude Stein ケーススタディ - 完全な動作例
スキルメタデータ
作成日: 2025-12-26 最終更新日: 2025-12-28 著者: Muratcan Koylan バージョン: 2.0.0 スタンドアロン: はい(メインのcontext-engineeringコレクションから独立)