Tensor含义

Tensor(张量)可以看作是一个多维数组,它是标量、向量和矩阵向更高维度的扩展。

张量维度

数学等价物

实例说明(PyTorch创建示例)

0维

标量 (Scalar)

单个数值,如损失值:tensor(3.1416)

1维

向量 (Vector)

一维数组,如特征向量:tensor([1, 2, 3])

2维

矩阵 (Matrix)

二维数组,如全连接层权重:tensor([[1, 2], [3, 4]])

3维及以上

高阶张量

如RGB图像(3, 224, 224)、图像批次(16, 3, 224, 224)

Tensor的关键属性

  • 数据类型(dtype):指定张量中常见的数据类型,如torch.float32、torch.float64、torch.int64、torch.bool等
  • 设备(device):表明张量当前存储在何处,是cpu还是cuda:0(GPU)等
  • 形状(shape):一个元组,表示张量在每个维度上的大小。
  • 是否需要梯度(requires_grad):一个布尔值,指示是否需要为张量计算梯度。

Tensor常见操作

创建Tensor

1. numpy转tensor

import numpy as np
import torch 

numpy_array = np.array([1, 1, 1])

tensor = torch.from_numpy(numpy_array)

print(tensor)


"""
tensor([1, 1, 1])
"""

2. 列表转tensor

import torch

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

3. 利用大写接受以shape创建

import torch

torch.Tensor(3, 2)
tensor([[2.1439e-34, 0.0000e+00],
        [2.7678e-04, 4.5817e-41],
        [4.4842e-44, 0.0000e+00]])


torch.empty(3, 2)
tensor([[3.8401e-34, 0.0000e+00],
        [3.8398e-34, 0.0000e+00],
        [1.1210e-43, 0.0000e+00]])

4. 随机创建tensor

import torch
torch.rand(2, 3)
tensor([[0.3263, 0.2546, 0.9332],
        [0.3767, 0.1792, 0.3704]])

tensor的切片(start:end:step)

如果要存一张RGB的minist(28*28)图片

image = torch.rand(4, 3, 28, 28)

image[1] # 获取第2张图片

image[0, 0].shape # 获取第1张图片第0个通道的图片形状

image[0, 0, 2, 4] # 返回像素灰度值的标量

# ...

tensor的索引查找(index_select/masked_select/take)

x1 = np.array([[1,0,0,1],[1,1,0,1],[0,1,1,0],[0,1,1,1],[1,1,0,0]])
x2 = np.array([[1,0,1],[1,1,1]])

x1= torch.from_numpy(x1).long()
x2= torch.from_numpy(x2).long()

tensor的拼接与拆分

拼接

cat拼接

torch.cat(torsors, dim)

import torch

a = torch.rand(4, 3, 18, 18)
b = torch.rand(5, 3, 18, 18)
c = torch.rand(4, 2, 18, 18)

torch.cat([a, b], dim=0) # 拼接得到(9, 3, 18, 18)的数据
torch.cat([a, c], dim=1) # 拼接得到(4, 5, 18, 18)的数据

stack拼接
>>> import torch
>>> a = torch.rand(4, 3, 18, 18)
>>> b = torch.rand(4, 3, 18, 18)
>>> torch.stack([a, b], dim=0).shape
torch.Size([2, 4, 3, 18, 18])
>>> 

拆分

split拆分 (按尺寸拆分)

torch.split()

>>> a  = torch.rand(4, 4)
>>> b = a.split(1, dim=0) # 对第0维按照1个一份进行拆分,拆分得到4个形状为(1, 4)的张量
>>> a
tensor([[0.3690, 0.0144, 0.2122, 0.7564],
        [0.1500, 0.1695, 0.3577, 0.3648],
        [0.7477, 0.8034, 0.0912, 0.0237],
        [0.2286, 0.0323, 0.5477, 0.3168]])
