深度学习的核心是神经网络,而要真正理解神经网络,就必须掌握其背后的数学原理。本文将从最基础的概念开始,逐步深入讲解神经网络的数学基础,包括线性代数、微积分在神经网络中的应用,以及神经网络的基本结构和工作原理。

从生物神经元到人工神经元

在开始数学推导之前,我们先从直观的角度理解神经网络的起源。

生物神经元的启发

人脑中有大约860亿个神经元,它们通过突触相互连接,形成了一个复杂的网络。每个神经元接收来自其他神经元的信号,经过处理后,决定是否向下一个神经元传递信号。

树突
接收信号

细胞体
处理信号

轴突
传递信号

突触
连接下一个神经元

这个过程给了我们启发:能否用数学模型来模拟这个过程?

人工神经元模型

1943年,McCulloch和Pitts提出了第一个人工神经元模型,也称为感知机。这个模型非常简单:

  1. 接收多个输入信号 x₁, x₂, …, xₙ
  2. 每个输入都有一个权重 w₁, w₂, …, wₙ
  3. 计算加权和:z = w₁x₁ + w₂x₂ + … + wₙxₙ + b
  4. 通过激活函数得到输出:y = f(z)

用数学公式表示就是:

y = f(Σwᵢxᵢ + b) = f(w^T·x + b)

其中,b是偏置项,f是激活函数。

w₁

w₂

w₃

wₙ

x₁

Σ

x₂

x₃

xₙ

b偏置

激活函数f

输出y

下面用代码实现一个简单的神经元:

import numpy as np

class Neuron:
    def __init__(self, n_inputs):
        # 随机初始化权重和偏置
        self.weights = np.random.randn(n_inputs)
        self.bias = np.random.randn()
    
    def forward(self, inputs):
        """前向传播"""
        # 计算加权和
        z = np.dot(self.weights, inputs) + self.bias
        # 应用激活函数(这里使用sigmoid)
        output = self.sigmoid(z)
        return output
    
    def sigmoid(self, z):
        """Sigmoid激活函数"""
        return 1 / (1 + np.exp(-z))

# 创建一个有3个输入的神经元
neuron = Neuron(n_inputs=3)
inputs = np.array([1.0, 2.0, 3.0])
output = neuron.forward(inputs)
print(f"输入: {inputs}")
print(f"权重: {neuron.weights}")
print(f"偏置: {neuron.bias}")
print(f"输出: {output:.4f}")

神经网络的数学基础

要深入理解神经网络,需要掌握一些数学知识,主要包括线性代数和微积分。

线性代数基础

神经网络的计算本质上是大量的矩阵运算,因此线性代数是理解神经网络的关键。

向量和矩阵

在神经网络中:

  • 输入数据通常表示为向量或矩阵
  • 权重表示为矩阵
  • 偏置表示为向量

例如,对于一个有3个输入、2个输出的层:

输入向量:x = [x₁, x₂, x₃]^T (3×1)
权重矩阵:W = [[w₁₁, w₁₂, w₁₃], [w₂₁, w₂₂, w₂₃]] (2×3)
偏置向量:b = [b₁, b₂]^T (2×1)

输出计算:z = W·x + b

import numpy as np

# 定义输入、权重和偏置
x = np.array([[1.0], [2.0], [3.0]])  # 3×1
W = np.array([[0.1, 0.2, 0.3],       # 2×3
              [0.4, 0.5, 0.6]])
b = np.array([[0.1], [0.2]])          # 2×1

# 计算输出
z = np.dot(W, x) + b
print("输入形状:", x.shape)
print("权重形状:", W.shape)
print("偏置形状:", b.shape)
print("输出形状:", z.shape)
print("输出值:\n", z)

批量处理

在实际应用中,我们通常一次处理多个样本(一个批次),这时输入变成了矩阵:

输入矩阵:X = [[x₁₁, x₁₂, x₁₃], [x₂₁, x₂₂, x₂₃], …] (batch_size × n_features)

输出计算:Z = X·W^T + b

注意这里权重矩阵需要转置。

# 批量处理示例
batch_size = 4
n_features = 3
n_outputs = 2

# 输入:4个样本,每个3个特征
X = np.random.randn(batch_size, n_features)  # 4×3
W = np.random.randn(n_outputs, n_features)   # 2×3
b = np.random.randn(n_outputs, 1)            # 2×1

# 计算输出
Z = np.dot(X, W.T) + b.T  # 4×2

print(f"批量输入形状: {X.shape}")
print(f"批量输出形状: {Z.shape}")
print(f"输出:\n{Z}")

微积分基础

神经网络的训练依赖于梯度下降算法,而梯度的计算需要用到微积分中的导数和链式法则。

导数的直观理解

导数表示函数在某一点的变化率。对于函数 y = f(x),导数 dy/dx 表示当 x 变化一个很小的量时,y 变化的速度。

在神经网络中,我们需要计算损失函数对每个参数的导数,这告诉我们如何调整参数才能减小损失。

import matplotlib.pyplot as plt

def f(x):
    """示例函数 f(x) = x²"""
    return x ** 2

def df(x):
    """f(x)的导数 f'(x) = 2x"""
    return 2 * x

# 绘制函数和切线
x = np.linspace(-3, 3, 100)
y = f(x)

x0 = 1.5
y0 = f(x0)
slope = df(x0)

# 切线方程:y - y0 = slope * (x - x0)
tangent_y = y0 + slope * (x - x0)

plt.figure(figsize=(10, 6))
plt.plot(x, y, label='f(x) = x²', linewidth=2)
plt.plot(x, tangent_y, 'r--', label=f'切线 (斜率={slope})', linewidth=2)
plt.scatter([x0], [y0], color='red', s=100, zorder=5)
plt.xlabel('x')
plt.ylabel('y')
plt.title('函数及其在x=1.5处的切线')
plt.legend()
plt.grid(True)
plt.show()

链式法则

链式法则是反向传播算法的核心。对于复合函数 y = f(g(x)),链式法则告诉我们:

dy/dx = (dy/dg) × (dg/dx)

在神经网络中,输出是输入经过多层变换得到的,链式法则让我们能够计算损失对每一层参数的梯度。

# 链式法则示例
def g(x):
    """内层函数 g(x) = 2x + 1"""
    return 2 * x + 1

def f(g):
    """外层函数 f(g) = g²"""
    return g ** 2

def dg_dx(x):
    """g对x的导数"""
    return 2

def df_dg(g):
    """f对g的导数"""
    return 2 * g

# 计算复合函数在x=3处的导数
x = 3
g_val = g(x)
f_val = f(g_val)

# 使用链式法则
dy_dx = df_dg(g_val) * dg_dx(x)

print(f"x = {x}")
print(f"g(x) = {g_val}")
print(f"f(g(x)) = {f_val}")
print(f"df/dx = {dy_dx}")

# 验证:直接计算 f(g(x)) = (2x+1)² = 4x² + 4x + 1
# 导数:8x + 4
print(f"直接计算导数: {8*x + 4}")

神经网络的基本结构

理解了单个神经元后,我们来看如何将多个神经元组织成网络。

层的概念

神经网络由多个层组成,每一层包含多个神经元。常见的层类型有:

  1. 输入层:接收原始数据
  2. 隐藏层:进行特征提取和变换
  3. 输出层:产生最终预测结果

输出层

隐藏层2

隐藏层1

输入层

x₁

x₂

x₃

h₁₁

h₁₂

h₁₃

h₁₄

h₂₁

h₂₂

h₂₃

y₁

y₂

全连接层

全连接层(也叫密集层)是最基本的层类型,层中的每个神经元都与上一层的所有神经元相连。

对于一个全连接层:

  • 输入:x ∈ ℝⁿ
  • 权重:W ∈ ℝᵐˣⁿ
  • 偏置:b ∈ ℝᵐ
  • 输出:y = f(Wx + b) ∈ ℝᵐ
class DenseLayer:
    def __init__(self, n_inputs, n_neurons):
        """
        全连接层
        n_inputs: 输入特征数
        n_neurons: 神经元数量
        """
        # He初始化
        self.weights = np.random.randn(n_neurons, n_inputs) * np.sqrt(2.0 / n_inputs)
        self.biases = np.zeros((n_neurons, 1))
    
    def forward(self, inputs):
        """
        前向传播
        inputs: shape (n_inputs, batch_size)
        """
        self.inputs = inputs
        self.z = np.dot(self.weights, inputs) + self.biases
        return self.z
    
    def __repr__(self):
        return f"DenseLayer(输入={self.weights.shape[1]}, 输出={self.weights.shape[0]})"

