mirror of
https://github.com/hanzoai/open-instruct.git
synced 2026-10-30 18:26:32 +00:00
* first pass * fix spelling, ground truth stuff * fix misspelling * count verifieds and intermediate saving * save intermediate steps * small fix to logging * fix bug for forward rollout batching * support gsm8k and math, more flexibility in future * add costas plo thing * add numina math * remove plo, add value model rand init, first stab at rephrase model loading * math strict verify * ifeval code * ifeval debug * incorporate val fixes * data fixed, remove skips * Prototype ppo + ray (#390) * Prototype ppo + ray * reduce gradient * push * push changes * quick push * cache changes; this actually works with 6 nodes * push changes * psuh changes * push the latest change * push changes * Fix uploading * Make style * style and quality * update docs * update mason.py * log wandb tables * update docs * make style quality * make sure to save the right thing * push changes * push * push * push changes * push * push * fix * remove preemption code * fix * push changes * push * quick fix * quick push * add weka save override * add multinode ray file * lint and fix * first stab at flan * eval on intermediate checkpoints (#414) * quick change (#418) * works with the weka eval and nfs eval * fixes to ground truth * push ppo ray * add warm up * revert changes * merge * quick change --------- Co-authored-by: Hamish Ivison <[email protected]> Co-authored-by: Hamish Ivison <[email protected]> Co-authored-by: Costa Huang <[email protected]>
379 lines
9.9 KiB
Python
379 lines
9.9 KiB
Python
import re
|
|
import signal
|
|
import logging
|
|
from typing import Optional
|
|
|
|
import sympy
|
|
from sympy.parsing.latex import parse_latex
|
|
|
|
|
|
eval_logger = logging.getLogger("math_utils")
|
|
|
|
|
|
# from https://github.com/EleutherAI/lm-evaluation-harness/blob/main/lm_eval/tasks/minerva_math/utils.py#L187
|
|
def last_boxed_only_string(string: str) -> Optional[str]:
|
|
idx = string.rfind("\\boxed")
|
|
if "\\boxed " in string:
|
|
return "\\boxed " + string.split("\\boxed ")[-1].split("$")[0]
|
|
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 remove_boxed(s: str) -> str:
|
|
if "\\boxed " in s:
|
|
left = "\\boxed "
|
|
assert s[: len(left)] == left
|
|
return s[len(left) :]
|
|
|
|
left = "\\boxed{"
|
|
|
|
assert s[: len(left)] == left
|
|
assert s[-1] == "}"
|
|
|
|
return s[len(left) : -1]
|
|
|
|
|
|
def get_unnormalized_answer(text: str) -> str:
|
|
INVALID_ANSWER = "[invalidanswer]"
|
|
end_seq = "I hope it is correct."
|
|
text += end_seq
|
|
match = re.search(
|
|
r"Final Answer: The final answer is(.*?). I hope it is correct.",
|
|
text,
|
|
)
|
|
if match:
|
|
return match.group(1).strip()
|
|
else:
|
|
return INVALID_ANSWER
|
|
|
|
|
|
SUBSTITUTIONS = [
|
|
("an ", ""),
|
|
("a ", ""),
|
|
(".$", "$"),
|
|
("\\$", ""),
|
|
(r"\ ", ""),
|
|
(" ", ""),
|
|
("mbox", "text"),
|
|
(",\\text{and}", ","),
|
|
("\\text{and}", ","),
|
|
("\\text{m}", "\\text{}"),
|
|
]
|
|
REMOVED_EXPRESSIONS = [
|
|
"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_final_answer(final_answer: str) -> str:
|
|
"""
|
|
Normalize a final answer to a quantitative reasoning question.
|
|
|
|
Copied character for character from appendix D of Lewkowycz et al. (2022)
|
|
"""
|
|
final_answer = final_answer.split("=")[-1]
|
|
|
|
for before, after in SUBSTITUTIONS:
|
|
final_answer = final_answer.replace(before, after)
|
|
for expr in 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
|
|
|
|
|
|
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 is_equiv(x1: str, x2: str) -> bool:
|
|
"""
|
|
x1 and x2 are normalized latex string
|
|
"""
|
|
try:
|
|
with timeout(seconds=5):
|
|
try:
|
|
parsed_x1 = parse_latex(x1)
|
|
parsed_x2 = parse_latex(x2)
|
|
except (
|
|
sympy.parsing.latex.errors.LaTeXParsingError,
|
|
sympy.SympifyError,
|
|
TypeError,
|
|
):
|
|
eval_logger.debug(f"couldn't parse one of {x1} or {x2}")
|
|
return False
|
|
|
|
try:
|
|
diff = parsed_x1 - parsed_x2
|
|
except TypeError:
|
|
eval_logger.debug(f"couldn't subtract {x1} and {x2}")
|
|
return False
|
|
|
|
try:
|
|
if sympy.simplify(diff) == 0:
|
|
return True
|
|
else:
|
|
return False
|
|
except ValueError:
|
|
eval_logger.debug(
|
|
f"Had some trouble simplifying when comparing {x1} and {x2}"
|
|
)
|
|
except TimeoutError:
|
|
eval_logger.debug(f"Timed out comparing {x1} and {x2}")
|
|
return False
|
|
except ImportError as e:
|
|
eval_logger.error(e)
|
|
raise
|
|
except Exception as e:
|
|
eval_logger.debug(f"Failed comparing {x1} and {x2} with {e}")
|
|
return False
|
|
|
|
|
|
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 hendrycks_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)
|
|
if verbose:
|
|
print(ss1, ss2)
|
|
return ss1 == ss2
|
|
except Exception:
|
|
return str1 == str2
|