crf 命名实体 java,pytorch实现BiLSTM+CRF用于NER(命名实体识别)

pytorch实现BiLSTM+CRF用于NER(命名实体识别)

在写这篇博客之前,我看了网上关于pytorch,BiLstm+CRF的实现,都是一个版本(对pytorch教程的翻译),

翻译得一点质量都没有,还有一些竟然说做得是词性标注,B,I,O是词性标注的tag吗?真是误人子弟。所以

自己打算写一篇关于pytorch上实现命名实体识别的翻译,加入自己的理解。前面是一些牢骚话

BiLSTM

我上篇博客介绍了pytorch实现LSTM 链接,这里是BiLSTM,网络结构图如下

单向的LSTM,当前序列元素只能看见前面的元素,而无法看见后面的元素,双向LSTM克服了这个缺点,既能

看见前面的元素,也能看见后面的元素。学术一点的就是,单向LSTM无法编码从后往前的信息

注意一点双向LSTM的输出O OO是[Oleft O_{left}O

left

,Oright O_{right}O

right

],即size为(2, 隐藏单元数)

CRF

CRF是判别模型, 判别公式如下y yy是标记序列,x xx是单词序列,即已知单词序列,求最有可能的标记序列

P(y∣x)=exp(Score(x,y))∑y′exp(Score(x,y′)) P(y|x) = \frac{\exp{(\text{Score}(x, y)})}{\sum_{y'} \exp{(\text{Score}(x, y')})}

P(y∣x)=

y

exp(Score(x,y

))

exp(Score(x,y))

Score(x,y) Score(x,y)Score(x,y)即单词序列x xx产生标记序列y yy的得分,得分越高,说明其产生的概率越大。

在pytorch教程中链接,其用于实体识别定义的Score(x,y) Score(x,y)Score(x,y)包含两个特征函数,一个是转移特征函数

一个是状态特征函数

Score(x,y)=∑ilogψEMIT(yi→xi)+logψTRANS(yi−1→yi) {Score}(x,y) = \sum_i \log \psi_\text{EMIT}(y_i \rightarrow x_i) + \log \psi_\text{TRANS}(y_{i-1} \rightarrow y_i)

Score(x,y)=

i

logψ

EMIT

(y

i

→x

i

)+logψ

TRANS

(y

i−1

→y

i

)

代码中用到了前向算法和维特比算法,在代码中我会具体解释

log_sum_exp函数就是计算log∑ni=1exi log\sum^n_{i=1}{e^{x_{i}}}log∑

i=1

n

e

x

i

,前向算法需要用到这个函数

def log_sum_exp(vec):

max_score = vec[0, argmax(vec)]

max_score_broadcast = max_score.view(1, -1).expand(1, vec.size()[1])

return max_score + \

torch.log(torch.sum(torch.exp(vec - max_score_broadcast)))

1

2

3

4

5

前向算法,求出α \alphaα,即Z(x) Z(x)Z(x), 也就是∑y′exp(Score(x,y′)) {\sum_{y'} \exp{(\text{Score}(x, y')})}∑

y

exp(Score(x,y

)),如果不懂可以看一下李航的书关于CRF的前向算法

但是不同于李航书的是,代码中α \alphaα都取了对数,一个是为了运算方便,二个为了后面的最大似然估计。

这个代码里面没有进行优化,作者也指出来了,其实对feats的迭代完全没有必要用两次循环,其实矩阵相乘

就够了,作者是为了方便我们理解,所以细化了步骤

def _forward_alg(self, feats):

init_alphas = torch.full((1, self.tagset_size), -10000.)

#初始时,start位置为0,其他位置为-10000

init_alphas[0][self.tag_to_ix[START_TAG]] = 0.

#赋给变量方便后面反向传播

forward_var = init_alphas

for feat in feats:

alphas_t = []

for next_tag in range(self.tagset_size):

#状态特征函数的得分

emit_score = feat[next_tag].view(1, -1).expand(1, self.tagset_size)