>>> b
(tensor([[0.3690, 0.0144, 0.2122, 0.7564]]), tensor([[0.1500, 0.1695, 0.3577, 0.3648]]), tensor([[0.7477, 0.8034, 0.0912, 0.0237]]), tensor([[0.2286, 0.0323, 0.5477, 0.3168]]))
>>> 

>>> c = a.split(2, dim=0)
>>> a
tensor([[0.8824, 0.4966, 0.5721, 0.3487],
        [0.3847, 0.2010, 0.5711, 0.4622],
        [0.7004, 0.3376, 0.7784, 0.9710],
        [0.9798, 0.1327, 0.9444, 0.1308]])
>>> c
(tensor([[0.8824, 0.4966, 0.5721, 0.3487],
        [0.3847, 0.2010, 0.5711, 0.4622]]), tensor([[0.7004, 0.3376, 0.7784, 0.9710],
        [0.9798, 0.1327, 0.9444, 0.1308]]))


# 不同长度进行拆分,(4, 4) 拆成 (1, 4)和 (3, 4)
>>> d, f = a.split([1, 3], dim=0)
>>> a
tensor([[0.8824, 0.4966, 0.5721, 0.3487],
        [0.3847, 0.2010, 0.5711, 0.4622],
        [0.7004, 0.3376, 0.7784, 0.9710],
        [0.9798, 0.1327, 0.9444, 0.1308]])
>>> d
tensor([[0.8824, 0.4966, 0.5721, 0.3487]])
>>> f
tensor([[0.3847, 0.2010, 0.5711, 0.4622],
        [0.7004, 0.3376, 0.7784, 0.9710],
        [0.9798, 0.1327, 0.9444, 0.1308]])
chunk拆分(按块数拆分)
>>> a = torch.rand(4, 4)
>>> a.chunk(4, dim=0) # 将张量依第0维拆分成4个(1, 3, 18, 18)的张量,等效于a.split(1, dim=0)
(tensor([[0.7528, 0.9029, 0.5109, 0.1446]]), tensor([[0.7029, 0.9443, 0.9600, 0.4517]]), tensor([[0.2038, 0.8926, 0.0444, 0.0144]]), tensor([[0.5201, 0.1761, 0.5112, 0.8281]]))
>>> a
tensor([[0.7528, 0.9029, 0.5109, 0.1446],
        [0.7029, 0.9443, 0.9600, 0.4517],
        [0.2038, 0.8926, 0.0444, 0.0144],
        [0.5201, 0.1761, 0.5112, 0.8281]])

那么chunk和split有啥区别?

在torch中,chunk和split都用于拆分张量,他们的核心区别在于指定的拆分方式不同:chunk指定的是想要得到的块数,而split指定的是每块的尺寸

特性

torch.chunk

torch.split

核心逻辑

按块数拆分:指定希望将张量在某个维度上分成几块。

按尺寸拆分:指定拆分后每块在该维度上的大小。

关键参数

chunks(整数):目标块数。

split_size_or_sections(整数或整数列表):每块的尺寸。

尺寸计算

每块尺寸 = 总尺寸 / 块数 (尽可能平均)。

尺寸由用户直接指定。

整除问题

若不能整除,前几块尺寸会稍大,最后一块包含余下部分。实际块数可能小于指定值。

若按整数拆分且不能整除,最后一块尺寸会较小。实际块数由总尺寸和指定尺寸决定。

灵活性

相对固定,目标是均分。

更灵活,可通过整数列表精确控制每一块的大小。

典型场景

需要将张量等分成N份,例如数据并行、分批处理。

1. 需要每块具有固定尺寸。
2. 需要不规则拆分,例如按特定比例分割数据集。

重塑

view

在 PyTorch 中,torch.Tensor.view() 方法用于重塑张量(Tensor)的形状,它返回一个与原张量数据共享内存的新张量(即修改新张量会影响原张量)。其核心作用是在不改变数据的情况下,调整张量的维度和大小。

