分类: AI

  • 朴素贝叶斯与注意力机制:一场“心有灵犀”的邂逅

    嘿,朋友们!今天我们要聊的可是AI界的两位“大明星”——朴素贝叶斯(Naive Bayes)和注意力机制(Attention)。这两位一个朴实无华,一个炫酷吊炸天,但你知道吗?它们之间其实有很多“不可告人”的秘密!

    朴素贝叶斯:老实人的自白

    先来认识一下朴素贝叶斯,这位老兄可是AI界的“老黄牛”,简简单单,踏踏实实。他的工作原理就像是你在餐厅点菜,完全按照菜单上的推荐组合来点。每道菜都有它自己的独立评分,然后你根据这些评分来决定哪道菜最可能是你喜欢的。

    一般化的朴素贝叶斯:变身超级英雄

    不过,老实人的日子久了,朴素贝叶斯也想来点儿新花样,于是他决定进行一次“大变身”。他学会了一种叫做“加权平均”的技术,不再是每道菜的评分都一样,而是根据餐厅老板的推荐来调整每道菜的权重。这样一来,他的推荐就显得更加聪明了。

    注意力机制:时尚界的弄潮儿

    接下来登场的是注意力机制,这位可是AI界的新锐潮人。他的理念是“要注意到每一个细节”。在他看来,每个词对当前词的影响力是不一样的,有的词可能只是打酱油的,有的词则是重头戏。于是,他发明了“注意力分数”,用来衡量每个词的重要性,然后根据这些分数进行加权平均。

    当朴素贝叶斯遇上注意力机制

    这时,朴素贝叶斯和注意力机制这两个大明星终于碰面了。朴素贝叶斯惊讶地发现,这个炫酷的注意力机制,竟然和自己有那么多相似之处!原来,注意力机制也是一种广义的朴素贝叶斯,只不过是通过更加灵活的加权平均,来捕捉词与词之间的复杂关系。

    深度模型:层叠与残差的魔法

    不过,单层的朴素贝叶斯和注意力机制能力有限,为了让自己变得更强大,他们决定学习一种叫做“层叠”的魔法。简单来说,就是把多个简单的模型叠加起来,形成一个超级复杂的模型。

    在这个过程中,他们还学会了一种叫做“残差连接”的技巧。这就像是你在写文章的时候,既要保持文章的连贯性,又要突出重点。通过这种方式,模型在捕捉复杂关系的同时,也能保留一些简单的、直接的关系。

    总结:一场“心有灵犀”的邂逅

    今天,我们见证了朴素贝叶斯与注意力机制这两位大明星的“心有灵犀”时刻。通过这种视角,我们不仅更好地理解了注意力机制中的层叠与残差,还为未来的研究提供了新的思路和方向。

    所以,朋友们,如果你对AI技术感兴趣,不妨多了解一下朴素贝叶斯和注意力机制这两位大明星。他们之间的“化学反应”一定会让你大开眼界!


    希望通过这篇文章,大家不仅学到了知识,还能会心一笑。AI的世界其实并不枯燥,关键是要找到对的方式去探索。下次我们再来聊聊其他有趣的话题吧!

  • Transformer升级之路:逆用Leaky ReRoPE解决推理成本问题

    在上周的文章《Transformer升级之路:12、无限外推的ReRoPE?》中,我们介绍了ReRoPE和Leaky ReRoPE的创新方法。实验结果表明,它们在几乎不损失训练效果的情况下能够扩展LLM的Context长度,并且实现了“longer context, lower loss”的理想特性。尤其是ReRoPE,似乎表现出了无限的Context处理能力。然而,这些方法也带来了推理阶段的成本增加问题。本文将探讨一种新的解决方案:逆用Leaky ReRoPE。

    回顾RoPE与Leaky ReRoPE

    RoPE与位置编码

    RoPE(Rotary Position Embedding)形式上是一种绝对位置编码,但实际上它实现了相对位置编码。其对应的相对位置矩阵为:

    [latex][
    \left(\begin{array}{cccccccccc}
    0 & 1 & 2 & 3 & \cdots & L-2 & L-1\
    -1 & 0 & 1 & 2 & \cdots & L-3 & L-2\
    -2 & -1 & 0 & 1 & \cdots & L-4 & L-3\
    \vdots & \vdots & \vdots & \vdots & \ddots & \vdots & \vdots\
    2-L & 3-L & 4-L & \cdots & -2 & -1 & 0\
    1-L & 2-L & 3-L & \cdots & -3 & -2 & -1\
    \end{array}\right)
    ][/latex]

    Leaky ReRoPE与窗口化处理

    为了在保留局域性的同时避免Long Context导致位置越界问题,Leaky ReRoPE将推理阶段的相对位置矩阵改为:

    [latex][
    \left(\begin{array}{cccccccccc}
    0 & 1 & 2 & \cdots & w-1 & w & \cdots & w+k & \cdots & w+L-1-wk\
    -1 & 0 & 1 & \cdots & w-2 & w-1 & \cdots & w+k-1 & \cdots & w+L-2-wk\
    \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots\
    -w+1 & -w+2 & -w+3 & \cdots & -1 & 0 & \cdots & k-1 & \cdots & L-2-wk\
    \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots\
    \end{array}\right)
    ][/latex]

    其中,( w ) 是窗口宽度,( k ) 用来调节可处理的最大长度。

    ReRoPE的无限扩展能力

    ReRoPE直接取了 ( k \to \infty ) 的极限:

    [latex][
    \left(\begin{array}{cccccccccc}
    0 & 1 & 2 & \cdots & w-1 & w & \cdots & w & \cdots & w\
    -1 & 0 & 1 & \cdots & w-2 & w-1 & \cdots & w & \cdots & w\
    \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots\
    -w+1 & -w+2 & -w+3 & \cdots & -1 & 0 & \cdots & w & \cdots & w\
    \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots\
    \end{array}\right)
    ][/latex]

    逆用Leaky ReRoPE:让训练阶段变慢,推理阶段变快

    为什么逆用?

    原本的ReRoPE和Leaky ReRoPE在推理阶段增加了计算成本。如果我们反过来在训练阶段使用Leaky ReRoPE,而在推理阶段使用常规的RoPE,能否解决这一问题呢?

    实验与结果

    我们进行了以下实验组合:“GAU + Deep Norm + Tiger + 语言模型”。在训练阶段使用 ( k=1/16, w=128 ) 的Leaky ReRoPE,在推理阶段使用正常的RoPE。测试结果如下:

    测试长度BaselineBaseline-lognNTK-RoPE-fixedNTK-RoPE-logn†-fixedNTK-RoPE-logn-fixedNTK-RoPE-mixedNTK-RoPE-logn†-mixedNTK-RoPE-logn-mixedReRoPE-w256ReRoPE-w256-logn†ReRoPE-w256-lognInvLeaky ReRoPE-w128-lognInvLeaky ReRoPE-w128-b8-lognHFWA512 (训练)
    训练49.41%49.40%49.41%49.41%49.40%49.41%49.41%49.40%49.41%49.41%49.40%49.38%49.62%48.70%
    4096(重复)24.17%24.60%51.86%55.94%62.85%53.09%59.11%68.91%77.90%82.40%85.12%82.25%81.15%80.84%
    4096(不重复)23.16%24.02%39.61%41.11%44.14%40.12%42.38%45.41%48.48%48.85%49.07%48.32%48.85%48.15%

    其中,( b8 ) 是指RoPE的频率底数从10000换成了80000。可以看到,“Leaky ReRoPE → RoPE”的InvLeaky ReRoPE虽然效果上不如“RoPE → ReRoPE/Leaky ReRoPE”,但依然胜过了HFWA,并且由于推理阶段是常规的RoPE,可以套用现成的加速技术,因此依然是有相当竞争力的。

    我们对 [latex]( k, w, b )[/latex] 等参数做了一些简单的调参,发现最优解基本上就是上述两个组合。具体来说:

    调参与训练速度

    • ( k ) 设置为“扩展倍数的2倍的倒数”
    • ( w ) 设置为训练长度的四分之一
    • ( b ) 可选乘以扩展倍数

    在上述实验中,模型参数量为1亿,训练长度为512,每1000步的训练时间从330秒增加到了350秒,增加不到10%。虽然这里有GAU的原因,因为GAU是单头的注意力,本身比多头注意力快。如果是多头注意力或者训练长度更长,增加幅度可能会更大,但估计不会超过50%,依然在可接受范围内。

    小结

    本文提出了Leaky ReRoPE的“逆用”方法,通过在训练阶段使用更大步长的Leaky ReRoPE,使得推理阶段可以退回常规的RoPE,从而保持推理速度不变。实验结果表明,这种方法依然具有相当的竞争力。未来的工作可以进一步优化参数设置,提升模型性能。


    希望这篇文章能够帮助您更好地理解逆用Leaky ReRoPE的方法及其优势。如果有任何疑问或建议,欢迎在评论区留言讨论。

  • 增大Tokenizer词表:LLM续写任务的新挑战与解决方案

    语言模型(LLM)在自然语言处理中的应用越来越广泛,而通过增大Tokenizer的词表来提高压缩率,从而缩短串行长度、降低解码成本,是大家都喜闻乐见的事情。然而,这种方法在带来诸多优点的同时,也可能产生一些问题。本文将探讨增大词表后语言模型在续写任务中遇到的问题,并提出解决方案。

    优劣分析

    优点

    1. 解码速度提升:由于LLM是自回归的,解码过程会随着序列长度的增加变得越来越慢。通过“增大词表 → 提高压缩率 → 缩短串行长度”,可以减少相同文本对应的tokens数量,从而减少解码步数,提升解码速度。
    2. 缓解Exposure Bias:语言模型的训练方式通常是Teacher Forcing,缩短串行长度能够缓解Teacher Forcing带来的Exposure Bias问题,从而可能提升模型效果。

    缺点

    1. 割裂字符联系:增大词表可能会割裂token与token之间在字符层面的联系,影响模型的泛化能力。例如,“太阳能”和“太阳”都是词表中的一个词时,模型可能不知道“太阳能”是由“太阳”和“能”组成,从而难以完成一些子词相关的任务。
    2. 续写问题:增大词表后,常见的命令或短语可能被视为单个token,导致模型在续写时无法正确生成。例如,“import numpy as np”被当作一个token,用户输入“import numpy”时,模型无法续写出“ as np”。

    续写问题

    Armen Aghajanyan分享了一个典型的例子:在训练代码模型时使用超大词表,导致“import numpy as np”变成了一个token。当用户输入“import numpy”时,模型无法续写出“ as np”。这种现象在自然语言模型中也很常见。例如,“太阳能”和“太阳”都是独立的token时,用户输入“太阳”后,模型续写出的内容可能不符合用户的期望。

    参考对策

    虽然Armen Aghajanyan提到的问题确实存在,但笔者认为通过适当的处理,这个问题不仅可以解决,还能转化为增大词表的优点。以下是一个可行的解决方案:

    基于词表的前缀搜索

    假设用户输入了“广州的白云”,Tokenizer将其分为“广州/的/白云”。此时,如果直接将这三个词转换为id输入模型,模型可能无法续写出“广州/的/白云机场”等结果。因此,我们可以进行以下步骤:

    1. 前缀搜索:对“白云”进行词表的前缀搜索,假设搜索结果为“白云”、“白云机场”、“白云山”、“白云路”四个词。
    2. 计算条件概率:用LLM计算以下条件概率:
      [latex][p(\text{白云}|\text{广州, 的})p(\text{白云机场}|\text{广州, 的})p(\text{白云山}|\text{广州, 的})p(\text{白云路}|\text{广州, 的})][/latex]
    3. 归一化与采样:将条件概率归一化后进行采样,决定续写内容。例如,采样结果为“白云机场”,则输出“机场”,并按照“广州/的/白云机场”进行续写。

    这种方法不仅解决了Armen Aghajanyan所提到的问题,还能在词表压缩率高的情况下,一次性生成更多的字。特别地,回退操作只需在采样第一步进行,从第二步开始就不需要回退操作,计算量很少。

    文章小结

    本文介绍了增大词表后LLM在续写任务中可能出现的问题,并分享了参考的解决方案。通过结合基于LLM的续写和基于词表的前缀搜索,可以有效地解决续写问题,并将增大词表的缺点转化为优点。希望这些思路能为语言模型的进一步优化提供参考。

  • Viterbi Sampling算法的改进与完善

    在自然语言处理领域,分词是一个至关重要的步骤。最近,一篇名为《随机分词浅探:从Viterbi Decoding到Viterbi Sampling》的文章中,作者提出了一种名为“Viterbi Sampling”的随机分词算法。本文将详细讨论该算法的改进,并从数学上证明其效果可以与Subword Regularization等价。

    问题分析

    在知乎的评论中,用户 @鹤舞 指出,当前的采样算法可能会在多次二选一的过程中“稀释”了部分方案的出现概率,导致原本分数最高的切分并不是以最高概率出现。

    例如,假设有三种切分方案,每种方案的得分都一样,理应每种方案的出现概率都是1/3。然而,Viterbi Sampling算法将多选一的过程拆分成多步的二选一,从而导致概率分布发生偏移。

    示例分析

    以单词“watching”为例,有三种切分方案:watch ing, wat ching, 和 w atching。按照Viterbi Sampling的操作,先在前两种方案中选择,概率均为1/2;然后再与第三种方案比较,概率依然为1/2。最终,前两种方案的出现概率为1/4,而第三种方案的出现概率变为了1/2。

    解决办法

    为了解决上述问题,可以采用“水塘采样(Reservoir sampling)”的算法。具体来说,每次进行二选一后,同时缓存累积概率,并在后续的二选一过程中使用累积概率进行比较。

    具体实现

    假设前两种切分方案的概率均为1/3,选择其中一种后,总的累积概率为2/3。接下来,新方案被选中的概率为1/3,而不是1/2。这保证了每种方案的出现概率均保持为1/3。

    在实际计算时,为避免指数爆炸问题,可以缓存对数形式的概率,并利用logsumexp函数避免溢出。

    Zlogi = logsumexp(Zlogi-1, αsi)

    相应的实现已经内置在bytepiece>=0.5.0中。

    完美采样的证明

    为了证明改进后的Viterbi Sampling算法是“完美采样”,我们需要回顾Viterbi Decoding的基本原理。Viterbi Decoding通过动态规划找出最优的分词方案,其子串也必须是对应子字节串的最优切分方案。

    数学推导

    通过动态规划,可以计算出每个位置的最优切分方案及其得分。而在Viterbi Sampling中,我们需要对所有切分方案进行采样,其采样概率应与得分成正比。

    Z(c1, c2, ..., cl) = ∑ Z(c1, ..., ck) * e^(α * s(ck+1, ..., cl))

    通过逐步计算累积权重Z. 可以实现对所有切分方案的完美采样。

    递归式转换

    在实际计算中,使用对数形式的累积权重Zlog,并通过logsumexp函数进行累积计算。

    Zlog(c1, c2, ..., cl) = logsumexp(Zlog(c1, ..., ck) + α * s(ck+1, ..., cl))

    这样可以避免直接计算指数导致的溢出问题。

    文章小结

    本文通过详细分析和数学推导,完善了之前提出的Viterbi Sampling算法。改进后的算法不仅解决了概率“稀释”问题,而且在效果上与Subword Regularization等价,同时在使用效率上更具优势。

    通过这些改进,Viterbi Sampling算法在实际应用中将具备更高的可靠性和效率,为自然语言处理领域的分词任务提供了更优的解决方案。

  • 探索线性Attention的局限性:从“集中注意力”角度出发

    近年来,Transformer架构在自然语言处理领域取得了显著的成果,而Attention机制则是其核心所在。然而,随着研究的深入,传统的标准Attention机制暴露出了一些计算复杂度和资源需求上的问题,这促使研究者们开始探索更高效的线性Attention。然而,线性Attention在实际效果上却一直不如标准Attention。本文将从“集中注意力”的角度,探讨线性Attention的局限性,并尝试给出一个合理的解释。

    Attention机制的稀疏性

    什么是稀疏性?

    在《从熵不变性看Attention的Scale操作》一文中,我们用信息熵来度量Attention的“集中注意力”程度。熵越低,Attention越有可能集中在某个token上。然而,信息熵只能用于归一化且非负的Attention矩阵,这限制了其适用范围。因此,我们引入了另一种稀疏性度量指标:S(x) = E[|x|] / sqrt(E[x^2])。这个指标与信息熵类似,S(x)越小,表示对应的随机矢量越稀疏,即越有可能“一家独大”。

    标准Attention的稀疏性

    对于标准Attention机制,f = exp,我们可以推导出其稀疏性:

    S(a) = exp(-1/2 * σ^2 * ||q||^2)

    σ||q||趋向无穷大时,S(a)趋向于0,这意味着标准Attention可以任意稀疏地“集中注意力”。

    GAU的稀疏性

    对于Gated Attention Unit (GAU)机制,f = relu2,其稀疏性较为复杂,但通过计算可以发现,只有当偏置项β小于0时,稀疏性才有机会趋于0。这表明,GAU在某些条件下也能实现较高的稀疏性。

    线性Attention的局限性

    极简线性Attention

    对于最简单的线性Attention,即不加任何激活函数f = identical,其稀疏性为:

    S(a) = sqrt(2/π * γ * exp(-β^2/(2γ^2)) + β * erf(β/(2√γ)) / (β^2 + γ^2))

    从图像可以看出,极简线性Attention的稀疏性存在一个较高的下限,这意味着它难以“集中注意力”到关键位置上。

    一般线性Attention

    线性Attention的一般形式为a_j ∝ g(q) ⋅ h(k_j),其稀疏性为:

    S(a) = 1 / sqrt(1 + (σ~ * μ~ * ||q~||_2 / ||q~||_1)^2)

    这表明,要想线性Attention变得稀疏,可以通过降低k~串行的信噪比或增大q的模长。然而,非负型线性Attention通常只能表示绝对位置的重要性,难以表达相对位置的重要性。

    线性衰减Attention

    对于带有显式递归的线性RNN模型,其稀疏性为:

    S(a) = 1 - λ^n / n(1 - λ) * sqrt(1 + λ / (1 + λ^n))

    λ < 1时,随着n趋向无穷大,S(a)趋向0。这意味着这种模型可以实现较高的稀疏性,但其注意力仅能表达固定不变的注意力衰减,难以自适应地关注到长距离的上下文。

    结论

    本文通过Attention矩阵的稀疏程度,考察了不同Attention机制的潜力,得出以下结论:

    1. 标准Attention可以实现任意稀疏的注意力矩阵。
    2. 线性Attention难以实现高稀疏性,尤其是在表示相对位置的重要性时。
    3. 带有显式衰减的线性Attention可以实现稀疏性,但其注意力固定,难以自适应。

    这些发现或许能够解释线性Attention在实际效果上略逊一筹的原因。线性Attention在“集中注意力”方面存在固有的局限性,这使得它在处理复杂上下文时表现不如标准Attention。未来的研究或许需要在如何提高线性Attention的稀疏性和灵活性上继续努力,以期实现更高效且性能优越的Transformer模型。

    参考文献

    1. 《Transformer升级之路:3、从Performer到线性Attention》
    2. 《为什么现在的LLM都是Decoder-only的架构?》
    3. 《从熵不变性看Attention的Scale操作》
    4. 《FLASH:可能是近来最有意思的高效Transformer设计》
    5. 《相对位置编码Transformer的一个理论缺陷与对策》
    6. 《如何度量数据的稀疏程度?》
    7. 《线性Attention的探索:Attention必须有个Softmax吗?》
    8. 《Google新作试图“复活”RNN:RNN能否再次辉煌?》

    通过上述分析,我们不仅理解了不同Attention机制的稀疏性差异,还揭示了线性Attention在实际应用中的局限性。希望本文的讨论能够为未来的研究提供一些新的思路和方向。

  • 深度学习中的状态空间模型(SSM)初探

    引言

    前几天,笔者看了几篇介绍SSM(State Space Model)的文章,才发现原来自己从未认真了解过SSM,于是打算认真去学习一下SSM的相关内容,顺便开了这个新坑,记录一下学习所得。

    SSM的概念由来已久,但这里我们特指深度学习中的SSM,一般认为其开篇之作是2021年的S4,不算太老,而SSM最新最火的变体大概是去年的Mamba。当然,当我们谈到SSM时,也可能泛指一切线性RNN模型,这样RWKV、RetNet还有此前我们在《Google新作试图“复活”RNN:RNN能否再次辉煌?》介绍过的LRU都可以归入此类。不少SSM变体致力于成为Transformer的竞争者,尽管笔者并不认为有完全替代的可能性,但SSM本身优雅的数学性质也值得学习一番。

    尽管我们说SSM起源于S4,但在S4之前,SSM有一篇非常强大的奠基之作《HiPPO: Recurrent Memory with Optimal Polynomial Projections》(简称HiPPO),所以本文从HiPPO开始说起。

    基本形式

    先插句题外话,上面提到的SSM代表作HiPPO、S4、Mamba的一作都是Albert Gu,他还有很多篇SSM相关的作品,毫不夸张地说,这些工作筑起了SSM大厦的基础。不论SSM前景如何,这种坚持不懈地钻研同一个课题的精神都值得我们由衷地敬佩。

    言归正传。对于事先已经对SSM有所了解的读者,想必知道SSM建模所用的是线性ODE系统:

    [latex][ x′(t) = Ax(t) + Bu(t) ][/latex]
    [latex][ y(t) = Cx(t) + Du(t) ][/latex]

    其中 [latex]( u(t) \in \mathbb{R}^{d_i}, x(t) \in \mathbb{R}^d, y(t) \in \mathbb{R}^{d_o}, A \in \mathbb{R}^{d \times d}, B \in \mathbb{R}^{d \times d_i}, C \in \mathbb{R}^{d_o \times d}, D \in \mathbb{R}^{d_o \times d_i} )[/latex]。当然我们也可以将它离散化,那么就变成一个线性RNN模型,这部分我们在后面的文章再展开。不管离散化与否,其关键词都是“线性”,那么马上就有一个很自然的问题:为什么是线性系统?线性系统够了吗?

    我们可以从两个角度回答这个问题:线性系统既足够简单,也足够复杂。简单是指从理论上来说,线性化往往是复杂系统的一个最基本近似,所以线性系统通常都是无法绕开的一个基本点;复杂是指即便如此简单的系统,也可以拟合异常复杂的函数,为了理解这一点,我们只需要考虑一个 [latex]( \mathbb{R}^4 )[/latex] 的简单例子:

    [latex][ x′(t) = \begin{pmatrix} 1 & 0 & 0 & 0 \ 0 & -1 & 0 & 0 \ 0 & 0 & 0 & 1 \ 0 & 0 & -1 & 0 \end{pmatrix} x(t) ][/latex]

    这个例子的基本解是 [latex]( x(t) = (e^t, e^{-t}, \sin t, \cos t) )[/latex]。这意味着只要 [latex]( d )[/latex] 足够大,该线性系统就可以通过指数函数和三角函数的组合来拟合足够复杂的函数,而我们知道拟合能力很强的傅里叶级数也只不过是三角函数的组合,如果再加上指数函数显然就更强了,因此可以想象线性系统也有足够复杂的拟合能力。

    当然,这些解释某种意义上都是“马后炮”。HiPPO给出的结果更加本质:当我们试图用正交基去逼近一个动态更新的函数时,其结果就是如上的线性系统。这意味着,HiPPO不仅告诉我们线性系统可以逼近足够复杂的函数,还告诉我们怎么去逼近,甚至近似程度如何。

    有限压缩

    接下来,我们只考虑 [latex]( d_i = 1 )[/latex] 的特殊情形,[latex]( d_i > 1 )[/latex] 只不过是 [latex]( d_i = 1 )[/latex] 时的并行推广。此时,[latex]( u(t) )[/latex] 的输出是一个标量,进一步地,作为开头我们先假设 [latex]( t \in [0, 1] )[/latex],HiPPO的目标是:用一个有限维的矢量来储存这一段 [latex]( u(t) )[/latex] 的信息。

    看上去这是一个不大可能的需求,因为 [latex]( t \in [0, 1] )[/latex] 意味着 [latex]( u(t) )[/latex] 可能相当于无限个点组成的矢量,压缩到一个有限维的矢量可能严重失真。不过,如果我们对 [latex]( u(t) )[/latex] 做一些假设,并且允许一些损失,那么这个压缩是有可能做到的,并且大多数读者都已经尝试过。比如,当 [latex]( u(t) )[/latex] 在某点 ( n+1 ) 阶可导的,它对应的 ( n ) 阶泰勒展开式往往是 [latex]( u(t) )[/latex] 的良好近似,于是我们可以只储存展开式的 ( n+1 ) 个系数来作为 [latex]( u(t) )[/latex] 的近似表征,这就成功将 [latex]( u(t) )[/latex] 压缩为一个 ( n+1 ) 维矢量。

    当然,对于实际遇到的数据来说,“( n+1 ) 阶可导”这种条件可谓极其苛刻,我们通常更愿意使用在平方可积条件下的正交函数基展开,比如傅里叶(Fourier)级数,它的系数计算公式为:

    [latex][ c_n = \int_0^1 u(t) e^{-2i\pi nt} dt ][/latex]

    这时候取一个足够大的整数 [latex]( N. [/latex],只保留 [latex]( |n| \leq N )[/latex] 的系数,那么就将 [latex]( u(t) )[/latex] 压缩为一个 ( 2N+1 ) 维的矢量了。

    接下来,问题难度就要升级了。刚才我们说 [latex]( t \in [0, 1] )[/latex],这是一个静态的区间,而实际中 [latex]( u(t) )[/latex] 代表的是持续采集的信号,所以它是不断有新数据进入的,比如现在我们近似了 [latex]( [0, 1] )[/latex] 区间的数据,马上就有 [latex]( [1, 2] )[/latex] 的数据进来,你需要更新逼近结果来试图记忆整个 ( [0, 2] ) 区间,接下来是 ( [0, 3] )、( [0, 4] ) 等等,这我们称为“在线函数逼近”。而上面的傅里叶系数公式只适用于区间 ( [0, 1] ),因此需要将它进行推广。

    为此,我们设 [latex]( t \in [0, T] ),( s \mapsto t \leq T(s) )[/latex] 是 ( [0, 1] ) 到 ( [0, T] ) 的一个映射,那么 [latex]( u(t \leq T(s)) )[/latex] 作为 ( s ) 的函数时,它的定义区间就是 [latex]( [0, 1] )[/latex],于是就可以复用傅里叶系数公式:

    [latex][ c_n(T. = \int_0^1 u(t \leq T(s)) e^{-2i\pi ns} ds ][/latex]

    这里我们已经给系数加了标记 [latex]( (T. )[/latex],以表明此时的系数会随着 [latex]( T )[/latex] 的变化而变化。

    线性初现

    能将 [latex]( [0, 1] )[/latex] 映射到 [latex]( [0, T] )[/latex] 的函数有无穷多,而最终结果也因 [latex]( t \leq T(s) )[/latex] 而异,一些比较直观且相对简单的选择如下:

    1. [latex]( t \leq T(s) = sT )[/latex],即将 ( [0, 1] ) 均匀地映射到 ( [0, T] );
    2. 注意 [latex]( t \leq T(s) )[/latex] 并不必须是满射,所以像 [latex]( t \leq T(s) = s + T – 1 )[/latex] 也是允许的,这意味着只保留了最邻近窗口 ( [T – 1, T] ) 的信息,丢掉了更早的部分,更一般地有 [latex]( t \leq T(s) = sw + T – w )[/latex],其中 ( w ) 是一个常数,这意味着 ( T – w ) 前的信息被丢掉了;
    3. 也可以选择非均匀映射,比如 [latex]( t \leq T(s) = T\sqrt{s} )[/latex],它同样是 ( [0, 1] ) 到 ( [0, T] ) 的满射,但 ( s = 1/4 ) 时就映射到 ( T/2 ) 了,这意味着我们虽然关注全局的历史,但同时更侧重于 ( T. 时刻附近的信息。

    现在我们以 [latex]( t \leq T(s) = (s + 1)w/2 + T – w )[/latex] 为例,代入傅里叶系数公式得到:

    [latex][ c_n(T. = \int_0^1 u(sw + T – w) e^{-2i\pi ns} ds ][/latex]

    现在我们两边求关于 ( T. 的导数:

    [latex][ \frac{d}{dT} c_n(T. = \frac{1}{w} \left[ u(T) – u(T – w) \right] + \frac{2i\pi n}{w} c_n(T) ][/latex]

    其中我们用了分部积分公式。由于我们只保留了 [latex]( |n| \leq N. [/latex] 的系数,所以根据傅里叶级数的公式,可以认为如下是 [latex]( u(sw + T – w) )[/latex] 的一个良好近似:

    [latex][ u(sw + T – w) \approx \sum_{k=-N}^N c_k(T. e^{2i\pi ks} ][/latex]

    那么 [latex]( u(T – w) = u(sw + T – w) \big|{s=0} \approx \sum{k=-N}^N c_k(T. )[/latex],代入上式得:

    [latex][ \frac{d}{dT} c_n(T. \approx \frac{1}{w} \left[ u(T) – \sum_{k=-N}^N c_k(T) \right] + \frac{2i\pi n}{w} c_n(T) ][/latex]

    将 ( T. 换成 ( t ),然后所有的 [latex]( c_n(t) )[/latex] 堆在一起记为 [latex]( x(t) = (c_{-N}, c_{-(N-1)}, \ldots, c_0, \ldots, c_{N-1}, c_N) )[/latex],并且不区分 [latex]( \approx )[/latex] 和 [latex]( = )[/latex],那么就可以写出:

    [latex][ x′(t) = A x(t) + B u(t) ][/latex]

    其中:

    [latex][ A_{n,k} = \begin{cases} \frac{2i\pi n – 1}{w}, & k = n \ -\frac{1}{w}, & k \ne n \end{cases}, \quad B_n = \frac{1}{w} ][/latex]

    这就出现了如上所示的线性ODE系统。即当我们试图用傅里叶级数去记忆一个实时函数的最邻近窗口内的状态时,结果自然而然地导致了一个线性ODE系统。

    一般框架

    当然,目前只是选择了一个特殊的 [latex]( t \leq T(s) )[/latex],换一个 [latex]( t \leq T(s) )[/latex] 就不一定有这么简单的结果了。此外,傅里叶级数的结论是在复数范围内的,进一步实数化也可以,但形式会变得复杂起来。所以,我们要将这一过程推广成一个一般化的框架,从而得到更一般、更简单的纯实数结论。

    设 [latex]( t \in [a, b] )[/latex],并且有目标函数 [latex]( u(t) )[/latex] 和函数基 [latex]( { g_n(t) }_{n=0}^N. [/latex],我们希望有后者的线性组合来逼近前者,目标是最小化 [latex]( L^2 )[/latex] 距离:

    [latex][ \arg\min_{c_1, \ldots, c_N} \int_a^b \left[ u(t) – \sum_{n=0}^N c_n g_n(t) \right]^2 dt ][/latex]

    这里我们主要在实数范围内考虑,所以方括号直接平方就行,不用取模。更一般化的目标函数还可以再加个权重函数 [latex]( \rho(t) )[/latex],但我们这里就不考虑了,毕竟HiPPO的主要结论其实也没考虑这个权重函数。

    对目标函数展开,得到:

    [latex][ \int_a^b u^2(t) dt – 2 \sum_{n=0}^N c_n \int_a^b u(t) g_n(t) dt + \sum_{m=0}^N \sum_{n=0}^N c_m c_n \int_a^b g_m(t) g_n(t) dt ][/latex]

    这里我们只考虑标准正交函数基,其定义为 [latex]( \int_a^b g_m(t) g_n(t) dt = \delta_{m,n} ),( \delta_{m,n} )[/latex] 是克罗内克δ函数,此时上式可以简化成:

    [latex][ \int_a^b u^2(t) dt – 2 \sum_{n=0}^N c_n \int_a^b u(t) g_n(t) dt + \sum_{n=0}^N c_n^2 ][/latex]

    这只是一个关于 [latex]( c_n )[/latex] 的二次函数,它的最小值是有解析解的:

    [latex][ c^*_n = \int_a^b u(t) g_n(t) dt ][/latex]

    这也被称为 [latex]( u(t) )[/latex] 与 [latex]( g_n(t) )[/latex] 的内积,它是有限维矢量空间的内积到函数空间的并行推广。简单起见,在不至于混淆的情况下,我们默认 [latex]( c_n )[/latex] 就是 [latex]( c^*_n )[/latex]。

    接下来的处理跟上一节是一样的,我们要对一般的 [latex]( t \in [0, T] )[/latex] 考虑 [latex]( u(t) )[/latex] 的近似,那么找一个 [latex]( [a, b] )[/latex] 到 [latex]( [0, T] )[/latex] 的映射 [latex]( s \mapsto t \leq T(s) )[/latex],然后计算系数:

    [latex][ c_n(T. = \int_a^b u(t \leq T(s)) g_n(s) ds ][/latex]

    同样是两边求 [latex]( T. [/latex] 的导数,然后用分部积分法:

    [latex][ \frac{d}{dT} c_n(T. = \int_a^b u'(t \leq T(s)) \frac{\partial t \leq T(s)}{\partial T} g_n(s) ds ][/latex]

    [latex][ = \int_a^b \left( \frac{\partial t \leq T(s)}{\partial T} / \frac{\partial t \leq T(s)}{\partial s} \right) g_n(s) du(t \leq T(s)) ][/latex]

    [latex][ = \left( \frac{\partial t \leq T(s)}{\partial T} / \frac{\partial t \leq T(s)}{\partial s} \right) g_n(s) \bigg|_{s=b}^{s=a} – \int_a^b u(t \leq T(s)) d \left[ \left( \frac{\partial t \leq T(s)}{\partial T} / \frac{\partial t \leq T(s)}{\partial s} \right) g_n(s) \right] ][/latex]

    请勒让德

    接下来的计算,就依赖于 [latex]( g_n(t) )[/latex] 和 [latex]( t \leq T(s) )[/latex] 的具体形式了。HiPPO的全称是High-order Polynomial Projection Operators,第一个P正是多项式(Polynomial)的首字母,所以HiPPO的关键是选取多项式为基。现在我们请出继傅里叶之后又一位大牛——勒让德(Legendre),接下来我们要选取的函数基正是以他命名的“勒让德多项式”。

    勒让德多项式 [latex]( p_n(t) )[/latex] 是关于 [latex]( t )[/latex] 的 [latex]( n )[/latex] 次函数,定义域为 [latex]( [-1, 1] )[/latex],满足:

    [latex][ \int_{-1}^1 p_m(t) p_n(t) dt = \frac{2}{2n+1} \delta_{m,n} ][/latex]

    所以 [latex]( p_n(t) )[/latex] 之间只是正交,还不是标准(平分积分为1),[latex]( g_n(t) = \sqrt{\frac{2n+1}{2}} p_n(t) )[/latex] 才是标准正交基。

    当我们对函数基 [latex]( {1, t, t^2, \ldots, t^n} )[/latex] 执行施密特正交化时,其结果正是勒让德多项式。相比傅里叶基,勒让德多项式的好处是它是纯粹定义在实数空间中的,并且多项式的形式能够有助于简化部分 [latex]( t \leq T(s) )[/latex] 的推导过程,这一点我们后面就可以看到。勒让德多项式有很多不同的定义和性质,这里我们不一一展开,有兴趣的读者自行看维基百科介绍即可。

    接下来我们用到两个递归公式来推导一个恒等公式,这两个递归公式是:

    [latex][ p′_{n+1}(t) – p′_{n-1}(t) = (2n+1) p_n(t) ][/latex]
    [latex][ p′_{n+1}(t) = (n+1) p_n(t) + t p′_n(t) ][/latex]

    由第一个公式迭代得到:

    [latex][ p′_{n+1}(t) = (2n+1) p_n(t) + (2n−3) p_{n−2}(t) + (2n-7) p_{n-4}(t) + ⋯ = \sum_{k=0}^n (2k+1) χ_{n−k} p_k(t) ][/latex]

    其中当 [latex]( k )[/latex] 是偶数时 [latex]( χ_k = 1 )[/latex] 否则 [latex]( χ_k = 0 )[/latex] 。代入第二个公式得到:

    [latex][ t p′_n(t) = n p_n(t) + (2n−3) p{n-2}(t) + (2n-7) p_{n-4}(t) + ⋯ ][/latex]

    继而有:

    [latex][ (t+1) p′_n(t) = n p_n(t) + (2n-1) p_{n−1}(t) + (2n-3) p_{n-2}(t) + ⋯ = – (n+1) p_n(t) + \sum_{k=0}^n (2k+1) p_k(t) ][/latex]

    这些就是等会要用到的恒等式。此外,勒让德多项式满足 ( p_n(1) = 1, p_n(-1) = (-1)^n ),这个边界值后面也会用到。

    正如 ( n ) 维空间中不止有一组正交基也一样,正交多项式也不止有勒让德多项式一种,比如还有切比雪夫(Chebyshev)多项式,如果算上加权的目标函数(即 [latex]( ρ(t) ≢ 1 )[/latex] ),还有拉盖尔多项式等,这些在原论文中都有提及,但HiPPO的主要结论还是基于勒让德多项式展开的,所以剩余部分这里也不展开讨论了。

    邻近窗口

    完成准备工作后,我们就可以代入具体的 [latex]( t ≤ T(s) )[/latex] 进行计算了,计算过程跟傅里叶级数的例子大同小异,只不过基函数换成了勒让德多项式构造的标准正交基 [latex]( g_n(t) = \sqrt{\frac{2n+1}{2}} p_n(t) )[/latex]。作为第一个例子,我们同样先考虑只保留最邻近窗口的信息,此时 [latex]( t ≤ T(s) = \frac{(s+1)w}{2} + T – w )[/latex] 将 [latex]( [−1, 1] )[/latex] 映射到 [latex]( [T−w, T] )[/latex],原论文将这种情形称为“LegT(Translated Legendre)”。

    直接代入之前得到的公式,马上得到:

    [latex][ \frac{d}{dT} c_n(T. = \sqrt{\frac{2(2n+1)}{w}} [u(T) – (−1)^n u(T−w)] – \frac{2}{w} \int_{-1}^1 u\left(\frac{(s+1)w}{2} + T – w\right) g′_n(s) ds ][/latex]

    我们首先处理 [latex]( u(T−w) )[/latex] 项,跟傅里叶级数那里同样的思路,我们截断 ( n ≤ N. 作为 [latex]( u\left(\frac{(s+1)w}{2} + T – w\right) )[/latex] 的一个近似:

    [latex][ u\left(\frac{(s+1)w}{2} + T – w\right) ≈ \sum_{k=0}^N c_k(T. g_k(s) ][/latex]

    从而有 [latex]( u(T−w) ≈ \sum_{k=0}^N c_k(T. g_k(−1) = \sum_{k=0}^N (−1)^k c_k(T) \sqrt{\frac{2k+1}{2}} )[/latex] 。接着,利用之前的递归公式得到:

    [latex][ \int_{-1}^1 u\left(\frac{(s+1)w}{2} + T – w\right) g′n(s) ds = \int{-1}^1 u\left(\frac{(s+1)w}{2} + T – w\right) \sqrt{\frac{2n+1}{2}} p′_n(s) ds ][/latex]

    [latex][ = \int_{-1}^1 u\left(\frac{(s+1)w}{2} + T – w\right) \sqrt{\frac{2n+1}{2}} \left[\sum_{k=0}^{n-1} (2k+1) \chi_{n-1-k} p_k(s) \right] ds ][/latex]

    [latex][ = \int_{-1}^1 u\left(\frac{(s+1)w}{2} + T – w\right) \sqrt{\frac{2n+1}{2}} \left[\sum_{k=0}^{n-1} \sqrt{2(2k+1)} \chi_{n-1-k} g_k(s) \right] ds ][/latex]

    [latex][ = \sqrt{\frac{2n+1}{2}} \sum_{k=0}^{n-1} \sqrt{2(2k+1)} \chi_{n-1-k} c_k(T. ][/latex]

    将这些结果集成起来,就有:

    [latex][ \frac{d}{dT} c_n(T. ≈ \sqrt{\frac{2(2n+1)}{w}} u(T) – \sqrt{\frac{2(2n+1)}{w}} (−1)^n \sum_{k=0}^N (−1)^k c_k(T) \sqrt{\frac{2k+1}{2}} – \frac{2}{w} \sqrt{\frac{2n+1}{2}} \sum_{k=0}^{n-1} \sqrt{2(2k+1)} \chi_{n-1-k} c_k(T) ][/latex]

    再次地,将 [latex]( T. [/latex] 换回 [latex]( t )[/latex],并将所有的 [latex]( c_n(t) )[/latex] 堆在一起记为 [latex]( x(t) = (c_0, c_1, ⋯, c_N) )[/latex],那么根据上式可以写出:

    [latex][ x′(t) = A x(t) + B u(t) ][/latex]

    其中:

    [latex][ A_{n,k} = \begin{cases} -\frac{2}{w} \sqrt{\frac{(2n+1)(2k+1)}{2}}, & k < n \ -\frac{2}{w} \sqrt{\frac{(2n+1)(2k+1)}{2}} (−1)^{n−k}, & k ≥ n \end{cases}, \quad B_n = \sqrt{\frac{2(2n+1)}{w}} ][/latex]

    我们还可以给每个 [latex]( c_n(T. )[/latex] 都引入一个缩放因子,来使得上述结果更一般化。比如我们设 [latex]( c_n(T) = λ_n \tilde{c}_n(T) )[/latex],代入上式整理得:

    [latex][ \frac{d}{dT} \tilde{c}n(T. ≈ \sqrt{\frac{2(2n+1)}{w}} \frac{u(T)}{λ_n} – \sqrt{\frac{2(2n+1)}{w}} \frac{(−1)^n}{λ_n} \sum{k=0}^N \frac{(−1)^k c_k(T)}{λk} \sqrt{\frac{2k+1}{2}} – \frac{2}{w} \sqrt{\frac{2n+1}{2}} \sum{k=0}^{n-1} \frac{λk}{λ_n} \sqrt{2(2k+1)} \chi{n-1-k} \tilde{c}_k(T) ][/latex]

    如果取 [latex]( λ_n = \sqrt{2} )[/latex],那么 ( A. 不变,[latex]( B_n = \sqrt{2(2n+1)} )[/latex],这就对齐了原论文的结果。如果取 [latex]( λ_n = \sqrt{\frac{2}{2n+1}} )[/latex],那么就得到了Legendre Memory Units中的结果:

    [latex][ x′(t) = A x(t) + B u(t) ][/latex]

    其中:

    [latex][ A_{n,k} = \begin{cases} 2n+1, & k < n \ (−1)^{n−k} (2n+1), & k ≥ n \end{cases}, \quad B_n = 2n+1 ][/latex]

    这些形式在理论上都是等价的,但可能存在不同的数值稳定性。比如一般来说当 [latex]( u(t) )[/latex] 的性态不是特别糟糕时,我们可以预期 [latex]( n )[/latex] 越大,[latex]( |c_n| )[/latex] 的值就相对越小,这样直接用 [latex]( c_n )[/latex] 的话 [latex]( x(t) )[/latex] 矢量的每个分量的尺度就不大对等,这样的系统在实际计算时容易出现数值稳定问题,而取 [latex]( λ_n = \sqrt{\frac{2}{2n+1}} )[/latex] 改用 [latex]( \tilde{c}_n )[/latex] 的话意味着数值小的分量会被适当放大,可能有助于缓解多尺度问题从而使得数值计算更稳定。

    整个区间

    现在我们继续计算另一个例子:[latex]( t ≤ T(s) = \frac{(s+1)T}{2} )[/latex],它将 [latex]( [−1, 1] )[/latex] 均匀映射到 [latex]( [0, T] )[/latex],这意味着我们没有舍弃任何历史信息,并且平等地对待所有历史,原论文将这种情形称为“LegS(Scaled Legendre)”。

    同样地,通过代入之前得到的公式:

    [latex][ \frac{d}{dT} c_n(T. = \sqrt{\frac{2(2n+1)}{T}} u(T) – \frac{1}{T} \int_{-1}^1 u\left(\frac{(s+1)T}{2}\right) (s+1) g′_n(s) ds ][/latex]

    利用之前的递归公式得到:

    [latex][ \int_{-1}^1 u\left(\frac{(s+1)T}{2}\right) (s+1) g′n(s) ds = \int{-1}^1 u\left(\frac{(s+1)T}{2}\right) \left[g_n(s) + (s+1) g′_n(s)\right] ds ][/latex]

    [latex][ = c_n(T. + \int_{-1}^1 u\left(\frac{(s+1)T}{2}\right) \sqrt{\frac{2n+1}{2}} p′_n(s) ds ][/latex]

    [latex][ = c_n(T. + \int_{-1}^1 u\left(\frac{(s+1)T}{2}\right) \left[-(n+1) g_n(s) + \sum_{k=0}^n \sqrt{(2n+1)(2k+1)} g_k(s)\right] ds ][/latex]

    [latex][ = c_n(T. – n c_n(T) + \sum_{k=0}^n \sqrt{(2n+1)(2k+1)} c_k(T) ][/latex]

    于是有:

    [latex][ \frac{d}{dT} c_n(T. = \sqrt{\frac{2(2n+1)}{T}} u(T) – \frac{1}{T} \left(-n c_n(T) + \sum_{k=0}^n \sqrt{(2n+1)(2k+1)} c_k(T)\right) ][/latex]

    将 ( T. 换回 ( t ),将所有的 ( c_n(t) ) 堆在一起记为 [latex]( x(t) = (c_0, c_1, ⋯, c_N) )[/latex],那么根据上式可以写出:

    [latex][ x′(t) = A x(t) + B u(t) ][/latex]

    其中:

    [latex][ A_{n,k} = \begin{cases} \sqrt{(2n+1)(2k+1)}, & k < n \ n+1, & k = n \ 0, & k > n \end{cases}, \quad B_n = \sqrt{2(2n+1)} ][/latex]

    引入缩放因子来一般化结果也是可行的:设 [latex]( c_n(T. = λ_n \tilde{c}_n(T) )[/latex],代入上式整理得:

    [latex][ \frac{d}{dT} \tilde{c}n(T. = \sqrt{\frac{2(2n+1)}{T}} \frac{u(T)}{λ_n} – \frac{1}{T} \left(-n \tilde{c}_n(T) + \sum{k=0}^n \sqrt{(2n+1)(2k+1)} \frac{λ_k}{λ_n} \tilde{c}_k(T)\right) ][/latex]

    取 [latex]( λ_n = \sqrt{\frac{2}{2n+1}} )[/latex],就可以让 ( A. 不变,[latex]( B_n = \sqrt{2(2n+1)} )[/latex],就对齐了原论文的结果。如果取 [latex]( λ_n = \sqrt{\frac{2}{2n+1}} )[/latex],就可以像上一节LegT的结果一样去掉根号:

    [latex][ x′(t) = A x(t) + B u(t) ][/latex]

    其中:

    [latex][ A_{n,k} = \begin{cases} 2(2n+1), & k < n \ n+1, & k = n \ 0, & k > n \end{cases}, \quad B_n = 2(2n+1) ][/latex]

    但原论文没有考虑这种情况,原因不详。

    延伸思考

    回顾Leg-S的整个推导,我们可以发现其中关键一步是将 [latex]( (s+1) g′_n(s) )[/latex] 拆成 [latex]( g_0(s), g_1(s), ⋯, g_n(s) )[/latex] 的线性组合,对于正交多项式来说,[latex]( (s+1) g′_n(s) )[/latex] 是一个 [latex]( n )[/latex] 次多项式,所以这种拆分必然可以精确成立,但如果是傅立叶级数的情况,[latex]( g_n(s) )[/latex] 是指数函数,此时类似的拆分做不到了,至少不能精确地做到,所以可以说选取正交多项式为基的根本目的是简化后面推导。

    特别要指出的是,HiPPO是一个自下而上的框架,它并没有一开始就假设系统必须是线性的,而是从正交基逼近的角度反过来推出其系数的动力学满足一个线性ODE系统,这样一来我们就可以确信,只要认可所做的假设,那么线性ODE系统的能力就是足够的,而不用去担心线性系统的能力限制了你的发挥。

    当然,HiPPO对于每一个解所做的假设及其物理含义也很清晰,所以对于重用了HiPPO矩阵的SSM,它怎么储存历史、能储存多少历史,从背后的HiPPO假设就一清二楚。比如LegT就是只保留 [latex]( w )[/latex] 大小的最邻近窗口信息,如果你用了LegT的HiPPO矩阵,那么就类似于一个Sliding Window Attention;而LegS理论上可以捕捉全部历史,但这有个分辨率问题,因为 [latex]( x(t) )[/latex] 的维度代表了拟合的阶数,它是一个固定值,用同阶的函数基去拟合另一个函数,肯定是区间越小越准确,区间越大误差也越大,这就好比为了一次性看完一幅大图,那么我们必须站得更远,从而看到的细节越少。

    诸如RWKV、LRU等模型,并没有重用HiPPO矩阵,而是改为可训练的矩阵,原则上具有更多的可能性来突破瓶颈,但从前面的分析大致上可以感知到,不同矩阵的线性ODE只是函数基不同,但本质上可能都只是有限阶函数基逼近的系数动力学。既然如此,分辨率与记忆长度就依然不可兼得,想要记忆更长的输入并且保持效果不变,那就只能增加整个模型的体量(即相当于增加hidden_size),这大概是所有线性系统的特性。

    文章小结

    本文以尽可能简单的方式重复了《HiPPO: Recurrent Memory with Optimal Polynomial Projections》(简称HiPPO)的主要推导。HiPPO通过适当的记忆假设,自下而上地导出了线性ODE系统,并且针对勒让德多项式的情形求出了相应的解析解(HiPPO矩阵),其结果被后来诸多SSM(State Space Model)使用,可谓是SSM的重要奠基之作。

    HiPPO框架展现了优雅的数学结构和强大的应用潜力,在处理时间序列数据时提供了一种高效的记忆机制。未来的研究可以进一步探索其在不同领域中的应用和改进。

    参考文献: https://spaces.ac.cn/archives/10114

  • DeepSeek-V2 大模型

    DeepSeek-V2 是一个强大、经济高效的混合专家语言模型。它具有2360亿个总参数,每个标记激活的参数为210亿。相比于DeepSeek 670亿,DeepSeek-V2 在性能上更强大,同时节省了42.5%的训练成本,将KV缓存减少了93.3%,并将最大生成吞吐量提升了5.76倍[1]

    DeepSeek-V2的配置方法如下:

    1. 下载模型:DeepSeek-V2 可以从 HuggingFace 的官方网站下载[1]
    2. 安装依赖:在配置 DeepSeek-V2 之前,确保已经安装了以下依赖项:
    • Python 3.6 或更高版本
    • PyTorch 1.7.0 或更高版本
    • Transformers 4.0.0 或更高版本
    1. 导入模型:在 Python 代码中导入 DeepSeek-V2 模型,可以使用以下代码:
       from transformers import GPT2LMHeadModel, GPT2Tokenizer
    
       model_name = "deepseek-ai/DeepSeek-V2"
       tokenizer = GPT2Tokenizer.from_pretrained(model_name)
       model = GPT2LMHeadModel.from_pretrained(model_name)
    1. 使用模型:使用导入的模型进行文本生成或其他自然语言处理任务。以下是一个简单的示例:
       input_text = "你好,DeepSeek-V2!"
       input_ids = tokenizer.encode(input_text, return_tensors="pt")
       output = model.generate(input_ids, max_length=50)
       generated_text = tokenizer.decode(output, skip_special_tokens=True)
       print(generated_text)

    这些是配置 DeepSeek-V2 的基本步骤。根据具体的应用场景和需求,你可能需要进一步调整模型的参数和设置。


    Learn more:

    1. GitHub – deepseek-ai/DeepSeek-V2: DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model
    2. README.md · deepseek-ai/DeepSeek-V2 at main
    3. DeepSeek-V2/README.md at main · deepseek-ai/DeepSeek-V2 · GitHub

    配置和使用 DeepSeek-V2:强大、经济高效的混合专家语言模型

    DeepSeek-V2 是一款强大且经济高效的混合专家(Mixture-of-Experts, MoE)语言模型。相比于之前的 DeepSeek 67B. DeepSeek-V2 不仅性能更强,还显著降低了训练成本和资源消耗。本文将介绍如何配置和使用 DeepSeek-V2 模型。

    模型简介

    DeepSeek-V2 拥有 2360 亿个总参数,其中每个标记激活 210 亿个参数。与 DeepSeek 67B 相比,DeepSeek-V2 在多个方面表现出色:

    • 性能提升:模型性能显著提升。
    • 成本节约:节省了 42.5% 的训练成本。
    • 资源优化:KV 缓存减少了 93.3%。
    • 吞吐量提升:最大生成吞吐量提升了 5.76 倍。

    详细信息可以参考 DeepSeek-V2 的 GitHub 页面

    模型下载

    DeepSeek-V2 可以从 HuggingFace 的官方网站下载。以下是下载链接:

    环境依赖

    在配置 DeepSeek-V2 之前,确保你的环境中已经安装了以下依赖项:

    • Python 3.6 或更高版本
    • PyTorch 1.7.0 或更高版本
    • Transformers 4.0.0 或更高版本

    导入和使用模型

    导入模型

    在 Python 代码中导入 DeepSeek-V2 模型,可以使用以下代码:

    from transformers import GPT2LMHeadModel, GPT2Tokenizer
    
    model_name = "deepseek-ai/DeepSeek-V2"
    tokenizer = GPT2Tokenizer.from_pretrained(model_name)
    model = GPT2LMHeadModel.from_pretrained(model_name)

    使用模型进行文本生成

    使用导入的模型进行文本生成或其他自然语言处理任务。以下是一个简单的示例:

    input_text = "你好,DeepSeek-V2!"
    input_ids = tokenizer.encode(input_text, return_tensors="pt")
    output = model.generate(input_ids, max_length=50)
    generated_text = tokenizer.decode(output[0], skip_special_tokens=True)
    print(generated_text)

    详细配置和优化

    使用 Huggingface 的 Transformers 进行推理

    你可以直接使用 Huggingface 的 Transformers 库来进行模型推理。以下是一个示例代码:

    import torch
    from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
    
    model_name = "deepseek-ai/DeepSeek-V2"
    tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
    max_memory = {i: "75GB" for i in range(8)}
    model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True, device_map="sequential", torch_dtype=torch.bfloat16, max_memory=max_memory, attn_implementation="eager")
    model.generation_config = GenerationConfig.from_pretrained(model_name)
    model.generation_config.pad_token_id = model.generation_config.eos_token_id
    
    text = "An attention function can be described as mapping a query and a set of key-value pairs to an output, where the query, keys, values, and output are all vectors. The output is"
    inputs = tokenizer(text, return_tensors="pt")
    outputs = model.generate(**inputs.to(model.device), max_new_tokens=100)
    
    result = tokenizer.decode(outputs[0], skip_special_tokens=True)
    print(result)

    使用 vLLM 进行推理(推荐)

    为了更高效地执行模型推理,可以使用 vLLM 解决方案。以下是示例代码:

    from transformers import AutoTokenizer
    from vllm import LLM, SamplingParams
    
    max_model_len, tp_size = 8192, 8
    model_name = "deepseek-ai/DeepSeek-V2-Chat"
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    llm = LLM(model=model_name, tensor_parallel_size=tp_size, max_model_len=max_model_len, trust_remote_code=True, enforce_eager=True)
    sampling_params = SamplingParams(temperature=0.3, max_tokens=256, stop_token_ids=[tokenizer.eos_token_id])
    
    messages_list = [
        [{"role": "user", "content": "Who are you?"}],
        [{"role": "user", "content": "Translate the following content into Chinese directly: DeepSeek-V2 adopts innovative architectures to guarantee economical training and efficient inference."}],
        [{"role": "user", "content": "Write a piece of quicksort code in C++."}],
    ]
    
    prompt_token_ids = [tokenizer.apply_chat_template(messages, add_generation_prompt=True) for messages in messages_list]
    
    outputs = llm.generate(prompt_token_ids=prompt_token_ids, sampling_params=sampling_params)
    
    generated_text = [output.outputs[0].text for output in outputs]
    print(generated_text)

    许可证和引用

    许可证

    这个代码库使用 MIT 许可证。DeepSeek-V2 Base/Chat 模型的使用遵循模型许可证。DeepSeek-V2 系列(包括 Base 和 Chat)支持商业用途。

    引用

    如果你在研究中使用了 DeepSeek-V2,请引用以下文献:

    @misc{deepseekv2,
          title={DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model},
          author={DeepSeek-AI},
          year={2024},
          eprint={2405.04434},
          archivePrefix={arXiv},
          primaryClass={cs.CL}
    }

    联系方式

    如果你有任何问题,请在 GitHub 提出 issue 或联系 service@deepseek.com。


    更多信息请参考以下链接:

    1. GitHub – deepseek-ai/DeepSeek-V2
    2. README.md · deepseek-ai/DeepSeek-V2 at main
    3. DeepSeek-V2/README.md at main · deepseek-ai/DeepSeek-V2 · GitHub
  • 深入解析 LoRA+:如何通过调整学习率进一步优化LLM

    在当前大规模语言模型(LLM)的参数高效微调方法中,LoRA(Low-Rank Adaptation)无疑是一个重要的手段。此前,我们在《梯度视角下的LoRA:简介、分析、猜测及推广》中曾简单讨论过LoRA。今天,我们来学习LoRA的一个新结论:给LoRA的两个矩阵分配不同的学习率,LoRA的效果还能进一步提升。这一结论出自最近的论文《LoRA+: Efficient Low Rank Adaptation of Large Models》(下称“LoRA+”)。

    为什么调整学习率显得如此重要?

    乍一看,给不同的矩阵分配不同的学习率似乎并没有什么特别之处,因为配置不同的学习率相当于引入了新的超参数,而通常来说,只要引入并精调超参数,模型性能都会有所提升。然而,“LoRA+”的特别之处在于,它从理论角度肯定了这一必要性,并且明确指出最优解必然是右矩阵的学习率大于左矩阵的学习率。

    LoRA基本原理回顾

    假设预训练参数为

    [latex]( W_0 \in \mathbb{R}^{n \times m} )[/latex]

    ,如果使用全量参数微调,那么增量也是一个 ( n \times m ) 矩阵。为了降低参数量,LoRA将更新量约束为低秩矩阵,即设 ( W = W_0 + AB ),其中

    [latex]( A \in \mathbb{R}^{n \times r}, B \in \mathbb{R}^{r \times m}, r \ll \min(n, m) )[/latex]

    。在训练时,只更新 ( A. 和 ( B )。

    为了便于理解,我们假设层的输入是

    [latex]( X \in \mathbb{R}^{b \times n} )[/latex]

    ,层的运算为

    [latex]( XW = X(W_0 + AB) )[/latex]

    。由于“LoRA+”的结论跟预训练权重无关,不失一般性可以设

    [latex] ( W_0 = 0 )[/latex]

    ,那么层运算简化为

    [latex]( Y = XAB \in \mathbb{R}^{b \times m} )[/latex]

    结论简析

    “LoRA+”的结论是:为了使LoRA的效果尽可能接近最优,权重 ( B. 的学习率应该要大于权重 ( A ) 的学习率。

    这个结论的核心在于,虽然 ( A. 和 ( B ) 在表面上是对称的,但实际上它们有着固有的不对称性。即使将 ( A ) 或 ( B ) 全零初始化,结论依然成立。

    数值稳定性与贡献相当假设

    1. 数值稳定性

    数值稳定性意味着 ( X. 、 ( XA )、 ( XAB ) 的每个分量都应该是 ( O(1) ) 级别的,而不依赖于网络宽度 ( n, m )。虽然 ( XA ) 是中间变量,但它的数值稳定性对梯度稳定性至关重要。

    为了实现数值稳定性,假设 ( X. 是 ( O(1) ),那么 ( A ) 和 ( B ) 应该分别用 ( 1/n ) 和 ( 1/r ) 的方差初始化。

    2. 贡献相当

    为了使LoRA最优, ( A. 和 ( B ) 两个矩阵对效果应该有同等程度的贡献。我们可以通过损失函数 ( L ) 的变化来衡量 ( A ) 和 ( B ) 的贡献:

    [latex][ L(A + \Delta A, B + \Delta B. – L(A, B) \approx \langle \frac{\partial L}{\partial A}, \Delta A \rangle + \langle \frac{\partial L}{\partial B}, \Delta B \rangle ][/latex]

    在Adam优化器中,如果两个参数的学习率之比是 ( \lambda ),那么经过长期训练后,这两个参数的数量级之比也是

    [latex]( \lambda )[/latex]

    。因此,如果 [latex]( \eta_B / \eta_A = O(\sqrt{n}) )[/latex],那么 ( B. 和 ( A ) 的数量级之比也是 [latex]( O(\sqrt{n}) )[/latex],与我们的期望一致。

    快速推导

    通过矩阵求导,我们可以得到:

    [latex][ \frac{\partial L}{\partial A} = X^\top \frac{\partial L}{\partial Y} B^\top, \quad \frac{\partial L}{\partial B} = A^\top X^\top \frac{\partial L}{\partial Y} ][/latex]

    结合数值稳定性和贡献相当的假设,可以推导出:

    [latex][ \eta_B / \eta_A = O(\sqrt{n}) ][/latex]

    总结

    在这篇文章中,我们介绍并推导了“LoRA+”的结果,它支持LoRA的两个低秩矩阵 ( A. 和 ( B ) 存在固有的不对称性。不管将哪个矩阵全零初始化,都应该将 ( B ) 的学习率设置得大于 ( A ),以达到更优的效果。这一理论不仅在实践中有效,也为我们提供了一个经典的理论指导训练的范例。

  • 论文分享:Score Identity Distillation——更快更好的扩散模型蒸馏方法

    引言

    今天我们分享的是一篇名为《Score Identity Distillation: Exponentially Fast Distillation of Pretrained Diffusion Models for One-Step Generation》的新论文。该论文探讨了如何更快更好地蒸馏扩散模型。

    即便没有做过蒸馏,大家可能也能猜到蒸馏的常规步骤:随机采样大量输入,然后用扩散模型生成相应结果作为输出,用这些输入输出作为训练数据对,来监督训练一个新模型。然而,众所周知,作为教师的原始扩散模型通常需要多步(比如1000步)迭代才能生成高质量输出,所以且不论中间训练细节如何,该方案的一个显著缺点是生成训练数据太费时费力。此外,蒸馏之后的学生模型通常或多或少都有效果损失。

    有没有方法能一次性解决这两个缺点呢?这就是上述论文试图要解决的问题。

    思路简介

    论文将所提方案称为“Score Identity Distillation(SiD)”,基于几个恒等式来设计和推导了整个框架。实际上,它的设计思想与几个恒等式并没有直接联系,其次几个恒等式都是已知的公式而不是新的,所以这个名字显得相当随意。

    本文将其称之为“重现江湖”,是因为SiD的思路跟之前在《从去噪自编码器到生成模型》介绍过的论文《Learning Generative Models using Denoising Density Estimators》(简称“DDE”)几乎一模一样,甚至最终形式也有五六分相似。只不过当时扩散模型还未露头角,所以DDE是将其作为一种新的生成模型提出的,在当时反而显得非常小众。而在扩散模型流行的今天,它可以重新表述为一种扩散模型的蒸馏方法,因为它需要一个训练好的去噪自编码器——这正好是扩散模型的核心。

    接下来笔者用自己的思路去介绍SiD。

    初级形式

    假设我们有一个在目标数据集训练好的教师扩散模型 ( \epsilon_{\phi^}(x_t, t) ),它需要多步采样才能生成高质量图片。我们的目标是训练一个单步采样的学生模型 ( x = g_{\theta}(z) ),即一个类似GAN的生成器,输入指定噪声 ( z ) 就可以直接生成符合要求的图像。如果我们有很多的 ( (z, x) ) 对,那么直接监督训练就可以了(当然损失函数和其他细节还需要进一步确定,读者可以自行参考相关工作),但如果没有呢?肯定不是不能训,因为就算没有 ( \epsilon_{\phi^}(x_t, t) ) 也能训,比如GAN,所以关键是怎么借助已经训练好的扩散模型提供更好的信号。

    SiD及前作DDE使用了一个看上去很绕但是也很聪明的思路:

    如果 ( g_{\theta}(z) ) 产生的数据分布跟目标分布很相似,那么拿 ( g_{\theta}(z) ) 生成的数据集去训练一个扩散模型 ( \epsilon_{\psi^}(x_t, t) ) 的话,它也应该跟 ( \epsilon_{\phi^}(x_t, t) ) 很相似?

    这个思路的聪明之处在于,它绕开了对教师模型生成样本的需求,也不需要训练教师模型的真实样本,因为“拿 ( g_{\theta}(z) ) 生成的数据集去训练一个扩散模型”只需要学生模型 ( g_{\theta}(z) ) 生成的数据(简称“学生数据”),而 ( g_{\theta}(z) ) 是一个单步模型,用它来生成数据时间上比较友好。

    当然,这还只是思路,将其转换为实际可行的训练方案还有一段路要走。

    方法与公式

    扩散模型回顾

    我们采用《生成扩散模型漫谈(三):DDPM = 贝叶斯 + 去噪》的形式,对输入 ( x_0 ) 进行加噪:
    [ x_t = \bar{\alpha}_t x_0 + \bar{\beta}_t \epsilon, \quad \epsilon \sim N(0, I. ]

    训练 ( \epsilon_{\phi^}(x_t, t) ) 的方式则是去噪: [ \phi^ = \arg\min_{\phi} E_{x_0 \sim \tilde{p}(x_0), \epsilon \sim N(0, I. } \left[ | \epsilon_{\phi}(\bar{\alpha}_t x_0 + \bar{\beta}_t \epsilon, t) – \epsilon |^2 \right] ]

    同样地,如果我们想用 ( g_{\theta}(z) ) 的学生数据训练一个扩散模型,那么训练目标是:
    [ \psi^* = \arg\min_{\psi} E_{z, \epsilon \sim N(0, I. } \left[ | \epsilon_{\psi}(x_t^{(g)}, t) – \epsilon |^2 \right] ]

    其中 ( x_t^{(g)} = \bar{\alpha}t g{\theta}(z) + \bar{\beta}t \epsilon ),是由学生数据加噪后的样本,其分布记为 ( p{\theta}(x_t^{(g)}) )。

    学生模型的学习

    我们可以通过最小化教师模型和学生模型生成的数据分布差异来学习学生模型:
    [ \theta^* = \arg\min_{\theta} E_{z, \epsilon \sim N(0, I. } \left[ | \epsilon_{\phi^}(x_t^{(g)}, t) – \epsilon_{\psi^}(x_t^{(g)}, t) |^2 \right] ]

    注意这个优化依赖于 ( \theta ),所以当 ( \theta ) 通过上式发生改变时,( \psi^* ) 的值也随之改变,因此需要交替优化,类似GAN一样。

    点睛之笔

    上述方法存在理论与实践之间的gap,主要体现在两个问题:

    1. 理论上要求先求出上式的最优解,然后才去优化,但实际上从训练成本考虑,我们并没有将它训练到最优就去优化了;
    2. 理论上 ( \psi^* ) 随 ( \theta ) 而变,即应该写成 ( \psi^(\theta) ),从而在优化时应该多出一项 ( \psi^(\theta) ) 对 ( \theta ) 的梯度,但实际上在优化时我们都只当 ( \psi^* ) 是常数。

    SiD的核心贡献是通过恒等变换,尽量消除优化目标对 ( \psi^* ) 的依赖,从而有效缓解上述两个问题。

    恒等变换

    我们具体来看做了什么恒等变换。我们先来看去噪目标:
    [ E_{x_0 \sim \tilde{p}(x_0), x_t \sim p(x_t | x_0)} \left[ | \epsilon_{\phi}(x_t, t) + \bar{\beta}t \nabla{x_t} \log p(x_t | x_0) |^2 \right] ]

    根据得分匹配相关结果,上述目标的最优解是 ( \epsilon_{\phi^}(x_t, t) = -\bar{\beta}t \nabla{x_t} \log p(x_t) )。同理,学生模型的训练目标的最优解是 ( \epsilon_{\psi^}(x_t^{(g)}, t) = -\bar{\beta}t \nabla{x_t^{(g)}} \log p_{\theta}(x_t^{(g)}) )。

    此时我们有:
    [ E_{z, \epsilon \sim N(0, I. } \left[ | \epsilon_{\phi^}(x_t^{(g)}, t) – \epsilon_{\psi^}(x_t^{(g)}, t) |^2 \right] ]

    通过恒等变换,我们可以将上式化简为:
    [ E_{z, \epsilon \sim N(0, I. } \left[ \langle \epsilon_{\phi^}(x_t^{(g)}, t) – \epsilon_{\psi^}(x_t^{(g)}, t), \epsilon_{\phi^*}(x_t^{(g)}, t) – \epsilon \rangle \right] ]

    这就是SiD的核心结果,能够高效地实现蒸馏。

    其他细节

    1. 论文的推导默认了 ( \bar{\alpha}_t = 1 )。
    2. 论文的结果是以 ( \mu(\bar{x}_t) = x_t – \bar{\beta}_t \epsilon(x_t, t) / \bar{\alpha}_t ) 为标准给出的,这与扩散模型常见的表述方式不同。
    3. SiD最终取了上式的相反数作为额外的损失函数,加权到改进的损失函数上,以取得更优的蒸馏效果。

    延伸思考

    对于式(3)和式(4)的交替优化,有不少读者可能会想到,但SiD的精彩之处是提出了恒等变换,使得训练更加稳定高效。

    文章小结

    在这篇文章中,我们介绍了一种新的将扩散模型蒸馏为单步生成模型的方案,其思想可以追溯到前两年的利用去噪自编码器训练生成模型的工作,它不需要获得教师模型的真实训练集,也不需要迭代教师模型来生成样本对,而引入了类似GAN的交替训练,同时提出了关键的恒等变换来稳定训练过程,整个方法有颇多值得学习之处。

  • Fast Transformer Decoding: One Write-Head is All You Need

    引言

    《Fast Transformer Decoding: One Write-Head is All You Need》是一篇提出了一种变换模型多头注意力机制变体的研究论文。该论文解决了变换模型在增量推理过程中由于反复加载大规模“键”和“值”张量而导致的内存带宽成本问题。

    论文要点

    1. 多查询注意力(Multi-Query Attention)

    作者提出了一种称为多查询注意力的变体,其中键和值在所有注意力头之间共享。这种方法减少了张量的大小以及在增量解码过程中对内存带宽的需求。

    2. 内存带宽的减少

    通过共享键和值,所提出的方法显著减少了在解码过程中反复加载这些张量的内存带宽成本。

    3. 更快的解码速度

    采用多查询注意力的模型在解码速度上比传统的多头注意力模型要快得多。

    4. 质量轻微下降

    作者通过实验验证了多查询注意力模型在相较于基线模型的情况下,质量仅有轻微下降。

    进一步了解

    1. [1911.02150] Fast Transformer Decoding: One Write-Head is All You Need
    2. Fast Transformer Decoding: One Write-Head is All You Need | Papers With Code
    3. dblp: Fast Transformer Decoding: One Write-Head is All You Need.

    通过这篇论文,我们可以看到在不牺牲太多模型质量的前提下,通过优化内存带宽和共享键值对,可以显著提高变换模型的解码速度。这对于提升自然语言处理任务中的推理效率具有重要意义。


    多查询注意力

    多查询注意力是一种注意力机制,其中键和值在所有注意力头之间共享。这意味着在解码过程中,只需要加载一次键和值向量,从而显著减少了内存带宽成本。

    传统多头注意力

    传统的多头注意力是一种注意力机制,其中每个注意力头都有自己的键和值向量。这意味着在解码过程中,需要反复加载这些向量,从而导致内存带宽成本高。

    多查询注意力和传统多头注意力之间的区别

    多查询注意力和传统多头注意力之间的主要区别在于键和值的共享方式。在多查询注意力中,键和值在所有注意力头之间共享,而在传统的多头注意力中,每个注意力头都有自己的键和值向量。

    多查询注意力和传统多头注意力之间的区别是否会对模型的性能产生影响?

    实验表明,采用多查询注意力的模型在解码速度上比传统的多头注意力模型要快得多,且质量仅有轻微下降。

人生梦想 - 关注前沿的计算机技术 acejoy.com