1. 二维张量基础概念解析

在PyTorch中,二维张量(2D Tensor)是最常用的数据结构之一,它本质上就是一个矩阵的数值容器。与数学中的矩阵概念完全对应,我们可以通过行和列的索引来访问其中的元素。比如一个3x4的二维张量,就相当于3行4列的矩阵。

注意:PyTorch中的张量(Tensor)与NumPy中的数组(ndarray)非常相似,但关键区别在于张量可以在GPU上加速计算,这是深度学习框架的核心优势。

创建二维张量最直接的方式是使用torch.tensor()构造函数:

import torch

# 从Python列表创建
matrix = torch.tensor([[1, 2, 3], 
                      [4, 5, 6]])
print(matrix.shape)  # 输出:torch.Size([2, 3])

这里.shape属性会返回张量的维度信息,对于二维张量总是显示为(行数, 列数)。在实际项目中,我们更常用的是随机初始化或特定值初始化的方法:

# 随机初始化(均匀分布)
random_matrix = torch.rand(3, 4)  # 3行4列,值在0-1之间

# 全零矩阵
zeros_matrix = torch.zeros(2, 2)

# 单位矩阵
eye_matrix = torch.eye(3)  # 3x3单位矩阵

2. 核心操作与数学运算

2.1 基本索引与切片

二维张量的索引方式与Python列表类似,但支持更灵活的多维访问:

matrix = torch.tensor([[1, 2, 3],
                      [4, 5, 6],
                      [7, 8, 9]])

# 获取单个元素
print(matrix[0, 1])  # 输出:2(第0行第1列)

# 获取整行
print(matrix[1])  # 输出:tensor([4, 5, 6])

# 获取子矩阵
print(matrix[:2, 1:])  # 前两行,第1列及之后
# 输出:
# tensor([[2, 3],
#        [5, 6]])

实操技巧:PyTorch的切片操作返回的是原张量的视图(view),这意味着修改切片会影响原张量。如果需要独立副本,记得使用.clone()方法。

2.2 矩阵运算

PyTorch支持所有常见的矩阵运算:

a = torch.tensor([[1, 2], [3, 4]])
b = torch.tensor([[5, 6], [7, 8]])

# 矩阵加法(逐元素)
print(a + b)
# 输出:
# tensor([[ 6,  8],
#        [10, 12]])

# 矩阵乘法(点积)
print(torch.matmul(a, b))
# 输出:
# tensor([[19, 22],
#        [43, 50]])

# 转置操作
print(a.T)
# 输出:
# tensor([[1, 3],
#        [2, 4]])

对于更复杂的线性代数运算,PyTorch提供了torch.linalg模块:

# 计算行列式
det = torch.linalg.det(a)
print(det)  # 输出:-2.0

# 计算逆矩阵
inv_a = torch.linalg.inv(a)
print(inv_a)
# 输出:
# tensor([[-2.0000,  1.0000],
#        [ 1.5000, -0.5000]])

3. 广播机制与形状操作

3.1 广播规则

PyTorch的广播机制允许在不同形状的张量之间进行运算:

matrix = torch.tensor([[1, 2, 3],
                      [4, 5, 6]])

# 标量广播
print(matrix + 10)
# 输出:
# tensor([[11, 12, 13],
#        [14, 15, 16]])

# 向量广播
row_vector = torch.tensor([1, 0, -1])
print(matrix + row_vector)
# 输出:
# tensor([[2, 2, 2],
#        [5, 5, 5]])

广播遵循严格的规则:从最后一个维度开始向前比较,两个张量在每个维度上要么大小相同,要么其中一个为1,要么其中一个不存在。

3.2 形状变换

改变张量形状是常见操作,但需要注意元素总数不变:

matrix = torch.arange(12)  # 0到11的一维张量
matrix_2d = matrix.reshape(3, 4)  # 改为3x4
print(matrix_2d)
# 输出:
# tensor([[ 0,  1,  2,  3],
#        [ 4,  5,  6,  7],
#        [ 8,  9, 10, 11]])

# 展平操作
flattened = matrix_2d.flatten()
print(flattened)  # 输出:tensor([ 0,  1,  2, ..., 11])

避坑指南:reshape()和view()都能改变形状,但view()要求张量在内存中是连续的,否则会报错。当不确定时,建议使用.contiguous()确保连续性。

4. 高级应用与性能优化

4.1 批量矩阵运算

在深度学习中,我们经常需要处理批量数据,这时可以使用三维张量(批量×行×列):

