TensorFlow深度学习(三)——张量基础

本文是书籍《TensorFlow深度学习》的学习笔记之一

数据类型

TF中的数据类型分为标量Scalar、向量Vector、矩阵Matrix、张量Tensor,0维的张量就是标量,1维的张量就是向量,2维的张量就是矩阵,大于等于3维的张量没有名称,统一叫做张量。

a = tf.constant(1.2)  # 创建标量
b = tf.constant([1, 2., 3.3])  # 创建向量
c = tf.constant([[1, 2], [3, 4]])  # 创建矩阵
x = tf.constant([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])  # 创建张量,3维,2*2*2
str_ = tf.constant('hello,deep learning')  # 字符串
bool_ = tf.constant(True)

数值精度

tf.constant(12345678, dtype=tf.int16)
a = tf.constant([-1, 0, 1, 2])
tf.cast(a, tf.bool)

待优化张量

TF专门增加了一种专门的数据类型来支持梯度信息的记录,tf.Variable。如神经网络的输入X,设置为普通张量即可;如果是网络层的参数W和b,需要tf.Variable包装来跟踪梯度等相关信息。如
a = tf.Variable([[1,2],[3,4]])

创建张量

  • 从列表对象中创建:tf.convert_to_tensor([1,2.])
  • 从数组对象中创建:tf.convert_to_tensor(np.array([[1,2.],[3,4]]))
  • 创建全0/1的向量、矩阵:
tf.zeros([1])
a = tf.ones([2,2])
tf.ones_like(a)
  • 创建自定义张量:tf.fill([2,2],99),创建2行2列,元素全为99的矩阵
  • 创建已知分布的张量:tf.random.normal([2,2]),创建标准正态分布的张量,tf.random.uniform([2,2],maxval=10),创建采样自[0,10]均匀分布的2*2矩阵
  • 创建序列:tf.range(10),创建0~10,步长为1的整型序列
    tf.range(1,10,delta=2),创建1~10,步长为2的整型序列:[1,3,5,7,9]

切片

通过start:end:step切片可以方便地提取一段数据,step为步长

x = tf.random.normal([4,32,32,3])
x[1:3]#读取第二三张图片
x[0,::]#读取第一张图片
x[1,9,21]#读第2张图片,第10行,第3列,第二个通道的数据

在这里插入图片描述
step=-1时,表示逆序读取,

x=tf.range(9)
x[8:0:-1]#从8读取到0,逆序,不包含0
x[::-1]#逆序全部元素

维度变换

通过维度变换可以将数据任意地切换形式,满足不同场合的运算需求。

改变视图

使用reshape,把向量改成了4维张量,可以理解为2张图片,每张图片4行4列,每个点有3个RGB通道,也可以理解为2张图片,图片的特征是443。

>>> x=tf.range(96)
>>> x=tf.reshape(x,[2,4,4,3])#改变x的视图,获得4D张量
>>> x
<tf.Tensor: shape=(2, 4, 4, 3), dtype=int32, numpy=
array([[[[ 0,  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, 28, 29],
         [30, 31, 32],
         [33, 34, 35]],

        [[36, 37, 38],
         [39, 40, 41],
         [42, 43, 44],
         [45, 46, 47]]],


       [[[48, 49, 50],
         [51, 52, 53],
         [54, 55, 56],
         [57, 58, 59]],

        [[60, 61, 62],
         [63, 64, 65],
         [66, 67, 68],
         [69, 70, 71]],

        [[72, 73, 74],
         [75, 76, 77],
         [78, 79, 80],
         [81, 82, 83]],

        [[84, 85, 86],
         [87, 88, 89],
         [90, 91, 92],
         [93, 94, 95]]]])>

>>> x.ndim
4
>>> x.shape
TensorShape([2, 4, 4, 3])
>>> tf.reshape(x,[3,-1])
# -1表示根据张量总元素不变来推导,例如-1处应填(2*4*4*3/3)
<tf.Tensor: shape=(3, 32), dtype=int32, numpy=
array([[ 0,  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, 28, 29, 30, 31],
       [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
        48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63],
       [64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
        80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95]])>

