深度学习(11)TensorFlow基础操作七: 向前传播(张量)实战
What we have learned
- create tensor
- indexing and slices
- reshape and broadcasting
- math operations
Recap - o u t = r e l u { r e l u { r e l u [ X @ W 1 + b 1 ] @ W 2 + b 2 } @ W 3 + b 3 } out=relu\{relu\{relu[X@W_1+b_1]@W_2+b_2\}@W_3+b_3\} out=relu{relu{relu[X@W1+b1]@W2+b2}@W3+b3}
- p r e d = a r g m a x ( o u t ) pred=argmax(out) pred=argmax(out)
- l o s s = M S E ( o u t , l a b e l ) loss=MSE(out,label) loss=MSE(out,label)
-
m
i
n
i
m
i
z
e
l
o
s
s
minimize loss
minimizeloss
- [ W 1 ′ , b 1 ′ , W 2 ′ , b 2 ′ , W 3 ′ , b 3 ′ ] [W_1',b_1',W_2',b_2',W_3',b_3'] [W1′,b1′,W2′,b2′,W3′,b3′]
1. 导包
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets
2. 加载数据集
其中x.shape=[60k, 28, 28]; y.shap=[60k];
# x: [60k, 28, 28],
# y: [60k]
(x, y), _ = datasets.mnist.load_data()
3. 转换数据类型
将x的数据类型转换为Tensor,一般常用tf.float32;
将y的数据类型转换为Tensor,y中数据为整型,所以使用tf.int32;
# 转换数据类型
x = tf.convert_to_tensor(x, dtype=tf.float32)
y = tf.convert_to_tensor(y, dtype=tf.int32)
4. 查看x.shape, y.shape, x.dtype, y.dtype
# 查看x.shape, y.shape, x.dtype, y.dtype
print(x.shape, y.shape, x.dtype, y.dtype)
结果如下:
5. 查看x和y的范围
# 查看x和y的范围,即x和y的最大值和最小值
print(tf.reduce_min(x), tf.reduce_max(x))
print(tf.reduce_min(y), tf.reduce_max(y))
6. 将一些无关信息屏蔽掉
例如:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
[‘TF_CPP_MIN_LOG_LEVEL’] = ‘0’
就是打印全部信息,如果[‘TF_CPP_MIN_LOG_LEVEL’] = ‘2’
就是屏蔽这些信息;
7. 将x中的数值设置在[0, 1]之间
将x除以255.即可,这样x的范围就被设置在[0, 1]之间;
# x: [0~255] => [0~1.]
x = tf.convert_to_tensor(x, dtype=tf.float32) / 255.
y = tf.convert_to_tensor(y, dtype=tf.int32)
y在后面的代码中会做one-hot处理;
8. 创建数据集
创建数据集的好处在于可以每次取一个batch,而原数据集每次只能取一个数据;
# 创建数据集
train_db = tf.data.Dataset.from_tensor_slices((x, y)).batch(128)
9. 创建迭代器
# 创建迭代器
train_iter = iter(train_db)
sample = next(train_iter)
一个sample就代表了迭代一次,也就是一个batch的数据;
查看batch及sample[n]
# 查看sample[n].shape
print('batch:', sample[0].shape, sample[1].shape)
结果如下:
sample[0]
代表x的数据,一个batch里有128张28×28的照片,所以sample[0].shape=[128, 28, 28];
sample[1]
代表y的数据,一个batch里有128个整型数据,这些数据代表了每张图片的标签,也就是[0~9],所以sample[1].shape=[128,]
10. 创建权值
创建w1和b1并初始化
w1一般按照裁剪过的正态分布来初始化; b1一般初始化为0;
初始化的过程要注意维度设置,w: [dim_in, dim_out], b: [dim_out];
# 创建权值
# [b, 784] => [b, 256] => [b, 128] => [b, 10]
# w: [dim_in, dim_out], b: [dim_out]
w1 = tf.random.truncated_normal([784, 256])
b1 = tf.zeros([256])
w2, b2, w3, b3同理;
w2 = tf.random.truncated_normal([256, 128])
b2 = tf.zeros([128])
w3 = tf.random.truncated_normal([128, 10])
b3 = tf.zeros([10])
11. 前向运算
for(x, y) in train_db:
# x: [128, 28, 28]
# y: [128]
# 我们需要一个维度变换的操作才能将x.shape由[b, 28, 28]转换为[b, 28*28]
x = tf.reshape(x, [-1, 28*28])
# x: [b, 28*28]
# h1 = x@w1 + b1
# [b, 784]@[784, 256] + [256] => [b, 256] + [256] => [b, 256] + [b, 256]
# b会自动做broadcasting操作,当然如过想要手动也可以按照如下操作:
# h1 = x@w1 + tf.broadcast_to(b1, [x.shape[0], 256])
h1 = x@w1 + b1
# [b, 256] => [b, 128]
h2 = h1@w2 + b2
# [b, 128] => [b, 10]
out = h2@w3 + b3
注: 这里的x就是刚才设置的一个batch的数据;
12. 添加激活函数
使其非线性化;
h1 = x@w1 + b1
h1 = tf.nn.relu(h1)
# [b, 256] => [b, 128]
h2 = h1@w2 + b2
h2 = tf.nn.relu(h2)
# [b, 128] => [b, 10]
out = h2@w3 + b3
13. 计算误差
(1) 将y进行one-hot编码
# 将y进行one-hot编码
y_onehot = tf.one_hot(y, depth=10)
depth=10
代表一共有10类,即[0~9];
注: 将y进行one-hot编码这一步在数据创建时或者在这里都行,只要在计算误差之前操作就行;
(2) 计算均方差
# mse = mean(sum(y-out)^2)
# [b, 10]
loss = tf.square(y_onehot - out)
(3) 计算误差均值
得到一个Scalar(标量)
# mean: scalar
loss = tf.reduce_mean(loss)
14. 设置自动求导功能
将所有误差计算的部分全部放入自动求导的功能内:
with tf.GradientTape() as tape:
# x: [b, 28*28]
# h1 = x@w1 + b1
# [b, 784]@[784, 256] + [256] => [b, 256] + [256] => [b, 256] + [b, 256]
# b会自动做broadcasting操作,当然如过想要手动也可以按照如下操作:
# h1 = x@w1 + tf.broadcast_to(b1, [x.shape[0], 256])
h1 = x@w1 + b1
h1 = tf.nn.relu(h1)
# [b, 256] => [b, 128]
h2 = h1@w2 + b2
h2 = tf.nn.relu(h2)
# [b, 128] => [b, 10]
out = h2@w3 + b3
# compute loss
# out: [b, 10]
# y: [b]
# 将y进行one-hot编码
y_onehot = tf.one_hot(y, depth=10)
# mse = mean(sum(y-out)^2)
# [b, 10]
loss = tf.square(y_onehot - out)
# mean: scalar
loss = tf.reduce_mean(loss)
15. 求解梯度
# compute gradients
grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
16. 设置学习率
即leaning rate,lr
lr = 1e-3
1e-3
表示10^(-3),即0.001;
17. 向前传播
w ′ = w − l r ∗ g r a d w'=w-lr*grad w′=w−lr∗grad
# w1 = w1 - lr * w1_grads
w1 = w1 - lr * grads[0]
b1 = b1 - lr * grads[1]
w2 = w2 - lr * grads[2]
b2 = b2 - lr * grads[3]
w3 = w3 - lr * grads[4]
b3 = b3 - lr * grads[5]
这里grads
是一个列表,所以grads[0]=w1, grads[1]=b1, …;
18. 打印步数
由:
for(x, y) in train_db:
变为:
for step, (x, y) in enumerate(train_db):
这样每一步都会打印当前步数;
19. 每100步打印步数和loss
if step % 100 == 0:
print(step, 'loss:', float(loss))
20. 运行程序
发现如下报错:
打印grads:
print(grads)
发现全部为None类型:
原因: tf.GradientTape()
默认只会跟踪类型为tf.Variable的数据,但是我们在创建权重的时候,w1, b1, w2, b2, w3, b3为Tensor类型的数据,所以我们需要使用tf.Variable()
函数将这些权重进行封装:
# 创建权值
# [b, 784] => [b, 256] => [b, 128] => [b, 10]
# w: [dim_in, dim_out], b: [dim_out]
w1 = tf.Variable(tf.random.truncated_normal([784, 256]))
b1 = tf.Variable(tf.zeros([256]))
w2 = tf.Variable(tf.random.truncated_normal([256, 128]))
b2 = tf.Variable(tf.zeros([128]))
w3 = tf.Variable(tf.random.truncated_normal([128, 10]))
b3 = tf.Variable(tf.zeros([10]))
这样,tf.Variable()
会自动跟踪梯度信息;
再次运行,还是出现同样的错误。
原因: w1 = w1 - lr * grads[0]
中后面的w1是刚才已经改好的Variable类型的数据,而前边的w1却还是Tensor类型的数据,前后两个w1是两个不同的对象,这样在下一次计算的时候就会出错。
解决办法: 使用原地更新的函数assign_sub()
:
w1.assign_sub(lr * grads[0])
b1.assign_sub(lr * grads[1])
w2.assign_sub(lr * grads[2])
b2.assign_sub(lr * grads[3])
w3.assign_sub(lr * grads[4])
b3.assign_sub(lr * grads[5])
其中assign_sub就是w - lrgrads[0]; assign_add就是w + lrgrads[0];
这样就会使得数据类型保持不变;
验证: 打印b3的数据类型
print(isinstance(b3, tf.Variable))
print(isinstance(b3, tf.Tensor))
运行结果如下:
这说明原地更新的方法成功了,b3以及其它权重已经是Variable类型了。
21. 再次运行
运行结果如下:
可以看到,100轮迭代过后loss值为nan,这是梯度爆炸现象。
解决: 在初始化权重的时候,需要初始化在一个较好的范围内。原来的truncated_normal()
满足均值为0,方差为1的正态分布,我们需要将其方差变小,在truncated_normal()
里设置参数stddev=0.1:
# 创建权值
# [b, 784] => [b, 256] => [b, 128] => [b, 10]
# w: [dim_in, dim_out], b: [dim_out]
w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))
b1 = tf.Variable(tf.zeros([256]))
w2 = tf.Variable(tf.random.truncated_normal([256, 128], stddev=0.1))
b2 = tf.Variable(tf.zeros([128]))
w3 = tf.Variable(tf.random.truncated_normal([128, 10], stddev=0.1))
b3 = tf.Variable(tf.zeros([10]))
运行程序,结果如下:
可以看到,梯度爆炸问题得到了非常明显的改善。所以说,在深度学习中,每一个参数对于整个结果影响是非常大的。
从运行结果可以看出,loss在不断下降,完成了对60k张图片的一次完整的迭代,如果想要多迭代几次,可以再添加一个epoch循环:
for epoch in range(10): # iterate db for 10
for step, (x, y) in enumerate(train_db):
…
if step % 100 == 0:
print(epoch, step, 'loss:', float(loss))
运行结果如下:
从这两次运行结果可以看出,多轮epoch迭代要比单次迭代的效果好很多。
参考文献:
[1] 龙良曲:《深度学习与TensorFlow2入门实战》
附录: 完整代码
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# x: [60k, 28, 28],
# y: [60k]
(x, y), _ = datasets.mnist.load_data()
# 转换数据类型
# x: [0~255] => [0~1.]
x = tf.convert_to_tensor(x, dtype=tf.float32) / 255.
y = tf.convert_to_tensor(y, dtype=tf.int32)
# 查看x.shape, y.shape, x.dtype, y.dtype
print(x.shape, y.shape, x.dtype, y.dtype)
# 查看x和y的范围,即x和y的最大值和最小值
print(tf.reduce_min(x), tf.reduce_max(x))
print(tf.reduce_min(y), tf.reduce_max(y))
# 创建数据集
train_db = tf.data.Dataset.from_tensor_slices((x, y)).batch(128)
# 创建迭代器
train_iter = iter(train_db)
sample = next(train_iter)
# 查看sample[n].shape
print('batch:', sample[0].shape, sample[1].shape)
# 创建权值
# [b, 784] => [b, 256] => [b, 128] => [b, 10]
# w: [dim_in, dim_out], b: [dim_out]
w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))
b1 = tf.Variable(tf.zeros([256]))
w2 = tf.Variable(tf.random.truncated_normal([256, 128], stddev=0.1))
b2 = tf.Variable(tf.zeros([128]))
w3 = tf.Variable(tf.random.truncated_normal([128, 10], stddev=0.1))
b3 = tf.Variable(tf.zeros([10]))
lr = 1e-3
for epoch in range(10): # iterate db for 10
for step, (x, y) in enumerate(train_db): # for every batch
# x: [128, 28, 28]
# y: [128]
# 我们需要一个维度变换的操作才能将x.shape由[b, 28, 28]转换为[b, 28*28]
x = tf.reshape(x, [-1, 28*28])
with tf.GradientTape() as tape: # tf.Variable
# x: [b, 28*28]
# h1 = x@w1 + b1
# [b, 784]@[784, 256] + [256] => [b, 256] + [256] => [b, 256] + [b, 256]
# b会自动做broadcasting操作,当然如过想要手动也可以按照如下操作:
# h1 = x@w1 + tf.broadcast_to(b1, [x.shape[0], 256])
h1 = x@w1 + b1
h1 = tf.nn.relu(h1)
# [b, 256] => [b, 128]
h2 = h1@w2 + b2
h2 = tf.nn.relu(h2)
# [b, 128] => [b, 10]
out = h2@w3 + b3
# compute loss
# out: [b, 10]
# y: [b]
# 将y进行one-hot编码
y_onehot = tf.one_hot(y, depth=10)
# mse = mean(sum(y-out)^2)
# [b, 10]
loss = tf.square(y_onehot - out)
# mean: scalar
loss = tf.reduce_mean(loss)
# compute gradients
grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
# print(grads)
# w1 = w1 - lr * w1_grads
w1.assign_sub(lr * grads[0])
# w1 = w1 - lr * grads[0]
b1.assign_sub(lr * grads[1])
# b1 = b1 - lr * grads[1]
w2.assign_sub(lr * grads[2])
# w2 = w2 - lr * grads[2]
b2.assign_sub(lr * grads[3])
# b2 = b2 - lr * grads[3]
w3.assign_sub(lr * grads[4])
# w3 = w3 - lr * grads[4]
b3.assign_sub(lr * grads[5])
# b3 = b3 - lr * grads[5]
# print(isinstance(b3, tf.Variable))
# print(isinstance(b3, tf.Tensor))
if step % 100 == 0:
print(epoch, step, 'loss:', float(loss))