基本用法
view(*shape) -> Tensor
  • 参数shape 是一个整数序列,表示新张量的形状。可以包含 -1,表示该维度的大小由其他维度自动计算。
  • 返回值:重塑后的新张量,与原张量共享数据。
>>> x = torch.rand(6)
>>> x
tensor([0.9957, 0.2430, 0.0249, 0.4692, 0.8152, 0.2445])
>>> x.shape
torch.Size([6])
>>> x.view(2, 3)
tensor([[0.9957, 0.2430, 0.0249],
        [0.4692, 0.8152, 0.2445]])
>>> x
tensor([0.9957, 0.2430, 0.0249, 0.4692, 0.8152, 0.2445])
>>> y = x.view(2, -1)
>>> y
tensor([[0.9957, 0.2430, 0.0249],
        [0.4692, 0.8152, 0.2445]])
>>> x
tensor([0.9957, 0.2430, 0.0249, 0.4692, 0.8152, 0.2445])
>>> y[0] += 1
>>> y
tensor([[1.9957, 1.2430, 1.0249],
        [0.4692, 0.8152, 0.2445]])
>>> x
tensor([1.9957, 1.2430, 1.0249, 0.4692, 0.8152, 0.2445])
>>> 

在 PyTorch 中,view() 和 reshape() 都是用于重塑张量形状的核心方法,但二者在内存连续性要求、数据内存共享策略、适用场景上有显著区别。下面从核心差异、使用场景、底层实现三个维度详细拆解。

一、核心区别对比
特性 view() reshape()
内存连续性要求 要求原张量是连续的(内存中按顺序存储,无间隔),否则直接调用会报错。 无连续性要求,自动处理非连续张量(内部会判断是否需要转换为连续张量)。
内存共享策略 始终与原张量共享内存(修改新张量会影响原张量)。 可能共享内存,也可能创建数据副本:1. 若原张量连续,行为与 view() 一致(共享内存);2. 若原张量非连续,会创建新的连续张量副本(不共享内存)。
功能灵活性 相对受限,仅适用于连续张量。 更灵活,是 view() 的超集,兼容所有重塑场景。
底层实现 直接修改张量的维度元信息(stride/shape),无数据拷贝,效率极高。 内部逻辑:1. 若张量连续 → 调用 view();2. 若张量非连续 → 调用 contiguous() + view()(会拷贝数据)。
二、具体使用场景与示例
1. 当张量是连续的时:二者行为一致

此时 reshape() 等价于 view(),均共享内存,无数据拷贝。

python

运行

import torch

# 创建连续张量(默认创建的张量都是连续的)
x = torch.tensor([[1, 2, 3], [4, 5, 6]])  # shape: (2, 3),is_contiguous() → True

# view() 重塑
y_view = x.view(3, 2)
# reshape() 重塑
y_reshape = x.reshape(3, 2)

# 修改新张量,原张量也会被改变(共享内存)
y_view[0, 0] = 100
print(x)  # 输出: tensor([[100,   2,   3], [  4,   5,   6]])
y_reshape[1, 1] = 200
print(x)  # 输出: tensor([[100,   2,   3], [  4,   5, 200]])
2. 当张量是非连续的时:二者行为不同

张量经过 transpose()/permute() 等操作后,会变成非连续(内存存储顺序与维度顺序不一致),此时 view() 会报错,而 reshape() 能正常工作。

python

运行

# 转置张量(转置后张量变为非连续)
x = torch.tensor([[1, 2, 3], [4, 5, 6]])
x_trans = x.transpose(0, 1)  # shape: (3, 2),is_contiguous() → False

# 直接调用 view() 会报错
try:
    x_trans.view(6)
except RuntimeError as e:
    print("view() 报错:", e)  # 输出:view size is not compatible with input tensor's size and stride

