- TensorFlow是一个面向深度学习的计算库,内部数据保存在张量对象上,所有的运算操作也都是基于张量进行的。
- 复杂的神经网络本质上就是各种张量相乘、相加等基本操作的组合。
数据类型
数值型、字符串型、布尔型
1.数值型
标量(shape=())、向量(shape=(n))、矩阵(shape=(n,m))、张量
注意,下面这两不一样:
print(tf.constant([2]))
print(tf.constant(2))
- 创建张量
tf.constant([[1, 2], [3, 4]]) - 打印张量时,不仅会有数据信息,还有dtype,shape的信息
- 转换成numpy,使用numpy()方法
- tf.is_tensor(a)
判断是否是张量。
a = tf.constant(1.2)
print(a)
print(type(a))
print(tf.is_tensor(a))
x = tf.constant([1.2, 3.3])
print(x)
num_x = x.numpy()
print(num_x, type(num_x))
<class 'tensorflow.python.framework.ops.EagerTensor'>
True
tf.Tensor([1.2 3.3], shape=(2,), dtype=float32)
[1.2 3.3] <class 'numpy.ndarray'>
注意:如果要使用tensorflow提供的功能函数。就必须通过tf规定的方式去创建张量,不能用Python语言的标准变量创建方式。
2.字符串类型
在 tf.strings 模块中,提供了常见的字符串类型的工具函数,如小写化 lower()、拼接join()、长度 length()、切分 split()等。
s = tf.constant('Hello, Deep Learning!')
print(s)
print(tf.strings.lower(s))
tf.Tensor(b'Hello, Deep Learning!', shape=(), dtype=string)
tf.Tensor(b'hello, deep learning!', shape=(), dtype=string)
3.bool类型
注意:tf的bool类型和python的bool类型并不等价!!!数值比较可以,但是对象比较不行。
b = tf.constant(True)
print(b)
print(b == True)
print(b is True)
tf.Tensor(True, shape=(), dtype=bool)
tf.Tensor(True, shape=(), dtype=bool)
False
4.数值精度
常用的精度类型有 tf.int16、tf.int32、tf.int64、tf.float16、tf.float32、tf.float64 等。
对于大部分深度学习算法,一般使用 tf.int32 和 tf.float32 可满足大部分场合的运算精度要求,部分对精度要求较高的算法,如强化学习某些算法,可以选择使用 tf.int64 和tf.float64 精度保存张量。
可以使用dtype属性读取精度。
5.类型转换
对于不符合要求的张量的类型及精度,需要类型转换,使用 : tf.cast 函数
xx = tf.constant(np.pi, dtype=tf.float16)
print(xx)
yy = tf.cast(xx, tf.float64)
print(yy)
tf.Tensor(3.14, shape=(), dtype=float16)
tf.Tensor(3.140625, shape=(), dtype=float64)
注意:需要保证转换操作的合法性,例如将高精度的张量转换为低精度的张量时,可能发生数据溢出隐患:
aa = tf.constant([123456789], dtype=tf.int32)
bb = tf.cast(aa, tf.int8)
print(bb)
tf.Tensor([21], shape=(1,), dtype=int8)
待优化张量
为了区分需要优化和不需要优化的变量。 需要优化的变量要是 tf.Variable 数据类型,支持记录梯度信息。 在普通张量上加了name, trainable 等属性来支持计算图构建。
- name和trainable是Variable的特有属性,name 属性用于命名计算图中的变量,这套命名体系是 TensorFlow 内部维护的,一般不需要用户关注 name 属性;;trainable属性表征当前张量是否需要被优化,创建 Variable 对象时是默认启用优化标志,可以设置trainable=False 来设置张量不需要优化。
- tf.Variable() 函数将普通张量转换为待优化张量
z = tf.constant([1, 2, 3.3])
vz = tf.Variable(z, trainable=False)
print(vz)
print(vz.name, vz.trainable)
<tf.Variable 'Variable:0' shape=(3,) dtype=float32, numpy=array([1. , 2. , 3.3], dtype=float32)>
Variable:0 False
- tf.Variable()也可以直接创建
vv = tf.Variable([1, 2])
- 普通张量其实也可以通过 GradientTape.watch()方法临时加入跟踪梯度信息的列表,从而支持自动求导功能
5.创建张量
tf.convert_to_tensor() tf.constant() 都能够自动的把 Numpy 数组或者 Python 列表数据类型转化为 Tensor 类型.
tf.zeros() tf.ones()
tf.zeros_like() tf.ones_like()
tf.fill(shape, value): tf.fill([], -1) # 创建-1 的标量 / tf.fill([2,2], 99) # 创建 2 行 2 列,元素全为 99 的矩阵
创建已知分布的张量 : tf.random.normal(shape, mean=0.0, stddev=1.0)
tf.random.uniform(shape, minval=0, maxval=None, dtype=tf.float32)可以创建采样自[minval, maxval)区间的均匀分布的张量。 如果需要均匀采样整形类型的数据,必须指定采样区间的最大值 maxval 参数,同时指 定数据类型为 tf.int*型: tf.random.uniform([2,2],maxval=100,dtype=tf.int32)
创建序列: tf.range(limit, delta=1)可以创建[0, limit)之间,步长为 delta 的整型序列,不包含 limit 本身。
6.张量典型应用
标量:误差值、准确度、精度、召回率 tf.keras.losses.mse(或 tf.keras.losses.MSE)返回每个样本上的误差值
向量:偏置b 通过高层接口类Dense()的方式创建网络层,张量W和b存储在类的内部,由类自动创建并管理。
fc = layers.Dense(3)
fc.build(input_shape=(2, 4))
print(fc.bias)
<tf.Variable 'bias:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>
可以发现 ,bias初始化为全0,这也是默认的初始化方式。 bias是Variable数据类型,因为它是待优化变量。
矩阵: 权值矩阵W
x = tf.random.normal([2, 4])
w = tf.ones([4, 3])
b = tf.zeros([3])
o = x @ w + b
print(o)
fc = layers.Dense(3)
fc.build(input_shape=(2, 4))
print(fc.kernel)
tf.Tensor(
[[3.6994305 3.6994305 3.6994305]
[0.3701757 0.3701757 0.3701757]], shape=(2, 3), dtype=float32)
<tf.Variable 'kernel:0' shape=(4, 3) dtype=float32, numpy=
array([[-0.01163948, 0.28521907, -0.06265694],
[-0.42561263, 0.9115014 , 0.67937183],
[-0.48957258, -0.55697334, -0.39781886],
[ 0.39810383, 0.85386693, -0.6553062 ]], dtype=float32)>
三维张量: 三维的张量一个典型应用是表示序列信号 四维张量: 卷积神经网络用的多。 [b,h,w,c]: b表示batch、h表示图片高、w表示图片宽、c表示图片通道数,一般是1 or 3;1就是灰度图片,3就是RGB三通道图片。
创建卷积层的简单例子:
x = tf.random.normal([4, 32, 32, 3])
layer = layers.Conv2D(16, kernel_size=3)
out = layer(x)
print(out)
print(out.shape)
索引与切片
print("---------------------------------")
x = tf.random.normal([4, 32, 32, 3])
print(x[0])
print(x[0][5])
print(x[0, 1:6:2, :, 0])
- 特别地,step 可以为负数,考虑最特殊的一种例子,当step = ?1时,start: end: ?1表示从 start 开始,逆序读取至 end 结束(不包含 end),索引号𝑒𝑛𝑑 ≤ 𝑠𝑡𝑎𝑟𝑡。
维度变换
reshape、插入新维度 expand_dims,删除维度 squeeze、交换维度 transpose、复制数据 tile 等函数。
改变视图: 张量的存储体现在张量在内存上保存为一段连续的内存区域,对于同样的存储,我们可以有不同的理解方式。 比如 shape 为[2,4,4,3]的张量𝑨,可以理解为 2 张图片,每张图片 4 行 4 列,每个位置有 RGB 3 个通道的数据;还理解为 2个样本,每个样本的特征为长度 48 的向量。 如果新的逻辑结构不需要改变数据的存储方式,就可以节省大量计算资源,这也是改变视图操作的优势。
所以tf.reshape只是从观察角度改变张量的形状,不改变内存。
x = tf.range(100)
y = tf.reshape(x, [2, 25, 2])
注意:由于只是从观察视角改变形状,x和y共享内存,y改变,x也会改变。 ??不太确定
增、删维度
通过 tf.expand_dims(x, axis)可在指定的 axis 轴前可以插入一个新的维度:
x = tf.random.normal([28, 28])
y = tf.expand_dims(x, axis=2)
print(y.shape)
(28, 28, 1)
是增加维度的逆操作,与增加维度一样,删除维度只能删除长度为 1 的维度,也不会改变张量的存储。
z = tf.squeeze(y, axis=2)
print(z.shape)
(28, 28)
如果不指定维度参数 axis,即 tf.squeeze(x),那么它会默认删除所有长度为 1 的维度。 建议使用 tf.squeeze()时逐一指定需要删除的维度参数,防止 TensorFlow 意外删除某些长度为 1 的维度,导致计算结果不合法。
交换维度
tf.transpose(x, perm)函数完成维度交换操作。,其中参数 perm表示新维度的顺序 List。 比如在 TensorFlow 中,图片张量的默认存储格式是通道后行格式:[𝑏, ?,w , 𝑐],但是部分库的图片格式是通道先行格式:[𝑏, 𝑐, ?,w ],因此需要完成[𝑏, ?, , 𝑐]到[𝑏, 𝑐, ?, w]维度交换运算,考虑图片张量 shape 为[2,32,32,3],“图片数量、行、列、通道数”的维度索引分别为 0、1、2、3,如果需要交换为[𝑏, 𝑐, ?, w]格式,则新维度的排序为“图片数量、通道数、行、列”,对应的索引号为[0,3,1,2],因此参数 perm 需设置为[0,3,1,2]。
需要注意的是,通过 tf.transpose 完成维度交换后,张量的存储顺序已经改变,视图也随之改变,后续的所有操作必须基于新的存续顺序和视图进行。相对于改变视图操作,维度交换操作的计算代价更高。
x = tf.random.normal([2, 24, 24, 3])
y = tf.transpose(x, [0, 3, 1, 2])
复制数据
tf.tile(x, multiples)函数完成数据在指定维度上的复制操作,multiples 分别指定了每个维度上面的复制倍数。 例子: 线性回归
Y
=
X
@
W
+
b
Y=X@W+b
Y=X@W+b中,b的维度和
X
@
W
X@W
X@W不匹配,需要对b进行复制:1.先将向量b转换成矩阵2.再使用tile复制几行b(行数和样本数匹配):
b = tf.constant([1, 2, 3])
b = tf.expand_dims(b, axis=0)
b = tf.tile(b, multiples=[3, 1])
print(b)
tf.Tensor(
[[1 2 3]
[1 2 3]
[1 2 3]], shape=(3, 3), dtype=int32)
但是,实际上,我们不需要做增加维度和复制的工作,tensor的广播机制会帮我们完成。 需要注意的是,tf.tile 会创建一个新的张量来保存复制后的张量,由于复制操作涉及大量数据的读写 IO 运算,计算代价相对较高。
Broadcasting★
是一种轻量级的张量复制手段,逻辑上扩展张量数据的形状,但只有在需要的时候才会执行实际的复制手段。 broadcasting 和 tile 的区别: tile: 会创建一个新的张量,执行复制IO操作,并保存复制后的张量数据。 broadcasting:避免实际复制数据,最终效果同tile,大大节省了计算资源,计算过程尽可能使用。
broadcasting会在运算时自动实现,也可以手动使用函数 tf.broadcast_to(x, new_shape)实现:
y = x@w + tf.broadcast_to(b,[2,3])
Broadcasting的核心思想是:普适性 有关Broadcasting的具体规则,见书。
数学运算
加、减、乘、除是最基本的数学运算,分别通过 tf.add, tf.subtract, tf.multiply, tf.divide函数实现,TensorFlow 已经重载了**+、 ? 、 ? 、/运算符,一般推荐直接使用运算符来完成加、减、乘、除运算。 整除和余除也是常见的运算之一,分别通过//和%运算符实现。 通过 tf.pow(x, a)可以方便地完成𝑦 =x^𝑎的乘方运算,也可以通过运算符实现 x?? 𝑎运 算
数e^𝑥,可以通过 **tf.exp(x)**实现
数log_e x可以通过 tf.math.log(x)
矩阵相乘运算:
- 使用@运算符
- 通过 tf.matmul(a, b)函数。
注: TensorFlow 中的矩阵相乘可以使用批量方式,也就是张量𝑨和𝑩的维度数可以大于 2。当张量𝑨和𝑩维度数大于 2 时,TensorFlow 会选择𝑨和𝑩的最后两个维度进行矩阵相乘,前面所有的维度都视作Batch 维度。
矩阵相乘同样适用于广播机制: 会自动将b扩展成[4,32,16]
前向传播实战
print("前向传播---------------")
(x, y), (x_test, y_test) = mnist.load_data()
x = tf.convert_to_tensor(x, dtype=tf.float32)
y = tf.convert_to_tensor(y, dtype=tf.int32)
with tf.GradientTape() as tape:
w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))
b1 = tf.Variable(tf.zeros([256]))
w2 = tf.Variable(tf.random.normal([256, 128], stddev=0.1))
b2 = tf.Variable(tf.zeros([128]))
w3 = tf.Variable(tf.random.normal([128, 10], stddev=0.1))
b3 = tf.Variable(tf.zeros(10))
x = tf.reshape(x, [-1, 28 * 28])
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1)
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
out = h2 @ w3 + b3
y = tf.one_hot(y, depth=10)
loss = tf.square(y, out)
loss = tf.reduce_mean(loss)
grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
lr = 0.01
w1.assign_sub(lr*grads[0])
b3.assign_sub(lr*grads[-1])
|