TensorFlow实现自编码器

自编码器介绍:
深度学习可以解决一些人工难以提取有效特征的问题。在深度学习的早期,它一直被认为是一种无监督特征学习的方法。对于深度学习用于特征学习有两个关键点:
1)无监督学习,即不需要标注的数据就可以对数据进行一定程度的学习,提取频繁出现的特征;
2)逐层抽象,即从简单的微观的特征开始,不断抽象特征的层级,逐渐往复杂的宏观特征转变。例如以汽车图片为例,深度学习的特征学习过程为:将一张图片的原始像素慢慢抽象,从像素组成点、线,再将点线组成小零件,再将小零件组成车轮、车窗、车身等高阶特征,这便是深度学习在训练过程中所做的特征学习。

特征是可以进行不断抽象转化为高一级的特征,那我们如何找到一些基本的结构,然后如何抽象呢?主要可以分为两种情况:
1)在有很多标注的数据时,可以使用训练一个深层的神经网络来获得;
2)在没有标注数据时,我们可以使用无监督的自编码器来提取特征。

自编码器,即可以使用自身的高阶特征编码自己。自编码器也是一种神经网络,它的输入和输出是一致的,它借助稀疏编码器的思想,目标是使用稀疏的一些高阶特征重新组合来重构自己。
它的特点:
1)期望输入和输出一致,自编码器的输入节点和输出节点的数量是一致的;
2)希望使用高阶特征来重构自己,而不是复制像素点。

自编码器通常希望使用少量稀疏的高阶特征来重构输入,因此我们可以加入几种限制:
1)如果限制训练中间隐含层的节点数量,比如使中间隐含层节点的数量小于输入/输出节点的数量,就相当于一个降维的过程。只能够学习到数据中最重要的特征复原,将可能不相关的内容去除。如果再给中间隐含层的权重加入L1正则,则可以根据惩罚系数控制隐含节点的稀疏程度,系数越大,学到的特征组合越稀疏,实际使用的特征数量越少。
2)如果给数据加入噪声,那么就是去噪自编码器。我们将从噪声中学习出数据的特征,这时我们只有学习数据中频繁出现的模式和结构,将无规律的噪声略去,才可以复原数据。去噪自编码器中最长使用的噪声是加性高斯噪声。
如果自编码器的隐含层只有一层,那么其原理类似于主成分分析(PCA)。

自编码器的结构图如下图所示:
这里写图片描述

在训练很深的神经网络时,直接训练会变得非常困难,Hinton提出了一种可行的方案:
1)可以使用无监督的逐层训练提取特征,将网络权重的初始化到一个比较好的位置,辅助后面的监督训练。
2)利用监督方法训练整个网络。
其中无监督的逐层训练,其思想和自编码器非常相似。
同时,Hinton提出的DBN模型,它包括多个隐含层,每个隐含层都是限制玻尔兹曼机。DBN在训练时,需要对每两层间进行无监督的预训练,这个过程其实就相当与一个多层的自编码器,可以将整个网络的权重初始化到一个理想的分布,最后通过反向传播算法调整模型的权重,这个过程会使用经过标注信息来做监督性的分类训练。上述两个过程,能够很好的解决由于神经网络过深导致的梯度弥散的问题。

下面我们以去噪自编码器为例,简单介绍一下如何使用TensorFlow实现自编码器。

import numpy as np
import sklearn.preprocessing as prep
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

def xavier_init(fan_in, fan_out, constant=1):
    # 权重初始化,使得权重满足(low, high)的均匀分布
    # fan_in: 输入节点的数量; fan_out: 输出节点的数量
    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):
    def __init__(self, n_input, n_hidden, transfer_function = tf.nn.softplus, 
                 optimizer = tf.train.AdamOptimizer(), scale = 0.1):
        # 神经网络的构建函数;
        # n_input:输入变量数; n_hidden:隐含层节点数; transfer_function: 隐含层激活函数
        # optimizer: 训练优化器; scale: 高斯噪声系数
        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_weight = self._initialize_weights()
        self.weights = network_weight

        # 网络结构
        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']) # 不需要使用激活函数

        # 自编码器的损失函数
        self.cost = 0.5 * tf.reduce_sum(tf.pow(tf.subtract(self.x, self.reconstruction), 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})

    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):
        return self.sess.run(self.weights['b1'])


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

