Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第四十 生成式AI(Generative AI)

第四十 生成式AI
├── 什么是生成式AI(Generative AI)
├── 判别模型 vs 生成模型
├── 自编码器(Autoencoder)
│   ├── 编码器与解码器
│   └── 潜在空间与重构
├── 变分自编码器(VAE)
│   ├── ELBO 与证据下界
│   └── 重参数化技巧
├── 生成对抗网络(GAN)
│   ├── 生成器与判别器
│   └── 对抗训练与纳什均衡
├── Diffusion 扩散模型
│   ├── 前向加噪过程
│   └── 逆向去噪过程
├── 大语言模型的生成能力
├── 多模态生成
│   ├── 文生图(Stable Diffusion / DALL-E)
│   └── 文生视频(Sora / Kling)
├── Rust 生成式AI生态
│   ├── candle 框架
│   └── burn 框架
└── 总结与练习

生成式AI(Generative AI)是人工智能领域中最具创造力的分支。如果说判别式AI擅长“判断“——识别猫是猫、判断邮件是否为垃圾邮件——那么生成式AI则擅长“创造“——生成逼真的图像、撰写流畅的文章、谱写动人的旋律。

$$\text{判别模型:} P(y \mid x) \quad \longleftrightarrow \quad \text{生成模型:} P(x)$$

从2014年GAN的横空出世,到2022年Stable Diffusion的全民狂欢,再到2024年Sora的震撼发布,生成式AI正在重塑人类与机器的协作边界。


一、什么是生成式AI

1.1 定义

生成式AI是指能够学习数据分布 $P(x)$,并据此生成新的、与训练数据统计特征相似但又不完全相同的样本的人工智能系统。

$$x_{\text{new}} \sim P_{\theta}(x), \quad x_{\text{new}} \notin {x_1, x_2, \ldots, x_n}$$

核心思想:模型不仅学习数据的模式,还能“创造“出符合这些模式的新数据。

1.2 生成式AI的能力谱系

能力层级任务代表系统
文本生成文章、代码、对话GPT-4, Claude, DeepSeek
图像生成艺术、照片、设计Stable Diffusion, DALL-E 3, Midjourney
音频生成语音、音乐、音效Whisper, Suno, ElevenLabs
视频生成短片、动画Sora, Kling, Runway
3D生成模型、场景Point-E, Shap-E
多模态生成跨模态理解与生成Gemini, GPT-4o

1.3 发展简史

  • 2014:GAN(生成对抗网络)提出,生成模型进入深度学习时代
  • 2015:VAE(变分自编码器)成熟,概率生成模型框架确立
  • 2020:GPT-3 展现大规模语言模型的生成能力
  • 2022:Stable Diffusion 开源,文生图技术全民化
  • 2023:GPT-4、Midjourney V5 多模态生成能力飞跃
  • 2024:Sora 文生视频模型发布,Diffusion Transformer 架构兴起
  • 2025:多模态统一模型成为主流趋势

二、判别模型 vs 生成模型

2.1 核心区别

机器学习模型按建模目标可分为判别模型和生成模型两大类。

$$\underbrace{P(y \mid x)}{\text{判别模型}} \quad \text{vs} \quad \underbrace{P(x, y) = P(x \mid y) \cdot P(y)}{\text{生成模型}}$$

维度判别模型生成模型
建模目标条件概率 $P(y \mid x)$联合概率 $P(x, y)$ 或边际概率 $P(x)$
核心问题“这个是什么?”“生成一个这样的”
典型算法SVM、逻辑回归、CNN分类器GAN、VAE、Diffusion、Flow
输出类别标签 / 决策边界新样本
数据需求标注数据可无标注(自监督)

2.2 直观理解

#![allow(unused)]
fn main() {
/// 判别模型:给定输入,输出类别
fn discriminative_model(input: &Input) -> Class {
    // "这张图是猫还是狗?"
    if input.features().cat_score() > 0.8 {
        Class::Cat
    } else {
        Class::Dog
    }
}

/// 生成模型:给定条件,生成样本
fn generative_model(condition: &Condition) -> Sample {
    // "生成一张猫的图片"
    let noise = random_noise();       // 随机种子
    let sample = decode(condition, noise); // 解码生成
    sample
}
}

2.3 生成模型的统一视角

所有生成模型都可以归结为对数据分布 $P(x)$ 的建模。不同的方法采用了不同的策略:

$$P_{\theta}(x) \approx P_{\text{data}}(x)$$

  • 显式密度估计:直接建模 $P_{\theta}(x)$(VAE、Normalizing Flow)
  • 隐式密度估计:不直接建模概率,通过采样来逼近分布(GAN)
  • 分数匹配:建模数据的梯度场 $\nabla_x \log P(x)$(Diffusion)

三、自编码器(Autoencoder)

自编码器是最简单的生成模型雏形,通过“压缩-解压“学习数据的紧凑表示。

3.1 架构

自编码器由两部分组成:

$$\text{输入 } x \xrightarrow{\text{编码器 } f_{\phi}} \text{潜在表示 } z \xrightarrow{\text{解码器 } g_{\theta}} \text{重构 } \hat{x}$$

  • 编码器(Encoder):$z = f_{\phi}(x)$,将高维输入压缩为低维潜在向量
  • 解码器(Decoder):$\hat{x} = g_{\theta}(z)$,从潜在向量重构原始输入

3.2 损失函数

自编码器的训练目标是最小化重构误差:

