作者:hjimce 一、什么是自编码 简单的自编码是一种三层神经网络模型,包含了数据输入层、隐藏层、输出重构层,同时它是一种无监督学习模型。在有监督的神经网络中,我们的每个训练样本是(X,y),然后y一般是我们人工标注的数据。比如我们用于手写的字体分类,那么y的取值就是0~9之间数值,最后神经网络设计的时候,网络的输出层是一个10个神经元的网络模型(比如网络输出是(0,0,1,0,0,0,0,0,0,0),那么就表示该样本标签为2)。然而自编码是一种无监督学习模型,我们训练数据本来是没有标签的,那么自编码是这样干的,它令每个样本的标签为y=x,也就是每个样本的数据x的标签也是x。自编码就相当于自己生成标签,而且标签是样本数据本身。 三层自编码神经网络模型如下:
自编码网络包含两个过程: (1)从输入层-》隐藏层的原始数据X的编码过程:
(2)从隐藏层-》输出层的解码过程:
那么数据X的重构误差损失函数就是: 其中M是训练样本的个数。 因此等我们训练完网络后,当我们随便输入一个测试样本数据X',那么自编码网络将对X‘先进行隐藏层的编码,然后再从隐藏-》输出完成解码,重构出X’。隐藏层可以看成是原始数据x的另外一种特征表达。 这个时候就会有人问自编码有什么作用,训练一个神经网络模型,让它输入是x,然后输出也近似x,有毛用?其实自编码可以实现类似于PCA等数据降维、数据压缩的特性。从上面自编码的网络结构图,如果输入层神经云的个数n小于隐层神经元个数m,那么我们就相当于把数据从n维降到了m维;然后我们利用这m维的特征向量,进行重构原始的数据。这个跟PCA降维一模一样,只不过PCA是通过求解特征向量,进行降维,是一种线性的降维方式,而自编码是一种非线性降维。 当然自编码隐藏层可以比输入层的神经元个数还多,然后我们可以在神经网络的损失函数构造上,加入正则化约束项进行稀疏约束,这个时候就演化成了稀疏自编码了,因此我们接着就说说稀疏自编码。 二、什么是稀疏自编码(SAE) 所谓的稀疏自编码就对隐藏层神经元加入的稀疏约束,因为我们希望,用尽可能少的神经元来表示原始数据X。我们知道隐藏层神经元的激活值就相当于原始数据X的另外一种表达,我们希望这种表达是稀疏表达(也就是隐藏层的神经元激活值尽可能多的为0)。我们知道隐藏层神经元j的平均激活度为:
其中m表示样本的个数。我们要让隐藏层神经元j尽量为0,如果可以让,然后让是一个趋近于零的小数,那该有多好。于是我们构造了KL散度作为网络的正则约束项,使得尽量的接近于:
最后网络训练损失函数就是:
总之:稀疏自编码就是在原来自编码的损失函数上,加入了稀疏约束项。除了让我们的隐藏层特征稀疏性外,江湖传说它还有加速网络训练的功能(参考自文献《Deep Learning of Part-based Representation of Data Using Sparse Autoencoders with Nonnegativity》) 不过除了稀疏约束项之外,一般我们为了防止过拟合,还加入了权重衰减项,所以最后的损失函数就是:
三、什么是降噪自编码(DAE文献:《Extracting and Composing Robust Features with Denoising Autoencoders》) 降噪自编码与自编码的区别:在原始数据X上加入了噪声X',然后再作为网络的输入数据,来重构输出原始还未加入噪声的数据。 因此降噪自编码的损失函数是构造原始数据X与网络输出X''之间的一个差异性度量。加入噪声,然后训练恢复原始数据,可以让我们的网络具有更强的抗噪能力,使得自编码更加鲁邦。
文献的具体做法是,通过对原始数据输入神经元,进行人为随机损坏加噪声,得到损坏数据X'。 方法1:可以采用高斯噪声: 方法2:采用binary mask 噪声,也就是把输入神经元值随机置0(跟drop out 一样,把某些神经元的激活值置0),然后再作为神经网络的输入。 四、什么是收缩自编码(CAE文献:《Contractive auto-encoders: Explicit invariance during feature extraction》) 收缩自编码也很简单,只不过是在损失函数中,加入了一项惩罚项。 以前加入正则项的自编码损失函数一般是这样的: 现在采用CAE的损失函数是这样的: 如果网络采用的Sigmod函数,那么Jf(x)的计算公式就是: h表示隐层神经元。 五、什么是栈式自编码 开始讲什么是栈式自编码前,让我们先来了解一些深度学习中的无监督预训练。我们知道,在深度学习中,一般网络都有很多层,因为网络层数一多,训练网络采用的梯度下降,在低层网络会出现梯度弥散的现象,导致了深度网络一直不招人待见。直到2006年的3篇论文改变了这种状况,由Hinton提出了一种深层网络的训练方法,改变了人们对深度学习的态度。Hinton所提出的训练思想,整体过程如下; A、网络各层参数预训练。我们在以前的神经网络中,参数的初始化都是用随机初始化方法,然而这种方法,对于深层网络,在低层中,参数很难被训练,于是Hinton提出了参数预训练,这个主要就是采用RBM、以及我们本篇博文要讲的自编码,对网络的每一层进行参数初始化。也就是我们这边要学的稀疏自编码就是为了对网络的每一层进行参数初始化,仅仅是为了获得初始的参数值而已(这就是所谓的无监督参数初始化,或者称之为“无监督
pre-training”)。 B、比如采用自编码,我们可以把网络从第一层开始自编码训练,在每一层学习到的隐藏特征表示后作为下一层的输入,然后下一层再进行自编码训练,对每层网络的进行逐层无监督训练。 C、当我们无监督训练完毕后,我们要用于某些指定的任务,比如分类,这个时候我们可以用有标签的数据对整个网络的参数继续进行梯度下降调整。 这就是深层网络的训练思想,总体归结为:无监督预训练、有监督微调。 OK,我们回到本篇文章的主题,从上面的解释中,我们知道稀疏自编码仅仅只是为了获得参数的初始值而已。栈式自编码神经网络是一个由多层稀疏自编码器组成的神经网络,其前一层自编码器的输出作为其后一层自编码器的输入。栈式自编码就是利用上面所说的:无监督pre-training、有监督微调进行训练训练的深度网络模型。接着就让我们来学一学具体的栈式自编码网络训练。下面是来自斯坦福的深度学习教程的一个例子:http://ufldl./wiki/index.php/%E6%A0%88%E5%BC%8F%E8%87%AA%E7%BC%96%E7%A0%81%E7%AE%97%E6%B3%95 问题描述:假设我们要训练一个4层的神经网络模型用于分类任务,网络结构如下:
接着我们就讲讲,栈式自编码的训练上面这个网络的方法: 1、无监督pre-training阶段; A、首先采用稀疏自编码网络,先训练从输入层到H1层的参数:
训练完毕后,我们去除解码层,只留下从输入层到隐藏层的编码阶段。 B、接着我们训练从H1到H2的参数 我们把无标签数据的H1层神经元的激活值,作为H2层的输入层,然后在进行自编码训练:
最后训练完毕后,再去除H2层的解码层。如此重复,可以训练更高层的网络,这就是逐层贪婪训练的思想。
C、训练完H2后,我们就可以接分类层softmax,用于多分类任务
至此参数的初始化阶段就算是结束了,这个过程就是所谓的无监督预训练。 2、有监督微调阶段 后面接着就是,利用上面的参数作为网络的初始值,继续进行神经网络的训练了,没什么可讲的。 二、源码实现 下面还是把theano的相关代码实现,贴一下: <span style="font-family:Arial;">import os import sys import timeit import numpy import theano import theano.tensor as T from logistic_sgd import load_data from utils import tile_raster_images try: import PIL.Image as Image except ImportError: import Image '''''代码编写参考文献:Contractive Auto-Encoder class 这篇文献主要是提出一个约束正则项 整个总计算过程,参考文献的公式:7''' class cA(object): def __init__(self, numpy_rng, input=None, n_visible=784, n_hidden=100, n_batchsize=1, W=None, bhid=None, bvis=None): """ input:输入训练数据数据, input与n_batchsize是对应的,input中有n_batchsize个样本 input每一行代表一个样本,共有n_batchsize个样本 n_visible:可见层神经元的个数 n_hidden: 隐藏层神经元个数 n_batchsize:批量训练,每批数据的个数 W:输入当隐藏层的全连接权值矩阵,因为使用了tied weight 所以从隐藏到输入的权值 矩阵为:W.transpose() bhid:从输入到隐藏层的偏置单元 bvis:从隐藏层的偏置单元 """ self.n_visible = n_visible self.n_hidden = n_hidden self.n_batchsize = n_batchsize # 如果没有输入W,则在类里面进行初始化 if not W: '''''W 采用[-a,a]的均匀采样方法进行初始化,因为后面采用s函数,所以 a=4*sqrt(6./(n_visible+n_hidden)) ,矩阵类型theano.config.floatX 这样才能保证在GPU上运行''' initial_W = numpy.asarray( numpy_rng.uniform( low=-4 * numpy.sqrt(6. / (n_hidden + n_visible)), high=4 * numpy.sqrt(6. / (n_hidden + n_visible)), size=(n_visible, n_hidden) ), dtype=theano.config.floatX ) W = theano.shared(value=initial_W, name='W', borrow=True) if not bvis: bvis = theano.shared(value=numpy.zeros(n_visible, dtype=theano.config.floatX), borrow=True) if not bhid: bhid = theano.shared(value=numpy.zeros(n_hidden, dtype=theano.config.floatX), name='b', borrow=True) self.W = W # 输入到隐藏的偏置单元 self.b = bhid # 隐藏到输出的偏置单元 self.b_prime = bvis # 使用了tied weights, 所以 W_prime 是 W 的转置 self.W_prime = self.W.T # 如果没有给定input,那么创建一个 if input is None: self.x = T.dmatrix(name='input') else: self.x = input self.params = [self.W, self.b, self.b_prime] #1、输入层到隐藏层 def get_hidden_values(self, input): return T.nnet.sigmoid(T.dot(input, self.W) + self.b) #2、隐藏层到输出层。重建结果 x' = s(W' h + b') ,因为文献使用了tied weigth,所以 #W'等于W的转置,这个可以百度搜索:自编码,tied weight等关键词 def get_reconstructed_input(self, hidden): return T.nnet.sigmoid(T.dot(hidden, self.W_prime) + self.b_prime) #计算 J_i = h_i (1 - h_i) * W_i def get_jacobian(self, hidden, W): return T.reshape(hidden * (1 - hidden),(self.n_batchsize, 1, self.n_hidden))*T.reshape(W, (1, self.n_visible, self.n_hidden)) #权值更新函数 def get_cost_updates(self, contraction_level, learning_rate): y = self.get_hidden_values(self.x)#输入-》隐藏 z = self.get_reconstructed_input(y)#隐藏-》输出 J = self.get_jacobian(y, self.W)#y*(1-y)*W #文献Contractive Auto-Encoders:公式4损失函数计算公式 self.L_rec = - T.sum(self.x * T.log(z) +(1 - self.x) * T.log(1 - z),axis=1) # 因为J是由n_batchsize*n_hidden计算而来,有n_batchsize个样本,所以要求取样本平均值 self.L_jacob = T.sum(J ** 2) / self.n_batchsize #整个惩罚函数 cost = T.mean(self.L_rec) + contraction_level * T.mean(self.L_jacob) #对参数求导 gparams = T.grad(cost, self.params) #梯度下降法更新参数 updates = [] for param, gparam in zip(self.params, gparams): updates.append((param, param - learning_rate * gparam)) return (cost, updates) #测试验证上面的类是否正确 def test_cA(learning_rate=0.01, training_epochs=20, dataset='mnist.pkl.gz', batch_size=10, output_folder='cA_plots', contraction_level=.1): """ learning_rate:梯度下降法的学习率 training_epochs: 最大迭代次数 contraction_level:为正则项的权重 """ #datasets[0]为训练集,datasets[1]为验证集,datasets[2]为测试集 datasets = load_data(dataset) train_set_x, train_set_y = datasets[0] # 批量下降法,训练的批数 n_train_batches = train_set_x.get_value(borrow=True).shape[0] / batch_size # allocate symbolic variables for the data index = T.lscalar() # 每一批训练数据的索引 x = T.matrix('x') # 每一批训练数据 if not os.path.isdir(output_folder): os.makedirs(output_folder) os.chdir(output_folder) rng = numpy.random.RandomState(123) ca = cA(numpy_rng=rng, input=x, n_visible=28 * 28, n_hidden=500, n_batchsize=batch_size) cost, updates = ca.get_cost_updates(contraction_level=contraction_level, learning_rate=learning_rate) #每一批,训练更新函数,输入参数index train_ca = theano.function( [index], [T.mean(ca.L_rec), ca.L_jacob], updates=updates, givens={ x: train_set_x[index * batch_size: (index + 1) * batch_size] } ) start_time = timeit.default_timer() ############ # TRAINING # ############ # go through training epochs for epoch in xrange(training_epochs): # go through trainng set c = [] for batch_index in xrange(n_train_batches): c.append(train_ca(batch_index)) c_array = numpy.vstack(c) print 'Training epoch %d, reconstruction cost ' % epoch, numpy.mean( c_array[0]), ' jacobian norm ', numpy.mean(numpy.sqrt(c_array[1])) end_time = timeit.default_timer() training_time = (end_time - start_time) print >> sys.stderr, ('The code for file ' + os.path.split(__file__)[1] + ' ran for %.2fm' % ((training_time) / 60.)) image = Image.fromarray(tile_raster_images( X=ca.W.get_value(borrow=True).T, img_shape=(28, 28), tile_shape=(10, 10), tile_spacing=(1, 1))) image.save('cae_filters.png') os.chdir('../') if __name__ == '__main__': test_cA()</span>
|