{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "Wesam Khallaf",
  "title_listed": "What Your Loss Function Actually Tells the Model: MSE, Cross-Entropy, and the softmax Bug That Trains Anyway",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "技术教程",
  "is_paid_flag": "否",
  "url": "https://dev.to/pytorchfromgroundup/what-your-loss-function-actually-tells-the-model-mse-cross-entropy-and-the-softmax-bug-that-fan",
  "publish_date": "2026-09-08",
  "corpus_stage": "",
  "notes": "",
  "id": "7fa85e83de16",
  "collected_at": "2026-10-02T13:53:49",
  "title": "What Your Loss Function Actually Tells the Model: MSE, Cross-Entropy, and the softmax Bug That Trains Anyway - DEV Community",
  "body_markdown": "the last three articles walked the training loop backwards,\n.backward()\nand what it fills in, the four gradients computed by hand, then\noptimizer.step()\nturning those gradients into an actual update. all of that starts at one number, the loss, and i kept saying \"and then the loss comes from somewhere\" and moving on. so this one is about where it comes from, which is a much shorter piece of arithmetic than you would expect, and then about the one mistake in this area that every beginner is warned about and almost nobody has actually measured.\nthe warning is \"never apply softmax before\nCrossEntropyLoss\n\", and you will find it in every tutorial, every course and most stack overflow answers on the subject, almost always with the words \"training will fail quietly\". i wanted a number for \"quietly\", so i ran it. it does not fail. it trains, it reaches roughly the same accuracy, and it lies to you the whole way in a specific and measurable manner, which turns out to be a lot more interesting than failing would have been.\nThe short answer\nif the answer your model produces is a number, use\nMSELoss\n. if the answer is a category, use\nCrossEntropyLoss\n. binary yes or no,\nBCEWithLogitsLoss\n. that is the whole decision for the vast majority of problems, and neither of the two main ones has anything hidden inside it:\nMSE\n=\n((\npred\n-\ntarget\n)\n**\n2\n).\nmean\n()\nCrossEntropy\n=\n-\nlog\n(\nsoftmax\n(\nlogits\n)[\ncorrect_class\n]\n)\nEnter fullscreen mode\nExit fullscreen mode\nthe loss function is the only place in the entire loop where you tell the model what \"better\" means. the architecture, the optimizer, the data, all of it is machinery in service of that one definition.\nMSE, which is exactly what it says\ntake the difference, square it so negative errors do not cancel positive ones, average over the batch.\nimport\ntorch\nimport\ntorch.nn\nas\nnn\npred\n=\ntorch\n.\ntensor\n([\n2.5\n,\n0.5\n,\n3.0\n])\ntarget\n=\ntorch\n.\ntensor\n([\n3.0\n,\n0.0\n,\n3.0\n])\nprint\n(\nnn\n.\nMSELoss\n()(\npred\n,\ntarget\n))\n# tensor(0.1667)\nprint\n(((\npred\n-\ntarget\n)\n**\n2\n).\nmean\n())\n# tensor(0.1667)\nEnter fullscreen mode\nExit fullscreen mode\nby hand the errors are\n(-0.5, 0.5, 0.0)\n, squared they are\n(0.25, 0.25, 0.0)\n, and\n0.5 / 3\nis\n0.1667\n. the\nnn.MSELoss()\nobject is a wrapper around that one line and nothing else.\nthe squaring is the part with consequences. shift all your predictions by 1.0 and the loss is\n1.0\n, shift them by 2.0 and it is\n4.0\n, so an error twice as large costs four times as much and MSE will always spend most of its effort on your worst outliers. that is either what you want or the reason your model is being dragged around by three bad rows in the dataset.\nthe MSE trap that costs an afternoon\nthis one is not in the docs where you would look for it. give MSE a prediction of shape\n(4, 1)\nand a target of shape\n(4,)\n, which happens the moment you use\nnn.Linear(n, 1)\nand your labels came out of a dataframe column:\npr\n=\ntorch\n.\ntensor\n([[\n1.0\n],\n[\n2.0\n],\n[\n3.0\n],\n[\n4.0\n]])\n# (4, 1)\ntg\n=\ntorch\n.\ntensor\n([\n1.0\n,\n2.0\n,\n3.0\n,\n4.0\n])\n# (4,)\nprint\n(\nnn\n.\nMSELoss\n()(\npr\n,\ntg\n))\n# tensor(2.5000)\nEnter fullscreen mode\nExit fullscreen mode\nthe prediction is exactly right on every row and the loss is\n2.5\n. broadcasting expanded\n(4,1)\nagainst\n(4,)\ninto a\n(4,4)\ngrid and compared every prediction against every target, so twelve of the sixteen comparisons are between rows that have nothing to do with each other. pytorch does warn here, and the warning is easy to scroll past because your training loop still prints a falling loss:\nUserWarning: Using a target size (torch.Size([4])) that is different to the input size\n(torch.Size([4, 1])). This will likely lead to incorrect results due to broadcasting.\nPlease ensure they have the same size.\nEnter fullscreen mode\nExit fullscreen mode\nfix is\ntarget.unsqueeze(1)\nor\npred.squeeze(1)\n, and then the loss is\n0.0\nlike it should be. if the\n(4,4)\npart is surprising, it is the same three broadcasting rules from\nthe broadcasting article\n, just showing up somewhere you were not looking for them.\nCross-entropy, by hand\nclassification is a different shape of problem. the model outputs one raw score per class, called logits, and the target is an integer saying which class was right. cross-entropy turns the scores into probabilities with softmax, then takes the negative log of the probability sitting on the correct class. more probability on the right answer, lower loss.\nlogits\n=\ntorch\n.\ntensor\n([\n[\n2.0\n,\n1.0\n,\n0.1\n,\n-\n1.0\n],\n# sample 0, correct class 0\n[\n0.5\n,\n2.5\n,\n0.3\n,\n0.2\n],\n# sample 1, correct class 1\n[\n0.1\n,\n0.2\n,\n3.0\n,\n0.1\n],\n# sample 2, correct class 2\n])\nlabels\n=\ntorch\n.\ntensor\n([\n0\n,\n1\n,\n2\n])\nprint\n(\nnn\n.\nCrossEntropyLoss\n()(\nlogits\n,\nlabels\n))\n# tensor(0.3015)\nEnter fullscreen mode\nExit fullscreen mode\nnow the same thing by hand for sample 0:\nl\n=\ntorch\n.\ntensor\n([\n2.0\n,\n1.0\n,\n0.1\n,\n-\n1.0\n])\np\n=\ntorch\n.\nsoftmax\n(\nl\n,\ndim\n=\n0\n)\nprint\n(\np\n)\n# tensor([0.6381, 0.2347, 0.0954, 0.0318])\nprint\n(\np\n.\nsum\n())\n# tensor(1.0000)\nprint\n(\n-\ntorch\n.\nlog\n(\np\n[\n0\n]))\n# tensor(0.4493)\nEnter fullscreen mode\nExit fullscreen mode\nsoftmax exponentiates every score and divides by the total, so the four numbers come out positive and sum to one. class 0 got 63.8 percent of the probability and\n-log(0.6381)\nis\n0.4493\n. the other two samples give\n0.2974\nand\n0.1577\n, and the mean of the three is\n0.3015\n, which is what the loss function returned.\none habit worth building right here. whenever you print a softmax, print its sum on the next line. it costs four characters and it is the only cheap check that exists, because a vector that does not add up to 1 is not a probability distribution and something upstream of it is wrong, usually a softmax taken over the wrong dimension. i have run into more than one published table of \"probabilities\" that quietly fails that test, and the sum is what catches it every time.\nwhy the negative log, and not something simpler\nbecause of what it does to the gradient. compute the gradient of cross-entropy with respect to the logits and you get one of the cleanest results in the whole subject:\nlg\n=\ntorch\n.\ntensor\n([[\n2.0\n,\n1.0\n,\n0.1\n,\n-\n1.0\n]],\nrequires_grad\n=\nTrue\n)\nnn\n.\nCrossEntropyLoss\n()(\nlg\n,\ntorch\n.\ntensor\n([\n0\n])).\nbackward\n()\nprint\n(\nlg\n.\ngrad\n)\n# tensor([[-0.3619, 0.2347, 0.0954, 0.0318]])\nEnter fullscreen mode\nExit fullscreen mode\nthat is\np - y\n, the predicted probability vector minus the one-hot target, exactly, to every decimal place.\n0.6381 - 1 = -0.3619\non the correct class and the raw probability on each of the others. the softmax and the log cancel each other's derivatives and what reaches your network is just \"how far off was each probability\". if you followed\nthe backpropagation article\n, this is the number that starts the whole chain, and every gradient in the model is that vector pushed backwards through the layers.\nit also means the size of the gradient is bounded by 1 per class and it goes to zero exactly when the prediction is right. no tuning, no scaling, it just behaves.\nThe softmax bug, actually measured\nhere is the thing everyone tells you.\nCrossEntropyLoss\napplies softmax internally, so if you also apply softmax in your model's\nforward\n, you softmax twice. everyone repeats it and i have repeated it, and almost nobody says what it actually costs, so that is what i went and measured.\nso, first, it does not fail:\ncorrect\n=\nnn\n.\nCrossEntropyLoss\n()(\nlogits\n,\nlabels\n)\nbugged\n=\nnn\n.\nCrossEntropyLoss\n()(\ntorch\n.\nsoftmax\n(\nlogits\n,\ndim\n=\n1\n),\nlabels\n)\nprint\n(\ncorrect\n)\n# tensor(0.3015)\nprint\n(\nbugged\n)\n# tensor(0.9388)\nEnter fullscreen mode\nExit fullscreen mode\nno error, no warning, two perfectly ordinary numbers. and there is no way for pytorch to catch this, because a probability vector is a valid float tensor and \"valid logits\" is not a thing you can test for.\nwhat happened is that softmax ran on top of softmax. sample 0's logits are\n[2.0, 1.0, 0.1, -1.0]\n, spread over three units. after one softmax they are\n[0.6381, 0.2347, 0.0954, 0.0318]\n, spread over about 0.6. after the second one they are\n[0.3578, 0.2391, 0.2080, 0.1951]\n, spread over 0.16. every pass through softmax squeezes the numbers closer together, and the model's confidence is what gets squeezed out.\nwhat that costs, in one number\ni pushed it to the extreme, a model that is maximally confident and correct, and the same model maximally confident and wrong, and asked both losses what they think.\nconfident + right      confident + wrong\nlogits in                    0.0000                40.0000\nsoftmax in                   1.4612                 2.4612\nEnter fullscreen mode\nExit fullscreen mode\nthat is with 10 classes. a correctly wired cross-entropy has a floor at zero and no ceiling at all, so being badly wrong is expensive without limit and the loss carries real information about how wrong you are. once you softmax first, the entire range of model behaviour from perfect to catastrophic gets compressed into a band exactly\n1.0\nwide. i checked this for 3, 4, 10 and 100 classes and the band is\n1.0000\nevery single time, it only slides upward as the class count grows.\nso the loss stops being a measurement. and the gradient goes with it:\ngradient on the true-class logit of a confidently wrong sample, 10 classes\n\nlogits in     -1.000e+00\nsoftmax in    -4.871e-18\nEnter fullscreen mode\nExit fullscreen mode\nseventeen orders of magnitude. the example your model is most badly wrong about, the one it most needs to learn from, produces a gradient of effectively zero. that is not a rounding artifact, it is the second softmax having saturated, and the derivative of a saturated softmax is nothing.\nand yet it trains\nthis is the part i did not expect and it is the reason i think the standard warning is told wrong. ten classes, twenty dimensions, same network, same seed, same optimizer, the only difference being one\ntorch.softmax\nin the forward pass:\nepoch          1        10        50       200      1000      4000     final test acc\n------------------------------------------------------------------------------------\nlogits in    20.3%     75.9%     85.6%     85.9%     85.0%     82.5%       82.5%\nsoftmax in   12.6%     24.7%     62.4%     85.6%     86.4%     85.4%       85.4%\nEnter fullscreen mode\nExit fullscreen mode\nthe bugged run is far slower, 24.7 percent against 75.9 at epoch 10, and it needs roughly four times as long to get anywhere. but it gets there. it finishes ahead, in fact, because the correct run had started overfitting by epoch 4000 and the crushed gradients acted as a brake. i am not claiming the bug is good, on a real problem with a real budget being four times slower is the whole ball game, and i would not want to defend \"my regularizer is a bug\" to anyone. the point is that \"training will fail quietly\" sets you up to look for a failure, and there is no failure to find.\nwhat you get instead is worse in a more annoying way. your loss is now uninterpretable. that final\nsoftmax in\nrun reports a loss of\n1.53\n. with 10 classes and correct wiring,\n1.53\nwould mean a mediocre model, since a model guessing at random sits at\nlog(10) = 2.3026\nand\n1.53\nis two thirds of the way to useless. with the bug,\n1.53\nis\n0.07\nabove the best value that run can physically produce. same number, opposite meanings, and nothing on your screen says which one you are looking at.\nand accuracy will not save you either, because softmax is monotone, so\nargmax\ndoes not move and your accuracy metric is completely unaffected by the bug. i checked it over a thousand random rows, the predicted class is identical in every one. the only two things that change are the number you watch and the speed you learn, which is exactly the pair you would least like to have quietly corrupted.\nhow to spot it in thirty seconds\nlook at the last line of your\nforward\n. if it is a softmax, a sigmoid, or anything else that squashes into a range, and your loss is\nCrossEntropyLoss\nor\nBCEWithLogitsLoss\n, that is the bug. those two losses do the squashing themselves, in a numerically stable way, which is a second reason to let them.\nthe other check is the floor. run a batch you know the model gets right and see whether the loss can reach something near zero. if the lowest loss you ever see is suspiciously far from zero and suspiciously close to a constant, you are looking at a band, not a measurement.\n# wrong\nclass\nNet\n(\nnn\n.\nModule\n):\ndef\nforward\n(\nself\n,\nx\n):\nreturn\ntorch\n.\nsoftmax\n(\nself\n.\nfc\n(\nx\n),\ndim\n=\n1\n)\n# then CrossEntropyLoss -> bug\n# right\nclass\nNet\n(\nnn\n.\nModule\n):\ndef\nforward\n(\nself\n,\nx\n):\nreturn\nself\n.\nfc\n(\nx\n)\n# raw logits, that is all\n# and at inference time, when you want probabilities to show a user:\nwith\ntorch\n.\nno_grad\n():\nprobs\n=\ntorch\n.\nsoftmax\n(\nmodel\n(\nx\n),\ndim\n=\n1\n)\nEnter fullscreen mode\nExit fullscreen mode\nsoftmax at prediction time is normal and correct. softmax before the loss is the bug. same function, and the only thing that differs is which side of the loss it sits on.\nWhy not just use MSE for classification\npeople ask this, and the answer is the same gradient story. take a model that is confidently wrong on a 3-class problem, logits\n[-6, 0, 0]\nwith the true class being 0, and look at the gradient on the true-class logit under each loss:\ncross-entropy                  -0.998762\nMSE on softmax outputs         -0.00123478\nEnter fullscreen mode\nExit fullscreen mode\n809 times smaller. MSE on a squashed output inherits the squashing, so the more wrong the model is the less it learns, which is precisely backwards. cross-entropy's\np - y\nis at its largest exactly when the model is at its most wrong. that is the entire reason cross-entropy is the classification loss and not just a convention.\nThe error messages, verified\nthese are the literal strings from torch 2.14, not from memory, because the ones you find on stack overflow are frequently from 2019.\nwhat you did\nwhat you get\nfloat labels into\nCrossEntropyLoss\nRuntimeError: expected target dtype to be Long or Byte, but got Float\na label larger than\nnum_classes - 1\nIndexError: Target 9 is out of bounds.\nbatch sizes do not match\nValueError: Expected input batch_size (3) to match target batch_size (2).\nraw logits into\nBCELoss\nRuntimeError: all elements of input should be between 0 and 1\nsoftmax into\nCrossEntropyLoss\nnothing at all, which is the whole article\nthat last row is why this piece exists. four of the five mistakes stop your program. the fifth one promotes itself to a training run.\nworth noting that the\nBCELoss\nrow is the friendlier cousin of the softmax bug.\nBCELoss\ngenuinely requires probabilities and\nBCEWithLogitsLoss\nrequires raw scores, and if you mix those up in the direction of feeding logits to\nBCELoss\nyou at least get told, because a logit of\n1.5\nis outside\n[0, 1]\nand that is checkable. the other direction, sigmoid then\nBCEWithLogitsLoss\n, is silent in exactly the way described above.\nReduction, briefly\nby default every loss averages over the batch, and you almost always want that, because it makes your learning rate independent of batch size.\npred\n=\ntorch\n.\ntensor\n([\n1.0\n,\n2.0\n,\n5.0\n])\ntgt\n=\ntorch\n.\ntensor\n([\n1.0\n,\n3.0\n,\n3.0\n])\nnn\n.\nMSELoss\n(\nreduction\n=\n'\nmean\n'\n)(\npred\n,\ntgt\n)\n# tensor(1.6667)\nnn\n.\nMSELoss\n(\nreduction\n=\n'\nsum\n'\n)(\npred\n,\ntgt\n)\n# tensor(5.)\nnn\n.\nMSELoss\n(\nreduction\n=\n'\nnone\n'\n)(\npred\n,\ntgt\n)\n# tensor([0., 1., 4.])\nEnter fullscreen mode\nExit fullscreen mode\nsum\nscales your effective learning rate with batch size, which is a real bug source when someone changes the batch size and training suddenly diverges.\nnone\nis genuinely useful, it gives you the per-sample loss, which is how you weight samples differently or go find which rows your model hates.\nThe rule\nregression,\nMSELoss\n. multi-class,\nCrossEntropyLoss\non raw logits. binary or multi-label,\nBCEWithLogitsLoss\non raw logits. never squash before a loss whose name contains \"Logits\", and\nCrossEntropyLoss\ncounts even though its name does not say so. MSE is\n((pred - target) ** 2).mean()\nand cross-entropy is\n-log(softmax(logits)[label])\n, and if you can write both of those from memory you understand loss functions well enough to move on.\nTry it before you close the tab\ntake the ten-class setup, or any classifier you already have, and add\ntorch.softmax(x, dim=1)\nto the end of the forward pass. do not change anything else. then train it and look only at the loss curve and tell me you could spot which one is broken. that experiment took me about four minutes and it is the reason i no longer trust a falling loss on its own.\nthen print the smallest loss your run ever reaches and compare it to\nlog(num_classes)\n. a healthy run gets near zero on data it has memorised. a run stuck at a constant well above zero is telling you something, and this bug is one of the things it could be telling you.\nwhat is the loss value you would consider \"good\" on your current problem, and do you know why that number and not a different one?\ni asked myself that while writing this chapter and my honest answer was that i had been reading loss curves by their shape for a long time and had never once checked what the floor should be.\nThis is one chapter's worth of an idea from my book,\nPyTorch From Ground Up\n,\nwhich builds everything from tensors upward so nothing stays vague. If it helped:\n8 chapters are free, no email required\n, there's a\nfree one-page tensor cheat-sheet here\n, every example runs in the\ncompanion notebooks on GitHub\n, and the full book is on\nLeanpub\nor in\npaperback and Kindle on Amazon\n.\nMore in this series\nHow Training Actually Works\n, the part where the training loop stops being magic:\nPyTorch Autograd Explained: What\n.backward()\nActually Does\nBackpropagation by Hand: Two Layers, a Pen, and Then Autograd Agrees\nWhat\noptimizer.step()\nActually Does: SGD, Momentum and Adam by Hand\nThe shape mechanics underneath all of it, worth having solid first:\nReshape vs View in PyTorch\nPyTorch Broadcasting Explained\nWhat Does\nunsqueeze\nDo in PyTorch?\nWhat Does\nkeepdim\nDo in PyTorch?\nPyTorch\npermute\nvs\ntranspose\nComing next in\nHow Training Actually Works\n: building the same network twice, once from raw tensors and once from\nnn.Module\n, and what the module system is actually keeping track of for you.",
  "metadata": {
    "title": "What Your Loss Function Actually Tells the Model: MSE, Cross-Entropy, and the softmax Bug That Trains Anyway - DEV Community",
    "description": "the last three articles walked the training loop backwards, .backward() and what it fills in, the... Tagged with pytorch, deeplearning, machinelearning, beginners.",
    "keywords": "pytorch, deeplearning, machinelearning, beginners, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/pytorchfromgroundup/what-your-loss-function-actually-tells-the-model-mse-cross-entropy-and-the-softmax-bug-that-fan",
    "og:title": "What Your Loss Function Actually Tells the Model: MSE, Cross-Entropy, and the softmax Bug That Trains Anyway",
    "og:description": "the last three articles walked the training loop backwards, .backward() and what it fills in, the...",
    "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%2Farzh1uvut76w1emi9s2c.png",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@wesamkhallaf",
    "twitter:title": "What Your Loss Function Actually Tells the Model: MSE, Cross-Entropy, and the softmax Bug That Trains Anyway",
    "twitter:description": "the last three articles walked the training loop backwards, .backward() and what it fills in, the...",
    "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%2Farzh1uvut76w1emi9s2c.png"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/7fa85e83de16/code/body-000.txt",
      "chars": 101,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-001.txt",
      "chars": 239,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-002.txt",
      "chars": 166,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-003.txt",
      "chars": 212,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-004.txt",
      "chars": 300,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-005.txt",
      "chars": 229,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-006.txt",
      "chars": 188,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-007.txt",
      "chars": 187,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-008.txt",
      "chars": 182,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-009.txt",
      "chars": 126,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-010.txt",
      "chars": 333,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-011.txt",
      "chars": 410,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-012.txt",
      "chars": 84,
      "language": "text"
    },
    {
      "file": "attachments/7fa85e83de16/code/body-013.txt",
      "chars": 254,
      "language": "text"
    }
  ],
  "images": [],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://dev.to/pytorchfromgroundup/pytorch-broadcasting-explained-the-3-rules-and-the-silent-bug-that-bites-everyone-3606",
      "label": "the broadcasting article",
      "type": "a"
    },
    {
      "href": "https://dev.to/pytorchfromgroundup/backpropagation-by-hand-two-layers-a-pen-and-then-autograd-agrees-13i6",
      "label": "the backpropagation article",
      "type": "a"
    },
    {
      "href": "https://github.com/pytorch-from-ground-up/book_code/blob/main/PyTorch-From-Ground-Up-8-Free-Chapters.pdf",
      "label": "8 chapters are free, no email required",
      "type": "a"
    },
    {
      "href": "https://payhip.com/b/7ukxh",
      "label": "free one-page 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": "Leanpub",
      "type": "a"
    },
    {
      "href": "https://www.amazon.com/dp/B0H8WMCV33",
      "label": "paperback and Kindle on Amazon",
      "type": "a"
    },
    {
      "href": "https://dev.to/pytorchfromgroundup/pytorch-autograd-explained-what-backward-actually-does-4acg",
      "label": "PyTorch Autograd Explained: What .backward() Actually Does",
      "type": "a"
    },
    {
      "href": "https://dev.to/pytorchfromgroundup/what-optimizerstep-actually-does-one-step-of-sgd-momentum-and-adam-by-hand-44fe",
      "label": "What optimizer.step() Actually Does: SGD, Momentum and Adam by Hand",
      "type": "a"
    },
    {
      "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/what-does-unsqueeze-do-in-pytorch-and-why-your-model-keeps-asking-for-it-181a",
      "label": "What Does unsqueeze Do in PyTorch?",
      "type": "a"
    },
    {
      "href": "https://dev.to/pytorchfromgroundup/what-does-keepdim-do-in-pytorch-and-the-silent-bug-when-you-forget-it-17p1",
      "label": "What Does keepdim Do in PyTorch?",
      "type": "a"
    },
    {
      "href": "https://dev.to/pytorchfromgroundup/pytorch-permute-vs-transpose-whats-the-difference-and-the-reshape-bug-that-scrambles-your-7ke",
      "label": "PyTorch permute vs transpose",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:48:51Z",
  "source_url": "https://dev.to/pytorchfromgroundup/what-your-loss-function-actually-tells-the-model-mse-cross-entropy-and-the-softmax-bug-that-fan",
  "papers": [
    {
      "status": "ok",
      "url": "https://raw.githubusercontent.com/pytorch-from-ground-up/book_code/main/PyTorch-From-Ground-Up-8-Free-Chapters.pdf",
      "bytes": 2552607,
      "reused": true,
      "local_path": "papers/repair/2927ef550363909d894f.pdf"
    }
  ],
  "actual_video_files": [],
  "body_chars": 18149,
  "expected_body_images": 0,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/7fa85e83de16/pages/source.html",
  "local_html": "attachments/7fa85e83de16/pages/article.html",
  "body_base_path": "attachments/7fa85e83de16/pages/",
  "body_markdown_path": "attachments/7fa85e83de16/pages/article.md"
}