用Python和TensorFlow实现自组织地图

220 阅读14分钟

伴随着这篇文章的代码可以在订阅后收到

*表示需要

电子邮件地址*

当谈到神经网络和深度学习时,我们通常会想到前馈和监督学习。 神经网络和监督学习。更准确地说,我们关注的是在 学习过程中,有输入和输出数据可用的神经网络。基于这些信息,这种神经网络会改变其权重,并能够学习如何解决某个问题。然而,还有其他类型的学习,我们将探索使用这些其他方法的神经网络。

也就是说,我们要熟悉无监督学习。这种类型的学习在过去几年中得到了普及。使用这种类型的学习的神经网络只获得输入数据,并在此基础上产生某种形式的输出。在学习过程中,正确答案并不为人所知,神经网络试图自己找出数据中的模式。这种方法的一个结果是,我们通常有某种数据的聚类或分类。自组织地图,简称SOM,就是采用这种方法。

Ultimate Guide to Machine Learning with Python

这捆电子书是专门为 初学者制作的 。
从Python基础知识到机器学习算法在生产中的部署,一切都在一个地方。
今天就成为机器学习的超级英雄

在**上一篇文章**中,我们熟悉了自组织地图的主要概念,在这篇文章中,我们将更深入地研究。我们探讨了他们如何利用一种不同的学习类型,而不是我们在人工神经网络世界的旅行中所看到的--无监督学习。这是一种学习的类型,在这种学习中,网络对于某种输入并没有得到预期的结果,但是它却能自己找出内在的数据关系。自组织地图(SOM)使用这种方法来进行聚类和分类,它们在这方面相当出色。在这篇文章中,我们将介绍。

  1. 自组织地图(SOM)架构
  2. 自组织地图(SOM)的学习过程
  3. 现有的实施方案 自组织地图(SOM)。
  4. 用Python和Tensorflow实现

1.自组织地图(SOM)架构

尽管这种网络的早期概念可以追溯到1981年,但它们是在1992年由芬兰学院的教授Teuvo Kohonen开发并正式确定的。从本质上讲,它们是利用矢量量化来检测多维数据中的模式,并在许多低维空间中表示它们--通常是一维或二维,但我们将在后面了解更多细节。

然而,重要的是要以全新的视角来看待这些网络,忘记标准的神经元/连接和权重概念。这些网络使用的是同样的术语,但在它们的世界里有不同的含义。让我们来看看。

这些网络被称为地图是有原因的。它们是片状的神经网络,其神经元被输入信号中的各种模式或类模式激活。看一下下面的图片。

Self-Organizing Maps High Level Overview

这里我们可以看到一个简单的自组织地图结构。我们有两个输入神经元,它们基本上呈现了我们数据集中的特征。这也意味着我们的输入数据可以用三维向量来表示。在它们上面,我们可以看到所谓的地图神经元。这些神经元的目标是将输入神经元上收到的数据呈现为二维数据。意思是,在这个例子中,自组织地图使用无监督学习将三维数据聚类为二维表示。

当然,我们的输入数据可以有任何数量的维度,我们的输出(映射)数据可以有任何数量的维度。值得注意的是,每个映射神经元都与每个输入神经元相连,而映射神经元之间是不相连的。这使得每个地图神经元对它们的邻居有什么值都视而不见。

Self-Organizing Maps Visual

每个连接仍然有一个权重,但它们的使用方式与前馈网络中的不同。基本上,你可以看到权重现在代表映射神经元与输入矢量的关系。每个映射神经元都可以通过唯一的 i, j坐标来识别,它们连接上的权重是根据输入数据上的值来更新的,但后面会有更多的介绍。

现在你可以看到为什么对这种类型的网络采取新的观点是很重要的。即使他们使用相同的术语,如神经元、连接和权重,其含义也完全不同。除此以外,你可以看到他们的结构要比其他 结构 ,简单得多。 前馈神经网络.这就造成了学习过程也是不同的。因此,让我们看看这些网络是如何学习的。

2.自组织图(SOM)的学习过程

正如我们之前提到的,自组织地图使用无监督学习。这种类型的学习也被称为竞争性学习,我们将在第二部分看到原因。 自组织地图学习过程中的第一步是初始化连接上的所有权重。

之后,数据集中的一个随机样本被用作网络的输入。然后,网络计算哪些神经元最像输入数据(输入向量)的权重。为此目的,使用了这个公式。

其中 n 是连接(权重)的数量。具有最佳结果的地图神经元被称为最佳匹配单元或BMU。从本质上讲,这意味着输入矢量可以用这个映射神经元表示。现在,自组织地图在学习过程中不仅仅是计算这个点,而且还试图使它 "更接近 "所收到的输入数据。

Self-Organizing Maps High Level Overview

