从实战的角度看看,稍微有点收获,视频:CNN 从0到1助你快速入门PyTorch和TensorFlow。github网页:Convolutional Neural Network
------------------Pytorch学习--------------
class:torch.nn.
Linear
(in_features, out_features, bias=True, device=None, dtype=None)全连接层(fully connected layers,FC)在整个卷积神经网络中起到“分类器”的作用。如果说卷积层、池化层和激活函数层等操作是将原始数据映射到隐层特征空间的话,全连接层则起到将学到的“分布式特征表示”映射到样本标记空间的作用。在实际使用中,全连接层可由卷积操作实现:对前层是全连接的全连接层可以转化为卷积核为1x1的卷积;而前层是卷积层的全连接层可以转化为卷积核为hxw的全局卷积,h和w分别为前层卷积结果的高和宽(注1)。参考文章《CNN 入门讲解:什么是全连接层(Fully Connected Layer)?》、《全连接层的作用是什么?》
在计算机视觉中,全连接层和卷积层没有太大区别。卷积层其实就是在spatial上部分连接,在channel上全连接的结构。因此如果舍弃在spatial上的部分连接,将卷积核换成1x1的,那么就只有在channel上全连接。这个时候卷积层=全连接层。这就是为什么imagenet分类最后一层是pooling+fc。pooling完之后feature就1x1xC的了,这时候再做fc就是对channel进行全连接,把feature映射到label上。细心的人会发现很多特征融合的卷积都采用1x1的kernel,这也算是全连接层的一个功能。
# 例子1
# in_features指的是输入的二维张量的大小,即输入的[batch_size, size]中的size。
input = torch.randn(128, 20) # input(batch_size, size)
m = nn.Linear(20, 30)# Linear(in_features, out_features)等同于Linear(size, out_features)
output = m(input) # output二维张量输出形状为[batch_size,out_features],
print(output.size())# torch.Size([128, 30])
#例子2
import torch as t
from torch import nn
# in_features由输入张量的形状决定,out_features则决定了输出张量的形状
connected_layer = nn.Linear(in_features = 64*64*3, out_features = 1)
# 假定输入的图像形状为[64,64,3]
input = t.randn(1,64,64,3)
# 将四维张量转换为二维张量之后,才能作为全连接层的输入
input = input.view(1,64*64*3)
print(input.shape) # torch.Size([1, 12288])
output = connected_layer(input) # 调用全连接层
print(output.shape) # torch.Size([1, 1])
# 例子3
def __init__(self):
super(LeNet, self).__init__()
# Conv2d的第一个参数是输入的channel数量,
# 第二个是输出的channel数量,第三个是kernel size
self.conv1 = nn.Conv2d(3, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# 由于上一层有16个channel输出,
# 每个feature map大小为5*5,
# 所以linear的输入样本大小是16*5*5
self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
# 最终有10类,所以最后一个Linear输出样本大小是10
self.fc3 = nn.Linear(84, 10)
self.pool = nn.MaxPool2d(2, 2)
定义:torch.nn.
Conv2d
(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)[# With square kernels and equal stride
m = nn.Conv2d(16, 33, 3, stride=2) #nn.Conv2d(Cin, Cout, kernel_size, stride=1)
input = torch.randn(20, 16, 50, 100) # torch.Size([20, 16, 50, 100])
output = m(input) # torch.Size([20, 33, 24, 49])
# input和output对比,Conv2d只改变通道数和图像大小
kernel_size
、stride
与 padding
三个参数就行了。也就是N和C不变,只关心图像大小变化H、W。torch.nn.
AvgPool2d
(kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True, divisor_override=None)# pool of square window of size=3, stride=2
m = nn.AvgPool2d(3, stride=2)
input = torch.randn(20, 16, 50, 32) # torch.Size([20, 16, 50, 32])
output = m(input) # torch.Size([20, 16, 24, 15])
# input和output之间,N和C不变,只关心图像大小变化H、W。
# pool of square window of size=3, stride=2
m = nn.MaxPool2d(3, stride=2)
# pool of non-square window
m = nn.MaxPool2d((3, 2), stride=(2, 1))
input = torch.randn(20, 16, 50, 32)
output = m(input)
input.shape
# Out[33]: torch.Size([20, 16, 50, 32])
output.shape
# Out[34]: torch.Size([20, 16, 24, 31])
torch.nn.
BatchNorm2d
(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)>>> # With Learnable Parameters
>>> m = nn.BatchNorm2d(100)
>>> input = torch.randn(20, 100, 35, 45)
>>> output = m(input)
①不仅仅极大提升了训练速度,收敛过程大大加快;
②还能增加分类效果,一种解释是这是类似于Dropout的一种防止过拟合的正则化表达方式,所以不用Dropout也能达到相当的效果;
③另外调参过程也简单多了,对于初始化要求没那么高,而且可以使用大的学习率等。
torch.nn.
Dropout
(p=0.5, inplace=False) >>> m = nn.Dropout(p=0.2)
>>> input = torch.randn(20, 16)
>>> output = m(input)
激活函数是向神经网络中引入非线性因素,通过激活函数神经网络就可以拟合各种曲线。激活函数主要分为饱和激活函数(Saturated Neurons)和非饱和函数(One-sided Saturations)。Sigmoid和Tanh是饱和激活函数,而ReLU以及其变种为非饱和激活函数。非饱和激活函数主要有如下优势:
1.非饱和激活函数可以解决梯度消失问题。
2.非饱和激活函数可以加速收敛。
1. Sigmoid极容易导致梯度消失问题。饱和神经元会使得梯度消失问题雪上加霜,假设神经元输入Sigmoid的值特别大或特别小,对应的梯度约等于0,即使从上一步传导来的梯度较大,该神经元权重(w)和偏置(bias)的梯度也会趋近于0,导致参数无法得到有效更新。
2. 计算费时。 在神经网络训练中,常常要计算Sigmid的值进行幂计算会导致耗时增加。
3. Sigmoid函数不是关于原点中心对称的(zero-centered)。
4.Tanh激活函数解决了原点中心对称问题。
5.ReLU激活函数的提出就是为了解决梯度消失问题。ReLU的梯度只可以取两个值:0或1,当输入小于0时,梯度为0;当输入大于0时,梯度为1。好处就是:ReLU的梯度的连乘不会收敛到0,连乘的结果也只可以取两个值:0或1 。如果值为1,梯度保持值不变进行前向传播;如果值为0 ,梯度从该位置停止前向传播。
6.像ReLU这样单侧饱和还能使得神经元对于噪声干扰更具鲁棒性。
7.ReLU激活函数在计算上也是高效的。相对于Sigmoid函数梯度的计算,ReLU函数梯度取值只有0或1。且ReLU将负值截断为0 ,为网络引入了稀疏性,进一步提升了计算高效性。
8.ReLU尽管稀疏性可以提升计算高效性,但同样也可能阻碍训练过程。通常,激活函数的输入值有一偏置项(bias),假设bias变得太小,以至于输入激活函数的值总是负的,那么反向传播过程经过该处的梯度恒为0,对应的权重和偏置参数此次无法得到更新。如果对于所有的样本输入,该激活函数的输入都是负的,那么该神经元再也无法学习,称为神经元”死亡“问题。
9.Leaky ReLU可以解决神经元”死亡“问题
Leaky ReLU的提出就是为了解决神经元”死亡“问题,Leaky ReLU与ReLU很相似,仅在输入小于0的部分有差别,ReLU输入小于0的部分值都为0,而LeakyReLU输入小于0的部分,值为负,且有微小的梯度。函数图像为(d)。
使用Leaky ReLU的好处就是:在反向传播过程中,对于Leaky ReLU激活函数输入小于零的部分,也可以计算得到梯度(而不是像ReLU一样值为0),这样就避免了梯度方向锯齿问题。
10.梯度误差是在神经网络训练期间计算的方向和梯度,神经网络以正确的方向和数值更新网络权重。在深度网络或递归神经网络中,梯度误差可能在更新过程中累积,造成非常大的梯度。这反过来会导致网络权重的大量更新,进而导致网络不稳定。在极端情况下,权重值可能变得太大,以至于溢出并导致NaN值现成梯度爆炸现象。
梯度爆炸是通过指数增长发生的,通过在网络层(其值大于1.0)中重复乘以梯度。
11.梯度爆炸现象
比较明显的现象:
1.模型无法“加入”训练数据,比如损失函数很差。
2.模型不稳定,每次更新的损失变化很大。
3.模型损失在训练过程中变为NaN
另外还有一些不太明显的现象:
1.模型权重在训练期间很快变化很大。
2.模型权重在训练过程中变为NaN.
3.训练期间每个节点和层的梯度误差始终高于1.0。
12.何解决梯度爆炸
1.重现设计神经网络
减少网络层数、减小batch szie、截断。
2.使用LSTM
3.使用梯度裁剪
clipnorm=1.0 clipvalue=0.5
4.使用权重正则
L1 & L2
13.如何选择激活函数
1.除非在二分类问题中,否则请小心使用Sigmoid函数。
2.可以试试Tanh,不过大多数情况下它的效果会比不上 ReLU 和 Maxout。
3.如果你不知道应该使用哪个激活函数, 那么请优先选择ReLU。
4.如果你使用了ReLU, 需要注意一下Dead ReLU问题, 此时你需要仔细选择 Learning rate, 避免出现大的梯度从而导致过多的神经元 “Dead” 。
5.如果发生了Dead ReLU问题, 可以尝试一下leaky ReLU,ELU等ReLU变体, 说不定会有很好效果。
from collections import OrderedDict
net3= nn.Sequential(OrderedDict([
('conv1', nn.Conv2d(3, 3, 3)),
('bn1', nn.BatchNorm2d(3)),
('relu1', nn.ReLU())
]))
print('net3:', net3)
输出:
net3: Sequential(
(conv1): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
(bn1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu1): ReLU()
)
import torch
import torch.nn as nn
import math
criterion = nn.CrossEntropyLoss()
output = torch.randn(3, 5, requires_grad=True)
label = torch.empty(3, dtype=torch.long).random_(5)
loss = criterion(output, label)
print("网络输出为3个5类:")
print(output)
print("要计算loss的类别:")
print(label)
print("计算loss的结果:")
print(loss)
first = [0, 0, 0]
for i in range(3):
first[i] = -output[i][label[i]]
second = [0, 0, 0]
for i in range(3):
for j in range(5):
second[i] += math.exp(output[i][j])
res = 0
for i in range(3):
res += (first[i] + math.log(second[i]))
print("自己的计算结果:")
print(res/3)
##########################
网络输出为3个5类:
tensor([[ 1.1499, -0.2208, -0.8943, -1.5002, 0.3065],
[-0.0155, 0.7495, 0.4617, 0.5376, 1.2006],
[-0.3464, 0.7741, -1.9237, 0.0350, 0.5038]], requires_grad=True)
要计算loss的类别:
tensor([1, 0, 4])
计算loss的结果:
tensor(1.8443, grad_fn=)
自己的计算结果:
tensor(1.8443, grad_fn=)
# Example of target with class probabilities
loss = nn.CrossEntropyLoss()
input = t.randn(3, 5, requires_grad=True)
target = t.randn(3, 5).softmax(dim=1)
output = loss(input, target)
output.backward()
# batch_size=3,计算对应每个类别的分数(只有两个类别)
score = t.randn(3, 2)
# 三个样本分别属于1,0,1类,label必须是LongTensor
label = t.Tensor([1, 0, 1]).long()
# loss与普通的layer无差异
criterion = nn.CrossEntropyLoss()
loss = criterion(score, label)
loss
opt_SGD=torch.optim.SGD(net_SGD.parameters(),lr=LR)
opt_Momentum=torch.optim.SGD(net_Momentum.parameters(),lr=LR,momentum=0.8)
opt_RMSprop=torch.optim.RMSprop(net_RMSprop.parameters(),lr=LR,alpha=0.9)
opt_Adam=torch.optim.Adam(net_Adam.parameters(),lr=LR,betas=(0.9,0.99))
nn.init
模块就是专门为初始化而设计。import torch
import torch.nn as nn
w = torch.empty(3,5)
#1.均匀分布 - u(a,b)
#torch.nn.init.uniform_(tensor, a=0.0, b=1.0)
print(nn.init.uniform_(w))
# =======================================================
# tensor([[0.9160, 0.1832, 0.5278, 0.5480, 0.6754],
# [0.9509, 0.8325, 0.9149, 0.8192, 0.9950],
# [0.4847, 0.4148, 0.8161, 0.0948, 0.3787]])
# =======================================================
def __init__(self):
self._parameters = OrderedDict()
self._modules = OrderedDict()
self._buffers = OrderedDict()
self._backward_hooks = OrderedDict()
self._forward_hooks = OrderedDict()
self.training = True
其中每个属性的解释如下: