自組織映射網絡和學習向量量化網絡


在人的視網膜、脊髓中有一種現象,當一個神經細胞興奮后,會對周圍神經細胞產生抑制作用。極端情況下,不允許其他細胞興奮,這就是上文提到的學習規則中的勝者為王。

競爭學習算法分為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一種有監督形式的擴展,結合了競爭學習和監督學習的優點。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM