TensorFlow学习笔记(四)

自编码器及多层感知器

MNIST介绍

  • MNIST是在机器学习领域中的一个经典问题。该问题解决的是把28x28像素的灰度手写数字图片识别为相应的数字,其中数字的范围从0到9.

  • 首先需要下载数据集 LeCun给出了下载地址

  • 这里有非常详细的MNIST介绍以及可视化的图片演示Chris Olah’s visualizations of MNIST.

文件 内容
train-images-idx3-ubyte.gz training set images (9912422 bytes)
train-labels-idx1-ubyte.gz training set labels (28881 bytes)
t10k-images-idx3-ubyte.gz test set images (1648877 bytes)
t10k-labels-idx1-ubyte.gz test set labels (4542 bytes)

TensorFlow直接提供了MNIST的下载

1
2
3
4
5
6
7
8
9
10
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('data/', one_hot=True)

'''
文件已存在会这样显示,不存在时会有一行是显示文件大小的
Extracting data/train-images-idx3-ubyte.gz
Extracting data/train-labels-idx1-ubyte.gz
Extracting data/t10k-images-idx3-ubyte.gz
Extracting data/t10k-labels-idx1-ubyte.gz
'''

先查看MNIST数据集的情况

1
2
3
4
5
6
7
8
9
print(mnist.train.images.shape,mnist.train.labels.shape) # 训练集
print(mnist.test.images.shape,mnist.test.labels.shape) # 测试集
print(mnist.validation.images.shape,mnist.validation.labels.shape) # 验证数据集

'''
(55000, 784) (55000, 10)
(10000, 784) (10000, 10)
(5000, 784) (5000, 10)
'''
  • 使用Softmax Regression实现一个简单的机器学习
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
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('mnist/data/', one_hot=True)

print(mnist.train.images.shape, mnist.train.labels.shape)
print(mnist.test.images.shape, mnist.test.labels.shape)
print(mnist.validation.images.shape, mnist.validation.labels.shape)

import tensorflow as tf
sess = tf.InteractiveSession() # 注册为默认session
x = tf.placeholder(tf.float32, [None, 784]) # None表示不限制条数的输入

W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

y = tf.nn.softmax(tf.matmul(x, W) + b) # softmax regression算法

# cross—entropy
y_ = tf.placeholder(tf.float32,[None,10])
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y),reduction_indices=[1]))

# SGD
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

# 初始化所有变量
tf.global_variables_initializer().run()

# 每次随机从训练集选择100个进行训练构成mini-batch
for i in range(1000):
batch_xs,batch_ys = mnist.train.next_batch(100)
train_step.run({x:batch_xs,y_:batch_ys})

# tf.argmax是从一个tensor中找到最大的序号,tf.argmax(y,1)求各个预测的数字中概率最大的一个
# tf.argmax(y_,1)找样本的真实数字类别,tf.equal表示是否相等
correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(y_,1))

# 用tf.cast将之前的correct_prediction输出的bool值转换为float32;之后再求平均
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

# 输出准确率
print(accuracy.eval({x:mnist.test.images,y_:mnist.test.labels}))

'''
输出(每次可能不一样)
0.9194
'''
  • 这个可以看成是一个没有隐藏层的最浅的神经网络,一共有4步:

    1. 定义算法公式,也就是神经网络forward时的计算
    2. 定义loss,选定优化器,并制定优化器优化loss
    3. 迭代地对数据进行训练
    4. 在测试集或是验证集上对准确率进行评测

