当前位置:首页 » 《休闲阅读》 » 正文

TensorFlow2 入门指南 | 05 TensorFlow2 基本操作总结_不积跬步,无以至千里!

8 人参与  2021年05月03日 16:00  分类 : 《休闲阅读》  评论

点击全文阅读


前言:

本专栏在保证内容完整性的基础上,力求简洁,旨在让初学者能够更快地、高效地入门TensorFlow2 深度学习框架。如果觉得本专栏对您有帮助的话,可以给一个小小的三连,各位的支持将是我创作的最大动力!

系列文章汇总:TensorFlow2 入门指南
Github项目地址:https://github.com/Keyird/TensorFlow2-for-beginner

文章目录

    • 一、数据类型
      • (1) 字符串型
      • (2) 布尔类型
      • (3) 整型、浮点型
      • (4) 张量转换
      • (5) 数值类型
      • (6) 待训练张量
    • 二、创建张量Tensor
      • (1) 创建全0、全1张量
      • (2) 创建自定义数值张量
      • (3) 创建已知分布的张量
      • (4) 创建序列
      • (5) 从 Numpy、List 对象创建张量
    • 三、索引与切片
      • (1) 索引
      • (2) 切片
    • 四、维度变换
    • 五、广播机制
    • 六、数学运算
    • 七、前向传播实战
      • (1) 导入相关库:
      • (2) 数据集准备:
      • (3) 初始化变量
      • (4) 迭代训练


TensorFlow2 是一个面向于深度学习算法的科学计算库,内部数据均以张量形式保存,所有的运算操作也都是基于张量进行的。复杂的神经网络算法本质上就是各种张量相乘、相加等基本运算操作的组合,所以在学习神经网络算法之前,我们先来了解一下 TensorFlow2 张量的基础操作方法~

在这里插入图片描述


一、数据类型

(1) 字符串型

通过tf.constant()即可创建字符串类型的张量:

s = tf.constant('Hello, TensoeFlow2!')

在 tf.strings 模块中,提供了常见的字符串型的工具函数,如拼接 join(),长度 length(),切分 split(),转换小写lower() 等等:

tf.strings.lower(s)

深度学习算法主要还是以数值类型张量运算为主,字符串类型的数据使用频率较低,我们不做过多阐述。

(2) 布尔类型

布尔类型张量的创建方式如下:

 a = tf.constant(True)

传入布尔类型的向量:

a = tf.constant([True, False])

需要注意的是,TensorFlow 的布尔类型和 Python 语言的布尔类型并不对等,不能通用。

(3) 整型、浮点型

创建一个数值类型为整型的张量:

a = tf.constant(12, dtype=tf.int16)

创建一个数值类型为浮点型的张量:

b = tf.constant(12.5698, dtype=tf.float32)

(4) 张量转换

当然,我们也能对张量中数据的精度以及类型进行转换:

对精度进行转换:

a = tf.constant(12.5698, dtype=tf.float32)
a = tf.cast(a, tf.float64) 

对类型进行转换:

b = tf.constant(12, dtype=tf.int16)
tf.cast(b, tf.double)

(5) 数值类型

数值类型的张量是 TensorFlow 的主要数据载体,主要分为:标量、向量、矩阵、张量(维度大于2)

创建标量,注意必须通过 TensorFlow 规定的方式去创建张量,而不能使用 Python 语言的标准变量创建方式。

a = tf.constant(1.2) 

创建两个元素的向量:

b = tf.constant([1,2, 3.])

同样的方法来定义矩阵:

c = tf.constant([[1,2],[3,4]])

三维张量可以定义为:

d = tf.constant([[[1,2],[3,4]],[[5,6],[7,8]]])

(6) 待训练张量

在深度学习网络训练过程中,需要通过TensorFlow来建立梯度更新环境,用来对参数进行更新。为了区分需要计算梯度信息的张量与不需要计算梯度信息的张量,TensorFlow 增加了一种专门的数据类型来支持梯度信息的记录:tf.Variable

