自组织映射(SOM),或者大家可能据说过的Kohonen映射,是自组织神经网络的基本类型之一。自组织的能力提供了对之前不可见的输入数据的适应性。它被理论化为最天然的学习方式之一,就像咱们的大脑所使用的学习方式同样,在咱们的大脑中,没有预先定义的模式被认为是存在的。这些模式是在学习过程当中造成的,而且在以更低的维度(如二维或一维)表示多维数据方面具备难以想象的天赋。此外,该网络以这样一种方式存储信息,即在训练集中保持任何拓扑关系。html
更正式地说,SOM是一种集群技术,它将帮助咱们发现大型数据集中有趣的数据类别。它是一种无监督的神经网络,神经元被排列在一个单一的二维网格中。网格必须是矩形的,例如,纯矩形或六边形。在整个迭代过程当中,网格中的神经元将逐渐合并到数据点密度更高的区域。当神经元移动时,它们会弯曲和扭曲网格,直到它们更靠近感兴趣的点,并反映出数据的形状。算法
在这一章中,咱们将讨论如下主题:数组
简而言之,咱们网格上的神经元,经过迭代,它们逐渐适应数据的形状(在咱们的示例中,以下面的图所示,位于点面板左侧)。咱们再来讨论一下迭代过程自己。网络
1.第一步是在网格上随机放置数据。咱们将随机将网格的神经元放在数据空间中,以下图所示:dom
2.第二步是算法将选择单个数据点。机器学习
3.在第三步中,咱们须要找到最接近所选数据点的神经元(数据点)。这就成了咱们最匹配的单元。工具
4.第四步是将最匹配的单元移向该数据点。咱们移动的距离是由咱们的学习率决定的,每次迭代后学习率都会降低。学习
5.第五,咱们将把最匹配单元的邻居移得更近,距离越远的神经元移动得越少。你在屏幕上看到的初始半径变量是咱们用来识别邻居的。这个值,就像初始学习率同样,会随着时间的推移而下降。若是您已经启动并运行了监视器,您能够看到初始学习率随着时间的推移而下降,以下面的屏幕截图所示:优化
6.咱们的第六步也是最后一步,是更新初始学习速率和初始半径,就像咱们目前描述的那样,而后重复它。咱们将继续这一进程,直到咱们的数据点稳定下来并处于正确的位置。this
如今咱们已经有了了一些关于SOMs的直观的认识,咱们再来讨论一下咱们这一章要作的事情。咱们选择了一个很是常见的机制来教咱们的程序,那就是颜色的映射。
颜色自己是由红色、绿色和蓝色表示的三维对象,可是咱们将把它们组织成二维。颜色的组织有两个关键点。首先,颜色被汇集成不一样的区域,其次,具备类似属性的区域一般彼此相邻。
第二个例子更高级一些,它将使用ANN(人工神经网络);这是机器学习的一种高级形式,用于建立与呈现给它的映射相匹配的组织映射。
让咱们看第一个例子。下面是咱们示例的屏幕截图。正如所看到的,咱们有一个随机的颜色图案,当完成时,它由一组类似的颜色组成:
若是咱们成功了,咱们的结果应该是这样的:
让咱们从如下步骤开始:
1.咱们将首先进行500次迭代来实现咱们的目标。使用较小的数字可能不会产生咱们最终想要的混合。例如,若是咱们进行500次迭代,下面是咱们的结果:
2.正如所看到的,咱们离咱们须要达到的目标还很远。可是咱们可以更改迭代,以容许使用彻底正确的设置进行试验。到了这里咱们必须认可,500次的迭代远远不够,因此我把它留做练习,让你本身算出进展到哪里中止,才会让你感到满意(因此这个迭代次数是因人而异的,但能够确定的是500次,确定是不符合绝大多数人的要求的)
3.在设置了迭代次数以后,咱们所要作的就是确保程序拥有咱们想要的随机颜色模式,这能够经过单击随机按钮来实现。有了想要的模式后,只需单击Start按钮并查看结果。
4.一旦你点击开始,中止按钮将被激活,你能够随时中止进程。一旦达到指定的迭代次数,程序将自动中止。
在咱们进入实际代码以前,让咱们看一些组织模式的屏幕截图。咱们能够经过简单地更改不一样的参数来实现出色的结果,咱们将在后面详细描述这些参数。
在下面的截图中,咱们将迭代次数设置为3000次,初始半径为10:
在下面的截图中,咱们将迭代次数设置为4000次,初始半径为18:
在下面的截图中,咱们将迭代次数设置为4000次,初始半径为5:
这里咱们将迭代次数设为5000次,初始学习率设为0.3,初始半径设为25,以下截图所示,获得了指望的结果:
如今让咱们深刻研究代码。
在本例中,咱们将使用AForge并使用DistanceNetwork对象。距离网络是只有一个距离的神经网络。除了用于SOM以外,它还用于一个弹性网络操做,这就是咱们将要使用的来展现进程中对象之间的弹性链接。
咱们将使用三个输入神经元和10000个神经元来建立咱们的距离网络:
// 建立网络 network = new DistanceNetwork(3, 100 * 100);
当咱们点击随机化颜色按钮来随机化颜色时:
/// <summary> /// 赋予网络随机权重 /// </summary> private void RandomizeNetwork() { if (network != null) { foreach (var neuron in (network?.Layers.SelectMany(layer => layer?.Neurons)).Where(neuron => neuron != null)) neuron.RandGenerator = new UniformContinuousDistribution(new Range(0, 255)); network?.Randomize(); } UpdateMap(); }
这里须要注意的是,咱们处理的随机化范围保持在任何颜色的红、绿色或蓝色特征的范围内,即0-255之间(包含0,包含255)。
接下来,咱们来看看咱们的学习循环,就像这样。咱们一下子会深刻研究它:
/// <summary> /// 工做内容 /// </summary> private void SearchSolution() { SOMLearning trainer = new SOMLearning(network); double[] input = new double[3]; double fixedLearningRate = learningRate / 10; double driftingLearningRate = fixedLearningRate * 9; int i = 0; while (!needToStop) { trainer.LearningRate = driftingLearningRate * (iterations - i) / iterations + fixedLearningRate; trainer.LearningRadius = radius * (iterations - i) / iterations; if (rand != null) { input[0] = rand.Next(256); input[1] = rand.Next(256); input[2] = rand.Next(256); } trainer.Run(input); // 每50次迭代更新一次map if ((i % 10) == 9) { UpdateMap(); } i++; SetText(currentIterationBox, i.ToString()); if (i >= iterations) break; } EnableControls(true); }
若是仔细观察,会发现咱们建立的第一个对象是SOMLearning对象。这个对象是为正方形空间学习而优化的,这意味着它指望它所处理的网络具备与其宽度相同的高度。这使得计算网络神经元数量的平方根变得更加容易:
SOMLearning trainer = new SOMLearning(network);
接下来,咱们须要建立变量来保存红色、绿色和蓝色的输入颜色,从中咱们将不断地随机化输入颜色,以实现咱们的目标:
if (rand != null) { input[0] = rand.Next(256); input[1] = rand.Next(256); input[2] = rand.Next(256); }
一旦咱们进入while循环,咱们将不断地更新变量,直到咱们达到所选择的迭代总数。在这个更新循环中,会发生一些事情。首先,咱们将更新学习速率和学习半径,并将其存储在咱们的SOMLearning对象:
trainer.LearningRate = driftingLearningRate * (iterations - i) / iterations + fixedLearningRate;
trainer.LearningRadius = radius * (iterations - i) / iterations;
学习率决定了咱们的学习速度。学习半径会对视觉输出产生很是显著的影响,它决定了相对于获胜神经元多少距离,须要更新的神经元数量。指定半径的圆由神经元组成,神经元在学习过程当中不断更新。一个神经元离获胜神经元越近,它接收到的更新信息就越多。请注意,若是在实验中,将该值设置为零,那么只能更新获胜神经元的权重,而不能更新其余神经元的权重。
如今咱们对这个迭代进行一次训练,并将Input数组传递给它:
trainer.Run(input);
让咱们谈一下学习。正如咱们所提到的,每次迭代都将尝试和学习愈来愈多的信息。这个学习迭代返回一个学习误差,即神经元的权重和输入向量Input的差值。如前所述,距离是根据与获胜神经元之间的距离来测量的。过程以下。
训练器运行一次学习迭代,找到获胜的神经元(权重值与Input中提供的值最接近的神经元),并更新其权重。它还会更新相邻神经元的权重。随着每次学习迭代的进行,网络愈来愈接近最优解。
接下来是运行应用程序的屏幕截图。在实时调试和诊断工具中,能够看到咱们是如何记录每次迭代的,更新地图时使用的颜色值,以及学习速率和学习半径。
因为SOM是自组织的,咱们的第二个例子会更加形象。它能帮助咱们更好地理解幕后所发生的事情。
在本例中,咱们将再次使用AForge。并构建一个二维平面的对象,组织成几个组。咱们将从一个单独的位置开始,直观地获得这些形状的位置。这与咱们的颜色示例在概念上是相同的,它使用了三维空间中的点,只是此次,咱们的点是二维的。可视化在map面板中,这是一个自顶向下的视图,用于查看二维空间中发生的事情,从而得到一个一维图形视图。
起初,SOM网格中的神经元从随机位置开始,但它们逐渐被揉捏成咱们数据形状的轮廓。这是一个迭代的过程,我已经在迭代过程的不一样地方截屏,向你们展现了发生了什么。固然你也能够本身运行这个示例来实时查看它。
咱们将运行500次迭代来展现演进。咱们将从一个空白的白色面板获得一个相似的点的面板:
在咱们点击开始按钮,咱们将看到这些点开始经过移动到正确的位置来组织本身,但愿这将反映出咱们所指定的点:
通过262次迭代:
通过343次迭代:
在完成以后,咱们能够看到对象已经像咱们最初建立的模式那样组织了它们本身。蓝色的点是活跃的神经元,浅灰色的点是不活跃的神经元,画的线是神经元之间的弹性链接。嗯,这有些像《黑客帝国》中的那个绿色的数据流。若是你看到足够仔细,是可以看到3D效果的。
若是你窗体中没有显示链接和不活跃的神经元,你会看到map中的组织模式与咱们的目标达到了相同的集群,对咱们来讲这意味着成功:
那么这一切到底是如何工做的呢。像往常同样,让咱们来看看咱们的主执行循环。正如咱们所看到的,咱们将使用与前面讨论的相同的DistanceNetwork和SOMLearning对象:
/// <summary> /// 工做内容 /// </summary> private void SearchSolution() { // 建立网络 DistanceNetwork network = new DistanceNetwork(2, networkSize * networkSize); // 设置随机生成器范围 foreach (var neuron in network.Layers.SelectMany(layer => layer.Neurons)) neuron.RandGenerator = new UniformContinuousDistribution( new Range(0, Math.Max(pointsPanel.ClientRectangle.Width, pointsPanel.ClientRectangle.Height))); // 建立学习算法 SOMLearning trainer = new SOMLearning(network, networkSize, networkSize); // 建立map map = new int[networkSize, networkSize, 3]; double fixedLearningRate = learningRate / 10; double driftingLearningRate = fixedLearningRate * 9; // 迭代次数 int i = 0; while (!needToStop) { trainer.LearningRate = driftingLearningRate * (iterations - i) / iterations + fixedLearningRate; trainer.LearningRadius = (double)learningRadius * (iterations - i) / iterations; // 开始纪元的训练 trainer.RunEpoch(trainingSet); UpdateMap(network); i++; // 设置当前迭代的信息 SetText(currentIterationBox, i.ToString()); // stop ? if (i >= iterations) break; } // 启用设置控件 EnableControls(true); }
正如咱们前面提到的,LearningRate和LearningRadius在每次迭代中都在不断发展。这一次,让咱们来谈谈训练器的RunEpoch方法。这个方法虽然很是简单,但其设计目的是获取一个输入值向量,而后为该迭代返回一个学习误差(正如咱们如今看到的,有时也称为epoch)。它经过计算向量中的每一个输入样原本实现这一点。学习偏差是神经元的权值和输入之间的绝对差。这种差别是根据获胜神经元之间的距离来测量的。如前所述,咱们针对一个学习迭代/历元运行此计算,找到获胜者,并更新其权重(以及邻居权重)。应该指出的是,当说赢家时,指的是权重值与指定输入向量最接近的神经元,也就是给网络输入的最小距离。
接下来,将重点介绍如何更新映射自己;咱们计算的项目应该与初始输入向量(点)匹配:
private void UpdateMap(DistanceNetwork network) { // 获得第一层 Layer layer = network.Layers[0]; // 加锁 Monitor.Enter(this); // 遍历全部神经元 for (int i = 0; i < layer.Neurons.Length; i++) { Neuron neuron = layer.Neurons[i]; int x = i % networkSize; int y = i / networkSize; map[y, x, 0] = (int)neuron.Weights[0]; map[y, x, 1] = (int)neuron.Weights[1]; map[y, x, 2] = 0; } // 收集活动的神经元 for (int i = 0; i < pointsCount; i++) { network.Compute(trainingSet[i]); int w = network.GetWinner(); map[w / networkSize, w % networkSize, 2] = 1; } // 解锁 Monitor.Exit(this); mapPanel.Invalidate(); }
从这段代码中能够看到,咱们获得了第一层,计算了全部神经元的map,收集了活动神经元,这样咱们就能够肯定获胜者,而后更新map。
咱们肯定获胜者的过程,实际上就是寻找权重与网络输入距离最小的神经元,就是这么简单,必定不要把简单的问题复杂化,就好像那个雨滴与硕士博士教授的笑话同样。
在这一章中,咱们学习了如何利用SOMs和弹性神经网络的力量。如今咱们已经正式从机器学习跨入了神经网络的阶段。