简单得令人尴尬的FSQ:“四舍五入”超越了VQ-VAE
By 苏剑林 | 2023-10-31 | 82249位读者 |正如“XXX is all you need”一样,有不少论文都以“简单得令人尴尬”命名(An Embarrassingly Simple XXX),但在笔者看来,这些论文大多数都是噱头多于实力。不过,笔者最近阅读到的一篇论文,真的让人不由得发出“简单得令人尴尬”的感叹~
论文的标题是《Finite Scalar Quantization: VQ-VAE Made Simple》,顾名思义,这是一篇旨在用FSQ(Finite Scalar Quantization)简化VQ-VAE的工作。随着生成模型、多模态LLM的逐渐流行,VQ-VAE及其后续工作也作为“图像的Tokenizer”而“水涨船高”。然而,VQ-VAE的训练本身也存在一些问题,而FSQ这篇论文则声称通过更简单的“四舍五入”就可以达到同样的目的,并且有着效果更好、收敛更快、训练更稳的优点。
FSQ真有这么神奇?接下来我们一起学习一下。
VQ #
首先,我们来了解一下“VQ”。VQ全称是“Vector Quantize”,可以翻译为“向量量子化”或者“向量量化”,是指将无限、连续的编码向量映射为有限、离散的整数数字的一种技术。如果我们将VQ应用在自编码器的中间层,那么可以在压缩输入大小的同时,让编码结果成为一个离散的整数序列。
假设自编码器的重构损失能够让我们满意,那么这个整数序列就是原始图像的等价物,所有关于原始图像的操作都可以转化为整数序列上的操作。比如我们想训练图像生成模型,就只需要训练整数序列生成模型,而这跟本文生成等价,所以我们可以用它来训练一个GPT,模型和流程都跟文本一模一样,训练完成后,我们就可以从GPT模型中采样整数序列,然后送到解码器中得到图像,从而完完成了图像生成模型的构建。说白了,“VQ+自编码器”将任意输入都转化为跟文本一致的整数序列,统一了不同模态数据的输入形式,同时也统一了它们的处理和生成模型。
而这样的一个带有VQ功能的自编码器,就被称为“VQ-VAE”。
AE #
早在四年前的文章《VQ-VAE的简明介绍:量子化自编码器》中我们就介绍过了VQ-VAE,尽管被冠以“VAE(Variational AutoEncoder)”之名,但它实际上跟VAE没啥关系,如上一节所说,它只是一个带有VQ功能的AE(AutoEncoder)。
既然是AE,那么有encoder和decoder,一个普通的AE是这样的:
\begin{equation}z = encoder(x),\quad \hat{x}=decoder(z),\quad \mathcal{L}=\Vert x - \hat{x}\Vert^2 \end{equation}
VQ-VAE则稍微复杂一些:
\begin{equation}\begin{aligned}
z =&\, encoder(x)\\[5pt]
z_q =&\, z + \text{sg}[e_k - z],\quad k = \mathop{\text{argmin}}_{i\in\{1,2,\cdots,K\}} \Vert z - e_i\Vert\\
\hat{x} =&\, decoder(z_q)\\[5pt]
\mathcal{L} =&\, \Vert x - \hat{x}\Vert^2 + \beta\Vert e_k - \text{sg}[z]\Vert^2 + \gamma\Vert z - \text{sg}[e_k]\Vert^2
\end{aligned}\label{eq:vqvae}\end{equation}
让我们来逐步解释一下。首先,第一步是相同的,输入$x$到encoder中,输出编码向量$z$。然而,我们并不是直接将$z$输入到decoder中,而是先维护一个编码向量表$\{e_1,e_2,\cdots,e_K\}$(Codebook),从中选取与$z$最相近的一个$e_k$送入到decoder中进行重构$x$。由于编码表是有限的,所以我们也可以将实际的编码结果理解为一个整数(即与$z$最相近的$e_k$的$k$),这就是VQ-VAE中“VQ”的含义。
当然,实际应用中,为了保证重构的清晰度,encoder的输出可能是多个向量,每个向量经历同样的量化步骤变成一个整数,所以结果就是一张原本在连续实数空间的图片,被编码VQ-VAE编码为了一个整数的序列,这跟文本Tokenizer的作用是类似的,所以就有了“图像的Tokenizer”的说法。
梯度 #
然而,由于整个前向计算的流程中出现了$\mathop{\text{argmin}}$,所以梯度无法回传到encoder,这意味着我们无法优化encoder。此时常见的手段是Gumbel Softmax,但Gumbel Softmax的效果通常也是次优的,所以作者巧妙地借助了Straight-Through为VQ-VAE设计了更好的梯度。可以说,这是VQ-VAE最精彩的内容,它告诉我们什么“Attention is all you need”都是虚的,“Gradient”才是真正的“all we need”!
具体来说,VQ-VAE利用了深度学习框架基本上都自带的stop_gradient(即公式中的$\text{sg}$)函数来自定义梯度,所有经过$\text{sg}$的输入,都会保持同样的输出,但梯度被强迫为零。所以对于式$\eqref{eq:vqvae}$中的$z_q$,我们有
\begin{equation}z_q = e_k,\quad \nabla z_q = \nabla z\label{eq:sg}\end{equation}
这样一来,送入decoder的还是量化过后的$e_k$,但优化器求梯度时用的是$z$,而$z$是encoder出来的,所以encoder也能够被优化了。这个操作就叫做“Straight-Through Estimator(STE)”,是为神经网络的不可导模块设计梯度的常用技巧之一。
由于基于梯度的优化器依然是当前的主流,所以直接设计梯度往往比设计loss更贴近本质,当然通常也更难、更让人由衷地赞叹。
Loss #
不顾,事情还没完,此时有两个问题:1、现在encoder是有梯度了,但是编码表$e_1,e_2,\cdots,e_K$却没了梯度;2、$\text{sg}$虽然可以随意定义梯度,但不是胡乱定义一个梯度都可以成功优化模型的。从$\eqref{eq:sg}$可以看成,要使它在数学上严格成立,那么唯一的解是$e_k=z$,这告诉我们如果STE是合理的,那么$e_k$与$z$至少是相近的。于是为了梯度的合理性,同时也为了优化编码表,我们还可以补充一项辅助loss:
\begin{equation}\Vert e_k - z\Vert^2\label{eq:ez}\end{equation}
这样既可以迫使$e_k$与$z$接近,又可以让$e_k$也拥有了梯度,一举两得!但细想之下,还是有点美中不足:理论上encoder和decoder的重构loss已经足够优化$z$了,所以额外引入的一项应该主要用来优化$e_k$,而不应该反过来明显影响$z$。为此,我们再次利用$\text{sg}$技巧,不难证明式$\eqref{eq:ez}$的梯度等价于
\begin{equation}\Vert e_k - \text{sg}[z]\Vert^2 + \Vert z - \text{sg}[e_k]\Vert^2\end{equation}
第一项把$z$的梯度停掉了,剩下$e_k$的梯度,第二项则反过来,目前两项是$1:1$的权重求和,意味着两项相同程度地相互影响,而刚才我们说了,这辅助loss应该主要用来优化$e_k$而不是$z$,所以我们引入$\beta > \gamma > 0$,将辅助loss改为
\begin{equation}\beta\Vert e_k - \text{sg}[z]\Vert^2 + \gamma\Vert z - \text{sg}[e_k]\Vert^2\label{eq:ez2}\end{equation}
然后再加到重构loss中,就得到了VQ-VAE总的loss了
除此之外,$e_k$的优化还有另外的方案:首先将式$\eqref{eq:ez2}$的$\beta$置零,这样一来$e_k$就又没有梯度了;然后我们观察到,VQ-VAE的VQ操作其实跟K-Means聚类是有点相似的,$e_1,e_2,\cdots,e_K$相当于是$K$个聚类中心。根据我们对K-Means的了解,聚类中心等于该类的所有向量的平均,所以$e_k$的一种优化方案就是$z$的滑动平均
\begin{equation}e_k^{(t)} = \alpha e_k^{(t-1)} + (1-\alpha) z \end{equation}
这等价于指定使用SGD优化$\Vert e_k - \text{sg}[z]\Vert^2$这一项loss(其他项可以用Adam等)。该方案被VQ-VAE-2所使用。
FSQ #
可能有些读者疑惑,本文的主题不是FSQ吗?前面介绍VQ-VAE的篇幅是不是有点多了?事实上,由于FSQ完全对得起“简单得令人尴尬”这个评价,相比VQ-VAE,介绍FSQ只需要“寥寥几行”,所以VQ-VAE不写长点,这篇博客就没几个字了哈~当然,将VQ-VAE写详细一点,也能让大家更深刻体会到FSQ的简单。
准确来说,FSQ只是用来替代VQ-VAE中的“VQ”的,它的离散化思路非常非常简单,就是“四舍五入”。首先,假设我们有一个标量$t\in\mathbb{R}$,我们定义:
\begin{equation}\text{FSQ}(t)\triangleq \text{Round}[(L-1)\sigma(t)] \end{equation}
这里的$L\in\mathbb{N}$是一个超参数,$\sigma(x)=1/(1+e^{-x})$就是sigmoid函数(原论文用了$\tanh$,笔者认为用sigmoid更科学),$\text{Round}$就是四舍五入为一个整数,所以不难看出$\text{FSQ}(t)\in\{0,1,\cdots,L-1\}$,即FSQ运算将输出限制在了$L$个整数之中,从而实现了离散化。当然,多数情况下一个标量还不够,对于$z\in\mathbb{R}^d$,每一维可以执行FSQ运行,于是
\begin{equation}\text{FSQ}(z) = \text{Round}[(L-1)\sigma(z)]\in\{0,1,\cdots,L-1\}^d \end{equation}
即$d$维向量$z$被离散为$L^d$个整数之一。但要注意,$\text{Round}$操作同样是没有梯度的(或者说梯度为零),不过经过VQ-VAE的铺垫,有些读者可能已经猜到接下来要做什么了:同样是利用STE技巧
\begin{equation}\text{FSQ}(z) = (L-1)\sigma(z) + \text{sg}\big[\text{Round}[(L-1)\sigma(z)] - (L-1)\sigma(z)\big] \end{equation}
即反向传播用$\text{Round}$之前的$(L-1)\sigma(z)$求梯度。由于$\text{Round}$前后本身是数值近似的,所以FSQ不需要额外loss来迫使近似的出现,也没有额外的编码表需要更新,FSQ的简洁可见一斑!
实验 #
如果将VQ理解为直接将编码向量聚类为$K$个不同的类别,那么FSQ就是将编码向量归纳出$d$个属性,每个属性划分为了$L$个等级,从而直接表达了$L^d$个不同的整数。当然,从最一般的考虑,每个属性的等级数也可以是不相同的$L_1,L_2,\cdots,L_d$,从而不同的组合数为$L_1 L_2\cdots L_d$。
按照原论文的建议$L\geq 5$(之前的LFQ则相当于$L=2$),所以如果要对齐VQ-VAE的编码数量$K$的话,对于FSQ来说应该有$d = \log_L K$,即FSQ对编码向量的维度$d$是有限制的(一般就只是个位数),并且通常是远小于VQ-VAE的编码维度(一般是三位数),这个直接后果是当编码总数$K$比较小(从而$d$也比较小)时,FSQ的效果通常不如VQ:
从图上可以看到,当编码表大小在1000左右时,FSQ与VQ的效果接近;当编码表大小明显超过1000时,FSQ占优;反之当编码表大小明显小于1000时,则VQ占优,这跟笔者自己的实验结果相近。笔者的参考代码为:
Github: https://github.com/bojone/FSQ
其他实验就是比较常规的证明FSQ比VQ更优异的各种任务实验了,读者自行阅读原论文就好。
思考 #
从形式上来看,假设$K=L^d$,那么VQ就好比是“一个$L^d$类的分类器”,而FSQ则是“$d$个$L$级的打分器”,不管是从参数量、几何直观或者表达能力来看,其实FSQ都不如VQ,但为什么FSQ有机会取得比VQ更好的结果呢?笔者认为有两方面的原因。
第一个原因,是encoder和decoder太强。虽然FSQ本身弱一些,但是encoder和decoder都足够强了,所以基于神经网络的万能拟合能力假设,FSQ相对于VQ的劣势,完全可以在encoder和decoder中弥补过来。而在$K=L^d$的设定下,两者的离散化程度都是一样的,也就是说encoder与decoder之间的“信息瓶颈”是一样的,因此FSQ本身的问题就显得微不足道了。
第二个原因,是VQ的“队友”(梯度)太弱。VQ的经典问题是编码表坍缩:当编码表增大时,编码表并没有被充分利用起来,反而由于恶性竞争导致编码表聚集到一块了,经典表现就是一个5000的编码表,最终效果还不如500的编码表。归根结底,这是梯度不够合理所致,尽管VQ已经巧妙地设计了梯度,但对于$\mathop{\text{argmin}}$这种硬指派的运算,基于梯度的优化都存在“赢者通吃”问题,这是坍缩的根本原因,而FSQ的$\text{Round}$运算并不涉及到指派,它是直接取的近似值。当然,往大了讲,其实跟VQ类似的K-Means经常也有聚类中心坍缩的问题,可见$\mathop{\text{argmin}}$难优化已经是一个老大难的问题了。因此与其说FSQ太强,倒不如说是VQ的“队友”太弱。
从以上两点分析可以看出,FSQ要想超过VQ,除了编码表要足够大之外,还有encoder与decoder要足够复杂,但这并非总能满足,比如有些场景下,我们希望模型的每一层输出都被量化,这时候平摊下来的encoder和decoder未必足够复杂,此时FSQ本身的不足就成为效果的瓶颈了。此外,VQ之后的向量维度没有变化,可以是任意多维,而FSQ之前的向量必须投影到$d = \log_L K$维,这是很严重的降维,当我们需要用到投影之前的高维度近似向量时,就很难靠FSQ之后的低维向量简单恢复过来。
所以,如果单纯是作为“图像的Tokenzier”,那么FSQ或许已经可以取代VQ,但这并不意味着任意场景下VQ都可以被FSQ取代。
小结 #
本文介绍了VQ-VAE的“VQ”的一个及其简单的替代品——FSQ(Finite Scalar Quantization),它直接通过四舍五入来对连续向量进行离散化,并且不需要额外的loss进行辅助。实验结果表明,当编码表足够大时,FSQ比VQ更有优势。
转载到请包括本文地址:https://kexue.fm/archives/9826
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (Oct. 31, 2023). 《简单得令人尴尬的FSQ:“四舍五入”超越了VQ-VAE 》[Blog post]. Retrieved from https://kexue.fm/archives/9826
@online{kexuefm-9826,
title={简单得令人尴尬的FSQ:“四舍五入”超越了VQ-VAE},
author={苏剑林},
year={2023},
month={Oct},
url={\url{https://kexue.fm/archives/9826}},
}
October 31st, 2023
[...]Read More [...]
October 31st, 2023
苏神好!感谢分享,每期大作都拜读,尤其是理论推导结合intuition方面,引人入胜!
请问您怎么看Google提的这篇MagViTv2,文中的LFQ和您提到的FSQ是什么关系呐(理论基础很渣的学渣求教)
另外也想听听您对image预训练的tokenizer以及和LLM如何一起训练的看法(如果能开一篇新博文更妙!),比如说Fuyu8B,直接从头接入image/从头训练(虽然解决了分别率问题,但他们那种直接塞入的方式,感觉少了一些相对性方面的先验)
1、LFQ相当于FSQ取$L=2$吧,实验结果同等编码表大小时显示不如$L\geq 5$。
2、fuyu8b直接输入image patch,是简单省事,但是它输出只有文本而没有图片,也就是说这种方式只能理解图片,不能生成图片。如果要生成图片的话,还是得先离散化。
FSQ里展示VQ-VAE方法在codebook增大的时候,后期construction会变差;但是LFQ展示的是VQ-VAE在codebook增大的时候,后期construction在变好,但是模型生成能力会变差。说法好像有些不一致?
按道理,VQ只是一个离散化方法,增大codebook的话construction肯定会变好的,至于生成能力则取决于另外训练的生成模型,跟VQ没有太大关系?当然,也有可能同样生成模型下,大codebook导致了更难训练(或者是codebook的坍缩导致了离散结果并不完美,从而更难训练)。
您好,为什么说“如果要生成图片的话,还是得先离散化”?谢谢
https://kexue.fm/archives/9984
November 1st, 2023
苏神您好,看了您写的VAE专栏慕名而来,受益良多。另外,想问一下您对NTK(Neural Tangent Kernal)是否有了解,最近接触这方面,感觉颇为吃力,想多向您学习学习。
对于NTK,除了 https://kexue.fm/archives/8009 这篇勉强挨边的文章外,可以算得上完全不了解。
November 29th, 2023
[...]前些天我们在《VQ一下Key,Transformer的复杂度就变成线性了》介绍了“Transformer-VQ”,这是通过将Key序列做VQ(Vector Quantize)变换来实现Attention复杂度线性化的方案。诚然,Transformer-VQ提供了标准Attention到线性Attentino的一个非常漂亮的过渡,给人一种“大道至简”的美感,但熟悉VQ的读者应该能感觉到,当编码表大小[...]
November 30th, 2023
苏神,VQ和FSQ是不是可以结合一下,把L个整数对应L个可学习的embedding,然后在得到d维特征时,相当于d个embedding拼一起,总维度就是d的倍数。通过这样做来解决一下VQ里K较小时,FSQ效果不如VQ的情况
如果将FSQ的结果变成整数然后又变成embedding,那么就回到VQ的问题,梯度无法直接回传,需要人为设计梯度了。
December 19th, 2023
苏神,文章里提到VQ-VAE可以作为图像tokenization的方案,想问下这里主要有哪些考量呢?
如果只需要把图像的表示离散化,那应该还是有挺多思路可以尝试的,甚至直接拿原始像素值也是一种思路
我理解这里面是不是有两层考虑,一层是信息的无损性,即抽取的图像表示以及离散化后的结果相比原图尽量没有信息损失;第二层是可学习性,即离散化的结果是否容易通过自回归的方式学习,这块感觉大家讨论的比较少
苏神对这个问题的看法呢?
信息的无损性是通过重构loss来保证的。用VQ-VAE主要是从压缩率的角度来考虑吧,比如 https://kexue.fm/archives/6760 中的实验,可以将原始的128*128*3个像素,可以压缩为32*32个token。我不知道除了VQ和FSQ还有哪些方法实现这个目的哈,如果还有,还请指教。
January 2nd, 2024
讲解的很清楚,思考的部分尤其让人受益匪浅,佩服佩服!VQ的方式有很多种,例如在VQ- VAE基础上还有拓展的层次化的RQ- VAE;在高效向量检索里面也经常使用量化的方式,和FSQ类似的从d的维度上做切割,但是量化的方法又和VQ类似是做聚类,不过是一次性聚类而VQ是分 batch在学习中聚类。想知道大佬对这些量化方式的思考和直觉上它们适合的场景,优势和劣势分别有哪些呢?
FSQ跟数值上量化原理相同,但场景完全不一样,所以没什么好比较的。唯一能比的就是FSQ和VQ,它们都属于向量量化方法,我没看到过其他的向量量化工具,这两个的优劣,本文也稍微介绍过了。
January 3rd, 2024
我实现了你上文提到的模型的另一种形式。不得不佩服,你们没有直观图像,却能从数学上去描述。
请问你是指哪个?
January 3rd, 2024
我看到好几篇使用STE而不使用Gumble Softmax的paper了,是不是现在的趋势啊?
大部分STE也要配合Gumble Softmax使用吧?两者并不是互为对立的替代品。
March 29th, 2024
请问 fsqae 代码中,reconstruction loss 使用的 l2_loss 为什么不能替换为 mse loss 呢?我理解这两者只有一个 batch_size 常量的差别
mse_loss相当于l2_loss除以了“img_size * img_size * 3”这一个大常数,也就是每个参数的grad也除以了这个大常数,这会导致原本的grad变得很小。
而Adam优化器有一个epsilon参数,如果它是零、并且使用fp32精度的话,那么两者理论上基本上是等价的;但如果epsilon不是零的话,那么部分grad很小(比epsilon小)的参数,它的更新量就偏小了,有时候会陷入局部最优点无法跳出来。