双向联想记忆神经网络
联想记忆网络的研究是神经网络的重要分支,在各种联想记忆网络模型中,由B·Kosko于1988年提出的双向联想记忆(Bidirectional
Associative Memory,BAM)网络的应用最为广泛。前面介绍过的Hopfiled网络可实现自联想,具体内容可以参考博文《反馈神经网络Hopfield网络》。而BAM网络可以实现双向异联想,具有离散型、连续型和自适应型等多种形式。本篇博文主要介绍离散型BAM网络。
用外积和法设计的权矩阵,不能保证任意P对模式的全部正确联想,但下面的定理表明,如对记忆模式对加以限制,用外积和法设计BAM网具有较好的联想能力。
定理:若P个记忆模式Xp,p =1,2 ,… ,P,x ∈{ -1,1}构成的n维模式,两两正交,且权值矩阵W按上式得到,则向BAM网输入P个记忆模式中的任何一个Xp时,只需一次便能正确联想起对应的模式Yp。
以下代码实现的BAM网络,并应用BAM网络存储记忆三个人的名字及其电话号码,按照上面介绍的权值设计方法,进行了网络权值设计,并通过含有噪声的名字输入,实现对名字和电话号码的双联想。具体实现参看以下代码(感谢大佬的倾情贡献:)
**********************************************************
一、BAM网络结构与原理
BAM网络是一种双层双向网络,当向其中一层加入输入信号时,另一层可得到输出。由于初始模式可以作用于网络的任一层,信息可以双向传播,所以没有明确的输入层或输出层。可将其中的一层称为X层,有n个神经元节点;另一层称为Y层,有m个神经元节点。两层的状态向量可取单极性二进制0或1,也可以取双极性离散值1或-1。如果令由X到Y的权矩阵为W,则由Y到X的权矩阵便是其转置矩阵WT
BAM网实现双向异联想的过程是网络运行从动态到稳态的过程。对已建立权值矩阵的BAM网,当将输入样本XP作用于X侧时,该侧输出X(1)=XP通过 W阵加权传到Y侧,通过该侧节点的转移函数fy进行非线性变换后得到输出Y(1)=fy (WX(1));再将该输出通过WT阵加权从Y侧传回X侧作为输入,通过X侧节点的转移函数fx进行非线性变换后得到输出X(2)=fx[WTY(1)]=fx{[WT[ fy (WX(1))]}。这种双向往返过程一直进行到两侧所有神经元的状态均不再发生变化为止。此时的网络状态称为稳态,对应的Y侧输出向量YP便是模式 XP经双向联想后所得的结果。同理,如果从Y侧送入模式YP,经过上述双向联想过程,X侧将输出联想结果X。这种双向联想过程可用下图表示:
对应的计算公式如下:
对于经过充分训练的权值矩阵,当向BAM网络一侧输入有残缺的已存储模式时,网络经过有限次运行不仅能在另一侧实现正确的异联想,而且在输入侧重建了完整的输入模式。也就是说,从残缺的输入模式,既可以通过BAM实现异联想,还可以重建完整的输入模式,这个功能将会非常有用。比如对于下面将分享的一个BAM网络实现就是如此,通过(X,Y)几对需要存储的模式,计算出所需要的权矩阵,例子中选择的是人名和电话的双联想,通过输入残缺的人名,计算好的BAM网络能够实现对电话号码的联想,同时将残缺的人名补全。
二、BAM网络结构的能量函数与稳定性分析
跟Hopfield网络类似,若BAM网络的阈值T为0,则能量函数定义为:
BAM 网双向联想的动态过程就是能量函数量沿其状态空间中的离散轨迹逐渐减少的过程。当达到双向稳态时,网络必落入某一局部或全局能量最小点。对于具体的推理过程,本博文不再进行详述。经过一番的推导,可以得到如下的能量变化公式:
上式表明BAM网的能量在动态运行过程中不断下降,当网络达到能量极小点时即进入稳定状态,此时网络两侧的状态都不再变化。证明过程对BAM网权矩阵的学习规则并未作任何限制,而且得到的稳定性的结论与状态更新方式为同步或异步无关。考虑到同步更新比异步更新时能量变化大,收敛速度比串行异步方式快,故采常用同步更新方式。
三、BAM网络结构的权值设计
对于离散 BAM
网络,一般选转移函数f (·) =sign(·)。当网络只需存储一对模式(X1,Y1)时,若使其成为网络的稳定状态,应满足如下条件:
当需要存储 P对模式时,将以上结论扩展为P对模式的外积和,从而得到Kosko提出的权值学习公式:
定理:若P个记忆模式Xp,p =1,2 ,… ,P,x ∈{ -1,1}构成的n维模式,两两正交,且权值矩阵W按上式得到,则向BAM网输入P个记忆模式中的任何一个Xp时,只需一次便能正确联想起对应的模式Yp。
具体的一个例子:含噪声字符的联想过程,如下图:
四、BAM网络的应用
BAM 网络的设计比较简单,只需由几组典型输入、输出向量构成权矩阵。运行时由实测到的数据向量与权矩阵作内积运算便可得到相应的信息输出。这是一种大规模并行处理大量数据的有效方法,具有实时性和容错性。更具魅力的是,这种联想记忆法无需对输入向量进行预处理。便可直接进入搜索,省去了编码与解码工作。
五、BAM网络的实现
/****************************************************************************** D E C L A R A T I O N S ******************************************************************************/ #include "stdafx.h" #include #include typedef int BOOL; typedef char CHAR; typedef int INT; #define FALSE 0 #define TRUE 1 #define NOT ! #define AND && #define OR || #define LO -1 #define HI +1 #define BINARY(x) ((x)==LO ? FALSE : TRUE) #define BIPOLAR(x) ((x)==FALSE ? LO : HI) typedef struct { /* A LAYER OF A NET: */ INT Units; /* - number of units in this layer */ INT* Output; /* - output of ith unit */ INT** Weight; /* - connection weights to ith unit */ } LAYER; typedef struct { /* A NET: */ LAYER* X; /* - X layer */ LAYER* Y; /* - Y layer */ } NET; /****************************************************************************** R A N D O M S D R A W N F R O M D I S T R I B U T I O N S ******************************************************************************/ void InitializeRandoms() { srand(4711); } BOOL RandomEqualBOOL() { return rand() % 2; } /****************************************************************************** A P P L I C A T I O N - S P E C I F I C C O D E ******************************************************************************/ #define NUM_DATA 3 #define IN_CHARS 6 #define OUT_CHARS 8 #define BITS_PER_CHAR 6 //使用双极型的二码元对字符进行编码,因为字符都是大写,对应的A-Z Ascii码为:65... #define FIRST_CHAR ' '//如果使用A-空格的ASCII码32就可以使用上面的6位进行编码了。 #define N ((IN_CHARS) * BITS_PER_CHAR)//所以X层就是IN_CHARSx编码数量个神经元 #define M (OUT_CHARS * BITS_PER_CHAR)//所以Y层就是OUT_CHARSx编码数量个神经元 CHAR Names [NUM_DATA][IN_CHARS] = { "TINA ", "ANTJE", "LISA " }; CHAR Names_[NUM_DATA][IN_CHARS] = { "TINE ", "ANNJE", "RITA " }; CHAR Phones[NUM_DATA][OUT_CHARS] = { "6843726", "8034673", "7260915" }; INT Input [NUM_DATA][N]; INT Input_[NUM_DATA][N]; INT Output[NUM_DATA][M]; FILE* f; void InitializeApplication(NET* Net) { INT n,i,j,a,a_; for (n=0; nUnits / BITS_PER_CHAR); i++) { a = 0; p = 1; for (j=0; jOutput[i*BITS_PER_CHAR+j]) * p; p *= 2; } fprintf(f, "%c", a + FIRST_CHAR); printf("%c", a + FIRST_CHAR); } } void FinalizeApplication(NET* Net) { fclose(f); } /****************************************************************************** I N I T I A L I Z A T I O N ******************************************************************************/ void GenerateNetwork(NET* Net) { INT i; Net->X = (LAYER*) malloc(sizeof(LAYER)); Net->Y = (LAYER*) malloc(sizeof(LAYER)); Net->X->Units = N; Net->X->Output = (INT*) calloc(N, sizeof(INT)); Net->X->Weight = (INT**) calloc(N, sizeof(INT*)); Net->Y->Units = M; Net->Y->Output = (INT*) calloc(M, sizeof(INT)); Net->Y->Weight = (INT**) calloc(M, sizeof(INT*)); for (i=0; iX->Weight[i] = (INT*) calloc(M, sizeof(INT)); } for (i=0; iY->Weight[i] = (INT*) calloc(N, sizeof(INT)); } } void CalculateWeights(NET* Net) { INT i,j,n; INT Weight; for (i=0; iX->Units; i++) { for (j=0; jY->Units; j++) { Weight = 0; for (n=0; nX->Weight[i][j] = Weight; Net->Y->Weight[j][i] = Weight; } } } void SetInput(LAYER* Layer, INT* Input) { INT i; for (i=0; iUnits; i++) { Layer->Output[i] = Input[i]; } WriteLayer(Layer); } void SetRandom(LAYER* Layer) { INT i; for (i=0; iUnits; i++) { Layer->Output[i] = BIPOLAR(RandomEqualBOOL()); } WriteLayer(Layer); } void GetOutput(LAYER* Layer, INT* Output) { INT i; for (i=0; iUnits; i++) { Output[i] = Layer->Output[i]; } WriteLayer(Layer); } /****************************************************************************** P R O P A G A T I N G S I G N A L S ******************************************************************************/ BOOL PropagateLayer(LAYER* From, LAYER* To) { INT i,j; INT Sum, Out; BOOL Stable; Stable = TRUE; for (i=0; iUnits; i++) { Sum = 0; for (j=0; jUnits; j++) { Sum += To->Weight[i][j] * From->Output[j]; } if (Sum != 0) { if (Sum < 0) Out = LO; if (Sum > 0) Out = HI; if (Out != To->Output[i]) { Stable = FALSE; To->Output[i] = Out; } } } return Stable; } void PropagateNet(LAYER* From, LAYER* To) { BOOL Stable1, Stable2; do { Stable1 = PropagateLayer(From, To);//依次调整,先X作为输入,Y作为输出;对Y的输出状态进行调节;然后Y作为输入,X作为输出,对X的输出状态进行调节。 Stable2 = PropagateLayer(To, From); } while (NOT (Stable1 AND Stable2));//一直到所有的状态都已经稳定为止。 } /****************************************************************************** S I M U L A T I N G T H E N E T ******************************************************************************/ void SimulateNet(LAYER* From, LAYER* To, INT* Pattern, INT* Input, INT* Output) { SetInput(From, Pattern); fprintf(f, " -> ");printf(" -> "); SetRandom(To); fprintf(f, " | ");printf(" | "); //设置输出的状态为随机,然后不断的进行状态调整 PropagateNet(From, To); GetOutput(From, Input); fprintf(f, " -> ");printf(" -> "); GetOutput(To, Output); fprintf(f, "\n\n");printf("\n\n"); } /****************************************************************************** M A I N ******************************************************************************/ void main() { NET Net; INT n; INT I[N], O[M]; InitializeRandoms(); GenerateNetwork(&Net); InitializeApplication(&Net); CalculateWeights(&Net); for (n=0; n
**********************************************************
2015-8-7