“我用Stable Diffusion画图,ChatGPT写文案,Midjourney做设计——它们的工作方式完全不一样,对吧?” 如果你这样想,说明你还没看到那根贯穿所有生成式AI的“金线”。今天,我们就来揭开这个秘密。

开篇:一张图引发的“血案”与认知革命

让我们从一个真实的故事开始。2021年,OpenAI的研究员发现了一个诡异现象:他们用DALL-E生成“宇航员骑马”的图片时,系统偶尔会画出“马骑宇航员”的鬼畜图像。更奇怪的是,这种错误不是随机的,而是有规律可循。

为什么? 因为DALL-E的核心——自回归模型(AR)——在理解“A在B上”这种空间关系时,本质上是从左到右猜词。当它看到“宇航员”时,已经开始想象画面;等看到“骑马”时,前面的“画面颜料”已经干了,修改成本太高。

与此同时,另一个实验室用扩散模型(Diffusion)生成同样的提示,却很少出现这种错误。扩散模型的工作方式截然不同:它从纯噪声开始,一步步去噪,每一步都能调整整个画面。

而使用GAN的研究者更郁闷:他们的模型要么生成完美的马,要么生成完美的宇航员,但几乎不可能生成“宇航员骑马”这种复杂组合——因为GAN的生成器太“急于求成”了。

这就是我们今天要探索的核心问题:为什么同样的生成任务,不同模型的表现天差地别?它们的本质差异到底是什么?

为了让你在深入细节前不迷路,我们先来看一张“生成式模型家族图谱”。这张图将贯穿全文,帮你理解这些看似迥异的模型如何共享同一套“思维框架”:

理解这个框架后,让我们正式开始这次探索。第一站,我们先回到“生成式AI的史前时代”——看看这个领域最初的朴素想法,以及它为何会裂变成今天的四大流派。

第一部分:生成问题的“本质三问”与四大流派的分道扬镳

哲学拷问:什么是“生成”?

在深入技术细节前,我们必须回答一个根本问题:让机器“生成”一张图片、一段文字,到底意味着什么?

从概率视角看,世界上所有图片(即使是梵高的《星空》)都只是高维空间中的一个点。比如256×256的RGB图片,就是256×256×3=196,608维空间中的一个点。而“生成”就是在这个196,608维空间中,找到那些“看起来像真实图片”的点

更精确地说:存在一个真实数据分布 p_data(x),它定义了哪些x是“合理图片”。我们的目标是学习一个模型分布 p_θ(x),让它尽可能接近 p_data(x)。

核心困境:如何表示一个分布?

这才是所有分歧的起点。想象你要向朋友描述“猫的长相”,有几种方法:

  1. 自回归法(AR):“先有个椭圆脸,脸上有两个三角形耳朵,耳朵下面有胡须...”(一步步描述)

  2. 标准化流法(Flow):“把‘猫’这个概念,通过一系列变形,变成具体图像”(可逆变换)

  3. 对抗法(GAN):“我画,你判断像不像猫,直到你分不清”(博弈优化)

  4. 扩散法(Diffusion):“先画一团毛线球,一点点理清,变成猫”(逐步去噪)

关键洞察:所有生成模型都在解决同一个问题,只是选择了不同的“数学表示”

历史的选择:为什么会有四条技术路线?

让我们坐上时光机,看看关键的历史节点:

2014年,GAN诞生:Ian Goodfellow在酒吧里想出“生成对抗”的点子时,他的核心洞察是:“我们不需要显式定义p_θ(x),只需要能从中采样就行。” 就像你不需要知道“美”的数学定义,只要能认出美的事物。

2016年,PixelCNN/RNN:DeepMind选择了一条“保守但可靠”的路:老老实实计算p_θ(x),哪怕要一个像素一个像素地生成。他们的信条是:“可计算的似然值,比炫酷的生成效果更重要。”

2018年,Glow模型:OpenAI尝试了“优雅但苛刻”的标准化流。想法很美:“如果生成过程是可逆的,我们就能精确计算概率。” 但代价是:每一层变换都必须可逆且行列式易计算——数学很美,工程很痛。

2020年,DDPM横空出世:Jonathan Ho等人发现了“噪声到数据的渐变”这条路。核心思想是:“我们不直接建模p(x),而是建模‘如何从噪声变成x’的逆过程。” 这条路意外地平衡了质量与稳定。

现在的问题来了:为什么这些方法会并存?为什么没有一种“通吃”所有场景?

让我们用一个代码实验来直观感受它们的差异:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons

# 生成一个简单的二维分布:月牙形数据
np.random.seed(42)
n_samples = 1000
X, _ = make_moons(n_samples=n_samples, noise=0.05)

# 可视化真实分布
plt.figure(figsize=(15, 10))

plt.subplot(2, 3, 1)
plt.scatter(X[:, 0], X[:, 1], s=1, alpha=0.5)
plt.title("真实数据分布 p_data(x)")
plt.xlim(-2, 2.5)
plt.ylim(-1.5, 1.5)
plt.grid(True, alpha=0.3)

# 模拟不同生成模型的“拟合”方式
def simulate_ar_samples(n=500):
    """模拟自回归模型:依次生成x1, x2"""
    samples = []
    for _ in range(n):
        # 先采样x1(边缘分布)
        x1 = np.random.normal(X[:, 0].mean(), X[:, 0].std() * 1.2)
        # 给定x1,采样x2(条件分布)
        # 简单模拟:在真实数据中找最近的x1,用其x2分布
        idx = np.argmin(np.abs(X[:, 0] - x1))
        x2 = X[idx, 1] + np.random.normal(0, 0.1)
        samples.append([x1, x2])
    return np.array(samples)

def simulate_flow_samples(n=500):
    """模拟流模型:从简单分布通过变换得到复杂分布"""
    # 基础分布:高斯
    z = np.random.normal(0, 1, (n, 2))
    # 模拟一个简单的非线性变换(实际流模型更复杂)
    samples = np.zeros_like(z)
    samples[:, 0] = z[:, 0] + 0.3 * z[:, 1]**2
    samples[:, 1] = z[:, 1] + 0.3 * z[:, 0]**2
    # 加上一个旋转和缩放,模仿月牙形
    angle = np.pi / 6
    rot = np.array([[np.cos(angle), -np.sin(angle)], 
                    [np.sin(angle), np.cos(angle)]])
    samples = samples @ rot
    samples = samples * 0.5 + np.array([0.5, -0.2])
    return samples

def simulate_gan_samples(n=500):
    """模拟GAN:可能陷入模式崩溃,或者拟合不完整"""
    # GAN容易只学到部分模式
    modes = [
        [0.8, 0.3],   # 月牙的一端
        [-0.2, -0.5]  # 月牙的另一端
    ]
    samples = []
    for _ in range(n):
        mode = modes[np.random.randint(0, 2)]
        sample = mode + np.random.normal(0, 0.15, 2)
        samples.append(sample)
    return np.array(samples)

