返回列表

挑战组获奖方案|金融工具学习赛题一等奖(文末有代码)

AFAC2024挑战组-赛题一:金融工具学习 | 532193

开始: 2024-07-23 结束: 2024-07-26 金融文本智能 AI大模型赛
挑战组获奖方案|金融工具学习赛题一等奖

挑战组获奖方案|金融工具学习赛题一等奖(文末有代码)

作者: 彭欣怡—华东师大;马千里—虾皮;戎妍—港科广
说在前面: 在当今信息技术迅猛发展的背景下,大模型技术已经成为推动人工智能领域进步的重要力量。前段时间备受瞩目的AFAC赛题聚焦于金融对话领域,旨在推动大模型在金融场景中的应用和落地。我们有幸脱颖而出,拿到冠军。技术之路永无止境,每一次的成功都是新的起点,所以在这里分享我们团队在其中的一些想法,期望能和大家共同进步。

赛题背景

参赛者需要根据用户的自然语言查询(用户请求),从给定的API集合中筛选出合适的API列表,设计出正确的API调用逻辑(模型回答),并生成准确的答案。

数据案例解析

下面先给出一个样本案例,帮助大家理解比赛的目标。

  • 用户请求 (input)
    我一年前购买了2000元浙商智选先锋A基金,并且在6个月前追加了3000元,我到现在总的收益是多少?
  • 模型回答 (output)
    {
        "relevant APIs": [{
            "api_id": "0",
            "api_name": "查询代码",
            "required_parameters": [["浙商智选先锋一年持有期混合型证券投资基金A类"]],
            "rely_apis": [],
            "tool_name": "基金查询"
        }, {
            "api_id": "1",
            "api_name": "查询近期收益率",
            "required_parameters": ["api_0的结果", "1年"],
            "rely_apis": ["0"],
            "tool_name": "基金查询"
        }, {
            "api_id": "2",
            "api_name": "查询近期收益率",
            "required_parameters": ["api_0的结果", "6个月"],
            "rely_apis": ["0"],
            "tool_name": "基金查询"
        }, {
            "api_id": "3",
            "api_name": "乘法计算",
            "required_parameters": ["api_1的结果", "2000"],
            "rely_apis": ["1"],
            "tool_name": "数值计算"
        }, {
            "api_id": "4",
            "api_name": "乘法计算",
            "required_parameters": ["api_2的结果", "3000"],
            "rely_apis": ["2"],
            "tool_name": "数值计算"
        }, {
            "api_id": "5",
            "api_name": "加法计算",
            "required_parameters": ["api_3的结果", "api_4的结果"],
            "rely_apis": ["3", "4"],
            "tool_name": "数值计算"
        }],
        "result": ["api_5的结果"]
    }
  • 模型回答解析
    这段API链的意思是:首先提取用户提到的基金名(产品),用“查询代码”提取该产品。然后,根据用户的需求,用“查询近期收益率”提取该产品的近期信息,如一年收益率。最后,用四则运算组合几个数值结果,拿到用户想要的信息并返回。

评价指标

  • 评价方法 采用API运行通过率作为评价标准,官网给出的具体计算方法如下:
    • 最终得分 = 主指标*0.8 + 副指标*0.2
    • 执行准确率(主指标):执行后得到结果的准确率
    • 逻辑准确率(副指标):各种出入参的逻辑准确率
  • 抖个激灵 为了简化评测逻辑呵呵,实际是用字符串提取方法进行静态评估的,但我们没从中找到空子[旺柴]。

赛题理解

根据对样本进行分析,再结合一些经验,我们可以总结出比赛的三个要点:

  1. 标准产品名提取 查询类别API需要传入标准产品名(例如“浦发银行”)才能执行,但query中只有缩写(例如“浦发最近一月的最高价是多少”),若不加入提示,大模型很难还原真实的标准名称。
  2. 用户意图识别 根据所需要调用的API,可以把用户意图分为4类:基金查询、股票查询、条件选基、条件选股。不同的意图用到的API有较大不同,规范也有差异。
  3. 相关例子召回 若要让大模型理解各种API的用法,需要给出相似的例子,让模型从例子中理解。例子与目标query的相关性极大程度地影响了模型表现,若只给标准例子,模型难以学会API的规范用法。

