Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

DL——VAE

  • 参考文献:
    • 原始论文:Auto-Encoding Variational Bayes:

VAE整体说明

  • 变分自编码器(Variational Auto-Encoder,VAE)是一种生成式模型,在机器学习和深度学习领域有广泛应用

VAE的问题设定

  • 给定观测数据 \( \mathbf{x} \),假设其由隐变量 \( \mathbf{z} \) 生成,联合分布为 \( p_\theta(\mathbf{x}, \mathbf{z}) = p_\theta(\mathbf{x}|\mathbf{z}) p(\mathbf{z}) \),其中:
    • \( p(\mathbf{z}) \) 是隐变量的先验分布(通常为标准正态 \( \mathcal{N}(0, I) \))
    • \( p_\theta(\mathbf{x}|\mathbf{z}) \) 是生成模型(解码器),参数为 \( \theta \)
  • 目标:最大化观测数据的边际似然 \( p_\theta(\mathbf{x}) = \int p_\theta(\mathbf{x}|\mathbf{z}) p(\mathbf{z}) d\mathbf{z} \),但积分难计算

一些设想(基本推导思路,可以跳过)

  • 为了最大化概率 \(\sum_{x}\log P(x)\),可先进行如下推导:
    $$
    \begin{align}
    L&=\sum_{x}\log P(x)\\
    &=\int_{z}q(z|x)\cdot\log P(x)dz\\
    &=\int_{z}q(z|x)\cdot\log\left(\frac{p(z,x)}{p(z|x)}\right)dz\\
    &=\int_{z}q(z|x)\cdot\log\left(\frac{p(z,x)}{q(z|x)}\cdot\frac{q(z|x)}{p(z|x)}\right)dz\\
    &=\int_{z}q(z|x)\cdot\log\left(\frac{p(z,x)}{q(z|x)}\right)dz+\underbrace{\int_{z}q(z|x)\cdot\log\left(\frac{q(z|x)}{p(z|x)}\right)dz}_{KL(q(z|x)||p(z|x))\geq0}\\
    &\geq\int_{z}q(z|x)\cdot\log\left(\frac{p(z,x)}{q(z|x)}\right)dz\\
    &=\int_{z}q(z|x)\cdot\log\left(\frac{p(x|z)\cdot p(z)}{q(z|x)}\right)dz\\
    &=\underbrace{\int_{z}q(z|x)\cdot\log(p(x|z))dz}_{Entropy}+\underbrace{\int_{z}q(z|x)\cdot\log\left(\frac{p(z)}{q(z|x)}\right)dz}_{-KL(q(z|x)||p(z))}
    \end{align}
    $$
    • 上述推导说明,最大化似然函数 \(L = \sum_{x}\log P(x)\) 可变成最大化:
      $$L’ = \int_{z}q(z|x)\cdot\log(p(x|z))dz + \int_{z}q(z|x)\cdot\log\left(\frac{p(z)}{q(z|x)}\right)dz$$
      • 实际上,后续会提到 \(L’\) 就是 \(L\) 的变分下界

VAE的推导

  • 引入变分分布 \( q_\phi(\mathbf{z}|\mathbf{x}) \)(编码器),近似真实后验 \( p_\theta(\mathbf{z}|\mathbf{x}) \),参数为 \( \phi \)。通过最小化 \( q_\phi(\mathbf{z}|\mathbf{x}) \) 与 \( p_\theta(\mathbf{z}|\mathbf{x}) \) 的KL散度:
    $$
    \min_{\phi} D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p_\theta(\mathbf{z}|\mathbf{x})\right)
    $$
  • 展开KL散度:
    $$
    D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p_\theta(\mathbf{z}|\mathbf{x})\right) = \mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ \log q_\phi(\mathbf{z}|\mathbf{x}) - \log p_\theta(\mathbf{z}|\mathbf{x}) \right]
    $$
  • 利用贝叶斯公式 \( p_\theta(\mathbf{z}|\mathbf{x}) = \frac{p_\theta(\mathbf{x}|\mathbf{z}) p(\mathbf{z})}{p_\theta(\mathbf{x})} \),代入得:
    $$
    D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p_\theta(\mathbf{z}|\mathbf{x})\right) = \mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ \log q_\phi(\mathbf{z}|\mathbf{x}) - \log p_\theta(\mathbf{x}|\mathbf{z}) - \log p(\mathbf{z}) \right] + \log p_\theta(\mathbf{x})
    $$
  • 整理后得到:
    $$
    \log p_\theta(\mathbf{x}) - D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p_\theta(\mathbf{z}|\mathbf{x})\right) = \mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ \log p_\theta(\mathbf{x}|\mathbf{z}) \right] - D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p(\mathbf{z})\right)
    $$

证据下界(ELBO)

  • 证据下界(Evidence Lower Bound, ELBO),也称为变分下界(Variational Lower Bound, VLB)
  • 由于 \( D_{\text{KL} } \geq 0 \),有:
    $$
    \log p_\theta(\mathbf{x}) \geq \underbrace{\mathbb{E}_{q_\phi} \left[ \log p_\theta(\mathbf{x}|\mathbf{z}) \right] - D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p(\mathbf{z})\right)}_{\text{ELBO}(\theta, \phi)}
    $$
  • 目标转为最大化ELBO:
    $$
    \mathcal{L}(\theta, \phi; \mathbf{x}) = \mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ \log p_\theta(\mathbf{x}|\mathbf{z}) \right] - D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p(\mathbf{z})\right)
    $$
    • 此时,最大化ELBO \(\mathcal{L}(\theta, \phi; \mathbf{x})\) 就可以实现最大化原始对数似然函数目标 \(\log p_\theta(\mathbf{x})\)
    • ELBO的更多等价形式见附录

