DM&ML_note.2.2-C4.5決策樹

這個學期要學DM&ML,用的是《數據挖掘算法原理與實現》王振武 本着造福同學的思想,開一個DM&ML的筆記系列,打算給書上的源代碼添加一點註釋,方便閱讀和理解。


寫在前面:

提示:本博文不適合(未滿18歲||碼齡<1年||編碼量<5K行||年均編碼量2K行)的讀者,
頁面可能包含輕度或中度的吐槽,嘲諷,以及大量的不規範編碼以及錯誤的編程設計等內容;
閱讀時有可能產生輕微不適感;
請確信自己已滿當地法律許可年齡且心智成熟後再來閱覽;

警告:

《數據挖掘算法原理與實現》書中提供的C4.5源碼,存在大量的浮點數判斷錯誤,log函數設計錯誤,內存泄漏,編程設計等錯誤。切勿模仿。

典型錯誤舉例分析:

  1. 浮點數判斷錯誤:
    if((double) n==0)諸如此類的浮點數相等/大小判斷,由於浮點數存儲的設計決定了會存在誤差,所以應該引入誤差處理,或者認爲設定誤差精度。
    比如:

    if(fabs(1/2)-0.5<FLT_EPSILON)
    或者
    if(fabs(1/2)-0.5<0.00001/*精確到0.00001*/)。
  2. 本算法在計算信息熵的時候需要計算2爲底的log值,由於math.h並不提供這樣的函數,需要像ID3的實現那樣使用換底公式,本實現一概沒有使用。

  3. 內存泄漏:全文沒有任何delete

  4. 程序設計上的錯誤:詳情看註釋
  5. 如果看到這裏您並不打算浪費時間在如下地方:糾正代碼錯誤,看懂不知所云的奇怪編程風格,糾結於遞歸出口,被代碼誤導日後編程,歡迎點擊本頁面的右上角查看其他關於C4.5的資料,並跳過本書的本部分教學內容。

前置知識要求:

C++,STL,樹,深度優先搜索(DFS)
一點點數學(換底公式)

具體實現:

/*hiro:修改了頭文件*/
/*hiro:爲何要用縮進來傷害我!*/
#include <iostream>
#include <fstream>/*hiro:添加了用於文本IO的頭文件*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
// using namespace std;
/*hiro:本來我可以用全局的using namesapce std的,但是考慮到
他好像寫了個sort,我怕引起各方面的命名衝突,於是顯式使用了明明空間
std::cout,std::endl,下面不再重複描述。*/

const int A = 4;//data中條件屬性的個數
const int C = 3;//data中類的個數
const int ON = 150;//ordata中樣本的個數
const int N = 120;//測試集中樣本的個數
const int MAX = 200;//條件屬性取值的最大值
const int CX = 2;//運行20次求平均值




double ordata[ON][A + 2];/*hiro:樣例數據,更改爲文本輸入*/
double data[N][A + 2];//訓練集
double test[ON - N][A + 2];//測試集
double rule[N][A + 2];//規則集
int bb = 0;//規則集中規則的個數
int attnum[A];//各個屬性取值的個數
//int iii,jjj;
/***********統計各屬性取值的個數************/
//初始化
/*
for(iii=0;iii<A;iii++)
{
attnum[iii]=1;
sort(&ordata[0][0],ON,iii+1);
for(jjj=1;jjj<ON;jjj++)
{
if(ordata[jjj][iii+1]==ordata[jjj-1][iii+1]) continue;
else attnum[iii]++;
}
std::cout<<attnum[iii]<<std::endl;
}
*/

struct node
{
    int leaf;//標誌,葉子爲1,否則爲中間節點,取值爲0
    double cla;//如果是葉子節點,表示決策類的值
    int att[A];//已確定的條件屬性,1爲已確定,0爲未確定
    double attvalue[A];//已確定的條件屬性的取值,初始值爲-1
    int i;//當前進行分支的條件屬性號(0~A-1)
    double nextvalue[MAX];//當前條件屬性的取值
    struct node *next[MAX];//指向前條件屬性的取值的下一級

}*Tree;

