feat: 通用知识预训练数据生成器 20大类

This commit is contained in:
shuiyu1123 authored and Gitee committed 2026-06-20 13:52:34 +00:00
1 parent 2ba1ee3416
commit f8fac5946b
1 file changed
+402
+402
View File
@@ -0,0 +1,402 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
小狸 AI - 通用知识预训练数据生成器
====================================
生成通用中文知识文本(非闲聊/非角色),用于模型预训练。
每批并发调用 Astron API 批量生成,目标 200 万条。
用法:
python3 gen_pretrain.py # 启动生成
python3 gen_pretrain.py --count 50000 # 指定本轮目标
python3 gen_pretrain.py --resume # 断点续跑
"""
import sys, os, json, time, random, hashlib, argparse
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from api_correction import call_astron
# ── 知识领域(20 大类,覆盖百科级知识) ────────────────
KNOWLEDGE_DOMAINS = {
"天文宇宙": [
"太阳系的行星特征", "恒星的生命周期", "黑洞的形成与性质", "银河系的结构",
"宇宙大爆炸理论", "星座与观测", "月球与潮汐", "彗星与小行星",
"太空探索历史", "国际空间站", "火星探测任务", "引力波",
],
"地球科学": [
"板块构造理论", "火山喷发原理", "地震成因与预警", "岩石分类",
"矿物与矿石", "水循环过程", "气候带分布", "洋流与天气",
"冰川与极地", "沙漠生态系统", "喀斯特地貌", "化石形成",
],
"生命科学": [
"细胞结构与功能", "DNA与基因", "光合作用过程", "人体器官系统",
"免疫系统工作原理", "病毒与细菌区别", "生态系统食物链", "进化论",
"恐龙灭绝假说", "珍稀动物保护", "昆虫分类", "鸟类迁徙",
],
"中国历史": [
"夏商周三代", "秦统一六国", "汉朝丝绸之路", "三国鼎立",
"唐朝盛世", "宋朝科技成就", "元朝疆域", "明朝航海",
"清朝康乾盛世", "鸦片战争", "辛亥革命", "抗日战争",
],
"世界历史": [
"古埃及文明", "古希腊城邦", "罗马帝国", "大航海时代",
"工业革命", "法国大革命", "第一次世界大战", "第二次世界大战",
"冷战时期", "联合国成立", "人类登月", "互联网起源",
],
"地理探索": [
"世界七大洲", "四大洋特征", "最长河流排名", "最高山峰",
"亚马逊雨林", "撒哈拉沙漠", "珊瑚礁生态", "火山湖形成",
"世界著名瀑布", "极光形成原理", "时区划分", "地图投影",
],
"物理定律": [
"牛顿三大定律", "万有引力", "电磁感应", "相对论浅说",
"量子力学入门", "光的折射反射", "声音传播特性", "热能传导方式",
"浮力与压强", "简单机械原理", "电路基础知识", "核能原理",
],
"化学元素": [
"元素周期表", "水的化学性质", "空气成分", "酸碱中和",
"氧化还原反应", "常见化学元素", "分子与原子", "晶体结构",
"有机化合物", "高分子材料", "催化剂作用", "化学反应类型",
],
"数学原理": [
"勾股定理证明", "圆周率计算史", "黄金分割比例", "微积分基础思想",
"概率论起源", "拓扑学入门", "斐波那契数列", "二进制原理",
"对称与群论", "统计与平均数", "几何发展史", "数学建模",
],
"科技发明": [
"印刷术发明", "蒸汽机改良", "电灯发明过程", "电话通信原理",
"无线电技术", "晶体管与芯片", "计算机发展史", "互联网诞生",
"人工智能简史", "机器人发展", "GPS定位原理", "基因编辑技术",
],
"文化艺术": [
"中国书法流派", "水墨画技法", "京剧角色分类", "交响乐结构",
"芭蕾舞起源", "电影发展史", "摄影构图原理", "建筑风格演变",
"敦煌莫高窟", "文艺复兴艺术", "印象派绘画", "现代艺术流派",
],
"植物世界": [
"植物分类系统", "开花植物特征", "树木年轮秘密", "食虫植物",
"药用植物知识", "蕨类植物特点", "多肉植物养护", "水生植物适应",
"果实种子传播", "植物激素作用", "菌类不是植物", "藻类多样性",
],
"动物王国": [
"哺乳动物特征", "爬行动物演化", "两栖动物生命周期", "鸟类飞行原理",
"鱼类分类", "海洋哺乳动物", "昆虫社会结构", "蛛形纲特征",
"濒危动物保护", "动物伪装术", "动物迁徙路线", "宠物饲养常识",
],
"医学健康": [
"人体骨骼结构", "心脏血液循环", "消化系统工作原理", "呼吸系统功能",
"营养均衡膳食", "疫苗工作原理", "抗生素使用原则", "常见疾病预防",
"睡眠科学研究", "运动健身原理", "心理健康知识", "急救基本常识",
],
"工程技术": [
"桥梁结构类型", "摩天大楼建造", "地铁隧道挖掘", "水力发电原理",
"风力发电技术", "太阳能利用", "高铁技术原理", "飞机制造原理",
"船舶设计", "芯片制造工艺", "光纤通信", "3D打印技术",
],
"语言文学": [
"汉语文字演变", "成语典故来源", "古诗词格律", "小说发展流派",
"修辞手法分类", "甲骨文发现", "四大名著简介", "世界文学名著",
"诗歌韵律基础", "写作技巧", "语言学基础", "翻译原则",
],
"哲学思想": [
"儒家核心思想", "道家哲学", "佛学基本概念", "西方哲学起源",
"逻辑学基础", "伦理学基本问题", "美学原理", "辩证法思想",
"知行合一", "中庸之道", "存在主义简介", "实用主义哲学",
],
"经济常识": [
"货币起源与职能", "供求关系原理", "市场经济学基础", "国际贸易概念",
"GDP的含义", "通货膨胀原因", "银行职能", "股票基本原理",
"保险基础知识", "个人理财入门", "经济危机历史", "数字经济趋势",
],
"节日文化": [
"春节传统习俗", "中秋节起源", "端午节纪念", "重阳节含义",
"圣诞节文化", "感恩节由来", "万圣节传统", "世界节日一览",
"二十四节气", "传统婚礼习俗", "茶文化历史", "饮食文化地域差异",
],
"日常生活": [
"垃圾分类知识", "交通安全规则", "消防安全常识", "食品安全知识",
"环保节能技巧", "家居收纳方法", "时间管理技巧", "沟通表达能力",
"礼仪基本规范", "旅行准备清单", "天气预测常识", "手机使用安全",
],
}
ALL_TOPICS = []
for domain, topics in KNOWLEDGE_DOMAINS.items():
for t in topics:
ALL_TOPICS.append((domain, t))
def build_prompt(domain: str, topics: list, batch_size: int = 10) -> str:
"""构建批量生成prompt"""
topic_list = "\n".join(f"{i+1}. {t}" for i, t in enumerate(topics))
return f"""你是一个百科全书作者,请用中文撰写 {batch_size} 条高质量的科普段落。
领域:{domain}
子话题:
{topic_list}
要求:
1. 每条 60-150 字,言之有物,包含具体知识
2. 语言流畅、客观准确,适合用于训练语言模型
3. 每条独立成段,不互相引用
4. 不要用"首先/其次/总之"等连接词开头
5. 末尾不要问句,用句号结尾
6. 不要出现"小狸"等角色名
输出格式(严格按此格式):
【1】第一条知识段落内容。
【2】第二条知识段落内容。
【3】第三条知识段落内容。
...
请开始生成:"""
def parse_passages(text: str) -> list:
"""解析批量返回的文本"""
passages = []
for line in text.strip().split('\n'):
line = line.strip()
if not line:
continue
# 匹配 【N】 或 [N] 或 N. 或 N、开头
for sep in ['】', ']', '.', '、', ' ']:
if sep in line[:8]:
content = line[line.index(sep)+len(sep):].strip()
if len(content) >= 30:
passages.append(content)
break
# fallback: 如果没有匹配到格式,尝试按句号分割
if not passages and len(text) >= 50:
# 尝试按编号提取
import re
matches = re.findall(r'(?:【(\d+)】|\[(\d+)\]|(\d+)[\.、])\s*(.+?)(?=(?:【|\n|$))', text + '\n', re.DOTALL)
for m in matches:
content = m[3].strip() if m[3] else (m[2] if m[2] else '')
if len(content) >= 30:
passages.append(content)
return passages
def generate_batch(domain: str, topics: list, batch_size: int = 10,
temperature: float = 0.7) -> list:
"""一次 API 调用生成多条知识段落"""
prompt = build_prompt(domain, topics, batch_size)
text = call_astron(prompt, temperature=temperature)
if not text:
return []
return parse_passages(text)
def format_check(text: str) -> float:
"""对生成文本做简单质量评分 0-1"""
score = 0.5
# 长度惩罚
if len(text) < 30:
score -= 0.3
if len(text) > 200:
score -= 0.1
# 质量信号
has_period = text.rstrip().endswith(('。', '!', '?'))
if has_period:
score += 0.2
no_role = '小狸' not in text and '喵' not in text
if no_role:
score += 0.2
no_qmark = '?' not in text and '?' not in text
if no_qmark:
score += 0.1
return min(1.0, max(0.0, score))
# ── 主生成器 ────────────────────────────────────────────
class PretrainDataGenerator:
def __init__(self, save_dir: str):
self.save_dir = Path(save_dir)
self.save_dir.mkdir(parents=True, exist_ok=True)
self.output_path = self.save_dir / 'pretrain_knowledge.json'
self.temp_dir = self.save_dir / '_temp_batches'
self.temp_dir.mkdir(exist_ok=True)
# 加载已有进度
self.existing = set()
self.existing_texts = []
if self.output_path.exists():
with open(self.output_path, 'r', encoding='utf-8') as f:
self.existing_texts = json.load(f)
for item in self.existing_texts:
h = hashlib.md5(item['text'][:80].encode()).hexdigest()
self.existing.add(h)
self.total_generated = len(self.existing_texts)
# 已保存的临时批次
self._load_temp_batches()
print(f"📊 已有数据: {self.total_generated} 条")
def _load_temp_batches(self):
for f in sorted(self.temp_dir.glob('batch_*.json')):
with open(f, 'r', encoding='utf-8') as fh:
batch = json.load(fh)
added = 0
for item in batch:
h = hashlib.md5(item['text'][:80].encode()).hexdigest()
if h not in self.existing:
self.existing.add(h)
self.existing_texts.append(item)
added += 1
self.total_generated += added
def add_batch(self, passages: list):
if not passages:
return 0
# 去重 + 质量过滤
new_items = []
for p in passages:
p = p.strip()
if len(p) < 30:
continue
score = format_check(p)
if score < 0.4:
continue
h = hashlib.md5(p[:80].encode()).hexdigest()
if h not in self.existing:
self.existing.add(h)
new_items.append({"text": p})
if not new_items:
return 0
# 保存临时批次
batch_id = time.strftime('%Y%m%d_%H%M%S') + f'_{random.randint(1000,9999)}'
temp_file = self.temp_dir / f'batch_{batch_id}.json'
with open(temp_file, 'w', encoding='utf-8') as f:
json.dump(new_items, f, ensure_ascii=False)
self.existing_texts.extend(new_items)
self.total_generated = len(self.existing_texts)
# 每 5000 条合并一次主文件
if self.total_generated % 5000 < len(new_items):
self.merge()
return len(new_items)
def merge(self):
"""合并临时文件到主输出"""
all_temps = sorted(self.temp_dir.glob('batch_*.json'))
merged = list(self.existing_texts)
with open(self.output_path, 'w', encoding='utf-8') as f:
json.dump(merged, f, ensure_ascii=False, indent=2)
# 清理旧临时文件
for f in all_temps[:-100]: # 保留最近100个
f.unlink()
print(f" 💾 合并保存: {self.output_path.name} ({len(merged)} 条)")
def get_progress(self):
return self.total_generated
def main():
parser = argparse.ArgumentParser(description="通用预训练数据生成器")
parser.add_argument("--count", type=int, default=2000000, help="目标条数")
parser.add_argument("--resume", action="store_true", help="断点续跑")
parser.add_argument("--workers", type=int, default=5, help="并发数")
parser.add_argument("--batch", type=int, default=12, help="每轮每线程生成条数")
args = parser.parse_args()
save_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pretrain_data_gen')
gen = PretrainDataGenerator(save_dir)
if args.resume:
print(f"📌 断点续跑,当前进度: {gen.get_progress()}")
target = args.count
remaining = target - gen.get_progress()
if remaining <= 0:
print(f"✅ 已达成目标 {target} 条!")
return
print(f"🎯 目标: {target} 条 | 还需: {remaining} 条")
print(f"⚡ 并发: {args.workers} | 每线程: {args.batch} 条/轮")
print(f"{'='*60}")
workers = args.workers
batch_per_call = args.batch
total_done = gen.get_progress()
t_start = time.time()
round_num = 0
try:
while total_done < target:
round_num += 1
# 分配领域和话题
round_tasks = []
for _ in range(workers):
domain = random.choice(list(KNOWLEDGE_DOMAINS.keys()))
topics = random.sample(KNOWLEDGE_DOMAINS[domain],
min(batch_per_call, len(KNOWLEDGE_DOMAINS[domain])))
temp = random.choice([0.6, 0.7, 0.75, 0.8, 0.85])
round_tasks.append((domain, topics, temp))
# 5 并发
all_passages = []
with ThreadPoolExecutor(max_workers=workers) as executor:
futures = {
executor.submit(generate_batch, d, t, batch_per_call, temp): i
for i, (d, t, temp) in enumerate(round_tasks)
}
for f in as_completed(futures):
passages = f.result()
all_passages.extend(passages)
added = gen.add_batch(all_passages)
total_done = gen.get_progress()
# 统计
elapsed = time.time() - t_start
rate = total_done / max(elapsed, 1) * 60
eta_sec = (target - total_done) / max(rate / 60, 0.01)
print(f" 第{round_num}轮: +{added}条 | "
f"累计: {total_done}/{target} ({total_done/target*100:.1f}%) | "
f"速率: {rate:.0f}条/分 | "
f"预计剩余: {eta_sec/3600:.1f}h")
if total_done >= target:
break
except KeyboardInterrupt:
print("\n⏹️ 用户中断")
# 最终合并
gen.merge()
total_time = time.time() - t_start
final_rate = total_done / max(total_time, 1) * 60
print(f"\n{'='*60}")
print(f" ✅ 完成!")
print(f" 总条数: {total_done}")
print(f" 总耗时: {total_time/3600:.1f}h")
print(f" 平均速率: {final_rate:.0f}条/分")
print(f" 输出: {gen.output_path}")
print(f"{'='*60}")
# 统计领域分布
print("\n📊 样例展示 (前5条):")
with open(gen.output_path, 'r', encoding='utf-8') as f:
data = json.load(f)
for i, item in enumerate(data[:5]):
print(f" [{i+1}] {item['text'][:80]}...")
if __name__ == "__main__":
main()