admin

  • This content is password-protected. To view it, please enter the password below.

  • 在最初尝试了解 diffusion 模型的时候,原本是打算跳过 VAE (Variational Autoencoder 变分自编码 )模型的,后面发现有点跳不过去。再花了一些时间去了解 VAE,才意识到其实不应该跳过去,相反的,了解 VAE 实现的一些架构、原理、直觉、初衷、数学原理则可以大大帮助理解后续的生成模型。

    1. 为什么现在你依旧需要了解 VAE

    “潜空间”(latent space)的处理依旧是现代(SOTA)生成模型最为核心的组件。而从autoencoder模型,扩展到 VAE 模型,则是生成模型走出的关键步骤之一。将潜空间限制在一个正态分布的空间内,然后,在这个空间进行采样后进行 decoder 的生成思想,则是现代生成模型很多思想来时的路。

    如果跳过这一段,很多的概念则会显得非常突然。

    2. VAE 关键直觉与主要思想

    2.1 关键直觉

    在计算机与数学科学中有几个概念是反复出现的,其中之一就是“高斯分布”(或者叫“正态分布”)。在图像生成模型中非常关键的,则是将潜空间限制在了一个正态分布之中,为什么会这样?似乎并没有人去说明这一点,这里做一个简单的阐述和理解。高斯分布可以理解为大量伯努利分布的极限形式,现实世界的分布我们通常会假设其为大量微小因子的共同作用下的宏观表现,故通常假设其满足高斯分布。

    那这与 VAE 有什么关系呢?在经典的 Guassian VAE 中,一个关键假设是:“潜空间” \(z \) 符合高斯分布的(通过训练来将其拉向高斯分布)。然后再对潜空间采样后,就可以生成较为“逼真”的图像了。那么假设“潜空间” \(z \) 符合高斯分布有什么深意吗?还是只是为了方便采样?

    从直觉构建的角度,我们可以这样认为:潜空间的每个“维度”都有某这非常强的意义,例如色彩、风格、类型等,那么则有理由相信,一个对象在每一个“维度”上的分布也是符合正态分布的。即“潜空间”是一个符合正态分布的空间,那么从这个样本空间进行采样时,也就更容易得到一个有代表意义的点。

    2.2 VAE 的主要思想

    如何使用“神经网络”生成“逼真”的数据(例如图片、视频等)呢?在机器学习领域,一个比较自然的探索是从“autoencoder”架构去做一些尝试:“autoencoder” 由一个确定的encoder 将输入压缩到一个低维的“潜编码”(latent code)空间中,然后再由decoder根据“latent code”重建数据。

    如右图所示,对于这样的神经网络设计其优势是非常明显的,这可以是非常好的“无监督学习”的神经网络,对比最终输出和输入的数据即可以作为损失函数。

    但,在数据生成(例如新的图像生成)上,这样的设计在实践中也有着非常明显的限制:即对于如果随机选取一个“latent code”,通常只能生成一些无意义的数据。

    2013年的 “Variational Autoencoder (VAE)” (Kingma and Welling, 2013) 架构则尝试解决这个问题:将“潜空间”(latent space)设计为某个符合某个概率分布结构。终于,可以生成出色的、逼真的数据(例如“图像”)。

    上述的描述可能还是比较抽象的,下面将实现一个基础的“VAE”模型,从而观测该模型的各个模块。

    3. 一个简单的 VAE 模型的观测

    3.1 模型架构概述

    这里实现了(主要靠Gemini)一个由多个全连接层构成 VAE 模型。输入是 MNIST 数据集,输入层维度是 \(28 \times 28 = 784 \),中间的潜空间(Latent Space)维度为 20 ,包括 20 个均值和20个方差,然后是一个基本对称的decoder,包括一个400个神经元的全连接层和一个784个神经元的输出层,手绘描述如右图。

    相关的代码实现参考:VAE-my-practice.ipynb

    这个神经网络结构之简单、生成效果之惊人,我自己是有点被震惊了的,相比于Autoencoder ,这里将“潜空间”限制在了某个概率分布之中,更为具体的是将每一个样本都“尝试”映射到正态分布中(注:\(p(z|x) \)是正态分布)。这里的“尝试”的做法是,先让神经网络输出“均值”和“方差”,然后再做一次重参数化的“采样”(Sampling)得到 \(z \)。

    说明:\(p(z|x) \)是正态分布;从模型架构上看,\(p(z) \) 似乎并不是正态分布,但最终由训练的目标函数将\(p(z) \) “拉向”一个正态分布。

    3.2 模型效果观测:某个点附近的数据

    这里使用上述模型,将 MNIST 数据集中的一张原始图片计算出对应的正态分布,即均值和方差,然后使用该均值和方差进行了 11 一次采样,再生成 11 张图片,可以看到这些生成的图片和原始图片有非常高的相似度,同时又与原始图片不同。

    即达到了生成“逼真”图片的效果。这种实现范式,展现出了非常强的图片生成潜力。也是后续 diffusion 模型的重要基础之一。

    再从 \(z \sim \mathcal{N}(0,I) \) 随机采样一些值,再看看这些随机生成的效果,如右图。

    可以看到,基本上能够生成一些有意义的图。这里只是使用了一些全连接层,即可以产生非常好的效果,可以预期,如果使用卷积神经网络,是可以有更好的效果的。

    3.3 模型效果观测:空间中交接处的情况

    从构建直觉的角度我们可以这样理解,encoder 层将原始的“向量空间”映射到了一个“潜空间”(latent space),该潜空间中的数据符合标准正态分布。所以,这就使得我们在“潜空间”变量 \(z \) 中做一个标准正态分布的采样,就可以使用 decoder 生成一个有意义的图片。并且,从构建直觉的角度,我们可以认为decoder 将正态分布中的点映射到“10个”数字的“空间”(784维)中,甚至可以认为(直觉角度)目标空间中有一片“连续”的空间即为“像空间”,这些空间某种程度可以“聚类”为“10类”,这些聚类的边缘则是一些介于不同数字之间的难以辨认的数字。

    例如,我们考虑这样观测这些“聚类”中数字“1”和数字“0”之间的图像。我们先使用encoder计算(找一张图片作为输入)出数字“1”、“0”对应的均值,并以此作为对应数字在“潜空间”中的中心(更为严格的可以多计算一些相同的数字,再计算均值),然后在“1”、“1”对应的质心之间进行插值,然后观测这些“插值”经过encoder后的图片。

    在放大33% ~ 66% 这个关键阶段:

    3.4 模型小结

    如上所述,encoder 最终把“原始的数据”映射到一个“潜空间”(latent space),“潜空间”中的数据(在线性代数中类似的概念称为“像空间”)符合正态分布。从从构建直觉角度我们可以有如下的一些理解和疑问。

    VAE 模型尝试把“潜空间”限制在一个非常小区域,即以“原点”为中心的一个小“球”中(想想 3 \(\sigma \) 内就可能覆盖了 99.73% 的点),更为准确的是一片标准正态分布的概率云当中。问题:这种把“像空间”限制在小区域内,是否是实现“逼真生成”的关键因素?尝试回答:像空间的大小也许并不是关键的,毕竟,从数学角度来看,单位“球”可能与整个空间是一样大的,只是看起来小了而已;那么,关键可能在于把“像空间”现在在了某种结构上,这里的结构是标准正态分布空间。问题:为什么把“像空间”限制在某种结构上,就能够达到这样的效果呢?

    在上述示例的“概率云”中,所有的点并不是“对等的”,概率密度相等的点,其意义也有着非常大的区别。从直觉的角度,例如最某个数字1的点 \(z_1 \in z \),在点 \(z_1 \) 附近的点则映射到的“像”也是更为接近的与数字“1”的点(这里的 \(z_1 = q_{\theta}(x) \quad \text{where} \, x \in \text{Samples} \))。

    4. VAE 实现的一些细节

    4.1 VAE的训练目标

    VAE 模型将潜空间限制在了一个“正态分布”之中,通过在正态分布中采样的方式用来生成新的图片。这个过程并不是很好理解,这里介绍一下 VAE 的数学建模思路。

    模型的训练目标(ELBO):

    $$
    \min_{\theta,\phi} \mathbb{E}_{q_{\theta}(z|x)}[\frac{1}{2\sigma^2}|| x-\mu_{\phi}(z) ||^2] + \mathcal{D}_{KL}(q_{\theta}(z|x)||p(z) ) \tag{1}
    $$

    关于这个式子的完整的数学推导,在网上比较好找到,有一些复杂、也不是很好理解。但是,这个式子的意义却比较明确:

    • 前一部分,对于某个输入 \(x \),需要神经网络的参数 \(\theta , \phi \) 生成的图像\(x’ \)能够与原始图像接近
    • 后半部分,则让神经网络参数\(\theta \) 是的分布 \(q_{\theta}(z|x) \) 靠近 \(p(z) \)即标准正态分布

    4.2 VAE的数学模型

    理解 VAE 比较困难的大概是这背后的数学模型,而理解了这个“数学模型”才可能进一步理解上述的公式,以及更多的VAE以及后续生成模型的思想。所以这里总结一些我对这个“数学模型”的理解:

    Encoder 部分 \(q_{\theta} \) 是将一个图片 \(x \) 映射到一个概率分布(而不是某个具体的值),通常是 \(\mathcal{N}(\mu,\sigma^2) \)。那么,在神经网络中,如何将一个具体的样本/数据 \(x \) 映射到一个概率分布呢?是这样的:神经网络则需要输出/拟合/回归出该分布的关键参数即可,例如对于正态分布只需要给均值和方差即可。这也是为什么在上述的“示例VAE”中 Encoder 部分输出即为分布 \(z \sim p(z) \) 的均值与方差。那么,要从潜空间 \(z \) 中取一个值(即采样一个值)的时候,则需要“随机”的做一次生成,这里的“随机”则使用了“重参数化”技巧进行处理从而解决训练时向后传播的随机性问题。更为一般的即:$$ q_{\theta}(z|x) := \mathcal{N}(z;\mu_{\theta}(x),\text{diag}(\sigma_{\theta}^2(x))) \tag{2} $$

    Decoder 部分 \(p_{\phi} \) 则是将潜空间的采样 \(z \) 映射到一个概率分布,而不是一个具体的值,但这里可能感觉是一个具体指的原因是,通常输出就是这个分布的均值(或期望),而不再另外进行采样操作。在常见的 Gaussian VAE 中,更为一般的有:

    $$ p_{\phi}(x|z) := \mathcal{N}(x;\mu_{\phi}(z),\sigma^2 I) \tag{3} $$

    最后,有了这两个定义公式(2)、(3),就可以计算上述的损失函数公式(1)了。

    5. VAE 中的数学推导

    这大概是最难理解的部分,甚至注意到很多领域内的专家(更加注重实践能力),对于这部分也很头疼,这里做一些尝试。

    5.1 VAE 基本数学概率模型与训练目标

    我们有一个上述的 VAE 模型,其中:

    • \(\theta \) 为encoder部分参数,\(\phi \) 为decoder 的参数
    • \(p_{\theta}(z|x) \) 表示对于给定的输入 \(x \),encoder 将输入映射为分布\(p_{\theta}(z|x) \) ,通常按照上述的公式(2)定义
    • \(p_{\phi}(x|z) \) 表示对于给定的潜空间采样值 \(z \),decoder 将其映射为分布\(p_{\phi}(x|z) \) ,通常按照上述的公式(3)定义

    在数学形式上,就可以有目标端(神经网络的输出)\(x \) 的分布:

    $$
    p_{\phi}(x) = \int p_{\phi}(x|z)p(z)dz
    $$

    这是一个形式上的表达,并不能直接计算,这是因为 \(p_{\phi}(x|z) \) 是一个由神经网络定义的映射或分布,对于每一个 \(z \),并没有一个简单的表达式给出\(p_{\phi}(x|z) \)的形式,或者更为直接的,在公式(3)中的 \(\mu_{\phi}(z) \) 由神经网络定义,并没有数学形式的表达。

    这个式子确实不可计算,在实际训练中,则做了一些数学“推导”,训练另一个与此相关的式子(ELBO),这个我们后续再看。

    所以,对于 VAE 模型的训练,即需要最大化:

    $$
    \max_{\phi}\sum_\limits{i} \log p_{\phi}(x_i) \quad \forall x_i \in \text{Sample Data} \tag{4}
    $$

    即对于最终的概率分布(PDF)\(p_{\phi}(x) \),任何的真实的图像(对应的 \(\forall x \in {\text{data}} \))所对应的密度值取值都需要很大,才能最终使得上述式子取值最大。

    这似乎还比较抽象,这里我们进一步解释一下上述的式子(4)。考虑真实分布为 \(p_{\text{data}}(x) \),通常我们可以使用 KL 散度衡量两个分布的距离,那么,就需要最小化这个散度值:

    $$
    \begin{aligned}
    \mathcal{D}_{\text{KL}} &= \int p_{\text{data}}(x) \log \frac{p_{\text{data}}(x)}{ p_{\phi}(x)}dx \\[0.8em]
    &= \int p_{\text{data}}(x) \log p_{\text{data}}(x)dx – \int p_{\text{data}}(x) \log p_{\phi}(x)dx
    \end{aligned}
    $$

    上述式子中的前项是一个常数(认为真实世界的分布是确定的,是拟合的目标,其实就是这个分布的“熵”),那么要这个散度值最小,就是让上述式子中后一项最大,即 \(\int p_{\text{data}}(x)p_{\phi}(x)dx \) 最大;在离散场景,积分则变成求和,并且 \(p_{\text{data}}(x) \)通常不可知,则通常使用\(\frac{1}{N} \)替代或者直接忽略,即有上面的式子(4)。

    5.2 使用 ELBO 对上述目标进行近似

    关于“Evidence Lower Bound” (简称 ELBO )虽然网上有很多的说明,但总是不够细致(或者要求对概率论有非常深的理解),这里做一个最为详细的推导和说明如下(应该是互联网上能够找到的最为详细的说明了)。

    ELBO的定义为:

    $$
    \mathcal{L}_{\text{ELBO}}
    =
    \underbrace{
    \mathbb{E}_{z \sim q_\theta(z|x)}
    \left[ \log p_\phi(x|z) \right]
    }_{\text{Reconstruction Term}}

    \underbrace{
    D_{\mathrm{KL}}\!\left( q_\theta(z|x) \,\|\, p(z) \right)
    }_{\text{Latent Regularization}}
    $$

    可以通过严格的证明,如下不等式成立:

    $$
    \begin{equation}
    \log p_\phi(x) \ge \mathcal{L}_{\text{ELBO}}(\theta, \phi; x)
    \end{equation}
    $$

    证明如下:

    $$
    \begin{aligned}
    \log p_\phi(x) &\stackrel{(1)}{=} \log \int p_\phi(x, z) \, dz \\[0.8em]
    &\stackrel{(2)}{=} \log \int q_\theta(z|x) \frac{p_\phi(x, z)}{q_\theta(z|x)} \, dz \\[0.8em]
    &\stackrel{(3)}{=} \log \mathbb{E}_{z \sim q_\theta(z|x)} \left[ \frac{p_\phi(x, z)}{q_\theta(z|x)} \right] \\[0.8em]
    &\stackrel{(4)}{\geq} \mathbb{E}_{z \sim q_\theta(z|x)} \left[ \log \frac{p_\phi(x, z)}{q_\theta(z|x)} \right] \\[0.8em]
    &\stackrel{(5)}{=} \mathbb{E}_{z \sim q_\theta(z|x)} \left[ \log \frac{p_\phi(x, z) p(z)}{q_\theta(z|x)p(z)} \right] \\[0.8em]
    &\stackrel{(6)}{=} \mathbb{E}_{z \sim q_\theta(z|x) p(z)} \left[ \log \frac{p_{\phi}(x|z)p(z)}{q_\theta(z|x)} \right] \\[0.8em]
    &\stackrel{(7)}{=} \mathbb{E}_{z \sim q_\theta(z|x)} \left[ \log {p_{\phi}(x|z)} – \log { \frac{q_\theta(z|x)}{p(z)} } \right] \\[0.8em]
    &\stackrel{(8)}{=} \mathbb{E}_{z \sim q_\theta(z|x)} \left[ \log {p_{\phi}(x|z)} \right] – \mathbb{E}_{z \sim q_\theta(z|x)} \left[ \log { \frac{q_\theta(z|x)}{p(z)} } \right] \\[0.8em]
    &\stackrel{(9)}{=} \mathbb{E}_{z \sim q_\theta(z|x)} \left[ \log {p_{\phi}(x|z)}] – \mathcal{D}_{\mathrm{KL}}(q_\theta(z|x)||p(z)) \right] \\[0.8em]
    \end{aligned}
    $$

    各步骤的说明:

    • 步骤 (1) 为边缘概率公式
    • 步骤(2) 分子、分母都乘以了 \(q_\theta(z|x) \)
    • 步骤(3) 使用期望的形式改写上述公式(可以想一想连续形式的概率期望计算),这一步是为了转化为下一步可以使用 “Jensen” 不等式
    • 步骤(4) 这里使用 “Jensen” 不等式,对于凹函数 \(\varphi \)有 \(\varphi(\mathbb{E}[X]) \geq \mathbb{E}[\varphi(X)] \) ,函数 \(f(x) = \log (x) \) 即为凹函数
    • 步骤(5) 分子、分母都乘以 \(p(z) \)
    • 步骤(6) 使用基础的条件概率公式 \(p_{\phi}(x|z)p(z) = p_{\phi}(x,z) \)
    • 步骤(7) 使用对数函数基本性质 \(\log \frac{ab}{c} = \log {a} – \log \frac{c}{b} \)
    • 步骤(8) 使用期望公式的线性特性 \(\mathbb{E}[X+Y] = \mathbb{E}[X] + \mathbb{E}[Y] \)
    • 步骤(9) 使用了KL散度的定义,简单的说,即概率密度对数的差的期望即为散度

    步骤(9) 也最终与前述的 \(\mathcal{L}_{\mathrm{ELBO}} \) 定义完全相同,故得证。

  • 最近 midjourney 用的比较多,很好奇,这背后的技术大概是怎样的。好了,那就去做一些了解吧(补充注:后来我才发现这个坑有多大…)。

    除了大语言模型之外,另一个非常活跃的领域就是图片处理技术,例如文生图(Text-to-Image)、多模态模型等。除了在原有的 CNN 技术架构上,也出现了很多新的突破。虽然同样使用的是深度神经网络,但图像生成技术与大语言模型技术(LLM)是非常不同的。其模型架构不同 ,底层的数学、物理原理也非常不一样。在对大语言模型有一个框架性的了解 之后,现在打算开一个新的“坑”,当然,也就没有打算爬出来,可能就“浅尝辄止”做个最为基础的了解。一般学东西,都是从 “Why/How/What” 的顺序,但在尝试了一下,无法理解“Why”后,于是就打算绕道,看看 How 和 What 了。那么,“What”打算就从“Foward Diffusion Process”开始吧。

    整个文生图(Text-to-Image)的架构是比较复杂的,其中较为“简单”、基础 的一步即位“Forward Diffusion Process”,本文从“Denoising Diffusion Probabilistic Models”论文中为例,说明这个过程。

    1. 概述:“FDP” 是一个加随机噪声的过程

    很多地方都会说:“Forward Diffusion Process” 就是一个添加随机噪声的过程。这话对、也不对。首先,“FDP” 确实是一个添加随机噪声的过程,但是这个随机噪声添加得非常有“讲究”。我们就从“添加随机噪声”和“讲究”两个角度去介绍。

    “Forward Diffusion Process” 过程的数据是用于训练神经网络(通常是一个U-Net架构)的参数的,从而最终实现其逆过程(即“Reverse Diffusion Process”),而生成图片。

    1.1 添加随机噪声

    “FDP” 做如下的事情:

    • 先读取一张图片(这里使用的一张博客图片)
    • 将图片的每个像素点取值,随机“迭代加上”一个随机值 \(N(0,1) \)

    例如,我们使用论文 DDPM 中的设定对如下图片添加噪声,就可以观察到如下过程:

    更为详细的:

    • 1. 先读取一张图片,并将其 RGB 通道的数据读取出来
    • 2. 将像素值从 [0, 255] 的整数缩放到 [0.0, 1.0] 的浮点数
    • 3. 每个通道每个点随机“迭代加上”一个随机值,按照 \(N(0,1) \) 分布生成该随机值

    2. “线性”添加噪声

    Denoising Diffusion Probabilistic Models 论文中使用了“线性调度”的方式添加噪声。即添加噪声的强度“线性”的逐渐增强,这里的“线性”是指增加的噪声的“方差”线性增加。

    先用更加形式化的数学语言描述上述的噪声添加,即:

    $$ x_t = A x_{t-1} + B \epsilon \quad \text{where} \quad \epsilon \sim \mathcal{N}(0,1) $$

    2.1 调整权重系数

    直觉上,可以这样理解,在添加噪声的过程中,刚开始是清晰图片,所以噪声添加的较少,而后,随着图片变得模糊,也逐步增加了添加噪声的强度(方差)。

    论文中 “线性调度” 模式做了如下设计:

    $$
    x_t = \sqrt{1 – \beta_t} x_{t-1} + \sqrt{\beta_t} \epsilon, \quad \epsilon \sim \mathcal{N}(0, \mathbf{I}) \tag{1}
    $$

    这里的 \(\beta_t \) 是一个随着时间序列推进逐渐增大的值,从而在迭代过程中(或者说这个马尔科夫链中),逐步增加噪声在图片中的影响。论文中,\(\beta_t \) 是一个线性变换的序列,从 \(10^{-4} \),通过1000步迭代,增加到\(0.02 \)。

    2.2 计算的“简化”

    上述的“公式(1)”是一个迭代计算的“数列”(或者说是“马尔科夫链”),在实际的计算中,会经常使用如下的“通项公式”计算上述的迭代“数列”:

    $$
    x_t = \sqrt{\bar{\alpha}_t} x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon \quad \epsilon \sim \mathcal{N}(0,1) \\
    \text{Where} \quad \alpha_t := 1-\beta_t ,\, \bar{\alpha}_t = \prod_{s=1}^{t}\alpha_s \tag{2}
    $$

    关于详细的如何从“公式(1)”严格的推导到上述表达式(2),参考本文小结“4.5 “调度公式”的推导”。

    上述的表达式,在论文中出现的形式则是:

    $$
    q(x_t|x_0) = \mathcal{N}(x_t; \sqrt{\bar{\alpha}}x_0,(1-\bar{\alpha_t}\mathbf{I}) ) \tag{3}
    $$

    这里的“公式(2)、(3)”所表达的意思是等价的。简单的说明:

    • (a) \(\mathcal{N}(x; \mu \, , \sigma^2) \) 表示正态分布的随机变量 \(x \),均值为\(\mu \) 方差为 \(\sigma^2 \)
    • (b) 上述的表达式中的 \(I \) 表示单位矩阵。这是因为公式中的 \(x_t \) 是一个表示所有像素值的向量(例如,128×128的向量,即可能有16384个随机变量),\(I \) 表示协方差矩阵是一个对角矩阵,即所有随机变量都是完全独立的。

    3. 代码实现

    完整的代码参考:Forward-Diffusion-Process.ipynb.ipynb

    3.1 读取并预处理图片

    该函数输入原始图片、迭代次数、初始噪声,即 (x_0, t,noise) 。原始图片在读取后,需要做几个处理:

    • 统一 Resize 到 128 x 128 像素来出来
    • 按 RGB 三通道转成一个 1 x 3 x 128 x 128 的张量/数组
    • 像素值从 [0, 255] 的整数缩放到 [0.0, 1.0] 的浮点数

    对应代码:

    # 3. 加载测试图片
    url = "https://www.orczhou.com/wp-content/uploads/2025/12/IMG_1710-scaled.jpg"
    img = Image.open(requests.get(url, stream=True).raw).convert("RGB")
    transform = transforms.Compose([transforms.Resize((128, 128)), transforms.ToTensor()])
    x_0 = transform(img).unsqueeze(0) # 变为 (1, 3, 128, 128)

    3.2 生成随机噪声

    生成一个随机按 \(N(0,1) \) 分布的对象,与上述图片相同,即:1 x 3 x 128 x 128

    noise = torch.randn_like(x_0) # 采样纯噪声 epsilon

    3.3 原始图片叠加噪声

    噪声的叠加并不是简单的直接相加( \(x_0 + \text{noise} \) ),而是一个迭代式的,并考虑原始图片影响的方式(线性采样考虑):

    $$
    \begin{aligned}
    x_t &= \sqrt{1 – \beta_t} x_{t-1} + \sqrt{\beta_t} \epsilon, \quad \epsilon \sim \mathcal{N}(0, \mathbf{I}) \\[0.5em]
    x_t &= \sqrt{\bar{\alpha}} x_0 + \sqrt{1-\bar{\alpha}}\epsilon
    \end{aligned}
    $$

    4. 公式(1)的说明

    我们再来看看 “公式(1)” 的设计:

    $$
    x_t = \sqrt{1 – \beta_t} x_{t-1} + \sqrt{\beta_t} \epsilon, \quad \epsilon \sim \mathcal{N}(0, \mathbf{I}) \tag{1}
    $$

    为什么不用最为直观、自然的 \(1-\beta_t \) 与 \(\beta_t \) 作为上述表达式中的系数,而是使用了他们的平方根?

    这个“设计”思路的根源是因为:高斯分布乘以一个常数后,其方差则为该常数的平方再乘以原来的方差。即: \(X \sim \mathcal{N}(\mu,\sigma^2) \),那么 \(aX \sim \mathcal{N}(a\mu,a^2\sigma^2) \)。

    整体上,考虑是希望在传播过程中,方差不要偏离太大。并且随着时间的推进,最终的数值是一个标准正态分布的,如果来看推迟到出来的“公式3”:

    $$
    q(x_t|x_0) = \mathcal{N}(x_t; \sqrt{\bar{\alpha}}x_0,(1-\bar{\alpha_t})\mathbf{I}) \tag{3}
    $$

    可以看到在这样的“设计”(即使用“根号”)下,最终迭代的\(x_t \) 的均值是 \(\sqrt{\bar{\alpha}}x_0 \),方差为 \(1-\bar{\alpha_t} \),随着\(t \)的增加,就逐步趋向于 \(\mathcal{N}(0,1) \)了。

    5. 一些数学公式与推导

    Diffusion 相关的数学基础还是非常、非常复杂的,而这里的公式推导看起来虽然有点复杂,但可能是整个Diffusion模型的数学基础中最为简单的部分了。这里,勉强祭出右边的图片。

    我们这里还是来做一些尝试吧。

    5.1 Forward Diffusion Process 公式

    $$x_t = \sqrt{1 – \beta_t} x_{t-1} + \sqrt{\beta_t} \epsilon, \quad \epsilon \sim \mathcal{N}(0, \mathbf{I}) \tag{1} $$

    这个公式本身已经有一定的复杂度了,要搞清楚大概需要关注如下点:

    • 这里的 \(\epsilon \) 是什么意思
    • \(\beta_t \) 的计算设计
    • 从“迭代公式(1)”到“通项公式(2)”

    5.2 唬人的 \(\epsilon \)

    多维变量的概率已经忘得差不多了…,好在这里是一些完全“独立”的随机变量,还比较好理解。我们先看看这里的: \(\epsilon \sim \mathcal{N}(0, \mathbf{I}) \)。

    第一次看到这些个符号的时候,也是被“怔”了一下的,仔细一看还好。

    首先,这里公式中的 \(x_t \) 是一张图片所有的像素信息,例如,如果是一张 128×128 的图片,那么所有的像素信息则是一个长度为 1 x 3 x 128 x 128 的向量。即,\(x_t \) 是一个 3 x 128 x 128 的向量。

    对应的,这里的 \(\epsilon \) 也是一个这样的向量(例如, 3 x 128 x 128 的向量,而不是数学中常见的表示一个很小的值),可以这样理解,这个向量的每一个取值都是一个随机变量(例如一共 3 x 128 x 128 个随机变量),每一个随机变量都是独立的,即协方差矩阵为单位矩阵(这里的 \(\mathbf{I} \)),并且每个随机变量符合标准正态分布,即 \(\mathcal{N}(0, 1) \)。

    再回头看看原公式,是不是简单了很多:

    $$x_t = \sqrt{1 – \beta_t} x_{t-1} + \sqrt{\beta_t} \epsilon, \quad \epsilon \sim \mathcal{N}(0, \mathbf{I}) $$

    5.3 线性调度

    再来看公式中的 \(\beta_{t} \):

    $$x_t = \sqrt{1 – \beta_t} x_{t-1} + \sqrt{\beta_t} \epsilon, \quad \epsilon \sim \mathcal{N}(0, \mathbf{I}) $$

    在原始的 Denoising Diffusion Probabilistic Models 论文中,取值如下:\( \beta_1 = 10^{-4} ,\, \beta_{T} = 0.02 \),即均匀线性的在1000次噪声添加中,\(\beta \)均匀的从\(0.0001 \) 增长到 \(0.02 \),即:

    $$ \beta_1 = 0.0001, \beta_2 = 0.0001199, \beta_3 = 0.0001398 , … , \beta_{1000} = 0.02 $$

    在 Python 中就是如下代码:

    T = 1000  # 总步数
    betas = torch.linspace(0.0001, 0.02, T) # 线性调度:噪声方差逐渐增大

    5.4 “线性调度”的真实计算式

    但是,在实际的运算不会用上面的公式。而是,使用了这个版本的推导,从而更加高效,更加直觉,同时,看起来更加“抽象”,即在论文中的如下公式:

    $$ q(x_t|x_0) = \mathcal{N}(x_t; \sqrt{\bar{\alpha}}x_0,(1-\bar{\alpha_t}\mathbf{I}) ) \tag{3}$$

    这个版本看起来就很“唬人” ,但理解了其意思还是感觉比较“简洁”的,再理解之后,就觉得也还比较“简单”。

    本质上,这公式是前面“公式(1)”的推导与快速计算版本,该公式提供了一个无需迭代计算,而直接根据\(x_0 \) 计算 \(x_t \) 的方法。其中的 \(\alpha_t := 1-\beta_t ,\, \bar{\alpha}_t = \prod_{s=1}^{t}\alpha_s \) 。

    5.5 “调度公式”的推导

    这里来尝试做一下“公式(1)”到“公式(2)”的推导,尝试理解以下研究者们这部分工作(也可以参考这里:Diffusion Models: A Mathematical Introduction的第14页)。

    $$
    \begin{aligned}
    x_t &= \sqrt{1 – \beta_t} x_{t-1} + \sqrt{\beta_t} \epsilon \\[0.5em]
    x_{t-1} &= \sqrt{1 – \beta_{t-1}} x_{t-2} + \sqrt{\beta_{t-1}} \epsilon \\[0.5em]
    x_{t-2} &= \sqrt{1 – \beta_{t-2}} x_{t-3} + \sqrt{\beta_{t-2}} \epsilon \\[0.5em]
    \quad &\vdots & \\[0.5em]
    x_{1} &= \sqrt{1 – \beta_{1}} x_{0} + \sqrt{\beta_{1}} \epsilon \\[0.5em]
    \end{aligned}
    $$

    所以:

    $$
    \begin{aligned}
    x_t &= \sqrt{1 – \beta_t} (\sqrt{1 – \beta_{t-1}} x_{t-2} + \sqrt{\beta_{t-1}} \epsilon) + \sqrt{\beta_t} \epsilon \\[0.5em]
    &= ( \sqrt{1 – \beta_t}\sqrt{1 – \beta_{t-1}} )x_{t-2} + ( \sqrt{1 – \beta_t}\sqrt{\beta_{t-1}} )\epsilon + \sqrt{\beta_t} \epsilon
    \end{aligned} \tag{4}
    $$

    接下去看看上面等式的后面两部分:

    $$ ( \sqrt{1 – \beta_t}\sqrt{\beta_{t-1}} )\epsilon + \sqrt{\beta_t} \epsilon \tag{5}$$

    这里并不是简单的加法,而是两个独立概率分布的加法:如果概率基础还在的话,就有如下的公式,两个独立的高斯分布(例如:\(X \sim \mathcal{N} (\mu_X ,\sigma^2_X ) \quad Y \sim \mathcal{N} (\mu_Y ,\sigma^2_Y ) \))的随机变量之和,其结果依旧是高斯分布,并且均值依据是两个均值的和、方差也是两个方差的和( \(X+Y \sim \mathcal{N} (\mu_X + \mu_Y ,\sigma^2_X + \sigma^2_Y ) \) )。

    注意上面的表示 \(\sqrt{\beta_t}\epsilon \) 中,\(\sqrt{\beta_t} \) 是标准差,方差即 \(\beta_t \)。

    所以上面“公式(5)”两个分布的和,依旧是高斯分布,且均值依旧是 0,方差则为 \((1-\beta_t)\beta_{t-1}+\beta_t \),即有了如下看似错误的,但是却是正确的推导:

    $$
    ( \sqrt{1 – \beta_t}\sqrt{\beta_{t-1}} )\epsilon + \sqrt{\beta_t} \epsilon = \sqrt{(1-\beta_t)\beta_{t-1}+\beta_t} \epsilon
    $$

    其实上面并不是一个一般意义的“等式”,而是表达了如下的含义:

    $$
    ( \sqrt{1 – \beta_t}\sqrt{\beta_{t-1}} )\epsilon + \sqrt{\beta_t} \epsilon \sim \mathcal{N}(0,(1-\beta_t)\beta_{t-1}+\beta_t)
    $$

    有了这里的理解,就以继续上面公式(4)的推导就非常容易有如下的结论了:

    $$
    \begin{aligned}
    x_t &= \sqrt{1 – \beta_t} (\sqrt{1 – \beta_{t-1}} x_{t-2} + \sqrt{\beta_{t-1}} \epsilon) + \sqrt{\beta_t} \epsilon \\[0.5em]
    &= ( \sqrt{1 – \beta_t}\sqrt{1 – \beta_{t-1}} )x_{t-2} + ( \sqrt{1 – \beta_t}\sqrt{\beta_{t-1}} )\epsilon + \sqrt{\beta_t} \epsilon \\[0.5em]
    &= \sqrt{\alpha_t\alpha_{t-1}}x_{t-2} + \sqrt{1-\alpha_t\alpha_{t-1}} \epsilon \\[0.5em]
    &\vdots \\[0.5em]
    &=\sqrt{\alpha_t\alpha_{t-1}\cdots\alpha_1}x_{0} + \sqrt{1-\alpha_t\alpha_{t-1}\cdots\alpha_{1}} \epsilon \\[0.5em]
    &= \sqrt{\bar{\alpha_t}}x_0 + \sqrt{1-\bar{\alpha_t}} \epsilon \\[0.5em]
    \text{where}\, \alpha_t &:= 1-\beta_t ,\, \bar{\alpha_t} = \prod_{s=1}^{t}\alpha_s
    \end{aligned}
    $$

    即有了最终的公式:

    $$
    x_t = \sqrt{\bar{\alpha_t}}x_0 + \sqrt{1-\bar{\alpha_t}} \epsilon \\
    \text{where}\, \alpha_t := 1-\beta_t ,\, \bar{\alpha_t} = \prod_{s=1}^{t}\alpha_s
    $$

    5.6 在Python中的实现

    了解了上面这些,再看这些代码就很简单了:

    T = 1000  # 总步数
    betas = torch.linspace(0.0001, 0.02, T) # 线性调度:噪声方差逐渐增大
    
    # 计算中间变量
    alphas = 1. - betas
    alphas_cumprod = torch.cumprod(alphas, axis=0) # 对应公式中的 alpha_bar

    以及最后的 Forward Diffusion Process 计算:

        sqrt_alphas_cumprod_t = torch.sqrt(alphas_cumprod[t]) # 信号系数
        sqrt_one_minus_alphas_cumprod_t = torch.sqrt(1. - alphas_cumprod[t]) # 噪声系数
    
        # 核心公式实现
        return sqrt_alphas_cumprod_t * x_0 + sqrt_one_minus_alphas_cumprod_t * noise, noise

    6. 微分方程角度的考虑

    从随机微分方程的角度考虑 “DDPM” 模型是该模型发布之后的事情了,我们这里先看看这个随机微分方程的“漂移/Drift”部分与上述迭代式子的关系。

    先不考虑“随机项”的增加,那么在设计时,希望随着时间步骤的迭代,变化的速率逐渐增加,即迁移变化慢,后期变化快,即考虑在微分方程的右侧增加一下 \(\beta(t) \),该函数随着时间增加而增加,最为常见的即为线性增长(对应于“线性调度”)。此外,该变化率应该与当前值有关,当前值越大,则变化率应该越大;并且期望最终迭代结果趋向于零(即均值最终为零的正态分布),就有最终的微分方程设计:

    $$ \frac{dx}{dt} = -\frac{1}{2}\beta(t)x \tag{5} $$

    说明:

    • 方程右侧的负号,表示总是朝着x取值相反的方向移动,即总是朝着原点方向移动
    • \(\beta(t)x \)则表达了上述的两个关于“变化率”大小的意图

    该微分方程的迭代解就有如下的迭代表达式:

    $$
    \begin{aligned}
    &x_{t} – x_{t-1} = -\frac{1}{2}\beta(t)x_{t-1} \\[0.5em]
    &x_{t} = x_{t-1} -\frac{1}{2}\beta(t)x_{t-1} \\[0.5em]
    &x_{t} = (1 -\frac{1}{2}\beta(t))x_{t-1} \\[0.5em]
    \end{aligned}
    $$

    很神奇的是,在\(\beta \)很小的时候,根据泰勒展开有:

    $$
    \sqrt{1-\beta} = 1 – \frac{1}{2}\beta – \frac{1}{8}\beta^2 + \cdots
    $$

    最终就有:

    $$
    x_{t} = \sqrt{1-\beta_t}x_{t-1}
    $$

    这里就可以从“微分方程”的角度去理解上述的 stable diffusion 中 Forward Diffusion Process 中前半部分了。

    7. 小结 FDP

    在了解 What 中,也在慢慢理解 How 以及Why 。这里再次从宏观上概述 FDP 的过程,从而跳出上述的 What 细节,再次审视这个过程。

    7.1 首先,为什么需要 “Forward Diffusion Process” ?

    简单回答:给样本添加噪声,构建训练数据。

    “Forward Diffusion Process” 过程的数据主要用于训练,对于一个给定的图片,逐步添加噪声,最后让其变成一张纯粹的、高斯分布的噪声。而这个过程的数据,则可以用于训练 U-Net 的神经网络,让该U-Net具备一个神奇的能力:即给出一张图片(带有噪声的),该 U-Net 可以预测出这张图片中有哪些是噪声。

    7.2 “Forward Diffusion Process” 操作的数学计算

    其核心公式如下:

    $$
    x_t = \sqrt{1 – \beta_t} x_{t-1} + \sqrt{\beta_t} \epsilon, \quad \epsilon \sim \mathcal{N}(0, \mathbf{I})
    $$

    经过推导,等价与如下公式(关于公式的推导:Forward Diffusion Process):

    $$
    x_t = \sqrt{\bar{\alpha_t}} x_0 + \sqrt{1-\bar{\alpha_t}}\epsilon \quad \epsilon \sim \mathcal{N}(0,1) \\
    \text{Where} \quad \alpha_t := 1-\beta_t ,\, \bar{\alpha_t} = \prod_{s=1}^{t}\alpha_s \tag{a}
    $$

    在论文中可能看到的形式:

    $$
    q(x_t|x_0) = \mathcal{N}(x_t; \sqrt{\bar{\alpha}}x_0,(1-\bar{\alpha_t}\mathbf{I}) )
    $$

    对于一张照片实际做上述操作则有如下效果:

    关于上述公式的一些重要特性:

    • 经过若干次迭代后,一张清晰的图片最终变成一张“随机”噪声图片,这里的“随机”是指的正态分布
    • 从“公式(a)”可以看到,逐步迭代和多步合并迭代有一样的效果。当然,为了获得训练数据,总是逐步迭代

  • 了解 CLIP 模型

    ·

    深度学习领域除了大语言模型之外,另一个极其活跃的领域即为视频、图像处理领域(或者叫“多模”数据)。随着 Stable Diffusion 取得成功,这个领域也在非常快速的发展。这里也打算极其粗浅的了解一下这个领域。打算,从最为基础“组件”之一 “CLIP” 开始。

    “CLIP”的全称是“Contrastive Language–Image Pre-training”,描述的是一种训练方法,可以把“图片”、“文字”嵌入(Embedding)到同一个空间(例如 512 维的向量空间),并保持非常好的相关性。利用这个特点,则可以非常好的进行诸如:图片相似性、文本图片相关性的人物;也可以作为其他模型的一部分,提供非常好的文本与图片一致性的关系。这里以典型的“clip-vit-base-patch32”模型为例,去简单实践并了解该模型。

    1. 实验设计

    这里选择一张图片,如右图,使用该模型对图片进行 Embedding;然后再选择一组文本,如下,使用该模型对其进行 Embedding ,然后观察这些向量之间的余弦距离关系。

    texts = [
        "a dog",
        "a cat",
        "a car",
        "a landscape",
        "a person",
        "a person behind a statue",
        "a person riding a bicycle",
        "tourist attraction",
        "a vacation"
    ]

    2. 实验数据与结果分析

    在使用模型进行Embedding后,然后把所有文本向量与图片计算余弦相似度后有右侧数据:

    可以看到,图片和文本之间表现出了非常强的相关度。相关度最高的文本为:“a person behind a statue”,也是所有文本中对图片最为准确的描述。“a person” 也比 “a person riding a bicycle” 有着更高的相似度,更比诸如 “a dog”、“a cat” 要高非常多。

    0.2707  |  a person behind a statue
    0.2508  |  tourist attraction
    0.2074  |  a person
    0.2063  |  a vacation
    0.1974  |  a person riding a bicycle
    0.1875  |  a dog
    0.1821  |  a landscape
    0.1754  |  a cat
    0.1655  |  a car

    2.1 可视化结果

    这里在一个数轴上,简单对上述结果进行可视化,从而建立一个更为直观一点的认识:

    在数轴上将上述文本与图片的相似度取值绘制出来,可以看到,“a person behind a statue”远比其他的文本相似度要高。

    3. 其他

    对于 Embedding 出来的向量,这里也使用诸如“T-SNE”、“PCA”、“UMAP”、“MDS”等方式去降维到二维空间分析,但是并没有观察余弦相似度的保持,与上述计算结果不符合。也许是点太少、也许是用法不对。这也让对诸如“T-SNE”等算法在可视化上效果,提高了警惕。关于这部分计算的数据和结果可以参考:SD-Forward-Diffusion-Process.ipynb@Colab,这里不再展示。

  • 线性代数回顾(二)

    ·

    到目前为止,前面的线性方程组的解,还有一些问题没有彻底回答(例如,解空间的描述),在回答这个问题之前,我们需要先了解一下“向量空间”。“向量空间”的严格定义是有些枯燥的,这里暂时把“向量空间”的限制为大家所熟悉的、最为典型的“ \(n \) 维欧氏空间”。

    1. 向量空间

    1.1 基

    要描述一个向量空间中的元素,则首先需要一组基(坐标)。在\(n\)维欧氏空间,最为常见的一组基,即为多个“垂直”(“正交”)的单位向量,即:

    $$ \begin{align}
    \alpha_1 &= (1,0,\dots) \\
    & \vdots \\
    \alpha_i &= (\dots,0,1,0,\dots) \\
    & \vdots \\
    \alpha_n &= (0,\dots,1)
    \end{align}
    $$

    在二维平面空间中,则为:\( \alpha_1 = (1,0) \quad \alpha_2 = (0,1)\);三维空间则为:\( \alpha_1 = (1,0,0) \quad \alpha_2 = (0,1,0) \quad \alpha_3 = (0,0,1)\)。

    既然有“正交”基,那么当然有不那么“正交”的基,而此类“基”则是更为普遍的。事实上,更为普遍的,任何 \( n \) 个线性无关的向量都可以作为向量空间的基。

    1.2 线性相关与线性无关

    考虑一组向量\( \alpha_1,\dots , \alpha_n \),如果当且仅当所有\( a_i = 0 \quad i=1,\dots,n\)时如下的等式才成立:

    $$ a_1\alpha_1 + a_2\alpha_2 + \dots + a_n\alpha_n = 0 $$

    那么,就说 这组向量 \( \alpha_1,\dots , \alpha_n \) 是线性无关的。反之,则称这组向量是线性相关的。

    或者这么说,对于一组线性无关的向量\( \alpha_1,\dots , \alpha_n \):任何一个向量都不能用剩余的向量做“线性表示”。

    1.3 一些重要的结论

    结论:设\( \{ \alpha_1,\dots , \alpha_n \}\)是向量空间\( V \)的一组基,那么\( V \)空间中的每一个向量都可以唯一的表示为这组基的线性组合。这个线性组合的系数,就叫“坐标”(注:相对于这组基)。

    结论:\( W_1 \)、\( W_2 \)是\( V \)的有限子空间,那么有:

    $$ dim(W_1+W_2) = dim(W_1) + dim(W_2) – dim(W_1\cap W_2) $$

    结论:\( n \)维向量空间中,任意\( n \)个线性无关的向量都可以取做基。

    2. 线性变换与矩阵

    2.1 概述

    “线性变换”是指向量空间中一类特殊的映射 \( \sigma : R^n \to R^m \) ,需要满足条件是:

    • \( \sigma(\xi + \eta) = \sigma(\xi) + \sigma(\eta) \)
    • \( \sigma(a\xi) = a\sigma(\xi) \)

    “线性变换” 描述了向量空间之间的映射。后续所有的内容大概都是围绕此而展开,后续所有的内容都会尝试通过各种方式将 “线性变换” 的特性研究清楚。这里写出部分结论,后面再慢慢展开:

    • 线性变换之下,原点保持不变。即 \( \sigma( \vec{0} ) = \vec{0} \)
    • 几何意义下,通常,线性变换包括了:旋转、镜像、拉伸/压缩(特别的,有时候会压缩到零)、剪切

    为了研究清楚一个线性变换上述的特点,通常需要选取一组“基”,然后使用这组“基”的“坐标”来描述空间中的点,进而再描述对应的线性变换。最为常见的基为“正交单位基”。

    从方法上来看,研究清楚“线性变换”最为关键的是研究清楚对应的“变换矩阵”。所以,“线性代数”的核心后面就变成了对矩阵特性的研究,但是,也不要忘记了初衷,否则很快就迷失了。

    2.2 线性变换

    我在大学期间对于线性变换、矩阵有什么作用,是完全没有概念的。所以对于他们的特性研究也没有掌握的很深,基本上是停留在能够把一些联系题做对这个层面。而现在,注意线性变换的广泛应用之后,尝试去理解去本质之后,就会寻根问底的去理解清楚什么是线性变换、什么是矩阵。这里再次说说我的理解。

    在一个向量空间中,最为常见的是 \(n \) 维欧氏空间,会有很多的向量,例如每个 Embedding 就可以理解是在一个线性空间中,“线性变换”表述了是空间中的一类映射,该映射满足上述“小结2.1”中的两个要求,即原点依旧到原点、映射保持所谓的“线性”(例如,向量和的映射等于映射的和等)。

    在一个向量空间中,一个线性变换就是一个符合一定条件的映射。与向量空间的基的选取是没有关系的。自此,与矩阵也是没有关系的。所以,线性变换本身是更为底层、更为基础的概念。

    2.3 欧氏空间的线性变换与矩阵

    现在我们把问题限定在 \(n \) 维欧氏空间中。那么,这时候,我们如何描述一个线性变换呢?是的,就是“基”与“矩阵”。

    通常,\(n \) 维欧氏空间,我们会先选取一组基,然后使用一个矩阵去描述这个线性变换。并且非常幸运的,一旦这组基 选定了,这个矩阵是唯一的。

    结论:在\(n \) 维欧氏空间(这个条件似乎可以去掉)中,对于线性变换 \(\sigma \),如果选定一组“基”,那么就存在唯一的“矩阵”描述该线性变换。

    上述的结论,是比较明显的。我们考虑对于线性变换中的上述选定的基向量 \(\alpha_i,\quad \text{where } i = 1,\ldots,n \),线性变换将其映射到 \(\beta_i,\quad \text{where } i = 1,\ldots,n \),那么根据“基”的基本性质,对于这里的任何 \(\beta_i \)都可以表示成\(\alpha_i \)的线性组合,所有的这些系数构成的矩阵,就是上述描述的唯一的“矩阵”。具体的:

    $$
    \begin{aligned}
    \beta_1 &= a_{11}\alpha_1 + a_{21}\alpha_2 + \cdots + a_{n1}\alpha_n \\[0.5em]
    \beta_2 &= a_{12}\alpha_1 + a_{22}\alpha_2 + \cdots + a_{n2}\alpha_n \\[0.5em]
    &\ \vdots \\[0.5em]
    \beta_n &= a_{1n}\alpha_1 + a_{2n}\alpha_2 + \cdots + a_{nn}\alpha_n
    \end{aligned}
    $$

    即:

    $$
    \begin{aligned}
    \begin{bmatrix}
    \beta_1 & \beta_2 & \cdots & \beta_n
    \end{bmatrix}
    =
    \begin{bmatrix}
    \alpha_1 & \alpha_2 & \cdots & \alpha_n
    \end{bmatrix}
    \begin{bmatrix}
    a_{11} & a_{12} & \cdots & a_{1n} \\
    a_{21} & a_{22} & \cdots & a_{2n} \\
    \vdots & \vdots & \ddots & \vdots \\
    a_{n1} & a_{n2} & \cdots & a_{nn}
    \end{bmatrix}
    \end{aligned}
    $$

    即在这个线性变换\(\sigma \) 在选定基 \(\alpha_i,\quad \text{where } i = 1,\ldots,n \) 对应的矩阵为:

    $$
    \begin{aligned}
    A =
    \begin{bmatrix}
    a_{11} & a_{12} & \cdots & a_{1n} \\
    a_{21} & a_{22} & \cdots & a_{2n} \\
    \vdots & \vdots & \ddots & \vdots \\
    a_{n1} & a_{n2} & \cdots & a_{nn}
    \end{bmatrix}
    \end{aligned}
    $$

    从这段简洁的“证明”(或“说明”)来看,我们很自然有如下结论,根据空间中的“基”的选取不同,我们会得到不同的矩阵。因为我们反复会提到,我们经常会通过研究矩阵的特性来研究线性变换。那么,同一个线性变换在不同的“基”下的不同“矩阵”,很自然的能够想到,这些“矩阵”是有某些共性的,是的,我们称这些矩阵为“相似矩阵”,相关特性,暂不展开。

    3. 特征向量与特征值

    3.1 为什么

    为什么我们需要关注“特征值与特征向量”呢?为什么我们要去了解奇异值分解(SVD)呢?

    原因是“线性变换”是一个映射,是非常抽象的。而特征值 、特征向量、SVD分解可以把线性变换最为关键的特性,以非常“直观”的形式表达出来。当然,这里的“直观”并不是简单意义上能够一眼就看出什么来,事实上,“线性变换”本身就有很强的抽象性,这里的“直观”只是相对的,是否直观,完全依赖于各位看客自己的“悟性”了 。

    特征向量的基本定义:如果有 \( \sigma(\xi) = \lambda \xi \) ,那么这里的 \( \xi \) 就是特征向量,对应的 \(\lambda \) 就是对应的特征值。

    要想真正说清楚特征向量与特征值是需要非常多篇幅的,而且关于对特征向量的理解对于理解线性变化也是非常关键的,所以,建议花些时间较为系统的做一些理解。如果你已经建立的基础概念,这里的一篇文章可能是帮助你增强一些理解:特征向量与特征值。

    3.2 关于对特征向量的理解

    完整的讨论特征向量与特征值是复杂的,这里将其限定在一些较为简单的情况,作为一个入门。我们这里考虑最为简单的情况,即对于一个 \(n \times n \)的矩阵,其秩为 \(n \),并且在计算特征值时,有 \(n \) 是不重复的实数解,即没有任何根式重根。如果,恰好 \( n = 2 \)这大概是最为简单的情况了,不过理解这种情况,再进一步拓展,则学习曲线会平滑很多。

    我们来看一个实例,在二维空间中,在标准基下,我们有如下的线性变换矩阵:

    $$
    W = \begin{bmatrix}
    2 & 1 \\
    1 & 2
    \end{bmatrix}
    $$

    根据上述特征向量特征值的定义进行求解,我们可以有如下的特征值与特征向量:

    • \( \lambda_1 = 3 \) 特征向量 \( (1,1) \)
    • \( \lambda_2 = 1 \) 特性向量 \( (-1,1) \)

    从特征向量角度理解线性变换:那么上述的矩阵A对应的线性变换 \(\sigma \) 有如下特性,在这个二维空间任何向量 \(\beta \),都可以分解(投影)为上述两个特征向量方向的向量: \(\beta_1 \,, \beta_2 \),且有:\(\beta = \beta_1 + \beta_2 \)。那么,则有:\(\sigma(\beta) = \lambda_1 \beta_1 + \lambda_2 \beta_2 \)。即,这个线性变换可以这样描述:先将任何向量沿着特征向量方向分解,然后再按照特征值的大小进行拉伸或压缩,然后再把向量合并起来。

    上述的解释,可以对照着右图去理解。特征向量分别为 \((1,1) \) 和 \((-1,1) \) ,即图中浅绿色、浅蓝色方向。该矩阵作用在向量 \((0,1) \) 上,即图中的红色向量。先将红色向量沿着浅绿色、浅蓝色方向分解,然后按照特征值进行拉伸,即图中的绿色、蓝色向量,最后合并为图中的紫色最终向量。

    上述的场景是线性变换中,最为简单的一类。而实际的线性变换,则更为复杂,可能还涉及到对于向量的旋转、镜像、剪切等变换。关于更多场景可以自己探索,或者阅读相关书籍,也可以看看这篇文章中的更多直观的例子:特征值与特征向量

    “特征向量”可以很好的帮助理解“方阵”变换,还有一类变换时非方阵的情况,通常这时候可以借助于奇异值分解的方式去理解,关于奇异值分解可以参考:奇异值分解–深度学习的数学基础

    4. 最后

    初等的线性代数核心部分大概是这些内容,出于完整性的考虑,可以再进一步了解“Jordan 块”相关的内容,从而把相关理论补充完整,这里不再详述。

    如果再回到最初的线性方程组解的问题,我们这里就可以回答最后一个问题:对于一个线性方程组,如果有解,那么所有的解空间是怎样的?

    结论:如果方程组的系数矩阵的秩为\( r \),那么解空间的维度为\( n-r \)。解空间的“基”则可以通过初等变换求得。这里不再详述。

  • 2025 云数据库年度总结

    ·

    年度总结是从前两年开始写的,鉴于考虑把“行业动态”的发布频率改为“月度”的,所以,也就同时要求自己每年都写一个数据库的年度总结了。在总结时,大家总是倾向于赋予最近一年过多的意义,但如果放在更长的时间尺度里面,可能不尽然。过去的2025年,并不是属于数据库的,而依旧是属于“AI”的,摆正这个位置后,在这个大背景下,我们再来看看数据库的发展情况吧。

    1. 数据库与 AI

    数据库与AI结合的方向包含了向量数据库、Memory、以及如何使用 AI 用好数据库。

    OceanBase 是可能是国内数据库厂商中 AI 投入最大的厂商[43],除了持续建设向量存储与搜索技术外,今年还全新发布轻量向量数据库 seekdb [41];全新发布 PowerMem[42](兼容Mem0);此外,ODC 发布 DataPilot、诊断Agent等能力。

    新的向量数据库 VexDB 正式发布,该数据库由清华研发团队,能够支持百亿千维向量数据毫秒级查询,召回准确度高,并在国际权威的 DABSTEP 非结构化数据分析测试中,VexDB 的数据代理系统以领先第二名超 10 个百分点的成绩夺冠[24]

    不约而同,在年底火山引擎 MySQL[50]、阿里云 MySQL[51]分别支持向量类型的索引与搜索能力,均使用 HNSW 索引,这非常大程度弥补了开源MySQL的弱项(仅能够进行向量存储,并不支持基于向ANN的检索算法)。

    感受上,Google 也在各种方式尝试将AI技术更好的与数据库进行结合。具体的,在数据库与 AI 能力结合上,Google发布了 (1) MCP Toolbox for Databases[19] (2) 在控制台集成了Gemini 的 Text2SQL (3) 使用 Gemini 修复 Studio 中的错误[18] (4) 将数据库访问与管理集成到 Gemini CLI 中[20](说明:使用的是MCP Toolbox)。

    阿里云数据库团队在 AI 结合上也做了大量探索,还是比较有意思的。包括尝试加速推理效率的 PolarKVCache [48];另外,Tair KVCache [56]团队似乎也在尝试通过文件系统、内存优化解决推理效率。目前,PolarKVCache 相关功能还处于内测阶段。LLM 推理过程中,在 Decoder 阶段由于KV Cache的问题,显存是巨大的瓶颈,如果通过硬件、软件结合的技术扩展缓存池的大小,可以大大增加推理的并发性,并更好的利用内存去加速推理。

    此外,阿里云 RDS PostgreSQL 现推出AI插件“rds_ai”[6];PolarDB for AI [8][9]则内置了多种机器学习算法模型、NL2SQL等能力。

    Zilliz Milvus 流行度进一步增强,GitHub Star 数量达到4万[60];Milvus 在过去一年则新增了如下的重要能力:RaBitQ 1-bit Quantization[57]、内置文本向量化[58]Decay Ranker[59]、增强了JSON支持、Geospatial Data Type 等功能。

    2. 云数据库或厂商的重要发布

    AWS RDS/Aurora 支持了 MySQL 9.4/9.5[69],PostgreSQL 18[75],InfluxDB 3 [70],SQL Server Developer Edition[71] ;Aurora DSQL 现已全面推出[72];主要的数据库实例均支持了新的 Gravition 4 实例 [73][74];Aurora DSQL正式GA[77],且支持在几秒钟内创建集群[76]; 继续使用 Zero-ETL 方式打通数据库与Redshift 、SageMaker等平台[11][13];Aurora PostgreSQL 现在支持与 Kiro powers(MCP)集成[78]

    RDS 支持了 PostgreSQL 18[61]、SQL Server 2025 企业集群版、标准版[62]。RDS PostgreSQL、MySQL推出DuckDB分析实例[63][64][65];PolarDB 发布“文档数据库”MongoDB的访问能力[66]“PolarSearch”功能[4];阿里云 RDS 发布 Supabase 托管服务,帮助客户利用 Supabase / RDS PostgreSQL 能力快速构建全栈应用 [67];基于阿里云 DTS 构建了 RDS MySQL、PostgreSQL全球多活数据库(GAD)[7][68],Zero-ETL AnalyticDB等[5]

    MySQL/veDB支持 RockDB [79],ARM 架构规格[80],Sequence Engine[81],Flashback Query[82],多节点实例[83];此外,还发布了DBCopilot[84]、NL2SQL[85]、MongoDB 8.0[86];补充细节竞争力,例如提供SSL加密传输[23]、varchar的Online DDL、提升IN谓词时的性能等[21];支持 veDB-Search 混合检索[87]

    TDSQL-C MySQL 支持了创建“分析集群”(“LibraDB 内核”)[88]、全球数据库功能[89]、二级存储功能(Serverless 版)[90]。云数据库支持了PostgreSQL 18.0[91]、通过通过 SQL 调用大模型 API(PostgreSQL版 / tencentdb_ai )[92]、SQL Server 发布全新多节点架构(一主多备) [93]。TDSQL-C MySQL 版发布多项性能优化提升全缓存、大数据集等场景性能[94]

    HeatWave MySQL 新增支持 9.2.0、8.4.4 和 8.0.41 [95]、9.3.2[97]等版本、规格 MySQL.96 [96]等。PostgreSQL 增加了对 PostgreSQL 16 、pg_cron 和 pgaudit 等支持[98];Autonomous AI Database 内支持 Select AI RAG[99]、 API for DynamoDB[100]、最新备份克隆数据库[101]、数据库恢复到特定的 SCN [102]等。

    Cloud SQL 支持了 PostgreSQL/MySQL 读取池自动扩缩容[103][104]、PostgreSQL 18[105]、MySQL 8.4[107]、AI 辅助故障排除等;AlloyDB 支持了 C4 系列(288vCPU)[106]、更新 alloydb_scann 扩展[108]、自然语言查询功能[109]、自动向量化[112];Spanner 支持了向量索引和近似最近邻 [110]、更新了优化器[111]、预过滤向量索引 [113]

    发布了“云原生”数据库HorizonDB[114][115]; 支持了Cassandra v5.0 [116]、MySQL 8.4[117]、PostgreSQL 机密计算[118]、PostgreSQL 18 正式发布[119]、Near-zero-downtime MySQL/PostgreSQL [120][121];Cosmos DB支持了全局二级索引[122]、Float16 的向量索引[123]、向量存储和搜索[124]、MCP工具[125]等。Azure DocumentDB 发布[126];SQL Server 2025 发布;Azure SQL 数据库 DiskANN 向量索引[127]

    TaurusDB 发布 PostgreSQL 引擎支持[128]、Serverless支持冷热分离能力[129]、开放弹性策略自定义[130]、支持动态脱敏[131]、增强智能DBA SQL限流[132];RDS支持MySQL 8.4[133];GeminiDB Redis 接口性能版基于存算分离架构,单分片最高可支持百万QPS[134];GaussDB 透明多写功能发布[135];GaussDB Doer 基于AI的智能助手;openGauss 发布 MCP Server[136]

    RDS 支持了数据库大版本升级[137]、线程池优化、持跨地域备份(PostgreSQL)[138];GaiaDB 支持双机房部署[139]、新增大规格[140]、一键从RDS同步[141];VectorDB 支持可视化[142]、新的CLI 支持[143]

    发布新的中文名“海扬数据库”[137];发布PowerRAG、共享存储和桌面版(2c6g环境运行)[138]、轻量向量数据库 seekdb [139];seekdb 支持嵌入式、Server两种模式;发布 PowerMem[140](兼容Mem0);ODC 发布 DataPilot、诊断Agent等能力。

    发布基于对象存储(例如:S3)的版本 “TiDB X” [144],使用对象存储天然具备的“扩展性”、快照等能力去实现一个具备“PB”级别扩展的 OLTP 数据库[145][146]

    TeleDB 荣登 TPC-DS 全球测评总榜第二(10 TB)[147]

    3. 其他

    在今年国产数据库目录新增了两个新的国产数据库[33]:大云海山数据库(He3DB for PostgreSQL)、崖山数据库。去年新增的国产数据库包括[34]:GaussDB(集中式/分布式版)、TaurusDB、KingBase、神州通用、Vastbase,分布式数据库还有 TiDB、达梦、PolarDB、GBase、虚谷、TDSQL、GoldenDB、OceanBase。整个国产数据库的目录大抵就是在这个范围内了,后续相信不会再增加太多。而这些数据库也可能就组成了未来中国10~30年、甚至更长时间的数据库基础设施的格局。

    Gartner 2025云数据库魔力象限发布[149],阿里云依旧处于 Leaders 象限、华为云在 Challengers 象限。Redis、Neo4j从 Visionaries 象限落入 Niche Players。

    阿里云 PolarDB 打破 TPC-C 记录[3][4],以更高的性能、更好的性价比超越之前OceanBase、TDSQL创下的记录。PolarDB本次使用的使用的版本为多主集群(Limitless),总计使用2340个数据节点,56,160个cores,1170个处理器。

    达梦数据库创始人冯裕才教授获2024年“CCF最高科学技术奖”[2]。获奖理由:“冯裕才教授是我国著名数据库科技工作者,长期致力于我国自主数据库的研发和产业化工作,突破了数据库共享存储集群技术,开发了达梦系列数据库,打造了国内首家数据库上市企业“达梦数据”,为推动我国基础软件产业的发展做出了重要贡献。”;达梦发布2024年度报告,该年年度营收达10.4亿,同比增长31.49%,净利润为3.6亿[148]

    清华大学李国良入选ACM Fellow,以表彰其在人机协同(human-in-the-loop)数据集成与基于学习的数据库系统领域做出的重要贡献。此外,李国良还是 openGauss 社区技术委员会主席。[7]

    4. 重要的收购与融资

    在数据库领域,收购或融资相关的事件几乎都与“AI”相关。

    MongoDB $2.2 亿收购 Voyage AI :Voyage AI 提供相比于通用模型效果更好的 Embedding 能力、以及 ReRank 模型等;这很好的补充了 MongoDB 在向量存储、搜索的能力[26][27]

    Elastic 收购 Jina AI:通过收购“Jina AI”,Elastic 公司可以快速的具备更强的向量处理技术,包括更好的向量算法、更好的 ReRank 功能,可以让 ElasticSearch 在 RAG 场景(或类似场景)有更强的竞争力[25]

    多云数据库平台服务商Tessell获 $6000万融资。此次融资将用于进一步扩大市场覆盖,并计划推出基于 AI 驱动的对话式数据库管理服务[150]

    Databricks收购Neon[28],该产品则基于云基础设施提供Serverless的PostgreSQL服务,并且提供诸如快速Branching等能力。

    Snowflake 以 $2.5 亿收购了CrunchyData[29][30],该公司提供云端企业级全托管的PostgreSQL服务,同时支持基于相关数据的分析能力;

    Supabase 今年有两次重大的融资,分别是 $2 亿美元(估值$20亿)[32]、$1 亿美元(估值$50亿)[31]。凭借在开发者中良好的采用率,并随着 “Vibe coding”的流行,该公司快速起飞,可以将其理解为未来自然语言编程的基础组件。

    IBM 110亿美元收购数据流厂商 Confluent[54],该公司创建于2014年,最初致力于 Apache Kafka 的商业化,逐步扩展为完整的实时数据流平台。

    IBM 计划收购 DataStax,该公司基于 Apache Cassandra 提供企业级的数据库存储与AI方案[151]

    MariaDB 宣布收购高可用产品 Galera Cluster。 通过此次收购,MariaDB可以向客户提供更具竞争力的企业级高可用方案;而对于 Galera 则可以获得更多可靠的客户[152]

     ClickHouse 宣布融资3.5亿美元C轮融资[153],主要用于全球化业务推进、AI与分析能力增强等方向。

    5. 开源与商业

    今年,Redis 将 SSPL 协议改成更容易被社区接受的 AGPL 协议, 并发布 8.0 版本[36]。但是,社区似乎已经开始逐步转向了Valkey,例如,Azure 已经计划于2027[15]、2028[16] 年停用Redis Enterprise与Azure Cache for Redis。Redis 在2024年将协议从 BSD 更改为了dual RSALv2+SSPLv1[37],而后多家大型公司“联合”起来fork了新的项目 Valkey 。

    开源软件和商业软件的竞争,经过了MongoDB、Redis、ElasticSearch等与云厂商的探索,逐渐形成了某些模式:主线足够强大,则可以合作分润;主线如果稍有羸弱,则可能会被分叉出新分支;或者面临着云厂商的自研。因为对于云厂商来说,这部分收入与利润,足以支撑起在该方向的投入。

    6. 相关阅读

    Databases in 2025: A Year in Review

    Andy Pavlo

    ……

    Databases in 2024: A Year in Review

    Andy Pavlo

    Jan. 01, 2025

    Databases in 2023: A Year in Review

    Andy Pavlo

    Jan. 04, 2024

    Databases in 2022: A Year in Review

    Andy Pavlo

    Dec. 31, 2022

    Databases in 2021: A Year in Review

    Andy Pavlo

    Dec. 28, 2021

    What Goes Around Comes Around… And Around…

    Michael Stonebraker, Andrew Pavlo

    June 2024

    What Goes Around Comes Around

    Michael Stonebraker, Joey Hellerstein

    2005

    参考链接