YAOTU INSIGHTS

【Bug已解决】How to resize a PyTorch tensor? 解决方案

【Bug已解决】How to resize a PyTorch tensor? 解决方案
【Bug已解决】How to resize a PyTorch tensor? 解决方案问题描述在 PyTorch 中处理张量时调整张量形状resize是一个极其常见的操作。然而PyTorch 提供了多种调整形状的方法包括view()、reshape()、resize_()、unsqueeze()、squeeze()、flatten()、permute()、transpose()等每种方法的行为和适用场景各不相同。许多开发者在使用这些方法时会遇到各种问题如RuntimeError: shape xxx is invalid for input of size yyy调整形状后数据排列不正确对非连续张量使用view()报错不理解view()和reshape()的区别需要调整张量的空间维度如图像缩放但不知道如何操作本文将全面讲解 PyTorch 中调整张量形状的各种方法包括它们的使用场景、区别和最佳实践。错误复现以下是使用不同方法调整张量形状时可能遇到的问题import torch # 场景 1view() 对非连续张量报错 x torch.randn(3, 4, 5) x_transposed x.permute(2, 0, 1) # 非连续张量 print(fIs contiguous: {x_transposed.is_contiguous()}) # False try: # view() 要求张量是连续的 y x_transposed.view(12, 5) except RuntimeError as e: print(fError: {e}) # RuntimeError: view size is not compatible with input tensors size and stride # Use .reshape(...) or .contiguous() to make a contiguous copy # 场景 2view() 的形状不匹配 x torch.randn(3, 4) try: y x.view(2, 5) # 3*412 ! 2*510 except RuntimeError as e: print(fError: {e}) # RuntimeError: shape xxx is invalid for input of size yyy # 场景 3resize_() 的原地修改问题 x torch.randn(2, 3) print(fBefore resize_: {x}) x.resize_(3, 2) # 原地修改 print(fAfter resize_: {x}) # 注意resize_ 可能导致数据被截断或填充未初始化的值 # 场景 4需要调整图像的空间维度 # 模拟一张 3x32x32 的图像需要缩放到 3x64x64 image torch.randn(1, 3, 32, 32) print(fOriginal image shape: {image.shape}) # 不能用 view/reshape 来缩放图像因为它们不改变数据内容 # 需要使用插值方法根因分析1. PyTorch 张量的内存布局PyTorch 张量在内存中以一维数组的形式存储多维形状只是对这个一维数组的索引方式。理解这一点对于正确使用形状调整方法至关重要。内存中的数据: [1, 2, 3, 4, 5, 6] 形状 (2, 3): [[1, 2, 3], [4, 5, 6]] 形状 (3, 2): [[1, 2], [3, 4], [5, 6]] 形状 (6,): [1, 2, 3, 4, 5, 6]2. 连续性Contiguity连续性是指张量在内存中的存储顺序是否与其形状所暗示的顺序一致x torch.randn(3, 4) print(fDefault is_contiguous: {x.is_contiguous()}) # True # 转置操作会改变形状但不改变内存中的数据顺序 x_t x.t() # 或 x.transpose(0, 1) print(fAfter transpose is_contiguous: {x_t.is_contiguous()}) # False # 使用 .contiguous() 创建连续的副本 x_t_contiguous x_t.contiguous() print(fAfter .contiguous(): {x_t_contiguous.is_contiguous()}) # True3. view() vs reshape() vs resize_()方法是否连续是否原地是否复制数据是否改变总元素数推荐场景view()要求连续否否共享内存否已知连续时最高效reshape()不要求否可能复制否通用形状调整resize_()不要求是可能是可改变大小极少使用contiguous()使连续否可能复制否配合 view()4. view() 的工作原理view()只是重新解释内存中的数据不复制数据因此要求张量是连续的。如果张量不连续view()无法正确映射形状x torch.arange(12).reshape(3, 4) # x 在内存中: [0, 1, 2, ..., 11] x_t x.t() # 转置后形状是 (4, 3) # x_t 在内存中仍然是: [0, 1, 2, ..., 11] # 但形状暗示的顺序是按列读取的 # view() 期望按行读取但内存中的数据不是按行排列的 # 所以 view() 会报错5. reshape() 的工作原理reshape()更灵活——如果张量是连续的它的行为与view()相同不复制数据如果张量不连续它会自动调用contiguous()创建连续的副本然后应用新的形状。x torch.arange(12).reshape(3, 4) x_t x.t() # 非连续 # reshape 会自动处理非连续情况 y x_t.reshape(12) # 自动创建连续副本 print(y) # tensor([0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11])6. 空间维度调整 vs 形状调整需要区分两种调整形状调整reshape不改变数据内容只改变数据的排列方式。总元素数不变。空间缩放interpolate改变数据内容通过插值算法生成新的像素/数据点。总元素数改变。# 形状调整总元素数不变 x torch.randn(4, 6) # 24 个元素 y x.view(2, 12) # 仍然是 24 个元素 z x.reshape(3, 8) # 仍然是 24 个元素 # 空间缩放总元素数改变 img torch.randn(1, 3, 32, 32) # 3072 个元素 img_resized torch.nn.functional.interpolate(img, size(64, 64), modebilinear) # img_resized 有 12288 个元素解决方案方案一使用 reshape()推荐通用方法reshape()是最通用的形状调整方法适用于所有场景import torch x torch.randn(3, 4, 5) print(fOriginal shape: {x.shape}) # torch.Size([3, 4, 5]) # 展平 y1 x.reshape(3, 20) # 保留 batch 维度 y2 x.reshape(3, -1) # -1 表示自动推断 y3 x.reshape(-1) # 完全展平 y4 x.flatten() # 等同于 reshape(-1) print(freshape(3, -1): {y1.shape}) # torch.Size([3, 20]) print(freshape(-1): {y2.shape}) # torch.Size([60]) # 添加维度 y5 x.reshape(1, 3, 4, 5) # 添加 batch 维度 y6 x.unsqueeze(0) # 等同于在位置 0 添加维度 print(funsqueeze(0): {y6.shape}) # torch.Size([1, 3, 4, 5]) # 改变形状 y7 x.reshape(5, 12) # 重新排列 y8 x.reshape(2, 3, 10) # 三维 print(freshape(5, 12): {y7.shape}) # torch.Size([5, 12]) print(freshape(2, 3, 10): {y8.shape}) # torch.Size([2, 3, 10])方案二使用 view()高效方法当张量是连续的时view()比reshape()更高效不复制数据import torch x torch.randn(3, 4, 5) print(fIs contiguous: {x.is_contiguous()}) # True # 连续张量可以直接使用 view() y1 x.view(3, 20) # 高效不复制数据 y2 x.view(-1) # 展平 y3 x.view(2, 3, 10) # 三维 # 对于非连续张量先调用 contiguous() x_t x.permute(2, 0, 1) # 非连续 y4 x_t.contiguous().view(12, 5) # 先连续化再 view # 或者直接使用 reshape()自动处理连续性 y5 x_t.reshape(12, 5) # 等同于上面的操作方案三使用 interpolate() 进行空间缩放当需要调整图像或序列的空间维度时使用torch.nn.functional.interpolate()import torch import torch.nn.functional as F # 图像缩放 image torch.randn(1, 3, 32, 32) # (batch, channels, height, width) print(fOriginal: {image.shape}) # 放大到 64x64 image_upscaled F.interpolate(image, size(64, 64), modebilinear, align_cornersFalse) print(fUpscaled: {image_upscaled.shape}) # torch.Size([1, 3, 64, 64]) # 缩小到 16x16 image_downscaled F.interpolate(image, size(16, 16), modebilinear, align_cornersFalse) print(fDownscaled: {image_downscaled.shape}) # torch.Size([1, 3, 16, 16]) # 使用 scale_factor image_scaled F.interpolate(image, scale_factor2.0, modenearest) print(fScaled 2x: {image_scaled.shape}) # torch.Size([1, 3, 64, 64]) # 1D 序列缩放 sequence torch.randn(1, 64, 100) # (batch, channels, length) seq_resized F.interpolate(sequence, size200, modelinear, align_cornersFalse) print(fSequence resized: {seq_resized.shape}) # torch.Size([1, 64, 200]) # 3D 体积缩放 volume torch.randn(1, 1, 16, 16, 16) vol_resized F.interpolate(volume, size(32, 32, 32), modetrilinear, align_cornersFalse) print(fVolume resized: {vol_resized.shape}) # torch.Size([1, 1, 32, 32, 32])方案四使用 unsqueeze() 和 squeeze() 调整维度import torch x torch.randn(3, 4) print(fOriginal: {x.shape}) # torch.Size([3, 4]) # 添加维度 y1 x.unsqueeze(0) # 在位置 0 添加维度 print(funsqueeze(0): {y1.shape}) # torch.Size([1, 3, 4]) y2 x.unsqueeze(1) # 在位置 1 添加维度 print(funsqueeze(1): {y2.shape}) # torch.Size([3, 1, 4]) y3 x.unsqueeze(-1) # 在最后添加维度 print(funsqueeze(-1): {y3.shape}) # torch.Size([3, 4, 1]) # 移除大小为 1 的维度 z1 y1.squeeze(0) # 移除位置 0 的大小为 1 的维度 print(fsqueeze(0): {z1.shape}) # torch.Size([3, 4]) z2 y3.squeeze() # 移除所有大小为 1 的维度 print(fsqueeze(): {z2.shape}) # torch.Size([3, 4])完整修复代码以下是一个全面的张量形状调整工具集和示例 完整示例PyTorch 张量形状调整的各种方法 涵盖 reshape, view, interpolate, unsqueeze, squeeze, permute, flatten 等 import torch import torch.nn.functional as F import numpy as np # # 1. 形状调整工具函数 # def safe_view(tensor, *shape): 安全的 view 操作。 如果张量不连续先调用 contiguous()。 if not tensor.is_contiguous(): tensor tensor.contiguous() return tensor.view(*shape) def safe_reshape(tensor, *shape): 安全的 reshape 操作reshape 本身已处理连续性 return tensor.reshape(*shape) if len(shape) 1 else tensor.reshape(shape[0]) def resize_image(image, sizeNone, scale_factorNone, modebilinear): 调整图像大小。 参数: image: (B, C, H, W) 或 (C, H, W) size: 目标大小 (H, W) 或 int正方形 scale_factor: 缩放因子 mode: 插值模式 (nearest, bilinear, bicubic) # 确保 4D (B, C, H, W) if image.dim() 3: image image.unsqueeze(0) was_3d True else: was_3d False # 处理 size 参数 if isinstance(size, int): size (size, size) # 插值 resized F.interpolate( image, sizesize, scale_factorscale_factor, modemode, align_cornersFalse if mode ! nearest else None, ) if was_3d: resized resized.squeeze(0) return resized def resize_sequence(sequence, target_length, modelinear): 调整 1D 序列长度。 参数: sequence: (B, C, L) 或 (C, L) target_length: 目标长度 mode: 插值模式 (nearest, linear) if sequence.dim() 2: sequence sequence.unsqueeze(0) was_2d True else: was_2d False resized F.interpolate( sequence, sizetarget_length, modemode, align_cornersFalse if mode ! nearest else None, ) if was_2d: resized resized.squeeze(0) return resized # # 2. 形状调整方法全面演示 # def demonstrate_all_methods(): 演示所有形状调整方法 print( * 60) print(PyTorch Tensor Shape Manipulation Methods) print( * 60) # 创建示例张量 x torch.arange(24, dtypetorch.float32).reshape(2, 3, 4) print(f\nOriginal tensor:\nshape{x.shape}\n{x}) # --- 1. reshape --- print(\n--- 1. reshape() ---) print(freshape(6, 4): {x.reshape(6, 4).shape}) print(freshape(2, -1): {x.reshape(2, -1).shape}) # 自动推断 print(freshape(-1): {x.reshape(-1).shape}) # 展平 # --- 2. view --- print(\n--- 2. view() ---) print(fview(6, 4): {x.view(6, 4).shape}) print(fview(2, 12): {x.view(2, 12).shape}) # --- 3. flatten --- print(\n--- 3. flatten() ---) print(fflatten(): {x.flatten().shape}) # 全展平 print(fflatten(1): {x.flatten(1).shape}) # 从维度 1 开始展平 print(fflatten(start_dim0, end_dim1): {x.flatten(0, 1).shape}) # --- 4. unsqueeze / squeeze --- print(\n--- 4. unsqueeze() / squeeze() ---) print(funsqueeze(0): {x.unsqueeze(0).shape}) # (1, 2, 3, 4) print(funsqueeze(3): {x.unsqueeze(3).shape}) # (2, 3, 4, 1) y x.unsqueeze(0) print(fsqueeze(0): {y.squeeze(0).shape}) # (2, 3, 4) # --- 5. permute / transpose --- print(\n--- 5. permute() / transpose() ---) print(fpermute(2, 0, 1): {x.permute(2, 0, 1).shape}) # (4, 2, 3) print(ftranspose(1, 2): {x.transpose(1, 2).shape}) # (2, 4, 3) print(ft() [2D only]: {x.reshape(4, 6).t().shape}) # (6, 4) # --- 6. expand / repeat --- print(\n--- 6. expand() / repeat() ---) y torch.randn(1, 3, 1) print(fexpand(4, 3, 5): {y.expand(4, 3, 5).shape}) # 广播扩展 print(frepeat(4, 1, 5): {y.repeat(4, 1, 5).shape}) # 复制扩展 # --- 7. contiguous --- print(\n--- 7. contiguous() ---) x_t x.permute(2, 0, 1) print(fAfter permute - is_contiguous: {x_t.is_contiguous()}) x_c x_t.contiguous() print(fAfter contiguous - is_contiguous: {x_c.is_contiguous()}) # --- 8. interpolate (空间缩放) --- print(\n--- 8. interpolate() (spatial resize) ---) img torch.randn(1, 3, 32, 32) img_up F.interpolate(img, size(64, 64), modebilinear, align_cornersFalse) print(fImage upscale 32-64: {img.shape} - {img_up.shape}) img_down F.interpolate(img, size(16, 16), modebilinear, align_cornersFalse) print(fImage downscale 32-16: {img.shape} - {img_down.shape}) # # 3. 实际应用场景 # def practical_examples(): 实际应用场景示例 print(\n * 60) print(Practical Examples) print( * 60) # --- 场景 1CNN 中的形状调整 --- print(\n--- Scenario 1: CNN Feature Map Reshaping ---) # 卷积层输出 - 全连接层输入 conv_output torch.randn(32, 64, 7, 7) # (batch, channels, H, W) print(fConv output: {conv_output.shape}) # 方法 1flatten fc_input conv_output.flatten(1) # (32, 64*7*7) print(fAfter flatten(1): {fc_input.shape}) # 方法 2reshape fc_input conv_output.reshape(conv_output.size(0), -1) print(fAfter reshape(batch, -1): {fc_input.shape}) # 方法 3view需要连续 fc_input conv_output.contiguous().view(conv_output.size(0), -1) print(fAfter view(batch, -1): {fc_input.shape}) # --- 场景 2调整图像大小 --- print(\n--- Scenario 2: Image Resizing ---) # 模拟不同大小的输入图像 images [ torch.randn(3, 128, 128), # 大图 torch.randn(3, 64, 64), # 中图 torch.randn(3, 32, 32), # 小图 ] target_size (64, 64) batch [] for i, img in enumerate(images): print(f Image {i}: {img.shape} - , end) resized resize_image(img, sizetarget_size, modebilinear) print(f{resized.shape}) batch.append(resized) # 堆叠成 batch image_batch torch.stack(batch) print(f Batch: {image_batch.shape}) # --- 场景 3序列长度调整 --- print(\n--- Scenario 3: Sequence Length Adjustment ---) sequences [ torch.randn(128, 50), # 短序列 torch.randn(128, 100), # 中序列 torch.randn(128, 200), # 长序列 ] target_length 100 for i, seq in enumerate(sequences): print(f Sequence {i}: {seq.shape} - , end) resized resize_sequence(seq, target_length, modelinear) print(f{resized.shape}) # --- 场景 4多头注意力中的形状调整 --- print(\n--- Scenario 4: Multi-Head Attention Reshaping ---) batch_size 4 seq_len 16 d_model 64 num_heads 8 head_dim d_model // num_heads x torch.randn(batch_size, seq_len, d_model) print(f Input: {x.shape}) # 分割为多头 x_multihead x.view(batch_size, seq_len, num_heads, head_dim) print(f Multi-head view: {x_multihead.shape}) # 转置为 (batch, heads, seq_len, head_dim) x_multihead x_multihead.transpose(1, 2) print(f After transpose: {x_multihead.shape}) # 合并多头 x_merged x_multihead.transpose(1, 2).reshape(batch_size, seq_len, d_model) print(f Merged: {x_merged.shape}) # --- 场景 5处理非连续张量 --- print(\n--- Scenario 5: Handling Non-contiguous Tensors ---) x torch.randn(3, 4, 5) # permute 产生非连续张量 x_permuted x.permute(2, 0, 1) print(f Permuted shape: {x_permuted.shape}, contiguous: {x_permuted.is_contiguous()}) # 方法 1reshape自动处理 y1 x_permuted.reshape(12, 5) print(f reshape result: {y1.shape}) # 方法 2contiguous view y2 x_permuted.contiguous().view(12, 5) print(f contiguousview result: {y2.shape}) # 验证结果相同 print(f Results equal: {torch.allclose(y1, y2)}) # # 4. 方法选择指南 # def method_selection_guide(): 方法选择指南 print(\n * 60) print(Method Selection Guide) print( * 60) guide ------------------------------------------------------------------------ | Method | Requires | Copies Data? | Use When | | | Contiguous? | | | ------------------------------------------------------------------------ | view() | Yes | No (shared memory) | Tensor is known | | | | | to be contiguous | ------------------------------------------------------------------------ | reshape() | No | Only if needed | General purpose | | | | | shape change | ------------------------------------------------------------------------ | flatten() | No | Only if needed | Collapsing dims | ------------------------------------------------------------------------ | unsqueeze() | No | No | Adding dimensions | ------------------------------------------------------------------------ | squeeze() | No | No | Removing size-1 | | | | | dimensions | ------------------------------------------------------------------------ | permute() | No | No (changes stride) | Reordering dims | ------------------------------------------------------------------------ | transpose() | No | No (changes stride) | Swapping 2 dims | ------------------------------------------------------------------------ | contiguous() | N/A | Yes if not contig. | Making tensor | | | | | contiguous | ------------------------------------------------------------------------ | interpolate() | No | Yes (new data) | Spatial resize | | | | | (images, seqs) | ------------------------------------------------------------------------ | expand() | No | No (broadcast) | Broadcasting to | | | | | larger shape | ------------------------------------------------------------------------ | repeat() | No | Yes (copies) | Tiling/replicating| ------------------------------------------------------------------------ print(guide) print(\nDecision Tree:) print(1. Do you need to change spatial dimensions (e.g., image resize)?) print( - Use F.interpolate()) print() print(2. Do you need to reorder dimensions?) print( - Use permute() for multiple dims, transpose() for 2 dims) print() print(3. Do you need to add/remove size-1 dimensions?) print( - Use unsqueeze() / squeeze()) print() print(4. Do you need to change the shape (same total elements)?) print( - Use reshape() (safe, handles non-contiguous)) print( - Use view() only if tensor is contiguous (faster)) print() print(5. Do you need to flatten dimensions?) print( - Use flatten() or reshape(-1)) # # 运行 # if __name__ __main__: demonstrate_all_methods() practical_examples() method_selection_guide()常见陷阱与注意事项1. view() 和 reshape() 的内存共享view()和reshape()当张量连续时返回的张量与原始张量共享内存。修改一个会影响另一个x torch.randn(3, 4) y x.view(12) y[0] 999.0 print(x[0, 0]) # 999.0 - 原始张量也被修改了如果需要独立的副本使用.clone()y x.view(12).clone() # 独立副本2. -1 自动推断view()和reshape()支持使用 -1 自动推断某个维度的大小x torch.randn(3, 4, 5) # 60 个元素 y x.reshape(6, -1) # -1 自动推断为 10 (60/6) z x.reshape(-1, 3) # -1 自动推断为 20 (60/3)注意只能有一个 -1。3. interpolate 的 align_corners 参数interpolate的align_corners参数影响插值的对齐方式# align_cornersTrue: 角角对齐边缘像素精确对应 # align_cornersFalse: 半像素对齐更平滑推荐 F.interpolate(x, size(64, 64), modebilinear, align_cornersFalse)4. expand() vs repeat()expand()不复制数据通过修改步长实现广播而repeat()实际复制数据x torch.randn(1, 3, 1) # expand: 不复制数据高效 y x.expand(4, 3, 5) # 共享内存 # repeat: 复制数据 z x.repeat(4, 1, 5) # 独立数据 # expand 只能扩展大小为 1 的维度 # repeat 可以重复任意维度5. 维度顺序在处理图像时注意 PyTorch 使用(B, C, H, W)格式而其他库可能使用不同格式# PyTorch: (B, C, H, W) - Batch, Channel, Height, Width # NumPy/Matplotlib: (H, W, C) # PIL: (H, W, C) # 转换 PyTorch - NumPy img_pytorch torch.randn(3, 32, 32) # (C, H, W) img_numpy img_pytorch.permute(1, 2, 0).numpy() # (H, W, C)6. resize_() 的危险性resize_()是原地操作可能截断数据或引入未初始化的值x torch.randn(2, 3) x.resize_(3, 2) # 原地修改可能不安全 # 不推荐使用优先使用 reshape()总结PyTorch 中调整张量形状有多种方法选择正确的方法取决于具体需求。关键要点总结reshape() 是通用方法适用于所有形状调整场景自动处理非连续张量。不确定时用reshape()。view() 更高效但有限制要求张量连续不复制数据。适用于已知连续的场景。interpolate() 用于空间缩放当需要改变图像或序列的空间维度如缩放图像大小时使用通过插值生成新数据。unsqueeze()/squeeze() 用于增减维度添加或移除大小为 1 的维度。permute()/transpose() 用于重排维度改变维度的顺序不改变数据内容。flatten() 用于展平将多个维度合并为一个维度。连续性很重要view()要求连续reshape()不要求。使用.contiguous()或.reshape()处理非连续张量。内存共享view()和连续时的reshape()与原始张量共享内存。需要独立副本时使用.clone()。通过理解这些方法的区别和适用场景你可以高效、正确地调整 PyTorch 张量的形状避免常见的错误和陷阱。