# reshape() 可正常工作(内部创建了数据副本)
x_reshape = x_trans.reshape(6)
x_reshape[0] = 100
print(x)  # 输出: tensor([[1, 2, 3], [4, 5, 6]])(原张量未被修改,因为是副本)

# 若要让 view() 工作,需先调用 contiguous() 转换为连续张量
x_trans_cont = x_trans.contiguous()
x_view = x_trans_cont.view(6)
x_view[0] = 100
print(x)  # 输出: tensor([[100, 2, 3], [4, 5, 6]])(共享内存,原张量被修改)
三、如何选择?
  1. 优先用 reshape()

    • 如果你不确定张量是否连续(比如经过转置、维度交换后),reshape() 更安全,无需手动处理连续性。
    • 对代码简洁性要求高,不想关注内存细节时。
  2. 用 view() 的场景

    • 明确知道张量是连续的(比如刚创建的张量、经过 contiguous() 后的张量),且需要极致的效率(无数据拷贝)。
    • 强制共享内存(修改新张量必须影响原张量)时。
四、补充:reshape() 与 clone() 的配合

如果希望重塑后的张量与原张量完全独立(不共享内存),可在 reshape() 后调用 clone()

x = torch.tensor([[1, 2, 3], [4, 5, 6]])
x_reshape = x.reshape(6).clone()
x_reshape[0] = 100
print(x)  # 输出: tensor([[1, 2, 3], [4, 5, 6]])(原张量无变化)
总结
  • view():轻量级重塑,仅适用于连续张量,始终共享内存,效率高但有使用限制。
  • reshape():通用型重塑,兼容所有张量,灵活安全,是日常开发的首选。

简单记:reshape() 是 view() 的 “增强版”,解决了 view() 对非连续张量的限制,代价是在非连续场景下会产生数据副本。

tensor的运算

乘法

#tensor直接相*,其结果为element wise,矩阵乘法为torch.matmul或者@ #高维tensor矩阵相乘实际上是对多个二维矩阵进行并行运算 a=torch.rand(4,3,28,64) b=torch.rand(4,3,64,32) a@b#结果得到的形状为(4,3,28,32)

>>> a = torch.rand(2, 2)
>>> b = torch.rand(2, 3)
>>> a
tensor([[0.9662, 0.4762],
        [0.6365, 0.2129]])
>>> b
tensor([[0.6583, 0.9811, 0.2332],
        [0.5010, 0.9111, 0.3308]])
>>> a @ b 
tensor([[0.8746, 1.3818, 0.3828],
        [0.5257, 0.8185, 0.2189]])
>>> 
>>> a = torch.rand(4, 16, 28, 28)
>>> b = torch.rand(4, 16, 28, 32)
>>> (a @ b).shape
torch.Size([4, 16, 28, 32])
平方/开方
>>> a = torch.full([2, 2], 2) # 创建一个(2, 2)的全2矩阵
>>> a
tensor([[2, 2],
        [2, 2]])
>>> a.pow(2) # a的每个元素都平方,等价于 a ** 2
tensor([[4, 4],
        [4, 4]])
>>> a ** 2
tensor([[4, 4],
        [4, 4]])
>>> 

#开方
a.sqrt()#平方根
a**0.5#等价于上一句

#exp,log
a.torch.exp(torch.ones(2,2))
torch.log(a)


#近似
#floor()向下取整,ceil()向上取整,round()四舍五入。

#取整取小数
#trunc()取整,frac()取小数

#clamp取范围
a=torch.tensor([[3,5],[6,8]])
a.clamp(6)#得到[[6,6],[6,8]],小于6的都变为6
a.clamp(5,6)#得到[[5,5],[6,6]],小于下限变为下限,大于上限变为上限。

爱因斯坦标示库

代码操作概览

操作类别

代码示例

功能描述

关键参数说明

1. 转置

rearrange(x, 'b c h w -> b h c w')

交换维度c(通道)和h(高度)的顺序。

