deeplearn-cv

传统CNN网络学习记录

CNN卷积神经网络算法原理

CNN结构

全连接神经网络的整体结构

image-20240908155521888

image-20240908155532331

全连接神经网络的结构单元

image-20240908155603892

神经元的数学表达式为·

$a = h(w*x+b)$

为什么要加入激活函数?

image-20240908155724536

这里我们考虑把线性函数 $ℎ(𝑥)=𝑐𝑥$ 作为 激活函数,把 $𝑦(𝑥)=ℎ(ℎ(ℎ(𝑥)))$ 的运算 对应3层神经网络,这个运算会进行 $y(x)=ccc*x$的乘法运算,但是同 样的处理可以由$𝑦(𝑥)=𝑎𝑥$(注意,这里 $ a=𝑐∗∗3$)一次乘法运算(既没有隐藏 层的神经网络)来表示。

激活函数

Sigmoid函数

Sigmoid函数的公式和导数如式表示:

​ $y=\frac{1}{1+e^-z}$ ——-> $y’=y(1-y)$

image-20240908160616237

Sigmoid函数优点:

  1. 简单、非常适合分类任务

Sigmoid函数缺点

  1. 反向传播训练时有梯度消失的问题;
  2. 输出值区间为(0,1),关于0不对称;
  3. 梯度更新在不同方向走得太远,使得优化难度 增大,训练耗时

Tanh函数

Tanh函数和公式和导数如式所示:

​ $y = \frac{e^z - e^-z}{e^z + e^-z}$ ——-> $y‘ = 1 -y^2$

Tanh函数优点:

  1. 解决了Sigmoid函数输出值非0对称的问题

  2. 训练比Sigmoid函数快,更容易收敛;

Tanh函数缺点:

  1. 反向传播训练时有梯度消失的问题;
  2. Tanh函数和Sigmoid函数非常相似。

ReLU函数

ReLU函数的公式和导数如式所示:

image-20240908161144681

ReLU函数优点:

  1. 解决了梯度消失的问题;
  2. 计算更为简单,没有Sigmoid函数和Tanh函数 的指数运算;

ReLU函数缺点:

  1. 训练时可能出现神经元死亡;

Leaky ReLU函数

Leaky ReLU函数的公式和导数如式所示:

image-20240908161307611

Leaky ReLU函数优点:

  1. 解决了ReLU的神经元死亡问题;

Leaky ReLU函数缺点:

  1. 无法为正负输入值提供一致的关系预测(不同区间函数不同)

前向传播算法

image-20240908161435997

  1. image-20240908161446375

  2. image-20240908161459316

损失函数

梯度下降法

LeNet&AleNet

LeNet-5

结构图

image-20240908135229571

import torch
from torch import nn
from torchsummary import summary
import torch.nn.functional as F




'''
第1层输入层: Input为227×227×3

第2层卷积层: Input为227×227×3,卷积核为11×11×3× 96 ;stride=4,output为55×55×96
第3层最大池化层: Input为55×55×96,池化感受野为3×3,stride=2,output为27×27×96

第4层卷积层: Input为27×27×96,卷积核为5×5×96× 256 ;stride=1,padding=2 ,output为27×27×256
第5层最大池化层: Input为27×27×256,池化感受野为3 ×3,stride=2,output为13×13×256

第6层卷积层: Input为13×13×256,卷积核为3×3×256 × 384,stride=1,padding=1,output 为13×13×384

第7层卷积层: Input为13×13×384,卷积核为3×3×384 × 384,stride=1,padding=1,output 为13×13×384。

第8层卷积层: Input 13×13×384,卷积核为3×3×384 × 256,stride=1,padding=1,output 为13×13×256

第9层最大池化层: Input为13×13×256,池化感受野为3 ×3,stride=2,output 为6×6×256,Flatten操作,通过展平得到9216个数据后与之后的全连接层相连。

第10~12层全连接层:第10~12层神经元个数分别为4096,4096,1000。其中前两层在使用relu后还使用了Dropout对神经元随机失活,最后一层全连接层用softmax输出1000个分类(这里要说的是,softmax输出1000个分类原因是当时该网络设计是为了参加
ImageNet这个比赛,该比赛最后是对1000个物体进行分类)

'''


'''
in_channels 
out_channels 
kernel_size  
stride  步幅
padding 填充

'''

class AlexNet(nn.Module):
    def __init__(self):
        # 继承
        super(AlexNet,self).__init__()
        self.relu = nn.ReLU() # 定义激活函数
        self.c1 = nn.Conv2d(in_channels=1,out_channels=96,kernel_size=11,stride=4)
        self.s2 = nn.MaxPool2d(kernel_size=3,stride=2)
        self.c3 = nn.Conv2d(in_channels=96,out_channels=256,kernel_size=5,padding=2)
        self.s4 = nn.MaxPool2d(kernel_size=3,stride=2)
        self.c5 = nn.Conv2d(in_channels=256,out_channels=384,kernel_size=3,padding=1)
        self.c6 = nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, padding=1)
        self.c7 = nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, padding=1)
        self.s8 = nn.MaxPool2d(kernel_size=3,stride=2)
        self.flatten = nn.Flatten()
        # 全连接层
        self.f1 = nn.Linear(in_features=6*6*256,out_features=4096)
        self.f2 = nn.Linear(in_features=4096,out_features=4096)
        self.f3 = nn.Linear(in_features=4096,out_features=10) # 最后的输出为任务而定

    def forward(self,x):
        # 每次经过卷积都应该有个relu函数激活
        x = self.relu(self.c1(x))
        x = self.s2(x)
        x = self.relu(self.c3(x))
        x = self.s4(x)
        x = self.relu(self.c5(x))
        x = self.relu(self.c6(x))
        x = self.relu(self.c7(x))
        x = self.s8(x)

        x = self.flatten(x)

        x = self.relu(self.f1(x))
        x = F.dropout(x,0.5) # 神经元随机失活,有0.5的神经元失活
        x = self.relu(self.f2(x))
        x = F.dropout(x,0.5)
        x = self.f3(x)
        return x

if __name__ == '__main__':
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model = AlexNet()
    model = model.to(device)
    print(summary(model,(1,227,227)))

AleNet

结构图如下

image-20240908135250525

Dropout

随机失活,随机【临时】删除掉网络的一部分

import torch
from torch import nn
from torchsummary import summary
import torch.nn.functional as F




'''
第1层输入层: Input为227×227×3

第2层卷积层: Input为227×227×3,卷积核为11×11×3× 96 ;stride=4,output为55×55×96
第3层最大池化层: Input为55×55×96,池化感受野为3×3,stride=2,output为27×27×96

第4层卷积层: Input为27×27×96,卷积核为5×5×96× 256 ;stride=1,padding=2 ,output为27×27×256
第5层最大池化层: Input为27×27×256,池化感受野为3 ×3,stride=2,output为13×13×256

第6层卷积层: Input为13×13×256,卷积核为3×3×256 × 384,stride=1,padding=1,output 为13×13×384

第7层卷积层: Input为13×13×384,卷积核为3×3×384 × 384,stride=1,padding=1,output 为13×13×384。

第8层卷积层: Input 13×13×384,卷积核为3×3×384 × 256,stride=1,padding=1,output 为13×13×256

第9层最大池化层: Input为13×13×256,池化感受野为3 ×3,stride=2,output 为6×6×256,Flatten操作,通过展平得到9216个数据后与之后的全连接层相连。

第10~12层全连接层:第10~12层神经元个数分别为4096,4096,1000。其中前两层在使用relu后还使用了Dropout对神经元随机失活,最后一层全连接层用softmax输出1000个分类(这里要说的是,softmax输出1000个分类原因是当时该网络设计是为了参加
ImageNet这个比赛,该比赛最后是对1000个物体进行分类)

'''


'''
in_channels 
out_channels 
kernel_size  
stride  步幅
padding 填充

'''

