深度学习学习经验——卷积神经网络(CNN)

卷积神经网络(CNN)

卷积神经网络(Convolutional Neural Network, CNN)是深度学习中一种非常重要的模型结构,特别擅长处理图像数据。CNN 通过卷积操作和池化操作来自动提取图像的特征,并使用全连接层进行分类或回归等任务。

1. 卷积神经网络的基本组成

CNN 主要由以下几部分组成:

  • 卷积层(Convolutional Layer): 通过卷积操作提取输入数据中的局部特征。
  • 激活函数(Activation Function): 通常使用 ReLU 函数,增加网络的非线性能力。
  • 池化层(Pooling Layer): 通过下采样减少特征图的尺寸,保留重要信息。
  • 全连接层(Fully Connected Layer): 将提取的特征用于分类或回归任务。

2. 卷积操作

定义: 卷积操作是用一个小的卷积核在输入数据上滑动,通过点积计算生成一个新的特征图。卷积核是一个固定大小的权重矩阵,它通过学习能够提取图像的边缘、纹理等特征。

解释: 可以将卷积操作想象成一个“特征探测器”,它在图像上滑动,寻找特定的模式(例如边缘、角点等)。

代码示例:

import torch
import torch.nn as nn

# 定义一个 2D 卷积层
conv_layer = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=1)

# 创建一个示例输入 (1, 1, 5, 5),表示 1 张单通道的 5x5 图像
input_data = torch.tensor([[[[1.0, 2.0, 3.0, 4.0, 5.0],
                             [6.0, 7.0, 8.0, 9.0, 10.0],
                             [11.0, 12.0, 13.0, 14.0, 15.0],
                             [16.0, 17.0, 18.0, 19.0, 20.0],
                             [21.0, 22.0, 23.0, 24.0, 25.0]]]])

# 进行卷积操作
output = conv_layer(input_data)
print(f"卷积层输出: \n{output}")

3. 激活函数(ReLU)

定义: ReLU(Rectified Linear Unit)是一种常用的激活函数,输出为输入值与零的最大值。

解释: ReLU 可以看作是“开启”或“关闭”神经元的开关,只有大于零的值才能通过。

代码示例:

relu = nn.ReLU()

# 使用 ReLU 激活函数
activated_output = relu(output)
print(f"ReLU 激活后的输出: \n{activated_output}")

4. 池化操作

定义: 池化操作通过下采样减少特征图的尺寸,常见的池化方式有最大池化(Max Pooling)和平均池化(Average Pooling)。

解释: 池化操作可以理解为信息压缩,将特征图中的重要信息保留,同时减少数据量和计算量。

代码示例:

pool_layer = nn.MaxPool2d(kernel_size=2, stride=2)

# 进行池化操作
pooled_output = pool_layer(activated_output)
print(f"池化层输出: \n{pooled_output}")

5. 全连接层

定义: 全连接层将池化后的特征图展平为一维向量,并通过线性变换得到最终的输出。它通常用于分类或回归任务。

解释: 全连接层就像是一个决策器,根据提取到的特征做出最终的判断。

代码示例:

flattened_output = pooled_output.view(-1, 1 * 2 * 2)  # 展平
fc_layer = nn.Linear(4, 1)  # 定义全连接层
final_output = fc_layer(flattened_output)
print(f"全连接层输出: {final_output.item()}")

6. 卷积神经网络的完整实现

以下是一个简单的 CNN 的完整实现,它包括一个卷积层、ReLU 激活函数、池化层和全连接层。

import torch
import torch.nn as nn
import torch.nn.functional as F

# 定义一个简单的 CNN
class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
        self.fc1 = nn.Linear(10 * 4 * 4, 1)  # 假设输入图像为8x8大小
    
    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = x.view(-1, 10 * 4 * 4)  # 展平
        x = self.fc1(x)
        return x

# 创建网络实例
net = SimpleCNN()

# 创建示例输入 (batch_size=1, channels=1, height=8, width=8)
input_data = torch.rand(1, 1, 8, 8)

# 进行前向传播
output = net(input_data)
print(f"卷积神经网络输出: {output.item()}")

卷积神经网络(CNN)是图像处理领域中非常强大的工具。它通过卷积层提取局部特征,使用 ReLU 激活函数增加非线性能力,再通过池化层进行信息压缩,最后通过全连接层进行决策。CNN 的层次结构使它能够高效地处理图像数据,并在计算机视觉等领域表现出色。

