当前位置: 首页 > news >正文

初识CNN02——认识CNN2

系列文章目录

初识CNN01——认识CNN


文章目录

  • 系列文章目录
  • 一、卷积知识扩展
    • 1.1 反卷积
    • 1.2 膨胀卷积
    • 1.3 可分离卷积
    • 1.4 扁平卷积
    • 1.5 分组卷积
    • 1.6 感受野
  • 二、卷积神经网络案例
    • 2.1 模型定义
    • 2.2 数据加载
    • 2.3 模型训练及保存
    • 2.4 模型加载及验证
    • 2.5 数据增强
    • 2.6 完整案例
  • 总结


一、卷积知识扩展

复习一下之前对于卷积的理解,其本质就是对图片的特征进行提取。
<img src=https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fmyfirstjava.oss-cn-beijing.aliyuncs.com%2FCSDN%2F%25E5%258D%25B7%25E7%25A7%25AF%25E8%25BF%2587%25E7%25A8%258B3.gif&pos_id=img-Yrp5UK8i-1755258868407)>

1.1 反卷积

卷积是对输入图像及进行特征提取,这样会导致尺寸会越变越小,而反卷积是进行相反操作。并不会完全还原到跟输入图一样,只是保证了与输入图像尺寸一致,主要用于向上采样。从数学上看,反卷积相当于是将卷积核转换为稀疏矩阵后进行转置计算。也被称为转置卷积。

  • 计算过程

如图,在2x2的输入图像上使用【步长1、边界全0填充】的3x3卷积核,进行转置卷积(反卷积)计算,向上采样后输出的图像大小为4x4
在这里插入图片描述
在语义分割里面就需要反卷积还原到原始图像大小。

具体计算过程如下:
在这里插入图片描述
反卷积通过以下步骤将小特征图放大:

  • 零填充:在输入特征值之间或边缘插入零。
  • 标准卷积:使用卷积核在填充后的特征图上滑动。
  • 输出放大:通过步长控制输出尺寸。

1.2 膨胀卷积

为扩大感受野,在卷积核的元素之间插入空格“膨胀”内核,形成空洞卷积,并用膨胀率参数L表示要扩大内核的范围,即在内核元素之间插入L-1个空格。当L=1时,内核元素之间没有插入空格,变为标准卷积。图中是L=2的空洞卷积。
在这里插入图片描述
膨胀卷积通过在每层增加输入之间的间距,使得感受野呈指数增长,从而在不增加太多层数的前提下,捕获长距离依赖。具体实现上,膨胀卷积通过设置不同的膨胀率(dilation rate)在卷积核中填充0元素,使卷积核膨胀到相应的尺寸,从而达到扩大卷积核感受野的目的。

示例:

# 创建输入张量 
x = torch.randn(1, 1, 8, 8)# 膨胀卷积 (dilation=2)
dilated_conv = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, dilation=2
)# 应用膨胀卷积
output = dilated_conv(x)
print(f"输入尺寸: {x.shape}\n输出尺寸: {output.shape}")

1.3 可分离卷积

  • 空间可分离卷积
    空间可分离卷积是将卷积核分解为两项独立的核分别进行操作。在数学中我们可以将矩阵分解:
    [−101−202−101]=[121]×[−101]\left[\begin{matrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{matrix}\right]=\left[\begin{matrix} 1 \\ 2 \\ 1 \end{matrix}\right]\times\left[\begin{matrix} -1 & 0 & 1 \end{matrix}\right] 121000121=121×[101]
    所以对3x3的卷积核,我们同样可以拆分成 3x1 和 1x3 的两个卷积核,对其进行卷积,且采用可分离卷积的计算量比标准卷积要少。
    在这里插入图片描述
  • 深度可分离卷积
    深度可分离卷积由两部组成:深度卷积核1×11\times11×1卷积,我们可以使用Animated AI官网的图来演示这一过程。

如图,输入图的每一个通道,我们都使用了对应的卷积核进行卷积。 通道数量 = 卷积核个数,每个卷积核只有一个通道。
<img src=https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fmyfirstjava.oss-cn-beijing.aliyuncs.com%2FCSDN%2F20240521153330.gif&pos_id=img-ziokaNZz-1755258868683)>

