一、核心概念

1.基本定义 

        神经网络是一种模仿人脑神经元结构的机器学习模型,由输入层、隐藏层、输出层组成,核心是通过数据驱动的方式学习输入与输出之间的映射关系。

        (注:通过输入层、隐藏层、输出层顺序串联,完成从原始数据到最终预测结果的转换,就像工厂的 “原材料接收→加工生产→成品输出” 流水线,且只有输入层和输出层是 “可见层”,直接对接数据和结果;隐藏层是网络的 “黑箱核心”,负责特征学习和数据变换。)

2.核心流程

1. 前向传播——预测环节

数据从输入层进入,依次经过隐藏层、输出层:

  • 输入层:只传递原始数据,如图像像素、文字特征,不做任何处理,神经元数量和数据维度一致;
  • 隐藏层:网络的 “核心加工区”,通过权重调整输入信号的重要性,再用激活函数做非线性转换,把原始数据变成有意义的特征,如从像素提取图像边缘、笔画,层数和神经元数量可自定义;
  • 输出层:把隐藏层的特征转化为最终结果,神经元数量和任务目标匹配,如识别 0-9 数字就设 10 个神经元,输出结果贴合任务,其中分类输出概率,回归输出连续值。
2. 激活函数

        给隐藏层、输出层加的 “转换规则”,让网络能识别非线性规律,比如区分圆形和方形。不同场景选不同类型:隐藏层常用 ReLU(简单高效),二分类输出层用 Sigmoid(输出 0-1 概率),多分类用 Softmax(输出各类概率总和为 1)。

3.反向传播 + 梯度下降

这是网络 “自我修正” 的核心,目的是减少预测误差:

  • 损失函数:先判断预测结果和真实结果的差距,误差越大,后续调整幅度越大;
  • 反向传播:从输出层往回推,找出每个权重、偏置对误差的影响,明确 “该调整哪些参数”;
  • 梯度下降:沿着 “减少误差” 的方向,逐步微调所有权重和偏置,让下次预测更准确。优化器是梯度下降的升级工具,能让调整更高效、更稳定。

二、数学原理

1. 计算前提

神经元

        作用:神经网络的基本单元,接收输入→加权求和→激活函数→输出。

权重 (W)

        作用:神经元之间连接的强度,模型学习的核心参数。

偏置 (b)

        作用:调整神经元输出的基准值,解决加权求和后结果可能偏离有效范围的问题。

激活函数

        作用:引入非线性,让网络能拟合复杂的非线性关系(如 ReLU、Sigmoid、Tanh)。

损失函数

        作用:衡量预测值与真实值的差距(如 MSE、交叉熵)。

反向传播

        作用:沿着损失函数下降的方向,反向更新权重和偏置,核心是链式求导。

梯度下降

        作用:优化算法,通过迭代更新参数,最小化损失函数。

2、计算公式

1. 单个神经元

        设神经元接收输入x_{1},x_{2}...x_{n}​,对应的权重为 w_{1},w_{2}...w_{n}w1​,w2​,...,wn​,偏置为 b,激活函数为 f(⋅)。

则神经元输出:

z=\sum_{i=1}^{n}w_{i}x_{i}+b=W^{T}X+b

a=f\left ( z \right )

2. 单层神经网络——感知机

        输入层有 m 个神经元,隐藏层有 k 个神经元,则:

  • 输入矩阵 X\epsilon R^{N\times M},其中N 为样本数;
  • 权重矩阵 W\epsilon R^{M\times K};
  • 偏置向量 b\epsilon R^{1\times K};
  • 隐藏层输出:A=f\left ( XW+b \right )。
3. 反向传播核心

        设输出层输出为 \hat{y}​,真实值为 y,损失函数 L=\frac{1}{2N}\sum_{i=1}^{N}\left( \hat{y}_{i} -y_{i}\right )^{2}。

        反向传播的本质是用链式法则求损失对每个参数的偏导数。

4. 梯度下降更新规则

参数更新公式(η 为学习率):

W=W-\eta \cdot \frac{\partial L}{\partial w}

b=b-\eta \cdot \frac{\partial L}{\partial b}

三、代码解释

        用 Python 实现一个两层神经网络,输入层 + 1 个隐藏层 + 输出层。

模块一:数据库导入

# 导入numpy库,用于数值计算(矩阵运算、随机数生成等),是神经网络的核心计算库
import numpy as np
# 从tensorflow.keras.datasets导入mnist数据集(手写数字识别经典数据集)
from tensorflow.keras.datasets import mnist
# 从tensorflow.keras.utils导入to_categorical,用于标签的独热编码
from tensorflow.keras.utils import to_categorical

