分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
一、前言
当年实习公司布置了一个任务让写一个决策树,以前并未接触数据挖掘的东西,但作为一个数据挖掘最基本的知识点,还是应该有所理解的。
程序的源码可以点击这里进行下载,下面简要介绍一下决策树以及相关算法概念。
决策树是一个预测模型;他代表的是对象属性与对象值之间的一种映射关系。树中每个节点表示某个对象,而每个分叉路径则代表的某个可能的属性值,而每个叶结点则对应从根节点到该叶节点所经历的路径所表示的对象的值。决策树仅有单一输出,若欲有复数输出,可以建立独立的决策树以处理不同输出。 数据挖掘中决策树是一种经常要用到的技术,可以用于分析数据,同样也可以用来作预测(就像上面的银行官员用他来预测贷款风险)。从数据产生决策树的机器学习技术叫做决策树学习, 通俗说就是决策树。(来自维基百科)
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数据集合如下:
它记录了再不同的天气状况下,是否出去觅食的数据。
四、程序代码
程序引入状态树作为统计和计算属性的数据结构,它记录了每次计算后,各个属性的统计数据,其定义如下:
- struct attrItem
- {
- std::vector<int> itemNum; //itemNum[0] = itemLine.size()
- //itemNum[1] = decision num
- set<int> itemLine;
- };
- struct attributes
- {
- string attriName;
- vector<double> statResult;
- map<string, attrItem*> attriItem;
- };
- vector<attributes*> statTree;
决策树节点数据结构如下:
- struct TreeNode
- {
- std::string m_sAttribute;
- int m_iDeciNum;
- int m_iUnDecinum;
- std::vector<TreeNode*> m_vChildren;
- };
程序源码如下所示(程序中有详细注解):
- #include "DecisionTree.h"
- int main(int argc, char* argv[]){
- string filename = "source.txt";
- DecisionTree dt ;
- int attr_node = 0;
- TreeNode* treeHead = nullptr;
- set<int> readLineNum;
- vector<int> readClumNum;
- int deep = 0;
- if (dt.pretreatment(filename, readLineNum, readClumNum) == 0)
- {
- dt.CreatTree(treeHead, dt.getStatTree(), dt.getInfos(), readLineNum, readClumNum, deep);
- }
- return 0;
- }
- /*
- * @function CreatTree 预处理函数,负责读入数据,并生成信息矩阵和属性标记
- * @param: filename 文件名
- * @param: readLineNum 可使用行set
- * @param: readClumNum 可用属性set
- * @return int 返回函数执行状态
- */
- int DecisionTree::pretreatment(string filename, set<int>& readLineNum, vector<int>& readClumNum)
- {
- ifstream read(filename.c_str());
- string itemline = "";
- getline(read, itemline);
- istringstream iss(itemline);
- string attr = "";
- while(iss >> attr)
- {
- attributes* s_attr = new attributes();
- s_attr->attriName = attr;
- //初始化属性名
- statTree.push_back(s_attr);
- //初始化属性映射
- attr_clum[attr] = attriNum;
- attriNum++;
- //初始化可用属性列
- readClumNum.push_back(0);
- s_attr = nullptr;
- }
- int i = 0;
- //添加具体数据
- while(true)
- {
- getline(read, itemline);
- if(itemline == "" || itemline.length() <= 1)
- {
- break;
- }
- vector<string> infoline;
- istringstream stream(itemline);
- string item = "";
- while(stream >> item)
- {
- infoline.push_back(item);
- }
- infos.push_back(infoline);
- readLineNum.insert(i);
- i++;
- }
- read.close();
- return 0;
- }
- int DecisionTree::statister(vector<vector<string>>& infos, vector<attributes*>& statTree,
- set<int>& readLine, vector<int>& readClumNum)
- {
- //yes的总行数
- int deciNum = 0;
- //统计每一行
- set<int>::iterator iter_end = readLine.end();
- for (set<int>::iterator line_iter = readLine.begin(); line_iter != iter_end; ++line_iter)
- {
- bool decisLine = false;
- if (infos[*line_iter][attriNum - 1] == "yes")
- {
- decisLine = true;
- deciNum++;
- }
- //如果该列未被锁定并且为属性列,进行统计
- for (int i = 0; i < attriNum - 1; i++)
- {
- if (readClumNum[i] == 0)
- {
- std::string tempitem = infos[*line_iter][i];
- auto map_iter = statTree[i]->attriItem.find(tempitem);
- //没有找到
- if (map_iter == (statTree[i]->attriItem).end())
- {
- //新建
- attrItem* attritem = new attrItem();
- attritem->itemNum.push_back(1);
- decisLine ? attritem->itemNum.push_back(1) : attritem->itemNum.push_back(0);
- attritem->itemLine.insert(*line_iter);
- //建立属性名->item映射
- (statTree[i]->attriItem)[tempitem] = attritem;
- attritem = nullptr;
- }
- else