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

自定义MLP模型中logit(expit(50))=inf引发NaN问题的解决方法

MLP反向传播中NaN/Inf问题的解决方案与代码优化

问题判断

你对问题的分析完全正确:这是Sigmoid激活函数饱和引发的数值不稳定问题,最终导致反向传播中出现Inf和NaN。当Sigmoid输入过大时输出趋近于1,此时用logit计算导数会得到np.inf,后续运算中正负Inf相加产生NaN,进而污染整个参数矩阵。

核心解决方法

1. 修正Sigmoid导数的计算逻辑

Sigmoid的导数不需要通过logit反推,其数学性质为:
σ'(x) = σ(x) * (1 - σ(x))
直接用激活后的输出值计算导数,完全避免logit(1)导致的Inf问题。

2. 优化权重初始化

当前的np.random.rand(...) *2 -1均匀初始化容易导致加权和数值过大,改用Xavier初始化(适配Sigmoid/Tanh):

np.random.randn(number_of_nodes[i], number_of_nodes[i-1]) * np.sqrt(1 / number_of_nodes[i-1])

该初始化方式保证每层输入的方差稳定,减少激活函数饱和的概率。

3. 数值截断(可选)

对进入Sigmoid的加权和进行截断,限制在[-10,10]范围内,因为expit(10)≈0.99995,已经足够接近1,不会触发logit的Inf:

weighted_sum = np.matmul(weights[j-1], neuron_values[j-1]) + biases[j-1]
weighted_sum = np.clip(weighted_sum, -10, 10)
neuron_values[j] = activate(weighted_sum, "Sigmoid")

4. 替换激活函数(可选)

隐藏层改用ReLU/Leaky ReLU,这类激活函数不会出现饱和问题,从根源上避免数值不稳定。

代码中的关键问题修正

1. 修正deactivate函数

def deactivate(values, activation_type):
    if activation_type == "Sigmoid":
        # 正确的Sigmoid导数计算
        return values * (1 - values)
    if activation_type == "ReLU":
        return np.where(values > 0, 1, 0)
    if activation_type == "Softmax":
        # Softmax导数的正确形式(针对单个样本)
        diag = np.diagflat(values)
        outer = np.outer(values, values)
        return diag - outer
    if activation_type == "Tanh":
        return 1 - np.power(values, 2)

2. 权重初始化优化

for i in range(1, len(number_of_nodes)):
    # Xavier初始化
    weights.append(np.random.randn(number_of_nodes[i], number_of_nodes[i-1]) * np.sqrt(1 / number_of_nodes[i-1]))
    biases.append(np.zeros((number_of_nodes[i], 1)))  # 偏置初始化为0即可
    neuron_values.append(np.zeros((number_of_nodes[i], 1)))

3. 输入处理修正

在feed_forward中确保输入是列向量:

def feed_forward(inputs):
    # 将输入转为(784,1)的列向量
    neuron_values[0] = inputs.reshape(-1, 1)
    for j in range(1, len(number_of_nodes) - 1):
        weighted_sum = np.matmul(weights[j-1], neuron_values[j-1]) + biases[j-1]
        # 可选:数值截断
        weighted_sum = np.clip(weighted_sum, -10, 10)
        neuron_values[j] = activate(weighted_sum, "Sigmoid")
    # 输出层Softmax
    weighted_sum = np.matmul(weights[j], neuron_values[j]) + biases[j]
    neuron_values[j+1] = activate(weighted_sum, "Softmax")
    return neuron_values[-1]

4. 损失函数优化(可选)

分类任务更适合交叉熵损失,替代当前的MSE损失,能提升收敛速度和数值稳定性:

# 新增交叉熵损失计算
def cross_entropy_loss(outputs, targets):
    # 防止log(0),加极小值
    return -np.sum(targets * np.log(outputs + 1e-10))

# 反向传播中输出层误差修正(交叉熵+Softmax的简化导数)
errors[-1] = outputs - targets  # 交叉熵损失下的误差项,无需乘以2

完整修正后的代码

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import expit, softmax


LEARNING_RATE = 0.01
TEST_DATA_AMOUNT = 0.8
EPOCHS = 20
number_of_nodes = [28 * 28, 16, 16, 10]


df = pd.read_csv("5000_records_784I_1O_mnist.csv", index_col=0)
x_data = df.iloc[:, : 28 * 28]
y_data = df.iloc[:, 28 * 28 :]
normalized_x_data = x_data / 255
x_train = normalized_x_data[
    : round(TEST_DATA_AMOUNT * len(normalized_x_data))
].to_numpy()
y_train = y_data[: round(TEST_DATA_AMOUNT * len(y_data))].to_numpy()
x_test = normalized_x_data[
    round(TEST_DATA_AMOUNT * len(normalized_x_data)) :
].to_numpy()
y_test = y_data[round(TEST_DATA_AMOUNT * len(y_data)) :].to_numpy()

