小白零基础30分钟速通指南:
Datawhale
参考以上文档,可报名参加上海科学智能研究院举办的第二届世界科学智能大赛逻辑推理赛道:复杂推理能力评估竞赛,并在短时间内拿到第一个自己的分数。本文所使用的代码也可通过上述文档下载。
赛题背景
该比赛聚焦于通过解决复杂的逻辑推理题,测试大型语言模型的逻辑推理能力。这些逻辑题涵盖了多种关系和推理规则,能够全面评估模型的逻辑推理能力。赛题设置上,采用了多样化的逻辑题,覆盖了不同难度的逻辑推理任务,强调了逻辑推理在AI领域的重要性。比赛的研究成果将有助于评估和改进模型的逻辑推理能力。这对于开发更智能、更有效的人工智能系统具有重要意义。同时,大赛希望提供机会给选手学习和应用逻辑推理与自然语言处理的知识,培养跨学科的研究人才。
比赛任务
本次比赛提供基于自然语言的逻辑推理问题,涉及多样的场景,包括关系预测、数值计算、谜题等,期待选手通过分析推理数据,利用机器学习、深度学习算法或者大语言模型,建立预测模型。
任务:构建一个能够完成推理任务的选择模型
-
运用机器学习模型或者深度学习模型解决推理问题。或者利用训练集数据对开源大语言模型进行微调。
传统逻辑推理解决方式
知识表示:奠定推理基石
逻辑推理始于将知识转化为计算机可以理解和操作的形式。这里有几个常用的逻辑系统:
-
一阶逻辑:这是一种强大的语言,用于表达个体、属性及它们之间的关系,如“所有猫都有四条腿”。
-
命题逻辑:简单直观,适用于描述基本的真伪陈述,如“A是真”。
-
模态逻辑:引入了“可能”和“必然”的概念,帮助我们处理不确定性和可能性。
-
描述逻辑:专为构建知识图谱设计,有助于定义概念、类别和它们之间的联系,非常适合语义网。
推理引擎:推动逻辑向前发展
推理引擎是执行逻辑推理的核心组件,它通过不同的方式分析知识并得出结论:
-
前向链式推理:从已知事实出发,一步步应用规则,直至得出结论。
-
后向链式推理:从目标逆向寻找支持其成立的依据,类似侦探破案。
-
溯因推理:在直接证据不足时,通过最合理解释填补空白。
-
非单调推理:随着新信息的加入,允许之前的结论被修正或推翻。
应对不确定性和不完整信息
现实世界的数据往往不完整或模糊,为此,我们采用特殊策略应对:
-
概率逻辑:结合概率理论,为不确定性提供数学基础。
-
模糊逻辑:允许不同程度的真值,超越了非黑即白的二元思维。
-
缺省逻辑:在信息缺失时,基于合理假设做出最佳推测。
算法与优化:提升推理效率
高效的推理需要优化算法,以下是一些常见的技术:
-
单元传播:在处理布尔逻辑问题时,一旦确定某个变量的状态,立即更新所有相关条件。
-
冲突驱动的子句学习(CDCL):从冲突中提炼新规则,避免重复错误。
-
约束传播:通过缩小变量的取值范围,快速排除不可能选项,加速搜索过程
传统机器学习如何进行逻辑推理
-
特征工程: 首先,需要将问题和选项转换为机器可以理解和操作的特征向量。这可能包括将文本问题和答案选项编码为数值向量,比如通过词袋模型(Bag-of-Words)、TF-IDF 或者词嵌入(Word Embeddings)如 Word2Vec 或 GloVe。
-
模型选择: 接下来,选择一个适合分类任务的模型。对于选择题,常见的模型有:
决策树、支持向量机、随机森林、逻辑回归、神经网络等 -
训练模型: 使用已知正确答案的题目作为训练数据,将问题和选项的特征向量输入模型,并标记正确的答案。模型会学习到从特征到正确答案之间的映射关系。
-
预测与评估: 当模型训练完成后,可以使用测试集来评估模型的准确性和泛化能力。测试集应该包含模型未曾见过的问题和选项。
-
解决新问题: 对于新的选择题,模型会接收问题和选项的特征向量作为输入,并输出每个选项的概率或分数,最高分的选项即为模型认为的正确答案。
为了提高模型在逻辑推理任务上的性能,可以尝试以下策略:
-
逻辑规则嵌入:在模型中添加逻辑规则,例如,如果模型是神经网络,可以考虑使用神经符号集成(Neuro-Symbolic Integration)技术,其中逻辑规则被编码为网络的一部分。
-
增强学习:使用增强学习来奖励模型在逻辑上合理的选择,这样模型不仅学习到数据中的模式,还能学会基于逻辑原则做出选择。
-
元学习:让模型学习如何学习,通过在多个相关任务上训练,使模型能够更快地适应新类型的选择题。
深度学习如何进行逻辑推理
深度学习方法在处理逻辑推理类型的选择题时,主要依赖于其强大的模式识别和抽象能力,以及对复杂数据结构的处理能力。以下是深度学习解决这类问题的一般步骤和方法:
-
数据预处理: 将文本数据转换为可以输入到神经网络的格式,常见的做法是使用词嵌入(如Word2Vec、GloVe或FastText)或字符级嵌入,将文本转化为数值向量。
-
模型架构选择: 根据问题的复杂度和数据的特性选择合适的模型。常用的深度学习模型包括:
-
循环神经网络(RNNs): 特别是长短期记忆网络(LSTMs)和门控循环单元(GRUs),它们擅长处理序列数据,能够捕捉到文本中的上下文关系。
-
卷积神经网络(CNNs): 卷积层能够检测局部模式,对于短文本和固定长度的输入有效。
-
变换器(Transformers): 如BERT、RoBERTa等预训练模型,它们利用自注意力机制处理序列数据,能够高效地处理长文本和理解上下文。
-
记忆增强网络: 如记忆网络(Memory Networks)和端到端记忆网络(End-to-End Memory Networks),它们可以在内部存储和检索信息,有助于逻辑推理。
-
-
多选题处理: 将选择题设计为多标签分类问题,其中每个选项都是一个潜在的标签。模型需要预测每个选项的得分或概率,最终选择得分最高的选项作为答案。
-
训练: 利用标注过的数据集进行训练,目标是最小化损失函数,通常是交叉熵损失,以提高模型在预测正确答案时的准确性。
-
推理阶段: 在测试或应用阶段,模型接收新的问题和选项,将其转换为相应的向量表示,然后通过模型进行预测,得到每个选项的得分或概率分布,最后选择得分最高的选项作为答案。
-
后处理和解释: 可能需要额外的步骤来解释模型的决策,例如通过注意力权重来了解模型在做决策时关注了哪些部分的文本。
-
集成学习: 结合多个模型的预测结果,通过投票或加权平均的方式提高最终预测的准确率。
-
持续学习和调整: 如果可用,可以使用增量学习或在线学习方法,使模型能够随着更多数据的到来而不断改进。
深度学习模型的一个关键优势在于它们能够自动学习特征表示,不需要人工进行特征工程,这对于逻辑推理问题尤为重要,因为这些问题可能涉及到复杂的语言模式和隐含的逻辑关系。此外,预训练模型(如BERT)的出现使得模型能够在大量未标记文本上进行预训练,然后再针对具体任务进行微调,这种迁移学习的能力极大地提高了模型的性能和泛化能力。
Baseline解读
Baseline代码总体较长,这里选出重点模块进行解读。
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
这段代码定义了一个API重试函数,如果调用call_qwen_api失败则继续尝试调用该函数,最多调用5次
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()
这段代码调用名为dashscope的API服务,通过HTTP传输JSON格式的数据来进行模型推理,并返回处理结果。具体来说,这段代码执行以下步骤:
-
定义输入消息:将用户的查询(
query
)封装成一个消息字典,包含角色(role
)和内容(content
)。 -
调用API:使用
dashscope.Generation.call
方法,向指定的模型(MODEL_NAME
)发送消息进行推理,并设置结果格式为消息格式(result_format='message'
)。 -
处理响应:
- 如果响应状态码是
HTTPStatus.OK
,即请求成功,提取并返回推理结果。 - 如果请求失败,打印错误信息并抛出异常。
- 如果响应状态码是
# 这里定义了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
这段代码定义了一个函数 get_prompt
,其作用是生成一个用于逻辑推理问题的提示模板。具体来说,这个函数接受三个参数:problem
(问题描述)、question
(具体问题)和 options
(选项列表),然后将这些参数格式化成一个完整的提示文本,以便用于后续的逻辑推理任务。
# 这里使用extract抽取模获得抽取的结果
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
函数用于从输入文本中提取答案。它使用正则表达式来查找格式为“答案是:X”的答案,其中 X
是单个字符。如果未找到答案,则默认返回 '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
process_datas
函数并行处理一组数据,其中每条数据通过调用 api_retry
获取结果并提取答案。
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
函数中,目标是读取输入文件(ifn
),处理数据,并将处理结果保存到一个列表中。然后,该函数返回这个列表。
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)
evaluate
函数用于评估模型的预测结果,它从一个文件中读取数据,计算模型预测的正确率,并输出相关统计信息。
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
和 filter_problems
。第一个函数检查问题集合中是否每个问题都包含答案,第二个函数则根据问题的完整答案来筛选数据。
总的来说,Baseline还有很多可修改之处,但作为提供给初学者学习的材料,它足够通俗易懂。在了解以上内容过后,就算正式入门AI逻辑推理了。