第六章 PyTorch基本知识和异或神经网络实践

1.介绍:

很多初学者,最头疼的就是知识点掌握了,但是不知道怎么去实现所学习到的知识点,现在这一章,将会手把手教会你怎么用pytorch创建自己的神经网络模型。

2.PyTorch基本知识

2.1 tensor类型变量

2.1.1 神经网络的输入和输出怎么创建
在之前的感知器模型里面,我们创建了几个float类型的变量作为输入,这是可以的,但是现实中输入的数据往往不会那么简单,可能是图片,也可能是其他更加复杂的东西,所以,我们采用tensor类型来作为输入和输出

2.1.2 tensor函数的参数
tensor()函数需要传入的参数一共有四个,分别是,但是需要记住的只有两个分别是data和device就可以。
(1)数据data:需要是数组,可以是一维也可是多维,必须定义。(所以需要把图片或者其他的输入转换成数组形式,也可以是矩阵)
(2)类型dtype:数据类型,是整数int,还是浮点数float,它可以返回不同的tensor类型,比如LongTensor和FloatTensor,一般不用填。
(3)device设备类型:可以选择把数据放在GPU还是CPU里面,想要放在GPU里面的话需要定义:device=cuda:0(这个0代表放在第一张显卡里面,如果你只有一张显卡,那写成cuda也可以),不写的话默认在CPU中。
(4) requires_grad: 这个需要一个bool值,表示这个tensor的类型是否需要计算梯度,一般都是默认不需要,不用特意去写。

2.1.3 tensor的其他操作
(1)类型转换,我们可以把已有的tensor进行任意的其他类型的tensor转换,转换方式和普通的参数转换一模一样:

tensor变量.转换类型()

(2)从CPU转到GPU,从GPU转到CPU:

tensor变量.cuda # 原本在cpu转到gpu
tensot变量.cpu  # 原本在gpu转到cpu

需要注意的是,不管数据是从GPU转到CPU还是CPU到GPU都是很费时间的,所以转换语句要尽可能写在循环之外。

(3)代码示例:
代码演示了两个变量,一个开始在CPU中,一个开始在GPU中,都是LongTensor,之后更改类型且互换位置:

"""
@FileName:Tensor.py
@Description:Tensor变量的使用
@Author:段鹏浩
@Time:2023/3/9 20:15
"""

import torch

ten1 = torch.tensor([1, 2, 3, 4], device='cuda:0')  # 创建一个一维tensor变量,传入的是一个一维的向量,且放在GPU中
ten2 = torch.tensor([[1, 2], [3, 4]])  # 传入了一个2x2的矩阵,且放在CPU中

print("修改前:\n")
print(f'ten1:{ten1},ten1的类型:{ten1.type()},ten1的尺寸:{ten1.size()}\n')  # f''格式可以进行多字符串输入,同时在里面的{}内可以插入
# 返回值为字符串的代码,我们可以看到它放在GPU中,那么它后面会跟上其位置
print(f'ten2:{ten2},ten2的类型:{ten2.type()},ten2的尺寸:{ten2.size()}\n')

ten1 = ten1.float()  # 把ten1转换成float
ten2 = ten2.cuda()  # 把ten2放入GPU
ten1 = ten1.cpu()  # 把ten1放入cpu

print("修改以后\n")
print(f'ten1:{ten1},ten1的类型:{ten1.type()},ten1的尺寸:{ten1.size()}\n')
print(f'ten2:{ten2},ten2的类型:{ten2.type()},ten2的尺寸:{ten2.size()}\n')

运行结果如下:
在这里插入图片描述

2.2 如何用pytorch搭建一个神经网络框架

2.2.1 torch.nn接口
在pytorch中,我们通过使用torch.nn这个接口,可以方便且快速地创建一个神经网络模型,因为在构建神经网络的时候,这个接口非常常用,建议在开始导入pytorch后,给它取一个别称,就像这样:

import torch.nn as nn

之后,我们的代码里面就叫它nn就行,十分方便。

