概述

马尔可夫随机场(Markov Random Field, MRF)和能量模型(Energy-Based Model, EBM)是概率图模型中的核心概念,它们通过能量函数而不是直接的概率分解来定义联合分布。1

这种表示方式特别适合建模无向依赖关系,在条件随机场、图像建模、图神经网络等领域有广泛应用。


马尔可夫随机场基础

无向图模型的问题

贝叶斯网络中,联合分布可以分解为条件概率的乘积:

但在无向图中,由于没有天然的拓扑顺序,不能直接做类似的分解。

团与最大团

团(Clique):图中两两相邻的节点集合。

最大团(Maximal Clique):不能被其他团包含的团。

团示例:
    
    ●──●──●     {X1, X2} 是团
    │     │     {X1, X3} 不是团(X2和X3不相邻)
    ●──●──●

Hammersley-Clifford定理

定理:如果一个正的(严格正)分布 满足马尔可夫性质,那么它可以因子分解为势函数的乘积:

其中:

  • 是图的最大团集合
  • 是定义在团 上的势函数(Potential Function)
  • 配分函数(Partition Function)

势函数的性质

势函数 必须是严格正的(),通常用指数形式表示:

其中 能量函数


能量模型(Energy-Based Models)

从势函数到能量函数

定义能量函数 ,联合分布为:

其中 是配分函数。

能量函数的直观理解

概念物理类比
能量 系统能量
玻尔兹曼分布
低能量状态稳定、概率高
高能量状态不稳定、概率低
配分函数 归一化常数

常见的能量函数形式

1. 对数线性模型

其中 是特征函数, 是权重。

2. 高斯MRF

这对应于高斯分布。

3. Hopfield网络能量


条件随机场(CRF)

CRF的定义

条件随机场是在给定输入条件下对输出序列建模的无向模型。相比于HMM,CRF可以直接建模任意特征的依赖关系。

线性链CRF是最常用的形式:

势函数的参数化

通常使用指数线性形式:

其中 是特征函数, 是参数。

特征函数示例

def build_feature_functions():
    """
    CRF特征函数示例
    """
    features = []
    
    # 状态特征:当前词性和观测
    for y in ['NN', 'VB', 'JJ', ...]:
        for word in vocabulary:
            features.append(
                lambda y_prev, y, X, t, y=y, word=word: 
                1 if y_prev == y and X[t] == word else 0
            )
    
    # 转移特征:词性转移
    for y_prev in POS_tags:
        for y in POS_tags:
            features.append(
                lambda y_prev, y, X, t: 
                1 if y_prev == y_prev and y == y else 0
            )
    
    return features

配分函数的计算困境

配分函数的定义

计算复杂度

对于 个二元变量,配分函数涉及 项的求和,指数级复杂度

与精确推断的联系

配分函数的计算等价于:

近似方法

方法原理适用场景
变分推断优化下界一般情况
MCMC采样从分布采样估计大规模问题
信念传播树结构的精确计算树/近似树
期望传播矩匹配近似中等规模

对比散度训练(Contrastive Divergence)

最大似然学习的困难

学习能量模型需要最大化观测数据的对数似然:

梯度

第一项可以从观测数据直接计算,第二项需要对 采样——这正是问题所在!

CD-k算法

Hinton提出的对比散度算法使用短链MCMC近似期望:

def contrastive_divergence(model, data, k=1, learning_rate=0.01):
    """
    对比散度(CD-k)算法
    
    使用k步Gibbs采样从当前模型生成样本
    """
    # 第一项:从真实数据计算
    positive_grad = compute_gradient_from_data(model, data)
    
    # 第二项:从模型采样(CD-k)
    # 初始化为真实数据
    samples = data.clone()
    
    # 运行k步Gibbs采样
    for _ in range(k):
        samples = gibbs_step(model, samples)
    
    # 计算负相梯度
    negative_grad = compute_gradient_from_data(model, samples)
    
    # 梯度更新
    grad = positive_grad - negative_grad
    model.parameters -= learning_rate * grad
    
    return grad
 
 
def gibbs_step(model, x):
    """
    吉布斯采样一步
    
    对于RBM可以交替更新可见层和隐藏层
    """
    # 采样隐藏层
    h_prob = model.hidden_activation(x)
    h = (torch.rand_like(h_prob) < h_prob).float()
    
    # 采样可见层
    x_recon_prob = model.visible_activation(h)
    x_recon = (torch.rand_like(x_recon_prob) < x_recon_prob).float()
    
    return x_recon

CD-k的理论分析

