機器學習之決策樹(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文件中