int noden = 0;/*hiro:結點個數*/

void Input();/*hiro:添加的文本IO的函數*/

void out(double *a, int n);//輸出數組a,它有n行,A+2列
void sort(double *a, int num, int n);//對長爲num的a數組根據第n列進行排序
struct node * root();//生成根節點
double gainratio(double *d, int n, int a);//求有n個樣本的數據集d的屬性a的信息增益率
struct node * copynode(struct node *a, struct node *b);//拷貝a節點的數據到b節點
int isleaf(struct node *p);//判斷節點p是否爲葉子節點
void nextnode(struct node *p);//根據當前節點生成孩子節點,返回值爲0表示都是葉子節點,爲1表示還存在孩子節點
void outnode(struct node *p);//輸出節點p
void outTree(struct node *p);//輸出樹,p爲根,
int nodemaxi(struct node *p);//求節點p的i

//函數聲明;

void main()
{
    Input();/*hiro:添加的文本IO*/
    int i, j, k;
    int bz1, bz2;
    int reco[CX];//能正確識別的樣本個數
    int cannotreco[CX];//拒絕識別
    int recoerr[CX];//錯誤識別
    double recoratio[CX];//能正確識別的樣本個數
    double cannotrecoratio[CX];//拒絕識別
    double recoerrratio[CX];//錯誤識別
    double nodenumber[CX];//節點個數


    double averagereco = 0;//平均正確識別率
    double averagecannotreco = 0;//平均拒絕識別率
    double averagerecoerr = 0;//平均錯誤識別率
    double averagerule = 0;//平均規則個數
    double averagenodenumber = 0;//平均節點個數
    int c;
    for (c = 0; c<CX; c++)
    {
        reco[c] = 0;
        recoerr[c] = 0;
        cannotreco[c] = 0;
    }
    /************統計各屬性取值的個數*************/
    //初始化
    for (i = 0; i<A; i++)
    {
        attnum[i] = 1;
        sort(&ordata[0][0], ON, i + 1);
        //  out(&ordata[0][0],ON);
        for (j = 1; j<ON; j++)
        {
            if (ordata[j][i + 1] == ordata[j - 1][i + 1]) 
                /*hiro:!!!嚴重錯誤!!!,注意這裏兩個是浮點數,浮點數的相等判斷是不能直接用
                ==的,*/
                continue;
            else 
                attnum[i]++;
        }
        //  std::cout<<attnum[i]<<std::endl;
    }

    /*hiro:循環CX次*/
    for (c = 0; c<CX; c++)
    {

        noden = 0;
        bb = 0;
        int l = 0;
        /******************生成訓練集*******************/
        int a[ON];//標誌
        for (i = 0; i<ON; i++)
            a[i] = 0;//標誌初始化爲0
        /*hiro:在大數據的情況下我覺得不允許這樣子寫產生隨機數
        不然會死循環,之前寫一個10000選7500的,像他這種暴力隨機選取,
        基本可以當作死循環了。應該維護一個未被選中的鏈表,或者一開始就生成
        一個隨機數map,從裏面遍歷取隨機ID,*/
        for (i = 0; i<N; i++)
        {
            /*hiro:↓↓↓我就想問一句,rand要不要初始化設seed?*/
            k = rand() % ON;
            if (a[k] == 0)//該隨機產生的樣本尚未被選中
            {
                //該樣本記入data[i]
                for (j = 0; j<A + 2; j++)
                    data[i][j] = ordata[k][j];
                    //標記a[rand()%150]爲1
                a[k] = 1;
            }
            else//該隨機產生的樣本已經被選中
            {
                k = rand() % ON;
                //繼續產生下一個隨機數,直到對應樣本未被選中
                while (a[k] == 1)
                {
                    k = rand() % ON;
                }
                //該樣本記入data[i]
                for (j = 0; j<A + 2; j++)
                    data[i][j] = ordata[k][j];
                //標記a[rand()%150]爲1
                a[k] = 1;
            }
        }

        /******************生成測試集*******************/
        /*hiro:看樣子,提供的ON個數中,N個數用於訓練這個決策樹,ON-N是用來檢驗這個
        決策樹的*/
        k = 0;
        for (i = 0; i<ON - N; i++)
        {
            while (a[k] == 1)
            {
                k++;
            }
            for (j = 0; j<A + 2; j++)
                test[i][j] = ordata[k][j];
            k++;
        }

        for (i = 0; i<N; i++)
            /*hiro:A+1列當中,具體的數字貌似代表了分類,
            考慮可讀性其實可以用宏,關於這份代碼,樣例,場景的可讀性,我會在後文詳述*/
            if (data[i][A + 1] == 2)
                l++;
        //   std::cout<<"aaaa==  "<<l<<std::endl;
        /*hiro:給提取出來的數據(測試集和訓練集)重新從1開始編號*/
        for (i = 0; i<N; i++)
            data[i][0] = i + 1;
        for (i = 0; i<ON - N; i++)
            test[i][0] = i + 1;
        //for(i=0;i<A;i++)
        //std::cout<<i<<":"<<gainratio(&ordata[0][0],ON,i)<<std::endl;
        /*hiro:處理完數據,開始建樹*/
        Tree = root();
        nextnode(Tree);
        out(&data[0][0], N);
        std::cout << std::endl;
        std::cout << std::endl;
        std::cout << std::endl;
        std::cout << std::endl;
        std::cout << std::endl;
        out(&test[0][0], ON - N);
        //outnode(Tree);
        outTree(Tree);
        //std::cout<<bb<<std::endl;

        for (i = 0; i<ON - N; i++)
        {
            bz2 = 1;//默認爲拒絕識別的

            for (j = 0; j<bb; j++)
            {
                bz1 = 1;//默認爲匹配的
                for (k = 0; k<A; k++)
                {
                    if ((test[i][k + 1] == rule[j][k + 1]) || (rule[j][k + 1] == 0)) 
                        bz1 = bz1 * 1;/*hiro:騷年你需要與或非嗎?想出*1,*0也是辛苦你了*/
                    else
                    {
                        bz1 = bz1 * 0;
                        break;
                    }
                }

                if ((bz1 == 1) && (test[i][A + 1] == rule[j][A + 1]))
                {
                    reco[c]++;
                    bz2 = 0;
                    break;
                }
                /*hiro:我沒有眼花??不是我碼多了吧??↓↓↓對比↑↑↑*/
                /*if ((bz1 == 1) && (test[i][A + 1] != rule[j][A + 1]))
                {
                    recoerr[c]++;
                    bz2 = 0;
                    break;
                }*/
            }
            if (bz2 == 1)
            {
                cannotreco[c]++;
            }
        }
        /*hiro:計算一堆統計信息,詳情看對應註釋*/
        nodenumber[c] = (double)noden;
        recoratio[c] = (double)reco[c] / (double)(ON - N);
        recoerrratio[c] = (double)recoerr[c] / (double)(ON - N);
        cannotrecoratio[c] = (double)cannotreco[c] / (double)(ON - N);

        std::cout << "次數:" << c << std::endl;

        std::cout << "recog-right:" << (double)reco[c] / (double)(ON - N) << std::endl;

        std::cout << "recog-worng:" << (double)recoerr[c] / (double)(ON - N) << std::endl;

        std::cout << "recog-cannot:" << (double)cannotreco[c] / (double)(ON - N) << std::endl;

        std::cout << "nodenumber:" << noden << std::endl;

        averagerule = averagerule + bb;

    }
    /*hiro:統計各種平均值*/
    for (c = 0; c<CX; c++)
    {
        averagereco = averagereco + recoratio[c];
        averagerecoerr = averagerecoerr + recoerrratio[c];
        averagecannotreco = averagecannotreco + cannotrecoratio[c];
        averagenodenumber = averagenodenumber + nodenumber[c];
    }


    averagereco = averagereco / (double)CX;
    averagerecoerr = averagerecoerr / (double)CX;
    averagecannotreco = averagecannotreco / (double)CX;
    averagenodenumber = averagenodenumber / (double)CX;

    std::cout << "average recog-right:" << averagereco << std::endl;

    std::cout << "average recog-worng:" << averagerecoerr << std::endl;

    std::cout << "average recog-cannot:" << averagecannotreco << std::endl;

    std::cout << "average nodenumber:" << averagenodenumber << std::endl;

    std::cout << "averagerule:" << averagerule / CX << std::endl;

    //  out(&test[0][0],ON-N);
    //  std::cout<<ON-N<<std::endl;
    //  out(&ordata[0][0],ON);
    //  std::cout<<std::endl;
    //  out(&data[0][0],N);


    std::cout << "sss" << std::endl;
}

