在深度学习的世界里,张量(Tensor)是所有数据的载体,也是PyTorch框架的基石。无论你是刚接触Python的新手,还是熟悉C++、Java或Go的开发者,掌握张量操作都是迈向模型构建的第一步。本文将从零开始,系统讲解张量的创建、变形、索引、运算和广播机制,并用大量实例帮你避开常见陷阱。

一、张量是什么?为什么它如此重要?

张量本质上就是多维数组,但它比NumPy数组更强大——支持GPU加速和自动求导。你可以把它想象成“跑在GPU上的NumPy数组”。在PyTorch中,几乎所有数据(图像、文本、音频)和模型参数都以张量形式存在。

维度速查:标量是0维张量,向量是1维,矩阵是2维,彩色图像通常是4维(批次、通道、高、宽)。

很多新手一上来就写神经网络,却因为数据处理错误、形状不匹配、设备不一致而报错。根本原因就是张量操作不熟。本文将通过大量代码实例,带你系统掌握张量的核心操作,彻底告别形状错误。

二、创建张量的10种常用方法

首先导入PyTorch库:

import torch
import numpy as np

2.1 从数据直接创建

最直观的方式是从Python列表或NumPy数组创建张量:

# 从列表创建
a = torch.tensor([1, 2, 3])
b = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
print(a, a.dtype)  # torch.int64
print(b, b.dtype)  # torch.float32

2.2 与NumPy互相转换

张量和NumPy数组共享内存,转换几乎零开销:

y([1, 2, 3]) t = torch.from_numpy(arr) # numpy -> tensor,共享内存 t2 = torch.tensor(arr) # 会复制一份 # tensor 转 numpy n = t.numpy() # 同样共享内存

注意: 创建出来的张量和原数组共享内存,修改其中一个,另一个也会变。 则是深拷贝。

2.3 创建特殊张量

初始化常用全零、全一、单位矩阵或随机张量:

zeros = torch.zeros(2, 3)          # 全 0
ones  = torch.ones(2, 3)           # 全 1
eye   = torch.eye(3)               # 单位矩阵
rand  = torch.rand(2, 3)           # [0,1) 均匀分布
randn = torch.randn(2, 3)          # 标准正态分布 N(0,1)

2.4 按范围创建

类似Python的range():

arange = torch.arange(0, 10, step=2)   # tensor([0,2,4,6,8])
linspace = torch.linspace(0, 1, steps=5) # tensor([0.00, 0.25, 0.50, 0.75, 1.00])

2.5 创建同形状张量

保持形状不变,快速生成新张量:

x = torch.ones(2, 3)
y = torch.zeros_like(x)            # 形状同 x,全 0
z = torch.randn_like(x)            # 形状同 x,正态分布

2.6 指定数据类型和设备

⚠️ 重要:训练时务必统一dtype和device,否则会报RuntimeError:

f = torch.tensor([1, 2], dtype=torch.float32, device='cuda')

三、张量的基本属性

每个张量都自带以下属性,调试时非常有用:

t = torch.randn(2, 3, 4)
print(t.shape)        # torch.Size([2, 3, 4])
print(t.size())       # 功能同上
print(t.dtype)        # torch.float32
print(t.device)       # cpu 或 cuda:0
print(t.ndim)         # 维度数 3
print(t.numel())      # 元素总数 2*3*4=24
  • shape:维度大小元组
  • dtype:数据类型(如torch.float32)
  • device:所在设备(CPU/GPU)
  • ndim:维度数量
  • numel:元素总数

四、张量索引与切片:和NumPy一模一样

索引方式完全遵循Python/NumPy规范,上手极快:

t = torch.arange(12).reshape(3, 4)
print(t)
# tensor([[ 0,  1,  2,  3],
#         [ 4,  5,  6,  7],
#         [ 8,  9, 10, 11]])
# 取第 1 行
print(t[0])           # tensor([0, 1, 2, 3])
# 取第 1 列
print(t[:, 0])        # tensor([0, 4, 8])
# 区域切片
print(t[0:2, 1:3])    # 前 2 行,第 1-2 列
# 步长步取
print(t[::2, ::2])    # 每隔一行一列取一次

高级索引(Fancy Indexing)同样支持:

# 整数数组索引
idx = [0, 2]
print(t[idx])          # 取第 0 和第 2 行
# 布尔索引
mask = t > 5
print(t[mask])         # tensor([6, 7, 8, 9, 10, 11])

提示:如果你熟悉NumPy,这里几乎零学习成本。如果你更习惯C++或Java的数组操作,记住Python索引从0开始,且支持负索引。

五、张量变形:reshape、view、transpose、permute

变形是实际编码中出错最多的地方,务必牢记。

5.1 reshape vs view

x = torch.arange(12)
a = x.reshape(3, 4)        # 安全,但可能复制
b = x.view(3, 4)           # 必须内存连续,否则报错
c = x.contiguous().view(3, 4) # 保证内存连续后再 view

核心规则reshapeview 功能类似,但 view 要求张量内存连续,否则会报错;reshape 更保险,但在非连续时会复制一份,不共享数据。

5.2 增加/移除维度

x = torch.tensor([1, 2, 3])   # shape (3,)
print(x.unsqueeze(0))         # shape (1,3)  在第 0 维前加一维
print(x.unsqueeze(1))         # shape (3,1)  在第 1 维后加一维
y = torch.randn(1, 3, 1, 4)
print(y.squeeze())            # 移除所有大小为 1 的维度,变成 (3,4)
print(y.squeeze(0))           # 只移除第 0 维,若它等于 1

5.3 transpose 和 permute

t = torch.randn(2, 3, 4)
# 交换两维
t1 = t.transpose(0, 2)        # shape (4,3,2)
# 多重转置用 permute
t2 = t.permute(2, 1, 0)       # shape (4,3,2)

 一次只能交换两个维度, 可以一次性对全部维度重新排列。

5.4 扩维广播:unsqueeze 与 expand

a = torch.tensor([[1], [2], [3]])   # shape (3,1)
b = a.expand(3, 4)                  # 广播成 (3,4),不复制数据
c = a.repeat(1, 4)                  # 实际复制数据成 (3,4)

性能建议expand 只在需要时扩展,不分配新内存;repeat 是真正复制,占用更多内存。

六、张量的数学运算

6.1 基本运算

a = torch.tensor([1.0, 2.0])
b = torch.tensor([3.0, 4.0])
print(a + b)       # 按元素加
print(a * b)       # 按元素乘(不是矩阵乘法)
print(a @ b)       # 点积
print(a.pow(2))    # 平方
print(a.sqrt())    # 开方

6.2 矩阵乘法

x = torch.randn(2, 3)
y = torch.randn(3, 4)
result = torch.mm(x, y)        # 矩阵乘,结果 (2,4)
result = x @ y                 # 等价写法
# 对于批量矩阵乘用 torch.bmm 或 torch.matmul

6.3 聚合操作

t = torch.randn(3, 4)
print(t.sum())                 # 所有元素和
print(t.sum(dim=0))            # 按行方向求和(压缩第 0 维),形状 (4,)
print(t.sum(dim=1, keepdim=True)) # 按列方向求和并保持维度,形状 (3,1)
print(t.mean(), t.max(), t.min())
print(t.argmax(dim=1))         # 每行最大值的索引

 参数必须理解清楚: 就是把第 0 维压缩掉,你可以想象成“在这个方向上拍扁”。

七、广播机制(Broadcasting)

广播是PyTorch最重要的隐式操作之一,允许形状不同的张量运算时自动扩展。规则很简单:从最后一个维度向前对比,满足以下任一条件即可广播:

  • 两个维度大小相等
  • 其中一个维度是1
  • 其中一个维度不存在

示例:

a = torch.randn(3, 1)   # shape (3,1)
b = torch.randn(1, 4)   # shape (1,4)
c = a + b               # 广播成 (3,4)

常见错误:形状 (3,4) + (3,)(3,) 无法广播,因为最后一个维度4 vs 3不匹配。需要显式调整为 (3,1)(1,4)

八、张量在CPU与GPU间移动

x = torch.randn(3, 3)
if torch.cuda.is_available():
    device = torch.device("cuda")
    x_gpu = x.to(device)         # 搬到 GPU
    # 或者 x.cuda()
    x_cpu = x_gpu.cpu()          # 搬回 CPU

⚠️ 重要:模型和数据必须在同一设备上,否则报RuntimeError。如果你同时使用TypeScript或Go做后端服务,记得在推理前检查设备一致性。

九、张量与自动求导的初遇

张量通过 requires_grad=True 开启梯度追踪:

x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x.pow(2).sum()
y.backward()
print(x.grad)   # tensor([2., 4., 6.])

当对标量调用 backward() 时,所有路径上的梯度自动计算并累加到张量的 .grad 属性中。这是反向传播的核心机制。

这也是后面要详细讲解的 autograd 机制,现在你只需要知道张量自带这个超级能力。

[AFFILIATE_SLOT_1]

十、本讲总结与练习

今天我们全面拆解了张量的创建、索引、变形、运算和广播机制。掌握这些操作,你就拿到了玩转PyTorch的钥匙。与NumPy的相似性让你可以快速迁移经验,而GPU加速和自动求导则是PyTorch的独特优势。

试试做几个练习

  1. torch.randn 创建一个形状为 (4, 5) 的张量,提取出第1、3行和第2、4列构成的子矩阵。
  2. 实现一个形状为 (3, 1) 的张量与形状为 (4,) 的向量相加,结果形状是什么?
  3. 将上面创建的张量搬到GPU并验证device。
[AFFILIATE_SLOT_2]

如果你平时主要用Python或NumPy,张量操作会非常自然;如果你来自C++、Java或Go背景,多写多练就能快速掌握。下篇我们将继续拆解PyTorch的核心组件,敬请期待!

from_numpytorch.tensor()transposepermutedimsum(dim=0)