You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python3复现Nielsen MNIST神经网络无法学习问题排查

问题说明
  • 尝试复现Michael Nielsen神经网络教程第一章的MNIST手写数字识别代码,原代码基于Python 2.7编写,本次使用Python 3实现。
  • 网络可以正常完成样本遍历、权重与偏置更新流程,但完全没有学习效果,测试集准确率稳定在10%左右,和随机猜测水平相当。
  • 此前直接复制原代码在Python 2.7环境运行可以达到95%左右的准确率。当前实现和原示例只有两处核心差异:
    • 使用自行从MNIST官网下载的最新数据集
    • 为了方便跟踪数组形状,统一使用(N,)格式一维数组代替原代码的(N,1)列向量,将反向传播中两处计算权重梯度的np.dot替换为np.outer,核对过各层矩阵运算维度匹配,学习率、网络层尺寸等超参数和原示例完全一致。
  • 目前未定位到网络失效原因,求排查思路。
  • 完整实现代码如下:
import matplotlib.pyplot as plt
import numpy as np
import idx2numpy
import random

### LOAD DATASET ###
train = idx2numpy.convert_from_file("mnist/train-images.idx3-ubyte")
train_labels = idx2numpy.convert_from_file("mnist/train-labels.idx1-ubyte")
test = idx2numpy.convert_from_file("mnist/t10k-images.idx3-ubyte")
test_labels = idx2numpy.convert_from_file("mnist/t10k-labels.idx1-ubyte")

def vectorize(x):
    e = np.zeros(10)
    e[x] = 1.0
    return e

training_images = [np.reshape(i, (784))/255 for i in train]
training_labels = [vectorize(i) for i in train_labels]
training_set = list(zip(training_images,training_labels))

test_images = [np.reshape(i, (784))/255 for i in test]
test_set = list(zip(training_images,test_labels)) # 错误点

### NETWORK CLASS ###
class myNet():
    def __init__ (self , sizes ):
        self.sizes = sizes
        self.N = len(sizes)
        self.w = [np.random.randn(y, x) for x, y in zip( sizes [: -1] , sizes [1:]) ]
        self.b = [np.random.randn(i) for i in sizes[1:]]

    def sigmoid (self,z):
        return 1.0/(1.0+ np.exp(-z))

    def sigmoid_prime (self,z):
        return self.sigmoid (z)*(1 - self.sigmoid (z))

    def cost_derivative (self,output_activations , y):
        return ( output_activations - y)

    def feedforward (self , a):
        for bb, ww in zip(self.b , self.w ):
            a = self.sigmoid (np.dot(ww, a)+bb)
        return a

    def backprop (self , x, y):
        nabla_b = [np. zeros (bb. shape ) for bb in self.b ]
        nabla_w = [np. zeros (ww. shape ) for ww in self.w ]
        activation = x
        activations = [x] 
        zs = [] 
        for bb, ww in zip(self.b , self. w ):
            z = np.dot(ww, activation )+bb
            zs. append (z)
            activation = self.sigmoid (z)
            activations . append ( activation )
        # 反向传播
        delta = self. cost_derivative ( activations [-1], y) * self.sigmoid_prime (zs [ -1])
        nabla_b [-1] = delta
        nabla_w [-1] = np.outer(delta , activations [ -2])

        for l in range (2, self.N ):
            z = zs[-l]
            sp = self.sigmoid_prime (z)
            delta = np.dot(self.w [-l+1]. transpose () , delta ) * sp
            nabla_b [-l] = delta
            nabla_w [-l] = np.outer(delta , activations [-l -1])
        return (nabla_b , nabla_w )

    def update(self,mini_batch,eta):
        nabla_b = [np.zeros (bb.shape ) for bb in self.b ]
        nabla_w = [np.zeros (ww.shape ) for ww in self.w ]
        for x, y in mini_batch :
            delta_nabla_b , delta_nabla_w = self. backprop (x, y)
            nabla_b = [nb+dnb for nb , dnb in zip(nabla_b , delta_nabla_b )]
            nabla_w = [nw+dnw for nw , dnw in zip(nabla_w , delta_nabla_w )]
        self.w = [ww -( eta/len( mini_batch ))*nw
                         for ww, nw in zip(self.w , nabla_w )]
        self.b = [bb -( eta/len( mini_batch ))*nb
                        for bb, nb in zip(self.b, nabla_b )]
        return

    def gradient_descent(self,training_data,epochs,mini_batch_size,eta,test_data):
        n = len( training_data )
        for j in range (epochs):
            random.shuffle (training_data)
            mini_batches = [
                training_data [k:k+ mini_batch_size ]
                for k in range (0, n, mini_batch_size )]
            for mini_batch in mini_batches :
                self.update( mini_batch , eta)
            print("Epoch {0}: {1}". format (
                j, self.evaluate(test_data)))
        return

    def evaluate (self, test_data):
        test_results = [( np.argmax (self.feedforward (x)), y)
                        for (x, y) in test_data ]
        return sum(int(x == y) for (x, y) in test_results )

sizes =[28*28, 30, 10]
net = myNet(sizes)
net.gradient_descent(training_set,30,10,3.0,test_set)
问题根因

和你换Python3、改np.outer、新下载的数据集都没关系,就是个手滑的低级错误:构造测试集的时候把训练集图片和测试集标签配对了。
你写的错误行:

test_set = list(zip(training_images,test_labels))

前面明明已经预处理完存了test_images,这里错写成了training_images。Python的zip会按长度更短的列表截断,最后生成的1万条测试数据,全是训练集前1万张图搭配完全不相关的测试集标签,二者没有任何对应关系。不管你模型训练得有多准,拿训练图的预测结果去对完全无关的测试标签,准确率自然就是10%上下的随机猜测水平。

修复方法

把这行里的training_images换成test_images即可:

test_set = list(zip(test_images,test_labels))

改完直接跑就能达到原代码95%左右的准确率。
补充:你把列向量换成一维数组、用np.outer计算权重梯度的写法完全正确,维度匹配没有问题,这部分不用调整。

内容的提问来源于stack exchange,提问作者nfko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 01:15:32