心法利器
本栏目主要和大家一起讨论近期自己学习的心得和体会,与大家一起成长。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有。
2022年新一版的文章合集已经发布,累计已经60w字了,获取方式看这里:CS的陋室60w字原创算法经验分享-2022版。
往期回顾
心法利器[73] | 科研和落地之间的差别
心法利器[74] | 技术分享中的技术陷阱
心法利器[75] | 相似、搜索和问答对匹配问题的差异
心法利器[76] | 向量检索和字面检索的对比探究
心法利器[77] | 文本分类日常提点技巧
从学生学习到毕业,其实我们接触的很多东西,都是以单个任务的形式去做的,甚至是很多研究,其实都在固话我们的这块的思维,尤其是NLP任务,之前比较多的文本分类、NER等,都是简单的一个任务,然后我们可以用一个简单的方案很好地解决,然而现实情况,我们遇到的可能就不是一个抽象简单的任务了,亦或者我们可能还是类似的问题,但是我们无法通过一个方案很好地处理了,此时,我们需要或者说可以做的,不应该只是换模型、调参数,这里给大家提供一个思路,那就是拆解问题,然后各个击破。
这次先给大家讲拆解的内涵以及几个可以参考的方案。
开始聊之前,先说下我这里对任务拆解概念的理解,有了个共识,应该会让我的解释更好理解。
所谓的任务拆解,其实就是把一个很复杂的任务,拆成一个个小的任务,然后再通过不同手段完成各自的任务,最后合并起来,完成端到端的目标。
之前其实我有写过一篇文章,虽然阅读量并不高,但其实自己觉得自己悟到的这个东西其实很有意义,那就是准召分治(心法利器[15] | 准招分治效果调优方案)。
推荐系统,本质是把合适的内容分发给用户的过程,然而资源众多,我们需要从多角度来考虑用户可能喜欢的东西,而且我们都需要兼顾,例如:
用户最近看过的内容,可能会继续深入研究。(CB,content-base)
一些新的热门的内容,可能用户会感兴趣。(新热)
和这个用户相似的一些用户在关注,那这个用户可能和会喜欢。(协同过滤)
而另一方面,因为推荐位的稀缺性,我们是需要经过筛选最终确保推荐给用户的,让用户能尽可能点击,保证留存,所以必须保证准确。
如此一来,其实我们要同时权衡两个方面,即内容的多样性和推荐内容的精准性,其实就是一个要同时保证准确和召回,如此一来,就产生了一个设计,那就是把整个推荐系统分成了两大块——召回模块和排序模块,召回模块负责分路召回多个符合不同条件的内容进到池子,排序模块负责对这些内容做更加精准的筛选,把用户可能点击的内容筛选出来推送给用户。
沿着这个思路走下去,其实很多场景也用到了类似的思路,例如较为复杂的搜索系统,也是把召回和排序给分开,一方面有向量和字面检索的多路召回,甚至有多模态信息,另一方面把这些内容整合起来做精排。
而我们,其实也有很多场景可以考虑使用,就例如对话系统,一个复杂的对话系统可不止有文本生成的结果,还可以有一些检索式的内容,分多路召回可能可行的答案,然后最终进行决策筛选,也是非常合理,举个现实的例子,之前写过平安的客服对话系统(前沿重器[3] | 平安智能问答系统),召回链路两个,ES的字面检索和孪生网络的向量检索,然后再配合复杂的rank系统来做最终的决策筛选。
之前也有写过一篇文章,这个:心法利器[29] | 把文本分类任务做成一个系统,讲把一个分类问题,当做一个系统来看,其实就是把分类的问题进行了拆解,每个部分都可以由一个小的方案来解决:
简单高频的问题,词典、规则可以快速、灵活的解决,而且高准确。
比较泛化的问题,交给模型解决,模型具有更强的泛化能力。
比较长尾,数据比较少、比较新的部分,放到库里,用检索的方式来解决(最近我看的文章看来,其实就是few shot的思路)。
每个模块有各自负责的任务,只要完成好自己的那个模块,组合起来,其实就能很轻松地解决整个问题。
好了此处我们升级一下视角,从一个更大的分类任务上看,对于一个开放域的搜索系统,各个领域都有个自己的结果呈现方式,此时我们就需要做意图识别,这本质其实还是个分类任务,然而,如果把他们都放在一个很宽的模型里,有影视意图、音乐意图、天气意图、百科意图、新闻意图等,甚至还有没有覆盖或者位置的“其他类”,而这些意图的随着时间的发展很难不改变,可能因为知识的更新、产品概念的迭代、资源的上下线等,而且意图之间也很容易交叉打架,因此很多时候实践上更喜欢把这个其实是多分类的问题拆解为多个二分类的问题,然后对多个结果进行排序的方式。
从拆解为多个二分类这个问题的角度看,其实就是一个横向分解,分成很多个小模块,分别解决“是否是影视”、“是否是音乐”、“是否是天气”的进行,此时每个问题就会变得很简单了,信息的筛选、样本的处理,也会变得成本更低,同时还会有额外的好处,因为各管各的,各自的方案基本独立,所以每个意图的更新迭代其实不会大面积地影响其他意图的判断,不至于去动整个分类模型,因此灵活性有了不小的提升。
当然,在这里我们也能看到准召分治的思路,拆解为多个二分类问题其实是一个召回模块,可能会召回多个意图,此时通过精排和拆选选出最优意图,其实就是一个排序,这个前面说过了,点到为止吧。
对于一个端到端的系统,是可能有很多问题的,而这些问题如果进行合理的拆分,然后各个击破,其实非常合理。
这个想举的例子是纠错。在大部分的比赛和论文里,其实都在讲端到端的方式,用类似机器翻译的模式,是可以得到不错的结果,但是在实际应用上还是会有很多问题的,举个例子:
某些错误随着时间的发展可能变成对的了,例如“耗子尾汁”。
错误的样式多样化,例如同音字等,模型没有这方面的信息也没见过的话就解决不了的。
另一方面,实际上纠错的bad case是多方面的,来看看:
句子就是对的,结果被改了。
句子是错的,但是没有发现。
句子内有错误,但是改的位置不对。
句子内有错,改的位置也是对的,但是没改到正确的那个。
本着这些问题,纠错系统其实会这样搭建,这点和我之前写的pycorrector的源码解析是一致的。NLP.TM[37] | 深入讨论纠错系统
错误检测。判断句子有没有错误以及错在哪个位置。
候选召回。召回可能的正确答案。
候选排序。从召回的答案中选择最合适的结果。
这也是我为什么评价pycorrector的结构设计合理的原因,可能这个工具的效果一般(当然大部分原因我认为是错误场景不同,pycorrector里面的关注的点不见得是那个场景下所需要关注的),但是结构的设计是绝对合理的,这样拆解能让问题被系统的划分,各自负责各自的任务即可,而且每个模块都可以考虑通过多插件的方式来设计解决,这样也就是前面所提到的“横向拆解”了,当然,还能看到“召准分治”的影子。
错误检测。通过ppl检测句子是否通顺,通过序列标注分析上下文前后文的错别字,通过词典或者正则判断某些常见的触发词。
候选召回。文本生成召回,拼音谐音召回,整词替换,映射表。
候选排序。ppl、词汇完整度、覆盖率等构造精排模型或者规则来处理。
如此一来,一个可控、灵活、符合场景需求的纠错系统,就完成了。
所谓的任务拆解,是一种实际场景下常见的用于解决问题的方式,既然一个大问题难解,那就拆成一个一个小问题来处理,这种方式有如下优点:
可解释性强。在某个问题出现的时候,我们可以快速定位到是哪个模块的问题,并进行针对性解决,对于那种直接端到端的模型,这样的可定位和可操作空间其实很小,也很困难。
调整风险小,由于有多个模块的有机组合,因此对里面的部分方案调整,其实对其他模块的影响会很小,尤其是前面提到的横向拆解,拆解后其实多个模块之间是并列的,一个模块的修改甚至是增减,对最终端到端的影响可以说是很小或者是可控的。
问题处理的难度下降,分成小问题后,每个小问题要考虑的难点其实是大大压缩的,例如前面说的把多分类拆成多个二分类,其实在每个二分类下,需要考虑的问题就少很多了,例如在分析天气问题的时候,其实不需要考虑影视和百科的交叉问题了,只需要分别考虑天气和其他意图的问题即可。
场景内的下限其实很高。与拆解方案相对的是端到端的方案,因为拆解方案重在各个击破,所以很快能把很细的问题解决掉,很快就能拿到不错的效果。
效果和问题的把控性强。只要每个模块都解决的还行,最终的效果一般不会太差,不像端到端的方案,其实要崩很容易会崩,而且崩的很摸不着头脑。
但是但是,不是在说这个思路就完美无缺,其实也是优缺点的。
经验上,其实这样的系统是比较缺乏泛化能力的,这里的泛化是指泛用性。就是某个系统迁移到别的系统用,其实是可能效果会大打折扣的,举两个例子,一个是pycorrector在一些系统里效果还行,但是在有些场景就很差,另一个例子是DSTC对话系统竞赛,AB榜对比,B榜排名保持的比较差的,大都是这种拆解类的模型。
拆解意味着精力和资源也被拆解,当模块拆的多了,要干的事其实就会变多,也变得复杂了。
这一期给大家分享的是端到端任务的拆解设计,这是一个算法架构设计里非常重要的一项能力吧,希望能对大家有用。
下一期,会和大家讲一下拆解后的稀释问题,针对拆解这件事,后面会是一个小系列,大概三四篇吧,敬请期待。