#状态转移函数的得分

trans_score = self.transitions[next_tag].view(1, -1)

#从上一个单词的每个状态转移到next_tag状态的得分

#所以next_tag_var是一个大小为tag_size的数组

next_tag_var = forward_var + trans_score + emit_score

#对next_tag_var进行log_sum_exp操作

alphas_t.append(log_sum_exp(next_tag_var).view(1))

forward_var = torch.cat(alphas_t).view(1, -1)

terminal_var = forward_var + self.transitions[self.tag_to_ix[STOP_TAG]]

alpha = log_sum_exp(terminal_var)

return alpha

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

维特比算法中规中矩,可以参考李航书上条件随机场的预测算法

def _viterbi_decode(self, feats):

backpointers = []

#初始化

init_vvars = torch.full((1, self.tagset_size), -10000.)

init_vvars[0][self.tag_to_ix[START_TAG]] = 0

forward_var = init_vvars

for feat in feats:

#保持路径节点,用于重构最优路径

bptrs_t = []

#保持路径变量概率

viterbivars_t = []

for next_tag in range(self.tagset_size):

next_tag_var = forward_var + self.transitions[next_tag]

best_tag_id = argmax(next_tag_var)

bptrs_t.append(best_tag_id)

viterbivars_t.append(next_tag_var[0][best_tag_id].view(1))

forward_var = (torch.cat(viterbivars_t) + feat).view(1, -1)

backpointers.append(bptrs_t)

#转移到STOP_TAG

terminal_var = forward_var + self.transitions[self.tag_to_ix[STOP_TAG]]

best_tag_id = argmax(terminal_var)

path_score = terminal_var[0][best_tag_id]

#反向迭代求最优路径

best_path = [best_tag_id]

for bptrs_t in reversed(backpointers):

best_tag_id = bptrs_t[best_tag_id]

best_path.append(best_tag_id)

#把start_tag pop出来,最终的结果不需要

start = best_path.pop()

assert start == self.tag_to_ix[START_TAG]

best_path.reverse()

return path_score, best_path

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

其实我最想讲的是这个函数

def neg_log_likelihood(self, sentence, tags):

feats = self._get_lstm_features(sentence)

forward_score = self._forward_alg(feats)

gold_score = self._score_sentence(feats, tags)

return forward_score - gold_score

1

2

3

4

5

我们知道forward_score是logZ(x) logZ(x)logZ(x),即log∑y′exp(Score(x,y′)) log{\sum_{y'} \exp{(\text{Score}(x, y')})}log∑

y

exp(Score(x,y

)),

gold_score是logexp(Score(x,y′) log\exp{(\text{Score}(x, y')}logexp(Score(x,y

)

我们的目标是极大化

P(y∣x)=exp(Score(x,y))∑y′exp(Score(x,y′)) P(y|x) = \frac{\exp{(\text{Score}(x, y)})}{\sum_{y'} \exp{(\text{Score}(x, y')})}

P(y∣x)=

y

exp(Score(x,y

))

exp(Score(x,y))

两边取对数即

logP(y∣x)=log exp(Score(x,y))−log∑y′exp(Score(x,y′))logP(y∣x)=gold_score−forward_score logP(y|x) = log\ {\exp{(\text{Score}(x, y)})}-log{\sum_{y'} \exp{(\text{Score}(x, y')})} \\logP(y|x)=gold\_score-forward\_score

logP(y∣x)=log exp(Score(x,y))−log

y

exp(Score(x,y

))

logP(y∣x)=gold_score−forward_score

所以我们需要极大化gold_score−forward_score gold\_score - forward\_scoregold_score−forward_score,也就是极小化forward_score−gold_score forward\_score -gold\_scoreforward_score−gold_score

也就是为什么forward_score−gold_score forward\_score - gold\_scoreforward_score−gold_score可以作为loss的根本原因

---------------------

作者:zycxnanwang

来源:CSDN

原文:https://blog.csdn.net/zycxnanwang/article/details/90385259

版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值