精通 Transformers(三)(2)

本文涉及的产品
NLP自然语言处理_基础版,每接口每天50万次
NLP自然语言处理_高级版,每接口累计50万次
NLP 自学习平台,3个模型定制额度 1个月
简介: 精通 Transformers(三)

精通 Transformers(三)(1)https://developer.aliyun.com/article/1510705

使用 Sentence-BERT 进行文本聚类

对于聚类算法,我们将需要一个适用于文本相似度的模型。让我们在这里使用paraphrase-distilroberta-base-v1模型进行变化。我们将首先加载亚马逊极性数据集进行我们的聚类实验。该数据集包括了从 18 年前到 2013 年 3 月期间的亚马逊网页评论。原始数据集包含超过 3500 万条评论。这些评论包括产品信息、用户信息、用户评分和用户评论。让我们开始吧:

  1. 首先,通过洗牌随机选择 10K 条评论,如下所示:
import pandas as pd, numpy as np
import torch, os, scipy
from datasets import load_dataset
dataset = load_dataset("amazon_polarity",split="train")
corpus=dataset.shuffle(seed=42)[:10000]['content']
  1. 现在语料库已准备好进行聚类。以下代码示例实例化了一个使用预训练的paraphrase-distilroberta-base-v1模型的句子转换器对象:
from sentence_transformers import SentenceTransformer
model_path="paraphrase-distilroberta-base-v1"
model = SentenceTransformer(model_path)
  1. 使用以下执行对整个语料库进行编码,其中模型将一系列句子映射到一系列嵌入向量:
>>> corpus_embeddings = model.encode(corpus)
>>> corpus_embeddings.shape
(10000, 768)
  1. 这里,向量大小为768,这是 BERT-base 模型的默认嵌入大小。从现在开始,我们将继续使用传统的聚类方法。我们选择Kmeans,因为它是一种快速且广泛使用的聚类算法。我们只需要将聚类数目(K)设置为5。实际上,这个数目可能不是最优的。有许多技术可以确定最优的聚类数目,比如肘部法或轮廓法。然而,让我们把这些问题放在一边。以下是执行过程:
>>> from sklearn.cluster import KMeans
>>> K=5
>>> kmeans = KMeans(
           n_clusters=5,
           random_state=0).fit(corpus_embeddings)
>>> cls_dist=pd.Series(kmeans.labels_).value_counts()
>>> cls_dist
3 2772 
4 2089 
0 1911 
2 1883 
1 1345 
  1. 在这里,我们得到了五个评论的聚类。从输出中我们可以看到,我们有相当均匀的聚类。聚类的另一个问题是我们需要了解这些聚类意味着什么。作为一个建议,我们可以对每个聚类应用主题分析或检查基于聚类的 TF-IDF 来了解内容。现在,让我们基于聚类中心来看另一种做法。Kmeans 算法计算聚类中心,称为质心,它们保存在kmeans.cluster_centers_属性中。质心只是每个聚类中向量的平均值。因此,它们都是虚拟点,不是现有的数据点。让我们假设距离质心最近的句子将是相应聚类的最具代表性的例子。
  2. 让我们尝试找到每个中心点最接近的一个真实句子嵌入。如果你愿意,你可以捕捉多于一个句子。以下是代码:
distances = \
scipy.spatial.distance.cdist(kmeans.cluster_centers_, corpus_embeddings)
centers={}
print("Cluster", "Size", "Center-idx", 
                      "Center-Example", sep="\t\t")
for i,d in enumerate(distances):
    ind = np.argsort(d, axis=0)[0]
    centers[i]=ind
    print(i,cls_dist[i], ind, corpus[ind] ,sep="\t\t")
  1. 输出如下:

    图 7.7 – 聚类中心
    通过这些代表性句子,我们可以推断出聚类。看起来 Kmeans 将评论聚类成了五个不同的类别:电子产品音频 CD/音乐DVD 电影书籍家具与家居。现在,让我们在 2D 空间中可视化句子点和聚类中心。我们将使用均匀流形近似和投影UMAP)库来降低维度。你还可以使用其他广泛使用的 NLP 降维技术,如 t-SNE 和 PCA(参见第一章从词袋模型到Transformers)。
  2. 我们需要安装umap库,如下所示:
!pip install umap-learn
  1. 以下执行将所有嵌入都降维并映射到 2D 空间:
import matplotlib.pyplot as plt
import umap
X = umap.UMAP(
           n_components=2,
           min_dist=0.0).fit_transform(corpus_embeddings)
labels= kmeans.labels_fig, ax = plt.subplots(figsize=(12,
8))
plt.scatter(X[:,0], X[:,1], c=labels, s=1, cmap='Paired')
for c in centers:
    plt.text(X[centers[c],0], X[centers[c], 1],"CLS-"+ str(c), fontsize=18) 
    plt.colorbar()
  1. 输出如下:

图 7.8 – 聚类点可视化

在前面的输出中,点根据其聚类成员资格和质心的颜色进行了着色。看起来我们选择了正确的聚类数目。

为了捕捉主题并解释聚类,我们简单地将句子(每个聚类一个句子)定位在聚类的中心附近。现在,让我们用主题建模的更准确方法来捕捉主题。

使用 BERTopic 进行主题建模

您可能熟悉许多用于从文档中提取主题的无监督主题建模技术;潜在狄利克雷分配 (LDA) 主题建模和非负矩阵分解 (NMF) 是文献中广泛应用的传统技术。BERTopic 和 Top2Vec 是两个重要的基于转换器的主题建模项目。在本节中,我们将把 BERTopic 模型应用到我们的亚马逊语料库中。它利用了 BERT 嵌入和基于类的 TF-IDF 方法,以获取易于解释的主题。

首先,BERTopic 模型通过使用句子转换器或任何句子嵌入模型对句子进行编码,然后进行聚类步骤。聚类步骤分为两个阶段:首先通过UMAP减少了嵌入的维度,然后通过Hierarchical Density-Based Spatial Clustering of Applications with Noise (HDBSCAN)对减少的向量进行了聚类,从而得到了相似文档的分组。在最后阶段,通过每个聚类的 TF-IDF 来捕获主题,模型提取了每个聚类的最重要单词,而不是每个文档,并为每个聚类获取主题的描述。让我们开始吧:

  1. 首先,让我们安装必要的库,如下所示:
!pip install bertopic
  1. 重要提示
    您可能需要重新启动运行时,因为此安装将更新一些已加载的包。因此,从 Jupyter 笔记本中,转到运行时 | 重新启动运行时
  2. 如果您想使用自己的嵌入模型,您需要实例化并将其传递给 BERTopic 模型。我们将实例化一个句子转换器模型,并将其传递给 BERTopic 的构造函数,如下所示:
from bertopic import BERTopic
sentence_model = SentenceTransformer(
                  "paraphrase-distilroberta-base-v1")
topic_model = BERTopic(embedding_model=sentence_model)
     topics, _ = topic_model.fit_transform(corpus)
     topic_model.get_topic_info()[:6]
  1. 输出如下:

图 7.9 – BERTopic 结果

请注意,使用相同参数的不同 BERTopic 运行可能会产生不同的结果,因为 UMAP 模型是随机的。现在,让我们看看第五个主题的词分布,如下所示:

topic_model.get_topic(5)

输出如下:

图 7.10 – 主题模型的第五个主题词

主题词是那些在语义空间中与主题向量接近的词语。在这个实验中,我们没有对语料库进行聚类,而是将这种技术应用到整个语料库中。在我们之前的例子中,我们分析了最接近的句子的聚类。现在,我们可以通过将主题模型分别应用到每个聚类中来找到主题。这非常简单,您可以自己运行它。

有关更多详细信息和有趣的主题建模应用,请参阅 Top2Vec 项目github.com/ddangelov/Top2Vec

使用 Sentence-BERT 进行语义搜索

我们可能已经熟悉基于关键字的搜索(布尔模型),在这种模型中,对于给定的关键字或模式,我们可以检索与模式匹配的结果。或者,我们可以使用正则表达式,我们可以定义高级模式,例如词法-句法模式。这些传统方法无法处理同义词(例如,carautomobile 相同)或词义问题(例如,bank 是指河流的一侧还是金融机构)。虽然第一个同义词情况由于漏掉了不应错过的文件而导致低召回率,但第二个情况由于捕获了不应捕获的文件而导致低精度。基于向量或语义的搜索方法可以通过构建查询和文档的密集数值表示来克服这些缺点。

让我们为常见问题FAQs)建立一个案例研究,这些问题在网站上处于闲置状态。我们将利用语义搜索问题内的常见问题资源。常见问题包含经常被问到的问题。我们将使用来自世界自然基金会WWF)的常见问题,这是一个自然非政府组织(www.wwf.org.uk/)。

给定这些描述,很容易理解使用语义模型执行语义搜索与一次性学习问题非常相似,其中我们只有一个类别的一次性学习(一个样本),并且我们希望根据它重新排序其余的数据(句子)。您可以重新定义问题,即根据给定样本搜索与给定样本在语义上接近的样本,或者根据样本进行二元分类。您的模型可以提供相似度度量,并且将使用此度量对所有其他样本进行重新排序。最终的有序列表是搜索结果,根据语义表示和相似度度量进行重新排序。

WWF 在他们的网页上有 18 个问题和答案。我们将它们定义为 Python 列表对象,称为 wf_faq,用于此实验:

  • 我还没有收到我的领养包裹。我该怎么办?
  • 我将多快收到我的领养包裹?
  • 我该如何续订我的领养?
  • 我该如何更改我的地址或其他联系方式?
  • 如果我不住在英国,我可以领养一只动物吗?
  • 如果我领养了一只动物,我会是唯一领养该动物的人吗?
  • 我的包裹中没有包含证书?
  • 我的领养是一份礼物,但不会准时送达。我该怎么办?
  • 我可以用一次性付款支付领养费用吗?
  • 在下订单后,我可以更改领养包裹的交付地址吗?
  • 我的领养将持续多久?
  • 我将多久收到有关我所领养动物的更新?
  • 你们有哪些动物可以领养?
  • 如何查找更多关于我所领养动物的信息?
  • 我的领养费用如何使用?
  • 你们的退款政策是什么?
  • 我的直接付款出现了错误;我可以收到退款吗?
  • 我该如何更改你们与我联系的方式?

用户可以自由提出任何问题。我们需要评估 FAQ 中哪个问题与用户的问题最相似,这是quora-distilbert-base模型的目标。SBERT hub 中有两个选项 - 一个用于英语,另一个用于多语言,如下所示:

  • quora-distilbert-base:这是为 Quora 重复问题检测检索而微调的。
  • quora-distilbert-multilingual:这是quora-distilbert-base的多语言版本。它使用 50 多种语言的平行数据进行微调。

让我们按照以下步骤构建一个语义搜索模型:

  1. 以下是 SBERT 模型的实例化:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('quora-distilbert-base')
  1. 让我们对 FAQ 进行编码,如下所示:
faq_embeddings = model.encode(wwf_faq)
  1. 让我们准备五个问题,使它们分别与常见问题解答中的前五个问题相似;也就是说,我们的第一个测试问题应该与常见问题解答中的第一个问题相似,第二个问题应该与第二个问题相似,依此类推,这样我们就可以轻松地跟踪结果。让我们将问题定义在test_questions列表对象中并对其进行编码,如下所示:
test_questions=["What should be done, if the adoption pack did not reach to me?",
" How fast is my adoption pack delivered to me?",
"What should I do to renew my adoption?",
"What should be done to change address and contact details ?",
"I live outside of the UK, Can I still adopt an animal?"]
test_q_emb= model.encode(test_questions)
  1. 以下代码测量每个测试问题与常见问题解答中每个问题之间的相似度,然后对它们进行排名:
from scipy.spatial.distance import cdist
for q, qe in zip(test_questions, test_q_emb):
    distances = cdist([qe], faq_embeddings, "cosine")[0]
    ind = np.argsort(distances, axis=0)[:3]
    print("\n Test Question: \n "+q)
    for i,(dis,text) in enumerate(
                           zip(
                           distances[ind],
                           [wwf_faq[i] for i in ind])):
        print(dis,ind[i],text, sep="\t")
  1. 输出如下所示:

    图 7.11 – 问题-问题相似度
    在这里,我们可以看到按顺序的索引01234,这意味着模型成功地找到了预期的相似问题。
  2. 对于部署,我们可以设计以下getBest()函数,它接受一个问题并返回 FAQ 中最相似的K个问题:
def get_best(query, K=5):
    query_emb = model.encode([query])
    distances = cdist(query_emb,faq_embeddings,"cosine")[0]
    ind = np.argsort(distances, axis=0)
    print("\n"+query)
    for c,i in list(zip(distances[ind], ind))[:K]:
        print(c,wwf_faq[i], sep="\t")
  1. 让我们提出一个问题:
get_best("How do I change my contact info?",3)
  1. 输出如下所示:

    图 7.12 – 相似问题的相似度结果
  2. 如果输入的问题与常见问题解答中的问题不相似怎么办?以下是这样一个问题:
get_best("How do I get my plane ticket \
    if I bought it online?")
  1. 输出如下所示:

图 7.13 – 不相似问题的相似度结果

最佳不相似度分数为 0.35。因此,我们需要定义一个阈值,例如 0.3,以便模型忽略高于该阈值的问题,并显示未找到相似答案

除了问题-问题对称搜索相似度之外,我们还可以利用 SBERT 的问题-答案不对称搜索模型,例如msmarco-distilbert-base-v3,它是在大约 50 万个 Bing 搜索查询数据集上训练的。它被称为 MSMARCO Passage Ranking。该模型帮助我们衡量问题和上下文的相关性,并检查答案是否在段落中。

总结

在本章中,我们学习了关于文本表示方法的知识。我们学习了如何使用不同和多样的语义模型执行零/少/一次学习等任务。我们还学习了关于 NLI 及其在捕获文本语义方面的重要性。此外,我们还研究了一些有用的用例,如基于 Transformer 的语义模型进行语义搜索、语义聚类和主题建模。我们学习了如何可视化聚类结果,并了解了在此类问题中中心点的重要性。

在下一章中,您将学习关于高效 Transformer 模型的知识。您将学习有关蒸馏、修剪和量化基于 Transformer 的模型的知识。您还将学习有关不同和高效的 Transformer 架构,这些架构改进了计算和内存效率,并学习如何在 NLP 问题中使用它们。

进一步阅读

请参阅本章中涉及的以下作品/论文,以获取有关涉及主题的更多信息:

  • Lewis, M., Liu, Y., Goyal, N., Ghazvininejad, M., Mohamed, A., Levy, O., … & Zettlemoyer, L. (2019). Bart:用于自然语言生成、翻译和理解的去噪序列到序列预训练。arXiv 预印本 arXiv:1910.13461。
  • Pushp, P. K., & Srivastava, M. M. (2017). 一次训练,随处测试:用于文本分类的零样本学习。arXiv 预印本 arXiv:1712.05972。
  • Reimers, N., & Gurevych, I. (2019). Sentence-bert:使用孪生 BERT 网络的句子嵌入。arXiv 预印本 arXiv:1908.10084。
  • Liu, Y., Ott, M., Goyal, N., Du, J., Joshi, M., Chen, D., … & Stoyanov, V. (2019). Roberta:一个鲁棒优化的 BERT 预训练方法。arXiv 预印本 arXiv:1907.11692。
  • Williams, A., Nangia, N., & Bowman, S. R. (2017). 用于通过推理理解句子的广覆盖挑战语料库。arXiv 预印本 arXiv:1704.05426。
  • Cer, D., Yang, Y., Kong, S. Y., Hua, N., Limtiaco, N., John, R. S., … & Kurzweil, R. (2018). 通用句子编码器。arXiv 预印本 arXiv:1803.11175。
  • Yang, Y., Cer, D., Ahmad, A., Guo, M., Law, J., Constant, N., … & Kurzweil, R. (2019). 用于语义检索的多语言通用句子编码器。arXiv 预印本 arXiv:1907.04307。
  • Humeau, S., Shuster, K., Lachaux, M. A., & Weston, J. (2019). Poly-encoders:用于快速和准确的多句子评分的 Transformer 架构和预训练策略。arXiv 预印本 arXiv:1905.01969。

第三部分:高级主题

完成本节后,您将获得如何在有限的计算能力下训练长上下文 NLP 任务等具有挑战性的问题的高效模型的经验,以及如何处理多语言和跨语言语言建模。您将了解监控模型内部部分以进行解释和解释,并跟踪您的模型训练性能所需的工具。您还将能够在真实生产环境中提供模型服务。

本节包括以下章节:

  • 第八章, 使用高效 Transformer
  • 第九章, 跨语言和多语言语言建模
  • 第十章, 为 Transformer 模型提供服务
  • 第十一章, 注意力可视化和实验跟踪

第八章:使用高效的Transformers