完成卷积后,对输出内容进行1x1的卷积点卷积做通道间的特征融合。
<img src=https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fmyfirstjava.oss-cn-beijing.aliyuncs.com%2FCSDN%2Fdepthwise-separable-convolution-animation-3x3-kernel.gif&pos_id=img-mBBkQl92-1755258868885)>

示例:

# 创建输入张量 (3通道, 16x16)
x = torch.randn(1, 3, 16, 16)# 深度可分离卷积 = 深度卷积 + 逐点卷积
depthwise = nn.Conv2d(3, 3, kernel_size=3, groups=3)  # 深度卷积
pointwise = nn.Conv2d(3, 16, kernel_size=1)           # 逐点卷积# 应用可分离卷积
output = pointwise(depthwise(x))
print(f"输入尺寸: {x.shape}\n输出尺寸: {output.shape}")

1.4 扁平卷积

扁平卷积是将标准卷积拆分成为3个1x1的卷积核,然后再分别对输入层进行卷积计算。
在这里插入图片描述

  • 标准卷积参数量XYC,计算量为MNCXY
  • 拆分卷积参数量(X+Y+C),计算量为MN(C+X+Y)

示例:

# 创建输入张量 (128通道, 14x14)
x = torch.randn(1, 128, 14, 14)# 1x1卷积 (通道降维)
conv1x1 = nn.Conv2d(128, 32, kernel_size=1)# 应用1x1卷积
output = conv1x1(x)
print(f"输入尺寸: {x.shape}\n输出尺寸: {output.shape}")

1.5 分组卷积

2012年,AlexNet论文中最先提出来的概念,当时主要为了解决GPU显存不足问题,将卷积分组放到两个GPU中并行执行。在分组卷积中,卷积核被分成不同的组,每组负责对相应的输入层进行卷积计算,最后再进行合并。

下图中卷积核被分成两个组,前半部负责处理前半部的输入层,后半部负责后半部的输入层,最后将结果组合。
在这里插入图片描述
分组卷积中:

  • 输入通道被划分为若干组。
  • 每组通道只与对应的卷积核计算。
  • 不同组之间互相独立,卷积核不共享。

深度卷积是分组卷积的特例。当分组卷积的分组数 groups = in_channels 时,就变成了深度卷积。

混洗分组卷积
分组卷积中最终结果会按照原先的顺序进行合并组合,阻碍了模型在训练时特征信息在通道间流动,削弱了特征表示。混洗分组卷积,主要是将分组卷积后的计算结果混合交叉在一起输出。
在这里插入图片描述

1.6 感受野

字面意思是感受的视野范围。指的是卷积神经网络中,某一层特征图上的一个像素点所对应输入图像上的区域大小。感受野描述了特征图上某个位置能够“看到”原始输入图像中的多大范围。 它反映了该位置的特征是由输入图像中多大范围的像素信息计算而来的。下图为感受野的直观表示。
在这里插入图片描述
上图堆叠了2个3 x 3的卷积层,并且保持滑动窗口步长为1,容易得出其感受野就是5×5, 这跟一个使用5x5卷积核的结果是一样的,那为什么非要堆叠多个个小卷积呢?

试想一下,假设将图片输入大小都是h × w × C,并且都使用C个卷积核(得到C个特征图),其中一个用3 x 3的卷积三次,另一个用7 x 7卷积一次,可以来计算一下其各自所需参数。
在这里插入图片描述
很明显,堆叠小的卷积核所需的参数更少一些,并且卷积过程越多,特征提取也会越细致,加入的非线性变换也随着增多,还不会增大权重参数个数,用小的卷积核来完成体特征提取操作。

二、卷积神经网络案例

2.1 模型定义

代码:

import torch
import torch.nn as nnclass ImageClassification(nn.Module):def __init__(self):super(ImageClassification, self).__init__()# 这是一层卷积层self.layer1 = nn.Sequential(nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1),nn.ReLU(),nn.MaxPool2d(kernel_size=2, stride=2),nn.Dropout(0.25),)self.layer2 = nn.Sequential(nn.Conv2d(in_channels=32, out_channels=128, kernel_size=3, stride=1),nn.ReLU(),nn.MaxPool2d(kernel_size=2, stride=2),nn.Dropout(0.25),)self.linear1 = nn.Sequential(nn.Linear(128 * 6 * 6, 2048), nn.ReLU(), nn.Dropout(0.5))self.linear2 = nn.Sequential(nn.Linear(2048, 1024), nn.ReLU(), nn.Dropout(0.5))self.out = nn.Linear(1024, 10)def forward(self, x):x = self.layer1(x)x = self.layer2(x)x = x.reshape(x.size(0), -1)x = self.linear1(x)x = self.linear2(x)return self.out(x)

对应的网络结构如下:
在这里插入图片描述

  1. 输入形状: 32x32
  2. 第一个卷积层输入 3 个 Channel, 输出 6 个 Channel, Kernel Size 为: 3x3
  3. 第一个池化层输入 30x30, 输出 15x15, Kernel Size 为: 2x2, Stride 为: 2
  4. 第二个卷积层输入 6 个 Channel, 输出 16 个 Channel, Kernel Size 为 3x3
  5. 第二个池化层输入 13x13, 输出 6x6, Kernel Size 为: 2x2, Stride 为: 2
  6. 第一个全连接层输入 576 维, 输出 120 维
  7. 第二个全连接层输入 120 维, 输出 84 维
  8. 最后的输出层输入 84 维, 输出 10 维

我们在每个卷积计算之后应用 relu 激活函数来给网络增加非线性因素。

2.2 数据加载

这里使用的CIFAR10数据源:

def get_data():dir = os.path.dirname(__file__)# 加载数据集train = CIFAR10(root=os.path.join(dir, "data"),train=True,download=True,transform=Compose([ToTensor()]),)test= CIFAR10(root=os.path.join(dir, "data"),train=False,download=True,transform=Compose([ToTensor()]),)# 观察一下数据集信息print("训练数据集数量:", train.__len__())# 观察一下数据集分类情况print("训练数据集分类情况:", train.class_to_idx)train_loader = DataLoader(train, batch_size=128, shuffle=True)test_loader = DataLoader(test, batch_size=128, shuffle=True)return train_loader,test_loader

2.3 模型训练及保存

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torchvision.datasets import CIFAR10
from torchvision.transforms import Compose, ToTensor
from torch.utils.data import DataLoader
import os
import time# 从modele目录导入模型
from model.image_classification import ImageClassificationdef train():dir = os.path.dirname(__file__)device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 加载数据集train = CIFAR10(root=os.path.join(dir, "data"),train=True,download=True,transform=transform,)# 导入模型model = ImageClassification()model.to(device)# 定义i模型训练的超参数epochs = 80lr = 1e-3batch_size = 256loss_history = []# 构建训练用的损失函数及优化器criterion = nn.CrossEntropyLoss()optimizer = optim.Adam(model.parameters(), lr=lr)for i, epoch in enumerate(range(epochs)):# 构建训练数据批次train_loader = DataLoader(train, batch_size=batch_size, shuffle=True)# 记录样本数量train_num = 0# 记录总的损失值:用于计算平均损失total_loss = 0.0# 记录正确记录数correct = 0# 记录训练开始时间start = time.time()# 开始使用批次数据进行训练for x, y in train_loader:# 更改模型训练设备x = x.to(device)y = y.to(device)# 送入模型output = model(x)# 计算损失loss = criterion(output, y)# 梯度清零optimizer.zero_grad()# 反向传播loss.backward()# 更新参数optimizer.step()# 更新训练过程的数据train_num += len(y)total_loss += loss.item() * len(y)correct += output.argmax(1).eq(y).sum().item()print("epoc:%d  loss:%.3f accuracy:%.3f time:%.3f"% (i + 1, total_loss / train_num, correct / train_num, time.time() - start))loss_history.append(total_loss / train_num)# 更新图形update_plot(loss_history)# 训练完成之后,保存模型torch.save(model.state_dict(), os.path.join(dir, "model.pth"))print("模型保存成功:", i)

