深度学习在早期一度被认为是无监督的特征学习。

  1. 无监督学习,即不需要标注数据就可以对数据进行一定程度的学习,这种学习是对数据内容的组织形式的学习,提取的是频繁出现的特征;
  2. 逐层抽象,特征是需要不断抽象的,就像人总是从简单基础的概念开始学习,再到复杂的概念。

对于那些特征并不明确的领域,人工的提取特征需要行业相关的专业知识。比如图像识别,图像是有像素点构成。像素点数值是没有区分性的,一张图片真正有意义的不仅仅是数值的大小,而是不同像素点之间的空间结构信息。早期学者研究稀疏编码的时,提取了很多16X16像素的图像碎片,他们发现几乎所有的图像碎片都可以通过64种正交的边组合而成。而且组合出一张图像碎片所需要的边的数量是很少的,也就是说是稀疏。对于声音,也同样发现了20种基本结构,绝大多数声音可以由这些基本结构线性组合得到。使用少量的基本特征组装出更高层的抽象特征。

一张图片从原始的像素慢慢抽象,从像素组成点、线,再将点、线组合成小零件,再将小零件组成车轮、车窗、车声等高阶特征,这便是深度学习在训练过程中所做的特征学习。

如果有很多标注数据,则可以训练一个深层的神经网络。如果没有标注的数据呢?依然可以使用无监督的自编码器来提取特征。

自编码器(AutoEncoder),顾名思义,即可以使用自身的高阶特征编码自己。也就是说,图片不再是使用原始3维像素点来表示,而是用更为高阶的特征。自编码器其实也是一种神经网络,输入和输出是一致的,目标是使用稀疏一些的高阶特征重新组合来重构自己。

  1. 输入/输出一致
  2. 使用高阶特征来重构自己,而不只是复制像素点

要达成以上两点要求,那么对于神经网络加入几个限制。

a. 限制中间隐含层节点的数量,让中间层节点的数量小于输入/输出节点的数量,降维。降维过程,必然是丢失信息的过程。这个时候模型就会去寻找最主要的特征保留下来。如果再给中间隐藏层的权重加L1的正则,那么根据调控惩罚系数$\lambda$大小,就能调整特征的稀疏程度(L1正则化会导致出现系数为0的权重);

b. 如果给数据加入噪声,那么就是Denoising AutoEncoder(去噪自编码器)。特意加上噪声就是为了让模型去掉噪声,找出真正的模式和结构。提高模型的泛化性能。


In [17]:
%matplotlib inline

In [1]:
import numpy as np
from sklearn import preprocessing
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.examples.tutorials import mnist
from __future__ import division

这里的自编码器会使用到的参数初始化方法叫做Xavier intialization,其特点是根据某一层网络的输入、输出节点数量自动调整最合适的分布。论文中指出,如果深度学习的权重初始化得太小,那信号将在每层传递中逐渐缩小而难以产生作用,但如果权重初始化得太大,那信号将在每层间传递时逐渐放大而导致发散。而Xavier初始化器就是让权重被初始化得不大不。从数学上来看,Xavier就是让权重满足0均值,同时方差为$\frac{2}{n_{in}+n_{out}}$。分布上可以使用均与分布或者高斯分布。$n_{in}, n_{out}$分别表示输入节点数量,输出节点数量


In [2]:
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)

In [3]:
class AdditiveGaussianNoiseAutoEncoder(object):
    def __init__(self, n_input, n_hidden, transfer_function=tf.nn.softplus, optimizer=tf.train.AdamOptimizer(), noise_scale=0.1):
        """
        Parameters
        ------------
        n_input: 输入变量数
        n_hidden: 隐含层节点数
        transfer_function: 隐含层激活函数,默认为softplus
        optimizer: 优化器,默认为Adam
        noise_scale: 高斯噪声系数,默认为0.1
        """
        self.n_input = n_input
        self.n_hidden = n_hidden
        self.transfer = transfer_function
        self.noise_scale = noise_scale
        self.weights = self._initialize_weights()
        # 噪声比例
        self.scale = tf.placeholder(tf.float32)
        # 输入值[batch_size, input_dimension]
        self.x = tf.placeholder(tf.float32, [None, self.n_input])
        # 使用高斯分布加上noise。
        self.noise_x = self.x + self.scale * tf.random_normal((self.n_input,))
        # 关联输入层和隐藏层的权重系数,使用转化函数处理 transfer(w1*input + b1)
        self.hidden = self.transfer(tf.add(tf.matmul(self.noise_x, self.weights['w1']), self.weights['b1']))
        # 关联隐藏层和输出层的权重系数 w2 * hidden + b2
        self.reconstrunction = tf.add(tf.matmul(self.hidden, self.weights['w2']), self.weights['b2'])
        # 使用平方误差 0.5*sum((y-x)^2)
        self.cost = 0.5 * tf.reduce_sum(tf.pow(tf.subtract(self.reconstrunction, self.x), 2.0))
        # 接下来使用优化算法计算最优解
        self.optimizer = optimizer.minimize(self.cost)
        
        # 初始化参数
        self.sess = tf.Session()
        self.sess.run(tf.global_variables_initializer())
    
    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):
        """
        执行优化算法,更新权重
        返回成本结果
        
        Parameters
        ---------------
        X: 训练数据。通常是一个batch大小的数据块。
        """
        cost, opt = self.sess.run([self.cost, self.optimizer], 
                                  feed_dict={self.x: X, self.scale: self.noise_scale})
        return cost
    
    def calc_total_cost(self, X):
        """
        计算最终成本结果。
        
        Parameters
        ------------
        X:测试数据。通常是传入一整个测试样本数据。
        """
        return self.sess.run(self.cost, feed_dict={self.x: X, self.scale: self.noise_scale})
    
    def transform(self, X):
        """
        返回隐藏层的结果,自编码器的隐藏层最主要的功能就是学习数据中的高阶特征。
        主要是为了解析一张图片的高阶特征到底是什么样子。
        
        Parameters
        ---------------
        X: 图片数据。
        """
        return self.sess.run(self.hidden, feed_dict={self.x: X, self.scale: self.noise_scale})
    
    def generate(self, hidden=None):
        """
        生成结果的API。输入隐藏层,可以得到最终的输出结果。与以上的transform合并起来,就构成自编码的完整步骤。
        """
        if hidden is None:
            hidden = np.random.normal(size=self.weights['b1'])
        return self.sess.run(self.reconstrunction, feed_dict={self.hidden: hidden})
    
    def getWeights(self):
        return self.sess.run(self.weights['w1'])
    
    def getBiases(self):
        return self.sess.run(self.weights['b1'])

