Tensorflow#

import tensorflow as tf

数据表示#

标量#

a = tf.constant(3.0)
b = tf.constant(2.0)
a, b

向量#

x = tf.range(4, dtype=tf.float32)
y = tf.ones(4)
x, y

矩阵#

A = tf.reshape(tf.range(12, dtype=tf.float32), (3, 4))
B = tf.constant([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
A, B

张量#

X = tf.reshape(tf.range(36), (3, 4, -1))
X

属性和方法#

shape 和 size()#

x.shape, tf.size(x), X.shape, tf.size(X)

reshape()#

tf.reshape(A, (2, -1))

zeros()#

tf.zeros((2, 3, 4))

random#

tf.random.normal(shape=[3, 4])  # 符合正态分布的随机数

concat()#

tf.concat([A, B], axis=0), tf.concat([A, B], axis=1)

求和#

# 对所有元素求和(这是一种降维方法)
sum_total = tf.reduce_sum(X)
sum_total
# 指定 axis 求行和或列和,keepdims=True 保持轴数不变,可以方便后期利用广播机制
sum_X = tf.reduce_sum(X, axis=[0,1], keepdims=True)
sum_X

获取切片#

值得注意的是,图像一般默认为(H, W, C)即 (高度, 宽度, 通道数) 但获取切片时的参数一般为(C, H, W)

X, X[-1], X[1:3] # 把 X 想成是图片的三个通道,X[-1] 获取最后一个通道,X[1:3] 获取第 2 和第 3 通道。

代数运算#

向量 \(\times\) 向量#

x, y, tf.tensordot(x, y, axes=1) # 点积后是一个标量,且 x 和 y 的数据类型保持一致

矩阵 \(\times\) 向量#

A, x, tf.linalg.matvec(A, x)

矩阵 \(\times\) 矩阵#

A, B, tf.matmul(tf.transpose(A), B)

范数#

# 1 范数 和 2 范数
tf.reduce_sum(tf.abs(x)), tf.norm(x)

运算符#

加减乘除#

x + y, x - y, x * y, x / y, x ** y  # ** 运算符是求幂运算

广播机制#

广播机制让形状不同的张量也能计算。它先将两个向量扩张成一致的形状(两个向量先变成矩阵),然后再相加(矩阵加法)。

yt = tf.reshape(y, (4, -1))
x, yt, x + yt

逻辑运算#

A == B # 逻辑运算符 "按元素"

声明变量#

TensorFlow 中的 Tensors 是不可变的,也不能被赋值。 TensorFlow 中的 Variables 是支持赋值的可变容器。 请记住,TensorFlow 中的梯度不会通过 Variable 反向传播,这句话没说错,记住。

X_var = tf.Variable(X) # 声明变量,预分配存储空间
X_var[0:2, 0:2, :].assign(tf.ones(X_var[0:2, 0:2, :].shape, dtype = tf.int32) * 12) # 把前两个通道的前两行都变成 12
X, X_var

节省内存#

节省内存的意思就是不要重复地开辟内存,尽量原地操作。

# 默认行为
before = id(y)
y = y + x
id(y) == before

# 节省内存的做法是使用切片
z = tf.Variable(tf.zeros_like(y))
print('id(z):', id(z))
z.assign(x + y)
print('id(z):', id(z))

由于 TensorFlow 的 Tensors 是不可变的,而且梯度不会通过 Variable 流动, 因此 TensorFlow 没有提供一种明确的方式来原地运行单个操作。

但是,TensorFlow 提供了 tf.function 修饰符, 将计算封装在 TensorFlow 图中,该图在运行前经过编译和优化。 这允许 TensorFlow 删除未使用的值,并复用先前分配的且不再需要的值。 这样可以最大限度地减少 TensorFlow 计算的内存开销。

@tf.function
def computation(X, Y):
    Z = tf.zeros_like(Y)  # 这个未使用的值将被删除
    A = X + Y  # 当不再需要时,分配将被复用
    B = A + Y
    C = B + Y
    return C + Y

computation(x, y)

类型转换#

Tensorflow 转换后的结果不共享内存。这个小的 不便 实际上是非常重要的:当你在 CPU 或 GPU 上执行操作的时候,如果 Python 的 NumPy 包也 希望使用相同的内存块 执行其他操作,你不希望停下计算来等它。这个不便之处,MXNet 和 Tensorflow 都没有解决,只有 PyTorch 是解决了的。

tensor 转 ndarray#

X.numpy()

ndarray 转 tensor#

tf.constant(A)

tensor 转 scalar#

tf.constant([3.5]).numpy().item() # 仅限只含一个元素

自动求导#

这可能是最重要的部分了,有了上面的基础,相信这部分很容易看懂的。

需要求导的函数:

\[\begin{split} y = x^2 \\ u = y\\ z = u * x \end{split}\]

其中,变量 \(x\) 的取值点为 \((0, 1, 2, 3)\)

x = tf.range(4, dtype=tf.float32) # 初始化数据
x = tf.Variable(x) # 声明变量

# 把所有计算记录在磁带(GradientTape())上,可以把磁带想象成一种资源,可能是计算图吧
with tf.GradientTape(persistent=True) as t: # 设置 persistent=True 来运行 t.gradient 多次
    y = x * x
    u = tf.stop_gradient(y) # 分离计算
    z = u * x

x_grad = t.gradient(z, x) # 求梯度

z, x_grad, x_grad == u
x = tf.range(4, dtype=tf.float32) # 初始化数据
x = tf.Variable(x) # 声明变量

# 把所有计算记录在磁带(GradientTape())上,可以把磁带想象成一种资源,可能是计算图吧
with tf.GradientTape(persistent=True) as t: # 设置 persistent=True 来运行 t.gradient 多次
    y = x * x
    u = y # 没有分离计算
    z = u * x

x_grad = t.gradient(z, x) # 求梯度

z, x_grad, x_grad == u