2.2.2 如何创建神经网络的网络层
在这里插入图片描述
如图我们还是以之前的简单多层神经网络为例,现在我们先不考虑卷积神经网络,而是分析像图上的这种多层神经网络,其实就相当于一个全连接层。
那我们可以看见,上面一共有四层(输入和输出也算),但是我们写代码不是按照它有几层来写的,而是两层两层来写的,因为每两层其实需要两个东西:输入输出(上一层的神经元也就是圆圈节点的个数和下一层的)以及激活函数组成
这样我们就可以通过如下的方式来定义图上的输出层,两个函数之间记得有逗号:

nn.Linear(2,4),
nn.Sigmoid()

上面代码中的nn.Linear(x,y)代表了这一层的输入和输出数量。
nn.Sigmoid自然就是Sigmoid函数
,直接这样写就行,它会把Linear的四个输出进行处理的。nn.ReLU()则是ReLU函数,非常方便。
所以,我们可以得出,上面的这个神经网络是这样的,我们都用Sigmoid做激活函数:

# 先是输入层和第一个隐含层,输入层有2个隐含层有4个,且隐含层那里要用Sigmoid激活一下
nn.Linear(2,4),
nn.Sigmoid()

# 之后就是两个隐含层之间,两层各有四个,且下一层也是拿Sigmoid把这些处理一下
nn.Linera(4,4),
nn.Sigmoid()

#  最后就到了第二个隐含层和输出层,第二个隐含层有4个输出层有一个,输出层还要激活一下
nn.Linear(4,1),
nn.Sigmoid()

2.2.3 nn.Sequential()容器函数
Sequential这个词的意思是连续的,所以这个函数里面装的就是我们的神经网络运行的顺序和流程。它就像一个饼干盒子,我们一层一层的神经网络就是饼干,我们把饼干给依次装进入放进去,可以把饼干包装起来,拿去出售,赚钱。饼干我们在上面已经写好了,现在装入罐子就可以,其实Sequential就一个网络变量,我们可以给它起名为mynet:

mynet=nn.Sequential(
	# 先是输入层和第一个隐含层,输入层有2个隐含层有4个,且隐含层那里要用Sigmoid激活一下
	nn.Linear(2,4),
	nn.Sigmoid(),
	
	# 之后就是两个隐含层之间,两层各有四个,且下一层也是拿Sigmoid把这些处理一下
	nn.Linera(4,4),
	nn.Sigmoid(),
	
	#  最后就到了第二个隐含层和输出层,第二个隐含层有4个输出层有一个,输出层还要激活一下
	nn.Linear(4,1),
	nn.Sigmoid()
)

2.2.4 向前传播
定义好了神经网络以后,我们要如何让神经网络跑起来呢,其实直接用就可以了,我们需要像mynet里面传入参数(因为开始的时候输入为2),之后它就会跑起来了,像这样:

output = mynet(Tensor([x1,x2]))

output就是我们最后的输出值。
这是一个完整的演示代码,我们把神经网络设置在构造函数里面,就可以用类来表示一整个神经网络,因为神网络往往还需要其他的函数,比如反向传播之类的,但是需要继承nn.Module类才行(nn.Module是pytorch已经帮我们打包好的,用类来表示神经网络的类。):

"""
@FileName:MyNet.py
@Description:一个用于演示的简单神经网络模型
@Author:段鹏浩
@Time:2023/3/10 16:46
"""

import torch.nn as nn
import torch


class Mynet(nn.Module):
    """
    @ClassName:Mynet
    @Description:这是一个简单的神经网络类
    @Author:段鹏浩
    """

    def __init__(self):
        super().__init__()	# 继承父类
        self.mynet = nn.Sequential(
            # 输入层和第一个隐含层
            nn.Linear(2, 4),
            nn.Sigmoid(),

            # 两个隐含层
            nn.Linear(4, 4),
            nn.Sigmoid(),

            # 最后一个隐含层和输出层
            nn.Linear(4, 1),
            nn.Sigmoid()
        )

    def forward(self, x1=1, x2=1):
        """
        @Description:向前传播函数,其实返回的是输出
        """
        x = torch.tensor([x1, x2], dtype=torch.float)   # 传入的必须是float类型的
        output = self.mynet(x)
        return output


if __name__ == "__main__":
    net = Mynet()
    y = net.forward()
    print(y)

有一点要注意,现在有些百度上找到的代码在继承类初始化时,初始化父类的时候,写的是

super(本类名,self).__init__()

但这其实这是python2的用法,现在Python3.8必须要写成:

super().__init__()

这上面代码的输出:

tensor([0.5490], grad_fn=<SigmoidBackward0>)

可以看到,1,1经过刚刚的神经网络,它已经计算出来了最终结果,同时grad_fn记录的是这个结果是什么激活函数出来的,方便后面进行反向传播时的梯度计算。

2.2.5 反向传播的算法
现在我们就需要定义反向传播的算法,以便我们的神经网络可以真正的跑起来。定义的方法也很简单,pytorch已经帮我们写好了很多的反向传播算法(叫做优化器,Optimizer),我们直接调用就可。当你掌握了这些函数的使用以后,你可以直接在pychram里面ctrl+单击去查看源代码,来构建自己的算法,现在先跟着做。
调用方法是:

自己取的优化器名 = torch.optim.优化器接口名称(网络名.paramters(), 其他参数)

其中的网络名就是我们自己定义的那个网络的名称,可以是一个nn.Module的子类,也可以是一个nn.Sequential()容器。其他参数包括了学习率lr:xx以及衰减权重momentum等,用到的时候百度就行。

之后还需要两句代码才能开始优化,分别是:

自己已经定义好的优化器名称.zero_grad()
自己已经定义好的优化器名称.step()

第一句代码用于梯度清零,因为我们学过,数据其实不是一起全部进来的,而是一批一批的进来的,这样的话,如果我们在运行完一批数据以后,在下一批时,就可以选择是否清零。这样的好处在于,如果我们一直不清0,那么就相当于把几个批次的输入全部都输入到一个神经网络中,一直训练,等于很大的扩大了神经网络。但是我们又知道神经网络在训练时,梯度其实是层层传递的,可能有不好的数据,导致梯度会一直上升,出现在最优点附近左右横跳的现象(学习率太大也会),为了防止它到后面梯度大的得太多,导致运行不出结果被卡死,我们在运行了几批以后,就进行一次清零,这样网络又可以根据当前新的数据来调整网络梯度,以达到全局最优点。更具体的讲解可以看这个知乎文章,就等于先更新误差,误差够了一次优化。下一句就表示开始优化算法

2.2.6 损失函数的定义以及梯度下降法:
其实损失函数的定义方法很简单的,只需要像上面的优化器一样的定义即可,唯一不同指出在于它在nn里面,写法如下:

自己取个名 = nn.函数接口()

像之前的均方差损失函数的定义在这里变得十分简单,如下:

loss_fun = nn.MSELoss()

把损失进行反向传播(把误差传回去以后,优化算法才能根据这些误差来更新权值参数):

loss = loss_fun(实际的输出值,期望输出值)
loss.backward()

至于怎么把他们和神经网络组合在一起,这个等下我们通过一个例子来说明。

2.2.7 如何保存和加载已有的网络
这是神经网络的最后一个零件了,学了以后,我们就可以开始学习如何组装它。它一共有两种方式,一种是全部网络都保存,一直是只保存参数。
(1)第一种用法如下:
保存:

torch.save(网络名称,'路径/命名.pkl')

加载:

网络名 = torch.load(网络名称,'路径/命名.pkl')

需要注意的是,如果一开始保存的不是一个类,只是一个nn.Sequential()变量,那么,你可以在任意的文件里面通过上面的加载方式把这个网络加载进来,然后使用。但是,如果你保存的网络是一个类,那么你相当于只保存了Sequential参数,必须把那个神经网络类全部复制到你运行的这个代码页中来才可以运行(复制过来就行,什么都不用干)
如果你好奇pkl里面是什么可以看看这个,这是打开pkl文件的办法,它是python的延申文件:

import pickle

F=open(r'C:\blabala.pkl','rb')

content=pickle.load(F)

(2)第二种方法是只保存参数,这样一来它的大小要比第一种小很多,但是缺点是即便是nn.Sequential()类型也不能直接加载运行,必须把原本的网络复制到运行的前面才行。
保存:

torch.save(网络名称.state_dict(),'路径/命名.pkl')	# 只保存状态参数

加载:

网络名.load_state_dict(torch.load(网络名称,'路径/命名.pkl'))	# 先把它加载下来,再解析其参数到网络中

2.2.8 训练一个可以进行异或判断的模型
这里,我用一个常用的例子来介绍一下怎么使用上面的知识,创建一个实用的神经网络模型。我们的目标是实现异或运算,异或就是有两个布尔输入,一个输出,如果这两个输入相同(都是0或者是(1),那么输出就为0,只有它们不同的时候,输出才为1
那其实我们的训练集很明确,是(其实这里只需要一个输入就行,网络会把这个x_t分成两份丢进去计算,最好给出的结果也是和x_t一样的四维张量,如果输出有两个,那就是两个四维张量):

x=[[0,0], [1,1], [0,1], [1,0]]
x_t = torch.tensor(x,device='cuda:0', dtype=torch.float)

因为就这几种,而且如果数据集明确是很有限的,我们又可以拿到全部的数据集,那我们是不需要划分训练集和验证集的,训练集去练就行,不存在过拟合,拟合出来就可以用。我们同时也可以给出对应的数学期望:

y=[[0], [0], [1], [1]]
y_t = torch.tensor(y, device = 'cuda:0', dtype = torch.float)

记得要是Float_tensor类型,并且把它放GPU里面
图片来自网络,侵权请联系1700098233@qq.com
(2)从百度上找了一个异或运算的网络模型,据说对一开始用ReLU更好,最后的输出用Sigmoid可以保证结果在0-1范围内。
那我们就开始跟着它搭建我们的神经网络模型,这其实是一个单层神经网络,所以很好搭建:

net = nn.Sequential(
	# 输入层和隐含层
	nn.Linear(2,20),
	nn.ReLU(),

	# 隐含层和输出层
	nn.Linear(20,1),
	nn.Sigmoid()
).cuda()
# net.to(device=torch.device("cuda" if torch.cuda.is_available() else "cpu")) # 移动到GPU的另一种办法

最后加上把网络移动到GPU,如果.cuda()没有效果就用第二种 ,一定有效,而且还能在没有GPU时自动跳到CPU。
(3)现在我们只需要设置损失函数和优化器就行:
损失函数我们采用均方差来进行:

loss_fun = nn.MSELoss()

优化器我们用随机梯度下降函数SGD算法。学习率设置成0.05:

optimizer = torch.optim.SGD(net.parameters(), lr=0.05)

(4)训练神经网络:
每一次训练称为一次epoch,我们训练n论,先设n=5000,那么训练代码如下:

n = 5000
for epoch in range(n):
	# 先计算输出
	out = net(x_t)
	# 有了输出以后就可以计算误差,传入刚刚算的输出和前面的期望
	loss = loss_fun(out, y_t)
	# 先把梯度清零一下,其实这个可以放前面也行,在反向传播前干了就可以
	optimizer.zero_grad()
	# 把误差进行反向传播
	loss.backward()
	# 有了误差,就可以在这些误差的基础上用优化器更新权值了
	optimizer.step()
	# 可以搞个监视器,看看每1000轮是个什么情况
	if epoch%1000 == 0:
		print(f'已经跑了{epoch}轮,此时的误差为: {loss}')
	

(5)查看最终结果并且保存:
网络训练好以后,就可以直接用了,我们把它传回CPU,这样显示会快些:

out = net(x_t).cpu()
print(f'结果为{out.data}')
# 保存一下
torch.save(net,'net3.pkl')

(6)完整代码:

"""
@FileName:traIn.py
@Description:用于异或判断的神经网络
@Author:段鹏浩
@Time:2023/3/11 12:32
"""
import torch
import torch.nn as nn

# 输入
x = [[0, 0], [1, 1], [0, 1], [1, 0]]
x_t = torch.tensor(x, device='cuda:0', dtype=torch.float)

# 期望
y = [[0], [0], [1], [1]]
y_t = torch.tensor(y, device='cuda:0', dtype=torch.float)

# 神经网络
net = nn.Sequential(
    # 输入层和隐含层
    nn.Linear(2, 20),
    nn.ReLU(),

    # 隐含层和输出层
    nn.Linear(20, 1),
    nn.Sigmoid()
).cuda()
# net.to(device=torch.device("cuda" if torch.cuda.is_available() else "cpu")) # 移动到GPU的方法二
# 损失函数和优化器
loss_fun = nn.MSELoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.05)

