• 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)
  • 相关阅读:
    AutoLISP圆内接多边形
    AutoLISP以直线为基线添加文字
    jquery easyuidatagrid 如何清空数据
    MyBatis直接执行SQL查询及批量插入数据
    mappers文件中的#{}语法与${}语法的区别
    在windows7桌面上新建一个快捷方式,指向cmd命令窗口
    ResultSet转换List或直接遍历解决null问题
    linux命令
    flash build 4.6 报错 内存不足,无法加载...
    关于linux安装openoffice无法启动
  • 原文地址:https://www.cnblogs.com/nickchen121/p/14698979.html
Copyright © 2020-2023  润新知