文章目录
概述
神经网络其实并不像听起来那么复杂。这份学习笔记面向完全初学者,假设对机器学习没有任何先验知识。我们将通过理解神经网络的工作原理,并用Python从零开始实现一个简单的神经网络。
1. 基础构建块:神经元
神经元的工作原理
神经元是神经网络的基本单元。一个神经元的工作过程包括:
- 接收输入:神经元接收多个输入值
- 加权处理:每个输入乘以对应的权重
- 求和加偏置:将所有加权输入相加,再加上偏置项
- 激活函数:将结果通过激活函数处理得到最终输出
输入神经元结构图
输入层 权重 求和+偏置 激活函数 输出
x₁ ────────→ w₁ ──┐
├──→ Σ + b ────→ f() ────→ y
x₂ ────────→ w₂ ──┘
该结构包含核心要素:
- 两个输入 x 1 x_1 x1 和 x 2 x_2 x2
- 对应的权重 w 1 w_1 w1 和 w 2 w_2 w2
- 求和节点将加权输入相加并加上偏置 b b b
- 激活函数 f ( ) f() f() 处理结果产生输出 y y y
数学表示
对于一个2输入神经元:
- 输入: x 1 , x 2 x_1, x_2 x1,x2
- 权重: w 1 , w 2 w_1, w_2 w1,w2
- 偏置: b b b
- 输出: f ( ( x 1 × w 1 ) + ( x 2 × w 2 ) + b ) f((x_1 × w_1) + (x_2 × w_2) + b) f((x1×w1)+(x2×w2)+b)
其中 f f f是激活函数,常用的是sigmoid函数:
s i g m o i d ( x ) = 1 / ( 1 + e − x ) sigmoid(x) = 1 / (1 + e^{-x}) sigmoid(x)=1/(1+e−x)
Sigmoid激活函数图像
1.0 ┤ ╭─────
│ ╭─╯
0.8 ┤ ╭─╯
│ ╭─╯
0.6 ┤ ╭─╯
│ ╭─╯
0.4 ┤ ╭─╯
│ ╭─╯
0.2 ┤ ╭─╯
│╭╯
0.0 ┼─────────────────────
-6 -4 -2 0 2 4 6
Sigmoid函数将任何实数输入压缩到 ( 0 , 1 ) (0,1) (0,1)区间内,形成S型曲线。
Python实现示例
python
import numpy as np
def sigmoid(x):
"""Sigmoid激活函数"""
return 1 / (1 + np.exp(-x))
class Neuron:
def __init__(self, weights, bias):
self.weights = weights
self.bias = bias
def feedforward(self, inputs):
"""前向传播计算"""
total = np.dot(self.weights, inputs) + self.bias
return sigmoid(total)
# 示例使用
weights = np.array([0, 1])
bias = 4
neuron = Neuron(weights, bias)
x = np.array([2, 3])
output = neuron.feedforward(x)
print(f"神经元输出: {output}")
2. 构建神经网络
网络结构
一个简单的神经网络包含:
- 输入层:接收原始数据
- 隐藏层:进行特征处理(可以有多层)
- 输出层:产生最终结果
简单神经网络结构图
输入层 隐藏层 输出层
x₁ ──┐ ┌─ h₁ ──┐
├───┤ ├─── o₁
x₂ ──┘ └─ h₂ ──┘
详细连接图
输入层 隐藏层 输出层
x₁ ────w₁───→ h₁ ────w₅───┐
│ w₃───→ h₂ ────w₆───┼──→ o₁
x₂ ────w₂─────┘ │
└────w₄─────────────────┘
该网络的参数构成:
- 2个输入节点 ( x 1 , x 2 x_1, x_2 x1,x2)
- 2个隐藏层节点 ( h 1 , h 2 h_1, h_2 h1,h2)
- 1个输出节点 ( o 1 o_1 o1)
- 6个权重参数 ( w 1 w_1 w1- w 6 w_6 w6)
- 3个偏置参数 (每层一个)
前向传播过程
数据从输入层开始,逐层向前传递:
- 输入层接收数据
- 隐藏层处理特征
- 输出层产生预测结果
前向传播数据流图
步骤1: 输入数据x₁ = 2, x₂ = 3
步骤2: 隐藏层计算
h₁ = sigmoid(x₁×w₁ + x₂×w₃ + b₁)
h₂ = sigmoid(x₁×w₂ + x₂×w₄ + b₂)
步骤3: 输出层计算
o₁ = sigmoid(h₁×w₅ + h₂×w₆ + b₃)
数据流向:输入 → 加权求和 → 激活函数 → 下一层
网络实现示例
python
class SimpleNeuralNetwork:
def __init__(self):
# 随机初始化权重和偏置
self.w1 = np.random.normal()
self.w2 = np.random.normal()
self.w3 = np.random.normal()
self.w4 = np.random.normal()
self.w5 = np.random.normal()
self.w6 = np.random.normal()
self.b1 = np.random.normal()
self.b2 = np.random.normal()
self.b3 = np.random.normal()
def feedforward(self, x):
# 隐藏层计算
h1 = sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)
h2 = sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)
# 输出层计算
output = sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)
return output
3. 训练神经网络
损失函数
训练的目标是最小化预测误差。常用**均方误差(MSE)**作为损失函数:
M S E = ( 1 / n ) × Σ ( y t r u e − y p r e d ) 2 MSE = (1/n) × \Sigma(y_{true} - y_{pred})^2 MSE=(1/n)×Σ(ytrue−ypred)2
其中:
- n n n:样本数量
- y t r u e y_{true} ytrue:真实值
- y p r e d y_{pred} ypred:预测值
损失函数实现
python
def mse_loss(y_true, y_pred):
"""计算均方误差"""
return ((y_true - y_pred) ** 2).mean()
# 示例
y_true = np.array([1, 0, 0, 1])
y_pred = np.array([0.8, 0.2, 0.1, 0.9])
loss = mse_loss(y_true, y_pred)
print(f"损失值: {loss}")
训练数据示例
假设我们要根据身高体重预测性别,标准化后的数据集如下:
| 姓名 | 体重(标准化) | 身高(标准化) | 性别 |
|---|---|---|---|
| Alice | -2 | -1 | 1 (女) |
| Bob | 25 | 6 | 0 (男) |
| Charlie | 17 | 4 | 0 (男) |
| Diana | -15 | -6 | 1 (女) |
性别预测网络结构
体重(标准化) 隐藏层 性别预测
weight ──┐ ┌─ h₁ ──┐
├───┤ ├─── gender
height ──┘ └─ h₂ ──┘
身高(标准化) (0=男, 1=女)
网络学习从身高体重特征到性别标签的映射关系。
4. 反向传播算法
基本思想
反向传播用于计算损失函数相对于网络参数的梯度,从而指导参数更新的方向。
梯度计算
使用链式法则 计算偏导数:
∂ L / ∂ w = ∂ L / ∂ y p r e d × ∂ y p r e d / ∂ w ∂L/∂w = ∂L/∂y_{pred} × ∂y_{pred}/∂w ∂L/∂w=∂L/∂ypred×∂ypred/∂w
反向传播流程图
前向传播:输入 → 隐藏层 → 输出 → 损失
x h y L
反向传播:∂L/∂x ← ∂L/∂h ← ∂L/∂y ← ∂L/∂L
梯度 梯度 梯度 损失
梯度计算顺序:
1. 计算输出层梯度: ∂L/∂w₅, ∂L/∂w₆, ∂L/∂b₃
2. 计算隐藏层梯度: ∂L/∂w₁, ∂L/∂w₂, ∂L/∂w₃, ∂L/∂w₄, ∂L/∂b₁, ∂L/∂b₂
3. 更新所有参数
参数更新
使用梯度下降 更新参数:
w n e w = w o l d − l e a r n i n g _ r a t e × ∂ L / ∂ w w_{new} = w_{old} - learning\_rate × ∂L/∂w wnew=wold−learning_rate×∂L/∂w
带权重标注的网络图
输入层 隐藏层 输出层
x₁ ──w₁──→ h₁ ──w₅──┐
│ w₃──→ h₂ ──w₆──┼──→ o₁
x₂ ──w₂────┘ │
└──w₄──────────────┘
偏置: b₁(h₁), b₂(h₂), b₃(o₁)
该图明确了所有需要训练的参数:
- 权重: w 1 , w 2 , w 3 , w 4 , w 5 , w 6 w_1, w_2, w_3, w_4, w_5, w_6 w1,w2,w3,w4,w5,w6
- 偏置: b 1 , b 2 , b 3 b_1, b_2, b_3 b1,b2,b3
5. 完整训练示例
python
class TrainableNetwork:
def __init__(self):
# 初始化参数
self.w1 = np.random.normal()
self.w2 = np.random.normal()
# ... 其他权重和偏置
def train(self, data, all_y_trues):
"""训练网络"""
learn_rate = 0.1
epochs = 1000
for epoch in range(epochs):
for x, y_true in zip(data, all_y_trues):
# 前向传播
y_pred = self.feedforward(x)
# 计算损失
loss = mse_loss(y_true, y_pred)
# 反向传播(计算梯度)
# ... 梯度计算代码
# 更新参数
# ... 参数更新代码
# 每100轮输出一次损失
if epoch % 100 == 0:
print(f"Epoch {epoch}, Loss: {loss}")
6. 关键概念总结
重要术语
- 前向传播:数据从输入到输出的计算过程
- 反向传播:计算梯度并更新参数的过程
- 激活函数:引入非线性的函数
- 损失函数:衡量预测误差的函数
- 梯度下降:优化算法,用于最小化损失
训练流程
- 初始化网络参数
- 前向传播计算预测
- 计算损失
- 反向传播计算梯度
- 更新参数
- 重复步骤2-5直到收敛
7. 实际应用建议
使用现有框架
在实际项目中,建议使用成熟的深度学习框架,提升开发效率:
- TensorFlow:Google开发的开源框架
- PyTorch:Facebook开发的动态图框架
- Keras:高级API,易于使用
进一步学习
- 了解不同类型的神经网络(CNN、RNN等)
- 学习正则化技术防止过拟合
- 掌握数据预处理和特征工程
- 实践更复杂的实际项目
注:本文档基于学习目的整理,实际应用中请使用专业的深度学习框架。
8. 梯度下降详解
偏导数的意义
当我们计算 ∂ L / ∂ w 1 ∂L/∂w_1 ∂L/∂w1 时,核心是探究:如果改变 w 1 w_1 w1,损失 L L L 会如何变化?
- 如果 ∂ L / ∂ w 1 ∂L/∂w_1 ∂L/∂w1 是正数,减小 w 1 w_1 w1 会使 L L L 减小
- 如果 ∂ L / ∂ w 1 ∂L/∂w_1 ∂L/∂w1 是负数,增大 w 1 w_1 w1 会使 L L L 减小
随机梯度下降(SGD)训练流程
- 选择样本:从数据集中选择一个样本(这就是"随机"的含义)
- 计算梯度:计算损失相对于所有权重和偏置的偏导数
- 更新参数:使用更新方程调整每个权重和偏置
- 重复:返回步骤1继续训练
参数更新公式
w n e w = w o l d − l e a r n i n g _ r a t e × ∂ L / ∂ w w_{new} = w_{old} - learning\_rate × ∂L/∂w wnew=wold−learning_rate×∂L/∂w
其中 l e a r n i n g _ r a t e learning\_rate learning_rate(学习率)控制每次更新的步长,是训练的关键超参数。
9. 完整神经网络实现
完整训练代码
python
import numpy as np
def sigmoid(x):
"""Sigmoid激活函数"""
return 1 / (1 + np.exp(-x))
def deriv_sigmoid(x):
"""Sigmoid导数"""
fx = sigmoid(x)
return fx * (1 - fx)
def mse_loss(y_true, y_pred):
"""均方误差损失"""
return ((y_true - y_pred) ** 2).mean()
class NeuralNetwork:
def __init__(self):
# 随机初始化权重
self.w1 = np.random.normal()
self.w2 = np.random.normal()
self.w3 = np.random.normal()
self.w4 = np.random.normal()
self.w5 = np.random.normal()
self.w6 = np.random.normal()
# 随机初始化偏置
self.b1 = np.random.normal()
self.b2 = np.random.normal()
self.b3 = np.random.normal()
def feedforward(self, x):
"""前向传播"""
h1 = sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)
h2 = sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)
o1 = sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)
return o1
def train(self, data, all_y_trues):
"""
训练神经网络
- data: (n x 2) numpy数组,n为样本数
- all_y_trues: 包含n个元素的numpy数组
"""
learn_rate = 0.1
epochs = 1000 # 训练轮数
for epoch in range(epochs):
for x, y_true in zip(data, all_y_trues):
# === 前向传播 ===
sum_h1 = self.w1 * x[0] + self.w2 * x[1] + self.b1
h1 = sigmoid(sum_h1)
sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2
h2 = sigmoid(sum_h2)
sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3
o1 = sigmoid(sum_o1)
y_pred = o1
# === 计算偏导数 ===
d_L_d_ypred = -2 * (y_true - y_pred)
# 输出层神经元 o1
d_ypred_d_w5 = h1 * deriv_sigmoid(sum_o1)
d_ypred_d_w6 = h2 * deriv_sigmoid(sum_o1)
d_ypred_d_b3 = deriv_sigmoid(sum_o1)
d_ypred_d_h1 = self.w5 * deriv_sigmoid(sum_o1)
d_ypred_d_h2 = self.w6 * deriv_sigmoid(sum_o1)
# 隐藏层神经元 h1
d_h1_d_w1 = x[0] * deriv_sigmoid(sum_h1)
d_h1_d_w2 = x[1] * deriv_sigmoid(sum_h1)
d_h1_d_b1 = deriv_sigmoid(sum_h1)
# 隐藏层神经元 h2
d_h2_d_w3 = x[0] * deriv_sigmoid(sum_h2)
d_h2_d_w4 = x[1] * deriv_sigmoid(sum_h2)
d_h2_d_b2 = deriv_sigmoid(sum_h2)
# === 更新权重和偏置 ===
# 神经元 h1
self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1
self.w2 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2
self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1
# 神经元 h2
self.w3 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3
self.w4 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4
self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2
# 神经元 o1
self.w5 -= learn_rate * d_L_d_ypred * d_ypred_d_w5
self.w6 -= learn_rate * d_L_d_ypred * d_ypred_d_w6
self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3
# 每10轮输出一次损失
if epoch % 10 == 0:
y_preds = np.apply_along_axis(self.feedforward, 1, data)
loss = mse_loss(all_y_trues, y_preds)
print(f"Epoch {epoch} loss: {loss:.3f}")
# 准备数据集
data = np.array([
[-2, -1], # Alice
[25, 6], # Bob
[17, 4], # Charlie
[-15, -6], # Diana
])
all_y_trues = np.array([
1, # Alice (女)
0, # Bob (男)
0, # Charlie (男)
1, # Diana (女)
])
# 训练网络
network = NeuralNetwork()
network.train(data, all_y_trues)
训练结果
随着训练的进行,损失会稳步下降,网络逐步学习到数据的规律:
Epoch 0 loss: 0.350
Epoch 10 loss: 0.221
Epoch 20 loss: 0.149
...
Epoch 990 loss: 0.003
训练损失下降曲线
损失值
0.35 ┤●
│ ●
0.30 ┤ ●
│ ●
0.25 ┤ ●
│ ●●
0.20 ┤ ●●
│ ●●
0.15 ┤ ●●●
│ ●●●
0.10 ┤ ●●●●
│ ●●●●●
0.05 ┤ ●●●●●●●●●
│ ●●●●●●●●●●●
0.00 ┼─────────────────────────────────────────────────→
0 100 200 300 400 500 600 700 800 900 1000
训练轮数 (Epochs)
图中显示损失函数随训练轮数逐渐减小,表明网络正在有效学习特征映射关系。
使用训练好的网络进行预测
python
# 进行预测
emily = np.array([-7, -3]) # 128磅,63英寸
frank = np.array([20, 2]) # 155磅,68英寸
print(f"Emily: {network.feedforward(emily):.3f}") # 0.951 - 女性
print(f"Frank: {network.feedforward(frank):.3f}") # 0.039 - 男性
10. 总结与展望
我们学到了什么
通过本教程,完成了神经网络从基础到实现的核心学习:
- 神经元基础:理解了神经网络的基本构建块
- 激活函数:学习了sigmoid激活函数的作用
- 网络结构:了解了如何将神经元连接成网络
- 数据准备:创建了包含特征和标签的数据集
- 损失函数:学习了均方误差(MSE)损失
- 训练目标:理解了训练就是最小化损失
- 反向传播:使用反向传播计算偏导数
- 优化算法:使用随机梯度下降(SGD)训练网络
完整的神经网络学习流程图
数据准备 → 网络初始化 → 前向传播 → 损失计算 → 反向传播 → 参数更新
↑ ↓
└──────────────────── 重复训练过程 ←─────────────────────────┘
详细训练流程
- 准备训练数据
- 随机初始化权重和偏置
- 前向传播计算预测值
- 计算损失函数
- 反向传播计算梯度
- 使用梯度下降更新参数
- 重复步骤3-6直到收敛
下一步学习方向
深入学习
- 探索更多激活函数(ReLU、Tanh、Softmax等)
- 学习不同的优化器(Adam、RMSprop等)
- 了解正则化技术(Dropout、L2正则化)
- 研究批量归一化(Batch Normalization)
实践项目
- 使用Keras构建第一个神经网络
- 尝试TensorFlow或PyTorch框架
- 在TensorFlow Playground中实验
- 处理真实世界的数据集
高级主题
- 卷积神经网络(CNN):用于图像处理和计算机视觉
- 循环神经网络(RNN):用于序列数据和自然语言处理
- 生成对抗网络(GAN):用于生成新数据
- 迁移学习:利用预训练模型
实用建议
- 从简单开始:先掌握基础概念,再处理复杂问题
- 动手实践:理论结合实践,多写代码
- 使用框架:实际项目中使用成熟的深度学习框架
- 持续学习:机器学习领域发展迅速,保持学习热情
- 参与社区:加入开源项目,与他人交流学习
常见问题解答
Q: 为什么要使用激活函数?
A: 激活函数引入非线性,使神经网络能够学习复杂的模式。没有激活函数,多层网络等价于单层线性模型。
Q: 如何选择学习率?
A: 学习率太大可能导致训练不稳定,太小则训练缓慢。通常从0.001或0.01开始尝试,根据训练效果调整。
Q: 需要多少训练数据?
A: 这取决于问题的复杂度。一般来说,数据越多越好,但也要注意数据质量和多样性。
Q: 如何避免过拟合?
A: 使用正则化技术、增加训练数据、使用Dropout、早停法等方法可以帮助避免过拟合。
Q: 神经网络适合所有问题吗?
A: 不是。对于简单问题,传统机器学习方法可能更合适。神经网络在处理大规模、高维度、非线性问题时表现出色。