# 创建一个简单的网络
layer1 = DenseLayer(n_inputs=3, n_neurons=4)
layer2 = DenseLayer(n_inputs=4, n_neurons=2)

print("网络结构:")
print(f"  {layer1}")
print(f"  {layer2}")

# 前向传播
x = np.random.randn(3, 1)  # 单个样本
h1 = layer1.forward(x)
h2 = layer2.forward(h1)

print(f"\n输入形状: {x.shape}")
print(f"隐藏层输出形状: {h1.shape}")
print(f"最终输出形状: {h2.shape}")

激活函数的作用

如果神经网络只有线性变换,那么无论有多少层,整个网络都等价于一个线性变换。激活函数引入了非线性,使得神经网络能够学习复杂的非线性关系。

常用的激活函数有:

  1. Sigmoid:σ(x) = 1 / (1 + e⁻ˣ)
  2. Tanh:tanh(x) = (eˣ - e⁻ˣ) / (eˣ + e⁻ˣ)
  3. ReLU:ReLU(x) = max(0, x)
  4. Leaky ReLU:LeakyReLU(x) = max(αx, x)
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def tanh(x):
    return np.tanh(x)

def relu(x):
    return np.maximum(0, x)

def leaky_relu(x, alpha=0.01):
    return np.where(x > 0, x, alpha * x)

# 绘制激活函数
x = np.linspace(-5, 5, 100)

plt.figure(figsize=(15, 10))

plt.subplot(2, 2, 1)
plt.plot(x, sigmoid(x), linewidth=2)
plt.title('Sigmoid')
plt.grid(True)
plt.axhline(y=0, color='k', linestyle='--', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='--', alpha=0.3)

plt.subplot(2, 2, 2)
plt.plot(x, tanh(x), linewidth=2)
plt.title('Tanh')
plt.grid(True)
plt.axhline(y=0, color='k', linestyle='--', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='--', alpha=0.3)

plt.subplot(2, 2, 3)
plt.plot(x, relu(x), linewidth=2)
plt.title('ReLU')
plt.grid(True)
plt.axhline(y=0, color='k', linestyle='--', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='--', alpha=0.3)

plt.subplot(2, 2, 4)
plt.plot(x, leaky_relu(x), linewidth=2)
plt.title('Leaky ReLU')
plt.grid(True)
plt.axhline(y=0, color='k', linestyle='--', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='--', alpha=0.3)

plt.tight_layout()
plt.show()

前向传播:从输入到输出

前向传播是神经网络进行预测的过程,数据从输入层流向输出层。

前向传播的数学表示

对于一个L层的神经网络,前向传播的过程可以表示为:

第1层:

  • z⁽¹⁾ = W⁽¹⁾x + b⁽¹⁾
  • a⁽¹⁾ = f⁽¹⁾(z⁽¹⁾)

第2层:

  • z⁽²⁾ = W⁽²⁾a⁽¹⁾ + b⁽²⁾
  • a⁽²⁾ = f⁽²⁾(z⁽²⁾)

第L层:

  • z⁽ᴸ⁾ = W⁽ᴸ⁾a⁽ᴸ⁻¹⁾ + b⁽ᴸ⁾
  • ŷ = a⁽ᴸ⁾ = f⁽ᴸ⁾(z⁽ᴸ⁾)

输入x

线性变换
z¹=W¹x+b¹

激活函数
a¹=f¹z¹

线性变换
z²=W²a¹+b²

激活函数
a²=f²z²

...

线性变换
zᴸ=Wᴸaᴸ⁻¹+bᴸ

激活函数
ŷ=fᴸzᴸ

完整的前向传播实现

