卷积神经网络(CNN)

是一类专门处理网格状数据(如图像、视频、音频频谱)的深度学习模型。它的核心思想是:局部连接、权值共享、层次化特征提取。因为主特征提取,所以完成后接,神经网络,逻辑回归等分类或回归模型。
CNN 更适合图像
卷积核:(一组固定的权重:因为每个神经元的多个权重固定,所以又可以看做一个恒定的滤波器filter)做内积(逐个元素相乘再求和)的操作就是所谓的『卷积』操作,也是卷积神经网络的名字来源。
一、为什么 CNN 适合图像
1,局部连接:图像中相邻像素关系密切。
2,权值共享:同一特征在不同位置出现时可用同一卷积核检测。
3,平移等变性:卷积对小幅平移较稳定。
4,层级特征:浅层学边缘、纹理,深层学部件、物体语义。
二、原理
1,卷积层:用卷积核在输入上滑动,提取局部特征,得到特征图。(常见参数:卷积核大小 K、步长 S、填充 P、输出通道数。)
在这里插入图片描述

2,激活函数:引入非线性,常用 ReLU、Leaky ReLU、GELU。ReLU 可缓解梯度消失。
3,池化层:如最大池化、平均池化,用于降维、减少计算量,并增强一定的平移不变性。常见为 2×2、步长 2。
平均池化(average pooling):计算图像区域的平均值作为该区域池化后的值。
最大池化(max pooling):选图像区域的最大值作为该区域池化后的值。是最为常见的。
通常来说,CNN的卷积层之间都会周期性地插入池化层。
4,批归一化:加速训练、稳定分布,常放在卷积和激活之间
5,全连接层 / 全局平均池化:将最后的输出映射到线性可分的空间,最后用于分类或回归。常用全局平均池化替代全连接层,减少参数量。
6,Dropout:随机失活,用于正则化,防止过拟合。
三、 典型流程
输入图像 → 卷积 + 激活 + 池化(重复多次)→ 展平或全局池化 → 全连接 →分类/回归。
在这里插入图片描述
四、经典网络
1,LeNet-5:早期手写数字识别。
2,AlexNet:2012 年 ImageNet 突破,使用 ReLU、Dropout、GPU。
3,VGG:堆叠 3×3 卷积,强调深度。
4,GoogLeNet / Inception:多尺度卷积、1×1 降维。
5,ResNet:残差连接,解决深层网络退化,可训练上百层。
6,DenseNet:密集连接,特征复用。
7,MobileNet:深度可分离卷积,轻量化。
8,EfficientNet:复合缩放,兼顾精度与效率。
五、应用
图像分类、目标检测、语义分割、实例分割、人脸识别、OCR、医学影像、遥感、视频分析、语音识别、文本分类等。
六、优缺点
优点:参数少、自动提取特征、善于处理空间结构、局部平移较稳定。
缺点:需要大量数据和算力;对大幅旋转、尺度变化、长距离依赖建模有限;可解释性较弱。
七、代码讲解

#导入库
import os
from torch import nn
# -------------------------- 生成训练集、测试集txt文件 --------------------------
"""
    遍历数据集文件夹,生成txt标签文件
    :param root: 数据集根目录,如 .\food_dataset
    :param dir: train / test 文件夹名
    输出txt文件:每一行 图片路径 标签id
    """
def train_test_file(root, dir):
    file_txt = open(dir+'.txt','w')
    path = os.path.join(root,dir)
    # os.walk递归遍历文件夹:返回 当前目录路径,子文件夹列表,文件列表
    for roots, direcotories, files in os.walk(path):
    # 如果当前目录还有子文件夹,保存子文件夹名称(类别文件夹)
        if len(direcotories) != 0:
            dirs = direcotories

        else:
        # 走到最底层(图片所在文件夹),拆分路径,拿到类别文件夹名称
            mow_dir = roots.split('\\')
            # 遍历该类别下所有图片
            for file in files:
                path_1 = os.path.join(roots,file)
                # 写入:图片完整路径 + 空格 + 类别索引
                # print(path_1)
                file_txt.write(path_1+' '+str(dirs.index(mow_dir[-1]))+'\n')
    file_txt.close()
# 数据集根目录
root = r'.\food_dataset'
train_dir = 'train'
test_dir = 'test'
# 生成 train.txt 和 test.txt
train_test_file(root, train_dir)
train_test_file(root, test_dir)

'''补充1个类中__getitem__小知识'''
class USE_getitem():
    def __init__(self,text):
        self.text = text                   ##调用类的时候,就运行init,初始化text
    def __getitem__(self, index):           #可以通过列表索引的方式获取类对象的数据
        return self.text[index].upper()      ##字符串中一个方法,将字符串大写
    def __len__(self):
        return len(self.text)
