统计机器学习方法 for NLP:基于CRF的词性标注

前言

最近在重刷李航老师的《统计机器学习方法》尝试将其与NLP结合,通过具体的NLP应用场景,强化对书中公式的理解,最终形成「统计机器学习方法 for NLP」的系列。这篇将介绍条件随机场CRF(绝对给你一次讲明白)并基于CRF完成一个词性标注的任务。

CRF是什么

条件随机场(Conditional random field, CRF)是一个NLP领域广泛使用的模型,即使在深度学习时代也是如此。尤其在序列标注任务上,DNN+CRF依然是目前最主流的范式。 CRF是一个判别式模型,通过训练数据直接学习输入序列

X和对应的标签序列
Y 的条件概率
P(Y|X) 。相比于逻辑回归(Logistic Regression)的分类,CRF能学习到标签之间的序列关系来辅助分类。例如在词性标注任务中,如果之前上一个词的词性是「动词」,那么当前词的词性就很小的概率依然是「动词」,因为「动词」后面继续跟「动词」是小概率事件。

马尔可夫随机场

条件随机场CRF中的随机场指的是马尔可夫随机场。马尔可夫随机场满足马尔可夫随机性,即节点

v 上的随机变量
Y_{v} 只与跟节点
v 直接相连的节点有关。所以我们就可以把一个无向图上随机变量进行因式分解,写成「最大团」乘积的形式。这里「最大团」指的是一个子图
C 中的任何两个节点都有边相连(这就是「团」),并且在这个子图
C 上不能再添加一个新的节点使其构成一个更大的团。例如下图中的{A,B,C}就是一个最大团。

01f164179b8bb4ccf2a940c21ded730f.jpeg

所以随机变量

Y 可以写成下面的形式,
c 为图中的一个最大团:

P(Y) = \frac{1}{Z} \prod_{c} \psi_{c}(Y_{c} )

这里

Z 是全部可能的
Y 的概率加和,用来进行概率的归一化(类似softmax);这里的
\psi_{c}(Y_{c} ) 是一个严格意义上正函数,一般取指数函数。

条件随机场

当马尔可夫随机场中有一部分节点是确定节点

X ,这时候就是求解在
X 的条件下,
Y的概率,即
P(Y|X) 。我们可以继续按照「最大团」进行概率展开:

P(Y|X) = \frac{1}{Z} \prod_{c} \psi_{c}(Y_{c} | X)

线性链条件随机场

b172c938a6835a55256868bcdd96d71c.jpeg

NLP任务中一般为上图所示的线性链随机场,

X
Y 均为线性表示的序列,即
X=(x_{1},x_{2}, ..., x_{n})
Y=(y_{1},y_{2},...,y_{3}) 。这里我们称已知变量(确定变量)
X 为观测变量,未知变量(随机变量)
Y 为隐变量。如上图所示,在图上的边只有
x_{i}y_{i} (观测变量和对应的隐变量的边)以及
y_{i}y_{i+1} (相邻的两个隐变量的边)。

所以图中的最大团只有两部分构成:(1) 观测变量和对应的隐变量之间

\{(x_{1},y_{1}), (x_{2},y_{2}), ... , (x_{n},y_{n})\} (2) 相邻的隐变量之间
\{(y_{1},y_{2}), (y_{2},y_{3}), ... , (y_{n-1},y_{n})\} 。 根据上面的公式进行展开:

P(Y|X) = \frac{1}{Z} \prod_{c} \psi_{c}(Y_{c} | X) = \frac{1}{Z} (\prod_{i} \psi(X,i,y_{i}) * \prod_{i} \psi(X,i,y_{i},y_{i-1}))

进一步将

\psi_{c}(Y_{c} |X ) 写成指数函数的形式,概率相乘就会转写成指数相加的形式:

P(Y|X) = \frac{1}{Z} exp( \sum_{i,l}u_{l}s_{l}(y_{i},X,i) +  \sum_{i,k}\lambda_{k}t_{k}(y_{i-1},y_{i},X,i))

这里

s_{l}, t_{k} 为特征函数,函数值为1或者0,满足特征为1,不满足特征为0;其中
s_{l} 为定义在节点的特征函数,称之为状态特征,依赖当前位置;
t_{k} 为定义在边的特征函数,称之为转移特征,依赖当前和前一个位置。
u_{l},\lambda_{k} 为特征函数对应的系数,
Z 依然是归一化因子。

再进一步,我们可以将这两类最大团合并进行形式上的简化,所以可以写成下面的形式:

P(Y|X) = \frac{1}{Z} exp(\sum_{i}\sum_{k}\lambda_{k}f_{k}(y_{i},y_{i-1},i,X))

这就是线性CRF最终推出的公式了 !!!

额外补充

特征函数:可以看到特征函数与4个变量有关:(1) 整体的观测变量

X (2) 当前位置
i (3) 当前的隐变量
y_{i} (4) 前一个隐变量
y_{i-1} 。所以在词性标注的例子中,整体的观测变量就是输入的句子
s , 当前位置就是当前的单词
w ,当前的隐变量就是当前的位置的词性标签
tag_{i} ,前一个隐变量就是前一个单词的词性标签
tag_{i-1} 。这时候我们就可以定义特征函数,例如:如果句子的结尾是问号,当前的单词为第一个单词,且当前单词的词性为动词,那么函数输出为1,否则为0。如果这个特征函数的权重越大,说明这种范式越正确,即问句的第一个词的词性是动词的概率较大,比如:“吃饭了吗?”

与HMM的关系:HMM在之前的文章中专门介绍过,具体参见:胡勇:统计机器学习方法 for NLP:基于HMM的词性标注 。先说结论,HMM是一种特殊的CRF。下面进行推倒,首先对于HMM:

P(x,y) = p(y_{1})\prod_{i}p(y_{i}|y_{i-1}) \prod_{i}p(y_{i}|x_{i})

写成log相加的形式:

logP(x,y) = logp(y_{1}) + log\sum_{i}p(y_{i}|y_{i-1}) + log\sum_{i}p(y_{i}|x_{i})

接着我们通过定义两类特征函数就能转变成CRF的形式:

首先定义一类特征函数,对于HMM中的两个tag

t_{1}
t_{2} 的转移概率
p(t_{2} | t_{1}) ,定义特征函数
f_{t_{1},t_{2}}(y_{i},y_{i-1},i,X) ,如果
y_{i} = t_{2} , y_{i-1} = t_{1} 那么函数
f 为1,否则函数
f 为0,同时这个函数对应的特征权重就是
t_{1}
t_{2} 的转移概率
p(t_{2} | t_{1})

首先定义一类特征函数,对于HMM中的tag

t 到单词
w 的发射概率
p(w|t) ,定义特征函数
f_{t,w}(y_{i},y_{i-1},i,X) ,如果
y_{i} = t , x_{i}= w 那么函数
f 为1,否则函数
f 为0,同时这个函数对应的特征权重就是
t
w 的发射概率
p(w|t)

所以我们可以看到HMM是一种特殊的CRF,同时具有两方面的局限性:(1) HMM是局部特征而非全局特征,也就是没有利用

X 整体的特征,而CRF可以利用整个句子的全局特征 (2) HMM中的写成CRF特征函数的形式后,权重就是概率值,所以有加和为1的限制,而CRF没有系数和的限制。

模型训练与预测

在定义了特征函数之后,CRF模型本质是一个线性的模型,模型的参数就是特征函数的权重,这里可以采用「梯度下降」的方法学习模型参数。模型训练之后,预测阶段可通过viterbi算法进行解码,来获得最优的隐变量序列。

基于CRF的词性标注

词性标注任务是指给定一句话,给这种话中的每个词都标记上词性,例如动词/形容词等。例如给定句子:“I love China”, 需要输出: (I: 代词, love: 动词, China: 名词),具体可以参见HMM章节中对词性标注任务的介绍:胡勇:统计机器学习方法 for NLP:基于HMM的词性标注

下面将分为:数据处理,模型训练,模型预测 三个部分来介绍如何利用CRF实现词性标注,具体参考的是这篇工作:NLP Guide: Identifying Part of Speech Tags using Conditional Random Fields

数据处理

这里采用的数据集是:NLTK Treebank 。获取数据并切分数据集为训练集和测试集。

tagged_sentence = nltk.corpus.treebank.tagged_sents(tagset='universal')
print("Number of Tagged Sentences ",len(tagged_sentence))
tagged_words=[tup for sent in tagged_sentence for tup in sent]
print("Total Number of Tagged words", len(tagged_words))
vocab=set([word for word,tag in tagged_words])
print("Vocabulary of the Corpus",len(vocab))
tags=set([tag for word,tag in tagged_words])
print("Number of Tags in the Corpus ",len(tags))
“”“
Number of Tagged Sentences  3914
Total Number of Tagged words 100676
Vocabulary of the Corpus 12408
Number of Tags in the Corpus  12
”“”
train_set, test_set = train_test_split(tagged_sentence,test_size=0.2,random_state=1234)
print("Number of Sentences in Training Data ",len(train_set))
print("Number of Sentences in Testing Data ",len(test_set))
“”“
Number of Sentences in Training Data  3131
Number of Sentences in Testing Data  783
”“”

