深度学习(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=wlrgrad

# 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))

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值