機器學習之決策樹(C++實現C4.5決策樹)

機器學習之決策樹(C++實現C4.5決策樹)


決策樹是一種樹形結構,其中每個內部節點表示一個屬性上的測試,每個分支代表一個測試輸出,每個葉節點代表一種類別。常見的算法包括ID3,C4.5, CART,這裏我們實現一下C4.5算法(信息增益率)構建決策樹。

數據集使用電離層數據集(Ionosphere Dataset): 數據集下載傳送門

C4.5決策樹代碼實現

  • Decision Tree.hpp
// Decision Tree.hpp: 採用C4.5算法構建決策樹

#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <cmath>
#include <set>
#include <map>
using namespace std;

/*
	數據處理的數據結構:
	·ItemLine 是用於存儲每條數據索引及其分類標籤的數據結構
	·重載運算符'<'用於通過屬性值的大小對ItemLine進行排序
*/
struct ItemLine
{
	int Index;						// 訓練集樣本的索引
	double AttrValue;				// 訓練集樣本對應屬性值

	ItemLine(int Index, double AttrValue)
	{
		this->Index = Index;
		this->AttrValue = AttrValue;
	}

	// 運算符重載能對ItemLine按AttrValue的大小進行排序
	bool operator < (const ItemLine& n) const
	{
		return AttrValue < n.AttrValue;
	}

};

/*
	決策樹搭建的數據結構:
	·TreeNode 是用於存儲決策樹結點信息的數據結構
	·DecisionTree 是用於構建完整的決策樹的數據結構
*/
struct TreeNode
{
	int dt_Index;					// 結點編號
	int dt_Attribute;				// 屬性特徵編號
	double dt_AttrValue;			// 特徵屬性分裂值
	int dt_GoodNum;				// 分類一數量
	int dt_BadNum;				// 分類二數量
	TreeNode* dt_GreaterChild;		// 大於屬性值的子結點
	TreeNode* dt_LessChild;		// 小於屬性值的子結點

	TreeNode()
	{
		dt_Index = 0;
		dt_Attribute = 0;
		dt_AttrValue = 0;
		dt_GoodNum = 0;
		dt_BadNum = 0;
		dt_GreaterChild = NULL;
		dt_LessChild = NULL;
	}

	void Output()
	{
		// 輸出結點編號
		cout << setw(10) << dt_Index;
		// 輸出結點特徵屬性
		if (dt_Attribute != 0)
			cout << "ATTR" << setw(6) << dt_Attribute;
		else
			cout << setw(10) << " ";
		// 輸出結點屬性閾值
		if(dt_AttrValue != 0)
			cout << setw(10) << setprecision(6) << dt_AttrValue;
		else 
			cout << setw(10) << " ";
		// 輸出小於閾值的結點編號
		if (dt_LessChild)
			cout << setw(10) << dt_LessChild->dt_Index;
		else
			cout << setw(10) << " ";
		// 輸出大於閾值的結點編號
		if(dt_GreaterChild)
			cout << setw(13) << dt_GreaterChild->dt_Index;
		else
			cout << setw(13) << " ";
		// 輸出樣本所屬類別
		if (dt_GoodNum == 0)
			cout << setw(10) << "Good";
		if(dt_BadNum == 0)
			cout << setw(10) << "Bad";
		cout << endl;
	}

	// 決策樹的遍歷
	void preOrder();
	void midOrder();

	// 決策樹的存儲遍歷
	void preStore(ofstream& file);
};
struct DecisionTree
{
	TreeNode* TreeRoot;			// 決策樹根結點
	int dt_Depth;					// 決策樹深度

	DecisionTree()
	{
		TreeRoot = NULL;
		dt_Depth = 0;
	}

	// 決策樹構建前預處理 
	int pretreatment(string filename, vector<vector<double>>& trainSet, vector<vector<double>>& testSet, vector<string>& classification, int& inputNum, int& sampleSum);
	// 決策樹數據集刷新
	vector<vector<double>> updateDateSet(vector<vector<double>>& trainSet, vector<string>& classification, int& attribute, double& splitValue, int& sampleNum, int& deciNum, int GreatorLess);
	// 決策樹的遞歸生成
	void DeciTreeCreate(TreeNode*& Node, vector<vector<double>>& trainSet, vector<string>& classification, vector<int>& readAttribute, int& sampleSum, int& deciNum, int& i, int attribute = 0, double splitValue = 0, int GreatorLess = 0);
	// 決策樹結點初始化
	void InitDecisiNode(TreeNode*& best_node, vector<vector<double>>& trainSet, vector<string>& classification, vector<int>& readAttribute, int sampleSum, int deciNum, int& i);
	// 計算特徵屬性的特徵分裂點
	double* AttrSplitPoint(multiset<ItemLine>& AttrInfo, vector<string>& classification, int& deciNum, int& sampleSum, int& attr1Num, int& attr2Num, int& attr1Sum);
	// 計算特徵屬性的Information Entropy
	double InfoEntropy(int deciNum, int sampleSum);
	// 計算特徵屬性的Information Gain
	double InfoGain(int deciNum, int sampleSum, int attr1Num, int attr2Num, int attr1Sum);
	// 計算特徵屬性的Gain Ratio
	double GainRatio(int deciNum, int sampleSum, int attr1Num, int attr2Num, int attr1Sum);
	// 用測試集評估決策樹模型
	double EvaluateModel(vector<vector<double>>& testSet, vector<string>& classification);
	// 決策樹可視化輸出
	void DeciTreeOutput();
	// 對訓練的擬合效果好的決策樹模型存儲
	void StoreModel();
};
  • Decision Tree.cpp
// Decision Tree.cpp: 訓練並評估決策樹模型

#include "./Decision Tree.hpp"
using namespace std;
#define ERROR -1

/*
* @function: main() 主函數
* @description: 用於構建決策樹模型,並評估模型好壞;存儲決策樹模型以便後續使用              
* @return: 0 程序返回
*/
int main()
{
	DecisionTree dTree;
	string filename = "../ionosphere.csv";	// 訓練集文件
	int inputNum = 34;						// 決策樹輸入參數
	int index = 0;							// 決策樹結點編號
	int sampleSum, deciNum;					// 訓練集的樣本數目,有效樣本數目
	TreeNode* node = new TreeNode();		// 決策樹結點
	vector<vector<double>> trainSet;		// 訓練集的二維矩陣
	vector<vector<double>> testSet;			// 測試集的二維矩陣
	vector<string> classification;			// 訓練集分類表
	vector<int> readAttrNum;				// 屬性選擇表
	for (int i = 0; i <= inputNum; i++)
		readAttrNum.push_back(0);			// 初始化屬性選擇表
	if (dTree.pretreatment(filename, trainSet, testSet, classification, inputNum, sampleSum) == 0)
	{
		dTree.DeciTreeCreate(node, trainSet, classification, readAttrNum, sampleSum, deciNum, index);	// 決策樹構建
		cout << "決策樹構建成功!!!" << endl;
		double accuracy = dTree.EvaluateModel(testSet, classification);		// 模型評估
		cout << "決策樹測試測試集的準確率爲:" << setprecision(4) << accuracy * 100.0 << "%" << endl;;
		dTree.StoreModel();						// 存儲決策樹	
		dTree.DeciTreeOutput();					// 決策樹輸出
	}
	else
		cout << "Unable to open '"<< filename <<"', Please check the file path!" << endl;
	return 0;
}

/*
* @function: pretreatment() 決策樹構建前預處理
* @description: 訓練集的導入,並生成信息矩陣和訓練集列表
* @param: filename 文件名
* @param: trainSet 訓練集的二維矩陣,第一列爲數據項索引
* @param: testSet 測試集的二維矩陣,第一列爲數據項索引
* @param: classification 訓練集列表
* @param: inputNum 決策樹輸入參數個數
* @param: sampleSum 記錄訓練集的樣本數目
* @return: 0 mean import succeeded, -1 mean import failed
*/
int DecisionTree::pretreatment(string filename, vector<vector<double>>& trainSet, vector<vector<double>>& testSet, vector<string>& classification, int& inputNum, int& sampleSum)
{
	double index = 0;					// 樣本的索引
	//try {
		ifstream fp(filename.c_str());	// 讀取文件
		if (fp.fail())
			return ERROR;
		string ionosphere_data_str;	// 用於存儲一整行的string格式的數據	
		while (getline(fp, ionosphere_data_str))
		{
			vector<double> data_line;	// 數據集行數組
			string number;			// 存儲每個string格式的數據
			istringstream readstr(ionosphere_data_str);	  // 將string數據流化
			//將一行數據按','分割
			data_line.push_back(index);
			index++;
			for (int j = 0; j < inputNum; j++)
			{
				getline(readstr, number, ',');
				data_line.push_back(atof(number.c_str()));// string轉double存入vector中
			}
			getline(readstr, number);
			classification.push_back(number);	// 插入分類集中	
			trainSet.push_back(data_line);		// 插入數據集中
		}
		fp.close();
		sampleSum = (int)index;				// 數據集中樣本數目
		// 把數據集按 7:3 劃分出訓練集和測試集
		int train_sample = sampleSum * 0.7;		// 訓練集的樣本個數
		for (int i = sampleSum - 1; i > train_sample; i--)
		{
			vector<double> dataline;
			dataline.assign(trainSet[i].begin(), trainSet[i].end());
			testSet.push_back(dataline);		// 測試集	
			trainSet.pop_back();				// 訓練集
		}
		sampleSum = trainSet.size();			// 訓練集中樣本的數目

	//}
	//catch (...) {
	//	return ERROR;
	//}
	return 0;
}

/*
* @function: updateDateSet() 決策樹數據集刷新
* @description: 根據每個屬性結點的屬性分裂值,重新更新數據集,用於決策樹子結點的生成
* @param: trainSet 訓練集的二維矩陣,第一列爲數據項索引
* @param: classification 訓練集分類表
* @param: attribute 決策樹結點屬性編號
* @param: splitValue 決策樹結點屬性分裂點的值
* @param: sampleSum 記錄輸入樣本數目
* @param: deciNum 有效樣本數目
* @param: GreatorLess 左右子樹標誌位,如果爲0,則取小於splitValue的所有樣本集;如果爲1,則取大於splitValue的所有樣本集
* @return: vector<vector<double>> update_trainSet 返回更新後的數據集
*/
vector<vector<double>> DecisionTree::updateDateSet(vector<vector<double>>& trainSet, vector<string>& classification, int& attribute, double& splitValue, int& sampleSum, int& deciNum, int GreatorLess)
{
	vector<vector<double>> update_trainSet;		// 存儲更新後的數據集
	deciNum = 0;						// 初始化有效樣本數
	if (attribute == 0)				// 屬性0是索引,此時爲根節點,不必更新數據集
		update_trainSet = trainSet;
	else
	{
		if (GreatorLess == 0)			// 小於splitValue的所有樣本集
		{
			for (int i = 0; i < trainSet.size(); i++)
			{
				if (trainSet[i][attribute] < splitValue)
				{
					vector<double> dataline;
					dataline.assign(trainSet[i].begin(), trainSet[i].end());
					update_trainSet.push_back(dataline);
				}
			}
		}
		else									// 大於splitValue的所有樣本集
		{
			for (int i = 0; i < trainSet.size(); i++)
			{
				if (trainSet[i][attribute] >= splitValue)
				{
					vector<double> dataline;
					dataline.assign(trainSet[i].begin(), trainSet[i].end());
					update_trainSet.push_back(dataline);
				}
			}
		}
	}
	sampleSum = update_trainSet.size();			// 重新記錄樣本總數
	for (int i = 0; i < sampleSum; i++)
	{
		int index = (int)update_trainSet[i][0];
		if (classification[index] == "g")
			deciNum++;				// 記錄good的樣本數目 
	}
	return update_trainSet;
}