到目前为止,您已经学会了如何设计自然语言处理NLP)架构,以实现使用Transformers的成功任务执行。在本章中,您将学习如何使用蒸馏、修剪和量化将训练模型变为高效的模型。其次,您还将了解到关于高效稀疏Transformers的知识,如 Linformer、BigBird、Performer 等。您将看到它们在各种基准测试中的表现,例如内存与序列长度和速度与序列长度的比较。您还将看到模型尺寸缩减的实际用途。

随着在有限的计算能力下运行大型神经模型变得困难,本章的重要性凸显出来。拥有像 DistilBERT 这样的轻量级通用语言模型是很重要的。然后可以像其未蒸馏的对应物一样对其进行微调以获得良好的性能。基于Transformers的架构由于Transformers中注意力点积的二次复杂度而面临复杂性瓶颈,尤其是对于长上下文 NLP 任务。字符级语言模型、语音处理和长文档是长上下文问题的一部分。近年来,我们已经看到了使自注意更加高效的许多进展,例如 Reformer、Performer 和 BigBird。

简而言之,在本章中,您将学习以下主题:

  • 介绍高效、轻量和快速的Transformers
  • 用于模型尺寸缩减的实施
  • 使用高效的自注意力

技术要求

我们将使用 Jupyter Notebook 运行我们的编码练习,需要 Python 3.6+,并且需要安装以下软件包:

  • TensorFlow
  • PyTorch
  • Transformers >=4.00
  • 数据集
  • 句子Transformers
  • py3nvml

所有包含编码练习的笔记本都可以在以下 GitHub 链接中找到:

github.com/PacktPublishing/Mastering-Transformers/tree/main/CH08

查看以下链接以查看代码演示视频:

bit.ly/3y5j9oZ

介绍高效、轻量和快速的Transformers

基于Transformers的模型在许多 NLP 问题上已经明显取得了最先进的结果,但代价是二次内存和计算复杂度。我们可以总结如下有关复杂性的问题:

  • 由于自注意机制的存在,模型无法有效地处理长序列,这是因为其随序列长度呈二次方增长。
  • 通过使用典型 GPU 进行的实验设置,可以处理包含 512 个标记的句子进行训练和推断。然而,较长的条目可能会引起问题。
  • NLP 模型从 BERT-base 的 1.1 亿参数增长到 Turing-NLG 的 170 亿参数,再到 GPT-3 的 1750 亿参数。这一概念引起了人们对计算和内存复杂性的担忧。
  • 我们还需要关注成本、生产、可复制性和可持续性。因此,我们需要更快、更轻的转换器,特别是在边缘设备上。

已经提出了几种方法来减少计算复杂性和内存占用。其中一些方法侧重于改变体系结构,而其他一些方法不改变原始体系结构,而是改进已训练模型或训练阶段。我们将它们分为两组,模型大小缩减和高效自注意力。

可以使用三种不同的方法来实现模型大小缩减:

  • 知识蒸馏
  • 剪枝
  • 量化

这三个方法都有各自的方式来缩小模型的大小,我们将在 模型大小缩减的实现 部分进行简要描述。

在知识蒸馏中,较小的转换器(学生)可以传输大型模型(教师)的知识。我们训练学生模型,使其能够模仿教师的行为或对相同的输入产生相同的输出。蒸馏模型可能性能不如教师模型。压缩、速度和性能之间存在权衡。

剪枝是机器学习中用于通过删除对产生结果的贡献较小的模型部分来减小模型尺寸的模型压缩技术。最典型的例子是决策树剪枝,它有助于减少模型复杂性并增加模型的泛化能力。量化将模型权重类型从较高分辨率更改为较低分辨率。例如,我们使用典型的浮点数(float64),每个权重占用 64 位内存。相反,我们可以在量化中使用 int8,每个权重占用 8 位内存,并且在表示数字时自然精度较低。

自注意力头不适用于长序列。为了解决这个问题,已经提出了许多不同的方法。最有效的方法是自注意力稀疏化,我们将很快讨论。另一个最常用的方法是内存高效的反向传播。该方法在缓存中间结果和重新计算之间平衡了权衡。在正向传播期间计算的中间激活在反向传播期间需要用来计算梯度。梯度检查点可以减少内存占用和计算量。另一种方法是管道并行算法。小批量被分成微批量,并行管道利用了正向和反向操作期间的等待时间,同时将批量传输到 GPU 或 TPU 等深度学习加速器中。

