0%

Ave Mujica 第五集一直在沉重的气氛中度过。无言的悲伤透过屏幕,来到我的心里。就像被巨石堵住的水流,我想要做些什么来释放这些情绪,却茫然无措。终于,在这集动画最后,灯的一句「一起组乐队吧!」瞬间击穿了我的心理防线,泪水止不住地流了下来……

在生活中,我一直保持了理性的态度,冷静地看待一切。只有偶尔有闲暇时,才会把感性的一面留给自己。一边哭着,我一边试图分析剧情,分析自己的想法,想要弄清楚现在的我究竟在想什么。可是,泪水就是止不住。本以为是困了,可是两天后在外面吃饭时,我又想起了这段剧情,眼睛又湿润了起来。

前几周,哪怕观点大不相同,我也积极地浏览着网上关于 Ave Mujica 热火朝天的讨论。而看完这集后,稍微看了一下贴吧,我就知道我的感想果然难以在网上找到共鸣。这一次,我害怕了,我不敢再去看别人的评论了。我想去诉说我的想法,传递给人海中的那些同路人,但不被理解的痛苦与恐惧占了上风。所以,这一周,我不敢看,也不敢想 Ave Mujica 的事。

一周快过去了,新的一集要播出了。不把此刻的想法说出来,我好像就会失去什么。最后我打算鼓起最后的一点勇气,在博客上讲一讲我的感想。不管是这篇文章,还是之后有关 Ave Mujica 的文章,很多东西不是在写剧中的角色,而是在写我自己;我也明白,这些文章不怎么会照顾他人的阅读体验,而只是写给我自己看的。不管自己的内心被暴露成了什么样子,我都想坦荡地面对。


在接连不断的事件后,第四集快节奏地结束了。剧终,在队员们的宣言下,Ave Mujica 解散了。

正如那干脆利落的解散宣言,第五集的开头,我们直接看到的是乐队解散后的场景。当红乐队在巡演中途突然解散,观看最后一场演出的观众排队退票。随后,网上布满了乐队解散的流言蜚语。面对事务所的一次次来电,祥子无力地趴在初华家的桌子上。初华想关心祥子,但真正在意的问题却是「我们还能在一起吗?」某日,仅留下一段简短告别便签的祥子,彻底宣告乐队的一切关系都结束了。

就像什么都没有来过似的,队员们的生活重新步入了「正轨」。在海铃看来,不过又多见识了一支解散的乐队,自己雇佣兵的身份并没有改变。初华还留在 Sumimi,和她的好搭档真奈继续着这个组合的活动。喵梦还是当着网红,活跃地出现在人们面前。祥子回到了破碎的家庭,重操客服旧业,赡养老爹,如轮回般过着和之前一样的日子。

可是,真的什么都没改变吗?

初华焦急地寻找着她心心念念的祥子,海铃则断然拒绝了一切有关过去的讨论。以知名演员为梦想的喵梦,也因再怎么努力也无法拥有天才般的演技,而回绝了演戏的良机。睦更是从人们的视线里消失了。

「那个人好像是 Ave Mujica 的 Oblivionis 哦。」

即使是听到了这样的杂谈,祥子依然带着职业般的微笑结束了客服的工作。平常地坐着电车,平常地回到出租屋。

老爹又被带到了警局。领他回家的时候,他又狼狈地坐在了地上。「如果是以前的话,这里是不是应该生一生气比较好呢?」在这一刻,祥子或许这样想过。但最后,她选择了最平常的做法,默默地看着。

对祥子来说,一切平常才是最大的不寻常——她已经不再有激情反抗,而是无奈地接受了一切。

无论是努力学习,努力工作,努力养家,都只是机械般重复地之前的动作,只是自我麻痹与忘却的手段。她不再努力改变自己的命运了。

动画的前半段正是这样如流水般展示主角们的生活片段。虽然没有深刻的剧情,但这淡淡的日常中流露出一股抹不去的哀伤。乐队解散就像还未下雨的乌云一样,笼罩在角色们的心里。


事情到这里还没有结束。很快,祥子的外公在她放学时找了过来。乐队解散的违约金已经全部付清,只需要告别不争气的父亲,回去当大小姐就行了。这一次离开破旧的出租屋时,祥子只是平静地带走了行李。重返大宅的那一刻,那个要强的她也已经死了。

——不,或许比真正的死亡还要痛苦。

作为一个在战争中失利的士兵,最渴求的应该是一次痛快的死亡。可是,如果被带到了敌国,好吃好喝伺候着,甚至连反抗的意识也完全失去了。这不是更大的一场悲哀吗?

只是为了和亲生父亲生活在一起,为了这小小的愿望,她头也不回地告别了大小姐的生活。命运给了和她年龄不匹配的生活压力,让她在学习之余不得不打工养家。只是为了和父亲在一起,只是想要反抗这不对的命运,她摧毁了心爱的乐队,冷酷地斩断了一切。可是,不管身体能够承受住多少劳累,心灵的腐朽只会与日俱增。在心理被压垮之后,她只好把一切押在了一支新乐队上。这或许是无意识间把乐队和幸福联系在了一起,又或许是通过获取名利向世界的一次复仇。为了保护那破碎不堪的心,祥子把自己抽离出来,全部寄托在乐队上——只要乐队做到了世界最好,我是不是就能获得幸福呢?

但很可惜,乐队又一次解散了。连接灵魂和世界的最后的一根弦,最后也断掉了。

「不就是大小姐过家家吗?」

当她放弃父亲,重回大宅的那一刻,她连最初反抗一切的念头也消失了。一切看似都回来了,但她的一切也全部消失了。

「我讨厌我自己」

她也意识到,丰川祥子,在精神上死去了。


在悲伤的镜头之间,穿插着高松灯幸福的回忆。

因为独特的爱好,她总是离人群有些疏远。但她从来没有放弃解读他人的想法,想要尽可能融入进去。

那一天,在随风飘散的花瓣前,她碰到了生命中那个重要的人。

祥子带她加入了乐队,认识了乐队的大家。就像春天的阳光一样,样子为她驱散了身边的寒气。《春日影》的每一个字,每一声调,都是大家共同创作的结晶。那是一段多么美好的日子啊!

后来,CRYCHIC 解散了。几经沉浮,灯用自己的心声,在 MyGO 乐队里与陌生和熟悉的朋友建立了牢固的关系。这一回,她终于融入了自己所属之处。

可是,回头望去,春日的暖阳已经不在了。留下的,只有月光下一个孤寂的影子,就和当年的自己一样。明明同样在弹着曲子,灯却无法从祥子那里再感受到之前的温暖。她敏锐地察觉到了祥子的变化,想要为祥子做点什么。虽然不知道这么做有没有用,她一直用便签试图和祥子沟通。

「祝你幸福」是祥子给她留下的最后的话。向他人送上最诚挚的祝福时,祝福的话语并不是为对方量身定做的考虑,而只是希望对方获得自己最求之不得的东西。可能是想到了这里,灯又一次在便签上写道:「小祥,你幸福吗?」


幸福是什么呢?

最早,祥子的幸福只是简简单单地和家人生活在一起。母亲去世后,幸福变成了用自己双手就可以争取到的东西。而父亲的事业失败,则让幸福变成了牺牲一切也难以把握的东西。

越是缺失,越想追求。越是追求,越是求而不得。幸福就是香气扑鼻的毒药,惹人沉醉,使人沦陷,最后带走一切。又一次经历乐队解散的祥子,终于明白,追求幸福才是一切的元凶。只有忘记一切,忘记幸福,才能「正确地」在世界上活下去。

「幸福是什么呢?」

灯向立希问道。经历了种种后,MyGO 队员间的关系已经牢不可破。望着眼前这个自己在意的人,想着眼前这美好而平凡的日子能够一天一天持续下去,立希害羞地说:「我现在就很幸福……」。

是啊,我们都很幸福。灯虽然不见得理解了立希对她特别的关心,但她能够感觉到,在 MyGO 里,大家都是幸福的。

所以,察觉到了什么的灯,向祥子发出了「是否幸福」的拷问。其他的话语都可以回避,唯有这个可望不可及的词语,一击突破了祥子的心防。她粗暴地回应了这个便签。她只是害怕,害怕这个总是给自己带来痛苦的东西。

祥子又一次逃跑了。只有灯的话,大概永远都追不上吧。而这一次,有行动力极强的爱音在她身边。机缘巧合之下,灯和爱音坐上了祥子回家的车,灯有了和祥子直面对话的机会。

灯又进入了那座豪宅。可是,一切都物是人非。再次来到那个曾经熟悉的琴房后,「你幸福吗」的答案已经显而易见了。现在,在这里的,只是一名楚楚可怜、自我封闭的少女。现在的她,就像当年的自己一样。而现在的自己,又能够说些什么,来表达自己的关心呢?

「我喜欢你」只是压在对方身上的一副重担。

「希望你能一直幸福」只是一句廉价的同情。

「小祥!一起组乐队吧!」

这是高松灯此时能说出的最恰当的话语。

听到这句话后,作为观众的我不禁潸然泪下。


看过 MyGO 的我们,知道「组乐队」对于灯来说有着特别的含义。要组乐队,就是一辈子的事。进乐队,不只是为了演奏,更是希望和关系紧密的朋友在一起,一直,一直,直到走完一辈子。

灯一直在尝试理解他人的心灵。哪怕没有交流,仅仅是通过乐曲,通过行为,通过一架钢琴,灯就体会到了祥子的那份孤独。所以,她下意识地说出要一起组乐队的话。组乐队,不仅是「希望你能幸福」的期望,还是「我会让你一辈子幸福」的承诺,更是「让我们携手共同幸福」的祈求。灯想表达的一切的一切,都浓缩在这句话里了。

如果祥子和我一样,能站在上帝视角理解灯的心意,也会情不自禁地落泪吧?

断绝一切的原因,是不相信会被他人理解。哪怕说出去了,换来的浮于表面的关心,只会让不被理解的痛苦又增一分。所以,对于自己难过的情绪,祥子永远都不会说出去。可是,哪怕只是有一刻,祥子会不会幻想着,有人能跨越一切,真正来到她的身边,为她抚平伤痕呢?

但幻想只是幻想,祥子不会在生活中相信这种事的存在。没有看过对方心路历程的她,或许无法理解灯的心意。这就是另一种悲哀了。


总之,Ave Mujica 第五集对氛围的渲染十分恰当,将细腻的情感一点一点展现出来。我一直把心安在角色们的身上,希望能体会她们每刻的感受。在片尾灯说出要组一起乐队的那一刻,在我的理性理解一切之前,我自然而然地明白到了灯的心意,感受到了祥子的寂寞。我心中暂存的两个灵魂连结到了一起,强烈的情感洪流涌了上来。这一幕着实让我感动。

我知道,和我有同样感受的观众太少了。我也害怕自己的想法无法被人理解。但我在网上,也看到了少数和我一样愿意仔细分析角色心理的人。所以,在新的一集播出之前,我也想分享我此时的感受,分享给同样用心感受动画的观众。

Transformer 中平方复杂度的注意力运算一直是其性能瓶颈,该问题在序列长度极大的视觉生成任务中尤为明显。为了缓解此问题,并生成分辨率高至 4K (4096x4096) 的图像,英伟达近期推出了文生图模型 Sana。Sana 不仅使用了线性注意力的 Transformer,还通过增加 VAE 压缩比等方式,极大提升了超高分辨率图像生成的性能。

Sana 论文示意图如下,生成的图像看起来还不错。值得注意的是,在 1K 图像生成上,Sana-0.6B 的生成速度是 FLUX-Dev 的 25 倍。由于采用了线性注意力机制,这一速度优势会随着图像尺寸增加而越发明显。

在这篇博文里,我们来着重学习 Sana 相较标准 DiT 文生图模型 (SD3, FLUX.1) 做了哪些提升性能的改进。

本文中,计算机术语「性能」表示运行速度。

线性注意力真的是「免费午餐」吗?

在看完论文摘要后,我的第一反应不是去细读论文,而是立刻去网上搜索社区用户对于 Sana 的评价。我一直坚持一个观点:「天下没有免费的午餐」。线性注意力比标准注意力快了那么多,那么它的质量一定会有所下降,不然所有人都会立刻把标准注意力换成线性注意力。

有网友在 Reddit 上分享了 Sana 的生成结果并对此开展了讨论:https://www.reddit.com/r/StableDiffusion/comments/1hzxeb7/it_is_now_possible_to_generate_16_megapixel/ 。有关图像质量的讨论有:

  • 楼主:「Sana 的 4K 模型生成出的图片没有 2K 的真实」。
  • 社区用户:「生成结果看起来更靠近抽象艺术,但这些效果已经能够通过 SD 1.5 + 超分辨率达到了。能否用 Sana 生成照片级图片?」楼主:「它不太擅长生成照片级图片。」
  • 社区用户:「4K 图片质量太差了,没有比 1K 图片更多的细节。许多上采样算法的结果会比这个更好。论文应该宣传其生成速度而不是丢人的 4K 图像质量。」
  • 楼主:「手画得不是很好。」

另一个帖子 (https://www.reddit.com/r/StableDiffusion/comments/1h5xujr/sana_nvidia_image_generation_model_is_finally_out/ )里的评论有:

  • 「考虑模型大小和速度的话,它的效果非常惊人。第一感看起来艺术感比 FLUX 好,但文本生成能力不太行。」
  • 「有 SD 1.5 的质量,但很快。再训练它可以达到 SDXL 的质量。」同楼里另一个用户则认为,Sana 质量在 SDXL 基础版和 FLUX 之间。

简单看下来,根据社区用户的反馈,Sana 的质量没有显著好于 SDXL。它的 4K 图像生成效果跟先生成较低分辨率再使用超分辨率算法的结果差不多。我们应该着重关注 Sana 的加速方式而不是其生成质量。

以往工作

潜扩散模型(Latent Diffusion Models)

扩散模型能够在多次迭代中生成高质量图像。而有研究发现,扩散模型在生成时早早就生成完了图像结构,后续时间都浪费在完善图像细节上。为此,潜扩散模型 (Latent Diffusion Models, LDM)使用了一个两阶段方法来提升扩散模型的性能:

  • 先用一个自编码器 VAE 把图像压缩成尺寸更小、信息更丰富的潜图像 (latent images)。
  • 用标准扩散模型生成潜图像。

目前主流的扩散模型几乎都属于 LDM。其中,最经典的 LDM 是 LDM 论文作者实现的 Stable Diffusion (SD)。根据 LDM 论文的实验结果,令 VAE 的压缩比 f 为 4 或 8 比较好。SD 采用了 f=8 的 VAE。后续多数 LDM 都使用了这样的压缩比。

Diffusion Transformers

早期扩散模型是用 U-Net 实现的,而后来大家逐渐把 U-Net 替换成了 DiT (Diffusion Transformers)。基于 DiT 的著名文生图模型有 SD3, FLUX。这些模型的核心仍是标准 Transformer 中的多头注意力运算。

线性注意力

设矩阵 $Q \in \mathbb{R}^{n \times d}, K \in \mathbb{R}^{n \times d}, V \in \mathbb{R}^{n \times d}$,其中 $n$ 为序列长度,$d$ 为特征长度,则注意力操作可以简写成:

对于矩阵 $A \in \mathbb{R}^{a \times b}, B \in \mathbb{R}^{b \times c}$,不做加速的前提下,朴素的矩阵乘法 $A B$ 的复杂度是 $O(a \cdot b \cdot c)$。

根据这个结论,注意力操作的复杂度为 $O(n^2d)$。这是因为 $Q \in \mathbb{R}^{n \times d}, K^T \in \mathbb{R}^{d \times n}$,第一步 $QK^T$ 的复杂度是 $O(n^2d)$。类似地,后一步矩阵乘法也是同样的复杂度。总复杂度不变,仍是 $O(n^2d)$。

由于模型特征长度 $d$ 是常数,我们只考虑序列长度 $n$ 的变化,所以可以认为标准注意力操作的复杂度是 $O(n^2)$。

但假如注意力运算中没有 softmax 操作的话,注意力运算就是两次矩阵乘法。

由于矩阵乘法满足结合律,通过改变矩阵乘法的顺序,我们能够神奇地改变注意力运算的计算复杂度。

由于 $K^T \in \mathbb{R}^{d \times n}, V \in \mathbb{R}^{n \times d}$,$K^TV$ 操作的复杂度是 $O(nd^2)$。由于 $Q \in \mathbb{R}^{n \times d}, K^TV \in \mathbb{R}^{d \times d}$,第二步矩阵乘法的复杂度还是 $O(nd^2)$。因此,总复杂度变为 $O(nd^2)$。不考虑 $d$ 的增长的话,这种注意力运算就是线性复杂度的。

我们再回头看去掉 softmax 对注意力运算有什么影响。softmax 函数同时做了两件事:1)保证 QK 的输出非负,以表示相似度;2)保证对于一个 query,它对所有 key 的相似度权重之和为 1,使得输出向量的范数(向量的「大小」)几乎不变。所以,线性注意力都会设置一个非负相似度函数 $sim(q, k)$,并用下面的注意力公式保证权重归一化。

