DataWhale Task04:手搓一个LLM Eval 241001

DataWhale Task04:手搓一个LLM Eval 241001

开源内容:https://github.com/datawhalechina/tiny-universe/blob/main/content/TinyEval/readme.md

视频link:https://meeting.tencent.com/user-center/shared-record-info?id=8b9cf6ca-add6-477b-affe-5b62e2d8f27e&from=3

微调遇到的问题:

  • 如何判断各大模型在当前数据集上的表现
  • 对于选择式、判别式、生成式等不同的生成任务,如何才能够客观地评价模型生成质量

评测任务的流程:

image-20241001173745747

EVal流程

  • 首先,根据目标数据集的任务类型指定合理的评测metric
  • 根据目标数据的形式总结模型引导prompt.
  • 根据模型初步预测结果采纳合理的抽取方式
  • 对相应的pred与anwser进行得分计算

inference.py___预测

eval.py__evaltion

支持的测评数据集与测评Metric

nametypemetric
multi_news长文本回答Rouge
multifieldqa_zh短文本回答F1
trec生成式选择accuracy

生成式F1

  • 引导prompt为

阅读以下文字并用中文简短回答:\n\n{context}\n\n现在请基于上面的文章回答下面的问题,只告诉我答案,不要输出任何其他字词。\n\n问题:{input}\n回答:

  • config内包含的为设置一些路径,设置一些问询引导的promote形式,设置最大的生存数量,前置设置的参数

image-20241001194613139- model2path 模型输入

-load_model 指出path,devices等等

image-20241001195908923

中间一段进行截断。截断策略,对于模型而言,尤其是制定了输入的长度,如果使用阶段命令则其会在输入的末尾进行阶段,但由于引导性prompt的存在,在inputs的两端均有关键信息,故需要对两端的信息进行保留,对中间部位进行截断操作,才能最大限度地抱持输出效果!

get_pred函数:

def get_pred(self, data, max_length, max_gen, prompt_format, device, out_path):
        model, tokenizer = self.load_model_and_tokenizer(self.path, device)
        for json_obj in tqdm(data):
            prompt = prompt_format.format(**json_obj)
            # 在中间截断,因为两头有关键信息.
            tokenized_prompt = tokenizer(prompt, truncation=False, return_tensors="pt").input_ids[0]
            if len(tokenized_prompt) > max_length:
                half = int(max_length/2)
                prompt = tokenizer.decode(tokenized_prompt[:half], skip_special_tokens=True)+tokenizer.decode(tokenized_prompt[-half:], skip_special_tokens=True)

            prompt = self.build_chat(prompt)

            input = tokenizer(prompt, truncation=False, return_tensors="pt").to(device)
            # 表示喂进去的tokens的长度
            context_length = input.input_ids.shape[-1]
            eos_token_id = [tokenizer.eos_token_id, tokenizer.convert_tokens_to_ids(["<|im_end|>"])[0]]

            output = model.generate(
                **input,
                max_new_tokens=max_gen,
                do_sample=False,
                temperature=1.0,
                eos_token_id=eos_token_id,
            )[0]
            
            pred = tokenizer.decode(output[context_length:], skip_special_tokens=True)
            pred = self.post_process(pred)
            
            with open(out_path, "a", encoding="utf-8") as f:
                json.dump({"pred": pred, "answers": json_obj["answers"], "all_classes": json_obj["all_classes"], "length": json_obj["length"]}, f, ensure_ascii=False)
                f.write('\n')

data: 输入的数据,通常是一个包含多个 JSON 对象的列表。

max_length: 最大输入长度,控制输入提示的长度。

max_gen: 最大生成的标记数,控制模型生成的输出长度。

prompt_format: 用于格式化输入提示的字符串模板。

device: 指定模型运行的设备(如 CPU 或 GPU)。

out_path: 输出文件的路径,预测结果将写入该文件。

model, tokenizer = self.load_model_and_tokenizer(self.path, device)

代码调用 load_model_and_tokenizer 方法加载指定路径下的模型和分词器,并将其转移到指定的设备上。

for json_obj in tqdm(data):

