作者 | 李秋键

责编 | Carol

来源 | AI科技大本营(ID:rgznai100)

语义解析作为自然语言处理的重要方面,其主要作用如下:在词的层次上,语义分析的基本任务是进行词义消歧;在句子层面上,语义角色标注是所关心的问题;在文章层次上,指代消解、篇章语义分析是重点。

而实体识别和关系抽取是构建知识图谱等上层自然语言处理应用的基础。关系抽取可以简单理解为一个分类问题:给定两个实体和两个实体共同出现的句子文本,判别两个实体之间的关系。

使用CNN或者双向RNN加Attention的深度学习方法被认为是现在关系抽取state of art的解决方案。已有的文献和代码,大都是针对英文语料,使用词向量作为输入进行训练。

这里以实践为目的,介绍一个用双向GRU、字与句子的双重Attention模型,以天然适配中文特性的字向量(characterembedding)作为输入,网络爬取数据作为训练语料构建的中文关系抽取模型。代码主要是基于清华的开源项目thunlp/TensorFlow-NRE开发,其中效果如下:

实验前的准备

首先,我们使用的python版本是3.6.5所用到的模块如下:

tensorflow模块:用来创建整个模型训练和保存调用以及网络的搭建框架等等。

numpy模块:用来处理数据矩阵运算。

Sklearn模块:是一些机器学习算法的集成模块。

模型的网络搭建

其中模型的网络图如下:

双向GRU加字级别attention的模型想法来自文章 “Attention-Based Bidirectional Long Short-Term Memory Networks for Relation Classification” [Zhou et al.,2016]。这里将原文的模型结构中的LSTM改为GRU,且对句子中的每一个中文字符输入为character embedding。这样的模型对每一个句子输入做训练,加入字级别的attention。

句子级别attention的想法来自文章 “Neural Relation Extraction with Selective Attention over Instances” [Lin et al.,2016]。原文模型结构图如下,这里将其中对每个句子进行encoding的CNN模块换成上面的双向GRU模型。这样的模型对每一种类别的句子输入做共同训练,加入句子级别的attention。

建立network.py文件,定义词向量大小、步数、类别数等等:

def __init__(self):self.vocab_size = 16691self.num_steps = 70self.num_epochs = 10self.num_classes = 12self.gru_size = 230self.keep_prob = 0.5self.num_layers = 1self.pos_size = 5self.pos_num = 123# the number of entity pairs of each batch during training or testingself.big_num = 50

然后建立GRU网络。按照所给出的网络模型图,定义出网络基本框架作为具体参数的调用:

def __init__(self, is_training, word_embeddings, settings):self.num_steps = num_steps = settings.num_stepsself.vocab_size = vocab_size = settings.vocab_sizeself.num_classes = num_classes = settings.num_classesself.gru_size = gru_size = settings.gru_sizeself.big_num = big_num = settings.big_numself.input_word = tf.placeholder(dtype=tf.int32, shape=[None, num_steps], name='input_word')self.input_pos1 = tf.placeholder(dtype=tf.int32, shape=[None, num_steps], name='input_pos1')self.input_pos2 = tf.placeholder(dtype=tf.int32, shape=[None, num_steps], name='input_pos2')self.input_y = tf.placeholder(dtype=tf.float32, shape=[None, num_classes], name='input_y')self.total_shape = tf.placeholder(dtype=tf.int32, shape=[big_num + 1], name='total_shape')total_num = self.total_shape[-1]word_embedding = tf.get_variable(initializer=word_embeddings, name='word_embedding')pos1_embedding = tf.get_variable('pos1_embedding', [settings.pos_num, settings.pos_size])pos2_embedding = tf.get_variable('pos2_embedding', [settings.pos_num, settings.pos_size])attention_w = tf.get_variable('attention_omega', [gru_size, 1])sen_a = tf.get_variable('attention_A', [gru_size])sen_r = tf.get_variable('query_r', [gru_size, 1])relation_embedding = tf.get_variable('relation_embedding', [self.num_classes, gru_size])sen_d = tf.get_variable('bias_d', [self.num_classes])gru_cell_forward = tf.contrib.rnn.GRUCell(gru_size)gru_cell_backward = tf.contrib.rnn.GRUCell(gru_size)if is_training and settings.keep_prob < 1:gru_cell_forward = tf.contrib.rnn.DropoutWrapper(gru_cell_forward, output_keep_prob=settings.keep_prob)gru_cell_backward = tf.contrib.rnn.DropoutWrapper(gru_cell_backward, output_keep_prob=settings.keep_prob)cell_forward = tf.contrib.rnn.MultiRNNCell([gru_cell_forward] * settings.num_layers)cell_backward = tf.contrib.rnn.MultiRNNCell([gru_cell_backward] * settings.num_layers)sen_repre = []sen_alpha = []sen_s = []sen_out = []self.prob = []self.predictions = []self.loss = []self.accuracy = []self.total_loss = 0.0self._initial_state_forward = cell_forward.zero_state(total_num, tf.float32)self._initial_state_backward = cell_backward.zero_state(total_num, tf.float32)# embedding layerinputs_forward = tf.concat(axis=2, values=[tf.nn.embedding_lookup(word_embedding, self.input_word),tf.nn.embedding_lookup(pos1_embedding, self.input_pos1),tf.nn.embedding_lookup(pos2_embedding, self.input_pos2)])inputs_backward = tf.concat(axis=2,values=[tf.nn.embedding_lookup(word_embedding, tf.reverse(self.input_word, [1])),tf.nn.embedding_lookup(pos1_embedding, tf.reverse(self.input_pos1, [1])),tf.nn.embedding_lookup(pos2_embedding,tf.reverse(self.input_pos2, [1]))])outputs_forward = []state_forward = self._initial_state_forward# Bi-GRU layerwith tf.variable_scope('GRU_FORWARD') as scope:for step in range(num_steps):if step > 0:scope.reuse_variables()(cell_output_forward, state_forward) = cell_forward(inputs_forward[:, step, :], state_forward)outputs_forward.append(cell_output_forward)outputs_backward = []state_backward = self._initial_state_backwardwith tf.variable_scope('GRU_BACKWARD') as scope:for step in range(num_steps):if step > 0:scope.reuse_variables()(cell_output_backward, state_backward) = cell_backward(inputs_backward[:, step, :], state_backward)outputs_backward.append(cell_output_backward)output_forward = tf.reshape(tf.concat(axis=1, values=outputs_forward), [total_num, num_steps, gru_size])output_backward = tf.reverse(tf.reshape(tf.concat(axis=1, values=outputs_backward), [total_num, num_steps, gru_size]), [1])# word-level attention layeroutput_h = tf.add(output_forward, output_backward)attention_r = tf.reshape(tf.matmul(tf.reshape(tf.nn.softmax(tf.reshape(tf.matmul(tf.reshape(tf.tanh(output_h), [total_num * num_steps, gru_size]), attention_w),[total_num, num_steps])), [total_num, 1, num_steps]), output_h), [total_num, gru_size])

模型的训练和使用

其中用来训练的语料获取,由于中文关系抽取的公开语料比较少。我们从distant supervision的方法中获取灵感,希望可以首先找到具有确定关系的实体对,然后再去获取该实体对共同出现的语句作为正样本。负样本则从实体库中随机产生没有关系的实体对,再去获取这样实体对共同出现的语句。

对于具有确定关系的实体对,我们从复旦知识工厂得到,感谢他们提供的免费API!一个小问题是,相同的关系label在复旦知识工厂中可能对应着不同的标注,比如“夫妻”,抓取到的数据里有的是“丈夫”,有的是“妻子”,有的是“伉俪”等等,需要手动对齐。

(1) 模型的训练:

建立train_GRU文件,通过训练已经经过处理后得到的npy文件进行训练。

其中训练的数据如下:

代码如下:

def main(_):# the path to save modelssave_path = './model/'print('reading wordembedding')wordembedding = np.load('./data/vec.npy')print('reading training data')train_y = np.load('./data/train_y.npy')train_word = np.load('./data/train_word.npy')train_pos1 = np.load('./data/train_pos1.npy')train_pos2 = np.load('./data/train_pos2.npy')settings = network.Settings()settings.vocab_size = len(wordembedding)settings.num_classes = len(train_y[0])big_num = settings.big_numwith tf.Graph().as_default():sess = tf.Session()with sess.as_default():initializer = tf.contrib.layers.xavier_initializer()with tf.variable_scope("model", reuse=None, initializer=initializer):m = network.GRU(is_training=True, word_embeddings=wordembedding, settings=settings)global_step = tf.Variable(0, name="global_step", trainable=False)optimizer = tf.train.AdamOptimizer(0.0005)train_op = optimizer.minimize(m.final_loss, global_step=global_step)sess.run(tf.global_variables_initializer())saver = tf.train.Saver(max_to_keep=None)merged_summary = tf.summary.merge_all()summary_writer = tf.summary.FileWriter(FLAGS.summary_dir + '/train_loss', sess.graph)def train_step(word_batch, pos1_batch, pos2_batch, y_batch, big_num):feed_dict = {}total_shape = []total_num = 0total_word = []total_pos1 = []total_pos2 = []for i in range(len(word_batch)):total_shape.append(total_num)total_num += len(word_batch[i])for word in word_batch[i]:total_word.append(word)for pos1 in pos1_batch[i]:total_pos1.append(pos1)for pos2 in pos2_batch[i]:total_pos2.append(pos2)total_shape.append(total_num)total_shape = np.array(total_shape)total_word = np.array(total_word)total_pos1 = np.array(total_pos1)total_pos2 = np.array(total_pos2)feed_dict[m.total_shape] = total_shapefeed_dict[m.input_word] = total_wordfeed_dict[m.input_pos1] = total_pos1feed_dict[m.input_pos2] = total_pos2feed_dict[m.input_y] = y_batchtemp, step, loss, accuracy, summary, l2_loss, final_loss = sess.run([train_op, global_step, m.total_loss, m.accuracy, merged_summary, m.l2_loss, m.final_loss],feed_dict)time_str = datetime.datetime.now().isoformat()accuracy = np.reshape(np.array(accuracy), (big_num))acc = np.mean(accuracy)summary_writer.add_summary(summary, step)if step % 50 == 0:tempstr = "{}: step {}, softmax_loss {:g}, acc {:g}".format(time_str, step, loss, acc)print(tempstr)for one_epoch in range(settings.num_epochs):temp_order = list(range(len(train_word)))np.random.shuffle(temp_order)for i in range(int(len(temp_order) / float(settings.big_num))):temp_word = []temp_pos1 = []temp_pos2 = []temp_y = []temp_input = temp_order[i * settings.big_num:(i + 1) * settings.big_num]for k in temp_input:temp_word.append(train_word[k])temp_pos1.append(train_pos1[k])temp_pos2.append(train_pos2[k])temp_y.append(train_y[k])num = 0for single_word in temp_word:num += len(single_word)if num > 1500:print('out of range')continuetemp_word = np.array(temp_word)temp_pos1 = np.array(temp_pos1)temp_pos2 = np.array(temp_pos2)temp_y = np.array(temp_y)train_step(temp_word, temp_pos1, temp_pos2, temp_y, settings.big_num)current_step = tf.train.global_step(sess, global_step)if current_step > 8000 and current_step % 100 == 0:print('saving model')path = saver.save(sess, save_path + 'ATT_GRU_model', global_step=current_step)tempstr = 'have saved model to ' + pathprint(tempstr)

训练过程:

(2) 模型的测试:

其中得到训练后的模型如下:

while True:#try:#BUG: Encoding error if user input directly from command line.line = input('请输入中文句子,格式为 "name1 name2 sentence":')#Read file from test file'''infile = open('test.txt', encoding='utf-8')line = ''for orgline in infile:line = orgline.strip()breakinfile.close()'''en1, en2, sentence = line.strip().split()print("实体1: " + en1)print("实体2: " + en2)print(sentence)relation = 0en1pos = sentence.find(en1)if en1pos == -1:en1pos = 0en2pos = sentence.find(en2)if en2pos == -1:en2post = 0output = []# length of sentence is 70fixlen = 70# max length of position embedding is 60 (-60~+60)maxlen = 60#Encoding test xfor i in range(fixlen):word = word2id['BLANK']rel_e1 = pos_embed(i - en1pos)rel_e2 = pos_embed(i - en2pos)output.append([word, rel_e1, rel_e2])for i in range(min(fixlen, len(sentence))):word = 0if sentence[i] not in word2id:#print(sentence[i])#print('==')word = word2id['UNK']#print(word)else:#print(sentence[i])#print('||')word = word2id[sentence[i]]#print(word)output[i][0] = wordtest_x = []test_x.append([output])#Encoding test ylabel = [0 for i in range(len(relation2id))]label[0] = 1test_y = []test_y.append(label)test_x = np.array(test_x)test_y = np.array(test_y)test_word = []test_pos1 = []test_pos2 = []for i in range(len(test_x)):word = []pos1 = []pos2 = []for j in test_x[i]:temp_word = []temp_pos1 = []temp_pos2 = []for k in j:temp_word.append(k[0])temp_pos1.append(k[1])temp_pos2.append(k[2])word.append(temp_word)pos1.append(temp_pos1)pos2.append(temp_pos2)test_word.append(word)test_pos1.append(pos1)test_pos2.append(pos2)test_word = np.array(test_word)test_pos1 = np.array(test_pos1)test_pos2 = np.array(test_pos2)prob, accuracy = test_step(test_word, test_pos1, test_pos2, test_y)prob = np.reshape(np.array(prob), (1, test_settings.num_classes))[0]print("关系是:")#print(prob)top3_id = prob.argsort()[-3:][::-1]for n, rel_id in enumerate(top3_id):print("No." + str(n+1) + ": " + id2relation[rel_id] + ", Probability is " + str(prob[rel_id]))

完整代码:

链接:https://pan.baidu.com/s/1aY2WOAw9lgG_1I2rk_EPKw

提取码:noyv

作者介绍:

李秋键,CSDN 博客专家,CSDN达人课作者。硕士在读于中国矿业大学,开发有taptap安卓武侠游戏一部,vip视频解析,文意转换工具,写作机器人等项目,发表论文若干,多次高数竞赛获奖等等。

更多精彩推荐
☞无代码火了,短板有哪些?
☞写不出满分作文怎么办,GPT-3 来帮你
☞互联网不相信学渣
☞收藏!美国博士明确给出Python的高效学习技巧
☞垃圾回收策略和算法,看这篇就够了
☞2020 以太坊技术及应用大会·中国圆满落幕,大咖们的演讲精华都在这里了!
点分享点点赞点在看

