在人的视网膜、脊髓中有一种现象,当一个神经细胞兴奋后,会对周围神经细胞产生抑制作用。极端情况下,不允许其他细胞兴奋,这就是上文提到的学习规则中的胜者为王。

竞争学习算法分为3步

  1. 向量归一化
    输入的模式向量X和竞争层各细胞的内星权向量Wj(j-1,2,...,m)都是进行归一化。并且每次迭代都要进行归一化操作。
  2. 寻找获胜神经元
    竞争层各细胞的内星权向量Wj(j-1,2,...,m)与输入向量X进行相似度比较,不论是用欧氏距离,还是夹角法,(由于X和W都已归一化,)得到的结论都是:与X点积最大的Wj对应的竞争层的细胞j中获胜者。
  3. 网络输出权值调整
    获胜神经元的输出为1,其他细胞的输出为0。
    只有获胜细胞才可以调整其权向量

    α是学习率,随着学习的进展应逐渐减小。

由于这是一种无导师的学习规则,所以迭代的终止条件是学习率衰减到0或某个预定的很小的数。

竞争学习的原理

获胜神经元调整权值的结果是使Wj进一步向当前的输入向量X靠近。当下次出现与X相像的输入模式时,上次的神经元更容易获胜。在反复的竞争学习中,竞争层的各神经细胞对应的权值逐渐被调整为输入样本空间的聚类中心。

自组织映射网(SOFM,Self-Organizing Feature Map)

当人脑接收外界的时空信息时,大脑皮层的特定区域会兴奋,而且类似的外界信息在对应的区域是连续的。因此Kohonen认为,一个神经网络在接受外界输入模式时,将会分为不同的对应区域,且各个区域对输入模式有不同的响应特征,而且这个特征是自动完成的。

SOFM只有两层:输入层和竞争层,竞争层神经元的排列有多种形式:一维线阵、二维平面、三维栅格等等。

权值调整方法是在胜者为王基础上改进的,即优胜领域内的神经元都可以调整权值。理论上应该是离胜者越近,学习率的越大,但是为简化计算,实际中优胜领域内一般取相同的学习率。优胜领域开始定的很大,随着训练次数的增加,最终应该收缩到0。

SOFM分为训练阶段和工作阶段,要训练阶段,权向量被训练为输入样本空间的聚类中心。在工作阶段,当输入向量与某个竞争层的内星权值相似时,自然会被分到对应的聚类上去。因此SOFM可用作模式分类器。注意当输入模式在训练集中从未出现过时,SOFM网只能将它归入最接近的模式分类中去。

输出层的设计

在未知样本类别数目的情况下,输出层细胞数目设计偏多偏少都不好。在这种情况下,宁可先设计较多的输出神经元,以便地映射样本的拓扑结构,如果分类过细再酌情减少输出神经元。输出神经元过多带来的“死神经元”问题(在训练过程中某神经元从未获胜过且远离其他获胜神经元,因而权值从未调整过)一般可通过重新初始化权值得到解决。

输出神经元的排列结构一般要反应实际问题的物理意义。例如对于旅行商问题,二维平面比较直观;对于一般分类问题,一维线阵意义明确且结构简章;对于机械手臂控制问题,用三维栅格更能反应其空间轨迹特征。

权值初始化问题

初始权向量也不能完全均匀地随机分布,而应该与输入样本的大致区域充分重合。

一种简单易行的方法是从训练集中随机抽取m个输入样本作为初始权值,即

另一种方法是先计算全体输入向量的质心,再在质心的基础上叠加小随机值作为初始权向量。

优胜领域的设计

优胜领域的设计原则是不断缩小,通常凭借经验,下面给出两种计算方法:

C1是与竞争层神经元个数m有关的正常数,B1是大于1的常数,tm为预选定的最大训练次数。

学习率的设计

η(t)在训练开始时取很大,之后快速下降,这样有利于快速捕捉到输入向量的大致结构。然后η(t)又在较小的值上缓降至趋于0的值,这样可以精细地调整权值,使之符合输入空间的样本分布结构。比如可用下式

或者就让η(t)随时间线性下降至0

