這是HIT2019人工智能實驗三,因爲時間緊張,代碼沒有進行任何優化,實驗算法僅供參考。html
實現貝葉斯網絡的機率推導(Probabilistic Inference)python
具體實驗指導書見githubgit
這裏首先給出代碼github
關於貝葉斯網絡的學習,我參考的是這篇博客算法
這篇博客講述的雖然全面,但細節部分,尤爲是貝葉斯網絡機率推導的具體實現部分,一筆帶過。然而本次實驗的要求就是實現貝葉斯網絡的機率推導,所以我在學習完這篇博客的基礎上,又把老師發的ppt學了一遍,(因爲ppt是英文的,一開始我是拒絕學的),最後又挑重點看了下博客和ppt,感受豁然開朗。網絡
所以若是沒有學習過貝葉斯網絡,建議按照我上面列出的順序學習。app
因爲ppt較大,所以這裏以網盤形式給出,提取碼:cn3h,該ppt僅供我的學習參考,嚴禁以盈利形式傳播函數
關於貝葉斯網絡的機率推導,最重要的公式是如下這兩個:學習
這兩個公式具體什麼意思,網上或者是ppt中都有講解,這裏再也不贅述。重點在於這兩個公式是完成本實驗代碼的核心公式,這一點我在完成實驗以後才意識到,在以前學習ppt的時候,因爲公式衆多,並無意識到這兩個公式的重要性。
代碼所在的github地址已給出
須要注意的是,因爲該實驗指定了數據格式,所以代碼徹底是在指定數據格式要求下完成的,不具備普適性,所以實驗代碼僅供參考算法使用。
設計的cpt格式以下:
class cpt: def __init__(self, name, parents, probabilities): self.name = name self.parents = parents self.probabilities = probabilities
貝葉斯網絡代碼以下
from cpt import cpt class BN: def __init__(self, nums, variables, graph, cpts): self.nums = nums self.variables = variables self.graph = graph self.cpts = cpts # 建立一個名字與編號的字典,便於查找 index_list = [i for i in range(self.nums)] self.variables_dict = dict(zip(self.variables, index_list)) # 計算全機率矩陣 self.TotalProbability = self.calculateTotalProbability() def calculateProbability(self, event): # 分別計算待求變量個數k1和待消除變量個數k2,剩餘的爲條件變量個數 k1 = self.count(event, 2) k2 = self.count(event, 3) probability = [] for i in range(2**k1): p = 0 for j in range(2**k2): index = self.calculateIndex(self.int2bin_list(i, k1), self.int2bin_list(j, k2), event) p = p + self.TotalProbability[index] probability.append(p) # 最後輸出的機率矩陣的格式:先輸出true,再輸出false return list(reversed([x/sum(probability) for x in probability])) def calculateTotalProbability(self): # 全機率矩陣爲一個1 * 2^n大小的矩陣,將列號轉化爲2進制,可表示事件的發生狀況 # 例如共有5個變量,則第7列的機率爲p,表示事件00111(12不發生,345發生)發生的機率爲p TotalProbability = [0 for i in range(2 ** self.nums)] for i in range(2 ** self.nums): p = 1 binary_list = self.int2bin_list(i,self.nums) for j in range(self.nums): # 分沒有父節點和有父節點的狀況 # 注意python float在相乘時會產生不精確的問題,所以每次相乘前先乘1000將其轉化成整數相乘,最後再除回來 if self.cpts[j].parents == []: p = p * (self.cpts[j].probabilities[0][1-binary_list[j]] * 1000) else: parents_list = self.cpts[j].parents parents_index_list = [self.variables_dict[k] for k in parents_list] index = self.bin_list2int([binary_list[k] for k in parents_index_list]) p = p * (self.cpts[j].probabilities[index][1 - binary_list[j]] * 1000) TotalProbability[i] = p / 10 ** (self.nums * 3) return TotalProbability def int2bin_list(self, a, b): # 將列號轉化成指定長度的二進制數組 # 下面兩句話的含義:將a轉化成二進制字符串,而後分割成字符串數組,再將字符串數組轉化成整形數組 # 若獲得的整型數組長度不知足self.nums,則在前面補上相應的零 binary_list = list(map(int, list(bin(a).replace("0b", '')))) binary_list = (b - len(binary_list)) * [0] + binary_list return binary_list def bin_list2int(self, b): # 將二進制的數組轉化成整數 result = 0 for i in range(len(b)): result = result + b[len(b)-1-i] * (2 ** i) return result def calculateIndex(self, i, j, event): # 用於生成下標 # 原理暫略 index_list = [] for k in range(len(event)): if event[k] == 2: index_list.append(i[0]) del(i[0]) elif event[k] == 3: index_list.append(j[0]) del(j[0]) else: index_list.append(event[k]) return self.bin_list2int(index_list) def count(self, list, a): # 用於統計一個list中含有多少個指定的數字 c = 0 for i in list: if i == a: c = c + 1 return c
該實驗的主程序(包括讀取指定數據文件的函數)以下:
import sys from BN import BN from cpt import cpt # 讀取文件並生成一個貝葉斯網絡 def readBN(filename): f = open(filename, 'r') # 讀取變量數 nums = int(f.readline()) f.readline() # 讀取變量名稱 variables = f.readline()[:-1].split(' ') f.readline() # 讀取有向圖鄰接表 graph = [] for i in range(nums): line = f.readline()[:-1].split(' ') graph.append(list(map(int, line))) f.readline() # 讀取cpt表 # 注意,文件中數據格式必須徹底按照指定要求,不可有多餘的空行或空格 cpts = [] for i in range(nums): probabilities = [] while True: line = f.readline()[:-1].split(' ') if line != ['']: probabilities.append(list(map(float, line))) else: break CPT = cpt(variables[i], [], probabilities) cpts.append(CPT) f.close() # 根據鄰接表爲每一個節點生成其父親節點 # 注意,這裏父親節點的順序是按照輸入的variables的順序排列的,不保證更換測試文件時的正確性 for i in range(nums): for j in range(nums): if graph[i][j] == 1: cpts[j].parents.append(variables[i]) # 測試父節點生成狀況 # for i in range(nums): # print(cpts[i].parents) bayesnet = BN(nums, variables, graph, cpts) return bayesnet # 讀取須要求取機率的命令 def readEvents(filename, variables): # 條件機率在本程序中的表示: # 對變量分類,2表示待求的變量,3表示隱含的須要被消去的變量,0和1表示條件變量的false和true # 例如變量爲[Burglar, Earthquake, Alarm, John, Mary] # 待求的條件機率爲P(Burglar | John=true, Mary=false),則event爲[2, 3, 3, 1, 0] f = open(filename, 'r') events = [] while True: line = f.readline() event = [] if line == "\n": continue elif not line: break else: for v in variables: index = line.find(v) if index != -1: if line[index+len(v)] == ' ' or line[index+len(v)] == ',': event.append(2) elif line[index+len(v)] == '=': if line[index+len(v)+1] == 't': event.append(1) else: event.append(0) else: event.append(3) # 檢查文本錯誤 if len(event) != len(variables): sys.exit() events.append(event) return events # 主程序 filename1 = "burglarnetwork.txt" bayesnet = readBN(filename1) filename2 = "burglarqueries.txt" events = readEvents(filename2, bayesnet.variables) for event in events: print(bayesnet.calculateProbability(event))
這一部分主要記錄在實驗過程當中參考的博客,方便以後複習
因爲沒有系統學過python,其中有挺多都是python基本技巧的,看來之後還要系統學一遍
python中判斷readline讀到文件末尾
這篇博客參考的是讀文件時如何判斷讀完
python 字符串和整數,浮點型互相轉換
這篇博客參考的是如何將從文件讀進來的文本轉化成數據
python-使用列表建立字典
這篇博客參考的是用list建立字典的方式
python在字符串中查找字符
在Python中,如何將一個字符串數組轉換成整型數組
Python-八、Python如何將整數轉化成二進制字符串
這三篇博客一樣是在處理讀入數據時參考的
Python3浮點型(float)運算結果不正確處理辦法
因爲多個浮點數的機率在連乘的時候,致使出現了較大偏差,所以查了這篇博客,不過最後沒有使用Decimal模塊,而是直接乘1000再除1000解決了。
Python 技巧(三)—— list 刪除一個元素的三種作法
python numpy查詢數組是否有某個數的總個數
這篇博客,我試了一下發現不能夠,報錯說不能夠對布爾類型求和,恐怕是python版本的問題吧,這個我暫時沒有深究,本身寫了一個count函數
python list中數字與一個數相乘
對於list中一個數字與一個數相乘的方法,網上廣泛給出的另外一種方法是用numpy庫,其生成的數組能夠直接與數相乘。然而因爲我全程沒有用到numpy,不想在這個地方單獨用個numpy,因此採用了本篇博客中的方法。
python反轉列表的三種方式
因爲實驗指導書指定的輸出結果與我算出來的相反,所以翻轉了一次列表
用一句話總結該實驗的做用:使我對於貝葉斯網絡的機率推導過程理解的更加透徹
作完實驗才意識到若是沒有手推幾個貝葉斯網絡的機率推導,那幾乎至關於沒有學,要是放到考試絕對寫不出來(想起了以前聽覺考試,平時沒有練習過手推隱馬爾科夫,致使考試的時候給了一個很簡單的HMM,最後因爲太不熟練致使時間不足而沒有寫完)
整個實驗過程比較順暢,總時間大體8小時左右,其中寫代碼時間很短,全程幾乎沒有遇到bug,花時間的地方在於如何設計表示條件機率。這個東西花了我特別長的時間,最後的形式我的感受不是特別簡潔,可是放在程序裏仍是挺好用的。