tf.Variable 类型在普通的张量类型基础上添加了 name,trainable 等属性来支持计算图的构建。由于梯度运算会消耗大量的计算资源,而且会自动更新相关参数,对于不需要的优化的张量,如神经网络的输入 X,不需要通过 tf.Variable 封装;相反,对于需要计算梯度并优化的张量,如神经网络层的W和𝒃,需要通过 tf.Variable 包裹以便 TensorFlow 跟踪相关梯度信息。

下面,通过 tf.Variable()函数可以将普通张量转换为待优化张量:

a = tf.constant([-1, 0, 1, 2])
a = tf.Variable(a)

当然,也可以直接创建待训练张量:

a = tf.Variable([[1,2],[3,4]])

待优化张量可看做普通张量的特殊类型,普通张量也可以通过 GradientTape.watch()方法临时加入跟踪梯度信息的列表。

二、创建张量Tensor

(1) 创建全0、全1张量

创建全 0 和全 1 的向量:

tf.zeros([1]), tf.ones([1])

创建全 0 和全 1 的矩阵:

tf.zeros([2,2]), tf.ones([3,2])

通过 tf.zeros_like, tf.ones_like 可以方便地新建与某个张量 shape 一致,内容全 0 或全 1 的张量。例如,创建与张量 a 形状一样的全 0 张量:

a = tf.ones([4,5])
tf.zeros_like(a)

创建与张量 a 形状一样的全 1 张量:

a = tf.ones([4,5])
tf.ones_like(a)

(2) 创建自定义数值张量

除了初始化为全 0,或全 1 的张量之外,有时也需要全部初始化为某个自定义数值的张量,比如将张量的数值全部初始化为-1 等。通过 tf.fill(shape, value)可以创建全为自定义数值 value 的张量。

创建所有元素为-1 的向量:

tf.fill([1], -1)

创建所有元素为 9 的矩阵:

tf.fill([2,2], 9)

(3) 创建已知分布的张量

正态分布(Normal Distribution)和均匀分布(Uniform Distribution)是最常见的分布之一,创建采样自这 2 种分布的张量非常有用,比如在卷积神经网络中,卷积核张量 W 初始化为正态分布有利于网络的训练;在对抗生成网络中,隐藏变量 z 一般采样自均匀分布。

通过 tf.random.normal(shape, mean=0.0, stddev=1.0)可以创建形状为 shape,均值为mean,标准差为 stddev 的正态分布。例如,创建均值为 0,标准差为 1的正太分布:

tf.random.normal([2,2])

创建均值为 1,标准差为 2 的正太分布:

tf.random.normal([2,2], mean=1,stddev=2)

通过 tf.random.uniform(shape, minval=0, maxval=None, dtype=tf.float32)可以创建采样自 [𝑚𝑖𝑛𝑣𝑎𝑙, 𝑚𝑎𝑥𝑣𝑎𝑙] 区间的均匀分布的张量。

例如创建采样自区间[0,1],shape 为[2,2]的矩阵:

tf.random.uniform([2,2])

创建采样自区间[0,10],shape 为[2,2]的矩阵:

tf.random.uniform([2,2],maxval=10)

如果需要均匀采样整形类型的数据,必须指定采样区间的最大值 maxval 参数,同时制定数据类型为 tf.int* 型:

tf.random.uniform([2,2],maxval=100,dtype=tf.int32)

(4) 创建序列

在循环计算或者对张量进行索引时,经常需要创建一段连续的整形序列,可以通过tf.range()函数实现。tf.range(limit, delta=1)可以创建[0,𝑙𝑖𝑚𝑖𝑡)之间,步长为 delta 的整形序列,不包含 limit 本身。

例如,创建 0~99,步长为 1 的整形序列:

tf.range(100)

创建 0~99,步长为 2 的整形序列:

tf.range(100, 2)