项目示例

我FCNN那篇文章相同,假设我们要开发一个简单的图像分类器,识别手写数字(比如经典的 MNIST 数据集,获取MNIST数据集的部分请看FCNN那篇文章中这部分内容)。我们将一步一步构建这个项目,并逐步深入讲解 CNN 的各个组成部分。

项目目标

我们将构建一个简单的卷积神经网络,输入是手写数字图片(28x28 像素),输出是图片中数字的类别(0 到 9)。我们将逐步搭建这个网络,并理解每个部分的作用。

1. 加载数据

首先,我们需要加载手写数字数据集。这些图片是灰度图像,每个像素点的值介于 0 和 255 之间。我们将把这些值标准化为 0 到 1 之间,以便更好地训练模型。

import torch
import torchvision
import torchvision.transforms as transforms

# 定义数据变换,将图片转换为张量,并标准化到 [0, 1] 范围
transform = transforms.Compose([transforms.ToTensor(),
                                transforms.Normalize((0.5,), (0.5,))])

# 下载并加载训练数据集
trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)

# 下载并加载测试数据集
testset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)

2. 卷积层:图像的特征探测器

卷积层是 CNN 的核心部分。它通过一个小的卷积核(filter)在图像上滑动,计算局部区域的特征。这个过程可以理解为扫描整张图片,寻找特定的模式(如边缘、角点等)。

2.1 第一个卷积层

我们将定义一个卷积层,输入是 28x28 的图像,输出是一个特征图。这相当于通过一组特征探测器,将图片中的重要信息提取出来。

import torch.nn as nn

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        # 定义第一个卷积层
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=1)
    
    def forward(self, x):
        x = self.conv1(x)
        return x

# 创建模型实例并测试输出
model = SimpleCNN()
sample_input = torch.rand(1, 1, 28, 28)  # 单张28x28的灰度图像
output = model(sample_input)
print(f"卷积层输出形状: {output.shape}")

解释: 卷积层的输出形状是 [1, 16, 28, 28],意味着我们从一张灰度图像(单通道)提取出了 16 个不同的特征图。每个特征图都表示图像的某种特定模式,例如边缘或角点。

3. 激活函数:增加非线性

卷积操作本质上是线性的(即加权求和),但我们知道世界是非线性的。为此,我们使用激活函数(通常是 ReLU),将卷积层的输出映射到非线性空间中,从而使网络能够学习更复杂的模式。

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)
        self.relu = nn.ReLU()  # 添加 ReLU 激活函数
    
    def forward(self, x):
        x = self.relu(self.conv1(x))
        return x

model = SimpleCNN()
output = model(sample_input)
print(f"ReLU 激活后的输出形状: {output.shape}")

解释: ReLU 函数将负值设为零,保留正值。这种激活使得网络能够更好地处理复杂的非线性关系。

4. 池化层:信息压缩器

池化层(通常使用最大池化)用于降低特征图的尺寸,同时保留重要信息。池化操作通过下采样,减少数据量和计算量,并控制过拟合。

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)
        self.relu = nn.ReLU()
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)  # 添加池化层
    
    def forward(self, x):
        x = self.pool(self.relu(self.conv1(x)))  # 先卷积,再ReLU,最后池化
        return x

model = SimpleCNN()
output = model(sample_input)
print(f"池化层后的输出形状: {output.shape}")

解释: 池化层将特征图的尺寸从 28x28 减小到 14x14(因为池化核的大小为 2x2,步长为 2),这意味着每次将相邻的 2x2 像素区域压缩为一个像素点,同时保留了最重要的信息。

5. 多层卷积和池化:逐层提取更高层次的特征

我们可以堆叠多个卷积层和池化层,逐步提取图像的更高层次特征。例如,第一层可能提取边缘特征,第二层提取更复杂的形状,最后一层提取整个图像的抽象特征。

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)  # 添加第二个卷积层
        self.fc1 = nn.Linear(32 * 7 * 7, 128)  # 添加全连接层
    
    def forward(self, x):
        x = self.pool(self.conv1(x))
        x = self.pool(self.conv2(x))
        x = x.view(-1, 32 * 7 * 7)  # 展平为一维向量
        x = self.fc1(x)
        return x

model = SimpleCNN()
output = model(sample_input)
print(f"第二个卷积层后的输出形状: {output.shape}")

解释: 第二个卷积层提取了 32 个更复杂的特征图,并将尺寸进一步缩小到 7x7。最后,我们将这些特征展平成一维向量,为分类器做好准备。

6. 全连接层:分类器

全连接层接收从卷积层传递过来的高层次特征,并将它们映射到分类结果。对于手写数字识别问题,全连接层的输出是 10 个类别(数字 0 到 9)。

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)
        self.fc1 = nn.Linear(32 * 7 * 7, 128)
        self.fc2 = nn.Linear(128, 10)  # 添加输出层,用于分类
    
    def forward(self, x):
        x = self.pool(self.conv1(x))
        x = self.pool(self.conv2(x))
        x = x.view(-1, 32 * 7 * 7)
        x = self.fc1(x)
        x = self.fc2(x)
        return x

model = SimpleCNN()
output = model(sample_input)
print(f"分类器输出形状: {output.shape}")

解释: 最后,全连接层将展平后的特征映射到 10 个类别,模型的最终输出是每个类别的得分(未经过激活的概率)。

7. 模型训练和测试

构建完模型后,我们可以使用交叉熵损失函数(适用于分类任务)和优化器(例如 Adam)来训练模型。训练过程中,模型会不断调整参数,以最小化训练集上的损失,并最终在测试集上进行评估。

import torch.optim as optim

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练模型
for epoch in range(2):  # 简单训练2个周期
    running_loss = 0.0
    for inputs, labels in trainloader:
        optimizer.zero_grad()  # 清空梯度
        outputs = model(inputs)  # 前向传播
        loss = criterion(outputs, labels)  # 计算损失
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数

        running_loss += loss.item()
    print(f"第 {epoch + 1} 轮训练,损失: {running_loss / len(trainloader)}")

# 测试模型
correct = 0
total = 0
with torch.no_grad():
    for inputs, labels in testloader:
        outputs = model(inputs)
        _, predicted = torch.max(otputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f"在测试集上的准确率: {100 * correct / total}%")

在前面的内容中,我逐步讲解了卷积神经网络(CNN)的各个组成部分,并分段展示了代码。现在我将所有步骤的代码汇总成一个完整的项目示例。

完整的项目示例代码

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms

# 1. 数据加载和预处理
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

# 加载训练集
trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)

# 加载测试集
testset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)

# 2. 定义卷积神经网络
class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)  # 第一个卷积层
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)        # 池化层
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)  # 第二个卷积层
        self.fc1 = nn.Linear(32 * 7 * 7, 128)                               # 第一个全连接层
        self.fc2 = nn.Linear(128, 10)                                       # 输出层
    
    def forward(self, x):
        x = self.pool(self.conv1(x))  # 卷积 -> ReLU -> 池化
        x = self.pool(self.conv2(x))  # 卷积 -> ReLU -> 池化
        x = x.view(-1, 32 * 7 * 7)    # 展平为一维向量
        x = self.fc1(x)               # 全连接层1
        x = self.fc2(x)               # 输出层
        return x

# 创建模型实例
model = SimpleCNN()

# 3. 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 4. 训练模型
for epoch in range(2):  # 简单训练2个周期
    running_loss = 0.0
    for inputs, labels in trainloader:
        optimizer.zero_grad()  # 清空梯度
        outputs = model(inputs)  # 前向传播
        loss = criterion(outputs, labels)  # 计算损失
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数

        running_loss += loss.item()
    print(f"第 {epoch + 1} 轮训练,损失: {running_loss / len(trainloader)}")

# 5. 测试模型
correct = 0
total = 0
with torch.no_grad():
    for inputs, labels in testloader:
        outputs = model(inputs)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f"在测试集上的准确率: {100 * correct / total}%")

总结

通过这个项目示例,我们逐步搭建了一个简单的卷积神经网络,从卷积层、激活函数、池化层、到全连接层,最后完成图像分类任务。我们讲解了每个步骤的作用,并通过代码示例展示了如何实现和训练这个模型。卷积神经网络的这种层次化结构,使得它在图像处理任务中非常强大和有效。

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linductor

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值