前言
在前面的时间里已经学习过了NB朴素贝叶斯算法, 又刚刚初步的学习了贝叶斯网络的一些基本概念和经常使用的计算方法。因而就有了上篇初识贝叶斯网络的文章,因为本人最近一直在研究学习<<贝叶斯网引论>>,也接触到了许多与贝叶斯网络相关的知识,能够说朴素贝叶斯算法这些只是咱们所了解贝叶斯知识的很小的一部分。今天我要总结的学习成果就是基于NB算法的,叫作Tree Augmented Naive Bays,中文意思就是树型朴素贝叶斯算法,简单理解就是树加强型NB算法,那么问题来了,他是如何加强的呢,请继续往下正文的描述。java
朴素贝叶斯算法
又得要从朴素贝叶斯算法开始讲起了,由于在前言中已经说了,TAN算法是对NB算法的加强,了解过NB算法的,必定知道NB算法在使用的时候是假设属性事件是相互独立的,而决策属性的分类结果是依赖于各个条件属性的状况的,最后选择分类属性中拥有最大后验几率的值为决策属性。好比下面这个模型能够描述一个简单的模型,node

上面帐号是否真实的依赖属性条件有3个,好友密度,是否使用真实头像,日志密度,假设这3个属性是相互独立的,可是事实上,在这里的头像是否真实和好友密度实际上是有关联的,因此更加真实的状况是下面这张状况;android

OK,TAN的出现就解决了条件间的部分属性依赖的问题。在上面的例子中咱们是根据本身的主观意识判断出头像和好友密度的关系,可是在真实算法中,咱们固然但愿机器可以本身根据所给数据集帮咱们得出这样的关系,使人高兴的事,TAN帮咱们作到了这点。算法
TAN算法
互信息值
互信息值,在百度百科中的解释以下:数组
互信息值是信息论中一个有用的信息度量。它能够看出是一个信息量里包含另外一个随机变量的信息量。网络
用图线来表示就是下面这样。app

中间的I(x;y)就是互信息值,X,Y表明的2种属性。因而下面这个属性就很好理解了,互信息值越大,就表明2个属性关联性越大。互信息值的标准公式以下:ide

可是在TAN中会有少量的不同,会有类变量属性的加入,由于属性之间的关联性的前提是要在某一分类属性肯定下进行从新计算,不一样的类属性值会有不一样的属性关联性。下面是TAN中的I(x;Y)计算公式:工具

如今看不懂没关系,后面在给出的程序代码中可自行调试。学习
算法实现过程
TAN的算法过程其实并不简单,在计算完各个属性对的互信息值以后,要进行贝叶斯网络的构建,这个是TAN中最难的部分,这个部分有下面几个阶段。
一、根据各个属性对的互信息值降序排序,依次取出其中的节点对,遵循不产生环路的原则,构造最大权重跨度树,直到选择完n-1条边为止(由于总共n个属性节点,n-1条边便可肯定)。按照互信息值从高到低选择的缘由就是要保留关联性更高的关联依赖性的边。
二、上述过程构成的是一个无向图,接下来为整个无向图肯定边的方向。选择任意一个属性节点做为根节点,由根节点向外的方向为属性节点之间的方向。
三、为每个属性节点添加父节点,父节点就是分类属性节点,至此贝叶斯网络结构构造完毕。
为了方便你们理解,我在网上截了几张图,下面这张是在5个属性节点中优先选择了互信息值最大的4条做为无向图:

上述带了箭头是由于,我选择的A做为树的根节点,而后方向就所有肯定了,由于A直接连着4个属性节点,而后再此基础上添加父节点,就是下面这个样子了。

OK,这样应该就比较好理解了吧,若是还不理解,请仔细分析我写的程序,从代码中去理解这个过程也能够。
分类结果几率的计算
分类结果几率的计算其实很是简单,只要把查询的条件属性传入分类模型中,而后计算不一样类属性下的几率值,拥有最大几率值的分类属性值为最终的分类结果。下面是计算公式,就是联合几率分布公式:

代码实现
测试数据集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