机器学习算法-决策树C4.5练习

来源:互联网 发布:安卓游戏源码下载 编辑:程序博客网 时间:2024/06/05 10:08

本文转载http://www.cnblogs.com/michaelGD/archive/2012/11/14/2770758.html

程序的源码可以点击这里进行下载,下面简要介绍一下决策树以及相关算法概念。

  决策树是一个预测模型;他代表的是对象属性与对象值之间的一种映射关系。树中每个节点表示某个对象,而每个分叉路径则代表的某个可能的属性值,而每个叶结点则对应从根节点到该叶节点所经历的路径所表示的对象的值。决策树仅有单一输出,若欲有复数输出,可以建立独立的决策树以处理不同输出。 数据挖掘中决策树是一种经常要用到的技术,可以用于分析数据,同样也可以用来作预测(就像上面的银行官员用他来预测贷款风险)。从数据产生决策树的机器学习技术叫做决策树学习, 通俗说就是决策树。(来自维基百科)

  1986年Quinlan提出了著名的ID3算法。在ID3算法的基础上,1993年Quinlan又提出了C4.5算法。为了适应处理大规模数据集的需要,后来又提出了若干改进的算法,其中SLIQ (super-vised learning in quest)和SPRINT (scalable parallelizableinduction of decision trees)是比较有代表性的两个算法,此处暂且略过。

  本文实现了C4.5的算法,在ID3的基础上计算信息增益,从而更加准确的反应信息量。其实通俗的说就是构建一棵加权的最短路径Haffman树,让权值最大的节点为父节点。

  下面简要介绍一下ID3算法:

  ID3算法的核心是:在决策树各级结点上选择属性时,用信息增益(information gain)作为属性的选择标准,以使得在每一个非叶结点进行测试时,能获得关于被测试记录最大的类别信息。

  其具体方法是:检测所有的属性,选择信息增益最大的属性产生决策树结点,由该属性的不同取值建立分支,再对各分支的子集递归调用该方法建立决策树结点的分支,直到所有子集仅包含同一类别的数据为止。最后得到一棵决策树,它可以用来对新的样本进行分类。

  某属性的信息增益按下列方法计算:

  信息熵是香农提出的,用于描述信息不纯度(不稳定性),其计算公式是Info(D)。

  其中:Pi为子集合中不同性(而二元分类即正样例和负样例)的样例的比例;j是属性A中的索引,D是集合样本,Dj是D中属性A上值等于j的样本集合。

      这样信息收益可以定义为样本按照某属性划分时造成熵减少的期望,可以区分训练样本中正负样本的能力。信息增益定义为结点与其子结点的信息熵之差,公式为Gain(A)。

  ID3算法的优点是:算法的理论清晰,方法简单,学习能力较强。其缺点是:只对比较小的数据集有效,且对噪声比较敏感,当训练数据集加大时,决策树可能会随之改变。

  C4.5算法继承了ID3算法的优点,并在以下几方面对ID3算法进行了改进:

  1) 用信息增益率来选择属性,克服了用信息增益选择属性时偏向选择取值多的属性的不足,公式为GainRatio(A);

  2) 在树构造过程中进行剪枝;

  3) 能够完成对连续属性的离散化处理;

  4) 能够对不完整数据进行处理。

  C4.5算法与其它分类算法如统计方法、神经网络等比较起来有如下优点:产生的分类规则易于理解,准确率较高。其缺点是:在构造树的过程中,需要对数据集进行多次的顺序扫描和排序,因而导致算法的低效。此外,C4.5只适合于能够驻留于内存的数据集,当训练集大得无法在内存容纳时程序无法运行。

  实现的C4.5数据集合如下:

  它记录了再不同的天气状况下,是否出去觅食的数据。

  程序引入状态树作为统计和计算属性的数据结构,它记录了每次计算后,各个属性的统计数据,其定义如下:

复制代码
 1 struct attrItem 2 { 3    std::vector<int>  itemNum;  //itemNum[0] = itemLine.size() 4                                //itemNum[1] = decision num 5    set<int>          itemLine; 6 }; 7 
8 struct attributes 9 {10 string attriName;11 vector<double> statResult;12 map<string, attrItem*> attriItem;13 }; 14 15 vector<attributes*> statTree;
复制代码

  决策树节点数据结构如下:

复制代码
1 struct TreeNode 2 {3     std::string               m_sAttribute;4     int                       m_iDeciNum;5     int                       m_iUnDecinum;6     std::vector<TreeNode*>    m_vChildren;    7 };
复制代码

  程序源码如下所示(程序中有详细注解):

复制代码
  1 #include "DecisionTree.h"  2   3 int main(int argc, char* argv[]){  4     string filename = "source.txt";  5     DecisionTree dt ;  6     int attr_node = 0;  7     TreeNode* treeHead = nullptr;  8     set<int> readLineNum;  9     vector<int> readClumNum; 10     int deep = 0; 11     if (dt.pretreatment(filename, readLineNum, readClumNum) == 0) 12     { 13         dt.CreatTree(treeHead, dt.getStatTree(), dt.getInfos(), readLineNum, readClumNum, deep); 14     } 15     return 0; 16 } 17 /* 18 * @function CreatTree 预处理函数,负责读入数据,并生成信息矩阵和属性标记 19 * @param: filename 文件名 20 * @param: readLineNum 可使用行set 21 * @param: readClumNum 可用属性set 22 * @return int 返回函数执行状态 23 */ 24 int DecisionTree::pretreatment(string filename, set<int>& readLineNum, vector<int>& readClumNum) 25 { 26     ifstream read(filename.c_str()); 27     string itemline = ""; 28     getline(read, itemline); 29     istringstream iss(itemline); 30     string attr = ""; 31     while(iss >> attr) 32     { 33         attributes* s_attr = new attributes(); 34         s_attr->attriName = attr; 35         //初始化属性名 36         statTree.push_back(s_attr); 37         //初始化属性映射 38         attr_clum[attr] = attriNum; 39         attriNum++; 40         //初始化可用属性列 41         readClumNum.push_back(0); 42         s_attr = nullptr; 43     } 44  45     int i  = 0; 46     //添加具体数据 47     while(true) 48     { 49         getline(read, itemline); 50         if(itemline == "" || itemline.length() <= 1) 51         { 52             break; 53         } 54         vector<string> infoline; 55         istringstream stream(itemline); 56         string item = ""; 57         while(stream >> item) 58         { 59             infoline.push_back(item); 60         } 61  62         infos.push_back(infoline); 63         readLineNum.insert(i); 64         i++; 65     } 66     read.close(); 67     return 0; 68 } 69  70 int DecisionTree::statister(vector<vector<string>>& infos, vector<attributes*>& statTree,  71                             set<int>& readLine, vector<int>& readClumNum) 72 { 73     //yes的总行数 74     int deciNum = 0; 75     //统计每一行 76     set<int>::iterator iter_end = readLine.end(); 77     for (set<int>::iterator line_iter = readLine.begin(); line_iter != iter_end; ++line_iter) 78     { 79         bool decisLine = false; 80         if (infos[*line_iter][attriNum - 1] == "yes") 81         { 82             decisLine = true; 83             deciNum++; 
84 } 85 //如果该列未被锁定并且为属性列,进行统计 86 for (int i = 0; i < attriNum - 1; i++) 87 { 88 if (readClumNum[i] == 0) 89 { 90 std::string tempitem = infos[*line_iter][i]; 91 auto map_iter = statTree[i]->attriItem.find(tempitem); 92 //没有找到 93 if (map_iter == (statTree[i]->attriItem).end()) 94 { 95 //新建 96 attrItem* attritem = new attrItem(); 97 attritem->itemNum.push_back(1); 98 decisLine ? attritem->itemNum.push_back(1) : attritem->itemNum.push_back(0); 99 attritem->itemLine.insert(*line_iter);100 //建立属性名->item映射101 (statTree[i]->attriItem)[tempitem] = attritem;102 attritem = nullptr;103 }104 else105 {106 (map_iter->second)->itemNum[0]++;107 (map_iter->second)->itemLine.insert(*line_iter);108 if(decisLine)109 {110 (map_iter->second)->itemNum[1]++;111 }112 }113 }114 }115 }116 return deciNum;117 }118 119 /*120 * @function CreatTree 递归DFS创建并输出决策树121 * @param: treeHead 为生成的决定树122 * @param: statTree 为状态树,此树动态更新,但是由于是DFS对数据更新,所以不必每次新建状态树123 * @param: infos 数据信息124 * @param: readLine 当前在infos中所要进行统计的行数,由函数外给出125 * @param: deep 决定树的深度,用于打印126 * @return void127 */128 void DecisionTree::CreatTree(TreeNode* treeHead, vector<attributes*>& statTree, vector<vector<string>>& infos, 129 set<int>& readLine, vector<int>& readClumNum, int deep)130 {131 //有可统计的行132 if (readLine.size() != 0)133 {134 string treeLine = "";135 for (int i = 0; i < deep; i++)136 {137 treeLine += "--";138 }139 //清空其他属性子树,进行递归140 resetStatTree(statTree, readClumNum);141 //统计当前readLine中的数据:包括统计哪几个属性、哪些行,142 //并生成statTree(由于公用一个statTree,所有用引用代替),并返回目的信息数143 int deciNum = statister(getInfos(), statTree, readLine, readClumNum);144 int lineNum = readLine.size();145 int attr_node = compuDecisiNote(statTree, deciNum, lineNum, readClumNum);//本条复制为局部变量146 //该列被锁定147 readClumNum[attr_node] = 1;148 //建立树根149 TreeNode* treeNote = new TreeNode();150 treeNote->m_sAttribute = statTree[attr_node]->attriName;151 treeNote->m_iDeciNum = deciNum;152 treeNote->m_iUnDecinum = lineNum - deciNum;153 if (treeHead == nullptr)154 {155 treeHead = treeNote; //树根156 }157 else158 {159 treeHead->m_vChildren.push_back(treeNote); //子节点160 }161 cout << "节点-"<< treeLine << ">" << statTree[attr_node]->attriName << endl;162 163 //从孩子分支进行递归164 for(map<string, attrItem*>::iterator map_iterator = statTree[attr_node]->attriItem.begin();165 map_iterator != statTree[attr_node]->attriItem.end(); ++map_iterator)166 {167 //打印分支168 int sum = map_iterator->second->itemNum[0];169 int deci_Num = map_iterator->second->itemNum[1];170 cout << "分支--"<< treeLine << ">" << map_iterator->first << endl;171 //递归计算、创建172 if (deci_Num != 0 && sum != deci_Num )173 {174 //计算有效行数175 set<int> newReadLineNum = map_iterator->second->itemLine;176 //DFS177 CreatTree(treeNote, statTree, infos, newReadLineNum, readClumNum, deep + 1);178 }179 else180 {181 //建立叶子节点182 TreeNode* treeEnd = new TreeNode();183 treeEnd->m_sAttribute = statTree[attr_node]->attriName;184 treeEnd->m_iDeciNum = deci_Num;185 treeEnd->m_iUnDecinum = sum - deci_Num;186 treeNote->m_vChildren.push_back(treeEnd);187 //打印叶子188 if (deci_Num == 0)189 {190 cout << "叶子---"<< treeLine << ">no" << endl;191 }192 else193 {194 cout << "叶子---"<< treeLine << ">yes" << endl;195 }196 }197 }198 //还原属性列可用性199 readClumNum[attr_node] = 0;200 }201 }202 /*203 * @function compuDecisiNote 计算C4.5204 * @param: statTree 为状态树,此树动态更新,但是由于是DFS对数据更新,所以不必每次新建状态树205 * @param: deciNum Yes的数据量206 * @param: lineNum 计算set的行数207 * @param: readClumNum 用于计算的set208 * @return int 信息量最大的属性号209 */210 int DecisionTree::compuDecisiNote(vector<attributes*>& statTree, int deciNum, int lineNum, vector<int>& readClumNum)211 {212 double max_temp = 0;213 int max_attribute = 0;214 //总的yes行的信息量215 double infoD = info_D(deciNum, lineNum);216 for (int i = 0; i < attriNum - 1; i++)217 {218 if (readClumNum[i] == 0)219 {220 double splitInfo = 0.0;221 //info222 double info_temp = Info_attr(statTree[i]->attriItem, splitInfo, lineNum);223 statTree[i]->statResult.push_back(info_temp);224 //gain225 double gain_temp = infoD - info_temp;226 statTree[i]->statResult.push_back(gain_temp);227 //split_info228 statTree[i]->statResult.push_back(splitInfo);229 //gain_info230 double temp = gain_temp / splitInfo;231 statTree[i]->statResult.push_back(temp);232 //得到最大值*/233 if (temp > max_temp)234 {235 max_temp = temp;236 max_attribute = i;237 }238 }239 }240 return max_attribute;241 }242 /*243 * @function Info_attr info_D 总信息量244 * @param: deciNum 有效信息数245 * @param: sum 总信息量246 * @return double 总信息量比例247 */248 double DecisionTree::info_D(int deciNum, int sum)249 {250 double pi = (double)deciNum / (double)sum;251 double result = 0.0;252 if (pi == 1.0 || pi == 0.0)253 {254 return result;255 }256 result = pi * (log(pi) / log((double)2)) + (1 - pi)*(log(1 - pi)/log((double)2));257 return -result;258 }259 /*260 * @function Info_attr 总信息量261 * @param: deciNum 有效信息数262 * @param: sum 总信息量263 * @return double 264 */265 double DecisionTree::Info_attr(map<string, attrItem*>& attriItem, double& splitInfo, int lineNum)266 {267 double result = 0.0;268 for (map<string, attrItem*>::iterator item = attriItem.begin();269 item != attriItem.end();270 ++item271 )272 {273 double pi = (double)(item->second->itemNum[0]) / (double)lineNum;274 splitInfo += pi * (log(pi) / log((double)2));275 double sub_attr = info_D(item->second->itemNum[1], item->second->itemNum[0]);276 result += pi * sub_attr;277 }278 splitInfo = -splitInfo;279 return result;280 }281 /*282 * @function resetStatTree 清理状态树283 * @param: statTree 状态树284 * @param: readClumNum 需要清理的属性set285 * @return void286 */287 void DecisionTree::resetStatTree(vector<attributes*>& statTree, vector<int>& readClumNum)288 {289 for (int i = 0; i < readClumNum.size() - 1; i++)290 {291 if (readClumNum[i] == 0)292 {293 map<string, attrItem*>::iterator it_end = statTree[i]->attriItem.end();294 for (map<string, attrItem*>::iterator it = statTree[i]->attriItem.begin();295 it != it_end; it++)296 {297 delete it->second;298 }299 statTree[i]->attriItem.clear();300 statTree[i]->statResult.clear();301 }302 }303 }
复制代码

程序输出结果为:

以图形表示为:

小结:

  1、在设计程序时,对程序逻辑有时会发生混乱,·后者在纸上仔细画了些草图才解决这些问题,画一个好图可以有效的帮助你理解程序的流程以及逻辑脉络,是需求分析时最为关键的基本功。

  2、在编写程序之初,一直在纠结用什么样的数据结构,后来经过几次在编程实现推敲,才确定最佳的数据结构,可见数据结构在程序中的重要性。

  3、决策树的编写,其实就是理论与实践的相结合,虽然理论上比较简单,但是实践中却会遇到这样那样的问题,而这些问题就是考验一个程序员对最基本的数据结构、算法的理解和熟练程度,所以,勤学勤练基本功依然是关键。

  4、程序的效率还有待提高,欢迎各路高手指正。


0 0