def simulate_diffusion_samples(n=500, steps=50):
    """模拟扩散模型:从噪声逐步去噪"""
    # 扩散模型的逆过程:从噪声逐步“塑造”形状
    # 初始化:纯噪声
    samples = np.random.normal(0, 1.5, (n, 2))
    
    # 逐步去噪(简化的线性插值到数据分布)
    for step in range(steps):
        alpha = (step + 1) / steps  # 去噪进度
        # 计算“目标位置”:混合真实数据点
        target_indices = np.random.choice(len(X), n)
        targets = X[target_indices]
        
        # 线性插值:从噪声向目标移动
        samples = samples * (1 - alpha*0.5) + targets * (alpha*0.5)
        
        # 添加一点随机性(模拟采样噪声)
        samples += np.random.normal(0, 0.1 * (1 - alpha), (n, 2))
    
    return samples

# 绘制各种模型的模拟结果
models = [
    ("自回归模型 (AR)", simulate_ar_samples()),
    ("标准化流 (Flow)", simulate_flow_samples()),
    ("生成对抗网络 (GAN)", simulate_gan_samples()),
    ("扩散模型 (Diffusion)", simulate_diffusion_samples()),
]

for idx, (name, samples) in enumerate(models, 2):
    plt.subplot(2, 3, idx)
    plt.scatter(samples[:, 0], samples[:, 1], s=1, alpha=0.5, color='red')
    plt.scatter(X[:, 0], X[:, 1], s=1, alpha=0.1, color='blue')  # 真实分布淡色背景
    plt.title(f"{name}\n生成样本 vs 真实分布")
    plt.xlim(-2, 2.5)
    plt.ylim(-1.5, 1.5)
    plt.grid(True, alpha=0.3)

# 添加总结对比
plt.subplot(2, 3, 6)
plt.text(0.1, 0.9, "四大流派对比总结:", fontsize=12, fontweight='bold')
plt.text(0.1, 0.75, "• AR: 顺序生成,结构严谨", fontsize=10)
plt.text(0.1, 0.65, "• Flow: 精确可逆,数学优雅", fontsize=10)
plt.text(0.1, 0.55, "• GAN: 质量优先,训练博弈", fontsize=10)
plt.text(0.1, 0.45, "• Diffusion: 稳定去噪,步骤繁多", fontsize=10)
plt.text(0.1, 0.35, "共同目标: 逼近 p_data(x)", fontsize=10, style='italic')
plt.text(0.1, 0.25, "不同策略: 不同的数学表示", fontsize=10, style='italic')
plt.axis('off')

plt.suptitle("生成式模型四大流派如何拟合同一个分布", fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()

运行这段代码,你会看到四种模型用完全不同的方式“理解”和“生成”同一个月牙形分布。这个简单的二维示例,放大了高维图像生成中的核心差异

观察到的关键差异

  1. AR模型:生成的样本有清晰的“序列痕迹”——x2明显依赖于x1

  2. Flow模型:整体形状对了,但细节不够精确(变换太简单)

  3. GAN模型:只捕捉到两个主要模式,中间的过渡区域缺失(模式崩溃)

  4. Diffusion模型:最接近真实分布,但需要很多步骤

现在你有了直观感受,让我们深入每种模型的数学心脏。


第二部分:自回归模型——当生成变成“填空游戏”

核心思想:链式法则的暴力美学

自回归模型的思想朴素得令人感动:把高维联合分布p(x),分解为一系列一维条件分布的乘积

对于一个28×28的MNIST手写数字(784维):

text

p(图像) = p(像素1) × p(像素2 | 像素1) × p(像素3 | 像素1,像素2) × ... × p(像素784 | 前783个像素)

这就像玩一个超大型的“填字游戏”:你已知左上角第一个像素,猜第二个;已知前两个,猜第三个...直到填满整个画布。

数学形式:简洁而沉重

自回归模型的数学表达异常简洁:

p_θ(x) = ∏_{t=1}^T p_θ(x_t | x_<t)

其中x_t是第t个维度(像素、词元等),x_<t是所有之前的维度。

简洁的代价

  1. 顺序依赖:必须按固定顺序生成

  2. 指数级复杂度:理论上需要建模 p(x_t | x_<t) 这个条件分布,其条件集大小随时间线性增长

神经网络实现:从PixelRNN到Transformer

早期的PixelRNN/CNN用RNN或CNN建模条件分布,但真正的革命来自注意力机制

import torch
import torch.nn as nn
import torch.nn.functional as F

class SimpleAutoregressiveModel(nn.Module):
    """极简的自回归模型演示"""
    def __init__(self, vocab_size=256, embed_dim=128, hidden_dim=256):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim)
        # 使用因果掩码的Transformer解码器
        self.transformer_layer = nn.TransformerDecoderLayer(
            d_model=embed_dim, nhead=8, dim_feedforward=hidden_dim
        )
        self.decoder = nn.TransformerDecoder(self.transformer_layer, num_layers=3)
        self.output_proj = nn.Linear(embed_dim, vocab_size)
        
    def forward(self, x):
        # x: (batch_size, seq_len)
        embeddings = self.embedding(x)  # (batch_size, seq_len, embed_dim)
        
        # 创建因果注意力掩码:只能看到当前位置及之前的位置
        seq_len = x.size(1)
        causal_mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1).bool()
        
        # Transformer解码
        output = self.decoder(
            embeddings.transpose(0, 1),  # Transformer期望(seq_len, batch_size, dim)
            memory=None,  # 自回归,没有编码器记忆
            tgt_mask=causal_mask
        ).transpose(0, 1)  # 恢复(batch_size, seq_len, dim)
        
        logits = self.output_proj(output)
        return logits
    
    def generate(self, start_token, max_len=100, temperature=1.0):
        """自回归生成"""
        self.eval()
        with torch.no_grad():
            tokens = [start_token]
            
            for _ in range(max_len - 1):
                # 准备当前序列
                input_seq = torch.tensor([tokens], dtype=torch.long)
                
                # 前向传播
                logits = self.forward(input_seq)[0, -1, :]  # 最后一个位置的logits
                
                # 温度采样
                probs = F.softmax(logits / temperature, dim=0)
                next_token = torch.multinomial(probs, 1).item()
                
                tokens.append(next_token)
                
                # 简单停止条件(实际中更复杂)
                if next_token == 0:  # 假设0是结束符
                    break
                    
        return tokens

# 演示:自回归生成如何累积误差
def demonstrate_ar_error_propagation():
    """展示自回归生成的误差传播问题"""
    print("自回归生成的'蝴蝶效应'演示:")
    print("-" * 50)
    
    # 模拟一个简单的文本生成
    ground_truth = ["我", "爱", "机", "器", "学", "习"]
    
    # 完美生成
    print("完美生成(每一步100%准确):")
    print(" → ".join(ground_truth))
    
    # 包含一个早期误差的生成
    print("\n早期误差(第2步出错):")
    generated = ["我", "恨", "", "", "", ""]  # 第2个词错了
    # 后续生成基于错误上下文
    # 假设基于"我恨"的后续可能
    possible_continuations = [
        ["我", "恨", "这", "个", "世", "界"],
        ["我", "恨", "数", "学", "课", "程"],
        ["我", "恨", "早", "上", "起", "床"]
    ]
    
    for i, cont in enumerate(possible_continuations):
        print(f"  可能性{i+1}: {' → '.join(cont)}")
    
    return ground_truth, possible_continuations

# GPT系列的本质:规模拯救一切
print("GPT的秘密:用海量数据和模型容量,让条件分布 p(x_t | x_<t) 尽可能准确")
print("但根本限制仍在:必须按顺序生成,无法'回头修改'")