TensorFlow实现自编码器

  • 自编码器

    传统的机器学习的任务很大程度上依赖于特征工程。但是特征工程往往非常耗时耗力,而且在图像、视频、语音中提取到有效特征更难。而深度学习可以大大缓解机器学习模型对特征工程的依赖。深度学习在早期一度被认为是一种无监督的特征学习,模仿了人脑的对特征逐层抽象提取的过程。

    早年有学者研究稀疏编码时,收集了大量的黑白风景照片,并从中提取了许多16*16的图像碎片。他们发现机会所有的图像碎片都可以由64种正交的边组合得到。还有学者同事发现声音也存在这种情况。这就是特征的稀疏表达,使用少量的基本特征组合拼装得到更高层抽象的特征。通常我们需要多层的神经网络,对于每一层神经网络来说,前一层的输出都是为加工的像素,而这一层则是对像素进行加工组织成更高阶的特征。

    自编码器,即可以使用自身的高阶特征编码自己。自编码器其实也是一种神经网络,它的输入和输入是一致的,它借助稀疏编码的思想,目标是使用稀疏的一些高阶特征重构自己。因此,它的特点非常明显:第一,期望输入/输出一致;第二,希望使用高阶特征来重构自己,而不只是复制像素点。

    Hinton 在Reducing the dimensionality of data with neural networks 论文中讲解了使用自编码器对数据进行降维的方法。Hinton还提出了基于深度信念网络(Deep Belief Networks,DBN)可使用无监督的逐层训练的贪心算法,为训练很深的网络提供了一个可行的方案:很难直接训练极深的网络,但是可以用无监督的逐层训练提取特征,将网络的权重初始化到一个比较好的位置,辅助后面的监督训练。这个思想是相当于学习一个恒等式y=x,自编码器的输入节点和输出节点的数量是一致的,但是如果只是单纯地逐个重复输入节点则没有意义,所以希望使用少量稀疏的高阶特征来重构输入,所以可以加入几种限制。

    (1) 如果限制中间隐含层节点的数量,比如让中间隐含层节点的数量小与输入/输出节点的数量,就相当于一个降维的过程。此时如果再给中间隐含层的权重加一个L1正则,则可以通过惩罚系数控制隐含节点的稀疏程度,惩罚系数越大,学到的特征组合越稀疏。至于为什么L1正则化可以增加稀疏性,请看某大神博客有数学推导。

    (2) 如果给数据加入噪声,那么就构成了去噪自编码器,我们将从噪声中学习出数据的特征。同样,我们也不可能完全复制节点,完全复制并不能去除我们添加的噪声,无法完全复原数据。所以唯有学习数据频繁出现的模式和结构,将无规律的噪声略去,才可以复原数据。去噪自编码器最常用的噪声是加性高斯噪声。

  • TensorFlow实现去噪自编码器

    刚刚用Linux配置sklearn简直要疯。顺带提一下坑的地方,import sklearn一直提示没找到 _bz2 ,查了好久是要先安装bz2的模块yum install bzip2-devel
    然后重新编译python3.6:./configure make all make install make clean make distclean

1
2
3
4
import numpy as np
import sklearn.preprocessing as prep
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

xavier initialization 是一种参数初始化方法。
Xavier在caffe早期版本频繁使用 它的特点是根据某一层网络的输入输出节点数量自动的调整最合适的分布。
Yoshua Bengio 在一篇论文中指出,如果学习模型的权重初始化得太小,
那么信号将在每层间传递时逐渐缩小而难以产生作用,如果初始化的太大,那信号将在每层间的传递时逐渐放大而导致发散和失效。

而Xavier初始化就是让初始化得到不大不小的值。
从数学上就是使权重0均值,方差为$2 / (n_{in}+n_{out})$,分布可以用均匀分布或者高斯分布。
这里创建了一个$(- \sqrt{6 \over {n_{in}+n_{out}}},\sqrt{6 \over {n_{in}+n_{out}}})$ 范围内的均匀分布
它的方差根据公式$D(X)={(max-min)}^2 / 12$刚好等于$2 / (n_{in}+n_{out})$

因此这里实现了标准均匀分布的Xavier初始化器,其中fan_in是输入节点的数量,fan_out是输出节点的数量