/*
* @function: DeciTreeCreate() 決策樹構建
* @description: 遞歸生成決策樹
* @param: Node 決策樹頭結點
* @param: trainSet 訓練集的二維矩陣,第一列爲數據項索引
* @param: classification 訓練集分類表
* @param: readAttribute 記錄特徵屬性是否被選取,readAttribute[i]爲0代表該屬性未選,爲1代表該屬性已選
* @param: sampleSum 記錄輸入樣本數目
* @param: deciNum 有效樣本數目
* @param: attribute 決策樹結點屬性編號
* @param: i	決策樹結點編號
* @param: GreatorLess=0 左右子樹標誌位,決定數據集的更新方式;缺省值時,表示根節點,數據集不需刷新
* @return: void
*/
void DecisionTree::DeciTreeCreate(TreeNode*& Node, vector<vector<double>>& trainSet, vector<string>& classification, vector<int>& readAttribute, int& sampleSum, int& deciNum, int& i, int attribute, double splitValue, int GreatorLess)
{
	// 1. 創建決策樹結點
	TreeNode* treeNode = new TreeNode();
	// 2. 更新訓練集
	vector<vector<double>> update_trainSet = updateDateSet(trainSet, classification, attribute, splitValue, sampleSum, deciNum, GreatorLess);
	// 3. 初始化決策樹結點
	InitDecisiNode(treeNode, update_trainSet, classification, readAttribute, sampleSum, deciNum, i);
	Node = treeNode;       // 結點連接
	// 4. 判斷是否是葉子結點 treeNode->dt_GoodNum == 0 || treeNode->dt_BadNum == 0
	if (treeNode->dt_Attribute == 0)
		return;
	// 5. 遞歸建樹
	// 遞歸生成小於splitValue子樹
	DeciTreeCreate(treeNode->dt_LessChild, update_trainSet, classification, readAttribute, sampleSum, deciNum, i, treeNode->dt_Attribute, treeNode->dt_AttrValue, 0);
	// 遞歸生成大於splitValue子樹
	DeciTreeCreate(treeNode->dt_GreaterChild, update_trainSet, classification, readAttribute, sampleSum, deciNum, i, treeNode->dt_Attribute, treeNode->dt_AttrValue, 1);
	// 將構建好的決策樹存儲到DecisionTree中
	TreeRoot = treeNode;
}

/*
* @function: InitDecisiNode() Initializ決策樹的屬性結點
* @description: 採用C4.5算法,通過檢測所有的特徵屬性,計算其信息增益率,選擇信息增益最大的屬性來創建決策樹的屬性結點
* @param: best_node TreeNode*類型的結點,初始化(增益率最大的屬性編號及最佳分裂屬性值)的屬性結點
* @param: trainSet 訓練集的二維矩陣,第一列爲數據項索引
* @param: classification 訓練集分類表
* @param: readAttribute 記錄特徵屬性是否被選取,readAttribute[i]爲0代表該屬性未選,爲1代表該屬性已選
* @param: sampleSum 記錄輸入樣本數目
* @param: deciNum 有效樣本數目
* @param: i	決策樹結點編號
* @return: void
*/
void DecisionTree::InitDecisiNode(TreeNode*& best_node, vector<vector<double>>& trainSet, vector<string>& classification, vector<int>& readAttribute, int sampleSum, int deciNum, int& i)
{
	i++;
	// 判斷是否爲葉子結點
	if (deciNum == sampleSum || deciNum == 0)
	{
		best_node->dt_Index = i;			// 初始化屬性結點的數值信息
		best_node->dt_Attribute = 0;	
		best_node->dt_AttrValue = 0;
		if (deciNum == 0)
		{
			best_node->dt_GoodNum = 0;
			best_node->dt_BadNum = sampleSum;
		}
		else
		{
			best_node->dt_GoodNum = sampleSum;
			best_node->dt_BadNum = 0;
		}
		return;
	}
	// 如果是非葉子結點,進行如下操作
	multiset<ItemLine> AttrInfo;		// 訓練集在該特徵屬性下的索引值與屬性值(屬性值由小到大排列)
	int max_attribute = 0;				// 存儲信息增益率最大的屬性編號
	double best_split_value = 0;		// 存儲最佳分裂屬性值
	double max_gain_ratio = 0;			// 存儲最大的信息增益率

	int attr1Num = 0; int attr2Num = 0;	// 特徵屬性分支1上有效樣本數目,特徵屬性分支2上有效樣本數目
	int attr1Sum = 0;					// 特徵屬性分支1上樣本數目
	for (int i = 1; i < readAttribute.size(); i++)		// 遍歷readAttribute,找到未被選取的特徵屬性
	{
		//if (readAttribute[i] == 0)
		//{
			for (int j = 0; j < sampleSum; j++)		// 初始化AttrValue映射
				AttrInfo.insert(ItemLine((int)trainSet[j][0], trainSet[j][i])); // i代表屬性編號
			double* SplitInfo = AttrSplitPoint(AttrInfo, classification, deciNum, sampleSum, attr1Num, attr2Num, attr1Sum); // 獲取連續屬性值的最佳屬性分裂點信息
			if (SplitInfo[1] > max_gain_ratio)
			{
				max_attribute = i;					// 更新最佳屬性標號
				best_split_value = SplitInfo[0];	// 更新最佳分裂點屬性值
				max_gain_ratio = SplitInfo[1];		// 更新最佳信息增益率
			}
			AttrInfo.clear();	// 每次循環結束重新記錄
		//}
	}
	readAttribute[max_attribute] = 1;			// 標記已經選擇過的特徵屬性
	best_node->dt_Index = i;					// 初始化屬性結點的數值信息
	best_node->dt_Attribute = max_attribute;	
	best_node->dt_AttrValue = best_split_value;
	best_node->dt_GoodNum = deciNum;
	best_node->dt_BadNum = sampleSum - deciNum;
}

