TensorFlow搭建神经网络
实验目的
使用TensorFlow搭建一个神经网络。在此次实验中,实现TensorFlow模型有两部分
- 创建计算图
- 运行计算图
实验内容
导入TensorFlow库
import math |
数据集
此次使用的数据集为手语图片,你需要构建一个算法,以便于从语言障碍人士到不懂手语的人进行沟通。
- 训练集:1080张手势图片,每张图片像素为(64×64),手势共有6类,分别表示从0到5的数字(每种有180张图片)
- 测试集:120张手势图片,每张图片像素为(64×64),手势共有6类,分别表示从0到5的数字(每种有20张图片)
下列代码用于加载数据集: # 加载数据集
X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = load_dataset()
更改下面代码中的index并运行代码,可视化数据集中的一个样本。 # 图片样本
index = 0
plt.imshow(X_train_orig[index])
print ("y = " + str(np.squeeze(Y_train_orig[:, index])))
代码运行结果为: y = 5
将数据集平坦化,然后通过对数据矩阵除以255对其进行归一化。并且将标签转换为one-hot编码的向量,运行下面的代码执行此操作。 # 平坦化数据集
X_train_flatten = X_train_orig.reshape(X_train_orig.shape[0], -1).T
X_test_flatten = X_test_orig.reshape(X_test_orig.shape[0], -1).T
# 归一化数据集
X_train = X_train_flatten/255.
X_test = X_test_flatten/255.
# 将标签转换为one-hot编码向量
Y_train = convert_to_one_hot(Y_train_orig, 6)
Y_test = convert_to_one_hot(Y_test_orig, 6)
print ("number of training examples = " + str(X_train.shape[1]))
print ("number of test examples = " + str(X_test.shape[1]))
print ("X_train shape: " + str(X_train.shape))
print ("Y_train shape: " + str(Y_train.shape))
print ("X_test shape: " + str(X_test.shape))
print ("Y_test shape: " + str(Y_test.shape))
代码运行结果为: number of training examples = 1080
number of test examples = 120
X_train shape: (12288, 1080)
Y_train shape: (6, 1080)
X_test shape: (12288, 120)
Y_test shape: (6, 120)
注意:12288=64×64×3。每个图片为64×64像素,3是RGB颜色。 目的:搭建一个高精度识别手势的算法。要做到这一点,需要建立一个TensorFlow模型,最后使用softmax层输出。 模型:LINEAR - > RELU - > LINEAR - > RELU - > LINEAR - > SOFTMAX。
创建placeholders
首先为X和Y创建placeholder,在运行session时传递训练数据。 练习:补充下面的函数,在TensorFlow中创建placeholder。 # GRADED FUNCTION: create_placeholders
def create_placeholders(n_x, n_y):
"""
为session创建placeholder
Arguments:
n_x -- scalar, 图片向量的大小 (num_px * num_px = 64 * 64 * 3 = 12288)
n_y -- scalar, 类别数量 (从0到5的数字, 因此为6)
Returns:
X -- 输入数据集的placeholder, shape:[n_x, None] ,dtype:"float"
Y -- 输出标签的placeholder, shape:[n_y, None] ,dtype:"float"
Tips:
- 创建placeholder时使用None可以灵活地处理placeholder的数量,
因为训练集和测试集的样本数量是不相同的。
"""
### 填写代码 ###
X = None
Y = None
### 完成代码 ###
return X, Y
检验: X, Y = create_placeholders(12288, 6)
print ("X = " + str(X))
print ("Y = " + str(Y))
代码运行结果为: X = Tensor("Placeholder:0", shape=(12288, ?), dtype=float32)
Y = Tensor("Placeholder_1:0", shape=(6, ?), dtype=float32)
初始化参数变量
接下来需要在TensorFlow中初始化参数。 练习:实现下面的功能来初始化tensorflow中的参数。使用Xavier初始化权重,Zero 初始化偏置项。参数的形状如下。给出例子,W1和B1:
- W1 = tf.get_variable("W1", [25,12288], initializer =tf.contrib.layers.xavier_initializer(seed = 1))
- b1 = tf.get_variable("b1", [25,1], initializer = tf.zeros_initializer())
为了确保所有人的输出值都相同,使用seed=1。 # GRADED FUNCTION: initialize_parameters
def initialize_parameters():
"""
初始化神经网络的参数,其形状为:
W1 : [25, 12288]
b1 : [25, 1]
W2 : [12, 25]
b2 : [12, 1]
W3 : [6, 12]
b3 : [6, 1]
Returns:
parameters -- 存储W1, b1, W2, b2, W3, b3张量的字典
"""
tf.set_random_seed(1) # 为了确保所有人的输出值都相同
### 填写代码 ###
W1 = None
b1 = None
W2 = None
b2 = None
W3 = None
b3 = None
### 完成代码 ###
parameters = {"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2,
"W3": W3,
"b3": b3}
return parameters
测试代码: tf.reset_default_graph()
with tf.Session() as sess:
parameters = initialize_parameters()
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
代码运行结果为: W1 = <tf.Variable 'W1:0' shape=(25, 12288) dtype=float32_ref>
b1 = <tf.Variable 'b1:0' shape=(25, 1) dtype=float32_ref>
W2 = <tf.Variable 'W2:0' shape=(12, 25) dtype=float32_ref>
b2 = <tf.Variable 'b2:0' shape=(12, 1) dtype=float32_ref>
此时参数还未进行赋值。
前向传播
现在实现TensorFlow前向传播的模块。该函数将接收参数字典并完成正向传播。可以使用的函数是:
- tf.add(...,...) 矩阵加法
- tf.matmul(...,...) 矩阵乘法
- tf.nn.relu(...) ReLu激活函数
问题:实现神经网络的正向传播。将numpy和TensorFlow进行比较。注意前向传播在z3处停止,因为在TensorFlow中,最后的线性层输出作为损失函数的输入。因此,不需要计算a3. # GRADED FUNCTION: forward_propagation
def forward_propagation(X, parameters):
"""
前向传播模型: LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SOFTMAX
Arguments:
X -- input dataset placeholder, of shape (input size, number of examples)
parameters -- python字典,包含 "W1", "b1", "W2", "b2", "W3", "b3"
其形状由initialize_parameters函数给出
Returns:
Z3 -- 最后一个线性层的输出
"""
# 从"parameters"字典中恢复参数
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']
W3 = parameters['W3']
b3 = parameters['b3']
### 填写代码 ### # Numpy 等式:
Z1 = None # Z1 = np.dot(W1, X) + b1
A1 = None # A1 = relu(Z1)
Z2 = None # Z2 = np.dot(W2, a1) + b2
A2 = None # A2 = relu(Z2)
Z3 = None # Z3 = np.dot(W3,Z2) + b3
### 完成代码 ###
return Z3
代码运行结果为: Z3 = Tensor("add_2:0", shape=(6, ?), dtype=float32)
此时发现前向传播不会输出任何值,在反向传播的过程中可以知道原因。
代价函数
在之前的实验中,代价函数可以根据一行代码给出: tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits = ..., labels = ...))
问题:补充代价函数
- tf.nn.softmax_cross_entropy_with_logits的“logits”和“labels”输入具有的形状应为(样本数,类别数),这里提供已经变形后的Z3和Y
- tf.reduce_mean是对样本进行求和
# GRADED FUNCTION: compute_cost |
测试代码: tf.reset_default_graph()
with tf.Session() as sess:
X, Y = create_placeholders(12288, 6)
parameters = initialize_parameters()
Z3 = forward_propagation(X, parameters)
cost = compute_cost(Z3, Y)
print("cost = " + str(cost))
代码运行结果为: cost = Tensor("Mean:0", shape=(), dtype=float32)
反向传播
使用TensorFlow时,所有反向传播的参数更新都使用1行代码处理。计算了代价函数后,创建一个“optimizer”对象,运行tf.session时,对象必须和代价函数同时运行。当它被调用时,优化给定的方法和学习率的代价函数。例如,使用梯度下降法优化代价函数: optimizer = tf.train.GradientDescentOptimizer(learning_rate = learning_rate).minimize(cost)
为了运行这个对象,需要: _ , c = sess.run([optimizer, cost], feed_dict={X: minibatch_X, Y: minibatch_Y})
在计算图中,从成本到输入,反向地计算,来更新参数值。 注意:在编码时,通常使用“ _ ”作为一次性变量,存储以后不需要使用的值,c为代价的值。
创建模型
将上面的模块融合到一起就是一个神经网络的模型。 练习:创建model,使用上面完成的函数。 def model(X_train, Y_train, X_test, Y_test, learning_rate = 0.0001,
num_epochs = 1500, minibatch_size = 32, print_cost = True):
"""
创建一个三层的TensorFlow神经网络: LINEAR->RELU->LINEAR->RELU->LINEAR->SOFTMAX.
Arguments:
X_train -- 训练集,形状为: (输入特征 = 12288, 训练样本数 = 1080)
Y_train -- 训练集的标签,形状为 (输出标签 = 6, 训练样本数 = 1080)
X_test -- 测试集,形状为 (输入特征 = 12288, 测试样本数 = 120)
Y_test -- 测试集的标签,形状为 (输出标签 = 6, 测试样本数 = 120)
learning_rate -- 学习率
num_epochs -- 迭代次数
minibatch_size -- 小批量的大小
print_cost -- 每迭代100次打印一次代价值
Returns:
parameters -- 模型学习得到的参数,可以直接用于预测。
"""
ops.reset_default_graph()
tf.set_random_seed(1) # 便于产生相同的输出
seed = 3 # 便于产生相同的输出
(n_x, m) = X_train.shape # (n_x: 输入样本特征, m : 训练集样本数)
n_y = Y_train.shape[0] # n_y : 输出标签数
costs = [] # 记录cost
# 创建形状为 (n_x, n_y)的placeholder
### 填写代码 ###
X, Y = None
### 完成代码 ###
# 初始化参数
### 填写代码 ###
parameters = None
### 完成代码 ###
# 前向传播: 创建前向传播计算图
### 填写代码 ###
Z3 = None
### 完成代码 ###
# 代价函数: 在计算图中增加代价函数
### 填写代码 ###
cost = None
### 完成代码 ###
# 反向传播: 使用 AdamOptimizer优化代价函数。
### 填写代码 ###
optimizer = None
### 完成代码 ###
# 初始化所有变量
init = tf.global_variables_initializer()
# 运行session计算TensorFlow图
with tf.Session() as sess:
# 运行初始化
sess.run(init)
# 循环训练
for epoch in range(num_epochs):
epoch_cost = 0. # 定义每次迭代的代价函数
num_minibatches = int(m / minibatch_size) # 训练集选择的小批量的大小
seed = seed + 1
minibatches = random_mini_batches(X_train, Y_train, minibatch_size, seed)
for minibatch in minibatches:
# 选择一个批量
(minibatch_X, minibatch_Y) = minibatch
# 运行session执行 "optimizer"最小化 "cost"
### 填写代码 ###
_ , minibatch_cost = None
### 完成代码 ###
epoch_cost += minibatch_cost / num_minibatches
# 每100次迭代输出一次代价值
if print_cost == True and epoch % 100 == 0:
print ("Cost after epoch %i: %f" % (epoch, epoch_cost))
if print_cost == True and epoch % 5 == 0:
costs.append(epoch_cost)
# 画出代价值随迭代次数变化的图
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per tens)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
# 存储参数变量
parameters = sess.run(parameters)
print ("Parameters have been trained!")
# 计算正确率
correct_prediction = tf.equal(tf.argmax(Z3), tf.argmax(Y))
# 计算测试集的正确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print ("Train Accuracy:", accuracy.eval({X: X_train, Y: Y_train}))
print ("Test Accuracy:", accuracy.eval({X: X_test, Y: Y_test}))
return parameters
测试代码: parameters = model(X_train, Y_train, X_test, Y_test)
代码运行结果: Train Accuracy: 0.999074
Test Accuracy: 0.725
- 模型看起来可以很好地训练数据集,但是,由于训练集和测试集的准确率之间差异过大,可以尝试添加L2或Dropout正则化来减少过拟合。
- 将Session看作是一个训练模型的代码块,每次都在小批量的训练数据上运行会话训练参数。训练过1500次之后可以得到很好的参数。
测试自己的图片
将自己想要测试的图片添加到“images”文件夹中,再运行下列代码: import scipy
from PIL import Image
from scipy import ndimage
### 填写代码 ### (PUT YOUR IMAGE NAME)
my_image = "thumbs_up.jpg"
### 完成代码 ###
fname = "images/" + my_image
image = np.array(ndimage.imread(fname, flatten=False))
my_image = scipy.misc.imresize(image, size=(64,64)).reshape((1, 64*64*3)).T
my_image_prediction = predict(my_image, parameters)
plt.imshow(image)
print("Your algorithm predicts: y = " + str(np.squeeze(my_image_prediction)))
总结:
TensorFlow是用于深度学习的编程框架
TensorFlow中的两个主要对象类是张量和操作符。
在TensorFlow中编码时,必须采取以下步骤:
- 创建一个包含张量(变量,占位符...)和操作(tf.matmul,tf.add,...)的图形
- 创建一个会话
- 初始化会话
- 运行会话以执行图形
可以多次执行计算图,如本实验中的model()
在“优化器”对象上运行会话时,会自动完成反向传播和优化