# 训练的过程
n = 5000
for epoch in range(n):
    # 先计算输出
    out = net(x_t)
    # 有了输出以后就可以计算误差,传入刚刚算的输出和前面的期望
    loss = loss_fun(out, y_t)
    # 先把梯度清零一下,其实这个可以放前面也行,在反向传播前干了就可以
    optimizer.zero_grad()
    # 把误差进行反向传播
    loss.backward()
    # 有了误差,就可以在这些误差的基础上用优化器更新权值了
    optimizer.step()
    # 可以搞个监视器,看看每1000轮是个什么情况
    if epoch % 1000 == 0:
        print(f'已经跑了{epoch}轮,此时的误差为: {loss}')

# 最终结果和保存:
out = net(x_t).cpu()
# out = out.to(device=torch.device("cpu")) # 移动到CPU的方法二
print(f'结果为{out.data}')
# 保存一下
torch.save(net,'net3.pkl')

运行结果如下,可以看到很接近真实值:

已经跑了0轮,此时的误差为: 0.2511649429798126
已经跑了1000轮,此时的误差为: 0.06107582896947861
已经跑了2000轮,此时的误差为: 0.01107136718928814
已经跑了3000轮,此时的误差为: 0.004729895852506161
已经跑了4000轮,此时的误差为: 0.0028015682473778725
结果为tensor([[0.0431],
        [0.0428],
        [0.9615],
        [0.9496]])

把轮数改成10000可以显著的增加准确率,但是只能无限逼近真实值

已经跑了0轮,此时的误差为: 0.25448012351989746
已经跑了1000轮,此时的误差为: 0.059336621314287186
已经跑了2000轮,此时的误差为: 0.01277837622910738
已经跑了3000轮,此时的误差为: 0.00557954516261816
已经跑了4000轮,此时的误差为: 0.003303739009425044
已经跑了5000轮,此时的误差为: 0.0022679318208247423
已经跑了6000轮,此时的误差为: 0.0016945755342021585
已经跑了7000轮,此时的误差为: 0.001338329864665866
已经跑了8000轮,此时的误差为: 0.0010980369988828897
已经跑了9000轮,此时的误差为: 0.0009258244535885751
结果为tensor([[0.0274],
        [0.0273],
        [0.9776],
        [0.9655]])

这里说一个有趣的现象,如果我们不清梯度:

n = 10000
for epoch in range(n):
    # 先计算输出
    out = net(x_t)
    # 有了输出以后就可以计算误差,传入刚刚算的输出和前面的期望
    loss = loss_fun(out, y_t)
    # # 先把梯度清零一下,其实这个可以放前面也行,在反向传播前干了就可以
    # optimizer.zero_grad()
    # 把误差进行反向传播
    loss.backward()
    # 有了误差,就可以在这些误差的基础上用优化器更新权值了
    optimizer.step()
    # 可以搞个监视器,看看每1000轮是个什么情况
    if epoch % 1000 == 0:
        print(f'已经跑了{epoch}轮,此时的误差为: {loss}')

或者说每1000轮清一次梯度

for epoch in range(n):
    # 先计算输出
    out = net(x_t)
    # 有了输出以后就可以计算误差,传入刚刚算的输出和前面的期望
    loss = loss_fun(out, y_t)
    # # 先把梯度清零一下,其实这个可以放前面也行,在反向传播前干了就可以
    # optimizer.zero_grad()
    # 把误差进行反向传播
    loss.backward()
    # 有了误差,就可以在这些误差的基础上用优化器更新权值了
    optimizer.step()
    # 可以搞个监视器,看看每1000轮是个什么情况
    if epoch % 1000 == 0:
        optimizer.zero_grad()
        print(f'已经跑了{epoch}轮,此时的误差为: {loss}')

都可以在前1000轮里面就把精度确切到100%,这也说明了,可以手动确定梯度清除位置的好处。
这是结果:

已经跑了0轮,此时的误差为: 0.24800866842269897
已经跑了1000轮,此时的误差为: 0.0
已经跑了2000轮,此时的误差为: 0.0
已经跑了3000轮,此时的误差为: 0.0
已经跑了4000轮,此时的误差为: 0.0
已经跑了5000轮,此时的误差为: 0.0
已经跑了6000轮,此时的误差为: 0.0
已经跑了7000轮,此时的误差为: 0.0
已经跑了8000轮,此时的误差为: 0.0
已经跑了9000轮,此时的误差为: 0.0
结果为tensor([[0.],
        [0.],
        [1.],
        [1.]])

