TensorFlow实战之实现自编码器过程

        关于本文说明,已同步本人另一个博客地址位于http://blog.csdn.net/qq_37608890,详见http://blog.csdn.net/qq_37608890/article/details/79352212。
python

      本文根据最近学习TensorFlow书籍网络文章的状况,特将一些学习心得作了总结,详情以下.若有不当之处,请各位大拿多多指点,在此谢过。算法

1、相关概念

一、稀疏性(Sparsity)及稀疏编码(Sparse Coding)
网络

         Sparsity 是当今机器学习领域中的一个重要话题。
dom

          Sparsity 的最重要的“客户”大概要属 high dimensional data 了吧。如今的机器学习问题中,具备很是高维度的数据随处可见。例如,在文档或图片分类中经常使用的 bag of words 模型里,若是词典的大小是一百万,那么每一个文档将由一百万维的向量来表示。高维度带来的的一个问题就是计算量:在一百万维的空间中,即便计算向量的内积这样的基本操做也会是很是费力的。不过,若是向量是稀疏的的话(事实上在 bag of words 模型中文档向量一般都是很是稀疏的),例如两个向量分别只有L1 和 L2 个非零元素,那么计算内积能够只使用min(L1,L2) 次乘法完成。所以稀疏性对于解决高维度数据的计算量问题是很是有效的。机器学习

          稀疏编码(Sparse Coding)算法是一种无监督学习方法,它用来寻找一组“超完备”基向量来更高效地表示样本数据。稀疏编码算法的目的就是找到一组基向量 ,使得咱们能将输入向量 表示为这些基向量的线性组合:

函数

       虽然形如主成分分析技术(PCA)能使咱们方便地找到一组“完备”基向量,可是这里咱们想要作的是找到一组“超完备”基向量来表示输入向量(也就是说,k > n)。超完备基的好处是它们能更有效地找出隐含在输入数据内部的结构与模式。然而,对于超完备基来讲,系数 ai 再也不由输入向量 惟一肯定。所以,在稀疏编码算法中,咱们另加了一个评判标准“稀疏性”来解决因超完备而致使的退化(degeneracy)问题。要求系数 ai 是稀疏的意思就是说:对于一组输入向量,咱们只想有尽量少的几个系数远大于零。选择使用具备稀疏性的份量来表示咱们的输入数据是有缘由的,由于绝大多数的感官数据,好比天然图像,能够被表示成少许基本元素的叠加,在图像中这些基本元素能够是面或者线。oop

         在早期,学者们研究稀疏编码(Sparse Coding)时,搜集了大量黑白风景照片,且从中提取了16像素*16像素的图片碎片。结果发现:几乎全部的图像碎片均可以用64种正交的边组合获得,且组合出一张图像碎片须要的边的数量是不多的,即稀疏的。学者们同时也发现,声音其实也存在这种状况,他们从大量的未标注的音频中发现了20种基本结构,绝大多数声音能够由这些基本结构线性组合获得。显然,这就是特征的稀疏表达,使用少许的基本特征组合拼装获得更高层抽象的特征。通常状况想,咱们也须要多层的神经网络,对每一层神经网络而言,前一层的输出都是未加工的像素,而这一层则是对像素进行加工组织成更高阶的特征。性能

二、自编码(AutoEncoder)
学习

         顾名思义,便可以使用自身的高阶特征编码本身。自编码器其实也是一种神经网络,它的输入和输出是一致的,它借助稀疏编码的思想,目标是使用稀疏的一些高阶特征从新组合来重构本身,即 :对全部的自编码器来说,目标都是样本重构。测试

         在机器学习中,自编码器的使用十分普遍。自编码器首先经过编码层,将高维空间的向量,压缩成低维的向量(潜在变量),而后经过解码层将低维向量解压重构出原始样本

三、隐含层

        指输入层和输出层之外,中间的那些层。输入层和输出层是可见的,且层的结构是相对固定的,而隐含层结构不固定,至关于不可见。只要隐含的节点足够多,便是只有一个隐含层的神经网络也能够拟合任意函数。隐含层层数越多,越容易拟合复杂的函数。拟合复杂函数须要的隐含节点数目随着层数的增多而呈指数降低。即层数越深,概念越抽象,这就是深度学习。

四、过拟合

      指模型预测准确率在训练集上升高,但在测试集上反而降低。这是模型的泛化性很差,只记住了当前数据的特征。

五、Dropout

       Dropout:防止过拟合的一种方法。将神经网络某一层的输出节点数据随机丢弃一部分。能够理解为是对特征的采样。

六、优化函数

        优化调试网络中的参数。通常状况下,在调参时,学习率的设置会致使最后结果差别很大。神经网络一般不是凸优化,充满局部最优,可是神经网络可能有不少个局部最优都能达到良好效果,反而全局最优容易出现过拟合。

       对于SGD,一般一开始学习率大一些,能够快速收敛,可是训练的后期,但愿学习率能够小一些,能够比较稳定地落到一个局部最优解。

        除SGD以外,还有自适应调节学习率的Adagrad、Adam、Adadelta等优化函数

七、激活函数

         Sigmoid函数的输出在(0,1),最符合几率输出的定义,但局限性较大。
ReLU,当x<=0时,y=0;当x>0时,y=x。这很是相似于人类的阈值响应机制。ReLU的3个显著特色:单侧抑制;相对宽阔的兴奋边界;稀疏激活性。它是目前最符合实际神经元的模型。

 2、自解码器算法原理      

一、BacsicAuto-Encoder

         Auto-Encoder(AE)是20世纪80年代晚期提出的,简单讲,AE能够被视为一个三层神经网络结构:一个输入层、一个隐藏层和一个输出层,从数据规模上讲,输入层与输出层具备相同的规模。

            

                             图1  Auto-Encoder网络结构示意图

     其中,n表示输入层(同时也是做为输出层)的规模;m表示隐藏层的规模;分别表示输出层、隐藏层和输出层上的向量,也是各层上对应的数据个数,这里隐藏层h的数据低于输入层和输出层的数据,即x>h<y且x=y。根据输入层x到隐藏层h的映射矩阵求出h,再根据隐藏层h到输出层的映射矩阵求出y。分别表示隐藏层和输出层上的偏置向量。表示输入层与隐藏层之间的权值矩阵,即x到h的映射矩阵,是n乘m阶矩阵;表示隐藏层与输出层之间的权值矩阵,即h到y的映射矩阵,是m乘n阶矩阵,也是W的逆矩阵。   

      针对AE而言,重要的是解决好矩阵W问题,一个好的矩阵W能够保证x彻底等于y,可实际上很难,尤为当输入层x的数据量大成千上百个时,x和y的差异就能够说明矩阵W的优良程度。总之,咱们的工做就是要求出矩阵W,并使得x和y之间的差别尽量小。下面给出下图,进一步作出解释

     

 

                                          

                         图2 Auto-Encoder抽象结构 

       如图2所示,AE由编码器(Enconder)和解码器(Decoder)两部分构成。显然,从输入层到隐藏层属于编码过程,从隐藏层到输出层属于解码过程。咱们设定f和g分别表示编码和解码函数,结合上面的内容,咱们能够给出f和g的方程,以下(1.1)和(1.2)式所示

 

                 

       其中 为编码器的激活函数,一般状况下取Sigmoid函数,即 为解码器的激活函数,通常取Sigmoid函数或恒等函数,即。权值矩阵通常取一些文献资料中说起称,这种状况下的AE具备tiedweights。本文指讨论两者相等的状况。

       因此,截止目前,咱们可知,AE的参数为

       

         假设目前咱们有一个训练集S=那么咱们要考虑的问题是如何利用S训练,首先是创建一个训练目标。

       结合前面的梳理,咱们可知,AE能够被当作一个普通的三层神经网络,y也能够当作由x作的一个预测(prediction),且保证x和y尽量接近,这种接近程度能够经过重构偏差(reconstruction error)函数L(x,y)来进行表达。

    根据解码器激活函数的不一样,这里L(x,y)通常有两种取值方法:
             第一种,当为恒等函数时,取平方偏差(sequared error)函数
     第二种,当 为sigmoid函数(此种状况输入)时,取交叉熵(cross-entropy)函数
    取得重构函数后,咱们就能够对数据集S进行针对性的训练,去获得一个总体的损失函数(loss function)(1.5)
                                

        将上面这个函数(1.5)进行极小化处理,咱们就能够获得所需的参数

        注意,通常文献中损失函数多数使用的是平均重构偏差(average reconstruction error),即下面函数(1.6)

                        

         对于一个给定的训练集S而言,系数1/N并不对于上面这个损失函数最小化产生多大的影响,因此,为了简便,这里忽略这个系数。

    最开始,AE只是做为一种降维技巧来使用的,视隐藏层h为x的一种降维表示,也要求m<n(h<x)。可是,目前来看,AE在m>=n的状况(有些文献中称为 over-complete setting)下应用更多,以便获取更高维数更有意义的表达方式。固然,在次状况下,也会有一个问题不可忽略:若直接对损失函数(1.5)进行极小化的而没有加入任何其余限制条件的话,AE在这里学到的极可能是一个恒等函数(where the auto-encoder could perfectly reconstruction the input without needing to extract any useful feature),这种结果是不符合预期的。须要解决,例如  , 在损失函数中加入稀疏性限制(Sparse Auto-Encoder),或在网络中引入随机性(如RBN,Denoising Auto-Encoder)等。
    下面先介绍 Regularized Auto-Encoer 和Sparse Auto-Encoder,其他的AE变种算法,后续有机会继续梳理。

 二、 Regularized Auto-Encoder

         咱们在损失函数(1.5)中加入正则项,即可以获得所谓的Regularized Auto-Encoder。 常见的正则化有L1 正则和 L2正则。以L2正则为例进行介绍,损失  函数则变为以下(2.7):

                                
      其中      为权值矩阵W的元素。