损失函数分解(ELBO包含两项)

  • 1. 重构项(Reconstruction Term)最大化 :
    $$
    \mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ \log p_\theta(\mathbf{x}|\mathbf{z}) \right]
    $$

    • 作用:鼓励解码器重建输入数据,通常用均方误差(MSE)或交叉熵实现
    • 理解:最大化\(\mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ \log p_\theta(\mathbf{x}|\mathbf{z}) \right]\)等价于上面的公式等价于:
      • 从原始数据集 \(\mathcal{D}\) 任意采样一个数据 \(\mathbf{x}_0\);
      • 经过编码器 \(q_\phi(\mathbf{z}|\mathbf{x})\) 将 \(\mathbf{x}_0\) 编码成 \(\mathbf{z}\),其中 \(\mathbf{z} \sim q_\phi(\mathbf{z}|\mathbf{x}_0)\);
      • 再经过解码器 \(p_\theta(\mathbf{x}|\mathbf{z})\) 将编码器的输出 \(\mathbf{z}\) 解码成 \(\mathbf{x}_i\)
      • 最大化 \(\log p_\theta(\mathbf{x}|\mathbf{z})\),等价于最小化 \(\mathbf{x}_i\) 和 \(\mathbf{x}_0\) 的距离(常用交叉熵损失或者MSE)
  • 2. 正则项(KL Divergence Term)最小化 :
    $$
    D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p(\mathbf{z})\right)
    $$

    • 作用:约束编码器输出接近先验分布 \( p(\mathbf{z}) \),避免过拟合
    • 理解:先验分布 \( p(\mathbf{z}) \)可以设定为任意我们方便采样的值,比如VAE中将其设定为标准正态分布 \(\mathcal{N}(0, I) \)

KL散度的闭式解

  • 假设 \( p(\mathbf{z}) = \mathcal{N}(0, I) \),且 \( q_\phi(\mathbf{z}|\mathbf{x}) = \mathcal{N}(\mu_\phi(\mathbf{x}), \sigma_\phi^2(\mathbf{x}) I) \),则KL散度有闭式解:
    $$
    D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p(\mathbf{z})\right) = -\frac{1}{2} \sum_{j=1}^J \left(1 + \log \sigma_j^2 - \mu_j^2 - \sigma_j^2\right)
    $$
    • 其中 \( J \) 是隐变量维度
    • 证明过程见附录

重参数化技巧(Reparameterization Trick)

  • 为可微分地采样 \( \mathbf{z} \sim q_\phi(\mathbf{z}|\mathbf{x}) \),令:
    $$
    \mathbf{z} = \mu_\phi(\mathbf{x}) + \sigma_\phi(\mathbf{x}) \odot \epsilon, \quad \epsilon \sim \mathcal{N}(0, I)
    $$
    • 使得梯度可回传

最终损失函数(负 ELBO)

  • 总损失函数 :
    $$
    \mathcal{L}_{\text{VAE} }(\theta, \phi; \mathbf{x}) = \underbrace{\mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ -\log p_\theta(\mathbf{x}|\mathbf{z}) \right]}_{\text{Reconstruction Loss} } + \underbrace{D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p(\mathbf{z})\right)}_{\text{KL Divergence} }
    $$
    • 重构损失采用 MSE 或交叉熵损失函数:
      $$
      \text{Reconstruction Loss} = |\mathbf{x} - \text{Decoder}(\text{Encoder}(\mathbf{x}))|_2^2
      $$
    • KL 散度闭式解(假设 \( q_\phi(\mathbf{z}|\mathbf{x}) = \mathcal{N}(\mu, \sigma^2) \)):
      $$
      D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p(\mathbf{z})\right) = - \frac{1}{2} \sum_{j=1}^J \left( 1 + \log \sigma_j^2 - \mu_j^2 - \sigma_j^2 \right)
      $$
      • 其中 \( J \) 是隐变量维度
  • 最终,VAE的最终版MSE版损失函数为:
    $$
    \begin{align}
    \mathcal{L}_{\text{VAE} }(\theta, \phi; \mathbf{x}) &= \text{Reconstruction Loss} + D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p(\mathbf{z})\right) \\
    &= |\mathbf{x} - \text{Decoder}(\text{Encoder}(\mathbf{x}))|_2^2 - \frac{1}{2} \sum_{j=1}^J \left( 1 + \log \sigma_j^2 - \mu_j^2 - \sigma_j^2 \right)
    \end{align}
    $$
  • 总体来说:VAE通过最大化ELBO,同时优化生成模型 \( p_\theta(\mathbf{x}|\mathbf{z}) \) 和推断模型 \( q_\phi(\mathbf{z}|\mathbf{x}) \),平衡了数据重建与隐变量正则化

VAE网络结构

  • 下面的网络输出对数方差(能保证方差非负),但是仍然使用 \(\sigma\),容易让人误解,此时使用 \(e^\sigma\) 表示方差,此时有 \(\sigma\) 就是对数方差(原\(\log \sigma^2\))

AE-VAE-CVAE

  • AE-VAE-CVAE结构差异:

VAE的简单代码实现

  • 代码实现如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torchvision import datasets, transforms
    import torchvision.utils as vutils
    import matplotlib.pyplot as plt

    # 定义 VAE 模型
    class VAE(nn.Module):
    def __init__(self, input_size, hidden_size=400, latent_size=20):
    super(VAE, self).__init__()

    # 编码器
    self.fc1 = nn.Linear(input_size, hidden_size)
    self.fc_mu = nn.Linear(hidden_size, latent_size)
    self.fc_logvar = nn.Linear(hidden_size, latent_size)

    # 解码器
    self.fc2 = nn.Linear(latent_size, hidden_size)
    self.fc3 = nn.Linear(hidden_size, input_size)

    def encode(self, x):
    h = torch.relu(self.fc1(x))
    return self.fc_mu(h), self.fc_logvar(h)

    def reparameterize(self, mu, logvar):
    std = torch.exp(0.5 * logvar)
    eps = torch.randn_like(std)
    return mu + eps * std

    def decode(self, z):
    h = torch.relu(self.fc2(z))
    return torch.sigmoid(self.fc3(h))

    def forward(self, x):
    mu, logvar = self.encode(x.view(-1, 784))
    z = self.reparameterize(mu, logvar)
    return self.decode(z), mu, logvar


    # 定义损失函数
    def loss_function(recon_x, x, mu, logvar):
    BCE = nn.functional.binary_cross_entropy(recon_x, x.view(-1, 784), reduction='sum')
    KLD = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
    return BCE + KLD


    # 训练函数
    def train(model, train_loader, optimizer, epoch):
    model.train()
    train_loss = 0
    for batch_idx, (data, _) in enumerate(train_loader):
    data = data.to(device)
    optimizer.zero_grad()
    recon_batch, mu, logvar = model(data)
    loss = loss_function(recon_batch, data, mu, logvar)
    loss.backward()
    train_loss += loss.item()
    optimizer.step()
    print(f'====> Epoch: {epoch} Average loss: {train_loss / len(train_loader.dataset):.4f}')


    # 生成图片函数
    def generate_image(model, device):
    model.eval()
    with torch.no_grad():
    z = torch.randn(1, 20).to(device)
    sample = model.decode(z).cpu()
    sample = sample.view(1, 1, 28, 28)
    vutils.save_image(sample, 'generated_image.png')
    plt.imshow(sample.squeeze().numpy(), cmap='gray')
    plt.show()


    # 数据加载
    transform = transforms.Compose([
    transforms.ToTensor()
    ])
    train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
    train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=128, shuffle=True)

    # 设备配置
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    # 初始化模型、优化器
    model = VAE(input_size=784).to(device)
    optimizer = optim.Adam(model.parameters(), lr=1e-3)

    # 训练模型
    num_epochs = 10
    for epoch in range(1, num_epochs + 1):
    train(model, train_loader, optimizer, epoch)

    # 生成图片
    generate_image(model, device)

附录:VAE李宏毅公式推导

  • 目标是让似然函数最大化,也就是最大化 \(\sum_x \log P(x)\),推导可得相当于最大化变分下界(Evidence Lower Bound, \(ELBO(q)\))
  • 为什么要通过求解 \(q\) 来实现似然函数最大化/ELBO最大化呢?因为优化 \(q\) 时,与 \(P(x)\) 无关,相当于最小化KL散度
  • 进一步拆解变分下界
  • 变分下界的两个部分分别可用在网络中建模,两个损失函数同时优化就是VAE * 期望部分:通过带采样的Auto-Encoder实现,损失函数为Auto-Encoder的损失函数

附录:KL散度闭市解的推导

  • 假设 \(p(z)\) 是均值为0方差为1的标准正太分布 \(N(0,I)\),所以这里KL散度本质是要尽量保证分布 \(q(z|x)\) 尽可能接近标准正太分布,使用一个关于均值和方差的损失函数可以实现
  • KL散度部分的闭市解推导,来自 苏神的科学空间:
  • 原始论文推导可见:Auto-Encoding Variational Bayes:

附录:ELBO的各种等价形式

  • 一些等价形式:一些推导中会涉及到ELBO的不同形式:
    $$
    \begin{align}
    \mathcal{L}(\theta, \phi; \mathbf{x}) &= \mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ \log p_\theta(\mathbf{x}|\mathbf{z}) \right] - D_{\text{KL} }\left(q_\phi(\mathbf{z}|\mathbf{x}) | p(\mathbf{z})\right) \\
    &= \mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ \log p_\theta(\mathbf{x}|\mathbf{z}) \right] - \mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})}\left[\frac{\log q_\phi(\mathbf{z}|\mathbf{x})}{\log p(\mathbf{z})}\right] \\
    &= \mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ \log p_\theta(\mathbf{x}|\mathbf{z}) \frac{\log p(\mathbf{z})}{\log q_\phi(\mathbf{z}|\mathbf{x})}\right] \\
    &= \mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ \log p_\theta(\mathbf{x}|\mathbf{z}) \frac{\log p(\mathbf{z})}{\log q_\phi(\mathbf{z}|\mathbf{x})}\right] \\
    &= \mathbb{E}_{q_\phi(\mathbf{z}|\mathbf{x})} \left[ \frac{\log p_\theta(\mathbf{x},\mathbf{z})}{\log q_\phi(\mathbf{z}|\mathbf{x})}\right] \\
    \end{align}
    $$

DL——VQ-VAE

  • 参考链接:
    • 原始论文:Neural Discrete Representation Learning, NeurIPS 2017, Google DeepMind
    • VQ-VAE的简明介绍:量子化自编码器:论文大部分内容参考自本博客
      • 苏神的代码实现:vq_vae_keras.py
    • 轻松理解 VQ-VAE:首个提出 codebook 机制的生成模型
    • VQVAE PyTorch 实现教程
      • 代码路径:dldemos/VQVAE

名词解释

  • VQ-VAE :Vector Quantised - Variational Auto-Encoder,用于将变量编码为离散向量,并可将离线向量恢复为原始向量
  • codebook :通常指一种将图像编码为离散向量的机制,实际上指 VQ-VAE 中的 Embedding Space(词表)
  • PixelCNN :生成离散像素的自回归模型

PixelCNN的简单介绍

  • 原始论文:Conditional Image Generation with PixelCNN Decoders, NIPS 2016, Google DeepMind
  • 假设要生成一张 \(32 \times 32 \times 3\) 的三通道图片,矩阵元素是 0-255 的整数,Pixcel将其视为长度为 3072 的句子,词表大小是 256,并用语言模型的方法自回归的生成图片
    $$
    \begin{align}
    p(\mathbf{x})=p(x_1)p(x_2|x_1)p(x_3|x_1,x_2)\dots p(x_{3n^2}|x_1,x_2,\dots,x_{3n^2-1})
    \end{align}
    $$
    • 输出类别为 256 维度
  • 自回归方法的缺点:
    • 序列太长,按照序列依次生成,生成速度慢;长程依赖不容易捕捉(不管是CNN还是RNN)
    • 像素值是有连续大小关系的(比如像素值为99时,预估为100也不是不可以,大事预估为10就不可以),但序列化的自回归方法本质是多分类建模,无法捕捉这种含义(但回过头来看,应该还好,毕竟大模型中的相似词也不需要显示建模)

AE、AVE和VQ-VAE

  • AE(Auto-Encoder) :包含一个编码器和一个解码器
    • 编码器 :将原始输入向量 \(\mathbf{x}\) 编码为一个较小的连续向量 \( \mathbf{z} = encoder(\mathbf{x}) \)
    • 解码器 :将连续向量 \(\mathbf{z}\) 解码为和原始输入差不多的向量\( \mathbf{x}’ = decoder(\mathbf{z}) \)
  • VAE(Variational Auto-Encoder) :包含一个编码器和一个解码器
    • 编码器 :将原始输入向量 \(\mathbf{x}\) 编码为一个服从标准正太分布的连续向量 \( \mathbf{z} = encoder(\mathbf{x}) \),具体实现是先用模型输出 \(\mu,\sigma^2\),再采样得到向量 \( \mathbf{z} \)
    • 解码器 :将连续向量 \(\mathbf{z}\) 解码为和原始输入差不多的向量\( \mathbf{x}’ = decoder(\mathbf{z}) \)
  • VQ-VAE(Vector Quantised Variational Auto-Encoder) :
    • 编码器 :将原始输入向量 \(\mathbf{x}\) 编码为一个较小的离散向量 \( \mathbf{z} = encoder(\mathbf{x}) \)(这里的离散向量是隐式的,是 Embedding Space 中的索引,不会直接表示出来)
    • 解码器 :将离散向量 \(\mathbf{z}\) 解码为和原始输入差不多的向量\( \mathbf{x}’ = decoder(\mathbf{z}) \)
    • 许多博主认为VQ-VAE更像是一个AE,而不是VAE,因为无法直接从已知分布中采样隐变量来生成图片,而是借助PixelCNN来实现

VQ-VAE讲解

  • VQ-VAE整体示意图如下(from Neural Discrete Representation Learning, NIPS 2017, Google DeepMind)
    • 其中 Embedding Space 是一个 \(K\times d\) 维的此表,每个向量 \(\mathbf{e}_k\) 都是 \(d\) 维向量
  • 如上图所示,从左到右,VQ-VAE的整个过程可以拆解为如下的流程:
    • 编码器 :将输入图片 \(\mathbf{x}\) 编码为 \(m\times m\) 个 \(d\) 维的连续向量,得到结果为 \(m\times m \times d\) 维,\(\mathbf{z} = z_e(\mathbf{x})\)
    • 最近邻匹配 :用 \(\mathbf{z}\) 在 Embedding Space 词表中通过最近邻匹配 ,将 \(\mathbf{z}\) 中的每个向量都映射为 Embedding Space 中的最近邻点(nearest point \(e_{k^*}\),匹配后得到的结果使用 \(z_q(\mathbf{x})\) 表示
      $$ z_q(\mathbf{x})_{i,j} = e_{k^*} = \mathop{\arg\min}_{e_k} \Vert \mathbf{z}_{i,j} - \mathbf{e}_k \Vert_2 $$
      • 最近邻搜索隐含离散的思想 :实际上,最近邻的匹配过程可以看做是,先将连续向量转化为一个离散值(索引 \(k^*\)),此时得到的是一个 \(m\times m\) 的离散矩阵,称为离散编码或离散隐变量(discrete latents),然后再将离散索引值转换为一个连续向量(索引 \(k^*\) 在 Embedding Space 中抽取向量即可)的过程
    • 解码器 :将最近邻搜索的映射结果 \(z_q(\mathbf{x})\) 解码为原始输入大小的向量 \(\hat{\mathbf{x}}\),试图重构原始输入 \(\hat{\mathbf{x}} = decoder(z_q(\mathbf{x}))\)

补充知识:Straight-Through Estimator

  • 关于 Straight-Through Estimator 的其他说明:

    from VQ-VAE的简明介绍:量子化自编码器
    VQ-VAE使用了一个很精巧也很直接的方法,称为Straight-Through Estimator,你也可以称之为“直通估计”,它最早源于Benjio的论文《Estimating or Propagating Gradients Through Stochastic Neurons for Conditional Computation》,在VQ-VAE原论文中也是直接抛出这篇论文而没有做什么讲解。但事实上直接读这篇原始论文是一个很不友好的选择,还不如直接读源代码
    事实上Straight-Through的思想很简单,就是前向传播的时候可以用想要的变量(哪怕不可导),而反向传播的时候,用你自己为它所设计的梯度

  • 博客中给了例子来说明,举例来说,设计一个如下的目标函数:
    $$ Loss = \Vert x - decoder(z + sg[z_q - z]) \Vert_2^2 $$
    • 其中 \(sg[\cdot]\) 表示 stop gradient 的含义
    • 前向传播时,上面的损失函数等价于 \(\Vert x - decoder(z_q) \Vert_2^2\)
    • 后向传播时,上面的损失函数等价于 \(\Vert x - decoder(z) \Vert_2^2\)
    • 同理,我们可以任意定义函数的梯度(实现前向和后向不一致)

VQ-VAE的损失函数

重建误差

  • 直接优化下面的重建误差是不行的,因为最近邻匹配过程梯度不可导:
    $$ L_{reconstruct} = \Vert \mathbf{x} - decoder(z_q(\mathbf{x})) \Vert_2^2 $$
  • 作者设计了下面的重建误差:
    $$ L_{reconstruct} = \Vert \mathbf{x} - decoder(z_e(\mathbf{x}) + sg[z_q(\mathbf{x}) - z_q(\mathbf{x})]) \Vert_2^2 $$
    • 前向传播时,上面的损失函数等价于 \(\Vert \mathbf{x} - decoder(z_q(\mathbf{x})) \Vert_2^2\)
    • 后向传播时,上面的损失函数等价于 \(\Vert \mathbf{x} - decoder(z_e(\mathbf{x})) \Vert_2^2\)
    • 本质上实现了将梯度从 \(z_q(\mathbf{x})\) 全部复制给了 \(z_e(\mathbf{x})\)
    • 问题:为什么可以直接这样, \(z_q(\mathbf{x})\) 和 \(z_e(\mathbf{x})\) 不相同,可以直接这样传递梯度吗?如何理解这种传递?
      • 回答:VQ-VAE中,同时会增加损失函数,保证 \(z_q(\mathbf{x})\) 和 \(z_e(\mathbf{x})\) 足够接近

Embedding Space 优化

  • Embedding Space 优化的目标 :为了保证从 \(z_q(\mathbf{x})\) 向 \(z_e(\mathbf{x})\) 复制梯度传播是OK的,我们需要 \(z_q(\mathbf{x})\) 和 \(z_e(\mathbf{x})\) 足够接近
    $$ L_{similar} = \Vert z_e(\mathbf{x}) - z_q(\mathbf{x}) \Vert_2^2$$
  • 理论上,至此已经可以了,但是原始论文中,作者将上述的损失拆解成两个了,根据博客 VQ-VAE的简明介绍:量子化自编码器 的说法,考虑到实际上 \(z_q(\mathbf{x})\) 是相对自由的,而 \(z_e(\mathbf{x})\) 则需要保证重构效果,所以希望让 \(z_q(\mathbf{x})\) 去靠近 \(z_e(\mathbf{x})\),而不是 \(z_e(\mathbf{x})\) 去靠近 \(z_q(\mathbf{x})\),所以把上面的损失函数拆解为
    $$ L_{similar} = \beta \Vert sg[z_e(\mathbf{x})] - z_q(\mathbf{x}) \Vert_2^2 + \gamma\Vert z_e(\mathbf{x}) - sg[z_q(\mathbf{x})] \Vert_2^2$$
    • 第一项相当于固定 \(z_e(\mathbf{x})\),让 \(z_q(\mathbf{x})\) 靠近 \(z_e(\mathbf{x})\),第二项同理
    • \(\beta,\gamma\) 用于调节学习比重,文章中使用 \(\gamma = 0.25\beta\)
  • codebook的其他优化方式-滑动平均方法 :
    • 实际上,对于同一个 codebook 向量 \(e_i\),可能会作为多个 \(z_e(\mathbf{x})\) 的最近邻向量而被检索,假设 \(e_i\) 的最近邻向量共 \(n_i\) 个,组合为 \(z_{i,j}\),此时有
      $$ e_i = \frac{1}{n_i}\sum_{j=1}^{n_i} z_{i,j}$$
    • 由于小批量训练时上面的公式容易出现波动,所以可以使用滑动平均(文章中使用指数移动平均(EMA))来更新 codebook,实现 codebook的在线更新

VQ-VAE损失函数的最终形式

  • VQ-VAE损失函数的最终形式可以表示如下:
    $$ L_{vq-vae} = \Vert \mathbf{x} - decoder(z_e(\mathbf{x}) + sg[z_q(\mathbf{x}) - z_q(\mathbf{x})]) \Vert_2^2 + \beta \Vert sg[z_e(\mathbf{x})] - z_q(\mathbf{x}) \Vert_2^2 + \gamma\Vert z_e(\mathbf{x}) - sg[z_q(\mathbf{x})] \Vert_2^2 $$

附录:离散化采样编码

  • 有了VQ-VAE以后,我们已经可以将图片编码为 \(m\times m\) 的离散矩阵了,此时使用 PixelCNN 来学习编码分布,然后再利用 PixelCNN 来随机生成新的编码矩阵,再映射回到 \(z_q(\mathbf{x})\),从而可通过解码器生成图片

附录:VQ-VAE与VAE的关系讨论

  • 从VAE将图片编码为一个高斯分布,然后再重建的思路看,VQ-VAE将图片编码为一个离散分布,然后再重建,只是VQ-VAE的这个离散分布不容易采样,导致需要使用一个额外的 PixelCNN 来学习
  • 相对VAE,VQ-VAE中是没有KL散度项的,但也有人推导,博客 VQ-VAE的简明介绍:量子化自编码器 中的评论

附录:VQ-VAE-2

  • 原始论文:Generating Diverse High-Fidelity Images with VQ-VAE-2, NeurIPS 2019, Google
  • 整体结构如下:
  • VQ-VAE-2 相对 VQ-VAE 的核心改进是:

    主要变化就是把 VQ-VAE 的 encoder 和 decoder 都进行了分层, bottom层对local feature进行建模,top层采取全局自注意力机制


附录:VQ-VAE中的 Codebook Collapse 问题

  • Codebook Collapse(码本坍塌) 是 VQ-VAE(Vector Quantized Variational Autoencoder)及其变体中常见的一个问题,指的是在训练过程中 ,编码本(codebook)中的大量向量从未被使用 ,只有少数几个编码向量被频繁使用的情况
  • Codebook Collapse 问题表现 :
    • 编码向量利用率低 :大部分codebook向量在训练过程中从未被选择
    • 信息损失 :由于实际使用的编码向量远少于设计容量,模型表达能力受限
    • 重建质量下降 :有限的活跃编码向量难以充分表示输入的多样性
  • Codebook Collapse 问题产生原因 :
    • 某些编码向量可能在训练早期更容易被选择,后来跟输出越来越接近,形成”富者愈富”效应;
    • VQ的硬分配机制使得未被选择的编码向量无法获得梯度更新,从而导致某些编码向量长期得不到更新
  • Codebook Collapse 问题解决方案 :
    • 定期重置未被使用的编码向量,让这些向量有机会被更新;
    • 引入一定程度的软分配(如Soft-VQ),以一定概率匹配最近邻向量;
    • 添加鼓励codebook利用的正则项;
  • 注:Codebook 向量越多,Codebook Collapse 问题就越严重
  • Codebook collapse问题直接影响VQ-VAE的性能,解决这一问题对于提高模型表现和压缩效率至关重要

Linux——Ubuntu和Centos服务器管理


Centos

配置静态IP

  • 查看网卡名称

    1
    ifconfig
    • 查看网卡信息,每行的第一项为网卡名称
    • 一般服务器可能有多个网卡,按照需求选择一个即可
    • 网卡信息包含当前网卡的ipv4和ipv6地址,MAC地址等信息
  • 根据网卡名字打开相应网卡的配置文件

    1
    2
    # 假设网卡名称为eth0
    vi /etc/sysconfig/network-scripts/ifcfg-eth0
  • 修改相关配置内容

    1
    2
    3
    4
    5
    6
    7
    IPADDR=12.12.12.12
    NETMASK=255.255.255.0
    GATEWAY=12.12.12.1
    ONBOOT=yes
    BOOTPROTO=static
    DNS1=114.114.114.114
    DNS2=8.8.8.8
    • 等号后面是具体的ip地址名称
    • DNS可以有多个,用数字标识即可
  • 重新启动网络服务

    1
    service network restart

管理用户

修改root密码

默认可远程登录root用户,只需知道密码即可(SSH默认已经安装)

  • 修改root密码

    1
    passwd
  • 若需要找回root密码,参考博客 https://blog.csdn.net/shanvlang/article/details/80385913

添加用户

假设添加用户名为test的用户

  • 添加新用户

    1
    useradd test
    • centos中,以上命令将自动为test用户分配一个属于test用户的/home/test文件夹
  • 为新用户修改密码

    1
    passwd test
  • 添加用户权限

    • 打开配置文件

    • 如果root用户没有该文件的写权限的话需要用chmod u+w /etc/sudoers, 修改完成再改回到chmod u-w /etc/sudoers

      1
      vi /etc/sudoers
    • 编辑以下内容,注意空格类型,最好复制一行修改

      1
      2
      3
      ## Allow root to run any commands anywhere
      root ALL=(ALL) ALL
      test ALL=(ALL) ALL
  • 删除用户

    1
    userdel test

Ubuntu

配置静态IP

  • 查看网卡名称

    1
    ifconfig
    • 查看网卡信息,每行的第一项为网卡名称
    • 一般服务器可能有多个网卡,按照需求选择一个即可
    • 网卡信息包含当前网卡的ipv4和ipv6地址,MAC地址等信息
  • 修改配置文件

    1
    vi /etc/network/interfaces
  • 修改相关配置内容

    1
    2
    3
    4
    5
    6
    7
    8
    # 假设网卡名称为eth0

    auto eth0
    iface eth0 inet static
    address 12.12.12.12
    netmask 255.255.255.0
    gateway 12.12.12.1
    dns-nameserver 114.114.114.114 8.8.8.8
    • dns可以有多个,以空格间隔开
  • 重新启动网络服务

    1
    sudo /etc/init.d/networking restart

管理用户

修改root密码

Ubuntu默认禁用root用户登录,需要配置root账户才能以root身份登录(SSH默认已经安装)

  • 修改root密码

    1
    passwd
  • 若需要找回root密码,参考博客 https://blog.csdn.net/shanvlang/article/details/80385913

添加用户

假设添加用户名为test的用户

  • 添加新用户

    1
    useradd test
    • Ubuntu中以上命令并不会直接为当前用户分配自己的文件夹,所以需要我们为其手动添加一个并使用chown test /home/test命令将文件家分配给test用户,这样使用test用户登录时将会自动转到/home/test文件夹下面工作
  • 为新用户修改密码

    1
    passwd test
  • 添加用户权限

    • 打开配置文件

    • 如果root用户没有该文件的写权限的话需要用chmod u+w /etc/sudoers, 修改完成再改回到chmod u-w /etc/sudoers

      1
      vi /etc/sudoers
    • 编辑以下内容,注意空格类型,最好复制一行修改

      1
      2
      3
      # User privilege specification
      root ALL=(ALL:ALL) ALL
      test ALL=(ALL:ALL) ALL
  • 删除用户

    1
    userdel test

Linux——Ubuntu和Centos服务器管理


Terminal无法访问外网

  • 问题现象 : 当在Linux上安装Clash后,浏览器可以访问而终端(Terminal)不行

  • 问题原因 :Clash设置的代理环境变量未正确配置到终端中,导致终端无法使用代理

  • 解决方法 :手动配置终端的代理环境变量。假设Clash监听的端口是7890,可以在终端中输入以下命令设置HTTP和HTTPS代理:

    1
    2
    export http_proxy=http://127.0.0.1:7890
    export https_proxy=http://127.0.0.1:7890
    • 如果使用的是Zsh shell,需要将上述命令添加到~/.zshrc文件中;
    • 如果是Bash shell,则添加到~/.bashrc文件中,然后执行source ~/.zshrc或source ~/.bashrc使配置生效

Git——GitHub开源协议


整体说明

  • GitHub上有许多开源协议,本文总结常见的开源协议及其含义

MIT License

  • 极宽松,允许自由使用、修改和分发,只需保留版权声明。无担保,作者不承担责任
  • 适用场景:适合个人项目或希望代码广泛传播的场景
  • 个人开发者常使用的协议

Apache License 2.0

  • 允许商业使用、修改和分发,需保留版权和许可证
  • 特殊条款 :专利授权,明示禁止商标使用
  • 适用场景:企业项目,需明确专利授权
  • 目前的大模型大多使用的这个协议

GNU GPL v3

  • 强Copyleft,修改和分发需开源且保持相同许可证。衍生作品必须开源,商业分发需提供源代码
  • 适用场景:确保代码及其衍生版本永远开源

GNU LGPL v3

  • 弱Copyleft,允许闭源软件通过库引用方式使用。修改LGPL库本身需开源
  • 适用场景:开发开源库,允许闭源应用调用

BSD 2-Clause “Simplified” License

  • 允许自由使用和修改,禁止用作者名义推广。需保留版权声明
  • 适用场景:类似MIT,但限制稍多

BSD 3-Clause “New” License

  • 在2-Clause基础上增加禁止使用作者商标的条款
  • 适用场景:需明确商标保护的项目

Mozilla Public License 2.0 (MPL 2.0)

  • 部分Copyleft,修改文件需开源,未修改部分可闭源
  • 适用场景:适合混合开源/闭源的组件化项目

Creative Commons Licenses

  • CC0 :放弃版权,进入公有领域
  • BY :允许商用,但需署名
  • NC :禁止商业使用
  • 适用场景:非软件类作品(如文档、设计)

Unlicense

  • 放弃所有版权,代码进入公有领域
  • 适用场景:完全开放的公共项目

选择建议

  • 希望代码广泛传播: MIT/Apache
  • 确保衍生作品开源: GPL
  • 开发开源库: LGPL
  • 非软件作品: CC系列

Git——Git环境配置


安装 Git

  • 可以命令行安装,也可以从 Git 的官方网站(https://git-scm.com/downloads)下载安装程序,然后按照安装向导的提示完成安装
    1
    git --version

配置用户名和邮箱

  • Git 在提交代码时会使用这些信息来标识提交者

  • 执行以下命令来设置用户名和邮箱:

    1
    2
    git config --global user.name "你的用户名"
    git config --global user.email "你的邮箱地址"
  • 这里使用的--global参数表示这是全局配置,在这台计算机上进行的所有Git操作都会使用这些信息

  • 如果你想为某个特定的项目设置不同的用户名和邮箱,可以在该项目的根目录下执行相同的命令 ,但不使用--global参数


查看配置信息

  • 可以通过以下命令来查看当前的 Git 配置信息:

    1
    git config --list
  • 执行该命令后,终端会列出所有的Git配置项,你可以从中找到user.name和user.email,确认它们是否是你刚刚设置的内容


SSH 配置

  • 查看 SSH 秘钥

    1
    ls -al ~/.ssh
    • 若存在,会看到类似 id_rsa.pub 或 id_ed25519.pub 的文件(.pub 为公钥,无后缀为私钥)
  • 若无秘钥,需先生成秘钥

    1
    ssh-keygen -t ed25519 -C "your_email@example.com"
    • -t ed25519:使用更安全的 Ed25519 算法(推荐使用)

附录:配置文件位置

  • Git 的配置信息存储在不同的文件中,具体取决于配置的范围:
    • 全局配置 :存储在用户主目录下的.gitconfig文件中
    • 项目配置 :存储在项目根目录下的.git/config文件中

Git——问题记录


Ubuntu 中文乱码问题

  • 解决方案
    1
    git config --global core.quotepath false

Windows 拉取项目后无法 checkout

  • 表现:拉取远程分支后无法进行 checkout 操作,具体错误表现为

    1
    fatal:unable to checkout working tree
  • 原因:一般是因为文件名命名包含非法字符导致,比如亲测文件名包含 | 就会出错(通常是有些不适用 Windows 且命名不规范的作者容易出现该错误)

  • 解决方案:修改文件名称,将非法字符去掉


文件权限导致记录更改

  • 现象:文件权限修改(如执行权限改为无执行权限)也会导致文件被默认为修改,此时文件内容没有任何修改
  • 具体细节:在使用 git diff 时看到的 old mode 100755 和 new mode 100644,核心是 Git 检测到文件的执行权限发生了变化,而非文件内容的修改
    • 755:代表文件拥有执行权限(所有者可读可写可执行,组和其他用户可读可执行),常见于脚本、可执行程序
    • 644:代表文件无执行权限(所有者可读可写,组和其他用户仅可读),是普通文论文件的默认权限
    • 注:权限位规则:755(可执行)= rwxr-xr-x,644(不可执行)= rw-r–r–
  • TLDR:有意修改则直接提交,误改可恢复权限,跨系统开发建议关闭 core.filemode 忽略权限检测

补充问题:为什么会出现这个差异?

  • 常见原因有 2 种:
    • 1)手动修改了权限:可能通过 chmod 命令(如 chmod 644 文件名)修改了文件权限
    • 2)系统/工具自动调整:不同操作系统(如 Windows 和 Linux)、Git 配置或编辑器可能自动调整权限

解法1:接受权限变更并提交(如果是有意修改)

  • 如果这个权限修改是刻意想要的,直接正常提交即可:
    1
    2
    3
    4
    5
    # 添加权限变更的文件到暂存区
    git add 你的文件名

    # 提交变更(备注说明权限修改)
    git commit -m "调整xxx文件权限:从100755改为100644,移除执行权限"

忽略权限变更(避免 Git 追踪权限)

  • 如果只是误操作,或不想让 Git 检测权限变化(比如跨系统开发时),可以配置 Git 忽略文件权限:

    1
    2
    3
    4
    5
    6
    7
    8
    # 临时忽略(仅当前仓库生效,关闭终端/切换仓库失效)
    git config core.filemode false

    # 永久忽略(当前仓库全局生效)
    git config --local core.filemode false

    # 全局忽略(所有Git仓库生效)
    git config --global core.filemode false
    • 配置后,Git 就不会再检测文件权限的变化,git diff 也不会再显示这类差异

恢复原有权限(撤销误改的权限)

  • 如果想把文件权限改回原来的(如 100755),执行:
    1
    2
    3
    4
    5
    # 恢复为可执行权限(755)
    chmod 755 你的文件名

    # 验证权限是否恢复
    ls -l 你的文件名 # 输出中权限列会显示 -rwxr-xr-x