1
2
3
4
5
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)
1
2
3
4
5
6
7
8
n_input, 输入变量数
n_hidden, 隐含层节点数
transfer_function=tf.nn.softplus, 隐含层激活函数,默认tf.nn.softplus
optimizer=tf.train.AdamOptimizer(), 优化器,默认为Adam
scale=0.1, 高斯噪声系数,默认为0.1
其中class内的scale参数做成了一个placeholder
参数初始化使用了接下来定义的_initialize_weifghts函数
这里只用了一个隐含层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AdditiveGausssianNoiseAutoencoder(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
1
2
3
4
5
6
7
定义网络结构
输入x创建了一个维度为n_input的placeholder
建立了一个能提取特征的隐含层,先将输入x加入噪声,即self.x + scale * tf.random_normal((n_input, ))
然后用tf.matmul将计入噪声的输入与隐含层的权重w1相乘
并使用tf.add加上隐含层的偏置b1,最后使用self.transfer对结果进行激活函数处理
经过隐含层之后我们需要在输出层进行数据复原、重建操作(即建立reconstruction层)
这里就不需要激活函数了,直接将隐含层的输出self.hidden乘上输出层的权重w2,再加上输出层的权重b2即可
1
2
3
4
5
6
7
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'])
1
2
3
4
5
定义自编码器的损失函数
这里使用平方误差作为cost,即用tf.subtract计算输出(self.reconstruction)与输入(self.x)之差,
再使用tf.pow求差的平方,最后使用tf.reduce_sum求和即可得到平方误差。
定义训练操作作为优化器self.optimizer对损失self.cost进行优化。
最后创建Session,并初始化自编码器的全部模型参数
1
2
3
4
5
6
7
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)
1
2
3
4
5
6
创建初始化函数
先创建一个字典,将w1,b1,w2,b2存入字典中
最后返回
w1要使用之前创建的xavier_init初始化,所以输入 输入节点数和隐含层节点数
而偏置b1只需要使用tf.zeros全部置0即可
对于输出层,没有激活函数直接将w2,b2全初始化为0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
1
2
3
4
定义计算损失及执行一步训练的函数partial_fit
函数里只需让Session执行两个计算图的节点,分别是损失cost和训练过程中的optimizer
输入的feed_dict包括输入数据x,以及噪声的系数scale
函数partial_fit的作用就是用一个batch的数据进行训练并返回当前的损失cost
1
2
3
4
5
6
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
1
2
3
4
5
只求损失cost的函数
就只让Session执行一个计算图节点self.cost
传入的参数和之前的partial_fit一致
这个函数是在自编码器训练完毕后,在测试集上对模型的性能进行评测时会用到的,
它不会像partial_fit那样触发训练操作
1
2
3
4
def calc_total_cost(self, X):
return self.sess.run(
self.cost, feed_dict={self.x: X,
self.scale: self.training_scale})
1
2
定义了transform函数,返回自编码器隐含层的输出结果
提供一个接口来获取抽象后的特征,自编码器的隐含层的最主要的功能就是学习出数据中的高阶特征
1
2
3
4
def transform(self, X):
return self.sess.run(
self.hidden, feed_dict={self.x: X,
self.scale: self.training_scale})
1
2
3
再定义generate函数,它将隐含层的输出结果作为输入,通过之后的重建层将提取到的高阶特征复原为原始数据
这个接口和前面的transform正好将整个自编码器拆分为两部分,这里的generate接口是后半部分
将高阶特征复原为原始数据的步骤
1
2
3
4
5
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})
1
2
3
4
定义reconstruct
它整体运行一遍复原过程
包括提取高阶特征和通过高阶特征提取数据,即包括transform和generate两块
输入是原数据、输出是复原后的数据
1
2
3
4
5
def reconstruct(self, X):
return self.sess.run(
self.reconstruction,
feed_dict={self.x: X,
self.scale: self.training_scale})
1
获取隐含层的权重和偏差
1
2
3
4
5
def getWeights(self):
return self.sess.run(self.weights['w1'])

def getBiases(self):
return self.sess.run(self.weights['b1'])
1
加载MNIST数据集
1
mnist = input_data.read_data_sets('data/', one_hot=True)
1
2
3
4
5
对训练测试数据标准化的函数
标准化让数据变成0均值,标准差为1的分布
方法是先减去平均值,再除以标准差
直接使用sklearn.preprossing的StandardScaler类,现在训练集上进行fit再将这个Scaler用到训练集和测试集上
这里必须要保证训练测试数据都使用完全相同的Scaler,这样才能保证后面模型处理数据的一致性,这就是先在训练集上fit出一个公用的Scaler的原因
1
2
3
4
5
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
1
2
定义一个获取随机block数据的函数:
取一个从0到len(data)-batch_size之间的随机整数 再以这个随机数作为block的起始位置,然后顺序取到一个batch size的数据
1
2
3
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)]
1
2
3
4
5
6
使用之前定义的standard_scale函数对训练集、测试集进行标准化变换

