0603-常用的神经网络层

0603-常用的神经网络层

pytorch完整教程目录:https://www.cnblogs.com/nickchen121/p/14662511.html

一、图像相关层

图像相关层主要包括卷积层(Conv)、池化层(Pool)等

  • 这些层在实际的使用中也分为一维(1D)、二维(2D)和三维(3D)
  • 池化方式分为平均池化(AvgPool)、最大值池化(MaxPool)、自适应池化(AdaptiveAvgPool)
  • 卷积层除了常用的前向卷积,还有逆卷积(TransposeConv)
import torch as t
from torch import nn
from torch.autograd import Variable as V
from PIL import Image
from torchvision.transforms import ToTensor, ToPILImage
to_tensor = ToTensor()  # 把 img 转为 Tensor
to_pil = ToPILImage()
nick = Image.open('img/0802程序输出nick图.jpeg')  # 只能在这里牺牲我帅气的面貌
nick

png

上图是 nick 原图

# 锐化卷积核后的卷积,可以看上一篇文章分享的  06-01 DeepLearning-图像识别 那篇文章里讲到的特征提取

# 输入是一个 batch,batch_size=1
inp = to_tensor(nick).unsqueeze(0)

# 卷积核的构造

kernel = t.ones(3, 3, 3) / -9  # 锐化卷积核,即刻画人物边缘特征,去除掉背景
kernel[:, 1, 1] = 1

conv = nn.Conv2d(
    in_channels=3,  # 输入通道数
    out_channels=1,  # 输出通道数,等同于卷积核个数
    kernel_size=3,  # 核大小
    stride=1,
    bias=False)
conv.weight.data = kernel.view(1, 3, 3, 3)
"""
conv.weight.size() 输出为 torch.Size([1, 3, 3, 3])
第 1 个 1 代表着卷积核的个数,
第 2 个 3 表示的输入通道数,
后面两个 3 是二维卷积核的尺寸
"""

out = conv(V(inp))
to_pil(out.data.squeeze(0))

png

# 普通卷积

# 输入是一个 batch,batch_size=1
inp = to_tensor(nick).unsqueeze(0)

# 卷积核的构造
conv = nn.Conv2d(
    in_channels=3,  # 输入通道数
    out_channels=1,  # 输出通道数,等同于卷积核个数
    kernel_size=3,  # 核大小
    stride=1,
    bias=False)

out = conv(V(inp))
to_pil(out.data.squeeze(0))

png

上图是卷积后处理的 nick图,可以发现已经进行了一定程度的特征提取,并且通过对卷积核进行特定的处理,对人物边缘进行了一定的刻画,并且去除了背景

pool = nn.AvgPool2d(2, 2)
list(pool.parameters())
[]
out = pool(V(inp))
print(inp.size(), out.size())
to_pil(out.data.squeeze(0))
torch.Size([1, 3, 318, 320]) torch.Size([1, 3, 159, 160])

png

上图是池化后的 nick 图,由于我们选择 2 个像素池化为 1 个,可以发现池化后的图像大小正好是原图的 0.5 倍

除了卷积核池化,深度学习还经常用到以下几个层:

  • Linear:全连接层
  • BatchNorm:批规范化层,分为 1D、2D 和 3D。除了标准的 BatchNorm 外,还有在风格迁移中常用到的 InstanceNorm 层
  • Dropout:dropout 层,用来防止过拟合,同样分为 1D、2D 和 3D

下面讲解它们的用法。

# 输入 batch_size=2,维度为 3

inp = V(t.randn(2, 3))
linear = nn.Linear(3, 4)
h = linear(inp)  # (2,3)*(3,4)=(2,4)
h
tensor([[-0.7140, -0.0469,  1.1187,  2.0739],
        [-1.0575, -1.8866, -1.0009, -0.2695]], grad_fn=<AddmmBackward>)
# 4通道,初始化标准差为 4,均值为 0
bn = nn.BatchNorm1d(4)
bn.weight.data = t.ones(4) * 4
bn.bias.data = t.zeros(4)
bn_out = bn(h)
# 注意输出的均值和方差
# 方差是标准差的平方,计算无偏方差分母会减 1
# 使用 unbiased=False,分母不减 1
bn_out.mean(0), bn_out.var(0, unbiased=False)
(tensor([0., 0., 0., 0.], grad_fn=<MeanBackward1>),
 tensor([15.9946, 15.9998, 15.9999, 15.9999], grad_fn=<VarBackward1>))
# 每个元素以 0.5 的概率舍弃
dropout = nn.Dropout(0.5)
o = dropout(bn_out)
bn_out, o  # 有一半左右的数变为 0
(tensor([[ 3.9993,  4.0000,  4.0000,  4.0000],
         [-3.9993, -4.0000, -4.0000, -4.0000]],
        grad_fn=<NativeBatchNormBackward>),
 tensor([[ 0.0000,  8.0000,  8.0000,  0.0000],
         [-7.9986, -8.0000, -0.0000, -8.0000]], grad_fn=<MulBackward0>))

上述的例子中都对 module 的属性也就是 data 进行了直接操作,其中大多都是可学习参数,这些参数在学习中会不断变化。在实际使用中,如非必要,还是不要去修改它们。

二、激活函数

2.1 ReLU 函数

tocrh 实现了常见的激活函数,具体的使用可以去查看官方文档或查看上面分享的文章,看文章的时候需要注意的是激活函数也可以作为独立的 layer 使用。在这里我们就讲讲最常使用的 ReLU 函数,它的数学表达式为:\(ReLU(x) = max(0,x)\),其实就是小于 0 的数置为 0

relu = nn.ReLU(inplace=True)
inp = V(t.randn(2, 3))
inp
tensor([[-1.5703,  0.0868,  1.0811],
        [-0.9903,  0.5288,  0.5926]])
output = relu(inp)  # 小于 0 的都被置为 0,等价于 inp.camp(min=0)
output
tensor([[0.0000, 0.0868, 1.0811],
        [0.0000, 0.5288, 0.5926]])

其中 ReLU 函数有个 inplace 参数,如果设置为 True,它会让输出直接覆盖输入,这样可以节省内存的占用。

在上述例子中,是将每一层的输出直接作为下一层的输入,这种网络称为前馈传播网络。对于这类网络,如果每次也都写上 forward 函数,就会特别麻烦。因此又两者简化方式——ModuleList 和 Sequential。其中 Sequential 是一个特殊的 Module,包含几个子 module,可以像用 list 一样使用它,但不能直接把输入传给它。

2.2 通过Sequential 构建前馈传播网络

# Sequential 的三种写法
net1 = nn.Sequential()
net1.add_module('conv', nn.Conv2d(3, 3, 3))
net1.add_module('batchnorm', nn.BatchNorm2d(3))
net1.add_module('activation_layer', nn.ReLU())

net2 = nn.Sequential(nn.Conv2d(3, 3, 3), nn.BatchNorm2d(3), nn.ReLU())

from collections import OrderedDict
net3 = nn.Sequential(
    OrderedDict([('conv1', nn.Conv2d(3, 3, 3)), ('bn1', nn.BatchNorm2d(3)),
                 ('relu1', nn.ReLU())]))
net1
Sequential(
  (conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (batchnorm): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (activation_layer): ReLU()
)
net2
Sequential(
  (0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (2): ReLU()
)
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()
)
# 可以根据名字或序号取出子 module
net1.conv, net2[0], net3.conv1
(Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)),
 Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)),
 Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)))
# 通过 Sequential 构建的网络处理数据
inp = V(t.rand(1, 3, 4, 4))
output = net1(inp)
output = net2(inp)
output = net3(inp)
output = net3.relu1(net1.batchnorm(net1.conv(inp)))

2.3 通过 ModuleList 构建前馈传播网络

modellist = nn.ModuleList([nn.Linear(3, 4), nn.ReLU(), nn.Linear(4, 2)])
inp = V(t.rand(1, 3))
for model in modellist:
    inp = model(inp)
# output = modellist(inp) # 报错,因为 modellist 没有实现 forward 方法,需要新建一个类定义 forward 方法

在这里使用 ModuleList 而不是使用 list,是因为 ModuleList 是 Module 的子类,在 Module 中使用它时,可以自动识别为子 module,下面我们举例说明。

class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.list = [nn.Linear(3, 4), nn.ReLU()]
        self.module_list = nn.ModuleList([nn.Conv2d(3, 3, 3), nn.ReLU()])

    def forward(self):
        pass


