您的位置:首页 > 其它

基于知识图谱的问答系统(下)

2020-03-27 19:29 579 查看

基于知识图谱的问答系统(下)

本项目是基于知识图谱的问答系统,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
  • 收藏
  • 分享
  • 文章举报
自助者天助也 发布了4 篇原创文章 · 获赞 2 · 访问量 348 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: