决策树IDC算法笔记(c++代码)

预备知识

信息熵

信息熵是由香农于1948年提出来的,从此以后,就通过信息熵的大小来判断这个信息包含的信息量。计算公式如下,
在这里插入图片描述
其中pi代表的是信息 i 再所有信息中出现的几率。不难看出如果一个事件出现的概率小,他的信息熵就会大。因此它所包含的信息量也越多,为什么呢?举一个例子,太阳东升西落,每天都出现,因此它的信息熵很小,而且这种经常出现的事并不会包含什么信息量。而某地地震的话,不经常出现,因此信息熵很大,但每一次地震都会包含大量的信息。信息熵越大,不确定性越大。

条件熵

条件熵**H(Y|X)**代表在已知随机变量X的条件下随机变量Y的不确定度。 公式为
在这里插入图片描述
pi为信息i在所有信息中出现几率,后面是在X = xi的条件下计算H(Y)。

信息增益

含义为特征A对训练数据集D的信息增益gain(D, A)。
公式为: gain(D, A) = H(D) - H(D|A)

信息增益大的特征具有更强的分类能力。怎么理解呢? 我个人理解是H(D)为整个信息集的信息熵,也就是信息不确定度,对于一个信息集来说是固定的,因此如果信息增益比较大的话,说明H(D|A)比较小。H(D|A)含义是已知特征A的情况下求信息集D的不确定度,H(D|A)小,说明不确定度小,因此它一定在决策树的上方。

决策树

决策树或者叫做分类树,这个算法就是通过计算信息增益确定特征的上下级关系,然后递归建树。其中信息增益越大,在树上越高。其中已被分配过的特征是不会在子树中被考虑的,还需要注意的是特征值的取值是不会在树上分配节点的,而是作为下一层节点的一个成员。如下图:
在这里插入图片描述
其中的Sunny Overvast Rain等属于Outlook特征的取值均不会占用节点。

在这里插入图片描述

代码

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>

using namespace std;

const int MAXLEN = 6;
vector <vector <string>> state; //训练数据实例集
vector <string> attribute_row; //保存属性值
string end("end");  //输入结束标志
string no("no");
string yes("yes");
string blank(""); 
map<string, vector<string>> map_attribute_values; //把属性可能取值存下来,比如天气属性有晴天,阴天
int tree_size = 0;

//decision tree 节点结构体 
struct Node
{
	string attribute; //当前节点对应的属性值
	string arrived_value;  //到达的属性值
	vector <Node *> childs; //所有孩子
	
	Node ()
	{
		attribute = blank;
		arrived_value = blank; // 设置为空 
	} 
};

Node *root;

//根据提供的训练数据计算属性和其取值组成的map(映射) 比如map["天气"] = {"晴天", "阴天"}
void ComputeMapFrom2DVector()
{
	bool exited = false;  //检查有没有保存过
	vector <string> values;  //所有取值
	int state_len = state.size();  //把实例集的数目保存下来,这是一个好的习惯,如果每次都求state.size()的话,时间可能达到O(n^2) 
	
	//按照列遍历,因为每一列对应同一个属性 
	for (int i=1; i<MAXLEN-1; i++) // 最后一行是数据集的y,也就是结果,不能保存到取值里面
	{
		for (int j=1; j<state_len; j++)
		{
			for (int k=0; k<values.size(); k++) //由于values的长度一直变化,这里不需要预处理 
			{
				if (values[k] == state[j][i]) //当前取值出现过了
				{	
					exited = true; 
					break;
				}
			}
			
			if (!exited)
				values.push_back(state[j][i]); //把这个没出现过的值保存下来 
			
			exited = false;
		}
		
		map_attribute_values[state[0][i]] = values; //values里面放的就是state[0][i]的所有可能取值 
		values.clear();  //不要忘记清空,好进行接下来的查找 
	}
}

double ComputeEntropy(vector <vector <string>> remain_state, string attribute, string value, bool ifparent)
{
	int count[2] = {0, 0}; //用来计数
	bool done_flag = false;
	
	for (int i=1; i<MAXLEN; i++)
	{
		if (done_flag) //计算完当前这列属性了 
			break;
		
		if (attribute_row[i] == attribute)  //找到同属性列 
		{
			for (int j=1; j<remain_state.size(); j++)
			{   //如果不是根节点并且当前数据集的这个位置的取值和value一样 
				if ((!ifparent && remain_state[j][i] == value) || ifparent)
				{
					if (remain_state[j][MAXLEN-1] == yes) //如果是正例,也就是y值为yes
						count[0]++;
					else
						count[1] ++;
				} 
			}
			
			done_flag = true; //计算完这个属性的数量就结束 
		}
	}
	
	if (count[0] == 0 || count[1] == 0) //所有数据均为正例或者负例 
		return 0;
	
	double sum = count[0] + count[1]; //当前属性出现总次数 
	double entropy = -count[0]/sum*log(count[0]/sum) - count[1]/sum*log(count[1]/sum);
	
	return entropy; 
}

