神经网络概述
神经网络包括输入层、隐藏层(中间层)、输出层
设计神经网络时,输入与输出层的沈金元数量是固定的,中间层的层数以及神经元数量可以自由指定
如下图,展示了一个输入层2个输入单元、隐藏层1层4个神经元、输出层2个输出单元的神经网络结构。
箭头单标训练与测试时的数据流向。
一般输入层和输出层的神经元数量是固定的,隐藏层的神经元数量我们可以指定。
在神经网络中输入层的数据经过每一个神经元的计算后就变成了输出值
神经网络的中间层越多、神经元数量越多,功能越强大,同时所需计算资源越大
例如,经典的AlexNet模型有8个中间层(5个Cov卷积层、3个FC全连接层)、65万个神经元、6000万个参数
每个神经元包括:输入、输出以及两个计算
x表示输入,x1,x2,..., xr
w表示权值,wi1,wi2,...,wir。其中下标中第一个值i对应于整个神经元的最终输出yi的下标i;第二个下标1....r表示输入x的下标值
第一个运算-先相乘:w与相对应的x相乘
第一个运算-后求和:∑是求和运算
α是求和后得到的值
第二个运算-激活函数:f是激活函数,把α的值转换到一个固定范围内
y表示神经元的输出
w的值再最开始都是随机的,或者可以定义成0或者1,注意不能全0。
(本文出自oschina博主happyBKs的博文:https://my.oschina.net/happyBKs/blog/3171003)
在神经网络的训练过程中,数据的传递方向有正向和反向之分。
正向传递:在学习过程中,输入数据经过神经元的运算,变成输出数据的一个过程。
反向传递:但是在学习过程中,我们要想调节每一个权重w的最优值,所以我们会把输出数据y与标签数据label进行比较,即把输出数据与正确答案进行比较,然后把比较的无法逐层传递回最前面的一层,这就是所谓的反向传递。
神经网络中的反向传递是用梯度来解决的,具体的说叫做梯度速降,来计算每一个梯度的最优值,然后去进行权重w的更新。
关于梯度是如何运算的我这里就不介绍了,pytorch中所有的梯度计算、反向传递都是自动完成的
Autograd包
Pytorch所有神经网络的核心,这个包完成了所有梯度计算、反向传递的过程。
为tensor上的所有操作提供了自动区分
在autograd下,反向传递(backprop)代码自动定义
参数、方法、操作 | 说明 |
张量参数requires_grad | 在tensor上设定.requires_grad=true后,,autograd会自动追踪与该tensor有关的所有运算。 |
.backward() | 所有梯度运算完成后,执行.backward(),autograd会自动计算梯度并执行反向传递 |
.grad | 用来访问梯度 |
with torch.no_grad() | 自动忽略梯度 |
第一步,创建tensor,设定参数requires_grad=true
import torch
# 创建一个2d tensor,2乘2的矩阵,元素全为1,类型<class 'torch.Tensor'>
x = torch.ones(2, 2, requires_grad=True)
print(x)
输出
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
第二步,对输入的tensor进行计算。(之前我们说过pytorch是动态计算图,构建过程和计算过程可以同时进行)
y = x + 2
z = y * y
out = z.mean()
print("y:", y)
print("z:", z)
print("out:", out)
第三步,计算梯度
调用backward()则梯度会自动被计算完,并被反向传播回去。
# 计算梯度、反向传播
out.backward()
运算过程中,日志默认不会打印任何结果。但我们可以打印梯度值。
# 打印梯度值
print("grad:", x.grad)
梯度值输出:
grad: tensor([[1.5000, 1.5000],
[1.5000, 1.5000]])
第四步,忽略梯度计算结果,多用于训练结束之后的预测计算
# 如果忽略梯度,看看结果
with torch.no_grad():
k = x + 1
print("k:", k)
输出结果,可以看到,这里输出的tensor,已经没有requires_grad=True属性了
神经网络的训练过程
定义神经网络
迭代输入数据
神经网络计算输出
计算损失
反向传递梯度回到网络的参数
更新网络的权重
下图是AlexNet(5层卷积层,3层全连接层)的示意图:
我们可以看到从输入值进入第一层隐藏层之后,第一层隐藏层(Cov1)输出的是一些类似于边界特征的值(Edge+Blob);输出的值继续往下传递,当传递到中间层(Cov3)的时候,神经网络能够找到一些纹理性的特征(Texture);然后输出越靠后,后面的层输出的内容越靠近我们人眼能够看到的东西,如Cov5最后一层卷积层输出对象部分(object parts);通过基层全连接层将识别的对象分类。
接下来我们以一个示例代码来看看,神经网络在pytorch中是如何实现的。
我们尝试实现一个AlexNet网络的简化版。实现一个隐藏层只有5层,其中2层卷积层,3层全连接层的神经网络结构。
我们定义一个类HappyBKsNet,在其构造函数中(1)定义神经网络的基本结构,通过实现覆盖nn.Module的forward方法(2)定义数据流向。
import torch
import torch.nn as nn
import torch.nn.functional as F
class HappyBKsNet(nn.Module):
def __init__(self):
super(HappyBKsNet, self).__init__()
# 定义神经网络基本结构
# 输入数据的格式 为 1x32x32,通道数x图像像素行数x图像像素列数。例如,rgb图片通道是为3,所以输入数据应该是3x32x32
# 第一层(卷积层)
# Conv2d为2维卷积层的类,三个参数分别为(输入通道数量,输出通道数量, 卷积大小3x3)
self.conv1 = nn.Conv2d(1, 6, 3)
# 第二层(卷积层)
# 第二层卷积层的输入通道是第一层输出的通道,所以数量都是6;第二层卷积层输出通道数是16;并且本层还是用3x3的卷积
self.conv2 = nn.Conv2d(6, 16, 3)
# 第三层(全连接层)
# 全连接层在pytorch的nn中又叫线性层,用Linear类创建。其有2个参数,(输入通道数量,输出通道数量)
# 注意,全连接层的输入数量不单单是上一层卷积层的输出16,而是16 * 28 * 28 = 12544。这里的28是我们神经网络的输入32x32,经过两次卷积之后,每次减2,得到32 - 2 -2 = 28
self.fc1 = nn.Linear(16 * 28 * 28, 512)
# 第四层(全连接层)
# 上一层已经是全连接层了,所以上一层输出的就是一个1*n的向量。所以按照上一层全连接层输出的向量尺寸输入本层全连接层即可。
# 输入维度512, 输出维度64
self.fc2 = nn.Linear(512, 64)
# 第五层(全连接层)
# 输入维度64, 输出维度2
self.fc3 = nn.Linear(64, 2)
def forward(self, *input, **kwargs):
# 定义数据流向
# 输入数据为x
x = input[0]
# 输入数据输入第一层conv1
x = self.conv1(x)
# 第一层之后,我们要做一个激活:把x值变换到一个固定的范围内
# 激活变换到的数据范围,可以是0~1, -1~1, 0~+∞,对应于不同的激活函数
# 这里我们使用relu
x = F.relu(x)
# 将第一层的输出传入第二层中,也同样再做一个激活
x = self.conv2(x)
x = F.relu(x)
# 从卷积层到全连接层,数据的形状是要发生变化的。和上面定义网络结构时,定义输出输入参数一样,这里也需要对输入数据做一个形状变换。
# 我们需要把上一层数据的输出形状变一下,
x = x.view(-1, 16 * 28 * 28)
# 将变换形状后的输入传入下一层全连接层中。
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
x = F.relu(x)
x = self.fc3(x)
return x
if __name__ == "__main__":
happyBKsNet= HappyBKsNet()
print(happyBKsNet)
具体神经网络结构的定义说明,请见上面每一步附加的注释说明。我特地写得很仔细,方便一些对深度神经网络不太熟悉的同学也能有个大概的理解。
在Pytorch中定义一个神经网络模型,需要继承Module,并实现一些特定的方法,如forward。
而一些特殊的层,pytorch也为我们定义好了,如二维卷积层,通过继承关系可以发现,这样的卷积层,也是继承Module。这与Keras中的情形非常相似,哪天我写Keras博客文章的时候再说吧。
全连接层也是一样。
在定义完神经网络的基本结构之后,我们还需要定义神经网络的数据流向,即告诉网络:哪一个是第一层,哪一个是第二层。。。
具体也是参考上面代码中我的注释吧。值得注意的是两个地方:一个是卷积层到全连接层的输入数据的形状变换,无论在定义结构和定义数据流向时都是需要的;第二是激活函数的引入,这个我们后面专门放一篇博客来介绍。
我们运行上面的程序,可以看见该网络打印出来的神经网络结构:
Net(
(conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
(fc1): Linear(in_features=12544, out_features=512, bias=True)
(fc2): Linear(in_features=512, out_features=64, bias=True)
(fc3): Linear(in_features=64, out_features=2, bias=True)
)
同事喊我玩游戏了,今天先到这里,后面继续写。