上一节我们讲到自编码可以用于进行数据降维、数据压缩、对文字或图像提取主题并用于信息检索等。 根据所解决的问题不同 ,自编码可以有许多种不同形式的变形,例如: 去噪自编码器(DAE)、变分自编码器 (VAE)、收缩自编码器(CAE)和稀疏自编码器等 。下面我们先从去噪自编码为讲起。
一 去噪自编码
要想取得好的特征只靠重构输入数据是不够的,在实际应用中,还需要让这些特征具有靠干扰的能力,即当输入数据发生一定程度的干扰时,生成的特征仍然保持不变。这时需要添加噪声来为模型增加更大的困难。在这种情况下训练出来的模型才会有更好的鲁棒性,于是就有了本节所要介绍的去噪自编码。
去噪自编码(Denoising Auto Encoder),是在自编码的基础上,训练数据加入噪声,输出的标签仍是原来的样本(没有加过噪声的),这样自编码必须学习去除噪声而获得真正的没有被噪声污染过的输入特征。因此,这就迫使编码器去学习输入信号的更具有鲁棒性的特征表达,即具有更强的泛化能力。
在实际训练中,人为加入的噪声有两种途径:
- 在选择训练数据集时,额外选择一些样本集以外的数据。
- 改变已有的样本数据集中的数据(使样本个体不完整,或通过噪声与样本进行加减乘除之类的运算,使样本数据发生变化)
二 使用去燥自编码提取MNIST特征
# -*- coding: utf-8 -*- """ Created on Sun May 27 17:49:18 2018 @author: zy """ import tensorflow as tf import numpy as np import matplotlib.pyplot as plt from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets('MNIST-data',one_hot=True) print(type(mnist)) #<class 'tensorflow.contrib.learn.python.learn.datasets.base.Datasets'> print('Training data shape:',mnist.train.images.shape) #Training data shape: (55000, 784) print('Test data shape:',mnist.test.images.shape) #Test data shape: (10000, 784) print('Validation data shape:',mnist.validation.images.shape) #Validation data shape: (5000, 784) print('Training label shape:',mnist.train.labels.shape) #Training label shape: (55000, 10) train_X = mnist.train.images train_Y = mnist.train.labels test_X = mnist.test.images test_Y = mnist.test.labels def denoising_auto_encoder(): ''' 去燥自编码器 对MNIST原始输入图片加入噪声,在自编码网络中进行训练,以得到抗干扰更强的特征提取模型 ''' n_input = 784 #输入节点数 n_hidden = 256 #隐藏层节点个数 learning_rate = 0.01 #学习率 training_epochs = 20 #迭代轮数 batch_size = 256 #小批量数量大小 display_epoch = 2 show_num = 10 #显示的图片个数 #定义占位符 input_x = tf.placeholder(dtype=tf.float32,shape=[None,n_input]) input_y = tf.placeholder(dtype=tf.float32,shape=[None,n_input]) keep_prob = tf.placeholder(dtype=tf.float32) #定义参数 weights = { 'h1':tf.Variable(tf.random_normal(shape=[n_input,n_hidden],stddev=0.1)), 'h2':tf.Variable(tf.random_normal(shape=[n_hidden,n_hidden],stddev=0.1)), 'out':tf.Variable(tf.random_normal(shape=[n_hidden,n_input],stddev=0.1)) } biases = { 'b1':tf.Variable(tf.zeros(shape=[n_hidden])), 'b2':tf.Variable(tf.zeros(shape=[n_hidden])), 'out':tf.Variable(tf.zeros(shape=[n_input])) } #网络模型 去燥自编码 h1 = tf.nn.sigmoid(tf.add(tf.matmul(input_x,weights['h1']),biases['b1'])) h1 = tf.nn.dropout(h1,keep_prob) h2 = tf.nn.sigmoid(tf.add(tf.matmul(h1,weights['h2']),biases['b2'])) h2 = tf.nn.dropout(h2,keep_prob) pred = tf.nn.sigmoid(tf.add(tf.matmul(h2,weights['out']),biases['out'])) #计算代价 cost = tf.reduce_mean((pred-input_y)**2) #定义优化器 #train = tf.train.RMSPropOptimizer(learning_rate).minimize(cost) train = tf.train.AdamOptimizer(learning_rate).minimize(cost) num_batch = int(np.ceil(mnist.train.num_examples / batch_size)) #开始训练 with tf.Session() as sess: sess.run(tf.global_variables_initializer()) print('开始训练') for epoch in range(training_epochs): total_cost = 0.0 for i in range(num_batch): batch_x,batch_y = mnist.train.next_batch(batch_size) #添加噪声 每次取出来一批次的数据,将输入数据的每一个像素都加上0.3倍的高斯噪声 batch_x_noise = batch_x + 0.3*np.random.randn(batch_size,784) #标准正态分布 _,loss = sess.run([train,cost],feed_dict={input_x:batch_x_noise,input_y:batch_x,keep_prob:1.0}) total_cost += loss #打印信息 if epoch % display_epoch == 0: print('Epoch {0}/{1} average cost {2}'.format(epoch,training_epochs,total_cost/num_batch)) print('训练完成') #数据可视化 test_noisy= mnist.test.images[:show_num] + 0.3*np.random.randn(show_num,784) reconstruction = sess.run(pred,feed_dict = {input_x:test_noisy,keep_prob:1.0}) plt.figure(figsize=(1.0*show_num,1*2)) for i in range(show_num): #原始图像 plt.subplot(3,show_num,i+1) plt.imshow(np.reshape(mnist.test.images[i],(28,28)),cmap='gray') plt.axis('off') #加入噪声后的图像 plt.subplot(3,show_num,i+show_num*1+1) plt.imshow(np.reshape(test_noisy[i],(28,28)),cmap='gray') plt.axis('off') #去燥自编码器输出图像 plt.subplot(3,show_num,i+show_num*2+1) plt.imshow(np.reshape(reconstruction[i],(28,28)),cmap='gray') plt.axis('off') plt.show() #测试鲁棒性 为了测试模型的鲁棒性,我们换一种噪声方式,然后再生成一个样本测试效果 plt.figure(figsize=(2.4*3,1*2)) #生成一个0~mnist.test.images.shape[0]的随机整数 randidx = np.random.randint(test_X.shape[0],size=1) orgvec = test_X[randidx] #1x784 #获取标签 label = np.argmax(test_Y[randidx],1) print('Label is %d'%(label)) #噪声类型 对原始图像加入噪声 print('Salt and Paper Noise') noisyvec = test_X[randidx] # 1 x 784 #噪声点所占的比重 rate = 0.15 #生成噪声点索引 noiseidx = np.random.randint(test_X.shape[1],size=int(test_X.shape[1]*rate)).astype(np.int32) #对这些点像素进行反向 for i in noiseidx: noisyvec[0,i] = 1.0 - noisyvec[0,i] #噪声图像自编码器输出 outvec = sess.run(pred,feed_dict={input_x:noisyvec,keep_prob:1.0}) outimg = np.reshape(outvec,(28,28)) #可视化 plt.subplot(1,3,1) plt.imshow(np.reshape(orgvec,(28,28)),cmap='gray') plt.title('Original Image') plt.axis('off') plt.subplot(1,3,2) plt.imshow(np.reshape(noisyvec,(28,28)),cmap='gray') plt.title('Input Image') plt.axis('off') plt.subplot(1,3,3) plt.imshow(outimg,cmap='gray') plt.title('Reconstructed Image') plt.axis('off') if __name__ == '__main__': denoising_auto_encoder()
上面我们在训练的时候给keep_prob传入的值为1.0,运行结果如下:
第一张图片总共有3行10列,第一行我原始图像,第二行为加入随机高斯噪声之后的图像,第三行为经过去噪编码器的输出。
第二幅图像是给定一个输入,经过加入其它噪声之后,处理输出的图像,是用来测试改网络的鲁棒性。
我们通过训练时的keep_prob改成0.5,运行结果如下:
对比一下,我们会发现效果都还不错。
三 栈式自编码
1、简单介绍
栈式自编码神经网络(Stacked Autoencoder,SA)是自编码网络的一种使用方法,是一个由多层训练好的自编码器组成的神经网络。由于网络中的每一层都是单独训练而来,相当于都初始化了一个合理的数值。所以,这样的网络会更容易训练,并且有更快的收敛性及更高的准确度。
栈式自编码常常被用于预训练(初始化)深度神经网络之前的权重预训练步骤。例如,在一个分类问题上,可以按照从前向后的顺序执行每一层通过自编码器来训练,最终将网络中最深层的输出作为softmax分类器的输入特征,通过softmax层将其分开。
为了使这个过程很容易理解,下面以训练一个包含两个隐藏层的栈式自编码网络为例,一步一步的详细介绍:
2、栈式自编码器在深度学习中的意义
看到这里或许你会奇怪?为什么要这么麻烦,之间使用多层神经网络来训练不是也可以吗?在这里是为大家介绍一种训练方法,而这个训练更像是手动训练,之所以我们愿意这么麻烦,主要是因为有如下几个有点:
- 每一层都是单独训练,保证降维特征的可控性。
- 对于高维度的分类问题,一下拿出一套完整可用的模型相对来讲并不是容易的事,因为节点太多,参数太多,一味地增加深度只会使结果越来越不可控,成为彻底的黑盒,而使用栈式自编码器逐层降维,可以将复杂问题简单化,更容易完成任务。
- 任意深层,理论上是越深层的神经网络对现实的拟合度越高,但是传统的多层神经网络,由于使用的是误差反向传播方式,导致层越深,传播的误差越小,栈式自编码巧妙的绕过这个问题,直接使用降维后的特征值进行二次训练,可以任意层数的加深。
- 栈式自编码神经网络具有强大的表达能力和深度神经网络的所有优点,但是它通常能够获取到输入的"层次型分组"或者"部分-整体分解"结构,自编码器倾向于学习得到与样本相对应的低位向量,该向量可以更好地表示高维样本的数据特征。
如果网络输入的是图像,第一层会学习识别边,第二层会学习组合边,构成轮廓等,更高层会学习组合更形象的特征。例如:人脸识别,学习如何识别眼睛、鼻子、嘴等。
3、代替和级联
栈式自编码会将网络中的中间层作为下一个网络的输入进行训练。我们可以得到网络中每一个中间层的原始值,我了能有更好的效果,还可以使用级联的方式进一步优化网络的参数。
在已有的模型上接者优化参数的步骤习惯上成为"微调"。该方法不仅在自编码网络,在整个深度学习里都是常见的技术。
微调通常在有大量已标注的训练数据的情况下使用。在这样的情况下,微调能显著提高分类器的性能。但如果有大量未标记数据集,却只有相对较少的已标注数据集,则微调的作用有限。
四 自编码器的应用场合
在之前我们使用自编码器和去噪自编码器演示了MNIST的例子,主要是为了得到一个很好的可视化效果。但是在实际应用中,全连接网络的自编码器并不适合处理图像类的问题(网络参数太多)。
自编码器更像是一种技巧,任何一种网络及方法不可能不变化就可以满足所有的问题,现实环境中,需要使用具体的模型配合各种技巧来解决问题。明白其原理,知道它的优缺点才是核心。在任何一个多维数据的分类中也可以使用自编码,或者在大型图片分类任务中,卷积池化后的特征数据进行自编码降维也是一个好办法。
五 去噪自编码器和栈式自编码器的综合实现
- 我们首先建立一个去噪自编码器,
- 然后再对第一层的输出做一次简单的自编码压缩,
- 然后再将第二层的输出做一个softmax分类
- 最后把这3个网络里的中间层拿出来,组成一个新的网络进行微调。