这意味着这个连接上的权重被更新,以使计算出的距离更小。然而,这并不是唯一要做的事情。BMU的邻居的权重也被修改,以便它们也更接近这个输入向量。这就是整个地图如何被 "拉 "向这个点的原因。为此,我们必须知道将被更新的邻居的 半径。这个半径最初是很大的,但在每次迭代(epoch)中都会缩小。因此,训练自组织地图的下一步实际上是计算上述半径值。下面的公式被应用。

其中t 是当前迭代,σo 是地图的半径。公式中的λ是这样定义的。

其中k 是迭代的次数。这个公式利用了指数衰减,使半径随着训练的进行而变小,这也是最初的目标。简而言之,这意味着通过数据的每一次迭代都会使相关点更接近输入数据。自组织地图是以这种方式进行微调的。

Self-Organizing Maps Learning

当计算出当前迭代的半径时,半径内所有神经元的权重都会被更新。神经元离BMU越近,其权重变化越大。这是通过使用这个公式实现的。

这是主要的学习公式,它有几个重要的点需要讨论。第一个是L(t) ,它代表学习率。与半径公式类似,它也是利用指数衰减,而且每次迭代都会变小。

除此之外,我们还提到,如果神经元离BMU更近,那么该神经元的权重将被更多地修改。在公式中,这是用Θ*(t)处理的。* 这个值是这样计算的。

显然,如果神经元离BMU更近, distBMU 更小,而Θ*(t)*值更接近于1。这意味着这种神经元的权重值将被更多地改变。这整个过程要重复几次。

Self-Organizing Maps Learning

总而言之,这些是自组织地图学习过程中最重要的步骤。

  1. 权重初始化
  2. 从数据集中选择输入向量,作为网络的输入。
  3. 计算BMU
  4. 计算将被更新的邻居的半径
  5. 调整半径内的每个神经元的权重,使其更像输入向量
  6. 对于数据集的每个输入向量,重复2到5的步骤。

当然,在自组织地图的学习过程中,所提出的方程式有很多变化。事实上,人们已经做了大量的研究,试图找到迭代次数、学习率和邻域半径的最佳值。发明者Teuvo Kohonen建议将这个学习过程分成两个阶段。在第一阶段,学习率将从0.9降至0.1,邻域半径从格子直径的一半降至紧邻的节点。

在第二阶段,学习率将进一步从0.1降低到0.0。然而,在第二阶段将有双倍或更多的迭代,邻域半径值应保持在1,这意味着只有BMU。这意味着,第一阶段将用于学习,第二阶段将用于微调。

3.自组织地图(SOM)的现有实现方式

列表中的最后一个实施方案--MiniSOM是最受欢迎的方案之一。它是一个极简的、基于Numpy的自组织地图的实现,而且非常方便用户使用。它可以使用 pip来安装*。*

pip install minisom

或使用下载的设置来安装。

python setup.py install

如前所述,这个库的使用是非常简单和直接的。一般来说,你所要做的就是创建一个 SOM 类的对象,并定义其大小、输入的大小、学习率和半径(sigma)。之后,你可以使用这个实现提供的两个训练选项之一-- train_batch 或train_random。 第一个选项按照数据集中记录的顺序使用样本,而第二个选项则是对样本进行洗牌。下面是一个例子。

from minisom import MiniSom    
som = MiniSom(6, 6, 4, sigma=0.5, learning_rate=0.5)
som.train_random(data, 100)

在这个例子中, 创建了 6×6的 自组织地图,有4个输入节点(因为这个例子的数据集有4个特征)。学习率和半径(sigma)都被初始化为0.5。然后使用 train_random对 输入数据进行100次迭代的自组织地图训练

4.用Python和Tensorflow实现

对于这个实现,我们使用了TensorFlow的低级API。 在这里 ,你可以找到关于如何快速安装和如何开始使用它的快速指南。一般来说,这个库的低级API被用于实现。那么,让我们来看看代码。

import tensorflow as tf
import numpy as np
 
