正向传播
线性组合
z = wᵀx + b(线性组合),这是神经元的线性计算部分:
diff
- w:权重向量,包含了神经元对每个输入特征的重要性权重
- x:输入向量,包含了所有输入特征
- wᵀ:权重向量的转置
- b:偏置项,用于调整神经元的激活阈值
- wᵀx:表示权重向量与输入向量的点积运算
// 对于有3个输入特征的情况:
z = w₁x₁ + w₂x₂ + w₃x₃ + b
z = wᵀx + b的作用
- 在预处理阶段添加这些特征
- 通过深层网络自动学习这些非线性关系
- 使用特殊的网络结构(如多项式网络)
z = wᵀx + b的原因
- 普适性和灵活性:
- wᵀx + b 形式可以为每个输入特征分配不同的权重
- 通过调整权重 w,可以反映不同特征的重要程度
- 偏置项 b 提供了额外的自由度,使模型更灵活
- 可解释性:
- 线性组合形式直观且容易理解
- 每个权重 w 直接表示对应特征的贡献度
- 便于分析和解释模型的决策过程
- 计算效率:
- 线性运算计算简单,效率高
- 求导简单,有利于反向传播
- 易于并行化处理
- 非线性能力:虽然单个神经元是线性的,但通过激活函数引入非线性,多层网络叠加可以逼近任意复杂函数,不需要在输入端就引入复杂的非线性变换
- 梯度传播:
- 线性组合的导数形式简单
- 有利于梯度的稳定传播
- 减少梯度消失/爆炸的风险
非线性激活:将线性组合的结果通过激活函数进行非线性变换
a = σ(z)(激活函数)
这是神经元的非线性变换部分:
erlang
- z:上一步计算得到的线性组合结果
- σ:激活函数,常见的有:
- Sigmoid: σ(z) = 1/(1+e⁻ᶻ)
- ReLU: σ(z) = max(0,z)
- tanh: σ(z) = (eᶻ-e⁻ᶻ)/(eᶻ+e⁻ᶻ)
- a:神经元的最终输出
激活函数的作用:
diff
- 引入非线性:使网络能够学习复杂的非线性关系
- 标准化输出:某些激活函数(如sigmoid)可以将输出压缩到特定区间
- 解决梯度消失/爆炸:某些激活函数(如ReLU)可以缓解这些问题
激活函数的原因
- 引入非线性
- 使网络能够学习和拟合复杂的非线性函数
- 增强模型的表达能力
- 能够解决更复杂的现实问题
- 特征转换
- 将输入映射到不同的空间
- 提取更有意义的特征表示
- 增加模型的泛化能力
- 梯度信息
- 提供有意义的梯度信息
- 使得反向传播成为可能
- 指导网络权重的更新
- 防止退化
- 避免网络退化为简单的线性模型
- 保持深层网络的学习能力
- 维持网络的深度优势
损失函数
损失函数用于衡量模型预测值与真实值之间的差距。常见的损失函数包括:
- 均方误差(MSE):适用于回归问题
scss
// y是真实值,ŷ是预测值
// n是样本数量
MSE = (1/n)∑(y - ŷ)²
- 交叉熵损失(Cross Entropy):适用于分类问题
arduino
// y是真实标签(one-hot编码)
// ŷ是预测概率
CE = -∑(y * log(ŷ))
反向传播
反向传播是计算神经网络中各参数梯度的算法:
- 基本步骤
- 前向传播计算预测值
- 计算损失函数值
- 计算损失函数对各层参数的梯度
- 更新参数
- 链式法则应用
less
// L是损失函数
// a是激活函数输出
// z是加权和
// w是权重
∂L/∂w = ∂L/∂a * ∂a/∂z * ∂z/∂w
- 梯度计算流程
- 从输出层开始
- 逐层向后计算梯度
- 利用链式法则传递误差
最终结果
- 前向传播公式
scss
// 隐藏层
z₁ = w₁x + b₁
a₁ = σ(z₁) = 1/(1 + e^(-z₁))
// 输出层
z₂ = w₂a₁ + b₂
ŷ = σ(z₂) = 1/(1 + e^(-z₂))
//损失函数(均方误差)
L = 1/2(y - ŷ)²
- 反向传播推导
- 输出层参数梯度
scss
//对w₂的梯度
∂L/∂w₂ = ∂L/∂ŷ * ∂ŷ/∂z₂ * ∂z₂/∂w₂
= -(y - ŷ) * ŷ(1-ŷ) * a₁
//对b₂的梯度
∂L/∂b₂ = ∂L/∂ŷ * ∂ŷ/∂z₂ * ∂z₂/∂b₂
= -(y - ŷ) * ŷ(1-ŷ) * 1
- 隐藏层参数梯度
css
// 对w₁的梯度
∂L/∂w₁ = ∂L/∂ŷ * ∂ŷ/∂z₂ * ∂z₂/∂a₁ * ∂a₁/∂z₁ * ∂z₁/∂w₁
= -(y - ŷ) * ŷ(1-ŷ) * w₂ * a₁(1-a₁) * x
// 对b₁的梯度
∂L/∂b₁ = ∂L/∂ŷ * ∂ŷ/∂z₂ * ∂z₂/∂a₁ * ∂a₁/∂z₁ * ∂z₁/∂b₁
= -(y - ŷ) * ŷ(1-ŷ) * w₂ * a₁(1-a₁) * 1
- 最终更新公式
ini
// 输出层参数更新
w₂_new = w₂_old - α * ∂L/∂w₂
b₂_new = b₂_old - α * ∂L/∂b₂
// 隐藏层参数更新
w₁_new = w₁_old - α * ∂L/∂w₁
b₁_new = b₁_old - α * ∂L/∂b₁
完整代码
python
import numpy as np
class NeuralNetwork:
def __init__(self, layers):
"""
初始化神经网络
layers: 列表,包含每层神经元数量,如[2,3,1]表示2个输入,3个隐藏,1个输出
"""
self.layers = layers
self.weights = []
self.biases = []
# 初始化权重和偏置
for i in range(len(layers)-1):
w = np.random.randn(layers[i], layers[i+1]) * 0.01
b = np.zeros((1, layers[i+1]))
self.weights.append(w)
self.biases.append(b)
def sigmoid(self, x):
"""sigmoid激活函数"""
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(self, x):
"""sigmoid导数"""
return x * (1 - x)
def forward(self, X):
"""前向传播"""
self.activations = [X]
for i in range(len(self.weights)):
net = np.dot(self.activations[-1], self.weights[i]) + self.biases[i]
self.activations.append(self.sigmoid(net))
return self.activations[-1]
def backward(self, X, y, learning_rate):
"""反向传播"""
m = X.shape[0]
delta = self.activations[-1] - y
for i in range(len(self.weights) - 1, -1, -1):
dW = np.dot(self.activations[i].T, delta) / m
db = np.sum(delta, axis=0, keepdims=True) / m
if i > 0:
delta = np.dot(delta, self.weights[i].T) * self.sigmoid_derivative(self.activations[i])
# 更新权重和偏置
self.weights[i] -= learning_rate * dW
self.biases[i] -= learning_rate * db
def train(self, X, y, epochs, learning_rate):
"""训练神经网络"""
for epoch in range(epochs):
# 前向传播
output = self.forward(X)
# 计算损失
loss = np.mean(np.square(output - y))
# 反向传播
self.backward(X, y, learning_rate)
if epoch % 1000 == 0:
print(f"Epoch {epoch}, Loss: {loss}")
def main():
# 创建示例数据:XOR问题
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([[0], [1], [1], [0]])
# 创建神经网络:2个输入,4个隐藏,1个输出
nn = NeuralNetwork([2, 4, 1])
# 训练网络
nn.train(X, y, epochs=10000, learning_rate=0.1)
# 测试网络
print("\n测试结果:")
predictions = nn.forward(X)
for i in range(len(X)):
print(f"输入: {X[i]}, 预测: {predictions[i][0]:.4f}, 实际: {y[i][0]}")
if __name__ == "__main__":
main()
总结
- 神经网络是由一个一个神经元组成,最简单的神经网络:输入层 -> 隐藏层 -> 输出层
- 每一个神经元包含了线性变化z = wᵀx + b和激活函数a = σ(z)来你和输入到输出之间的各种关系
- 通过链式运算来拟合输入到输出的各种关系,通过方向传播和梯度下降算法使用大量的测试集来不断修正w和b从而提升输入到输出的准确性,来完成识图、预测房价等各种各样的功能