自回归的优势与致命伤

优势

  1. 似然可计算:可以直接计算p(x),便于模型比较和选择

  2. 理论优雅:链式法则在数学上无可挑剔

  3. 成功案例:GPT系列证明了它的强大

致命伤

  1. 顺序瓶颈:生成时间复杂度O(n),无法并行

  2. 误差累积:早期错误会放大(蝴蝶效应)

  3. 上下文遗忘:生成长序列时,早期信息可能被稀释

关键洞察:自回归模型本质上是将生成问题转化为序列预测问题。它牺牲了生成速度,换取了训练稳定性和似然可计算性。

当我们被自回归的缓慢折磨时,另一批研究者想到了一个完全不同的思路:如果生成过程是可逆的,会怎样? 这就引出了标准化流模型。


第三部分:标准化流——当数学优雅遇见工程现实

核心思想:可逆变换的优雅之舞

标准化流的核心想法极其优雅:找一个可逆且雅可比行列式易计算的变换f,将简单分布(如高斯)映射到复杂分布

数学上:

z ~ p_z(z)  # 简单分布,如N(0, I)
x = f(z)    # 通过可逆变换
p_x(x) = p_z(f^{-1}(x)) × |det(J_{f^{-1}}(x))|

其中|det(J)|是变换的雅可比行列式绝对值,用于“体积修正”。

物理比喻:想象一个可无限拉伸、压缩但不撕裂的橡皮泥。标准化流就是学习如何把“标准高斯球”橡皮泥,捏成“猫形状”橡皮泥,并且这个过程可逆

数学细节:从变量代换公式到实际实现

变量代换公式(Change of Variables):

p_x(x) = p_z(z) × |det(∂z/∂x)|, 其中 z = f^{-1}(x)

关键在于:变换f必须满足:

  1. 可逆:存在f^{-1}

  2. 雅可比行列式易计算:否则概率密度无法计算

  3. 足够灵活:能拟合复杂分布

实际实现:从NICE、RealNVP到Glow

import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np

class AffineCouplingLayer(nn.Module):
    """RealNVP中的仿射耦合层(标准化流的核心组件)"""
    def __init__(self, dim, hidden_dim=256):
        super().__init__()
        self.dim = dim
        self.split_dim = dim // 2
        
        # 网络s和t:计算缩放和平移参数
        self.net = nn.Sequential(
            nn.Linear(self.split_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, (dim - self.split_dim) * 2)  # 输出s和t
        )
        
    def forward(self, x, reverse=False):
        """前向:x -> z,反向:z -> x"""
        if not reverse:
            # 分割输入
            x1, x2 = x[:, :self.split_dim], x[:, self.split_dim:]
            
            # 计算仿射参数(s, t)
            st = self.net(x1)
            s, t = st.chunk(2, dim=1)
            s = torch.tanh(s)  # 限制s的范围,保证稳定性
            
            # 仿射变换
            z2 = x2 * torch.exp(s) + t
            z = torch.cat([x1, z2], dim=1)
            
            # 计算对数雅可比行列式
            log_det = s.sum(dim=1)  # 对角线雅可比
            
            return z, log_det
        
        else:
            # 逆变换:z -> x
            z1, z2 = x[:, :self.split_dim], x[:, self.split_dim:]
            
            # 计算仿射参数(需要从z1计算,因为前向时只用x1)
            st = self.net(z1)
            s, t = st.chunk(2, dim=1)
            s = torch.tanh(s)
            
            # 逆仿射变换
            x2 = (z2 - t) * torch.exp(-s)
            x = torch.cat([z1, x2], dim=1)
            
            # 逆变换的对数雅可比是负的
            log_det = -s.sum(dim=1)
            
            return x, log_det

class NormalizingFlow(nn.Module):
    """完整的标准化流模型"""
    def __init__(self, dim, num_layers=4):
        super().__init__()
        self.dim = dim
        self.layers = nn.ModuleList()
        
        # 创建多个耦合层
        for i in range(num_layers):
            self.layers.append(AffineCouplingLayer(dim))
            # 每隔一层后交换分割维度,确保所有维度都被变换
            if i % 2 == 0:
                self.layers.append(PermuteLayer(dim))
    
    def forward(self, x, reverse=False):
        """前向:x -> z,反向:z -> x"""
        log_det_sum = 0
        
        if not reverse:
            z = x
            for layer in self.layers:
                z, log_det = layer(z, reverse=False)
                log_det_sum = log_det_sum + log_det
            return z, log_det_sum
        else:
            # 逆序应用逆变换
            z = x
            for layer in reversed(self.layers):
                z, log_det = layer(z, reverse=True)
                log_det_sum = log_det_sum + log_det
            return z, log_det_sum
    
    def log_prob(self, x):
        """计算x的对数概率密度"""
        z, log_det = self.forward(x, reverse=False)
        
        # 基础分布:标准高斯
        log_pz = -0.5 * (z**2).sum(dim=1) - 0.5 * self.dim * np.log(2*np.pi)
        
        # 变量代换:p(x) = p(z) × |det(dz/dx)|
        log_px = log_pz + log_det
        
        return log_px
    
    def sample(self, num_samples):
        """从模型采样"""
        # 从基础分布采样
        z = torch.randn(num_samples, self.dim)
        
        # 通过逆变换得到x
        x, _ = self.forward(z, reverse=True)
        
        return x

class PermuteLayer(nn.Module):
    """置换层:交换维度顺序"""
    def __init__(self, dim):
        super().__init__()
        # 创建一个随机但固定的置换
        self.perm = torch.randperm(dim)
        self.inv_perm = torch.argsort(self.perm)
        
    def forward(self, x, reverse=False):
        if not reverse:
            return x[:, self.perm], 0  # 置换的对数行列式为0
        else:
            return x[:, self.inv_perm], 0

# 演示标准化流的工作方式
def demonstrate_flow_transformation():
    """可视化标准化流如何变换分布"""
    import matplotlib.pyplot as plt
    
    # 创建简单流模型
    flow = NormalizingFlow(dim=2, num_layers=6)
    
    # 从基础分布(高斯)采样
    num_samples = 1000
    z = torch.randn(num_samples, 2)
    
    # 应用逆变换得到复杂分布
    x, _ = flow.forward(z, reverse=True)
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(10, 4))
    
    # 基础分布
    z_np = z.numpy()
    axes[0].scatter(z_np[:, 0], z_np[:, 1], s=1, alpha=0.5)
    axes[0].set_title("基础分布: 标准高斯")
    axes[0].set_xlim(-4, 4)
    axes[0].set_ylim(-4, 4)
    axes[0].grid(True, alpha=0.3)
    
    # 变换后的分布
    x_np = x.detach().numpy()
    axes[1].scatter(x_np[:, 0], x_np[:, 1], s=1, alpha=0.5)
    axes[1].set_title("变换后分布: 流模型输出")
    axes[1].set_xlim(-4, 4)
    axes[1].set_ylim(-4, 4)
    axes[1].grid(True, alpha=0.3)
    
    plt.suptitle("标准化流: 通过可逆变换将简单分布映射到复杂分布", fontweight='bold')
    plt.tight_layout()
    plt.show()
    
    return flow

# 运行演示
print("标准化流的核心: 可逆变换 + 精确概率计算")
print("关键限制: 每一层变换都必须可逆且雅可比易计算")
flow_model = demonstrate_flow_transformation()

