11统计学习方法(第2版)

条件随机场

序列标注

CRF概率无向图模型条件随机场的计算

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第11章:条件随机场(Conditional Random Field)

一、导读

11.1 本章学习目标

第11章介绍条件随机场(CRF)——序列标注任务的主流方法。与HMM不同,CRF是判别模型,直接建模条件概率 P(Y|X),不需要对观测数据做独立性假设。这使得CRF在命名实体识别、词性标注、分词等任务上通常优于HMM。

本章的核心学习目标包括:

  • 理解概率无向图模型:马尔可夫随机场的基本概念
  • 掌握条件随机场的定义:给定观测序列的条件下,输出序列满足马尔可夫性
  • 理解线性链CRF:适用于序列标注的特殊CRF结构
  • 掌握特征函数:状态特征和转移特征的设计
  • 掌握CRF的参数估计:极大似然估计和改进的迭代尺度法
  • 了解CRF的预测:Viterbi算法在CRF中的应用

11.2 为什么本章如此重要

CRF是序列标注领域最重要的模型之一。在深度学习兴起之前,CRF几乎是命名实体识别、词性标注等任务的标准方法。即使在深度学习时代,CRF层仍然被广泛用于结构化预测任务(如BiLSTM-CRF)。

CRF相比HMM的核心优势在于:它是判别模型,不需要对观测数据做条件独立假设。这意味着CRF可以灵活地使用任意复杂的特征,包括重叠特征、长距离依赖等。

11.3 核心问题引导

在阅读本章时,请带着以下问题思考:

CRF和HMM有什么本质区别?为什么CRF通常表现更好?

什么是概率无向图模型?马尔可夫随机场是什么?

条件随机场的定义是什么?"条件"体现在哪里?

线性链CRF的特征函数如何设计?状态特征和转移特征有什么区别?

CRF的参数估计为什么比HMM更困难?

CRF在序列标注中如何应用?


二、核心概念详解

11.1 概率无向图模型

11.1.1 马尔可夫随机场

概率无向图模型(也称为马尔可夫随机场,MRF)是用无向图表示的联合概率分布。

给定一个无向图 G = (V, E),如果随机变量 Y 满足马尔可夫性质——每个节点在给定邻居的条件下与其他节点条件独立——则 Y 构成马尔可夫随机场。

11.1.2 因子分解

MRF的联合概率可以分解为图上的团(clique)的势函数的乘积:

P(Y) = (1/Z) Π_C Ψ_C(Y_C)

其中 C 是图中的团,Ψ_C 是团 C 的势函数,Z 是归一化因子。

python
import numpy as np

def mrf_intuition():
    """马尔可夫随机场的直觉理解"""
    print("概率无向图模型(MRF):")
    print()
    print("  Y1 --- Y2 --- Y3 --- Y4 --- Y5")
    print()
    print("马尔可夫性质:")
    print("  每个节点在给定邻居的条件下与其他节点独立")
    print("  例如:P(Y3 | Y1,Y2,Y4,Y5) = P(Y3 | Y2,Y4)")
    print()
    print("联合概率分解:")
    print("  P(Y) = (1/Z) * Ψ(Y1,Y2) * Ψ(Y2,Y3) * Ψ(Y3,Y4) * Ψ(Y4,Y5)")
    print()
    print("这种分解使得高维概率的计算变得可行")

mrf_intuition()

11.2 条件随机场的定义

11.2.1 定义

给定无向图 G = (V, E),如果 Y 是以 X 为条件的马尔可夫随机场:

P(Y_v | X, Y_{W}) = P(Y_v | X, Y_{N(v)})

其中 N(v) 是节点 v 的邻居,则 (X, Y) 构成条件随机场。

11.2.2 线性链CRF

对于序列标注任务,最常用的CRF结构是线性链:

X_1 --- X_2 --- ... --- X_T
  |        |              |
Y_1 --- Y_2 --- ... --- Y_T

