DM&ML_note.7-神經網絡聚類算法:SOM

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


前置知識要求

C++

SOM網絡設計

注意,請仔細看這一部分的設計,這裏寫的是源碼的相關參數,書上P191寫的是例子的參數,有所不同。
1.輸入層結點數:樣本維度=7*5
2.輸出層結點數:取96個神經元構成8*12的二維平面陣。
3.權值初始化:隨機歸一化小數
4.領域半徑:

r(t+1)=r(t)*(1-當前迭代數n/總迭代數N),t>1
r(t)=Max_size of outputLayer,t=1

5.學習率:

a(t+1)=a(t)*(1-當前迭代數n/總迭代數N),t>1
a(t)=default efficiency,t=1

具體實現

#include <fstream>
#include <string>
#include <iomanip>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
using namespace std;/*hiro:忘記聲明命名空間*/

#define InputLayerNum 35
#define OutputLayerRow 8
#define OutputLayerColumn 12
#define total_iteration_Num 1000000//10000//80//100//1000
#define error_limit 0.0000000000008//0.1//0.0000000000008//0.000000000000008//0.0001
#define efficiency 0.9//0.3//0.9//0.3//0.9
#define is_win  true
/*hiro:添加全局的字符-下標轉換數組,真是的,該全局的不全局,,,寫函數也好啊。。。*/
const string character = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

/*hiro:寫在前面,我真的想不懂爲何一堆應該局部的變量寫全局,,偷懶也不是這麼偷的啊。。。
而且該全局可以偷懶的又不寫,,*/
int i,j,k,l,m,n;
int inputMode[26][7][5];

/*hiro:↓↓修改了原本不友好的寫法*/
double weight[OutputLayerRow][OutputLayerColumn][InputLayerNum];

/*hiro:記錄迭代次數*/
int current_iteration_num=0;
/*hiro:當前學習效率*/
double study_efficiency=efficiency;

/*hiro:↓↓修改了原本不友好的寫法*/
/*hiro:這個變量的名字應該和一些庫文件裏的聲明衝突了,遂更改爲my_distance*/
long double my_distance[OutputLayerRow][OutputLayerColumn];

/*hiro:保存了當前時刻的結點影響範圍*/
int neighbor_width=OutputLayerColumn;
int neighbor_height=OutputLayerRow;

/*hiro:姑且是保存了所有獲勝節點的下標,但是並沒有用上
後面它又通過遍歷的方法來獲取獲勝結點的下標。。*/
int row[OutputLayerRow], column[OutputLayerColumn];

/*hiro:改爲bool 型更符合語意,保存了該下標是否爲獲勝節點*/
bool flag[OutputLayerRow][OutputLayerColumn];

int temp_row,temp_column;
int winner_row,winner_column;
long double min_distance=1000.0;


/****************************************************************/
//該函數初始化距離變量爲0,初始化保存勝出節點的位置的變量
/****************************************************************/
void init_distance()
{
    for(i=0;i<OutputLayerRow;i++)
        for(j=0;j<OutputLayerColumn;j++)
            my_distance[i][j]=0.0;
 }

/*hiro:增加函數用於處理範圍參數
for_weight字段用來判斷在weight_change函數裏需要額外進行的操作*/
void legalizeInput(int &ttLow, int & ttUp, int &ppLow, int &ppUp,bool for_weight){
    if (for_weight){
        winner_row = temp_row;
        winner_column = temp_column;
    }
    ttLow = winner_column - neighbor_width / 2;
    ttUp = winner_column + neighbor_width / 2;
    ppLow = winner_row - neighbor_height / 2;
    ppUp = winner_row + neighbor_height / 2;
    if (ttLow<0)
        ttLow = 0;
    if (ttUp >= OutputLayerColumn)
        ttUp = OutputLayerColumn - 1;
    if (ppLow<0)
        ppLow = 0;
    if (ppUp >= OutputLayerRow)
        ppUp = OutputLayerRow - 1;
}