流的优势与局限

优势

  1. 精确似然:可以直接优化对数似然

  2. 可逆性:编码和解码是同一过程的正反方向

  3. 隐空间可解释:z空间通常是结构化的

局限

  1. 架构约束:网络必须是可逆的,限制了模型容量

  2. 计算成本:需要计算雅可比行列式,内存和计算量大

  3. 表达能力:需要很多层才能拟合复杂分布

关键洞察:标准化流是数学家的选择——优雅、精确,但工程上苛刻。当研究人员在可逆变换的约束中挣扎时,另一条更“实用主义”的道路正在兴起:如果不在乎精确概率,只在乎生成质量呢?


第四部分:生成对抗网络——当博弈论遇见深度学习

核心思想:造假者与鉴定师的猫鼠游戏

GAN的核心思想来自博弈论:训练一个生成器G和一个判别器D,让它们相互对抗、共同进化

生成器G的目标:生成逼真数据,骗过判别器。
判别器D的目标:区分真实数据与生成数据。

博弈的纳什均衡:当生成器生成的数据分布与真实分布完全一致,且判别器无法区分(输出0.5)时。

数学框架:最小最大博弈

GAN的原始目标函数:

min_G max_D V(D, G) = E_{x~p_data}[log D(x)] + E_{z~p_z}[log(1 - D(G(z)))]
  • D(x):判别器认为x是真实数据的概率

  • G(z):生成器从噪声z生成的数据

直观理解:判别器想最大化自己的“鉴真能力”,生成器想最小化判别器的“鉴真能力”。

实现细节:从DCGAN到StyleGAN

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np

