神经网络基础(带数学公式、代码)

本文最后更新于 2025年2月16日 晚上

一,神经网络结构

  • 输入层:最简单的一层,每个结点就是一个变量,结点值就是变量值。
  • 隐藏层:最复杂的一层,可以有多层,根据实际问题复杂情况进行选择。每个结点都为一个神经元。
  • 输出层:如果是分类任务,输出层的结点数即为类别数,每个输出层结点也是一个神经元,但还附带分类器实现值到概率的转换。

二,前向传播(神经元的计算过程)

  • 前一层每个结点都有一个权值,Sum即为前一层所有结点的带权求和,Sgn即为激活函数,其目的是实现非线性组合以及实现最小梯度下降算法满足反向传播条件。

2.1激活函数

2.1.1 饱和神经元激活函数

  • 饱和的含义是任意 x 的输入,其对应的 y 输出的取值范围都位于一个区间内。
  1. Sigmoid函数 $$ f(x)=\frac{1}{1+e^{-x} } $$
    • 函数输出范围是[0,1],已经不太受欢迎了
    • 函数输出不是以 0 为中心的,梯度可能就会向特定方向移动,从而降低权重更新的效率。
    • 函数执行指数运算,计算机运行得较慢,比较消耗计算资源。
  2. Tanh函数 $$ f(x)=\frac{e^{x}-e^{-x} }{e^{x}+e^{-x}} $$
    • 函数输出范围是[-1,1],以 0 为中心,不会向特定方向移动
    • 依然进行的是指数运算
  • 上述两种激活函数在 x 值离中心较远时均会出现梯度消失现象。

2.1.2 非饱和神经元激活函数

  1. Relu函数 $$ f(x)=\max (0,x) $$
    • 输出不是以0为中心的.
    • 当输入为负时,梯度为0。这个神经元及之后的神经元梯度永远为0,不再对任何数据有所响应。因此学习率应当设置得较小。
  2. Leaky Relu函数 $$ f(x)=\max (\alpha x,x) $$
    • 函数中的α,需要通过先验知识人工赋值(一般设为0.01)
    • 有些近似线性,导致在复杂分类中效果不好
    • 尚未完全证明 Leaky ReLU 总是比 ReLU 更好

三,反向传播(链式法则)

  • 损失函数对前一层对应结点的变量求偏导,中间隔着激活函数就用链式法则。
  • 对于设定的步长,偏导函数值的相反数即为权重调整的大小。
  • 用相反数是是为了满足变量的变化与函数变化的关系。

1. 数学背景

在神经网络中,每一层的线性输出 $ Z $ 和激活值 $ A $ 的关系如下:

$$
Z = X \cdot W + b
$$

$$
A = \sigma(Z)
$$

其中:

  • $ X $ 是输入(或前一层的激活值)。
  • $ W $ 是权重矩阵。
  • $ b $ 是偏置向量。
  • $ \sigma $ 是激活函数(如 Sigmoid)。

损失函数 $ L $ 对权重 $ W $ 的偏导数 $ \frac{\partial L}{\partial W} $ 可以通过链式法则计算:

$$
\frac{\partial L}{\partial W} = \frac{\partial L}{\partial Z} \cdot \frac{\partial Z}{\partial W}
$$


2. 链式法则的展开

(1) 计算 $ \frac{\partial L}{\partial Z} $

  • $ \frac{\partial L}{\partial Z} $ 是损失函数对线性输出 $ Z $ 的偏导数,通常记作 $ dZ $。
  • 对于输出层,$ dZ $ 可以直接通过损失函数和激活函数的导数计算得到。
  • 对于隐藏层,$ dZ $ 通过后一层的梯度传播而来。

(2) 计算 $ \frac{\partial Z}{\partial W} $

  • 线性输出 $ Z $ 对权重 $ W $ 的偏导数是输入 $ X $(或前一层的激活值 $ A_{\text{prev}} $)。

  • 这是因为:

    $$
    Z = X \cdot W + b
    $$

    对 $ W $ 求偏导时,$ X $ 是常数,因此:

    $$
    \frac{\partial Z}{\partial W} = X^T
    $$

3. 总结

(1) 前向传播

$$
Z = X \cdot W + b
$$

$$
A = \sigma(Z)
$$

(2) 反向传播

  1. 计算输出层的梯度 $ dZ $:

    $$
    dZ = \frac{\partial L}{\partial Z} = A - Y
    $$

  2. 计算权重梯度 $ dW $:

    $$
    dW = \frac{\partial L}{\partial W} = X^T \cdot dZ
    $$

  3. 更新权重:

    $$
    W = W - \alpha \cdot dW
    $$

四,Softmax分类器

Softmax函数:$$ Softmax(x)=\frac{e^{x_i} }{ {\textstyle \sum_{i}^{}e^{x_i}} } $$

  • Softmax函数一般用于最后一层输出层,实现将最后的计算值转化为不同类别的概率,其中概率最大的就是预测类别。
  • 用指数函数进行累加是为了增大不同值之间的区分度。

