Skip to main content

11 posts tagged with "ai"

View All Tags

paper-reading, code&rl方向

· 27 min read
ayanami

Effi-code: Unleashing code efficiency in language modelsSWIFTCODER: Enhancing Code Generation in Large Language Models through Efficiency-Aware Fine-tuning

问题:以前的方法主要关注正确性忽略效率(effibench,gpt4 代码执行时间是标准解决方案的1.69与45.49倍(avg, worst))

衡量效率:本地测量执行时间和内存

具体来说是三个指标:

  • 执行时间ET
  • 最大内存使用量MU
  • 总内存使用量TMU

这篇论文并没有用RL的方法去激发LLM生成更高效率代码的能力,而是”优化“训练数据集的代码效率,并证明了训练集代码效率高也会让LLM生成更高效率的代码(ET 的相关性为 0.972,MU 的相关性为 0.950,TMU 的相关性为 0.986)

Refer to caption

效果:qwen2.5-coder-7b-instruct pass@1 44.8 -> 57.7,正确任务的执行时间减少48.4%

方法:构建代码生成数据集,进行微调

Refer to caption

具体来说,先拉下来开源数据集,过滤一遍后,直接让更强的LLM生成更好的解决方案,然后本地跑一遍得到效率

不同效率的代码示例 Refer to caption

文章认为的效果提升来源:

  1. 多语言数据集
  2. 数据准备阶段过滤充分
  3. 数据量(有超过 70,000 个训练示例,比先前的mercury 1.8k要大很多)

简评:大体上感觉是工作量密集型的工作,比如finetune多个llm和在多个数据集上进行相关评测,但方法上并没有创新之感,洗的数据是高效率代码的自然输出的结果效率也会变高,并不令人感觉新奇,只是讲好了我们需要同时看重代码质量(这里是效率)这一指标的故事


EffiBench: Benchmarking the Efficiency of Automatically Generated Code

问题:现在衡量代码正确性的文章已经有很多了,但是兼顾正确和效率的相对少

如果要考虑效率的话,一个问题是原先的代码数据集的任务太简单了,很难区分效率;同时很多任务也不是效率密集型的,并且效率相关的测试也不够

数据集构建:leetcode

“标准解决方案”: stackoverflow最多star/leetcode top answer

测试用例:LLM生成

简评:典型的benchmark工作


Diversity-Aware Policy Optimization for Large Language Model Reasoning

问题:diversity在reasoning能力中扮演重要角色,但缺乏定量的研究

动机:传统RL认为,多样性有助于策略探索(例如,SAC等算法),帮助跳出局部最优、加速训练收敛,但对于LLM呢?

方法:

直接增加熵,长度bias, 较长的响应 -> 引入token level diversity

tradeoff 质量和多样性 -> 仅对正样本用多样性增强,确保以性能标准为主导

贡献:

  • 多样性的Potential@k指标和LLM reasoning存在正相关关系
  • token-level diversity objective, selectively applied to positive samples

奖励:和R1一致,acc reward和format reward,前者和ground truth 比较,后者让答案以 \boxed{}格式呈现

多样性度量:response中不同方程的比例

DivEqu:=1NkUADivEqu := \frac{1}{N} \sum_k \frac{U}{A}

其中,U是k个采样中的独立方程数量,A是总方程数量

Potential@k: 衡量模型在第一次失败后,在k次(k=16 in paper)内纠正答案的能力

Potential@k:=Pass@k(1Pass@1)1Pass@1Potential@k := \frac{\sum Pass@k (1 - Pass@1)}{ \sum 1 - Pass@1}

(为啥定义这样一个指标?这个分子分母分别求和挺怪异的,也不是类似条件概率的算法)

结果:对于推理能力有限的LLM(Pass@1<0.4) 多样性和Potential@k没什么关系,但对于更好的LLM,就有明显的正相关关系

Refer to caption

定义的token-level熵

image-20250817161107663

在实测中,作者发现直接把这个熵带入训练会增强错误样本的多样性,相当于对错误样本做增强,因此打了只对正样本做的补丁

image-20250817161240377

而对这个式子求导能直观感受到多样性的部分

image-20250817161427155

对于大多数token, 采样概率π\pi的值都是小于e1e^{-1}的,则前一个乘项小于0 ,熵的梯度和采样概率的梯度成正相关,熵增有利于对稀有Token的采样

image-20250817161718666

实际取 λ=0.01\lambda=0.01

简评:

动机非常清晰,实验也比较充分,展示了虽然是 well-known 的需要基模能力达标多样性才有意义的结论。

对于LLM优化目标的改造的说明是合理的。理论说法是GRPO带来的更新依赖于组内样本的差异,(因为是用std/mean来计算优势函数A),所以增强多样性能避免优势消失带来的一些问题,本质上是在避免 rmeanstd\frac{r - mean}{std} 里面的stdstd 过小导致不稳定的问题

只对正样本应用多样性损失的trick也是有意义的。

但衡量多样性的时候比较草率,首先是局限在数学范围,其次感觉 方程多样性 != 解法多样性,所以多样性指标总感觉欠说服力。

他们自己也在文章中说:"许多现实世界的应用需要用户意图的多样性(例如,需要数学问题的代数和算术解,或者生成具有不同算法方法的代码), 这样的多样性不等于token level的多样性"


Beyond the 80/20 Rule: High-Entropy Minority Tokens Drive Effective Reinforcement Learning for LLM Reasoning

Refer to caption

问题: 现有RL for LLM算法对不同的token一视同仁,没有考虑token自身的异构性

观察: 低熵token主要决定语言结构,高熵token则作为关键的决策点。手动调整forking token的熵,适度增加这些token的熵可以显著提升推理性能,降低熵会导致性能下降。

仅保留20% token的策略梯度更新,剩下的mask掉,性能上能和全量媲美甚至超越,在RL过程中,只有一小部分高熵 token 对探索有实际贡献,而其他 token 则可能中性甚至有害

20%是实验得到的最优比例

另一个发现是32B的性能提升大于14B大于8B

熵定义:

image-20250817165117549

一个很直观的分布图,高熵Token基本是重要的转折词,而低熵token是一些前后缀等

Refer to caption

另一个稍微不直观的图

可以得到的结论是高熵token配合高温度能够得到好性能,而低熵token在不同温度下都不太影响最终效果

Refer to caption

作者还发现,RLVR的过程基本就是这些高熵token的熵改变的过程,低熵Token的熵相对稳定,初始熵较高的 token 在 RLVR 后往往会经历更大的熵增。

而只对高熵Token进行RLVR能得到更好的效果

Refer to caption

甚至作者在OOD数据(代码数据)上进行评测,发现高熵的token选择后,泛化性也更好

Refer to caption

讨论:

  1. RL倾向于保持高熵令牌的熵,而SFT倾向于将输出推向one-hot,降低熵,作者表示这可能是RL更能泛化而SFT容易记忆、难以泛化的原因
  2. 传统RL假设一整条轨迹上的熵是接近均匀分布的,这对LLM不成立
  3. LLM RL中,之前常用的熵损失鼓励探索可能未必适用,因为可以是低熵token的熵增也可能是高熵token的,而DAPO的clip higher能筛选出高熵token,即重要性比率ratio(π/πold\pi/\pi_{old})更高的token通常对应高熵token,这呼应了前文中,RLVF对高熵token的熵值有较大改变

简评:开始的手动把熵调高感觉等价于把奖励调高等价于数据增强,作为一种RL trick细想并不惊奇,是稀有样本情况下的常用技术

从这个角度继续往下想,如何理解这个多数token对训练甚至有害呢,感觉也是能合上现有对LLM RL的state定义不太合理,或者说探索空间太大,采样样本太少,不可能得到正确的Q函数,导致对于一些本来就很无所谓的token,更新的方向也是比较盲目

总之,这篇文章实验非常充足,分析也比较到位,效果也十分亮眼,确实是在当前的SOTA上往前推进的好文章


Pass@k Training for Adaptively Balancing Exploration and Exploitation of Large Reasoning Models

问题:RLVR 通常采用 Pass@1作为奖励,但容易收敛到局部最优;Pass@k则常用于验证,本文用Pass@k直接作为训练,并设计了对应的优势函数,发现效果比Pass@1更好(更高的Pass@k分数,保持的Pass@1分数)

Refer to caption

Pass@1 收敛到局部最优的问题在于正向奖励的探索可能路径太长,模型会倾向于利用而不是探索

Refer to caption

方法:

  • Full Sampling: 每组采样的k个rollout计算奖励,之后整组的奖励由每个rollout的最大值给出Refer to caption

  • Bootstrap Sampling: Full Sampling虽然提高了性能,但计算量太大。为了减少推理次数,同时保持组数不变,采用bootstrap采样,先生成一个候选池,再从这个池子里面抽取k个答案,就形成了一组(这样允许某个答案被分到多个组里面重用),论文中候选池大小就和正常top1大小相同,也就是平均而言每个样本被重用k次

Refer to caption

  • 既然Bootstrap Sampling只不过是对样本的采样重用,那其实可以直接计算对应的优势值的期望,所以可以省去采样这一步,直接计算候选池中正负样本的个数,通过解析解得到期望带入计算

其他实验:

Pass@k的熵在训练中是上升的,而Pass@1后期会收敛,支持了前面的探索-利用论

k的值的影响?k的值不是跳出局部解的重要因素,但k值越大,优势越小(因为只有抽样全负才会是负奖励,k值越大正奖励概率越高),步长越小,训练效率降低,这个结论和也可以在改变学习率中得到验证

无论是小规模还是大规模的 LLM,都可以从 Pass@k 训练中受益。此外,模型架构和模型系列不会影响持续 Pass@1 训练的提升,下游任务的领域和形式也不会影响 LLM Pass@k 性能向 Pass@1 性能的迁移

分析:

同样是奖励从0到1,Pass@k的梯度出现在比Pass@1更早的地方(一次做对和K次做对),会使得Pass@k更倾向于解决更难的问题而不是中等难度的问题(由于Pass@k有一个argmax, 所以提高已经会做的题的正确率的效果是不断减小的)

Pass@1

Refer to caption

Pass@k

Refer to caption

还做了一个对比试验是仅将简单问题的奖励设置为0,不能防止模型过度优化

Refer to caption

为了分析是否全是梯度曲线峰值带来的影响,手动调整奖励曲线,设计了一个这样的曲线

Refer to caption

发现太注重困难的样本也不好,模型后期乏力

既然Pass@k 更注重困难样本,Pass@1 更注重一般样本,能否动态结合?

一个样本池中,正样本越多,越需要注重困难样本;反之需要先学会一般样本,因此设计了这样的优势函数

image-20250818003914103

发现效果非常好

Refer to caption

文章还做了另一个实验是,用熵而不是正样本数量来判断一个问题是否是困难的,熵高的50%认为是困难问题,使用Pass@1, 低的使用Pass@k,也得到了不错的效果

简评:感觉没太多好说的了,他们做的相当好,从最开始的发现topk training可以提升效果,再到用bootstrap sample提高效率,再到公式的推出,自然发现topk就是本质上对应的难度-奖励曲线的不同,再到设计相关的实验验证,最后提出简单的自适应机制来结合Pass@1和Pass@k,也取得了相当好的实验效果,感觉挺一气呵成的,感觉是一个会成为范式的trick


Structure-Aware Fill-in-the-Middle Pretraining for Code

问题:现有的FIM将代码视为字符序列,而忽视句法结构

Refer to caption

方法: 结合AST和FIM, 在训练时,被mask的部分始终是AST的一个或多个完整子树

代码解析:Tree-sitter

mask算法:涵盖不同的AST节点,提高泛化能力,且与具体语言无关

  • 单节点mask: 按照对应文本的数量成比例抽样
  • 多节点mask: 先进行一次字符区间的采样,再找到包含这个字符区间的最小3节点的AST子树,子树中再取和原始字符区间有最大交并比的部分

评估:字符级别困惑度,文章给出不用实际benchmark的原因是大规模单测太难。

简评:非常直接的想法,就类似word-level BERT对原始BERT的改进,不过它怎么构建AST的倒是可以参考。文章最后的评估用困惑度说服力不高,但考虑到它的数据量确实大(256*H100训练),也可以理解


The Entropy Mechanism of Reinforcement Learning for Reasoning Language Models

问题:现有RL后训练存在策略熵减小导致模型快速收敛,后期探索较少难以提升的问题

Refer to caption

作者发现,在前1/3的epoch中,基本就已经达到了大部分的性能,而熵也进入低值,作者称之为熵崩溃"entropy collapse"

且对于不同的模型大小,对于不同的RL方法,都能拟合近似的定律 R=aeH+bR=-a e^{H} + b

有了这样的拟合公式,可以在训练早期估计后期的性能,且ab与算法几乎无关,极限就是 a+b-a+b

Refer to caption

另一个有趣的发现是,ab和模型大小呈对数线性关系

Refer to caption

也就是说,不仅可以在训练前期拟合后期,还可以用小模型预测大模型的RL效果

image-20250818222517778

熵变公式如上,直观地讲,如果动作 a 同时获得高/低概率和高/低优势,则熵会降低,反之亦然

Refer to caption

作者还提出,直接使用熵损失的方法,如L=L0αHL = L_0 - \alpha H, 不仅对超参数敏感,实验效果也并不优于基线

所以作者提出的方法是,针对高协方差的一小部分token做Clip或者KL,就能防止熵崩溃,下面的实验结果也表现很好,熵后期不下降,回答长度增长,正确率大幅度提高

Refer to caption

Refer to caption

作者发现策略熵对超参数设置非常敏感。具体来说,我们的方法仅干预一小部分 token( 10−4 到 10−3 ),却完全改变了熵曲线。这意味着几个“关键” token 对 LLM 的熵至关重要

简评:搬运作者对clip-higher的讨论,作者认为,clip-higher也有类似的功能,提高重要性采样的上限会带来更多低概率的token,上限阈值仅影响具有正优势的 token,这意味着 clip-higher 实际上在梯度计算中添加了更多低协方差(低概率、高优势)的 token,所以结论殊途同归。而作者直接提出协方差是更胜一筹。

不过作者也说了现在还不清楚熵和模型性能的完整关系,也不清楚最优的熵值

另一个就是这些熵的论文主要还是在math任务上做的,code任务能否有相同的结论还是一个问题 (我认为这两个任务关键在于中间过程是不是重要的,math只有结果可能会得到一些错误的结论)


Improving LLM-Generated Code Quality with GRPO

问题:take code quality into consideration,不多赘述

方法:维护了一个库,把现有的一些评估代码质量的方案整合了起来(code complexity, dead code, code structure(linter等), style&doc, safety, performance...), 然后质量奖励和正确性奖励一起放到奖励里面丢给GRPO

简评:只是占坑的,很草率的方法(对于奖励参数的设定),定量结果也不足。


Enhancing High-Quality Code Generation in Large Language Models with Comparative Prefix-Tuning

问题:take code quality into consideration

方法:比较有新意,将Dynamic Prefix和代码质量结合起来了,并且是使用对比学习的方法做这个前缀

基于Pylint打分,构建了一套数据处理流水线标注大量高、低质量的代码对(相似度高,质量差距大,且都至少通过一项基本测试)

然后在微调Dynamic Prefix的的时候,加上一个排名Loss,希望模型倾向于生成高质量的样本。

里面的掩码是用difflib做的,目标是聚焦于差异的导致质量出现区别的token,而不要考虑重复的token

image-20250818231615372

然后进行PEFT微调,再加上KL散度来保证不要丢失原始模型的代码生成能力

