关于einops rearrange与torch.view输出形状相同但内容不同的技术疑问
嘿,这个问题我之前踩过坑,一开始看着形状一样结果内容不一样,属实懵了半天!核心原因其实在于两者对张量元素的排列逻辑完全不同,跟内存布局直接相关,我给你拆解清楚:
首先得明确两个函数的本质:
torch.view()是个“偷懒”的重塑工具——它根本不会改变张量在内存里的存储顺序,只是换个方式解读现有数据的形状。它要求新形状的元素总数和原张量一致,而且如果原张量内存不连续,还得先调用.contiguous()才能用。einops.rearrange()则是“认真”的重新排列——它会完全按照你指定的维度顺序,把元素在内存里重新洗牌,不管原张量的内存布局是什么样的。
结合你给出的LoFTR里的例子,我们把细节掰碎了看:
原张量 feature_c0 形状是 [1, 256, 60, 60],也就是 (batch, channel, height, width)。
先看 rearrange(feature_c0, 'n c h w -> n (h w) c')
这个写法的意思是:把每个空间位置 (h,w) 的所有通道元素打包成一组,然后把所有空间位置排成一个维度。具体来说:
- 新张量的第
i个空间位置(也就是(h w)维度的第i项),对应的是原张量里h = i//60、w = i%60这个坐标点的256个通道值。 - 简单说就是按空间位置优先分组,每个位置管自己的所有通道。
再看 feature_c0.view(1, -1, 256)
view 是严格遵循原张量的内存存储顺序来重塑的。原张量的内存顺序是 batch → channel → height → width,也就是先存完第一个通道的所有60×60个空间点,再存第二个通道的所有空间点,以此类推。
当你写成 view(1, -1, 256) 时,相当于先把整个张量拉成一维序列,再每256个元素分成一组:
- 新张量的第
i组,对应的是原张量里所有通道的第i个空间点(这里的i是按height→width顺序数出来的第i个点)。 - 说白了就是按通道优先分组,每个组是所有通道的同一个空间点。
举个极简例子直观对比
把张量缩小成 [1, 2, 2, 2](batch=1, channel=2, height=2, width=2),元素按内存顺序记为:a(c0,h0w0)、b(c0,h0w1)、c(c0,h1w0)、d(c0,h1w1)、e(c1,h0w0)、f(c1,h0w1)、g(c1,h1w0)、h(c1,h1w1)
用 rearrange 后的结果是 [1,4,2]:
[ [a,e], # h0w0的两个通道 [b,f], # h0w1的两个通道 [c,g], # h1w0的两个通道 [d,h] # h1w1的两个通道 ]
用 view(1,-1,2) 后的结果是 [1,4,2]:
[ [a,b], # c0的前两个空间点 [c,d], # c0的后两个空间点 [e,f], # c1的前两个空间点 [g,h] # c1的后两个空间点 ]
这下就能明显看到,虽然形状完全一样,但每组里的元素天差地别!
最后给你个小总结
- 如果你的需求是把每个空间位置的所有通道聚在一起(比如LoFTR里做特征匹配时,通常需要每个空间点的特征向量),那就用
einops.rearrange,它能严格按你要的维度逻辑排列。 - 如果只是想在不打乱内存顺序的前提下改形状,再用
torch.view,但一定要注意原张量的内存连续性(可以用.is_contiguous()检查)。
备注:内容来源于stack exchange,提问作者Robin Müller