>>> tf.reshape(x,[2,-1,3])
<tf.Tensor: shape=(2, 16, 3), dtype=int32, numpy=
array([[[ 0,  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, 28, 29],
        [30, 31, 32],
        [33, 34, 35],
        [36, 37, 38],
        [39, 40, 41],
        [42, 43, 44],
        [45, 46, 47]],

       [[48, 49, 50],
        [51, 52, 53],
        [54, 55, 56],
        [57, 58, 59],
        [60, 61, 62],
        [63, 64, 65],
        [66, 67, 68],
        [69, 70, 71],
        [72, 73, 74],
        [75, 76, 77],
        [78, 79, 80],
        [81, 82, 83],
        [84, 85, 86],
        [87, 88, 89],
        [90, 91, 92],
        [93, 94, 95]]])>

无论reshape怎么变,张量的存储顺序都没变,还是按照0,1,2……95的序列保存的。

增删维度

>>> x = tf.random.uniform([28,28],maxval=10,dtype=tf.int32)
>>> x
<tf.Tensor: shape=(28, 28), dtype=int32, numpy=
array([[4, 0, 4, 9, 3, 2, 5, 4, 7, 2, 3, 8, 3, 2, 2, 5, 3, 5, 9, 1, 8, 7,
        7, 1, 5, 6, 0, 5],
       [1, 2, 4, 7, 9, 6, 8, 1, 5, 8, 9, 8, 0, 9, 7, 4, 4, 6, 1, 1, 6, 6,
        3, 0, 4, 3, 2, 0],
       [4, 0, 0, 2, 8, 8, 6, 1, 9, 2, 7, 8, 8, 5, 9, 6, 1, 8, 5, 8, 8, 2,
        9, 8, 0, 5, 4, 7],
		……
		……
>>> tf.expand_dims(x,axis=2)#在指定的axis轴钱插入一个新的维度
<tf.Tensor: shape=(28, 28, 1), dtype=int32, numpy=
array([[[4],
        [0],
        [4],
        [9],
        [3],
        [2],
        [5],
        [4],
        [7],
        [2],
        [3],
        [8],
        [3],
        [2],
        [2],
        [5],
        [3],
        [5],
        [9],
        [1],
        [8],
        [7],
        [7],
        [1],
        [5],
        [6],
        [0],
        [5]],
		……
x=tf.squeeze(x,axis=2)#删除图片通道数
#交换维度
x=tf.random.normal([2,32,32,3])
tf.transpose(x,perm=[0,3,1,2])#重排索引号,变为2,3,32,32

复制数据


b = tf.constant([1,2]) # 创建向量b
b = tf.expand_dims(b, axis=0) # 插入新维度,变成矩阵
b
Out[80]:
<tf.Tensor: id=645, shape=(1, 2), dtype=int32, numpy=array([[1, 2]])>
在Batch 维度上复制数据1 份,实现如下:
In [81]: b = tf.tile(b, multiples=[2,1]) # 样本维度上复制一份
Out[81]:
<tf.Tensor: id=648, shape=(2, 2), dtype=int32, numpy=
array([[1, 2],
[1, 2]])>

broadcasting

broadcasting是轻量级的复制操作。通过 tf.broadcast_to(x, new_shape)函数可以显式地执行自动扩展功能,将现有shape 扩张为new_shape

In [87]:
A = tf.random.normal([32,1]) # 创建矩阵
tf.broadcast_to(A, [2,32,32,3]) # 扩展为4D 张量
Out[87]:
<tf.Tensor: id=13, shape=(2, 32, 32, 3), dtype=float32, numpy=
array([[[[-1.7571245 , -1.7571245 , -1.7571245 ],
[ 1.580159 , 1.580159 , 1.580159 ],
[-1.5324328 , -1.5324328 , -1.5324328 ],...

数学运算

a = tf.range(5)
b = tf.constant(2)
a//b # 整除运算
a%b # 余除运算
x = tf.range(4)
tf.pow(x,3) # 乘方运算
x**2 # 乘方运算符
x**(0.5) # 平方根
In [96]: x = tf.constant([1.,2.,3.])
2**x # 指数运算
Out[96]:
<tf.Tensor: id=179, shape=(3,), dtype=float32, numpy=array([2., 4., 8.],
dtype=float32)>
In [98]: x=tf.exp(3.)
tf.math.log(x) # 对数运算
Out[98]:
<tf.Tensor: id=186, shape=(), dtype=float32, numpy=3.0>
#如果希望计算其它底数的对数,可以根据对数的换底公式:
In [99]: x = tf.constant([1.,2.])
x = 10**x
tf.math.log(x)/tf.math.log(10.) # 换底公式
Out[99]:
<tf.Tensor: id=6, shape=(2,), dtype=float32, numpy=array([1., 2.],
dtype=float32)>

In [100]:
a = tf.random.normal([4,3,28,32])
b = tf.random.normal([4,3,32,2])
a@b # 批量形式的矩阵相乘
Out[100]:
<tf.Tensor: id=236, shape=(4, 3, 28, 2), dtype=float32, numpy=
array([[[[-1.66706240e+00, -8.32602978e+00],
[ 9.83304405e+00, 8.15909767e+00],
[ 6.31014729e+00, 9.26124632e-01],…
得到shape 为[4,3,28,2]的结果。

前向传播实战

到现在为止,我们已经介绍了如何创建张量、对张量进行索引切片、维度变换和常见的数学运算等操作。最后我们将利用已经学到的知识去完成三层神经网络的实现:out = 𝑅𝑒𝐿𝑈{𝑅𝑒𝐿𝑈{𝑅𝑒𝐿𝑈[𝑿@𝑾1 + 𝒃1]@𝑾2 + 𝒃2}@𝑾 + 𝒃 }



import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras.datasets as datasets

plt.rcParams['font.size'] = 16
plt.rcParams['font.family'] = ['STKaiti']
plt.rcParams['axes.unicode_minus'] = False


def load_data():
    # 加载 MNIST 数据集
    (x, y), (x_val, y_val) = datasets.mnist.load_data()
    # 转换为浮点张量, 并缩放到-1~1
    x = tf.convert_to_tensor(x, dtype=tf.float32) / 255.
    # 转换为整形张量
    y = tf.convert_to_tensor(y, dtype=tf.int32)
    # one-hot 编码
    y = tf.one_hot(y, depth=10)

    # 改变视图, [b, 28, 28] => [b, 28*28]
    x = tf.reshape(x, (-1, 28 * 28))

    # 构建数据集对象
    train_dataset = tf.data.Dataset.from_tensor_slices((x, y))
    # 批量训练
    train_dataset = train_dataset.batch(200)
    return train_dataset


def init_paramaters():
    # 每层的张量都需要被优化,故使用 Variable 类型,并使用截断的正太分布初始化权值张量
    # 偏置向量初始化为 0 即可
    # 第一层的参数
    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]))
    return w1, b1, w2, b2, w3, b3


def train_epoch(epoch, train_dataset, w1, b1, w2, b2, w3, b3, lr=0.001):
    for step, (x, y) in enumerate(train_dataset):
        with tf.GradientTape() as tape:
            # 第一层计算, [b, 784]@[784, 256] + [256] => [b, 256] + [256] => [b,256] + [b, 256]
            h1 = x @ w1 + tf.broadcast_to(b1, (x.shape[0], 256))
            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

            # 计算网络输出与标签之间的均方差, mse = mean(sum(y-out)^2)
            # [b, 10]
            loss = tf.square(y - out)
            # 误差标量, mean: scalar
            loss = tf.reduce_mean(loss)

            # 自动梯度,需要求梯度的张量有[w1, b1, w2, b2, w3, b3]
            grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])

        # 梯度更新, 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])

        if step % 100 == 0:
            print(epoch, step, 'loss:', loss.numpy())

    return loss.numpy()


def train(epochs):
    losses = []
    train_dataset = load_data()
    w1, b1, w2, b2, w3, b3 = init_paramaters()
    for epoch in range(epochs):
        loss = train_epoch(epoch, train_dataset, w1, b1, w2, b2, w3, b3, lr=0.001)
        losses.append(loss)

    x = [i for i in range(0, epochs)]
    # 绘制曲线
    plt.plot(x, losses, color='blue', marker='s', label='训练')
    plt.xlabel('Epoch')
    plt.ylabel('MSE')
    plt.legend()
    plt.savefig('MNIST数据集的前向传播训练误差曲线.png')
    plt.close()


if __name__ == '__main__':
    train(epochs=20)

总结

(todo)前一阵的前向传播如何做的?本章如何做的?细节内容?

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值