在http://blog.csdn.net/c_son/article/details/43956585 这篇文章中,介绍了KNN算法,同时也指出它无法给出任何数据的基础数据结构信息,以及它不能保存训练结果,每次分类必须重写计算一遍,这样带来的一个问题是如果数据量非常的庞大,这会大大降低该算法的执行效率。而这次的ID3算法正好可以解决此问题。
首先是数据集,这里和网上大多数例子一样,用了weka的weather数据集,如下:
@attribute outlook {sunny, overcast, rainy}
@attribute temperature {hot, mild, cool}
@attribute humidity {high, normal}
@attribute windy {TRUE, FALSE}
@attribute play {yes, no}
@data
sunny,hot,high,FALSE,no
sunny,hot,high,TRUE,no
overcast,hot,high,FALSE,yes
rainy,mild,high,FALSE,yes
rainy,cool,normal,FALSE,yes
rainy,cool,normal,TRUE,no
overcast,cool,normal,TRUE,yes
sunny,mild,high,FALSE,no
sunny,cool,normal,FALSE,yes
rainy,mild,normal,FALSE,yes
sunny,mild,normal,TRUE,yes
overcast,mild,high,TRUE,yes
overcast,hot,normal,FALSE,yes
rainy,mild,high,TRUE,no
考虑到以后使用其他格式的数据集文件,我在这里使用了一个接口,便于以后的扩展。
public interface InitDataSetDao {
/**
* @return 返回样本数据集中的每一条数据
*/
public List<String[]> getMeteDataSet();
/**
* @return 返回数据集中属性的名字
*/
public List<String> getAttrbuteNames();
/**
* @return map集合的键为每一个属性名称,值为属性值的集合
*/
public Map<String,List<String>> getAttrbuteOptions();
/**
* @return 得到目标属性的名称与值,第一项为属性名称其余为值
*/
public List<String> getDestinaltion();
}
建立好数据集之后,就是核心的创建决策树了。看到网上很多代码的例子都是针对上面的数据集进行硬编码式编程,为了以后代码的移植,我在这里花了些时间重构了下
private void buildDT(Element element, List<String[]> dataSet) {
// 要创建的节点的名字
String elementName = "";
double min = Double.MAX_VALUE;
// 计算每个属性熵值,选出最小值
for (int i = 0; i < initDataSet.getAttrbuteNames().size() - 1; i++) {
if (!skipAttrbuteList.contains(initDataSet.getAttrbuteNames().get(i))) {
String attrbuteName = initDataSet.getAttrbuteNames().get(i);
double sum = 0.0;
// 计算每个属性的熵
sum = calOptionEntropy(attrbuteName,dataSet);
// 找最小值,确定节点
if (sum < min) {
min = sum;
elementName = attrbuteName;
}
}
}
for (String attrbuteOption : initDataSet.getAttrbuteOptions().get(
elementName)) {
List<String[]> subDataSet = new ArrayList<String[]>();
//使用set集合的无重复的特性来判断是否是叶节点,如果是叶节点,则set集合长度为一
Set<String> set = new HashSet<String>();
// 目标属性在数据集中的位置
int decAttrbuteIndex = getValueIndex(initDataSet.getDestinaltion().get(
0));
for (String[] data : dataSet) {
if (data[getValueIndex(elementName)].equals(attrbuteOption)) {
subDataSet.add(data);
set.add(data[decAttrbuteIndex]);
}
}
// 创建节点
Element subElement = element.addElement(elementName).addAttribute(
"value", attrbuteOption);
//长度大于一则进行递归,否则输出
if (set.size() > 1) {
//将刚才生成的节点加入到已访问属性集合中
skipAttrbuteList.add(elementName);
//递归调用
buildDT(subElement,subDataSet);
} else {
for (String string : set) {
subElement.setText(string);
}
}
}
}
这里用到了信息熵的概念,来决定选取哪个特征作为分类基准,对于此算法这里不在深入。
为了直观看到生成后决策树的形式,我这里将其输出到xml文件中,输出结果如下
<?xml version="1.0" encoding="UTF-8"?>
<root>
<outlook value="sunny">
<humidity value="high">no</humidity>
<humidity value="normal">yes</humidity>
</outlook>
<outlook value="overcast">yes</outlook>
<outlook value="rainy">
<windy value="TRUE">no</windy>
<windy value="FALSE">yes</windy>
</outlook>
</root>
细心的读者可能发现,怎么少了温度这一项,仔细想想就会发现因为在建立决策树考虑温度之前,整个决策树已经完成,可以进行决策。这也可以说是最好的情况,因为可能遇到当所有决策变量都使用上后,任然无法做出唯一决策(可能有点晕,请读者细想一下这种情形),这时就需要依照概率去做出决策。对于这一点,我在程序里没有体现,只考虑了最好情况。
最后就是预测了,给定一种情形进行决策
public String pridictResult(Map<String, String> data) {
Element element = document.getRootElement();
List<Element> list = element.elements();
while(true) {
for (Element e : list) {
if (data.get(e.getName()).equals(e.attributeValue("value"))) {
list = e.elements();
if (list.size() == 0) {
return e.getText();
}else {
break;
}
}
}
}
}
整个程序的大致轮廓就是这样,如需详细代码请到如下下载地址进行下载。最后希望这篇文章可以给你带来帮助
算法不足:虽然决策树非常好地匹配了实验数据,然后这些匹配选项可能太多,我们将这种问题称之为过度匹配。为了减少过度匹配,我们可以裁剪决策树,去掉一些不必要的叶子节点。如果叶子节点只能增加少许信息,则我们可以删除该节点,将它并入到其他叶子节点中去。还有一个问题是ID3算法无法直接处理数值型数据,尽管我们可以通过量化的方法将数值型数据转化为标称性数据,但是如果存在过多的特征划分,ID3仍会面临其他问题。