/****************************************************************/
//該函數用於計算歐氏距離,並找到獲勝神經元
/****************************************************************/
void eula_distance()
{
    int ttLow,ttUp,ppLow,ppUp;
    /*hiro:替換爲用函數處理參數合法性*/
    legalizeInput(ttLow, ttUp, ppLow, ppUp,false);
    for(i=ppLow;i<=ppUp;i++)
        for(j=ttLow;j<=ttUp;j++)
        {
        if (flag[i][j] != is_win)
            {
                for(m=0;m<7;m++)
                    for(n=0;n<5;n++)
                        my_distance[i][j]+=pow((inputMode[l][m][n]-weight[i][j][m*5+n]),2);
                if(my_distance[i][j]<min_distance)
                {
                    min_distance=my_distance[i][j];
                    temp_row=i;
                    temp_column=j;
                }
            }

        }

    if(current_iteration_num>0)
    {
        if(min_distance<=error_limit)
        {
            row[temp_row]=temp_row;
            column[temp_column]=temp_column;
            flag[temp_row][temp_column]=is_win;
        }

    }
}

/****************************************************************/
//調整權值
/****************************************************************/
void weight_change()
{
    int ttLow,ttUp,ppLow,ppUp;
    /*hiro:替換爲用函數處理參數合法性*/
    legalizeInput(ttLow, ttUp, ppLow, ppUp,true);
    for(i=ppLow;i<=ppUp;i++)
        for(j=ttLow;j<=ttUp;j++)
        {
            if(!(flag[i][j]==is_win))
            {
                for(m=0;m<7;m++)
                    for(n=0;n<5;n++)
                    weight[i][j][m*5+n]=
                            weight[i][j][m*5+n]+
                            study_efficiency*(inputMode[l][m][n]-weight[i][j][m*5+n]);
            }
        }   
}


/****************************************************************/
//調整學習效率以及獲勝節點的鄰域大小
/****************************************************************/
void paraChange()
{
    /*hiro:省略了一些類型強制轉換,稍微改了一下寫法*/
    double temp_rate = (double)current_iteration_num / total_iteration_Num;
    study_efficiency*=(1.0-temp_rate);
    neighbor_width*=(1.0 - temp_rate);
    neighbor_height*=(1.0 - temp_rate);
}


/*****************************************************************/
//該函數用於將所有輸入模式從文件中讀入,並存放到數組inputMode中;
//同時進行權值的初始化,採用隨機賦值的方法;
/*****************************************************************/
void initialize()
{
    for(i=0;i<OutputLayerRow;i++)
        row[i] = 100;/*hiro:這個100只是個初始值,沒有什麼意義*/
    for(j=0;j<OutputLayerColumn;j++)
        column[j]=100;
    for(i=0;i<OutputLayerRow;i++)
        for(j=0;j<OutputLayerColumn;j++)
            flag[i][j]=false;
    //從文件中將所有輸入模式讀入,並存放到數組inputMode中
    FILE *pf=fopen("相關數據\\輸入數據\\input.txt","a+");
    if(pf==NULL)
    {
        cout<<"Can not open input file!\n";
        exit(0);
    }

    for(i=0;i<26;i++)
        for(j=0;j<7;j++)
            for(k=0;k<5;k++)
                fscanf(pf,"%d",&inputMode[i][j][k]);
    fclose(pf);/*hiro:忘記關閉文件*/
    /////////////////////////////////////////////////////

    //用於測試是否能夠正確讀入輸入模式
    ofstream mode ("相關數據\\輸出數據\\向量模式.txt",ios::out) ;
    for(i=0;i<26;i++)
    {
        mode<<character[i]<<"\n"<<endl;
        for(j=0;j<7;j++)
        {
            for(k=0;k<5;k++)
                mode<<inputMode[i][j][k]<<" ";
            mode<<endl;
        }
        mode << "\n\n"<<endl;;
    }
    mode.close();
    /*hiro:沒有關閉文件,沒有刷新緩衝*/
    ////////////////////////////////////////////////////

    //權值初始化,採用隨機賦值的方法
    for(i=0;i<OutputLayerRow;i++)
        for(j=0;j<OutputLayerColumn;j++)
            for(k=0;k<InputLayerNum;k++)
                weight[i][j][k]=(double(rand()%101))/100.0;
    /////////////////////////////////////////////////////

    //用於測試是否能夠正確初始化權值
    ofstream quan ("相關數據\\輸出數據\\初始的權值.txt",ios::out) ;
    for(i=0;i<OutputLayerRow;i++)
        for(j=0;j<OutputLayerColumn;j++)
            {
                quan<<"\n\n\n"<<"Node["<<i+1<<"]["<<j+1<<"]"<<endl;
                for(k=0;k<InputLayerNum;k++)
                {
                    if(k%5==0)
                        quan<<endl;
                    /*hiro:設置顯示浮點數後6位*/
                    quan<<setprecision(6)<<setiosflags(ios::fixed)<<weight[i][j][k]<<"      ";
                }
                quan<<"\n\n"<<endl;
            }
    quan.close();
    /*hiro:忘記關閉文件和刷新緩衝區*/
    ////////////////////////////////////////////////////
}


