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

从零搭建神经网络无法拟合小样本?求排查反向传播问题

问题:两层Sigmoid神经网络训练后输出固定类分布,单层模型效果更优

我手动实现了仅用sigmoid激活函数的两层神经网络,尝试拟合小样本训练数据,但训练后无论输入什么,模型始终输出固定类分布;移除隐藏层改用单层神经网络时,效果却好很多。

我知道正确实现应全程使用向量化运算,但想先手动实现以深入理解训练的每一个环节,怀疑反向传播存在错误,反复检查却未能发现问题。

补充说明:我了解仅使用sigmoid可能存在梯度消失问题,但仅两层隐藏层应该仍可正常工作,希望能在仅用sigmoid和手动运算的前提下解决问题。


实现代码

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt

data = pd.read_csv('train.csv')

# Training data management
data= np.array(data)

# Train test split 80:20
test_datas = data[int(len(data)*0.8):]
train_datas = data[:int(len(data)*0.8)]

# Separating pixel data and label data
train_labels = train_datas[:,0] # label col
train_datas = (train_datas[:,1:] - np.min(train_datas[:,1:]))/(np.max(train_datas[:,1:])-np.min(train_datas[:,1:])) # pixel data, scaled to 0-1

test_labels = test_datas[:,0] # label col
test_datas = (test_datas[:,1:] - np.min(test_datas[:,1:]))/(np.max(test_datas[:,1:])-np.min(test_datas[:,1:])) # pixel data, scaled to 0-1

def sigmoid(x): # sigmoid func to squish all inputs into range 0 to 1
    return 1 / (1 + np.exp(-x))

# Initialization
size=[16, 10]
train_data = train_datas[:10] 
train_label = train_labels

weights = [] # list to store all the weights for every layer
biases = [] # list to store all the biases for every layer

# Try using Xavier/Glorot initialization
for i in range(len(size)): # Initialize weights for each layer
    if i == 0:
        weights.append(np.random.randn(size[0], len(train_data[0])) * np.sqrt(1/len(train_data[0])))
    else:
        weights.append(np.random.randn(size[i], size[i-1]) * np.sqrt(1/size[i-1]))

for i in range(len(size)):  # Initialize biases for each layer
    if i == 0:
        biases.append(np.zeros(size[0])) # First layer biases
    else:
        biases.append(np.zeros(size[i]))  

# Temporarily training on 10 data example for trouble shooting
learning_rate = 0.1
for w in range(1):
    train_data = train_datas[w*10:(w+1)*10] 
    for o in range(10):
        cost = 0

        # Create temporary storage for averaging weights and biases
        temp_weights = [] 
        temp_biases = [] 

        temp_weights.append(np.zeros(shape=(size[0],len(train_data[0])))) # First layer
        temp_biases.append(np.zeros(size[0])) 
        for i in range(len(size)-1): 
            temp_weights.append(np.zeros(shape=(size[i+1],size[i]))) # following layers
            temp_biases.append(np.zeros(size[i+1])) 

        for i in range(len(train_data)): # Iterate through every train_data
            # Forward propagation
            Zs = []
            As = [train_data[i]] 
            z = weights[0] @ train_data[i] + biases[0] # First layer
            a = sigmoid(z)
            Zs.append(z) # Storing data for backward propagation
            As.append(a)
            
            for j in range(len(size)-1): 
                z = weights[j+1] @ a + biases[j+1] # Following layers
                a = sigmoid(z)
                Zs.append(z) # Storing data for backward propagation
                As.append(a)

            # Calculating cost
            one_hot = np.zeros(10)
            one_hot[train_label[i]]=1
            
            cost = cost + np.sum((a - one_hot)**2) # Just to keep track of model fit

            # final/output layer Backpropagation
            dC_da = 2*(a - one_hot) 
            dadz = (np.exp(-z) / (1 + np.exp(-z))**2)
            
            for x in range (len(weights[-1][0])): # iterating through weights column by column
                # updating weights              
                dzdw = As[-2][x] # This one input, affects a whole column of weights
                dC_dw = dC_da * dadz * dzdw 

                (temp_weights[-1])[:,x] += -dC_dw*learning_rate/len(train_data) # keeping track of updates to the weights
                
            # updating Biases
            dzdb = 1
            dC_db = dC_da * dadz * dzdb
            temp_biases[-1] += -dC_db*(learning_rate)/len(train_data) # keeping track of updates to the biases

            # Previous layer Backpropagation
            dCda_0 = np.array([])
            for x in range (len(weights[-1][0])): # iterating through inputs, a, summing weights column by column, 
                dzda_0 = weights[-1][:,x] # A whole column of weights affect how ONE prev layer input affects the next layer 
                dC_da_0 = np.sum(dC_da*dadz*dzda_0)/len(weights[-1]) # Keep track of how previous layer output affect next layer for chain rule later
                dCda_0 = np.append(dCda_0,dC_da_0)

            # Previous layer weights
            for k in range(len(size)-1): # iterating through layers, starting from the second last
                z = Zs[-k-2]
                dadz = (np.exp(-z) / (1 + np.exp(-z))**2)
                
                # Updating previous layer weights
                for l in range (len(weights[-2-k][0])): # iterating through weights column by column (-2-k because we start from second from last)
                    
                    dzdw = As[-3-k][l] # This one input, affects a whole column of weights
                    dC_dw = dCda_0 * dadz * dzdw
                    
                    (temp_weights[-2-k])[:,l] += -dC_dw*(learning_rate)/len(train_data) # keeping track of updates to the weights

                # updating Biases
                dzdb = 1
                dC_db = dCda_0 * dadz * dzdb
                temp_biases[-2-k] += -dC_db*(learning_rate)/len(train_data) # keeping track of updates to the biases

                # Keep track of how this layer output affect next layer for chain rule later
                temp_dCda_0 = np.array([])
                for x in range (len(weights[-2-k][0])): # iterating through inputs, a, summing weights column by column
                    dzda_0 = weights[-2-k][:,x] # A whole column of weights affect how ONE prev layer input affects the next layer 
                    dC_da_0 = np.sum(dCda_0*dadz*dzda_0)/len(weights[-2-k]) 
                    temp_dCda_0 = np.append(temp_dCda_0,dC_da_0)
                
                dCda_0 = temp_dCda_0 

        # Updating biases and weights
        for i in range(len(size)):
            weights[i] += temp_weights[i]
            biases[i] += temp_biases[i]

        # Analysis of changes to weights 
        print("weights, iteration",o)
        print(temp_weights[0][0][132:136])
        print("\n", weights[0][0][132:136])
        print("\n",temp_weights[1][0])
        print("\n", weights[1][0])

        # Analysis of changes to biases 
        print("biases, iteration",o)
        print("\n",temp_biases[0])
        print("\n", biases[0])
        print("\n", temp_biases[1])
        print("\n", biases[1])

