pytorch_10卷积神经网络(基础篇)

全连接层:即前面提到的全连接神经网络的线性层linear layer,每一个输入结点都要参与到下一层的任何一个输出结点上。


卷积神经网络 Convolution Neural Network

问题引入:之前在MINIST 中,用的是全连接神经网络,即将图像转化为1*28*28的张量,然后将张量转为矩阵形式输入,是一整行的数据。以这种形式可能会造成两个相邻节点在矩阵中隔得远,即做成全连接后丧失了图像的空间特征,由此引入卷积神经网络,把图像按照原始的的空间结构进行保存,从而可以保留原始的空间信息。

 

kernel是 5 * 5    (下面红色编号为特征提取Feature Exatraction, 3,4为Classification)

1.把input 进行5*5卷积,其中要注意这里通道数、W 、 H都会发生改变(当然,也可以不变),在这边,一个图像扔到卷积层中依然是一个三维的张量

2.进行2*2 的下采样通道数是不变的,但是W、H会发生改变。下采样目的为减少元素数量,降低运算需求

3.到绿色部分,进行全连接,把张量展开为向量(上一节用到的view),得到n1.

4.再一次全连接映射到十维的输出n2.

5.然后就用softmax计算交叉熵损失

即不断地进行过卷积、下采样等大小的变化,把1*28*28转化成十维的向量,即放到十维的线性空间。(先明确输出要得到啥,然后不断运用各种层进行维度上或每个维度上尺寸大小的变化,从而最终映射到我们想要的空间。)



 Convolution 卷积

一些概念:

  • 栅格图像:从自然界获取图像的方式。

   * 单色图像采集:通过光敏电阻获取像素,通过每个电阻来获得该光锥的光强。

             

    * 彩色:RGB。灰度值0-255,将数据转变为图像。

 

             

  • 矢量图象:描述圆心,直径、边、填充。这里要区分于栅格图像,栅格图像是很多个像素提前存到一个个格子里,所以放大之后就像一个个马赛克; 而矢量图象是描绘的。

 

关于卷积要做的:

——>栅格图像,所以把 图像放到R、G、B三个channel【Input Channel】,如下图

先在input里面取一个块patch [ h‘,w' ],然后开始在图像块中移动,对patch进行卷积操作,得到C、,W、H 都变的块,即上图的output

通过对patch每个像素进行加权求和,所以output里面包含了全部信息,有点特征提取的意思,对patch进行加权,可以让满足该特征的值变大,不满足的变小。

 卷积的运算过程:

♦单通道single:

   使用3*3卷积核进行数乘,得出output

       

♦三通道(神经网络中可能达到几百个通道,这里以三通道RGB为例)

  •  对每一个通道做卷积,数乘后求和,该过程其实就是卷积运算。

 

       

  •   将上述的几个摞起来成为块,那么就分别是3*3*3的patch和一个用卷积核摞起来的3*3*3的块进行数乘后求和,即下图

         

  • 实现下图,由三通道变为单通道,且W、H均改变。

      

  如下图,因为是做3*3的卷积,由左上角的中心点可得出会少掉两行两列

  有个公式:output的W、H = input的W、H - kernel_size + 1     (例如在这里5-3+1=3

   

 

♦输入N个channel,得到M 个channel

由上面易得,一个input为N*W*H,在通过一个卷积核N*W'H'后变成了1*W'*H'的map。

因此若要m个通道,就用m个卷积核,再将所有得到的一个channel摞起来。

规律:(1)每一个卷积核的通道数量与输入的通道数量相同。  (2)输出的通道数与卷积核个数相同。

四维张量,m个n通道w宽度h高度的相同的卷积核。

 

代码:

import torch
in_channels,out_channels = 5,10
width,height = 100,100#图像大小
kernel_size = 3#卷积核大小
batch_size = 1

input = torch.randn(batch_size,in_channels,width,height)#小批量的batch
conv_layer = torch.nn.Conv2d(in_channels,out_channels,kernel_size=kernel_size)
output = conv_layer(input)

print(input.shape)
print(output.shape)
print(conv_layer.weight.shape)#卷积核权重,10个5*3*3,其实就是前面提到的m*n*w*h

>>

torch.Size([1, 5, 100, 100])
torch.Size([1, 10, 98, 98])
torch.Size([10, 5, 3, 3])  

 

 

卷积层——Padding填充

若要输出和输入的W、H相同,就用填充。如上图,若卷积为3*3就填1,5*5就填2

公式为:kernel_size / 2

得到结果如下图:

 下面是关于上述padding的例子代码实现:【注意:在下面代码加粗部分可以看到bias=False,可以说明卷积其实也是一种线性运算,这也为后面卷积神经网络中加了激活函数做铺垫,进行非线性运算。】

import torch
input = [3,4,5,6,7,     #输入的图像
         2,4,6,8,2,
         1,6,7,8,4,
         9,7,4,6,2,
         3,7,5,4,1]
input = torch.Tensor(input).view(1,1,5,5)   #reshape一下,batch,in_channel,w,h
#初始化这个卷积,规定out_channel,in_channel,kernel_size,是否填充
#这里padding = 1,即行列各加一
#注意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)#O, I, W, H,卷积的权重定义
conv_layer.weight.data = kernel.data

output = conv_layer(input)
print(output)

>>

tensor([[[[ 91., 162., 225., 216., 131.],
[114., 208., 296., 263., 150.],
[192., 259., 282., 214., 122.],
[194., 251., 253., 169., 86.],
[ 96., 112., 110., 68., 31.]]]], grad_fn=<ThnnConv2DBackward0>)

 

 卷积层——stride步长

作用:有效降低图像的宽度和高度。

代码实现:

import torch
input = [3,4,5,6,7,     #输入的图像
         2,4,6,8,2,
         1,6,7,8,4,
         9,7,4,6,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.],
[251., 169.]]]], grad_fn=<ThnnConv2DBackward0>)

 


 

