概述
上一篇文章已经跑通了第一个 Paddle 程序,知道了如何创建 Tensor、做基本计算、查看设备位置。这一篇要继续往下挖:真正写模型时,绝大多数初学错误都不是模型太复杂,而是 Tensor 的形状、数据类型、广播和索引没搞清楚。
典型问题包括:
- 矩阵乘法报错,不知道该看哪两个维度。
- 分类损失报错,不知道标签为什么要用
int64。 x + b能运行,但不理解b是怎么自动扩展的。- 切片后维度变了,后面的网络层输入对不上。
- 用
reshape强行改形状,结果把 batch 维和特征维搞混。 - 高级索引能取出数据,但不知道结果 shape 为什么是那样。
本篇文章围绕四个核心问题展开:
text
shape:Tensor 是什么结构
dtype:Tensor 中每个元素是什么类型
broadcast:形状不同为什么还能一起计算
index:如何从 Tensor 中取出或修改局部数据
这四个概念看起来基础,但它们贯穿所有深度学习任务:图像分类、文本分类、目标检测、OCR、推荐系统和大模型训练都离不开它们。
读完这篇文章,你应该能看到一个 Tensor 的 shape 就推断出它的含义,能根据任务选择合适的 dtype,能手动判断广播是否成立,并能写出常见索引和切片操作。
准备代码:统一使用一个打印函数
为了让示例更清晰,先准备一个小工具函数:
python
import paddle
def show(name, tensor):
print(f"\n{name}")
print("-" * 60)
print(tensor)
print("shape:", tensor.shape)
print("ndim:", tensor.ndim)
print("dtype:", tensor.dtype)
print("place:", tensor.place)
后面的代码都可以在同一个 Python 文件里运行。建议新建:
text
tensor_shape_dtype_broadcast_index.py
并在文件开头写入:
python
import paddle
paddle.seed(2026)
本文不依赖 GPU,CPU 环境即可运行。
形状基础:shape、ndim、axis、size 分别是什么
Paddle 官方文档中把 shape、ndim、axis、size 都作为理解 Tensor 形状的重要概念。先用一个三维 Tensor 来看:
python
import paddle
x = paddle.arange(0, 24, dtype="float32").reshape([2, 3, 4])
print(x)
print("shape:", x.shape)
print("ndim:", x.ndim)
print("numel:", x.numel())
输出含义是:
text
shape = [2, 3, 4]
ndim = 3
numel = 24
可以这样理解:
text
第 0 维:2 个块
第 1 维:每个块 3 行
第 2 维:每行 4 个元素
总元素个数:2 * 3 * 4 = 24
这些概念的关系如下:
| 概念 | 含义 | 示例 |
|---|---|---|
shape |
每个维度上的元素数量 | [2, 3, 4] |
ndim |
Tensor 有几个维度 | 3 |
axis |
指定沿哪个维度操作 | axis=0/1/2 |
numel() |
Tensor 中元素总数 | 24 |
很多 Paddle 报错都会带 shape 信息。训练时遇到错误,第一反应应该是打印:
python
print(x.shape)
print(x.dtype)
这比盲目改网络结构更有效。
形状读法:把常见任务的 shape 看懂
不同任务会使用不同的 Tensor 形状。下面是最常见的几类。
表格或向量数据
text
[batch_size, feature_dim]
例如:
python
x = paddle.randn([32, 10])
含义是:
- 32 个样本。
- 每个样本 10 个特征。
这种形状常见于多层感知机、回归模型、传统特征输入等场景。
图像数据
Paddle 图像模型中常见格式是 NCHW:
text
[batch_size, channels, height, width]
例如:
python
images = paddle.randn([8, 3, 224, 224])
含义是:
- 8 张图片。
- 每张图片 3 个通道。
- 高度 224。
- 宽度 224。
如果是灰度图,通道数通常是 1:
python
gray_images = paddle.randn([8, 1, 28, 28])
文本 token 数据
文本模型中常见形状是:
text
[batch_size, sequence_length]
例如:
python
token_ids = paddle.randint(0, 10000, [16, 128], dtype="int64")
含义是:
- 16 条文本。
- 每条文本最多 128 个 token。
- 每个位置是一个词表 ID。
如果进入 Embedding 层后,常见形状会变成:
text
[batch_size, sequence_length, hidden_size]
例如:
text
[16, 128, 768]
分类输出
分类模型输出通常是:
text
[batch_size, num_classes]
例如:
python
logits = paddle.randn([32, 10])
labels = paddle.randint(0, 10, [32], dtype="int64")
含义是:
logits:32 个样本,每个样本对应 10 个类别分数。labels:32 个样本的真实类别编号。
这里有一个关键点:分类标签不是 [32, 10] 的 one-hot 时,常见损失函数通常期待 [32] 的类别 ID,并且 dtype 通常是 int64。
reshape:改变形状但不改变元素总数
reshape 是最常用的形状变换方法之一。
python
import paddle
x = paddle.arange(0, 12, dtype="float32")
show("x", x)
y = x.reshape([3, 4])
show("y = x.reshape([3, 4])", y)
z = x.reshape([2, 2, 3])
show("z = x.reshape([2, 2, 3])", z)
reshape 的核心规则是:变换前后元素总数必须一致。
text
[12] -> [3, 4] 可以,因为 12 = 3 * 4
[12] -> [2, 2, 3] 可以,因为 12 = 2 * 2 * 3
[12] -> [5, 3] 不可以,因为 12 != 5 * 3
使用 -1 自动推断维度
-1 可以让 Paddle 自动推断这一维的大小:
python
x = paddle.arange(0, 24, dtype="float32")
a = x.reshape([2, -1])
b = x.reshape([-1, 3, 4])
show("a = x.reshape([2, -1])", a)
show("b = x.reshape([-1, 3, 4])", b)
推断过程是:
text
x 有 24 个元素
[2, -1] -> [2, 12]
[-1, 3, 4] -> [2, 3, 4]
注意:一个 shape 里通常只应该出现一个 -1。多个未知维度会让框架无法唯一推断。
reshape 的常见误区
reshape 只改变视图形状,不会理解你的业务含义。下面这类代码很危险:
python
images = paddle.randn([8, 3, 224, 224])
wrong = images.reshape([3, 8, 224, 224])
这段代码可能能运行,但它把 batch 维和 channel 维混在了一起,业务含义已经错了。图像数据的 [N, C, H, W] 不是随便换顺序;如果要换维度顺序,应该使用 transpose,而不是用 reshape 硬改。
reshape 改的是形状,不是维度语义;换维度顺序用 transpose。
squeeze 和 unsqueeze:处理尺寸为 1 的维度
深度学习里经常需要增加或删除大小为 1 的维度。
unsqueeze:增加一个维度
python
import paddle
x = paddle.to_tensor([1.0, 2.0, 3.0])
show("x", x)
y = paddle.unsqueeze(x, axis=0)
z = paddle.unsqueeze(x, axis=1)
show("unsqueeze axis=0", y)
show("unsqueeze axis=1", z)
形状变化是:
text
x: [3]
axis=0 -> [1, 3]
axis=1 -> [3, 1]
常见用途:
- 给单个样本增加 batch 维。
- 给向量增加可广播维度。
- 将
[H, W]转成[1, H, W]或[1, 1, H, W]。
squeeze:删除尺寸为 1 的维度
python
x = paddle.randn([1, 3, 1, 4])
y = paddle.squeeze(x)
z = paddle.squeeze(x, axis=0)
show("x", x)
show("squeeze all size-1 dims", y)
show("squeeze axis=0", z)
形状变化是:
text
[1, 3, 1, 4] -> squeeze() -> [3, 4]
[1, 3, 1, 4] -> squeeze(axis=0) -> [3, 1, 4]
使用 squeeze() 时要谨慎。如果 batch_size 恰好是 1,直接 squeeze() 可能把 batch 维也删掉,导致后续代码在 batch_size 大于 1 时行为不一致。
更稳的写法是明确指定 axis:
python
y = paddle.squeeze(x, axis=2)
transpose:调整维度顺序
当你需要交换维度顺序时,使用 transpose。
python
import paddle
x = paddle.randn([2, 3, 4])
y = paddle.transpose(x, perm=[0, 2, 1])
show("x", x)
show("transpose [0, 2, 1]", y)
形状变化是:
text
[2, 3, 4] -> [2, 4, 3]
这里 perm=[0, 2, 1] 表示:
text
新第 0 维 = 原第 0 维
新第 1 维 = 原第 2 维
新第 2 维 = 原第 1 维
在图像任务中,经常会遇到 HWC 和 CHW 的转换:
text
HWC: [height, width, channels]
CHW: [channels, height, width]
对应写法:
python
image_hwc = paddle.randn([224, 224, 3])
image_chw = paddle.transpose(image_hwc, perm=[2, 0, 1])
show("image_chw", image_chw)
不要用 reshape 做 HWC 到 CHW 的转换。 reshape 不会按维度语义重新排列数据,只是换一种形状解释底层元素。
dtype 基础:数据类型决定算子行为和训练稳定性
Paddle Tensor 中所有元素的数据类型相同,可以通过 tensor.dtype 查看。官方文档列出的常见类型包括:
boolfloat16float32float64uint8int8int16int32int64complex64complex128
先看一个简单示例:
python
import paddle
a = paddle.to_tensor([1, 2, 3])
b = paddle.to_tensor([1.0, 2.0, 3.0])
c = paddle.to_tensor([1.0, 2.0, 3.0], dtype="float64")
show("a from Python integers", a)
show("b from Python floats", b)
show("c specified float64", c)
常见默认规则:
- Python 整数通常创建为
int64。 - Python 浮点数通常创建为
float32。 - 也可以通过
dtype显式指定。
什么时候用 float32
大多数模型输入、模型参数、回归目标、损失计算都使用 float32。
python
features = paddle.to_tensor(
[[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]],
dtype="float32",
)
float32 是深度学习中的常规选择,在精度、性能和显存之间比较均衡。
什么时候用 int64
类别标签、词表 ID、索引通常使用 int64。
python
labels = paddle.to_tensor([0, 2, 1, 3], dtype="int64")
token_ids = paddle.randint(0, 30000, [2, 8], dtype="int64")
例如分类任务中:
python
logits = paddle.randn([4, 5], dtype="float32")
labels = paddle.to_tensor([0, 3, 1, 4], dtype="int64")
这里 logits 是浮点分数,labels 是类别编号。不要把标签写成:
python
labels = paddle.to_tensor([0.0, 3.0, 1.0, 4.0])
这会得到浮点标签,很多分类损失函数并不接受。
什么时候用 bool
布尔类型常用于掩码:
python
scores = paddle.to_tensor([0.1, 0.9, 0.3, 0.7], dtype="float32")
mask = scores > 0.5
show("mask", mask)
mask 的 dtype 是 bool,可以用于筛选、条件判断或后续构造 attention mask。
astype:转换数据类型
如果已经创建了 Tensor,可以使用 astype 转换类型:
python
x = paddle.to_tensor([1, 2, 3])
y = x.astype("float32")
show("x int64", x)
show("y float32", y)
常见转换场景:
- 图像像素从
uint8转成float32。 - 标签从默认整型整理成
int64。 - 某些统计结果需要转为浮点数计算。
示例:
python
pixels = paddle.to_tensor([[0, 127, 255]], dtype="uint8")
pixels_float = pixels.astype("float32") / 255.0
show("pixels_float", pixels_float)
隐式类型提升:能自动转,不代表应该依赖它
当不同 dtype 的 Tensor 一起计算时,框架可能进行类型提升。
python
import paddle
x = paddle.to_tensor([1, 2, 3], dtype="int64")
y = paddle.to_tensor([0.5, 0.5, 0.5], dtype="float32")
z = x + y
show("z = int64 + float32", z)
虽然这种写法可能正常运行,但不建议在关键训练逻辑中依赖隐式类型推断。更稳的写法是提前把输入整理成明确类型:
python
x = x.astype("float32")
z = x + y
经验规则:
- 模型输入统一整理成
float32。 - 分类标签统一整理成
int64。 - 索引类 Tensor 统一整理成
int64。 - 掩码统一整理成
bool或框架 API 明确要求的类型。
广播机制:为什么不同 shape 可以一起计算
广播是 Tensor 运算里非常重要的机制。官方文档说明:当较小形状的 Tensor 与较大形状的 Tensor 一起计算时,广播会在不进行真实数据拷贝的情况下,把较小 Tensor 按规则扩展到可匹配形状。
最简单例子:
python
import paddle
x = paddle.ones([2, 3], dtype="float32")
b = paddle.to_tensor([10.0, 20.0, 30.0])
y = x + b
show("x", x)
show("b", b)
show("y = x + b", y)
形状关系:
text
x: [2, 3]
b: [3]
b 会按行广播:
[[10, 20, 30],
[10, 20, 30]]
结果 y: [2, 3]
广播判断规则
判断两个 Tensor 是否能广播,可以按下面流程:
text
1. 从最后一个维度开始向前比较
2. 两个维度相等,可以广播
3. 其中一个维度是 1,可以广播
4. 其中一个维度不存在,可以广播
5. 否则不能广播
示例一:
text
x: [2, 3, 4]
y: [3, 4]
对齐后:
x: [2, 3, 4]
y: [1, 3, 4]
结果:[2, 3, 4]
代码:
python
x = paddle.ones([2, 3, 4])
y = paddle.ones([3, 4])
z = x + y
show("z", z)
示例二:
text
x: [2, 3, 1, 5]
y: [3, 4, 1]
对齐后:
x: [2, 3, 1, 5]
y: [1, 3, 4, 1]
逐维比较:
2 vs 1 -> 可以
3 vs 3 -> 可以
1 vs 4 -> 可以
5 vs 1 -> 可以
结果:[2, 3, 4, 5]
代码:
python
x = paddle.ones([2, 3, 1, 5])
y = paddle.ones([3, 4, 1])
z = x + y
show("broadcast result", z)
不能广播的例子
text
x: [2, 3, 4]
y: [2, 3, 6]
最后一维:
4 vs 6
不相等,也没有任何一个是 1,因此不能广播。
代码:
python
x = paddle.ones([2, 3, 4])
y = paddle.ones([2, 3, 6])
try:
z = x + y
except Exception as exc:
print(type(exc).__name__)
print(exc)
调试广播错误时,把 shape 写成右对齐形式最有效。
text
[2, 3, 4]
[2, 3, 6]
^
broadcast_to:显式扩展到目标形状
多数时候广播是自动发生的,但有时为了让代码更清楚,可以使用 paddle.broadcast_to()。
python
import paddle
b = paddle.to_tensor([10.0, 20.0, 30.0])
b2 = paddle.broadcast_to(b, shape=[2, 3])
show("b", b)
show("b2", b2)
这段代码表达得很明确:把 [3] 的向量广播成 [2, 3]。
注意:broadcast_to 不是随便复制成任意形状,它同样要符合广播规则。
可以广播:
python
x = paddle.ones([1, 3, 1])
y = paddle.broadcast_to(x, shape=[2, 3, 4])
show("y", y)
不能广播:
python
x = paddle.ones([2, 3])
try:
y = paddle.broadcast_to(x, shape=[2, 4])
except Exception as exc:
print(type(exc).__name__)
print(exc)
因为原始最后一维是 3,目标最后一维是 4,不相等且原始维度不是 1。
广播在模型里的真实用途
广播不是语法糖,它在模型里很常见。
给每个样本加同一个 bias
python
x = paddle.randn([4, 3])
b = paddle.to_tensor([0.1, 0.2, 0.3], dtype="float32")
y = x + b
show("y", y)
形状:
text
x: [4, 3]
b: [3]
y: [4, 3]
图像按通道归一化
假设图像 Tensor 是 [N, C, H, W]:
python
images = paddle.randn([8, 3, 224, 224])
mean = paddle.to_tensor([0.485, 0.456, 0.406], dtype="float32").reshape([1, 3, 1, 1])
std = paddle.to_tensor([0.229, 0.224, 0.225], dtype="float32").reshape([1, 3, 1, 1])
normalized = (images - mean) / std
show("normalized", normalized)
为什么 mean 要 reshape 成 [1, 3, 1, 1]?
text
images: [8, 3, 224, 224]
mean: [1, 3, 1, 1]
第 0 维:1 广播到 8
第 1 维:3 对 3
第 2 维:1 广播到 224
第 3 维:1 广播到 224
这就是广播在图像任务里的典型用法。
文本 mask 扩展维度
文本 attention mask 常从 [batch_size, seq_len] 扩展成更高维:
python
mask = paddle.to_tensor(
[[1, 1, 1, 0], [1, 1, 0, 0]],
dtype="float32",
)
expanded_mask = mask.reshape([2, 1, 1, 4])
show("expanded_mask", expanded_mask)
这样后续可以和 attention 分数做广播计算。
基础索引:像 Python 和 NumPy 一样取数据
Paddle 支持标准 Python 风格索引。
python
import paddle
x = paddle.arange(0, 12, dtype="float32").reshape([3, 4])
show("x", x)
show("x[0]", x[0])
show("x[1, 2]", x[1, 2])
show("x[:, 1]", x[:, 1])
show("x[1:, 2:]", x[1:, 2:])
原始 Tensor:
text
x =
[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]]
索引含义:
| 写法 | 含义 | 结果形状 |
|---|---|---|
x[0] |
第 0 行 | [4] |
x[1, 2] |
第 1 行第 2 列 | [] |
x[:, 1] |
所有行,第 1 列 | [3] |
x[1:, 2:] |
第 1 行到末尾,第 2 列到末尾 | [2, 2] |
注意:x[1, 2] 的结果是一个 0 维 Tensor,不是 Python float。如果需要 Python 数值,可以进一步使用:
python
value = x[1, 2].item()
print(value)
切片:start、end、step 的直觉
切片语法:
text
start:end:step
示例:
python
x = paddle.arange(0, 10)
show("x", x)
show("x[2:8]", x[2:8])
show("x[2:8:2]", x[2:8:2])
show("x[:5]", x[:5])
show("x[5:]", x[5:])
show("x[::-1]", x[::-1])
含义:
x[2:8]:从下标 2 到下标 8 之前。x[2:8:2]:从 2 到 8 之前,每隔 2 个取一个。x[:5]:从开头取到下标 5 之前。x[5:]:从下标 5 取到末尾。x[::-1]:反向取全部元素。
在二维 Tensor 中,逗号分隔不同维度:
python
x = paddle.arange(0, 20).reshape([4, 5])
show("x", x)
show("first two rows", x[:2, :])
show("last three columns", x[:, -3:])
show("every other row", x[::2, :])
保留维度:整数索引和切片索引的差异
整数索引会减少维度,切片索引通常保留维度。
python
x = paddle.randn([2, 3, 4])
a = x[0]
b = x[0:1]
show("x", x)
show("x[0]", a)
show("x[0:1]", b)
形状变化:
text
x: [2, 3, 4]
x[0]: [3, 4]
x[0:1]: [1, 3, 4]
这在模型推理时很重要。如果网络期待输入带 batch 维 [N, C, H, W],那么对单张图片更稳的做法是保留 batch 维:
python
single_image = images[0:1]
而不是:
python
single_image = images[0]
后者会从 [N, C, H, W] 变成 [C, H, W],可能导致模型输入维度不匹配。
布尔索引:用条件筛选数据
布尔条件会生成 bool Tensor:
python
import paddle
x = paddle.to_tensor([0.1, 0.8, 0.3, 0.9, 0.2])
mask = x > 0.5
show("mask", mask)
show("x[mask]", x[mask])
结果中只保留满足条件的位置。
二维示例:
python
x = paddle.arange(0, 12, dtype="float32").reshape([3, 4])
mask = x > 5
show("mask", mask)
show("x[mask]", x[mask])
注意:布尔索引通常会把结果拉平成一维,因为它返回所有满足条件的元素,而不再保留原来的矩阵结构。
如果你希望保留结构,可以使用 paddle.where:
python
x = paddle.arange(0, 12, dtype="float32").reshape([3, 4])
y = paddle.where(x > 5, x, paddle.zeros_like(x))
show("y", y)
这段代码表示:
text
大于 5 的位置保留原值
其他位置替换为 0
高级索引:按指定下标取元素
当你需要取指定行或指定位置时,可以使用高级索引。
按行取数据
python
x = paddle.arange(0, 20).reshape([5, 4])
rows = paddle.to_tensor([0, 2, 4], dtype="int64")
selected = x[rows]
show("x", x)
show("selected rows", selected)
形状关系:
text
x: [5, 4]
rows: [3]
selected: [3, 4]
按二维坐标取元素
python
x = paddle.arange(0, 12).reshape([3, 4])
row_ids = paddle.to_tensor([0, 1, 2], dtype="int64")
col_ids = paddle.to_tensor([3, 2, 1], dtype="int64")
selected = x[row_ids, col_ids]
show("selected", selected)
它取的是:
text
x[0, 3]
x[1, 2]
x[2, 1]
结果形状是 [3]。
使用 gather_nd 表达坐标索引
当坐标已经组织成矩阵时,可以使用 paddle.gather_nd:
python
x = paddle.arange(0, 12).reshape([3, 4])
index = paddle.to_tensor(
[[0, 3], [1, 2], [2, 1]],
dtype="int64",
)
selected = paddle.gather_nd(x, index)
show("selected by gather_nd", selected)
index 的每一行都是一个坐标:
text
[0, 3]
[1, 2]
[2, 1]
这类写法在目标检测、序列标注、推荐召回等任务中经常出现。
修改 Tensor 局部数据:setitem 的基本用法
Paddle 支持通过索引修改 Tensor。
python
import paddle
x = paddle.zeros([3, 4], dtype="float32")
x[0, 0] = 1.0
x[1, :] = paddle.to_tensor([2.0, 2.0, 2.0, 2.0])
x[:, 3] = 9.0
show("x after assignment", x)
这种写法适合构造测试数据、mask 或小规模调试。训练主流程中要谨慎使用频繁的原地修改,尤其是在涉及自动微分时,要注意是否影响计算图。
更函数式的写法可以用 paddle.where、scatter、gather 等 API。初学阶段先掌握索引赋值即可。
完整练习:模拟一个小 batch 的数据处理
下面用一个小例子把 shape、dtype、广播和索引串起来。
假设有 4 个样本,每个样本 3 个特征:
python
import paddle
features = paddle.to_tensor(
[
[10.0, 100.0, 1.0],
[20.0, 200.0, 0.0],
[30.0, 300.0, 1.0],
[40.0, 400.0, 0.0],
],
dtype="float32",
)
labels = paddle.to_tensor([0, 1, 0, 1], dtype="int64")
mean = paddle.mean(features, axis=0)
std = paddle.std(features, axis=0)
normalized = (features - mean) / (std + 1e-6)
positive_features = normalized[labels == 1]
show("features", features)
show("labels", labels)
show("mean", mean)
show("std", std)
show("normalized", normalized)
show("positive_features", positive_features)
这里发生了几件事:
features是[4, 3],表示 4 个样本、3 个特征。labels是[4],dtype 是int64,表示分类标签。mean和std是[3],表示每个特征维度的统计量。(features - mean)使用广播,将[3]扩展到[4, 3]。labels == 1得到[4]的 bool mask。normalized[labels == 1]选出标签为 1 的样本,结果形状是[2, 3]。
这段代码虽然简单,但已经很接近真实机器学习预处理流程。
常见错误:shape、dtype、广播、索引排查清单
错误一:reshape 后元素总数不一致
python
x = paddle.arange(0, 10)
try:
y = x.reshape([3, 4])
except Exception as exc:
print(type(exc).__name__)
print(exc)
原因:
text
10 != 3 * 4
排查方式:
python
print(x.numel())
错误二:矩阵乘法维度不匹配
python
a = paddle.randn([2, 3])
b = paddle.randn([2, 4])
try:
c = paddle.matmul(a, b)
except Exception as exc:
print(type(exc).__name__)
print(exc)
判断方式:
text
[2, 3] @ [2, 4]
3 != 2
正确写法之一:
python
b = paddle.randn([3, 4])
c = paddle.matmul(a, b)
错误三:分类标签 dtype 错误
错误倾向:
python
labels = paddle.to_tensor([0.0, 1.0, 2.0], dtype="float32")
更常见的分类标签写法:
python
labels = paddle.to_tensor([0, 1, 2], dtype="int64")
排查方式:
python
print(labels.dtype)
错误四:广播维度从左边开始对齐
很多初学者会误以为广播从第 0 维开始比较。实际应从最后一个维度开始比较。
text
x: [2, 3, 4]
y: [3, 4]
对齐方式:
x: [2, 3, 4]
y: [1, 3, 4]
不是:
text
x: [2, 3, 4]
y: [3, 4, ?]
错误五:整数索引导致 batch 维丢失
python
images = paddle.randn([8, 3, 224, 224])
bad = images[0]
good = images[0:1]
print(bad.shape)
print(good.shape)
输出:
text
[3, 224, 224]
[1, 3, 224, 224]
模型通常需要 batch 维,因此推理单样本时优先保留 [1, C, H, W]。
错误六:布尔索引后结构被拉平
python
x = paddle.arange(0, 12).reshape([3, 4])
selected = x[x > 5]
print(selected.shape)
这不是 [3, 4] 的结构,而是所有满足条件元素组成的一维结果。如果想保留原结构,使用:
python
y = paddle.where(x > 5, x, paddle.zeros_like(x))
调试方法:看到 Tensor 错误先打印这 5 个信息
建议在调试脚本中保留一个函数:
python
def debug_tensor(name, tensor):
print(f"{name}:")
print(" shape:", tensor.shape)
print(" ndim:", tensor.ndim)
print(" dtype:", tensor.dtype)
print(" place:", tensor.place)
print(" stop_gradient:", tensor.stop_gradient)
遇到错误时打印:
python
debug_tensor("x", x)
debug_tensor("y", y)
通常能快速定位:
- shape 是否匹配。
- dtype 是否符合算子要求。
- Tensor 是否在同一设备。
- 是否意外丢失 batch 维。
- 是否需要
unsqueeze或reshape。
一句话经验:Paddle 初学阶段,调试不是先看模型代码,而是先看每个关键 Tensor 的结构。
总结
这一篇围绕 Paddle Tensor 的四个核心基础展开:
- shape:描述 Tensor 的结构,排查维度错误时第一时间查看。
- dtype :描述元素类型,模型输入常用
float32,分类标签常用int64。 - broadcast:允许不同形状 Tensor 一起计算,判断时从最后一维向前比较。
- index:用于取出、筛选和修改 Tensor 局部数据,注意整数索引和切片索引对维度的影响。
理解这四个核心对于写模型至关重要。