数据挖掘中 决策树算法实现——Bash

 

一、决策树简介:

 

关于决策树,几乎是数据挖掘分类算法中最先介绍到的。

决策树,顾名思义就是用来做决定的树,一个分支就是一个决策过程。

 

每个决策过程中涉及一个数据的属性,而且只涉及一个。然后递归地,贪心地直到满足决策条件(即可以得到明确的决策结果)。

 

决策树的实现首先要有一些先验(已经知道结果的历史)数据做训练,通过分析训练数据得到每个属性对结果的影响的大小,这里我们通过一种叫做信息增益的理论去描述它,期间也涉及到的概念。也可参考文章信息增益与熵.

 

下面我们结合实例说一下决策树实现过程中的上述关键概念:

 

假设我们有如下数据:

 

age job house credit class
1 0 0 1 0
1 0 0 2 0
1 1 0 2 1
1 1 1 1 1
1 0 0 1 0
2 0 0 1 0
2 0 0 2 0
2 1 1 2 1
2 0 1 3 1
2 0 1 3 1
3 0 1 3 1
3 0 1 2 1
3 1 0 2 1
3 1 0 3 1
3 0 0 1 0

(一)

我们首先要通过计算找到哪个属性的所有属性值能更好地表达class字段的不同。通过计算,我们发现house的属性值最能表现class字段的不同。这个衡量标准其实就是信息增益。计算方法是:首先计算全部数据的,然后除class之外的其他属性逐个遍历,找到最小的那个属性(house),然后将全部数据的减去按照house属性划分数据之后的数据的

 

这个值如果满足条件假如(>0.1),我们认为数据应该按照这个节点进行分裂,也就是说这个属性(house)构成了我们的一次决策过程。

 

(二)

然后

在按照house分裂的每个数据集上,针对其他属性(house除外)进行与(一)相同的过程,直到信息增益不足以满足数据分裂的条件。

 

这样,我们就得到了一个关于属性数据划分的一棵树。可以作为class字段未知的数据的决策依据。

 

 

二、决策树代码实现:

 

具体计算代码如下:---假设上述数据我们保存为descision.dat文件,以及需要bash4.0及以上支持运行。

 