def standard_scale(X_train, X_test):
    # 对训练和测试数据进行标准化,需要注意的是必须保证训练集和测试集都使用完全相同的Scale
    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)
train_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(train_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))

# 输出总的测试误差        
print("Total cost:" + str(autoencoder.calc_total_cost(X_test)))

1. import sklearn.preprocessing as prep
这是一个对数据进行预处理的常用模块,在后面的程序中我们会使用它来进行数据的标准化。

2. def xavier_init(fan_in, fan_out, constant=1):
该函数定义了一种参数初始化的方法,它会根据某一层网络的输入、输出节点数量自动调整最合适的分布。对于深度学习而言,如果权重初始化得太小,那信号将在每层间传递时逐渐缩小而难以产生作用,但如果权重初始化得太大,那信号将在每层间传递时逐渐放大并导致发散和失效。上述函数初始化就是实现权重初始化得大小正好合适。

3.self.hidden=self.transfer(tf.add(tf.matmul(self.x+scale*tf.random_normal((n_input,)), self.weights[‘w1’]), self.weights[‘b1’]))
加入高斯噪声的信号,从而实现去噪自编码器,可以将噪声去除,实现标准的自编码器。

4. self.cost=0.5*tf.reduce_sum(tf.pow(tf.subtract(self.x, self.reconstruction), 2.0))
使用平方误差作为损失函数,计算重构的输出与输入之间的差异。

5. def _initialize_weights(self):
初始化权重的函数,其中w1使用前面定义的xavier_init函数初始化,即可返回一个比较适合于softplus等激活函数的权重初始分布,而偏置b1将其全部置为0,对于输出层,因为没有使用激活函数,这里将w2,b2全部初始化为0即可。

6. def partial_fit(self, X):
定义计算损失以及执行一步训练的函数。

7. def standard_scale(X_train, X_test):
对训练数据和测试数据进行标准化处理的函数。标准化也就是让数据变成0均值且标准差为1的分布。方法是先减去均值,再除以标准差(使用sklearn.preprossingd的StandardScaler这个类实现)。值得注意的是,必须保证训练数据和测试数据使用完全相同的Scaler,这样才能保证后面模型处理数据时的一致性,这也就是为什么先在训练数据上fit出一个共用的Scaler的原因。

8. def get_random_block_from_data(data, batch_size):
定义一个随机获取block数据的函数,值得注意的是,这属于不放回抽样,可以提高数据的利用效率。

9.autoencoder=AdditiveGaussianNoiseAutoencoder(n_input=784,n_hidden=200,transfer_function=tf.nn.softplus,optimizer=tf.train.AdamOptimizer(learning_rate = 0.001),scale=0.01)
创建一个autoencoder的实例,输入节点数量为784,引层节点数为200,激活函数为softplus,优化器optimizer为Adam并且学习率为0.001,噪声系数为0.01。

以上就是实现自编码器的过程,从上面的过程中我们可以看出,实现自编码器和实现一个单引层的神经网络差不多,只不过是在数据输入时做了标准化,并且加上了高斯噪声,同时我们的输出结果不是数字分类结果,而是复原的数据,因此不需要用标注过的数据(损失函数的构建是通过重建数据与实际输入的均方误差得到的)。因此自编码器作为一种无监督学习的方法,它不是对数据进行聚类,而是提取其中最有用、最频繁出现的高阶特征,根据这些高阶特征重构数据。
现在无监督预训练的使用场景比以前少了许多,训练全连接的MLP或者CNN、RNN时,我们都不需要先使用无监督训练提取特征。但是无监督学习乃至自编码器依然是非常有用的。由于现实生活中,有标注的信息还是少数,我们可以提取无监督数据的高阶特征,并使用在其他地方。

猜你喜欢

转载自blog.csdn.net/wyl1813240346/article/details/79794864