Process finished with exit code 0

不清楚梯度,跑一下我们可以发现,在213轮以后,误差已经低得超过了计算机浮点数上限,可以认为已经实现了100%的精确度。

# 训练的过程
n = 500
for epoch in range(n):
    # 先计算输出
    out = net(x_t)
    # 有了输出以后就可以计算误差,传入刚刚算的输出和前面的期望
    loss = loss_fun(out, y_t)
    # # 先把梯度清零一下,其实这个可以放前面也行,在反向传播前干了就可以
    # optimizer.zero_grad()
    # 把误差进行反向传播
    loss.backward()
    # 有了误差,就可以在这些误差的基础上用优化器更新权值了
    optimizer.step()
    # 可以搞个监视器,看看每1000轮是个什么情况
    print(f'已经跑了{epoch}轮,此时的误差为: {loss}')
已经跑了212轮,此时的误差为: 7.006492321624085e-45
已经跑了213轮,此时的误差为: 2.802596928649634e-45
已经跑了214轮,此时的误差为: 0.0
已经跑了215轮,此时的误差为: 0.0
已经跑了216轮,此时的误差为: 0.0
已经跑了217轮,此时的误差为: 0.0
结果为tensor([[0.],
        [0.],
        [1.],
        [1.]])

我们把这个精度100%的模型单独拿出来,并且改变输入值的顺序:

"""
@FileName:test.py
@Description:用于判断我们训练的模型,是否可用
@Author:段鹏浩
@Time:2023/3/11 12:32
"""
import torch
# 输入
x = [[0, 0], [1, 0], [0, 1], [1, 1]]
x_t = torch.tensor(x, device='cuda:0', dtype=torch.float)
net = torch.load('net3.pkl')
out = net(x_t).cpu()
print(out.data)

可以确定它确实有100%的精度

tensor([[0.],
        [1.],
        [1.],
        [0.]])

最后一点,训练好的模型是可以支持任何的输入的,哪怕只有一个,因为它原本是对4个参数分别计算,一个的话它就会忽视其他位置

"""
@FileName:test.py
@Description:用于判断我们训练的模型,是否可用
@Author:段鹏浩
@Time:2023/3/11 12:32
"""
import torch
# 输入
x = [[0, 0]]
x_t = torch.tensor(x, device='cuda:0', dtype=torch.float)
net = torch.load('net3.pkl')
out = net(x_t).cpu()
print(out.data)

给出的结果也很精确

tensor([[0.]])

当然,如果到不了100%也无所谓,因为只需要一个简单的if else语句,判断一下大于0.5就是1,小于0.5就是0即可。

3.用pytorch做卷积神经网络

1. 卷积层:
在pythorch中有对文本等一维数据做卷积的nn.Conv1d(),也有对图像这样的二维数据做卷积的nn.Conv2d()函数,它们的参数都是一样的,就以Conv2d为例子,一共有6个参数,每个都需要定义:

nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, bias)

(1)in_channels:它代表卷积层输入图片的通道的个数(RGB是3,灰度图是1);

(2)out_channels:它代表输出图片的通道数,比如用三个卷积核可以把灰度值卷积成3d的,也可以把彩图平铺;

(3)kernel_size:它代表卷积核的大小(注意这里可以是单数字也可以是元组,比如:如果是3,则卷积核是3x3,你也可以写(3,3))。卷积核是内定的,不需要管是什么样子;

(4)stride:这个就是卷积的步长;

(5)padding: 这个是填充的距离;

(6)bias:这个是是否设置偏置数:填True或者是False

如果你真的很好奇这个卷积核是什么,可以在设置好卷积层以后,查看它的weight.data,这个就是它的卷积核,是一个Tensor类型的矩阵。例子如下:

    ler = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0, bias=False)
    a = ler.weight.data
    print(a)

输出为:

tensor([[[[ 0.1719, -0.0283, -0.2331],
          [ 0.1650,  0.0751, -0.3169],
          [ 0.0790,  0.0434,  0.2435]]]])

每一次运行结果都不同

