4、TensorFlow运算操作:算术运算、矩阵运算、广播机制、规约操作
各位同学,欢迎来到第四章。这一章我们聊聊TensorFlow里最基础、也最核心的东西——运算操作。
说实话,我刚开始学TensorFlow的时候,觉得运算嘛,不就是加减乘除?后来在项目中踩了不少坑,才发现事情没那么简单。尤其是广播机制,我第一次用的时候,结果完全出乎意料。嗯,咱们今天就把这些坑一个个填平。
4.1 算术运算:加减乘除那些事儿
算术运算,说白了就是张量之间的加减乘除。TensorFlow提供了两种方式:一种是运算符重载(+、-、*、/),另一种是函数式API(tf.add、tf.subtract等)。
我个人习惯用运算符重载,代码更简洁。但要注意,除法有陷阱。
import tensorflow as tf
a = tf.constant([[1, 2], [3, 4]])
b = tf.constant([[5, 6], [7, 8]])
# 加法
c = a + b # 或者 tf.add(a, b)
print(c.numpy())
# 输出: [[ 6 8]
# [10 12]]
# 除法——这里要注意
d = a / b # 浮点除法
e = a // b # 整数除法
print(d.numpy()) # 输出: [[0.2 0.33333334]
# [0.42857143 0.5 ]]
print(e.numpy()) # 输出: [[0 0]
# [0 0]]
我曾经在模型训练时,用整数除法计算学习率衰减,结果学习率直接变成0,模型再也不收敛了。排查了半天才发现是除法类型搞错了。记住:整数除法会截断小数!
还有取模运算 % 和幂运算 **,也很常用。比如在循环神经网络里,我经常用取模来重置状态。
# 取模和幂运算
f = a % 3 # 每个元素对3取模
g = a ** 2 # 每个元素平方
print(f.numpy()) # 输出: [[1 2]
# [0 1]]
print(g.numpy()) # 输出: [[ 1 4]
# [ 9 16]]
4.2 矩阵运算:线性代数的核心
矩阵运算在深度学习里太重要了。全连接层、卷积操作,本质上都是矩阵乘法。TensorFlow的矩阵运算非常高效,底层调用了优化的BLAS库。
最常用的就是 tf.matmul,或者直接用 @ 运算符。
# 矩阵乘法
x = tf.constant([[1, 2], [3, 4]]) # 2x2
y = tf.constant([[5, 6], [7, 8]]) # 2x2
z = x @ y # 或者 tf.matmul(x, y)
print(z.numpy())
# 输出: [[19 22]
# [43 50]]
你想想看,为什么矩阵乘法这么重要?因为神经网络的一层,本质上就是 y = W @ x + b。我在做图像分类项目时,一个卷积层就要做几百万次矩阵乘法,性能优化全靠它。
矩阵乘法要求维度匹配:
(m, n) @ (n, p) = (m, p)。不匹配会报错,这是新手最常见的错误之一。
除了乘法,还有转置、求逆、行列式等操作。
# 转置
x_t = tf.transpose(x)
print(x_t.numpy())
# 输出: [[1 3]
# [2 4]]
# 矩阵求逆(需要浮点类型)
x_float = tf.cast(x, tf.float32)
x_inv = tf.linalg.inv(x_float)
print(x_inv.numpy())
# 输出: [[-2. 1. ]
# [ 1.5 -0.5]]
我在做线性回归时,直接用
tf.linalg.inv 求解析解。但要注意,矩阵不可逆时会报错。实际项目中我更推荐用 tf.linalg.lstsq 做最小二乘解,更稳定。
4.3 广播机制:自动扩展的魔法
广播机制,说白了就是TensorFlow自动帮你把不同形状的张量「对齐」,然后做运算。这个机制非常强大,但也容易让人困惑。
为什么会这样?因为实际项目中,我们经常需要把一个标量和一个矩阵相加,或者把一个向量和一个矩阵相加。手动扩展维度太麻烦,广播机制自动帮你搞定。
# 标量 + 矩阵:广播
a = tf.constant([1, 2, 3]) # 形状 (3,)
b = tf.constant(10) # 标量
c = a + b # 广播:10 扩展为 [10, 10, 10]
print(c.numpy()) # 输出: [11 12 13]
# 向量 + 矩阵:广播
matrix = tf.constant([[1, 2, 3],
[4, 5, 6]]) # 形状 (2, 3)
vector = tf.constant([10, 20, 30]) # 形状 (3,)
result = matrix + vector # 广播:vector 扩展为 (2, 3)
print(result.numpy())
# 输出: [[11 22 33]
# [14 25 36]]
广播的规则其实很简单:从最后一个维度开始比较,如果维度相等或者其中一个为1,就可以广播。如果都不满足,就报错。
| 张量A形状 | 张量B形状 | 结果形状 | 是否可广播 |
|---|---|---|---|
| (3,) | (1,) | (3,) | ✅ 是 |
| (3, 1) | (1, 4) | (3, 4) | ✅ 是 |
| (3, 4) | (4,) | (3, 4) | ✅ 是 |
| (3, 4) | (3,) | ❌ 报错 | ❌ 否 |
我曾经在写自定义损失函数时,忘记检查张量形状,结果广播机制悄悄把错误维度的数据「对齐」了,导致损失计算完全错误。模型训练了三天才发现。所以,我建议你在关键运算前,用
tf.shape() 打印形状确认一下。
4.4 规约操作:降维的艺术
规约操作,就是沿着某个维度做聚合计算。比如求和、求均值、求最大值等。这在模型评估、损失计算中无处不在。
最常用的规约操作有:
tf.reduce_sum:求和tf.reduce_mean:求均值tf.reduce_max/tf.reduce_min:求最大/最小值tf.reduce_all/tf.reduce_any:逻辑与/或
# 规约操作示例
data = tf.constant([[1, 2, 3],
[4, 5, 6]]) # 形状 (2, 3)
# 全局求和
total = tf.reduce_sum(data)
print(total.numpy()) # 输出: 21
# 按行求和(axis=1)
row_sum = tf.reduce_sum(data, axis=1)
print(row_sum.numpy()) # 输出: [6 15]
# 按列求均值(axis=0)
col_mean = tf.reduce_mean(data, axis=0)
print(col_mean.numpy()) # 输出: [2.5 3.5 4.5]
# 保持维度
row_sum_keep = tf.reduce_sum(data, axis=1, keepdims=True)
print(row_sum_keep.shape) # 输出: (2, 1)
keepdims=True 这个参数很实用。它保持结果的维度数不变,方便后续的广播运算。我在做批量归一化时,经常用它来保持维度结构。
还有一个很实用的操作——tf.argmax 和 tf.argmin,返回最大/最小值的索引。分类任务中,我们用它来获取预测的类别。
# 获取最大值的索引
logits = tf.constant([[0.1, 0.8, 0.1],
[0.3, 0.2, 0.5]])
predictions = tf.argmax(logits, axis=1)
print(predictions.numpy()) # 输出: [1 2]
嗯,这里要注意:tf.argmax 返回的是整数索引,不是概率值。如果你想同时获取最大值和索引,可以用 tf.math.top_k。
我在做多标签分类时,经常用
tf.math.top_k 获取前K个预测结果。比如推荐系统里,我们不是只推荐一个,而是推荐Top-5或Top-10。这个函数比 argmax 灵活得多。
4.5 综合实战:一个简单的线性回归
说了这么多,咱们来个小例子。用TensorFlow的运算操作,实现一个最简单的线性回归。
# 生成数据
x = tf.constant([[1.0], [2.0], [3.0], [4.0]])
y_true = tf.constant([[2.0], [4.0], [6.0], [8.0]]) # y = 2x
# 初始化参数
w = tf.Variable(0.0)
b = tf.Variable(0.0)
# 训练循环
for epoch in range(100):
with tf.GradientTape() as tape:
# 预测:矩阵乘法 + 广播
y_pred = x @ w + b # 广播机制自动处理 b
# 损失:规约操作
loss = tf.reduce_mean((y_pred - y_true) ** 2)
# 梯度计算
grads = tape.gradient(loss, [w, b])
# 参数更新:算术运算
w.assign_sub(0.01 * grads[0])
b.assign_sub(0.01 * grads[1])
if epoch % 20 == 0:
print(f'Epoch {epoch}: loss = {loss.numpy():.4f}')
print(f'Final: w = {w.numpy():.2f}, b = {b.numpy():.2f}')
你看,这个例子把本章的知识点全串起来了:矩阵乘法、广播机制、规约操作、算术运算。这就是TensorFlow运算操作的精髓。
好了,第四章就到这里。下一章我们聊聊数据加载和预处理,那是实际项目中更头疼的部分。有什么问题,欢迎随时交流。