class SOM(object):
    def __init__(self, x, y, input_dim, learning_rate, radius, num_iter=111):
        
        #Initialize properties
        self._x = x
        self._y = y
        self._learning_rate = float(learning_rate)
        self._radius = float(radius)
        self._num_iter = num_iter
        self._graph = tf.Graph()
 
        #Initialize graph
        with self._graph.as_default():
            
            #Initializing variables and placeholders
            self._weights = tf.Variable(tf.random_normal([x*y, input_dim]))
            self._locations = self._generate_index_matrix(x, y)
            self._input = tf.placeholder("float", [input_dim])
            self._iter_input = tf.placeholder("float")
 
            #Calculating BMU
            input_matix = tf.stack([self._input for i in range(x*y)])
            distances = tf.sqrt(tf.reduce_sum(tf.pow(tf.subtract(self._weights, input_matix), 2), 1))
            bmu = tf.argmin(distances, 0)
            
            #Get BMU location
            mask = tf.pad(tf.reshape(bmu, [1]), np.array([[0, 1]]))
            size = tf.cast(tf.constant(np.array([1, 2])), dtype=tf.int64)
            bmu_location = tf.reshape(tf.slice(self._locations, mask, size), [2])
 
            #Calculate learning rate and radius
            decay_function = tf.subtract(1.0, tf.div(self._iter_input, self._num_iter))
            _current_learning_rate = tf.multiply(self._learning_rate, decay_function)
            _current_radius = tf.multiply(self._radius, decay_function)
 
            #Adapt learning rate to each neuron based on position
            bmu_matrix = tf.stack([bmu_location for i in range(x*y)])
            bmu_distance = tf.reduce_sum(tf.pow(tf.subtract(self._locations, bmu_matrix), 2), 1)
            neighbourhood_func = tf.exp(tf.negative(tf.div(tf.cast(bmu_distance, "float32"), tf.pow(_current_radius, 2))))
            learning_rate_matrix = tf.multiply(_current_learning_rate, neighbourhood_func)
 
            #Update all the weights
            multiplytiplier = tf.stack([tf.tile(tf.slice(
                learning_rate_matrix, np.array([i]), np.array([1])), [input_dim])
                                               for i in range(x*y)])
            delta = tf.multiply(
                multiplytiplier,
                tf.subtract(tf.stack([self._input for i in range(x*y)]), self._weights))                
                         
            new_weights = tf.add(self._weights, delta)
            self._training = tf.assign(self._weights, new_weights)                                       
 
            #Initilize session and run it
            self._sess = tf.Session()
            initialization = tf.global_variables_initializer()
            self._sess.run(initialization)
 
    def train(self, input_vects):
        for iter_no in range(self._num_iter):
            for input_vect in input_vects:
                self._sess.run(self._training,
                               feed_dict={self._input: input_vect,
                                          self._iter_input: iter_no})
 
        self._centroid_matrix = [[] for i in range(self._x)]
        self._weights_list = list(self._sess.run(self._weights))
        self._locations = list(self._sess.run(self._locations))
        for i, loc in enumerate(self._locations):
            self._centroid_matrix[loc[0]].append(self._weights_list[i])
  
    def map_input(self, input_vectors):
        return_value = []
        for vect in input_vectors:
            min_index = min([i for i in range(len(self._weights_list))],
                            key=lambda x: np.linalg.norm(vect – self._weights_list[x]))
            return_value.append(self._locations[min_index])
        return return_value
    
    def _generate_index_matrix(self, x,y):
        return tf.constant(np.array(list(self._iterator(x, y))))
    
    def _iterator(self, x, y):
        for i in range(x):
            for j in range(y):
                yield np.array([i, j])

这是相当多的代码,所以让我们把它分解成小块,并解释每一块的含义。大部分的代码都在类的构造函数中,与MiniSOM的实现类似,它将自组织地图的维度、输入维度、半径和学习率作为输入参数。

4.1 初始化

首先要做的是用传入类构造函数的值初始化所有的字段。

##Initialize properties
self._x = x
self._y = y
self._learning_rate = float(learning_rate)
self._radius = float(radius)
self._num_iter = num_iter
self._graph = tf.Graph()

Programming

注意,我们创建了TensorFlow 图作为*_graph* 字段。在代码的下一部分,我们基本上是向这个图添加操作,并初始化我们的自组织地图。如果你需要更多关于TensorFlows 图和会话如何工作的信息,你可以在这里找到它 。总之,需要做的第一步是初始化变量和占位符。

#Initializing variables and placeholders
self._weights = tf.Variable(tf.random_normal([x*y, input_dim]))
self._locations = self._generate_index_matrix(x, y)
self._input = tf.placeholder("float", [input_dim])
self._iter_input = tf.placeholder("float")

基本上,我们创建了 _weights 作为一个随机初始化的张量。 为了 方便操作,我们创建了索引的神经元矩阵-- _locations。它们是通过使用 _generate_index_matrix生成的 ,看起来像这样。

def _generate_index_matrix(self, x,y):
        return tf.constant(np.array(list(self._iterator(x, y))))
    
def _iterator(self, x, y):
    for i in range(x):
        for j in range(y):
            yield np.array([i, j])

另外,注意 _input (输入向量)和 _iter_input (迭代数,用于半径计算)被定义为占位符。这是由于这些信息是在训练阶段填写的,而不是在构建阶段。一旦所有的变量和占位符被初始化,我们就可以开始进行自组织地图的学习过程算法。

4.2 BMU的计算

首先,计算BMU并确定它的位置。

#Calculating BMU
input_matix = tf.stack([self._input for i in range(x*y)])
distances = tf.sqrt(tf.reduce_sum(tf.pow(tf.subtract(self._weights, input_matix), 2), 1))
bmu = tf.argmin(distances, 0)

