Created
Jul 28, 2024 03:49 AM
Favorite
Favorite
Priority
备注
推荐
🌟🌟🌟🌟🌟
类型
模型测试
心法利器
本栏目主要和大家一起讨论近期自己学习的心得和体会。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有
2023年新的文章合集已经发布,获取方式看这里:又添十万字-CS的陋室2023年文章合集来袭,更有历史文章合集,欢迎下载。
往期回顾
文本分类在NLP任务里有多重要我就不多说了,之前我也经常提一些比较常用的文本分类方案,比较容易想到的是从fasttext开始,后续的textcnn、bert等系列方案,然后还有以搜代分(心法利器[60] | 以搜代分的生效机理)和词典(心法利器[41] | 我常说的词典匹配到底怎么做)之类的方案吧,大模型出来后,势必要用大模型来试试看。
叠个甲,本文提出了一种相对简便而且baseline还不低的方案,同时开源了代码,供大家尝试使用,不代表药到病除,具体效果需要结合实际情况进行调优(后面我估计会出一期手把手bad case分析的文章,敬请期待)。
目录:
  • 基本原理
  • 具体实现
  • 效果分析
  • 改进空间
  • 方案机理理解
代码已开源:https://github.com/ZBayes/poc_project/tree/main/llm_classification

基本原理

由于大模型自己具备较强的理解和推理能力,常规的指令大模型都是了解的,因此利用大模型做文本分类更关注下面几个内容:
  • 分类任务的具体目标需要在prompt中体现。
  • 尽可能每个类目的概念都有相对详细的解释,尤其尤其强调类目之间的差别。
而配合in-context learning的思想,比较简洁地使用大模型进行文本分类的prompt应该包含如下成分:
  1. 分类任务的介绍及其需求细节。
  1. 每个类目的概念解释。
  1. 每个类目最好还有些例子(用学术的方法说,就是few-shot吧)。
  1. 需要分类的文本。
但在实际应用过程中,可能会出现类目较多、样本较多的问题,2/3是非常容易让prompt膨胀过长的,然而很长的prompt往往容易让大模型的推理效果下降,里面某些内容要求容易被忽略,因此如果有经过筛选再进入大模型就会变得更方便。因此,前面借助向量检索来缩小范围,然后交给大模型来进行最终的决策。
此时方案就形成了,思路如下。
  • 离线,提前配置好每个类目的概念及其对应的样本。(某种程度上,其实算是一种训练,整个思路其实就跟KNN里的训练是类似的)
  • 在线,先对给定query进行向量召回,然后把召回结果信息交给大模型做最终决策。
这么说比较抽象,这里我给出例子,方便大家理解处理吧。
强调,本方法不对任何模型进行参数更新,都是直接下载开源模型参数直接使用的,这也算是本方案的一大优势吧。

具体实现

代码结构

解释一下:
  • src是核心代码,data内是原始数据和生成的必须数据,config是配置文件,script是必要的批跑脚本。
  • 核心代码内,分成了4个部分,classifier是集成好的分类器,models里面存放的是两个模型类,searcher内是检索模块,utils内就是比较普通的工具函数了。