五,代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import numpy as np

def sigmoid(x):
return 1 / (1 + np.exp(-x))

def sigmoid_derivative(sigmoid_x):
return sigmoid_x * (1 - sigmoid_x)

# 初始化网络参数
def initialize_parameters(layer_dims):
"""
layer_dims: 列表,表示每一层的神经元数量,例如 [input_size, hidden_size1, hidden_size2, ..., output_size]
return: 参数字典,包含每一层的 W 和 b
"""
np.random.seed(42)
parameters = {}
L = len(layer_dims) # 网络的总层数

for l in range(1, L):
parameters[f'W{l}'] = np.random.randn(layer_dims[l-1], layer_dims[l]) * 0.01
parameters[f'b{l}'] = np.zeros((1, layer_dims[l]))

return parameters

# 前向传播
def forward_propagation(X, parameters):
"""
X: 输入数据
parameters: 参数字典
return: 最后一层的输出 A,以及缓存(包含每一层的 Z 和 A)
"""
caches = []
A = X
L = len(parameters) // 2 # 网络的总层数(不包括输入层)

for l in range(1, L+1):
W = parameters[f'W{l}']
b = parameters[f'b{l}']
Z = np.dot(A, W) + b # 全连接
A = sigmoid(Z) # 激活函数
caches.append((Z, A)) # 缓存每一层的 Z 和 A

return A, caches

# 计算损失
def compute_loss(A2, Y):
"""
A2: 最后一层的输出
Y: 真实标签
"""
m = Y.shape[0]
loss = -np.sum(Y * np.log(A2) + (1 - Y) * np.log(1 - A2)) / m
return loss

# 反向传播
def backward_propagation(X, Y, parameters, caches):
"""
X: 输入数据
Y: 真实标签
parameters: 参数字典
caches: 缓存(包含每一层的 Z 和 A)
return: 梯度字典,包含每一层的 dW 和 db
"""
gradients = {}
L = len(parameters) // 2 # 网络的总层数(不包括输入层)
m = X.shape[0]

# 最后一层的梯度
A = caches[-1][1] # 最后一层的输出
dZ = A - Y
dW = np.dot(caches[-2][1].T, dZ) / m # 倒数第二层的 A 是倒数第一层的输入
db = np.sum(dZ, axis=0, keepdims=True) / m
gradients[f'dW{L}'] = dW
gradients[f'db{L}'] = db

# 从倒数第二层到第一层的梯度
for l in reversed(range(1, L)):
dA_prev = np.dot(dZ, parameters[f'W{l+1}'].T) # 变化量的大小
dZ = dA_prev * sigmoid_derivative(caches[l-1][1]) # 变化量与偏导值的乘积,即反向传播的变化量
dW = np.dot(caches[l-2][1].T, dZ) / m if l > 1 else np.dot(X.T, dZ) / m # 损失函数对权重 W 的偏导数,Z=WX+B
db = np.sum(dZ, axis=0, keepdims=True) / m
gradients[f'dW{l}'] = dW
gradients[f'db{l}'] = db

return gradients

# 更新参数
def update_parameters(parameters, gradients, learning_rate):
"""
parameters: 参数字典
gradients: 梯度字典
learning_rate: 学习率
return: 更新后的参数字典
"""
L = len(parameters) // 2

for l in range(1, L+1):
parameters[f'W{l}'] -= learning_rate * gradients[f'dW{l}'] # 梯度下降
parameters[f'b{l}'] -= learning_rate * gradients[f'db{l}'] # 梯度下降

return parameters

def train(X, Y, layer_dims, learning_rate, epochs):
"""
X: 输入数据
Y: 真实标签
layer_dims: 列表,表示每一层的神经元数量
learning_rate: 学习率
epochs: 训练轮数
return: 训练后的参数字典
"""
parameters = initialize_parameters(layer_dims)

for i in range(epochs):
A, caches = forward_propagation(X, parameters)
loss = compute_loss(A, Y)
gradients = backward_propagation(X, Y, parameters, caches)
parameters = update_parameters(parameters, gradients, learning_rate)
print(gradients)
if i % 1000 == 0:
print(f"Epoch {i}, Loss: {loss}")

return parameters

def predict(X, parameters):
"""
X: 输入数据
parameters: 参数字典
return: 预测结果
"""
A, _ = forward_propagation(X, parameters)
predictions = np.round(A)
return predictions

# 示例数据
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
Y = np.array([[0], [1], [1], [0]])

# 定义网络结构
layer_dims = [2, 4, 4, 1] # 输入层 2 个神经元,两个隐藏层各 4 个神经元,输出层 1 个神经元

# 训练网络
learning_rate = 0.1
epochs = 10000
parameters = train(X, Y, layer_dims, learning_rate, epochs)

# 预测
predictions = predict(X, parameters)
print("Predictions:", predictions)

神经网络基础(带数学公式、代码)
https://jimes.cn/2025/01/19/神经网络基础(带数学公式)/
作者
Jimes
发布于
2025年1月19日
许可协议