深度学习在早期一度被认为是无监督的特征学习。
对于那些特征并不明确的领域,人工的提取特征需要行业相关的专业知识。比如图像识别,图像是有像素点构成。像素点数值是没有区分性的,一张图片真正有意义的不仅仅是数值的大小,而是不同像素点之间的空间结构信息。早期学者研究稀疏编码的时,提取了很多16X16像素的图像碎片,他们发现几乎所有的图像碎片都可以通过64种正交的边组合而成。而且组合出一张图像碎片所需要的边的数量是很少的,也就是说是稀疏。对于声音,也同样发现了20种基本结构,绝大多数声音可以由这些基本结构线性组合得到。使用少量的基本特征组装出更高层的抽象特征。
一张图片从原始的像素慢慢抽象,从像素组成点、线,再将点、线组合成小零件,再将小零件组成车轮、车窗、车声等高阶特征,这便是深度学习在训练过程中所做的特征学习。
如果有很多标注数据,则可以训练一个深层的神经网络。如果没有标注的数据呢?依然可以使用无监督的自编码器来提取特征。
自编码器(AutoEncoder),顾名思义,即可以使用自身的高阶特征编码自己。也就是说,图片不再是使用原始3维像素点来表示,而是用更为高阶的特征。自编码器其实也是一种神经网络,输入和输出是一致的,目标是使用稀疏一些的高阶特征重新组合来重构自己。
要达成以上两点要求,那么对于神经网络加入几个限制。
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)
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)]
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))
In [11]:
print("Total cost: " + str(autoencoder.calc_total_cost(X_test)))
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 [ ]: