当前位置: 首页 > news >正文

NLP(20)--知识图谱+实体抽取

前言

仅记录学习过程,有问题欢迎讨论

基于LLM的垂直领域问答方案:

  • 特点:不是通用语料;准确度要求高,召回率可以低(转人工);拓展性和可控性(改变特定内容的回答);确切的评价指标

实现:
传统方法:

  • 知识库+文本匹配 (问题转向量找知识库的相似度最高的问题来回答)

基于LLM:

1.直接生成:

  • 直接使用LLM获答案,然后使用SFT微调
  • 缺点:fine-tune的成本高;模型的泛用性下降;不易调整数据

2.RAG思路(推荐):

  • 段落召回+阅读理解(基于获取的信息给模型提示,期望获取一个更好的答案)
  • 召回的就是你垂直领域的内容,去给llm提示
  • 缺点:对LLM要求高,受召回算法限制(如果正确答案被丢弃,LLM无法挽回);生成结果不可控

3.基于知识体系(图谱)

  • 树形结构设置知识体系结构,然后给LLM去匹配最可能的知识点选择项,一级一级往下走
  • 缺点:需要大量标注数据,标注数据需要人工标注,标注成本高

知识图谱:

  • 知识图谱是图数据库的一种,用于存储和表示知识。
  • 如:姚明-身高-226cm(三元组)
  • 知识图谱的构建取决于你想要完成的任务,如果想要获取实体之间的关系,就要构建实体关系的图谱
以下为知识图谱的具体内容:

实体抽取:ner任务获取实体属性
关系抽取:

  • 限定领域:
    • 文本+实体送入模型 预测关系(本质还是分类任务)
    • 可以同时训练实体抽取和关系抽取,loss为二者相加
  • 开放领域:
    基于序列标注 (NER)

属性抽取:同关系抽取

知识融合:

  • 实体对齐:通过判断不同来源的属性的相似度
  • 实体消歧:根据上下文和语义关系进行实体消歧
  • 属性对齐:属性和属性值的相似度计算

知识推理:通过模型来推断两个实体的关系
知识表示:实体关系属性都转化为向量,都可以用id表示某个信息

图数据库的使用:

noe4j
使用NL2SQL把输入的文字变为sql查询

  • 方法1:基于模版+文本匹配,输入的文本去匹配对应的问题模版–再去匹配SQL(依赖模版,易于拓展,可以写复杂sql)
  • 方法2:semantic parsing(语义解析)–通过训练多个模型来获取sql(不易于拓展)
  • 方法3:用LLM写sql

代码展示

构建基于neo4j的知识图谱问答:
这里采用的是方法1,依赖问题模版。

import re
import json
import pandas
import itertools
from py2neo import Graph"""
使用neo4j 构建基于知识图谱的问答
需要自定义问题模板
"""class GraphQA:def __init__(self):# 启动neo4j neo4j consoleself.graph = Graph("http://localhost:7474", auth=("neo4j", "password"))schema_path = "kg_schema.json"templet_path = "question_templet.xlsx"self.load(schema_path, templet_path)print("知识图谱问答系统加载完毕!\n===============")# 对外提供问答接口def query(self, sentence):print("============")print(sentence)# 对输入的句子找和模板中最匹配的问题info = self.parse_sentence(sentence)  # 信息抽取print("info:", info)# 匹配模板templet_cypher_score = self.cypher_match(sentence, info)  # cypher匹配for templet, cypher, score, answer in templet_cypher_score:graph_search_result = self.graph.run(cypher).data()# 最高分命中的模板不一定在图上能找到答案, 当不能找到答案时,运行下一个搜索语句, 找到答案时停止查找后面的模板if graph_search_result:answer = self.parse_result(graph_search_result, answer)return answerreturn None# 加载模板def load(self, schema_path, templet_path):self.load_kg_schema(schema_path)self.load_question_templet(templet_path)return# 加载模板信息def load_question_templet(self, templet_path):dataframe = pandas.read_excel(templet_path)self.question_templet = []for index in range(len(dataframe)):question = dataframe["question"][index]cypher = dataframe["cypher"][index]cypher_check = dataframe["check"][index]answer = dataframe["answer"][index]self.question_templet.append([question, cypher, json.loads(cypher_check), answer])return# 返回输入中的实体,关系,属性def parse_sentence(self, sentence):# 先提取实体,关系,属性entitys = self.get_mention_entitys(sentence)relations = self.get_mention_relations(sentence)labels = self.get_mention_labels(sentence)attributes = self.get_mention_attributes(sentence)# 然后根据模板进行匹配return {"%ENT%": entitys,"%REL%": relations,"%LAB%": labels,"%ATT%": attributes}# 获取问题中谈到的实体,可以使用基于词表的方式,也可以使用NER模型def get_mention_entitys(self, sentence):return re.findall("|".join(self.entity_set), sentence)# 获取问题中谈到的关系,也可以使用各种文本分类模型def get_mention_relations(self, sentence):return re.findall("|".join(self.relation_set), sentence)# 获取问题中谈到的属性def get_mention_attributes(self, sentence):return re.findall("|".join(self.attribute_set), sentence)# 获取问题中谈到的标签def get_mention_labels(self, sentence):return re.findall("|".join(self.label_set), sentence)# 加载图谱信息def load_kg_schema(self, path):with open(path, encoding="utf8") as f:schema = json.load(f)self.relation_set = set(schema["relations"])self.entity_set = set(schema["entitys"])self.label_set = set(schema["labels"])self.attribute_set = set(schema["attributes"])return# 匹配模板的问题def cypher_match(self, sentence, info):# 根据提取到的实体,关系等信息,将模板展开成待匹配的问题文本templet_cypher_pair = self.expand_question_and_cypher(info)result = []for templet, cypher, answer in templet_cypher_pair:# 求相似度 距离函数score = self.sentence_similarity_function(sentence, templet)# print(sentence, templet, score)result.append([templet, cypher, score, answer])# 取最相似的result = sorted(result, reverse=True, key=lambda x: x[2])return result# 根据提取到的实体,关系等信息,将模板展开成待匹配的问题文本def expand_question_and_cypher(self, info):templet_cypher_pair = []# 模板的数据for templet, cypher, cypher_check, answer in self.question_templet:# 匹配模板cypher_check_result = self.match_cypher_check(cypher_check, info)if cypher_check_result:templet_cypher_pair += self.expand_templet(templet, cypher, cypher_check, info, answer)return templet_cypher_pair# 校验 减少比较次数def match_cypher_check(self, cypher_check, info):for key, required_count in cypher_check.items():if len(info.get(key, [])) < required_count:return Falsereturn True# 对于单条模板,根据抽取到的实体属性信息扩展,形成一个列表# info:{"%ENT%":["周杰伦", "方文山"], “%REL%”:[“作曲”]}def expand_templet(self, templet, cypher, cypher_check, info, answer):# 获取所有组合combinations = self.get_combinations(cypher_check, info)templet_cpyher_pair = []for combination in combinations:# 替换模板中的实体,属性,关系replaced_templet = self.replace_token_in_string(templet, combination)replaced_cypher = self.replace_token_in_string(cypher, combination)replaced_answer = self.replace_token_in_string(answer, combination)templet_cpyher_pair.append([replaced_templet, replaced_cypher, replaced_answer])return templet_cpyher_pair# 对于找到了超过模板中需求的实体数量的情况,需要进行排列组合# info:{"%ENT%":["周杰伦", "方文山"], “%REL%”:[“作曲”]}def get_combinations(self, cypher_check, info):slot_values = []for key, required_count in cypher_check.items():# 生成所有组合slot_values.append(itertools.combinations(info[key], required_count))value_combinations = itertools.product(*slot_values)combinations = []for value_combination in value_combinations:combinations.append(self.decode_value_combination(value_combination, cypher_check))return combinations# 将提取到的值分配到键上def decode_value_combination(self, value_combination, cypher_check):res = {}for index, (key, required_count) in enumerate(cypher_check.items()):if required_count == 1:res[key] = value_combination[index][0]else:for i in range(required_count):key_num = key[:-1] + str(i) + "%"res[key_num] = value_combination[index][i]return res# 将带有token的模板替换成真实词# string:%ENT1%和%ENT2%是%REL%关系吗# combination: {"%ENT1%":"word1", "%ENT2%":"word2", "%REL%":"word"}def replace_token_in_string(self, string, combination):for key, value in combination.items():string = string.replace(key, value)return string# 求相似度 距离函数 Jaccard相似度def sentence_similarity_function(self, sentence1, sentence2):# print("计算  %s %s"%(string1, string2))jaccard_distance = len(set(sentence1) & set(sentence2)) / len(set(sentence1) | set(sentence2))return jaccard_distance# 解析结果def parse_result(self, graph_search_result, answer):graph_search_result = graph_search_result[0]# 关系查找返回的结果形式较为特殊,单独处理if "REL" in graph_search_result:graph_search_result["REL"] = list(graph_search_result["REL"].types())[0]answer = self.replace_token_in_string(answer, graph_search_result)return answerif __name__ == '__main__':graph = GraphQA()res = graph.query("谁导演的不能说的秘密")print(res)res = graph.query("发如雪的谱曲是谁")print(res)

代码2

通过联合训练同时实现 属性和实体(关系)的抽取
config

"""
配置参数信息
"""
Config = {"model_path": "./output/","model_name": "model.pt","schema_path": r"schema.json","train_data_path": r"D:\NLP\video\第十四周\week13 知识问答\triplet_data\train_triplet_data.json","valid_data_path": r"D:\NLP\video\第十四周\week13 知识问答\triplet_data\valid_triplet_data.json","vocab_path": r"D:\NLP\video\第九周 序列标注任务\课件\week9 序列标注问题\ner\chars.txt","model_type": "lstm",# 文本向量大小"char_dim": 20,# 文本长度"max_len": 200,# 词向量大小"hidden_size": 64,# 训练 轮数"epoch_size": 15,# 批量大小"batch_size": 32,# 训练集大小"simple_size": 500,# 学习率"lr": 1e-3,# dropout"dropout": 0.5,# 优化器"optimizer": "adam",# 卷积核"kernel_size": 3,# 最大池 or 平均池"pooling_style": "avg",# 模型层数"num_layers": 3,"bert_model_path": r"D:\NLP\video\第六周\bert-base-chinese",# 输出层大小"output_size": 9,# 随机数种子"seed": 987,"attribute_loss_ratio": 0.01
}

loader

"""
数据加载
"""
import os
import numpy as np
import json
import re
import os
import torch
import torch.utils.data as Data
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer# 获取字表集
def load_vocab(path):vocab = {}with open(path, 'r', encoding='utf-8') as f:for index, line in enumerate(f):word = line.strip()# 0留给padding位置,所以从1开始vocab[word] = index + 1vocab['unk'] = len(vocab) + 1return vocabclass DataGenerator:def __init__(self, data_path, config):self.data_path = data_pathself.config = configself.schema = self.load_schema(config["schema_path"])if self.config["model_type"] == "bert":self.tokenizer = BertTokenizer.from_pretrained(config["bert_model_path"])self.vocab = load_vocab(config["vocab_path"])self.config["vocab_size"] = len(self.vocab)self.max_len = config["max_len"]# 实体标签self.bio_schema = {"B_object": 0,"I_object": 1,"B_value": 2,"I_value": 3,"O": 4}self.config["label_count"] = len(self.bio_schema)self.config["attribute_count"] = len(self.schema)# 中文的语句list# self.sentence_list = []self.load_data()def __len__(self):return len(self.data)def __getitem__(self, idx):return self.data[idx]def load_schema(self, path):with open(path, encoding="utf8") as f:return json.load(f)def load_data(self):# 储存实体数据 测试使用self.text_data = []# 处理完的数据self.data = []# 是否超长self.exceed_max_length = 0with open(self.data_path, 'r', encoding='utf-8') as f:for line in f:data = json.loads(line.strip())# 文本sentence = data["context"]# 获取实体object = data["object"]attribute = data["attribute"]value = data["value"]if attribute not in self.schema:attribute = "UNRELATED"self.text_data.append([sentence, object, attribute, value])input_id, attribute_label, sentence_label = self.process_sentence(sentence, object, attribute, value)self.data.append([torch.LongTensor(input_id),torch.LongTensor([attribute_label]),torch.LongTensor(sentence_label)])return data# 文本预处理# 转化为向量def sentence_to_index(self, text):input_ids = []vocab = self.vocabif self.config["model_type"] == "bert":# 中文的文本转化为tokenizer的idinput_ids = self.tokenizer.encode(text, padding="max_length", truncation=True,max_length=self.config["max_len"], add_special_tokens=False)else:for char in text:input_ids.append(vocab.get(char, vocab['unk']))# 填充or裁剪input_ids = self.padding(input_ids)return input_ids# 数据预处理 裁剪or填充def padding(self, input_ids, padding_dot=0):length = self.config["max_len"]padded_input_ids = input_idsif len(input_ids) >= length:return input_ids[:length]else:padded_input_ids += [padding_dot] * (length - len(input_ids))return padded_input_ids# 处理数据def process_sentence(self, sentence, object, attribute, value):if len(sentence) > self.max_len:self.exceed_max_length += 1object_start = sentence.index(object)value_start = sentence.index(value)# 文本转向量input_id = self.sentence_to_index(sentence)# 属性转向量attribute_index = self.schema[attribute]# assert len(sentence) == len(input_id)# 构建实体和属性标签 在一句话中 一个变量就可以 初始化要用 Olabel = [self.bio_schema["O"]] * len(input_id)# print(sentence,"======",object,"=====",object_start)if object_start < self.max_len:label[object_start] = self.bio_schema["B_object"]# 标记实体for index in range(object_start + 1, object_start + len(object)):if index >= self.max_len:breaklabel[index] = self.bio_schema["I_object"]# 标记属性if value_start < self.max_len:label[value_start] = self.bio_schema["B_value"]for index in range(value_start + 1, value_start + len(value)):if index >= self.max_len:breaklabel[index] = self.bio_schema["I_value"]if len(input_id) != len(label):label = self.padding(label, -1)return input_id, attribute_index, label# 用torch自带的DataLoader类封装数据
def load_data_batch(data_path, config, shuffle=True):dg = DataGenerator(data_path, config)# DataLoader 类封装数据 dg除了data 还包含其他信息(后面需要使用)dl = DataLoader(dg, batch_size=config["batch_size"], shuffle=shuffle)return dlif __name__ == '__main__':from config import Configdg = DataGenerator(Config["train_data_path"], Config)print(len(dg))print(dg[0])