/*
* @function: AttrSplitPoint() 計算一個連續的特徵屬性,最佳的屬性分裂點
* @description: 通過對每個相鄰數據取中位數,找到信息增益最大的分裂點,再求出該分裂點的信息增益率並返回,用於與其他屬性比較找到決策樹最佳特徵結點
* @param: multiset<ItemLine>& AttrInfo 訓練集在該特徵屬性下的索引值與屬性值(屬性值由小到大排列)
* @param: classification 訓練集分類表
* @param: deciNum 有效樣本數目(good)
* @param: sampleSum 樣本總數
* @param: attr1Num 特徵屬性分支1上有效樣本數目
* @param: attr2Num 特徵屬性分支2上有效樣本數目
* @param: attr1Sum 特徵屬性分支1上樣本數目
* @return: double* SplitInfo 返回增益率最大的屬性分裂值和其增益率
*/
double* DecisionTree::AttrSplitPoint(multiset<ItemLine>& AttrInfo, vector<string>& classification, int& deciNum, int& sampleSum, int& attr1Num, int& attr2Num, int& attr1Sum)
{
	double SplitInfo[2] = { 0,0 };		// SplitInfo[0]爲屬性分裂點的值
										// SplitInfo[1]爲屬性分類點的信息增益率
	set<double> AttrSplitPoint;			// 存放特徵屬性分裂值的備選值(只保留不同的備選值)
	double previous = 0;				// 記錄上一個屬性值用於和當前屬性值取平均值
	for (multiset<ItemLine>::iterator it = AttrInfo.begin(); it != AttrInfo.end(); it++)
	{
		if (it != AttrInfo.begin())
		{
			double mid = (previous + it->AttrValue) / 2; // 求特徵屬性分裂值的備選值
			AttrSplitPoint.insert(mid);
		}
		previous = it->AttrValue;			// 記錄上一個屬性值用於和當前屬性值取平均值
	}
	for (set<double>::iterator attrSplit = AttrSplitPoint.begin(); attrSplit != AttrSplitPoint.end(); attrSplit++)
	{
		// 確定attr1Sum,attr1Num,attr2Num的值 
		for (multiset<ItemLine>::iterator it = AttrInfo.begin(); it != AttrInfo.end(); it++)
		{
			if (it->AttrValue < *attrSplit)
			{
				attr1Sum++;
				if (classification[it->Index] == "g")
					attr1Num++;
			}
			else
			{
				if (classification[it->Index] == "g")
					attr2Num++;
			}
		}
		// 求出信息增益
		double info_gain = InfoGain(deciNum, sampleSum, attr1Num, attr2Num, attr1Sum);
		double gain_ratio;
		// 不斷更新SplitInfo數組,找到信息增益最大的結點信息,並求出其信息增益率
		if (info_gain > SplitInfo[1])
		{
			gain_ratio = GainRatio(deciNum, sampleSum, attr1Num, attr2Num, attr1Sum);
			SplitInfo[0] = *attrSplit;
			SplitInfo[1] = gain_ratio;
		}
		// 每次循環結束重新記錄
		attr1Sum = 0;
		attr1Num = 0;
		attr2Num = 0;
	}
	return SplitInfo;
}

