chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:24:13 +08:00
commit 1037506f2e
6050 changed files with 1731598 additions and 0 deletions
+193
View File
@@ -0,0 +1,193 @@
import json
import sys
from datasets import load_dataset
from general_util.logger import get_child_logger
logger = get_child_logger(__name__)
sys.set_int_max_str_digits(0)
class APPsReader:
def __init__(self, split: str = "train", train_sub_split: str = ""):
self.train_sub_val_ids = set(json.load(open("apps_train_sub_val_ids.json")))
self.split = split
self.train_sub_split = train_sub_split
def __call__(self, file_path):
data = load_dataset(file_path, split=self.split, trust_remote_code=True).to_list()
logger.info(len(data))
if self.split == "train" and self.train_sub_split:
if self.train_sub_split == "train":
data = [item for item in data if item["problem_id"] not in self.train_sub_val_ids]
elif self.train_sub_split == "val":
data = [item for item in data if item["problem_id"] in self.train_sub_val_ids]
else:
raise ValueError(f"Invalid train_sub_split: {self.train_sub_split} [train, val]")
logger.info(f"Using {self.train_sub_split} split for training data")
logger.info(len(data))
missing_solutions = 0
missing_test_cases = 0
for item in data:
if item["solutions"]:
item["solutions"] = json.loads(item["solutions"])
else:
missing_solutions += 1
if item["input_output"]:
item["input_output"] = json.loads(item["input_output"])
else:
missing_test_cases += 1
print(f"Missing solutions: {missing_solutions}")
print(f"Missing test cases: {missing_test_cases}")
return data
class APPsWithFunctionName:
def __init__(self, split: str = "train", train_sub_split: str = "", use_starter_code: bool = False):
self.train_sub_val_ids = set(json.load(open("apps_train_sub_val_ids.json")))
self.split = split
self.train_sub_split = train_sub_split
self.use_starter_code = use_starter_code
def __call__(self, file_path):
data = load_dataset(file_path, split=self.split, trust_remote_code=True).to_list()
logger.info(len(data))
if self.split == "train" and self.train_sub_split:
if self.train_sub_split == "train":
data = [item for item in data if item["problem_id"] not in self.train_sub_val_ids]
elif self.train_sub_split == "val":
data = [item for item in data if item["problem_id"] in self.train_sub_val_ids]
else:
raise ValueError(f"Invalid train_sub_split: {self.train_sub_split} [train, val]")
logger.info(f"Using {self.train_sub_split} split for training data")
logger.info(len(data))
missing_solutions = 0
missing_test_cases = 0
for item in data:
if item["solutions"]:
item["solutions"] = json.loads(item["solutions"])
else:
missing_solutions += 1
if item["input_output"]:
item["input_output"] = json.loads(item["input_output"])
if "fn_name" in item["input_output"]:
item["fn_name"] = item["input_output"]["fn_name"]
if self.use_starter_code:
assert item["starter_code"]
item["question"] += f"\n\n{item['starter_code']}"
else:
item["question"] += f"\n\nYou should name the function as `{item['fn_name']}`."
else:
missing_test_cases += 1
print(f"Missing solutions: {missing_solutions}")
print(f"Missing test cases: {missing_test_cases}")
return data
class APPsFlatTestCasesReader(APPsWithFunctionName):
def __call__(self, file_path):
data = super().__call__(file_path)
outputs = []
for item in data:
if item["input_output"]:
inputs = item["input_output"]["inputs"]
inputs = [str(_input) for _input in inputs]
item["test_inputs"] = inputs
outputs.append(item)
return outputs
class PseudoInputsWithFunctionName:
def __init__(self, use_starter_code: bool = False, train_sub_split: str = "train"):
self.use_starter_code = use_starter_code
self.train_sub_val_ids = set([f"apps-train-{x}" for x in json.load(open("apps_train_sub_val_ids.json"))])
self.train_sub_split = train_sub_split
def __call__(self, file_path):
data = json.load(open(file_path, encoding="utf-8"))
logger.info(len(data))
if self.train_sub_split == "train":
data = [item for item in data if item["problem_id"] not in self.train_sub_val_ids]
elif self.train_sub_split == "val":
data = [item for item in data if item["problem_id"] in self.train_sub_val_ids]
else:
raise ValueError(f"Invalid train_sub_split: {self.train_sub_split} [train, val]")
logger.info(f"Using {self.train_sub_split} split for training data")
logger.info(len(data))
missing_solutions = 0
missing_test_cases = 0
for item in data:
if item["input_output"]:
if not isinstance(item["input_output"], dict):
item["input_output"] = json.loads(item["input_output"])
if "fn_name" in item["input_output"]:
item["fn_name"] = item["input_output"]["fn_name"]
if self.use_starter_code:
# FIXME: This is a bug for synthesized data, since we didn't generate starter code.
# We should use `fn_name` to compose simple starter_code instead.
# See PseudoInputsWithFunctionNameFixStarterCode for the fix.
if "starter_code" in item and item["starter_code"]:
item["question"] += f"\n\n{item['starter_code']}"
else:
item["question"] += f"\n\nYou should name the function as `{item['fn_name']}`."
else:
missing_test_cases += 1
print(f"Missing solutions: {missing_solutions}")
print(f"Missing test cases: {missing_test_cases}")
return data
class PseudoInputsWithFunctionNameFixStarterCode:
def __init__(self, use_starter_code: bool = False, train_sub_split: str = "train"):
self.use_starter_code = use_starter_code
self.train_sub_val_ids = set([f"apps-train-{x}" for x in json.load(open("apps_train_sub_val_ids.json"))])
self.train_sub_split = train_sub_split
def __call__(self, file_path):
data = json.load(open(file_path, encoding="utf-8"))
logger.info(len(data))
if self.train_sub_split == "train":
data = [item for item in data if item["problem_id"] not in self.train_sub_val_ids]
elif self.train_sub_split == "val":
data = [item for item in data if item["problem_id"] in self.train_sub_val_ids]
else:
raise ValueError(f"Invalid train_sub_split: {self.train_sub_split} [train, val]")
logger.info(f"Using {self.train_sub_split} split for training data")
logger.info(len(data))
missing_solutions = 0
missing_test_cases = 0
for item in data:
if item["input_output"]:
if not isinstance(item["input_output"], dict):
item["input_output"] = json.loads(item["input_output"])
if "fn_name" in item["input_output"]:
item["fn_name"] = item["input_output"]["fn_name"]
if self.use_starter_code and "starter_code" in item and item["starter_code"]:
item["question"] += f"\n\n{item['starter_code']}"
else:
item["question"] += f"\n\nYou should name the function as `{item['fn_name']}`."
else:
missing_test_cases += 1
print(f"Missing solutions: {missing_solutions}")
print(f"Missing test cases: {missing_test_cases}")
return data
+87
View File
@@ -0,0 +1,87 @@
import os.path
from datasets import load_dataset
from glob import glob
from tqdm import tqdm
class CodeContestReader:
def __call__(self, file_path):
if os.path.exists(file_path):
data = load_dataset("parquet", data_files=file_path)["train"]
else:
data = load_dataset("parquet", data_files=list(glob(file_path)))["train"]
print(len(data))
outputs = []
for item in tqdm(data):
description = item["description"]
title = item["name"]
if item["time_limit"]:
time_limits = item["time_limit"]["seconds"]
else:
time_limits = "N/A"
if item["memory_limit_bytes"]:
mem_limits = item["memory_limit_bytes"]
else:
mem_limits = "N/A"
pos_solution = item["solutions"]["solution"]
neg_solution = item["incorrect_solutions"]["solution"]
outputs.append({
"name": title,
"description": description,
"time_limit": time_limits,
"memory_limit": mem_limits,
"pos_solution": pos_solution,
"neg_solution": neg_solution,
"id": f"{title}",
})
return outputs
class CodeContestFlatReader:
def __call__(self, file_path):
if os.path.exists(file_path):
data = load_dataset("parquet", data_files=file_path)["train"]
else:
data = load_dataset("parquet", data_files=list(glob(file_path)))["train"]
print(len(data))
outputs = []
for item in tqdm(data):
description = item["description"]
title = item["name"]
if item["time_limit"]:
time_limits = item["time_limit"]["seconds"]
else:
time_limits = "N/A"
if item["memory_limit_bytes"]:
mem_limits = item["memory_limit_bytes"]
else:
mem_limits = "N/A"
pos_solution = item["solutions"]["solution"]
neg_solution = item["incorrect_solutions"]["solution"]
for i, solution in enumerate(pos_solution):
outputs.append({
"name": title,
"description": description,
"time_limit": time_limits,
"memory_limit": mem_limits,
"solution": solution,
"id": f"{title}_{i}",
"value": 1,
})
for i, solution in enumerate(neg_solution):
outputs.append({
"name": title,
"description": description,
"time_limit": time_limits,
"memory_limit": mem_limits,
"solution": solution,
"id": f"{title}_{i}",
"value": 0,
})
return outputs
+392
View File
@@ -0,0 +1,392 @@
import collections
import json
import os.path
import random
from typing import List, Dict, Callable
import hydra
from omegaconf import DictConfig
from torch.utils.data import Dataset
from transformers import PreTrainedTokenizer
from data.input_aligner import empty_aligner
from data.input_utils import json_read_fn
from general_util.logger import get_child_logger
logger = get_child_logger(__name__)
class ResponseAlignDataset(Dataset):
def __init__(self,
file_path: str,
tokenizer: PreTrainedTokenizer,
template: str,
aligner: Callable = empty_aligner,
instruction: str = "",
few_shot_prompt: str = "",
api_based: bool = False, message_compose_fn: Callable = None,
service_based: bool = False, service_processor: Callable = None,
flush_file: str = None,
split_size: int = -1,
split_id: int = 0,
index_field: str = "id",
max_data_num: int = -1,
read_fn: Callable = json_read_fn,
replacement: Dict[str, str] = None,
):
self.tokenizer = tokenizer
self.template = template
self.instruction = instruction
self.few_shot_prompt = few_shot_prompt
self.api_based = api_based
self.message_compose_fn = message_compose_fn
self.service_based = service_based
self.service_processor = service_processor
self.flush_file = flush_file
self.split_size = split_size
self.split_id = split_id
self.index_field = index_field
self.max_data_num = max_data_num
self.replacement = replacement
data = read_fn(file_path)
self.data: List[Dict] = aligner(data)
for item in self.data:
if self.instruction:
item["instruction"] = self.instruction
if self.few_shot_prompt:
item["few_shot_prompt"] = self.few_shot_prompt
flushed_data = set()
flushed_full_data = []
if flush_file is not None and os.path.exists(flush_file):
tmp = open(flush_file, "r", encoding="utf-8").readlines()
has_reading_error = False
for line in tmp:
try:
item = json.loads(line)
except json.JSONDecodeError as e:
logger.warning(f"Error reading line: {line}")
has_reading_error = True
continue
if "response" in item and item["response"]:
if item["id"] in flushed_data:
continue
flushed_data.add(item["id"])
flushed_full_data.append(item)
logger.info(f"Loaded flushed data: {len(flushed_data)} from {flush_file}")
if has_reading_error:
logger.warning("There are reading errors in the flush file")
with open(flush_file, "w", encoding="utf-8") as f:
for item in flushed_full_data:
f.write(json.dumps(item) + "\n")
if split_size > 0:
batch_size = (len(self.data) + split_size - 1) // split_size
self.data = self.data[split_id * batch_size: (split_id + 1) * batch_size]
self.data = [item for item in self.data if item[self.index_field] not in flushed_data and str(item[self.index_field]) not in flushed_data]
def __len__(self):
if self.max_data_num > 0:
return min(self.max_data_num, len(self.data))
return len(self.data)
def api_getitem(self, index):
item = self.data[index]
if self.replacement:
tmp = self.template
for k, v in self.replacement.items():
tmp = tmp.replace(k, item[v])
text = tmp
else:
text = self.template.format(**item)
if self.message_compose_fn is not None:
text = self.message_compose_fn(text)
item["text"] = text
return {
"text": text,
"meta_data": item,
}
def service_getitem(self, index):
inputs = self.api_getitem(index)
response = self.service_processor(inputs["text"])
inputs["response"] = response
return inputs
def __getitem__(self, idx):
if self.api_based:
return self.api_getitem(idx)
if self.service_based:
return self.service_getitem(idx)
item = self.data[idx]
if self.replacement:
tmp = self.template
for k, v in self.replacement.items():
tmp = tmp.replace(k, item[v])
text = tmp
else:
text = self.template.format(**item)
item["text"] = text
return {
"text": text,
"meta_data": item,
}
class PromptResponseDataset(Dataset):
def __init__(self,
file_path: str,
tokenizer: PreTrainedTokenizer,
prompt_template: str,
response_template: str,
aligner: Callable = empty_aligner,
instruction: str = "",
few_shot_prompt: str = "",
api_based: bool = False,
service_based: bool = False, service_processor: Callable = None,
flush_file: str = None,
split_size: int = -1,
split_id: int = 0,
index_field: str = "id",
max_data_num: int = -1,
read_fn: Callable = json_read_fn,
kv_mapping: Dict[str, str] = None,
):
self.tokenizer = tokenizer
self.prompt_template = prompt_template
self.response_template = response_template
self.instruction = instruction
self.few_shot_prompt = few_shot_prompt
self.api_based = api_based
self.service_based = service_based
self.service_processor = service_processor
self.flush_file = flush_file
self.split_size = split_size
self.split_id = split_id
self.index_field = index_field
self.max_data_num = max_data_num
self.kv_mapping = kv_mapping
data = read_fn(file_path)
self.data: List[Dict] = aligner(data)
for item in self.data:
if self.instruction:
item["instruction"] = self.instruction
if self.few_shot_prompt:
item["few_shot_prompt"] = self.few_shot_prompt
flushed_data = set()
if flush_file is not None and os.path.exists(flush_file):
tmp = open(flush_file, "r", encoding="utf-8").readlines()
for line in tmp:
item = json.loads(line)
if "response" in item and item["response"].strip() != "":
flushed_data.add(item["id"])
logger.info(f"Loaded flushed data: {len(flushed_data)} from {flush_file}")
self.data = [item for item in self.data if item[self.index_field] not in flushed_data]
if split_size > 0:
batch_size = (len(self.data) + split_size - 1) // split_size
self.data = self.data[split_id * batch_size: (split_id + 1) * batch_size]
def __len__(self):
if self.max_data_num > 0:
return min(self.max_data_num, len(self.data))
return len(self.data)
def api_getitem(self, index):
raise NotImplementedError
def service_getitem(self, index):
raise NotImplementedError
def __getitem__(self, idx):
if self.api_based:
return self.api_getitem(idx)
if self.service_based:
return self.service_getitem(idx)
item = self.data[idx]
prompt = self.prompt_template.format(**item)
response = self.response_template.format(**item)
text = prompt + response
item["text"] = text
item["prompt"] = prompt
if not self.kv_mapping:
return {
"text": text,
"meta_data": item,
}
res = {v: item[k] for k, v in self.kv_mapping.items()}
res["meta_data"] = item
return res
class MultiMappingDataset(Dataset):
def __init__(self,
file_path: str,
tokenizer: PreTrainedTokenizer,
template: Dict[str, str],
aligner: Callable = empty_aligner,
instruction: str = "",
few_shot_prompt: str = "",
api_based: bool = False,
service_based: bool = False, service_processor: Callable = None,
flush_file: str = None,
split_size: int = -1,
split_id: int = 0,
index_field: str = "id",
max_data_num: int = -1,
read_fn: Callable = json_read_fn,
kv_mapping: Dict[str, str] = None,
replacement: Dict[str, str] = None,
):
self.tokenizer = tokenizer
self.template = template
self.instruction = instruction
self.few_shot_prompt = few_shot_prompt
self.api_based = api_based
self.service_based = service_based
self.service_processor = service_processor
self.flush_file = flush_file
self.split_size = split_size
self.split_id = split_id
self.index_field = index_field
self.max_data_num = max_data_num
self.kv_mapping = kv_mapping
self.replacement = replacement
data = read_fn(file_path)
self.data: List[Dict] = aligner(data)
for item in self.data:
if self.instruction:
item["instruction"] = self.instruction
if self.few_shot_prompt:
item["few_shot_prompt"] = self.few_shot_prompt
flushed_data = set()
if flush_file is not None and os.path.exists(flush_file):
tmp = open(flush_file, "r", encoding="utf-8").readlines()
for line in tmp:
item = json.loads(line)
if "response" in item and item["response"]:
flushed_data.add(item["id"])
logger.info(f"Loaded flushed data: {len(flushed_data)} from {flush_file}")
self.data = [item for item in self.data if item[self.index_field] not in flushed_data]
if split_size > 0:
batch_size = (len(self.data) + split_size - 1) // split_size
self.data = self.data[split_id * batch_size: (split_id + 1) * batch_size]
def __len__(self):
if self.max_data_num > 0:
return min(self.max_data_num, len(self.data))
return len(self.data)
def api_getitem(self, index):
raise NotImplementedError
def service_getitem(self, index):
raise NotImplementedError
def __getitem__(self, idx):
if self.api_based:
return self.api_getitem(idx)
if self.service_based:
return self.service_getitem(idx)
item = self.data[idx]
inputs = {}
if self.replacement:
for name, tem in self.template.items():
tmp = tem
for k, v in self.replacement.items():
tmp = tmp.replace(k, item[v])
inputs[name] = tmp
item[name] = tmp
else:
for k, v in self.template.items():
item[k] = v.format(**item)
inputs[k] = item[k]
inputs["meta_data"] = item
if not self.kv_mapping:
return inputs
res = {v: item[k] for k, v in self.kv_mapping.items()}
res["meta_data"] = item
return res
class MultiMappingDatasetGrouping(MultiMappingDataset):
def __init__(self,
file_path: str,
tokenizer: PreTrainedTokenizer,
template: Dict[str, str],
aligner: Callable = empty_aligner,
instruction: str = "",
few_shot_prompt: str = "",
api_based: bool = False,
service_based: bool = False, service_processor: Callable = None,
flush_file: str = None,
split_size: int = -1,
split_id: int = 0,
index_field: str = "id",
max_data_num: int = -1,
read_fn: Callable = json_read_fn,
kv_mapping: Dict[str, str] = None,
group_field: str = "id",
):
super().__init__(file_path,
tokenizer,
template,
aligner,
instruction,
few_shot_prompt,
api_based,
service_based,
service_processor,
flush_file,
split_size,
split_id,
index_field,
max_data_num,
read_fn,
kv_mapping)
random.shuffle(self.data)
groups = collections.defaultdict(list)
for item in self.data:
groups[item[group_field]].append(item)
new_data = []
for group in groups.values():
new_data.extend(group)
self.data = new_data
class ReplayDataset(Dataset):
def __init__(self, file_path: str, tokenizer: PreTrainedTokenizer, new_dataset_cfg: DictConfig, old_dataset_cfg: DictConfig, replay_ratio: float = 0.1):
logger.info(f"Loading new dataset from {file_path}")
self.new_dataset = hydra.utils.instantiate(new_dataset_cfg, file_path=file_path, tokenizer=tokenizer)
logger.info(f"Loading old dataset from {old_dataset_cfg}")
old_dataset = hydra.utils.instantiate(old_dataset_cfg, tokenizer=tokenizer)
logger.info(f"Replay ratio: {replay_ratio}")
self.replay_ratio = replay_ratio
self.old_data = random.sample([item for item in old_dataset], int(len(old_dataset) * replay_ratio))
def __len__(self):
return len(self.new_dataset) + len(self.old_data)
def __getitem__(self, index):
if index < len(self.new_dataset):
return self.new_dataset[index]
else:
return self.old_data[index - len(self.new_dataset)]
@@ -0,0 +1,354 @@
# Copied from https://github.com/deepseek-ai/DeepSeek-Math/blob/main/evaluation/data_processing/answer_extraction.py
import re
import regex
def _fix_fracs(string):
substrs = string.split("\\frac")
new_str = substrs[0]
if len(substrs) > 1:
substrs = substrs[1:]
for substr in substrs:
new_str += "\\frac"
if len(substr) > 0 and substr[0] == "{":
new_str += substr
else:
try:
assert len(substr) >= 2
except:
return string
a = substr[0]
b = substr[1]
if b != "{":
if len(substr) > 2:
post_substr = substr[2:]
new_str += "{" + a + "}{" + b + "}" + post_substr
else:
new_str += "{" + a + "}{" + b + "}"
else:
if len(substr) > 2:
post_substr = substr[2:]
new_str += "{" + a + "}" + b + post_substr
else:
new_str += "{" + a + "}" + b
string = new_str
return string
def _fix_a_slash_b(string):
if len(string.split("/")) != 2:
return string
a = string.split("/")[0]
b = string.split("/")[1]
try:
if "sqrt" not in a:
a = int(a)
if "sqrt" not in b:
b = int(b)
assert string == "{}/{}".format(a, b)
new_string = "\\frac{" + str(a) + "}{" + str(b) + "}"
return new_string
except:
return string
def _fix_sqrt(string):
_string = re.sub(r"\\sqrt(-?[0-9.a-zA-Z]+)", r"\\sqrt{\1}", string)
_string = re.sub(r"\\sqrt\s+(\w+)$", r"\\sqrt{\1}", _string)
return _string
def _fix_tan(string):
_string = re.sub(r"\\tan(-?[0-9.a-zA-Z]+)", r"\\tan{\1}", string)
_string = re.sub(r"\\tan\s+(\w+)$", r"\\tan{\1}", _string)
return _string
def strip_string(string):
string = str(string).strip()
# linebreaks
string = string.replace("\n", "")
# right "."
string = string.rstrip(".")
# remove inverse spaces
string = string.replace("\\!", "")
# string = string.replace("\\ ", "")
# replace \\ with \
# string = string.replace("\\\\", "\\")
# string = string.replace("\\\\", "\\")
if string.startswith("\\text{") and string.endswith("}"):
string = string.split("{", 1)[1][:-1]
# replace tfrac and dfrac with frac
string = string.replace("tfrac", "frac")
string = string.replace("dfrac", "frac")
string = string.replace("cfrac", "frac")
# remove \left and \right
string = string.replace("\\left", "")
string = string.replace("\\right", "")
# Remove unit: miles, dollars if after is not none
_string = re.sub(r"\\text{.*?}$", "", string).strip()
if _string != "" and _string != string:
# print("Warning: unit not removed: '{}' -> '{}'".format(string, _string))
string = _string
# Remove circ (degrees)
string = string.replace("^{\\circ}", "").strip()
string = string.replace("^\\circ", "").strip()
string = regex.sub(r"\{(c|m)?m\}(\^(2|3))?", "", string).strip()
string = regex.sub(r"p\.m\.$", "", string).strip()
string = regex.sub(r"(\d)\s*t$", r"\1", string).strip()
# remove dollar signs
string = string.replace("\\$", "")
string = string.replace("$", "")
# string = string.replace("\\text", "")
string = string.replace("x\\in", "")
# remove percentage
string = string.replace("\\%", "%")
string = string.replace("\%", "%")
# string = string.replace("%", "")
# " 0." equivalent to " ." and "{0." equivalent to "{." Alternatively, add "0" if "." is the start of the string
string = string.replace(" .", " 0.")
string = string.replace("{.", "{0.")
# cdot
string = string.replace("\\cdot", "")
# inf
string = string.replace("infinity", "\\infty")
if "\\infty" not in string:
string = string.replace("inf", "\\infty")
string = string.replace("+\\inity", "\\infty")
# and
# string = string.replace("and", "")
string = string.replace("\\mathbf", "")
string = string.replace("\\mathrm", "")
# use regex to remove \mbox{...}
string = re.sub(r"\\mbox{.*?}", "", string)
# quote
string.replace("'", "")
string.replace("\"", "")
# i, j
if "j" in string and "i" not in string:
string = string.replace("j", "i")
# replace a.000b where b is not number or b is end, with ab, use regex
string = re.sub(r"(\d+)\.0+([^\d])", r"\1\2", string)
string = re.sub(r"(\d+)\.0+$", r"\1", string)
# if empty, return empty string
if len(string) == 0:
return string
if string[0] == ".":
string = "0" + string
# to consider: get rid of e.g. "k = " or "q = " at beginning
# if len(string.split("=")) == 2:
# if len(string.split("=")[0]) <= 2:
# string = string.split("=")[1]
string = _fix_sqrt(string)
string = _fix_tan(string)
string = string.replace(" ", "")
# \frac1b or \frac12 --> \frac{1}{b} and \frac{1}{2}, etc. Even works with \frac1{72} (but not \frac{72}1). Also does a/b --> \\frac{a}{b}
string = _fix_fracs(string)
# NOTE: X/Y changed to \frac{X}{Y} in dataset, but in simple cases fix in case the model output is X/Y
string = _fix_a_slash_b(string)
string = regex.sub(r"(\\|,|\.)+$", "", string)
return string
def extract_boxed_answers(text):
answers = []
for piece in text.split('boxed{')[1:]:
n = 0
for i in range(len(piece)):
if piece[i] == '{':
n += 1
elif piece[i] == '}':
n -= 1
if n < 0:
if i + 1 < len(piece) and piece[i + 1] == '%':
answers.append(piece[: i + 1])
else:
answers.append(piece[:i])
break
return answers
def extract_program_output(pred_str):
"""
extract output between the last ```output\n...\n```
"""
if "```output" not in pred_str:
return ""
if '```output' in pred_str:
pred_str = pred_str.split('```output')[-1]
if '```' in pred_str:
pred_str = pred_str.split('```')[0]
output = pred_str.strip()
return output
def extract_answer(pred_str, exhaust=False):
pred = []
if 'final answer is $' in pred_str and '$. I hope' in pred_str:
tmp = pred_str.split('final answer is $', 1)[1]
pred = [tmp.split('$. I hope', 1)[0].strip()]
elif 'boxed' in pred_str:
pred = extract_boxed_answers(pred_str)
elif ('he answer is' in pred_str):
pred = [pred_str.split('he answer is')[-1].strip()]
else:
program_output = extract_program_output(pred_str)
if program_output != "":
# fall back to program
pred.append(program_output)
else: # use the last number
pattern = '-?\d*\.?\d+'
ans = re.findall(pattern, pred_str.replace(",", ""))
if (len(ans) >= 1):
ans = ans[-1]
else:
ans = ''
if ans:
pred.append(ans)
# multiple line
_pred = []
for ans in pred:
ans = ans.strip().split("\n")[0]
ans = ans.lstrip(":")
ans = ans.rstrip(".")
ans = ans.rstrip("/")
ans = strip_string(ans)
_pred.append(ans)
if exhaust:
return _pred
else:
return _pred[-1] if _pred else ""
def extract_math_answer(question, reasoning, task):
answer = []
for ans in extract_answer(reasoning, exhaust=True):
if 'separated by commas' in question and all(ch not in ans for ch in '()[]'):
answer.extend([a.strip() for a in ans.split(",")])
elif regex.search(r"\\text\{\s*and\s*\}", ans):
answer.extend([a.strip() for a in regex.sub(r"\\text\{\s*and\s*\}", "[SEP]", ans).split("[SEP]")])
else:
answer.append(ans.strip())
return answer
def extract_math_few_shot_cot_answer(question, reasoning, task):
if 'Problem:' in reasoning:
reasoning = reasoning.split("Problem:", 1)[0]
return extract_math_answer(question, reasoning, task)
def extract_last_single_answer(question, reasoning, task):
return extract_answer(reasoning, exhaust=False)
def extract_gsm_few_shot_cot_answer(question, reasoning, task):
if 'Q: ' in reasoning:
reasoning = reasoning.split("Q: ", 1)[0]
pred = [s for s in regex.findall(r'-?\d+\.?\d*', reasoning)]
if pred:
return pred[-1]
else:
return "[invalid]"
def extract_agieval_gaokao_mathcloze_few_shot_cot_test(question, reasoning, task):
if '问题 ' in reasoning:
reasoning = reasoning.split("问题 ", 1)[0]
if '答案是' in reasoning:
ans = reasoning.split('答案是', 1)[1].strip()
ans = ans.split("\n")[0].strip()
ans = [ans.strip("$")]
else:
ans = ['placeholder']
return ans
def extract_agieval_gaokao_mathqa_few_shot_cot_test(question, reasoning, task):
if '问题 ' in reasoning:
reasoning = reasoning.split("问题 ", 1)[0]
if '答案是' in reasoning:
ans = reasoning.split('答案是', 1)[1].strip()
ans = ans.split("\n")[0].strip()
else:
ans = 'placeholder'
return ans
def extract_sat_few_shot_answer(question, reasoning, task):
if 'Problem:' in reasoning:
reasoning = reasoning.split("Problem:", 1)[0]
patt = regex.search(r"the final answer is \(?(?P<ans>[abcd])\)?", reasoning.lower())
if patt is not None:
return patt.group('ans').upper()
return 'placeholder'
def extract_ocwcourses_few_shot_answer(question, reasoning, task):
if 'Problem:' in reasoning:
reasoning = reasoning.split("Problem:", 1)[0]
patt = regex.search(r"final answer is (?P<ans>.*)\. I hope it is correct.", reasoning)
if patt is None:
pred = "[invalid]"
print(f"DEBUG >>>\n{reasoning}", flush=True)
else:
pred = patt.group('ans')
return pred
def extract_mmlu_stem(question, reasoning, task):
if 'Problem:' in reasoning:
reasoning = reasoning.split("Problem:", 1)[0]
return extract_sat_few_shot_answer(question, reasoning, task)
def extract_minif2f_isabelle(question, reasoning, task):
if 'Informal:' in reasoning:
reasoning = reasoning.split("Informal:", 1)[0]
return reasoning.strip()
def extract_cmath_few_shot_test(question, reasoning, task):
if '问题:' in reasoning:
reasoning = reasoning.split("问题:", 1)[0]
if '答案是' in reasoning:
ans = reasoning.split('答案是', 1)[1].strip()
ans = ans.split("\n")[0]
ans = ans.strip("")
ans = ans.strip("")
try:
ans = [s for s in regex.findall(r'-?\d+\.?\d*', ans)][-1]
except:
print(f"DEBUG CMATH: {reasoning}", flush=True)
ans = "[invalid]"
else:
ans = extract_last_single_answer(question, reasoning, task)
return ans
@@ -0,0 +1,181 @@
import regex
from copy import deepcopy
from data.deepseek_math_utils.eval_utils import math_equal
from data.deepseek_math_utils.ocwcourses_eval_utils import normalize_numeric, numeric_equality, normalize_symbolic_equation, SymbolicMathMixin
def is_correct(item, pred_key='prediction', prec=1e-3):
pred = item[pred_key]
ans = item['answer']
if isinstance(pred, list) and isinstance(ans, list):
pred_matched = set()
ans_matched = set()
for i in range(len(pred)):
for j in range(len(ans)):
item_cpy = deepcopy(item)
item_cpy.update({
pred_key: pred[i],
'answer': ans[j]
})
if is_correct(item_cpy, pred_key=pred_key, prec=prec):
pred_matched.add(i)
ans_matched.add(j)
if item_cpy[pred_key] == '2,3,4':
print(item, flush=True)
print("wtf", flush=True)
return len(pred_matched) == len(pred) and len(ans_matched) == len(ans)
elif isinstance(pred, str) and isinstance(ans, str):
if '\\cup' in pred and '\\cup' in ans:
item = deepcopy(item)
item.update({
pred_key: pred.split('\\cup'),
'answer': ans.split('\\cup'),
})
return is_correct(item, pred_key=pred_key, prec=prec)
else:
label = False
try:
label = abs(float(regex.sub(r',', '', str(pred))) - float(regex.sub(r',', '', str(ans)))) < prec
except:
pass
label = label or (ans and pred == ans) or math_equal(pred, ans)
return label
else:
print(item, flush=True)
raise NotImplementedError()
def eval_math(item, pred_key='prediction', prec=1e-3):
pred = item[pred_key]
if pred_key == 'program_output' and isinstance(pred, str):
pred = [pred]
ans = item['answer']
if isinstance(pred, list) and isinstance(ans, list):
# for some questions in MATH, `reference` repeats answers
_ans = []
for a in ans:
if a not in _ans:
_ans.append(a)
ans = _ans
# some predictions for MATH questions also repeats answers
_pred = []
for a in pred:
if a not in _pred:
_pred.append(a)
# some predictions mistakenly box non-answer strings
pred = _pred[-len(ans):]
item.update({
pred_key: pred,
'answer': ans
})
return is_correct(item, pred_key=pred_key, prec=prec)
def eval_last_single_answer(item, pred_key='prediction', prec=1e-3):
for key in [pred_key, 'answer']:
assert isinstance(item[key], str), f"{key} = `{item[key]}` is not a str"
return is_correct(item, pred_key=pred_key, prec=prec)
def eval_agieval_gaokao_math_cloze(item, pred_key='prediction', prec=1e-3):
if pred_key == 'program_output' and isinstance(item[pred_key], str):
item[pred_key] = [item[pred_key]]
for key in [pred_key, 'answer']:
assert isinstance(item[key], list), f"{key} = `{item[key]}` is not a list"
pred = item[pred_key]
ans = item['answer']
_pred = []
for p in pred:
p = p + ";"
while p:
left_brackets = 0
for i in range(len(p)):
if p[i] == ';' or (p[i] == ',' and left_brackets == 0):
_p, p = p[:i].strip(), p[i + 1:].strip()
if _p not in _pred:
_pred.append(_p)
break
elif p[i] in '([{':
left_brackets += 1
elif p[i] in ')]}':
left_brackets -= 1
pred = _pred[-len(ans):]
if len(pred) == len(ans):
for p, a in zip(pred, ans):
item.update({
pred_key: p,
'answer': a,
})
if not is_correct(item, pred_key=pred_key, prec=prec):
return False
return True
else:
return False
def eval_agieval_gaokao_mathqa(item, pred_key='prediction', prec=1e-3):
if pred_key == 'program_output' and isinstance(item[pred_key], str):
item[pred_key] = [item[pred_key]]
pred_str = " ".join(item[pred_key])
ans = item['answer']
tag = None
idx = -1
for t in 'ABCD':
if t in pred_str and pred_str.index(t) > idx:
tag = t
idx = pred_str.index(t)
return tag == ans
def eval_math_sat(item, pred_key='prediction', prec=1e-3):
for key in [pred_key, 'answer']:
assert isinstance(item[key], str), f"{key} = `{item[key]}` is not a str"
return item[pred_key].lower() == item['answer'].lower()
def eval_mmlu_stem(item, pred_key='prediction', prec=1e-3):
return eval_math_sat(item, pred_key=pred_key, prec=prec)
def eval_ocwcourses(item, pred_key='prediction', prec=1e-3):
INVALID_ANSWER = "[invalidanswer]"
for key in [pred_key, 'answer']:
assert isinstance(item[key], str), f"{key} = `{item[key]}` is not a str"
pred = item[pred_key]
ans = item['answer']
try:
float(ans)
normalize_fn = normalize_numeric
is_equiv = numeric_equality
answer_type = "numeric"
except ValueError:
if "=" in ans:
normalize_fn = normalize_symbolic_equation
is_equiv = lambda x, y: x == y
answer_type = "equation"
else:
normalize_fn = SymbolicMathMixin().normalize_tex
is_equiv = SymbolicMathMixin().is_tex_equiv
answer_type = "expression"
correct_answer = normalize_fn(ans)
unnormalized_answer = pred if pred else INVALID_ANSWER
model_answer = normalize_fn(unnormalized_answer)
if unnormalized_answer == INVALID_ANSWER:
acc = 0
elif model_answer == INVALID_ANSWER:
acc = 0
elif is_equiv(model_answer, correct_answer):
acc = 1
else:
acc = 0
return acc
def eval_minif2f_isabelle(item, pred_key='prediction', prec=1e-3):
return True
+330
View File
@@ -0,0 +1,330 @@
import multiprocessing
from math import isclose
import numpy as np
from typing import Union, Any, Dict
from sympy import simplify, N
from sympy.parsing.sympy_parser import parse_expr
from sympy.parsing.latex import parse_latex
import re
import regex
from data.deepseek_math_utils.answer_extraction import extract_answer, extract_program_output, strip_string
def extract_program(result: str, last_only=True):
"""
extract the program after "```python", and before "```"
"""
program = ""
start = False
for line in result.split("\n"):
if line.startswith("```python"):
if last_only:
program = "" # only extract the last program
else:
program += "\n# ========\n"
start = True
elif line.startswith("```"):
start = False
elif start:
program += line + "\n"
return program
def parse_ground_truth(example: Dict[str, Any], data_name):
if 'gt_cot' in example:
return example['gt_cot'], strip_string(example['gt'])
# parse ground truth
if data_name in ["math", 'ocw']:
gt_cot = example['solution']
gt_ans = extract_answer(gt_cot)
elif data_name == "gsm8k":
gt_cot, gt_ans = example['answer'].split("####")
elif data_name == "gsm-hard":
gt_cot, gt_ans = example['code'], example['target']
elif data_name == "svamp":
gt_cot, gt_ans = example['Equation'], example['Answer']
elif data_name == "asdiv":
gt_cot = example['formula']
gt_ans = re.sub(r"\(.*?\)", "", example['answer'])
elif data_name == "mawps":
gt_cot, gt_ans = None, example['target']
elif data_name == "tabmwp":
gt_cot = example['solution']
gt_ans = example['answer']
if example['ans_type'] in ['integer_number', 'decimal_number']:
if '/' in gt_ans:
gt_ans = int(gt_ans.split('/')[0]) / int(gt_ans.split('/')[1])
elif ',' in gt_ans:
gt_ans = float(gt_ans.replace(',', ''))
elif '%' in gt_ans:
gt_ans = float(gt_ans.split('%')[0]) / 100
else:
gt_ans = float(gt_ans)
elif data_name == "bbh":
gt_cot, gt_ans = None, example['target']
else:
raise NotImplementedError(data_name)
# post process
gt_cot = str(gt_cot).strip()
gt_ans = strip_string(gt_ans)
return gt_cot, gt_ans
def parse_question(example, data_name):
question = ""
if data_name == "asdiv":
question = f"{example['body'].strip()} {example['question'].strip()}"
elif data_name == "svamp":
body = example["Body"].strip()
if not body.endswith("."):
body = body + "."
question = f'{body} {example["Question"].strip()}'
elif data_name == "tabmwp":
title_str = f'regarding "{example["table_title"]}" ' if example['table_title'] else ""
question = f'Read the following table {title_str}and answer a question:\n'
question += f'{example["table"]}\n{example["question"]}'
if example['choices']:
question += f' Please select from the following options: {example["choices"]}'
else:
for key in ['question', 'problem', 'Question', 'input']:
if key in example:
question = example[key]
break
assert question != ""
return question.strip()
def run_execute(executor, result, prompt_type, execute=False):
if not result or result == 'error':
return None, None
report = None
if "program_only" in prompt_type:
prediction = extract_program_output(result)
elif prompt_type in ["pot", "pal"] and execute:
code = extract_program(result)
prediction, report = executor.apply(code)
else:
prediction = extract_answer(result)
prediction = strip_string(prediction)
return prediction, report
def parse_digits(num):
# format: 234.23 || 23%
num = regex.sub(',', '', str(num))
try:
return float(num)
except:
if num.endswith('%'):
num = num[:-1]
if num.endswith('\\'):
num = num[:-1]
try:
return float(num) / 100
except:
pass
return None
def is_digit(num):
# paired with parse_digits
return parse_digits(num) is not None
def normalize_prediction(prediction):
try: # 1. numerical equal
if is_digit(prediction):
prediction = np.round(float(str(prediction).replace(",", "")), 6)
return str(prediction)
except:
pass
# 2. symbolic equal
prediction = str(prediction).strip()
## deal with [], (), {}
brackets = []
while prediction.startswith("[") and prediction.endswith("]") or (prediction.startswith("(") and prediction.endswith(")")):
bracket = prediction[0]
prediction = prediction[1:-1]
if brackets and ',' in prediction:
pred_parts = [normalize_prediction(part) for part in prediction.split(",")]
prediction = ",".join(pred_parts)
if brackets:
for b in reversed(brackets):
if b == '[':
prediction = '[' + prediction + ']'
else:
assert b == '('
prediction = '(' + prediction + ')'
def _parse(s):
for f in [parse_latex, parse_expr]:
try:
return f(s)
except:
pass
return s
prediction = _parse(prediction)
for s in ['{', "}", "(", ")"]:
prediction = prediction.replace(s, "")
return prediction
def math_equal(prediction: Union[bool, float, str],
reference: Union[float, str],
include_percentage: bool = True,
is_close: bool = True,
timeout: bool = False,
) -> bool:
"""
Exact match of math if and only if:
1. numerical equal: both can convert to float and are equal
2. symbolic equal: both can convert to sympy expression and are equal
"""
if str(prediction) == str(reference):
return True
try: # 1. numerical equal
if is_digit(prediction) and is_digit(reference):
prediction = parse_digits(prediction)
reference = parse_digits(reference)
# number questions
if include_percentage:
gt_result = [reference / 100, reference, reference * 100]
else:
gt_result = [reference]
for item in gt_result:
try:
if is_close:
if isclose(item, prediction, abs_tol=1e-3):
return True
else:
if item == prediction:
return True
except Exception:
continue
return False
except:
pass
if not prediction and prediction not in [0, False]:
return False
# 2. symbolic equal
reference = str(reference).strip()
prediction = str(prediction).strip()
if regex.match(r'(\(|\[).+(\)|\])', prediction) is not None and regex.match(r'(\(|\[).+(\)|\])', reference) is not None:
pred_parts = prediction[1:-1].split(",")
ref_parts = reference[1:-1].split(",")
if len(pred_parts) == len(ref_parts):
if all([math_equal(pred_parts[i], ref_parts[i], include_percentage, is_close) for i in range(len(pred_parts))]):
return True
if (prediction.startswith("\\begin{pmatrix}") or prediction.startswith("\\begin{bmatrix}")) and (
prediction.endswith("\\end{pmatrix}") or prediction.endswith("\\end{bmatrix}")) and \
(reference.startswith("\\begin{pmatrix}") or reference.startswith("\\begin{bmatrix}")) and (
reference.endswith("\\end{pmatrix}") or reference.endswith("\\end{bmatrix}")):
pred_lines = [line.strip() for line in prediction[len("\\begin{pmatrix}"): -len("\\end{pmatrix}")].split("\\\\") if line.strip()]
ref_lines = [line.strip() for line in reference[len("\\begin{pmatrix}"): -len("\\end{pmatrix}")].split("\\\\") if line.strip()]
matched = True
if len(pred_lines) == len(ref_lines):
for pred_line, ref_line in zip(pred_lines, ref_lines):
pred_parts = pred_line.split("&")
ref_parts = ref_line.split("&")
if len(pred_parts) == len(ref_parts):
if not all([math_equal(pred_parts[i], ref_parts[i], include_percentage, is_close) for i in range(len(pred_parts))]):
matched = False
break
else:
matched = False
if not matched:
break
else:
matched = False
if matched:
return True
if prediction.count('=') == 1 and reference.count('=') == 1:
pred = prediction.split('=')
pred = f"{pred[0].strip()} - ({pred[1].strip()})"
ref = reference.split('=')
ref = f"{ref[0].strip()} - ({ref[1].strip()})"
if symbolic_equal(pred, ref) or symbolic_equal(f"-({pred})", ref):
return True
elif prediction.count('=') == 1 and len(prediction.split('=')[0].strip()) <= 2 and '=' not in reference:
if math_equal(prediction.split('=')[1], reference, include_percentage, is_close):
return True
elif reference.count('=') == 1 and len(reference.split('=')[0].strip()) <= 2 and '=' not in prediction:
if math_equal(prediction, reference.split('=')[1], include_percentage, is_close):
return True
# symbolic equal with sympy
if timeout:
if call_with_timeout(symbolic_equal_process, prediction, reference):
return True
else:
if symbolic_equal(prediction, reference):
return True
return False
def math_equal_process(param):
return math_equal(param[-2], param[-1])
def symbolic_equal(a, b):
def _parse(s):
for f in [parse_latex, parse_expr]:
try:
return f(s)
except:
pass
return s
a = _parse(a)
b = _parse(b)
try:
if simplify(str(a - b)) == 0:
return True
except:
pass
try:
if isclose(N(a), N(b), abs_tol=1e-3):
return True
except:
pass
return False
def symbolic_equal_process(a, b, output_queue):
result = symbolic_equal(a, b)
output_queue.put(result)
def call_with_timeout(func, *args, timeout=1, **kwargs):
output_queue = multiprocessing.Queue()
process_args = args + (output_queue,)
process = multiprocessing.Process(target=func, args=process_args, kwargs=kwargs)
process.start()
process.join(timeout)
if process.is_alive():
process.terminate()
process.join()
return False
return output_queue.get()
@@ -0,0 +1,268 @@
import re
import numpy as np
import sympy
from sympy.core.sympify import SympifyError
from sympy.parsing.latex import parse_latex
import signal
INVALID_ANSWER = "[invalidanswer]"
class timeout:
def __init__(self, seconds=1, error_message="Timeout"):
self.seconds = seconds
self.error_message = error_message
def handle_timeout(self, signum, frame):
raise TimeoutError(self.error_message)
def __enter__(self):
signal.signal(signal.SIGALRM, self.handle_timeout)
signal.alarm(self.seconds)
def __exit__(self, type, value, traceback):
signal.alarm(0)
def normalize_numeric(s):
if s is None:
return None
for unit in [
"eV",
" \\mathrm{~kg} \\cdot \\mathrm{m} / \\mathrm{s}",
" kg m/s",
"kg*m/s",
"kg",
"m/s",
"m / s",
"m s^{-1}",
"\\text{ m/s}",
" \\mathrm{m/s}",
" \\text{ m/s}",
"g/mole",
"g/mol",
"\\mathrm{~g}",
"\\mathrm{~g} / \\mathrm{mol}",
"W",
"erg/s",
"years",
"year",
"cm",
]:
s = s.replace(unit, "")
s = s.strip()
for maybe_unit in ["m", "s", "cm"]:
s = s.replace("\\mathrm{" + maybe_unit + "}", "")
s = s.replace("\\mathrm{~" + maybe_unit + "}", "")
s = s.strip()
s = s.strip("$")
try:
return float(eval(s))
except:
try:
expr = parse_latex(s)
if expr.is_number:
return float(expr)
return INVALID_ANSWER
except:
return INVALID_ANSWER
def numeric_equality(n1, n2, threshold=0.01):
if n1 is None or n2 is None:
return False
if np.isclose(n1, 0) or np.isclose(n2, 0) or np.isclose(n1 - n2, 0):
return np.abs(n1 - n2) < threshold * (n1 + n2) / 2
else:
return np.isclose(n1, n2)
def normalize_symbolic_equation(s):
if not isinstance(s, str):
return INVALID_ANSWER
if s.startswith("\\["):
s = s[2:]
if s.endswith("\\]"):
s = s[:-2]
s = s.replace("\\left(", "(")
s = s.replace("\\right)", ")")
s = s.replace("\\\\", "\\")
if s.startswith("$") or s.endswith("$"):
s = s.strip("$")
try:
maybe_expression = parse_latex(s)
if not isinstance(maybe_expression, sympy.core.relational.Equality):
# we have equation, not expression
return INVALID_ANSWER
else:
return maybe_expression
except:
return INVALID_ANSWER
class SymbolicMathMixin:
"""
Methods useful for parsing mathematical expressions from text and determining equivalence of expressions.
"""
SUBSTITUTIONS = [ # used for text normalize
("an ", ""),
("a ", ""),
(".$", "$"),
("\\$", ""),
(r"\ ", ""),
(" ", ""),
("mbox", "text"),
(",\\text{and}", ","),
("\\text{and}", ","),
("\\text{m}", "\\text{}"),
]
REMOVED_EXPRESSIONS = [ # used for text normalizer
"square",
"ways",
"integers",
"dollars",
"mph",
"inches",
"ft",
"hours",
"km",
"units",
"\\ldots",
"sue",
"points",
"feet",
"minutes",
"digits",
"cents",
"degrees",
"cm",
"gm",
"pounds",
"meters",
"meals",
"edges",
"students",
"childrentickets",
"multiples",
"\\text{s}",
"\\text{.}",
"\\text{\ns}",
"\\text{}^2",
"\\text{}^3",
"\\text{\n}",
"\\text{}",
r"\mathrm{th}",
r"^\circ",
r"^{\circ}",
r"\;",
r",\!",
"{,}",
'"',
"\\dots",
]
def normalize_tex(self, final_answer: str) -> str:
"""
Normalizes a string representing a mathematical expression.
Used as a preprocessing step before parsing methods.
Copied character for character from appendix D of Lewkowycz et al. (2022)
"""
final_answer = final_answer.split("=")[-1]
for before, after in self.SUBSTITUTIONS:
final_answer = final_answer.replace(before, after)
for expr in self.REMOVED_EXPRESSIONS:
final_answer = final_answer.replace(expr, "")
# Extract answer that is in LaTeX math, is bold,
# is surrounded by a box, etc.
final_answer = re.sub(r"(.*?)(\$)(.*?)(\$)(.*)", "$\\3$", final_answer)
final_answer = re.sub(r"(\\text\{)(.*?)(\})", "\\2", final_answer)
final_answer = re.sub(r"(\\textbf\{)(.*?)(\})", "\\2", final_answer)
final_answer = re.sub(r"(\\overline\{)(.*?)(\})", "\\2", final_answer)
final_answer = re.sub(r"(\\boxed\{)(.*)(\})", "\\2", final_answer)
# Normalize shorthand TeX:
# \fracab -> \frac{a}{b}
# \frac{abc}{bef} -> \frac{abc}{bef}
# \fracabc -> \frac{a}{b}c
# \sqrta -> \sqrt{a}
# \sqrtab -> sqrt{a}b
final_answer = re.sub(r"(frac)([^{])(.)", "frac{\\2}{\\3}", final_answer)
final_answer = re.sub(r"(sqrt)([^{])", "sqrt{\\2}", final_answer)
final_answer = final_answer.replace("$", "")
# Normalize 100,000 -> 100000
if final_answer.replace(",", "").isdigit():
final_answer = final_answer.replace(",", "")
return final_answer
def parse_tex(self, text: str, time_limit: int = 5) -> sympy.Basic:
"""
Wrapper around `sympy.parse_text` that outputs a SymPy expression.
Typically, you want to apply `normalize_text` as a preprocessing step.
"""
try:
with timeout(seconds=time_limit):
parsed = parse_latex(text)
except (
# general error handling: there is a long tail of possible sympy/other
# errors we would like to catch
Exception
) as e:
print(f"failed to parse {text} with exception {e}")
return None
return parsed
def is_exp_equiv(self, x1: sympy.Basic, x2: sympy.Basic, time_limit=5) -> bool:
"""
Determines whether two sympy expressions are equal.
"""
try:
with timeout(seconds=time_limit):
try:
diff = x1 - x2
except (SympifyError, ValueError, TypeError) as e:
print(
f"Couldn't subtract {x1} and {x2} with exception {e}"
)
return False
try:
if sympy.simplify(diff) == 0:
return True
else:
return False
except (SympifyError, ValueError, TypeError) as e:
print(f"Failed to simplify {x1}-{x2} with {e}")
return False
except TimeoutError as e:
print(f"Timed out comparing {x1} and {x2}")
return False
except Exception as e:
print(f"failed on unrecognized exception {e}")
return False
def is_tex_equiv(self, x1: str, x2: str, time_limit=5) -> bool:
"""
Determines whether two (ideally normalized using `normalize_text`) TeX expressions are equal.
Does so by first checking for string exact-match, then falls back on sympy-equivalence,
following the (Lewkowycz et al. 2022) methodology.
"""
if x1 == x2:
# don't resort to sympy if we have full string match, post-normalization
return True
else:
return False
parsed_x2 = self.parse_tex(x2)
if not parsed_x2:
# if our reference fails to parse into a Sympy object,
# we forgo parsing + checking our generated answer.
return False
return self.is_exp_equiv(self.parse_tex(x1), parsed_x2, time_limit=time_limit)
+383
View File
@@ -0,0 +1,383 @@
import collections
import copy
import json
import os.path
import random
from glob import glob
from typing import List, Dict, Tuple, Union, Any, Callable, Optional
import torch
from omegaconf import DictConfig
from omegaconf.listconfig import ListConfig
from torch.utils.data import Dataset
from transformers import PreTrainedTokenizer
from data.math import decompose_deepseek_math_cot_v2
from general_util.logger import get_child_logger
logger = get_child_logger(__name__)
class DPOCollator:
def __init__(self, tokenizer: PreTrainedTokenizer, max_seq_length: int, padding: str = "longest"):
self.tokenizer = tokenizer
self.max_seq_length = max_seq_length
self.padding = padding
def __call__(self, batch):
chosen = [item["chosen"] for item in batch]
reject = [item["reject"] for item in batch]
indices = [item["index"] for item in batch]
text_inputs = chosen + reject
text_prompts = []
for item in batch:
if "chosen_prompt" in item:
text_prompts.append(item["chosen_prompt"])
else:
text_prompts.append(item["prompt"])
for item in batch:
if "reject_prompt" in item:
text_prompts.append(item["reject_prompt"])
else:
text_prompts.append(item["prompt"])
encoded_prompts = self.tokenizer(text_prompts, padding=self.padding, truncation=True, max_length=self.max_seq_length, return_tensors="pt")
input_lens = torch.sum(encoded_prompts["attention_mask"], dim=-1)
encoded_inputs = self.tokenizer(text_inputs, padding=self.padding, truncation=True, max_length=self.max_seq_length, return_tensors="pt")
if self.tokenizer.padding_side == "left":
padding_len = torch.sum(1 - encoded_inputs["attention_mask"], dim=-1)
input_lens = input_lens + padding_len
labels = encoded_inputs["input_ids"].clone()
prompt_mask = torch.arange(encoded_inputs["input_ids"].size(1))[None, :] < input_lens[:, None]
# TODO: @2024/09/13
# There is another case that the chosen prompt is sth. like <prompt> + <space> + <eos>
# Since usually I also set pad_token as eos_token, then the labels here could be all pad_token.
# This could cause NAN loss when computing SFT loss.
if prompt_mask.sum() == labels.numel(): # FIXME: This could also induce NAN loss during DPO with SFT loss. @2024/08/09
logger.warning(f"Prompt mask is all True. Indices: {indices}")
prompt_mask[0, -1] = False
labels[prompt_mask] = self.tokenizer.pad_token_id
encoded_inputs["labels"] = labels
encoded_inputs["meta_data"] = {
"index": indices,
"prompt": text_prompts,
"chosen": chosen,
"reject": reject,
}
return encoded_inputs
class DPODataSFTCollator:
"""
Note that when you are using the DPO pair dataset, you may overlook the oversampling of chosen samples.
"""
def __init__(self, tokenizer: PreTrainedTokenizer, max_seq_length: int):
self.tokenizer = tokenizer
self.max_seq_length = max_seq_length
def __call__(self, batch):
prompt = [item["prompt"] for item in batch]
chosen = [item["chosen"] for item in batch]
indices = [item["index"] for item in batch]
text_prompts = prompt
text_inputs = chosen
encoded_prompts = self.tokenizer(text_prompts, padding="longest", truncation=True, max_length=self.max_seq_length, return_tensors="pt")
input_lens = torch.sum(encoded_prompts["attention_mask"], dim=-1)
encoded_inputs = self.tokenizer(text_inputs, padding="longest", truncation=True, max_length=self.max_seq_length, return_tensors="pt")
if self.tokenizer.padding_side == "left":
padding_len = torch.sum(1 - encoded_inputs["attention_mask"], dim=-1)
input_lens = input_lens + padding_len
labels = encoded_inputs["input_ids"].clone()
prompt_mask = torch.arange(encoded_inputs["input_ids"].size(1))[None, :] < input_lens[:, None]
if prompt_mask.sum() == labels.numel():
logger.warning(f"Prompt mask is all True. Indices: {indices}")
prompt_mask[0, -1] = False
labels[prompt_mask] = self.tokenizer.pad_token_id
encoded_inputs["labels"] = labels
encoded_inputs["meta_data"] = {
"index": indices,
"prompt": prompt,
"chosen": chosen,
"response": chosen,
}
if "label" in batch[0]:
encoded_inputs["meta_data"]["label"] = [item["label"] for item in batch]
return encoded_inputs
class DPOCollatorWithExtraInputs:
def __init__(self, tokenizer: PreTrainedTokenizer, max_seq_length: int, padding: str = "longest", extra_keys: List[str] = None):
self.tokenizer = tokenizer
self.max_seq_length = max_seq_length
self.padding = padding
self.extra_keys = extra_keys
def __call__(self, batch):
chosen = [item["chosen"] for item in batch]
reject = [item["reject"] for item in batch]
indices = [item["index"] for item in batch]
text_inputs = chosen + reject
text_prompts = []
for item in batch:
if "chosen_prompt" in item:
text_prompts.append(item["chosen_prompt"])
else:
text_prompts.append(item["prompt"])
for item in batch:
if "reject_prompt" in item:
text_prompts.append(item["reject_prompt"])
else:
text_prompts.append(item["prompt"])
encoded_prompts = self.tokenizer(text_prompts, padding=self.padding, truncation=True, max_length=self.max_seq_length, return_tensors="pt")
input_lens = torch.sum(encoded_prompts["attention_mask"], dim=-1)
encoded_inputs = self.tokenizer(text_inputs, padding=self.padding, truncation=True, max_length=self.max_seq_length, return_tensors="pt")
if self.tokenizer.padding_side == "left":
padding_len = torch.sum(1 - encoded_inputs["attention_mask"], dim=-1)
input_lens = input_lens + padding_len
labels = encoded_inputs["input_ids"].clone()
prompt_mask = torch.arange(encoded_inputs["input_ids"].size(1))[None, :] < input_lens[:, None]
# TODO: @2024/09/13
# There is another case that the chosen prompt is sth. like <prompt> + <space> + <eos>
# Since usually I also set pad_token as eos_token, then the labels here could be all pad_token.
# This could cause NAN loss when computing SFT loss.
if prompt_mask.sum() == labels.numel(): # FIXME: This could also induce NAN loss during DPO with SFT loss. @2024/08/09
logger.warning(f"Prompt mask is all True. Indices: {indices}")
prompt_mask[0, -1] = False
labels[prompt_mask] = self.tokenizer.pad_token_id
encoded_inputs["labels"] = labels
for k in self.extra_keys:
_ex_inputs = [item[k] for item in batch]
_ex_inputs = torch.tensor(_ex_inputs, dtype=torch.float)
encoded_inputs[k] = _ex_inputs
encoded_inputs["meta_data"] = {
"index": indices,
"prompt": text_prompts,
"chosen": chosen,
"reject": reject,
}
return encoded_inputs
class Trajectory2ValueCollator:
def __init__(self, tokenizer: PreTrainedTokenizer, max_seq_length: int):
self.tokenizer = tokenizer
self.max_seq_length = max_seq_length
def __call__(self, batch):
prompt = [item["prompt"] for item in batch]
inputs = [item["input"] for item in batch]
indices = [item["index"] for item in batch]
values = [item["value"] for item in batch]
text_prompts = prompt
text_inputs = inputs
encoded_prompts = self.tokenizer(text_prompts, padding="longest", truncation=True, max_length=self.max_seq_length, return_tensors="pt")
input_lens = torch.sum(encoded_prompts["attention_mask"], dim=-1)
encoded_inputs = self.tokenizer(text_inputs, padding="longest", truncation=True, max_length=self.max_seq_length, return_tensors="pt")
if self.tokenizer.padding_side == "left":
padding_len = torch.sum(1 - encoded_inputs["attention_mask"], dim=-1)
input_lens = input_lens + padding_len
labels = encoded_inputs["input_ids"].clone()
prompt_mask = torch.arange(encoded_inputs["input_ids"].size(1))[None, :] < input_lens[:, None]
if prompt_mask.sum() == labels.numel():
logger.warning(f"Prompt mask is all True. Indices: {indices}")
prompt_mask[0, -1] = False
labels[prompt_mask] = self.tokenizer.pad_token_id
encoded_inputs["labels"] = labels
encoded_inputs["values"] = torch.tensor(values, dtype=torch.long)
encoded_inputs["meta_data"] = {
"index": indices,
"prompt": prompt,
"input": inputs,
"response": inputs,
"label": values,
}
return encoded_inputs
class StepEndingsCollator:
def __init__(self, tokenizer: PreTrainedTokenizer, max_seq_length: int):
self.tokenizer = tokenizer
self.max_seq_length = max_seq_length
def __call__(self, batch):
prompt = [item["prompt"] for item in batch]
chosen = [item["chosen"] for item in batch]
indices = [item["index"] for item in batch]
text_prompts = prompt
text_inputs = chosen
encoded_prompts = self.tokenizer(text_prompts, padding="longest", truncation=True, max_length=self.max_seq_length, return_tensors="pt")
input_lens = torch.sum(encoded_prompts["attention_mask"], dim=-1)
encoded_inputs = self.tokenizer(text_inputs, padding="longest", truncation=True, max_length=self.max_seq_length, return_tensors="pt")
if self.tokenizer.padding_side == "left":
padding_len = torch.sum(1 - encoded_inputs["attention_mask"], dim=-1)
input_lens = input_lens + padding_len
else:
padding_len = torch.zeros(len(batch), dtype=torch.long)
labels = encoded_inputs["input_ids"].clone()
prompt_mask = torch.arange(encoded_inputs["input_ids"].size(1))[None, :] < input_lens[:, None]
if prompt_mask.sum() == labels.numel():
logger.warning(f"Prompt mask is all True. Indices: {indices}")
prompt_mask[0, -1] = False
labels[prompt_mask] = self.tokenizer.pad_token_id
endings = []
for b, item in enumerate(batch):
ending = decompose_deepseek_math_cot_v2(item["prompt"], item["response"], self.max_seq_length, self.tokenizer)
ending = [e + padding_len[b].item() for e in ending]
endings.append(ending)
encoded_inputs["labels"] = labels
encoded_inputs["meta_data"] = {
"index": indices,
"prompt": prompt,
"chosen": chosen,
"response": [item["response"] for item in batch],
"ending": endings,
"type": [None] * len(endings),
}
if "label" in batch[0]:
encoded_inputs["meta_data"]["label"] = [item["label"] for item in batch]
return encoded_inputs
def iterative_mask(text_segment_list: List[List[str]], masks: List[int], tokenizer: PreTrainedTokenizer, **tokenize_kwargs):
if len(text_segment_list) == 0:
raise ValueError("Input groups should be greater than 0.")
if len(text_segment_list) == 1:
return tokenizer(text_segment_list[0], **tokenize_kwargs), None
assert len(masks) == 1 or masks[0] == 0, "The prefix should always be masked if there are multiple groups of inputs"
all_input_lens = []
all_inputs = []
for group in text_segment_list:
group_inputs = tokenizer(group, **tokenize_kwargs)
all_inputs.append(group_inputs)
all_input_lens.append(torch.sum(group_inputs["attention_mask"], dim=-1, keepdim=True))
if tokenizer.padding_side == "left":
# If left padding, we should first compute the padding length at last
padding_len = torch.sum(1 - all_inputs[-1]["attention_mask"], dim=-1, keepdim=True)
else:
padding_len = 0
last_len = torch.zeros(all_inputs[-1]["input_ids"].size(0), 1, dtype=torch.long)
prompt_mask = torch.zeros(all_inputs[-1]["input_ids"].shape, dtype=torch.long)
seq_range = torch.arange(all_inputs[-1]["input_ids"].size(1))
for _acc_lens, mask in zip(all_input_lens, masks):
_condition_lens = padding_len + _acc_lens
if mask == 0:
group_mask = (seq_range[None, :] < _condition_lens) & (last_len <= seq_range[None, :])
prompt_mask += group_mask
last_len = _condition_lens
return all_inputs[-1], prompt_mask
class SFTFoldAttnMaskCollator:
def __init__(self, tokenizer: PreTrainedTokenizer, max_seq_length: int, text_keys: List[str], text_masks: List[int]):
self.tokenizer = tokenizer
self.max_seq_length = max_seq_length
self.text_keys = text_keys
self.text_masks = text_masks
def __call__(self, batch):
text_segment_list = []
for b in batch:
assert self.text_keys[0] in b, f"At least the first group of inputs is contained in the batch, got {list(b.keys())}"
last_input = b[self.text_keys[0]]
for k in self.text_keys[1:]:
if k not in b:
b[k] = last_input
last_input = b[k]
for i, key in enumerate(self.text_keys):
batch_item = [b[key] for b in batch]
text_segment_list.append(batch_item)
indices = [item["index"] for item in batch]
encoded_inputs, prompt_mask = iterative_mask(text_segment_list, self.text_masks, self.tokenizer,
padding="longest", truncation=True, max_length=self.max_seq_length, return_tensors="pt")
labels = encoded_inputs["input_ids"].clone()
if prompt_mask is not None:
if prompt_mask.sum() == labels.numel():
logger.warning(f"Prompt mask is all True. Indices: {indices}")
prompt_mask[0, -1] = False
labels[prompt_mask] = self.tokenizer.pad_token_id
encoded_inputs["labels"] = labels
encoded_inputs["meta_data"] = {
"index": indices,
}
encoded_inputs["meta_data"].update({
k: text_segment_list[i] for i, k in enumerate(self.text_keys)
})
if "label" in batch[0]:
encoded_inputs["meta_data"]["label"] = [item["label"] for item in batch]
return encoded_inputs
class TextPromptCollator:
def __init__(self, tokenizer: PreTrainedTokenizer, max_seq_length: int, padding: str = "longest", extra_text_inputs: DictConfig[str, bool] = None,
**kwargs):
self.tokenizer: PreTrainedTokenizer = tokenizer
self.max_seq_length = max_seq_length
self.padding = padding
self.extra_text_inputs = extra_text_inputs
def __call__(self, batch):
inputs = [b["input"] for b in batch]
index = [b["index"] for b in batch]
model_inputs = self.tokenizer(inputs, padding=self.padding, truncation=True, max_length=self.max_seq_length,
return_tensors="pt")
if self.extra_text_inputs is not None:
for k, v in self.extra_text_inputs.items():
_ex_inputs = [b[k] for b in batch]
if v:
model_inputs[k] = self.tokenizer(_ex_inputs, padding=self.padding, truncation=True, max_length=self.max_seq_length,
return_tensors="pt")
else:
model_inputs[k] = _ex_inputs
model_inputs["meta_data"] = {
"inputs": inputs,
"index": index,
}
return model_inputs
+49
View File
@@ -0,0 +1,49 @@
import json
import sys
from datasets import load_dataset
from general_util.logger import get_child_logger
logger = get_child_logger(__name__)
sys.set_int_max_str_digits(0)
class HumanEvalReader:
def __call__(self, file_path: str = "openai_humaneval"):
data = load_dataset(file_path, split="test").to_list()
logger.info(len(data))
return data
class MBPPReader:
def __init__(self, sanitized: bool = True):
self.sanitized = sanitized
def __call__(self, file_path: str = "mbpp"):
if self.sanitized:
dataset = load_dataset("mbpp", "sanitized", split="test").to_list()
else:
dataset = load_dataset("mbpp", split="test").to_list()
for item in dataset:
item["test_list"] = "\n".join(item["test_list"])
return dataset
class MBPPReaderFixed:
def __init__(self, sanitized: bool = True):
self.sanitized = sanitized
def __call__(self, file_path: str = "mbpp"):
if self.sanitized:
dataset = load_dataset("mbpp", "sanitized", split="test").to_list()
else:
dataset = load_dataset("mbpp", split="test").to_list()
for item in dataset:
item["test_list"] = "\n".join(item["test_list"])
return dataset
+592
View File
@@ -0,0 +1,592 @@
import copy
import json
import os.path
import random
from functools import partial
from glob import glob
from multiprocessing.pool import Pool
from typing import Dict, List, Callable, Union
from omegaconf.listconfig import ListConfig
from tqdm import tqdm
from general_util.logger import get_child_logger
logger = get_child_logger(__name__)
def _format_option_list(option_list: List[str], _rank2option: List[str]) -> str:
res = []
for op_id, op in enumerate(option_list):
res.append(f"{_rank2option[op_id]}. {op}")
return "\n".join(res)
def option_id2str_aligner():
option_id2str = ["A", "B", "C", "D", "E"]
def func(data: List[Dict]):
for sample in data:
sample["str_label"] = option_id2str[sample["label"]]
return data
return func
def key_based_filter_aligner(key, value):
if isinstance(value, ListConfig):
value = list(value)
if not isinstance(value, list):
value = [value]
def func(data: List[Dict]):
return [item for item in data if item[key] in value]
return func
def dpo_confidence_ratio_filter(lower_bound: float, upper_bound: float, pos_field: str, response_field: str):
def func(item):
pos_num = len(item[pos_field])
total_num = len(item[response_field])
ratio = pos_num / total_num
if lower_bound <= ratio <= upper_bound:
return True
return False
return func
def filter_aligner(filter_func: Callable):
def func(data: List[Dict]):
return [item for item in data if filter_func(item)]
return func
def json_field2str(key, val: str = None, indent: int = 4):
def func(data: List[Dict]):
for item in data:
if val:
item[val] = json.dumps(item[key], indent=indent, ensure_ascii=False)
else:
item[key] = json.dumps(item[key], indent=indent, ensure_ascii=False)
return data
return func
def starts_with_filter(key, value):
def func(data: List[Dict]):
return [item for item in data if item[key].startswith(value)]
return func
def not_none_filter(key):
def func(data: List[Dict]):
return [item for item in data if key in item and item[key] not in ["", None, []]]
return func
def rename_field_aligner(kv_pair: Dict):
def func(data: List[Dict]):
for item in data:
for k, v in kv_pair.items():
tmp = item.pop(k)
item[v] = tmp
return data
return func
def field_extract_aligner(input_index_field: str, extract_index_field: str, extract_fields: List[str], extra_file: str, renamed_fields: Dict[str, str] = None):
if os.path.exists(extra_file):
extra_data = json.load(open(extra_file, encoding="utf-8"))
else:
extra_data = []
for file in glob(extra_file):
extra_data += json.load(open(file))
if len(extra_data) == 0:
raise ValueError(f"No data found in {extra_file}")
id2extra_data = {str(item[extract_index_field]): item for item in extra_data}
renaming = {}
for _field in extract_fields:
if renamed_fields and _field in renamed_fields:
renaming[_field] = renamed_fields[_field]
else:
renaming[_field] = _field
def func(data: List[Dict]):
missing = 0
missing_field = 0
outputs = []
for item in data:
item_id = str(item[input_index_field])
if item_id not in id2extra_data:
missing += 1
continue
extra_item = id2extra_data[item_id]
if any(x not in extra_item for x in extract_fields):
missing_field += 1
continue
for field in extract_fields:
item[renaming[field]] = extra_item[field]
outputs.append(item)
logger.info(f"Extracted {len(outputs)} items from {extra_file}")
logger.info(f"Missing {missing} items in {extra_file}")
logger.info(f"Missing {missing_field} fields in {extra_file}")
return outputs
return func
def flat_aligner(input_index_field: str, extract_field: Union[str, List[str]], mode: str = "single"):
if isinstance(extract_field, str):
extract_field = [extract_field]
def func(data: List[Dict]):
outputs = []
for item in data:
item_id = item[input_index_field]
# if not all(item[_field] for _field in extract_field):
# continue
if any(item[_field] in [None, "", []] for _field in extract_field):
continue
num = len(item[extract_field[0]])
for _field in extract_field[1:]:
assert len(item[_field]) == num, f"Length not match: {item[_field]}"
tmp_list = []
for i in range(num):
new_item = copy.deepcopy(item)
if any(item[_field][i] in [None, "", []] for _field in extract_field):
continue
for _field in extract_field:
new_item[_field] = item[_field][i]
new_item[input_index_field] = f"{item_id}_{i}"
tmp_list.append(new_item)
if mode == "single":
break
if len(tmp_list) == 0:
continue
if mode == "single":
outputs.append(tmp_list[0])
elif mode == "random":
outputs.append(random.choice(tmp_list))
else:
outputs += tmp_list
return outputs
return func
def option_flatten_aligner():
def func(data: List[Dict]):
for sample in data:
sample["option_list"] = _format_option_list(sample["options"], ["A", "B", "C", "D"])
return data
return func
def empty_aligner(data: List[Dict]):
return data
def add_id_aligner(id_field: str = "id"):
def func(data: List[Dict]):
for i, item in enumerate(data):
item[id_field] = i
return data
return func
def concat_aligner(aligners: List[Callable]):
def func(data: List[Dict]):
for aligner in aligners:
data = aligner(data)
return data
return func
def dpo_pair_aligner_cleaned(response_field: str = "response",
id_field: str = "id",
do_sample: bool = False, ):
"""
This aligner only accepts the cleaned file, which has removing all empty responses and combined with original data.
:return: Callable
"""
def func(data: List[Dict]):
outputs = []
for item in data:
pos_resp = []
neg_resp = []
for i, (resp, pred) in enumerate(zip(item[response_field], item["pred"])):
assert resp
# assert pred
if isinstance(resp, list):
assert isinstance(resp[0], str)
# assert "The answer is" in resp[-1], resp
resp = "".join(resp)
if isinstance(item["label"], str):
if pred == item["label"]:
pos_resp.append((i, resp))
else:
neg_resp.append((i, resp))
elif isinstance(item["label"], int):
if pred and ord(pred) - ord("A") == item["label"]:
pos_resp.append((i, resp))
else:
neg_resp.append((i, resp))
else:
raise ValueError(f"Unknown type of label: {type(item['label'])}")
if not (len(pos_resp) and len(neg_resp)):
continue
if do_sample:
pos = random.choice(pos_resp)
neg = random.choice(neg_resp)
pos_resp = [pos]
neg_resp = [neg]
for pos in pos_resp:
for neg in neg_resp:
new_item = copy.deepcopy(item)
new_item["pos"] = pos[1]
new_item["neg"] = neg[1]
new_item["pos_id"] = f"{item[id_field]}_{pos[0]}"
new_item["neg_id"] = f"{item[id_field]}_{neg[0]}"
outputs.append(new_item)
logger.info(f"Counted {len(outputs)} DPO contrastive pairs.")
return outputs
return func
def dpo_pair_aligner(pos_field: Union[str, ListConfig], neg_field: Union[str, ListConfig]):
def func(data: List[Dict]):
outputs = []
if isinstance(pos_field, str):
_pos_fields = [pos_field]
else:
_pos_fields = list(pos_field)
if isinstance(neg_field, str):
_neg_fields = [neg_field]
else:
_neg_fields = list(neg_field)
for item in tqdm(data, desc="DPO pair aligner", total=len(data)):
pos_resp = []
neg_resp = []
for _field in _pos_fields:
pos_resp += item[_field]
for _field in _neg_fields:
neg_resp += item[_field]
for pos in pos_resp:
for neg in neg_resp:
new_item = copy.deepcopy(item)
# To save memory
for _field in _pos_fields:
new_item.pop(_field)
for _field in _neg_fields:
new_item.pop(_field)
new_item["pos"] = pos
new_item["neg"] = neg
outputs.append(new_item)
logger.info(f"Counted {len(outputs)} DPO contrastive pairs.")
return outputs
return func
def eval_multiple_choice(item):
if isinstance(item, dict):
pred = item["prediction"]
label = item["answer"]
elif isinstance(item, tuple):
pred, label = item
else:
raise ValueError(f"Unknown type of item: {type(item)}")
if isinstance(label, str):
if pred == label:
return True
return False
if isinstance(label, int):
if pred and ord(pred) - ord("A") == label:
return True
return False
raise ValueError(f"Unknown type of label: {type(item['label'])}")
def prompt_fill_aligner(prompt_file: str, mapping: Dict[str, str], prompt_field: str = "prompt"):
full_prompt = open(prompt_file).read()
def func(data: List[Dict]):
for item in data:
prompt = copy.deepcopy(full_prompt)
for k, v in mapping.items():
prompt = prompt.replace(k, item[v])
item[prompt_field] = prompt
return data
return func
def value2pair_aligner(field: str, pos_field: str, neg_field: str, value_field: str):
def func(data: List[Dict]):
for item in data:
pair_data = item.pop(field)
values = item.pop(value_field)
pos = []
neg = []
for x, v in zip(pair_data, values):
if v:
pos.append(x)
else:
neg.append(x)
item[pos_field] = pos
item[neg_field] = neg
return data
return func
def return_threshold_mapping(value_threshold: float):
def func(v):
if v >= value_threshold:
return True
return False
return func
def return_binary_mapping():
def func(v):
if v is True:
return 1
return 0
return func
def value_mapping_aligner(value_field: str, value_mapping_func: Callable, return_int: bool = False):
def func(data: List[Dict]):
for item in data:
if return_int:
item[value_field] = int(value_mapping_func(item[value_field]))
else:
item[value_field] = value_mapping_func(item[value_field])
return data
return func
def dpo_pair2value_aligner(pos_field: str, neg_field: str, seq_field: str, value_field: str, flatten: bool = True):
def func(data: List[Dict]):
outputs = []
for item in data:
pos = item.pop(pos_field)
neg = item.pop(neg_field)
if flatten:
item_a = copy.deepcopy(item)
item_a[seq_field] = pos
item_a[value_field] = 1
item_b = copy.deepcopy(item)
item_b[seq_field] = neg
item_b[value_field] = 0
outputs.append(item_a)
outputs.append(item_b)
else:
item[seq_field] = [pos, neg]
item[value_field] = [1, 0]
outputs.append(item)
return outputs
return func
def value2pair_mapping_aligner(field: str, pos_field: str, neg_field: str, value_field: str, value_mapping_func: Callable):
def func(data: List[Dict]):
for item in data:
pair_data = item.pop(field)
values = item.pop(value_field)
pos = []
neg = []
for x, v in zip(pair_data, values):
if value_mapping_func(v):
pos.append(x)
else:
neg.append(x)
item[pos_field] = pos
item[neg_field] = neg
return data
return func
def dpo_random_choice_aligner(anchor_field: str, paired_field: str):
def func(data: List[Dict]):
outputs = []
for item in tqdm(data, desc="DPO random choice aligner", total=len(data)):
if len(item[anchor_field]) == 0:
continue
if len(item[paired_field]) == 0:
continue
for anchor in item[anchor_field]:
new_item = copy.deepcopy(item)
new_item[anchor_field] = anchor
new_item[paired_field] = random.choice(item[paired_field])
outputs.append(new_item)
return outputs
return func
def dpo_flat_random_choice_aligner(paired_field: str):
def func(data: List[Dict]):
outputs = []
for item in tqdm(data, desc="DPO random choice aligner", total=len(data)):
if len(item[paired_field]) == 0:
continue
new_item = copy.deepcopy(item)
new_item[paired_field] = random.choice(item[paired_field])
outputs.append(new_item)
return outputs
return func
def dpo_paired_random_choice_aligner(anchor_field: str, paired_field, sort_accord_to_len: bool = False, top_k: int = 5, num_workers: int = 16):
def func(data: List[Dict]):
outputs = []
for item in tqdm(data, desc="processing dpo pairs", total=len(data)):
if len(item[anchor_field]) == 0:
continue
if len(item[paired_field]) == 0:
continue
assert len(item[anchor_field]) == len(item[paired_field]), (item[anchor_field], item[paired_field])
for anchor, targets in zip(item[anchor_field], item[paired_field]):
if len(targets) == 0:
continue
new_item = copy.deepcopy(item)
new_item[anchor_field] = anchor
new_item[paired_field] = random.choice(targets)
outputs.append(new_item)
return outputs
def func_sorted(data: List[Dict]):
outputs_before_sort = []
for item in tqdm(data, desc="DPO paired random choice aligner", total=len(data)):
if len(item[anchor_field]) == 0:
continue
if len(item[paired_field]) == 0:
continue
assert len(item[anchor_field]) == len(item[paired_field]), (item[anchor_field], item[paired_field])
for anchor, targets in zip(item[anchor_field], item[paired_field]):
if len(targets) == 0:
continue
new_item = copy.deepcopy(item)
new_item[anchor_field] = anchor
new_item[paired_field] = targets
outputs_before_sort.append(new_item)
_annotate = partial(_sort_worker, _pos_field=anchor_field, _neg_field=paired_field, _top_k=top_k)
with Pool(num_workers) as p:
outputs_after_sort = list(tqdm(p.imap(_annotate, outputs_before_sort), total=len(outputs_before_sort)))
for item in outputs_after_sort:
item[paired_field] = random.choice(item[paired_field])
return outputs_after_sort
if sort_accord_to_len:
return func_sorted
return func
def sample_steps(response: str):
lines = response.split("\n")
lines = [line for line in lines if line.strip()]
return len(lines)
def _sort_worker(item, _pos_field: str, _neg_field: str, _top_k: int = 5):
anchor = item[_pos_field]
targets = item[_neg_field]
anchor_steps = sample_steps(anchor)
sorted_targets = sorted(targets, key=lambda x: abs(anchor_steps - sample_steps(x)))
item[_neg_field] = sorted_targets[:_top_k]
return item
def dpo_bi_random_choice_aligner(pos_field: str, neg_field: str, sort_accord_to_len: bool = False, top_k: int = 5, num_workers: int = 16):
def func(data: List[Dict]):
outputs = []
for item in tqdm(data, desc="DPO random choice aligner", total=len(data)):
if len(item[pos_field]) == 0:
continue
if len(item[neg_field]) == 0:
continue
pos = random.choice(item[pos_field])
neg = random.choice(item[neg_field])
item[pos_field] = pos
item[neg_field] = neg
outputs.append(item)
return outputs
def func_sorted(data: List[Dict]):
outputs_before_sort = []
for item in tqdm(data, desc="DPO random choice aligner", total=len(data)):
if len(item[pos_field]) == 0:
continue
if len(item[neg_field]) == 0:
continue
new_item = copy.deepcopy(item)
new_item["pos"] = random.choice(item[pos_field])
new_item["neg"] = item[neg_field]
outputs_before_sort.append(new_item)
_annotate = partial(_sort_worker, _pos_field="pos", _neg_field="neg", _top_k=top_k)
with Pool(num_workers) as p:
outputs_after_sort = list(tqdm(p.imap(_annotate, outputs_before_sort), total=len(outputs_before_sort)))
for item in outputs_after_sort:
item["neg"] = random.choice(item["neg"])
return outputs_after_sort
if sort_accord_to_len:
return func_sorted
return func
+49
View File
@@ -0,0 +1,49 @@
import json
from datasets import load_dataset
from typing import Dict
def read_text(file_path: str):
return open(file_path, encoding="utf-8").read()
def json_read_fn(file_path: str):
return json.load(open(file_path, encoding="utf-8"))
def hf_datasets_load_fn(**kwargs):
def func(file_path):
return load_dataset(file_path, **kwargs)
return func
def jsonl_read_fn():
def func(file_path):
return [json.loads(line) for line in open(file_path, encoding="utf-8").readlines()]
return func
def compose_message(system_prompt: str = ""):
def compose_fn(text: str):
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": text})
return messages
return compose_fn
def recompose_template(units: Dict[str, str], compositions: Dict[str, str]) -> Dict[str, str]:
templates = {}
for k, v in compositions.items():
templates[k] = v.format(**units)
return templates
def compose_template(units: Dict[str, str], composition: str) -> str:
return composition.format(**units)
+361
View File
@@ -0,0 +1,361 @@
# Copied from https://github.com/meta-math/MetaMath/blob/main/eval_math.py
# and https://github.com/meta-math/MetaMath/blob/main/eval_gsm8k.py
import re
import sys
from typing import List, Dict
from fraction import Fraction
from data.math_util import is_equiv, last_boxed_only_string
from transformers import PreTrainedTokenizer
from general_util.logger import get_child_logger
from data.deepseek_math_utils.answer_extraction import extract_math_answer, extract_last_single_answer
import torch
logger = get_child_logger(__name__)
MAX_INT = sys.maxsize
def is_number(s):
try:
float(s)
return True
except ValueError:
pass
try:
import unicodedata
unicodedata.numeric(s)
return True
except (TypeError, ValueError):
pass
return False
def extract_answer_number(completion, separator: str = "The answer is: "):
text = completion.split(separator)
if len(text) > 1:
extract_ans = text[-1].strip()
match = re.search(r'[\-+]?\d*[\.,/]?\d+', extract_ans)
if match:
if '/' in match.group():
denominator = match.group().split('/')[1]
numerator = match.group().split('/')[0]
if is_number(denominator) == True and is_number(numerator) == True:
if denominator == '0':
return round(float(numerator.replace(',', '')))
else:
frac = Fraction(match.group().replace(',', ''))
num_numerator = frac.numerator
num_denominator = frac.denominator
return round(float(num_numerator / num_denominator))
else:
return None
else:
if float(match.group().replace(',', '')) == float('inf'):
return None
return round(float(match.group().replace(',', '')))
else:
return None
else:
return None
def gsk8k_answer_cleaner(separator: str = "The answer is: "):
def func(completion: str):
res = extract_answer_number(completion, separator=separator)
if res is None:
return ""
return str(res) # To be compatible with `OpenAICallBack`
return func
def number_answer_extractor(separator: str = "The answer is: ", completion_field: str = "response"):
def func(data: List[Dict]):
for item in data:
res = extract_answer_number(item[completion_field], separator=separator)
if res is None:
item["label"] = ""
else:
item["label"] = str(res)
return data
return func
def gsm8k_gold_answer_extractor(response_field: str = "response"):
def func(data: List[Dict]):
for i, item in enumerate(data):
temp_ans = item[response_field].split('#### ')[1]
temp_ans = int(temp_ans.replace(',', ''))
item["label"] = str(temp_ans)
item["index"] = i
return data
return func
def remove_boxed(s):
left = "\\boxed{"
try:
assert s[:len(left)] == left
assert s[-1] == "}"
return s[len(left):-1]
except:
return None
def math_gold_answer_extractor(response_field: str = "output", kv_mapping: Dict = None):
def func(data: List[Dict]):
for item in data:
item["label"] = remove_boxed(last_boxed_only_string(item[response_field]))
if kv_mapping is not None: # "instruction" is maintained for composition. So the `instruction` key in MATH dataset should be changed.
for k, v in kv_mapping.items():
item[v] = item.pop(k)
return data
return func
def math_boxed_answer_cleaner():
def func(s):
return remove_boxed(last_boxed_only_string(s))
return func
def math_boxed_answer_cleaner_proxy():
def func(question, reasoning, task):
return remove_boxed(last_boxed_only_string(reasoning))
return func
def math_gold_answer_extractor_deepseek(query_field: str = "instruction", response_field: str = "output", kv_mapping: Dict = None):
def func(data: List[Dict]):
for item in data:
item["label"] = extract_math_answer(item[query_field], item[response_field], "cot")
if kv_mapping is not None: # "instruction" is maintained for composition. So the `instruction` key in MATH dataset should be changed.
for k, v in kv_mapping.items():
item[v] = item.pop(k)
return data
return func
def gsm8k_gold_answer_extractor_deepseek(query_field: str = "instruction", response_field: str = "output", kv_mapping: Dict = None):
def func(data: List[Dict]):
for item in data:
item["label"] = extract_last_single_answer(item[query_field], item[response_field], "cot")
if kv_mapping is not None:
for k, v in kv_mapping.items():
item[v] = item.pop(k)
return data
return func
# This is the original one from MetaMath repository.
def process_results(doc, completion, answer):
split_ans = completion.split('The answer is: ')
if len(split_ans) > 1:
ans = split_ans[-1]
extract_ans_temp = ans.split('.\n')[0]
extract_ans_temp = extract_ans_temp.strip()
if len(extract_ans_temp) > 0 and extract_ans_temp[-1] == '.':
extract_ans = extract_ans_temp[0:-1]
else:
extract_ans = extract_ans_temp
extract_ans = extract_ans.strip()
if is_equiv(extract_ans, answer):
return True
else:
return False
else:
temp = {'question': doc, 'output': completion, 'answer': answer}
# invalid_outputs.append(temp)
return False
def math_answer_cleaner(separator: str = "The answer is: "):
def func(completion: str):
split_ans = completion.split(separator)
if len(split_ans) > 1:
ans = split_ans[-1]
extract_ans_temp = ans.split('.\n')[0]
extract_ans_temp = extract_ans_temp.strip()
if len(extract_ans_temp) > 0 and extract_ans_temp[-1] == '.':
extract_ans = extract_ans_temp[0:-1]
else:
extract_ans = extract_ans_temp
extract_ans = extract_ans.strip()
if "$" in extract_ans: # Added new filtering
extract_ans = extract_ans.replace("$", "")
if "=" in extract_ans:
extract_ans = extract_ans.split('=')[-1].strip()
return extract_ans
else:
return ""
return func
def meta_math_gold_answer_extractor(response_field: str = "response"):
cleaner = math_answer_cleaner(separator="The answer is: ")
def func(data: List[Dict]):
outputs = []
cnt = 0
for item in data:
label = cleaner(item[response_field])
if label:
item["label"] = label
outputs.append(item)
else:
cnt += 1
logger.info(f"Counted {len(outputs)} items, {cnt} items are invalid")
return outputs
return func
def decompose_rap(prompt: str, response: str, max_seq_length: int, tokenizer: PreTrainedTokenizer):
# raw_steps = response.strip().split("\n")
raw_steps = response.split("\n")
steps = [raw_steps[0]]
for line in raw_steps[1:]:
if line.replace("#", "").strip() == "":
continue
if not (line.startswith("SubQuestion ") or line.startswith("Answer ")):
steps[-1] += "\n" + line
else:
steps.append(line)
endings = []
acc_step = prompt
for i, step in enumerate(steps):
if i == 0:
acc_step = acc_step + step
else:
acc_step = acc_step + "\n" + step
input_ids = tokenizer(acc_step, truncation=True, max_length=max_seq_length)["input_ids"]
endings.append(len(input_ids) - 1)
assert len(endings) > 0, (prompt, response)
return endings
def decompose_cot(prompt: str, response: str, max_seq_length: int, tokenizer: PreTrainedTokenizer):
steps = response.split("\n")
endings = []
acc_step = prompt
for i, step in enumerate(steps):
if i == 0:
acc_step = acc_step + step
else:
acc_step = acc_step + "\n" + step
input_ids = tokenizer(acc_step, truncation=True, max_length=max_seq_length)["input_ids"]
endings.append(len(input_ids) - 1)
assert len(endings) > 0, (prompt, response)
return endings
def decompose_deepseek_math_cot_v2(prompt: str, response: str, max_seq_length: int, tokenizer: PreTrainedTokenizer):
assert isinstance(prompt, str), prompt
assert isinstance(response, str), response
steps = response.split("\n")
endings = []
acc_step = prompt
for i, step in enumerate(steps):
if i == 0:
acc_step = acc_step + step
else:
acc_step = acc_step + "\n" + step
if step.strip():
input_ids = tokenizer(acc_step, truncation=False)["input_ids"]
endings.append(len(input_ids) - 1)
full_text = prompt + response
true_input_ids = tokenizer(full_text, truncation=True, max_length=max_seq_length)["input_ids"]
endings = [e for e in endings if e < len(true_input_ids)]
# assert len(endings) > 0, (prompt, response)
if len(endings) == 0:
logger.warning(f"Warning: Bad response:\n\n=========================Prompt====================\n{prompt}\n\n"
f"=======================Response=====================\n{response}\n\n")
return endings
# def decompose_deepseek_math_cot_v2_aligner(tokenizer: PreTrainedTokenizer, max_seq_length: int, response_field: str, prompt_field: str):
# def func(data: List[Dict]):
# for item in data:
# item["ending"] = decompose_deepseek_math_cot_v2(item[prompt_field], item[response_field], max_seq_length, tokenizer)
# return data
#
# return func
class RAPResponseStepRewardCollator:
_decompose_fns = {
"rap": decompose_rap,
"cot": decompose_cot,
}
def __init__(self, tokenizer: PreTrainedTokenizer, max_seq_length: int, cot_type: str = "rap"):
self.tokenizer = tokenizer
self.max_seq_length = max_seq_length
self.decompose_fn = self._decompose_fns[cot_type]
def __call__(self, batch):
prompt = [item["prompt"] for item in batch]
inputs = [item["input"] for item in batch]
indices = [item["index"] for item in batch]
text_prompts = prompt
text_inputs = inputs
encoded_prompts = self.tokenizer(text_prompts, padding="longest", truncation=True, max_length=self.max_seq_length, return_tensors="pt")
input_lens = torch.sum(encoded_prompts["attention_mask"], dim=-1)
encoded_inputs = self.tokenizer(text_inputs, padding="longest", truncation=True, max_length=self.max_seq_length, return_tensors="pt")
if self.tokenizer.padding_side == "left":
padding_len = torch.sum(1 - encoded_inputs["attention_mask"], dim=-1)
input_lens = input_lens + padding_len
labels = encoded_inputs["input_ids"].clone()
prompt_mask = torch.arange(encoded_inputs["input_ids"].size(1))[None, :] < input_lens[:, None]
labels[prompt_mask] = self.tokenizer.pad_token_id
endings = []
padding_len = torch.sum(1 - encoded_inputs["attention_mask"], dim=-1)
for b, item in enumerate(batch):
# ending = decompose_rap(item["prompt"], item["response"], self.max_seq_length, self.tokenizer)
ending = self.decompose_fn(item["prompt"], item["response"], self.max_seq_length, self.tokenizer)
if self.tokenizer.padding_side == "left":
ending = [e + padding_len[b].item() for e in ending]
endings.append(ending)
encoded_inputs["labels"] = labels
encoded_inputs["meta_data"] = {
"index": indices,
"prompt": prompt,
"input": inputs,
"response": [item["response"] for item in batch],
"ending": endings,
"type": [None] * len(endings),
}
return encoded_inputs
+40
View File
@@ -0,0 +1,40 @@
import json
from datasets import load_dataset
class MetaMathReaderHF:
def __init__(self, category: str):
self.category = category
def __call__(self, file_path: str = "meta-math/MetaMathQA"):
data = load_dataset(file_path, split="train").to_list()
for i, data in enumerate(data):
data["id"] = i
if not self.category or (self.category and self.category == "all"):
return data
if self.category == "math":
return [item for item in data if "MATH" in item["type"]]
if self.category == "gsm":
return [item for item in data if "GSM" in item["type"]]
raise ValueError(f"Invalid category: {self.category}")
class MetaMathReader:
def __init__(self, category: str = "math"):
self.category = category
def __call__(self, file_path: str = "meta-math/MetaMathQA"):
data = json.load(open(file_path))
if not self.category or (self.category and self.category == "all"):
return data
if self.category == "math":
return [item for item in data if "MATH" in item["type"]]
if self.category == "gsm":
return [item for item in data if "GSM" in item["type"]]
raise ValueError(f"Invalid category: {self.category}")
+258
View File
@@ -0,0 +1,258 @@
# Copied from https://github.com/meta-math/MetaMath/blob/main/util.py
import pprint
import re
def last_boxed_only(sample):
q, a = sample
a = last_boxed_only_string(a)
if a is None:
return None
return q, a
def last_boxed_only_string(string):
idx = string.rfind("\\boxed")
if idx < 0:
idx = string.rfind("\\fbox")
if idx < 0:
return None
i = idx
right_brace_idx = None
num_left_braces_open = 0
while i < len(string):
if string[i] == "{":
num_left_braces_open += 1
if string[i] == "}":
num_left_braces_open -= 1
if num_left_braces_open == 0:
right_brace_idx = i
break
i += 1
if right_brace_idx is None:
retval = None
else:
retval = string[idx:right_brace_idx + 1]
return retval
def only_until_first_boxed_from_tokens(string, tokens):
idx = string.find("\\boxed")
if idx < 0:
idx = string.find("\\fbox")
if idx < 0:
return None
cum_length = 0
for i, t in enumerate(tokens):
cum_length += len(t)
if cum_length >= idx:
break
return tokens[:i]
def clean_numbers(sample):
if not sample:
return None
new_sample = list()
for s in sample:
new_sample.append(_clean_numbers(s))
return tuple(new_sample)
def _clean_numbers(string):
"""
Clean Numbers in the given string
>>> _clean_numbers(None, "Hello 123")
'Hello 123'
>>> _clean_numbers(None, "Hello 1234")
'Hello 1,234'
>>> _clean_numbers(None, "Hello 1234324asdasd")
'Hello 1,234,324asdasd'
"""
num_prev_digits = 0
new_string = ""
for i, c in enumerate(string):
# isdigit() doesnt work here because of weird unicode chars.
if c in {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}:
num_prev_digits += 1
else:
if num_prev_digits > 3:
# Some fixing
string_number = new_string[-num_prev_digits:]
new_string = new_string[:-num_prev_digits] + "{0:,}".format(int(string_number))
num_prev_digits = 0
new_string += c
if num_prev_digits > 3:
# Some fixing
string_number = new_string[-num_prev_digits:]
new_string = new_string[:-num_prev_digits] + "{0:,}".format(int(string_number))
return new_string
def fix_fracs(string):
substrs = string.split("\\frac")
new_str = substrs[0]
if len(substrs) > 1:
substrs = substrs[1:]
for substr in substrs:
new_str += "\\frac"
if substr[0] == "{":
new_str += substr
else:
try:
assert len(substr) >= 2
except AssertionError:
return string
a = substr[0]
b = substr[1]
if b != "{":
if len(substr) > 2:
post_substr = substr[2:]
new_str += "{" + a + "}{" + b + "}" + post_substr
else:
new_str += "{" + a + "}{" + b + "}"
else:
if len(substr) > 2:
post_substr = substr[2:]
new_str += "{" + a + "}" + b + post_substr
else:
new_str += "{" + a + "}" + b
string = new_str
return string
def fix_a_slash_b(string):
if len(string.split("/")) != 2:
return string
a = string.split("/")[0]
b = string.split("/")[1]
try:
a = int(a)
b = int(b)
assert string == "{}/{}".format(a, b)
new_string = "\\frac{" + str(a) + "}{" + str(b) + "}"
return new_string
except AssertionError:
return string
def remove_right_units(string):
# "\\text{ " only ever occurs (at least in the val set) when describing units
if "\\text{ " in string:
splits = string.split("\\text{ ")
assert len(splits) == 2
return splits[0]
else:
return string
def fix_sqrt(string):
if "\\sqrt" not in string:
return string
splits = string.split("\\sqrt")
new_string = splits[0]
for split in splits[1:]:
if split[0] != "{":
a = split[0]
new_substr = "\\sqrt{" + a + "}" + split[1:]
else:
new_substr = "\\sqrt" + split
new_string += new_substr
return new_string
def strip_string(string):
# linebreaks
string = string.replace("\n", "")
# remove inverse spaces
string = string.replace("\\!", "")
# replace \\ with \
string = string.replace("\\\\", "\\")
# replace tfrac and dfrac with frac
string = string.replace("tfrac", "frac")
string = string.replace("dfrac", "frac")
# remove \left and \right
string = string.replace("\\left", "")
string = string.replace("\\right", "")
# Remove circ (degrees)
string = string.replace("^{\\circ}", "")
string = string.replace("^\\circ", "")
# remove dollar signs
string = string.replace("\\$", "")
# remove units (on the right)
string = remove_right_units(string)
# remove percentage
string = string.replace("\\%", "")
string = string.replace("\%", "") # noqa: W605
# " 0." equivalent to " ." and "{0." equivalent to "{." Alternatively, add "0" if "." is the start of the string
string = string.replace(" .", " 0.")
string = string.replace("{.", "{0.")
# if empty, return empty string
if len(string) == 0:
return string
if string[0] == ".":
string = "0" + string
# to consider: get rid of e.g. "k = " or "q = " at beginning
if len(string.split("=")) == 2:
if len(string.split("=")[0]) <= 2:
string = string.split("=")[1]
# fix sqrt3 --> sqrt{3}
string = fix_sqrt(string)
# remove spaces
string = string.replace(" ", "")
# \frac1b or \frac12 --> \frac{1}{b} and \frac{1}{2}, etc. Even works with \frac1{72} (but not \frac{72}1). Also does a/b --> \\frac{a}{b}
string = fix_fracs(string)
# manually change 0.5 --> \frac{1}{2}
if string == "0.5":
string = "\\frac{1}{2}"
# NOTE: X/Y changed to \frac{X}{Y} in dataset, but in simple cases fix in case the model output is X/Y
string = fix_a_slash_b(string)
return string
def is_equiv(str1, str2, verbose=False):
if str1 is None and str2 is None:
print("WARNING: Both None")
return True
if str1 is None or str2 is None:
return False
try:
ss1 = strip_string(str1)
ss2 = strip_string(str2)
# pdb.set_trace()
if verbose:
print(ss1, ss2)
return ss1 == ss2
except Exception:
return str1 == str2
class NotEqual:
def __eq__(self, other):
return False
+546
View File
@@ -0,0 +1,546 @@
# ======================== MATH Scale Utils ======================================
# https://github.com/microsoft/unilm/blob/master/mathscale/MWPBench/eval_vllm/util.py#L282-L336
import re
from typing import List, Dict
def fix_fracs(string):
substrs = string.split("\\frac")
new_str = substrs[0]
if len(substrs) > 1:
substrs = substrs[1:]
for substr in substrs:
new_str += "\\frac"
if substr[0] == "{":
new_str += substr
else:
try:
assert len(substr) >= 2
except AssertionError:
return string
a = substr[0]
b = substr[1]
if b != "{":
if len(substr) > 2:
post_substr = substr[2:]
new_str += "{" + a + "}{" + b + "}" + post_substr
else:
new_str += "{" + a + "}{" + b + "}"
else:
if len(substr) > 2:
post_substr = substr[2:]
new_str += "{" + a + "}" + b + post_substr
else:
new_str += "{" + a + "}" + b
string = new_str
return string
def fix_a_slash_b(string):
if len(string.split("/")) != 2:
return string
a = string.split("/")[0]
b = string.split("/")[1]
try:
a = int(a)
b = int(b)
assert string == "{}/{}".format(a, b)
new_string = "\\frac{" + str(a) + "}{" + str(b) + "}"
return new_string
except Exception as e:
return string
def remove_right_units(string):
# "\\text{ " only ever occurs (at least in the val set) when describing units
if "\\text{ " in string:
splits = string.split("\\text{ ")
assert len(splits) == 2
return splits[0]
else:
return string
def fix_sqrt(string):
if "\\sqrt" not in string:
return string
splits = string.split("\\sqrt")
new_string = splits[0]
for split in splits[1:]:
if split[0] != "{":
a = split[0]
new_substr = "\\sqrt{" + a + "}" + split[1:]
else:
new_substr = "\\sqrt" + split
new_string += new_substr
return new_string
def is_number(s):
s = s.strip("$")
try:
# Try to convert the string to an integer
int(s)
return True
except ValueError:
try:
# Try to convert the string to a float
float(s)
return True
except ValueError:
return False
def unbox_and_extract(text):
start_indices = [m.start() for m in re.finditer(r'\\boxed{', text)]
extracted_contents = []
for start in start_indices:
brace_count = 0
for i, char in enumerate(text[start:]):
if char == '{':
brace_count += 1
elif char == '}':
brace_count -= 1
if brace_count == 0:
end = start + i + 1
extracted_contents.append(text[start + 7:end - 1]) # +7 to skip '\\boxed{'
break
# Replace '\\boxed{...}' with the content inside it
unboxed_text = re.sub(r'\\boxed{(.*?)}', r'\1', text)
return unboxed_text, extracted_contents
def convert_to_latex_fraction(text: str) -> str:
# Use regex to find all occurrences of ((num)/(denom))
pattern = re.compile(r"\(\(([\d]+)\)/\(([\d]+)\)\)")
matches = pattern.findall(text)
for match in matches:
num, denom = match
latex_frac = f"\\\\frac{{{num}}}{{{denom}}}"
# Replace the old expression with the LaTeX fraction
text = text.replace(f"(({num})/({denom}))", latex_frac)
return text
def strip_string(string):
# convert ((3)/(4)) -> \\frac{3}{4}
string = convert_to_latex_fraction(string)
# remove ,
string = string.replace(",", "")
# linebreaks
string = string.replace("\n", "")
# remove inverse spaces
string = string.replace("\\!", "")
# replace \\ with \
string = string.replace("\\\\", "\\")
# replace tfrac and dfrac with frac
string = string.replace("tfrac", "frac")
string = string.replace("dfrac", "frac")
# remove \left and \right
string = string.replace("\\left", "")
string = string.replace("\\right", "")
# Remove circ (degrees)
string = string.replace("^{\\circ}", "")
string = string.replace("^\\circ", "")
# remove dollar signs
string = string.replace("\\$", "")
# remove units (on the right)
string = remove_right_units(string)
# remove percentage
string = string.replace("\\%", "")
string = string.replace("\%", "") # noqa: W605
# " 0." equivalent to " ." and "{0." equivalent to "{." Alternatively, add "0" if "." is the start of the string
string = string.replace(" .", " 0.")
string = string.replace("{.", "{0.")
# if empty, return empty string
if len(string) == 0:
return string
if string[0] == ".":
string = "0" + string
# to consider: get rid of e.g. "k = " or "q = " at beginning
if len(string.split("=")) == 2:
if len(string.split("=")[0]) <= 2:
string = string.split("=")[1]
# fix sqrt3 --> sqrt{3}
string = fix_sqrt(string)
# My own
string = string.replace("\\quad", " ")
# remove spaces
string = string.replace(" ", "")
# \frac1b or \frac12 --> \frac{1}{b} and \frac{1}{2}, etc. Even works with \frac1{72} (but not \frac{72}1). Also does a/b --> \\frac{a}{b}
string = fix_fracs(string)
# manually change 0.5 --> \frac{1}{2}
if string == "0.5":
string = "\\frac{1}{2}"
# NOTE: X/Y changed to \frac{X}{Y} in dataset, but in simple cases fix in case the model output is X/Y
string = fix_a_slash_b(string)
return string
def is_single_inline_math(expression: str) -> bool:
# Use regex to check for a pattern that starts and ends with dollar signs,
# and contains no other dollar signs in between.
pattern = re.compile(r"^\$[^$]+\$$")
match = pattern.match(expression)
return bool(match)
def mathscale_is_equiv(prediction_ans, reference_ans, verbose=False):
if prediction_ans is None and reference_ans is None:
print("WARNING: Both None")
return True, prediction_ans, reference_ans
if prediction_ans is None or reference_ans is None:
return False, prediction_ans, reference_ans
try:
clean_prediction_ans = strip_string(prediction_ans)
clean_reference_ans = strip_string(reference_ans)
if is_number(clean_prediction_ans) and is_number(clean_reference_ans):
judge = float(clean_prediction_ans.strip("$")) == float(clean_reference_ans.strip("$"))
# print(f"1 judge: {judge}")
elif is_single_inline_math(clean_reference_ans):
judge = (clean_reference_ans.strip("$") in clean_prediction_ans.strip("$"))
# print(f"2 judge: {judge}")
elif (len(clean_prediction_ans) >= 3) and (not is_number(clean_prediction_ans)) and (not clean_prediction_ans.startswith("-")) and (
not clean_reference_ans.startswith("-")) and (clean_prediction_ans in clean_reference_ans):
judge = True
# print(f"3 judge: {judge}")
elif (len(clean_reference_ans) >= 3) and (not is_number(clean_reference_ans)) and (not clean_prediction_ans.startswith("-")) and (
not clean_reference_ans.startswith("-")) and (clean_reference_ans in clean_prediction_ans):
judge = True
# print(f"4 judge: {judge}")
else:
judge = clean_prediction_ans == clean_reference_ans
# print(f"5 judge: {judge}")
if verbose:
print(f"clean_prediction_ans: {clean_prediction_ans} | clean_reference_ans: {clean_reference_ans} | judge: {judge}")
return judge, clean_prediction_ans, clean_reference_ans
except Exception as e:
# print(e)
return prediction_ans == reference_ans, prediction_ans, reference_ans
def mathscale_is_equiv_proxy(prediction_ans, reference_ans, verbose=False):
return mathscale_is_equiv(prediction_ans, reference_ans.lower(), verbose=verbose)[0]
def is_correct(completion, answer, verbose=False):
completion = completion.lower()
answer = answer.lower()
# Extract short answer from completion
extract_ans = ""
try:
clean_reference_ans = strip_string(answer)
except Exception as e:
clean_reference_ans = answer
print(f"Failed to strip answer string: {answer} =============== {e}")
is_reference_ans_number = is_number(clean_reference_ans)
# First extract boxed answer
unbox_long_answer, box_short_answers = unbox_and_extract(completion)
if box_short_answers != []:
extract_ans = box_short_answers[-1].strip()
# print(f"1 extract_ans: {extract_ans}")
# extract the last number answer
elif is_reference_ans_number:
numbers = re.findall(r"[\-+]?\d*[\.,/]?\d+", completion)
if numbers:
extract_ans = numbers[-1]
# print(f"2 extract_ans: {extract_ans}")
# extract "the answer is ..." answer
elif ("answer is" in completion) or ("solution is" in completion):
if "answer is" in completion:
split_ans = completion.split('answer is')
else:
split_ans = completion.split('solution is')
ans = split_ans[-1].strip().lstrip(":").strip()
extract_ans_temp = ans.split('.\n')[0]
extract_ans_temp = extract_ans_temp.strip()
extract_ans_temp = extract_ans_temp.strip('.')
if len(extract_ans_temp) > 0 and extract_ans_temp[-1] == '.':
extract_ans = extract_ans_temp[0:-1]
else:
extract_ans = extract_ans_temp
extract_ans = extract_ans.strip()
# print(f"3 extract_ans: {extract_ans}")
# extract "therefore xx is xxx" answer
elif "is" in completion:
pos = completion.rfind("is")
ans = completion[pos + 2:].strip().lstrip(":").strip()
extract_ans_temp = ans.split('.\n')[0]
extract_ans_temp = extract_ans_temp.strip()
extract_ans_temp = extract_ans_temp.strip('.')
if len(extract_ans_temp) > 0 and extract_ans_temp[-1] == '.':
extract_ans = extract_ans_temp[0:-1]
else:
extract_ans = extract_ans_temp
extract_ans = extract_ans.strip()
# print(f"4 extract_ans: {extract_ans}")
else:
return False, f"failed extracting answer from completion", clean_reference_ans
judge, clean_prediction_ans, clean_reference_ans = mathscale_is_equiv(extract_ans, answer, verbose=verbose)
return judge, clean_prediction_ans, clean_reference_ans
def mathscale_extract_answer():
def func(completion: str):
completion = completion.lower()
# Extract short answer from completion
extract_ans = ""
# First extract boxed answer
unbox_long_answer, box_short_answers = unbox_and_extract(completion)
if box_short_answers != []:
extract_ans = box_short_answers[-1].strip()
# print(f"1 extract_ans: {extract_ans}")
# extract the last number answer
# elif is_reference_ans_number:
# numbers = re.findall(r"[\-+]?\d*[\.,/]?\d+", completion)
# if numbers:
# extract_ans = numbers[-1]
# print(f"2 extract_ans: {extract_ans}")
# extract "the answer is ..." answer
elif ("answer is" in completion) or ("solution is" in completion):
if "answer is" in completion:
split_ans = completion.split('answer is')
else:
split_ans = completion.split('solution is')
ans = split_ans[-1].strip().lstrip(":").strip()
extract_ans_temp = ans.split('.\n')[0]
extract_ans_temp = extract_ans_temp.strip()
extract_ans_temp = extract_ans_temp.strip('.')
if len(extract_ans_temp) > 0 and extract_ans_temp[-1] == '.':
extract_ans = extract_ans_temp[0:-1]
else:
extract_ans = extract_ans_temp
extract_ans = extract_ans.strip()
# print(f"3 extract_ans: {extract_ans}")
# extract "therefore xx is xxx" answer
elif "is" in completion:
pos = completion.rfind("is")
ans = completion[pos + 2:].strip().lstrip(":").strip()
extract_ans_temp = ans.split('.\n')[0]
extract_ans_temp = extract_ans_temp.strip()
extract_ans_temp = extract_ans_temp.strip('.')
if len(extract_ans_temp) > 0 and extract_ans_temp[-1] == '.':
extract_ans = extract_ans_temp[0:-1]
else:
extract_ans = extract_ans_temp
extract_ans = extract_ans.strip()
# print(f"4 extract_ans: {extract_ans}")
else:
return ""
return extract_ans
return func
def mathscale_extract_answer_fn_v2_list(separator: str = "The answer is: ", completion_field: str = "response"):
from data.math import extract_answer_number
mathscale_extract_fn = mathscale_extract_answer()
def func(data: List[Dict]):
for item in data:
res = extract_answer_number(item[completion_field], separator=separator)
if not res:
item["label"] = str(mathscale_extract_fn(item[completion_field]))
else:
item["label"] = str(res)
return data
return func
def mathscale_extract_answer_v2(completion: str):
completion = completion.lower()
# Extract short answer from completion
extract_ans = ""
# First extract boxed answer
unbox_long_answer, box_short_answers = unbox_and_extract(completion)
numbers = re.findall(r"[\-+]?\d*[\.,/]?\d+", completion)
if box_short_answers != []:
extract_ans = box_short_answers[-1].strip()
# print(f"1 extract_ans: {extract_ans}")
# extract the last number answer
# elif is_reference_ans_number:
elif numbers:
extract_ans = numbers[-1]
# print(f"2 extract_ans: {extract_ans}")
# extract "the answer is ..." answer
elif ("answer is" in completion) or ("solution is" in completion):
if "answer is" in completion:
split_ans = completion.split('answer is')
else:
split_ans = completion.split('solution is')
ans = split_ans[-1].strip().lstrip(":").strip()
extract_ans_temp = ans.split('.\n')[0]
extract_ans_temp = extract_ans_temp.strip()
extract_ans_temp = extract_ans_temp.strip('.')
if len(extract_ans_temp) > 0 and extract_ans_temp[-1] == '.':
extract_ans = extract_ans_temp[0:-1]
else:
extract_ans = extract_ans_temp
extract_ans = extract_ans.strip()
# print(f"3 extract_ans: {extract_ans}")
# extract "therefore xx is xxx" answer
elif "is" in completion:
pos = completion.rfind("is")
ans = completion[pos + 2:].strip().lstrip(":").strip()
extract_ans_temp = ans.split('.\n')[0]
extract_ans_temp = extract_ans_temp.strip()
extract_ans_temp = extract_ans_temp.strip('.')
if len(extract_ans_temp) > 0 and extract_ans_temp[-1] == '.':
extract_ans = extract_ans_temp[0:-1]
else:
extract_ans = extract_ans_temp
extract_ans = extract_ans.strip()
# print(f"4 extract_ans: {extract_ans}")
else:
return ""
try:
extract_ans = strip_string(extract_ans)
except Exception as e:
print(f"Failed to strip answer string: {extract_ans} =============== {e}")
return extract_ans
def mathscale_extract_answer_v3(completion: str):
completion = completion.lower()
# Extract short answer from completion
extract_ans = ""
# First extract boxed answer
unbox_long_answer, box_short_answers = unbox_and_extract(completion)
if "answer is" in completion:
numbers = re.findall(r"[\-+]?\d*[\.,/]?\d+", completion.split("answer is")[-1])
elif "solution is" in completion:
numbers = re.findall(r"[\-+]?\d*[\.,/]?\d+", completion.split("solution is")[-1])
else:
numbers = []
if box_short_answers != []:
extract_ans = box_short_answers[-1].strip()
# print(f"1 extract_ans: {extract_ans}")
# extract the last number answer
# elif is_reference_ans_number:
elif numbers:
extract_ans = numbers[0]
# print(f"2 extract_ans: {extract_ans}")
# extract "the answer is ..." answer
elif ("answer is" in completion) or ("solution is" in completion):
if "answer is" in completion:
split_ans = completion.split('answer is')
else:
split_ans = completion.split('solution is')
ans = split_ans[-1].strip().lstrip(":").strip()
extract_ans_temp = ans.split('.\n')[0]
extract_ans_temp = extract_ans_temp.strip()
extract_ans_temp = extract_ans_temp.strip('.')
if len(extract_ans_temp) > 0 and extract_ans_temp[-1] == '.':
extract_ans = extract_ans_temp[0:-1]
else:
extract_ans = extract_ans_temp
extract_ans = extract_ans.strip()
# print(f"3 extract_ans: {extract_ans}")
# extract "therefore xx is xxx" answer
elif "is" in completion:
pos = completion.rfind("is")
ans = completion[pos + 2:].strip().lstrip(":").strip()
extract_ans_temp = ans.split('.\n')[0]
extract_ans_temp = extract_ans_temp.strip()
extract_ans_temp = extract_ans_temp.strip('.')
if len(extract_ans_temp) > 0 and extract_ans_temp[-1] == '.':
extract_ans = extract_ans_temp[0:-1]
else:
extract_ans = extract_ans_temp
extract_ans = extract_ans.strip()
# print(f"4 extract_ans: {extract_ans}")
else:
return ""
try:
extract_ans = strip_string(extract_ans)
except Exception as e:
print(f"Failed to strip answer string: {extract_ans}")
return extract_ans
def mathscale_extract_answer_fn_v3(completion_field: str = "response"):
def func(data: List[Dict]):
for item in data:
res = mathscale_extract_answer_v2(item[completion_field])
if res:
item["label"] = str(res)
else:
item["label"] = ""
return data
return func
def mathscale_extract_answer_fn_v4(completion_field: str = "response"):
def func(data: List[Dict]):
for item in data:
res = mathscale_extract_answer_v3(item[completion_field])
if res:
item["label"] = str(res)
else:
item["label"] = ""
return data
return func
def extract_pure_prompt_aligner():
def func(data: List[Dict]):
for i, item in enumerate(data):
prompt = item["prompt"]
_s = prompt.index("### Instruction:") + len("### Instruction:")
_e = prompt.index("### Response:")
question = prompt[_s:_e].strip()
item["question"] = question
item["id"] = i
return data
return func
+22
View File
@@ -0,0 +1,22 @@
import json
import sys
from datasets import load_dataset
from general_util.logger import get_child_logger
logger = get_child_logger(__name__)
class NuminaMathReader:
def __init__(self, id_field: str = "id", split: str = "train"):
self.id_field = id_field
self.split = split
def __call__(self, file_path):
data = load_dataset(file_path, split=self.split).to_list()
logger.info(len(data))
for i, item in enumerate(data):
item[self.id_field] = i
return data
+201
View File
@@ -0,0 +1,201 @@
import os
import time
import traceback
from typing import Union, List
from openai import OpenAI, AzureOpenAI
from general_util.logger import get_child_logger
logger = get_child_logger(__name__)
class GPTAPIInterface:
def __init__(self, model: str, max_tokens: int, api_time_interval: int = 2, api_key: str = None):
self.model = model
self.max_tokens = max_tokens
self.api_time_interval = api_time_interval
if api_key:
self.client = OpenAI(
# This is the default and can be omitted
api_key=api_key,
)
else:
self.client = OpenAI(
# This is the default and can be omitted
api_key=os.environ.get("OPENAI_API_KEY"),
)
def __call__(self, text: str):
raise NotImplementedError
class GPTTurbo(GPTAPIInterface):
def __init__(self,
model: str = "gpt-3.5-turbo",
max_tokens: int = 2048,
temperature: float = 0.0,
top_p: int = 1,
frequency_penalty: float = 0.0,
presence_penalty: float = 0.0,
api_time_interval: int = 2,
organization: str = "",
n: int = 1,
api_key: str = None):
super().__init__(model, max_tokens, api_time_interval, api_key)
self.temperature = temperature
self.top_p = top_p
self.frequency_penalty = frequency_penalty
self.presence_penalty = presence_penalty
self.organization = organization
self.n = n
def __call__(self, text: Union[str, List[str]]):
if isinstance(text, list):
if isinstance(text[0], str):
assert len(text) == 1, "Currently we only support one input in single batch."
text = text[0]
else:
assert isinstance(text[0], dict)
assert "role" in text[0] and "content" in text[0]
flag = False
error_time = 0
response = None
max_tokens = self.max_tokens
while not flag:
try:
if isinstance(text, str):
messages = [{"role": "user", "content": text}]
else:
messages = text
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
max_tokens=max_tokens,
temperature=self.temperature,
top_p=self.top_p,
frequency_penalty=self.frequency_penalty,
presence_penalty=self.presence_penalty,
n=self.n,
)
error_time = 0
flag = True
except Exception as exc:
logger.warning(exc)
logger.warning(">>>>>>>>>>>>>>>>>>>>>>>>>>>>")
err_msg = traceback.format_exc()
# logger.warning(traceback.print_exc())
logger.warning(err_msg)
logger.warning(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
if "maximum context" in err_msg:
max_tokens -= 100
logger.warning("max_tokens: {}".format(max_tokens))
error_time += 1
if error_time > 20:
logger.warning("Too many errors. Sleep 60s.")
time.sleep(60)
return {"response": ""}
if self.api_time_interval:
time.sleep(self.api_time_interval)
response = [choice.message.content for choice in response.choices]
if len(response) == 1:
response = response[0]
return {"response": response}
class AzureGPTEndpoint:
def __init__(self,
model_name: str = "gpt-4-32k",
max_tokens: int = 2048,
temperature: float = 0.0,
top_p: int = 1,
# frequency_penalty: float = 0.0,
# presence_penalty: float = 0.0,
api_time_interval: int = 0,
# organization: str = "",
n: int = 1,
return_text: bool = True,
):
if model_name == "gpt-4o":
self.client = AzureOpenAI(
azure_endpoint="https://gcrgpt4aoai5.openai.azure.com/",
api_key=os.getenv("OPENAI_API_KEY"),
api_version="2024-02-01"
)
else:
self.client = AzureOpenAI(
azure_endpoint="https://gcraoai5sw1.openai.azure.com//",
api_key=os.getenv("OPENAI_API_KEY"),
api_version="2023-03-15-preview"
)
self.model = model_name
self.max_tokens = max_tokens
self.temperature = temperature
self.top_p = top_p
# self.frequency_penalty = frequency_penalty
# self.presence_penalty = presence_penalty
# self.organization = organization
self.n = n
self.api_time_interval = api_time_interval
self.return_text = return_text
def __call__(self, text: Union[str, List[str]]):
if isinstance(text, list):
if isinstance(text[0], str):
assert len(text) == 1, "Currently we only support one input in single batch."
text = text[0]
else:
assert isinstance(text[0], dict)
assert "role" in text[0] and "content" in text[0]
flag = False
error_time = 0
response = None
max_tokens = self.max_tokens
while not flag:
try:
if isinstance(text, str):
messages = [{"role": "user", "content": text}]
else:
messages = text
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
max_tokens=max_tokens,
temperature=self.temperature,
top_p=self.top_p,
n=self.n,
)
error_time = 0
flag = True
except Exception as exc:
logger.warning(exc)
logger.warning(">>>>>>>>>>>>>>>>>>>>>>>>>>>>")
err_msg = traceback.format_exc()
logger.warning(err_msg)
logger.warning(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
if "maximum context" in err_msg:
max_tokens -= 100
logger.warning("max_tokens: {}".format(max_tokens))
error_time += 1
if error_time > 20:
logger.warning("Too many errors. Sleep 60s.")
time.sleep(60)
return {"response": ""}
if self.api_time_interval:
time.sleep(self.api_time_interval)
response = [choice.message.content for choice in response.choices]
if len(response) == 1:
response = response[0]
if self.return_text:
return response
return {"response": response}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Zhibin Gou
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+52
View File
@@ -0,0 +1,52 @@
### Requirements
You can install the required packages with the following command:
```bash
cd latex2sympy
pip install -e .
cd ..
pip install -r requirements.txt
pip install vllm==0.5.1 --no-build-isolation
pip install transformers==4.42.3
```
### Evaluation
You can evaluate Qwen2.5/Qwen2-Math-Instruct series model with the following command:
```bash
# Qwen2.5-Math-Instruct Series
PROMPT_TYPE="qwen25-math-cot"
# Qwen2.5-Math-1.5B-Instruct
export CUDA_VISIBLE_DEVICES="0"
MODEL_NAME_OR_PATH="Qwen/Qwen2.5-Math-1.5B-Instruct"
bash sh/eval.sh $PROMPT_TYPE $MODEL_NAME_OR_PATH
# Qwen2.5-Math-7B-Instruct
export CUDA_VISIBLE_DEVICES="0"
MODEL_NAME_OR_PATH="Qwen/Qwen2.5-Math-7B-Instruct"
bash sh/eval.sh $PROMPT_TYPE $MODEL_NAME_OR_PATH
# Qwen2.5-Math-72B-Instruct
export CUDA_VISIBLE_DEVICES="0,1,2,3"
MODEL_NAME_OR_PATH="Qwen/Qwen2.5-Math-72B-Instruct"
bash sh/eval.sh $PROMPT_TYPE $MODEL_NAME_OR_PATH
# Qwen2-Math-Instruct Series
PROMPT_TYPE="qwen-boxed"
# Qwen2-Math-1.5B-Instruct
export CUDA_VISIBLE_DEVICES="0"
MODEL_NAME_OR_PATH="Qwen/Qwen2-Math-1.5B-Instruct"
bash sh/eval.sh $PROMPT_TYPE $MODEL_NAME_OR_PATH
# Qwen2-Math-7B-Instruct
export CUDA_VISIBLE_DEVICES="0"
MODEL_NAME_OR_PATH="Qwen/Qwen2-Math-7B-Instruct"
bash sh/eval.sh $PROMPT_TYPE $MODEL_NAME_OR_PATH
# Qwen2-Math-72B-Instruct
export CUDA_VISIBLE_DEVICES="0,1,2,3"
MODEL_NAME_OR_PATH="Qwen/Qwen2-Math-72B-Instruct"
bash sh/eval.sh $PROMPT_TYPE $MODEL_NAME_OR_PATH
```
## Acknowledgement
The codebase is adapted from [math-evaluation-harness](https://github.com/ZubinGou/math-evaluation-harness).
+85
View File
@@ -0,0 +1,85 @@
import os
import json
import random
import datasets
from datasets import load_dataset, Dataset, concatenate_datasets
from utils import load_jsonl, lower_keys
def load_data(data_name, split, data_dir="./data"):
data_file = f"{data_dir}/{data_name}/{split}.jsonl"
if os.path.exists(data_file):
examples = list(load_jsonl(data_file))
else:
if data_name == "math":
dataset = load_dataset(
"competition_math",
split=split,
name="main",
cache_dir=f"{data_dir}/temp",
)
elif data_name == "gsm8k":
dataset = load_dataset(data_name, split=split)
elif data_name == "svamp":
# evaluate on training set + test set
dataset = load_dataset("ChilleD/SVAMP", split="train")
dataset = concatenate_datasets(
[dataset, load_dataset("ChilleD/SVAMP", split="test")]
)
elif data_name == "asdiv":
dataset = load_dataset("EleutherAI/asdiv", split="validation")
dataset = dataset.filter(
lambda x: ";" not in x["answer"]
) # remove multi-answer examples
elif data_name == "mawps":
examples = []
# four sub-tasks
for data_name in ["singleeq", "singleop", "addsub", "multiarith"]:
sub_examples = list(load_jsonl(f"{data_dir}/mawps/{data_name}.jsonl"))
for example in sub_examples:
example["type"] = data_name
examples.extend(sub_examples)
dataset = Dataset.from_list(examples)
elif data_name == "mmlu_stem":
dataset = load_dataset("hails/mmlu_no_train", "all", split="test")
# only keep stem subjects
stem_subjects = [
"abstract_algebra",
"astronomy",
"college_biology",
"college_chemistry",
"college_computer_science",
"college_mathematics",
"college_physics",
"computer_security",
"conceptual_physics",
"electrical_engineering",
"elementary_mathematics",
"high_school_biology",
"high_school_chemistry",
"high_school_computer_science",
"high_school_mathematics",
"high_school_physics",
"high_school_statistics",
"machine_learning",
]
dataset = dataset.rename_column("subject", "type")
dataset = dataset.filter(lambda x: x["type"] in stem_subjects)
elif data_name == "carp_en":
dataset = load_jsonl(f"{data_dir}/carp_en/test.jsonl")
else:
raise NotImplementedError(data_name)
examples = list(dataset)
examples = [lower_keys(example) for example in examples]
dataset = Dataset.from_list(examples)
os.makedirs(f"{data_dir}/{data_name}", exist_ok=True)
dataset.to_json(data_file)
# add 'idx' in the first column
if "idx" not in examples[0]:
examples = [{"idx": i, **example} for i, example in enumerate(examples)]
# dedepulicate & sort
examples = sorted(examples, key=lambda x: x["idx"])
return examples
+109
View File
@@ -0,0 +1,109 @@
import argparse
import numpy as np
from tqdm import tqdm
from pebble import ProcessPool
from concurrent.futures import TimeoutError
from grader import *
from parser import *
from utils import load_jsonl
from python_executor import PythonExecutor
def evaluate(data_name, prompt_type, samples: list=None, file_path: str=None, max_num_samples=None, execute=False):
assert samples or file_path, "samples or file_path must be provided"
if not samples:
samples = list(load_jsonl(file_path))
if 'idx' in samples[0]:
samples = {sample['idx']: sample for sample in samples}.values()
samples = sorted(samples, key=lambda x: x['idx'])
else:
samples = [dict(idx=idx, **sample) for idx, sample in enumerate(samples)]
if max_num_samples:
print(f"max_num_samples: {max_num_samples} / {len(samples)}")
samples = samples[:max_num_samples]
# parse gt
for sample in samples:
sample['gt_cot'], sample['gt'] = parse_ground_truth(sample, data_name)
params = [(idx, pred, sample['gt']) for idx, sample in enumerate(samples) for pred in sample['pred']]
scores = []
timeout_cnt = 0
with ProcessPool(max_workers=1) as pool:
future = pool.map(math_equal_process, params, timeout=3)
iterator = future.result()
with tqdm(total=len(samples), desc="Evaluate") as progress_bar:
while True:
try:
result = next(iterator)
scores.append(result)
except StopIteration:
break
except TimeoutError as error:
print(error)
scores.append(False)
timeout_cnt += 1
except Exception as error:
print(error.traceback)
exit()
progress_bar.update(1)
idx = 0
score_mat = []
for sample in samples:
sample['score'] = scores[idx: idx+len(sample['pred'])]
assert len(sample['score']) == len(sample['pred'])
score_mat.append(sample['score'])
idx += len(sample['pred'])
max_len = max([len(s) for s in score_mat])
for i, s in enumerate(score_mat):
if len(s) < max_len:
score_mat[i] = s + [s[-1]] * (max_len - len(s)) # pad
# output mean of each column of scores
col_means= np.array(score_mat).mean(axis=0)
mean_score = list(np.round(col_means * 100, decimals=1))
result_json = {
"num_samples": len(samples),
"num_scores": len(scores),
"timeout_samples": timeout_cnt,
"empty_samples": len([s for s in samples if not s['pred'][-1]]),
"acc": mean_score[0]
}
# each type score
if "type" in samples[0]:
type_scores = {}
for sample in samples:
if sample['type'] not in type_scores:
type_scores[sample['type']] = []
type_scores[sample['type']].append(sample['score'][-1])
type_scores = {k: np.round(np.array(v).mean() * 100, decimals=1) for k, v in type_scores.items()}
type_scores = {k: v for k, v in sorted(type_scores.items(), key=lambda item: item[0])}
result_json['type_acc'] = type_scores
print(result_json)
return samples, result_json
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--data_name", type=str, default="math")
parser.add_argument("--prompt_type", type=str, default="tool-integrated")
parser.add_argument("--file_path", type=str, default=None, required=True)
parser.add_argument("--max_num_samples", type=int, default=None)
parser.add_argument("--execute", action="store_true")
args = parser.parse_args()
return args
if __name__ == "__main__":
args = parse_args()
evaluate(data_name=args.data_name, prompt_type=args.prompt_type, file_path=args.file_path,
max_num_samples=args.max_num_samples, execute=args.execute)
+378
View File
@@ -0,0 +1,378 @@
import json
def get_examples():
examples = {}
examples["gsm8k"] = [
(
"There are 15 trees in the grove. Grove workers will plant trees in the grove today. After they are done, there will be 21 trees. How many trees did the grove workers plant today?",
"There are 15 trees originally. Then there were 21 trees after some more were planted. So there must have been 21 - 15 = 6. The answer is 6.",
),
(
"If there are 3 cars in the parking lot and 2 more cars arrive, how many cars are in the parking lot?",
"There are originally 3 cars. 2 more cars arrive. 3 + 2 = 5. The answer is 5.",
),
(
"Leah had 32 chocolates and her sister had 42. If they ate 35, how many pieces do they have left in total?",
"Originally, Leah had 32 chocolates. Her sister had 42. So in total they had 32 + 42 = 74. After eating 35, they had 74 - 35 = 39. The answer is 39.",
),
(
"Jason had 20 lollipops. He gave Denny some lollipops. Now Jason has 12 lollipops. How many lollipops did Jason give to Denny?",
"Jason started with 20 lollipops. Then he had 12 after giving some to Denny. So he gave Denny 20 - 12 = 8. The answer is 8.",
),
(
"Shawn has five toys. For Christmas, he got two toys each from his mom and dad. How many toys does he have now?",
"Shawn started with 5 toys. If he got 2 toys each from his mom and dad, then that is 4 more toys. 5 + 4 = 9. The answer is 9.",
),
(
"There were nine computers in the server room. Five more computers were installed each day, from monday to thursday. How many computers are now in the server room?",
"There were originally 9 computers. For each of 4 days, 5 more computers were added. So 5 * 4 = 20 computers were added. 9 + 20 is 29. The answer is 29.",
),
(
"Michael had 58 golf balls. On tuesday, he lost 23 golf balls. On wednesday, he lost 2 more. How many golf balls did he have at the end of wednesday?",
"Michael started with 58 golf balls. After losing 23 on tuesday, he had 58 - 23 = 35. After losing 2 more, he had 35 - 2 = 33 golf balls. The answer is 33.",
),
(
"Olivia has $23. She bought five bagels for $3 each. How much money does she have left?",
"Olivia had 23 dollars. 5 bagels for 3 dollars each will be 5 x 3 = 15 dollars. So she has 23 - 15 dollars left. 23 - 15 is 8. The answer is 8.",
),
]
examples["gsm8k-pal"] = [
(
"Olivia has $23. She bought five bagels for $3 each. How much money does she have left?",
'```python\ndef solution():\n """Olivia has $23. She bought five bagels for $3 each. How much money does she have left?"""\n money_initial = 23\n bagels = 5\n bagel_cost = 3\n money_spent = bagels * bagel_cost\n money_left = money_initial - money_spent\n result = money_left\n return result\n```',
),
(
"Michael had 58 golf balls. On tuesday, he lost 23 golf balls. On wednesday, he lost 2 more. How many golf balls did he have at the end of wednesday?",
'```python\ndef solution():\n """Michael had 58 golf balls. On tuesday, he lost 23 golf balls. On wednesday, he lost 2 more. How many golf balls did he have at the end of wednesday?"""\n golf_balls_initial = 58\n golf_balls_lost_tuesday = 23\n golf_balls_lost_wednesday = 2\n golf_balls_left = golf_balls_initial - golf_balls_lost_tuesday - golf_balls_lost_wednesday\n result = golf_balls_left\n return result\n```',
),
(
"There were nine computers in the server room. Five more computers were installed each day, from monday to thursday. How many computers are now in the server room?",
'```python\ndef solution():\n """There were nine computers in the server room. Five more computers were installed each day, from monday to thursday. How many computers are now in the server room?"""\n computers_initial = 9\n computers_per_day = 5\n num_days = 4 # 4 days between monday and thursday\n computers_added = computers_per_day * num_days\n computers_total = computers_initial + computers_added\n result = computers_total\n return result\n```',
),
]
examples["gsm8k-tora"] = [
(
"Olivia has $23. She bought five bagels for $3 each. How much money does she have left?",
"```python\ndef money_left():\n money_initial = 23\n bagels = 5\n bagel_cost = 3\n money_spent = bagels * bagel_cost\n remaining_money = money_initial - money_spent\n return remaining_money\n \nremaining_money = money_left()\nprint(remaining_money)\n```\n```output\n8\n```\nOlivia has $\\boxed{8}$ dollars left.",
),
(
"Michael had 58 golf balls. On tuesday, he lost 23 golf balls. On wednesday, he lost 2 more. How many golf balls did he have at the end of wednesday?",
"```python\ndef remaining_golf_balls():\n golf_balls_initial = 58\n golf_balls_lost_tuesday = 23\n golf_balls_lost_wednesday = 2\n golf_balls_left = golf_balls_initial - golf_balls_lost_tuesday - golf_balls_lost_wednesday\n remaining_golf_balls = golf_balls_left\n return remaining_golf_balls\n\nanswer = remaining_golf_balls() \nprint(answer)\n```\n```output\n33\n```\nMichael had $\\boxed{33}$ golf balls at the end of Wednesday.",
),
(
"There were nine computers in the server room. Five more computers were installed each day, from monday to thursday. How many computers are now in the server room?",
"```python\ndef total_computers():\n computers_initial = 9\n computers_per_day = 5\n num_days = 4 # 4 days between monday and thursday\n computers_added = computers_per_day * num_days\n computers_total = computers_initial + computers_added\n return computers_total\n\ntotal_computers = total_computers()\nprint(total_computers)\n```\n```output\n29\n```\nThere're $\\boxed{29}$ computers in the server room.",
),
]
examples["math"] = [
(
"Kevin Kangaroo begins hopping on a number line at 0. He wants to get to 1, but he can hop only $\\frac{1}{3}$ of the distance. Each hop tires him out so that he continues to hop $\\frac{1}{3}$ of the remaining distance. How far has he hopped after five hops? Express your answer as a common fraction.",
"Let's think step by step\nKevin hops $1/3$ of the remaining distance with every hop.\nHis first hop takes $1/3$ closer.\nFor his second hop, he has $2/3$ left to travel, so he hops forward $(2/3)(1/3)$.\nFor his third hop, he has $(2/3)^2$ left to travel, so he hops forward $(2/3)^2(1/3)$.\nIn general, Kevin hops forward $(2/3)^{k-1}(1/3)$ on his $k$th hop.\nWe want to find how far he has hopped after five hops.\nThis is a finite geometric series with first term $1/3$, common ratio $2/3$, and five terms.\nThus, Kevin has hopped $\\frac{\\frac{1}{3}\\left(1-\\left(\\frac{2}{3}\\right)^5\\right)}{1-\\frac{2}{3}} = \\boxed{\\frac{211}{243}}$.\nThe answer is \\frac{211}{243}}",
),
(
"What is the area of the region defined by the equation $x^2+y^2 - 7 = 4y-14x+3$?",
"Let's think step by step\nWe rewrite the equation as $x^2 + 14x + y^2 - 4y = 10$ and then complete the square,\nresulting in $(x+7)^2-49 + (y-2)^2-4=10$,\nor $(x+7)^2+(y-2)^2=63$.\nThis is the equation of a circle with center $(-7, 2)$ and radius $\\sqrt{63},$\nso the area of this region is $\\pi r^2 = \\boxed{63\\pi}$.\nThe answer is 63\\pi",
),
(
"If $x^2+y^2=1$, what is the largest possible value of $|x|+|y|$?",
"Let's think step by step\nIf $(x,y)$ lies on the circle,\nso does $(x,-y),$ $(-x,-y),$ and $(-x,-y),$ (which all give the same value of $|x| + |y|$),\nso we can assume that $x \\ge 0$ and $y \\ge 0.$\nThen $|x| + |y| = x + y.$ Squaring, we get\n\\[(x + y)^2 = x^2 + 2xy + y^2 = 1 + 2xy.\\]\nNote that $(x - y)^2 \\ge 0.$\nExpanding, we get $x^2 - 2xy + y^2 \\ge 0,$ so $2xy \\le x^2 + y^2 = 1.$\nHence,\\[1 + 2xy \\le 2,\\]which means $x + y \\le \\sqrt{2}.$\nEquality occurs when $x = y = \\frac{1}{\\sqrt{2}},$\nso the maximum value of $|x| + |y|$ is $\\boxed{\\sqrt{2}}.$\nThe answer is \\sqrt{2}",
),
(
"If $f(x)=\\frac{ax+b}{cx+d}, abcd\\not=0$ and $f(f(x))=x$ for all $x$ in the domain of $f$, what is the value of $a+d$?",
"Let's think step by step\nThe condition $f(f(x))$ means that $f$ is the inverse of itself,\nso its graph is symmetrical about the line $y = x$.\nWith a rational function of this form, we will have two asymptotes:\na vertical one at $x=-d/c$ if $cx+d$ does not divide $ax+b$,\nand a horizontal one at $y=a/c$,\nif we take the limit of $f(x)$ as $x$ goes to $\\pm\\infty$.\nIn order for $f$ to be its own inverse, the intersection of the asymptotes must lie on the line $y=x$\nso that it and its asymptotes reflect onto themselves.\nThis means that $-d/c=a/c$,\nand therefore $-d=a$ and $a+d=\\boxed{0}$.\nThe answer is 0",
),
(
"Expand $(2z^2 + 5z - 6)(3z^3 - 2z + 1)$.",
"Let's think step by step\n$$\\begin{array}{crrrrrrr}\n& & & 3z^3 & & -2z & + 1 & \\\\\n\\times & & & & 2z^2 & +5z & -6 \\\\\n\\cline{1-7}\\rule{0pt}{0.17in}\n& & & -18z^3 & & +12z & -6 & \\\\\n& & +15z^4 & & -10z^2 & +5z & & \\\\\n+ & 6z^5 & & -4z^3 & +2z^2 & & & \\\\\n\\cline{1-7}\\rule{0pt}{0.17in}\n& 6z^5 & +15z^4 & -22z^3 & - 8z^2 &+17z & -6 &\n\\end{array}$$\nThe answer is 6z^5+15z^4-22z^3-8z^2+17z-6",
),
]
examples["math_pal"] = [
(
"Display the final result in LaTeX.\n\n Find the coefficient of $x^3$ when $3(x^2 - x^3+x) +3(x +2x^3- 3x^2 + 3x^5+x^3) -5(1+x-4x^3 - x^2)$ is simplifie.",
"```python\nfrom sympy import symbols, simplify\n\ndef solution():\n x = symbols('x')\n expr = 3*(x**2 - x**3 + x) + 3*(x + 2*x**3 - 3*x**2 + 3*x**5 + x**3) - 5*(1 + x - 4*x**3 - x**2)\n simplified_expr = simplify(expr)\n\n x3_coefficient = simplified_expr.as_coefficients_dict()[x**3]\n result = x3_coefficient\n return result\n```",
),
(
"The surface area of a sphere with radius $r$ is $4\\pi r^2$. Including the area of its circular base, what is the total surface area of a hemisphere with radius 6 cm? Express your answer in terms of $\\pi$.",
"```python\nimport math\n\ndef solution():\n radius = 6\n\n # Surface area of the hemisphere\n hemisphere_area = 2 * math.pi * radius**2\n\n # Area of the circular base\n base_area = math.pi * radius**2\n\n # Total surface area\n total_surface_area = hemisphere_area + base_area\n\n # Formatting the result in LaTeX\n result = r'{}\\\\pi'.format(total_surface_area / math.pi)\n return result\n```",
),
(
"Monica tosses a fair 6-sided die. If the roll is a prime number, then she wins that amount of dollars (so that, for example, if she rolls 3, then she wins 3 dollars). If the roll is composite, she wins nothing. Otherwise, she loses 3 dollars. What is the expected value of her winnings on one die toss? Express your answer as a dollar value to the nearest cent.",
'```python\ndef solution():\n # Probabilities of each outcome\n prime_prob = 1 / 6\n composite_prob = 1 / 3\n otherwise_prob = 1 / 6\n\n # Expected value of each outcome\n prime_expected_value = (2 * prime_prob) + (3 * prime_prob) + (5 * prime_prob)\n composite_expected_value = 0 * composite_prob\n otherwise_expected_value = -3 * otherwise_prob\n\n # Total expected value\n total_expected_value = prime_expected_value + composite_expected_value + otherwise_expected_value\n\n # Dollar value to the nearest cent\n result = "{:.2f}".format(total_expected_value)\n return result\n```',
),
(
"Given $\\mathbf{a} = \\begin{pmatrix} -7 \\\\ 0 \\\\ 1 \\end{pmatrix}$ and $\\mathbf{b} = \\begin{pmatrix} 4 \\\\ 2 \\\\ -1 \\end{pmatrix},$ find $\\mathbf{a} - 3 \\mathbf{b}.$",
"```python\nimport numpy as np\n\ndef solution()\n a = np.array([-7, 0, 1])\n b = np.array([4, 2, -1])\n\n result = a - 3 * b\n\n result = r'\\begin{{pmatrix}} {} \\\\ {} \\\\ {} \\end{{pmatrix}}'.format(result[0], result[1], result[2])\n return result\n```",
),
]
examples["math-tora"] = [
(
"Convert the point $(0, -3 \\sqrt{3}, 3)$ in rectangular coordinates to spherical coordinates. Enter your answer in the form $(\\rho,\\theta,\\phi),$ where $\\rho > 0,$ $0 \\le \\theta < 2 \\pi,$ and $0 \\le \\phi \\le \\pi.$",
"To convert the point $(x, y, z)$ in rectangular coordinates to spherical coordinates $(\\rho, \\theta, \\phi)$, we use the following formulas:\n1. $\\rho = \\sqrt{x^2 + y^2 + z^2}$\n2. $\\theta = \\arctan{\\frac{y}{x}}$\n3. $\\phi = \\arccos{\\frac{z}{\\rho}}$\n\nLet's write a function:\n\n```python\nfrom sympy import sqrt, atan2, acos, pi\n\ndef rectangular_to_spherical():\n x, y, z = 0, -3*sqrt(3), 3\n rho = sqrt(x**2 + y**2 + z**2)\n theta = atan2(y, x)\n phi = acos(z/rho)\n return rho, theta, phi\n\nspherical_coordinates = rectangular_to_spherical()\nprint(spherical_coordinates)\n```\n```output\n(6, -pi/2, pi/3)\n```\n\nThe required form of the answer is $(\\rho, \\theta, \\phi)$, where $\\rho > 0$, $0 \\le \\theta < 2\\pi$, and $0 \\le \\phi \\le \\pi$. Since our initial output has $\\theta = -\\pi/2$, which doesn't meet the condition for $\\theta$, we add $2\\pi$ to adjust its value.\n\nHence, the spherical coordinates for the point $(0, -3\\sqrt{3}, 3)$ are $\\boxed{(6, \\frac{3\\pi}{2}, \\frac{\\pi}{3})}$.",
),
(
"Compute $1011_2 + 101_2 - 1100_2 + 1101_2$. Express your answer in base 2.",
'Let\'s write a single python function for binary computation:\n\n```python\ndef binary_sum_diff():\n # Convert binary to decimal\n num1 = int("1011", 2)\n num2 = int("101", 2)\n num3 = int("1100", 2)\n num4 = int("1101", 2)\n\n # Perform the operations\n result = num1 + num2 - num3 + num4\n\n # Convert the result back to binary\n result_binary = format(result, "b")\n\n return result_binary\n\nresult = binary_sum_diff()\nprint(result)\n```\n```output\n10001\n```\nThe answer expressed in base 2 is $\\boxed{10001_2}$.',
),
(
"Find the matrix that corresponds to projecting onto the vector $\\begin{pmatrix} 2 \\\\ -3 \\end{pmatrix}.$",
"```python\nfrom sympy import Matrix, simplify\n\ndef projection_matrix():\n v = Matrix([2, -3])\n\n # Calculate the projection matrix\n P = (v * v.T) / (v.T * v)[0]\n\n # Simplify the elements\n P = P.applyfunc(simplify)\n\n return P\n\nresult = projection_matrix()\nprint(result)\n```\n```output\nMatrix([[4/13, -6/13], [-6/13, 9/13]])\n```\n\nThe matrix is $\\boxed{\\begin{pmatrix} \\frac{4}{13} & -\\frac{6}{13} \\ -\\frac{6}{13} & \\frac{9}{13} \\end{pmatrix}}.$",
),
(
"Solve the inequality\n\\[\\frac{x^2 - 25}{x + 5} < 0.\\]",
"Let's use `sympy` to solve the inequality:\n\n```python\nfrom sympy import symbols, simplify\nfrom sympy.solvers.inequalities import solve_univariate_inequality\nfrom sympy.core.relational import StrictLessThan\n\ndef solve_inequality():\n x = symbols('x')\n expression = (x**2 - 25) / (x + 5)\n inequality = StrictLessThan(expression, 0)\n solution = solve_univariate_inequality(inequality, x, relational=False)\n simplified_solution = simplify(solution)\n \n return simplified_solution\n\nresult = solve_inequality()\nprint(result)\n```\n```output\nUnion(Interval.open(-oo, -5), Interval.Lopen(-5, 5))\n```\nThe answer is $\\boxed{(-\\infty,-5)\\cup(-5,5)}$",
),
(
'In the figure, triangles $ABC$ and $BCD$ are equilateral triangles. What is the value of $AD \\div BC$ when expressed in simplest radical form?\n\n[asy]\ndraw((0,0)--(5,8.7)--(10,0)--cycle);\ndraw((10,0)--(15,8.7)--(5,8.7));\nlabel("$A$",(0,0),SW);\nlabel("$B$",(5,8.7),N);\nlabel("$C$",(10,0),SE);\nlabel("$D$",(15,8.7),NE);\n[/asy]',
"```python\nfrom sympy import Rational, sqrt, simplify\n\ndef ad_divided_by_bc():\n\n x = Rational(1, 1) # Side length of equilateral triangles\n\n ad_squared = 2 * x**2 * (1 + Rational(1, 2)) # Using the law of cosines with cos(2*pi/3) = -1/2\n ad = sqrt(ad_squared)\n\n bc = x # BC is the side length of the equilateral triangles\n\n simplified_ratio = simplify(ad / bc)\n\n return simplified_ratio\n\nresult = ad_divided_by_bc()\nprint(result)\n```\n```output\nsqrt(3)\n```\nThe value of $AD \\div BC$ is $\\boxed{\\sqrt{3}}$.",
),
]
examples["carp_en"] = [
(
"If $3 a ^ { m + 2 } b$ and $\\frac { 1 } { 2 } ab ^ { n - 1 }$ are similar terms, then $m + n$ is equal to.",
"Let's think step by step\n$3 a ^ { m + 2 } b$ and $\\frac { 1 } { 2 } ab ^ { n - 1 }$ are like terms. We can obtain $m + 2 = 1$ and $n - 1 = 1$. Solving for $m$ and $n$, we get $m = - 1$ and $n = 2$. Therefore, $m + n = - 1 + 2 = 1$.\nThe answer is: 1",
),
(
"The solution to the equation $y - \\frac { y - 1 } { 2 } = - \\frac { y + 2 } { 5 }$ is ____ ?",
"Let's think step by step\nTo eliminate the denominator, we have $10 y - 5 ( y - 1 ) = - 2 ( y + 2 )$. Expanding the brackets gives $10 y - 5 y + 5 = - 2 y - 4$. Rearranging terms gives $10 y - 5 y + 2 y = - 4 - 5$, which simplifies to $7 y = - 9$. Dividing both sides by 7 gives $y = - \\frac { 9 } { 7 }$.\nThe answer is: y = - \\frac { 9 } { 7 }",
),
(
"If $( m + 4 ) ^ 2 + | n - 3 | = 0$, then $\\frac { 1 } { 2 } m - n$ = ____?",
"Let's think step by step\n$\\because ( m + 4 ) ^ 2 + | n - 3 | = 0$, $\\therefore m + 4 = 0$, $n - 3 = 0$, which means $m = - 4$, $n = 3$. Then the original expression equals $- 2 - 3 = - 5$.\nThe answer is: - 5",
),
(
"Given a quadratic equation in one variable $x$, $x ^ 2 + x + m = 0$, with one root being $x = 1$, what is the other root of this equation?",
"Let's think step by step\nSuppose the quadratic equation in one variable about $x$ is $x ^ 2 + x + m = 0$, and $\\alpha$ is another real root of the equation. Since one real root of the quadratic equation in one variable about $x$ is $1$, we have $\\alpha + 1 = - 1$. Therefore, $\\alpha = - 2$.\nThe answer is: - 2",
),
(
"The parabola $y = - 5 { x } ^ 2 + 1$ is translated $2$ units upward and $1$ unit to the left, resulting in the parabola _____.",
"Let's think step by step\nThe parabola $y = - 5 { x } ^ 2 + 1$ is first shifted upward by 2 units, resulting in $y = - 5 { x } ^ 2 + 3$. Then it is shifted left by 1 unit, resulting in $y = - 5 {( x + 1 )} ^ 2 + 3$.\nThe answer is: y = - 5 ( x + 1 ) ^ { 2 } + 3",
),
(
"If the radical $\\sqrt { x - 8 }$ is defined, then the range of real numbers for $x$ is ____ ?",
"Let's think step by step\nSince the radical $\\sqrt { x - 8 }$ is defined, therefore $x - 8 \\ge 0$, which implies $x \\ge 8$.\nThe answer is: x \\ge 8",
),
(
"If $a ^ { m } \\times a ^ { 2 } = a ^ { 7 }$, then the value of $m$ is ____?",
"Let's think step by step\nAccording to the multiplication rule of powers with the same base: when multiplying powers with the same base, keep the base the same and add the exponents. We have $m + 2 = 7$, so solving for $m$ gives $m = 5$.\nThe answer is: 5",
),
(
"If line segment $a$ and $b$ satisfy $\\frac { a } { b } = \\frac { 5 } { 2 }$, then the value of $\\frac { a - b } { b }$ is ____?",
"Let's think step by step\n$\\because \\frac { a } { b } = \\frac { 5 } { 2 }$, $\\therefore$ we can assume $a = 5 k$, then $b = 2 k$, $\\therefore \\frac { a - b } { b } = \\frac { 5 k - 2 k } { 2 k } = \\frac { 3 } { 2 }$.\nThe answer is: \\frac { 3 } { 2 }",
),
]
examples["minerva_math"] = [
(
"Find the domain of the expression $\\frac{\\sqrt{x-2}}{\\sqrt{5-x}}$.}",
"The expressions inside each square root must be non-negative.\nTherefore, $x-2 \\ge 0$, so $x\\ge2$, and $5 - x \\ge 0$, so $x \\le 5$.\nAlso, the denominator cannot be equal to zero, so $5-x>0$, which gives $x<5$.\nTherefore, the domain of the expression is $\\boxed{[2,5)}$.",
),
(
"If $\\det \\mathbf{A} = 2$ and $\\det \\mathbf{B} = 12,$ then find $\\det (\\mathbf{A} \\mathbf{B}).$",
"We have that $\\det (\\mathbf{A} \\mathbf{B}) = (\\det \\mathbf{A})(\\det \\mathbf{B}) = (2)(12) = \\boxed{24}.$",
),
(
"Terrell usually lifts two 20-pound weights 12 times. If he uses two 15-pound weights instead, how many times must Terrell lift them in order to lift the same total weight?",
"If Terrell lifts two 20-pound weights 12 times, he lifts a total of $2\\cdot 12\\cdot20=480$ pounds of weight. If he lifts two 15-pound weights instead for $n$ times, he will lift a total of $2\\cdot15\\cdot n=30n$ pounds of weight. Equating this to 480 pounds, we can solve for $n$: \\begin{align*}\n30n&=480\\\\\\\n\\Rightarrow\\qquad n&=480/30=\\boxed{16}\n\\end{align*}",
),
(
"If the system of equations\n\n\\begin{align*}\n6x-4y&=a,\\\\\\\n6y-9x &=b.\n\\end{align*}has a solution $(x, y)$ where $x$ and $y$ are both nonzero, find $\\frac{a}{b},$ assuming $b$ is nonzero.",
"If we multiply the first equation by $-\\frac{3}{2}$, we obtain\n\n$$6y-9x=-\\frac{3}{2}a.$$Since we also know that $6y-9x=b$, we have\n\n$$-\\frac{3}{2}a=b\\Rightarrow\\frac{a}{b}=\\boxed{-\\frac{2}{3}}.$$",
),
]
examples["aqua"] = [
(
"John found that the average of 15 numbers is 40. If 10 is added to each number then the mean of the numbers is?\nAnswer Choices: (A) 50 (B) 45 (C) 65 (D) 78 (E) 64",
"If 10 is added to each number, then the mean of the numbers also increases by 10. So the new mean would be 50. The answer is (A).",
),
(
"If a / b = 3/4 and 8a + 5b = 22,then find the value of a.\nAnswer Choices: (A) 1/2 (B) 3/2 (C) 5/2 (D) 4/2 (E) 7/2",
"a / b = 3/4, then b = 4a / 3. So 8a + 5(4a / 3) = 22. This simplifies to 8a + 20a / 3 = 22, which means 44a / 3 = 22. So a is equal to 3/2. The answer is (B).",
),
(
"A person is traveling at 20 km/hr and reached his destiny in 2.5 hr then find the distance?\nAnswer Choices: (A) 53 km (B) 55 km (C) 52 km (D) 60 km (E) 50 km",
"The distance that the person traveled would have been 20 km/hr * 2.5 hrs = 50 km. The answer is (E).",
),
(
"How many keystrokes are needed to type the numbers from 1 to 500?\nAnswer Choices: (A) 1156 (B) 1392 (C) 1480 (D) 1562 (E) 1788",
"There are 9 one-digit numbers from 1 to 9. There are 90 two-digit numbers from 10 to 99. There are 401 three-digit numbers from 100 to 500. 9 + 90(2) + 401(3) = 1392. The answer is (B).",
),
]
examples["sat_math"] = [
(
"If $\frac{x-1}{3}=k$ and $k=3$, what is the value of $x$ ? \nAnswer Choices: (A) 2 (B) 4 (C) 9 (D) 10",
"If k = 3, then x - 1 = 3 * 3, therfore, x - 1 = 9 and x = 10. The answer is D",
),
(
"For $i=\\sqrt{-1}$, what is the sum $(7+3 i)+(-8+9 i)$ ? \nAnswer Choices: (A) $-1+12 i$ (B) $-1-6 i$ (C) $15+12 i$ (D) $15-6 i$ 3",
"For (7+3 i)+(-8+9 i), the real part is 7 + (-8) = -1, the imageinary part is 3 i + 9 i = 12 i. The answer is A",
),
(
"On Saturday afternoon, Armand sent $m$ text messages each hour for 5 hours, and Tyrone sent $p$ text messages each hour for 4 hours. Which of the following represents the total number of messages sent by Armand and Tyrone on Saturday afternoon?\nAnswer Choices: (A) $9 m p$ (B) $20 m p$ (C) $5 m+4 p$ (D) $4 m+5 p$",
"Armand texts m messages each hour for 5 hours, which leads to 5m messages. Tyrone texts p messages each hour for 4 hours, which leds to 4p messages. The total is 5m + 4p. The answer is C.",
),
(
"$$\begin{array}{r}3 x+4 y=-23 \\2 y-x=-19\\end{array}$$What is the solution $(x, y)$ to the system of equations above?\nAnswer Choices: (A) $(-5,-2)$ (B) $(3,-8)$ (C) $(4,-6)$ (D) $(9,-6)$",
"By solving this equation, we found that x = 3 and y = -8. The answer is B.",
),
]
examples["mmlu_mathematics"] = [
(
"Simplify and write the result with a rational denominator: $$\\sqrt{\\sqrt[3]{\\sqrt{\frac{1}{729}}}}$$\nAnswer Choices: (A) \\frac{3\\sqrt{3}}{3} (B) \\frac{1}{3} (C) \\sqrt{3} (D) \\frac{\\sqrt{3}}{3}",
"Factoring $729=3^6$ and combining the roots $\frac{1}{2}\frac{1}{3}\frac{1}{2}=\frac{1}{12}$, we get that $\\sqrt{\\sqrt[3]{\\sqrt{\frac{1}{729}}}}=\\left(\frac{1}{3^6}\right)^{\frac{1}{12}}=\frac{1}{3^{\frac{1}{2}}}=\frac{3}{\\sqrt{3}}$. The answer is (D).",
),
(
"Five thousand dollars compounded annually at an $x\\%$ interest rate takes six years to double. At the same interest rate, how many years will it take $\\$300$ to grow to $\\$9600$?\nAnswer Choices:(A) 12 (B) 1 (C) 30 (D) 5",
"To go from $\\$300$ to $\\$9600$, the value must go up by a factor of $9600/300=32=2^5$. Since at this interest rate it takes six years for it to double, it will take $5*6=30$ years to grow to $\\$9600$. The answer is (C).",
),
(
"Ten students take a biology test and receive the following scores: 45, 55, 50, 70, 65, 80, 40, 90, 70, 85. What is the mean of the students test scores?\nAnswer Choices: (A) 55 (B) 60 (C) 62 (D) 65",
"There are 10 students and the sum of their scores is $45 + 55 + 50 + 70 + 65 + 80 + 40 + 90 + 70 + 85 = 650$, the mean is $650/10=65$. The answer is (D).",
),
(
"The variable $x$ varies directly as the square of $y$, and $y$ varies directly as the cube of $z$. If $x$ equals $-16$ when $z$ equals 2, what is the value of $x$ when $z$ equals $\frac{1}{2}$?\nAnswer Choices: (A) -1 (B) 16 (C) -\frac{1}{256} (D) \\frac{1}{16}",
"We know that $x \\propto y^2$ and $y \\propto z^3$, so $x = k z^6$ for some constant $k$. Plugging in for $x=-16$ and $z=2$, the constant value is $k=\frac{x}{z^6}=\frac{-16}{64}=-\frac{1}{4}$. So, when $z=\frac{1}{2}$, the value of $x$ is $x=kz^6=-\frac{1}{4}\frac{1}{2^6}=-\frac{1}{256}$. The answer is (C).",
),
(
"Joe was in charge of lights for a dance. The red light blinks every two seconds, the yellow light every three seconds, and the blue light every five seconds. If we include the very beginning and very end of the dance, how many times during a seven minute dance will all the lights come on at the same time? (Assume that all three lights blink simultaneously at the very beginning of the dance.)\nAnswer Choices: (A) 3 (B) 15 (C) 6 (D) 5",
"The least common multiple of 2, 3 and 5 is 30, so during a 7 minute dance, all the three lights will come on at the same time $2*7+1=15$ times. The answer is (B).",
),
]
examples["mmlu_physics"] = [
(
"A microwave oven is connected to an outlet, 120 V, and draws a current of 2 amps. At what rate is energy being used by the microwave oven?\nAnswer Choices: (A) 10 W (B) 30 W (C) 60 W (D) 240 W",
"Rate of energy usage is known as power; in an dissipative electrical circuit, power is given by voltage times current. So in our case, the power is 120 V times 2 amps, or 240 W. The answer is (D).",
),
(
"A point charge, Q = +1 mC, is fixed at the origin. How much work is required to move a charge, Q = +8 µC, from the point (0, 4 meters) to the point (3 meters, 0)?\nAnswer Choices: (A) 3.5 J (B) 6.0 J (C) 22.5 J (D) 40 J",
"To calculate the work required to move a charge from one location to another in a fixed electric field, it is enough to calculate the potential difference between the two locations. Here, the potential only depends on the distance between the charges; its $k q_1 q_2 / r$, where $k$ is Coulombs constant. Plugging in values $q_1 = $ 1 mC, $q_2 = 8 \\mu$ C, gives the answer as 5.992 J, which rounds to 6 J. The answer is (B).",
),
(
"Which of the following conditions will ensure that angular momentum is conserved? I. Conservation of linear momentum II. Zero net external force III. Zero net external torque.\nAnswer Choices: (A) I and II only (B) I and III only (C) II and III only (D) III only",
"Torque is defined as the change in angular momentum; if there is zero external torque, angular momentum is conserved. The answer is (D).",
),
(
"A photocell of work function ϕ = 2eV is connected to a resistor in series. Light of frequency f = 1 × 10^15 Hz hits a metal plate of the photocell. If the power of the light is P = 100 W, what is the current through the resistor?\nAnswer Choices: (A) 2:00 AM (B) 6:00 AM (C) 12:00 AM (D) 24 A",
"The only answer above which has units of current is D, 24 A. The answer is (D).",
),
(
"A pipe full of air is closed at one end. A standing wave is produced in the pipe, causing the pipe to sound a note. Which of the following is a correct statement about the waves properties at the closed end of the pipe?\nAnswer Choices: (A) The pressure is at a node, but the particle displacement is at an antinode. (B) The pressure is at an antinode, but the particle displacement is at a node. (C) The pressure and the particle displacement are both at nodes. (D) The pressure and the particle displacement are both at antinodes.",
"At the closed end of the pipe, the particles cannot have any net displacement because the pipe closure stops them. So the particle displacement is at a node. This closure also causes the pressure to be maximal, i.e. an antinode. The answer is (B).",
),
]
examples["mmlu_chemistry"] = [
(
"Which of the following is considered an acid anhydride?\nAnswer Choices: (A) HCl (B) H2SO3 (C) SO2 (D) Al(NO3)3",
"An acid anhydride is a compound that is derived by removing water from an acid. The chemical formula for water is H2O, which means that we need to determine which of these options, when combined with H2O, forms an acid. SO2, or Sulfur dioxide, when combined with H2O, makes H2SO4, or sulfuric acid. The answer is (C).",
),
(
"Which of the following is expected to be a polar molecule?\nAnswer Choices: (A) PCl4F (B) BF3 (C) CO2 (D) Si(CH3)4",
"A polar molecule is one that has a slightly positive charge on one end of the molecule and a slightly negative charge on the other end. Boron trifluoride (BF3) has Boron as the center atom and three fluorine atoms attached to it; it is trigonal planar and symmetric, so it is nonpolar. Carbon Dioxide (CO2) has Carbon as the central atom with double bonds to two Oxygen atoms - this is also symmetrical and therefore nonpolar. The same is the case for tetramethyl silane (SI(CH3)4), which is a Silicon atom surrounded by four methyl groups. The structure of PCL4F is that Phosphorus is the central atom, attached to four chlorines and one fluorine atom. This is asymmetrical, and therefore has a net dipole and is expected to be a polar molecule. The answer is (A).",
),
(
"From the solubility rules, which of the following is true?\nAnswer Choices: (A) All chlorides, bromides, and iodides are soluble (B) All sulfates are soluble (C) All hydroxides are soluble (D) All ammonium-containing compounds are soluble",
"The chlorides, bromides, and iodides of lead, silver, and mercury are not soluble in water. This rules out (A). The sulfates of lead, barium, and calcium are not soluble in water, which rules out (B). The hydroxides of any metal besides sodium, potassium, ammonium, calcium, and barium are insoluble. This rules out (C). Typically ammonium ions indicate a soluble ionic substance. The answer is (D).",
),
(
"A new compound is synthesized and found to be a monoprotic acid with a molar mass of 248 g/mol. When 0.0050 mol of this acid are dissolved in 0.500 L of water, the pH is measured as 3.89. What is the pKa of this acid?\nAnswer Choices: (A) 3.89 (B) 7.78 (C) 5.78 (D) 2.33",
"Recall that $[A] = [H^{+}]$. Here, this is equal to $$10^{-3.89}$. Then we have $K_{a} = $frac{[H^{+}][A^{-}]}{[HA]} = \\frac{10^{-3.89} \\cdot 10^{-3.89}}{10^{-2}}. The resulting exponent is $-3.89 + (-3.89) - (-2) = 5.78$, therefore $K_a = 10^{-5.78}$. The $pK_a$ is the negative log of $K_a$, which is equal to $5.78$. The answer is (C).",
),
(
"A solution contains 2.00 mole of acetic acid, CH3COOH, and 1.00 mole of calcium acetate, Ca(CH3COO)2. The solution is able to resist the addition of a small amount of strong acid or strong base with only minor changes in the pH of the solution. Larger quantities of strong acid or strong base can cause a significant change in pH. How many moles of nitric acid, HNO3, may be added before the pH begins to change significantly?\nAnswer Choices: (A) 0.500 mole (B) 1.00 mole (C) 2.00 mole (D) 3.00 mole",
"We would like to compute the buffer capacity of this solution. First we write the equation for the ionization of the weak acid, in this case of acetic acid. $CH_{3}COOH (aq) + H_{2}O \rightarrow H_{3}O^{+} + CH3COO^{-}$. The conjugate base is therefore the acetate ion. The added strong acid, Nitric acid, will react with the conjugate base. Therefore the maximum amount of acid that can be added will be equal to the amount of acetate ion, or 2 moles. The answer is (C).",
),
]
examples["mmlu_biology"] = [
(
"In animal cells, which of the following represents the most likely pathway that a secretory protein takes as it is synthesized in a cell?\nAnswer Choices: (A) Plasma membraneGolgi apparatusribosomesecretory vesiclerough ER (B) RibosomeGolgi apparatusrough ERsecretory vesicleplasma membrane (C) Plasma membraneGolgi apparatusribosomesecretory vesiclerough ER (D) Ribosomerough ERGolgi apparatussecretory vesicleplasma membrane",
"Protein synthesis starts at the ribosome, so we can eliminate (A) and (C). The ribosome is often in the endoplasmic reticulum and moves from there to the Golgi apparatus, where it is modified and packaged into a vesicle. The vesicle then floats to the plasma membrane and is secreted. The answer is (D).",
),
(
"A mutation in a bacterial enzyme changed a previously polar amino acid into a nonpolar amino acid. This amino acid was located at a site distant from the enzymes active site. How might this mutation alter the enzymes substrate specificity?\nAnswer Choices: (A) By changing the enzymes pH optimum (B) By changing the enzymes location in the cell (C) By changing the shape of the protein (D) An amino acid change away from the active site cannot alter the enzymes substrate specificity.",
"A change in an amino acid leads to a change in the primary structure of the protein. A change in the primary structure may lead to a change in the secondary and the tertiary structure of the protein. A change in the tertiary structure means a change in the shape of the protein, so (C) has to be correct. Since the change does not affect the active site of the enzyme, we do not expect the activity of the enzyme to be affected. The answer is (C).",
),
(
"Which of the following is not a way to form recombinant DNA?\nAnswer Choices: (A) Translation (B) Conjugation (C) Specialized transduction (D) Transformation",
"The introduction of foreign DNA or RNA into bacteria or eukaryotic cells is a common technique in molecular biology and scientific research. There are multiple ways foreign DNA can be introduced into cells including transformation, transduction, conjugation, and transfection. In contrast, (A) is not a way to form DNA: during translation the ribosomes synthesize proteins from RNA. The answer is (A).",
),
(
"Homologous structures are often cited as evidence for the process of natural selection. All of the following are examples of homologous structures EXCEPT\nAnswer Choices: (A) the wings of a bird and the wings of a bat (B) the flippers of a whale and the arms of a man (C) the pectoral fins of a porpoise and the flippers of a seal (D) the forelegs of an insect and the forelimbs of a dog",
"Homologous structures are similar physical features in organisms that share a common ancestor but different functions. Comparisons (B) and (C) are clearly homologous because they share a common ancestor and the structures serve different purposes. Bat wings and birg wings are also homologous, while they are both wings, the forelimbs serve different purposes. Insects and dogs are very far ancestors since one is vertebrate while the other is invertebrate and the forelimbs serve the same purpose, so they are not homologous. The answer is (D).",
),
(
"Which of the following is not known to be involved in the control of cell division?\nAnswer Choices: (A) Cyclins (B) Protein kinases (C) Checkpoints (D) Fibroblast cells",
"Normal cells move through the cell cycle in a regulated way. At the checkpoint stage, they use information about their own internal state and cues from the environment around them to decide whether to proceed with cell division. Cues like these act by changing the activity of core cell cycle regulators inside the cell. The most common regulators are cyclins and cyclin-dependent kinases. Fibroblast cells do not play any role in cell division. The answer is (D).",
),
]
examples["mmlu_computer"] = [
(
"Which of the following is an example of the use of a device on the Internet of Things (IoT) ?\nAnswer Choices: (A) A car alerts a driver that it is about to hit an object. (B) A hiker uses a G P S watch to keep track of her position. (C) A refrigerator orders milk from an online delivery service when the milk in the refrigerator is almost gone. (D) A runner uses a watch with optical sensors to monitor his heart rate.",
"The term Internet of Things (IoT) refers to common devices which are connected to the internet, enabling new functionality. Choice A is incorrect because it does not describe an internet connected device. In choice B, the watch is only described as having GPS functionality but no internet connectivity. Choice C describes a common device (a refrigerator) which has internet connectivity enabling new functionality (online ordering). Choice D does not mention internet connectivity for the watch, only optical sensors. The answer is (C).",
),
(
"Many Web browsers allow users to open anonymous windows. During a browsing session in an anonymous window, the browser does not record a browsing history or a list of downloaded files. When the anonymous window is exited, cookies created during the session are deleted. Which of the following statements about browsing sessions in an anonymous window is true?\nAnswer Choices: (A) The activities of a user browsing in an anonymous window will not be visible to people who monitor the user's network, such as the system administrator. (B) Items placed in a Web store's shopping cart for future purchase during the anonymous browsing session will not be saved on the user's computer. (C) A user will not be able to log in to e-mail or social media accounts during the anonymous browsing session. (D) A user browsing in an anonymous window will be protected from viruses launched from any web sites visited or files downloaded.",
"Choice A is incorrect as it only describes network traffic, which an anonymous browser does not change. Choice B is correct as it correctly describes how an anonymous browser will prevent saving data on the users computer after the session is ended. Choice C is incorrect because an anonymous browser will not prevent logging in to email or social media accounts. Choice D is incorrect because an anonymous browser in itself performs no virus protection. The answer is (B).",
),
(
'What is the output of "abc"[::-1] in Python 3? \nAnswer Choices: (A) Error (B) abc (C) cba (D) c',
'We know that the slicing operator [::-1] takes all of the elements in the string in reverse order, so we reverse the order of the string "abc", resulting in "cba". The answer is (C).',
),
(
'In the program below, the initial value of X is 5 and the initial value of Y is 10.\nIF (X < 0){\n DISPLAY ("Foxtrot")\n} ELSE {\n IF (X > Y){\n DISPLAY ("Hotel")\n } ELSE {\n IF (Y > 0){\n DISPLAY ("November")\n } ELSE {\n DISPLAY ("Yankee")\n }\n }\n}\nWhat is displayed as a result of running the program?\nAnswer Choices: (A) Foxtrot (B) Hotel (C) November (D) Yankee',
'Because X has the value 5, the first conditional IF (X < 0) is false, so we move to the first ELSE clause. Because X is 5 and Y is 10, the second conditional IF (X > Y) is false, so we move to the following ELSE clause. Since Y is 10, the conditional IF (Y > 0) is true, so the command DISPLAY ("November") is executed. The answer is (C).',
),
(
"A list of numbers has n elements, indexed from 1 to n. The following algorithm is intended to display the number of elements in the list that have a value greater than 100. The algorithm uses the variables count and position. Steps 3 and 4 are missing.\n Step 1: Set count to 0 and position to 1.\n Step 2: If the value of the element at index position is greater than 100, increase the value of count by 1.\n Step 3: (missing step)\n Step 4: (missing step)\n Step 5: Display the value of count.\nWhich of the following could be used to replace steps 3 and 4 so that the algorithm works as intended?\nAnswer Choices: (A) Step 3: Increase the value of position by 1.\n Step 4: Repeat steps 2 and 3 until the value of count is greater than 100.\n(B) Step 3: Increase the value of position by 1.\n Step 4: Repeat steps 2 and 3 until the value of position is greater than n.\n(C) Step 3: Repeat step 2 until the value of count is greater than 100.\n Step 4: Increase the value of position by 1.\n(D) Step 3: Repeat step 2 until the value of position is greater than n.\n Step 4: Increase the value of count by 1.",
"Choice A is incorrect, because its Step 4 has an incorrect termination condition, stopping when count is greater than 100. We need to stop after inspecting all elements in the list. Choice B is correct because it correctly increments both count and position, and correctly repeats these steps and terminates when all elements in the list have been inspected. Choice C is incorrect because it incorrectly increments the variable count until its value is greater than 100, regardless of the elements in the list. Choice D is incorrect because its step 3 does not increment the value of position, so it will repeat forever. The answer is (B).",
),
]
# mammoth
examples["mmlu_stem"] = [
(
"Simplify and write the result with a rational denominator: $$\\sqrt{\\sqrt[3]{\\sqrt{\frac{1}{729}}}}$$\nAnswer Choices: (A) \\frac{3\\sqrt{3}}{3} (B) \\frac{1}{3} (C) \\sqrt{3} (D) \\frac{\\sqrt{3}}{3}",
"Factoring $729=3^6$ and combining the roots $\\frac{1}{2}\\frac{1}{3}\\frac{1}{2}=\\frac{1}{12}$, we get that $\\sqrt{\\sqrt[3]{\\sqrt{\frac{1}{729}}}}=\\left(\frac{1}{3^6}\right)^{\frac{1}{12}}=\frac{1}{3^{\frac{1}{2}}}=\frac{3}{\\sqrt{3}}$. The answer is (D).",
),
(
"In animal cells, which of the following represents the most likely pathway that a secretory protein takes as it is synthesized in a cell?\nAnswer Choices: (A) Plasma membraneGolgi apparatusribosomesecretory vesiclerough ER (B) RibosomeGolgi apparatusrough ERsecretory vesicleplasma membrane (C) Plasma membraneGolgi apparatusribosomesecretory vesiclerough ER (D) Ribosomerough ERGolgi apparatussecretory vesicleplasma membrane",
"Protein synthesis starts at the ribosome, so we can eliminate (A) and (C). The ribosome is often in the endoplasmic reticulum and moves from there to the Golgi apparatus, where it is modified and packaged into a vesicle. The vesicle then floats to the plasma membrane and is secreted. The answer is (D).",
),
(
"A microwave oven is connected to an outlet, 120 V, and draws a current of 2 amps. At what rate is energy being used by the microwave oven?\nAnswer Choices: (A) 10 W (B) 30 W (C) 60 W (D) 240 W",
"Rate of energy usage is known as power; in an dissipative electrical circuit, power is given by voltage times current. So in our case, the power is 120 V times 2 amps, or 240 W. The answer is (D).",
),
(
"Which of the following is considered an acid anhydride?\nAnswer Choices: (A) HCl (B) H2SO3 (C) SO2 (D) Al(NO3)3",
"An acid anhydride is a compound that is derived by removing water from an acid. The chemical formula for water is H2O, which means that we need to determine which of these options, when combined with H2O, forms an acid. SO2, or Sulfur dioxide, when combined with H2O, makes H2SO4, or sulfuric acid. The answer is (C).",
),
(
'What is the output of "abc"[::-1] in Python 3? \nAnswer Choices: (A) Error (B) abc (C) cba (D) c',
'We know that the slicing operator [::-1] takes all of the elements in the string in reverse order, so we reverse the order of the string "abc", resulting in "cba". The answer is (C).',
),
]
examples["gaokao"] = [
(
"已知 $\\alpha, \\beta, \\gamma$ 是互不相同的锐角, 则在 $\\sin \\alpha \\cos \\beta, \\sin \\beta \\cos \\gamma, \\sin \\gamma \\cos \\alpha$ 三个值中, 大于 $\\frac{1}{2}$ 的个数的最大值是 ( )\n从以下选项中选择:\n(A) 0\n(B) 1\n(C) 2\n(D) 3",
"1. 如果 $\\alpha, \\beta, \\gamma$ 均小于 $60^\\circ$,那么他们的正弦值都小于 $\\frac{1}{2}$,因此三个值中不可能有大于 $\\frac{1}{2}$ 的值。\n2. 如果有一个角大于 $60^\\circ$,假设为 $\\alpha$,那么对应的正弦值大于 $\\frac{1}{2}$。此时,由于三角形内角和为 $180^\\circ$,所以 $\\beta + \\gamma < 120^\\circ$。这意味着 $\\beta, \\gamma$ 的余弦值均大于 $\\frac{1}{2}$,所以此时 $\\sin \\alpha \\cos \\beta > \\frac{1}{2}, \\sin \\beta \\cos \\gamma > \\frac{1}{2}$。\n3. 如果有两个角大于 $60^\\circ$,例如 $\\alpha$ 和 $\\beta$,那么由于三角形内角和为 $180^\\circ$,我们可以得到 $\\gamma < 60^\\circ$,此时 $\\sin \\gamma < \\frac{1}{2}$。由于 $\\alpha$ 和 $\\beta$ 的余弦值都小于 $\\frac{1}{2}$,因此三个值中不可能有大于 $\\frac{1}{2}$ 的值。\n4. 如果三个角都大于 $60^\\circ$,显然不符合题意。\n综上所述,当有一个角大于 $60^\\circ$ 时,大于 $\\frac{1}{2}$ 的个数的最大值是 2。\n答案是 C",
),
(
"正方体 $A B C D-A_{1} B_{1} C_{1} D_{1}$ 中, $B B_{1}$ 与平面 $A C D_{1}$ 所成角的余弦值为 ( )\n从以下选项中选择:\n(A) $\\frac{\\sqrt{2}}{3}$\n(B) $\\frac{\\sqrt{3}}{3}$\n(C) $\\frac{2}{3}$\n(D) $\\frac{\\sqrt{6}}{3}$",
"设上下底面的中心分别为 $\\mathrm{O}_{1}, \\mathrm{O}$, 设正方体的棱长等于 1 , 则 $O_{1} O$ 与平面 $A C D_{1}$ 所成角就是 $B B_{1}$ 与平面 $A C D_{1}$ 所成角, 即 $\\angle O_{1} O D_{1}$, 直角三角形 $\\mathrm{OO}_{1} \\mathrm{D}_{1}$ 中, $\\cos \\angle \\mathrm{O}_{1} \\mathrm{OD}_{1}=\\frac{\\mathrm{O}_{1} \\mathrm{O}}{\\mathrm{OD}_{1}}=\\frac{\\frac{1}{\\sqrt{6}}}{2}=\\frac{\\sqrt{6}}{3}$. \n答案是 C",
),
(
"设函数 $f(x)=\\left\\{\\begin{array}{ll}1+\\log _{2}(2-x), & x<1 \\ 2^{x-1}, & x \\geqslant 1,\\end{array}\\right.$ 则 $f(-2)+f\\left(\\log _{2} 12\\right)=$ ( )\n从以下选项中选择:\n(A) 3\n(B) 6\n(C) 9\n(D) 12",
"首先,我们可以根据定义计算 $f(-2)$ 和 $f(\\log_2 12)$\n$f(-2)=1+\\log_2(2-(-2))=1+\\log_2 4=3$\n$f(\\log_2 12)=2^{\\log_2 12-1}=6$\n因此,$f(-2)+f(\\log_2 12)=3+6=9$。\n答案是 C",
),
(
"已知函数 $f(x)=a x^{3}-3 x^{2}+1$, 若 $f(x)$ 存在唯一的零点 $x_{0}$, 且 $x_{0}>$ 0 , 则实数 $\\mathrm{a}$ 的取值范围是 ( )\n从以下选项中选择:\n(A) $(1,+\\infty)$\n(B) $(2,+\\infty)$\n(C) $(-\\infty,-1)$\n(D) $(-\\infty,-2)$",
"首先,我们可以通过求出函数的导函数 $f'(x)$ 来判断函数在 $x>0$ 区间内的单调性。在这里,我们求出导函数 $f'(x)$ 为 $f'(x)=3ax^2-6x$。\n然后,我们需要求出导函数 $f'(x)$ 的零点,以确定函数 $f(x)$ 在 $x>0$ 区间内的单调性。导函数 $f'(x)$ 的零点为 $x=0$ 和 $x=\\frac{2}{\\sqrt{a}}$。注意到 $x>0$,所以我们得到 $a<0$。此外,由于函数 $f(x)$ 在 $x=0$ 处的函数值为 $1$,因此不能有 $a=\\frac{4}{3}$。\n综上所述,当 $a$ 的取值范围为 $a<-\\frac{4}{3}$ 时,函数 $f(x)$ 在 $x>0$ 区间内是单调递减的,此时存在唯一的零点 $x_0$。因此,答案为 $(-\\infty,-2)$。\n答案是 D",
),
(
"设 $\\left\\{a_{n}\\right\\}$ 是公差不为 0 的无穷等差数列, 则“ $\\left\\{a_{n}\\right\\}$ 为递增数列”是“存在正整数 $N_{0}$, 当 $n>N_{0}$ 时, $a_{n}>0$ ”的 ( )\n从以下选项中选择:\n(A) 充分而不必要条件\n(B) 必要而不充分条件\n(C) 充分必要条件\n(D) 既不充分也不必要条件",
"首先,我们可以通过举例来判断该条件是充分还是必要条件。如果一个数列递增,那么它的公差一定大于 0,也就是存在正整数 $N_{0}$,当 $n>N_{0}$ 时,$a_{n}>0$。因此,“ $\\left\\{a_{n}\\right\\}$ 为递增数列”是“存在正整数 $N_{0}$, 当 $n>N_{0}$ 时, $a_{n}>0$ ”的必要条件。\n接下来,我们需要判断是否充分。也就是说,如果存在正整数 $N_{0}$,当 $n>N_{0}$ 时,$a_{n}>0$,那么能否得出“ $\\left\\{a_{n}\\right\\}$ 为递增数列”这一结论。答案是肯定的。因为如果 $a_{n}>0$,那么 $a_{n+1}-a_{n}>0$,即公差大于 0,因此该数列是递增的。因此,该条件是充分条件。\n综上所述,选项为 (C) 充分必要条件。\n答案是 C",
),
]
return examples
+395
View File
@@ -0,0 +1,395 @@
"""
This logic is largely copied from the Hendrycks' MATH release (math_equivalence), and borrowed from:
- https://github.com/microsoft/ProphetNet/tree/master/CRITIC
- https://github.com/openai/prm800k
- https://github.com/microsoft/ToRA/blob/main/src/eval/grader.py
- https://github.com/deepseek-ai/DeepSeek-Math/blob/main/evaluation/eval/eval_utils.py
"""
import re
import regex
import multiprocessing
from math import isclose
from typing import Union
from collections import defaultdict
from sympy import simplify, N
from sympy.parsing.sympy_parser import parse_expr
from sympy.parsing.latex import parse_latex
from latex2sympy2 import latex2sympy
# from .parser import choice_answer_clean, strip_string
# from parser import choice_answer_clean
def choice_answer_clean(pred: str):
pred = pred.strip("\n").rstrip(".").rstrip("/").strip(" ").lstrip(":")
# Clean the answer based on the dataset
tmp = re.findall(r"\b(A|B|C|D|E)\b", pred.upper())
if tmp:
pred = tmp
else:
pred = [pred.strip().strip(".")]
pred = pred[-1]
# Remove the period at the end, again!
pred = pred.rstrip(".").rstrip("/")
return pred
def parse_digits(num):
num = regex.sub(",", "", str(num))
try:
return float(num)
except:
if num.endswith("%"):
num = num[:-1]
if num.endswith("\\"):
num = num[:-1]
try:
return float(num) / 100
except:
pass
return None
def is_digit(num):
# paired with parse_digits
return parse_digits(num) is not None
def str_to_pmatrix(input_str):
input_str = input_str.strip()
matrix_str = re.findall(r"\{.*,.*\}", input_str)
pmatrix_list = []
for m in matrix_str:
m = m.strip("{}")
pmatrix = r"\begin{pmatrix}" + m.replace(",", "\\") + r"\end{pmatrix}"
pmatrix_list.append(pmatrix)
return ", ".join(pmatrix_list)
def math_equal(
prediction: Union[bool, float, str],
reference: Union[float, str],
include_percentage: bool = True,
is_close: bool = True,
timeout: bool = False,
) -> bool:
"""
Exact match of math if and only if:
1. numerical equal: both can convert to float and are equal
2. symbolic equal: both can convert to sympy expression and are equal
"""
# print("Judge:", prediction, reference)
if prediction is None or reference is None:
return False
if str(prediction.strip().lower()) == str(reference.strip().lower()):
return True
if (
reference in ["A", "B", "C", "D", "E"]
and choice_answer_clean(prediction) == reference
):
return True
try: # 1. numerical equal
if is_digit(prediction) and is_digit(reference):
prediction = parse_digits(prediction)
reference = parse_digits(reference)
# number questions
if include_percentage:
gt_result = [reference / 100, reference, reference * 100]
else:
gt_result = [reference]
for item in gt_result:
try:
if is_close:
if numeric_equal(prediction, item):
return True
else:
if item == prediction:
return True
except Exception:
continue
return False
except:
pass
if not prediction and prediction not in [0, False]:
return False
# 2. symbolic equal
reference = str(reference).strip()
prediction = str(prediction).strip()
## pmatrix (amps)
if "pmatrix" in prediction and not "pmatrix" in reference:
reference = str_to_pmatrix(reference)
## deal with [], (), {}
pred_str, ref_str = prediction, reference
if (
prediction.startswith("[")
and prediction.endswith("]")
and not reference.startswith("(")
) or (
prediction.startswith("(")
and prediction.endswith(")")
and not reference.startswith("[")
):
pred_str = pred_str.strip("[]()")
ref_str = ref_str.strip("[]()")
for s in ["{", "}", "(", ")"]:
ref_str = ref_str.replace(s, "")
pred_str = pred_str.replace(s, "")
if pred_str.lower() == ref_str.lower():
return True
## [a, b] vs. [c, d], return a==c and b==d
if (
regex.match(r"(\(|\[).+(\)|\])", prediction) is not None
and regex.match(r"(\(|\[).+(\)|\])", reference) is not None
):
pred_parts = prediction[1:-1].split(",")
ref_parts = reference[1:-1].split(",")
if len(pred_parts) == len(ref_parts):
if all(
[
math_equal(
pred_parts[i], ref_parts[i], include_percentage, is_close
)
for i in range(len(pred_parts))
]
):
return True
if (
(
prediction.startswith("\\begin{pmatrix}")
or prediction.startswith("\\begin{bmatrix}")
)
and (
prediction.endswith("\\end{pmatrix}")
or prediction.endswith("\\end{bmatrix}")
)
and (
reference.startswith("\\begin{pmatrix}")
or reference.startswith("\\begin{bmatrix}")
)
and (
reference.endswith("\\end{pmatrix}") or reference.endswith("\\end{bmatrix}")
)
):
pred_lines = [
line.strip()
for line in prediction[
len("\\begin{pmatrix}") : -len("\\end{pmatrix}")
].split("\\\\")
if line.strip()
]
ref_lines = [
line.strip()
for line in reference[
len("\\begin{pmatrix}") : -len("\\end{pmatrix}")
].split("\\\\")
if line.strip()
]
matched = True
if len(pred_lines) == len(ref_lines):
for pred_line, ref_line in zip(pred_lines, ref_lines):
pred_parts = pred_line.split("&")
ref_parts = ref_line.split("&")
if len(pred_parts) == len(ref_parts):
if not all(
[
math_equal(
pred_parts[i],
ref_parts[i],
include_percentage,
is_close,
)
for i in range(len(pred_parts))
]
):
matched = False
break
else:
matched = False
if not matched:
break
else:
matched = False
if matched:
return True
if prediction.count("=") == 1 and reference.count("=") == 1:
pred = prediction.split("=")
pred = f"{pred[0].strip()} - ({pred[1].strip()})"
ref = reference.split("=")
ref = f"{ref[0].strip()} - ({ref[1].strip()})"
if symbolic_equal(pred, ref) or symbolic_equal(f"-({pred})", ref):
return True
elif (
prediction.count("=") == 1
and len(prediction.split("=")[0].strip()) <= 2
and "=" not in reference
):
if math_equal(
prediction.split("=")[1], reference, include_percentage, is_close
):
return True
elif (
reference.count("=") == 1
and len(reference.split("=")[0].strip()) <= 2
and "=" not in prediction
):
if math_equal(
prediction, reference.split("=")[1], include_percentage, is_close
):
return True
# symbolic equal with sympy
if timeout:
if call_with_timeout(symbolic_equal_process, prediction, reference):
return True
else:
if symbolic_equal(prediction, reference):
return True
return False
def math_equal_process(param):
return math_equal(param[-2], param[-1])
def numeric_equal(prediction: float, reference: float):
# Note that relative tolerance has significant impact
# on the result of the synthesized GSM-Hard dataset
# if reference.is_integer():
# return isclose(reference, round(prediction), abs_tol=1e-4)
# else:
# prediction = round(prediction, len(str(reference).split(".")[-1]))
return isclose(reference, prediction, rel_tol=1e-4)
def symbolic_equal(a, b):
def _parse(s):
for f in [parse_latex, parse_expr, latex2sympy]:
try:
return f(s.replace("\\\\", "\\"))
except:
try:
return f(s)
except:
pass
return s
a = _parse(a)
b = _parse(b)
# direct equal
try:
if str(a) == str(b) or a == b:
return True
except:
pass
# simplify equal
try:
if a.equals(b) or simplify(a - b) == 0:
return True
except:
pass
# equation equal
try:
if (abs(a.lhs - a.rhs)).equals(abs(b.lhs - b.rhs)):
return True
except:
pass
try:
if numeric_equal(float(N(a)), float(N(b))):
return True
except:
pass
# matrix
try:
# if a and b are matrix
if a.shape == b.shape:
_a = a.applyfunc(lambda x: round(x, 3))
_b = b.applyfunc(lambda x: round(x, 3))
if _a.equals(_b):
return True
except:
pass
return False
def symbolic_equal_process(a, b, output_queue):
result = symbolic_equal(a, b)
output_queue.put(result)
def call_with_timeout(func, *args, timeout=1, **kwargs):
output_queue = multiprocessing.Queue()
process_args = args + (output_queue,)
process = multiprocessing.Process(target=func, args=process_args, kwargs=kwargs)
process.start()
process.join(timeout)
if process.is_alive():
process.terminate()
process.join()
return False
return output_queue.get()
def _test_math_equal():
# print(math_equal("0.0833333333333333", "\\frac{1}{12}"))
# print(math_equal("(1,4.5)", "(1,\\frac{9}{2})"))
# print(math_equal("\\frac{x}{7}+\\frac{2}{7}", "\\frac{x+2}{7}", timeout=True))
# print(math_equal("\\sec^2(y)", "\\tan^2(y)+1", timeout=True))
# print(math_equal("\\begin{pmatrix}-\\frac{7}{4}&-2\\\\4&\\frac{1}{4}\\end{pmatrix}", "(\\begin{pmatrix}-\\frac{7}{4}&-2\\\\4&\\frac{1}{4}\\\\\\end{pmatrix})", timeout=True))
# pred = '\\begin{pmatrix}\\frac{1}{3x^{2/3}}&0&0\\\\0&1&0\\\\-\\sin(x)&0&0\\end{pmatrix}'
# gt = '(\\begin{pmatrix}\\frac{1}{3\\sqrt[3]{x}^2}&0&0\\\\0&1&0\\\\-\\sin(x)&0&0\\\\\\end{pmatrix})'
# pred= '-\\frac{8x^2}{9(x^2-2)^{5/3}}+\\frac{2}{3(x^2-2)^{2/3}}'
# gt= '-\\frac{2(x^2+6)}{9(x^2-2)\\sqrt[3]{x^2-2}^2}'
# pred = '-34x-45y+20z-100=0'
# gt = '34x+45y-20z+100=0'
# pred = '\\frac{100}{3}'
# gt = '33.3'
# pred = '\\begin{pmatrix}0.290243531202435\\\\0.196008371385084\\\\-0.186381278538813\\end{pmatrix}'
# gt = '(\\begin{pmatrix}0.29\\\\0.196\\\\-0.186\\\\\\end{pmatrix})'
# pred = '\\frac{\\sqrt{\\sqrt{11}+\\sqrt{194}}}{2\\sqrt{33}+15}'
# gt = '\\frac{\\sqrt{\\sqrt{11}+\\sqrt{194}}}{15+2\\sqrt{33}}'
# pred = '(+5)(b+2)'
# gt = '(a+5)(b+2)'
# pred = '\\frac{1+\\sqrt{5}}{2}'
# gt = '2'
# pred = '\\frac{34}{16}+\\frac{\\sqrt{1358}}{16}', gt = '4'
# pred = '1', gt = '1\\\\sqrt{19}'
# pred = "(0.6,2.6667]"
# gt = "(\\frac{3}{5},\\frac{8}{3}]"
gt = "x+2n+1"
pred = "x+1"
print(math_equal(pred, gt, timeout=True))
if __name__ == "__main__":
_test_math_equal()
+405
View File
@@ -0,0 +1,405 @@
import random
import os
import argparse
import time
from vllm import LLM, SamplingParams
from datetime import datetime
from tqdm import tqdm
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from evaluate import evaluate
from utils import set_seed, load_jsonl, save_jsonl, construct_prompt
from parser import *
from trajectory import *
from data_loader import load_data
from python_executor import PythonExecutor
from model_utils import load_hf_lm_and_tokenizer, generate_completions
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--data_names", default="gsm8k,math", type=str)
parser.add_argument("--data_dir", default="./data", type=str)
parser.add_argument("--model_name_or_path", default="gpt-4", type=str)
parser.add_argument("--output_dir", default="./output", type=str)
parser.add_argument("--prompt_type", default="tool-integrated", type=str)
parser.add_argument("--split", default="test", type=str)
parser.add_argument("--num_test_sample", default=-1, type=int) # -1 for full data
parser.add_argument("--seed", default=0, type=int)
parser.add_argument("--start", default=0, type=int)
parser.add_argument("--end", default=-1, type=int)
parser.add_argument("--temperature", default=0, type=float)
parser.add_argument("--n_sampling", default=1, type=int)
parser.add_argument("--top_p", default=1, type=float)
parser.add_argument("--max_tokens_per_call", default=2048, type=int)
parser.add_argument("--shuffle", action="store_true")
parser.add_argument("--use_vllm", action="store_true")
parser.add_argument("--save_outputs", action="store_true")
parser.add_argument("--overwrite", action="store_true")
parser.add_argument("--use_safetensors", action="store_true")
parser.add_argument("--num_shots", type=int, default=0)
parser.add_argument(
"--apply_chat_template",
action="store_true",
help="Apply chat template to prompt.",
)
parser.add_argument("--pipeline_parallel_size", type=int, default=1)
parser.add_argument(
"--adapt_few_shot",
action="store_true",
help="Few shot for multiple-choice questions, zero shot for others.",
)
args = parser.parse_args()
args.top_p = (
1 if args.temperature == 0 else args.top_p
) # top_p must be 1 when using greedy sampling (vllm)
return args
def prepare_data(data_name, args):
examples = load_data(data_name, args.split, args.data_dir)
# sample `num_test_sample` from dataset
if args.num_test_sample > 0:
# examples = random.sample(examples, min(args.num_test_sample, len(examples)))
examples = examples[: args.num_test_sample]
# shuffle
if args.shuffle:
random.seed(datetime.now().timestamp())
random.shuffle(examples)
# select start and end
examples = examples[args.start : len(examples) if args.end == -1 else args.end]
# get out_file name
dt_string = datetime.now().strftime("%m-%d_%H-%M")
model_name = "/".join(args.model_name_or_path.split("/")[-2:])
out_file_prefix = f"{args.split}_{args.prompt_type}_{args.num_test_sample}_seed{args.seed}_t{args.temperature}"
output_dir = args.output_dir
if not os.path.exists(output_dir):
output_dir = f"outputs/{output_dir}"
out_file = f"{output_dir}/{data_name}/{out_file_prefix}_s{args.start}_e{args.end}.jsonl"
os.makedirs(f"{output_dir}/{data_name}", exist_ok=True)
# load all processed samples
processed_samples = []
if not args.overwrite:
processed_files = [
f
for f in os.listdir(f"{output_dir}/{data_name}/")
if f.endswith(".jsonl") and f.startswith(out_file_prefix)
]
for f in processed_files:
processed_samples.extend(
list(load_jsonl(f"{output_dir}/{data_name}/{f}"))
)
# dedepulicate
processed_samples = {sample["idx"]: sample for sample in processed_samples}
processed_idxs = list(processed_samples.keys())
processed_samples = list(processed_samples.values())
examples = [example for example in examples if example["idx"] not in processed_idxs]
return examples, processed_samples, out_file
def setup(args):
# load model
available_gpus = os.environ["CUDA_VISIBLE_DEVICES"].split(",")
if args.use_vllm:
llm = LLM(
model=args.model_name_or_path,
tensor_parallel_size=len(available_gpus) // args.pipeline_parallel_size,
pipeline_parallel_size=args.pipeline_parallel_size,
trust_remote_code=True,
)
tokenizer = None
if args.apply_chat_template:
tokenizer = AutoTokenizer.from_pretrained(
args.model_name_or_path, trust_remote_code=True
)
else:
llm, tokenizer = load_hf_lm_and_tokenizer(
model_name_or_path=args.model_name_or_path,
load_in_half=True,
use_fast_tokenizer=True,
use_safetensors=args.use_safetensors,
)
# infer & eval
data_list = args.data_names.split(",")
results = []
for data_name in data_list:
results.append(main(llm, tokenizer, data_name, args))
# add "avg" result to data_list and results
data_list.append("avg")
results.append(
{
"acc": sum([result["acc"] for result in results]) / len(results),
}
)
# print all results
pad = max([len(data_name) for data_name in data_list])
print("\t".join(data_name.ljust(pad, " ") for data_name in data_list))
print("\t".join([f"{result['acc']:.1f}".ljust(pad, " ") for result in results]))
def is_multi_choice(answer):
for c in answer:
if c not in ["A", "B", "C", "D", "E"]:
return False
return True
def main(llm, tokenizer, data_name, args):
examples, processed_samples, out_file = prepare_data(data_name, args)
print("=" * 50)
print("data:", data_name, " ,remain samples:", len(examples))
if len(examples) > 0:
print(examples[0])
# init python executor
if "pal" in args.prompt_type:
executor = PythonExecutor(get_answer_expr="solution()")
else:
executor = PythonExecutor(get_answer_from_stdout=True)
samples = []
for example in tqdm(examples, total=len(examples)):
idx = example["idx"]
# parse question and answer
example["question"] = parse_question(example, data_name)
if example["question"] == "":
continue
gt_cot, gt_ans = parse_ground_truth(example, data_name)
example["gt_ans"] = gt_ans
full_prompt = construct_prompt(example, data_name, args)
if idx == args.start:
print(full_prompt)
sample = {
"idx": idx,
"question": example["question"],
"gt_cot": gt_cot,
"gt": gt_ans,
"prompt": full_prompt,
}
# add remain fields
for key in [
"level",
"type",
"unit",
"solution_type",
"choices",
"solution",
"ques_type",
"ans_type",
"answer_type",
"dataset",
"subfield",
"filed",
"theorem",
"answer",
]:
if key in example:
sample[key] = example[key]
samples.append(sample)
# repeat n times
input_prompts = [
sample["prompt"] for sample in samples for _ in range(args.n_sampling)
]
if args.apply_chat_template:
input_prompts = [
tokenizer.apply_chat_template(
[{"role": "user", "content": prompt.strip()}],
tokenize=False,
add_generation_prompt=True,
)
for prompt in input_prompts
]
remain_prompts = input_prompts
remain_prompts = [(i, prompt) for i, prompt in enumerate(remain_prompts)]
end_prompts = []
max_func_call = 1 if args.prompt_type in ["cot", "pal"] else 4
stop_words = ["</s>", "<|im_end|>", "<|endoftext|>"]
if args.prompt_type in ["cot"]:
stop_words.append("\n\nQuestion:")
if args.prompt_type in ["pal", "tool-integrated", "jiuzhang_tora"]:
stop_words.extend(["\n\n---", "```output"])
elif args.prompt_type in ["wizard_zs", "platypus_fs"]:
stop_words.extend(["Instruction", "Response"])
elif "jiuzhang" in args.prompt_type:
stop_words.append("\n\n## Question")
elif "numina" in args.prompt_type:
stop_words.append("\n### Problem")
elif "pure" in args.prompt_type:
stop_words.append("\n\n\n")
# start inference
# measure time use
start_time = time.time()
for epoch in range(max_func_call):
print("-" * 20, "Epoch", epoch)
current_prompts = remain_prompts
if len(current_prompts) == 0:
break
# get all outputs
prompts = [item[1] for item in current_prompts]
if args.use_vllm:
outputs = llm.generate(
prompts,
SamplingParams(
temperature=args.temperature,
top_p=args.top_p,
max_tokens=args.max_tokens_per_call,
n=1,
stop=stop_words,
stop_token_ids=(
[151645, 151643]
if "qwen2" in args.model_name_or_path.lower()
else None
),
),
)
outputs = sorted(
outputs, key=lambda x: int(x.request_id)
) # sort outputs by request_id
outputs = [output.outputs[0].text for output in outputs]
else:
outputs = generate_completions(
model=llm,
tokenizer=tokenizer,
prompts=prompts,
max_new_tokens=args.max_tokens_per_call,
batch_size=16,
stop_id_sequences=stop_words,
)
assert len(outputs) == len(current_prompts)
# process all outputs
remain_prompts = []
remain_codes = []
for (i, query), output in zip(current_prompts, outputs):
output = output.rstrip()
query += output
if args.prompt_type == "pal":
remain_prompts.append((i, query))
if "```python" in output:
output = extract_program(query)
remain_codes.append(output)
elif args.prompt_type == "cot":
end_prompts.append((i, query))
elif "boxed" not in output and output.endswith("```"):
program = extract_program(query)
remain_prompts.append((i, query))
remain_codes.append(program)
else:
end_prompts.append((i, query))
# execute the remain prompts
remain_results = executor.batch_apply(remain_codes)
for k in range(len(remain_prompts)):
i, query = remain_prompts[k]
res, report = remain_results[k]
exec_result = res if res else report
if "pal" in args.prompt_type:
exec_result = "\\boxed{" + exec_result + "}"
exec_result = f"\n```output\n{exec_result}\n```\n"
query += exec_result
# not end
if epoch == max_func_call - 1:
query += "\nReach max function call limit."
remain_prompts[k] = (i, query)
# unsolved samples
print("Unsolved samples:", len(remain_prompts))
end_prompts.extend(remain_prompts)
# sort by idx
end_prompts = sorted(end_prompts, key=lambda x: x[0])
# remove input_prompt from end_prompt
codes = []
assert len(input_prompts) == len(end_prompts)
for i in range(len(input_prompts)):
_, end_prompt = end_prompts[i]
code = end_prompt.split(input_prompts[i])[-1].strip()
for stop_word in stop_words:
if stop_word in code:
code = code.split(stop_word)[0].strip()
codes.append(code)
# extract preds
results = [
run_execute(executor, code, args.prompt_type, data_name) for code in codes
]
time_use = time.time() - start_time
# put results back to examples
all_samples = []
for i, sample in enumerate(samples):
code = codes[i * args.n_sampling : (i + 1) * args.n_sampling]
result = results[i * args.n_sampling : (i + 1) * args.n_sampling]
preds = [item[0] for item in result]
reports = [item[1] for item in result]
for j in range(len(preds)):
if sample["gt"] in ["A", "B", "C", "D", "E"] and preds[j] not in [
"A",
"B",
"C",
"D",
"E",
]:
preds[j] = choice_answer_clean(code[j])
elif is_multi_choice(sample["gt"]) and not is_multi_choice(preds[j]):
# remove any non-choice char
preds[j] = "".join(
[c for c in preds[j] if c in ["A", "B", "C", "D", "E"]]
)
sample.pop("prompt")
sample.update({"code": code, "pred": preds, "report": reports})
all_samples.append(sample)
# add processed samples
all_samples.extend(processed_samples)
all_samples, result_json = evaluate(
samples=all_samples,
data_name=data_name,
prompt_type=args.prompt_type,
execute=True,
)
# save outputs
if len(processed_samples) < len(all_samples) and args.save_outputs:
save_jsonl(all_samples, out_file)
result_json["time_use_in_second"] = time_use
result_json["time_use_in_minite"] = (
f"{int(time_use // 60)}:{int(time_use % 60):02d}"
)
with open(
out_file.replace(".jsonl", f"_{args.prompt_type}_metrics.json"), "w"
) as f:
json.dump(result_json, f, indent=4)
return result_json
if __name__ == "__main__":
args = parse_args()
set_seed(args.seed)
setup(args)
+267
View File
@@ -0,0 +1,267 @@
import re
import time
import os
import json
import random
import string
from enum import Enum, auto
from tqdm import tqdm
from collections import OrderedDict
import dataclasses
import pandas as pd
import timeout_decorator
import mpmath
import sympy as sp
from sympy.parsing.latex import parse_latex
import sympy as sp
from sympy import simplify
from sympy.printing import latex
from sympy.core.relational import Relational
from sympy.solvers.solveset import solvify
from sympy.solvers.inequalities import reduce_inequalities
from sympy.parsing.sympy_parser import (
parse_expr,
standard_transformations,
implicit_multiplication,
)
def compare_numerical_ans(ans_p, ans_l):
if ans_p is None:
return False
ans_p = ans_p.replace(",", "").replace("$", "")
ans_l = ans_l.replace(",", "").replace("$", "")
try:
if ans_p.endswith("%"):
ans_p = float(ans_p.rstrip("%")) / 100
if isinstance(ans_p, str):
ans_p = float(ans_p)
if isinstance(ans_l, str):
ans_l = float(ans_l)
except Exception as e:
return False
return abs(ans_p - float(ans_l)) < 1e-3
def my_parse_latex(expr_str):
expr_str = expr_str.replace("dfrac", "frac")
expr = parse_latex(expr_str)
if "\\pi" in expr_str:
expr = expr.subs({sp.Symbol("pi"): sp.pi})
expr = expr.subs({sp.Symbol("i"): sp.I})
return expr
def is_number(element: str) -> bool:
try:
float(element.replace(" ", ""))
return True
except ValueError:
return False
def percentage_to_fraction(text):
pattern = r"(\d+(\.\d+)?%)"
matches = re.findall(pattern, text)
for match in matches:
percentage_str = match[0]
percentage = float(percentage_str.strip("%")) / 100
fraction = str(percentage)
text = text.replace(percentage_str, fraction)
return text
def clean_expr_str(expr_str):
expr_str = (
expr_str.replace(" . ", ".")
.replace(". ", ".")
.replace("**", "^")
.replace("\\pm", "")
.replace("*", "\\times ")
.replace("\\\\", "\\")
.replace("\\ne ", "\\neq ")
.replace("!=", "\\neq")
.replace(">=", "\\ge")
.replace("<=", "\\le")
.replace("", "\\neq")
.replace("dfrac", "frac")
.replace("tfrac", "frac")
.replace("\\$", "")
.replace("$", "")
.replace("\\%", "")
.replace("%", "")
.replace("\\!", "")
.replace("^\circ", "\\times \\pi / 180")
.replace("//", "/")
.replace('"', "")
# .replace(",", "") # TODO
)
# expr_str = re.sub(r"\^\s(.*)", r"\^\s{\1}", expr_str)
expr_str = re.sub(r"\\+", r"\\", expr_str)
expr_str = re.sub(r"\^\s?\((.*?)\)", r"^{\1}", expr_str)
expr_str = re.sub(r"\\frac\s?(\d)\s?(\d+)", r"\\frac{\1}{\2}", expr_str)
expr_str = re.sub(r"\\log_\s?(\d)\s?(\d+)", r"\\log_{\1}{\2}", expr_str)
expr_str = re.sub(r"\\frac\s?{(.*?)}\s?(\d)", r"\\frac{\1}{\2}", expr_str)
expr_str = re.sub(r"\\frac\s?(\d)\s?{(.*?)}", r"\\frac{\1}{\2}", expr_str)
expr_str = re.sub(r"\\sqrt\s?(\d)", r"\\sqrt{\1}", expr_str)
expr_str = re.sub(r"sqrt\s?\((\d+)\)", r"\\sqrt{\1}", expr_str)
expr_str = re.sub(r"sqrt\s?\((.*?)\)", r"\\sqrt{\1}", expr_str)
expr_str = expr_str.replace(" sqrt", "\\sqrt")
expr_str = (
expr_str.replace("\\left", "").replace("\\right.", "").replace("\\right", "")
)
return expr_str
def parse_latex_answer(sample):
if isinstance(sample, int) or isinstance(sample, float):
sample = str(sample)
# return sample
sample = clean_expr_str(sample)
try:
expr = my_parse_latex(sample)
except:
print("[parse failed]", sample)
return None
return expr
def my_equals(ans_p, ans_l):
return ans_p.equals(ans_l)
def is_expr_equal(ans_p, ans_l, is_strict=False):
def is_equ_num_equal(equation, number):
if (
isinstance(equation, sp.Eq)
# and isinstance(equation.lhs, sp.Symbol)
and equation.rhs.is_number
and number.is_number
):
try:
ret = my_equals(equation.rhs, number)
return bool(ret)
except:
return equation.rhs == number
if ans_p is None or ans_l is None:
return False
if isinstance(ans_l, str):
return ans_p == ans_l
if (
not is_strict
and is_equ_num_equal(ans_l, ans_p)
or is_equ_num_equal(ans_p, ans_l)
):
return True
if ans_p.free_symbols != ans_l.free_symbols:
return False
if ans_p == ans_l:
return True
if isinstance(ans_l, sp.core.relational.Relational):
try:
if (
type(ans_l) == type(ans_p)
and my_equals(ans_p.lhs, ans_l.lhs)
and my_equals(ans_p.rhs, ans_l.rhs)
):
return True
except Exception as e:
print(ans_p, ans_l, e)
try:
ret = my_equals(ans_p, ans_l)
return bool(ret)
except:
return False
# @timeout_decorator.timeout(5)
# def compare_ans(ans_p_str, ans_l_str, is_strict=False):
# ans_p_str = clean_expr_str(ans_p_str)
# ans_p_str = ans_p_str.replace(",", "").replace("$", "")
# ans_l_str = clean_expr_str(ans_l_str)
# ans_l_str = ans_l_str.replace(",", "").replace("$", "")
# if ans_p_str is None:
# return False
# if ans_p_str.replace(" ", "") == ans_l_str.replace(" ", ""):
# return True
# ans_p = parse_latex_answer(ans_p_str)
# if ans_p is None:
# return False
# ans_l = parse_latex_answer(ans_l_str)
# if ans_l is None:
# return False
# return is_expr_equal(ans_p, ans_l, is_strict=is_strict)
def extract_answer_number(sentence: str) -> float:
sentence = sentence.replace(",", "")
pred = [s for s in re.findall(r"-?\d+\.?\d*", sentence)]
if not pred:
return ""
return pred[-1]
@timeout_decorator.timeout(5)
def compare_ans(ans_p_str, ans_l_str, is_strict=False):
ans_p_str = clean_expr_str(ans_p_str)
ans_p_str = ans_p_str.replace(",", "").replace("$", "")
ans_l_str = clean_expr_str(ans_l_str)
ans_l_str = ans_l_str.replace(",", "").replace("$", "")
if ans_p_str is None:
return False
if ans_p_str.replace(" ", "") == ans_l_str.replace(" ", ""):
return True
ans_p = parse_latex_answer(ans_p_str)
if ans_p is None:
return False
ans_l = parse_latex_answer(ans_l_str)
if ans_l is None:
return False
if is_expr_equal(ans_p, ans_l, is_strict=is_strict):
return True
# TODO not suitable
ans_p_str = extract_answer_number(ans_p_str)
if is_number(ans_p_str):
ans_p = parse_latex_answer(ans_p_str)
if is_expr_equal(ans_p, ans_l, is_strict=is_strict):
return True
return False
def vote(answers):
counter = Counter(answers)
return counter.most_common(1)[0][0]
def contains_number(s):
return any(i.isdigit() for i in s)
def rough_compare_ans(generation, answer):
for line in generation.split("\n")[::-1]:
if contains_number(line):
break
words = line.split()
for i, w in enumerate(words):
if i > 0 and words[i - 1] in ["+", "-", "*", "/", "^"]:
continue
if i < len(words) - 1 and words[i + 1] in ["+", "-", "*", "/", "^"]:
continue
if not contains_number(w):
continue
if compare_numerical_ans(w.replace("$", ""), answer) and "=" not in " ".join(
w[i:]
):
return 1
return 0
if __name__ == "__main__":
pred = "To find the minimum value of $f(x)$, we need to find the critical points where the derivative of $f(x)$ is either zero or undefined. Let's write a function to solve this problem:\n\n```python\nfrom sympy import symbols, diff, solve\n\ndef find_minimum_value():\n x, p = symbols('x p')\n f = abs(x - p) + abs(x - 15) + abs(x - p - 15)\n f_prime = diff(f, x)\n critical_points = solve(f_prime, x)\n \n # Evaluate f(x) at the critical points and endpoints\n values = [f.subs(x, p), f.subs(x, 15)]\n for point in critical_points:\n values.append(f.subs(x, point))\n \n # Find the minimum value\n minimum_value = min(values)\n \n return minimum_value\n\nresult = find_minimum_value()\nprint(result)\n```\n```output\nValueError(\"\\nCan't calculate derivative wrt re(x) + I*im(x).\")\n```\nIt seems that there is an error in the code. Let's modify the code to handle the case when the derivative is undefined.\n\n```python\nfrom sympy import symbols, diff, solve, oo\n\ndef find_minimum_value():\n x, p = symbols('x p')\n f = abs(x - p) + abs(x - 15) + abs(x - p - 15)\n f_prime = diff(f, x)\n critical_points = solve(f_prime, x)\n \n # Evaluate f(x) at the critical points and endpoints\n values = [f.subs(x, p), f.subs(x, 15)]\n for point in critical_points:\n if point.is_real:\n values.append(f.subs(x, point))\n \n # Find the minimum value\n minimum_value = min(values)\n \n return minimum_value\n\nresult = find_minimum_value()\nprint(result)\n```\n```output\nValueError(\"\\nCan't calculate derivative wrt re(x) + I*im(x).\")\n```\nApologies for the error in the code. It seems that the derivative calculation is causing an issue. Let's modify the code to handle the absolute value function and find the minimum value using a different approach.\n\n```python\nfrom sympy import symbols, Piecewise, Min, oo\n\ndef find_minimum_value():\n x, p = symbols('x p')\n f = abs(x - p) + abs(x - 15) + abs(x - p - 15)\n \n # Define the piecewise function for f(x)\n f_piecewise = Piecewise((f, x <= p), (f.subs(x, p), p < x <= 15), (f.subs(x, 15), x > 15))\n \n # Find the minimum value\n minimum_value = Min(f_piecewise, (x, p, 15))\n \n return minimum_value\n\nresult = find_minimum_value()\nprint(result)\n```\n```output\nTypeError('cannot determine truth value of Relational')\n```\nApologies for the error in the code. It seems that the piecewise function is causing an issue. Let's modify the code to handle the absolute value function and find the minimum value using a different approach.\n\n```python\nfrom sympy import symbols, Min, oo\n\ndef find_minimum_value():\n x, p = symbols('x p')\n f = abs(x - p) + abs(x - 15) + abs(x - p - 15)\n \n # Find the minimum value\n minimum_value = Min(f, (x, p, 15))\n \n return minimum_value\n\nresult = find_minimum_value()\nprint(result)\n```\n"
label = "15"
print(compare_ans(pred, label))
+235
View File
@@ -0,0 +1,235 @@
"""
https://github.com/allenai/open-instruct
"""
import torch
import tqdm
from transformers import StoppingCriteria, StoppingCriteriaList
class KeywordsStoppingCriteria(StoppingCriteria):
def __init__(self, keywords_str, tokenizer):
StoppingCriteria.__init__(self)
self.current_context = []
self.tokenizer = tokenizer
self.keywords_str = keywords_str
def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
if len(self.current_context) == 0:
self.current_context = [[] for _ in range(input_ids.shape[0])]
# self.current_context.append(input_ids[0][-1].item())
sequences_should_be_stopped = []
for i in range(input_ids.shape[0]):
_id = input_ids[i][-1].item()
self.current_context[i].append(_id)
current_context = self.tokenizer.decode(self.current_context[i])
should_be_stopped = False
for word in self.keywords_str:
if word in current_context:
should_be_stopped = True
break
sequences_should_be_stopped.append(should_be_stopped)
return all(sequences_should_be_stopped)
class KeyWordsCriteriaTrunc(StoppingCriteria):
def __init__(self, stop_id_sequences, prompt_length):
assert isinstance(stop_id_sequences[0], list), "stop_id_sequences should be a list of list of ids"
self.stop_sequences = stop_id_sequences
self.prompt_length = prompt_length
def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
sequences_should_be_stopped = []
for i in range(input_ids.shape[0]):
ids = input_ids[i][self.prompt_length:].tolist()
should_be_stopped = False
for stop_sequence in self.stop_sequences:
if input_ids.shape[0] == 1:
_ids = ids[-len(stop_sequence):]
else:
_ids = ids
for j in range(len(_ids), 0, -len(stop_sequence)):
if _ids[max(j - len(stop_sequence), 0): j] == stop_sequence:
should_be_stopped = True
break
if should_be_stopped:
break
sequences_should_be_stopped.append(should_be_stopped)
return all(sequences_should_be_stopped)
class KeyWordsCriteria(StoppingCriteria):
def __init__(self, stop_id_sequences):
assert isinstance(stop_id_sequences[0], list), "stop_id_sequences should be a list of list of ids"
self.stop_sequences = stop_id_sequences
def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
sequences_should_be_stopped = []
for i in range(input_ids.shape[0]):
sequence_should_be_stopped = False
for stop_sequence in self.stop_sequences:
if input_ids[i][-len(stop_sequence):].tolist() == stop_sequence:
sequence_should_be_stopped = True
break
sequences_should_be_stopped.append(sequence_should_be_stopped)
return all(sequences_should_be_stopped)
@torch.no_grad()
def generate_completions(model, tokenizer, prompts, batch_size=1, stop_id_sequences=None, add_special_tokens=True, disable_tqdm=False, **generation_kwargs):
generations = []
if not disable_tqdm:
progress = tqdm.tqdm(total=len(prompts), desc="Generating Completions")
num_return_sequences = generation_kwargs.get("num_return_sequences", 1)
for i in range(0, len(prompts), batch_size):
batch_prompts = prompts[i:i+batch_size]
tokenized_prompts = tokenizer(batch_prompts, padding="longest", return_tensors="pt", add_special_tokens=add_special_tokens)
batch_input_ids = tokenized_prompts.input_ids
attention_mask = tokenized_prompts.attention_mask
if model.device.type == "cuda":
batch_input_ids = batch_input_ids.cuda()
attention_mask = attention_mask.cuda()
# try:
stop_criteria = KeywordsStoppingCriteria(stop_id_sequences, tokenizer)
batch_outputs = model.generate(
input_ids=batch_input_ids,
attention_mask=attention_mask,
stopping_criteria=StoppingCriteriaList([stop_criteria]),
# stopping_criteria=[KeyWordsCriteria(stop_id_sequences)] if stop_id_sequences else None,
# stopping_criteria=[KeyWordsCriteriaTrunc(stop_id_sequences, batch_input_ids.size(1))] if stop_id_sequences else None,
**generation_kwargs
)
# the stopping criteria is applied at batch level, so if other examples are not stopped, the entire batch will continue to generate.
# so some outputs still have the stop sequence, which we need to remove.
# if stop_id_sequences:
# for output_idx in range(batch_outputs.shape[0]):
# for token_idx in range(batch_input_ids.shape[1], batch_outputs.shape[1]):
# if any(batch_outputs[output_idx, token_idx: token_idx+len(stop_sequence)].tolist() == stop_sequence for stop_sequence in stop_id_sequences):
# batch_outputs[output_idx, token_idx:] = tokenizer.pad_token_id
# break
# remove the prompt from the output
# we need to re-encode the prompt because we need to make sure the special tokens are treated the same way as in the outputs.
# we changed our previous way of truncating the output token ids dicrectly because some tokenizer (e.g., llama) won't add space token before the first token.
# space is important for some tasks (e.g., code completion).
batch_outputs = tokenizer.batch_decode(batch_outputs, skip_special_tokens=True)
batch_prompts = tokenizer.batch_decode(batch_input_ids, skip_special_tokens=True)
# duplicate the prompts to match the number of return sequences
batch_prompts = [prompt for prompt in batch_prompts for _ in range(num_return_sequences)]
batch_generations = [
output[len(prompt):] for prompt, output in zip(batch_prompts, batch_outputs)
]
# remove the remain stop sequence from the output.
for idx, prediction in enumerate(batch_generations):
for stop_sequence in stop_id_sequences:
batch_generations[idx] = prediction.split(stop_sequence)[0]
generations += batch_generations
if not disable_tqdm:
progress.update(len(batch_prompts)//num_return_sequences)
assert len(generations) == len(prompts) * num_return_sequences, "number of generations should be equal to number of prompts * num_return_sequences"
return generations
def load_hf_lm_and_tokenizer(
model_name_or_path,
tokenizer_name_or_path=None,
device_map="auto",
load_in_8bit=False,
load_in_half=True,
gptq_model=False,
use_fast_tokenizer=False,
padding_side="left",
use_safetensors=False,
):
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
if not tokenizer_name_or_path:
tokenizer_name_or_path = model_name_or_path
tokenizer = AutoTokenizer.from_pretrained(tokenizer_name_or_path, use_fast=use_fast_tokenizer, padding_side=padding_side, trust_remote_code=True)
# tokenizer = AutoTokenizer.from_pretrained(tokenizer_name_or_path, legacy=False, use_fast=use_fast_tokenizer, padding_side=padding_side, trust_remote_code=True)
# set pad token to eos token if pad token is not set
if tokenizer.pad_token is None:
if tokenizer.unk_token:
tokenizer.pad_token = tokenizer.unk_token
tokenizer.pad_token_id = tokenizer.unk_token_id
elif tokenizer.eos_token:
tokenizer.pad_token = tokenizer.eos_token
tokenizer.pad_token_id = tokenizer.eos_token_id
else:
raise ValueError("You are using a new tokenizer without a pad token."
"This is not supported by this script.")
# if tokenizer.pad_token is None:
# tokenizer.pad_token = tokenizer.unk_token
# tokenizer.pad_token_id = tokenizer.unk_token_id
if gptq_model:
from auto_gptq import AutoGPTQForCausalLM
model_wrapper = AutoGPTQForCausalLM.from_quantized(
model_name_or_path, device="cuda:0", use_triton=True
)
model = model_wrapper.model
elif load_in_8bit:
model = AutoModelForCausalLM.from_pretrained(
model_name_or_path,
device_map=device_map,
load_in_8bit=True
)
else:
# return "", tokenizer
# defaul load in float16
model = AutoModelForCausalLM.from_pretrained(model_name_or_path,
torch_dtype=torch.float16,
device_map=device_map,
trust_remote_code=True,
use_safetensors=use_safetensors)
if torch.cuda.is_available():
model = model.cuda()
if load_in_half:
model = model.half()
model.eval()
return model, tokenizer
def _test_generate_completions():
model_name_or_path = "../models/codellama_7b/v1-16k"
llm, tokenizer = load_hf_lm_and_tokenizer(
model_name_or_path=model_name_or_path,
load_in_half=True,
use_fast_tokenizer=True,
use_safetensors=True,
)
# some math word problems
prompts = [
"---\n1+1=2\n---2+2=4\n---3+3=6\n---4+4=8\n---5+5=10\n---6+6=",
"---\n1+1=2\n---12+12=24\n---3+3=6\n---12345+12345=",
# "A train leaves Chicago at 7am and travels at 60mph. Another train leaves Chicago at 9am and travels at 80mph. When will the second train overtake the first?",
# "The sum of two numbers is 10. The difference of the same two numbers is 4. What are the two numbers?",
]
stop_sequences = ["\n\n\n", "---"]
# Because many tokenizers will treat the word after space differently from the original word alone,
# to be consistent, we add a space before tokenization and remove it after tokenization.
# stop_id_sequences = [tokenizer.encode(" " + x, add_special_tokens=False)[1:] for x in stop_sequences]
outputs = generate_completions(
model=llm,
tokenizer=tokenizer,
prompts=prompts,
max_new_tokens=128,
batch_size=16,
# stop_id_sequences=stop_id_sequences,
stop_id_sequences=stop_sequences,
)
print(outputs)
if __name__ == "__main__":
_test_generate_completions()
+760
View File
@@ -0,0 +1,760 @@
import random
import regex
import re
import sympy
from latex2sympy2 import latex2sympy
from typing import TypeVar, Iterable, List, Union, Any, Dict
from word2number import w2n
from data.qwen25math.utils import *
def _fix_fracs(string):
substrs = string.split("\\frac")
new_str = substrs[0]
if len(substrs) > 1:
substrs = substrs[1:]
for substr in substrs:
new_str += "\\frac"
if len(substr) > 0 and substr[0] == "{":
new_str += substr
else:
try:
assert len(substr) >= 2
except:
return string
a = substr[0]
b = substr[1]
if b != "{":
if len(substr) > 2:
post_substr = substr[2:]
new_str += "{" + a + "}{" + b + "}" + post_substr
else:
new_str += "{" + a + "}{" + b + "}"
else:
if len(substr) > 2:
post_substr = substr[2:]
new_str += "{" + a + "}" + b + post_substr
else:
new_str += "{" + a + "}" + b
string = new_str
return string
def _fix_a_slash_b(string):
if len(string.split("/")) != 2:
return string
a = string.split("/")[0]
b = string.split("/")[1]
try:
if "sqrt" not in a:
a = int(a)
if "sqrt" not in b:
b = int(b)
assert string == "{}/{}".format(a, b)
new_string = "\\frac{" + str(a) + "}{" + str(b) + "}"
return new_string
except:
return string
def _fix_sqrt(string):
_string = re.sub(r"\\sqrt(\w+)", r"\\sqrt{\1}", string)
return _string
def convert_word_number(text: str) -> str:
try:
text = str(w2n.word_to_num(text))
except:
pass
return text
# units mainly from MathQA
unit_texts = [
"east",
"degree",
"mph",
"kmph",
"ft",
"m sqaure",
" m east",
"sq m",
"deg",
"mile",
"q .",
"monkey",
"prime",
"ratio",
"profit of rs",
"rd",
"o",
"gm",
"p . m",
"lb",
"tile",
"per",
"dm",
"lt",
"gain",
"ab",
"way",
"west",
"a .",
"b .",
"c .",
"d .",
"e .",
"f .",
"g .",
"h .",
"t",
"a",
"h",
"no change",
"men",
"soldier",
"pie",
"bc",
"excess",
"st",
"inches",
"noon",
"percent",
"by",
"gal",
"kmh",
"c",
"acre",
"rise",
"a . m",
"th",
"π r 2",
"sq",
"mark",
"l",
"toy",
"coin",
"sq . m",
"gallon",
"° f",
"profit",
"minw",
"yr",
"women",
"feet",
"am",
"pm",
"hr",
"cu cm",
"square",
"v â € ™",
"are",
"rupee",
"rounds",
"cubic",
"cc",
"mtr",
"s",
"ohm",
"number",
"kmph",
"day",
"hour",
"minute",
"min",
"second",
"man",
"woman",
"sec",
"cube",
"mt",
"sq inch",
"mp",
"∏ cm ³",
"hectare",
"more",
"sec",
"unit",
"cu . m",
"cm 2",
"rs .",
"rs",
"kg",
"g",
"month",
"km",
"m",
"cm",
"mm",
"apple",
"liter",
"loss",
"yard",
"pure",
"year",
"increase",
"decrease",
"d",
"less",
"Surface",
"litre",
"pi sq m",
"s .",
"metre",
"meter",
"inch",
]
unit_texts.extend([t + "s" for t in unit_texts])
def strip_string(string, skip_unit=False):
string = str(string).strip()
# linebreaks
string = string.replace("\n", "")
# right "."
string = string.rstrip(".")
# remove inverse spaces
# replace \\ with \
string = string.replace("\\!", "")
# string = string.replace("\\ ", "")
# string = string.replace("\\\\", "\\")
# matrix
string = re.sub(r"\\begin\{array\}\{.*?\}", r"\\begin{pmatrix}", string)
string = re.sub(r"\\end\{array\}", r"\\end{pmatrix}", string)
string = string.replace("bmatrix", "pmatrix")
# replace tfrac and dfrac with frac
string = string.replace("tfrac", "frac")
string = string.replace("dfrac", "frac")
string = (
string.replace("\\neq", "\\ne")
.replace("\\leq", "\\le")
.replace("\\geq", "\\ge")
)
# remove \left and \right
string = string.replace("\\left", "")
string = string.replace("\\right", "")
string = string.replace("\\{", "{")
string = string.replace("\\}", "}")
# Remove unit: miles, dollars if after is not none
_string = re.sub(r"\\text{.*?}$", "", string).strip()
if _string != "" and _string != string:
# print("Warning: unit not removed: '{}' -> '{}'".format(string, _string))
string = _string
if not skip_unit:
# Remove unit: texts
for _ in range(2):
for unit_text in unit_texts:
# use regex, the prefix should be either the start of the string or a non-alphanumeric character
# the suffix should be either the end of the string or a non-alphanumeric character
_string = re.sub(r"(^|\W)" + unit_text + r"($|\W)", r"\1\2", string)
if _string != "":
string = _string
# Remove circ (degrees)
string = string.replace("^{\\circ}", "")
string = string.replace("^\\circ", "")
# remove dollar signs
string = string.replace("\\$", "")
string = string.replace("$", "")
string = string.replace("\\(", "").replace("\\)", "")
# convert word number to digit
string = convert_word_number(string)
# replace "\\text{...}" to "..."
string = re.sub(r"\\text\{(.*?)\}", r"\1", string)
for key in ["x=", "y=", "z=", "x\\in", "y\\in", "z\\in", "x\\to", "y\\to", "z\\to"]:
string = string.replace(key, "")
string = string.replace("\\emptyset", r"{}")
string = string.replace("(-\\infty,\\infty)", "\\mathbb{R}")
# remove percentage
string = string.replace("\\%", "")
string = string.replace("\%", "")
string = string.replace("%", "")
# " 0." equivalent to " ." and "{0." equivalent to "{." Alternatively, add "0" if "." is the start of the string
string = string.replace(" .", " 0.")
string = string.replace("{.", "{0.")
# cdot
# string = string.replace("\\cdot", "")
if (
string.startswith("{")
and string.endswith("}")
and string.isalnum()
or string.startswith("(")
and string.endswith(")")
and string.isalnum()
or string.startswith("[")
and string.endswith("]")
and string.isalnum()
):
string = string[1:-1]
# inf
string = string.replace("infinity", "\\infty")
if "\\infty" not in string:
string = string.replace("inf", "\\infty")
string = string.replace("+\\inity", "\\infty")
# and
string = string.replace("and", "")
string = string.replace("\\mathbf", "")
# use regex to remove \mbox{...}
string = re.sub(r"\\mbox{.*?}", "", string)
# quote
string.replace("'", "")
string.replace('"', "")
# i, j
if "j" in string and "i" not in string:
string = string.replace("j", "i")
# replace a.000b where b is not number or b is end, with ab, use regex
string = re.sub(r"(\d+)\.0*([^\d])", r"\1\2", string)
string = re.sub(r"(\d+)\.0*$", r"\1", string)
# if empty, return empty string
if len(string) == 0:
return string
if string[0] == ".":
string = "0" + string
# to consider: get rid of e.g. "k = " or "q = " at beginning
if len(string.split("=")) == 2:
if len(string.split("=")[0]) <= 2:
string = string.split("=")[1]
string = _fix_sqrt(string)
string = string.replace(" ", "")
# \frac1b or \frac12 --> \frac{1}{b} and \frac{1}{2}, etc. Even works with \frac1{72} (but not \frac{72}1). Also does a/b --> \\frac{a}{b}
string = _fix_fracs(string)
# NOTE: X/Y changed to \frac{X}{Y} in dataset, but in simple cases fix in case the model output is X/Y
string = _fix_a_slash_b(string)
return string
def extract_multi_choice_answer(pred_str):
# TODO: SFT models
if "Problem:" in pred_str:
pred_str = pred_str.split("Problem:", 1)[0]
pred_str = pred_str.replace("choice is", "answer is")
patt = regex.search(r"answer is \(?(?P<ans>[abcde])\)?", pred_str.lower())
if patt is not None:
return patt.group("ans").upper()
return "placeholder"
direct_answer_trigger_for_fewshot = ("choice is", "answer is")
def choice_answer_clean(pred: str):
pred = pred.strip("\n")
# Determine if this is ICL, if so, use \n\n to split the first chunk.
ICL = False
for trigger in direct_answer_trigger_for_fewshot:
if pred.count(trigger) > 1:
ICL = True
if ICL:
pred = pred.split("\n\n")[0]
# Split the trigger to find the answer.
preds = re.split("|".join(direct_answer_trigger_for_fewshot), pred)
if len(preds) > 1:
answer_flag = True
pred = preds[-1]
else:
answer_flag = False
pred = pred.strip("\n").rstrip(".").rstrip("/").strip(" ").lstrip(":")
# Clean the answer based on the dataset
tmp = re.findall(r"\b(A|B|C|D|E)\b", pred.upper())
if tmp:
pred = tmp
else:
pred = [pred.strip().strip(".")]
if len(pred) == 0:
pred = ""
else:
if answer_flag:
# choose the first element in list ...
pred = pred[0]
else:
# choose the last e
pred = pred[-1]
# Remove the period at the end, again!
pred = pred.rstrip(".").rstrip("/")
return pred
def find_box(pred_str: str):
ans = pred_str.split("boxed")[-1]
if not ans:
return ""
if ans[0] == "{":
stack = 1
a = ""
for c in ans[1:]:
if c == "{":
stack += 1
a += c
elif c == "}":
stack -= 1
if stack == 0:
break
a += c
else:
a += c
else:
a = ans.split("$")[0].strip()
return a
def clean_units(pred_str: str):
"""Clean the units in the number."""
def convert_pi_to_number(code_string):
code_string = code_string.replace("\\pi", "π")
# Replace \pi or π not preceded by a digit or } with 3.14
code_string = re.sub(r"(?<![\d}])\\", "3.14", code_string)
# Replace instances where π is preceded by a digit but without a multiplication symbol, e.g., "3π" -> "3*3.14"
code_string = re.sub(r"(\d)(\\?π)", r"\1*3.14", code_string)
# Handle cases where π is within braces or followed by a multiplication symbol
# This replaces "{π}" with "3.14" directly and "3*π" with "3*3.14"
code_string = re.sub(r"\{(\\?π)\}", "3.14", code_string)
code_string = re.sub(r"\*(\\?π)", "*3.14", code_string)
return code_string
pred_str = convert_pi_to_number(pred_str)
pred_str = pred_str.replace("%", "/100")
pred_str = pred_str.replace("$", "")
pred_str = pred_str.replace("¥", "")
pred_str = pred_str.replace("°C", "")
pred_str = pred_str.replace(" C", "")
pred_str = pred_str.replace("°", "")
return pred_str
def extract_theoremqa_answer(pred: str, answer_flag: bool = True):
if any([option in pred.lower() for option in ["yes", "true"]]):
pred = "True"
elif any([option in pred.lower() for option in ["no", "false"]]):
pred = "False"
elif any(
[
option in pred.lower()
for option in ["(a)", "(b)", "(c)", "(d)", "(e)", "(f)"]
]
):
pass
else:
# Some of the models somehow get used to boxed output from pre-training
if "boxed" in pred:
pred = find_box(pred)
if answer_flag:
# Extract the numbers out of the string
pred = pred.split("=")[-1].strip()
pred = clean_units(pred)
try:
tmp = str(latex2sympy(pred))
pred = str(eval(tmp))
except Exception:
if re.match(r"-?[\d\.]+\s\D+$", pred):
pred = pred.split(" ")[0]
elif re.match(r"-?[\d\.]+\s[^\s]+$", pred):
pred = pred.split(" ")[0]
else:
# desparate search over the last number
preds = re.findall(r"-?\d*\.?\d+", pred)
if len(preds) >= 1:
pred = preds[-1]
else:
pred = ""
return pred
def extract_answer(pred_str, data_name, use_last_number=True):
pred_str = pred_str.replace("\u043a\u0438", "")
if data_name in ["mmlu_stem", "sat_math", "aqua", "gaokao2023"]:
# TODO check multiple choice
return choice_answer_clean(pred_str)
if "final answer is $" in pred_str and "$. I hope" in pred_str:
# minerva_math
tmp = pred_str.split("final answer is $", 1)[1]
pred = tmp.split("$. I hope", 1)[0].strip()
elif "boxed" in pred_str:
ans = pred_str.split("boxed")[-1]
if len(ans) == 0:
return ""
elif ans[0] == "{":
stack = 1
a = ""
for c in ans[1:]:
if c == "{":
stack += 1
a += c
elif c == "}":
stack -= 1
if stack == 0:
break
a += c
else:
a += c
else:
a = ans.split("$")[0].strip()
pred = a
elif "he answer is" in pred_str:
pred = pred_str.split("he answer is")[-1].strip()
elif "final answer is" in pred_str:
pred = pred_str.split("final answer is")[-1].strip()
elif "答案是" in pred_str:
# Handle Chinese few-shot multiple choice problem answer extraction
pred = pred_str.split("答案是")[1].strip().split("\n\n")[0].strip()
else: # use the last number
if use_last_number:
pattern = "-?\d*\.?\d+"
pred = re.findall(pattern, pred_str.replace(",", ""))
if len(pred) >= 1:
pred = pred[-1]
else:
pred = ""
else:
pred = ""
# choice answer
if (
data_name in ["sat_math", "aqua"]
or "mmlu" in data_name
):
tmp = re.findall(r"\b(A|B|C|D|E)\b", pred.upper())
if tmp:
pred = tmp[-1]
else:
pred = pred.strip().strip(".")
# multiple line
# pred = pred.split("\n")[0]
pred = re.sub(r"\n\s*", "", pred)
if pred != "" and pred[0] == ":":
pred = pred[1:]
if pred != "" and pred[-1] == ".":
pred = pred[:-1]
if pred != "" and pred[-1] == "/":
pred = pred[:-1]
pred = strip_string(pred, skip_unit=data_name in ["carp_en", "minerva_math"])
return pred
STRIP_EXCEPTIONS = ["carp_en", "minerva_math"]
def parse_ground_truth(example: Dict[str, Any], data_name):
if "gt_cot" in example and "gt" in example:
if data_name in ["math"]:
gt_ans = extract_answer(example["gt_cot"], data_name)
elif data_name in STRIP_EXCEPTIONS:
gt_ans = example["gt"]
else:
gt_ans = strip_string(example["gt"])
return example["gt_cot"], gt_ans
# parse ground truth
if data_name in ["math", "minerva_math"]:
gt_cot = example["solution"]
gt_ans = extract_answer(gt_cot, data_name)
elif data_name == "gsm8k":
gt_cot, gt_ans = example["answer"].split("####")
elif data_name == "svamp":
gt_cot, gt_ans = example["Equation"], example["Answer"]
elif data_name == "asdiv":
gt_cot = example["formula"]
gt_ans = re.sub(r"\(.*?\)", "", example["answer"])
elif data_name == "mawps":
gt_cot, gt_ans = None, example["target"]
elif data_name == "tabmwp":
gt_cot = example["solution"]
gt_ans = example["answer"]
if example["ans_type"] in ["integer_number", "decimal_number"]:
if "/" in gt_ans:
gt_ans = int(gt_ans.split("/")[0]) / int(gt_ans.split("/")[1])
elif "," in gt_ans:
gt_ans = float(gt_ans.replace(",", ""))
elif "%" in gt_ans:
gt_ans = float(gt_ans.split("%")[0]) / 100
else:
gt_ans = float(gt_ans)
elif data_name == "carp_en":
gt_cot, gt_ans = example["steps"], example["answer"]
elif data_name == "mmlu_stem":
abcd = "ABCD"
gt_cot, gt_ans = None, abcd[example["answer"]]
elif data_name == "sat_math":
gt_cot, gt_ans = None, example["Answer"]
elif data_name == "aqua":
gt_cot, gt_ans = None, example["correct"]
elif data_name in ["gaokao2023en", "college_math", "gaokao_math_cloze"]:
gt_cot, gt_ans = None, example["answer"].replace("$", "").strip()
elif data_name == "gaokao_math_qa":
gt_cot, gt_ans = None, example["label"]
elif data_name in ["gaokao2024_mix", "cn_middle_school"]:
if len(example["choice_answer"]) > 0:
gt_cot, gt_ans = None, example["choice_answer"]
else:
gt_cot, gt_ans = None, example["answer"]
elif data_name == "olympiadbench":
gt_cot, gt_ans = None, example["final_answer"][0].strip("$")
elif data_name in [
"aime24",
"amc23",
"cmath",
"gaokao2024_I",
"gaokao2024_II",
"imo2024",
]:
gt_cot, gt_ans = None, example["answer"]
else:
raise NotImplementedError(f"`{data_name}`")
# post process
gt_cot = str(gt_cot).strip()
if data_name not in STRIP_EXCEPTIONS:
gt_ans = strip_string(gt_ans, skip_unit=data_name == "carp_en")
else:
gt_ans = (
gt_ans.replace("\\neq", "\\ne")
.replace("\\leq", "\\le")
.replace("\\geq", "\\ge")
)
return gt_cot, gt_ans
def parse_question(example, data_name):
question = ""
if data_name == "asdiv":
question = f"{example['body'].strip()} {example['question'].strip()}"
elif data_name == "svamp":
body = example["Body"].strip()
if not body.endswith("."):
body = body + "."
question = f'{body} {example["Question"].strip()}'
elif data_name == "tabmwp":
title_str = (
f'regarding "{example["table_title"]}" ' if example["table_title"] else ""
)
question = f"Read the following table {title_str}and answer a question:\n"
question += f'{example["table"]}\n{example["question"]}'
if example["choices"]:
question += (
f' Please select from the following options: {example["choices"]}'
)
elif data_name == "carp_en":
question = example["content"]
elif data_name == "mmlu_stem":
options = example["choices"]
assert len(options) == 4
for i, (label, option) in enumerate(zip("ABCD", options)):
options[i] = f"({label}) {str(option).strip()}"
options = " ".join(options)
# question = f"{example['question'].strip()}\nWhat of the following is the right choice? Explain your answer.\n{options}"
question = f"{example['question'].strip()}\nAnswer Choices: {options}"
elif data_name == "sat_math":
options = example["options"].strip()
assert "A" == options[0]
options = "(" + options
for ch in "BCD":
if f" {ch}) " in options:
options = regex.sub(f" {ch}\) ", f" ({ch}) ", options)
# question = f"{example['question'].strip()}\nWhat of the following is the right choice? Explain your answer.\n{options.strip()}"
question = f"{example['question'].strip()}\nAnswer Choices: {options}"
elif "aqua" in data_name:
options = example["options"]
choice = "(" + "(".join(options)
choice = choice.replace("(", " (").replace(")", ") ").strip()
choice = "\nAnswer Choices: " + choice
question = example["question"].strip() + choice
elif data_name == "gaokao_math_qa":
options_dict = example["options"]
options = []
for key in options_dict:
options.append(f"({key}) {options_dict[key]}")
options = " ".join(options)
question = f"{example['question'].strip()}\n选项: {options}"
else:
for key in ["question", "problem", "Question", "input"]:
if key in example:
question = example[key]
break
# assert question != ""
# Yes or No question
_, gt_ans = parse_ground_truth(example, data_name)
if isinstance(gt_ans, str):
gt_lower = gt_ans.lower()
if gt_lower in ["true", "false"]:
question += " (True or False)"
if gt_lower in ["yes", "no"]:
question += " (Yes or No)"
return question.strip()
def run_execute(executor, result, prompt_type, data_name, execute=False):
if not result or result == "error":
return None, None
report = None
if "program_only" in prompt_type:
prediction = extract_program_output(result)
elif prompt_type in ["pot", "pal"] and execute:
code = extract_program(result)
prediction, report = executor.apply(code)
else:
prediction = extract_answer(result, data_name)
# prediction = strip_string(prediction, skip_unit=data_name == "carp_en")
prediction = strip_string(prediction, skip_unit=data_name in STRIP_EXCEPTIONS)
return prediction, report
def _test_extract_answer():
text = """
This is still not equal to $0$, so we must have made another mistake.
When we subtracted $7$ from $\frac{386}{64}$, we should have subtracted $7 \cdot 64$ from $386$, not the other way around. Let's correct that:
\[\frac{386}{64} - 7 = \frac{386}{64} - \frac{7 \cdot 64}{1 \cdot 64} = \frac{386 - 448}{64} = \frac{-62}{64}.\]
This is still not equal to $0$, so we must have made another mistake.
When we subtracted $7$ from $\frac{386}{64}$, we should have subtracted $7 \cdot 64$ from $386$, not the other way around. Let's correct that:
\[\frac{386}{64}
"""
print(extract_answer(text, "math-oai", use_last_number=False))
print(choice_answer_clean("\mathrm{(D)\}1,008,016"))
# should output a dict
if __name__ == "__main__":
_test_extract_answer()
+230
View File
@@ -0,0 +1,230 @@
import os
import io
import regex
import pickle
import traceback
import copy
import datetime
import dateutil.relativedelta
import multiprocess
from multiprocess import Pool
from typing import Any, Dict, Optional
from pebble import ProcessPool
from tqdm import tqdm
from concurrent.futures import TimeoutError
from functools import partial
from timeout_decorator import timeout
from contextlib import redirect_stdout
class GenericRuntime:
GLOBAL_DICT = {}
LOCAL_DICT = None
HEADERS = []
def __init__(self):
self._global_vars = copy.copy(self.GLOBAL_DICT)
self._local_vars = copy.copy(self.LOCAL_DICT) if self.LOCAL_DICT else None
for c in self.HEADERS:
self.exec_code(c)
def exec_code(self, code_piece: str) -> None:
if regex.search(r'(\s|^)?input\(', code_piece):
# regex.search(r'(\s|^)?os.', code_piece):
raise RuntimeError()
exec(code_piece, self._global_vars)
# TODO: use: https://github.com/shroominic/codebox-api
# @high safe exec in sandbox
# byte_code = compile_restricted(
# code_piece,
# filename='<inline code>',
# mode='exec'
# )
# print("global vars:", self._global_vars)
# _print_ = PrintCollector
# exec(byte_code, {'__builtins__': utility_builtins}, None)
def eval_code(self, expr: str) -> Any:
return eval(expr, self._global_vars)
def inject(self, var_dict: Dict[str, Any]) -> None:
for k, v in var_dict.items():
self._global_vars[k] = v
@property
def answer(self):
return self._global_vars['answer']
class DateRuntime(GenericRuntime):
GLOBAL_DICT = {
'datetime': datetime.datetime,
'timedelta': dateutil.relativedelta.relativedelta,
'relativedelta': dateutil.relativedelta.relativedelta
}
class CustomDict(dict):
def __iter__(self):
return list(super().__iter__()).__iter__()
class ColorObjectRuntime(GenericRuntime):
GLOBAL_DICT = {'dict': CustomDict}
class PythonExecutor:
def __init__(
self,
runtime: Optional[Any] = None,
get_answer_symbol: Optional[str] = None,
get_answer_expr: Optional[str] = None,
get_answer_from_stdout: bool = False,
timeout_length: int = 5,
) -> None:
self.runtime = runtime if runtime else GenericRuntime()
self.answer_symbol = get_answer_symbol
self.answer_expr = get_answer_expr
self.get_answer_from_stdout = get_answer_from_stdout
self.pool = Pool(multiprocess.cpu_count())
self.timeout_length = timeout_length
def process_generation_to_code(self, gens: str):
return [g.strip().split('\n') for g in gens]
@staticmethod
def execute(
code,
get_answer_from_stdout = None,
runtime = None,
answer_symbol = None,
answer_expr = None,
timeout_length = 10,
auto_mode=False
):
try:
if auto_mode:
if "print(" in code[-1]:
program_io = io.StringIO()
with redirect_stdout(program_io):
timeout(timeout_length)(runtime.exec_code)('\n'.join(code))
program_io.seek(0)
result = program_io.read()
else:
print(code)
timeout(timeout_length)(runtime.exec_code)('\n'.join(code[:-1]))
result = timeout(timeout_length)(runtime.eval_code)(code[-1])
else:
if get_answer_from_stdout:
program_io = io.StringIO()
with redirect_stdout(program_io):
timeout(timeout_length)(runtime.exec_code)('\n'.join(code))
program_io.seek(0)
result = program_io.read()
elif answer_symbol:
timeout(timeout_length)(runtime.exec_code)('\n'.join(code))
result = runtime._global_vars[answer_symbol]
elif answer_expr:
timeout(timeout_length)(runtime.exec_code)('\n'.join(code))
result = timeout(timeout_length)(runtime.eval_code)(answer_expr)
else:
timeout(timeout_length)(runtime.exec_code)('\n'.join(code[:-1]))
result = timeout(timeout_length)(runtime.eval_code)(code[-1])
report = "Done"
str(result)
pickle.dumps(result) # serialization check
except:
result = ''
report = traceback.format_exc().split('\n')[-2]
return result, report
def apply(self, code):
return self.batch_apply([code])[0]
@staticmethod
def truncate(s, max_length=400):
half = max_length // 2
if len(s) > max_length:
s = s[:half] + "..." + s[-half:]
return s
def batch_apply(self, batch_code):
all_code_snippets = self.process_generation_to_code(batch_code)
timeout_cnt = 0
all_exec_results = []
# with ProcessPool(max_workers=min(len(all_code_snippets), os.cpu_count())) as pool:
with ProcessPool(max_workers=min(len(all_code_snippets), 1)) as pool:
executor = partial(
self.execute,
get_answer_from_stdout=self.get_answer_from_stdout,
runtime=self.runtime,
answer_symbol=self.answer_symbol,
answer_expr=self.answer_expr,
timeout_length=self.timeout_length, # this timeout not work
auto_mode=True
)
future = pool.map(executor, all_code_snippets, timeout=self.timeout_length)
iterator = future.result()
if len(all_code_snippets) > 100:
progress_bar = tqdm(total=len(all_code_snippets), desc="Execute")
else:
progress_bar = None
while True:
try:
result = next(iterator)
all_exec_results.append(result)
except StopIteration:
break
except TimeoutError as error:
print(error)
all_exec_results.append(("", "Timeout Error"))
timeout_cnt += 1
except Exception as error:
print(error)
exit()
if progress_bar is not None:
progress_bar.update(1)
if progress_bar is not None:
progress_bar.close()
batch_results = []
for code, (res, report) in zip(all_code_snippets, all_exec_results):
# post processing
res, report = str(res).strip(), str(report).strip()
res, report = self.truncate(res), self.truncate(report)
batch_results.append((res, report))
return batch_results
def _test():
batch_code = [
"""
from sympy import Matrix
def null_space_basis():
# Define the matrix
A = Matrix([[3, 3, -1, -6], [9, -1, -8, -1], [7, 4, -2, -9]])
# Compute the basis for the null space
basis = A.nullspace()
# Round the elements of the basis vectors to three decimal places
basis_rounded = [v.evalf(3) for v in basis]
return basis_rounded
result = null_space_basis()
print(result)
"""
]
executor = PythonExecutor(get_answer_from_stdout=True)
predictions = executor.apply(batch_code[0])
print(predictions)
if __name__ == '__main__':
_test()
+15
View File
@@ -0,0 +1,15 @@
# common
vllm
tqdm
datasets
torch
transformers
python_dateutil
flash_attn
# math_eval
sympy==1.12
antlr4-python3-runtime==4.11.1 # ! The version needs to be compatible with sympy.
word2number
Pebble
timeout-decorator
+196
View File
@@ -0,0 +1,196 @@
import re
"""
trajcectory:
[
{"role": "rationale", "content": "..."},
{"role": "program", "content": "..."},
{"role": "output", "content": "..."},
{"role": "rationale", "content": "..."},
...
]
"""
def text_to_trajectory(traj_str: str) -> None:
"""
"""
# parse the above interleaved string of raionale, program, output, raionale, program, output, ...
# output a list of dict
trajectory = []
cur_role = "rationale"
cur_content = ""
# print(traj_str)
for i, line in enumerate(traj_str.split("\n")):
if line == "```python": # program begin
assert cur_role == "rationale"
if cur_content:
trajectory.append({"role": cur_role, "content": cur_content})
cur_content = ""
cur_role = "program"
elif cur_role == "program" and line == "```": # program end
assert cur_content
trajectory.append({"role": cur_role, "content": cur_content})
cur_content = ""
cur_role = "output"
elif cur_role == "output" and line.startswith("```output"): # output begin
assert cur_content == ""
elif cur_role == "output" and line == "```": # output end
trajectory.append({"role": cur_role, "content": cur_content})
cur_content = ""
cur_role = "rationale"
else: # content
cur_content += line
if i < len(traj_str.split("\n")) - 1:
cur_content += "\n"
# the last content
if cur_content:
trajectory.append({"role": cur_role, "content": cur_content})
return trajectory
def trajectory_to_text(trajectory: list) -> str:
text = ""
for item in trajectory:
content = item["content"]
if item["role"] == "program":
content = f"```python\n{content}```\n"
elif item["role"] == "output":
content = f"```output\n{content}```\n"
text += content
return text
def is_execution_success(output):
error_key_words = ["error", "exception", "no algorithms", "no algorithms", "cannot", "nan", "..."]
success = all([k not in output.lower() for k in error_key_words])
return success
def extract_program(text:str=None, trajectory:list=None, last_only=False) -> str:
assert text is not None or trajectory is not None, "Either text or trajectory should be provided."
if trajectory is None:
try:
trajectory = text_to_trajectory(text)
except:
return "raise ValueError('Invalid trajectory')"
program_list = []
import_lines = []
for i, item in enumerate(trajectory):
if item["role"] == "program":
cur_program = item["content"]
if i < len(trajectory) - 1:
assert trajectory[i+1]["role"] == "output"
output = trajectory[i+1]["content"].strip()
if is_execution_success(output):
program_list.append(cur_program)
else:
# extract import lines only
for line in cur_program.split("\n"):
if line.startswith("import") or line.startswith("from"):
import_lines.append(line)
else:
program_list.append(cur_program)
# add import lines to the first program
if len(program_list) == 0:
program_list.append("")
if len(import_lines) > 0:
program_list[0] = "\n".join(import_lines) + "\n" + program_list[0]
for i, program in enumerate(program_list[:-1]):
program_list[i] = "\n".join([line for line in program.split("\n") if not line.strip().startswith("print(")])
if last_only:
program = program_list[-1]
else:
program = "\n".join(program_list)
return program
def extract_program_output(pred_str, last_only=True):
"""
extract output between ```output\n...\n```, use regex, there might be multiple outputs, each output may have multiple lines
"""
outputs = re.findall(r"```output\n(.*?)\n```", pred_str, re.DOTALL)
if last_only:
return outputs[-1] if len(outputs) > 0 else ""
else:
return outputs
def _test_str_to_trajectory():
traj_text = """We can use the Chinese Remainder Theorem to solve this system of congruences. The Chinese Remainder Theorem states that if $n_1, n_2, \ldots, n_k$ are pairwise coprime (i.e., the greatest common divisor of any two of them is 1), and if $a_1, a_2, \ldots, a_k$ are any integers, then there is a number $x$ that satisfies the system of congruences:
$x \equiv a_1 \pmod{n_1}$
$x \equiv a_2 \pmod{n_2}$
$\vdots$
$x \equiv a_k \pmod{n_k}$
Moreover, any two solutions of this system are congruent modulo the product $n_1n_2 \cdots n_k$.
Let's write a function to solve this:
```python
from sympy import symbols, solve
def smallest_x():
x = symbols('x')
# Solve the system of congruences
solution = solve([x - 7, x - 17], x, domain='Z')
# Return the smallest positive solution
return min([sol for sol in solution if sol >= 0])
result = smallest_x()
print(result)
```
```output
ValueError: min() arg is an empty sequence
```
The error in the last example is due to the incorrect usage of the `solve` function. The `solve` function is used to solve equations, not congruences. To solve a system of congruences, we can use the `solve_congruence` function from the `sympy.ntheory.modular` module.
Here is the corrected code:
```python
from sympy.ntheory.modular import solve_congruence
def smallest_x():
# Solve the system of congruences
solution = solve_congruence(('x', 7, 12), ('x', 17, 11))
# Return the smallest positive solution
return solution[0][0]
result = smallest_x()
print(result)
```
```output
good
```
ValueError: too many values to unpack (expected 2)
The error in the last example is due to the incorrect usage of the `solve_congruence` function. The `solve_congruence` function returns a tuple of two elements: the solution and the modulus. We need to unpack these two values correctly.
Here is the corrected code:
```python
from sympy.ntheory.modular import solve_congruence
def smallest_x():
# Solve the system of congruences
solution, modulus = solve_congruence(('x', 7, 12), ('x', 17, 11))
# Return the smallest positive solution
return solution
result = smallest_x()
print(result)
```"""
import pprint
trajectory = text_to_trajectory(traj_text)
pprint.pprint(trajectory)
text = trajectory_to_text(trajectory)
assert text == traj_text
# print(extract_program(traj_text))
if __name__ == "__main__":
_test_str_to_trajectory()
+273
View File
@@ -0,0 +1,273 @@
import os
import json
import random
import json
import os
import numpy as np
from pathlib import Path
from typing import Iterable, Union, Any
from data.qwen25math.examples import get_examples
def set_seed(seed: int = 42) -> None:
np.random.seed(seed)
random.seed(seed)
os.environ["PYTHONHASHSEED"] = str(seed)
print(f"Random seed set as {seed}")
def load_jsonl(file: Union[str, Path]) -> Iterable[Any]:
with open(file, "r", encoding="utf-8") as f:
for line in f:
try:
yield json.loads(line)
except:
print("Error in loading:", line)
exit()
def save_jsonl(samples, save_path):
# ensure path
folder = os.path.dirname(save_path)
os.makedirs(folder, exist_ok=True)
with open(save_path, "w", encoding="utf-8") as f:
for sample in samples:
f.write(json.dumps(sample, ensure_ascii=False) + "\n")
print("Saved to", save_path)
def lower_keys(example):
new_example = {}
for key, value in example.items():
if key != key.lower():
new_key = key.lower()
new_example[new_key] = value
else:
new_example[key] = value
return new_example
EXAMPLES = get_examples()
def load_prompt(data_name, prompt_type, num_shots):
if not num_shots:
return []
if data_name in ["gsm_hard", "svamp", "tabmwp", "asdiv", "mawps"]:
data_name = "gsm8k"
if data_name in ["math_oai", "hungarian_exam", "math-oai", "aime24", "amc23"]:
data_name = "math"
if data_name in ["sat_math"]:
data_name = "mmlu_stem"
if data_name in [
"gaokao2024_I",
"gaokao2024_II",
"gaokao_math_qa",
"gaokao2024_mix",
"cn_middle_school",
]:
data_name = "gaokao"
if prompt_type in ["tool-integrated"]:
prompt_type = "tora"
return EXAMPLES[data_name][:num_shots]
PROMPT_TEMPLATES = {
"direct": ("Question: {input}\nAnswer: ", "{output}", "\n\n"),
"cot": ("Question: {input}\nAnswer: ", "{output}", "\n\n\n"),
"pal": ("Question: {input}\n\n", "{output}", "\n---\n"),
"tool-integrated": ("Question: {input}\n\nSolution:\n", "{output}", "\n---\n"),
"self-instruct": ("<|user|>\n{input}\n<|assistant|>\n", "{output}", "\n"),
"tora": ("<|user|>\n{input}\n<|assistant|>\n", "{output}", "\n"),
"wizard_zs": (
"### Instruction:\n{input}\n\n### Response: Let's think step by step.",
"{output}",
"\n\n\n",
),
"platypus_fs": (
"### Instruction:\n{input}\n\n### Response:\n",
"{output}",
"\n\n\n",
),
"deepseek-math": (
"User: {input}\nPlease reason step by step, "
"and put your final answer within \\boxed{{}}.\n\nAssistant:",
"{output}",
"\n\n\n",
),
"kpmath": (
"User: Please reason step by step and put your final answer at the end "
'with "The answer is: ".\n\n{input}\n\nAssistant:',
"{output}",
),
"jiuzhang": (
"## Question\n{input}\n\n## Solution\n",
"{output}",
"\n\n\n",
),
"jiuzhang_tora": (
"## Question\n{input}\n\n## Code Solution\n",
"{output}",
"\n\n\n",
),
"jiuzhang_nl": (
"## Question\n{input}\n\n## Natural Language Solution\n",
"{output}",
"\n\n\n",
),
"mmiqc": (
'Please solve the following problem and put your answer at the end with "The answer is: ".\n\n{input}\n\n',
"{output}",
"\n\n\n",
),
"abel": (
"Question:\n{input}\nAnswer:\nLet's think step by step.\n",
"{output}",
"\n\n",
),
"shepherd": ("{input}\n", "{output}", "\n\n\n"),
"qwen-boxed": (
"<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
"<|im_start|>user\n{input}\nPlease reason step by step, and put your final answer within \\boxed{{}}.<|im_end|>\n"
"<|im_start|>assistant\n",
"{output}",
"\n\n",
),
"qwen25-math-cot": (
"<|im_start|>system\nPlease reason step by step, and put your final answer within \\boxed{{}}.<|im_end|>\n"
"<|im_start|>user\n{input}<|im_end|>\n"
"<|im_start|>assistant\n",
"{output}",
"\n\n",
),
"mathstral": (
"{input}\nPlease reason step by step, and put your final answer within \\boxed{{}}.",
"{output}",
"\n\n",
),
"internlm-math-fs": ("Question:{input}\nAnswer:", "{output}", "\n"),
"internlm-math-chat": (
"<|im_start|>user\n{input}<|im_end|>\n" "<|im_start|>assistant\n",
"{output}",
"\n\n",
),
"mistral": (
"[INST] {input}[/INST]",
"{output}",
"\n\n",
),
"numina": ("### Problem: {input}\n### Solution:", " {output}", "\n\n"),
}
def construct_prompt(example, data_name, args):
if args.adapt_few_shot and data_name in [
"gaokao2024_I",
"gaokao2024_II",
"gaokao_math_qa",
"gaokao2024_mix",
"cn_middle_school",
]:
demos = load_prompt(data_name, args.prompt_type, 5)
else:
demos = load_prompt(data_name, args.prompt_type, args.num_shots)
prompt_type = args.prompt_type
if prompt_type == "platypus_fs":
prompt_type = "cot"
if prompt_type == "tool-integrated":
prompt_type = "tora"
prompt_temp = PROMPT_TEMPLATES[args.prompt_type]
splitter = prompt_temp[2]
input_template, output_template, splitter = (
prompt_temp[0],
prompt_temp[1],
prompt_temp[2],
)
if args.prompt_type == "qwen25-math-cot":
# Hotfix to support putting all demos into a single turn
demo_prompt = splitter.join([q + "\n" + a for q, a in demos])
else:
demo_prompt = splitter.join(
[
input_template.format(input=q) + output_template.format(output=a)
for q, a in demos
]
)
context = input_template.format(input=example["question"])
if len(demo_prompt) == 0 or (
args.adapt_few_shot and example["gt_ans"] not in ["A", "B", "C", "D", "E"]
):
full_prompt = context
else:
if args.prompt_type == "qwen25-math-cot":
# Hotfix to supportting put all demos into a single turn
full_prompt = demo_prompt + splitter + example["question"]
full_prompt = input_template.format(input=full_prompt)
else:
full_prompt = demo_prompt + splitter + context
if args.prompt_type == "platypus_fs":
full_prompt_temp = (
"Below is an instruction that describes a task. "
"Write a response that appropriately completes the request.\n\n"
"### Instruction:\n{instruction}\n\n### Response:\n"
)
full_prompt = full_prompt_temp.format(instruction=full_prompt)
if prompt_type == "tora":
full_prompt = (
"""Integrate step-by-step reasoning and Python code to solve math problems using the following guidelines:
- Analyze the question and write functions to solve the problem; the function should not take any arguments.
- Present the final result in LaTeX using a `\boxed{}` without any units.
- Utilize the `pi` symbol and `Rational`` from Sympy for $\pi$ and fractions, and simplify all fractions and square roots without converting them to decimal values.
Here are some examples you may refer to:
---
"""
+ full_prompt
)
return full_prompt.strip(" ") # important!
key_map = {
"gt": "Ground Truth",
"pred": "Prediction",
"gt_cot": "Reference CoT",
"score": "Score",
}
def show_sample(sample, print_all_preds=False):
print("==" * 20)
for key in ["idx", "type", "level", "dataset"]:
if key in sample:
# capitalize
print("{}: {}".format(key[0].upper() + key[1:], sample[key]))
print("Question:", repr(sample["question"]))
if "code" in sample:
if print_all_preds:
for code in sample["code"]:
print("-" * 20)
print("code:", code)
print("Execution:", sample["report"])
else:
print("Solution:\n", sample["code"][0])
print("Execution:", sample["report"][0])
if "pred" in sample:
print("Prediction:", repr(sample["pred"][0]))
for key in ["gt", "score", "unit", "gt_cot"]:
if key in sample:
_key = key_map.get(key, key)
print("{}: {}".format(_key, repr(sample[key])))
print()
+147
View File
@@ -0,0 +1,147 @@
import requests
import json
from typing import List, Iterable, Tuple, Union
from omegaconf.listconfig import ListConfig
from general_util.logger import get_child_logger
logger = get_child_logger("VLLM")
def post_http_request(api_url: str,
n: int = 1,
max_tokens: int = 16,
temperature: float = 0.0,
use_beam_search: bool = False,
stream: bool = False,
stop: List[str] = ["</s>"],
**kwargs) -> requests.Response:
headers = {"User-Agent": "MERIt Test Client"}
# headers = {
# "accept": "application/json",
# "content-type": "application/json",
#
# }
if "TOGETHER_API_KEY" in kwargs:
headers["authorization"] = f"Bearer {kwargs.pop('TOGETHER_API_KEY')}"
p_load = {
"n": n,
"use_beam_search": use_beam_search,
"temperature": temperature,
"max_tokens": max_tokens,
"stream": stream,
"stop": stop,
}
p_load.update(kwargs)
response = requests.post(api_url, headers=headers, json=p_load, stream=True)
return response
def get_streaming_response(response: requests.Response) -> Iterable[List[str]]:
for chunk in response.iter_lines(chunk_size=8192,
decode_unicode=False,
delimiter=b"\0"):
if chunk:
data = json.loads(chunk.decode("utf-8"))
output = data["text"]
yield output
def get_response(response: requests.Response) -> Tuple[str, str]:
data = json.loads(response.content)
output = data["text"]
return output, data
class VLLMRequestGenerator:
def __init__(self, api_url: str, n: int = 1, max_tokens: int = 1024, use_beam_search: bool = False, stream: bool = False,
temperature: float = 0.0, stop: Union[List[str], ListConfig] = ["</s>"], **kwargs):
self.api_url = api_url
self.n = n
self.max_tokens = max_tokens
self.use_beam_search = use_beam_search
self.stream = stream
self.stop = stop if isinstance(stop, list) else list(stop)
self.temperature = temperature
self.kwargs = kwargs
self.max_retry = 10
print(self.kwargs)
def __call__(self, prompt: str) -> str:
re_try = 0
max_tokens = self.max_tokens
while True:
if "completions" not in self.api_url:
response, content = get_response(post_http_request(prompt=prompt,
api_url=self.api_url,
n=self.n,
max_tokens=max_tokens,
temperature=self.temperature,
use_beam_search=self.use_beam_search,
stream=self.stream,
stop=self.stop,
**self.kwargs))[0]
response = response.replace(prompt, "")
# TODO: Add error processing here (need to remove `get_response` method)
elif "/chat/completions" in self.api_url:
if not isinstance(prompt, list):
prompt = [{"role": "user", "content": prompt}]
response = post_http_request(messages=prompt,
api_url=self.api_url,
n=self.n,
max_tokens=max_tokens,
temperature=self.temperature,
use_beam_search=self.use_beam_search,
stream=self.stream,
stop=self.stop,
**self.kwargs)
if response.status_code != 200:
logger.warning(response.json())
response = response.content.decode('utf-8')
if "maximum context length" in response:
max_tokens -= 100
re_try += 1
logger.warning("max_tokens reduced to: {}".format(max_tokens))
response = ""
else:
re_try = 0
outputs = []
for item in json.loads(response.content)["choices"]:
outputs.append(item["message"]["content"].replace(prompt[-1]["content"], ""))
if len(outputs) == 1:
response = outputs[0]
else:
response = outputs
else:
response = post_http_request(prompt=prompt,
api_url=self.api_url,
n=self.n,
max_tokens=max_tokens,
temperature=self.temperature,
use_beam_search=self.use_beam_search,
stream=self.stream,
stop=self.stop,
**self.kwargs)
if response.status_code != 200:
logger.warning(response.json())
response = response.content.decode('utf-8')
if "maximum context length" in response:
max_tokens -= 100
re_try += 1
logger.warning("max_tokens reduced to: {}".format(max_tokens))
response = ""
else:
re_try = 0
outputs = []
for item in json.loads(response.content)["choices"]:
outputs.append(item["text"].replace(prompt, ""))
if len(outputs) == 1:
response = outputs[0]
else:
response = outputs
if re_try == 0 or re_try > self.max_retry:
break
return response