# p = USE_getitem('pytorch')
# print(p.__getitem__(1))
# print(p[1])
# print(p.__len__())
# print(p[0],p[1])#对数据对象p进行[]的操作,就会执行__getitem的函数# 字符串,列表
# print(len(p))#当对数据对象p执行len函数操作时,就会自动运行__len__方法。
# # 从类里面得到的对象,是否可通过a[0]??? 字符串、列表、字典、numpy pandas

'''创建数据集的类,继承torch的Dataset'''
import torch
from torch.utils.data import Dataset, DataLoader   #用于处理数据集的
import numpy as np
import PIL.Image as Image
from torchvision import transforms   #对数据进行处理工具 转换

#数组增强

data_transforms = {
    'trainda':
        transforms.Compose([                    #对图片做预处理的。组合,
        transforms.Resize((256,256)),           #数据进行改变大小[256,256]
        transforms.ToTensor(),                  #数据转换为tensor,默认把通道维度放在前面
    ]),
    'valid':
        transforms.Compose([
        transforms.Resize((256,256)),
        transforms.ToTensor(),
    ]),
}
#Dataset用来处理数据的。
class food_dataset(Dataset):
    def __init__(self,file_path,transform=None):#类的初始化,解析数据文件txt
        """
        初始化:读取txt,保存所有图片路径和标签
        :param file_path: txt文件路径(train.txt / test.txt)
        :param transform: 图像预处理操作
        """
        self.file_path = file_path
        self.img = []          # 存储所有图片路径
        self.labels = []       # 存储所有图片对应的标签
        self.transform = transform
        with open(self.file_path) as f:  #是把train.txt文件中图片的路径保存在 self.imgs,trainda.txt文件中标签保存在self.labels
            samples = [x.strip().split(' ') for x in f.readlines()]   # 读取txt每一行,去除换行符,按空格切分成 [图片路径,标签]
            for img_path, label in samples:
                self.img.append(img_path)     #图像的路径存入列表
                self.labels.append(label)      #标签字符串存入列表,还不是tensor

#初始化:把图片目录加载到self,
    def __len__(self):   """必须实现:返回数据集总样本数量,len(数据集实例)自动调用"""
        return len(self.img)
    def __getitem__(self, idx):
    	    """
        必须实现:根据索引idx,返回单条样本 (image, label)
        DataLoader会自动调用这个函数取样本
        """
        image = Image.open(self.img[idx])  # 通过索引读取图片
        # 如果设置了预处理,执行图像变换
        if self.transform:
            image = self.transform(image)
        # 获取标签,转成int64类型tensor(CrossEntropyLoss要求标签long类型)
        label = self.labels[idx]
        label = torch.from_numpy(np.array(label,dtype=np.int64))
        return image, label

#training_data
# 实例化训练集、测试集
training_data = food_dataset(file_path = './train.txt',transform=data_transforms['trainda'])
test_data = food_dataset(file_path = './test.txt',transform=data_transforms['valid'])

#training_data需要具备索引的功能,还要确保数据是tensor
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
# 测试读取一个batch,查看tensor形状
for X ,y in test_dataloader:
    print(f"shape of x [N , C , H , W]: {X.shape} ")  # N:batch_size C通道 H高 W宽
    print(f"shape of y : {y.shape} {y.dtype} ")
    break
"""判断当前设备是否支持GPU,其中mps是苹果m系列芯片的GPU"""
# -------------------------- 设备选择:优先cuda,其次mps(苹果),最后cpu --------------------------
device = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'

print(f"Using {device} device") #CUDA驱动软件的功能: pytorch能够夫执行cuda的命令.cuda通过GPU指令集去控制GPU
##神经网络的模型也需要传入到GPU,1个batchsize的数据集也需要传入到GPU,才可以进行训练。
# -------------------------- 搭建CNN网络模型 --------------------------
class CNN(nn.Module):
    def __init__(self):
        super(CNN,self).__init__()# 继承父类nn.Module初始化
        self.conv1 = nn.Sequential(
            # Conv2d: in_channels输入通道3(RGB), out_channels输出16通道, kernel_size卷积核5×5, stride步长1, padding填充2保持尺寸不变
            nn.Conv2d(in_channels=3, out_channels=16, kernel_size=5, stride=1 ,padding=2,
        ),
            nn.ReLU(),# 激活函数,引入非线性
            nn.MaxPool2d(kernel_size=2),# 最大池化,窗口2×2,尺寸减半
    )
        self.conv2 = nn.Sequential(
            nn.Conv2d(in_channels=16, out_channels=32, kernel_size=5, stride=1 ,padding=2,),
            nn.ReLU(),
            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1 ,padding=2,),
            nn.ReLU(),
            nn.MaxPool2d(2),
        )
        self.conv3 = nn.Sequential(
            nn.Conv2d(in_channels=32, out_channels=128, kernel_size=5, stride=1 ,padding=2,),
            nn.ReLU(),
        )
        # 全连接层:输入特征维度128*64*64,输出20个类别
        self.out = nn.Linear(in_features=128*64*64, out_features=20)
    def forward(self, x):
        """
        前向传播:定义数据流动路线
        模型实例(x) 等价调用 forward(x)
        """
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = x.view(x.size(0), -1)  # flatten:保留batch维度,其余维度展平成一维
        output = self.out(x)
        return output
