基于知识图谱的问答系统(下)
基于知识图谱的问答系统(下)
本项目是基于知识图谱的问答系统,BERT/BILSTM+CRF做命名实体识别和句子相似度比较,最后实现线上的部署。
项目的分以下步骤进行描述:
- 1-项目介绍
- 1.1-数据集介绍
- 1.2-技术方案流程
- 1.3-评价标准 - 2-命名实体识别模型训练代码理解
- 2.1-数据集准备
- 2.2-数据预处理
- 2.3-定义主模型 - 3-BERT-BILSTM-CRF原理及使用
- 3.1-BERT原理介绍
- 3.2-BERT+CRF模型
- 3.3-训练结果
- 3.4-预测 - 4-属性映射模型原理和训练代码
- 4.1-数据集处理
- 4.2-BERT分类模型训练 - 5-知识库导入数据库,并抽取三元组
- 5.1-知识库导入数据库
- 5.2-返回分数最高的三元组
- 5.3-预测 - 6-总结
- 7-参考
1-项目介绍
1.1-数据集介绍
NLPCC全称自然语言处理与中文计算会议(The Conference on Natural Language Processing and Chinese Computing),它是由中国计算机学会(CCF)主办的 CCF 中文信息技术专业委员会年度学术会议,专注于自然语言处理及中文计算领域的学术和应用创新。
此次使用的数据集来自NLPCC ICCPOL 2016 KBQA 任务集,其包含 14 609 个问答对的训练集和包含 9 870 个问答对的测试集。 并提供一个知识库,包含 6 502 738 个实体、 587 875 个属性以及 43 063 796 个 三元组。知识库文件中每行存储一个事实( fact) ,即三元组 ( 实体、属性、属性值) 。各文件统计如下:
训练集:14609 开发集:9870 知识库:43063796
知识库样里如下:
空气干燥 ||| 别名 ||| 空气干燥 空气干燥 ||| 中文名 ||| 空气干燥 空气干燥 ||| 外文名 ||| air drying 空气干燥 ||| 形式 ||| 两个 空气干燥 ||| 作用 ||| 将空气中的水份去除
原数据中本只有问答对(question-answer),并无标注三元组(triple),本人所用问答对数据来自该比赛第一名的预处理https://github.com/huangxiangzhou/NLPCC2016KBQA。构造Triple的方法为从知识库中反向查找答案,根据问题过滤实体,最终筛选得到,也会存在少量噪音数据。该Triple之后用于构建实体识别和属性选择等任务的数据集。
<question id=1> 《机械设计基础》这本书的作者是谁? <triple id=1> 机械设计基础 ||| 作者 ||| 杨可桢,程光蕴,李仲生 <answer id=1> 杨可桢,程光蕴,李仲生 ================================================== <question id=2> 《高等数学》是哪个出版社出版的? <triple id=2> 高等数学 ||| 出版社 ||| 武汉大学出版社 <answer id=2> 武汉大学出版社 ================================================== <question id=3> 《线性代数》这本书的出版时间是什么? <triple id=3> 线性代数 ||| 出版时间 ||| 2013-12-30 <answer id=3> 2013-12-30 ==================================================
1.2-技术方案流程
自己复现的代码:https://blog.csdn.net/weixin_46133588/article/details/KBQA-BERT-CRF
- 1 输入问句
- 2 通过实体识别模型检测问句中的实体,得到实体(BERT+LSTM+CRF)
- 3 在知识库中检索实体,得到和实体相关的前n个三元组。
- 4 通过属性抽取模型在n个三元组中挑选最合适的属性,得到唯⼀三元组 (BERT)
- 5 输出答案
第一步:主要模型构建
基于知识图谱的⾃动问答主要的模型主要有两个:
命名实体识别步骤和属性映射步骤。(就是上述流程的第2、4步)
其中,实体识别步骤的⽬的是找到问句中询问的实体名称,如下图,就是找到“⽯头记”这个实体。
⽽属性映射步骤的⽬的在于找到问句中询问的相关属性,如下图,就是找到这句话问的是“作者”这个属
性。
命名实体识别步骤,采⽤BERT+BiLSTM+CRF⽅法(另外加上⼀些规则映射,可以提⾼覆盖度)。
属性映射步骤,转换成⽂本相似度问题,采⽤BERT作⼆分类训练模型。
第二步:通过实体识别模型检测问句中的实体,得到实体
输⼊任意⼀个问句,⽤上⼀步训练好的命名实体识别模型进⾏预测。
本项⽬⽀持在线预测,即可以⾃⼰输⼊⼀句话,然后进⾏预测,得到实体,预测完后,可以再次输⼊。
第三步:在知识库中检索实体,得到和实体相关的前n个三元组。
完成实体识别模型和属性抽取模型后还需进⾏知识库检索。
由于知识库数据量庞⼤,不可能加载到缓存中遍历检索知识库。所以可以导⼊到mysql数据库中,然后
按上步预测的三元组的实体进⾏检索,得到和实体相关的前n个三元组。
第四步:通过属性抽取模型在n个三元组中挑选最合适的属性,得到唯⼀三元组。
这步采⽤了两种⽅式来返回答案。
- ⾮语义匹配:如果前n个三元组有某个三元组的属性是输⼊问题字符串的⼦集(相当于字符串匹配),将
这个三元组的答案作为正确答案返回。 - 语义匹配:⾮语义匹配失败后,可以再利⽤bert对前n个三元组的属性进⾏打分,排序,输出topK个
答案,选择分数最⾼的作为正确答案返回。
1.3-评价标准
命名实体识别和属性映射评价标准:召回率 (Recall),精确率 (Precision) ,F1-Score。⽽对话系统的评价标准以⼈⼯评价为主,以及BLEU和Perplexity。
2-命名实体识别模型训练代码理解
2.1-数据预处理
清洗训练数据、测试数据、知识库过滤属性,去除‘-’,‘•’,空格等噪音符号;同时把每一行lower()转成小写。
https://github.com/huangxiangzhou/NLPCC2016KBQA
2.2-数据集准备
构造NER的数据集,我们的数据是有三元组实体信息的,可以反向标注问题,给训练集和测试集的
Question 打标签。我们这⾥采⽤BIO的标注⽅式,因为识别⼈名,地名,机构名的任务不是主要的,我
们只要识别出实体就可以了,因此,我们⽤B-LOC, I-LOC代替其他的标注类型,如下图,这⾥B是句
⾸,I是句中,O是⾮实体。
构造SIM的数据集:一个sample由“问题+属性+Label”构成,原始数据中的属性值置为1
0 你知道计算机应用基础这本书的作者是谁吗? 作者 1 1 你知道计算机应用基础这本书的作者是谁吗? 稀有程度 0 2 你知道计算机应用基础这本书的作者是谁吗? 已离开成员 0 3 你知道计算机应用基础这本书的作者是谁吗? 界面语言 0 4 你知道计算机应用基础这本书的作者是谁吗? 跨度 0 5 你知道计算机应用基础这本书的作者是谁吗? 英文名称 0 6 计算机应用基础这本书的出版社是那个? 出版社 1 7 计算机应用基础这本书的出版社是那个? 投资 0 8 计算机应用基础这本书的出版社是那个? 罗马拼音 0 9 计算机应用基础这本书的出版社是那个? 出身 0 10 计算机应用基础这本书的出版社是那个? 出生时间 0 ......
构造数据库的数据:实体+属性+答案
entity,attribute,answer 机械设计基础,作者,杨可桢,程光蕴,李仲生 高等数学,出版社,武汉大学出版社 线性代数,出版时间,2013-12-30 安德烈,国籍,摩纳哥 线性代数,isbn,978-7-111-36843-4 高等数学,书名,高等数学一(微积分) 万达广场,外文名,amoywandaplaza 李明,出生日期,1963.1 韩娱守护力,小说进度,连载 夏想,连载网站,潇湘书院 大学计算机基础,页数,272 城关镇,中文名,城关镇[临澧县] 李明,出生地,青海湟源 大学计算机基础,定价,25元
其中遇到的问题:
未完全匹配的实体样例:部分是识别错误,部分是同义词,部分是噪音
【识别错误】动物地鸠属是属于什么目呀? 地鸠属 动物地鸠 【同义词】《鲁迅全集》这本书是什么时候出版的呀? 《鲁迅全集》 鲁迅全集 【同义词】我想知道永安镇机场叫什么名字? 永安镇 永安镇机场 【同义词】海尔kfrd-35gw/03me-s4空调是什么类型的? 海尔kfrd-35gw/03me-s4 海尔kfrd-35gw/03me-s4空调 【问题噪音】你知道辛夷花粥管治疗什么吗? 辛夷花粥 辛夷花粥管
那么这少部分数据过滤掉。
最后生成插入到数据库中的数据:
以及:
上面展示的数据中:实体部分有很多不合理的:比如“2011第十二届中国(青岛)国际礼品工艺品及家居品博览会” ,有些属性也是过于长的。
然后通过数据的相关处理,输入到模型中的数据如下:
2.3-定义主模型
class BertCrf(nn.Module): def __init__(self,config_name:str,model_name:str = None,num_tags: int = 2, batch_first:bool = True) -> None: self.batch_first = batch_first if not os.path.exists(config_name): raise ValueError( "未找到模型配置文件 '{}'".format(config_name) ) else: self.config_name = config_name if model_name is not None: if not os.path.exists(model_name): raise ValueError( "未找到模型预训练参数文件 '{}'".format(model_name) ) else: self.model_name = model_name else: self.model_name = None super().__init__() self.bert_config = BertConfig.from_pretrained(self.config_name) self.bert_config.num_labels = num_tags self.model_kwargs = {'config': self.bert_config} # 如果模型不存在 if self.model_name is not None: self.bertModel = BertForTokenClassification.from_pretrained(self.model_name, **self.model_kwargs) else: self.bertModel = BertForTokenClassification(self.bert_config) # 调用这里 self.crf_model = CRF(num_tags=num_tags,batch_first=batch_first) def forward(self,input_ids:torch.Tensor, tags:torch.Tensor = None, attention_mask:Optional[torch.ByteTensor] = None, token_type_ids=torch.Tensor, decode:bool = True, # 是否预测编码 reduction: str = "mean") -> List: emissions = self.bertModel(input_ids = input_ids,attention_mask = attention_mask,token_type_ids=token_type_ids)[0] new_emissions = emissions[:,1:-1] # [seq,batchs,3] seq = 64-2 =62 [32,62,3] new_mask = attention_mask[:,2:] # [32,62] if tags is None: loss = None pass else: new_tags = tags[:, 1:-1] # shape= [32.62] loss = self.crf_model(emissions=new_emissions, tags=new_tags, mask=new_mask, reduction=reduction) # [1] if decode: tag_list = self.crf_model.decode(emissions = new_emissions,mask = new_mask) return [loss, tag_list] return [loss]
定义优化器和训练策略
no_decay = ['bias', 'LayerNorm.weight','transitions'] optimizer_grouped_parameters = [ {'params': [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': args.weight_decay}, {'params': [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0} ] optimizer = AdamW(optimizer_grouped_parameters,lr=args.learning_rate,eps=args.adam_epsilon) scheduler = WarmupLinearSchedule(optimizer, warmup_steps=args.warmup_steps, t_total=t_total)
3-BERT-BILSTM-CRF原理及使用
3.1-BERT原理介绍
参考论文BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
很多博客都有详细的介绍,这里就不多描述。
3.2-BERT+CRF模型
但是本项⽬的输⼊和输出有点不同,输⼊为分类符[CLS]+句⼦Token+结尾符[SEP],输出也是[CLS]+句
⼦Token+[SEP]对应的最后⼀层的向量,维度为[batch_sizes,seq_len,hidden_sizes]。
我参考的博客:
pytorch BiLSTM+CRF代码详解
pytorch中bilstm-crf部分code解析
最通俗易懂的BiLSTM-CRF模型中的CRF层介绍
Bidirectional LSTM-CRF Models for Sequence Tagging(论文翻译)
pytorch lstm crf 代码理解
我的代码:BERT_CRF.py / CRF_Model.py
- 模型的输入:
global_step = 0 tr_loss, logging_loss = 0.0, 0.0 model.zero_grad() train_iterator = trange(int(args.num_train_epochs), desc="Epoch") set_seed(args) best_f1 = 0. for _ in train_iterator: epoch_iterator = tqdm(train_dataloader, desc="Iteration") for step,batch in enumerate(epoch_iterator): batch = tuple(t.to(args.device) for t in batch) inputs = {'input_ids':batch[0], 'attention_mask':batch[1], 'token_type_ids':batch[2], 'tags':batch[3], 'decode':True }
- 经过BERT
得到单词的发射概率。
# 发射概率,每个单词wi->tagj的概率 # emissions.shape [bs,sl,num_labels] emissions = self.bertModel(input_ids = input_ids,attention_mask = attention_mask,token_type_ids=token_type_ids)[0]
- 经过CRF层
上节的发射矩阵输出作为CRF层的输⼊,可以得到总损失loss和状态转移矩阵trans[num_labels,num_labels]。
loss可以⽤来训练,状态转移矩阵trans可以⽤来预测。
class CRF(nn.Module): def __init__(self,num_tags : int = 2, batch_first:bool = True) -> None: super().__init__() self.num_tags = num_tags # 3 self.batch_first = batch_first self.start_transitions = nn.Parameter(torch.empty(num_tags)) self.end_transitions = nn.Parameter(torch.empty(num_tags)) self.transitions = nn.Parameter(torch.empty(num_tags,num_tags)) self.reset_parameters() def reset_parameters(self): init_range = 0.1 nn.init.uniform_(self.start_transitions,-init_range,init_range) nn.init.uniform_(self.end_transitions,-init_range,init_range) nn.init.uniform_(self.transitions, -init_range, init_range) def forward(self, emissions:torch.Tensor, tags:torch.Tensor = None, mask:Optional[torch.ByteTensor] = None, reduction: str = "mean") -> torch.Tensor: mask=torch.tensor(mask,dtype=torch.uint8) self._validate(emissions, tags = tags ,mask = mask) reduction = reduction.lower() # if reduction not in ('none','sum','mean','token_mean'): # raise ValueError(f'invalid reduction {reduction}') if mask is None: mask = torch.ones_like(tags,dtype = torch.uint8) if self.batch_first: # emissions.shape (seq_len,batch_size,tag_num) emissions = emissions.transpose(0,1) tags = tags.transpose(0,1) mask = mask.transpose(0,1) # numerator shape: (batch_size,) [32] numerator = self._computer_score(emissions=emissions,tags=tags,mask=mask) # 计算s(X,y) # shape: (batch_size,) [32] denominator = self._compute_normalizer(emissions=emissions,mask=mask) # 计算 log(Σe^(x,y')) # log(Σe^(S(X,y))) - S(X,y) llh = denominator - numerator # [batch_size] 32 if reduction == 'none': return llh elif reduction == 'sum': return llh.sum() elif reduction == 'mean': return llh.mean() assert reduction == 'token_mean' return llh.sum() / mask.float().sum() def decode(self,emissions:torch.Tensor, mask : Optional[torch.ByteTensor] = None) ->List[List[int]]: self._validate(emissions=emissions,mask=mask) if mask is None: mask = emissions.new_ones(emissions.shape[:2],dtype=torch.uint8) if self.batch_first: emissions = emissions.transpose(0,1) # [62,32,3] mask = mask.transpose(0,1) # [62,32] return self._viterbi_decode(emissions,mask)
-
模型分析
总的来说,⽤单独的CRF和单独的BiLSTM都可以做命名实体识别,以下是两个模型的⼀些区别。
LSTM:像 RNN、 LSTM、BILSTM 这些模型,它们在序列建模上很强⼤,它们能够捕捉⻓远的上下⽂信息,此外还具备神经⽹络拟合⾮线性的能⼒,这些都是 CRF ⽆法超越的地⽅,对于t时刻来说,输出层y_t受到隐层h_t(包含上下⽂信息)和输⼊层x_t (当前的输⼊)的影响,但是y_t和其他时刻的y_t是相互独⽴的,感觉像是⼀种point wise,对当前t时刻来说,我们希望找到⼀个概率最⼤的y_t,但其他时刻的y_t对当前y_t没有影响,如果y_t之间存在较强的依赖关系的话(例如,形容词后⾯⼀般接名词,存在⼀定的约束),LSTM⽆法对这些约束进⾏建模,LSTM模型的性能将受到限制。CRF:它不像LSTM等模型,能够考虑⻓远的上下⽂信息,它更多考虑的是整个句⼦的局部特征的线性加权组合(通过特征模版去扫描整个句⼦)。关键的⼀点是,CRF的模型为p(y|x,w) ,注意这⾥y和x都是序列,它有点像list wise,优化的是⼀个序列y=(y1,y2,…,yn),⽽不是某个时刻的y_t,即找到⼀个概率最⾼的序列y=(y1,y2,…,yn)使得p(y1,y2,…,yn|x,w)最⾼,它计算的是⼀种联合概率,优化的是整个序列(最终⽬标),⽽不是将每个时刻的最优拼接起来,在这⼀点上CRF要优于LSTM。
HMM:CRF不管是在实践还是理论上都要优于HMM,HMM模型的参数主要是“初始的状态分布”,“状态之间的概率转移阵”,“状态到观测的概率转移矩阵”,这些信息在CRF中都可以有,例如:在特征模版中考虑h(y1) , f(yi-1,yi),g(yi,xi) 等特征。
CRF与LSTM:从数据规模来说,在数据规模较⼩时,CRF的试验效果要略优于BILSTM,当数据规模较⼤时,BILSTM的效果应该会超过CRF。从场景来说,如果需要识别的任务不需要太依赖⻓久的信息,此时RNN等模型只会增加额外的复杂度,此时可以考虑类似科⼤讯⻜FSMN(⼀种基于窗⼝考虑上下⽂信息的“前馈”⽹络)。
CNN+BILSTM+CRF:这是⽬前学术界⽐较流⾏的做法,BILSTM+CRF是为了结合以上两个模型的优点,CNN主要是处理英⽂的情况,英⽂单词是由更细粒度的字⺟组成,这些字⺟潜藏着⼀些特征(例如:前缀后缀特征),通过CNN的卷积操作提取这些特征,在中⽂中可能并不适⽤(中⽂单字⽆法分解,除⾮是基于分词后),这⾥简单举⼀个例⼦,例如词性标注场景,单词football与basketball被标为名词的概率较⾼, 这⾥后缀ball就是类似这种特征。
BERT+CRF:其实在整个项目中变动的是,emissions的不同,每个单词到不同tag的发射概率。BERT在特征抽取和上下文语义的理解上来说,是由于BILSTM的。但时间上是没有它快的。
3.3-训练结果
- NER训练集:
训练集数量:13637
总的epoch:15
batch_sizes:16
累计更新梯度step:4 (相当于64的batch_size)
总的step:6392
运⾏环境:GPU
训练过程如下,最后loss的范围在14左右,可以看到,在3000个step左右,loss降的差不多了。
- NER测试集:
训练集数量:9016
batch_sizes:8
运⾏环境:GPU
评估指标:这⾥的测试集的评估指标评估的是真实标签值id和预测标签id的差别。
# 计算真实句子的长度,返回真实句子长度的list # batch[3]真实的命名实体识别,batch[1]mask def statistical_real_sentences(input_ids:torch.Tensor,mask:torch.Tensor,predict:list)-> list: # shape (batch_size,max_len) assert input_ids.shape == mask.shape # batch_size assert input_ids.shape[0] == len(predict) # 第0位是[CLS] 最后一位是<pad> 或者 [SEP] new_ids = input_ids[:,1:-1] # [batch_size, seq_len-2] new_mask = mask[:,2:] real_ids = [] for i in range(new_ids.shape[0]): seq_len = new_mask[i].sum() # 计算每句话有多少个字 assert seq_len == len(predict[i]) real_ids.append(new_ids[i][:seq_len].tolist()) return real_ids
precision recall f1-score support 0 0.989159 0.983189 0.986165 47053 1 0.977180 0.975417 0.976298 4434 2 0.965636 0.977964 0.971761 22872 accuracy 0.981119 74359 macro avg 0.977325 0.978857 0.978075 74359 weighted avg 0.981210 0.981119 0.981146 74359
准确率很高,98.11%
3.4-离线预测
运行脚本python test_pro.py
运行中间变量展示:
question entity answer predict 你知道计算机应⽤基础这本书的作者是谁吗? 计算机应⽤基础 作者 秦婉,王蓉 计算机应⽤基础 计算机应⽤基础这本书的出版社是那个? 计算机应⽤基础 出版社 机械⼯业出版社 计算机应⽤基础 告诉我⾼等数学的出版时间是什么时候? ⾼等数学 出版时间 2004年 ⾼等数学 我想知道戴维斯是什么国家的⼈? 戴维斯 国籍 美国 戴维斯 你知道⾼等数学的isbn吗? ⾼等数学 isbn 9.7873E+12 ⾼等数学 你知道mc的外⽂名是什么吗? mc 外⽂名 emcee mc 王平的出⽣⽇期是哪⼀年呀? 王平 出⽣⽇期 1954年 王平 彼岸之界⼩说进度到哪⾥了? 彼岸之界 ⼩说进度 情节展开 彼岸之界 告诉我剑魔传的连载⽹站是哪个? 剑魔传 连载⽹站 天涯读书 剑魔传 .............
最终结果如下:基本都预测正确。
input the test sentence: 中华⼈⺠共和国成⽴于什么时候? # 输⼊的句⼦ [['B-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O']]# 预测的实体结果 识别的实体有:中华⼈⺠共和国 time used: 1 sec input the test sentence: 统计学习⽅法的作者是谁? [['B-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'O', 'O', 'O', 'O', 'O', 'O']] 识别的实体有:统计学习⽅法 time used: 0 sec input the test sentence: 机器学习这本书是哪个出版社出版的? [['B-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O']] 识别的实体有:机器学习 time used: 0 sec input the test sentence: 统计学习⽅法和机器学习哪本书⽐较好? # 这⾥的两个实体之间的 “和” 字没有预测到 [['B-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'ILOC', 'I-LOC', 'I-LOC', 'O', 'O', 'O', 'O', 'O', 'O', 'O']] 识别的实体有:统计学习⽅法和机器学习 time used: 0 sec input the test sentence: ⽯头记和红楼梦是同⼀本书嘛? [['B-LOC', 'I-LOC', 'I-LOC', 'O', 'I-LOC', 'I-LOC', 'I-LOC', 'O', 'O', 'O', 'O', 'O', 'O', 'O']] 识别的实体有:⽯头记 红楼梦 time used: 0 sec input the test sentence: .............
4-属性映射模型原理和训练代码
4.1-数据集处理
输入模型的数据格式处理代码:
def sim_convert_examples_to_features(examples,tokenizer, max_length=512, label_list=None, pad_token=0, pad_token_segment_id = 0, mask_padding_with_zero = True): label_map = {label: i for i, label in enumerate(label_list)} features = [] for (ex_index, example) in enumerate(examples): # inputs.keys = ['special_tokens_mask':, 'input_ids', 'token_type_ids'] # special_tokens_mask : 1 00000 1 00 1 1表示CLS或者SEP,前面的零表示问题,后面的零表示正文 # input_ids : 101 问题的idx 102 属性 102 # token_type_ids : 0000 111 其中111表示答案所在的位置 inputs = tokenizer.encode_plus( text = example.question, # 问题 text_pair= example.attribute, # 属性 add_special_tokens=True, max_length=max_length, truncation_strategy='longest_first', # We're truncating the first sequence in priority if True ) input_ids, token_type_ids = inputs["input_ids"], inputs["token_type_ids"] attention_mask = [1 if mask_padding_with_zero else 0] * len(input_ids) padding_length = max_length - len(input_ids) input_ids = input_ids + ([pad_token] * padding_length) attention_mask = attention_mask + ([0 if mask_padding_with_zero else 1] * padding_length) token_type_ids = token_type_ids + ([pad_token_segment_id] * padding_length) assert len(input_ids) == max_length, "Error with input length {} vs {}".format(len(input_ids), max_length) assert len(attention_mask) == max_length, "Error with input length {} vs {}".format(len(attention_mask),max_length) assert len(token_type_ids) == max_length, "Error with input length {} vs {}".format(len(token_type_ids),max_length) # label = label_map[example.label] label = example.label if ex_index < 5: logger.info("*** Example ***") logger.info("guid: %s" % (example.guid)) logger.info("input_ids: %s" % " ".join([str(x) for x in input_ids])) logger.info("attention_mask: %s" % " ".join([str(x) for x in attention_mask])) logger.info("token_type_ids: %s" % " ".join([str(x) for x in token_type_ids])) logger.info("label: %s " % str(label)) features.append( SimInputFeatures(input_ids,attention_mask,token_type_ids,label) ) return features
训练代码就是:BERT分类fine-tine时取出的是BERT模型最后⼀层的CLS隐藏层向量model.get_pooled_output()。然后
在经过⼀层全连接层,就可以输出最终的类别输出概率了。
这部分内容的意思是:我给出一个问题,然后BERT+CRF识别,提取出我的问题中的实体,然后在数据库中找到这个实体,当然这个实体有可能有很多的 属性和答案。那么我们用bert来计算我的问题和哪些属性最相关,然后返回最相关的属性回来。并与正确答案属性ID进行匹配。统计正确率。
model = BertForSequenceClassification.from_pretrained(args.pre_train_model, **model_kwargs)
4.2-BERT分类模型训练
模型迭代的过程如下图所示:以下是3个epoch,总共7000个step的loss图,可以看到BERT⽐较粗暴,不到⼀个epoch,基本上已经
收敛了。
- 相似度匹配训练集、测试集和验证集结果:
test loss 0.0680166557431221 question_acc 0.8998987078666687 label_acc 0.9826409816741943 dev loss 0.026128364726901054 question_acc 0.9572441577911377 label_acc 0.9926713705062866 train loss 0.01614166982471943 question_acc 0.9722089171409607 label_acc 0.9953110814094543
5-知识库导入数据库,并抽取三元组
5.1-知识库导入数据库
这个章节我们主要解决项⽬介绍结束⽅案流程的第三步:在知识库中检索实体,得到和实体相关的前n个三元组。完成实体识别模型和属性抽取模型后还需进⾏知识库检索。知识库进⾏检索⽅式不可能⽤普通的for循环进⾏检索,所以这⾥直接把2个多G的⽂件nlpcc-iccpol-2016-total.kbqa.kb,导⼊mysql数据库中,有4千多万⾏,本地导⼊⼤概7个多⼩时,建议使⽤GPU服务器,我这边使⽤的本地数据库。导⼊数据库前需要下载安装mysql,具体安装⽅式根据⾃⼰的操作系统下载安装。安装完后,运⾏如下命令进⼊mysql数据库中。
mysql -u root -p
- 创建database
create database KB_QA character set utf8mb4 collate utf8mb4_unicode_ci; use KB_QA;
- 安装mysql
新建MYSQL的链接。
填入对应的密码和信息。双击链接。 - 运行
python test_pro.py
5.2-返回分数最高的三元组
抽取三元组有两种⽅式。
- 非语义匹配
如果前n个三元组有某个三元组的属性是输⼊问题字符串的⼦集(相当于字符串匹配),将这个三元组的答
案作为正确答案返回,这个检索速度超级快。 - 语义匹配
⾮语义匹配失败后,可以再调⽤训练好的Bert属性映射模型,对前n个三元组的属性进⾏打分,排序,
输出topK个答案,选择分数最⾼的作为正确答案返回。
5.3-预测
# 测试第⼀个句⼦ ------------------------------ input the test sentence: 统计学习⽅法的作者是谁? your input is:['统', '计', '学', '习', '⽅', '法', '的', '作', '者', '是', '谁', '?'] [['B-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'O', 'O', 'O', 'O', 'O', 'O']] LOC: 统计学习⽅法 模型识别的实体有:统计学习⽅法 ------------------------------ 返回结果如下: # ⾮语义匹配,速度很快 提出的问题: 统计学习⽅法的作者是谁? 模型识别的实体+属性:统计学习⽅法+作者 知识库中得到的答案: 李航 Time used: 1 sec # 测试第⼆个句⼦ ------------------------------ input the test sentence: 中华⼈⺠共和国成⽴时间? your input is:['中', '华', '⼈', '⺠', '共', '和', '国', '成', '⽴', '时', '间', '?'] [['B-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'O', 'O', 'O', 'O', 'O']] LOC: 中华⼈⺠共和国 模型识别的实体有:中华⼈⺠共和国 ------------------------------------ # 可以看到中华⼈⺠共和国在知识库中的属性有下⾯这么多 # 属性没有输⼊问题的“成⽴时间”,⾮语义匹配不成功,转到BERT语义匹配 建国时间 0.9826155 国庆⽇ 0.91526103 国庆节 0.067031346 政体 0.013450251 国⼟⾯积 0.007624702 国旗 0.006048701 国⻦ 0.0048175533 国歌 0.003324484 国花 0.002280873 时区 0.0021266777 .................. 最⾼⽴法机构 8.25461e-05 主要节⽇ 8.20243e-05 英⽂名 8.030466e-05 主要宗教 7.857168e-05 官⽅语⾔ 7.641088e-05 货币 7.606106e-05 主要戏曲 7.542803e-05 著名⾼校 7.509416e-05 基尼系数 7.4889205e-05 ⾼等学府 7.221342e-05 国家代码 7.152084e-05 返回结果如下: 提出的问题: 中华⼈⺠共和国成⽴时间? 模型识别的实体+属性:中华⼈⺠共和国+建国时间 知识库中得到的答案: 1949年10⽉1⽇ Time used: 13 sec # 在本地运⾏,有点慢,建议到GPU运⾏,会快很多。 # 第三个句⼦ ------------------------------ 平安大厦的地址在哪? your input is:['平', '安', '大', '厦', '的', '地', '址', '在', '哪', '?'] [['B-LOC', 'I-LOC', 'I-LOC', 'I-LOC', 'O', 'O', 'O', 'O', 'O', 'O']] LOC: 平安大厦 模型识别的实体有:平安大厦 # 知识库中没有平安大厦这个实体 ------------------------------ 返回结果如下: 知识库中没有此实体 input the test sentence:
6-总结
其实用question和attribute进行一个相似度计算做排序是有缺陷的,毕竟question的句子明显更长,语义明显比attribute更丰富,单拿attribute进行匹配有种断章取义的感觉,所以不提倡,但是也适用。
本项⽬的检索⽅式为完全匹配,⽐如《中国的⾸都在哪?》,假如我们的数据库中没有《中国》这个实体,但是有《中华⼈⺠共和国 ||| ⾸都 ||| 北京》这个三元组,我们可以想办法让数据库检索《中国》时,能同时检索到《中华⼈⺠共和国》作为候选三元组返回。⼀个好的想法是检索包含《中国》的所有实体,但是这样检索数据库时会需要遍历数据库,⾮常耗时间。
- TO DO
知识库问答⾮常适合前后端部署,效果看起来会很不错。
7-参考:
https://zhuanlan.zhihu.com/p/62946533
https://github.com/WenRichard/KBQA-BERT?tdsourcetag=s_pctim_aiomsg
https://www.zhihu.com/question/46688107?sort=created
https://www.jiqizhixin.com/articles/2019-01-09-17
https://blog.csdn.net/Jason__Liang/article/details/81772632
https://zhuanlan.zhihu.com/p/59845590
https://github.com/jkszw2014/bert-kbqa-NLPCC2017
https://github.com/WenRichard/KBQA-BERT?tdsourcetag=s_pctim_aiomsg
https://github.com/zhangyunxing37/knowledge_grapy-kbqa_demo?tdsourcetag=s_pctim_aiomsg
https://github.com/huangxiangzhou/NLPCC2016KBQA
https://zhuanlan.zhihu.com/p/44042528
https://blog.csdn.net/Elvira521yan/article/details/88415512
https://blog.csdn.net/appleml/article/details/78579675
- 点赞 2
- 收藏
- 分享
- 文章举报
- 基于知识图谱的电影自动问答系统(二)自动问答实现
- 基于知识图谱的问答系统(一)
- 基于知识图谱的电影自动问答系统(一)知识的获取与存储
- 基于知识图谱的问答系统(二): 语义解析
- 基于知识图谱的电影自动问答系统(二)自动问答实现
- 基于医疗知识图谱的问答系统源码详解
- 基于知识图谱的问答系统(三): 信息抽取
- 基于电影知识图谱的智能问答系统
- 基于知识图谱的电影自动问答系统(一)知识的获取与存储
- PaperWeekly 第32期 | 基于知识图谱的问答系统关键技术研究 #01
- 基于知识图谱的问答系统(四):向量建模篇
- 怎么利用知识图谱构建智能问答系统?
- 怎么利用知识图谱构建智能问答系统?
- 刘升平 | 基于知识图谱的人机对话系统
- 知性会话:基于知识图谱的人机对话系统方法与实践-CSDN公开课-专题视频课程...
- 知识图谱与问答系统
- 【知识图谱】杨娟:告别传统,基于知识图谱技术构建智能CRM
- 基于CNN的智能问答系统构建
- 论文浅尝 | 基于超平面的时间感知知识图谱嵌入
- 基于Asp.Net Core 2.1的简单问答社区系统源代码分享