TensorFlow 实现自编码器及多层感知机

4.1 自编码器简介

学者研究稀疏编码 (Sparse Coding) 时, 对大量黑白风景照提取许多 16*16 的图像碎片. 几乎所有的图像碎片可以由 64 种正交的边组合得到. 并且所需要的边数量是很少即稀疏的.

声音也如此, 音频有 20 种基本结构, 大多数声音可以由这些基本结构线性组合得到, 这就是特征的稀疏表达. 即用少量基本特征/基本结构 (basis) 组合成更高层抽象的特征.

特征可以不断抽象为高级特征

  • 若有很多标注数据, 则可以通过训练 DNN 抽象特征
  • 若没有标注的数据, 可以使用无监督的自编码器提取特征

自编码器 (AutoEncoder): 使用自身的高阶特征编码自己
自编码器也是神经网络, 特点:

  1. 输入和输出是一致的
  2. 目标是使用稀疏的高阶特征重新组合来重构自己, 而不只是复制像素点

自编码器输入节点和输出节点数量一致, 通常希望使用少量稀疏的高阶特征重构输入, 而不是单纯地逐个复制输入节点, 故做出以下限制:

  1. 限制隐含层节点数量, 相当于降维. 若给隐含层权重加一个 L1 正则, 则可以控制稀疏程度
  2. 给数据加入噪声, 即 Denoising AutoEncoder (去噪自编码器)

去噪自编码器常用噪声是加性高斯噪声 (Additive Gaussian Noise, AGN)

也可以用 Masking Noise, 即有随机遮挡的噪声. 即置图像中部分像素为 0.

4.2 TensorFlow 实现自编码器

开始实现去噪自编码器; Variational AutoEncoder (VAE) 相对复杂, 对中间节点分布有强假设, 拥有额外损失项, 使用 SGVB (Stochastic Gradient Variational Bayes) 算法训练. 在生成模型中发挥巨大作用.

1
2
3
4
5
6
import numpy as np
import sklearn.preprocessing as prep
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline

自编码器使用一种参数初始化方法 xavier initialization. 特点是根据某一层网络的输入, 输出节点数量自动调整最合适的分布. 如果深度学习权重初始化太小, 信号在层间传递时逐渐缩小难以产生作用. 初始化太大, 则造成发散. Xaivier 初始化器使权重初始化为合适值. 即 0 均值, 方差 $\frac{2}{n_{in}+n_{out}}$ 的均匀分布或高斯分布. 如

内的均匀分布

下面 fan_in 是输入节点数量, fan_out 是输出节点数量

1
2
3
4
5
6
7
8
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)

def plot_image(image):
plt.imshow(image.reshape([28, 28]), interpolation='nearest', cmap='binary')

下面定义去噪自编码器的 class.

先看构建函数.

transfer_function: 激活函数
scale: 高斯噪声系数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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)
self.training_scale = scale
network_weights = self._initialize_weights() # 参数初始化
self.weights = network_weights

# 定义网络结构: 输入 x, 得到隐含层, 然后在输出层进行重建操作
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'])

# 定义损失函数, 直接使用平方误差
# tf.reduce_mean 不指定第二个参数, 则对所有元素求和
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)

# 参数初始化函数 _initialize_weights
# xavier 返回一个适合于 softplus 等激活函数的初始权重分布
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

# 定义损失 cost 和执行一步训练的函数 partial_fit
# 函数里只需要让 Session 执行两个计算图节点: cost 与 optimizer
# 用一个 batch 数据训练并返回当前 cost
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

# 定义只求 cost 的函数 calc_total_cost, 用于在测试集评测, 不会出发训练操作
def calc_total_cost(self, X):
return self.sess.run(self.cost, feed_dict={self.x: X,
self.scale: self.training_scale})

# 定义 transform 函数, 返回自编码器隐含层的输出结果
# 提供一个接口来获取抽象后的特征/高阶特征
def transform(self, X):
return self.sess.run(self.hidden, feed_dict={self.x: X,
self.scale: self.training_scale})

# 定义 generate 函数, 将隐含层结果作为输入
def generate(self, hidden = None):
if hidden is None:
hidden = np.random.normal(size=self.n_hidden)
return self.sess.run(self.reconstruction,
feed_dict={self.hidden: hidden})

# 定义 reconstruct 函数, 包括 transform 与 generate
def reconstruct(self, X):
return self.sess.run(self.reconstruction, feed_dict={self.x: X,
self.scale: self.training_scale})

# 定义 getWeights 函数获取隐含层权重 w1
def getWeights(self):
return self.sess.run(self.weights['w1'])

# 定义 getBiases 函数获取隐含层偏置 b1
def getBiases(self):
return self.sess.run(self.weights['b1'])

# 可视化原输入图像和加入噪声后的图像
def plot_noiseimg(self, img, show_comp=True):
self.noise = self.sess.run(tf.random_normal((self.n_input,)))
noiseimg = img + self.training_scale * self.noise
plot_image(noiseimg)
if show_comp:
plt.subplot(121)
plot_image(img)
plt.subplot(122)
plot_image(noiseimg)
1
2
3
sess = tf.Session()
with sess:
print(tf.random_normal((10,)).eval())
[ 0.63090104  1.07427788 -0.12856543 -0.81171554 -0.38272545  0.12628117
 -0.89272839 -0.39979595 -0.88008153  0.78596574]

