一、概述

视觉处理三大任务:图像分类、目标检测、图像分割

上游:特征提取,CNN                      下游:分类,目标,分割等

        卷积神经网络是深度学习在计算机视觉领域的突破性成果。它是一种专门用于处理具有网格状结构数据的深度学习模型,是含有卷积层的神经网络. 卷积层的作用就是用来自动学习、提取图像的特征。

        CNN网络主要有三部分构成:卷积层、池化层和全连接层构成,其中卷积层负责提取图像中的局部特征;池化层用来大幅降低运算量并特征增强;全连接层类似神经网络的部分,用来输出想要的结果。

二、卷积层

2.1、卷积核

卷积核是卷积运算过程中必不可少的一个“工具”,在卷积神经网络中,卷积核是非常重要的,它们被用来提取图像中的特征,卷积核其实就是一个矩阵。

3 * 3 卷积核

2.2、卷积计算 

卷积的过程是将卷积核在图像上进行滑动计算,每次滑动到一个新的位置时,卷积核和图像进行点对点的计算,并将其求和得到一个新的值,然后将这个新的值加入到特征图中,最终得到一个新的特征图。

计算:

1x1+1x0+1x1+0x0+1x1+1x0+0x1+0x0+1x1=4 

   

# 面向对象的模块化编程
from matplotlib import pyplot as plt
import os
import torch
import torch.nn as nn


def test001():
    # 使用plt读取图片
    img = plt.imread('./img/彩色.png')
    print(img.shape)
    # 转换为张量:HWC  ---> CHW  ---> NCHW  链式调用
    img = torch.tensor(img).permute(2, 0, 1).unsqueeze(0)
    # 创建卷积核  (501, 500, 4)
    conv = nn.Conv2d(
        in_channels=4,  # 输入通道
        out_channels=32,  # 输出通道
        kernel_size=(5, 3),  # 卷积核大小
        stride=1,  # 步长
        padding=0,  # 填充
        bias=True
    )
    # 使用卷积核对图像进行卷积操作  [9999]  [[[[]]]]
    out = conv(img)

    # 输出128个特征图
    conv2 = nn.Conv2d(
        in_channels=32,  # 输入通道
        out_channels=128,  # 输出通道
        kernel_size=(5, 5),  # 卷积核大小
        stride=1,  # 步长
        padding=0,  # 填充
        bias=True
    )
    out = conv2(out)
    # print(out)
    # 把图像显示出来
    print(out.shape)
    plt.imshow(out[0][10].detach().numpy())
    plt.show()


# 作为主模块执行
if __name__ == "__main__":
    test001()

     执行结果:

2.3、边缘填充

padding  边缘填充可以更好的保护图像边缘数据的特征。

2.4、步长 

stride太小:重复计算较多,计算量大,训练效率降低;

stride太大:会造成信息遗漏,无法有效提炼数据背后的特征;

三、池化层

3.1、概述

池化层 (Pooling) 降低空间维度, 缩减模型大小,提高计算速度。

池化层主要分为两种:

1、最大池化 max pooling

最大池化是从每个局部区域中选择最大值作为池化后的值,这样可以保留局部区域中最显著的特征。

2、平均池化 avgPooling

平均池化是将局部区域中的值取平均作为池化后的值,这样可以得到整体特征的平均值。

特征图的空间维度直接缩小为原图的1/2

3.2、池化层的作用

优势:

  1. 通过降低特征图的尺寸,池化层能够减少计算量,从而提升模型的运行效率。

  2. 池化操作可以带来特征的平移、旋转等不变性,这有助于提高模型对输入数据的鲁棒性。

  3. 池化层通常是非线性操作,例如最大值池化,这样可以增强网络的表达能力,进一步提升模型的性能。

缺点:池化操作会丢失一些信息。

API:

最大池化层:kernel_size=2  卷积核大小为2,stride=2 步长为2

self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)

平均池化层:kernel_size=2  卷积核大小为2,stride=2 步长为2

self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2)

四、卷积知识扩展

4.1、多通道卷积