/************************************************************/
//利用數據測試訓練後的網絡
//hiro:由於原函數過於冗餘,對兩個函數進行整合,
//使用val來標記輸入輸出的區別,
//val==true使用標準測試數據
//val==false使用非標準測試數據
/************************************************************/
void test_netWork(bool val)
{
    string s_out,s_in;

    if (val)
        s_out = "相關數據\\輸出數據\\標準測試.txt";     
    else{
        s_out = "相關數據\\輸出數據\\非標準測試.txt";
        s_in = "相關數據\\輸入數據\\非標準數據測試.txt";       
        ifstream    fin(s_in);
        if (!fin.is_open()){
            cout << "Can not open input file!\n";
            exit(0);
        }
        for (i = 0; i<26; i++)
            for (j = 0; j<7; j++)
                for (k = 0; k<5; k++)
                    fin >> inputMode[i][j][k];
        fin.close();
    }

    ofstream   fout(s_out);

    for (l = 0; l<26; l++)
    {
        init_distance();
        min_distance = 1000;
        for (i = 0; i<OutputLayerRow; i++)
            for (j = 0; j<OutputLayerColumn; j++)
            {
            for (m = 0; m<7; m++)
                for (n = 0; n<5; n++)
                    /*hiro:稍微去掉一些冗餘的(long double)*/
                    my_distance[i][j] += (long double)pow((inputMode[l][m][n] - (long double)weight[i][j][m * 5 + n]), 2);
            if (my_distance[i][j]<min_distance)
            {
                min_distance = my_distance[i][j];
                temp_row = i;
                temp_column = j;
            }
            }
        fout<< character[l] << "'s winner is Node[" << temp_row + 1 << "][" << temp_column + 1 << "]\n" << endl;
    }
    fout.close();
}