class Generator(nn.Module):
    """DCGAN风格的生成器"""
    def __init__(self, z_dim=100, img_channels=3):
        super().__init__()
        self.z_dim = z_dim
        
        self.main = nn.Sequential(
            # 输入: z_dim维噪声
            nn.ConvTranspose2d(z_dim, 512, 4, 1, 0, bias=False),
            nn.BatchNorm2d(512),
            nn.ReLU(True),
            # 当前: (512, 4, 4)
            
            nn.ConvTranspose2d(512, 256, 4, 2, 1, bias=False),
            nn.BatchNorm2d(256),
            nn.ReLU(True),
            # 当前: (256, 8, 8)
            
            nn.ConvTranspose2d(256, 128, 4, 2, 1, bias=False),
            nn.BatchNorm2d(128),
            nn.ReLU(True),
            # 当前: (128, 16, 16)
            
            nn.ConvTranspose2d(128, 64, 4, 2, 1, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(True),
            # 当前: (64, 32, 32)
            
            nn.ConvTranspose2d(64, img_channels, 4, 2, 1, bias=False),
            nn.Tanh()
            # 输出: (img_channels, 64, 64)
        )
        
    def forward(self, z):
        # z: (batch_size, z_dim)
        z = z.view(-1, self.z_dim, 1, 1)
        img = self.main(z)
        return img

class Discriminator(nn.Module):
    """DCGAN风格的判别器"""
    def __init__(self, img_channels=3):
        super().__init__()
        
        self.main = nn.Sequential(
            # 输入: (img_channels, 64, 64)
            nn.Conv2d(img_channels, 64, 4, 2, 1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),
            # 当前: (64, 32, 32)
            
            nn.Conv2d(64, 128, 4, 2, 1, bias=False),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2, inplace=True),
            # 当前: (128, 16, 16)
            
            nn.Conv2d(128, 256, 4, 2, 1, bias=False),
            nn.BatchNorm2d(256),
            nn.LeakyReLU(0.2, inplace=True),
            # 当前: (256, 8, 8)
            
            nn.Conv2d(256, 512, 4, 2, 1, bias=False),
            nn.BatchNorm2d(512),
            nn.LeakyReLU(0.2, inplace=True),
            # 当前: (512, 4, 4)
            
            nn.Conv2d(512, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
            # 输出: (1, 1, 1) -> 标量概率
        )
        
    def forward(self, img):
        # img: (batch_size, img_channels, 64, 64)
        validity = self.main(img)
        return validity.view(-1, 1)

class GANTrainer:
    """GAN训练演示(简化的训练循环)"""
    def __init__(self, z_dim=100, img_size=64):
        self.z_dim = z_dim
        self.img_size = img_size
        
        self.generator = Generator(z_dim)
        self.discriminator = Discriminator()
        
        # 优化器
        self.optimizer_G = optim.Adam(self.generator.parameters(), lr=0.0002, betas=(0.5, 0.999))
        self.optimizer_D = optim.Adam(self.discriminator.parameters(), lr=0.0002, betas=(0.5, 0.999))
        
        # 损失函数
        self.criterion = nn.BCELoss()
        
    def train_step(self, real_imgs):
        """一个训练步骤"""
        batch_size = real_imgs.size(0)
        
        # 创建标签
        real_labels = torch.ones(batch_size, 1)
        fake_labels = torch.zeros(batch_size, 1)
        
        # ---------------------
        #  训练判别器
        # ---------------------
        self.optimizer_D.zero_grad()
        
        # 真实图片的损失
        real_validity = self.discriminator(real_imgs)
        d_real_loss = self.criterion(real_validity, real_labels)
        
        # 生成假图片
        z = torch.randn(batch_size, self.z_dim)
        fake_imgs = self.generator(z)
        
        # 假图片的损失
        fake_validity = self.discriminator(fake_imgs.detach())  # 阻止梯度传到生成器
        d_fake_loss = self.criterion(fake_validity, fake_labels)
        
        # 判别器总损失
        d_loss = d_real_loss + d_fake_loss
        d_loss.backward()
        self.optimizer_D.step()
        
        # ---------------------
        #  训练生成器
        # ---------------------
        self.optimizer_G.zero_grad()
        
        # 重新生成图片(或复用之前的,但需要重新计算梯度)
        fake_imgs = self.generator(z)
        
        # 生成器的目标:让判别器认为假图片是真的
        fake_validity = self.discriminator(fake_imgs)
        g_loss = self.criterion(fake_validity, real_labels)  # 注意:目标是real_labels!
        
        g_loss.backward()
        self.optimizer_G.step()
        
        return {
            'd_loss': d_loss.item(),
            'g_loss': g_loss.item(),
            'real_score': real_validity.mean().item(),
            'fake_score': fake_validity.mean().item()
        }
    
    def generate_samples(self, num_samples=16):
        """生成样本"""
        self.generator.eval()
        with torch.no_grad():
            z = torch.randn(num_samples, self.z_dim)
            samples = self.generator(z)
        self.generator.train()
        return samples

# GAN的经典问题演示
def demonstrate_gan_problems():
    """可视化GAN的典型问题"""
    print("GAN的典型训练问题:")
    print("=" * 50)
    
    problems = {
        "模式崩溃 (Mode Collapse)": [
            "生成器发现只生成某几类样本就能骗过判别器",
            "导致生成样本多样性不足",
            "比如人脸生成中只生成年轻白人女性"
        ],
        "梯度消失 (Gradient Vanishing)": [
            "判别器太强,生成器梯度趋近于0",
            "生成器无法从判别器得到有效反馈",
            "训练停滞"
        ],
        "训练不稳定": [
            "生成器和判别器的平衡很难维持",
            "损失函数震荡,不收敛",
            "需要精心调参"
        ],
        "评估困难": [
            "无法计算似然,难以定量评估",
            "依赖人工评估或FID等间接指标"
        ]
    }
    
    for problem, description in problems.items():
        print(f"\n{problem}:")
        for line in description:
            print(f"  • {line}")
    
    return problems

# WGAN-GP的改进思想
def explain_wgan_gp():
    """解释WGAN-GP如何解决GAN的问题"""
    print("\n\nWGAN-GP的改进:")
    print("=" * 50)
    
    improvements = {
        "Wasserstein距离": [
            "使用Earth-Mover距离代替JS散度",
            "即使分布不重叠,梯度仍然存在",
            "损失值有明确含义(越小越好)"
        ],
        "梯度惩罚": [
            "在真实和生成样本之间插值",
            "强制判别器在插值点上的梯度范数接近1",
            "满足Lipschitz约束,稳定训练"
        ],
        "移除Sigmoid": [
            "判别器输出无界实数",
            "不再是分类器,而是'批评家'",
            "训练更稳定"
        ]
    }
    
    for improvement, details in improvements.items():
        print(f"\n{improvement}:")
        for line in details:
            print(f"  • {line}")
    
    return improvements

# 运行演示
demonstrate_gan_problems()
explain_wgan_gp()

GAN的优势与挑战

优势

  1. 生成质量高:尤其在高分辨率图像生成上表现出色

  2. 采样快速:一次前向传播即可生成样本

  3. 无需似然:摆脱了概率密度计算的约束

挑战

  1. 训练不稳定:需要精心平衡生成器和判别器

  2. 模式崩溃:生成器可能只生成部分模式

  3. 评估困难:没有似然值,评估依赖间接指标

关键洞察:GAN是实用主义者的选择——牺牲理论严谨性,追求生成质量。但当研究人员在GAN的训练不稳定中挣扎时,一条“折中路线”正在浮现:能不能结合可计算的似然和高质量的生成?


第五部分:扩散模型——当物理学遇见深度学习

核心思想:热力学的逆向工程

扩散模型的灵感来自非平衡热力学:数据通过逐步加噪变成纯噪声(正向过程),然后学习如何逆向去噪(逆向过程)

物理比喻:一滴墨水在水中扩散的过程是可观测的(正向)。如果我们能精确学习这个过程的逆过程,就能让扩散的墨水重新聚集成一滴。

数学框架:从DDPM到SDE/ODE

正向过程(加噪)
q(x_t | x_{t-1}) = N(x_t; √(1-β_t) x_{t-1}, β_t I)

其中β_t是噪声调度(通常从很小值线性增加到接近1)。

逆向过程(去噪)
p_θ(x_{t-1} | x_t) = N(x_{t-1}; μ_θ(x_t, t), Σ_θ(x_t, t))

关键洞察:当β_t很小时,逆向过程也近似高斯分布。

实现细节:从噪声预测到得分匹配

import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt

class SinusoidalPositionEmbedding(nn.Module):
    """正弦位置嵌入(用于时间步t)"""
    def __init__(self, dim):
        super().__init__()
        self.dim = dim
        
    def forward(self, t):
        device = t.device
        half_dim = self.dim // 2
        embeddings = np.log(10000) / (half_dim - 1)
        embeddings = torch.exp(torch.arange(half_dim, device=device) * -embeddings)
        embeddings = t[:, None] * embeddings[None, :]
        embeddings = torch.cat([torch.sin(embeddings), torch.cos(embeddings)], dim=-1)
        return embeddings

class SimpleUNet(nn.Module):
    """简化的U-Net,用于扩散模型"""
    def __init__(self, in_channels=3, time_dim=256):
        super().__init__()
        
        # 时间嵌入
        self.time_mlp = nn.Sequential(
            SinusoidalPositionEmbedding(time_dim),
            nn.Linear(time_dim, time_dim),
            nn.ReLU(),
            nn.Linear(time_dim, time_dim)
        )
        
        # 编码器
        self.enc1 = nn.Conv2d(in_channels, 64, 3, padding=1)
        self.enc2 = nn.Conv2d(64, 128, 3, stride=2, padding=1)
        self.enc3 = nn.Conv2d(128, 256, 3, stride=2, padding=1)
        
        # 中间层
        self.mid = nn.Conv2d(256, 256, 3, padding=1)
        
        # 解码器
        self.dec3 = nn.ConvTranspose2d(256, 128, 3, stride=2, padding=1, output_padding=1)
        self.dec2 = nn.ConvTranspose2d(128, 64, 3, stride=2, padding=1, output_padding=1)
        self.dec1 = nn.Conv2d(64, in_channels, 3, padding=1)
        
        # 时间嵌入投影
        self.time_proj3 = nn.Linear(time_dim, 256)
        self.time_proj2 = nn.Linear(time_dim, 128)
        self.time_proj1 = nn.Linear(time_dim, 64)
        
    def forward(self, x, t):
        # x: (B, C, H, W), t: (B,)
        
        # 时间嵌入
        t_emb = self.time_mlp(t)
        
        # 编码器
        h1 = F.relu(self.enc1(x))  # (B, 64, H, W)
        
        h2 = F.relu(self.enc2(h1))  # (B, 128, H/2, W/2)
        h2 = h2 + self.time_proj2(t_emb)[:, :, None, None]
        
        h3 = F.relu(self.enc3(h2))  # (B, 256, H/4, W/4)
        h3 = h3 + self.time_proj3(t_emb)[:, :, None, None]
        
        # 中间层
        h_mid = F.relu(self.mid(h3))
        
        # 解码器(带跳跃连接)
        h = F.relu(self.dec3(h_mid + h3))
        h = h + self.time_proj2(t_emb)[:, :, None, None]
        
        h = F.relu(self.dec2(h + h2))
        h = h + self.time_proj1(t_emb)[:, :, None, None]
        
        h = self.dec1(h + h1)
        
        return h

class DiffusionModel:
    """简化的扩散模型实现"""
    def __init__(self, T=1000, beta_start=0.0001, beta_end=0.02):
        self.T = T
        
        # 定义噪声调度(线性)
        self.betas = torch.linspace(beta_start, beta_end, T)
        self.alphas = 1 - self.betas
        self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
        
        # 预计算用于训练和采样的值
        self.sqrt_alphas_cumprod = torch.sqrt(self.alphas_cumprod)
        self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1 - self.alphas_cumprod)
        
        # 模型
        self.model = SimpleUNet(in_channels=1)  # 为简单起见,使用单通道
        
    def forward_diffusion(self, x0, t):
        """正向扩散过程:x0 -> xt"""
        sqrt_alpha_cumprod_t = self.sqrt_alphas_cumprod[t].view(-1, 1, 1, 1)
        sqrt_one_minus_alpha_cumprod_t = self.sqrt_one_minus_alphas_cumprod[t].view(-1, 1, 1, 1)
        
        noise = torch.randn_like(x0)
        xt = sqrt_alpha_cumprod_t * x0 + sqrt_one_minus_alpha_cumprod_t * noise
        
        return xt, noise
    
    def train_step(self, x0, optimizer):
        """一个训练步骤"""
        batch_size = x0.size(0)
        
        # 随机采样时间步
        t = torch.randint(0, self.T, (batch_size,))
        
        # 正向扩散得到xt
        xt, noise = self.forward_diffusion(x0, t)
        
        # 预测噪声
        predicted_noise = self.model(xt, t)
        
        # 损失:预测噪声和真实噪声的均方误差
        loss = F.mse_loss(predicted_noise, noise)
        
        # 优化
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        return loss.item()
    
    @torch.no_grad()
    def sample(self, num_samples=1, img_size=32):
        """采样:从噪声生成数据"""
        # 从纯噪声开始
        x = torch.randn(num_samples, 1, img_size, img_size)
        
        # 逆向过程
        for t in reversed(range(self.T)):
            # 当前时间步
            t_tensor = torch.full((num_samples,), t, dtype=torch.long)
            
            # 预测噪声
            predicted_noise = self.model(x, t_tensor)
            
            # 计算均值
            alpha_t = self.alphas[t]
            alpha_cumprod_t = self.alphas_cumprod[t]
            
            if t > 0:
                noise = torch.randn_like(x)
            else:
                noise = 0
                
            # DDPM采样公式
            x = (1 / torch.sqrt(alpha_t)) * (
                x - ((1 - alpha_t) / torch.sqrt(1 - alpha_cumprod_t)) * predicted_noise
            ) + torch.sqrt(self.betas[t]) * noise
            
        return x
    
    def visualize_diffusion(self, sample_img, num_steps=10):
        """可视化扩散过程"""
        fig, axes = plt.subplots(2, num_steps // 2, figsize=(15, 6))
        axes = axes.flatten()
        
        # 选择要可视化的时间步
        steps = np.linspace(0, self.T-1, num_steps, dtype=int)
        
        for idx, t in enumerate(steps):
            # 对同一样本应用不同时间步的扩散
            t_tensor = torch.tensor([t])
            xt, _ = self.forward_diffusion(sample_img.unsqueeze(0), t_tensor)
            
            # 可视化
            ax = axes[idx]
            img = xt[0, 0].numpy()
            ax.imshow(img, cmap='gray', vmin=-1, vmax=1)
            ax.set_title(f't = {t}')
            ax.axis('off')
        
        plt.suptitle("正向扩散过程:数据逐渐变成噪声", fontweight='bold')
        plt.tight_layout()
        plt.show()

# 创建和演示扩散模型
def demonstrate_diffusion_process():
    """演示扩散过程"""
    print("扩散模型的核心: 逐步加噪与去噪")
    print("=" * 50)
    
    # 创建一个简单样本(二值图像)
    img_size = 32
    sample_img = torch.zeros(1, 1, img_size, img_size)
    
    # 画一个简单的形状(圆形)
    center = img_size // 2
    radius = img_size // 4
    for i in range(img_size):
        for j in range(img_size):
            if (i - center)**2 + (j - center)**2 <= radius**2:
                sample_img[0, 0, i, j] = 1.0
    
    # 创建扩散模型
    diffusion = DiffusionModel(T=200)  # 使用较小的T以便快速演示
    
    # 可视化扩散过程
    diffusion.visualize_diffusion(sample_img, num_steps=8)
    
    return diffusion

# 运行演示
diffusion_model = demonstrate_diffusion_process()

# 解释扩散模型的数学原理
def explain_diffusion_math():
    """解释扩散模型的数学原理"""
    print("\n扩散模型的数学核心:")
    print("=" * 50)
    
    concepts = {
        "正向过程 (q)": [
            "逐步加噪: x₀ → x₁ → ... → x_T",
            "x_t = √ᾱ_t x₀ + √(1-ᾱ_t) ε, 其中ε ~ N(0, I)",
            "ᾱ_t = ∏_{s=1}^t (1-β_s), β_t是噪声调度"
        ],
        "逆向过程 (p_θ)": [
            "学习去噪: x_T → x_{T-1} → ... → x₀",
            "p_θ(x_{t-1}|x_t) = N(x_{t-1}; μ_θ(x_t, t), Σ_θ(x_t, t))",
            "关键是学习均值μ_θ,通常固定方差Σ_θ"
        ],
        "训练目标": [
            "简化目标: 预测噪声 ε_θ(x_t, t)",
            "L_simple = E_{t,x₀,ε}[||ε - ε_θ(√ᾱ_t x₀ + √(1-ᾱ_t) ε, t)||²]",
            "不需要变分下界(ELBO)的复杂推导"
        ],
        "采样过程": [
            "从x_T ~ N(0, I)开始",
            "迭代计算: x_{t-1} = 1/√α_t (x_t - (1-α_t)/√(1-ᾱ_t) ε_θ(x_t, t)) + √β_t z",
            "z ~ N(0, I), 当t>0; z=0, 当t=0"
        ]
    }
    
    for concept, details in concepts.items():
        print(f"\n{concept}:")
        for line in details:
            print(f"  • {line}")
    
    return concepts

explain_diffusion_math()

# 加速采样方法
def explain_accelerated_sampling():
    """解释扩散模型的加速采样方法"""
    print("\n扩散模型的加速采样技术:")
    print("=" * 50)
    
    methods = {
        "DDIM (Denoising Diffusion Implicit Models)": [
            "关键洞察: 扩散过程可以是非马尔科夫的",
            "允许更大的步长,跳过中间步骤",
            "保持相同的训练目标,但采样更快"
        ],
        "知识蒸馏": [
            "用大模型训练小模型",
            "小模型学习'一步去噪'",
            "例如: Progressive Distillation"
        ],
        "潜在扩散模型 (LDM)": [
            "在VAE的潜在空间中操作扩散",
            "维度大幅降低,计算量减少",
            "Stable Diffusion使用此技术"
        ],
        "一致性模型": [
            "学习将任何噪声水平直接映射到数据",
            "支持一步生成",
            "质量和速度的折中"
        ]
    }
    
    for method, details in methods.items():
        print(f"\n{method}:")
        for line in details:
            print(f"  • {line}")
    
    return methods

explain_accelerated_sampling()

扩散模型:平衡的艺术

优势

  1. 训练稳定:目标简单明确(预测噪声)

  2. 似然可计算:可以估计变分下界(ELBO)

  3. 生成质量高:尤其适合图像生成

  4. 可扩展性强:容易与条件生成结合

挑战

  1. 采样缓慢:需要很多步(通常100-1000步)

  2. 计算成本高:每个采样步都需要模型前向传播

  3. 参数化选择:预测噪声、预测x0、预测得分等选择

关键洞察:扩散模型是工程师的选择——在理论严谨性(可计算似然)和生成质量之间找到了平衡点。它吸收了过去模型的优点:

  • 从GAN借鉴了高质量生成的追求

  • 从标准化流借鉴了可计算似然的思想

  • 从自回归模型借鉴了逐步生成的框架


第六部分:统一视角——四大流派的内在联系

深层联系:得分匹配、能量模型与马尔科夫链

表面上各不相同的四大流派,实际上共享着深刻的数学联系:

联系1:所有模型都在近似同一个目标

无论AR、Flow、GAN还是Diffusion,最终目标都是近似真实数据分布p_data(x)

联系2:扩散模型是AR模型的连续版本

自回归模型在离散维度上顺序生成,扩散模型在连续时间上逐步生成。如果把时间离散化,扩散模型就变成了一个特殊的自回归模型。

联系3:GAN可以看作是单步扩散模型

考虑极端情况:如果扩散模型只有一步(T=1),那么它需要直接从噪声生成数据——这正是GAN生成器的工作。

联系4:标准化流是确定性扩散

如果扩散过程的每一步都是确定性的(无噪声),那么就变成了一个可逆变换——这就是标准化流。

联系5:得分匹配的统一视角

扩散模型的核心是得分匹配(学习梯度∇log p(x))。有趣的是:

  • 扩散模型:显式地进行得分匹配

  • GAN:隐式地进行得分匹配(通过判别器梯度)

  • 流模型:通过变量代换得到得分

  • 自回归模型:通过条件分布间接建模得分

数学统一框架:基于得分的生成模型

考虑基于得分的生成模型框架:

dx = f(x,t)dt + g(t)dw

不同的生成模型对应不同的f和g选择:

  • 扩散模型:f(x,t) = -β(t)x/2, g(t)=√β(t)

  • 流模型:g(t)=0(确定性),f是可逆神经网络

  • 自回归模型:离散时间的特殊形式

  • GAN:难以用SDE表示,但可通过动力学系统理解

实际选择指南:何时用哪种模型?

让我们用一个决策树来总结:

def choose_generative_model(scenario):
    """根据场景选择生成模型"""
    decisions = []
    
    print(f"场景: {scenario}")
    print("=" * 40)
    
    if scenario["need_fast_sampling"]:
        decisions.append("需要快速采样 → 考虑GAN或流模型")
        if scenario["need_exact_likelihood"]:
            decisions.append("同时需要精确似然 → 选择流模型")
            decisions.append("注意: 流模型表达能力受限")
        else:
            decisions.append("不需要精确似然 → 选择GAN")
            decisions.append("警告: GAN可能训练不稳定")
    else:
        decisions.append("可以接受慢采样 → 考虑扩散模型或自回归模型")
        if scenario["data_type"] == "sequential":
            decisions.append("数据是序列的 → 选择自回归模型")
            decisions.append("优势: 天然适合序列,如文本")
        else:
            decisions.append("数据是非序列的 → 选择扩散模型")
            decisions.append("优势: 高质量图像生成")
    
    if scenario["need_conditional_generation"]:
        decisions.append("需要条件生成 → 扩散模型最灵活")
    
    if scenario["compute_budget"] == "low":
        decisions.append("计算预算低 → 谨慎选择扩散模型(采样慢)")
    
    if scenario["stability_important"]:
        decisions.append("训练稳定性重要 → 扩散模型最稳定")
    
    return decisions

# 示例场景
scenarios = [
    {
        "name": "文本生成",
        "need_fast_sampling": True,
        "need_exact_likelihood": False,
        "data_type": "sequential",
        "need_conditional_generation": True,
        "compute_budget": "medium",
        "stability_important": True
    },
    {
        "name": "高分辨率图像生成",
        "need_fast_sampling": False,
        "need_exact_likelihood": False,
        "data_type": "non_sequential",
        "need_conditional_generation": True,
        "compute_budget": "high",
        "stability_important": True
    },
    {
        "name": "密度估计任务",
        "need_fast_sampling": False,
        "need_exact_likelihood": True,
        "data_type": "non_sequential",
        "need_conditional_generation": False,
        "compute_budget": "medium",
        "stability_important": True
    },
    {
        "name": "实时图像编辑",
        "need_fast_sampling": True,
        "need_exact_likelihood": False,
        "data_type": "non_sequential",
        "need_conditional_generation": True,
        "compute_budget": "high",
        "stability_important": False
    }
]

for scenario in scenarios:
    decisions = choose_generative_model(scenario)
    print(f"\n{scenario['name']}的模型选择建议:")
    for decision in decisions:
        print(f"  • {decision}")
    print()

未来趋势:融合与统一

当前的研究趋势是融合各派优点

  1. 扩散模型 + GAN:使用GAN加速扩散采样

  2. 流模型 + 扩散:在流模型中引入随机性

  3. 自回归 + 扩散:在潜在空间中使用扩散

统一框架的展望

未来模型 = 流的可逆性 + GAN的质量 + 扩散的稳定性 + 自回归的序列建模能力

第七部分:实战对比——同一任务,四种实现

让我们用一个具体任务来对比四种模型:生成MNIST手写数字

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import numpy as np
import matplotlib.pyplot as plt

# 数据准备
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)

