TensorFlow实现自编码器
- 在深度学习中,自编码器是一种非常有用的无监督学习模型。自编码器(AutoEncoder),即可以用自身的高阶特征编码自己。自编码器也是一种神经网络,但它的输入与输出是一致的。自编码器的思想就是借助稀疏编码,使用稀疏的一些高阶特征重新组合来重构自己。
- 因此,自编码器的特点就是期望输入与输出一致,再就是希望使用高阶特征来重构自己,而不是复制像素点。
- 自编码器的输入节点和输出节点的数量是一致的,所以,为了使用少量稀疏的高阶特征来重构输入,可以加入几种限制。
- 如果限制中间隐含层节点的数量,就相当于一个降维的过程。如果再给中间隐含层的权重加一个L的正则,则可以根据惩罚系数控制隐含系数的稀疏程度,惩罚系数越大,学到的特征组合越稀疏,实际使用的特征数量越少。
- 如果给数据加入噪声,那么就是Denoising AutoEncoder(去噪自编码器),将从噪声中学习出数据的特征。因为,完全复制不能去掉添加的噪声,无法完全复原数据,只有学习数据的模式与结构,将无律的噪声略去,才可以复原数据。去噪编码器最常使用的噪声就是加性高斯噪声(Additive Gaussian Noise,AGN),也可以使用Masking Noise,即随机遮挡的噪声。
这次主要是根据《tensorflow实战》一书的指导,自己实现一下去噪自编码器。无噪声的自编码器只需要去掉噪声,并保证隐含层节点小于输入层节点;Masking Noise的自编码器只需要将高斯噪声改为随机遮挡噪声;VAE(Variational AutoEncoder)则相对复杂,关于VAE,接下来肯定是要学习的,到时候具体的过程也会记录下来。
import numpy as np
import sklearn.preprocessing as prep
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
Numpy是常用的库,是Python科学计算的基础包。它提供了多维数组对象、基于数组的各种派生对象(例如,masked Array, 矩阵)。除此之外,还提供了各种各样的加快数组操作的例程,包括数学基本计算、逻辑、图形操作、排序、选择、输入输出,离散傅立叶变换、基础线性代数、基础统计操作、随机仿真等等。还有Scikit-learn中的preprocessing模块,这是一个对数据进行预处理的常用模块,会使用其中的数据标准化的功能。Scikit-learn是一个功能强大的python包,这个星期刚好有模式识别的一个实验,需要用到这个包,到时候进行学习总结。实现中依然使用的MNIST数据集。代码主要来自于TensorFlow的开源实现。
def xavier_init(fan_in,fan_out,constant = 1):
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)
这里使用了一种参数初始化的方法,xavier initialization,fan_in为输入节点的数量,fan_out是输出节点的数量。Xavier初始化器在Caffe的早期版本中被频繁使用,特点是会根据某一层网络的输入,输出节点数量自动调整最合适的分布。在Caffe的学习过程中,如果遇到,会进行记录与学习。从数学的角度,Xavier就是让权重满足0均值,同时方差为2/in+out,分布可以用均匀分布或者高斯分布。这里使用的是均匀分布,根据方差公式可以得出方差为所要求的。因此,这里实现的就是标准的均匀分布的Xavier初始化器。
- 下面定义去噪自编码的class。
class AdditiveGaussianNoiseAutoencoder(object):
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) #将scale参数做成一个placeholder
self.training_scale = scale
network_weights = self._initialize_weights() #接下来会定义_initialize_weights()函数
self.weights = network_weights
这里定义了一个构建函数 _ init _(),包含这样几个输入:n_input输入变量数,n_hidden隐含层节点数,transfer_function隐含层激活函数(默认为softplus),optimizer优化器(默认为Adam),scale高斯噪声系数(默认为0.1)。
- 接下来定义网络结构
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']) #在输出层进行数据复原,重建操作,即建立reconstruction层
在隐含层,现将输入x加上了噪声,然后用tf.matmul将加入噪声的输入与隐含层的权重w1相乘,并使用tf.add加上隐含层的偏置b1,最后使用self.transfer(即softpuls)对结果进行激活函数处理。在reconstruction层不需要激活函数。
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)
定义自编码器的损失函数,这里使用的是平方误差(squared error)作为cost,tf.subtract是计算输出与输入之差。最后创建了Session,初始化自编码器的全部模型参数。
- 定义参数初始化函数_initialize_weights
def _initialize_weights(self):
all_weights = dict() #创建一个字典,存入w1,b1,w2,b2
all_weights['w1'] = tf.Variable(xavier_init(self.n_input,self.n_hidden)) #使用xavier_init函数初始化,返回一个比较适合于softplus等激活函数的权重初始分布
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
- 定义计算损失cost和执行一步训练的函数partial_fit
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
函数让Session执行了两个计算图的节点,分别是损失cost,训练过程optimizer。函数partial_fit做的就是用一个batch数据进行训练并返回当前的损失cost。
- 定义只求cost的函数calc_total_cost
def calc_total_cost(self,X):
return self.sess.run(self.cost,feed_ditc = {self.x:X,self.scale:self.training_scale})
这里只让Session执行一个计算图节点self.cost。这个函数实在自编码器训练完毕后,在测试集上对模型性能进行评测时会用到的,不会像partial_fit那样触发训练操作。
- 定义transform函数,返回自编码器隐含层的输出结果
def transform(self,X):
return self.sess.run(self.hidden)
transform函数的目的是提供一个接口来获取抽象后的特征,自编码器的隐含层的最主要功能就是学习数据中的高级特征。
- 定义generate函数
def genreate(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})
generate函数将隐含层的输出结果作为输入,通过之后的重建层将提取到的高阶特征复原为原始数据。这个接口和前面的transform正好将整个自编码器拆分为两部分,这里的generate接口是后半部分,将高阶特征复原为初始数据。
- 定义reconstruct函数
def reconstruct(self,X):
return self.sess.run(self.reconstruction,feed_dict = {self.x:X,self.scale:self.training_scale})
reconstruct函数整体运行了一遍复原过程,即包括了transform和generate两块。
- getWeights函数
def getWeights(self):
return self.sess.run(self.weights['w1']) #作用是获取隐含层的权重w1
- getBiases函数
def getBiases(self):
return self.sess.run(sefl.weights['b1']) #作用是获取隐含层的偏执b1
到这里,去噪自编码器的class就定义完了,回顾一下,包括了构建函数,神经网络结构,损失函数,权重的初始化,以及一些成员函数。
下面就是用定义好的AGN自编码器在MNIST数据集上的一些测试。
mnist = input_data.read_data_sets('MNIST_data',ont_hot = True)
#载入MNIST数据集
- 定义一个对训练,测试数据进行标准化处理的函数
def standard_scale(X_train,X_test):
preprocessor = prep.StandarScaler().fit(X_train) #StandarScaler是sklearn.preprossing工具包里面的类,先在训练集上fit
X_train = preprocessor.transform(X_train) #transform是返回隐含层的输出结果,所以X_train就完成了标准化处理
X_test = preprocessor.transform(X_test)
return X_train,X_test
- 再定义一个获取随机block数据的函数
def get_random_block_from_data(data,batch_size):
start_index = np.random.randint(o,len(data) - batch_size)#从0到len(data) - batch_size之间取一个随机数
return data[start_index:(start_index + batch_size)]#从随机数的位置开始,顺序取一个 batch size的数据。这里是不放回的抽样
X_train,X_test = standard_scale(mnist.train.images,mnist.test.images) #使用定义好的函数,对训练集和测试集进行标准化变换
- 定义一些常用参数
n_samples = int(mnist.train.num_samples) #总训练样本数
training_epochs = 20 #最大训练的轮数
batch_size = 128
display_step = 1 #每次显示一次损失cost的轮数
- 创建一个AGN自编码器的实例
autoencoder = AdditiveGaussianNoiseAutoencoder(n_input = 784,n_hidden = 200,transfer_function = tf.nn.softplus,optimizer = tf.train.AdamOptimizer(learining_rate = 0.001),scale = 0.01)
- 下面开始训练过程
for epoch in range(training_epochs):
avg_cost = 0.
total_batch = int(n_samples/batch_size)
for i in range(total_batch):
batch_xs = get_random_block_from_data(X_train,batch_size)
cost = autoencoder.partial_fit(batch_xs)
avg_cost += cost/n_samples * batch_size
if epoch % display_step == 0:
print("Epoch:",'%04d' % (epoch+1),"cost = ","{:.9f}".format(avg_cost))
- 最后对训练完的模型进行性能测试,对测试集X_test进行测试
print("Total cost:" + str(autoencoder.cal_total_cost(X_test))) #使用成员函数cal_total_cost计算平方误差
可以发现,实现自编码器和实现一个单隐层的神经网络差不多,只不过是在数据输入时做了标准化,并加上了一个高斯噪声,同时我们的输出结果不是数字分类结果,而是复原的数据,不需要标注过的数据进行监督训练。
自编码器作为一种无监督学习的方法,它与其他无监督学习的主要不同在于,它不是对数据进行聚类,而是提取其中最有用,最频繁出现的高阶特征,根据这些高阶特征重构数据。