model

import torch
import torch.nn as nn
from torch.optim import Adam, SGD
from transformers import BertModel
from torchcrf import CRF"""
建立网络模型结构
"""class TorchModel(nn.Module):def __init__(self, config):super(TorchModel, self).__init__()hidden_size = config["hidden_size"]vocab_size = config["vocab_size"] + 1output_size = config["output_size"]self.model_type = config["model_type"]num_layers = config["num_layers"]self.pooling_style = config["pooling_style"]# self.use_bert = config["use_bert"]# self.use_crf = config["use_crf"]self.emb = nn.Embedding(vocab_size + 1, hidden_size, padding_idx=0)if self.model_type == 'rnn':self.encoder = nn.RNN(input_size=hidden_size, hidden_size=hidden_size, num_layers=num_layers,batch_first=True)elif self.model_type == 'lstm':# 双向lstm,输出的是 hidden_size * 2(num_layers 要写2)self.encoder = nn.LSTM(hidden_size, hidden_size, num_layers=num_layers, bidirectional=True,batch_first=True)hidden_size = hidden_size * 2elif self.model_type == 'bert':self.encoder = BertModel.from_pretrained(config["bert_model_path"])# 需要使用预训练模型的hidden_sizehidden_size = self.encoder.config.hidden_sizeelif self.model_type == 'cnn':self.encoder = CNN(config)elif self.model_type == "gated_cnn":self.encoder = GatedCNN(config)elif self.model_type == "bert_lstm":self.encoder = BertLSTM(config)# 需要使用预训练模型的hidden_sizehidden_size = self.encoder.config.hidden_size# self.classify = nn.Linear(hidden_size, output_size)# 实体的分类输出self.bio_classifier = nn.Linear(hidden_size, config["label_count"])# 属性的分类输出self.attribute_classifier = nn.Linear(hidden_size, config["attribute_count"])self.pooling_style = config["pooling_style"]# self.crf_layer = CRF(output_size, batch_first=True)# 一起计算loss的权重self.attribute_loss_ratio = config["attribute_loss_ratio"]self.loss = torch.nn.CrossEntropyLoss(ignore_index=-1)  # loss采用交叉熵损失def forward(self, x, attribute_target=None, bio_target=None):if self.model_type == 'bert':# 输入x为[batch_size, seq_len]# bert返回的结果是 (sequence_output, pooler_output)# sequence_output:batch_size, max_len, hidden_size# pooler_output:batch_size, hidden_sizex = self.encoder(x)[0]else:x = self.emb(x)x = self.encoder(x)# 判断x是否是tupleif isinstance(x, tuple):x = x[0]# 序列标注(单个字)bio_predict = self.bio_classifier(x)  # (batch_size, max_length, 5) (Head-B, Head-I, Tail-B, Tail-I, O)# 文本分类# 池化层if self.pooling_style == "max":# shape[1]代表列数,shape是行和列数构成的元组self.pooling_style = nn.MaxPool1d(x.shape[1])elif self.pooling_style == "avg":self.pooling_style = nn.AvgPool1d(x.shape[1])x = self.pooling_style(x.transpose(1, 2)).squeeze()# 属性分类attribute_predict = self.attribute_classifier(x)if bio_target is not None:bio_loss = self.loss(bio_predict.view(-1, bio_predict.shape[-1]), bio_target.view(-1))# attribute_loss = self.loss(attribute_predict,  attribute_target.view(-1))attribute_loss = self.loss(attribute_predict.view(x.shape[0], -1), attribute_target.view(-1))return bio_loss + self.attribute_loss_ratio * attribute_losselse:return attribute_predict, bio_predict# 优化器的选择
def choose_optimizer(config, model):optimizer = config["optimizer"]learning_rate = config["lr"]if optimizer == "adam":return Adam(model.parameters(), lr=learning_rate)elif optimizer == "sgd":return SGD(model.parameters(), lr=learning_rate)# 定义CNN模型
class CNN(nn.Module):def __init__(self, config):super(CNN, self).__init__()hidden_size = config["hidden_size"]kernel_size = config["kernel_size"]pad = int((kernel_size - 1) / 2)self.cnn = nn.Conv1d(hidden_size, hidden_size, kernel_size, bias=False, padding=pad)def forward(self, x):  # x : (batch_size, max_len, embeding_size)return self.cnn(x.transpose(1, 2)).transpose(1, 2)# 定义GatedCNN模型
class GatedCNN(nn.Module):def __init__(self, config):super(GatedCNN, self).__init__()self.cnn = CNN(config)self.gate = CNN(config)# 定义前向传播函数 比普通cnn多了一次sigmoid 然后互相卷积def forward(self, x):a = self.cnn(x)b = self.gate(x)b = torch.sigmoid(b)return torch.mul(a, b)# 定义BERT-LSTM模型
class BertLSTM(nn.Module):def __init__(self, config):super(BertLSTM, self).__init__()self.bert = BertModel.from_pretrained(config["bert_model_path"], return_dict=False)self.rnn = nn.LSTM(self.bert.config.hidden_size, self.bert.config.hidden_size, batch_first=True)def forward(self, x):x = self.bert(x)[0]x, _ = self.rnn(x)return x# if __name__ == "__main__":
#     from config import Config
#
#     Config["output_size"] = 2
#     Config["vocab_size"] = 20
#     Config["max_length"] = 5
#     Config["model_type"] = "bert"
#     Config["use_bert"] = True
#     # model = BertModel.from_pretrained(Config["bert_model_path"], return_dict=False)
#     x = torch.LongTensor([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])
#     # sequence_output, pooler_output = model(x)
#     # print(x[1], type(x[2]), len(x[2]))
#
#     model = TorchModel(Config)
#     label = torch.LongTensor([0,1])
#     print(model(x, label))

