在計算機科學中,圖形作為一種特定的數據結構,用於表達數據之間的複雜關係,如社交關係、組織架構、交通信息、網絡拓撲等等。在圖計算中,基本的數據結構表達式是:G=(V,E),V=vertex(節點),E=edge(邊)。圖形結構的數據結構一般以節點和邊來表現,也可以在節點上增加鍵值對屬性。圖資料庫是 NoSQL(非關係型資料庫)的一種,它應用圖形數據結構的特點(節點、屬性和邊)存儲數據實體和相互之間的關係信息。
Neo4j 是當前較為主流和先進的原生圖資料庫之一,提供原生的圖數據存儲、檢索和處理。它由 Neo Technology支持,從 2003 年開始開發,1.0 版本發布於 2010 年,2.0版本發布於 2013 年。經過十多年的發展,Neo4j 獲得越來越高的關注度,它已經從一個 Java 領域內的圖資料庫逐漸發展成為適應多語言多框架的圖資料庫。Neo4j 支持ACID、集群、備份和故障轉移,具有較高的可用性和穩定性;它具備非常好的直觀性,通過圖形化的界面表示節點和關係;同時它具備較高的可擴展性,能夠承載上億的節點、關係和屬性,通過 REST 接口或者面向對象的 JAVA API進行訪問。
二、Neo4j簡介2.1 基本概念Neo4j使用圖相關的概念來描述數據模型,把數據保存為圖中的節點以及節點之間的關係。數據主要由三部分構成:
節點。節點表示對象實例,每個節點有唯一的ID區別其它節點,節點帶有屬性;關係。就是圖裡面的邊,連接兩個節點,另外這裡的關係是有向的並帶有屬性;屬性。key-value對,存在於節點和關係中,如圖1所示。圖片.png圖 1 節點、關係和屬性三者的關係
2.2 索引動機:Neo4j使用遍歷操作進行查詢。為了加速查詢,Neo4j會建立索引,並根據索引找到遍歷用的起始節點;介紹:默認情況下,相關的索引是由Apache Lucene提供的。但也能使用其他索引實現來提供。操作:用戶可以創建任意數量的命名索引。每個索引控制節點或者關係,而每個索引都通過key/value/object三個參數來工作。其中object要麼是一個節點,要麼是一個關係,取決於索引類型。另外,Neo4j中有關於節點(關係)的索引,系統通過索引實現從屬性到節點(關係)的映射。查找操作:系統通過設定訪問條件比如,遍歷的方向,使用深度優先或廣度優先算法等條件對圖進行遍歷,從一個節點沿著關係到其他節點;刪除操作:Neo4j可以快速的插入刪除節點和關係,並更新節點和關係中的屬性。2.3 Neo4j的優勢那麼,如此引人入勝的 Neo4j,與其他資料庫相比,具有哪些明顯的優勢呢?這可以從以下幾個方面來分析,主要表現為查詢的高性能、設計的靈活性和開發的敏捷性等。
Neo4j是一個原生的圖資料庫引擎,它存儲了原生的圖數據,因此,可以使用圖結構的自然伸展特性來設計免索引鄰近節點遍歷的查詢算法,即圖的遍歷算法設計。圖的遍歷是圖數據結構所具有的獨特算法,即從一個節點開始,根據其連接的關係,可以快速和方便地找出它的鄰近節點。這種查找數據的方法並不受數據量的大小所影響,因為鄰近查詢查找的始終是有限的局部數據,而不會對整個資料庫進行搜索。所以,Neo4j具有非常高效的查詢性能,相比於RDBMS,它的查詢速度可以提高數倍乃至數十倍.而且查詢速度不會因數據量的增長而下降,即資料庫可以經久耐用,並且始終保持最初的活力。不像RDBMS那樣,因為不可避免地使用了一些範式設計,所以在查詢時如果需要表示一些複雜的關係,勢必會構造很多連接,從而形成很多複雜的運算。並且在查詢中更加可怕的是還會涉及大量數據,這些數據大多與結果毫無關係,有的可能僅僅是通過ID查找它的名稱而已,所以隨著數據量的增長,即使查詢一小部分數據,查詢也會變得越來越慢,性能日趨下降,以至於讓人無法忍受。
在日新月異的網際網路應用中,業務需求會隨著時間和條件的改變而發生變化,這對於以往使用結構化數據的系統來說,往往很難適應這種變化的需要。圖數據結構的自然伸展特性及其非結構化的數據格式,讓 Neo4j的資料庫設計可以具有很大的伸縮性和靈活性。因為隨著需求的變化而增加的節點、關係及其屬性並不會影響到原來數據的正常使用,所以使用Neo4j來設計資料庫,可以更接近業務需求的變化,可以更快地趕上需求發展變化的腳步。大多數使用關係型資料庫的系統,為了應對快速變化的業務需求,往往需要採取推倒重來的方法重構整個應用系統。而這樣做的成本是巨大的。使用Neo4j可以最大限度地避免這種情況發生。雖然有時候,也許是因為最初的設計考慮得太不周全,或者為了獲得更好的表現力,資料庫變更和遷移在所難免,但是使用Neo4j來做這項工作也是非常容易的,至少它沒有模式結構定義方面的苦惱。
圖資料庫設計中的數據模型,從需求的討論開始,到程序開發和實現,以及最終保存在資料庫中的樣子,直觀明了,似乎沒有什麼變化,甚至可以說本來就是一模一樣的。這說明,業務需求與系統設計之間可以拉近距離,需求和實現結果之間越來越接近。這不但降低了業務人員與設計人員之間的溝通成本,也使得開發更加容易迭代,並且非常適合使用敏捷開發方法。
Neo4j本身可伸縮的設計靈活性,以及直觀明了的數據模型設計,以及其自身簡單易用的特點等,所有這些優勢都充分說明,使用Neo4j很適合以一種測試驅動的方法應用於系統設計和開發自始至終的過程之中,通過迭代來加深對需求的理解,並通過迭代來完善數據模型設計。
在圖資料庫領域,除Neo4j外,還有其他如OrientDB、Giraph、AllegroGraph等各種圖資料庫。與所有這些圖資料庫相比,Neo4j的優勢表現在以下兩個方面。
(1)Neo4j是一個原生圖計算引擎,它存儲和使用的數據自始至終都是使用原生的圖結構數據進行處理的,不像有些圖資料庫,只是在計算處理時使用了圖資料庫,而在存儲時還將數據保存在關係型資料庫中。
(2)Neo4j是一個開源的資料庫,其開源的社區版吸引了眾多第三方的使用和推如開源項目Spring Data Neo4j就是一個做得很不錯的例子,同時也得到了更多開發者的擁躉和支持,聚集了豐富的可供交流和學習的資源與案例。這些支持、推廣和大量的使用,反過來會很好地推動Neo4j的發展。
Neo4j 查詢的高性能表現、易於使用的特性及其設計的靈活性和開發的敏捷性,以及堅如磐石般的事務管理特性,都充分說明了使用Neo4j是一個不錯的選擇。有關它的所有優點,總結起來,主要表現在以下幾個方面。
非結構化數據存儲方式,在資料庫設計上具有很大的靈活性;很容易使用,可以用嵌入式、伺服器模式、分布式模式等方式來使用資料庫;資料庫安全可靠,可以實時備份數據,很方便恢復數據;圖的數據結構直觀而形象地表現了現實世界的應用場景。三、Neo4j 數據導入3.1 數據集簡介數據源:39健康網。包括15項信息,其中7類實體,約3.7萬實體,21萬實體關係。
知識圖譜結構圖 5 知識圖譜結構
知識圖譜實體類型圖 6 知識圖譜實體類型
知識圖譜實體關係類型圖 6 知識圖譜實體關係類型
知識圖譜疾病屬性圖 7 知識圖譜疾病屬性
基於特徵詞分類的方法來識別用戶查詢意圖圖 8 基於特徵詞分類的方法來識別用戶查詢意圖
3.2 數據導入參考task2:Task02:快速搭建易於醫療領域的小型知識圖譜
3.3 知識圖譜展示運行介紹之後,打開瀏覽器進入網址:http://localhost:7474/browser/,可以看到我們導入的數據的知識圖譜,如下:
圖片.png圖 9 知識圖譜 展示圖
3.4 主體類 MedicalGraph 介紹class MedicalGraph:
def __init__(self):
pass
# 讀取文件,獲得實體,實體關係
def read_file(self):
psss
# 創建節點
def create_node(self, label, nodes):
pass
# 創建疾病節點的屬性
def create_diseases_nodes(self, disease_info):
pass
# 創建知識圖譜實體
def create_graphNodes(self):
pass
# 創建實體關係邊
def create_graphRels(self):
pass
# 創建實體關係邊
def create_relationship(self, start_node, end_node, edges, rel_type, rel_name):
pass
3.5 主體類 MedicalGraph 中關鍵代碼講解cur_dir = '/'.join(os.path.abspath(__file__).split('/')[:-1])
self.data_path = os.path.join(cur_dir, 'DATA/disease.csv')self.graph = Graph("http://localhost:7474", username="neo4j", password="自己設定的密碼")這部分代碼的核心就是讀取 數據文件,並 獲取實體和實體關係信息。
disease_to_symptom 疾病與症狀關係disease_to_alias 疾病與別名關係diseases_to_part 疾病與部位關係disease_to_department 疾病與科室關係disease_to_complication 疾病與併發症關係def read_file(self):
"""
讀取文件,獲得實體,實體關係
:return:
"""
# cols = ["name", "alias", "part", "age", "infection", "insurance", "department", "checklist", "symptom",
# "complication", "treatment", "drug", "period", "rate", "money"]
# 實體
diseases = [] # 疾病
aliases = [] # 別名
symptoms = [] # 症狀
parts = [] # 部位
departments = [] # 科室
complications = [] # 併發症
drugs = [] # 藥品
# 疾病的屬性:age, infection, insurance, checklist, treatment, period, rate, money
diseases_infos = []
# 關係
disease_to_symptom = [] # 疾病與症狀關係
disease_to_alias = [] # 疾病與別名關係
diseases_to_part = [] # 疾病與部位關係
disease_to_department = [] # 疾病與科室關係
disease_to_complication = [] # 疾病與併發症關係
disease_to_drug = [] # 疾病與藥品關係
all_data = pd.read_csv(self.data_path, encoding='gb18030').loc[:, :].values
for data in all_data:
disease_dict = {} # 疾病信息
# 疾病
disease = str(data[0]).replace("...", " ").strip()
disease_dict["name"] = disease
# 別名
line = re.sub("[,、;,.;]", " ", str(data[1])) if str(data[1]) else "未知"
for alias in line.strip().split():
aliases.append(alias)
disease_to_alias.append([disease, alias])
# 部位
part_list = str(data[2]).strip().split() if str(data[2]) else "未知"
for part in part_list:
parts.append(part)
diseases_to_part.append([disease, part])
# 年齡
age = str(data[3]).strip()
disease_dict["age"] = age
# 傳染性
infect = str(data[4]).strip()
disease_dict["infection"] = infect
# 醫保
insurance = str(data[5]).strip()
disease_dict["insurance"] = insurance
# 科室
department_list = str(data[6]).strip().split()
for department in department_list:
departments.append(department)
disease_to_department.append([disease, department])
# 檢查項
check = str(data[7]).strip()
disease_dict["checklist"] = check
# 症狀
symptom_list = str(data[8]).replace("...", " ").strip().split()[:-1]
for symptom in symptom_list:
symptoms.append(symptom)
disease_to_symptom.append([disease, symptom])
# 併發症
complication_list = str(data[9]).strip().split()[:-1] if str(data[9]) else "未知"
for complication in complication_list:
complications.append(complication)
disease_to_complication.append([disease, complication])
# 治療方法
treat = str(data[10]).strip()[:-4]
disease_dict["treatment"] = treat
# 藥品
drug_string = str(data[11]).replace("...", " ").strip()
for drug in drug_string.split()[:-1]:
drugs.append(drug)
disease_to_drug.append([disease, drug])
# 治癒周期
period = str(data[12]).strip()
disease_dict["period"] = period
# 治癒率
rate = str(data[13]).strip()
disease_dict["rate"] = rate
# 費用
money = str(data[14]).strip() if str(data[14]) else "未知"
disease_dict["money"] = money
diseases_infos.append(disease_dict)
return set(diseases), set(symptoms), set(aliases), set(parts), set(departments), set(complications), \
set(drugs), disease_to_alias, disease_to_symptom, diseases_to_part, disease_to_department, \
disease_to_complication, disease_to_drug, diseases_infos這部分代碼主要是為了創建不包含屬性的 節點
def create_node(self, label, nodes):
"""
創建節點
:param label: 標籤
:param nodes: 節點
:return:
"""
count = 0
for node_name in nodes:
node = Node(label, name=node_name)
self.graph.create(node)
count += 1
print(count, len(nodes))
returndef create_diseases_nodes(self, disease_info):
"""
創建疾病節點的屬性
:param disease_info: list(Dict)
:return:
"""
count = 0
for disease_dict in disease_info:
node = Node("Disease", name=disease_dict['name'], age=disease_dict['age'],
infection=disease_dict['infection'], insurance=disease_dict['insurance'],
treatment=disease_dict['treatment'], checklist=disease_dict['checklist'],
period=disease_dict['period'], rate=disease_dict['rate'],
money=disease_dict['money'])
self.graph.create(node)
count += 1
print(count)
returndef create_graphNodes(self):
"""
創建知識圖譜實體
:return:
"""
disease, symptom, alias, part, department, complication, drug, rel_alias, rel_symptom, rel_part, \
rel_department, rel_complication, rel_drug, rel_infos = self.read_file()
self.create_diseases_nodes(rel_infos)
self.create_node("Symptom", symptom)
self.create_node("Alias", alias)
self.create_node("Part", part)
self.create_node("Department", department)
self.create_node("Complication", complication)
self.create_node("Drug", drug)
returndef create_graphRels(self):
disease, symptom, alias, part, department, complication, drug, rel_alias, rel_symptom, rel_part, \
rel_department, rel_complication, rel_drug, rel_infos = self.read_file()
self.create_relationship("Disease", "Alias", rel_alias, "ALIAS_IS", "別名")
self.create_relationship("Disease", "Symptom", rel_symptom, "HAS_SYMPTOM", "症狀")
self.create_relationship("Disease", "Part", rel_part, "PART_IS", "發病部位")
self.create_relationship("Disease", "Department", rel_department, "DEPARTMENT_IS", "所屬科室")
self.create_relationship("Disease", "Complication", rel_complication, "HAS_COMPLICATION", "併發症")
self.create_relationship("Disease", "Drug", rel_drug, "HAS_DRUG", "藥品")def create_relationship(self, start_node, end_node, edges, rel_type, rel_name):
"""
創建實體關係邊
:param start_node:
:param end_node:
:param edges:
:param rel_type:
:param rel_name:
:return:
"""
count = 0
# 去重處理
set_edges = []
for edge in edges:
set_edges.append('###'.join(edge))
all = len(set(set_edges))
for edge in set(set_edges):
edge = edge.split('###')
p = edge[0]
q = edge[1]
query = "match(p:%s),(q:%s) where p.name='%s'and q.name='%s' create (p)-[rel:%s{name:'%s'}]->(q)" % (
start_node, end_node, p, q, rel_type, rel_name)
try:
self.graph.run(query)
count += 1
print(rel_type, count, all)
except Exception as e:
print(e)
return
四、總結Neo4j是一個高性能的,NOSQL圖形資料庫,它將結構化數據存儲在網絡上而不是表中。Neo4j也可以被看作是一個高性能的圖引擎,該引擎具有成熟資料庫的所有特性。Neo4j是一個高度可擴展的本機圖形資料庫,旨在專門利用