NUMBER_OF_RECORDS = x_train.shape[0]

neuron_values = [np.zeros((number_of_nodes[0], 1))]
weights = []
biases = []
for i in range(1, len(number_of_nodes)):
    # Xavier初始化
    weights.append(np.random.randn(number_of_nodes[i], number_of_nodes[i-1]) * np.sqrt(1 / number_of_nodes[i-1]))
    biases.append(np.zeros((number_of_nodes[i], 1)))
    neuron_values.append(np.zeros((number_of_nodes[i], 1)))

predicted_outputs = []
network_errors = []


def activate(values, activation_type):
    if activation_type == "Sigmoid":
        return expit(values)
    if activation_type == "ReLU":
        return np.maximum(values, 0)
    if activation_type == "Softmax":
        return softmax(values)
    if activation_type == "Tanh":
        return np.tanh(values)


def deactivate(values, activation_type):
    if activation_type == "Sigmoid":
        # 正确的Sigmoid导数
        return values * (1 - values)
    if activation_type == "ReLU":
        return np.where(values > 0, 1, 0)
    if activation_type == "Softmax":
        # Softmax导数矩阵(单个样本)
        diag = np.diagflat(values)
        outer = np.outer(values, values)
        return diag - outer
    if activation_type == "Tanh":
        return 1 - np.power(values, 2)


def one_hot(value):
    one_hot_value = np.zeros((10, 1))
    one_hot_value[value] = 1
    return one_hot_value


def feed_forward(inputs):
    # 确保输入是列向量
    neuron_values[0] = inputs.reshape(-1, 1)
    for j in range(1, len(number_of_nodes) - 1):
        weighted_sum = np.matmul(weights[j-1], neuron_values[j-1]) + biases[j-1]
        # 数值截断,防止Sigmoid饱和
        weighted_sum = np.clip(weighted_sum, -10, 10)
        neuron_values[j] = activate(weighted_sum, "Sigmoid")
    # 输出层
    weighted_sum = np.matmul(weights[j], neuron_values[j]) + biases[j]
    neuron_values[j+1] = activate(weighted_sum, "Softmax")
    return neuron_values[-1]


def back_propagate(outputs, targets, learning_rate):
    errors = [[] for _ in range(1, len(number_of_nodes))]
    gradients = [[] for _ in range(1, len(number_of_nodes))]
    
    # 输出层:交叉熵损失下的误差项(替代MSE的2*(outputs-targets))
    errors[-1] = outputs - targets
    # Softmax导数与误差的乘积,这里简化为直接相乘(因为单个样本时矩阵乘法等价于点乘)
    gradients[-1] = learning_rate * np.matmul(errors[-1].T, deactivate(outputs, "Softmax")).T
    biases[-1] -= gradients[-1]
    weights[-1] -= np.matmul(gradients[-1], neuron_values[-2].T)
    
    # 隐藏层反向传播
    for j in range(len(number_of_nodes) - 3, -1, -1):
        errors[j] = np.matmul(weights[j+1].T, errors[j+1])
        gradients[j] = learning_rate * errors[j] * deactivate(neuron_values[j+1], "Sigmoid")
        biases[j] -= gradients[j]
        weights[j] -= np.matmul(gradients[j], neuron_values[j].T)
    
    return outputs - targets


def guess(inputs):
    return feed_forward(inputs)


def train(inputs, target, epochs, learning_rate):
    for epoch in range(epochs):
        p = np.random.permutation(len(inputs))
        shuffled_inputs = inputs[p]
        shuffled_targets = target[p]
        epoch_total_error = 0
        for j, set_of_inputs in enumerate(shuffled_inputs):
            epoch_input = set_of_inputs
            epoch_target = shuffled_targets[j]
            outputs = feed_forward(epoch_input)
            error = back_propagate(outputs, one_hot(epoch_target), learning_rate)
            sum_of_squared_errors = np.sum(np.power(error, 2))
            epoch_total_error += sum_of_squared_errors
            network_errors.append(sum_of_squared_errors)
        # 可选:学习率衰减
        # learning_rate *= 0.99
        print(
            f"Epoch #{epoch+1}: Average squared error: {epoch_total_error/len(shuffled_inputs):.4f}, Last Target: {epoch_target}, Last Predict: {outputs.argmax()}"
        )


train(x_train, y_train, EPOCHS, LEARNING_RATE)
plt.figure()
plt.plot(network_errors)
plt.grid()
plt.title("Iteration Number vs Error")
plt.xlabel("Iteration Number")
plt.ylabel("Squared Error")
plt.show()
print("Trained weights:")
for layer_weights in weights:
    print(layer_weights.shape)
    print(layer_weights)
print("Trained biases:")
for layer_biases in biases:
    print(layer_biases.shape)
    print(layer_biases)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 08:25:55