pytorch卷积神经网络CNN 代码

卷积神经网络原理

 一文教你搞懂2D卷积和3D卷积

单通道卷积

多通道卷积

Depthwise conv

深度可分离卷积同时考虑图像的区域和通道(深度可分离卷积首先只考虑区域depth_wise,然后再只考虑通道point_wise)实现了图像的区域和通道的完全分离。

(a)是常规卷积

(b)(c)是深度可分离卷积,

(b)是depth_wise conv,用于收集每一channel的特征,

(c)是point_wise conv,将原通道数为M的feature map 调整为N。

举个例子比较参数量:假设input.shape = [M, H, W] output.shape = [N, H, W]

(a)常规卷积参数量=k* k * M * N

(b)深度可分离卷积参数量=k * k *M + 1*1*M*N

import torch
import torch.nn as nn
...
model = nn.Sequential(
        nn.Conv2d(in_channels = in_channel, out_channels = in_channel, 
kernel_size = kernel_size, stride = stride, padding = 1, dilation = dilation, group = in_channel),
        nn.Conv2d(in_channels = in_channel, out_channels = out_channel kernel_size = 1, padding = 0)
        )

Grop conv

具体操作:如上图(b)所示,将(a)中常规卷积的输入分成g组,每个卷积核也相应地分成g组,在对应的组内分别做卷积,每组卷积都生成一个feature map,共生成g个feature map。
假设 input.shape = [c1, H, W] 、 output.shape = [c2, H, W]、 kernel_size=k*k
(a)常规卷积参数量=k * k * c1 * c2
(b)分组卷积参数量=k * k * (c1/g) * (c2/g) * g =k* k * c1 * c2 / g,其中g为分组数量。

import torch
import torch.nn as nn
...
model = nn.Conv2d(in_channels = in_channel, out_channels = out_channel, kernel_size = kernel_size, stride = stride, padding = 1, dilation = dilation, group = group_num)

CondenseNet

轻量化神经网络CondenseNet--对DenseNet的进一步改进-CSDN博客

CondenseNet:可学习分组卷积,原作对DenseNet的轻量化改造 | CVPR 2018 - 知乎

代码

Big-Little Net

代码

瓶颈结构(Bottleneck)

ResNet的核心内容之一即“Deeper Bottleneck Architectures”(简称DBA),一言概之,bottleneck是一种特殊的残差结构。

左图是普通的残差结构,右图是瓶颈结构。具体而言,block的输入和输出channel_num是一样的(上右图中是256,左图为64),

而在block结构中的channel_num(上右图中是64)是小于输入/输出channel_num(256),很形象。也就是先降维再升维。

换成bottleneck design以后,网络的参数减少了很多,深度也加深了,训练也就相对容易一些。

3DConv

3D卷积使用的数据和2D卷积最大的不同就在于数据的时序性。3D卷积中的数据通常是视频的多个帧或者是一张医学图像的多个分割图像堆叠在一起,这样每帧图像之间就有时间或者空间上的联系。

下面这张图可以说明3D卷积的大致过程。左边是输入层,此时我们在空间上看到的3维(即深度方向)不再表示通道数,而是图片的帧数,也就是说这其实是图像中的单个通道的一帧帧图片堆叠之后的效果。

因此对于三通道(RGB)图像组成的一段视频,实际应该包含3个这样的3D数据,这里只是其中一个通道。Convolution Filter的深度不再与通道数相同(很显然),只需要满足<=帧数即输入层的深度即可。总结一下,输入层现在的维度是(Channel×Depth1×Height1×Width1),Filter的维度是(Depth2×Height2×Width2)。
    自然地,卷积核就可以在深度、高度、宽度三个方向上自由移动,两个立方体之间的每一层进行卷积然后再在深度上逐元素相加,得到一个数据,形成一个平面后,卷积核向深度方向移动,继续卷积,于是就输出了一个3D的Feature map
    有人可能会问,一个通道的多个帧和一个Filter生成了一个3D Feature map,那三通道的图像卷积最后就一定生成三个3D Feature map吗,它们的转化关系究竟是怎么样的??关于这里的疑惑,我们放到代码部分来讲解。先再次强调,输出层的通道数就等价于输出层的Feature map数!!

torch.nn.Conv3d(in_channelsout_channelskernel_sizestride=1padding=0dilation=1groups=1bias=Truepadding_mode='zeros'device=Nonedtype=None)

参数:

  • in_channels ( int ) – 输入通道数

  • out_channels ( int ) – 输出的通道数

  • kernel_size ( int or tuple ) – 卷积核的大小

  • stride ( int or tuple , optional ) – 卷积的步幅。默认值:1

  • padding ( int , tuple or str , optional ) – 添加到输入的所有六个边的填充。默认值:0

  • padding_mode ( str , optional ) – 'zeros''reflect','replicate''circular'. 默认:'zeros'

  • dilation ( int or tuple , optional ) – 内核元素之间的间距。默认值:1

  • groups ( int , optional ) – 从输入通道到输出通道的阻塞连接数。默认值:1

  • bias ( bool , optional ) – 如果True, 向输出添加可学习的偏差。默认:True