在线性链CRF中,给定观测序列 X,输出序列 Y 满足一阶马尔可夫性:

P(Y_t | X, Y_1, ..., Y_{t-1}) = P(Y_t | X, Y_{t-1}, Y_{t+1})

11.3 线性链CRF的模型

11.3.1 条件概率

线性链CRF的条件概率定义为:

P(y|x) = (1/Z(x)) * exp(Σ_k w_k * f_k(y, x))

其中:

  • f_k 是特征函数
  • w_k 是特征权重
  • Z(x) 是归一化因子

11.3.2 特征函数

CRF的特征函数通常分为两类:

转移特征(transition features):依赖于当前位置和前一位置的标签

s_k(y_{t-1}, y_t, x, t) = t_k(y_{t-1}, y_t) * l_k(x, t)

状态特征(state features):依赖于当前位置的标签和观测

u_k(y_t, x, t) = l_k(x, t) * I(y_t = label)
python
import numpy as np

def crf_features_example():
    """CRF特征函数示例"""
    print("CRF特征函数设计(以词性标注为例):")
    print()
    print("转移特征(捕捉标签之间的转移规律):")
    print("  f1: 如果 y_{t-1}=动词 且 y_t=名词,则=1")
    print("  f2: 如果 y_{t-1}=形容词 且 y_t=名词,则=1")
    print("  f3: 如果 y_{t-1}=名词 且 y_t=动词,则=1")
    print()
    print("状态特征(捕捉标签与观测之间的关系):")
    print("  f4: 如果 x_t 以'ly'结尾 且 y_t=副词,则=1")
    print("  f5: 如果 x_t 首字母大写 且 y_t=专有名词,则=1")
    print("  f6: 如果 x_t 在词典中 且 y_t=名词,则=1")
    print()
    print("CRF的优势:可以使用任意复杂的、重叠的特征")

crf_features_example()

11.3.3 权值化和矩阵形式

线性链CRF可以用矩阵形式表示。定义 M 个 T×T 的矩阵:

M_i(x) = [exp(Σ_k w_k * f_k(y_{t-1}=i, y_t=j, x, t))]

则归一化因子可以写成矩阵连乘:

Z(x) = [M_1(x) * M_2(x) * ... * M_T(x)]_{start, stop}
python
import numpy as np

def crf_matrix_form():
    """CRF的矩阵形式"""
    # 简化的CRF矩阵形式
    # 2个标签,3个时间步
    
    T = 3  # 序列长度
    n_labels = 2
    
    # 每个位置的转移矩阵
    M = np.random.rand(T, n_labels, n_labels)
    for t in range(T):
        M[t] = np.exp(M[t])  # 确保正值
    
    # 计算Z(x):矩阵连乘
    Z_matrix = M[0]
    for t in range(1, T):
        Z_matrix = Z_matrix @ M[t]
    
    Z = np.sum(Z_matrix)
    
    print(f"序列长度 T={T}, 标签数={n_labels}")
    print(f"Z(x) = {Z:.4f}")
    print(f"\n矩阵连乘的复杂度: O(T * n_labels^3)")
    print(f"比暴力枚举 O(n_labels^T) 高效得多")

crf_matrix_form()

11.4 CRF的参数估计

11.4.1 对数似然

CRF使用条件对数似然作为目标函数:

L(w) = Σ_i log P(y^{(i)} | x^{(i)})
     = Σ_i [Σ_k w_k * f_k(y^{(i)}, x^{(i)}) - log Z(x^{(i)})]

11.4.2 梯度

∂L/∂w_k = Σ_i [f_k(y^{(i)}, x^{(i)}) - E_{P(y|x^{(i)})}[f_k(y, x^{(i)})]]

即:经验特征期望 - 模型特征期望。

11.4.3 优化方法

由于对数似然是凸函数,可以使用梯度下降、L-BFGS等方法优化。

python
import numpy as np

