从神经元到神经网络:深度学习的本质
这篇文章将带你从零开始理解神经网络。我们会从生物神经元讲起,一步步推导到人工神经网络,理解前向传播、反向传播的原理,最后用PyTorch实现你的第一个神经网络。
关于作者
- 深耕领域:大语言模型开发 / RAG 知识库 / AI Agent 落地 / 模型微调
- 技术栈:Python | RAG (LangChain / Dify + Milvus) | FastAPI + Docker
- 工程能力:专注模型工程化部署、知识库构建与优化,擅长全流程解决方案
「让 AI 交互更智能,让技术落地更高效」
欢迎技术探讨与项目合作,解锁大模型与智能交互的无限可能!
从神经元到神经网络:深度学习的本质
这篇文章将带你从零开始理解神经网络。我们会从生物神经元讲起,一步步推导到人工神经网络,理解前向传播、反向传播的原理,最后用PyTorch实现你的第一个神经网络。
零、前置知识:从生物神经元到人工神经元
在深入神经网络之前,让我们先理解一下"神经元"这个概念从何而来。如果你已经了解生物神经元的基本结构,可以跳过这一节。
0.1 生物神经元是如何工作的?
人脑由约860亿个神经元组成。每个神经元都是一个微小的信息处理单元,它们通过电信号和化学信号相互通信。
一个生物神经元由三部分组成:
树突:接收来自其他神经元的信号,就像天线的输入端。
细胞体:整合所有输入信号,决定是否"激活"。当输入信号的总和超过某个阈值时,神经元会发放一个电脉冲。
轴突:将输出信号传递给其他神经元,就像输出导线。
关键洞察:神经元的工作方式可以抽象为"加权求和 + 阈值判断"。这个简单的抽象,正是人工神经网络的理论基础。
0.2 人工神经元:用数学模拟生物
1943年,McCulloch和Pitts提出了第一个人工神经元模型。他们将生物神经元抽象为数学运算:
- 输入:来自其他神经元的信号,用 x 1 , x 2 , . . . , x n x_1, x_2, ..., x_n x1,x2,...,xn 表示
- 权重:每个输入的重要性,用 w 1 , w 2 , . . . , w n w_1, w_2, ..., w_n w1,w2,...,wn 表示
- 偏置:激活的门槛,用 b b b 表示
- 激活函数:决定是否激活,用 σ \sigma σ 表示
人工神经元的输出公式:
y = σ ( ∑ i = 1 n w i x i + b ) y = \sigma\left(\sum_{i=1}^{n} w_i x_i + b\right) y=σ(i=1∑nwixi+b)
这个公式看起来简单,但它蕴含了深刻的智慧:通过调整权重 w w w 和偏置 b b b,神经元可以学习识别不同的模式。
一、为什么需要神经网络?
在机器学习的世界里,我们已经有了线性回归、逻辑回归、决策树等算法。为什么还需要神经网络?
1.1 线性模型的局限
假设你要预测房价。线性回归假设房价与特征(面积、房间数、位置等)之间存在线性关系:
房价 = w 1 × 面积 + w 2 × 房间数 + b \text{房价} = w_1 \times \text{面积} + w_2 \times \text{房间数} + b 房价=w1×面积+w2×房间数+b
这个模型在简单情况下有效,但现实世界远比这复杂。房价与面积的关系可能是非线性的——面积太小或太大都不好,中等面积最受欢迎。这种"倒U型"关系,线性模型无法捕捉。
更复杂的情况是图像识别。一张28x28像素的手写数字图片有784个像素,每个像素都是0-255的灰度值。如果用线性模型,我们需要学习784个权重。但问题是:同一个数字在不同位置、不同大小、不同书写风格下,像素值会完全不同。线性模型无法处理这种复杂性。
1.2 神经网络的解决方案
神经网络的核心理念是:通过多层非线性变换,将原始特征转换为更抽象、更有表达力的特征。
想象你要识别一张猫的图片。第一层可能学习识别边缘和纹理,第二层可能学习识别眼睛、耳朵等部件,第三层可能学习识别完整的猫。每一层都在前一层的基础上构建更高级的特征。
这种"层层抽象"的方式,使得神经网络能够学习极其复杂的模式。这就是为什么神经网络在图像识别、语音识别、自然语言处理等领域取得了突破性进展。
二、神经网络的核心组件
2.1 激活函数:引入非线性
激活函数是神经网络的灵魂。没有激活函数,无论神经网络有多少层,它本质上还是一个线性模型。
为什么?因为多个线性变换的组合仍然是线性变换:
y = W 2 ( W 1 x + b 1 ) + b 2 = W 2 W 1 x + ( W 2 b 1 + b 2 ) y = W_2(W_1 x + b_1) + b_2 = W_2 W_1 x + (W_2 b_1 + b_2) y=W2(W1x+b1)+b2=W2W1x+(W2b1+b2)
这等价于一个线性变换 y = W ′ x + b ′ y = W' x + b' y=W′x+b′。所以,非线性激活函数是神经网络能够学习复杂模式的关键。
常用激活函数
ReLU(Rectified Linear Unit)
ReLU是目前最常用的激活函数。它的定义非常简单:
ReLU ( x ) = max ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x)
当输入为正时,输出等于输入;当输入为负时,输出为0。
ReLU的优势在于计算简单,且在正区间梯度恒为1,避免了梯度消失问题。但它有一个缺点:当输入为负时,梯度为0,神经元"死亡"不再更新。
Sigmoid
Sigmoid将输入压缩到0-1之间:
σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1 + e^{-x}} σ(x)=1+e−x1
Sigmoid常用于二分类问题的输出层,将预测值解释为概率。但它在深层网络中容易导致梯度消失,因为当输入很大或很小时,梯度接近0。
Tanh
Tanh将输入压缩到-1到1之间:
tanh ( x ) = e x − e − x e x + e − x \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} tanh(x)=ex+e−xex−e−x
Tanh是Sigmoid的缩放版本,输出以0为中心,收敛速度通常比Sigmoid快。
如何选择激活函数?
隐藏层:首选ReLU。如果出现大量神经元死亡,可以尝试Leaky ReLU或ELU。
输出层:
- 二分类:Sigmoid
- 多分类:Softmax
- 回归:线性激活(不使用激活函数)
2.2 前向传播:从输入到输出
前向传播是神经网络"推理"的过程:输入数据经过各层的计算,最终得到输出。
让我们用一个具体的例子来理解。假设有一个简单的三层神经网络:
- 输入层:2个神经元
- 隐藏层:3个神经元,使用ReLU激活
- 输出层:1个神经元,使用Sigmoid激活
前向传播的计算步骤:
第1步:输入层到隐藏层
z1 = W1 · x + b1 # 线性变换
h = ReLU(z1) # 激活函数
第2步:隐藏层到输出层
z2 = W2 · h + b2 # 线性变换
y = Sigmoid(z2) # 激活函数
最终输出:y(预测值)
其中:
- W 1 W_1 W1 是输入层到隐藏层的权重矩阵,形状为 (3, 2)
- b 1 b_1 b1 是隐藏层的偏置向量,形状为 (3, 1)
- W 2 W_2 W2 是隐藏层到输出层的权重矩阵,形状为 (1, 3)
- b 2 b_2 b2 是输出层的偏置,形状为 (1, 1)
2.3 损失函数:衡量预测与真实的差距
损失函数衡量神经网络的预测值与真实值之间的差距。训练神经网络的目标就是最小化损失函数。
回归问题常用损失:
均方误差(MSE):
L = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 L = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 L=n1i=1∑n(yi−y^i)2
分类问题常用损失:
交叉熵损失(Cross-Entropy Loss):
L = − ∑ i = 1 n y i log ( y ^ i ) L = -\sum_{i=1}^{n} y_i \log(\hat{y}_i) L=−i=1∑nyilog(y^i)
对于二分类问题:
L = − [ y log ( y ^ ) + ( 1 − y ) log ( 1 − y ^ ) ] L = -[y \log(\hat{y}) + (1-y)\log(1-\hat{y})] L=−[ylog(y^)+(1−y)log(1−y^)]
交叉熵损失的优势在于:当预测值与真实值差距很大时,梯度也很大,模型能够快速学习。
三、反向传播:神经网络如何学习
反向传播是神经网络训练的核心算法。它通过计算损失函数对每个参数的梯度,告诉我们应该如何调整参数才能减小损失。
3.1 反向传播的核心思想
反向传播基于一个简单的数学原理:链式法则。
假设有一个复合函数 y = f ( g ( x ) ) y = f(g(x)) y=f(g(x)),那么:
d y d x = d y d g ⋅ d g d x \frac{dy}{dx} = \frac{dy}{dg} \cdot \frac{dg}{dx} dxdy=dgdy⋅dxdg
在神经网络中,输出是输入经过多层变换的结果:
y = f L ( f L − 1 ( . . . f 1 ( x ) . . . ) ) y = f_L(f_{L-1}(...f_1(x)...)) y=fL(fL−1(...f1(x)...))
要计算损失 L L L 对第一层参数 W 1 W_1 W1 的梯度,我们需要沿着计算图反向传播:
∂ L ∂ W 1 = ∂ L ∂ y ⋅ ∂ y ∂ f L − 1 ⋅ . . . ⋅ ∂ f 1 ∂ W 1 \frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial f_{L-1}} \cdot ... \cdot \frac{\partial f_1}{\partial W_1} ∂W1∂L=∂y∂L⋅∂fL−1∂y⋅...⋅∂W1∂f1
3.2 反向传播一步步图解
让我们用一个简单的两层网络来理解反向传播:
网络结构: x → h → y x \rightarrow h \rightarrow y x→h→y
前向传播:
- z 1 = W 1 x + b 1 z_1 = W_1 x + b_1 z1=W1x+b1
- h = ReLU ( z 1 ) h = \text{ReLU}(z_1) h=ReLU(z1)
- z 2 = W 2 h + b 2 z_2 = W_2 h + b_2 z2=W2h+b2
- y ^ = σ ( z 2 ) \hat{y} = \sigma(z_2) y^=σ(z2)
- L = − [ y log ( y ^ ) + ( 1 − y ) log ( 1 − y ^ ) ] L = -[y \log(\hat{y}) + (1-y)\log(1-\hat{y})] L=−[ylog(y^)+(1−y)log(1−y^)]
反向传播步骤:
第1步:计算损失对输出的梯度
dL_dy_hat = -y/y_hat + (1-y)/(1-y_hat)
第2步:计算输出层激活前的梯度
dL_dz2 = dL_dy_hat * sigmoid_derivative(z2)
= dL_dy_hat * y_hat * (1 - y_hat)
第3步:计算输出层权重和偏置的梯度
dL_dW2 = dL_dz2 · h.T
dL_db2 = dL_dz2
第4步:计算隐藏层的梯度
dL_dh = W2.T · dL_dz2
第5步:计算隐藏层激活前的梯度
dL_dz1 = dL_dh * relu_derivative(z1)
= dL_dh * (z1 > 0)
第6步:计算隐藏层权重和偏置的梯度
dL_dW1 = dL_dz1 · x.T
dL_db1 = dL_dz1
3.3 梯度下降:更新参数
计算出梯度后,我们使用梯度下降更新参数:
W = W − α ∂ L ∂ W W = W - \alpha \frac{\partial L}{\partial W} W=W−α∂W∂L
b = b − α ∂ L ∂ b b = b - \alpha \frac{\partial L}{\partial b} b=b−α∂b∂L
其中 α \alpha α 是学习率,控制每次更新的步长。学习率太大可能导致震荡,太小则收敛太慢。
四、用PyTorch实现第一个神经网络
现在让我们把理论付诸实践,用PyTorch实现一个完整的神经网络。
4.1 PyTorch核心概念
张量(Tensor)
张量是PyTorch的核心数据结构,类似于NumPy的数组,但支持GPU加速和自动求导。
import torch
# 创建张量
x = torch.tensor([1.0, 2.0, 3.0])
print(f"张量: {x}")
print(f"形状: {x.shape}")
print(f"数据类型: {x.dtype}")
# 张量运算
y = x * 2 + 1
print(f"运算结果: {y}")
# GPU加速
if torch.cuda.is_available():
x_gpu = x.cuda()
y_gpu = x_gpu * 2 + 1
print(f"GPU运算结果: {y_gpu.cpu()}")
自动求导(Autograd)
PyTorch的Autograd模块能够自动计算梯度,这是反向传播的基础。
# 创建需要梯度的张量
x = torch.tensor([2.0], requires_grad=True)
# 定义计算过程
y = x ** 2
z = y + 3
# 反向传播计算梯度
z.backward()
# 查看梯度
print(f"dz/dx = {x.grad}")
4.2 定义神经网络模型
PyTorch提供了nn.Module类来定义神经网络。我们需要实现两个方法:
__init__:定义网络结构forward:定义前向传播
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import numpy as np
class SimpleNeuralNetwork(nn.Module):
"""
简单的全连接神经网络
用于二分类任务,包含一个隐藏层。
网络结构:
输入层 -> 隐藏层(ReLU) -> 输出层(Sigmoid)
Attributes:
hidden: 隐藏层线性变换
output: 输出层线性变换
relu: ReLU激活函数
sigmoid: Sigmoid激活函数
使用示例:
model = SimpleNeuralNetwork(input_size=10, hidden_size=20)
output = model(torch.randn(32, 10))
print(output.shape)
"""
def __init__(self, input_size: int, hidden_size: int):
"""
初始化神经网络
Args:
input_size: 输入特征维度
hidden_size: 隐藏层神经元数量
"""
super().__init__()
# 定义隐藏层:input_size -> hidden_size
self.hidden = nn.Linear(input_size, hidden_size)
# 定义输出层:hidden_size -> 1
self.output = nn.Linear(hidden_size, 1)
# 定义激活函数
self.relu = nn.ReLU()
self.sigmoid = nn.Sigmoid()
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
前向传播
执行流程:
1. 输入经过隐藏层线性变换
2. 应用ReLU激活函数
3. 经过输出层线性变换
4. 应用Sigmoid激活函数得到概率
Args:
x: 输入张量,形状为 (batch_size, input_size)
Returns:
输出张量,形状为 (batch_size, 1),值为0-1之间的概率
"""
# 隐藏层:线性变换 + ReLU激活
hidden_out = self.hidden(x)
hidden_activated = self.relu(hidden_out)
# 输出层:线性变换 + Sigmoid激活
output_out = self.output(hidden_activated)
output_activated = self.sigmoid(output_out)
return output_activated
class DeepNeuralNetwork(nn.Module):
"""
深层神经网络
支持任意数量的隐藏层,使用BatchNorm和Dropout提高训练稳定性。
Attributes:
layers: 神经网络层列表
batch_norms: BatchNorm层列表
dropouts: Dropout层列表
使用示例:
model = DeepNeuralNetwork(
input_size=784,
hidden_sizes=[256, 128, 64],
output_size=10,
dropout_rate=0.3
)
output = model(torch.randn(32, 784))
print(output.shape)
"""
def __init__(
self,
input_size: int,
hidden_sizes: list,
output_size: int,
dropout_rate: float = 0.3
):
"""
初始化深层神经网络
Args:
input_size: 输入特征维度
hidden_sizes: 各隐藏层神经元数量列表
output_size: 输出维度
dropout_rate: Dropout概率,用于防止过拟合
"""
super().__init__()
self.layers = nn.ModuleList()
self.batch_norms = nn.ModuleList()
self.dropouts = nn.ModuleList()
# 构建隐藏层
prev_size = input_size
for hidden_size in hidden_sizes:
# 线性层
self.layers.append(nn.Linear(prev_size, hidden_size))
# BatchNorm层:加速训练,提高稳定性
self.batch_norms.append(nn.BatchNorm1d(hidden_size))
# Dropout层:随机丢弃神经元,防止过拟合
self.dropouts.append(nn.Dropout(dropout_rate))
prev_size = hidden_size
# 输出层
self.output_layer = nn.Linear(prev_size, output_size)
# 激活函数
self.relu = nn.ReLU()
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
前向传播
执行流程:
1. 依次通过各隐藏层
2. 每个隐藏层:线性变换 -> BatchNorm -> ReLU -> Dropout
3. 输出层:线性变换
Args:
x: 输入张量,形状为 (batch_size, input_size)
Returns:
输出张量,形状为 (batch_size, output_size)
"""
# 通过各隐藏层
for layer, bn, dropout in zip(self.layers, self.batch_norms, self.dropouts):
x = layer(x)
x = bn(x)
x = self.relu(x)
x = dropout(x)
# 输出层(不加激活函数,后续用CrossEntropyLoss)
x = self.output_layer(x)
return x
def train_model(
model: nn.Module,
train_loader: DataLoader,
val_loader: DataLoader,
epochs: int = 100,
learning_rate: float = 0.001,
device: str = 'cpu'
) -> dict:
"""
训练神经网络模型
训练流程:
1. 定义损失函数和优化器
2. 遍历epoch进行训练
3. 每个epoch后验证
4. 记录训练和验证损失
Args:
model: 要训练的神经网络模型
train_loader: 训练数据加载器
val_loader: 验证数据加载器
epochs: 训练轮数
learning_rate: 学习率
device: 计算设备
Returns:
包含训练历史的字典:
- train_losses: 各epoch训练损失
- val_losses: 各epoch验证损失
- val_accuracies: 各epoch验证准确率
使用示例:
history = train_model(model, train_loader, val_loader, epochs=50)
plt.plot(history['train_losses'], label='Train Loss')
plt.plot(history['val_losses'], label='Val Loss')
plt.legend()
plt.show()
"""
# 将模型移到指定设备
model = model.to(device)
# 定义损失函数
# 对于二分类,使用BCELoss(二元交叉熵)
criterion = nn.BCELoss()
# 定义优化器
# Adam是常用的自适应学习率优化器
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 记录训练历史
history = {
'train_losses': [],
'val_losses': [],
'val_accuracies': []
}
# 训练循环
for epoch in range(epochs):
# 训练阶段
model.train()
train_loss = 0.0
for batch_x, batch_y in train_loader:
# 将数据移到指定设备
batch_x = batch_x.to(device)
batch_y = batch_y.to(device)
# 清零梯度
# PyTorch默认会累积梯度,需要手动清零
optimizer.zero_grad()
# 前向传播
outputs = model(batch_x)
# 计算损失
# 需要将batch_y调整为与outputs相同的形状
loss = criterion(outputs, batch_y.unsqueeze(1))
# 反向传播
# 计算所有参数的梯度
loss.backward()
# 参数更新
# 根据梯度调整参数
optimizer.step()
# 累加训练损失
train_loss += loss.item()
# 计算平均训练损失
avg_train_loss = train_loss / len(train_loader)
history['train_losses'].append(avg_train_loss)
# 验证阶段
model.eval()
val_loss = 0.0
correct = 0
total = 0
# 不需要计算梯度,节省内存
with torch.no_grad():
for batch_x, batch_y in val_loader:
batch_x = batch_x.to(device)
batch_y = batch_y.to(device)
# 前向传播
outputs = model(batch_x)
# 计算损失
loss = criterion(outputs, batch_y.unsqueeze(1))
val_loss += loss.item()
# 计算准确率
# 将概率转换为预测类别(大于0.5为1,否则为0)
predicted = (outputs > 0.5).float()
total += batch_y.size(0)
correct += (predicted.squeeze() == batch_y).sum().item()
# 计算平均验证损失和准确率
avg_val_loss = val_loss / len(val_loader)
val_accuracy = correct / total
history['val_losses'].append(avg_val_loss)
history['val_accuracies'].append(val_accuracy)
# 每10个epoch打印一次
if (epoch + 1) % 10 == 0:
print(f"Epoch [{epoch+1}/{epochs}]")
print(f" Train Loss: {avg_train_loss:.4f}")
print(f" Val Loss: {avg_val_loss:.4f}")
print(f" Val Accuracy: {val_accuracy:.4f}")
return history
def create_sample_data(n_samples: int = 1000, n_features: int = 10):
"""
创建示例数据用于演示
生成一个简单的二分类数据集。
Args:
n_samples: 样本数量
n_features: 特征数量
Returns:
train_loader: 训练数据加载器
val_loader: 验证数据加载器
"""
# 生成随机特征
X = np.random.randn(n_samples, n_features).astype(np.float32)
# 生成标签:简单的线性决策边界
# 真实权重
true_weights = np.random.randn(n_features).astype(np.float32)
# 真实偏置
true_bias = 0.5
# 计算线性组合
linear_combination = np.dot(X, true_weights) + true_bias
# 转换为概率
probs = 1 / (1 + np.exp(-linear_combination))
# 生成标签
y = (probs > 0.5).astype(np.float32)
# 划分训练集和验证集
split_idx = int(0.8 * n_samples)
X_train, X_val = X[:split_idx], X[split_idx:]
y_train, y_val = y[:split_idx], y[split_idx:]
# 转换为PyTorch张量
X_train_tensor = torch.from_numpy(X_train)
y_train_tensor = torch.from_numpy(y_train)
X_val_tensor = torch.from_numpy(X_val)
y_val_tensor = torch.from_numpy(y_val)
# 创建数据集和数据加载器
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
val_dataset = TensorDataset(X_val_tensor, y_val_tensor)
# batch_size控制每次训练的样本数量
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)
return train_loader, val_loader
if __name__ == "__main__":
# 设置随机种子,保证结果可复现
torch.manual_seed(42)
np.random.seed(42)
# 创建示例数据
print("创建示例数据...")
train_loader, val_loader = create_sample_data(n_samples=1000, n_features=10)
# 创建模型
print("\n创建神经网络模型...")
model = SimpleNeuralNetwork(input_size=10, hidden_size=20)
print(model)
# 检查是否有GPU
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f"\n使用设备: {device}")
# 训练模型
print("\n开始训练...")
history = train_model(
model=model,
train_loader=train_loader,
val_loader=val_loader,
epochs=100,
learning_rate=0.01,
device=device
)
# 打印最终结果
print("\n" + "=" * 50)
print("训练完成!")
print(f"最终验证准确率: {history['val_accuracies'][-1]:.4f}")
print("=" * 50)
4.3 代码关键点解析
nn.Linear:全连接层,执行线性变换 y = W x + b y = Wx + b y=Wx+b
nn.Module:所有神经网络模块的基类,自动管理参数
optimizer.zero_grad():清零梯度,PyTorch默认累积梯度
loss.backward():反向传播,计算所有参数的梯度
optimizer.step():根据梯度更新参数
model.train() / model.eval():切换训练/评估模式,影响Dropout和BatchNorm
torch.no_grad():禁用梯度计算,节省内存,用于验证和推理
五、神经网络的训练技巧
5.1 权重初始化
好的初始化可以加速收敛,避免梯度消失或爆炸。
Xavier初始化:适用于Sigmoid、Tanh激活函数
W ∼ N ( 0 , 2 n i n + n o u t ) W \sim \mathcal{N}\left(0, \sqrt{\frac{2}{n_{in} + n_{out}}}\right) W∼N(0,nin+nout2)
He初始化:适用于ReLU激活函数
W ∼ N ( 0 , 2 n i n ) W \sim \mathcal{N}\left(0, \sqrt{\frac{2}{n_{in}}}\right) W∼N(0,nin2)
PyTorch默认使用He初始化:
# 手动初始化
def init_weights(m):
if isinstance(m, nn.Linear):
nn.init.kaiming_normal_(m.weight)
nn.init.zeros_(m.bias)
model.apply(init_weights)
5.2 正则化
Dropout:训练时随机丢弃一部分神经元,防止过拟合
dropout = nn.Dropout(p=0.5) # 丢弃概率为50%
权重衰减(L2正则化):在损失函数中添加权重的平方项
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
5.3 学习率调度
学习率调度器可以在训练过程中动态调整学习率。
# StepLR:每step_size个epoch,学习率乘以gamma
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
for epoch in range(epochs):
train(...)
scheduler.step() # 更新学习率
5.4 Batch Normalization
BatchNorm对每个mini-batch的数据进行标准化,加速训练并提高稳定性。
bn = nn.BatchNorm1d(num_features=64)
六、常见问题与解决方案
6.1 梯度消失/爆炸
症状:深层网络训练困难,梯度变得极小或极大
解决方案:
- 使用ReLU替代Sigmoid/Tanh
- 使用BatchNorm
- 使用残差连接(ResNet)
- 使用梯度裁剪
# 梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
6.2 过拟合
症状:训练集表现好,验证集表现差
解决方案:
- 增加训练数据
- 使用Dropout
- 使用权重衰减
- 早停(Early Stopping)
- 数据增强
6.3 欠拟合
症状:训练集和验证集都表现差
解决方案:
- 增加模型复杂度
- 增加训练时间
- 减小正则化强度
- 使用更好的特征
七、总结
神经网络的核心思想可以概括为三点:
第一:通过多层非线性变换,神经网络能够学习极其复杂的模式。激活函数是引入非线性的关键,没有它,再深的网络也只是线性模型。
第二:反向传播算法通过链式法则高效计算梯度,使得训练深层网络成为可能。理解反向传播对于调试神经网络至关重要。
第三:PyTorch通过张量、自动求导和神经网络模块,大大简化了神经网络的实现。掌握这些工具,你就能快速实现各种网络架构。
神经网络的魅力在于它的通用性——同样的架构可以处理图像、文本、语音等各种类型的数据。在接下来的文章中,我们将探索专门用于图像处理的卷积神经网络(CNN)和专门用于序列数据的循环神经网络(RNN)。
参考资料
- PyTorch官方教程:https://pytorch.org/tutorials/
- 深度学习入门:斋藤康毅
- 神经网络与深度学习:Michael Nielsen
- 3Blue1Brown神经网络视频:https://www.youtube.com/watch?v=aircAruvnKk
下篇预告
在下一篇文章中,我们将深入卷积神经网络(CNN)的世界。你将了解到:
- 卷积操作是如何提取图像特征的
- 池化层为什么能增强模型的鲁棒性
- 经典CNN架构(LeNet、AlexNet、VGG、ResNet)的设计思想
- 如何用PyTorch实现图像分类模型
敬请期待:《卷积神经网络:让机器看见世界》
更多推荐

所有评论(0)