CD-k的性质

  • 通常就足够好
  • 梯度是有偏的,但偏差随 增大而减小
  • 比完整MCMC训练快得多

持续对比散度(PCD)

class PersistentCD(nn.Module):
    """
    持久对比散度
    
    维护一组"幻想粒子"持续采样
    """
    def __init__(self, model, num_particles=100, visible_dim=784, hidden_dim=500):
        super().__init__()
        self.model = model
        self.num_particles = num_particles
        
        # 维护幻想粒子
        self.particles = torch.randn(num_particles, visible_dim).sign()  # 二值数据
    
    def step(self, data_batch):
        # 正相梯度
        pos_grad = self.compute_grad(self.model, data_batch)
        
        # 负相梯度:从幻想粒子采样
        for _ in range(k_steps):
            self.particles = self.gibbs_step(self.particles)
        
        neg_grad = self.compute_grad(self.model, self.particles)
        
        # 更新模型
        grad = pos_grad - neg_grad
        self.update_parameters(grad)
        
        return grad.norm()
    
    def gibbs_step(self, x):
        h_prob = torch.sigmoid(x @ self.model.W + self.model.h_bias)
        h = (torch.rand_like(h_prob) < h_prob).float()
        x_prob = torch.sigmoid(h @ self.model.W.T + self.model.v_bias)
        x = (torch.rand_like(x_prob) < x_prob).float()
        return x

能量模型与神经网络的融合

能量模型作为神经网络层

现代深度学习中,能量模型被嵌入到神经网络中:

class EnergyBasedLayer(nn.Module):
    """
    基于能量的神经网络层
    学习一个能量函数
    """
    def __init__(self, input_dim, hidden_dim):
        super().__init__()
        self.energy_net = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, 1)  # 能量是标量
        )
    
    def forward(self, x):
        """
        返回能量
        """
        return self.energy_net(x)
    
    def energy(self, x):
        """
        计算样本的能量
        """
        return self.forward(x)
    
    def log_prob(self, x):
        """
        计算对数概率(需要配分函数)
        使用近似方法
        """
        E = self.energy(x)
        # 近似:使用JEM风格的能量归一化
        return -E - self.log_Z_approx
    
    def sample(self, num_samples, steps=100):
        """
        从能量模型采样
        使用MCMC
        """
        samples = torch.randn(num_samples, self.input_dim)
        samples.requires_grad = True
        
        optimizer = torch.optim.SGD([samples], lr=1.0)
        
        for step in range(steps):
            optimizer.zero_grad()
            energy = self.energy(samples)
            # Langevin动态
            energy.backward()
            
            # 添加噪声(退火)
            samples.grad += torch.randn_like(samples.grad) * 0.01
            
            optimizer.step()
        
        return samples.detach()

联合能量模型(JEM)

class JEM(nn.Module):
    """
    联合能量模型(JEM)
    
    结合分类器和能量模型
    """
    def __init__(self, classifier, energy_model):
        super().__init__()
        self.classifier = classifier
        self.energy_model = energy_model
    
    def forward(self, x):
        """
        返回分类logits和能量
        """
        logits = self.classifier(x)
        energy = self.energy_model(x)
        return logits, energy
    
    def classify(self, x):
        return self.classifier(x)
    
    def log_prob(self, x):
        """
        估计对数概率
        """
        return -self.energy_model(x)
    
    def train_step(self, x_real, y_real, x_fake=None):
        """
        训练JEM
        
        使用FiLM条件化分类器
        """
        # 真实数据的对数似然
        log_prob_real = self.log_prob(x_real)
        
        # 分类损失
        logits = self.classifier(x_real)
        cls_loss = F.cross_entropy(logits, y_real)
        
        # 能量正则化
        energy_real = self.energy_model(x_real).mean()
        
        # 如果有生成样本
        if x_fake is not None:
            log_prob_fake = self.log_prob(x_fake)
            loss = -log_prob_real.mean() + cls_loss + 0.1 * energy_real
        else:
            loss = -log_prob_real.mean() + cls_loss + 0.1 * energy_real
        
        return loss

基于能量的生成模型

能量引导的扩散模型

能量函数可以引导扩散模型的采样过程:

