1、合并与分割
接口:拼接tf.concat、tf.split、合并tf.stack、tf.unstack
(1)拼接tf.concat
不会创造新的维度,tf.concat([a,b],axis=x):a、b表示需要合并的tensor,x表示合并的维度
(2)合并tf.stack
使两个相同shape(所有维度相同)的tensor合并,且创造一个新的维度
(3)全拆分tf.unstack
按照指定轴,将tensor的该轴全部拆分,且维度-1
(4)split
在指定维度均分,num_or_size_splits属性表示tensor打散后的数量或者打散后的大小
2、数据统计
(1)范数tf.norm()
张量范数包含向量范数、矩阵范数。
向量范数分为一范数:绝对值的和。二范数:平方和开根号
tf.norm(a,ord=2,axis=0):ord默认为2求解二范数,axis可指定求解某维度范数
例子:
(2)tf.reduce_min/max/mean
是一个降维过程。axis可指定处理维度
(3)tf.argmax/argmin
求最大值与最小值的位置
(4)tf.equal
逐元素比较,可用于计算正确率
(5)tf.unique
返回不重复的数据,以及每个数据的下标位置
3、张量排序
(1)Sort(排序)/argsort(排序下标)
sort:返回对张量排序后的tensor,默认升序排列,DESCENDING表示降序
argsort:返回张量排序后元素的下标顺序
对多维度tensor排序
(2)Top_k获取最大几个值
返回前k个最大值或最大值下标
Top-k accuracy:用于判断accuracy,可同时考虑预测结果中的多个预测值可能正确的情况
上图中,预测结果k_b= ,表示第一个值最可能的结果为[2, 1, 0],第二个值最可能的结果为[1, 0, 2]。转置后表示为k_b= ,真实结果为target=[2, 0],升维后target=
然后可比较得预测结果k_b与真实结果 target可得出,只考虑top1正确率为50%,再将top2(次有可能正确的值)考虑进来则正确率为2/2=100%,若再将top3(次次有可能正确的值)考虑进来则正确率同样为2/2=100%
Top-k Accuracy计算过程
(3)实例代码:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import tensorflow as tf
tf.random.set_seed(2467)
def accuracy(output, target, topk=(1,)):
# 获取需要计算的类型数量
maxk = max(topk)
# 记录真实数据数量
batch_size = target.shape[0]
# 获取预测值前maxk个数据的排序下标,(每一行按照可能性由大到小排列)
pred = tf.math.top_k(output, maxk).indices
# 维度变换,由10行6列变换为6行10列(每一列按照可能性由大到小排列)
pred = tf.transpose(pred, perm=[1, 0])
# 真实数据升维,由1行10列复制为6行10列(不占内存的复制)
target_ = tf.broadcast_to(target, pred.shape)
#print("target_.shape=",target_.shape)
# 比较得到所有预测数据正确与否[True,False,...],correct.shape=(6, 10)
correct = tf.equal(pred, target_)
#print('correct:',correct)
#创建列表保存正确率
res = []
for k in topk:
# 先将correct的前k行数据化为以为一维张量,并将True、False数字化为1.、0.
correct_k = tf.cast(tf.reshape(correct[:k], [-1]), dtype=tf.float32)
# 求和,并将1维张量化为0维标量
correct_k = tf.reduce_sum(correct_k)
acc = float(correct_k * (100.0 / batch_size) )
res.append(acc)
return res
# 正态分布10个样本,6类,10行6列
output = tf.random.normal([10, 6])
output = tf.math.softmax(output, axis=1)
# 随机生成0~5的类型,1行10列
target = tf.random.uniform([10], maxval=6, dtype=tf.int32)
print('prob:', output.numpy())
pred = tf.argmax(output, axis=1)
print('pred:', pred.numpy())
print('label:', target.numpy())
# 分别计算top1、2、3、4、5、6的正确率
acc = accuracy(output, target, topk=(1,2,3,4,5,6))
print('top-1-6 acc:', acc)
运行结果:
4、数据填充与复制
(1)pad
上下左右均可填充,默认填充0,常用语图片、句子的padding
例子:
图片填充:
(2)tf.tile
tile为真实复制,占用空间,broadcast为隐式复制,实际上不占用空间。
tf.tile(a, [x1, x2]):x1、x2表示新Tenor在该维是之前的n倍,1则保持不变
5、(代码)张量限幅
(1)clip_by_value
对具体元素值进行clip:
tf.maximum(a, x):对数据一边进行限幅,取a、x中最大值作为返回数,
即tf.maximum(a, x)>=a&& tf.maximum(a, x)>=x
tf.minimum(a, x):对数据一边进行限幅,取a、x中最小值作为返回数,
即tf.minimum(a, x)<=a&& tf.minimum(a, x)<=x
对数据两边进行限幅
clip_by_value(a, 2, 8):对数据两边进行限幅,取(2, 8)之间的值
(2)relu
tf.nn.relu()使小于0的值取0,大于0的值取自身
vector、gradient有方向,gradient正方向表示(导数大于0)函数增加,gradient负方向表示(导数小于0)函数减少
clip_by_norm(a, k):使a的范数值变为15且保证a方向不变。
在clip时希望不要改变gradient方向,以免降低模型性能,因此需要等比例放缩(x, y),先将(x, y)(一个vector)除以它的模||(x, y)||(即(x, y)的二范数)归一化到0~1的范围,可再乘一个比例k,得到0~K范围的值,则可以在不改变gradient方向的前提下对张量限幅
上图中,保证a与aa的方向不变,范数值改变
(4)gradient clipping
使每个vector的方向、比例保持不变,gradient方向保持不变,norm作等比例的缩放。[w1, w2, w3]->[2, 4, 8]->[1, 2, 4]
下图,越训练值越大
下图,进行裁剪
下图,可观察到裁剪后值保持稳定
(5)代码比较gradient clipping的效果
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets, layers, optimizers
print(tf.__version__)
(x, y), _ = datasets.mnist.load_data()
# 输入范围从0~1放大到0~50
x = tf.convert_to_tensor(x, dtype=tf.float32) / 50.
y = tf.convert_to_tensor(y)
y = tf.one_hot(y, depth=10)
print('x:', x.shape, 'y:', y.shape)
train_db = tf.data.Dataset.from_tensor_slices((x,y)).batch(128).repeat(30)
x,y = next(iter(train_db))
print('sample:', x.shape, y.shape)
# print(x[0], y[0])
def main():
# 784 => 512
w1, b1 = tf.Variable(tf.random.truncated_normal([784, 512], stddev=0.1)), tf.Variable(tf.zeros([512]))
# 512 => 256
w2, b2 = tf.Variable(tf.random.truncated_normal([512, 256], stddev=0.1)), tf.Variable(tf.zeros([256]))
# 256 => 10
w3, b3 = tf.Variable(tf.random.truncated_normal([256, 10], stddev=0.1)), tf.Variable(tf.zeros([10]))
optimizer = optimizers.SGD(lr=0.01)
for step, (x,y) in enumerate(train_db):
# [b, 28, 28] => [b, 784]
x = tf.reshape(x, (-1, 784))
with tf.GradientTape() as tape:
# layer1.
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1)
# layer2
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
# output
out = h2 @ w3 + b3
# out = tf.nn.relu(out)
# compute loss
# [b, 10] - [b, 10]
loss = tf.square(y-out)
# [b, 10] => [b]
loss = tf.reduce_mean(loss, axis=1)
# [b] => scalar
loss = tf.reduce_mean(loss)
# compute gradient
grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
print('==before==')
for g in grads:
print(tf.norm(g))
# 进行clipping
grads, _ = tf.clip_by_global_norm(grads, 15)
print('==after==')
for g in grads:
print(tf.norm(g))
# update w' = w - lr*grad
optimizer.apply_gradients(zip(grads, [w1, b1, w2, b2, w3, b3]))
if step % 100 == 0:
print(step, 'loss:', float(loss))
if __name__ == '__main__':
main()
比较norm:
无tf.clip_by_global_norm(grads, 15) 有tf.clip_by_global_norm(grads, 15)
可观察到tf.clip_by_global_norm(grads, 15) 可以有效控制数据的norm
比较loss:
可观察到没有使用tf.clip_by_global_norm(grads, 15)的loss变大并超过了正常值导致nan(not a number),而使用了tf.clip_by_global_norm(grads, 15)之后loss被限制在一个方便观察的范围。
6、高阶操作
(1)where选择
where(tensor):只接收一个参数时,该参数为bool类型,where函数返回参数中为true的坐标。
使用boolean_mask与gather_nd分别收集大于0的数据:
where(cond, A, B):接收三个参数时,根据cond中true和false的信息,对应位置为true则选择A中数据,对应位置为false则选择B中数据:
(2)scatter_nd更新
scatter_nd(indices, updates, shape):shape默认为一个数值全为0的Tensor数据,将updates中的数据依次按indices指定的位置更新到shape中再返回新的Tensor数据。
复杂例子:
其中indices=tf.sctter_nd([[0], [2]])则是指定了[x, y, z]中x维度的两个值0、2,因此y、z维度的值全部选中。
(3)meshgrid生成坐标系
numpy生成坐标系:x轴、y轴分别在[-2, 2]区间取5个值,一共取25个点,保存到list中,并生成numpy的array。
TensorFlow构建坐标系,使用tf.linspace()限制x、y取值范围,用tf.meshgrid()返回拆分后的坐标数据保存在x、y两个tensor中
x、y中点位信息:
用tf.stack合并两个tensor数据创建坐标轴,得到[5, 5, 2]的tensor数据,还可以Reshape得到[25, 2]的点位信息,便于理解
应用:画出函数z=sin(x)+sin(y)的等高线
代码:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import tensorflow as tf
import matplotlib.pyplot as plt
def func(x):
"""
:param x: [b, 2]
:return:
"""
# x: [b, 2]中[b, 0]表示x坐标,[b, 1]表示y坐标
# 函数:z=sin(x)+sin(y)
z = tf.math.sin(x[...,0]) + tf.math.sin(x[...,1])
return z
# 确定x、y范围
x = tf.linspace(0., 2*3.14, 500)
y = tf.linspace(0., 2*3.14, 500)
# [50, 50],保存x、y数据
point_x, point_y = tf.meshgrid(x, y)
# [50, 50, 2]
points = tf.stack([point_x, point_y], axis=2)
# points = tf.reshape(points, [-1, 2])
print('points:', points.shape)
z = func(points)
print('z:', z.shape)
plt.figure('plot 2d func value')
plt.imshow(z, origin='lower', interpolation='none')
plt.colorbar()
plt.figure('plot 2d func contour')
# 画出函数的等高线
plt.contour(point_x, point_y, z)
plt.colorbar()
plt.show()
运行结果:
绘制等高线