熟悉我的小伙伴应该有发现,整个项目的结构和之前我写的basic_rag(https://github.com/ZBayes/basic_rag)整个项目的非常相近,仔细想想大家也会理解,本文中提及的大模型文本分类方案,其实就是一种RAG,通过检索查询到用户query接近的样本,然后利用大模型来生成最终的类目,这个就是RAG的含义。有关这块的代码,我分了几期来展开讲解了:
有这个理解,看这个项目的整合就会更加清晰了。

models

本模块使用了两个模型,分别是simcse向量表征模型,以及qwen2-1.5B的大模型基座,此处两者都没有进行额外的训练,参数下载后直接使用。
simcse使用的是https://blog.csdn.net/qq_44193969/article/details/126981581提供的加载方案。
这个模型我在外面多包了一层,方便内部进行模型切换,即有一个vec_model。值得注意的是,此处有一个带有v2的方案,这是之前我写的加速方案,此处只有推理部分,完整原文、加速代码以及具体实验可参考:心法利器[107] onnx和tensorRT的bert加速方案记录,这块并非本文重点,就不赘述了。
另一方面就是千问模型了,此处我也包装了一层方便使用,里面基本没什么复杂的东西,就是跟着官方教程走,然后划分模块单独弄了一波而已。

searcher

检索器和之前的basic_rag类似,低层使用的是FAISS索引工具做向量索引,这里会分3层,分别是index->vec_searcher->searcher,index重在索引的构建,vec_searcher聚焦向量的检索,searcher是综合检索器,理解下来和搜索引擎的3层概念接近,索引-BS(basic search)-AS(advanced search),向量检索支持多种索引构造模式,而向量检索只是整体搜索引擎的一部分而已。现在我开始从内向外展示。
首先是基础的index部分,即基础索引,这里就是直接调的FAISS的接口了,初始化、插入、保存、检索功能都有。
然后是向量检索器vec_searcher。另外需要提醒,此处我是把正排放在这一层了,当然直接放到searcher层也是可以的,因为一套正排背后可能有多套索引或者子检索器。接口上,其实和底层的index几乎是一样的,不过对数据的处理会更精细。
最外层就是检索了,这里的除了检索,前期的向量表征也要在这一步完成,再者召回的粗排,我也写在了这一步。

分类主函数

然后就是分类的主函数了,先放代码再来解释吧。
提一些关键点:
  • 此处需要加载的,是检索器(因为我把向量模型写在检索器里了,所以此处就不需要重复加载,当然写到外面通用化也可以)、Qwen大模型还有一些必要的配置项,注意这里的配置除了检索器、大模型的配置,还有一些prompt相关的配置。
  • data_processing里面都是各种数据处理的脚本了,批量、重复的数据处理,包括一些数据加载啥的,我都扔这个文件里了,本文就不赘述了。
  • 核心流程我写了完整注释,可以直接看,预处理、向量召回、拼prompt并请求大模型。
  • prompt模板写在配置文件里,然后预留好预留位,我这里偷了懒,其实类似<query>之类的东西要写成大写的const方前面的,这些都是预留位,即使placeholder。
  • 向量召回内容的解析到prompt组装,这块的活比较琐碎,需要仔细写,避免出错。
  • 大模型识别非常简单,但是别忘了后面的解析和校验,避免模型出一些奇怪的结果,要结构化最终再来返回结果。

必要脚本

script文件夹下有两个脚本,一个是用来灌数据的脚本build_vec_index.py,一个是用来跑测数据结果的批跑脚本run_toutiao_cases.py,我一一展示。
注释同样写的比较明白了,说白了就是海量数据的预处理后,逐步把数据存入库中,当然这里没忘记把数据按照一定比例分为训练集和测试集(从KNN的角度,入库过程本质就算是一个训练过程了)。大家也可以根据实际业务场景,调整入库的数据策略,例如限定个数等,这个就大家自己写吧。
另一个脚本是批跑脚本,这个并不难看懂,就是一个读数据、预测、计算指标的流程罢了。

数据细节和prompt

此处我用的demo数据是头条的新闻标题分类数据,在这里:https://github.com/aceimnorstuvwxz/toutiao-text-classfication-dataset,很多数据处理的工作基本是从这里来的,数据可以说是量大管饱吧。但其实这个类目体系多少还是有些问题,我这里提一下:
  • 类目是多分类任务,理论上要求类目之间互斥,但是类目体系下重合的还是不少的,如“国际-国际”和别的新闻很容易混淆,还有“财经-财经”和“证券-股票”。
  • 类目还是比较不均匀的,类似“证券-股票”的数据是非常少的,样本少导致前面提到的类目互斥问题更严重。
prompt这块,我给出我设计的prompt。
解析:
  • 开篇给出角色和具体任务。
  • 参考案例对应前面提到的in-context learning,之前有过实验表明,随便给例子和给出和用户query相似的例子相比,后者效果更好,这也是前面要用向量召回的关键原因。
  • 备选类目来自向量召回的结果,结果里有什么类目我们就把哪些放在备选类目里,这样有效缩小类目范围,简化分类问题,这也是前面要先做检索的重要原因,类目多了prompt很长而且大模型识别也没那么准。
  • 类目概念能让大模型更好地理解到类目的概念,对分类肯定是要有收益的。
  • 请注意下是任务相关的约束,例如类目约束、拒识兜底等,是结合大模型的输出结果调校得到的,毕竟要约束类目的规范性、类目个数等信息。

效果分析

很自然的就是要看看效果咋样,我这里用的是头条开源的新闻标题分类数据:https://github.com/aceimnorstuvwxz/toutiao-text-classfication-dataset。
实验
F1-avg
开源项目结果
84%
全量入库
84%
随机9000数据
76%
每个类10条
53%
可以看到,在数据比较完善的情况下,效果还是挺高的,但是随着样本的下降,效果衰减的还是很明显,few-shot的能力体现的并不优秀。
有关这里,做一下预告,后续我会有专门的文章将这个的case分析,用来作为case分析的案例。

改进空间

有关这个数据、问题的改进,我感觉可以以此为例专门讲一下bad case分析怎么做,而且很特异化,我这里就不展开了,后续专门写文章聊。
这里,我专门讲讲这套方案本身可以考虑的空间。
  • 首先是最容易想到的,就是模型更新,向量模型方面,simcse显然不是现在的sota,类似BGE等的方案,都是可以考虑的;大模型这里,是受限于我自己的电脑问题所以用的比较小,更好更大的大模型还是可以做一下实验的。
  • 向量模型会存在两个特殊的极端情况:全都是一个类目,以及召回多个类都没有正确的。对于前者,如果比较信任向量模型,则可以考虑不过大模型了,降低成本;对于后者,需要集中精力优化向量模型,必要时可能就要优化了。
  • 如果向量模型信任感不足,且数据比较足,可以考虑向量模型后再接一个更可靠的交互式相似度模型做精排,然后再进大模型,此时向量召回的数据量可以一定程度提升,精排再来压缩到合适的程度。
  • 入库样本的典型性和类目概念的清晰度、完整性,都对分类效果有很大影响,精雕细刻prompt有很大价值。
  • 如果因为部署成本、耗时等因素,大模型无法上线,完全考虑,这个方案蒸馏一个小模型来做这个事,例如T5。
  • 样本的覆盖率和准确性,对一个系统而言,如果一个案例没见过,那系统大概率就不认识,大模型虽然有较强的泛化能力,但不代表全知全能,尤其是在分类这种边界要求明确,内部信息丰富而又复杂的问题下。

方案机理理解

这块方案探索后再深究,我发现一些比较有意思的理解方式,我在这里逐个解释一下。
  • 概念解释和样本案例,对大模型而言实质都在做一件事——给大模型解释类目的边界概念,早年分类模型需要样本进行训练,本质也是这个目标,只是因为目前大模型的理解和推理能力变强,且有很强的生成能力,因此这个事可以通过prompt来解决。
  • 向量召回在此处的作用,本质是提前筛选更合适的样本和可能性更高的类目,协助大模型更好地理解边界,且从普遍理性而言,更贴切的例子更有利于理解具体概念和含义。
  • 续上,向量召回如果是以搜代分,大模型则可以看做向量召回后的一种精排,一种精筛,前面的向量召回就是召回(缩小范围)+粗排。此时,一整个分类方案就变成一个相对完整地搜索系统,也呼应了我之前有讲过的把一个任务方案当做系统的想法(心法利器[29] | 把文本分类任务做成一个系统,把分类当做一个系统)。
  • 再换个角度,极端的,搜索系统可以看做是一个N分类的问题,这个N等于整个库里的物料条数,只不过类目太多且变更太快,所以才需要做各种召回缩小范围,精排精筛等操作。
  • 另外,向量召回本身召回就比较粗,像我之前在搜索系统的时候说过(前沿重器[49] | 聊聊搜索系统2:常见架构),粗排是判断“像不像”的问题,但是“谁更像”还得再要一个精排,进一步提升准确率,所以两者互相帮助,逻辑自洽没什么毛病。
  • 如果把整套方案当做是搜索,那大模型的生成,这套分类方案就可以看做一种RAG。

方案优缺点

要说一个方案,就要讨论一下这个方案的优缺点,从而方便我们在后续的任务中进行选择。
首先说一下优点。
  • 无训练的高基线。整个项目做下来,不需要进行模型的训练,只需要模型推理的资源,就能到达一个比较高的下限。
  • 少样本的高基线。标注经常是业务场景的一个痛点,现在只需要给些例子和类目的解释,就能快速解决问题,便捷度还是很高的,这个方案会比以搜代分方案要再高一点。
  • 灵活性高。对经常要做类目个数、边界、样本的变更,会更灵活,该类目配置和增删样本就可以解决了,不像分类模型那么死板要重训。
缺点:
  • 上限不会太高,还是不如微调向量模型和大模型。
  • 只能是通用领域的知识,对专业领域还是避免不了的领域知识问题。
  • 对样本数和覆盖率有一定要求。要想效果好,依旧需要更多样本,可以这么理解,从信息传播角度(心法利器[45] | 模型需要的信息提供够了吗),对于没见过的东西,系统不认识是无法做事的,尤其是类似音乐、文学作品之类的信息分类,这套方案的效果甚至不如花点时间总结总结词典然后用词典匹配。
  • 老生常谈的大模型成本和耗时问题。不过如果仅仅是这点,还是可以通过本方案预标注样本后训一个小模型来解决。
心法利器119
搜索150
对话149
自然语言处理161
心法利器 · 目录
上一篇心法利器[113] | RAG结构思考:搜索系统范式和大模型作用压缩
Loading...