最平常的彩色图片拥有R,G,B三层通道,所以会分别进行卷积,然后再将这三个通道的卷积结果进行合并。

合并为一个
合并为一个

4.2、膨胀卷积

目的:为了扩大感受野。

 在卷积核的元素之间插入空格“膨胀”内核,形成空洞卷积,并用膨胀率参数L表示要扩大内核的范围,即在内核元素之间插入L-1个空格。当L=1时,内核元素之间没有插入空格,变为标准卷积。当L=2时,内核元素之间插入一个空格,变成空洞卷积。

import torch
import torch.nn as nn

torch.nn.Conv2d(
    in_channels=3,         # 输入通道数
    out_channels=16,       # 输出通道数
    kernel_size=5,         # 卷积核大小
    stride=1,              # 步长
    padding=0,             # 填充
    dilation=1,            # 膨胀率(默认1为标准卷积)
)

API:

dilation=1

4.3、可分离卷积

4.3.1、空间可分离卷积

空间可分离卷积是将卷积核分解为两项独立的核分别进行操作。在数学中我们可以将矩阵分解:

根据数学矩阵的理论我们可以:

import torch
import torch.nn as nn
#可分离卷积
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()


        self.c1 = nn.Conv2d(
            in_channels=1,
            out_channels=1,
            kernel_size=(3,1),
            stride=1,

        )

        self.c2= nn.Conv2d(
            in_channels=1,
            out_channels=1,
            kernel_size=(1, 3),
            stride=1,

        )

    def forward(self, x):

        x = self.c1(x)
        out = self.c2(x)
        return out
#正常卷积
class net(nn.Module):
    def __init__(self):
        super(net, self).__init__()
        self.c1 = nn.Conv2d(
            in_channels=1,
            out_channels=1,
            kernel_size=(3,3),
            stride=1,
        )
    def forward(self, x):
        x = self.c1(x)
        return x
if __name__ == '__main__':
    torch.manual_seed(1)
    input_data = torch.randn(1, 1, 32,32)
    model1 = Net()
    out = model1(input_data)
    model2 = net()
    out1 = model2(input_data)
    print(out.shape)
    print(out1.shape)
4.3.2、深度可分离卷积
 深度可分离卷积由两部分组成:深度卷积核和1 * 1 逐点卷积
先深度卷积,再1 * 1逐点卷积
import torch
import torch.nn as nn
#深度可分离卷积
class DepthwiseSeparableConv(nn.Module):
    def __init__(self):
        super(DepthwiseSeparableConv, self).__init__()
        self.depthwise = nn.Conv2d(
            in_channels=8,
            out_channels=8,
            kernel_size=3,
            stride=1,
            groups=8
        )
        self.pointwise = nn.Conv2d(
            in_channels=8,
            out_channels=8,
            kernel_size=1,
            stride=1,
        )

    def forward(self, x):
        x = self.depthwise(x)
        out = self.pointwise(x)
        return out

#正常卷积
class Conv(nn.Module):
    def __init__(self):
        super(Conv, self).__init__()
        self.conv = nn.Conv2d(
            in_channels=8,
            out_channels=8,
            kernel_size=3,
            stride=1,
         )


    def forward(self, x):
        out = self.conv(x)
        return out


if __name__ == '__main__':
    x = torch.randn(1, 8, 32, 32)
    model = DepthwiseSeparableConv()
    model1 = Conv()
    out = model(x)
    out1 = model1(x)
    print(out.shape)
    print(out1.shape)

4.4、感受野

就是感受的视野范围。

作用:所需的参数更少,卷积的过程更多了,特征提取也会更细致。

五、卷积神经网络案例

实现一个基于全连接层的神经网络,用于 CIFAR-10 图像分类任务。

import torch
from torch import nn,optim
from torchvision import datasets,transforms
from torch.utils.data import DataLoader

#数据增强 随机水平翻转、随机旋转
def prepare_data():
    transform = transforms.Compose(
        [transforms.ToTensor(),
        transforms.Normalize((0.4914,0.4822,0.4465),(0.2023,0.1994,0.2010,)),
         transforms.RandomHorizontalFlip(p=0.5),
         transforms.RandomRotation(10)
    ])