void main(void)
{
    int iteration_numbers[26] = { 0 };/*hiro:簡化了初始化*/
    int total_num=0;
    initialize();
    for(l=0;l<26;l++)
    {
        winner_row=OutputLayerRow/2;
        winner_column=OutputLayerColumn/2;
        while(current_iteration_num<total_iteration_Num)
        {
            init_distance();
            eula_distance();
            weight_change();
            if(min_distance<=error_limit)
                break;
            ++current_iteration_num;
            paraChange();
        };
        iteration_numbers[l]=current_iteration_num+1;
        neighbor_width=OutputLayerColumn;
        neighbor_height=OutputLayerRow;
        study_efficiency = efficiency;
        current_iteration_num=0;
        min_distance=1000.0;
    }

    for(l=0;l<26;l++)
        total_num+=iteration_numbers[l];
    ofstream iteration_num("相關數據\\輸出數據\\迭代次數.txt",ios::out);
    for(l=0;l<26;l++)
    {
        iteration_num<<character[l]<<" 迭代"<<iteration_numbers[l]<<"次!\n"<<endl;
    }
    iteration_num << "整個訓練過程共迭代" << total_num << "次!\n" << endl;
    iteration_num.close();

    /*hiro:去掉DOS顯示,縮短嚴重冗餘的代碼*/
    ofstream all_weight("相關數據\\輸出數據\\訓練後所有權值.txt",ios::out ) ;
    ofstream winner_weight("相關數據\\輸出數據\\訓練後勝出權值.txt", ios::out);
    ofstream winner_node("相關數據\\輸出數據\\獲勝節點.txt", ios::out);
    for (i = 0; i<OutputLayerRow; i++)
        for(j=0;j<OutputLayerColumn;j++)
        {

            all_weight<<"\n\n\n"<<"Node["<<i+1<<"]["<<j+1<<"]"<<endl;
            if (flag[i][j] == is_win)
                winner_weight << "\n\n\n" << "Node[" << i + 1 << "][" << j + 1 << "]" << endl;
            for(k=0;k<InputLayerNum;k++)
            {
                if(k%5==0)  
                    all_weight<<endl;       
            /*  /////////////////////////////////////////////////
                if(weight[i][j][k]>0.9999999)
                    weight[i][j][k]=1.0;
                if(weight[i][j][k]<0.0000001)
                    weight[i][j][k]=0.0;
            */  /////////////////////////////////////////////////
                /*hiro:顯示浮點數小數點後8位*/
                all_weight<<setprecision(8)<<setiosflags(ios::fixed)<<weight[i][j][k]<<"      ";
                if (flag[i][j] == is_win)
                    winner_weight << setprecision(8) << setiosflags(ios::fixed) << weight[i][j][k] << "      ";
            }
            if (flag[i][j] == is_win)
                winner_node << "Node[" << i + 1 << "][" << j + 1 << "]" << endl;
        }
    /*hiro:沒有刷緩衝區和關文件*/

    printf("\n");
    all_weight.close();
    winner_weight.close();
    winner_node.close();
    ///////////////////////////////////////////////////
    //網絡測試
    test_netWork(true);
    test_netWork(false);
}


感想

1.這份代碼應該是出自寫C4.5算法的那位兄臺之手,看着代碼還不算長,於是對代碼的可讀性進行了一定優化,並且減少大量冗餘代碼。
2.算法本身是模擬人體的神經元工作,某一類信息能夠使得一定區域的神經元“興奮”,在數據上表現爲權值高,並且並且該區域內的興奮度隨着半徑減小而提高。通過不斷的學習輸入樣例,最終訓練出用於聚類的網絡。算法本身是不保證收斂的,但是在這份樣例輸入當中,收斂次數只有8次,這一點有點讓我驚訝。迭代一次的複雜度爲O(神經元個數*輸入樣例長度)。並且該算法有個特點,就是訓練出來的結果可視化比較好。相似的元素的獲勝節點【即最能反映出該元素類別的神經元】在神經元網絡中是相近的,以樣例數據爲例,A,O,D,Q,C這幾個數據。往往O,D,Q,C的座標相對集中,因爲他們更“相似”,而往往A的獲勝節點的座標和這四個數據的座標相距甚遠,因爲他們“不太相似”。因爲在學習的時候獲勝節點的會影響到領域半徑內結點的權值,所以相似的元素總能在空間上聚集。這樣可以可視化的顯示出數據的相關性和聚類特性。
3.到此爲止基本上這本書上的源碼就學習完畢了,當然他還提供了一份C#的SVM支持向量機的源碼我還沒有看,我會再寫個總結帖把這個學期的數據挖掘學習筆記,鏈接,資源總結到一塊去,順便考慮把並行也做了。先這樣。【吃飯】

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