自学内容网 自学内容网

第十五周:机器学习笔记

摘要

本周周报在机器学习的理论内容中,详细描述了attention matrix计算的各种优化形式,其中包括用人类自身的理解而诞生的Local Attention、Stride Attention、Global Attention、Clustering,也有通过机器自身通过学习而诞生Sinkhorn Sorting Network,最后还有简化attention matrix格式的Linformer。此外,周报在Pytorch学习中还补充了模型训练的细节和描述了如何利用GPU训练的过程。最后,在数学拓展部分,对self-attention的计算过程优化进行了数学推导,解释了如何通过矩阵乘法结合律来优化self-attention的计算速度。

Abstract

In this week’s report, the theoretical aspects of machine learning are explored, with a detailed exposition on the diverse optimization techniques for calculating the attention matrix. Various forms of attention mechanisms are discussed, including Local Attention, Stride Attention, Global Attention, and Clustering, which have been conceptualized through human comprehension. The Sinkhorn Sorting Network, a novel approach that has been independently developed through machine learning algorithms, is also presented. Moreover, the Linformer, which streamlines the attention matrix format, is introduced.Furthermore, the report provides additional insights into the nuances of model training within the PyTorch framework and elucidates the process of leveraging GPU resources for training purposes. In the mathematical extension section, the report delves into the optimization of the self-attention computation process, offering a mathematical derivation that explains how the associative property of matrix multiplication can be utilized to enhance the computational efficiency of self-attention mechanisms.

一、机器学习

1. 各式各样神奇的自注意力机制

