深度学习blog-卷积神经网络(CNN)

发布于:2025-02-11 ⋅ 阅读:(47) ⋅ 点赞:(0)

卷积神经网络(Convolutional Neural Network,CNN)是一种广泛应用于计算机视觉领域,如图像分类、目标检测和图像分割等任务中的深度学习模型。

1. 结构
卷积神经网络一般由以下几个主要层组成:
输入层:接收原始图像数据,通常是三维(高、宽、通道)的张量。
卷积层(Convolutional Layer):使用多个卷积核(滤波器)对输入数据进行卷积操作,提取特征。该层的输出是特征图,显示了输入数据中的特征。卷积层是 CNN 的核心组成部分,它的主要功能是通过卷积操作提取局部特征。
在这里插入图片描述

卷积操作是通过一个小的滤波器(或卷积核)在输入图像上滑动来计算的,每次滑动时,卷积核与局部区域的像素值做点积运算,并输出一个新的值。这些新值组成了特征图(feature map)。

步长指定卷积核在输入数据上滑动的步伐。
填充(Padding)
填充是为了确保卷积操作不会丢失边缘信息,通常会在输入数据的边缘添加一些零值,称为零填充。
激活层(Activation Layer):常用的激活函数包括ReLU(修正线性单元)等,负责引入非线性因素,提高网络学习能力。通常放在卷积层之后。
池化层(Pooling Layer):对特征图进行下采样,通常使用最大池化或平均池化,减少特征的尺寸,降低计算复杂度,同时保留重要特征。避免过拟合。
常见的池化操作有最大池化和平均池化
最大池化(Max Pooling),对每个子区域选择最大值。
平均池化(Average Pooling),对每个子区域取平均值。

全连接层(Fully Connected Layer):将高层次的特征输出转换为最终的分类结果。每个神经元与前一层的所有神经元相连接。(将提取的高维特征映射到标签空间)

**输出层:**提供最终的预测结果,比如分类标签或回归值。

**pooling池化的作用:**体现在降采样:保留显著特征、降低特征维度,增大kernel的感受野。另外一点值得注意:pooling也可以提供一些旋转不变性。池化层可对提取到的特征信息进行降维,一方面使特征图变小,简化网络计算复杂度并在一定程度上避免过拟合的出现;一方面进行特征压缩,提取主要特征。

padding的理解:保持边界信息,如果没有加padding的话,输入图片最边缘的像素点信息只会被卷积核操作一次,但是图像中间的像素点会被扫描到很多遍。

  1. 原理
    卷积神经网络的核心原理是利用卷积操作进行特征提取。卷积层通过卷积核在输入图像上滑动,不断提取局部区域的特征,能够自动学习并优化这些特征。
    卷积操作:通过卷积核与输入图像的局部区域进行点积,生成特征图。这个过程能够捕捉图像中的边缘、角点等基础特征。
    参数共享:同一个卷积核在整个图像上重复使用,可以减少模型参数,提高模型的泛化能力。
    局部感知:卷积核的大小限制了每个神经元的感知范围,使网络能学习到局部特征。

  2. 工作流程
    卷积神经网络的工作流程通常包括以下几个步骤:
    图像输入:将图像数据输入到网络中。
    特征提取:
    在卷积层中,通过多个卷积核对输入图像进行卷积,生成特征图。
    通过激活函数引入非线性。
    使用池化层进行特征降维。
    分类阶段:
    将经过多层特征提取后的特征图展平成一维向量,输入到全连接层。
    使用激活函数进行处理。
    损失计算:通过损失函数计算预测值与真实值之间的误差。
    反向传播:通过反向传播算法更新网络中的权重和偏置,以最小化损失。
    预测输出:经过最后的输出层,网络给出分类结果或回归输出。

输入张量的形状
假如输入张量的形状为 (1, 3, 640, 640)。
1:批量大小(batch size),表示一次处理1张图片。
3:通道数,表示输入的图片是彩色(RGB),具有3个通道。
640:图片的高度。
640:图片的宽度。

卷积核的形状
假如卷积核的形状是 (16, 3, 3, 3)。
16:表示该卷积层将输出16个特征图(feature maps)。
3:表示输入通道数,与输入的通道数相同(3个RGB通道)。
3,3:卷积核的高度和宽度(3x3的大小)。
卷积层中输出通道数 16 代表你期望从输入中提取到的不同特征的数量。配置卷积核时,可以指定任意数量的输出通道,这通常根据模型设计的目标、复杂性以及要捕捉的特征类型进行调整。