void Input(){

    std::ifstream fin;
    char c;
    fin.open("input.txt");
    for (int i = 0; i < ON; i++){
        for (int j = 0; j < A + 2; j++){
            fin >> ordata[i][j];
        }
    }

}
/*hiro:輸出數組a[n][A+2]的內容*/
void out(double *a, int n)
{
    int i, j;
    for (i = 0; i<n; i++)
    {
        //std::cout<<a[i*(A+2)]<<",";
        //std::cout<<a[i*(A+2)+A+1]<<",";

        for (j = 0; j<A + 2; j++)
        {/*hiro:這位兄臺的加法真是好,看來是學習彙編很透徹,
         對指針運算也很瞭解嘛
         but,
         why not a[i][j]???*/
            std::cout << a[i*(A + 2) + j] << ',';
        }
        std::cout << std::endl;
    }
}

void sort(double *a, int num, int n)//對a數組根據第n列進行排序
{


    int i = 0, j = 0, k = 0;
    double aa;
    /*hiro:檢驗輸入的n的合法性,但,,,
    why 0<n&&n<A+2   ???*/
    k = 0;
    for (i = 0; i<A + 2; i++)
    {
        if (n != i) 
            k++;
    }
    if (k == A + 2)
    {
        std::cout << "依據錯誤的屬性序號進行排序!" << std::endl;
        exit(0);
    }

    /*hiro:嗯,下面開始排序,看好了,認真看*/
    for (i = 0; i<num; i++)
    {
        j = i;
        k = i;
        /*hiro:先遍歷整個表,找到最小值的下標*/
        while (j<num)
        {
            /*hiro:大家當作複習指針加法*/
            if (a[j*(A + 2) + n]<a[k*(A + 2) + n])
            {
                k = j;
            }
            j++;
        }
        /*hiro:然後,將最小的放到表的最前面*/
        for (j = 0; j<A + 2; j++)
        {
            aa = a[i*(A + 2) + j];
            a[i*(A + 2) + j] = a[k*(A + 2) + j];
            a[k*(A + 2) + j] = aa;
        }

        /*hiro:.......*/
    }

    /*hiro:嗯,這位兄弟就是這樣排序的。到這裏我真的是死心了,
    估計這位兄弟是數學系的研究生被老師抓去寫代碼了。
    【這位道友,你沒有聽說過快排,寫個冒泡總還可以吧,你這足足的O(N^2)啊...
    冒泡都起碼是O((N^2)/2)啊..........】
    不過一想到可能是數學系的可能沒有學過算法,只能用原生的思想寫程序,能寫這麼長,已經是很不錯的了。
    嗯。
    可這本是教材啊,不是寫編程語言的實驗啊,別誤人子弟啊!*/
}