//按照属性划分剩下的信息,根据信息增益划分 
double ComputeGain(vector <vector <string>>remain_state, string attribute)
{
	double parent_entropy = ComputeEntropy(remain_state, attribute, blank, true); //计算当前始于信息的总信息熵,最后一个参数设置为true后,就是计算总的 
	double children_entropy = 0;
	vector <string> values = map_attribute_values[attribute]; //把当前属性所有取值赋值给values
	vector <double> ratio;
	vector <int> count_values;
	int tempint;
	
	for (int m=0; m<values.size(); m++)
	{
		tempint = 0;
		for (int k=1; k<MAXLEN-1; k++)
		{
			if (attribute_row[k] == attribute)
			{
				for (int j=1; j<remain_state.size(); j++)
				{
					if (remain_state[j][k] == values[m])
					{
						tempint++;
					}
				}
			}
		}
		
		count_values.push_back(tempint);  
	}
	
	for (int j=0; j<values.size(); j++)
	{
		ratio.push_back((double) count_values[j]/(double)(remain_state.size()-1));
	}
	
	double temp_entropy;
	
	for (int i=0; i<values.size(); i++)
	{
		temp_entropy = ComputeEntropy(remain_state, attribute, values[i], false);
		children_entropy += ratio[i] * temp_entropy;  //信息增益计算公式 
	}
	
	return (parent_entropy - children_entropy);
}

//查找属性在state中第几列 
int FindAttriNumByName(string attri)
{
	for (int i=0; i<MAXLEN; i++)
	{
		if (state[0][i] == attri)
			return i;
	}
	
	cerr << "can't find the numth of attribute" << endl;
	return 0;
}

//找到数据中yes多还是no多,以便于能够在属性使用完后,但是节点还没有分完时直接给定标签 
string MostCommonLabel(vector <vector <string>> remain_state)
{
	int positive = 0, negative = 0;
	int remain_state_len = remain_state.size();
	
	for (int i=0; i<remain_state_len; i++)
	{
		if (remain_state[i][MAXLEN-1] == yes)
		{
			positive++;
		}
		else
		{
			negative++;
		}
	}
	
	if (positive > negative)
		return yes;
	else
		return no;
}

//计算是不是当前数据中y都是一样的,如果是一样的,那么这个节点肯定是一个叶子节点 
bool AllTheSameLabel(vector <vector <string>>remain_state, string label)
{
	int count = 0;
	int remain_state_len = remain_state.size();
	
	for (int i=0; i<remain_state_len; i++)
	{
		if (remain_state[i][MAXLEN-1] == label)
			count++;
	}
	
	if (count == remain_state_len-1) //??
		return true;
	else
		return false; 
}

Node *BuildDecisionTreeDFS(Node *p, vector <vector <string>>remain_state, vector <string> remain_attribute)
{
	if (p == NULL)
		p = new Node();
		
	if (AllTheSameLabel(remain_state, yes))
	{
		p->attribute = yes;
		return p;
	}
	else if (AllTheSameLabel(remain_state, no))
	{
		p->attribute = no;
		return p;
	}
	else if (remain_attribute.size() == 0)
	{
		p->attribute = MostCommonLabel(remain_state);
		return p;
	}
	
	double max_gain = 0, temp_gain = 0;
	vector <string> ::iterator max_it = remain_attribute.begin(); //指向gain最大的属性
	vector <string> ::iterator it1;
	
	for (it1 = remain_attribute.begin(); it1!=remain_attribute.end(); it1++)
	{
		temp_gain = ComputeGain(remain_state, (*it1));
		
		if (temp_gain > max_gain)
		{
			max_gain = temp_gain;
			max_it = it1;
		}
	}
	
	//通过max_it进行划分 
	vector <string> new_attribute; //划分剩下的属性集 
	vector <vector <string>> new_state; //划分剩下的数据集 
	
	for (vector <string>::iterator it2 = remain_attribute.begin(); it2 != remain_attribute.end(); it2++)
	{
		if ((*it2) != (*max_it))
			new_attribute.push_back((*it2));
	}
	
	//确定划分,给当前节点attribute分配结果
	p->attribute = *max_it;
	vector <string> values = map_attribute_values[(*max_it)];  //找到所有max_it的取值,来划分子树 
	int attribute_num = FindAttriNumByName(*max_it);
	
	new_state.push_back(attribute_row);  //把属性行先push进去,使得上面的函数能够通用
	//划分并且进行递归建树 
	for (vector <string >::iterator it3 = values.begin(); it3!=values.end(); it3++)
	{
		for (int i=1; i<remain_state.size(); i++)
		{
			if (remain_state[i][attribute_num] == (*it3))  //按照不同取值进行划分,比如天气的晴天阴天等 
			{
				new_state.push_back(remain_state[i]);
			}
		}
		
		Node *new_node = new Node();
		
		new_node->arrived_value = *it3;
		
		if (new_state.size() == 0)
		{
			new_node->attribute = MostCommonLabel(remain_state);
		}
		else
		{
			BuildDecisionTreeDFS(new_node, new_state, new_attribute);
		}
		
		p->childs.push_back(new_node);
		new_state.erase(new_state.begin()+1, new_state.end()); //第一行保持算法唯一的,不用删除 
	} 
	
	return p;
}

