决策树就是一棵树,内部节点表示一个属性,分支代表属性值,叶子节点表示最终的结果。
训练过程:初始化所有数据分布在根节点上,然后这个根节点通过选择属性进行分裂。
直到没有多余的可以分裂的属性,或每个叶节点的样本都是一个类别的。
使用算法:通过自顶而下递归分治的做法,将数据样本进行划分
决策过程:从根节点开始,测试相应的属性,并按照属性值选择输出分支,直到到达叶子节点,就可以得到决策结果。
优势:(1)非常直观;(2)效率高,只需要构造一次,就可以不断使用,每一次预测的最大计算次数不超过决策树的深度。
(3)而且构造过程不需要设置任何参数,贝叶斯就需要
决策树的构造:
首先把所有记录看作一个节点
1. 遍历每个变量的每一种分割方式,找到最好的分割点
2. 分割成两个节点
3. 对这两个节点再执行分割,直到每个节点足够“纯”,每个节点下待分类项属于同一个类别
关键在于分割属性,在某个节点处按照某一特征属性的不同划分构造不同的分支。
属性又分为离散型跟连续型,对于离散性属性,可以使用属性划分的一个子集进行测试,根据“属于此子集”和“不属于”分成两个分支。
对于连续型属性,先进行离散化,确定一个值作为分裂点,根据大于这个值和小于这个值分成两个分支。
-------------------------------------------------------------------------------
ID3算法:
分裂属性的选择:选择信息增益大的
信息增益: 划分前数据集的熵 - 划分后数据集的条件熵
(其中D为划分前的数据样本,A为本次划分的属性,设A将原来的样本D划分成了n个样本)
数据集经验熵H(D):
特征A对数据集D的条件熵H(D|A):
算法实现:
ID3缺点:
⑴不能处理连贯属性,只能处理离散型的描述性属性。
⑵用信息增益选择属性时偏向于选择分枝比较多的属性值
------------------------------------------------------------------------------
C4.5算法:
分裂属性的选择:选择信息增益率大的
信息增益率:
————————————————————————
CTAR算法:
分裂属性的选择:选择gini划分系数小的
gini划分系数:
-----------------------------------------------------------------------------
优化:剪枝
剪枝对决策树正确率的影响很大,一般有前向剪枝和后向剪枝。
前向:在构建决策树的过程当中提前停止。所以切分节点的条件会比较苛刻,决策树很短,无法达到最优。
后向:决策树建好后,才开始剪枝。
错误率修正剪枝:(后向)
使用部分测试数据进行修正,用单一叶节点代替整个子树,看看错误率有没有减少。
或者用一个子树替代另外一颗子树。问题:有些节点计算后就被剪掉了,导致浪费。
悲观剪枝:(后向)
使用所有测试数据,递归估算每个内部节点所覆盖样本节点的误判率。
剪枝后该内部节点会变成一个叶子节点,该叶子节点的类别为原内部节点的最优叶子节点所决定。
再比较剪枝前后该节点的错误率。
最后附上CTAR代码实现:
<span style="font-size:12px;">#include<bits/stdc++.h>
#define line_train 27751
#define line 39646
#define item_num 58
#define PLENTY 1.1
using namespace std;
int all_share = 0;
int leaves = 1;
set<int> ATT,all;//剩余属性与样例
struct TEXT{ //文本
string Item;
vector <double> feature; //属性
int shares;
}text[40000];
struct attribute{ //属性
int index,is_share,att_type;
double att_value;
};
bool operator < (attribute A, attribute B){
return A.att_value < B.att_value;
}
struct Node{ //节点
int type,trains,leaf_num,more;
double error;
attribute property;
Node*left,*right;
Node(int ty){type = ty;}
void change_point(Node*l,Node*r){
left = l; right = r;
}
};
void input(){
string str;
double data;
ifstream fin("Datac_all.csv");
getline(fin, str);
for(int i=1;i<line;i++){
getline(fin, str);
istringstream ss(str);
vector<double> tmp;
while (!ss.eof()){
ss >> data; //忽略逗号
ss.ignore(str.size(), ',');
text[i].feature.push_back(data);
}
if(i<=line_train){
text[i].shares = data;
all_share += data;
}
if(i==line_train) i++;
}
fin.close();
cout << "Read done.\n";
}
//计算数据集经验熵H(D)
double entropy(int size, int share_num){
if(size <= 1) return 0;
double p = (double)share_num/size;
if(p == 0 || p == 1) return 0;
return - p*(log(p)/log(2)) - (1-p)*(log(1-p)/log(2));
}
//计算划分系数gini index
double gini_index(int rb, int size, int ls, int sum){
int lns = rb - ls, rs = sum - ls;
int rns = size - rb - rs;
return (double)((rb/size)*(1-(pow(ls,2)+pow(lns,2))/pow(rb,2))+
(1-rb/size)*(1-(pow(rs,2)+pow(rns,2))/pow(size-rb,2)));
}
//寻找最佳划分属性
attribute FindBestAttribute(const set<int> &s, const set<int> &A, int share_num){
attribute RES;
RES.att_type = -1;
int size = s.size(),size_A = A.size();
if(size <= 1 ||size_A == 0)
return RES;
double MAX = -100000;
int type_res;
double value_res;
double H = entropy(size, share_num);
for(set<int>::iterator ite = A.begin(); ite != A.end(); ite++)
{
int i = *ite;
attribute att_temp[size + 1];
int count = 0;
for(set<int>::iterator it = s.begin(); it != s.end(); it++){
att_temp[count].att_value = text[*it].feature[i];
att_temp[count].is_share = text[*it].shares;
att_temp[count++].index = *it;
}
sort(att_temp, att_temp + size);
int min_gini_count = 1, left_share = 0, ls;
double min_gini = 1;
for(int j = 1; j < size; j++){
left_share += att_temp[j-1].is_share;
double gini_temp = gini_index(j, size, left_share, share_num);
if(gini_temp < min_gini){
min_gini = gini_temp;
min_gini_count = j;
ls = left_share;
}
}
set<int> temp0,temp1;
for(int j = 0; j < size; j++){
if(j < min_gini_count)
temp0.insert(att_temp[j].index);
else
temp1.insert(att_temp[j].index);
}
double lp = (double)min_gini_count/size;
double H_cond = (double)(lp)*entropy(min_gini_count, ls) +
(1-lp)*entropy(size-min_gini_count, share_num-ls);
double g = (H - H_cond) / H;
if(g > MAX){
MAX = g; type_res = i;
value_res = (double)(att_temp[min_gini_count-1].att_value+
att_temp[min_gini_count].att_value)/2;
}
}
if(MAX == -100000) type_res = -1;
RES.att_type = type_res;
RES.att_value = value_res;
return RES;
}
//DFS构建决策树
//parent为各阶段,s为剩余样例,A为剩余属性,share_num为分享的个数
void desision(Node *parent, const set<int> &s, const set<int> &A, int share_num)
{
int s_size = s.size(), A_size = A.size();
attribute RES;
RES.att_type = -1;
//设置父节点参数
parent->change_point(NULL,NULL);
parent->type = -1;
parent->property = RES;
parent->trains = s_size;
parent->error = (double)(min(share_num, s_size - share_num) + PLENTY);
parent->more = (share_num > s_size - share_num);
//判断是否搜索到树叶
if(share_num == s_size){//全为share
parent->type = 1; return;
}
if(share_num == 0){//全不share
parent->type = 0;return;
}
//然后判断进来的数据集是否属于同一个类
if(A.size() == 0){
parent->type = (share_num > s_size/2); //使用多数判决法
return;
}
//否则选择最佳属性划分数据集,并更新样例集和属性集
attribute best = FindBestAttribute(s, A, share_num);
if(best.att_type == -1 || s_size == 0) return;
set<int> t1, t2;
int ls = 0, rs = 0;
for(set<int>::iterator it = s.begin(); it != s.end(); it++){
if(text[*it].feature[best.att_type] < best.att_value){
t1.insert(*it);
ls += text[*it].shares;
}else{
t2.insert(*it);
rs += text[*it].shares;
}
}
set<int> A_temp = A;
A_temp.erase(best.att_type);
Node *Left = new Node(-1);
Node *Right = new Node(-1);
parent->change_point(Left,Right);
parent->property = best;
desision(Left, t1, A_temp, ls);
desision(Right, t2, A_temp, rs);
}
void cutting(Node *parent){//悲观剪枝
if(parent == NULL) return;
double et = parent->error;
double eT = (double)parent->leaf_num * PLENTY;
double SE = (double)sqrt(eT * (parent->trains - eT) / parent->trains);
if(et <= eT + SE){
parent->type = parent->more;
parent->change_point(NULL,NULL);
}else{ //子树替换叶子节点
cutting(parent->left);
cutting(parent->right);
}
}
int get_leaf_num(Node *p){ //获取叶子节点数目
if(p == NULL){
p->leaf_num = 0; return 0;
}
if(p->type != -1){
p->leaf_num = 0; return 1;
}
p->leaf_num = get_leaf_num(p->left) + get_leaf_num(p->right);
return p->leaf_num;
}
int find_Node(int t, Node *p){//寻找节点
if(p == NULL) return -1;
if(p->type != -1)
return p->type;
if(p->property.att_type == -1)
return -1;
if(text[t].feature[p->property.att_type] < p->property.att_value)
return find_Node(t, p->left);
else
return find_Node(t, p->right);
}
int main(){
input();
for(int i=1;i<=line_train;i++)
all.insert(i); //初始样例
for(int j=0;j<item_num;j++)
ATT.insert(j); //初始属性
Node *top = new Node(-1);
desision(top, all, ATT, all_share);
int all_leaves = get_leaf_num(top);
cutting(top);
ofstream out("CART.txt");
for(int i = line_train+2; i < line; i++)
out << find_Node(i, top) << endl;
return 0;
}</span>