决策树C4.5分类算法的C++实现

  公司布置了一个任务让写一个决策树,以前并未接触数据挖掘的东西,但作为一个数据挖掘最基本的知识点,还是应该有所理解的。

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

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

  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 else 105 { 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 void 127 */ 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 else 158 { 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 //DFS 177 CreatTree(treeNote, statTree, infos, newReadLineNum, readClumNum, deep + 1); 178 } 179 else 180 { 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 else 193 { 194 cout << "叶子---"<< treeLine << ">yes" << endl; 195 } 196 } 197 } 198 //还原属性列可用性 199 readClumNum[attr_node] = 0; 200 } 201 } 202 /* 203 * @function compuDecisiNote 计算C4.5 204 * @param: statTree 为状态树,此树动态更新,但是由于是DFS对数据更新,所以不必每次新建状态树 205 * @param: deciNum Yes的数据量 206 * @param: lineNum 计算set的行数 207 * @param: readClumNum 用于计算的set 208 * @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 //info 222 double info_temp = Info_attr(statTree[i]->attriItem, splitInfo, lineNum); 223 statTree[i]->statResult.push_back(info_temp); 224 //gain 225 double gain_temp = infoD - info_temp; 226 statTree[i]->statResult.push_back(gain_temp); 227 //split_info 228 statTree[i]->statResult.push_back(splitInfo); 229 //gain_info 230 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 ++item 271 ) 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 需要清理的属性set 285 * @return void 286 */ 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、程序的效率还有待提高,欢迎各路高手指正。

转载于:https://www.cnblogs.com/michaelGD/archive/2012/11/14/2770758.html

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
五子棋游戏是一款非常经典的桌面游戏,下面我为您介绍如何使用 c++ 来设计和实现一个五子棋游戏。 1. 游戏界面设计 五子棋游戏的界面设计可以使用控制台窗口来实现。首先需要定义一个棋盘,可以使用二维数组来表示棋盘。然后使用 cout 语句在控制台中输出棋盘,可以用“O”表示黑子,“X”表示白子,“+”表示空格。同时,需要在控制台中输出游戏提示信息,比如当前是哪个玩家下棋,哪方胜利等等。 2. 游戏逻辑实现 游戏逻辑实现包括下棋,判断胜负等操作。可以定义一个函数来进行下棋,该函数需要接收三个参数:下棋位置的行数、列数,以及当前下棋玩家的颜色。每次下完棋之后,需要调用判断胜负的函数来判断是否有玩家获胜。 判断胜负可以分为横向、纵向、斜向三种情况。对于每种情况,需要从当前下棋位置开始,向左、右、上、下、左上、右下、左下、右上八个方向扫描。如果在某个方向上出现连续五个同色棋子,则判定该玩家获胜。 3. AI 实现 如果想要实现 AI 玩家,可以使用极大极小算法(Minimax Algorithm)或者 Alpha-Beta 剪枝算法(Alpha-Beta Pruning)来实现。这里简单介绍一下 Minimax 算法实现。 Minimax 算法是一种博弈树搜索算法,用于求解两个玩家的零和博弈问题。在五子棋游戏中,黑方和白方是两个玩家,游戏中的胜负结果是互相对称的。 具体实现时,可以定义一个递归函数来搜索博弈树。该函数需要接收当前棋盘状态、当前搜索深度、当前玩家颜色等参数。在搜索过程中,轮流让黑方和白方下棋,直到搜索深度达到指定值为止。在每次递归结束时,需要返回当前局面的得分(评估函数),以便于搜索算法进行决策。 评估函数可以根据当前局面的情况来设计。一般来说,可以考虑当前棋盘上每个位置的连续棋子数量、是否形成了“三”“四”等特殊棋型、是否有防守和进攻的需求等等。得分越高,表示当前局面对当前玩家越有利。 4. 其他功能实现 除了基本的游戏功能之外,我们可以添加一些其他功能来提升游戏体验。比如: - 悔棋功能:允许玩家悔棋,可以撤销上一步的操作。 - 保存和加载游戏进度:允许玩家保存当前游戏进度,下次再继续游戏。 - 多人对战功能:允许两个玩家在同一台电脑上进行对战,或者通过网络进行远程对战。 以上就是五子棋游戏的设计和实现过程,希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值