复杂推理能力评估竞赛学习笔记(1)- 题目分析和Baseline

522 阅读11分钟

#AI夏令营 #Datawhale

一.前言

image.png 最近在参加第二届世界科学智能大赛逻辑推理赛道:复杂推理能力评估的比赛,这项比赛是进行LLM的逻辑推理研究和我最近在研究和学习LLM Agent内容比较相似,所以感兴趣就报名参赛了,跟着Datawhale组织的夏令营活动一起,接下来我会随着比赛进程,持续更新几篇学习笔记✨,记录参加该比赛的学习心得。

第一篇我们就先来研究一下本次比赛的题目和跑通Datawhale提供的Baseline

二.题目分析

赛题背景
该比赛聚焦于通过解决复杂的逻辑推理题,测试大型语言模型的逻辑推理能力。这些逻辑题涵盖了多种关系和推理规则,能够全面评估模型的逻辑推理能力。赛题设置上,采用了多样化的逻辑题,覆盖了不同难度的逻辑推理任务,强调了逻辑推理在AI领域的重要性。比赛的研究成果将有助于评估和改进模型的逻辑推理能力。这对于开发更智能、更有效的人工智能系统具有重要意义。同时,大赛希望提供机会给选手学习和应用逻辑推理与自然语言处理的知识,培养跨学科的研究人才。

背景知识
本比赛旨在测试参与者的逻辑推理和问题解决能力。参与者将面对一系列复杂的逻辑谜题,涵盖多个领域的推理挑战。比赛内容涉及以下几个方面的背景知识:
逻辑推理概念[1]:理解基本的逻辑推理概念,包括命题逻辑和谓词逻辑。熟悉如何从一组假设中得出结论,并能够识别和运用逻辑关系和规则。
结构化问题解决:

  • 事实和规则的应用:了解如何在复杂问题中应用已知事实和规则,进行有条理的推理和解题。
  • 关系和属性的推理:掌握推理中常见的关系,如相邻、位置、顺序等,以及如何通过这些关系进行推理。
  • 常见逻辑谜题类型:熟悉各类逻辑谜题,如斑马问题、房间问题等。这些谜题通常涉及多种关系和属性的推理,需要通过分析和组合已知信息来解决问题。
    自然语言模型:
  • 知识抽取方法:可能会使用到相关知识抽取技术,比如命名实体识别[2]、关系抽取[3]等。
  • 生成模型方法:可能会使用到相关生成模型,比如语言模型[4]等。

这是官网提供的赛题介绍,可以本次比赛主要就是运用大模型进行逻辑推理,考验通过设计一个算法或者work flow来提升大模型的逻辑推理能力

测试集例子分析

{
	'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'
		}
	]
}

看一下官网提供的测试集数据例子,通过分析可以看到problem是推理问题的背景,questions为具体的子问题。questions是一个子问题列表,每个子问题包括optionsanswer字段,其中options是一个列表,包含具体的选项,按照ABCDE顺序排列,answer是标准答案。
简单来说,就是根据题目提供背景,引导到模型进行逻辑推理,根据每个问题从选项中选择出正确答案

实验环境分析

本次比赛提供基于自然语言的逻辑推理问题,涉及多样的场景,包括关系预测、数值计算、谜题等,期待选手通过分析推理数据,利用机器学习、深度学习算法或者大语言模型,建立预测模型。

初赛

任务:构建一个能够完成推理任务的选择模型

  • 运用机器学习模型或者深度学习模型解决推理问题。或者利用训练集数据对开源大语言模型进行微调。

复赛

任务:构建一个能够完成推理任务的选择模型

  • 运用机器学习模型或者深度学习模型解决推理问题。或者利用训练集数据对开源大语言模型进行微调。

数据集介绍

特别说明:参赛选手可以使用开源数据,若使用了开源数据,需要在代码审核阶段提供数据来源以及相关说明

复赛评测相关说明

1)本赛道复赛线上推理与评测时间限制为3个小时,镜像运行服务器为单卡V100(32G显存版本)
2)复赛环境网络配置:复赛采用镜像方式进行评测,容器中无可用网络,无法进行下载安装,外部API调用等操作。请选手提前将所需软件环境,数据,模型等装进镜像,不要在镜像中写入包含网络操作的代码,否则镜像运行会卡死或中断
3)复赛环境及服务器情况,将根据实际情况持续更新说明,请持续关注大赛选手群及大赛官网信息

