Datawhale AI+X夏令营活动大模型技术(AI+逻辑推理)——task2_精读理解baseline

 学习链接:Docs

在跑通baseline后,我们精读baseline代码,baseline试运行:Datawhale AI+X夏令营活动大模型技术(AI+逻辑推理)——baseline代码学习-CSDN博客

大语言模型是一种人工智能模型,旨在理解和生成人类语言。它可以理解一些基本的逻辑,并且通过逻辑去思考问题

大模型推理实现最常用方法——提示工程(Prompt Engineering)

我们打开 round1_test_data.jsonl文件,观察具体代码,下面是其中第一行举例

{"problem": "有一群人和一些食物类型。下列是关于这些个体和食物的已知信息:\n\n1. 
鸡肉是一种食物。\n2. 
苹果是一种食物。\n3. 
如果X吃了Y,且X活着,则Y是一种食物。\n4. 
Bill存活。\n5. 
Bill吃了花生。\n6. 
John吃所有食物。\n7. 
Sue吃所有Bill吃的食物。\n8. 
John喜欢所有食物。\n\n
根据以上信息,回答以下选择题:", "questions": [{"question": "选择题 1:\n谁喜欢吃花生?", "options": ["Bill", "Sue", "John", "None of the above"]}], "id": "round1_test_data_000"}

首先每一行对应了一个问题,具体格式为先给出问题的描述和告知大语言模型的任务,本测试集为选择题,每题至少包括 A、B两个选项(这也是后面答案检查时填充的原理),通过把数据集投喂给大预言模型而大语言模型生成相应的jsonl文件,检查命中次数进行判分。

具体的prompt(MD格式便于阅读):

你是一个逻辑推理专家,擅长解决逻辑推理问题。以下是一个逻辑推理的题目,形式为单项选择题。所有的问题都是(close-world assumption)闭世界假设,即未观测事实都为假。请逐步分析问题并在最后一行输出答案,最后一行的格式为"答案是:A"。题目如下:

###题目:
有一群人和一些食物类型。下列是关于这些个体和食物的已知信息:

鸡肉是一种食物。
苹果是一种食物。
如果X吃了Y,且X活着,则Y是一种食物。
Bill存活。
Bill吃了花生。
John吃所有食物。
Sue吃所有Bill吃的食物。
John喜欢所有食物。

根据以上信息,回答以下选择题:

###问题:
选择题 1:
谁喜欢吃花生?
A. Bill
B. Sue
C. John
D. None of the above

baseline01.ipynb精读

!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-7b-instruct'

这段代码主要是有很多条import语句来引入包和调用大预言模型和线程池一些各种各样的东西。这里我们还是用api模式,先改prompt,以后再进行微调,这里换一个模型试试,之前使用的是通义千问qwen2-7b-instruct,这里我们改成qwen1.5-0.5b-chat(之前的那个跑的token还是挺多的再跑一次就欠费了,我们可以在阿里云Dash scope调用统计里查看自己的api最近tokens的调用量,避免爆不必要的大米)

同时我们可以在管理中心:计费管理里查看免费的大模型来调用:

接着往下看:

dashscope.api_key="sk-"

这里填入自己的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

这里定义了一个retry函数来进行重传,如果请求失败了,这个函数会写入日志里然后尝试重传,提高命中率。

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:
        # print(response)
        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()

这是一个调取通义千问模型的函数。模型返回的回答会存放在response['output']['choices'][0]['message']['content']这里

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}
"""
    # print(prompt)
    return prompt

这里是具体的prompt,我们可以修改优化promt来提高准确率

def extract(input_text):
    ans_pattern = re.compile(r"答案是:(.)", re.S)

    problems = ans_pattern.findall(input_text)
    # print(problems)
    if(problems == ''):
        return 'A'
    return problems[0]

定义了一个抽取函数extract,我们采用正则表达式re.compile方法匹配到答案对应的选项。当我们匹配为空时,我们默认选"A",因为每个题目是一定有选项A的。

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"):
            # print('data',data)
            data = future_data[future][0]
            problem_id = future_data[future][1]
            try:
                res  = future.result()
                extract_response = extract(res)
                # print('res',extract_response)
                data['questions'][problem_id]['answer'] = extract_response
                results.append(data)
                # print('data',data)
                
            except Exception as e:
                logger.error(f"Failed to process text: {data}. Error: {e}")
    
    return results

这里是进行多线程处理,我们定义了一个线程池,选择16线程,选择future_data来储存每个线程的数据,我们把每个问题送入线程池等待处理,使用api_retry,向api_retry传入MODEL_NAME, prompt参数。同时每个线程我们存储对应的json问题数据以及问题序号id,这样我们就能定位出执行的是哪个子问题,用time.sleep(0.6)来控制每0.6秒提交一个任务,防止接口超过并发数。res = future.result()获取每次调用api得到的答案,并将结果存入线程池answei字段

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
    # print(data)
    # 均匀地分成多个数据集
    return_list = process_datas(datas,MODEL_NAME)
    print(len(return_list))
    print("All tasks finished!")
    return return_list

main函数,接收一个测试数据集文件,产生一个结果文件

if __name__ == '__main__':

    a = extract("""根据欧几里得算法,逐步解析计算两个数6和7的最大公约数(gcd)的步骤如下:

1. 判断6和7是否相等:不相等。
2. 判断6和7大小关系,7 > 6,所以用更大的数7减去较小的数6得到结果1。
3. 现在计算6和1的最大公约数。
4. 6 > 1,根据算法用更大的数6减去较小的数1得到结果5。
5. 再计算5和1的最大公约数。
6. 5 > 1,用5减去1得到结果4。
7. 再计算4和1的最大公约数。
8. 4 > 1,用4减去1得到结果3。
9. 再计算3和1的最大公约数。
10. 3 > 1,用3减去1得到结果2。
11. 再计算2和1的最大公约数。
12. 2 > 1,用2减去1得到结果1。
13. 最后计算1和1的最大公约数,两数相等,gcd即为这两个数,也就是1。

因此,6和7的最大公约数是1。

答案是:C.""")

    print(a)
    return_list = main('round1_test_data.jsonl', 'upload.jsonl')

抽取范例参考

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:
        # print('处理的item' ,item)
        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

has_complete_answer函数用来检查是否对问题产生了回答,如果有回答,我们将一个问题背景下的所有问题存入同一个字典,并调用sort函数按id序号排序。如果这种补错机制大家觉得不满意可以再送入多线程函数处理一遍,准确率会更高

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:]))
        
with open('upload.jsonl', 'w') as writer:
    for sample in sorted_data:
        writer.write(json.dumps(sample, ensure_ascii=False))
        writer.write('\n')

最后我们生成答案jsonl文件就可以了

没有机器学习基础和熟练使用python的功底确实到学习中期会有一定难度,后续继续加油!

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值