卷积神经网络(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、医学影像、遥感、视频分析、语音识别、文本分类等。
六、优缺点
优点:参数少、自动提取特征、善于处理空间结构、局部平移较稳定。
缺点:需要大量数据和算力;对大幅旋转、尺度变化、长距离依赖建模有限;可解释性较弱。
七、代码讲解
#导入库importosfromtorchimportnn# -------------------------- 生成训练集、测试集txt文件 --------------------------""" 遍历数据集文件夹,生成txt标签文件 :param root: 数据集根目录,如 .\food_dataset :param dir: train / test 文件夹名 输出txt文件:每一行 图片路径 标签id """deftrain_test_file(root,dir):file_txt=open(dir+'.txt','w')path=os.path.join(root,dir)# os.walk递归遍历文件夹:返回 当前目录路径,子文件夹列表,文件列表forroots,direcotories,filesinos.walk(path):# 如果当前目录还有子文件夹,保存子文件夹名称(类别文件夹)iflen(direcotories)!=0:dirs=direcotorieselse:# 走到最底层(图片所在文件夹),拆分路径,拿到类别文件夹名称mow_dir=roots.split('\\')# 遍历该类别下所有图片forfileinfiles: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.txttrain_test_file(root,train_dir)train_test_file(root,test_dir)'''补充1个类中__getitem__小知识'''classUSE_getitem():def__init__(self,text):self.text=text##调用类的时候,就运行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的Dataset'''importtorchfromtorch.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,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=transformwithopen(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#初始化:把图片目录加载到self,def__len__(self):"""必须实现:返回数据集总样本数量,len(数据集实例)自动调用"""returnlen(self.img)def__getitem__(self,idx):""" 必须实现:根据索引idx,返回单条样本 (image, label) DataLoader会自动调用这个函数取样本 """image=Image.open(self.img[idx])# 通过索引读取图片# 如果设置了预处理,执行图像变换ifself.transform:image=self.transform(image)# 获取标签,转成int64类型tensor(CrossEntropyLoss要求标签long类型)label=self.labels[idx]label=torch.from_numpy(np.array(label,dtype=np.int64))returnimage,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需要具备索引的功能,还要确保数据是tensortrain_dataloader=DataLoader(training_data,batch_size=64,shuffle=True)test_dataloader=DataLoader(test_data,batch_size=64,shuffle=True)# 测试读取一个batch,查看tensor形状forX,yintest_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'iftorch.cuda.is_available()else'mps'iftorch.backends.mps.is_available()else'cpu'print(f"Using{device}device")#CUDA驱动软件的功能: pytorch能够夫执行cuda的命令.cuda通过GPU指令集去控制GPU##神经网络的模型也需要传入到GPU,1个batchsize的数据集也需要传入到GPU,才可以进行训练。# -------------------------- 搭建CNN网络模型 --------------------------classCNN(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)defforward(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)returnoutput model=CNN().to(device)#把刚网创建的模型传入到Gpuprint(model)# 损失函数:多分类任务交叉熵损失loss_fn=nn.CrossEntropyLoss()# 优化器Adam,更新模型权重,lr学习率optimizer=torch.optim.Adam(model.parameters(),lr=0.001)# -------------------------- 训练一轮函数 --------------------------deftrain(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数量forX,yindataloader:#其中batch为每一个数据的编号X,y=X.to(device),y.to(device)#把训练数据集和标签传入cpu或GPUpred=model.forward(X)#.forward可以被省略,父类中已经对此功能进行了设置。自动初始化 w权值loss=loss_fn(pred,y)#通过交叉熵损失函数计算损失值loss# Backpropagation 进来一个batch的数据,计算一次梯度,更新一次网络optimizer.zero_grad()#梯度值清零loss.backward()#反向传播计算得到每个参数的梯度值woptimizer.step()#根据梯度更新网络w参数loss_value=loss.item()#从tensor数据中提取数据出来,tensor获取损失值ifbatch_size_num%100==0:print(f"loss:{loss_value:>7f}[number:{batch_size_num}]")batch_size_num+=1# -------------------------- 测试/验证一轮函数 --------------------------deftest(dataloader,model,loss_fn):size=len(dataloader.dataset)# 测试集总样本数量num_batches=len(dataloader)# 测试集一共多少个batchmodel.eval()#测试,w就不能再更新。模型设置评估模式:关闭dropout,BN不更新统计量,权重冻结test_loss,correct=0,0withtorch.no_grad():#with open一个上下文管理器,关团梯度计算。当你确认不会调用Tensor.backWard()的时候。这可以减少计算所用内存消forX,yindataloader: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=10forepochinrange(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=10fortinrange(epochs):print(f"Epoch{t+1}\n------------------------")train(train_dataloader,model,loss_fn,optimizer)#10次训练print('Done!')test(test_dataloader,model,loss_fn)