用条件随机场(CRF)实现词性标注(基于tensorflow)
条件随机场在词性标注方面发挥重要作用,通过这个实例,既可以帮助理解条件随机场,也可以了解词性标注。
词性标注
1.词性标注就是给一个句子中的每个单词注明词性。例如:“He drank coffee at Starbucks”,注明词性后是:“He(代词)drank(动词)coffee(名词)at(介词)Starbucks(名词)”。我们把(代词,动词,名词,介词,名词)作为标记序列(也可认为是状态序列),记为Y,把(He,drank,coffee,at,Starbucks)作为观测序列,记为X。
2.我们可以定义一个特征函数集合,用这个特征函数集合来为一个标记序列打分,并据此选出最靠谱的标注序列。
3.特征函数介绍: 它接收四个参数:
- 句子X:就是我们要标注词性的句子。
- i:用来表示句子X中第i个参数。
- yi:表示要评分的标注序列给第i个单词标注的词性。
- yi-1:表示要评分的标注序列给第i-1个单词标注的词性。
特征函数的输出值是0或1,0表示要评分的标注序列不符合这个特征,1表示要评分的标注序列符合这个特征。这里,特征函数仅仅依靠当前单词的标签和它前面的单词的标签对标注序列进行评判,这样建立的CRF叫做线性链CRF。
4.定义好一组特征函数后,要给每个特征函数fi赋予一个权重
λ
\lambda
λj。只要有个句子X,有一个标注序列Y,就可以利用前面定义特征函数集来对1评分:
s
c
o
r
e
(
Y
∣
X
)
=
∑
j
=
1
m
∑
i
=
1
n
λ
j
f
j
(
X
,
i
,
y
i
,
y
i
−
1
)
score(Y|X) = \sum_{j=1}^m\sum_{i=1} ^n\lambda_jf_j~(X,i,y_i , y_{i-1})
score(Y∣X)=j=1∑mi=1∑nλjfj (X,i,yi,yi−1)
上式中有两个求和,外面求和是求每个特征函数fi评分值得和,里面的求和来求每个位置单词的特征值的和。
5.对这个分数进行指数化和标准化,就可以得到标注序列1的概率值P(X|Y):
P
(
X
∣
Y
)
=
e
x
p
[
s
c
o
r
e
(
Y
∣
X
)
]
∑
Y
′
e
x
p
[
s
c
o
r
e
(
Y
′
∣
X
)
]
P(X|Y) = \frac{exp[score(Y|X)]}{\sum_{Y'}exp[score(Y'|X)]}
P(X∣Y)=∑Y′exp[score(Y′∣X)]exp[score(Y∣X)]
TensorFlow实现
用TensorFlow实现CRF的主要步骤包括参数设置、构建随机特征、随机场tag、训练及评估模型、以下为详细代码:
import numpy as np
import tensorflow as tf
# 参数设置
num_examples = 10
num_words = 20
num_features = 100
num_tags = 5
# 构建随机特征
x = np.random.rand(num_examples, num_words, num_features).astype(np.float32)
# 构建随机tag
y = np.random.randint(
num_tags, size=[num_examples, num_words]).astype(np.int32)
# 获取样本句长向量(因为每一个样本可能包含不一样多的词),在这里统一设为 num_words - 1,真实情况下根据需要设置
sequence_lengths = np.full(num_examples, num_words - 1, dtype=np.int32)
# 训练,评估模型
with tf.Graph().as_default():
with tf.Session() as session:
x_t = tf.constant(x)
y_t = tf.constant(y)
sequence_lengths_t = tf.constant(sequence_lengths)
# 在这里设置一个无偏置的线性层
weights = tf.get_variable("weights", [num_features, num_tags])
matricized_x_t = tf.reshape(x_t, [-1, num_features])
matricized_unary_scores = tf.matmul(matricized_x_t, weights)
unary_scores = tf.reshape(matricized_unary_scores,
[num_examples, num_words, num_tags])
# 计算log-likelihood并获得transition_params
log_likelihood, transition_params = tf.contrib.crf.crf_log_likelihood(
unary_scores, y_t, sequence_lengths_t)
# 进行解码(维特比算法),获得解码之后的序列viterbi_sequence和分数viterbi_score
viterbi_sequence, viterbi_score = tf.contrib.crf.crf_decode(
unary_scores, transition_params, sequence_lengths_t)
loss = tf.reduce_mean(-log_likelihood)
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(loss)
session.run(tf.global_variables_initializer())
mask = (np.expand_dims(np.arange(num_words), axis=0) < # np.arange()创建等差数组
np.expand_dims(sequence_lengths, axis=1)) # np.expand_dims()扩张维度
# 得到一个num_examples*num_words的二维数组,数据类型为布尔型,目的是对句长进行截断
# 将每个样本的sequence_lengths加起来,得到标签的总数
total_labels = np.sum(sequence_lengths)
# 进行训练
for i in range(1000):
tf_viterbi_sequence, _ = session.run([viterbi_sequence, train_op])
if i % 100 == 0:
correct_labels = np.sum((y == tf_viterbi_sequence) * mask)
accuracy = 100.0 * correct_labels / float(total_labels)
print("Accuracy: %.2f%%" % accuracy)