pytorch学习-11卷积神经网络(高级篇)

发布于:2025-07-07 ⋅ 阅读:(14) ⋅ 点赞:(0)

2.线性模型

3.梯度下降算法

4.反向传播(用pytorch算梯度)

5.用pytorch实现线性回归

6.logistic回归

7.处理多维特征的输入

8.加载数据集

9.多分类问题

10.卷积神经网络(基础篇)

11.卷积神经网络(高级篇)_哔哩哔哩_bilibili

11.1 GoogleNet

        GoogleNet其创新的Inception模块而闻名,这种模块允许网络在同一层次中进行不同尺寸的卷积操作,从而捕捉不同尺度的特征。

11.1.2 Inception模块

        它的主要思想是在同一层次中使用不同大小的卷积核进行特征提取,从而能够捕捉不同尺度的特征。设计神经网络时以模块为单位去组装整个网络结构,在神经网络中多次重复使用该模块,因此将其封装为类,来减少代码量。该模块构成如下图所示:

 

11.1.3 1*1convolution

        1x1卷积是一种在卷积神经网络中使用的特殊类型的卷积操作。它的主要作用是调整特征图的维度(即通道数),同时保持特征图的空间尺寸(高度和宽度),此外,它还能跨通道的特征整合(不同通道的卷积结果最后求和)。例如,数据集为3*3*3经过3*1*1的卷积操作后,输出结果为1*3*3,如下图所示:

 

        1x1卷积的计算量相对较小,因为卷积核的大小为1x1,只需要进行简单的点乘和加法操作。这使得1x1卷积在深层网络中非常有用,可以作为其他卷积操作的降维或升维层,提高整体网络的计算效率。如下图所示:经过1*1卷积后下方操作次数减少许多

 

11.1.4 Inception模块的实现

课上的Inception模块结构如下: 

  • 平均池化路径
    • 3x3平均池化。
    • 1x1卷积降维。
  • 1x1卷积:用于降维。
  • 5x5卷积路径
    • 1x1卷积降维。
    • 5x5卷积提取特征。 
  • 3x3卷积路径
    • 1x1卷积降维。
    • 3x3卷积提取特征。
    •  3x3卷积提取特征。

最后再将每条路径所得张量,沿着channel的方向整合成一个张量 

        

         代码演示:

import torch
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets,transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt

#1.准备数据
#1.1 定义transform
transform=transforms.Compose([transforms.ToTensor(),
                              transforms.Normalize((0.1307),(0.3081))])

#1.2 下载数据
trainset = datasets.MNIST('../dataset/mnist',
                               train=True,
                               download=True,
                               transform=transform)
#1.3 定义dataloader
train_loader = DataLoader(trainset,batch_size=64,shuffle=True)

#1.4测试集
testset=datasets.MNIST(root='../dataset/mnist',
                       train=False,
                       download=True,
                       transform=transform)
test_loader = DataLoader(testset,batch_size=64,shuffle=False)


#2.定义网络

#2.1 定义inception模块



class Inception(torch.nn.Module):
    def __init__(self,in_channels):
        super(Inception,self).__init__()

        #2.1.1 平均池化路径,所用到的1x1的卷积层
        self.branch_pool = torch.nn.Conv2d(in_channels,24,kernel_size=1)
            #输入通道为in_channels,输出通道为24,卷积核大小为1x1


        #2.1.2 1x1卷积路径,仅用到1x1的卷积层
        self.branch1x1 = torch.nn.Conv2d(in_channels,16,kernel_size=1)
            #输入通道为in_channels,输出通道为16,卷积核大小为1x1

        #2.1.3 5x5卷积路径 ,用到1X1和5x5的卷积层
        self.branch5x5_1 = torch.nn.Conv2d(in_channels,16,kernel_size=1)
            #输入通道为in_channels,输出通道为16,卷积核大小为1x1
        self.branch5x5_2 = torch.nn.Conv2d(16,24,kernel_size=5,padding=2)
            #输入通道为16,输出通道为24,卷积核大小为5x5,padding为2(保证输出尺寸不变)

        #2.1.4 3x3卷积路径,用到1x1、3x3的卷积层、3x3的卷积层
        self.branch3x3_1 = torch.nn.Conv2d(in_channels,16,kernel_size=1)
            #输入通道为in_channels,输出通道为16,卷积核大小为1x1
        self.branch3x3_2 = torch.nn.Conv2d(16,24,kernel_size=3,padding=1)
            #输入通道为16,输出通道为24,卷积核大小为3x3,padding为1(保证输出尺寸不变)
        self.branch3x3_3 = torch.nn.Conv2d(24,24,kernel_size=3,padding=1)
            #输入通道为24,输出通道为24,卷积核大小为3x3,padding为1(保证输出尺寸不变)

    def forward(self,x):
        #2.1.1 平均池化路径,先经过平均池化,再经过1x1卷积
        branch_pool=F.avg_pool2d(x,kernel_size=3,stride=1,padding=1)
             #输入为x,池化核大小为3x3,步长为1,padding为1
        branch_pool=self.branch_pool(branch_pool)
            #输入为branch_pool,卷积核大小为1x1,输出通道为24

        #2.1.2 1x1卷积路径,直接经过1x1卷积
        branch1x1=self.branch1x1(x)
            #输入为x,卷积核大小为1x1,输出通道为16

        #2.1.3 5x5卷积路径,先经过1x1卷积,再经过5x5卷积
        branch5x5=self.branch5x5_1(x)
            #输入为x,卷积核大小为1x1,输出通道为16
        branch5x5=self.branch5x5_2(branch5x5)
            #输入为branch5x5,卷积核大小为5x5,输出通道为24,padding为2

        #2.1.4 3x3卷积路径,先经过1x1卷积,再经过3x3卷积,再经过3x3卷积
        branch3x3=self.branch3x3_1(x)
            #输入为x,卷积核大小为1x1,输出通道为16
        branch3x3=self.branch3x3_2(branch3x3)
            #输入为branch3x3,卷积核大小为3x3,输出通道为24,padding为1
        branch3x3=self.branch3x3_3(branch3x3)
            #输入为branch3x3,卷积核大小为3x3,输出通道为24,padding为1

        #2.1.5 将所有路径的输出拼接起来
        outputs=[branch_pool,branch1x1,branch5x5,branch3x3]
        return torch.cat(outputs,dim=1)
            #将outputs中的元素沿着dim=1方向(即通道维度)拼接起来,输出为(batch_size,88,28,28)