#Get BMU location
mask = tf.pad(tf.reshape(bmu, [1]), np.array([[0, 1]]))
size = tf.cast(tf.constant(np.array([1, 2])), dtype=tf.int64)
bmu_location = tf.reshape(tf.slice(self._locations, mask, size), [2])

Programming

第一部分基本上是计算所有神经元和输入矢量之间的欧几里得距离。不要被这段代码的第一行所迷惑。实质上,这个输入样本向量被重复,矩阵被创建,用于权重张量的计算。一旦距离被计算出来,BMU的索引就被返回。这个索引在gist的第二部分被用来获取BMU的位置。我们依靠 切片 函数来实现这一点。一旦完成,我们需要计算当前迭代的学习率和半径的值。这是这样做的。

#Adapt learning rate to each neuron based on position
bmu_matrix = tf.stack([bmu_location for i in range(x*y)])
bmu_distance = tf.reduce_sum(tf.pow(tf.subtract(self._locations, bmu_matrix), 2), 1)
neighbourhood_func = tf.exp(tf.negative(tf.div(tf.cast(bmu_distance, "float32"), tf.pow(_current_radius, 2))))
learning_rate_matrix = tf.multiply(_current_learning_rate, neighbourhood_func)

首先创建BMU位置值的矩阵。然后计算神经元到BMU的位置。之后, 创建 所谓的 邻域函数(neighbourhood_func) 。这个函数基本上定义了 具体神经元的权重将如何改变。

4.3 更新权重

最后,权重被相应地更新,TensorFlow会话被初始化和运行。

#Update all the weights
multiplytiplier = tf.stack([tf.tile(tf.slice(
    learning_rate_matrix, np.array([i]), np.array([1])), [input_dim])
                                   for i in range(x*y)])
delta = tf.multiply(
    multiplytiplier,
    tf.subtract(tf.stack([self._input for i in range(x*y)]), self._weights))                

new_weightages = tf.add(self._weights, delta)
self._training = tf.assign(self._weights, new_weightages)                                       

#Initilize session and run it
self._sess = tf.Session()
initialization = tf.global_variables_initializer()
self._sess.run(initialization)

Programming

除了之前看到的 _generate_index_matrix 函数之外,这个类还有两个重要的函数-- trainmap_input。 第一个函数,正如它的名字所暗示的,用来训练具有适当输入的自组织地图。下面是这个函数的样子。

def train(self, input_vects):
    for iter_no in range(self._num_iter):
        for input_vect in input_vects:
            self._sess.run(self._training,
                           feed_dict={self._input: input_vect,
                                      self._iter_input: iter_no})

    self._centroid_matrix = [[] for i in range(self._x)]
    self._weights_list = list(self._sess.run(self._weights))
    self._locations = list(self._sess.run(self._locations))
    for i, loc in enumerate(self._locations):
        self._centroid_matrix[loc[0]].append(self._weights_list[i])

从本质上讲,我们只是在传递的输入数据上运行了规定数量的迭代。为此,我们使用了 _training ,我们在构建类时创建了这个操作。请注意,这里的迭代数和输入样本的占位符已经被填满。这就是我们如何用正确的数据运行创建的会话。

这个类的第二个函数是map_input。这个函数是将定义的输入样本映射到正确的输出。下面是它的样子。

def map_input(self, input_vectors):
    return_value = []
    for vect in input_vectors:
        min_index = min([i for i in range(len(self._weights_list))],
                        key=lambda x: np.linalg.norm(vect – self._weights_list[x]))
        return_value.append(self._locations[min_index])
    return return_value

4.4 使用方法

最后,我们得到了一个自组织地图,它有一个非常直接的API,可以轻松使用。在下一篇文章中,我们将使用这个类来解决一个真实世界的问题。总结一下,它可以这样使用。

from somtf import SOM

som = SOM(6, 6, 4, 0.5, 0.5, 100)
som.train(data)

结语

在这篇文章中,我们学习了如何使用TensorFlow实现自组织地图算法。我们使用了灵活的低级别的API,以便在学习过程中获得更多的细节,并对它感到满意。总而言之,我们应用了我们在 之前的文章中所学到的所有理论知识。除此之外,我们还看到了如何使用现有的自组织实现,即MiniSOM。下一步将是使用这个实现来解决一些现实世界的问题,我们将在未来进行。

谢谢您的阅读!

Nikola M. Zivkovic

Nikola M. Zivkovic

尼古拉-M-日夫科维奇是 书籍的作者 。 机器学习终极指南面向程序员的深度学习.他热爱知识分享,是一位经验丰富的演讲者。你可以看到他在 聚会、会议上发言 ,也可以在诺维萨德大学担任客座讲师。

分享。