公式(2.7)中的即为L2正则项,也叫作权重衰减(weight-decay)项(下标中的 就是weight-decay的意思),lambda为权重衰减参数,用来控制公式中两项的相对重要性。

三、Sparse Auto-Encoder
         由公式(2.7)能够看出,权重衰减项其实是对权重提出一些要求,也就是要求它们不能过大,不然会被惩罚。这里所讲的Sparse Auto-Encoder 是对隐藏层上神经元的激活度提出要求,使其知足必定的稀疏性。
        关于隐藏层上神经元激活度如何刻画的问题,下面展开来看:
            假设表示输入为x时,隐藏层上第j个神经元的激活度(  是(1.1)中向量h的第j个份量),则(3.8)

         表示隐藏层上第j个神经元在训练集 上的平均激活度。为了保证隐藏层上的神经元大部分时间被抑制为零或者接近于零(即稀疏性限制),这里要求(3.9)
其中,为稀疏性参数,通常状况下取一个很小的数(如   )。对于那些与有显著不一样的,会进行惩罚。这个限制方法的实现有多种,这里以一种基于KL divergence的方法,即引入(3.10)

其中,(3.11)

函数 具备以下性质:
      随着和   之间的差别增大而单调递增,尤为当 时,存在达到最小值。因此,若是将(3.10)加入到损失函数里,则最小化损失函数便可达到使得尽量靠近 的效果。这样来看,Sparse Auto-Encoder的损失函数就能够表达为(3.12)
                          
         这里beta为控制稀疏性惩罚项的权重系数。

        咱们也能够将L2正则和稀疏性限制结合起来使用,这时的损失函数也就变为(3.13)

                       

3、实现去噪自编码器过程

一、代码实现过程以下

# 这里以最具表明性的去噪自编码器为例。

#导入MNIST数据集

import numpy as np

import sklearn.preprocessing as prep

import tensorflow as tf

from tensorflow.examples.tutorials.mnist import input_data


#这里使用一种参数初始化方法xavier initialization,须要对此作好定义工做。