步长(Stride)
步长为2,表示卷积操作在每次移动时,移动2个像素。

填充(Padding)
我们将假定没有填充(有效填充)。

输出形状的计算
输出形状可以使用以下公式计算:
在这里插入图片描述
在这里插入图片描述

最终输出形状
结合上面的计算,输出的形状为 (1, 16, 320, 320),其中:

1 是批量大小(batch size)。
16 是卷积层输出的特征图数量。
320 是输出的高度。
320 是输出的宽度。

如何计算输出张量
下面给出计算输出张量的详细步骤:

1、每个卷积核的运算:
当进行卷积操作时,每个卷积核将与输入的3个通道相乘并求和。在开放的卷积核中,一个卷积核实际上是一个形状为 (3, 3, 3) 的张量,表示一个深度为3的3x3核。所以,每个输出通道都是通过对输入中的3个通道应用各自的卷积核并求和得到的。

2、输出通道的数量:
在卷积计算过程中,输入的3个通道通过16个不同的卷积核,最终得到16个输出特征图。这个过程可以理解为对输入图像的不同特征进行学习和提取。因此,虽然输入只有3个通道,但我们定义了16个卷积核,从而生成16个输出通道。

3、激活函数:
在YOLO等模型中,卷积后的输出通常还会通过非线性激活函数,像Sigmoid、ReLU等,以增加网络的表达能力。Sigmoid函数的使用通常是为了将输出值压缩到(0, 1)之间,在某些特定情况下(如二分类)很有用。
有效填充计算输出形状公式

卷积核的数量(输出通道数)是一个可配置的参数。卷积层输出的通道数,实际就是卷积核的个数,那每个卷积核里的元素怎么来的?
在使用 nn.Conv2d 时,每个卷积核的元素通常是在创建卷积层时自动初始化的。默认情况下,PyTorch 使用 Xavier (Glorot) 初始化或均匀分布随机初始化卷积核的权重。这些权重是通过随机生成的,旨在帮助网络训练时收敛。

torch.nn.Conv2d(  
    in_channels,  
    out_channels,  
    kernel_size,  
    stride=1,  
    padding=0,  
    dilation=1,  
    groups=1,  
    bias=True  
)

in_channels: 输入通道数(例如 RGB 图像为 3)。
out_channels: 输出通道数(卷积核的数量)。
kernel_size: 卷积核的大小(可为单个数或元组)。
stride: 步幅,卷积核在输入数据上滑动的步长。
padding: 填充,在输入数据的边缘添加额外的像素以控制输出尺寸。
dilation: 膨胀系数,影响卷积核的起始和结束位置。
groups: 组卷积的数量,通常为1,但可以设置为 >1 来实现深度可分离卷积。
bias: 是否使用偏置项,默认为 True。

正向传播计算过程
当输入数据通过 nn.Conv2d 时,PyTorch 会执行以下步骤:

1、输入张量形状:
输入张量的形状通常为 (N, C_{in}, H, W),其中:
N: 批量大小
C_{in}: 输入通道数
H: 输入高度
W: 输入宽度

2、计算输出形状:
输出形状可以通过以下公式计算:
在这里插入图片描述
3、卷积操作:
每个卷积核会在整个输入特征图上滑动(根据步幅),在每个位置计算核与输入的点积。
输出通道中每个值是通过将卷积核在输入的对应覆盖区域和相应像素的乘积相加得到的。

import torch  
import torch.nn as nn  

# 创建随机输入张量,形状为 (1, 3, 640, 640)  
input_tensor = torch.rand(1, 3, 640, 640)  

# 定义卷积层,输出通道为16,卷积核为3x3,步长为2  
conv_layer = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=(3, 3), stride=2)  

# 计算输出  
output_tensor = conv_layer(input_tensor)  

# 输出形状  
print("输出形状:", output_tensor.shape)  # 应输出 (1, 16, 320, 320)

深入理解卷积核的具体计算

import torch  

# 示例输入张量 (2, 2, 5) 代表一个2通道5x5的图像  
input_tensor = torch.tensor([  
    [[1, 2, 3, 0, 1],  
     [0, 1, 2, 3, 0],  
     [2, 3, 0, 1, 2],  
     [1, 0, 2, 2, 1],  
     [0, 1, 0, 1, 2]],  

    [[5, 0, 1, 2, 5],  
     [3, 1, 0, 0, 1],  
     [2, 3, 1, 1, 0],  
     [0, 2, 3, 1, 0],  
     [0, 0, 1, 2, 3]]  
])  

