本文是书籍《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,2,1]#读第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)前一阵的前向传播如何做的?本章如何做的?细节内容?