batch_size = 3
batch_matrices = torch.randn(batch_size, 2, 3)  # 3个2x3矩阵

# 批量矩阵乘法
weights = torch.randn(3, 4)  # 3x4权重矩阵
result = torch.bmm(batch_matrices, weights)  # 结果形状:(3,2,4)

4.2 GPU加速

将张量移动到GPU可以显著加速计算:

if torch.cuda.is_available():
    device = torch.device("cuda")
    matrix_gpu = matrix.to(device)
    # 后续运算将在GPU上执行

4.3 自动微分

PyTorch的核心特性是自动微分,这对训练神经网络至关重要:

x = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)
y = x.pow(2).sum()  # 计算x中所有元素的平方和
y.backward()  # 自动计算梯度
print(x.grad)  # 导数应为2x
# 输出:
# tensor([[2., 4.],
#        [6., 8.]])

5. 实际应用案例:图像处理

二维张量在图像处理中应用广泛,灰度图像可以直接表示为二维张量:

from PIL import Image
import numpy as np

# 加载灰度图像
img = Image.open("example.jpg").convert("L")
img_array = np.array(img)
img_tensor = torch.from_numpy(img_array).float() / 255.0  # 归一化

# 应用简单的卷积滤波器
kernel = torch.tensor([[0, -1, 0],
                      [-1, 5, -1],
                      [0, -1, 0]])

# 手动实现卷积(简化版)
def conv2d(image, kernel):
    h, w = image.shape
    kh, kw = kernel.shape
    pad_h, pad_w = kh//2, kw//2
    padded = torch.zeros(h+2*pad_h, w+2*pad_w)
    padded[pad_h:-pad_h, pad_w:-pad_w] = image
    
    result = torch.zeros_like(image)
    for i in range(h):
        for j in range(w):
            result[i,j] = (padded[i:i+kh, j:j+kw] * kernel).sum()
    return result

filtered = conv2d(img_tensor, kernel)

6. 常见问题排查

6.1 形状不匹配错误

a = torch.rand(2, 3)
b = torch.rand(3, 2)
try:
    c = a + b  # 会引发错误
except RuntimeError as e:
    print(e)  # 输出:The size of tensor a (3) must match the size of tensor b (2) at non-singleton dimension 1

解决方案:使用广播机制或调整张量形状:

# 方法1:转置其中一个矩阵
c = a + b.T

# 方法2:使用expand_as
c = a + b.expand_as(a)

6.2 内存不连续问题

matrix = torch.rand(3, 4)[::2, :]  # 跨步切片
try:
    view = matrix.view(2, 4)  # 会失败
except RuntimeError as e:
    print(e)  # 输出:view size is not compatible with input tensor's size and stride

解决方案:使用.contiguous()方法:

matrix_contig = matrix.contiguous()
view = matrix_contig.view(2, 4)  # 现在可以工作

6.3 自动微分相关错误

x = torch.rand(2, 2)
y = x.pow(2)
try:
    y.backward()  # 会失败
except RuntimeError as e:
    print(e)  # 输出:grad can be implicitly created only for scalar outputs

解决方案:对于非标量输出,需要提供gradient参数:

grad_output = torch.ones_like(y)
y.backward(gradient=grad_output)
print(x.grad)  # 现在可以正确计算梯度

7. 性能优化技巧

  1. 向量化操作 :尽量避免Python循环,使用内置的向量化操作:
# 不好的做法
result = torch.zeros(100, 100)
for i in range(100):
    for j in range(100):
        result[i,j] = i + j

# 好的做法
i = torch.arange(100).view(-1, 1)
j = torch.arange(100).view(1, -1)
result = i + j  # 广播机制自动处理
  1. 原地操作 :使用后缀为"_"的操作可以节省内存:
a = torch.rand(3, 3)
a.add_(1)  # 原地加1,不创建新张量
  1. 预分配内存 :对于需要逐步填充的张量,预先分配好内存:
# 不好的做法
result = torch.tensor([])
for i in range(100):
    result = torch.cat([result, torch.rand(10)])

# 好的做法
result = torch.empty(1000)  # 预分配
for i in range(100):
    result[i*10:(i+1)*10] = torch.rand(10)
  1. 使用torch.no_grad() :在不需要计算梯度时禁用自动微分:
with torch.no_grad():
    # 这里面的操作不会跟踪计算图
    big_matrix = torch.rand(1000, 1000)
    result = big_matrix @ big_matrix.T
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