关注微信公众号:NLP分享汇。【喜欢的扫波关注,每天都在更新自己之前的积累】
文章链接:https://mp.weixin.qq.com/s/OebxnvwjQiVbBZZFL2Un3A
前言
信息抽取(Information Extraction, IE)是从自然语言文本中抽取实体、属性、关系及事件等事实类信息的文本处理技术,是信息检索、智能问答、智能对话等人工智能应用的重要基础,一直受到业界的广泛关注。信息抽取任务涉及命名实体识别、指代消解、关系分类等复杂技术,极具挑战性。而本文旨在介绍如何利用Bert预训练模型进行关系抽取任务。
相关链接
GitHub:https://github.com/yuanxiaosc/Entity-Relation-Extraction
竞赛官网:http://lic2019.ccf.org.cn/
理解难度:★★★★★
解决思路
1、先使用bert搭建的关系分类模型,简单来看就是一个多标签分类任务,类别就是下述的那几种关系;
2、接着用预测出来的关系和文本,使用bert搭建一个实体抽取的模型,其简单来看也是一个分类模型,类别是:
["[Padding]", "[category]", "[##WordPiece]", "[CLS]", "[SEP]", "B-SUB", "I-SUB", "B-OBJ", "I-OBJ", "O"]
【 SUB对应的就是subject,B-SUB就是第一个实体开始的位置,后续的是I-SUB,OBJ就是第二个实体 】
关系类型如下:
['丈夫', '上映时间', '专业代码', '主持人', '主演', '主角', '人口数量', '作曲', '作者', '作词', '修业年限', '出品公司', '出版社', '出生地', '出生日期', '创始人', '制片人', '占地面积', '号', '嘉宾', '国籍', '妻子', '字', '官方语言', '导演', '总部地点', '成立日期', '所在城市', '所属专辑', '改编自', '朝代', '歌手', '母亲', '毕业院校', '民族', '气候', '注册资本', '海拔', '父亲', '目', '祖籍', '简称', '编剧', '董事长', '身高', '连载网站', '邮政编码', '面积', '首都']
思路分析
所以第二个模型就是预测每一个tokens的标示,最后根据标示可提取出实体对。
第二个模型是一个多分类任务,我们知道一句话中有可能有多个三元组,为此在进行第二个模型的时候,是先依据第一个模型预测出来的关系类如当前句子预测出3个关系,那么就重复该句话分成3个样本,那么3个样本就对应的是3个多分类单标签任务,为了使实体对和关系对应,所以第二个模型在计算loss的时候是综合考虑了关系和tokens标示的预测的。
说到这里可能会有这样的疑惑?两个模型(所谓的管道),似乎没有交集,都是先处理准备好各自数据,然后各自训练各自的,而且第二个模型同时进行了关系和实体标示预测,那么第一个模型只预测了关系,那么第一个模型存在的意义是什么?直接用第二模型不就可以啦?
逻辑是这样的:
训练确实没有交集,各自训练各自的,因为训练样本都是精确的,无可厚非,但是在预测的时候我们只有一句话,要预测出这句话中的所有三元组,如果只采用第二个模型的话,它一句话根据当前的关系只能预测出一种三元组,所以需要第一个模型打前阵,先将句子中有多少种关系预测出来,然后再将句子依照关系分成一句话一个三元组,训练的时候我们是知道每一个句子有多少种关系,但是预测的时候我们并不知道,这就是第一个模型存在的意义,那么我就要用第二个模型同时解决一句话预测所有三元组呢?其实很难,因为首先tokens标记就是问题,怎么将标示和关系对应呢?是吧,所以这里所谓管道,其实是在预测过程体现的,下面就可以看到另外按照上面的思路,该模型bert的输入端一个样本的特征应该是当前句子和关系,而不仅仅是一个句子,不然的话将一句话拆分成多个样本还有什么意义?大家不就都一样啦,正是由于还有关系,才能实现同一句子抽取出不同的实体对(对应当前关系)。
那么输入端是怎么将将关系整合进去的呢?
核心代码部分
模型一:bert搭建的关系分类模型
def create_model(bert_config, is_training, input_ids, input_mask, segment_ids,
labels, num_labels, use_one_hot_embeddings):
"""Creates a classification model."""
model = modeling.BertModel(
config=bert_config,
is_training=is_training,
input_ids=input_ids,
input_mask=input_mask,
token_type_ids=segment_ids,
use_one_hot_embeddings=use_one_hot_embeddings)
# In the demo, we are doing a simple classification task on the entire
# segment.
#
# If you want to use the token-level output, use model.get_sequence_output()
# instead.
output_layer = model.get_pooled_output()
hidden_size = output_layer.shape[-1].value
output_weights = tf.get_variable(
"output_weights", [num_labels, hidden_size],
initializer=tf.truncated_normal_initializer(stddev=0.02))
output_bias = tf.get_variable(
"output_bias", [num_labels], initializer=tf.zeros_initializer())
with tf.variable_scope("loss"):
if is_training:
# I.e., 0.1 dropout
output_layer = tf.nn.dropout(output_layer, keep_prob=0.9)
logits_wx = tf.matmul(output_layer, output_weights, transpose_b=True)
logits = tf.nn.bias_add(logits_wx, output_bias)
probabilities = tf.sigmoid(logits)
label_ids = tf.cast(labels, tf.float32)
per_example_loss = tf.reduce_sum(
tf.nn.sigmoid_cross_entropy_with_logits(logits=logits, labels=label_ids), axis=-1)
loss = tf.reduce_mean(per_example_loss)
return loss, per_example_loss, logits, probabilities
模型二:bert搭建一个实体抽取模型
1、关系预测部分
predicate_output_layer = model.get_pooled_output()
intent_hidden_size = predicate_output_layer.shape[-1].value
predicate_output_weights = tf.get_variable(
"predicate_output_weights", [num_predicate_labels, intent_hidden_size],
initializer=tf.truncated_normal_initializer(stddev=0.02))
predicate_output_bias = tf.get_variable(
"predicate_output_bias", [num_predicate_labels], initializer=tf.zeros_initializer())
with tf.variable_scope("predicate_loss"):
if is_training:
# I.e., 0.1 dropout
predicate_output_layer = tf.nn.dropout(predicate_output_layer, keep_prob=0.9)
predicate_logits = tf.matmul(predicate_output_layer, predicate_output_weights, transpose_b=True)
predicate_logits = tf.nn.bias_add(predicate_logits, predicate_output_bias)
predicate_probabilities = tf.nn.softmax(predicate_logits, axis=-1)
predicate_prediction = tf.argmax(predicate_probabilities, axis=-1, output_type=tf.int32)
predicate_labels = tf.one_hot(predicate_label_id, depth=num_predicate_labels, dtype=tf.float32)
predicate_per_example_loss = tf.reduce_sum(tf.nn.sigmoid_cross_entropy_with_logits(logits=predicate_logits, labels=predicate_labels), -1)
predicate_loss = tf.reduce_mean(predicate_per_example_loss)
2、序列标注部分
token_label_output_layer = model.get_sequence_output()
token_label_hidden_size = token_label_output_layer.shape[-1].value
token_label_output_weight = tf.get_variable(
"token_label_output_weights", [num_token_labels, token_label_hidden_size],
initializer=tf.truncated_normal_initializer(stddev=0.02)
)
token_label_output_bias = tf.get_variable(
"token_label_output_bias", [num_token_labels], initializer=tf.zeros_initializer()
)
with tf.variable_scope("token_label_loss"):
if is_training:
token_label_output_layer = tf.nn.dropout(token_label_output_layer, keep_prob=0.9)
token_label_output_layer = tf.reshape(token_label_output_layer, [-1, token_label_hidden_size])
token_label_logits = tf.matmul(token_label_output_layer, token_label_output_weight, transpose_b=True)
token_label_logits = tf.nn.bias_add(token_label_logits, token_label_output_bias)
token_label_logits = tf.reshape(token_label_logits, [-1, FLAGS.max_seq_length, num_token_labels])
token_label_log_probs = tf.nn.log_softmax(token_label_logits, axis=-1)
token_label_one_hot_labels = tf.one_hot(token_label_ids, depth=num_token_labels, dtype=tf.float32)
token_label_per_example_loss = -tf.reduce_sum(token_label_one_hot_labels * token_label_log_probs, axis=-1)
token_label_loss = tf.reduce_sum(token_label_per_example_loss)
token_label_probabilities = tf.nn.softmax(token_label_logits, axis=-1)
token_label_predictions = tf.argmax(token_label_probabilities, axis=-1)
# return (token_label_loss, token_label_per_example_loss, token_label_logits, token_label_predict)
loss = 0.5 * predicate_loss + token_label_loss