這個學期要學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支持向量機的源碼我還沒有看,我會再寫個總結帖把這個學期的數據挖掘學習筆記,鏈接,資源總結到一塊去,順便考慮把並行也做了。先這樣。【吃飯】