struct node * root()//生成根節點
{
    /*hiro:果然是沒有delete的,注意內存回收*/
    struct node *r = new node;
    noden++;
    int i;
    int k;//標誌
    double gainr[A];
    int maxi = 0;
    double max = 0;


    //如果訓練集爲空,則直接返回空
    if (N == 0) return(NULL);
    //判斷訓練集是否只含有一個類
    /*hiro:這個變量k完全可以不要*/
    k = 0;
    for (i = 1; i<N; i++)
    {
        if (data[i][A + 1] != data[0][A + 1])
        {
            k = 1;
            break;
        }
        else continue;
    }
    /*hiro:直接判斷i==N就行了*/
    //如果標誌k==1,表示不止一個類
    if (k == 0)
    {
        r->leaf = 0;
        r->cla = data[0][A + 1];
    }
    else
    {
        r->leaf = 0;
        r->cla = 0;
        for (i = 0; i<A; i++)
        {
            r->att[i] = 0;
            r->attvalue[i] = -1;
        }
        //計算每個未確定條件屬性的信息增益比
        for (i = 0; i<A; i++)
        {
            gainr[i] = 0;
            if (r->att[i] == 1) 
                continue;
            gainr[i] = gainratio(&data[0][0], N, i);
            if (gainr[i]>max)
            {
                maxi = i;
                max = gainr[i];
            }
        }//maxi記錄信息增益比最大的屬性(0~3)
        r->i = maxi;
    }
    return (r);
}

/*hiro:警告!!!!
該函數內有大量的浮點數錯誤,log函數調用錯誤,程序設計錯誤等問題
切勿模仿*/
double gainratio(double *d, int n, int a)//求有n個樣本的數據集d的屬性a的信息增益率
{
    sort(d, n, a);
    int i, j, k, m, s;
    double sp = 0;//屬性a的信息熵
    double I = 0;//決策類的熵
    double E = 0;//屬性a的條件熵
    double E1 = 0;
    //value數組記錄屬性a各個取值
    //double *value=new double(attnum[a]);
    //num數組記錄決策類各個取值的個數
    int num[C];



    /*******************求決策類的熵***********************/
    sort(d, n, A + 1);
    i = 0;
    k = 1;/*hiro:K爲計數變量*/
    for (j = 1; j<n; j++)
    {
        if (d[j*(A + 2) + A + 1] == d[i*(A + 2) + A + 1]) 
            k++;/*hiro:因爲已經排序,所以可以這樣統計有多少個相等的值*/
        else
        {
            /*hiro:是不是應該用換底公式計算??貌似math.h庫沒有提供2爲底的log函數
            參考自www.cplusplus.com:
            The natural logarithm is the base-e logarithm:
            the inverse of the natural exponential function (exp). 
            For common (base-10) logarithms, see log10.*/
            I = I - (double)k / (double)n*log((double)k / (double)n);
            i = j;
            k = 1;
        }
    }
    I = I - (double)k / (double)n*log((double)k / (double)n);
    //std::cout<<"I="<<I<<std::endl;
    /*******************求屬性a的熵和條件熵***********************/
    sort(d, n, a + 1);
    i = 0;
    k = 1;
    /*hiro:同上,先排序,然後統計個數,實際操作起來,因爲這樣利用到了cache
          所以很有可能更加快,
          相關問題閱讀:
          http://stackoverflow.com/questions/11227809/why-is-it-faster-to-process-a-sorted-array-than-an-unsorted-array*/
    for (j = 1; j<n; j++)
    {
        /*hiro:浮點數判斷問題*/
        if (d[j*(A + 2) + a + 1] == d[i*(A + 2) + a + 1])
        {
            k++;
            continue;
        }
        else
        {/*hiro:同理,應該要使用換底公式*/
            sp = sp - (double)k / (double)n*log((double)k / (double)n);//熵
            for (m = 0; m<C; m++)
                num[m] = 0;
            for (s = i; s<j; s++)
            {
                for (m = 0; m<C; m++)
                {
                    /*hiro:同樣的嚴重問題,浮點數的相等判斷*/
                    if (d[s*(A + 2) + A + 1] == (double)(m + 1)) 
                        num[m]++;
                }
            }
            E1 = 0;
            for (m = 0; m<C; m++)
            {
                /*hiro:同樣的嚴重問題,浮點數的相等判斷*/
                if ((double)num[m] / (double)k == 0) 
                    continue;
                /*hiro:同樣的問題,需要換底*/
                E1 = E1 - (double)num[m] / (double)k*log((double)num[m] / (double)k);//條件熵
            }
            E = E + (double)k / (double)n*E1;

            i = j;
            k = 1;


        }


    }
    /*hiro:需要換底*/
    sp = sp - (double)k / (double)n*log((double)k / (double)n);
    /*hiro:下面這一段應該不是手抖複製多了的
    是由於上面的大循環寫的姿勢不算很好,對於有n類值的屬性,上面的大循環只能
    處理n-1,於是,,,他,,,,他,,,,他,,,,把第n次的處理,,,直接
    ,,,,,複製了一次代碼。。。。。。
    我是這麼理解的*/
    for (m = 0; m<C; m++) 
        num[m] = 0;
    for (s = i; s<j; s++)
    {
        for (m = 0; m<C; m++)
        {
            /*hiro:浮點數問題*/
            if (d[s*(A + 2) + A + 1] == (double)(m + 1)) 
                num[m]++;
            // break;
        }
    }
    E1 = 0;
    for (m = 0; m<C; m++)
    {
        if ((double)num[m] / (double)k == 0) 
            continue;

        E1 = E1 - (double)num[m] / (double)k*log((double)num[m] / (double)k);//條件熵
    }
    E = E + (double)k / (double)n*E1;
    /*hiro:到這裏爲止,*/
    //  std::cout<<"E="<<E<<std::endl;
    //  std::cout<<"sp="<<sp<<std::endl;

    /*******************求屬性a的條件熵***********************/

    if (sp == 0) /*hiro:浮點數問題*/
        return(-10000);
    return ((I - E) / sp);
}


