引言

此文不是对预训练模型融入实体信息、知识图谱等类似ERNIE,k-bert这种,而是在拿到bert输出后,突出指定位置信息进去,从而控制判定的结果。

问题

比如这句话:

该报还报道,法国达能集团日前宣布将投资1亿欧元,加强在中国市场的奶粉生产和研发,并表示“我们对中国市场的长期增长能力充满信心”。

谁加强和谁表示呢?是法国达能集团,而不是该报。

主体 触发词 客体
法国达能集团 加强 在中国市场的奶粉生产和研发
法国达能集团 表示 “我们对中国市场的长期增长能力充满信心”

那假设,我们在知道触发词客体的情况下,如何从原句中获取主体呢?

思路

1. 引入其他layer和bert进行concat

这怕是最容易想到的方法了。比如将句子触发词客体分别输入到bert,然后将这三者concat。emmm,这也怕是最蠢的方法了。

2. 修改触发词客体所在bert输出索引的权重

比如一句话长度为128,拿到bert输出后为(128, 768),假设触发词客体对应的span为(10,12)(15, 30),那如何手动修改对应span的weight呢??

方法一,直接进行mask_fill,将对应span的值改为比如0。这种方式,,,emmmm,怎么说呢,看看就行。
方法二,也是操作对应span的weights,但是分成两步,第一是取触发词的权重,第二是取客体的权重,各自经过各自的linear,得到新的权重,然后再和bert output做一个交互,意思还是想突出触发词客体的权重。和方法一一样,只不过对应的weight不是0,而是经过反向传播更新后的值。

比如conditional layer norm(对原conditional layer norm做修改了哦)。

伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class NewConditionalLayerNorm(nn.Module):
def __init__(self, normalized_shape1, normalized_shape2):
super().__init__()
# 触发词的
self.trigger_feature_weight = ...
self.trigger_feature_bias = ...

self.trigger_feature_weight_dense = ...
self.trigger_feature_bias_dense = ...
# 客体的
self.object_feature_weight = ...
self.object_feature_bias = ...
self.object_feature_weight_dense = ...
self.object_feature_bias_dense = ...

def forward(self, bert_output, trigger_feature, object_feature):
# 1. 先计算bert_output的layer norm
# 2. 计算trigger的weight和bias
# 3. 计算object的weight和bias
# 4. 三者相乘

关于Conditional Layer Normalization,可看:讯飞2020年事件提取比赛第一名-主客体提取中Conditional Layer Normalization实现方式,难点在于变长罢了。

效果没试,总感觉复杂了些。

另外如果输入的feature个数是变化的,那这种方式就不可行了。

3. 变化标注方式,还是利用bert本身

输入一句话,tokenizer后拿到input_ids,token_type_ids, attention_mask,那其中的token_type_ids是干嘛的呢?百度下就有结果,说是如果是0就表示第一句话,如果是1就表示第二句话。

如果这样的话,那直接将触发词客体的所对应的token_type_ids置为1不就又是一种方式么~,试了下效果出奇的不错。准确率嗖嗖的往上。而且还没引入额外的layer,相当拿bert就把这件事情搞定了。

这也是我写这篇文章的动力。。。

实现方式如下:

比如一句话”我们喜欢晴天。”,label设计为如下:

1
2
3
4
5
6
# 假设我们为主语
input_tokens = ['我', '们', '喜', '欢', '晴', '天', '。']
# token_type_ids
token_type_ids = [0, 0, 1, 1, 1, 1, 0]
# label
label = [[1, 0], [0, 1], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]

模型就是bert+linear,linear输出hidden_size为2,loss使用binary_cross_entropy即可。

缺陷嘛就是它没有区分每个feature本身的label。客体就是客体,触发词就是触发词~~

总结

所以通篇看下来,目的是想突出触发词客体的weight,从而提取其对应的主体。前两者不同,那主体也有可能不同。

实现下来呢,如果不区分触发词和客体的话呢,可以使用上面那种方式。如果区分呢,那貌似只能引入新的layer来解决。

bert本身就够大的了,就不能一个预训练模型就能解决这种问题的么?比如下面这种方式:

1
2
3
4
5
6
预训练模型:
> 输入句子:*******
> 输入客体和触发词: *******

返回:
> 主体:***

嘿嘿,现在也有这种,不过坑更大,而且消耗的资源也更多,,,以后有机会再聊。

额外说一句,和本文无关,如果一个任务业界没有相关的研究,那你对这个任务进行建模、训练等,怎么评估这个模型是有效的呢?

  1. 有一定量的标注数据,这个应该是必不可免的。但是也是操作可能性很低的地方。
  2. 搭建一个简单的模型,试下基本效果。为后续方式作参考。
  3. 变换一种建模方式,模型参数尽量行之有效,并且建模方式又足够巧妙。
  4. 看loss走向和评估指标的变化。

又水了一篇~