对于某一图形或某一频率的特定兴奋过程是自组织特征映射网中竞争机制的生物学基础。神经元的有序排列以及对外界信息的连续映像在自组织特征映射网中也有反映,当外界输入不同的样本时,网络中哪个位置的神经元兴奋在训练开始时时随机的。但自组织训练后会在竞争层形成神经元的有序排列,功能相近的神经元非常靠近,功能不同的神经元离的较远。这一特点与人脑神经元的组织原理十分相似。Kohonen 教授提出一种自组织特征映射网 (Self-Organizing Feature Map , SOFM ), 又称 Kohonen 网 。 Kohonen 认为 ,一个神经网络接受外界输入模式时, 将会分为不同的对应区域, 各区域对输入模式具有不同的响应特征,而且这个过程是自动完成的。 自组织特征映射正是根据这一看法提出来的 ,其特点与人脑的自组织特性相类似。
SOFM网共有两层,输入层个神经元通过权向量将外界信息汇集到输出层的个神经元。输入层的神经元数与样本维数相等。输出层为竞争网络中的竞争层。神经元的排列有多种形式,如一维线阵、二维平面阵和三维栅格阵,常见的是一维和二维。一维是最简单的,结构特点如下图所示,每个竞争层的神经元之间都有侧向连接。输出按照二维平面组织是SOFM网最典型的组织方式,更具有大脑皮层形象,输出层每个神经元同它周围的其他神经元侧向连接,排列成棋盘状平面,结构如下图右所示:
SOFM网采用的学习算法称为Kohonen算法,是在胜者为王算法基础上加以改进而成的,其主要的区别在于调整权向量与侧抑制的方式不同。在胜者为王学习规则中,只有竞争获胜神经元才能调整权向量,其他任何神经元都无权调整权向量,因此它对周围所有神经元的抑制是“封杀”式的。而SOFM网的获胜神经元对其学习算法中不仅获胜神经元本身要调整权向量,它周围的神经元在其影响下也要程度不同地调整权向量。这种调整可用下图所示的三种函数表示,其中(b)中的函数曲线是由(a)中的两个正态曲线组合而成的。
b-d中的3中函数沿中心轴旋转后可形成形状似帽子的曲面,按顺序分别为墨西哥帽函数、大礼帽函数和厨师帽函数。墨西哥帽函数是Kohonen提出的,它表明获胜神经元有最大的权值调整量,邻近的神经元有稍小的调整量,离获胜神经元距离越大,权的调整量越小,直到某一距离R时,权值调整量为0,。当距离再远一些时,权值调整量略负,更远时又回到0。墨西哥帽函数表现出的特点与生物系统的十分相似,但计算上的复杂性影响了网络训练的收敛性。因此,在SOFM网络中常使用与墨西哥函数类似的简化函数,如大礼帽函数和进一步简化后的厨师帽函数。
以获胜神经元为中心设定一个邻域半径,该半径圈定的范围称为优胜邻域。在SOFM网学习网络算法中,优胜邻域内所有神经元均按其离开获胜神经元的距离远近不同程度地调整权值。优胜邻域开始定得很大,但其大小随着训练次数的增加不断收缩,最终收缩到半径为零。
下面给出简单的代码来实现SOFM算法:
package com.gl.test; import java.awt.*; import java.applet.*; import java.util.*; class synapse { // this class models a weighted connection to a cell double weight; static double sharpness = 2.0; cell c; public static double sigmoid(double x) { return 1.0 / (1.0 + Math.exp(-x)); } public synapse(cell c, double dist) { weight = 0.2 * sigmoid((SOFM.limit - sharpness * dist) / sharpness); // weight = 0.2 / (1.0 + dist); // 简单距离函数 this.c = c; } public void update(double[] v, Graphics g) { c.update(v, map.rate * weight, g); } } class cell { // has three basic properties: a grid position (gridv) // an input space position (ipv) // and a set of neighbours (stored in a java.util.Vector) double[] gridv; double[] ipv; int d; static int neighboursEstimate = 30; //紧邻数目,可以根据实际修改 Vector neighbours; // 向量 public cell(int d, int x, int y) { // 创建一个随机初始化向量 ipv = new double[d]; gridv = new double[2]; gridv[0] = (double) x; gridv[1] = (double) y; this.d = d; randCell(); neighbours = new Vector(neighboursEstimate); } public void randCell() { for (int i = 0; i < d; i++) ipv[i] = Math.random(); } public void addNeighbour(cell c) { neighbours.addElement(new synapse(c, dist(gridv, c.gridv))); } public void removeNeighbours() { neighbours.removeAllElements(); } public void updateNeighbours(double[] v, Graphics g) { // 以权重迭代并更新计算紧邻 g.setColor(map.v2c(v)); g.fillOval((int) gridv[0] * map.size, (int) gridv[1] * map.size, map.size, map.size); g.setColor(Color.black); g.drawOval(1 + (int) gridv[0] * map.size, 1 + (int) gridv[1] * map.size, map.size - 2, map.size - 2); try { Thread.sleep(100); } catch (Exception e) { } for (Enumeration e = neighbours.elements(); e.hasMoreElements();) ((synapse) e.nextElement()).update(v, g); } public void update(double[] v, double w, Graphics g) { // 以加权平局更新当前向量 for (int i = 0; i < d; i++) { ipv[i] = (1.0 - w) * ipv[i] + w * v[i]; g.setColor(map.v2c(ipv)); g.fillRect((int) gridv[0] * map.size, (int) gridv[1] * map.size, map.size, map.size); } } public static double sqr(double x) { return x * x; } public static double dist(double[] x, double[] y) { double dis = 0.0; for (int i = 0; i < x.length; i++) dis += sqr(x[i] - y[i]); return Math.sqrt(dis); } public double dist(double[] v) { return dist(ipv, v); } } class map { int d; // 输入维数 int n; // 网格数目 // cell[][] points; static int size = 40; // 绘图像数 static double rate; static double decayFac = 0.99; Vector cells; public map(int d, int n) { this.n = n; this.d = d; cells = new Vector(n * n); rate = 1.0; makeMap(); setNeighbours(); } public void makeMap() { for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) cells.addElement(new cell(d, i, j)); } public void reset() { rate = 1.0; for (Enumeration e = cells.elements(); e.hasMoreElements();) ((cell) e.nextElement()).randCell(); } public void setNeighbours() { for (Enumeration e = cells.elements(); e.hasMoreElements();) cellNeighbours((cell) e.nextElement()); } public void cellNeighbours(cell c) { c.removeNeighbours(); for (Enumeration e = cells.elements(); e.hasMoreElements();) { cell cand = (cell) e.nextElement(); // cand is current candidate if (cell.dist(c.gridv, cand.gridv) < SOFM.limit) c.addNeighbour(cand); } } public cell getWinner(double[] v) { double minDist = 100000.0; // 默认距离,做比较用 cell choice = null; for (Enumeration e = cells.elements(); e.hasMoreElements();) { cell cand = (cell) e.nextElement(); double curDist = cand.dist(v); if (curDist < minDist) { choice = cand; minDist = curDist; } } return choice; } public void updateMap(Color c, Graphics g) { update(c2v(c), g); // 将颜色转为向量 } public void update(double[] v, Graphics g) { cell winner = getWinner(v); winner.updateNeighbours(v, g); rate *= decayFac; } public static double[] c2v(Color c) { double[] v = new double[3]; v[0] = (double) c.getRed() / 255.0; v[1] = (double) c.getGreen() / 255.0; v[2] = (double) c.getBlue() / 255.0; return v; } public static Color v2c(double[] v) { return new Color((float) v[0], (float) v[1], (float) v[2]); } public void paint(Graphics g) { for (Enumeration e = cells.elements(); e.hasMoreElements();) { cell c = (cell) e.nextElement(); g.setColor(v2c(c.ipv)); g.fillRect((int) c.gridv[0] * size, (int) c.gridv[1] * size, size, size); } } } public class SOFM extends Applet implements Runnable { map sorg; Thread animator; int nCols = 8; Color[] cols = new Color[8]; static double limit = 5.0; int sleepTime = 10; Button start, stop, reset; Choice neighbourhood, decay, speed; // decay not used Panel buttons; public void init() { setControlPanel(); initColors(); sorg = new map(3, 10); animator = new Thread(this); animator.start(); } public void setControlPanel() { setLayout(new BorderLayout()); buttons = new Panel(); start = new Button("start"); stop = new Button("stop"); reset = new Button("reset"); neighbourhood = new Choice(); neighbourhood.addItem("1.0"); neighbourhood.addItem("2.0"); neighbourhood.addItem("3.0"); neighbourhood.addItem("5.0"); neighbourhood.addItem("7.0"); neighbourhood.addItem("10.0"); neighbourhood.select("5.0"); speed = new Choice(); speed.addItem("fast"); speed.addItem("slow"); speed.select("fast"); buttons.add(new Label("Radius")); buttons.add(neighbourhood); buttons.add(start); buttons.add(stop); buttons.add(reset); buttons.add(speed); add("South", buttons); } public void initColors() { cols[0] = Color.red; cols[1] = Color.blue; cols[2] = Color.green; cols[3] = Color.white; cols[4] = Color.black; cols[5] = Color.magenta; cols[6] = Color.pink; cols[7] = Color.yellow; } public static int randInt(int range) { return (int) (Math.random() * (double) range); } public void paint(Graphics g) { validate(); sorg.paint(g); } public boolean handleEvent(Event event) { // use 1.0 event model for maxmimum browser compatibilty if (event.id == Event.ACTION_EVENT) { if (event.target == start) { // System.out.println("start"); animator.resume(); return true; } if (event.target == stop) { animator.suspend(); return true; } if (event.target == reset) { sorg.reset(); repaint(); return true; } if (event.target == speed) { String s = speed.getSelectedItem(); if (s.equals("slow")) sleepTime = 1000; else sleepTime = 10; return true; } if (event.target == neighbourhood) { limit = new Double(neighbourhood.getSelectedItem()).doubleValue(); // System.out.println(limit); sorg.setNeighbours(); return true; } } return super.handleEvent(event); } public void run() { sorg.paint(getGraphics()); while (true) { Color current = cols[randInt(nCols)]; Graphics g = getGraphics(); g.setColor(current); g.fillRect(180, 410, 40, 30); // lazy - ouch! sorg.updateMap(current, g); try { Thread.sleep(sleepTime); } catch (Exception e) { System.out.println(e); } } } }