基于python BiLSTM-CRF的命名实体识别 附完整代码

完整代码:https://download.csdn.net/download/qq_38735017/87427497

实验一、中文分词实现

1.1 问题描述


中文分词指的是将一个汉字序列切分成一个一个单独的词。中文分词是文本挖掘的基础,对于输入的一段中文,成功的进行中文分词,可以达到电脑自动识别语句含义的效果。它是信息提取、信息检索、机器翻译、文本分类、自动文摘、语音识别、文本语音转换、自然语言理解等中文信息处理领域的基础。

1.1.1 基础任务

实现基于词典的分词算法

实验一资料包下的“Dictionary_based”文件夹中提供了基础词典和分词算法的大致框架。分词算法的核心部分需要大家完成,实验中提供了若干测试样本用以帮助大家判断算法是否正确实现。

实现基于统计学习的分词算法

实验中给出 Bi-LSTM+CRF 模型的基础实现,相关代码及说明文档位于实验一资料包下的“Bi-LSTM+CRF”文件夹下。请根据给定的实验资料中 README.md 文件配置相应实验环境,说明:(1)提供源码 PyTorch 语言编写(可根据个人掌情况用其他语言编写),默认运行版本是 CPU 版本;(2)如希望运行 NPU 版本,大家可跟任课老师联系,申请华为云资源运行(需提前统计名单:姓名 + 学号 + 个人手机号码 + 邮箱);

1.1.2 选做任务

优化基础任务中实现的分词器,可考虑的优化方案有:

  • 修改网络结构,例如引入 BERT 等预训练语言模型;

  • 与命名实体识别算法相互配合,减少对命名实体的错误分割;

  • 构造合适的词典集(可扩充 + 人工整理);

  • 实现新词发现(登录)功能,识别测试集中的新词(未登录词);

  • 调整、优化模型训练过程中的超参数。

完成优化后对测试文件“Bi-LSTM+CRF/data/test.txt”进行分词,分词结果保存到.txt 文件中 utf-8 编码,词与词之间以空格分隔,每个测试样本占一行。文件“Bi-LSTM+CRF/cws_result.txt”中给出了输出示例。提交分词结果后,依据单词级别的 F1-score 进行评判,决定选做部分的实验分数。

单词级别的 F1-score 的计算方式如下:

  • Gold: 共同 创造 美好 的 新 世纪 —— 二 ○○ 一年 新年 贺词

  • Hypothesis: 共同 创造 美 好 的 新 世纪 —— 二 ○○ 一年 新年 贺词

  • Precision = 9 / 11 = 0.818

  • Recall = 9 / 10 = 0.9

  • F1-score = 2PrecisionRecall/(Precision+Recall)=0.857

1.2 模块设计-基于词典


1.2.1 前向最大匹配

从待分词句子的左边向右边搜索,寻找词的最大匹配。我们需要规定一个词的最大长度,每次扫描的时候寻找当前开始的这个长度的词来和字典中的词匹配,如果没有找到,就缩短长度继续寻找,直到找到字典中的词或者成为单字。

算法流程如下:

  • (1)从待分词子串中从前往后取出 max_len 个字,然后扫描分词字典,测试该 max_len 个字的子串是否在字典中;

  • (2)如果存在,则从待分词子串中删除掉该 max_len 个字的子串,重新按照规则取子串,重复(1);

  • (3)如果不存在于字典中,则减少该子串的最右一个字,之后重复(1)。

1.2.2 后向最大匹配

从待分词句子的右边向左边搜索,寻找词的最大匹配。同样,我们也需要规定一个词的最大长度,每次扫描的时候寻找当前开始的这个长度的词来和字典中的词匹配,如果没有找到,就缩短长度继续寻找,直到找到字典中的词或者成为单字。

算法流程如下:

  • (1)从待分词子串中从后往前取出 max_len 个字,然后扫描分词字典,测试该 max_len 个字的子串是否在字典中;

  • (2)如果存在,则从待分词子串中删除掉该 max_len 个字的子串,重新按照规则取子串,重复(1);

  • (3)如果不存在于字典中,则减少该子串的最左一个字,之后重复(1)。

1.2.3 双向最大匹配

将前向最大匹配算法和后向最大匹配算法进行比较,从而确定正确的分词方法。

算法流程如下:

  • (1)比较前向最大匹配和后向最大匹配结果;

  • (2)如果分词结果相同,返回其中任意一个;

  • (3)如果分词结果不同:

  • 比较两者分词总数量,数量高者罚分;

  • 比较两者分词中单字词数量,单字词多者罚分;

  • 比较两者分词中非字典词数量,非字典词多者罚分;

  • 选择罚分最少的作为最终结果。

1.3 模块设计-基于统计学习


1.3.1 data_u.py

  • getist:单个分词转换成 tag 序列。按行读入数据,并分析各个字对应的标签,然后返回分析结果。

  • handle_data:处理数据,并保存至 save_path。按行读取对应文件中的数据,并做相应的处理,然后把处理的结果保存到 data_save.pkl 中。