#Xaiver初始化器的做用就是让权重大小正好合适。

#这里实现的是标准均匀分布的Xaiver初始化器。

def xavier_init(fan_in, fan_out, constant = 1):

"""

目的是合理初始化权重。

参数:

fan_in --行数;

fan_out -- 列数;

constant --常数权重,条件初始化范围的倍数。

return 初始化后的权重tensor.

"""

low = -constant * np.sqrt(6.0 / (fan_in + fan_out))

high = constant * np.sqrt(6.0 / (fan_in + fan_out))

return tf.random_uniform((fan_in, fan_out),

minval = low, maxval = high,

dtype = tf.float32)


#定义一个去噪的自编码类

class AdditiveGaussianNoiseAutoencoder(object):

"""

__init__() :构建函数;

n_input : 输入变量数;

n_hidden : 隐含层节点数;

transfer_function: 隐含层激活函数,默认是softplus;

optimizer : 优化器,默认是Adam;

scale : 高斯噪声系数,默认是0.1;

"""

def __init__(self, n_input, n_hidden, transfer_function =tf.nn.softplus, optimizer = tf.train.AdamOptimizer(),

scale = 0.1):

self.n_input = n_input

self.n_hidden = n_hidden

self.transfer = transfer_function

self.scale = tf.placeholder(tf.float32)

self.training_scale = scale

network_weights = self._initialize_weights()

self.weights = network_weights


# 定义网络结构,为输入x建立一个维度为n_input的placeholder,而后

#创建一个能提取特征的隐含层。

self.x = tf.placeholder(tf.float32, [None, self.n_input])

self.hidden = self.transfer(tf.add(tf.matmul(self.x +scale * tf.random_normal((n_input,)),

self.weights['w1']),

self.weights['b1']))

self.reconstruction = tf.add(tf.matmul(self.hidden,self.weights['w2']), self.weights['b2'])


#首先,定义自编码器的损失函数,在此直接使用平方偏差(SquaredError)做为cost。

#而后,定义训练操做做为优化器self.optimizer对损失self.cost进行优化。

#最后,建立Session,并初始化自编码器所有模型参数。

self.cost = 0.5 *tf.reduce_sum(tf.pow(tf.subtract(self.reconstruction, self.x), 2.0))

self.optimizer = optimizer.minimize(self.cost)


init = tf.global_variables_initializer()

self.sess = tf.Session()

self.sess.run(init)


def _initialize_weights(self):

all_weights = dict()

all_weights['w1'] = tf.Variable(xavier_init(self.n_input,self.n_hidden))

all_weights['b1'] = tf.Variable(tf.zeros([self.n_hidden],dtype = tf.float32))

all_weights['w2'] = tf.Variable(tf.zeros([self.n_hidden,self.n_input], dtype = tf.float32))

all_weights['b2'] = tf.Variable(tf.zeros([self.n_input],dtype = tf.float32))

return all_weights


def partial_fit(self, X):

cost, opt = self.sess.run((self.cost, self.optimizer),feed_dict = {self.x: X,

self.scale: self.training_scale})

return cost


def calc_total_cost(self, X):

return self.sess.run(self.cost, feed_dict = {self.x: X,

self.scale:self.training_scale})

#定义一个transform函数,以便返回自编码器隐含层的输出结果,目的是提供一个接口来获取抽象后的特征。

def transform(self, X):

return self.sess.run(self.hidden, feed_dict = {self.x: X,

self.scale:self.training_scale})


def generate(self, hidden = None):

if hidden is None:

hidden = np.random.normal(size = self.weights["b1"])

return self.sess.run(self.reconstruction, feed_dict ={self.hidden: hidden})

def reconstruct(self, X):

return self.sess.run(self.reconstruction, feed_dict ={self.x: X,

self.scale: self.training_scale})


def getWeights(self): #获取隐含层的权重w1.

return self.sess.run(self.weights['w1'])


def getBiases(self): #获取隐含层的偏执系数b1.

return self.sess.run(self.weights['b1'])

