您的位置:首页 > 理论基础 > 计算机网络

DM&ML_note.7-神经网络聚类算法:SOM

2016-11-10 11:52 260 查看
这个学期要学DM&ML,用的是《数据挖掘算法原理与实现》王振武 本着造福同学的思想,开一个DM&ML的笔记系列,打算给书上的源代码添加一点注释,方便阅读和理解

前置知识要求

SOM网络设计

具体实现

感想

前置知识要求

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]
-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]
-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]
- (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支持向量机的源码我还没有看,我会再写个总结帖把这个学期的数据挖掘学习笔记,链接,资源总结到一块去,顺便考虑把并行也做了。先这样。【吃饭】
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: