大模型在知識圖譜問答上的核心算法詳細思路及實踐 原創
前言
本文介紹了一個融合RAG(Retrieval-Augmented Generation)思路的KBQA(Knowledge-Based Question Answering)系統的核心算法及實現步驟。KBQA系統的目標是通過自然語言處理技術,從知識圖譜中提取和生成精確的答案。系統的實現包括多個關鍵步驟:mention識別、實體鏈接及排序、屬性選擇及排序、文本拼接以及最終的Text2SQL生成。通過這些步驟,系統能夠準確識別用戶提出的問題中的關鍵實體和屬性,并生成相應的查詢語句,從而從知識圖譜或數據庫中檢索所需的信息。本文將詳細介紹每個步驟的實現思路和技術細節,并提供核心算法具體的代碼示例和開源地址供參考。

總體技術鏈路
一、mention識別
KBQA中的mention識別是指在用戶提出的問題中,識別出與知識庫中的實體或概念相對應的詞語或短語。mention識別是KBQA系統中至關重要的一步,因為準確的mention識別直接影響后續的實體鏈接、關系抽取和答案生成等步驟。KBQA中的mention識別的主要方法和技術:
- 規則方法
基于規則的方法通常使用手工設計的規則和模式來識別mention。這些規則可以包括命名實體識別(NER)工具、正則表達式、詞典匹配等。
- NER工具:使用現有的NER工具(如Stanford NER、spaCy)來識別出問題中的實體。
- 正則表達式:設計特定的正則表達式模式來匹配特定類型的mention。
- 詞典匹配:使用預先構建的實體詞典進行匹配,通過查找詞典中的條目來識別mention。
- 統計方法
基于統計的方法利用大規模的訓練數據,通過統計特征來識別mention。這些方法通常需要預處理步驟,如詞頻統計和n-gram分析。
- n-gram分析:將問題分割成n-gram(如單詞、雙詞、三詞短語等),并計算每個n-gram在知識庫中的匹配情況。
- 詞頻統計:統計每個詞或短語在知識庫中的出現頻率,并根據頻率高低來判斷其是否為mention。
- 機器學習方法
基于機器學習的方法利用有標簽的數據,通過訓練分類器來識別mention。這些方法通常需要特征工程和模型訓練。
- 特征工程:提取文本的各種特征,如詞性、詞向量、上下文信息等。
- 分類模型:使用機器學習算法(如SVM、隨機森林、邏輯回歸等)訓練分類器,判斷一個詞或短語是否為mention。
- 深度學習方法
基于深度學習的方法利用神經網絡模型,通過端到端的方式來識別mention。這些方法可以避免復雜的特征工程,通過大量的數據訓練模型來自動提取特征。如:BERT-CRF、LLM等模型等。
本文結合大模型的方法進行mention識別。主要流程如下:
mention識別SFT數據構造
原始數據
q1:莫妮卡·貝魯奇的代表作?
select ?x where { <莫妮卡·貝魯奇> <代表作品> ?x. }
<西西里的美麗傳說>通過一些規則方式,構建sft數據如下:
[
{
"instruction": "你是一個實體抽取的專家,請你抽取問句:“莫妮卡·貝魯奇的代表作?”中的實體。",
"input": "",
"output": "莫妮卡·貝魯奇"
},
{
"instruction": "你是一個實體抽取的專家,請你抽取問句:“《湖上草》是誰的詩?”中的實體。",
"input": "",
"output": "湖上草"
},
...
]LLM微調mention識別
本文以LLaMA-Factory框架進行微調,微調腳本如下:
import json
import os
model_name_or_path = "ZhipuAI/glm-4-9b-chat"
template = "glm4"
cutoff_len = 256
num_train_epochs = 8
train_dataset = "train_ner"
predict_dataset = "test_ner"
output_dir = f"saves/{train_dataset}-{predict_dataset}-ep{num_train_epochs}-{cutoff_len}-{template}"
adapter_name_or_path = output_dir
do_train = True
do_predict = True
train_args = dict(
stage="sft", # 進行指令監督微調
do_train=do_train,
model_name_or_path=model_name_or_path,
dataset=train_dataset,
template=template,
finetuning_type="lora",
cutoff_len=cutoff_len,
lora_target="all",
output_dir=output_dir,
per_device_train_batch_size=4,
gradient_accumulation_steps=2,
lr_scheduler_type="cosine",
logging_steps=10,
warmup_ratio=0.1,
save_steps=1000,
learning_rate=1e-4,
num_train_epochs=num_train_epochs,
max_samples=7625,
max_grad_norm=1.0,
fp16=True,
temperature=0.1,
ddp_timeout=180000000,
overwrite_cache=True,
overwrite_output_dir=True
)
predict_args = dict(
stage="sft",
do_predict=do_predict,
model_name_or_path=model_name_or_path,
adapter_name_or_path=adapter_name_or_path,
dataset=predict_dataset,
template=template,
finetuning_type="lora",
cutoff_len=cutoff_len,
per_device_eval_batch_size=2,
overwrite_cache=True,
preprocessing_num_workers=16,
output_dir=f'{output_dir}/predict',
overwrite_output_dir=True,
ddp_timeout=180000000,
temperature=0.1,
max_samples=1292,
predict_with_generate=True
)
train_args_file = f"config/{train_dataset}-{predict_dataset}-ep{num_train_epochs}-{cutoff_len}-{template}-train.json"
predict_args_file = f"config/{train_dataset}-{predict_dataset}-ep{num_train_epochs}-{cutoff_len}-{template}-pred.json"
json.dump(train_args, open(train_args_file, "w", encoding="utf-8"), indent=2)
json.dump(predict_args, open(predict_args_file, "w", encoding="utf-8"), indent=2)
if __name__ == '__main__':
os.system(f'llamafactory-cli train {train_args_file}')
os.system(f'llamafactory-cli train {predict_args_file}')輸出示例如:
question:<篝火圓舞曲>的作曲家屬于什么民族?
mention:篝火圓舞曲二、實體鏈接及實體排序
中文短文本的實體鏈指,簡稱 EL(Entity Linking),是NLP、知識圖譜領域的基礎任務之一,即對于給定的一個中文短文本(如搜索 Query、微博、對話內容、文章/視頻/圖片的標題等),EL將其中的實體與給定知識庫中對應的實體進行關聯。
針對中文短文本的實體鏈指存在很大的挑戰,主要原因如下:
- 口語化嚴重,導致實體歧義消解困難;
- 短文本上下文語境不豐富,須對上下文語境進行精準理解;
- 相比英文,中文由于語言自身的特點,在短文本的鏈指問題上更有挑戰。
EL實現思路-基于“粗排-精排”的兩階段方案
思路1:
主要流程描述:
- 候選實體召回。通過ES知識庫,召回相關實體,把知識庫實體的關系轉化為:“實體id-實體信息” 和 “實體指稱-實體id” 的映射。從原文本的mention文本出發,根據“實體指稱-實體id”匹配實體文本召回候選實體。
- 候選實體特征提取。首先用指稱項分類模型,來預測輸入數據的指稱項的實體類型。根據候選實體召回結果,對于有召回的實體:用“實體id-實體信息”提取處實體信息,按順序組織實體信息的文本內容后拼接原始文本豐富實體的語義信息,最后把指稱項的實體類型加入構成完整的實體候選集合。對于無召回的實體,就無需進行候選實體排序,直接與排序結果進行后處理整合即可。
- 候選實體排序模型。輸入標記指稱項的原始文本和候選實體信息的拼接,輸出指稱項和候選實體的匹配程度。
- 后處理。“候選實體排序模型”的輸出結果