之前的self-attention学习中,我们接触到了各种各样的Transformer的变形。
我们都知道self-attention机制是Transformer里面重要的一环
所以有些情况改进self-attention对优化Transformer有很大的帮助
在这里插入图片描述
所以我们今天来探究一下如何对self-attention进行优化
不过在说优化self-attention之前,我们需要知道一个非常重要的东西
self-attention只是Transformer中的一个module
这就是为什么我上面要强调“有些情况优化self-attention” 而不是 “任何时候优化self-attention”
是因为有时候我们优化self-attention未必对整个Transformer的优化有很好的效果。
我们都知道Transformer是一个Seq 2 Seq的模型,Sequence的长度决定了self-attention的运算复杂度。
如果我们的Sequence 长度(记为N)并不是很大时,整个Transformer的运算复杂程度可能就是由fully connected 层主导的。所以彼时,我们优化self-attention其实并没有太大的效果
但是,我们在做图像识别的时候,N往往很大的。
比如我们有一张256 * 256 size的图片,我们把图片中的每个pixel展开为一列就是 256 * 256 个pixel(即Sequence length为2562
在这里插入图片描述
假设Sequence的长度为N,那么我们计算其α(attention score)时,就要进行N2次运算(如下图矩阵所示,每个key与query相乘)。
如果 N 的值为256*256就要进行2564次运算,这个运算量是非常庞大的。
所以在图像识别中,Transformer主要的运算集中在self-attention中,因此我们此时优化self-attention模块是非常有必要的
在这里插入图片描述
那么我们有什么优化self-attention的方式呢?

1.1 Local Attention/Truncated Attention(截断注意力机制)

self-attention之所以运算复杂,是因为我们要处理N2次运算(即处理q与k的matrix)
如果我们对self-attention实际情况有所理解,是不是就可以不用计算N2次那么多呢?
如下图所示
我们的输入只与其相邻的有关,我们就不用关注全局的输入
因此我们计算α时,只需计算相邻的。
那这样就可以加速运算,因为灰色的部分不用计算

解释如下:
在这里插入图片描述
但这显然有一个问题:
就是每次你在做self-attention的时候,你只看得到某一个小范围之内的信息
如果attention都是在一个很小的范围内,那其实他就是CNN。
所以用Truncated Attention那就等于是做CNN,那不如直接用CNN就好了
所以它是一个可以加快self-attention的运算的方法。但不一定可以有非常好的结果。

1.2 Stride Attention(跨步注意力机制)

那有那除了local attention以外,还有各式各样的变形
只看相邻的信息不好,那我们就看比较远一点的信息
比如说在每一个位置,我们不是看下一个位置的信息和上一个位置的信息
而是先跳两格,看第三个位置之后的信息和三个位置之前的信息,这种方法叫做Stride Attention
当用Stride Attention的时候,在下图灰色的部分就是直接填零就,我们只计算有颜色的部分。

当然可以根据你现在处理的问题决定可以只空一格,也可以空三格。
在这里插入图片描述

1.3 Global Attention(全局注意力机制)

不管是刚才看过的local attention还是stride attention,都是以某一个位置作为中心的看这个位置,看左右两边的信息。
如果我们想要知道一整个attention的信息,就可以使用Global Attention。
在做Global Attention的时候,会在原来的Sequence里面加上一个特殊的token。

假设是处理NLP的问题,在文字的加入一个特殊的符号。这个特殊的符号代表这个位置要做global attention。 他会从里面的每一个token去收集信息来了解一下这整个句子里面整个里面发生了什么样的事情。这些special token他们会attend到所有的token,也会被所有的token attend
global attention的具体操作有两种:
①在你原来的token里面就直接选择一些token作为special token。
举个例子,一般用Transformer来处理这个文字的时候,会放一个开头的token当做special token,或者是把句号当做special token(就是你从原来就有的token选一些当做 special token)
②另外一种做法是外加额外的token
不管句子是什么内容,都直接插如两个符号,这两个符号代表是special token。

如下图所示:
这一个句子里面这一个input sequence里面头两个位置。
头两个位置就是special token叫做global attention的special token
这两个special token会看到所有其他的token,做其他的token也都会看到这两个token。
在这里插入图片描述
凭借着人的理解决定的这一种self-attention的有local attention、stride attention、global attention。
那哪一种最好呢?
真正好的结果就是全部都有,
在multi-head attention中我们可以设定多个不同的head。就让每一个head做不同的事
有的是local attention、有的是stride attention、有的是global attention等,这样你就不需要做选择了。
在这里插入图片描述
以一些比较知名的transformer这个的变形就是这么做的,比如:
Longformer就是把local attention+stride attention+global attention。
BigBird则使用了random attention(即随机选一些token,让它们彼此之间是有联系的)
在这里插入图片描述

1.4 Clustering(聚类)

刚刚我们是用人的理解直接告诉你说哪一些位置就不需要算哪些位置。也许人类设计的attention,并不能得到最好的结果。
那我们能不能够不要用人力的方法呢?
举例来说,在一个self-attention的吗matrix里,可能某些位置他的value特别大,某一些位置他的attention的value特别小。那这个时候其实你可以把特别小的位置直接补零,因为特别小的位置本来他可能本来就很接近零,也许有变成零跟没有变成零,结果是不会差距太大的。
那如果我们用这样子的方法来加快的运算,那我也那也许我们就是就是简化以后的跟原来也不会有太大的差距。
如下图:
我们可以直接估计在一个里面哪一些位置有可能会有比较大的值,我们只计算那些有可能会有比较大的值的位置。
如果有些位置它的attention的位置的value就会很小,直接就不进行任何的计算,直接把它当做是零。

在这里插入图片描述
问题是怎么快速的估算出哪些位置可能有大的、哪些位置可能有小到直接就可以无视呢?
这里有一个技术叫做——Clustering(聚类)
step 1 :先把query与key拿出来,根据query与key他们相近的程度做clustering。
比较近的就分配在一起,比较远的就属于不同的cluster
如下图所示:
这个例子里,边框红色为cluster 1 、边框紫色为cluster 2、边框绿色为cluster 3、边框黄色为cluster 4。
clustering的目的是要让相近的vector就属于同一个cluster,不相近的cluster就属于不同的vector。
在这里插入图片描述
**那clustering会不会也耗费很大的运算量呢?**如果clustering的complexity也跟sequence的平方有关的话,那根本没有办法加速的运算。
但是实际上clustering有很多可以加速的方法
其会采取一个可能是估测不是非常准确,但非常快速的方法来将query与key进行分区(以后再补充这个方法)

step 2 :把query跟key归在同一群里面以后,接下来我们才去计算的weight
如下图:
红色的query跟这三个key是在同一个cluster里面,那我们就计算一下红色的attention weight
红色的query跟跟其他的这五个key,不在同一个cluster里面,就代表他们的距离其实是偏远的,直接把他们的attention weight设为0。
其他颜色同理
所以整个attention matrix里面会有很多位置都不需要去计算,这样就可以加快这个attention matrix的计算
在这里插入图片描述

1.5 Sinkhorn Sorting Network(Sinkhorn分拣网络)

我们怎么决定哪一些位置要计算哪些位置和不要计算都是凭着人类对这个问题的理解去解决的,因为clustering是假设比较近的vector,我们才去计算它的attention
所以到目前为止,要不要计算attention往往还是基于人类对这个问题的理解。

那有没有办法把要不要计算attention这件事情,直接把它learn出来呢?
我们要计算的的时候,你要先产生一个attention matrix,这个attention matrix里有一些位置是1,有一些位置是0。
如下图示:
涂深色的代表1;涂浅色的代表0。只有涂深色的地方需要去计算attention,浅色的则不需要计算。
哪个query要与对应的key做点积,我们需要用下图的matrix来决定。matrix而这个怎么来呢?在到目前为止,这部分计算的都是人决定的。
在这里插入图片描述
但是在Sinkhorn Sorting Network里,它决定直接learn一个network,让另外一个network来决定哪些地方需要计算attention
那要怎么做呢?
如下图示:
绿色部分是你input sequence。
然后把input sequence的每一个position通过一个network产生一个vector(vector的长度要跟sequence一致)
然后input sequence每一个vector通过一个network就产生另外一排vector。
把这些vector全部拼起来以后,它的大小也要是N × N(跟attention matrix一样)
那我们现在的目标就是要把这么多排的vector变成attention matrix。
计算出来那么多排vector不是binary的而是连续的(即其值不止0和1),而我们的attention matrix是binary(只有0和1),其用一个很特别的技术来解决vector排到matrix转换这个问题,而且这一个过程是可以微分的,所以最后你在train这个nn的时候是跟整个network一起train出来的。

那有个疑问?难道input sequence 的 vector 通过 NN 产生另外一个vector就不耗时了吗?
其实这里面是有技巧的,即好几个input vector会共用同一个产生出来的vector
例如:
假设input一个很长的sequence(假设100个vector)。将其分成10段,每段10个vector,10个vector一起通过network,以后产生另外一个vector,那10个vector要共用,同一个产生初出来的vector。
可以理解为假设本来要算100×100的matrix(即100×100的attention)
在Sinkhorn Sorting Network是解析度比较低的,它其实做10 × 10 的attention,然后再放大为100 × 100

在这里插入图片描述

1.6 Linformer

到目前为止都想产生一个N × N的matrix,但是真的需要N × N的matrix吗?
如下图所示:
在我们计算attention matrix中,其实很多grid的值都是一样的(颜色一致)
即很多信息都是重复的,那我们为何不把这些重复的信息拿掉,产生一个比较小的attention matrix。
在这里插入图片描述
那要怎么实现呢?
来有N个key,从N个k里面是挑大K个出来当做key的代表。
在这里插入图片描述
把这个attention的matrix变瘦,我们只算一个n×k个attention matrix。
在这里插入图片描述
那产生attention matrix后,接下来你要怎么用attention matrix这个产生self-attention layer的output呢?
你有n个value你一样要挑出k个具代表性的value
有k个key vector,就有k个value vector
k个key对第一个行query算出来的attention weight,对K个value vector做运算得到第一个位置的output。
其他以此类推
在这里插入图片描述
为什么我们只选有代表性的key而不选有代表性的query呢?
如果我们把query变为原来的一半,那么我们的output sequence也会变为原来的一半
假设你今天的任务是input sequence里面的每一个位置都需要output一个label,那么如果我们选择减少query的数量,输出结果就会出错

那么我们的代表性的key是如何选出来的呢?
方式一:直接将input sequence 它通过CNN,将N个key变为K个key

方式二:我们input sequence 的 vector 维度是 d,那么有 N 个 vector 整个就为d × N 的矩阵
我们可以用这d × N的矩阵 跟一个 N × K的 矩阵相乘变为一个 d × K的矩阵
实际上就是一个线性结合的过程,选出来代表性的key 都是结合了之前N个vector信息的。
在这里插入图片描述

二、Pytorch学习

1. 完整的模型训练套路(下)

上一周,我们学习了完整的训练套路,从数据集的准备与处理、模型的搭建、模型的训练、结果的输出、模型的测试等各方面做了一次完整的学习。
但是我们训练的时候不单只有回归的模型。在我们日常生活中分类问题也是很重要的
但是分类很重要的一点就是要需要展示正确率,例如,我们测试集中有100张图片,我在这100张图片到底预测正确了多少张。这就是所谓的正确率。
然而在我们上一周的模型中,却没有对分类这一特定的问题展示模型预测的正确率。所以这一周我们要实现这个功能。
思路如下图所示:
argmax就是帮我们选取模型预测后概率最大的那个类别的下标。
在这里插入图片描述
我们可以用一个demo来感受一下:

import torch

output = torch.tensor([[0.1, 0.2],
                       [0.05, 0.3]])

print(output.argmax(1))

可以看到,其结果是[1,1],跟我们想象的一样,是横向排0,1的。
在这里插入图片描述
当argmax取0时,结果如下

import torch

output = torch.tensor([[0.1, 0.2],
                       [0.05, 0.3]])

print(output.argmax(0))

可以看到,其结果是[0,1],因为是横向排0,1的。
在这里插入图片描述
整个demo代码如下:

import torch

# 假设这个是模型预测的结果,只有两个类别 0 与 1
output = torch.tensor([[0.1, 0.2],
                       [0.05, 0.3]])

# 输出argmax的结果
print(output.argmax(1))

# 预测值
preds = output.argmax(1)

# 真实值
input_targert = torch.tensor([[0, 1]])

# 比较真实值与预测值,输出 每一个预测的true与false
print(preds == input_targert)

# 输出全部预测的结果
print((preds == input_targert).sum())

可以看到,跟我们手写的结果差不多
在这里插入图片描述
接下来,我们要将其运用到我们的上一周的CIFAR-10 的 Model 中
主要修改的地方就是测试模块部分
需要加上我们上述的代码
看看最终的预测结果
代码如下:

import torch
import torchvision
from torch.utils.tensorboard import SummaryWriter

from p23_model import *

# 第一步:准备数据集
# 训练集
train_data = torchvision.datasets.CIFAR10(root='./datasets', train=True, download=True,
                                          transform=torchvision.transforms.ToTensor())
# 测试集
test_data = torchvision.datasets.CIFAR10(root='./datasets', train=False, download=True,
                                         transform=torchvision.transforms.ToTensor())
# length 用于展示训练集和测试集的长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# format的用法:{}.format(变量) ,变量的值会替换{} -》例如,train_data_size = 10,那么{}就会被替换成10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))

# 第二步:利用dataloader 加载训练数据集
# 加载训练集
train_dataloader = torch.utils.data.DataLoader(train_data, batch_size=64)
# 加载测试集
test_dataloader = torch.utils.data.DataLoader(test_data, batch_size=64)

# 加载模型
MCifar = MCifar()

# 定义损失函数(cross entropy)
loss_func = torch.nn.CrossEntropyLoss()

# 优化器
# learning_rate = 0.1
# 1e-2 = 1 * (10)^-2
learning_rate = 1e-2
optimizer = torch.optim.SGD(MCifar.parameters(), lr=learning_rate)

# 设置训练网络的一些参数
# 训练的轮数
total_train_step = 0
# 记录测试的次数
total_test_step = 0

# 训练的轮数
epoch = 10

# 添加tensorboard
writer = SummaryWriter("./logs_train")

for i in range(epoch):
    print('------第 {}轮训练开始-------'.format(i + 1))
    # 训练步骤开始
    for data in train_dataloader:
        images, labels = data
        optimizer.zero_grad()
        outputs = MCifar(images)
        loss = loss_func(outputs, labels)
        loss.backward()
        optimizer.step()
        # 训练完一次,数训练的次数+1
        total_train_step += 1
        if total_train_step % 100 == 0:
            print('训练次数{},Loss:{}'.format(total_train_step, loss.item()))
            # 将train_loss的数值加入到tensorboard中
            writer.add_scalar('train_loss', loss.item(), total_train_step)

    # 测试步骤开始
    total_test_loss = 0
    # 测试的正确率
    total_test_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            images, labels = data
            outputs = MCifar(images)
            loss = loss_func(outputs, labels)
            total_test_step += 1
            total_test_loss = total_test_loss + loss
            # 正确率
            accuracy = (outputs.argmax(1) == labels).sum()
            total_test_accuracy = total_test_accuracy + accuracy
    print('整体的loss为{}'.format(total_test_loss))
    print('整体的正确率为{}'.format(total_test_accuracy / test_data_size))
    # 将test_loss的数值加入到tensorboard中
    writer.add_scalar('test_loss', total_test_loss, total_test_step)

    # 保存模型
    # 每训练一轮保存一次
    torch.save(MCifar, "Mcifar_{}.pth".format(i))
    print('模型已保存')

# 关闭tensorboard
writer.close()

可以看到,随着训练轮数的增加,正确率也在慢慢上升
在这里插入图片描述
此外,我们还要注意一些小细节。
例如我们在训练的时候可以加上.train()表示训练开始

 # 训练步骤开始
    MCifar.train()
    for data in train_dataloader:
        images, labels = data
        optimizer.zero_grad()
        outputs = MCifar(images)
        loss = loss_func(outputs, labels)
        loss.backward()
        optimizer.step()
        # 训练完一次,数训练的次数+1
        total_train_step += 1
        if total_train_step % 100 == 0:
            print('训练次数{},Loss:{}'.format(total_train_step, loss.item()))
            # 将train_loss的数值加入到tensorboard中
            writer.add_scalar('train_loss', loss.item(), total_train_step)

.train()就是将模块设置为训练模式。
这个操作或设置只对特定的模块有影响。例如,像Dropout和Batch Normalization这样的模块可能会受到这些操作或设置的影响。
在这里插入图片描述

同样,在测试集中我们可以使用.eval()


    # 测试步骤开始
    MCifar.eval()
    total_test_loss = 0
    # 测试的正确率
    total_test_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            images, labels = data
            outputs = MCifar(images)
            loss = loss_func(outputs, labels)
            total_test_step += 1
            total_test_loss = total_test_loss + loss
            # 正确率
            accuracy = (outputs.argmax(1) == labels).sum()
            total_test_accuracy = total_test_accuracy + accuracy
    print('整体的loss为{}'.format(total_test_loss))
    print('整体的正确率为{}'.format(total_test_accuracy / test_data_size))
    # 将test_loss的数值加入到tensorboard中
    writer.add_scalar('test_loss', total_test_loss, total_test_step)

.eval()将模块设置为评估模式。
这种设置只对某些模块有影响。如果这些模块受到影响,可以在特定模块的文档中查看它们在训练/评估模式下的行为细节,例如Dropout(随机失活)和BatchNorm(批量归一化)。
在这里插入图片描述
补充了.trian()与.eval()后,我们的代码实战的细节就比较完整了

2. 利用GPU训练

方式一
cuda就是利用我们的独显训练,可以加快运行效率,极大提升运行速度
这是我们用cpu训练的速度,训练一轮需要25秒,非常耗时
在这里插入图片描述
所以我们如果有一台好的电脑,需要发挥它强大的GPU
以下内容,可以使用cuda(即使用GPU)
在这里插入图片描述
接下来,我们加入 计时器 以及 模型、训练数据集和测试数据集的cuda

# 计时器
import time
# 开始时间
start_time = time.time()
# 结束时间
end_time = time.time()

# cuda的使用
# 加载模型
mCigar = MCifar()
if torch.cuda.is_available():
    mCigar = mCigar.cuda()
    
# 训练数据集的cuda使用
if torch.cuda.is_available():
    images = images.cuda()
    labels = labels.cuda()
    
# 测试数据集的cuda使用
if torch.cuda.is_available():
    images = images.cuda()
    labels = labels.cuda()

整体代码如下:
(只训练一轮)

import torch
import torchvision
from torch import nn
from torch.utils.tensorboard import SummaryWriter
import time

# 第一步:准备数据集
# 训练集
train_data = torchvision.datasets.CIFAR10(root='./datasets', train=True, download=True,
                                          transform=torchvision.transforms.ToTensor())
# 测试集
test_data = torchvision.datasets.CIFAR10(root='./datasets', train=False, download=True,
                                         transform=torchvision.transforms.ToTensor())
# length 用于展示训练集和测试集的长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# format的用法:{}.format(变量) ,变量的值会替换{} -》例如,train_data_size = 10,那么{}就会被替换成10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))

# 第二步:利用dataloader 加载训练数据集
# 加载训练集
train_dataloader = torch.utils.data.DataLoader(train_data, batch_size=64)
# 加载测试集
test_dataloader = torch.utils.data.DataLoader(test_data, batch_size=64)


# 模型
class MCifar(torch.nn.Module):
    def __init__(self):
        super(MCifar, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2, 2),
            nn.Flatten(),
            nn.Linear(1024, 64),
            nn.Linear(64, 10),
        )

    def forward(self, x):
        x = self.model(x)
        return x


