四、TensorFlow基础
内容参考来自https://github.com/dragen1860/Deep-Learning-with-TensorFlow-book开源书籍《TensorFlow2深度学习》,这只是我做的简单的学习笔记,方便以后复习
1.数据类型
数值类型的张量是 TensorFlow 的主要数据载体,根据维度数来区分,可分为:标量(Scalar),向量(Vector),矩阵(Matrix),张量(Tensor)
# tensor和Numpy相互转换
import tensorflow as tf
a = 1.2 # python 语言方式创建标量
aa = tf.constant(1.2) # TF 方式创建标量
type(a), type(aa), tf.is_tensor(aa) # (float, tensorflow.python.framework.ops.EagerTensor, True)
aa.numpy() # 1.2
2. 数据精度
常用的精度类型有 tf.int16、tf.int32、tf.int64、tf.float16、tf.float32、tf.float64 等,其中 tf.float64 即为 tf.double。
# 创建指定精度的张量
tf.constant(123456789, dtype=tf.int16)
类型转换
a = tf.constant(np.pi, dtype=tf.float16) # 创建 tf.float16 低精度张量
tf.cast(a, tf.double) # 转换为高精度张量
a = tf.constant([True, False])
tf.cast(a, tf.int32) # 布尔类型转整型 numpy=array([1, 0])
3.待优化张量
为了区分需要计算梯度信息的张量与不需要计算梯度信息的张量,TensorFlow 增加了一种专门的数据类型来支持梯度信息的记录:tf.Variable。
a = tf.constant([-1, 0, 1, 2]) # 创建 TF 张量
aa = tf.Variable(a) # 转换为 Variable 类型
aa.name, aa.trainable # Variable 类型张量的属性 ('Variable:0', True)
4.创建张量
通过 tf.convert_to_tensor 函数可以创建新 Tensor,并将保存在 Python List 对象或者Numpy Array 对象中的数据导入到新 Tensor 中。
tf.convert_to_tensor([1,2.]) # 从列表创建张量
实际上,tf.constant()和 tf.convert_to_tensor()都能够自动的把 Numpy 数组或者 Python列表数据类型转化为 Tensor 类型,这两个 API 命名来自 TensorFlow 1.x 的命名习惯,在TensorFlow 2 中函数的名字并不是很贴切,使用其一即可。
特殊张量的创建
tf.zeros([2,2]) # 创建全 0 矩阵,指定 shape 为 2 行 2 列
tf.ones([3,2]) # 创建全 1 矩阵,指定 shape 为 3 行 2 列
a = tf.ones([2,3]) # 创建一个矩阵
tf.zeros_like(a) # 创建一个与 a 形状相同,但是全 0 的新矩阵
a = tf.zeros([3,2]) # 创建一个矩阵
tf.ones_like(a) # 创建一个与 a 形状相同,但是全 1 的新矩阵
tf.fill([2,2], 99) # 创建 2 行 2 列,元素全为 99 的矩阵
创建已知分布的张量
通过 tf.random.normal(shape, mean=0.0, stddev=1.0)可以创建形状为 shape,均值为mean,标准差为 stddev 的正态分布𝒩(mean,stddev^2 )。
tf.random.normal([2,2]) # 创建标准正态分布的张量
tf.random.normal([2,2], mean=1,stddev=2) # 创建正态分布的张量
通过 tf.random.uniform(shape, minval=0, maxval=None, dtype=tf.float32)可以创建采样自[minval,maxval)区间的均匀分布的张量。
tf.random.uniform([2,2]) # 创建采样自[0,1)均匀分布的矩阵
tf.random.uniform([2,2],maxval=10) # 创建采样自[0,10)均匀分布的矩阵
tf.random.uniform([2,2],maxval=100,dtype=tf.int32) # # 创建采样自[0,100)均匀分布的整型矩阵
5.张量的典型应用
6.索引与切片
切片方式 意义
# 切片方式 意义
# start::end:step 从 start 开始读取到 end(不包含 end),步长为 step
# start:end 从 start 开始读取到 end(不包含 end),步长为 1
# start: 从 start 开始读取完后续所有元素,步长为 1
# start::step 从 start 开始读取完后续所有元素,步长为 step
# :end:step 从 0 开始读取到 end(不包含 end),步长为 step
# :end 从 0 开始读取到 end(不包含 end),步长为 1
# ::step 步长为 step 采样
# :: 读取所有元素
# : 读取所有元素
# 切片方式 意义
# a,⋯,b a 维度对齐到最左边,b 维度对齐到最右边,中间的维度全部读取,其他维度按 a/b 的方式读取
# a,⋯ a 维度对齐到最左边,a 维度后的所有维度全部读取,a 维度按 a 方式读取。这种情况等同于 a 索引/切片方式
# ⋯,b b 维度对齐到最右边,b 之前的所有维度全部读取,b 维度按 b 方式读取
# ⋯ 读取张量所有数据
7.维度变换
基本的维度变换操作函数包含了改变视图 reshape、插入新维度 expand_dims,删除维度 squeeze、交换维度 transpose、复制数据 tile 等函数。
改变视图tf.reshape
x=tf.range(96) # 生成向量
x=tf.reshape(x,[2,4,4,3]) # 改变 x 的视图,获得 4D 张量,存储并未改变
增加维度tf.expand_dims(x, axis)和删除维度 tf.squeeze(x, axis)
# 产生矩阵
x = tf.random.uniform([28,28],maxval=10,dtype=tf.int32)
x = tf.expand_dims(x,axis=2) # axis=2 表示宽维度后面的一个维度 > shape=(28, 28, 1)
x = tf.expand_dims(x,axis=0) # 高维度之前插入新维度 > shape=(1, 28, 28, 1)
x = tf.squeeze(x, axis=0) # 删除图片数量维度 > shape=(28, 28, 1)
# 如果不指定维度参数 axis,即 tf.squeeze(x),那么它会默认删除所有长度为 1 的维度
x = tf.random.uniform([1,28,28,1],maxval=10,dtype=tf.int32)
tf.squeeze(x) # 删除所有长度为 1 的维度 > shape=(28, 28)
交换维度(Transpose(x, perm))
x = tf.random.normal([2,32,32,3])
tf.transpose(x,perm=[0,3,1,2]) # 交换维度 > shape=(2, 3, 32, 32)
复制数据:可以通过 tf.tile(x, multiples)函数完成数据在指定维度上的复制操作,multiples 分别指定了每个维度上面的复制倍数,对应位置为 1 表明不复制,为 2 表明新长度为原来长度的2 倍,即数据复制一份,以此类推。
b = tf.constant([1,2]) # 创建向量 b shape=(2,)
b = tf.expand_dims(b, axis=0) # 插入新维度,变成矩阵 shape=(1, 2)
b = tf.tile(b, multiples=[2,1]) # 样本维度上复制一份 shape=(2, 2) numpy=array([[1, 2],[1, 2]])
8.Broadcasting
tf.broadcast_to(x, new_shape)函数可以显式地执行自动扩展功能,将现有 shape 扩张为 new_shape
A = tf.random.normal([32,1]) # 创建矩阵
tf.broadcast_to(A, [2,32,32,3]) # 扩展为 4D 张量
9.数学运算
矩阵相乘:根据矩阵相乘的定义,𝑩和𝑪能够矩阵相乘的条件是,𝑩的倒数第一个维度长度(列)和𝑪的倒数第二个维度长度(行)必须相等。
a = tf.random.normal([4,3,28,32])
b = tf.random.normal([4,3,32,2])
a@b # 批量形式的矩阵相乘 得到 shape 为[4,3,28,2]
a = tf.random.normal([4,28,32])
b = tf.random.normal([32,16])
tf.matmul(a,b) # 先自动扩展,再矩阵相乘 得到 shape 为[4,28,16]
10. 前向传播实战
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)