参数共享可以被看作是朝向高效深度学习的第一种方法之一。 最典型的例子是循环神经网络(RNN),如第一章中所述,从词袋模型到 Transformer,在展开表示的单元中使用了共享参数。 因此,可训练参数的数量不受输入大小的影响。 一些共享的参数,也称为权重绑定或权重复制,会扩展网络,以减少可训练参数的数量。 例如,Linformer 在头和层之间共享投影矩阵。 Reformer 通过牺牲性能来共享查询和键。

现在让我们试着用相应的实际例子来理解这些问题。

模型大小缩减的实现

尽管基于 Transformer 的模型在 NLP 许多方面取得了最先进的成果,它们通常共享同样的问题:它们是庞大的模型,速度不够快无法使用。 在业务案例中,如果需要将它们嵌入到移动应用程序或 Web 界面中,如果尝试使用原始模型,似乎是不可能的。

为了提高这些模型的速度和大小,提出了一些技术,列举如下:

  • 蒸馏(又称为知识蒸馏)
  • 精简
  • 量化

对于每种技术,我们提供一个单独的小节来解决技术和理论洞察。

使用 DistilBERT 进行知识蒸馏

从一个更大的模型向一个更小的模型转移知识的过程称为知识蒸馏。 换句话说,有一个老师模型和一个学生模型;老师通常是一个更大更强大的模型,而学生则更小更弱。

这种技术被用于各种问题,从视觉到声学模型和自然语言处理。 这种技术的典型实现如图 8.1所示:

图 8.1 - 用于图像分类的知识蒸馏

DistilBERT 是这一领域最重要的模型之一,引起了研究者甚至企业的关注。 这个模型试图模仿 BERT-Base 的行为,参数少了 50%,但实现了老师模型 95%的性能。

细节如下:

  • DistilBert 压缩了 1.7 倍,速度提高了 1.6 倍,相对性能提高了 97%(与原始 BERT 相比)。
  • Mini-BERT 压缩了 6 倍,速度提高了 3 倍,相对性能提高了 98%。
  • TinyBERT 压缩了 7.5 倍,速度提高了 9.4 倍,相对性能提高了 97%。

用于训练模型的蒸馏训练步骤在 PyTorch 中非常简单(原始描述和代码可在medium.com/huggingface/distilbert-8cf3380435b5找到):

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.optim import Optimizer
KD_loss = nn.KLDivLoss(reduction='batchmean')
def kd_step(teacher: nn.Module,
            student: nn.Module,
            temperature: float,
            inputs: torch.tensor,
            optimizer: Optimizer):
    teacher.eval()
    student.train()
    with torch.no_grad():
        logits_t = teacher(inputs=inputs)
    logits_s = student(inputs=inputs)
    loss = KD_loss(input=F.log_softmax(
                            logits_s/temperature, 
                            dim=-1),
                   target=F.softmax(
                            logits_t/temperature, 
                            dim=-1))
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()

这种模型监督训练为我们提供了一个行为非常类似于基础模型的较小模型。然而,这里使用的损失函数是Kullback-Leibler损失,以确保学生模型模仿了老师模型的好坏方面,并且对最后 softmax logits 的决策没有修改。这个损失函数显示了两个分布之间的不同程度;差异越大,损失值越高。使用这个损失函数的原因是使学生模型尽可能地模仿老师的行为。BERT 和 DistilBERT 的 GLUE 宏分数只相差 2.8%。

精简Transformers

精简包括根据预先指定的标准在每一层上将权重设置为零的过程。例如,一个简单的精简算法可以获取每层的权重并将低于阈值的权重设置为零。这种方法消除了值非常低且不会对结果产生太大影响的权重。

同样地,我们还精简了Transformers网络的一些多余部分。精简后的网络更有可能比原始网络更好地泛化。我们看到了成功的精简操作,因为精简过程可能保留了真实的潜在解释因素并且丢弃了多余的子网络。但是我们仍然需要训练一个大型网络。合理的策略是尽可能地训练一个大型神经网络。然后,丢弃一些不太明显的权重或单元,这些权重或单元的移除对模型性能的影响很小。

有两种方法:

  • 非结构化精简:不管它们位于神经网络的哪个部分,都移除具有较小显著性(或最小权重幅度)的个别权重。
  • 结构化精简:此方法精简头部或层。

然而,精简过程必须与现代 GPU 兼容。