# 加载模型
mCigar = MCifar()
if torch.cuda.is_available():
    mCigar = mCigar.cuda()

# 定义损失函数(cross entropy)
loss_func = torch.nn.CrossEntropyLoss()
if torch.cuda.is_available():
    loss_func = loss_func.cuda()
# 优化器
# learning_rate = 0.1
# 1e-2 = 1 * (10)^-2
learning_rate = 1e-2
optimizer = torch.optim.SGD(mCigar.parameters(), lr=learning_rate)

# 设置训练网络的一些参数
# 训练的轮数
total_train_step = 0
# 记录测试的次数
total_test_step = 0

# 训练的轮数
epoch = 1

# 添加tensorboard
writer = SummaryWriter("./logs_train_gpu1")
start_time = time.time()
for i in range(epoch):
    print('------第 {}轮训练开始-------'.format(i + 1))

    # 训练步骤开始
    mCigar.train()
    for data in train_dataloader:
        images, labels = data
        if torch.cuda.is_available():
            images = images.cuda()
            labels = labels.cuda()
        optimizer.zero_grad()
        outputs = mCigar(images)
        loss = loss_func(outputs, labels)
        loss.backward()
        optimizer.step()
        # 训练完一次,数训练的次数+1
        total_train_step += 1
        if total_train_step % 100 == 0:
            print('训练次数{},Loss:{}'.format(total_train_step, loss.item()))
            # 将train_loss的数值加入到tensorboard中
            writer.add_scalar('train_loss', loss.item(), total_train_step)

    # 测试步骤开始
    mCigar.eval()
    total_test_loss = 0
    # 测试的正确率
    total_test_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            images, labels = data
            if torch.cuda.is_available():
                images = images.cuda()
                labels = labels.cuda()
            outputs = mCigar(images)
            loss = loss_func(outputs, labels)
            total_test_step += 1
            total_test_loss = total_test_loss + loss
            # 正确率
            accuracy = (outputs.argmax(1) == labels).sum()
            total_test_accuracy = total_test_accuracy + accuracy
    print('整体的loss为{}'.format(total_test_loss))
    print('整体的正确率为{}'.format(total_test_accuracy / test_data_size))
    # 将test_loss的数值加入到tensorboard中
    writer.add_scalar('test_loss', total_test_loss, total_test_step)

    # 保存模型
    # 每训练一轮保存一次
    torch.save(mCigar, "mCigar_{}.pth".format(i))
    print('模型已保存')