# 示例卷积核  
weight = torch.tensor([  
    [[0.1, 0.2, 0.1],  
     [0.5, 0.1, 0.0],  
     [0.1, 0.3, 0.0]],  

    [[0.2, 0.1, 0.0],  
     [0.0, 0.0, 0.1],  
     [0.2, 0.1, 0.0]],  

    [[0.1, 0.0, 0.0],  
     [0.1, 0.2, 0.1],  
     [0.1, 0.0, 0.1]]  
])  

# 初始化卷积核的权重  
conv_layer = nn.Conv2d(2, 3, kernel_size=(3,3))  
conv_layer.weight.data = weight.unsqueeze(0)  

# 进行卷积  
output = conv_layer(input_tensor.unsqueeze(0).float())  

print("输出的结果:", output)

卷积核中的数据确实是模型在训练过程中要学习的内容。这些权重的值是在训练过程中通过反向传播算法不断更新的,以使模型能够更好地适应训练数据并正确地进行预测。下面是一些关于卷积核及其学习内容的详细解释:

  1. 卷积核的作用
    卷积核(或过滤器)用于提取输入数据中的特征。在每次卷积操作中,卷积核会在输入特征图上滑动,不断计算出局部区域的加权和。这些卷积核的作用是:

特征提取: 卷积核能够检测图像中的局部模式,例如边缘、角点、纹理等。通过在多个卷积层中堆叠和组合,CNN 可以提取越来越复杂的特征。

降低维度: 通过提取关键信息,卷积同时也可以降低输入数据的维度和复杂性,实现更高层次的抽象。

  1. 卷积核中的数据(权重)
    可学习参数: 在模型的训练过程中,卷积核的权重是可学习的参数。这些权重初始化为随机值,在训练过程中通过优化算法(如SGD、Adam等)不断更新。每个卷积核都由许多权重值组成,具体数量取决于卷积核的大小和输入通道数。

优化过程: 使用反向传播算法,计算损失函数相对于卷积核权重的梯度,然后根据这些梯度更新权重。通过多次迭代,卷积核将学习到如何更好地提取输入数据的特征。

  1. 权重的初始化和学习
    初始化: 在许多深度学习框架中(如PyTorch),卷积层的权重默认使用某种初始化方法(比如 Xavier 或 Kaiming 初始化)。这些方法旨在让网络在训练开始时更快地收敛。

训练过程中的变化: 通过训练,卷积核的权重将根据模型看到的训练样本逐渐调整,学习到适合特定任务的特征。例如,在人脸识别任务中,卷积核可能最初学习到的是边缘和角点,随后学习到更复杂的形状,最终细化到能够识别特定个体脸部特征的权重。

  1. 不同卷积层的不同特征
    低层卷积层: 学习到简单特征(如边缘、纹理)。
    中层卷积层: 学习到组合的特征(如形状、部件)。
    高层卷积层: 学习到高层次的抽象概念(如物体类别)。
  2. 总结
    卷积核中的数据(权重)确实是模型要训练学习的内容,而这些权重在训练中会不断地被调整,以便更好地适应输入数据并执行特定的任务。通过这种方式,卷积神经网络能够从原始输入(例如图像)中学习到丰富的特征表示,以便最终可以用于分类、检测或其他机器学习任务。

例子,识别手写数字:

import numpy as np
import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt

# 加载 MNIST 数据集
(x_train, y_train), (x_test, y_test) = datasets.mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
x_train = x_train.reshape((x_train.shape[0], 28, 28, 1))
x_test = x_test.reshape((x_test.shape[0], 28, 28, 1))
y_train = tf.keras.utils.to_categorical(y_train, 10)
y_test = tf.keras.utils.to_categorical(y_test, 10)

# 构建 CNN 模型
model = models.Sequential([
    layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
    layers.MaxPooling2D((2, 2)),
    layers.Conv2D(64, (3, 3), activation='relu'),
    layers.MaxPooling2D((2, 2)),
    layers.Conv2D(64, (3, 3), activation='relu'),
    layers.Flatten(),
    layers.Dense(64, activation='relu'),
    layers.Dropout(0.15),
    layers.Dense(10, activation='softmax')  # 10 类输出
])