Conv3d的维度:

输入是5维的Tensor(N,C_in,D_in,H_in,W_in), 

输出为(N,C_out,D_out,H_out,W_out)

权重为(N, C_in, C_out, K, K)

m = nn.Conv3d(16, 33, (3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1))
input = torch.randn(20, 16, 10, 50, 100)
output = m(input) # torch.Size([20, 33, 10, 50, 100])

手写卷积

import torch
from torch import nn
from d2l import torch as d2l

def corr2d(X, K):  #@save
    """计算二维互相关运算"""
    h, w = K.shape
    Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i, j] = (X[i:i + h, j:j + w] * K).sum()
    return Y

X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
corr2d(X, K)

卷积层

卷积层对输入和卷积核权重进行互相关运算,并在添加标量偏置之后产生输出。
所以,卷积层中的两个被训练的参数是卷积核权重和标量偏置。
就像我们之前随机初始化全连接层一样,在训练基于卷积层的模型时,我们也随机初始化卷积核权重。

基于上面定义的corr2d函数[实现二维卷积层]。在__init__构造函数中,将weightbias声明为两个模型参数。前向传播函数调用corr2d函数并添加偏置。

class Conv2D(nn.Module):
    def __init__(self, kernel_size):
        super().__init__()
        self.weight = nn.Parameter(torch.rand(kernel_size))
        self.bias = nn.Parameter(torch.zeros(1))

    def forward(self, x):
        return corr2d(x, self.weight) + self.bias

图像中目标的边缘检测

如下是[卷积层的一个简单应用:]通过找到像素变化的位置,来(检测图像中不同颜色的边缘)。
首先,我们构造一个像素的黑白图像。中间四列为黑色(),其余像素为白色()。

X = torch.ones((6, 8))
X[:, 2:6] = 0

#接下来,我们构造一个高度为 、宽度为 的卷积核K。当进行互相关运算时,如果水平相邻的两元素相同,则输出为零,否则输出为非零。
K = torch.tensor([[1.0, -1.0]])

Y = corr2d(X, K)

#现在我们将输入的二维图像转置,再进行如上的互相关运算。 其输出如下,之前检测到的垂直边缘消失了。 不出所料,这个[卷积核K只可以检测垂直边缘],无法检测水平边缘。

corr2d(X.t(), K)

学习卷积核

如果我们只需寻找黑白边缘,那么以上[1, -1]的边缘检测器足以。然而,当有了更复杂数值的卷积核,或者连续的卷积层时,我们不可能手动设计滤波器。那么我们是否可以[学习由X生成Y的卷积核]呢?

现在让我们看看是否可以通过仅查看“输入-输出”对来学习由X生成Y的卷积核。
我们先构造一个卷积层,并将其卷积核初始化为随机张量。接下来,在每次迭代中,我们比较Y与卷积层输出的平方误差,然后计算梯度来更新卷积核。为了简单起见,我们在此使用内置的二维卷积层,并忽略偏置。

# 构造一个二维卷积层,它具有1个输出通道和形状为(1,2)的卷积核
conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)

# 这个二维卷积层使用四维输入和输出格式(批量大小、通道、高度、宽度),
# 其中批量大小和通道数都为1
X = X.reshape((1, 1, 6, 8))
Y = Y.reshape((1, 1, 6, 7))
lr = 3e-2  # 学习率

for i in range(10):
    Y_hat = conv2d(X)
    l = (Y_hat - Y) ** 2
    conv2d.zero_grad()
    l.sum().backward()
    # 迭代卷积核
    conv2d.weight.data[:] -= lr * conv2d.weight.grad
    if (i + 1) % 2 == 0:
        print(f'epoch {i+1}, loss {l.sum():.3f}')

在10次迭代之后,误差已经降到足够低。现在我们来看看我们[所学的卷积核的权重张量]。

conv2d.weight.data.reshape((1, 2))

LeNet

import torch
from torch import nn
from d2l import torch as d2l

net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Flatten(),
    nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
    nn.Linear(120, 84), nn.Sigmoid(),
    nn.Linear(84, 10))

X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape: \t',X.shape)

卷积神经网络pytorch 代码实现

# 读取数据
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torchvision import datasets,transforms
# transforms  进行预处理,比如进行tensor转换
# import matplotlib.pyplot as plt
import numpy as np
#全连接:batch*28*28,全连接各个像素点之间无关
# cnn:batch*1*28*28  ,多了一个参数channel,卷积会综合考虑一个窗口之间的关系,因此各个像素点并不是独立的,卷积网络更适合处理图像数据
# 定义超参数
input_size = 28  #图像的总尺寸28*28
num_classes = 10  #标签的种类数
num_epochs = 3  #训练的总循环周期
batch_size = 64  #一个撮(批次)的大小,64张图片

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
if torch.cuda.is_available():
    print('GPU is available!')
else:
    print('GPU is not available!')

# 训练集
train_dataset = datasets.MNIST(root='./data',
                            train=True,
                            transform=transforms.ToTensor(),
                            download=True)
# 测试集
test_dataset = datasets.MNIST(root='./data',
                           train=False,
                           transform=transforms.ToTensor())

# 构建batch数据
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                           batch_size=batch_size,
                                           shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                           batch_size=batch_size,
                                           shuffle=True)
# 卷积网络模块构建
# 一般卷积层,relu层,池化层可以写成一个套餐
# 注意卷积最后结果还是一个特征图,需要把图转换成向量才能做分类或者回归任务
# 定义一个网络
class CNN(nn.Module):
    def __init__(self):
        #         构造函数
        # 卷积网络一般是组合进行的:conv pool relu可以当一个组合
        super(CNN, self).__init__()
        self.conv1 = nn.Sequential(  # 输入大小 (1, 28, 28)
            nn.Conv2d(  # 2d卷积做任务
                in_channels=1,  # 灰度图
                out_channels=16,  # 要得到几多少个特征图,就是卷积核的个数,相当于有16个卷积核
                kernel_size=5,  # 卷积核大小 5*5的
                stride=1,  # 步长
                padding=2,  # 如果希望卷积后大小跟原来一样,需要设置padding=(kernel_size-1)/2 if stride=1,一般是这么希望的
                #                                             如果不能整除pytorch采用向下取整
            ),  # 输出的特征图为 (16, 28, 28)
            nn.ReLU(),  # relu层
            nn.MaxPool2d(kernel_size=2),  # 进行池化操作(2x2 区域), 输出结果为: (16, 14, 14),一般是pooling后是之前的一半
        )
        self.conv2 = nn.Sequential(  # 下一个套餐的输入 (16, 14, 14)
            nn.Conv2d(16, 32, 5, 1, 2),  # 输出 (32, 14, 14)
            nn.ReLU(),  # relu层
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(2),  # 输出 (32, 7, 7)
        )

        self.conv3 = nn.Sequential(  # 下一个套餐的输入 (32, 7, 7)
            nn.Conv2d(32, 64, 5, 1, 2),  # 输出 (64, 7, 7)
            nn.ReLU(),  # 输出 (64, 7, 7)
        )
        # 只有pool的时候才会筛选特征

        self.out = nn.Linear(64 * 7 * 7, 10)  # 全连接层得到的结果,最后的任务是10分类任务,进行一个wx+b的操作去做分类

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = x.view(x.size(0), -1)  # flatten操作,结果为:(batch_size, 32 * 7 * 7),和reshape操作一样
        # reshape操作:总的大小是不变的,提供一个维度后,后边的维度自动计算
        # 比如当前的x:64*7*7,x.size:64,也就是要从三维转成两维,总的大小不变,就变为64*49这样,-1可以简单的看成一个占位符号
        # 变换维度,开始是64*7*7,转成batchsize*特征个数,比如64*49
        output = self.out(x)
        return output
# 定义准确率
def accuracy(predictions, labels):
    pred = torch.max(predictions.data, 1)[1] # 最大值是多少,最大值的索引,只要索引就可以
    rights = pred.eq(labels.data.view_as(pred)).sum()
    return rights, len(labels)
# 训练网络模型
# 实例化
net = CNN().to(device)
# 损失函数
criterion = nn.CrossEntropyLoss().to(device)
# 优化器,学习率是0.001
optimizer = optim.Adam(net.parameters(), lr=0.001)  # 定义优化器,普通的随机梯度下降算法
# 开始训练循环
for epoch in range(num_epochs):
    # 当前epoch的结果保存下来
    train_rights = []

    for batch_idx, (data, target) in enumerate(train_loader):  # 针对容器中的每一个批进行循环
        net.train()
        data = data.to(device)
        target =target.to(device)
        output = net(data)
        loss = criterion(output, target)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        right = accuracy(output, target)
        train_rights.append(right)
        # 每一个batch都进行训练,每一百个batch进行一次评估
        if batch_idx % 100 == 0:

            net.eval()
            val_rights = []

            for (data, target) in test_loader:
                data = data.to(device)
                target =target.to(device)
                output = net(data)
                right = accuracy(output, target)
                val_rights.append(right)

            # 准确率计算
            train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))
            val_r = (sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))

            print('当前epoch: {} [{}/{} ({:.0f}%)]\t损失: {:.6f}\t训练集准确率: {:.2f}%\t测试集正确率: {:.2f}%'.format(
                epoch, batch_idx * batch_size, len(train_loader.dataset),
                       100. * batch_idx / len(train_loader),
                       loss.data,
                       100. * train_r[0].cpu().numpy() / train_r[1],
                       100. * val_r[0].cpu().numpy() / val_r[1]))
  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值