引言:当计算机学会"看"世界
想象一下,你只需看一眼就能区分猫和狗、识别人脸、读懂路标。这种看似简单的视觉能力,对计算机来说曾经是巨大的挑战。直到卷积神经网络(CNN)的出现,才真正让计算机获得了"视觉智能"。今天,CNN已成为计算机视觉的基石,从手机人脸解锁到自动驾驶,无处不在。
一、CNN的核心思想:从生物视觉到数学抽象
1.1 灵感来源:人类视觉系统
CNN的设计灵感直接来源于人类视觉皮层的研究。Hubel和Wiesel在1950年代发现,视觉皮层中的神经元对特定区域的视觉刺激最为敏感------这一发现为他们赢得了诺贝尔奖,也为CNN奠定了生物学基础。
```python
一个简单的类比:人类视觉如何工作
import numpy as np
人类看到图像时的处理层次:
1. 边缘检测(初级视觉皮层)
2. 形状识别(中级视觉皮层)
3. 对象识别(高级视觉皮层)
这与CNN的层次结构惊人地相似:
1. 卷积层 - 检测边缘和纹理
2. 池化层 - 抽象特征
3. 全连接层 - 分类识别
```
1.2 卷积操作:CNN的数学心脏
卷积的核心思想是局部连接和权值共享,这与传统的全连接神经网络形成鲜明对比。
```python
import numpy as np
import matplotlib.pyplot as plt
手动实现一个简单的2D卷积操作
def simple_convolution(image, kernel):
"""演示卷积的基本计算过程"""
image_height, image_width = image.shape
kernel_height, kernel_width = kernel.shape
计算输出特征图的尺寸
output_height = image_height - kernel_height + 1
output_width = image_width - kernel_width + 1
初始化输出特征图
output = np.zeros((output_height, output_width))
执行卷积操作
for i in range(output_height):
for j in range(output_width):
region = image[i:i+kernel_height, j:j+kernel_width]
output[i, j] = np.sum(region * kernel)
return output
创建一个简单的测试图像(包含垂直线条)
test_image = np.array([
1, 0, 1, 0, 1\], \[1, 0, 1, 0, 1\], \[1, 0, 1, 0, 1\], \[1, 0, 1, 0, 1\], \[1, 0, 1, 0, 1
])
定义一个垂直边缘检测卷积核
vertical_kernel = np.array([
1, 0, -1\], \[1, 0, -1\], \[1, 0, -1
])
执行卷积
result = simple_convolution(test_image, vertical_kernel)
print("卷积结果(检测垂直边缘):")
print(result)
```
二、CNN的经典架构:从LeNet到ResNet
2.1 LeNet-5:CNN的鼻祖
Yann LeCun在1998年提出的LeNet-5是第一个成功的CNN应用,用于手写数字识别。
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
LeNet-5的PyTorch实现
class LeNet5(nn.Module):
"""经典的LeNet-5架构"""
def init(self, num_classes=10):
super(LeNet5, self).init()
特征提取部分
self.conv1 = nn.Conv2d(1, 6, kernel_size=5, padding=2) # 输入1通道,输出6通道
self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2) # 平均池化
self.conv2 = nn.Conv2d(6, 16, kernel_size=5) # 第二个卷积层
self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2)
分类部分(全连接层)
self.fc1 = nn.Linear(16 * 5 * 5, 120) # 展平后连接
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, num_classes)
def forward(self, x):
卷积层 + 激活函数(原始LeNet使用tanh,这里用ReLU)
x = F.relu(self.conv1(x))
x = self.pool1(x)
x = F.relu(self.conv2(x))
x = self.pool2(x)
展平特征图
x = x.view(x.size(0), -1)
全连接层
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
测试LeNet-5
def test_lenet():
创建一个模拟输入(批次大小=4,通道=1,高度=32,宽度=32)
input_tensor = torch.randn(4, 1, 32, 32)
初始化模型
model = LeNet5()
前向传播
output = model(input_tensor)
print(f"输入形状: {input_tensor.shape}")
print(f"输出形状: {output.shape}")
print(f"预测类别数: {output.shape[1]}")
return model
model = test_lenet()
```
2.2 VGG16:深度与规范化的力量
VGG16通过使用连续的3×3小卷积核堆叠深度网络,展示了"深度"对性能的重要性。
```python
VGG16的简化实现
class VGGBlock(nn.Module):
"""VGG的基本构建块"""
def init(self, in_channels, out_channels, num_convs):
super(VGGBlock, self).init()
layers = []
for i in range(num_convs):
layers.append(nn.Conv2d(in_channels if i == 0 else out_channels,
out_channels,
kernel_size=3,
padding=1))
layers.append(nn.ReLU(inplace=True))
layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
self.block = nn.Sequential(*layers)
def forward(self, x):
return self.block(x)
class SimpleVGG16(nn.Module):
"""简化的VGG16架构"""
def init(self, num_classes=1000):
super(SimpleVGG16, self).init()
卷积部分:13个卷积层
self.features = nn.Sequential(
Block 1: 2个卷积层
VGGBlock(3, 64, 2),
Block 2: 2个卷积层
VGGBlock(64, 128, 2),
Block 3: 3个卷积层
VGGBlock(128, 256, 3),
Block 4: 3个卷积层
VGGBlock(256, 512, 3),
Block 5: 3个卷积层
VGGBlock(512, 512, 3),
)
全连接部分
self.classifier = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096), # 假设输入图像为224x224
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(4096, num_classes),
)
def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), -1) # 展平
x = self.classifier(x)
return x
计算VGG16的参数数量
def calculate_parameters(model):
total_params = sum(p.numel() for p in model.parameters())
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"总参数数量: {total_params:,}")
print(f"可训练参数: {trainable_params:,}")
return total_params, trainable_params
测试
vgg = SimpleVGG16(num_classes=10)
total_params, _ = calculate_parameters(vgg)
print(f"VGG16参数量是LeNet-5的{total_params/60000:.1f}倍")
```
2.3 ResNet:解决梯度消失的突破
残差网络通过跳跃连接解决了深度网络中的梯度消失问题,使得训练数百甚至上千层的网络成为可能。
```python
残差块的基本实现
class ResidualBlock(nn.Module):
"""基本的残差块"""
def init(self, in_channels, out_channels, stride=1, downsample=None):
super(ResidualBlock, self).init()
主路径
self.conv1 = nn.Conv2d(in_channels, out_channels,
kernel_size=3, stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(out_channels, out_channels,
kernel_size=3, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
跳跃连接
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)
残差连接:F(x) + x
out += identity
out = self.relu(out)
return out
class SimpleResNet(nn.Module):
"""简化的ResNet-18"""
def init(self, block, layers, num_classes=10):
super(SimpleResNet, self).init()
self.in_channels = 64
初始卷积层
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)