model = MyModule()
model
MyModule(
  (module_list): ModuleList(
    (0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
    (1): ReLU()
  )
)
for name, param in model.named_parameters():
    print(name, param.size())
module_list.0.weight torch.Size([3, 3, 3, 3])
module_list.0.bias torch.Size([3])

从上面的打印结果可以看出,list 中的子 module 并没有被主 module 识别,而 ModuleList 中的子 module 却被主 module 识别了。这也意味着如果用 list 保存子 module,将无法调整其参数,因为这些参数会加入到 module 的参数中。

三、循环神经网络层

循环神经网络多用于自然语言处理(RNN),torch 中目前实现了最常用的三种 RNN:RNN(vanilla RNN)、LSTM 和 GRU,此外还有对应的三种 RNNCell。

RNN 和 RNNCell 的区别在于前者能够处理整个序列,而后者一次只处理序列中的一个时间点和数据,前者更易于使用,后者的灵活性更高。因此 RNN 一般组合调用 RNNCell 一起使用。

t.manual_seed(1000)
inp = V(t.randn(2, 3, 4))  # 输入:batch_size=3,序列长度为 2,序列中每个元素占 4 维
lstm = nn.LSTM(4, 3, 1)  # lstm 输入向量 4 维,3 个隐藏元,1 层

# 初始状态:1 层,batch_size=3,3 个隐藏元
h0 = V(t.randn(1, 3, 3))
c0 = V(t.randn(1, 3, 3))

out, hn = lstm(inp, (h0, c0))
out
tensor([[[-0.3610, -0.1643,  0.1631],
         [-0.0613, -0.4937, -0.1642],
         [ 0.5080, -0.4175,  0.2502]],

        [[-0.0703, -0.0393, -0.0429],
         [ 0.2085, -0.3005, -0.2686],
         [ 0.1482, -0.4728,  0.1425]]], grad_fn=<StackBackward>)
t.manual_seed(1000)
inp = V(t.randn(2, 3, 4))  # 输入:batch_size=3,序列长度为 2,序列中每个元素占 4 维

# 一个 LSTMCell 对应的层数只能是一层
lstm = nn.LSTMCell(4, 3)  # lstm 输入向量 4 维,3 个隐藏元,默认1 层也只能是一层
hx = V(t.randn(3, 3))
cx = V(t.randn(3, 3))

out = []
for i_ in inp:
    hx, cx = lstm(i_, (hx, cx))
    out.append(hx)

t.stack(out)
tensor([[[-0.3610, -0.1643,  0.1631],
         [-0.0613, -0.4937, -0.1642],
         [ 0.5080, -0.4175,  0.2502]],

        [[-0.0703, -0.0393, -0.0429],
         [ 0.2085, -0.3005, -0.2686],
         [ 0.1482, -0.4728,  0.1425]]], grad_fn=<StackBackward>)

词向量在自然语言中应用十分广泛,torch 同样也提供了 Embedding 层

embedding = nn.Embedding(4, 5)  # 有 4 个词,每个词用 5 维的向量表示
embedding.weight.data = t.arange(0, 20).view(4, 5)  # 可以用预训练好的词向量初始化 embedding
with t.no_grad():  # 下面代码的梯度回传有问题,因此去掉梯度
    inp = V(t.arange(3, 0, -1)).long()
    output = embedding(inp)
    print(output)
tensor([[15, 16, 17, 18, 19],
        [10, 11, 12, 13, 14],
        [ 5,  6,  7,  8,  9]])

四、损失函数

在深度学习中也要用到各种各样的损失函数,这些损失函数可以看作是特殊的 layer,torch 也将这些损失函数实现为 nn.Module 的子类。

然而在实际的应用中,一般把损失函数独立出来,作为独立的一部分。详细的 loss 使用可以参考官方文档,这里仅以最常用的交叉熵损失为例讲解。

# batch_size=3,计算对应每个类别的分数(只有两个类别)
score = V(t.randn(3, 2))
# 三个样本分别属于 1,0,1 类,label 必须是 LongTensor
label = V(t.Tensor([1, 0, 1])).long()

# loss 与普通的 layer 没有什么差别
criterion = nn.CrossEntropyLoss()
loss = criterion(score, label)
loss
tensor([[ 1.0592,  1.4730],
        [-0.1558, -0.8712],
        [ 0.2548,  0.0817]])
tensor([1, 0, 1])





tensor(0.5630)
posted @ 2021-04-25 09:02  B站-水论文的程序猿  阅读(1568)  评论(0编辑  收藏  举报