end_time = time.time()
print(end_time - start_time)
# 关闭tensorboard
writer.close()

可以看到,我们训练一轮使用了10秒左右,比原来的25秒快乐15秒,速度得到极大的提升!
在这里插入图片描述
方式二——.to(device)

# 引入设备(意思即使cuda可用时用cuda,否则用cpu)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# 加载模型
mCigar = MCifar()
mCigar.to(device)

# 训练数据集的cuda使用
images, labels = images.to(device), labels.to(device)

# 测试数据集的cuda使用
images, labels = images.to(device), labels.to(device)

整体代码如下:

import torch
import torchvision
from torch import nn
from torch.utils.tensorboard import SummaryWriter
import time

# 引入设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# 第一步:准备数据集
# 训练集
train_data = torchvision.datasets.CIFAR10(root='./datasets', train=True, download=True,
                                          transform=torchvision.transforms.ToTensor())
# 测试集
test_data = torchvision.datasets.CIFAR10(root='./datasets', train=False, download=True,
                                         transform=torchvision.transforms.ToTensor())
# length 用于展示训练集和测试集的长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# format的用法:{}.format(变量) ,变量的值会替换{} -》例如,train_data_size = 10,那么{}就会被替换成10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))

# 第二步:利用dataloader 加载训练数据集
# 加载训练集
train_dataloader = torch.utils.data.DataLoader(train_data, batch_size=64)
# 加载测试集
test_dataloader = torch.utils.data.DataLoader(test_data, batch_size=64)


