模型训练

2021/3/10:使用训练好的Bert/Albert-CRF模型,同时,在此基础上,加一层BiLSTM网络,得修改后的Albert-BiLSTM-CRF模型(见下一篇文章),开始训练。

修改思路:以已有的Albert+CRF模型代码为基础,参考网上的Albert+BiLSTM+CRF模型,稍加修改即可。值得注意的,无非是“三种模型”之间的数据传递类型,比如,将Albert模型训练得到的embedding,传入BiLSTM(参考:ALBERT+BiLSTM+CRF实现序列标注 - 光彩照人 - 博客园)。

调试过程:其间,多次用到命令行,安装需要的库、工具包,按部就班去做即可。

import numpy as np
from bert4keras.backend import keras, K
from bert4keras.models import build_transformer_model
from bert4keras.tokenizers import Tokenizer
from bert4keras.optimizers import Adam
from bert4keras.snippets import sequence_padding, DataGenerator
from bert4keras.snippets import open, ViterbiDecoder
from bert4keras.layers import ConditionalRandomField
from keras.layers import Dense
from keras.models import Model
from tqdm import tqdm
from tensorflow import ConfigProto
from tensorflow import InteractiveSession
from numpy import array
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense
from keras.layers import Bidirectional
from keras.layers import Dropout
from keras.layers import TimeDistributed
from keras_contrib.layers import CRF
from keras_contrib.losses import crf_loss
from keras_contrib.metrics import crf_accuracy, crf_viterbi_accuracyconfig = ConfigProto()
# config.gpu_options.per_process_gpu_memory_fraction = 0.2
config.gpu_options.allow_growth = True
session = InteractiveSession(config=config)maxlen = 256
epochs = 1#10
batch_size = 16
bert_layers = 12
learing_rate = 1e-5  # bert_layers越小,学习率应该要越大
crf_lr_multiplier = 10  # 必要时扩大CRF层的学习率#1000# # bert配置
# config_path = './bert_model/chinese_L-12_H-768_A-12/bert_config.json'
# checkpoint_path = './bert_model/chinese_L-12_H-768_A-12/bert_model.ckpt'
# dict_path = './bert_model/chinese_L-12_H-768_A-12/vocab.txt'#albert配置
config_path = './bert_model/albert_large/albert_config.json'
checkpoint_path = './bert_model/albert_large/model.ckpt-best'
dict_path = './bert_model/albert_large/vocab_chinese.txt'def load_data(filename):D = []with open(filename, encoding='utf-8') as f:f = f.read()for l in f.split('\n\n'):if not l:continued, last_flag = [], ''for c in l.split('\n'):char, this_flag = c.split(' ')if this_flag == 'O' and last_flag == 'O':d[-1][0] += charelif this_flag == 'O' and last_flag != 'O':d.append([char, 'O'])elif this_flag[:1] == 'B':d.append([char, this_flag[2:]])else:d[-1][0] += charlast_flag = this_flagD.append(d)return D# 标注数据
train_data = load_data('./data/example.train')
valid_data = load_data('./data/example.dev')
test_data = load_data('./data/example.test')# 建立分词器
tokenizer = Tokenizer(dict_path, do_lower_case=True)# 类别映射
labels = ['PER', 'LOC', 'ORG']
id2label = dict(enumerate(labels))
label2id = {j: i for i, j in id2label.items()}
num_labels = len(labels) * 2 + 1class data_generator(DataGenerator):"""数据生成器"""def __iter__(self, random=False):batch_token_ids, batch_segment_ids, batch_labels = [], [], []for is_end, item in self.sample(random):token_ids, labels = [tokenizer._token_start_id], [0]for w, l in item:w_token_ids = tokenizer.encode(w)[0][1:-1]if len(token_ids) + len(w_token_ids) < maxlen:token_ids += w_token_idsif l == 'O':labels += [0] * len(w_token_ids)else:B = label2id[l] * 2 + 1I = label2id[l] * 2 + 2labels += ([B] + [I] * (len(w_token_ids) - 1))else:breaktoken_ids += [tokenizer._token_end_id]labels += [0]segment_ids = [0] * len(token_ids)batch_token_ids.append(token_ids)batch_segment_ids.append(segment_ids)batch_labels.append(labels)if len(batch_token_ids) == self.batch_size or is_end:batch_token_ids = sequence_padding(batch_token_ids)batch_segment_ids = sequence_padding(batch_segment_ids)batch_labels = sequence_padding(batch_labels)yield [batch_token_ids, batch_segment_ids], batch_labelsbatch_token_ids, batch_segment_ids, batch_labels = [], [], []"""
后面的代码使用的是bert类型的模型,如果你用的是albert,那么前几行请改为:
"""
model = build_transformer_model(config_path,checkpoint_path,model='albert',
)
output_layer = 'Transformer-FeedForward-Norm'
albert_output = model.get_layer(output_layer).get_output_at(bert_layers - 1)lstm = Bidirectional(LSTM(units=128, return_sequences=True), name="bi_lstm")(albert_output)
drop = Dropout(0.1, name="dropout")(lstm)
dense = TimeDistributed(Dense(num_labels, activation="softmax"), name="time_distributed")(drop)output = Dense(num_labels)(dense)
CRF = ConditionalRandomField(lr_multiplier=crf_lr_multiplier)
output = CRF(output)model = Model(model.input, output)
model.summary()model.compile(loss=CRF.sparse_loss,optimizer=Adam(learing_rate),metrics=[CRF.sparse_accuracy]
)class NamedEntityRecognizer(ViterbiDecoder):"""命名实体识别器"""def recognize(self,text):tokens = tokenizer.tokenize(text)while len(tokens) > 512:tokens.pop(-2)mapping = tokenizer.rematch(text, tokens)token_ids = tokenizer.tokens_to_ids(tokens)segment_ids = [0] * len(token_ids)nodes = model.predict([[token_ids], [segment_ids]])[0]labels = self.decode(nodes)entities, starting = [], Falsefor i, label in enumerate(labels):if label > 0:if label % 2 == 1:starting = Trueentities.append([[i], id2label[(label - 1) // 2]])elif starting:entities[-1][0].append(i)else:starting = Falseelse:starting = Falsereturn [(text[mapping[w[0]][0]:mapping[w[-1]][-1] + 1], l)for w, l in entities]def evaluate(data):"""评测函数"""X, Y, Z = 1e-10, 1e-10, 1e-10for d in tqdm(data):text = ''.join([i[0] for i in d])R = set(NER.recognize(text))T = set([tuple(i) for i in d if i[1] != 'O'])X += len(R & T)Y += len(R)Z += len(T)f1, precision, recall = 2 * X / (Y + Z), X / Y, X / Zreturn f1, precision, recallclass Evaluate(keras.callbacks.Callback):def __init__(self):self.best_val_f1 = 0def on_epoch_end(self, epoch, logs=None):trans = K.eval(CRF.trans)NER.trans = transprint(NER.trans)f1, precision, recall = evaluate(valid_data)# 保存最优if f1 >= self.best_val_f1:self.best_val_f1 = f1model.save_weights('best_model.weights')print('valid:  f1: %.5f, precision: %.5f, recall: %.5f, best f1: %.5f\n' %(f1, precision, recall, self.best_val_f1))f1, precision, recall = evaluate(test_data)print('test:  f1: %.5f, precision: %.5f, recall: %.5f\n' %(f1, precision, recall))if __name__ == '__main__':evaluator = Evaluate()train_generator = data_generator(train_data, batch_size)model.fit_generator(train_generator.forfit(),steps_per_epoch=len(train_generator),epochs=epochs,callbacks=[evaluator])else:model.load_weights('best_model.weights')

模型评估

2021/3/11:今早,查看Albert+BiLSTM+CRF模型运行结果,发现其精度很低,仅为0.8左右。然而,使用同样的数据,Albert+CRF模型精度在0.95以上。→→→思考其中原因,尝试调整代码:①尝试调整LSTM相关参数(dropout),甚至去除dropout,皆无改善。②尝试去除dropout与。dropout的作用?防止模型过拟合,但我认为,其使用需要看场景,参考:为什么模型加入dropout层后变得更差了?最后dense层的作用?我认为,可以将其理解为分类输出层,因此模型中有CRF用于输出转换,故可能不需要dens层。参考:LSTM模型后增加Dense(全连接)层的目的是什么? →→→去除下面代码后两行后,Albert+BiLSTM+CRF模型精度在0.95以上。至于模型原理,待深究。

lstm = Bidirectional(LSTM(units=128, return_sequences=True), name="bi_lstm")(albert_output)
#drop = Dropout(0.2, name="dropout")(lstm)
#dense = TimeDistributed(Dense(num_labels, activation="softmax"), name="time_distributed")(drop)

读写文件

2021/3/12:上午,一直在尝试Python读写文件,如此简单之事,竟耗费我两小时之久。原因:总是报错'open' object has no attribute 'readlines'。解决思路:新建一个py文件,在里面进行读写操作,可行。然而,同样的语句,在Albert+BiLSTM+CRF模型py文件中,不可行。→这说明,语句本身没错,可能是Albert+BiLSTM+CRF模型py文件中变量/函数等名称与读写语句冲突。→的确如此,Albert+BiLSTM+CRF模型py文件的开头,有“from bert4keras.snippets import open, ViterbiDecoder”,此"open"非彼"open"。

model.load_weights('best_model.weights')
NER = NamedEntityRecognizer(trans=K.eval(CRF.trans), starts=[0], ends=[0])r = open("D:\Asian elephant\gao A_geography_NER\A_geography_NER\data\\result.txt", 'w')
with open("D:\Asian elephant\gao A_geography_NER\A_geography_NER\data\\t.txt",'r',encoding='utf-8') as tt:content = tt.readlines()
for line in content:ner=NER.recognize(line)print(ner,file=r)

模型训练

2021/3/14:训练模型(迭代3次,学习率设为1000,其他参数设置如下)。

训练数据:现有标注数据集+自己标注的数据;测试数据:自己标注的数据;验证数据:自己标注的数据。

耗时:纯CPU,迭代一次大约需要7小时。

结果LOW:epoch 1 →1304/1304:loss: 3.9929 - sparse_accuracy: 0.9648,test:  f1: 0.13333, precision: 0.41176, recall: 0.07955,valid:  f1: 0.15493, precision: 0.64706, recall: 0.08800, best f1: 0.15493

epoch 2→1304/1304:loss: 0.5454 - sparse_accuracy: 0.9849,test:  f1: 0.25455, precision: 0.63636, recall: 0.15909,valid:  f1: 0.18919, precision: 0.60870, recall: 0.11200, best f1: 0.18919

epoch 3→test与valid的precision达0.7以上

maxlen = 256 #文本保留的最大长度
epochs = 3 #迭代次数
batch_size = 16 #训练时,每次传入模型的特征数量
bert_layers = 12
learing_rate = 1e-5  # bert_layers越小,学习率应该要越大
crf_lr_multiplier = 1000  # 必要时扩大CRF层的学习率#1000

各种bug及其解决

ValueError: substring not found

bug之“substring not found”

2021/3/5:问题:迭代三次的模型已训练完毕,但将所有数据放入模型时,得到上述bug。解决:解决bug并不难,甚至无需了解其原理,只需进行比对——多试几次,发现数据中报错行的规律。本以为是标点符号的问题,但排查过后,了解到,是字母的问题。

ValueError: not enough values to unpack (expected 2got 1)

2021/3/13:问题:其他设置一致,仅是使用的数据不同,精度结果却大相径庭。使用Albert+BiLSTM+CRF模型代码包自带的训练数据用于训练模型,使用自己标注的少量数据用于测试与验证,得到较好的结果;但在训练数据中,加上自己标注的少量数据,一起用于训练,却得到很差的结果。解决:仍是,找不同。我标注的数据与原数据有何不同?答:是否有'\n',这“不起眼”的'\n',却有很重要的作用(如下)。

2021/3/15:新增一些自己标注的数据,而后,程序又报错。错误原因:类似于2021/3/13那次报错原因,仍是数据里的格式问题(字符/空格/换行符多余或缺失),但本次错误更为细致——文件末尾两个换行符的缺失,而这两个换行符十分重要(见代码中的for l in f.split('\n\n'): #查找双换行符)。解决方案:仍是对比正确数据VS我的报错数据,①以为是数据中空格的问题(上次是此原因报错),就一直纠结空格;②对比的所谓“正确数据”并非原始的、真正正确的数据,导致迟迟未能解决。

def load_data(filename): #加载标注数据:训练集、测试集与验证集D = [] with open(filename, encoding='utf-8') as f: #打开并读取文件内容f = f.read() #读取文件全部内容for l in f.split('\n\n'): #查找双换行符if not l: #若无双换行符continue #跳出本次循环,可执行下一次 (而break是跳出整个循环)d, last_flag = [], '' for c in l.split('\n'): #查找换行符char, this_flag = c.split(' ') if this_flag == 'O' and last_flag == 'O': d[-1][0] += char elif this_flag == 'O' and last_flag != 'O':d.append([char, 'O'])elif this_flag[:1] == 'B': #从索引0开始取,到1,但不包括1(即标注首字母为B)d.append([char, this_flag[2:]]) #从索引2开始取,char竖着到最后,如“梁子老寨”每个字的标注都非O,输出('梁子老寨', 'LOC')。else:d[-1][0] += char #若无换行符,last_flag = this_flagD.append(d)return D
#结果格式:[('良子', 'LOC'), ('勐乃通达', 'LOC'), ('梁子老寨', 'LOC'), ('黑山', 'LOC'), ('黑山', 'LOC'), ('勐乃通达', 'LOC')]

【NLP_命名实体识别】Albert+BiLSTM+CRF模型训练、评估与使用相关推荐

  1. 零基础入门--中文命名实体识别(BiLSTM+CRF模型,含代码)

    https://github.com/mali19064/LSTM-CRF-pytorch-faster 中文分词 说到命名实体抽取,先要了解一下基于字标注的中文分词. 比如一句话 "我爱北 ...

  2. 信息抽取实战:命名实体识别NER【ALBERT+Bi-LSTM模型 vs. ALBERT+Bi-LSTM+CRF模型】(附代码)

    实战:命名实体识别NER 目录 实战:命名实体识别NER 一.命名实体识别(NER) 二.BERT的应用 NLP基本任务 查找相似词语 提取文本中的实体 问答中的实体对齐 三.ALBERT ALBER ...

  3. 代码实现中文命名实体识别(包括多种模型:HMM,CRF,BiLSTM,BiLSTM+CRF)

    作者 | 忆臻 地址 | https://zhuanlan.zhihu.com/p/100969186 专栏 | 机器学习算法与自然语言处理 代码实现中文命名实体识别(包括多种模型:HMM,CRF,B ...

  4. NLP(二十五)实现ALBERT+Bi-LSTM+CRF模型

      在文章NLP(二十四)利用ALBERT实现命名实体识别中,笔者介绍了ALBERT+Bi-LSTM模型在命名实体识别方面的应用.   在本文中,笔者将介绍如何实现ALBERT+Bi-LSTM+CRF ...

  5. 命名实体识别NER遗留问题----模型构建

    深度学习模型预测实质:训练保存的模型里面参数 整个只有一套参数 不仅保存了训练数据全部的正确信息,而且同字多义的情况下通过其同行的词来判断,虽然参数都是一套但是因为输入的值不同导致计算的结果不同 导致 ...

  6. 知识图谱 基于CRF的命名实体识别模型

    基于CRF的命名实体识别模型 条件随机场 CRF ​ 条件随机场 CRF 是在已知一组输入随机变量条件的情况下,输出另一组随机变量的条件概率分布模型:其前提是假设输出随机变量构成马尔可夫随机场:条件随 ...

  7. NLP15:使用BiLSTM、BiLSTM-CRF、BiLSTM-Attention、Bert-BiLSTM-CRF进行命名实体识别

    公众号:数据挖掘与机器学习笔记 1.基于BiLSTM的命名实体识别 Embedding+BiLSTM+BiLSTM+Dense from tensorflow.keras.layers import ...

  8. 中文电子病历命名实体识别

    ★★★ 本文源自AlStudio社区精品项目,[点击此处]查看更多精品内容 >>> 1 项目介绍 在本项目中将会介绍如何使用BERT+BiLSTM+CRF模型,实现对中文电子病历中病 ...

  9. 基于预训练模型的军事领域命名实体识别研究

    摘要 [目的]为了解决开源非结构化军事领域数据的命名实体识别问题.[方法]本文提出基于预训练模型(Bidirectional Encoder Representations from Transfor ...

  10. 【实体识别】深入浅出讲解命名实体识别(介绍、常用算法)

    本文收录于<深入浅出讲解自然语言处理>专栏,此专栏聚焦于自然语言处理领域的各大经典算法,将持续更新,欢迎大家订阅! 个人主页:有梦想的程序星空 个人介绍:小编是人工智能领域硕士,全栈工程师 ...

最新文章

  1. 大龄屌丝自学笔记--Java零基础到菜鸟--001
  2. S5PV210开发 -- 串口驱动开发
  3. 真的!最难啃的《深度学习》圣经花书,居然新出版了视频课!
  4. 在JavaFX程序中嵌入Swing内容
  5. [密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第19篇]Shamir密钥交换场景
  6. BP算法是从天上掉下来的吗?
  7. python读取、写入txt文本内容
  8. Windows 系统(包含Server) 官方镜像下载--阿里云盘
  9. Android:如何查看Android源码
  10. 学计算机电脑硬盘容量多大好,电脑系统盘应该分多大空间最合适,赶紧学习一下...
  11. 网络API接口的使用
  12. 关于第一次深度学习项目的总结
  13. HTML超链接实现页面内跳转
  14. HTPPS请求 证书 解决方案
  15. 抖音直播带货数据统计,抖音直播带货复盘必看的4个数据
  16. 字符串类型的算法面试
  17. 鸿蒙系统安装第三方应用是什么,网友表示:鸿蒙最新系统可以通过连接U盘安装第三方软件了...
  18. 第二课等效变化与基尔霍夫定律
  19. 速卖通小伙伴们,关于欧盟VAT税改最全面解读,杭州海赢科技分享!
  20. Django 搭建博客网站-task01:基础知识

热门文章

  1. 基于STM32的双向DC-DC变换器(论文+原理图+PCB+源码)
  2. 高频量化交之李:在华尔街狼舞岁
  3. 起名源码PHP(宝宝取名源码)
  4. SIFT算法原理介绍
  5. chrome 内核CEF 编译和qt 封装(下)
  6. linux装流量宝,流量宝下载_流量宝官方APP手机最新版本下载安装 - 风云下载
  7. 解读阿里巴巴Java手册:为什么不建议使用Executors创建线程池?
  8. 【ArcGIS教程03】基础知识(建议收藏)
  9. 手机App常见功能测试点
  10. 我的Android进阶之旅------经典的大牛博客推荐(排名不分先后)!!