struct node * copynode(struct node *a, struct node *b)//拷貝a節點的數據到b節點,非完全拷貝,後三項重新附初值
{
    /*hiro:這是我目前在這份代碼裏看得最舒服的一段函數了*/
    int i;
    b->leaf = a->leaf;
    b->cla = a->cla;
    for (i = 0; i<A; i++)
    {
        b->att[i] = a->att[i];
        b->attvalue[i] = a->attvalue[i];
    }
    b->i = 0;
    for (i = 0; i<MAX; i++)
    {
        b->nextvalue[i] = 0;
        b->next[i] = NULL;
    }
    return(b);
}

void nextnode(struct node *p)//根據當前節點生成孩子節點,返回非葉子節點的個數
{/*hiro:呃。。。你還真好意思眼睜睜看着函數的返回類型寫這
 種函數說明的註釋誒,,返回非葉子結點的個數*/
    int notleaf = 0;
    sort(&data[0][0], N, 0);
    int i, j, k;
    int jsq;
    struct node *q;
    //  double gainr[A];
    //  int maxi=0;
    //  double max=0;
    /****************生成當前數組d**************/
    double *d;
    int bz[N];
    int n;//d中樣本的個數
    for (i = 0; i<N; i++) 
        bz[i] = 1; //等於1爲滿足節點p的樣本
    for (i = 0; i<A; i++)
    {
        if (p->att[i] == 0) 
            continue;
        for (j = 0; j<N; j++)
        {
            /*hiro:浮點數問題*/
            if (data[j][i + 1] != p->attvalue[i])
                bz[j] = bz[j] * 0;
        }
    }
    n = 0;
    /*hiro:統計個數*/
    for (i = 0; i<N; i++)
    {
        if (bz[i] == 1) 
            n++;
    }
    //  std::cout<<"aaaaaaaaan"<<n<<std::endl;
    /*hiro:忘記delete了吧*/
    d = new double[n*(A + 2)];
    k = 0;
    /*hiro:尋找滿足結點的樣本並提取到d數組中*/
    for (i = 0; i<n; i++)
    {
        while ((bz[k] == 0) && (k<N))
            k++;
        //      std::cout<<k<<std::endl;
        for (j = 0; j<A + 2; j++)
        {
            /*hiro:↓真是一日看盡長安花式的數組調用*/
            d[i*(A + 2) + j] = data[k][j];
        }
        k++;
    }

    //  out(d,n);~
    //std::cout<<"aaaaaaaaaaaaaan:"<<n<<std::endl;
    /************生成孩子節點************/
    sort(&d[0], n, (p->i) + 1);
    //  std::cout<<"p->i:"<<p->i<<std::endl;
    k = 0;
    jsq = 0;//計數器清零
    for (i = 0; i<n; i++)
    {
        if (d[i*(A + 2) + (p->i) + 1] == d[k*(A + 2) + (p->i) + 1]) 
            continue;
        /*hiro:忘記delete了大兄弟*/
        q = new node;
        noden++;
        q = copynode(p, q);
        q->att[p->i] = 1;
        q->attvalue[p->i] = d[k*(A + 2) + (p->i) + 1];
        p->nextvalue[jsq] = d[k*(A + 2) + (p->i) + 1];
        p->next[jsq] = q;
        jsq++;
        k = i;
    }
    //最後一個孩子節點
    q = new node;
    noden++;
    q = copynode(p, q);
    q->att[p->i] = 1;
    q->attvalue[p->i] = d[k*(A + 2) + (p->i) + 1];
    p->nextvalue[jsq] = d[k*(A + 2) + (p->i) + 1];
    p->next[jsq] = q;
    jsq++;
    p->nextvalue[jsq] = -1;
    //標誌葉子節點
    //  std::cout<<"jsq:"<<jsq<<std::endl;
    for (i = 0; i<jsq; i++)
    {
        //      std::cout<<i<<std::endl;
        if (isleaf(p->next[i]) != 0)
        {
            p->next[i]->leaf = 1;
            p->next[i]->cla = isleaf(p->next[i]);
        }
        else
        {
            //  if(p->nextvalue[i]==-1) break;
            notleaf++;
            //計算每個未確定條件屬性的信息增益比
            p->next[i]->i = nodemaxi(p->next[i]);
            //;     std::cout<<"aaaaaaaaa"<<std::endl;
            nextnode(p->next[i]);

        }
    }
    /*hiro:忘記了寫return?
    這個遞歸函數我怎麼看怎麼覺得很危險,
    各種爆棧,遞歸出口不明確*/
    //return(notleaf);
}

