Files
wehub-resource-sync 2aaeece67c
Codestyle Check / Lint (push) Has been cancelled
Codestyle Check / Check bypass (push) Has been cancelled
Pipelines-Test / Pipelines-Test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:37:14 +08:00

190 lines
7.5 KiB
Python

# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Adapted from https://github.com/ymcui/Chinese-LLaMA-Alpaca and https://github.com/SJTU-LIT/ceval
import os
import random
import re
import numpy as np
import paddle
from evaluator import Evaluator
from tqdm import tqdm
from paddlenlp.transformers import AutoModelForCausalLM, AutoTokenizer
class ModelEvaluator(Evaluator):
def __init__(self, choices, k, model_name_or_path, temperature=0.2):
super().__init__(choices, model_name_or_path, k)
self.model_name_or_path = model_name_or_path
self.tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
self.model = AutoModelForCausalLM.from_pretrained(model_name_or_path, dtype="float16")
self.model.eval()
self.generation_config = dict(
temperature=temperature,
top_k=40,
top_p=0.9,
do_sample=True,
num_beams=1,
repetition_penalty=1.1,
max_new_tokens=20,
)
self.A_id = self.tokenizer.encode("A", add_special_tokens=False)["input_ids"][0]
self.B_id = self.tokenizer.encode("B", add_special_tokens=False)["input_ids"][0]
self.C_id = self.tokenizer.encode("C", add_special_tokens=False)["input_ids"][0]
self.D_id = self.tokenizer.encode("D", add_special_tokens=False)["input_ids"][0]
def eval_subject(
self,
subject_name,
test_df,
dev_df=None,
few_shot=False,
cot=False,
save_result_dir=None,
with_prompt=False,
constrained_decoding=False,
do_test=False,
):
all_answers = {}
correct_num = 0
if save_result_dir:
result = []
score = []
if few_shot:
history = self.generate_few_shot_prompt(subject_name, dev_df, cot=cot)
else:
history = ""
answers = ["NA"] * len(test_df) if do_test is True else list(test_df["answer"])
for row_index, row in tqdm(test_df.iterrows(), total=len(test_df)):
question = self.format_example(row, include_answer=False, cot=cot, with_prompt=with_prompt)
instruction = history + question
inputs = self.tokenizer(instruction, return_tensors="pd")
batch_size, length = inputs.input_ids.shape
if constrained_decoding is True:
# batch_size is 1, take the last logits as the logits for next token prediction
with paddle.no_grad():
logits = self.model(**inputs)[0][0, -1, :]
choices_logits = logits[[self.A_id, self.B_id, self.C_id, self.D_id]].numpy()
assert not (np.any(np.isinf(choices_logits)) or np.any(np.isnan(choices_logits)))
ans = {0: "A", 1: "B", 2: "C", 3: "D"}[np.argmax(choices_logits)]
response = self.tokenizer.decode([logits.argmax(-1).item()])
else:
generation_output = self.model.generate(
**inputs,
eos_token_id=self.tokenizer.eos_token_id,
pad_token_id=self.tokenizer.pad_token_id,
**self.generation_config,
)
response = self.tokenizer.decode(generation_output[0][0, length:], skip_special_tokens=True)
ans, direct_extract = self.extract_answer(row, response)
if ans == answers[row_index]:
correct_num += 1
correct = 1
else:
correct = 0
print(f"\n=======begin {str(row_index)}=======")
print("question: ", question)
print("response: ", response)
print("ans: ", ans)
print("ground truth: ", answers[row_index], "\n")
if save_result_dir:
result.append(response)
score.append(correct)
print(f"=======end {str(row_index)}=======")
all_answers[str(row_index)] = ans
correct_ratio = 100 * correct_num / len(answers)
if save_result_dir:
test_df["model_output"] = result
test_df["correctness"] = score
test_df.to_csv(os.path.join(save_result_dir, f"{subject_name}_test.csv"))
return correct_ratio, all_answers
def format_example(self, line, include_answer=True, cot=False, with_prompt=False):
example = line["question"]
for choice in self.choices:
example += f'\n{choice}. {line[f"{choice}"]}'
if include_answer:
if cot:
example += "\n答案:让我们一步一步思考,\n" + line["explanation"] + f"\n所以答案是{line['answer']}\n\n"
else:
example += "\n答案:" + line["answer"] + "\n\n"
else:
if with_prompt is False:
if cot:
example += "\n答案:让我们一步一步思考,\n1."
else:
example += "\n答案:"
else:
if cot:
example += "\n答案是什么?让我们一步一步思考,\n1."
else:
example += "\n答案是什么? "
return example
def generate_few_shot_prompt(self, subject, dev_df, cot=False):
prompt = f"以下是中国关于{subject}考试的单项选择题,请选出其中的正确答案。\n\n"
k = self.k
if self.k == -1:
k = dev_df.shape[0]
for i in range(k):
prompt += self.format_example(dev_df.iloc[i, :], include_answer=True, cot=cot)
return prompt
def extract_answer(self, line, gen_ans):
m = re.findall(r"所以答案是(.+?)。", gen_ans, re.M)
if len(m) > 0 and m[-1] in self.choices:
return m[-1], True
answer_patterns = [
r"([ABCD])是正确的",
r"选项([ABCD])正确",
r"答案为([ABCD])",
r"答案是([ABCD])",
r"答案([ABCD])",
r"选择([ABCD])",
r"答案:([ABCD])",
r"选择答案([ABCD])",
]
# RE extraction
for answer_pattern in answer_patterns:
m = re.search(answer_pattern, gen_ans, re.M)
if m:
answer = m.group(1)
return answer, False
# only containing one choice-character
m = re.findall(r"[ABCD]", gen_ans, re.M)
if len(m) >= 1:
answer = m[0]
return answer, False
# only containing one choice-context
choices_dict = {}
pattern = ""
for c in self.choices:
choices_dict[str(line[f"{c}"])] = c
pattern += re.escape(str(line[f"{c}"])) + "|"
pattern = pattern[:-1]
m = re.findall(pattern, gen_ans, re.M)
print("w/ escape:", repr(pattern), gen_ans, (len(m) >= 1))
if len(m) >= 1:
answer = choices_dict[m[0]]
return answer, False
return random.choice("ABCD"), False