前言
一看到貝葉斯網路,馬上讓人聯想到的是5個字,樸素貝葉斯,在所難免,NaiveByes的知名度確實會被貝葉斯網路演算法更高一點。其實不管是樸素貝葉斯演算法,還是今天我打算講述的貝葉斯網路演算法也罷,歸根結底來說都是貝葉斯系列分類演算法,他的核心思想就是基於機率學的知識進行分類判斷,至於分類得到底準不準,大家盡可以自己用資料集去測試測試。OK,下面進入正題--貝葉斯網路演算法。 樸素貝葉斯
一般我在介紹某種演算法之前,都事先會學習一下相關的演算法,以便於新演算法的學習,而與貝葉斯網路演算法相關性比較大的在我看來就是樸素貝葉斯演算法,而且前段時間也恰好學習過,簡單的來說,樸素貝葉斯演算法的假設條件是各個事件相互獨立,然後利用貝葉斯定理,做機率的計算,於是這個演算法的核心就是就是這個貝葉斯定理的運用了嘍,不錯,貝葉斯定理的確很有用,他是基於條件機率的先驗機率和後驗機率的轉換公式,這麼說有點抽象,下面是公式的運算式:
大學裡機率學的課本上都有介紹過的,這個公式的好處在於對於一些比較難直接得出的機率通過轉換後的機率計算可得,一般是把決策屬性值放在先驗屬性中,當做目標值,然後通過決策屬性值的後驗機率計算所得。具體請查看我的樸素貝葉斯演算法介紹。 貝葉斯網路
下面這個部分就是文章的主題了,貝葉斯網路,裡面有2個字非常關鍵,就是網路,網路代表的潛在意思有2點,第一是有結構的,第二存在關聯,我們可以馬上聯想到DAG有向非循環圖。不錯,存在關聯的這個特點就是與樸素貝葉斯演算法最大的一個不同點,因為樸素貝葉斯演算法在計算機率值上是假設各個事務屬性是相互獨立的,但是理性的思考一下,其實這個很難做到,任何事務,如果你仔細去想想,其實都還是有點聯絡的。比如這裡有個例子:
在SNS社區中檢驗帳號的真實性
如果用樸素貝葉斯來做的話,就會是這樣的假設:
i、真實帳號比非真實帳號平均具有更大的日誌密度、各大的好友密度以及更多的使用真實頭像。
ii、日誌密度、好友密度和是否使用真實頭像在帳號真實性給定的條件下是獨立的。
但是其實往深入一想,使用真實的頭像其實是會提高人家添加你為好友的機率的,所以在這個條件的獨立其實是有問題的,所以在貝葉斯網路中是允許關聯的存在的,假設就變為如下:
i、真實帳號比非真實帳號平均具有更大的日誌密度、各大的好友密度以及更多的使用真實頭像。
ii、日誌密度與好友密度、日誌密度與是否使用真實頭像在帳號真實性給定的條件下是獨立的。
iii、使用真實頭像的使用者比使用非真實頭像的使用者平均有更大的好友密度。
在貝葉斯網路中,會用一張DAG來表示,每個節點代表某個屬性事件,每條邊代表其中的條件機率,如下:
貝葉斯網路機率的計算
貝葉斯網路機率的計算很簡單,是從聯合機率分布公式中變換所得,下面是聯合機率分布公式:
而在貝葉斯網路中,由於存在前述的關係存在,該公式就被簡化為了如下:
其中Parent(xi),表示的是xi的前驅結點,如果還不理解,可以對照我後面的代碼,自行調試分析。 代碼實現
需要輸入2部分的資料,依賴關係,用於構建貝葉斯網狀圖,第二個是測試資料集,演算法總代碼地址:
https://github.com/linyiqun/DataMiningAlgorithm/tree/master/Others/DataMining_BayesNetwork
依賴關係資料如下:
B AE AA MA J
測試資料集:
B E A M J Py y y y y 0.00012y y y y n 0.000051y y y n y 0.000013y y y n n 0.0000057y y n y y 0.000000005y y n y n 0.00000049y y n n y 0.000000095y y n n n 0.0000094y n y y y 0.0058y n y y n 0.0025y n y n y 0.00065y n y n n 0.00028y n n y y 0.00000029y n n y n 0.000029y n n n y 0.0000056y n n n n 0.00055n y y y y 0.0036n y y y n 0.0016n y y n y 0.0004n y y n n 0.00017n y n y y 0.000007n y n y n 0.00069n y n n y 0.00013n y n n n 0.013n n y y y 0.00061n n y y n 0.00026n n y n y 0.000068n n y n n 0.000029n n n y y 0.00048n n n y n 0.048n n n n y 0.0092n n n n n 0.91
節點類Node.java:
package DataMining_BayesNetwork;import java.util.ArrayList;/** * 貝葉斯網路節點類 * * @author lyq * */public class Node {// 節點的屬性名稱String name;// 節點的父親節點,也就是上遊節點,可能多個ArrayList<Node> parentNodes;// 節點的子節點,也就是下遊節點,可能多個ArrayList<Node> childNodes;public Node(String name) {this.name = name;// 初始設定變數this.parentNodes = new ArrayList<>();this.childNodes = new ArrayList<>();}/** * 將自身節點串連到目標給定的節點 * * @param node * 下遊節點 */public void connectNode(Node node) {// 將下遊節點加入自身節點的孩子節點中this.childNodes.add(node);// 將自身節點加入到下遊節點的父節點中node.parentNodes.add(this);}/** * 判斷與目標節點是否相同,主要比較名稱是否相同即可 * * @param node * 目標結點 * @return */public boolean isEqual(Node node) {boolean isEqual;isEqual = false;// 節點名稱相同則視為相等if (this.name.equals(node.name)) {isEqual = true;}return isEqual;}}
演算法類BayesNetworkTool.java:
package DataMining_BayesNetwork;import java.io.BufferedReader;import java.io.File;import java.io.FileReader;import java.io.IOException;import java.util.ArrayList;import java.util.HashMap;/** * 貝葉斯網路演算法工具類 * * @author lyq * */public class BayesNetWorkTool {// 聯合機率分布資料檔案地址private String dataFilePath;// 事件關聯資料檔案地址private String attachFilePath;// 屬性列列數private int columns;// 機率分布資料private String[][] totalData;// 關聯資料對private ArrayList<String[]> attachData;// 節點存放列表private ArrayList<Node> nodes;// 屬性名稱與列數之間的對應關係private HashMap<String, Integer> attr2Column;public BayesNetWorkTool(String dataFilePath, String attachFilePath) {this.dataFilePath = dataFilePath;this.attachFilePath = attachFilePath;initDatas();}/** * 初始化關聯資料和機率分布資料 */private void initDatas() {String[] columnValues;String[] array;ArrayList<String> datas;ArrayList<String> adatas;// 從檔案中讀取資料datas = readDataFile(dataFilePath);adatas = readDataFile(attachFilePath);columnValues = datas.get(0).split(" ");// 屬性割名稱代表事件B(盜竊),E(地震),A(警鈴響).M(接到M的電話),J同M的意思,// 屬性值都是y,n代表yes發生和no不發生this.attr2Column = new HashMap<>();for (int i = 0; i < columnValues.length; i++) {// 從資料中取出屬性名稱行,列數值存入圖中this.attr2Column.put(columnValues[i], i);}this.columns = columnValues.length;this.totalData = new String[datas.size()][columns];for (int i = 0; i < datas.size(); i++) {this.totalData[i] = datas.get(i).split(" ");}this.attachData = new ArrayList<>();// 解析關聯資料對for (String str : adatas) {array = str.split(" ");this.attachData.add(array);}// 構造貝葉斯網路結構圖constructDAG();}/** * 從檔案中讀取資料 */private ArrayList<String> readDataFile(String filePath) {File file = new File(filePath);ArrayList<String> dataArray = new ArrayList<String>();try {BufferedReader in = new BufferedReader(new FileReader(file));String str;while ((str = in.readLine()) != null) {dataArray.add(str);}in.close();} catch (IOException e) {e.getStackTrace();}return dataArray;}/** * 根據關聯資料構造貝葉斯網路無環有向圖 */private void constructDAG() {// 節點存在標識boolean srcExist;boolean desExist;String name1;String name2;Node srcNode;Node desNode;this.nodes = new ArrayList<>();for (String[] array : this.attachData) {srcExist = false;desExist = false;name1 = array[0];name2 = array[1];// 建立節點srcNode = new Node(name1);desNode = new Node(name2);for (Node temp : this.nodes) {// 如果找到相同節點,則取出if (srcNode.isEqual(temp)) {srcExist = true;srcNode = temp;} else if (desNode.isEqual(temp)) {desExist = true;desNode = temp;}// 如果2個節點都已找到,則跳出迴圈if (srcExist && desExist) {break;}}// 將2個節點進行串連srcNode.connectNode(desNode);// 根據標識判斷是否需要加入列表容器中if (!srcExist) {this.nodes.add(srcNode);}if (!desExist) {this.nodes.add(desNode);}}}/** * 查詢條件機率 * * @param attrValues * 條件屬性值 * @return */private double queryConditionPro(ArrayList<String[]> attrValues) {// 判斷是否滿足先驗屬性值條件boolean hasPrior;// 判斷是否滿足後驗屬性值條件boolean hasBack;int priorIndex;int attrIndex;double backPro;double totalPro;double pro;double currentPro;// 先驗屬性String[] priorValue;String[] tempData;pro = 0;totalPro = 0;backPro = 0;attrValues.get(0);priorValue = attrValues.get(0);// 得到後驗機率attrValues.remove(0);// 取出先驗屬性的列數priorIndex = this.attr2Column.get(priorValue[0]);// 跳過第一行的屬性名稱行for (int i = 1; i < this.totalData.length; i++) {tempData = this.totalData[i];hasPrior = false;hasBack = true;// 當前行的機率currentPro = Double.parseDouble(tempData[this.columns - 1]);// 判斷是否滿足先驗條件if (tempData[priorIndex].equals(priorValue[1])) {hasPrior = true;}for (String[] array : attrValues) {attrIndex = this.attr2Column.get(array[0]);// 判斷值是否滿足條件if (!tempData[attrIndex].equals(array[1])) {hasBack = false;break;}}// 進行計數統計,分別計算滿足後驗屬性的值和同時滿足條件的個數if (hasBack) {backPro += currentPro;if (hasPrior) {totalPro += currentPro;}} else if (hasPrior && attrValues.size() == 0) {// 如果只有先驗機率則為純機率的計算totalPro += currentPro;backPro = 1.0;}}// 計算總的機率=都發生機率/只發生後驗條件的時間機率pro = totalPro / backPro;return pro;}/** * 根據貝葉斯網路計算機率 * * @param queryStr * 查詢條件串 * @return */public double calProByNetWork(String queryStr) {double temp;double pro;String[] array;// 先驗條件值String[] preValue;// 後驗條件值String[] backValue;// 所有先驗條件和後驗條件值的屬性值的匯總ArrayList<String[]> attrValues;// 判斷是否滿足網路結構if (!satisfiedNewWork(queryStr)) {return -1;}pro = 1;// 首先做查詢條件的分解array = queryStr.split(",");// 機率的初值等於第一個事件發生的隨機機率attrValues = new ArrayList<>();attrValues.add(array[0].split("="));pro = queryConditionPro(attrValues);for (int i = 0; i < array.length - 1; i++) {attrValues.clear();// 下標小的在前面的屬於後驗屬性backValue = array[i].split("=");preValue = array[i + 1].split("=");attrValues.add(preValue);attrValues.add(backValue);// 算出此種情況的機率值temp = queryConditionPro(attrValues);// 進行積的相乘pro *= temp;}return pro;}/** * 驗證事件的查詢因果關係是否滿足貝葉斯網路 * * @param queryStr * 查詢字串 * @return */private boolean satisfiedNewWork(String queryStr) {String attrName;String[] array;boolean isExist;boolean isSatisfied;// 當前節點Node currentNode;// 候選節點列表ArrayList<Node> nodeList;isSatisfied = true;currentNode = null;// 做查詢字串的分解array = queryStr.split(",");nodeList = this.nodes;for (String s : array) {// 開始時預設屬性對應的節點不存在isExist = false;// 得到屬性事件名attrName = s.split("=")[0];for (Node n : nodeList) {if (n.name.equals(attrName)) {isExist = true;currentNode = n;// 下一輪的候選節點為當前節點的孩子節點nodeList = currentNode.childNodes;break;}}// 如果存在未找到的節點,則說明不滿足依賴結構跳出迴圈if (!isExist) {isSatisfied = false;break;}}return isSatisfied;}}情境測試類別Client.java:
package DataMining_BayesNetwork;import java.text.MessageFormat;/** * 貝葉斯網路情境測試類別 * * @author lyq * */public class Client {public static void main(String[] args) {String dataFilePath = "C:\\Users\\lyq\\Desktop\\icon\\input.txt";String attachFilePath = "C:\\Users\\lyq\\Desktop\\icon\\attach.txt";// 查詢串語句String queryStr;// 結果機率double result;// 查詢語句的描述的事件是地震發生了,導致響鈴響了,導致接到Mary的電話queryStr = "E=y,A=y,M=y";BayesNetWorkTool tool = new BayesNetWorkTool(dataFilePath,attachFilePath);result = tool.calProByNetWork(queryStr);if (result == -1) {System.out.println("所描述的事件不滿足貝葉斯網路的結構,無法求其機率");} else {System.out.println(String.format("事件%s發生的機率為%s", queryStr, result));}}}輸出結果:
事件E=y,A=y,M=y發生的機率為0.005373075715453122
參考文獻
百度百科
http://www.cnblogs.com/leoo2sk/archive/2010/09/18/bayes-network.html
更多資料採礦演算法
https://github.com/linyiqun/DataMiningAlgorithm