main

# -*- coding: utf-8 -*-
import reimport torch
import os
import random
import os
import numpy as np
import logging
from config import Config
from model import TorchModel, choose_optimizer
from loader import load_data_batch
from evaluate import Evaluator# [DEBUG, INFO, WARNING, ERROR, CRITICAL]logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)"""
模型训练主程序
"""
# 通过设置随机种子来复现上一次的结果(避免随机性)
seed = Config["seed"]
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)def main(config):# 保存模型的目录if not os.path.isdir(config["model_path"]):os.mkdir(config["model_path"])# 加载数据dataset = load_data_batch(config["train_data_path"], config)# 加载模型model = TorchModel(config)# 是否使用gpuif torch.cuda.is_available():logger.info("gpu可以使用,迁移模型至gpu")model.cuda()# 选择优化器optim = choose_optimizer(config, model)# 加载效果测试类evaluator = Evaluator(config, model, logger)for epoch in range(config["epoch_size"]):epoch += 1logger.info("epoch %d begin" % epoch)epoch_loss = []# 训练模型model.train()for index, batch_data in enumerate(dataset):if torch.cuda.is_available():batch_data = [d.cuda() for d in batch_data]# x, y = dataiter# 反向传播optim.zero_grad()input_id, attribute_index, label = batch_data     # 输入变化时这里需要修改,比如多输入,多输出的情况# 计算梯度loss = model(input_id, attribute_index,label)# 梯度更新loss.backward()# 优化器更新模型optim.step()# 记录损失epoch_loss.append(loss.item())logger.info("epoch average loss: %f" % np.mean(epoch_loss))# 测试模型效果evaluator.eval(epoch)model_path = os.path.join(config["model_path"], "epoch_%d.pth" % epoch)torch.save(model.state_dict(), model_path)return model, datasetif __name__ == "__main__":main(Config)

evaluate

"""
模型效果测试
"""
import re
from collections import defaultdictimport numpy as np
import torch
from loader import load_data_batchclass Evaluator:def __init__(self, config, model, logger):self.config = configself.model = modelself.logger = logger# 选择验证集合self.dataset = load_data_batch(config["valid_data_path"], config, shuffle=False)# 获取loader时的字符集self.bio_schema = self.dataset.dataset.bio_schemaself.attribute_schema = self.dataset.dataset.schemaself.text_data = self.dataset.dataset.text_data# 将index转换为标签self.index_to_label = dict((y, x) for x, y in self.attribute_schema.items())def eval(self, epoch):self.logger.info("开始测试第%d轮模型效果:" % epoch)# 测试模式self.model.eval()self.stats_dict = {"object_acc": 0, "attribute_acc": 0, "value_acc": 0, "full_match_acc": 0}# 遍历验证集for index, batch_data in enumerate(self.dataset):# 分割为一个batch 因为text_data是没分割batch的 所以评估数据集shuffle = Falsetext_data = self.text_data[index * self.config["batch_size"]: (index + 1) * self.config["batch_size"]]if torch.cuda.is_available():batch_data = [d.cuda() for d in batch_data]# 这里只取input_id (32*200)input_id = batch_data[0]  # 输入变化时这里需要修改,比如多输入,多输出的情况with torch.no_grad():attribute_pred, bio_pred = self.model(input_id)  # 不输入labels,使用模型当前参数进行预测self.write_stats(attribute_pred, bio_pred, text_data)self.show_stats()returndef write_stats(self, attribute_pred, bio_pred, text_data):attribute_pred = torch.argmax(attribute_pred, dim=-1)bio_pred = torch.argmax(bio_pred, dim=-1)for attribute_p, bio_p, info in zip(attribute_pred, bio_pred, text_data):context, object, attribute, value = infobio_p = bio_p.cpu().detach().tolist()# 获取实体的预测值pred_object, pred_value = self.decode1(bio_p, context)# 获取属性的预测值pred_attribute = self.index_to_label[int(attribute_p)]self.stats_dict["object_acc"] += int(pred_object == object)self.stats_dict["attribute_acc"] += int(pred_attribute == attribute)self.stats_dict["value_acc"] += int(pred_value == value)if pred_value == value and pred_attribute == attribute and pred_object == object:self.stats_dict["full_match_acc"] += 1return# 打印结果def show_stats(self):for key, value in self.stats_dict.items():self.logger.info("%s : %s " % (key, value / len(self.text_data)))self.logger.info("--------------------")return# 相当于截取对应的句子def decode1(self, labels, sentence):labels = "".join([str(i) for i in labels])pred_obj, pred_value = "", ""# * 表示0 or d多个for location in re.finditer("(01*)", labels):s, e = location.span()pred_obj = sentence[s:e]breakfor location in re.finditer("(23*)", labels):s, e = location.span()pred_value = sentence[s:e]breakreturn pred_obj, pred_valuedef decode(self, pred_label, context):pred_label = "".join([str(i) for i in pred_label])pred_obj = self.seek_pattern("01*", pred_label, context)pred_value = self.seek_pattern("23*", pred_label, context)return pred_obj, pred_valuedef seek_pattern(self, pattern, pred_label, context):pred_obj = re.search(pattern, pred_label)if pred_obj:s, e = pred_obj.span()pred_obj = context[s:e]else:pred_obj = ""return pred_obj

predict

# -*- coding: utf-8 -*-
import torch
import re
import json
import numpy as np
from collections import defaultdict
from config import Config
from model import TorchModel"""
模型效果测试
"""class SentenceLabel:def __init__(self, config, model_path):self.config = configself.bio_schema = {"B_object": 0,"I_object": 1,"B_value": 2,"I_value": 3,"O": 4}self.attribute_schema = json.load(open(config["schema_path"], encoding="utf8"))self.index_to_label = dict((y, x) for x, y in self.attribute_schema.items())self.config["label_count"] = len(self.bio_schema)self.config["attribute_count"] = len(self.attribute_schema)self.vocab = self.load_vocab(config["vocab_path"])self.config["vocab_size"] = len(self.vocab)self.model = TorchModel(config)self.model.load_state_dict(torch.load(model_path))self.model.eval()print("模型加载完毕!")# 加载字表或词表def load_vocab(self, path):vocab = {}with open(path, 'r', encoding='utf-8') as f:for index, line in enumerate(f):word = line.strip()# 0留给padding位置,所以从1开始vocab[word] = index + 1vocab['unk'] = len(vocab) + 1return vocabdef decode(self, attribute_label, bio_label, context):pred_attribute = self.index_to_label[int(attribute_label)]bio_label = "".join([str(i) for i in bio_label.detach().tolist()])pred_obj = self.seek_pattern("01*", bio_label, context)pred_value = self.seek_pattern("23*", bio_label, context)return pred_obj, pred_attribute, pred_valuedef seek_pattern(self, pattern, pred_label, context):pred_obj = re.search(pattern, pred_label)if pred_obj:s, e = pred_obj.span()pred_obj = context[s:e]else:pred_obj = ""return pred_objdef predict(self, sentence):input_id = []for char in sentence:input_id.append(self.vocab.get(char, self.vocab["[UNK]"]))with torch.no_grad():attribute_pred, bio_pred = self.model(torch.LongTensor([input_id]))attribute_pred = torch.argmax(attribute_pred)bio_pred = torch.argmax(bio_pred[0], dim=-1)object, attribute, value = self.decode(attribute_pred, bio_pred, sentence)return object, attribute, valueif __name__ == "__main__":sl = SentenceLabel(Config, "output/epoch_15.pth")sentence = "浙江理工大学是一所以工为主,特色鲜明,优势突出,理、工、文、经、管、法、艺术、教育等多学科协调发展的省属重点建设大学。"res = sl.predict(sentence)print(res)sentence = "出露地层的岩石以沉积岩为主(其中最多为碳酸盐岩),在受到乌江的切割下,由内外力共同作用,形成沟壑密布、崎岖复杂的地貌景观。"res = sl.predict(sentence)print(res)

相关文章:

NLP(20)--知识图谱+实体抽取

前言 仅记录学习过程&#xff0c;有问题欢迎讨论 基于LLM的垂直领域问答方案&#xff1a; 特点&#xff1a;不是通用语料&#xff1b;准确度要求高&#xff0c;召回率可以低&#xff08;转人工&#xff09;&#xff1b;拓展性和可控性&#xff08;改变特定内容的回答&#xf…...

【mysql数据库】mycat中间件

MyCat 简介 Mycat 是数据库 中间件 。 1、 数据库中间件 中间件 是一类连接软件组件和应用的计算机软件&#xff0c; 以便于软件各部件之间的沟通 。 例子 Tomcat web 中间件 。 数据库 中间件 连接 java 应用程序和数据库 2、 为什么要用 Mycat ① Java 与数据库紧耦合 …...

满帮集团 Eureka 和 ZooKeeper 的上云实践

作者&#xff1a;胡安祥 满帮集团&#xff0c;作为“互联网物流”的平台型企业&#xff0c;一端承接托运人运货需求&#xff0c;另一端对接货车司机&#xff0c;提升货运物流效率。2021 年美股上市&#xff0c;成为数字货运平台上市第一股。根据公司年报&#xff0c;2021 年&a…...

ubuntu中彻底删除mysql (配置文件删除可选)

ubuntu中彻底删除mysql (配置文件删除可选) 对于此类即搜即用的分享文章&#xff0c;也不过多赘述&#xff0c;直接依次按照下面的操作执行即可&#xff1a; 一、删除 mysql 数据文件 sudo rm /var/lib/mysql/ -R二、删除 mysql 配置文件 sudo rm /etc/mysql/ -R三、查看 m…...

根据模板和git commit自动生成日·周·月·季报

GitHub - qiaotaizi/dailyreport: 日报生成器 GitHub - yurencloud/daily: 程序员专用的日报、周报、月报、季报自动生成器&#xff01; config.json: { "Author": "gitname", "Exclude": ["update:", "add:", "…...

matlab GUI界面设计

【实验内容】 用MATLAB的GUI程序设计一个具备图像边缘检测功能的用户界面&#xff0c;该设计程序有以下基本功能&#xff1a; &#xff08;1&#xff09;图像的读取和保存。 &#xff08;2&#xff09;设计图形用户界面&#xff0c;让用户对图像进行彩色图像到灰度图像的转换…...

MyBatis 面试题

一、什么是 Mybatis? 1、Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时 只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性 能,灵活度高。 …...

C#根据数据量自动排版标签的样例

这是一个C#根据数据量自动排版标签的样例 using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Drawing; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using HslCommuni…...

【网络安全】Web安全基础 - 第一节:使用软件及环境介绍

VMware VMware&#xff0c;是全球云基础架构和移动商务解决方案的佼佼者。 VMware可是一个总部位于美国加州帕洛阿尔托的计算机虚拟化软件研发与销售企业呢。简单来说&#xff0c;它就是通过提供虚拟化解决方案&#xff0c;让企业在数据中心改造和公有云整合业务上更加得心应…...

Mac下载docker

先安装homebrew Mac下载Homebrew-CSDN博客 然后输入以下命令安装docker brew install --cask --appdir/Applications docker 期间需要输入密码。输入完等待即可...

k8s_设置dns

配置k8s dns 在 Kubernetes 集群中&#xff0c;CoreDNS 是默认的 DNS 服务器&#xff0c;它负责处理集群内所有的 DNS 请求。 kubectl edit cm coredns -n kube-system (此命令修改coredns 配置) kubectl describe cm coredns -n kube-system&#xff08;此命令查看coredns 配…...

翻译《The Old New Thing》- What a drag: Dragging a virtual file (HGLOBAL edition)

What a drag: Dragging a virtual file (HGLOBAL edition) - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20080318-00/?p23083 Raymond Chen 2008年03月18日 拖拽虚拟文件&#xff08;HGLOBAL 版本&#xff09; 现在我们已经对简单的数据…...

SA316系列音频传输模块-传输距离升级音质不打折

SA316是思为无线研发的一款远距离音频传输模块&#xff0c;音频采样率为48K&#xff0c;传输距离可达200M。为了满足更多用户需求&#xff0c;思为无线在SA316基础上进一步增加传输距离推出SA316F30。相比SA316性能&#xff0c;同样其采用48K采样&#xff0c;-96dBm灵敏度&…...

【机器学习】智能选择的艺术:决策树在机器学习中的深度剖析

在机器学习的分类和回归问题中&#xff0c;决策树是一种广泛使用的算法。决策树模型因其直观性、易于理解和实现&#xff0c;以及处理分类和数值特征的能力而备受欢迎。本文将解释决策树算法的概念、原理、应用、优化方法以及未来的发展方向。 &#x1f680;时空传送门 &#x…...

电脑缺少运行库,无法启动程序

在我们使用一些软件的时候&#xff0c;由于电脑缺少一些运行库&#xff0c;导致无法启动应用软件&#xff0c;此时需要我们安装缺少的运行库。 比如当电脑提示&#xff1a; Cannot load library Qt5Xlsx.dll 我们就需要下载C得运行库&#xff0c;以满足软件运行需要。 下载链…...

【计算机软考_初级篇】每日十题2

各位老师大家好&#xff0c;软考对于日常的知识储备和企业中的考试&#xff0c;或者说在校大学生来说&#xff0c;那用处是非常大的&#xff01;&#xff01;那么下面我们进入正题&#xff0c;软考呢是分两种语言&#xff0c;java和C&#xff0c;对于其他语言目前还没&#xff…...

HR人才测评,如何做营销人员岗位素质测评?

营销人员是企业中的重要角色&#xff0c;他们直接负责企业产品或服务的销售和推广&#xff0c;是企业中最直接影响销售业绩的人才之一。因此&#xff0c;营销人员的基本素质测评非常重要&#xff0c;能够有效评估营销人员的能力和潜力&#xff0c;为企业招聘和培养优秀的营销人…...

LabVIEW调用第三方硬件DLL常见问题及开发流程

在LabVIEW中调用第三方硬件DLL时&#xff0c;除了技术问题&#xff0c;还涉及开发流程、资料获取及与厂家的沟通协调。常见问题包括函数接口不兼容、数据类型转换错误、内存管理问题、线程安全性等。解决这些问题需确保函数声明准确、数据类型匹配、正确的内存管理及线程保护。…...

datax实现MySQL数据库迁移shell自动化脚本

datax实现MySQL数据库迁移 &#xff08;1&#xff09;生成python脚本 # codingutf-8 import json import getopt import os import sys import MySQLdb#MySQL相关配置&#xff0c;需根据实际情况作出修改 mysql_host "xxxx" mysql_port "3306" mysql_u…...

PostgreSQL的学习心得和知识总结(一百四十四)|深入理解PostgreSQL数据库之sendTuples的实现原理及功能修改

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…...

C++数据结构之:链List

摘要&#xff1a; it人员无论是使用哪种高级语言开发东东&#xff0c;想要更高效有层次的开发程序的话都躲不开三件套&#xff1a;数据结构&#xff0c;算法和设计模式。数据结构是相互之间存在一种或多种特定关系的数据元素的集合&#xff0c;即带“结构”的数据元素的集合&am…...

10.Redis之set类型

谈到一个术语,这个术语很可能有多种含义~~ 1.Set 1) 集合. 2)设置 (和 get 相对应) 集合就是把一些有关联的数据放到一起~~ 1.集合中的元素是无序的! 【此处说的无序和 前面list这里的有序 是对应的, 有序: 顺序很重要. 变换一下顺序, 就是不同的 list 了 无序: 顺序不…...

SpringBoot + mongodb 删除集合中的数据

MongoTemplate是Spring Data MongoDB提供的一个工具类&#xff0c;用于与MongoDB进行交互。它提供了许多方法来执行数据库操作&#xff0c;包括删除数据。 本文将介绍如何使用Java MongoTemplate删除集合内的数据&#xff0c;并提供相应的代码示例。 1. 引入MongoTemplate 首…...

【日常记录】【JS】前端预览图片的两种方式,Base64预览和blob预览

文章目录 1、前言1、FileReader3、window.URL.createObjectURL4、参考链接 1、前言 一般来说&#xff0c;都是 后端返回给前端图片的url&#xff0c;前端直接把这个值插入到 img 的src 里面即可还有一种情况是前端需要预览一下图片&#xff0c;比如&#xff1a;上传头像按钮&a…...

每日刷题——杭电2156.分数矩阵和杭电2024.C语言合法标识符

杭电2156.分数矩阵 原题链接&#xff1a;Problem - 2156 题目描述 Problem Description&#xff1a;我们定义如下矩阵: 1/1 1/2 1/3 1/2 1/1 1/2 1/3 1/2 1/1 矩阵对角线上的元素始终是1/1&#xff0c;对角线两边分数的分母逐个递增。请求出这个矩阵的总和。 Input&#xf…...

爬虫学习--18.反爬斗争 selenium(3)

操作多窗口与页面切换 有时候窗口中有很多子tab页面。这时候肯定是需要进行切换的。selenium提供了一个叫做switch_to.window来进行切换&#xff0c;具体切换到哪个页面&#xff0c;可以从driver.window_handles中找到。 from selenium import webdriver from selenium.webdri…...

如何评价GPT-4o?

GPT-4o是OpenAI为聊天机器人ChatGPT发布的一款新语言模型&#xff0c;其名称中的“o”代表Omni&#xff0c;即全能的意思&#xff0c;凸显了其多功能的特性。这款模型在多个方面都有着显著的优势和进步。 首先&#xff0c;GPT-4o具有极强的多模态能力&#xff0c;它能够接受文本…...

算能BM1684+FPGA+AI+Camera推理边缘计算盒

搭载算丰智算芯片BM1684&#xff0c;是面向AI推理的边缘计算盒。高效适配市场上所有AI算法&#xff0c;实现视频结构化、人脸识别、行为分析、状态监测等应用&#xff0c;为智慧城市、智慧交通、智慧能源、智慧金融、智慧电信、智慧工业等领域进行AI赋能。 产品规格 处理器芯片…...

不同厂商SOC芯片在视频记录仪领域的应用

不同SoC公司芯片在不同产品上的应用信息&#xff1a; 大唐半导体 芯片型号: LC1860C (主控) LC1160 (PMU)产品应用: 红米2A (399元)大疆晓Spark技术规格: 28nm工艺&#xff0c;4个ARM Cortex-A7处理器&#xff0c;1.5GHz主频&#xff0c;2核MaliT628 GPU&#xff0c;1300万像…...

【Python入门学习笔记】Python3超详细的入门学习笔记,非常详细(适合小白入门学习)

Python3基础 想要获取pdf或markdown格式的笔记文件点击以下链接获取 Python入门学习笔记点击我获取 1&#xff0c;Python3 基础语法 1-1 编码 默认情况下&#xff0c;Python 3 源码文件以 UTF-8 编码&#xff0c;所有字符串都是 unicode 字符串。 当然你也可以为源码文件指…...

通用代码生成器应用场景三,遗留项目反向工程

通用代码生成器应用场景三&#xff0c;遗留项目反向工程 如果您有一个遗留项目&#xff0c;要重新开发&#xff0c;或者源代码遗失&#xff0c;或者需要重新开发&#xff0c;但是希望复用原来的数据&#xff0c;并加快开发。 如果您的项目是通用代码生成器生成的&#xff0c;…...

轻量级动态可监控线程池 - DynamicTp

一、背景介绍 使用线程池ThreadPoolExecutor的过程中你是否有以下痛点呢&#xff1f; 代码中创建了一个 ThreadPoolExecutor&#xff0c;但是不知道那几个核心参数设置多少比较合适凭经验设置参数值&#xff0c;上线后发现需要调整&#xff0c;改代码重新发布服务&#xff0c…...

对于vsc中的vue命令 vue.json

打开vsc 然后在左下角有一个设置 2.点击用户代码片段 3.输入 vue.json回车 将此代码粘贴 &#xff08;我的不一定都适合&#xff09; { "vue2 template": { "prefix": "v2", "body": [ "<template>", " <…...

Spring Boot 官方不再支持 Spring Boot 的 2.x 版本!新idea如何创建java8项目

idea现在只能创建最少jdk17 使用 IDEA 内置的 Spring Initializr 创建 Spring Boot 新项目时&#xff0c;没有 Java 8 的选项了&#xff0c;只剩下了 > 17 的版本 是因为 Spring Boot 官方不再支持 Spring Boot 的 2.x 版本了&#xff0c;之后全力维护 3.x&#xff1b;而 …...

分享一个 ASP.NET Web Api 上传和读取 Excel的方案

前言 许多业务场景下需要处理和分析大量的数据&#xff0c;而 Excel 是业务人员常用的数据表格工具&#xff0c;因此&#xff0c;将 Excel 表格中内容上传并读取到网站&#xff0c;是一个很常见的功能&#xff0c;目前有许多成熟的开源或者商业的第三方库&#xff0c;比如 NPO…...

【算法实战】每日一题:将某个序列中内的每个元素都设为相同的值的最短次数(差分数组解法,附概念理解以及实战操作)

题目 将某个序列中内的每个元素都设为相同的值的最短次数 1.差分数组&#xff08;后面的减去前面的值存储的位置可以理解为中间&#xff09; 差分数组用于处理序列中的区间更新和查询问题。它存储序列中相邻元素之间的差值&#xff0c;而不是直接存储每个元素的值 怎么对某…...

EXCEL数据透视图中的日期字段,怎样自动分出年、季度、月的功能?

在excel里&#xff0c;这个果然是有个设置的地方&#xff0c;修改后就好了。 点击文件选项卡&#xff0c;选项&#xff0c;在高级里&#xff0c;将图示选项的勾选给取消&#xff0c;然后再创建数据透视表或透视图&#xff0c;日期就不会自动组合了&#xff1a; 这个选项只对新…...

【设计模式深度剖析】【1】【行为型】【模板方法模式】| 以烹饪过程为例加深理解

&#x1f448;️上一篇:结构型设计模式对比 文章目录 模板方法模式定义英文原话直译如何理解呢&#xff1f; 2个角色类图代码示例 应用优点缺点使用场景 示例解析&#xff1a;以烹饪过程为例类图代码示例 模板方法模式 模板方法模式&#xff08;Template Method Pattern&…...

JAVA:异步任务处理类CompletableFuture让性能提升一倍

一、前言 CompletableFuture 是 Java 8 引入的一个功能强大的类&#xff0c;用于异步编程。它表示一个可能尚未完成的计算的结果&#xff0c;你可以对其添加回调函数来在计算完成时执行某些操作。在 Spring Boot 应用中&#xff0c;CompletableFuture 可以用于提高应用的响应性…...

10Linux 进程管理学习笔记

Linux 进程管理 目录 文章目录 Linux 进程管理一.进程1.显示当前进程状态(ps)进程树(pstree)1.1实时显示进程信息(top)顶部概览信息&#xff1a;CPU 状态&#xff1a;内存状态&#xff1a;进程信息表头&#xff1a;进程列表&#xff1a;1.2(htop) 2.终止进程(kill)2.1通过名称…...

一些关于深度聚类以及部分对比学习的论文阅读笔记

目录 资料SwAV问题方法方法的创新点为什么有效有什么可以借鉴的地方聚类Multi-crop 代码 PCL代码 Feature Alignment and Uniformity for Test Time Adaptation代码 SimSiam 资料 深度聚类算法研究综述(很赞&#xff0c;从聚类方法和深度学习方法两个方面进行了总结&#xff0…...

【ARM-Linux篇】u-boot编译

一、u-boot简介 uboot是一种通用的引导加载程序&#xff0c;它可以用于多种嵌入式系统&#xff0c;支持多种操作系统&#xff0c;如Linux, Android,NetBSD等。uboot的主要作用是将操作系统内核从存储设备&#xff08;如Flash, SD卡等&#xff09;加载到内存中&#xff0c;并执…...

Lombok一文通

1、Lombok简介 作为java的忠实粉丝&#xff0c;但也不得不承认&#xff0c;java是一门比较啰嗦的语言&#xff0c;很多代码的编写远不如其他静态语言方便&#xff0c;更别说跟脚本语言比较了。 因此&#xff0c;lombok应运而生。 Lombok是一种工具库&#xff0c;它提供了一组…...

Seq2Seq模型:详述其发展历程、深远影响与结构深度剖析

Seq2Seq&#xff08;Sequence-to-Sequence&#xff09;模型是一种深度学习架构&#xff0c;专为处理从一个输入序列到一个输出序列的映射任务设计。这种模型最初应用于机器翻译任务&#xff0c;但因其灵活性和有效性&#xff0c;现已被广泛应用于自然语言处理&#xff08;NLP&a…...

公网如何访问内网?

公网和内网已经成为我们生活中不可或缺的存在。由于内网的安全性考虑&#xff0c;公网无法直接访问内网资源。如何实现公网访问内网呢&#xff1f;本文将介绍一种名为【天联】的私有通道技术&#xff0c;通过安全加密&#xff0c;保障数据传输的安全性。 【天联】私有通道技术 …...

手机定制开发_基于天玑900的5G安卓手机定制方案

手机定制方案基于联发科天玑900强劲旗舰八核2.4GHz处理器。这款处理器采用了6nm先进制程工艺&#xff0c;为用户带来了痛快淋漓的性能体验。不论是进行游戏还是日常娱乐&#xff0c;用户都能轻松驾驭。手机搭载了最新的Android 13操作系统&#xff0c;提高了数据读取的准确性&a…...

免费,C++蓝桥杯等级考试真题--第2级

C蓝桥杯等级考试真题–第2级...

panic 、asset、crash 的含义和区别

在编程中&#xff0c;“panic” 和 “assert” 都是用于处理错误和异常情况的机制&#xff0c;但在不同的编程语言和框架中有一些区别。 panic&#xff1a; 含义&#xff1a;通常表示程序发生了无法恢复的错误或异常情况&#xff0c;需要立即终止程序的执行。 用法&#xff1…...

解决Windows 10通过SSH连接Ubuntu 20.04时的“Permission Denied”错误

在使用SSH连接远程服务器时&#xff0c;我们经常可能遇到各种连接错误&#xff0c;其中“Permission denied, please try again”是较为常见的一种。本文将分享一次实际案例的解决过程&#xff0c;帮助你理解如何排查并解决这类问题。 问题描述 在尝试从Windows 10系统通过SS…...

Windows 下 PostgreSQL 图形化界面安装、配置详解

相信大家对PostgreSQL都不陌生吧&#xff0c;自从MySQL被Oracle所控制后&#xff0c;PostgreSQL就成为了国内去O的首选数据库了&#xff0c;并且PostgreSQL目前不受任何商业公司控制&#xff0c;所以国内很多厂商都是基于PostgreSQL做二次开发来实现数据库自主可控的目标(国内很…...