用Bi-GRU语义解析,实现中文人物关系分析 | 附完整代码相关推荐

  1. OpenCV语义细分深度学习网络的实例(附完整代码)

    OpenCV语义细分深度学习网络的实例 OpenCV语义细分深度学习网络的实例 OpenCV语义细分深度学习网络的实例 #include <fstream> #include <ss ...

  2. Pytorch TextCNN实现中文文本分类(附完整训练代码)

    Pytorch TextCNN实现中文文本分类(附完整训练代码) 目录 Pytorch TextCNN实现中文文本分类(附完整训练代码) 一.项目介绍 二.中文文本数据集 (1)THUCNews文本数 ...

  3. 华中科技大学--数据结构课程设计 ---红楼梦人物关系分析

    数据结构课程设计–<<基于社会网络分析技术的<红楼梦>人物关系分析>> 开发时间:2016-2017 第二学期华中科技大学 选题 – <题目二 基于社会网络分 ...

  4. Pytorch LSTM实现中文单词预测(附完整训练代码)

    Pytorch LSTM实现中文单词预测(附完整训练代码) 目录 Pytorch LSTM实现中文单词预测(词语预测 附完整训练代码) 1.项目介绍 2.中文单词预测方法(N-Gram 模型) 3.训 ...

  5. 超详细解读:神经语义解析的结构化表示学习 | 附代码分析

    在碎片化阅读充斥眼球的时代,越来越少的人会去关注每篇论文背后的探索和思考. 在这个栏目里,你会快速 get 每篇精选论文的亮点和痛点,时刻紧跟 AI 前沿成果. 点击本文底部的「阅读原文」即刻加入社区 ...

  6. 【OCR炼丹】解析CASIA数据集OLHWDB部分Python版完整代码

    上一篇记录了HIT-OR3C联机数据的解析代码,由于OLHWDB不同于HIT-OR3C,其在采集联机手写体数据时就没有按照固定size去采集(HIT-OR3C保存的坐标是转换后相对128*128大小画 ...

  7. 基于词典方法和机器学习方法的中文情感倾向分析(毕业设计:附完整代码)

    项目概述: 使用机器学习 和 情感词典 这两种方法 分别对中文新闻类文本进行情感极性分析 输入一段新闻文本能够得到文本的情感极性 1.3 目标人群 需要对已有中文文本数据进行情感分析的企业及用户 AP ...

  8. Python实现小说人物关系输出(完整+修正)

    这篇博客提供能够输出小说人物关系的完整python代码. 代码的原作者是Qingyu Mao,十分感谢!! 以下是Mao的github链接和博客教程链接: @source: https://githu ...

  9. OpenCV语义分割semantic segmentation的实例(附完整代码)

    OpenCV语义分割semantic segmentation的实例 OpenCV语义分割semantic segmentation的实例 OpenCV语义分割semantic segmentatio ...

最新文章

  1. Weblogic基本概念整理
  2. MS SQL专用管理员连接DAC
  3. 专访元知科技崔兴龙:这位十几年前就在做AI研究的“谷歌老兵”,现在已经把AI用在了12个赛道上...
  4. 微软职位内部推荐-SW Engineer II for Azure Network
  5. 喜报!「神策 SA 分析师认证」第三期认证名单正式公布
  6. SAP成都研究院马洪波:提升学习力,增强竞争力,收获一生乐趣
  7. 麦块未发现java怎么解决_教大家麦块我的世界盒子如何打开游戏
  8. 机器学习实战 | 数据探索
  9. 三种model 在lfw 上的精度
  10. [ios]object-c math.h里的数学计算公式介绍
  11. 3.14 使用画笔工具给头发着色 [Ps教程]
  12. 无人驾驶车辆轨迹跟踪控制文献分享(1)
  13. Gitee 多人协作开发教程
  14. 敏捷 绩效_管理敏捷团队绩效的四个技巧
  15. mysql查询练习tblstudent_通过Navicat进行MySQL的基础查询练习
  16. 传统服务器销售转型,传统IDC企业云化转型有了新思路!
  17. PTA ## 统计字符
  18. windows手机改为Android,如何在平板电脑上把windows系统变成安卓系统
  19. No qualifying bean of type [com.*.*.dao.InfoDao] found for :错误!
  20. 区块链发展的重要里程碑

热门文章

  1. 统计学习方法十:隐马尔科夫模型
  2. Struts2漏洞分析,漏洞波及全系版本
  3. 初学java之Map接口
  4. 大智慧找不到服务器,【图】大智慧官方居然没有全推服务器请老师解决_股票软件,股票指标,炒股,股票论坛,选股公式_股票软件技术交流论坛_理想论坛 - 股票论坛...
  5. leetcode python3 简单题14. Longest Common Prefix
  6. qtxlsx编译报错_Qt5安装Qxlsx模块,以及Qxlsx模块的简单使用,附资源例程下载
  7. python调用.so动态链接库,运行时报错 liblapack.so.3: undefined symbol: icamin_k
  8. 《推荐系统实战(二)》音乐推荐系统(数据清洗、召回、排序)
  9. 【图像处理】MATLAB:形态学
  10. 剑指Offer之平衡二叉树