简评:这篇文章写得特别冗长,实验做的重点不突出,但思想是有意思的,并且明显可以继续挖,例如他们的代码相似度是简单的词频向量,自然挖掘出来的是细微处的代码风格问题,如是用index还是for each的形式遍历循环(只有这样的才会词频上高度相似)。但实际上是否可以用例如bge-code这样的代码语义嵌入呢?值得探究。

还有就是,这个数据收集的方法依赖于大语料库,也只能挖掘常见的代码模式,如果用自生成的方法,例如假设我们已经有一些高质量的代码库作为ground truth,

用llm得到的补全片段当负项,也能构造正负样本对啊,既然都是训练得到一个通用的“code style prefix”,这样数据丰富程度能高很多。他们明显的数据少训练小(2*A6000*3h)。


Augmenting Large Language Models with Static Code Analysis for Automated Code Quality Improvements

问题:LLM refactor code没结合静态分析

方法:RAG + 静态分析软件 + Prompt 工程

简评:垃圾文章,真要做也是做一个能排序Code Quality的专用BERT,或者对现有的code embedder/reranker做adapter研究怎么把code quality调进去


A Hierarchical and Evolvable Benchmark for Fine-Grained Code Instruction Following with Multi-Turn Feedback

只需要看一张图就行了

image-20250818234727444

现有模型在约束生成时,quality这种抽象的约束是满足最差的

而对于多种约束的组合,现有LLM都很差

而对于有反馈的情况(例如linter之类),在3轮迭代左右就能有很大的提升,但后续再增加轮数也难以获得更高收益


Training Language Models on Synthetic Edit Sequences Improves Code Synthesis

问题:LLM这样“一口气生成所有代码”和先前的软件工程实践(增量式开发)是相悖的,而现在的code agent又需要增量开发的能力,有绕远路之感,于是研究能不能从预训练的数据侧上解决这个问题,即大规模合成 增量编辑数据

方法: 文章提出了一个LintSeq的方法,对于一段已有的代码,从里面修建某些部分回退,让回退后的代码不会触发linter错误,则构建了一个edit stage

Refer to caption

然后这样构建了数据集后自己SFT codellm,发现确有提升

简评:简单有效,或许可以想想这个怎么和RL结合?

Focused-DPO: Enhancing Code Generation Through Focused Preference Optimization on Error-Prone Points

问题:代码错误很多是中间的“易错点”出错,对于所有token一视同仁的奖励函数在代码任务上可能未必高效

image-20250819001722237

方法:

  1. 该方法从真实代码库中提取概念,生成问题、代码和测试
  2. 由于有了测试,所以可以比较不同的生成代码的相对性能
  3. 通过共同前缀和共同后缀,得到中间不一样的中缀,就认为是“易错点”

然后DPO专注这一块的优化

image-20250819002037550

简评:感觉上是更软件工程的熵方法的简化,感觉这个易错点是能从LLM自身状态或者其他软件工程分析技巧中得到的,从前面几篇也可以看出,现在这种广义上的RL ”attention“ mask类工作越来越多了

这个方法要求生成测试,实际生产中感觉并不可用;抛开这个不谈,感觉就单纯对一个大型代码数据集,去分析里面的编码模式,找到相对少的n-gram,或者AST level迅速变化的地方作为易错点重点训都或许可行

Paper reading - Context Pruning and beyond hard pruning

· 17 min read
ayanami

引子

我们知道,在现在Agent需要处理的一大问题是长上下文下性能的开销问题,对此infra团队有非常多的优化,从attention架构的优化如各种windowed attention到kv的压缩重用如cacheblend和megicdec等都提出了一系列的解决方案,但有一个最本质的方法是:有没有可能直接减少上下文的长度(去掉不必要的上下文?) ,这就是Context Pruning的出发点。

而截止2025年8月,相关的方法已经发展了两三年了,大体上可以分成几个类别,本文会对此做一些简单的介绍和总结。

借用naver lab最新的相关论文里面的说法,现在的方法可以被一个四方格归纳:

其中,Hard和Soft代表裁剪方法是直接作用于token上(hard,相当于裁剪结束后,输入的是一个新的prompt),还是作用于token的embedding上(soft,相当于裁剪结束后,输入的是一个新的qkv和其他东西,无法还原出“原始”的token输入)

在线和离线一般代表着这个裁剪方案是否依赖于用户查询q,依赖q的方案是在线的,不依赖q的方案是离线的,可以提前做好。但传统上,如果你的裁剪方法也需要用到和原始模型一样大的LLM,也一般称之为离线,或许“是否会对在线推理造成明显时延影响”做划分更好一些

离线硬裁剪

在最早期的时候,就有相关的一些朴素方法,例如直接对查询文本段做一次总结摘要,再用总结后的文本段去做后续的任务,这种方法是离线硬裁剪的典型代表。如果用的是llm就是离线的,如果用轻量级模型做摘要或者总结就是在线的

而在后面的时候,出现了例如微软的llmlingua这样的工作,直接用一个小模型(gpt2 small,llama-7b, etc)去预测哪些token是重要的,哪些token是不重要的,然后把不重要的token直接裁剪掉,这种方法也是离线硬裁剪的典型代表。(llmlingua2 换成了微调的 BERT 来做这个事情,所以可以说在线的), 其出发点和常规的硬裁剪可能有部分地方不同,例如llmlingua认为,裁剪本身是可以得到一些人类不可读但是大模型可以理解的token序列的,所以可解释性上可能并没有想象的那么强。

离线软裁剪

和硬裁剪同时推进的是软裁剪相关的工作,其想法很简单: 如果我牺牲解释性,直接调整prompt的embedding这类,即使产生的是不对应任何token的"fake embedding",其在高维空间中也应该融合了多个token的语义,理应得到更高的压缩率(可以理解为,在训练过程中为llm 扩充为无限词表,然后定义了一些高效的"额外语言")

比较早期的工作是 xRAG, 其裁剪策略非常极端,将整个段落都压缩成1个embedding X,怎么训练呢?一个在此类论文中经常出现的是重建loss,即

压缩前Doc+queryxDoc + query \to x

压缩后Doc+queryxDoc' + query \to x', L=L(x,x)=DKL(x,x)L=L(x',x)=D_{KL}(x',x), 即自蒸馏,希望压缩后依然能重建原始的输出,论文实际中可能会用变体版本来实现指令遵循等

xRAG的做法是,使用一个通用编码器E,把这个编码器E视作一个新的模态,仿照CLIP的方法直接用MLP projector做通用编码器和实际使用的LLM token embedding的模态对齐

但[大家实测下来](笔记:RAG 的相关优化方法之六(xRAG/PISCO) - 刀刀宁的文章 - 知乎 https://zhuanlan.zhihu.com/p/29292925032),xRAG的效果并不好,而相对较好的是更新的Pisco方法

Refer to caption

Pisco将检索到的文档D和memory tokens一起送到LLM中,产生embeddings

再将embeddings +query送到相同的LLM中,产生输出,这个 q+E 和原始的 q+D 比较, 计算交叉熵损失

这里有一些复杂的地方:

  • 虽然叫解码和编码,但是Student LLM都是同一个LLM, 只是训练不同LoRA模块

  • 交叉熵是怎么得出的? teacher模型和student模型都是采用的最大长度128的贪婪解码,就可以直接令 L=1logp+0log(1p)=ilogP(aiq,e,a<i,θc,θd)L=-\sum 1logp + 0log(1-p) = - \sum_i log P(a_i|q,e,a_{<i},\theta_c,\theta_d) , 优化目标是 θc\theta_cθd\theta_d 还有 memory_tokens

  • 如何理解memory token? 我觉得文章是借用了之前的一些研究比如ICAE, 在这些文章之中,训练的压缩机制是,将上下文压缩成一个定长的memory slot, 这里的memory token实际上只是多个embedding向量而已,而更关键的是LoRA微调的θc\theta_c,我的理解是,memory tokens只是一个后置的、可以看到Documents的所有信息(假设它没有魔改注意力)的语义位置,叫tokens也可以理解为直接扩了词表加入了l个特殊token,类似BERT里面的[BOS] ,只是decoder llm需要后置。

    • 文章并没有细说这里的注意力是怎么设置的,但从后文中发现的memory tokens具有明显的位置特性(例如1位mem token主要注意最开头一段),感觉应该是没改过
  • 文章的另一个重要的实验结论是,微调student llm(θd\theta_d)是必要的,之前的研究中没有相关模块,会导致性能的大幅度下降。这细想其实是一个很有趣的事情,可以注意到,压缩的时候是没有接触到query信息的(这也是为什么称为离线的原因),可以理解为某种意义上的LLM as an embedder,而加入了query和embedding再训练的时候,θd\theta_d一边学会了如何理解自己产生的embedding,另一方面学会了如何根据query去选择embedding,整体上类似于ColBERT架构的Reranker(前面是multi-vec embed, 后面是maxsim)

在线硬裁剪

Provence

之前的裁剪方案只注重于“自然语言是有冗余的”,所以主要做的都是token-level的pruning,而provence则更注重实际一些,它发掘了一个问题是,其实现在RAG里面的 “Chunk” 是一个特别微妙的概念

如果chunk切得大了,那上下文自然就长了,甚至效果也会明显下降(详见ground truth在chunk中的不同位置的position bias相关的研究,现有embedder对这个bias耐受性不佳,会狠狠掉点);但如果chunk切得小了,语义信息的丢失、检索的困难又是很恼人的事情(先不论检索,检索到了多个小块之后信息不够怎么办?一种是合并,但策略怎么定?另一种是Anthropic的Contextual Retrieval,把上下文放进来,本质上还是变成大块(我说这个a一串真是炒作勾啊.jpg))。

而Provence给了一个折中的方案,既然我们有句子级别的语义,为什么不用呢?分几步走

  1. 训练一个接受q,d的BERT,给每一个token打0~1分,并根据用户指定的阈值进行二值化变为0/1, 表示删除/留下
  2. 进行句子级别的聚类,裁剪掉0的token数量大于1的token数量的句子

如何训练呢?选取有5~10个句子的段(可以多次选取来拓展到更长的上下文),标上句子序号,让LLM选择相关句子来产生label,从而训练模型

这里其实做了很有意思的工程设计,

  • 如果让LLM来打token-level的标,肯定是收集不到足够的样本的,并且真的无所谓多出来的几个token,更在意句意的完整性

  • BERT带来了相当多的好处:

    • 这样进行的句子裁剪,每个句子都可以和整个chunk里面的所有上下文交互,使得一个句子的保留与否不仅取决于这个句子和查询的相关性,还取决于其于其他(和查询相关性高的)句子的相关性,这就使得这个方法必然会优于按句子切分的朴素方法

    • 我们的 reranker 也就是个BERT啊,完全可以训裁剪和训rerank一起进行,推的时候也一样,相当于和rerank overlap了

      image/png

在线软裁剪

Oscar

Pisco为代表的离线软裁剪有一个问题是,它的压缩需要微调,并且受限于难以对齐encoder-only架构的预训练编码器模态和实际推理使用的decoder LLM的模态,难以把压缩这一步在线做

Oscar就提出了一种方法是,我的对齐既然难做,我直接不对齐了,使用LLM的前L层 + memory token(他们也做了用Llama硬对齐的版本),足以得到够好的embedding,文章最大的贡献其实是实验证明了这样表达能力已经足够,能训出来(太神奇了LLM)。当然,L越大效果越好

而还是复用Provence的工程技巧,把裁剪和rerank overlap起来,OSCAR的compressor留了一个RR头,在这个头和Teacher Reranker对齐,整体的Loss就是rerank loss + generation loss

而令LLM理解embedding这件事情还是通过LoRA adapter来做,这篇文章其实像是序列工作的延申,综合了PISCO的训练方法,把PISCO的压缩部分从LLM + LoRA换成目标模型的前N层transformer,然后压缩器全参微调、生成器LoRA微调,再使用和Provence相同的技巧进行rerank的overlap

image-20250907213839337

异曲同工

从HyDE到“投机解码”

另一个有趣的工作是广义上的“裁剪”,或者就是更好的搜索吧。我们知道HyDE的思想是原始query一般都比较短,而生成的假设文档可能会更好地与索引文档对齐,所以使用 q‘ = q + generated d 来进行搜索。

而智谱的memorag 则提出了这样一种场景,我们是否能以低成本训练一个小模型,来根据源文本生成这个假设答案呢?(例如,使用Llama3-8B在哈利波特上训练比用Deepseek-R1在哈利波特上训练成本要低廉的多,将HyDE的生成方从R1自己换成小模型)这就非常像是投机解码的思想了

外接模块: memory decoder, catridges

其实这种将memory训为embedding的方法确实不少,如果说前面的压缩器是在训一个meta network,能够从doc生成embedding的话,外接模块的工作就是在训练embedding本身 -> 我能否直接从一个大的文档库中训练出一个参数化的memory?

最近的memory decoder选择的是直接扭曲生成过程,将一个小模型在目标适配数据集上训练,在大模型生成token时,将小模型的概率和大模型的概率相加(再重归一化),认为这样会带来领域知识的纠正(比较暴力www)

而另一篇catridges 则是在使用类似P-tuning的方式训一个Prefix KVCache,在推理时实时加载,而希望这个KV中有相关的memory

包括一系列的kvcache evict的工作也是在做类似的东西,为了决定evict哪些甚至都把搜索又搬上来了,比如clusterKV的knn(笑)

总结

总体而言,我感觉相关工作已经进入了深水区了,硬裁剪可能在某些程度上到头了,现在主流在探索一些牺牲解释性的,更能scale out的方法来进行参数化memory来解决长上下文、领域适配等一系列问题

而大家方法逐渐趋向于无标签学习的统一也再次证明了scale out能力在广义embedding能力的训练上的重要性

另一个很有意思的是,可以看到搜索中的多向量和多memory token有一些很有趣的相似性,或许在后续的一些工作中,我们能看到一些多向量的方法被用到memory之中,希望会让memory这个很多时候靠prompt编故事的领域更多可验证性吧

而从另一个方面,正如这里列出的部分文章说的,自从eagle在投机解码中得到了确实很好的效果之后,大家都开始用 token + embedding的混合来捕捉更强的信息了,还有HyDE和投机解码这种很有趣的对应

结构化输出与AI工具与Agent

· 17 min read
ayanami

假如大伙接到一个需求,需要把claude code接入jupyter前端(例如,在jupyter前端直接输入魔法指令和claude code交互,而后台claude code展示claude code的一些关键节点,工具调用,费用开销,输出结果等),会怎么做?

一种想法是,将claude code的输出塞到一个文件里面去,起一个后台线程读取这个文件,尝试解析之中的某些部分,再以插件的形式加载到jupyter前端

但带来了一个问题是,效果(尤其是工具数量upup,上下文长度upup后的效果)不稳定,纯prompt的形式约束claude code及时向这个文件中写入以向前端通信,在经过长的交互过程后,claude经常会把这个文件忘掉

那claude code直接全塞前端呢?

在claude code里面问一个问题,可能就是几千上万token的交互,全塞前端,那用户体验就烂掉了。

另一个很容易想到的方案是,那我们不要让他输出文件了,直接当场处理把,定义一些特殊块叫 display 之类的东西,在prompt里面指定这个块里面是什么格式,让他如果想要和前端输出的话,放到这个块里面

这样看起来比文件好一些,但带来了新的问题没解决,长上下文下,display块的结构偶尔会有不稳定,会有不少特殊的渲染格式如html等由于几个字符的差异退化成了纯文本

如何修复这个呢?一个简单的方法,也是你能在任意一个现在的agent中看到的,是及时判错,再把把错误的部分发给模型让他修复一下,但又带来了额外的开销,并且前端的呈现也收到影响

有没有更优雅的办法呢?

如果你做AI应用比较多的话,肯定注意到了这实际上是一个结构化输出(约束解码)的场景,但现在的问题是,输出不止是一个json,而是正常文本块和display块的交错

(对于不了解约束解码的简单介绍一下,就是把上层的json等约束编译成状态机之后,用于动态建立llm output logits的mask,从而杜绝输出非法输出的技术)

看起来似乎不能约束解码?但display块本身是可以约束解码的,好恶心。

让我们打开vllm文档,翻到 Structured Outputs,你会发现,除了常见的regex约束解码之外,还有两种更强语义的解决方案,救赎之道就在其中,ebnf解码和structure tags解码

实际上,json解码只不过是ebnf解码的特殊情况罢了,毕竟实际都是状态机 (不知道ebnf是什么的同学,可以搜索一下编译前端,BNF范式,就能看懂下面的示例啦)

官方给的一个ebnf解码的例子如下, 用于执行一个简化sql的约束解码以提升sql正确率

simplified_sql_grammar = """
root ::= select_statement

select_statement ::= "SELECT " column " from " table " where " condition

column ::= "col_1 " | "col_2 "

table ::= "table_1 " | "table_2 "

condition ::= column "= " number

number ::= "1 " | "2 "
"""

completion = client.chat.completions.create(
model=model,
messages=[
{
"role": "user",
"content": "Generate an SQL query to show the 'username' and 'email' from the 'users' table.",
}
],
extra_body={"guided_grammar": simplified_sql_grammar},
)
print(completion.choices[0].message.content)

如果放到这个问题,我们可以快乐地写出类似这样的定义

output := (display | normal text) *
display := (```display json ```)
json = ...
normal text = others

其中,display, json都是容易得到的,但恶心的地方在于什么是“others” 未拓展的ebnf是没有“非”定义的,从实操上虽然感觉可行(mask token取反),但这下已经没有支持了

(但ebnf解码肯定是有大用的,还是以Text2SQL举例,任何一个数据库都会给你他们的解析引擎的ebnf定义,都不需要你写)

怎么办呢,就带来了最后一个冷门工具,structured tags, 我先上代码,

def get_structural_tag_params(
tags: list[StructuralTag], triggers: list[str]
) -> dict:
return {
"type": "structural_tag",
"structures": [model.model_dump() for model in tags],
"triggers": triggers,
}

model_v2 = ChatOpenAI(
base_url=base_url,
model=model_name,
api_key=api_key,
temperature=0.15,
top_p=0.9,
extra_body={
"response_format": get_structural_tag_params(
tags=[
StructuralTag(
begin="<block=text>",
end="</block>",
schema=TextMsgSchema.model_json_schema(),
),
StructuralTag(
begin="<block=image>",
end="</block>",
schema=ImageMsgSchema.model_json_schema(),
),
StructuralTag(
begin="<block=tool_use>",
end="</block>",
schema=ToolUseMsgSchema.model_json_schema(),
),
StructuralTag(
begin="<block=todo_list>",
end="</block>",
schema=TodoListMsgSchema.model_json_schema(),
),
StructuralTag(
begin="<block=html>",
end="</block>",
schema=HTMLMsgSchema.model_json_schema(),
),
],
triggers=["<block="],
)
},
)

这个tags + triggers, 就是structured output的关键之处,它允许我们在trigger触发的时候才开始约束解码,在end结束的时候停止约束解码

至此,这个工作已经做完了


那约束解码和不约束带来的效果差距有多大呢,我在24B的Mistral-Small上做了个实验 最后的结果直接尝试解析后渲染到前端

Prompt如下,

sys_prompt = f"""

你是一个agent模型,你负责处理用户的问题,发起工具调用, 绘制图片、html、获取文本等。

由于你的token交互量很大,不是所有信息都需要展示给前端。

你可以正常思考和输出,但你需要将你认为需要展示给用户的有效信息包裹在 `<block={{tag}}> {{schema}} </block>` 中。

前端会将这部分内容进行渲染,交给用户。

你现在可用的tag有:

tags: "text", "image", "tool_use", "todo_list", "html"

对应的schema(pydantic格式)如下:

- {schemas_str}

例如,你可以先产生一个todo list,然后不断执行子任务,并更新todo list,直到所有任务完成。

由于你现在没有接入工具调用,所以对于所有工具调用交互,你只需要“假装”执行了工具调用并得到一个合理的响应就行,这是一个debug环境,

你需要根据用户的问题尽可能多的展示不同的block,并给出一个合理的响应。

"""

这个prompt下,<block=text>111</block> 这种就取代了上文所述的display块的效果

只定义了五种特殊的前端展示格式,文本,图片,TODO list,工具调用和HTML块

效果对比如下:

用户:帮我完成编写一个论坛帖子,打开浏览器的水源社区论坛,登录之后在discourse发帖的流程。

alt text alt text alt text alt text

可以看到,左侧没有约束解码的模型,在这样的任务负载下,json 参数就已经频频出现失误了,而右边的即使是24B模型的fp8量化非思考版本,却跑出了几百B agent的气势,并且token开销是来回倒腾的几分之一


一点感想: 我们常说一个子领域的知识对于另一个子领域是用处寥寥的,然而,这不是拒绝新领域知识的理由啊,vllm和xgrammer、outlines这种框架都把几种更强大的结构化解码方法摆到人们的脸上了,还是能在知乎看到“ebnf好像是编译原理的内容,(作为后端程序员)跳过”,或者是在各种开源仓库中还在广泛使用的拿prompt指导llm输出,完全不考虑(甚至不知道)结构化输出这样的东西

现在的后端、infra、算法,又有多少更深的优化方案是独立的呢?今天在看snowflakes优化方案,真是把上层算法和底层infra相辅相成,只是缺乏探索性的人们,会拿"这不是我的工作,这是专攻模型/infra/算法的人的工作"搪塞,最后又堆起来一个prompt史山罢了

在现在的agent框架中,充斥的也是prompt的兜底方案,带来的是qwen3-coder几个问题爆掉用户百万token,带来的是claude code问个“你是谁”都要花一角钱,但有没有一种可能,我们本可以用更确定的东西呢?LLM是一种万能的模糊推理,但好钢也要用在刀刃上啊。

参考完整代码如下

# ruff: noqa: E501
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project

import argparse
import asyncio
import enum
import json
import os
import re
from pathlib import Path
from typing import Any

import colorlog
import openai
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from dotenv import load_dotenv

load_dotenv()


class StructuralTag(BaseModel):
begin: str
end: str
schema: dict[
str, Any
] # JSON schema for validation, model_dump by pydantic model


class TextMsgSchema(BaseModel):
text: str = Field(..., description="Text message")

def to_html(self) -> str:
"""Render text message as HTML"""
return f'<div class="text-message">{self.text}</div>'


class HTMLMsgSchema(BaseModel):
raw_html: str = Field(..., description="raw html str, like <div></div>")

def to_html(self) -> str:
"""Render HTML message as HTML"""
return f'<div class="html-message">{self.raw_html}</div>'


class ImageMsgSchema(BaseModel):
image_url: str = Field(..., description="Image URL")
image_name: str = Field(..., description="Image name")

def to_html(self) -> str:
"""Render image message as HTML"""
return f"""<div class="image-message">
<img src="{self.image_url}" alt="{self.image_name}" style="max-width: 100%; height: auto;">
<p class="image-caption">{self.image_name}</p>
</div>"""


class ToolUseMsgSchema(BaseModel):
tool_name: str = Field(..., description="Tool name")
args: dict[str, Any] = Field(..., description="Tool args")
tool_output: dict[str, Any] = Field(..., description="Tool output")

def to_html(self) -> str:
"""Render tool use message as HTML"""
args_html = json.dumps(self.args, indent=2, ensure_ascii=False)
output_html = json.dumps(self.tool_output, indent=2, ensure_ascii=False)
return f"""<div class="tool-use-message">
<h4>Tool: {self.tool_name}</h4>
<div class="tool-args">
<strong>Arguments:</strong>
<pre>{args_html}</pre>
</div>
<div class="tool-output">
<strong>Output:</strong>
<pre>{output_html}</pre>
</div>
</div>"""


class TodoListMsgSchema(BaseModel):
todo_list: list[tuple[bool, str]] = Field(..., description="Todo list")

def to_html(self) -> str:
"""Render todo list message as HTML"""
items = []
for done, item in self.todo_list:
checked = "checked" if done else ""
item_class = "completed" if done else "pending"
items.append(
f'<li class="{item_class}"><input type="checkbox" {checked} disabled> {item}</li>'
)
items_html = "\n".join(items)
return f"""<div class="todo-list-message">
<h4>Todo List</h4>
<ul class="todo-list">
{items_html}
</ul>
</div>"""


def get_structural_tag_params(
tags: list[StructuralTag], triggers: list[str]
) -> dict:
return {
"type": "structural_tag",
"structures": [model.model_dump() for model in tags],
"triggers": triggers,
}


def parse_structured_response(response: str) -> str:
"""Parse structured response and convert blocks to HTML"""
# Schema mapping
schema_classes = {
"text": TextMsgSchema,
"image": ImageMsgSchema,
"tool_use": ToolUseMsgSchema,
"todo_list": TodoListMsgSchema,
"html": HTMLMsgSchema,
}

def replace_block(match):
tag_type = match.group(1)
content = match.group(2).strip()

if tag_type not in schema_classes:
return match.group(0) # Return original if unknown tag

try:
# Parse JSON content
data = json.loads(content)
# Create schema instance
schema_instance = schema_classes[tag_type](**data)
# Return HTML
return schema_instance.to_html()
except (json.JSONDecodeError, ValueError) as e:
return (
f'<div class="error">Error parsing {tag_type} block: {e}</div>'
)

# Replace all <block=type>content</block> with HTML
pattern = r"<block=(\w+)>\s*(.*?)\s*</block>"
return re.sub(pattern, replace_block, response, flags=re.DOTALL)


def create_comparison_html(response1: str, response2: str) -> str:
"""Create a comparison HTML page with both responses"""
parsed_response2 = parse_structured_response(response2)

css = """
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: #2563eb;
color: white;
padding: 20px;
text-align: center;
}
.comparison {
display: flex;
min-height: 600px;
}
.column {
flex: 1;
padding: 20px;
border-right: 1px solid #e5e5e5;
}
.column:last-child {
border-right: none;
}
.column h3 {
margin-top: 0;
color: #1f2937;
border-bottom: 2px solid #e5e5e5;
padding-bottom: 10px;
}
.content {
line-height: 1.6;
color: #374151;
}

/* Schema-specific styles */
.text-message {
background: #f8fafc;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
border-left: 4px solid #3b82f6;
}
.image-message {
background: #f0fdf4;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
border-left: 4px solid #10b981;
text-align: center;
}
.image-caption {
margin: 10px 0 0 0;
font-style: italic;
color: #6b7280;
}
.tool-use-message {
background: #fefce8;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
border-left: 4px solid #eab308;
}
.tool-use-message h4 {
margin: 0 0 10px 0;
color: #92400e;
}
.tool-args, .tool-output {
margin: 10px 0;
}
.tool-args pre, .tool-output pre {
background: #1f2937;
color: #f9fafb;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
}
.todo-list-message {
background: #fdf2f8;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
border-left: 4px solid #ec4899;
}
.todo-list-message h4 {
margin: 0 0 10px 0;
color: #be185d;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-list li {
margin: 5px 0;
padding: 5px 0;
}
.todo-list li.completed {
text-decoration: line-through;
opacity: 0.7;
}
.html-message {
background: #f5f3ff;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
border-left: 4px solid #8b5cf6;
}
.error {
background: #fef2f2;
color: #dc2626;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
border-left: 4px solid #dc2626;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
}
</style>
"""

return f"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模型响应对比</title>
{css}
</head>
<body>
<div class="container">
<div class="header">
<h1>模型响应对比</h1>
<p>左侧:无结构化标签 | 右侧:带结构化标签(已渲染)</p>
</div>
<div class="comparison">
<div class="column">
<h3>无 Structure Tag</h3>
<div class="content">
<pre>{response1}</pre>
</div>
</div>
<div class="column">
<h3>Structure Tag(已渲染)</h3>
<div class="content">
{parsed_response2}
</div>
</div>
</div>
</div>
</body>
</html>
"""


if __name__ == "__main__":
base_url = "localhost:8000/v1"
model = openai.OpenAI(base_url=base_url, api_key="sk-")
schemas = [
TextMsgSchema.model_json_schema(),
ImageMsgSchema.model_json_schema(),
ToolUseMsgSchema.model_json_schema(),
TodoListMsgSchema.model_json_schema(),
HTMLMsgSchema.model_json_schema(),
]
schemas_str = "\n- ".join([json.dumps(s, indent=4) for s in schemas])
sys_prompt = f"""
你是一个agent模型,你负责处理用户的问题,发起工具调用, 绘制图片、html、获取文本等。
由于你的token交互量很大,不是所有信息都需要展示给前端。
你可以正常思考和输出,但你需要将你认为需要展示给用户的有效信息包裹在 `<block={{tag}}> {{schema}} </block>` 中。
前端会将这部分内容进行渲染,交给用户。

你现在可用的tag有:
tags: "text", "image", "tool_use", "todo_list", "html"
对应的schema(pydantic格式)如下:
- {schemas_str}

例如,你可以先产生一个todo list,然后不断执行子任务,并更新todo list,直到所有任务完成。
由于你现在没有接入工具调用,所以对于所有工具调用交互,你只需要“假装”执行了工具调用并得到一个合理的响应就行,这是一个debug环境,
你需要根据用户的问题尽可能多的展示不同的block,并给出一个合理的响应。

"""
base_url = "http://localhost:8000/v1"
model_name = "stelterlab/Mistral-Small-3.2-24B-Instruct-2506-FP8"
api_key = "sk-"
model = ChatOpenAI(
base_url=base_url,
model=model_name,
api_key=api_key,
temperature=0.15,
top_p=0.9,
)
print("-" * 50)
logger = colorlog.getLogger("Agent")
msgs = [
SystemMessage(content=sys_prompt),
HumanMessage(
content="帮我完成编写一个论坛帖子,打开浏览器的水源社区论坛,登录之后在discourse发帖的流程。"
),
]

model_v2 = ChatOpenAI(
base_url=base_url,
model=model_name,
api_key=api_key,
temperature=0.15,
top_p=0.9,
extra_body={
"response_format": get_structural_tag_params(
tags=[
StructuralTag(
begin="<block=text>",
end="</block>",
schema=TextMsgSchema.model_json_schema(),
),
StructuralTag(
begin="<block=image>",
end="</block>",
schema=ImageMsgSchema.model_json_schema(),
),
StructuralTag(
begin="<block=tool_use>",
end="</block>",
schema=ToolUseMsgSchema.model_json_schema(),
),
StructuralTag(
begin="<block=todo_list>",
end="</block>",
schema=TodoListMsgSchema.model_json_schema(),
),
StructuralTag(
begin="<block=html>",
end="</block>",
schema=HTMLMsgSchema.model_json_schema(),
),
],
triggers=["<block="],
)
},
)

logger.info("=== 测试开始 ===")
response1 = model.invoke(msgs).content
logger.info(f"=== 测试结束 ===\n{response1}")

logger.info("=== 测试开始 ===")
response2 = model_v2.invoke(msgs).content
logger.info(f"=== 测试结束 ===\n{response2}")

# 生成对比HTML文件
comparison_html = create_comparison_html(response1, response2)
Path("tmp/test_comparison.html").write_text(
comparison_html, encoding="utf-8"
)
logger.info("已生成对比HTML文件: tmp/test_comparison.html")

# 保留原有的Markdown文件
with Path("tmp/test_diff.md").open("w", encoding="utf-8") as f:
f.write("无structure tag: \n")
f.write(response1)
f.write("\n\nstructure tag: \n")
f.write(response2)

Paper reading-Ask in Any Modality A Comprehensive Survey on Multimodal Retrieval-Augmented Generation

· 16 min read
ayanami

RAG 抽象来说就是,embed - opitional[rerank] - generate管道

有许多的增强方案,例如 Plan X RAG(将问题分解为子问题的DAG,然后设计一些critic LLM判断流的状态正常与否,一个执行LLM按照拓扑序执行DAG),Agentic RAG, feedback-driven iterative refinement

局限是:传统RAG主要针对文本,多模态集成还是挑战

流程概述如下图


Refer to caption


Multimodel RAG

LLM拓展为MLLM带来了多模态RAG的挑战

  • 检索哪些模态
  • 数据类型的有效融合
  • 跨模态相关性

特定模态的编码器将不同的模态映射到共享语义空间,实现跨模态对齐


现有数据集和基准

数据集

  • 图文任务(字幕、检索):MS-COCO, Flickr30K, LAION-400M

  • 利用外部知识的视觉问答: OK-VQA

  • 多模态推理:MultimodalQA

  • 视频文本任务:ActivityNet,YouCook2

  • 医学:MIMIC-CXR

许多数据集都是单模态的,随后与其他模态的互补数据集集成。


Benchmark

M2RAGM^2⁢R⁢A⁢G: 我们执行以下步骤来处理图像,以确保它们具有高质量并且与用户查询相关: (1)缓存和转换:使用 URL 下载所有图像,并将其转换为广泛接受的格式,例如 JPG、PNG、GIF 或 WEBP。无法成功下载或转换的图像将被丢弃; (2)过滤:小于某个阈值或与查询文本的基于 CLIP 相似度得分较低的图像将被删除。此类图像通常包含非代表性的视觉内容,例如图标、横幅等。 (3)重复数据删除:使用 PHash Zauner 算法删除重复或高度相似的图像。

指标设计:主要靠prompt gpt-4o做评估

  • 文本模态指标:流畅性,相关性,忠实度,上下文准确率
  • 多模态指标:图像连贯性(图像和周围文本逻辑的连贯性,图像有用性, 图像引用(验证图像和文本引用的适当性),图像召回率(高度相关图像的召回比例)
  • 取所有指标的平均值用于计算总分

两种联合建模策略

  • single-stage:直接生成多模态输出
  • multi-stage: 文本生成 - 图像插入 - 文本重润色 三个阶段


Refer to caption


视觉为中心的评估

MRAG-Bench, VQAv2, VisDoMBench, Dyn-VQA, ScienceQA

img


知识密集型评估

TriviaQA, RAG Check, Natural Questions


image-20250528162131659


image-20250528162212109


创新和方法

检索策略

高效和精度

现代MRAG将不同输入模态编码到统一的embedding空间实现直接跨模态检索

方法上,主要为Maximum inner product search (MIPS) 变体:近似MIPS,分布式MIPS,KNN变体,近似KNN,ScaNN


创新主要在效率提升和精度降低:

  • 混合搜索
  • 自适应量化
  • learned index: 神经网络驱动的索引建立,主要是数据库那边的工作

以模态为中心的检索

文本中心

  • BM25
  • bge-m3
  • ColBERT
  • RAFT(混合干扰和ground truth文档微调模型增强抗干扰能力)
  • ...

视觉中心

  • 直接用图像表示进行知识提取
  • 基于参考图像的检索,如EchoSight和ImgRet
    • EchoSight 引入了多模态重排
    • Teaser

具体来说,对于一个图文问题query, 先用image视觉相似度找到对应的wiki条目,再将wiki的section与图+文的完整query(经过Q-Former之后)进行文本rerank,最后综合视觉分数和文本rerank分数,选取topk后输入LLM。专注于问题和知识库都是图+文的情况,也只是finding, 感觉确实创新度不够 Overall Structure h:500


  • 组合多张图像特征形成综合查询表示
  • 图文映射:Pic2word 如下图,将视觉映射到文本描述

img


视频中心

  • iRAG,增量检索
  • MV-Adapter
  • Video RAG
  • RTime: 时间因果关系
  • OmAgent:分治处理复杂视频理解
  • DRVideo:基于文档检索处理长视频理解
  • ...

文档检索和布局理解

ColPali, ColQwen2: 端到端文档图像检索,动态分辨率处理,整体多页推理,绕过OCR技术,1.9k star

它的想法是这样的

  • OCR的多个组件和分块带来误差传播,且预处理流程耗时也长,能不能直接端到端一次使用文档截图解决
  • 但是如果将整页的文档编码成一个向量,肯定精度不够
  • 多向量方案最经典的ColBERT, 并且在这样一个视觉的情况下,视觉patch做多向量比文本token还合理

  • 贡献
    • benchmark ViDoRe
    • 将ColBERT和视觉语言模型结合,利用多向量不仅启发了文搜文,文搜图,还启发了“给一个文档,查找相似的文档”这样的任务
    • 提供了一个良好的视觉文本融合的范式(例如,解决了CLIP这样的模型缺乏文本细粒度的问题),允许最先进的VLM如Qwen-VL-2B,以相同的训练策略微调后作为嵌入器,+5.3 nDCG@5

Refer to caption


可不可以将这个范式沿用到引用溯源?

已经有一些了,ColPali自己就做了每个词条最显著的图像块

Refer to caption h:500

一些布局理解的新框架:ViTLP, DocLLM, CREAM, mPLUG-DocOwl


To our knowledge, no benchmark evaluates document retrieval systems in practical settings; in an end-to-end manner, across several document types and topics, and by evaluating the use of both textual and visual document features.

https://huggingface.co/blog/fsommers/document-similarity-colpali 基于 OCR 的文本提取,以及随后的布局和边界框分析,仍然是重要文档 AI 模型(例如 LayoutLM)的核心。例如, LayoutLMv3 对文档文本进行编码,包括文本标记序列的顺序、标记或线段的 OCR 边界框坐标以及文档本身。这在关键的文档 AI 任务中取得了最佳成果,但前提是第一步——OCR 文本提取——能够顺利完成。

但通常情况并非如此。

根据我最近的经验,OCR 瓶颈导致现实世界生产文档档案中的命名实体识别 (NER) 任务的性能下降近 50%。


Architecture h:600


为下游任务提供了一系列微调版本

  • Image Caption 加字幕
  • VQA
  • Detection (Detect [entity])
  • 图像实体分割
  • 文档理解

重排序和选择

多用多步骤检索,整合监督和非监督策略

  • probabilistic control keywords to improve credibility
    • 对示例的关键信息进行关键词提取,为关键词赋予概率权重,使用概率进行控制信号,让模型倾向于选择高概率关键词的示例
  • RULE 利用统计方法(Bonferroni校正)校准相关上下文
    • 利用统计方法,将“5%概率存在错误上下文”这样的朴素要求通过统计运算转换成单个上下文相关度的硬阈值
  • 视频检索中基于聚类的关键帧选择来提高多样性

相关性评估

  • SSIM (Structural Similarity Index Measure) 最早用于图像领域,衡量两幅图像间的结构、亮度、对比度相似度。现在常用于多模态信息检索,例如图片和文本联合时的相似性计算。
    • 比起传统的均方差等简单像素差,更符合人类对视觉感知的一致性判断,综合考虑亮度对比度等
  • NCC (Normalized Cross-Correlation) 标准化互相关,常见于信号处理,也可以衡量不同模态数据间的相关强度。
    • 衡量两个向量或数组的线性相关性
  • BERTScore 利用BERT这样的深度语义模型计算文本间的语义相似度,比传统关键词对齐更关注上下文语义一致性
  • 分层后处理:重排、相似度筛选、上下文窗口、合并、...

  • LDRE

    结合多种特征(如caption描述、上下文语义、实体识别等),通过权重自适应集成,提高不同表示方式下的检索相关性适应能力

  • BM25等传统排名的集成


过滤机制

  • 硬负样本挖掘:比起文本的硬负样本挖掘需要多处理跨模态的问题,如不同模态的bias等

    • GME
    • MM Embed
  • 共识过滤、多向量过滤

    • MuRAR
    • ColPali
  • 动态模态过滤

    • 训练retriever判断哪部分是噪声
    • RAFT, Img2Loc, MAIN-RAG

融合机制

分数融合和对齐

  • 训练交叉编码器将多模态转换为文本格式

  • 引入交错文本对,合并垂直多张few shot images(?)

  • CLIP分数融合,BLIP特征融合,嵌入到相同的空间

  • VISA 使用文档截图嵌入(DSE)模型,对齐文本查询和视觉文档表示

  • MA-LMM视频文本嵌入

  • LLM-RA 将文本和视觉嵌入连接成联合查询

  • ...

注意力机制:

注意力方法动态加权跨模态交互,支持特定任务推理

EMERGE, MORE, Alzheimer RAG,RAMM,RAGTrans, MV-Adapter, M2-RAAP


统一的框架和预测

M3DocRAG : 多页文档展平为单个嵌入张量

PDF-MVQA 融合了基于感兴趣区域 (RoI) 和基于块 (CLIP) 的视觉语言模型

DQU-CIR 图像转换为复杂查询的文本标题以及将文本叠加到图像上来统一原始数据,然后通过 MLP 学习的权重融合嵌入

SAM-RAG生成图像的标题来对齐图像-文本模态

UFineBench 利用共享粒度解码器进行超精细文本人物检索

Dense2Sparse 投影,将来自 BLIP/ALBEF Li 等人 ( 2022a ) 等模型的密集嵌入转换为稀疏词汇向量,使用层归一化和概率扩展控制来优化存储和可解释性


增强技术

Context Enrichment

查询 重构为结构化检索请求, Video-RAG,EMERGE 整合实体关系和语义描述

Img2Loc 提示中包含数据库中最相似的和最不相似的点来让模型排除预测中不可信的位置

虽然说只是prompt工作,但想法似乎挺有趣,只是这样的作法能否比简单的几层MLP强呢?

Refer to caption h:400


动态检索

  • SKURG 查询复杂度决定跳数

  • MR2AG 动态评估和过滤

  • OmniSearch 分解问题


生成技术

  • In context learning

    • 记忆数据 RAG-Driver(可解释的自动驾驶)

      • 检索引擎 接收到当前驾驶场景(如视频帧和对应的车辆控制信号)后,先在专家示范的记忆库中检索出与当前最相似的历史驾驶样本。
      • 多模态大语言模型处理 将检索到的样本与当前场景一同输入多模态大语言模型(MLLM),利用指令微调(Instruction Tuning),实现三项任务:
        • 动作解释(Driving Action Explanation):输出当前行为的自然语言解释;
        • 行为理由(Action Justification):对决策作出合理性说明;
        • 控制信号预测(Control Signal Prediction):给出下一个动作的具体数值(如速度和转角)

MY ALT TEXT h:600


  • 融合上下文Fusion-in-Context Learning (没太看懂RAVEN这篇论文和融合上下文这一个比较早期的encoder-decoder模型的机制有什么关系)

  • Reasoning

    • CoT RAGAR RAG链和RAG树,迭代方式优化事实核查
    • VisDoM CoT和证据整理
    • SAM-RAG 推理链和多阶段验证

指令调优:如mR2AG 用 mR2AG-IT的数据调优MLLM


来源归属

VISA 视觉来源归属

  • 看了看他的论文,VLM直接输出边界框(也就是,输入为文档图片,输出为答案 + Box)的,再LoRA微调......

image-20250528205321419 h:400


对齐

主要是对比学习:文档/图片/字幕...

噪声管理

RagVL 噪声注入训练,数据级别加负样本,token级别加Gauss噪声

RA-CM3 随机删除查询token做query dropout


MRAG解决的任务

  • 图像字幕
  • QA
  • 事实验证
  • 视觉叙事连贯性
  • 图文检索
  • .....

未来方向

泛化

  • 领域自适应

  • 模态偏差,过度依赖文本

  • 可解释性

  • 引用来源归属,在视觉/语音等模块更严重,难以识别出对应的小区域

  • 多模态的对抗性扰动,误导性信息


推理

多模态融入KG

如何进行实体感知检索

位置敏感性

冗余检索

具身智能

长上下文,效率,可拓展

  • 带图像的多页文档
  • 视频这种超长上下文

Paper reading - Fit and Prune Fast and Training-free Visual Token Pruning for Multi-modal Large Language Models

· 10 min read
ayanami

任务

当前MLLM依赖于大量的视觉token做出高精度的视觉推理,例如LLaVa使用576 image patches as visual tokens,这相较于纯文本带来了6.2倍的计算时长开销。此外,一些其他工作正在使用提高图像分辨率的方法来缓解MLLM的视觉缺陷,但进一步加剧了计算量

作者想要得到一种方法来在MLLM的图像token输入中,进行压缩,从而进行推理时的加速,且不能太影响下游任务精度。

同时,作者认为先前的方法依赖于大量的实验来确定超参数,他提出的方法需要具有一定的泛化能力,并且超参数确认简单 can be obtained in about 5 minutes for all VL tasks


motivation

  1. 大规模视觉token在MLLM中的存在明显的冗余,MLLMs 的多头注意力机制是单向的,而非真正“全局”的。简而言之,MLLMs 仅将信息从前一个标记传递到后一个标记,其视觉标记通常置于文本问题之前。在这种情况下,它们主要作用是为文本标记提供视觉语义,但实际上其中大部分并未被激活。

img


如图,大部分蓝色部分(不相关语义)实际上几乎不参加推理,图像到文本注意力非常集中。

image-20250527131819120


  1. 作者将确定压缩比例这一超参数的问题转换成一个统计问题。将压缩问题转换为这样的问题:给定一个采样样本集合DD, 再给定一个计算开销δ\delta ,设压缩策略为PP, 目标是找到一个压缩比够大(满足计算开销到δ\delta以下)的PP使得在DD上整体的注意力分布变化最小

方法

作者只对多头注意力层进行修剪

image-20250527155650905


得到修剪策略

对于采样样本集DD, 计算每一层的视觉token自注意力和视觉-文本交叉注意力。假设视觉token数N,文本token数M,第i层的第j个视觉token的平均注意力为 as,ci,j=m=1NAm,jia_{s,c}^{i,j}=\sum_{m=1}^{N}A_{m,j}^{i}, s和c分别代表自注意和交叉注意,A代表是在DD上取的平均

移除策略P可以建模成[t1,t2,...tk][t_1^*, t_2^*,...t_k^*] (假设模型有k层)

tit_i^*表示在第i层新移除的token数量,注意前面层移除的token也不会传递给后面层,也就是说移除的总数是单调增的

采用一个注意力相差阈值α\alpha和计算开销δ\delta两者一起控制裁剪,具体来说,δ\delta是提前给定的,α\alpha是二分查找计算出来的值


height:600 width:500


用通俗的话翻译就是:

  1. 将注意力分布的差别简化为平均每个token的自注意力/交叉注意力之和的差别,即是否删除某个token,注意力和的相对变化需要小于阈值α\alpha
  2. 由于只计算和,所以可以对自注意力、交叉注意力两个集合分别按照大小排序 —— 注意力分布变化最小的保证转化为,总是优先考虑删除注意力最小的token
  3. 给定一个阈值α\alpha, 对于每一层遍历,对于自注意力、交叉注意力分别不断尝试删除token,直到注意力变化达到阈值α\alpha, 而这一层最后的策略P,即token删除数量为自注意力删除集合TsT_s和交叉注意力集合TcT_c交集的大小
  4. 现在有了一个删除策略PP, 计算它是否满足计算开销约束(文中并没有具体说是怎么计算的,应该是根据模型的删除后token和参数量估算FLOPS,或者是某种直接测量计算量的工具,用的显卡是单张A100)

  1. 如果满足,说明删除策略PP是可行的,但说不定α\alpha太大删除太多了,需要调小α\alpha;如果不满足,说明删除策略PP不可行,说明α\alpha太小了,需要调大α\alpha。因此,二分查找α\alpha直到找到一个满足计算开销约束的α\alpha,且这个α\alpha的左右区间长度小于阈值ϵ\epsilon(后文实验是0.01),则这个α\alpha对应的删除策略PP就是最终的删除策略。

  2. 最后效果是在满足计算开销约束δ\delta的情况下,尽可能保留更多的视觉token


关于这样的算法最后带来的δα\delta - \alpha关系,作者附了这么一个曲线

image-20250527162141135


根据策略在推理时修剪

在实际推理时,作者将得到的删除策略PP应用到模型中。具体来说,对于每一层的视觉token,按照PP中给定的删除数量进行修剪。

具体删除哪些token呢?作者的方法是,

对于第i层

计算第i层剩余视觉token j的自注意力和asi,ja_s^{i,j}和交叉注意力和aci,ja_c^{i,j},然后将这两个和的乘积作为用于排序的参考,排序之后去除最小的kk个token(kk是删除数量)


实验结果

作者使用 LLaVA-655k 数据集(Liu et al. 2023b)中的 655 个样本(0.1%)来生成剪枝策略

在LLaVA, LLaVA-HR,LLaVA-NEXT三个具有不同大小的视觉token(7B模型,576,1024,2880 tokens)的模型上进行测试,十余个下游任务数据集上进行测试


image-20250527160437182


可以看到,剪枝之后,在保持准确率几乎不下降的情况下, 能够带来计算量的大幅下降

作者还做了其他几组实验

  1. 视觉冗余在不同层级的变化

    采用在不同层级上,随机删除裁剪视觉Token的方法。作者发现,深层次token的冗余度更高,裁剪深层次token几乎不影响准确度,可视化图也表明深层次的注意力几乎集中在最关键的元素中。但具体到每一层的最佳剪枝比例,层间也有比较大的不同


image-20250527161223832


image-20250527161358014


  1. 与baseline的对比

    对比了FastV和ToMe两种裁剪方法,表明了自身的SOTA性质。同时指出,在裁剪程度低的时候大家都差不多,裁剪程度高的时候才显露方法的性能差距

    image-20250527161538762


  1. 样本数量的消融实验

    作者将"LLaVA-655k 数据集(Liu et al. 2023b)中的 655 个样本(0.1%)来生成剪枝策略" 换成1%的数据,发现性能相当。作者进一步推测MLLM层间信息交换的模式可能更多地依赖于模型本身的特性,而在不同的输入样本上有较高的泛化性,FitPrune 方法可以有效地捕捉这种模式。同时下面的表还表明,这个方法有着很强的少样本泛化性,确实是模型的特性而不是样本数据集的特性,在仅有10个样本的时候也能得到非常优秀的策略

image-20250527162201012


结论

作者介绍了一种FitPrune的无训练方法,用于对 MLLMs 进行视觉标记剪枝。通过将标记剪枝问题表述为一个统计问题,FitPrune 旨在最小化注意力分布的偏差,从而实现冗余视觉token的高效剪枝,进而提高计算效率。FitPrune 能够基于少量数据生成最优的剪枝策略,避免了昂贵的手动试验。

Paper reading-Eagle Exploring The Design Space for Multi- modal LLMs with Mixture of Encoders

· 8 min read
ayanami

nvidia的论文, 主要还是实践训练MLLM上的一堆经验


任务

探究通过使用不同的视觉编码器和分辨率来提高MLLM系统性能的不同设计带来的效果


motivation

  1. 解读高分辨率的精细视觉信息是MLLM重要的课题,常用的CLIP-ViT 预训练时候的分辨率只有如224*224或者336*336,对OCR等细粒度信息不够好
  2. 近期研究发现enhanced visual perception显著减少幻觉和提高性能,许多近期MLLM用了混合视觉编码器
    • 有扩大视觉编码器的预训练量和参数的
    • 有将高分辨率编码器和CLIP融合的
    • 也有更复杂的融合和路由,根据任务选用不同编码器,"视觉MoE"的
  3. 但缺乏对此类方法设计的通用考量, 以及综合性的大benchmark

方法

  1. 不同的视觉编码器进行基准测试,寻找更高分辨率自适应的方案
  2. 不同的视觉编码器混合策略做同类比较(论文将近期的混合策略归为了CC,SA,LH等几类)
  3. 寻找多个视觉编码器的最优组合
  4. 改进pre-alignment和数据混合

增加输入分辨率的做法

  • Tiling 将输入分割为子图,CLIP-ViT单独编码
  • 直接放大输入分辨率,并对位置编码进行进行插值

Eagle做的实验:

预训练,LLaVA-1.5 + CLIP 基础模型,和LLaVA相同的 595k 图文对,冻结整个模型,只训练projection layer

SFT: 1809k 多模态对话数据

评估:11个任务,包含VQA任务, OCR/文档/图表理解,视觉中心任务,基于知识的任务


结果 - Strong CLIP

  1. 如果插值,需要unfrozen视觉编码器,否则损害性能。这个结论和以前实验不同。

  2. 输入分辨率和预训练分辨率差越大,插值越掉点

  3. 672分辨率下,插值和子图方法性能差不多,但是考虑效率的话还是插值更好

  4. 进行分辨率adaption,300M的CLIP-ViT性能接近6B的InternVL

按照下表,nvidia着重提了448*448+解锁视觉编码器的方案,300M就达到非常接近SOTA的性能了。


image-20250601233933871


Vision Encoder

选取了以下的encoder

  • 视觉语言对比学习的视觉Encoder,比如CLIP的ViT和OpenCLIP的ConxNeXt;

  • 以目标检测为中心的任务预训练的视觉Encoder,EVA-02

  • OCR上训练的Pix2Struct

  • 分割上预训练的SAM

  • 自监督训练的DINO-V2

对不同预训练的视觉encoder输出的特征图进行resize和插值,使得视觉token数量相同.


结果:

image-20250601234936395


分析:

  • 在freeze的情况下他们通常能在和自己预训练任务相近的MLLM benchmark上实现最佳性能。例如来自CLIP的ConvNeXt进行了图文对齐,因此在TextVQA、SQA任务上时所有编码器里表现的最好的。而Text Recognition任务上训练所得的Pix2Struct视觉编码器,在OCR任务上是表现的最好的。
  • 当跟随CLIP-ViT高分辨率拓展策略,unfreeze视觉编码器时,基本都能有性能提升,也有反超对应domain上训练的视觉编码器的可能性,例如CLIP-ConvNeXt微调后在OCR上性能超过了Pix2Struct。

融合策略:

Refer to caption


  • 序列维度拼接:SA sequence append
  • 通道维度拼接:CC concat channel
  • LLAVA-HR式:LH 将高分辨率特征使用adapter注入低分辨率特征中,维持序列长度、通道维度不变
  • Mini-Gemini式:MG 将高分辨率特征使用local windows cross attention注入到低分辨率的queries中。
  • Deformable Attention式:DA 将MG的local windows变成了Deformable Attention

结果:

image-20250601235208565

  • 融合策略越复杂,性能的提升似乎越差,简单的SA/CC稳定涨点

  • 由于SA需要处理边长的序列长度,所以后面用CC


Pre-Alignment

Refer to caption

考虑对其他的视觉专家进行预先的文本模态对齐,再学会去融合不同视觉专家的特征。因此在目前的两阶段MLLM训练框架之前,添加了一个vision-language pre-alignment training阶段,首先使用next-token prediction监督每个视觉专家的特征+各自单独的projector(与LLaVA原始预训练策略不同)训练,让其与一个冻结的较小语言模型对齐。


  • 进行一个额外的预先对齐,可以比较好提升MLLM性能。
  • 预对齐后,再合并所有的视觉专家,训练projector和encoder
  • 虽然在 SFT 期间解冻视觉专家有助于通过更新视觉专家以适应语言模型来提高性能,但预对齐策略更有效地减轻了每位视觉专家的固有偏差,并稳定了训练过程,从而提高了整体性能 (unfreeze + pre-align效果加性

Fusion choice

w h:600


采用上述的3阶段训练和最好最简单的Channel concat策略,就可以进一步研究哪种视觉编码器组合最好。组合的策略是依次增加模型视觉编码器的数量,每次的选择基于上一个数量下最好的组合进行进一步添加。四到五个编码器(X4, X5)目前看来就已经比较合适了。

最佳组合是 CLIPConvNeXtSAMPix2StructEVA-02


最终和benchmark的比较

Refer to caption


高分辨率的文档任务的展示: 红色baseline失败,蓝色eagle成功

h:600


结论

  1. MLLM训练期间解锁视觉编码器matters
  2. 设计先进的融合策略并不能较简单的通道级联显露优势
  3. 更多的视觉专家MoE能带来持续增益,是增强MLLM能力的有效途径
  4. 视觉专家如果开始时候设计的任务和文本无关(没有对齐),用冻结的LLM进行预对齐(+解锁)后再整体训练能显著提升性能

RAG的一些思考与细节

· 13 min read
ayanami

Langchain needle in haystack 实验

长上下文之后,越后面的部分的事实性细节越容易找,尤其是多事实的情况下

引发的一个思考是 rerank 时是否需要将最关注的块放在 prompt 的最后面,也就是倒序?

  • 后补: 但其实又有attention sink相关的研究,可能还是需要具体任务具体测试分析

image-20250417222048515

Maybe recency bias in LLMs:只记得最近的了

No retrieval guarantees

image-20250417222613216

query analysis:将 question 联系到正确的文档

routing (to right DB)

full doc -> summary -> embedding: doc 中噪声非常大, summary 是必要的,语义层次的保留 level 通过 prompt 保证

self-reflection 听起来很美好,但实际常常用不到,太慢了,并且搜不出来更多是前期处理没做好,再换着花样也很难搜出来

HyDE 对于高度 Domain Knowledge 和抽象性理解的任务基本没用:

一些自己的解释

  1. 能否生成正确的假设文档, 难
  2. 即使通过先行的小批量搜索教导 LLM 根据这些 example 生成假设文档,也很难让 LLM 从这些文档中抽取某个泛化的问题,经常会 过度 specific 而导致后续漏掉文档
  3. 目前实验下来垂域脏文档类型最好的解决方案还是 reranker,embedder 如果不微调分布太接近了,例如全部的 chunk 都在 0.5~0.6 之间,意义寥寥

和数据分析的结合:

分析波动->(数据分析)找出波动的阶段-> 对每个波动的阶段做查询

GraphRag 这种 KG-based 的方法经常强调“对整个数据集信息的整合”

但这个要分领域,例如,个人知识库之中,这是好的

但垂域的知识文档常常是相似的格式,固定的路由,同时信息的整合关键不在“多实体”的关系上,而是在于“单个实体随时间的变化”上。

又或者说实体关系 R(e1,e2)R(e_1, e_2) 本身应该建模成一个包含时间的 R(e1,e2,t)R(e_1, e_2, t)

如果仅仅是靠新加入的文档来动态更新 KG 的话,滞后性会很强

在这种半结构化的模板式文档中,LLM 实际上在干一个 Fuzzy DB manager, 提取信息,充当一个搜索引擎

利用 KG 进行某种意义上的多跳推理本质上也只是对文档的多次检索,推理跳数越多,关系越复杂,离线生成 KG 就越难,不是所有领域都像是法律一样有一个明确的 A 判例引用 BCD 法条的连接关系的,这样复杂的 KG 在要想随时间变化也更不可能

从某种意义上来说,KG 是在横向生成,而类似金融这种领域的 RAG 做的是纵向的 Timeline, 这部分对于关键实体是有数据的,并且可能数据都不需要自己做(例如各种行情的图),而离线准备好这些 timeline 之后,如何在 timeline 上进行一个跳跃和查询分析才是关键的。

如果从 DB 的角度上分析的话,金融领域这种关注点快速变化的 RAG 系统(with cache)也就相当于 lazy generated timeseries DB 了,例如问了一个 A 的价格变化,就像是生成了一个 time, delta_price, event(detail) 的 timeseries DB 表,把生成 reason 这样的 LLM 工作 lazy 化了而已

chunk 的前总结和后总结(离线在线)

离线总结最大的问题在于总结哪些方面,实际上是文档预处理的一个部分

最简单的方法就是整个提示模板每个 chunk 问一次 LLM,有 langchain 的 map reduce 等稍微 high level 一点的工具可以支持这个事情

对长文档总结更有效一些的做法是利用好 embedding,先对 chunk embedding 做聚类,再每个聚类里面抽几个 chunk, 从而保证多样性和 chunk 数量的平衡

后总结,或者说 query-based 总结大体上是用 LLM 做比较多,但对于时延和开销的增加太高了,一个比较新的方法是 paragraph sentence-level mask bert(自己造的词),在段落中根据 q, d 的交叉编码得到句子级别的二进制掩码,从而删除无关部分。有一篇 ICLR2025 基于 bge 训了个,https://huggingface.co/blog/nadiinchi/provence

provence效果非常好,又快又几乎对齐例如GPT4.1这种顶级模型的效果

另一个思路就是绕过这个问题,切小块,依赖 rerank 和重新合并乃至知识图谱检索之类的策略保证相关性,也就是在查询完之后是合并还是切分的思路差距

半结构化数据

https://docs.superlinked.com/getting-started/installation 聚焦半结构化的异构数据,例如朴素 embedding 方案对数字的理解不足,无法建模 1-99 的相似度分数与 higher/lower 这种文本的关系

https://github.com/microsoft/multifield-adaptive-retrieval 做多字段的权重学习(自适应选择查询应该着重的权重)

embedding 相关的调优

colbert架构是一个better embedding的方向,其核心在于将文档的token level embedding保存下来,对于每一个query token,计算maxsim算子得到单token的score,再求和

img

对比朴素embedding方案,它在token level进行计算可以很好的带来类似关键词匹配的效果,有效避免长文档下,embedding过于平均化余弦相似太不敏感的问题

对比rerank方案,它的优点又在嵌入矩阵可以离线计算,不需要完全在线的交叉编码器

引入方案: https://python.langchain.com/docs/integrations/providers/ragatouille/

Prompt

基本没有什么特别通用的工作,但值得一提的是将prompt作为一个优化变量,使用LLM在Trajatory上进行采样和跑各种论文的“prompt优化算法”的解耦框架dsPy https://dspy.ai/ 用户以类似类型/对象系统的简短注释提供给dspy作为“初始意图”,而后续复杂的提示由dspy生成,核心思想是让用户专注于编程

class CheckCitationFaithfulness(dspy.Signature):
"""Verify that the text is based on the provided context."""

context: str = dspy.InputField(desc="facts here are assumed to be true")
text: str = dspy.InputField()
faithfulness: bool = dspy.OutputField()
evidence: dict[str, list[str]] = dspy.OutputField(desc="Supporting evidence for claims")

context = "The 21-year-old made seven appearances for the Hammers and netted his only goal for them in a Europa League qualification round match against Andorran side FC Lustrains last season. Lee had two loan spells in League One last term, with Blackpool and then Colchester United. He scored twice for the U's but was unable to save them from relegation. The length of Lee's contract with the promoted Tykes has not been revealed. Find all the latest football transfers on our dedicated page."

text = "Lee scored 3 goals for Colchester United."

faithfulness = dspy.ChainOfThought(CheckCitationFaithfulness)
faithfulness(context=context, text=text)

DSPy 中的不同优化器将通过为每个模块合成良好的小样本示例 (如 dspy.BootstrapRS 1 ) 来调整程序的质量;为每个提示提出并智能地探索更好的自然语言指令 (如 dspy.MIPROv2 2 ) ,以及为您的模块构建数据集并使用它们来微调系统中的 LM 权重 (如 dspy.BootstrapFinetune 3 )

LLM评估

测试不可靠:有多少答案是被记忆出来的?

有多篇相关的paper在讨论这个问题,然后采用了一些方法来衡量这个事情,例如,在数学问题题集中,替换无关的描述、修改数字等等,看看模型性能变差多少

类似数学问题集这种在网络上数据中很难过滤干净,还需要考虑多语言影响

另一些评估指标如ARC-AGI通过抽象图像智力问题集来评估模型的推理能力,相对来说泄题风险小一些(并且有隐藏test set)

  • 丢给LLM的时候不是图像,而是矩阵,用数字表示不同颜色

image-20250505134411046

Chatbot Arena: 让全世界的人都来进行判断哪个模型好

但还是有办法hack: 更fit人的倾向(粗体字、分点、emoji.....)

Elo Score 考虑除了人的直接倾向之外其他因素的影响,在BF模型计算时加上一项β0\beta_0, 11+exp(βiβj+β0)=Eij\frac{1}{1 + exp(\beta_i - \beta_j + \beta_0)} = E_{ij}, EijE_{ij} 是模型i和j的胜率,βi\beta_i 是模型i的真实评分,β0\beta_0 是一个全局偏差项,表示人类评估者的偏好。通过最大化似然函数来估计参数βi\beta_iβ0\beta_0,从而得到模型的真实评分。

β0=γ1长度差+γ2emoji个数差+γ3...\beta_0 = \gamma_1 * 长度差 + \gamma_2 * emoji个数差 + \gamma_3 * ...

image-20250505135351256

可以看到,考不考虑这个β0\beta_0,模型的排名差别很大

Goodhart's Law

一旦一项指标被用作目标,它就不再是一个好的指标

http://becomingahacker.org/integrating-agentic-rag-with-mcp-servers-technical-implementation-guide-1aba8fd4e442

However, traditional RAG has limitations: it usually queries a single data source and only performs one retrieval pass, so if the initial results are poor or the query is phrased oddly, the answer will suffer 但是,传统的 RAG 存在局限性:它通常查询单个数据源,并且只执行一次检索传递,因此如果初始结果不佳或查询措辞奇怪,答案将受到影响

There’s no built-in mechanism for the system to reason about how to retrieve better information or to use additional tools if needed. 系统没有内置机制来推理如何检索更好的信息或在需要时使用其他工具。

关于结构化输出的另一篇特别好的文章: https://www.boundaryml.com/blog/schema-aligned-parsing

推理加速:是对的,例如huggingface-text-embedding项目,将各种转trt/onnx 可以让吞吐提升5x

H100 bge-reranker-v2-m3 1024 * 512char sentence, 13s -> 2.3s

关键词抽取

基于主题LDA,词典等

小模型方法:先用spaCy、hanLP等得到语法树,再从语法树中拿到名词性关键词等

无监督,经典如YAKE!综合考虑词频,词位,共现等。可以考虑https://github.com/JackHCC/Chinese-Keyphrase-Extraction

一篇非常有insight的blog:上下文相关!=上下文充足,定量充足性和它的应用

https://research.google/blog/deeper-insights-into-retrieval-augmented-generation-the-role-of-sufficient-context/

Paper reading - Interleaved Scene Graph for Interleaved Text-and-Image Generation Assessment

· 5 min read
ayanami

开发了一个交错文本和图像生成综合评估框架ISG

使用scene graph捕获文本和图像的关系,提供四个级别的评估:整体的、结构性的、块级别和特定于图像的,并引入了一个新benchmark,ISG-BENCH

作者实验认为现有模型在端到端生成文本图像交错内容时,效果不好,于是做了一个Agent来完成这个任务


motivation

image-20250530152606364

如图,现有MLLM不能直接生成交错文本和图像内容,需要将生成图像部分交给SD等外部模型再组合,带来了更大的开销与不一致性


为了专注这一任务,作者的Benchmark优先考虑视觉为中心的任务,例如风格迁移等图像输出的特定要求。

  • 作者的数据集和人工标注比较有较高Pearson相似度,以此说明准确性
  • 作者表示先前没什么benchmark主要以视觉为中心,以此说明新颖度
  • 但有一说一,作者的表还是有点不公平的,例如它自己的sample很少(一千多),同时评估级别是自己提出的这个四级别评估

作者的表

image-20250530160048840


方法

image-20250530153213139 h:500

注意点: 中间看起来很复杂, 实际上是很多组prompt完成的


评估框架将query拆成scene-graph-like structure,其中图文作为节点,而它们的关系作为边

在整体,结构,块和图四级别的评估中,每个级别都会生成一些用于评估的QA对。作者的意图是,让整体和结构评估连贯性和整体质量,块和图像评估指令完成的细节


结构性:用一个LLM预估图文交替内容的结构,然后与实际生成的内容进行比较

image-20250530163448151


整体:MLLM-as-a-Judge和CoT,用1-10打分配合Yes/No判断

块: 将prompt P用LLM表示成三元组 (subj, obj, rel),再用LLM生成问题,并用VQA评估

image-20250530163519317


图像:从prompt 给的图像中用LLM抽出三元组关系和实体,判断query类别,根据类别不同使用不同的prompt产生判断的VQA,例如如果是"How to",则需要包含特定实体,如果是“Painting”,则需要图像的准确生成

image-20250530163331400 h:600


实验结果

所有统一模型在按照说明生成交错文本和图像内容方面都存在重大缺陷。许多模型只生成 1 到 3 张图像,而有些模型根本无法生成任何图像。

整体评估结果与三个细粒度级别的评估结果之间的不一致表明,即使同时提供用户指示和正确的黄金答案,MLLM-as-a-Judge 在全面评估回答方面也存在显着局限性。具体来说,Judge MLLM 努力根据细粒度的标准评估响应,例如输出结构(包括图像数量)和提示中规定的详细文本-图像关系。此外,我们对结果的分析揭示了 MLLM-as-a-Judge 中固有的偏见,即“图像质量偏见”,即具有更高质量图像内容的回答始终获得更高的分数,尽管这些回答可能违反用户的指导要求和评判指南。这种偏见表明,即使获得了黄金答案,MLLM-as-a-Judge 仍然无法正确地对符合指定要求的交错回答进行准确评估。


image-20250530160948640


效果展示: 跑一次它这个Benchmark要60美刀

image-20250530163815015 h:600


结论

  1. MLLM-as-a-judge存在图像质量bias
  2. 现有端到端MLLM生成图文内容效果不佳, 可能需要在工程性上的agent做补救

美团技术博客阅读

· 19 min read
ayanami

美团外卖基于GPU的向量检索系统实践

美团外卖的向量检索系统使用了GPU来加速向量检索过程。该系统主要包括以下几个方面: 美团外卖业务特点具有较强的Location Based Service(LBS)依赖,即商家的配送范围,决定了用户所能点餐的商家列表。以商品向量检索场景为例:向量检索结果集需要经过“可配送商家列表”过滤。

美团外卖向量检索基于Elasticsearch+FAISS进行搭建,实现了10亿级别+高维向量集的标量+向量混合检索的能力。为了在保证业务高召回率的同时进一步减少检索时间,我们探索基于GPU的向量检索,并实现了一套通用的检索系统。

相继使用了HNSW(Hierarchical Navigable Small World),IVF(Inverted File),IVF-PQ(Inverted File with Product Quantization)以及IVF-PQ+Refine等算法,基于CPU实现了向量检索能力

在HNSW算法中,这种导航小世界图的层次结构使得搜索过程可以从图的高层开始,快速定位到目标点的大致位置,然后逐层向下精细化搜索,最终在底层找到最近邻,在通用检索场景上有显著的优势。然而该算法在高过滤比下性能会有折损,从而导致在到家搜推这种强LBS过滤场景下会暴露其性能的劣势。业界有较多相关的benchmark可以参考,以Yahoo的向量检索系统Vespa相关博客为例

图片

索引吞吐

Observations: 观察结果:

  • Indexing throughput depends on corpus size for Annoy and HNSW, where throughput is halved when corpus size is increased by 10x. 对于 Annoy 和 HNSW,索引吞吐量取决于语料库大小,当语料库大小增加 10 倍时,吞吐量就会减半。
  • Indexing throughput for RPLSH is independent of corpus size. RPLSH 的索引吞吐量与语料库大小无关。
  • Annoy is 4.5 to 5 times faster than HNSW. Annoy 比 HNSW 快 4.5 到 5 倍
  • RPLSH is 23 to 24 times faster than HNSW at 1M documents. 对于 1M 文档,RPLSH 的速度比 HNSW 快 23 到 24 倍

img

查询吞吐

Observations: 观察结果:

  • HNSW outperforms Annoy and RPLSH. At corpus size 1M the QPS is 9 times as high as Annoy, and 16 times as high as RPLSH at comparable quality. Similar observations between hnswlib and Annoy are found in ANN Benchmarks, where the QPS of hnswlib is 5-10 times higher at the same quality on all tested datasets. HNSW 的表现优于 Annoy 和 RPLSH。在 1M 语料库规模下,其每秒查询速度 (QPS) 是 Annoy 的 9 倍 ,在同等质量下是 RPLSH 的 16 倍 。在 ANN 基准测试中也发现了 hnswlib 与 Annoy 之间的类似现象:在所有测试数据集上,相同质量下 hnswlib 的每秒查询速度 (QPS) 比 Annoy 高 5-10 倍。
  • HNSW 搜索算法很大程度上取决于节点之间的链接数量,而链接数量又取决于语料库的大小。当语料库规模增加 10 倍时,QPS 会减半。在索引过程中,我们也看到了同样的情况,因为它使用搜索算法来查找要连接的候选节点。

img

内存占用

Observations: 观察结果:

  • The Annoy index is almost 3 times larger than the HNSW index, which results in ~40% more total memory usage in the 1M SIFT dataset. Annoy 索引几乎比 HNSW 索引大 3 倍,这导致 1M SIFT 数据集的总内存使用量增加约 40%。
  • Both indexes are independent of dimension size, but max points in a leaf node (Annoy) and max links per level (HNSW) might need adjustments with higher dimensionality to get decent quality. 这两个索引都与维度大小无关,但叶节点中的最大点数(Annoy)和每级的最大链接数(HNSW)可能需要使用更高的维度进行调整才能获得不错的质量。

博客给出了一个很重要的观察是:当超过 90% 到 95% 的文档被过滤掉时,过滤后计算精确最近邻比搜索 HNSW 索引(过滤器会丢弃候选匹配项)的成本更低

2.2 IVF (Inverted File)

IVF是一种基于倒排索引的方法,它将高维向量空间分为多个簇(Cluster),每个簇对应一个倒排列表,存储了属于该簇的向量索引。这种方法大大减少了搜索时需要比较的向量数量,从而提高了检索速度。它的缺点是需要存储原始的向量数据,同时为了保证检索性能需要将其全量加载到内存中,从而占用了大量的内存空间,容易造成内存资源瓶颈。

2.3 IVF-PQ(Inverted File with Product Quantization)

在候选集数量巨大的场景下,比如商品向量检索场景下,IVF带来的内存空间大的问题很快就显现出来,为了解决内存空间的问题,开始尝试使用了IVF-PQ方法。该方法在IVF的基础上,使用了乘积量化(Product Quantization,PQ)的方法来压缩向量数据。PQ将高维向量分为多个子向量,然后对每个子向量进行量化,从而大大减少了对内存空间的需求。

然而,由于量化过程会引入误差,因此IVF-PQ的检索精度会低于IVF,从而导致召回率无法满足线上要求,对召回率要求相对较低的场景可以使用IVF-PQ,对召回率有一定要求的场景需要其他解决方案。

2.4 IVF-PQ+Refine

为了提高IVF-PQ的检索精度,进一步采用了IVF-PQ+Refine的方案,在IVF-PQ的基础上,在SSD磁盘上保存了未经压缩的原始向量数据。检索时,通过IVF-PQ召回数量更大的候选向量集合,然后获取对应的原始向量数据进行精确计算,从而提高检索精度。这种方法既保留了IVF-PQ的存储优势,解决了内存资源瓶颈,又保证了召回率,因此在实际应用中得到了广泛的使用。

2.5 基于地理位置的向量检索

通过将经纬度编码为向量,优化具体做法是将用户或商家的经纬度以加权的方式加入查询Query和候选向量中,在计算Query和候选向量的相似度时,距离因素就可以在不同程度上影响最终的检索结果,从而达到让向量索引具备LBS属性的目标。

这里没有细讲,但怎么具体怎么融入的LBS属性还是比较有意思的,最直接的方法是将经纬度信息直接拼接到现有的文本embedding向量上,也可以将经纬度用geohash,或者以用户为中心的极坐标系统表示?

我觉得最复杂的在于:

  • 如何确定经纬度特征的维度,这也算是一种权值
  • 如何让经纬度特征和其他向量特征上对齐?美团是否有一个专用的embedding模型来嵌入地理信息特征,这个模型又是根据什么进行微调的?是类似推荐系统那种基于用户反馈的,还是内部有一个地理加权的人工设计公式,这个模型提供的地理特征使得整体效果向这个公式靠齐的?

https://docs.google.com/document/d/1R5nOiwFUn9ZJtuWywmos2yfB4aCWCGy1TUN5VAnMRaY/edit?usp=sharing

考虑到美团外卖的业务场景,目标方案应该满足以下要求:

  • 支持向量+标量混合检索:在向量检索的基础上,支持复杂的标量过滤条件。
  • 高过滤比:标量作为过滤条件,有较高的过滤比(大于99%),过滤后候选集大(以外卖商品为例,符合LBS过滤的商品向量候选集仍然超过百万)。
  • 高召回率:召回率需要在95%+水平。
  • 高性能:在满足高召回率的前提下,检索耗时Tp99控制在20ms以内。
  • 数据量:需要支持上亿级别的候选集规模。

实现向量+标量混合检索,一般有两种方式:前置过滤(pre-filter)和后置过滤(post-filter)。前置过滤指先对全体数据进行标量过滤,得到候选结果集,然后在候选结果集中进行向量检索,得到TopK结果。后置过滤指先进行向量检索,得到TopK*N个检索结果,再对这些结果进行标量过滤,得到最终的TopK结果。其中N为扩召回倍数,主要是为了缓解向量检索结果被标量检索条件过滤,导致最终结果数不足K个的问题。

业界已有较多的成熟的全库检索的方案,后置过滤方案可以尽量复用现有框架,开发量小、风险低,因此我们优先考虑后置过滤方案。我们基于GPU的后置过滤方案快速实现了一版向量检索引擎,并验证其召回率与检索性能。GPU中成熟的检索算法有Flat、IVFFlat和IVFPQ等,在不做扩召回的情况下,召回率偏低,因此我们在benchmark上选择了较大的扩召回倍数以提高召回率。

图片

测试结果表明,以上三种算法均无法同时满足我们对检索性能和召回率的需求。其中IVF与IVFPQ召回率较低,Flat算法虽然召回率较高,但是与全体候选集计算向量相似度导致其性能较差。

根据用户的地理位置信息计算其GeoHash值,并扩展至附近9个或25个GeoHash块,在这些GeoHash块内采用Flat算法进行向量检索,可以有效减少计算量。这种向量子空间划分方式有效地提高了检索性能,但是存在某些距离稍远的商家无法被召回的情况,最终测得的召回率只有80%左右,无法满足要求。

综上,后置过滤方案无法同时满足检索性能和召回率的需求,而GPU版本的Faiss无法实现前置过滤功能,考虑到美团外卖的业务场景,向量+标量混合检索能力是最基本的要求,因此我们决定自研GPU向量检索引擎。

基于GPU的向量检索,要想实现前置过滤,一般有三种实现方案:

  1. 所有原始数据都保存在GPU显存中,由GPU完成前置过滤,再进行向量计算。
  2. 所有原始数据都保存在CPU内存中,在CPU内存中完成前置过滤,将过滤后的原始向量数据传给GPU进行向量计算。(能存更大的数据集)
  3. 原始向量数据保存在GPU显存中,其他标量数据保存在CPU内存中,在CPU内存完成标量过滤后,将过滤结果的下标传给GPU,GPU根据下标从显存中获取向量数据进行计算。(省显存带宽)

由于GPU与CPU结构与功能上的差异性,使用GPU完成前置过滤,显存资源占用量更大,过滤性能较差,且无法充分利用过滤比大的业务特点,因此不考虑方案1

图片

实验结果表明,方案2在数据拷贝阶段耗时严重,时延无法达到要求。因为在美团外卖的场景下,过滤后的数据集仍然很大,这对CPU到GPU之间的数据传输带宽(A30显卡带宽数据如下 CPU-GPU:PCIe Gen4: 64GB/s;GPU-GPU:933GB/s)提出了很高的要求,因此我们最终选择了方案3。

考虑到显存的价格远高于内存,因此我们在设计方案的过程中,尽可能将数据存储在内存当中,仅将需要GPU计算的数据存储在显存当中。

内存中保存了所有的标量数据,数据按列存储,通过位置索引可以快速找到某条数据的所有字段信息,数据按列存储具备较高的灵活性和可扩展性,同时也更容易进行数据压缩和计算加速。针对需要用于过滤的标量字段,在内存中构造了倒排索引,倒排链中保存了对应的原始数据位置索引信息,内存数据结构如下图所示

图片

显存中保存了所有的向量数据,数据位置索引与内存中的数据一一对应,可以通过位置索引快速获取某条数据的向量信息,如下图所示:

图片

最后的流程图(Flat)

图片

最后的流程图(IVF),放宽召回率,提升性能

图片

图片

可见,无论是Flat还是IVF,在相同的召回率下,使用前置过滤的性能都要明显好于后置过滤。

性能优化

  • 高并发支持,通过Cuda Stream,GPU可以并行处理多个查询请求,高并发压测下,GPU利用率可以达到100%。

  • 通过GPU实现部分标量过滤功能,支持在GPU上实现部分标量过滤功能,向量计算与标量过滤同处一个Kernel,充分利用GPU并行计算能力

  • 资源管理优化,支持句柄机制,资源预先分配,重复利用。每个句柄持有一部分私有资源,包含保存向量检索中间计算结果的可读写内存、显存,以及单独的Cuda Stream执行流;共享一份全局只读公有资源。在初始化阶段,创建句柄对象池,可以通过控制句柄数量,来调整服务端并发能力,避免服务被打爆。在检索阶段,每次向量检索需从句柄对象池中申请一个空闲的句柄,然后进行后续的计算流程,并在执行完后释放响应的句柄,达到资源回收和重复利用的目的

图片

我们最终选择了单机多卡的数据分片方案,单台服务器部署多张GPU,检索时并行从本地多张GPU中检索数据,在CPU内存中进行数据合并。

为了支持更大规模的向量数据检索,我们还在GPU检索引擎上支持了半精度计算,使用FP16替换原来的FP32进行计算,可以节省一半的GPU显存占用,经验证Flat召回率由100%下降到99.4%,依然满足需求。使用半精度之后,单机可以加载近10亿数据,足够支撑较长时间的业务数据增长。

GPU 检索系统上线后实际性能数据如下(数据量1亿+):

图片


22年还有一篇早期的搜索基于elasticsearch的优化实践

https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651772026&idx=1&sn=6ff4cb024bb416c46d5d2850a6ae77d1&chksm=bd120d378a6584217f1838c0f951204023e5c32b0ad413a731078e2f11f8f0009b39c3dec4ea&scene=21#wechat_redirect

但这个就很工程很机架了

李沐dl笔记

· 30 min read
ayanami

vgg

内存占用大,推理慢(深),但效果好

卷积层参数小,全连接层最大问题是参数太大过拟合

所以最后一层全连接是很大的问题

大参数还有内存 bound 的问题

NiN

用卷积层替代全连接

两个 1*1 卷积无 padding, stride1 起全连接的作用(只做通道混合)

每个卷积后面跟两个全连接作为 NiN block

交替使用 NiN 块和 stride = 2 的 maxpooling 逐步减小高宽和增大通道数

最后使用全局平均池化得到输出(通道数 = 分类个数)

打印结构:

for layer in net:
X = layer(X)
print(layer.__class__.__name__, "output shape:\t", X.shape)

超级宽的 hidden layer: 非常容易过拟合

泛化性提高-> 收敛变慢

全连接的方案: 非常强, 收敛很快

GoogLeNet

inception 块: 不做选择, 全都要

output = output1 + o2 + o3 + o4

o1 = conv1x1

o2 = conv1x1 + conv3x3, padding 1

o3 = conv1x1 + conv3x3, padding 1 + conv5x5, padding 2

o4 = 3x3 maxpool, padding 1

四条路径从不同层面抽取信息, 在输出通道合并 concatenation

四条路径分配不同的通道数(你认为那种模式哪个通道的信息更重要)

降低通道数来控制模型复杂度

googlenet 5 段, 9 个 inception 块

不降低维数的 1x1 卷积就是通道融合

第一个 stage 总是把通道数拉上去, 高宽减下去, 先筛选出足够多的特征

v2: batch normalization

v3: 5x5-> 3x3, 5x5-> 1x7+7x1(单长和单宽)

v4: 残差连接

优点是模型参数少, 计算复杂度低

批量归一化

损失出现在最后, 后面的层训练快

反向传播: loss 在顶层, 数据在最底部, 底部的层(前面的层)训练慢, 底部层一变, 所有都得跟着变

导致离 loss 近的后面层需要重新学习多次, 导致收敛变慢

有没有方法让学习前面层的时候避免变化后面层?

批量归一化: 将分布固定, 来让输出模式稳定一些, 固定小批量的均值和方差

正则化, 将数据分布固定为 N(0,1)N(0,1) 正态分布, 数据的修改只是在变化正态分布的超参数, 限制变化不要太剧烈

对于全连接, 作用 在激活函数前面, 作用在特征维度

对卷积, 作用在通道维

效果太好了, 原始论文觉得是减少内部协变量转移, 后续发现 可能就是等效于在每个小批量里面加入噪音来控制模型, 均值近似于随机偏移, 方差近似于随机缩放

因此没必要和丢弃混合使用

加速收敛(模式更稳定之后可以把 lr 调得更大), 但一般不改变模型精度

根据内存挑 batch size, 不能太大也不能太小, 然后调学习率和 epoch

ResNet

残差的重要性不必多言

深网络必有残差思想

新硬件

DSP 主要做数字计算处理长指令, FPGA 可编程阵列

工具链质量良莠不齐, 一次 "编译" 需要很久

AI ASIC: Google TPU eg

核心 systolic array, 专门做大矩阵乘法 2d 计算单元(PE)阵列, 卷积换成矩阵乘法

一般的矩阵乘: 切开和填充匹配 SA 大小

批量输入来降低延时, 其他硬件单元来处理别的 NN 操作子, 例如激活层

多卡并行

数据并行(切割小批量), 模型并行(切割模型, 适用于模型太大的时候),

all reduce: 将所有 gpu 的结果放到一个 gpu 上, 然后相加, 加完再复制回其他 gpu

nn.parallel.scatter

nn.DataParallel

多卡时也要相应的加大 batchsize 和 lr

大 batch size 在小模型上会采出重复样本导致浪费和一定程度上的过拟合

分布式

GPU 和 GPU 通信快, 和 CPU 通信慢, 和交换机网卡更慢

  • 类似存储器山

解法是把 parameter server 尽量从 cpu 搬到 gpu 上

这样简单的 parameter 迁移分配就能在 gpu 本地完成, 不涉及到 cpu 的 copy(感觉像 DMA)

每个 worker 拿参数, 复制到 GPU 上, 每个 GPU 算自己的梯度, GPU 梯度求和, 传回服务器, 再更新, 回发

类似 mr, server mapper, 本地 gpu 完成计算和 combine, 在 server reduce

同步 SGD, 每个 worker 同步计算一个批量

所以需要调 batch size, 来针对并行省下的时间与通信开销做 trade off

实践:

  • 大数据集
  • 好的 GPU-GPU 和机器-机器带宽
  • 高效的数据读取和预处理
  • 好的计算(FLOP)和通信(model size)比 Inception > ResNet > AlexNet
  • 足够大的 batch size
  • 高效优化算法(因为 batch size 变大了, 如何适配)
  • 更复杂的分布式有异步, 模型并行

一般 N 个类, batch size 差不多到 10N 再往上就不太能 fit 了

数据增广

已有数据集让他有更多多样性

  • 在语言里面加背景噪音
  • 改变图片的亮度, 颜色, 形状

一般的做法: 原始数据在线生成, 随机增强

测试不做增强

翻转:

  • 左右翻转, 上下翻转
  • 切割, 随即高宽比, 随机大小, 随机位置

其他:

  • 高斯模糊
  • 锐化
  • 变形
  • 滤镜
  • 马赛克(相当于遮挡, 逼着去看全局)
  • ...

从实际部署的场景反推需要什么样的增强

异常检测, 偏斜数据, 重采样, 增广

mixup 增广: 有效但不知道为什么

torchvision.transforms

微调(迁移学习)

标注一个数据集很贵

希望在大数据集上做好的东西, 能以小代价迁移到小数据集上

神经网络分层两块: 特征提取+线性分类

dl: 让特征提取变得可以学习, 而不是人来提取特征

训练:

  • 更强正则化
  • 更小学习率
  • 更少的数据迭代

源数据集远复杂于目标, 微调效果更好

固定一些层, 固定底部一些层的参数, 不参与更新

低层次的特征更加通用

小 trick, 微调的时候最后一层用大学习率, 前面用小的

迁移的也不能差太大, 否则效果很可能不够好

目标检测

bounding box

锚框: 提出多个被称为锚框的区域, 预测每个框里面是否有关注的物体, 如果是, 预测锚框到真实框的偏移

交并比 IoU

每个锚框是一个训练样本, 要么标注成背景, 要么关联一个真实边缘框

可能生成大量锚框, 导致大量的负类样本

选择合适的锚框(赋予锚框标号):

先生成一堆框, 之后算锚框 i 和真实框 j 的 IoU, 在 i, j 之中找最大的, 就得到了一组锚框和真实框的对应

然后从集合中剔除这个锚框 i 和边缘框 j(删除矩阵行列), 再找下一组

重复直到真实框为空, 这就是正类样本, 剩下的锚框挑一些作为负类样本

锚框生成: 一种固定切分画格子

NMS 非极大抑制: 合并相似的预测框

  • 选中非背景类的最大预测值
  • 去掉所有和它 IoU 大于阈值的预测
  • 重复直到所有预测要么被选中, 要么被去掉

生成锚框的另一种示例方法

宽度 wsrws\sqrt r, 高度 hs/rhs/\sqrt{r}

对给定几组 (s,r)(s,r) 对每(n)个像素生成

算法的核心之一: 如何生成高质量锚框

锚框到偏移的算法: 多种多样

autogluon

工业界很少用模型融合和测试增强, 计算代价过高

通常固定模型超参数, 简单模型, 精力花在提升数据质量和加入的新数据

RCNN:

启发式搜索算法选择锚框

预训练模型对每个锚框抽取特征

训练一个 SVM 对类别分类

训练一个线性回归来预测偏移

RoI pooling

锚框均匀分割 mxn, 输出每块里面的最大值

不管锚框多大, 总是输出 mn

Fast RCNN

不再对每一个锚框抽取特征

而是将所有的锚框丢进 cnn(输入里面对应的映射区域), 一次 CNN 对整个图片抽取

Faster RCNN: 使用区域提议网络代替启发式搜索来获得更好的锚框

2-stage

Mask RCNN 如果有像素级别的编号, 给每个像素做预测, 用 FCN 利用信息

Faster RCNN: 速度非常慢, 精度高

SSD: single stage

基础网络抽特征, 多个 conv 减半高宽

每段都生成锚框

  • 底部段拟合小物体, 顶部段拟合大物体

对每个锚框预测类别和边缘框

yolo: 追求快

ssd 锚框大量重叠, 浪费计算

均匀切分 SxS 个锚框, 每个锚框预测 B 个边缘框

后续有许多微调和改进

工业常用

非锚框: 例如 central net

语义分割

像素级分类

应用: 背景虚化, 路面分割

另一个相近的概念: 实例分割

数据集: 输入是图片, label 也是图片(每个像素的值就是 label)

crop: 怎么做, 对输入进行裁剪, 在 label 上也要相对应的裁剪

拉伸也是需要特殊处理的

旋转? 一种是可以加一个 label 是旋转角度, 另一个是可以在转完的斜框上涨再画一个大框框住斜框

人像: 难点在光照, 阴影和背景

人像语义分割: pretrain model 已经很成熟

转置卷积

卷积的问题:不能很有效的增加高宽

类似语义分割这种-> 卷积不断减小高宽, 会影响像素级别的输出

Y[i:i+h,j:j+w]+=X[i,j]×KY[i:i+h, j:j+w] += X[i,j] \times K

增大输入高宽

为什么是转置卷积:

卷积等价于矩阵乘法 Y=VXY = VX, 转置卷积就是 Y=VTXY = V^{T}X

nn.ConvTranspose2d

卷积是下采样, 卷积是上采样

转置卷积与线性插值: 可以用线性插值作为转置卷积核的初始值

FCN

全连接卷积神经网络

用 dl 做语义分割的最早工作

用转置卷积替换 CNN 最后的全连接层+全局池化

  • 先过 1x1 conv 压缩通道
  • 再过转置卷积拉大图片, 得到像素级别的切分
    • 思想是每个像素的的 label 信息这个 feature 应该存在 channels 里面

net = nn.Sequential(*list(pretrained_cnn.children()))[:-2]

可以用双线性插值的矩阵初始化转置卷积层的 kernel

loss: 由于每一个像素都有了 label

所以在矩阵上做均值再 cross_entropy

样式迁移

基于 CNN 的样式迁移

核心思想: 训练一个 CNN, 将他作用在内容图片上得到输出, 在样式图片上得到输出

而输出图片在内容层上的输出和内容图片在内容层上的输出相近(content loss)

输出图片在样式层上的输出和样式图片在样式层上的输出相近(style loss)

训练的不是 CNN, 而是输入网络的的“输出图片”

哪些层是“style layer”, 哪些是 "content layer"?

样式: 最小, 中间和上层, 较均匀

  • 样式有全局的特征和局部的特征, 各个尺度均有

内容: 偏末尾的层, 靠近 loss

  • 允许内容上更多的变形

内容损失可以是简单的 MSE

  • 元素值, 通道里面的值, 认为是内容

样式损失? 通道内部和之间的统计分布, 认为是样式

  • 分布匹配, 一阶平均值, 二阶协方差, 用二阶就还不错

最后: tv_loss, 不要有噪点, 每个像素和周围像素不要差太多, 计算每个与周围的 MSE 再求平均

这几个损失如何加起来? 加权平均, 权值是超参数

style 一般更重要, 例如 content:style:tv=1:1000:10

这几个超参数的调整是训练几次之后, 观察三种 loss, 调到差不多大小得出的

不更新: y.detach()

卷积只作为抽特征

麻烦: 后续技术, GAN, 使用 CNN 接收随机输入生成图片等

序列模型

标号和样本是一个东西: 自回归模型 t-k ~ t-1 -> t

方法 A: 马尔可夫假设: 假设当前数据只和 k 个过去数据点相关

方法 B: 潜变量模型: 引入潜变量 hth_t 来表示过去信息 xt=p(xtht)x_t = p(x_t|h_t), ht=f(x1,...xt1)h_t = f(x_1,...x_{t-1})

那我们就可以将预测拆成两步:

  1. ht=Model1(ht1,xt1)h_t = Model1(h_{t-1}, x_{t-1})
  2. xt=Model2(ht,xt1)x_t = Model2(h_t, x_{t-1})

文本预处理

预处理的核心是分词

GPU 上存算的是 token 索引而非字符串

语言模型:

给定文本序列, 估计联合概率

  • 做预训练模型
  • 生成文本
  • 判断多个序列之中哪个更常见

简单方法: 基于计数建模

序列很长的时候, 由于文本量不够大, 可能 n(x1,...xt)1n(x_1,...x_t)\le 1

使用马尔可夫假设缓解, n 元语法假设, 假设只和前 n 个词相关

以二元为例, 则有 p(x1,x2,x3,x4)=n(x1)x1n(x1,x2)n(x1)n(x2,x3)n(x2)n(x3,x4)n(x3)p(x_1,x_2,x_3,x_4) = \frac{n(x_1)}{x1} \frac{n(x_1,x_2)}{n(x1)} \frac{n(x_2,x_3)}{n(x2)} \frac{n(x_3,x_4)}{n(x3)}

RNN

更新隐藏状态: ht=ϕ(Whhht1+Whxxt1+bh)h_t = \phi (W_{hh}h_{t-1} + W_{hx}x_{t-1} + b_h)

输出: ot=ϕ(Whoht+bo)o_t = \phi (W_{ho}h_t + b_o)

训练的模型: Whh,Whx,Who,bh,boW_{hh}, W_{hx}, W_{ho}, b_h, b_o

如果没有 Whhht1W_{hh}h_{t-1} 就是 MLP

loss 设计: 困惑度 perplexity

把输出看成是词典大小为 label 数量的话, 可以用交叉熵, 然后对整个句子取平均

但实际不是用这个, 而是用 exp(平均交叉熵)

梯度裁剪: 在 T 个时间步上的梯度, 反向传播 O(T)矩阵乘法, 梯度爆炸

如果梯度长度超过 θ\theta, 变回 θ\theta

g×min(1,θg)gg \times min(1, \frac{\theta}{||g||}) \to g

更多的 RNN:

  • 1 对多: 文本生成
  • 多对 1: 文本分类
  • 多对多: 问答, 机器翻译
  • 多对多: tag 生成

GRU&LSTM

对于一个序列, 记住相关观察需要 更新门(能关注的机制) + 重置门(能遗忘的机制)

Rt=σ(XtWxr+Ht1Whr+br)R_t = \sigma (X_tW_{xr} + H_{t-1}W_{hr} + b_r) reset gate

Zt=σ(XtWxz+Ht1Whz+bz)Z_t = \sigma (X_tW_{xz} + H_{t-1}W_{hz} + b_z) update gate

候选隐状态

Hcand(t)=tanh(XtWxh+(RtHt1)Whh+bh)H_{cand(t)} = tanh(X_tW_{xh} + (R_t \odot H_{t-1})W_{hh}+b_h)

RtR_t : [0,1][0,1] 软控制

Ht=ZtHt1+(1Zt)Hcand(t)H_t = Z_t \odot H_{t-1} + (1 - Z_t) \odot H_{cand(t)}

隐藏层多大? 例如 128,256, 长序列就 1024

实际不考虑 RNN, 一般 GRU/LSTM

超过 100,1000 这样的长度量级, 考虑 Attention

LSTM

忘记门: 将值朝 0 减少

输入门: 决定是不是忽略输入

输出门: 决定是不是使用隐状态

image-20250121223000822

更深(多个隐藏层)的 RNN, 更多的非线性性

双向 RNN

一个前向 RNN 隐层

一个反向 RNN 隐层

合并两个得到输出

image-20250121230923026

output 是前向和反向的共同贡献

推理怎么推? 非常不适合做推理, 几乎不能推

主要作用: 对句子做特征提取, 填空, 而不是预测未来

输入需要定长(为了以 batch 的形式读入)

如何做不定长的? 填充或者截断, 例如翻译

encoder-decoder 架构

encoder: 将输入编程成中间表达形式(特征)

decoder: 将中间表示解码成输出

encoder 将 state 传给解码器做输入

seq2seq

encoder 是一个 RNN, 可以双向

decoder 是另一个 RNN

编码器是没有输出的 RNN

encoder 最后时间步的 hidden state 作为 decoder 的初始 hidden state

训练, 训练时 decoder 用目标句子作为输入

衡量生成序列的好坏: BLEU

image-20250122095953273

exp 项: 防止 pred 句子长度过短偷懒提高精度

BLEU 越大越好

seq2seq: 从一个句子生成另一个句子

sequence_mask:在序列中屏蔽不相关的项(padding)

拓展 softmax 来屏蔽不相关的预测(padding 对应的 output)

预测

最开始输入 <bos>, 然后 RNN 每次输出作为下一个的输入

seq2seq 可以纯 transformer

束搜索

beam search

seq2seq:用当前时刻预测概率最大词输出(贪心)

但贪心很可能不是最优的

暴搜指数级增长肯定不行

bin search: 对每个时刻, 保存最好的 K 个序列

每一次新预测会对 k 的 kn 个可能的下一个序列之中再调最好的 k 个

如何选择 "最好"?

单纯的概率乘总是倾向于选择短句子, 需要给长句子加权

每个候选的最终分数 1Lαlogp(y1,...yL)\frac{1}{L^{\alpha}}logp(y_1,...y_L), 取 α=0.75<1\alpha=0.75 < 1 给长句子加权

Attention

卷积, 全连接, 池化只考虑“不随意”的线索

  • "最大值", 明显的特征

注意力机制显式地考虑随意线索

  • 随意线索被称为查询 query
  • 每个输入是一个值 value 和不随意线索 key 的对
  • 通过注意力池化层来对有偏向性的选择某些输入

非参(不需要任何先验参数)注意力池化层

给定数据(环境, 先验, context) (xi,yi)(x_i,y_i)

查询: 给定一个 x, 求对应的 y=f(x)y=f(x)

注意力: f(x)=iα(x,xi)yif(x)=\sum_i \alpha(x,x_i)y_i, α(x,xi)\alpha(x,x_i) 就是注意力权重

最简单的方法: 平均池化, f(x)=1niyif(x)=\frac{1}{n}\sum_i y_i

更好的方案是 60 年代的 Nadaraya-Watson 核回归

f(x)=iK(xxi)jK(xxj)yif(x)=\sum_{i} \frac{K(x-x_i)}{\sum_j K(x-x_j)}y_i

高斯核 K(u)=12πexp(u22)K(u)=\frac{1}{\sqrt{2\pi}}exp(-\frac{u^2}{2})

f(x)=isoftmax(12(xxi)2)yif(x)= \sum_{i} softmax(-\frac{1}{2}(x-x_i)^2)y_i

参数化:

再引入可以学习的 w

f(x)=isoftmax(12((xxi)w)2)yif(x)= \sum_{i} softmax(-\frac{1}{2}((x-x_i)w)^2)y_i

相较非参的注意力, 变得更不平滑

拓展到高维度 α(q,ki)\alpha(q, k_i)

  1. Additive Attention: 可学参数 WkRh×kW_k \in R^{h\times k}, WqRh×qW_q \in R^{h\times q}, vRhv \in R^{h}

a(k,q)=vTtanh(Wkk+Wqq)a(k,q) = v^{T}tanh(W_kk + W_qq)

等价于将 kv 合并之后放入一个隐藏大小为 h, 输出大小为 1 的单隐藏层 MLP

也是当 q, k 不一样长的时候最常用的做法

如果 q, k 都是同样长度的

2.Scaled Dot-Product Attention

a(q,ki)=<q,ki>/dka(q,k_i)=<q,k_i>/\sqrt{d_k}, 相当于 q 在 k 基上的分量+归一化

a(Q,K)=QKT/da(Q,K)=QK^{T}/\sqrt{d}

f=softmax(a(Q,K))Vf=softmax(a(Q,K))V

Q, K, V 是一个矩阵?self-attention 自注意力 f=softmax(XXT/d))Xf=softmax(XX^{T}/\sqrt d))X

但实际运用会给 X 做不同线性线性变换后再输入

f=softmax(XWQXTWK/d))XWVf=softmax(XW_QX^{T}W_K/\sqrt d))XW_V

Attention 机制的 seq2seq

翻译的词可能相关于原始句子之中不同的值

原始 seq2seq 只能看到单一词的输入, 虽然有隐藏层, 但长距离丢失信息

  • encoder 的对每个词的输出作为 key 和 value(key = value)

  • decoder RNN 对上一个词的输出是 query

  • attention 的输出和下一个词的 embedding 合并进入 decoder

原始的 seq2seq 相当于是只将上一个词的 state+t-1 时刻的 encoder 输出丢到了 decoder 里面

decoder(statet,eoutputt,outputt1)decoder(state_{t}, eoutput_{t}, output_{t-1})

现在拓展其表达力, 认为 decoder 应该获取的不是单单最后一个词的输出, 而是和之前的词输出(更长的上下文)都有点关系, 具体关系用 attention 学习, 以编码器的 output 作为 query key, 获取这个 output 最相关的上下文, 并认为翻译的文本之中也应该有类似的上下文关系

decoder(statet,attention(outputt1,eoutputs,eoutputs))decoder(state_t, attention(output_{t-1}, eoutputs, eoutputs))

tokenizer: sentencepiece

embedding: 专业词, 需要调整 tokenizer, 需要添加词 pair, 需要训练新添加的 embedding, 正常领域 frozen 不动, 加 LoRA/Adapter

自注意力

self-attention

序列长度是 n, 卷积核大小 k

CNNRNNself-attention
计算复杂度O(knd2)O(knd^2)O(nd2)O(nd^2)O(n2d)O(n^2d)
并行度O(n)O(n)O(1)O(1)O(n)O(n)
最长路径O(n/k)O(n/k)O(n)O(n)O(1)O(1)

自注意力适合处理长文本

代价: 计算代价 n2n^2 增长

位置编码:

和 CNN, RNN 不同, 自注意力没有记录位置的信息, 位置编码将位置信息注入到输入里

  • 输入 XRn×dX\in R^{n\times d}, 叠加位置编码 P, X+P 作为自编码输入

pi,2j=sin(i100002j/d),pi,2j+1=cos(i100002j/d)p_{i,2j}=sin(\frac{i}{10000^{2j/d}}),p_{i,2j+1}=cos(\frac{i}{10000^{2j/d}})

为什么这么设计

ωj=1/100002j/d\omega_j = 1/10000^{2j/d}, pi+δ=RotateMatrix(δωj)×pi,δp_{i+\delta} = RotateMatrix(\delta \omega_j) \times p_{i,\delta}

所以实际上是相对位置的编码

也就是对于同一个序列 j, 位置 i 有 <i, i+k> 的关系的 pair 始终是一个相同的关系

Transformer

纯基于(self-)attention

encoder-decoder 架构

multi-head attention

对同一的 QKV, 希望抽取不同的信息

使用 h 个独特的注意力池化

image-20250122150747190

attention 没有时序信息, encoder 无所谓

decoder 不应该看到不该看到的信息, 需要加入掩码

计算 xix_i 输出时, 假装当前序列长度为 i

基于位置的前馈网络 Positionwise FFN

将输入形状 (b,n,d)(b, n, d) 变换成 (bn,d)(bn, d), 输出再换回来

两层全连接, 添加非线性, 做更多的特征融合

FFN(x)=f(xW1T)W2FFN(x)=f(xW^{T}_{1})W_2

Add&Norm: 残差+归一化

image-20250122152949678

编码器的输出 y_1, ... y_n

作为解码器之中第 i 个 transformer 块之中多头注意力的 key 和 value

预测, t+1 输出

decoder 输入前 t 个预测值作为 key, value, 第 t 个预测还作为 query

Bert

nlp 的迁移学习

使用 pretrain 的模型抽取词句的特征

不更新 pretrain 模型

问题: 1. 做 embedding 的话忽略了时序信息 2.后续模型还要自己设计, 只有 embedding 似乎没有很大用处

Bert: 能不能也通过改最后一层复用?

只有编码器的 transformer

对输入的修改:

  • 每个样本都是一个句子对

  • 加入额外的片段嵌入

  • 位置编码可学习

三种 embedding: position, segment, token

image-20250122155938538

通用的任务?

任务 1: 带掩码的语言模型

带掩码的语言模型每次随机(15%概率)将一些词元换成 <mask>

微调任务之中不出现 <mask>

微调任务是没有 <mask> 标记的,如果设计方案是:只要 token 被选中 mask 处理,并且处理方法只要一种就是 token 别替换为 <mask>,这样的话,预训练任务和微调任务的数据太不一样了。BERT 的 3 种 mask 方法,可以使得,有 20%情况,句子对没有 <mask> 标记。

我理解的说白了就是不仅仅是因为看到了 <mask> 才去找上下文的信息,而是一直保持联系上下文的“习惯”

  • 80%下, 变成 <mask>
  • 10%, 随机(错误的结果)
  • 10%, 原有(正确的结果)

10%的词会被替换成随机词元的原因: 作者在论文中谈到了采取上面的 mask 策略的好处。大致是说采用上面的策略后,Transformer encoder 就不知道会让其预测哪个单词,或者说不知道哪个单词会被随机单词给替换掉,那么它就不得不保持每个输入 token 的一个上下文的表征分布(a distributional contextual representation)。也就是说如果模型学习到了要预测的单词是什么,那么就会丢失对上下文信息的学习,而如果模型训练过程中无法学习到哪个单词会被预测,那么就必须通过学习上下文的信息来判断出需要预测的单词,这样的模型才具有对句子的特征表示能力。另外,由于随机替换相对句子中所有 tokens 的发生概率只有 1.5%(即 15%的 10%),所以并不会影响到模型的语言理解能力。(网上复制的,这是我找到的可以说服我自己的一个解释)

任务 2: 下一句子预测:

训练样本之中, 50%选择相邻句子对, 50%选择随机句子对

微调 Bert

bert 对每一个次元返回抽取了上下文信息的特征向量

不同的任务取不同的特征

  • 句子分类, 将 <cls> 对应的向量输入到 MLP 分类
  • 命名实体识别, 识别一个词元是不是命名实体, 例如人名机构位置
    • 把每一个非特殊词元(不是 <cls><sep>...)放进 MLP 分类
  • 问题回答: 给定一个问题和描述文字, 找一个片段作为回答
    • 对片段的每一个词元预测是不是回答的开头或者结束

实用机器学习

不讲模型, 讲数据

知识积累, 学会读论文, 经典论文需要读懂每一句话

结合代码了解细节

对读过的论文做整理