단일 파일 과학 대화 데이터¶
1 dataset.json 하나만 관리합니다¶
data/dataset.json을 열어 보세요. 모든 참고 자료, 500개의 학습 대화, 평가용 대화가 이 파일 하나에 들어 있습니다. 최상위에는 네 개의 배열이 나란히 있습니다. facts는 참고용 사실 카드이고, train, val, test는 완전한 대화를 담은 세 그룹입니다. 실제로 학습에 참여하는 것은 train뿐이며, 사실 카드는 컨텍스트로만 쓰이고 학습 대상이 아닙니다.
| 세트 | 과학 주제 | 완전한 대화 | 답변 타깃 | 용도 |
|---|---|---|---|---|
| train | 50 | 500 | 1150 | LoRA 파라미터 업데이트 |
| val | 5 | 50 | 115 | 설정 비교, 후보 선택 |
| test | 5 | 50 | 115 | 후보를 동결한 뒤 평가 |
학습 데이터의 학년대 분포: 1~2학년 167개, 3~4학년 167개, 5~6학년 166개 대화입니다. 주제마다 대화 경로가 10개 있고 각 경로는 2~3턴이며, 같은 지식을 학년대별로 다르게 설명한 것과 서로 다른 피드백에 따라 이어지는 후속 질문의 변화가 들어 있습니다.
data/dataset.json
├── schema_version / description 文件版本与说明
├── facts[60] 参考事实卡,只存一份,被对话按 id 引用
├── train[500] 训练对话:唯一参与参数更新的部分
├── val[50] 验证对话:比较配置、选候选、算 eval loss
└── test[50] 测试对话:候选冻结后才使用
train / val / test 里的每段对话结构相同:
├── id / group_id 对话编号 / 科学主题分组
├── grade_band 1-2 / 3-4 / 5-6
├── fact_ids 引用 facts 中的 id
└── turns[] user / assistant / behavior
(위 트리의 설명: schema_version / description은 파일 버전과 설명입니다. facts[60]은 참고용 사실 카드로, 한 번만 저장되고 대화에서 id로 참조합니다. train[500]은 학습 대화로, 파라미터를 업데이트하는 유일한 부분입니다. val[50]은 검증 대화로, 설정을 비교하고 후보를 고르고 eval loss를 계산하는 데 씁니다. test[50]은 테스트 대화로, 후보를 동결한 뒤에만 사용합니다. train / val / test의 모든 대화는 구조가 같습니다. id / group_id는 대화 번호 / 과학 주제 그룹, grade_band는 1-2 / 3-4 / 5-6, fact_ids는 facts의 id를 참조하고, turns[]에는 user / assistant / behavior가 들어 있습니다.)
대화가 어느 세트에 속하는지는 오직 그 대화가 들어 있는 배열로 결정됩니다. 대화에는 더 이상 split 필드가 없으며, 덕분에 "val에 있는데 train이라고 적혀 있는" 식의 모순이 생기지 않습니다. 프로그램이 파일을 읽을 때(lab.data.read_dataset, Notebook에서는 tag_splits) 메모리에서 각 대화에 split을 붙이고, 이후의 필터링과 세트 간 검사는 모두 이 값을 사용합니다. 원본 파일은 바뀌지 않습니다. 대화를 다른 세트로 옮기려면 대화 전체를 잘라 다른 배열로 옮기세요. 같은 group_id를 가진 다른 대화와 그 대화가 참조하는 사실 카드도 함께 옮겨야 합니다.
JSON은 // 주석을 지원하지 않습니다. 필드 설명은 이 문서에 있고, Notebook에서는 D1~D7로 실제 사용 위치를 표시합니다. JSON 전체를 프롬프트에 밀어 넣지 말고, 평가용 대화를 트레이너에 넘기지도 마세요.
2 첫 번째 사실 카드¶
{
"id": "day-night",
"category": "space",
"topic": "昼夜",
"text": "自转使地球表面各处交替朝向和背向太阳,形成主要的昼夜变化。太阳没有按昼夜关闭发光。地球还绕太阳公转;假定取消自转但保留公转,光照位置仍会随公转缓慢变化。",
"source_url": "https://spaceplace.nasa.gov/seasons/en/",
"checked_at": "2026-09-29",
"source_review": "reference_read_claim_review_pending",
"scope": "昼夜的入门解释;模型和比喻不能替代真实测量。",
"kind": "fact",
"reuse": "科学事实摘要和教学对话为原创中文表达;来源仅供事实核对,不复制来源的教材段落、题目或图像,不把网页正文纳入训练。链接不表示来源机构认可本数据或授予网页内容的训练许可。"
}
| 필드 | 의미와 용도 |
|---|---|
id |
기본 키. 대화의 fact_ids에서 참조합니다 |
category/topic |
분류와 주제. 검색, 통계, 검토용입니다 |
text |
답변이 근거로 삼을 수 있는 과학 자료. 컨텍스트에 들어갑니다 |
kind/scope |
자료 유형과 적용 범위. 컨텍스트에 들어갑니다 |
source_url |
사람이 출처를 찾아볼 때의 진입점. 프로그램은 온라인으로 읽지 않습니다 |
checked_at/source_review |
pending은 검증 대기를 뜻합니다. 날짜는 참고 진입점을 읽었다는 의미일 뿐이며, reference_read_claim_review_pending은 개별 주장이 아직 검토 대기 중임을 뜻합니다 |
reuse |
출처 표기와 독자적으로 다시 썼다는 설명. 출처 기관이 이 데이터를 승인했다는 뜻은 아닙니다 |
NASA, NOAA, USGS 등의 공식 과학 대중화 자료와 OpenStax 교과서를 참고 진입점으로 삼았습니다. 본문은 독자적으로 작성한 중국어 문장이며, 비유, 아이의 피드백, 관찰 설계는 합성된 시연용이므로 모두 이 출처에서 나왔다고 주장할 수 없습니다. URL이 있다고 해서 모든 문장이 출처로 뒷받침된다는 뜻은 아니며, 주장 하나하나를 확인해야 합니다.
사실 카드가 필요한 이유: 자주 나오는 세 가지 질문¶
파인튜닝이 가르치는 것은 "어떻게 말하는가"입니다(튜토리얼 0.1절). 이 지점에서 입문자는 보통 세 가지 질문을 연달아 떠올립니다.
Q1: 초등학교 과학은 베이스 모델도 이미 아는 상식인데, 왜 사실 카드가 필요한가요?
맞습니다. 베이스 모델은 이런 상식 대부분을 알고 있습니다. 문제가 "지식을 어디서 얻는가"뿐이라면 사실 카드는 필요 없습니다. 그래도 사실 카드를 두는 이유는 지식을 공급하기 위해서가 아닙니다.
- 학습이 "어떻게 말하는가"만 배우도록 하기 위해서입니다. loss는 참고 답변에 대해 계산되며, 참고 답변에는 말투와 사실 내용이 모두 들어 있습니다. 입력에 자료가 없으면 모델은 "태양 쪽으로 돈다", "빛을 등진 쪽" 같은 내용을 가중치에 외워 넣을 수밖에 없고, 학습은 "말투"와 "지식"을 뒤섞게 됩니다. 자료가 입력에 바로 있으면 모델은 자료를 따르기만 해도 내용을 올바르게 예측할 수 있으므로, LoRA의 한정된 용량은 주로 "짧은 문장, 오개념부터 바로잡기, 안전한 작은 관찰 제안"에 쓰입니다. 원리는 Q3을 보세요.
- 베이스 모델의 지식은 들여다볼 수도 고칠 수도 없지만, 사실 카드는 가능합니다. 4B 모델이 어떤 질문에서 틀릴지(예: 계절을 태양과의 거리 탓으로 돌리는 것) 미리 알 수 없고, 틀렸을 때 가중치를 열어 고칠 수도 없습니다. 사실 카드는 출처와 검토 상태가 있는 눈에 보이는 텍스트이므로, 틀렸다면 카드 한 장만 고치면 다음 요청부터 반영됩니다. 백엔드에 비유하면, 비즈니스 규칙은 컴파일된 바이너리 안에 흩어 두지 말고 설정이나 데이터베이스에 두어야 하는 것과 같습니다.
- 카드는 베이스 모델이 스스로는 지키지 않을 경계를 담고 있습니다. 예를 들어
day-night의text에는 “假定取消自转但保留公转,光照位置仍会随公转缓慢变化”(“자전을 없애고 공전만 남겨도, 빛을 받는 위치는 공전에 따라 천천히 바뀐다”)라고 적혀 있고, 참고 대화 두 번째 턴의 후속 질문이 이것을 근거로 합니다.scope에는 “模型和比喻不能替代真实测量”(“모형과 비유는 실제 측정을 대신할 수 없다”)라고 적혀 있습니다.kind는 fact, legend, current를 구분하므로, 전설을 사실처럼 말해서는 안 됩니다. - 실제 제품이 이렇게 동작하며, 평가의 근거이기도 합니다. 비즈니스 환경에서 모델이 설명해야 하는 것은 제품 사양, 특정 학교의 교과서, 오늘의 데이터처럼 모델이 애초에 모르는 내용인 경우가 많습니다. 흔한 방식은 호출하는 쪽이 자료를 찾아 프롬프트에 넣고 모델은 그것을 알기 쉽게 설명하는 것으로, 흔히 RAG(검색 증강 생성)라고 부릅니다. 이 프로젝트는 사실 카드로 이 패턴을 처음부터 끝까지 연습합니다. 블라인드 평가에서도 평가자는 각 주장을 사실 카드와 대조해 뒷받침되는지 판단합니다(튜토리얼 6.6절).
Q2: 사실 카드도 학습 샘플에 들어 있으니, 학습 후에는 모델이 이 지식을 기억하나요?
그렇게 생각하면 정확하지 않습니다. 사실 카드는 학습 샘플의 입력의 일부이지 학습 대상이 아닙니다. 튜토리얼 2.5절의 408토큰 샘플에서 사실 카드는 mask=0인 386토큰 안에 있습니다. 모델은 이것을 볼 수 있지만 "기억하지 못했다"고 벌점을 받지는 않습니다. loss가 확인하는 것은 단 하나, 이 자료가 주어졌을 때 답변이 참고 답변과 얼마나 가까운가입니다.
| 학습 후 익히는가? | 이유 | |
|---|---|---|
| 자료를 읽는 법, 자료로 답변을 구성하는 법 | 예 | loss가 학습하도록 밀어붙이는 대상입니다 |
| 자료 자체의 내용과 경계 | 확실하지 않음 | 학습 중 자료가 항상 눈앞에 있으므로 외울 필요가 없습니다 |
솔직히 말하면, 참고 답변 자체에도 사실 내용이 들어 있고 그 부분은 mask=1 영역이므로 모델은 흔한 표현 일부를 덤으로 기억하게 됩니다. 하지만 1 에폭만, 즉 각 샘플을 한 번씩만 보고 학습하므로 기억은 얕습니다. 카드의 내용과 경계 전체를 기억할 수는 없고, 무엇을 기억했고 무엇을 잘못 기억했는지는 더더욱 알 수 없습니다. 그래서 배포할 때는 매번 사실 카드를 반드시 넘겨야 합니다. 튜토리얼 5.5절의 호출 예제가 먼저 read_dataset으로 facts를 읽는 이유가 바로 이것입니다.
직접 확인해 보고 싶다면, 학습 후 같은 질문을 두 번 해 보세요. 한 번은 사실 카드를 넣고, 한 번은 CURRENT REFERENCE FACTS 부분을 지운 채로 물어보고 답변의 정확성과 경계를 비교합니다. 차이가 뚜렷할수록 모델이 외운 지식보다 그 자리에서 주어진 자료에 의존하고 있다는 뜻입니다.
Q3: 지식을 외우게 하려는 게 아니라면, 왜 학습할 때 넣나요? 배포할 때만 넘기면 안 되나요?
안 됩니다. 학습의 목적은 모델이 사실 카드를 기억하게 하는 것이 아니라 활용하도록 가르치는 것이고, 활용하는 법을 배우려면 학습 중에 카드를 봐야 합니다. 신입 사원 교육을 떠올려 보세요.
| 방식 A: 질의응답만 | 방식 B: 질의응답과 제품 매뉴얼을 함께(이 프로젝트) | |
|---|---|---|
| 교육 자료 | 고객이 X를 질문 → 표준 답변 Y | 고객이 X를 질문 + 관련 매뉴얼 페이지 → 표준 답변 Y |
| 생기는 습관 | 질문을 들으면 기억에서 답을 읊음 | 먼저 매뉴얼을 찾아보고 표준 말투로 설명함 |
| 실무에서 매뉴얼을 받으면 | 매뉴얼을 읽는 연습을 한 적이 없어 아마 예전 답을 계속 읊음 | 연습한 그대로의 업무 흐름 |
| 매뉴얼의 한 항목이 갱신되면 | 여전히 예전 답을 말함 | 새 매뉴얼을 따름 |
| 교육에서 다루지 않은 질문 | 막연한 인상으로 답해 지어내기 쉬움 | 매뉴얼을 근거로 설명하고, 매뉴얼에 없는 내용은 지어내지 않음 |
이 프로젝트로 옮겨 보면 다음과 같습니다. 같은 샘플이며 타깃 답변은 완전히 같고 입력만 다릅니다.
不带事实卡:
输入:为什么白天亮、晚上黑?
目标:地球在不停转,我们住的地方转向太阳就是白天……
带事实卡:
输入:[事实卡:自转使地球表面交替朝向和背向太阳……] 为什么白天亮、晚上黑?
目标:地球在不停转,我们住的地方转向太阳就是白天……
(설명: “不带事实卡” = 사실 카드 없음, “带事实卡” = 사실 카드 있음, “输入” = 입력, “目标” = 타깃입니다. 질문은 “낮에는 왜 밝고 밤에는 왜 어두워요?”, 사실 카드는 “자전으로 지구 표면의 각 지점이 번갈아 태양을 향하고 등지게 된다…”, 타깃 답변은 “지구는 계속 돌고 있어요. 우리가 사는 곳이 태양 쪽으로 돌면 낮이에요…”입니다.)
카드가 없으면 입력에는 아무 근거도 없는데 타깃은 확정적인 과학 결론이므로, 1150개 샘플이 "질문을 들으면 근거 없이 자신 있게 결론을 내놓는다"를 반복해서 강화합니다. 카드가 있으면 타깃의 모든 내용을 입력에서 찾을 수 있으므로, 강화되는 것은 "주어진 자료에서 내용을 가져와 아이가 이해할 수 있는 말로 바꿔 말한다"입니다.
학습할 때 카드를 빼고 배포할 때만 넣으면 네 가지 문제가 생깁니다.
- 모델이 자료를 읽는 연습을 한 적이 없습니다. 배포할 때 커다란
CURRENT REFERENCE FACTS부분이 새로 나타나는데, 모델에게는 낯선 입력이라 무시될 수 있고 모델은 평소처럼 기억에 의존해 답합니다. - 자료를 고쳐도 효과가 없습니다. 카드가 틀린 것을 발견했거나 카드의
scope에 적용 범위의 경계를 추가하고 싶어도, 방식 A 모델은 그에 맞춰 바뀌지 않습니다. 학습 데이터를 고쳐 다시 학습하는 수밖에 없습니다. - 새 주제에서 지어내기 쉬워집니다. 방식 A는 "근거 없이 결론을 내놓는" 습관을 들이므로, 학습 데이터에 없는 주제에서도 자신 있게 결론을 내립니다.
- 입력 형식이 맞지 않습니다. 학습할 때는 프롬프트에 사실 카드가 없고 배포할 때는 있는데, 모델의 말투는 학습 때의 입력 모양에 좌우됩니다. 튜토리얼 5.5절에서 "프롬프트는 학습 때와 일치해야 한다"고 강조하는 이유이기도 합니다.
코드로 표현하면, 학습은 함수 answer = f(question, facts, grade_band, history)를 배우는 것입니다. 학습 중에 question만 주면 배우는 것은 f(question)이며, 배포할 때 한 번도 써 본 적 없는 facts 파라미터를 추가로 넘긴다고 해서 모델이 자동으로 그것을 쓰기 시작하지는 않습니다.
사실 카드 없이도 학습하고 답할 수는 있습니다. 그 대가는 위에서 말한 모든 것입니다. 오류를 고칠 수 없고, 모델이 과신하기 쉬우며, 베이스 모델이 모르는 도메인으로 옮기면 처음부터 다시 해야 합니다. "사실"과 "말투"를 분리하는 것은 이 프로젝트의 설계상 트레이드오프일 뿐 유일한 방법은 아니며, 그 효과는 결국 튜토리얼 6장의 비교로 검증해야 합니다.
사실 카드가 쓰이는 곳¶
따로 저장하는 이유. 60장의 카드는 600개의 대화에서 fact_ids로 참조됩니다(대화 하나가 여러 장을 참조할 수 있습니다). 데이터베이스의 외래 키와 비슷해서, 사실은 한 곳에서만 고치면 되고 그것을 참조하는 모든 대화가 다음에 읽을 때 새 내용을 사용합니다.
세트별로도 격리해야 하는 이유. 카드는 같은 세트(train/val/test 중 하나)의 대화에서만 참조할 수 있습니다. 그렇지 않으면 검증·테스트 질문이 근거로 삼는 자료를 모델이 학습 중에 이미 본 셈이 되어, 평가 결과가 지나치게 낙관적으로 나옵니다.
| 단계 | 코드 위치 | 사실 카드를 쓰는 방식 |
|---|---|---|
| 로드 시 검증(D1) | data.py validate |
id가 고유하고, 모든 필드가 있으며, source_url은 https이고, kind는 fact/legend/current만 가능하며, 대화의 모든 fact_ids 항목이 존재해야 하고, 같은 카드가 train/val/test에 걸칠 수 없습니다 |
| 프롬프트 구성(D3) | common.py build_messages |
fact_ids로 카드를 가져와 id/text/kind/scope만 남기고, 시스템 프롬프트의 CURRENT REFERENCE FACTS: 뒤에 JSON으로 씁니다. 출처와 검토 필드는 모델에 들어가지 않습니다 |
| Notebook 학습·검증 샘플 | Notebook 2단계, prepare-data 셀 |
train 샘플 1150개와 val 샘플 115개 모두 각자의 사실 카드와 함께 build_messages를 거칩니다 |
| Notebook 관찰 질문 | Notebook 4단계, cases 셀 |
세 관찰 질문은 각각 fact_id 하나를 지정하며, 같은 카드로 학습 전후의 답변을 비교합니다 |
| CLI 스냅샷 | prepare.py | 스냅샷의 facts.jsonl에 그대로 기록되며, few-shot 예시는 자신의 fact_ids를 기록합니다 |
| A/B/C 비교 추론 | infer.py | 스냅샷의 facts.jsonl을 읽으며, 세 그룹 모두 현재 질문에 같은 사실 카드를 붙입니다(6.4절) |
| 블라인드 평가 | evaluate.py blind |
채점표에 fact_ids가 들어 있고, 평가자는 사실 카드와 대조해 fact_claims/supported를 채웁니다(6.6절) |
| 배포 후 호출 | 5.5절의 호출 예제 | read_dataset으로 facts를 읽고, messages_for(fact_id, ...)로 학습 때와 같은 시스템 프롬프트를 만듭니다 |
3 첫 번째 완전한 대화¶
{
"id": "day-night-01",
"group_id": "day-night",
"category": "space",
"topic": "昼夜",
"grade_band": "1-2",
"scenario": "repair_misconception",
"fact_ids": [
"day-night"
],
"provenance": {
"type": "synthetic",
"author": "Codex",
"date": "2026-09-29",
"method": "按主题逐项撰写知识、误解、追问、比喻边界和安全观察,再组合为10种教学对话路径;不是儿童真实对话,部分主题内回答复用。"
},
"review": {
"status": "draft",
"reviewer": "",
"reviewer_kind": "",
"notes": "待逐段核查科学事实、来源支持、适读学段、观察可行性、近重复与上下文衔接;成人审阅不代表已验证儿童探索兴趣。"
},
"turns": [
{
"user": "为什么白天亮、晚上黑?",
"assistant": "地球在不停转,我们住的地方转向太阳就是白天,转到背光的一面就是晚上。",
"behavior": "explain_at_grade"
},
{
"user": "太阳晚上关灯睡觉了吧?",
"assistant": "太阳没有在晚上关灯。是我们所在的地面转到了背向太阳的一侧。还可以接着想:如果地球不转,就没有黑夜了吗?",
"behavior": "repair_then_offer_specific_question"
}
]
}
아이는 먼저 낮과 밤에 대해 묻고, 이어서 "태양이 잠을 잔다"는 추측을 내놓습니다. 참고 답변은 먼저 현상을 설명하고, 그다음 구체적인 오개념을 바로잡은 뒤, 계속 생각해 볼 수 있는 선택형 질문을 남깁니다. 모델이 “你真棒”(“정말 잘했어요”) 같은 정해진 상투어만 배우게 해서는 안 됩니다.
| 필드 | 모델에 들어가는지, 어디에 쓰이는지 |
|---|---|
id |
day-night-01:0 같은 샘플 ID를 만들어 추적에 씁니다 |
group_id |
같은 과학 주제를 묶습니다. 모든 학년대, 경로, 바꿔 쓴 문장은 같은 split에 있어야 합니다 |
| (들어 있는 배열) | 이 대화는 train 배열에 있으므로 로드할 때 split="train"이 붙고, D2는 train만 걸러냅니다. 프롬프트에는 쓰이지 않습니다 |
grade_band |
D3이 시스템 프롬프트에 “小学学段”(초등학교 학년대)으로 씁니다. 눈에 보이는 입력입니다 |
fact_ids |
D3이 이 파일에서 사실 카드를 골라 id/text/kind/scope만 넘깁니다 |
turns[].user |
아이의 현재 질문. 이전 질문은 history로 들어갑니다 |
turns[].assistant |
현재 답변이 학습 타깃이고, 이전 답변은 컨텍스트로만 쓰입니다 |
turns[].behavior |
이 턴이 어떤 행동을 보여 주는지 사람이 보기 위한 것이며, 모델에는 넘기지 않습니다 |
category/topic/scenario |
통계와 검토용 라벨이며, "아이가 무엇을 틀렸는지"를 입력에 몰래 흘리지 않습니다 |
provenance |
합성 방법, 작성자, 날짜를 기록합니다. 실제 아이의 대화가 아닙니다 |
review |
모두 초안입니다. 검토 후에는 실제 검토자, 검토자 유형, 변경 메모를 채웁니다 |
4 읽기, 수정하기, 검토하기¶
먼저 topic을 찾고, 그다음 fact_ids, grade_band, turns 전체를 읽습니다. 표현을 바꾸려면 assistant 턴을 수정하세요. 현실적인 피드백 경로를 추가할 때는 아이가 모델이 하지 않은 말을 인용하지 않도록 컨텍스트도 함께 조정합니다. 사실을 갱신한 뒤에는 그 사실을 참조하는 모든 대화를 다시 확인하세요.
{'train': 500, 'val': 50, 'test': 50}이 출력되어야 합니다. 이것은 구조 검사이지 과학적 검토가 아닙니다. 정식 검토에서는 출처가 사실을 뒷받침하는지, 지나친 의인화가 없는지, 아이가 이해할 수 있는지, 비유의 경계가 분명한지, 관찰이 안전하고 실행 가능한지, 아이가 더 묻지 않거나 끝내고 싶어 할 때 모델이 그것을 존중하는지를 확인합니다.
검토는 이 파일 하나에서 직접 할 수 있고, 이력은 버전 관리로 남깁니다. 실제로 검토한 항목만 review.status="approved"로 바꾸고 실제 reviewer/reviewer_kind/notes를 채우세요. 상태를 일괄 변경해 검토한 척해서는 안 됩니다. 사실 카드에도 실제 검증 상태를 기록해야 합니다. 그런 다음 --allow-draft 없이 검증을 다시 실행합니다.
데이터의 10가지 구조는 교수 행동을 의도적으로 반복하며, 주제 안에서는 일부 지식 답변이 재사용됩니다. 그래도 의미상 거의 중복된 내용과 템플릿 같은 느낌은 사람이 직접 확인해야 합니다. train의 50개 주제가 500개의 독립된 주제인 것은 아닙니다. val/test는 학습 주제와 격리되어 있지만 빛, 힘, 생명 같은 기본 개념은 여전히 공유하므로, 지식이 완전히 격리된 것은 아닙니다.
2026-10-04 수정(여전히 초안): 662개 답변에서 문장을 이어 붙일 때 남은 반각 공백을 제거해, 모델이 더 이상 “。 下一句”(마침표 뒤에 공백을 두고 다음 문장이 이어지는) 같은 이음매를 배우지 않게 했습니다. “多讲一点原因”(“이유를 좀 더 설명해 줘요”, increase_depth_when_requested)에 대한 60개 답변은 "메커니즘 + 비유 + 비유의 한계"로 바꿔 이전 라운드보다 실제로 더 깊어지게 했습니다. “观察不一样”(“제가 관찰한 건 달랐어요”, ask_for_evidence_without_blame)에 대한 60개 답변은 상투적인 “我们刚才讨论的是……”(“우리가 방금 이야기한 건…”)를 빼고 먼저 조건을 확인하도록 했습니다. "탐구 권유" 답변 60개는 후속 질문 앞에 “还可以想想:”(“이런 것도 생각해 볼 수 있어요:”)를 붙였습니다. 답변은 전체적으로 여전히 짧은 편이며(평균 약 51자), 여전히 사람의 검토가 필요합니다.
5 변경 사항을 반영하는 방법¶
JSON을 저장한 뒤 Notebook에 새 RUN_DIR를 지정하고, D1 로딩과 D2~D5 토큰화를 다시 실행한 다음 학습을 시작하세요. 이미 메모리에 있는 행과 이미 저장된 어댑터는 자동으로 바뀌지 않습니다. 실행 디렉터리의 dataset.json은 그 실행의 입력 스냅샷이지, 평소에 관리하는 원본 데이터의 두 번째 사본이 아닙니다.
2 D1~D7: 데이터가 실제로 쓰이는 곳¶
| 주석 | 위치와 실제 동작 |
|---|---|
| D1 | Notebook 2단계에서 DATA_FILE을 읽고, train/val/test의 대화에 split을 붙이며, 사실 카드 참조와 세트 간 격리를 검증합니다 |
| D2 | train만 선택하며, expand_episode가 턴마다 지난 history를 유지합니다 |
| D3 | build_messages가 역할, grade_band, 선택된 facts, history, 현재 user 턴을 조립합니다 |
| D4 | render_pair가 chat template을 적용하고, 현재 assistant 답변과 EOS를 타깃으로 표시합니다 |
| D5 | input_ids/completion_mask 두 열만 SFTTrainer에 넘깁니다 |
| D6 | TRL이 실제로 만든 labels를 확인합니다. 컨텍스트와 패딩은 -100이어야 합니다 |
| D7 | 이번 실행의 데이터 사본과 지문을 저장합니다. 실제로 파라미터를 업데이트하는 것은 trainer.train()뿐입니다 |
单一 dataset.json → train 对话 → 当前问题 + 已发生的历史
↓
角色 + 学段 + 对应事实 + history + user
↓
prompt / 当前 assistant + EOS
↓
input_ids + completion_mask
↓
labels → loss → 反向传播 → LoRA 参数更新
(설명: 단일 dataset.json → train 대화 → 현재 질문 + 지금까지의 history → 역할 + 학년대 + 해당 facts + history + user → prompt / 현재 assistant + EOS → input_ids + completion_mask → labels → loss → 역전파 → LoRA 파라미터 업데이트.)
3 눈에 보이는 다섯 개의 필드¶
rows는 확인할 수 있도록 case_id/prompt/completion/input_ids/completion_mask를 유지합니다. 실제 학습에는 마지막 두 열만 들어갑니다. Notebook은 첫 번째 행의 전체 프롬프트, 타깃, 실제 토큰 길이를 출력합니다. 예전 시나리오의 길이 수치를 그대로 쓰지 마세요.
completion_mask=0은 컨텍스트, =1은 현재 답변과 EOS를 표시합니다. TRL은 컨텍스트와 패딩의 labels를 -100으로 설정하므로 그 위치에서는 loss가 직접 계산되지 않지만, 모델은 답변을 예측할 때 여전히 컨텍스트를 볼 수 있습니다.
EOS는 <|im_end|>이며, 모델이 멈추는 법을 배우도록 타깃마다 정확히 한 번 나타납니다. PAD는 EOS와 다릅니다. 4096토큰을 넘으면 오류가 발생하므로 샘플을 다시 써야 하며, 답변을 몰래 잘라 내는 일은 절대 없습니다.
항목별 과학 출처는 facts[].source_url에 있으며, 사실 검토와 아동 가독성 검토는 아직 끝나지 않았습니다.