1.概述
C45算法在weka已經有具體的實現,即weka中的J48.java。不過J48.java中的具體代碼牽扯到較多的類和其他東西,直接看源代碼比較容易混亂,且需要了解的東西較多,有比較多和C45算法本身沒有太大關係而是爲了方便代碼實現的類、變量和方法等。
本文是基於C45算法思想和對J48源代碼的詳細解讀,自己寫了一個C45算法的代碼(之後均稱爲MyC45)。該代碼只含有兩個類(99%的代碼只在一個類中實現),需要了解的結構相對簡單,算法的實現過程相對清晰,算法的效果和J48.java相差無幾,寫在這裏權作參考。
weka中的每個分類器的類文件都實現了buildClassifier和distributionForInstance兩個算法,前者是構建分類器,後者是根據構建的分類器對每個實例進行類標記預測。所以,MyC45算法中主要分爲建樹(buildClassifier)和預測(distributionForInstance)兩部分,其中建樹(buildClassifier)是主體部分,預測(distributionForInstance)只是簡單的利用前面構建好的樹對每個實例進行預測。
特別說明,本文所使用的實例集均已進行數據預處理,所以在MyC45中沒有J48中對缺失值的處理、判斷J48能否處理該實例集等等對實例集的處理。
2.類及其變量說明
MyC45包括兩個類,一個是MyC45,另一個是MyClassifierTree,它們的具體功能和變量說明如下:
1.MyC45:實現C45算法的類。
變量:
m_Root:C45決策樹的根節點。
2.MyClassifierTree:具體實現C45算法中的建樹和預測實例類標記兩大部分的功能。
變量:
int[] m_AttributeList: 當前節點的可選分裂屬性集合,以整數形式保存.-1表示當前節點不能選擇該屬性作爲分裂屬性。
int m_SplitAttribute:當前節點的分裂屬性,以整數的形式表示。
int m_NumAttributes:訓練實例集中屬性的個數(包括類屬性)。
MyCLassifierTree[] m_Sons:當前節點的兒子節點。
Instances m_Instances:當前節點對應的實例集。
int m_MinInstances:最小實例數,若當前節點對應的實例集的實例數小於該值,則當前節點只能作爲葉節點。
注:以上是關鍵變量說明,類中其他變量並不是很重要就沒有特別說明,在後面的代碼中出現時會有說明。
3.僞代碼
輸入:訓練實例集D,可選屬性列表m_AttributeList,最小實例數m_MinInstances。
1.創建根節點m_root,m_AttributeList初始化爲全0.
2.if D中的所有實例類標記都一樣,則將m_root標記爲葉節點並返回m_root。
3.if m_AttributeList爲空,則將m_root標記爲葉節點並返回m_root。
4.if m_Instances的實例數小於m_MinInstances,則將m_root標記爲葉節點並返回m_root。
5.根據getBestSplitAttribute方法,在m_AttributeList中找出增益率最大的屬性作爲當前節點的分裂屬性m_SplitAttribute。
6.根據m_SplitAttribute的屬性值將當前節點的實例集m_Instances劃分成多個子集,也就是當前節點的兒子節點 m_Sons。
7.對m_Sons中的每個節點遞歸地循環2-6的步驟以構建子樹。
8.決策樹構建好後,需要對其進行collapse處理和prune處理。前者是摺疊子樹過程,後者是後剪枝過程,具體說明在後面。
4.函數調用流程圖
以下是各個部分的主要函數調用流程圖,後面將對這些主要函數進行詳細說明。
圖1. buildClassifier部分的主要函數調用流程圖
在MyC45類中的buildClassifier函數中,先初始化可選屬性列表m_AttributeList和最小實例數m_MinInstances,然後用這兩個參數初始化m_Root,之後再用m_Root調用MyClassifierTree類中的buildeClassifier函數即可。
圖2. distributionForInstance部分的主要函數調用流程圖
在MyC45類中的distributionForInstance函數中,直接用m_Root調用MyClassifierTree類中的distributionForInstance函數即可。
圖3. 建樹(buildTree)的主要函數調用流程圖
建樹首先根據isAllTheSame和canSplit兩個函數和其他條件判斷當前節點是否爲葉節點;若可以分裂則調用getBestSplitAttribute函數求出最佳的分裂屬性(也就是增益率最大的屬性),getBestSplitAttribute函數中對每個可選屬性分別調用beforeSplit、afterSplit和computeSplitInfo函數以求出該可選屬性的增益率;隨後將當前節點對應的實例集根據最佳分裂屬性(即當前節點的分裂屬性)分裂出多個子集,這些子集即爲當前節點的子節點的實例集;然後對每個子集調用getNewTree函數以構建相應的子樹,getNewTree函數中即調用buildTree函數,從而遞歸地構建決策樹。
圖4. 摺疊子樹(collapse)的主要函數調用流程圖
collapse是對非葉節點進行操作,對於非葉節點,調用getCurrentTraningErrors求出當前節點的實例集上誤分實例數,再調用getSubTraningErrors求出所有子樹上實例集上誤分實例數;如果後者大於前者,說明這些子樹並不能提高這顆樹的準確度,則把這些子樹刪除。否則在每個子樹上遞歸的collapse。
圖5. 後剪枝(prune)的主要函數調用流程圖
prune是後剪枝操作,所以需要先遞歸找到葉節點的上一層的第一個子樹開始剪枝。首先,調用getLargetBranch求出當前節點的最大樹枝;隨後,調用getEstimatedErrorsForBranch求出當前結點實例集在最大樹枝上的誤分實例數,標記爲a;調用getEstimatedErrorsForDistribution求出當前節點的實例集在當前節點上的誤分實例數,標記爲b;調用getEstimatedErrors求出當前節點的所有子樹上的誤分實例數,標記爲c。接着,判斷是否應該把當前結點設置爲葉節點,即第一個if語句。若不成立,則判斷是否用最大樹枝代替當前結點,即第二個if語句。若是,則將最大樹枝上的變量信息覆蓋當前結點的變量信息,並調用restInstances根據更改後的當前節點對當前實例集進行調整樹的結構,並遞歸地對更改後的當前節點進行prune操作。
5.buildTree
isAllTheSame和canSplit函數比較簡單,split就是根據屬性值對實例集進行劃分,getNewTree只是簡單的初始化節點並調用buildTree形成遞歸,這些都比較簡單,略過。
1.buildTree代碼
public void buildTree(Instances instances) throws Exception
{
initializePara(instances);//初始化一些變量
if (instances.numInstances() <= m_MinInstances|| isAllTheSame(instances))
//小於m_MinInstances的實例集只能做葉節點,或當前實例集的類標記都一樣也做葉節點,
{
m_IsLeaf = true; // 是則該節點爲葉節點
m_Instances = instances;// 葉節點對應的實例集爲instances
return;
}
if (!canSplit(m_AttributeList)) // 若可選屬性集爲空,則實例集不能繼續分裂,所以當前節點是葉節點
{
m_IsLeaf = true;
m_Instances = instances;
return;
} else
{
int[] sonAttributeList = new int[m_NumAttributes]; //子節點的可選屬性列表
for (int i = 0; i < sonAttributeList.length; i++)
{
if (i == m_ClassIndex)
sonAttributeList[i] = -1;
else
sonAttributeList[i] = m_AttributeList[i];
}
m_SplitAttribute = getBestSplitAttribute(instances, m_AttributeList); // 從當前可選的分裂屬性集合中獲取最佳的分裂屬性
m_NameOfCurrentNode = instances.attribute(m_SplitAttribute).name(); // 獲取當前分裂屬性的名稱
if (m_SplitAttribute != -1) // 當前實例集可以劃分,且求得最佳分裂屬性時
{
sonAttributeList[m_SplitAttribute] = -1; // 當前分裂屬性在所有子節點上是不可選的,所以這裏進行標記一下
int numOfSubTree = m_NumAttsValues[m_SplitAttribute];// 該節點對應的子節點數量,等於當前分裂屬性的屬性值個數
Instances[] localInstances;
localInstances = split(instances, numOfSubTree, m_SplitAttribute);// 根據分裂屬性的屬性值個數將實例集進行劃分
m_NameOfLineToSon = new String[numOfSubTree];//每個子節點對應的屬性值
for (int i = 0; i < numOfSubTree; i++)
m_NameOfLineToSon[i] = m_Instances.attribute(m_SplitAttribute).value(i);
m_Sons = new MyCLassifierTree[numOfSubTree];
for (int i = 0; i < m_Sons.length; i++)
{// 接着爲每一個localInstances構建子樹
m_Sons[i] = getNewTree(localInstances[i], sonAttributeList,m_MinInstances);
localInstances[i] = null;
if (m_Sons[i].m_IsLeaf) //統計當前結點葉節點數
m_NumLeaf ++;
}
} else// 當前實例集不可以劃分,說明該節點是葉節點
{
m_IsLeaf = true;
m_Instances = instances;
return;
}
}
}
2.getBestSplitAttribute
/**
* 從當前可選屬性列表中求出最佳分裂屬性,即增益率最大的屬性
* @param instances
* @param attributesList
* @return
*/
public int getBestSplitAttribute(Instances instances, int[] attributesList)
{
int bestSplitAttribute = 0; //標記最佳分裂屬性
boolean canSplit = false; //判斷該實例集是否可以繼續劃分
double[] gainRatio = new double[m_NumAttributes ]; //增益率,
double[] infoGain = new double[m_NumAttributes ]; //信息增益,
double[] splitInfo = new double[m_NumAttributes ]; //分裂信息,
for (int i = 0; i < m_NumAttributes ; i++) //遍歷屬性,計算每個屬性增益率
{
if (i != m_ClassIndex && attributesList[i] != -1)//對可選的非類屬性進行計算增益率
{
infoGain[i] = beforeSplit(instances) - afterSplit(instances,i);
splitInfo[i] = computeSplitInfo(instances,i);
gainRatio[i] = infoGain[i] / splitInfo[i];
canSplit = true; //當進入增益率計算時,說明該實例集可以進行劃分
}
else
{
gainRatio[i] = 0;
infoGain[i] = 0;
splitInfo[i] = 0;
}
}
if (canSplit)
{ //若可以分裂,則找出gainRatio數組中最大且attributesList數組中不等於-1的下標,即爲最佳分裂屬性
bestSplitAttribute = getMaxIndex(gainRatio,attributesList);
}
else {//若當前實例集無法繼續分裂,則返回-1作爲沒有找到最佳分裂屬性的標記
bestSplitAttribute = -1;
}
return bestSplitAttribute;
}
getBestSplitAttribute是通過計算增益率求出的,以下下是計算增益率的一些公式:
(1)GainRito(A) = Gain(A)/SplitInfo(A)
GainRito(A):屬性A的增益率。
Gain(A):屬性A 的信息增益。
SplitInfo(A):屬性A的分裂信息量。
(2)Gain(A) = Info(Insts) - Info(Insts,A)
Info(Insts):實例集Insts分裂前的信息量。
Info(Insts,A):實例集Insts根據屬性A分裂後的信息量。
(3)
C:實例集Insts中的類標記個數。
Pi:第i種類標記對應的實例數與實例總數的比值。
(4)
nA:屬性A的屬性值個數。
n:實例集的實例總數。
ni:屬性A的第i種屬性值對應的實例數。
Instsi:屬性A的第i種屬性值對應的實例集。
Info(Instsi):根據公式(3)計算屬性A的第i種屬性值對應的實例集的信息量。
(5)
3.beforeSplit
/**
* 計算分裂前實例集的信息值
* @param instances
* @return
*/
public double beforeSplit(Instances instances)
{
double infoBeforeSplit = 0;
int numClasses = instances.numClasses();
int numInstances = instances.numInstances(); //實例總數
double allWeight = 0; //實例集instances中的實例數(權重之和)
double[] numInstancesInClass = new double[numClasses];//每個類標記對應的實例數(權重)
for (int i = 0; i < numInstances; i++) //遍歷每個實例,統計出每個類標記對應的實例數(權重)
{
int classLable = (int)instances.instance(i).classValue();
numInstancesInClass[classLable] += instances.instance(i).weight();
allWeight += instances.instance(i).weight();
}
if (onlyOneNotZero(numInstancesInClass,allWeight))//1.如果只有一個類標記的實例數(權重)不爲0(其他類標記的實例數(權重)爲0),則信息值爲0
return 0.0;
if (eachEqualAve(numInstancesInClass))//2.如果所有類標記對應的實例數(權重)相等,則信息值最大,這裏設置爲1
return 1.0;
for (int i = 0; i < numClasses; i++) //3.根據公式(3)求出實例集的信息值
{
double pi = numInstancesInClass[i] / allWeight;
if (pi != 0.0) //注意pi爲0時不要納入計算,因爲log0是一個無效值,這會導致整個infoBeforeSplit值無效(NaN)。反正pi等於0時 pi * log2(pi)即爲0,所以不納入計算即可
infoBeforeSplit = infoBeforeSplit + pi * log2(pi) ;
}
return - infoBeforeSplit; //注意加個負號
}
4.afterSplit
/**
* 計算實例集根據屬性attribute進行分裂後的信息量
* @param instances
* @param attribute
* @return
*/
public double afterSplit(Instances instances, int attribute)
{
double infoAfterSplit = 0;
int numAttributeValue = instances.attribute(attribute).numValues(); //屬性attribute的屬性值個數
int numInstances = instances.numInstances(); // 實例總數
double allWeight = 0; ////實例集instances中的實例數權重之和
double[] weightInAttValue = new double[numAttributeValue];//每個屬性值對應的實例數(權重之和)
Instances[] instsOfValue = new Instances[numAttributeValue];//每個屬性值對應的實例子集
for (int i = 0; i < instsOfValue.length; i++)//初始化
instsOfValue[i] = new Instances(instances, 0);
for (int i = 0; i < numInstances; i++)//遍歷實例集,將實例集instances根據屬性值劃分實例集,統計每個實例集的實例數(權重)
{
int attValue = (int)instances.instance(i).value(attribute); //獲取第i個實例在屬性attribute中的屬性值
weightInAttValue[attValue] += instances.instance(i).weight(); //計算每個屬性值對應的實例數(權重之和)
allWeight += instances.instance(i).weight(); //計算實例集instances中的實例權重之和
instsOfValue[attValue].add(instances.instance(i)); //將實例i放入對應的實例子集之中
}
for (int i = 0; i < numAttributeValue; i++)//根據公式(4)計算根據屬性i分裂後的實例集的信息值
{
double value = weightInAttValue[i]/allWeight * beforeSplit(instsOfValue[i]);
infoAfterSplit = infoAfterSplit + value;
}
return infoAfterSplit;
}
}
5.computeSplitInfo
/**
* 計算分裂信息量
* @param instances
* @param attribute
* @return
*/
public double computeSplitInfo(Instances instances, int attribute)
{
double splitInfo = 0;
double allWeight = 0; ////實例集instances中的實例數(權重之和)
int numAttributeValue = instances.attribute(attribute).numValues(); //屬性attribute的屬性值個數
double[] weightInEachValue = new double[numAttributeValue]; //每個屬性值對應的實例數(權重之和)
for (int i = 0; i < instances.numInstances(); i++)
{
int attValue = (int)instances.instance(i).value(attribute); //獲取第i個實例在屬性attribute中的屬性值
weightInEachValue[attValue] += instances.instance(i).weight(); //計算每個屬性值對應的實例數(權重之和)
allWeight += instances.instance(i).weight(); //計算實例集instances中的實例數(權重之和)
}
for (int i = 0; i < numAttributeValue; i++) //根據公式(5)計算分裂信息值
{
double pi = weightInEachValue[i] / allWeight;
if (pi != 0)//注意pi爲0時不要納入計算,因爲log0是一個無效值,這會導致整個splitInfo值無效。
splitInfo = splitInfo +pi* log2(pi);
}
return - splitInfo; //注意加個負號
}
6.collapse
1.collapse
/**
* Collapses a tree to a node if training error doesn't increase.
* 如果當前節點存在很多子節點,但這些子節點並不能提高這顆分類樹的準確度,則把這些孩子節點刪除。否則在每個孩子上遞歸的collapse。
* 通過collapse方法可以在不減少精度的前提下減少決策樹的深度,進而提高效率。
*/
public final void collapse( )
{
double errorsOfTree; // 當前節點上訓練實例集誤分的實例數(權重)
double errorsOfSubtree; // 當前節點的所有子樹上訓練實例集誤分的實例數(權重)
int i;
if (!m_IsLeaf)//只有對非葉節點才進行摺疊子樹操作
{
errorsOfTree = getCurrentTrainingErrors();
errorsOfSubtree = getSubTrainingErrors();
if (errorsOfSubtree >= errorsOfTree - 1E-3)
//所有子樹上誤分實例數(權重)大於當前節點誤分實例數(權重)時,說明這些孩子節點不好,將他們刪除。
//刪除的方式是將當前節點 的子樹變量設置爲空並將該節點設置爲葉節點。1E-3是10的-3次方,即0.001
{
m_Sons = null;
m_IsLeaf = true;
}
else
for (i = 0; i < m_Sons.length; i++) // 在每個孩子上遞歸地進行摺疊子樹操作
m_Sons[i].collapse();
}
}
2.getCurrentTrainingErrors
/**
* 計算當前結點的誤分實例數
* @return
*/
private double getCurrentTrainingErrors()
{
double wrongWeight = 0;
int majorityClassLable = majorityClassLable(m_Instances); //獲取當前實例集中的多數類
for (int i = 0; i < m_Instances.numInstances(); i++) //遍歷當前實例集,求出總誤分實例數(權重)
{
int classValue = (int)m_Instances.instance(i).classValue();
if (classValue != majorityClassLable)
{
m_Predictions[i] = -1;
wrongWeight += m_Instances.instance(i).weight();
}
}
return wrongWeight;
}
3.getSubTrainingErrors
/**
* 計算當前結點的所有子節點中誤分的實例數(權重)
* @return
*/
private double getSubTrainingErrors()
{
double errors = 0;
if (m_IsLeaf) //對葉節點,直接調用getCurrentTrainingErrors函數求出該葉節點上的誤分實例數(權重)
return getCurrentTrainingErrors();
else //對非葉節點,遞歸調用getSubTrainingErrors以求出所有子節點上的誤分實例數(權重)
{
for (int i = 0; i < m_Sons.length; i++)
errors = errors + m_Sons[i].getSubTrainingErrors();
return errors;
}
}
7.prune
1.prune
/**
* 後剪枝操作
* @throws Exception
*/
public final void prune( ) throws Exception
{
double errorsLargestBranch; //當前節點實例集在最大樹枝上的誤分實例數
double errorsLeaf; //假設當前節點是葉節點時,該節點對應的實例集在該節點上的誤分實例數
double errorsTree; //計算當前節點的所有子樹上的誤分實例數
int indexOfLargestBranch; //最大樹枝的下標
MyCLassifierTree largestBranch; //臨時保存最大樹枝
int i;
if (!m_IsLeaf) //對非葉節點均進行剪枝
{
for (i = 0; i < m_Sons.length; i++)// 對當前節點的子節點遞歸地進行剪枝,由於是後剪枝,所以從樹的最底層開始往上
m_Sons[i].prune();
// 求出當前樹上的最大樹枝,即當前節點的所有子集中實例數最大的子集下標,
indexOfLargestBranch = getLargetBranch();
// 計算當前節點實例集在最大樹枝上的誤分實例數
errorsLargestBranch = m_Sons[indexOfLargestBranch].getEstimatedErrorsForBranch(m_Instances);
//計算當前節點對應的實例集在當前節點上的誤分實例數
errorsLeaf = getEstimatedErrorsForDistribution(m_Instances);
// 計算當前節點的所有子樹上的誤分實例數
errorsTree = getEstimatedErrors();
// 判斷將該節點設置爲葉節點是不是最好的選擇,
if (Utils.smOrEq(errorsLeaf, errorsTree + 0.1) && Utils.smOrEq(errorsLeaf, errorsLargestBranch + 0.1))
{
m_Sons = null;
m_IsLeaf = true;
return;
}
// 判斷用最大樹枝代替當前節點是不是最好的選擇
if (Utils.smOrEq(errorsLargestBranch, errorsTree + 0.1))
{
largestBranch = m_Sons[indexOfLargestBranch]; //獲取最大樹枝
m_Sons = largestBranch.m_Sons;
m_AttributeList = largestBranch.m_AttributeList; //將最大樹枝的可選分裂屬性列表覆蓋當前節點的可選分裂屬性列表
m_AttributeList[m_SplitAttribute] = 0; //由於會用最大樹枝的分裂屬性代替了原先節點的分裂屬性,所以原先節點的分裂屬性處於可選狀態
m_SplitAttribute = largestBranch.m_SplitAttribute; //用最大樹枝的分裂屬性代替了原先節點的分裂屬性
m_IsLeaf = largestBranch.m_IsLeaf;
resetInstances(m_Instances); //將當前實例集根據修改後的分裂屬性進行劃分
prune(); //遞歸地對修改後的節點進行剪枝
}
}
}
2.getEstimatedErrorsForDistribution
/**
* 求出testInstances實例集在以m_Instances爲根據的分類器中的誤分實例數(權重)
* @param theDistribution
* the distribution to use
* @return the estimated errors
*/
private double getEstimatedErrorsForDistribution(Instances testInstances)
{
if (Utils.eq(testInstances.numInstances(), 0)) //若testInstances實例數爲0,則誤分實例數只能爲0
return 0;
else
{
double inCorrectWeight = 0;
double allWeight = 0.0;
int majorityClassLable ;
majorityClassLable = majorityClassLable(m_Instances);//求出當前實例集m_Instances中的多數類
for (int i = 0; i < testInstances.numInstances(); i++)
{
allWeight += testInstances.instance(i).weight(); //統計測試實例集testInstances中的實例總數(權重)
int classVlaue = (int)testInstances.instance(i).classValue();
if (classVlaue != majorityClassLable)
inCorrectWeight += testInstances.instance(i).weight(); //統計測試實例集testInstances中誤分實例的實例總數(權重)
}
return inCorrectWeight + Stats.addErrs(allWeight,inCorrectWeight, 0.25f);
}
}
3.getEstimatedErrorsForBranch
/**
* 求出testInstances實例集在以m_Instances爲根據的分類器中的誤分實例數(權重)
* @param data
* the data to work with
* @return the estimated errors
* @throws Exception
* if something goes wrong
*/
private double getEstimatedErrorsForBranch(Instances testInstances) throws Exception
{
double errors = 0;
int i;
if (m_IsLeaf) //若當前節點是葉節點,則調用getEstimatedErrorsForDistribution求出當前節點上的誤分實例數
return getEstimatedErrorsForDistribution(testInstances);
else //若當前節點不是葉節點,則計算testInstances在其所有子節點上的誤分實例數之和
{
//將testInstances根據當前節點的分裂屬性的屬性值,劃分成不同的測試實例集
int numSubset = testInstances.attribute(m_SplitAttribute).numValues();
Instances[] localInstances = split(testInstances, numSubset, m_SplitAttribute);//測試實例子集
for (i = 0; i < m_Sons.length; i++) //計算每個測試實例子集在對應子節點上的誤分實例數(權重)
errors = errors + m_Sons[i].getEstimatedErrorsForBranch(localInstances[i]);
return errors;
}
}
4.getEstimatedErrors
/**
*計算當前結點的所有子樹上的誤分實例數(權重)
* @return the estimated errors
*/
private double getEstimatedErrors()
{
double errors = 0;
int i;
if (m_IsLeaf) //若當前結點是葉節點,則直接計算誤分實例樹(權重)
return getEstimatedErrorsForDistribution(m_Instances);
else
{
for (i = 0; i < m_Sons.length; i++) //若當前節點不是葉節點,則遞歸地計算其所有子樹上的誤分實數(權重)
errors = errors + m_Sons[i].getEstimatedErrors();
return errors;
}
}
5.resetInstances
/**
* 將當前實例集根據修改後的分裂屬性進行劃分,並修改預測結果數組m_Prediction[]
* @param instances
* @throws Exception
*/
private void resetInstances(Instances instances) throws Exception
{
m_Instances = instances;
if (!m_IsLeaf) //若當前節點不是葉子節點,則遞歸地對其及其子樹進行重新劃分實例集
{
int numSubset = (int)instances.attribute(m_SplitAttribute).numValues();
Instances[] localInstances = split(instances, numSubset, m_SplitAttribute);
for (int i = 0; i < m_Sons.length; i++)//遞歸地對其子樹進行重新劃分實例集
{
m_Sons[i].m_Instances = localInstances[i];
m_Sons[i].resetInstances(localInstances[i]);
}
}
else
{//由於實例集發生了變化,所以需要根據新實例集和新的可選分裂屬性列表構建樹
m_AttributeList[m_SplitAttribute] = -1; //在當前節點上建樹,則當前分裂屬性在其子樹的構建過程中不可選
m_IsLeaf = false;
int numSubset = (int)instances.attribute(m_SplitAttribute).numValues(); //此時是在各個子節點上建樹
Instances[] localInstances = split(instances, numSubset, m_SplitAttribute);
m_Sons = new MyCLassifierTree[numSubset];
for (int i = 0; i < localInstances.length; i++)
{
m_Sons[i] = getNewTree(localInstances[i], m_AttributeList, m_MinInstances);
}
}
}