# Forward propagation, testing training fit
m=0
z = weights[0] @ train_datas[m] + biases[0] # First layer
a = sigmoid(z)
print("\nFirst layer, \nz=",z,"\na=",a )

for j in range(len(size)-1): 
    z = weights[j+1] @ a + biases[j+1] # Following layers
    a = sigmoid(z)
    print("\n",j+1,"th layer, \nz=",z,"\na=",a )

print("\nevaluation=",a,"max= ",np.argmax(a)," label= ",train_labels[m])

# Forward propagation, testing training fit
m=4
z = weights[0] @ train_datas[m] + biases[0] # First layer
a = sigmoid(z)
print("\nFirst layer, \nz=",z,"\na=",a )

for j in range(len(size)-1): 
    z = weights[j+1] @ a + biases[j+1] # Following layers
    a = sigmoid(z)
    print("\n",j+1,"th layer, \nz=",z,"\na=",a )

print("\nevaluation=",a,"max= ",np.argmax(a)," label= ",train_labels[m])

# Check accuracy on training set
correct = 0
k = 100
for i in range(k):
    z = weights[0] @ train_datas[i] + biases[0] # First layer
    a = sigmoid(z)

    for j in range(len(size)-1): 
        z = weights[j+1] @ a + biases[j+1] # Following layers
        a = sigmoid(z)

    if train_labels[i] == np.argmax(a): 
        correct += 1
        
print(correct/k)

核心问题排查与修正

1. 梯度计算的错误缩放

反向传播中计算前一层神经元梯度时,你错误地对求和结果做了除法(/len(weights[-1])、/len(weights[-2-k])),这会导致梯度被不必要地缩小,直接影响权重更新的有效性:

# 错误代码
dC_da_0 = np.sum(dC_da*dadz*dzda_0)/len(weights[-1])
# 修正后
dC_da_0 = np.sum(dC_da*dadz*dzda_0)

反向传播中,前一层每个神经元的梯度是后一层所有相关梯度的加权和,不需要额外做平均操作。

2. 样本与标签不匹配

训练时train_label直接使用了整个训练集的标签,但train_data只取了10个样本,导致样本和标签对应错误:

# 错误代码
train_label = train_labels
# 修正后,取对应批次的标签
train_label = train_labels[w*10:(w+1)*10]

3. Sigmoid导数计算优化

原代码中sigmoid导数的计算可以简化为a * (1 - a),既高效又避免数值不稳定:

# 替换原来的dadz计算
dadz = a * (1 - a)

4. 训练迭代次数不足

当前仅做了10次迭代,对于两层网络来说远远不够,建议将迭代次数提升至1000次以上,同时可以适当调高学习率(比如0.5或1.0),弥补手动计算的效率问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 19:35:52