目录

摘要

Abstract

一、CNN

1.1 CNN原理

1.2 Spatial Transformer Layer

1.3 代码

二、RNN

2.1 RNN原理

2.2 LSTM

2.3 RNN在训练会遇到的问题

2.4 应用

三、自注意力机制

3.1 自注意力机制概述

3.2 自注意力机制原理

3.3 其他

总结


摘要

本周主要跟着李宏毅老师的课程,学习了深度学习的经典网络模型,详细学习了CNN、RNN、LSTM、自注意力机制等。对其原理进行了具体的学习,同时也通过Pytorch对这些经典的模型,进行了简易的训练。在完成后续课程之后,我会跟着李沐老师的课程,从头动手敲写代码,深入了解深度学习代码的整个编写过程。

 

Abstract

This week, I mainly followed Professor Li Hongyi's course and learned classic network models of deep learning, including CNN, RNN, LSTM, self attention mechanism, etc. in detail. We have studied its principles in detail and also conducted simple training on these classic models through Pytorch. After completing the subsequent courses, I will follow Professor Li Mu's course and start coding from scratch to gain a deeper understanding of the entire process of writing deep learning code.

 

一、CNN

1.1 CNN原理

卷积神经网络(CNN)是专为图像识别打造的神经网络模型,在图像识别过程中,如果将整张图片做全连接,将会有大量参数,这不仅增加了计算的难度,还加大了过拟合的风险。所以CNN不需要每个神经元和输入的每一个维度都有权重。

CNN在识别物体时,主要判断是否出现重要的模式(pattern)。CNN会设定一个区域的感受野,每一个感受野会有一组专门的神经元去守备。同一组神经元只关心自己感受野里的事,且有不同的分工,如识别鸟嘴、眼镜等。

感受野如下图:

c8ed693897914f49ab297ac38e098c51.png

感受野的深度等于图片的通道数,感受野核大小是人为设定,通常为3x3。

同样的pattern出现在不同的感受野时,不需要分别设置单独的神经元去检测,只需共享不同感受野的对应功能神经元的参数即可,这些共享的参数就称为滤波器(filter)。

121dbb7413ca4867916ada1dd893df5d.png

参数共享如下图: 

6e36a546523c43019ea2a3cc7391ec84.png

感受野+参数共享就是卷积层,有卷积层的神经网络就是卷积神经网络。 

 卷积层与全连接层的关系如下图:

de5034670e514d24bcccc12fc1509e0f.png

做完卷积以后,往往后面还会搭配池化。池化就是把图像变小,从而减少运算量。通常运用最大池化(max pooling)。如下图所示:

a71630f388644fbf82bf5a4afaa40b22.png

89b22b7ee1244cbebac7e8695ab46672.png

池化之后,需要对池化后输出的数值“拉直”变成一个向量。

综上步骤,CNN流程图如下:

83b9c8a25e354a8e83b4b39373646aff.png

CNN不适合处理缩放和旋转的问题,同一张图片,缩放之后的张量对于CNN而言是不同的。 

1.2 Spatial Transformer Layer

空间变换层(Spatial Transformer Layer)使神经网络能够学习到对输入数据进行空间变换的能力,如旋转、缩放、平移等。从而提高模型的表达能力和泛化性能。

主要通过以下几个模块实现:

  • 定位网络(Localisation Network):负责学习输入图像或特征图所需的空间变换参数。
  • 网格生成器(Grid Generator):根据定位网络输出的变换参数和定义的变换方式,网格生成器计算输出特征图上每个点对应到输入特征图上的位置。
  • 采样器(Sampler):根据网格生成器计算出的位置,使用插值法从输入特征图中获取对应的像素值,生成输出特征图。

1.3 代码

使用PyTorch提供的CIFAR-10数据集进行网络训练,共进行3个回合,每回合6batch,每2000次输出一次loss。