下采样

♦   Max Pooling Layer最大池化层(信息存在丢失的情况)

特点:是对一个通道做Max pooling, 而不是通道之间,所以通道数不变。下图用2*2做MxaPooling,图像缩为原来的一半。

没有权重,是一种采样方式。用2*2的maxpooling,stride=2 ,扔掉了一半的特征,但是减少了卷积的操作,速度更快。

注意注意:Maxpooling如果没有指定stride的值,会默认等于maxpooling的值! (类似这里maxpooling=2所以默认为2)

 


 

下面举一个简单的卷积神经网络

卷积——>最大池化——>到最后需要做一个view,将(batch20*4*4)->(batch,320)——>然后单纯做一个全连接层,将320直接降到十维

 

 

 最后的维度查看用print

 


 


 

okook重头戏来咯,之前MNIST用的是全连接神经网络,现在活学活用一下,用卷积神经网络

在这里解释一些可能会困惑的点吧:

1.做卷积是因为我们如果单纯的用前面的linear layer会导致空间特征没办法保留。

2.在这里卷积之后的进行激活是因为做卷积运算本身是让图像乘权重,本质上是一种线性变换,而我们通过激活加入非线性因子可以解决一些线性无法解决的问题(联想之前线性输出的值没法对应分类,而激活后映射到了[0,1] / [-1,1]后就可以通过这个概率解决分类问题)。

3.最后要用view将(batch20*4*4)->(batch,320),从而便于后面直接用全连接层降到十维,对应分类。

4.降到十维之后的输出与图像分类的输出有对应,就可以扔到交叉熵损失进行训练咯(所以可以看到最后一层不用做激活,直接扔CrossEntropyLoss)

 

下面是具体代码(其实跟之前全连接层不一样的也就是model了,其他都一样)

import torch
import numpy as np
from torch.utils.data import Dataset    #抽象类,只能由子类来继承
from torch.utils.data import DataLoader #可实例化

filepath  = 'C:\\Users\\jiwenting\\Desktop\\diabetes.csv'
class DiabetsDataset(Dataset):  #自定义类
    #魔法
    def __init__(self,filepath):
            xy = np.loadtxt(filepath,delimiter=',',dtype=np.float32)
            self.len = xy.shape[0]  #用shape[0]输出行的数目,即N
            self.x_data = torch.from_numpy(xy[:,:-1])   #x_data为前八列
            self.y_data = torch.from_numpy(xy[:,[-1]])  #y_data为最后一列
    def __getitem__(self, index) :    #通过索引调出数据
            return self.x_data[index],self.y_data[index]
    def __len__(self):
            return self.len

dataset = DiabetsDataset(filepath)  #通过自定义类实例化
#传入定义的数据集,然后输入 batch_size 和 shuffle 来进行loader; 而num-wokers表示几个并行的进程来读取mini_batch的数据
train_loader = DataLoader(dataset=dataset,batch_size=32,shuffle=True,num_workers=0)   

#model
class Model(torch.nn.Module):
    def __init__(self) -> None:
        super(Model,self).__init__()
        self.linear1 = torch.nn.Linear(8,6)#构造对象,八维到六维
        self.linear2 = torch.nn.Linear(6,4)
        self.linear3 = torch.nn.Linear(4,1)
        self.activate = torch.nn.Sigmoid()
    def forward(self,x):
        x = self.activate(self.linear1(x))
        x = self.activate(self.linear2(x))
        x = self.activate(self.linear3(x))
        return x
model = Model()
#loss & optimizer
criterion = torch.nn.BCELoss(reduction='mean')
optimzer = torch.optim.SGD(model.parameters(),lr=0.01)


#training circle
if __name__ == '__main__':
    for epoch in range(100):    #一整批的数据都跑完一次叫做一个epoch
        '''
        用enumerate获得当前为第几次迭代,0为起始位置 :直接对train_loader做迭代 
        从train_loader拿出Dataset自己实现的__getitem__的返回值即(x,y)元组放入data中 

        在这里loader自动的把xy构成的矩阵转化为Tensor类型的计算图,所以直接传进去就好了
        '''
        for i,(input,labels) in enumerate(train_loader,0):  
            #forward
            y_pred = model(input)
            loss = criterion(y_pred,labels)
            print(epoch,i,loss.item())  

            optimzer.zero_grad()
            loss.backward()
            optimzer.step()   
device = torch.device("cuda=0"if torch.cuda.is_available() else "cpu")

结果:可以看到这里比之前上涨了1%,ok接下来就是老科研角度,从出错角度看3%到2%,降了1/3啊!牛不牛吧就说!

另外如果有显卡要用GPU跑的话要加上

device = torch.device("cuda:0"if torch.cuda.is_available() else "cpu")
#然后就是按照视频里在对应模块分别丢入device
  images,lables = images.to(device),lables.to(device) #测试集放到gpu
 
  inputs,target = inputs.to(device),target.to(device) #把Tensor迁移到gpu
 
  model.to(device)    #把模型迁移到gpu

这里就是说如果有显卡就返回True执行“cuda=0”,当然如果电脑有很多显卡就指定呗。如果没有显卡就返回False执行“gpu”

 



作业:

在这里提一嘴

 

posted @ 2023-07-07 20:08  jjj爱吃香菜  阅读(46)  评论(0)    收藏  举报