tensor([[[[-0.2502,  0.3159, -0.0492],
          [ 0.1367, -0.3052, -0.2918],
          [-0.2302,  0.0404,  0.0507]]]])

所以就知道它在进行特征提取就行,用法和之前一样,放到Sequential里面作为一层

同时,我们也就知道,如果要自定义卷积核,要根据输入in_channel记为in,输出out_channel记为out和尺寸kernel_size记为(x,y),来定义一个: o u t × i n × x × y out\times in\times x\times y out×in×x×y的矩阵。
比如我的输入是3,输出是2,大小是3x3,那么我的Tensor矩阵需要是一个2x3x3x3的矩阵(为了防止初学者脑子乱了,我们一步步来):
(1)首先这是3x3:

C  = torch.Tensor(
[[1, 1, 1],
 [1, 1, 0],
 [0, 1, 1]]
)

(2)这是3x3x3:

C  = torch.Tensor(
[[[1, 1, 1],
 [1, 1, 0],
 [0, 1, 1]],

[[1, 1, 1],
 [1, 1, 0],
 [0, 1, 1]]
 
[[1, 1, 1],
 [1, 1, 0],
 [0, 1, 1]]]
)

(3)这是2x3x3x3:

[[[[1, 1, 1],
 [1, 1, 0],
 [0, 1, 1]],

[[1, 1, 1],
 [1, 1, 0],
 [0, 1, 1]]
 
[[1, 1, 1],
 [1, 1, 0],
 [0, 1, 1]]]

[[[1, 1, 1],
 [1, 1, 0],
 [0, 1, 1]],

[[1, 1, 1],
 [1, 1, 0],
 [0, 1, 1]]
 
[[1, 1, 1],
 [1, 1, 0],
 [0, 1, 1]]]]

总结就是一步步写最后在外面加括号。

2.池化层
池化和卷积很相似,这里只介绍最大池化,因为平均池化已经被淘汰了:

torch.nn.MaxPool2d(kernel_size, stride, padding, dilation, return_indices, ceil_mode)

它不在乎输入的图片是多少道的(因为池化也不用在乎,不管多少都能池化),所以依次是卷积核,步长,填充,接下来几个有点难,但是你可以默认不填,不填就是我们前面学的最大池化。这里还是简单讲一下:
dilation:一个控制窗口中元素步幅的参数,也就是可以传入一个步数,然后程序会先把这个数的步长来逐个插入空值到池化核中,把池化核变大,但是又不影响池化的正常使用,然后池化的覆盖面就更大,池化出来的图片就更小,这在图片尺寸很大的时候有用。你肯定会疑惑,这和扩大kernel_size没有区别,但它和扩池化核是有区别的,它对被扩大的区域的数据是不计算的,就等于在池化时,会忽视图片上一些地方的数据(原理是如果图片很大,那么舍去一些数据也不影响整体),有效的减小计算,如果是单纯的扩大尺寸,那尺寸里面的每一处都要计算。其实对卷积核也可以进行这样的操作,因为这么做就像是在图片上挖了几个洞一样,所以也叫空洞卷积
return_indices:输入一个bool值,如果等于 True,会返回输出最大值的序号,对于上采样操作会有帮助。(什么是上下采样?下一章OpenCV的使用里面会说,上采样就是放大图片的插值法,下采样就是缩小图片的丢弃值法)
ceil_mode :也是一个布尔值,如果等于True,计算输出信号大小的时候,如果有小数,会使用向上取整,代替默认的向下取整的操作。

3.反卷积
反卷积(Deconvolution)也叫Transposed Convolution或者Fractional Strided Convolution。它的目的不是反向传播,只是试图把卷积以后缩小的图片扩大或者恢复回去,具体原理可以说是卷积的逆过程。有兴趣了解其具体过程可以看这里
反卷积因为是卷积的逆过程,所以它们参数是一样的,格式如下:

nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride, padding, output_padding, bias)

这里只有一个参数和上面不同:
output_padding (int or tuple, optional) : 输出的每条边补充0的层数。可以默认不填。

4.总结:

神经网络发展到今天,已经有多种知识点,作为一个正常人是很难学会和学懂全部知识点的,所以这里建议大家对反卷积,空洞卷积什么的不要太为难自己,如果无法理解就先放着,真的需要理解的时候(要用到时),再具体去查阅更多的资料了解。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值