从机器学习的角度理解向量
如果你从事机器学习,你将需要使用向量。几乎没有 ML 模型在项目生命周期的某一点不使用向量。
虽然向量被用于许多其他领域,但是它们在 ML 中的使用方式有所不同。这可能会令人困惑。从 ML 的角度来看,向量的潜在混淆是我们在 ML 项目的不同阶段出于不同的原因使用它们。
这意味着向量的严格数学定义可能无法传达您在 ML 上下文中处理和理解向量所需的所有信息。例如,如果我们认为一个典型的 ML 项目的简单生命周期是这样的:
…然后有三个不同的阶段。每一个都有稍微不同的向量用例。在本文中,我们将通过查看与这些阶段相关的向量来澄清这一切:
- 输入:机器不能像你我一样阅读文字或看图像。它们需要输入被转换或编码成数字。向量和矩阵(我们一会儿会讲到)将文本和图像等输入表示为数字,这样我们就可以训练和部署我们的模型。我们不需要对向量有很深的数学理解,就可以用它们来为我们的输入编码信息。我们只需要知道向量与特征之间的关系,以及如何将这些特征表示为向量。
- 模型:大多数 ML 项目的目标是创建一个执行某些功能的模型。它可以对文本进行分类,或者预测房价,或者识别情绪。在深度学习模型中,这是通过神经网络实现的,其中神经网络层使用线性代数(如矩阵和向量乘法)来调整你的参数。这就是向量的数学定义与 ML 相关的地方。在这篇文章中,我们不会深入线性代数的细节,但是我们会看看向量和矩阵的重要方面,我们需要使用这些模型。这包括理解向量空间以及为什么它们对 ML 很重要。
- 输出:根据我们的目标,我们的 ML 模型的输出可以是一系列不同的实体。如果我们预测房价,输出将是一个数字。如果我们对图像进行分类,输出将是图像的类别。然而,输出也可以是向量。例如,NLP 模型像通用句子编码器(使用)接受文本,然后输出一个表示句子的向量(称为嵌入)。然后,您可以使用这个向量来执行一系列操作,或者作为另一个模型的输入。您可以执行的操作包括在向量空间中将相似的句子聚集在一起,或者使用余弦相似性等操作来查找不同句子之间的相似性。理解这些操作将有助于您了解如何使用像 NLP 模型这样输出向量的模型。
注:你可以在 Github 上找到这个帖子的所有代码。
标量、向量和矩阵
向量并不是机器处理和转换输入时表示数字的唯一方式。虽然我们在这篇文章中主要关注向量,但我们也需要定义其他表示数字的结构。
这将有助于我们在接下来的部分,我们需要了解向量如何与其他结构相互作用。出于本节的目的,让我们将关于房价的 Kaggle 数据集作为我们的参考框架,看看我们如何使用标量、向量和矩阵来表示这些数据。
*Our house price data has 18 potential features (21 less id, data and the price we want to predict) that a model could use to help it predict the price. *
标量:对我们来说,标量只是数字。我们可以把它们想象成我们使用的任何常规值。我们数据集中的任何单个值都代表一个标量。例如,我们房价数据中的卧室数量将是一个标量。如果我们只使用一个特征作为房价数据的输入,那么我们可以将其表示为一个标量值。
print(house_price_df['bedrooms'].values[0])
3
向量:在我们的房价数据中,似乎有不止一个可用的特征。我们如何表示多个特征?当试图预测房价时,房子的总面积将是一条有用的信息。最简单的形式是,我们可以把向量看作一维数据结构。我们稍后将更详细地定义一个 vector,但是现在可以把它看作一个列表,让我们把更多的特性传递给我们的模型:
print(house_price_df[["bedrooms", "sqft_lot"]].values[:1])
[[ 3 5650]]
我们还可以通过以列而不是行的格式排列数据来创建一维向量:
print(house_price_df[["bedrooms", "sqft_lot"]].values.T[:2, :1].shape)
[[ 3]
[5650]]
矩阵:到目前为止,我们只看了数据集中的第一栋房子。如果我们需要批量通过多个房子,有他们的卧室和平方英尺/平方米的价值?这就是矩阵的用武之地。您可以将矩阵视为二维数据结构,其中两个维度指的是行数和列数:
print(house_price_df[["bedrooms", "sqft_lot"]].values[:2])
[[ 3 5650]
[ 3 7242]]
这是一个包含行和列的二维数据结构:
print(house_price_df[["bedrooms", "sqft_lot"]].values[:2].shape)
(2, 2)
我们可以添加更多的行,查看前 3 个房价数据,看看这如何改变我们矩阵的形状:
print(house_price_df[["bedrooms", "sqft_lot"]].values[:3].shape)
(3, 2)
需要注意的一点是,我们之前创建的向量实际上是一维矩阵。从这个意义上说,向量也可以是矩阵,但反之则不然。矩阵不能是向量。
现在我们已经澄清了一些术语,让我们看看如何使用向量作为我们深度学习模型的输入。
你可以看到 TensorBoard 如何处理标量和向量
输入:作为编码器的矢量
正如我们前面提到的,我们在 ML 中使用向量的方式非常依赖于我们是处理输入、输出还是模型本身。当我们使用向量作为输入时,主要用途是它们能够以我们的模型可以处理的格式编码信息,然后输出对我们的最终目标有用的东西。让我们用一个简单的例子来看看向量是如何被用作编码器的。
假设我们想要创建一个写新大卫·鲍依歌曲的 ML 模型。要做到这一点,我们需要在实际的大卫·鲍依歌曲上训练模型,然后用输入文本提示它,然后模型将把它“翻译”成大卫·鲍依式的歌词。从高层次来看,我们的模型应该是这样的:
我们如何创建输入向量?
我们需要向我们的模型传递大量大卫·鲍依的歌词,这样它就可以像齐格·星尘一样学习写作。我们知道,我们需要将歌词中的信息编码成一个向量,以便模型可以处理它,神经网络可以开始对我们的输入进行大量的数学运算,以调整参数。另一种思考方式是,我们使用向量来表示我们希望模型学习的特征。
该模型可能试图预测房价,使用我们之前使用的房价数据集来解释标量、向量和矩阵之间的差异。在这种情况下,我们可能会传递诸如房子大小、卧室数量、邮政编码之类的信息。这些都是可能有助于模型预测更准确的房价的特征。很容易看出我们如何为我们的房价模型创建一个输入向量。当我们创建一个包含卧室数量和房屋平方英尺/平方米值的向量时,我们已经展示了如何对这些数据进行编码:
print(house_price_df[["bedrooms", "sqft_lot"]].values[:1])
[[ 3 5650]]
我们将其描述为一个二维向量,因为它编码了房价数据集的两个部分。我们不应该将这与矩阵是二维数据结构的事实相混淆。当我们说一个矩阵是二维的,我们的意思是它有 2 个索引,行和列。
要标识矩阵中的单个元素,需要指定其行和列的位置。而在 vector 中,它只有一个索引,所以只需要指定一个索引。可以把它想象成对一个列表进行寻址,你只需要确定列表的索引。而矩阵就像一个 numpy 数组,需要指定行和列的值。
但是一句话呢?
房价数据集已经包含了创建特征向量所需的所有列,我们可以将这些列用作模型的输入。数据集本身对它们进行了描述。因此,很容易将数据转换或编码成矢量格式。然而,我们的鲍伊歌词不容易转换成矢量。相反,我们需要找到一些规则来编码歌词中包含的信息。
一个简单的方法是给每个单词分配一个数字,并用它来创建一个向量。这可以通过计数矢量化来完成,您可以在 Scikit Learn 中找到一种方法。
阅读如何将 Sklea 与 Neptune 集成,并跟踪您的分类器、回归器和 k-means 聚类结果。
在这里,具体的实现并不重要,只是我们希望找到一种方法,以类似于 house 数据的方式来表示句子。举个例子,就拿鲍伊《一个自由节日的记忆》中的两行歌词来说吧:
首先我们需要创建一个固定长度的向量。在我们根据房价数据示例创建的向量中,每栋房子有 2 个数据点。通过从数据集中添加更多的特征,我们可以很容易地将它变成一个 18 维向量。
为了尝试和确定我们的 Bowie 歌词向量的长度,我们可以用上面两行来代表我们的整个 Bowie 歌词语料库。然后我们有 9 个独特的词或特征。
def get_vocab(text):
vocab = set()
for line in text:
vocab.update([word.lower() for word in line.split()])
return(vocab)
vocab = get_vocab(lyric_list[0:2])
print(f'There are {len(vocab)} unique words in these lyrics:')
print(f'They are: {vocab}')
There are 9 unique words in these lyrics:
They are: {'children', 'in', 'of', 'grass', 'gathered', 'the', 'end', "summer's", 'dampened'}
我们这里的输入向量将包含 9 个特征。显然,就输入时可能用到的所有单词而言,这是一个很小的数字。如果我们试图编码一个包含我们不认识的单词的句子,我们可以忽略它。如果该单词确实存在,我们将计算它在输入中出现的次数,并将该值添加到 vocab 中表示该单词的向量维数或特征中。
我们的特征看起来像这样:
count_vector = vector_lookup(lyric_list[0:2])
print(count_vector)
{0: 'children', 1: 'in', 2: 'of', 3: 'grass', 4: 'gathered', 5: 'the', 6: 'end', 7: "summer's", 8: 'dampened'}
如果单词“children”在输入中出现 2 次,那么向量的第一维将是 2。如果不发生,则为 0。如果我们遇到一个不在词汇库中的单词,我们将什么也不做,因为它没有特征或维度。如果我们想训练出最好的鲍伊歌词写作模型,这显然是不理想的。在下一个例子中,我们将会看到更好的输入编码方式。
如果我们的输入句子是“在草地上玩耍的孩子们”,那么表示这个句子的输入向量将如下所示:
print(input_vector(count_vector, "The children played in the grass"))
[1, 1, 0, 1, 0, 2, 0, 0, 0]
单词*‘children’出现一次,是我们向量的第一个特征,所以是 1。的没有出现在输入句中,所以为零。单词“*”出现了两次,因此索引 5 处的特征的值为 2。**
*现在让我们使用“一个自由节日的记忆”中的所有歌词,看看我们对同一个句子的输入向量是什么样子的:
count_vector = vector_lookup(lyric_list)
full_vec = input_vector(count_vector, "The children played in the grass")
print(f'The new feture size is {len(count_vector)}')
print(full_vec)
新的特征尺寸是 129
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
我们看到更多的零,因为有更多的特征,而且它们并不都出现在每个输入句子中。这就是我们所说的稀疏向量——它不是“密集”分布的。大多数特征不编码任何信息,因为它们是空的(它们被设置为零)。
这是低效的,因为大多数特征不包含太多信息。在实践中,我们可以通过用更少的零创建“更密集”的向量的方法来解决这个问题。
密集向量是什么样子的?
我们为输入 Bowie lyric 模型而创建的向量很少。我们可以使用一个完全独立的模型来接收一个句子并返回一个密集的向量,而不是创建我们自己的向量。然后我们可以用它作为我们自己学习鲍伊歌词模型的输入。
这是一个将模型链接在一起的例子,其中您使用一个模型的输出作为另一个模型的输入。这也是一个模型输出向量的例子,我们将在后面的文章中讨论。
通用句子编码器 (USE)是一个可以接受文本输入并输出向量的模型的例子,就像我们需要的 Bowie 模型一样。使用将产生包含 512 个维度的输出向量。这些可以被认为是我们新的输入向量,而不是我们稀疏填充的计数向量。
如前所述,这些向量包含 512 个特征或维度。有趣的是,向量的大小并不随输入句子的大小而变化:
sample_sentences = ["We scanned the skies with rainbow eyes",
"We looked up into the sky",
"It was raining and I saw a rainbow",
"This sentence should not be similar to anything",
"Erer blafgfgh jnjnjn ououou kjnkjnk"]
logging.set_verbosity(logging.ERROR)
sentence_embeddings = embed(sample_sentences)
for i, sentence_embedding in enumerate(np.array(sentence_embeddings).tolist()):
print("Lyrics: {}".format(sample_sentences[i]))
print("Embedding size: {}".format(len(sentence_embedding)))
sentence_embedding_snippet = ", ".join(
(str(x) for x in sentence_embedding[:3]))
print("Embedding: [{}, ...]n".format(sentence_embedding_snippet))
如果你仔细观察这些向量,你会发现在所有的特征中很少有零。这就是为什么它们被描述为“密集”向量。另一件要注意的事情是,我们不知道这些特性意味着什么。在我们的 Bowie 歌词示例中,我们定义了特性,因此我们确切地知道每个输入向量维度与什么相关。相比之下,对于使用,我们不知道第 135 维是否与大写、长度或特定单词相关。甚至可能没有这些特征的尺寸,我们只是猜测。
在我们对维度知之甚少的情况下创建向量似乎毫无意义。但这是向量的一个关键之处,它让向量在 ML 中变得如此强大。我们不需要理解任何关于向量的维度,或者它们是如何被创建的,就可以使用它们。我们需要理解的只是我们可以对向量执行的操作,以及这些向量所在的空间。但是在我们看当我们把一个向量传递给模型时会发生什么之前,我们需要看一下我们如何把矩阵作为输入。
鲍伊歌词矩阵
import numpy as np
np.set_printoptions(threshold=10)
lyrics = []
for lyric in lyric_list[15:18]:
print(lyric)
vec = input_vector(count_vector, lyric)
lyrics.append(vec)
lyric_matrix = np.array(lyrics)
print(f'Matrix shape: {lyric_matrix.shape})')
print(lyric_matrix)
And fly it from the toppest top
of all the tops that man has
pushed beyond his Brain.
Matrix shape: (3, 129))
[[0 0 0 ... 0 0 0]
[1 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]]
正如我们所提到的,矩阵是向量的矩形阵列。它们是使我们能够以二维格式存储信息的数据结构。对于我们的 Bowie 模型,我们可能希望成批传递句子,而不是一次一个句子作为输入。为此,我们将信息存储在一个矩阵中,并将其传递给我们的模型。然后,模型可以以类似于向量的方式处理矩阵。在图像处理中,矩阵用于表示作为模型输入的图像。例如,著名的 Minst 数据集使用 28 X 28 矩阵来表示灰度数字。行中的每个元素都是 0-255 之间的值,代表其灰度强度。
模型:向量作为变压器
此时,我们已经将输入表示为一个向量,并希望用它来训练我们的模型。换句话说,我们希望我们的模型学习一种转换,这种转换使用我们输入中的特性来返回实现某个目标的输出。我们已经讨论了这种目标的一个例子:
- 预测房价:我们展示了如何创建一个向量,该向量对房价数据中所需的特征进行编码。在这个场景中,我们想要学习一个使用这些特性来输出房子的预测价格的转换。
- 创建 Bowie 风格的歌词:对于我们的 Bowie 模型,输出是文本,转换是将输入的句子转换成 Bowie 风格的歌词。
- 句子编码器:使用模型将输入句子转换成输出向量。它的目标是创建一个输出句子,使用输入句子的特征,在一个叫做向量空间的东西中表示那个句子。
神经网络是一种创建可以学习这些转换的模型的方法,因此我们可以将我们的房价数据转化为预测,将我们枯燥的句子转化为鲍伊式的思考。
具体来说,神经网络的隐藏层接收我们的矢量化输入,创建一个权重矩阵,然后使用该权重矩阵创建我们想要的输出:
The hidden layer is where the computation takes place to learn the transformations we need to create our outputs. Source DeepAi.org
我们不需要进入神经网络如何工作的细节。但我们确实需要了解更多关于向量和矩阵的知识,以及它们是如何相互作用的,这样我们才能理解我们为什么使用它们,更重要的是,我们如何使用这些网络的输出。因此,在本节中,我们将:
- 定义向量:我们需要从数学的角度简要定义向量,并展示上下文与我们使用它们作为输入的方式有何不同。
- 向量空间:向量生活在向量空间中。如果你理解了这些空间是如何工作的,那么你就能理解为什么向量在 ML 中很重要的大部分原因。
- 向量和矩阵运算:我们通过将向量与矩阵相乘来操作向量。它们挤压、挤压、移动和变换向量,直到我们的模型学会了一些我们可以用于输出的东西。这些操作对最后一节很重要,在最后一节中,我们将看看当我们的模型输出向量时使用的常见操作,就像使用模型对我们的 Bowie 歌词所做的那样。
本节的目标是帮助我们理解,我们在 ML 模型中使用向量学习函数的方式与我们在输入和输出中使用向量的方式略有不同。
这就是为什么 ML 项目中管道阶段的环境是重要的。关于向量的有用知识可能是不同的,这取决于你想用你的向量做什么。希望了解这一点能对你未来的 ML 项目有所帮助。
什么是向量?
以前,我们将向量定义为一系列数字或一维数据结构。这有助于我们理解如何将来自数据集的信息编码为输入传递给 ML 模型。
在这些模型中,一旦接收到输入,重要的是要理解向量是具有不同寻常的大小和方向的对象:
Most people will be familiar with the definition of a vector as being something that has both magnitude and direction. Source MathInsight.org
我们不会在这个定义上花时间,因为大多数人都熟悉它,但是我们要注意:
- 几何:这个定义有一个几何方面,因为它有一个方向。这意味着它存在于一个几何空间中,我们可以对向量进行操作,改变它的方向和/或大小。这也意味着这些空间可以有维度。上面的例子显示了一个二维向量,但它可以有三维甚至更多维,这使得人们很难想象。
- 与输入的相关性:我们在这里也可以看到,理解一个矢量有大小和方向并不能真正帮助我们将输入编码为矢量。我们不需要理解我们从房价数据中创建的向量的方向来创建有用的输入。
MathInsight 网站有一个很好的工具来探索向量的一些重要性质。您可以操纵向量在图形中移动,但请注意向量的大小和方向不会改变:
We can see the vector has a magnitude and direction, and is located in a certain part of the graph.
We note that we can move the vector to a different part of the graph, but the magnitude and direction are still the same. The point in space to which the vector now points is different.
从上面的例子中我们可以看到,一个向量存在于一个叫做向量空间的空间中,我们可以操纵这个向量在这个空间中移动。理解这个向量空间的本质是在 ML 中使用向量的关键。我们需要定义向量空间的含义。
向量空间
我们可以把向量空间想象成包含所有向量的区域。我们可以认为这些空间与向量的维数有关。如果我们上面的向量例子是二维的,它可以用 X 和 Y 坐标来描述,就像你在 matplotlib 上画的大多数二维图形一样:
2-D vector spaces are similar to common graphs we would draw in matplotlib. Notice here that we have drawn the vectors as points without the arrows. This is to show that we can also think of vectors as points in these vector spaces.
向量空间的关键在于它是一种定义一组向量的方法,我们可以在其中执行运算,比如向量加法和乘法。结果,我们可以得到三维向量空间,其中向量由 3 个坐标定义。这些向量空间之间的唯一区别是它们的维度。
我们可以对每个空间中的向量执行相同的操作,例如:
- 加法:我们将两个向量相加,创建第三个向量。
- 乘法:我们可以将一个矢量乘以一个标量来改变矢量的大小。
这些向量空间具有定义所有这些操作如何发生的性质。换句话说,向量空间具有以下性质:
- 加法必须创建一个新的矢量。
- 加法是可换的,即向量 A +向量 B ==向量 B +向量 A。
- 必须有一个零向量,当加到另一个向量上时返回该向量的单位向量,即 0 +向量 A ==向量 A + 0 ==向量 A。
- 对于每个矢量,都有一个指向相反方向的反向矢量。
- 向量加法也是联想,即向量 A +(向量 B +向量 C) ==(向量 A +向量 B) +向量 C。
向量空间对 ML 很重要,因为它们是我们所有变换发生的空间,在这里输入向量被操纵以创建我们输出所需的权重。我们不需要深入这些转换的细节,但是我们需要提到一些在下一节中很重要的操作,在下一节中,我们尝试对我们的输出执行操作以实现一些最终目标。
向量和矩阵运算
有一些关键的向量操作会在大多数 ML 项目中频繁出现,所以我们需要在这里涵盖它们。在下一节中,我们将查看这些操作的一些工作示例,使用 USE 模型来生成 512 维句子向量。我们将了解的操作有:
- 向量范数:在第一部分,我们注意到你可以把向量想象成代表特征的数字列表。特征的数量与向量的维数有关,正如我们刚刚看到的,与向量所在的向量空间有关。因为这些向量有一个大小,我们可以用向量范数得到它的大小。这些范数表示向量的大小或长度,可以用于不同向量之间的简单比较。因为范数代表向量的大小,所以它总是正数。即使向量中的所有值都是负的,也是如此。有很多方法可以得到向量的范数,最常见的有:
- L1 范数=矢量的绝对值之和,也称为曼哈顿距离。
这篇文章很好地概括了这些标准之间的区别以及它们在距离度量中的应用。
- L2 范数=向量平方值之和的平方根,也称为欧几里德距离。下面例子中的红线
这是另一篇更详细讨论 L2 规范的文章。
- 内积:内积是对两个向量进行运算并返回一个值的另一种方式。数学上,我们将向量 A 中的每个元素乘以向量 B 中相应的元素,然后将结果相加。与规范不同,内积可以是负数。你可能会听到这被称为点积,它只是欧几里得空间内积的一个特例。几何上,我们可以把内积想成两个向量的大小和它们之间夹角的余弦的乘积。
Wikipedia definition of dot product in Euclidean space
-
余弦:你可以把点积想成两个向量之间的夹角乘以两个向量的长度的余弦。如果向量的大小很重要,也就是说,你认为它携带了一些信息,那么你应该使用点积。相反,如果您只想知道两个向量在方向上有多接近,那么您可以使用余弦相似性度量。余弦只是点积的一种操作,可以忽略幅度,即两个向量之间的余弦角是相同的,即使向量的大小不同:
-
向量归一化:我们现在知道向量有大小,不同的向量可以有不同的大小。有时我们不关心向量的大小,只对它的方向感兴趣。如果我们根本不在乎大小,那么我们可以让每个向量大小相同。我们通过将每个向量除以其大小来实现,从而使每个向量的大小为 1,或者将它们转换为单位向量。现在余弦相似度和点积将返回相同的值:
Source: Example of vector normalization
- 降维:我们的房价向量只有两个维度,因为它们只包含两个特征值。相比之下,我们生成的使用向量有多达 512 个维度。对我们来说,画二维图很容易,但是画 512 维图呢?没那么容易。因此,我们需要使用某些技术来减少向量的维数,而不会丢失太多重要的信息。我们将看看执行这种缩减的最常见方法之一:
主成分分析(PCA): PCA 试图识别解释数据中大部分变化的超平面。类似于我们试图在线性回归中寻找趋势线的方法。PCA 中的主成分是这些超平面的轴。现在,只要把它想成一种用更少的维度来寻找代表向量中大部分变化的值的方法:
Source: A great post on PCA if you want to understand it in more detail
我们在这一部分已经讲了很多,所以在我们看最后一部分之前,让我们来回顾一下。到目前为止,我们:
- 更详细地定义了向量,并展示了如何在向量空间中表示它们。
- 展示了 ML 模型需要从我们的输入向量的特征中学习,以创建将输入数据转化为我们需要的输出的转换。
- 定义了发生这些变换的向量空间。
- 看了一些我们可以在这些空间中对向量执行的常见操作。
现在我们可以看看所有这些工作的结果,也就是我们的产出。我们将展示我们讨论过的所有操作的工作实例,并展示它们如何在许多情况下作为你下一个 ML 项目的一部分被使用。
虽然您不需要理解我们刚刚讨论的所有运算的数学原理,但是对为什么使用它们有一个宽泛的概念基础会有所帮助。当涉及到输出时,这将让您为相关任务选择正确的操作。
输出:使用向量运算
正如我们在这篇文章中提到的,如何在 ML 中使用向量取决于管道阶段。在上一节中,我们看到了向量数学、运算和变换是理解深度学习神经网络“幕后”发生的事情的关键。这些计算都发生在输入和输出之间的“隐藏层”。
但是,对于大多数 ML 项目,您不需要这种级别的细节。当然,理解深度学习算法的核心数学肯定是好的,但这对开始使用这些模型并不重要。
例如,正如我们在输入部分所展示的,你不需要矢量数学的详细知识就能以矢量格式编码输入。当你想出如何最好地编码一个句子以便你的模型可以处理它时,向量的大小并不重要。相反,重要的是要知道你正在用编码解决的问题,你是需要一个稀疏的还是密集的向量,或者你想要捕捉什么特征。
现在,类似地,关于你的模型的输出,我们将看看向量的方面,它对 ML 管道的这个阶段最有影响。请记住,这些模型的输出甚至可能不是一个向量,它可能是一幅图像,或者一些文本,或者一个类别,或者一个数字,比如房价。在这些情况下,你不需要处理任何向量,所以你可以像以前一样继续。
但是如果你有一个矢量输出,你将主要关心两个目标:
- 其他模型的输入:你可能想要使用一个模型的矢量输出作为另一个模型的输入,就像我们之前使用的和我们的 Bowie 模型一样。在这些情况下,您可以参考输入部分,将向量视为一个值列表和一个表示相关信息的编码。您还可以使用输出在这个输入的基础上构建一个分类器,并训练它来区分一些领域特定的数据。无论哪种方式,都是将向量作为另一个模型的输入。
- **向量函数的输入:**如果输出是一个向量,并且我们没有将其用作另一个模型的输入,那么我们需要将其与向量函数结合使用。使用模型输出一个嵌入(即向量),但是我们不能孤立地解释这个 512 数组的数字。我们需要对它执行一些功能,以生成我们可以解释的结果。正如我们在上一节中提到的,这些函数可以识别两个向量之间的相似性,并降低向量的维数,以便我们可以可视化它们。知道对于给定的目的需要哪种操作可能会令人困惑。因此,我们将在本节中查看这些操作的一些工作示例。
为我们的行动生成向量
我们需要一些示例向量,以便在向量运算中使用。我们可以通过创建一些例句,将它们传递给用户,并存储向量输出来实现这一点。这样,我们可以将每个操作的结果与句子本身进行比较。我们将创建一组句子,一些看起来相似,另一些语义上非常不同:
- 我们用彩虹眼扫描天空—《自由节日的记忆》中鲍伊的歌词
- 我们仰望天空——这是一首非鲍伊的歌词,看起来语义相似
- 下雨了,我看到了彩虹,这句话似乎和歌词更不一样
- 此句不应与任何东西相似–此句不应有语义重叠
- 一句完整的胡言乱语
现在让我们看看我们的向量运算告诉我们这些句子。
向量范数
我们之前已经看到,向量范数是一种确定向量“大小”的方法。在为我们的向量生成嵌入之后,我们可以得到如下的 L1 和 L2 范数:
for sentence, vector in zip(sample_sentences, sentence_embeddings):
l1 = norm(vector, 1)
print(f"L1 norm of '{sentence}': is {l1:.4}")
for sentence, vector in zip(sample_sentences, sentence_embeddings):
l1 = norm(vector)
print(f"L2 norm of '{sentence}': is {l1:.4}")
我们可以看到关于这些规范的两点:
- L1 规范值在数值上接近,
- L2 标准值加起来都是 1。
这可能看起来很奇怪,但是根据我们现在对向量的了解,让我们看看他们在 Tensorflow 页面上对使用嵌入是怎么说的:
看起来嵌入已经被规范化了(近似规范化,我不确定它们是什么意思,但是让我们假设它意味着它们被规范化了)。这意味着向量除以它们的大小,然后当我们得到 L2 范数时,它总是 1,也就是单位向量。
作为一个不同的例子,让我们用一个不同的模型来得到我们的向量的 L2 范数,这个模型没有以同样的方式归一化向量。有一个很棒的库可以做到这一点,叫做快速句子嵌入 (FSE)。这些向量的 L2 范数不都是 1:
for idx, sentence in enumerate(sample_sentences):
l2 = norm(model.sv[idx])
print(f"L2 norm of '{sentence}': is {l2:.4}")
这告诉我们关于向量的什么?实际上,不多。它不擅长识别我们句子之间的语义差异。这可能表明向量的大小不是识别句子之间语义相似性的可靠方法。
内积
如果大小本身不能告诉我们很多关于向量的信息,让我们试试内积。记住,这是两个向量乘积的和:
res = {}
for sen in sample_sentences:
res[sen] = []
for i, s1 in enumerate(sample_sentences):
for j, s2 in enumerate(sample_sentences):
dot_prod = np.dot(sentence_embeddings[i], sentence_embeddings[j])
res[s1].append(round(dot_prod, 3))
pd.DataFrame(res, index=[s for s in sample_sentences])
得到每对句子的内积,我们可以看到,当它们相同时,我们得到 1,当它们不相同时,我们得到其他值。如果你看看我们的胡言乱语句子的分数,你可以看到我们对一些对得到一个负分。对于“这个句子不应该与任何东西相似”这个句子,我们也得到了低分。我们的鲍伊歌词句子和我们不太折衷的尝试确实产生了相对较高的分数,事实上是所有比较中最高的分数。也许我们毕竟有一些抒情的创造力。
余弦相似性
另一个得分是两个向量之间的余弦相似性得分。正如我们之前提到的,这忽略了向量的大小,所以它应该给内积一个不同的结果。但是请记住,我们的嵌入是标准化的,这意味着我们应该得到与内积相同的余弦相似性分数。知道这一点很有好处,因为如果要进行大量的相似性比较,内积运算会更有效。
def get_cos_sim(sen1, sen2):
sts_encode1 = embed(tf.constant([sen1]))
sts_encode2 = embed(tf.constant([sen2]))
cosine_similarities = tf.reduce_sum(tf.multiply(sts_encode1, sts_encode2), axis=1)
return cosine_similarities
res = {}
for sen in sample_sentences:
res[sen] = []
for i, s1 in enumerate(sample_sentences):
for j, s2 in enumerate(sample_sentences):
cosine = get_cos_sim(sample_sentences[i], sample_sentences[j])
res[s1].append(round(cosine.numpy()[0], 3))
pd.DataFrame(res, index=[s for s in sample_sentences])
而且看起来分数确实一样!
向量归一化
所以我们知道我们的嵌入已经被规范化了,或者,正如他们在 TF 页中所描述的,接近规范化了。在这种情况下,原始嵌入和我们对原始嵌入进行规范化应该没有区别。我们如何衡量这一点?为什么不用嵌入的大小,或者 L2 范数,就像我们之前做的那样。
non_normed_vector = embed(tf.constant([sample_sentences[0]]))
normed_vector = tf.nn.l2_normalize(embed(tf.constant([sample_sentences[0]])), axis=1)
non_normed_vector - normed_vector
是的,这两个向量之间的差很小,所以看起来它们已经被归一化了。当我们归一化一个向量时,我们只是将向量中的每个元素除以它的大小。所以你也可以这样做:
x = np.array([7, 6, 10])
nrm = norm(x)
print(f'Norm of vector {x} is {nrm}')
normal_array = x/nrm
print(f'The new normalized vecotr is {normal_array}')
print(f'And the norm of this new vector should be 1.... {norm(normal_array)}')
降维
当我们谈到向量空间时,我们注意到我们可以降低 512 维向量的维数,这样我们就可以将它们可视化。这是比较我们句子相似度的另一种方式。我们可以做像聚类这样的事情来识别哪些句子彼此相似。这是在大量句子之间寻找相似性的更好的方法,因为我们不需要在所有可能的组合之间进行成对比较。
scikit-learn 中提供了实现这一点的代码,因此您可以相对容易地减少我们嵌入的维度:
from sklearn.decomposition import PCA
import plotly.express as px
def get_3d_viz(X, sentences):
pca = PCA(n_components=3)
pca_embed = pca.fit_transform(X)
df = pd.DataFrame(columns=['x', 'y', 'z', 'word'])
df['x'], df['y'], df['z'], df['sentence'] = pca_embed[:,0], pca_embed[:,1], pca_embed[:,2], sentences
fig = px.scatter_3d(df, x='x', y='y', z='z', color='sentence')
return(fig)
get_3d_viz(sentence_embeddings, sample_sentences)
这产生了一个很好的视觉效果,就像这样:
如果你看了上面的可视化,你可能会认为“这个句子不应该和任何东西相似”这个句子和我们的胡言乱语的句子看起来很接近,彼此很相似。然而,让我们转换一下我们的视觉,看看三维图像如何帮助我们更好地区分这些句子:
这样做,我们可以看到这些句子和我们的鲍伊歌词之间有一些距离,而我们自己模仿它的尝试是最接近的。正如我们的余弦相似性和内积分数所示。
这显示了能够将向量的维度降低到我们可以在 3d 中可视化的程度的好处。
摘要
那么,我们从向量的旋风之旅中学到了什么?希望你能看到我们在 ML 中使用向量的方式与你在学校或大学中学习向量的方式不同——尽管这些知识仍然很重要。向量的数学定义是我们深度学习算法下发生的事情的关键。
然而,关键的区别在于,我们在这些模型的输入和输出中使用向量的方式不同。当你看到一些关于特征向量或者向量的线性无关性的东西时,不要混淆。相反,要知道这些信息很重要,但是为了编码你的输入和使用你的输出向量,你并不真的需要这些信息。
你可以在积累使用向量的经验时涉猎这些知识,但是在 ML 的上下文中,知道你对管道的哪个阶段感兴趣是使用向量的关键。
参考
如果你想更深入地了解向量在 ML 中是如何使用的,下面的列表提供了一些很好的资源:
- 你可以在 MathInsight 上找到一个很好的向量概述,它提供了一些关于如何执行常见向量运算(如加法和减法)的几何描述
- 如果你想更好地描述向量规范和标准化,这是一篇很好的文章,它也涵盖了如何通过 numpy 在 Python 中实现这一点。
- 如果你想理解维数减少,那么看看这篇文章,它对 PCA 是如何工作的有一些很好的可视化。
- 这是多伦多大学计算机科学系的一个讲座,讲述不同类型的神经网络以及它们如何转换输入向量来识别模式。
- 这是另一篇关于神经网络转换的伟大文章,专门关注前馈网络并描述向量和矩阵如何参与这些操作。*
揭开 BERT 的面纱:变压器模型性能的关键
原文:https://web.archive.org/web/https://neptune.ai/blog/unmasking-bert-transformer-model-performance
如果你正在阅读这篇文章,你可能知道像 BERT 这样的深度学习变压器模型。他们正在彻底改变我们处理自然语言的方式。
💡如果你不知道,我们在上一篇文章中写了关于 BERT 和 Transformer 架构的历史和影响。
Examples of some Transformer models available from HuggingFace (the largest open-source library of Deep Learning NLP models). You can see that most of them are based on BERT in some way.
这些型号性能非常好。但是为什么呢?为什么与其他变压器型号相比,BERT 的性能如此出色?
有人可能会说伯特没什么特别的。变压器架构是我们近年来看到的最先进(SOTA)改进的独特原因。
这是令人困惑的,因为我们需要确切地知道 BERT 的哪些部分值得在未来的模型中复制。否则,我们最终可能会生产出不必要的庞大的模仿模型,并且包含无助于改进的层次和培训目标。
Is it masking that makes BERT so special? How does it work? Hopefully, this slide will make more sense after you read this post. Source: Stanford slides by Jacob Devlin
为了澄清这一困惑,我们将看看这一领域的一些最新研究,并提出:
- 掩蔽是关键:“掩蔽”是培训目标,我们将大部分成功归功于 BERT 和类似 BERT 的模型。
- 屏蔽需要注意:虽然屏蔽是区别 BERT 与其他模型的关键因素,但它是建立在通过变压器架构引入的注意机制之上的。
- 我们仍然不理解掩蔽:尽管掩蔽是最近 NLP SOTA 结果中的核心元素,我们仍然不完全理解掩蔽是如何工作的。我们来看看最近的研究,这些研究表明,以前被认为对掩蔽的成功至关重要的语言(如单词排序)的句法方面并不重要。这提出了一个重要的问题,即我们认为这些模型在学习什么语言。
- 我们可能需要重新评估我们是如何学习语言的:人类如何学习语言是一个持续争论的话题。例如,我们认为,当人类学习单词的含义时,他们所做的不仅仅是简单的共现和模式匹配。但是,如果这实际上是像 BERT 这样的模型学习语言的方式,如果它们可以达到或接近人类的水平,那么,人类只是以同样的方式学习和理解语言的有机统计推理引擎吗?也许我们需要重新审视我们认为人类是如何从语言中学习意义的。
似乎很明显,目前,在 NLP 深度学习的技术生命周期中,实践远远领先于理论。我们使用了一些方法,比如屏蔽,这些方法看起来很有效,我们稍微摆弄一下数字,效果会好一点或者差一点。然而,我们无法完全解释为什么会发生这种情况!
有些人可能会觉得这令人沮丧和失望。如果我们不能解释它,那么我们如何使用它?
也许我们不需要完全解释这些模型。我们仍然可以放心地在生产和重要的商业应用中使用它们,同时努力更好地理解它们的内部工作方式。
表示和学习:变压器模型如何学习上下文
Nearly all transformer models learn some form of context which helps improve their performance in NLP tasks. The question is why do models like BERT appear to learn more from context than other models? Is this due to their learning objective? Source: Demystifying BERT
伯特不是镇上唯一的表演,或者在这种情况下,是街上唯一的木偶!还有其他模型使用 Transformer 架构,而没有学习目标,如屏蔽(不要担心,我们将很快定义所有这些术语)。我们可以看看这些模型是如何执行的,以及这些模型内部发生了什么,以比较和对比 BERTs 学习目标在来自其输入的学习环境中的执行情况。
上下文是将 BERT 和其他变形金刚模型(如 GPT-3)与以前的模型(如 Word2Vec )区分开来的原因,以前的模型只知道一个单词的一种“意思”。从这个意义上说,Word2Vec 产生静态定义(或嵌入,它们只是表示有问题的单词的向量),因为它们只有一个含义,不会根据使用它的上下文而改变。
这有明显的局限性。你目前正在阅读一个关于伯特的帖子,但是你也可以在 帖子 中接收邮件,或者在你的花园里贴一个木制 帖子 等等。“ post ”的每一次使用都有非常不同的含义。这个问题阻止了像 Word2Vec 这样的模型匹配像 BERT (… 或者也许不匹配?稍后我们将讨论 BERT 是否仅仅是带有动态上下文的 Word2Vec 的大规模放大版本。
*在这一节中,我们将看看三种不同类型的模型,它们具有三种不同的学习目标,并表明这些学习目标改变了关于意义和上下文的信息在它们的神经网络层之间传递的方式。这将导致我们声称,当掩蔽被用作学习目标时,这就是为什么像 BERT 这样的模型比非掩蔽替代方案更好地学习上下文的原因。本帖中我们要看的模型是:
- 机器翻译(MT)模型:这是第一批展示 Transformer 架构在神经机器翻译(NMT)等应用中产生重大改进潜力的模型。
- 语言模型(LM) : LMs 如类 GPT 模型,以及它们的递归神经网络(RNN)前身,通过预测序列中的下一个单词来学习。
- 掩蔽语言模型(MLM) :像伯特这样的 MLM 使用了一种叫做掩蔽的方法,他们试图预测文本序列中的一个随机单词。这对这些模型的工作方式有着非常不同的影响,我们很快就会谈到这一点。
表现:变压器模型如何相似
From a high level, we can think of the Transformer models as composed of two main “parts”; one where they represent the text, and the other where they learn from the text. Source: Author
所有变压器模型在其神经网络设计中共享一个通用方法。我们可以认为这种常见的方法有两个主要部分。尽管所有这些网络之间存在许多细微差别,但我们可以从高层次上将它们视为在方法上有些相似。这些网络的两个主要部分是:
- 表示:这是模型接受文本输入并将其表示为我们称之为嵌入的向量的地方。网络的表示部分将文本信息编码成网络可以“读取”或处理的格式。这包括对单词在句子中的位置以及它与句子中其他单词的关系进行编码。
在《变形金刚》中,这种信息编码是通过“注意”机制实现的(注意机制的细节超出了本文的范围,我们在这里详细讨论了;或者,这篇帖子很好地说明了注意力在基于变压器的网络中是如何工作的。对于这篇文章,我们需要知道的是,注意力是一种将关于我们输入文本的信息编码为矢量格式的机制——一种嵌入——我们的神经网络可以处理。 - 学习:一旦 Transformer 模型可以将文本表示为嵌入,我们就可以创建一个学习目标,使模型能够从文本中“学习”。我们希望学习目标将使这些模型能够学习诸如句法结构、语义和上下文之类的东西,这将使它们能够在广泛的语言任务中表现良好。学习目标规定了这些模型可以从它们的训练数据中学习什么,因为它控制了信息如何通过它们的网络流动。
学习:变压器型号有何不同
Each Transformer model can have its learning objective which defines how information flows through the network and how it learns from the input. Source: Author
每个变压器模型可以创建其学习对象,该学习对象将来自网络表示部分的输出作为其输入。表示部分由记号赋予器和注意层组成,它们接受输入并将其转化为嵌入,以矢量格式表示输入文本。
学习目标的选择对变压器模型至关重要,因为这种选择-
- 定义网络如何处理输入:根据学习目标,变换器模型将一次处理一个令牌(单词)的输入,即它将不能“预测”下一个单词;或者它将能够“双向”处理文本,并在学习时访问过去和未来的标记。
- 定义信息如何在网络中流动:学习对象的目标将定义信息如何在网络中流动。这一点很重要,因为像 Transformer 或 BERT 模型这样的深度神经网络由多层组成,每一层都建立在前一层的输出之上。
理想情况下,我们希望每一层都在输入中添加信息,并建立在前一层所学的基础上。这样,理论上,随着信息从较低水平传递到较高水平,深度神经网络应该建立更复杂和更高水平的知识。学习对象将定义这个信息流。
例如,在图像处理中使用的卷积神经网络(CNN)中,网络的低层学习一般细节,如形状的边界和轮廓。然后,更高层学习更多细微的细节,如面部特征和区分同一类型图像的细节方面。如果图像是一只猫,较低层将识别猫的一般格式,并能够将其与狗或人区分开来,较高层将能够区分不同类型的猫。理想情况下(我们不确定,但似乎有可能),我们希望语言模型以类似的方式从文本中学习信息,以便更高层理解语义和句法特征。
我们在这篇文章中看到的三种模型有不同的学习目标:
- 机器翻译(MT)学习目标 : MT 模型获取输入文本,并尝试预测目标句子中的单词。输入句子可以是英语,而目标句子是该句子的法语翻译。ML 模型与这里讨论的其他模型略有不同,因为表示层嵌入不用于直接预测输出。相反,对于 MT 模型,表示层的输出被传递给解码器,解码器将嵌入内容转换回目标语言。出于我们的目的,我们仍然可以用与其他模型相同的方式来考虑它,即学习目标试图预测给定不同源语言输入的目标句子的单词。
- 语言模型(LM)学习目标:像 GPT 这样的 LM 模型试图根据输入句子中的前几个单词来预测下一个单词。因此,学习目标只能访问句子中过去的单词,即它不能“期待”句子中的下一个单词来帮助它预测当前单词。这样,LM 模型被称为是单向的,因为它只从输入开始到输入结束“读取”文本。
- 屏蔽语言模型(MLM) : 学习目标 : MLMs 不是预测下一个单词,而是试图预测从输入中随机选择的“屏蔽”单词。这是完形填空的一种形式,要求参与者预测一个句子中隐藏的单词。它通常被用来评估一个人的语言能力。由于完形填空测试要求用户能够看到所有的文本以理解缺失单词的上下文,所以 MLMs 需要能够同时“看到”所有的文本。这与 LMs 相反,LMs 在预测当前单词时只看到过去的单词。这就是为什么我们说 MLM 是“双向的”,因为它们可以访问当前预测单词前后的单词。
掩蔽语言模型(MLM)的表现更好吗?
Example of a person dragging words to missing spaces in a cloze test. Source: Wikipedia
我们想要回答的关键问题是,MLM 模型是否比其他模型更擅长学习语言信息。虽然这是一件很难衡量的事情,因为我们首先不知道人类是如何学习语言的,但我们可以通过观察网络各层之间输入信息的变化来估计它。具体来说,我们关注的是:
- 模型如何学习输入:最初,模型需要理解给定的输入。它会查看可用的信息,并在生成学习目标所需的输出之前,尽可能多地尝试和理解输入。
- 模型如何使用该信息来执行其任务:学习目标定义了我们模型的目标,即它试图预测什么。一旦模型了解了一些可用的输入,它就可以关注它需要生成的输出,以尝试并满足学习目标的要求。
例如,假设给你一个类似于完形填空测试的任务,并要求你预测遗漏的单词:
“这是我看过的[ 漏字 ]帖子”
在这种情况下,你可以先读完整个句子,然后把注意力集中在缺失单词的上下文上。一开始,你不会去想那个缺失的单词本身。你会看它前后的单词,并把它们作为预测丢失单词的依据。
一旦你思考了周围的单词,你就试着去思考缺失的单词。你为这个词想出了一些可能的选项,并考虑它们是否符合上下文。在这种情况下,你会(很明显地)想到像“最好的”、“最伟大的”、“完美的”、“杰出的”、“最好的”这样的词,并选择其中一个你认为正确的。
一些研究人员已经研究了网络之间信息变化的这个方面,并试图估计不同层之间丢失或获得的信息。他们用几种不同的方法来做这件事。一种方法是查看不同阶段输入层和输出层之间获得或丢失的信息,即输入层和第二层之间的差异、输入层和第五层、第 n 层之间的差异等等。
通过这样做,他们表明:
- 机器翻译在各层之间变化较小:由于机器翻译不预测完形填空类型学习对象的标记,因此它在连续各层的表示之间变化较小。你可以在下图中看到这一点,它显示了层与层之间越来越少的变化。它试图翻译整个句子,所以它没有我们上面提到的两步焦点变化,其中模型理解上下文,然后预测标签。
- 语言模型显示伪造和重新记忆的证据:相比之下,LMs 显示出更多的层间变化,因为它们最初专注于周围单词的上下文,层间的差异逐渐变小。然后,当模型试图预测下一个单词时,我们会看到各层之间的较大变化。
- 掩蔽语言模型显示了层间最大的变化:MLM 在经历这两步过程时,显示了层间更加明显的变化。最初,MLM 丢弃或“丢失”它现在不需要的信息,即该模型关注周围单词的上下文,而不是它试图预测的缺失单词。然后,当它将注意力转移到重新记住与它试图预测的丢失标记相关的信息,即学习目标时,我们看到各层之间的表示发生了巨大变化。
被掩盖的语言模型秘密:上下文编码和标记重构
MLMs 的类似完形填空的任务,即屏蔽输入中的随机单词,似乎迫使变压器模型经历两个部分的过程,研究的作者描述为:
- 上下文编码:正如我们之前提到的,这是模型被迫关注周围单词的上下文,而不是学习目标的目的。MLMs 似乎比 LMs 做得更好,因为它们可以访问输入文本中的整个单词数组,而不像 LMs 那样只能访问前面的单词。与 LMs 或 MLMs 类似,MT 模型没有预测输出需求任务的标签,因此它们甚至更快地“忘记”与正在处理的当前单词相关的信息。
- 记号重建 : MLMs 显示了一个清晰的第二阶段,其中模型将焦点从学习上下文转移到试图预测丢失或被屏蔽的单词。为此,它会尝试“恢复”或“重建”关于当前输入令牌的信息。它这样做是因为它试图理解当前标记与句子中其他标记的相关程度。在上下文编码阶段,模型查看周围的工作,例如,如果当前单词是“银行”,我们可以首先查找类似“钓鱼”或“金钱”的周围单词,以了解我们所指的是什么类型的“银行”,即,一条河“银行”或一个金融机构。
该研究中另一个实验的下图更清楚地显示了这一两阶段过程的影响:
在上图中,我们可以看到,MLM 开始重建关于当前输入令牌的信息,而 ML 和 LM 继续丢失与输入令牌相关的信息,即它们没有明确的“重建”阶段。
像 BERT 这样的屏蔽语言模型受益于未来
像上面提到的那些研究表明,MLM 似乎是像 BERT 这样的大型预训练模型的更好选择,这些模型需要有更一般的语言知识,以便在下游 NLP 任务中表现得更好。
这似乎与 MLMs 可以访问输入文本中的未来信息的事实有关,因为 Transformer 架构提供了双向特性。还记得引言吗,我们提到掩蔽是至关重要的,但它取决于注意力?Transformer 架构的注意机制使模型能够在学习与当前输入令牌相关的周围单词的上下文时“展望”未来。
语言模型也可以使用这一功能,因为它们使用相同的底层架构,但是它们的学习目标的性质意味着它们只查看句子中以前的单词。他们不会“预测”接下来的单词,因为这会给网络设计带来一些问题。例如,如果模型可以访问输入中的所有标记,那么从理论上讲,它可以“欺骗”并总是预测正确的下一个单词,而无需学习任何东西。
因此,在模型简单性和学习目标收益之间有一个权衡。MLM 使模型了解更多关于输入文本的信息,但这是以更大的模型复杂性为代价的。
例如,GPT-3 是一种 LM 模型,它使用转换器架构的解码器部分来预测输入序列中的下一个字。像 GPT-3 这样的模型可以通过在更大和多样化的数据集上进行训练来补偿学习目标的差异。GPT-3 的表现表明,你仍然可以通过这条路线实现 SOTA 的结果。
然而,从上面的结果,我们可以看到 MLM 的好处,由于明确的上下文编码和令牌重建阶段。如果你想从你的输入文本中学习上下文,那么传销从更少的内容中学到更多。这似乎与 MLMs 访问当前输入令牌周围的未来上下文的能力有关。
这种环境优势是 MLM 在许多 NLP 任务中表现的关键。但是,有些任务不适合这种双向访问。就像在 GPT X 这样的文本生成模型中,主要目标是预测文本序列中的下一个单词。这些模型可能适合通过 LM 学习目标进行训练,以增强它们在给定输入任务的情况下预测未来单词的能力。
BERT 中的遮罩是如何工作的?
在我们结束之前,有必要特别了解一下 BERT 及其屏蔽过程实现。
在前面几节中,我们讨论了屏蔽的一般概念,即屏蔽掉一个随机单词。听起来相对容易。
但是,如果您过去曾经使用过 Transformer 模型,您会知道事情会变得更加复杂。BERT 掩蔽实施有两个主要部分:
- 屏蔽 15%的输入标记:BERT 中的屏蔽不只是屏蔽一个标记。相反,它随机选择 15%的输入标记并屏蔽它们。15%是通过试错法选择的。如果你屏蔽了更多的信息,那么模型就很难从输入中学习,因为隐藏了太多的信息。如果您使用少于 15%,可能需要更长的时间和更多的数据来学习足够的上下文,因为模型可能更容易预测丢失的令牌。
- 屏蔽记号,正确记号,或错误记号:这就是开始变得有点奇怪的地方!在选择了我们想要屏蔽的令牌之后,我们需要决定我们是否真的想要屏蔽它们。我们最终不会屏蔽掉我们说过的所有令牌。相反,我们有另一个随机选择过程(我们将在下面讨论),我们选择添加一个隐藏单词的掩码标记,或者用随机选择的不同单词替换该标记,或者用我们最初打算掩码的正确单词替换该标记。听起来很奇怪,对吧?确实是,下面我们会尽量澄清。
戴口罩还是不戴口罩?
一旦我们选择了要屏蔽的 15%的输入标记,我们就需要决定是否要屏蔽这些标记。他们在 BERT 中的工作如下:
- 80%替换为[MASK]标记:对于 80%的选定输入,标记被替换为[MASK]标记,类似于前面提到的经典完形填空测试。
- 10%替换为不正确的单词:对于 10%的所选输入,令牌被另一个随机选择的单词替换,该单词的唯一要求是与所选令牌不同。
- 10%替换为正确的单词:剩余 10%的时间,所选令牌简单地替换为正确的令牌。
伯特论文的作者指出,这些不同比率的选择有些武断,是在反复试验的过程中选择的。然而,这背后的想法似乎与:
- 如果你一直使用掩码标记:仅仅使用掩码标记导致模型很少了解周围单词的上下文。这似乎是因为模型知道它可以“忘记”关于周围单词的所有信息,而只关注目标单词。这类似于类似 LM 的方法,意味着 MLM 没有创建我们之前看到的清晰的两阶段、上下文编码和令牌重建信息流,这对于 MLMs 中看到的改进学习非常重要。
- 如果你使用掩码标记和正确的单词:为了解决这个缺点,你可以在 80%的时候使用掩码标记,然后在 20%的时候用正确的单词替换标记。但是有一个问题。模型将知道当掩码不存在时,那么单词是正确的,并且它不需要学习任何东西,只要保持当前单词,因为它是正确的。换句话说,该模型可以“欺骗”而什么也学不到,因为它知道非屏蔽令牌总是正确的。
- 如果您使用了掩码标记和错误的单词:或者,如果您只是一直使用错误的标记,那么当掩码没有出现时,模型将知道所选的标记是错误的标记,并且它会将其视为另一个掩码,即您可能会遇到与上述相同的问题。
结果是,理想的 80/10/10 分割迫使 BERT 了解输入中所有令牌的更多信息,而不仅仅是当前的输入令牌。或者正如作者指出的、这个过程的优点是变换器编码器不知道它将被要求预测哪些单词或者哪些单词已经被随机单词替换,因此它被迫保持每个输入标记的分布式上下文表示。
关于掩蔽的最新研究?
别担心,我写这篇文章的时候没有中风。这一节的标题是故意弄乱的,以表明词序对我们理解语言的重要性。这就是为什么单词袋 TF-IDF 模型在一些 NLP 任务中表现不佳。他们不保持输入句子中的词序,所以当输入句子的顺序对正在执行的任务很重要时,他们最终会失去一些意义。
像 BERT 这样的 MLM 被认为保留了输入文本中单词的顺序和位置。这经常被认为是他们性能提高的原因。同样,类似完形填空的任务被认为迫使这些模型学习语言的句法和语义方面。这与 Word2Vec 等模型形成对比,word 2 vec 仅从大量文本的分布属性中学习语义。
两篇新的研究论文对掩蔽和 MLMs 给予了更多的关注,并表明我们仍然没有完全理解这一新学习目标的所有细微差别和复杂性:
- 掩蔽语言建模和分布假设:语序对 Little 来说很重要:这篇论文声称单词的语序对 MLMs 来说并不重要。他们通过随机改变输入的句子来做到这一点,并表明这对一系列 NLP 任务的整体性能影响有限。他们认为 MLMs 性能提高的原因可能是:
- 构造糟糕的 NLP 评估框架:这些模型的测试和评估方式对于这些模型来说可能过于简单。因此,结果不能反映模型在语言任务中的实际能力。相反,作者声称这些评估框架需要改进,以跟上 BERT 等变压器模型的性能。我们在早先的帖子中谈到变压器模型的潜在极限时也提到了这一点。
- MLMs 对高阶单词共现统计建模的能力:该论文声称 BERT 表现如此之好完全是因为它能够从共现数据中学习。掩蔽和注意力只是让 BERT 比 Word2Vec 这样的模型学到更多的信息。
- 关于掩蔽语言建模的归纳偏差:从统计到句法依赖;这篇论文聚焦于 MLMs 的完形填空任务方面并声称:
- MLMs close 任务不是真正的 close 任务:因为 MLMs 随机选择要屏蔽的单词,所以它不是一个监督式完形填空测试。在完形填空测试中,通常选择缺失的单词,以确保问题要求参与者了解该语言的句法性质。随机选择这些缺失的单词意味着一些常见的或无意义的单词,如“这个”、“那个”、“那个”等可以被选择用于完形填空。根据任务的不同,这些单词在强迫模式学习对该任务有意义的东西时可能没有用。
- MLMs 不直接学习句法:人们认为类似完形填空的测试迫使模型学习关于语言句法结构的信息。然而,该论文声称表明,相反,MLM 学习标记之间的直接统计依赖性,但这使得模型能够间接学习语法。以前人们认为仅仅从统计推断中学习句法是不可能的。这篇论文声称,传销确实表明这是可能的
摘要
总而言之,让我们重温一下我们在引言中提到的最初的四点:
- 掩蔽是 BERTs 成功的关键:我们看到研究表明掩蔽作为一个学习目标如何改变像 BERT 这样的深度学习变压器神经网络中的信息流。这种变化创建了一个两阶段学习过程,其中模型首先执行上下文编码来学习当前输入单词周围的单词。然后它执行记号重建,试图预测通过类似完形填空的 MLM 测试选择的输出单词。这种两阶段过程的性质似乎将 MLMs 与 LMs 和 MT 等其他方法区分开来。
- 屏蔽需要注意:虽然屏蔽使 BERT 表现出色,但它仍然需要 Transformer 架构来实现。Transformer 网络使用注意力来双向处理文本,这意味着模型可以在当前输入标记之前和之后查看文本。非 MLM 没有充分利用转换器的这一特性,因此在学习语言特性的能力方面没有以同样的方式受益。
- 我们仍然不理解掩蔽:我们看到最近的研究提出了更多关于掩蔽如何提高性能的问题,而不是答案。然而,这并不意味着掩蔽不起作用。它只是以我们没有想到的方式运作。事实上,我们讨论的一篇论文表明,我们可能能够仅从统计推断中学习高级知识,如语言句法。直到最近,我们才认为这是可能的。
- 我们需要重新评估我们学习语言的方式吗?如果真的有可能从同现统计这样简单的东西中学习语义和句法知识,那么这对语言意味着什么呢?具体来说,它对我们如何学习语言有什么看法?虽然这是一个有争议的研究领域,但人们认为语言是一个复杂而独特的知识领域,需要独特的类似人类的技能来理解它。但也许我们和伯特这样的模特一样,通过识别常见单词何时一起出现来学习语言?如果是这样的话,那么为什么我们似乎比这些模型更快地学习像语义这样的东西,并且使用更少的数据?通过这种方式,当我们努力更好地理解像 BERT 这样的模型和像掩蔽这样的学习目标时,我们也可以更多地了解我们学习和理解语言的独特能力。
最后,是不是所有的模特都应该像伯特一样接受蒙版?与 NLP 和机器学习中的大多数事情一样,答案是:
看情况!
我们可以自信地说,大多数模型都可以通过使用像预训练中的掩蔽这样的方法来改进。然而,在某些情况下,如文本生成,像掩蔽这样的学习目标可能不适合该任务,因为在输入中访问未来的单词可能与特定任务的目标相抵触。对于更一般的模型,目标是训练一个具有一般语言能力并能在广泛的下游任务中表现良好的模型,看起来这些类型的模型确实会受益于类似掩蔽的方法。
参考
这种对变压器的研究主要来源于像 Lean Voita 这样的人的惊人工作。她的博客是所有变形金刚资料的一个很好的来源,甚至还有她教的的课程的资料,非常值得一看。像 Lean 这样的人解释这些模型内部发生了什么的工作对于提高我们对像 BERT 这样的模型如何学习语言任务的理解是至关重要的。在这篇文章中,我特别提到了她的工作:
关于 BERT 和 Masking 的更多细节,值得查看的其他文章有:
Cathal Horan
在 Intercom 的 ML 团队工作,在那里他创造了人工智能产品,帮助企业提高支持客户和与客户沟通的能力。他对哲学和技术的交叉感兴趣,尤其着迷于深度学习等技术如何能够创建有朝一日可能理解人类语言的模型。他最近完成了商业分析理学硕士学位。他的主要学位是电气和电子工程,但他也拥有哲学学位和精神分析研究的哲学硕士学位。
阅读下一篇
关于正在重塑人工智能格局的 BERT 和 Transformer 架构,你需要知道的 10 件事
25 分钟阅读|作者 Cathal Horan |年 5 月 31 日更新
目前,很少有人工智能领域比 NLP 更令人兴奋。近年来,可以执行类似人类语言任务的语言模型(LM)已经发展到比任何人预期的更好。
事实上,他们表现得如此之好,以至于人们怀疑他们是否达到了一般智力的水平,或者我们用来测试他们的评估标准跟不上。当像这样的技术出现时,无论是电力、铁路、互联网还是 iPhone,有一点是明确的——你不能忽视它。它将最终影响现代世界的每一个部分。
了解这样的技术很重要,因为这样你就可以利用它们。所以,我们来学习吧!
我们将涵盖十个方面,向您展示这项技术的来源、开发方式、工作原理以及在不久的将来会有什么样的前景。这十件事是:
- **什么是 BERT 和变压器,为什么我需要了解它?**像 BERT 这样的模型已经对学术界和商业界产生了巨大的影响,因此我们将概述这些模型的一些使用方法,并澄清围绕它们的一些术语。
- 在这些模型之前,我们做了什么?要了解这些模型,重要的是要了解这一领域的问题,并了解在 BERT 等模型出现之前我们是如何解决这些问题的。通过这种方式,我们可以了解以前模型的局限性,并更好地理解 Transformer 架构关键设计方面背后的动机,这是大多数 SOTA 模型(如 BERT)的基础。
- **NLPs“ImageNet 时刻;预训练模型:**原来我们都是自己训练模型,或者你要针对某个特定任务,全面训练一个模型。实现性能快速发展的关键里程碑之一是创建预先训练的模型,这些模型可以“现成”使用,并根据您的具体任务进行调整,只需很少的努力和数据,这一过程称为迁移学习。理解这一点是理解为什么这些模型在一系列 NLP 任务中一直表现良好的关键。
- **了解变形金刚:**你可能听说过伯特和 GPT-3,但是关于罗伯塔、艾伯特、 XLNet ,或者龙前、改革者,或者 T5 变形金刚呢?新模型的数量看起来势不可挡,但是如果您理解 Transformer 架构,您将有机会了解所有这些模型的内部工作方式。这和你理解 RDBMS 技术的时候是一样的,让你很好的掌握 MySQL、PostgreSQL、SQL Server 或者 Oracle 之类的软件。支撑所有数据库的关系模型与支撑我们的模型的转换器架构是一样的。明白了这一点,RoBERTa 或 XLNet 就成了使用 MySQL 或 PostgreSQL 的区别。学习每个模型的细微差别仍然需要时间,但是你有一个坚实的基础,你不是从零开始。
- 双向性的重要性:当你读这篇文章时,你并没有严格地从一边读到另一边。你不是从一边到另一边一个字母一个字母地读这个句子。相反,你正在向前跳跃,从你现在所处的位置之前的单词和字母中学习上下文。事实证明,这是变压器架构的一个关键特性。Transformer 架构支持模型以双向方式处理文本,从开始到结束,从结束到开始。这是以前模型局限性的核心,以前的模型只能从头到尾处理文本。
供应链中机器学习的用例、算法、工具和示例实现
我们生活在一个速度至关重要的时代。你的公司交付产品越快,获利越快。速度是公司竞争的关键领域。
与机器相比,人类速度慢且不一致。管理交付系统的端到端流程(从获取数据、管理数据、理解数据到做出决策)可能会非常困难和累人。
公司需要一个机器人伴侣,能够在日常和重复任务中表现出色,而不会感到疲劳——人工智能和机器学习。
需求不确定,供应有风险,竞争激烈。供应链(SC)的卓越通常依赖于组织整合端到端流程的能力,这些流程包括获取材料或组件、将它们组装成产品以及将它们交付给客户。
人工智能在改善人类决策过程和商业项目的后续生产力方面显示出巨大的潜力。它可以识别模式,学习商业现象,寻找信息,并智能地分析数据。
在本文中,我们将讨论:
- 人工智能在供应链中的潜在作用,
- 必要的算法,
- 工具和框架,
- 三个著名公司在供应链中使用 AI 的案例研究,
- 结论。
AI/ML 在供应链中的用例
供应链管理已经成为数据密集型。如今,所有的信息都被收集并存储在数据中心,仓库、运输设备的需求可以被替代。
可以获得大量的数据。专业人士知道它对 SCs 有多重要,在人工智能(AI)的帮助下他们可以利用它,提出优化的解决方案,并构建可以帮助他们做出更好决策的工具。
预测客户行为
客户是不确定的,并根据情绪采取行动。然而,供应链的成功取决于客户数据及其行为。
为了预测客户行为,提出了许多基于电子表格的方法,但随着大数据的兴起,这些方法被证明是过时的。电子表格模型在需求预测方面失败的主要原因是它们对于大规模数据不可伸缩。它们带来了供应链管理中的复杂性和不确定性,无法通过简单的统计方法(如移动平均或指数平滑)提取、分析和解决。
这种不一致的订单模式会导致团队之间的沟通不畅,并降低生产力。对许多公司来说,不一致订单量的可预测性是一个挑战。在这种情况下,AI 和 ML 在最佳水平上更早地为我们提供了客户行为不一致性的更接近预测。
预测能力有助于需求预测
需求预测是预测分析的一个领域,在这里,公司预测整个供应链中对产品和装运的需求,即使是在不可控的条件下。
如前所述,传统方法(电子表格模型、统计模型、移动平均和指数平滑)由于影响供应链需求的大量参数而受到限制,这使得这些方法过于简单且极不准确。
在这方面,预测只能提供对供应链中需求变化的部分理解。此外,无法解释的需求变化可以简单地认为是统计噪声**,这就是为什么它们在本质上是非线性。因此,传统的或简单的模型不能映射重要的和非线性的特征。**
幸运的是,机器学习提供了可以映射重要和非线性特征的算法,并将它们减少为变量,这些变量可以帮助理解过去,准确预测未来事件,帮助他们改善关于现金流、风险评估、容量规划和劳动力规划的决策过程,并满足客户需求。
一些人工智能驱动的需求预测工具包括:
- 我爱 CRM
- 胶囊!胶囊
- 蜂鸟
- 合夥人
- 效果管理器
- 未来市场
- 管道驱动
- 智能需求计划程序
避免收费风险
如前所述,客户是情绪化的。如果交货延迟,他们可能会重新考虑购买。或者购买产品后要求退款。
这最终会导致罚款,其中可能包括运费、税款和其他费用。通过像亚马逊使用的集成人工智能,公司可以分析数据,找到最近的配送中心,并减少交付时间。
这种系统可以分析延误的原因和失败的原因,比如合作伙伴之间的争执或与恶劣天气有关的灾难。
感知市场形势
市场是建立在人类情绪的基础上的,这使得整个市场变得不可预测和难以理解。
借助人工智能和深度学习系统,我们可以从天气、就业、季节等数据中发现人类行为模式,并帮助公司在仓库存储产品和优化配送系统方面进行良好投资。
这种用于研究市场的模式识别系统可以帮助公司改善其产品组合,并提供更好的客户体验。
提高跟踪出发和到达订单的准确性
供应链管理系统与不同的区域配送中心相互连接,这些中心通过运输连接在一起。但是有一些配送中心是为了运输而谨慎连接的。
这引起了企业对能否按时履行合同承诺的担忧。人工智能可以提供实时预测可见性,知道产品在任何给定时间的准确位置,以进行智能决策并提高交付准确性。
Fourkites 是为供应链提供实时可见性的一个很好的例子。
改进交货时间和降低成本的遗传算法
在物流业务中,时间和速度至关重要。公司可以使用基于遗传算法的路线规划器来规划最佳的送货路线。
人们认为,人工智能将为供应链、交付和物流过程设立一个新的效率标准。该系统正在快速变化,以自动化、智能化和更高效的方式创造了全球物流公司管理数据、运营和服务客户的“新常态”。
改善客户体验
一个企业要成功,顾客必须满意。有一件事可以帮助满足他们,那就是在正确的时间推荐正确的产品。机器学习很好地做到了这一点。
基于客户兴趣的推荐系统可以集成在移动或 web 应用程序中,这样客户的主页就个性化了。
PS:几乎所有热门 app 都有推荐系统。
智能仓库效率更高
智能仓库是一个完全自动化的设施,其中大部分工作是通过自主机器人或软件完成的。在此过程中,复杂的任务变得简单,操作变得更具成本效益。
阿里巴巴(Alibaba)和亚马逊(Amazon)通过自动化的使用,将他们的仓库改造成了一个效率的乌托邦。
供应链算法
卷积神经网络
卷积神经网络(CNN)是一种通常用于处理图像识别的算法,但事实证明,CNN 对于预测也非常有用。
CNN 以从数据集中提取有用的模式和特征而闻名。这使得 CNN 在解决分类和回归问题时非常可靠。
CNN 的一个优势是它们共享参数,即与其他分类算法相比,它们需要更少的超参数和更少的监督。
这就是为什么 CNN 是供应链管理中应用最广泛的算法之一。这里有几个使用 CNN 的例子:
图像分类
- 图像分类用于识别给定图像的类别。它在供应链中非常方便,因为它可以在一个实例中对不同的产品进行分类,并相应地将它们分开。
目标检测
- 对象检测将帮助您立即识别不同的对象。
- 在供应链管理中,你会同时遇到很多产品。手动分离这些产品非常昂贵和耗时。物体检测可以帮助你在没有任何人为干预的情况下,快速识别物体并进行分类(使图像分类更加精确)。
图象分割法
- 图像分割是另一种算法,它使用 CNN 在物体本身周围创建一个像素级的遮罩,从而了解物体的尺寸。
自主移动机器人
- 自主移动机器人使用 CNN识别路线和导航到仓库中指定的区域。这些类型的自动化机器人减少了仓库管理中的错误,也减少了仓库中的人工参与,最终降低了事故风险。
- 这些机器人使用图像分类、物体检测和图像分割在仓库中导航,为物体找到适当的指定名称,并知道物体的尺寸,以及避开路上的障碍物。
- 一些公司如IAM Roboticsgrey orange和 Bleum 提供移动机器人采摘解决方案,可以提高生产力水平。
预测
- 如前所述,CNN 的能力在于它可以提取有用的模式和特征或表示,使它们在预测销售和未来需求时非常有效。
递归神经网络
递归神经网络(RNN) 是一种用于处理序列数据的神经网络,包括文本、句子、语音或视频,或者任何具有序列的东西。
RNN 通过评估输入的先验信息并预测后验或下一个信息来工作。
rnn 在预测上下文信息方面很有用,如完成一个不完整的句子或语音序列。
自然语言处理
自然语言处理 (NLP)处理:
情感分析–每个公司都需要来自客户的反馈,这通常来自每个产品的评论部分。手动检查每一份评估,并将其分为好的、差的以及介于两者之间的任何等级,这可能是一项单调乏味的工作。通过情感分析,公司可以根据顾客提供的评论和评级来区分好的和坏的产品。这有助于改善用户体验。
聊天机器人是改善用户体验的另一种方式。客户可以与机器人谈论他们的问题或反馈,NLP 可以帮助机器人理解他们。
预测
如前所述,RNN 用于处理序列数据,因此在需求和销售预测中非常有用。
预测的关键架构之一是长期短期记忆 (LSTM)。LSTM 是一种已经被用于时间序列预测的架构。当像 CNN 这样的好的特征提取算法被添加到它上面时,预测的能力提高了。
集成方法
集成方法结合了两种或多种方法来实现给定的结果。由于供应链与理解数据有很大关系,一种方法可能无法提供足够的信息或提取足够的模式来做出任何决策。
一个供应链有几个供应点、几个仓库和来自世界各地的客户,会产生大量的产品,这使得需求预测成为一个高维问题。
为了解决这个问题,数据科学家应用了一种称为二分图聚类的聚类技术,来分析数据和不同产品出现的不同模式。例如,用移动平均模型和贝叶斯信任网络数据科学家创建一个集成模型,现在可以提高预测的准确性。
您可以在本文中了解更多信息:供应链需求预测的预测性大数据分析:方法、应用和研究机会。
作者在这篇论文中得出结论,集合方法产生了最好的精度和最小的预测误差。
表征学习
表征学习是机器学习中的另一种重要方法。表示学习用于提取模式和特征,以:
- 通过生成模型创建现实生活场景,
- 了解数据,
- 降低维度。
表征学习使用 VAE 自动编码器,其中 CNN 架构用于压缩数据,以使其包含潜在变量或主要变量。使用这些变量,我们可以了解数据的行为,并使用它来创建现实生活中的模拟,以防止公司遭受巨大的财务损失。
深度强化学习
供应链有许多组件,包括管理从原材料到制造、仓储和向客户配送的投入。
公司必须尽最大努力高效、优化地完成这些任务,同时尽可能降低成本。优化是关键。
已经投入了大量的时间和精力来构建有效的供应链优化模型,但是由于它们的规模和复杂性,它们可能难以构建和管理。随着机器学习的进步,特别是强化学习,我们可以训练一个机器学习模型来为我们做出这些决定,并且在许多情况下,比传统方法做得更好。
合适的库和框架
数据可视化和分析
数据可视化在任何 ML 项目中都占有重要的地位,在供应链中也是如此。但是由于供应链处理地理空间和时间序列数据可视化,找到正确的库变得至关重要。
空间数据分析
- 叶
- Folium 是一个强大的 Python 库,可以帮助您创建多种类型的传单地图。事实上,follow 结果是交互式的,这使得这个库对于构建仪表板非常有用。要获得一个想法,只需在下一张地图上缩放/点击即可获得印象。
*** 地质公园
* Geoplot 是 matplotlib 的扩展,用于地理空间可视化。
* geoplot 的一个关键特性是它与 matplotlib 的兼容性,这使得它更容易使用。
- NetworkX
- NetworkX 是一个 Python 包,用于创建、操作和研究复杂网络的结构、动态和功能。
- NetworkX 提供:
- 研究社会、生物和基础设施网络的结构和动态的工具;
- 适用于许多应用程序的标准编程接口和图形实现;
- 多学科合作项目的快速开发环境;
- 轻松处理大型非标准数据集的能力。
时间序列分析
【matplot lib】
- Matplotlib 是 python 社区中使用最广泛的可视化库之一,用于创建图表和绘图。它有很多功能,各有所用。在时间序列分析中,matplotlib 提供了几个非常方便的主要函数。它们是:
- 线形图。
- 直方图和密度图。
- 盒须图。
- 热图。
- 滞后图或散点图。
- 自相关图。
- 海博
- 线形图。
Seaborn 是另一个被广泛用于可视化的工具,因为它提供了调色板和通过各种阴影和设计的交互。
-
- Plotly 提供了一个以上库没有的附加功能。它有一个现场互动设计,具有各种功能,如选择,放大和缩小等。
-
机器学习
- 请求预报
ARIMA
ARIMA 代表自回归综合移动平均线。它是一类用于理解时间序列数据中不同标准时间结构的模型。
ARIMA 非常普遍地用于时间序列分析,包括需求预测。
- 使聚集
- Sklearn
- Sklearn 提供了很多不同的函数来对你的数据进行聚类。最常用的聚类算法之一是 K-means 。使用 K 均值,一组 N 个数据点被分组为 K 个聚类,每个聚类的均值成为其识别位置。
深度学习
- 自然语言处理
Transformers 是一个先进的 python 框架,可以与 pytorch 和 tensorflow 一起使用。
变压器提供了各种预训练模型,用于:
- 面向情感分析的文本分类
- 聊天机器人的问答
- 文本摘要
- 文本翻译
- 文本生成
- 计算机视觉
- Pytorch
- PyTorch 是一个科学计算框架,广泛支持机器学习算法。基于 Lua 的脚本语言为深度学习提供了广泛的算法,并使用脚本语言 LuaJIT 和底层 C 实现。
它易于实现和使用。
- 您可以创建自己的定制 CNN 模型,也可以应用迁移学习。
- Pytorch 有一个广泛而活跃的社区,通过它的论坛帮助和支持其他程序员。
- 张量流
- Tensorflow 或 tf 是 Google 开发的。它提供了来自 Google 机器学习社区的良好教育支持,并用于构建简单和复杂的神经网络。
- 与 pytorch 类似,tensorflow 提供了广泛的数学算法来从头构建神经网络。
- Tensorflow 还有 Keras ,这是一个深度学习框架。它是使用最广泛的深度学习框架之一。
- 与 pytorch 和 tensorflow 相比,Keras 简单易用。
- OpenCV
- OpenCV 是一个视觉库,用于分析和处理图像和视频。多用于实时应用。
- 它与 pytorch 和 tensorflow 都兼容。
- 强化学习
- 多级库存系统严重依赖于分布在多个配送中心(DC)的多层供应商,并以外包制造为基础。例如,耐克的分销网络由 7 个区域分销中心(RDC)和 30 多万个 DC 组成;这些配送中心服务于终端客户。
- It is compatible with both pytorch and tensorflow.
我们可以使用 Ray 和 or-gym 建立深度强化学习模型,以优化多级库存管理模型。
雷
Ray 是一个开源框架,为构建分布式应用提供了一个简单、通用的 API。
Ray 打包了 RLlib ,一个可扩展的强化学习库,和 Tune,一个可扩展的超参数调优库。
- RLib 支持深度学习框架,包括 PyTorch、PyTorch Lightning、TensorFlow 和 Keras。
- 或-健身房
- OR-Gym ,一个开源库,用于开发强化学习算法来解决运筹学问题。
- 在供应链管理中实施人工智能的三个用例
- 1.亚马逊智能收入和供应链(IRAS)管理
- 埃森哲的智能收入和供应链(IRAS)平台由埃森哲开发,将 ML 和 AI 模型产生的见解和发现整合到其业务和技术生态系统中。
其目的是改善整个供应链管理系统。IRAS 还负责预测和各种其他模型的优化,确保整个系统是最优的和具有成本效益的。
1. Amazon Intelligent Revenue and Supply Chain (IRAS) Management
2.劳斯莱斯用人工智能重新定义运输货物的安全措施
英国传奇汽车制造商劳斯莱斯(Rolls Royce)与英特尔(Intel)合作设计了一种智能人工智能系统,可以使商业运输更快、更安全。他们声称,这项技术将有能力独立管理导航,障碍检测和通信,开发一个新的自主船只系统。
IRAS objectives and their components | Source: Amazon Blog
在一份声明中,他们表示:“这种合作可以帮助我们支持船主实现导航和操作的自动化,减少人为错误的机会,让船员专注于更有价值的任务。”–劳斯莱斯
3.UPS ORIAN(道路集成优化和导航)
UPS 是一家跨国包裹递送和供应链公司管理公司。UPS 声称每天递送成千上万件货物,每个工作日 UPS 司机平均递送约 100 件货物。
为了确保包裹能够及时轻松地送达,UPS 提供了最优化的导航系统,称为路上集成优化和导航(ORIAN)。它确保 UPS 司机使用在距离、燃料和时间方面最优的递送路线。
据该公司称,“猎户座使用非常先进的算法来收集和处理大量数据,以便他们可以为司机优化路线。这有助于 UPS 以更高效的方式递送和收取包裹。该系统依靠在线地图数据来计算距离和旅行时间,设计最具成本效益的路线。”–猎户座
结论
人工智能在所有行业都在快速发展,它已经被证明是供应链管理中的一个有益工具。如果没有人工智能和人工智能,像亚马逊、耐克、UPS 或沃尔玛这样的公司就不会像现在这样快。人工智能不仅提供了敏捷性、效率和客户满意度,还通过自动驾驶汽车提供了仓库的安全性,使工作流程变得超级顺畅和无错误。
我们讨论的工具、框架和算法为我们提供了一种从消费者的角度理解世界的方式,并构建智能系统(包括混合系统),预测需求、故障、情绪,提供安全性并节省大量资金。
除了我们讨论的所有内容之外,我希望您尝试一下这个供应链模拟。如果你是供应链的新手,这将让你了解供应链管理是如何运作的。
希望你喜欢这篇文章,感谢阅读!
The tools, frameworks and algorithms that we discussed offer us a way to understand the world from a consumer’s perspective, and to build intelligent systems (including hybrid) that forecast demands, failures, sentiments, provide safety and save lots of money.
In addition to all that we discussed, I’d like you to try out this supply chain simulation. If you’re new to the supply chain, this will give you an understanding of how supply chain management works.
Hope you enjoyed this article, thanks for reading!**
使用差异隐私构建安全模型:工具、方法、最佳实践
2020 年的新冠肺炎·疫情让我们看到了生活中不同的挑战,有些是痛苦的,有些则暴露了社会的缺陷。这也让我们记住了适当数据的重要性和缺乏。
世卫组织的这篇文章引用了*“数据的收集、使用、共享和进一步处理有助于限制病毒的传播,有助于加速恢复,特别是通过* 数字接触追踪 。”
它还引用了*“例如,从人们使用手机、电子邮件、银行、社交媒体、邮政服务中获得的移动数据可以帮助监测病毒的传播,并支持联合国系统各组织授权活动的实施。"*稍后发布伦理考量,指导使用数字邻近追踪技术进行新冠肺炎接触者追踪。
这听起来像是一个简单的答案,但确实是一个需要解决的复杂问题。医疗数据是最机密的数据之一,与任何其他个人信息不同,它既可以用于个人,也可以用于个人。例如,医疗保健数据泄露可能导致 COVID 欺诈骗局,我们在过去的一年中已经听说过。
在本文中,我们将继续探讨 ML 隐私,讨论某些问题,并深入研究差分隐私(DP)概念,这是解决隐私问题的方法之一。进一步列出五个您可以使用或贡献的开源差分隐私库或工具。
什么是数据,它是如何创建的?
数据是为参考或分析而收集的事实或统计数据。
我们几乎每天都在创造数据。可能是既有在线又有离线的数据。
比如医院的病人健康记录;学校或学院的学生信息;员工信息和项目绩效的公司内部日志;或者只是简单的记笔记就可以认为是离线数据。
然而,从连接到互联网的在线平台或应用程序收集的数据被视为在线数据,如发布推文、YouTube 视频或博客帖子,或收集用户表现数据的移动应用程序等。
隐私与安全
虽然敏感的个人数据(如癌症患者记录或合同追踪数据)对于数据科学家和分析师来说似乎是一座金矿,但这也引发了人们对收集此类数据的方法的担忧,而且谁来确保这些数据不会被用于恶意目的呢?
术语“隐私”和“安全”经常被混淆,但还是有区别的。安全控制"、谁"可以访问数据,而隐私更多的是关于"、何时"和"什么"类型的数据可以被访问。 “没有安全你不可能有隐私,但没有隐私你可以有安全。
例如,我们都熟悉术语“登录认证和授权”。在这里,认证是关于谁可以访问数据,所以这是一个安全问题。授权是关于什么,什么时候,以及如何 大部分数据对特定用户是可访问的,所以这是一个隐私问题。
私有安全机器学习(ML)
来自数据泄露和数据滥用的风险已经导致许多政府制定数据保护法。为了遵守数据隐私法并最小化风险,ML 研究人员提出了解决这些隐私和安全问题的技术,称为私有和安全机器学习(ML)。
正如 PyTorch 的这篇博文所说:
私有和安全机器学习(ML) 深受密码学和隐私研究的启发。它由一组技术组成,这些技术允许在不直接访问数据的情况下训练模型,并防止这些模型无意中存储有关数据的敏感信息。”
*同一篇博文列举了一些应对不同隐私问题的常用技巧:
联合学习意味着根据存储在世界各地不同设备或服务器上的数据训练您的 ML 模型,而不必集中收集数据样本。
有时,人工智能模型可以记住他们训练过的数据的细节,并可能在以后“泄露”这些细节。差异隐私是一个衡量这种泄露并降低其发生风险的框架。
同态加密让你的数据不可读,但你仍然可以对它进行计算。
安全多方计算允许多方共同执行一些计算,并接收结果输出,而不会暴露任何一方的敏感输入。
当两方想要测试他们的数据集是否包含匹配值,但不想向对方“展示”他们的数据时,他们可以使用 PSI 来这样做。
虽然联合学习和差分隐私可以用来保护数据所有者免受隐私损失,但它们不足以保护模型免受数据所有者的盗窃或滥用。例如,联合学习要求模型所有者将模型的副本发送给许多数据所有者,从而通过数据中毒将模型置于知识产权盗窃或破坏的风险中。通过允许模型在加密状态下训练,加密计算可用于解决这种风险。最著名的加密计算方法是同态加密、安全多方计算和函数加密。
我们将重点关注差分隐私–让我们看看它是如何工作的,以及您可以使用哪些工具。
什么是差分隐私?
“差分隐私描述了数据持有人或管理者对数据主体(所有者)做出的承诺,该承诺如下: 通过允许在任何研究或分析中使用您的数据,您将不会受到不利影响或其他影响,无论有什么其他研究、数据集或信息源可用。”
差分隐私背后的直觉是“ 我们限制如果改变数据库 中单个个体的数据,输出可以改变多少”。
也就是说,如果在同一个查询中从数据库中删除了某人的数据,会改变输出吗?如果是,那么对手能够分析它并找到一些辅助信息的可能性很高。简单来说——隐私泄露!
例如:
Adam 想知道向他的 XYZ 组织捐款的捐赠者的平均年龄。在这一点上,这可能看起来没问题!但是,这些数据也有可能被用来发现特定捐赠者的年龄。简单地说,1000 人进行了捐赠,结果发现捐赠者的平均年龄为 28 岁。
现在,仅通过从数据库中排除 John Doe 的数据进行相同的查询,让我们假设 999 个捐献者的平均值已经变为 28.007。从这一点,亚当可以很容易地发现,约翰多伊是 21 岁。(1000 * 28–999 * 28.007 = 21.007)同样,Adam 可以对其他捐献者重复该过程,以找到他们的实际年龄。
注意:如果 Adam 可以进行逆向工程并获得他们的值,即使每个捐献者的年龄都被加密(例如同态加密),结果也是一样的。
为了避免这种数据泄露,我们添加了受控数量的统计噪声来掩盖数据集中个体的数据贡献。
也就是说,捐献者被要求在提交之前或者在加密他们的年龄之前,给他们的原始年龄加上 100 到 100 之间的任何值。假设 John Doe 在他的原始年龄即 21 岁上加了-30,则加密前登记的年龄将是-9。
这听起来可能很疯狂?!但是,有趣的是,根据概率统计中的大数定律,可以看出,当对这些统计收集的数据取平均值时,噪声被抵消,并且获得的平均值接近于真实平均值(没有添加噪声的数据平均值(随机数))
现在,即使亚当对无名氏的年龄进行逆向工程,-9 也没有任何意义,因此,在保护无名氏隐私的同时允许亚当找到捐献者的平均年龄。
换句话说,差分隐私是 不是 数据库的一个属性,而是查询的一个属性。t 有助于提供输出隐私,也就是说,通过对输出进行逆向工程,某人可以从输入中获得多少洞察力。
在人工智能模型训练的情况下,添加了噪声,同时确保模型仍然能够洞察总体人口,从而提供足够准确的有用预测——同时让任何人都难以从查询的数据中获得任何意义。
注:关于差分隐私的更多细节,请查看我的差分隐私基础系列。
谁在使用差分隐私?
顶尖的科技公司 FAANGs,IBM,都在使用差别隐私,并且经常发布开源工具和库。
最有趣的例子是:
- RAPPOR ,谷歌在这里使用本地差分隐私收集用户的数据,就像其他正在运行的进程和 Chrome 主页一样。
- Private Count Mean Sketch(以及 variances)苹果利用本地差分隐私收集 iPhone 用户(iOS 键盘)表情符号使用数据、单词使用情况等信息的地方。
- 隐私保护的个人健康数据流聚集 论文提出了一种新的隐私保护的个人健康数据流收集机制,其特征在于利用局部差分隐私(Local DP)以固定间隔收集时态数据
- 人口普查局为 2020 年人口普查采用尖端隐私保护 即美国人口普查局将在公布数据前使用差分隐私匿名。
更多信息,请查看我的差分隐私基础系列的本地与全球 DP 博客。
我们真的需要它吗?为什么重要?
从上面 Adam 试图找到捐献者平均年龄的例子可以看出,加密本身不能保护个人的数据隐私,因为去匿名化是可能的。
一个这样的现实世界的例子将是对 Netflix 奖品数据集 的 的 去匿名化,其中对单个订户仅了解一点点的对手可以容易地在数据集中识别该订户的记录。使用互联网电影数据库(IMDb)作为背景知识的来源,有可能成功地确定已知用户的网飞记录,揭示他们明显的政治偏好和其他潜在的敏感信息。
有时,由于深度神经网络的过度参数化导致不必要的数据泄漏,机器学习模型也可能无意中记住单个样本。
例如,设计用于发出预测文本(如智能手机上看到的下一个单词建议)的语言模型可以被探测以发布有关用于训练的单个样本的信息(“我的 ID 是……”)。
这一领域的研究让我们可以计算隐私损失的程度,并根据隐私“预算”的概念对其进行评估。最终, 差分隐私的使用是隐私保护和模型实用性或准确性之间的谨慎权衡。
差异隐私最佳实践
1.了解差别隐私承诺什么和不承诺什么总是很重要的。(参考: 辛西娅·德沃克,《差分隐私的算法基础》 )
- 差分隐私承诺保护个人免受由于他们的数据在私人数据库 x 中而可能面临的任何额外伤害,如果他们的数据不是 x 的一部分,他们就不会面临这些伤害。
- 差别隐私并不能保证一个人认为是自己的**秘密将会保持秘密。**也就是说,它承诺使数据成为不同的私有数据,不会泄露,但不会保护数据免受攻击者的攻击!
Ex:差分攻击是最常见的隐私攻击形式之一。 - 它仅仅确保一个人参与调查本身不会被披露,如果保持有差别的私密性,参与调查也不会导致任何个人参与调查的细节被披露。
2.差分隐私不是数据库的属性,而是查询的属性。(如前所述)
3.添加的噪声量很重要,因为为使数据保密而添加的噪声越高,模型的实用性或准确性就越低。
4.了解局限性,例如:
- 差分隐私一直是学术界和研究界广泛探讨的话题,但由于其强大的隐私保障,在业界较少涉及。
- 如果要在 k 个查询中保持原始保证,则必须注入 k 次噪声。当 k 较大时,输出的效用被破坏。
- 对于一系列的查询,需要添加更多的噪音,这将耗尽隐私预算,并可能最终导致用户死亡。这意味着一旦隐私预算耗尽,用户就不再被允许询问任何更多的问题,如果你开始允许用户之间的勾结,你就开始陷入这个隐私预算对每个用户意味着什么的麻烦中。最终导致用户死亡。
- 单独的差分隐私不能保护用户数据,因为隐私攻击可能会发生!
是的,当然,可以采取各种方法来解决这个问题,但这超出了本博客的范围。
脸书的 Opacus 是一个面向任何人的库,这些人希望以最少的代码更改训练一个具有不同隐私的模型,或者用他们的 PyTorch 代码或纯 Python 代码快速原型化他们的想法。它也有很好的文档,作为一个开源库,如果你感兴趣,你也可以贡献到它的代码库中。
加入 PyTorch 论坛提出任何问题。
资源:
Google 提供了两个开源库(或存储库)wrt 差分隐私。
这是一个包含 3 个构建块库的存储库,用于针对适用于研究、实验或生产用例的 C++ 、 Go 和 Java 支持的数据集生成ε-和(ε,δ)-差分私有统计数据。
而提供的其他工具,如 Privacy on Beam 、random tester、differential Privacy accounting library和命令行接口,用于运行带有 ZetaSQL 的 DP 查询,都是相当实验性的。
就像以前的图书馆一样,这个图书馆对投稿开放,并有一个公共讨论组。
这个库可以称为上面提到的 PyTorch Opacus 库的 TensorFlow 对应物,实现了 TensorFlow 优化器,用于训练具有差分隐私的机器学习模型。这也接受捐款,并有据可查。
资源:
另一个通用库用于试验,调查和开发差分隐私的应用程序,例如使用分类和聚类模型探索差分隐私对机器学习准确性的影响,或者只是开发一个新的应用程序。面向具有不同隐私知识的专家。
资源:
这是 Google 的 Java 差分隐私库的 Python 版本,提供了一组ε差分私有算法,用于生成包含私有或敏感信息的数字数据集的聚合统计数据。现在它被 OpenMined 的 PySyft 库所支持。
PyDP 团队正在积极招募成员,以进一步开发 Google Java 差分隐私库之外的库。
加入 OpenMined Slack #lib_pydp 与团队互动,开始贡献。
资源:
这是 SmartNoise Project 和 OpenDP 之间的合作,旨在将学术知识应用到实际的部署中。
它提供了用于发布隐私保护查询和统计数据的不同私有算法和机制,以及用于定义分析的 API 和用于评估这些分析和组成数据集的总隐私损失的验证器。
也开放贡献,所以如果感兴趣,请随时加入。
资源:
虽然这个项目已被否决,没有得到维护,但它可以用于教育目的。它是为查询分析和重写框架而构建的,目的是为通用 SQL 查询实施不同的隐私保护。
摘要
如您所见,差分隐私是当今数据科学领域的一个重要话题,也是所有顶级科技巨头都关心的问题。
这不足为奇,因为在一个依靠数据运行的世界里,尽最大努力保护这些数据符合我们的利益。
如果你对差分隐私的更新感兴趣,在 Twitter 上关注我,如果你还没有关注过的话,也关注一下海王星。
感谢阅读!
资源或额外读取:
- opened隐私会议(pricon 2020)–视频
- 为什么许多国家在 COVID 联系追踪方面失败了——但有些国家成功了
- 患者数据存在哪些风险?
- 关于公司如何使用大数据的 40 个统计数据和真实例子
- 了解联合学习的类型
- 谷歌联合学习漫画
- 什么是安全多方计算?技术+代码实现。
- 隐私和机器学习:两个意想不到的盟友?
- 差分密码分析教程*
神经网络模型中的消失和爆炸梯度:调试、监控和修复
神经网络模型采用梯度下降的优化算法进行训练。输入训练数据有助于这些模型学习,损失函数衡量参数更新时每次迭代的预测性能有多准确。随着训练的进行,目标是通过迭代调整参数来减少损失函数/预测误差。具体来说,梯度下降算法有一个前进步骤和一个后退步骤,这让它做到这一点。
- 在前向传播中,输入向量/数据通过网络向前移动,使用一个公式来计算下一层中的每个神经元。该公式由输入/输出、激活函数 f 、权重 W 和偏差 b 组成:
该计算向前迭代,直到达到输出或预测。然后,我们计算由目标变量 y(在输出层中)和每个预测 y cap 之间的损失函数(例如,均方误差 MSE)定义的差:
- 有了这个初始评估,我们通过反向传递(也称为反向传播)来调整每层中每个神经元的权重和偏差。为了更新我们的神经网络,我们首先计算梯度,这只不过是损失函数 w.r.t. 权重和偏差的导数。然后,我们推动我们的算法采取梯度下降步骤来最小化损失函数(其中α是学习速率):
在这种情况下,可能发生两种相反的情况:导数项变得非常小,即接近零,而该项变得非常大并溢出。这些问题分别被称为消失和爆炸梯度。
当你训练你的模型一段时间后,性能似乎没有变得更好,很可能你的模型正在遭受消失或爆炸梯度。
本文针对这些问题,具体来说,我们将涵盖:
- 消失和爆炸渐变问题背后的直觉
- 为什么会出现这些梯度问题
- 如何在模型训练过程中识别梯度问题
- 解决消失和爆炸渐变的案例演示和解决方案
- 消失渐变
- ReLU 作为激活函数
- 降低模型复杂性
- 具有方差的权重初始值设定项
- 更好的优化器,具有良好的学习率
- 爆炸渐变
- 渐变剪辑
- 合适的权重初始化器
- L2 范数正则化
- 消失渐变
消失或爆发的渐变——问题背后的直觉
消失
在反向传播期间,权重更新公式中的(部分)导数/梯度的计算遵循链式法则,其中早期层中的梯度是后期层的梯度的乘积:
由于梯度经常变小,直到它们接近零,新的模型权重(初始层的)将几乎与旧的权重相同,而没有任何更新。因此,梯度下降算法永远不会收敛到最优解。这就是所谓的消失梯度问题,这是神经网络不稳定行为的一个例子。
爆炸
相反,如果随着反向传播的进行,梯度变得越来越大,甚至越来越小,我们将会以具有大的权重更新的爆炸梯度结束,导致梯度下降算法的发散。
为什么会出现渐变消失或爆炸的问题?
有了对什么是消失/爆炸梯度的直观理解,您一定想知道-为什么梯度首先会消失或爆炸,也就是说,为什么这些梯度值在通过网络返回时会减小或爆炸?
消失
简而言之,当我们在隐藏层中使用 Sigmoid 或 Tanh 激活函数时,就会出现渐变消失的问题;这些函数将一个大的输入空间压缩成一个小的空间。以乙状结肠为例,我们有以下概率密度函数:
对参数 x 求导,我们得到:
如果我们想象 Sigmoid 函数及其导数:
*Sigmoid function and its derivative | Source: Author *
我们可以看到,Sigmoid 函数将我们的输入空间压缩到[0,1]之间的范围内,当输入变得相当小或相当大时,这个函数 在 0 或 1 处饱和 。这些区域被称为“饱和区域”,其导数变得非常接近于零。同样适用于在-1 和 1 处 饱和 的 Tanh 函数。
假设我们有位于任何饱和区域中的输入,我们将基本上没有梯度值传播回来,导致早期层权重的零更新。通常,对于只有几层的浅层网络来说,这不是什么大问题,但是,当我们添加更多层时,初始层中梯度的消失将导致模型训练或收敛失败。
这是由于将这些小数字的 n 相乘以计算 n 层网络中早期层的梯度的效果,这意味着梯度随着 n 呈指数下降,而早期层训练非常缓慢,因此整个网络的性能下降。
爆炸
转到爆炸梯度,简而言之,这个问题是由于分配给神经网络的初始权重造成了巨大的损失。大的梯度值可以累积到观察到大的参数更新的点,导致梯度下降振荡而不会达到全局最小值。
更糟糕的是,这些参数可能太大,以至于溢出并返回无法再更新的 NaN 值。
如何识别渐层消失或爆炸的问题?
承认梯度的问题是我们需要避免或解决的事情,当它们发生时,我们应该如何知道一个模型正在遭受消失或爆炸的梯度问题?以下是一些迹象。
消失
- 在后面的层的参数中观察到大的变化,而前面的层的参数变化很小或保持不变
- 在某些情况下,随着训练的进行,早期层的权重可以变成 0
- 该模型学习缓慢,并且经常多次,在几次迭代之后训练停止
- 模型性能很差
爆炸
- 与消失的情况相反,爆炸梯度显示出它本身是不稳定的,批量/迭代与批量/迭代之间的大参数变化
- 模型权重可以很快变为 NaN
- 南也失去了模特
修复消失或爆炸渐变问题的实践
记住这些梯度问题的指标,让我们探索潜在的补救措施来解决它们。
- 首先,我们将关注于消失场景:模拟一个遭受此问题的二进制分类网络模型,然后演示各种解决方案
- 出于同样的原因,我们将在稍后用一个回归网络模型来解决爆炸场景
通过解决不同类型的深度学习任务,我的目标是演示不同的场景供您带走。还请注意,本文致力于为您提供实用的方法和技巧,因此我们将只讨论每种方法背后的一些直觉,而跳过数学或理论证明。
如上所述,由于观察是确定这些问题的关键部分,我们将使用 Neptune.ai 来跟踪我们的建模管道:
import neptune.new as neptune
import os
myProject = 'YourUserName/YourProjectName'
project = neptune.init(api_token=os.getenv('NEPTUNE_API_TOKEN'),
project=myProject)
project.stop()
渐变消失时的解决方案
首先我们定义几个 helper 函数来训练并在 Neptune.ai 中登录我们的模型。
- 记录梯度和重量:
def getBatchGradWgts(grads, wgts, lossVal,
gradHist, lossHist, wgtsHist,
recordWeight=True, npt_exp=None):
dataGrad, dataWeight = {}, {}
for wgt, grad in zip(wgts, grads):
if '/kernel:' not in wgt.name:
continue
layerName = wgt.name.split("/")[0]
dataGrad[layerName] = grad.numpy()
dataWeight[layerName] = wgt.numpy()
if npt_exp:
npt_exp[f'MeanGrads{layerName.upper()}'].log(np.mean(grad.numpy()))
npt_exp[f'MeanWgtBatch{layerName.upper()}'].log(np.mean(wgt.numpy()))
gradHist.append(dataGrad)
lossHist.append(lossVal.numpy())
if recordWeight:
wgtsHist.append(dataWeight)
def fitModel(X, y, model, optimizer,
n_epochs=n_epochs, curBatch_size=batch_size, npt_exp=None):
lossFunc = tf.keras.losses.BinaryCrossentropy()
subData = tf.data.Dataset.from_tensor_slices((X, y))
subData = subData.shuffle(buffer_size=42).batch(curBatch_size)
gradHist, lossHist, wgtsHist = [], [], []
for epoch in range(n_epochs):
print(f'== Starting epoch {epoch} ==')
for step, (x_batch, y_batch) in enumerate(subData):
with tf.GradientTape() as tape:
yPred = model(x_batch, training=True)
lossVal = lossFunc(y_batch, yPred)
grads = tape.gradient(lossVal, model.trainable_weights)
wgts = model.trainable_weights
optimizer.apply_gradients(zip(grads, model.trainable_weights))
if step == 5:
getBatchGradWgts(gradHist=gradHist, lossHist=lossHist, wgtsHist=wgtsHist,
grads=grads, wgts=wgts, lossVal=lossVal, npt_exp=npt_exp)
if npt_exp:
npt_exp['BatchLoss'].log(lossVal)
getBatchGradWgts(gradHist=gradHist, lossHist=lossHist, wgtsHist=wgtsHist,
grads=grads, wgts=wgts, lossVal=lossVal, npt_exp=npt_exp)
return gradHist, lossHist, wgtsHist
- 可视化各层的平均梯度:
def gradientsVis(curGradHist, curLossHist, modelName):
fig, ax = plt.subplots(1, 1, sharex=True, constrained_layout=True, figsize=(7,5))
ax.set_title(f"Mean gradient {modelName}")
for layer in curGradHist[0]:
ax.plot(range(len(curGradHist)), [gradList[layer].mean() for gradList in curGradHist], label=f'Layer_{layer.upper()}')
ax.legend()
return fig
渐变消失模型
现在,我们将模拟一个数据集,并构建我们的基线二元分类神经网络:
X, y = make_moons(n_samples=3000, shuffle=True , noise=0.25, random_state=1234)
batch_size, n_epochs = 32, 100
npt_exp = neptune.init(
api_token=os.getenv('NEPTUNE_API_TOKEN'),
project=myProject,
name='VanishingGradSigmoid',
description='Vanishing Gradients with Sigmoid Activation Function',
tags=['vanishingGradients', 'sigmoid', 'neptune'])
neptune_cbk = NeptuneCallback(run=npt_exp, base_namespace='metrics')
def binaryModel(curName, curInitializer, curActivation, x_tr=None):
model = Sequential()
model.add(InputLayer(input_shape=(2, ), name=curName+"0"))
model.add(Dense(10, kernel_initializer=curInitializer, activation=curActivation, name=curName+"1"))
model.add(Dense(10, kernel_initializer=curInitializer, activation=curActivation, name=curName+"2"))
model.add(Dense(5, kernel_initializer=curInitializer, activation=curActivation, name=curName+"3"))
model.add(Dense(1, kernel_initializer=curInitializer, activation='sigmoid', name=curName+"4"))
return model
curOptimizer = tf.keras.optimizers.RMSprop()
optimizer = curOptimizer
curInitializer = RandomUniform(-1, 1)
model = binaryModel(curName="SIGMOID", curInitializer=curInitializer, curActivation="sigmoid")
model.compile(optimizer=curOptimizer, loss='binary_crossentropy', metrics=['accuracy'])
curGradHist, curLossHist, curWgtHist = fitModel(X, y, model, optimizer=curOptimizer, npt_exp=npt_exp)
npt_exp['Comparing All Layers'].upload(neptune.types.File.as_image(gradientsVis(curGradHist, curLossHist, modelName='Sigmoid_Raw')))
npt_exp.stop()
几个注意事项:
- 我们目前的香草/基线模型由 3 个隐藏层组成,每一层都有一个 sigmoid 激活
- 我们使用 RMSprop 作为优化器,Uniform [-1,1]作为权重初始化器
运行此模型将返回 Neptune.ai 中所有时期每个层的(平均)梯度,下面显示了层 1 和层 4 之间的比较:
Comparison between Layer 1 and Layer 4 from the baseline Sigmoid model generated using Neptune.ai | Source
对于第 4 层,随着训练的进行,我们看到平均梯度的明显波动,然而,对于第 1 层,梯度实际上为零,即,值大约小于 0.006。消失渐变发生了!现在我们来谈谈如何解决这个问题。
使用 ReLU 作为激活功能
如前所述,消失梯度问题是由于 Sigmoid 或 Tanh 函数的饱和性质。因此,一个有效的补救办法是切换到其他激活函数,这些激活函数的导数不饱和,例如 ReLU(整流线性单位):
ReLU as the activation function | Source
如此图所示,对于正输入 x,ReLU 不饱和。当 x <= 0, ReLU has derivative/gradient = 0, and when x > 0 时,导数/梯度= 1。因此,乘以 ReLU 导数会返回 0 或 1;因此,不会有消失的梯度。
为了实现 ReLU 激活,我们可以简单地在如下所示的模型函数中指定` relu ':
model = binaryModel(curName="Relu", curInitializer=curInitializer, curActivation="relu")
model.compile(optimizer=curOptimizer, loss='binary_crossentropy', metrics=['accuracy'])
运行该模型并比较从该模型计算的梯度,我们观察到跨时期的梯度变化,甚至对于标记为 RELU1:
Comparison between Layer 1 and Layer 4 from the ReLu model generated using Neptune.ai | Source
在大多数情况下,类似 ReLU 的激活函数本身应该足以处理渐变消失的问题。但是,这是否意味着我们要一直使用 ReLU,彻底抛弃乙状结肠?嗯,消失梯度的存在不应该阻止你使用 Sigmoid,它有许多可取的属性,如单调性和易于微分。即使使用 Sigmoid 激活函数,也有一些方法可以解决这个问题,这些方法是我们将在接下来的会议中尝试的。
降低模型的复杂性
由于消失梯度的根本原因在于一堆小梯度的相乘,直观上,通过减少梯度的数量,即减少我们网络中的层数来解决这个问题是有意义的。例如,与基线模型中指定 3 个隐藏层不同,我们可以只保留 1 个隐藏层,以使我们的模型更简单:
def binaryModel(curName, curInitializer, curActivation, x_tr=None):
model = Sequential()
model.add(InputLayer(input_shape=(2, ), name=curName+"0"))
model.add(Dense(3, kernel_initializer=curInitializer, activation=curActivation, name=curName+"3"))
model.add(Dense(1, kernel_initializer=curInitializer, activation='sigmoid', name=curName+"4"))
return model
该模型为我们提供了清晰的梯度更新,显示在该图中:
Comparison between Layer 1 and Layer 4 from the reduced Sigmoid model generated using Neptune.ai | Source
这种方法的一个警告是,我们的模型性能可能不如更复杂的模型(具有更多隐藏层)。
使用带有方差的权重初始值设定项
当我们的初始权重设置得太小或缺少方差时,通常会导致梯度消失。回想一下,在我们的基线模型中,我们将权重初始化为均匀的[-1,1]分布,这可能会陷入这些权重太小的陷阱!
在他们 2010 年的论文中,Xavier Glorot 和 Yoshua Bengio 提供了从特定方差的均匀或正态分布中采样初始权重的理论依据,并保持所有层的激活方差相同。
在 Keras/Tensorflow 中,这种方法被实现为 Glorot 正态 *glorot_normal* 和 Glorot Uniform
Glorot _ Uniform`,顾名思义,这两种方法分别从(截尾)正态和均匀分布中采样初始权重。两者都考虑了输入和输出单元的数量。
对于我们的模型,让我们用 glorot_uniform 进行实验,根据 Keras 文档:
回到具有 3 个隐藏层的原始模型,我们将模型权重初始化为 glorot_uniform :
def binaryModel(curName, curInitializer, curActivation, x_tr=None):
model = Sequential()
model.add(InputLayer(input_shape=(2, ), name=curName+"0"))
model.add(Dense(10, kernel_initializer=curInitializer, activation=curActivation, name=curName+"1"))
model.add(Dense(10, kernel_initializer=curInitializer, activation=curActivation, name=curName+"2"))
model.add(Dense(5, kernel_initializer=curInitializer, activation=curActivation, name=curName+"3"))
model.add(Dense(1, kernel_initializer=curInitializer, activation='sigmoid', name=curName+"4"))
return model
curOptimizer = tf.keras.optimizers.RMSprop()
optimizer = curOptimizer
curInitializer = 'glorot_uniform'
npt_exp['Comparing All Layers'].upload(neptune.types.File.as_image(gradientsVis(curGradHist, curLossHist,
modelName='Sigmoid_NormalWeightInit')))
npt_exp.stop()
检查我们的 Neptune.ai 跟踪器,我们看到梯度随着这个权重初始化而变化,尽管第 1 层(在左边)与最后一层相比显示出较少的波动:
Comparison between Layer 1 and Layer 4 from the Glorot weight initializer model generated using Neptune.ai | Source
选择更好的优化器并调整学习率
现在,我们已经解决了导数和初始权重的选择,公式的最后一部分是学习率。随着梯度接近零,优化器陷入次优局部最小值或鞍点。为了克服这一挑战,我们可以使用一个优化器,它具有将累积的先前梯度考虑在内的动力。例如, Adam 有一个动量项,计算为过去梯度的指数衰减平均值。
此外,作为一个高效的优化器,Adam 可以快速收敛或发散。因此,稍微降低学习率将有助于防止你的网络太容易发散,从而降低梯度接近零的可能性。要使用 Adam 优化器,我们需要修改的只是 curOptimizer
arg。:
curOptimizer = keras.optimizers.Adam(learning_rate=0.008)
curInitializer = RandomUniform(-1, 1)
model = binaryModel(curName="SIGMOID", curInitializer=curInitializer, curActivation="sigmoid")
model.compile(optimizer=curOptimizer, loss='binary_crossentropy', metrics=['accuracy'])
在上面的代码中,我们将 Adam 指定为模型优化器,学习率相对较小,为 0.008,激活函数设置为 sigmoid。下面是第 1 层和第 4 层渐变的比较:
Comparison between Layer 1 and Layer 4 from the Adam model generated using Neptune.ai | Source
正如我们所看到的,通过 Adam 和调整良好的小学习率,我们看到梯度的变化使它们的值远离零,并且我们的模型也根据下面所示的损失图收敛到局部最小值:
Selecting better optimizer and adjusting learning rate | Source
到目前为止,我们已经讨论了渐变消失的解决方案,让我们继续讨论渐变爆炸的问题。
渐变爆炸时的解决方案
对于爆炸梯度问题,让我们看看这个回归模型。
nfeatures = 15
X, y = make_regression(n_samples=1500, n_features=nfeatures, noise=0.2, random_state=42)
def regressionModel(X, y, curInitializer, USE_L2REG, secondLayerAct='relu'):
inp = Input(shape = (X.shape[1],))
if USE_L2REG:
x = Dense(35, activation='tanh', kernel_initializer=curInitializer,
kernel_regularizer=regularizers.l2(0.01),
activity_regularizer=regularizers.l2(0.01))(inp)
else:
x = Dense(35, activation=secondLayerAct, kernel_initializer=curInitializer)(inp)
out = Dense(1, activation='linear')(x)
model = Model(inp, out)
return model
为了编译该模型,我们将使用统一的[4,5]权重初始化器以及 ReLu 激活,目的是创建爆炸梯度情况:
sgd = tf.keras.optimizers.SGD()
curOptimizer = sgd
curInitializer = RandomUniform(4,5)
model = regressionModel(X, y, curInitializer, USE_L2REG=False)
model.compile(loss='mean_squared_error', optimizer=curOptimizer, metrics=['mse'])
curModelName = 'Relu_Raw'
curGradHist, curLossHist, curWgtHist = fitModel(X, y, model, optimizer=curOptimizer, modelType = 'regression', npt_exp=npt_exp)
npt_exp['Comparing All Layers'].upload(neptune.types.File.as_image(gradientsVis(curGradHist, curLossHist,
modelName=curModelName)))
npt_exp.stop()
有了这么大的权重初始化,随着训练的进行,下面的错误消息出现在我们的 Neptune.ai 跟踪器中就不足为奇了,正如前面讨论的那样,这清楚地表明我们的梯度爆炸了:
Error message in Neptune.ai | Source
渐变剪辑
为了防止渐变爆炸,最有效的方法之一是渐变裁剪。简而言之,梯度裁剪将导数限定在一个阈值,并使用限定的梯度来更新权重。如果您对该方法的详细解释感兴趣,请参考文章“了解渐变裁剪(以及它如何修复爆炸渐变问题)”。
可以通过` clipvalue '参数指定将渐变限制为某个值。如下图所示:
sgd = tf.keras.optimizers.SGD(clipvalue=50)
curOptimizer = sgd
curInitializer = 'glorot_normal'
model = regressionModel(X, y, curInitializer, USE_L2REG=False)
model.compile(loss='mean_squared_error', optimizer=curOptimizer, metrics=['mse'])
curModelName = 'GradClipping'
通过裁剪运行该模型,我们能够将梯度保持在定义的范围内:
合适的权重初始化器
Layer gradients from the gradients clipping model generated using Neptune.ai | Source
如前所述,梯度爆炸的一个主要原因在于太大的权重初始化和更新,这是我们的回归模型中梯度爆炸的原因。因此,正确初始化模型权重是解决这个爆炸梯度问题的关键。
与消失梯度相同,我们将使用正态分布实现 Glorot 初始化。
由于 Glorot 初始化与 Tanh 或 Sigmoid 配合使用效果最佳,我们将在本实验中将 Tanh 指定为激活函数:
这是该模型的渐变图,修复了渐变爆炸问题:
curOptimizer = tf.keras.optimizers.SGD()
curInitializer = 'glorot_normal'
model = regressionModel(X, y, curInitializer, USE_L2REG=False, secondLayerAct='tanh')
model.compile(loss='mean_squared_error', optimizer=curOptimizer, metrics=['mse'])
curModelName = 'GlorotInit'
L2 范数正则化
除了权重初始化之外,另一个优秀的方法是采用 L2 正则化,它通过将模型权重的平方项强加到损失函数来惩罚大的权重值:
Layer gradients from the Glorot initialization model generated using Neptune.ai | Source
添加 L2 范数经常会导致整个网络中较小的权重更新,并且在 Keras 中使用 args 实现这种正则化相当简单。*内核 _ 正则化子*
和*活动 _ 正则化子*
:
根据 Razvan 等人的建议,我们将这个模型的初始权重设置为 glorot normal。,2013 初始化小值和方差的参数。这里显示了每层的损耗曲线和梯度:
curInitializer = 'glorot_normal'
x = Dense(35, activation='tanh', kernel_initializer=curInitializer,
kernel_regularizer=regularizers.l2(0.01),
activity_regularizer=regularizers.l2(0.01))(inp)
curInitializer = 'glorot_normal'
model = regressionModel(X, y, curInitializer, USE_L2REG=True)
model.compile(loss='mean_squared_error', optimizer=curOptimizer, metrics=['mse'])
curModelName = 'L2Reg'
再次,通过所有历元将梯度控制在合理的范围内,并且模型逐渐收敛。
最后的话
Layer gradients from the L2 regularization model generated using Neptune.ai | Source
除了我们在本文中讨论的主要技术,其他值得尝试避免/修复渐变的方法包括和 缩放输入数据 。这两种方法都可以使您的网络更加健壮。直觉是,在反向投影过程中,我们每一层的输入数据可能会有很大的不同(就像前一层的输出一样)。使用批量归一化允许我们固定每个图层的输入数据的均值和方差,从而防止其移动过多。有了更强大的网络,就不太可能遇到两个梯度问题。
**## 在本文中,我们讨论了与神经网络训练相关的两个主要问题-消失和爆炸梯度问题。我们解释了它们的原因和后果。我们还研究了解决这两个问题的各种方法。
希望你发现这篇文章是有用的,并且学到了实用的技术来训练你自己的神经网络模型。供您参考,完整的代码可以在我的 GitHub repo 这里获得,而 Neptune 项目可以在这里获得。
In this article, we have discussed two major issues associated with neural network training – the Vanishing and Exploding gradients problems. We explained their causes and consequences. We also walked through various approaches to address the two problems.
Hope you have found this article useful and learned practical techniques to use in training your own neural network models. For your reference, the full code is available in my GitHub repo here and the Neptune project is available here.**
NLP 中的矢量化技术[指南]
原文:https://web.archive.org/web/https://neptune.ai/blog/vectorization-techniques-in-nlp-guide
自然语言是我们人类交流思想和观点的方式。自然语言有两种主要媒介——语音和文本。
对于健康的人来说,听和读毫不费力,但对于机器学习算法来说却很难。这就是为什么科学家们不得不提出自然语言处理(NLP)。
什么是自然语言处理?
- NLP 使计算机能够处理人类语言,理解意义和上下文,以及背后相关的情感和意图,并最终使用这些见解来创造新的东西。
- NLP 将计算语言学与统计机器学习和深度学习模型相结合。
我们如何开始让计算机能够解释单词呢?这就是矢量化的目的。
什么是矢量化?
-
矢量化是一种经典方法的行话,这种方法将输入数据从原始格式(即文本)转换为 ML 模型支持的实数矢量格式。这种方法自从计算机诞生以来就一直存在,它在各个领域都非常有效,现在被用于 NLP。
-
在机器学习中,矢量化是特征提取的一个步骤。这个想法是通过将文本转换为数字向量,从文本中获取一些独特的特征,供模型训练。
我们很快就会看到,有很多方法可以执行矢量化,从简单的二进制术语出现特征到高级的上下文感知特征表示。根据用例以及模型的不同,他们中的任何一个都有可能完成所需的任务。
让我们了解一下这些技术,看看如何使用它们。
矢量化技术
1.一袋单词
所有技术中最简单的。它包括三个操作:
首先,输入文本被标记化。一个句子被表示为其组成单词的列表,对所有输入的句子都是如此。
在所有获得的标记化单词中,仅选择唯一的单词来创建词汇表,然后按字母顺序排序。
最后,从词汇单词的频率中为输入创建稀疏矩阵。在这个稀疏矩阵中,每一行都是一个句子向量,其长度(矩阵的列)等于词汇表的大小。
让我们来看一个例子,看看它在实践中是怎样的。在这个练习中,我们将使用 Sklearn 库。
让我们进行必要的进口。
from sklearn.feature_extraction.text import CountVectorizer
考虑我们有下面的文档列表。
sents = ['coronavirus is a highly infectious disease',
'coronavirus affects older people the most',
'older people are at high risk due to this disease']
让我们创建一个 CountVectorizer 的实例。
cv = CountVectorizer()
现在让我们对输入进行矢量化,并将其转换为 NumPy 数组,以便于查看。
X = cv.fit_transform(sents)
X = X.toarray()
这是向量的样子:
我们把词汇表打印出来,了解一下为什么会是这个样子。
sorted(cv.vocabulary_.keys())
- 你可以看到每一行都是‘sents’中各个句子的相关向量表示。
- 每个向量的长度等于词汇的长度。
- 列表中的每个成员都代表了相关单词在分类词汇中出现的频率。
在上面的例子中,我们仅将单个单词视为在词汇键中可见的特征,即它是一个单字表示。这可以被调整以考虑 n-gram 特性。
假设我们想考虑输入的二元模型表示。这可以通过在实例化 CountVectorizer 对象时简单地更改默认参数来实现:
cv = CountVectorizer(ngram_range=(2,2))
在这种情况下,我们的向量和词汇看起来像这样。
因此,我们可以随心所欲地操作这些特性。事实上,我们还可以将单字母词、双字母词、三字母词等组合起来,形成特征空间。
虽然我们在这里使用 sklearn 构建了一个单词包模型,但它可以通过多种方式实现,例如 Keras、Gensim 等库。您也可以很容易地编写自己的单词包实现。
这是一种简单而有效的文本编码技术,可以多次完成这项工作。
2.TF-IDF
TF-IDF 或术语频率-逆文档频率,是一个数字统计,旨在反映一个单词对文档的重要性*。*虽然是另一种基于频率的方法,但也没有一袋文字那么幼稚。
TF-IDF 如何改进单词袋?
在单词包中,我们看到了矢量化只与给定文档中单词的频率有关。结果,对意义贡献不大的冠词、介词和连词变得和形容词一样重要。
TF-IDF 帮助我们克服了这个问题。经常重复的单词不会压倒不太频繁但重要的单词。
它有两个部分:
TF 代表词频。可以理解为归一化的频率得分。它通过以下公式计算:
因此,可以想象这个数字将始终保持≤ 1,因此我们现在判断一个单词在文档中所有单词的上下文中的出现频率。
IDF 代表逆文档频率,但在我们进入 IDF 之前,我们必须弄清楚 DF–文档频率。它由以下公式给出:
DF 告诉我们包含某个单词的文档的比例。那么 IDF 是什么?
它是文档频率的倒数,最终 IDF 得分由以下公式得出:
为什么要逆 DF?
正如我们上面讨论的,其背后的直觉是,一个单词在所有文档中越常见,它对当前文档的重要性就越小。
在最终计算中,采用对数来抑制 IDF 的影响。
TF-IDF 的最终得分为:
这就是 TF-IDF 如何设法融入一个词的意义。分数越高,这个词就越重要。
现在就让我们把手弄脏,看看 TF-IDF 在实践中是什么样子的。
同样,在这个练习中,我们将使用 Sklearn 库,就像我们在单词袋中所做的那样。
进行所需的进口。
from sklearn.feature_extraction.text import TfidfVectorizer
让我们再次使用同一套文件。
sents = ['coronavirus is a highly infectious disease',
'coronavirus affects older people the most',
'older people are at high risk due to this disease']
创建 TfidfVectorizer 的实例。
tfidf = TfidfVectorizer()
让我们现在转换我们的数据。
transformed = tfidf.fit_transform(sents)
现在让我们看看哪些特性是最重要的,哪些特性是无用的。为了便于解释,我们将使用熊猫图书馆,只是为了更好地查看分数。
进行所需的导入:
import pandas as pd
创建以特征名称(即单词)作为索引、以排序的 TF-IDF 分数作为列的数据帧:
df = pd.DataFrame(transformed[0].T.todense(),
index=tfidf.get_feature_names(), columns=["TF-IDF"])
df = df.sort_values('TF-IDF', ascending=False)
由于转换后的 TFIDF 特征矩阵以 Scipy 压缩稀疏行矩阵的形式出现,无法以原始形式查看,因此我们在进行转换后,通过 todense()操作将其转换为 Numpy 数组。类似地,我们通过 get_feature_names()获得了标记化单词的完整词汇表。
这是从另一端出来的:
因此,根据 TF-IDF 的说法,“传染性”一词是最重要的特征,而在词汇包等幼稚方法中用于特征构建的许多词在这里都等于 0。这是我们一直想要的。
关于 TF-IDF 的几点建议:
- n 元语法的概念在这里也是适用的,我们可以将单词组合成 2、3、4 等组来构建我们的最终特征集。
- 除了 n-gram,还有许多参数,如 min_df、max_df、max_features、sublinear_tf 等。一起玩耍。仔细调整这些参数可以为您的模型的功能创造奇迹。
尽管如此简单,TF-IDF 还是被广泛用于信息检索等任务中,以判断哪个响应对查询来说是最好的,在聊天机器人或关键字提取中特别有用,以确定哪个单词在文档中最相关,因此,您会发现自己经常依赖 TF-IDF 的直觉智慧。
到目前为止,我们已经看到了基于频率的文本编码方法,现在是时候看看更复杂的方法了,正如我们所知,这些方法改变了单词嵌入的世界,并在 NLP 中开辟了新的研究机会。
3. Word2Vec
这种方法早在 2013 年由谷歌研究人员在这篇论文中发布,它席卷了 NLP 行业。简而言之,这种方法使用简单的神经网络的能力来生成单词嵌入。
与基于频率的方法相比,Word2Vec 有何改进?
在单词袋和 TF-IDF 中,我们看到了每个单词是如何被当作一个单独的实体来对待的,语义完全被忽略了。随着 Word2Vec 的引入,单词的向量表示被认为是上下文感知的,这可能是有史以来的第一次。
或许,Word2Vec 最著名的例子之一就是下面这个表达式:
国王——男人+女人=王后
因为每个单词都被表示为一个 n 维向量,所以可以想象所有的单词都被映射到这个 n 维空间,使得具有相似含义的单词在这个多维空间中彼此非常接近。
Word2Vec 主要有两种实现方式,我们一个一个来看看:
答:跳格
第一种是跳格法,我们向神经网络提供一个单词,让它预测上下文。大致的想法可以在下图的帮助下捕捉到:
这里 w[i]是句子中“I”位置的输入单词,输出包含关于“I”的两个前面的单词和两个后面的单词。
从技术上讲,它预测一个单词成为给定目标单词的上下文单词的概率。网络输出的概率会告诉我们在输入单词附近找到每个词汇单词的可能性有多大。
这个浅层网络包括一个输入层、一个隐藏层和一个输出层,我们很快就会看到。
然而,有趣的是,我们实际上并没有使用这个经过训练的神经网络。相反,目标只是在正确预测周围单词的同时学习隐藏层的权重。这些权重就是单词嵌入。
网络要预测多少个邻词是由一个叫做“窗口大小”的参数决定的。该窗口向单词的两个方向延伸,即向左和向右。
假设我们想要在一个输入句子上训练一个跳过 gram 的 word2vec 模型:
“敏捷的棕色狐狸跳过懒惰的狗”
下图说明了窗口大小为 2 时,从这句话生成的训练样本。
- ‘成为第一个目标单词,并且因为它是句子的第一个单词,所以在它的左边没有单词,所以大小为 2 的窗口只延伸到它的右边,导致列出的训练样本。
- 当我们的目标移动到下一个单词时,由于在目标的左边出现了一个单词,所以窗口向左扩展了 1。
- 最后,当目标单词在中间某处时,训练样本按预期生成。
神经网络
现在,让我们来讨论将要根据上述训练样本进行训练的网络。
直觉
-
如果你知道什么是自动编码器,你会发现这个网络背后的想法类似于自动编码器。
-
你取一个非常大的输入向量,在隐藏层将其压缩成一个密集的表示,然后不是像自动编码器那样重建原始向量,而是输出与词汇表中每个单词相关的概率。
输入/输出
现在问题来了,你如何输入一个单一的目标词作为一个大的
向量?
答案是 One-Hot 编码。
- 假设我们的词汇包含大约 10,000 个单词,而我们当前的目标单词“fox”介于两者之间。我们要做的是,在对应于单词“fox”的位置放置 1,在其他位置放置 0,这样我们就有了一个 10,000 维的向量,其中一个 1 作为输入。
- 类似地,我们网络的输出也将是一个 10,000 维的向量,包含我们词汇表中的每个单词成为我们输入目标单词的上下文单词的概率。
这是我们的神经网络的架构,看起来像这样:
- 可以看出,给定我们的词汇量=10,000,输入是一个 10,000 维的向量,包含对应于目标单词位置的 1。
- 输出层由应用了 Softmax 激活函数的 10,000 个神经元组成,以便获得我们词汇表中每个单词的相应概率。
- 现在,这个网络的最重要的部分,隐藏层是一个线性层,即没有应用激活函数,并且该层的优化权重将成为学习的单词嵌入。
- 例如,假设我们决定用上面的网络学习单词嵌入。在这种情况下,隐藏层权重矩阵形状将是 M x N,其中 M =词汇量(在我们的例子中是 10,000),N =隐藏层神经元(在我们的例子中是 300)。
- 一旦模型得到训练,我们的目标单词的最终单词嵌入将由以下计算给出:
1×10000 输入向量 10000×300 矩阵= 1×300 向量*
- Google 在其训练的模型中使用了 300 个隐藏层神经元,然而,这是一个超参数,可以相应地进行调整以获得最佳结果。
这就是跳格 word2vec 模型的一般工作方式。是时候看看它的竞争对手了。
B. CBOW
CBOW 代表连续单词包。在 CBOW 方法中,我们不是预测上下文单词,而是将它们输入到模型中,并要求网络预测当前单词。总体思路如下所示:
你可以看到 CBOW 是跳格法的镜像。这里所有的符号的意思和它们在 skip-gram 中的意思完全一样,只是方法颠倒了。
现在,既然我们已经深入研究了什么是 skip-gram 以及它是如何工作的,我们就不再重复这两种方法中常见的部分。相反,我们将只讨论 CBOW 在工作上与 skip-gram 有何不同。为此,我们将粗略地看一下 CBOW 模型架构。
它看起来是这样的:
- 我们的隐藏层和输出层的尺寸保持与 skip-gram 模型相同。
- 然而,正如我们所读到的,CBOW 模型将上下文单词作为输入,这里的输入是 C 个上下文单词,其形式为大小为 1xV 的单热编码向量,其中 V =词汇的大小,使得整个输入 CxV 是多维的。
- 现在,这些 C 向量中的每一个都将乘以我们的隐藏层的权重,其形状为 VxN,其中 V = vocab 大小,N =隐藏层中的神经元数量。
- 如果你可以想象,这将导致 C,1xN 向量,所有这些 C 向量将平均元素的方式,以获得我们的隐藏层的最终激活,然后将馈入我们的输出 softmax 层。
- 隐藏层和输出层之间的学习权重构成了单词嵌入表示。
如果这对你来说有点太难了,CBOW 模型的 TLDR 是:
因为有多个上下文单词,所以进行平均来计算隐藏层值。在这之后,它变得类似于我们的 skip-gram 模型,并且学习的单词嵌入来自输出层权重而不是隐藏层权重。
什么时候使用跳格模型,什么时候使用 CBOW?
- 根据原始论文,skip-gram 可以很好地处理小数据集,并可以更好地表示罕见的单词。
- 然而,CBOW 被发现比 skip-gram 训练得更快,并且可以更好地表示频繁出现的单词。
- 所以选择 skip-gram 还是 CBOW 取决于我们要解决的问题的类型。
现在有了足够的理论,让我们看看如何使用 word2vec 来生成单词嵌入。
在这个练习中,我们将使用 Gensim 库。
进行所需的进口。
from gensim import models
现在这里有两个选择,要么我们可以使用预先训练的模型,要么我们自己训练一个新的模型。我们会经历这两种方式。
让我们先使用谷歌预先训练好的模型,看看我们能用它做些什么。你可以从这里下载这个模型,并在下面给出解压文件的路径,或者你可以通过下面的 Linux 命令得到它。
wget -c "https://s3.amazonaws.com/dl4j-distribution/GoogleNews-vectors-negative300.bin.gz"
gzip -d GoogleNews-vectors-negative300.bin.gz
让我们现在加载模型,但是,请注意,这是一个非常沉重的模型,您的笔记本电脑可能会因为内存不足而死机。
w2v = models.KeyedVectors.load_word2vec_format(
'./GoogleNews-vectors-negative300.bin', binary=True)
任何单词的矢量表示,比如健康,可以通过以下方式获得:
vect = w2v['healthy']
这将给出一个 300 维的向量。
我们还可以利用这个预先训练的模型来获得输入单词的相似意思的单词。
w2v.most_similar('happy')
令人惊讶的是,它对这项任务的表现是如此之好,输出包括一个相关单词元组及其相应的相似性得分的列表,按相似性降序排列。
如前所述,您也可以训练自己的 word2vec 模型。
让我们再次使用前面的句子集作为数据集来训练我们的自定义 word2vec 模型。
sents = ['coronavirus is a highly infectious disease',
'coronavirus affects older people the most',
'older people are at high risk due to this disease']
Word2vec 需要标记化句子列表形式的训练数据集,因此我们将预处理 sents 并将其转换为:
sents = [sent.split() for sent in sents]
最后,我们可以用以下内容训练我们的模型:
custom_model = models.Word2Vec(sents, min_count=1,size=300,workers=4)
这个自定义模型的表现如何将取决于我们的数据集以及它的训练强度。然而,它不太可能击败谷歌预先训练的模型。
这就是 word2vec 的全部内容。如果您想直观感受 word2vec 模型的工作方式,并想更好地理解它,请访问这个链接。这是见证 CBOW & skip-gram 运行的一个非常酷的工具。
4.手套
GloVe 代表单词表示的全局向量。它是在斯坦福开发的。你可以在这里找到原始论文,它发表于 word2vec 一年之后。
与 Word2Vec 类似,GloVe 背后的直觉也在创建上下文单词嵌入,但考虑到 Word2Vec 的出色性能。为什么需要像手套这样的东西?
【GloVe 如何比 Word2Vec 有所提升?
- Word2Vec 是一种基于窗口的方法,在这种方法中,模型依赖于本地信息来生成单词嵌入,而单词嵌入又受到我们选择的判定窗口大小的限制。
- 这意味着学习目标词的语义只受原句中周围词的影响,这是一种有点低效的统计使用,因为我们可以处理更多的信息。
- 另一方面,GloVe 捕获全局和局部统计数据,以便得出单词 embeddings。
我们看到 Word2Vec 中使用了局部统计,但是现在什么是全局统计呢?
GloVe 通过对共现矩阵进行训练来获得语义。它建立在单词-单词共现是一条重要信息的想法上,使用它们是对生成单词嵌入的统计的有效使用。这就是 GloVe 如何设法将“全局统计”合并到最终结果中的。
对于那些不知道共现矩阵的人,这里有一个例子:
假设我们有两个文档或句子。
文献 1: 闪光的不一定都是金子。
**文件二:**结局好的都是好的。
然后,对于 n = 1 的固定窗口大小,我们的共生矩阵将如下所示:
- 如果您花点时间看一下,就会发现行和列是由我们的词汇表组成的,也就是从两个文档中获得的一组唯一的标记化单词。
- 这里,和用来表示句子的开始和结束。
- 大小为 1 的窗口向单词的两个方向延伸,因为“that”&“is”只在“glitters”附近的窗口中出现一次,这就是为什么(that,glitters)和(is,glitters) = 1 的值,现在你知道如何处理这个表了。
关于它的训练,GloVe 模型是一个加权最小二乘模型,因此它的成本函数如下所示:
对于可能共现的每一对单词(I,j ),我们试图最小化它们的单词嵌入的乘积和(I,j)的共现计数的对数之间的差异。f(Pij)项使其成为加权总和,并允许我们对非常频繁的词共现给予较低的权重,从而限制了这种对的重要性。
什么时候使用手套?
- 已经发现 GloVe 在单词类比、单词相似性和命名实体识别任务方面优于其他模型,所以如果你试图解决的问题的性质与这些类似,GloVe 将是一个明智的选择。
- 由于它结合了全局统计,所以它可以捕获罕见词的语义,即使在小语料库上也能表现良好。
现在让我们看看如何利用手套单词嵌入的力量。
首先,我们需要下载嵌入文件,然后我们将使用下面的代码创建一个查找嵌入字典。
Import numpy as np
embeddings_dict={}
with open('./glove.6B.50d.txt','rb') as f:
for line in f:
values = line.split()
word = values[0]
vector = np.asarray(values[1:], "float32")
embeddings_dict[word] = vector
在这个嵌入字典中查询一个单词的向量表示时,结果是这样的。
你可能会注意到这是一个 50 维的向量。我们下载了文件 glove.6B.50d.txt,这意味着这个模型已经在 60 亿个单词上进行了训练,可以生成 50 维的单词嵌入。
我们还可以定义一个函数来从这个模型中获取相似的单词,首先进行所需的导入。
From scipy import spatial
定义功能:
def find_closest_embeddings(embedding):
return sorted(embeddings_dict.keys(), key=lambda word:
spatial.distance.euclidean(embeddings_dict[word], embedding))
让我们看看当我们在这个函数中输入“健康”这个词时会发生什么。
我们提取了模型认为与“健康”最相似的前 5 个单词,结果还不错,我们可以看到上下文已经被很好地捕捉到了。
我们可以用手套做的另一件事是把我们的词汇转换成向量。为此,我们将使用 Keras 库。
您可以通过以下方式安装 keras:
pip install keras
我们将使用到目前为止一直在使用的同一组文档,但是,我们需要将它们转换成一个标记列表,以使它们适合矢量化。
sents = [sent.split() for sent in sents]
首先,在将数据集转换成嵌入之前,我们必须对其进行一些预处理。
进行所需的进口:
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
以下代码将索引分配给单词,这些单词稍后将用于将嵌入映射到索引单词:
MAX_NUM_WORDS = 100
MAX_SEQUENCE_LENGTH = 20
tokenizer = Tokenizer(num_words=MAX_NUM_WORDS)
tokenizer.fit_on_texts(sents)
sequences = tokenizer.texts_to_sequences(sents)
word_index = tokenizer.word_index
data = pad_sequences(sequences, maxlen=MAX_SEQUENCE_LENGTH)
现在我们的数据看起来像这样:
最后,我们可以通过使用上面刚刚创建的嵌入字典执行简单的查找操作,将数据集转换为手套嵌入。如果在字典中找到这个单词,我们将只获取与之相关的单词嵌入。否则,它将仍然是一个零向量。
为此操作进行所需的导入。
from keras.layers import Embedding
from keras.initializers import Constant
EMBEDDING_DIM = embeddings_dict.get(b'a').shape[0]
num_words = min(MAX_NUM_WORDS, len(word_index)) + 1
embedding_matrix = np.zeros((num_words, EMBEDDING_DIM))
for word, i in word_index.items():
if i > MAX_NUM_WORDS:
continue
embedding_vector = embeddings_dict.get(word.encode("utf-8"))
if embedding_vector is not None:
embedding_matrix[i] = embedding_vector
这是从另一端出来的:
这是一个简单的 NumPy 矩阵,其中索引 I 处的条目是矢量器词汇表中索引 I 的单词的预训练矢量。
您可以看到我们的嵌入矩阵具有 19×50 的形状,因为我们的词汇表中有 19 个唯一的单词,并且我们下载的手套预训练模型文件具有 50 维向量。
您可以使用 dimension,只需更改文件或从头开始训练您自己的模型。
这个嵌入矩阵可以以你想要的任何方式使用。它可以被输入到神经网络的嵌入层,或者只是用于单词相似性任务。
这就是手套,让我们继续下一个矢量化技术。
5. FastText
FastText 是脸书在 2016 年推出的。FastText 背后的思想与 Word2Vec 非常相似。然而,像 Word2Vec 和 GloVe 这样的方法仍然缺少一样东西。
如果你一直在关注,你一定注意到了 Word2Vec 和 GloVe 的一个共同点——我们如何下载一个预先训练好的模型,并执行查找操作来获取所需的单词嵌入。尽管这两个模型都经过了数十亿单词的训练,但这仍然意味着我们的词汇量是有限的。
FastText 如何超越其他产品?
FastText 比其他方法有所改进,因为它具有对未知单词的泛化能力,这是其他方法一直缺少的。
它是怎么做到的?
- FastText 不是使用单词来构建单词嵌入,而是更深入一层,即字符层。积木是字母而不是单词。
- 通过 FastText 获得的单词嵌入不是直接获得的。它们是低层嵌入的组合。
- 用字符代替单词还有一个好处。训练所需的数据更少,因为一个单词在某种程度上变成了它自己的上下文,从而可以从一段文本中提取更多的信息。
现在我们来看看 FastText 是如何利用子词信息的。
-
假设我们有单词“reading ”,长度为 3-6 的字符 n 元语法将以如下方式为该单词生成:
-
尖括号表示开始和结束。
-
由于可能有大量的 n 元文法,所以使用散列,而不是学习每个唯一 n 元文法的嵌入,我们学习全部 B 个嵌入,其中 B 表示桶大小。原纸用的是 200 万的桶大小。
-
通过这个散列函数,每个字符 n-gram(比如‘eadi’)被映射到 1 到 B 之间的一个整数,并且该索引具有相应的嵌入。
-
最后,通过平均这些组成的 n 元文法嵌入来获得完整的单词嵌入。
-
尽管这种散列方法会导致冲突,但它有助于在很大程度上控制词汇表的大小。
FastText 中使用的网络类似于我们在 Word2Vec 中看到的网络,就像我们可以在两种模式下训练 fast text——CBOW 和 skip-gram,因此我们在这里不再重复这一部分。如果想详细了解 Fasttext 的更多内容,可以参考这里的原始论文——paper-1和 paper-2 。
让我们继续,看看我们能用 FastText 做些什么。
你可以用 pip 安装 fasttext。
pip install fasttext
你可以从这里下载一个预先训练好的快速文本模型,或者你可以训练你自己的快速文本模型并将其用作文本分类器。
因为我们已经看到了足够多的预训练模型,即使在这种情况下也没有什么不同,所以在这一节中,我们将专注于如何创建自己的快速文本分类器。
假设我们有以下数据集,其中有关于一些药物的对话文本,我们必须将这些文本分为 3 种类型,即与它们相关的药物类型。
现在,为了在任何数据集上训练快速文本分类器模型,我们需要以某种格式准备输入数据,该格式为:
_ _ 标签 __ <标签值> <空格> <关联数据点>
我们也将为我们的数据集这样做。
all_texts = train['text'].tolist()
all_labels = train['drug type'].tolist()
prep_datapoints=[]
for i in range(len(all_texts)):
sample = '__label__'+ str(all_labels[i]) + ' '+ all_texts[i]
prep_datapoints.append(sample)
在这一步中,我省略了很多预处理,在现实世界中,最好进行严格的预处理,以使数据适合建模。
让我们将这些准备好的数据点写入一个. txt 文件。
with open('train_fasttext.txt','w') as f:
for datapoint in prep_datapoints:
f.write(datapoint)
f.write('\n')
f.close()
现在我们有了训练快速文本模型所需的一切。
model = fasttext.train_supervised('train_fasttext.txt')
由于我们的问题是一个监督分类问题,我们训练了一个监督模型。
同样,我们也可以从训练好的模型中获得预测。
该模型给出了预测的标签以及相应的置信度得分。
同样,这个模型的性能取决于很多因素,就像任何其他模型一样,但是如果您想快速了解基线精度应该是多少,fasttext 可能是一个非常好的选择。
这就是关于快速文本和如何使用它的全部内容。
结束了!
在本文中,我们涵盖了单词嵌入的所有主要分支,从简单的基于计数的方法到子单词级别的上下文嵌入。随着自然语言处理的效用不断增加,完全了解它的组成部分是非常必要的。
鉴于我们对幕后发生的事情和这些方法的用例了解得如此之多,我希望现在当你偶然发现一个 NLP 问题时,你将能够做出关于使用哪种嵌入技术的明智决定。
未来方向
我希望不言而喻的是,无论我们在本文中涵盖了什么,都不是详尽无遗的,还有许多技术有待探索。这些只是主要的支柱。
从逻辑上讲,下一步应该是阅读更多关于文档(句子)级嵌入的内容,因为我们已经在这里介绍了基础知识。我会鼓励你去阅读像 Google 的 BERT、通用句子编码器以及相关的主题。
如果你决定尝试 BERT,从这个开始。它提供了一个惊人的方式来利用伯特的力量,而不是让你的机器做所有繁重的工作。阅读自述文件以设置它。
目前就这些。感谢阅读!
阿布舍克·贾
一个好奇的家伙,目前正在建造模型,希望有一天能建造天网。跟随这个空间,学习未被理清的数据科学概念,并站在未来的正确一边!
阅读下一篇
如何构建和管理自然语言处理(NLP)项目
Dhruvil Karani |发布于 2020 年 10 月 12 日
如果说我在 ML 行业工作中学到了什么的话,那就是:机器学习项目很乱。
这并不是说人们不想把事情组织起来,只是在项目过程中有很多事情很难组织和管理。
你可以从头开始,但有些事情会阻碍你。
一些典型的原因是:
- 笔记本中的快速数据探索,
- 取自 github 上的研究报告的模型代码,
- 当一切都已设置好时,添加新的数据集,
- 发现了数据质量问题并且需要重新标记数据,
- 团队中的某个人“只是快速地尝试了一些东西”,并且在没有告诉任何人的情况下改变了训练参数(通过 argparse 传递),
- 从高层推动将原型转化为产品“仅此一次”。
多年来,作为一名机器学习工程师,我学到了一堆东西,它们可以帮助你保持在事物的顶端,并检查你的 NLP 项目(就像你真的可以检查 ML 项目一样:)。
在这篇文章中,我将分享我在从事各种数据科学项目时学到的关键指针、指南、技巧和诀窍。许多东西在任何 ML 项目中都是有价值的,但有些是 NLP 特有的。