载入数据


In [4]:
mnist_data = mnist.input_data.read_data_sets('MNIST', one_hot=True)


Extracting MNIST/train-images-idx3-ubyte.gz
Extracting MNIST/train-labels-idx1-ubyte.gz
Extracting MNIST/t10k-images-idx3-ubyte.gz
Extracting MNIST/t10k-labels-idx1-ubyte.gz

标准化

先将数据处理为均值为0,方差为1的分布。


In [5]:
def standard_scale(X_train, X_test):
    standarder = preprocessing.StandardScaler()
    X_train = standarder.fit_transform(X_train)
    X_test = standarder.transform(X_test)
    return X_train, X_test

In [ ]:
X_train, X_test = standard_scale(mnist_data.train.images, mnist_data.test.images)

获取数据块


In [6]:
def get_random_block_from_data(data, batch_size):
    start_index = np.random.randint(0, len(data) - batch_size) # 避免index越界
    return data[start_index: (start_index+batch_size)]

全局参数

总样本数 最大训练轮数(epoch) batch_size 特定轮数展示cost


In [8]:
n_samples = int(mnist_data.train.num_examples)
training_epochs = 20
batch_size = 128
display_step = 1

In [9]:
autoencoder = AdditiveGaussianNoiseAutoEncoder(n_input=784, n_hidden=200, transfer_function=tf.nn.softplus, optimizer=tf.train.AdamOptimizer(learning_rate=0.001), noise_scale=0.01)

In [10]:
for epoch in xrange(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 * (batch_size / n_samples)
    if epoch % display_step == 0:
        print("Epoch: %4d" % (epoch+1), "cost=", "{:.9f}".format(avg_cost))


('Epoch:    1', 'cost=', '19614.778119318')
('Epoch:    2', 'cost=', '12392.425858523')
('Epoch:    3', 'cost=', '10848.817247727')
('Epoch:    4', 'cost=', '9841.163826705')
('Epoch:    5', 'cost=', '9874.639818750')
('Epoch:    6', 'cost=', '9289.234441477')
('Epoch:    7', 'cost=', '8157.059838068')
('Epoch:    8', 'cost=', '8747.697441477')
('Epoch:    9', 'cost=', '8655.771263068')
('Epoch:   10', 'cost=', '9114.193946023')
('Epoch:   11', 'cost=', '8527.577067614')
('Epoch:   12', 'cost=', '8285.892010795')
('Epoch:   13', 'cost=', '8300.039256818')
('Epoch:   14', 'cost=', '7977.954819318')
('Epoch:   15', 'cost=', '8519.870825568')
('Epoch:   16', 'cost=', '7761.309712500')
('Epoch:   17', 'cost=', '8018.388336932')
('Epoch:   18', 'cost=', '7985.400672159')
('Epoch:   19', 'cost=', '8674.740591477')
('Epoch:   20', 'cost=', '7401.665698864')

In [11]:
print("Total cost: " + str(autoencoder.calc_total_cost(X_test)))


Total cost: 666507.0

编码器的效果

绘制转换前后的图片可以看出,编码器突出了图片更为关键的信息。自编码器能够将更为重要的特征提取出来。


In [39]:
def plot_transform_sample(sample_image):
    plt.title("Origin Image")
    plt.imshow(sample_image.reshape(28, 28), cmap='binary')
    plt.show()
    sample_transform_hidden = autoencoder.transform(sample_image.reshape(-1, 784))
    sample_transform_image = autoencoder.generate(hidden=sample_transform_hidden)
    plt.title("Transform Image")
    plt.imshow(sample_transform_image.reshape(28, 28), cmap='binary')
    plt.show()

In [41]:
plot_transform_sample(X_train[20200])



In [ ]: