决策树模型,其基本思想类似于if else的结构,即满足什么条件则将它判定为某一类,而这里的决策树的深度就类似于if else的深度。
决策树的问题焦点在于,对于一个拥有多维数据特征的数据点,如何选择合适的分类依据。例如一只鸡(两条腿,有翅膀,没有脚蹼。。。),一只鸭(两条腿,有翅膀,有脚蹼。。),等等,现在来了一只奇怪的生物(两条腿,有翅膀,没有脚蹼。。),如果先根据腿或翅膀来判断的话,根本无法判断它属于哪一种生物,而如果根据脚蹼来判断的话,立刻就能分辨出来。从这个例子中,想表达的就是决策树如果去抉择一种最合适的特征来得到不同的判决类。
本文是基于数据集信息熵最小的原则,来确定这种树的生长规则的。信息熵的背景,不多说,简而言之,越有序的系统熵越小,越无序的系统熵越大。其计算公式如下:
H(x) = E[I(xi)] = E[ log(2,1/p(xi)) ] = -∑p(xi)log(2,p(xi)) (i=1,2,..n)
其中p(xi)为xi样本在x总体中的取值概率(或统计学中的频率)。
在给出具体实现代码之前,我先给出此处用到的树结构。
/**
* Created by Song on 2017/1/4.
* 树节点,可序列化存储
*/
public class Node implements Serializable{
public Object element;
public Map<Object,Node> child;
}
之所以这样设计,是基于此处具体的应用环境。e在此应用环境中,element为String类型的特征名称,而Map中的每个键值对,键名代表着判决条件(连接两个节点的线的标称),值代表着下一个节点。
下面再给出,Java中对象序列化存储的部分代码(在测试时,我注释掉了),用于在通过训练集得到决策树结构之后,将该树保存在文件中,而不需要,每次都重新训练得到决策树结构。
Node root = handler.createTree(dataSet,featurelabels,labelStr);
//树结构存储
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:\\dectree.txt")));
oos.writeObject(root);
oos.flush();
oos.close();
//树结构读取
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:\\dectree.txt")));
Node tree = (Node) ois.readObject();
下面是决策树分类器的具体实现代码:
/**
* Created by Song on 2017/1/3.
* 决策树
*/
public class DectreeHandler {
/**
* 计算数据集的香农熵
* @param dataSet 数据集(最后一列为分类信息)
* @return 香农熵
*/
private static double calcShannonEnt(Matrix dataSet){
int m = dataSet.getRowDimension();
int n = dataSet.getColumnDimension();
double currentLabel = 0;
double shannonEnt = 0;
double rate = 0;
HashMap<Double,Integer> labelCounts = new HashMap<Double, Integer>();
//统计各类出现次数
for(int i=0;i<m;i++){
currentLabel = dataSet.get(i,n-1);
if(!labelCounts.containsKey(currentLabel))
labelCounts.put(currentLabel,0);
labelCounts.put(currentLabel,labelCounts.get(currentLabel)+1);
}
//计算整体香农熵
for(double key:labelCounts.keySet()){
rate =labelCounts.get(key)/(float)m;
shannonEnt -= rate*Math.log(rate)/Math.log(2);
}
return shannonEnt;
}
/**