模型训练

首先我们手工定义一组特征函数(状态特征函数),例如「第一个字母是不是大写」,「是不是第一个单词」,「是不是最后一个单词」,「前一个单词」,「后一个单词」等,并从数据集中进行特征的抽取:

# 特征定义
def features(sentence,index):
    ### sentence is of the form [w1,w2,w3,..], index is the position of the word in the sentence
    return {
        'is_first_capital':int(sentence[index][0].isupper()),
        'is_first_word': int(index==0),
        'is_last_word':int(index==len(sentence)-1),
        'is_complete_capital': int(sentence[index].upper()==sentence[index]),
        'prev_word':'' if index==0 else sentence[index-1],
        'next_word':'' if index==len(sentence)-1 else sentence[index+1],
        'is_numeric':int(sentence[index].isdigit()),
        'is_alphanumeric': int(bool((re.match('^(?=.*[0-9]$)(?=.*[a-zA-Z])',sentence[index])))),
        'prefix_1':sentence[index][0],
        'prefix_2': sentence[index][:2],
        'prefix_3':sentence[index][:3],
        'prefix_4':sentence[index][:4],
        'suffix_1':sentence[index][-1],
        'suffix_2':sentence[index][-2:],
        'suffix_3':sentence[index][-3:],
        'suffix_4':sentence[index][-4:],
        'word_has_hyphen': 1 if '-' in sentence[index] else 0
    }

def untag(sentence):
    return [word for word,tag in sentence]


def prepareData(tagged_sentences):
    X,y=[],[]
    for sentences in tagged_sentences:
        X.append([features(untag(sentences), index) for index in range(len(sentences))])
        y.append([tag for word,tag in sentences])
    return X,y

# 特征抽取
X_train,y_train=prepareData(train_set)
X_test,y_test=prepareData(test_set)

下面简单看一下抽取出来的特征:

[{'is_alphanumeric': 0,
  'is_complete_capital': 0,
  'is_first_capital': 1,
  'is_first_word': 1,
  'is_last_word': 0,
  'is_numeric': 0,
  'next_word': 'Wall',
  'prefix_1': 'O',
  'prefix_2': 'On',
  'prefix_3': 'On',
  'prefix_4': 'On',
  'prev_word': '',
  'suffix_1': 'n',
  'suffix_2': 'On',
  'suffix_3': 'On',
  'suffix_4': 'On',
  'word_has_hyphen': 0},
 {'is_alphanumeric': 0,
  'is_complete_capital': 0,
  'is_first_capital': 1,
  'is_first_word': 0,
  'is_last_word': 0,
  'is_numeric': 0,
  'next_word': 'Street',
  'prefix_1': 'W',
  'prefix_2': 'Wa',
  'prefix_3': 'Wal',
  'prefix_4': 'Wall',
  'prev_word': 'On',
  'suffix_1': 'l',
  'suffix_2': 'll',
  'suffix_3': 'all',
  'suffix_4': 'Wall',
  'word_has_hyphen': 0},
...
]

以及对应的标签:

['ADP',
 'NOUN',
 'NOUN',
...
]

接着使用sklearn-crfsuite - sklearn-crfsuite 0.3 documentation 进行模型训练:

from sklearn_crfsuite import CRF
crf = CRF(
    algorithm='lbfgs',
    c1=0.01,
    c2=0.1,
    max_iterations=100,
    all_possible_transitions=True
)
crf.fit(X_train, y_train)

模预预测

对测试集进行预测,并计算F1指标

y_pred=crf.predict(X_test)
metrics.flat_f1_score(y_test, y_pred,average='weighted',labels=crf.classes_)
“”“
0.9738471726864286
”“”

可以看到最终评估指标高达97.3% 表明抽取的特征很有效,CRF也是一个很强的模型。

下面看一下头部的转移特征和状态特征:

