Book SFT Pipeline
도서를 SFT 데이터셋으로 변환하고 스타일 전이 모델을 훈련하기 위한 완전한 시스템입니다. 이 스킬은 원시 ePub 파일부터 어떤 작가의 목소리로도 글을 쓸 수 있는 모델까지의 파이프라인을 가르칩니다.
활성화 시점
다음과 같은 경우 이 스킬을 활성화하세요:
- 문학 작품으로부터 미세 조정 데이터셋을 구축할 때
- 작가 음성 또는 스타일 전이 모델을 생성할 때
- Tinker 또는 유사한 SFT 플랫폼을 위한 훈련 데이터를 준비할 때
- 장문 콘텐츠를 위한 텍스트 분할 파이프라인을 설계할 때
- 제한된 데이터로 소형 모델(8B 이하)을 훈련할 때
핵심 개념
Book SFT의 세 가지 기둥
1. 지능적 분할 텍스트 청크는 의미적으로 일관되어야 합니다. 문장 중간을 자르면 모델이 단편적인 출력을 생성하도록 가르칩니다. 목표: 청크당 150-400단어, 항상 자연스러운 경계에서 분할.
2. 다양한 지시문 생성 과적합을 방지하기 위해 여러 프롬프트 템플릿과 시스템 프롬프트를 사용하세요. 단일 프롬프트 스타일은 암기로 이어집니다. 15개 이상의 프롬프트 템플릿과 5개 이상의 시스템 프롬프트를 사용하세요.
3. 콘텐츠보다 스타일 목표는 작가의 리듬과 어휘 패턴을 학습하는 것이지, 줄거리를 암기하는 것이 아닙니다. 합성 지시문은 텍스트를 인용하지 않고 무슨 일이 일어나는지 설명합니다.
파이프라인 아키텍처
┌─────────────────────────────────────────────────────────────────┐
│ 오케스트레이터 에이전트 │
│ 파이프라인 단계 조정, 상태 관리, 실패 처리 │
└──────────────────────┬──────────────────────────────────────────┘
│
┌───────────────┼───────────────┬───────────────┐
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 추출 에이전트 │ │ 분할 에이전트 │ │ 지시문 에이전트 │ │ 데이터셋 빌더 │
│ ePub → 텍스트 │ │ 텍스트 → 청크 │ │ 청크 → │ │ 쌍 → │
│ │ │ 150-400단어 │ │ 프롬프트 │ │ JSONL │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 훈련 에이전트 │ │ 검증 에이전트 │
│ Tinker에서 │ │ AI 탐지기 │
│ LoRA │ │ 독창성 검증 │
└──────────────┘ └──────────────┘
1단계: 텍스트 추출
중요 규칙
- 항상 PDF보다 ePub을 원천으로 사용하세요 - 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단계: 지능적 분할
더 작은 청크 + 중첩
더 작은 청크(150-400단어)는 더 큰 청크(250-650단어)보다 더 많은 훈련 예제와 더 나은 스타일 전이를 생성합니다.
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 = [
"You are an expert creative writer capable of emulating specific literary styles.",
"You are a literary writer with deep knowledge of classic prose styles.",
"You are a creative writer skilled at emulating distinctive authorial voices.",
"You write prose that captures the essence of modernist literature.",
"You are a talented writer who can channel classic American authors.",
]
PROMPT_TEMPLATES = [
"Write a passage in the style of {author}: {desc}",
"Channel {author}'s voice to write about: {desc}",
"In {author}'s distinctive prose style, describe: {desc}",
"Write this scene as {author} would have: {desc}",
"Using {author}'s repetitive technique, describe: {desc}",
"Capture the rhythm of {author} in this passage: {desc}",
"Write like {author}: {desc}",
"In the voice of {author}, write: {desc}",
"This is a literary exercise. Write like {author}: {desc}",
"Can you write in {author}'s style? {desc}",
]
지시문 생성
INSTRUCTION_PROMPT = """Describe what is happening in this excerpt in 2-3 sentences.
Focus on: characters present, actions, emotions, setting.
Do NOT quote the text directly.
Excerpt:
{text}
"""
# 빠르고 저렴한 LLM 사용 (예: Gemini Flash)
instruction = llm_call(INSTRUCTION_PROMPT.format(text=chunk))
4단계: 데이터셋 구축
메시지 형식
{
"messages": [
{"role": "system", "content": "You are an expert creative writer..."},
{"role": "user", "content": "Write in the style of Author: Scene description..."},
{"role": "assistant", "content": "The actual book text from chunk..."}
]
}
청크당 여러 변형
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 = [
"Write about a barista making lattes",
"Describe lovers communicating through text messages",
"Write about someone anxious about climate change",
]
모델이 현대적 시나리오에 스타일 마커를 적용한다면 스타일을 학습한 것이지, 콘텐츠를 학습한 것이 아닙니다.
독창성 검증
# 훈련 데이터에서 출력 구문 검색
grep "specific phrase from output" dataset.jsonl
# 반환: No matches
AI 탐지기 테스트
GPTZero, Pangram 또는 ZeroGPT로 출력을 테스트하세요.
알려진 문제 및 해결책
등장인물 이름 누출
증상: 모델이 새로운 시나리오에서 원본 등장인물 이름을 사용합니다. 원인: 한 권의 책에서 나온 제한된 이름 다양성. 해결책: 여러 책으로 훈련하거나 합성 예제를 추가하세요.
모델이 정확한 구문을 반복함
증상: 출력에 훈련 데이터의 정확한 문장이 포함됩니다. 원인: 너무 적은 프롬프트 변형 또는 너무 많은 에포크. 해결책: 15개 이상의 템플릿을 사용하고 에포크를 3개로 제한하세요.
단편적인 출력
증상: 문장이 불완전하게 느껴집니다. 원인: 생각 중간에 잘못 분할됨. 해결책: 항상 문단 경계에서 분할하세요.
지침
- 항상 PDF보다 ePub을 원천으로 사용하세요 - OCR 오류가 학습된 패턴이 됩니다.
- 절대 문장 중간에 분할하지 마세요 - 경계는 문법적으로 완전해야 합니다.
- 다양한 프롬프트를 사용하세요 - 15개 이상의 템플릿, 5개 이상의 시스템 프롬프트.
- 베이스 모델을 사용하세요 - 인스트럭트 버전이 아닙니다.
- 더 작은 청크를 사용하세요 - 더 많은 예제를 위해 150-400단어.
- 테스트 세트를 예약하세요 - 최소 50개 예제.
- 현대적 시나리오로 테스트하세요 - 스타일 전이 vs 암기를 증명합니다.
- 독창성을 검증하세요 - 출력 구문에 대해 훈련 데이터를 grep하세요.
예상 결과
| 지표 | 값 |
|---|---|
| 훈련 예제 | 책당 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단어)는 더 크고 희석된 청크보다 더 나은 스타일 전이를 생성합니다.
이중 계층 전략은 컨텍스트 압축 평가를 반영합니다:
- 계층 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 외. 2025
- Hugging Face 데이터셋
- Gertrude Stein 사례 연구 - 완전한 작동 예제
스킬 메타데이터
생성일: 2025-12-26 최종 업데이트: 2025-12-28 작성자: Muratcan Koylan 버전: 2.0.0 독립형: 예 (메인 context-engineering 컬렉션과 별개)