void Input()
{
	string s;
	vector <string> item(MAXLEN); //一行实例集

	while (cin >> s && s != "end")
	{
		item[0] = s;
		
		for (int i=1; i<MAXLEN; i++)
		{
			cin >> item[i];
		}
		
		state.push_back(item);
	}
	
	for (int j=0; j<MAXLEN; j++)
	{
		attribute_row.push_back(state[0][j]);
	}
}

vector <string> UsefulAttribute;

// 打印树 
void PrintTree(Node *p, int depth)
{
	for (int i=0; i<depth; i++)
	{
		cout << "\t";
	}
	
	if (!p->arrived_value.empty())
	{
		cout << p->arrived_value << endl;
		for (int i=0; i<depth+1; i++)
			cout << "\t";
	}
	
	cout << p->attribute << endl;

	if (p->attribute != yes && p->attribute != no)
		UsefulAttribute.push_back(p->attribute);
	
	for (vector <Node *>::iterator it = p->childs.begin(); it != p->childs.end(); it++)
	{
		PrintTree(*it, depth+1);
	}
}

//销毁树 

void FreeTree(Node *p)
{
	if (p == NULL)  
        return;  
    for (vector<Node*>::iterator it = p->childs.begin(); it != p->childs.end(); it++){  
        FreeTree(*it);  
    }  
    delete p;  
    tree_size++;  
}

string strInformation;

void FindInformation(struct Node *p)
{
	bool flag = true;
	
	if (p == NULL)
		return;
	
	if (p->attribute == yes)
	{	
		cout << "he can play basketball" << endl;
		return ;
	}
	else if (p->attribute == no)
	{
		cout << "He can't play basketball" << endl;
		return ;
	}
	
	cout << "Please choose one, then enter it" << endl;
	 
	for (vector <Node *>::iterator it = p->childs.begin(); it != p->childs.end(); it++)
	{
		cout << (*it)->arrived_value << "\t";
	}
	
	while (flag)
	{
		cout << endl;
		cin >> strInformation;

		for (vector <Node *>::iterator it = p->childs.begin(); it != p->childs.end(); it++)
		{
			if ((*it)->arrived_value == strInformation)
			{
				FindInformation((*it));
				flag = false;
			}
		}
		
		if (flag)
			cout << "Please enter right data" << endl; 
	}
}

int main()
{
	Input();  
    vector <string> remain_attribute;  
      
    string Outlook("Outlook");  
    string Temperature("Temperature");  
    string Humidity("Humidity");  
    string Wind("Wind");
	remain_attribute.push_back(Outlook);  
    remain_attribute.push_back(Temperature);  
    remain_attribute.push_back(Humidity);  
    remain_attribute.push_back(Wind);  
    vector <vector <string> > remain_state;  
    for(unsigned int i = 0; i < state.size(); i++){
        remain_state.push_back(state[i]);   
    }  
    
	ComputeMapFrom2DVector();
    root = BuildDecisionTreeDFS(root,remain_state,remain_attribute);  
    cout<<"the decision tree is :"<<endl;  
    PrintTree(root,0);  
      
    cout<<endl;  
    cout<<"tree_size:"<<tree_size<<endl;  
    cout << endl;
    
    FindInformation(root);
    FreeTree(root);
    
    system("pause");
	return 0;  
}

训练数据为:

Day Outlook Temperature Humidity Wind PlayTennis
1 Sunny Hot High Weak no
2 Sunny Hot High Strong no
3 Overcast Hot High Weak yes
4 Rainy Mild High Weak yes
5 Rainy Cool Normal Weak yes
6 Rainy Cool Normal Strong no
7 Overcast Cool Normal Strong yes
8 Sunny Mild High Weak no
9 Sunny Cool Normal Weak yes
10 Rainy Mild Normal Weak yes
11 Sunny Mild Normal Strong yes
12 Overcast Mild High Strong yes
13 Overcast Hot Normal Weak yes
14 Rainy Mild High Strong no
end

输入训练数据后就可以推测你喜欢的人明天会不会去打球了。更改部分代码后,就可以实现别的功能。

ID3算法的缺点:

(1)不能对连续数据进行处理,只能通过连续数据离散化进行处理;

(2)采用信息增益进行数据分裂容易偏向取值较多的特征,准确性不如信息增益率;

(3)缺失值不好处理。

(4)没有采用剪枝,决策树的结构可能过于复杂,出现过拟合。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值