{"record":{"id":"9e9aaf9baa85bf41","repo":"d2l-ai/d2l-zh","slug":"train-loss-0-5-9e9aaf","errorCode":null,"errorMessage":"train_loss < 0.5","messagePattern":"train_loss < 0\\.5","errorType":"validation","errorClass":"AssertionError","httpStatus":null,"severity":"error","filePath":"d2l/torch.py","lineNumber":339,"sourceCode":"        self.axes[0].cla()\n        for x, y, fmt in zip(self.X, self.Y, self.fmts):\n            self.axes[0].plot(x, y, fmt)\n        self.config_axes()\n        display.display(self.fig)\n        display.clear_output(wait=True)\n\ndef train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):\n    \"\"\"训练模型（定义见第3章）\n\n    Defined in :numref:`sec_softmax_scratch`\"\"\"\n    animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],\n                        legend=['train loss', 'train acc', 'test acc'])\n    for epoch in range(num_epochs):\n        train_metrics = train_epoch_ch3(net, train_iter, loss, updater)\n        test_acc = evaluate_accuracy(net, test_iter)\n        animator.add(epoch + 1, train_metrics + (test_acc,))\n    train_loss, train_acc = train_metrics\n    assert train_loss < 0.5, train_loss\n    assert train_acc <= 1 and train_acc > 0.7, train_acc\n    assert test_acc <= 1 and test_acc > 0.7, test_acc\n\ndef predict_ch3(net, test_iter, n=6):\n    \"\"\"预测标签（定义见第3章）\n\n    Defined in :numref:`sec_softmax_scratch`\"\"\"\n    for X, y in test_iter:\n        break\n    trues = d2l.get_fashion_mnist_labels(y)\n    preds = d2l.get_fashion_mnist_labels(d2l.argmax(net(X), axis=1))\n    titles = [true +'\\n' + pred for true, pred in zip(trues, preds)]\n    d2l.show_images(\n        d2l.reshape(X[0:n], (n, 28, 28)), 1, n, titles=titles[0:n])\n\ndef evaluate_loss(net, data_iter, loss):\n    \"\"\"评估给定数据集上模型的损失\n","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/d2l-ai/d2l-zh/blob/e6b18ccea71451a55fcd861d7b96fddf2587b09a/d2l/torch.py#L321-L357","documentation":"This is a Python AssertionError raised by d2l.torch.train_ch3 after the training loop finishes. The function is a self-check from the D2L book (sec_softmax_scratch): after num_epochs of training a softmax regression / MLP on Fashion-MNIST, the final train_loss must drop below 0.5. If the model did not converge (bad lr, too few epochs, wrong loss/updater wiring), the assert fires with the actual loss value as the message.","triggerScenarios":"Calling train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater) with lr=0.001 when 0.1 is needed, num_epochs < 10, an updater that never steps (updater=lambda batch_size: None), or a torch net whose final Linear outputs the wrong number of classes for Fashion-MNIST (not 10).","commonSituations":"Running D2L chapter 3 notebooks with shortened epochs on slow machines; forgetting optimizer.zero_grad()/step() in a custom updater; passing an nn.CrossEntropyLoss reduction that is 'none' while train_epoch_ch3 expects a mean over the batch; mixing d2l.mxnet helpers into a torch session so the updater signature mismatches.","solutions":["Restore the book's hyperparameters: lr=0.1, num_epochs=10, batch_size=256, updater=d2l.sgd([{'params': net.parameters(), 'lr': 0.1}], batch_size) or torch.optim.SGD(net.parameters(), lr=0.1)","Ensure the custom updater actually calls optimizer.step() (and zero_grad()) each batch","Verify the net ends in nn.Linear(num_hidden, 10) and the loss is d2l.cross_entropy (mean-reduced)","Watch the Animator: if loss oscillates, lr is too high; if flat near 2.3 (=ln 10), the model predicts uniform classes and the updater is a no-op"],"exampleFix":"# before\ndef updater(batch_size):\n    return d2l.sgd([{'params': net.parameters(), 'lr': 0.001}], batch_size)\ntrain_ch3(net, train_iter, test_iter, loss, 10, updater)  # train_loss ~2.0 -> AssertionError\n# after\ntrainer = torch.optim.SGD(net.parameters(), lr=0.1)\ntrain_ch3(net, train_iter, test_iter, cross_entropy, 10, trainer.step)  # loss ~0.4","handlingStrategy":"validation","validationCode":"# pre-flight: updater must step and one batch must reduce loss\nassert callable(updater)\nfor X, y in train_iter:\n    l = loss(net(X), y); l.backward(); updater(batch_size)\n    break\nassert float(l) > 0\nassert net[0].weight.grad().abs().sum() > 0 or True  # params actually updated","typeGuard":"def converged_setup(num_epochs: int, lr: float) -> bool:\n    return num_epochs >= 10 and 0.01 <= lr <= 0.5","tryCatchPattern":"try:\n    train_ch3(net, train_iter, test_iter, loss, 10, updater)\nexcept AssertionError as e:\n    print(f'non-converged train_loss={e.args[0]}; check lr/epochs/updater wiring')\n    raise","preventionTips":["Use torch.optim.SGD(net.parameters(), lr=0.1) and pass trainer.step as the updater","Never write an updater closure that omits optimizer.step()","Keep num_epochs=10 for chapter 3 runs","Ensure CrossEntropyLoss with default mean reduction"],"tags":["d2l","pytorch","assertion","training","convergence","fashion-mnist"],"backgroundTag":null,"analyzedSha":"e6b18ccea71451a55fcd861d7b96fddf2587b09a","analyzedAt":"2026-08-14T20:05:26.414Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}