使用 tqdm 包来显示进度条,遍历输入数据中的每个 JSON 对象。

prompt = prompt_format.format(**json_obj)

使用 JSON 对象中的数据填充提示格式字符串。

tokenized_prompt = tokenizer(prompt, truncation=False, return_tensors="pt").input_ids[0]
if len(tokenized_prompt) > max_length:
    half = int(max_length/2)
    prompt = tokenizer.decode(tokenized_prompt[:half], skip_special_tokens=True) + tokenizer.decode(tokenized_prompt[-half:], skip_special_tokens=True)

将提示文本进行分词并检查其长度。如果长度超过 max_length,则截断提示,只保留前后各一半的信息。

prompt = self.build_chat(prompt)

对提示进行进一步处理,以适应聊天模型的输入格式。

input = tokenizer(prompt, truncation=False, return_tensors="pt").to(device)
context_length = input.input_ids.shape[-1]

对处理后的提示进行分词并转移到设备上。获取上下文的长度。

eos_token_id = [tokenizer.eos_token_id, tokenizer.convert_tokens_to_ids(["<|im_end|>"])[0]]

定义结束标记的 ID,用于模型生成的输出。

output = model.generate(
    **input,
    max_new_tokens=max_gen,
    do_sample=False,
    temperature=1.0,
    eos_token_id=eos_token_id,
)[0]

调用模型的 generate 方法生成文本。设置生成的最大新标记数、是否进行采样等参数。

pred = tokenizer.decode(output[context_length:], skip_special_tokens=True)
pred = self.post_process(pred)

将生成的输出解码为文本,跳过特殊标记,并进行后处理。

with open(out_path, "a", encoding="utf-8") as f:
    json.dump({"pred": pred, "answers": json_obj["answers"], "all_classes": json_obj["all_classes"], "length": json_obj["length"]}, f, ensure_ascii=False)
    f.write('\n')

将预测结果、原始答案、所有类别和长度以 JSON 格式写入输出文件,确保字符编码为 UTF-8。

f1评分函数

def f1_score(prediction, ground_truth, **kwargs):
    # Counter以dict的形式存储各个句子对应的词与其对应个数,&操作符返回两个Counter中共同的元素的键值对
    common = Counter(prediction) & Counter(ground_truth)
    # 显示prediction与gt的共同元素的个数  
    num_same = sum(common.values())                       
    if num_same == 0:
        return 0
    # 即模型预测正确的样本数量与总预测样本数量的比值
    precision = 1.0 * num_same / len(prediction)
    # 模型正确预测的样本数量与总实际样本数量的比值         
    recall = 1.0 * num_same / len(ground_truth)           
    f1 = (2 * precision * recall) / (precision + recall)
    return f1

prediction: 模型生成的预测结果,通常是一个词的列表。

ground_truth: 真实标签或标准答案,也是一个词的列表。

**kwargs: 可选的额外参数,未在函数中使用。

计算共同元素:

common = Counter(prediction) & Counter(ground_truth)

使用 Counter 类计算 predictionground_truth 中的共同元素(即同时存在于两个列表中的词)。& 操作符返回两个 Counter 对象中共同的键及其最小计数。

计算共同元素的数量:

num_same = sum(common.values())

统计共同元素的总数,即模型预测正确的词的数量。

处理特殊情况:

if num_same == 0:
    return 0

如果没有共同元素(即 num_same 为 0),则返回 F1 分数为 0,避免除以零的情况。

计算精确率:

precision = 1.0 * num_same / len(prediction)

精确率(Precision)是正确预测的数量与模型总预测数量的比值,反映模型的预测准确性。

计算召回率:

recall = 1.0 * num_same / len(ground_truth)

召回率(Recall)是正确预测的数量与真实样本数量的比值,反映模型的覆盖率。

计算 F1 分数:

f1 = (2 * precision * recall) / (precision + recall)

F1 分数是精确率和召回率的调和平均数,综合考虑了模型的准确性和覆盖率,值越高表示模型性能越好。

返回结果:

return f1

最后返回计算得到的 F1 分数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

L念安dd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值