通过 tf.range(start, limit, delta=1)可以创建[𝑠𝑡𝑎𝑟𝑡, 𝑙𝑖𝑚𝑖𝑡),步长为 delta 的序列,不包含 limit 本身。例如,创建5~99,步长为2的序列:

 tf.range(5,100,delta=2)

(5) 从 Numpy、List 对象创建张量

通过 tf.convert_to_tensor 可以创建新 Tensor,并将保存在 Python List 对象或者 Numpy Array 对象中的数据导入到新 Tensor 中。

从List对象创建张量:

tf.convert_to_tensor([1,2.])

从Numpy对象创建张量:

tf.convert_to_tensor(np.array([[1,2.],[3,4]]))

三、索引与切片

(1) 索引

在 TensorFlow 中,支持基本的[𝑖][𝑗]…标准索引方式,也支持通过逗号分隔索引号的索引方式

考虑输入张量 x 为 8 张 32x32 大小的彩色3通道图片,即 shape 为 [4,32,32,3] 的张量:

# x使用随即分布模拟产生
x = tf.random.normal([4,32,32,3])

接下来我们使用索引方式读取张量的部分数据。

取第 1 张图片的数据:

x[0]  # 索引是从0开始的

取第 1 张图片的第 10 行:

x[0][10]

取第 1 张图片,第 10 行,第 8 列的像素:

x[0][10][8]

取第 3 张图片,第 10 行,第 8 列的像素,B 通道(第 2 个通道)颜色强度值:

x[0][10][8][2]

当然,也可以采用它的等价写法:

x[0, 10, 8, 2]

(2) 切片

通过𝑠𝑡𝑎𝑟𝑡: 𝑒𝑛𝑑: 𝑠𝑡𝑒𝑝切片方式可以方便地提取一段数据,其中 start 为开始读取位置的索引,end 为结束读取位置的索引(不包含 end 位),step 为读取步长。

我们还是以上面 shape 为 [4,32,32,3] 的图像为例,首先创建一个张量:

# x使用随即分布模拟产生
x = tf.random.normal([4,32,32,3])

读取第 2,3 张图片:

x[1:3]

start: end: step切片方式有很多简写方式,其中 start、end、step 3 个参数可以根据需要选择性地省略,全部省略时即::,表示从最开始读取到最末尾,步长为 1,即不跳过任何元素。

如 x[0,::]表示读取第 1 张图片的所有行,其中::表示在行维度上读取所有行,它等于x[0]的写法:

 x[0,::]

为了更加简洁,::可以简写为单个冒号:,如取所有图片,隔行采样,隔列采样,所有通道信息,相当于在图片的高宽各缩放至原来的 50%:

x[:, 0:28:2, 0:28:2, :]

特别地,step 可以为负数,考虑最特殊的一种例子,step = −1时,表示逆序读取元素。

当张量的维度数量较多时,不需要采样的维度一般用单冒号:表示采样所有元素。比如读取第一张图片:

x[0,:,:,:]

在TensorFlow中,为了避免出现像 𝑥[0, : , : , : ] 这样出现过多冒号的情况,可以使用...符号表示取多个维度上所有的数据,其中维度的数量需根据规则自动推断。例如上面的例子就可以写成:

 x[0,...]

四、维度变换

TensorFlow中对张量维度进行变换的操作函数有以下几种:

  1. tf.reshape(x, [ b, w, h, c ])。按照 [b, w, h, c]的维度对输入张量x进行变换。
  2. tf.transpose(x, perm=[0, 1, 3 , 2])。按照perm对维度进行转置,比如原来x的维度信息是[4,3,2,1],经过转置后,维度信息变为[4,3,1,2]。如果没有设置perm,那么x按照默认转置方式,变换后的维度为[1,2,3,4]。
  3. tf.expand_dims(x, axis=0)。在第0轴增加一个维度,比如原来x的维度是[28,28,3],增加第0维度后,x的维度变为[1,28,28,3].。
  4. tf.squeeze(x)。仅仅减去所有通道数为1的维度,比如原来x的维度是[1,28,28,1],处理后维度变为[28,28]。
  5. tf.squeeze(x, axis=0)。指定减去某个通道数为1的维度。比如原来x的维度是[1,28,28,1],处理后维度变为[28,28,1]。

五、广播机制

广播机制,即 Broadcasting,它能很方便的解决不同维度张量间的运算问题。广播操作类似tf.tile()对数据进行复制扩张,不同的是,tf.tile()是真的复制。而 Broadcasting 并不会立即复制数据,它只是在逻辑上改变张量的形状,使得视图上变成了复制后的形状。

Broadcasting 会通过深度学习框架的优化手段避免实际复制数据而完成逻辑运算,相对比于 tf.tile 函数而言,减少了大量计算代价,而作用却和 tf.tile 一样。所以建议在运算过程中,尽量使用 Broadcasting 来提高计算效率。

下图展示了几种不同情形下,不同维度张量间执行加法元素时的广播操作,如下图所示:
在这里插入图片描述

以神经网络中前向传播的过程为例,网络中某一层的计算公式是:Y=X@W+b,假设该层X@W 的 shape 是 [100, 5],偏置 b 的 shape 是 [5]。很明显 X@W 和 b 的 shape 是不一样的,所以不能直接相加。这个时候就需要先对 b 进行 Broadcasting,具体操作如下:

y = x@w + tf.broadcast_to(b,[100,5])

注意:之所以采用广播操作,是为了优化计算,并且考虑到复制会占用内存。

六、数学运算

Tensorflow中张量a、b间的数学运算主要分为3大类:

  • 张量中逐元素计算,比如:+、-、*、/、//、%。
  • 对张量中某一个维度上的计算,比如:reduce_mean、max、min、sum。
  • 张量整体计算,类似矩阵间的乘法一样。比如@、matmul。

Tensorflow中用于数学运算的元素符以及函数:

  • 对于逐元素计算:常用的运算符有:+、-、*、**、/、//、%;常用的函数有:tf.math.log()、tf.exp()、tf.pow()、tf.sqrt()、
  • 整体上计算:a@b、tf.matmul(a,b)

七、前向传播实战

下面,我们将利用已经学到的知识来搭建三层神经网络,实现 MNIST 手写数字识别。MNIST数据集在前面的文章中已经介绍过,这里就不再赘述。

(1) 导入相关库:

import tensorflow as tf
from tensorflow.keras import datasets

(2) 数据集准备:

# 加载数据集
(x, y), (x_test, y_test) = datasets.mnist.load_data()
# 转为张量
x = tf.convert_to_tensor(x, dtype=tf.float32) / 255.
y = tf.convert_to_tensor(y, dtype=tf.float32)
# 构建每一个batch数据
train_db = tf.data.Dataset.from_tensor_slices((x, y)).batch(128)
train_iter = iter(train_db)
sample = next(train_iter)
print('batch:', sample[0].shape, sample[1].shape)

(3) 初始化变量

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

(4) 迭代训练

for epoch in range(10):
    # x:[128,28,28], y:[128]
    for step, (x, y) in enumerate(train_db):   # step = nums/batch
        x = tf.reshape(x, [-1, 28*28])
        # 构建梯度环境
        with tf.GradientTape() as tape:
            # 第一层: [b,784]*[784,256]+[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
            # 将输出转换成热独码
            y_onehot = tf.one_hot(y, depth=10)
            # 建立mse损失函数
            loss = tf.square(y_onehot - out)
            loss = tf.reduce_mean(loss)

        # 计算梯度
        grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
        # 参数更新
        lr = 1e-3
        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: ', float(loss))

本教程所有代码会逐渐上传github仓库:https://github.com/Keyird/TensorFlow2-for-beginner
如果对你有帮助的话,欢迎star收藏~

最好的关系是互相成就,各位的「三连」就是【AI 菌】创作的最大动力,我们下期见!

在这里插入图片描述


点击全文阅读


本文链接:http://zhangshiyu.com/post/18351.html

张量  维度  创建  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1