# 模型
class MCifar(torch.nn.Module):
    def __init__(self):
        super(MCifar, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2, 2),
            nn.Flatten(),
            nn.Linear(1024, 64),
            nn.Linear(64, 10),
        )

    def forward(self, x):
        x = self.model(x)
        return x


# 加载模型
mCigar = MCifar()
mCigar.to(device)

# 定义损失函数(cross entropy)
loss_func = torch.nn.CrossEntropyLoss()
loss_func.to(device)
# 优化器
# learning_rate = 0.1
# 1e-2 = 1 * (10)^-2
learning_rate = 1e-2
optimizer = torch.optim.SGD(mCigar.parameters(), lr=learning_rate)

# 设置训练网络的一些参数
# 训练的轮数
total_train_step = 0
# 记录测试的次数
total_test_step = 0

# 训练的轮数
epoch = 10

# 添加tensorboard
writer = SummaryWriter("./logs_train_gpu1")
start_time = time.time()
for i in range(epoch):
    print('------第 {}轮训练开始-------'.format(i + 1))

    # 训练步骤开始
    mCigar.train()
    for data in train_dataloader:
        images, labels = data
        # 训练数据集的cuda使用
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = mCigar(images)
        loss = loss_func(outputs, labels)
        loss.backward()
        optimizer.step()
        # 训练完一次,数训练的次数+1
        total_train_step += 1
        if total_train_step % 100 == 0:
            print('训练次数{},Loss:{}'.format(total_train_step, loss.item()))
            # 将train_loss的数值加入到tensorboard中
            writer.add_scalar('train_loss', loss.item(), total_train_step)

    # 测试步骤开始
    mCigar.eval()
    total_test_loss = 0
    # 测试的正确率
    total_test_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            images, labels = data
            # 测试数据集的cuda使用
            images, labels = images.to(device), labels.to(device)
            outputs = mCigar(images)
            loss = loss_func(outputs, labels)
            total_test_step += 1
            total_test_loss = total_test_loss + loss
            # 正确率
            accuracy = (outputs.argmax(1) == labels).sum()
            total_test_accuracy = total_test_accuracy + accuracy
    print('整体的loss为{}'.format(total_test_loss))
    print('整体的正确率为{}'.format(total_test_accuracy / test_data_size))
    # 将test_loss的数值加入到tensorboard中
    writer.add_scalar('test_loss', total_test_loss, total_test_step)

    # 保存模型
    # 每训练一轮保存一次
    torch.save(mCigar, "cifar10_{}.pth".format(i))
    print('模型已保存')

