基础架构
张量(Tensor):
张量有多种:
- 零阶张量为 纯量或标量 (scalar) 也就是一个数值. 比如 [1]
- 一阶张量为 向量 (vector), 比如 一维的 [1, 2, 3]
- 二阶张量为 矩阵 (matrix), 比如 二维的 [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
- 以此类推, 还有 三阶 三维的 …
基本步骤
创捷数据
1 | import tensorflow as tf |
搭建模型
1 | Weights = tf.Variable(tf.random_uniform([1], -1.0, 1.0)) |
计算误差
1 | loss = tf.reduce_mean(tf.square(y-y_data)) |
传播误差
1 | optimizer = tf.train.GradientDescentOptimizer(0.5) |
训练
初始化
1
init = tf.global_variables_initializer()
激活Session
1
2
3
4
5
6
7sess = tf.Session()
sess.run(init) # Very important
for step in range(201):
sess.run(train)
if step % 20 == 0:
print(step, sess.run(Weights), sess.run(biases))
Session用法
1 | # method 1 |
Variable 变量
定义变量
1
2
3
4
5
6
7
8
9
10
11
12import tensorflow as tf
state = tf.Variable(0, name='counter')
# 定义常量 one
one = tf.constant(1)
# 定义加法步骤 (注: 此步并没有直接计算)
new_value = tf.add(state, one)
# 将 State 更新成 new_value
update = tf.assign(state, new_value)定义
init
1
init = tf.global_variables_initializer()
激活
init
1
2
3
4
5with tf.Session() as sess:
sess.run(init)
for _ in range(3):
sess.run(update)
print(sess.run(state))
一定要把 sess
的指针指向 state
再进行 print
Placeholder 传入值
placeholder
是 Tensorflow 中的占位符,暂时储存变量.
Tensorflow 如果想要从外部传入data, 那就需要用到 tf.placeholder()
, 然后以这种形式传输数据 sess.run(***, feed_dict={input: **})
.
1 | import tensorflow as tf |
接下来, 传值的工作交给了 sess.run()
, 需要传入的值放在了feed_dict={}
并一一对应每一个 input. placeholder
与 feed_dict={}
是绑定在一起出现的。
1 | with tf.Session() as sess: |
优化器 optimizer
tf.train.Optimizer
tf.train.GradientDescentOptimizer
tf.train.AdadeltaOptimizer
tf.train.AdagradOptimizer
tf.train.AdagradDAOptimizer
tf.train.MomentumOptimizer
tf.train.AdamOptimizer
tf.train.FtrlOptimizer
tf.train.ProximalGradientDescentOptimizer
tf.train.ProximalAdagradOptimizer
tf.train.RMSPropOptimizer
可视化工具 Tensorboard
可视化神经网络结构
同时我们也可以展开看每个layer中的一些具体的结构:
为xs
指定名称为x_in
:1
xs= tf.placeholder(tf.float32, [None, 1],name='x_in')
使用with tf.name_scope('inputs')
可以将xs
和ys
包含进来,形成一个大的图层,图层的名字就是with tf.name_scope()
方法里的参数。1
2
3
4with tf.name_scope('inputs'):
# define placeholder for inputs to network
xs = tf.placeholder(tf.float32, [None, 1])
ys = tf.placeholder(tf.float32, [None, 1])
之后需要使用 tf.summary.FileWriter()
(tf.train.SummaryWriter()
这种方式已经在 tf >= 0.12 版本中摒弃) 将上面‘绘画’出的图保存到一个目录中,以方便后期在浏览器中可以浏览。 这个方法中的第二个参数需要使用sess.graph
, 因此我们需要把这句话放在获取session
的后面。 这里的graph
是将前面定义的框架信息收集起来,然后放在logs/
目录下面。1
2
3sess = tf.Session() # get session
# tf.train.SummaryWriter soon be deprecated, use following
writer = tf.summary.FileWriter("logs/", sess.graph)
最后在你的terminal(终端)中 ,使用以下命令1
tensorboard --logdir=logs
输出的logs
目录不要放在桌面上,最好直接放在磁盘的下级目录。
可视化训练过程
在histograms里面我们还可以看到更多的layers的变化:
这里还有一个events , 在这次练习中我们会把 整个训练过程中的误差值(loss)在event里面显示出来, 甚至你可以显示更多你想要显示的东西.
在 layer 中为 Weights, biases 设置变化图表
在 add_layer()
方法中添加一个参数 n_layer
,用来标识层数, 并且用变量 layer_name
代表其每层的名名称, 代码如下:
层中的Weights
设置变化图, tensorflow中提供了tf.histogram_summary()
方法,用来绘制图片, 第一个参数是图表的名称, 第二个参数是图表要记录的变量
1 | def add_layer(inputs, in_size, out_size, n_layer, activation_function=None): |
修改之后的名称会显示在每个tensorboard中每个图表的上方显示, 如下图所示:
设置loss的变化图
Loss
的变化图和之前设置的方法略有不同. loss是在tesnorBorad 的event下面的, 这是由于我们使用的是tf.scalar_summary()
方法.
1 | with tf.name_scope('loss'): |
给所有训练图合并
接下来, 开始合并打包。 tf.merge_all_summaries()
方法会对我们所有的 summaries
合并到一起. 因此在原有代码片段中添加:
1 | merged = tf.summary.merge_all() |
训练数据
为了较为直观显示训练过程中每个参数的变化,我们每隔上50次就记录一次结果 , 同时我们也应注意, merged 也是需要run 才能发挥作用的,所以在for循环中写下:
1 | if i%50 == 0: |
卷积神经网络CNN
层
tf.truncated_normal(shape, mean, stddev)
:shape
表示生成张量的维度,mean
是均值,stddev
是标准差。这个函数产生正态分布,均值和标准差自己设定。
tf.nn.conv2d(x,W,strides=[1,1,1,1],padding='SAME')
函数是tensoflow里面的二维的卷积函数,x
是图片的所有参数,W
是此卷积层的权重,然后定义步长strides=[1,1,1,1]
值,strides[0]
和strides[3]
的两个1
是默认值,中间两个1
代表padding
时在x
方向运动一步,y
方向运动一步,padding
采用的方式是SAME
。
tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1])
:池化的核函数大小为2x2,因此ksize=[1,2,2,1]
,步长为2,因此strides=[1,2,2,1]
。
Saver 保存读取
保存
保存时, 首先要建立一个 tf.train.Saver()
用来保存, 提取变量. 再创建一个名为my_net
的文件夹, 用这个 saver 来保存变量到这个目录 "my_net/save_net.ckpt"
。1
2
3
4
5
6
7saver = tf.train.Saver()
with tf.Session() as sess:
sess.run(init)
save_path = saver.save(sess, "my_net/save_net.ckpt")
print("Save to path: ", save_path)
提取
提取时, 先建立零时的W
和 b
容器. 找到文件目录, 并用saver.restore()
我们放在这个目录的变量。1
2
3
4
5
6saver = tf.train.Saver()
with tf.Session() as sess:
# 提取变量
saver.restore(sess, "my_net/save_net.ckpt")
print("weights:", sess.run(W))
print("biases:", sess.run(b))
循环神经网络RNN
莫烦老师讲得感觉他自己有些不理解了,略。
自编码 (Autoencoder)
压缩与解压
可以看出图片其实是经过了压缩,再解压的这一道工序. 当压缩的时候, 原有的图片质量被缩减, 解压时用信息量小却包含了所有关键信息的文件恢复出原本的图片。
原来有时神经网络要接受大量的输入信息, 比如输入信息是高清图片时, 输入信息量可能达到上千万, 让神经网络直接从上千万个信息源中学习是一件很吃力的工作. 所以, 何不压缩一下, 提取出原图片中的最具代表性的信息, 缩减输入信息量, 再把缩减过后的信息放进神经网络学习. 这样学习起来就简单轻松了. 可以看出, 从头到尾, 我们只用到了输入数据 X, 并没有用到 X 对应的数据标签, 所以也可以说自编码是一种非监督学习.
编码器 Encoder
这部分也叫作 encoder 编码器. 编码器能得到原数据的精髓, 然后我们只需要再创建一个小的神经网络学习这个精髓的数据,不仅减少了神经网络的负担, 而且同样能达到很好的效果.
这是一个通过自编码整理出来的数据, 他能从原数据中总结出每种类型数据的特征, 如果把这些特征类型都放在一张二维的图片上, 每种类型都已经被很好的用原数据的精髓区分开来. 如果你了解 PCA 主成分分析, 再提取主要特征时, 自编码和它一样,甚至超越了 PCA. 换句话说, 自编码可以像 PCA 一样 给特征属性降维.
解码器 Decoder
解码器在训练的时候是要将精髓信息解压成原始信息, 那么这就提供了一个解压器的作用, 甚至我们可以认为是一个生成器 (类似于GAN). 那做这件事的一种特殊自编码叫做 variational autoencoders.
编程实践
今天的代码,我们会运用两个类型:
第一,是通过Feature的压缩并解压,并将结果与原始数据进行对比,观察处理过后的数据是不是如预期跟原始数据很相像。(这里会用到MNIST数据)
第二,我们只看encoder
压缩的过程,使用它将一个数据集压缩到只有两个Feature时,将数据放入一个二维坐标系内,特征压缩的效果如下:
同样颜色的点,代表分到同一类的数据。
Autoencoder
1 | # Parameter |
MNIST数据,每张图片大小是 28x28 pix,即 784 Features:1
2# Network Parameters
n_input = 784 # MNIST data input (img shape: 28*28)
- 在压缩环节:我们要把这个Features不断压缩,经过第一个隐藏层压缩至256个 Features,再经过第二个隐藏层压缩至128个。
- 在解压环节:我们将128个Features还原至256个,再经过一步还原至784个。
- 在对比环节:比较原始数据与还原后的拥有 784 Features 的数据进行 cost 的对比,根据 cost 来提升我的 Autoencoder 的准确率,下图是两个隐藏层的 weights 和 biases 的定义:
1 | # hidden layer settings |
下面来定义 Encoder 和 Decoder ,使用的 Activation function 是 sigmoid
, 压缩之后的值应该在 [0,1] 这个范围内。在 decoder
过程中,通常使用对应于 encoder
的 Activation function:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# Building the encoder
def encoder(x):
# Encoder Hidden layer with sigmoid activation #1
layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['encoder_h1']),
biases['encoder_b1']))
# Decoder Hidden layer with sigmoid activation #2
layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['encoder_h2']),
biases['encoder_b2']))
return layer_2
# Building the decoder
def decoder(x):
# Encoder Hidden layer with sigmoid activation #1
layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['decoder_h1']),
biases['decoder_b1']))
# Decoder Hidden layer with sigmoid activation #2
layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['decoder_h2']),
biases['decoder_b2']))
return layer_2
来实现 Encoder 和 Decoder 输出的结果:1
2
3
4
5
6
7
8# Construct model
encoder_op = encoder(X) # 128 Features
decoder_op = decoder(encoder_op) # 784 Features
# Prediction
y_pred = decoder_op # After
# Targets (Labels) are the input data.
y_true = X # Before
再通过我们非监督学习进行对照,即对 “原始的有 784 Features 的数据集” 和 “通过 ‘Prediction’ 得出的有 784 Features 的数据集” 进行最小二乘法的计算,并且使 cost 最小化:1
2
3# Define loss and optimizer, minimize the squared error
cost = tf.reduce_mean(tf.pow(y_true - y_pred, 2))
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(cost)
最后,通过 Matplotlib
的 pyplot
模块将结果显示出来, 注意在输出时MNIST数据集经过压缩之后 x 的最大值是1,而非255: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# Launch the graph
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
total_batch = int(mnist.train.num_examples/batch_size)
# Training cycle
for epoch in range(training_epochs):
# Loop over all batches
for i in range(total_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size) # max(x) = 1, min(x) = 0
# Run optimization op (backprop) and cost op (to get loss value)
_, c = sess.run([optimizer, cost], feed_dict={X: batch_xs})
# Display logs per epoch step
if epoch % display_step == 0:
print("Epoch:", '%04d' % (epoch+1),
"cost=", "{:.9f}".format(c))
print("Optimization Finished!")
# # Applying encode and decode over test set
encode_decode = sess.run(
y_pred, feed_dict={X: mnist.test.images[:examples_to_show]})
# Compare original images with their reconstructions
f, a = plt.subplots(2, 10, figsize=(10, 2))
for i in range(examples_to_show):
a[0][i].imshow(np.reshape(mnist.test.images[i], (28, 28)))
a[1][i].imshow(np.reshape(encode_decode[i], (28, 28)))
plt.show()
通过5个 Epoch 的训练,(通常情况下,想要得到好的的效果,我们应进行10 ~ 20个 Epoch 的训练)我们的结果如下:
上面一行是真实数据,下面一行是经过 encoder
和 decoder
之后的数据,如果继续进行训练,效果会更好。
Encoder
在类型二中,我们只显示 encoder
之后的数据, 并画在一个二维直角坐标系内。做法很简单,我们将原有 784 Features 的数据压缩成仅剩 2 Features 的数据:1
2
3
4
5# Parameters
learning_rate = 0.01 # 0.01 this learning rate will be better! Tested
training_epochs = 10 # 10 Epoch 训练
batch_size = 256
display_step = 1
通过四层 Hidden Layers 实现将 784 Features 压缩至 2 Features:1
2
3
4
5# hidden layer settings
n_hidden_1 = 128
n_hidden_2 = 64
n_hidden_3 = 10
n_hidden_4 = 2
Weights 和 biases 也要做相应的变化:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22weights = {
'encoder_h1': tf.Variable(tf.truncated_normal([n_input, n_hidden_1],)),
'encoder_h2': tf.Variable(tf.truncated_normal([n_hidden_1, n_hidden_2],)),
'encoder_h3': tf.Variable(tf.truncated_normal([n_hidden_2, n_hidden_3],)),
'encoder_h4': tf.Variable(tf.truncated_normal([n_hidden_3, n_hidden_4],)),
'decoder_h1': tf.Variable(tf.truncated_normal([n_hidden_4, n_hidden_3],)),
'decoder_h2': tf.Variable(tf.truncated_normal([n_hidden_3, n_hidden_2],)),
'decoder_h3': tf.Variable(tf.truncated_normal([n_hidden_2, n_hidden_1],)),
'decoder_h4': tf.Variable(tf.truncated_normal([n_hidden_1, n_input],)),
}
biases = {
'encoder_b1': tf.Variable(tf.random_normal([n_hidden_1])),
'encoder_b2': tf.Variable(tf.random_normal([n_hidden_2])),
'encoder_b3': tf.Variable(tf.random_normal([n_hidden_3])),
'encoder_b4': tf.Variable(tf.random_normal([n_hidden_4])),
'decoder_b1': tf.Variable(tf.random_normal([n_hidden_3])),
'decoder_b2': tf.Variable(tf.random_normal([n_hidden_2])),
'decoder_b3': tf.Variable(tf.random_normal([n_hidden_1])),
'decoder_b4': tf.Variable(tf.random_normal([n_input])),
}
与类型一类似,创建四层神经网络。(注意:在第四层时,输出量不再是 [0,1] 范围内的数,而是将数据通过默认的 Linear activation function 调整为 (-∞,∞) :1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22def encoder(x):
layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['encoder_h1']),
biases['encoder_b1']))
layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['encoder_h2']),
biases['encoder_b2']))
layer_3 = tf.nn.sigmoid(tf.add(tf.matmul(layer_2, weights['encoder_h3']),
biases['encoder_b3']))
layer_4 = tf.add(tf.matmul(layer_3, weights['encoder_h4']),
biases['encoder_b4'])
return layer_4
def decoder(x):
layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['decoder_h1']),
biases['decoder_b1']))
layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['decoder_h2']),
biases['decoder_b2']))
layer_3 = tf.nn.sigmoid(tf.add(tf.matmul(layer_2, weights['decoder_h3']),
biases['decoder_b3']))
layer_4 = tf.nn.sigmoid(tf.add(tf.matmul(layer_3, weights['decoder_h4']),
biases['decoder_b4']))
return layer_4
在输出图像时,我们只关心 encoder
压缩之后,即 decoder
解压之前的结果:
scope 命名方法
scope 能让你命名变量的时候轻松很多. 同时也会在 reusing variable 代码中常常见到. 讨论下 tensorflow 当中的两种定义 scope 的方式.
tf.name_scope()
在 Tensorflow 当中有两种途径生成变量 variable, 一种是 tf.get_variable()
, 另一种是 tf.Variable()
. 如果在 tf.name_scope()
的框架下使用这两种方式, 结果会如下.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import tensorflow as tf
with tf.name_scope("a_name_scope"):
initializer = tf.constant_initializer(value=1)
var1 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)
var2 = tf.Variable(name='var2', initial_value=[2], dtype=tf.float32)
var21 = tf.Variable(name='var2', initial_value=[2.1], dtype=tf.float32)
var22 = tf.Variable(name='var2', initial_value=[2.2], dtype=tf.float32)
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
print(var1.name) # var1:0
print(sess.run(var1)) # [ 1.]
print(var2.name) # a_name_scope/var2:0
print(sess.run(var2)) # [ 2.]
print(var21.name) # a_name_scope/var2_1:0
print(sess.run(var21)) # [ 2.0999999]
print(var22.name) # a_name_scope/var2_2:0
print(sess.run(var22)) # [ 2.20000005]
可以看出使用 tf.Variable()
定义的时候, 虽然 name
都一样, 但是为了不重复变量名, Tensorflow 输出的变量名并不是一样的. 所以, 本质上 var2
, var21
, var22
并不是一样的变量. 而另一方面, 使用tf.get_variable()
定义的变量不会被tf.name_scope()
当中的名字所影响.
tf.variable_scope()
如果想要达到重复利用变量的效果, 我们就要使用 tf.variable_scope()
, 并搭配 tf.get_variable()
这种方式产生和提取变量. 不像 tf.Variable()
每次都会产生新的变量, tf.get_variable()
如果遇到了同样名字的变量时, 它会单纯的提取这个同样名字的变量(避免产生新变量). 而在重复使用的时候, 一定要在代码中强调 scope.reuse_variables()
, 否则系统将会报错, 以为你只是单纯的不小心重复使用到了一个变量.
1 | with tf.variable_scope("a_variable_scope") as scope: |