$$\mathcal{L}{\text{AE}} = \frac{1}{N} \sum{i=1}^{N} | x_i - g_{\theta}(f_{\phi}(x_i)) |^2$$

3.3 Rust 实现

use candle_core::{Device, Tensor, DType, Result};
use candle_nn::{VarMap, Linear, Module, Builder};

/// 简易自编码器
struct Autoencoder {
    encoder1: Linear,
    encoder2: Linear,
    decoder1: Linear,
    decoder2: Linear,
}

impl Autoencoder {
    fn new(vs: candle_nn::VarBuilder) -> Result<Self> {
        // 编码器: 784 -> 128 -> 32(MNIST 图像 28x28=784)
        let encoder1 = candle_nn::linear(784, 128, vs.pp("enc1"))?;
        let encoder2 = candle_nn::linear(128, 32, vs.pp("enc2"))?;
        // 解码器: 32 -> 128 -> 784
        let decoder1 = candle_nn::linear(32, 128, vs.pp("dec1"))?;
        let decoder2 = candle_nn::linear(128, 784, vs.pp("dec2"))?;
        Ok(Self { encoder1, encoder2, decoder1, decoder2 })
    }

    /// 编码:将图像压缩为潜在向量
    fn encode(&self, x: &Tensor) -> Result<Tensor> {
        let z = x.apply(&self.encoder1)?.relu()?.apply(&self.encoder2)?;
        Ok(z)
    }

    /// 解码:从潜在向量重构图像
    fn decode(&self, z: &Tensor) -> Result<Tensor> {
        let x_hat = z.apply(&self.decoder1)?.relu()?.apply(&self.decoder2)?;
        Ok(x_hat)
    }

    /// 前向传播:编码 -> 解码
    fn forward(&self, x: &Tensor) -> Result<Tensor> {
        let z = self.encode(x)?;
        self.decode(&z)
    }
}

fn main() -> Result<()> {
    let device = Device::Cpu;
    let mut varmap = VarMap::new();
    let vs = candle_nn::VarBuilder::from_varmap(&varmap, DType::F32, &device);

    let ae = Autoencoder::new(vs)?;

    // 模拟输入:batch=4, 每张图 784 像素
    let input = Tensor::randn(0f32, 1.0, (4, 784), &device)?;
    let output = ae.forward(&input)?;

    // 计算重构误差 (MSE)
    let diff = input.sub(&output)?;
    let mse = (diff.powf(2.0)?.mean_all()? as f32).sqrt();
    println!("重构误差 (RMSE): {:.6}", mse);
    println!("输入形状:  {:?}", input.shape());   // [4, 784]
    println!("输出形状:  {:?}", output.shape());  // [4, 784]

    // 查看潜在空间维度
    let z = ae.encode(&input)?;
    println!("潜在向量形状: {:?}", z.shape());    // [4, 32]

    Ok(())
}

3.4 自编码器的局限

普通自编码器存在一个关键问题:潜在空间 $z$ 是不连续的。在潜在空间中随机采样一个点,解码后往往得到无意义的输出。这意味着它无法真正“生成“新样本,只能“记忆“和“重构“已有样本。

$$z \sim \mathcal{U}(\text{latent space}) \quad \Rightarrow \quad g_{\theta}(z) \text{ 可能无意义}$$


四、变分自编码器(VAE)

VAE 通过引入概率框架,解决了自编码器潜在空间不连续的问题,使其具备了真正的生成能力。

4.1 核心思想

VAE 不再将输入映射为确定性的潜在向量,而是映射为一个概率分布:

$$x \xrightarrow{\text{编码器}} (\mu, \sigma^2) \xrightarrow{\text{采样}} z \sim \mathcal{N}(\mu, \sigma^2) \xrightarrow{\text{解码器}} \hat{x}$$

编码器输出均值 $\mu$ 和方差 $\sigma^2$,从该高斯分布中采样 $z$,再由解码器生成样本。

4.2 ELBO 与证据下界

VAE 的训练目标是最大化数据的对数似然 $\log P(x)$。由于真实后验 $P(z|x)$ 不可计算,我们通过变分推断引入证据下界(ELBO, Evidence Lower BOund):

$$\log P(x) \geq \mathbb{E}{q{\phi}(z|x)}[\log P_{\theta}(x|z)] - D_{\text{KL}}(q_{\phi}(z|x) | P(z))$$

等价地,最小化以下损失函数:

$$\mathcal{L}{\text{VAE}} = \underbrace{\mathbb{E}{q_{\phi}(z|x)}[| x - g_{\theta}(z) |^2]}{\text{重构损失}} + \underbrace{D{\text{KL}}(q_{\phi}(z|x) | \mathcal{N}(0, I))}_{\text{KL 散度(正则项)}}$$

其中 KL 散度在两个高斯分布之间有闭式解:

$$D_{\text{KL}}(\mathcal{N}(\mu, \sigma^2) | \mathcal{N}(0, 1)) = \frac{1}{2} \sum_{j=1}^{J} \left( \mu_j^2 + \sigma_j^2 - \ln \sigma_j^2 - 1 \right)$$

4.3 重参数化技巧

直接从 $q_{\phi}(z|x)$ 采样时,梯度无法通过随机节点反向传播。重参数化技巧将随机性从计算图中分离:

$$z = \mu + \sigma \odot \epsilon, \quad \epsilon \sim \mathcal{N}(0, I)$$