end_time = time.time()
print(end_time - start_time)
# 关闭tensorboard
writer.close()

结果如下:
在这里插入图片描述

三、数学拓展——通过矩阵运算加速self-attention的过程

我们来回顾一下从Input Sequence 到 Output Sequence的过程,如下图所示:
其实attention的这个process其实就是一连串矩阵的相乘的过程
在这里插入图片描述
这个矩阵相乘的过程中有没有可以减少运算量的部分呢?
如下图所示,在我们的矩阵乘法中,是有结合律的。
不同的结合顺序,他们的运算复杂度就不同
在这里插入图片描述
举个具体的例子:
在这里插入图片描述
然后代入到我们的self-attention运算当中
可以看到先算(V×KT) × Q 明显比V ×(KT× Q)的运算量要小
在这里插入图片描述
那我们在self-attention的运算中要如何想让V与KT先相乘呢?
我们先来复习一下self-attention的计算过程:
在这里插入图片描述
换算过程如下:
拿计算b1举例
在这里插入图片描述
在这里插入图片描述
整个过程图示如下:
拿b1举例
在这里插入图片描述
算出的是分子,分母的运算就不用赘述了。
在这里插入图片描述
exp ⁡ ( q ⋅ k ) ≈ ϕ ( q ) ⋅ ϕ ( k ) \begin{aligned}& \exp (\boldsymbol{q} \cdot \boldsymbol{k}) \\\approx & \phi(\boldsymbol{q}) \cdot \phi(\boldsymbol{k})\end{aligned} exp(qk)ϕ(q)ϕ(k)
以上公式的转换,有很多方法,具体可以参考以下论文
在这里插入图片描述
其实现在的transformer中,attention matrix甚至都不需要训练,其直接将其作为参数放入到network中,训练的效果也是大差不差的,因此人们对attention matrix是否留用又产生了进一步的思考
在这里插入图片描述