int isleaf(struct node *p)//判斷節點p是否爲葉子節點,否返回0,是返回決策類的值
{
    sort(&data[0][0], N, 0);
    int i, j, k;
    //  int jsq;
    //  struct node *q;
    /****************生成當前數組d**************/
    double *d;
    int bz[N];
    int n;//d中樣本的個數
    for (i = 0; i<N; i++) 
        bz[i] = 1; //等於1爲滿足節點p的樣本
    for (i = 0; i<A; i++)
    {
        if (p->att[i] == 0)
            continue;
        for (j = 0; j<N; j++)
        {
            if (data[j][i + 1] != p->attvalue[i])
                bz[j] = bz[j] * 0;
        }
    }
    n = 0;
    for (i = 0; i<N; i++)
    {
        if (bz[i] == 1) n++;
    }
    //  std::cout<<"n"<<n<<std::endl;
    d = new double[n*(A + 2)];
    k = 0;
    for (i = 0; i<n; i++)
    {
        while ((bz[k] == 0) && (k<N)) k++;
        //      std::cout<<k<<std::endl;
        for (j = 0; j<A + 2; j++)
        {
            d[i*(A + 2) + j] = data[k][j];
        }
        k++;
    }
    /*hiro:嗯,是不是覺得上面的代碼好眼熟?
    同樣的也是做提取數據的操作
    寫個函數比複製要困難很多嗎?
    實際上判斷葉子節點的是下面這點*/
    for (i = 1; i<n; i++)
    {
        if (d[i*(A + 2) + A + 1] != d[0 * (A + 2) + A + 1])
            return(0);
    }
    p->cla = (int)d[0 * (A + 2) + A + 1];
    return((int)d[0 * (A + 2) + A + 1]);
}