这样梯度可以流向 $\mu$ 和 $\sigma$,而 $\epsilon$ 被视为常数。

4.4 Rust 实现

use candle_core::{Device, Tensor, DType, Result};
use candle_nn::{VarMap, Linear, Module};

/// 变分自编码器
struct VAE {
    // 编码器
    enc1: Linear,
    enc2: Linear,
    // 均值和对数方差
    fc_mu: Linear,
    fc_logvar: Linear,
    // 解码器
    dec1: Linear,
    dec2: Linear,
}

impl VAE {
    fn new(vs: candle_nn::VarBuilder) -> Result<Self> {
        let enc1 = candle_nn::linear(784, 256, vs.pp("enc1"))?;
        let enc2 = candle_nn::linear(256, 64, vs.pp("enc2"))?;
        let fc_mu = candle_nn::linear(64, 32, vs.pp("fc_mu"))?;
        let fc_logvar = candle_nn::linear(64, 32, vs.pp("fc_logvar"))?;
        let dec1 = candle_nn::linear(32, 256, vs.pp("dec1"))?;
        let dec2 = candle_nn::linear(256, 784, vs.pp("dec2"))?;
        Ok(Self { enc1, enc2, fc_mu, fc_logvar, dec1, dec2 })
    }

    /// 编码:输出均值和对数方差
    fn encode(&self, x: &Tensor) -> Result<(Tensor, Tensor)> {
        let h = x.apply(&self.enc1)?.relu()?.apply(&self.enc2)?.relu()?;
        let mu = h.apply(&self.fc_mu)?;
        let logvar = h.apply(&self.fc_logvar)?;
        Ok((mu, logvar))
    }

    /// 重参数化技巧:z = mu + sigma * epsilon
    fn reparameterize(&self, mu: &Tensor, logvar: &Tensor) -> Result<Tensor> {
        let sigma = (logvar.exp()? * 0.5)?;
        let epsilon = Tensor::randn(0f32, 1.0, mu.shape(), &mu.device())?;
        mu.add(&sigma.mul(&epsilon)?)
    }

    /// 解码
    fn decode(&self, z: &Tensor) -> Result<Tensor> {
        let h = z.apply(&self.dec1)?.relu()?;
        h.apply(&self.dec2)
    }

    /// 前向传播
    fn forward(&self, x: &Tensor) -> Result<(Tensor, Tensor, Tensor)> {
        let (mu, logvar) = self.encode(x)?;
        let z = self.reparameterize(&mu, &logvar)?;
        let recon = self.decode(&z)?;
        Ok((recon, mu, logvar))
    }

    /// 计算 VAE 损失
    fn loss(&self, x: &Tensor) -> Result<(Tensor, f32, f32)> {
        let (recon, mu, logvar) = self.forward(x)?;

        // 重构损失 (MSE)
        let recon_loss = x.sub(&recon)?.powf(2.0)?.mean_all()?;

        // KL 散度: 0.5 * sum(mu^2 + logvar.exp() - logvar - 1)
        let kl_loss = (mu.powf(2.0)?
            + logvar.exp()?
            - logvar
            - Tensor::ones_like(&logvar)?
        )?.mean_all()? * 0.5;

        let total = recon_loss.add(&kl_loss)?;
        Ok((total, recon_loss.to_scalar::<f32>()?, kl_loss.to_scalar::<f32>()?))
    }
}

fn main() -> Result<()> {
    let device = Device::Cpu;
    let mut varmap = VarMap::new();
    let vs = candle_nn::VarBuilder::from_varmap(&varmap, DType::F32, &device);

    let vae = VAE::new(vs)?;

    // 模拟 MNIST 输入
    let input = Tensor::randn(0f32, 1.0, (8, 784), &device)?;
    let (total_loss, recon_loss, kl_loss) = vae.loss(&input)?;

    println!("总损失:     {:.6}", total_loss.to_scalar::<f32>()?);
    println!("重构损失:   {:.6}", recon_loss);
    println!("KL 散度:    {:.6}", kl_loss);

    // 从潜在空间采样生成新样本
    let z = Tensor::randn(0f32, 1.0, (4, 32), &device)?;
    let generated = vae.decode(&z)?;
    println!("生成样本形状: {:?}", generated.shape()); // [4, 784]

    Ok(())
}

五、生成对抗网络(GAN)

GAN 是生成式AI的里程碑式工作,由 Ian Goodfellow 于2014年提出。它通过两个网络的对抗博弈来学习数据分布。

5.1 核心架构

GAN 由两个网络组成:

$$\min_G \max_D ; \mathbb{E}{x \sim P{\text{data}}}[\log D(x)] + \mathbb{E}_{z \sim P_z}[\log(1 - D(G(z)))]$$

  • 生成器(Generator, $G$):接收随机噪声 $z$,生成伪造样本 $G(z)$
  • 判别器(Discriminator, $D$):判断输入是真实样本还是生成样本

$$z \sim \mathcal{N}(0, I) \xrightarrow{G} \hat{x} \xrightarrow{D} \text{ 真/假}$$

5.2 对抗训练

训练过程可以类比为“造假者“与“警察“的博弈:

  1. 训练判别器:使其更好地区分真实样本和生成样本
  2. 训练生成器:使其生成的样本更逼真,骗过判别器

理想状态下,达到纳什均衡:$D(x) = 0.5$,判别器无法区分真假,生成器学会了真实数据的分布。