#利用TensorFlow提供的读取示例数据的函数载入MNIST数据集。

mnist = input_data.read_data_sets('MNIST_data', one_hot = True)

#定义一个对训练、测试数据进行标准化处理的函数。

def standard_scale(X_train, X_test):

preprocessor = prep.StandardScaler().fit(X_train)

X_train = preprocessor.transform(X_train)

X_test = preprocessor.transform(X_test)

return X_train, X_test


def get_random_block_from_data(data, batch_size):

start_index = np.random.randint(0, len(data) - batch_size)

return data[start_index:(start_index + batch_size)]


X_train, X_test = standard_scale(mnist.train.images,mnist.test.images)


n_samples = int(mnist.train.num_examples)

training_epochs = 20

batch_size = 128

display_step = 1

autoencoder = AdditiveGaussianNoiseAutoencoder(n_input = 784,

n_hidden = 200,

transfer_function =tf.nn.softplus,

optimizer =tf.train.AdamOptimizer(learning_rate = 0.001),

scale = 0.01)

for epoch in range(training_epochs):

avg_cost = 0.

total_batch = int(n_samples / batch_size)

# Loop over all batches

for i in range(total_batch):

batch_xs = get_random_block_from_data(X_train, batch_size)


# Fit training using batch data

cost = autoencoder.partial_fit(batch_xs)

# Compute average loss

avg_cost += cost / n_samples * batch_size

# Display logs per epoch step

if epoch % display_step == 0:

print("Epoch:", '%04d' % (epoch + 1), "cost=","{:.9f}".format(avg_cost))

#最后对训练完的模型进行性能测试。

print("Total cost: " +str(autoencoder.calc_total_cost(X_test)))

 

二、执行结果以下

Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
Epoch: 0001 cost= 18871.253996591
Epoch: 0002 cost= 12308.673515909
Epoch: 0003 cost= 10227.495348864
Epoch: 0004 cost= 11243.596613636
Epoch: 0005 cost= 10782.029647727
Epoch: 0006 cost= 9165.328120455
Epoch: 0007 cost= 8487.490198295
Epoch: 0008 cost= 9195.667004545
Epoch: 0009 cost= 9026.087407955
Epoch: 0010 cost= 8301.502948295
Epoch: 0011 cost= 9921.268600568
Epoch: 0012 cost= 8789.966229545
Epoch: 0013 cost= 9115.636243182
Epoch: 0014 cost= 8993.681156818
Epoch: 0015 cost= 7670.030270455
Epoch: 0016 cost= 8108.834190341
Epoch: 0017 cost= 7897.135417045
Epoch: 0018 cost= 8332.914957955
Epoch: 0019 cost= 8091.132888068
Epoch: 0020 cost= 7822.976949432
Total cost: 725054.5

 

 

4、小结

    
           至此,咱们能够发现,从实现的角度而言,自编码器其实和一个单隐含层的神经网络差很少,只是自编码器在数据输入时作了标准化处理,且加上了一个高斯噪声,同时咱们的输出结果不是数字分类结果,而是复原的数据,所以不须要用标注过的数据进行监督训练。自编码器做为一种无监督学习方法,它与其余无监督学习的区别主要在于:它不是对数据进行聚类,而是把数据中最有用、最频繁的高阶特征提取出来,而后根据这些高阶特征进行重构数据。在深度学习发展早期很是流行的DBN,也是依靠这种思想,先对数据进行无监督学习,提取到一些有用的特征,将神经网络权重初始化到一个较好的分布,而后再使用有标注的数据进行监督训练,即对权重进行fine-tune。

         现实生活中,大部分数据是没有标准信息的,但人脑比较擅长处理这些数据,会提取出其中的高阶抽象特征,并使用在别的地方。自编码器做为深度学习在无监督领域的应用的确很是成功,同时无监督学习也将成为深度学习一个重要发展方向。

 

参考资料   主要参考资料《TensorFlow实战》(黄文坚  唐源 著)(电子工业出版社)

相关文章
相关标签/搜索