2.4 模型加载及验证

# 测试集评估
def vaild():dir = os.path.dirname(__file__)device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 定义超参数batch_size = 100# 定义测试记录数据vaild_num = 0total_correct = 0vaild_data = CIFAR10(root=os.path.join(dir, "data"),train=False,download=False,transform=transform,)# 构建测评数据集批次vaild_loader = DataLoader(vaild_data, batch_size=batch_size, shuffle=False)model = ImageClassification()# 加载模型参数model.load_state_dict(torch.load(os.path.join(dir, "model.pth")))# 切换为验证模式model.to(device)model.eval()for x, y in vaild_loader:x = x.to(device)y = y.to(device)output = model(x)total_correct += output.argmax(1).eq(y).sum().item()vaild_num += len(y)print("测试集正确率:%.3f" % (total_correct / vaild_num))

2.5 数据增强

data_transforms = {'train': transforms.Compose([transforms.RandomRotation(45),#随机旋转,-45到45度之间随机选transforms.CenterCrop(224),#从中心开始裁剪transforms.RandomHorizontalFlip(p=0.5),#随机水平翻转 选择一个概率概率transforms.RandomVerticalFlip(p=0.5),#随机垂直翻转transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),#参数1为亮度,参数2为对比度,参数3为饱和度,参数4为色相transforms.RandomGrayscale(p=0.025),#概率转换成灰度率,3通道就是R=G=Btransforms.ToTensor(),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])#均值,标准差]),'valid': transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]),
}

2.6 完整案例

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision.datasets import CIFAR10
from torchvision import transforms
import os
import time# 定义CNN模型
class ImageClassification(nn.Module):def __init__(self):super(ImageClassification, self).__init__()# 第一层卷积块self.layer1 = nn.Sequential(nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=1),nn.ReLU(),nn.MaxPool2d(kernel_size=2, stride=2),nn.Dropout(0.25),)# 第二层卷积块self.layer2 = nn.Sequential(nn.Conv2d(in_channels=32, out_channels=128, kernel_size=3, stride=1, padding=1),nn.ReLU(),nn.MaxPool2d(kernel_size=2, stride=2),nn.Dropout(0.25),)# 全连接层self.linear1 = nn.Sequential(nn.Linear(128 * 8 * 8, 2048),  # 修正为8x8特征图nn.ReLU(), nn.Dropout(0.5))self.linear2 = nn.Sequential(nn.Linear(2048, 1024), nn.ReLU(), nn.Dropout(0.5))self.out = nn.Linear(1024, 10)  # CIFAR10有10个类别def forward(self, x):x = self.layer1(x)x = self.layer2(x)x = x.view(x.size(0), -1)  # 展平操作x = self.linear1(x)x = self.linear2(x)return self.out(x)# 数据增强和加载
def get_data():# 数据增强变换data_transforms = {'train': transforms.Compose([transforms.RandomRotation(15),transforms.RandomHorizontalFlip(),transforms.RandomCrop(32, padding=4),transforms.ToTensor(),transforms.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010])]),'test': transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010])]),}# 加载数据集data_dir = os.path.dirname(__file__)train_set = CIFAR10(root=os.path.join(data_dir, "data"),train=True,download=True,transform=data_transforms['train'])test_set = CIFAR10(root=os.path.join(data_dir, "data"),train=False,download=True,transform=data_transforms['test'])# 观察数据集信息print(f"训练集数量: {len(train_set)}, 测试集数量: {len(test_set)}")print("类别映射:", train_set.class_to_idx)# 创建数据加载器train_loader = DataLoader(train_set, batch_size=128, shuffle=True, num_workers=2)test_loader = DataLoader(test_set, batch_size=128, shuffle=False, num_workers=2)return train_loader, test_loader# 训练函数
def train_model(model, train_loader, test_loader, device, epochs=50):criterion = nn.CrossEntropyLoss()optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=5, factor=0.5)best_acc = 0.0history = {'train_loss': [], 'test_acc': []}for epoch in range(epochs):# 训练阶段model.train()running_loss = 0.0start_time = time.time()for inputs, labels in train_loader:inputs, labels = inputs.to(device), labels.to(device)optimizer.zero_grad()outputs = model(inputs)loss = criterion(outputs, labels)loss.backward()optimizer.step()running_loss += loss.item() * inputs.size(0)epoch_loss = running_loss / len(train_loader.dataset)history['train_loss'].append(epoch_loss)# 验证阶段model.eval()correct = 0total = 0with torch.no_grad():for inputs, labels in test_loader:inputs, labels = inputs.to(device), labels.to(device)outputs = model(inputs)_, predicted = torch.max(outputs, 1)total += labels.size(0)correct += (predicted == labels).sum().item()epoch_acc = correct / totalhistory['test_acc'].append(epoch_acc)scheduler.step(epoch_loss)# 保存最佳模型if epoch_acc > best_acc:best_acc = epoch_acctorch.save(model.state_dict(), 'best_model.pth')# 打印进度time_elapsed = time.time() - start_timeprint(f'Epoch {epoch+1}/{epochs} | 'f'Loss: {epoch_loss:.4f} | 'f'Test Acc: {epoch_acc:.4f} | 'f'Time: {time_elapsed:.0f}s | 'f'LR: {optimizer.param_groups[0]["lr"]:.6f}')print(f'训练完成,最佳测试准确率: {best_acc:.4f}')return history# 主函数
def main():# 设置设备device = torch.device("cuda" if torch.cuda.is_available() else "cpu")print(f"使用设备: {device}")# 加载数据train_loader, test_loader = get_data()# 初始化模型model = ImageClassification().to(device)# 训练模型history = train_model(model, train_loader, test_loader, device, epochs=50)if __name__ == "__main__":main()

总结

本文进一步介绍了卷积方面的知识,包括反卷积膨胀卷积等。还给出了卷积神经网络的构建案例。

http://www.xdnf.cn/news/1305235.html

相关文章:

  • 数据结构初阶:排序算法(二)交换排序
  • Boost库中boost::function函数使用详解
  • Redis面试精讲 Day 22:Redis布隆过滤器应用场景
  • 测控一体化闸门驱动灌区信息化升级的核心引擎
  • 波浪模型SWAN学习(1)——模型编译与波浪折射模拟(Test of the refraction formulation)
  • yolo安装
  • es7.x中分片和节点关系以及查看节点数
  • WEB安全--Java安全--Servlet内存马
  • 前端基础知识版本控制系列 - 01( 对版本管理的理解)
  • pyqt5无法显示opencv绘制文本和掩码信息
  • Map、Dictionary、Hash Table:到底该用哪一个?
  • 机械学习---- PCA 降维深度解析
  • 朗空量子与 Anolis OS 完成适配,龙蜥获得抗量子安全能力
  • redis-保姆级配置详解
  • 焊接机器人保护气体效率优化
  • 18- 网络编程
  • NAS播放器的新星,一站式全平台媒体库管理工具『Cinemore』体验
  • 文档对比(java-diff-utils)
  • HTML5新增属性
  • 【机器学习深度学习】OpenCompass 评测指标全解析:让大模型评估更科学
  • 从前端框架到GIS开发系列课程(26)在mapbox中实现地球自转效果,并添加点击事件增强地图交互性
  • 物联网(IoT)系统中,通信协议如何选择
  • 20250815在荣品RD-RK3588-MID开发板的Android13下调通TP芯片FT8206
  • 智慧零碳园区——解读2025 零碳产业园区实施路径规划【附全文阅读】
  • MqSQL中的《快照读》和《当前读》
  • SQL182 连续两次作答试卷的最大时间窗
  • C++第二十课:快递运费计算器 / 黑白配+石头剪刀布小游戏
  • Linux入门(十九)定时备份数据库
  • 第1篇_Go语言初探_环境搭建与HelloWorld
  • 802.11 Wi-Fi 竞争机制深度分析:CSMA/CA 与 DCF