$$P_G = P_{\text{data}} \quad \Rightarrow \quad D(x) = \frac{1}{2}, ; \forall x$$

5.3 Rust 实现

use candle_core::{Device, Tensor, DType, Result};
use candle_nn::{VarMap, Linear, Module, Builder};

/// 生成器:将噪声向量映射为图像
struct Generator {
    fc1: Linear,
    fc2: Linear,
    fc3: Linear,
}

impl Generator {
    fn new(vs: candle_nn::VarBuilder) -> Result<Self> {
        let fc1 = candle_nn::linear(64, 256, vs.pp("g1"))?;
        let fc2 = candle_nn::linear(256, 256, vs.pp("g2"))?;
        let fc3 = candle_nn::linear(256, 784, vs.pp("g3"))?;
        Ok(Self { fc1, fc2, fc3 })
    }

    fn forward(&self, z: &Tensor) -> Result<Tensor> {
        // 使用 LeakyReLU 激活(避免梯度消失)
        let x = z.apply(&self.fc1)?;
        let x = x.maximum(&x.mul(0.01)?)?; // LeakyReLU(0.01)
        let x = x.apply(&self.fc2)?;
        let x = x.maximum(&x.mul(0.01)?)?;
        let x = x.apply(&self.fc3)?;
        // 使用 Tanh 将输出限制在 [-1, 1]
        x.tanh()
    }
}

/// 判别器:判断输入是真实图像还是生成图像
struct Discriminator {
    fc1: Linear,
    fc2: Linear,
    fc3: Linear,
}

impl Discriminator {
    fn new(vs: candle_nn::VarBuilder) -> Result<Self> {
        let fc1 = candle_nn::linear(784, 256, vs.pp("d1"))?;
        let fc2 = candle_nn::linear(256, 256, vs.pp("d2"))?;
        let fc3 = candle_nn::linear(256, 1, vs.pp("d3"))?;
        Ok(Self { fc1, fc2, fc3 })
    }

    fn forward(&self, x: &Tensor) -> Result<Tensor> {
        // 使用 LeakyReLU,最后用 Sigmoid 输出概率
        let x = x.apply(&self.fc1)?;
        let x = x.maximum(&x.mul(0.01)?)?;
        let x = x.apply(&self.fc2)?;
        let x = x.maximum(&x.mul(0.01)?)?;
        x.apply(&self.fc3)?.sigmoid()
    }
}

/// 二元交叉熵损失
fn binary_cross_entropy(pred: &Tensor, target: &Tensor) -> Result<Tensor> {
    let eps = 1e-7;
    let pred = pred.clamp(eps, 1.0 - eps)?;
    let loss = target.mul(&pred.log()?)?
        + &(Tensor::ones_like(target)?.sub(target)?.mul(&(Tensor::ones_like(&pred)?.sub(&pred)?.log()?))?)?;
    loss.neg()?.mean_all()
}

fn main() -> Result<()> {
    let device = Device::Cpu;

    // 构建生成器和判别器(使用独立的 VarMap)
    let mut g_varmap = VarMap::new();
    let g_vs = candle_nn::VarBuilder::from_varmap(&g_varmap, DType::F32, &device);
    let generator = Generator::new(g_vs)?;

    let mut d_varmap = VarMap::new();
    let d_vs = candle_nn::VarBuilder::from_varmap(&d_varmap, DType::F32, &device);
    let discriminator = Discriminator::new(d_vs)?;

    // 模拟真实数据
    let real_data = Tensor::randn(0f32, 1.0, (32, 784), &device)?;
    // 采样噪声
    let noise = Tensor::randn(0f32, 1.0, (32, 64), &device)?;

    // 生成假数据
    let fake_data = generator.forward(&noise)?;
    println!("假数据形状: {:?}", fake_data.shape()); // [32, 784]

    // 判别器对真实数据的评分
    let real_score = discriminator.forward(&real_data)?;
    let real_label = Tensor::ones_like(&real_score)?;
    let d_loss_real = binary_cross_entropy(&real_score, &real_label)?;

    // 判别器对假数据的评分
    let fake_score = discriminator.forward(&fake_data.detach()?)?;
    let fake_label = Tensor::zeros_like(&fake_score)?;
    let d_loss_fake = binary_cross_entropy(&fake_score, &fake_label)?;

    // 判别器总损失
    let d_loss = (d_loss_real.add(&d_loss_fake)? * 0.5)?;
    println!("判别器损失: {:.6}", d_loss.to_scalar::<f32>()?);

    // 生成器损失(希望判别器将假数据判断为真)
    let gen_score = discriminator.forward(&fake_data)?;
    let gen_label = Tensor::ones_like(&gen_score)?;
    let g_loss = binary_cross_entropy(&gen_score, &gen_label)?;
    println!("生成器损失: {:.6}", g_loss.to_scalar::<f32>()?);

    Ok(())
}

5.4 GAN 的变体

变体核心改进应用场景
DCGAN使用卷积层替代全连接层图像生成
WGAN使用 Wasserstein 距离替代 JS 散度训练稳定性
StyleGAN风格注入、渐进式增长高清人脸生成
CycleGAN循环一致性损失无配对图像风格迁移
Pix2Pix配对图像翻译边缘到照片、素描到彩图

六、Diffusion 扩散模型

Diffusion 模型是当前最主流的生成模型架构,Stable Diffusion、DALL-E、Sora 等明星模型均基于此。

