知识图谱(BILSTM+CRF项目完整实现、训练结果优化方向(面试))【第八章】

张开发
2026/4/21 6:14:24 15 分钟阅读

分享文章

知识图谱(BILSTM+CRF项目完整实现、训练结果优化方向(面试))【第八章】
一、训练、评估模型训练函数基本步骤1.构建数据迭代器Dataloader(包括数据处理与构建数据源Dataset)2.实例化模型3.实例化损失函数对象4.实例化优化器对象5.定义打印日志参数6.开始训练6.1 实现外层大循环epoch6.2 将模型设置为训练模式6.3 内部遍历数据迭代器dataloader1将数据送入模型得到输出结果2计算损失3梯度清零: optimizer.zero_grad()4反向传播(计算梯度): loss.backward()5梯度更新(参数更新): optimizer.step()6打印内部训练日志6.4 使用验证集进行模型评估【将模型设置为评估模式】6.5 保存模型: torch.save(model.state_dict(), model_path)6.6 打印外部训练日志验证函数基本步骤1.定义打印日志参数2.将模型设置为评估模式3.内部遍历数据迭代器dataloader3.1 将数据送入模型得到输出结果3.2 计算损失3.3 处理结果3.4 统计批次内指标4.统计整体指标train.pyimport time import torch import torch.nn as nn from sklearn.metrics import precision_score, recall_score, f1_score, classification_report from tqdm import tqdm from P03_NER.LSTM_CRF.config import Config from P03_NER.LSTM_CRF.model.BiLSTM import NERLSTM from P03_NER.LSTM_CRF.model.BiLSTM_CRF import NERLSTM_CRF from P03_NER.LSTM_CRF.utils.data_loader import get_data, word2id conf Config() def model2dev(val_dataloader, model, criterionNone): 使用验证集评估模型的效果 :param val_dataloader: 验证集 :param model: 需要评估的模型对象 :param criterion: 损失函数对象【只有BiLSTM模型会用到】 :return: 评估指标 # 1.定义打印日志参数 avg_loss 0 preds [] # 用来保存非padding位置的预测标签 golds [] # 用来保存非padding位置的真实标签 # 2.将模型设置为评估模式 model.eval() # 3.内部遍历数据迭代器dataloader for index, (input_ids, labels, attention_mask) in enumerate(tqdm(val_dataloader)): # 3.1 将数据送入模型得到输出结果 # 将数据放到 GPU input_ids input_ids.to(conf.device) labels labels.to(conf.device) attention_mask attention_mask.to(conf.device) # 对模型进行判断 if conf.model BiLSTM: # 将数据送入模型得到输出结果 output model(input_ids, attention_mask) # print(foutput--{output.shape}) # 3.2 计算损失 reshape_output output.view(-1, len(conf.tag2id)) reshape_labels labels.view(-1) loss criterion(reshape_output, reshape_labels) # 对损失进行累加操作 avg_loss loss.item() # 3.3 处理结果 predict output.argmax(dim-1).tolist() # print(fpredict--{predict}) else: # 将数据送入模型得到输出结果 predict model(input_ids, attention_mask) # print(fpredict--{predict}) # 3.2 计算损失 loss model.log_likelihood(input_ids, labels, attention_mask).mean() # 对损失进行累加操作 avg_loss loss # 3.3 处理结果略 # 目的基于真实样本长度去获取非padding位置的预测标签和真实标签 # 1)先获取真实长度 real_len (labels ! 11).sum(-1).tolist() # print(freal_len--{real_len}) # 2)基于真实长度去获取非padding位置的预测标签 for index, label in enumerate(predict): real_len_label label[0:real_len[index]] # print(freal_len_label--{real_len_label}) preds.extend(real_len_label) # 注意这里需要使用extend()方法将每个标签添加到列表中 # 3)基于真实长度去获取非padding位置的真实标签 for index, label in enumerate(labels.tolist()): golds.extend(label[0:real_len[index]]) # print(fpreds--{preds}) # print(fgolds--{golds}) # 3.4 统计批次内指标 # break # 4.统计整体指标 avg_loss / len(val_dataloader) precision precision_score(golds, preds, averageweighted) recall recall_score(golds, preds, averageweighted) f1 f1_score(golds, preds, averageweighted) report classification_report(golds, preds) # print(fprecision--{precision}) # print(frecall--{recall}) # print(ff1--{f1}) # print(freport--{report}) return avg_loss, precision, recall, f1, report def model2train(): # 1.构建数据迭代器Dataloader(包括数据处理与构建数据源Dataset) train_dataloader, val_dataloader get_data() # 2.实例化模型 models {BiLSTM: NERLSTM, BiLSTM_CRF: NERLSTM_CRF} model models[conf.model](conf.embedding_dim, conf.hidden_dim, conf.tag2id, word2id, conf.dropout).to(conf.device) print(fmodel--{model}) # 3.实例化损失函数对象 # 忽略索引为11的标签即[PAD]。这样做是因为[PAD]标签不需要参与损失的计算 criterion nn.CrossEntropyLoss(ignore_index11) # 4.实例化优化器对象 optimizer torch.optim.Adam(model.parameters(), lrconf.lr) # 5.定义打印日志参数 start_time time.time() # 6.开始训练 best_f1 -100 # 用来保存最好的模型对应的f1值 # 6.1 实现外层大循环epoch if conf.model BiLSTM: for epoch in range(conf.epochs): # 6.2 将模型设置为训练模式 model.train() # 6.3 内部遍历数据迭代器dataloader for index, (input_ids, labels, attention_mask) in enumerate(tqdm(train_dataloader)): # 1将数据送入模型得到输出结果 # 将数据放到 GPU input_ids input_ids.to(conf.device) labels labels.to(conf.device) attention_mask attention_mask.to(conf.device) # 将数据送入模型得到输出结果 output model(input_ids, attention_mask) # print(foutput--{output.shape}) # 2计算损失 # 在计算损失之前需要将预测结果的形状转变成(batch_size*seq_len, tag_size)并将标签结果转成(batch_size*seq_len) reshape_output output.view(-1, len(conf.tag2id)) # print(freshape_output--{reshape_output.shape}) reshape_labels labels.view(-1) # print(freshape_labels--{reshape_labels.shape}) loss criterion(reshape_output, reshape_labels) # print(floss--{loss}) # 3梯度清零: optimizer.zero_grad() optimizer.zero_grad() # 4反向传播(计算梯度): loss.backward() loss.backward() # 5梯度更新(参数更新): optimizer.step() optimizer.step() # 6打印内部训练日志 if (index1) % 50 0: print(epoch:%04d,------------loss:%f % (epoch, loss.item())) # break # 6.4 使用验证集进行模型评估【将模型设置为评估模式】 avg_loss, precision, recall, f1, report model2dev(val_dataloader, model, criterion) # 6.5 保存模型: torch.save(model.state_dict(), model_path) if f1 best_f1: print(fepoch:{epoch}, avg_loss:{avg_loss}, precision:{precision}, recall:{recall}, f1:{f1}) print(freport--{report}) torch.save(model.state_dict(), save_model/bilstm_best_f1.pth) best_f1 f1 # 注意不忘忘记更新best_f1 # break else: for epoch in range(conf.epochs): # 6.2 将模型设置为训练模式 model.train() # 6.3 内部遍历数据迭代器dataloader for index, (input_ids, labels, attention_mask) in enumerate(tqdm(train_dataloader)): # 1将数据送入模型得到输出结果 # 将数据放到 GPU input_ids input_ids.to(conf.device) labels labels.to(conf.device) attention_mask attention_mask.to(conf.device) # 2计算损失 # 需要使用mean()方法将损失求平均 loss model.log_likelihood(input_ids, labels, attention_mask).mean() # print(floss--{loss}) # 3梯度清零: optimizer.zero_grad() optimizer.zero_grad() # 4反向传播(计算梯度): loss.backward() loss.backward() # 梯度裁剪作用是防止训练不稳定或梯度爆炸 torch.nn.utils.clip_grad_norm_(parametersmodel.parameters(), max_norm10) # 5梯度更新(参数更新): optimizer.step() optimizer.step() # 6打印内部训练日志 if (index 1) % 50 0: print(epoch:%04d,------------loss:%f % (epoch, loss.item())) # break # 6.4 使用验证集进行模型评估【将模型设置为评估模式】 avg_loss, precision, recall, f1, report model2dev(val_dataloader, model) # 6.5 保存模型: torch.save(model.state_dict(), model_path) if f1 best_f1: print(fepoch:{epoch}, avg_loss:{avg_loss}, precision:{precision}, recall:{recall}, f1:{f1}) print(freport--{report}) torch.save(model.state_dict(), save_model/bilstm_crf_best_f1.pth) best_f1 f1 # 注意不忘忘记更新best_f1 # break # 6.6 打印外部训练日志 print(训练结束总耗时%f % (time.time() - start_time)) if __name__ __main__: model2train()使用CRF之后效果比之前稍微好一些但是训练成本会提高很多。二、模型优化BiLSTM_CRF模型在训练完后可以做哪些优化来改善模型性能1模型优化预训练词向量使用预训练的词向量如Word2Vec、GloVe、FastText替代随机初始化的词嵌入可以更好地捕捉词汇语义信息。自注意力机制在BiLSTM后加入自注意力层增强模型对长距离依赖的捕捉能力。替换BiLSTM模型使用Bert或Bert变体来替换BiLSTM一般来说是可以获取更好的语义表达。调整随机失活层可以在embedding层后添加随机失活层也可以修改随机失活比例。2训练过程优化shuffles设置注意真正训练时需要将DataLoader中的shuffle设置为True梯度裁剪在反向传播时对梯度进行裁剪防止梯度爆炸比如预设最大梯度值为10然后反向传播计算梯度得到20那么等比例缩放得到梯度值为10除以200.5这样可以有效防止梯度爆炸。早停机制监控验证集F1值若连续多个epoch未提升则提前终止训练存储多次得到的f1值如果为提升那么提前终止训练。3训练数据优化如果训练集和验证集数据分布不同也就是说使用的是差距很大的样本会使模型的效果较差所以可以将数据打散后再送到dataloader中shuffleTrue这样可以防止一种情况数据存储在多个文件夹下面不同文件夹之间的数据关联性不高那么前几个文件夹的数据都用来训练大模型了后面几个文件夹的数据作为验证集效果不好除了这种方式之外也可以使用分类采样的方式。这种方式可以绝对类型上训练集和验证集的分布是一致的在每个文件夹里面的数据全部采用8:2的方式划分训练集和验证集这样训练效果可以更好。另外还有以下方法更多数据收集或标注更多数据送到模型中进行训练。实体替换保留实体边界随机替换实体内容如疾病名称、药品名称提升实体识别泛化能力。三、模型预测使用训练好的模型随机抽取文本进行NER3.1预测流程基本步骤1.实例化模型2.加载训练好的模型参数3.处理数据4.模型预测5.结果处理整体思路结果解析思路提取实体和标签获取标签类型先获取标签类型然后把这个类型添加到类型列表entity里面最后把实体和标签类型添加到实体列表entities里面3.2预测代码import torch from P03_NER.LSTM_CRF.config import Config from P03_NER.LSTM_CRF.model.BiLSTM import NERLSTM from P03_NER.LSTM_CRF.model.BiLSTM_CRF import NERLSTM_CRF from P03_NER.LSTM_CRF.utils.data_loader import word2id conf Config() id2tag {v: k for k, v in conf.tag2id.items()} print(fid2tag--{id2tag}) # 1.实例化模型 models {BiLSTM: NERLSTM, BiLSTM_CRF: NERLSTM_CRF} model models[conf.model](conf.embedding_dim, conf.hidden_dim, conf.tag2id, word2id, conf.dropout).to(conf.device) print(fmodel--{model}) # 2.加载训练好的模型参数 if conf.model BiLSTM: model.load_state_dict(torch.load(save_model/bilstm_best_f1.pth, weights_onlyTrue)) else: model.load_state_dict(torch.load(save_model/bilstm_crf_best_f1.pth, weights_onlyTrue)) def model2predict(text): # 3.处理数据 ids [] for char in text: if char not in word2id: ids.append(word2id[UNK]) else: ids.append(word2id[char]) # 需要给ids加上一个batch_size维度 ids_tensor torch.tensor([ids]).to(conf.device) # print(fids_tensor--{ids_tensor}) attention_mask (ids_tensor ! 0).long() # 4.模型预测 model.eval() with torch.no_grad(): if conf.model BiLSTM: # 获取预测结果 result model(ids_tensor, attention_mask) # print(fresult--{result}) predict result.argmax(dim-1).tolist()[0] # print(fpredict--{predict}) else: # 获取预测结果 predict model(ids_tensor, attention_mask)[0] # print(fpredict--{predict}) # 5.结果处理 tags [id2tag[tag_id] for tag_id in predict] # print(ftags--{tags}) # 将实体从文本中抽取出来返回一个字典 chars [char for char in text] result extract_entities(chars, tags) # print(fresult--{result}) return result def extract_entities(tokens, labels): entities [] # 用来保存所有 (实体类型, 实体) entity [] # 用来保存单个实体 entity_type None # 实体类型 for token, label in zip(tokens, labels): if label.startswith(B-): # 实体的开始 if entity: # 如果已经有实体先保存 entities.append((entity_type, .join(entity))) entity [] entity_type label.split(-)[1] entity.append(token) elif label.startswith(I-) and entity: # 实体的中间或结尾 entity.append(token) else: if entity: # 保存上一个实体 entities.append((entity_type, .join(entity))) entity [] # 如果最后一个实体没有保存手动保存 if entity: entities.append((entity_type, .join(entity))) # print(fentities--{entities}) return {entity: entity_type for entity_type, entity in entities} if __name__ __main__: text 小明的父亲患有冠心病及糖尿病无手术外伤史及药物过敏史 result model2predict(text) print(ftext--{text}) print(fresult--{result})预测结果

更多文章