一、前言
基于知識圖譜的問答系統(tǒng)(Knowledge-Based Question Answering system: KBQA)在以下場景下比較有優(yōu)勢:
- 對于領域類型是結構化數據場景:電商、醫(yī)藥、系統(tǒng)運維(微服務、服務器、事件)、產品支持系統(tǒng)等,其中作為問答系統(tǒng)的參考對象已經是結構化數據;
- 問題的解答過程涉及多跳查詢,比如“周杰倫的媽媽今年是本命年嗎?”,“你們家的產品 v1 和 v2 的區(qū)別是什么?”;
- 為了解決其他需求(風控、推薦、管理),已經構建了圖結構數據、知識圖譜的情況。
KBQA簡單講就是將把問題解析、轉換成在知識圖譜中的查詢,查詢得到結果之后進行篩選、翻譯成方便人理解的結果。
問題到圖譜查詢的轉換又有不同的方法可以實現(xiàn):
- 對語義進行分析:理解問題的意圖,針對不同意圖匹配最可能的問題類型,從而構建這個類型問題的圖譜查詢,查得結果;
- 基于信息的抽取:從問題中抽取主要的實體,在圖譜中獲取實體的所有知識、關系條目(子圖),再對結果根據問題中的約束條件匹配、排序選擇結果。
這里一切從簡,選擇第一條對語義進行分析流程一般分為以下4步:
- 實體檢測,獲取問題的關鍵詞,比如問題“周杰倫有哪些專輯?”,那么首先必須找到周杰倫,才可以進行下一步。
- 意圖獲取,一個問題,我們只獲取了實體還不夠,比如上面,只有周杰倫,還要有目的,不然可能我是想問周杰倫是哪里人,今年幾歲了等等,所以需要找到問題的真實目的。
- 關系預測,有了實體和目的,那么我們就需要在知識庫里面尋找雙方的關系,想辦法聯(lián)系起來。
- 查詢構建,將處理好的三元組帶入知識庫搜索答案。
二、知識圖譜
知識圖譜,本質上是一種揭示實體之間關系的語義網絡。
知識圖譜通過對錯綜復雜的文檔的數據進行有效的加工、處理、整合,轉化為簡單、清晰的“實體,關系,實體”的三元組,最后聚合大量知識,從而實現(xiàn)知識的快速響應和推理。
2.1 數據入庫
圖數據庫選用: Nebula Graph(星云圖數據庫)是一個以 Apache 2.0 許可證開源的分布式圖數據庫,地址:https://github.com/vesoft-inc/nebula
數據采用Nebula Graph官方測試數據 游戲中口袋怪物的數據關系
2.1.1 Nebula Graph搭建
根據官方手冊安裝步驟,安裝Nebula Graph
# Centos 7 下載安裝 3.0.1版本
$ wget https://oss-cdn.nebula-graph.com.cn/package/3.0.1/nebula-graph-3.0.1.el7.x86_64.rpm
$ sudo rpm -ivh nebula-graph-3.0.1.el7.x86_64.rpm
# ubuntu 下載安裝
wget https://oss-cdn.nebula-graph.com.cn/package/nightly/2021.11.24/nebula-graph-2021.11.24-nightly.ubuntu1804.amd64.deb
sudo dpkg -i nebula-graph-3.0.1.ubuntu1804.amd64.deb
# 下載完成之后啟動nebula
$ /usr/local/nebula/scripts/nebula.service start all
# 然后檢查是否成功
$ /usr/local/nebula/scripts/nebula.service status all
# 關掉服務
$ /usr/local/nebula/scripts/nebula.service stop all
# 卸載的話要卸載干凈
$ rpm -qa|grep nebula
nebula-graph-studio-1.2.5-1.x86_64
nebula-2021.03.02_nightly-1.x86_64
$ sudo rpm -e nebula-2021.03.02_nightly-1.x86_64
$ sudo rpm -e nebula-graph-studio-1.2.5-1.x86_64
# 手動刪掉文件夾
rm -rf /usr/local/nebula
rm -rf /vesoft
# 安裝客戶端來連接nebula
# https://github.com/vesoft-inc/nebula-console/tags 找對應版本點Asset下載
# 將下載好的文件放進/usr/local/nebula/bin 目錄下,然后改名
$ cd /usr/local/nebula/bin
$ mv nebula-console-linux-amd64-v3.0.0 nebula-console
# 設置權限
$ chmod 111 nebula-console
# 進入nebula
$ ./nebula-console -addr=127.0.0.1 -port 9669 -u root -p nebula
# ./nebula-console -addr <ip> -port <port> -u <username> -p <password> [-t 120] [-e "nGQL_statement" | -f filename.nGQL]
# 安裝可視化工具Nebula Graph Studio
# 下載 https://oss-cdn.nebula-graph.io/nebula-graph-studio/Nebula-Graph-Studio-2.1.9-beta-Linux.rpm
# Centos安裝
$ sudo rpm -ivh --replacepkgs Nebula-Graph-Studio-2.1.9-beta-Linux.rpm
$ cd nebula-graph-studio
$ npm run start
# ubuntu 下載安裝
# https://docs.nebula-graph.com.cn/3.0.1/nebula-studio/deploy-connect/st-ug-deploy/#tar_studio 選擇版本對應的安裝包安裝
$ tar -xvf nebula-graph-studio-3.2.2.x86_64.tar.gz
$ cd nebula-graph-studio
$ ./server
# 停止服務
$ kill $(lsof -t -i :7001)
可以看到如下頁面,填寫host 、用戶名密碼登錄。
至此,nebula graph安裝配置已完成~
2.1.2數據導入
# 首先進入nebula
$ cd /usr/local/nebula/bin
$ ./nebula-console -addr=127.0.0.1 -port 9669 -u root -p nebula
# 將 Storage 節(jié)點加入 Nebula Graph 集群。等一分鐘等他起來
$ ADD HOSTS 127.0.0.1:9779
$ SHOW HOSTS;
將上面測試數據下載下來: 游戲中口袋怪物的數據關系
# 導入數據
$ ./nebula-console -addr=127.0.0.1 -port 9669 -u root -p nebula -f monster.gql
# 導入完畢后,我們可以使用 Nebula Graph 服務做一些簡單的查詢。圖語句使用可參考官方文檔
# https://docs.nebula-graph.com.cn/3.0.1/3.ngql-guide/1.nGQL-overview/1.overview/
導入完成后就可以選擇圖空間進來。
有了這個知識圖譜,接下來的任務就是在它之上搭一個簡單的基于語法解析的 QA 系統(tǒng)了~
三、后端
后端采用 Flask 框架作為web server來接收HTTP的POST請求,大體流程如下:
- 接收問句
- 處理之后訪問知識圖譜(圖數據庫)
- 將處理結果返回給用戶
3.1 搭建Flask框架,處理http請求
先建一個我們的配置文件conf.json ,定義我們http信息跟日志信息。
{
"httpserver": {
"httpPoolNumber": 10,
"httpMaxConn": 200,
"port": 10001,
"host": "0.0.0.0"
},
"log": {
"filename": "KBQA_demo_log",
"dir": "../logs"
}
}
然后是我們的服務啟動文件main.py。簡單的搭建一個Flask框架
import json
import logging
import os
from flask import Flask
app = Flask(__name__)
# 處理用戶請求
@app.route('/query', methods=['GET', 'POST'])
def query():
return "hello"
# 封裝一個啟動引擎的類
class Engine(object):
def __init__(self, http_cfg, log_cfg):
self.port = http_cfg['port']
self.host = http_cfg['host']
self.logDir = log_cfg['dir']
self.logFileName = log_cfg['filename']
self.debug = False
def run(self):
app.run(host=self.host, port=self.port, debug=self.debug, use_reloader=False)
def set_log(self):
# 創(chuàng)建app實例前先配置好日志文件
fmt = '%(asctime)s [%(levelname)s] [%(message)s]'
logging.basicConfig(level=logging.INFO,
format=fmt, # 定義輸出log的格式
datefmt='[%Y-%m-%d %H:%M:%S]')
if not os.path.exists(self.logDir): # 創(chuàng)建日志目錄
os.makedirs(self.logDir)
# 實例對象從配置文件中加載配置
app.config.from_object(logging.INFO)
return app
if __name__ == '__main__':
# 載入配置
conf = json.load(open('./conf.json', 'r'))
eng = Engine(conf['httpserver'], conf['log'])
eng.set_log()
eng.run()
運行起來可以看到,已經走通了。
ok,那稍微修改下我們的query函數,使用戶的問題在body里面question關鍵字的value里。
import question_solve
# 處理用戶請求
@app.route('/query', methods=['GET', 'POST'])
def query():
# 采用form表單的方式請求,
question = request.form.to_dict().get("question", "")
if question:
answer = Solve().query(question)
else:
answer = "Sorry, I can't understand what you say."
return jsonify({"answer": answer})
接下來question_solve就是核心處理請求的邏輯了~
3.2 處理請求(核心)
按照我們上面說的步驟來:
a. 實體提取和意圖分類
b. 轉換成在知識圖譜中的查詢語句
c. 查詢結果格式化輸出
這里,我們把a.的邏輯放在analyze里; b,c.的邏輯放在transfer_ngql里.
a. HTTP請求的問題句子question傳過來,用analyze解析它的意圖和句子實體
b. 用意圖和句子實體構造action,轉化成ngql語言。
c. 連接圖數據庫執(zhí)行,獲取結果,并將結果格式化輸出。
代碼段:question_solve.py
from src.transfer_ngql import TransferNgql
from src.analyze import Analyze
class Solve(object):
def __init__(self):
self.analyze = Analyze()
self.transfer_ngql = TransferNgql()
def query(self, question):
anal = self.analyze.solve(question)
result = self.transfer_ngql.solve(anal)
return result
3.2.1 實體提取和意圖分類
analyze需要在get(question)方法里將句子中的實體和句子的意圖解析、分類出來。一般做法是NLP分詞實現(xiàn),這里只是是通過判斷輸入問句中是否存在特定的實地類別和特定的意圖詞,來進行意圖判斷,屬于模板匹配。
我們這里實現(xiàn)了五類意圖的問題:
從屬關系:比如小剛有小拳石嗎?
進化情況:皮卡丘進化變成啥?
類別情況:噴火龍是龍屬性嗎?
傷害情況:電系打飛行系傷害加倍嗎?
數量問題:小智有多少個神奇寶貝?
建立AC樹
實體提取是利用知識圖譜中實體名字構成的特征庫,建立AC樹(Aho-Corasick Algorithm ),利用AC算法,匹配問句中是否存在特征詞,即查詢問句中是否存在知識圖譜實體名字來實現(xiàn)實體提取。此種提取方式為硬提取方式,不具有模糊推測能力,后續(xù)改進應該用 NLP 里的分詞的方法來做。
代碼段:src/analyze.py
import ahocorasick
def build_ac_tree(self) -> None:
# 建立AC樹
self.ac_tree = ahocorasick.Automaton()
# wordlist為3類實體所有名字構成的特征詞庫
for index, word in enumerate(self.entity_type_map.keys()):
self.ac_tree.add_word(word, (index, word))
self.ac_tree.make_automaton()
wordlist特征庫樣例如下所示,包含了3類實體的所有名字。
實體提取
代碼段:src/analyze.py
def get_match_entities(self, question: str) -> dict:
# 實體提取
entities_matched = []
for i in self.ac_tree.iter(question):
entities_matched.append(i[1][1])
print(i)
stop_wds = []
for wd1 in entities_matched:
for wd2 in entities_matched:
if wd1 in wd2 and wd1 != wd2:
stop_wds.append(wd1)
final_wds = [i for i in entities_matched if i not in stop_wds]
return {
entity: self.entity_type_map[entity] for entity in final_wds
}
來測試一下,可以看到,已經匹配到了Giovanni 是個人,rapidash(烈焰馬)是個monster。
建立意圖特征庫
這里,將匹配的規(guī)則寫在intention.yaml中,后面增加規(guī)則只需更新配置文件就可以了。在實際場景下,訓練模型去做匹配效果會更好。
intents:
relationship:
action:
RelationshipAction
keywords:
- owm
- have
- hold
damage:
action:
DamageAction
keywords:
- damage
- double
- half
- reduce
- increase
- addition
- add
- constant
property:
action:
PropertyAction
keywords:
- monster_type
- belongto
- type
- belong
evolution:
action:
EvolutionAction
keywords:
- evolute
- evolution
- change
- grow
count:
action:
CountAction
keywords:
- count
- num
- number
- amount
- many
drawback:
action:
DrawbackAction
keywords: []
對于每一個意圖來說:
-
intents.<名字>代表名字
-
action代表后邊在要實現(xiàn)的相應的xxxAction的類,比如EvolutionAction將是用來處理進化關系這樣的問題的Action類
-
keywords代表在句子之中匹配的關鍵詞,比如問句里出現(xiàn)evolute,evolution,change,grow的字眼的時候,將會匹配進化的問題
代碼段:src/analyze.py
def load_data(self) -> None:
path = os.path.abspath(os.path.dirname(os.getcwd())) + os.path.sep
# 加載意圖特征庫
with open(f"{path}intention.yaml", "r") as file:
self.intents = yaml.safe_load(file)["intents"]
for name, intent in self.intents.items():
self.intents_map.update({keyword: name for keyword in intent['keywords']})
# 加載實體庫
self.entity_type_map.update({key: "person" for key in self.entities['person'].keys()})
self.entity_type_map.update({key: "monster" for key in self.entities['monster'].keys()})
self.entity_type_map.update({key: "property" for key in self.entities['property'].keys()})
意圖模板匹配
代碼段:src/analyze.py
# 意圖模板匹配
def check_intent_words(self, question: str):
intents_words = set()
for word in self.intents_map.keys():
if word in question:
intents_words.add(self.intents_map[word])
return intents_words
測試一下,同樣的匹配到了relationship關鍵詞。
到這里已經能返回解析、分類出來的意圖和實體了,下一步它們將會被傳給Actions去執(zhí)行知識圖譜的查詢。
3.2.2 轉換成ngql語句
該模塊根據實體提取和意圖分類返回的結果,生成對應意圖的ngql語句。
代碼段:src/transfer_ngql.py
import os
import yaml
class TransferNgql(object):
def __init__(self) -> None:
self.intents = {}
self.load_data()
def load_data(self) -> None:
path = os.path.abspath(os.path.dirname(os.getcwd())) + os.path.sep
# 加載意圖特征庫
with open(f"{path}intention.yaml", "r") as file:
self.intents = yaml.safe_load(file)["intents"]
def solve(self, intent: dict):
intent_name = "drawback"
if len(intent["intents"]) > 0:
intent_name = intent["intents"][0]
act_name = self.intents.get(intent_name).get("action")
result = ''
# 根據匹到的不同意圖,生成對應意圖的ngql語句
if act_name == 'RelationshipAction':
result = relationship_action(intent)
elif act_name == 'DamageAction':
result = damage_action(intent)
elif act_name == 'PropertyAction':
result = property_action(intent)
elif act_name == 'EvolutionAction':
result = evolution_action(intent)
elif act_name == 'CountAction':
result = count_action(intent)
return result
最后,來實現(xiàn)其中一個方法,比如relationship_action()對應的代碼如下:
def relationship_action(intent):
try:
nodes = {key: value for value, key in intent["entities"].items()}
person, monster = nodes['person'], nodes['monster']
person_vid = data_load().get_vid(person)
monster_vid = data_load().get_vid(monster)
# 拼接ngql語句
query = f"""USE Game_Monsters;
MATCH p=(v)-[e:own*1]->(v1:monster)
WHERE id(v) == '{person_vid}' and id(v1) == '{monster_vid}'
RETURN p LIMIT 1000;"""
# 連接nebula查詢
result = data_load().get_nebula_connect(query)
if result.is_empty():
answ = f'{person} does not have {monster}.'
else:
answ = f'Yes! {person} has a lovely monster named {monster}.'
return answ
except Exception:
print(f"關系實體識別錯誤!intent: {intent}")
return ''
3.3 連接nebula查詢結果返回
首先安裝nebula3-python,如果nebula graph是2+的版本請安裝nebula2-python
https://github.com/vesoft-inc/nebula-python
from nebula3.gclient.net import ConnectionPool
from nebula3.Config import Config
def get_nebula_connect(self, query):
config = Config()
config.max_connection_pool_size = 10
connection_pool = ConnectionPool()
connection_pool.init([(self.nebula_conf['host'], self.nebula_conf['port'])], config)
with connection_pool.session_context(self.nebula_conf['username'], self.nebula_conf['passwd']) as session:
result = session.execute(query)
if not result.is_succeeded():
return f"連接nebula時出錯!{query}"
return result
ok 至此后端已經搭建完成,讓我們來測試一下~文章來源:http://www.zghlxwxcb.cn/news/detail-822744.html
開始啟動~
postman 模擬發(fā)送一下請求,可以看到,已經返回結果了
換一個神奇寶貝,可以看到,Sabrina 是有venomoth(末入蛾 )的。文章來源地址http://www.zghlxwxcb.cn/news/detail-822744.html
到了這里,關于從0到1構建一個基于知識圖譜的智能問答系統(tǒng)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!