根据 NLP 社区的反馈,线性注意力的效果比不过标准注意力。

相比之下,CV 社区对线性注意力的探索没有那么多。Sana 主要参考的早期工作为 Efficientvit: Lightweight multi-scale attention for high-resolution dense prediction。

Sana 模型架构改进

在模型架构上,Sana 主要做了三大改进:增加 VAE 压缩比、使用线性注意力、使用轻量级文本编码器。

深度压缩 VAE

在基于 LDM 的 DiT 中,和图块数(参与 Transformer 计算的元素个数,类似于 NLP 的 token)相关的超参数有:

  • VAE 压缩比例 f
  • DiT 图块化 (patchify) 比例 p

此外,VAE 输出的潜图像通道数 c 决定了重建质量与学习难度:c 越大,自编码器的重建效果越好,但扩散模型的学习难度也变大。

经过 VAE 后,图像会从 $H \times W \times 3$ 压缩成 $\frac{H}{f} \times \frac{W}{f} \times C$。又经过图块化操作后,图像大小会变成 $\frac{H}{fp} \times \frac{W}{fp} \times d$,其中 $d$ 是 Transformer 的特征长度。决定 Transformer 运算量的是图像尺寸 $\frac{H}{fp} \times \frac{W}{fp}$。标准 Transformer 一般至多处理 $64 \times 64$ 大小的图像。

此前的文生图 DiT 一般采用 f8 c4 p2f8 c16 p2 的配置。而 Sana 直接把 f 设置成了 32,实现了一个 f32 c32 的 VAE。

其实,LDM 论文尝试用过一个 f32 c64 的 VAE,但生成效果并不好。为什么这次 Sana 的 VAE 就好了不少呢?论文对此没有做深入分析,只是简单列举了一些 Sana 的 VAE 做出的改进:

  • 从模型设计与训练策略上:此前 f32 VAE 效果不好的一大原因是模型没有充分收敛。为此,Sana VAE 将模型中的标准自注意力换成了线性注意力。另外,为了提升在高分辨率图像上的生成效果,Sana VAE 先在低分辨率上训练,之后在 1K 图像上微调。
  • 从通道数上:作者比较了 f=32c=16, 32, 64 的实验结果,发现将 c=32 改成 c=64 会显著提升扩散模型收敛速度。

尽管 VAE 是卷积网络,里面还是包含了自注意力运算。

除了调整 f, c 外,作者还认为 p=1,即不使用图块化操作,是最好的。论文展示了总压缩比 fp 不变的前提下, p 提升、 f下降后扩散模型的学习进度。结果发现,不用图块化而让 VAE 全权负责压缩是最好的。

线性 Transformer 块

为了降低注意力操作的复杂度以提升性能,Sana 使用了线性注意力操作。如前所述,线性注意力会去掉 softmax 并通过矩阵乘法的结合律来降低计算复杂度,但需要通过额外设计一个相似度指标。

Sana 对线性注意力的定义如下:

其中,$O_i$ 是某一条 query $Q_i$ 的输出。这其实是让相似度函数为:

ReLU 函数用于让相似度非负。

在上面的线性注意力公式中,不管是分子还是分母,都可以通过结合律来优化计算复杂度。此处细节可参见原论文。

根据之前的经验,线性注意力会降低注意力运算的效果。为了弥补这一点,Sana 参考 EfficientViT,把前馈网络 (FFN) 从 MLP (即 1x1 卷积网络)换成了带 3x3 逐深度卷积的网络。这个小卷积网络不仅提升了整个 Transformer 块的拟合能力,还省去了位置编码——由于卷积网络天然能够建模相对位置关系并打破 Transformer 的对称性,不需要再加入位置编码了。

轻量级文本编码器

之前的 SD3 和 FLUX 都用 T5 作为文本编码器,但 Sana 为了提升模型性能,使用了速度为 T5-XXL 6 倍的 Gemma-2-2B。

定量实验

最后,我们简单看一下论文的主要实验结果。论文用到的实验数据集为 MJHQ-30K,包含三万张 Midjourney 生成的图像。

从定量指标上看,Sana 确实好过不少此前的文生图模型。但是,这些指标无法如实反映人类的审美偏好。如前文所述,社区用户认为 Sana 并没有明显好于 SDXL,但指标无法反映这一点。这些指标参考价值不大的另一个证据是 FLUX-dev 和 FLUX-schnell 的比较结果——作为一个被进一步蒸馏出来的模型,schnell 显然是比 dev 的生成质量更差的,但它的 FID, GenEval, DPG 竟然比 dev 还好。因此,在比较文生图模型质量时,我个人建议不要参考文生图的定量指标,而是去参考社区用户的反馈。

另外,虽然 Sana-1.6B 比 FLUX-dev 快了很多,但它比 FLUX-schnell 只快了一倍。或许 Sana 也可以通过蒸馏获得进一步的推理加速。

总结

Sana 是一个以降低运算开销为主要目标的高分辨率文生图模型。它主要通过增加 VAE 压缩比例、使用线性注意力来提升 DiT 的效率。为了弥补线性注意力的能力损失,Sana 将 FFN 改成了 3x3 卷积网络,在提升模型能力的同时免去了位置编码。除了这两大主要设计外,Sana 还通过使用轻量级文本编码器等诸多细节改进提升了模型的训练与推理效率。整体上看,这个工作主要在工程上作出了贡献,线性注意力的设计几乎照搬了之前的工作,没有使用比较新颖的模块设计。

从生成效果上看,尽管 Sana 论文给出的定量指标还不错,但这些指标是否能如实反映文生图质量还存疑。据社区用户反映,Sana 的质量没有明显好于 SDXL。另外,虽然论文一开头就宣称 Sana 能够生成 4096x4096 的图片,但这种图片的细节很差,和 1024x1024 的差不多。这是因为不管是 VAE 还是 DiT 都只在 1024x1024 上训练过。在加大生成尺寸后,图像的清晰程度没有变,只是看起来像素变多了。这篇论文真正应该强调的是生成 4K 图像的速度会更快,而不应该去强调 4K 图像的质量有多高。

从生成速度上来看,Sana 确实比最强开源文生图模型 Flux-Dev 要快很多。但尴尬的是,在 1024x1024 图像生成上,Sana 的速度仅仅是精心蒸馏的 Flux-schnell 的两倍。当然这个对比可能不是很公平,因为 Sana 还没有经过蒸馏。但就目前来看社区用户在生成 1024x1024 的图像时难以感受到 Sana 性能优势。

这篇文章很好地指明了 DiT 的一个发展方向:我们能不能将线性注意力更好地引入 DiT 呢?按我目前的理解,线性注意力就是通过牺牲自注意力全局依赖关系来提升模型计算速度。它的本质和 MAR (Autoregressive Image Generation without Vector Quantization)、VAR (Visual Autoregressive Modeling: Scalable Image Generation via Next-Scale Prediction) 很像,都是通过减少计算像素间的依赖关系来提升速度。这个假设在视觉任务中是很合理的:在确定了图像的主要结构后,理解细节只需要局部像素信息。然而,这些加速方法都不可避免地降低模型的能力。在完全不用和完全使用全局信息之间,我们或许要找到一个平衡点,来使 DiT 具有最佳性能和效果。

最近一篇论文因其吸引眼球的标题而刷屏科技自媒体:”The GAN is dead; long live the GAN!
A Modern Baseline GAN (GAN 已死?GAN 万岁!一个现代 GAN 基模)”。我不喜欢这种浮夸的标题,因为真正有价值的论文不必靠标题吸引人。带着怨气通读完论文后,我发现这篇论文果然没有做出特别大的创新。

这篇论文提出了一种叫做 R3GAN (读作 “Re-GAN”)的 GAN 基准模型。R3GAN 综合使用了 RpGAN 损失函数和特殊的梯度惩罚 (Gradient Penalty, GP) 损失函数,并基于前沿卷积网络 ConvNeXt 重新设计了一套 GAN 网络。实验表明 R3GAN 在 FFHQ 和低分辨率 ImageNet 图像生成上有着比肩扩散模型的 FID 分数。该工作主要是在工程实验上做了贡献,没有提出太多科研创新。在这篇博文里,我会简单介绍 R3GAN 的主要实现细节,并为各项细节提供参考文献而不做深入讲解,感兴趣的读者可以查阅文末总结的参考文献。

GAN 回顾

在这一小节里,我们会回顾读懂 R3GAN 必备的和生成对抗网络 (GAN) 相关的知识。

GAN 基础知识

和其他多数生成模型一样,GAN 的训练目标是建模一个好采样的分布(高斯分布)到一个不好训练的分布(训练数据集)的映射方式。具体来说,GAN 会用一个生成器 (Generator) 把来自高斯分布的噪声 $z$ 设法变成图像 $x$。其他生成模型大多有自己的一套理论基础,并根据某理论来设置生成器的学习目标。而 GAN 用另一个神经网络——判别器 (Discriminator) 来学习生成器的训练目标。

两个模型的学习是通过一种博弈游戏实现的:判别器试图分辨图片是否是「假」的,或者说是否是生成出来的,而生成器通过提升生成图像质量来让判别器无法分辨图片真假。二者共用一套优化目标,但一方希望最小化目标而另一方希望最大化目标。

在上面的损失函数中,$f$ 有很多种选取方式。R3GAN 选用了 softplus 函数,如上图所示。

两大经典结构 DCGAN 和 StyleGAN

GAN 的开山之作是用全连接网络实现的。在 GAN 的后续发展过程中,有两个经典的网络结构:2016 年 的 DCGAN 和 2019 年的 StyleGAN。

DCGAN 是一个生成器基于卷积神经网络 (CNN) 的 GAN。它的特点是对低通道特征逐渐上采样并逐渐减少通道数,直至生成目标大小的三通道图像。

StylGAN 是一个训练较稳定且适合做图像编辑的 GAN。和传统 GAN 生成器相比,StyleGAN 从一条「旁路」输入被映射网络 (Mapping Network) 预处理过的噪声 $z$,此处信息注入的方式是风格转换 (Style Transfer) 中的 AdaIN 操作。由于输入噪声的方式变了,原来的低分辨率特征图输入被换成了一个常量。

两大难题:难收敛和模式崩溃

相比其他生成模型,GAN 常被诟病「难训练」。这种难训练体现在难收敛、模式崩溃 (Mode Collapse)。难收敛即意味着模型对数据集拟合效果不佳,我们可以用 FID 来评估模型输出与训练集的相似程度。模式崩溃指的是对于一个多类别数据集,模型只能生成其中少数类别,示意图如下所示(图片来源)。为了检测是否出现模式崩溃,我们既可以让网络随机生成大量图片并用另一个分类网络统计其中出现的类别数,也可以用生成召回率 (recall) 来大致评估模型的采样丰富度。

R3GAN 实现方法

R3GAN 在引言里把 StyleGAN 中的各种提升 GAN 稳定性的小技巧批判了一通,并主张应该使用尽可能简洁的 GAN 生成器。虽然论文是这样写的,但实际上 R3GAN 是在更早的 DCGAN 的基础上,更新了损失函数,并用上了最新的 CNN 结构,和 StyleGAN 结构几乎无关。我们来从这两个方面来学习 R3GAN:损失函数和模型结构。

损失函数

在描述博弈游戏的 GAN 损失上,R3GAN 把标准 GAN 损失换成了 RpGAN (relativistic pairing GAN) 论文中的 GAN 损失。相比之下,RpGAN 损失用一对真假样本的判别器输出之差送入激活函数 $f$,而不是分别把真假样本的判别器输出送入 $f$。

根据之前的研究结果,作者从直觉和理论上简单解释了 RpGAN 的好处:

  • 以前的 GAN 损失只要求判别器区分真假样本,而不要求真假样本之间的距离要尽可能大。通过把一对真假样本的差输入损失函数,RpGAN 损失可以让真假样本之间的差距尽可能大。
  • 根据之前论文的理论分析,在一些简单的配置下,标准 GAN loss 有数量指数增长的较差局部极小值,而 RpGAN loss 的每个局部极小值都是全局最小值。

R3GAN 还重新对最佳的梯度惩罚(Gradient Penalty, GP)损失函数做了消融实验。$n$-GP 表示让模型对输入的梯度尽可能靠近实数 $n$,从而使得训练更加稳定。常用的 GP 有 0-GP 和 1-GP:

  • 0-GP:最优情况下,模型对任意输入都给出完全一样的结果。
  • 1-GP:最优情况下,模型的输出对输入平滑变动:输入张量的范数变 1,输出张量的范数也变 1。

作者认为 0-GP 对 GAN 判别器是较好的。因为当生成器的输出完全和训练集一样时,判别器是无法区分任何输入的,对任何输入都会给出一样的输出。

对判别器的 GP 有 $R_1$ 和 $R_2$ 两种形式,分别表示对真/假数据采取 GP。作者发现同时用 $R_1$ 和 $R_2$ 更好。

总结一下,R3GAN 使用的损失函数为 RpGAN + $R_1$ + $R_2$。作者用简单的实验证明这是最优的。如下图所示,在一个有 1000 种类别的简单数据集上,最佳损失配置能够生成所有类别的数据,且有更小的分布距离 $D_{KL}$ (和 FID 指标类似,越小越好)。不用 RpGAN 损失会同时降低输出多样性和收敛度,不用 $R_2$ 会使训练完全无法收敛。

现代化的卷积网络

找出了一个简洁而有效的损失函数后,R3GAN 论文接着探究了更优的卷积网络结构。文中提及了五套配置:

  • A:原版 StyleGAN2
  • B:把 StyleGAN2 里的绝大多数设计删掉,让模型和 DCGAN 几乎相同。
  • C:换用上一节里发现的新损失函数
  • D:把类似 VGG 的神经网络加上 ResNet 中的残差连接
  • E:用 ConvNeXt 里的模块更新 ResNet

我不明白作者为什么要强行从 StyleGAN2 开始做实验,然后把 StyleGAN2 中的设计批评一顿,删掉全部设计,再换成一个更早期的 DCGAN。直接用 DCGAN 不是更加简洁吗?我猜测是因为作者用了 StyleGAN2 的代码做开发,花很多精力把 StyleGAN2 删成了 DCGAN。既然代码都改了,不写进论文就太浪费了。

我们跳过配置 A,直接看配置 B 和早期的 DCGAN 有什么区别。经作者总结,配置 B 中比较重要的几项区别有:

  • a) 用 $R_1$ 损失。
  • b) 用较小的学习率且关闭 Adam 优化器中的动量
  • c) 所有网络中均不使用 Normalization 层
  • d) 用 bilinear 上采样代替反卷积

其中,不改 a), b), c) 会使训练失败。d) 是当今神经网络上采样的标准配置,可以防止出现棋盘格 artifact。

配置 C 中的新损失函数我们已经在上一节中讨论过了。

包括 StyleGAN 在内,此前 GAN 的神经网络多采用了类 VGG 结构,没有残差块。而配置 D 将标准 ResNet 中的 1-3-1 残差块引入了网络。

配置 E 则是进一步更新了卷积层的设计。该配置首先引入了 group convolution 操作(将通道分组,同组通道相互连接。group=1 即 depthwise convolution)。由于该操作更加高效,网络能够在总运行时间不变的前提下加入更多参数。此外,配置 E 还用到了 ConvNeXt 中的反瓶颈(inverted bottleneck)块,该模块的设计灵感来自于 Transformer 中的全连接层。

我们再看一次各配置的简单消融实验结果。看起来新损失函数并没有多大的提升,最后比较有效的是网络架构的改进。最好的配置 E 模型能够略优于 StyleGAN2。

定量实验结果

最后,我们来看论文中的定量结果。如前所述,我们主要关心 GAN 的两个指标:多样性与收敛度/图像质量。前者能够通过数类别或者召回率反映,后者能够通过与训练集的 FID(以及本文使用的 $D_{KL})$反映。

多样性

在小型多类别数据集上,R3GAN 能够生成出所有类别,且有最好的训练集相似度。而 StyleGAN2 无法生成少数类别。

另一个能够反映图像多样性的指标是召回率,它大致表示了训练集有多少内容能够在生成集里找到。论文没有提供表格,只简单说了一下 CIFAR-10 上 StyleGAN-XL 是 0.47,R3GAN 0.57。但总体来看 R3GAN 的召回率总是会低于扩散模型。