#2.2 定义网络结构
class Net(torch.nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.conv1=torch.nn.Conv2d(1,10,kernel_size=5)
        self.conv2=torch.nn.Conv2d(88,20,kernel_size=5)

        self.inception1=Inception(in_channels=10)
        self.inception2=Inception(in_channels=20)

        self.maxpool=torch.nn.MaxPool2d(2)
        self.fc1=torch.nn.Linear(88*4*4,10)

    def forward(self,x):
        #获取batch_size
        in_size=x.size(0)

        x=F.relu(self.maxpool(self.conv1(x)))
        x=self.inception1(x)

        x=F.relu(self.maxpool(self.conv2(x)))
        x=self.inception2(x)

        x=x.view(in_size,-1)
        x=self.fc1(x)
        return x

model=Net()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)

#3.定义损失函数和优化器
criterion=torch.nn.CrossEntropyLoss()
optimizer=optim.SGD(model.parameters(),lr=0.01,momentum=0.5)

#4.训练网络
#4.1 训练函数
def train(epoch):
    running_loss=0.0
    for i,data in enumerate(train_loader,0):
        inputs,labels=data
        inputs,labels=inputs.to(device),labels.to(device)
        optimizer.zero_grad()

        #forward + backward + update
        outputs=model(inputs)
        loss=criterion(outputs,labels)
        loss.backward()
        optimizer.step()
        running_loss+=loss.item()
        if i%300==299:
            print('[%d, %5d] loss: %.3f' %(epoch+1,i+1,running_loss/300))
            running_loss=0.0

#4.2 测试函数
acuracy_list=[]
def Net_test():
    correct=0
    total=0
    with torch.no_grad():
        for data in test_loader:
            inputs,targets=data
            inputs,targets=inputs.to(device),targets.to(device)
            outputs=model(inputs)
            _,predicted=torch.max(outputs.data,1)
            total+=targets.size(0)
            correct+=predicted.eq(targets.data).sum().item()
    print('Accuracy of the network  test : %.2f %%' % (100.0*correct/total))
    acuracy_list.append(100.0*correct/total)


#4.开始训练
for epoch in range(10):
    train(epoch)
    Net_test()

#5.绘制准确率变化图
epochs=list(range(len(acuracy_list)))
plt.plot(epochs,acuracy_list)
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Accuracy of the network')
plt.show()

        运行结果:

 

11.2  ResidualNet(ResNet)

11.2.1深度带来的问题

  • 一是vanishing/exploding gradient,导致了训练十分难收敛,这类问题能够通过normalized initialization 和intermediate normalization layers解决;
  • 另一个是被称为degradation的退化现象。对合适的深度模型继续增加层数,模型准确率会下滑(不是overfit造成),training error和test error都会很高,相应的现象在CIFAR-10和ImageNet都有出现。

        当梯度小于1时,由于backword()求梯度时遵循链式法则,致使梯度相乘后不断减小最终出现梯度消失,影响所求权重(W^{`}=W-\alpha G ),使得训练不充分,最终导致准确率下降。下图为深度增加而导致错误率增大

11.2.2 残差结构


        普通直连的卷积神经网络和 ResNet 的最大区别在于,ResNet 有很多旁路的支线将输入直接连到后面的层,使得后面的层可以直接学习残差,这种结构也被称shortcut connections

        传统的卷积层或全连接层在信息传递时,或多或少会存在信息丢失、损耗等问题。ResNet 在某种程度上解决了这个问题,通过直接输入信息绕道传到输出,保护信息的完整性,整个网络则只需要学习输入、输出差别的那一部分,简化学习目标和难度。注意:实线部分是深度未发生变化的连接,虚线部分是深度发生变化的连接。 对应深度有变化的连接有两种解决方案: 

  • 使用 zero-pading 进行提升深度 parameter-free。
  • 使用 1*1的卷积核提升维度 有卷积核的运算时间。

        两种方法,使用下面一种方法效果更好,但是运行会更耗时,一般还是更倾向于第一种方案节约运算成本。

 

11.2.3  残差块

在正常的神经网络中就是一层连一层,如下图所示:

        假定某段神经网络的输入是 x,期望输出是 H(x),即 H(x) 是期望的复杂潜在映射,但学习难度大;如果我们直接把输入 x 传到输出作为初始结果,通过下图“shortcut connections”,那么此时我们需要学习的目标就是 F(x)=H(x)-x,于是 ResNet 相当于将学习目标改变了,不再是学习一个完整的输出,而是最优解 H(X) 全等映射 x 的差值,即残差 F(x) = H(x) - x

        此时,梯度的计算公式\frac{\partial ( H(x))}{x}= \frac{\partial ( F(x))}{x}+1,确保梯度大于等于1,就不会造成梯度消失。

 11.2.4 ResNet 结构

Residual Block 实现

值得注意的是,我们需要输入通道和输出通道一致

第一层中先做卷积在做relu(conv1(x)),第二层中做卷积conv2(y),最后返回relu(x+y)

        代码演示:

import torch
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets,transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt

#1.准备数据
#1.1 定义transform
transform=transforms.Compose([transforms.ToTensor(),
                              transforms.Normalize((0.1307),(0.3081))])

#1.2 下载数据
trainset = datasets.MNIST('../dataset/mnist',
                               train=True,
                               download=True,
                               transform=transform)
#1.3 定义dataloader
train_loader = DataLoader(trainset,batch_size=64,shuffle=True)

#1.4测试集
testset=datasets.MNIST(root='../dataset/mnist',
                       train=False,
                       download=True,
                       transform=transform)
test_loader = DataLoader(testset,batch_size=64,shuffle=False)


#2.定义网络
#2.1 定义残差块
class ResidualBlock(torch.nn.Module):
    def __init__(self, channels):
        super(ResidualBlock, self).__init__()
        self.channels = channels
        self.conv1 =torch.nn.Conv2d(channels, channels,
        kernel_size=3, padding=1)
        self.conv2 = torch.nn.Conv2d(channels, channels,
        kernel_size=3, padding=1)
    def forward(self, x):
        y = F.relu(self.conv1(x))
        y = self.conv2(y)
        return F.relu(x + y)
        # 注:这里的x + y是残差单元的核心,即残差单元的输出等于输入与残差单元输出的和,
        # 其中残差单元输出经过两次卷积后与输入相加,再经过激活函数ReLU。

#2.2 定义网络结构
class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = torch.nn.Conv2d(1, 16, kernel_size=5)
        self.conv2 = torch.nn.Conv2d(16, 32, kernel_size=5)
        self.mp = torch.nn.MaxPool2d(2)
        self.rblock1 = ResidualBlock(16)
        self.rblock2 = ResidualBlock(32)
        self.fc =torch.nn.Linear(512, 10)

    def forward(self, x):
        in_size = x.size(0)
        x = self.mp(F.relu(self.conv1(x)))
        x = self.rblock1(x)
        x = self.mp(F.relu(self.conv2(x)))
        x = self.rblock2(x)
        x = x.view(in_size, -1)
        x = self.fc(x)
        return x

model=Net()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)

#3.定义损失函数和优化器
criterion=torch.nn.CrossEntropyLoss()
optimizer=optim.SGD(model.parameters(),lr=0.01,momentum=0.5)

#4.训练网络
#4.1 训练函数
def train(epoch):
    running_loss=0.0
    for i,data in enumerate(train_loader,0):
        inputs,labels=data
        inputs,labels=inputs.to(device),labels.to(device)
        optimizer.zero_grad()

        #forward + backward + update
        outputs=model(inputs)
        loss=criterion(outputs,labels)
        loss.backward()
        optimizer.step()
        running_loss+=loss.item()
        if i%300==299:
            print('[%d, %5d] loss: %.3f' %(epoch+1,i+1,running_loss/300))
            running_loss=0.0

#4.2 测试函数
acuracy_list=[]
def Net_test():
    correct=0
    total=0
    with torch.no_grad():
        for data in test_loader:
            inputs,targets=data
            inputs,targets=inputs.to(device),targets.to(device)
            outputs=model(inputs)
            _,predicted=torch.max(outputs.data,1)
            total+=targets.size(0)
            correct+=predicted.eq(targets.data).sum().item()
    print('Accuracy of the network  test : %.2f %%' % (100.0*correct/total))
    acuracy_list.append(100.0*correct/total)


#4.开始训练
for epoch in range(10):
    train(epoch)
    Net_test()

#5.绘制准确率变化图
epochs=list(range(len(acuracy_list)))
plt.plot(epochs,acuracy_list)
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Accuracy of the network')
plt.show()

        运行结果:

 


网站公告

今日签到

点亮在社区的每一天
去签到