最近学习Machine Learning ,发觉颇为有趣。准备使用相关算法来跑一跑基因组,做做机器人的控制。
决策树是一种颇为有趣的东西,其基本原理是所谓的Occam’s razor:优先选择拟合数据最简单的假设。于是自然的在java上写了个决策树玩玩。
废话少说,看看有趣的代码
- ID3(String[][] data,boolean[] value) throws IOException//通过一堆String和一个bool数组建立这个决策树,log是一些调试信息
- {
- int i,n;
- log=new PrintWriter(new FileWriter(new File(“fout.txt”)),true);
- Set<obje> dat=new HashSet<obje>();//Set 巨好用!
- for(i=0;i<data.length;i++)
- dat.add(new obje(data[i],value[i]));
- root=new node(dat);
- }
ID3是一种最原始的决策树,也就是我们今天谈论的对象。
写在决策树时候,我是这么搞递归建立过程。
- node(Set<obje> data)
- {
- int i=0,j,tem=0,n;
- double max=0;
- obje top=data.iterator().next();
- n=top.numv;
- no=++num;//节点编号,方便debug
- if(sam(data))//如果结论相同,那么就开心的得出决策树的决策啦
- {
- res=data.iterator().next().res;
- return;
- }
- double gas;
- for(i=0;i<n;i++)
- if(!top.seleted[i])//这个选项选过了那么就不要它了
- {
- gas=Gain(data,i);//选一个信息增益最好的点
- if(max<gas)
- {
- max=gas;
- tem=i;
- }
- }
- item=tem;
- if(max<1e-4)//如果信息增益都太小就无可可选了,倒有可能是数据有误差或者错误,此处是ID3基本算法的局限的,在改进中此行应该把结论以概率形式给出
- return;
- HashMap<String,HashSet<obje> > lf=new HashMap<String,HashSet<obje>>();//一个map拖一个set,是用来造树的,决策树以后难免遇到可怕的情况,还是提前hash了保证速度安全
- for(obje ij:data)//for_each每个选项
- if(lf.get(ij.field[item])==null)//是空的就把它放到对应选项的集合下。
- {
- HashSet<obje> bra=new HashSet<obje>();
- ij.seleted[item]=true;
- bra.add(ij);
- lf.put(ij.field[item],bra);
- }
- else//空的就创造一个集合
- {
- ij.seleted[item]=true;
- lf.get(ij.field[item]).add(ij);
- }
- Set<Map.Entry<String,HashSet<obje>>> lfs=lf.entrySet();//哈~颇为奇葩的建树方法,用一个map解决,这样树有几万个分岔也不怕!兄弟孩子表示效率不高的
- for(Map.Entry<String,HashSet<obje>> entry:lfs)
- leaf.put( entry.getKey(),new node(entry.getValue()) );
- }
- }
于是呢,我的建立树的任务就用new node这种来实现了,虽然这是一个技术问题,但是忍不住多提一句,发现面对对象编程和函数式编程颇有异曲同工之妙诶
比如,如下是我的haskell二叉树的建立
- — this is haskell code
- insert :: (Ord a) => a-> Tree a-> Tree a
- insert x Empty=leaf x
- insert x (Node v l r)
- |x>v =Node v l (insert x r)
- |x<v =Node v (insert x l) r
- |otherwise=Node x l r
是不是很像呢。。。
好了不跑题了,说说代码本身吧。
决策树是在一堆各种命题得出结论,总体感觉和K-Dimensions Tree部分类似,就是一堆特征找啊找,不过决策树用了一个叫信息熵的玩意(似乎是香农老爷子搞的东西?但是作为物理系学生总是想起来可爱的麦克斯韦小妖精),使得每次找一个最短的属性项来将集合划分。ID3每次计算一个叫做信息增益的东西,也就是上文的Gain,Gain越大的则说明这个选项的有效度越高,则可以取之为信。于是我们从Mitchell同志网站上找的数据拿来实验下(顺便提一句,cmu的这位大神把自己的书放到网上公开,而且他的样例程序都是看不懂的Lisp)
- protected double Gain(Set<obje> data,int k)
- {
- return Entropy0(data,k)+Entropy(data,k);
- }
Gain是这么一个东西,Entropy0是data对于某一属性的熵,Entropy是该属性对于每一值熵的和
- protected double Entropy0(Set<obje> data,int k)
- {
- HashMap <String,Integer> hm=new HashMap<String,Integer>();
- int len=data.size();
- int i=0;
- double res=0,temp;
- double up=0,down=0;
- for(obje obj:data)
- if(obj.res)
- up++;
- else
- down++;
- return -(up/(up+down)*log2(up/(up+down))
- +down/(up+down)*log2(down/(up+down)));
- }
- protected double Entropy(Set<obje> data,int k)
- {
- HashMap <String,HashSet<obje>> hm=new HashMap<String,HashSet<obje>>();
- double len=data.size();
- int i=0;
- double res=0,temp;
- for(obje tem:data)
- if(hm.get(tem.field[k])==null)
- {
- HashSet<obje> set=new HashSet<obje>();
- set.add(tem);
- hm.put(tem.field[k],set);
- }
- else
- hm.get(tem.field[k]).add(tem);
- Set<Map.Entry<String,HashSet<obje>>> set = hm.entrySet();
- double up,down;
- for(Map.Entry<String, HashSet<obje>> entry: set)
- {
- up=0;down=0;
- HashSet<obje> set2=entry.getValue();
- for(obje ob1:set2)
- {
- if(ob1.res)
- up++;
- else
- down++;
- }
- double t=up/(up+down)*log2(up/(up+down))
- +down/(up+down)*log2(down/(up+down));
- res+=(up+down)/len*t;
- }
- return res;
-
具体熵什么的去看Mitchell大神的书啦。。要打扎实基础哦(其实是困了改天再写)反正这玩意越大则数据就分化明显。
比如这个数据
- 0 1 2 3 4 5
- {“sunny”,“hot”,“high”,“weak”,“0”},
- {“sunny”,“hot”,“high”,“strong”,“0”},
- {“overcast”,“hot”,“normal”,“weak”,“1”},
- {“rain”,“mild”,“high”,“weak”,“1”},
- {“rain”,“cool”,“normal”,“weak”,“1”},
- {“rain”,“cool”,“normal”,“strong”,“0”},
- {“overcast”,“cool”,“normal”,“strong”,“1”},
- {“sunny”,“mild”,“high”,“weak”,“0”},
- {“sunny”,“cool”,“normal”,“weak”,“1”},
- {“rain”,“mild”,“normal”,“weak”,“1”},
- {“sunny”,“mild”,“normal”,“strong”,“1”},
- {“overcast”,“mild”,“high”,“strong”,“1”},
- {“overcast”,“hot”,“normal”,“weak”,“1”},
- {“rain”,“mild”,“high”,“strong”,“0”}
无妨认为(幻想为)是xuhao出去和妹纸约会的故事
0~4属性分别代表天气的各种情况,阳光,温度,湿度,风速,0or1代表是否(幻想)去约会。
然后Jre去找了mathematica简单的处理了自己的数据告诉我们这样的结果
嗯,一颗树!,这就是产生决策的树啦,我们可以看到,只要overcast了,xuhao就会不由自主,但还有其他可能。但是有几个有趣的问题,其一是我们更改下数据
比如,加一个错的!
加上这个
{“overcast”,”cool”,”normal”,”weak”,”0″}
(我们认为这个是错的哈,因为根据常识cool的天气更乐于出去玩,上面的判断这个无论如何也要去约妹纸,只能是其他原因比如吃豆角住医院这种不可抗拒因素引起的,那么就是误差了!)
但是呢,树就变的不可理解了
这是什么家伙!都是些什么结论嘛,本来约妹纸搞的莫名奇妙。
再比如,偷偷加一列
- {“1”,“sunny”,“hot”,“high”,“weak”,“0”},
- {“2”,“sunny”,“hot”,“high”,“strong”,“0”},
- {“3”,“overcast”,“hot”,“normal”,“weak”,“1”},
- {“4”,“rain”,“mild”,“high”,“weak”,“1”},
- {“5”,“rain”,“cool”,“normal”,“weak”,“1”},
- {“6”,“rain”,“cool”,“normal”,“strong”,“0”},
- {“7”,“overcast”,“cool”,“normal”,“strong”,“1”},
- {“8”,“sunny”,“mild”,“high”,“weak”,“0”},
- {“9”,“sunny”,“cool”,“normal”,“weak”,“1”},
- {“10”,“rain”,“mild”,“normal”,“weak”,“1”},
- {“11”,“sunny”,“mild”,“normal”,“strong”,“1”},
- {“12”,“overcast”,“mild”,“high”,“strong”,“1”},
- {“13”,“overcast”,“hot”,“normal”,“weak”,“1”},
- {“14”,“rain”,“mild”,“high”,“strong”,“0”}
毫无违和感,不过是加了个日期嘛,可是。。树变成了这样子
哟,果然日期的信息增益够大了,但是一点都不可爱了嘛!日期是完全正确的符合了数据,但是!这不是明显我们想要的预测。
再有。。上面挖了个坑在建树的27行毫不负责的打了个return,也就是说,只要数据错误就会蹦出来NullPointer一类的,这显然不是一个物理系学生所能容忍的严谨(大雾试验误差三倍也得给他分析成天时地利人和心情不好被妹纸甩了天上有飞碟下月有流星出现的误差,这种误差能不处理?)
于是有个叫C4.5的东西出现了。
困了。。欲知后事如何,请见下回分晓。
兄台你原来的linux手记哪里去了,戳进来想看看的。。。。