/*
* @function: InfoEntropy() 計算信息熵
* @param: deciNum 有效樣本數目(good)
* @param: sampleSum 樣本總數
* @return infoEntropy 屬性信息熵的值
*/
double DecisionTree::InfoEntropy(int deciNum, int sampleSum)
{
	double infoEnt1, infoEnt2, infoEntropy;
	int undeciNum = sampleSum - deciNum;
	if (sampleSum == 0)
		return 0;
	double P1 = (double)deciNum / (double)sampleSum;
	double P2 = (double)undeciNum / (double)sampleSum;
	if (P1 == 0)
		infoEnt1 = 0;
	else
		infoEnt1 = P1 * log2(P1);
	if (P2 == 0)
		infoEnt2 = 0;
	else
		infoEnt2 = P2 * log2(P2);
	if (P1 == 0 && P2 == 0)
		infoEntropy = 0;
	else
		infoEntropy = -1 * (infoEnt1 + infoEnt2);
	return infoEntropy;
}

/*
* @function: InfoGain() 計算信息增益
* @param: deciNum 有效樣本數目(good)
* @param: sampleSum 樣本總數
* @param: attr1Num 特徵屬性分支1上有效樣本數目
* @param: attr2Num 特徵屬性分支2上有效樣本數目
* @param: attr1Sum 特徵屬性分支1上樣本數目
* @return Gain_A 屬性信息增益的值
*/
double DecisionTree::InfoGain(int deciNum, int sampleSum, int attr1Num, int attr2Num, int attr1Sum)
{
	int attr2Sum = sampleSum - attr1Sum;
	double InfoEntropy_D = InfoEntropy(deciNum, sampleSum);
	double InfoEntropy_A1 = InfoEntropy(attr1Num, attr1Sum);
	double InfoEntropy_A2 = InfoEntropy(attr2Num, attr2Sum);
	double A1 = (double)attr1Sum / (double)sampleSum;
	double A2 = (double)attr2Sum / (double)sampleSum;
	double InfoEntropy_A = A1 * InfoEntropy_A1 + A2 * InfoEntropy_A2;
	double Gain_A = InfoEntropy_D - InfoEntropy_A;
	return Gain_A;
}

/*
* @function: GainRatio() 計算信息增益率
* @param: deciNum 有效樣本數目(good)
* @param: sampleSum 樣本總數
* @param: attr1Num 特徵屬性分支1上有效樣本數目
* @param: attr2Num 特徵屬性分支2上有效樣本數目
* @param: attr1Sum 特徵屬性分支1上樣本數目
* @return GainRatio_A 屬性信息增益率的值
*/
double DecisionTree::GainRatio(int deciNum, int sampleSum, int attr1Num, int attr2Num, int attr1Sum)
{
	double IV_A = InfoEntropy(attr1Sum, sampleSum);
	double InfoGain_A = InfoGain(deciNum, sampleSum, attr1Num, attr2Num, attr1Sum);
	double GainRatio_A = InfoGain_A / IV_A;
	return GainRatio_A;
}

/*
* @function: log_2() 計算一個數值得以2爲底的對數
* @param: n 真數
* @return: log2(n)的值
*/
double log_2(double n)
{
	return log10(n) / log10(2.0);
}

/*
* @function: EvaluateModel() 用測試集評估決策樹模型
* @description: 運用測試集來對生成的決策樹模型進行測試
* @param: testSet 訓練集的二維矩陣,第一列爲數據項索引
* @param: classification 訓練集列表
* @return: 返回決策樹分類的準確率
*/
double DecisionTree::EvaluateModel(vector<vector<double>>& testSet, vector<string>& classification)
{
	double Accuracy;
	int sampleSum = testSet.size();		// 樣本總數
	int correctNum = 0;					// 決策樹判斷正確的個數
	TreeNode* p = TreeRoot;				// 決策樹遍歷指針
	for (vector<vector<double>>::iterator it = testSet.begin(); it != testSet.end(); it++)
	{
		int index = (int)(*it)[0];			// 樣本的索引
		while (p->dt_Attribute != 0)		// 屬性編號爲0代表是葉子結點
		{
			if ((*it)[p->dt_Attribute] < p->dt_AttrValue)	// 小於分裂值進入左子樹
				p = p->dt_LessChild;
			else											// 大於分裂值進入右子樹		
				p = p->dt_GreaterChild;
		}
		if (p->dt_GoodNum == 0 && classification[index] == "b")
			correctNum++;
		if (p->dt_BadNum == 0 && classification[index] == "g")
			correctNum++;
	}
	Accuracy = (double)correctNum / (double)sampleSum;
	return Accuracy;
}

