{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "Wesam Khallaf",
  "title_listed": "PyTorch `permute` vs `transpose`: What's the Difference (and the `reshape` Bug That Scrambles Your Images)",
  "keywords": "",
  "domain": "图像处理",
  "content_type": "技术教程",
  "is_paid_flag": "否",
  "url": "https://dev.to/pytorchfromgroundup/pytorch-permute-vs-transpose-whats-the-difference-and-the-reshape-bug-that-scrambles-your-7ke",
  "publish_date": "2026-08-02",
  "corpus_stage": "",
  "notes": "",
  "id": "fbd0b453ca42",
  "collected_at": "2026-10-02T13:54:00",
  "title": "PyTorch `permute` vs `transpose`: What's the Difference (and the `reshape` Bug That Scrambles Your Images) - DEV Community",
  "body_markdown": "You loaded an image, got a tensor shaped\n(batch, height, width, channels)\n, and your convolution wants\n(batch, channels, height, width)\n. Stack Overflow says\npermute\n. Someone else says\ntranspose\n. And\nreshape(2, 3, 28, 28)\ngives you the right shape too — so why is everyone making this complicated?\nBecause two of those three are the same tool, and the third one silently destroys your data.\nThe short answer\ntranspose(dim0, dim1)\nswaps\nexactly two\ndimensions.\npermute(...)\nreorders\nall\nof them in one call, and you must list every dimension.\ntranspose\nis a special case of\npermute\n. Both return a\nview\n— no data is copied, only the strides change — which also means both leave you with a non-contiguous tensor.\nreshape\nis not in this family at all. It reinterprets the flat memory under a new shape without moving anything, so it can produce the shape you asked for while completely scrambling what the numbers mean.\nimport\ntorch\nt\n=\ntorch\n.\narange\n(\n24\n).\nreshape\n(\n2\n,\n3\n,\n4\n)\nprint\n(\nt\n.\ntranspose\n(\n0\n,\n1\n).\nshape\n)\n# torch.Size([3, 2, 4])  — swapped dims 0 and 1\nprint\n(\nt\n.\npermute\n(\n2\n,\n0\n,\n1\n).\nshape\n)\n# torch.Size([4, 2, 3])  — full reorder\nEnter fullscreen mode\nExit fullscreen mode\ntranspose\n— swap two axes\ntranspose(dim0, dim1)\ntakes two dimension indices and swaps them. Everything else stays put.\nt\n=\ntorch\n.\narange\n(\n24\n).\nreshape\n(\n2\n,\n3\n,\n4\n)\nprint\n(\nt\n.\nshape\n)\n# torch.Size([2, 3, 4])\nprint\n(\nt\n.\ntranspose\n(\n0\n,\n1\n).\nshape\n)\n# torch.Size([3, 2, 4])\nprint\n(\nt\n.\ntranspose\n(\n1\n,\n2\n).\nshape\n)\n# torch.Size([2, 4, 3])\nEnter fullscreen mode\nExit fullscreen mode\nThe order of the two arguments doesn't matter —\nt.transpose(0, 1)\nand\nt.transpose(1, 0)\nare the same thing. A swap is a swap.\nOn a 2-D tensor this is the matrix transpose you already know, and\n.T\nis the shorthand:\nm\n=\ntorch\n.\narange\n(\n6\n).\nreshape\n(\n2\n,\n3\n)\nprint\n(\nm\n.\nT\n.\nshape\n)\n# torch.Size([3, 2])\nprint\n(\nm\n.\ntranspose\n(\n0\n,\n1\n).\nshape\n)\n# torch.Size([3, 2])  — identical\nEnter fullscreen mode\nExit fullscreen mode\nOne caution on\n.T\n:\non tensors with more than two dimensions,\n.T\nreverses\nevery\ndimension, and modern PyTorch has deprecated that behaviour — it warns now and is slated to become an error. If you want the \"transpose the last two axes\" behaviour on a batch of matrices, use\n.mT\n, which is explicit and safe:\nt\n=\ntorch\n.\narange\n(\n24\n).\nreshape\n(\n2\n,\n3\n,\n4\n)\nprint\n(\nt\n.\nmT\n.\nshape\n)\n# torch.Size([2, 4, 3]) — swaps the last two dims, batch untouched\nEnter fullscreen mode\nExit fullscreen mode\nReserve plain\n.T\nfor 2-D matrices. On anything higher, say what you mean with\npermute\nor\n.mT\n.\npermute\n— state the whole new order\npermute\ndoesn't swap; it\nrewrites the dimension order in full\n. You pass the old index of each dimension in the position you want it to end up.\nt\n=\ntorch\n.\narange\n(\n24\n).\nreshape\n(\n2\n,\n3\n,\n4\n)\nprint\n(\nt\n.\npermute\n(\n2\n,\n0\n,\n1\n).\nshape\n)\n# torch.Size([4, 2, 3])\nEnter fullscreen mode\nExit fullscreen mode\nRead\npermute(2, 0, 1)\nas:\n\"new dim 0 is old dim 2, new dim 1 is old dim 0, new dim 2 is old dim 1.\"\nNot \"move dim 2 somewhere\" — you are writing out the destination order, left to right.\nTwo consequences worth internalising:\nYou must list\nevery\ndimension. Miss one and you get\nRuntimeError: number of dims don't match in permute\n.\nAny reorder you can do with a chain of transposes, you can do in one\npermute\n:\nt\n=\ntorch\n.\narange\n(\n24\n).\nreshape\n(\n2\n,\n3\n,\n4\n)\nvia_permute\n=\nt\n.\npermute\n(\n1\n,\n2\n,\n0\n)\n# torch.Size([3, 4, 2])\nvia_transposes\n=\nt\n.\ntranspose\n(\n0\n,\n1\n).\ntranspose\n(\n1\n,\n2\n)\n# torch.Size([3, 4, 2])\nprint\n(\ntorch\n.\nequal\n(\nvia_permute\n,\nvia_transposes\n))\n# True\nEnter fullscreen mode\nExit fullscreen mode\nSame result. The\npermute\nversion says the destination shape out loud; the chained version makes the reader simulate two swaps in their head. Prefer\npermute\nfor anything beyond a single swap.\nThe one that actually bites:\npermute\nis not\nreshape\nHere is the bug this article exists for. You have a batch of images in\n(N, H, W, C)\n— the layout you get from OpenCV, PIL, TensorFlow, and most image files — and PyTorch convolutions need\n(N, C, H, W)\n.\nBoth of these produce a tensor of the right shape:\nimg\n=\ntorch\n.\narange\n(\n1\n*\n2\n*\n2\n*\n3\n).\nreshape\n(\n1\n,\n2\n,\n2\n,\n3\n)\n# N=1, H=2, W=2, C=3\nprint\n(\nimg\n.\npermute\n(\n0\n,\n3\n,\n1\n,\n2\n).\nshape\n)\n# torch.Size([1, 3, 2, 2])  ✅\nprint\n(\nimg\n.\nreshape\n(\n1\n,\n3\n,\n2\n,\n2\n).\nshape\n)\n# torch.Size([1, 3, 2, 2])  ⚠️ same shape!\nEnter fullscreen mode\nExit fullscreen mode\nSame shape. No error. No warning. Now look at what's actually inside the first channel:\nprint\n(\nimg\n.\npermute\n(\n0\n,\n3\n,\n1\n,\n2\n)[\n0\n,\n0\n])\n# tensor([[0, 3],\n#         [6, 9]])            ← the red value of each of the 4 pixels ✅\nprint\n(\nimg\n.\nreshape\n(\n1\n,\n3\n,\n2\n,\n2\n)[\n0\n,\n0\n])\n# tensor([[0, 1],\n#         [2, 3]])            ← pixel 0's R, G, B and then pixel 1's R ❌\nEnter fullscreen mode\nExit fullscreen mode\nThe\npermute\nversion collected the red channel: pixels are stored as\n(R,G,B)(R,G,B)…\n, so the reds are elements 0, 3, 6, 9. That's a real red channel.\nThe\nreshape\nversion just took the first four numbers in memory and called them \"channel 0\" — that's one whole pixel plus a third of the next one. Your \"red channel\" is now a mixture of red, green and blue from different pixels. The tensor is the right shape, the model trains, the loss goes down a little, and the accuracy is quietly terrible. Nothing on screen ever tells you.\nThe rule:\npermute\nmoves\ndimensions\n.\nreshape\nre-cuts the\nsame flat sequence of numbers\ninto new brackets. If you want to change what an axis\nmeans\n, you need\npermute\n— always.\nIf you want the full picture on how\nreshape\nre-cuts memory, that's covered in\nReshape vs View in PyTorch\n.\nReal code: the two reorders you'll actually write\nImages — channels-last to channels-first:\nimg\n=\ntorch\n.\nrandn\n(\n2\n,\n28\n,\n28\n,\n3\n)\n# N, H, W, C  (from PIL / OpenCV)\nx\n=\nimg\n.\npermute\n(\n0\n,\n3\n,\n1\n,\n2\n)\n# N, C, H, W  (what nn.Conv2d wants)\nprint\n(\nx\n.\nshape\n)\n# torch.Size([2, 3, 28, 28])\nEnter fullscreen mode\nExit fullscreen mode\nAttention — splitting into heads.\nThis is the one place where\nreshape\nand\npermute\ncorrectly appear back-to-back, and seeing why makes the distinction click:\nB\n,\nS\n,\nE\n,\nH\n=\n2\n,\n5\n,\n8\n,\n2\n# batch, seq_len, embed_dim, heads\nx\n=\ntorch\n.\nrandn\n(\nB\n,\nS\n,\nE\n)\n# torch.Size([2, 5, 8])\nx\n=\nx\n.\nreshape\n(\nB\n,\nS\n,\nH\n,\nE\n//\nH\n)\n# torch.Size([2, 5, 2, 4])  — split, don't move\nx\n=\nx\n.\npermute\n(\n0\n,\n2\n,\n1\n,\n3\n)\n# torch.Size([2, 2, 5, 4])  — move heads forward\nEnter fullscreen mode\nExit fullscreen mode\nThe\nreshape\nis legitimate here because it only\nsplits\nthe last axis — the 8 embedding values were already laid out contiguously, so cutting them into 2 groups of 4 doesn't reorder anything. The\npermute\nthen genuinely moves the head axis in front of the sequence axis. Splitting an axis is\nreshape\nwork; moving an axis is\npermute\nwork.\nBoth of them break contiguity\ntranspose\nand\npermute\nnever touch the underlying data. They change the tensor's\nstrides\n— the step size PyTorch takes through memory for each dimension:\na\n=\ntorch\n.\narange\n(\n6\n).\nreshape\n(\n2\n,\n3\n)\nprint\n(\na\n.\nstride\n(),\na\n.\nis_contiguous\n())\n# (3, 1) True\nb\n=\na\n.\npermute\n(\n1\n,\n0\n)\nprint\n(\nb\n.\nshape\n,\nb\n.\nstride\n(),\nb\n.\nis_contiguous\n())\n# torch.Size([3, 2]) (1, 3) False\nEnter fullscreen mode\nExit fullscreen mode\nThat's why they're free. It's also why the very next\n.view()\nyou call will blow up:\nb\n.\nview\n(\n6\n)\n# RuntimeError: view size is not compatible with input tensor's size and stride\n# (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.\nEnter fullscreen mode\nExit fullscreen mode\nTwo fixes, and the choice matters:\nb\n.\nreshape\n(\n6\n)\n# works — copies quietly when it has to\nb\n.\ncontiguous\n().\nview\n(\n6\n)\n# works — reorders memory first, then views\nEnter fullscreen mode\nExit fullscreen mode\nUse\n.reshape()\nby default. Reach for an explicit\n.contiguous()\nwhen you're about to do many operations on the permuted tensor and want the memory laid out well once, up front, rather than copied repeatedly.\nCommon mistakes and gotchas\nUsing\nreshape\nto convert NHWC → NCHW.\nThe headline bug. Right shape, scrambled channels, no error. Use\npermute(0, 3, 1, 2)\n.\nReading\npermute(2, 0, 1)\nas instructions to move things.\nIt's a destination list: new dim\ni\nis old dim\nargs[i]\n. Say it out loud before you trust it.\nForgetting a dimension.\npermute\nneeds all of them →\nRuntimeError: number of dims don't match in permute\n.\nUsing\n.T\non 3-D+ tensors.\nDeprecated (it reverses\nall\ndims, which is almost never what you want). Use\n.mT\nfor a batch of matrices, or\npermute\nto say it exactly.\nCalling\n.view()\nright after a permute.\nGuaranteed contiguity error.\n.reshape()\nor\n.contiguous().view()\n.\nAssuming a transpose changed your data.\nIt changed the strides only.\nprint(x.stride())\nnext to\nprint(x.shape)\nwhen a shape bug won't make sense — the strides usually tell you the real story.\nRecap\ntranspose(a, b)\nswaps two dimensions;\npermute(...)\nrewrites the whole dimension order and needs every index listed. They do the same kind of work, and\npermute\nis the one to reach for whenever more than one swap is involved. Both are free view operations that leave the tensor non-contiguous, so the next\n.view()\nwill error — use\n.reshape()\n.\nAnd the part that costs people a weekend:\nreshape\nis not a reordering tool.\nIt can hand you a correctly-shaped tensor full of scrambled values, with nothing on screen to warn you. When an axis needs to\nmean\nsomething different, permute it.\nMore in this series:\nReshape vs View\n·\nBroadcasting Explained\n·\nWhat\nunsqueeze\nDoes\n·\nWhat\nkeepdim\nDoes\nA question for you:\nwhat's the shape bug that cost you the most time? I'm collecting the ones that hit hardest — drop it in the comments and I'll write up the fix.\nThis is one idea from my book\nPyTorch From Ground Up\n, which builds everything from tensors upward so nothing stays vague. If it helped, you can grab the\nfree PyTorch tensor cheat-sheet here\n, run every example from the book in the\ncompanion notebooks on GitHub\n, get the\ndigital edition on Leanpub\n, or find the\nfull paperback on Amazon here\n.",
  "metadata": {
    "title": "PyTorch `permute` vs `transpose`: What's the Difference (and the `reshape` Bug That Scrambles Your Images) - DEV Community",
    "description": "You loaded an image, got a tensor shaped (batch, height, width, channels), and your convolution wants... Tagged with deeplearning, python, tutorial.",
    "keywords": "deeplearning, python, tutorial, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/pytorchfromgroundup/pytorch-permute-vs-transpose-whats-the-difference-and-the-reshape-bug-that-scrambles-your-7ke",
    "og:title": "PyTorch `permute` vs `transpose`: What's the Difference (and the `reshape` Bug That Scrambles Your Images)",
    "og:description": "You loaded an image, got a tensor shaped (batch, height, width, channels), and your convolution wants...",
    "og:site_name": "DEV Community",
    "og:image": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqus13qgx55xmlulvufr8.png",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@wesamkhallaf",
    "twitter:title": "PyTorch `permute` vs `transpose`: What's the Difference (and the `reshape` Bug That Scrambles Your Images)",
    "twitter:description": "You loaded an image, got a tensor shaped (batch, height, width, channels), and your convolution wants...",
    "twitter:card": "summary_large_image",
    "twitter:widgets:new-embed-design": "on",
    "twitter:image:src": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqus13qgx55xmlulvufr8.png"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/fbd0b453ca42/code/body-000.txt",
      "chars": 211,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-001.txt",
      "chars": 207,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-002.txt",
      "chars": 153,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-003.txt",
      "chars": 125,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-004.txt",
      "chars": 96,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-005.txt",
      "chars": 255,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-006.txt",
      "chars": 229,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-007.txt",
      "chars": 256,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-008.txt",
      "chars": 223,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-009.txt",
      "chars": 321,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-010.txt",
      "chars": 199,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-011.txt",
      "chars": 183,
      "language": "text"
    },
    {
      "file": "attachments/fbd0b453ca42/code/body-012.txt",
      "chars": 140,
      "language": "text"
    }
  ],
  "images": [],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://dev.to/pytorchfromgroundup/reshape-vs-view-in-pytorch-whats-the-difference-and-when-view-breaks-4a9o",
      "label": "Reshape vs View in PyTorch",
      "type": "a"
    },
    {
      "href": "https://dev.to/pytorchfromgroundup/pytorch-broadcasting-explained-the-3-rules-and-the-silent-bug-that-bites-everyone-3606",
      "label": "Broadcasting Explained",
      "type": "a"
    },
    {
      "href": "https://dev.to/pytorchfromgroundup/what-does-unsqueeze-do-in-pytorch-and-why-your-model-keeps-asking-for-it-181a",
      "label": "What unsqueeze Does",
      "type": "a"
    },
    {
      "href": "https://dev.to/pytorchfromgroundup/what-does-keepdim-do-in-pytorch-and-the-silent-bug-when-you-forget-it-17p1",
      "label": "What keepdim Does",
      "type": "a"
    },
    {
      "href": "https://payhip.com/b/7ukxh",
      "label": "free PyTorch tensor cheat-sheet here",
      "type": "a"
    },
    {
      "href": "https://github.com/pytorch-from-ground-up/book_code",
      "label": "companion notebooks on GitHub",
      "type": "a"
    },
    {
      "href": "https://leanpub.com/pytorchfromgroundup",
      "label": "digital edition on Leanpub",
      "type": "a"
    },
    {
      "href": "https://www.amazon.com/dp/B0H8WMCV33",
      "label": "full paperback on Amazon here",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:48:51Z",
  "source_url": "https://dev.to/pytorchfromgroundup/pytorch-permute-vs-transpose-whats-the-difference-and-the-reshape-bug-that-scrambles-your-7ke",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 10064,
  "expected_body_images": 0,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/fbd0b453ca42/pages/source.html",
  "local_html": "attachments/fbd0b453ca42/pages/article.html",
  "body_base_path": "attachments/fbd0b453ca42/pages/",
  "body_markdown_path": "attachments/fbd0b453ca42/pages/article.md"
}