# 编译模型
model.compile(optimizer='adam',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# 训练模型
history = model.fit(x_train, y_train, epochs=5, batch_size=64, validation_data=(x_test, y_test))

# 在测试集上评估模型
test_loss, test_acc = model.evaluate(x_test, y_test)
print(f"Test accuracy: {test_acc:.4f}")

# 随机选择一些测试图像的索引
num_images = 10
random_indices = np.random.choice(x_test.shape[0], num_images, replace=False)

test_images = x_test[random_indices]
true_labels = np.argmax(y_test[random_indices], axis=1)
predicted_labels = np.argmax(model.predict(test_images), axis=1)

plt.figure(figsize=(12, 4))
for i in range(num_images):
    plt.subplot(2, 5, i + 1)
    plt.imshow(test_images[i].reshape(28, 28), cmap='gray')
    plt.title(f"True: {true_labels[i]}\nPred: {predicted_labels[i]}")
    plt.axis('off')
plt.show()


# 绘制训练过程中的准确率和损失
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Training Accuracy')
plt.plot(history.history['val_accuracy'], label='Validation Accuracy')
plt.legend()
plt.title('Training and Validation Accuracy')

在这里插入图片描述
pytorch实现:

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
from torchvision import datasets

# 加载 MNIST 数据集
transform = transforms.Compose([
    transforms.ToTensor(),  # 转换为 tensor,并归一化为 [0, 1] 区间
])
train_dataset = datasets.MNIST(root='../../data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='../../data', train=False, download=True, transform=transform)

# 数据加载器
train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

# 构建 CNN 模型
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)  # 28x28x1 -> 28x28x32
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)  # 下采样
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)  # 28x28x32 -> 28x28x64
        self.conv3 = nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1)  # 28x28x64 -> 28x28x64
        self.fc1 = nn.Linear(64 * 7 * 7, 64)  # 根据池化后特征的形状计算输入大小
        self.dropout = nn.Dropout(0.15)  # Dropout 层
        self.fc2 = nn.Linear(64, 10)  # 输出10类

    def forward(self, x):
        x = self.pool(torch.relu(self.conv1(x)))  # Conv1 + ReLU + Pooling
        x = self.pool(torch.relu(self.conv2(x)))  # Conv2 + ReLU + Pooling
        x = torch.relu(self.conv3(x))               # Conv3 + ReLU
        x = x.view(-1, 64 * 7 * 7)  # 展平
        x = torch.relu(self.fc1(x))  # FC1 + ReLU
        x = self.dropout(x)           # Dropout
        x = self.fc2(x)               # FC2
        return x

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

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

# 训练模型
num_epochs = 5
for epoch in range(num_epochs):
    model.train()
    for batch_images, batch_labels in train_loader:
        optimizer.zero_grad()  # 梯度清零
        outputs = model(batch_images)  # 前向传播
        loss = criterion(outputs, batch_labels)  # 计算损失
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数
    print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')

# 在测试集上评估模型
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
    for batch_images, batch_labels in test_loader:
        outputs = model(batch_images)  # 前向传播
        loss = criterion(outputs, batch_labels)  # 计算损失
        test_loss += loss.item()  # 累加损失
        _, predicted = torch.max(outputs.data, 1)  # 预测
        correct += (predicted == batch_labels).sum().item()  # 统计正确样本数

# 计算准确率
test_accuracy = correct / len(test_dataset)
print(f"Test accuracy: {test_accuracy:.4f}")

# 随机选择一些测试图像的索引并可视化
num_images = 10
random_indices = np.random.choice(len(test_dataset), num_images, replace=False)

test_images = []
true_labels = []
predicted_labels = []

for idx in random_indices:
    img, label = test_dataset[idx]
    test_images.append(img)
    true_labels.append(label)

test_images_tensor = torch.stack(test_images)
with torch.no_grad():
    outputs = model(test_images_tensor)  # 前向传播
    _, predicted = torch.max(outputs.data, 1)  # 预测
    predicted_labels = predicted.numpy()

# 可视化结果
plt.figure(figsize=(12, 4))
for i in range(num_images):
    plt.subplot(2, 5, i + 1)
    plt.imshow(test_images[i].numpy()[0], cmap='gray')  # 仅显示通道1
    plt.title(f"True: {true_labels[i]}\nPred: {predicted_labels[i]}")
    plt.axis('off')
plt.show()

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到