总结

本周的进度比较缓慢,希望下一周再接再厉。
本周在机器学习理论部分,学习了的变形。自注意力机制是深度学习中的一种关键技术,它允许模型在序列的不同位置关注不同的信息。本周的报告涵盖了以下自注意力机制的优化形式:1、 Local Attention/Truncated Attention(截断注意力机制):这种机制限制了注意力的范围,只关注输入序列中的局部区域,从而减少了计算复杂度。2、Stride Attention(跨步注意力机制):通过在计算注意力时采用步长,这种机制可以有效地处理长序列,同时保持对重要信息的关注。3 、Global Attention(全局注意力机制):与局部注意力相反,全局注意力机制考虑整个输入序列,捕捉全局依赖关系。4、Clustering(聚类):通过将输入序列分组到不同的簇中,这种机制可以有效地处理具有相似特征的序列部分。5、Sinkhorn Sorting Network(Sinkhorn分拣网络):这是一种新颖的自注意力机制,它通过Sinkhorn排序算法来优化注意力权重的分配。6、Linformer:Linformer通过简化注意力矩阵的格式,减少了计算量,同时保持了模型的性能。
在Pytorch学习中,针对分类问题利用argmax()加入了准确率的输出,才外还补充训练的一些细节,比如在训练模块加上.train();在测试模块加上.eval()。此外还并详细描述了如何利用GPU加速模型训练过程。
在数学拓展部分,探讨了如何通过矩阵运算来加速self-attention的计算过程,通过重新排列矩阵乘法的顺序,可以减少计算量并提高计算效率。
下一周计划学习生成式模型,然后完结Pytorch课程学习,跑一次完整的模型。


原文地址:https://blog.csdn.net/Zcymatics/article/details/142640920

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!