根据比赛要求,我们可以使用机器学习模型、深度学习模型和开源大语言模型进行微调,但我们选择路线基本上是采用开源大模型来进行实现了。
同时,对我们所使用的显存有限制<32G,那基本上就圈定在 7B 8B这类小模型上了。目前比较优秀模型主要有这些。

类别大模型分类能力信息抽取阅读理解数据分析总分排名
开源Llama-3.1-8B-Instruct-----1
开源glm-4-9b-chat9082.290.082.086.12
开源Qwen2-7B-Instruct8983.786.775.383.73
开源Llama-3-8B-Instruct8674.080.090.082.54

注:Llama-3.1最新刚发布,虽然对比评测还没有出来,但是按照当前大家放出来的表现来看,应该是要比GLM4-9b要强,暂且排在第一

赛事还提供了500条微调数据,可以用于开源模型的微调。

以上就是我梳理的赛题相关说明,接下来我们跑一下Baseline

三.运行Baseline

Datawhale提供了一个基础Baseline,相关介绍地址
linklearner.com/activity/12…

image.png

根据官方文档介绍Step1~Step5步骤,就能跑完Baseline。官方用的是魔搭环境,有个问题就是服务器一个小时不操作就会暂停服务器并回收资源,如果一不小心就忘记查看运行结果,就白跑了(我就是😂),所以建议还是下载到本地自己电脑上跑更好。

根据操作,其实就只需要配置阿里云大模型密钥,运行即可。大约30分钟这样就能跑完。 将结果文件upload.jsonl提交至赛事平台,拿下第一分

image.png

分数很低(意料之中😄),我们来一起分析一下Baseline的源码

MODEL_NAME = 'qwen2-7b-instruct'

首先这个Baseline使用模型是 qwen2-7b-instruct 这个模型,在我们开源小模型排名第三

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

这两个函数是常规的模型API调用封装

# 这里定义了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}
"""
    # print(prompt)
    return prompt

这个是我们的核心代码,定义了推理Prompt,这个Prompt结构比较简单,定义了角色和将题目的信息提供给了Prompt

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]
    
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

采用多线程的方式去读取测试集的数据,并且通过extract来正则匹配模型输出答案,这里会有个问题,就是该extract可能会匹配不到,因为模型输出的随机性,不一定按照我们正则模版来输出。

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

这里就是过滤掉没有完整答案的题目

return_list
return_list = filter_problems(return_list)
sorted_data = sorted(return_list, key=lambda x: int(str(x['id'])[-3:]))
print(sorted_data)
def find_missing_ids(dict_list):
    # 提取所有序号
    extracted_ids = {int(d['id'][-3:]) for d in dict_list}
    
    # 创建0-500的序号集合
    all_ids = set(range(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:]))

with open('upload.jsonl', 'w') as writer:
    for sample in sorted_data:
        writer.write(json.dumps(sample, ensure_ascii=False))
        writer.write('\n')

这里首选将列表根据题目标号进行排序,同时找出缺少完整答案的题目序号。
将针对缺少完整答案的题目统一答案赋值给A,最后将完整答案写入提交文件upload.jsonl

以上是我们针对Baseline代码的解读,接下来我们分析一下这个Baseline的可改进的地方和下一步的探索方向

四.下一步计划

Baseline不足之处

  1. 该Baseline只是用一个简单的Prompt去解答题目,对于目前LLM小模型来说,推理能力不足是一个瓶颈,需要通过增强Prompt的方式去提升推理能力。
  2. 该Prompt未定义输出格式,模型输出的格式参差不齐,通过正则表达式去过滤,容易出错

通过这两点分析,如何提高模型的推理能力是重点,根据我的经验和目前流行的做法我们来一起看一下

如何提高推理能力

  1. 选择能力更强的基础模型,比如llama3.1、GLM4-9B
  2. 采用思维链(COT)的Prompt方案,通过few-shot等方式让模型进行思考,一步步进行推理,不要一步得出答案,提高推理准确性
  3. 引入反思(Reflection)机制,通过多Agents协同方式,一个Agent输出推理过程和答案,一个Agent进行反思这个推理过程是否正确,提出改进意见,循环迭代,直至满意的推理和答案

五.总结

以上就是我针对第二届世界科学智能大赛逻辑推理赛道:复杂推理能力评估的比赛第一篇笔记文章,接下来让开始改造行动吧,正确高点的分数!

欢迎关注持续关注我~

d094193215b948299ee0a9bc5b062e8b~tplv-3jr8j4ixpe-aigc_resize_2048_2048.webp