深度学习实例--服装识别(Tensorflow Version)
1. 说明
之前手搓了一个全连接(FC)神经网络, 现在用 TensorFlow 低阶API 重新实现一遍, 在理解了神经网络的工作原理之后,框架对于学习无疑是最好的选择,熟练使用框架能压缩构架模型的时间以及排除了大量难以发现(或者说,你发现了,但是解决不了)的 Bug 。 由于之前手搓的文章,已经解决一些预处理等问题, 所以现在就不会再次提出, 但是为了保证代码版本(我不确定有没有修改过),一切辅助函数都会在最后再次贴出。
2. 思路
使用 TensorFlow 建立模型的过程, 跟手搓其实差不多。依然是:
- 初始化
- 前向传播
由于前向传播的过程决定了反向传播的过程,所以 TensorFlow 依据前向传播来推导出了反向传播, 这不仅大大减少了工作量,也在某种角度上来说,使得大多数人都能够使用和学习机器学习。
按得上面的思路, 就开始实现了, 这里说一下数据的 Shape,X_train.shape == [784, 60000], Y_train.shape == [10, 60000], X_test.shape == [784, 10000], Y_test.shape == [10, 10000]
。
3. 初始化
3.1 Placeholder ——TensorFlow 张量
观察下面代码:
def create_placeholder(nx, ny):
X = tf.placeholder(dtype = tf.float64, shape = [784, None], name = 'X')
Y = tf.placeholder(dtype = tf.float64, shape = [ 10, None], name = 'Y')
return X, Y
上面的函数输入两个参数, 返回两个在 TensorFlow 中叫做 Placeholder (占位符)的张量(Tensor)。 首先要知道在 TensorFlow 比较重要的概念有 Op 以及 Tensor, 其中 Op 表示操作(各种运算), Tensor 就是我们平时理解的用来运算数据(变量或者常量)。 自然 Placeholder 应该归为 Tensor 一类,可以看下面在IPython中的实验(其结果是我们所得理解和接受的):
另外,建议使用在IPython环境下实验,例如:输入tf.placeholder?
即可以显示关于 placeholder 的用法, 不必死记硬背,自然函数说明是英文。
可以发现上面的 Shape = [784, None] ,784 表示输入的特征个数(图片为 28*28),这个自然是固定的; None表示不确定的值, 这里自然是我们的数据的个数。 以这样的方式创建的占位符会自动检查传入的数据的格式为 [784, m] (m>=1,且m为整数)。 因为输入数据的 Shape 的不确定性, 所以 TensorFlow 提供了 Placeholder 来表示输入。
3.2 参数初始化
参数初始化的基本跟手搓的一样:
def initialize_parameters(layers_dims):
parameters = {}
L = len(layers_dims)
for l in range(1, L):
parameters['W' + str(l)] = tf.get_variable(name = 'W' + str(l), shape = [layers_dims[l], layers_dims[l-1]], dtype = tf.float64,
initializer = tf.contrib.layers.xavier_initializer())
parameters['b' + str(l)] = tf.get_variable(name = 'b' + str(l), shape = [layers_dims[l], 1], dtype = tf.float64,
initializer = tf.initializers.zeros())
return parameters
这里仅多了一个知识点就是 tf.get_variable()
的用法,自然可以用上面提到的 IPython 的帮助,这里演示一次,下面是部分截图:
嗯, 参数意外的多, 但是我们在这里能用到却仅有几个。 上面会有很多信息,但是必需要看的是 Docstring(大致告诉了我们,此方法的作用) :用提供的参数返回一个存在的变量或者创建一个新的变量。 那么我们这里便是创建新的变量, 用到的参数还有便是 initializer,这个参数指示了给当前这个变量指定一个初始化器,也就是创建这个变量时,他的值是什么。大多数初识化器在 tf.initializers
中,到官网文档https://tensorflow.google.cn/api_docs/python/tf/initializers ,可以看到各个初始化器的说明。但是上面却用到了 tf.contrib.layers.xavier_initializer()
,我想这些不唯一的用法便是自学的难点,如果难以接受,便到 tf.initializers
找到自己熟悉的参数初始化方法,然后使用他,就像对偏差的一样。
4. 前向传播及计算损失
参数初始化完毕,然后就可以开始前向传播了,由于 TensorFlow 已经封装好了激活函数,所以不必再将线性传播和激活传播分开实现,而是整合到一起,如下:
def forward_propogation(X, parameters):
L = len(parameters) // 2
A_prev = X
for l in range(1, L):
'''
下面的计算可以换成这样,tensorflow 已经实现的运算符重载。 但是部分框架是未实现重载的, 但是又允许这样的运算
不过可能会报错,而这种错误就很难发现出现。所以,需要记住的是,除非官方文档明确说明已实现重载, 否则还是老老实实这样写。
就如 Keras 一样。
tf.matmul(parameters['W' + str(l)], A_prev) + parameters['b' + str(l)]
'''
Z = tf.add( tf.matmul( parameters['W' + str(l)], A_prev), parameters['b' + str(l)] )
A_prev = tf.nn.relu(Z)
ZL = tf.add( tf.matmul(parameters['W' + str(L)], A_prev), parameters['b' + str(L)] )
return ZL
矩阵相乘的用法为 tf.matmul(A, B)
,这里返回的是 ZL, 不是之前的 AL,在给出计算损失之后会给出解释。自然的,前向传播完成后,就可以计算损失,如下:
def compute_cost(ZL, Y):
logits = tf.transpose(ZL)
labels = tf.transpose(Y)
cost = tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits, labels=labels)
cost = tf.reduce_mean(cost)
return cost
这里在计算前,先进行的矩阵转置,即 Shape 为 [None, 10],这是TensorFlow中的格式,并没有什么好说的。然后就跟我们平时计算损失不太一样,以前计算可能是以下实现比较能理解:
AL = tf.nn.softmax(tf.matmul(x,W) + b)
cross_entropy = -tf.reduce_mean(Y*tf.log(AL ))
'''
其实
cost = tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits, labels=labels)
就是
AL = tf.nn.softmax(ZL)
cost = Y*tf.log(AL)
把两步整合成为了一步, 最后tf.reduce_mean()就是和然后计算均值。
'''
还有其他损失的计算也都在 tf.nn
中,可以到官网查阅,你会看到熟悉的。
5. 模型
已经说过Tensorflow 不需要实现反向传播, 所以我们已经可以整合一起,然后训练模型了,下面给出实现,由于代码有一定长度,为了阅读方便,就在注释中讲解:
def model(X_train, Y_train, X_test, Y_test, layers_dims, learning_rate, num_epoch = 5, minibatch_size = 128):
# 获得特征的个数、标签类别数以及
nx = X_train.shape[0]
ny = Y_train.shape[0]
#创建X、Y占位符和参数
X, Y = create_placeholder(nx, ny)
parameters = initialize_parameters(layers_dims)
seed = 0
costs = []
#在这里我们先“完成”了前向传播和损失的计算。
ZL = forward_propogation(X, parameters)
cost = compute_cost(ZL, Y)
"""
tf.train 中有各种优化器, 这里选择了 Adam 优化器,仅修改了学习率, 其他参数使用 Adam 的默认值
用法也是如此, 最后 minimize() 将我们的 cost 传进去, 表示要最小化这个值。 记得我们手搓的时候
也是通过最小化 cost 来实现优化模型的。
"""
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
"""
这个是生成一个初始化器,这里表示的是全局(并不是常识中的全局,而是属于 global 这个集合中的变量,
TensorFlow 中每个变量都属于一个或者多个集合)变量的初始化器。
为什么要弄个初始化器呢? 这跟 TensorFlow 的机制有关, TensorFlow 先要求我们创建一个计算图,然后
根据此图完成计算图任务,这样做的目的是为了减少开销,将所有计算任务集中起来一起计算,减少从 Python
到 C++ 的切换开销。
其实在我们完成初始化和前向传播以及损失的计算和生成 optimizer (上面的优化器)时,我们的计算图就构建
完成了(后面利用 Tensorboard 可视化我们的计算图)。请注意, 按照 TensorFlow 的机制我们仅是创建了一个
图, 没有进行任何计算任务, 也就是变量实际上都没有进行初始化。 所以我们需要在运行图前先初始化变量,
(至于为什么还分集合,等你该懂得的时候,你自然就会懂得了)。
"""
init = tf.global_variables_initializer()
#创建一个会话, 创建了会话之后才会开始调用资源,所以使用后,记得关闭会话以释放资源
with tf.Session() as sess:
#先初始化一下变量
sess.run(init)
for epoch in range(num_epoch):
seed = seed + 1
#已实现的随机小批量算法
minibatches = random_mini_batches(X_train, Y_train, minibatch_size, seed)
epoch_cost = 0
for minibatch in minibatches:
(minibatch_X, minibatch_Y) = minibatch
"""
这里run时,可以以dict 或者 list 的方式传入需要 run 的 Tensor 或者 Op。
由于会对应的返回一个 None 和 数值,我们不需要 None。另外记得 X、Y都为Placeholder
并没有真正的值,所以我们在运行时将小批量传过去,格式如下。
"""
_, minibatch_cost = sess.run([optimizer, cost], feed_dict = {X:minibatch_X, Y:minibatch_Y})
epoch_cost += minibatch_cost
epoch_cost = epoch_cost / len(minibatches)
print(epoch_cost)
costs.append(epoch_cost)
plt.plot(costs)
plt.xticks([])
plt.yticks([])
plt.xlabel('iteration nums')
plt.show()
"""
这里就是对模型的评估, 代码还是比较好理解的。 先得出真正的label以及预测的label
然后以element-wise 的方式判断是否相等,其数据类型为 bool
在计算平均值时,先转到 float 型。
这里参与运算的是, Y (占位符), ZL(前向传播结果)。由于前向传播的过程需要用到
X、Y占位符,所以下面 run() 的时候,传入了数据给X、Y(可以理解成为对X、Y的初始化)。
"""
prediction = tf.equal( tf.argmax(Y), tf.argmax(ZL) )
accuracy = tf.reduce_mean( tf.cast( prediction, float ) )
#在构建了图后, 传入不同的数据,就会有不同的结果。
train_acc = sess.run(accuracy, feed_dict = {X:X_train, Y:Y_train})
print('Train accuracy:%.2f%%' % (train_acc*100))
test_acc = sess.run(accuracy, feed_dict = {X:X_test, Y:Y_test})
print('Train accuracy:%.2f%%' % (test_acc*100))
测试及输出:
tf.reset_default_graph()
layers_dims = [784, 128, 10]
learning_rate = 0.0005
model(X_train, Y_train, X_test, Y_test, layers_dims, learning_rate=learning_rate)
嗯。。可以发现学习的过程居然没有波动呢。。真是可以的。最后给出创建的计算图,有点小。
计算图的生成代码如下:
writer = tf.summary.FileWriter('.')
writer.add_graph(tf.get_default_graph())
这样会在当前目录下生成一个文件,然后在当前目录下运行CMD,输入tensorboard --logdir .
接着会提醒你在浏览器输入 localhost:6006(不确定你们的是不是这样) 就会打开。还有为了防止,计算图变得乱七八糟, tf.reset_default_graph()
就可以重置。
6. 总结
通过上面的例子,要理解的 TensorFlow 概念有:
- 计算图的构建与运算之间的关系
- Op 及 Tensor
- Session
利用 TensorFlow 进行试验的步骤也如下:
- 构建计算图
- 创建Session
- 初始化变量
- 执行计算图
其他方法需要自己去探索, TensorFlow 会将一类相关的方法放在一起,例如前面提到的 tf.nn 和 tf.initializers
。
可能用到的辅助函数:
def load_mnist(path, kind='train'):
import os
import gzip
import numpy as np
"""Load MNIST data from `path`"""
labels_path = os.path.join(path,
'%s-labels-idx1-ubyte.gz'
% kind)
images_path = os.path.join(path,
'%s-images-idx3-ubyte.gz'
% kind)
with gzip.open(labels_path, 'rb') as lbpath:
labels = np.frombuffer(lbpath.read(), dtype=np.uint8,
offset=8)
with gzip.open(images_path, 'rb') as imgpath:
images = np.frombuffer(imgpath.read(), dtype=np.uint8,
offset=16).reshape(len(labels), 784)
images = images / 255.0
images = images.T
labels = labels.reshape(labels.shape[0], 1).T
temp = np.zeros((10, labels.shape[1]))
temp[labels, np.arange(labels.shape[1])] = 1
return images, temp
def random_mini_batches(X, Y, mini_batch_size = 512, seed = 1):
m = X.shape[1]
mini_batches = []
np.random.seed(seed)
permutation = np.random.permutation(m)
shuffled_X = X[:, permutation]
shuffled_Y = Y[:, permutation]
num_complete_minibatches = m // mini_batch_size
for i in range(num_complete_minibatches):
mini_batch_X = shuffled_X[:, i*mini_batch_size : (i+1)*mini_batch_size]
mini_batch_Y = shuffled_Y[:, i*mini_batch_size : (i+1)*mini_batch_size]
mini_batch = (mini_batch_X, mini_batch_Y)
mini_batches.append(mini_batch)
if m % mini_batch_size != 0:
mini_batch_X = shuffled_X[:, num_complete_minibatches*mini_batch_size:]
mini_batch_Y = shuffled_Y[:, num_complete_minibatches*mini_batch_size:]
mini_batch = (mini_batch_X, mini_batch_Y)
mini_batches.append(mini_batch)
return mini_batches
最后, TensorFlow 中已经有其他方法实现了不用手搓一个随机小批量的方法,自己去学习哟,点我传送。
更多推荐
所有评论(0)