大多数库,如 Torch 或 TensorFlow,都具有此功能。我们将描述如何使用 Torch 对模型进行精简。有许多不同的精简方法(基于幅度或基于互信息)。其中一个最容易理解和实现的方法是 L1 精简方法。此方法获取每一层的权重并将具有最低 L1 范数的权重置为零。您还可以指定在精简后必须将多少百分比的权重转换为零。为了使这个示例更容易理解并展示它对模型的影响,我们将使用 第七章 中的文本表示示例。我们将精简模型并查看精简后的表现:

  1. 我们将使用 Roberta 模型。您可以使用以下代码加载模型:
from sentence_transformers import SentenceTransformer
distilroberta = SentenceTransformer('stsb-distilroberta-base-v2')
  1. 您还需要加载指标和数据集进行评估:
from datasets import load_metric, load_dataset
stsb_metric = load_metric('glue', 'stsb')
stsb = load_dataset('glue', 'stsb')
mrpc_metric = load_metric('glue', 'mrpc')
mrpc = load_dataset('glue','mrpc')
  1. 为了评估模型,就像在 第七章 中,文本表示,您可以使用以下函数:
import math
import tensorflow as tf
def roberta_sts_benchmark(batch):
    sts_encode1 = tf.nn.l2_normalize(
                distilroberta.encode(batch['sentence1']),
                axis=1)
    sts_encode2 = tf.nn.l2_normalize(
        distilroberta.encode(batch['sentence2']), axis=1)
    cosine_similarities = tf.reduce_sum(
        tf.multiply(sts_encode1, sts_encode2), axis=1)
    clip_cosine_similarities = tf.clip_by_value(cosine_similarities,-1.0,1.0)
    scores = 1.0 -\
              tf.acos(clip_cosine_similarities) / math.pi
return scores
  1. 当然,还必须设置标签:
references = stsb['validation'][:]['label']
  1. 并且运行基础模型而不对其进行任何更改:
distilroberta_results = roberta_sts_benchmark(stsb['validation'])
  1. 在所有这些事情完成之后,这是我们实际开始修剪模型的步骤:
from torch.nn.utils import prune
pruner = prune.L1Unstructured(amount=0.2)
  1. 上述代码使用每层 20%的 L1 范数修剪创建了一个修剪对象。要将其应用于模型,可以使用以下代码:
state_dict = distilroberta.state_dict()
for key in state_dict.keys():
    if "weight" in key:
        state_dict[key] = pruner.prune(state_dict[key])
  1. 它将迭代地修剪所有具有权重名称的层;换句话说,我们将修剪所有重量层,而不触及有偏差的层。当然,您也可以出于实验目的尝试这样做。
  2. 再次加载状态字典到模型中是很有必要的:
distilroberta.load_state_dict(state_dict)
  1. 现在我们已经完成了所有事情,可以测试新模型:
distilroberta_results_p = roberta_sts_benchmark(stsb['validation'])
  1. 为了对结果进行良好的可视化表示,可以使用以下代码:
import pandas as pd
pd.DataFrame({
"DistillRoberta":stsb_metric.compute(predictions=distilroberta_results, references=references),
"DistillRobertaPruned":stsb_metric.compute(predictions=distilroberta_results_p, references=references)
})
  1. 以下截图显示了结果:

图 8.2 - 原始模型和修剪模型的比较

但你所做的是,你消除了模型 20%的所有权重,减少了它的大小和计算成本,并且性能下降了 4%。但是,这一步骤可以与量化等其他技术结合使用,在下一小节中进行探索。

这种修剪类型应用于层中的一些权重;但是,也有可能完全丢弃某些部分或层的Transformers架构,例如,可以丢弃一部分关注头,并跟踪更改。

PyTorch 还提供了其他类型的修剪算法,例如迭代和全局修剪,值得一试。

量化

量化是一种信号处理和通信术语,通常用于强调所提供的数据的准确度。更多的位数意味着数据分辨率方面的更高准确度和精度。例如,如果您有一个用 4 位表示的变量,并且您想将其量化为 2 位,这意味着您需要降低分辨率的准确度。使用 4 位,您可以指定 16 个不同的状态,而使用 2 位,您只能区分 4 个状态。换句话说,通过将数据的分辨率从 4 位降低到 2 位,您可以节省 50%的空间和复杂度。