def crf_training_demo():
    """CRF训练演示(简化版)"""
    # 简化的线性链CRF
    # 2个标签,序列长度3
    
    n_labels = 2
    T = 3
    n_features = 4
    
    # 随机生成训练数据
    np.random.seed(42)
    n_train = 10
    X_train = [np.random.randn(T, 2) for _ in range(n_train)]
    y_train = [np.random.randint(0, n_labels, T) for _ in range(n_train)]
    
    # 特征函数(简化:直接用观测的线性组合)
    def compute_features(x, y_prev, y_curr, t):
        features = np.zeros(n_features)
        features[0] = x[t, 0] * (y_curr == 0)
        features[1] = x[t, 1] * (y_curr == 1)
        features[2] = (y_prev == 0) * (y_curr == 0)
        features[3] = (y_prev == 1) * (y_curr == 1)
        return features
    
    # 简化的训练(梯度上升)
    w = np.zeros(n_features)
    lr = 0.01
    
    for epoch in range(50):
        grad = np.zeros(n_features)
        
        for x, y in zip(X_train, y_train):
            # 经验特征期望
            for t in range(T):
                y_prev = y[t-1] if t > 0 else 0
                grad += compute_features(x, y_prev, y[t], t)
            
            # 模型特征期望(简化:用贪心近似)
            # 实际中需要用前向-后向算法精确计算
        
        grad /= n_train
        
        # 正则化
        grad -= 0.01 * w
        
        w += lr * grad
    
    print(f"训练后的权重: {w}")
    print(f"特征0(标签0的观测权重): {w[0]:.4f}")
    print(f"特征1(标签1的观测权重): {w[1]:.4f}")
    print(f"特征2(标签0→0转移权重): {w[2]:.4f}")
    print(f"特征3(标签1→1转移权重): {w[3]:.4f}")

crf_training_demo()

11.5 CRF的预测

CRF的预测问题是:给定模型和观测序列 x,找到最可能的标签序列 y*:

y* = argmax_y P(y|x)

这与HMM的预测问题相同,可以使用Viterbi算法求解。

python
import numpy as np

def crf_viterbi(x, w, T, n_labels):
    """CRF的Viterbi预测"""
    n_features = len(w)
    
    # 定义特征函数
    def score(x, y_prev, y_curr, t):
        s = 0
        s += w[0] * x[t, 0] * (y_curr == 0)
        s += w[1] * x[t, 1] * (y_curr == 1)
        s += w[2] * (y_prev == 0) * (y_curr == 0)
        s += w[3] * (y_prev == 1) * (y_curr == 1)
        return s
    
    # Viterbi
    delta = np.zeros((T, n_labels))
    psi = np.zeros((T, n_labels), dtype=int)
    
    # 初始化
    for j in range(n_labels):
        delta[0, j] = score(x, 0, j, 0)
    
    # 递推
    for t in range(1, T):
        for j in range(n_labels):
            scores = [delta[t-1, i] + score(x, i, j, t) for i in range(n_labels)]
            delta[t, j] = max(scores)
            psi[t, j] = np.argmax(scores)
    
    # 回溯
    path = np.zeros(T, dtype=int)
    path[T-1] = np.argmax(delta[T-1])
    for t in range(T-2, -1, -1):
        path[t] = psi[t+1, path[t+1]]
    
    return path

# 测试
np.random.seed(42)
w = np.array([1.5, -0.8, 0.5, 0.7])
x = np.random.randn(5, 2)

path = crf_viterbi(x, w, T=5, n_labels=2)
print(f"CRF预测的标签序列: {path}")

11.6 CRF vs HMM

python
import numpy as np