SOFM的特点:

  1. 保序映射。即将输入空间的样本模式类有序地映射在输出层上。
  2. 数据压缩。即将高维空间样本在保持拓扑结构不变的情况下映射到低维空间。SOFM在这方面有明显优势。无论输入空间样本有多少维,都可以在SOFM输出层的某个区域得到响应。
  3. 特征提取。高维空间的向量经过特征提取后可以在低维特征空间得到更清晰的表达,因此映射不仅是单纯的数据压缩,更是一种规律的发现。

作为练习,下面用SOFM求解旅行商最优路径问题

#include<iostream>
#include<set>
#include<cstdlib>
#include<vector>
#include<cmath>
#include<ctime>

using namespace std;

const int city_num=10;      //城市的个数
int iteration_ceil;      //迭代的上限
vector<pair<double,double> > cities(city_num);      //城市位置
vector<vector<double> > weight(city_num);   //权向量
const int prime_neighbour=(city_num-1)/2;      //初始优胜领域半径
const double prime_eta=0.7;     //初始学习率
/**
void init_city(){
    srand(time(0));
    for(int i=0;i<cities.size();++i){
        double x=(rand()+1)/(double)RAND_MAX;
        double y=(rand()+1)/(double)RAND_MAX;
        cities[i]=make_pair(x,y);
    }
}**/

void init_city(){
    cities[0]=make_pair(0.4,0.4439);
    cities[1]=make_pair(0.2439,0.1463);
    cities[2]=make_pair(0.1707,0.2293);
    cities[3]=make_pair(0.2239,0.7610);
    cities[4]=make_pair(0.5171,0.9414);
    cities[5]=make_pair(0.8732,0.6536);
    cities[6]=make_pair(0.6878,0.5219);
    cities[7]=make_pair(0.8488,0.3609);
    cities[8]=make_pair(0.6683,0.2536);
    cities[9]=make_pair(0.6195,0.2634);
}


/*计算两个城市之间的距离*/
double calDist(pair<double,double> c1,pair<double,double> c2){
    double x1=c1.first;
    double y1=c1.second;
    double x2=c2.first;
    double y2=c2.second;
    return sqrt(pow(x1-x2,2)+pow(y1-y2,2));
}

/*向量模长归一化*/
void normalize(vector<double> &vec){
    double sum=0.0;
    for(int i=0;i<vec.size();++i)
        sum+=pow(vec[i],2);
    sum=sqrt(sum);
    for(int i=0;i<vec.size();++i)
        vec[i]/=sum;
}

void init_weight(){
    srand(time(0));
    for(int i=0;i<weight.size();++i){
        vector<double> ele(2);
        ele[0]=rand()/(double)RAND_MAX;
        ele[1]=rand()/(double)RAND_MAX;
        normalize(ele);
        weight[i]=ele;
    }
}

/*根据输入,选择获胜者*/
int pick_winner(double x1,double x2){
    int rect=-1;
    double max=0.0;
    for(int i=0;i<weight.size();++i){
        double product=x1*weight[i][0]+x2*weight[i][1];
        if(product>max){
            max=product;
            rect=i;
        }
    }
    return rect;
}

/*上一次的获胜者本次不能再成为获胜者*/
int pick_winner_2(double x1,double x2,set<int> &has){
    int rect=-1;
    double max=0.0;
    for(int i=0;i<weight.size();++i){
        if(has.find(i)!=has.end())
            continue;
        double product=x1*weight[i][0]+x2*weight[i][1];
        if(product>max){
            max=product;
            rect=i;
        }
    }
    has.insert(rect);
    return rect;
}