收敛度

本工作宣传时的一大亮点是它在部分数据集的 FID 分数超过了扩散模型。我们看一下本工作在单一、多样数据集上的 FID 结果。

先看经典的 FFHQ 人脸数据集。在这种多样性较低的数据集上,GAN 的表现一直很不错。R3GAN 的 FID 好于 StyleGAN2 和多数扩散模型,且只需要一次推理 (NFE=1)。但它的 FID 并没有优于此前最好的 GAN 模型。当然,此前的 GAN 用了一种提升 FID 而不提升图像质量的技巧,R3GAN 没有用。

再看类别比较丰富的 CIFAR-10 和 ImageNet 数据集。R3GAN 的表现也比所有扩散模型和大部分 GAN 强。但 R3GAN 没有在更大分辨率的 ImageNet 上测试。现在前沿生成模型一般都会测试 ImageNet-256,而 R3GAN 并没有提供相关实验结果。

总结与评论

R3GAN 是一种现代版的 DCGAN。它从损失函数和模型结构两个方面做了改进。损失函数方面,DCGAN 用了 RpGAN + $R_1$ + $R_2$ 损失函数;模型结构方面,R3GAN 用 ConvNeXt 中的最新卷积结构代替了原来的 VGG 结构。实验表明 R3GAN 在 FFHQ-256 和 ImageNet-64 的 FID 分数上超过了全部扩散模型和多数 GAN,略差于此前最强的 GAN。在生成多样性上,R3GAN 依然没有扩散模型好。

从科研贡献上来看,这篇文章并没有提出新理论或新想法,完全复用了之前工作提出的方法。这篇文章主要提供了一些工程上的启发,有助于我们去开发更好的基于 CNN 的 GAN。从结果来看,R3GAN 并没有在目前主流指标 ImageNet-256 上测试,没有迹象表明它能好过扩散模型。我们可以从其他数据集上的实验结果推断,R3GAN 的最佳表现和之前的 GAN 差不多,没有对 GAN 作出本质的改进。综上,我认为这篇文章是质量满足顶会要求的平庸之作,被 NIPS 2024 选为 Poster 合情合理。

参考文献

DCGAN: Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks

StyleGAN: A Style-Based Generator Architecture for Generative Adversarial Networks

StyleGAN2: Analyzing and Improving the Image Quality of StyleGAN

GP (WGAN-GP): Improved Training of Wasserstein GANs

RpGAN: The relativistic discriminator: a key element missing from standard GAN

RpGAN Landscape Explanation: Towards a Better Global Loss Landscape of GANs

ConvNeXt: A convnet for the 2020s

ImageNet FID Trick: The role of imagenet classes in fréchet inception distance

最近,我在网上看到了一个专门介绍深度学习源代码的网站(https://nn.labml.ai/index.html ),各类流行网络架构一应俱全。这个网站最吸引我的,是它的双列式结构。如下所示,每个页面右边显示代码,左边显示每段代码对应的注释。

一直以来,我在博客中介绍代码时,都是先写描述文字,再贴一段代码。这种方式对作者和读者来说都十分低效。受到上面那个网站的启发,我决定以后也采用这种方式介绍源代码。很可惜,上面那个网站是由 labml.ai 这个组织维护的,似乎并没有提供开源的、可定制的注释网站搭建方式。为此,我找到了一个替代品:Pycco。它可以方便地为 Python 代码生成双列带注释的静态页面。

Pycco 安装与使用

Pycco 是页面生成工具 Docco 的 Python 实现版。它们都是「快而脏」的文档生成器:源代码只有几百行,没有多余封装,直观而暴力地生成 HTML 页面的所有内容。它们为初学者只提供了一个文档页面,这个文档页面讲的是它们的源代码,且文档页面就是由它们本身创建出来的。Pycco 的官方文档页面如下所示。一开始介绍完 Pycco 的背景信息和安装方式后,文档就会直接开始介绍源代码。

根据文档的指示,我们可以通过下面的命令在 pip 里安装 Pycco:

1
pip install pycco

在使用 Pycco 时,我们完全不用去理解其源代码的意思,只需要准备一个带注释的 Python 源文件就行。Pycco 提供了一键式命令,帮我们把源文件里的注释和代码分离,并生成左侧为注释,右侧为代码的静态文档页面。这里的注释既包括了 # 开头的单行注释,也包括了 """ """" 包裹的多行注释。注意,三个单引号构成的多行注释 ''' ''' 不会被该工具识别。

我们来看一个示例。假设我们有下面一个名为 hello.py 的 Python 源文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
"""
Hello
World
"""
import torch
import pycco

# Hello
print("hello world")

"""
End of file
"""

我们用下面的命令为其生成文档页面。

1
pycco hello.py

页面会生成在目录下的 docs 子目录里,子目录包含一个 hello.html 文件和一个 pycco.css 文件。我们可以用浏览器打开 .html 网页,看到下图所示内容。

注释默认使用 Markdown 语法。如果平时就习惯用 Markdown 写博客的话可以无缝切换。但是,默认情况下网页无法渲染公式。

页面跳转

除了普通的 Markdown 语法外,Pycco 还支持一个特殊的功能:文档跳转。我们可以把文件名写在 [[ ]] 内,实现源文件内部或源文件间的跳转。特别地,我们还可以在页面某处打上标签,并跳转到某页面的标签处。以下是一个示例。为了给「注释」加上注释,我别出心裁地把注释写进了代码部分。

让页面渲染 LaTeX 公式

刚才我们发现,目前的 Pycco 页面并不支持公式渲染。而在解释深度学习代码时,很多时候不得不用到公式。因此,我决定给 Pycco 加上渲染公式的功能。

Pycco 这种直观暴力的实现方法让网页开发者能够快速地修改页面生成逻辑。然而,我已经把 HTML 的知识快忘光了,配不上「网页开发者」这个名号。因此,我让 ChatGPT o1 来帮我开发这一功能。

经指导,我认识了 MathJax 这个在网页上渲染公式的工具。只需要在 HTML 的 head 里导入一些包,网页就可以自动识别单行公式 和多行公式 $$$ $$$。我不记得 head 是什么了,但大概能猜到这个是一个相当于声明全局变量的语句块。

我在 pycco_resources\__init__.py 文件里找到了设置 head 的地方。这个文件提供了生成网页的模板,包括了写死的 CSS 文件和部分 HTML 代码。打开这个文件的最快方式是在某处写 import pycco_resources,之后用 IDE 的代码跳转找到这个包在本地的位置。

我们在该文件下图所示的位置插入和 MathJax 相关的代码。

1
2
3
4
5
6
7
8
<script>
MathJax = {
tex: {
inlineMath: [ ["$","$"] ]
}
};
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>

修改模板后,重新生成网页,可以发现现在公式能够正确渲染了。

高亮新增/删除代码

在以后讲解代码的时候,我想高亮新增的或者删去的代码,就像 GitHub 里显示代码的更改一样。由于 Pycco 非常容易做二次开发,我又去请教 ChatGPT o1 该如何实现这样的功能。

经指导,我了解到代码高亮可以通过设置背景颜色 style 来实现。为此,我需要做两件事:

  1. 新增有关背景颜色的 CSS style
  2. 想办法指定要高亮的代码块

第一件事很简单,只需要打开模板文件 pycco_resources\__init__.py,添加背景颜色 style 即可。我添加了两种背景颜色,分别表示删去的和新增的代码块。我是在 VSCode 里找到想要的颜色值的。随便打开一个 CSS 文件,输入一个大概的颜色值,VSCode 就会弹出一个选择颜色的小窗口。

1
2
3
4
5
6
.highlighted-line-1 {
background-color: #fa53208c;
}
.highlighted-line-2 {
background-color: #68fc5d;
}

第二件事就比较难了。我需要先看懂目前高亮源代码的逻辑,再在其基础之上添加背景高亮的功能。Pycco 的主函数在文件 pycco/main.py 里,我们可以用导入 Python 包的方式快速找到这份源文件。原来的高亮关键字的逻辑如下,我在其中加入了一些代码用于输出中间结果。

函数的主要输入是列表 sections,函数的输出存储在列表项 section"docs_html", "code_html" 字段。

sections 内容如下,可见其中是过了解析器的注释块和代码块。每一段注释都会对应一段代码。

1
[{'docs_text': '=== BeGin ===\r\n\n', 'code_text': 'import torch\r\nimport pycco\r\n\r'}, {'docs_text': '# Hello\r\n## World\r\nThis is a **Python** [code](https://www.python.org/).\r\nTry formula $\\epsilon$ .\r\n$$\r\n3 = 1 + 2\r\n$$\r\n\n\n', 'code_text': 'print("hello world")\r\n\r'}, {'docs_text': 'End of file\r\n\n', 'code_text': '\n'}]

函数的输出是完整 HTML 文件的一部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
<h3><span id="begin" href="begin"> BeGin </span></h3>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">torch</span>
<span class="kn">import</span> <span class="nn">pycco</span></pre></div>
<h1>Hello</h1>
<h2>World</h2>
<p>This is a <strong>Python</strong> <a href="https://www.python.org/">code</a>.
Try formula $\epsilon$ .
$$
3 = 1 + 2
$$</p>
<div class="highlight"><pre><span class="nb">print</span><span class="p">(</span><span class="s2">&quot;hello world&quot;</span><span class="p">)</span></pre></div>
<p>End of file</p>
<div class="highlight"><pre></pre></div>

那么,我的目标就是修改这个文件。我需要先根据输入的原始代码块,判断这段代码是否要高亮,再修改 HTML 代码块的内容。

首先,从用户的角度考虑,应该怎么指定要背景高亮的代码呢?既然现在代码被拆成了一块一块的,且每块代码对应一段注释,我决定用一些特殊注释来高亮一整块代码,就像前面的设置标签和跳转标签的特殊注释一样。我加入了以下判断:如果注释前几个字符是 '===ADD===''===DEL===',就用对应的颜色高亮这段代码。

判断了是否要高亮后,我还需要做对应的修改。我不仅要在 HTML 代码块里高亮代码,还需要把注释块里的特殊命令删掉。通过观察相关代码,我忽然回忆起了 HTML 的部分实现原理:背景高亮就是把一段 HTML 字符串封进一个带有背景高亮样式的标签 <div></div> 里。那剩下的删除注释也很简单,只需要对字符串做一点简单操作就行了。代码修改过程及使用示例如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
# Add following code

delete_background_start = '<div class="highlighted-line-1">'
add_background_start = '<div class="highlighted-line-2">'
background_end = '</div>'

if section["docs_text"].startswith('===ADD==='):
section["docs_text"] = section["docs_text"][9:]
section["code_html"] = add_background_start + section["code_html"] + background_end
elif section["docs_text"].startswith('===DEL==='):
section["docs_text"] = section["docs_text"][9:]
section["code_html"] = delete_background_start + section["code_html"] + background_end

修正多行注释缩进

在写这篇博文的时候,我又发现 Pycco 的一个 bug:在多行注释带缩进时,HTML 页面上的注释也会缩进,导致页面布局完全乱套。

我找到了 pycco/main.py 里处理多行注释的代码。我把旧代码全部删掉,换了一种暴力的字符串处理方式:删除多行注释左侧所有空格及制表符。

1
docs_text += line.lstrip('\t ') + '\n'

改完之后 bug 就修好了。

总结

在这篇文章中,我介绍了 Pycco 这个为 Python 代码生成双列注释-代码静态页面的 Python 工具,并分享了我对其的三个修改:支持 MathJax 公式、支持背景高亮、修复多行注释缩进 bug。相比介绍开源项目的 readthedocs 等文档构建工具,Pycco 能够为每行 Python 代码写注释,尤其适合详细讲解深度学习代码。对于熟练的网页开发者,Pycco 的源代码很短,改起来很方便;但另一方面,Pycco 似乎比较小众,多年都没人维护,可能有各种 bug。主要推荐想在个人博客里讲解代码的朋友使用这个工具。我以后都会用它来讲解代码。

ChatGPT 小插曲

ChatGPT 在指导我添加 MathJax 时,先叫我下载 python-markdown-math 这个 pip 包,让 Python 脚本里的 Markdown 解析器能够解析公式。但我在实践中发现不解析 $$$$ 符号,在正确导入 MathJax 包后,网页依然能够正确渲染。

ChatGPT 一开始提供的 MathJax 导入代码是 MathJax V3 的。经过 python-markdown-math 解析后,双行注释被转换成了 <script type="math/tex; mode=display"></script> 包裹的内容。这些公式没法成功渲染。我把添加双行公式的详细过程提供给 ChatGPT 后,它分析出这种 <script> 的写法是 MathJax V2 的,让我把 head 里的包改成 MathJax V2。修改过后,双行公式果然渲染成功了。然而,如上一段所述,我发现哪怕不用 python-markdown-math 也行。于是我又用回了 MathJax V3。可见 ChatGPT 有功有过,给我制造了一些麻烦,也算是能够帮我解决。

在求助 ChatGPT 添加背景高亮功能时,它非常出色地分析了问题:先理解 Pycco 生成 HTML 的过程,再决定用户如何指定要高亮的内容,最后根据不同的方案做实现。但它给的三个信息输入方案都不尽如人意:(1) 在命令行里指定要高亮的行;(2) 在每行要高亮的代码前都加特殊注释;(3) 用另一个配置文件来指定要高亮的行。这些方法的用户体验都不好,且难于实现。当然,由于我没有把所有源代码给它,它或许没有发挥完整的实力。最后我还是靠自己理解了 Pycco 的页面生成逻辑,选了一种好实现且好用的方案。

我从来没有用 ChatGPT 做过特别复杂的编程任务,这次稍微体会了一下。感觉它的能力有时候会出乎意料地强(比如发现 HTML 的风格是 MathJax V2 的),但不见得次次都靠谱。它的能力主要还是基于大量数据的「搜索」,加上一点浅显的思考。只是因为看过的数据过多,所以很多时候能从历史经验中直接给出很好的答案。当然,作为语言模型,它对于语言的理解能力极强,哪怕描述不完全也能准确理解用户的意图。做为使用者,我们要在用之前就想好能从 ChatGPT 那里获取到哪些帮助,并提供充足的信息,而不能全盘接受它的输出,就和我们日常和别人交流一样。

年底难得有闲暇,我打算首次写一下年终回顾。写这样的文章总是很害羞,我现在不好意思去看我以前写的「随笔」类文章了。但是,今天,我就是有一股冲劲,想要实现心中那小小的想法,不想被生活的洪荒冲走我那鲜活的热情。不去思考为谁而写,不去思考是否有人会看,不去纠结于遣词造句。只求能够纯粹地表达。

人的思考有时非常奇怪。有时会对未来产生过分的期待,期待过后又是更深的失落。小的时候,盼望长大。可自从大学毕业以后,见过了周围人的种种言谈,我又害怕起了变老。明明二十多岁,正值人生中精力最鼎盛的时期,一向乐观的我也时常害怕起了年龄变大,害怕像周围人一样慢慢失去对未来的期盼。就是在这样一种心理下,我在 2024 年却碰到很多令人惊喜的事,让我继续有了向前看的动力。


先说游戏吧。我一直觉得这几年新游戏都没什么创新,但从今年年初开始,我见到了非常多有意思的游戏。「小丑牌」将自走棋的经济系统与爬塔类游戏结合,创造出了一款机制简明却不失趣味与深度的游戏,挖掘出了 Roguelike 类游戏好玩的本质。《背包乱斗》拓展了以前背包类游戏的机制,通过 PVP 自走棋的游戏形式让游戏自带了平衡性与挑战性,从而让背包类游戏的魅力得以绽放。但成也自走棋败也自走棋,做成 PVP 后,游戏的趣味性必然会大打折扣。作者也没能深刻理解自走棋类游戏该如何设计,不去思考怎么做平衡和简化游戏机制,不停地试图加新内容。游戏一下就玩腻了,非常可惜。年中出的《动物井》和《海天岛传奇》我没玩,但我很高兴能见到这些优秀的解谜类游戏。《风暴之城》是今年最令我惊喜的游戏。我从小就很喜欢玩《主题医院》、《龙之崛起》这类模拟经营游戏,但这些游戏的特点是趣味性大于挑战性,战役的每一关的玩法都大同小异,很容易玩腻。而《风暴之城》把近年来的 Roguelike 设计思路引入了模拟经营游戏,并精心设计了会让玩家游戏失败的「危机」,玩游戏时能体会到玩即时战略游戏的那种刺激感。最终,《风暴之城》兼具了 Roguelike、 模拟经营、即时战略等多种游戏的优点,对于喜欢二十多年前的游戏的老玩家来说非常新鲜又亲切。在我心中,《风暴之城》已经代替《英雄无敌3》,成为了综合游戏体验最佳的游戏。另一个我今年才挖掘出来的游戏是《幸运房东》,这个脱胎于爬塔机制的游戏成功开创了「幸运房东类」这一新型游戏。这类游戏操作简单,只需要每次从新的「卡牌」中三选一,最终组建出一套成型构筑。这类游戏把构筑类游戏复杂的「测试构筑」环节(比如暗黑类游戏用当前装备打怪,《杀戮尖塔》里用当前卡组战斗)大幅简化,只单独考验玩家的组建构筑能力,充分把一项游戏机制做到了极致。除了本体外,我还尝试了许多后续「幸运房东类」游戏,最终认识了《轮作法》这个游戏。这个游戏把自走棋玩法融入「幸运房东类」游戏,同时把《炉石传说:酒馆战棋》里的卡牌空位管理机制加入了游戏,让一个有诸多随机机制的游戏充满了思考深度。《轮作法》是我继《岛民》(islander) 以来见过我最像棋类的游戏(信息公开,需要玩家做运算,搜索当前已知最优解),我非常喜欢这类有思考深度的游戏。《流放之路》我也稍微玩了下,但游戏机制没怎么大改,我稍微回味了一下就没玩了。倒是新暗黑类游戏《最后纪元》也让我很惊喜。由于游戏是新出的,攻略很少,我第一次享受了在暗黑类游戏里自己组构筑的快乐。可惜这个游戏重玩体验没有《流放之路》高,第二个赛季我就不太想玩了。去年年底到今年年初我还认识了《战场兄弟》这个游戏,我也没想到这几年来还有类似于《英雄无敌》的以战棋为子玩法的游戏,我很开心能见到这类游戏。

剩下我的日常游戏时间都主要投入在了重玩性强的挑战类游戏上。我从去年下半年开始打《雀魂》,年初被打到道心破碎。后来抽时间稍微玩了一下,总算上了雀豪2,暂时没有继续玩的打算。上半年我又反复提升了《亿万僵尸》的技术,最高难度不暂停的胜率大幅提高。只要我完全认真玩,就很少有过不了的地图。下半年压力很大,我又开始玩《杀戮尖塔》,对游戏的理解越来越深,近期随随便便打出了猎手7连胜。我甚至开始重新捡起了围棋,下的盘数可能快超过了小时候的。但我只和星阵 AI 下,大概能打平 3 段,所以我现在一直在挑战准 4 段。

跟我有类似游戏品味的人太少了,所以十多年来我很少有机会和别人深入交流游戏攻略。而我玩完游戏就忘。所以这两个月我一直在反思我是不是失去了探索新游戏的胆量,只敢玩老游戏了。但看了 Steam 的年终回顾我才发现,原来我今年的游戏之路是这么丰富多彩。我并不想跟随潮流,玩最新最热门的游戏。看到好玩的就玩,不管是几年前出的。顺其自然地玩,才是真正地享受游戏。


今年年中的时候我被直播弹幕引流,去看了「MyGO」。本以为只是消遣,没想到这部动漫彻底改变了我的生活。「MyGO」讲述了几个美少女组乐队的故事。少女们性格各异,因巧合凑到了一起,因矛盾而散开,最终又凭借着人与人之间的牵绊又走回到了一起。动漫对感情的描写非常细腻,且擅长用特色鲜明的场景、台词来展现角色间的冲突。可能是某些场景乍看之下非常滑稽,这部本来仅属于少部分情感细腻的观众的作品被涌入的各式各样的观众玩梗、做二创、引流,形成了别具一格的二创文化。也得益于这些引流,我一个平时不怎么看动漫的人能看到这样一部温馨的作品,我为今年能认识「MyGO」而感到惊喜与感动。

关于「MyGO」的观后感,不管是有关剧内的剧情、剧外的同人创作,还是结合我个人经历得出的评价,我有太多太多想说的话。但是,今天时间有限,我不能快速把它们全部表达出来。我只能想到什么说什么。

作为一部动漫,「MyGO」的表现能力很强,其对于感情的描写甚至能够达到视觉小说的程度(毕竟小说由大量文字组成,更容易表达深刻的思想)。故事中的每个人看似有着各种各样的毛病,但在二次元这个理想的世界里,她们每个人的本质都是纯洁善良的。所以,在矛盾过后,少女们能够把人性中最美好的那些情感展示出来。哪怕大家性格各异,无法简单地用语言达成共识,但是,只要有歌声,有了想要表达自己的冲动,有了想要和他人建立连结的念头,那么演唱过后,那些无法用言语描述的情感会连在一处,并自然地流入每个人的心中。剧中的高松灯并不是我最喜欢的角色,但是她在剧中的做法让我非常感动与向往:作为一名不太敢于他人交流,略有心理障碍的人,她能为了留住和他人的友谊,去忘我地、不计后果地在台上念诗,尽自己一切努力,勇敢地表达着自己。最终,她成功地把队友找回来了。「诗超绊」的名场面,我看一次哭一次。作为极端理性的人,我知道这些事在现实生活中是不可能的。没有耐心的观众,没有愿意追求真挚友情的朋友,自然也没有敢于忘我地表达自己的人。是作者的温柔,让理想的二次元世界里诞生了这样的故事。我知道这样的事是不可能的,不存在的,不应该去梦想的。但是,我羡慕这种能够忘我地追求目标的人。我开始不断反思自己前进的步伐还不够大。我一直说自己有理想,现在我却分不清这到底是真正有想做的事,还是仅仅虚伪地创造一个引诱自己前进的「胡萝卜」了。我不想忘记我的理想。我也想不计后果地去追求!

「MyGO」的二创作品也是盛况空前。歌曲、剪辑、小故事……,催泪向、搞笑向……。看着这么多人都和我有着类似的喜好,都分享着自己的喜好,我感到非常幸福,一度想起了十年前认识「东方」时的感动。如果只有作品本身,「MyGO」对我来说只不过是和优秀 Galgame 一样的存在。但是「MyGO」的二创给我带来了各种全新的体验。比如我开始了解角色的声优,了解所谓的 2.5 次元偶像企划。为了现场见声优,我甚至年底久违地去了次漫展。本来几乎不唱歌的我,也开始努力把「MyGO」的歌曲唱得好听。这些新的体验对我来说也仅仅是调剂,我不会像某些过分热情的粉丝一样花太多精力在这上面,但我很高兴能够认识这些全新的体验,去看到生活中我不曾见过的风景。

看过「MyGO」后我又自然而然地看了「Grils Band Cry」(GBC)。GBC 也是讲少女乐队的故事,但风格更偏现实,更沉重一些。剧中的人物,一直在强调一个观点:要创作自己的心声,而非迎合观众。这同样也是我一直以来的信念。这个世界不断在教人们做「正确」的事情,强迫人们放其自己本来想做的事情。不知道是幸运还是不幸,我从来就没有从迎合世界中获取过恩惠。等回过神来的时候,我的心中只剩下了对世界的复仇之情。我不想被任何世俗的负担左右,我只想做我想做的事情,我想证明我是对的。我想做的事情并不是真正意义上从我自己出发,只会有利于我的事情。就像 GBC 中的女主角被一首歌鼓舞了一样,我也是在种种鼓舞中走过来的人。我想做的,是再现这些对他人的鼓舞。所以,我所追求的,并不是只有我在追求,是很多人想追求,却因种种原因难以伸手把握的事物。既然如此,我会从我开始,斩断这些世俗的枷锁。

我今年看的这两部动漫助长了追梦的热情。我对它们的感谢无以言表,所以只能用实际行动来表达。就像现在写这篇文章的时候一样。我不想思考是不是太晚了要睡觉,不去害怕会不会写不完。只要心中还有一丝冲动,我就会继续诉说,直到心满意足了为止。


最后聊今年玩的视觉小说类游戏。由于今年「逆转裁判 4 5 6 」上架了 Steam,所以我把之前没能在模拟器上玩的《逆转裁判5》玩完了,还顺便玩完了《大逆转裁判》两部曲。感叹于巧舟的才华后,我又玩了他的《幽灵诡计》。这些作品质量都还不错,但由于我早就玩过前几部「逆转」作品,那股新鲜感已经没了。我还顺便玩了之前剩下的《弹丸论破V3》,只能说作者有创作故事的才华,却完全不会做游戏。

年底赶论文之余,我竟然玩完了老版「寒蝉」、老版《月姬》、《魔法使之夜》。「寒蝉」写了超长的日常,却不会让人感觉无趣(像 Muv Luv 本体那样)。整体故事恐怖悬疑氛围渲染到位,故事框架及伏笔回收还算合理。对我而言,故事中并没有太多感人的表达,但有不少引人思考的表达。我非常认可它的质量,能够理解为什么它作为画风简陋的同人作品能受到那么多人的喜爱。相比之下,《月姬》画风稍好,虽然故事偏短,但每条结局都做到了叙事完整。由于我是先玩过「FSN」的,玩《月姬》时能够在画风和叙事上体会到熟悉感,这种体验很有趣。正如很多其他评论一样,相比「FSN」,《月姬》有一种清冷感,那淡淡的氛围更人回味无穷。至此,时隔二十年,我总算欣赏完了当年的「同人三大奇迹」。好像这个时间有点晚。但可能时间的早晚并不是很重要,因为那份同(独)人(立)创(游)作(戏)的创作理念已经传达到了我这里。我会将它们传递下去。《魔法使之夜》就是成熟的商业作品了,一流的美术和演出,笔法成熟的作家,按理来说会给人舒适的体验。但在我看来,这部作品显然是未完成之作,很多东西没讲完故事就戛然而止了。我不认为未完成的作品是好作品。

我一般通过玩游戏来休闲。但仔细想来,还是看视觉小说时那种沉醉的心情更令我放松。我其实是喜欢文字的,但一直没有胆量去探索,只能局限于和游戏略有交集的 Galgame。我希望从明年起能够更深入地挖掘出自己的喜好,去欣赏更多优秀文学作品。


科研就没什么可以讲的了。科研是工作,并不让人讨厌,只能说是空气一般自然的存在。既然是空气,也没有太多可以讨论的必要了。倒是今年写论文时的感想我过段时间会正式发表出来。

关于科研,我最想说的是:哪怕科研是工作,是一份非常有趣的工作,我也不想让它不必要地占用我做其他事情的时间。无论会承担怎样的后果,我都要花时间去做其他想做的事情。


按照习惯,我还是收个尾吧。我不想花太多的理性去思考,为什么要写这篇文章,这篇文章是写给谁看,到底写完了有什么意义。只是昨天睡着之前想起这几天看了一些别人的年终总结,突发奇想要把自己今年的事情也整理一下而已。于其花几个小时去思考做这件事的意义,不如像现在这样就动笔写完了。不需要有什么意义,仅凭一时的冲动就够了。是的,不经深思熟虑的冲动会导致失败。但是,我心中总有一些坎是不靠冲动就跨不过去的。如果说深思熟虑后,仍然愿意面对困难的事物可以称之为勇敢的话,那么先不顾一切地做出行动,再为失败负责,同样也是勇敢的。可能明早开始我也会羞得不敢看这篇文章,那也没什么关系。我不会许诺说明年年底还会写这样的文章,我不想给自己添加任何负担。想到做就做,想做就做,不想做就不做,我要尽力地去自由地活下去。

由于我严谨的作风,我又把文章通读了一遍,忍不住站在读者视角重新感受了一下这篇文章。说实话,我文字功底太差了,没有足够的写作经验,不能把复杂深刻的情感充分表达。哪怕我写的时候自己的情感已经酝酿得很到位了,读起来还是太理性了。但没什么关系,就凭我的洞察力和自我进化能力,提升文字表达能力是分分钟的事情。是啊,文字的表达能力太有限了,不管怎么描写,都只能勾勒出复杂思绪的一角。在那表层的文字之下,究竟藏住了什么呢?不付出大量的心思,是猜不到的。也因此,无法期盼读者能够有耐心猜下去。那么,就不断地强化笔锋的锐度,不断地写下去吧。

今年年初,多尺度自回归模型 VAR 为图像生成开辟了新的发展方向:通过将图像生成建模成下一尺度预测,且每轮一次性生成同一尺度的所有像素,VAR 以极快的速度实现了高质量图像生成。随后,有许多工作都尝试对其改进。为弥补 VAR 中 VQ (Vector Quantization,向量量化) 操作引入的信息损失,HART (Hybrid Autoregressive Transformer,混合自回归 Transformer) 把 VQ 损失的信息用一张残差图表示,并用一个轻量的扩散模型来生成该残差图。做完改进后,作者用 HART 实现了 $1024 \times 1024$ 高分辨率文生图任务。在这篇博文中,我们将学习 HART 的核心方法并分析它在文生图任务上的实验结果。

论文链接:https://arxiv.org/abs/2410.10812

以往工作

本文涉及的所有自回归图像生成方法都起源于 VQVAE, VQGAN。在阅读本文前,建议读者先熟悉这两个经典工作。

HART 直接基于 VAR (Visual Autoregressive Modeling: Scalable Image Generation via Next-Scale Prediction) 开发,且其部分思想和 MAR (Masked Autoregressive models,出自论文 Autoregressive Image Generation without Vector Quantization) 类似。欢迎大家阅读我之前的解读。

VAR 解读

MAR 解读

在 VQGAN 两阶段生成方法的基础上,VAR 让自编码器输出一系列不同尺度的图像词元 (token),而不仅仅是最大尺度的词元。生成时,VAR 自回归地生成不同尺度的词元图,同一尺度的词元图会在一轮 Transformer 推理中一次性生成。

VQ 操作会丢失编码器输出中的信息,这导致所有使用 VQ 自编码器的图像生成模型生成质量略低。VAR, VQGAN 等方法之所以不得不使用 VQ,是因为这些方法都用类别分布(categorical distribution)来建模词元的分布。为了彻底去除 VQ 操作,MAR 使用扩散模型来代替类别分布,使得我们能够用精度更高的 VAE 来压缩图像。

弥补 VQ 的信息损失

为了缓解 VAR 中 VQ 造成的质量下降,HART 使用了一项思路直接的设计:既然 VQ 无论如何都会造成信息损失,不妨把损失的信息看成一张残差图像。用普通的 VAR 生成完图片后,再用扩散模型生成该残差图像。把残差图像加到原输出图像后,新输出图像会质量更高。

让我们通过论文里的图片来直观感受这一点。第一行是 VAR 自编码器和 HART 的混合自编码器的重建结果。可以看出,由于 VQ 操作,模型难以重建输入图像。第二行原 VAR 的输出和残差图像输出。我们发现,加上残差图像后,图像的细节更加丰富,不会像之前一样模糊。

在下两个小节里,我们来学习 HART 是怎么分别改进 VAR 的词元生成模型和自编码器的。

用扩散模型生成残差图像

为了理解整套方法,我们需要理解 HART 的「残差图像」是从哪来的。因此,我们先看词元生成模型上的修改,再看自编码器的对应修改。

我们先仔细回顾一下 VAR 中 VQ 误差是怎么引入的。VAR 借用了传统拉普拉斯金字塔的思想来建模不同尺度的词元图。

也就是说,VAR 并没有将完整图像拆解成内容相同、不同分辨率的词元图,而是拆解成了最低分辨率的图以及各个尺度上的信息损失。这里的信息损失不仅包括了下采样导致的,还包括了 VQ 导致的。

即使在多尺度拆解时考虑了 VQ 的信息损失,最终的重建特征(即解码器输入,词元查表输出的累加)依然不能和编码器输出特征完全一致。HART 想用扩散模型生成的「残差图像」,就是上图中重建特征和编码器输出特征的差。

和离散的词元图不同,残差图像是连续的。为了生成该连续图像,HART 参考 MAR,使用了一个图像约束的扩散模型。该任务可以解释为:已知离散词元图的输出,该如何用扩散模型生成细节,以提升输出图像质量。

HART 的生成模型示意图如下所示。前面的生成过程和 VAR 一模一样。在最后一步,Transformer 的中间隐状态会输入给用 MLP 表示的扩散模型,扩散模型会为每个词元独立地预测残差量。也就是说,这不是一个图像扩散模型,而是只生成一个词元值的像素扩散模型。词元之间的采样互相独立。得益于这种独立性假设,HART 可以用一个非常轻量的扩散模型来生成残差图,几乎没有增加整体的生成时间。

HART 还将 VAR 的类别约束换成了文本约束。我们稍后在实验部分讨论。

AE + VQVAE 混合自编码器

知道了 HART 要生成的残差图像从何而来,我们可以回头学习自编码器上的对应修改。现在,自编码器的解码器有两种输入:一种是 VAR 离散词元累加而成的近似重建特征,一种是加上了 HART 的残差图的精确重建特征,这个重建特征就等于编码器输出特征。为了同时处理这两类输入,在训练 HART 的混合自编码器时,解码器的输入一半的时候是编码器输出,另一半的时候是离散词元的重建特征。当然,在生成时,由于加上了残差图像,可以认为解码器的输入就等于编码器的输出。

下图中采用的术语 token 与 VAR 不同。VAR 把编码器输出和解码器输入都叫做特征图 (feature map),把过了 VQ 操作的索引图叫做词元图 (token map)。而 HART 将 VAR 里的特征图称为 token,continuous token 表示编码器输出特征,discrete token 表示词元的重建特征。这篇博文采用了 VAR 的称呼方法。同理,HART 里的 residual token 在本文被称为「残差图像」。

这样看来,HART 的混合编码器既像没有 KL Loss 的 VAE,即普通自编码器 (AE),也像 VQVAE。

高分辨率文生图实现细节

我们来简单看一下 HART 是如何把 ImageNet $256 \times 256$ 按类别生成的 VAR 拓展成 $1024 \times 1024$ 的文生图模型的。

  • 文本约束:HART 没有通过交叉注意力输入文本信息,而是和 VAR 对类别嵌入的做法一样,将文本嵌入作为第一尺度的输入及 AdaLN 层的输入。
  • 位置编码:不管是对于尺度编号还是图像位置编号,VAR 用的是可学习的绝对位置编码。HART 对尺度采取了正弦编码,对图像词元采取了 2D RoPE(旋转位置编码)。
  • 更大尺度:原 VAR 词元图的最大边长是 16,HART 往后面添加了 21,27,36,48,64 这几个边长。
  • 轻量级扩散模型:由于扩散模型仅需建模单个词元的分布,它仅有 37M 参数,只需 8 步就能完成高质量采样。

定量实验结果

先看一下最热门的「刷点」指标——ImageNet $256 \times 256$ 按类别生成。作者没放最好的 MAR 模型,我补上去了。

在这个任务上,HART 和 VAR 的主要区别在于是否使用扩散模型输出残差图像。从结果可以看出,残差扩散模型几乎没有提升推理时间,却对 FID 指标有不小的提升(考虑到数值越低,提升难度越大)。并且,通过比较不同模型的速度,我们发现类 VAR 模型最大的优势在于推理速度快。

再看一下这篇论文重点关注的文生图指标。除了常用的主要衡量文图匹配度的 GenEval 外,论文还展示了两个今年刚出的指标: MJHQ-30K 数据集上的指标和 DPG-Bench。

这些指标不见得很有说服力。在由用户投票的排名中 (https://imgsys.org/rankings),Playground v2.5 最好,SD3 和 PixelArt-Σ 差不多。但是,MJHQ FID 和 DPG-banech 指标都不能反映出这些模型的排名。特别地,FID 用到的 Inception V3 网络是在 $299 \times 299$ 的 ImageNet 上训练的,所以 FID 既不能很好地反映高分辨率图像的相似度,也不能反映更复杂的图像的相似度。

综上,HART 在高分辨率文生图任务上的表现暂时不能通过实验结果反映。根据部分社区用户的反馈(https://www.reddit.com/r/StableDiffusion/comments/1glig4u/mits_hart_fast_texttoimage_model_you_need_to_see/ ),HART 在高频细节的生成上存在缺陷。通过回顾 HART 的方法,我们可以猜测这是残差扩散模型的设计不够好导致的。

总结

为了缓解 VQ 自编码器中 VQ 操作带来的信息损失,HART 把信息损失当成一张残差图,并额外用一个轻量级像素扩散模型来独立地生成残差图的每个像素。HART 把这一改进直接应用到了 VAR 上,并提升了 VAR 的 ImageNet FID 指标。HART 在高分辨率文生图任务上依然无法媲美扩散模型,并且由于扩散模型存在诸多加速手段,它在生成速度上也没有优势。

VQ 操作将复杂的图像转换成了易于学习的图像词元,但牺牲了自编码器的重建质量。为了改进这一点,有许多工作都试图改进原 VQVAE 的最近邻 VQ 操作。但无论如何,VQ 导致的误差是不可避免的。HART 从另一个角度缓解 VQ 重建误差:用另一个模型来生成残差图像。这种设计思想很有前途,有希望彻底去除 VQ 的误差。然而,天下没有免费的午餐,提升了生成效果,就不得不增加训练和生成时间。HART 用轻量级像素扩散模型生成残差图的做法虽然不会拖慢模型速度,但效果还不够好。或许可以将其换成一个感受野稍大一点的扩散模型,在不显著增加生成时间的前提下提升残差图生成效果。

今年四月,北大和字节跳动在 Arxiv 上发表了论文 Visual Autoregressive Modeling: Scalable Image Generation via Next-Scale Prediction,介绍了一种叫做 Visual Autoregressive Modeling (视觉自回归建模,VAR)的全新图像生成范式。这种自回归生成方法将高清图像用多尺度词元图像表示,并用下一尺度预测代替了此前常用的下一词元预测。在 ImageNet $256 \times 256$ 图像生成任务上,VAR 的表现超越了 DiT。我们组的同学第一时间看了这篇论文,大家都觉得这篇论文有不小的创新,但其方法能否完全代替扩散模型还有待验证。通常来说,这篇论文的关注度会逐渐降下去,但近期发生的两件大事将 VAR 论文的热度推向了空前的高度:论文一作的严重违纪行为招致字节跳动对其索赔 800 万元、论文被评选为 Neurips 2024 会议的最佳论文。借此机会,我决定认真研究一下这篇论文并把我的学习结果分享给大家。

在这篇博文中,我会先回顾与 VAR 密切相关的早期工作 VQVAE 和 VQGAN,再介绍论文的方法细节与实验结果,最后分享我对该工作的测试结果与原理探究。在读 VAR 论文时,我发现有个地方的设计存在缺陷。相关实验结果表明, VAR 论文并没有完整地分析出这套方法有效的原因。欢迎大家仔细阅读这一部分并提出自己的思考与见解。

论文链接:https://arxiv.org/abs/2404.02905

VQGAN 原理回顾

VAR 算是 VQGAN 工作的改进版,而 VQGAN 又是 VQVAE 工作的改进版。要了解 VAR 的背景知识,最直接的方法就是回顾 VQVAE 与 VQGAN 这两个经典工作。我们先从自回归这种生成范式开始聊起,再将目光移向图像自回归生成,最后复习 VQVAE, VQGAN, Transformer 的实现细节。

推荐大家阅读我之前写的相关博文:

轻松理解 VQ-VAE:首个提出 codebook 机制的生成模型

VQGAN 论文与源码解读:前Diffusion时代的高清图像生成模型

Attention Is All You Need (Transformer) 论文精读

图像自回归生成

自回归(Autoregressive)是一种直观易懂的序列生成范式:给定序列前 $n$ 个元素,模型输出第 $n+1$ 个元素;把新元素添加进输入序列,再次输出第 $n+2$ 个元素……。以下是文本自回归生成的一个示例:

1
2
3
4
(空)  -> 今
今 -> 天
今天 -> 早
今天早 -> 上

具体来说,模型的输出并不是下一个元素应该是什么,而是下一个元素可能是什么。也就是说,模型的输出是下一个元素的概率分布。通过不断对下一个元素采样,我们就能随机生成出丰富多样的句子。

自回归生成仅适用于有顺序的序列数据。为了用自回归生成图像,我们需要做两件事:1)把图像拆分成一个个元素;2)给各个元素标上先后顺序。为此,最简单的做法是将图像拆成像素,并从左到右,从上到下地给图像生成像素。比如下图是经典自回归图像生成模型 PixelCNN 的示意图。假设图像有 $3 \times 3$ 个像素,并按顺序从左上到右下标号。在生成第 $5$ 个像素时,模型只能利用已经生成好的前 $4$ 个像素的信息。模型的输出是一个概率分布,表示灰度值大小分别取 0, 1, ..., 255 的概率。

顺带一提,建模概率分布的方法有很多种,这里我们使用的分布被称为类别分布(categorical distribution)。这种方法的好处是形式简洁,可以用简单的算法采样,缺点是元素的取值必须是离散的。比如虽然图像的灰度值理论上可以取 0~1 中间的任何实数(假设灰度值被归一化了),但我们用上图所示的 PixelCNN 时,只能表示 0, 1/255, 2/255, ..., 1 这 256 种灰度值,而不能表示更加精确的值。

VQVAE

PixelCNN 虽然能做图像生成,但它的效率太慢了:由于像素是逐个生成的,要生成几个像素,就要运行几次神经网络。能不能加速生成过程呢?如果要生成的图像更小一点就好了。

为了加速 PixelCNN,借助图像压缩网络,VQVAE 工作提出了一种两阶段的图像生成方法:先生成压缩图像,再用图像压缩网络将其复原成真实图像。由于压缩图像的像素数较少,而复原压缩图像的速度又很快,整套生成方法的速度快了很多。

以下是一个 VQVAE 的生成示例。根据 PixelCNN 输出的类别分布,我们可以采样出一些由离散值构成的压缩图像。这些离散值就和 NLP 里的文字一样,每一种值都有一种特殊的含义。我们可以认为离散值表示原始图像中一大块像素的颜色。借助图像压缩网络的解码器,我们可以把压缩图像复原成清晰的原始图像。

VQVAE 的训练顺序和生成顺序相反。我们先训练一个图像压缩网络。这种由编码器和解码器组成的图像压缩网络被称为自编码器,压缩出来的图像被称为隐图像(latent image)。训练好了自编码器后,我们再把训练集的所有图像都转成隐图像,让 PixelCNN 学习生成隐图像。比较有趣的是,训练 PixelCNN 时,只会用到编码器;而生成时,只会用到解码器。

在上述讨论中,我们略过了一个实现细节:该怎么让网络以离散值为输入或输出呢?输入离散值倒还好办,在 NLP 中,我们用嵌入层把离散的词语变成连续向量,这里的做法同理。可怎么让网络输出离散值呢?这里就要用到向量离散化(vector quantization, VQ)操作了。

离散化操作我们都很熟悉,将小数四舍五入至整数就是一种最常见的离散化。四舍五入,本质上是让一个小数变成最近的整数。同理,对于向量而言,假设我们已经准备好了一些向量(对应前面的「整数」),那么向量离散化就表示把输入的任意向量变成最近的已知向量。这里的「最近」指的是欧几里得距离。

具体示例如下所示。编码器可以输出一个由任意向量构成的二维特征。通过查找嵌入层里的最近邻,这些任意的向量会被转换成整数,表示最近邻的索引。索引可以被认为是 NLP 里的词元 (token),这样编码器输出特征就被转换成了词元构成的隐图像。而在将隐图像输入进解码器时,我们把嵌入层当成一张表格,利用隐图像里的索引,以查表的形式将隐图像转换成由嵌入构成的特征。准确来说,这个把图像压缩成离散隐图像的自编码器才被叫做 “VQVAE”,但有时我们也会用 VQVAE 代表整套两阶段生成方法。

上图中的「编码器输出特征」、「词元」、「嵌入」在不同论文里有不同的叫法,且一般作者都只会用数学符号来称呼它们。这里我们用了 VAR 论文的叫法。

嵌入层的具体学习过程我们不在此展开,对这块知识不熟悉的读者可以去仔细学习 VQVAE 论文。

VQGAN

VQVAE 的效果并不理想,这是因为它的压缩网络和生成网络都不够强大。为此,VQGAN 工作同时改进了 VQVAE 的两个网络。

  • VQGAN 工作将离散自编码器 VQVAE 换成了 VQGAN。在 VQVAE 的基础上,VQGAN 在训练时添加了感知误差和 GAN 误差,极大提升了自编码器的重建效果。
  • VQGAN 工作还把生成模型从 PixelCNN 换成了 Transformer。

Transformer

Transformer 是目前最主流的主干网络。相比其他网络架构,Transformer 的最大特点是序列里的元素仅通过注意力操作进行信息交互。因此,为了兼容文本自回归生成任务,最早的 Transformer 使用了两个特殊设计:

  • 由于注意力操作不能反映输入元素的顺序,词元嵌入在输入进网络之前,会和蕴含了位置信息的位置编码相加。
  • 自回归生成要求之前的词元不能看到之后的词元的信息。为了控制词元间的信息传播,Transformer 给自注意力操作加上了掩码。

VQGAN 用了完全相同的设计,把图像词元当成文本词元用 Transformer 来生成。

从词元预测到尺度预测

上述的传统图像自回归生成都是采用下一个词元预测策略:

  • 将图像用自编码器拆成离散词元。
  • 从左到右、从上到下按顺序逐个生成词元。

尽管通过自编码器的压缩,要生成的词元数已经大大减少,但一个个去生成词元还是太慢了。为了改进这一点,VAR 提出了一种更快且更符合直觉的自回归生成策略:

  • 将图像用自编码器拆成多尺度的离散词元。比如,原来一张隐图像的大小是 $16 \times 16$,现在我们用一系列尺度为 $1 \times 1, 2 \times 2, …, 16 \times 16$ 的由词元构成的图像来表示一张隐图像。
  • 从最小的词元图像开始,从小到大按尺度生成词元图像。

在这种策略下,我们要同时修改自编码器和生成模型。我们来看一下 VAR 是怎么做的。

多尺度残差离散自编码器

先来看自编码的修改。现在词元图像不是一张图像,而是多张不同尺度的图像。由于词元图像的定义发生了改变,编码器特征和嵌入的定义也要发生改变,如下图所示。

向量离散化部分我们可以沿用 VQVAE 的做法。现在新的问题来了:编码器的输出和解码器的输入都只是一张图像。该怎么把多尺度的图像组合成一张图像呢?

最简单的做法是完全不修改编码器和解码器,还是让它们输入输出最大尺度的图片。只有在中间的向量离散化/查表部分,我们才把这些图片下采样。

VAR 用了一种更加高级的做法:用残差金字塔来表示这些隐空间特征。我们先来回顾一下拉普拉斯金字塔这一经典图像处理算法。我们知道,图像每次下采样的时候,都会损失一些信息。既然如此,我们可以将一张高分辨率的图像表示为一张低分辨率的图像及其在各个分辨率下采样后的信息损失。如下图所示,最右侧的一列表示拉普拉斯金字塔的输出。

在计算拉普拉斯金字塔时,我们不断下采样图像,并计算当前尺度的图像和下一尺度的复原图像(通过上采样复原)的残差。这样,通过不断上采样最低尺度的图像并加上每一层的残差,我们最终就能精准复原出高分辨率的原图像。

现在,我们想把类似的金字塔算法应用到编码器特征上。该怎么把最大尺度的编码器特征拆解成不同尺度的图像的累加呢?

在计算拉普拉斯金字塔时,本质上我们用到了两类操作:退化和复原。对于图像而言,退化就是下采样,复原就是上采样。那么,对于编码器输出的隐空间特征,我们也需要定义类似的退化和复原操作。比较巧妙的是,VAR 并没有简单地把退化和复原定义为下采样和上采样,而是参考 Autoregressive Image Generation using Residual Quantization 这篇论文,将向量离散化引入的误差也算入金字塔算法的退化内。也就是说,我们现在的目标不是让编码器特征金字塔的累加和编码器特征相等,而是想办法让嵌入金字塔的累加和编码器特征尽可能相似,如下图所示。

基于这一目标,我们可以把退化定义为下采样加上离散化、查表,复原定义成上采样加一个可学习的卷积。我们来看看在这种新定义下,原来 VQVAE 的向量离散化操作和查表操作应该怎么做。

先看新的多尺度向量离散化操作。这个操作的输入是编码器特征,输出是一系列多尺度词元图像。算法从最低尺度开始执行,每个循环输出当前尺度的词元图像,并将残差特征作为下一个循环的输入特征。

对于多尺度查表操作,输入是多尺度词元图像,输出是一张最大尺度的隐空间特征,它将成为自编码器的解码器的输入。在这步操作中,我们只需要分别对各个尺度的词元图像做查表和复原(上采样+卷积),再把各尺度的输出加起来,就能得到一个和编码器特征差不多的特征。注意,为了方便理解,这几张示意图都省略了部分实现细节,且一些数值不是十分严谨。比如在查表时,我们可以让不同尺度的词元共享一个嵌入层,也可以分别指定嵌入层。

总结一下这一小节。为了实现尺度自回归生成,我们需要把图像编码成多尺度的词元图像。VAR 采用了一种多尺度残差离散化操作:将编码器特征拆解成最小尺度的特征以及不同尺度的残差特征,并对不同尺度的特征分别做向量离散化。这种做法不仅能高效地将特征拆解成多个尺度,还有一个额外的好处:原来 VQVAE 仅对最大尺度的特征做向量离散化,离散化后的误差会很大;而 VAR 把向量离散化引入的误差分散到多尺度离散化中,巧妙地降低了离散化的误差,提升了 VQVAE 的重建精度。

下一尺度自回归生成

把图像压缩成多尺度词元图像后,剩下的事就很简单了。我们只需要把所有词元拆开,拼成一维词元序列,之后用 Transformer 在这样的序列上训练即可。由于现在模型的任务是下一尺度预测,模型会一次性输出同尺度各词元的概率分布,而不是仅仅输出下一个词元的。这样,尽管序列总长度变长了,模型的整体生成速度还是比以前快。同时,随着预测目标的变更,自注意力的掩码也变了。现在同尺度的词元之间可以互相交换信息,只是前一尺度的词元看不到后面的词元。以下是一个 $3 \times 3$ 词元图像在下一词元和下一尺度预测任务下的注意力掩码示意图及生成过程示意图。

除此之外,VAR 的 Transformer 还做了一些其他修改:1)除了给每个词元加上一维位置编码外,同一尺度的词元还会加上同一个表示尺度序号的位置编码。所有位置编码都是可学习的,而不是预定义的正弦位置编码。2)Transformer 与解码器的共用嵌入层。另外,在生成新一层时,为了复用已经生成好的图像的信息,新一层的初始嵌入是通过对上一层的生成结果 bicubic 上采样得到的。

该 Transformer 的其他设计都与 VQGAN 相同。比如,Transformer 采用了 decoder-only 的结构。为了添加 ImageNet 类别约束,第一层的输入是一个表示类别的特殊词元。训练时用的误差函数是交叉熵函数。

ImageNet 图像生成定量实验

VAR 的方法部分我们看得差不多了,现在来简单看一下实验部分。论文宣称 VAR 在图像生成实验和参数扩增实验上都取得了不错的成果。特别地,VAR 的拟合能力胜过了 DiT,生成速度是 DiT 的 45 倍以上。我们就主要看一下 VAR 在 ImageNet $256 \times 256$ 图像生成上的实验结果。以下是论文中的表格。我同时还附上了何恺明团队的 MAR 工作(Autoregressive Image Generation without Vector Quantization)的实验结果。

先比一下 DiT 和 VAR。先看速度,不管是多大的模型,DiT 的速度都远远慢于 VAR。再看以 FID 为代表的图像拟合指标。VAR 在参数量为 600M 左右时并没有 DiT 效果好。但继续增加参数量后,DiT 的 FID 没有变好的趋势,而 VAR 的 FID 一直在降。最终 VAR 的 FID 甚至超过了 ImageNet 的验证集,可以认为 FID 再低的也意义不大了。

