1. 全连接神经网络基础解析
全连接神经网络(Fully Connected Neural Network)是深度学习中最基础的网络结构之一,也是理解现代卷积神经网络的必经之路。这种网络结构中,相邻层的所有神经元之间都存在连接,因此得名"全连接"。
1.1 网络结构组成
一个典型的三层全连接网络包含以下组件:
-
输入层:接收原始数据输入,如图像像素矩阵、文本向量或任何数值型数据。对于一张28×28的灰度图像,输入层通常会被展平为784个神经元(28×28=784)。
-
隐藏层:负责对输入数据进行非线性变换和特征提取。隐藏层的数量和每层的神经元个数是重要的超参数。实践中,深层网络(多个隐藏层)通常比浅层网络具有更强的表达能力。
-
输出层:产生最终的预测结果。对于分类任务,输出层神经元数量通常等于类别数;对于回归任务,输出层可能只有一个神经元。
注意:在实际应用中,输入层通常不视为"层",因为不涉及任何计算。因此一个"单隐藏层网络"实际上包含输入层、隐藏层和输出层三部分。
1.2 前向传播过程
前向传播是神经网络进行预测的核心过程。以一个单隐藏层网络为例:
- 输入数据x通过权重矩阵W₁和偏置b₁进行线性变换:z₁ = W₁x + b₁
- 经过激活函数(如ReLU)的非线性变换:a₁ = σ(z₁)
- 隐藏层输出通过权重矩阵W₂和偏置b₂再次变换:z₂ = W₂a₁ + b₂
- 最终输出经过适当的激活函数(如softmax):y = softmax(z₂)
在PyTorch中,这个过程可以简洁地表示为:
python复制import torch.nn as nn
class SimpleNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.fc2 = nn.Linear(hidden_size, output_size)
self.relu = nn.ReLU()
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
return out
1.3 激活函数的作用
激活函数为神经网络引入了非线性能力,使其能够拟合复杂的函数关系。常用的激活函数包括:
- Sigmoid:将输入压缩到(0,1)区间,适合二分类问题的输出层
- Tanh:输出范围(-1,1),相比sigmoid有更好的梯度特性
- ReLU:max(0,x),计算简单且能缓解梯度消失问题,是隐藏层的首选
- LeakyReLU:对负输入给予小的斜率,避免"神经元死亡"问题
实操心得:在隐藏层中,ReLU通常是默认选择。如果遇到训练困难(如大量神经元输出为0),可以尝试LeakyReLU或ELU等变体。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从全连接网络到卷积神经网络
2.1 全连接网络的局限性
虽然全连接网络理论上可以拟合任何函数,但在处理图像等高维数据时存在明显问题:
- 参数爆炸:对于1000×1000像素的RGB图像,输入层到第一个隐藏层的全连接将产生30亿个参数(1000×1000×3×1000,假设隐藏层1000个神经元)
- 平移不变性缺失:全连接网络难以识别图像中平移后的相同特征
- 局部相关性忽略:图像中相邻像素通常高度相关,全连接网络无法有效利用这种局部结构
2.2 卷积神经网络的创新
卷积神经网络(CNN)通过以下机制解决了上述问题:
- 局部连接:每个神经元只与输入数据的局部区域连接
- 权重共享:在不同空间位置使用相同的卷积核
- 池化操作:逐步降低空间分辨率,增加感受野
这些特性使CNN能够:
- 大幅减少参数数量
- 自动学习空间层次特征
- 对平移、旋转、缩放等变化具有一定鲁棒性
2.3 CNN的核心组件
2.3.1 卷积层
卷积操作通过滑动窗口(卷积核)在输入数据上提取局部特征。关键参数包括:
- 核大小(kernel_size):通常3×3或5×5
- 步长(stride):控制滑动步长,影响输出尺寸
- 填充(padding):控制边界处理方式
- 输出通道数(out_channels):决定提取多少种特征
PyTorch实现示例:
python复制import torch.nn as nn
# 输入通道3(RGB),输出通道64,3×3卷积核
conv_layer = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1)
2.3.2 池化层
池化操作通过下采样减少计算量并增加感受野。常用类型:
- 最大池化(MaxPooling):取区域最大值,保留最显著特征
- 平均池化(AvgPooling):取区域平均值,平滑特征
PyTorch实现:
python复制pool_layer = nn.MaxPool2d(kernel_size=2, stride=2)
2.3.3 全连接层
在CNN末端通常会有1-2个全连接层,将学到的特征映射到最终输出空间。现代架构如ResNet已逐渐用全局平均池化替代全连接层。
3. PyTorch实现经典CNN架构
3.1 LeNet-5实现
LeNet-5是最早的实用CNN之一,结构简单但包含CNN所有核心组件:
python复制import torch.nn as nn
class LeNet5(nn.Module):
def __init__(self, num_classes=10):
super(LeNet5, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5),
nn.Tanh(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5),
nn.Tanh(),
nn.AvgPool2d(kernel_size=2, stride=2)
)
self.classifier = nn.Sequential(
nn.Linear(16*4*4, 120),
nn.Tanh(),
nn.Linear(120, 84),
nn.Tanh(),
nn.Linear(84, num_classes)
)
def forward(self, x):
x = self.features(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
注意事项:LeNet最初设计用于MNIST(28×28)数据集,输入尺寸较小。现代数据集通常需要调整网络结构。
3.2 AlexNet实现
AlexNet在2012年ImageNet竞赛中取得突破,引入了ReLU、Dropout等现代技术:
python复制class AlexNet(nn.Module):
def __init__(self, num_classes=1000):
super(AlexNet, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(64, 192, kernel_size=5, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(192, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
)
self.avgpool = nn.AdaptiveAvgPool2d((6, 6))
self.classifier = nn.Sequential(
nn.Dropout(),
nn.Linear(256*6*6, 4096),
nn.ReLU(inplace=True),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Linear(4096, num_classes),
)
def forward(self, x):
x = self.features(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
3.3 ResNet关键创新
ResNet通过残差连接解决了深层网络训练难题:
python复制class BasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(BasicBlock, self).__init__()
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.downsample = downsample
self.stride = stride
def forward(self, x):
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
4. 实战技巧与经验分享
4.1 数据预处理标准化
图像数据通常需要进行标准化处理:
python复制from torchvision import transforms
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], # ImageNet统计值
std=[0.229, 0.224, 0.225])
])
提示:对于自定义数据集,应该计算自己的均值和标准差,而非直接使用ImageNet的统计值。
4.2 学习率调度策略
合理的学习率调度能显著提升模型性能:
python复制from torch.optim import lr_scheduler
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
scheduler = lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
for epoch in range(100):
train(...)
validate(...)
scheduler.step()
4.3 常见问题排查
-
Loss不下降:
- 检查学习率是否合适(太大导致震荡,太小导致收敛慢)
- 确认数据加载和预处理正确
- 检查模型结构是否合理
-
过拟合:
- 增加数据增强
- 添加Dropout层
- 使用权重衰减(L2正则化)
- 简化模型结构
-
GPU内存不足:
- 减小batch size
- 使用混合精度训练
- 梯度累积技术
4.4 模型保存与加载
保存完整模型:
python复制torch.save(model, 'model.pth')
loaded_model = torch.load('model.pth')
保存状态字典(推荐方式):
python复制torch.save(model.state_dict(), 'model_weights.pth')
model.load_state_dict(torch.load('model_weights.pth'))
重要提示:在加载模型时,确保模型结构与保存时完全一致,否则会引发错误。
