0.前言
CRF的原理已經(jīng)夠難理解了,需要解決的問題主要包括三大塊:
- 概率計算問題,前向—后向算法庄呈,是一個遞推公式,這個和hmm是一樣的派阱。
- 學(xué)習(xí)問題诬留,這是判別式模型必須要有的東西,得訓(xùn)練參數(shù)贫母,常用的方法是改進(jìn)的迭代尺度法文兑,擬牛頓法。
- 預(yù)測問題腺劣,維特比算法绿贞,這是個動態(tài)規(guī)劃方法,hmm和crf都會用到橘原。這個好像廢話籍铁,目的都是為了預(yù)測,當(dāng)然要用靠柑。
數(shù)學(xué)公式一大堆寨辩,什么向量形式,矩陣形式歼冰,著實難以理解,但是關(guān)于事先就很簡單了耻警,哈哈哈隔嫡。下面分別基于tensorflow甸怕、keras、pytorch來實現(xiàn)CRF腮恩。
1.tensorflow實現(xiàn)
tensorflow1.0可真難用啊梢杭,吐槽一下,還是2.0好用秸滴。舉個小例子武契,你定義一個op操作以后,即使是簡單的x1+x2荡含,要想看輸出咒唆,還得print(sess.run()),2.0就不用了释液,大家趕緊上手2.0全释。不過這里還是基于tensorflow1.0實現(xiàn)的。
tensorflow實現(xiàn)crf就三個函數(shù)误债,crf_log_likelihood浸船、viterbi_decode、crf_decode寝蹈,他們都在tf.contrib.crf這個API里李命,搞懂這三個函數(shù),不管事BiLSTM+CRF還是BERT+BiLSTM+CRF你都游刃有余了箫老。
- tf.contrib.crf.crf_log_likelihood
crf_log_likelihood(inputs,tag_indices,sequence_lengths,transition_params=None)
通俗理解封字,這是CRF的訓(xùn)練函數(shù)。
首先來看輸入:
(1)inputs槽惫,維度為[batch_size, max_seq_len, num_tags]周叮,一般是LSTM的輸出,要轉(zhuǎn)換成這個要求的維度界斜,再到CRF里邊訓(xùn)練仿耽。
batch_size是批次訓(xùn)練樣本量,好理解各薇,不解釋项贺。
maxseq_len是輸入文本的長度,相當(dāng)于LSTM里的input_dim峭判,就是輸入幾個單詞开缎。
num_tags是可供選擇的單詞個數(shù),比如你覺得這個位置有5個可能的單詞林螃,那這個就是5奕删。
(2)tag_indices,維度為[batch_size, max_seq_len]疗认。
具體的和inputs一樣完残,只不過這個是真實的標(biāo)簽伏钠,也就是相應(yīng)位置對應(yīng)的真實y值。
(3)sequence_lengths谨设,維度為 [batch_size]熟掂。
表示的是每一個序列的長度,是一維的扎拣,相當(dāng)于max_sql_len赴肚,可以用np.full這個函數(shù)實現(xiàn)。
(4)transition_params二蓝,維度為[num_tags, num_tags]誉券,是轉(zhuǎn)移矩陣,要是事先沒有就訓(xùn)練一個侣夷。
然后來看輸出:
(1)log_likelihood横朋,標(biāo)量,還記得吧百拓,CRF訓(xùn)練參數(shù)用的是極大似然估計琴锭,這個值取負(fù)數(shù)就是交叉熵?fù)p失。
(2)transition_params衙传,維度為[num_tags, num_tags]决帖,轉(zhuǎn)移矩陣,這個是我們預(yù)測要用到的蓖捶。 - tf.contrib.crf.viterbi_decode
viterbi_decode(score,transition_params)
這個函數(shù)返回最好序列的標(biāo)簽地回,用的場景不是特別多。
輸入:
(1)score,維度為[seq_len, num_tags]俊鱼,參數(shù)的意思就不解釋了刻像,具體看上邊的說法,這就是一個得分并闲。
(2)transition_params细睡,維度為[num_tags, num_tags],上邊訓(xùn)練輸出的轉(zhuǎn)移矩陣帝火。
輸出:
(1)viterbi溜徙,維度[seq_len],保留了每一步對應(yīng)得分值最高的索引犀填。
(2)viterbi_score蠢壹,維度為[sel_len],這個是維特比的具體得分九巡。 - tf.contrib.crf.viterbi_decode
crf_decode(potentials,transition_params,sequence_length)
這個函數(shù)和上邊那個差不多图贸,但是很常用。
輸入:
(1)potentials,維度為[batch_size, max_seq_len, num_tags]求妹,這個是滿足條件的一個輸入乏盐,可以使輸入和一個權(quán)重矩陣乘后的結(jié)果佳窑。
(2)transition_params制恍,轉(zhuǎn)義矩陣不多說。
(3)sequence_length神凑,和上邊一樣净神,輸入長度構(gòu)成的一維矩陣。
輸出:
(1)decode_tags溉委,維度為[batch_size, max_seq_len] 鹃唯,是一個最好序列的標(biāo)記。
(2)best_score瓣喊,維度為[batch_size]坡慌,每個序列的最好得分。
來看一個小例子藻三,這個例子是一個隨機(jī)的數(shù)字輸入洪橘,對應(yīng)一個只含0,1兩個狀態(tài)的目標(biāo)矩陣棵帽,然后根據(jù)輸入預(yù)測輸出熄求。代碼如下:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
Timestep = 15#輸入的總長度,可以理解為15個rnn cell
Batchsize = 1#一次就輸入一個
Inputsize = 1
LR = 0.5
num_tags = 2
#定義batch輸出
def get_batch():
xs = np.array([[2, 3, 4, 5, 5, 5, 1, 5, 3, 2, 5, 5, 5, 3, 5]])
res = np.array([[0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1]])
return [xs[:, :, np.newaxis], res]
# xs, res = get_batch()
# print(xs)
# xs變成三維的 res還是二維的
class crf:
def __init__(self, time_steps, input_size, num_tags, batch_size):
self.time_steps = time_steps
self.input_size = input_size
self.num_tags = num_tags
self.batch_size = batch_size
self.xs = tf.placeholder(tf.float32, [None, self.time_steps, self.input_size], name='xs')
self.res = tf.placeholder(tf.int32, [self.batch_size, self.time_steps], name='res')#為什么和xs的定義模式不一樣
weights = tf.get_variable('weights', [self.input_size, self.num_tags])
matricized_xs = tf.reshape(self.xs, [-1, self.input_size])
matricized_unary_scores = tf.matmul(matricized_xs, weights)
unary_scores = tf.reshape(matricized_unary_scores, [self.batch_size, self.time_steps, self.num_tags])
sequence_len = np.full(self.batch_size, self.time_steps, dtype=np.int32)
log_likelihood, transition_params = tf.contrib.crf.crf_log_likelihood(unary_scores, self.res, sequence_len)
self.pred, viterbiscore = tf.contrib.crf.crf_decode(unary_scores, transition_params, sequence_len)
self.loss = tf.reduce_mean(-log_likelihood)
self.train_op = tf.train.AdamOptimizer(LR).minimize(self.loss)
if __name__ == '__main__':
model = crf(Timestep, Inputsize, num_tags, Batchsize)
sess = tf.Session()
sess.run(tf.initialize_all_variables())
plt.ion()#動態(tài)曲線
plt.show()
for i in range(150):
xs, res = get_batch()
feed_dict = {model.xs: xs,
model.res: res}
_, cost, pred = sess.run([model.train_op, model.loss, model.pred],
feed_dict=feed_dict)#只有placeholder才可以feed
x = xs.reshape(-1, 1)
r = res.reshape(-1, 1)
p = pred.reshape(-1, 1)
x = range(len(x))
plt.clf()
plt.plot(x, r, 'r', x, p, 'g')
plt.ylim(-1.2, 1.2)
plt.draw()
plt.pause(0.3)
if i % 20 == 0:
print('cost:', round(cost, 4))