void outnode(struct node *p)//輸出節點p
{
    int i;
    std::cout << "p->leaf:" << p->leaf << std::endl;
    std::cout << "p->cla:" << p->cla << std::endl;
    std::cout << "   att:  ";
    for (i = 0; i<A; i++)
    {
        std::cout << i << "\t";
    }
    std::cout << std::endl;
    std::cout << " att[i]: ";
    for (i = 0; i<A; i++)
    {
        std::cout << p->att[i] << "\t";
    }
    std::cout << std::endl;
    std::cout << "attvalue: ";
    for (i = 0; i<A; i++)
    {
        std::cout << p->attvalue[i] << "\t";
    }
    std::cout << std::endl;
    std::cout << "p->i:" << p->i << std::endl;

    std::cout << " next[i]: " << std::endl;
    i = 0;
    while (p->nextvalue[i] != -1)
    {
        std::cout << p->nextvalue[i] << "\t" << isleaf(p->next[i]) << std::endl;;
        i++;
    }
    std::cout << std::endl;

    std::cout << std::endl;
    std::cout << std::endl;
    std::cout << std::endl;
}

void outTree(struct node *p)//輸出樹
{
    int i;

    if (isleaf(p) != 0)//是葉子
    {
        for (i = 0; i<A; i++)/*hiro:←這個for很可能是忘記和下面的一併註釋的*/
            //      std::cout<<p->attvalue[i]*p->att[i]<<"\t";
            //  std::cout<<p->cla<<std::endl;
            /*******寫入規則集*******/
            rule[bb][0] = bb;
        for (i = 0; i<A; i++)
        {
            /*hiro:編程語言都支持邏輯運算的,請放過乘法器*/
            rule[bb][i + 1] = p->attvalue[i] * p->att[i];
        }
        rule[bb][A + 1] = p->cla;

        bb++;

    }
    else//是中間節點
    {
        i = 0;
        while (p->nextvalue[i] != -1)
        {
            outTree(p->next[i]);
            i++;
        }
    }

}

int nodemaxi(struct node *p)//求節點p的i
{
    int notleaf = 0;
    sort(&data[0][0], N, 0);
    int i, j, k;
    //  int jsq;
    //  struct node *q;
    double gainr[A];
    int maxi = -1;
    double max = -1;
    /****************生成當前數組d**************/
    double *d;
    int bz[N];
    int n;//d中樣本的個數
    for (i = 0; i<N; i++) 
        bz[i] = 1; //等於1爲滿足節點p的樣本
    for (i = 0; i<A; i++)
    {
        if (p->att[i] == 0) 
            continue;
        for (j = 0; j<N; j++)
        {
            if (data[j][i + 1] != p->attvalue[i])
                bz[j] = bz[j] * 0;
        }
    }
    n = 0;
    for (i = 0; i<N; i++)
    {
        if (bz[i] == 1) 
            n++;
    }/*hiro:至此爲止統計了訓練集中滿足結點P屬性的數量n*/
    //
    //  std::cout<<"n"<<n<<std::endl;
    /*hiro:老問題,內存泄漏了*/
    d = new double[n*(A + 2)];
    k = 0;
    for (i = 0; i<n; i++)
    {
        while ((bz[k] == 0) && (k<N))
            k++;
        //      std::cout<<k<<std::endl;
        for (j = 0; j<A + 2; j++)
        {
            d[i*(A + 2) + j] = data[k][j];
        }
        k++;
    }
    /*hiro:生成了一個待求gain_ratio的數據集及其樣本數量n*/
    /*hiro:下面就是用來求最大gain_ratio*/
    //  out(d,n);
    max = -1;
    for (j = 0; j<A; j++)
    {

        gainr[j] = 0;
        if (p->att[j] == 1) 
            continue;
        gainr[j] = gainratio(d, n, j);
        //  if(gainr[j]<=0) continue;
        //  std::cout<<"aaaaaaaaaaa"<<gainr[j]<<std::endl;
        if (gainr[j]>max)
        {
            maxi = j;
            max = gainr[j];
        }
    }//maxi記錄信息增益比最大的屬性(0~3)
    //  std::cout<<max<<","<<maxi<<std::endl;

    return(maxi);


}

感想:

誤人子弟。

附:

樣例數據

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章