# 头部的转移特征
[(('ADJ', 'NOUN'), 4.114996),
 (('NOUN', 'NOUN'), 2.935448),
 (('NOUN', 'VERB'), 2.891987),
 (('VERB', 'PRT'), 2.519179),
 (('X', 'VERB'), 2.271558),
 (('ADP', 'NOUN'), 2.265833),
 (('NOUN', 'PRT'), 2.172849),
 (('PRON', 'VERB'), 2.117186),
 (('NUM', 'NOUN'), 2.059221),
 (('DET', 'NOUN'), 2.053832),
 (('ADV', 'VERB'), 1.994419),
 (('ADV', 'ADJ'), 1.957063),
 (('NOUN', 'ADP'), 1.838684),
 (('VERB', 'NOUN'), 1.763319),
 (('ADJ', 'ADJ'), 1.660578),
 (('NOUN', 'CONJ'), 1.591359),
 (('PRT', 'NOUN'), 1.398473),
 (('NOUN', '.'), 1.381863),
 (('NOUN', 'ADV'), 1.380086),
 (('ADV', 'ADV'), 1.301282)]
# 头部的状态特征
[(('prev_word:will', 'VERB'), 6.751359),
 (('prev_word:would', 'VERB'), 5.940819),
 (('prefix_1:*', 'X'), 5.830558),
 (('suffix_4:rest', 'NOUN'), 5.644523),
 (('suffix_2:ly', 'ADV'), 5.260228),
 (('is_first_capital', 'NOUN'), 5.043121),
 (('prev_word:could', 'VERB'), 5.018842),
 (('suffix_3:ous', 'ADJ'), 4.870949),
 (('prev_word:to', 'VERB'), 4.849822),
 (('suffix_4:will', 'VERB'), 4.677684),
 (('next_word:appeal', 'ADJ'), 4.386434),
 (('prev_word:how', 'PRT'), 4.35094),
 (('suffix_4:pany', 'NOUN'), 4.329975),
 (('prefix_4:many', 'ADJ'), 4.205028),
 (('prev_word:lock', 'PRT'), 4.153643),
 (('word_has_hyphen', 'ADJ'), 4.151036),
 (('prev_word:tune', 'PRT'), 4.147576),
 (('next_word:Express', 'NOUN'), 4.137127),
 (('suffix_4:food', 'NOUN'), 4.116688),
 (('suffix_2:ed', 'VERB'), 4.070659)]

根据转移特征('ADJ', 'NOUN')可以看到「形容词」后面接「名词」的概率非常高;根据状态特征('prev_word:will', 'VERB')可以看到如果上一个单词是will,那么当前词的词性为「动词」的概率非常高。

本篇的CRF还处于统计学习阶段,所以构建的特征也都是手工构建的特征,还是存在一定的局限性。所以后来在深度学习时代,一般会先用LSTM或者BERT这种深度神经网络先进行特征的抽取,再送到CRF进行标签的预测,能有更加显著的效果提升。

参考

Introduction to Conditional Random Fields

NLP Guide: Identifying Part of Speech Tags using Conditional Random Fields

github.com/AiswaryaSrin

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在 Keras 中使用 LSTM-CRF 模型进行词性标注的步骤如下: 1. 准备数据:将文本数据和标签数据处理成模型所需的格式,可以使用 Keras 的 Tokenizer 来转换文本数据。 2. 构建模型:使用 Keras 中的 Sequential 模型,并添加 Embedding 层、LSTM 层和 CRF 层。 3. 编译模型:指定损失函数和优化器,并选择评价指标。 4. 训练模型:使用训练数据对模型进行训练。 5. 预测:使用训练好的模型对新的文本数据进行预测。 下面是一个简单的 LSTM-CRF 模型实现的示例代码: ```python from keras.models import Sequential from keras.layers import Embedding, LSTM, Dense, TimeDistributed from keras_contrib.layers import CRF # 定义模型 model = Sequential() model.add(Embedding(input_dim=vocab_size, output_dim=embedding_dim, input_length=max_len)) model.add(LSTM(units=lstm_units, return_sequences=True)) model.add(TimeDistributed(Dense(num_tags))) crf = CRF(num_tags) model.add(crf) # 编译模型 model.compile(optimizer='adam', loss=crf.loss_function, metrics=[crf.accuracy]) # 训练模型 model.fit(X_train, y_train, batch_size=batch_size, epochs=num_epochs, validation_data=(X_test, y_test)) # 预测 y_pred = model.predict(X_new) ``` 其中,`vocab_size` 表示词汇表大小,`embedding_dim` 表示词向量维度,`max_len` 表示文本序列的最大长度,`lstm_units` 表示 LSTM 层的神经元个数,`num_tags` 表示标签数量,`batch_size` 表示批次大小,`num_epochs` 表示训练轮数。`X_train` 和 `y_train` 分别表示训练数据的文本和标签,`X_test` 和 `y_test` 分别表示测试数据的文本和标签,`X_new` 表示需要预测的新数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nghuyong

您的鼓励是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值