model = CNN().to(device)#把刚网创建的模型传入到Gpu
print(model)
# 损失函数:多分类任务交叉熵损失
loss_fn = nn.CrossEntropyLoss()
# 优化器Adam,更新模型权重,lr学习率
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# -------------------------- 训练一轮函数 --------------------------
def train(dataloader , model , loss_fn , optimizer):
    model.train() #告诉模型,我要开始训练,模型中w进行随机化操作,已经更新w。在训练过程中,w会被修改的
    # #pytorch提供2种方式来切换训练和测试的模式,分别是:model.train()和model.eval()。
    # 一般用法是:在训练开始之前写上model.trian(),在测试时写上 model.eval()
    batch_size_num = 1  # 统计 训练的batch数量
    for X ,y in dataloader:                           #其中batch为每一个数据的编号
        X ,y = X.to(device) ,y.to(device)             #把训练数据集和标签传入cpu或GPU
        pred = model.forward(X)                       #.forward可以被省略,父类中已经对此功能进行了设置。自动初始化 w权值
        loss = loss_fn(pred ,y)                       #通过交叉熵损失函数计算损失值loss
        # Backpropagation 进来一个batch的数据,计算一次梯度,更新一次网络


        optimizer.zero_grad()                         #梯度值清零
        loss.backward()                               #反向传播计算得到每个参数的梯度值w
        optimizer.step()                              #根据梯度更新网络w参数

        loss_value = loss.item()                        #从tensor数据中提取数据出来,tensor获取损失值
        if batch_size_num % 100 == 0:
            print(f"loss: {loss_value:>7f} [number: {batch_size_num}]")
        batch_size_num += 1
# -------------------------- 测试/验证一轮函数 --------------------------
def test(dataloader , model , loss_fn ):
    size = len(dataloader.dataset)# 测试集总样本数量
    num_batches = len(dataloader) # 测试集一共多少个batch
    model.eval()  #测试,w就不能再更新。模型设置评估模式:关闭dropout,BN不更新统计量,权重冻结
    test_loss , correct = 0, 0
    with torch.no_grad():   #with open一个上下文管理器,关团梯度计算。当你确认不会调用Tensor.backWard()的时候。这可以减少计算所用内存消
        for X ,y in dataloader:
            X ,y = X.to(device) ,y.to(device)
            pred = model.forward(X)
            test_loss += loss_fn(pred ,y).item()   #test_loss是会自动累加每一个批次的损失值
            # pred.argmax(1): 在第1维(类别维度)取最大值索引,即预测类别;和真实标签对比,统计正确样本
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()  # 标量
            a = (pred.argmax(1) == y)  #dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号    
            b = (pred.argmax(1) == y).type(torch.float)
    test_loss /= num_batches    #  所有batch平均损失,能来衡量模型测试的好坏。
    correct /= size    #平均的正确率
    print(f"Test result: \n Accuracy: {(100*correct)}% , Avg loss: {test_loss}")


# ========== 第一组:Adam优化训练10轮 ==========
epochs = 10
for epoch in range(epochs):
    # print(f"Epoch {epoch+1}\n-------------------------------")
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model, loss_fn)
print("Training complete!")

# ========== 第二组:切换SGD优化器继续训练 ==========
optimizer =torch.optim.SGD(model.parameters(), lr=0.01, )  #创建一个优化器,SGD为随机梯度下降算法
# params:要训练的参数,一般我们传入的都是model.parameters()。
#lr:learning_rate学习率,也就是步长。
#Loss表示模型训练后的输出结果与 样本标签的差距。如果差距越小,就表示模型训练越好,越逼近于真实的模型。
train(train_dataloader,model,loss_fn,optimizer)#训练1次完整的数据,多轮训练
epochs = 10
for t in range(epochs):
    print(f"Epoch {t + 1}\n------------------------")
    train(train_dataloader,model,loss_fn,optimizer)#10次训练
print('Done!')
test(test_dataloader,model,loss_fn)

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