class AlexNet(nn.Module):
    def __init__(self):
        # 继承
        super(AlexNet,self).__init__()
        self.relu = nn.ReLU() # 定义激活函数
        self.c1 = nn.Conv2d(in_channels=1,out_channels=96,kernel_size=11,stride=4)
        self.s2 = nn.MaxPool2d(kernel_size=3,stride=2)
        self.c3 = nn.Conv2d(in_channels=96,out_channels=256,kernel_size=5,padding=2)
        self.s4 = nn.MaxPool2d(kernel_size=3,stride=2)
        self.c5 = nn.Conv2d(in_channels=256,out_channels=384,kernel_size=3,padding=1)
        self.c6 = nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, padding=1)
        self.c7 = nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, padding=1)
        self.s8 = nn.MaxPool2d(kernel_size=3,stride=2)
        self.flatten = nn.Flatten()
        # 全连接层
        self.f1 = nn.Linear(in_features=6*6*256,out_features=4096)
        self.f2 = nn.Linear(in_features=4096,out_features=4096)
        self.f3 = nn.Linear(in_features=4096,out_features=10) # 最后的输出为任务而定

    def forward(self,x):
        # 每次经过卷积都应该有个relu函数激活
        x = self.relu(self.c1(x))
        x = self.s2(x)
        x = self.relu(self.c3(x))
        x = self.s4(x)
        x = self.relu(self.c5(x))
        x = self.relu(self.c6(x))
        x = self.relu(self.c7(x))
        x = self.s8(x)

        x = self.flatten(x)

        x = self.relu(self.f1(x))
        x = F.dropout(x,0.5) # 神经元随机失活,有0.5的神经元失活
        x = self.relu(self.f2(x))
        x = F.dropout(x,0.5)
        x = self.f3(x)
        return x

if __name__ == '__main__':
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model = AlexNet()
    model = model.to(device)
    print(summary(model,(1,227,227)))

VGG-16

import torch
from torch import nn # 撰写网络
from torchsummary import summary # 验证

"使用nn.Sequential来模块化定义"

class VGG16(nn.Module):
    def __init__(self):
        super(VGG16,self).__init__()
        # nn.Sequential 是序列的意思
        self.block1 = nn.Sequential(
            nn.Conv2d(in_channels=1,out_channels=64,kernel_size=3,padding=1,stride=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=1, stride=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.block2 = nn.Sequential(
            nn.Conv2d(in_channels=64,out_channels=128,kernel_size=3,padding=1,stride=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=1, stride=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.block3 = nn.Sequential(
            nn.Conv2d(in_channels=128,out_channels=256,kernel_size=3,padding=1,stride=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=1, stride=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=1, stride=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.block4 = nn.Sequential(
            nn.Conv2d(in_channels=256,out_channels=512,kernel_size=3,padding=1,stride=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1, stride=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1, stride=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.block5 = nn.Sequential(
            nn.Conv2d(in_channels=512,out_channels=512,kernel_size=3,padding=1,stride=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1, stride=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1, stride=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        # 全连接层
        self.block6 = nn.Sequential(
            nn.Flatten(), # 展开
            nn.Linear(in_features=7*7*512,out_features=4096),
            nn.ReLU(),
            nn.Linear(in_features=4096,out_features=4096),
            nn.ReLU(),
            nn.Linear(in_features=4096,out_features=10),
        )

        # 参数初始化
        for m in self.modules():
            if isinstance(m,nn.Conv2d):
                nn.init.kaiming_normal_(m.weight,nonlinearity='relu')
                if m.bias is not None:
                    nn.init.constant_(m.bias,0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight,0,0.01)
                if m.bias is not None:
                    nn.init.constant_(m.bias,0)

    def forward(self,x):
        x = self.block1(x)
        x = self.block2(x)
        x = self.block3(x)
        x = self.block4(x)
        x = self.block5(x)
        x = self.block6(x)

        return x

if __name__ == '__main__':
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model = VGG16().to(device)
    print(summary(model,(1,224,224)))

GoogLeNet

GoogLeNet 提出

import torch
from torch import nn
from torchsummary import summary

'''Inception模块来实现模块化搭建'''
class Inception(nn.Module):
    def __init__(self,in_channels,c1,c2,c3,c4): # c1,c2,c3,c4为输出通道数和输出通道数的数量
        super(Inception,self).__init__()
        self.rulu = nn.ReLU()

        # 路线1,单1*1卷积层
        self.p1 = nn.Conv2d(in_channels=in_channels,out_channels=c1,kernel_size=1)

        # 路线2, 1*1卷积,3*3卷积
        self.p2_1 = nn.Conv2d(in_channels=in_channels,out_channels=c2[0],kernel_size=1)
        self.p2_2 = nn.Conv2d(in_channels=c2[0],out_channels=c2[1],kernel_size=3,padding=1)

        # 路线3, 1*1卷积,5*5卷积
        self.p3_1 = nn.Conv2d(in_channels=in_channels,out_channels=c3[0],kernel_size=1)
        self.p3_2 = nn.Conv2d(in_channels=c3[0],out_channels=c3[1],kernel_size=5,padding=2)

        # 路径4,  3*3最大池化,1*1卷积
        self.p4_1 = nn.MaxPool2d(kernel_size=3,padding=1,stride=1) # 池化的步幅会和kernel_size相等
        self.p4_2 = nn.Conv2d(in_channels=in_channels,out_channels=c4,kernel_size=1)

    # 因为是并行的结构
    def forward(self,x):
        p1 = self.rulu(self.p1(x))
        # 路径2
        p2 = self.rulu(self.p2_2(self.rulu(self.p2_1(x))))
        # 路线3
        p3 = self.rulu(self.p3_2(self.rulu(self.p3_1(x))))
        # 路线4
        p4 = self.rulu(self.p4_2(self.p4_1(x)))

        return torch.cat((p1,p2,p3,p4),dim=1) # 特征融合,dim=1指定为通道

class GoogLeNet(nn.Module):
    def __init__(self,Inception):
        super(GoogLeNet,self).__init__()
        # 模块1
        self.b1 = nn.Sequential(
            nn.Conv2d(in_channels=1,out_channels=64,kernel_size=7,stride=2,padding=3),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3,padding=1,stride=2)
        )

        # 模块2
        self.b2 = nn.Sequential(
            nn.Conv2d(in_channels=64,out_channels=64,kernel_size=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=64, out_channels=192, kernel_size=3,padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3,padding=1,stride=2)
        )

        '''以池化层为结尾作为区分'''
        # 搭建Inception模块
        self.b3 = nn.Sequential(
            Inception(192,64, (96,128), (16,32), 32),
            Inception(256,128, (128,192), (32,96), 64),
            nn.MaxPool2d(kernel_size=3,padding=1,stride=2),
        )

        #  5个Inception
        self.b4 = nn.Sequential(
            Inception(480,192, (96,208), (16,48), 64),
            Inception(512,160, (112,224), (24,64), 64),
            Inception(512,128, (128, 256), (24, 64), 64),
            Inception(512,112, (128, 288), (32, 64), 64),
            Inception(528,256, (160, 320), (32, 128), 128),
            nn.MaxPool2d(kernel_size=3,padding=1,stride=2),
        )

        # 2个Inception
        self.b5 = nn.Sequential(
            Inception(832,256,(160,320),(32,128),128),
            Inception(832,384,(192,384),(48,128),128),
            nn.AdaptiveAvgPool2d((1,1)), # 全局平均池化
            nn.Flatten(),
            # 全连接层
            nn.Linear(1024,10)

        )

        # 模型初始化
        for m in self.modules():
            if isinstance(m,nn.Conv2d):
                nn.init.kaiming_normal_(m.weight,mode='fan_out',nonlinearity='relu')
                if m.bias is not None:
                    nn.init.constant_(m.bias,0)

                elif isinstance(m,nn.Linear):
                    nn.init.normal_(m.weight,0,0.01) # 线性全连接层使用正态分布来初始化参数
                    if m.bias is not None:
                        nn.init.constant_(m.bias,0)
    # 前向传播
    def forward(self,x):
        x = self.b1(x)
        x = self.b2(x)
        x = self.b3(x)
        x = self.b4(x)
        x = self.b5(x)
        return x


'''模型测试'''
if __name__ == '__main__':
    device = torch.device('cuda' if torch.cuda.is_available() else 'cup')
    model = GoogLeNet(Inception).to(device)
    print(summary(model,(1,224,224)))

ResNet

ResNet 残差网络,提出残差单元和凯明初始化

网络结构 ResNet-5

image-20240908134834921

残差块:

image-20240908134911244

代码如下

import torch
from torch import nn
from torchsummary import summary
import torch.nn.functional as F




'''
第1层输入层: Input为227×227×3

第2层卷积层: Input为227×227×3,卷积核为11×11×3× 96 ;stride=4,output为55×55×96
第3层最大池化层: Input为55×55×96,池化感受野为3×3,stride=2,output为27×27×96

第4层卷积层: Input为27×27×96,卷积核为5×5×96× 256 ;stride=1,padding=2 ,output为27×27×256
第5层最大池化层: Input为27×27×256,池化感受野为3 ×3,stride=2,output为13×13×256

第6层卷积层: Input为13×13×256,卷积核为3×3×256 × 384,stride=1,padding=1,output 为13×13×384

第7层卷积层: Input为13×13×384,卷积核为3×3×384 × 384,stride=1,padding=1,output 为13×13×384。

第8层卷积层: Input 13×13×384,卷积核为3×3×384 × 256,stride=1,padding=1,output 为13×13×256

第9层最大池化层: Input为13×13×256,池化感受野为3 ×3,stride=2,output 为6×6×256,Flatten操作,通过展平得到9216个数据后与之后的全连接层相连。

第10~12层全连接层:第10~12层神经元个数分别为4096,4096,1000。其中前两层在使用relu后还使用了Dropout对神经元随机失活,最后一层全连接层用softmax输出1000个分类(这里要说的是,softmax输出1000个分类原因是当时该网络设计是为了参加
ImageNet这个比赛,该比赛最后是对1000个物体进行分类)

'''


'''
in_channels 
out_channels 
kernel_size  
stride  步幅
padding 填充

'''

class AlexNet(nn.Module):
    def __init__(self):
        # 继承
        super(AlexNet,self).__init__()
        self.relu = nn.ReLU() # 定义激活函数
        self.c1 = nn.Conv2d(in_channels=1,out_channels=96,kernel_size=11,stride=4)
        self.s2 = nn.MaxPool2d(kernel_size=3,stride=2)
        self.c3 = nn.Conv2d(in_channels=96,out_channels=256,kernel_size=5,padding=2)
        self.s4 = nn.MaxPool2d(kernel_size=3,stride=2)
        self.c5 = nn.Conv2d(in_channels=256,out_channels=384,kernel_size=3,padding=1)
        self.c6 = nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, padding=1)
        self.c7 = nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, padding=1)
        self.s8 = nn.MaxPool2d(kernel_size=3,stride=2)
        self.flatten = nn.Flatten()
        # 全连接层
        self.f1 = nn.Linear(in_features=6*6*256,out_features=4096)
        self.f2 = nn.Linear(in_features=4096,out_features=4096)
        self.f3 = nn.Linear(in_features=4096,out_features=10) # 最后的输出为任务而定

    def forward(self,x):
        # 每次经过卷积都应该有个relu函数激活
        x = self.relu(self.c1(x))
        x = self.s2(x)
        x = self.relu(self.c3(x))
        x = self.s4(x)
        x = self.relu(self.c5(x))
        x = self.relu(self.c6(x))
        x = self.relu(self.c7(x))
        x = self.s8(x)

        x = self.flatten(x)

        x = self.relu(self.f1(x))
        x = F.dropout(x,0.5) # 神经元随机失活,有0.5的神经元失活
        x = self.relu(self.f2(x))
        x = F.dropout(x,0.5)
        x = self.f3(x)
        return x

if __name__ == '__main__':
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model = AlexNet()
    model = model.to(device)
    print(summary(model,(1,227,227)))
github