1.3.2 dataloader.py

读取通过 data_u.py 处理完后的文件 data_save.pkl,并将其向量化。

1.3.3 infer.py

通过已经训练好的模型,完成对测试文件的分析,并将分词结果保存到 cws_result.txt 文件中。

1.3.4 model.py

  • init_hidden:通过 torch.randn 函数进行初始化操作。

  • _get_lstm_features:获取 LSTM 框架。

  • forward:预测每个标签的 loss 值,以减少无效预测。

  • infer:采用 Bi-LSTM+CRF 的基础结构的分析结果。

1.3.5 run.py

采用小批量梯度下降法,对模型进行训练,使得 loss 值降低。

小批量梯度下降,是对批量梯度下降以及随机梯度下降的一个折中办法。其思想是:每次迭代 使用 batch_size 个样本来对参数进行更新,每次使用一个 batch 可以大大减小收敛所需要的迭代次数,同时可以使收敛到的结果更加接近梯度下降的效果。

1.4 代码实现-基于词典


1.4.1 前向最大匹配

defforward_mm_split(self,fmm_text):"""
正向最大匹配分词算法
:param fmm_text:待分词字符串
:return:分词结果,以list形式存放,每个元素为分出的词
"""# 字词列表,存放分词结果word_list=[]# 用于记录分词的起始位置count=0# 字或词当前的长度word_len=self.max_lenwhileword_len>0andcount<len(fmm_text):word=fmm_text[count:count+word_len]word_len=len(word)if(wordinself.words)or(wordinself.delimiter):word_list.append(word)count=count+word_lenword_len=self.max_lenelse:word_len=word_len-1returnword_list

1.4.2 后向最大匹配

defreverse_mm_split(self,rmm_text):"""
逆向最大匹配分词算法
:param rmm_text:待分词字符串
:return:分词结果,以list形式存放,每个元素为分出的词
"""# 字词列表,存放分词结果word_list=[]# 用于记录分词的末尾位置count=len(rmm_text)# 字或词当前的长度word_len=self.max_lenwhileword_len>0andcount>0:ifcount<=word_len:word=rmm_text[:count]else:word=rmm_text[(count-word_len):count]word_len=len(word)if(wordinself.words)or(wordinself.delimiter):word_list.insert(0,word)count=count-word_lenword_len=self.max_lenelse:word_len=word_len-1returnword_list

1.4.3 双向最大匹配

defbidirectional_mm_split(self,bi_text):"""
双向最大匹配分词算法
:param bi_text:待分词字符串
:return:分词结果,以list形式存放,每个元素为分出的词
"""# 前向最大匹配得到的分词结果forward=self.forward_mm_split(bi_text)# 后向最大匹配得到的分词结果reverse=self.reverse_mm_split(bi_text)# 总词数forward_total_words=len(forward)reverse_total_words=len(reverse)# 单字词个数forward_single_words=0reverse_single_words=0# 非字典词数forward_illegal_words=0reverse_illegal_words=0# 罚分,分值越低,表明结果越好forward_score=0reverse_score=0ifforward==reverse:returnforwardelse:# 统计前向匹配的各个词情况
	forwordinforward:iflen(word)==1:forward_single_words+=1ifwordnotinself.words:forward_illegal_words+=1# 统计后向匹配的各个词情况
	forwordinreverse:iflen(word)==1:reverse_single_words+=1ifwordnotinself.words:reverse_illegal_words+=1# 计算罚分
	ifforward_total_words<reverse_total_words:reverse_score+=1
	else:forward_score+=1
	ifforward_illegal_words<reverse_illegal_words:reverse_score+=1
	else:forward_score+=1
	ifforward_single_words<reverse_single_words:reverse_score+=1
	else:forward_score+=1# 比较罚分情况,罚分最小的选做最终结果
	ifforward_score<reverse_score:
		returnforward
	else:
		returnreverse

1.5 运行结果


利用实验包已给出的代码框架,实现完对应的匹配函数后,运行测试样例。

(1)基于词典的中文分词,得到输出结果如下:

(2)基于统计学习的中文分词,将输出的分词文件进行在线测评,结果如下:

1.6 实验小结


实验中总体的代码框架已给出,需要实现的部分为前向最大匹配、后向最大匹配和双向最大匹配三个核心函数。通过相关资料的参考以及对中文分词的个人理解,能够顺利实现各个匹配模式的实现。

基于统计学习的中文分词代码,由于已经给出了代码框架,所以只是在其基础上做了部分优化,并增加了一部分的训练数据。

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个简单的 bilstm-crf 命名实体识别Python 实现代码: ```python import numpy as np import tensorflow as tf class BiLSTM_CRF: def __init__(self, vocab_size, tag_to_id, embedding_dim, hidden_dim): self.vocab_size = vocab_size self.tag_to_id = tag_to_id self.embedding_dim = embedding_dim self.hidden_dim = hidden_dim self.num_tags = len(tag_to_id) self.word_ids = tf.placeholder(tf.int32, shape=[None, None], name="word_ids") self.sequence_lengths = tf.placeholder(tf.int32, shape=[None], name="sequence_lengths") self.labels = tf.placeholder(tf.int32, shape=[None, None], name="labels") with tf.variable_scope("words"): self.word_embeddings = tf.get_variable("word_embeddings", [vocab_size, embedding_dim]) self.embedded_words = tf.nn.embedding_lookup(self.word_embeddings, self.word_ids) with tf.variable_scope("bi-lstm"): cell_fw = tf.contrib.rnn.LSTMCell(hidden_dim) cell_bw = tf.contrib.rnn.LSTMCell(hidden_dim) (output_fw, output_bw), _ = tf.nn.bidirectional_dynamic_rnn(cell_fw, cell_bw, self.embedded_words, sequence_length=self.sequence_lengths, dtype=tf.float32) self.lstm_output = tf.concat([output_fw, output_bw], axis=-1) with tf.variable_scope("proj"): W = tf.get_variable("W", shape=[2*hidden_dim, self.num_tags], initializer=tf.contrib.layers.xavier_initializer()) b = tf.get_variable("b", shape=[self.num_tags], initializer=tf.zeros_initializer()) self.logits = tf.matmul(tf.reshape(self.lstm_output, [-1, 2*hidden_dim]), W) + b self.logits = tf.reshape(self.logits, [-1, tf.shape(self.word_ids)[1], self.num_tags]) with tf.variable_scope("crf"): self.transition_params = tf.get_variable("transition_params", shape=[self.num_tags, self.num_tags], initializer=tf.contrib.layers.xavier_initializer()) log_likelihood, self.transition_params = tf.contrib.crf.crf_log_likelihood(self.logits, self.labels, self.sequence_lengths, self.transition_params) self.loss = tf.reduce_mean(-log_likelihood) with tf.variable_scope("train_step"): self.train_op = tf.train.AdamOptimizer().minimize(self.loss) def train(self, train_data, dev_data, epochs, batch_size): with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for epoch in range(epochs): print("Epoch {}/{}".format(epoch+1, epochs)) for batch in self.generate_batches(train_data, batch_size): word_ids, labels, sequence_lengths = batch feed_dict = {self.word_ids: word_ids, self.labels: labels, self.sequence_lengths: sequence_lengths} _, loss = sess.run([self.train_op, self.loss], feed_dict=feed_dict) print("Train loss: {}".format(loss)) self.evaluate(sess, dev_data) def evaluate(self, sess, data): correct_preds, total_correct, total_preds = 0., 0., 0. for batch in self.generate_batches(data, 1): word_ids, labels, sequence_lengths = batch feed_dict = {self.word_ids: word_ids, self.labels: labels, self.sequence_lengths: sequence_lengths} logits, transition_params = sess.run([self.logits, self.transition_params], feed_dict=feed_dict) lengths = sequence_lengths.tolist() for logit, sequence_length, label in zip(logits, lengths, labels): logit = logit[:sequence_length] viterbi_seq, _ = tf.contrib.crf.viterbi_decode(logit, transition_params) viterbi_seq = np.array(viterbi_seq) label = label[:sequence_length] correct_preds += np.sum(np.equal(viterbi_seq, label)) total_preds += len(viterbi_seq) total_correct += len(label) p = correct_preds / total_preds if correct_preds > 0 else 0 r = correct_preds / total_correct if correct_preds > 0 else 0 f1 = 2 * p * r / (p + r) if correct_preds > 0 else 0 print("Accuracy: {:.2f}, Precision: {:.2f}, Recall: {:.2f}, F1: {:.2f}".format(100*correct_preds/total_preds, 100*p, 100*r, 100*f1)) def generate_batches(self, data, batch_size): num_batches = (len(data) + batch_size - 1) // batch_size for i in range(num_batches): start_index = i * batch_size end_index = min((i+1) * batch_size, len(data)) batch = data[start_index:end_index] word_ids = [sentence[0] for sentence in batch] labels = [sentence[1] for sentence in batch] sequence_lengths = [len(sentence[0]) for sentence in batch] max_length = max(sequence_lengths) word_ids = [sentence + [0]*(max_length-len(sentence)) for sentence in word_ids] labels = [sentence + [0]*(max_length-len(sentence)) for sentence in labels] yield word_ids, labels, sequence_lengths ``` 这个实现代码使用 TensorFlow 实现了一个双向 LSTM 和 CRF 的模型,用于进行命名实体识别。输入数据是一个句子的单词序列和对应的标签序列,输出是对应的标签序列的预测值。在训练时使用 Adam 优化器进行参数优化,使用 CRF 来计算模型的损失。在评估时使用精度、精确率、召回率和 F1 值来评估模型的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员奇奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值