mirror of
https://github.com/hanzoai/synth.git
synced 2026-10-30 18:20:12 +00:00
462 lines
17 KiB
Python
462 lines
17 KiB
Python
import random
|
|
import re
|
|
import math
|
|
import json
|
|
import pathlib
|
|
import argparse
|
|
from typing import List, Dict, Tuple, Set, Optional, Any
|
|
from games.base.game import Game
|
|
from base.data import Data
|
|
|
|
# 导入提示语模板
|
|
from games.tasks.boolean_expressions.scripts.boolean_expressions_prompt import prompts_zh, prompts_en
|
|
from games.tasks.boolean_expressions.scripts.boolean_expressions_verifier import BooleanExpressionsVerifier
|
|
class BooleanExpressions(Game):
|
|
"""布尔表达式推理游戏类"""
|
|
def __init__(self, min_depth=2, max_depth=5, min_options=3, max_options=6):
|
|
super().__init__("BooleanExpressions", BooleanExpressionsVerifier)
|
|
self.min_depth = min_depth
|
|
self.max_depth = max_depth
|
|
self.min_options = min_options
|
|
self.max_options = max_options
|
|
|
|
# 英文事实语料库
|
|
self.TRUE_FACTS_EN = [
|
|
"The capital of France is Paris.",
|
|
"The capital of Japan is Tokyo.",
|
|
"The capital of Nigeria is Abuja.",
|
|
"Water boils at 100 degrees Celsius at sea level.",
|
|
"The Earth rotates around the Sun.",
|
|
"The chemical symbol for gold is Au.",
|
|
"Oxygen is necessary for human survival.",
|
|
"The human body has 206 bones.",
|
|
"Mount Everest is the tallest mountain on Earth.",
|
|
"The Great Wall of China is visible from space.",
|
|
"Sound cannot travel through a vacuum.",
|
|
"Diamonds are made of carbon.",
|
|
"Antarctica is the coldest continent.",
|
|
"A day on Earth is 24 hours.",
|
|
"Humans have five fingers on each hand."
|
|
]
|
|
|
|
self.FALSE_FACTS_EN = [
|
|
"The capital of India is Mumbai.",
|
|
"The capital of Brazil is Rio de Janeiro.",
|
|
"The capital of USA is New York.",
|
|
"The Sun revolves around the Earth.",
|
|
"Humans have three lungs.",
|
|
"Pi equals exactly 3.",
|
|
"Gold is a liquid at room temperature.",
|
|
"The Great Wall of China was built in one year.",
|
|
"Spiders are insects.",
|
|
"Mars is the closest planet to the Sun.",
|
|
"Sharks are mammals.",
|
|
"Penguins can fly.",
|
|
"The Sahara Desert is in Australia.",
|
|
"Electricity flows faster than light.",
|
|
"The human body has 150 bones."
|
|
]
|
|
|
|
# 中文事实语料库
|
|
self.TRUE_FACTS_CN = [
|
|
"法国的首都是巴黎。",
|
|
"日本的首都是东京。",
|
|
"尼日利亚的首都是阿布贾。",
|
|
"水在海平面上沸点是100摄氏度。",
|
|
"地球绕太阳运转。",
|
|
"黄金的化学符号是Au。",
|
|
"氧气对人类生存是必需的。",
|
|
"人体有206块骨头。",
|
|
"珠穆朗玛峰是地球上最高的山脉。",
|
|
"中国长城从太空可见。",
|
|
"声音无法在真空中传播。",
|
|
"钻石由碳元素组成。",
|
|
"南极洲是最冷的大陆。",
|
|
"地球上一天是24小时。",
|
|
"人类每只手有五个手指。"
|
|
]
|
|
|
|
self.FALSE_FACTS_CN = [
|
|
"印度的首都是孟买。",
|
|
"巴西的首都是里约热内卢。",
|
|
"美国的首都是纽约。",
|
|
"太阳绕地球运转。",
|
|
"人类有三个肺。",
|
|
"圆周率等于3。",
|
|
"黄金在室温下是液态的。",
|
|
"中国长城是在一年内建成的。",
|
|
"蜘蛛是昆虫。",
|
|
"火星是距离太阳最近的行星。",
|
|
"鲨鱼是哺乳动物。",
|
|
"企鹅能飞翔。",
|
|
"撒哈拉沙漠在澳大利亚。",
|
|
"电流比光速更快。",
|
|
"人体有150块骨头。"
|
|
]
|
|
|
|
# 默认设置为英文
|
|
self.TRUE_FACTS = self.TRUE_FACTS_EN
|
|
self.FALSE_FACTS = self.FALSE_FACTS_EN
|
|
|
|
# 英文操作符映射
|
|
self.TEXT_TO_OP_EN = {
|
|
"is greater than": ">",
|
|
"is less than": "<",
|
|
"is greater than or equal to": ">=",
|
|
"is less than or equal to": "<=",
|
|
"is equal to": "==",
|
|
"equals": "==",
|
|
"plus": "+",
|
|
"minus": "-",
|
|
"multiplied by": "*",
|
|
"times": "*",
|
|
"divided by": "/",
|
|
"divided into": "/",
|
|
"is not equal to": "!=",
|
|
"is not": "!="
|
|
}
|
|
|
|
# 中文操作符映射
|
|
self.TEXT_TO_OP_CN = {
|
|
"大于": ">",
|
|
"小于": "<",
|
|
"大于等于": ">=",
|
|
"小于等于": "<=",
|
|
"等于": "==",
|
|
"加": "+",
|
|
"减": "-",
|
|
"乘以": "*",
|
|
"乘": "*",
|
|
"除以": "/",
|
|
"不等于": "!=",
|
|
}
|
|
|
|
# 默认设置为英文
|
|
self.TEXT_TO_OP = self.TEXT_TO_OP_EN
|
|
|
|
# 选项标识列表
|
|
self.OPTION_LABELS = ["A", "B", "C", "D", "E", "F", "G", "H"]
|
|
|
|
def extract_answer(self, test_solution: str):
|
|
return self.verifier.extract_answer(test_solution)
|
|
|
|
def verify(self, data: Data, test_solution: str):
|
|
return self.verifier.verify(data, test_solution)
|
|
|
|
def generate_arithmetic_expr(self):
|
|
"""生成简单的算术表达式"""
|
|
ops = ["+", "-", "*"]
|
|
text_ops = list(self.TEXT_TO_OP.keys())
|
|
num1 = random.randint(-12, 12)
|
|
num2 = random.randint(-12, 12)
|
|
|
|
use_text_op = random.random() < 0 # 30%概率使用文本操作符
|
|
|
|
if use_text_op:
|
|
op = random.choice(text_ops)
|
|
else:
|
|
op = random.choice(ops)
|
|
|
|
# 避免除以零
|
|
if random.random() < 0.3: # 30%概率生成带除法的表达式
|
|
num3 = random.randint(-9, 9)
|
|
while num3 == 0: # 确保除数不为零
|
|
num3 = random.randint(-9, 9)
|
|
expr = f"{num1} - ({num2} / {num3})"
|
|
else:
|
|
if random.random() < 0.6: # 60%概率生成复合表达式
|
|
num3 = random.randint(-9, 9)
|
|
num4 = random.randint(-9, 9)
|
|
|
|
if use_text_op and random.random() < 0.5:
|
|
op2 = random.choice(text_ops)
|
|
if "times" in self.TEXT_TO_OP:
|
|
expr = f"{num1} times {num2} {op2} {num3} times {num4}"
|
|
else:
|
|
expr = f"{num1} 乘以 {num2} {op2} {num3} 乘以 {num4}"
|
|
else:
|
|
op2 = random.choice(ops)
|
|
expr = f"{num1} * {num2} {op2} {num3} * {num4}"
|
|
else:
|
|
if use_text_op:
|
|
expr = f"{num1} {op} {num2}"
|
|
else:
|
|
expr = f"{num1} {op} {num2}"
|
|
|
|
return expr
|
|
|
|
def generate_comparison_expr(self):
|
|
"""生成比较表达式"""
|
|
expr1 = self.generate_arithmetic_expr()
|
|
expr2 = self.generate_arithmetic_expr()
|
|
|
|
comparison_ops = list(self.TEXT_TO_OP.keys())
|
|
op = random.choice(comparison_ops)
|
|
|
|
return f"({expr1}) {op} ({expr2})"
|
|
|
|
def generate_fact_expr(self):
|
|
"""生成基于事实的表达式"""
|
|
if random.random() < 0.5:
|
|
return random.choice(self.TRUE_FACTS)
|
|
else:
|
|
return random.choice(self.FALSE_FACTS)
|
|
|
|
def generate_boolean_expr(self, depth=5):
|
|
"""递归生成布尔表达式,控制深度"""
|
|
if depth <= 0 or (depth <= 2 and random.random() < 0.3): # 到达一定深度才增加终止概率
|
|
choice = random.random()
|
|
if choice < 0.4:
|
|
return self.generate_comparison_expr()
|
|
elif choice < 0.7:
|
|
return self.generate_fact_expr()
|
|
else:
|
|
return random.choice(["True", "False"])
|
|
|
|
# 递归生成子表达式
|
|
choice = random.random()
|
|
if choice < 0.35: # 35%概率生成NOT表达式
|
|
sub_expr = self.generate_boolean_expr(depth-1)
|
|
prefix = "not " * random.randint(1, 3) # 随机添加1-3个not
|
|
return f"{prefix}({sub_expr})"
|
|
elif choice < 0.7: # 35%概率生成AND表达式
|
|
left = self.generate_boolean_expr(depth-1)
|
|
right = self.generate_boolean_expr(depth-1)
|
|
|
|
# 随机添加更多子表达式
|
|
if random.random() < 0.4 and depth > 2:
|
|
middle = self.generate_boolean_expr(depth-2)
|
|
return f"({left}) and ({middle}) and ({right})"
|
|
return f"({left}) and ({right})"
|
|
else: # 30%概率生成OR表达式
|
|
left = self.generate_boolean_expr(depth-1)
|
|
right = self.generate_boolean_expr(depth-1)
|
|
|
|
# 随机添加更多子表达式
|
|
if random.random() < 0.4 and depth > 2:
|
|
middle = self.generate_boolean_expr(depth-2)
|
|
return f"({left}) or ({middle}) or ({right})"
|
|
return f"({left}) or ({right})"
|
|
|
|
def preprocess_expression(self, expr):
|
|
"""预处理表达式以便于评估"""
|
|
# 首先替换事实为True/False
|
|
for fact in self.TRUE_FACTS:
|
|
expr = expr.replace(f'"{fact}"', 'True')
|
|
# 确保未被引号包围的事实也被正确替换
|
|
if fact in expr:
|
|
expr = expr.replace(fact, 'True')
|
|
|
|
for fact in self.FALSE_FACTS:
|
|
expr = expr.replace(f'"{fact}"', 'False')
|
|
# 确保未被引号包围的事实也被正确替换
|
|
if fact in expr:
|
|
expr = expr.replace(fact, 'False')
|
|
|
|
# 然后按长度排序文本操作符,从长到短替换,避免前缀冲突
|
|
sorted_text_ops = sorted(self.TEXT_TO_OP.keys(), key=len, reverse=True)
|
|
|
|
# 替换文本操作符为符号
|
|
for text in sorted_text_ops:
|
|
op = self.TEXT_TO_OP[text]
|
|
expr = expr.replace(text, op)
|
|
|
|
return expr
|
|
|
|
def evaluate_expression(self, expr):
|
|
"""评估布尔表达式"""
|
|
|
|
# 预处理表达式
|
|
processed_expr = self.preprocess_expression(expr)
|
|
# 安全地评估表达式
|
|
try:
|
|
result = eval(processed_expr)
|
|
except Exception as e:
|
|
return None
|
|
return result
|
|
|
|
|
|
def generate_expressions(self, num_expressions: int, depth: int, language: str) -> List[Tuple[str, bool]]:
|
|
"""
|
|
生成指定数量和深度的布尔表达式
|
|
|
|
Args:
|
|
num_expressions: 要生成的表达式数量
|
|
depth: 表达式的复杂度深度
|
|
|
|
Returns:
|
|
表达式及其值的列表
|
|
"""
|
|
expressions = []
|
|
for i in range(num_expressions):
|
|
while True:
|
|
expr = self.generate_boolean_expr(depth)
|
|
value = self.evaluate_expression(expr)
|
|
if value is not None:
|
|
break
|
|
|
|
|
|
# 如果评估失败或者有过长的表达式,重新生成
|
|
max_attempts = 5
|
|
attempt = 0
|
|
while (value is None or len(expr) > 500) and attempt < max_attempts:
|
|
expr = self.generate_boolean_expr(depth)
|
|
value = self.evaluate_expression(expr)
|
|
attempt += 1
|
|
|
|
if value is not None:
|
|
expressions.append((expr, value))
|
|
if not any(value for _, value in expressions):
|
|
return self.generate_expressions(num_expressions, depth, language)
|
|
|
|
return expressions
|
|
|
|
def generate_problem(self, language="en"):
|
|
"""
|
|
生成一个布尔表达式多选题
|
|
|
|
Args:
|
|
language: 语言,"cn"为中文,"en"为英文
|
|
|
|
Returns:
|
|
问题描述和答案
|
|
"""
|
|
# 随机确定选项数量和深度
|
|
num_options = random.randint(self.min_options, self.max_options)
|
|
depth = random.randint(self.min_depth, self.max_depth)
|
|
|
|
# 生成表达式
|
|
expressions = self.generate_expressions(num_options, depth, language)
|
|
|
|
# 确保至少有一些表达式
|
|
if len(expressions) < 2:
|
|
return self.generate_problem(language)
|
|
|
|
|
|
# 创建选项列表
|
|
options = []
|
|
true_options = [] # 存储为真的选项标识
|
|
|
|
for i, (expr, value) in enumerate(expressions):
|
|
if i >= len(self.OPTION_LABELS):
|
|
break # 防止选项超出标识范围
|
|
|
|
label = self.OPTION_LABELS[i]
|
|
options.append(f"{label}. {expr}")
|
|
|
|
if value: # 如果表达式为真
|
|
true_options.append(label)
|
|
|
|
|
|
# 将为真的选项按字母顺序排序
|
|
true_options.sort()
|
|
answer = ",".join(true_options)
|
|
|
|
# 构建完整问题
|
|
if language == "cn":
|
|
context = "请评估以下布尔表达式,选择其中为真(True)的选项:\n\n" + "\n".join(options)
|
|
question = "哪些选项的表达式值为真?请列出所有为真的选项标识,用逗号分隔。"
|
|
|
|
# 随机选择一个中文提示模板
|
|
prompt_template = random.choice(prompts_zh)
|
|
else:
|
|
context = "Evaluate the following boolean expressions and select the ones that are true:\n\n" + "\n".join(options)
|
|
question = "Which options are true? List all option labels that are true, separated by commas."
|
|
|
|
# 随机选择一个英文提示模板
|
|
prompt_template = random.choice(prompts_en)
|
|
|
|
# 使用提示模板格式化问题
|
|
full_question = prompt_template.format(context=context, question=question)
|
|
|
|
return full_question, answer
|
|
|
|
def generate(self, num_of_data=100, language="mixed"):
|
|
"""
|
|
生成游戏数据
|
|
|
|
Args:
|
|
num_of_data: 生成的数据数量
|
|
language: 使用的语言,可选值为"cn"、"en"或"mixed"
|
|
|
|
Returns:
|
|
生成的数据列表
|
|
"""
|
|
|
|
data_list = []
|
|
for _ in range(num_of_data):
|
|
# 如果是混合语言,为每个问题随机选择语言
|
|
if language == "mixed":
|
|
curr_lang = random.choice(["cn", "en"])
|
|
self.curr_lang = curr_lang
|
|
else:
|
|
curr_lang = language
|
|
|
|
|
|
if curr_lang == "cn":
|
|
self.TRUE_FACTS = self.TRUE_FACTS_CN
|
|
self.FALSE_FACTS = self.FALSE_FACTS_CN
|
|
self.TEXT_TO_OP = self.TEXT_TO_OP_CN
|
|
prompts = prompts_zh
|
|
else:
|
|
self.TRUE_FACTS = self.TRUE_FACTS_EN
|
|
self.FALSE_FACTS = self.FALSE_FACTS_EN
|
|
self.TEXT_TO_OP = self.TEXT_TO_OP_EN
|
|
prompts = prompts_en
|
|
# 随机选择提示模板
|
|
prompt_template = random.choice(prompts)
|
|
|
|
# 生成问题数据
|
|
question, answer = self.generate_problem(curr_lang)
|
|
|
|
# # 格式化提示
|
|
# question = prompt_template.format(context=question, question=question)
|
|
|
|
# 添加到列表
|
|
data_list.append(Data(question=question, answer=answer))
|
|
|
|
return data_list
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="生成布尔表达式推理游戏数据")
|
|
parser.add_argument("--num_of_data", type=int, default=100, help="生成的题目数量")
|
|
parser.add_argument("--min_depth", type=int, default=2, help="最小表达式深度")
|
|
parser.add_argument("--max_depth", type=int, default=5, help="最大表达式深度")
|
|
parser.add_argument("--min_options", type=int, default=3, help="每个问题的最小选项数量")
|
|
parser.add_argument("--max_options", type=int, default=6, help="每个问题的最大选项数量")
|
|
parser.add_argument("--language", type=str, default="mixed", help="语言:cn, en, mixed")
|
|
args = parser.parse_args()
|
|
|
|
# 创建数据目录
|
|
data_dir = pathlib.Path(__file__).parent.parent / "data"
|
|
if not data_dir.exists():
|
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
print(f"创建数据目录: {data_dir}")
|
|
|
|
# 创建游戏实例
|
|
game = BooleanExpressions(
|
|
min_depth=args.min_depth,
|
|
max_depth=args.max_depth,
|
|
min_options=args.min_options,
|
|
max_options=args.max_options
|
|
)
|
|
|
|
# 使用参数构建文件名
|
|
filename = f"data_depth{args.min_depth}-{args.max_depth}_options{args.min_options}-{args.max_options}_{args.language}_n{args.num_of_data}.jsonl"
|
|
output_file = data_dir / filename
|
|
|
|
# 生成游戏数据
|
|
game_data_list = game.generate(
|
|
num_of_data=args.num_of_data,
|
|
language=args.language
|
|
)
|
|
|
|
# 将数据保存到文件
|
|
try:
|
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
for game_data in game_data_list:
|
|
f.write(json.dumps(game_data.to_json(), ensure_ascii=False) + "\n")
|
|
print(f"成功生成{len(game_data_list)}条数据,保存至: {output_file}")
|
|
except Exception as e:
|
|
print(f"保存数据时出错: {e}")
|