Datawhale AI夏令营第3期学习笔记1
大模型API获取
获取平台
阿里云灵积平台:https://dashscope.console.aliyun.com/apiKey
获取方法
若为新用户,按平台提示注册账号并登录,然后按以下提示开通模型服务,开通该平台可获赠一些模型的限时免费使用额度,大部分有效期为30天。
模型介绍
该平台提供通义千问、LLaMa2大语言模型、百川开源大语言模型、ChatGLM开源双语对话语言模型、通用文本向量,各有不同版本,计费标准各有相同,在“DashScope 模型服务灵积”管理界面可查详情。
每个账号使用模型的额度有限制,可在“调用统计”中选择要查询的模型名称、调用API-Key、时间,单击“查询”后可查看统计,会提示所用额度及剩余免费额度。
开发环境介绍
魔塔Notebook平台:https://www.modelscope.cn/my/mynotebook/preset
点击链接后可用支付宝扫描登录账号(若无账号则注册账号,并绑定阿里云账号,绑定后可领取100小时GPU免费使用额度),进入“首页”,选择“我的Notebook”,(选择默认方式)点击“启动”,启动成果后单击“查看Notebook”(图1所示),打开环境界面(图2)。
图1
图2
赛题分析
赛题“复杂推理能力评估”是一个大语言模型处理推理任务的问题。
数据集为逻辑推理数据,其中训练集中包含500条训练数据,测试集中包含500条测试数据。每个问题包括若干子问题,每个子问题为单项选择题,选项不定(最多5个)。目标是为每个子问题选择一个正确答案。推理答案基于闭世界假设(closed-world assumption),即未观测事实或者无法推断的事实为假。
具体的,每条训练数据包含 content, questions字段,其中content是题干,questions为具体的子问题。questions是一个子问题列表,每个子问题包括options和answer字段,其中options是一个列表,包含具体的选项,按照ABCDE顺序排列,answer是标准答案。
其中提供训练集round1_train_data.jsonl(每一行代表一条反应),格式如下:
{'id': 'round_train_data_001',
'problem': '有一个计算阶乘的递归程序。该程序根据给定的数值计算其阶乘。以下是其工作原理:\n\n当数字是0时,阶乘是1。\n对于任何大于0的数字,其阶乘是该数字乘以其前一个数字的阶乘。\n根据上述规则,回答以下选择题:',
'questions': [{'question': '选择题 1:\n3的阶乘是多少?\n',
'options': ('3', '6', '9', '12'),
'answer': 'B'},
{'question': '选择题 2:\n8的阶乘是多少?\n',
'options': ('5040', '40320', '362880', '100000'),
'answer': 'B'},
{'question': '选择题 3:\n4的阶乘是多少?\n',
'options': ('16', '20', '24', '28'),
'answer': 'C'},
{'question': '选择题 4:\n3的阶乘是9吗?\n',
'options': ('是', '否'),
'answer': 'B'}]
}
测试集 round1_test_data.jsonl(不包含answer字段)。
项目实践
安装并导入所需第三方库
!pip install scipy openai tiktoken retry dashscope loguru
from multiprocessing import Process, Manager
import json
import os
from pprint import pprint
import re
from tqdm import tqdm
import random
import uuid
import openai
import tiktoken
import json
import numpy as np
import requests
from retry import retry
from scipy import sparse
#from rank_bm25 import BM25Okapi
#import jieba
from http import HTTPStatus
import dashscope
from concurrent.futures import ThreadPoolExecutor, as_completed
from loguru import logger
import json
import time
from tqdm import tqdm
获取调用的模型名称和账号密钥
logger.remove() # 移除默认的控制台输出
logger.add("logs/app_{time:YYYY-MM-DD}.log", level="INFO", rotation="00:00", retention="10 days", compression="zip")
MODEL_NAME = 'qwen2-1.5b-instruct' #表示调用通义千问的一个版本模型,可查看上述计费管理选择所需模型名称
dashscope.api_key="sk-****8b" # 将申请的API-Key复制到此处。
设置相关函数
1、重复调用API进行推理,处理网络超时和调用失败等异常情况。
def api_retry(MODEL_NAME, query):
max_retries = 5
retry_delay = 60 # in seconds
attempts = 0
while attempts < max_retries:
try:
return call_qwen_api(MODEL_NAME, query)
except Exception as e:
attempts += 1
if attempts < max_retries:
logger.warning(f"Attempt {attempts} failed for text: {query}. Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
else:
logger.error(f"All {max_retries} attempts failed for text: {query}. Error: {e}")
raise
def call_qwen_api(MODEL_NAME, query): # 这里采用dashscope的api调用模型推理,通过http传输的json封装返回结果
messages = [
{'role': 'user', 'content': query}]
response = dashscope.Generation.call(
MODEL_NAME,
messages=messages,
result_format='message', # set the result is message format.
)
if response.status_code == HTTPStatus.OK:
return response['output']['choices'][0]['message']['content']
else:
print('Request id: %s, Status code: %s, error code: %s, error message: %s' % (
response.request_id, response.status_code,
response.code, response.message
))
raise Exception()
2、定义prompt推理模版;可通过优化该部分prompt的表达以提高模型推理性能。
def get_prompt(problem, question, options):
options = '\n'.join(f"{'ABCDEFG'[i]}. {o}" for i, o in enumerate(options))
prompt = f"""你是一个逻辑推理专家,擅长解决逻辑推理问题。以下是一个逻辑推理的题目,形式为单项选择题。所有的问题都是(close-world assumption)闭世界假设,即未观测事实都为假。请逐步分析问题并在最后一行输出答案,最后一行的格式为"答案是:A"。题目如下:
### 题目:
{problem}
### 问题:
{question}
{options}
"""
return prompt
3、使用extract函数抽取模型获得的结果
def extract(input_text):
ans_pattern = re.compile(r"答案是:(.)", re.S)
problems = ans_pattern.findall(input_text)
if(problems == ''):
return 'A'
return problems[0]
4、多线程处理数据,并输出处理过程进度,返回结果。
def process_datas(datas,MODEL_NAME):
results = []
with ThreadPoolExecutor(max_workers=16) as executor:
future_data = {}
lasttask = ''
lastmark = 0
lens = 0
for data in tqdm(datas, desc="Submitting tasks", total=len(datas)):
problem = data['problem']
for id,question in enumerate(data['questions']):
prompt = get_prompt(problem,
question['question'],
question['options'],
)
future = executor.submit(api_retry, MODEL_NAME, prompt)
future_data[future] = (data,id)
time.sleep(0.6) # 控制每0.5秒提交一个任务
lens += 1
for future in tqdm(as_completed(future_data), total=lens, desc="Processing tasks"):
data = future_data[future][0]
problem_id = future_data[future][1]
try:
res = future.result()
extract_response = extract(res)
data['questions'][problem_id]['answer'] = extract_response
results.append(data)
except Exception as e:
logger.error(f"Failed to process text: {data}. Error: {e}")
return results
5、整合各功能函数和数据集
def main(ifn, ofn):
if os.path.exists(ofn):
pass
data = []
with open(ifn) as reader: # 按行读取数据
for line in reader:
sample = json.loads(line)
data.append(sample)
datas = data
return_list = process_datas(datas,MODEL_NAME) # 均匀地分成多个数据集
print(len(return_list))
print("All tasks finished!")
return return_list
if __name__ == '__main__':
return_list = main('round1_test_data.jsonl', 'upload.jsonl')
6、除去各题中的重复答案,并按原始数据顺序将处理得到的数据排序,
def has_complete_answer(questions):
# 这里假设完整答案的判断逻辑是:每个question都有一个'answer'键
for question in questions:
if 'answer' not in question:
return False
return True
def filter_problems(data):
result = []
problem_set = set()
for item in data:
problem = item['problem']
if problem in problem_set:
for existing_item in result: # 找到已存在的字典
if existing_item['problem'] == problem:
if has_complete_answer(item['questions']): # 如果当前字典有完整答案,替换已存在的字典
existing_item['questions'] = item['questions']
existing_item['id'] = item['id']
break
else: # 如果当前字典有完整答案,添加到结果列表
if has_complete_answer(item['questions']):
result.append(item)
problem_set.add(problem)
return result
return_list = filter_problems(return_list)
sorted_data = sorted(return_list, key=lambda x: int(str(x['id'])[-3:]))
7、纠错并补错;查看数据中各题的答案是否为空,提取其索引值进行处理。
def find_missing_ids(dict_list):
extracted_ids = {int(d['id'][-3:]) for d in dict_list} # 提取所有序号
all_ids = set(range(500)) # 创建0-500的序号集合
missing_ids = all_ids - extracted_ids # 找出缺失的序号
return sorted(missing_ids)
dict_list = sorted_data # 示例字典列表
missing_ids = find_missing_ids(dict_list) # 找出缺失的序号
# print("缺失的序号:", missing_ids)
data = []
with open('round1_test_data.jsonl') as reader:
for id,line in enumerate(reader):
if(id in missing_ids):
sample = json.loads(line)
for question in sample['questions']:
question['answer'] = 'A'
sorted_data.append(sample)
sorted_data = sorted(sorted_data, key=lambda x: int(str(x['id'])[-3:]))
8、将模型推理后处理好的数据保存到.jsonl文件中
with open('upload.jsonl', 'w') as writer:
for sample in sorted_data:
writer.write(json.dumps(sample, ensure_ascii=False))
writer.write('\n')
9、模型推理结果评分函数;可利用该函数将训练集的数据与模型生成的答案对比,从而评估准确率。
def evaluate(ofn):
data = []
with open(ofn) as reader:
for line in reader:
sample = json.loads(line)
data.append(sample)
pse = 0
cnt = 0
tot = 0
for task in data:
for question in task['questions']:
if MODEL_NAME in question:
tot += 1
cnt += question[MODEL_NAME] == question['answer']
else:
pse += 1
print(cnt, tot, cnt/tot, pse)