模块二:数据预处理

  • reshape(-1, 784):将 28×28 的二维图片展平为 784 维的一维向量,适配神经网络输入层;
  • 归一化:将像素值除以 255 缩放到 [0,1],避免大数值导致权重更新不稳定;
  • 独热编码:将标签转为 10 维向量,适配输出层的 10 个神经元(每个神经元对应一个数字的概率)。
# 加载MNIST数据集
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# 1.1 归一化:将像素值从[0,255]缩放到[0,1],加速训练
x_train = x_train.reshape(-1, 784) / 255.0  # 28*28=784,展平为一维向量
x_test = x_test.reshape(-1, 784) / 255.0

# 1.2 标签独热编码:将数字标签(0-9)转为10维独热向量(如3→[0,0,0,1,0,0,0,0,0,0])
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)

模块三:神经网络核心类

  • 初始化(init):
    • 权重初始化采用 Xavier 初始化,除以输入维度的平方根)】,解决 ReLU 激活函数下的梯度消失问题;
    • 偏置初始化为 0,学习率默认设为 0.1,可根据训练效果调整。
  • 激活函数:
    • ReLU:max(0,1),计算简单、缓解梯度消失,是隐藏层最常用的激活函数;
    • Softmax:将输出转为概率分布,满足多分类任务 “概率和为 1” 的要求。
  • 前向传播(forward):严格遵循 “加权求和(z)→激活函数(a)” 的流程,依次计算隐藏层和输出层的输出,同时保存中间结果(z1、a1、z2、a2)供反向传播使用。
  • 反向传播(backward):核心是链式求导,步骤为:
    1. 计算输出层梯度(dz2、dW2、db2);
    2. 链式推导隐藏层梯度(dz1、dW1、db1);
    3. 用梯度下降更新所有权重和偏置。
  • 损失函数(compute_loss):交叉熵损失是多分类任务的标准损失函数,加1e-8防止log(0)导致数值错误。
class SimpleNN:
    def __init__(self, input_size, hidden_size, output_size, learning_rate=0.1):
        """
        初始化神经网络参数
        :param input_size: 输入层神经元数(MNIST为784)
        :param hidden_size: 隐藏层神经元数
        :param output_size: 输出层神经元数(MNIST为10)
        :param learning_rate: 学习率
        """
        # 初始化权重(使用Xavier初始化,避免梯度消失/爆炸)
        self.W1 = np.random.randn(input_size, hidden_size) / np.sqrt(input_size/2)
        self.b1 = np.zeros((1, hidden_size))  # 隐藏层偏置
        self.W2 = np.random.randn(hidden_size, output_size) / np.sqrt(hidden_size/2)
        self.b2 = np.zeros((1, output_size))  # 输出层偏置
        self.lr = learning_rate  # 学习率

    def relu(self, x):
        """ReLU激活函数:f(x)=max(0,x),引入非线性"""
        return np.maximum(0, x)

    def relu_derivative(self, x):
        """ReLU导数:x>0时为1,x≤0时为0"""
        return np.where(x > 0, 1, 0)

    def softmax(self, x):
        """Softmax激活函数:将输出转为概率分布(所有值和为1),用于多分类"""
        # 防止数值溢出:减去每行最大值
        exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
        return exp_x / np.sum(exp_x, axis=1, keepdims=True)

    def forward(self, x):
        """前向传播:从输入到输出的计算过程"""
        # 隐藏层计算
        self.z1 = np.dot(x, self.W1) + self.b1  # 加权求和
        self.a1 = self.relu(self.z1)            # 激活函数输出
        
        # 输出层计算
        self.z2 = np.dot(self.a1, self.W2) + self.b2  # 加权求和
        self.a2 = self.softmax(self.z2)               # 激活函数输出(概率)
        return self.a2

    def backward(self, x, y):
        """反向传播:计算参数梯度并更新"""
        batch_size = x.shape[0]
        
        # 输出层梯度
        dz2 = self.a2 - y  # 损失对z2的偏导(交叉熵+softmax的简化结果)
        dW2 = np.dot(self.a1.T, dz2) / batch_size  # 损失对W2的偏导
        db2 = np.sum(dz2, axis=0, keepdims=True) / batch_size  # 损失对b2的偏导
        
        # 隐藏层梯度(链式法则)
        dz1 = np.dot(dz2, self.W2.T) * self.relu_derivative(self.z1)  # 损失对z1的偏导
        dW1 = np.dot(x.T, dz1) / batch_size  # 损失对W1的偏导
        db1 = np.sum(dz1, axis=0, keepdims=True) / batch_size  # 损失对b1的偏导
        
        # 梯度下降更新参数
        self.W1 -= self.lr * dW1
        self.b1 -= self.lr * db1
        self.W2 -= self.lr * dW2
        self.b2 -= self.lr * db2

    def compute_loss(self, y_pred, y_true):
        """计算交叉熵损失(多分类任务常用)"""
        # 防止log(0):加极小值epsilon
        epsilon = 1e-8
        return -np.sum(y_true * np.log(y_pred + epsilon)) / y_pred.shape[0]

模块四:训练与测试

# 初始化网络
nn = SimpleNN(input_size=784, hidden_size=128, output_size=10, learning_rate=0.1)

# 超参数设置
epochs = 20  # 训练轮数
batch_size = 64  # 批次大小
n_batches = x_train.shape[0] // batch_size  # 每轮训练的批次数

# 训练过程
for epoch in range(epochs):
    # 打乱训练数据(防止过拟合)
    indices = np.random.permutation(x_train.shape[0])
    x_train_shuffled = x_train[indices]
    y_train_shuffled = y_train[indices]
    
    total_loss = 0
    # 按批次训练
    for i in range(n_batches):
        # 取当前批次数据
        start = i * batch_size
        end = start + batch_size
        x_batch = x_train_shuffled[start:end]
        y_batch = y_train_shuffled[start:end]
        
        # 前向传播计算预测值
        y_pred = nn.forward(x_batch)
        # 计算损失
        loss = nn.compute_loss(y_pred, y_batch)
        total_loss += loss
        # 反向传播更新参数
        nn.backward(x_batch, y_batch)
    
    # 每轮训练后打印损失
    avg_loss = total_loss / n_batches
    print(f"Epoch {epoch+1}/{epochs}, Average Loss: {avg_loss:.4f}")

# 测试模型
y_test_pred = nn.forward(x_test)
# 预测结果:取概率最大的索引(即预测的数字)
test_pred_labels = np.argmax(y_test_pred, axis=1)
test_true_labels = np.argmax(y_test, axis=1)
# 计算准确率
accuracy = np.mean(test_pred_labels == test_true_labels)
print(f"\nTest Accuracy: {accuracy:.4f}")

运行结果

Epoch 1/20, Average Loss: 0.3662
Epoch 2/20, Average Loss: 0.1968
Epoch 3/20, Average Loss: 0.1496
Epoch 4/20, Average Loss: 0.1212
Epoch 5/20, Average Loss: 0.1026
Epoch 6/20, Average Loss: 0.0890
Epoch 7/20, Average Loss: 0.0778
Epoch 8/20, Average Loss: 0.0696
Epoch 9/20, Average Loss: 0.0626
Epoch 10/20, Average Loss: 0.0567
Epoch 11/20, Average Loss: 0.0514
Epoch 12/20, Average Loss: 0.0472
Epoch 13/20, Average Loss: 0.0431
Epoch 14/20, Average Loss: 0.0396
Epoch 15/20, Average Loss: 0.0363
Epoch 16/20, Average Loss: 0.0340
Epoch 17/20, Average Loss: 0.0314
Epoch 18/20, Average Loss: 0.0290
Epoch 19/20, Average Loss: 0.0267
Epoch 20/20, Average Loss: 0.0250

Test Accuracy: 0.9778

  • 训练损失:持续下降且最终极低(0.025),说明模型在训练集上完全学会了手写数字的规律;
  • 测试准确率:97.78% 的高准确率,说明模型的规律学习不是 “死记硬背”,而是能迁移到新数据上,泛化能力优秀;
  • 模型状态:收敛良好、无过拟合或欠拟合,是一份 “近乎完美” 的新手实战结果。

四、总结

核心要点回顾

  1. 神经网络本质:通过权重和偏置的学习,实现输入到输出的非线性映射,核心是 “前向传播算输出,反向传播更参数”;
  2. 数学核心:前向传播基于线性组合 + 激活函数,反向传播基于链式法则求梯度,参数更新基于梯度下降;
  3. 代码关键:
    • 激活函数引入非线性(ReLU+Softmax);
    • 反向传播需保存前向传播的中间结果;
    • 批次训练和数据打乱是提升模型效果的重要方法。

        感谢大家的观看!有缺点和不足,请大家指正!

更多推荐