import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        # 定义网络层:卷积层+池化层
        self.conv1 = nn.Conv2d(3, 6, stride=1, kernel_size=3)  #in_channels:输入图像通道数;out_channels:卷积产生的通道数;kernel_size:卷积核尺寸;strided:卷积步长
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)  #特征图大小变为13x13
        self.conv2 = nn.Conv2d(6, 16, stride=1, kernel_size=3)
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)  #特征图大小变为5x5
        # 全连接层
        self.linear1 = nn.Linear(400, 120)  #输入400个特征值,输出120个
        self.linear2 = nn.Linear(120, 84)
        self.out = nn.Linear(84, 10)

    def forward(self, x):
        # 卷积+relu+池化
        x = F.relu(self.conv1(x))  #relu:max(0,x)
        x = self.pool1(x)
        # 卷积+relu+池化
        x = F.relu(self.conv2(x))
        x = self.pool2(x)
        # 将特征图做成以为向量的形式:相当于特征向量
        x = x.reshape(x.size(0), -1)  #特征图展平,为全连接层的输入做准备
        # 全连接层
        x = F.relu(self.linear1(x))
        x = F.relu(self.linear2(x))
        # 返回输出结果
        return self.out(x)

if __name__ == '__main__':
    # 数据预处理
    transform = transforms.Compose([transforms.ToTensor(),
                                    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])  #图像转换为张量并归一化

    # 训练集,使用PyTorch提供的CIFAR-10数据集
    trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
    trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)

    # 测试集
    testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
    testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)

    classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

    net = Net()

    criterion = nn.CrossEntropyLoss()  #定义交叉熵损失函数
    optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)  #SGD优化器,net.parameters()返回网络中所有可学习的参数(如权重和偏置)

    # 训练网络
    for epoch in range(3):  # 多次遍历数据集
        running_loss = 0.0
        for i, data in enumerate(trainloader, 0):
            inputs, labels = data

            optimizer.zero_grad()  #清除之前累积的梯度,这是进行反向传播之前的重要步骤

            outputs = net(inputs)
            loss = criterion(outputs, labels)
            loss.backward()  #反向传播
            optimizer.step()  #更新参数

            running_loss += loss.item()
            if i % 2000 == 1999:  # 每2000个小批量数据打印一次损失值
                print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000))
                running_loss = 0.0

    print('训练结束!!!')

    correct = 0
    total = 0
    # 禁用梯度追踪
    with torch.no_grad():
        for data in testloader:
            images, labels = data
            outputs = net(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

    print('10000张测试图片的准确率: %d %%' % (100 * correct / total))

训练结果1w张图片测试准确率为59%,截图如下:

ccaa8313b5994803ac91e33875f22e8a.png

 

二、RNN

2.1 RNN原理

RNN是用于处理时间序列数据、文本数据等具有时序特性的数据的神经网络架构。RNN在序列的每个时间步上都会接受输入,并且其输出会依赖于当前的输入以及之前时间步上的信息。

RNN示例图如下:

0b9d8cf1612e461db7a099e2493263e5.png RNN通常使用交叉熵损失函数来计算损失。在进行梯度下降前,需要先进行BPTT(随时间反向传播)。因为RNN在时间序列上运作,所以要考虑时间上的信息。 

2.2 LSTM

由于传统的RNN中的记忆元只是简单的按照时间顺序记录,每当有新输入时,就会更新记忆元中的数据,所以不利于当前元素寻找所有有关元素的信息。

于是引入长短期记忆网络(LSTM),由三个门控制记忆元里的数据是否更新、遗忘、输出等。

LSTM结构如下图所示: 

  • 输入门:输入门打开时,数据才能被写进记忆元里面;
  • 输出门:输出门打开时,外界才能从这个记忆元里把数据读出来;
  • 遗忘门: 决定记忆元什么时候忘记过去记得的数据。

记忆元公式:

 eq?c%27%3Dg%28z%29f%28z_%7Bi%7D%29+cf%28z_%7Bf%7D%29

 z为输入,g()为激活函数,eq?f%28z_%7Bi%7D%29为输入门控制信号(值为0或1),c为原记忆元的值,eq?f%28z_%7Bf%7D%29为遗忘门信号(值为0或1)。

记忆元示意图如下:

ba35f0032cbd4a73aca276c560859369.png

 综上,LSTM是四个输入,一个输出;输入的时候还需考虑peephole,即记忆元里的值。

LSTM示意图如下:

756f4cb8fd6b4ff48fa1a46892e2bc7e.png

2.3 RNN在训练会遇到的问题

RNN的训练比较困难,因为RNN每个时间步的输出都与前一时间步有关,在反向传播时,导致梯度在传播过程中被连续相乘,所以就会导致以下两个问题。举一个简单例子,如下图所示:

e69c667b8ddc4eb7b15bff335617aa1a.png

2.3.1 梯度爆炸

当遇到梯度爆炸时,通过裁剪(clipping)解决,即梯度大于某个阈值时,不让其再增加。如下图所示:

bdfd085a0dd1447bac67d56e8402676d.png

2.3.4 梯度消失

当遇到梯度消失时,通过LSTM解决。因为LSTM是把原来记忆元里面的值乘上一个值再把输入的值加起来放到单元里面。所以它的记忆和输入是相加的。在 LSTM 里面,一旦对记忆元造成影响,影响一直会被留着,除非遗忘门要把记忆元的值洗掉。不然记忆元一旦有改变,只会把新的东西加进来,不会把原来的值洗掉,所以它不会有梯度消失的问题。

2.4 应用

  • 多对一序列:输入是序列,输出是向量

例如:RNN对一篇文章进行关键术语抽取,然后进行情感分析,如评价正负面等。

  • 多对多序列:输入和输出都是序列,但输出比输入短

例如:CTC穷举所有可能,进行语音识别。

  • 序列到序列

例如:机器翻译等。

 

三、自注意力机制

3.1 自注意力机制概述

自注意力机制(Self-Attention)允许模型在处理一个序列时,考虑到序列中每个元素与其他所有元素之间的关系。这种机制通过计算序列中每个元素与其他元素的关联度eq?%5Calpha,来捕捉序列内的复杂依赖关系。

自注意力机制的目的是考虑整个序列,但又不希望把整个序列的所有信息包在一个窗口里,所以就要找出每个元素对其影响较大的元素。

自注意力机制运行方式如下:

97b0fe25b0d44daf97478b030083234b.png

3.2 自注意力机制原理

自注意力机制一般采用查询-键-值(QKV)模式。

下图将会展示输入如何从一整个序列中得到输出的,如图所示:

5f585c299d6f40c48d94d4e6bd7dc861.png

 eq?a%5E%7B1%7Deq?a%5E%7B2%7Deq?a%5E%7B3%7Deq?a%5E%7B4%7D作为一组序列里的元素,以eq?a%5E%7B1%7D为例,从整个序列中寻找关系输出eq?b%5E%7B1%7D

eq?q%5E%7Bi%7D%3DW%5E%7Bq%7Da%5E%7Bi%7D

eq?k%5E%7Bi%7D%3DW%5E%7Bk%7Da%5E%7Bi%7D

eq?v%5E%7Bi%7D%3DW%5E%7Bv%7Da%5E%7Bi%7D

eq?%5Calpha%20_%7Bi%2Cj%7D%3Dq%5E%7Bi%7D%5Ccdot%20k%5E%7Bj%7D通过softmax得到eq?%5Calpha%20_%7Bi%2Cj%7D%27

eq?b%5E%7B1%7D%3D%5Csum_%7Bi%3D1%7D%5E%7BI%7D%5Calpha%20_%7Bi%2Cj%7D%27v%5E%7Bi%7D  (加权和)

所以谁的注意力分数eq?%5Calpha%20_%7Bi%2Cj%7D%27越大,谁的v就会主导b。

公式中只有eq?W%5E%7Bq%7Deq?W%5E%7Bk%7Deq?W%5E%7Bv%7D是未知的,需要通过训练学习的。

通过矩阵表达上述式子,如图所示:

a54cb32c72644ed19465072168db88f8.png

3.3 其他

(1)多头自注意力,即不同的q负责不同种类的相关性,如图所示:

c82173161bf24e78a1566dbad051d619.png

(2)做自注意力时,需要对每一个输入元素做位置编码,通常使用正弦函数产生。

16031cc9e9ae4e39a9e1d1063678a29b.png

(3)当输入序列长度过大时,需要做截断自注意力,人为规定一个范围即可。 

(4)CNN是自注意力的一个特例,在数据量较小的时,CNN准确率优于自注意力。

22f5afaa06414e99ba612464bb377b36.png

(5)RNN是“弱版”自注意力,双向RNN可以做到考虑整个序列,但它要保证记忆元一直记忆所有有可能产生影响的信息。

 

总结

本周的学习到此结束,下周将继续学习深度学习的神经网络模型,如:Transformer、生成式对抗网络等。如有错误,请各位大佬指出,谢谢!

 

 

 

 

 

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