TowardsDataScience 2023 博客中文翻译(三百三十四)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

迁移学习入门

原文:towardsdatascience.com/transfer-learning-for-beginner-9b59490d1b9d

图像分类中的迁移学习实用指南

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Mina Ghashami

·发布于 Towards Data Science ·阅读时间 7 分钟·2023 年 10 月 29 日

在这篇文章中,我们将探讨迁移学习的概念,并将看到一个图像分类任务的例子。

什么是迁移学习?

迁移学习是深度学习中的一种技术,其中在大规模数据集上训练的预训练模型被用于解决具有有限标记数据的新任务。

它涉及到使用一个已经在源任务上学习到丰富且通用特征表示的预训练模型,并在目标任务上进行微调。

例如,ImageNet 是一个大型数据集(1400 万张图像,1000 个类别),通常用于训练大型卷积神经网络,如 VGGNet 或 ResNet。

如果我们在 ImageNet 上训练这些网络,这些模型会学习提取强大而富有信息的特征。我们称这种训练为预训练,这些模型在 ImageNet 上经过预训练。注意,它们是在 ImageNet 上进行图像分类任务的训练。我们称之为源任务

要在一个新的任务上进行迁移学习,我们称之为目标任务,首先我们需要有标记的数据集,即目标数据集。目标数据集通常比源数据集要小得多。我们这里的源数据集非常庞大(有 1400 万张图像)。

然后,我们取这些预训练的模型,去掉最后的分类层,在末尾添加一个新的分类器层,并在我们自己的目标数据集上进行训练。在训练时,我们冻结所有层,除了最后一层,因此只有极少量的参数进行训练,从而训练速度很快。瞧!我们完成了迁移学习。

模型经过的第二次训练称为微调。正如我们所看到的,在微调期间,大多数预训练的权重被冻结,只有最后几层会根据新数据集进行调整。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

迁移学习的好处

迁移学习的关键优势在于,它允许你利用预训练模型中已开发的专业知识,从而避免从头开始训练大型模型。它还减少了需要大量标注数据集的需求,这些数据集收集和注释起来非常耗时。

微调预训练模型比从头开始训练要快得多,计算成本也更低。这些模型通过建立在预训练期间学到的一般特征之上,通常能实现高准确率。

迁移学习的注意事项

迁移学习的注意事项是目标任务和数据集必须接近源任务和数据集。否则,预训练过程中学到的知识对目标任务将没有用。如果是这样,我们不如从头开始训练模型。

实际示例

我们将使用 VGGNet 来演示迁移学习。在 上一篇文章 中,我们看到了 VGGNet。如果你不熟悉,请查看一下。

## 初学者图像分类

VGG 和 ResNet 架构,2014 年

[towardsdatascience.com

VGG(视觉几何组)是由牛津大学视觉几何组开发的深度卷积神经网络(CNN)架构。它有许多变体,如 VGG16 和 VGG19。所有变体的架构类似,除了层数不同。例如,VGG-16 具有 16 层,包括 13 层卷积层和 3 层全连接层。

在 ImageNet 上训练的 VGGNet 通常作为图像分类中的预训练模型用于迁移学习。

对于微调,我们将使用 STL10 数据集,其中包含来自 10 个不同类别的 5000 张小尺寸彩色图像(96x96 像素)。该数据集分为 5000 张图像的训练集和 8000 张图像的测试集。

STL10 数据集是我们的目标数据集,ImageNet 是我们的源数据集,它们在本质上非常相似,因此在迁移学习中使用它们是有意义的。

下面是我们设置的摘要表格:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源于作者

加载预训练模型

由于 VGG 的最后一层是 1000(因为它是为包含 1000 类的 ImageNet 训练的),我们将删除它并用 10 类的层替换它。

from torchvision.models import vgg16

# Load the pre-trained VGG-16 model
vgg = vgg16(pretrained=True)
print(vgg)

当我们打印 VGG 架构时,我们看到:最后 3 层是全连接层,其中最后一个全连接层是分类头,它将 4096 维的输入分类为 1000 类。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源于作者

我们需要剪掉最后一层,并放置一个新的层,将输入分类为 10 类!因为 STL10 只有 10 个类别。所以我们这样做:

# Modify the last layer of VGG by changing it to 10 classes
vgg.classifier[6] = nn.Linear(in_features=4096, out_features=len(classes))

device = torch.device("cuda" if torch.cuda.is_available() else "cpu");
vgg.to(device)

数据准备

我们首先加载和转换目标数据。代码如下:

# train transformation
transform_train = transforms.Compose([
    transforms.RandomCrop(96, padding = 4), # we first pad by 4 pixels on each side then crop
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize((0.44671103, 0.43980882, 0.40664575), (0.2603408 , 0.25657743, 0.2712671))
])

# test transformation
transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.44671103, 0.43980882, 0.40664575), (0.2603408 , 0.25657743, 0.2712671))
])

trainset = torchvision.datasets.STL10(root = './data', split = 'train', download = True, transform=transform_train)
trainloader = torch.utils.data.DataLoader(trainset, batch_size = 128, shuffle = True, num_workers = 2)

testset = torchvision.datasets.STL10(root = './data', split = 'test', download = True, transform=transform_test)
testloader = torch.utils.data.DataLoader(testset, batch_size = 256, shuffle = True, num_workers = 2)In above transformation that we have defined on train data you see that we are augmenting the data by cropping a random 28x28 patch and flipping it. The reason we augment the data is to increase diversity in the training data and force the model to learn better.

我们解释每一部分。首先,

transforms.RandomCrop(96, padding = 4)
transforms.RandomHorizontalFlip(),

***RandomCrop()***接受两个参数——输出大小和填充。例如,输出大小为 32 和填充 4 时,它首先在每边填充 4 像素,然后从填充后的图像中随机裁剪一个 32x32 的区域。

这允许裁剪包括原始图像的边缘像素,因此通过从相同输入生成不同的裁剪来实现数据增强。如果没有填充,裁剪将总是从中心进行,而不包括边缘区域。

这种数据增强帮助模型接触图像的不同部分,提高了泛化能力。

其次,

transforms.ToTensor()

***ToTensor()***将 PIL 图像或 numpy 数组转换为可以输入神经网络的 Tensor。它处理从图像数据到 PyTorch 兼容的张量所需的所有转换,例如将数据标准化到(0,1)范围,并将(H, W, C)数组转置为(C, H, W)以供 PyTorch 模型输入。例如,一个 RGB 图像将变成一个 3xHxW 的 Tensor,而一个灰度图像变成一个 1xHxW 的 Tensor。

最后,

transforms.Normalize((0.44671103, 0.43980882, 0.40664575), (0.2603408 , 0.25657743, 0.2712671))

通过减去均值并除以标准差来规范化数据。

微调模型

微调模型有两种方式:

  1. 要么我们冻结之前的层,仅训练分类头。

  2. 或者我们一起训练所有层。

虽然第一种方法更快,但第二种方法可能更准确。我们首先通过选项 2 训练模型。为此,我们需要以下函数:

def train_batch(epoch, model, optimizer):
    print("epoch ", epoch)
    model.train()
    train_loss = 0
    correct = 0
    total = 0

    for batch_idx, (input, targets) in enumerate(trainloader):
        inputs, targets = input.to(device), targets.to(device)
        optimizer.zero_grad()
        outputs, _ = model(inputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()

        train_loss += loss.item()
        _, predicted = outputs.max(1)
        total += targets.size(0)
        correct += predicted.eq(targets).sum().item()
    print(batch_idx, len(trainloader), 'Loss: %.3f | Acc: %.3f%% (%d/%d)'
                         % (train_loss/(batch_idx+1), 100.*correct/total, correct, total))

def validate_batch(epoch, model):
    model.eval()
    test_loss = 0
    correct = 0
    total = 0
    with torch.no_grad():
        for batch_idx, (inputs, targets) in enumerate(testloader):
            inputs, targets = inputs.to(device), targets.to(device)
            outputs,_ = model(inputs)
            loss = criterion(outputs, targets)

            test_loss += loss.item()
            _, predicted = outputs.max(1)
            total += targets.size(0)
            correct += predicted.eq(targets).sum().item()

    print(batch_idx, len(testloader), 'Loss: %.3f | Acc: %.3f%% (%d/%d)'
                 % (test_loss/(batch_idx+1), 100.*correct/total, correct, total))

然后将它们放在一起得到完整的训练:

start_epoch = 0
for epoch in range(start_epoch, start_epoch+20):
    train_batch(epoch, vgg_model, vgg_optimizer)
    validate_batch(epoch, vgg_model)
    vgg_scheduler.step()

如果我们决定冻结某些层并不训练它们,我们需要将某一层的权重和偏置设置为requires_grad = False以冻结该层。

这就结束了我们关于图像分类中迁移学习的话题。

结论

迁移学习是一种技术,其中在一个任务上训练的模型被重新用作第二个相关任务的起点。它允许你利用预训练模型中的知识,而不是从头开始训练模型。例如,我们可以使用一个在 ImageNet 上预训练的模型,并在一个新的类似图像的数据集上重新训练它。预训练模型在第一个任务中学到的特征被转移并在新任务中重用。

如果你有任何意见或问题,请告诉我。

如果你有任何问题或建议,请随时与我联系:

邮箱:mina.ghashami@gmail.com

LinkedIn: www.linkedin.com/in/minaghashami/

变压器辅助的供应链网络设计

原文:towardsdatascience.com/transformer-aided-supply-chain-network-design-fe45bb846f0f?source=collection_archive---------9-----------------------#2023-02-17

使用变压器来帮助解决供应链中的经典问题——设施选址问题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Guangrui Xie

·

关注 发表在 Towards Data Science ·15 min read·2023 年 2 月 17 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由Mika Baumeister拍摄,来源于Unsplash

由于 ChatGPT 的广泛智能来完成各种任务,最近成为了一个热门话题。ChatGPT 的核心模型是变压器,最初在谷歌研究团队的著名论文Attention is all you need中首次提出。变压器使用注意力机制和位置编码一次处理整个句子,而不是像递归神经网络(如 LSTM、GRU)那样逐字处理。这样可以减少递归过程中的信息丢失,从而使变压器比传统的递归神经网络更能有效地学习长距离上下文依赖关系。

变压器的应用不仅限于语言模型。自首次提出以来,研究人员已将其应用于许多其他任务,如时间序列预测、计算机视觉等。我一直在思考的问题是,变压器能否用于帮助解决供应链领域的实际运筹学(OR)问题?在本文中,我将展示一个使用变压器来辅助解决供应链领域经典 OR 问题——设施选址问题的例子。

设施选址问题

让我们首先回顾一下这个问题的基本设置。我在我的第一篇文章中也简要讨论了这个问题。

假设一家公司想要在I个候选地点中建设配送中心(DCs),以将成品运送给J个客户。每个地点i都有其关联的存储容量,最多可以存放m_i单位产品。 在地点i建立一个 DC 需要固定的建设费用f_i。从地点i向客户j运送每单位产品需要运费c_ij。每个客户j的需求为d_j,且所有客户的需求必须得到满足。设二进制变量y_i表示是否在地点i建立一个 DC,而x_ij表示从地点i向客户j运送的产品量。目标是最小化总成本的优化问题可以表述如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

设施选址问题的公式化(图由作者提供)

这是一个混合整数线性规划(MILP)问题,可以使用任何商业或非商业求解器(例如 CPLEX、GUROBI、SCIP)来解决。然而,当模型变得很大(IJ 很大)时,求解模型可能会花费很长时间。这个问题的主要复杂性来自整数变量 *y_i’*s,因为这些变量是需要在分支定界算法中进行分支的。如果我们能找到一种更快的方法来确定 *y_i’*s 的值,那将节省大量的求解时间。这就是变压器发挥作用的地方。

变压器辅助解决方案方法

这种解决方案方法的基本思想是通过从大量实例的设施选址问题的解决方案中学习,训练一个变压器来预测 *y_i’*s 的值。这个问题可以被公式化为一个分类问题,因为 *y_i’*s 只能取 0 或 1 的值,对应两个类别。类别 0 表示站点 i 没有被选中用于建立 DC,类别 1 表示站点 i 被选中。一旦训练完成,变压器可以一次性预测每个 y_i 属于哪个类别,从而节省大量用于整数变量分支的时间。下图展示了这里采用的变压器架构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

本文采用的变压器模型的架构(作者提供的图像)

如上图所示,每个候选 DC 站点和客户的向量表示(包含每个候选 DC 站点和客户的相关特征)被传递到一个线性层。线性层为每个候选 DC 站点和客户生成一个嵌入向量。这些嵌入向量随后通过论文中描述的 N 个标准编码块 Attention is all you need 进行处理。在编码块中,多头注意机制允许每个候选 DC 站点关注所有客户和其他候选 DC 站点的信息。然后,编码块的输出通过一个线性层 + softmax 层作为解码过程。

我为这个特定问题对变压器架构所做的一些调整是:

  1. 由于从优化问题的角度来看,输入序列 V_dc1, …, V_cusJ 的确切顺序并不重要,因此去除了位置编码。换句话说,随机打乱输入序列对变压器的输出不应影响设施选址问题的解决。

  2. 输出层为输入序列中的每个元素 V_dc1, …, V_cusJ 生成一个二维向量。这个二维向量包含该元素属于类别 0 和类别 1 的概率。注意,这个二维向量对于元素 V_cus1, …, V_cusJ 实际上是无效的,因为我们不能在客户的站点上建立 DC。因此,在训练变换器时,我们对 V_cus1, …, V_cusJ 对应的输出进行掩蔽,以计算损失函数,因为我们不关心这些输出是什么。

在这个设施选址问题中,我们假设单位运输成本 c_ij 与站点 i 和客户 j 之间的距离成正比。我们定义 V_dci[dci_x, dci_y, m_i, f_i, 0],其中 dci_xdci_y 是候选 DC 站点 i 的坐标,0 表示这是一个候选 DC 站点特征的向量表示;我们定义 V_cusj[cusj_x, cusj_y, d_j, 0, 1],其中 cusj_xcusj_y 是客户 j 的坐标,0V_dcif_i 的对应项,表示在客户处没有固定费用,并且 1 表示这是客户的向量表示。因此,输入序列中的每个元素是一个 5 维向量。通过第一层线性层,我们将每个向量投影到一个高维嵌入向量,并将其输入到编码器块中。

要训练变换器,我们首先需要构建一个数据集。为了设置数据集中的标签,我们使用优化求解器(即 SCIP)求解大量设施选址问题的实例,然后记录每个实例的 *y_i’*s 的值。每个实例的坐标、*d_j’*s、*m_i’*s 和 *f_i’*s 用于构建变换器的输入序列。整个数据集被分成训练集和测试集。

一旦变换器训练完成,它可以用来预测每个候选 DC 站点 i 属于哪个类别,换句话说,就是 y_i 是 0 还是 1。然后我们将每个 y_i 固定为其预测值,并用剩余的变量 *x_ij’s 来求解 MILP。注意,一旦 *y_i’*s 的值被固定,设施选址问题变成一个 LP,这使得使用优化求解器解决起来更为简单。得到的解可能是次优的,但对于大规模实例可以节省大量的求解时间。

变换器辅助解决方案方法的一个警告是,变换器给出的预测* y_i 值有时可能导致 MILP 不可行,因为变换器的预测并不完全准确。不可行性是由于 m_i * y_i 的总和小于 d_j 的总和,这意味着如果我们按照预测的 y_i 值操作,将没有足够的供应来满足所有客户的总需求。在这种情况下,我们保留 y_i *为 1 的值,并在剩余的候选 DC 站点中搜索,以弥补总供应和需求之间的差距。实际上,我们需要解决如下的另一个优化问题:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解决不可行性的问题表述(图片由作者提供)

这里,* I_0 是预测 y_i 值为 0 的候选 DC 站点集合, I_1 是预测 y_i 值为 1 的候选 DC 站点集合。这本质上是一个背包问题,可以通过优化求解器解决。然而,对于大型实例,该问题仍可能消耗大量求解时间。因此,我决定采用启发式方法解决这个问题——解决分数背包问题的贪婪算法。具体来说,我们计算每个候选 DC 站点在 I_0 中的比例 f_i / m_i ,并根据比例对站点进行升序排序。然后我们从第一个站点到最后一个站点逐个选择,直到满足约束条件。完成此过程后,将 I_0 中所选站点的 y_i 值与 I_1 中的所有站点设置为 1,其余站点的 y_i 值设置为 0。然后将这种配置输入优化求解器以求解其余的 x_ij *变量。

数值实验

我随机创建了 800 个设施选址问题实例,包含 100 个候选 DC 站点和 200 个客户。我使用了 560 个实例进行训练,240 个实例进行测试。非商业求解器 SCIP 被用于解决这些实例。生成实例的代码如下。

from pyscipopt import Model, quicksum
import numpy as np
import pickle

rnd = np.random
models_output = []
for k in range(1000):
    info = {}
    np.random.seed(k)
    n_dc = 100
    n_cus = 200
    c = []
    loc_x_dc = rnd.rand(n_dc)*100
    loc_y_dc = rnd.rand(n_dc)*100
    loc_x_cus = rnd.rand(n_cus)*100
    loc_y_cus = rnd.rand(n_cus)*100
    xy_dc = [[x,y] for x,y in zip(loc_x_dc,loc_y_dc)]
    xy_cus = [[x,y] for x,y in zip(loc_x_cus,loc_y_cus)]
    for i in xy_dc:
        c_i = []
        for j in xy_cus:
            c_i.append(np.sqrt((i[0]-j[0])**2 + (i[1]-j[1])**2)*0.01)
        c.append(c_i)    

    f= []
    m = []
    d = []

    for i in range(n_dc):
        f_rand = np.round(np.random.normal(100,15))
        if f_rand < 40:
            f.append(40)
        else:
            f.append(f_rand)
        m_rand = np.round(np.random.normal(70,10))
        if m_rand < 30:
            m.append(30)
        else:
            m.append(m_rand)
    for i in range(n_cus):
        d_rand = np.round(np.random.normal(20,5))
        if d_rand < 5:
            d.append(5)
        else:
            d.append(d_rand)

    f = np.array(f)
    m = np.array(m)
    d = np.array(d)
    c = np.array(c)

    model = Model("facility selection")
    x,y = {},{}
    x_names = ['x_'+str(i)+'_'+str(j) for i in range(n_dc) for j in range(n_cus)]
    y_names = ['y_'+str(i) for i in range(n_dc)]
    for i in range(n_dc):
        for j in range(n_cus):
            x[i,j] = model.addVar(name=x_names[i*n_cus+j])
    for i in range(n_dc):
        y[i] = model.addVar(name=y_names[i],vtype='BINARY')
    model.setObjective(quicksum(f[i]*y[i] for i in range(n_dc))+quicksum(c[i,j]*x[i,j] for i in range(n_dc) for j in range(n_cus)), 'minimize')
    for j in range(n_cus):
        model.addCons(quicksum(x[i,j] for i in range(n_dc)) == d[j])
    for i in range(n_dc):
        model.addCons(quicksum(x[i,j] for j in range(n_cus)) <= m[i]*y[i])
    model.optimize()

    y_sol = []
    for i in range(n_dc):
        y_sol.append(model.getVal(y[i]))
    x_sol = []
    for i in range(n_dc):
        x_i = []
        for j in range(n_cus):
            x_i.append(model.getVal(x[i,j]))
        x_sol.append(x_i)
    obj_vol = model.getObjVal()

    info['f'] = f
    info['m'] = m
    info['d'] = d
    info['c'] = c
    info['y'] = y_sol
    info['x'] = x_sol
    info['obj'] = obj_vol
    info['xy_dc'] = xy_dc
    info['xy_cus'] = xy_cus
    models_output.append(info)

with open('models_output.pkl', 'wb') as outp:
    pickle.dump(models_output, outp)

然后我们读取解决方案并创建用于训练变换器的数据集。请注意,在构建数据集时,我将笛卡尔坐标系转换为极坐标系,因为后者能提高变换器的准确性。

from sklearn.preprocessing import MinMaxScaler
import torch

with open('models_output.pkl', 'rb') as f:
    models_output = pickle.load(f)

def convert_coord(xy):
    r = np.sqrt((xy[0]-50)**2 + (xy[1]-50)**2)
    theta = np.arctan2(xy[1],xy[0])
    return [theta, r]

dataset = []
for k in range(len(models_output)):
    data = {}
    xy_site = []
    new_xy_dc = [convert_coord(i) for i in models_output[k]['xy_dc']]
    new_xy_cus = [convert_coord(i) for i in models_output[k]['xy_cus']]
    xy_site.extend(new_xy_dc)
    xy_site.extend(new_xy_cus)
    d_site = []
    d_site.extend([[i] for i in models_output[k]['m']])
    d_site.extend([[i] for i in models_output[k]['d']])
    f_site = []
    f_site.extend([[i] for i in models_output[k]['f']])
    f_site.extend([[0] for i in range(200)])
    i_site = []
    i_site.extend([[0] for i in range(100)])
    i_site.extend([[1] for i in range(200)])
    x = np.concatenate((xy_site, d_site), axis=1)
    x = np.concatenate((x, f_site), axis=1)
    x = np.concatenate((x, i_site), axis=1)
    if k == 0:
        scaler_x = MinMaxScaler()
        x = scaler_x.fit_transform(x)
    else:
        x = scaler_x.transform(x)
    x = np.expand_dims(x,axis=1)
    y = []
    y_cus = [2 for i in range(200)]
    y.extend(models_output[k]['y'])
    y.extend(y_cus)
    x = torch.from_numpy(x).float()
    y = torch.from_numpy(np.array(y)).long()
    data['x'] = x
    data['y'] = y
    dataset.append(data)
    mask = []
    mask_true = [True for i in range(100)]
    mask_false = [False for i in range(200)]
    mask.extend(mask_true)
    mask.extend(mask_false)

然后我们定义变换器的架构。这里的嵌入向量是 256 维的,多头注意力机制中的头数是 8,编码器块的数量是 3。我没有实验太多的超参数设置,因为这个设置已经达到了令人满意的准确性。可以进行更多的超参数调整,以进一步提高变换器的准确性。

import torch
from torch import nn, Tensor
import torch.nn.functional as F
from torch.nn import TransformerEncoder, TransformerEncoderLayer

class TransformerModel(nn.Module):
    def __init__(self, n_class=2, d_input=5, d_model=256, nhead=8, d_hid=256, nlayers=3, dropout=0.5):
        super().__init__()
        encoder_layers = TransformerEncoderLayer(d_model, nhead, d_hid, dropout)
        self.transformer_encoder = TransformerEncoder(encoder_layers, nlayers)
        self.d_model = d_model
        self.encoder = nn.Linear(d_input, d_model)
        self.decoder = nn.Linear(d_model, n_class)

        self.init_weights()

    def init_weights(self) -> None:
        initrange = 0.1
        self.decoder.weight.data.uniform_(-initrange, initrange)

    def forward(self, src: Tensor) -> Tensor:
        src = self.encoder(src)
        output = self.transformer_encoder(src)
        output = self.decoder(output)
        return output

然后我们使用先前创建的数据集对变换器进行训练和测试。

import torch.optim as optim

def acc(y_pred, y_test):
    y_pred_softmax = torch.log_softmax(y_pred, dim = 1)
    _, y_pred_tags = torch.max(y_pred_softmax, dim = 1)
    correct_pred = (y_pred_tags == y_test).float()
    acc = correct_pred.sum() / len(correct_pred)
    return acc

LEARNING_RATE = 0.0001
EPOCHS = 100
model = TransformerModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
n_class = 2

for e in range(1,EPOCHS+1):
    model.train()
    e_train_loss = []
    e_train_acc = []
    for i in range(560):
        optimizer.zero_grad()
        y_pred = model(dataset[i]['x'])
        train_loss = criterion(y_pred.view(-1,n_class)[mask], dataset[i]['y'][mask])
        train_acc = acc(y_pred.view(-1,n_class)[mask], dataset[i]['y'][mask])
        train_loss.backward()
        optimizer.step()
        e_train_loss.append(train_loss.item())
        e_train_acc.append(train_acc.item())
    with torch.no_grad():
        model.eval()
        e_val_loss = []
        e_val_acc = []
        for i in range(560,800):
            y_pred = model(dataset[i]['x'])
            val_loss = criterion(y_pred.view(-1,n_class)[mask], dataset[i]['y'][mask])
            val_acc = acc(y_pred.view(-1,n_class)[mask], dataset[i]['y'][mask])
            e_val_loss.append(val_loss.item())
            e_val_acc.append(val_acc.item())
    print('epoch:', e)
    print('train loss:', np.mean(e_train_loss))
    print('train acc:', np.mean(e_train_acc)) 
    print('val loss:', np.mean(e_val_loss))
    print('val acc:', np.mean(e_val_acc))

torch.save(model.state_dict(), desired_path)

在训练后,变压器在包含 240 个实例的测试集上达到了约 92%的准确率。考虑到测试集没有不平衡(测试集中的标签 0 和 1 分别占 46.57%和 53.43%),并且我们的变压器并不是总是预测相同的类别,92%的准确率应该是一个令人满意的分数。

最后,我们测试了变压器辅助解决方案方法与仅使用 SCIP 求解器直接求解 MILP 的比较。主要目的是证明变压器有助于减少大量的求解时间,而不会显著恶化解决方案质量。

我创建了另外 100 个随机未见的实例,参数为m_i, f_i, d_j,这些实例遵循与用于训练变压器的实例相同的分布,并对每种解决方案方法进行应用。我测试了 3 种情况,不同的候选 DC 站点和客户数量,分别是(n_dc=100, n_cus=200)、(n_dc=200, n_cus=400)和(n_dc=400, n_cus=800)。代码如下。

import time
from bisect import bisect

def resolve_infeasibility(m,f,d,y):
    idx = np.arange(len(y))
    idx = idx[y==0]
    m = m[idx]
    f = f[idx]
    r = f/m
    r_idx = np.argsort(r)
    m_sort = m[r_idx]
    idx = idx[r_idx]
    m_sort_sum = np.cumsum(m_sort)
    up_to_idx = bisect(m_sort_sum,d)
    idx = idx[:up_to_idx+1]
    for i in idx:
        y[i] = 1
    return y

transformer_model = TransformerModel()
transformer_model.load_state_dict(torch.load(desired_path))

obj_transformer = []
gap_transformer = []
time_transformer = []
obj_original = []
gap_original = []
time_original = []

rnd = np.random

for k in range(800,900):
    np.random.seed(k)
    n_dc = 100
    n_cus = 200
    c = []
    loc_x_dc = rnd.rand(n_dc)*100
    loc_y_dc = rnd.rand(n_dc)*100
    loc_x_cus = rnd.rand(n_cus)*100
    loc_y_cus = rnd.rand(n_cus)*100
    xy_dc = [[x,y] for x,y in zip(loc_x_dc,loc_y_dc)]
    xy_cus = [[x,y] for x,y in zip(loc_x_cus,loc_y_cus)]
    for i in xy_dc:
        c_i = []
        for j in xy_cus:
            c_i.append(np.sqrt((i[0]-j[0])**2 + (i[1]-j[1])**2)*0.01)
        c.append(c_i)    

    f= []
    m = []
    d = []

    for i in range(n_dc):
        f_rand = np.round(np.random.normal(100,15))
        if f_rand < 40:
            f.append(40)
        else:
            f.append(f_rand)
        m_rand = np.round(np.random.normal(70,10))
        if m_rand < 30:
            m.append(30)
        else:
            m.append(m_rand)
    for i in range(n_cus):
        d_rand = np.round(np.random.normal(20,5))
        if d_rand < 5:
            d.append(5)
        else:
            d.append(d_rand)

    f = np.array(f)
    m = np.array(m)
    d = np.array(d)
    c = np.array(c)

    start_time = time.time()
    model = Model("facility selection")
    x,y = {},{}
    x_names = ['x_'+str(i)+'_'+str(j) for i in range(n_dc) for j in range(n_cus)]
    y_names = ['y_'+str(i) for i in range(n_dc)]
    for i in range(n_dc):
        for j in range(n_cus):
            x[i,j] = model.addVar(name=x_names[i*n_cus+j])
    for i in range(n_dc):
        y[i] = model.addVar(name=y_names[i],vtype='BINARY')
    model.setObjective(quicksum(f[i]*y[i] for i in range(n_dc))+quicksum(c[i,j]*x[i,j] for i in range(n_dc) for j in range(n_cus)), 'minimize')
    for j in range(n_cus):
        model.addCons(quicksum(x[i,j] for i in range(n_dc)) == d[j])
    for i in range(n_dc):
        model.addCons(quicksum(x[i,j] for j in range(n_cus)) <= m[i]*y[i])
    model.optimize()
    time_original.append(time.time()-start_time)
    obj_original.append(model.getObjVal())
    gap_original.append(model.getGap())

    start_time = time.time()
    xy_site = []
    new_xy_dc = [convert_coord(i) for i in xy_dc]
    new_xy_cus = [convert_coord(i) for i in xy_cus]
    xy_site.extend(new_xy_dc)
    xy_site.extend(new_xy_cus)
    d_site = []
    d_site.extend([[i] for i in m])
    d_site.extend([[i] for i in d])
    f_site = []
    f_site.extend([[i] for i in f])
    f_site.extend([[0] for i in range(200)])
    i_site = []
    i_site.extend([[0] for i in range(100)])
    i_site.extend([[1] for i in range(200)])
    x = np.concatenate((xy_site, d_site), axis=1)
    x = np.concatenate((x, f_site), axis=1)
    x = np.concatenate((x, i_site), axis=1)
    x = scaler_x.transform(x)
    x = np.expand_dims(x,axis=1)
    x = torch.from_numpy(x).float()

    transformer_model.eval()
    y_pred = transformer_model(x)
    y_pred_softmax = torch.log_softmax(y_pred.view(-1,2)[mask], dim = 1)
    _, y_pred_tags = torch.max(y_pred_softmax, dim = 1)
    if np.sum(m*y_pred_tags.numpy()) < np.sum(d):
        y_pred_corrected = resolve_infeasibility(m,f,np.sum(d)-np.sum(m*y_pred_tags.numpy()),y_pred_tags.numpy())
    else:
        y_pred_corrected = y_pred_tags.numpy()

    model = Model("facility selection with transformer")
    x,y = {},{}
    x_names = ['x_'+str(i)+'_'+str(j) for i in range(n_dc) for j in range(n_cus)]
    y_names = ['y_'+str(i) for i in range(n_dc)]
    for i in range(n_dc):
        for j in range(n_cus):
            x[i,j] = model.addVar(name=x_names[i*n_cus+j])

    for i in range(n_dc):
        if y_pred_corrected[i] == 1:
            y[i] = model.addVar(name=y_names[i],vtype='BINARY',lb=1)
        else:
            y[i] = model.addVar(name=y_names[i],vtype='BINARY',ub=0)
    model.setObjective(quicksum(f[i]*y[i] for i in range(n_dc))+quicksum(c[i,j]*x[i,j] for i in range(n_dc) for j in range(n_cus)), 'minimize')
    for j in range(n_cus):
        model.addCons(quicksum(x[i,j] for i in range(n_dc)) == d[j])
    for i in range(n_dc):
        model.addCons(quicksum(x[i,j] for j in range(n_cus)) <= m[i]*y[i])
    model.optimize()
    time_transformer.append(time.time()-start_time)
    obj_transformer.append(model.getObjVal())
    gap_transformer.append(model.getGap())

每种解决方案方法在每种情况下的平均求解时间(单位:秒,在配备 Intel® Core™ i7–10750H CPU,6 核心的笔记本电脑上)和目标值报告在下表中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从参数遵循与变压器训练集相同分布的实例中获得的测试结果(作者提供的图片)。

我们可以看到,在所有情况下,变压器辅助解决方案方法消耗的时间约为仅使用 SCIP 解决方案方法的 2%,而目标值恶化仅约 1%。

到目前为止,我们仅在参数遵循与训练集相同分布的实例上测试了变压器辅助的解决方案方法。如果我们将其应用于参数遵循不同分布的实例会怎么样?为了测试这一点,我生成了 100 个测试实例,参数为m_i, f_i, d_j,这些参数遵循双倍均值和双倍标准差的正态分布,使用以下代码。

 f = []
m = []
d = []

for i in range(n_dc):
    f_rand = np.round(np.random.normal(200,30))
    if f_rand < 80:
        f.append(80)
    else:
        f.append(f_rand)
    m_rand = np.round(np.random.normal(140,20))
    if m_rand < 60:
        m.append(60)
    else:
        m.append(m_rand)
for i in range(n_cus):
    d_rand = np.round(np.random.normal(40,10))
    if d_rand < 10:
        d.append(10)
    else:
        d.append(d_rand)

从这些实例中获得的结果报告在下表中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从参数遵循双倍均值和标准差的分布的实例中获得的测试结果,与变压器的训练集相比(作者提供的图片)。

我们看到,变压器辅助的解决方案方法消耗的时间约为仅使用 SCIP 解决方案方法的 2%,而目标值恶化约 3%,比之前的测试稍高,但仍在接受范围内。

我们生成了另一组 100 个测试实例,参数为m_i, f_i, d_j,这些参数遵循半均值和标准差的正态分布,使用以下代码。

f = []
m = []
d = []

for i in range(n_dc):
    f_rand = np.round(np.random.normal(50,7.5))
    if f_rand < 20:
        f.append(20)
    else:
        f.append(f_rand)
    m_rand = np.round(np.random.normal(35,5))
    if m_rand < 15:
        m.append(15)
    else:
        m.append(m_rand)
for i in range(n_cus):
    d_rand = np.round(np.random.normal(10,2.5))
    if d_rand < 3:
        d.append(3)
    else:
        d.append(d_rand)

结果报告在下表中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从参数分布的实例中获得的测试结果,其分布的均值和标准差为训练集的一半(图像来源:作者)。

我们看到,变压器辅助的解决方法仍然大大节省了解决时间,然而,解质量也有显著下降。因此,变压器确实有些过拟合于训练集中实例的参数分布。

结论

在这篇文章中,我训练了一个变压器模型,以帮助解决供应链领域的经典 MILP——设施选址问题。通过学习 SCIP 提供的数百个实例的解决方案,该变压器能够预测整数变量应取的正确值。然后,我们将 SCIP 求解器中的整数变量固定为变压器模型提供的预测值,并解决其余变量。数值实验表明,当应用于参数遵循相同或类似分布的实例时,变压器辅助的解决方法显著减少了解决时间,同时仅有轻微的解质量下降。

将机器学习(ML)应用于加速 MILP 求解是 ML 和 OR 社区中的新兴研究主题,如我的第一篇文章中提到的。现有的研究思路包括监督学习(从求解器中学习)和强化学习(通过探索解空间和观察奖励来学习解决 MILP 本身)等。本文的核心思想属于前者(监督学习),并利用变压器模型中的注意力机制来学习为整数变量做决策。由于变压器模型从求解器中学习,变压器辅助的解决方法的解质量永远无法超越求解器,但我们获得了在求解时间上的巨大节省。

使用预训练的机器学习模型生成 MILP 的部分解法可能是加快大实例商业应用求解过程的一个有前景的想法。在这里,我选择了设施选址问题作为 MILP 的示例进行实验,但相同的想法适用于其他问题,如分配问题。此方法最适合用于我们希望重复解决具有相同或类似分布的参数的大型 MILP 实例的情况,因为预训练的机器学习模型往往会过拟合于训练实例中的参数分布。提高这种方法鲁棒性的一种方式可能是包括更多具有更广泛分布的实例,以减少过拟合,这可以成为我未来文章中的探索方向。

感谢阅读!

Transformer 模型 101:入门 — 第一部分

原文:towardsdatascience.com/transformer-models-101-getting-started-part-1-b3a77ccfa14d?source=collection_archive---------1-----------------------#2023-02-18

用简单的语言解释 Transformer 模型背后的复杂数学

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Nandini Bansal

·

关注 发表在 Towards Data Science ·11 分钟阅读·2023 年 2 月 18 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:Kerttu 来自 Pixabay

变换器架构在自然语言处理(NLP)领域的突破并不是什么秘密。它克服了诸如 RNN 等序列到序列(seq-to-seq)模型在捕捉文本中的长期依赖性方面的限制。变换器架构成为了 BERT、GPT 和 T5 及其变体等革命性架构的基石。正如许多人所说,NLP 正处于一个黄金时代,毫不夸张地说,变换器模型正是这一切的起点。

变换器架构的必要性

正如所说的,需求是发明的母亲。传统的序列到序列模型在处理长文本时表现不佳。这意味着模型在处理输入序列的后半部分时,往往会忘记前半部分的学习内容。这种信息丧失是不可取的。

尽管像 LSTM 和 GRU 这样的门控架构通过 丢弃沿途无用的信息以记住重要信息,在处理长期依赖性方面表现出了一些改进,但仍然不够。世界需要更强大的东西,于是,在 2015 年,Bahdanau 等人引入了“注意力机制”。它们与 RNN/LSTM 结合使用,模仿人类行为,专注于选择性事物,同时忽略其余部分。Bahdanau 提议为句子中的每个单词分配相对重要性,以便模型关注重要单词并忽略其他部分。这被证明是对神经机器翻译任务中的编码器-解码器模型的巨大改进,很快,注意力机制的应用也扩展到了其他任务中。

变换器模型的时代

变换器模型完全基于注意力机制,也称为 “自注意力”。这一架构在 2017 年通过论文 “Attention is All You Need” 介绍给世界。它由编码器-解码器架构组成。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图. 高层次的变换器模型架构(来源:作者)

从高层次来看,

  • 编码器 负责接受输入句子,并将其转换为一个隐藏表示,其中所有无用信息都被丢弃。

  • 解码器 接受这一隐藏表示,并尝试生成目标句子。

在本文中,我们将详细探讨变换器模型的编码器组件。在下一篇文章中,我们将详细讨论解码器组件。让我们开始吧!

变换器的编码器

变换器的编码器块由 N 个编码器的堆叠组成,这些编码器顺序工作。一个编码器的输出是下一个编码器的输入,依此类推。最后一个编码器的输出是输入句子的最终表示,这一表示被传递给解码器块。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图:具有堆叠编码器的编码器块(来源:作者)

每个编码器块可以进一步分为下图所示的两个组件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图:编码器层的组件(来源:作者)

让我们详细查看这些组件,以理解编码器块是如何工作的。编码器块中的第一个组件是多头注意力,但在深入细节之前,我们先理解一个基本概念:自注意力

自注意力机制

每个人可能会产生的第一个问题是:注意力和自注意力是不同的概念吗? 是的,它们不同。(显而易见!)

传统上,注意力机制在前一节中讨论的神经机器翻译任务中产生。因此,本质上,注意力机制用于将源句和目标句进行映射。由于 seq-to-seq 模型逐词执行翻译任务,注意力机制帮助我们识别在为目标句生成令牌 x 时应该更多关注源句中的哪些令牌。为此,它利用来自编码器和解码器的隐藏状态表示来计算注意力分数,并基于这些分数生成上下文向量作为解码器的输入。如果你想了解更多关于注意力机制的内容,请查看这篇文章(解释得非常精彩!)。

回到自注意力,主要思想是在将源句映射到自身时计算注意力分数。如果你有一句话,如,

“那个男孩没有过马路,因为 太宽了。”

对我们人类来说,理解句子中的“it”指的是“road”很简单,但我们如何让语言模型也理解这种关系呢?这就是自注意力的作用!

从高层次来看,句子中的每个单词都会与句子中的其他单词进行比较,以量化关系并理解上下文。为了表示目的,你可以参考下图。

让我们详细了解一下自注意力是如何计算的(实际上)。

  • 为输入句子生成嵌入

查找所有单词的嵌入并将其转换为输入矩阵。这些嵌入可以通过简单的标记化和独热编码生成,也可以通过像 BERT 等嵌入算法生成。输入矩阵的维度将等于句子长度 x 嵌入维度。我们将其称为输入矩阵 X以便于将来参考。

  • 将输入矩阵转换为 Q、K 和 V

为了计算自注意力,我们需要将 X(输入矩阵)转换为三个新矩阵:

  • 查询 (Q)

  • 关键 (K)

  • 值 (V)

为了计算这三个矩阵,我们将随机初始化三个权重矩阵,即 Wq、Wk 和 Wv。输入矩阵 X 将与这些权重矩阵 Wq、Wk 和 Wv 相乘,以分别获得 Q、K 和 V 的值。权重矩阵的最优值将在过程中学习,以获得更准确的 Q、K 和 V 值。

  • 计算 Q 和 K 转置的点积

从上图中,我们可以推断 qi、ki 和 vi 代表句子中第 i 个词的 Q、K 和 V 的值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图例:Q 和 K 转置的点积示例(来源:作者)

输出矩阵的第一行会告诉你词汇 word1 由 q1 表示的与句子中其余词汇的关系,使用点积来计算。点积值越高,词汇之间的相关性越大。为了理解为何计算这个点积,你可以从信息检索的角度理解 Q(查询)和 K(键)矩阵。因此,

  • Q 或 Query = 你正在搜索的术语

  • K 或 Key = 搜索引擎中的一组关键词,与 Q 进行比较和匹配。

  • 缩放点积

与之前的步骤一样,我们正在计算两个矩阵的点积,即执行乘法操作,这可能导致值的爆炸。为了确保不会发生这种情况并稳定梯度,我们将 Q 和 K 转置的点积除以嵌入维度的平方根(dk)。

  • 使用 softmax 归一化值

使用 softmax 函数的归一化将导致值在 0 和 1 之间。具有高点积的单元将被进一步提升,而低值将被减少,使匹配词对之间的区别更清晰。结果输出矩阵可以视为 得分矩阵 S

  • 计算注意力矩阵 Z

值矩阵或 V 会与上一步骤中获得的得分矩阵 S 相乘,以计算注意力矩阵 Z。

但等等,为什么要相乘?

假设 Si = [0.9, 0.07, 0.03] 是句子中第 i 个词的得分矩阵值。这个向量会与 V 矩阵相乘,以计算 Zi(第 i 个词的注意力矩阵)。

Zi = [0.9 * V1 + 0.07 * V2 + 0.03 * V3]

我们可以说,为了理解第 i 个词的上下文,我们应该只关注 word1(即 V1),因为注意力分数的 90% 来自 V1 吗?我们可以明确地定义出应该更多关注的重要词汇,以理解第 i 个词的上下文。

因此,我们可以得出结论:词汇在 Zi 表示中的贡献越高,词汇之间的相关性和重要性就越大。

现在我们知道如何计算自注意力矩阵,让我们深入了解 多头注意力机制 的概念。

多头注意力机制

如果你的分数矩阵偏向于特定的词表示会发生什么?这会误导你的模型,结果可能不如预期。让我们看一个例子来更好地理解这一点。

S1: “一切都好

Z(well) = 0.6 * V(all) + 0.0 * v(is) + 0.4 * V(well)

S2: “狗吃了食物,因为它饿了

Z(it) = 0.0 * V(the) + 1.0 * V(dog) + 0.0 * V(ate) + …… + 0.0 * V(hungry)

在 S1 的情况下,计算 Z(well) 时,更重要的是 V(all)。这甚至比 V(well) 自身还要多。没有保证这个结果的准确性。

在 S2 的情况下,计算 Z(it) 时,所有的重点都放在 V(dog) 上,而其他单词的分数,包括 V(it) 本身,都为 0.0。这看起来是可以接受的,因为“it”这个词是模糊的。将其与其他词相关联比与词本身相关联更合理。这就是计算自注意力的整个目的,即处理输入句子中模糊词的上下文。

换句话说,我们可以说,如果当前词是模糊的,那么在计算自注意力时给其他单词更多的关注是可以的,但在其他情况下,这可能会误导模型。那么,我们现在该怎么办?

如果我们计算多个注意力矩阵,而不是计算一个注意力矩阵并从中推导最终的注意力矩阵,会怎么样?

这正是 多头注意力 的意义所在!我们计算多个版本的注意力矩阵 z1, z2, z3, ……, zm 并将它们连接起来以推导最终的注意力矩阵。这样我们可以对我们的注意力矩阵更有信心。

接下来要讨论的另一个重要概念是,

位置编码

在 seq-to-seq 模型中,输入句子逐词输入网络,这使得模型能够跟踪单词相对于其他单词的位置。

但在变压器模型中,我们采取不同的方法。不是逐词输入,而是并行输入,这有助于减少训练时间并学习长期依赖。然而,这种方法会丧失词序。为了正确理解句子的意义,词序是极其重要的。为了解决这个问题,引入了一个新的矩阵称为“位置编码”(P)。

这个矩阵 P 与输入矩阵 X 一起发送,以包含与词序相关的信息。出于明显的原因,X 和 P 矩阵的维度是相同的。

计算位置编码时,使用下面给出的公式。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图。计算位置编码的公式(来源:作者)

在上述公式中,

  • pos = 单词在句子中的位置

  • d = 单词/标记嵌入的维度

  • i = 表示嵌入中的每一个维度

在计算中,d 是固定的,但 pos 和 i 变化。如果 d=512,那么 i ∈ [0, 255],因为我们取 2^i。

如果你想深入了解位置编码,这个视频涵盖了详细内容。

变换器神经网络视觉指南 — (第一部分)位置嵌入

我正在使用上述视频中的一些视觉内容来用我的话解释这一概念。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图。位置编码向量表示(来源:作者)

上图展示了一个位置编码向量及其不同的变量值示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图。位置编码向量,i 和 d 恒定(来源:作者)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图。位置编码向量,i 和 d 恒定(来源:作者)

上图展示了如何在 i 恒定而只有 pos 变化的情况下,PE(pos, 2i) 的值会变化。我们知道正弦波是一个周期性函数,倾向于在固定的间隔后重复自己。我们可以看到 pos = 0 和 pos = 6 的编码向量是相同的。这并不可取,因为我们希望对不同的 pos 值有不同的位置编码向量

这可以通过改变正弦波的频率来实现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图。位置编码向量,pos 和 i 变化(来源:作者)

随着 i 的变化,正弦波的频率也会变化,导致不同的波形,因此,每个位置编码向量的值也不同。这正是我们想要实现的。

位置编码矩阵(P)被添加到输入矩阵(X)中,并送入编码器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图。将位置编码添加到输入嵌入中(来源:作者)

编码器的下一个组件是前馈网络

前馈网络

编码器块中的这一子层是经典的神经网络,包含两个全连接层和 ReLU 激活。它接受来自多头注意力层的输入,对其进行一些非线性变换,最终生成上下文化的向量。全连接层负责考虑每个注意力头,并从中学习相关信息。由于注意力向量彼此独立,它们可以以并行方式传递给变换器网络。

编码器块的最后一个组件是加法和规范化组件

加法和规范化组件

这是一个残差层,后跟层归一化。残差层确保在处理过程中不会丢失与子层输入相关的重要信息,而归一化层则促进模型训练的速度,并防止值的大幅变化。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图。包含加法和规范化层的编码器组件(来源:作者)

在编码器内部,有两个加法和规范化层:

  • 连接多头注意力子层的输入和输出

  • 将前馈网络子层的输入连接到其输出。

到此为止,我们总结了编码器的内部工作原理。为了总结文章,让我们快速回顾编码器使用的步骤:

  • 生成输入句子的嵌入或标记化表示。这将是我们的输入矩阵 X。

  • 生成位置嵌入,以保留与输入句子单词顺序相关的信息,并将其添加到输入矩阵 X 中。

  • 随机初始化三个矩阵:Wq、Wk 和 Wv,即查询、键和值的权重。这些权重将在 Transformer 模型的训练过程中更新。

  • 将输入矩阵 X 与每个 Wq、Wk 和 Wv 相乘,生成 Q(查询)、K(键)和 V(值)矩阵。

  • 计算 Q 和 K 转置的点积,将该乘积除以 dk 或嵌入维度的平方根进行缩放,最后使用 softmax 函数进行归一化。

  • 通过将 V 或值矩阵与 softmax 函数的输出相乘来计算注意力矩阵 Z。

  • 将此注意力矩阵传递给前馈网络,以执行非线性变换并生成上下文化的嵌入。

在下一篇文章中,我们将了解 Transformer 模型的解码器组件是如何工作的。

这篇文章就到这里。我希望你觉得它有用。如果有的话,请不要忘记点赞并与朋友分享。

Transformer 模型 101:入门指南 — 第二部分

原文:towardsdatascience.com/transformer-models-101-getting-started-part-2-4c9a45bf8b81?source=collection_archive---------13-----------------------#2023-06-06

Transformer 模型背后的复杂数学,用简单的词汇解释

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Nandini Bansal

·

关注 发表于 Towards Data Science · 7 分钟阅读 · 2023 年 6 月 6 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由Dariusz Sankowski提供,来源于Pixabay

在上一篇文章中,我们详细介绍了 Transformer 模型的编码器(Encoder)块的工作原理。如果你还没有阅读那篇文章,我建议你在阅读本文之前先阅读它,因为那里的概念在本文中有所延续。你可以前往:

## 变换器模型 101:入门 — 第一部分

用简单的语言解释变换器模型背后的复杂数学…

towardsdatascience.com

如果你已经阅读了这些内容,太棒了!让我们开始深入了解解码器块及其相关的复杂数学。

变换器的解码器

像变换器模型的编码器块一样,解码器块由 N 个堆叠的解码器组成,这些解码器按顺序工作,并接受来自前一个解码器的输入。然而,这并不是解码器唯一接受的输入。由编码器块生成的句子表示会被传递给解码器块中的每一个解码器。因此,我们可以得出结论,每个解码器接受两个不同的输入:

  • 来自编码器块的句子表示

  • 之前解码器的输出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1. 编码器和解码器块的协同工作(图片由作者提供)

在我们进一步探讨解码器的不同组件之前,了解解码器一般是如何生成输出句子或目标句子的直观感受是很重要的。

目标句子是如何生成的?

在时间步 t=1 时,只有 标记或 句子开始 被传递给解码器块作为输入。基于 ,解码器块生成目标句子的第一个单词。

在下一个时间戳,即 t=2,解码器块的输入包括 标记以及解码器块生成的第一个单词。下一个单词是基于这个输入生成的。

类似地,每次时间戳增加时,解码器块的输入长度也会增加,因为在前一个时间戳生成的单词会被添加到当前输入句子中。

当解码器块完成整个目标句子的生成时,会生成 或 句子结束 标记。

你可以将其视为一个递归过程!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2 递归生成输出标记使用解码器(图片由作者提供)

现在,这就是当输入给定到变换器模型时我们期望的输出情况。然而,在训练/微调变换器模型时,我们已经在训练数据集中拥有目标句子。那么它是如何工作的呢?

这使我们接触到解码器的一个极其重要的概念:掩蔽多头注意力。听起来很熟悉?当然了。在之前的部分,我们理解了在编码器块中使用的 多头注意力 的概念。现在让我们了解这两者的不同之处。

掩蔽多头注意力

解码器块逐字生成目标句子,因此,模型必须以类似的方式进行训练,以便即使在有限的标记集下也能做出准确预测。

因此,正如名字所示,我们在计算自注意力矩阵之前会掩蔽句子右侧尚未预测的所有标记。这将确保自注意力机制仅考虑模型在每次递归预测步骤中将可用的标记。

让我们举一个简单的例子来理解它:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3. 掩蔽多头注意力矩阵表示(图像来源:作者)

计算自注意力矩阵的步骤和公式与我们在编码器块中做的相同。我们将在本文中高层次地覆盖这些步骤。欲了解更深入的内容,请随时前往本文系列的上一部分。

  • 为目标句子生成嵌入并获得 目标矩阵 Y

  • 通过将随机权重矩阵 Wq、Wk 和 Wv 与目标矩阵 Y 相乘,将目标句子转换为 Q、K 和 V

  • 计算 Q 和 K 的转置 的点积

  • 通过将点积除以 平方根 的嵌入维度 (dk) 来缩放

  • 通过将所有 单元替换为 — inf 来对缩放矩阵应用掩蔽

  • 现在 对矩阵应用 softmax 函数并将其与 Vi 矩阵 相乘以生成注意力矩阵 Zi

  • 将多个注意力矩阵 Zi 连接成一个单一的 注意力矩阵 M

这个注意力矩阵将与编码器块生成的输入句子表示一起传递到解码器块的下一个组件。现在让我们理解这两个矩阵是如何被解码器块使用的。

多头注意力

解码器块的这个子层也被称为***“编码器-解码器注意力层”,因为它接收掩蔽注意力矩阵 (M)由编码器生成的句子表示 ®*。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4. 多头注意力机制(图像来源:作者)

自注意力矩阵的计算与前一步骤中的计算非常相似,只是有一点小变化。由于这个层有两个输入矩阵,它们被转化为 Q、K 和 V,如下所示:

  • Q 是使用 Wq 和 M 生成的

  • K 和 V 矩阵是使用 Wk 和 Wv 与 R 生成的

到现在为止,你一定已经明白了变换器模型中的每一步和计算都有非常具体的理由。类似地,每个矩阵使用不同的输入矩阵生成也是有原因的。你能猜到吗?

快速提示:答案在于自注意力矩阵是如何计算的…

是的,你答对了!

如果你还记得,当我们通过一个输入句子理解自注意力概念时,我们谈到了它如何计算注意力分数,同时将源句子映射到自身。源句子中的每个词都与同一句子中的每个其他词进行比较,以量化关系并理解上下文。

在这里,我们也做着相同的事情,唯一的区别是,我们将输入句子(K-转置)的每个词与目标句子词(Q)进行比较。这将帮助我们量化这两个句子彼此的相似程度,并理解单词之间的关系。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5. 输入句子和目标句子的注意力矩阵表示(作者提供的图像)

最终,生成的注意力矩阵 Zi 的维度将是 N X 1,其中 N = 目标句子的词数。

由于这也是一个多头注意力层,为了生成最终的注意力矩阵,多个注意力矩阵被拼接在一起。

有了这些,我们已经涵盖了 Decoder 块的所有独特组件。然而,某些其他组件的功能与 Encoder 块中的相同。我们也简要看看它们:

  • 位置编码 — 就像编码器块一样,为了保留目标句子的词序,我们在将其输入到 Masked Multi-attention 层之前,将位置编码添加到目标嵌入中。

  • 前馈网络 — 这个解码器块中的子层是经典的神经网络,具有两个全连接层和 ReLU 激活。它接受来自多头注意力层的输入,对其进行一些非线性变换,最后生成上下文化的向量。

  • Add & Norm 组件 — 这是一个残差层,后面跟着层归一化。它有助于加快模型训练,同时确保不会丢失来自子层的信息。

我们在第一部分中详细介绍了这些概念。

有了这些,我们也总结了 Decoder 块的内部工作原理。正如你可能猜到的,编码器和解码器块用于处理和生成输入句子的上下文化向量。那么实际的下一个词预测任务由谁来完成呢?让我们找出答案。

线性与 Softmax 层

它位于 Decoder 网络的顶部,接受由堆栈中最后一个解码器生成的输出矩阵作为输入。这个输出矩阵被转换为与词汇表大小相同的 logit 向量。然后我们对这个 logit 向量应用 softmax 函数,以生成与每个词对应的概率。具有最高概率的词被预测为下一个词。该模型使用Adam 优化器进行交叉熵损失优化。

为了避免过拟合,dropout 层在每个编码器/解码器网络的子层后面添加。

这就是整个 Transformer 模型的全部内容。通过这个,我们已经用尽可能简单的语言完成了对 Transformer 模型架构的深入讲解。

结论

现在你已经了解了所有关于 Transformer 模型的内容,基于此构建你的知识并深入研究更复杂的 LLM 模型架构,如 BERT、GPT 等,不应该会很困难。

你可以参考以下资源:

  1. huggingface.co/blog/bert-101

  2. jalammar.github.io/illustrated-gpt2/

希望这篇两部分的文章能让 Transformer 模型的理解变得稍微不那么令人生畏。如果你觉得这篇文章有用,请传播这个好消息。

下次见!

通过微调的变换器模型进行自定义文本分类

原文:towardsdatascience.com/transformer-models-for-custom-text-classification-through-fine-tuning-3b065cc08da1

一个关于如何通过微调 DistilBERT 模型来构建垃圾邮件分类器(或任何其他分类器)的教程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Skanda Vivek

·发布于 Towards Data Science ·阅读时长 4 分钟·2023 年 1 月 20 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

微调的 SMS 垃圾邮件分类器模型 输出 | Skanda Vivek

DistilBERT 模型由 Hugging Face 团队发布,是一个较便宜、较快的替代大型变换器模型如 BERT 的选项。它 最初在一篇博客文章中介绍。该模型的工作方式是通过使用教师-学生训练方法,其中“学生”模型是教师模型的较小版本。然后,不是将学生模型训练在最终目标输出(基本上是标签类别的独热编码)上,而是将模型训练在原始“教师模型”的 softmax 输出上。这是一个极其简单的想法,作者们展示了:

“可以在保留 97%语言理解能力的同时,将 BERT 模型的大小减少 40%,并且速度提升 60%。”

数据加载和分类预处理

在这个例子中,我使用了 UCI 机器学习库中的 SMS 垃圾邮件收集数据集,构建了一个能够检测 SPAM 与 HAM(非 SPAM)的分类器。数据包含 5574 行标记为 SPAM 或 HAM 的短信。

首先,我从原始 csv 文件中制作训练和验证文件,并使用 Hugging Face 数据集库中的load_dataset函数。

from datasets import load_dataset
import pandas as pd

df=pd.read_csv(/content/spam.csv’, encoding = “ISO-88591)
df=df[['v1','v2']]
df.columns=['label','text']
df.loc[df['label']=='ham','label']=0
df.loc[df['label']=='spam','label']=1
df2[:4179].reset_index(drop=True).to_csv('df_train.csv',index=False)
df2[4179:].reset_index(drop=True).to_csv('df_test.csv',index=False)

dataset = load_dataset('csv', data_files={'train': '/content/df_train.csv',
                                              'test': '/content/df_test.csv'})

下一步是加载 DistilBERT 分词器以预处理文本数据。

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(“distilbert-base-uncased”)

def preprocess_function(examples):
    return tokenizer(examples["text"], truncation=True,padding=True)

tokenized_data = dataset.map(preprocess_function, batched=True)

from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

训练模型

在训练之前,你需要将 ID 映射到标签。之后,你需要指定训练超参数,调用 trainer.train()开始微调,并使用 trainer.push_to_hub()将训练好的模型推送到 Hugging Face 中心。

id2label = {0: “HAM”, 1: “SPAM”}
label2id = {“HAM”: 0, “SPAM”: 1}

from transformers import AutoModelForSequenceClassification, TrainingArguments, Trainer

model = AutoModelForSequenceClassification.from_pretrained(
    "distilbert-base-uncased", num_labels=2, id2label=id2label, label2id=label2id

training_args = TrainingArguments(
    output_dir="spam-classifier",
    learning_rate=2e-5,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=16,
    num_train_epochs=5,
    weight_decay=0.01,
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    push_to_hub=True,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_data["train"],
    eval_dataset=tokenized_data["test"],
    tokenizer=tokenizer,
    data_collator=data_collator,
    compute_metrics=compute_metrics,
)

trainer.train()

trainer.push_to_hub()

就是这样!正如你从 Hugging Face 中心看到的,模型的准确度相当不错(0.9885)!

[## skandavivek2/spam-classifier · Hugging Face

编辑模型卡 该模型是对 distilbert-base-uncased 在未知数据集上进行微调的版本。

huggingface.co](https://huggingface.co/skandavivek2/spam-classifier?source=post_page-----3b065cc08da1--------------------------------)

模型推理

推理也相对简单。你可以通过运行以下 Python 脚本查看输出:

text = “Email AlertFrom: Ash Kopatz. Click here to get a free prescription refill!”

from transformers import pipeline

classifier = pipeline("sentiment-analysis", model="skandavivek2/spam-classifier")
classifier(text)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

样本微调的 SMS 垃圾邮件分类器模型输出 | Skanda Vivek

或在 Hugging Face 中心运行:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

主要内容

就是这样!Hugging Face 使得适应最先进的变换器模型到定制语言任务非常简单易用,只要你有数据!

这是代码的 GitHub 链接:

[## GitHub - skandavivek/fine-tune-transformer-classifier

github.com](https://github.com/skandavivek/fine-tune-transformer-classifier?source=post_page-----3b065cc08da1--------------------------------)

如果你喜欢这篇博客,可以查看我关于针对问题回答微调变换器模型的其他博客!

参考文献:

  1. https://www.kaggle.com/datasets/uciml/sms-spam-collection-dataset

  2. Dua, D. 和 Graff, C. (2019). UCI 机器学习库 [http://archive.ics.uci.edu/ml]。加州欧文:加州大学信息与计算机科学学院。

  3. Almeida, T.A., Gómez Hidalgo, J.M., Yamakami, A. 对 SMS 垃圾邮件过滤的研究贡献:新数据集和结果。2011 年 ACM 文档工程研讨会(DOCENG’11)论文集,山景城,加利福尼亚,美国,2011 年。

  4. https://huggingface.co/docs/transformers/training

如果你还不是 Medium 的会员,想要支持像我这样的作者,可以通过我的推荐链接注册: https://skanda-vivek.medium.com/membership

每周的数据视角 在这里订阅

Transformers 可以生成 NFL 比赛:介绍 QB-GPT

原文:towardsdatascience.com/transformers-can-generate-nfl-plays-introducing-qb-gpt-2d40f16a03eb?source=collection_archive---------14-----------------------#2023-11-07

弥合 GenAI 与体育分析之间的差距

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Samuel Chaineau

·

关注 发表在 Towards Data Science · 10 分钟阅读 · 2023 年 11 月 7 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Zetong Li 摄影,发表于 Unsplash

自从我撰写了关于 StratFormer的第一篇文章后,我收到了相对较多的反馈和想法(所以首先谢谢大家!)。这促使我深入研究,并尝试进一步的步骤: 构建一个足球战术生成器*。在这篇文章中,我介绍了* QB-GPT*,一个能够在提供一些元素后有效生成足球战术的模型。可以在* 这里 找到一个专门的 HuggingFace 空间以进行尝试。我将在本月晚些时候分享我关于如何使用这种生成模型作为基础来更好地预测 NFL 战术的工作和发现。行业也在关注这一领域,因为 DeepMind 安全研究 目前正在与利物浦合作研究足球,了解球员在场上的移动方式。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

QB-GPT 生成的轨迹

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

真实轨迹

StratFormer,这是我在 2021 年 10 月开始的第一个想法,是一个仅编码器模型,它以轨迹作为输入,尝试完成它并预测与之相关的一些上下文元素(如团队、位置和战术)。虽然这个模型显示出有趣的模式(例如理解真正使 RB 和 WR 区别开来的因素),但它依赖于对战术的“后验”观察。更有趣的可能是深入理解玩家的布置以及一些上下文元素如何实际影响团队的路线。换句话说,当两队在进攻线对峙时,会发生什么?

通过构建这样的算法,我们现在能够建立一个“真正”理解足球比赛的模型,因为它基本上试图从少量元素中重现战术。这就是QB-GPT的目标。GPT 在这里的作用是因为它依赖于任何 GPT 模型使用的解码概念。

这篇文章将介绍模型和我为使其“正常”而必须实现的一些必要技巧。一个附加的 HuggingFace 空间可以在 这里 生成一些战术,如果你愿意,我会根据成本限制开放一段时间。虽然我认识到它有局限性,并且容易改进,但我认为这样的应用值得与更广泛的受众分享。如果有兴趣讨论或深入了解一些方面,我的联系方式在应用程序和文章末尾。再次声明,我是独自完成了这项工作,使用了我能找到的数据及其相关的不完美之处(如果有人在 NFL/NGS 团队工作看到这篇文章,私信随时欢迎)

第一部分:数据

执行此任务所需的数据非常难以获取。我依赖于 NFL 提供的Next Gen Stats (NGS) 工具的特定数据类型,其中,对于任何给定的进攻,我可以跟踪最多 22 名在场上的球员。问题是,这些数据不能通过经典的 API 请求访问。然而,自 2019 年以来,NFL 在 Kaggle 上提供了各种数据竞赛,通常附带 NGS 的数据集。此外,一些人在 GitHub 上曾经抓取过 NGS 浏览器。我没有尝试这样做,只依赖于具有可用格式的数据。

合并数据之间花费了大量时间(大约 200 小时)。最重要的任务是:

  • 使用 nflVerse 查找场上的球员

  • 关联他们的位置

  • 使用 nflVerse 的逐场数据定义进攻线

  • 通过从轨迹的每个实例中减去球员的原始位置来规范化轨迹。因此,轨迹的每个元素是时间 i 和时间 0 之间的距离。

  • 将距离转换为索引(类似于为 BERT 构建词汇表)

  • 执行一些合理性检查(例如,移除未在场上的球员)

  • 将数据转换为适用于 TensorFlow 的数组和字典格式

我在整个过程中使用了Polars。我强烈推荐任何数据科学家、机器学习工程师、数据工程师或处理大量表格数据的人员将这个令人印象深刻的包快速添加到他们的工具包中。TLDR:Polars 在小数据集上优于 pandas,在大数据集上优于 pypsark

总的来说,我汇编了47,991 个不同的进攻,代表870,559 个不同球员的场上轨迹(平均每场监测 18 名球员,不幸的是从未有 OL…)。

我以 0.2 秒的帧率监控每个球员的位置,总共28,147,112 个场上位置。我将数据限制在前 10 秒内,因为轨迹在之后往往变得越来越混乱,因此从概率的角度进行建模较为困难。

我的数据集从 2018 年到 2022 年,涵盖了3,190 名独特球员。数据并不完美,但提供了一个良好的样本,可能足以评估变压器是否有帮助。

总结一下,我的数据来源是:

  • NFL 大数据碗 2021 link

  • NFL 大数据碗 2022 link

  • NFL 大数据碗 2023 link

  • 公开的 git 仓库 NGS Highlights link

下面是一个表示单个球员输入嵌入的示意图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后将十一名球员按比赛进行连接,然后按帧截断,以始终保持标记数等于 256。为了确保每名球员的帧数始终相同,我将每名球员的帧数限制为 21(最大 = 21*11 = 231)。因此,我不得不从比赛的某个特定时刻直接创建新的轨迹,因为我的大多数比赛有超过 21 帧。我创建了一个 12 帧的填充步骤,这意味着轨迹现在被分成子轨迹,每次偏移 12 帧。这个过程往往使得预测第 12、24、36 和 48 帧的任务变得更加困难,正如我们稍后将看到的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

元素可以讨论。例如,用 1 码为基础对场地进行划分的相关性,或为何使用 0.2 秒的帧率。我认为模型(即其训练数据)是一个起点,我想承认并非一切都是完美的。反馈和意见都是欢迎的,只要它们是相关的。

第二部分:模型

该模型完全受 OpenAI GPT 架构的启发。它依赖于一个嵌入层,将不同的上下文元素添加到输入标记中。然后,将这些嵌入传递到一个使用 3 个头的多头注意力的单个 Transformer 模块中。在大型模型中,应用了第二个 Transformer 模块。输出随后传递到一个具有“relu”激活的全连接层中。为了获得预测结果,我们对 logits 应用 soft-max 激活。

为了适应架构和训练,需要两个技巧:

  • 多时间步因果掩码:在经典 GPT 中,位置 i 处的嵌入只能关注从位置 0 到 i-1 的标记。在我们的案例中,由于我正在完全解码团队,我需要时间 i 处的标记能够关注时间 0 到 i-1 之间的所有标记。与其使用所谓的“下三角掩码”,你最终会得到一个多三角掩码。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意力掩码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

原始注意力得分和注意力掩码减去后的得分

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

不同 vmax 规模下的注意力得分

  • 预层归一化: 受到 Sik-Ho Tsang 研究的启发,我实现了其提出的 Transformer 模块,其中归一化在多头注意力和 FFN 之前完成。

该模型不需要大量的注意力层,因为要建模的基本模式相当简单。我使用相同的设置训练了 4 种不同模型的架构。结果显示,嵌入维度在我们能达到的准确度水平中发挥了至关重要的作用,而注意力模块的数量对准确度的提升并不显著:

  • Tiny: 嵌入维度为 64,表示1,539,452 参数

  • 小型:嵌入维度为 128,代表3,182,716 个参数

  • 中等:嵌入维度为 256,代表6,813,308 个参数

  • 大型:嵌入维度为 128,但具有两个注意力模块,代表7,666,556 个参数

我认为大模型经过深入审查调度器后可能会获得更好的性能。

(用于比较,GPT3 有 1750 亿个参数)

以下图表展示了不同模型在训练过程中的准确率和损失的比较:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

四个模型的损失和准确率

第三部分:训练

训练集由 80% 的轨迹组成,共有205,851 个样本。测试集由 20% 的轨迹组成,共有51,463 个样本。

模型使用简单的回调调度器在 9 个周期中训练,学习率从 1e-3 开始,最终为 5e-4,批量大小为 32。

损失是一个类别交叉熵,具有基于训练集(更常见的整数权重在损失中权重较小)的类别权重。设置为 -100 的标签不计入损失。

使用的度量指标包括:

  • 准确率:下一步团队动作是否与标记的动作完全相同?

  • 前 3 准确率:下一步标记的动作是否在前 3 个预测中?

  • 前 5 准确率:下一步标记的动作是否在前 5 个预测中?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

四个模型的前 3 和前 5 准确率

最终,对与每个动作相关的 x 和 y 坐标进行了 RMSE 检查。此检查使我们能够监控,除了预测不准确之外,它不会偏离真实情况太远(预测一个 1 码的平方轨迹可能很困难)。

第四部分:结果

总体而言,不同的模型仍然能够学习潜在的动态模式,并在我的数据集上表现得相对良好。我们可以看到,增加嵌入维度会提高模型的准确率。猜测一个 1 码平方的任务确实具有挑战性,因为足球场上的动作并没有那么小的范围。

我决定将这些预测分为 3 类:时间、比赛类型和位置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

准确率和 RMSE 随时间(帧)的变化

对模型来说,前 5 帧相对较容易预测,因为球员经常以相似的动作开始。帧 5 到 10 之间的准确率下降了 40-50%。这是动作趋于不同且有各自路径的时刻。在四个模型中,小模型在轨迹末端特别困难。中等模型即使在长期(超过 20 帧)中也表现得非常好。峰值与填充轨迹的开始相关,因为这些数据在没有过去轨迹知识的情况下输入。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

比赛类型的准确性和 RMSE

相对静态的比赛(毫不意外)更容易预测。跑动、传球、开球和踢球(毫不意外)是最难猜测的,因为球员的移动更多,且可能存在混乱的模式。模型之间没有显著差异。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

位置的准确性和 RMSE

位置是一个非常显著的因素,不同位置之间的准确性差异可以达到 10% 到 20%。总体而言,移动较多的位置也是最难预测的。小型和微型模型通常比其他模型的准确性要差。

第五部分:我通过玩这个模型所见的一切

该模型的理想温度似乎在 1.5 和 2.5 之间。我测试了 10、20 和 50 的选择。温度和选择值越高,模型往往会变得越来越疯狂,产生奇怪的模式(球员离场、改变方向、两个帧之间的巨大间隙)。

然而,它似乎能够生成有趣的模式,这些模式可以用于模拟和战术手册识别。

接下来是什么?

这个模型是原始的。它可以用于后续探索对手策略、定义新的策略,甚至用于提升球员侦察系统。

然而,核心思想是看看这个模型是否能有效地预测比赛结果,即码数增益。我将很快分享我关于 NFL 比赛预测框架的工作,该框架基于 QBGPT 的发现。通过利用这种模型的生成能力,你可以从中绘制出大量场景,从而更容易预测比赛 😉

与此同时,你可以在这个 hugging face 空间里玩 QB-GPT。如果你喜欢这篇文章、这些工作和发现,别忘了分享和点赞!

(除非另有说明,每张图片均由作者提供)

Samuel Chaineau : Linkedin

深入了解 Transformers – 第一部分。5 分钟介绍 Transformer 模型

原文:towardsdatascience.com/transformers-in-depth-part-1-introduction-to-transformer-models-in-5-minutes-ad25da6d3cca?source=collection_archive---------0-----------------------#2023-03-27

在 5 分钟内理解 Transformer 架构及其关键见解

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Gabriel Furnieles

·

关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 3 月 27 日

这是文章扩展版的第一部分,不久你将能在这里找到其后续内容。

作者注释。 在这一部分,我决定介绍理解 Transformer 模型所需的概念和知识,以便更好地理解接下来的章节。如果你已经熟悉 Transformers,可以查看最后一节以获取本文的总结,并随意跳到第二部分,其中介绍了更多数学和复杂的概念。尽管如此,我希望你也能从这部分的解释中获得一些价值。感谢阅读!

自从最新的大型语言模型(LLaM)发布以来,例如 OpenAI 的 GPT 系列,Bloom 开源模型或 Google 关于 LaMDA 的公告等,Transformers 展示了其巨大潜力,成为深度学习模型的前沿架构。

尽管已有几篇关于 Transformer 及其背后数学的文章 [2] [3] [4],在这一系列文章中,我希望提供一个完整的概述,结合我认为最好的方法、我个人的观点以及与 Transformer 模型工作的经验。

本文试图提供 Transformer 模型的深度数学概述,展示其强大的来源并解释其每个模块背后的原因。

注。 文章遵循了原始 Transformer 模型,参考了论文 Vaswani, Ashish, et al. 2017.

环境设置。自然语言处理(NLP)简要介绍

在开始使用 Transformer 模型之前,有必要了解它们创建的任务,即处理文本。

由于神经网络处理的是数字,为了将文本输入神经网络,我们必须首先将其转换为数字表示。将文本(或其他对象)转换为数字形式的过程称为 嵌入。理想情况下,嵌入表示能够再现文本的特征,例如单词之间的关系或文本的情感。

嵌入有多种实现方式,本文并不打算解释它们(更多信息可以在 NLP 深度学习中找到),而是要理解它们的一般机制和产生的输出。如果你不熟悉嵌入,只需将其视为模型架构中的另一层,将文本转换为数字。

最常用的嵌入方法作用于文本中的单词,将每个单词转换为一个高维向量(文本中分割的元素称为标记)。在原始论文 [1] 中,每个标记/单词的嵌入维度512。值得注意的是,向量的模也被归一化,以便神经网络能够正确学习并避免梯度爆炸。

嵌入的一个重要元素是词汇表。这对应于可以用来输入到 Transformer 模型中的所有标记(单词)集合。词汇表不一定仅限于句子中使用的单词,而是与其主题相关的任何其他单词。例如,如果 Transformer 将用于分析法律文件,那么与官僚术语相关的每个单词都必须包含在词汇表中。请注意,词汇表越大(如果与 Transformer 任务相关),嵌入就越能找到标记之间的关系。

除了单词外,还有一些其他特殊标记被添加到词汇表和文本序列中。这些标记标记文本中的特殊部分,如开始、结束或填充(填充是为了使所有序列具有相同长度)。这些特殊标记也被嵌入为向量。

在数学中,嵌入空间构成了一个标准化向量空间,其中每个向量对应于一个特定的标记。向量空间的基由嵌入层能够找到的标记之间的关系决定。例如,一个维度可能对应于以*-ing* 结尾的动词,另一个可能是具有积极含义的形容词,等等。此外,向量之间的角度决定了标记之间的相似性,形成具有语义关系的标记簇。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

文本嵌入示意图。左侧,单词/标记被嵌入为向量,其中 d_model 代表最大嵌入维度。右侧,表示了前 3 个维度。尽管示例有所夸张,但请注意相似的单词如何形成组(簇),代表它们之间的相似性。图片由作者提供。

注 1. 尽管只提到了文本处理任务,但实际上 Transformer 是为了处理任何类型的序列数据而设计的。

Transformer 的工作流程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Transformer 图示。图片来源于论文 Vaswani, Ashish, et al. 2017.

上面是近年来深度学习研究中最常见的图示之一。它总结了 Transformers 的完整工作流程,表示了过程中的每个部分/模块。

从更高的视角来看,Transformer 被分为 编码器(图中的左侧蓝色块)和 解码器(右侧蓝色块)。

为了说明 Transformer 的工作原理,我将使用从英语到西班牙语的文本翻译任务作为例子。

注意 2. 我还没有定义注意力是什么,但本质上可以将其视为一个函数,该函数返回一些系数,用于定义句子中每个词相对于其他词的重要性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Transformer 的高层视角概述。编码器和解码器层已被简化为单个块,并且每个块的输出已被标注。图片由作者提供。

注意 3. 为了清晰起见,我将使用源输入来指代编码器的输入(英语句子),并使用目标输入来指代解码器中的期望输出(西班牙语句子)。这种标记将在文章的其余部分保持一致。

现在让我们更详细地查看 Transformer 的输入(源输入和目标输入)以及输出:

正如我们所见,Transformer 的输入文本被嵌入到一个高维向量空间中,因此输入的是一系列向量而不是一个句子。然而,还有一种更好的数学结构来表示向量序列,那就是矩阵!更进一步地,在训练神经网络时,我们不是逐样本训练,而是使用多个样本打包成的批次进行训练。最终的输入是形状为 [N, L, E] 的张量,其中 N 是批次大小,L 是序列长度,E 是嵌入维度

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

源输入张量的示意图。在左侧,二维张量表示批次的第一个 嵌入序列。在右侧,是包含单个批次所有序列的完整批次张量,输入到 Transformer 编码器中。同样,目标张量具有相同的形状,但包含真实的输出。图片由作者提供。

关于 Transformer 的输出,应用了一个线性 + Softmax 层,产生一些输出概率(回忆一下,Softmax 层输出的是定义类别的概率分布)。Transformer 的输出不是翻译后的句子,而是一个目标词汇的概率分布,决定了具有最高概率的单词。注意,对于序列长度中的每个位置,生成一个概率分布来选择下一个具有更高概率的令牌。由于在训练过程中 Transformer 一次处理所有句子,我们得到一个 3D 张量,表示词汇令牌的概率分布,其形状为[N, L, V]*,其中 N 是批量大小,L 是序列长度,V 是词汇长度*。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

输出概率张量的示意图。左侧是单个序列的词汇预测概率分布。每列表示目标词汇空间中的一个单词,每行对应序列中的令牌。右侧是整个批次的完整预测张量。图片由作者提供。

最终,预测的令牌是概率最高的

注意 4. 如在 NLP 引言部分所述,嵌入后的所有序列都具有相同的长度,这对应于 Transformer 可以引入/生成的最长序列。

训练与预测

在文章第一部分的最后一节中,我想谈谈 Transformer 的训练阶段与预测阶段的区别。

如上一节所述,Transformer 接受两个输入(源和目标)。在训练过程中,Transformer 能够一次处理所有输入,这意味着输入张量仅通过模型传递一次。输出实际上是前面图中呈现的三维概率张量。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Transformer 训练阶段。首先,源输入(英文句子)被嵌入并在编码器中应用注意力。然后,目标输入(西班牙语)被输入到解码器中,在解码器的第二层中,源注意力和目标注意力被结合。最后,生成词汇令牌的概率分布,并选择概率最高的令牌以形成翻译后的输出句子。注意,在层之间(L,E)的维度不变。图片由作者提供。

相反,在预测阶段,没有目标输入序列供 Transformer 输入(如果我们已经知道翻译后的句子,就不需要深度学习模型进行文本翻译了)。那么,我们输入什么作为目标输入呢?

就在这一点上,Transformer 的自回归行为显现出来。Transformer 可以在编码器中一次处理源输入序列,但对于解码器模块,它进入一个循环,每次迭代只生成序列中的下一个标记(一个关于词汇标记的行概率向量)。具有较高概率的选择标记随后作为目标输入再次输入,因此 Transformer 总是根据其之前的预测来预测下一个标记(因此具有自回归含义)。但是,在第一次迭代时应该输入哪个标记呢?

记得在 NLP 介绍部分提到的特殊标记吗?作为目标输入引入的第一个元素是起始标记,它标志着句子的开始。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Transformer 预测阶段。编码器部分保持不变,而解码器的张量是行向量。第一次(0 次)迭代的输入文本为空,因为只有标记。注意连接预测标记回到目标输入的箭头,表示自回归行为。在接下来的迭代中,目标输入会随着 Transformer 从序列中预测新的标记而增加。然而,只考虑输出概率张量中的最后一行,这意味着过去预测的标记不能改变。图片由作者提供。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

比较解码器在预测阶段的维度的表(上图展示了迭代 0)。注意维度从 1 增加到 L-1。一旦 Transformer 预测了序列中的倒数第二个标记,它会添加特殊标记来完成预测。图片由作者提供。

摘要

本部分介绍了了解 Transformer 模型所需的初步概念和思想。在下一部分,我将深入探讨 Transformer 架构的每个模块,其中大部分数学内容都在其中。

这篇文章的主要思想和概念是:

  • Transformers 在规范化向量空间中工作,该空间由嵌入系统定义,每个维度代表标记之间的一个特征。

  • Transformers 输入是形状为[N, L, E]的张量,其中 N 表示批量大小,L 是序列长度(由于填充对每个序列都是恒定的),E 代表嵌入维度。

  • 编码器在源嵌入空间中发现标记之间的关系时,解码器的任务是学习从源空间到目标空间的投影。

  • Transformer 的输出是一个行向量,其长度等于词汇表的大小,每个系数代表相应索引标记在序列中下一个位置的概率

  • 在训练期间,Transformer 会一次处理所有输入,输出一个[N, L, V]张量(V 为词汇表长度)。但在预测过程中,Transformers 是自回归的,总是基于之前的预测逐个预测标记。

下一篇文章的部分内容将很快在这里发布。

感谢阅读!如果你发现这篇文章有帮助或启发,请考虑关注我,以获取更多关于 AI 和深度学习的内容。

加布里埃尔·弗尼耶莱斯 - Medium

在 Medium 上阅读加布里埃尔·弗尼耶莱斯的文章。他是数学工程专业的学生,专注于 AI 和 ML。我写…

加布里埃尔·弗尼耶莱斯 - Medium

你可能还会对以下内容感兴趣:

5 种最有前途的图像翻译 AI 模型

最新图像生成模型的最前沿技术

链接

变换器 — 直观且详尽的解释

原文:towardsdatascience.com/transformers-intuitively-and-exhaustively-explained-58a5c5df8dbb

探索现代机器学习的浪潮:逐步拆解变换器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 丹尼尔·沃菲尔德

·发表于 Towards Data Science ·15 分钟阅读·2023 年 9 月 20 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图像由作者使用 MidJourney 制作。所有图像由作者提供,除非另有说明。

在这篇文章中,你将学习变换器架构,它是几乎所有前沿大型语言模型的核心架构。我们将从一些相关自然语言处理概念的简要时间线开始,然后逐步讲解变换器,揭示它的工作原理。

这对谁有用? 对自然语言处理(NLP)感兴趣的任何人。

这篇文章的难度如何? 这篇文章不是很复杂,但有很多概念,因此对经验较少的数据科学家可能会有些令人生畏。

前提条件: 对标准神经网络有良好的工作理解。对嵌入、编码器和解码器有一些初步了解也会很有帮助。

自然语言处理到变换器的简要时间线

以下章节包含在了解变换器之前需要知道的有用概念和技术。如果你感觉自信,可以跳过前面的内容。

词向量嵌入

对词向量嵌入的概念理解对于理解自然语言处理至关重要。实质上,词向量嵌入将单个单词转换成一个向量,这个向量以某种方式表示其含义。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

词向量嵌入器的工作:将单词转换为数字,这些数字以某种方式捕捉其一般意义。

细节可能因实现而异,但最终结果可以被认为是一个“单词空间”,其中空间遵循某些便利的关系。单词很难进行数学运算,但包含有关单词及其与其他单词关系信息的向量,进行数学运算则显著容易得多。 将单词转换为向量的任务通常被称为“嵌入”。

Word2Vect,作为自然语言处理领域的一篇开创性论文,旨在创建一个符合某些有用特性的嵌入。实际上,他们希望能够对单词进行代数运算,并创建了一个嵌入以促进这一点。通过 Word2Vect,你可以嵌入“king”这个词,减去“man”的嵌入,再加上“woman”的嵌入,你会得到一个与“queen”的嵌入最接近的向量。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对单词嵌入进行代数运算的概念性演示。如果你将每个点看作是从原点的向量,如果你从“king”的向量中减去“man”的向量,并加上“woman”的向量,那么结果向量将接近单词“queen”。实际上,这些嵌入空间的维度要高得多,而“接近度”的度量可能不那么直观(例如余弦相似度),但直觉保持不变。

随着技术的发展,单词嵌入仍然是一个重要工具,其中 GloVe、Word2Vec 和 FastText 都是流行的选择。子词嵌入通常比完整的单词嵌入更强大,但超出了本文的范围。

递归网络(RNNs)

现在我们可以将单词转换为具有某些意义的数字,我们可以开始分析单词序列。早期的一种策略是使用递归神经网络,你将训练一个在顺序输入上自我反馈的神经网络。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

RNN 的一般思想是一个正常的全连接神经网络,它自我反馈。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果一个 RNN 有 3 个隐藏神经元,并且用于 2 个输入,它可能看起来像这样。红色的箭头是递归连接,它们连接来自后续递归层的信息。蓝色的箭头是内部连接,类似于一个密集层。神经网络被复制以便说明,但请记住,网络实际上是自我反馈的,这意味着第二(以及后续)模块的参数将与第一个模块相同。

与传统神经网络不同,由于递归网络自我反馈,它们可以用于任意长度的序列。对于长度为 10 的序列或长度为 100 的序列,它们将具有相同数量的参数,因为它们重用每个递归连接的相同参数。

这种网络风格在许多建模问题中得到应用,这些问题通常可以归类为序列到序列建模、序列到向量建模、向量到序列建模和序列到向量到序列建模。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

几种不同建模策略的概念图,这些策略可能会使用 RNNs。序列到序列可能是预测文本完成的下一个词。序列到向量可能是评分客户对评论的满意度。向量到序列可能是将图像压缩成向量,并要求模型将该图像描述为文本序列。序列到向量到序列可能是文本翻译,其中你需要理解一个句子,将其压缩成某种表示,然后用不同的语言构建该压缩表示的翻译。

尽管无限长度序列建模的前景令人心动,但实际上并不切实际。由于每一层共享相同的权重,递归模型很容易忘记输入内容。因此,RNNs 实际上只能用于非常短的词序列。

曾经有一些尝试通过使用“门控”和“泄漏”RNNs 来解决这个问题。其中最著名的是 LSTM,下一部分将描述它。

长短期记忆(LSTMs)

LSTM 的创建旨在提高递归网络记忆重要信息的能力。LSTM 具有短期和长期记忆,可以在序列中的任何给定元素中将某些信息检查进或从长期记忆中移除。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LSTM 的核心要点

从概念上讲,LSTM 有三个关键子组件:用于忘记之前长期记忆的“遗忘门”,用于将信息写入长期记忆的“输入门”,以及用于制定下一次迭代的短期记忆的“输出门”。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LSTM 中的参数。这个特定的 LSTM 期望输入向量的维度为 3,并持有维度为 2 的内部状态向量。向量的维度是一个可配置的超参数。另外,请注意每个门末尾的“S”和“T”。这些代表 sigmoid 或 tanh 激活函数,用于将值压缩到某些范围,如 0 到 1 或-1 到 1。这个“压缩”使网络能够“忘记”和“记忆”某些信息。图像由作者提供,深受来源的启发。

LSTM 和类似的架构,如 GRU,证明比前面讨论的经典 RNN 有了显著改善。能够将记忆作为一个单独的概念进行检查和释放,证明是非常强大的。然而,尽管 LSTM 能够建模更长的序列,但它们在许多语言建模任务中太过遗忘。此外,由于它们依赖于先前的输入(像 RNN 一样),它们的训练难以并行化,结果也很慢。

通过对齐提取注意力

标志性论文,通过联合学习对齐和翻译的神经机器翻译普及了注意力的一般概念,并且是变换器中多头自注意力机制的概念先驱。

我有一篇关于这个具体主题的文章,其中包含了 PyTorch 的示例代码。简而言之,本文中的注意力机制会查看所有潜在的输入,并决定在任何给定的输出时将哪个输入呈现给 RNN。换句话说,它决定了哪些输入当前是相关的,哪些输入当前是不相关的

[## 从对齐中提取注意力,实际解释

关注重要的,忽略不重要的。

blog.roundtableml.com

这种方法被证明对翻译任务有着巨大的影响。它使递归网络能够确定哪些信息当前是相关的,从而在翻译任务中实现了前所未有的性能。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

链接文章中的一张图。方块代表词向量嵌入,圆圈代表中介向量表示。红色和蓝色圆圈是来自递归网络的隐藏状态,白色圆圈是由通过对齐机制的注意力创建的隐藏状态。关键在于,注意力机制可以选择在任何给定步骤向输出呈现正确的输入。

变换器

在前面的章节中,我们涉及了一些宏观的知识。现在我们将深入探讨变换器,它结合了先前成功的和新颖的想法,彻底改变了自然语言处理。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

变换器图示。source

我们将逐个元素讨论变换器,并讨论每个模块如何工作。内容很多,但不涉及复杂的数学,概念也相当易懂。

高级架构

从根本上讲,变换器是一个编码器/解码器风格的模型,有点像我们之前讨论的序列到向量到序列模型。编码器接受某些输入并将其压缩为一个表示,编码整个输入的意义。然后解码器接受这个嵌入并递归地构造输出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个变换器在序列到向量到序列任务中的工作,总的来说。输入(我是一名经理)被压缩为某种抽象表示,编码整个输入的意义。解码器像我们之前讨论的 RNN 一样递归地构造输出。

输入嵌入和位置编码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

原图中的输入嵌入。source

对于变换器,输入嵌入类似于之前讨论的策略;一个类似于 word2vect 的词空间嵌入器将所有输入词转换为向量。这个嵌入与模型一起训练,基本上是一个通过模型训练得到改进的查找表。因此,每个词汇表中的词会有一个随机初始化的向量,而这个向量会随着模型对每个词的学习而变化。

与递归策略不同,变换器一次性编码整个输入。因此,编码器可能会丢失关于输入中词位置的信息。为了解决这个问题,变换器还使用位置编码器,它是一个编码关于特定词在序列中位置的信息的向量。

"""
Plotting positional encoding for each index.
A positional encoding for a single token would be a horizontal row in the image

inspired by https://machinelearningmastery.com/a-gentle-introduction-to-positional-encoding-in-transformer-models-part-1/
"""

import numpy as np
import matplotlib.pyplot as plt

#these would be defined based on the vector embedding and sequence
sequence_length = 512
embedding_dimension = 1000

#generating a positional encodings
def gen_positional_encodings(sequence_length, embedding_dimension):
    #creating an empty placeholder
    positional_encodings = np.zeros((sequence_length, embedding_dimension))

    #itterating over each element in the sequence
    for i in range(sequence_length):

        #calculating the values of this sequences position vector
        #as defined in section 3.5 of the attention is all you need
        #paper: https://arxiv.org/pdf/1706.03762.pdf
        for j in np.arange(int(embedding_dimension/2)):
            denominator = np.power(sequence_length, 2*j/embedding_dimension)
            positional_encodings[i, 2*j] = np.sin(i/denominator)
            positional_encodings[i, 2*j+1] = np.cos(i/denominator)

    return positional_encodings

#rendering
fig, ax = plt.subplots(figsize=(15,5))
ax.set_ylabel('Sequence Index')
ax.set_xlabel('Positional Encoding')
cax = ax.matshow(gen_positional_encodings(sequence_length, embedding_dimension))
fig.colorbar(cax, pad=0.01)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

位置编码的示例。Y 轴表示随后的词,X 轴表示特定词位置编码中的值。图中的每一行代表一个单独的词。

"""
Rendering out a few individual examples

inspired by https://machinelearningmastery.com/a-gentle-introduction-to-positional-encoding-in-transformer-models-part-1/
"""
positional_encodings = gen_positional_encodings(100, 50)
fig = plt.figure(figsize=(15, 4))    
for i in range(4):
    ax = plt.subplot(141 + i)
    idx = i*10
    plt.plot(positional_encodings[:,idx])
    ax.set_title(f'positional encoding {idx}')
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

相对于序列中不同索引的位置向量值。K 表示序列中的索引,图表示向量中的值。

这个系统将正弦和余弦函数联合使用来编码位置,你可以在这篇文章中获得一些直观感受:

## 更频繁地使用频率

从简单到高级的频率分析手册。探索在数据中广泛未被充分利用的关键工具…

blog.roundtableml.com

我不会多讲,但值得注意的是;这个位置编码系统与电机中使用的位置编码器非常相似,其中两个相位差 90 度的正弦波使电机驱动器能够理解电机的位置、方向和速度。

用于编码词位置的向量会加到该词的嵌入中,创建一个包含该词在句子中位置以及词本身信息的向量。你可能会想“如果你将这些波动的波形加到嵌入向量中,这不会掩盖原始嵌入的一些含义,并可能混淆模型吗”?对此,我会说神经网络(变换器用来作为其可学习参数的网络)对理解和操作平滑且连续的函数非常擅长,因此对于一个足够大的模型来说,这几乎没有什么影响。

多头自注意力:高级概述

这可能是变换器机制中最重要的子组件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

原始图中的多头自注意力机制。 source

在作者谦虚的观点中,将其称为“注意力”机制有点用词不当。实际上,这是一种“关联”和“上下文化”机制。它允许词与其他词进行交互,将输入(即每个词的嵌入向量列表)转换为表示整个输入含义的矩阵。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

简而言之,多头自注意力机制。该机制从数学上将不同词的向量结合,创建一个矩阵,编码了整个输入的更深层次的含义。

这个机制可以分为四个独立的步骤:

  1. 创建查询、键和值

  2. 分成多个头部

  3. 注意力头

  4. 组合最终输出

多头自注意力步骤 1)创建查询、键和值

首先,不必过于担心“查询”、“键”和“值”这些名称。这些名称受到数据库的模糊启发,但实际上只是最模糊的意义。查询、键和值本质上是嵌入输入的不同表示,在整个注意力机制中会彼此关联。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将嵌入输入转换为查询、键和值。输入的维度为 num_words 乘 embedding_size,查询、键和值的维度与输入相同。实质上,密集网络将输入投射到一个具有三倍特征数量的张量中,同时保持序列长度。

上面显示的密集网络包括多头自注意力机制中唯一的可学习参数。多头自注意力可以被视为一个函数,模型学习输入(查询、键和值),以最大化该函数在最终建模任务中的性能。

多头自注意力步骤 2)分成多个头部

在进行实际的上下文化处理之前,我们将查询、键和值分成若干部分。核心思想是,我们可以用多种不同的方式来关联我们的词汇,而不是单一的方式。这样做可以编码出更微妙和复杂的含义。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个例子中,我们有 3 个注意力头。因此,查询、键和值被分成 3 部分,并传递给每个头。注意,我们是沿特征轴进行划分,而不是沿词汇轴。每个词的不同方面被传递到不同的注意力头,但每个词仍然存在于每个注意力头中。

多头自注意力机制。步骤 3) 注意力头

现在我们已经有了传递给注意力头的查询、键和值的子组件,我们可以讨论注意力头如何结合这些值来上下文化结果。在《Attention is All You Need》中,这通过矩阵乘法完成。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

矩阵乘法。来源

在矩阵乘法中,一个矩阵的行与另一个矩阵的列通过点积结合,从而创建出结果矩阵。在注意力机制中,查询和键矩阵相乘,产生我所称之为“注意力矩阵”的东西。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用查询和键计算注意力矩阵。注意,键被转置,以便矩阵乘法产生正确的注意力矩阵形状。

这是一个相当简单的操作,因此很容易低估其影响。在这一点上使用矩阵乘法迫使每个词的表示与其他词的表示结合。由于查询和键是由密集网络定义的,注意力机制学习如何转换查询和键,以优化这个矩阵的内容。

现在我们有了注意力矩阵,它可以与值矩阵相乘。这有三个主要目的:

  1. 通过关联输入的另一种表示来增加一些上下文。

  2. 建立一个系统,使查询和键能够转换值,从而根据查询、键和值的来源实现自注意力或交叉注意力。

  3. 或许最重要的是,这使得注意力机制的输出与输入大小相同,从而使某些实现细节更容易处理。

重要更正

注意力矩阵在乘以值矩阵之前,按行进行 softmax 处理。这一数学细节完全改变了注意力矩阵的概念意义及其与值矩阵的关系。

因为注意力矩阵的每一行都经过 softmax 处理,每一行都变成一个概率。这与我在另一篇文章中讨论的注意力对齐概念非常相似。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来自我的通过对齐的注意力文章。每一行是一个概率分布,总和为 1,强制最重要的内容与其他重要内容相关联。

这个细节在对变压器的更大解释中经常被忽视,但可以说这是变压器架构中最重要的操作,因为它将模糊的关联转化为稀疏且有意义的选择。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意力矩阵(即查询和键的矩阵乘法)与值矩阵相乘以产生注意力机制的最终结果。由于注意力矩阵的形状,结果与值矩阵的形状相同。请记住,这是来自单个注意力头的结果。

多头自注意力。步骤 4)合成最终输出

在上一节中,我们使用了查询(query)、键(key)和值(value)来构建一个新的结果矩阵,该矩阵与值矩阵具有相同的形状,但具有显著更高的上下文感知能力。

记住,注意力头仅计算输入空间子组件(沿特征轴划分)的注意力。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

记住,输入被分成了多个注意力头。在这个例子中,有 3 个头。

每个注意力头现在输出不同的结果,这些结果可以拼接在一起。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

每个注意力头的结果被拼接在一起

矩阵的形状与输入矩阵的形状完全相同。然而,与每行与单一单词清晰相关的输入不同,这个矩阵要抽象得多。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

记住,注意力机制简而言之是将嵌入的输入转换为一个抽象的、丰富上下文的表示。

Add 和 Norm

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在原始图中添加和归一化。source

Add 和 Norm 操作在编码器中应用了两次,每次效果相同。这里有两个关键概念:跳跃连接(skip connections)和层归一化(layer normalization)。

跳跃连接在机器学习中随处可见。我最喜欢的例子是在图像分割中使用 U-net 架构,如果你熟悉的话。基本上,当你进行复杂操作时,模型容易“脱离自我”。这有各种复杂的数学定义,比如梯度爆炸和秩崩溃,但从概念上讲很简单;模型可能过度思考问题,因此重新引入旧数据可以重新引入一些简单的结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

跳跃连接添加可能的样子。在这个例子中,左侧的矩阵代表原始编码输入。中间的矩阵代表注意力矩阵的超上下文化结果。右侧代表跳跃连接的结果:一个上下文感知的矩阵,仍然保留了原始输入的一些顺序。

层归一化类似于跳跃连接,从概念上讲,它可以控制数据的异常情况。对这些数据进行了许多操作,导致了值的大小不一。如果你对这个矩阵进行数据科学分析,可能会遇到非常小和极大的值。这被认为是一个问题。

层归一化计算均值和标准差(值的分布范围),并利用这些值将数据压缩回合理的分布中。

前馈

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

原始图示中的前馈。source

这一部分很简单。我们可以将注意力机制后的添加归一化的输出通过一个简单的全连接网络。我喜欢把这看作一种投影,模型可以学习如何将注意力输出投影到对解码器有用的格式中。

然后将前馈网络的输出通过另一个添加归一化层,这样就得到了最终输出。解码器将使用这个最终输出来生成结果。

解码器的一般功能

我们已经完全覆盖了编码器,并且得到了一个高度上下文化的输入表示。现在我们将讨论解码器如何利用这些表示生成输出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

高层次的表示解码器如何与编码器的输出相关。解码器在每次递归输出时都参考编码的输入。

解码器与编码器非常相似,只是有一些小的变化。在谈论这些变化之前,让我们先讨论相似之处。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Transformer 架构 source

如上图所示,解码器使用相同的词向量嵌入方法,并采用相同的位置编码器。解码器使用“掩码”多头自注意力机制,我们将在下一节讨论,并使用另一个多头注意力块。

第二种多头自注意力机制使用编码后的输入作为键和值,并使用解码器输入生成查询。因此,注意力矩阵从编码器和解码器的嵌入中计算,然后应用于来自编码器的值。这使得解码器能够根据编码器输入和解码器输入来决定最终应该输出什么。

其余部分与你在其他模型中可能发现的模板相同。结果经过另一层前馈、一个加法归一化、一个线性层和一个 softmax。这一 softmax 会输出例如一组单词的概率,从而允许模型决定输出哪个单词。

掩码多头自注意力

所以解码器真正新的部分就是“掩码”注意力。这与这些模型的训练方式有关。

循环神经网络的一个核心缺陷是你需要顺序训练它们。RNN 密切依赖于对前一步的分析来指导下一步。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

RNN 在步骤之间建立亲密依赖关系

这使得训练 RNN 变得非常缓慢,因为训练集中每个序列需要一个接一个地顺序传递通过模型。通过对注意力机制的一些精心修改,变换器可以解决这个问题,使模型能够并行训练整个序列。

细节可能会有些繁琐,但核心是:在训练模型时,你可以访问期望的输出序列。因此,你可以将整个输出序列(包括你还未预测的输出)馈送给解码器,并使用掩码将它们隐藏在模型之外。这使得你可以同时对序列的所有位置进行训练。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

掩码多头自注意力机制的工作示意图,用于英语到法语的翻译任务。该任务的输入是短语“我是一名经理”,期望的输出是短语“Je suis directeur”。请注意,为了简化起见,我通常忽略了实用令牌的概念。它们很容易理解,比如:开始序列、结束序列等。

结论

就这样!我们分解了一些导致变换器发现的技术创新以及变换器的工作原理,然后我们回顾了变换器作为编码器-解码器模型的高级架构,并讨论了重要的子组件,如多头自注意力、输入嵌入、位置编码、跳跃连接和归一化。

关注更多!

我描述了机器学习领域的论文和概念,重点在于实用和直观的解释。

[## 每当丹尼尔·沃菲尔德发布文章时获取邮件

高质量的数据科学文章直接送到您的收件箱。每当丹尼尔·沃菲尔德发布文章时获取邮件。通过注册,您…

medium.com](https://medium.com/@danielwarfield1/subscribe?source=post_page-----58a5c5df8dbb--------------------------------) 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从未预期,总是感激。通过捐赠,您使我能够将更多时间和资源分配到更频繁和更高质量的文章上。了解更多

归属: 本文档中的所有图像均由丹尼尔·沃菲尔德创建,除非另有来源说明。您可以将本文中的任何图像用于您的非商业用途,只要您引用了这篇文章,danielwarfield.dev,或两者都引用。

Transformers 是否输给了线性模型?

原文:towardsdatascience.com/transformers-lose-to-linear-models-902164ca5974?source=collection_archive---------11-----------------------#2023-01-23

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Nicholas Cappello 提供,来自 Unsplash

使用 Transformers 进行长期预测可能并非最佳选择

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Nakul Upadhya

·

关注 发表在 Towards Data Science · 9 分钟阅读 · 2023 年 1 月 23 日

近年来,基于 Transformer 的解决方案获得了极大的关注。随着 BERT、GTP 以及其他语言 Transformer 的成功,研究人员开始将这种架构应用于其他序列建模问题,特别是在时间序列预测领域(也称为长期时间序列预测或 LTSF)。注意力机制似乎是一种提取长序列中某些长期相关性的完美方法。

但是,最近来自香港中文大学和国际数字经济活动的研究人员决定质疑:变形金刚是否适用于时间序列预测 [1]? 他们表明,自注意机制(即使加入了位置编码)可能会导致时间信息丢失。他们随后用一组单层线性模型验证了这一说法,在几乎每个实验中都优于变压器基准。

简而言之,变压器可能并不是预测问题的最理想架构

在本文中,我旨在总结曾等人[1]的发现和实验,得出这一结论,并讨论该工作的一些潜在影响。作者开发的所有实验和模型也可以在他们的GitHub 仓库中找到。此外,我强烈建议每个人都阅读原始论文

模型和数据

在他们的工作中,作者在Electricity Transformer Dataset (ETDataset) [3]上评估了 5 种不同的 SOTA 变压器模型。这些模型及其主要特点如下:

  1. LogTrans [2]:提出了卷积自注意力,以便更好地将本地上下文纳入到注意力机制中。该模型还在注意力方案中编码了一种稀疏偏差。这有助于提高内存复杂性。

  2. Informer [3]:通过提出一种新的架构和一种直接多步(DMS)预测策略,解决了由自回归解码器引起的内存/时间复杂性和误差复杂性问题。

  3. Autoformer [4]: 在每个神经块后面应用季节趋势分解以提取趋势周期性成分。此外,Autoformer 设计了一种系列式自动相关机制,以取代常规的自注意力。

  4. Pyraformer [5]:实现了一种新颖的金字塔式注意力机制,捕捉分层多尺度时间依赖关系。像 LogTrans 一样,该模型还在注意力方案中明确编码了一种稀疏偏差。

  5. FEDFormer [6]:通过将季节趋势分解方法合并到体系结构中,有效地开发了Frequency-Enhanced Decomposed Transformer,增强了传统的变压器架构。

这些模型都对变压器架构的各个部分进行了各种改动,以解决传统变压器存在的各种不同问题(完整总结可在图 1 中找到)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1:现有基于变压器的时间序列预测解决方案的流程图(由曾等人制作的图 1)

为了与这些变压器模型竞争,作者提出了一些“令人尴尬地简单”的模型[1]来进行 DMS 预测。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2:基本 DMS 线性模型的示意图(图由 Zeng 等人[1]提供)

这些模型及其属性如下:

  1. 分解线性(D-Linear): D-Linear 使用分解方案将原始数据拆分为趋势和季节性组件。然后对每个组件应用两个单层线性网络,并将输出求和以获得最终预测。

  2. 归一化线性(N-Linear): N-Linear 首先从输入中减去序列的最后一个值。然后将输入传递到一个线性层中,最后将减去的部分加回,以进行最终预测。这有助于解决数据中的分布偏移。

  3. 重复:只需重复回顾窗口中的最后一个值。

这些是一些非常简单的基准线。线性模型都涉及少量的数据预处理和一个单层网络。重复是一个琐碎的基准。

实验是在各种广泛使用的数据集上进行的,如Electricity Transformer (ETDataset)[3]、交通、电力、天气、ILI 和汇率[7]数据集。

实验

在上述 8 个模型上,作者进行了一系列实验,以评估模型的性能并确定每个模型中各种组件对最终预测的影响。

第一个实验很简单:每个模型都经过训练并用于预测数据。回顾期也有所变化。完整的测试结果见表 1,但总的来说,FEDFormer [6]在大多数情况下是表现最好的transformer,但从未是总体最佳的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

表 1:实验误差。最佳 Transformer 结果下划线标记,最佳总体结果加粗。(图由 Zeng 等人[1]提供)

这种 Transformer 令人尴尬的表现可以在图 3 中对电力、汇率和 ETDataset 的预测中看到。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3:输入长度=96,输出长度=192 的 LTSF 输出(图由 Zeng 等人[1]提供)。

引用作者的话:

Transformers [28, 30, 31]未能捕捉到电力和 ETTh2 上未来数据的规模和偏差。此外,它们几乎无法预测诸如汇率等非周期性数据的适当趋势。这些现象进一步表明现有的 Transformer 解决方案在 LTSF 任务中的不足。

然而,许多人会认为这对变换器不公平,因为注意机制通常擅长保存长期信息,所以变换器应该在更长的输入序列中表现更好,作者在他们的下一个实验中测试了这个假设。他们将回溯期在 24 到 720 个时间步之间变化,并评估均方误差。作者发现,在许多情况下,变换器的性能没有提高,实际上几个模型的误差还增加了(完整结果见图 4)。相比之下,线性模型的性能在加入更多时间步后显著提高

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4:回溯期长度变化的均方误差结果(图由 Zeng 等人[1]制作)

然而,仍然有其他因素需要考虑。由于变换器的复杂性,它们通常需要比其他模型更大的训练数据集才能表现良好,因此,作者决定测试训练数据集大小是否是这些变换器架构的限制因素。他们利用了交通数据[7],并在原始数据集和截断数据集上训练了 Autoformer [4]和 FEDformer [6],期望较小的训练集会导致误差更高。令人惊讶的是,在较小训练集上训练的模型表现稍微更好。这并不意味着应该使用较小的训练集,但这确实意味着数据集大小不是 LTSF 变换器的限制因素。

除了改变训练数据大小和回溯期长度外,作者还尝试了不同的回溯窗口起始时间。例如,如果他们试图对t=196 之后的时期进行预测,而不是使用t=100, 101,…, 196(邻近或“接近”窗口),作者尝试了使用t=4, 5,…, 100(“远离”窗口)。这个想法是,预测应该依赖于模型是否能够很好地捕捉趋势和周期性,视野越远,预测效果应该越差。**作者发现变换器在“接近”窗口和“远离”窗口之间的性能仅略有下降。**这意味着变换器可能会对提供的数据过拟合,这可能解释了为什么线性模型表现更好。

在评估了各种变换器模型后,作者们还专门探讨了这些模型中自注意力和嵌入策略的有效性。他们的第一个实验涉及拆解现有的变换器,以分析变换器复杂设计是否必要。他们将注意力层拆解为简单的线性层,然后去除了除嵌入机制之外的辅助部分,最后将变换器简化为仅有的线性层。在每一步,他们记录了使用不同回溯期大小的均方误差,并发现随着逐步简化,变换器的性能有所提升。

作者们还想研究变换器在保持时间顺序方面的影响。他们假设由于自注意力是排列不变的(忽略顺序),而时间序列对排列敏感,因此位置编码和自注意力不足以捕获时间信息。为了测试这一点,作者通过打乱数据和交换输入序列的前半部分与后半部分来修改序列。模型捕获的时间信息越多,修改数据集时模型性能的下降应该越大。作者观察到,线性模型的性能下降比任何变换器模型都要大,这表明变换器捕获的时间信息少于线性模型。完整结果见下表

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

表 2:(图由 Zeng 等人 [1] 制作)

为了进一步探讨变换器的信息捕获能力,作者们通过去除变换器的位置信息和时间编码来检验不同编码策略的有效性。这些结果因模型而异。对于 FEDFormer [6] 和 Autoformer [4],去除位置编码在大多数回溯窗口大小上提高了交通数据集的性能。然而,Informer [3] 在具有所有位置编码时表现最好。

讨论与结论

理解这些结果时需要注意几点。变换器对超参数非常敏感,通常需要大量的调优才能有效地建模问题。然而,作者在实现这些模型时没有进行任何超参数搜索,而是选择了模型实现中使用的默认参数。有观点认为,他们也没有调整线性模型,因此比较是公平的。此外,调整线性模型所需的时间远远少于训练变换器,因为线性模型的简单性。尽管如此,可能会出现变换器在正确的超参数下表现极其出色的情况,而成本和时间可以忽略,以提高准确性。

尽管有这些批评,作者进行的实验详细地分解了变换器的缺陷。这些模型庞大且非常复杂,容易在时间序列数据上过拟合。虽然它们在语言处理和其他任务中表现良好,但自注意力的置换不变特性确实会导致显著的时间损失。此外,相比于复杂的变换器架构,线性模型在解释性和可解释性方面极其优越。如果对这些长期序列预测(LTSF)变换器的组件进行一些修改,我们可能会看到它们最终超越简单的线性模型或解决线性模型难以建模的问题(例如变点识别)。然而,与此同时,数据科学家和决策者不应盲目地将变换器应用于时间序列预测问题,除非有非常充分的理由来利用这种架构。

资源和参考文献

[1] A. Zeng, M. Chen, L. Zhang, Q. Xu. 变换器对时间序列预测有效吗? (2022). 第三十七届 AAAI 人工智能会议。

[2] S. Li, X. Jin, Y. Xuan, X. Zhou, W. Chen, Y. Wang, X. Yan. 提升变换器在时间序列预测中的局部性并打破记忆瓶颈 (2019). 神经信息处理系统进展 32。

[3] H. Zhou, S. Zhang, J. Peng, S. Zhang, J. Li, H. Xiong, W. Zhang. Informer: 超越高效变换器的长序列时间序列预测 (2021). 第三十五届 AAAI 人工智能会议,虚拟会议。

[4] H. Wu, J. Xu, J. Wang, M. Long. Autoformer: 用于长期序列预测的自相关分解变换器 (2021). 神经信息处理系统进展 34。

[5] S. Liu, H. Yu, C. Liao, J. Li, W. Lin, A.X. Liu, S. Dustdar. Pyraformer: 低复杂度金字塔注意力用于长时间序列建模和预测 (2021). 国际学习表示会议 2021。

[6] T. Zhou, Z. Ma, Q. Wen, X. Wang, L. Sun, R. Jin. EDformer: 用于长期序列预测的频率增强分解变换器 (2022). 第 39 届国际机器学习会议。

[7] G. Lai, W-C. Chang, Y. Yang, 和 H. Liu. 使用深度神经网络建模长短期时间模式 (2017). 第 41 届国际 ACM SIGIR 信息检索研究与发展会议。

简化 Transformers:使用你理解的词汇的最先进 NLP——第一部分——简介

原文:towardsdatascience.com/transformers-part-1-2a2755a2af0e?source=collection_archive---------7-----------------------#2023-07-26

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Chen Margalit

·

关注 发表在 Towards Data Science ·3 分钟阅读·2023 年 7 月 26 日

Transformers 是一种深度学习架构,对 AI 的进步做出了杰出的贡献。它在 AI 和整体技术领域中是一个重要的阶段,但也有点复杂。到目前为止,关于 Transformers 有很多不错的资源,那么为什么还要再做一个呢?两个原因:

  1. 我精通自学,从我的经验来看,能够阅读不同人对相同思想的描述,大大增强了理解能力。

  2. 我很少读到一篇文章并认为它解释得足够简单。技术内容创作者往往过于复杂化或解释不够充分。应该明确的是,没有什么是火箭科学,即便是火箭科学也不例外。你可以理解任何事物,只需要一个足够好的解释。在这个系列中,我尝试提供足够好的解释。

此外,作为一个将自己的职业生涯归功于文章和开源代码的人,我认为有义务回馈社会。

本系列将尝试为几乎对 AI 一无所知的人以及了解机器如何学习的人提供合理的指南。我打算如何做到这一点?首要任务是——解释。我在职业生涯中可能阅读了接近 1000 篇技术论文(如本篇),我面临的主要问题是作者(可能是无意识的)假设你知道很多东西。在本系列中,我计划假设你知道的比我为准备这篇文章而阅读的 Transformer 文章要少。

此外,我将结合直观理解、数学、代码和可视化,因此该系列的设计就像一个糖果店——人人都能找到合适的东西。考虑到这是一个相当复杂领域中的高级概念,我会冒险让你觉得:“哇,这么慢,别解释显而易见的东西”,但如果你对自己说:“他到底在说什么?”的情况会少得多。

Transformers,值得你花时间了解吗?

这到底是怎么回事?真的这么重要吗?嗯,既然它是一些世界上最先进的 AI 驱动技术工具(例如 GPT 等)的基础,它可能确实重要。

尽管与许多科学进展一样,一些想法以前已经被描述过,但对架构的实际深入、完整描述来自于“Attention is all you need”论文,该论文声称以下内容是一个“简单的网络架构”。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源于原始论文

如果你像大多数人一样,你不会认为这是一种简单的网络架构。因此,我的工作是努力做到,当你读完这系列文章时,你会对自己说:这仍然不简单,但我确实明白了。

那么,这个疯狂的图表,究竟是什么?

我们看到的是一种深度学习架构,这意味着每个方块应该被转换为一些代码,而这些代码组合在一起将实现一些目前人们还不知道如何做到的事情。

Transformers 可以应用于许多不同的用例,但可能最著名的是自动化聊天。一个可以谈论很多主题的程序,仿佛它知道很多东西。从某种程度上说,这与黑客帝国有些相似。

我想让人们只阅读他们实际需要的内容,因此系列将根据我认为 Transformer 故事应该如何讲述来进行拆分。第一部分在这里,将讨论架构的第一部分**— 输入**。

简化 Transformers:使用你理解的词语的最前沿 NLP — 第二部分 — 输入

原文:towardsdatascience.com/transformers-part-2-input-2a8c3a141c7d?source=collection_archive---------2-----------------------#2023-07-26

深入探讨 Transformer 输入的构建方式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Chen Margalit

·

关注 发表在 Towards Data Science ·10 分钟阅读·2023 年 7 月 26 日

输入

龙从蛋中孵化,婴儿从母腹中诞生,AI 生成的文本从输入开始。我们都必须从某个地方开始。

什么类型的输入?这取决于手头的任务。如果你正在构建一个语言模型,一个能够生成相关文本的程序(Transformers 架构在各种场景中都很有用),输入就是文本。然而,计算机能否接收任何类型的输入(文本、图像、声音)并神奇地知道如何处理它?它并不能。

我相信你认识一些不擅长文字但擅长数字的人。计算机有点类似于这样。它不能直接在 CPU/GPU(进行计算的地方)处理文本,但它可以处理数字!正如你将很快看到的,将这些单词表示为数字的方式是秘密配方中的关键成分。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来自 Vaswani, A.等人的原始论文

标记化器

标记化是将语料库(你拥有的所有文本)转换为机器可以更好利用的更小部分的过程。假设我们有一个 10,000 篇维基百科文章的数据集。我们将每个字符进行转换(标记化)。有很多种标记化文本的方法,让我们看看OpenAI 的标记化器如何处理以下文本:

许多单词映射到一个标记,但有些则不是:不可分割的。

像表情符号这样的 Unicode 字符可能会被拆分成包含底层字节的多个标记:🤚🏾

常见的字符序列可能会被组合在一起:1234567890

这是标记化的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来自 OpenAI,来源于这里

如你所见,这里大约有 40 个单词(取决于如何计算(标点符号))。在这 40 个单词中,生成了 64 个标记。有时候标记是整个单词,比如“Many, words, map”,有时候则是单词的一部分,比如“Unicode”。我们为什么要将整个单词拆分成更小的部分?甚至拆分句子又有何意义?最终,它们无论如何都会被转换为数字,那么从计算机的角度来看,标记是 3 个字符长还是 30 个字符长有何区别?

标记帮助模型学习,因为文本是我们的数据,它们是数据的特征。工程化这些特征的不同方式会导致性能的变化。例如,在句子:“Get out!!!”中,我们需要决定多个“!”是否与一个“!”不同,还是它们有相同的意义。从技术上讲,我们可以将句子保持为整体,但想象一下看一个人群与单独看每个人的场景,你会在什么场景中获得更好的见解?

现在我们有了标记,我们可以建立一个查找字典,这将允许我们摆脱单词而使用索引(数字)。例如,如果我们的整个数据集是句子:“Where is god”。我们可能会建立这样的词汇表,它只是单词和一个代表它们的数字的键值对。我们不必每次都使用整个单词,我们可以使用数字。例如:

{Where: 0, is: 1, god: 2}。每当我们遇到单词“is”时,我们用 1 来替换它。想了解更多关于分词器的例子,你可以查看Google开发的分词器,或者多玩一些 OpenAI 的TikToken

Word to Vector

直觉 我们在将词表示为数字的旅程中取得了很大进展。下一步将是从这些标记生成数值的语义表示。为此,我们可以使用一种称为Word2Vec的算法。细节目前不重要,但主要思想是你拿一个向量(我们暂时简化一下,想象成一个常规列表)包含任意大小的数字(论文的作者使用了 512),这个数字列表应该代表一个词的语义含义。想象一个数字列表,例如[-2, 4, -3.7, 41…-0.98],它实际上包含了一个词的语义表示。它应该以这样的方式创建,以便如果我们将这些向量绘制在二维图上,相似的词会比不相似的词更靠近。

如你在图片中所见(图片来源于这里),“Baby”接近于“Aww”和“Asleep”,而“Citizen”/“State”/“America’s”也有些被分组在一起。

*二维词向量(即一个包含 2 个数字的列表)即使对于一个词也无法承载任何准确的意义,如前所述,作者使用了 512 个数字。由于我们无法用 512 维绘制任何内容,我们使用一种称为PCA的方法来将维度减少到两个,希望保留大部分原始意义。在本系列的第三部分中,我们会深入探讨这一过程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Word2Vec 2D 演示 — 图片由 Piere Mergret 提供,来源于这里

它有效!你实际上可以训练一个模型,使其能够生成具有语义意义的数字列表。计算机不知道婴儿是一个尖叫的、缺乏睡眠的(超级可爱)小人类,但它知道它通常会在“aww”附近看到这个婴儿词,而不是“State”和“Government”。我会详细说明这一过程,但如果你感兴趣的话,这个可能是一个不错的地方。

这些“数字列表”相当重要,因此在机器学习术语中有了它们自己的名称——嵌入(Embeddings)。为什么叫嵌入?因为我们在执行嵌入(很有创意),这是将一个术语从一种形式(词)映射(翻译)到另一种形式(数字列表)的过程。这些有很多()。

从现在开始,我们将把词汇称为嵌入,正如之前解释的,它们是包含任何词语语义意义的数字列表。

使用 Pytorch 创建嵌入

我们首先计算我们有多少个唯一的标记,为了简化起见,假设为 2。嵌入层的创建,即 Transformer 架构的第一部分,将像这样简单:

一般代码说明——不要把这段代码及其约定当作良好的编码风格,它的编写特别是为了易于理解。

代码

import torch.nn as nn

vocabulary_size = 2
num_dimensions_per_word = 2

embds = nn.Embedding(vocabulary_size, num_dimensions_per_word)

print(embds.weight)
---------------------
output:
Parameter containing:
tensor([[-1.5218, -2.5683],
        [-0.6769, -0.7848]], requires_grad=True)

我们现在有一个嵌入矩阵,在这种情况下,它是一个 2 乘 2 的矩阵,生成于从正态分布 N(0,1)(例如,均值为 0,方差为 1 的分布)中得到的随机数。

请注意 requires_grad=True,这是 Pytorch 语言,用于表示这 4 个数字是可学习的权重。它们可以并且会在学习过程中被定制,以更好地表示模型接收到的数据。

在更现实的场景中,我们可以期望接近于一个 10k 乘 512 的矩阵,它以数字的形式表示我们的整个数据集。

vocabulary_size = 10_000
num_dimensions_per_word = 512

embds = nn.Embedding(vocabulary_size, num_dimensions_per_word)

print(embds)
---------------------
output:
Embedding(10000, 512)

有趣的事实(我们可以想一些更有趣的事情),你有时会听到语言模型使用数十亿的参数。这个初步的、不过于疯狂的层,包含 10_000 乘 512 的参数,即 500 万参数。这个 LLM(大语言模型)是复杂的,需要大量的计算。

这里的参数是这些数字(-1.525 等)的花哨词汇,只不过它们会发生变化,并且在训练过程中会变化。

这些数字是机器学习的内容,这就是机器正在学习的东西。稍后,当我们给它输入时,我们将输入与这些数字相乘,并希望得到一个好的结果。你知道吗,数字很重要。当你重要时,你会得到自己的名字,所以这些不仅仅是数字,它们是参数。

为什么要用多达 512 个参数而不是 5 个?因为更多的数字意味着我们可以生成更准确的意义。很好,别再小看了,让我们用一百万个参数吧!为什么不呢?因为更多的数字意味着更多的计算,更多的计算能力,更昂贵的训练费用等等。512 被发现是一个很好的折中点。

序列长度

在训练模型时,我们将把一堆词汇放在一起。这在计算上更有效,并且帮助模型在获取更多上下文时进行学习。如前所述,每个词将由一个 512 维的向量(包含 512 个数字的列表)表示,每次我们将输入传递给模型(即前向传播),我们会发送一批句子,而不仅仅是一个。例如,我们决定支持 50 词的序列。这意味着我们将取句子中的 x 个词,如果 x > 50,我们将其拆分并仅取前 50 个;如果 x < 50,我们仍需使其大小完全相同(我会很快解释原因)。为了解决这个问题,我们添加了填充,即特殊的虚拟字符串,填充到句子的其余部分。例如,如果我们支持 7 词的句子,而句子是“Where is god”。我们添加 4 个填充,所以模型的输入将是“Where is god ”。实际上,我们通常会再添加至少 2 个特殊填充,以便模型知道句子的开始和结束,所以实际情况可能是“ Where is god ”。

  • 为什么所有输入向量必须具有相同的大小?因为软件有“期望”,矩阵有更严格的期望。你不能随意进行任何“数学”计算,它必须遵循某些规则,其中之一就是向量的适当大小。

位置编码

直觉 我们现在有了一种表示(和学习)词汇中的词的方法。让我们通过编码词语的位置来使其更好。这为何重要?因为如果我们拿这两个句子:

1. 那个人和我的猫玩耍

2. 猫和我的男人玩耍

我们可以使用完全相同的嵌入来表示这两个句子,但这些句子的意义却不同。我们可以将这些数据视为顺序无关的数据。如果我在计算某物的和,起点无关紧要。在语言中——顺序通常很重要。嵌入包含语义意义,但没有确切的顺序意义。它们以某种方式保持顺序,因为这些嵌入最初是根据某种语言逻辑创建的(婴儿出现在“睡眠”旁边,而不是“状态”旁边),但同一个词本身可以有多个意义,更重要的是,当它处于不同的上下文中时可以有不同的意义。

表示词语时仅用文本而不考虑顺序是不够的,我们可以改进这一点。作者建议我们向嵌入中添加位置编码。我们通过计算每个词的位置向量并将其与两个向量相加(求和)来实现这一点。位置编码向量必须具有相同的大小,以便它们可以相加。位置编码的公式使用了两个函数:正弦函数用于偶数位置(例如第 0 个词、第 2 个词、第 4 个、第 6 个等),余弦函数用于奇数位置(例如第 1 个、第 3 个、第 5 个等)。

可视化 通过观察这些函数(红色的正弦函数,蓝色的余弦函数),你也许可以想象为什么特别选择了这两个函数。函数之间存在一些对称性,就像单词与它之前的单词之间的对称性,这有助于模型(表示)这些相关的位置。此外,它们输出的值范围从 -1 到 1,这些数字非常稳定(不会变得过大或过小)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

公式图像来自 Vaswani, A.等人的原始论文

在上述公式中,第一行表示从 0 开始的偶数(i = 0),并继续为偶数(21,22,2*3)。第二行以相同的方式表示奇数。

每个位置向量是一个具有维度数(在我们的例子中为 512)的向量,数字范围从 0 到 1。

代码

from math import sin, cos
max_seq_len = 50 
number_of_model_dimensions = 512

positions_vector = np.zeros((max_seq_len, number_of_model_dimensions))

for position in range(max_seq_len):
    for index in range(number_of_model_dimensions//2):
        theta = position / (10000 ** ((2*index)/number_of_model_dimensions))
        positions_vector[position, 2*index ] = sin(theta)
        positions_vector[position, 2*index + 1] = cos(theta)

print(positions_vector)
---------------------
output:
(50, 512)

如果我们打印第一个单词,我们会发现只得到 0 和 1 交替出现。

print(positions_vector[0][:10])
---------------------
output:
array([0., 1., 0., 1., 0., 1., 0., 1., 0., 1.])

第二个数字已经多样化得多。

print(positions_vector[1][:10])
---------------------
output:
array([0.84147098, 0.54030231, 0.82185619, 0.56969501, 0.8019618 ,
       0.59737533, 0.78188711, 0.62342004, 0.76172041, 0.64790587])

代码灵感来自这里

我们已经看到不同的位置导致不同的表示。为了最终确定作为整体的部分输入(在下图中用红色标出),我们将位置矩阵中的数字添加到我们的输入嵌入矩阵中。最终得到的矩阵与嵌入的矩阵大小相同,只不过这次数字包含了语义意义+顺序。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图像来自 Vaswani, A.等人的原始论文

总结 这总结了我们系列的第一部分(用红色标出)。我们讨论了模型如何获取输入。我们查看了如何将文本拆分为特征(标记),将它们表示为数字(嵌入),以及将位置编码添加到这些数字中的智能方法。

下一部分将重点讨论编码器块(第一个灰色矩形)的不同机制,每个部分描述不同颜色的矩形(例如,多头注意力、添加和归一化等)。

简化变换器:使用你理解的词汇进行的前沿 NLP — 第三部分 — 注意力机制

原文:towardsdatascience.com/transformers-part-3-attention-7b95881714df?source=collection_archive---------3-----------------------#2023-08-07

深入探讨大型语言模型的核心技术 — 注意力机制

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Chen Margalit

·

关注 发表在 Towards Data Science · 14 min read · 2023 年 8 月 7 日

变换器在人工智能领域,甚至在整个世界中产生了深远的影响。这一架构由多个组件组成,但正如原始论文所称“Attention is All You Need”(注意力机制是你所需要的一切),显然注意力机制具有特别的重要性。本系列的第三部分将主要集中在注意力机制及其周边功能上,以确保变换器的各个部分能够协调运行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图像来自 原始论文,作者:Vaswani, A. 等。

注意力机制

在 Transformers 的背景下,attention 指的是一种机制,使模型在处理过程中能够专注于输入的相关部分。想象一下一个手电筒,它照射到句子的特定部分,允许模型根据上下文给予不同的重视程度。我相信例子比定义更有效,因为它们是一种脑力挑战,提供给大脑弥合空白并自行理解概念的可能性。

当面对句子“那个人拿着椅子消失了”时,你自然会对句子的不同部分赋予不同程度的重要性(例如,attention)。有些许令人惊讶的是,如果我们去掉一些特定的词,意思基本上仍然保持不变:“man took chair disappeared。”虽然这个版本是断裂的英语,但相比于原句,你仍然可以理解信息的精髓。有趣的是,三个词(“The”,“the”,和“and”)占了句子中 43%的词汇,但对整体意义贡献不大。这一观察对每个遇到我惊人德语的柏林人来说可能是显而易见的(你可以选择学习德语或保持快乐,这取决于你自己的决定),但对 ML 模型来说却不那么明显。

过去,以前的架构如 RNN(递归神经网络)面临一个重大挑战:它们在“记住”出现在输入序列远端的词汇时存在困难,通常是超过 20 个词。如你所知,这些模型本质上依赖数学运算来处理数据。不幸的是,早期架构中使用的数学运算效率不足,无法将词汇表示充分地传递到序列的遥远未来。

这种长期依赖的限制阻碍了 RNN 在较长时间内保持上下文信息的能力,影响了语言翻译或情感分析等任务,其中理解整个输入序列至关重要。然而,Transformers 通过其 attention 机制和自注意力机制,更有效地解决了这个问题。它们可以有效地捕捉输入中长距离的依赖关系,使模型能够保留上下文和关联,即使是对于在序列中出现较早的词汇。因此,Transformers 成为克服以前架构限制的突破性解决方案,并显著提高了各种自然语言处理任务的性能。

要创建像我们今天遇到的先进聊天机器人这样的卓越产品,至关重要的是使模型具备区分高价值词汇和低价值词汇的能力,并在输入的长距离中保留上下文信息。Transformers 架构中引入的机制来应对这些挑战被称为 attention

人类长期以来一直在开发区分人的技术,尽管这些技术很有启发性,但我们在这里不会使用它们。

点积

模型如何从理论上辨别不同单词的重要性?在分析句子时,我们的目标是识别彼此关系更强的单词。由于单词被表示为向量(数字),我们需要一个测量数字相似性的标准。测量向量相似性的数学术语是“点积”。它涉及两个向量的元素相乘,产生一个标量值(例如 2、16、-4.43),作为它们相似性的表示。机器学习建立在各种数学操作之上,其中点积具有特别重要的意义。因此,我将花时间详细阐述这个概念。

直观 想象一下我们有 5 个单词的真实表示(嵌入):“florida”、“california”、“texas”、“politics”和“truth”。由于嵌入只是数字,我们可以将它们绘制在图表上。然而,由于它们的高维度(用于表示单词的数字数量),可能从 100 到 1000 不等,我们不能像现在这样绘制它们。我们无法在二维计算机/手机屏幕上绘制一个 100 维的向量。此外,人脑很难理解超过 3 维的事物。四维向量是什么样的?我不知道。

为了克服这个问题,我们使用主成分分析(PCA),这是一种减少维度的技术。通过应用 PCA,我们可以将嵌入投影到二维空间(x,y 坐标)。这种维度减少帮助我们在图表上可视化数据。尽管由于减少维度我们会丢失一些信息,但希望这些减少的向量仍能保留足够的相似性,使我们能够洞察并理解单词之间的关系。

这些数字基于 GloVe 嵌入。

florida = [-2.40062016,  0.00478901]
california = [-2.54245794, -0.37579669]
texas = [-2.24764634, -0.12963368]
politics = [3.02004564,  2.88826688]
truth = [4.17067881, -2.38762552]

你可能会注意到这些数字中有一些模式,但我们将绘制这些数字以简化处理。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5 个二维向量

在这个可视化中,我们看到五个二维向量(x,y 坐标),代表 5 个不同的单词。正如你所见,图示表明一些单词彼此之间的关系要比其他单词更加紧密。

数学 视觉化向量的数学对应物可以通过一个简单的方程来表达。如果你不特别喜欢数学,并且记得作者对 Transformers 架构的描述是一个“简单的网络架构”,你可能会觉得这就是机器学习者的状态,他们变得奇怪。这可能是真的,但在这种情况下,这确实很简单。我会解释:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

点积公式

符号 ||a|| 表示向量“a”的大小,代表了原点(点 0,0)和向量尖端之间的距离。计算大小的公式如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

向量大小公式

这个计算的结果是一个数字,例如 4 或 12.4。

Theta (θ) 指的是向量之间的角度(参见可视化)。θ的余弦,表示为 cos(θ),是将余弦函数应用于该角度的结果。

代码 使用GloVe算法,斯坦福大学的研究人员生成了实际单词的嵌入,正如我们之前讨论的那样。尽管他们有自己创建这些嵌入的具体技术,但基本概念与我们在系列的上一部分中讨论的相同。作为一个例子,我选择了 4 个单词,将它们的维度减少到 2,然后将它们的向量绘制为简单的 x 和 y 坐标。

为了使这个过程正确运行,下载GloVe词嵌入是一个必要的前提。

*部分代码,特别是第一个框,受到我见过的某些代码的启发,但我找不到来源。

import pandas as pd

path_to_glove_embds = 'glove.6B.100d.txt'

glove = pd.read_csv(path_to_glove_embds, sep=" ", header=None, index_col=0)
glove_embedding = {key: val.values for key, val in glove.T.items()}
words = ['florida', 'california', 'texas', 'politics', 'truth']
word_embeddings = [glove_embedding[word] for word in words]

print(word_embeddings[0]).shape # 100 numbers to represent each word.
---------------------
output:
(100,)
pca = PCA(n_components=2) # reduce dimensionality from 100 to 2.
word_embeddings_pca = pca.fit_transform(word_embeddings)
for i in range(5):
    print(word_embeddings_pca[i])

---------------------
output:
[-2.40062016  0.00478901] # florida
[-2.54245794 -0.37579669] # california
[-2.24764634 -0.12963368] # texas
[3.02004564 2.88826688] # politics
[ 4.17067881 -2.38762552] # truth

我们现在拥有了 5 个单词的真实表示。我们的下一步是进行点积计算。

向量的大小:

import numpy as np

florida_vector = [-2.40062016,  0.00478901]
florida_vector_magnitude = np.linalg.norm(florida_vector)

print(florida_vector_magnitude)
---------------------
output:
2.4006249368060817 # The magnitude of the vector "florida" is 2.4.

两个相似向量之间的点积。

import numpy as np

florida_vector = [-2.40062016,  0.00478901]
texas_vector = [-2.24764634 -0.12963368]

print(np.dot(florida_vector, texas_vector))

---------------------
output:
5.395124299364358

两个不相似向量之间的点积。

import numpy as np

florida_vector = [-2.40062016,  0.00478901]
truth_vector = [4.17067881, -2.38762552]

print(np.dot(florida_vector, truth_vector))

---------------------
output:
-10.023649994662344

从点积计算可以看出,它似乎捕捉并反映了不同概念之间的相似性。

缩放点积注意力

直觉 现在我们已经掌握了点积,我们可以重新深入关注。特别是自注意力机制。使用自注意力使模型能够确定每个单词的重要性,无论它与当前单词的“物理”接近程度如何。这使得模型能够根据每个单词的上下文相关性做出明智的决策,从而达到更好的理解。

为了实现这个宏大的目标,我们创建了 3 个由可学习的 (!) 参数组成的矩阵,称为查询矩阵、键矩阵和值矩阵(Q, K, V)。查询矩阵可以被看作是包含用户询问或请求的单词的矩阵(例如,当你问 chatGPT:“god is available today at 5 p.m.?” 这就是查询)。键矩阵包含了序列中的所有其他单词。通过计算这些矩阵之间的点积,我们可以得到每个单词与当前正在检查的单词(例如,翻译或生成查询答案)之间的相关度。

值矩阵提供了序列中每个词的“干净”表示。为什么我称其为干净,而其他两个矩阵以类似方式形成?因为值矩阵保持原始形式,我们不在另一个矩阵乘法后使用它,也不通过某些值对其进行归一化。这一区别使值矩阵独特,确保它保持原始嵌入,免于额外的计算或转换。*

所有 3 个矩阵的大小都为 word_embedding(512)。然而,它们被划分为“头”。在论文中,作者使用了 8 个头,导致每个矩阵的大小为 sequence_length 乘以 64。你可能会好奇,为什么同样的操作要对 1/8 的数据进行 8 次,而不是对所有数据进行一次。这种方法的理论依据是,通过用 8 个不同的权重集(如前所述,可学习的)进行 8 次相同的操作,我们可以利用数据中的固有多样性。每个头可以关注输入中的特定方面,总体上,这可以带来更好的性能。*

在大多数实现中,我们实际上并不会将主矩阵划分为 8 个部分。分割是通过索引实现的,从而允许每个部分进行并行处理。然而,这些只是实现细节。从理论上讲,我们也可以使用 8 个矩阵来实现相同的功能。

Q 和 K 被相乘(点积),然后通过维度数量的平方根进行归一化。我们将结果通过Softmax函数,然后将结果乘以矩阵 V。*

正常化结果的原因是 Q 和 K 是随机生成的矩阵。它们的维度可能完全不相关(独立),独立矩阵之间的乘法可能会产生非常大的数字,这可能会对学习产生不利影响,稍后我会在这一部分中解释。*

然后我们使用名为Softmax的非线性变换,将所有数字的范围调整到 0 到 1 之间,并使其总和为 1。结果类似于概率分布(因为数字从 0 到 1 相加为 1)。这些数字体现了序列中每个词与其他词的相关性。*

最后,我们将结果乘以矩阵 V,结果就是自注意力得分。*

编码器实际上由 N(在论文中,N=6)个相同的层构建而成,每一层都从上一层接收输入并进行相同的操作。最终一层将数据传递给解码器(我们将在本系列的后面部分讨论)以及编码器的上层。

这是自注意力的可视化。它就像课堂上的朋友小组。有些人与某些人联系更紧密,而有些人则与任何人都没有很好地联系。*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来自 Vaswani 等人的原始论文

数学 Q、K 和 V 矩阵是通过对嵌入矩阵进行线性变换得到的。线性变换在机器学习中非常重要,如果你有兴趣成为一名 ML 从业者,我建议你进一步探索这些内容。我不会深入探讨,但我会说线性变换是一种将向量(或矩阵)从一个空间移动到另一个空间的数学操作。这听起来比实际情况要复杂。想象一下一个箭头指向一个方向,然后转到右边 30 度。这就是线性变换的示例。这样的操作有几个条件才被认为是线性的,但目前这些不太重要。关键是它保留了许多原始向量的属性。

自注意力层的整个计算是通过应用以下公式来执行的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

缩放点积注意力 — 图片来自 Vaswani 等人的原始论文

计算过程如下:

  1. 我们将 Q 乘以转置的 K(翻转后的)。

  2. 我们将结果除以矩阵 K 维度的平方根。

  3. 我们现在有了描述每个单词与其他每个单词相似度的“注意力矩阵分数”。我们将每一行传递给Softmax(一种非线性)变换。Softmax 做了三件有趣的相关事情:

a. 它将所有数字缩放到 0 和 1 之间。

b. 它使所有数字的总和为 1。

c. 它强调了差距,使得稍微重要的变得更加重要。因此,我们现在可以轻松区分模型感知单词 x1 与 x2、x3、x4 等之间的连接程度。

  1. 我们将分数乘以 V 矩阵。这是自注意力操作的最终结果。

掩码

本系列的前一章中,我解释了我们如何使用虚拟标记来处理句子中的特殊情况,例如句子的第一个词、最后一个词等。其中一个标记,表示为,指示没有实际数据,但我们需要在整个过程中保持矩阵大小的一致性。为了确保模型理解这些是虚拟标记,因此在自注意力计算过程中不应考虑这些标记,我们将这些标记表示为负无穷(例如一个非常大的负数,例如-153513871339)。掩码值会被加到 Q 和 K 的乘法结果上。Softmax然后将这些数字转换为 0。这使我们能够在注意力机制中有效地忽略虚拟标记,同时保持计算的完整性。

Dropout

在自注意力层之后,应用了 dropout 操作。Dropout 是一种在机器学习中广泛使用的正则化技术。正则化的目的是在训练过程中对模型施加约束,使其更难过度依赖特定的输入细节。因此,模型学习得更为稳健,并提高了其泛化能力。实际的实现涉及随机选择一些激活(来自不同层的数字),并将它们置为零。在同一层的每次传递中,不同的激活将被置为零,防止模型找到特定于数据的解决方案。从本质上讲,dropout 有助于增强模型处理多样化输入的能力,并使模型更难以适应数据中的特定模式。

Skip connection

另一项在 Transformer 架构中执行的重要操作称为 Skip Connection。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图像来源于Vaswani 等人的原始论文

Skip Connection 是一种传递输入而不进行任何变换的方式。举例来说,想象一下我向我的经理汇报,经理再向他的上司汇报。即使抱着使报告更有用的纯粹意图,当输入经过另一位人(或 ML 层)处理时,输入也会经历一些修改。在这个类比中,Skip-Connection 就是我直接向我的经理的上司汇报。因此,上级经理既接收到经过我的经理处理的数据(处理过的数据)直接来自我(未经处理的数据)。高级经理可以因此做出更好的决策。使用跳跃连接的理由是解决潜在问题,例如消失的梯度,我将在下一节中解释。

Add & Norm Layer

直觉

“Add & Norm”层执行加法和标准化。我将从加法开始,因为它较为简单。基本上,我们将自注意力层的输出加到原始输入上(通过跳跃连接接收到的)。这个加法是逐元素的(每个数值加到其对应位置的数值)。结果然后进行标准化。

我们进行标准化的原因是,每层进行大量计算。多次相乘的数值可能导致意外情况。例如,如果我取一个分数,如 0.3,然后将其与另一个分数,如 0.9 相乘,我得到 0.27,比开始时的数值要小。如果我多次这样做,最终可能会得到非常接近 0 的数值。这可能导致深度学习中的一个问题,称为梯度消失。

我现在不会深入探讨,以免这篇文章阅读起来很耗时间,但基本思想是,如果数值接近 0,模型将无法有效学习。现代机器学习的基础是计算梯度并使用这些梯度(以及其他一些要素)来调整权重。如果这些梯度接近 0,模型将很难有效学习。

相反,另一种现象叫做梯度爆炸,当非分数的数值被非分数的数值相乘时,会导致值变得极其大。结果,模型在学习过程中由于权重和激活值的巨大变化面临困难,这可能导致训练过程中的不稳定和发散。

机器学习模型有点像小孩子,需要保护。保护这些模型免受数值过大或过小影响的一种方法是标准化。

数学

层标准化操作看起来令人害怕(像往常一样),但实际上相对简单。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Pytorch 提供,来自 这里

在层标准化操作中,我们对每个输入执行以下简单步骤:

  1. 从输入中减去其均值。

  2. 除以方差的平方根,并加上一个 epsilon(一个很小的数值),用于避免除以零。

  3. 将结果得分乘以一个可学习的参数,称为 gamma (γ)。

  4. 添加另一个可学习的参数,称为 beta (β)。

这些步骤确保均值接近 0,标准差接近 1。标准化过程增强了训练的稳定性、速度和整体性能。

代码

# x being the input.

(x - mean(x)) / sqrt(variance(x) + epsilon) * gamma + beta

总结:

到目前为止,我们对编码器的主要内部工作有了坚实的理解。此外,我们还探讨了跳跃连接,这是一种纯技术(且重要)的机器学习技术,可以提高模型的学习能力。

虽然这一部分有些复杂,但你已经对 Transformers 架构有了相当深入的理解。随着系列的深入,这种理解将帮助你掌握剩下的部分。

记住,这在复杂领域中代表了最前沿技术。这不是简单的内容。即使你还不能完全理解所有内容,也为取得这段伟大进步而感到自豪!

下一部分将讨论机器学习中的一个基础(且更简单的)概念——前馈神经网络。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源于 Vaswani, A.等人的原始论文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值