class NeuralNetwork:
    def __init__(self, layer_sizes):
        """
        初始化神经网络
        layer_sizes: 列表,每个元素表示该层的神经元数量
        例如 [3, 4, 2] 表示输入3个特征,隐藏层4个神经元,输出2个类别
        """
        self.layer_sizes = layer_sizes
        self.num_layers = len(layer_sizes)
        self.weights = []
        self.biases = []
        
        # 初始化权重和偏置
        for i in range(1, self.num_layers):
            w = np.random.randn(layer_sizes[i], layer_sizes[i-1]) * np.sqrt(2.0 / layer_sizes[i-1])
            b = np.zeros((layer_sizes[i], 1))
            self.weights.append(w)
            self.biases.append(b)
    
    def relu(self, z):
        return np.maximum(0, z)
    
    def softmax(self, z):
        """Softmax激活函数,用于多分类输出层"""
        exp_z = np.exp(z - np.max(z, axis=0, keepdims=True))  # 数值稳定性
        return exp_z / np.sum(exp_z, axis=0, keepdims=True)
    
    def forward(self, x):
        """
        前向传播
        x: 输入,shape (n_features, batch_size)
        返回: 输出,shape (n_outputs, batch_size)
        """
        a = x
        self.activations = [x]  # 保存每层的激活值,用于反向传播
        self.zs = []  # 保存每层的z值
        
        # 遍历每一层
        for i in range(self.num_layers - 1):
            z = np.dot(self.weights[i], a) + self.biases[i]
            self.zs.append(z)
            
            # 最后一层使用softmax,其他层使用ReLU
            if i == self.num_layers - 2:
                a = self.softmax(z)
            else:
                a = self.relu(z)
            
            self.activations.append(a)
        
        return a
    
    def predict(self, x):
        """预测类别"""
        output = self.forward(x)
        return np.argmax(output, axis=0)

# 创建一个简单的网络
nn = NeuralNetwork([3, 5, 4, 2])

print("网络结构:")
for i, (w, b) in enumerate(zip(nn.weights, nn.biases)):
    print(f"  层{i+1}: 权重形状={w.shape}, 偏置形状={b.shape}")

# 前向传播示例
x = np.random.randn(3, 10)  # 10个样本,每个3个特征
output = nn.forward(x)
predictions = nn.predict(x)

print(f"\n输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
print(f"预测类别: {predictions}")
print(f"输出概率(前3个样本):\n{output[:, :3]}")

损失函数:衡量预测的好坏

损失函数用来衡量模型预测值与真实值之间的差距。不同的任务使用不同的损失函数。

回归任务:均方误差

对于回归任务,常用均方误差(MSE):

L = (1/n) Σ(yᵢ - ŷᵢ)²

def mse_loss(y_true, y_pred):
    """均方误差损失"""
    return np.mean((y_true - y_pred) ** 2)

def mse_loss_derivative(y_true, y_pred):
    """MSE损失的导数"""
    return 2 * (y_pred - y_true) / y_true.shape[0]

分类任务:交叉熵损失

对于分类任务,常用交叉熵损失:

二分类:L = -[y·log(ŷ) + (1-y)·log(1-ŷ)]
多分类:L = -Σyᵢ·log(ŷᵢ)

def binary_cross_entropy(y_true, y_pred):
    """二分类交叉熵损失"""
    epsilon = 1e-15  # 避免log(0)
    y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
    return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))

def categorical_cross_entropy(y_true, y_pred):
    """多分类交叉熵损失"""
    epsilon = 1e-15
    y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
    return -np.mean(np.sum(y_true * np.log(y_pred), axis=0))

# 示例
y_true = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]).T  # 3个样本的one-hot编码
y_pred = np.array([[0.7, 0.2, 0.1], [0.1, 0.8, 0.1], [0.2, 0.1, 0.7]]).T

loss = categorical_cross_entropy(y_true, y_pred)
print(f"交叉熵损失: {loss:.4f}")

梯度下降:优化网络参数

有了损失函数,我们需要找到使损失最小的参数。梯度下降是最常用的优化算法。

梯度下降的原理

梯度下降的核心思想是:沿着梯度的反方向更新参数,逐步减小损失。

参数更新规则:

θ = θ - α·∇L(θ)

其中,α是学习率,∇L(θ)是损失函数对参数θ的梯度。

初始化参数

计算损失

计算梯度

更新参数
θ=θ-α∇L

损失是否收敛?

训练完成

批量梯度下降的变体

  1. 批量梯度下降(BGD):使用全部数据计算梯度
  2. 随机梯度下降(SGD):每次使用一个样本计算梯度
  3. 小批量梯度下降(Mini-batch GD):每次使用一小批样本计算梯度