def crf_vs_hmm():
    """对比CRF和HMM"""
    print("CRF vs HMM 对比:")
    print()
    print(f"{'特性':>20} {'HMM':>15} {'CRF':>15}")
    print("-" * 52)
    print(f"{'模型类型':>20} {'生成模型':>15} {'判别模型':>15}")
    print(f"{'建模目标':>20} {'P(X,Y)':>15} {'P(Y|X)':>15}")
    print(f"{'观测假设':>20} {'条件独立':>15} {'无假设':>15}")
    print(f"{'特征灵活性':>20} {'受限':>15} {'灵活':>15}")
    print(f"{'训练数据需求':>20} {'较多':>15} {'较少':>15}")
    print(f"{'预测速度':>20} {'快':>15} {'较慢':>15}")
    print(f"{'典型应用':>20} {'语音识别':>15} {'序列标注':>15}")
    print()
    print("CRF的核心优势:")
    print("  1. 不需要对观测数据做条件独立假设")
    print("  2. 可以使用任意复杂的特征")
    print("  3.  discriminative training通常效果更好")

crf_vs_hmm()

三、实战案例

3.1 简化的序列标注

python
import numpy as np

def simple_sequence_labeling():
    """简化的序列标注示例"""
    # 模拟NER任务
    # 标签:B-PER(人名开始), I-PER(人名中间), O(其他)
    
    sentences = [
        (["John", "lives", "in", "New", "York"], 
         ["B-PER", "O", "O", "B-LOC", "I-LOC"]),
        (["Mary", "works", "at", "Google"],
         ["B-PER", "O", "O", "B-ORG"]),
        (["Bob", "visited", "Paris"],
         ["B-PER", "O", "B-LOC"]),
    ]
    
    print("序列标注示例(NER):")
    for words, tags in sentences:
        print(f"  句子: {' '.join(words)}")
        print(f"  标签: {' '.join(tags)}")
        print()
    
    print("CRF在NER中的特征设计:")
    print("  当前词: word='John' → B-PER")
    print("  词性: POS='NNP' → B-PER")
    print("  前一个标签: prev_tag='O' + word='John' → B-PER")
    print("  词形: is_capitalized=True → B-PER/B-LOC/B-ORG")

simple_sequence_labeling()

3.2 特征模板

python
import numpy as np

def feature_templates():
    """CRF特征模板示例"""
    print("CRF特征模板(以词性标注为例):")
    print()
    print("Unigram特征(当前词):")
    print("  U00: %x[0,0]  → 当前词")
    print("  U01: %x[0,1]  → 当前词的词性")
    print("  U02: %x[-1,0]/%x[0,0]  → 前一个词/当前词")
    print()
    print("Bigram特征(标签转移):")
    print("  B: %x[-1,0]/%x[0,0]  → 前一个标签/当前标签")
    print()
    print("这些模板会自动生成大量具体的特征函数")
    print("例如 U00:word='John' 在词为'John'时激活")

feature_templates()

四、本章小结

第11章介绍了条件随机场:

概率无向图模型:用无向图表示的联合概率分布,满足马尔可夫性质。

条件随机场:以观测为条件的马尔可夫随机场,直接建模 P(Y|X)。

线性链CRF:适用于序列标注,条件概率用特征函数的指数形式表示。

特征函数:分为转移特征和状态特征,可以灵活设计。

参数估计:极大似然估计,使用梯度方法优化。

预测:使用Viterbi算法找到最优标签序列。

CRF vs HMM:CRF是判别模型,不需要观测独立性假设,特征更灵活。

CRF的核心启示是:判别模型 + 灵活的特征设计 = 强大的序列标注能力。


五、思考与练习

思考题

CRF为什么是判别模型?与生成模型(HMM)相比有什么优势?

线性链CRF的特征函数如何设计?状态特征和转移特征各捕捉什么信息?

CRF的参数估计为什么比HMM更困难?归一化因子Z(x)的计算复杂度是多少?

CRF在什么情况下会退化为HMM?

如何设计CRF的特征来捕捉长距离依赖?

编程练习

实现简化的线性链CRF,包括特征计算和Viterbi预测。

对比CRF和HMM在简单序列标注任务上的效果。

设计CRF的特征模板,用于词性标注任务。


11/12条件随机
0%