许多流行的库,例如 TensorFlow,PyTorch 和 MXNET,都支持混合精度运算。回想一下第五章TrainingArguments类中使用的fp16参数。fP16可以提高计算效率,因为现代 GPU 对降低精度的数学运算提供了更高的效率,但结果是在fP32中累积。混合精度可以减少训练所需的内存使用,从而允许我们增加批处理大小或模型大小。

量化可以应用于模型权重,以减少其分辨率并节省计算时间,内存和存储空间。在本小节中,我们将尝试为我们在上一小节中修剪的模型进行量化:

  1. 为此,使用以下代码以 8 位整数表示量化模型,而不是浮点数:
import torch
distilroberta = torch.quantization.quantize_dynamic(
            model=distilroberta,
            qconfig_spec = {
            torch.nn.Linear :
            torch.quantization.default_dynamic_qconfig,
                           },
            dtype=torch.qint8)
  1. 然后,使用以下代码获得评估结果:
distilroberta_results_pq = roberta_sts_benchmark(stsb['validation'])
  1. 而且,你可以看到结果:
pd.DataFrame({
"DistillRoberta":stsb_metric.compute(predictions=distilroberta_results, references=references),
"DistillRobertaPruned":stsb_metric.compute(predictions=distilroberta_results_p, references=references),
"DistillRobertaPrunedQINT8":stsb_metric.compute(predictions=distilroberta_results_pq, references=references)
})
  1. 结果如下:

    图 8.3 - 原始模型、修剪模型和量化模型的比较
  2. 到目前为止,你只是使用了一个经蒸馏的模型,对其进行了修剪,然后对其进行了量化,以减少其大小和复杂性。让我们看看通过保存模型你节省了多少空间:
distilroberta.save("model_pq")
  1. 为了查看模型大小,使用以下代码:
ls model_pq/0_Transformer/ -l --block-size=M | grep pytorch_model.bin
-rw-r--r-- 1 root 191M May 23 14:53 pytorch_model.bin
  1. 如你所见,它是 191MB。模型的初始大小为 313MB,这意味着我们设法将模型的大小减小到其原始大小的 61%,并且在性能方面只损失了 6%-6.5%。请注意,block-size参数可能在 Mac 上失败,必须使用-lh

直到这一点,你已经了解了在工业使用中实际模型准备方面的修剪和量化。然而,你也获得了关于蒸馏过程及其如何有用的信息。有许多其他方法可以进行修剪和量化,在阅读本节之后可以迈出的良好一步。有关更多信息和指南,你可以查看github.com/huggingface/block_movement_pruning运动修剪。这种修剪是一种简单且确定性的一阶权重修剪方法。它利用训练中的权重变化来找出哪些权重更有可能未被使用以减少对结果的影响。

精通 Transformers(三)(3)https://developer.aliyun.com/article/1510707

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
7月前
|
机器学习/深度学习 自然语言处理 PyTorch
精通 Transformers(一)(2)
精通 Transformers(一)
196 4
|
7月前
|
API TensorFlow 算法框架/工具
精通 Transformers(四)(1)
精通 Transformers(四)
72 0
精通 Transformers(四)(1)
|
7月前
|
机器学习/深度学习 数据可视化 API
精通 Transformers(四)(2)
精通 Transformers(四)
80 0
|
7月前
|
自然语言处理 数据可视化 NoSQL
精通 Transformers(四)(3)
精通 Transformers(四)
93 0
|
7月前
|
编解码 自然语言处理 数据可视化
精通 Transformers(四)(4)
精通 Transformers(四)
186 0
|
5月前
|
自然语言处理 PyTorch TensorFlow
Transformers 4.37 中文文档(二)(4)
Transformers 4.37 中文文档(二)
47 2
|
5月前
|
存储 PyTorch 测试技术
Transformers 4.37 中文文档(八十三)(4)
Transformers 4.37 中文文档(八十三)
34 2
|
5月前
|
存储 自然语言处理 测试技术
Transformers 4.37 中文文档(八)(4)
Transformers 4.37 中文文档(八)
91 2
|
5月前
|
存储 自然语言处理 测试技术
Transformers 4.37 中文文档(六)(3)
Transformers 4.37 中文文档(六)
81 0
|
5月前
|
自然语言处理 Rust PyTorch
Transformers 4.37 中文文档(七)(1)
Transformers 4.37 中文文档(七)
98 0