模式字符串直观表示维度重排。

2. 变形

rearrange(x, 'b c h w -> (b c) h w')

将批次和通道维度合并,改变张量形状而不改变数据顺序。

括号()表示合并维度。

3. 图像分块

rearrange(x, 'b c (h1 p1) (w1 p2) -> b c (h1 w1) (p1 p2)', p1=2, p2=2)

将图像分割成不重叠的小块(例如2x2的patch)。

(h1 p1)表示将原高度维度拆分为两个新维度。

4. 池化/规约

reduce(x, 'b c h w -> b c', 'max')

对高度和宽度维度进行最大化操作,实现全局最大池化。

模式定义输出形状,'max'指定规约操作。

5. 堆叠张量列表

rearrange(tensor_list, 'n b c h w -> n b c h w')

将张量列表堆叠成一个新的张量,增加一个批次维度。

对张量列表进行操作。

6. 扩维与复制

rearrange(x, 'b c h w -> b c h w 1')

在张量末尾增加一个大小为1的维度。

类似torch.unsqueeze

repeat(out1, 'b c h w 1 -> b c h w 2')

在新增的维度上将数据复制2次。

类似torch.tile

repeat(x, 'b c h w -> b c (2 h) (2 w)')

在空间维度上进行上采样,将图像尺寸放大2倍。

(2 h)表示将原高度维度数据按特定模式重复。

import torch
from einops import rearrange, reduce, repeat

x = torch.randn(2,3,4,4) # 4D Tensor: bs*c*h*w
## 1. 转置
out1 = x.transpose(1,2)
out2 = rearrange(x, 'b c h w -> b h c w')

## 2. 变形
out1 = x.reshape(6,4,4)
out2 = rearrange(x, 'b c h w -> (b c) h w')
out3 = rearrange(out2, '(b c) h w -> b c h w', b=2)

## 3. image2patch
out1 = rearrange(x, 'b c (h1 p1) (w1 p2) -> b c (h1 w1) (p1 p2)', p1=2, p2=2)  # p1,p2是patch的高度和宽度
out2 = rearrange(out1, 'b c n a -> b n (a c)')  # shape:[batchsize, num_patch, patch_depth]

## 4. 求平均池化
out1 = reduce(x, 'b c h w  -> b c h', 'mean') # mean, min, max, sum, prod
out2 = reduce(x, 'b c h w  -> b c h 1', 'sum') # keep dimension
out3 = reduce(x, 'b c h w  -> b c', 'max')

## 5. 堆叠张量
tensor_list = [x, x, x]
out1 = rearrange(tensor_list, 'n b c h w -> n b c h w')

## 6. 扩维与复制
out1 = rearrange(x, 'b c h w -> b c h w 1') # 类似 torch.unsqueeze
out2 = repeat(out1, 'b c h w 1 -> b c h w 2')  # 类似 torch.tile
out3 = repeat(x, 'b c h w -> b c (2 h) (2 w)') 

为什么使用 einops

从您提供的代码对比和上面的讲解可以看出,einops的核心优势在于代码的可读性和意图的清晰表达

  • 声明式语法:你只需要描述“你想让张量变成什么样子”(如 -> b c (2 h) (2 w)),而不需要一步步指挥程序“如何去做”(比如先unsqueezetile)。

  • 统一的API:无论是转置、重塑、切片、重复还是规约操作,都可以通过 rearrangerepeatreduce这几个函数和统一的模式字符串来完成,大大降低了记忆负担。

  • 内置维度检查einops会自动验证变换模式的合理性,有效减少因维度计算错误导致的Bug

参考资料:

https://cloud.tencent.com/developer/article/2345313

https://cloud.baidu.com/article/2995479

https://datawhalechina.github.io/thorough-pytorch/%E7%AC%AC%E4%BA%8C%E7%AB%A0/2.1%20%E5%BC%A0%E9%87%8F.html

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