6.1 核心思想

Diffusion 模型的灵感来自热力学中的扩散过程:向数据中逐步添加噪声,直到变成纯高斯噪声;然后学习一个逆向过程,从噪声中恢复数据。

$$\text{前向过程:} x_0 \xrightarrow{+\epsilon_1} x_1 \xrightarrow{+\epsilon_2} x_2 \xrightarrow{\cdots} x_T \approx \mathcal{N}(0, I)$$

$$\text{逆向过程:} x_T \xrightarrow{-\epsilon_T} x_{T-1} \xrightarrow{-\epsilon_{T-1}} \cdots \xrightarrow{-\epsilon_1} x_0$$

6.2 前向加噪过程

前向过程是一个马尔可夫链,每一步添加少量高斯噪声:

$$q(x_t \mid x_{t-1}) = \mathcal{N}(x_t; \sqrt{1 - \beta_t} x_{t-1}, \beta_t \mathbf{I})$$

其中 $\beta_t$ 是预定义的噪声调度(noise schedule),通常从 $\beta_1 = 10^{-4}$ 线性增长到 $\beta_T = 0.02$。

利用重参数化技巧,可以直接从 $x_0$ 得到任意时刻的 $x_t$:

$$x_t = \sqrt{\bar{\alpha}_t} x_0 + \sqrt{1 - \bar{\alpha}_t} \epsilon, \quad \epsilon \sim \mathcal{N}(0, I)$$

其中 $\alpha_t = 1 - \beta_t$,$\bar{\alpha}t = \prod{s=1}^{t} \alpha_s$。

6.3 逆向去噪过程

逆向过程由神经网络 $\epsilon_\theta(x_t, t)$ 预测每一步添加的噪声:

$$p_{\theta}(x_{t-1} \mid x_t) = \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t), \sigma_t^2 \mathbf{I})$$

训练目标是让网络预测的噪声与真实噪声尽可能接近:

$$\mathcal{L}{\text{simple}} = \mathbb{E}{t, x_0, \epsilon} \left[ | \epsilon - \epsilon_\theta(x_t, t) |^2 \right]$$

6.4 Rust 实现(简化的去噪过程)

use candle_core::{Device, Tensor, DType, Result};
use candle_nn::{VarMap, Linear, Module};

/// 简化的噪声预测网络
struct NoisePredictor {
    fc1: Linear,
    fc2: Linear,
    fc3: Linear,
}

impl NoisePredictor {
    fn new(vs: candle_nn::VarBuilder) -> Result<Self> {
        let fc1 = candle_nn::linear(784 + 1, 512, vs.pp("fc1"))?; // +1 为时间步
        let fc2 = candle_nn::linear(512, 256, vs.pp("fc2"))?;
        let fc3 = candle_nn::linear(256, 784, vs.pp("fc3"))?;
        Ok(Self { fc1, fc2, fc3 })
    }

    /// 预测噪声:输入为 (图像, 时间步),输出预测的噪声
    fn predict_noise(&self, x: &Tensor, t: &Tensor) -> Result<Tensor> {
        // 将时间步 t 广播并拼接到图像特征上
        let t_expanded = t.reshape((x.shape().dims()[0], 1))?;
        let t_broadcast = t_expanded.broadcast_as((x.shape().dims()[0], 784))?;
        let input = Tensor::cat(&[x, &t_broadcast], 1)?;

        let h = input.apply(&self.fc1)?.relu()?;
        let h = h.apply(&self.fc2)?.relu()?;
        h.apply(&self.fc3)
    }
}

/// 前向加噪:直接从 x_0 得到 x_t
fn forward_diffusion(x0: &Tensor, t: usize, total_steps: usize) -> Result<Tensor> {
    // 计算 alpha_bar_t
    let beta_start = 0.0001f32;
    let beta_end = 0.02f32;
    let betas: Vec<f32> = (0..total_steps)
        .map(|i| beta_start + (beta_end - beta_start) * i as f32 / total_steps as f32)
        .collect();
    let alphas: Vec<f32> = betas.iter().map(|b| 1.0 - b).collect();
    let alpha_bar_t: f32 = alphas[0..t].iter().product();

    let sqrt_alpha_bar = alpha_bar_t.sqrt();
    let sqrt_one_minus_alpha_bar = (1.0 - alpha_bar_t).sqrt();

    // x_t = sqrt(alpha_bar) * x_0 + sqrt(1 - alpha_bar) * noise
    let noise = Tensor::randn(0f32, 1.0, x0.shape(), &x0.device())?;
    let noisy = x0.mul(sqrt_alpha_bar)?.add(&noise.mul(sqrt_one_minus_alpha_bar)?)?;
    Ok(noisy)
}

/// 逆向去噪:单步去噪
fn reverse_step(
    predictor: &NoisePredictor,
    xt: &Tensor,
    t: f32,
    alpha_bar_t: f32,
    alpha_bar_prev: f32,
) -> Result<Tensor> {
    let device = xt.device();
    let batch_size = xt.shape().dims()[0];

    // 预测噪声
    let t_tensor = Tensor::from_vec(vec![t; batch_size], (batch_size,), device)?;
    let predicted_noise = predictor.predict_noise(xt, &t_tensor)?;

    // 计算 x_{t-1}
    let mean = (xt.sub(&predicted_noise.mul(1.0 - alpha_bar_t)?)?
        * (alpha_bar_prev / alpha_bar_t).sqrt())?;
    Ok(mean)
}