Python——PyCharm使用笔记


PyCharm使用技巧记录

  • PyCharm IDE 卡顿,可考虑 Help->VM Options 增加虚拟机内存
  • PyCharm IDE 显示连字形式,<= 仅显示一个 \(\le\),修改 File-> Settings -> Editor -> General -> Font 可以切换回来,亲测 Monaco 可以
  • PyCharm IDE 显示当前代码所在路径:View -> Appearance -> Navigation Bar
  • PyCharm IDE 显示当前文件结构:View -> Tool Windows -> Structure
  • PyCharm IDE 显示当前行所属类和函数: 在 Settings 全局搜索 Breadcrumbs 并打开
  • PyCharm IDE 识别指定子目录下的项目,比如第三方项目(./sub_module/my_project/)等,直接将该项目的文件夹设置为 Root Source

Python——Pydantic库简单学习


整体说明

  • Pydantic 库可利用 Python 类型提示对数据进行验证、解析、转换以及管理,确保数据的完整性、规范性和一致性
  • Pydantic 库还方便数据在不同格式间的序列化与反序列化

安装

  • 使用以下命令安装 Pydantic:
    1
    pip install pydantic

基本使用

定义数据模型

  • 通过继承BaseModel来定义数据模型类,类中的字段使用类型注解来指定数据类型
    1
    2
    3
    4
    5
    6
    7
    from pydantic import BaseModel

    class User(BaseModel):
    id: int
    name: str
    age: int
    is_active: bool = True

数据验证和解析

  • 创建数据模型的实例时,Pydantic 会自动进行数据验证和解析
    • 如果数据符合模型定义,就可以正常创建实例;
    • 如果数据无效,会抛出ValidationError异常
  • 示例如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    data = {
    "id": 1,
    "name": "Alice",
    "age": 30
    }
    user = User(**data)
    print(user)

    invalid_data = {
    "id": "invalid", # id应该是int类型
    "age": "thirty" # age应该是int类型
    }
    try:
    user = User(**invalid_data)
    except ValidationError as e:
    print(e)

高级特性

  • 可选字段 :使用Optional类型来定义可选字段。例如:

    1
    2
    3
    4
    5
    6
    7
    from typing import Optional
    from pydantic import BaseModel

    class User(BaseModel):
    id: int
    name: str
    age: Optional[int]
  • 默认值 :可以在定义字段时直接设置默认值。例如:

    1
    2
    3
    4
    5
    6
    from pydantic import BaseModel

    class User(BaseModel):
    id: int
    name: str = "Jane Doe"
    age: int = 18
  • 允许多种数据类型 :通过类型提示允许字段接受多种数据类型。例如:

    1
    2
    3
    4
    5
    6
    from typing import Union
    from pydantic import BaseModel

    class User(BaseModel):
    id: Union[int, str]
    name: str
  • 枚举类型 :Pydantic支持枚举类型,用于限制字段的值只能是预定义的一组值之一。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from enum import Enum
    from pydantic import BaseModel

    class Gender(str, Enum):
    MALE = "male"
    FEMALE = "female"

    class User(BaseModel):
    id: int
    name: str
    gender: Gender
  • 嵌套模型 :可以定义嵌套的模型来表示复杂的数据结构。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from pydantic import BaseModel

    class Address(BaseModel):
    street: str
    city: str
    zip_code: str

    class User(BaseModel):
    id: int
    name: str
    address: Address
  • 自定义验证器 :使用validator装饰器可以在数据被解析后进行额外的验证。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from pydantic import BaseModel, validator

    class User(BaseModel):
    age: int

    @validator('age')
    def check_age(cls, value):
    if value < 0:
    raise ValueError('Age must be a non - negative integer')
    return value

其他功能

  • 数据模型实例具有一些属性和方法,如dict()返回模型字段和值的字典,json()返回JSON字符串表示,copy()创建模型的副本等
  • 使用create_model方法可以动态创建模型(不常用):
    1
    2
    3
    from pydantic import create_model

    DynamicModel = create_model('DynamicModel', foo=(str, ...), bar=123)

Python——Pylint库简单学习


Pylint 整体说明

  • Pylint 是面向 Python 的静态代码分析工具(linter),在不运行代码的前提下检查错误、执行编码规范、识别代码异味,并给出重构建议;
  • Pylint 可与 flake8(轻量快检)、mypy/pyright/pyre(类型检查)、bandit(安全检查)、black/isort(格式化)、autoflake/pyupgrade(清理/升级)等配合使用
  • 大部分 IDE 会自动集成该功能
  • Pylint 高度可配置,支持编写自定义插件,适配内部库或框架(如 pylint-django)

Pylint 能做什么

  • 语法/语义错误与未使用变量/导入
  • 命名、缩进、行长度等风格与 PEP 8 一致性
  • 复杂度与设计问题(长函数、深嵌套等)
  • 未显式类型时的节点值推断(基于 astroid),能识别别名导入带来的误用风险
  • 提供代码质量评分与报告,并支持生成包/类图(pyreverse)、查找重复代码(symilar)

Pylint 安装与使用

  • 安装:pip install pylint
  • 命令行:pylint your_module.py 或 pylint your_package/
  • 可集成到 VS Code、PyCharm、Emacs 等编辑器/IDE
  • 首次使用:先开 --errors-only,再按需启用样式/重构提示,逐步收紧规则
1…303132…61
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

608 posts
49 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4