Pytorch:十、卷积神经网络(基础)


  • 全连接网络:网络中所使用的全都是线性层,并且串行器来,就跟上一章的那个模型一样的样子:

线性层中输入\出层的任意两个节点之间都有权重,即所有输入节点都要参与到下一层输出的计算中去。我们就把这样的线性层叫做全连接层full connected(FC)

但是做成全连接层也有一些不足:

以上图为例,将一个数字转换为1维的长串时,可能会丢失一些空间信息,即在长串中隔得很远的两个点可能在原来的数字图像中是相邻的。这说明:做成全连接之后会丧失一些原有的空间信息;

相应的,CNN则是把图像按照原始空间结构进行保存,从而保留住了许多空间信息。

卷积核进行的卷积计算

卷积计算:就是像上面这样,通过卷积核来对原来的图像进行压缩;

CNN流程图

不难看出,在进行下采样时,通道数不变,但是宽度和高度则是按照采样规格进行缩减;
下采样:为了减少数据量,从而降低运算需求;
最终将n1的输出映射到10维的n2去,这样就可以接上 交叉熵损失 从而计算其分布,这样就可以解决分类问题了;

通过上面的图,可以得出一套工作流程:
通过这些层,来进行维度/尺寸上的大小变化,最终将它映射到想要的输出空间中去;

特征提取器:也就是卷积,下采样等,表示通过卷积运算来找到某种特征(此阶段直接对图像进行卷积运算从而转为向量);
分类器,就是把经过特征提取后的向量放到全连接层中去做分类;

可以通过扩展来提升像素值:

将原来2* 2的图像扩展为一个3* 3的,空出来的部分用平均值来进行填充即可

注意,经过卷积操作后的通道数量也是可能发生变化的,因为原来有三个通道,现在卷积核有5个通道,这五个通道把你原来三个通道都遍历一遍(多少个卷积核就会得到不同的特征图),卷积核个数是下一层通道数

卷积的图示

将上面的过程整合一下就是:

不难看出,无论中间那个卷积核是多少层的,最后输出的结果一定是一个单通道的结果,如果想要一个多通道的结果就需要很多个卷积层,如下图所示;

这里的cat是指拼接的意思;

从这张图我们不难发现:

  1. 每个卷积核的通道数量要求和输入通道一样;
  2. 最终输出的通道数就是卷积核的数量;

如此一来就可以推出以下结论:

看看代码的计算过程:

import torch
#分别是n和m
in_channels, out_channels = 5,10
#这里的宽和高是图像的大小
w,h = 100, 100
#kernel_size表示卷积核的数量,输入一个3就是3*3,也可以直接给个数组
kernel_size = 3
#这里是指图片的数量
batch_size = 1

#生成一个输入图像
input = torch.rand(batch_size
                 ,in_channels #这个最需要关注,一定要和卷积的in一样
                 ,w
                 ,h
                 )

#Conv2d:对由多个输入平面组成的输入信号进行二维卷积
conv_layer = torch.nn.Conv2d(in_channels
                            ,out_channels
                            ,kernel_size = kernel_size
                            )

#用上面的那个卷积层来对input进行卷积并生成output
output = conv_layer(input)

print(input.shape)
#torch.Size([1, 5, 100, 100])
print(output.shape)
#torch.Size([1, 10, 98, 98])
print(conv_layer.weight.shape) #卷积层权重的形状
#torch.Size([10, 5, 3, 3]) 

卷积层中其他常见的参数:

  • padding:通过这个参数来改变输出矩阵的规格;

在外围加了一圈格子,从而使其在经过卷积核后的结果为5* 5 (7-3+1=5)的,一般都是按实际需求选择是否填充。填充的圈数就是卷积核的大小/2后取整的值

最常见的padding就是填充0,如下图所示:

而这一步也可以通过代码实现

import torch

#原来的矩阵
input = [3,4,5,6,7
        ,2,4,6,8,2
        ,1,6,7,8,4
        ,9,7,6,4,2
        ,3,7,5,4,1]
#按要求进行转换,view的四个数字分别是:batch, channel, w, h
input = torch.Tensor(input).view(1, 1, 5, 5)

#bias是加偏执量的参数,这里不需要加
conv_layer = torch.nn.Conv2d(1, 1, kernel_size = 3, padding=1, bias=False)

#构造卷积核
kernel = torch.Tensor([1,2,3,4,5,6,7,8,9]).view(1, 1, 3, 3)
#通过赋值来进行卷积层权重的初始化
conv_layer.weight.data = kernel.data

output = conv_layer(input)
print(output)
  • stride:步长,表示每次卷积之后框移动的距离;
import torch

input = [3,4,5,6,7
        ,2,4,6,8,2
        ,1,6,7,8,4
        ,9,7,6,4,2
        ,3,7,5,4,1]
input = torch.Tensor(input).view(1, 1, 5, 5)

conv_layer = torch.nn.Conv2d(1, 1, kernel_size = 3, stride=2, bias=False)

kernel = torch.Tensor([1,2,3,4,5,6,7,8,9]).view(1, 1, 3, 3)
conv_layer.weight.data = kernel.data

output = conv_layer(input)
print(output)
#tensor([[[[208., 263.],
#          [263., 167.]]]], grad_fn=<ConvolutionBackward0>)

下采样方法介绍:

  1. MaxPooling:按宽和高进行分组,然后在每组内找到最大值来作为那一组的代表;


默认stride=2。这里只能在同一个通道内进行MaxPooling,不能在不同通道之间进行;

import torch

input = [3,4,5,6
        ,2,4,6,8
        ,1,6,7,8
        ,9,7,6,4]
input = torch.Tensor(input).view(1,1,4,4)

#当kernel_size设为2时,默认步长也会变成2
maxpooling_layer = torch.nn.MaxPool2d(kernel_size=2)

output = maxpooling_layer(input)
print(output)

接下来做一个简单的CNN:

第一个卷积层:5* 5的卷积核,输入通道为1,输出通道为5;
第一个池化层:batch和10都不变,宽和高的24都减小一半;
以此类推。。。
全连接层:把结果映射成10个输出向量来作为预测值;

最后的那个分类器最在乎每个样本的元素个数,因此要看看前面的输出有多少个。但是在实操时可以先不写最后这个分类器--,定义到最后一个batch就停,然后看看它的输出是咋样的,然后根据输出来编写全连接层即可

CNN的网络架构图

在放进FC层之前要进行一次view,也就是把2044变成320再丢给FC;

import torch.nn.functional as F

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(10,20,kernel_size=5)
        self.pooling = torch.nn.MaxPool2d(2)
        self.fc = torch.nn.Linear(320,10)
        
    def forward(self, x):
        batch_size = x.size(0)
        x = self.pooling(F.relu(self.conv1(x)))
        x = self.pooling(F.relu(self.conv2(x)))
        x = x.view(batch_size, -1)
        x = self.fc(x)
        return x
    
model = Net()
#用显卡来算,就是把模型迁移到GPU上去
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#通过这句话改跑模型的设备
model.to(device)

def train(epoch):
    running_loss = 0.0
    for batch_idx, data in enumerate(train_loader, 0):
        inputs, target = data
        #把他俩都送去device上,一定要放到同一块显卡上
        inputs, target = inputs.to(device), target.to(device)
        optimizer.zero_grad()
        # forward + backward + update
        outputs = model(inputs)
        loss = criterion(outputs, target)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()
        if batch_idx % 300 == 299:
            print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 2000))
            running_loss = 0.0
            
def test():
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            inputs, target = data
            #测试这里也要放到显卡上
            inputs, target = inputs.to(device), target.to(device)
            outputs = model(inputs)
            _, predicted = torch.max(outputs.data, dim=1)
            total += target.size(0)
            correct += (predicted == target).sum().item()
        print('Accuracy on test set: %d %% [%d/%d]' % (100 * correct / total, correct, total))
用了GPU之后的结果,没给数据我们自己不好跑
  • 作业:
要求在右边,具体参数自己设置就行
class Net1(torch.nn.Module):
    def __init__(self):
        super(Net1, self).__init__()
        self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=4)#10,13,13
        self.conv2 = torch.nn.Conv2d(10,20,kernel_size=4) #20,6,6
        self.conv3 = torch.nn.Conv2d(20,40,kernel_size=4) #40,2,2
        self.pooling = torch.nn.MaxPool2d(2)
        self.fc1 = torch.nn.Linear(160,80)
        self.fc2 = torch.nn.Linear(80,40)
        self.fc3 = torch.nn.Linear(40,10)
        
    def forward(self, x):
        batch_size = x.size(0)
        x = self.pooling(F.relu(self.conv1(x)))
        x = self.pooling(F.relu(self.conv2(x)))
        x = self.pooling(F.relu(self.conv3(x)))
        x = x.view(batch_size, -1)
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.fc3(x)
        return x

不知道对不对,应该是这样?

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容