fn main() -> Result<()> {
    let device = Device::Cpu;
    let mut varmap = VarMap::new();
    let vs = candle_nn::VarBuilder::from_varmap(&varmap, DType::F32, &device);
    let predictor = NoisePredictor::new(vs)?;

    // 模拟原始图像
    let x0 = Tensor::randn(0f32, 0.5, (4, 784), &device)?;

    // 前向加噪到第 200 步(共 1000 步)
    let xt = forward_diffusion(&x0, 200, 1000)?;
    println!("加噪后形状: {:?}", xt.shape());

    // 逆向去噪(单步演示)
    let alpha_bar_t = 0.5f32;    // 第 200 步的 alpha_bar(示意值)
    let alpha_bar_prev = 0.55f32; // 第 199 步的 alpha_bar(示意值)
    let x_prev = reverse_step(&predictor, &xt, 200.0, alpha_bar_t, alpha_bar_prev)?;
    println!("去噪后形状: {:?}", x_prev.shape());

    Ok(())
}

6.5 Diffusion 模型的优势

优势说明
训练稳定不存在 GAN 的模式崩溃问题
生成质量高在图像生成上超越 GAN
可控生成通过条件引导(classifier-free guidance)精确控制生成内容
多样性好随机采样保证生成样本的多样性

七、大语言模型的生成能力

大语言模型(LLM)本质上是自回归的生成模型,通过预测下一个 token 来生成文本。

7.1 自回归生成

给定上下文 $x_{1}, x_{2}, \ldots, x_{t-1}$,模型预测下一个 token 的概率分布:

$$P(x_t \mid x_{<t}) = \text{softmax}(W_o \cdot h_t + b_o)$$

其中 $h_t$ 是 Transformer 最后一层的隐藏状态。

生成过程是自回归的:

$$P(x_{1:T}) = \prod_{t=1}^{T} P(x_t \mid x_{<t})$$

7.2 采样策略

策略公式特点
贪心解码$x_t = \arg\max P(x_t \mid x_{<t})$确定性,但可能重复
温度采样$P’(x_t) \propto P(x_t \mid x_{<t})^{1/T}$$T < 1$ 更确定,$T > 1$ 更随机
Top-k从概率最高的 $k$ 个 token 中采样限制候选集
Top-p (nucleus)从累积概率 $\geq p$ 的最小集合中采样动态调整候选集

7.3 Rust 调用 LLM 推理

use candle_core::{Device, Tensor, DType, Result};
use candle_nn::VarBuilder;

/// 简化的自回归文本生成演示
fn generate_token(
    logits: &Tensor,        // [vocab_size] 模型输出的 logits
    temperature: f32,       // 温度参数
    top_k: usize,           // top-k 采样
) -> Result<usize> {
    // 应用温度
    let scaled = logits.div(temperature)?;

    // Top-k 过滤
    let k = top_k.min(logits.dims1()?);
    let (top_values, top_indices) = scaled.topk(k, 0)?;

    // Softmax 转为概率
    let probs = candle_nn::ops::softmax(&top_values, 0)?;

    // 从概率分布中采样
    let probs_vec = probs.to_vec1::<f32>()?;
    let indices_vec = top_indices.to_vec1::<u32>()?;

    let mut rng = rand::thread_rng();
    let r: f32 = rand::Rng::gen_range(&mut rng, 0.0..1.0);
    let mut cumsum = 0.0;
    for (i, &p) in probs_vec.iter().enumerate() {
        cumsum += p;
        if cumsum >= r {
            return Ok(indices_vec[i] as usize);
        }
    }
    Ok(indices_vec[indices_vec.len() - 1] as usize)
}

/// 自回归生成循环(伪代码)
fn autoregressive_generate(
    model: &dyn Fn(&Tensor) -> Result<Tensor>,  // 语言模型
    prompt_tokens: &[usize],                     // 输入提示
    max_new_tokens: usize,                        // 最大生成长度
    temperature: f32,                            // 温度
    top_k: usize,                                 // top-k
) -> Result<Vec<usize>> {
    let mut tokens = prompt_tokens.to_vec();
    let device = Device::Cpu;

    for _ in 0..max_new_tokens {
        // 将当前 token 序列转为张量
        let input = Tensor::from_vec(
            tokens.clone(),
            (1, tokens.len()),
            &device,
        )?;

        // 模型前向传播,取最后一个位置的 logits
        let logits = model(&input)?;  // [1, seq_len, vocab_size]
        let last_logits = logits.i((.., tokens.len() - 1, ..))?.squeeze(0)?; // [vocab_size]

        // 采样下一个 token
        let next_token = generate_token(&last_logits, temperature, top_k)?;
        tokens.push(next_token);

        // 遇到结束符则停止
        if next_token == EOS_TOKEN {
            break;
        }
    }

    Ok(tokens)
}

const EOS_TOKEN: usize = 2;

fn main() -> Result<()> {
    // 模拟 vocab_size=1000 的 logits
    let device = Device::Cpu;
    let logits = Tensor::randn(0f32, 1.0, (1000,), &device)?;

    // 使用温度=0.8, top_k=50 采样
    let token_id = generate_token(&logits, 0.8, 50)?;
    println!("采样得到的 token ID: {}", token_id);

    // 演示不同温度的效果
    for temp in [0.3, 0.8, 1.2, 2.0] {
        let tid = generate_token(&logits, temp, 50)?;
        println!("温度={:.1} -> token={}", temp, tid);
    }

    Ok(())
}

八、多模态生成

多模态生成是生成式AI的前沿方向,目标是实现跨模态的内容创作。

8.1 文生图(Text-to-Image)

文生图模型将文本描述转化为视觉图像。当前主流方法基于 Latent Diffusion

$$\text{文本提示} \xrightarrow{\text{CLIP 编码器}} \text{条件向量} \xrightarrow{\text{U-Net 去噪}} \text{潜在图像} \xrightarrow{\text{VAE 解码器}} \text{像素图像}$$

核心流程:

  1. 文本编码:CLIP Text Encoder 将提示词编码为条件向量
  2. 扩散去噪:在潜在空间中,以条件向量为引导,从噪声逐步去噪
  3. 图像解码:VAE Decoder 将潜在表示解码为像素图像

8.2 文生视频(Text-to-Video)

文生视频是文生图的自然扩展,核心挑战在于保持时间一致性:

$$\text{文本} \xrightarrow{\text{编码}} \text{条件} \xrightarrow{\text{3D 扩散}} \text{潜在视频帧序列} \xrightarrow{\text{解码}} \text{视频}$$

模型发布方关键技术
SoraOpenAIDiffusion Transformer (DiT), 时空一致性
Kling快手3D VAE, 长视频生成
Runway Gen-3Runway时空注意力机制
VeoGoogle潜在扩散 + Transformer

8.3 Rust 中的文生图推理

use candle_core::{Device, Tensor, DType, Result};
use candle_nn::{VarBuilder, Module};

/// 简化的文生图推理流程演示
struct TextToImagePipeline {
    text_encoder: TextEncoder,
    unet: UnetModel,
    vae_decoder: VAEDecoder,
}

/// 文本编码器(简化版 CLIP)
struct TextEncoder {
    embed: candle_nn::Embedding,
    layers: Vec<candle_nn::Linear>,
}

impl TextEncoder {
    fn encode(&self, token_ids: &[usize]) -> Result<Tensor> {
        let device = Device::Cpu;
        let tokens = Tensor::from_vec(
            token_ids.to_vec(),
            (1, token_ids.len()),
            &device,
        )?;
        let mut hidden = self.embed.forward(&tokens)?;
        for layer in &self.layers {
            hidden = hidden.apply(layer)?.relu()?;
        }
        // 返回条件向量 [1, seq_len, dim]
        Ok(hidden)
    }
}

/// U-Net 去噪网络(简化)
struct UnetModel {
    input_proj: candle_nn::Linear,
    cond_proj: candle_nn::Linear,
    output_proj: candle_nn::Linear,
}

impl UnetModel {
    /// 去噪一步:预测噪声
    fn denoise_step(
        &self,
        noisy_latent: &Tensor,  // [1, latent_dim]
        text_condition: &Tensor, // [1, cond_dim]
        timestep: f32,
    ) -> Result<Tensor> {
        // 将条件信息注入
        let x = noisy_latent.apply(&self.input_proj)?;
        let c = text_condition.apply(&self.cond_proj)?;
        let combined = x.add(&c.broadcast_as(x.shape())?)?;
        let predicted_noise = combined.apply(&self.output_proj)?;
        Ok(predicted_noise)
    }
}

/// VAE 解码器(简化)
struct VAEDecoder {
    fc1: candle_nn::Linear,
    fc2: candle_nn::Linear,
}

impl VAEDecoder {
    /// 将潜在表示解码为像素
    fn decode(&self, latent: &Tensor) -> Result<Tensor> {
        let h = latent.apply(&self.fc1)?.relu()?;
        h.apply(&self.fc2) // [1, height * width * channels]
    }
}

fn main() -> Result<()> {
    println!("=== 文生图推理流程演示 ===");
    println!("1. 文本编码:将提示词编码为条件向量");
    println!("2. 扩散去噪:在潜在空间中逐步去噪");
    println!("3. 图像解码:将潜在表示解码为像素图像");
    println!();
    println!("完整实现请参考 candle-transformers 项目中的");
    println!("stable-diffusion 示例。");
    println!();
    println!("相关 crate:");
    println!("  candle-transformers = \"0.8\"");
    println!("  tokenizers = \"0.20\"");
    Ok(())
}

九、Rust 生成式AI生态

Rust 在生成式AI领域的生态正在快速发展,以下介绍两个核心框架。

9.1 candle — HuggingFace 的 Rust ML 框架

candle 是 HuggingFace 推出的纯 Rust 深度学习框架,特点是:

  • 纯 Rust 实现:无 Python 依赖,编译为单一二进制
  • 高性能:支持 CPU / CUDA / Metal 后端
  • 模型丰富:支持 BERT、LLaMA、Stable Diffusion、Whisper 等主流模型
  • 部署友好:适合边缘部署和推理服务
# Cargo.toml
[dependencies]
candle-core = "0.8"
candle-nn = "0.8"
candle-transformers = "0.8"  # 预置模型
use candle_core::Device;
use candle_transformers::models::stable_diffusion;

/// 使用 candle 运行 Stable Diffusion 推理
fn generate_image(prompt: &str) -> Result<(), Box<dyn std::error::Error>> {
    let device = Device::new_cuda(0).unwrap_or(Device::Cpu);

    // 加载 Stable Diffusion 模型
    let config = stable_diffusion::StableDiffusionConfig::v2_1(&device);
    let model = stable_diffusion::StableDiffusion::new(config)?;

    // 生成图像
    let generated = model.generate(
        prompt,          // 提示词
        25,              // 推理步数
        7.5,             // CFG 引导强度
        42,              // 随机种子
        None::<&str>,    // 负面提示词
    )?;

    // 保存为 PNG
    generated.save("generated.png")?;
    println!("图像已保存到 generated.png");

    Ok(())
}

fn main() {
    // candle 支持的生成模型:
    // - stable_diffusion: 文生图
    // - whisper: 语音识别与生成
    // - llama: 大语言模型文本生成
    // - mistral: Mistral 系列模型
    // - phi: Phi 系列模型
    println!("candle 支持的生成模型:");
    println!("  - Stable Diffusion (文生图)");
    println!("  - LLaMA / Mistral / Phi (文本生成)");
    println!("  - Whisper (语音识别)");
    println!("  - Wuerstchen (文生图, 轻量级)");
}

9.2 burn — Rust 深度学习框架

burn 是另一个活跃的 Rust 深度学习框架,特点是:

  • 多后端支持:可切换 Autodiff、LibTorch、NDArray 等后端
  • 训练支持:完整的训练循环、数据加载器、学习率调度
  • 模块化设计:灵活的组件组合
# Cargo.toml
[dependencies]
burn = "0.15"
burn-ndarray = "0.15"  # CPU 后端
#![allow(unused)]
fn main() {
use burn::module::Module;
use burn::tensor::{Tensor, backend::AutodiffBackend};
use burn::nn::{Linear, LinearConfig, ReLU, Builder};

/// 使用 burn 构建生成器
#[derive(Module, Debug)]
pub struct Generator<B: burn::tensor::backend::Backend> {
    fc1: Linear,
    fc2: Linear,
    fc3: Linear,
    activation: ReLU,
}

impl<B: burn::tensor::backend::Backend> Generator<B> {
    pub fn new(latent_dim: usize, hidden_dim: usize, output_dim: usize, device: &B::Device)
        -> Result<Self, burn::tensor::TensorError>
    {
        let fc1 = LinearConfig::new(latent_dim, hidden_dim).init(device);
        let fc2 = LinearConfig::new(hidden_dim, hidden_dim).init(device);
        let fc3 = LinearConfig::new(hidden_dim, output_dim).init(device);
        Ok(Self { fc1, fc2, fc3, activation: ReLU::new() })
    }

    pub fn forward(&self, z: Tensor<B, 2>) -> Result<Tensor<B, 2>, burn::tensor::TensorError> {
        let x = self.fc1.forward(z).apply(&self.activation)?;
        let x = self.fc2.forward(x).apply(&self.activation)?;
        self.fc3.forward(x)
    }
}
}

9.3 Rust 生成式AI 生态总览

crate/项目类型功能适用场景
candle框架深度学习推理与训练模型推理、部署服务
candle-transformers模型库预置 Transformer 模型LLM、Stable Diffusion
burn框架多后端深度学习模型训练、研究
tract推理引擎ONNX/TFLite 推理边缘部署
tch绑定PyTorch C++ 绑定快速原型验证
mistral-rs服务LLM 推理服务本地 LLM 部署
llm-chain链式调用LLM 应用开发AI Agent 构建

十、总结

核心知识回顾

主题核心要点
生成式AI定义学习数据分布 $P(x)$,生成新样本
判别 vs 生成$P(y|x)$ vs $P(x)$;“是什么” vs “创造什么”
自编码器编码器-解码器架构,重构误差最小化
VAEELBO 优化 + 重参数化技巧,概率生成
GAN生成器与判别器对抗训练,纳什均衡
Diffusion前向加噪 + 逆向去噪,当前最主流的生成架构
LLM 生成自回归 token 采样,温度/Top-k/Top-p
多模态生成文生图(Latent Diffusion)、文生视频(DiT)
Rust 生态candle(推理)、burn(训练)、mistral-rs(部署)

生成模型演进路线

自编码器 (AE)
    │  引入概率框架
    ▼
变分自编码器 (VAE) ─── ELBO + 重参数化
    │
    ├── 生成对抗网络 (GAN) ─── 对抗训练
    │       │
    │       └── StyleGAN, WGAN, CycleGAN ...
    │
    └── 扩散模型 (Diffusion) ─── 加噪/去噪
            │
            ├── Stable Diffusion (文生图)
            ├── Sora (文生视频)
            └── DALL-E 3 (文生图)

练习建议

  1. 基础练习:使用 candle 实现一个简单的自编码器,在 MNIST 数据集上训练,观察重构效果。
  2. VAE 实现:在自编码器基础上实现 VAE,比较两者的潜在空间结构(对潜在向量做插值,观察生成效果)。
  3. GAN 训练:实现一个简单的 GAN,记录生成器和判别器的损失曲线,观察训练稳定性问题。
  4. Diffusion 理解:实现前向加噪过程,可视化不同噪声步数下的图像变化;实现单步去噪并观察效果。
  5. 采样策略:实现温度采样、Top-k 采样和 Top-p 采样,比较不同参数下的文本生成效果。
  6. candle 实战:使用 candle-transformers 加载预训练的 Stable Diffusion 模型,尝试不同的提示词生成图像。
  7. 进阶挑战:使用 burn 框架从头训练一个 VAE,实现 MNIST 手写数字的生成,并实现潜在空间的插值可视化。