class EnergyGuidedDiffusion(nn.Module):
    """
    能量引导的扩散模型
    
    在采样过程中使用能量函数引导
    """
    def __init__(self, diffusion_model, energy_fn):
        super().__init__()
        self.diffusion = diffusion_model
        self.energy = energy_fn
    
    def energy_guided_sample(self, shape, num_steps=1000, guidance_scale=1.0):
        """
        能量引导采样
        """
        # 从纯噪声开始
        x = torch.randn(shape, device=next(self.parameters()).device)
        
        for t in reversed(range(num_steps)):
            # 预测噪声
            noise_pred = self.diffusion.predict_noise(x, t)
            
            # 能量梯度引导
            x.requires_grad = True
            energy = self.energy(x)
            energy_grad = torch.autograd.grad(energy.sum(), x)[0]
            
            # 组合预测
            guided_noise = noise_pred + guidance_scale * energy_grad
            
            # 去噪步骤
            x = self.diffusion.step(x, guided_noise, t)
        
        return x.detach()

图像建模中的应用

MRF用于图像去噪

class MRFImageDenoising(nn.Module):
    """
    MRF图像去噪模型
    
    能量函数包含:
    - 数据项:观测与重建的一致性
    - 平滑项:邻域像素的平滑性
    """
    def __init__(self, num_neighbors=8):
        super().__init__()
        self.num_neighbors = num_neighbors
        
        # 数据项参数
        self.data_weight = 1.0
        # 平滑项参数
        self.smooth_weight = 0.5
    
    def energy(self, x, y):
        """
        计算MRF能量
        
        Args:
            x: 观测图像
            y: 重建图像
        
        Returns:
            energy: 标量能量
        """
        # 数据项:L2损失
        E_data = self.data_weight * ((x - y) ** 2).sum()
        
        # 平滑项:邻域差异
        E_smooth = 0
        for dy, dx in [(0, 1), (0, -1), (1, 0), (-1, 0), 
                       (1, 1), (1, -1), (-1, 1), (-1, -1)]:
            shifted = torch.roll(y, shifts=(dy, dx), dims=(0, 1))
            E_smooth += ((y - shifted) ** 2).sum()
        
        E_smooth = self.smooth_weight * E_smooth
        
        return E_data + E_smooth
    
    def gibbs_sampling(self, x_init, observed, num_steps=100):
        """
        吉布斯采样去噪
        """
        y = x_init.clone()
        
        for _ in range(num_steps):
            # 随机选择像素
            i = torch.randint(0, y.shape[0], (1,)).item()
            j = torch.randint(0, y.shape[1], (1,)).item()
            
            # 计算每个可能值的能量
            energies = []
            for val in range(256):  # 假设灰度图
                y_test = y.clone()
                y_test[i, j] = val
                energies.append(self.energy(observed, y_test))
            
            # 采样新值(玻尔兹曼分布)
            energies = torch.tensor(energies)
            probs = F.softmax(-energies, dim=0)
            new_val = torch.multinomial(probs, 1).item()
            y[i, j] = new_val
        
        return y

卷积MRF

class ConvolutionalMRF(nn.Module):
    """
    卷积马尔可夫随机场
    
    使用卷积定义邻域势函数
    """
    def __init__(self, in_channels, hidden_channels, kernel_size=3):
        super().__init__()
        
        # 数据项网络
        self.data_net = nn.Conv2d(in_channels, hidden_channels, 1)
        
        # 平滑项网络(卷积势函数)
        self.smooth_net = nn.Conv2d(
            in_channels * 2, 
            hidden_channels, 
            kernel_size, 
            padding=kernel_size//2,
            padding_mode='reflect'
        )
        
        # 能量输出
        self.energy_head = nn.Conv2d(hidden_channels, 1, 1)
    
    def forward(self, x):
        """
        计算每个像素位置的能量
        """
        # 数据项
        data_energy = self.data_net(x) ** 2
        
        # 平滑项:中心像素与邻域的差异
        neighbors = F.pad(x, (1, 1, 1, 1), mode='replicate')
        diff = x - neighbors
        smooth_energy = self.smooth_net(diff) ** 2
        
        # 总能量
        energy = self.energy_head(data_energy + smooth_energy)
        
        return energy

与相关模型的关系

MRF vs 贝叶斯网络

特性MRF贝叶斯网络
图类型无向有向
分解形式势函数乘积条件概率乘积
归一化配分函数自然归一化
适用场景对称依赖因果关系
推断复杂度通常更难部分可分解

MRF vs 神经网络

特性MRF神经网络
表示形式能量函数层级特征
学习目标最大似然经验风险最小化
不确定性自然概率解释需要额外建模
可解释性能量直观特征重要性

能量模型 vs GAN

特性能量模型GAN
训练对比散度等对抗训练
采样MCMC生成器网络
损失函数能量/似然对抗损失
模式覆盖理论保证可能模式崩溃

与现有wiki内容的联系


参考


相关阅读

Footnotes

  1. Koller, D., & Friedman, N. (2009). Probabilistic Graphical Models: Principles and Techniques. MIT Press.