深度学习基础:神经网络基础与PyTorch
1. 神经网络基础概念
1.1 什么是神经网络
人工神经网络(Artificial Neural Network,ANN)是一种模仿生物神经网络结构和功能的计算模型。它由多个互相连接的人工神经元构成,可以用于处理和学习复杂的数据模式,尤其适合解决非线性问题。
生物神经元的工作原理:
- 树突接收输入信号
- 细胞核处理信号
- 轴突输出信号
人工神经元的构建:
python
# 神经元的基本计算过程
z = w·x + b # 内部状态值(加权求和)
a = f(z) # 激活值(通过激活函数)
1.2 神经网络结构
神经网络由三个基本部分组成:
- 输入层(Input Layer):接收原始数据
- 隐藏层(Hidden Layers):处理数据,提取特征
- 输出层(Output Layer):产生最终预测结果
神经网络特点:
- 同一层的神经元之间没有连接
- 第N层的每个神经元和第N-1层的所有神经元相连(全连接)
- 每个连接都有一个权重值(w系数和b系数)
- 输入层的特征数必须与对接神经元的输入参数数量相同
1.3 前向传播过程
前向传播是数据从输入到输出经过一层一层神经元产生预测值的过程:
python
# 前向传播示例
def forward_pass(x, weights, biases):
# 第一层
z1 = torch.matmul(x, weights[0]) + biases[0]
a1 = torch.relu(z1)
# 第二层
z2 = torch.matmul(a1, weights[1]) + biases[1]
a2 = torch.relu(z2)
# 输出层
z3 = torch.matmul(a2, weights[2]) + biases[2]
output = torch.softmax(z3, dim=-1)
return output
2. 激活函数详解
2.1 为什么需要激活函数
没有激活函数的神经网络等价于线性模型,无法处理复杂的非线性问题。激活函数为网络引入非线性因素,使得网络可以逼近任意函数。
2.2 常见激活函数
2.2.1 Sigmoid函数
公式: σ(x) = 1/(1 + e^(-x))
特点:
- 输出范围:(0, 1)
- 导数范围:(0, 0.25)
- 存在梯度消失问题
- 计算量大(指数运算)
- 输出不以0为中心
PyTorch实现:
python
import torch
import matplotlib.pyplot as plt
def plot_sigmoid():
fig, axes = plt.subplots(1, 2)
# 函数图像
x = torch.linspace(-20, 20, 1000)
y = torch.sigmoid(x)
axes[0].plot(x, y)
axes[0].grid()
axes[0].set_title('Sigmoid 函数图像')
# 导数图像
x = torch.linspace(-20, 20, 1000, requires_grad=True)
torch.sigmoid(x).sum().backward()
axes[1].plot(x.detach(), x.grad)
axes[1].grid()
axes[1].set_title('Sigmoid 导数图像')
plt.show()
2.2.2 Tanh函数
公式: tanh(x) = (e^x - e(-x))/(ex + e^(-x))
特点:
- 输出范围:(-1, 1)
- 以0为中心,收敛速度比Sigmoid快
- 导数范围:(0, 1)
- 仍然存在梯度消失问题
PyTorch实现:
python
def plot_tanh():
fig, axes = plt.subplots(1, 2)
# 函数图像
x = torch.linspace(-20, 20, 1000)
y = torch.tanh(x)
axes[0].plot(x, y)
axes[0].grid()
axes[0].set_title('Tanh 函数图像')
# 导数图像
x = torch.linspace(-20, 20, 1000, requires_grad=True)
torch.tanh(x).sum().backward()
axes[1].plot(x.detach(), x.grad)
axes[1].grid()
axes[1].set_title('Tanh 导数图像')
plt.show()
2.2.3 ReLU函数
公式: ReLU(x) = max(0, x)
特点:
- 计算简单,训练效率高
- 缓解梯度消失问题
- 可能产生"神经元死亡"现象
- 目前最常用的激活函数
PyTorch实现:
python
def plot_relu():
fig, axes = plt.subplots(1, 2)
# 函数图像
x = torch.linspace(-20, 20, 1000)
y = torch.relu(x)
axes[0].plot(x, y)
axes[0].grid()
axes[0].set_title('ReLU 函数图像')
# 导数图像
x = torch.linspace(-20, 20, 1000, requires_grad=True)
torch.relu(x).sum().backward()
axes[1].plot(x.detach(), x.grad)
axes[1].grid()
axes[1].set_title('ReLU 导数图像')
plt.show()
2.2.4 Softmax函数
公式: softmax(x_i) = e^(x_i) / Σ(e^(x_j))
特点:
- 用于多分类问题
- 输出概率分布(和为1)
- 将logits转换为概率
PyTorch实现:
python
def demo_softmax():
scores = torch.tensor([0.2, 0.02, 0.15, 0.15, 1.3, 0.5, 0.06, 1.1, 0.05, 3.75])
probabilities = torch.softmax(scores, dim=0)
print("Softmax输出:", probabilities)
print("概率和:", probabilities.sum()) # 应该等于1
2.3 激活函数选择指南
隐藏层:
- 优先选择ReLU
- 如果ReLU效果不好,尝试Leaky ReLU
- 少使用sigmoid,可以尝试tanh
输出层:
- 二分类:sigmoid
- 多分类:softmax
- 回归:identity(恒等函数)
3. 参数初始化
3.1 为什么需要参数初始化
不正确的权重初始化会导致梯度消失或爆炸问题,影响训练效果。
3.2 常见初始化方法
3.2.1 传统初始化方法
python
import torch.nn as nn
def traditional_init():
linear = nn.Linear(5, 3)
# 1. 均匀分布初始化
nn.init.uniform_(linear.weight)
# 2. 正态分布初始化
nn.init.normal_(linear.weight, mean=0, std=1)
# 3. 全0初始化
nn.init.zeros_(linear.weight)
# 4. 全1初始化
nn.init.ones_(linear.weight)
# 5. 固定值初始化
nn.init.constant_(linear.weight, 5)
print(linear.weight.data)
3.2.2 Kaiming初始化(He初始化)
专为ReLU激活函数设计:
python
def kaiming_init():
linear = nn.Linear(5, 3)
# Kaiming正态分布初始化
nn.init.kaiming_normal_(linear.weight)
# Kaiming均匀分布初始化
nn.init.kaiming_uniform_(linear.weight)
print(linear.weight.data)
公式:
- 正态分布:std = sqrt(2 / fan_in)
- 均匀分布:limit = sqrt(6 / fan_in)
3.2.3 Xavier初始化(Glorot初始化)
根据输入和输出维度自动选择权重范围:
python
def xavier_init():
linear = nn.Linear(5, 3)
# Xavier正态分布初始化
nn.init.xavier_normal_(linear.weight)
# Xavier均匀分布初始化
nn.init.xavier_uniform_(linear.weight)
print(linear.weight.data)
公式:
- 正态分布:std = sqrt(2 / (fan_in + fan_out))
- 均匀分布:limit = sqrt(6 / (fan_in + fan_out))
3.3 初始化方法选择
- 偏置一般初始化为0
- 权重优先选择Kaiming或Xavier初始化
- ReLU激活函数推荐使用Kaiming初始化
- 其他激活函数推荐使用Xavier初始化
4. PyTorch神经网络构建实战
4.1 自定义神经网络类
python
import torch
import torch.nn as nn
from torchsummary import summary
class MyModel(nn.Module):
def __init__(self):
super(MyModel, self).__init__()
# 第一层隐藏层
self.linear1 = nn.Linear(3, 3)
nn.init.xavier_normal_(self.linear1.weight)
nn.init.ones_(self.linear1.bias)
# 第二层隐藏层
self.linear2 = nn.Linear(3, 2)
nn.init.kaiming_normal_(self.linear2.weight, nonlinearity='relu')
nn.init.ones_(self.linear2.bias)
# 输出层
self.out = nn.Linear(2, 2)
def forward(self, x):
# 第一层 + 激活函数
x = self.linear1(x)
x = torch.sigmoid(x)
# 第二层 + 激活函数
x = self.linear2(x)
x = torch.relu(x)
# 输出层 + 激活函数
x = self.out(x)
x = torch.softmax(x, dim=-1)
return x
4.2 模型训练和测试
python
def train_model():
# 创建模型实例
model = MyModel()
# 准备数据
input_data = torch.randn(5, 3)
print("输入数据形状:", input_data.shape)
# 前向传播
output = model(input_data)
print("输出数据形状:", output.shape)
print("输出数据:", output)
# 查看模型参数
print("\n模型参数:")
for name, param in model.named_parameters():
print(f"{name}: {param.shape}")
# 计算模型参数量
summary(model, input_size=(3,), batch_size=5)
if __name__ == '__main__':
train_model()
4.3 线性回归实战案例
python
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import make_regression
from torch.utils.data import TensorDataset, DataLoader
import matplotlib.pyplot as plt
def create_dataset():
"""构建回归数据集"""
x, y, coef = make_regression(
n_samples=100,
n_features=1,
bias=2,
noise=10,
coef=True,
random_state=22
)
# 转换为张量
tensor_x = torch.tensor(x, dtype=torch.float)
tensor_y = torch.tensor(y, dtype=torch.float)
return tensor_x, tensor_y, coef
def train_linear_regression():
"""训练线性回归模型"""
# 获取数据
x, y, coef = create_dataset()
# 创建数据加载器
dataset = TensorDataset(x, y)
dataloader = DataLoader(dataset, batch_size=4, shuffle=True)
# 构建模型
model = nn.Linear(1, 1)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 训练参数
epochs = 100
total_loss = 0
epoch_losses = []
# 训练循环
for epoch in range(epochs):
epoch_loss = 0
for batch_x, batch_y in dataloader:
batch_y = batch_y.reshape(-1, 1)
# 前向传播
y_pred = model(batch_x)
loss = criterion(y_pred, batch_y)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_loss += loss.item()
epoch_losses.append(epoch_loss / len(dataloader))
if (epoch + 1) % 20 == 0:
print(f"Epoch {epoch+1}, Loss: {epoch_losses[-1]:.4f}")
# 绘制结果
plt.figure(figsize=(12, 4))
# 损失曲线
plt.subplot(1, 2, 1)
plt.plot(epoch_losses)
plt.title('Training Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.grid(True)
# 拟合结果
plt.subplot(1, 2, 2)
plt.scatter(x, y, alpha=0.6, label='Data')
plt.plot(x, x * coef + 2, 'r-', label='True Line')
plt.plot(x, x * model.weight.detach() + model.bias.detach(),
'g--', label='Predicted Line')
plt.title('Linear Regression Result')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
print(f"真实参数: w={coef[0]:.4f}, b=2.0000")
print(f"预测参数: w={model.weight.item():.4f}, b={model.bias.item():.4f}")
if __name__ == '__main__':
train_linear_regression()
5. 数据形状变化分析
5.1 形状变化示例
python
def analyze_shape_changes():
model = MyModel()
input_data = torch.randn(5, 3)
print("输入数据形状:", input_data.shape) # torch.Size([5, 3])
# 第一层
x = model.linear1(input_data)
print("第一层输出形状:", x.shape) # torch.Size([5, 3])
# 激活函数不改变形状
x = torch.sigmoid(x)
print("激活后形状:", x.shape) # torch.Size([5, 3])
# 第二层
x = model.linear2(x)
print("第二层输出形状:", x.shape) # torch.Size([5, 2])
# 输出层
x = model.out(x)
print("最终输出形状:", x.shape) # torch.Size([5, 2])
5.2 参数计算
对于全连接层,参数计算公式为:
参数量 = 输入特征数 × 输出特征数 + 输出特征数
python
def calculate_parameters():
model = MyModel()
total_params = 0
for name, param in model.named_parameters():
param_count = param.numel()
total_params += param_count
print(f"{name}: {param.shape} -> {param_count} 参数")
print(f"总参数量: {total_params}")
6. 广播机制(扩展知识)
6.1 广播规则
广播机制允许不同形状的张量进行运算:
- 如果两个数组的维度数不相同,小维度数组的形状将在左边补1
- 如果shape的维度不匹配,但有维度是1,可以扩展维度是1的维度匹配另一个数组
- 如果shape的维度不匹配,且没有任何维度是1,则匹配失败
6.2 广播示例
python
import numpy as np
def broadcast_examples():
# 示例1:标量与数组
a = np.array([0, 1, 2])
b = 2
print("a + b:", a + b) # [2, 3, 4]
# 示例2:不同形状的数组
a = np.array([[1, 2, 3], [4, 5, 6]]) # (2, 3)
b = np.array([1, 1, 1]) # (3,)
print("a + b:\n", a + b)
# 示例3:不满足广播条件
a = np.array([1, 2, 3, 4]) # (4,)
b = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]) # (4, 3)
try:
print("a + b:", a + b)
except ValueError as e:
print("广播失败:", e)
if __name__ == '__main__':
broadcast_examples()
7. 总结
通过今天的学习,我们掌握了:
- 神经网络基础概念:理解了人工神经元的构建和前向传播过程
- 激活函数:掌握了Sigmoid、Tanh、ReLU、Softmax等常用激活函数的特点和使用场景
- 参数初始化:学会了Kaiming和Xavier等现代初始化方法
- PyTorch实战:能够使用PyTorch构建和训练神经网络模型
- 线性回归案例:通过完整的案例理解了神经网络的训练流程
关键要点回顾
- 激活函数选择:隐藏层优先使用ReLU,输出层根据任务类型选择
- 参数初始化:权重使用Kaiming或Xavier初始化,偏置初始化为0
- 模型构建:继承nn.Module,实现__init__和forward方法
- 训练流程:前向传播 → 计算损失 → 反向传播 → 参数更新
希望这篇文章能帮助大家更好地理解神经网络的基础知识,为后续的深度学习学习打下坚实的基础!
日期:2025年10月9日