再比一下 MAR 和 VAR。MAR 的刷指标能力更加恐怖,943M 的模型就能有 1.55 的 FID。但根据 MAR 论文,其速度是 DiT-XL 的 5 倍左右,也就是说 VAR 还是比 MAR 快,是 MAR 速度的 9 倍左右。

ImageNet 图像生成已经被各个模型刷到头了。FID 结果能说明 VAR 的拟合能力很强,最起码不逊于 DiT。但在更有挑战性的文生图任务上,VAR 的效果还有待验证。另外,虽然刷指标的时候 DiT 用了 250 步采样,但实际用起来的时候一般就是采样 20 步。如果算上蒸馏的话,采样步数能缩小到 4 步。加上这些加速技巧的话,VAR 不见得会比 DiT 快。

VAR 各尺度生成结果

看完了论文的主要内容,我来分享一下我对 VAR 的一些理论分析与实验结果。

先看一下随机采样结果。我用的是最大的 d=30 的 VAR 模型。在官方采样脚本的默认配置下,两个随机种子 (0, 15) 的输出如下所示。用到的图像类别为火山、灯塔、老鹰、喷泉,每个类别的图各生成了两张。图像的生成速度很快,一秒就生成了全部 8 张图片。

我们还可以观察每个尺度的生成结束后解码出的临时图片。和我们预估得一样,图像是按从粗到精的顺序逐渐生成的。

为了进一步探究每一个尺度负责生成哪些图像成分,我们可以做如下的实验:从某个尺度开始,随机更换新的随机数生成器。这样,每张动图里不变的部分就是前几个尺度生成好的内容;不断在变的部分就是后几个尺度负责的内容。可以看出,从第三个尺度开始,图像的内容就基本固定下来了,也就是说结构信息是在前两个尺度里生成的。越往后,图像的细节越固定。

这个结果还挺令人惊讶的:难道 $2 \times 2$ 这么小的特征图就已经决定了图像的整体内容?让我们来仔细探究这一点。

有缺陷的单尺度生成

不知道大家在学习 VAR 的采样算法时候有没有感到不对劲:在生成同一个尺度的词元图像时,每个词元是独立地在一个概率分布里采样。

而根据作者在论文里的说法,VAR 的尺度自回归是一种新的自回归概率模型:

其中,$r_k$ 表示从小到大第 $k$ 个尺度的词元图像,共 $K$ 个尺度。同一个尺度的词元图像 $r_k$ 的每个词元的分布是并行生成的。这也就是说,VAR 的这种训练(用交叉熵误差)和采样方式是认为每张词元图像的概率等于所有词元的概率的乘积,词元的分布之间是独立的:

其中,$r_k^i$ 表示第 $k$ 个尺度的第 $i$ 个词元,$I_k$ 为第 $k$ 个尺度的词元总数。我觉得上面这个等式是不成立的,哪怕有之前尺度的信息作为约束,同一尺度的每个词元的概率分布之间不会是互不相关的。且随着 $I_k$ 的增大,上面这个式子的误差会越来越大。

词元之间的采样互相独立,理论上会导致图像出现不连贯的地方。比如,假设一个图像词元表示 $16 \times 16$ 个像素,那么每隔 16 个像素图像就会出现「断层」。但是,为什么 VAR 的输出结果那么正常呢?仔细分析 VAR 的生成算法,我们可以发现有两项设计提升了图像的连续性:

  • VAR 的自编码器使用了向量离散化操作。这个操作会让解码器的输入总是合理的,解码器也总是会输出连贯的图像。
  • 在生成一个新尺度的图像时,模型输入会被初始化成之前尺度的图像的 bicubic 上采样。bicubic 采样保证了词元嵌入之间的连续性。

此外,为了进一步缓解独立采样带来的负面影响,VAR 在生成完第二或第三个尺度后就已经把图像的整体内容确定下来了,后面的生成只是略微影响图像细节而已(因为随着词元数量变多,独立采样的误差越大)。这个结论已经在前文的可视化结果中验证了。为了证明只有前几个尺度是重要的,我做了一个大胆的实验:用 Transformer 生成完前两个尺度的词元后,后续所有词元都随机生成。如下图所示,我展示了固定前两个尺度的输出后,多个随机种子下的生成结果。结果显示,如果前两个尺度的词元生成得比较好,后面词元无论采样得多乱,都不怎么会影响最终的图像质量。

根据这些实验结果,我认为 VAR 真正有效的原因并不能用「下一尺度预测这种全新生成范式更好」这样粗浅的话来概括。VAR 中最核心的组件可能是其多尺度残差离散自编码器。这个编码器至少做到了以下几件事:

  • 使用向量离散化确保解码器的输入总是合理的。
  • 使用多尺度残差设计,且下一尺度的残差图像不仅记录了因下采样而导致的信息损失,还记录了因向量离散化带来的精度损失。相比简单的、人类能够理解的拉普拉斯金字塔,这种可学习的多尺度拆分方法或许更加合理。
  • 使用 bicubic 对低尺度词元图上采样。这步固定的操作让生成的图像总是连续的。

当然,这几件事是互相耦合的。不进行更深入的实验的话,我们难以解耦出 VAR 中最有效的设计。

多尺度生成其实并不是什么新奇的思想。之前 StyleGAN 和 Cascaded Diffusion 都用了类似的策略。然而,VAR 做了一个大胆的设计:同一尺度的不同词元在采样时是相互独立的。令人惊讶的是,这种在数学上不太合理的设计没怎么降低图像的质量。并且,得益于这一设计,VAR 能够并行地对同一尺度的词元采样,极大地提升了生成速度。

总结与评论

此前,以经典工作 VQGAN 为代表的图像自回归生成模型无论在速度上还是图像质量上都不尽如人意。究其原因,下一个图像词元预测的建模方式既不够合理,也拖慢了生成速度。为此,VAR 提出一种新式自回归策略:将词元图像拆分成多个尺度,通过下一尺度预测实现图像生成。为了兼容这一设计,VAR 对 VQGAN 的自编码器和 Transformer 都进行了修改:自编码器能够将图像编码成多尺度的残差词元图像,而 Transformer 同时输出同一尺度每个词元的独立分布。实验表明,VAR 在 ImageNet 图像生成指标上超越了以 DiT 为代表的扩散模型,且生成速度至少比 DiT 快 45 倍。另外,还有实验表明 VAR 符合扩增定律:增加参数量即可提升模型性能。

我个人认为,和其他前沿生成模型一样,VAR 在 ImageNet 上的表现已经满分了。它能否完成更困难的图像生成认为还有待验证。最近字节跳动发布了 VAR 的文生图版本:Infinity,但这个模型还没有开源。我们可以持续关注 VAR 的各个后续工作。VAR 的生成速度也没有比 DiT 快上那么多,通过减小采样步数,再加上模型蒸馏,DiT 不会比 VAR 慢。当然,VAR 或许也存在进一步加速的可能,只是相关研究暂时没有扩散模型那么多。

VAR 的数学模型是存在缺陷的:词元图的分布不应该等于词元间的独立分布的乘积。最起码论文里没有任何相关分析(用了类似做法的 MAR 论文也没有分析)。通过一些简单的生成实验,我们发现由于 VAR 在其他设计上提升了输出图像的连续性,哪怕同一尺度的词元间是独立采样,甚至是随机均匀采样,模型的输出质量也不会太差。我们需要通过更深入的实验来挖掘 VAR 的生效原理。

我觉得如果一个科研工作能够解释清楚 VAR 中哪些模块起到了最主要的作用,并取其精华,去其糟粕,提出一个更好的生成模型,那这会是一个很不错的工作。我觉得能够探索的方向有:

  • VAR 的前几个尺度的词元图是最重要的。能不能用更好的方式,比如用扩散模型,来生成前几个尺度的图像,而更大尺度的词元图用一个比 Transformer 更高效的模型来生成。这样模型的质量和效率能进一步提升。
  • VAR 还是用了 VQ 自编码器。无论怎么样,VQ 操作都会降低模型的重建质量。但另一方面,VQ 也能起到规范解码器输入的作用。究竟我们能不能把 VQ 自编码器换成精度更高的 VAE 呢?换了之后怎么设计多尺度编码呢?

以前我一直将 “quantization” 翻译成「离散化」,但经仔细学习后发现它与真正的离散化 “discretization” 存在区别。因此,以后我会采用「量化」这个更常见的翻译,尽管我认为这个翻译容易和「量化投资」里的「量化」混淆。

按分辨率从低到高的顺序生成图像是一种常见思路。此外,Diffusion Forcing 等论文带来了一种新的扩散模型视频生成思路:将视频生成转换为约束于前几帧图像的单张图像自回归生成。Pyramid Flow 工作把两种思路结合起来,提出了一种新的视频生成范式:在自回归生成新帧时,用低分辨率的前几帧图像作为约束。这使得模型能够更高效率地利用历史帧的信息。

论文名:Pyramidal Flow Matching for Efficient Video Generative Modeling

Arxiv: https://arxiv.org/abs/2410.05954

GitHub:https://github.com/jy0205/Pyramid-Flow

以往工作

将图像生成拆解成从低分辨率到高分辨率是一种很常见的思想。基于扩散模型,有多种方式来应用这种思想。一种比较直接的方式是显式将图像生成分解成生成最低分辨率的图像和多轮超分辨率,代表工作是 Cascaded Diffusion Models for High Fidelity Image Generation;另一种更加巧妙的方式是将图像上采样和扩散模型的去噪同时进行,代表工作是 f-DM: A Multi-stage Diffusion Model via Progressive Signal Transformation。本文的多尺度设计和 f-DM 非常相似,我会在文末详细分析二者的区别。

将视频生成转换为约束于之前所有帧的图像生成是一种再简单不过的想法。然而,在使用扩散模型时,选择最佳的约束方式并不是一个简单的问题。比较常见的添加图像约束的方式是与原输入在通道维度上拼接。对于视频自回归生成而言,这个做法的问题是网络对于约束图像和待生成图像的处理不统一。近期,Diffusion Forcing 工作告诉我们,我们可以给视频扩散模型的不同帧添加不同的噪声,并修改注意力机制,从而将其转换成一个约束于之前帧的图像生成模型,并且模型对每一帧的处理在注意力层以外是统一的。

流匹配 (Flow Matching) 可以简单看成一种改进了噪声调度机制的 DDPM。假设时刻 $0$ 表示纯高斯噪声,时刻 $1$ 表示清晰图像,按照最常用的流匹配方法,中间 $t$ 时刻的带噪图像为二者的线性插值。

此外,去噪网络的预测目标也从残余噪声 (epsilon) 改为了速度(velocity)。

相关博文:

Diffusion Forcing

多尺度的加噪过程

扩散模型中,我们要把清晰图像中的信息逐渐破坏掉。这样的图像退化方式不只添加高斯噪声一种,我们可以在添加噪声的同时下采样图像,定义出一种新的扩散模型前向过程(退化过程)。

如下图的一维扩散模型所示,Pyramid Flow 在加噪的同时做两次下采样。总时间被拆成了三段,第 $k$ 个阶段的时间范围是 $[s_k, e_k]$。

做下采样后,图像中的信息会突然减少。为了让同一个阶段的图像逐渐失去这些信息,而不是在一次下采样中突然失去,我们采用这样的插值策略:设 $\mathbf{x}_t$ 为最大分辨率下 $t$ 时刻的带噪图像(其计算方法由上文的流匹配噪声公式决定),那么在第 $k$ 阶段的分辨率下,$t$ 时刻的带噪图像 $\hat{\mathbf{x}}_t$ 为:

其中,$Down(\mathbf{x}, a)$ 表示把 $\mathbf{x}$ 下采样 $a$ 倍,$Up(\mathbf{x})$ 表示把 $\mathbf{x}$ 上采样 $2$ 倍,$t’=(t-s_k)/(e_k-s_k)$ 表示 $t$ 时刻在第 $k$ 阶段里的插值比例。初看这些公式可能会有点头大,我们可以先看下面的示意图再回头看公式。

公式里做插值的两项分别表示当前阶段最清楚、最模糊的图像,插值比例 $t’$ 为 $t$ 在当前阶段的时间窗口里的比例。这种插值不仅让噪声强度渐变,还让因下采样而产生的信息损失渐变,就好像是我们在连续地对图像下采样一样。

修改了退化机制后,除了定义流匹配的输入变量 $\hat{\mathbf{x}}_{t}$ 外,我们还可以定义新的流匹配学习目标。流匹配的学习目标是一个速度,而速度又可以由两个端点决定。我们可以用上面的公式定义每一个阶段的端点 $\hat{\mathbf{x}}_{e_k}, \hat{\mathbf{x}}_{s_k}$,再以速度 $\hat{\mathbf{x}}_{e_k} - \hat{\mathbf{x}}_{s_k}$ 为该阶段所有点的学习目标。

重新设置上采样噪声

在多尺度生成时,我们必须仔细考虑图像应该怎么跨越两个阶段,也就是图像该怎么上采样。

大多数涉及扩散模型多尺度生成的工作(比如 Laplacian Diffusion Models sampling, f-DM)都会在上采样时考虑调整噪声强度的大小。而 Pyramid Flow 不仅考虑了噪声的强度,还考虑到了噪声的协方差矩阵——原本扩散模型不同位置的噪声是相互独立的,但 2 倍上采样后一个像素会影响到周围 4 个像素,因此噪声之间的关系需要用协方差矩阵表示而不是独立表示。作者根据协方差矩阵必须半正定这一性质推导了一个更好的修改上采样后噪声的方法。

多尺度视频生成

Diffusion Forcing 工作表明,在训练视频扩散模型时,不同帧的退化程度可以不同。而在 Pyramid Flow 的框架下,图像的退化不仅包括加噪,还包括降采样。因此,用 Pyramid Flow 训练视频扩散模型时,可以让过往帧为低分辨率的,只让最新帧为最高分辨率的。这样做可以大大减少注意力操作的输入量,提升计算效率。此外,还可以参照 Diffusion Forcing 的做法,通过因果(causal)注意力把视频扩散模型转换成约束于前几帧的单张图像自回归生成。