主要流程圖
粗排方式簡單,使用ES庫進行粗排即可,精排構建一個二分類模型。
訓練數據構造形式(正負樣本比例1:5):
{"query": "莫妮卡·貝魯奇的代表作?", "query_rewrite": "#莫妮卡·貝魯奇#的代表作?", "entity": "<莫妮卡·貝魯奇>", "desc": "母親|畢業院校|類型|主演|別名|相關人物|中文名|國籍|作者|外文名|體重|職業|代表作品|出生日期|導演|身高|朋友", "label": 1}
{"query": "莫妮卡·貝魯奇的代表作?", "query_rewrite": "#莫妮卡·貝魯奇#的代表作?", "entity": "\"莫妮卡·貝魯\" ", "desc": "中文名", "label": 0}
{"query": "莫妮卡·貝魯奇的代表作?", "query_rewrite": "#莫妮卡·貝魯奇#的代表作?", "entity": "<莫妮卡·貝魯>", "desc": "類型|游戲大小|中文名|原版名稱|游戲類型", "label": 0}
{"query": "莫妮卡·貝魯奇的代表作?", "query_rewrite": "#莫妮卡·貝魯奇#的代表作?", "entity": "\"莫妮卡貝魯齊\" ", "desc": "中文名", "label": 0}
{"query": "莫妮卡·貝魯奇的代表作?", "query_rewrite": "#莫妮卡·貝魯奇#的代表作?", "entity": "<莫妮卡貝魯齊>", "desc": "類型|操作指南|基本介紹|中文名|原版名稱", "label": 0}
{"query": "莫妮卡·貝魯奇的代表作?", "query_rewrite": "#莫妮卡·貝魯奇#的代表作?", "entity": "\"莫妮卡·安娜·瑪麗亞·貝魯奇\" ", "desc": "中文名", "label": 0}字段解釋:
- query:原始問句
- query_rewrite:重寫后的問句
- entity:鏈接的實體
- desc:屬性的拼接
- label:類別標簽
在訓練時,我們只選擇??query_rewrite???與??desc??進行拼接,拼接形式如下:
query_rewrite[SEP]desc
示例:#莫妮卡·貝魯奇#的代表作?[SEP]母親|畢業院校|類型|主演|別名|相關人物|中文名|國籍|作者|外文名|體重|職業|代表作品|出生日期|導演|身高|朋友精排模型結構如下:

精排模型結構
計算實體鏈接得分:
思路2:
粗排仍然使用ES進行召回,精排使用一個與思路一相同結構的模型,區別就是數據構造方式不同。訓練數據構造形式(正負樣本比例1:5):
{'query': '莫妮卡·貝魯奇的代表作?', 'mention': '莫妮卡·貝魯奇', 'label': 1}
{'query': '莫妮卡·貝魯奇的代表作?','mention': '低鈣血癥', 'label': 0}
{'query': '莫妮卡·貝魯奇的代表作?', 'mention': '同居損友', 'label': 0}
{'query': '莫妮卡·貝魯奇的代表作?','mention': '"1964-09-22"', 'label': 0}
{'query': '莫妮卡·貝魯奇的代表作?', 'mention': '夏侯瑾軒', 'label': 0}
{'query': '莫妮卡·貝魯奇的代表作?', 'mention': '"日歷"', 'label': 0}選擇??query???與??mention??進行拼接,拼接形式如下:
query[SEP]mention
示例:莫妮卡·貝魯奇的代表作?[SEP]莫妮卡·貝魯奇計算實體鏈接得分:
為了避免噪聲影響,最后,根據得分獲取top5的鏈接實體作為候選實體。
模型結構代碼示例
import torch
from torch import nn
from transformers import BertModel, BertPreTrainedModel
class BertForSequenceClassification(BertPreTrainedModel):
def __init__(self, config):
super().__init__(config)
self.num_labels = config.num_labels
self.bert = BertModel(config)
self.dropout = nn.Dropout(config.hidden_dropout_prob)
self.classifier = nn.Linear(config.hidden_size, config.num_labels)
self.init_weights()
def forward(self, input_ids=None, attention_mask=None, token_type_ids=None, position_ids=None, head_mask=None, inputs_embeds=None, labels=None):
outputs = self.bert(
input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids,
position_ids=position_ids,
head_mask=head_mask,
inputs_embeds=inputs_embeds,
)
pooled_output = outputs[1]
pooled_output = self.dropout(pooled_output)
logits = self.classifier(pooled_output)
loss = None
if labels is not None:
if self.num_labels == 1:
# We are doing regression
loss_fct = nn.MSELoss()
loss = loss_fct(logits.view(-1), labels.view(-1))
else:
loss_fct = nn.CrossEntropyLoss()
loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1))
output = (logits,) + outputs[2:]
return ((loss,) + output) if loss is not None else output思路3:
向量模型的短文本匹配效果也不錯,不微調直接使用向量模型配合ES進行實體鏈接:
from sentence_transformers import SentenceTransformer
sentences_1 = "實體"
sentences_2 = ["es召回的實體1", "es召回的實體2",...,"es召回的實體n"]
model = SentenceTransformer('lier007/xiaobu-embedding-v2')
embeddings_1 = model.encode(sentences_1, normalize_embeddings=True)
embeddings_2 = model.encode(sentences_2, normalize_embeddings=True)
similarity = embeddings_1 @ embeddings_2.T
print(similarity)當然,更進一步的可以根據自己場景進行微調。
三、屬性選擇及屬性排序
屬性識別及屬性排序是從用戶提出的問題中識別出與知識庫中相關的屬性,并根據某些標準對這些屬性進行排序。這兩個步驟在KBQA系統中非常重要,因為它們直接影響最終答案的準確性和相關性。
通過上一節中獲取到的top5的鏈接實體,通過ES索引或者mysql等工具建立的知識圖譜,召回出對應實體的所有的屬性集合。但召回的所有的屬性集合存在一個問題,存在著大量的不相關的屬性內容,因此,需要訓練的一個屬性的排序模型選擇TOP5的屬性保留。
屬性排序訓練數據構造形式(正負樣本比例1:5):
{"entity": "<莫妮卡·貝魯奇>", "query": "莫妮卡·貝魯奇的代表作?", "attr": "<代表作品>", "label": 1}
{"entity": "<莫妮卡·貝魯奇>", "query": "莫妮卡·貝魯奇的代表作?", "attr": "<體重>", "label": 0}
{"entity": "<莫妮卡·貝魯奇>", "query": "莫妮卡·貝魯奇的代表作?", "attr": "<出生日期>", "label": 0}
{"entity": "<莫妮卡·貝魯奇>", "query": "莫妮卡·貝魯奇的代表作?", "attr": "<導演>", "label": 0}
{"entity": "<莫妮卡·貝魯奇>", "query": "莫妮卡·貝魯奇的代表作?", "attr": "<職業>", "label": 0}
{"entity": "<莫妮卡·貝魯奇>", "query": "莫妮卡·貝魯奇的代表作?", "attr": "<類型>", "label": 0}選擇??query???與??attr??進行拼接,拼接形式如下:
query[SEP]attr
示例:莫妮卡·貝魯奇的代表作?[SEP]<代表作品>計算屬性得分:
為了避免噪聲影響,最后,根據得分獲取top5的屬性作為候選屬性。
屬性排序模型也是BERT+Linear一個二分類模型:

屬性排序模型
四、文本拼接
因為本文介紹的是結合大模型的思想進行查詢語句的生成,本文的鏈路與RAG的思想非常相似,通過上述路徑檢索相關文本(相關實體片段、相關屬性片段),進行組合,組合方式如下:
prompt+question+候選實體+屬性結合五、LLM for Text2SQL
在KBQA系統中,使用大語言模型(LLM)生成SQL查詢是至關重要的一步。Text2SQL的任務是將自然語言問題轉換為結構化查詢語言(SQL),以便從數據庫或知識圖譜中檢索信息。
根據上節的介紹,對于訓練LLM的SFT數據構造示例如下:
[
{
"instruction": "你是一個Sparql生成專家,請根據給定的內容,生成Sparql語句。\n問題:“莫妮卡·貝魯奇的代表作?”,和候選實體信息:[0]名稱:<莫妮卡·貝魯奇>,屬性集:<代表作品>,<中文名>,<作者>,<外文名>,<別名>。對應查詢圖譜的Sparql的語句為:",
"input": "",
"output": "select ?x where { <莫妮卡·貝魯奇> <代表作品> ?x. }"
},
{
"instruction": "你是一個Sparql生成專家,請根據給定的內容,生成Sparql語句。\n問題:“《湖上草》是誰的詩?”,和候選實體信息:[0]名稱:<湖上草>,屬性集:<主要作品>,<中文名>,<傳世之作>,<所著>,<其丈夫>。對應查詢圖譜的Sparql的語句為:",
"input": "",
"output": "select ?x where { ?x <主要作品> <湖上草>. }"
},
...
]使用預訓練的大語言模型(例如GLM-4-9B)進行微調,使其能夠生成正確的SQL查詢(本文使用的sparql查詢,配合gstore圖數據庫使用,因gstore問題太多,個人不推薦使用,可以轉成其他的查詢語句,如:neo4j等)。本文以LLaMA-Factory為例進行微調,微調腳本如下:
import json
import os
model_name_or_path = "ZhipuAI/glm-4-9b-chat"
template = "glm4"
cutoff_len = 4096
num_train_epochs = 8
train_dataset = "train_data"
predict_dataset = "test_data"
output_dir = f"saves/{train_dataset}-{predict_dataset}-ep{num_train_epochs}-{cutoff_len}-{template}"
adapter_name_or_path = output_dir
do_train = True
do_predict = True
train_args = dict(
stage="sft", # 進行指令監督微調
do_train=do_train,
model_name_or_path=model_name_or_path,
dataset=train_dataset,
template=template,
finetuning_type="lora",
cutoff_len=cutoff_len,
lora_target="all",
output_dir=output_dir,
per_device_train_batch_size=2, # 批處理大小
gradient_accumulation_steps=4, # 梯度累積步數
lr_scheduler_type="cosine", # 使用余弦學習率退火算法
logging_steps=10, # 每 10 步輸出一個記錄
warmup_ratio=0.1, # 使用預熱學習率
save_steps=1000, # 每 1000 步保存一個檢查點
learning_rate=1e-4, # 學習率大小
num_train_epochs=num_train_epochs, # 訓練輪數
max_samples=7625, # 使用每個數據集中的 300 條樣本
max_grad_norm=1.0, # 將梯度范數裁剪至 1.0
fp16=True, # 使用 float16 混合精度訓練
temperature=0.1,
ddp_timeout=180000000,
overwrite_cache=True,
overwrite_output_dir=True
)
predict_args = dict(
stage="sft",
do_predict=do_predict,
model_name_or_path=model_name_or_path,
adapter_name_or_path=adapter_name_or_path,
dataset=predict_dataset,
template=template,
finetuning_type="lora",
cutoff_len=cutoff_len,
per_device_eval_batch_size=1,
overwrite_cache=True,
preprocessing_num_workers=16,
output_dir=f'{output_dir}/predict',
overwrite_output_dir=True,
ddp_timeout=180000000,
temperature=0.1,
max_samples=1292,
predict_with_generate=True
)
train_args_file = f"config/{train_dataset}-{predict_dataset}-ep{num_train_epochs}-{cutoff_len}-{template}-train.json"
predict_args_file = f"config/{train_dataset}-{predict_dataset}-ep{num_train_epochs}-{cutoff_len}-{template}-pred.json"
json.dump(train_args, open(train_args_file, "w", encoding="utf-8"), indent=2)
json.dump(predict_args, open(predict_args_file, "w", encoding="utf-8"), indent=2)
os.system(f'llamafactory-cli train {train_args_file}')
os.system(f'llamafactory-cli train {predict_args_file}')小結
- 優點:借助大模型的優勢,實現文本轉化為sparql查詢語句,實現單挑、多跳的從kg中查詢答案。
- 缺點:在實踐過程中發現,由于大模型的幻覺因素,生成的查詢語句“看上去對,實際上錯誤”,導致查詢答案不是準確的答案。
總結
本文詳細介紹了KBQA(知識圖譜問答)系統融合了RAG的思路,分為多個步驟。首先進行mention識別,使用大模型提取文本中的關鍵實體;接著進行實體鏈接,將識別到的實體提及與知識圖譜中的具體實體匹配和鏈接;然后對所有可能的實體進行排序,找出最相關的實體;在此基礎上進行屬性選擇及排序,提取與用戶問題相關的屬性并進行排序,確保返回的結果最符合用戶需求;接下來將上述步驟得到的文本內容拼接成完整的上下文;最后,將結構化的文本內容轉化為SQL查詢,以便從知識圖譜或數據庫中檢索信息。
數據集
????https://tianchi.aliyun.com/competition/entrance/532100/information??
本文轉載自???大模型自然語言處理?? 作者:余俊暉

