Bash代码  收藏代码
  1. #!/home/admin/bin/bash_bin/bash_4  
  2.   
  3. input=$1;  
  4.   
  5. if [ -z $input ]; then  
  6.     echo "please input the traning file";  
  7.     exit 1;  
  8. fi   
  9.   
  10. ## pre calculate the log2 value for the later calculate operation  
  11. declare -a log2;  
  12. logi=0;  
  13. records=$(cat $input | wc -l);  
  14. for i in `awk -v n=$records 'BEGIN{for(i=1;i<n;i++) print log(i)/log(2);}'`  
  15. do  
  16.     ((logi+=1));  
  17.     log2[$logi]=$i;  
  18. done  
  19.   
  20.   
  21. ## function for calculating the entropy for the given distribution of the class  
  22. function getEntropy {  
  23.     local input=`echo $1`;  
  24.     if [[ $input == *" "* ]]; then  
  25.         local current_entropy=0;  
  26.         local sum=0;  
  27.         local i;  
  28.         for i in $input  
  29.         do  
  30.             ((sum+=$i));  
  31.             current_entropy=$(awk -v n=$i -v l=${log2[$i]} -v o=$current_entropy 'BEGIN{print n*l+o}');  
  32.         done  
  33.         current_entropy=$(awk -v n=$current_entropy -v b=$sum -v l=${log2[$sum]} 'BEGIN{print n/b*-1+l;}')  
  34.         eval $2=$current_entropy;  
  35.     else  
  36.         eval $2=0;  
  37.     fi  
  38. }  
  39.   
  40.   
  41. ### the header title of the input data  
  42. declare -A header_info;  
  43. header=$(head -1 $input);  
  44. headers=(${header//,/ })  
  45. length=${#headers[@]};  
  46. for((i=0;i<length;i++))  
  47. do  
  48.     attr=${headers[$i]};  
  49.     header_info[$attr]=$i;  
  50. done  
  51.   
  52.   
  53.   
  54. ### the data content of the input data  
  55. data=${input}_dat;  
  56. sed -n '2,$p' $input > $data  
  57.   
  58.   
  59.   
  60. ## use an array to store the information of a descision tree  
  61. ## the node structure is {child,slibling,parent,attr,attr_value,leaf,class}  
  62. ## the root is a virtual node with none used attribute  
  63. ## only the leaf node has class flag and the "leaf,class" is meaningfull  
  64. ## the descision_tree  
  65. declare -a descision_tree;  
  66.   
  67. ## the root node with no child\slibing and anythings else  
  68. descision_tree[0]="0:0:0:N:N:0:0";  
  69.   
  70.   
  71. ## use recursive algrithm to build the tree   
  72. ## so we need a trace_stack to record the call level infomation  
  73. declare -a trace_stack;  
  74.   
  75. ## push the root node into the stack  
  76. trace_stack[0]=0;  
  77. stack_deep=1;  
  78.   
  79. ## begin to build the tree until the trace_stack is empty  
  80. while [ $stack_deep -ne 0 ]  
  81. do  
  82.     ((stack_deep-=1));  
  83.     current_node_index=${trace_stack[$stack_deep]};  
  84.     current_node=${descision_tree[$current_node_index]};  
  85.     current_node_struct=(${current_node//:/ });  
  86.   
  87.     ## select the current data set   
  88.     ## get used attr and their values  
  89.     attrs=${current_node_struct[3]};  
  90.     attrv=${current_node_struct[4]};  
  91.   
  92.     declare -a grepstra=();  
  93.   
  94.     if [ $attrs != "N" ];then  
  95.         attr=(${attrs//,/ });  
  96.         attrvs=(${attrv//,/ });  
  97.         attrc=${#attr[@]};  
  98.         for((i=0;i<attrc;i++))  
  99.         do  
  100.             a=${attr[$i]};  
  101.             index=${header_info[$a]};  
  102.             grepstra[$index]=${attrvs[$i]};  
  103.         done  
  104.     fi  
  105.   
  106.     for((i=0;i<length;i++))  
  107.     do  
  108.         if [ -z ${grepstra[$i]} ]; then  
  109.             grepstra[$i]=".*";  
  110.         fi  
  111.     done  
  112.     grepstrt=${grepstra[*]};  
  113.     grepstr=${grepstrt// /,};  
  114.     grep $grepstr $data > current_node_data  
  115.   
  116.     ## calculate the entropy before split the records  
  117.     entropy=0;  
  118.     input=`cat current_node_data | cut -d "," -f 5 | sort | uniq -c | sed 's/^ \+//g' | cut -d " " -f 1`  
  119.     getEntropy "$input" entropy;  
  120.   
  121.     ## calculate the entropy for each of the rest attrs  
  122.     ## and select the min one  
  123.     min_attr_entropy=1;   
  124.     min_attr_name="";  
  125.     min_attr_index=0;  
  126.     for((i=0;i<length-1;i++))  
  127.     do  
  128.         ## just use the rest attrs  
  129.         if [[ "$attrs" != *"${headers[$i]}"* ]]; then  
  130.             ## calculate the entropy for the current attr  
  131.             ### get the different values for the headers[$i]  
  132.             j=$((i+1));  
  133.             cut -d "," -f $j,$length current_node_data > tmp_attr_ds  
  134.             dist_values=`cut -d , -f 1 tmp_attr_ds | sort | uniq -c | sed 's/^ \+//g' | sed 's/ /,/g'`;  
  135.             totle=0;  
  136.             totle_entropy_attr=0;  
  137.             for k in $dist_values  
  138.             do  
  139.                 info=(${k//,/ });  
  140.                 ((totle+=${info[0]}));  
  141.                 cur_class_input=`grep "^${info[1]}," tmp_attr_ds | cut -d "," -f 2 | sort | uniq -c | sed 's/^ \+//g' | cut -d " " -f 1`  
  142.                 cur_attr_value_entropy=0;  
  143.                 getEntropy "$cur_class_input" cur_attr_value_entropy;  
  144.                 totle_entropy_attr=$(awk -v c=${info[0]} -v e=$cur_attr_value_entropy -v o=$totle_entropy_attr 'BEGIN{print c*e+o;}');  
  145.             done  
  146.             attr_entropy=$(awk -v e=$totle_entropy_attr -v c=$totle 'BEGIN{print e/c;}');  
  147.             if [ $(echo "$attr_entropy < $min_attr_entropy" | bc) = 1 ]; then  
  148.                 min_attr_entropy=$attr_entropy;  
  149.                 min_attr_name="${headers[$i]}";  
  150.                 min_attr_index=$j;  
  151.             fi  
  152.         fi  
  153.     done  
  154.   
  155.     ## calculate the gain between the original entropy of the current node   
  156.     ## and the entropy after split by the attribute which has the min_entropy  
  157.     gain=$(awk -v b=$entropy -v a=$min_attr_entropy 'BEGIN{print b-a;}');  
  158.   
  159.     ## when the gain is large than 0.1 and  then put it as a branch  
  160.     ##      and add the child nodes to the current node and push the index to the trace_stack  
  161.     ## otherwise make it as a leaf node and get the class flag  
  162.     ##      and do not push trace_stack  
  163.     if [ $(echo "$gain > 0.1" | bc)  = 1 ]; then  
  164.         ### get the attribute values  
  165.         attr_values_str=`cut -d , -f $min_attr_index current_node_data | sort | uniq`;  
  166.         attr_values=($attr_values_str);  
  167.   
  168.         ### generate the node and add to the tree and add their index to the trace_stack  
  169.         tree_store_length=${#descision_tree[@]};  
  170.         current_node_struct[0]=$tree_store_length;  
  171.         parent_node_index=$current_node_index;  
  172.          
  173.         attr_value_c=${#attr_values[@]};  
  174.         for((i=0;i<attr_value_c;i++))  
  175.         do  
  176.             tree_store_length=${#descision_tree[@]};  
  177.             slibling=0;  
  178.             if [ $i -lt $((attr_value_c-1)) ]; then  
  179.                 slibling=$((tree_store_length+1));  
  180.             fi  
  181.   
  182.             new_attr="";  
  183.             new_attrvalue="";  
  184.             if [ $attrs != "N" ]; then  
  185.                 new_attr="$attrs,$min_attr_name";  
  186.                 new_attrvalue="$attrv,${attr_values[$i]}";  
  187.             else  
  188.                 new_attr="$min_attr_name";  
  189.                 new_attrvalue="${attr_values[$i]}";  
  190.             fi  
  191.             new_node="0:$slibling:$parent_node_index:$new_attr:$new_attr_value:0:0";  
  192.             descision_tree[$tree_store_length]="$new_node";  
  193.             trace_stack[$stack_deep]=$tree_store_length;  
  194.             ((stack_deep+=1));  
  195.         done  
  196.         current_node_update=${current_node_struct[*]};  
  197.         descision_tree[$current_node_index]=${current_node_update// /:};  
  198.     else   ## current node is a leaf node   
  199.         current_node_struct[5]=1;  
  200.         current_node_struct[6]=`cut -d , -f $length current_node_data | sort | uniq -c | sort -n -r | head -1 | sed 's/^ \+[^ ]* //g'`;  
  201.         current_node_update=${current_node_struct[*]};  
  202.         descision_tree[$current_node_index]=${current_node_update// /:};  
  203.     fi   
  204.       
  205.     ## output the descision tree after every step for split or leaf node generater  
  206.     echo ${descision_tree[@]};  
  207. done  
 

执行代码:

 

Bash代码  收藏代码
  1. ./descision.sh descision.dat  

 执行结果为:

 

Java代码  收藏代码
  1. 1:0:0:N:N:0:0 0:2:0:house:0:0:0 0:0:0:house:1:0:0  
  2. 1:0:0:N:N:0:0 0:2:0:house:0:0:0 0:0:0:house:1:1:1  
  3. 1:0:0:N:N:0:0 3:2:0:house:0:0:0 0:0:0:house:1:1:1 0:4:1:house,job:0,0:0:0 0:0:1:house,job:0,1:0:0  
  4. 1:0:0:N:N:0:0 3:2:0:house:0:0:0 0:0:0:house:1:1:1 0:4:1:house,job:0,0:0:0 0:0:1:house,job:0,1:1:1  
  5. 1:0:0:N:N:0:0 3:2:0:house:0:0:0 0:0:0:house:1:1:1 0:4:1:house,job:0,0:1:0 0:0:1:house,job:0,1:1:1  

输出结果中展示了决策树结构生成过程的细节,以及生成过程中树的变化过程

 

本代码中使用了一维数组结构来存储整棵决策树,输出的先后顺序按照数组下标输出。

 

输出结果中最后一行表示最终的决策树。它表示的树形结构其实是:

 

决策树结果

这样看着是不是就爽多了。

 

说明:

关于上述决策树结果中其实有部分存在误导:

默认根节点是放在数组的第一个位置的,即索引值为0,而子节点中的child与sibling为0时并不表示指向跟节点,而是表示无意义,即没有子节点或兄弟节点。

 

该决策树所代表的分类规则:

根据该决策树输出,我们挖掘的规则是这样的:

首先根据house属性判断,当house属性为1时,走到索引为2的节点,此时该节点是叶子节点,预测值class为1.

当house属性为0时,接着按照job属性来判断,当job属性为0时走到索引为3的节点,预测值class为0。如果job属性为1时走到索引值为4的节点,此时预测值class为1.

 

posted @ 2014-04-02 15:12  Django's blog  阅读(611)  评论(0编辑  收藏  举报