深度学习-卷积神经网络
卷积神经网络(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)
更多推荐

所有评论(0)