int main(int argc,char *argv[]){
	cout<<"input iteration count"<<endl;
	int count;		//每个城市迭代的次数
	cin>>count;
	iteration_ceil=count*city_num;
    init_city();
    init_weight();
    
    double neighbour=prime_neighbour;
    double eta=prime_eta;
    double gradient1=-1*9*prime_eta/iteration_ceil;
    double gradient2=-1*prime_eta/(9*iteration_ceil);
    double b1=prime_eta;
    double b2=prime_eta/9;
    double B=-10*log(prime_neighbour)/iteration_ceil;
    for(int iteration=0;iteration<iteration_ceil;++iteration){
    	//cout<<1.0*iteration/iteration_ceil<<"\teta="<<eta<<"\tneighbour="<<neighbour<<endl;
        int city_index=iteration%city_num;
        double x1=cities[city_index].first;
        double x2=cities[city_index].second;
        int winner=pick_winner(x1,x2);
        /*更改领域内的权值*/
        weight[winner][0]+=eta*(x1-weight[winner][0]);
        weight[winner][1]+=eta*(x2-weight[winner][1]);
        for(int i=0;i<neighbour;++i){
            int index1=(winner-1-i+city_num)%city_num;  //左边的邻居
            int index2=(winner+1+i)%city_num;       //右边的邻居
            weight[index1][0]+=eta*(x1-weight[index1][0]);
            weight[index1][1]+=eta*(x2-weight[index1][1]);
            weight[index2][0]+=eta*(x1-weight[index2][0]);
            weight[index2][1]+=eta*(x2-weight[index2][1]);
        }
        /*更新领域半径*/
        if(iteration<iteration_ceil/10){        //前10%的迭代中以指数下降(先快后慢)使优胜领域半径缩小到1
            neighbour=prime_neighbour*pow(M_E,B*iteration);
        }
        else if(iteration<0.4*iteration_ceil){  //在接下来30%的迭代中,使领域半径保持为1
            neighbour=1;
        }
        else{   //在最后60%的迭代中,领域半径保持为0,即领域内只剩下优胜神经元
            neighbour=0;
        }
        /*更新学习率*/
        if(iteration<0.1*iteration_ceil){   //在前10%的迭代中,学习率线性下降到原来的10%
            eta=gradient1*iteration+b1;
        }
        else{       //后90%的迭代中线性降低到0
            eta=gradient2*iteration+b2;
        }
    }

    vector<int> path(city_num);
    set<int> has;
    for(int i=0;i<city_num;++i){
        double x1=cities[i].first;
        double x2=cities[i].second;
        int winner=pick_winner_2(x1,x2,has);
        path[winner]=i;
    }

    cout<<"环路:";
    vector<int>::const_iterator iter=path.begin();
    while(iter!=path.end())
        cout<<*iter++<<"-->";
    cout<<path[0]<<endl;

    double dist=0.0;
    for(int i=0;i<city_num;++i){
        int other=(i+city_num-1)%city_num;
        dist+=calDist(cities[path[i]],cities[path[other]]);
    }
    cout<<"总路程:"<<dist<<endl;
    return 0;
}

该问题的最优解是2.698,可以看到每个输入迭代300次得到的解是2.842。

(上面的代码中好像忘记了每次循环时对输入向量和权值向量进行归一化处理)

学习向量量化(LVQ,Learning Vector Quantization)神经网络

什么是“量化”?其实类似于把模拟量离散化的过程。向量量化就是把高维空间的一组向量进行聚类,用聚类中心来代表这些向量。自组织映射可以起到聚类的作用,但还不能用于分类和识别。接下来采用学习向量量化的方法进行分类,这是一种有监督的方法,通过在训练时加入教师信号对权值进行调整。

LVQ在SOFM的基础上多了一个输出层,输出层每个神经元只跟一组竞争层的细胞相连,且连接权值为1。竞争层获胜神经元的输出为1,而其他神经元输出均为0。这样一来,与获胜神经元相连接的输出层神经元输出也为1,其他输出层细胞输出均为0。从而达到分类的效果。

LVQ网络在训练前已确定好了竞争层到输出层的权值,训练过程通过调整输入层到竞争层的权值来完成。因为我们有教师信号(即每个输入样本所属的真实分类),当网络分类正确时,获胜神经元的权值向输入向量方向调整,分类错误则按相反方向调整。竞争层到输出层的权值的确定这里可以举一个例子:

表示竞争层的第1、3个细胞与输出层的第1个细胞相连;竞争层的第2、5个细胞与输出层的第2个细胞相连;竞争层的第4、6个细胞与输出层的第3个细胞相连。

LVQ学习算法步骤如下:

  1. 初始化。竞争层权值初始化为很小的随机数(不需要归一化),确定学习率和迭代次数。
  2. 输入样本向量X。
  3. 寻找获胜神经元。与输入向量欧氏距离最近的权值对应的竞争层细胞获胜。
  4. 调整权值。如果分类正确:

    如果分类错误:
  5. 更新学习率
  6. 转到第2步,除非迭代次数已经到了预定的次数。

LVQ是SOFM一种有监督形式的扩展,结合了竞争学习和监督学习的优点。

posted on 2012-07-13 18:44  高性能golang  阅读(10517)  评论(0编辑  收藏  举报