Files
Costa HuangandGitHub 6c6193b7cd Add optional r1-style thinking reward (#551)
* Add optional r1-style thinking reward

* quick change

* test

* quick change

* push latest change

* fix

* tested it to work

* Push changes

* push
2025-02-06 12:21:39 -05:00

166 lines
6.0 KiB
Python

"""
Collection of 'ground truth rewards' for different datasets/tasks.
Used to give feedback to the model based on the ground truth answer.
"""
import json
import re
import string
from open_instruct.if_functions import IF_FUNCTIONS_MAP
from open_instruct.math_utils import (
get_unnormalized_answer,
hendrycks_is_equiv,
is_equiv,
last_boxed_only_string,
normalize_final_answer,
remove_boxed,
)
def verify_gsm8k_sample(model_output, ground_truth_answer):
# gsm is easy: extract numbers, and then just compare last number with answer.
# matches how we do eval.
predictions = None
# replace numbers like `x,xxx` with `xxxx`
response = re.sub(r"(\d),(\d)", r"\1\2", model_output)
numbers = re.findall(r"[-+]?\d*\.\d+|\d+", response)
if numbers:
predictions = numbers[-1]
else:
predictions = response
return str(predictions).lower() == str(ground_truth_answer).lower()
def verify_math_sample(model_output, ground_truth_answer):
raw_answer = model_output
# for math, more complex. We will try a few different ways to extract the answer.
# this roughly follows 'flex em' in oe-eval-internal
all_answers = []
# First, try find answer in \boxed{}.
boxed_answer = last_boxed_only_string(raw_answer)
if boxed_answer is not None:
try:
boxed_answer = remove_boxed(boxed_answer)
except AssertionError:
boxed_answer = None
if boxed_answer is not None:
all_answers.append(boxed_answer)
# Second, try to extract via minerva format.
minerva_answer = normalize_final_answer(get_unnormalized_answer(raw_answer))
if minerva_answer is not None and minerva_answer != "[invalidanswer]":
all_answers.append(minerva_answer)
# If nothing still, try to find the last latex-formatted answer
if len(all_answers) == 0:
dollars = [m.start() for m in re.finditer("\\$", raw_answer)]
if len(dollars) > 1:
# Add the answer between the second to last and last dollar sign
answer = normalize_final_answer(raw_answer[dollars[-2] + 1 : dollars[-1]])
all_answers.append(answer)
# otherwise, just take the full output. Probably wont work, bit of a yolo.
if len(all_answers) == 0:
all_answers.append(normalize_final_answer(model_output))
# now, compare all answers to ground truth.
matched = False
for answer in all_answers:
if is_equiv(answer, ground_truth_answer):
matched = True
break
elif hendrycks_is_equiv(answer, ground_truth_answer):
matched = True
break
# if we got any match, we are good.
return matched
def verify_strict_math_sample(model_output, ground_truth_answer):
raw_answer = model_output
# just trying minerva format.
all_answers = []
# Second, try to extract via minerva format.
minerva_answer = normalize_final_answer(get_unnormalized_answer(raw_answer))
if minerva_answer is not None and minerva_answer != "[invalidanswer]":
all_answers.append(minerva_answer)
# otherwise, just take the full output. Probably wont work, bit of a yolo.
if len(all_answers) == 0:
all_answers.append(normalize_final_answer(model_output))
# now, compare all answers to ground truth.
matched = False
for answer in all_answers:
if is_equiv(answer, ground_truth_answer):
matched = True
break
elif hendrycks_is_equiv(answer, ground_truth_answer):
matched = True
break
# if we got any match, we are good.
return matched
def verify_ifeval_sample(model_output, constraint):
# TODO: just pass in final answer. this should be fine for other evals too.
answer = model_output.split("<|assistant|>\n")[-1].strip()
if isinstance(constraint, str):
constraint = json.loads(constraint)
if "func_name" not in constraint:
print("WARNING: constraint missing func_name")
print(constraint)
return False
# first, parse out the constraint string.
func_name = constraint.pop("func_name")
# get the function
func = IF_FUNCTIONS_MAP[func_name]
# now, run the function
# pop out any none args
non_none_args = {k: v for k, v in constraint.items() if v is not None}
# sometimes we have extra args, sometimes not.
if len(constraint) == 0:
return func(model_output)
return func(answer, **non_none_args)
def normalize_answer(s):
"""
Lower text and remove punctuation, articles and extra whitespace.
From https://github.com/huggingface/evaluate/blob/main/metrics/squad/compute_score.py
"""
def remove_articles(text):
return re.sub(r"\b(a|an|the)\b", " ", text)
def white_space_fix(text):
return " ".join(text.split())
def remove_punc(text):
exclude = set(string.punctuation)
return "".join(ch for ch in text if ch not in exclude)
def lower(text):
return text.lower()
return white_space_fix(remove_articles(remove_punc(lower(s))))
def verify_flan_sample(model_output, ground_truth_answer):
# Flan! we will just use... exact match with some basic cleaning, after extracting the answer.
answer_string = model_output.split("The answer is: ")[-1].strip()
return normalize_answer(answer_string) == normalize_answer(ground_truth_answer)
def soft_format_reward_func(responses: list[str], reward_scale: float = 1.0) -> list[float]:
"""Reward function that checks if the completion has a specific format."""
pattern = r".*?</think>\s*<answer>.*?</answer>"
matches = [re.match(pattern, r, re.DOTALL) for r in responses]
return [reward_scale if match else 0.0 for match in matches]
# debug code
if __name__ == "__main__":
from datasets import load_dataset
ds = load_dataset("ai2-adapt-dev/prompts_with_constraints_for_ground_truth")
test_model_output = "<|assistant|>\nThe answer is $\\boxed{3.14}$"
for sample in ds["train"]:
print(sample)
verify_ifeval_sample(test_model_output, sample["ground_truth"])