具体样例

用户查询 产品名 用户意图 部分标签
我打算用100万元买三羊马的股票,如果按照三羊马的最高价来计算,我能买多少股呢 三羊马 股票查询 {"API_id": "0", "API_name": "查询代码", "required_parameters": [["三羊马"]], "rely_APIs": [], "tool_name": "股票查询"}
上周结束的时候,有哪些股票的收盘价是不超过0.47元的呢? / 条件选股 {"API_id": "0", "tool_name": "条件选股", "API_name": "查询收盘价", "required_parameters": ["小于", "0.47", "上周"], "rely_APIs": []}
我今年5月1日用5000块买的国金金腾通C,如果我现在全部卖出,交易费用是多少 国金金腾通货币市场证券投资基金C类 基金查询 {"API_id": "0", "API_name": "查询代码", "required_parameters": [["国金金腾通货币市场证券投资基金C类"]], "rely_APIs": [], "tool_name": "基金查询"}
请问有哪些基金最近一个月中最长的解套期是20天,并且在今年已经创下新高达70次的? / 条件选基 {"API_id": "0", "tool_name": "条件选基", "API_name": "查询近期最长解套天数", "required_parameters": ["等于", "20.0", "1个月"], "rely_APIs": []}

算法实现

分析完赛题和数据,接下来我们将深入探讨实现方案。

框架介绍

首先给出我们的整体框架。根据实现流程,我们可以将框架分为三个部分:

  1. Prompt构造 我们设计了三个核心子模块,以帮助模型规范其输出。这三个子模块分别是产品召回、意图识别和例子召回。这些模块相互配合,确保模型能够更准确地理解和执行任务。
  2. 训练阶段 为了获得多样化的结果,我们对Prompt中的内容进行了部分删减,并调整了few-shot逻辑,从而得到五个不同的Lora微调模型。这种多样化的训练策略有助于模型在不同场景下的表现更加全面和可靠。
  3. 推理阶段 除了句子级别的多数投票外,我们还进行了许多其他尝试。同时,我们还实施了一些针对赛题的后处理技巧,如产品名称投票和错误API过滤,这些技巧对提升最终结果也有一定的帮助。同时,为了确保模型严格按照规定生成结果,我们的最终方案是使用五套差异化的Prompt对GLM进行Lora微调,然后对生成的五套执行语句进行句子级别的多数投票,以得出最终结果。

Prompt设计技巧

大模型比赛中最关键的就是prompt设计。这一节我们将深入探讨如何利用意图识别和few-shot技术来设计高质量的Prompt。

需要强调的是,我们采用技术的灵感源自统计学、传统机器学习和推荐系统的相关研究。尽管这些技术在原领域已经得到了广泛应用,但我们通过调整与优化,将它们迁移至大模型场景,并取得了效果。

1. 产品名召回-Text Match

  • 思路 因为用户的询问往往不够精准,比如用户可能会简称“平安银行”为“平安”,但系统只能识别标准名称“平安银行”。所以我们设计了产品召回模块,其核心任务是针对用户查询,找到对应的标准产品名称。
  • 实现 具体来说,我们设计了四种字符串匹配算法,包括最长公共子序列匹配、最长公共子串匹配,以及我们设计的一个变体算法(公共子串占总字符串比例的算法)。每个算法找到多个候选结果,然后拼接到一起,再让大模型从中选出最符合意图的一个作为最终结果。
  • 细节 之所以需要召回多个结果,是因为整个系统中涉及的产品总数超过三万个,单靠简单的匹配算法很难精准捕捉到用户真正提及的对象。我们的思路是先用这些算法召回十几个可能正确的候选产品,接着再让大模型从中挑选。
  • 示例代码
    def match_product(query: str, candidates: list):
        # query: 用户请求, e.g. 三羊马今日股价是多少
        # candidates: 代表所有可能的产品, e.g. ['三羊马','中国平安','浦发银行', ...]
        products = difflib.get_close_matches(query, candidates, n = 200, cutoff=0.01)
        pro = []
        # 多流量召回
        pro += products[:1]
        products = sorted(products[1:], key=lambda x: len(LCS(x, query)) / len(x), reverse=True)
        pro += products[:4]
        products = sorted(products[4:], key=lambda x: len(LCS(x, query)), reverse=True)
        pro += products[:4]
        products = sorted(products[4:], key=lambda x: len(LSC(x, query)), reverse=True)
        pro += products[:4]
        # 重排
        return sorted(pro, key=lambda x: len(LCS(translate(x), query)) / len(translate(x)), reverse=True)

2. 意图识别-KNN

  • 思路 具有相同意图的查询往往具有许多共性。在API链中,我们将第一条API定义为用户意图识别的节点。根据分析,用户查询主要有四类意图:股票查询、基金查询、条件选股以及条件选基。
  • 实现 这里的意图识别方法不同于传统的NLP任务,我们有大量的同源训练样本。所以我们使用了KNN算法来处理用户请求,取最邻近的20个样本的意图的众数作为该请求的最终意图。
  • 细节 为了找到最邻近的20个样本,我们选择了四种方法:编辑距离、SimCSE嵌入相似度、E5嵌入相似度和Bge嵌入相似度。每种方法分别召回5个最邻近的样本,最终将这20个邻居样本混合起来,通过投票来确定用户请求的意图。
  • 参考 更多的文本嵌入方法可以参考以下链接:
    https://huggingface.co/spaces/mteb/leaderboard
  • 示例代码
    def get_intention(row):
        query = row['query']
        # intentions 总共四类意图
        intentions = ['基金查询', '股票查询', '条件选基', '条件选股']
        # KNN选取每种算法的前五个邻居
        for i in range(5):
            intentions.append(train.iloc[row[f'edit_{i}']]['label'])
            intentions.append(train.iloc[row[f'sim_{i}']]['label'])
            intentions.append(train.iloc[row[f'm3e_{i}']]['label'])
            intentions.append(train.iloc[row[f'e5_{i}']]['label'])
        # 意图修正
        if ('股票' in query and not any([x in query for x in ['型', 'A', 'B', 'C', 'D', 'E', 'F']])):
            intentions = [x for x in intentions if '股' in x]
        elif any([x in query for x in stock_word]):
            intentions = [x for x in intentions if '股' in x]
        elif any([x in query for x in fund_word]):
            intentions = [x for x in intentions if '基' in x]
        if any([x in query for x in search_word]) and '(' not in query and '(' not in query:
            intentions = [x for x in intentions if '查询' in x]
        elif any([x in query for x in select_word]):
            intentions = [x for x in intentions if '选' in x]
        # 用邻居意图的mode代替
        return Counter(intentions).most_common(1)[0][0]

3. 例子召回-RAG

  • 思路 在大模型的提示词中加入少量示例能够帮助模型更好地理解任务,few-shot是大模型比赛中非常常用的技巧。
  • 实现 我们采用了与意图识别类似的思路来实现few-shot。首先,使用多种嵌入算法找到与当前查询相似的邻居并将其对应的问答作为例子,放入Prompt中作为few-shot提示。我们最终选取了三套嵌入方法,并使用蛇形召回算法来逐步从每个模型中提取结果,最终排出了六个示例。
  • 参考 更多的prompt构造技术可以参考以下链接:
    https://www.Promptingguide.ai/zh/techniques/consistency
  • few-shot示例
    Q1: Shawn有五个玩具。圣诞节,他从他的父母那里得到了两个玩具。他现在有多少个玩具?
    A1: 他有5个玩具。他从妈妈那里得到了2个,所以在那之后他有5 + 2 = 7个玩具。然后他从爸爸那里得到了2个,所以总共他有7 + 2 = 9个玩具。答案是9。
    Q2: 服务器房间里有9台计算机。从周一到周四,每天都会安装5台计算机。现在服务器房间里有多少台计算机?
    A2: 从周一到周四有4天。每天都添加了5

(注:原文内容至此结束)