接下来用定义好的 AGN 自编码器在 MNIST 上测试

1
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz

先定义函数对训练与测试数据进行标准化处理, 0 均值, 方差 1 的分布
使用 sklearn.preprossing 的 StandardScaler 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def standard_scale(X_train, X_test):
preprocessor = prep.StandardScaler().fit(X_train)
X_train = preprocessor.transform(X_train)
X_test = preprocessor.transform(X_test)
return X_train, X_test

# 定义一个获取随机 block 数据的函数: 取 0 到 len(data) - batch_size 间的随机
# 整数, 以此为起始位置获取 batch size 的数据. 这属于不放回抽样
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)

# 定义常用参数, 总训练样本数, 最大训练轮数 (epoch) 为 20, batch_size 为 128
n_samples = int(mnist.train.num_examples)
training_epochs = 20
batch_size = 128
display_step = 1

# 选择一副图像
image0 = mnist.train.images[5]

创建一个 AGN 自编码器实例, 定义模型输入节点数 n_input 为 784, 隐含节点数 n_hidden 为 200

1
2
3
4
5
autoencoder = AdditiveGaussianNoiseAutoencoder(n_input=784,
n_hidden=200,
transfer_function=tf.nn.softplus,
optimizer=tf.train.AdamOptimizer(learning_rate=0.001),
scale=0.1)

下面开始训练, 每一轮 (epoch) 开始时, avg_cost 设为 0

1
2
# 可视化原输入图像和加入噪声后的图像
autoencoder.plot_noiseimg(image0)

png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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))

# 可视化重建的图像
reimg = autoencoder.reconstruct(X_train)[5]
plot_image(reimg)
Epoch: 0001 cost= 18786.078810227
Epoch: 0002 cost= 12205.832331818
Epoch: 0003 cost= 11051.004750000
Epoch: 0004 cost= 10350.548848864
Epoch: 0005 cost= 10492.156286364
Epoch: 0006 cost= 9694.406333523
Epoch: 0007 cost= 9424.200915909
Epoch: 0008 cost= 9046.114718182
Epoch: 0009 cost= 8117.241700000
Epoch: 0010 cost= 9109.549274432
Epoch: 0011 cost= 9446.577418182
Epoch: 0012 cost= 8105.107715341
Epoch: 0013 cost= 9239.229179545
Epoch: 0014 cost= 8040.010325000
Epoch: 0015 cost= 8492.325622727
Epoch: 0016 cost= 7906.005873864
Epoch: 0017 cost= 8360.524640341
Epoch: 0018 cost= 7904.247780682
Epoch: 0019 cost= 8273.081871591
Epoch: 0020 cost= 8153.579868182

png

最后对训练完的模型测试

1
print('Total cost: ' + str(autoencoder.calc_total_cost(X_test)))
Total cost: 697430.0

4.3 多层感知机简介

  • 过拟合: Dropout
  • 参数调试: 自适应: Adagrad, Adam, Adadelta. 调试: SGD
  • 梯度消失: Sigmoid 函数在反向传播中梯度值指数减小. ReLU 解决.
    y = max(0, x).

    特点

    1. 单侧抑制
    2. 相对宽阔的兴奋边界
    3. 稀疏激活性

当隐含层使用非线性激活函数, 可以学习 XOR 分类问题.

4.4 TensorFlow 实现多层感知机

现在, 给神经网络加上隐含层, 并使用 Dropout, Adagrad, ReLU.

1
2
3
4
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
sess = tf.InteractiveSession()
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
1
2
3
4
5
6
in_units = 784 # 输入层节点数
h1_units = 300 # 隐含层节点数
W1 = tf.Variable(tf.truncated_normal([in_units, h1_units], stddev=0.1))
b1 = tf.Variable(tf.zeros([h1_units]))
W2 = tf.Variable(tf.zeros([h1_units, 10]))
b2 = tf.Variable(tf.zeros([10]))

接下来定义输入 x 的 placeholder. 在训练与测试时, Dropout 的比率 keep_prob (即保留节点的概率) 不同, 通常训练时小于 1, 预测时等于 1

1
2
x = tf.placeholder(tf.float32, [None, in_units])
keep_prob = tf.placeholder(tf.float32)

下面定义模型结构

1
2
3
hidden1 = tf.nn.relu(tf.matmul(x, W1) + b1)
hidden1_drop = tf.nn.dropout(hidden1, keep_prob)
y = tf.nn.softmax(tf.matmul(hidden1, W2) + b2)

现在已完成训练神经网络第 1 步, 即 forward 计算. 第 2 步定义损失函数并选择优化器优化 loss

1
2
3
4
y_ = tf.placeholder(tf.float32, [None, 10])
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y),
reduction_indices=[1]))
train_step = tf.train.AdagradOptimizer(0.3).minimize(cross_entropy)

第 3 步训练. 采用 3000 个 batch, 每一个 batch 有 100 个样本, 5 轮 (epoch) 迭代

1
2
3
4
tf.global_variables_initializer().run()
for i in range(3000):
batch_xs, batch_ys = mnist.train.next_batch(100)
train_step.run({x: batch_xs, y_: batch_ys, keep_prob: 0.75})

第 4 步, 评测

1
2
3
4
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(accuracy.eval({x: mnist.test.images, y_: mnist.test.labels,
keep_prob: 1.0}))
0.9779
分享到