第四十 生成式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 对抗训练
训练过程可以类比为“造假者“与“警察“的博弈:
- 训练判别器:使其更好地区分真实样本和生成样本
- 训练生成器:使其生成的样本更逼真,骗过判别器
理想状态下,达到纳什均衡:$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{像素图像}$$
核心流程:
- 文本编码:CLIP Text Encoder 将提示词编码为条件向量
- 扩散去噪:在潜在空间中,以条件向量为引导,从噪声逐步去噪
- 图像解码:VAE Decoder 将潜在表示解码为像素图像
8.2 文生视频(Text-to-Video)
文生视频是文生图的自然扩展,核心挑战在于保持时间一致性:
$$\text{文本} \xrightarrow{\text{编码}} \text{条件} \xrightarrow{\text{3D 扩散}} \text{潜在视频帧序列} \xrightarrow{\text{解码}} \text{视频}$$
| 模型 | 发布方 | 关键技术 |
|---|---|---|
| Sora | OpenAI | Diffusion Transformer (DiT), 时空一致性 |
| Kling | 快手 | 3D VAE, 长视频生成 |
| Runway Gen-3 | Runway | 时空注意力机制 |
| Veo | 潜在扩散 + 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 “创造什么” |
| 自编码器 | 编码器-解码器架构,重构误差最小化 |
| VAE | ELBO 优化 + 重参数化技巧,概率生成 |
| 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 (文生图)
练习建议
- 基础练习:使用
candle实现一个简单的自编码器,在 MNIST 数据集上训练,观察重构效果。 - VAE 实现:在自编码器基础上实现 VAE,比较两者的潜在空间结构(对潜在向量做插值,观察生成效果)。
- GAN 训练:实现一个简单的 GAN,记录生成器和判别器的损失曲线,观察训练稳定性问题。
- Diffusion 理解:实现前向加噪过程,可视化不同噪声步数下的图像变化;实现单步去噪并观察效果。
- 采样策略:实现温度采样、Top-k 采样和 Top-p 采样,比较不同参数下的文本生成效果。
- candle 实战:使用
candle-transformers加载预训练的 Stable Diffusion 模型,尝试不同的提示词生成图像。 - 进阶挑战:使用
burn框架从头训练一个 VAE,实现 MNIST 手写数字的生成,并实现潜在空间的插值可视化。