定义参数
epoch 最大训练轮数
batch_size
display_step 每隔几轮显示一次cost
1
2
3
4
5
X_train, X_test = standard_scale(mnist.train.images, mnist.test.images)
n_samples = int(mnist.train.num_examples)
training_epochs = 20
batch_size = 128
display_step = 1
1
2
3
4
5
创建一个AGN自编码器的实例
定义输入节点数n_input为784 隐含层为节点数200
隐含层的激活函数transfer_function为softplus
优化器为Adam,学习速率为0.001
噪声系数scale为0.01
1
2
3
4
5
6
autoencoder = AdditiveGausssianNoiseAutoencoder(
n_input=784,
n_hidden=200,
transfer_function=tf.nn.softplus,
optimizer=tf.train.AdamOptimizer(learning_rate=0.001),
scale=0.01)
1
2
3
4
开始训练过程
在每一轮循环开始时,将平均损失avg_vost设置为0
计算一共需要的batch数,在每一个batch的循环中,partial_fit训练这个batch的数据计算当前的cost
最后将cost整合到avg_cost中。在每一轮迭代后,显示当前迭代数和这一轮的平均cost
1
2
3
4
5
6
7
8
9
10
11
12
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))

print("Total cost: "+ str(autoencoder.calc_total_cost(X_test)))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
最终的输出结果 我这里大约需要2分多钟
Epoch: 0001 cost = 19775.792931818
Epoch: 0002 cost = 11964.003212500
Epoch: 0003 cost = 9655.042776705
Epoch: 0004 cost = 9977.502384091
Epoch: 0005 cost = 9495.442856250
Epoch: 0006 cost = 8875.182430114
Epoch: 0007 cost = 9942.972260227
Epoch: 0008 cost = 8377.960607386
Epoch: 0009 cost = 8419.574784091
Epoch: 0010 cost = 7888.692389773
Epoch: 0011 cost = 8359.316435227
Epoch: 0012 cost = 9017.994824432
Epoch: 0013 cost = 8194.735139773
Epoch: 0014 cost = 8732.054455114
Epoch: 0015 cost = 8273.645841477
Epoch: 0016 cost = 8323.325909091
Epoch: 0017 cost = 8407.965837500
Epoch: 0018 cost = 7300.461956818
Epoch: 0019 cost = 7638.011763636
Epoch: 0020 cost = 8552.155968750
Total cost: 650998.0

TensorFlow实现多层感知器

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
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('data/', one_hot=True)
sess = tf.InteractiveSession()

in_units = 784 # 输入节点
h1_units = 300 # 隐含层输出节点
W1 = tf.Variable(tf.truncated_normal([in_units, h1_units], stddev=0.1))
W2 = tf.Variable(tf.zeros([h1_units, 10]))
b1 = tf.Variable(tf.zeros([h1_units]))
b2 = tf.Variable(tf.zeros([10]))

# 定义输入x的placeholder,dropout的比率为keep_prob(保留节点的概率)
x = tf.placeholder(tf.float32, [None, in_units])
keep_prob = tf.placeholder(tf.float32)

# 隐含层的命名为hidden1
# 激活函数为ReLU
hidden1 = tf.nn.relu(tf.matmul(x, W1) + b1)
hidden1_drop = tf.nn.dropout(hidden1, keep_prob)
y = tf.nn.softmax(tf.matmul(hidden1_drop, W2) + b2)

# 这里使用Adagrad优化,优化算法可以参考下面cs231n
# http://cs231n.github.io/neural-networks-3/
# 这里有非常非常详细的介绍
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)

# 训练,这里keep_prob=0.75指训练时随机保留75%的节点,其余的节点为0
# cs231n提到过使dropout率为0.5最优
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})

# 预测时让keep_prob=1
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
}))
Buy me a coffe. XD