前言
在前面的時間裏已經學習過了NB樸素貝葉斯算法, 又剛剛初步的學習了貝葉斯網絡的一些基本概念和經常使用的計算方法。因而就有了上篇初識貝葉斯網絡的文章,因爲本人最近一直在研究學習<<貝葉斯網引論>>,也接觸到了許多與貝葉斯網絡相關的知識,能夠說樸素貝葉斯算法這些只是咱們所瞭解貝葉斯知識的很小的一部分。今天我要總結的學習成果就是基於NB算法的,叫作Tree Augmented Naive Bays,中文意思就是樹型樸素貝葉斯算法,簡單理解就是樹加強型NB算法,那麼問題來了,他是如何加強的呢,請繼續往下正文的描述。java
樸素貝葉斯算法
又得要從樸素貝葉斯算法開始講起了,由於在前言中已經說了,TAN算法是對NB算法的加強,瞭解過NB算法的,必定知道NB算法在使用的時候是假設屬性事件是相互獨立的,而決策屬性的分類結果是依賴於各個條件屬性的狀況的,最後選擇分類屬性中擁有最大後驗機率的值爲決策屬性。好比下面這個模型能夠描述一個簡單的模型,node
![](http://static.javashuo.com/static/loading.gif)
上面帳號是否真實的依賴屬性條件有3個,好友密度,是否使用真實頭像,日誌密度,假設這3個屬性是相互獨立的,可是事實上,在這裏的頭像是否真實和好友密度實際上是有關聯的,因此更加真實的狀況是下面這張狀況;android
![](http://static.javashuo.com/static/loading.gif)
OK,TAN的出現就解決了條件間的部分屬性依賴的問題。在上面的例子中咱們是根據本身的主觀意識判斷出頭像和好友密度的關係,可是在真實算法中,咱們固然但願機器可以本身根據所給數據集幫咱們得出這樣的關係,使人高興的事,TAN幫咱們作到了這點。算法
TAN算法
互信息值
互信息值,在百度百科中的解釋以下:數組
互信息值是信息論中一個有用的信息度量。它能夠看出是一個信息量裏包含另外一個隨機變量的信息量。網絡
用圖線來表示就是下面這樣。app
![](http://static.javashuo.com/static/loading.gif)
中間的I(x;y)就是互信息值,X,Y表明的2種屬性。因而下面這個屬性就很好理解了,互信息值越大,就表明2個屬性關聯性越大。互信息值的標準公式以下:ide
![](http://static.javashuo.com/static/loading.gif)
可是在TAN中會有少量的不同,會有類變量屬性的加入,由於屬性之間的關聯性的前提是要在某一分類屬性肯定下進行從新計算,不一樣的類屬性值會有不一樣的屬性關聯性。下面是TAN中的I(x;Y)計算公式:工具
![](http://static.javashuo.com/static/loading.gif)
如今看不懂沒關係,後面在給出的程序代碼中可自行調試。學習
算法實現過程
TAN的算法過程其實並不簡單,在計算完各個屬性對的互信息值以後,要進行貝葉斯網絡的構建,這個是TAN中最難的部分,這個部分有下面幾個階段。
一、根據各個屬性對的互信息值降序排序,依次取出其中的節點對,遵循不產生環路的原則,構造最大權重跨度樹,直到選擇完n-1條邊爲止(由於總共n個屬性節點,n-1條邊便可肯定)。按照互信息值從高到低選擇的緣由就是要保留關聯性更高的關聯依賴性的邊。
二、上述過程構成的是一個無向圖,接下來爲整個無向圖肯定邊的方向。選擇任意一個屬性節點做爲根節點,由根節點向外的方向爲屬性節點之間的方向。
三、爲每個屬性節點添加父節點,父節點就是分類屬性節點,至此貝葉斯網絡結構構造完畢。
爲了方便你們理解,我在網上截了幾張圖,下面這張是在5個屬性節點中優先選擇了互信息值最大的4條做爲無向圖:
![](http://static.javashuo.com/static/loading.gif)
上述帶了箭頭是由於,我選擇的A做爲樹的根節點,而後方向就所有肯定了,由於A直接連着4個屬性節點,而後再此基礎上添加父節點,就是下面這個樣子了。
![](http://static.javashuo.com/static/loading.gif)
OK,這樣應該就比較好理解了吧,若是還不理解,請仔細分析我寫的程序,從代碼中去理解這個過程也能夠。
分類結果機率的計算
分類結果機率的計算其實很是簡單,只要把查詢的條件屬性傳入分類模型中,而後計算不一樣類屬性下的機率值,擁有最大機率值的分類屬性值爲最終的分類結果。下面是計算公式,就是聯合機率分佈公式:
![](http://static.javashuo.com/static/loading.gif)
代碼實現
測試數據集input.txt:
[java] view plain copy
print?
- OutLook Temperature Humidity Wind PlayTennis
- Sunny Hot High Weak No
- Sunny Hot High Strong No
- Overcast Hot High Weak Yes
- Rainy Mild High Weak Yes
- Rainy Cool Normal Weak Yes
- Rainy Cool Normal Strong No
- Overcast Cool Normal Strong Yes
- Sunny Mild High Weak No
- Sunny Cool Normal Weak Yes
- Rainy Mild Normal Weak Yes
- Sunny Mild Normal Strong Yes
- Overcast Mild High Strong Yes
- Overcast Hot Normal Weak Yes
- Rainy Mild High Strong No
節點類Node.java:
[java] view plain copy
print?
- package DataMining_TAN;
-
- import java.util.ArrayList;
-
- /**
- * 貝葉斯網絡節點類
- *
- * @author lyq
- *
- */
- public class Node {
- //節點惟一id,方便後面節點鏈接方向的肯定
- int id;
- // 節點的屬性名稱
- String name;
- // 該節點所連續的節點
- ArrayList<Node> connectedNodes;
-
- public Node(int id, String name) {
- this.id = id;
- this.name = name;
-
- // 初始化變量
- this.connectedNodes = new ArrayList<>();
- }
-
- /**
- * 將自身節點鏈接到目標給定的節點
- *
- * @param node
- * 下游節點
- */
- public void connectNode(Node node) {
- //避免鏈接自身
- if(this.id == node.id){
- return;
- }
-
- // 將節點加入自身節點的節點列表中
- this.connectedNodes.add(node);
- // 將自身節點加入到目標節點的列表中
- node.connectedNodes.add(this);
- }
-
- /**
- * 判斷與目標節點是否相同,主要比較名稱是否相同便可
- *
- * @param node
- * 目標結點
- * @return
- */
- public boolean isEqual(Node node) {
- boolean isEqual;
-
- isEqual = false;
- // 節點名稱相同則視爲相等
- if (this.id == node.id) {
- isEqual = true;
- }
-
- return isEqual;
- }
- }
互信息值類.Java:
[java] view plain copy
print?
- package DataMining_TAN;
-
- /**
- * 屬性之間的互信息值,表示屬性之間的關聯性大小
- * @author lyq
- *
- */
- public class AttrMutualInfo implements Comparable<AttrMutualInfo>{
- //互信息值
- Double value;
- //關聯屬性值對
- Node[] nodeArray;
-
- public AttrMutualInfo(double value, Node node1, Node node2){
- this.value = value;
-
- this.nodeArray = new Node[2];
- this.nodeArray[0] = node1;
- this.nodeArray[1] = node2;
- }
-
- @Override
- public int compareTo(AttrMutualInfo o) {
- // TODO Auto-generated method stub
- return o.value.compareTo(this.value);
- }
-
- }
算法主程序類TANTool.java:
[java] view plain copy
print?
- package DataMining_TAN;
-
- import java.io.BufferedReader;
- import java.io.File;
- import java.io.FileReader;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.HashMap;
-
- /**
- * TAN樹型樸素貝葉斯算法工具類
- *
- * @author lyq
- *
- */
- public class TANTool {
- // 測試數據集地址
- private String filePath;
- // 數據集屬性總數,其中一個個分類屬性
- private int attrNum;
- // 分類屬性名
- private String classAttrName;
- // 屬性列名稱行
- private String[] attrNames;
- // 貝葉斯網絡邊的方向,數組內的數值爲節點id,從i->j
- private int[][] edges;
- // 屬性名到列下標的映射
- private HashMap<String, Integer> attr2Column;
- // 屬性,屬性對取值集合映射對
- private HashMap<String, ArrayList<String>> attr2Values;
- // 貝葉斯網絡總節點列表
- private ArrayList<Node> totalNodes;
- // 總的測試數據
- private ArrayList<String[]> totalDatas;
-
- public TANTool(String filePath) {
- this.filePath = filePath;
-
- readDataFile();
- }
-
- /**
- * 從文件中讀取數據
- */
- private void readDataFile() {
- File file = new File(filePath);
- ArrayList<String[]> dataArray = new ArrayList<String[]>();
-
- try {
- BufferedReader in = new BufferedReader(new FileReader(file));
- String str;
- String[] array;
-
- while ((str = in.readLine()) != null) {
- array = str.split(" ");
- dataArray.add(array);
- }
- in.close();
- } catch (IOException e) {
- e.getStackTrace();
- }
-
- this.totalDatas = dataArray;
- this.attrNames = this.totalDatas.get(0);
- this.attrNum = this.attrNames.length;
- this.classAttrName = this.attrNames[attrNum - 1];
-
- Node node;
- this.edges = new int[attrNum][attrNum];
- this.totalNodes = new ArrayList<>();
- this.attr2Column = new HashMap<>();
- this.attr2Values = new HashMap<>();
-
- // 分類屬性節點id最小設爲0
- node = new Node(0, attrNames[attrNum - 1]);
- this.totalNodes.add(node);
- for (int i = 0; i < attrNames.length; i++) {
- if (i < attrNum - 1) {
- // 建立貝葉斯網絡節點,每一個屬性一個節點
- node = new Node(i + 1, attrNames[i]);
- this.totalNodes.add(node);
- }
-
- // 添加屬性到列下標的映射
- this.attr2Column.put(attrNames[i], i);
- }
-
- String[] temp;
- ArrayList<String> values;
- // 進行屬性名,屬性值對的映射匹配
- for (int i = 1; i < this.totalDatas.size(); i++) {
- temp = this.totalDatas.get(i);
-
- for (int j = 0; j < temp.length; j++) {
- // 判斷map中是否包含此屬性名
- if (this.attr2Values.containsKey(attrNames[j])) {
- values = this.attr2Values.get(attrNames[j]);
- } else {
- values = new ArrayList<>();
- }
-
- if (!values.contains(temp[j])) {
- // 加入新的屬性值
- values.add(temp[j]);
- }
-
- this.attr2Values.put(attrNames[j], values);
- }
- }
- }
-
- /**
- * 根據條件互信息度對構建最大權重跨度樹,返回第一個節點爲根節點
- *
- * @param iArray
- */
- private Node constructWeightTree(ArrayList<Node[]> iArray) {
- Node node1;
- Node node2;
- Node root;
- ArrayList<Node> existNodes;
-
- existNodes = new ArrayList<>();
-
- for (Node[] i : iArray) {
- node1 = i[0];
- node2 = i[1];
-
- // 將2個節點進行鏈接
- node1.connectNode(node2);
- // 避免出現環路現象
- addIfNotExist(node1, existNodes);
- addIfNotExist(node2, existNodes);
-
- if (existNodes.size() == attrNum - 1) {
- break;
- }
- }
-
- // 返回第一個做爲根節點
- root = existNodes.get(0);
- return root;
- }
-
- /**
- * 爲樹型結構肯定邊的方向,方向爲屬性根節點方向指向其餘屬性節點方向
- *
- * @param root
- * 當前遍歷到的節點
- */
- private void confirmGraphDirection(Node currentNode) {
- int i;
- int j;
- ArrayList<Node> connectedNodes;
-
- connectedNodes = currentNode.connectedNodes;
-
- i = currentNode.id;
- for (Node n : connectedNodes) {
- j = n.id;
-
- // 判斷鏈接此2節點的方向是否被肯定
- if (edges[i][j] == 0 && edges[j][i] == 0) {
- // 若是沒有肯定,則制定方向爲i->j
- edges[i][j] = 1;
-
- // 遞歸繼續搜索
- confirmGraphDirection(n);
- }
- }
- }
-
- /**
- * 爲屬性節點添加分類屬性節點爲父節點
- *
- * @param parentNode
- * 父節點
- * @param nodeList
- * 子節點列表
- */
- private void addParentNode() {
- // 分類屬性節點
- Node parentNode;
-
- parentNode = null;
- for (Node n : this.totalNodes) {
- if (n.id == 0) {
- parentNode = n;
- break;
- }
- }
-
- for (Node child : this.totalNodes) {
- parentNode.connectNode(child);
-
- if (child.id != 0) {
- // 肯定鏈接方向
- this.edges[0][child.id] = 1;
- }
- }
- }
-
- /**
- * 在節點集合中添加節點
- *
- * @param node
- * 待添加節點
- * @param existNodes
- * 已存在的節點列表
- * @return
- */
- public boolean addIfNotExist(Node node, ArrayList<Node> existNodes) {
- boolean canAdd;
-
- canAdd = true;
- for (Node n : existNodes) {
- // 若是節點列表中已經含有節點,則算添加失敗
- if (n.isEqual(node)) {
- canAdd = false;
- break;
- }
- }
-
- if (canAdd) {
- existNodes.add(node);
- }
-
- return canAdd;
- }
-
- /**
- * 計算節點條件機率
- *
- * @param node
- * 關於node的後驗機率
- * @param queryParam
- * 查詢的屬性參數
- * @return
- */
- private double calConditionPro(Node node, HashMap<String, String> queryParam) {
- int id;
- double pro;
- String value;
- String[] attrValue;
-
- ArrayList<String[]> priorAttrInfos;
- ArrayList<String[]> backAttrInfos;
- ArrayList<Node> parentNodes;
-
- pro = 1;
- id = node.id;
- parentNodes = new ArrayList<>();
- priorAttrInfos = new ArrayList<>();
- backAttrInfos = new ArrayList<>();
-
- for (int i = 0; i < this.edges.length; i++) {
- // 尋找父節點id
- if (this.edges[i][id] == 1) {
- for (Node temp : this.totalNodes) {
- // 尋找目標節點id
- if (temp.id == i) {
- parentNodes.add(temp);
- break;
- }
- }
- }
- }
-
- // 獲取先驗屬性的屬性值,首先添加先驗屬性
- value = queryParam.get(node.name);
- attrValue = new String[2];
- attrValue[0] = node.name;
- attrValue[1] = value;
- priorAttrInfos.add(attrValue);
-
- // 逐一添加後驗屬性
- for (Node p : parentNodes) {
- value = queryParam.get(p.name);
- attrValue = new String[2];
- attrValue[0] = p.name;
- attrValue[1] = value;
-
- backAttrInfos.add(attrValue);
- }
-
- pro = queryConditionPro(priorAttrInfos, backAttrInfos);
-
- return pro;
- }
-
- /**
- * 查詢條件機率
- *
- * @param attrValues
- * 條件屬性值
- * @return
- */
- private double queryConditionPro(ArrayList<String[]> priorValues,
- ArrayList<String[]> backValues) {
- // 判斷是否知足先驗屬性值條件
- boolean hasPrior;
- // 判斷是否知足後驗屬性值條件
- boolean hasBack;
- int attrIndex;
- double backPro;
- double totalPro;
- double pro;
- String[] tempData;
-
- pro = 0;
- totalPro = 0;
- backPro = 0;
-
- // 跳過第一行的屬性名稱行
- for (int i = 1; i < this.totalDatas.size(); i++) {
- tempData = this.totalDatas.get(i);
-
- hasPrior = true;
- hasBack = true;
-
- // 判斷是否知足先驗條件
- for (String[] array : priorValues) {
- attrIndex = this.attr2Column.get(array[0]);
-
- // 判斷值是否知足條件
- if (!tempData[attrIndex].equals(array[1])) {
- hasPrior = false;
- break;
- }
- }
-
- // 判斷是否知足後驗條件
- for (String[] array : backValues) {
- attrIndex = this.attr2Column.get(array[0]);
-
- // 判斷值是否知足條件
- if (!tempData[attrIndex].equals(array[1])) {
- hasBack = false;
- break;
- }
- }
-
- // 進行計數統計,分別計算知足後驗屬性的值和同時知足條件的個數
- if (hasBack) {
- backPro++;
- if (hasPrior) {
- totalPro++;
- }
- } else if (hasPrior && backValues.size() == 0) {
- // 若是隻有先驗機率則爲純機率的計算
- totalPro++;
- backPro = 1.0;
- }
- }
-
- if (backPro == 0) {
- pro = 0;
- } else {
- // 計算總的機率=都發生機率/只發生後驗條件的時間機率
- pro = totalPro / backPro;
- }
-
- return pro;
- }
-
- /**
- * 輸入查詢條件參數,計算髮生機率
- *
- * @param queryParam
- * 條件參數
- * @return
- */
- public double calHappenedPro(String queryParam) {
- double result;
- double temp;
- // 分類屬性值
- String classAttrValue;
- String[] array;
- String[] array2;
- HashMap<String, String> params;
-
- result = 1;
- params = new HashMap<>();
-
- // 進行查詢字符的參數分解
- array = queryParam.split(",");
- for (String s : array) {
- array2 = s.split("=");
- params.put(array2[0], array2[1]);
- }
-
- classAttrValue = params.get(classAttrName);
- // 構建貝葉斯網絡結構
- constructBayesNetWork(classAttrValue);
-
- for (Node n : this.totalNodes) {
- temp = calConditionPro(n, params);
-
- // 爲了不出現條件機率爲0的現象,進行輕微矯正
- if (temp == 0) {
- temp = 0.001;
- }
-
- // 按照聯合機率公式,進行乘積運算
- result *= temp;
- }
-
- return result;
- }
-
- /**
- * 構建樹型貝葉斯網絡結構
- *
- * @param value
- * 類別量值
- */
- private void constructBayesNetWork(String value) {
- Node rootNode;
- ArrayList<AttrMutualInfo> mInfoArray;
- // 互信息度對
- ArrayList<Node[]> iArray;
-
- iArray = null;
- rootNode = null;
-
- // 在每次從新構建貝葉斯網絡結構的時候,清空原有的鏈接結構
- for (Node n : this.totalNodes) {
- n.connectedNodes.clear();
- }
- this.edges = new int[attrNum][attrNum];
-
- // 從互信息對象中取出屬性值對
- iArray = new ArrayList<>();
- mInfoArray = calAttrMutualInfoArray(value);
- for (AttrMutualInfo v : mInfoArray) {
- iArray.add(v.nodeArray);
- }
-
- // 構建最大權重跨度樹
- rootNode = constructWeightTree(iArray);
- // 爲無向圖肯定邊的方向
- confirmGraphDirection(rootNode);
- // 爲每一個屬性節點添加分類屬性父節點
- addParentNode();
- }
-
- /**
- * 給定分類變量值,計算屬性之間的互信息值
- *
- * @param value
- * 分類變量值
- * @return
- */
- private ArrayList<AttrMutualInfo> calAttrMutualInfoArray(String value) {
- double iValue;
- Node node1;
- Node node2;
- AttrMutualInfo mInfo;
- ArrayList<AttrMutualInfo> mInfoArray;
-
- mInfoArray = new ArrayList<>();
-
- for (int i = 0; i < this.totalNodes.size() - 1; i++) {
- node1 = this.totalNodes.get(i);
- // 跳過度類屬性節點
- if (node1.id == 0) {
- continue;
- }
-
- for (int j = i + 1; j < this.totalNodes.size(); j++) {
- node2 = this.totalNodes.get(j);
- // 跳過度類屬性節點
- if (node2.id == 0) {
- continue;
- }
-
- // 計算2個屬性節點之間的互信息值
- iValue = calMutualInfoValue(node1, node2, value);
- mInfo = new AttrMutualInfo(iValue, node1, node2);
- mInfoArray.add(mInfo);
- }
- }
-
- // 將結果進行降序排列,讓互信息值高的優先用於構建樹
- Collections.sort(mInfoArray);
-
- return mInfoArray;
- }
-
- /**
- * 計算2個屬性節點的互信息值
- *
- * @param node1
- * 節點1
- * @param node2
- * 節點2
- * @param vlaue
- * 分類變量值
- */
- private double calMutualInfoValue(Node node1, Node node2, String value) {
- double iValue;
- double temp;
- // 三種不一樣條件的後驗機率
- double pXiXj;
- double pXi;
- double pXj;
- String[] array1;
- String[] array2;
- ArrayList<String> attrValues1;
- ArrayList<String> attrValues2;
- ArrayList<String[]> priorValues;
- // 後驗機率,在這裏就是類變量值
- ArrayList<String[]> backValues;
-
- array1 = new String[2];
- array2 = new String[2];
- priorValues = new ArrayList<>();
- backValues = new ArrayList<>();
-
- iValue = 0;
- array1[0] = classAttrName;
- array1[1] = value;
- // 後驗屬性都是類屬性
- backValues.add(array1);
-
- // 獲取節點屬性的屬性值集合
- attrValues1 = this.attr2Values.get(node1.name);
- attrValues2 = this.attr2Values.get(node2.name);
-
- for (String v1 : attrValues1) {
- for (String v2 : attrValues2) {
- priorValues.clear();
-
- array1 = new String[2];
- array1[0] = node1.name;
- array1[1] = v1;
- priorValues.add(array1);
-
- array2 = new String[2];
- array2[0] = node2.name;
- array2[1] = v2;
- priorValues.add(array2);
-
- // 計算3種條件下的機率
- pXiXj = queryConditionPro(priorValues, backValues);
-
- priorValues.clear();
- priorValues.add(array1);
- pXi = queryConditionPro(priorValues, backValues);
-
- priorValues.clear();
- priorValues.add(array2);
- pXj = queryConditionPro(priorValues, backValues);
-
- // 若是出現其中一個計數機率爲0,則直接賦值爲0處理
- if (pXiXj == 0 || pXi == 0 || pXj == 0) {
- temp = 0;
- } else {
- // 利用公式計算針對此屬性值對組合的機率
- temp = pXiXj * Math.log(pXiXj / (pXi * pXj)) / Math.log(2);
- }
-
- // 進行和屬性值對組合的累加即爲整個屬性的互信息值
- iValue += temp;
- }
- }
-
- return iValue;
- }
- }
場景測試類client.java:
[java] view plain copy
print?
- package DataMining_TAN;
-
- /**
- * TAN樹型樸素貝葉斯算法
- *
- * @author lyq
- *
- */
- public class Client {
- public static void main(String[] args) {
- String filePath = "C:\\Users\\lyq\\Desktop\\icon\\input.txt";
- // 條件查詢語句
- String queryStr;
- // 分類結果機率1
- double classResult1;
- // 分類結果機率2
- double classResult2;
-
- TANTool tool = new TANTool(filePath);
- queryStr = "OutLook=Sunny,Temperature=Hot,Humidity=High,Wind=Weak,PlayTennis=No";
- classResult1 = tool.calHappenedPro(queryStr);
-
- queryStr = "OutLook=Sunny,Temperature=Hot,Humidity=High,Wind=Weak,PlayTennis=Yes";
- classResult2 = tool.calHappenedPro(queryStr);
-
- System.out.println(String.format("類別爲%s所求得的機率爲%s", "PlayTennis=No",
- classResult1));
- System.out.println(String.format("類別爲%s所求得的機率爲%s", "PlayTennis=Yes",
- classResult2));
- if (classResult1 > classResult2) {
- System.out.println("分類類別爲PlayTennis=No");
- } else {
- System.out.println("分類類別爲PlayTennis=Yes");
- }
- }
- }
結果輸出:
[java] view plain copy
print?
- 類別爲PlayTennis=No所求得的機率爲0.09523809523809525
- 類別爲PlayTennis=Yes所求得的機率爲3.571428571428571E-5
- 分類類別爲PlayTennis=No