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应该包含如下成分:
- 分类任务的介绍及其需求细节。
- 每个类目的概念解释。
- 每个类目最好还有些例子(用学术的方法说,就是few-shot吧)。
- 需要分类的文本。
但在实际应用过程中,可能会出现类目较多、样本较多的问题,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来解决。
- 向量召回在此处的作用,本质是提前筛选更合适的样本和可能性更高的类目,协助大模型更好地理解边界,且从普遍理性而言,更贴切的例子更有利于理解具体概念和含义。
- 向量召回换个角度,这里就是我之前说的“以搜代分”的操作(心法利器[60] | 以搜代分的生效机理),通过比对相似样本达成分类目标,也可以理解为KNN分类。
- 续上,向量召回如果是以搜代分,大模型则可以看做向量召回后的一种精排,一种精筛,前面的向量召回就是召回(缩小范围)+粗排。此时,一整个分类方案就变成一个相对完整地搜索系统,也呼应了我之前有讲过的把一个任务方案当做系统的想法(心法利器[29] | 把文本分类任务做成一个系统,把分类当做一个系统)。
- 再换个角度,极端的,搜索系统可以看做是一个N分类的问题,这个N等于整个库里的物料条数,只不过类目太多且变更太快,所以才需要做各种召回缩小范围,精排精筛等操作。
- 另外,向量召回本身召回就比较粗,像我之前在搜索系统的时候说过(前沿重器[49] | 聊聊搜索系统2:常见架构),粗排是判断“像不像”的问题,但是“谁更像”还得再要一个精排,进一步提升准确率,所以两者互相帮助,逻辑自洽没什么毛病。
- 如果把整套方案当做是搜索,那大模型的生成,这套分类方案就可以看做一种RAG。
方案优缺点
要说一个方案,就要讨论一下这个方案的优缺点,从而方便我们在后续的任务中进行选择。
首先说一下优点。
- 无训练的高基线。整个项目做下来,不需要进行模型的训练,只需要模型推理的资源,就能到达一个比较高的下限。
- 少样本的高基线。标注经常是业务场景的一个痛点,现在只需要给些例子和类目的解释,就能快速解决问题,便捷度还是很高的,这个方案会比以搜代分方案要再高一点。
- 灵活性高。对经常要做类目个数、边界、样本的变更,会更灵活,该类目配置和增删样本就可以解决了,不像分类模型那么死板要重训。
缺点:
- 上限不会太高,还是不如微调向量模型和大模型。
- 只能是通用领域的知识,对专业领域还是避免不了的领域知识问题。
- 对样本数和覆盖率有一定要求。要想效果好,依旧需要更多样本,可以这么理解,从信息传播角度(心法利器[45] | 模型需要的信息提供够了吗),对于没见过的东西,系统不认识是无法做事的,尤其是类似音乐、文学作品之类的信息分类,这套方案的效果甚至不如花点时间总结总结词典然后用词典匹配。
- 老生常谈的大模型成本和耗时问题。不过如果仅仅是这点,还是可以通过本方案预标注样本后训一个小模型来解决。
心法利器119
搜索150
对话149
自然语言处理161
心法利器 · 目录
上一篇心法利器[113] | RAG结构思考:搜索系统范式和大模型作用压缩