# 1. 自回归模型实现(PixelCNN风格)
class PixelCNN(nn.Module):
    def __init__(self, in_channels=1, num_classes=10):
        super().__init__()
        # 简化的PixelCNN实现
        self.embedding = nn.Embedding(256, 64)  # 像素值0-255
        self.conv1 = nn.Conv2d(64, 128, 3, padding=1)
        self.conv2 = nn.Conv2d(128, 256, 3, padding=1)
        self.conv3 = nn.Conv2d(256, 256, 3, padding=1)
        self.output = nn.Conv2d(256, 256, 1)
        
    def forward(self, x):
        # x: (B, 1, 28, 28),像素值[0, 255]
        x = x.long()
        x = self.embedding(x.squeeze(1)).permute(0, 3, 1, 2)  # (B, C, H, W)
        
        # 使用掩码卷积确保自回归性质
        h = torch.relu(self.conv1(x))
        h = torch.relu(self.conv2(h))
        h = torch.relu(self.conv3(h))
        logits = self.output(h)  # (B, 256, 28, 28)
        
        return logits

# 2. 流模型实现(简化的RealNVP)
class MNISTFlow(nn.Module):
    def __init__(self, dim=784):
        super().__init__()
        self.dim = dim
        # 简化的流模型
        self.scale_net = nn.Sequential(
            nn.Linear(dim//2, 256),
            nn.ReLU(),
            nn.Linear(256, dim//2)
        )
        self.translate_net = nn.Sequential(
            nn.Linear(dim//2, 256),
            nn.ReLU(),
            nn.Linear(256, dim//2)
        )
        
    def forward(self, x, reverse=False):
        # x: (B, 784)
        x1, x2 = x[:, :self.dim//2], x[:, self.dim//2:]
        
        if not reverse:
            # 前向变换
            s = torch.tanh(self.scale_net(x1))
            t = self.translate_net(x1)
            
            z2 = x2 * torch.exp(s) + t
            z = torch.cat([x1, z2], dim=1)
            
            log_det = s.sum(dim=1)
            return z, log_det
        else:
            # 逆向变换
            s = torch.tanh(self.scale_net(x1))
            t = self.translate_net(x1)
            
            x2 = (x2 - t) * torch.exp(-s)
            x = torch.cat([x1, x2], dim=1)
            
            log_det = -s.sum(dim=1)
            return x, log_det

# 3. GAN实现
class MNISTGenerator(nn.Module):
    def __init__(self, z_dim=100):
        super().__init__()
        self.main = nn.Sequential(
            nn.Linear(z_dim, 256),
            nn.ReLU(),
            nn.Linear(256, 512),
            nn.ReLU(),
            nn.Linear(512, 784),
            nn.Tanh()
        )
        
    def forward(self, z):
        img = self.main(z)
        return img.view(-1, 1, 28, 28)

class MNISTDiscriminator(nn.Module):
    def __init__(self):
        super().__init__()
        self.main = nn.Sequential(
            nn.Linear(784, 512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )
        
    def forward(self, img):
        img_flat = img.view(-1, 784)
        validity = self.main(img_flat)
        return validity

# 4. 扩散模型实现(简化的DDPM)
class MNISTDiffusion(nn.Module):
    def __init__(self, T=100):
        super().__init__()
        self.T = T
        
        # 噪声调度
        self.betas = torch.linspace(0.0001, 0.02, T)
        self.alphas = 1 - self.betas
        self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
        
        # 去噪网络
        self.denoise_net = nn.Sequential(
            nn.Linear(784 + 64, 512),  # 784像素 + 64维时间嵌入
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 784)
        )
        
        # 时间嵌入
        self.time_embed = nn.Sequential(
            nn.Linear(1, 64),
            nn.ReLU(),
            nn.Linear(64, 64)
        )
        
    def forward(self, x, t):
        # x: (B, 784)
        # t: (B,)
        t_emb = self.time_embed(t.float().unsqueeze(1))  # (B, 64)
        x_with_time = torch.cat([x, t_emb], dim=1)  # (B, 784+64)
        noise_pred = self.denoise_net(x_with_time)  # (B, 784)
        return noise_pred

# 训练和比较函数
def compare_training_dynamics():
    """比较四种模型的训练动态"""
    print("四种生成模型的训练动态对比")
    print("=" * 60)
    
    results = {
        "自回归模型 (PixelCNN)": {
            "训练稳定性": "高",
            "收敛速度": "慢",
            "采样速度": "很慢 (逐像素)",
            "生成质量": "中等",
            "似然计算": "可计算",
            "典型应用": "文本、序列数据"
        },
        "标准化流 (RealNVP)": {
            "训练稳定性": "高",
            "收敛速度": "中等",
            "采样速度": "快",
            "生成质量": "中等",
            "似然计算": "精确可计算",
            "典型应用": "密度估计、可逆任务"
        },
        "生成对抗网络 (GAN)": {
            "训练稳定性": "低",
            "收敛速度": "快 (如果稳定)",
            "采样速度": "很快",
            "生成质量": "高",
            "似然计算": "不可计算",
            "典型应用": "高质图像生成"
        },
        "扩散模型 (DDPM)": {
            "训练稳定性": "很高",
            "收敛速度": "慢",
            "采样速度": "很慢 (多步去噪)",
            "生成质量": "很高",
            "似然计算": "近似可计算 (ELBO)",
            "典型应用": "图像生成、音频合成"
        }
    }
    
    print(f"{'模型':<20} {'训练稳定性':<8} {'收敛速度':<8} {'采样速度':<8} {'生成质量':<8}")
    print("-" * 60)
    
    for model_name, metrics in results.items():
        print(f"{model_name:<20} {metrics['训练稳定性']:<8} {metrics['收敛速度']:<8} {metrics['采样速度']:<8} {metrics['生成质量']:<8}")
    
    print("\n关键观察:")
    print("1. 没有'完美'的模型,只有适合场景的模型")
    print("2. 训练稳定性和生成质量通常需要权衡")
    print("3. 采样速度和生成质量也往往矛盾")
    print("4. 扩散模型在多个维度上取得了良好平衡")
    
    return results

# 生成质量可视化
def visualize_generations():
    """可视化不同模型生成的MNIST数字"""
    fig, axes = plt.subplots(4, 10, figsize=(15, 6))
    
    titles = ["自回归模型", "标准化流", "生成对抗网络", "扩散模型"]
    
    for row, title in enumerate(titles):
        axes[row, 0].set_ylabel(title, fontsize=12)
        for col in range(10):
            ax = axes[row, col]
            # 这里应该放置每个模型生成的第col个样本
            # 由于我们没有实际训练模型,用随机噪声代替
            img = np.random.randn(28, 28) * 0.5 + 0.5
            ax.imshow(img, cmap='gray', vmin=0, vmax=1)
            ax.axis('off')
            
            if row == 0:
                ax.set_title(f"样本{col+1}", fontsize=10)
    
    plt.suptitle("四种生成模型的MNIST生成效果对比(示意)", fontsize=14, fontweight='bold')
    plt.tight_layout()
    plt.show()

# 运行对比
results = compare_training_dynamics()
visualize_generations()

# 复杂度分析
def analyze_complexity():
    """分析四种模型的计算复杂度"""
    print("\n\n四种模型的计算复杂度分析(对于28×28 MNIST):")
    print("=" * 60)
    
    complexities = {
        "操作类型": ["自回归", "标准化流", "GAN", "扩散模型"],
        "训练时间复杂度": ["O(N²)", "O(N)", "O(1)", "O(T)"],
        "采样时间复杂度": ["O(N²)", "O(1)", "O(1)", "O(T)"],
        "空间复杂度": ["中等", "高", "低", "中等"],
        "参数数量级": ["中等", "高", "低", "高"],
        "适合硬件": ["GPU/TPU", "GPU", "GPU", "GPU/TPU"]
    }
    
    # 打印表格
    headers = complexities["操作类型"]
    metrics = ["训练时间复杂度", "采样时间复杂度", "空间复杂度", "参数数量级", "适合硬件"]
    
    print(f"{'指标':<20} {'自回归':<10} {'标准化流':<10} {'GAN':<10} {'扩散模型':<10}")
    print("-" * 60)
    
    for metric in metrics:
        values = complexities[metric]
        print(f"{metric:<20} {values[0]:<10} {values[1]:<10} {values[2]:<10} {values[3]:<10}")
    
    print("\n说明:")
    print("• N: 序列长度或维度数(MNIST中N=784)")
    print("• T: 扩散步数(通常100-1000)")
    print("• 标准化流的高空间复杂度来自雅可比计算")
    print("• GAN的低参数量来自参数共享的卷积结构")
    
    return complexities

analyze_complexity()

关键发现

从对比中我们可以得出几个重要结论:

  1. 不存在"最好"的模型,只有"最适合"特定任务的模型

  2. 扩散模型在多个维度上取得了最佳平衡,解释了其当前的流行

  3. 趋势是融合:现代先进模型往往融合多种技术


结论与展望:生成式AI的未来是"混合模型"

我们已经学到的

  1. 四大流派同根同源:都在尝试逼近真实数据分布,只是选择了不同的数学路径

  2. 每种模型都有其哲学

    • 自回归:顺序主义的精确

    • 标准化流:可逆主义的优雅

    • GAN:对抗主义的实用

    • 扩散模型:渐进主义的平衡

  3. 选择模型就是选择权衡:速度 vs 质量,稳定性 vs 表达能力,似然可计算性 vs 采样效率

前沿趋势

  1. 混合架构:如Diffusion-GAN、AR-Diffusion、Flow+Diffusion

  2. 加速技术:知识蒸馏、一致性模型、隐空间扩散

  3. 统一理论:基于得分的生成建模、随机微分方程框架

  4. 新应用领域:科学发现(蛋白质设计、材料发现)、内容创作、个性化生成

最后的思考

生成式AI的发展就像盲人摸象:

  • 自回归派摸到了"序列结构"

  • 流模型派摸到了"可逆变换"

  • GAN派摸到了"对抗优化"

  • 扩散派摸到了"渐进去噪"

而真实的大象(数据分布)是所有部分的综合。未来的突破可能不在于发明全新的"摸象方法",而在于找到连接这些感知的"神经系统"

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