实现细节

最后,我们来简单看一下 Pyramid Flow 的实现细节。Pyramid Flow 可以基于任何一种 DiT 架构的文生图模型开发,比如 SD3 和 FLUX.1。为了将文生图模型转换成视频模型,本工作做了如下的适配:

  • 将图像 VAE 变成能在时间维度压缩视频的 3D VAE。
  • 在 DiT 中用 RoPE 描述时间维度的位置关系。空间维度位置编码保持原模型的设计。

另外,为了对齐不同尺度的空间位置编码,本工作参考 CogVideoX,对低分辨率图像使用了位置编码外推。

本工作开源的视频模型需要用 128 块 A100 训练约 1 天,能生成 241 帧总时长 10 秒的视频。相比其他视频模型来说要的资源已经少了很多了。

批判性分析与总结

Pyramid Flow 在多尺度加噪的设计上和以往工作 f-DM 非常相似。二者都把扩散模型的加噪拆成不同尺度,都在同一尺度内采取线性插值的方式计算带噪图片,都考虑了上采样时噪声的变化。二者的不同之处在于:

  • f-DM 用的是 DDPM 的噪声调度,而 Pyramid Flow 用的是 Rectified Flow 的公式。由于 Rectified Flow 的公式本来就是线性插值,因此它对同尺度带噪图像插值来说兼容性更好一点。
  • 基于上一点,可能是由于同尺度线性插值与 DDPM 不太兼容,f-DM 额外用一个残差项表示下采样导致的误差。Pyramid Flow 没用到这一设计。
  • f-DM 从局部信噪比的角度推导了上采样时噪声应该发生的变化,而 Pyramid Flow 是从协方差矩阵的角度。

Pyramid Flow 没引用 f-DM,作者大概是独立发明了一遍类似的加噪策略。我在 GitHub 上已经告知了作者有 f-DM 这篇类似论文。当然,哪怕是有类似工作在前,Pyramid Flow 在结合流匹配和多尺度生成上还是有一定创新的。

包括这个工作在内,有好几个工作都在用扩散模型做多尺度图像生成。但是,不像自回归中的 VAR,这些工作在纯图像生成任务上并不是很有名。因此,多尺度生成虽然是一个不错的想法,但我们还需要多多思考该怎么在扩散模型里应用它。Pyramid Flow 最大的意义可能不在于其多尺度的设计,而在于将多尺度生成融合进了视频生成中。这样做最直接的好处是减少了历史帧的数据量,提升了模型计算效率。

虽然这个工作试图用同一个去噪模型来处理任意尺度的图像,但实际上它只用了 3 个尺度。只用 3 个尺度并不能说明模型能够处理任意多尺度的图像,这可能仅仅是暴力拟合的结果。因此,我觉得一个比较重要的思考方向是:怎么让去噪扩散模型更好地理解图像尺度这一概念,复用各个尺度的知识,从而实现任意多尺度的图像去噪。

受到经典图像表示方法拉普拉斯金字塔(Laplacian Pyramid)的启发,英伟达最近公布了一种叫做 Laplacian Diffusion Model (拉普拉斯扩散模型,后文简称 LaDM)的新型像素空间扩散模型,并用这种架构实现了文生图、超分辨率、ControlNet 等多种任务。在这篇博文里,我们来着重学习一下这种新型扩散模型的设计思想。

以往工作

扩散模型奠基之作 DDPM 及其升级版 ADM (Diffusion Models Beat GANs on Image Synthesis) 都是像素空间里的扩散模型。相比 LDM (隐扩散模型,即 Stable Diffusion),这类扩散模型不需要额外的自编码器来压缩图像,避免了编码解码带来的精度损失。

将图像从分辨率的维度拆解是一种很常见的思想。比如 Cascaded Diffusion Models 就是一种先生成低分辨率图像,再不断超分的扩散模型。今年比较有名的 VAR(Visual Autoregressive Modeling: Scalable Image Generation via Next-Scale Prediction)也是一种按分辨率自回归的生成模型。

和这篇工作非常相关的早期工作是苹果在 2022 发表的 f-DM: A Multi-stage Diffusion Model via Progressive Signal Transformation。f-DM 将扩散模型的加噪推广到了降采样、模糊等其他退化策略上。降采样版的 f-DM 有非常多的设计和本工作很像。苹果该团队次年发表的 Matryoshka Diffusion Models 也用到了按分辨率逐次生成的设计。

将拉普拉斯金字塔融入扩散模型

拉普拉斯金字塔是一种图像表示方法,它把图像按频率成分拆成几张分辨率不同的图像,分辨率越低的图像表示频率越低的图像成分。我们直接通过下面的例子学习它的原理。假如 x 是原图,那么 x(3)=down(down(x))x(2)=down(x)-up(x(3)), x(1)=x-up(down(x))。对 x(1), x(2), x(3) 求加权和就可以还原输入图像。

受此启发,LaDM 将扩散模型的训练过程也用类似的方法分解:设 $\mathcal{X}$ 为训练图片集合,$\mathcal{X}^{(1)},\mathcal{X}^{(2)}, \mathcal{X}^{(3)}$ 分别是拉普拉斯金字塔不同成分构成的集合,那么我们在 $\mathcal{X}^{(3)}$, $\mathcal{X}^{(3)} \cup \mathcal{X}^{(2)}$,$\mathcal{X}$ 上分别训练三个去噪模型。也就是说,不同分辨率的模型生成不同层级的拉普拉斯金字塔复原结果。

根据经验,扩散模型早期(加噪后期)生成低频内容,后期(加噪前期)生成高频内容,所以训练时我们让不同分辨率的输入图像随噪声的衰退速度也不同。图像所代表的频率越低,衰减速度越慢,越需要从早期开始去噪。这样,在生成时,我们能生成到中途后再逐渐加上高频细节。

在采样过程中,我们按照下图所示的路线从低频到高频生成图像。有了该分辨率的初始图像后,按正常 DDPM 采样的步骤就可以生成当前分辨率的图像了。问题在于某分辨率的初始图像怎么从上一个分辨率过渡而来。

在切换当前带噪图像的分辨率时,我们既要放大其中的清晰图像(信号),也要放大其中的噪声。观察上一张图和下面的图,在分辨率切换时,新的高频成分(上图中的$\mathbf{x}^{(2)}$在时刻 3 及 $\mathbf{x}^{(2)}$ 在时刻 2)是一张纯黑图,新信号为零,所以对于信号的部分我们可以直接放大。而放大噪声时,我们要做一些噪声强度上的修改,保证放大后信噪比不变。这部分的细节详见论文。

1K 图像生成

为了生成 $1024 \times 1024$ 分辨率的图像,LaDM 采用了两阶段 Cascaded Diffusion Model 的设计,让生成高分辨率的图像约束于低分辨率图像。另外,由于注意力操作的时间复杂度很高,一般的像素扩散模型只能做到 $256 \times 256$ 大小。为了解决此问题,LaDM 依然用一个 $256 \times 256$ 的去噪模型来生成 1K 图片,但输入前后用小波变换来压缩/复原图像。

批判性分析与总结

这篇文章是一篇由公司发表的技术报告,展示了很多可视化结果,却没有任何定量结果,代码也没有开源,不知道它的生成能力和其他模型比起来如何。

这篇文章提出的模型虽然是像素空间扩散模型,但是其拉普拉斯金字塔的设计与模型是像素空间模型还是隐空间模型无关。我们完全可以把这套设计搬到隐空间上。VAR 已经向我们证明了对隐空间图像做拉普拉斯分解是可行的。另外,这篇文章的主干网络是 U-Net 而不是 DiT。想对这个工作做一点简单的改进的话,可以弄一个 LDM + DiT 版本的。

LaDM 设计最巧妙的点是其加噪过程,频率越高的成分越早变成纯噪声。这样的话我们可以在图像生成到一半的时候再直接把高频成分加上。如果高频成分一直在的话,我们还需要额外的设计在切换分辨率时把缺少的高频加上。

有工作证明神经网络不擅长拟合高频信息。因此,在图像任务中,手动将输入图像拆成不同频率成分可能有助于网络的学习。我们可能可以沿着这个思路去改进之前多种图像任务的输入。

随着视觉主干模型不断向 Transformer 靠拢,和 Transformer 配套的一些技术也从 NLP 社区涌入了 CV 社区。比如 Stable Diffusion 3 还在用标准 Transformer 那一套正弦位置编码,而其升级版 FLUX.1 就用上了旋转位置编码 (RoPE) , Lumina-T2X 模型甚至把 RoPE 的长度外推技术也从 NLP 社区搬了过来。在这篇博文中,我将站在一个对 NLP 技术了解不深的 CV 研究者的视角,较为详细地介绍一下 NLP 中 RoPE 相关的位置编码知识、RoPE 长度外推技术以及它们在 CV 里的应用。

长度外推,指的是使用在短序列上预训练的 Transformer 模型直接生成超出训练长度的长序列。类比到图像生成中,长度外推可以看成对模型所建模的图像分布做了一次超分辨率:比如模型训练时只见过 $256 \times 256$ 的图像,我们想直接用它生成 $512 \times 512$ 且同样清晰的图像。

推荐大家在阅读本文前先熟悉位置编码的基本原理,强烈推荐阅读 RoPE 提出者苏剑林的系列文章。

位置编码设计原则与 RoPE 的首次提出:https://kexue.fm/archives/8130

详细介绍 RoPE:https://kexue.fm/archives/8265

介绍长度外推的一项关键改进 (NTK-aware):https://kexue.fm/archives/9675 https://kexue.fm/archives/9706 https://kexue.fm/archives/9948

和这篇博文相关的两篇学术论文是:

YaRN,一种公认效果较好的长度外推技术:YaRN: Efficient Context Window Extension of Large Language Models (https://arxiv.org/abs/2309.00071)

Lumina-Next,前沿扩散 Transformer (Diffusion Transformer, DIT) 模型,采用了长度外推技术:Lumina-Next : Making Lumina-T2X Stronger and Faster with Next-DiT (https://arxiv.org/abs/2406.18583)

位置编码知识回顾

Transformer 中的位置编码

相比于此前流行的 CNN、RNN 模型,Transformer 的一大特点是其输出与输入次序无关。比如我们用 Transformer 建模文本的概率,那么模型会把「上海」和「海上」当成概率一样的词语。这也就是说 Transformer 无法从输入词元 (token) 的位置关系中获取信息。

如果让 Transformer 不输出信息聚合后的概率,还是保留输入词元的结构的话,那么打乱输入词元顺序就会同样地打乱输出词元顺序。模型依然无法获取输入词元间的位置关系。

为了把 1, 2, 3, 4 这样的位置信息输入进模型,标准 Tranformer 的做法是给不同位置的输入加上不同的位置编码。假设模型的中间变量都是二维向量,那么在句子中位置为 $k$ 的词元的位置编码是:

如果模型的中间变量都是 $d$ 维向量 (为了方便不妨认为 $d$ 是偶数),我们只需要把 $d$ 拆成 $d/2$ 组,每组用不同频率的三角函数即可。这样,长度为 $d$ 的词元在位置 $k$ 的位置编码是:

直观上来看,随着中间变量的维度越来越长,位置编码中对应的三角函数的频率不断变低,从一开始的 $1$ 逐渐靠向 $1/10000$。

上述公式来自论文,代码实现时要注意更多细节。比如有些代码中 $i$ 是从 $0$ 开始计数的。由于指数的分子的范围是从$0$到$d-2$,代码会把指数的分母也改成 $d-2$,保证最后一组三角函数的频率是 $1/10000$。

算出一个和输入词元向量等长的位置编码后,该编码会直接加到输入向量上。由于这种编码用了正弦函数,所以它被后续工作称为正弦位置编码。

相对位置编码与 RoPE

在设计位置编码时,最好能让编码传达词元的绝对位置相对位置信息。比如句号会出现在文本结尾而不是文本开头,这一规律来自绝对位置信息;而每几个词元会组成固定的词组,与它们在整段文本中的位置无关,这反映了相对位置信息的意义。

正弦位置编码同时满足了这两个性质。首先,正弦位置编码的输入只有绝对位置,它本质上就是一种绝对编码。另外,根据三角函数和角公式,假设偏移 $\Delta k$ 是常数,那么$sin(\alpha-\Delta k)$可以由含 $\alpha$ 的三角函数的线性组合表示。这说明模型能够从正弦编码中部分了解到一些相对位置的信息。

作为绝对位置编码,正弦编码虽然能够表达一些相对信息,但是这些信息太隐晦了。并且,该编码只在输入时加入,可能在网络运算中途这些信息就消失了。我们能不能更加显式地用某种绝对位置编码建模相对位置关系呢?

在 Transformer 中,不同位置的词元仅会在注意力操作时做信息交互。观察下面的注意力计算公式,更具体一点来说,信息交互发生在注意力的 QK 内积时。我们可以在每次注意力操作前都给 $Q$, $K$ 里各个向量加上位置编码,保证相对位置信息能反映在注意力计算里。

苏剑林设计了一种新的位置编码:考虑 $Q$ 里位置为 $m$ 的向量 $\mathbf{q}_m$ 和 $K$ 里位置为 $n$ 的向量 $\mathbf{k}_n$,给位置为 $j$ 的向量右乘上复数 $e^{ij\theta}$,其中 $i$ 是虚数单位, $\theta$ 是一个角度。这样,复数下的 QK 内积结果为:

其中,$Re[]$是取实部,$*$ 为共轭复数。可以发现,内积结果也出现了位置编码 $e^{i(m-n)\theta}$,且编码的值仅取决于相对位置 $(m-n)$。因此,这种编码能够更加显式地在注意力运算里建模相对位置。

由于最终结果取了实部,所以上述所有运算都可以转换成实数域的操作。假设 $\mathbf{q}_m = (q_0, q_1)$ 只是一个二维向量,那么上述右乘位置编码的操作可以写成:

从几何意义上讲,这个操作其实是二维向量旋转。因此,这种位置编码被称为旋转位置编码(RoPE)。

和正弦编码类似,要把二维中间变量拓展成 $d$ 维时,只要分组讨论,改变每组的频率(这里的频率是角度 $\theta$)就行了。

角度 $\theta$ 的设计可以参考 Transformer 的正弦编码:对于第 $i$ 组,我们令 $\theta_i=1/10000^{2i/d}$

RoPE 和正弦编码有同有异。相同之处在于:

  • 二者都是绝对位置编码,并通过编码公式的某些设计间接传递了相对位置信息。
  • 二者用了同样的正弦编码方式:随着变量通道数的增大,对应位置编码的正弦函数的频率不断指数衰减。

不同之处在于:

  • 正弦编码仅在模型输入时施加一次,RoPE 在所有自注意力计算时都施加。
  • 正弦编码会生成一组编码向量,加到输入上。而 RoPE 是一种操作,它的几何意义是把注意力输入向量旋转一个角度。

用 RoPE 实现长度外推

现在,我们正式进入本文的正题:长度外推。长度外推严格来说是一类任务,并不一定要用外推的做法。它似乎最早出自论文 ALiBi (Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation)。正如论文标题所示,该任务的目的就是「短训练,长推理」:在短序列上训练后,不经额外训练或只需少量微调,让模型生成长文本。后来这种任务也被称为「上下文窗口拓展」(Context Window Extension),目的依旧是用已经训好的模型来生成更大的文本,只是不强调方法是外推。为了称呼方便,我们在这篇博文里将该任务统称为「长度外推」。

我们想一想,假设模型训练时最大文本长度是 $L$,现在要生成长度为 $L’$ 的句子 ($L’>L$),我们需要做什么呢?其实我们只要把代码写好,除了生成长度以外啥也不改就行了。

这样的话,模型在运行时究竟哪里发生了变化呢?根据我们之前的分析,Transformer 是不知道位置信息的,只有位置编码传递了位置信息。因此,增加了生成句子长度后,原本只见过位置在 $L$ 之内的位置编码,现在要尝试解读位置为 $L, L+1, …, L’ - 1$ 的位置编码。因此,如果除了修改生成长度外什么也不做,其实就是让模型把学到的位置编码知识外推。但很可惜,由于没学过这些训练集之外的位置关系,这种外推法效果很差。

我们在接下来的几节里会讨论一些更加强大的长度外推策略。这里先补充介绍一点东西。看了对长度外推任务的基本介绍,读者或许会疑惑:长度外推似乎只要考虑位置编号就行了,不是非得和 RoPE 绑定起来?其实,长度外推真正要考虑的是位置编码的形式而不是只考虑编号。我们稍后的分析其实对所有类正弦编码都有效。但现在大家都是基于 RoPE 讨论,用基于 RoPE 的模型做实验,可能是因为 RoPE 更加直接、全面地建模了词元间的交互关系,只要调整了 RoPE 的公式,其效果立刻就能反映出来。相比之下,正弦位置编码只是在输入时提供了位置信息,修改位置编码的细节不能全面地影响模型的输出。

位置内插

既然超出 $L$ 的位置没有被训练过,那么在 $L$ 之内多选一些位置为分数的点不就行了?位置内插(Positional Interpolation, PI)就是这样的一种长度外推方法,它把长度为 $0$~$L’$ 的位置线性压缩到 $0$~$L$ 内。也就是说,对于位置$m$,将其的位置编号修改为:

由于位置编号会被送进正弦函数里,所以编号哪怕是分数也没关系。通过这种简单的线性内插方法,我们就能在已经学好的编号范围内多选一些位置,实现长度外推。

内插确实比外推的效果要好得多。后续所有长度外推方法实际上都是在研究如何更好的求插值位置编码。很快,有人就从频率分析的角度提出了线性内插的一个改进。

改变正弦函数基础频率:NTK-aware Scaled RoPE

直观认识

就在位置内插提出不久,就有研究者在社区 (https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/) 提出了一种效果更好,完全不需要微调的长度外推技术:NTK-aware Scaled RoPE (后文简称为”NTK-aware RoPE”)。该研究者后续将此方法进一步整理优化,发表了论文 YaRN: Efficient Context Window Extension of Large Language Models。我们先看一下 NTK-aware RoPE。

NTK-aware RoPE 的改动非常简洁,但它改动的地方却很出人意料:原来,总长度为 $d$ 的向量的第 $i$ 组位置编码的频率为:

现在,我们把 $10000$ 改掉,公式变为:

使用这种新长度外推方法,在上下文窗口大小为 2048 的 LLaMA 模型上,模型生成长文本的误差远低于之前的方法。

为什么这么简洁而奇怪的修改这么有效呢?在深入理解其原理之前,我们先直观地看一看这个方法具体修改了公式里的哪些参数。

先看新位置编码向量 $\sin m\theta_i^{NTK-aware}$ 的第二组(假设 $i$ 从 $0$ 开始计数),也就是含参频率最大的这一组。它现在是:

$\frac{L’}{L}$ 表示新长度是训练长度的几倍,它是一个大于 $1$ 的数。$d$ 在 LLaMA 里是 $128$,所以我们可以认为 $(d-2)$ 远大于 $2$。所以,$(\frac{L’}{L})^{(-2)/(d-2)}$ 这一项略小于 $1$。整体上看,这一改动差不多就是给三角函数的频率乘上了一个略小于 $1$ 的常数,几乎没变。

再看频率最小的 $i=d/2-1$ 项。它现在是:

而在线性内插中,我们直接把所有 $m$ 替换成了 $\frac{L}{L’}m$。所以,频率最小的项的公式和线性内插时的公式完全相同。

这里要澄清一下「外推」和「内插」的概念,这两个词的意义在很多博客和论文里并没有讲清楚。「内插」指的是通过像前面的线性位置内插一样,修改位置编号,使其恰好落在训练长度内。然而,一旦这个内插不够彻底,那么新位置编号就可能会超出训练长度,形成位置「外推」。我们本文讨论的所有方案,都是让不同频率的项在完全内插(恰好长度适合)和完全外推直接找一个平衡。一旦内插不彻底,就可以称为外推。所以很多文章里的「外推」,有的时候指的是不完全的内插。根据这样的术语定义,NTK-aware RoPE 的行为可以称为:最低频内插,其他频率外推。

从上面的分析可以看出,NTK-aware RoPE 还是沿用位置线性内插的思路,但是它对 RoPE 的影响更加平滑:对于位置编码高频项,公式几乎不变;对于最低频项,公式完全等于线性内插时的公式。

那么,NTK-aware RoPE 为什么有效呢?它又是怎么被想出来的呢?说起来,这个一直出现的 “NTK” 又是什么意思?NTK 其实是和神经网络相关的一种理论。NTK-aware RoPE 的提出者在构思这些公式时受到了 NTK 的启发,但他后续在论文里解释此方法时完全没有从严谨的理论入手,而只是讲了一些直觉的观察。在之后的两小节中,我将先从 NTK 理论的角度试图还原提出者的心路历程,再从一个广为人知、更易理解的角度来介绍 NTK-aware RoPE。

从 NTK 角度的解释

近几年和 NTK 理论比较相关的论文叫做 Fourier Features Let Networks Learn High Frequency Functions in Low Dimensional Domains。这篇论文用 NTK 理论分析了 NeRF 这种以位置坐标为输入的 MLP 需要位置编码的原因,并将这类位置编码归纳为「傅里叶特征」。

这篇论文最大的一个发现是:在形式为 $[\sin \theta_i x, \cos \theta_i x]$ 这样的傅里叶特征中,最重要的是决定最大频率。最大频率越大,MLP 拟合高频信息的能力越强。

由于 RoPE 的公式来自于正弦位置编码,而正弦编码又可以看成一种特殊的傅里叶特征,所以 NTK-aware RoPE 的提出者也试图将傅里叶特征中的规律套用在 RoPE 上。他可能观察到了应用线性内插后 RoPE 公式(正弦编码公式)的频率变化。原来编码第 $i$ 项为:

应用线性内插后,公式为:

这里 $\frac{L}{L’}$ 是一个小于 $1$ 的数。所以,加上线性内插后,所有项的频率都变小了。自然,公式能表达的最大频率也变小了,拟合高频信息的能力下降了。

我们可以把线性内插类比到 NeRF 这类任务中。如果我们增加输入坐标的密度,确实可以让图片/3D 模型的输出分辨率变大。但是,根据信号处理的知识,这种分辨率变大并不能超出原有的频率,所以变大后的图片/3D 模型会看起来很模糊。「模糊」在文本任务中的体现可能就是误差指标上升。

出于这些原因,NTK-aware RoPE 的策略是尽可能不动高频项的频率,仅动低频项的频率。当然,按照这种设计思路,我们其实可以提出各种各样的方案。NTK-aware RoPE 选了实现起来最方便的一种:修改频率基底,让它在最低频时和线性内插对齐(读者感兴趣可以设方程自行推导频率基底的修改值,把我们刚刚有关最低频项的分析倒过来)。这样,自然就有高频项几乎不变,低频项向线性内插靠拢,也就是我们在上一小节中的观察。

根据我的理解,傅里叶特征本身就只是稍微用到 NTK 相关的理论(参见我有关傅里叶特征的博文)。而 NTK-aware RoPE 的作者貌似仅是受到了傅里叶特征的某些启发,完全没有严谨地用 NTK 理论来推导 NTK-aware RoPE 的形式。所以,我认为,要学习 NTK-aware RoPE,完全不用学习 NTK 理论。

NTK-aware RoPE 的提出者在互联网上和 YaRN 论文中用了一些更好理解的方式解释 NTK-aware RoPE。类似地,从进制转换的角度,苏剑林也发表了两篇一针见血的解读博文:https://kexue.fm/archives/9675 https://kexue.fm/archives/9706 。我建议从这些角度来学习 NTK-aware RoPE,然后忘掉 NTK 这个词。我们在下一节里就从这个角度重新认识一遍位置编码。

从进制的角度解释

其实几乎每个人都理解位置编码。

不信?我来问个问题:看到 $1234$ 后,你看到了几个数?

确实,这只是一个数。但是,我们人在看到这个数的时候,其实是看到了 $4$ 个十进制数字。通过把不同位置的数字组合,我们才理解了这个数究竟是多少。真正的数是一个概念,我们可以把两个东西这一概念,表示成汉字「二」,阿拉伯数字「2」,或者是二进制下的 $10$。我们常见的十进制只是表达数的一种方式。

而进制表示其实就是一种表达数的位置编码。想象一个十进制计时器,它的数字从 $0, 1, …$ 开始不断增长。每隔 1 次,个位变一次;每隔 10 次,十位变一次;每隔 100 次,百位变一次……。也就是说,个位是频率最高的,位数越高频率越低。是不是这和正弦位置编码很像?正弦位置编码和进制表示的区别在于,进制用求余体现周期性,正弦位置编码用正弦函数体现周期性。

长度外推,就好像一个只见过 0-999 的模型,突然要处理 1000 以上的数一样。为了只用三位数来表达更大的数,一种简单的做法是进制转换。比如我们直接把十进制变成十六进制,那么可以表达的数就从 $10^3$ 变成了 $16^3$。

回到正弦编码的公式里,进制这个概念体现在哪呢?进制的底数又是什么呢?

在十进制里,不同位表示十、百、千……每算一个更高的位的值,就要多除以一次 $10$。所以,在正弦编码里,我们需要关注哪个被除以的量在做指数运算。通过观察发现,正弦编码的底数是 $10000^{2/d}$。

知道了我们想把句子长度拓展几倍,我们就可以精确地算出新底数。通过这种方式,我们就能推导出 NTK-aware RoPE。也就是说,NTK-aware RoPE 修改频率基底其实就是对正弦函数做进制转换。这部分推荐大家去阅读前面提到的苏剑林的博文。

基于数字进制,我们可以把位置编码类比成表示时间的时钟,便于后续概念的理解。这是因为:

  • 正弦函数本身就可以用周期旋转来解释。
  • 相比数字的进制,时间的进制的底数是不同的:1 天有 24 个小时,而一小时有 60 分钟。这提示我们:我们不一定要对每种频率做同样的处理。

利用这个时钟的比喻,NTK-aware RoPE 的提出者在社区解释了不应该像线性内插一样修改最高频率的原因:就像我们用秒针来区分最精确的时间一样,神经网络用最高频的正弦编码区分相对位置关系,且只能看清 1 秒以上的偏差。使用线性内插后,最小的时间偏差是 0.5 秒,神经网络就不能很好地处理最高频的那块信息了。而 NTK-aware RoPE 不会修改一秒的定义,只会在分钟、小时等更低频的分量上多插值一点,神经网络依然能区分最精细的时间。

改进 NTK-aware RoPE:分部 NTK

我们在上一节中学到,NTK-aware RoPE 的设计思想是高频不动(或理解成高频外推),只对低频内插。只改频率基底虽然做法简洁,但不见得是最优的做法。高频不动这部分应该没什么问题,我们把目光放在 RoPE 的低频分量上。

还是从十进制的角度看待位置编码。假设训练集的位置只有 $0$~$2800$,那么在千位上,模型只见过 $0, 1, 2$ 三个数字。由于在千位上模型没有完整见过 $0$~$9$ 的循环,模型不能推测出其他几个数字的意义。因此,在千位上做长度外推时,一定要用内插把位置编号正确缩放到已学习的范围内。

这套分析怎么迁移到正弦编码上呢?对于十进制数字,我们能很快判断出某一位是否走完了一个周期。比如要把千位上的 $0$~$9$ 都走一遍,就至少得要一万个数。怎么找出正弦编码每个频率走一个周期需要的距离呢?

在正弦函数中,我们可以用 $2 \pi$ 除以频率,得到波长。正弦位置编码某一项的波长表示当训练上下文长度至少为多少时,这一项会「转」完一个周期。比如时钟上,秒针 60 秒转一圈,分针 3600 秒转一圈。

$2 \pi$ 除以频率明明算出的是周期,周期乘上速度才是波长。但 YaRN 的作者就是在论文里把这个量定义成了周长。可能他们认为波长的单位是长度,上下文窗口大小也是长度,两个单位是匹配的。我认为这个名字取得很糟糕,就应该叫做周期的,只不过周期的单位也是长度而已。

根据这个定义出来的波长,我们可以对正弦位置编码的不同位置分类讨论:

  • 如果波长过大,大于了训练时的文本长度,那么就用普通的线性内插,保证不在这些维度上外推。设它们的内插程度为 $1$。相比之下, NTK-aware RoPE 只对最低频项做了完整内插,而没有考虑其他波长过大的项也应该完整内插。
  • 如果波长过小,说明频率很高,不应该做任何修改。设它们的内插程度为 $0$。
  • 对于其他位置,根据它们的波长,线性选择内插程度。

这里波长过大、过小的阈值用超参数来决定,每个模型都需要手动调整。

总之,NTK-aware RoPE 只是模糊地定义了高频分量应该尽可能不变,低频分量应该尽可能像线性内插。而分部 NTK 则允许我们显式对各个频率分量做分类讨论。最终的位置编码方案 YaRN 在分部 NTK 的基础上还做了少许修改,对此感兴趣的读者可以去阅读论文。

图像生成中的 RoPE 与长度外推

了解了近年来 NLP 社区的位置编码技术,我们来以 Lumina-T2X 为例,再看一下这些技术是怎么用到视觉生成任务上的。

多维 RoPE

RoPE 本来是设计给 1D 的文本数据的。而在视觉任务中,图像是二维的,视频是三维的,我们需要设计更高维的位置编码。

回顾 RoPE 的形式:

要把它拓展成高维很简单。比如要拓展成 3D RoPE,只要把上面的公式复制两份,放到原公式的下面就行。也就是说,我们把向量拆成三份分别处理,每一部分和 1D RoPE 一样。

在这种设计下,模型所有中间向量的不同维度有了不同的意义,它们可能负责了视频宽度、高度或长度上的信息处理。我们也可以根据实际需要,让负责不同视频维度的向量长度不同。

视觉扩散模型中 RoPE 的长度外推设计

为了生成比训练分辨率更大的图像,Lumina-T2X 也参考了 NTK-aware RoPE,提出了一些和图像相关的 RoPE 改进策略。

首先,和分布 NTK 策略一样,Lumina-T2X 提出了频率感知 RoPE。在这种策略下,波长大于等于训练长度的位置编码项完全使用线性内推,剩下的项使用 NTK-aware RoPE。

另外,Lumina-T2X 还提出了时刻感知 RoPE。这个「时刻」指的是扩散模型里的加噪/去噪时刻。根据实验结果,Lumina-T2X 的作者发现线性内插会保持图像整体结构,但是图像局部质量下降;NTK-aware 策略提升了局部质量,却会出现内容重复现象,也就是全局关系不合理。能不能在某一方面结合二者呢?根据之前使用扩散模型的经验,扩散模型在去噪初期只生成低频信息,也就是全局信息,后期才会生成高频细节。受此启发,Lumina-T2X 提出了时刻感知 RoPE,该策略会在去噪早期仅使用线性内插,后续慢慢过渡到频率感知 RoPE。

以下是论文展示的在各种长度外推策略下生成 2K 图片的效果图。最左侧的 1K 图片供参考。

总结

长度外推是生成任务中的一项重要技术,它让我们在不大规模重新训练模型的前提下提升输出内容的长度/大小。而 Transformer 本身是一种无法获取输入元素位置信息的生成模型,需要靠额外的位置编码来感知位置。那么正好,只要我们能够适当地修改位置编码的推理行为,就能想办法让模型生成更长的内容。目前长度外推的方案都和修改 RoPE——一种给 Transformer 显式提供相对位置信息的位置编码——有关。我们主要学习了 NTK-aware RoPE 的设计原理,并通过深入的分析学习了其改进版分部 NTK RoPE。基于这些知识,我们简单认识了 RoPE 长度外推在视觉生成中的应用,其中比较有趣的一项设计是做长度外推时考虑扩散模型的去噪时刻。

说白了,本文所有长度外推设计都是在从两个维度上排列组合:RoPE 可以看成是由多个频率项组成的正弦编码;外推方案可以从位置编号线性内插过渡到位置编号不变(即位置外推)。一般的设计策略是:对于没有学满一个完整周期的频率项,采用完全线性内插;对于其余频率项,按一定比例执行线性内插。加上了扩散模型的去噪时刻这一设计维度后,我们可以按同样的设计思路,早期更关注低频,晚期更关注高频。

我觉得长度外推技术的能力是有上限的。我们完全可以从信号处理或者信息论的角度来思考这一问题,因为它的本质和从频域对图像做超分辨率很像。在较短的序列中,模型只能学到这种长度的序列所能表示的最大频率的信息。强行用它来生成更长的序列,只会出现两种情况:要么序列局部不够清晰,要么每个局部很清晰但是没有很好的全局依赖关系。根据信息论,模型就是不能从短序列中学到长序列蕴含的一些规律。从 Lumina-T2X 展示的结果里,我感觉 NTK-aware RoPE 的做法某种程度上就像是把全图做超分辨率变成拆成几个小图,每个小图在原来的训练长度上分别做超分辨率。这样最后图像每一块都很清晰,但合起来看就有问题。可能对于一些文本任务来说,只要局部质量高就行了,长距离依赖没那么重要。