def gradient_descent_demo():
    """梯度下降可视化示例"""
    # 定义一个简单的二次函数
    def f(x):
        return x**2 + 2*x + 1
    
    def df(x):
        return 2*x + 2
    
    # 梯度下降
    x = 5.0  # 初始点
    learning_rate = 0.1
    history = [x]
    
    for i in range(20):
        grad = df(x)
        x = x - learning_rate * grad
        history.append(x)
    
    # 可视化
    x_range = np.linspace(-6, 6, 100)
    y_range = f(x_range)
    
    plt.figure(figsize=(12, 6))
    plt.plot(x_range, y_range, 'b-', linewidth=2, label='f(x) = x² + 2x + 1')
    plt.plot(history, [f(x) for x in history], 'ro-', markersize=8, label='梯度下降路径')
    plt.xlabel('x')
    plt.ylabel('f(x)')
    plt.title('梯度下降优化过程')
    plt.legend()
    plt.grid(True)
    plt.show()
    
    print(f"初始点: x = {history[0]:.4f}, f(x) = {f(history[0]):.4f}")
    print(f"最终点: x = {history[-1]:.4f}, f(x) = {f(history[-1]):.4f}")
    print(f"理论最优点: x = -1, f(x) = 0")

gradient_descent_demo()

神经网络的通用近似定理

神经网络为什么这么强大?通用近似定理给出了理论保证。

定理内容

通用近似定理指出:一个具有单个隐藏层的前馈神经网络,只要隐藏层有足够多的神经元,就可以以任意精度逼近任何连续函数。

这个定理告诉我们:

  1. 神经网络理论上可以学习任何函数
  2. 深度不是必需的,但深度网络通常更高效
  3. 关键是要有足够的神经元和合适的激活函数
# 用神经网络拟合复杂函数
def complex_function(x):
    """一个复杂的函数"""
    return np.sin(x) + 0.5 * np.sin(3*x) + 0.3 * np.cos(5*x)

# 生成训练数据
x_train = np.linspace(-np.pi, np.pi, 100).reshape(-1, 1)
y_train = complex_function(x_train)

# 使用sklearn的MLPRegressor
from sklearn.neural_network import MLPRegressor

# 不同复杂度的网络
models = {
    '小网络 (5个神经元)': MLPRegressor(hidden_layer_sizes=(5,), max_iter=5000, random_state=42),
    '中网络 (20个神经元)': MLPRegressor(hidden_layer_sizes=(20,), max_iter=5000, random_state=42),
    '大网络 (50个神经元)': MLPRegressor(hidden_layer_sizes=(50,), max_iter=5000, random_state=42),
}

plt.figure(figsize=(15, 5))

for idx, (name, model) in enumerate(models.items(), 1):
    model.fit(x_train, y_train.ravel())
    y_pred = model.predict(x_train)
    
    plt.subplot(1, 3, idx)
    plt.plot(x_train, y_train, 'b-', linewidth=2, label='真实函数')
    plt.plot(x_train, y_pred, 'r--', linewidth=2, label='神经网络拟合')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.title(name)
    plt.legend()
    plt.grid(True)

plt.tight_layout()
plt.show()

总结

本文从生物神经元出发,介绍了人工神经元的数学模型,然后深入讲解了神经网络的数学基础,包括线性代数和微积分的应用。

我们学习了:

  1. 人工神经元模型:通过加权和与激活函数模拟生物神经元的行为
  2. 线性代数基础:向量、矩阵运算是神经网络计算的基础
  3. 微积分基础:导数和链式法则是反向传播的数学基础
  4. 神经网络结构:由输入层、隐藏层、输出层组成,通过全连接层连接
  5. 激活函数:引入非线性,使网络能学习复杂函数
  6. 前向传播:数据从输入流向输出的计算过程
  7. 损失函数:衡量预测与真实值的差距
  8. 梯度下降:通过迭代优化找到最优参数
  9. 通用近似定理:理论保证神经网络的强大能力

掌握这些数学基础后,我们就可以深入学习反向传播算法、各种优化技巧,以及更复杂的神经网络架构了。神经网络的本质是一个复杂的数学函数,通过大量的参数和非线性变换,它能够学习数据中的复杂模式,这就是深度学习的魅力所在。

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