# 测试集仅标准化
    test_transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
    ])

 # 加载CIFAR-10数据集
    train_set = datasets.CIFAR10(
        root='./cifar10',
        train=True,
        transform=transform,
        download=False
    )

    test_set = datasets.CIFAR10(
        root='./cifar10',
        train=False,
        transform=test_transform,
        download=False
    )

# 创建数据加载器

    train_loader = DataLoader(
        dataset=train_set,
        batch_size=64,
        shuffle=True,
        num_workers=4
    )

    test_loader = DataLoader(
        dataset=test_set,
        batch_size=512,
        shuffle=False,
        num_workers=4
    )

    return train_loader,test_loader

#模型架构
class MyNet(nn.Module):
    def __init__(self):
        super(MyNet, self).__init__()
# 输入层 -> 隐藏层1
        self.fc1 = nn.Linear(32 * 32 * 3, 1024)
        self.bn1 = nn.BatchNorm1d(1024)
        self.dropout1 = nn.Dropout(0.3)
 # 隐藏层1 -> 隐藏层2
        self.fc2 = nn.Linear(1024, 512)
        self.bn2 = nn.BatchNorm1d(512)
        self.dropout2 = nn.Dropout(0.3)
 # 隐藏层2 -> 隐藏层3(新增)
        self.fc3 = nn.Linear(512, 256)  # 增加第三层
        self.bn3 = nn.BatchNorm1d(256)

 # 隐藏层3 -> 输出层

        self.fc4 = nn.Linear(256, 10)
        self.relu = nn.ReLU()

    def forward(self, x):
        x = x.view(-1, 32 * 32 * 3)
        x = self.dropout1(self.bn1(self.fc1(x)))
        x = self.relu(x)

        x = self.dropout2(self.bn2(self.fc2(x)))
        x = self.relu(x)
        x = self.bn3(self.fc3(x))
        x = self.relu(x)
        x = self.fc4(x)# 最终输出(logits)
        return x



#训练流程

def train(model, train_loader, epochs, device):
    model.train()
    criterion = nn.CrossEntropyLoss()
    opt = optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-08)
    scheduler = optim.lr_scheduler.ReduceLROnPlateau(opt, mode='min', factor=0.5, patience=10)

    for epoch in range(epochs):
        count = 0
        loss_sum = 0
        for x, y in train_loader:
            # 将数据移到指定设备
            x, y = x.to(device), y.to(device)
# 前向传播 + 反向传播

            y_pred = model(x)
            loss = criterion(y_pred, y)
            opt.zero_grad()
            loss.backward()
            opt.step()
 # 统计准确率
            loss_sum += loss.item()
            _, pred = torch.max(y_pred, dim=1)
            count += (pred == y).sum().item()

        avg_loss = loss_sum / len(train_loader)
        acc = count / len(train_loader.dataset)
        print(f"Epoch [{epoch + 1}/{epochs}], Loss: {avg_loss:.4f}, Acc: {acc:.4f}")
     
        # 学习率调度
        scheduler.step(avg_loss)

#评估流程
def eval(model, test_loader, device):
    model.eval()
    eval_loss = 0
    count = 0
    criterion = nn.CrossEntropyLoss()

    with torch.no_grad(): # 关闭梯度计算,加速推理
        for x, y in test_loader:
            # 将数据移到指定设备
            x, y = x.to(device), y.to(device)
            y_pred = model(x)

    # 计算损失和准确率

            eval_loss += criterion(y_pred, y).item()
            _, pred = torch.max(y_pred, dim=1)
            count += (pred == y).sum().item()

    eval_loss /= len(test_loader)
    acc = 100 * count / len(test_loader.dataset)
    print(f"Test Loss: {eval_loss:.4f}, Acc: {acc:.4f}")
#整体运行逻辑
if __name__ == '__main__':
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    print(f"Using device: {device}")
    model = MyNet().to("device")
    train_loader,test_loader = prepare_data()
#训练模型
    train(model,train_loader,epochs=25,device=device)
#评估模型
    eval(model,test_loader,device=device)

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