深度学习-卷积神经网络
卷积神经网络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全连接层 / 全局平均池化将最后的输出映射到线性可分的空间最后用于分类或回归。常用全局平均池化替代全连接层减少参数量。6Dropout随机失活用于正则化防止过拟合。三、 典型流程输入图像 → 卷积 激活 池化重复多次→ 展平或全局池化 → 全连接 →分类/回归。四、经典网络1LeNet-5早期手写数字识别。2AlexNet2012 年 ImageNet 突破使用 ReLU、Dropout、GPU。3VGG堆叠 3×3 卷积强调深度。4GoogLeNet / Inception多尺度卷积、1×1 降维。5ResNet残差连接解决深层网络退化可训练上百层。6DenseNet密集连接特征复用。7MobileNet深度可分离卷积轻量化。8EfficientNet复合缩放兼顾精度与效率。五、应用图像分类、目标检测、语义分割、实例分割、人脸识别、OCR、医学影像、遥感、视频分析、语音识别、文本分类等。六、优缺点优点参数少、自动提取特征、善于处理空间结构、局部平移较稳定。缺点需要大量数据和算力对大幅旋转、尺度变化、长距离依赖建模有限可解释性较弱。七、代码讲解#导入库importosfromtorchimportnn# -------------------------- 生成训练集、测试集txt文件 -------------------------- 遍历数据集文件夹生成txt标签文件 :param root: 数据集根目录如 .\food_dataset :param dir: train / test 文件夹名 输出txt文件每一行 图片路径 标签id deftrain_test_file(root,dir):file_txtopen(dir.txt,w)pathos.path.join(root,dir)# os.walk递归遍历文件夹返回 当前目录路径子文件夹列表文件列表forroots,direcotories,filesinos.walk(path):# 如果当前目录还有子文件夹保存子文件夹名称类别文件夹iflen(direcotories)!0:dirsdirecotorieselse:# 走到最底层图片所在文件夹拆分路径拿到类别文件夹名称mow_dirroots.split(\\)# 遍历该类别下所有图片forfileinfiles:path_1os.path.join(roots,file)# 写入图片完整路径 空格 类别索引# print(path_1)file_txt.write(path_1 str(dirs.index(mow_dir[-1]))\n)file_txt.close()# 数据集根目录rootr.\food_datasettrain_dirtraintest_dirtest# 生成 train.txt 和 test.txttrain_test_file(root,train_dir)train_test_file(root,test_dir)补充1个类中__getitem__小知识classUSE_getitem():def__init__(self,text):self.texttext##调用类的时候就运行init初始化textdef__getitem__(self,index):#可以通过列表索引的方式获取类对象的数据returnself.text[index].upper()##字符串中一个方法将字符串大写def__len__(self):returnlen(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的Datasetimporttorchfromtorch.utils.dataimportDataset,DataLoader#用于处理数据集的importnumpyasnpimportPIL.ImageasImagefromtorchvisionimporttransforms#对数据进行处理工具 转换#数组增强data_transforms{trainda:transforms.Compose([#对图片做预处理的。组合transforms.Resize((256,256)),#数据进行改变大小[256,256]transforms.ToTensor(),#数据转换为tensor默认把通道维度放在前面]),valid:transforms.Compose([transforms.Resize((256,256)),transforms.ToTensor(),]),}#Dataset用来处理数据的。classfood_dataset(Dataset):def__init__(self,file_path,transformNone):#类的初始化解析数据文件txt 初始化读取txt保存所有图片路径和标签 :param file_path: txt文件路径(train.txt / test.txt) :param transform: 图像预处理操作 self.file_pathfile_path self.img[]# 存储所有图片路径self.labels[]# 存储所有图片对应的标签self.transformtransformwithopen(self.file_path)asf:#是把train.txt文件中图片的路径保存在 self.imgs,trainda.txt文件中标签保存在self.labelssamples[x.strip().split( )forxinf.readlines()]# 读取txt每一行去除换行符按空格切分成 [图片路径,标签]forimg_path,labelinsamples:self.img.append(img_path)#图像的路径存入列表self.labels.append(label)#标签字符串存入列表还不是tensor#初始化:把图片目录加载到selfdef__len__(self):必须实现返回数据集总样本数量len(数据集实例)自动调用returnlen(self.img)def__getitem__(self,idx): 必须实现根据索引idx返回单条样本 (image, label) DataLoader会自动调用这个函数取样本 imageImage.open(self.img[idx])# 通过索引读取图片# 如果设置了预处理执行图像变换ifself.transform:imageself.transform(image)# 获取标签转成int64类型tensorCrossEntropyLoss要求标签long类型labelself.labels[idx]labeltorch.from_numpy(np.array(label,dtypenp.int64))returnimage,label#training_data# 实例化训练集、测试集training_datafood_dataset(file_path./train.txt,transformdata_transforms[trainda])test_datafood_dataset(file_path./test.txt,transformdata_transforms[valid])#training_data需要具备索引的功能还要确保数据是tensortrain_dataloaderDataLoader(training_data,batch_size64,shuffleTrue)test_dataloaderDataLoader(test_data,batch_size64,shuffleTrue)# 测试读取一个batch查看tensor形状forX,yintest_dataloader:print(fshape of x [N , C , H , W]:{X.shape})# N:batch_size C通道 H高 W宽print(fshape of y :{y.shape}{y.dtype})break判断当前设备是否支持GPU其中mps是苹果m系列芯片的GPU# -------------------------- 设备选择优先cuda其次mps(苹果)最后cpu --------------------------devicecudaiftorch.cuda.is_available()elsempsiftorch.backends.mps.is_available()elsecpuprint(fUsing{device}device)#CUDA驱动软件的功能: pytorch能够夫执行cuda的命令.cuda通过GPU指令集去控制GPU##神经网络的模型也需要传入到GPU1个batchsize的数据集也需要传入到GPU才可以进行训练。# -------------------------- 搭建CNN网络模型 --------------------------classCNN(nn.Module):def__init__(self):super(CNN,self).__init__()# 继承父类nn.Module初始化self.conv1nn.Sequential(# Conv2d: in_channels输入通道3(RGB), out_channels输出16通道, kernel_size卷积核5×5, stride步长1, padding填充2保持尺寸不变nn.Conv2d(in_channels3,out_channels16,kernel_size5,stride1,padding2,),nn.ReLU(),# 激活函数引入非线性nn.MaxPool2d(kernel_size2),# 最大池化窗口2×2尺寸减半)self.conv2nn.Sequential(nn.Conv2d(in_channels16,out_channels32,kernel_size5,stride1,padding2,),nn.ReLU(),nn.Conv2d(in_channels32,out_channels32,kernel_size5,stride1,padding2,),nn.ReLU(),nn.MaxPool2d(2),)self.conv3nn.Sequential(nn.Conv2d(in_channels32,out_channels128,kernel_size5,stride1,padding2,),nn.ReLU(),)# 全连接层输入特征维度128*64*64输出20个类别self.outnn.Linear(in_features128*64*64,out_features20)defforward(self,x): 前向传播定义数据流动路线 模型实例(x) 等价调用 forward(x) xself.conv1(x)xself.conv2(x)xself.conv3(x)xx.view(x.size(0),-1)# flatten保留batch维度其余维度展平成一维outputself.out(x)returnoutput modelCNN().to(device)#把刚网创建的模型传入到Gpuprint(model)# 损失函数多分类任务交叉熵损失loss_fnnn.CrossEntropyLoss()# 优化器Adam更新模型权重lr学习率optimizertorch.optim.Adam(model.parameters(),lr0.001)# -------------------------- 训练一轮函数 --------------------------deftrain(dataloader,model,loss_fn,optimizer):model.train()#告诉模型我要开始训练模型中w进行随机化操作已经更新w。在训练过程中w会被修改的# #pytorch提供2种方式来切换训练和测试的模式分别是:model.train()和model.eval()。# 一般用法是:在训练开始之前写上model.trian()在测试时写上 model.eval()batch_size_num1# 统计 训练的batch数量forX,yindataloader:#其中batch为每一个数据的编号X,yX.to(device),y.to(device)#把训练数据集和标签传入cpu或GPUpredmodel.forward(X)#.forward可以被省略父类中已经对此功能进行了设置。自动初始化 w权值lossloss_fn(pred,y)#通过交叉熵损失函数计算损失值loss# Backpropagation 进来一个batch的数据计算一次梯度更新一次网络optimizer.zero_grad()#梯度值清零loss.backward()#反向传播计算得到每个参数的梯度值woptimizer.step()#根据梯度更新网络w参数loss_valueloss.item()#从tensor数据中提取数据出来tensor获取损失值ifbatch_size_num%1000:print(floss:{loss_value:7f}[number:{batch_size_num}])batch_size_num1# -------------------------- 测试/验证一轮函数 --------------------------deftest(dataloader,model,loss_fn):sizelen(dataloader.dataset)# 测试集总样本数量num_batcheslen(dataloader)# 测试集一共多少个batchmodel.eval()#测试w就不能再更新。模型设置评估模式关闭dropoutBN不更新统计量权重冻结test_loss,correct0,0withtorch.no_grad():#with open一个上下文管理器关团梯度计算。当你确认不会调用Tensor.backWard()的时候。这可以减少计算所用内存消forX,yindataloader:X,yX.to(device),y.to(device)predmodel.forward(X)test_lossloss_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)#dim1表示每一行中的最大值对应的索引号dim0表示每一列中的最大值对应的索引号b(pred.argmax(1)y).type(torch.float)test_loss/num_batches# 所有batch平均损失能来衡量模型测试的好坏。correct/size#平均的正确率print(fTest result: \n Accuracy:{(100*correct)}% , Avg loss:{test_loss})# 第一组Adam优化训练10轮 epochs10forepochinrange(epochs):# print(fEpoch {epoch1}\n-------------------------------)train(train_dataloader,model,loss_fn,optimizer)test(test_dataloader,model,loss_fn)print(Training complete!)# 第二组切换SGD优化器继续训练 optimizertorch.optim.SGD(model.parameters(),lr0.01,)#创建一个优化器,SGD为随机梯度下降算法# params:要训练的参数一般我们传入的都是model.parameters()。#lr:learning_rate学习率也就是步长。#Loss表示模型训练后的输出结果与 样本标签的差距。如果差距越小就表示模型训练越好越逼近于真实的模型。train(train_dataloader,model,loss_fn,optimizer)#训练1次完整的数据,多轮训练epochs10fortinrange(epochs):print(fEpoch{t1}\n------------------------)train(train_dataloader,model,loss_fn,optimizer)#10次训练print(Done!)test(test_dataloader,model,loss_fn)