神经网络
二维卷积
官方文档
打开查看torch.functional.conv2d(2d代表二维卷积)。
![](https://i-blog.csdnimg.cn/blog_migrate/e3d63c45926327ab5d5bb38ce920ec01.png)
注意几个参数:
输入input的形状(维度)有要求,
权重weight即卷积核同样有要求,
batchsize是指这批数据的数据个数,这里就一个矩阵,可以看成是只有一张5*5的灰度图像,
步进stride表示卷积核横向和纵向一次移动的步数,卷积核的步幅。可以是单个数字,也可以是元组(sH, sW)。默认值:1。
padding填充可以保证数据维度。输入的两边都有隐式填充。可以是字符串{' valid ', ' same '},单个数字或元组(padH, padW)。默认值:0 padding='valid'等同于没有填充。Padding ='same'填充输入,使输出具有与输入相同的形状。但是,该模式不支持除1之外的任何步幅值。
卷积操作
假设输入图像和卷积核如下:
![](https://i-blog.csdnimg.cn/blog_migrate/210b55c8ab0212536498f0451fad8caa.png)
卷积后的结果就是卷积核与输入图像对应位相乘再相加。然后卷积核“移动”,当(横向步进和纵向步进)stride=1时。
卷积后的第一行数:10,12,12。
![](https://i-blog.csdnimg.cn/blog_migrate/66482db2f5ff79bc6535c8c9dc121ef7.png)
(1+4+0+0+1+0+2+2+0)=10
![](https://i-blog.csdnimg.cn/blog_migrate/2f789bc2aec3b03375937ca1e3133329.png)
(2+3+2+4+1)=12
![](https://i-blog.csdnimg.cn/blog_migrate/3cd04cf421552781e4c46a95876d8518.png)
(6+1+3+2)=12
卷积结果:
![](https://i-blog.csdnimg.cn/blog_migrate/3f79f7b4f8f47ce867d61c3763ac7f09.png)
二维卷积代码实例
大写的ToTensor可以传入size或者具体数值或者以一个其他的tensor来构建一个tensor,通常用来把PIL图片转换为tensor类型,而小写的tensor只能传入数据,并且小写的tensor可以自动根据你输入的数据进行dtype的设置。(这里的tensor是实例化一个张量,前面的ToTensor是将别的数据类型转换成张量类型)
import torch
import torch.nn.functional as F
input = torch.tensor([[1, 2, 0, 3, 1],
[0, 1, 2, 3, 1],
[1, 2, 1, 0, 0],
[5, 2, 3, 1, 1],
[2, 1, 0, 1, 1]])
kernel = torch.tensor([[1, 2, 1],
[0, 1, 0],
[2, 1, 0]])
# 要满足函数F输入参数的要求,用reshape将输入和权重改组
input = torch.reshape(input, [1, 1, 5, 5])
kernel = torch.reshape(kernel, [1, 1, 3, 3])
# 分别查看步进为1和2的时候的结果
output = F.conv2d(input, kernel, stride=1)
print(output)
output2 = F.conv2d(input, kernel, stride=2)
print(output2)
# 设置填充,周围补零,使输出与输入形状不变
output3 = F.conv2d(input, kernel, stride=1, padding=1)
print(output3)
# 结果
'''
tensor([[[[10, 12, 12],
[18, 16, 16],
[13, 9, 3]]]])
tensor([[[[10, 12],
[13, 3]]]])
tensor([[[[ 1, 3, 4, 10, 8],
[ 5, 10, 12, 12, 6],
[ 7, 18, 16, 16, 8],
[11, 13, 9, 3, 4],
[14, 13, 9, 7, 4]]]])
'''
卷积层
官方文档
打开pytorch官网,找到torch.nn.Conv2d。
![](https://i-blog.csdnimg.cn/blog_migrate/1cfedbc0eef7f51e39d58de7001a6927.png)
参数:
in_channels输入通道数, out_channels输出通道数, kernel_size卷积核大小, stride=1卷积过程中步进大小, padding=0卷积过程中对原始图像进行“填充”, dilation=1卷积核对应位的距离(空洞卷积,不常用), groups=1, bias=True卷积后的结果加减一个常数, padding_mode='zeros'填充模式, device=None, dtype=None。
out_channel=2时,会有两个卷积核(不一定相同),卷积后输出叠加起来。
![](https://i-blog.csdnimg.cn/blog_migrate/acc23df8a4dd936d990e7fec04c87921.png)
卷积层代码实例
实例中使用了之前学的torchvision中的CIFAR10下载数据集,用dataloader加载数据集,用SummaryWriter中的add_images向tensorboard中添加图片组。
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10(root=".\\P18_dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True)
dataloader = DataLoader(dataset=dataset, batch_size=64)
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.conv1 = Conv2d(in_channels=3, out_channels=6, kernel_size=(3, 3), stride=(1, 1), padding=0)
def forward(self, x):
x = self.conv1(x)
return x
model = Model()
writer = SummaryWriter("nn_conv2d")
step = 0
for data in dataloader:
imgs, target = data
output = model(imgs)
print(imgs.shape)
print(output.shape)
# torch.Size([64, 3, 32, 32])
writer.add_images("input", imgs, step)
# torch.Size([64, 6, 30, 30])
# -1是指保证后面的形状,第一个维度自动补
output = torch.reshape(output, (-1, 3, 30, 30))
writer.add_images("output", output, step)
writer.close()
step += 1
可以看到经过卷积之后,图片通道数从3变成6,高H和宽W都变小了(32->30),这样用add_images添加图片会报错,因为程序不知道怎么处理通道数为6的图片。用一种不严谨的方法,即torch.reshape把图片的通道变成3,这样的话imgs.shape的第一维会改变,把第一个参数设为-1让程序自动改变第一维的数值(自动调整维度),因为最后一组不是64张图片而是16张,此时会变成32。
在tensorboard中查看:
![](https://i-blog.csdnimg.cn/blog_migrate/8091d44dccec05899276b065ddcac3d5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8c2b873ac4ce948eca4d738861144723.png)
最后一组:
![](https://i-blog.csdnimg.cn/blog_migrate/5df95d2a7c2b021e83a1f8e10977c6fa.png)
输入和输出形状的关系如下:
![](https://i-blog.csdnimg.cn/blog_migrate/694fd37a06aadb93b31dd0496f29ae6a.png)
上边的例子中,Input(64, 3, 32, 32),Output(64, 6, 30, 30),H_out计算如下:
>>> H_out = (32 + 2 * 0 - 1 * (3 - 1) - 1)/1 + 1
>>> H_out
30.0
在一些文章中不会给出卷积核的大小,需要我们自己通过公式计算。
最大池化
池化函数使用某一位置的相邻输出的总体统计特征来代替网络在该位置的输出。本质是降采样,可以大幅减少网络的参数。卷积的作用是提取特征,池化的作用是降低特征的数据量,保留一些特征但减小数据量。
官方文档
![](https://i-blog.csdnimg.cn/blog_migrate/2b616ff1bae05b9ffdd96edf6cbeb211.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8e45e020ca5fe371aa40e27ddc7c5f91.png)
注意这里的参数,stride默认值为kernel_size,ceil_mode设置为True指的是当池化核移动到输入图像外面时进行操作的值保留。
最大池化操作
![](https://i-blog.csdnimg.cn/blog_migrate/dc01f2f0a92e5a0c841b7fdc6a3efc76.png)
每一个输出值为池化核窗口所在处输入的最大值,当ceil_mode为True时保留,这里写错了写成ceil_model。
![](https://i-blog.csdnimg.cn/blog_migrate/5555d0cae84052bfc0ea60f7e8b9f847.png)
最大池化代码实例一
import torch
from torch import nn
from torch.nn import MaxPool2d
Input = torch.tensor([[1, 2, 0, 3, 1],
[0, 1, 2, 3, 1],
[1, 2, 1, 0, 0],
[5, 2, 3, 1, 1],
[2, 1, 0, 1, 1]])
print(Input.shape)
Input = torch.reshape(Input, (-1, 1, 5, 5))
print(Input.shape)
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=True)
def forward(self, x):
output = self.maxpool1(x)
return output
model = Model()
output = model(Input)
print(output)
# 报错
# torch.Size([5, 5])
# torch.Size([1, 1, 5, 5])
# RuntimeError: "max_pool2d" not implemented for 'Long'
最大池化无法对(长整型)long数据类型进行实现,通常使用浮点数实现,将上面代码第五行修改为以下代码。
Input = torch.tensor([[1, 2, 0, 3, 1],
[0, 1, 2, 3, 1],
[1, 2, 1, 0, 0],
[5, 2, 3, 1, 1],
[2, 1, 0, 1, 1]], dtype=torch.float32)
运行结果:
torch.Size([5, 5])
torch.Size([1, 1, 5, 5])
tensor([[[[2., 3.],
[5., 1.]]]])
可以看到跟最大池化操作中ceil_mode=True的结果相同。
把ceil_mode设为False,结果如下:
torch.Size([5, 5])
torch.Size([1, 1, 5, 5])
tensor([[[[2.]]]])
池化的作用是降低特征的数据量,保留了特征但是数据量会大大减小。
最大池化代码实例二
import torch
import torchvision
from torch import nn
from torch.nn import MaxPool2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10(root=".\\P19_dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(dataset=dataset, batch_size=64)
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=False)
def forward(self, x):
output = self.maxpool1(x)
return output
model = Model()
writer = SummaryWriter("P19_logs")
step = 0
for data in dataloader:
imgs, target = data
writer.add_images("Input", imgs, step)
output = model(imgs)
writer.add_images("Output", output, step)
step += 1
writer.close()
注意最大池化操作并不会改变通道数,即输出维度不变,所以不需要进行前面卷积层代码中的改组操作。
输出结果:
![](https://i-blog.csdnimg.cn/blog_migrate/6f254bb67b953867036a5b5c1a9ad140.png)
可以看到输出图片变得模糊了。
在神经网络中通常先进行卷积,再进行池化然后经过非线性激活等操作,可以使数据量大大减小,缩短训练时间。
非线性激活
非线性激活函数在神经网络结构引入非线性的特征,提高泛化能力,否则线性的神经网络即使有多层也可以归为一层,表达能力极其有限。
官方文档
常用两个relu、sigmoid:
![](https://i-blog.csdnimg.cn/blog_migrate/ce7d53e9d9a9ea41f98f912fd4ff6c20.png)
![](https://i-blog.csdnimg.cn/blog_migrate/da6f7fd1b18a5c5f0bb5cbd70c8cbd34.png)
注意input中的参数inplace,为True时直接对原来的变量修改,为False时不改变原来的变量,一般设为False可以保存原变量。
![](https://i-blog.csdnimg.cn/blog_migrate/de8faeec4a04534cba12ac496341fa54.png)
代码实例
relu:
import torch
from torch import nn
Input = torch.tensor([[1, -0.5],
[-1, 3]])
# Input = torch.reshape(Input, (-1, 1, 2, 2))
print(Input)
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.relu1 = nn.ReLU()
def forward(self, x):
output = self.relu1(x)
return output
model = Model()
Output = model(Input)
print(Output)
运行结果:
tensor([[ 1.0000, -0.5000],
[-1.0000, 3.0000]])
tensor([[1., 0.],
[0., 3.]])
这里pytorch教程中使用reshape对Input进行改组(注释掉了),不知道为什么。
sigmoid:
因为数据集通常很大,没有办法一次性全取出来,单个单个取又很浪费时间效率(跟IO操作有关系),所以是一批一批取,使用DataLoader中的batchsize。
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
test_data = torchvision.datasets.CIFAR10(root=".\\P20_dataset", train=False,
transform=torchvision.transforms.ToTensor(), download=True)
dataloader = DataLoader(dataset=test_data, batch_size=64)
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.relu1 = nn.ReLU()
self.sigmoid1 = nn.Sigmoid()
def forward(self, x):
output = self.sigmoid1(x)
return output
model = Model()
writer = SummaryWriter("P20_logs")
step = 0
for data in dataloader:
imgs, targets = data
writer.add_images("Input", imgs, step)
Output = model(imgs)
writer.add_images("Output", Output, step)
step += 1
writer.close()
在tensorboard中查看结果:
![](https://i-blog.csdnimg.cn/blog_migrate/96c49933648ff4162e2915eea282ef78.png)
线性层及其它层
官方文档:
批标准化层,一篇论文中提到批归一化通过减少内部协变量移位加速深度网络训练。(正则化防止过拟合,作用于cost函数,归一化加快训练速度,作用于激活函数输入)
![](https://i-blog.csdnimg.cn/blog_migrate/7d736195a7c4aafdffafcd334570e8e9.png)
下面矩形框中的三个层是在特定的网络当中才会用到的,需要的时候再查看官方文档进行学习,可以自己查看dropout层的文档练手。
![](https://i-blog.csdnimg.cn/blog_migrate/2c9a07069483ee6a9c01811c50daad0b.png)
着重讲解线性层。
![](https://i-blog.csdnimg.cn/blog_migrate/e08ca8c52aefc408dfab778579430eb9.png)
![](https://i-blog.csdnimg.cn/blog_migrate/5aab5e912bd840c650bebdb4d0c6379e.png)
这里的xd中的d可以看成Linear参数中的in_features,gL中的L可以看成out_features。
线性层代码实例:
import torch
import torchvision.datasets
from torch import nn
from torch.nn import Linear
from torch.utils.data import DataLoader
test_set = torchvision.datasets.CIFAR10(root=".\\P21_dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(test_set, batch_size=64, drop_last=True)
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.linear1 = Linear(196608, 10)
def forward(self, input):
output = self.linear1(input)
return output
model = Model()
for data in dataloader:
imgs, targets = data
print(imgs.shape)
output1 = torch.reshape(imgs, (1, 1, 1, -1))
print(output1.shape)
output2 = model(output1)
print(output2.shape)
输出结果为:
torch.Size([64, 3, 32, 32])
torch.Size([1, 1, 1, 196608])
torch.Size([1, 1, 1, 10])
......
上面代码中的linear()的参数首先看output1的size,发现它可以展成1*1*1*196608,所以Linear参数中in_features就设置为196608,想要把它变成10的,就把out_features设为10。
可以使用torch中的flatten把imgs展平,把维度变成1,把上面的output1换成下面一行。
output1 = torch.flatten(imgs)
输出结果为:
torch.Size([64, 3, 32, 32])
torch.Size([196608])
torch.Size([10])
张量平坦化操作 ( flatten operation )是卷积神经网络内部的常见操作。这是因为传递给完全连接层的卷积层输出必须先平坦化,然后才能完全连接层接受输入。