/*
* @function: DeciTreeOutput() 決策樹可視化輸出
* @description: 將決策樹模型以表格形式可視化輸出到命令行
* @return: void
*/
void DecisionTree::DeciTreeOutput()
{
	cout << "*************************決策樹結點表************************" << endl;
	cout << endl;
	cout.setf(ios::left);
	cout << setw(10) << "結點編號";
	cout << setw(10) << "特徵屬性";
	cout << setw(10) << "屬性閾值";
	cout << setw(10) << "LessNode";
	cout << setw(13) << "GreaterNode";
	cout << setw(10) << "所屬類別";
	cout << endl;
	// 先序遍歷輸出
	TreeRoot->preOrder();
	/*
	// 中序遍歷輸出
	TreeRoot->midOrder();
	*/
}

/*
* @function: StoreModel() 對訓練的擬合效果好的決策樹模型存儲
* @description: 把利用訓練集訓練好的決策樹模型存儲到文件中,方便後續的使用
* @return: void
*/
void DecisionTree::StoreModel()
{
	ofstream outfile;
	outfile.open("../IonosphereDecisionTree.csv", ios::out);
	// 先序遍歷存儲
	TreeRoot->preStore(outfile);
	outfile.close();
	cout << "模型已經存儲到文件中!" << endl;
}

/*
* @function: TreeNode::preOrder() 先序遍歷輸出
* @description: 對決策樹進行先序遍歷,並輸出結點信息
* @return: void
*/
void TreeNode::preOrder()
{
	//實現先序遍歷輸出
	Output();
	if (this->dt_LessChild)
		this->dt_LessChild->preOrder();
	if (this->dt_GreaterChild)
		this->dt_GreaterChild->preOrder();
}

/*
* @function: TreeNode::midOrder() 中序遍歷輸出
* @description: 對決策樹進行中序遍歷,並輸出結點信息
* @return: void
*/
void TreeNode::midOrder()
{
	//實現中序遍歷輸出
	if (dt_LessChild)
		dt_LessChild->preOrder();
	Output();
	if (dt_GreaterChild)
		dt_GreaterChild->preOrder();
}

/*
* @function: TreeNode::preStore() 先序遍歷存儲
* @description: 對決策樹進行中序遍歷,並存儲結點信息到csv文件中
* @return: void
*/
void TreeNode::preStore(ofstream& file)
{
	//實現先序遍歷存儲
	file << this->dt_Index << ","; 
	// 輸出結點特徵屬性
	if (this->dt_Attribute != 0)
		file << "ATTR" << this->dt_Attribute << ",";
	else
		file << ",";
	// 輸出結點屬性閾值
	if (this->dt_AttrValue != 0)
		file << setprecision(6) << this->dt_AttrValue << ",";
	else
		file << ",";
	// 輸出小於閾值的結點編號
	if (this->dt_LessChild)
		file << this->dt_LessChild->dt_Index << ",";
	else
		file << ",";
	// 輸出大於閾值的結點編號
	if (this->dt_GreaterChild)
		file << this->dt_GreaterChild->dt_Index;
	if (this->dt_AttrValue == 0)
		file << ",";
	// 輸出樣本所屬類別
	if (dt_GoodNum == 0)
		file << "Good";
	if (dt_BadNum == 0)
		file << "Bad";
	file << "\n";
	if (dt_LessChild)
		dt_LessChild->preStore(file);
	if (dt_GreaterChild)
		dt_GreaterChild->preStore(file);
}

C4.5決策樹運行結果

  • 決策樹的可視化輸出及模型準確率評估
  • 模型存儲到CSV文件中
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章