PaperSpace-博客中文翻译-四-

PaperSpace 博客中文翻译(四)

原文:PaperSpace Blog

协议:CC BY-NC-SA 4.0

用张量流构建一个简单的生成式对抗网络

原文:https://blog.paperspace.com/implementing-gans-in-tensorflow/

生成对抗网络(GANs)是深度学习研究和发展中最活跃的领域之一,因为它们具有生成合成结果的惊人能力。在这篇博客中,我们将通过一个具体的例子来建立 GANs 的基本直觉。这篇文章按以下方式分类:

  • 生成性对抗网络工作背后的基本思想和直觉
  • 实施基于 GAN 的模型,从简单分布中生成数据
  • 可视化和分析 GAN 的不同方面,以更好地了解幕后发生的事情。

这个博客的代码可以在这里找到。

生成对抗网络

GANs 背后的基本思想其实很简单。在它的核心,一个甘包括两个代理人与竞争的目标,通过对立的目标工作。这种相对简单的设置导致两个代理都想出越来越复杂的方法来欺骗对方。这种情况在博弈论中可以建模为极小极大博弈。

我们来举一个货币伪造过程的理论例子。在这个过程中,我们可以想象两种类型的特工:一个罪犯和一个警察。让我们看看他们的竞争目标:

  • 罪犯的目标:罪犯的主要目标是想出复杂的伪造货币的方法,让的警察无法区分假币和真币。
  • Cop 的目标:Cop 的主要目标是想出复杂的方法来区分假币和真币。

随着这一过程的进行,警察开发出越来越复杂的技术来检测假币,而 T2 罪犯开发出越来越复杂的技术来伪造假币。这就是所谓的对抗过程的基础

生成对抗网络利用对抗过程来训练两个相互竞争的神经网络,直到达到理想的*衡。在这种情况下,我们有一个生成器网络 G(Z) ,它接受输入随机噪声,并试图生成非常接*我们数据集的数据。另一个网络被称为鉴别器网络 D(X) ,它接收输入的生成数据,并尝试区分生成数据和真实数据。该网络的核心实现了二元分类,并输出输入数据实际上来自真实数据集(与合成或虚假数据相对)的概率。

在形式意义上,整个过程的目标函数可以写成:

GAN's objective function

对于上述定义的 GANs,通常理想的*衡点是生成器应模拟真实数据,鉴别器应输出 0.5 的概率,因为生成的数据与真实数据相同,也就是说,不确定来自生成器的新数据是真实的还是假的,概率相等。

你可能想知道为什么需要这么复杂的学习过程?学习这样的模式有什么好处?这背后的直觉和所有的生成方法都遵循理查德·费曼的一句名言:

T2What I cannot create, I do not understand.

这是相关的,因为如果我们能够从一个模型中生成真实的数据分布,那么这意味着我们知道关于该模型的一切。很多时候,这些真实分布包括数百万张图像,我们可以使用具有数千个参数的模型来生成它们,然后这些参数捕捉到给定图像的本质。

gan 还有许多其他现实生活中的短期应用,我们将在后面的章节中讨论。

实施 GANs

在本节中,我们将生成一个非常简单的数据分布,并尝试学习一个使用上述 GANs 模型从该分布生成数据的生成器函数。本节大致分为三个部分。首先,我们将编写一个基本函数来生成二次分布(真实数据分布)。其次,我们为发生器鉴别器网络编写代码。然后,我们将使用数据和网络来编写代码,以对抗的方式训练这两个网络。

这个实现的目标是学习一个新函数,它可以从与训练数据相同的分布中生成数据。来自训练的期望是我们的生成器网络应该开始产生遵循二次分布的数据。这将在下一节详细解释和演示。虽然我们从非常简单的数据分布开始,但是这种方法可以很容易地扩展到从更复杂的数据集生成数据。很少有示例 GANs 成功地生成手写数字、名人、动物等的图像。

生成训练数据

我们通过使用numpy库生成随机样本,然后使用某种函数生成第二个坐标,来实现我们的真实数据集。出于演示的目的,为了简单起见,我们将该函数保持为二次函数。您可以使用该代码生成一个数据集,该数据集具有更多维度和/或其特征之间更复杂的关系,例如高次多项式、正弦、余弦等。

import numpy as np

def get_y(x):
    return 10 + x*x

def sample_data(n=10000, scale=100):
    data = []

    x = scale*(np.random.random_sample((n,))-0.5)

    for i in range(n):
        yi = get_y(x[i])
        data.append([x[i], yi])

    return np.array(data) 

生成的数据非常简单,可以绘制成图,如下所示:

Training data

发生器和鉴别器网络实现

我们现在将使用 tensorflow 层实现生成器鉴别器网络。我们使用以下函数实现发电机网络:

def generator(Z,hsize=[16, 16],reuse=False):
    with tf.variable_scope("GAN/Generator",reuse=reuse):
        h1 = tf.layers.dense(Z,hsize[0],activation=tf.nn.leaky_relu)
        h2 = tf.layers.dense(h1,hsize[1],activation=tf.nn.leaky_relu)
        out = tf.layers.dense(h2,2)

    return out 

该函数接受随机样本(Z)的placeholder,2 个隐藏层中单元数量的数组hsize,以及用于重用相同层的reuse变量。使用这些输入,它创建了一个具有给定节点数的 2 个隐藏层的完全连接的神经网络。这个函数的输出是一个二维向量,它对应于我们试图学习的真实数据集的维度。可以容易地修改上述函数,以包括更多的隐藏层、不同类型的层、不同的激活和不同的输出映射。

我们使用以下函数实现鉴频器网络:

def discriminator(X,hsize=[16, 16],reuse=False):
    with tf.variable_scope("GAN/Discriminator",reuse=reuse):
        h1 = tf.layers.dense(X,hsize[0],activation=tf.nn.leaky_relu)
        h2 = tf.layers.dense(h1,hsize[1],activation=tf.nn.leaky_relu)
        h3 = tf.layers.dense(h2,2)
        out = tf.layers.dense(h3,1)

    return out, h3 

该函数从真实数据集的向量空间获取样本的输入placeholder。样本可以是真实样本,也可以是从发生器网络生成的样本。类似于上面的发电机网络,它也接受输入hsizereuse。我们使用 3 个隐藏层作为鉴别器,其中前 2 层的尺寸作为输入。我们将第三个隐藏层的大小固定为 2,以便我们可以在 2D *面中可视化转换后的特征空间,如后面的部分所述。该函数的输出是给定Xlogit预测和最后一层的输出,该输出是鉴别器学习的X的特征变换。logit函数是 sigmoid 函数的逆函数,用于表示赔率的对数(变量为 1 的概率与变量为 0 的概率之比)。

对抗训练

出于训练的目的,我们分别为真实样本和随机噪声样本定义了以下占位符XZ:

X = tf.placeholder(tf.float32,[None,2])
Z = tf.placeholder(tf.float32,[None,2]) 

我们还需要创建图形,以便从生成器网络生成样本,并将真实样本和生成的样本馈送到鉴别器网络。这是通过使用上面定义的函数和占位符来完成的:

G_sample = generator(Z)
r_logits, r_rep = discriminator(X)
f_logits, g_rep = discriminator(G_sample,reuse=True) 

使用生成数据和真实数据的逻辑,我们定义发生器和鉴别器网络的损失函数如下:

disc_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=r_logits,labels=tf.ones_like(r_logits)) + tf.nn.sigmoid_cross_entropy_with_logits(logits=f_logits,labels=tf.zeros_like(f_logits)))
gen_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=f_logits,labels=tf.ones_like(f_logits))) 

这些损耗是基于sigmoid cross entropy的损耗,使用我们上面定义的公式。这是通常用于所谓的离散分类的损失函数。它将每个样本的logit(由我们的discriminator网络给出)和真实标签作为输入。然后计算每个样本的误差。我们使用 TensorFlow 实现的优化版本,它比直接计算交叉熵更稳定。更多细节,可以查看相关的 TensorFlow API 这里

接下来,我们使用上面定义的损失函数和在generatordiscriminator函数中定义的层的范围来定义两个网络的优化器。我们对两个网络都使用 RMSProp 优化器,学习率为0.001。使用范围,我们只获取给定网络的权重/变量。

gen_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,scope="GAN/Generator")
disc_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,scope="GAN/Discriminator")

gen_step = tf.train.RMSPropOptimizer(learning_rate=0.001).minimize(gen_loss,var_list = gen_vars) # G Train step
disc_step = tf.train.RMSPropOptimizer(learning_rate=0.001).minimize(disc_loss,var_list = disc_vars) # D Train step 

然后,我们以交替的方式训练两个网络所需的步骤数:

for i in range(100001):
    X_batch = sample_data(n=batch_size)
    Z_batch = sample_Z(batch_size, 2)
    _, dloss = sess.run([disc_step, disc_loss], feed_dict={X: X_batch, Z: Z_batch})
    _, gloss = sess.run([gen_step, gen_loss], feed_dict={Z: Z_batch})

    print "Iterations: %d\t Discriminator loss: %.4f\t Generator loss: %.4f"%(i,dloss,gloss) 

上述代码可以被修改以包括更复杂的训练过程,例如运行鉴别器和/或生成器更新的多个步骤,获取真实样本和生成样本的特征并绘制生成样本。请参考代码库了解这些修改。

分析 gan

可视化培训损失

为了更好地理解这个过程中发生了什么,我们可以在每一次10迭代后绘制训练损失图。从下面的图中,我们可以看到损耗的变化是如何逐渐减少的,并且在训练结束时损耗几乎保持不变。鉴别器
发生器的损耗的这种可忽略的变化表示*衡。

Training Loss

培训期间可视化样本

我们还可以在每1000次迭代训练后绘制真实样本和生成样本。这些图显示了生成器网络如何从输入和数据集向量空间之间的随机初始映射开始,然后逐渐发展到类似真实数据集样本。正如你所看到的,“假”样本开始看起来越来越像“真实”的数据分布。

samples_training

可视化生成器更新

在这一节中,我们设想在对抗训练过程中更新生成器网络权重的效果。我们通过绘制鉴别器网络的最后一个隐藏层的激活来做到这一点。我们选择最后一个隐藏层的大小为 2,这样就很容易绘制,不需要降维(即将输入样本转换到不同的向量空间)。我们对可视化由鉴别器网络学习的特征变换函数感兴趣。这个函数是我们的网络所学习的,这样真实和虚假的数据是可分的。

在我们更新生成器网络的权重之前和之后,我们绘制由鉴别器网络的最后一层学习的真实样本和生成样本的特征变换。我们还绘制了在输入样本的特征变换之后获得的点的质心。最后,在生成器更新之前和之后,我们分别计算真实和虚假数据的点的质心。从这些图中我们可以推断出以下事情:

  • 正如所料,真实数据样本的变换特征没有变化。从图中,我们可以看到它们完全一致。
  • 从质心可以看出,生成的数据样本的特征质心几乎总是向真实数据样本的特征质心移动。
  • 我们还可以看到,随着迭代次数的增加,真实样本的变换特征越来越多地与生成样本的变换特征混合在一起。这也是意料之中的,因为在训练结束时,鉴别器网络不应该能够区分真实样本和生成样本。因此,在训练结束时,两个样本的变换特征应该一致。

feature_transform feature_transform_centroid

讨论和未来工作

我们实施了一个概念验证 GAN 模型,用于从非常简单的数据分布中生成数据。作为对好奇读者的一个练习,我们建议修改上述代码以完成以下任务:

  • 直观显示鉴别器更新前后的情况。
  • 更改层的激活函数,并查看训练样本和生成样本的差异。
  • 添加更多层和不同类型的层,并查看对训练时间和训练稳定性的影响。
  • 修改用于生成数据的代码,以包含来自两条不同曲线的数据
  • 修改上述代码以处理更复杂的数据,如 MNIST、CIFAR-10 等。

在未来的工作中,我们将讨论 GANs 的局限性以及解决这些局限性所需的修改。

在 Python 中实现梯度增强

原文:https://blog.paperspace.com/implementing-gradient-boosting-regression-python/

您是否正在处理一个回归问题,并在寻找一个高效的算法来解决您的问题?如果是,你必须探索梯度推进回归(或 GBR)。

在本文中,我们将从回归问题的梯度推进的介绍开始,是什么使它如此有利,以及它的不同参数。然后,我们将在 Python 中实现 GBR 模型,使用它进行预测,并对其进行评估。

让我们开始吧。

Photo by Austin Neill / Unsplash

梯度推进回归简介

机器学习中的“助推”是一种将多个简单模型组合成单个复合模型的方式。这也是为什么 boosting 被称为加法模型,因为简单模型(也称为弱学习器)一次添加一个,同时保持模型中现有的树不变。随着我们结合越来越多的简单模型,完整的最终模型成为更强的预测器。“梯度提升”中的术语“梯度”来自于算法使用梯度下降来最小化损失。

当梯度增强用于预测连续值(如年龄、体重或成本)时,我们使用梯度增强进行回归。这与使用线性回归不同。这与用于分类的配置略有不同,因此在本文中我们将坚持回归。

决策树用作梯度提升中的弱学习器。决策树通过将数据转换成树表示来解决机器学习的问题。树表示的每个内部节点表示一个属性,每个叶节点表示一个类标签。损失函数通常是*方误差(特别是对于回归问题)。损失函数需要是可微的。

同样像线性回归一样,我们在梯度推进回归中也有残差的概念。梯度推进回归计算当前预测和已知正确目标值之间的差异。
这种差异叫做残差。之后,梯度推进回归训练将特征映射到该残差的弱模型。由弱模型预测的该残差被添加到现有的模型输入,因此该过程将模型推向正确的目标。一次又一次地重复这个步骤改进了整体模型预测。

还应注意,梯度推进回归用于预测连续值,如房价,而梯度推进分类用于预测类别,如患者是否患有特定疾病。

我们实施梯度推进回归所遵循的高级步骤如下:

  1. 选择一个弱学习者
  2. 使用加法模型
  3. 定义损失函数
  4. 最小化损失函数

Finding my roots

Photo by Jeremy Bishop / Unsplash

梯度增强与 Ada 增强的比较

梯度增强和 Ada 增强都与决策树一起工作,但是梯度增强中的树比 Ada 增强中的树大。

梯度增强和 Ada 增强都可以缩放决策树,但是,与 Ada 增强不同,梯度增强以相同量缩放所有树。

梯度增强的优势

更好的精度:梯度推进回归一般提供更好的精度。当我们将 GBR 的准确性与其他回归技术(如线性回归)进行比较时,GBR 几乎总是赢家。这就是为什么 GBR 被用于大多数在线黑客马拉松和竞赛。

更少的预处理:我们知道,数据预处理是机器学习工作流程中至关重要的步骤之一,如果我们做得不好,就会影响我们的模型准确性。然而,梯度推进回归需要最少的数据预处理,这有助于我们以更低的复杂性更快地实现该模型。虽然预处理在这里不是强制性的,但我们应该注意,我们可以通过花时间预处理数据来提高模型性能。

更高的灵活性:梯度推进回归可用于许多超参数和损失函数。这使得该模型高度灵活,可以用来解决各种各样的问题。

缺失数据:缺失数据是训练模型时的问题之一。梯度推进回归自己处理缺失数据,不需要我们显式处理。这显然是对其他类似算法的巨大胜利。在该算法中,缺失值被视为包含信息。因此,在树构建期间,通过最小化损失函数并且将缺失值视为可以向左或向右的单独类别来决定节点的分裂决策。

梯度增强参数

让我们讨论一下梯度推进回归中使用的几个重要参数。这些是我们可能想要调整的参数,以便从我们的算法实现中获得最佳输出。

Adjusting dials on a mixer

Photo by Drew Patrick Miller / Unsplash

估计量个数:表示为 n _ 估计量。
该参数的默认值为 100。
估计器的数量基本上是模型要执行的推进阶段的数量。换句话说,估计数表示森林中树木的数量。更多的树有助于更好地学习数据。另一方面,更多数量的树会导致更长的训练时间。因此,我们需要找到最佳性能的 n 估计量的正确和*衡的值。

最大深度:表示为 max_depth。
max _ depth 的默认值为 3,是可选参数。
最大深度是决策树估计器在梯度推进回归器中的深度。我们需要找到这个超参数的最佳值,以获得最佳性能。例如,该参数的最佳值可能取决于输入变量。

学习率:表示为 learning_rate。
learning _ rate 的默认值为 0.1,是一个可选参数。
学习率是梯度推进回归器算法中的一个超参数,该算法确定每次迭代的步长,同时向损失函数的最小值移动。

判据:表示为判据。
criteria 的默认值为 friedman_mse,可选参数。
标准用于衡量决策树的分割质量。
mse 代表均方误差。

损失:表示损失。
loss 的默认值为 ls,可选参数。
该参数表示要优化的损失函数。有各种损失函数,如 ls,代表最小二乘回归。缩写为 lad 的最小绝对偏差是另一个损失函数。第三种损失函数是最小二乘回归和最小绝对偏差的组合。

子样本:表示子样本。
子样本的默认值为 1.0,可选参数。
子样本是用于拟合个体树学习者的样本部分。如果二次抽样小于 1.0,这将导致方差减少,偏差增加。

迭代次数无变化:用 n_iter_no_change 表示。
子样本的默认值为无,是一个可选参数。
该参数用于决定当验证分数没有随着进一步迭代而提高时,是否使用提前停止来终止训练。
如果启用该参数,则将训练数据的 validation_fraction 大小设置为验证,当验证分数没有提高时,终止训练。

获取数据

在我们开始实现模型之前,我们需要获得数据。我在这里上传了一个样本数据。如果您想在自己的机器上尝试,可以下载本地的数据。

下面是数据描述截图。如你所见,我们有两个变量 x 和 y,x 是自变量,y 是因变量。

Gradient Boosting Regression Data

我们将把这些数据拟合成一条直线,它的方程式将是 y = mx+c

m 是等斜率, c 是直线的 y 截距。

培训 GBR 模型

现在是实现模型的时候了。正如您在下面的代码中看到的,我们将从定义参数 n_estimators、max_depth、learning_rate 和 criterion 开始。这些参数的值分别为 3、3、1 和 mse。我们将参数值存储在一个名为 params 的变量中。

我们从 sklearn 导入了 ensemble,并且使用了用 ensemble 定义的 GradientBoostingRegressor 类。

我们正在创建 GradientBoostingRegressor 类的实例 gradient _ boosting _ regressor _ model,方法是将上面定义的参数传递给构造函数。

之后,我们在模型实例 gradient _ boosting _ regressor _ model 上调用 fit 方法。

在下面的单元格 21 中,您可以看到生成了 GradientBoostingRegressor 模型。有许多参数,如 alpha、criterion、init、学习率、损耗、最大深度、最大特征、最大叶节点、最小杂质减少、最小杂质分割、最小样本叶、*均样本分割、最小权重分数叶、n 个估计值、n 个项无变化、预分类、随机状态、子样本、tol、验证分数、详细和热启动,并显示其默认值。

Implementing Gradient Boosting Regression in Python

评估模型

让我们评价一下这个模型。在评估模型之前,将我们创建的东西可视化总是一个好主意。因此,我绘制了 x_feature 与其预测值的关系,如下图所示。这让我们更好地理解模型与数据的吻合程度。从下面的图表中可以清楚地看到,看起来我们很合适。我们使用 pyplot 库来创建下图。正如你在下面的代码中看到的,我首先设置了 figsize。之后,使用标题功能,我们需要设置标题的情节。然后,我们需要将特征和标签传递给散布函数。最后使用 plot 函数来传递特征、其对应的预测和要使用的颜色。

Visualize the Gradient Boosting Regression model

完成上述可视化后,就到了寻找最佳模型如何定量拟合数据的时候了。sklearn 为我们提供了以数字形式评估模型的指标。

正如您在下面看到的,该模型的拟合度得分约为 98.90%。这是一个非常好的分数,正如梯度推进回归模型所预期的那样。

Finding how best model fits the data

结束注释:

在本教程中,我们学习了什么是梯度推进回归,使用它的优势是什么。我们还讨论了梯度推进回归中使用的各种超参数。之后,我们加载样本数据,并用这些数据训练一个模型。利用训练好的模型,我们试图可视化和量化该模型与超过 98%的数据的拟合程度。

success

Photo by bruce mars / Unsplash

感谢阅读!快乐机器学习:)

实现单词自动完成和自动更正的 Levenshtein 距离

原文:https://blog.paperspace.com/implementing-levenshtein-distance-word-autocomplete-autocorrect/

Levenshtein 距离是一种文本相似性度量,它比较两个单词并返回代表它们之间距离的数值。距离反映了将一个单词转换为另一个单词所需的单个字符编辑的总数。两个词越相似,它们之间的距离就越小,反之亦然。这个距离的一个常见用途是文本处理器或聊天应用程序的自动完成或自动更正功能。

之前我们讨论了Levenshtein 距离如何工作,我们考虑了几个使用动态编程方法的例子。

在本教程中,Levenshtein 距离将使用动态编程方法在 Python 中实现。我们将创建一个具有自动完成和自动更正功能的简单应用程序,它使用 Levenshtein 距离来选择词典中“最接*”的单词。

本教程涵盖的部分如下:

  • 创建distances矩阵
  • 初始化distances矩阵
  • 打印distances矩阵
  • 计算所有前缀之间的距离
  • 自动完成/自动更正的字典搜索

让我们开始吧。

创建距离矩阵

使用动态编程方法计算 Levenshtein 距离,创建一个 2d 矩阵,保存两个被比较单词的所有前缀之间的距离(我们在第 1 部分中看到了这一点)。因此,首先要做的是创建这个二维矩阵。

我们将创建一个名为levenshteinDistanceDP()的函数,它接受名为token1token2的两个参数,代表两个单词。它返回一个表示它们之间距离的整数。该函数的标题如下:

levenshteinDistanceDP(token1, token2)

给定两个长度为mn的单词,根据上一教程中讨论的步骤,首先要做的是根据第一个单词是代表行还是代表列来创建一个大小为(m+1, n+1)(n+1, m+1)的二维整数矩阵。哪个单词用于什么并不重要,但是您确实需要保持一致,因为代码的其余部分取决于这个选择。

下一行在名为distances的变量中创建了这样一个矩阵(在本例中,第一个字代表行,第二个字代表列)。注意,字符串的长度是使用length()函数返回的。

distances = numpy.zeros((len(token1) + 1, len(token2) + 1))

初始化距离矩阵

下一步是用从0开始的整数初始化矩阵的第一行和第一列。我们将使用下面所示的for循环来实现这一点,它使用了一个名为t1(token1的快捷键)的变量,该变量从 0 开始,到第二个单词的长度结束。注意,行索引固定为0,变量t1用于定义列索引。通过这样做,第一行被从0开始的值填充。

for t1 in range(len(token1) + 1):
    distances[t1][0] = t1

为了初始化distances矩阵的第一列,使用另一个for循环,如下所示。与之前的循环相比,有两个变化。第一个是循环变量被命名为t2而不是t1,以反映它从0开始直到参数token2结束。第二个变化是distances数组的列索引现在固定为0,而循环变量t2用于定义行的索引。这样,第一列由从0开始的值初始化。

for t2 in range(len(token2) + 1):
    distances[0][t2] = t2

打印距离矩阵

在初始化了distances数组的第一行和第一列之后,我们将使用一个名为printDistances()的函数,通过两个for循环来打印它的内容。它接受三个参数:

  1. 距离:保存距离的二维矩阵。
  2. token1Length :第一个单词的长度。
  3. token2Length :第二个字的长度。
def printDistances(distances, token1Length, token2Length):
    for t1 in range(token1Length + 1):
        for t2 in range(token2Length + 1):
            print(int(distances[t1][t2]), end=" ")
        print()

下面是到目前为止的完整实现。在传递单词kelmhello后调用levenshteinDistanceDP()函数,这在之前的教程中使用过。因为我们还没有完成,它将返回0

import numpy

def levenshteinDistanceDP(token1, token2):
    distances = numpy.zeros((len(token1) + 1, len(token2) + 1))

    for t1 in range(len(token1) + 1):
        distances[t1][0] = t1

    for t2 in range(len(token2) + 1):
        distances[0][t2] = t2

    printDistances(distances, len(token1), len(token2))
    return 0

def printDistances(distances, token1Length, token2Length):
    for t1 in range(token1Length + 1):
        for t2 in range(token2Length + 1):
            print(int(distances[t1][t2]), end=" ")
        print()

levenshteinDistanceDP("kelm", "hello")

levenshteinDistanceDP()函数内部,调用printDistances()函数根据下一行打印distances数组。

printDistances(distances, len(token1), len(token2))

这是输出。

0 1 2 3 4 5 
1 0 0 0 0 0 
2 0 0 0 0 0 
3 0 0 0 0 0 
4 0 0 0 0 0 

至此,distances矩阵成功初始化。在下一节中,我们将计算两个单词的所有前缀之间的距离。

计算所有前缀之间的距离

为了计算两个单词的所有前缀之间的距离,使用两个for循环来遍历矩阵中的每个单元(不包括第一行/列)。

for t1 in range(1, len(token1) + 1):
    for t2 in range(1, len(token2) + 1):

在循环内部,计算两个单词的所有前缀组合的距离。根据上一教程中的讨论,两个前缀之间的距离是基于一个 2 x 2 矩阵计算的,如下图所示。这样的矩阵总是有三个已知值和一个要计算的缺失值。

如果位于被比较的两个前缀末尾的两个字符相等,则距离等于 2 x 2 矩阵左上角的值。这在下一个if语句中实现。

if (token1[t1-1] == token2[t2-1]):
    distances[t1][t2] = distances[t1 - 1][t2 - 1]

如果两个字符不相等,则当前单元格中的距离等于在加上成本12×2矩阵中三个现有值的最小值。一个else块被添加到前面的if语句中,以根据下面的代码计算这样的距离。

if (token1[t1-1] == token2[t2-1]):
    distances[t1][t2] = distances[t1 - 1][t2 - 1]
else:
    a = distances[t1][t2 - 1]
    b = distances[t1 - 1][t2]
    c = distances[t1 - 1][t2 - 1]

if (a <= b and a <= c):
    distances[t1][t2] = a + 1
elif (b <= a and b <= c):
    distances[t1][t2] = b + 1
else:
    distances[t1][t2] = c + 1

下面是添加这个if-else块后的两个for循环。

for t1 in range(1, len(token1) + 1):
    for t2 in range(1, len(token2) + 1):
        if (token1[t1-1] == token2[t2-1]):
            distances[t1][t2] = distances[t1 - 1][t2 - 1]
        else:
            a = distances[t1][t2 - 1]
            b = distances[t1 - 1][t2]
            c = distances[t1 - 1][t2 - 1]

        if (a <= b and a <= c):
            distances[t1][t2] = a + 1
        elif (b <= a and b <= c):
            distances[t1][t2] = b + 1
        else:
            distances[t1][t2] = c + 1

此时,levenshteinDistanceDP()函数除了返回计算出的两个单词之间的距离外,几乎已经完成。这个距离位于distances矩阵的右下角,按照这条线返回。

return distances[len(token1)][len(token2)]

levenshteinDistanceDP()函数的实现现在已经 100%完成。这是完整的代码。

def levenshteinDistanceDP(token1, token2):
    distances = numpy.zeros((len(token1) + 1, len(token2) + 1))

    for t1 in range(len(token1) + 1):
        distances[t1][0] = t1

    for t2 in range(len(token2) + 1):
        distances[0][t2] = t2

    a = 0
    b = 0
    c = 0

    for t1 in range(1, len(token1) + 1):
        for t2 in range(1, len(token2) + 1):
            if (token1[t1-1] == token2[t2-1]):
                distances[t1][t2] = distances[t1 - 1][t2 - 1]
            else:
                a = distances[t1][t2 - 1]
                b = distances[t1 - 1][t2]
                c = distances[t1 - 1][t2 - 1]

                if (a <= b and a <= c):
                    distances[t1][t2] = a + 1
                elif (b <= a and b <= c):
                    distances[t1][t2] = b + 1
                else:
                    distances[t1][t2] = c + 1

    printDistances(distances, len(token1), len(token2))
    return distances[len(token1)][len(token2)]

下一行调用levenshteinDistanceDP()函数打印distances矩阵并返回两个单词之间的最终距离。

distance = levenshteinDistanceDP("kelm", "hello")

输出应该如下所示。

0 1 2 3 4 5 
1 1 2 3 4 5 
2 2 1 2 3 4 
3 3 2 1 2 3 
4 4 3 2 2 3 

3

“凯尔姆”和“你好”之间的距离是 3。

在下一节中,我们将在这个函数的基础上构建,允许用户输入一个单词,并将返回最接*的单词(基于字典搜索)。

词典搜索自动补全和自动更正

Levenshtein 距离的一个应用是通过自动纠正打字错误或完成单词来帮助作者写得更快。在本节中,我们将使用一个小版本的英语词典(仅包含 1000 个常用单词)来完成这项任务。该词典可在此链接下载。它是一个文本文件,我们将从中读取并提取每个单词,然后调用levenshteinDistanceDP()函数,最后返回最匹配的单词。当然,您总是可以用自己选择的全尺寸字典来扩展这个实现。

下面的代码块创建了一个名为calcDictDistance()的函数,它接受两个参数,读取字典,并计算搜索词与字典中所有词之间的距离。

第一个参数名为word,表示要与字典中的单词进行比较的搜索单词。第二个参数名为numWords,它接受要过滤的匹配单词的数量。

目前,calcDictDistance()函数只是使用open()函数读取指定路径的文本文件。变量line迭代保存使用readLines()方法返回的字典中的每一行(即单词)。calcDictDistance()函数还没有返回任何东西,但是我们很快会修改它,返回一个包含最匹配单词的列表。

def calcDictDistance(word, numWords):
    file = open('1-1000.txt', 'r') 
    lines = file.readlines() 

    for line in lines: 
        print(line.strip()) 

接下来是将搜索词与字典中的每个词进行比较,计算一个距离,并将所有距离存储在一个名为dictWordDist的列表中。它的长度是 1000,因为字典包含 1000 个单词。

dictWordDist列表属于String类型,包含距离和由-分隔的字典单词。例如,如果搜索词与词“follow”之间的距离是 2,那么保存在dictWordDist列表中的条目将是2-follow。稍后,使用split()方法将距离从单词中分离出来。

def calcDictDistance(word, numWords):
    file = open('1-1000.txt', 'r') 
    lines = file.readlines() 
    file.close()
    dictWordDist = []
    wordIdx = 0

    for line in lines: 
        wordDistance = levenshteinDistanceMatrix(word, line.strip())
        if wordDistance >= 10:
            wordDistance = 9
        dictWordDist.append(str(int(wordDistance)) + "-" + line.strip())
        wordIdx = wordIdx + 1

这个函数现在可以过滤dictWordDist列表,根据距离返回最匹配的单词。完整的功能如下所示。定义了一个名为closestWords的列表来保存最匹配的单词。它的长度被设置为numWords参数中的值。

dictWordDist列表进行排序,将最匹配的单词放在列表的顶部。在一个迭代次数等于numWords参数值的for循环中,dictWordDist列表被索引以返回一个包含距离和由-分隔的单词的字符串。

def calcDictDistance(word, numWords):
    file = open('1-1000.txt', 'r') 
    lines = file.readlines() 
    file.close()
    dictWordDist = []
    wordIdx = 0

    for line in lines: 
        wordDistance = levenshteinDistanceMatrix(word, line.strip())
        if wordDistance >= 10:
            wordDistance = 9
        dictWordDist.append(str(int(wordDistance)) + "-" + line.strip())
        wordIdx = wordIdx + 1

    closestWords = []
    wordDetails = []
    currWordDist = 0
    dictWordDist.sort()
    print(dictWordDist)
    for i in range(numWords):
        currWordDist = dictWordDist[i]
        wordDetails = currWordDist.split("-")
        closestWords.append(wordDetails[1])
    return closestWords

然后使用split()方法分割这个字符串,该方法返回一个包含两个元素的列表:第一个是距离,第二个是单词。这个单词被插入到由calcDictDistance()函数返回的closestWords列表中。现在这个功能被认为是完整的。

下面是一个调用calcDictDistance()函数的例子。搜索词是pape,匹配数是3

print(calcDictDistance("pape", 3))

上面代码的输出如下所示。Levenshtein 距离成功地帮助提出了好的建议,尤其是前两个词。通过这样做,用户不必输入完整的单词,只需输入一些区分该单词的字符,程序就能给出有助于自动完成或自动纠正的建议。

page
paper
age

结论

本教程讨论了使用动态编程方法的 Levenshtein 距离的 Python 实现。我们首先创建了一个名为levenshteinDistanceDP()的函数,其中创建了一个 2d 距离矩阵来保存两个单词的所有前缀之间的距离。这个函数接受这两个单词作为输入,并返回一个代表它们之间距离的数字。

创建另一个名为calcDictDistance()的函数来构建 Levenshtein 距离的有用应用程序,其中用户提供一个单词,程序基于字典搜索返回最匹配的单词。这有助于在用户键入时自动完成或自动更正文本。

如何用 Scikit-Learn 实现支持向量机

原文:https://blog.paperspace.com/implementing-support-vector-machine-in-python-using-sklearn/

在本教程中,我们将介绍:

  1. 支持向量机算法简介
  2. 使用 Python 和 Sklearn 实现 SVM

所以,让我们开始吧!

支持向量机简介

支持向量机(SVM)是一种监督机器学习算法,可用于分类和回归问题。SVM 即使在数据量有限的情况下也能表现得很好。

在这篇文章中,我们将专门学习支持向量机分类。让我们先来看看支持向量机算法的一些一般用例。

用例

疾病分类:例如,如果我们有关于患有糖尿病等疾病的患者的数据,我们可以预测新患者是否可能患有糖尿病。

文本的分类:首先我们需要将文本转换成向量表示。然后,我们可以将支持向量机算法应用于编码文本,以便将其分配到特定的类别。

图像分类:图像被转换成包含像素值的矢量,然后 SVM 分配一个类别标签。

现在让我们了解一些与 SVM 相关的关键术语。

关键术语

Source: Oscar Contreras Carrasco

支持向量:支持向量是距离超*面较*的数据点,如上图所示。使用支持向量,该算法最大化了类别之间的界限或间隔。如果支持向量改变,超*面的位置也将改变。

超*面:二维中的超*面简单来说就是一条最好地分隔数据的直线。这条线是类之间的判定边界,如上图所示。对于三维数据分布,超*面将是二维表面,而不是直线。

Margin: 这是每个支持向量之间的距离,如上图所示。该算法旨在最大化利润。寻找最大余量(以及最佳超*面)的问题是一个优化问题,可以通过优化技术来解决。

内核:内核是一种应用于数据点的函数,用于将原始非线性数据点映射到高维空间,在高维空间中它们是可分离的。在许多情况下,不会有一个线性的决策边界,这意味着没有一条直线将两个类别分开。内核解决了这个问题。有很多种内核函数可用。RBF(径向基函数)核通常用于分类问题。

理解了这些关键术语后,让我们开始研究数据。

探索性数据分析

首先,我们将导入所需的库。我们正在导入numpypandasmatplotlib。除此之外,我们还需要从sklearn.svm进口 SVM。

我们还将使用来自sklearn.model_selectiontrain_test_split,以及来自sklearn.metricsaccuracy_score。我们将使用matplotlib.pyplot进行可视化。

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt

一旦库被导入,我们需要从 CSV 文件读取数据到 Pandas 数据框。让我们检查前 10 行数据。

该数据是为潜在糖尿病患者检查的数据的集合。为简单起见,我们考虑两个特征年龄和血糖水*以及一个二元目标变量。值 1 表示糖尿病,0 表示没有糖尿病。

df = pd.read_csv('SVM-Classification-Data.csv')
df.head()

Support Vector Machine Classification Data

为了更好地了解数据,我们绘制了一个数据条形图,如下所示。条形图是一种用矩形条表示分类数据的图表,矩形条的高度代表它们所具有的值。条形图可以垂直或水*绘制。

下图是年龄和血糖栏之间的垂直图表。

Bar Chart

为了更好地了解异常值,我们也可以看看散点图。下面是数据中出现的特征的散点图。

探索完数据后,我们可能想做一些如下的数据预处理任务。

数据预处理:

在将数据输入支持向量分类模型之前,我们需要做一些预处理。

这里我们将创建两个变量 x 和 y。x 代表模型的特征,y 代表模型的标签。我们将创建 x 和 y 变量,方法是从数据集中取出它们,并使用 sklearn 的train_test_split函数将数据分成训练集和测试集。

x = df.drop('diabetes',axis=1)
y = df['diabetes']
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.25, random_state=42)

请注意,测试大小为 0.25 表示我们使用了 25%的数据进行测试。random_state确保再现性。对于train_test_split的输出,我们得到x_trainx_testy_trainy_test的值。我们将使用 x_train 和 y_train 来训练模型,然后我们将一起使用 x_test 和 y_test 来测试模型。

数据预处理完成后,现在是定义和拟合模型的时候了。

定义并拟合模型

我们将创建一个model变量并实例化 SVC 类。在此之后,我们将训练模型,但在此之前,让我们讨论支持向量分类器模型的一些重要参数,如下所列。

内核 : kernel是指用于模式分析的算法类。这是一个字符串参数,是可选的。默认值是 RBF。常见的可能值有“线性”、“多边形”、“rbf”、“sigmoid”、“预计算”。

线性核是最常用的核之一。当数据是线性可分离的时使用,这意味着数据可以用一条线来分离。

当数据不是线性可分时,使用 RBF 核。支持向量机的 RBF 核创建给定特征的非线性组合,并将给定数据样本转换到更高维的特征空间,在该空间中,我们可以使用线性决策边界来分离类别。

正则化 C : C是一个正则化参数。正则化与 c 成反比,它必须是正的。

次数 : 次数是多项式核函数的次数。它被所有其他内核忽略,比如 linear。

Verbose:这将启用详细输出。Verbose 是一个通用编程术语,用于生成大部分日志输出。Verbose 的意思是要求程序告诉所有关于它一直在做什么的事情。**

Random _ State:Random _ State 是随机数生成器使用的种子。这用于确保再现性。换句话说,为了在拟合期间获得确定性行为,random_state必须是固定的。

*`SupportVectorClassModel = SVC()
SupportVectorClassModel.fit(x_train,y_train)`*

在我们定义了上面的模型之后,我们需要使用给定的数据来训练模型。为此我们使用了如上图所示的fit()方法。该方法传递了两个参数,这是我们感兴趣的数据(在本例中,年龄和血糖以及数据集的 diebetes 训练部分)。**

一旦模型被正确训练,它将输出 SVC 实例,如下面单元格的输出所示。

模型训练完成将自动引导您尝试一些预测。

使用模型预测

一旦模型经过训练,它就可以进行预测了。我们可以在模型上使用predict方法,并将x_test作为参数传递,以获得作为y_pred的输出。

请注意,预测输出是一个对应于输入数组的实数数组。

**`y_pred = SupportVectorClassModel.predict(x_test)`**

**

Support Vector Machine Prediction**

一旦预测完成,我们自然会测试模型的准确性。

评估模型

现在是时候检查我们的模型在测试数据上的表现了。为此,我们通过发现模型产生的准确性来评估我们的模型。

我们将使用 accuracy_score 函数,并将两个参数 y_test 和 y_pred 传递给该函数。

**`accuracy = accuracy_score(y_test,y_pred)*100
99.19678714859438`**

正如你在上面看到的,这个模型的精确度大约是 99.19%。

尾注

在本教程中,我们学习了什么是支持向量机算法及其使用案例。对于这个问题,我们还讨论了各种探索性的数据分析图,如条形图和散点图。

最后实现了支持向量分类算法并打印了预测结果。

我希望你喜欢这篇文章,如果需要的话,你可能会在将来的项目中使用它。

快乐学习!

通过迁移学习、数据扩充、LR Finder 等提高模型准确性

原文:https://blog.paperspace.com/improving-model-accuracy/

在处理由专家预先清理、测试、分割和处理的流行数据集时,很容易获得 90%以上的准确率。您只需要将数据集导入并提供给互联网上最流行的模型架构。

在影像分类中,当一个新数据集的某个类别中只有很少的影像,或者影像与您将在生产中处理的影像不相似时,事情会变得有点困难。流行的模型架构似乎没有帮助,迫使你陷入一个只有 50%准确率的角落,然后变成一个概率游戏,而不是机器学习本身。

本文重点探讨所有这些方法、工具以及更多内容,以帮助您构建健壮的模型,这些模型可以在生产中轻松部署。尽管其中一些方法也适用于其他目标,但我们将重点放在图像分类上来探讨这个主题。

为什么自定义数据集达不到高精度?

重要的是要说明为什么定制数据集在实现良好的性能指标方面最失败。当您尝试使用自己创建的数据集或从团队获得的数据集来创建模型时,可能会遇到这种情况。缺乏多样性可能是基于它的模型性能不佳的主要原因之一。图像的光照、颜色、形状等参数可能会有显著变化,在构建数据集时可能不会考虑这一点。数据增强可能会帮助您解决这个问题,我们将进一步讨论这个问题。

另一个原因可能是缺乏对每个类别的关注:一个数据集有一种咖啡的 1000 多张图像,而另一种只有 100 多张图像,这在可以学习的特征方面产生了很大的不*衡。另一个失败可能是数据收集的来源与生产中收集数据的来源不匹配。这种情况的一个很好的例子可以是从具有差的视频质量的安全摄像机中检测到鸟,该差的视频质量作为在高清晰度图像上训练的模型的输入。有各种方法可以处理这种情况。

为什么生产水*的准确性很重要?

既然我们已经讨论了为什么定制数据集在第一次运行时无法达到“生产级别的准确性”,那么理解为什么生产级别的准确性很重要。简而言之,我们的模型应该能够给出在现实场景中可以接受的结果,但不一定要达到 100%的准确性。使用测试数据集图像或文本很容易看到正确的预测,这些图像或文本用于将模型超调至最佳状态。尽管我们不能确定一个阈值精度,超过这个精度,我们的模型就有资格进行部署,但根据经验,如果训练和验证数据是随机分割的,那么至少有 85-90%的验证精度是很好的。始终确保验证数据是多样化的,并且其大部分数据与模型在生产中使用的数据相似。数据预处理可以通过在输入前调整大小或过滤文本来确保图像大小,从而在一定程度上帮助您实现这一点。在开发过程中处理此类错误有助于改进您的生产模式并获得更好的结果。

数据扩充:改善数据集的完美方式

只要你能通过数据扩充等方法充分利用数据集,拥有一个小数据集是没问题的。这个概念侧重于预处理现有数据,以便在我们没有足够数据的时候生成更多样化的数据用于训练。让我们用一个小例子来讨论一下图像数据增强。这里我们有一个来自 TensorFlow 的石头剪子布数据集,我们希望不重复生成更多。 Tensorflow 数据集对象提供了许多有助于数据扩充的操作,等等。这里我们首先缓存数据集,这有助于我们进行内存管理,因为数据集第一次迭代时,它的元素将被缓存在指定的文件或内存中。然后缓存的数据可以在以后使用。

之后我们重复数据集两次,这增加了它的基数。仅仅重复的数据对我们没有帮助,但是我们在加倍的数据集上添加了一个映射层,这在某种程度上帮助我们随着基数的增加生成新的数据。在这个例子中,我们将随机图像左右翻转,这避免了重复并确保了多样性。

import tensorflow as tf
import tensorflow_datasets as tfds

DATASET_NAME = 'rock_paper_scissors'

(dataset_train_raw, dataset_test_raw), dataset_info = tfds.load(
    name=DATASET_NAME,
    data_dir='tmp',
    with_info=True,
    as_supervised=True,
    split=[tfds.Split.TRAIN, tfds.Split.TEST],
)

def preprocess_img(image, label):
    # Make image color values to be float.
    image = tf.cast(image, tf.float32)
    # Make image color values to be in [0..1] range.
    image = image / 255.
    # Make sure that image has a right size
    image = tf.image.resize(image, [256,256])
    return image, label

dataset_train = dataset_train_raw.map(preprocess_img)
dataset_test = dataset_test_raw.map(preprocess_img)

print("Dataset Cardinality Before Augmentation: ",dataset_train.cardinality().numpy())

dataset_train = dataset_train.cache().repeat(2).map(
    lambda image, label: (tf.image.random_flip_left_right(image), label)
)

print("Dataset Cardinality After Augmentation: ",dataset_train.cardinality().numpy())

输出

Dataset Cardinality Before Augmentation:  2520
Dataset Cardinality After Augmentation:  5040

图像上有更多的映射可以探索,可以在对比度、旋转等方面进一步创造更多的变化。阅读这篇文章了解更多细节。您可以对图像执行更多操作,如旋转、剪切、改变对比度等等。在图像数据在照明、背景和其他方面不代表真实世界输入的情况下,数据扩充至关重要。在这里,我们讨论了通过 Tensorflow 等框架进行数据扩充,但是除了旋转和剪切之外,您还可以进行手动数据扩充。

Mapping 是一个强大的工具,因为您可以对单个数据执行任何操作,而无需经历迭代。调整图像大小、格式化文本等等都可以用它来灵活处理。

迁移学习:使用小数据集

有些情况下,您只有少量图像,并且希望构建一个图像分类模型。如果图像很少,模型可能无法学习模式和更多内容,这将导致模型过拟合或欠拟合,在现实世界输入的生产中表现不佳。在这种情况下,建立一个好模型最简单的方法就是通过迁移学习。

有像 VGG16 这样著名的预训练模型,确实很擅长图像分类。由于它在构建时所接触到的各种各样的数据,以及其体系结构的复杂性质(包括许多卷积神经网络),它在图像分类目标方面比我们可以用小数据集构建的小模型更有深度。我们可以使用这样的预训练模型,通过替换最后几层(在大多数情况下)来处理我们问题的相同目标。我们之所以替换最后一层,是为了重构适合我们用例的模型输出,在图像分类的情况下,选择合适的类别数进行分类。如果我们遵循相应的预训练模型架构的文档和围绕它的框架文档,我们不仅可以替换最后一层,还可以替换任意多的层。让我们建立一个样本迁移学习机器学习模型。

首先,我们正在加载和预处理我们之前使用的相同的石头剪刀布数据集。

import tensorflow as tf
import tensorflow_datasets as tfds
from tensorflow.keras.applications import ResNet50
from keras.layers import GlobalAveragePooling2D, Dense
from keras.layers import BatchNormalization, Dropout
from keras.models import Model

DATASET_NAME = 'rock_paper_scissors'

(dataset_train_raw, dataset_test_raw), dataset_info = tfds.load(
    name=DATASET_NAME,
    data_dir='tmp',
    with_info=True,
    as_supervised=True,
    split=[tfds.Split.TRAIN, tfds.Split.TEST],
)

def preprocess_img(image, label):
    # Make image color values to be float.
    image = tf.cast(image, tf.float32)
    # Make image color values to be in [0..1] range.
    image = image / 255.
    # Resize images to ensure same input size
    image = tf.image.resize(image, [256,256])
    return image, label

dataset_train = dataset_train_raw.map(preprocess_img)
dataset_test = dataset_test_raw.map(preprocess_img)

dataset_train = dataset_train.batch(64)
dataset_test = dataset_test.batch(32)

现在我们将使用 ResNet50 作为迁移学习模型。我们将设置training able = false来冻结 ResNet50 架构,不将其暴露于训练。这将为我们节省大量时间,因为模型将只训练最后几层。当我们按小时付费进行培训时,这是有益的。

# ResNet50 with Input shape of our Images
# Include Top is set to false to allow us to add more layers

res = ResNet50(weights ='imagenet', include_top = False, 
               input_shape = (256, 256, 3)) 

# Setting the trainable to false
res.trainable = False

x= res.output
x = GlobalAveragePooling2D()(x)
x = BatchNormalization()(x)
x = Dropout(0.5)(x) 
x = Dense(512, activation ='relu')(x)
x = BatchNormalization()(x)
x = Dropout(0.5)(x)
x = Dense(3, activation ='softmax')(x)
model = Model(res.input, x)

model.compile(optimizer ='Adam', 
              loss ="sparse_categorical_crossentropy", 
              metrics =["sparse_categorical_accuracy"])

model.summary()

简而言之

( Only the Bottom part of Model Summary Included here as the ResNet Summary is long)
_____________________________________________________________________________
conv5_block3_out (Activation)   (None, 8, 8, 2048)   0           conv5_block3_add[0][0]           
_____________________________________________________________________________
global_average_pooling2d_5 (Glo (None, 2048)         0           conv5_block3_out[0][0]           
_____________________________________________________________________________
batch_normalization_11 (BatchNo (None, 2048)         8192        global_average_pooling2d_5[0][0] 
_____________________________________________________________________________
dropout_11 (Dropout)            (None, 2048)         0           batch_normalization_11[0][0]     
_____________________________________________________________________________
dense_11 (Dense)                (None, 512)          1049088     dropout_11[0][0]                 
_____________________________________________________________________________
batch_normalization_12 (BatchNo (None, 512)          2048        dense_11[0][0]                   
_____________________________________________________________________________
dropout_12 (Dropout)            (None, 512)          0           batch_normalization_12[0][0]     
_____________________________________________________________________________
dense_12 (Dense)                (None, 3)            1539        dropout_12[0][0]                 
=============================================================================
Total params: 24,648,579
Trainable params: 1,055,747
Non-trainable params: 23,592,832

模特培训

model.fit(dataset_train, epochs=6, validation_data=dataset_test)
Epoch 1/10
40/40 [==============================] - 577s 14s/step - loss: 0.2584 - sparse_categorical_accuracy: 0.9147 - val_loss: 1.1330 - val_sparse_categorical_accuracy: 0.4220

Epoch 2/10
40/40 [==============================] - 571s 14s/step - loss: 0.0646 - sparse_categorical_accuracy: 0.9802 - val_loss: 0.8574 - val_sparse_categorical_accuracy: 0.4247

Epoch 3/10
40/40 [==============================] - 571s 14s/step - loss: 0.0524 - sparse_categorical_accuracy: 0.9813 - val_loss: 0.7408 - val_sparse_categorical_accuracy: 0.6425

Epoch 4/10
40/40 [==============================] - 570s 14s/step - loss: 0.0376 - sparse_categorical_accuracy: 0.9881 - val_loss: 0.6260 - val_sparse_categorical_accuracy: 0.7016

Epoch 5/10
40/40 [==============================] - 570s 14s/step - loss: 0.0358 - sparse_categorical_accuracy: 0.9881 - val_loss: 0.5864 - val_sparse_categorical_accuracy: 0.6532

Epoch 6/10
40/40 [==============================] - 570s 14s/step - loss: 0.0366 - sparse_categorical_accuracy: 0.9873 - val_loss: 0.4445 - val_sparse_categorical_accuracy: 0.8602

我们可以看到,在相对较小的数据集上训练的模型表现非常好,验证准确率为 86%。如果你关注每个时期所花费的时间,它不到 10 分钟,因为我们保持 ResNet 层不可训练。ResNet50 帮助我们把它的学习转移到我们的问题上。您可以尝试各种预先训练好的模型,看看它们如何适合您的问题,以及哪种模型性能最好。

LR Finder:寻找完美的学习速度

Learning Rate Finder 是一款功能强大的工具,顾名思义,可以帮助你轻松找到 LR。尝试所有的学习率来找到完美的学习率是一种低效且耗时的方法。LR Finder 是实现这一点的最高效、最省时的方法。我们来看看如何实现。我们继续使用相同的数据集、预处理和模型架构,因此从这里开始不再重复。

!pip install tensorflow-hub
!git clone https://github.com/beringresearch/lrfinder/
!cd lrfinder && python3 -m pip install .

import numpy as np
from lrfinder import LRFinder
K = tf.keras.backend

BATCH = 64

# STEPS_PER_EPOCH = np.ceil(len(train_data) / BATCH)
# here Cardinality or Length of Train dataset is 2520

STEPS_PER_EPOCH = np.ceil(2520 / BATCH)
lr_finder = LRFinder(model)
lr_finder.find(dataset_train, start_lr=1e-6, end_lr=1, epochs=10,
               steps_per_epoch=STEPS_PER_EPOCH)

learning_rates = lr_finder.get_learning_rates()
losses = lr_finder.get_losses()

best_lr = lr_finder.get_best_lr(sma=20)

# Setting it as our model's LR through Keras Backend
K.set_value(model.optimizer.lr, best_lr)
print(best_lr)
Epoch 1/10
40/40 [==============================] - 506s 13s/step - loss: 1.7503 - sparse_categorical_accuracy: 0.3639
Epoch 2/10
40/40 [==============================] - 499s 12s/step - loss: 1.5044 - sparse_categorical_accuracy: 0.4302
Epoch 3/10
40/40 [==============================] - 498s 12s/step - loss: 0.9737 - sparse_categorical_accuracy: 0.6163
Epoch 4/10
40/40 [==============================] - 495s 12s/step - loss: 0.4744 - sparse_categorical_accuracy: 0.8218
Epoch 5/10
40/40 [==============================] - 495s 12s/step - loss: 0.1946 - sparse_categorical_accuracy: 0.9313
Epoch 6/10
40/40 [==============================] - 495s 12s/step - loss: 0.1051 - sparse_categorical_accuracy: 0.9663
Epoch 7/10
40/40 [==============================] - 89s 2s/step - loss: 0.1114 - sparse_categorical_accuracy: 0.9576

我们得到的最佳学习率是 6.31 e-05,,我们使用 Keras 后端将它设置为我们的模型 LR。从输出来看,很明显,这个过程只花了几个时期,它分析了所有可能的学习率,并找到了最好的一个。我们可以使用 Matplotlib 可视化学习率及其性能。红线代表最佳学习率。

import matplotlib.pyplot as plt

def plot_loss(learning_rates, losses, n_skip_beginning=10, n_skip_end=5, x_scale='log'):
    f, ax = plt.subplots()
    ax.set_ylabel("loss")
    ax.set_xlabel("learning rate (log scale)")
    ax.plot(learning_rates[:-1],
            losses[:-1])
    ax.set_xscale(x_scale)
    return(ax)

axs = plot_loss(learning_rates, losses)
axs.axvline(x=lr_finder.get_best_lr(sma=20), c='r', linestyle='-.')

learning rate finder graph

提前停止:在你的模型忘记之前拯救它

你可能记得训练一个模型超过 20 个历元,模型的损失在一个点之后开始增加。你被卡住了,你什么也做不了,因为打断会扼杀这个过程,等待会给你一个表现更差的模型。在这种情况下,当损失等参数开始增加时,你可以轻松地获得最佳模型并逃离这个过程,尽早停止正是你想要的。这也将节省您的时间,如果模型开始显示早期的正损失,该过程将通过向您提供最后的最佳损失模型而停止,并且不计算进一步的时期。您也可以根据任何可以监控的参数(如精确度)设置提前停止。提前停车的主要参数之一是耐心。这是您希望看到模型是否停止显示增加的损失并回到学习轨道的次数,否则它将保存增加前的最后最佳损失并停止训练。现在你可能已经有了一个小想法,让我们来看一个例子。

from tensorflow.keras.callbacks import EarlyStopping

earlystop_callback = EarlyStopping(
  monitor='val_loss', min_delta=0.0001, patience=2)

model.fit(dataset_train, epochs=20, validation_data=dataset_test, callbacks=[earlystop_callback])

在本例中,提前停止被设置为监控验证损失。参数最小 delta ,即我们希望损失的最小差值,被设置为 0.0001,耐心被设置为 2。耐心为 2 意味着模型可以在验证损失增加的情况下再运行 2 个时期,但是如果它没有显示减少的损失,那么(低于从其开始增加的损失),该过程将通过返回最后的最佳损失版本而被终止。

( Only the last part of training shown )

Epoch 10/20
40/40 [==============================]  loss: 0.0881 - sparse_categorical_accuracy: 0.9710 - val_loss: 0.4059 
Epoch 11/20
40/40 [==============================]  loss: 0.0825 - sparse_categorical_accuracy: 0.9706 - val_loss: 0.4107 
Epoch 12/20
40/40 [==============================]  loss: 0.0758 - sparse_categorical_accuracy: 0.9770 - val_loss: 0.3681 
Epoch 13/20
40/40 [==============================]  loss: 0.0788 - sparse_categorical_accuracy: 0.9754 - val_loss: 0.3904 
Epoch 14/20
40/40 [==============================]  loss: 0.0726 - sparse_categorical_accuracy: 0.9770 - val_loss: 0.3169 
Epoch 15/20
40/40 [==============================]  loss: 0.0658 - sparse_categorical_accuracy: 0.9786 - val_loss: 0.3422 
Epoch 16/20
40/40 [==============================]  loss: 0.0619 - sparse_categorical_accuracy: 0.9817 - val_loss: 0.3233 

即使设置了 20 个时期来训练,模型也在第 16 个时期后停止训练,从而避免了模型因验证损失增加而遗忘。我们的训练结果有一些非常好的观察,可以帮助我们更深入地了解早期停止。在第 14 个纪元模型是在其最佳损失,0.3168。下一个时期显示了 0.3422 的增加的损失,即使下一个时期显示了 0.3233 的减少的损失,其小于前一个时期,其仍然大于从增加开始的点(0.3168),因此在保存第 14 个时期的模型版本时训练停止。它等待 2 个时期,以查看训练是否会因为耐心参数被设置为 2 而自我纠正。

另一个有趣的观察结果是从第 10 个时期到第 12 个时期,尽管在第 11 个时期损失增加了(0.4107),但是与第 10 个时期的损失(0.4059)相比,第 12 个时期的损失减少了(0.3681)。因此,随着模型回到正轨,训练仍在继续。这可以被看作是对耐心的一个很好的利用,因为让它默认会在第 11 个纪元后终止训练,而不是尝试下一个纪元。

使用早期停止的一些技巧是,如果你正在 CPU 上训练,使用小的耐心设置。如果在 GPU 上训练,使用更大的耐心值。对于甘这样的模型,不如用小耐心,省模型检查点。如果您的数据集不包含大的变化,那么使用更大的耐心。设置 min_delta 参数始终基于运行几个历元并检查验证损失,因为这将使您了解验证损失如何随着历元而变化。

分析您的模型架构

这是一个通用的方法,而不是一个确定的方法。在大多数情况下,例如涉及卷积神经网络的图像分类,非常重要的一点是,即使框架处理流程,您也要很好地了解您的卷积、它们的核大小、输出形状等等。像 ResNet 这样的非常深入的体系结构是为了在 256x256 大小的图像上进行训练而设计的,调整它的大小以适应数据集图像为 64x64 的情况可能会执行得很差,导致某些预训练模型的精度为 10%。这是因为预训练模型中的层数和您的图像大小。很明显,随着通道*行增加,图像张量通过卷积时在尺寸上变得更小。在 256x256 上训练的预训练模型最终将具有至少 8×8 的张量大小,而如果将它重构为 64×64,则最后几个卷积将获得 1×1 的张量,与 8×8 的输入相比,它学习得很少。这是在处理预训练模型时要小心处理的事情。

另一方面是当你建立自己的回旋。确保它有超过 3 层的深度,同时,考虑到你的图像尺寸,它也不会影响输出尺寸。分析模型摘要非常重要,因为您可以根据卷积层的输出形状等因素来决定密集层的设置。在处理多特性和多输出模型时,架构非常重要。在这种情况下,模型可视化会有所帮助。

结论

到目前为止,我们已经讨论了一些最有影响力和最受欢迎的方法,这些方法可以提高您的模型准确性、改善您的数据集以及优化您的模型架构。还有很多其他的方法等着你去探索。除此之外,还有更多次要的方法或指南可以帮助您实现上述所有方面,例如加载数据时的混排、使用 TensorFlow dataset 对象处理您自定义创建的数据集、使用我们之前讨论的映射来处理操作。我建议你在训练时注重验证准确性,而不是训练准确性。验证数据必须得到很好的处理,它的多样性和对模型在生产中将要接触到的真实世界输入的代表性是非常重要的。

尽管我们对一个图像分类问题使用了所有的方法,其中一些像映射、学习率查找器等等。适用于涉及文本和更多的其他问题。构建低于*均精度的模型在现实生活中没有价值,因为精度很重要,在这种情况下,这些方法可以帮助我们构建一个接*完美的模型,并考虑到所有方面。超参数调优是一种流行的方法,本文没有详细讨论。简而言之,就是尝试超参数的各种值,如时期、批量大小等。超参数调整的目的是获得最佳参数,最终得到更好的模型。LR 查找器是超参数调整学习速率的有效方法。在处理 SVR 等其他机器学习算法时,超参数调整起着至关重要的作用。

我希望您已经很好地了解了使用各种想法和方法来处理您的模型以实现更好的性能是多么重要,并为您未来的机器学习之旅尽善尽美。我希望这些方法对你有用。感谢阅读!

如何提高 YOLOv3

原文:https://blog.paperspace.com/improving-yolo/

YOLOv3 是一种流行的快速对象检测算法,但遗憾的是不如 RetinaNet 或更快的 RCNN 准确,你可以在下图中看到。在这篇文章中,我将讨论最*的对象检测文献中提出的两种简单而强大的方法来改进 YOLOv3。这些是:1) 用于对象检测的不同训练试探法,以及 2) 特征金字塔的自适应空间融合。我们将一个一个来看。让我们深入研究一下。

Source: YOLOv3 paper

用于对象检测的不同训练启发法

随着精细训练程序的使用,图像分类网络的性能有了很大的提高。关于这些训练技巧的简单讨论可以在这里找到来自 CPVR 2019。类似地,对于对象检测网络,一些人提出了不同的训练试探法(1),例如:

  • 保持几何对齐的图像混淆
  • 使用余弦学习率调度程序
  • 同步批处理规范化
  • 数据扩充
  • 标签*滑

这些修改将 YOLOv3 的 mAP@(.5:.9)分数从 33.0 提高到 37.0,而在推断期间没有任何额外的计算成本,并且在训练期间计算成本的增加可以忽略不计(1)。可以在这里找到经过预训练重量的改良 YOLOv3。为了理解这些启发背后的直觉,我们将一个一个地看它们。

图像 _ 混合

先说 mixup 训练。在图像分类网络中,图像混合只是两幅图像的像素的线性插值(例如下面的左图)。用于图像分类的混合算法中的混合比率的分布是从一个贝塔分布,B(0.2,0.2)中得出的,该分布也用于混合使用相同比率的独热图像标签。为了执行混合,两个图像必须具有相同的尺寸,因此它们通常被调整大小,然而这将需要图像中存在的对象的边界框也被调整大小。为了避免这种麻烦,使用了一种新的图像混合策略。它从两个图像中取出一个最大宽度和最大高度的图像,像素值等于 0 到 255,并向其添加两个图像的线性插值。对于这种混合策略,从β分布 B(1.5,1.5)获得混合比率,因为(1)发现对于对象检测,B(1.5,1.5)给出视觉上连贯的混合图像和经验上更好的地图分数。对象标签被合并为一个新数组。这将在下面演示。现在我们有一种混合方法用于图像分类,另一种用于物体检测。

Left: image classification mixup (source). Right: object detection mixup (source).

目标在训练图像中的自然同现在目标检测网络的性能中起着重要作用。例如,一个碗、一个杯子和一个冰箱应该比一个冰箱和一只大象更频繁地出现在一起。这使得在其典型环境之外检测物体变得困难。使用具有增加的混合比率的图像混合使得网络对于这种检测问题更加鲁棒。Mixup 还充当正则化器,并强制网络支持简单的线性行为。

def object_det_mix_up_(image1, image2, mixup_ratio):

    '''
    image1, image2: images to be mixed up, type=ndarray
    mixup_ratio: ratio in which two images are mixed up
    Returns a mixed-up image with new set of smoothed labels
    '''

    height = max(image1.shape[0], image2.shape[0])
    width = max(image1.shape[1], image2.shape[1])
    mix_img = np.zeros((height, width, 3),dtype=np.float32)
    mix_img[:image1.shape[0], :image1.shape[1], :] = image1.astype(np.float32)\
                                                     * mixup_ratio
    mix_img[:image2.shape[0], :image2.shape[1], :] += image2.astype(np.float32)\
                                                     * (1-mixup_ratio)
    return mix_img 

Image mixup code

学习率调度程序

大多数流行的对象检测网络(更快的 RCNN,YOLO 等。)使用学习率计划程序。根据(1),所产生的急剧学习速率转变可能导致优化器在接下来的迭代中重新稳定学习势头。使用带有适当预热(两个时期)的余弦调度器(学习率缓慢下降)可以比使用步进调度器提供更好的验证精度,如下所示。

Comparison of step scheduler vs cosine scheduler on the PASCAL VOC 2007 test set (source)

分类标题标签*滑

在标签*滑中,我们使用以下公式将我们的独热编码标签转换为*滑概率分布:

source

其中 K 为类数,ε为小常数,q 为地面真值分布。这通过降低模型的置信度来起到正则化的作用。

同步批处理规范化

在当前的深度卷积架构中,批量规范化被认为是一个重要的层。它负责加速训练过程,并通过标准化隐藏层的激活使网络对权重初始化不那么敏感。由于大的输入图像大小、特征金字塔架构的存在以及大量的候选对象提议(在多级网络的情况下),单个 GPU 上可以容纳的批量大小变得非常小(即,每批少于 8 个左右的图像)。

在分布式训练范例中,隐藏的激活在每个 GPU 内被规范化。这会导致计算有噪声的均值和方差估计,从而阻碍整个批量标准化过程。同步批处理标准化因此被建议通过考虑多个 GPU 上的激活来帮助增加批处理大小,以便计算统计估计值。因此,这使得计算的噪声更小。

使用 NVIDIA 的 Apex 库在 PyTorch 中进行混合精度和分布式训练,可以轻松实现同步批量标准化。我们还可以使用convert_syncbn_model方法将 PyTorch 中的任何标准BatchNorm模块转换为SyncBatchNorm,该方法递归遍历传递的模块及其子模块,用apex.parallel.SyncBatchNorm替换torch.nn.modules.batchnorm._BatchNorm的所有实例,其中apex.parallel.SyncBatchNorm是 PyTorch 模块,用于在 NVIDIA GPUs 上执行同步批处理规范。

import apex
sync_bn_model = apex.parallel.convert_syncbn_model(model) 

Converting standard batch normalization to synchronized batch normalization in PyTorch using Apex

数据扩充

数据增强技术似乎也改进了目标检测模型,尽管它们对单级检测器的改进大于多级检测器。根据(1),这背后的原因是在像 fast-RCNN 这样的多级检测器中,从大量生成的 ROI 中采样一定数量的候选对象提议,通过重复裁剪特征图上的相应区域来产生检测结果。由于这种裁剪操作,多阶段模型代替了随机裁剪输入图像的操作,因此这些网络不需要在训练阶段应用大量的几何放大。

根据经验,像随机裁剪(带约束)、扩展、水*浮动、调整大小(带随机插值)和颜色抖动(包括亮度、色调、饱和度和对比度)这样的增强方法在训练期间效果更好。在测试过程中,通过随机选择一种流行的插值技术来调整图像的大小,然后进行归一化。

def horizontal_flip(image, boxes):
	''' 
    Flips the image and its bounding boxes horizontally
    '''

    _, width, _ = image.shape
    if random.randrange(2):
        image = image[:, ::-1]
        boxes = boxes.copy()
        boxes[:, 0::2] = width - boxes[:, 2::-2]
    return image, boxes

def random_crop(image, boxes, labels, ratios = None):
	''' 
    Performs random crop on image and its bounding boxes 
    '''

    height, width, _ = image.shape

    if len(boxes)== 0:
        return image, boxes, labels, ratios

    while True:
        mode = random.choice((
            None,
            (0.1, None),
            (0.3, None),
            (0.5, None),
            (0.7, None),
            (0.9, None),
            (None, None),
        ))

        if mode is None:
            return image, boxes, labels, ratios

        min_iou, max_iou = mode
        if min_iou is None:
            min_iou = float('-inf')
        if max_iou is None:
            max_iou = float('inf')

        for _ in range(50):
            scale = random.uniform(0.3,1.)
            min_ratio = max(0.5, scale*scale)
            max_ratio = min(2, 1\. / scale / scale)
            ratio = math.sqrt(random.uniform(min_ratio, max_ratio))
            w = int(scale * ratio * width)
            h = int((scale / ratio) * height)

            l = random.randrange(width - w)
            t = random.randrange(height - h)
            roi = np.array((l, t, l + w, t + h))

            iou = matrix_iou(boxes, roi[np.newaxis])

            if not (min_iou <= iou.min() and iou.max() <= max_iou):
                continue

            image_t = image[roi[1]:roi[3], roi[0]:roi[2]]

            centers = (boxes[:, :2] + boxes[:, 2:]) / 2
            mask = np.logical_and(roi[:2] < centers, centers < roi[2:]) \
                     .all(axis=1)
            boxes_t = boxes[mask].copy()
            labels_t = labels[mask].copy()
            if ratios is not None:
                ratios_t = ratios[mask].copy()
            else:
                ratios_t=None

            if len(boxes_t) == 0:
                continue

            boxes_t[:, :2] = np.maximum(boxes_t[:, :2], roi[:2])
            boxes_t[:, :2] -= roi[:2]
            boxes_t[:, 2:] = np.minimum(boxes_t[:, 2:], roi[2:])
            boxes_t[:, 2:] -= roi[:2]

            return image_t, boxes_t,labels_t, ratios_t 

Several data augmentations to be applied during training

def preproc_for_test(image, input_size, mean, std):
	''' 
    Data Augmentation applied during testing/validation 
    :image: an ndarray object
    :input_size: tuple of int with two elements (H,W) of image
    :mean: mean of training dataset or image_net rgb mean
    :std: standard deviation of training dataset or imagenet rgb std 
    '''

    interp_methods = [cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_NEAREST, cv2.INTER_LANCZOS4]
    interp_method = interp_methods[random.randrange(5)]
    image = cv2.resize(image, input_size,interpolation=interp_method)
    image = image.astype(np.float32)
    image = image[:,:,::-1]
    image /= 255.
    if mean is not None:
        image -= mean
    if std is not None:
        image /= std
    return image.transpose(2, 0, 1)

Data augmentation to be applied for testing/validation

除了上述试探法,在输入图像的不同比例下训练 YOLOv3 模型,如{ 320 x 320352 x 352384 x 384416 x 416448 x 448480 x 480512 x 512544 x 544576 x 576608 x 608 },降低了过度拟合的风险,并提高了模型的泛化能力,就像标准的 YOLOv3 训练一样。这些变化极大地提高了 YOLOv3 的性能,但接下来我们将研究另一种方法,称为要素金字塔的自适应空间融合。如果与这些训练试探法相结合,这种技术可以使 YOLOv3 的性能甚至比更快的 RCNN 或 Mask RCNN (2)等基线更好。

特征金字塔的自适应空间融合

使用特征金字塔的对象检测网络在不同尺度的特征或者不同尺度的特征的融合下进行预测。例如,YOLOv3 以 32、16 和 8 三种不同的步幅进行预测。换句话说,如果给定一个 416 x 416 T1 的输入图像,它将对 T2 13 x 13 T3、T4 26 x 26 T5 和 T6 52 x 52 T7 进行预测。

低分辨率特征语义价值高,高分辨率特征语义价值低。低分辨率特征图还包含覆盖图像更大区域的网格单元,因此更适合检测更大的对象。相反,来自较高分辨率特征地图的网格单元更适合于检测较小的物体。这意味着仅使用一个尺度的特征来检测不同尺度的对象是困难的。为了解决这个问题,可以在不同的尺度上分别进行检测,以检测不同尺度的对象,如在单次检测器( SSD )架构中。然而,尽管该方法在计算上需要很少的额外成本,但它仍然是次优的,因为高分辨率特征图不能充分地从图像中获得语义特征。像 RetinaNet、YOLOv3 等架构。因此组合高和低语义值特征来创建语义和空间强的特征。对这些特征执行检测在速度和准确性之间呈现了更好的折衷。

不同分辨率特征的组合是通过按元素方式连接或添加它们来完成的。一些人已经提出了一种方法来组合这些特征地图,使得只有来自每个比例特征地图的相关信息被保留用于组合(2)。下图对此进行了总结。简而言之,不是像在标准 YOLOv3 中那样对每个级别的特征进行预测,而是首先对来自三个级别的特征进行重新缩放,然后在每个级别进行自适应组合,然后对这些新特征执行预测/检测。

为了更好地理解这一点,我们将看看这种方法的两个重要步骤:1)相同的重新缩放和 2)自适应特征融合。

Illustration of Adaptive Spatial Fusion of Feature Pyramids (source)

相同的重新标度

每个级别的所有要素都将被重新缩放,并且它们的通道数也将被调整。假设大小为416×416的输入图像已经作为输入给出,我们必须将级别 2 的特征(其中特征图大小为26×26,通道数为 512)与级别 3 的更高分辨率特征(分辨率为52×52,通道数为 256)进行组合。然后,该层将被下采样到26×26,同时通道的数量增加到 512。另一方面,较低分辨率级别 1 的特征(分辨率13×13,通道数 1024)将被上采样到26×26,而通道数将减少到 512。

对于上采样,首先应用一个 1 x 1 卷积层来压缩特征的通道数,然后通过插值来进行放大。对于 1/2 比率的下采样,步长为 2 的3×3卷积层用于同时修改通道数量和分辨率。对于 1/4 的比例,在 2 步卷积之前使用 2 步最大池层。下面的代码使用 PyTorch 定义并执行这些操作。

def add_conv(in_ch, out_ch, ksize, stride, leaky=True):
    """
    Add a conv2d / batchnorm / leaky ReLU block.
    Args:
        in_ch (int): number of input channels of the convolution layer.
        out_ch (int): number of output channels of the convolution layer.
        ksize (int): kernel size of the convolution layer.
        stride (int): stride of the convolution layer.
    Returns:
        stage (Sequential) : Sequential layers composing a convolution block.
    """
    stage = nn.Sequential()
    pad = (ksize - 1) // 2
    stage.add_module('conv', nn.Conv2d(in_channels=in_ch,
                                       out_channels=out_ch, kernel_size=ksize, stride=stride,
                                       padding=pad, bias=False))
    stage.add_module('batch_norm', nn.BatchNorm2d(out_ch))
    if leaky:
        stage.add_module('leaky', nn.LeakyReLU(0.1))
    else:
        stage.add_module('relu6', nn.ReLU6(inplace=True))
    return stage

Adds a convolutional block with a sequence of conv, batchnorm and relu layers

def scaling_ops(level, x_level_0, x_level_1, x_level_2):
	"""
    Performs upscaling/downscaling operation for each level of features
    Args:
        level (int): level number of features.
        x_level_0 (Tensor): features obtained from standard YOLOv3 at level 0.
        x_level_1 (Tensor): features obtained from standard YOLOv3 at level 1.
        x_level_2 (Tensor): features obtained from standard YOLOv3 at level 2.
    Returns:
        resized features at all three levels and a conv block
    """
    dim = [512, 256, 256]
    inter_dim = dim[level]
    if level==0:
        stride_level_1 = add_conv(256, inter_dim, 3, 2)
        stride_level_2 = add_conv(256, inter_dim, 3, 2)
        expand = add_conv(inter_dim, 1024, 3, 1)

        level_0_resized = x_level_0
        level_1_resized = stride_level_1(x_level_1)
        level_2_downsampled_inter = F.max_pool2d(x_level_2, 3, stride=2, padding=1)
        level_2_resized = stride_level_2(level_2_downsampled_inter)
    elif level==1:
        compress_level_0 = add_conv(512, inter_dim, 1, 1)
        stride_level_2 = add_conv(256, inter_dim, 3, 2)
        expand = add_conv(inter_dim, 512, 3, 1)

        level_0_compressed = compress_level_0(x_level_0)
        level_0_resized = F.interpolate(level_0_compressed, scale_factor=2, mode='nearest')
        level_1_resized = x_level_1
        level_2_resized = stride_level_2(x_level_2)
    elif level==2:
        compress_level_0 = add_conv(512, inter_dim, 1, 1)
        expand = add_conv(inter_dim, 256, 3, 1)

        level_0_compressed = compress_level_0(x_level_0)
        level_0_resized = F.interpolate(level_0_compressed, scale_factor=4, mode='nearest')
        level_1_resized = F.interpolate(x_level_1, scale_factor=2, mode='nearest')
        level_2_resized = x_level_2

    return level_0_resized, level_1_resized,level_2_resized, expand

Performs upscaling or downscaling given the level number and set of features

自适应特征融合

重新调整要素后,通过对所有三个重新调整的要素地图的每个像素进行加权*均来合并要素(假设所有通道的权重相同)。这些权重是在我们训练网络时动态学习的。这个等式可以更好地解释它:

source

source

source

这里使用 PyTorch 定义这些操作。

def adaptive_feature_fusion(level, level_0_resized, level_1_resized,level_2_resized, expand):
	"""
    Combines the features adaptively.
    Args:
        level (int): level number of features.
        level_0_resized (Tensor): features obtained after rescaling at level 0.
        level_1_resized (Tensor): features obtained after rescaling at at level 1.
        level_2_resized (Tensor): features obtained after rescaling at at level 2.
        expand (Sequential): a conv block
    Returns:
        out (Tensor): new combibed feature on which detection will be performed.
    """
    dim = [512, 256, 256]
    inter_dim = dim[level]
    compress_c = 16  
    weight_level_0 = add_conv(inter_dim, compress_c, 1, 1)
    weight_level_1 = add_conv(inter_dim, compress_c, 1, 1)
    weight_level_2 = add_conv(inter_dim, compress_c, 1, 1)

    weight_levels = nn.Conv2d(compress_c*3, 3, kernel_size=1, stride=1, padding=0)
    level_0_weight_v = weight_level_0(level_0_resized)
    level_1_weight_v = weight_level_1(level_1_resized)
    level_2_weight_v = weight_level_2(level_2_resized)
    levels_weight_v = torch.cat((level_0_weight_v, level_1_weight_v, level_2_weight_v),1)
    levels_weight = weight_levels(levels_weight_v)
    levels_weight = F.softmax(levels_weight, dim=1)

    fused_out_reduced = level_0_resized * levels_weight[:,0:1,:,:]+\
                        level_1_resized * levels_weight[:,1:2,:,:]+\
                        level_2_resized * levels_weight[:,2:,:,:]

    out = expand(fused_out_reduced)
    return out 

Performs Adaptive Feature Fusion given rescaled features

根据(2),新适应的特征过滤掉不同尺度上的不一致性(使用自适应空间融合权重),这是具有特征金字塔的单次检测器的主要限制。当与使用上述训练试探法训练的 YOLOv3 模型一起使用时,它显著提高了(在 COCO test-dev 2014 上,给出的 mAP@(.5:.95)为 42.4,而 YOLOv3 基线 mAP@(.5:.95)为 33.0)(2)在推断期间,YOLOv3 基线的计算成本仅略有增加(也在 COCO test-dev 2014 上测量),即从 YOLOv3 基线的 52 FPS(每秒帧数)提高到 45.5 FPS (1)。此外,还集成了其他几个模块,如 DropBockRFB 等。在自适应特征融合之上,可以超越(2)更快的 RCNN 和掩模 RCNN 基线。你可以在这里下载预先训练好的重量。

尾注

在本文中,我们看到了如何通过使用简单的对象检测训练试探法和自适应特征融合的新技术,在不增加或仅少量增加推理成本的情况下,显著改善 YOLOv3 基线。这些方法需要最少的架构更改,并且可以轻松集成。上面提到的训练试探法也可以直接用于微调预训练的 YOLOv3 模型。改进后的 YOLOv3 无疑在速度和精度之间提供了更好的*衡。您可以在这里找到使用上述方法对您的自定义数据进行微调的完整代码。

SKRohit/Improving-YOLOv3Few training heuristics and small architectural changes that can significantly improve YOLOv3 performance with tiny increase in inference cost. - SKRohit/Improving-YOLOv3GitHubSKRohit

参考

  1. 用于训练目标检测神经网络的一包赠品
  2. 学习空间融合进行单镜头物体检测

Paperspace + Insight 数据科学

原文:https://blog.paperspace.com/insight-data-science/

在 Paperspace,我们致力于让专业人士和学者更容易获得云中的机器学习。

一键“ML-in-a-Box”的想法是我们对 ML 社区对更强大的 GPU 和新功能的需求激增的回应。众所周知,传统的云提供商如今主要关注开发运营,并且在许多情况下需要网络和基础设施方面的复杂专业知识。

事实上,很多 ML 研究仍然是在笔记本电脑和台式机上完成的。相比之下,我们希望解决更广泛的需求,包括数据科学家、研究人员,甚至学生,他们可能拥有某个领域的专业知识,但他们自己无法将复杂的云基础架构串联起来,帮助他们将工作流迁移到云。

随着全新的受众将他们的工作流迁移到云,这一新产品的目标是找到计算抽象的最佳点:允许用户只需几次点击就可以将处理能力卸载到云,同时仍然保持深入机器以调试代码的能力。对学术界和专业人士来说都是双赢。

为了突出这些可能性,我们与 Insight Data Science 及其人工智能研究员合作。

Insight Data Science 为他们的学生提供了为期 7 周的强化课程,弥合了学术研究或专业软件工程与人工智能职业生涯之间的差距。他们中的许多人最终在一些最知名的机构工作,比如谷歌和脸书。

赋予研究员启动和运行生产就绪的机器学习环境的能力,使他们能够在强化课程中构建更复杂的人工智能产品。该计划的结果反映了“盒子里的 ML”解决方案的潜力。

“试图进入应用人工智能领域的人们苦于缺乏负担得起的高端 GPU,因此行业专业人士无法获得快节奏的迭代。Insight 的人工智能研究员是一群有天赋的软件工程师和研究人员,他们接受领先行业专家的指导。这与 Paperspace 的尖端 GPU 相结合,使研究人员能够在几分钟内建立并运行模型,并在几天内完成最*发布的研究原型。”
——Jeremy Karnowski,AI 领衔洞察数据科学

在接下来的几个月里,我们将通过各种社交渠道展示人工智能研究员们的惊人作品。请随时联系 Insight Data Science,了解他们的计划和同事的工作。

要开始使用您自己的 ML-in-a-box,在此注册。

可解释机器学习入门

原文:https://blog.paperspace.com/interpretable-machine-learning/

机器学习模型长期以来一直因经常是黑箱或“无法解释”而臭名昭著。甚至使用这些模型的人也不知道模型的内部权重和决策。此外,如果你对利益相关者负责,拥有一个黑箱模型不再是一个选择。

这是一个关于可解释性的经典迷因。

Image Source

是不是只有业内人士,才是罪魁祸首?不完全是,人工智能领域几乎所有的研究都集中在更好的架构、超越基准、新颖的学习技术,或者在某些情况下只是建立具有 10 亿个参数的巨大模型。可解释的机器学习的研究相对来说还未被触及。人工智能在媒体中越来越受欢迎(由“点击诱饵”媒体标题引起)和复杂,只会使可解释性的情况恶化。

以下是可解释 ML 如此重要的其他原因。

  • 人类的好奇心和求知欲——人类这个物种天生好奇。当某些事情与人类先前的信念相矛盾时,人类会特别寻找解释。互联网上的人可能会好奇为什么某些产品和电影会被推荐。为了解决这种与生俱来的愿望,公司已经开始解释他们的建议。

Image Source

  • 建立信任——当你向潜在买家推销你的 ML 产品时,他们为什么要信任你的模型?他们如何知道模型在所有情况下都会产生好的结果?在我们的日常生活中,需要可解释性来增加 ML 模型的社会接受度。类似地,与他们的个人家庭助理交互的消费者会想要知道某个动作背后的原因。解释有助于我们理解和理解机器。
  • ****调试和检测偏差——当你试图推理一个意想不到的结果或者在你的模型中发现一个 bug 时,可解释性变得非常有用。最*,人工智能模型也因偏向于某些种族和性别而受到关注,在模型部署到现实世界之前,可解释的模型可以检测和纠正这一点。

可解释性和性能并不密切相关

在金融和医疗保健等高风险领域,数据科学家通常会使用更传统的机器学习模型(线性或基于树的模型)。这是因为,模型解释其决策的能力对业务非常重要。例如,如果你的模型拒绝了一个人的贷款申请,你不能因为不知道是什么因素促成了模型的决定而逃脱。尽管简单的最大似然模型的表现不如神经网络等更复杂的模型,但它们在本质上是可解释的,也更透明。决策树可以很容易地被可视化,以理解在哪个级别使用哪个特征来进行决策。它们还带有一个要素重要性属性,该属性告知哪些要素在模型中贡献最大。

然而,使用如此简单的模型总是以性能为代价并不是一个真正的解决方案。我们需要像集成和神经网络这样的复杂模型,它们可以更准确地捕捉数据中的非线性关系。这就是模型不可知解释方法的用武之地。

在这篇博客中,我们将使用一个糖尿病数据集来探索其中的一些解释技术,并在其上训练一个简单的分类算法。我们的目标是根据一个人的特征来预测他是否患有糖尿病,并且我们将尝试推理模型的预测。

所以,让我们开始吧!

import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split 

加载数据集以查看要素。

df = pd.read_csv("diabetes.csv")
df.head() 

我简单解释一下特性:

  • 怀孕——患者过去怀孕的次数
  • -血糖浓度(mg/dL)
  • -舒张压(毫米汞柱)
  • -三头肌皮褶厚度(mm)
  • 胰岛素 - 2 小时血清胰岛素(μU/ml)
  • BMI -身体质量指数(体重单位为 kg/(身高在 m)^2)
  • 糖尿病谱系功能——它决定了一个性状具有显性还是隐性的遗传模式。当患者在家族中有糖尿病病史时,它被计算出来。
  • ——年龄以年计。
  • ****结果——一个人是否患有糖尿病(0 =否,1 =是)

让我们将数据集分为训练和测试,并拟合一个模型。

*`target=df['Outcome']
df=df.drop(labels=['Outcome'],axis=1)
# train-test split
X_train, X_test, y_train, y_test = train_test_split(df, target, test_size=0.2, random_state=42)
# fit the model 
rfc=RandomForestClassifier(random_state=1234)
rfc.fit(X_train,y_train)
# evaluate the results
rfc.score(X_test,y_test)`* 

*现在,我们有了一个基本的模型,是时候探索解释技术了。第一个是特征重要性,这是一种特定于决策树及其变体的技术。

特征重要性

要素重要性或置换要素重要性通过置换要素并观察模型的误差来测量。直觉是,如果改变一个特征改变了模型的误差,这意味着模型依赖于该特征进行预测,那么该特征是重要的。反之亦然。

import seaborn as sns
features =["Pregnancies","Glucose","BP","SkinThickness","Insulin","BMI","DPFunc","Age"]
all_feat_imp_df = pd.DataFrame(data=[tree.feature_importances_ for tree in 
                                     rfc],
                               columns=features)
(sns.boxplot(data=all_feat_imp_df)
        .set(title='Feature Importance Distributions',
             ylabel='Importance')); 

根据特征的重要性,血液中的葡萄糖水*以及身体质量指数和年龄是将患者分类为糖尿病患者的最重要的特征。这个结果似乎是合理的。血液中葡萄糖水*高基本就是糖尿病,肥胖者更容易得。这项研究显示,由于随着年龄的增长,胰岛素抵抗增加和胰岛功能受损的综合影响,老年人处于患二型糖尿病的高风险中。

到目前为止,我们可以说我们的模型在数据分类方面做得很好。它已经学会了正确的权重,是可以信任的。

takealways:

  • 特征重要性提供了对模型行为的高度压缩的全局洞察。
  • 置换特征的重要性来源于模型的误差。在某些情况下,您可能想知道某个特性的模型输出变化有多大,而不考虑它对性能的影响。
  • 具有相关特征可以通过在两个特征之间分割重要性来降低相关特征的重要性。

示例 ML:决策树

使用决策树的一大优势是它们非常直观。接下来,我们将绘制树本身,以理解树中每个节点所做的决策。

from IPython.display import Image  
from sklearn.tree import export_graphviz
import graphviz
import pydotplus
from io import StringIO  

# Get all trees of depth 3 in the random forest
depths3 = [tree for tree in rfc.estimators_ if tree.tree_.max_depth==3]
# grab the first one
tree = depths3[0]
# plot the tree
dot_data = StringIO()
export_graphviz(tree, out_file=dot_data, feature_names=features, 
                filled=True, rounded=True, special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())  
Image(graph.create_png()) 

基于写在节点顶部的特征,每个非叶节点被分割。在树的左边部分,我们将样本分类为非糖尿病,在右边部分分类为糖尿病。最左边叶节点的熵函数变为 0,因为数据变得同质(所有样本不是糖尿病就是非糖尿病)。值数组中的第一个值表示有多少样本被归类为非糖尿病样本,第二个值表示有多少样本为糖尿病样本。对于最左边的叶节点,熵是 0,因为所有 54 个样本都是非糖尿病的。

树可能是最本质上可解释的模型。还有其他这样的模型,如广义线性模型,朴素贝叶斯,K-最*邻,但使用这些方法的问题是,它们对于不同的模型是不同的。因此,解释各不相同。用于解释逻辑回归模型的方法与用于解释 KNN 的方法不同。因此,博客的其余部分将致力于独立于应用它们的模型的方法。

模型不可知的方法

我们将通过可视化特征交互来开始模型不可知方法。因为在现实世界中,特性很少是相互独立的,所以理解它们之间的交互很重要。

特征交互

当预测模型中的功能之间存在交互时,预测不能表示为功能效果的总和,因为一个功能的效果取决于另一个功能的值。两个特征之间的相互作用被计算为在考虑了单个特征的影响之后通过改变特征而发生的预测变化。

这个情节是基于弗里德曼和波佩斯库提出的 H 统计量 。在不涉及技术细节的情况下,H 统计量将特征之间的相互作用定义为由相互作用解释的方差的份额。

R 中有很多实现这个的包。不幸的是,对于 python 用户来说,只有 sklearn-gbmi 包(据我所知)可以计算梯度增强模型的 H 统计量。

from sklearn.ensemble import GradientBoostingClassifier
from sklearn_gbmi import *
# fit the model
gbc = GradientBoostingClassifier(random_state = 2589)
gbc.fit(X_train,y_train)
# d is a dictionary of feature pairs and their respective interaction strength
d=h_all_pairs(gbc,X_train)
l=sorted(d.items(), key=lambda x: x[1])
l=l[-10:] # let's just take the top 10 interactions
data=pd.DataFrame(l)
data.columns=['Feature',"Interaction"]
data.index=data['Feature']
data=data.drop(labels=['Feature'],axis=1)
data.plot(kind='barh', color='teal', title="Feature Interaction Strength") 

怀孕次数和年龄之间以及血压和胰岛素之间有很强的相互作用。所有这些互动都是双向的。

take all:-

  • 该统计检测所有类型的交互,不管它们的特定形式。
  • 由于统计量是无量纲的,并且总是在 0 和 1 之间,所以它可以跨要素甚至跨模型进行比较(尽管对于 Python 用户来说还不行)
  • H 统计量告诉我们相互作用的强度,但是它没有告诉我们相互作用看起来如何。下一类解释方法正是为此。

部分相关图

部分相关性图(短 PDP 或 PD 图)显示了一个或两个特征对机器学习模型预测结果的边际效应。它可以显示目标和特征之间的关系的性质,该关系可以是线性的、单调的或更复杂的。

部分相关图是一种全局和局部的方法。该方法考虑了所有实例,并给出了关于特征与预测结果(通过黄线)的全局关系以及所有唯一实例(数据帧中的行)与结果(通过蓝线)的关系的陈述。

from pdpbox import pdp, info_plots
pdp_ = pdp.pdp_isolate(
    model=estimator, dataset=X_train, model_features=X_train.columns, feature='Glucose'
)
fig, axes = pdp.pdp_plot(
    pdp_isolate_out=pdp_, feature_name='Glucose', center=True, 
     plot_lines=True, frac_to_plot=100) 

y 轴可以解释为预测值相对于基线或最左侧值的变化。蓝线代表所有实例,黄线代表*均边际效应。不同的影响可以通过蓝线看出。

血糖升高会增加患糖尿病的几率。非糖尿病人的正常空腹血糖在70 ~ 100mg/dL之间,这也是图表所证明的。

pdp_ = pdp.pdp_isolate(
    model=estimator, dataset=X_train, model_features=X_train.columns, feature='Age'
)
fig, axes = pdp.pdp_plot(
    pdp_isolate_out=pdp_, feature_name='Age', center=True, x_quantile=True, 
     plot_lines=True, frac_to_plot=100) 

根据我们的模型,23 岁以后的人更容易患糖尿病。

PDP 易于实施且直观。由于 PDP 图显示的是边际效应,根据定义,边际效应假设其他协变量不变,因此它忽略了现实世界中的特征通常是相关的这一事实。因此,对于房价回归问题,两个特征是房子的面积和房间的数量。为了计算房间数量对价格的边际影响,它将保持房子的面积不变,比如说 30 *方米 2 *方米,这对于一个有 10 个房间的房子来说是不太可能的。

累积局部效应(ALE)图通过查看所有特征的条件分布(而非边缘分布)并考虑预测差异(而非*均值)来解决上述问题。

局部可解释模型不可知解释(LIME)

LIME 使用代理模型进行解释。代理模型被训练为使用稀疏线性模型(称为代理)来*似底层黑盒模型的预测。这些代理模型只是*似模型的局部行为,而不是全局行为。

让我们通过一个例子来看看 LIME 执行的步骤。

Image Source

原始模型的决策函数由蓝色/粉红色背景表示,这显然是非线性的。亮红色的十字是正在解释的实例(我们称它为 X)。我们对 X 周围的扰动实例进行采样,并根据它们与 X 的接*程度对它们进行加权(图中的权重由大小表示)。原始模型对这些扰动实例的预测用于学习线性模型(虚线),该线性模型很好地逼* x 附*的模型。因此,该解释在局部而非全局工作良好。

import lime
import lime.lime_tabular
classes=['non-diabetic','diabetic']
explainer = lime.lime_tabular.LimeTabularExplainer(X_train.astype(int).values,  
mode='classification',training_labels=y_train,feature_names=features,class_names=classes)
#Let's take a look for the patient in 100th row
i = 100
exp = explainer.explain_instance(X_train.loc[i,features].astype(int).values, estimator.predict_proba, num_features=5)
# visualize the explanation
exp.show_in_notebook(show_table=True)
``

橙色特征支持糖尿病类,蓝色特征支持非糖尿病类。

解释有三个部分:—

  1. 左上部分给出了类别 0 和类别 1 的预测概率。
  2. 中间部分给出了 5 个最重要的特征。橙色要素属于糖尿病类,蓝色要素属于非糖尿病类。
  3. 右侧部分遵循与 1 和 2 相同的颜色编码。它包含前 5 个变量的实际值。

这可以读作: 女方患糖尿病的概率为 0.67。她的葡萄糖水*、身体质量指数、年龄和糖尿病患者的功能都表明患有糖尿病,我们已经在 PDP 图中看到了这是如何发生的。然而,她只有一次怀孕,而不会导致糖尿病,但与确定糖尿病的其他更关键的特征相比,这具有较小的权重。

如果这个可视化激发了你对石灰的兴趣,这里有文档

take all:-

  • 向外行人解释时非常有用的人性化解释。
  • LIME 和我们讨论过的其他方法一样,也有忽略相关性的限制。数据点是从高斯分布中采样的,假设特征不相关。这可能导致不太可能的数据点,然后这些数据点可以用于学习局部解释模型。
  • 这些解释也可能是不稳定的。如果重复采样过程,那么得出的解释可能会不同。

SHAP

SHAP (SHapley 附加解释)是一种流行的解释方法,可用于全局和局部解释。它利用博弈论来衡量特征对预测的影响。为了解释预测,我们可以从假设实例的每个特征值是游戏中的 【玩家】 开始,其中预测是。然后,shapley 值将告诉您如何在特性之间公*地分配“支出”。

更准确地说,“游戏”是数据集的单个实例的预测任务。“增益”是该实例的实际预测减去输入到模型中的所有实例的*均预测。“玩家”是协作接收增益或预测某个值的实例的特征值。

让我们从这本中举个例子,更好地理解这一点。

*

Image Source*

回到我们之前预测公寓价格的例子。假设某套公寓的价格预测为 300,000 美元,我们的工作就是解释这一预测。这一预测的一些特征包括:

** 这套公寓的面积为 50 *方米

  • 它位于二楼
  • 它附*有一个公园
  • 禁止养猫。

现在,所有公寓的*均预测是 31 万美元。我们想知道,与*均预测相比,每个特征值对预测的贡献有多大?

答案可能是:公园附*贡献了 30,000 美元,大小- 50 贡献了 10,000 美元,二楼贡献了 0 美元,禁止猫贡献了-50,000 美元。贡献总计为-10,000 美元,最终预测减去准确预测的*均公寓价格。

Shapley 值计算为所有可能联盟中某个特征值的*均边际贡献。联盟只不过是不同的模拟环境,通过改变一个特性,同时保持其他一切不变,并注意效果。例如,如果“禁止养猫”变成了“允许养猫”,而所有其他特征都相同,我们检查预测是如何变化的。

让我们试着解释一下对病人的分类。

import shap
# create our SHAP explainer
shap_explainer = shap.TreeExplainer(estimator)
# calculate the shapley values for our data
shap_values = shap_explainer.shap_values(X_train.iloc[7])
# load JS to use the plotting function
shap.initjs()
shap.force_plot(shap_explainer.expected_value[1], shap_values[1], X_train.iloc[7]) 

导致预测增加的要素显示为粉红色,导致预测减少的要素显示为蓝色,它们的值代表影响的大小。基值是 0.3498,我们预测是 0.7。该患者被归类为糖尿病患者,将结果推向糖尿病的特征是葡萄糖水*=161、年龄=47、胰岛素=132 和 10 次怀孕。身体质量指数的功能,这是低,试图否定的影响,但不能,因为组合的影响,粉红色的功能远远超过了它。

如果从粉色条的长度中减去蓝色条的长度,则等于从基准值到输出的距离。

让我们也来看一下 Summary plo t,以了解该型号的整体情况。

shap_values = shap_explainer.shap_values(X_train)
shap.summary_plot(shap_values[1], X_train,auto_size_plot=False) 

好吧,我们来试着解读一下这个!这幅图是由许多点组成的。他们每个人都有三个特点:

  • 垂直位置显示了它所描绘的特征。
  • 颜色显示该特征对于数据集的该行是高还是低。
  • 水*位置显示该值对预测有负面影响还是正面影响。

葡萄糖行中最右边的点是粉红色的,这意味着葡萄糖水*高,这增加了患糖尿病的机会,就像我们以前看到的一样。其他特征如身体质量指数、年龄和怀孕也是如此,但对葡萄糖的影响更明显。

外卖:

  • 与其他方法相比,解释速度相当快,而且这种技术在博弈论中有坚实的基础。
  • 预测和*均预测之间的差异是 在实例的特征值中公*分布 而不像石灰。
  • KernelSHAP(我们之前讨论过的)忽略了特征依赖,因为从边缘分布中采样通常更容易。然而,如果特征是相关的,这将导致对不太可能的数据点赋予过多的权重。SHAP 的另一个变体 TreeSHAP 通过显式建模条件预期预测来解决这个问题。

结论

机器学习应用越来越多地被行业采用,在未来几十年里,它只会变得更加无处不在。为了确保这些系统不会在现实世界中灾难性地失败,就像 Zillow 的崩溃一样,我们需要更多地关注可解释性,而不是复杂和花哨的架构。

这个博客旨在让你一瞥可解释性的世界。如果你想了解更多,我强烈推荐这本由 Christoph Molnar 写的书:可解释的 ML Book 。**

解读计算机视觉模型

原文:https://blog.paperspace.com/interpreting-computer-vision-models/

计算机视觉作为一个领域可以追溯到 20 世纪 60 年代末,当时它起源于模仿人类视觉系统的雄心勃勃的目标。然而,在经历了漫长的人工智能冬天之后,这个领域最*在 2012 年成为了焦点,当时 AlexNet 赢得了第一届 ImageNet 挑战赛。AlexNet 在 GPU 上使用深度 CNN 对 ImageNet 中的数百万张图像以及数千个标签进行分类,前 5 名的错误率为 15.3%。

你们中的一些人可能想知道:CNN 是什么?

我简单给你解释一下。

卷积神经网络或 CNN 中神经元之间的连接模式受到动物视觉皮层组织的启发。皮层中的单个神经元只在视野中被称为感受野的有限区域内对刺激做出反应。类似地,CNN 有内核(一个具有共享权重的 n x n 矩阵),它沿着输入图像滑动以捕捉空间信息,而这是多层感知器所做不到的。

Source

对于高分辨率图像,拥有完全连接的节点网络在计算上是不可行的。此外,神经元的完全连接对于固有地包含空间局部输入模式的图像是浪费的。我们可以从图案中推断出更精细的细节,并节省成本。

CNN 的兴起和可解释性案例

CNN 在减小网络规模的同时,还增加了参考局部性,这对于图像数据非常重要。因此,CNN 被广泛用于计算机视觉任务。

  • 例如,在自动驾驶汽车中,它们被用于从检测和分类道路上的物体到从人行道分割道路的一切事情。
  • 在医疗保健领域,它们被用于 x 光、核磁共振成像和 CT 扫描来检测疾病。
  • 它们还用于制造和生产线,以检测有缺陷的产品。

这些只是众多用例中的少数几个,但它们正确地传达了这些系统与我们的日常生活是如何交织在一起的。但是,尽管它们很常见,但如果没有深度学习和人工智能领域的知识,对它们的解释可能会很困难。因此,能够解释这些“黑箱”的预测变得极其重要。在这篇博客中,我们将探索解读这些网络预测的技术。

我将把口译技巧大致分为两类:

基于遮挡或扰动的技术

这些方法处理部分图像以生成解释。这到底是怎么回事?我将用两个库来解释它。

  1. 石灰图像讲解器:

本地可解释模型不可知解释(或 LIME)是一个支持几乎所有数据形式的本地解释的库。这些解释是模型不可知的(独立于所使用的模型),并且试图只解释个别的预测而不是整个模型(局部的)。石灰处理图像的方式可以分解为以下步骤:

  • 获得图像的随机扰动:在时间上,图像的变化是通过将图像分割成“超像素”并关闭或打开它们而产生的。这些超像素相互连接,可以通过将每个像素替换为灰色来关闭。对于每个图像,我们有一组扰动图像。
  • 预测扰动图像的类别:对于每个扰动图像,使用我们试图解释的原始模型来预测类别。
  • 计算扰动的重要性:使用余弦相似度等距离度量来评估每个扰动与原始图像的不同程度。原始图像也是所有超像素开启时的扰动。
  • 训练一个可解释的代理模型:代理模型是一个线性模型,就像逻辑回归一样,本质上是可解释的。该模型接受原始模型的输入和输出,并试图逼*原始模型。初始权重是我们在上一步中计算的权重。模型拟合后,与图像中每个超像素相关的权重或系数告诉我们该超像素对预测类别有多重要。

我们现在将使用预训练的 Inception-V3 模型来对猫和老鼠图像进行预测。

from keras.applications import inception_v3 as inc_net
from keras.preprocessing import image
from keras.applications.imagenet_utils import decode_predictions
from skimage.io import imread
import matplotlib.pyplot as plt
import lime
from lime import lime_image

def load_img(path):
    img = image.load_img(path, target_size=(224, 224))
    img = image.img_to_array(img)
    img = np.expand_dims(img, axis=0)
    return img

def predict_class(img):
    img = inc_net.preprocess_input(img)
    img = np.vstack([img])
    return inet_model.predict(img)

inet_model = inc_net.InceptionV3()

img = load_img('cat-and-mouse.jpg')
preds = predict_class(img)
plt.figure(figsize=(3,3))
plt.imshow(img[0] / 2 + 0.5)
for x in decode_predictions(preds)[0]:
    print(x) 

Top 5 predicted labels for the image

接下来,我们将使用石灰来解释这些预测。

# instantiate lime image explainer and get the explaination 
explainer = lime_image.LimeImageExplainer()
explanation = explainer.explain_instance(img[0], inet_model.predict, top_labels=5, hide_color=0, num_samples=1000)

# plot interpretation
temp, mask = explanation.get_image_and_mask(282, positive_only=False, num_features=100, hide_rest=False)
plt.figure(figsize=(3,3))
plt.imshow(skimage.segmentation.mark_boundaries(temp / 2 + 0.5, mask)) 

绿色超像素对预测标签即虎猫有积极的贡献。而红色超像素负贡献。

即使有些红色超像素躺在猫身上。该模型在学习图像中的基本概念方面做得很好,颜色反映了这一点。

Lime 对图像的实现与其对表格数据的实现非常相似。但是我们改变超像素,而不是干扰单个像素。由于我们之前讨论过的位置引用,一个类中有多个像素。

  1. SHAP 分区解说:

另一个流行的解释库是 SHAP。它使用来自博弈论的概念来产生解释。考虑一个由一群人合作进行的游戏。每个玩家都对游戏有所贡献,有些玩家可能比其他人贡献更多,有些玩家可能贡献更少。将有一个最终的分配,每个玩家的贡献总和将决定游戏的结果。

我们想知道每个参与者对整体合作有多重要,以及他们能期望什么样的回报。为了更具体地说明手头的任务,每个超像素对图像整体预测类别的贡献有多重要。

Shapley 值提供了一个可能的答案。

让我们试着用 SHAP 来解读同一个猫和老鼠的形象。这里我们将使用一个 Resnet50 来代替一个初始模型。

from tensorflow.keras.applications.resnet50 import ResNet50, preprocess_input
import shap
import json 

# load pretrained model
model = ResNet50(weights='imagenet')
def predict(x):
    tmp = x.copy()
    preprocess_input(tmp)
    return model(tmp)

# get imagenet class names
url = "https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json"
with open(shap.datasets.cache(url)) as file:
    class_names = [v[1] for v in json.load(file).values()]

# define a masker that is used to mask out partitions of the input image.
masker = shap.maskers.Image("inpaint_telea", img.shape[1:])

# create an explainer with model and image masker
explainer = shap.Explainer(f, masker, output_names=class_names,algorithm='partition')

# here we explain the same image and use 1000 evaluations of Resnet50 to get the shap values 
shap_values = explainer(img, max_evals=1000, batch_size=50, outputs=shap.Explanation.argsort.flip[:4])

shap.image_plot(shap_values) 

第一个被预测的种类是埃及猫,接下来是狐松鼠、虎斑猫和猞猁。猫面部的超像素对预测有积极的贡献。该模型在鼻子或胡须区域放置了很大的重量。

在 SHAP 中使用分区解释器的一个优点是,它不会做出一个潜在的假设,即这里的特征(或超像素)独立于其他特征而存在。这是一个很大的(错误的)假设,是由很多解释模型做出的。

我们现在来看看下一类的解释技巧。

基于梯度的技术

这些方法计算预测相对于输入图像的梯度。简而言之,他们发现像素的变化是否会改变预测。如果您要更改像素的颜色值,预测的类概率将会上升(正梯度)或下降(负梯度)。梯度的大小告诉我们扰动的重要性。

我们将看看一些使用梯度进行解释的技术:

1.Grad-CAM

梯度加权类激活图或 Grad-CAM 使用流入最终卷积层的类的梯度来产生热图,该热图突出显示了用于预测类的图像中的重要区域。该热图随后被放大并叠加在输入图像上以获得可视化效果。获取热图的步骤如下:

  1. 我们通过网络前向传播图像以获得预测,以及全连接层之前的最后一个卷积层的激活。
  2. 然后,我们计算顶部预测类相对于最后一个卷积层的激活的梯度。
  3. 然后,我们通过类别的梯度对每个特征图像素进行加权。这给了我们全局汇集梯度。
  4. 我们计算特征图的*均值,该*均值通过梯度对每个像素进行加权。这是通过将在最后一张地图中获得的特征地图中的每个通道乘以最后一层的输出来实现的。这告诉我们通道对于输出类有多重要。然后,我们对所有通道求和,以获得该类的热图激活。
  5. 然后,我们将像素归一化到 0 和 1 之间,这样就很容易可视化了。
import tensorflow as tf
import keras
import matplotlib.cm as cm
from IPython.display import Image

def make_gradcam_heatmap(img_array, model, LAST_CONV_LAYER_NAME, pred_index=None):
    # Step 1
    grad_model = tf.keras.models.Model(
        [model.inputs], [model.get_layer(LAST_CONV_LAYER_NAME).output, model.output]
    )

    with tf.GradientTape() as tape:
        last_conv_layer_output, preds = grad_model(img_array)
        if pred_index is None:
            pred_index = tf.argmax(preds[0])
        class_channel = preds[:, pred_index]

    # Step 2
    grads = tape.gradient(class_channel, last_conv_layer_output)

    # Step 3
    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))

    # Step 4
    last_conv_layer_output = last_conv_layer_output[0]
    heatmap = last_conv_layer_output @ pooled_grads[..., tf.newaxis]
    heatmap = tf.squeeze(heatmap)

    # Step 5
    heatmap = tf.maximum(heatmap, 0) / tf.math.reduce_max(heatmap)
    return heatmap.numpy() 

我们使用与上面例子中相同的 ResNet50 模型。我们还有一个助手功能,可以将热图叠加在原始图像上并显示出来。

def save_and_display_gradcam(img_path, heatmap, alpha=0.4):
    # Load the original image
    img = keras.preprocessing.image.load_img(img_path)
    img = keras.preprocessing.image.img_to_array(img)

    # Rescale heatmap to a range 0-255
    heatmap = np.uint8(255 * heatmap)

    # Use jet colormap to colorize heatmap
    jet = cm.get_cmap("jet")

    # Use RGB values of the colormap
    jet_colors = jet(np.arange(256))[:, :3]
    jet_heatmap = jet_colors[heatmap]

    # Create an image with RGB colorized heatmap
    jet_heatmap = keras.preprocessing.image.array_to_img(jet_heatmap)
    jet_heatmap = jet_heatmap.resize((img.shape[1], img.shape[0]))
    jet_heatmap = keras.preprocessing.image.img_to_array(jet_heatmap)

    # Superimpose the heatmap on original image
    superimposed_img = jet_heatmap * alpha + img
    superimposed_img = keras.preprocessing.image.array_to_img(superimposed_img)
    # Display the image
    plt.figure(figsize=(8,4))
    plt.axis("off")
    plt.imshow(superimposed_img) 

厌倦了猫和老鼠的形象?这次我们将使用新的图像。

让我们看看这个图像的顶级类。

# Prepare image
img_array = preprocess_input(load_img('cars.jpg',224))
# Print the top 2 predicted classes
preds = model.predict(img_array)
print("Predicted:", decode_predictions(preds, top=2)[0]) 

该模型将汽车分类为跑车,并将站在它们之间的人分类为赛车手。让我们想象一下最后一个卷积层的激活。

heatmap = make_gradcam_heatmap(img_array, model, 'conv5_block3_out', pred_index=class_names.index("sports_car"))
save_and_display_gradcam('cars.jpg', heatmap) 

似乎第conv5_block3_out层的神经元被汽车的前部激活了。我们可以确信模型学习了这个类的正确特征。

现在,让我们为“racer”类设想一下。

这一次,汽车仍然使用汽车像素来将该图像分类为“赛车”,而不是赋予代表男人的像素重要性。这不符合我们的预期,也许模型使用图像中的全局上下文来预测类。例如,一个不站在跑车旁边的人会被称为赛车手吗?

那要由你自己去发现;)

2.制导摄像机

由于 Grad CAM 使用最后一个卷积层来生成热图,因此定位非常粗略。由于最后一个图层的分辨率比输入图像的分辨率要粗糙得多,因此在将热点图叠加到图像上之前,会对其进行升级。

我们使用引导反向传播来获得高分辨率的定位。我们针对输入图像的像素来计算损失梯度,而不是针对最后一个卷积层的激活来计算损失梯度。然而,这样做可能会产生噪声图像,因此我们在反向传播中使用 ReLu(换句话说,我们剪切所有小于 0 的值)。

@tf.custom_gradient
def guided_relu(x):
    # guided version of relu which allows only postive gradients in backpropogation
    def grad(dy):
        return tf.cast(dy > 0, "float32") * tf.cast(x > 0, "float32") * dy

    return tf.nn.relu(x), grad

class GuidedBackprop:
    def __init__(self, model):
        self.model = model
        self.gb_model = self.build_guided_model()

    def build_guided_model(self):
        # build a guided version of the model by replacing ReLU with guided ReLU in all layers
        gb_model = tf.keras.Model(
            self.model.inputs, self.model.output
        )
        layers = [
            layer for layer in gb_model.layers[1:] if hasattr(layer, "activation")
        ]
        for layer in layers:
            if layer.activation == tf.keras.activations.relu:
                layer.activation = guided_relu
        return gb_model

    def guided_backprop(self, image: np.ndarray, class_index: int):
        # convert to one hot representation to match our softmax activation in the model definition
        expected_output = tf.one_hot([class_index] * image.shape[0], NUM_CLASSES)
        # define the loss
        with tf.GradientTape() as tape:
            inputs = tf.cast(image, tf.float32)
            tape.watch(inputs)
            outputs = self.gb_model(inputs)
            loss = tf.keras.losses.categorical_crossentropy(
                expected_output, outputs
            )
        # get the gradient of the loss with respect to the input image
        grads = tape.gradient(loss, inputs)[0]
        return grads 

让我们看看上面看到的跑车示例的显著性图。我们将使用相同的 ResNet50 型号。

gb = GuidedBackprop(model)
NUM_CLASSES = 1000
saliency_map = gb.guided_backprop(img_array, class_index=class_names.index("sports_car")).numpy()

# Normalize with mean 0 and std 1
saliency_map -= saliency_map.mean()
saliency_map /= saliency_map.std() + tf.keras.backend.epsilon()
# Change mean to 0.5 and std to 0.25
saliency_map *= 0.25
saliency_map += 0.5
# Clip values between 0 and 1
saliency_map = np.clip(saliency_map, 0, 1)
# Change values between 0 and 255
saliency_map *= (2 ** 8) - 1
saliency_map = saliency_map.astype(np.uint8)

plt.axis('off')
plt.imshow(saliency_map) 

即使我们通过引导反向传播创建的显著图具有更高的分辨率,它也不是类别区分的,即定位不能区分类别。那是我们用摄像机的地方。Grad-CAM 热图使用双线性插值进行上采样,然后两个图按元素相乘。

gb = GuidedBackprop(model)

# Guided grad_cam is just guided backpropogation with feature importance coming from grad-cam
saliency_map = gb.guided_backprop(img_array, class_index=class_names.index("sports_car")).numpy()
gradcam = cv2.resize(heatmap, (224, 224))
gradcam =
np.clip(gradcam, 0, np.max(gradcam)) / np.max(gradcam)
guided_gradcam = saliency_map * np.repeat(gradcam[..., np.newaxis], 3, axis=2)

# Normalize
guided_gradcam -= guided_gradcam.mean()
guided_gradcam /= guided_gradcam.std() + tf.keras.backend.epsilon()
guided_gradcam *= 0.25
guided_gradcam += 0.5
guided_gradcam = np.clip(guided_gradcam, 0, 1)
guided_gradcam *= (2 ** 8) - 1
guided_gradcam = guided_gradcam.astype(np.uint8)

plt.axis('off')
plt.imshow(guided_gradcam) 

Grad-CAM 就像一个镜头,聚焦于通过引导反向传播获得的像素属性图的特定部分。这里,对于“跑车”类,主要是与汽车相关的像素被突出显示。

gb = GuidedBackprop(model)

# Guided grad_cam is just guided backpropogation with feature importance coming from grad-cam
saliency_map = gb.guided_backprop(img_array, class_index=class_names.index("racer")).numpy()
gradcam = cv2.resize(heatmap, (224, 224))
gradcam = np.clip(gradcam, 0, np.max(gradcam)) / np.max(gradcam)
guided_gradcam = saliency_map * np.repeat(gradcam[..., np.newaxis], 3, axis=2)

# Normalize
guided_gradcam -= guided_gradcam.mean()
guided_gradcam /= guided_gradcam.std() + tf.keras.backend.epsilon()
guided_gradcam *= 0.25
guided_gradcam += 0.5
guided_gradcam = np.clip(guided_gradcam, 0, 1)
guided_gradcam *= (2 ** 8) - 1
guided_gradcam = guided_gradcam.astype(np.uint8)

plt.axis('off')
plt.imshow(guided_gradcam) 

正如你所看到的,制导 Grad CAM 在定位“赛车”类的像素方面比 Grad CAM 做得更好。

3.预期梯度

接下来是预期梯度。它们结合了三个不同的概念:其中一个你已经在 SHAP 价值观中听说过。除了 SHAP 值,他们还使用综合梯度。积分梯度值与 SHAP 值略有不同,需要一个参考值进行积分。为了使它们接* SHAP 值,在预期梯度中,我们将积分重新表述为期望值,并将该期望值与从背景数据集中采样的参考值相结合。这导致单一组合的梯度期望,其向属性值收敛,该属性值相加给出期望的模型输出和当前输出之间的差异。

它使用的最后一个概念是局部*滑。在期望值计算过程中,它会将正态分布噪声与标准偏差(指定为参数)相加。这有助于创建更*滑的特征属性,从而更好地捕捉图像的相关区域。

这次我们将使用 VGG16,因为我们已经可视化了来自输入图像和最后一个卷积层的梯度,我们将可视化来自中间层(第 7 层)的梯度。

from keras.applications.vgg16 import VGG16
from keras.applications.vgg16 import preprocess_input, decode_predictions
import tensorflow.compat.v1.keras.backend as K
import tensorflow as tf
tf.compat.v1.disable_eager_execution()

# load pre-trained model and choose two images to explain
model = VGG16(weights='imagenet', include_top=True)
X,y = shap.datasets.imagenet50()
to_explain = load_img('cars.jpg',224)

# load the ImageNet class names
url = "https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json"
fname = shap.datasets.cache(url)
with open(fname) as f:
    class_names = json.load(f)

# explain how the input to the 7th layer of the model explains the top two classes
def map2layer(x, layer):
    feed_dict = dict(zip([model.layers[0].input], [preprocess_input(x.copy())]))
    return K.get_session().run(model.layers[layer].input, feed_dict)

e = shap.GradientExplainer((model.layers[7].input, model.layers[-1].output), map2layer(preprocess_input(X.copy()), 7) ,local_smoothing=100)
shap_values,indexes = e.shap_values(map2layer(to_explain, 7), ranked_outputs=3, nsamples=100)

# get the names for the classes
index_names = np.vectorize(lambda x: class_names[str(x)][1])(indexes)

# plot the explanations
shap.image_plot(shap_values, to_explain, index_names) 

模型预测的第一个标签是跑车。模型的第 7 层关注用红色突出显示的像素。

结论

我们研究了各种可视化 CNN 的技术。这里需要注意的是,当模型提供的解释与我们的预期不符时,可能有两个原因。要么模型没有正确学习,要么模型学习了比人类能够感知的更复杂的关系。

渐变笔记本上尝试一下,在笔记本创建的“高级选项”中创建一个 TensorFlow 运行时笔记本,并将以下内容作为您的“工作区 URL”:【https://github.com/gradient-ai/interpretable-ml-keras】T2

参考

构建 MLOps 社区:德米特里奥斯·布林克曼访谈

原文:https://blog.paperspace.com/interview-demetrios-brinkmann-mlops-community/

一年多前,我们第一次被介绍给德梅特里奥斯·布林克曼,当时他告诉我们,他正在为机器学习领域的专业人士在网上组建一个新的社区,讨论关于 T2 的 m lops T3。

对于许多人来说,MLOps 仍然是一个新概念,但当我们第一次见到 Demetrios 时,这个术语根本不流行——也就是说,O'Reilly 还没有开始 出版关于 MLOps 的书籍,第一次 MLOps 会议还没有举行,人们的脑海中或搜索历史中几乎没有任何关于 MLOps 的意识。

这是一个已经迅速消失的现实。

MLOps 已经在 ML 世界中扩散开来,现在正受到主流企业的采用和关注。像德勤福布斯谷歌这样受人尊敬的玩家正在权衡这个新兴行业,一系列新的会议主题和招聘信息每天都在涌现,新的课件正在适应这个行业不断变化的需求。有价值的职位向系统架构师敞开大门,他们可以将一家公司的 ML 开发管道变成瑞士铁路网。

布林克曼创立的 MLOps.community ,如今可能是 MLOps 世界的中心。布林克曼和他的团队在 ML 领域策划了一个充满活力的专业人士社区,试图定义最佳的哲学、工具和资源,以将决定论带到 ML 开发中——许多对话都在 slack group 中公开进行,任何人都可以自由加入。

我们非常兴奋能和 Demetrios 谈论 MLOps 社区的第一年。我们不仅想知道他在观察什么趋势,他从哪些团体那里听到的最多,还想知道一个居住在西班牙北部的美国侨民是如何建立起一个 5000 人的会员社区的。

我们还想谈谈-还有什么?-米罗普斯!让我们开始吧。

纸空间。你是如何为社区保持这样一个行动议程的?

布林克曼。 我们从一个核心计划开始,随着社区和愿意参与其中的人的增多,我们的计划也随之扩展。人们会过来对我说,“嘿,开一个 Q &频道怎么样?”或者“MLOps 频道的女人怎么样?”90%的新计划被证明是伟大的想法,我们已经能够投入时间和精力来寻找线索,将这些计划付诸实施。

一些项目像办公时间一样,在开始一年后才开始。我们一直希望在一周中有一个固定的时间,人们可以聚集在一起,非正式地谈论他们正在做的事情,但直到最*,我们才能够使它成为一种定期发生的事情。

纸空间。 你能告诉我们关于创建社区的事情吗?有没有一个时刻,你觉得自己“明白”了 MLOps 将会成为一件事,并认为其他人也会有类似的感觉?

布林克曼。MLOps 社区一开始只有我在 Slack 里给其他三个人发东西。过了一段时间,我们又多了几个人,现在又多了很多人。但是我记得有一个反馈调查是这样说的:“这还不是一个社区,这只是德米特里奥斯策划的新闻”——这仍然让我发笑。

在早期,我们没有像今天这样的势头,人们提出了这么多伟大的问题,全行业的专家都参与进来,引发了这么多伟大的讨论和辩论。

所以我不确定这是一个突然的变化,但在某个时候,这从我策划的新闻聚合器变成了一个真正的社区。“MLOps 问题解答”频道帮了大忙。突然之间,社区的专业知识变得显而易见——很明显,我们有很多真正有才华的人想要分享他们所知道的。

当我开始从 LinkedIn 或社区 Slack 上收到人们的随机留言,说他们多么喜欢正在发生的事情时,这表明我们正在做一些事情。我也开始有大量的人想和我见面或者提出要求。这是一个信号,表明人们注意到了发生在我们这个互联网小角落的事情。

纸空间。 现在谈 MLOps 都要参考论文吗?你认为这份文件对 MLOps 行业来说是一份很好的基础文件吗?你认为它应该被你的社区所重视吗?

布林克曼。 我想说我们播客的一个亮点是我们能够采访论文的主要作者之一 D . Scully的时刻。他是一个信息丰富的人,并不经常“在媒体上露面”——所以他坐下来和我们交谈令人难以置信地感到谦卑。

至于这篇论文,由于过去几年每篇文章或会议讨论都提到了它,它已经有点像一个迷因了。我认为我们都理解并看到了 d .提出的观点的价值,现在我们正在寻找真正解决论文中提到的一些问题的方法。

纸空间。 太酷了。你还记得其他最喜欢的采访或互动吗?

布林克曼。 我从每个客人身上学到东西。我经常引用某些人的话,最后我写了一篇博文,讲述了我迄今为止的 9 次对话。短名单是与谷歌 ML 的 SRE 负责人托德·安德伍德、ML in Production 的创始人 Luigi Patruno 和德勤的战略数据经理伊丽莎白·查博特的对话。

纸空间。 你对加入社区的成员有什么看法?从公司规模、职位或职能等方面来看,你能想到哪些特质表明你对 MLOps 感兴趣?

布林克曼。 从希望学习更多工程技能的数据科学家,到希望完善自己技能的 MLE,再到过渡到 MLOps 的 DevOps 人员,我们都有。

很难一概而论,因为我们的规模越来越大,但有一点我想说的是,我们中的绝大多数人都是从业者——每天都深陷其中。

我认为这也是我们有这么多好的讨论的原因之一——因为我们都在努力解决我们在日常职业生活中遇到的一些问题。

纸空间。 你怎么知道社区往哪个方向走?你的成员有没有告诉你他们希望你把事情引向何方?

布林克曼。 我确保每六个月至少一次通过反馈表向人们汇报。这有助于我感受社区的脉搏和它想要去的地方。

我们有如此多的计划和可能性,以至于有时很难知道应该优先考虑哪些。最终,我们希望做对社区最有利的事情,因此我们非常重视这种持续的沟通。此外,因为我们是一个分布式社区,我们欢迎任何人带头项目,如果他们有这样做的动力。

纸空间。 最*,对 MLOps 感兴趣的人问你最多的问题是什么?你认为现在有足够的信息让人们开始参与进来吗?

布林克曼。 很少看到来自社区的重复问题(除非是关于 Jupyter 笔记本)——我认为这说明了社区的深度。每个人都在各自的公司努力应对自己的艰难挑战。有无限的方法来构建你的 ML 系统,正因为如此,我们从不缺少讨论点。人们通常希望了解 MLOps 生态系统中非常具体的问题和困难。这是它如此有趣的部分原因!

纸空间。 有没有什么即将到来的举措让你特别兴奋?

布林克曼。 有几个——工程实验室和 MLOps 堆栈项目是首先想到的。

工程实验室是每几个月启动一次的团队,为参与者提供了一个获得 MLOps 问题实践经验的机会。我们把每个人分成小组,给他们一个要解决的问题和一个截止日期,然后让他们放松!在小组结束时,我们要求每个人展示他们的作品,并为评审团认为最好的团队颁发不同的奖项!

MLOps Stacks 项目旨在简化不同开源 MLOps 工具栈的测试。该团队每周都会添加新的堆栈,并且所有这些都已融入您的环境。

纸空间。 多么了不起的社区和一套举措。我们喜欢看着社区成长,喜欢和这么多优秀的主题专家一起参与。你会如何推荐这个博客的读者参与进来,帮助 MLOps 发展成为一个社区和软件工程实践?

布林克曼。 第一件事就是通过 https://mlops.community/ 加入 Slack 群。一旦你加入,请联系我(@Demetrios),让我知道你是否有兴趣加入或领导任何计划。我们永远不会缺少事情去做,我们总是在寻找新的领导者!

如果你是 MLOps 社区的新手,请访问 https://mlops.community/ 并加入 Slack 群组。您还可以在 Twitter @mlopscommunityYouTube 上关注社区,查看所有最新的专家会议。

生成艺术和流动集体意识的科学

原文:https://blog.paperspace.com/interview-with-daniel-canogar-loom/

在与 Bitforms GallerySmall Data Industries 的合作中,Paperspace 采访了艺术家 Daniel Canogar ,他的作品 Loom 正在作为基于云的、24/7 的生成性软件艺术流的一部分呈现

丹尼尔出生在马德里,经常往返于美国和西班牙。他毕业于 NYU 大学,获得了摄影学位,并对作为媒介的投影图像产生了兴趣。在他的职业生涯中,他为美国自然历史博物馆欧盟创作了装置作品,他的个人作品也在时代广场帕克城的圣丹斯电影节展出。

我们非常兴奋有机会与丹尼尔谈论织机和他作为技术第一艺术家的经历。我们发现他对技术和社会的日常共享空间的洞察力是深刻的,值得深入研究。

Paperspace : 艺术和技术值得区分吗?在你心目中有什么不同?

卡诺加 : 我们忘记了艺术一直都有技术的成分。例子包括更精细的画笔和颜料的发展,用于画轮廓的暗箱的精细光学,以及透视系统的发展,这本身就是一项科学发明。我对艺术和技术的交叉如何成为当代现象并不感兴趣;相反,我着迷于了解这种交叉在过去如何发生的许多历史例子,也许意识到在许多方面,同样的老问题一次又一次地重新出现,但随着当前的技术而更新。

纸空间 : 你的第一个媒介是摄影。是什么让你涉足电影和公共装置?你的过程会随着每种媒体而改变吗?

Canogar : 我的确是从摄影师开始的,但我在技术上对媒介非常笨拙。我的底片总是布满灰尘(我是从数码摄影之前开始的),从来没有耐心去完善我的工艺。过了一段时间,我意识到摄影真正让我着迷的是暗室的体验:放大机投射的光和在感光纸上燃烧的图像,化学品的气味,红光,看到图像神奇地出现在一张白纸上的奇迹。这些都是我十几岁时形成的经验,至今仍存在于我现在的作品中。对我来说,开始思考投影图像、体验式装置艺术和使用技术作为实现这些创作的方式是很自然的。

Paperspace : 那么到底什么是生成艺术呢?

卡诺加 : 生成艺术基本上是算法艺术,一个算法被编码,建立一套处理数据的行为规则。一旦这些规则启动,艺术品就有了自己的生命,总是以新的和令人惊讶的方式重新组合信息。在许多方面,生成艺术更接*于行为艺术,而不是视频艺术:它有自己的生命,短暂,并且永远不会以完全相同的方式重复。一旦我开始研究生成艺术,我就再也不想回到录像时代。

Paperspace : 在创作生成艺术的时候,你是否已经对这件作品要表达什么有了一个想法,或者你是在分析了生成的东西之后才得出这个结论的?

卡诺加 : 创造生成性艺术作品的过程就是一场对话。我有一个初步的概念,我与我的程序员分享,还有草图,其他视觉参考,等等。他们开始工作,几天或几周后,他们给我看了艺术作品的初稿。这是与艺术品对话的开始。我仔细看了一下草稿,注意到什么可行,什么不可行,试着去听算法想告诉我什么。我的程序员发现了我与正在开发的作品之间的内部对话,并继续调整。我试着对这个过程保持开放,不强加我最初的概念和我想象中的艺术作品的样子,而是对这个过程中发生的许多令人惊讶的事情保持开放。不用说,在我们的创作过程中,我也会听取程序员的意见。经过无止境的调整,和一件不断变化的艺术品一起生活,我觉得我对这件作品有了深刻的了解,并准备好向世界发布它。

Paperspace : 未知结果的可能性会让你兴奋吗?

卡诺加 : 机缘巧合,同步性,意外的艺术。我也对失去对结果的控制感到兴奋。我已经看到了令人惊奇的事情发生在我从来没有计划过的生殖工作中。它挖掘出无限的组合可能性,让你超越可以计划、组织和控制的范围。例如,当我写这篇文章时,我刚刚看到一个词“抗议”在我的屏幕上滑动,我让织机运行,以一种令人不寒而栗的美丽精度捕捉我们现在生活的时刻。

https://player.vimeo.com/video/410150948?app_id=122963

Paperspace : 你如何选择一个装置的长度?对于织机,我想对这首曲子的理解会随着它是一天、一周、一个月还是一年而改变。你同意吗?

卡诺加 : 时间被织机等艺术品扭曲。真的没有过去,也没有未来。通过使用实时数据,它只存在于现在。与叙事相关的传统时间概念,即视频或电影的时长,并不适用。当考虑艺术品的寿命时,也许我们应该考虑作品何时消亡:当运行它的软件变得过时,再也找不到可以运行的计算机时。

Paperspace : 你认为在实体画廊中展示艺术和在数字微观世界中展示艺术有什么不同?在这个另类的空间里,你的观众对你有新的理解吗?

卡诺加 : 也许观众可能不会改变太多:那些在网上体验织机的人可能会去参观美术馆。我想他们通常对艺术感兴趣,会在线上和线下消费。从根本上改变的是在线观众的态度、情绪和倾向。他们使用屏幕进行多种活动,包括创建 Excel 表格、流式传输网飞,以及通过缩放连接到外部世界。这是一个拥挤繁忙的分享艺术品的*台。也许因为这个原因,我想创造一种宁静的、*乎沉思的艺术作品,来对抗银幕上的商业。

Paperspace : 你希望人们从这个装置中得到什么?

卡诺加 : 我真的希望人们能花些时间来看这部作品,不一定要不停地看,而是把它放在电脑屏幕上,偶尔浏览一下。通过与作品一起生活,你能够看到当前的事件在你面前展开,不是耸人听闻的突发新闻,而是一种诗意的体验。织机抓住了人们现在的想法。他们的在线搜索经过艺术提炼,以便更好地把握当下的集体意识。最重要的是,我希望人们会喜欢这件艺术品。

Paperspace : 你还有什么想分享的吗?

卡诺加 : 我要衷心感谢 Paperspace 让这件艺术品成为现实。没有你我们不可能成功!


一定要参观 Biforms Gallery 的首个虚拟艺术画廊,从 Canogar 的 Loom 开始,它将持续到 2020 年 6 月 9 日。在 Paperspace Core P6000 虚拟机的帮助下,Bitforms Gallery(与小数据合作)将在未来三个月内每周 7 天、每天 24 小时播放另外五幅作品。

将噪音转化为信号:利用人工智能获得科学研究的背景

原文:https://blog.paperspace.com/interview-with-josh-nicholson-scite-ai/

Josh Nicholson 是 scite ( www.scite.ai )的联合创始人兼首席执行官,该公司正在使用深度学习来分析科学文献的整体,以更好地衡量科学工作的准确性。

我们很高兴能和他坐下来,了解更多关于他非凡而雄心勃勃的机器学习项目。

Paperspace :你是如何产生 scite 的?

【尼克尔森:scite 的想法来自于这样一种观察,即癌症研究在独立测试时往往无法重现。这个问题在其他领域也存在,不仅仅是癌症研究。我们想找到一种方法来使科学研究更加可靠,机器学习使我们能够大规模地分析文献。

Paperspace :到目前为止,你对多少篇科学文章进行了分析?

尼克尔森: 到目前为止,我们已经分析了 16,158,032 篇文章中的 526,695,986 条引用语句,这个数字每天都在快速攀升。

:scite 如何帮助读者评价一篇科技文章?

尼克尔森 :通过 scite,我们正在努力引入智能引用。这些引用提供了每个引用的上下文及其含义。例如,我们想知道引用是否提供了支持或反驳的证据——而不仅仅是它之前被引用、浏览或下载的次数。这使得人们可以查看一项研究,并快速确定它是否得到了支持或反驳。

Paperspace :你从哪里获得数据来训练你的模型并创建引用图?

尼克尔森 : scite 通过与领先的学术出版商合作,利用开放获取的文章以及未开放的内容,如威利、IOP、洛克菲勒大学出版社、卡尔格、BMJ 等。

Paperspace :模型是如何工作的?

尼克尔森 : 在我们确定了引用及其上下文之后,我们的深度学习模型将引用语句分为三类:支持、反驳或提及。该模型已经在来自各种科学领域的数万个人工注释片段上进行了训练。

****

paper space:您的主要模型是否全天候运行?正在进行多少调优?****

【尼克尔森 :它尽可能有效地运行,因为我们不断吸收新文章,并分析从中提取的引用语句。我们正在努力使这个过程完全自动化,这样一旦我们收到一篇新文章,它就会被处理并添加到数据库中。这些大部分是由 CPU 以及我们自己拥有和运行的 GPU 在云中完成的。

:每天都有那么多关于新冠肺炎的新信息出现。scite 如何跟上海量的入站数据?****

【尼克尔森】 : 起初我们并没有真正关注新冠肺炎的研究,因为我不认为我们能提供什么。一篇新论文不会仅仅因为太新而被引用。然而,考虑到人们现在每天都在发布新的内容,某些出版物在几天内就会收到引用,而通常需要几个月或几年。例如,这篇预印本着眼于新冠肺炎与信号分子关系的严重性,仅在五天后得到了另一篇预印本的支持,我们能够用 scite 捕获该预印本。

:你最*通过机器学习模型运行了维基百科中的每一条引文。是什么让你想进行这项研究,你有什么发现?

【尼克尔森】 : 我们决定这样做是因为维基百科往往是许多试图更好地理解某些东西的人的第一站,也是唯一一站。我们发现大多数被引用的文章(58%)未经后续研究证实或测试,而其余的在矛盾或支持证据方面表现出很大的可变性(2- 40%)。这听起来很糟糕,但实际上与一般的科学文章没有太大的不同。事实上,维基百科上的科学文章比整个科学文献获得了更多的支持性引用。

我们发现真正有趣的是,文章引用的参考文献在文献中实际上并没有得到支持。例如,维基百科的文章自杀和互联网指出:“一项调查发现,与没有报告更严重自杀风险症状的在线用户相比,出于自杀相关目的上网的有自杀风险的个人更不愿意寻求帮助,也更少获得社会支持。”这句话引用了的一份科学报告,该报告与另一份的报告相矛盾,这只能通过查看 scite 才能看到。拥有这篇维基百科文章的额外信息可能会影响行为选择,这些行为选择可能会对大量人群产生潜在的生死后果。**

Paperspace :这个模型运行了多长时间?

尼科尔森 :我们花了大约一天的时间对大约 1500 万条引用语句进行分类,但这项研究对我们来说很容易完成,因为我们采用了已经完成的分类,然后只查看了维基百科中的文章。

Paperspace :你对 scite 的目标是什么?

我希望 scite 将有助于改变科学家的行为方式,奖励那些做出足够强的工作的人,以便其他人可以验证它,并奖励更多的公开辩论。科学从本质上影响着我们生活的方方面面,我们的使命是让科学更加可靠。

**如需了解更多关于 scite 的信息,请访问:【https://scite.ai/】

自动驾驶汽车理论导论

原文:https://blog.paperspace.com/intro-autonomous-vehicle-theory/

“任何足够先进的技术都和魔法没什么区别”~亚瑟·C·克拉克

我记得我被热门电视剧《硅谷》中的一个场景逗乐了,在这个场景中,角色 Jared 被困在一辆自动驾驶汽车中。他的目的地被意外覆盖,汽车试图带他到 Arallon。当他喊道:“汽车先生!车先生!”

自动驾驶汽车从未让我们感到惊讶。虽然我们中的一些人有机会乘坐无人驾驶汽车旅行,但实际上世界上只有一小部分人,大多数人可能不知道无人驾驶汽车是什么,尤其是无人驾驶汽车是如何工作的。本文旨在对此类汽车进行深入介绍。

无人驾驶汽车背后的动机

我们距离拥有真正的自动驾驶汽车还有很长的路要走。所谓真正的自动驾驶汽车,我们指的是一辆基本上可以像人类驾驶汽车一样以任何方式驾驶的汽车。这是一件非常难以实现的事情。

主要汽车公司一直试图实现真正的自动驾驶。这一想法背后的主要动机是:

  • 更安全的道路
  • 生产率增长
  • 更经济
  • 运动将会更有效率
  • 更加环保

致力于这一想法的公司包括但不限于——特斯拉、Pony.ai、Waymo、苹果、起亚现代、福特、奥迪和华为。

自动驾驶汽车中使用的基本术语

在我们进入自动驾驶汽车的工作之前,让我们熟悉一下其中使用的常用术语。

1)自治程度

0 级(无驾驶自动化):车辆由人类驾驶员手动控制,驾驶员监控周围环境并执行驾驶任务,即加速/减速和转向。集成支持系统,即盲点警告、停车和车道保持辅助系统,属于这一类,因为它们只提供警报,不以任何方式控制车辆。

1 级(驾驶员辅助):自动系统辅助转向或加速,但驾驶员会监控道路和车辆参数。自适应巡航控制和自动制动属于这一类

2 级(部分自动化):高级驾驶辅助系统(ADAS)可以控制转向和加速。司机依然执行监控任务,随时待命接管车辆。特斯拉 Autopilot 就属于这一类。

第 3 级(有条件的自动化):ADAS 被编程为具有环境检测功能,使用来自传感器的输入数据来控制车辆。这种汽车是自动驾驶而不是自主驾驶,可以在特定条件下自动驾驶,但在必要时仍需要人为干预。交通堵塞飞行员就属于这一类。

第 4 级(高度自动化):这个级别的自动驾驶车辆具有额外的能力,允许它在 ADAS 出现故障的情况下做出决策。人类乘客必须仍然存在。目前,它们通过地理围栏被限制在特定区域。它旨在用于无人驾驶出租车和公共交通,其中车辆行驶固定路线。

第 5 级(全自动化):ADAS 可以在不同的驾驶模式下导航和处理不同种类的驾驶条件,无需人工干预。这些车辆可以去任何地方,甚至没有人类乘客。他们预计不会有任何方向盘或踏板。

2)激光雷达

根据维基百科:

激光雷达是一种通过用激光瞄准物体并测量反射光返回接收器的时间来确定距离(可变距离)的方法。

在自动驾驶汽车中,激光雷达是汽车的眼睛。它基本上是一个 360 度旋转摄像机,可以检测到任何种类的障碍。

3)自适应

无人驾驶汽车的自适应行为意味着它可以根据周围环境设置参数。例如,如果汽车周围的交通流量很大,它会自动减慢速度。

4)自动驾驶仪

自动驾驶仪是一种无需任何手动控制就能自动控制车辆行驶的系统。它不仅用于自动驾驶汽车,还用于飞机和潜艇。

自动驾驶汽车的工作

自动驾驶汽车由三个必不可少的部分组成:车辆、系统硬件和驾驶软件。硬件和软件的完美结合实现了安全驾驶。一系列连续的步骤将前一步骤的输出作为下一步骤的输入。工作流程可以分为五个不同的阶段:

  1. 感知:要采取的主要行动是从周围环境中收集数据,这是通过不同种类的传感器“看”和“听”来完成的。三个主要的传感器,相机,雷达和激光雷达,作为人类的眼睛和大脑一起工作。然后,车辆处理原始信息,从中获得意义。计算机视觉是通过卷积神经网络来实现的,以从相机馈送中识别对象。

Image Source

2.识别:软件然后处理这些输入,绘制路径,并向车辆的“执行器”发送指令,这些执行器控制加速、制动和转向。硬编码规则、避障算法、预测建模和“智能”物体识别()。知道自行车和摩托车的区别)帮助软件遵守交通规则和穿越障碍。这些系统通过各种接收器(如相机、雷达、激光雷达和导航地图)收集有关车辆、驾驶员和周围环境的数据,然后分析情况,并在驾驶方面做出适当的决策。这表明车辆需要连接到道路上的其他车辆,以及与道路和条件相关的不同类型信息的整体基础设施。车辆及其系统的相互连接程度,以及与其他车辆和多个信息提供者的连接程度变得非常重要。需要分析这些信息,由此产生的步骤将有助于自动驾驶汽车的决策方面。

Image Source

硬件组件

硬件组件分为三个主要角色,以完成诸如使用传感器进行观察、使用 V2V 技术进行通信以及通过致动器进行移动等任务。

  1. 传感器融合:传感器可以被认为类似于人的眼睛,使车辆能够吸收周围环境的信息。为了创建高质量的重叠数据模式,不同的传感器收集不同种类的数据,但一起工作以形成一致的观察系统。对于 360 度画面,来自所有传感器的输入以 1 GB/s 的速度合并。使用的包括:相机,激光雷达,雷达。

Image Source

  1. V2X tech :这些部件类似于人的嘴巴和耳朵。V2X 代表车辆到一切或车辆到 X,涉及从车辆到与其操作相关的任何对象的信息流,反之亦然。它们旨在允许自动驾驶汽车与另一辆汽车或其他连接系统(如交通灯)进行“对话”。根据目的地,它们可以分为:
  • V2I(车辆到基础设施):汽车和安装在道路旁边的设备之间的数据交换,用于向司机传递交通状况和紧急信息。
  • V2N(车联网):基于云服务的网络接入
  • V2V(车对车):车辆之间的数据交换。
  • V2P(车辆对行人):汽车和行人之间的数据交换。

Image Source

3.致动器:这个部分类似于人类肌肉对大脑发出的神经冲动做出反应。致动器由处理器控制执行物理活动,例如制动和转向。

自动驾驶汽车中使用的机器学习算法

软件组件

对于所有意图和目的,软件可以被认为是自动驾驶汽车的大脑。简而言之,在现实生活中训练的算法从传感器获取输入数据,并从中获取意义,以做出必要的驾驶决策。绘制出最合适的路径,并将相关指令传送给执行器。

ADAS 中包含的算法必须引导车辆通过自动驾驶的 4 个主要阶段:

  • 感知:通过对象检测和分类以及邻域中的必要参数来分析障碍物
  • 定位:定义车辆相对于周围区域的位置
  • 规划:考虑感知和定位阶段的数据,规划从当前位置到目的地的最佳路径
  • 控制:以适当的转向角和加速度值跟踪轨迹。

Image Source

卷积神经网络(CNN)

CNN 的是第一选择,当谈到特征提取,由于其高精度。其特点在于卷积层,它通过使用滤波器矩阵或内核执行卷积来减少数据。内核通常是 3×3 或 5×5 像素,在输入图像上滑动,并且在矩阵与其所包围的图像的像素值之间获得数学点积。该结果值被分配到特征图中,该特征图表示关于诸如边或角的特征的数据。更深的层将捕捉更复杂和全面的特征,例如物体的形状。CNN 的输出被提供给激活函数,以在数据之间引入非线性关系。ReLU(整流线性单元)是最常用的一种,因为它收敛很快。max-pooling 层用于数据简化,携带有关图像背景和纹理的信息。总的来说,该模型被训练以在不过度拟合的情况下获得最高的精度,然后被应用到真实世界的情况中进行对象检测和分类。

Image Source

自动驾驶汽车中使用的特定 CNN 有

  • 特斯拉的 HydraNet
  • 谷歌 Waymo 的司机网
  • 英伟达自动驾驶汽车

尺度不变特征变换

这种算法通过图像匹配和识别来解决部分可见物体的问题,通过提取所讨论的物体的区别特征来实现。例如,考虑一个停止标志的八个角。这些特征不会随着旋转、缩放或噪声干扰而改变,因此它们被认为是比例不变的特征。系统记录并存储对象和特征之间的关系。拍摄图像,将提取的特征与数据库中的 SIFT 特征进行比较。相比之下,车辆因此可以识别物体。

模式识别的数据简化算法

通过传感器融合接收的图像包含不必要的和重叠的数据,这些数据必须被过滤掉。为了确定特定对象类的出现,使用重复模式来帮助识别。这些算法通过将线段拟合到拐角,将圆弧拟合到类似弧形的元素,来帮助减少噪声和不重要的数据。这些线段和圆弧最终组合在一起,形成特定对象类特有的可识别特征。

自动驾驶汽车使用的具体算法有

  • 主成分分析(PCA):降低数据的维度。
  • 支持向量机(SVM):优秀的非概率二元线性分类。
  • 梯度方向直方图(HOG):非常适合人体检测
  • 你只看一次(YOLO):HOG 的一个替代方案,它根据整个图像的上下文来预测每个图像部分。

聚类算法

由于低分辨率或模糊的图像、间歇或稀疏的数据,分类算法可能很难检测到对象,并且可能会完全错过它们。聚类查找数据中存在的内在结构,以便根据最大数量的共同特征对它们进行分类。

自动驾驶汽车中使用的特定聚类算法有:

  • K-means: k 个质心用于定义不同的聚类。数据点被分配到最*的质心。随后,质心移动到聚类中点的*均值。
  • 多类神经网络:涉及使用数据中的固有结构将数据组织成具有最大共享特征的分组。

回归学习算法

与奇异博士不同,这些算法试图预测未来的情景。环境中的重复特征被用于算法的优势,以建立给定图像和特定对象在其中的位置之间的关系的统计模型。计算最少两个变量之间的关系,然后使用回归分析在不同尺度上进行比较。它的依赖关系是回归线的形状、因变量的类型和自变量的数量。最初,模型是离线学习的。当模型处于活动状态时,它对图像进行采样以进行快速检测,从而输出对象的位置及其对该位置的确定性。该模型可进一步应用于其他实体,无需额外建模。

自动驾驶汽车中使用的具体算法有

  • 随机森林回归
  • 贝叶斯回归
  • 神经网络回归

Image Source

决策算法

顾名思义,确定正确的选择是这些算法的全部内容。多个模型被分别训练和组合以给出具有最小误差的包含性预测。该决策考虑了算法在对象的识别、分类和运动预测中的置信度。

自适应增强(Adaboost) 是最流行的算法框架之一。它本质上是将一系列在加权数据上训练的弱学习器(低精度)组合成一个集成,以便所涉及的算法相互补充并提高最终性能,从而获得准确的分类器。

Image Source

让我们使用上面的图表来理解它是如何工作的。在原始未加权数据集中,正数据点用红色叉号表示,负数据点用蓝色减号表示。第一个模型能够正确地分类三个否定点和四个肯定点,但是也错误地分类一个肯定点和两个否定点。这些错误分类的点被赋予较高的权重,并作为输入来建立第二个模型。第二个模型中的决策边界已经转移,以正确地分类先前的错误,但是在该过程中,错误地分类了三个不同的点。权重被更新以给予新的误分类点更高的值,并且该迭代继续,直到满足指定的条件。我们可以看到,所有模型都有一些错误分类的数据点,因此,它们是弱学习器。通过对模型进行加权*均,这些模型覆盖了其他模型的弱点,并且所有的点都被正确分类,这表明最终的集成模型是强学习器。

TextonBoost 工作原理相同。它使用 textons:具有相同特征的图像数据簇,因此对滤波器的响应相同。它将外观、形状和上下文结合在一起,将图像视为一个整体,并收集上下文信息以理解其关系。例如,船像素总是被水像素包围。

Image Source

自动驾驶汽车面临的挑战

自动驾驶汽车自诞生以来已经走过了漫长的道路,但由于涉及的高风险,必须完美地解决技术和安全挑战。在我们能够在道路上看到这些车辆之前,还有一些障碍需要克服。前面的一些障碍是:

  1. 无法理解复杂的社会互动:人类驾驶并不完美,驾驶员通常依靠来自周围环境的社交能力来导航,如眼神交流和手势。与骑自行车的人和行人的互动主要需要对机器人仍然缺乏的社交线索和微妙之处做出反应。到目前为止,自动化系统需要能够理解环境,以及为什么他们遇到的人会有这样的行为,以便能够对每个怪癖做出反应。
  2. 不适用于所有天气条件:在恶劣的天气条件下,自动驾驶汽车将难以解释低能见度情况和失去牵引力。传感器被雪、冰覆盖或被大雨遮挡会降低有效性。此外,厚厚的积雪掩盖了标志和车道标志。传感器上的微型雨刷的解决方案工作良好。
  3. 自动驾驶的地图绘制是复杂的:在任何自动驾驶汽车能够驾驶之前,它必须需要一个预定义地图的组合,这些地图带有分类的障碍物,用作导航路线的参考。随着时间的推移,区域和道路特征会发生变化,需要不断更新。构建和维护这些 3D 地图是一个耗时的过程,并且需要大量资源。完全自动驾驶的汽车也仅限于完全地图化的区域。
  4. 缺乏基础设施:必须为这些车辆提供合适的驾驶环境,但这并不总是可行的,尤其是在发展中国家。即使相距 10 公里,道路基础设施也可能不同。向智能基础设施(包括物联网系统、交通灯连接和 V2V)的过渡非常缓慢。
  5. 网络安全问题:联网汽车中的数字安全是最关键的问题之一。车辆连接得越紧密,就越容易找到攻击系统的方法。人们害怕对汽车软件或部件的网络攻击。
  6. 事故责任:事故发生时的责任问题仍在争论中。要么是乘客,要么是制造商。错误可能是致命的,并耗费人的生命。

自动驾驶汽车的未来

目前,市场上的自动驾驶汽车主要属于 1 级和 2 级,包括特斯拉。经过严格的测试和研究,3 级原型车正慢慢向公众开放,例如本田有限发布的豪华轿车 Legend。自动驾驶汽车的发展已经取得了惊人的进步;然而,研究人员推测,完全自动驾驶汽车还需要几十年才能实现。即使在生产之后,最初,它们也最有可能在工业中被限制使用,以在固定路线上完成自动化任务。

在自动驾驶汽车日益增长的前景中,另一个需要考虑的角度是它们是否会被用于个人用途或用作服务车辆,如出租车、货运或公共交通。通用汽车等传统公司打算在未来向公众零售这些车辆,而 Waymo、AutoX 和特斯拉等新公司打算运营或已经运营 robotaxis。大众汽车(Volkswagen)等其他公司计划以订阅方式租赁这些车辆,客户按小时付费。谷歌的 Waymo 从 2020 年底开始在美国选定的城市中以每周 1000-2000 次的速度运营 4 级 robotaxis。在一个类似的展览中,AutoX 在上海部署了一支由 75 台机器人组成的车队,向公众开放。在大规模采用之前,这些机器人轴的功能中的许多缺陷和问题仍需要纠正,因为机器人在面对复杂情况时仍需努力。

这起事故涉及一辆特斯拉汽车,造成两人死亡,凸显了围绕自动驾驶汽车的安全不确定性。部署的 2 级车辆中的碰撞被推断为人为错误的结果,但汽车制造商有责任建立考虑到人为疏忽和疏忽因素的自主系统。建造 5 级自动驾驶汽车的技术可能比适当的安全和法律法规更容易获得。公众接受是自动驾驶汽车整合的一个重要因素。人类天生对陌生技术带来的变化抱有偏见。至关重要的是,制定公共安全法规时要考虑赢得公众的信任。

用 R 对数据分类的简单介绍

原文:https://blog.paperspace.com/intro-to-datascience/

垃圾邮件还是火腿?

我们生成的大部分数据都是非结构化的。这包括算法可能无法立即理解的文本、音频、视频和图像等来源。然而,想象一下,如果一个人必须单独地将每一封电子邮件分类到你的收件箱或垃圾邮件文件夹中!幸运的是,数据科学最令人兴奋的领域之一,尤其是机器学习,是处理这些数据源并找到对这些数据进行有意义分析的方法的任务。

在本文中,我们将构建一个垃圾邮件过滤器,我们可以用它来将文本消息分类为垃圾邮件,即合法邮件和垃圾邮件。这个例子将介绍文本作为数据源,以及帮助说明一些常见的机器学习概念。虽然本教程是为初学者设计的,但是对 R 统计语言不太熟悉但感兴趣的更有经验的用户可能会发现这个实现很有帮助。请随意编码!

R 中文本数据的一些基础知识

要构建垃圾邮件过滤器,我们首先需要将纯文本消息转换成我们的模型可以理解的格式。具体来说,我们需要用字数或频率来表示我们的文本信息。一旦以这种格式表示,就可以使用各种统计模型和程序来理解这些数据。这被称为自然语言处理。

我们可以通过首先将文本文件加载到统计软件包中来做到这一点。本教程将使用强大且广泛使用的统计语言 R,并通过 R Studio 来实现。有关在您的机器上安装和设置 R 的更多信息,请查看此链接。你需要的软件和软件包都是开源的。

让我们加载两个包: quantedaRColorBrewer 。前者是一个流行的文本数据统计分析包,后者为我们将要生成的图形提供了一套颜色选项。library()命令指示 R 加载这些已经安装的包。如果您不熟悉 R(或这些包),那么在从您的库中访问它们之前,用install.packages("")替换这个命令来安装这些包。

library(quanteda) 
library(RColorBrewer) 

加载我们的包后,让我们从这里导入一个公开可用的垃圾短信/垃圾邮件数据集,这是在 Almeida 等人中介绍的。

我们从自述文件中了解到,该数据集表示为一个纯文本文件,其中列出了带有相关联的垃圾邮件标签的 SMS 消息内容。短信 5574 条,其中垃圾短信占 13.4%,合法短信占 86.6%。我们的挑战是构建一个监督机器学习分类器,它可以完全独立地分离这两种类型。

首先从上面的 URL 下载数据,然后将您的 R 工作目录设置为您下载文件的文件夹。这告诉 R 在你的计算机上哪里寻找数据。最后,使用read.table命令将数据读入 R,并用引号指定文件名。括号中的其他参数告诉 R 如何读取纯文本文件。我们将使用<-将这个 SMS 消息集合命名为“raw.data”。

setwd("~/Your Folder Path")
raw.data <- read.table("SMSSpamCollection", header=FALSE, sep="\t", quote="", stringsAsFactors=FALSE) 

如果您在尝试导入数据时遇到任何错误消息,请查看 StackExchangeStackOverflow 以获得有用的建议。实际上,数据导入可能是比较繁琐的任务之一!

让我们给这两列信息添加标题。我们将第一列称为“标签”,第二列称为“文本”。这将使我们在以后的分析中容易记住和调用这些变量。最后,我们可以检查垃圾邮件的数量,以再次检查我们的数据是否正确导入。

names(raw.data) <- c("Label", "Text")
table(raw.data$Label) 

看起来我们正在处理 4,827 条垃圾邮件和 747 条垃圾邮件。这与数据集的自述文件中的数据描述相符,所以我们应该可以开始了。

我们在数据导入阶段要做的最后一件事是使用sample()命令随机化我们的数据。以防数据不是以随机分布存储的,这将有助于确保我们处理的是从数据中随机抽取的数据。set.seed()命令只是为了确保更加一致的复制,这样您的结果应该反映本教程中的结果。

set.seed(1912)
raw.data <- raw.data[sample(nrow(raw.data)),] 

您可以使用View()命令查看数据的样子。您会注意到,我们已经将一个纯文本文件变成了一个存储良好的数据帧,其中包含带标签的标题和随机观察。

在我们开始构建分类模型之前,让我们用一种直观的可视化方式来感受一下我们的数据:单词 cloud。然后,我们可以从视觉上检查图像,以查看 ham 和垃圾邮件之间的语言是否有所不同。

在自然语言处理中,我们喜欢处理语料库对象。语料库可以被认为是我们的数据集的主副本,我们可以根据需要从中提取子集或观察值。我们将使用quantedacorpus()命令从原始数据的文本字段构建一个语料库,我们可以将其命名为sms.corpus。然后,使用docvars()命令将标签字段作为文档变量附加到语料库。我们将 Label 作为一个变量直接附加到我们的语料库,以便我们可以在稍后的分析中将 SMS 消息与其各自的 ham/spam 标签相关联。

sms.corpus <- corpus(raw.data$Text) 
docvars(sms.corpus) <- raw.data$Label 

随着我们的主语料库的建立,我们现在可以将其分为垃圾邮件和业余邮件,以构建单词云,并查看它们使用的语言的差异。单词显示得越大,该单词在我们的数据中出现的频率就越高。

首先,将语料库分成垃圾邮件子集。请记住,我们需要将这些单词转换成计算机可以计数的东西。因此,使用dfm()命令构建一个文档术语频率矩阵(DFM ),并删除一些杂乱的内容,比如标点符号、数字和经常出现的单词,它们可能没有什么有意义的信息。这就是所谓的词汇袋方法。

spam.plot <- corpus_subset(sms.corpus, docvar1 == "spam")
spam.plot <- dfm(spam.plot, tolower = TRUE, removePunct = TRUE, removeTwitter = TRUE, removeNumbers = TRUE, remove=stopwords("SMART")) 

构建了 DFM 之后,让我们绘制垃圾邮件词云。我们将使用RColorBrewer包中的brewer.pal命令根据词频给单词图着色。我们也可以给情节加个标题。

spam.col <- brewer.pal(10, "BrBG")
spam.cloud <- textplot_wordcloud(spam.plot, min.freq = 16, color = spam.col)
title("Spam Wordcloud", col.main = "grey14") 

注意像“免费”、“紧急”和“客户”这样的词。当想到垃圾邮件时,这些词可能会直观地出现在脑海中,因此我们可能会有所发现。我们现在可以对 ham 消息重复这个编码过程,并比较两个云。

ham.plot <- corpus_subset(sms.corpus, docvar1 == "ham")
ham.plot <- dfm(ham.plot, tolower = TRUE, removePunct = TRUE, removeTwitter = TRUE, removeNumbers = TRUE, remove=c("gt", "lt", stopwords("SMART")))
ham.col <- brewer.pal(10, "BrBG")
textplot_wordcloud(ham.plot, min.freq = 50, colors = ham.col, fixed.asp = TRUE)
title("Ham Wordcloud", col.main = "grey14") 

注意像“家”、“时间”和“爱”这样的词。这似乎比垃圾邮件中出现的词语更具个人色彩。我们的直觉——这些信息中使用的词语应该不同——似乎得到了验证。现在,让我们继续构建一个机器学习模型,该模型可用于自动将消息分类为垃圾邮件或垃圾邮件类别。

DIY:构建垃圾邮件过滤器

想象你正在阅读一部新电影的评论。你会注意到一些五星评论包括“优秀”或“极好”这样的词。也许一些一星评论包括“可怕”或“糟糕”这样的词。事实上,你会变得非常擅长阅读电影评论,以至于你可以根据评论者使用的单词类型准确预测与给定评论相关的星级。

这是我们任务背后的直觉。在机器学习中,这被称为“有监督的”学习问题。更具体地说,它也被称为“分类”任务。它是受监督的,因为您提供了模型监督:您对标记为垃圾邮件或 ham 的数据训练模型,以便它可以学习垃圾邮件或 ham 消息的样子。这是一个分类问题,因为该模型试图根据新文本消息的内容将新文本消息分类为垃圾邮件或垃圾邮件。

我们可以使用一个简单的朴素贝叶斯分类器来完成这项任务。关于贝叶斯分类器的更多信息,请看这篇 StackOverflow 帖子。简而言之,贝叶斯分类器是基于贝叶斯定理的简单概率分类器,具有强特征独立性假设。

我们已经将我们的文本消息作为语料库(数据的主副本)导入,所以下一步是以 DFM 的形式将文本消息表示为字数。通常,我们会想要丢弃标点符号、数字和常用词,因为这些通常没有什么预测能力。然而,在对短信进行分类的情况下,研究人员发现,包含这样的信息可以提高性能。因此,我们将只对这些数据进行最低限度的预处理,并对字数进行加权。

sms.dfm <- dfm(sms.corpus, tolower = TRUE)
sms.dfm <- dfm_trim(sms.dfm, min_count = 5, min_docfreq = 3)
sms.dfm <- dfm_weight(sms.dfm, type = "tfidf") 

现在,我们需要将数据分成训练样本和测试样本。训练样本训练我们的模型,即帮助它学习火腿和垃圾邮件的样子。然后,可以使用测试样本来测试模型,看它的表现如何。让我们使用 85%的数据来训练我们的模型,看看它能在多大程度上预测剩余 15%数据的垃圾邮件或火腿标签。

在 R 中,我们可以通过调用数据名来对数据进行子集化,并使用方括号对其进行修改。我们数据的前 85%大约对应于第 4738 次观察。剩余的 15%将是最后一行的第 4,739 号(即数据集的nrow())。我们将对原始数据和我们的 DFM 都这样做,这样我们就有了模型所需的一切。

sms.raw.train <- raw.data[1:4738,]
sms.raw.test <- raw.data[4739:nrow(raw.data),]

sms.dfm.train <- sms.dfm[1:4738,]
sms.dfm.test <- sms.dfm[4739:nrow(raw.data),] 

通过将我们的数据分为训练和测试数据集,我们将训练我们的朴素贝叶斯模型,并告诉模型注意文本消息的内容以及消息的垃圾邮件标签。然后,我们将使用经过训练的模型来预测新的测试观察结果本身是垃圾邮件还是有害邮件。最后,我们将制作一个列联表,看看它的执行有多准确。

quanteda包允许我们使用textmodel_NB命令直接访问朴素贝叶斯分类模型。我们插入我们的训练 DFM,我们将其命名为sms.dfm.train,并使用sms.raw.train$Label插入与训练文档相关联的标签向量。我们称之为我们的sms.classifier

sms.classifier <- textmodel_NB(sms.dfm.train, sms.raw.train$Label) 

这产生了一个合适的模型,即一个经过训练的分类器。现在让我们使用这个模型来预测我们的测试数据的垃圾邮件/火腿标签。这意味着我们只给这个模型提供没有标签的文本信息。我们可以通过predict()命令传递我们拟合的模型sms.classifier,指定我们的sms.dfm.test数据作为进行预测的新数据。最后,table命令允许我们查看模型预测的结果。

sms.predictions <- predict(sms.classifier, newdata = sms.dfm.test)
table(sms.predictions$nb.predicted, sms.raw.test$Label) 

应该从左到右阅读该表:该模型将 703 条 ham 消息正确地分类为 ham,将 8 条 ham 消息错误地分类为垃圾邮件。该模型错误地将 9 条垃圾邮件分类为 ham,但是将 116 条垃圾邮件正确地分类为 spam。

通过将正确的分类数除以尝试的分类总数,我们发现我们的模型正确地分类了 98.9%的垃圾邮件和 92.8%的垃圾邮件。因此,每收到 100 封邮件,就有一封合法邮件可能会意外地出现在您的垃圾邮件文件夹中。如果你收到 100 封垃圾邮件,其中 7 封可能会偷偷通过垃圾邮件过滤器,最终进入你的收件箱。对于一个简单的分类器来说,这是非常令人印象深刻的!有关评估该模型的更多信息,请查看这篇 StackExchange 帖子

结束语

在本教程中,我们简要讨论了如何将文本作为数据处理,并在 r 中完成了导入和可视化该数据的过程。我们还概述了机器学习中监督分类问题的逻辑,并通过构建一个简单的垃圾邮件过滤器来说明这些概念。

这个例子只是文本数据分析的众多应用之一。如果您对这个主题感兴趣,请阅读 R 的quanteda包,以及用于文本挖掘的 tm 包和用于文本机器学习分类的 RTextTools 包。对实现统计过程更感兴趣的读者可能会发现流行的 e1071 包很有帮助。关于数据集和相关研究的更多信息可以从这篇论文和这篇论文中找到。

希望看到这些机器学习概念的运行有助于使它们的直觉和应用更加具体。也许这也说明了这些模型的潜力——我们建立了一个相对准确的模型,能够在不到一秒钟的时间内对数百条短信进行排序。感谢阅读和快乐编码!

要开始设置自己的简历,请在此注册。

深度学习中的优化介绍:梯度下降

原文:https://blog.paperspace.com/intro-to-optimization-in-deep-learning-gradient-descent/

图片来源:奥赖利媒体

深度学习在很大程度上实际上是解决大量讨厌的优化问题。神经网络仅仅是一个非常复杂的函数,由数百万个参数组成,代表一个问题的数学解决方案。考虑图像分类的任务。AlexNet 是一个数学函数,它采用一个表示图像 RGB 值的数组,并以一组类分数的形式产生输出。

通过训练神经网络,我们基本上意味着我们正在最小化损失函数。这个损失函数的值为我们提供了在给定数据集上我们的网络性能离完美有多远的度量。

损失函数

为了简单起见,让我们假设我们的网络只有两个参数。实际上,这个数字大约是 10 亿,但是我们仍然会在整篇文章中坚持使用两个参数的例子,这样我们就不会在试图可视化事物的时候把自己逼疯。现在,一个非常好的损失函数的计数可能是这样的。

损失函数的轮廓

为什么我说一个很好听的损失函数?因为具有如上轮廓的损失函数就像圣诞老人一样,它不存在。然而,它仍然是一个不错的教学工具,可以让你全面了解梯度下降的一些最重要的观点。所以,让我们开始吧!

xy 轴代表两个权重的值。 z 轴代表两个权重的特定值的损失函数值。我们的目标是找到损失最小的特定重量值。这种点被称为损失函数的最小值

**你在开始时已经随机初始化了权重,所以你的神经网络很可能表现得像喝醉了的你,把猫的图像归类为人类。这种情况对应于轮廓上的点 A,在该点处网络性能差,因此损耗高。

我们需要找到一种方法,以某种方式导航到“谷底”的 B 点,在那里损失函数有一个最小值?那么我们该怎么做呢?

Gradient Descent

梯度下降

梯度下降

当我们初始化我们的权重时,我们在损失图中的 A 点。我们做的第一件事是在 x-y *面的所有可能的方向中,检查沿着哪个方向移动带来损失函数值的最大下降。这是我们必须前进的方向。该方向由与梯度方向完全相反的方向给出。梯度,导数的更高维度的表亲,给了我们上升最快的方向。

要理解这一点,请考虑下图。在曲线的任意一点,我们可以定义一个与该点相切的*面。在更高的维度中,我们总是可以定义一个超*面,但现在让我们坚持 3-D。那么,我们可以在这个*面上有无限个方向。在这些方向中,恰好有一个方向会给我们函数上升最快的方向。这个方向是由梯度给出的。与之相反的方向是最陡下降的方向。这就是算法如何得到它的名字。我们沿着梯度的方向下降,因此,它被称为梯度下降。

grad

现在,一旦我们有了我们想要前进的方向,我们必须决定我们必须采取的步骤的大小。这一步的大小称为学习速率。我们必须仔细选择它,以确保我们能达到最低限度。

如果我们走得太快,我们可能会超过最小值,并继续沿着“谷”的脊反弹,永远不会达到最小值。走得太慢,训练可能会变得太长,根本不可行。即使事实并非如此,非常慢的学习速度会使算法更容易陷入极小值,这一点我们将在本文后面讨论。

一旦我们有了梯度和学习率,我们走一步,在我们结束的任何位置重新计算梯度,并重复这个过程。

虽然梯度的方向告诉我们哪个方向的上升最陡,但它的大小告诉我们最陡的上升/下降有多陡。所以,在最小值,轮廓几乎是*的,你会期望梯度几乎为零。事实上,对于极小点来说,它正好是零。

动作中的梯度下降

使用过大的学习率

实际上,我们可能永远不会精确地达到最小值,但是我们在极小值附*的*坦区域保持振荡。当我们在这个区域振荡时,损耗几乎是我们可以达到的最小值,并且不会改变太多,因为我们只是在实际最小值附*不断反弹。通常,当损失值在预先确定的次数内没有改善时,比如说 10 次或 20 次迭代,我们就停止迭代。当这样的事情发生时,我们说我们的训练已经收敛,或者说已经发生了收敛。

常见的错误

让我离题一下。如果你在谷歌上搜索梯度下降的可视化效果,你可能会看到一条从一个点开始并向一个极小点前进的轨迹,就像上面展示的动画一样。然而,这给了你一个非常不准确的梯度下降图。我们取的轨迹完全局限于 x-y *面,这个*面包含了重量。

如上面的动画所示,梯度下降根本不涉及在 z 方向移动。这是因为只有权重是自由参数,由 xy 方向描述。我们采用的实际轨迹在 x-y *面中定义如下。

真实梯度下降轨迹

x-y *面上的每个点代表一个唯一的权重组合,我们希望有一组由最小值描述的权重。

基本方程

描述梯度下降更新规则的基本方程是。

grad_eq-4

这种更新在每次迭代期间执行。这里, w 是位于 x-y *面的权重向量。从这个向量中,我们减去损失函数相对于乘以α学习速率的权重的梯度。梯度是一个矢量,它给出了损失函数上升最快的方向。最陡下降的方向是与梯度完全相反的方向,这就是为什么我们要从权重向量中减去梯度向量。

如果想象向量对你来说有点困难,那么几乎相同的更新规则同时应用于网络的每个权重。唯一的变化是,因为我们现在对每个权重单独执行更新,所以上面等式中的梯度被替换为梯度向量沿特定权重表示的方向的投影。

indiveq-2

对所有权重同时进行这种更新。

在减法之前,我们用学习率乘以梯度向量。这代表了我们之前谈到的步骤。要意识到,即使我们保持学习速率不变,步长也会因梯度的大小而变化,而不是损失轮廓的陡度。当我们接*最小值时,梯度接*零,我们向最小值迈越来越小的步。

理论上,这是好的,因为我们希望算法在接*最小值时采取更小的步骤。步长太大可能导致它超过最小值并在最小值的脊之间反弹。

梯度下降中广泛使用的技术是具有可变的学习率,而不是固定的学习率。最初,我们可以负担较大的学习率。但是后来,当我们接*最小值时,我们想放慢速度。一种实现这种策略的方法被称为模拟退火,或衰减学习率。在这种情况下,学习率在每固定数量的迭代中衰减。

梯度下降的挑战#1:局部最小值

好吧,到目前为止,梯度下降的故事似乎是一个真正快乐的故事。好吧。让我来给你捣乱吧。还记得我说过我们的损失函数很好看吗,这样的损失函数并不真的存在?他们没有。

首先,神经网络是复杂的函数,在我们的假设函数中有许多非线性变换。最终的损失函数看起来不像一个漂亮的碗,只有一个我们可以收敛的最小值。事实上,这种漂亮的圣诞老人般的损失函数被称为函数(总是向上弯曲的函数),深网的损失函数几乎不是凸的。事实上,它们可能是这样的。

challenges-1

在上面的图像中,存在一个梯度为零的局部最小值。但是,我们知道它们并不是我们能达到的最低损失,这是全局极小值对应的点。现在,如果你在 A 点初始化你的权重,那么你将会收敛到局部极小值,一旦你收敛到局部极小值,梯度下降就不可能让你离开那里。

梯度下降是由梯度驱动的,梯度在任何最小值的底部为零。因为损失函数的值在局部区域中的该点是最小的,所以称为局部最小值。然而,全局最小值之所以被称为全局最小值,是因为损失函数的值在此处最小,在整个域上损失函数是全局的。

更糟糕的是,考虑到我们正在考虑的三维等高线实际上从未出现过,损失等高线甚至可能更加复杂。在实践中,我们的神经网络可能有大约 10 亿个权重,给我们一个大致(10 亿+ 1)维的函数。我甚至不知道那个数字中零的个数。

事实上,很难想象如此高维的函数。然而,鉴于目前深度学习领域的纯粹天赋,人们已经想出了在 3d 中可视化损失函数轮廓的方法。最*的一篇论文开创了一种称为过滤归一化的技术,解释了这超出了本文的范围。然而,它确实给了我们一个关于我们处理的损失函数的潜在复杂性的观点。例如,以下等值线是在 CIFAR-10 数据集上构建的 VGG-56 深度网络损失函数的损失等值线的三维表示。

一个复杂的失落景观 图片来源:https://www.cs.umd.edu/~tomg/projects/landscapes/

正如你所看到的,损失景观充满了当地的最小值。

梯度下降的挑战#2:鞍点

关于梯度下降的限制,我们得到的基本教训是,一旦它到达梯度为零的区域,不管最小值的质量如何,它几乎不可能逃脱。我们面临的另一个问题是鞍点,看起来像这样。

saddle

一个鞍点

你还可以在之前的图片中看到两座“山”交汇处的鞍点。

鞍点的名字来源于一匹马的马鞍,因为它很像它。虽然它在一个方向上是最小值( x ),但它在另一个方向上是局部最大值,如果轮廓在 x 方向上更*坦,GD 将在 y 方向上不断来回振荡,给我们一种已经收敛到最小值的错觉。

随机救援!

那么,我们如何摆脱局部最小值和鞍点,同时试图收敛到一个全局最小值。答案是随机性。

到目前为止,我们一直在使用损失函数进行梯度下降,该损失函数是通过对训练集的所有可能示例的损失求和而创建的。如果我们陷入局部极小值或鞍点,我们就被困住了。帮助 GD 避开这些的一个方法是使用所谓的随机梯度下降。

在随机梯度下降中,我们不是通过计算所有损失函数相加产生的损失函数的梯度来采取步骤,而是通过计算仅一个随机采样(无替换)实例的损失梯度来采取步骤。与随机选择每个样本的随机梯度下降相反,我们早期的方法在一个批次中处理所有样本,因此被称为批次梯度下降。

相应地修改更新规则。

sgd

更新随机梯度下降规则

这意味着,在每一步,我们都要对一个损失函数求梯度,这个损失函数不同于我们实际的损失函数(它是每个例子损失的总和)。这种“单一实例损失”在某一特定点上的梯度实际上可能指向与“全部实例损失”的梯度略有不同的方向。

这也意味着,虽然“所有示例损失”的梯度可能会将我们推下局部最小值,或让我们卡在鞍点,但“一个示例损失”的梯度可能会指向不同的方向,并可能帮助我们避开这些。

人们也可以考虑一个点,它是“所有示例损失”的局部最小值。如果我们做批量梯度下降,我们会在这里卡住,因为梯度总是指向局部最小值。然而,如果我们使用随机梯度下降,这个点可能不在“一个例子损失”的损失轮廓中的局部最小值附*,允许我们远离它。

即使我们陷入了“一个示例损失”的最小值,下一个随机采样数据点的“一个示例损失”的损失前景可能会不同,这允许我们继续前进。

当它收敛时,它收敛到一个点,该点是几乎所有“单例损失”的最小值。它也显示了鞍点是非常不稳定的,轻轻一推就足以逃脱。

那么,这是否意味着在实践中,应该总是执行这种单示例随机梯度下降?

批量

答案是否定的。虽然从理论的角度来看,随机梯度下降可能会给我们最好的结果,但从计算的角度来看,这不是一个非常可行的选择。当我们使用通过对所有单个损失求和创建的损失函数执行梯度下降时,单个损失的梯度可以并行计算,而在随机梯度下降的情况下,必须一步一步地顺序计算。

所以,我们所做的是一个*衡的行为。我们使用固定数量的样本,比如 16 个、32 个或 128 个,形成所谓的小批量,而不是使用整个数据集,或者只是单个样本来构建我们的损失函数。该词与一次处理所有示例形成对比,通常称为批量梯度下降。选择小批量的大小,以确保我们获得足够的随机性来避免局部最小值,同时利用并行处理的足够计算能力。

局部极小再探:它们没有你想象的那么糟糕

在你反对局部最小值之前,最*的研究表明局部最小值并不必然是坏的。在神经网络的损失图中,有太多的最小值,一个“好的”局部最小值可能表现得和全局最小值一样好。

为什么我说“好”?因为你仍然可能陷入“坏的”局部极小值,这些极小值是由不稳定的训练样本产生的。在给定神经网络的高维损失函数的情况下,“好的”局部最小值,或者在文献中经常被称为最优局部最小值,可以以相当大的数量存在。

也可以注意到,许多神经网络执行分类。如果局部最小值对应于为正确的标签产生 0.7-0.8 之间的分数,而对于相同的例子,全局最小值为正确的标签产生 0.95-0.98 之间的分数,则两者的输出类别预测将是相同的。

最小值的一个理想属性应该是它应该在较*坦的一侧。为什么?因为*坦的最小值很容易收敛,所以很少有机会超过最小值,并在最小值的脊之间跳动。

更重要的是,我们希望测试集的损失面与我们进行训练的训练集略有不同。对于*坦和宽的最小值,损耗不会由于这种移动而改变太多,但是对于窄的最小值就不是这样了。我们试图说明的一点是,更*坦的极小值概括得更好,因此也更可取。

重新审视学习率

最*,关于学习率调度的研究激增,以解决损失景观中的次优最小值。即使学习率下降,也可能陷入局部极小值。传统上,要么在固定的迭代次数内完成训练,要么在损失没有改善的情况下,在比如说 10 次迭代之后停止训练。这在文学上被称为提前停止

快速的学习速度也有助于我们在训练中更早地跳过局部最小值。

人们还将早期停止与学习速率衰减相结合,其中学习速率在每次损失在 10 次迭代后未能改善后衰减,最终在速率低于某个确定的阈值后停止。

*年来,循环学习率已经变得流行,其中学习率缓慢增加,然后降低,并且以循环方式继续。

Screen-Shot-2018-05-22-at-3.18.37-PM

莱斯利·n·史密斯提出的循环学习率的“三角形”和“三角形 2”方法。在左图中,最小和最大 lr 保持不变。右边的差异在每个周期后减半。图片来源:Hafidz Zulkifli

一种叫做热重启随机梯度下降的方法基本上将学习速率退火到一个下限,然后将学习速率恢复到初始值。

对于学习率如何下降,我们也有不同的时间表,从指数衰减到余弦衰减。

1_3kkV66xEObjWpYiGdBBivg

余弦退火结合重启

最*的一篇论文介绍了一种叫做随机加权*均的技术。作者开发了一种方法,其中他们首先收敛到最小值,缓存权重,然后将学习速率恢复到更高的值。这种更高的学习速率然后将算法从最小值推进到损失表面中的随机点。然后使算法再次收敛到另一个极小值。这样反复几次。最后,它们对所有缓存权重集做出的预测进行*均,以产生最终预测。

0_7KQ5Yesnt4QGNLHl

一种叫做随机加权*均的技术

结论

所以,这是关于梯度下降的介绍性帖子,自从关于反向传播的开创性论文表明你可以通过计算梯度来训练神经网络以来,梯度下降一直是深度学习优化的工作马。然而,还有一个关于梯度下降的缺失,我们在这篇文章中没有谈到,那就是解决病理弯曲的问题。传统随机梯度下降的扩展,如 Momentum、RMSProp 和 Adam 被用来克服这个重要问题。

然而,我认为我们所做的对于一个帖子来说已经足够了,其余的将在另一个帖子中涉及。

进一步阅读

1。神经网络的视觉损失景观(论文)

2。Hafidz Zulkifli 的一篇关于学习进度的精彩文章。

3。随机加权*均(纸)**

深度学习中的优化介绍:Momentum、RMSProp 和 Adam

原文:https://blog.paperspace.com/intro-to-optimization-momentum-rmsprop-adam/

在另一篇文章中,我们讨论了随机梯度下降的基本原理,以及如何解决陷入局部极小值或鞍点等问题。在这篇文章中,我们来看看另一个困扰神经网络训练的问题,病理弯曲

虽然局部最小值和鞍点会拖延我们的训练,但病态曲率会在某种程度上减慢训练,以至于机器学习从业者可能会认为搜索已经收敛到次优马敏。让我们深入了解一下什么是病理性弯曲。

病理弯曲

考虑下面的损失轮廓。

Pathological Curvature

*病理弯曲 *

你看,在进入用蓝色标记的峡谷状区域之前,我们随机开始。颜色实际上代表损失函数在特定点的值有多高,红色代表最高值,蓝色代表最低值。

我们想达到最低限度,但为此我们已经走过了峡谷。这个区域就是所谓的病理弯曲。为了理解为什么称之为病理性,让我们深入探究一下。这是病理弯曲放大后的样子..

patho2-1

*病理弯曲 *

掌握这里发生的事情并不困难。梯度下降是沿着峡谷的山脊反弹,并向最小值缓慢移动。这是因为脊部的表面在 w1 的方向上弯曲得更加陡峭。

考虑山脊表面上的点 A。我们看到,该点的梯度可以分解成两个分量,一个沿方向 w1 ,另一个沿 w2 。由于损失函数的曲率,梯度在 w1 方向上的分量要大得多,因此梯度的方向更多地朝向 w1 ,而不是朝向 w2 (最小值所在的方向)。

patho3

正常情况下,我们可以使用一个缓慢的学习速率来处理脊之间的反弹问题,就像我们在上一篇关于梯度下降的文章中提到的那样。然而,这意味着麻烦。

当我们接*最小值时,放慢速度是有意义的,我们希望收敛到最小值。但是考虑一下梯度下降进入病态曲率区域的点,以及到达最小值的绝对距离。如果我们使用一个较慢的学习速率,可能要花太多时间才能达到最小值。事实上,一篇论文报告称,学习率小到足以防止在山脊附*反弹可能会导致练习者认为损失根本没有改善,并放弃所有训练。

并且如果 f 中显著减小的唯一方向是低曲率的方向,则优化可能变得太慢而不实际,甚至看起来完全停止,产生局部最小值的假象

大概我们想要的是能让我们先慢慢进入病态曲率底部的*坦区域,然后向极小值方向加速。二阶导数可以帮助我们做到这一点。

牛顿方法

梯度下降是一种一阶优化方法。它只考虑损失函数的一阶导数,而不考虑高阶导数。这基本上意味着它对损失函数的曲率没有任何线索。它可以判断损失是否在下降,下降速度有多快,但不能区分曲线是*面,向上弯曲还是向下弯曲。

firstorder

发生这种情况是因为梯度下降只关心梯度,对于上面的所有三条曲线,在红色点是相同的。解决办法?考虑二重导数,或者梯度变化的速度。

一种非常流行的技术可以使用二阶导数来解决我们的问题,这种技术叫做牛顿法。为了不偏离 post 的主题,我不会深入研究牛顿法的数学。我要做的是,试着建立一个直觉,牛顿方法是做什么的。

牛顿的方法可以给我们一个理想的步长向梯度方向移动。因为我们现在已经有了关于损失表面曲率的信息,所以可以相应地选择步长,以不超过具有病理曲率的区域的底部。

牛顿的方法通过计算海森矩阵来实现,海森矩阵是损失函数相对于所有权重组合的双导数的矩阵。我所说的重量组合,大概是这样的。

Screen-Shot-2018-06-01-at-8.57.31-PM

然后,一个 Hessian 矩阵将所有这些梯度累积在一个大矩阵中。
hessian

Hessian 给出了损失曲面在某一点的曲率估计。损失曲面可以有正曲率,这意味着曲面,这意味着随着我们的移动,曲面会迅速变得不那么陡峭。如果我们有一个负曲率,这意味着随着我们的移动,表面变得越来越陡。

Screen-Shot-2018-06-01-at-9.27.25-PM

注意,如果这一步是负的,这意味着我们可以使用任意一步。换句话说,我们可以切换回原来的算法。这对应于梯度变得更陡的以下情况。

steeper

然而,如果梯度变得不那么陡,我们可能会走向病理弯曲底部的区域。在这里,牛顿的算法给了我们一个修正的学习步骤,正如你所看到的,它与曲率成反比,或者与表面变得不那么陡峭的速度成反比。

如果表面变得不太陡,则学习步骤减少。

那么我们为什么不更多的使用牛顿的算法呢?

你看到公式中的海森矩阵了吗?hessian 要求你计算损失函数相对于每个权重组合的梯度。如果你知道你的组合,这个值是神经网络中存在的权值的*方的数量级。

对于现代架构,参数的数量可能是数十亿,并且计算十亿*方梯度使得我们难以使用高阶优化方法进行计算。

然而,这里有一个想法。二阶优化是关于整合梯度如何改变自身的信息。虽然我们不能精确地计算这些信息,但我们可以根据梯度的过去行为,选择遵循引导我们寻找最优解的试探法。

动力

与 SGD 一起使用的一个非常流行的技术叫做动量。动量不是只使用当前步骤的梯度来引导搜索,而是累积过去步骤的梯度来确定前进的方向。梯度下降方程修正如下。

momentum

第一个方程有两部分。第一项是从以前的迭代中保留的梯度。这个保留的梯度乘以一个叫做“动量系数”的值,动量系数是每次迭代保留的梯度的百分比。

momentum2-1

如果我们将 v 的初始值设置为 0,并将我们的系数选择为 0.9,则后续的更新等式将如下所示。

update_eq-1

我们看到先前的梯度也被包括在随后的更新中,但是最*的先前梯度的权重大于不太*的梯度。(对于数学上的倾斜,我们取梯度步长的指数*均值)

这对我们的案子有什么帮助?考虑图像,注意大多数渐变更新是在一个之字形方向。还要注意,每个梯度更新已经被分解成沿着 w1w2 方向的分量。如果我们将这些向量单独求和,它们沿着方向 w1 的分量被抵消,而沿着方向 w2 的分量被增强。

moment_compo

对于更新,这将增加沿 w2 的分量,同时将沿 w1 方向的分量清零。这有助于我们更快地走向最小值。由于这个原因,在我们的研究中,动量也被认为是一种抑制振荡的技术。

它也建立速度,并加快收敛,但你可能要使用模拟退火的情况下,你过了最小值。

在实践中,动量系数初始化为 0.5,并在多个时期内逐渐退火至 0.9。

RMSProp

RMSprop 或均方根传播有一段有趣的历史。这是传奇人物杰弗里·辛顿在 Coursera 课堂上提出的一个随机想法。

RMSProp 也试图抑制振荡,但方式与动量不同。RMS prop 还消除了调整学习率的需要,并且是自动进行的。更重要的是,RMSProp 为每个参数选择不同的学习速率。

在 RMS prop 中,每次更新都是根据下述公式完成的。对每个参数分别进行更新。

momprop2-2

那么,让我们来分析一下这里发生了什么。

在第一个等式中,我们计算梯度*方的指数*均值。因为我们对每个参数分别进行处理,所以这里的梯度 Gt 对应于投影,或者沿着我们正在更新的参数所表示的方向的梯度分量。

为此,我们将计算出的指数*均值乘以一个超参数,用希腊符号 nu 表示。然后我们将当前梯度的*方乘以 (1 - nu) 。然后我们将它们相加,得到当前时间点的指数*均值。

我们之所以使用指数*均,是因为正如我们在动量例子中看到的,它有助于我们更多地权衡最*的梯度更新。事实上,“指数”这个名字来源于前面几项的权重呈指数下降(最*一项的权重为 p ,下一项为 p 的*方,然后是 p 的立方,以此类推。)

注意我们表示病理弯曲的图,沿着 w1 的梯度分量比沿着 w2 的梯度分量大得多。因为我们将它们*方并相加,所以它们不会抵消,并且对于 w2 更新,指数*均值很大。

然后在第二个等式中,我们决定了步长。我们沿着梯度的方向移动,但是我们的步长受到指数*均值的影响。我们选择一个初始学习率 eta ,然后除以*均值。在我们的例子中,由于 w1 的*均值比 w2 大得多,因此 w1 的学习步长比 w2 的学习步长小得多。因此,这将帮助我们避免在脊线之间跳跃,并向最小值移动。

第三个等式只是更新步骤。超参数 p 通常选择为 0.9,但是您可能需要对其进行调整。ε是等式 2,是为了确保我们最终不会被零除,一般选择为 1e-10。

还需要注意的是,RMSProp 隐式执行模拟退火。假设我们正朝着最小值前进,我们想放慢速度以免超过最小值。当步长太大时,RMSProp 会自动向最小值减小梯度步长(大的步长会使我们容易过冲)

圣经》和《古兰经》传统中)亚当(人类第一人的名字

到目前为止,我们已经看到 RMSProp 和 Momentum 采用了截然不同的方法。动量加速了我们在极小值方向的搜索,而 RMSProp 阻碍了我们在振荡方向的搜索。

亚当自适应力矩优化算法结合了动量和 RMSProp 的启发式算法。这是更新方程式。

adam

这里,我们计算梯度的指数*均值以及每个参数的梯度*方(等式 1 和等式 2)。为了决定我们的学习步骤,我们将我们的学习速率乘以梯度的*均值(动量的情况也是如此)并除以等式 3 中梯度*方的指数*均值的均方根(动量的情况也是如此)。然后,我们添加更新。

超参数β1一般保持在 0.9 左右,而β2保持在 0.99。ε一般选择为 1e-10。

结论

在这篇文章中,我们看到了 3 种方法来建立梯度下降,以解决病理弯曲的问题,同时加快搜索速度。这些方法通常被称为“自适应方法”,因为学习步骤是根据轮廓的拓扑来调整的。

在以上三个因素中,你可能会发现动力是最普遍的,尽管亚当在理论上看起来最有希望。实验结果表明,在损失相同的情况下,所有这些算法都能收敛到不同的最优局部极小值。然而,具有动量的 SGD 似乎比 Adam 找到更多*坦的最小值,而自适应方法倾向于快速收敛到更尖锐的最小值。*坦的最小值比尖锐的最小值概括得更好。

different

尽管自适应方法有助于我们驯服深层网络的损失函数的不规则轮廓,但它们还不够,尤其是随着网络每天变得越来越深。在选择更好的优化方法的同时,大量的研究正在进行,以提出能够产生更*滑损失函数的架构。批量规范化和剩余连接是这一努力的一部分,我们将很快在博客上详细讨论它们。但是这篇文章就说到这里。欢迎在评论中提问。

进一步阅读

  1. 指数加权*均值视频
  2. 从数学角度来看,动量的精彩解释
  3. 关于病理曲率和二阶优化的更多信息
  4. 论牛顿法和一般最优化

新功能:拖放上传

原文:https://blog.paperspace.com/introducing-drag-and-drop/

把东西放到你的 Paperspace 机器上应该很容易。

这就是我们创建拖放上传的原因:文件、图像、pdf、文档、电子表格等。甚至可以把文件夹放到你的电脑上,瞧,你的东西马上就上传到你的云电脑上了。

这项功能在网络浏览器和我们的本地应用中都可以使用。

介绍渐变数据集和全新的渐变笔记本 IDE!

原文:https://blog.paperspace.com/introducing-gradient-datasets-ide-updates/

在渐变笔记本的最新更新中,我们引入了许多新功能和改进,包括渐变数据集、对交互式小部件的支持、更好的单元、文件和内核管理,等等!

让我们开始更新吧!

引入公共和私有梯度数据集

梯度数据集为本地笔记本存储提供了一种替代方案,可跨梯度团队和资源使用。在这个版本中,现在可以在笔记本中装载数据集。

数据集在 IDE 的Datasets选项卡中可用,文件存储在/datasets目录中。

作为此次发布的一部分,我们已经为所有笔记本电脑提供了许多公共数据集。这些数据集可以在 IDE 的Datasets菜单中的Public选项卡中找到。

https://blog.paperspace.com/content/media/2022/04/Screen-Recording-2022-04-01-at-3.07.43-PM-1.mp4

Mounting the tiny-imagenet-200 dataset.

公共数据集经常更新。首批公共数据集包括:

  • Tiny ImageNet 200:200 类 10 万张图像(每类 500 张)缩小为 64×64 彩色图像。每个类有 500 幅训练图像、50 幅验证图像和 50 幅测试图像。
  • OpenSLR:LibriSpeech ASR 语料库由 Vassil Panayotov 在 Daniel Povey 的协助下准备的大约 1000 小时的 16kHz 朗读英语语音组成。这些数据来源于 LibriVox 项目的 read audiobooks,并经过仔细的分割和排列。
  • MNIST:一个手写数字的数据库有 60,000 个样本的训练集和 10,000 个样本的测试集。这是从 NIST 可获得的更大集合的子集。数字已经过大小标准化,并在固定大小的图像中居中。
  • LSUN :包含 10 个场景类别,如餐厅、卧室、小鸡、户外教堂等。对于训练数据,每个类别包含大量图像,从大约 120,000 到 3,000,000。验证数据包括 300 幅图像,测试数据对于每个类别有 1000 幅图像。
  • FastAI : Paperspace 的 Fast.ai 模板是为程序员建立起来并运行实用的深度学习而构建的。相应的公共数据集使使用 FastAI 运行时创建的笔记本能够快速访问所需的演示数据。
  • COCO :这个数据集是一个大规模的对象检测、分割、关键点检测和字幕数据集。数据集由 328K 图像、边界框及其标签组成。

如果您想创建自己的数据集,只需使用笔记本 IDE 中数据集菜单内的Team选项卡来上传和安装您自己的数据。

下面是一个如何上传历史艺术品数据集,然后使用命令!ls ../datasets/historic_art/在笔记本中访问数据集的示例:

https://blog.paperspace.com/content/media/2022/04/Screen-Recording-2022-04-04-at-1.53.12-PM-1.mp4

Uploading and mounting a dataset within a notebook.

对于大于 5 GB 的数据集,将提示您使用渐变工作流或渐变 CLI 上传数据。

有关 Gradient 的公共数据集和数据存储的更多信息,请通读相关的 doc 文档。

交互式小工具

除了数据集,笔记本现在还支持交互式 ipywidgets 开箱即用!

https://blog.paperspace.com/content/media/2022/04/itsworking.mp4

Displaying a GIF file with ipywidgets.

Jupyter 小部件或 ipywidgets 在各种上下文中都很有用,从修改笔记本的外观和可解释性到启用许多深度学习功能。小部件现在可以在本地使用了——只需导入ipywidgets模块!

ipywidgets 还支持笔记本中的许多其他小部件,从输入提示到滑块、多选、日历等等。

https://blog.paperspace.com/content/media/2022/04/final.mp4

Widgets also enable sliders, calendars, and more.

查看 ipywidgets 文档获取完整的小部件列表。

IProgress and PyTorch/TensorFlow dataloaders

笔记本现在也支持 IProgress 文本进度条。

https://blog.paperspace.com/content/media/2022/04/progress2.mp4

IProgress helps provide visual cues for operations that take time to complete.

IProgress 经常用于显示有用的信息,如一次训练中还有多少训练期。通过此次更新,IProgress 现在可以完全在笔记本中呈现。

https://blog.paperspace.com/content/media/2022/04/Screen-Recording-2022-04-04-at-2.25.00-PM.mp4

An example of IProgress used with TensorFlow Datasets.

这尤其扩展到 TensorFlow 和 PyTorch 的扩展库套件。TensorFlow 数据集和 Pytorch Lightning 等库中的函数现在可以正常工作了。

https://blog.paperspace.com/content/media/2022/04/fixed.mp4

An example using the PyTorch fit method.

此外,TensorFlow 和 PyTorch 数据加载器的进度条和培训功能现在可以在笔记本中自然显示。不再需要向通过终端执行的 Python 脚本中添加代码来运行包含这些函数的代码。

plotly 和其他带有 HTML 输出的绘图库

有了 ipywidgets 促进 HTML 输出,我们现在可以充分利用交互式绘图库 plotly

https://blog.paperspace.com/content/media/2022/04/plotly.mp4

Example using Plotly.

与 Matplotlib 或 T2 Seaborn 不同,plotly 允许对现有绘图进行交互式动态更改,如删除数据类或缩放。现在,我们可以创建 plotly 图形,甚至保存它们嵌入到其他网页。

我们期待着看到你可以做什么,与全面的小部件支持。

现在我们来谈谈 IDE 本身的更新。

单元管理更新

首先,我们极大地增强和扩展了笔记本中的单元操作,以包括来自 JupyterLab 的熟悉概念,如joinsplitinsert等等。

https://blog.paperspace.com/content/media/2022/04/combinemd.mp4

The new Gradient Notebooks IDE makes it easy to cut, copy, paste, join, split cells, and more.

更新后的笔记本 IDE 还可以轻松隐藏单元格输出,从而在笔记本中腾出更多空间。

https://blog.paperspace.com/content/media/2022/04/Screen-Recording-2022-04-04-at-4.49.22-PM.mp4

我们还扩展了命令面板,使其更容易访问有用的快捷键和命令。命令面板实体的列表正在快速增长,所以一定要检查一下!

https://blog.paperspace.com/content/media/2022/04/cmd-p.mp4

Creating a new notebook from the command palette.

命令面板可通过键盘快捷键**command + p**访问。

更新的文件管理器

我们还向文件管理器引入了拖放功能和一些其他有用的操作。

https://blog.paperspace.com/content/media/2022/04/re.mp4

Moving files with the drag-and-drop file manager.

此外,现在可以将多个文件上传到一个文件夹,并且当右键单击一个文件或文件夹时,有许多新的文件管理选项可用。

https://blog.paperspace.com/content/media/2022/04/Screen-Recording-2022-04-04-at-3.35.21-PM.mp4

Right click on a file to perform a number of actions like renaming, downloading, copying, and more.

内核管理

现在选择、停止和重启笔记本内核比以往任何时候都更容易。

https://blog.paperspace.com/content/media/2022/04/kernel.mp4

Using the kernel selection window to select a Python 3 kernel for a new notebook.

导航到笔记本 IDE 左下角的内核会话管理器,与每个笔记本内核实时交互。使用适当的按钮停止并重启内核。

额外收获:终端更新!

作为对专业用户和成长型用户的奖励,我们已经将终端作为分屏项目移动到笔记本 IDE 中!

https://blog.paperspace.com/content/media/2022/04/new-terminal.mp4

Pro and Growth plan users can now interact with their notebooks simultaneously while using the new terminal.

现在可以在不离开笔记本文件本身的情况下发出终端命令。

尝试一下

准备好试用新的渐变笔记本 IDE 了吗?

尝试在 Gradient 中创建一个新的笔记本或者继续从 ML Showcase 中派生一个项目

请务必让我们知道您的项目,如果您有任何问题,请不要犹豫联系支持

引入渐变低成本实例

原文:https://blog.paperspace.com/introducing-gradient-low-cost-instances/

构建公共云的首要目的是让公司能够大规模交付 web 应用。一晃十年过去了,如今云的用途远不止于此。已经出现的最令人兴奋的用例之一是利用云的巨大计算能力来运行高端工作负载,例如进行科学实验或训练深度神经网络。

这些应用程序的使用模式与传统的 web 服务有很大不同:它们的寿命很短,并且倾向于成批运行。为了应对这种新的行为,低优先级实例(通常称为“点实例”)的概念应运而生。低优先级实例本质上是云中的备用容量,以显著的折扣提供(与常规的按需价格相比),但有一个警告,如果其他任务需要该容量,它们可能会被中断。

我们很高兴地宣布,Gradient 现在支持这类实例类型,我们称之为“低成本”实例。低成本实例的折扣高达 65% ,具体取决于实例类型。

要在低成本模式下运行笔记本作业,只需在使用 CLI 时添加--preemptible或切换界面中的选项:

低成本实例的功能类似于普通实例,但在以下方面有所不同:

  • 他们可以在任何时候被打断,甚至在开始的几分钟内。
  • 它们总是在 24 小时后关闭,因此不适合长时间运行的作业。
  • 它们不能迁移到常规虚拟机实例。

如果您的工作负载具有容错能力,能够承受可能的中断,那么梯度低成本实例非常适合,可以显著降低计算成本。例如,使用带有 TensorFlowPyTorch 的检查点,将使您能够在梯度低成本实例上训练深度学习模型,而没有丢失实例被中断之前所取得的进展的风险。

Create an account or sign inTry Paperspace Sign in

关于渐变低成本实例的更多细节,请查看帮助中心。欲了解更多定价信息,请查看我们的梯度定价页面

💗PS 工程团队

介绍 ML 新闻📈

原文:https://blog.paperspace.com/introducing-ml-news/

ML News (简称 MLN)是一个分享和讨论所有与机器学习、深度学习、人工智能、数据科学等相关事物的社区。

受 Hacker News、Lobste.rs、r/MachineLearning 和原始 Slashdot 的启发,ML News 被创建为专家和爱好者从事开放学习、讨论和偶尔突发奇想的专用场所。

科技新闻有许多不同的渠道。根据我们的经验,这些经常充斥着不太相关的帖子,不知何故这些帖子被归入了“技术”的范畴。我们有一个长期的习惯,与朋友和同行分享有趣的 ML 相关文章和领域定义的研究,并且知道到目前为止我们不是唯一的一个。这就是为什么我们决定建立一个致力于 ML 领域的社区。我们希望通过缩小范围,我们可以建立一个空间,像我们这样的 ML 人可以很容易地找到有趣的新闻,突破,和该领域的更新,或者本质上任何可以激起我们兴趣的东西。

https://cards.producthunt.com/cards/posts/267774?v=1

从人工智能专家到人工智能爱好者,这是一个重视诚实辩论、好奇心、积极性和良好幽默的社区。我们也很想听到你的声音。

我们也没有任何计划将这个产品货币化。如果有需求,我们可能会在某个时候决定提供一个 ML jobs board 但我们认为,只要社区持续存在,保持完全免费和所有数据从*台流出对我们的使命至关重要。

使用代码 convergence 加入社区。如果您有任何意见、建议或反馈,也请让我们知道您的想法。

最重要的是,欢迎来到 ML 新闻!

ML News - A community for sharing & discussing interesting ML content | Product Hunt Embed

介绍 Paperspace +拥抱脸🤗

原文:https://blog.paperspace.com/introducing-paperspace-hugging-face/

我们很高兴地宣布与拥抱脸的新合作,为社区提供最先进的 NLP 工具。

查看网络研讨会,在免费的 GPU 上运行新笔记本

渐变+拥抱脸

新的 Transformers 容器使得在研究和生产中部署先进的 NLP 技术变得简单。所有依赖项都是预安装的,这意味着单个开发人员和团队可以立即投入运行,而没有工具或兼容性问题的压力。

ML Showcase 中提供的演示笔记本概述了如何使用这些库。

变形金刚图书馆

Transformers 库为 TensorFlow 2.0 和 PyTorch 提供了最先进的 NLP。这是一个开源的、基于社区的库,用于培训、使用和共享基于 Transformer 架构的模型,包括 BERTRoBERTaGPT2XLNet 等等。

除了模型本身,该库还包含许多下游任务的多个变体,如【NER】情感分析语言建模问答**** 等等。

Transformers 库允许您从预先训练的大型语言模型中受益,而不需要昂贵的计算基础设施。在几行代码中加载和使用预先训练的模型,并在 PyTorch 或 Tensorflow 之间无缝移动模型。

Tokenizers 库

Tokenizers 库提供了针对研究和生产优化的快速、先进的标记化。

在进入任何机器学习或深度学习自然语言处理模型之前,每个从业者都应该找到一种方法,将原始输入字符串映射到可训练模型可以理解的表示。有了 tokenizers 库,您可以以一种可互换的方式创建端到端的 Tokenizers。令人难以置信的快,这提供了在普通多核机器上训练、标记和解码每秒几十千兆字节的文本的能力。

NLP 管道

transformers v2.3.0 中新引入的管道封装了每个 NLP 过程的整个过程。这些提供了一个高级的、易于使用的 API,用于对各种下游任务进行推理。

将来自每个库的工具放入上下文中,通过管道进行推理,包括句子分类(情感分析)标记分类问答遮罩填充特征提取

https://www.youtube.com/embed/l3-X12JM8V4?feature=oembed

还会有更多

我们很自豪能为社区提供先进的 NLP 工具(毫不费力)。随着时间的推移,我们将通过为 Hugging Face 和 Gradient 上的其他行业标准库提供更多资源来扩展产品。

请在接下来的几个月中再次查看!

介绍 Paperspace Terraform Provider

原文:https://blog.paperspace.com/introducing-paperspace-terraform-provider/

在 Paperspace,我们正在构建世界上最先进的 GPU 和机器学习云。⚡

作为这项任务的一部分,我们将在整个过程中提供工具,帮助您充分利用您的 Paperspace 云资源。

我们今天很高兴地宣布 Paperspace Terraform 供应商的到来。🌈

有了这个开源工具,您现在可以使用 Terraform 来配置 Paperspace 云基础架构,既可以自动作为代码,也可以直接从命令行配置。

您将在 Paperspace cloud 上创建什么?👩‍💻⛅

请和我一起快速演示一下这种新的超能力:

https://www.youtube.com/embed/P3__yTs24rU?feature=oembed

作为一个开源项目,你可以在 GitHub 上探索提供者代码。您将找到一个示例 Terraform 配置文件来帮助您开始。我们也欢迎您提交特性请求和 bug 报告,以及开放您自己贡献的 Pull 请求。

我们很想听听你想创造什么!🍍你可以在视频里找到我的邮箱:)

更新了 Terraform 安装程序、新的 CLI,并引入了托管私有梯度集群

原文:https://blog.paperspace.com/introducing-private-gradient-clusters/

我们最*推出了基于 Terraform 的渐变 MLOps 安装程序,让用户将任何云变成超级强大的机器学习*台。开箱即用的梯度安装程序允许你在公共云(AWS,GCP,Azure)上运行梯度,或者在你自己的私有云或本地集群上运行梯度。

私有集群🌱

我们很高兴地宣布今天我们推出了一键式、完全托管的私有集群,您无需离开 web 控制台就可以配置和管理这些集群!

任何拥有 T1 或更高梯度订阅的团队现在都可以提供他们自己的私有梯度集群。对于第一个版本,私有集群运行在 Paperspace 核心云上,这意味着您可以利用我们的高性能、低成本 GPU 基础设施。

您可以选择single nodemulti-node集群,并选择位于美国西部(CA1)、美国东部(NY2)或欧洲西部(AMS1)的机器。

新安装程序 CLI👩🏽‍💻

你更喜欢在命令行工作吗?对于 Gradient Installer ( 参见 GitHub ),我们还引入了一个 CLI 来管理您的集群。

要开始安装,请从以下来源安装 Gradient-Installer:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/paperspace/gradient-installer/master/bin/install)" 

接下来,运行更新程序:

gradient-installer update

当提示输入 API 密钥时,请访问 Paperspace 控制台以生成特定于应用程序的 API 令牌。一旦你登录到 Paperspace,你可以在这里找到你的 API 密匙:https://console.paperspace.com/account/api

输入你的 API 密匙就可以了!您已经准备好开始构建了。

首先运行gradient-installer clusters list来查看您可用的私有集群。

想要启动新的集群吗?运行gradient-installer clusters up并按照设置提示进行操作——这就是全部内容!

当需要拆除集群时,只需使用命令gradient-installer clusters down

这一切对✨意味着什么

我们希望您的机器学习团队能够随身携带 Gradient,无论您的数据位于何处,也无论您想要使用什么云进行训练和推理。这就是为什么我们对实现私有集群如此兴奋。

从现在开始,如果你想在 AWS 或 Azure 或你桌子下面的 GPU 上设置一个私有集群,我们可以帮助你。

无论在哪里运行计算,您都将获得 Gradient 的所有优势:

  • Jupyter 笔记本电脑
  • 协作团队工作区
  • CI/CD
  • 作业运行程序
  • 生命周期管理
  • 还有更多!

展望未来,我们将向托管专用集群产品添加新的配置和功能。

如果您想直接进入私有托管集群,登录控制台并开始行动!

如果你想为你的团队做一个演示,或者想咨询我们的解决方案架构师,请在 sales@paperspace.com 给我们写信。

单点登录(SSO)简介

原文:https://blog.paperspace.com/introducing-single-sign-on-sso/

单点登录已经成为企业授权和身份管理的主流。我们很高兴地宣布,基于 SAML 的 SSO 现已在所有 Paperspace 产品中普遍提供。

单点登录的优势包括:

  • 优化引入新应用程序的流程
  • 最大限度减少网络钓鱼
  • 简化用户入职/离职流程
  • 通过集中化提高合规性
  • 提供用户访问记录和报告
  • 最大限度地减少内部服务台请求

参见帮助中心文档此处

首先,请联系销售人员以便在您的团队中启用 SSO。

GPU 云提供商终极指南简介

原文:https://blog.paperspace.com/introducing-the-ultimate-guide-to-gpu-cloud-providers/

我们长期以来一直对 GPU 云提供商让识别和比较 GPU 硬件变得如此困难感到沮丧。

事实上,GPU 云提供商提供不同形状和大小的 GPU 实例,对附加组件的定义也不同,这加剧了这种挫折感。根据供应商的不同,我们可能会发现自己需要考虑添加 CPU 实例(如 Google Cloud)、添加内存、添加存储等的隐藏成本。

当比较不同的供应商时,这个过程变得非常复杂。当单元完全不同时,我们如何比较实例和产品?这需要一点努力。

为了给 GPU 云提供商的世界带来透明度,我们创建了云 GPU 提供商终极指南

主要目标是为 GPU 云客户提供资源,以比较和对比最受欢迎的 GPU 云提供商的产品。

Cloud GPU provider comparison by price

我们的方法如下:

  • 关注每种产品的底层 GPU 规格,而不是营销
  • 将所有实例标准化为每 GPU 每单位时间的价格
  • 只考虑有保证的 GPU 资源,而不是可抢占的产品
  • 仅考虑专用 GPU 资源,而非部分产品
  • 避免“双边市场”的 GPU 产品
  • 比较每小时和每月的定价选项
  • 不要考虑存储、网络性能或入口/出口

这些是我们纳入的 GPU 云提供商。此外,每个供应商都有一个图纸空间比较页面,链接如下。

尽情享受吧!我们希望本指南为比较不同云提供商的 GPU 实例提供有用的资源。

时间序列分析导论

原文:https://blog.paperspace.com/introduction-time-series-analysis/

时间序列分析和预测有许多应用:分析你的零售连锁店的销售,发现你的服务器流量的异常,预测股票市场,等等。

挖掘时间序列分析的方法多种多样。我们将通过以下主题进行介绍。

目录

  • 时间序列的性质是什么?
    • 绘制滚动统计数据
    • 自相关
    • 偏自相关
    • 季节性
  • 时间序列的*稳性
    • *稳性测试
      • ACF-PACF 图
      • 迪基富勒试验
      • KPSS 试验
    • *稳性的变换
      • 去趋势
      • 区别
  • 结论

你可以从 Gradient 社区笔记本上免费运行这篇文章的完整代码。

在我们深入时间序列的细节之前,让我们准备数据集。

我们将使用德国耶拿的天气数据进行实验。您可以从 CLI 使用以下命令下载它。

wget https://storage.googleapis.com/tensorflow/tf-keras-datasets/jena_climate_2009_2016.csv.zip

解压缩文件,你会发现 CSV 数据,你可以使用熊猫阅读。数据集记录了几个不同的天气参数。出于本教程的目的,我们将使用温度数据(摄氏度)。数据定期记录;超过 24 小时,间隔 10 分钟。

import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('jena_climate_2009_2016.csv')

time = pd.to_datetime(df.pop('Date Time'), format='%d.%m.%Y %H:%M:%S')
series = df['T (degC)']
series.index = time

print(df)

series.plot()
plt.show()

时间序列的性质是什么?

时间序列是按时间顺序进行的一系列观察。某个变量随时间变化,我们分析其中的模式,并尝试根据该序列在过去显示的变化进行预测。

需要注意时间序列的某些属性:

  1. 趋势:一段时间内变量的总体上升或下降趋势。
  2. 季节性:时间序列的周期性成分,其中某一模式每隔几个时间单位重复一次。
  3. 残差:时间序列的噪声成分。

将一个时间序列分解成这些组成部分可以给我们提供大量的信息和对时间序列行为的洞察。

Source

滚动统计

开始理解一个时间序列的最好方法是可视化它的滚动统计。让我们以 2600(月度数据,即 30 天)为窗口绘制滚动统计数据。您还可以将滚动统计数据与数据集的*均值以及所有数据的最佳拟合线进行比较。

import numpy as np

plt.plot(series.index, np.array([series.mean()] * len(series)))

x = np.arange(len(series))
y = series.values
m, c = np.polyfit(x, y, 1)

plt.plot(series.index, m*x + c)

series.rolling(3600).mean().plot()

plt.legend(['mean', 'regression line', 'rolling mean'])
plt.ylabel('Temp')
plt.show()

roll_std = series.rolling(3600).std()
roll_std.dropna(inplace=True)
plt.plot(roll_std.index, np.array([roll_std.mean()] * len(roll_std)))

x = np.arange(len(roll_std))
y = roll_std.values
m, c = np.polyfit(x, y, 1)

plt.plot(roll_std.index, m*x + c)

roll_std.plot()
plt.legend(['rolling std mean', 'rolling std regression', 'rolling std'])
plt.ylabel('Temp')
plt.show() 

Mean, best fit line and rolling mean.

Rolling standard deviation calculated over every month

从图上可以明显看出,气温值有上升趋势,数据中有很强的季节性。滚动标准差下降趋势很弱,但滚动标准差数值本身变化很大。

自相关

我们知道相关性让我们通过假设高斯分布来比较两个变量之间的关系,并使用皮尔逊系数来找出所述变量之间的关系强度。

皮尔逊系数可以计算如下。

def correlation(x, y):
	x_norm = x - x.mean()
	y_norm = y - y.mean()
	return np.sum(x_norm * y_norm) / np.sqrt(np.sum(x_norm ** 2) * np.sum(y_norm ** 2))

自相关是信号与其自身延迟副本的相关性,作为延迟的函数。我们试图找到一个时间序列和它本身的滞后版本之间的相关性。我们观察这些值,发现它们是如何随着时间延迟的增加而变化的。可以这样计算。

def autocorrelation(x, k):
	val_0 = np.sum((x - x.mean()) ** 2) / len(x)
	val_k = np.sum((x[:-k] - x.mean()) * (x[k:] - x.mean())) / len(x)
	return val_k / val_0

偏自相关

偏自相关函数让我们计算一个时间序列和具有时滞的相同序列之间的偏相关。相关和部分相关的区别在于,部分相关让我们可以控制其他滞后值的影响。

目的是计算具有不同延迟的相同时间序列的相关性。

为了找出所有滞后值彼此之间的相关性,我们可以用公式表示以下性质的线性方程。设\(X(t - k)\)是滞后\(k\)的时间序列的列向量。上面生成的矩阵将创建一个形状为\((N \ x k)\)的数组。姑且称之为\(A\)吧。现在定义另一个系数未知的矩阵\(B\),大小与\(X\)相同。

然后我们有一个线性方程组:

$ X(t)= A \乘以 B$

我们需要解决\(B\)

对于 1 或 2 的滞后值,这在分析上很容易解决。也就是说,它会很快变得很麻烦。

尤尔·沃克方程提供了解决这个问题的另一种方法。时间序列中的一个元素被表示为一个线性方程,依赖于它之前的每个元素,直到最大滞后值。

该方程乘以最大滞后时间步长的元素,并计算期望值。等式除以序列的长度,然后除以零阶自协方差。

这里,\(c(i)\)是自协方差值(它除以第零个值以获得自相关参数\(r(p)\))。

所有滞后值的方程式可以简洁地表示如下。

现在我们需要求解\(\phi\)。我们知道如何从我们定义的自相关函数中计算矩阵\(r\)的值。为了找到部分自相关,我们可以使用 Python 中statsmodels包的现成函数,如下所示。

from statsmodels.graphics.tsaplots import plot_acf, plot_pacf

def get_acf_pacf_plots(df):
    fig, ax = plt.subplots(2, figsize=(12,6))
    ax[0] = plot_acf(df, ax=ax[0])
    ax[1] = plot_pacf(df, ax=ax[1])

get_acf_pacf_plots(series[5::6])
plt.show()

季节性

从我们的几个图中,我们看到数据有季节性因素。我们凭直觉知道,温度数据也应该有季节因素。温度应该每天都在波动,晚上比白天低。除此之外,它们还应该在一年的时间里振荡。

为了验证这些怀疑,我们可以看看我们的级数的傅立叶变换。

傅立叶变换允许我们将基于振幅的序列转换成基于频率的序列。它们是复值函数,将每个级数表示为复*面中正弦波的叠加。

这里 N 是序列的长度, X 是在 t=k. 的变换值

3Blue1Brown 制作了非常有趣的视频,这些视频将让您非常直观地了解数学公式,为什么要在复*面中表示值,以及如何捕捉函数中的主频率。你可以在这里找到傅立叶级数和傅立叶变换的视频。

我们可以将傅里叶变换的幅度绘制如下。

from scipy.fft import fft

fft_vals = fft(series[5::6].values)
f_per_dataset = np.arange(0, len(fft_vals))

n_samples_h = len(series[5::6])

hours_per_year = 24*365.2524
years_per_dataset = n_samples_h/(hours_per_year)

f_per_year = f_per_dataset/years_per_dataset
plt.step(f_per_year, np.abs(fft_vals))
plt.xscale('log')
plt.xticks([1, 365.2524], labels=['1/Year', '1/day'])
_ = plt.xlabel('Frequency (log scale)')
plt.show() 

这个片段是从这里借用的

我们得到如下的情节:

正如我们所看到的,1/年和 1/天附*的值显示了一个不寻常的峰值,验证了我们之前的直觉。

上述方法是光谱分析的一种基本形式。出现了许多复杂的谱密度估计方法,这些方法利用了傅立叶变换的基本思想:将时域信息转换到频域。其中有周期图巴列特法韦尔奇法等。通常,为了获得更好的结果,像汉明布莱克曼-哈里斯这样的窗口函数被用来*滑时间序列。你可以把它们想象成一维卷积滤波器,其中的参数根据我们所研究的窗函数的类型而变化。你可以通过阅读数字信号处理来了解更多关于频谱分析和傅立叶变换的知识。

时间序列的*稳性

当时间序列的统计属性(如均值、方差和自相关)不随时间变化时,时间序列是稳定的。

像 ARIMA 和它的变体这样的方法都是在假设他们建模的时间序列是*稳的情况下工作的。如果时间序列不是*稳的,这些方法就不能很好地工作。

幸运的是,我们可以做几个测试来确定时间序列是否是*稳的。对我们来说更幸运的是,有几个转换可以将非*稳时间序列转换为*稳时间序列,或者反过来。

我们将首先看一下测试,然后是转换。

ACF 和 PACF 图

观察时间序列的均值和方差如何随时间变化,可以让我们初步了解时间序列的非*稳性,以及我们可以应用哪种*稳化机制。看着我们上面找到的图,我们可以猜测天气时间序列远不是*稳的。我们也可以看看 ACF 和 PACF 图。如果图上的值下降很快,时间序列很可能接**稳。但是我们上面得到的 ACF 和 PACF 图在时间上过于接*。天气几乎每 10 分钟就变一次。让我们来看看当你按天获取数据时的曲线图。

series2 = series[119::120]
get_acf_pacf_plots(series2)
plt.show() 

如果我们每 31 天收集一次数据呢?

series2 = series2[30::31]
get_acf_pacf_plots(series2)
plt.show() 

正如我们所看到的,有几个点落在蓝色区域之外,这表明时间序列不是*稳的。

迪基-富勒试验

单位根检验测试自回归模型中存在单位根的零假设。原检验将一个时间序列视为 lag-1 自回归模型,单位根证明一个时间序列不是*稳的。

测试有三个主要版本:

1.测试单位根:

$ $ \ Delta y _ { t } = \ Delta _ { y _ { t-1 } }+u _ { t } $ $

2.带漂移的单位根测试:

$ $ \ Delta y _ { t } = a _ { 0 }+\ Delta _ { y _ { t-1 } }+u _ { t } $ $

3.测试具有漂移和确定性时间趋势的单位根:

$ $ \ Delta y _ { t } = a _ { 0 }+a _ { 1 } t+\ Delta _ { y _ { t-1 } }+u _ { t } $ $

增强的 Dickey-Fuller 测试是一种单尾测试,使用相同的测试程序,但应用于如下所示的 lag-p 系列。

使用像阿凯克贝叶斯汉南-奎因这样的信息标准来找到滞后值。

from statsmodels.tsa.stattools import adfuller

def test_dickey_fuller_stationarity(df):
    dftest = adfuller(df, autolag='AIC')
    dfoutput = pd.Series(dftest[0:4], index=['Test Statistic',
                                             'p-value',
                                             'Number of Lags Used',
                                             'Number of Observations Used'])
    for key,value in dftest[4].items():
        dfoutput['Critical Value (%s)'%key] = value
    if dfoutput['Critical Value (1%)'] <  dfoutput['Test Statistic']:
        print('Series is not stationary with 99% confidence. ')
    elif dfoutput['Critical Value (5%)'] < dfoutput['Test Statistic']:
        print('Series is not stationary with 95% confidence. ')
    elif dfoutput['Critical Value (10%)'] < dfoutput['Test Statistic']:
        print('Series is not stationary with 90% confidence. ')
    else:
        print('Series is possibly stationary. ')
    return dfoutput

out = test_dickey_fuller_stationarity(series)
print(out)

我们得到的输出如下。

Series is possibly stationary. 

Test Statistic                -8.563581e+00
p-value                        8.564827e-14
Number of Lags Used            6.200000e+01
Number of Observations Used    7.002800e+04
Critical Value (1%)           -3.430443e+00
Critical Value (5%)           -2.861581e+00
Critical Value (10%)          -2.566792e+00 

你可以在这里了解更多关于增强的迪基-富勒测试

KPSS 试验

另一个单位根检验是 KPSS 检验。KPSS 检验没有假设一个单位根存在的零假设,而是认为这是另一个假设。使用下面的回归方程。

其中\(r_t\)为随机游走,\(\beta t\)为确定性趋势,\(\epsilon_t\) 为*稳误差。

因此,零假设被表述为 H₀: σ = 0 ,而备选项为 Hₐ: σ > 0。

在实现中,我们应该记住 KPSS 是一个双尾检验。

from statsmodels.tsa.stattools import kpss

def test_kpss(df):
    dftest = kpss(df)
    dfoutput = pd.Series(dftest[0:3], index=['Test Statistic',
                                             'p-value',
                                             'Number of Lags Used'])
    for key,value in dftest[3].items():
        dfoutput['Critical Value (%s)'%key] = value
    if abs(dfoutput['Critical Value (1%)']) < abs(dfoutput['Test Statistic']):
        print('Series is not stationary with 99% confidence. ')
    elif abs(dfoutput['Critical Value (5%)']) < abs(dfoutput['Test Statistic']):
        print('Series is not stationary with 95% confidence. ')
    elif abs(dfoutput['Critical Value (10%)']) < abs(dfoutput['Test Statistic']):
        print('Series is not stationary with 90% confidence. ')
    else:
        print('Series is possibly stationary. ')
    return dfoutput

out = test_kpss(series)
print(out)

输出如下所示。

Series is not stationary with 99% confidence. 

Test Statistic            1.975914
p-value                   0.010000
Number of Lags Used      62.000000
Critical Value (10%)      0.347000
Critical Value (5%)       0.463000
Critical Value (2.5%)     0.574000
Critical Value (1%)       0.739000

你还可以做几个其他的测试来测试*稳性,像菲利普·赫伦齐沃特·安德鲁斯傅立叶 ADF 等等。,取决于他们如何对待结构突变、内生变量和外生变量等。你可以在这里了解他们,在这里了解

虽然单位根检验意味着*稳性,但是像迪基-富勒这样的单位根检验和像 KPSS 这样的*稳性检验是有区别的。你可以在这里和这里了解更多。

我们的时间序列给了我们相互矛盾的结果。但是如果一个序列不是*稳的,*稳化一个时间序列是很重要的,因为自回归模型是在*稳性的基本假设下设计的。

*稳性的变换

有几种不同的方法可以使时间序列*稳。

1.去趋势

通过从时间序列中减去滚动*均值,然后对其进行归一化,可以消除时间序列中的趋势。

def eliminate_trends(series):
    roll = series.rolling(4).mean()
    avg_diff = (series - roll)/roll
    avg_diff.dropna(inplace=True)
    return avg_diff

diff = eliminate_trends(series[5::6])
test_dickey_fuller(diff)
test_kpss(diff)

迪基-富勒结果:

Series is possibly stationary. 

Test Statistic                  -264.739131
p-value                            0.000000
Number of Lags Used                0.000000
Number of Observations Used    70087.000000
Critical Value (1%)               -3.430443
Critical Value (5%)               -2.861581
Critical Value (10%)              -2.566792 

KPSS 结果:

Series is possibly stationary. 

Test Statistic            0.302973
p-value                   0.100000
Number of Lags Used      62.000000
Critical Value (10%)      0.347000
Critical Value (5%)       0.463000
Critical Value (2.5%)     0.574000
Critical Value (1%)       0.739000 

如果您在序列中找到线性趋势,您可以改为找到回归线,然后相应地删除线性趋势。

def eliminate_linear_trend(series):
    x = np.arange(len(series))
    m, c = np.polyfit(x, series, 1)
    return (series - c) / m

diff = eliminate_linear_trend(series[5::6])
test_dickey_fuller(diff)
test_kpss(diff) 

迪基-富勒:

Series is possibly stationary. 
Test Statistic                   -42.626441
p-value                            0.000000
Number of Lags Used               62.000000
Number of Observations Used    84045.000000
Critical Value (1%)               -3.430428
Critical Value (5%)               -2.861574
Critical Value (10%)              -2.566788

KPSS:好的:

Series is possibly stationary. 

Test Statistic            0.006481
p-value                   0.100000
Number of Lags Used      65.000000
Critical Value (10%)      0.347000
Critical Value (5%)       0.463000
Critical Value (2.5%)     0.574000
Critical Value (1%)       0.739000 

2.区别

您可以选择一个滞后值,并将时间\(t\) 处的值与时间\(t - p\)处的值进行比较,其中\(p\)是滞后值。

def difference(series, lag=1):
	differenced = []
    for x in range(lag, len(series)):
    	differenced.append(series[x] - series[x - lag])
    return pd.Series(differenced) 

区别之后,我们可以再次尝试迪基-富勒和 KPSS 测试。

diff = difference(series[6::5])
test_dickey_fuller(diff)
test_kpss(diff)

Dickey-Fuller 的输出:

Series is possibly stationary. 

Test Statistic                   -42.626441
p-value                            0.000000
Number of Lags Used               62.000000
Number of Observations Used    84045.000000
Critical Value (1%)               -3.430428
Critical Value (5%)               -2.861574
Critical Value (10%)              -2.566788

和 KPSS:

Series is possibly stationary. 

Test Statistic            0.006481
p-value                   0.100000
Number of Lags Used      65.000000
Critical Value (10%)      0.347000
Critical Value (5%)       0.463000
Critical Value (2.5%)     0.574000
Critical Value (1%)       0.739000

结论

我们涵盖了在时间序列中寻找什么来分析线性和非线性趋势的所有基础知识。我们研究了*稳性的概念,不同的测试来确定一个序列是否*稳,以及如何使一个非*稳序列*稳。在本系列的下一部分,我们将讨论时间序列预测。

不要忘记从Gradient Community Notebook免费运行这篇文章的完整代码,或者查看第 2 部分和第 3 部分,分别涉及回归和 LSTMs,以及自回归模型和*滑方法。

Blender 中使用 Python 脚本进行三维建模的介绍

原文:https://blog.paperspace.com/introduction-to-3-d-modeling-with-python-scripting/

TronCool

Photo by Xiaole Tao / Unsplash

我们在世界各地的日常生活中感知的自然环境和周围环境是一种三维视觉。从几何学的角度来解释三维是很简单的。我们考虑水* x 轴、垂直 y 轴和旋转 z 轴,它们决定了物体在三维空间中的位置。我们生活在一个本质上是纯三维的世界,由长度、宽度和高度这三个决定性的重要因素组成。大多数固体物质,如球体、圆柱体、立方体、长方体和许多其他类似的形状,在本质上都是三维的。因此,能够用简单的工具重建 3d 视图和透视图在现代具有重要意义。

3d 模型是在 20 世纪 60 年代创建的,从那时起,3d 建模的流行程度就在不断上升。简而言之,三维建模是在三维空间中创建一个对象,以捕捉其所有继承的属性,如真实或虚构对象的大小、形状和纹理。在这篇文章中,我们的主要目标是对 Blender 中最好的 3d 模型渲染软件之一有一个基本的了解,并尝试学习与这个主题相关的所有基础知识。

我们不仅会看到使用 Blender 操作对象的物理方面,还会深入 Python 脚本来协调任务和完成特定的动作。让我们看一下目录,了解一下我们将在本文剩余部分探索和试验的概念。请随意跳到您最想了解的主题。但是,如果您是 3d 建模的新手,建议您阅读整篇文章。

目录:

  • 介绍
  • Blender 入门
  • 使用 Blender 理解 Python 脚本
  • 用 Blender 开发一个简单的三维项目
  • 使用 Python 脚本执行相同的任务
    1。导入库
    2。删除默认对象和相机
    3。添加多猴子网格
    4。创造相机
    5。保存渲染图像
    6。2 个额外的多摄像头和节省
  • 结论

简介:

本文的重点是开始使用 Blender,在对 Blender 有了基本的了解之后,我们将深入了解如何开始使用 Blender 中的 Python 脚本。Blender 是创建三维模型的最佳工具之一。它是完全免费和开源的,发布了许多版本。撰写本文时最稳定的版本是 2.92,而目前正在测试的 Blender 最新版本是 2.93.0 Alpha。然而,出于本文的目的,我将使用 Blender 的 2.81a 版本。我会推荐那些希望使用相同版本的用户,因为每个新的类别可能会有细微的变化。因此,最好是相应地跟上等效的版本,以避免任何进一步的混淆。

Blender *台是计算机图形设计和三维建模的一个极好的工具。它支持整个 3D 管道——建模、装配、动画、模拟、渲染、合成和运动跟踪、视频编辑和 2D 动画管道。Blender 的受欢迎程度正不断达到顶峰,这是因为在 Blender 的帮助下,您可以构建大量漂亮的项目。它为您提供了大量的工具和各种帮助工具,适用于您正在尝试完成的任何类型的计算机设计。除了这些神奇的工具之外,它还为用户提供了像 Python 这样的编程语言,从中你可以从头开始编写你的脚本并构建许多项目。让我们开始学习如何在 Blender 中使用 Blender 和 Python 脚本。


Blender 入门:

对于整个文章系列,我们将使用 Blender 的 2.81 版本。撰写本文时 Blender 的最新版本是 2.93。用户也可以自由地探索最新的版本,并在这个环境中构建他们的项目。然而,这两个版本之间可能会有一些微妙的变化。Blender 2.81 版本可以从以下链接下载。

这个新版本比前几代有几个改进,特别是在更新的画笔和光标设置,遮罩的改进,附加工具,RTX 显卡支持,等等。确保您按照下载和安装程序进行操作,直到您成功地将其安装到您的系统上。一旦完成,用户可以继续点击 Blender 应用程序并打开该应用程序。

打开 Blender 后,您会在 Blender 屏幕中央找到上图所示的弹出菜单。在这里,您将找到关于您想要创建的文件类型的多个选项,包括像 General 这样的选项,它基本上是一个用于执行大多数操作的 3d 建模空间,以及我们将在本教程系列中使用的选项。我们也有其他类型的文件要处理,包括二维动画、雕刻、VFX 和视频编辑。您还可以访问您之前处理过的最*五个文件,或者选择使用打开图标浏览任何其他文件。选择常规选项,我们可以继续了解 Blender 的一些基本功能。

当您进入 Blender 的一般结构时,您可以通过单击鼠标 3(鼠标中键)按钮并四处移动鼠标来探索整个 3d 视口环境,以便更好地查看我们将构建 3d 建模项目的空间。您也可以使用三维空间右上角的“X”、“Y”和“Z”符号来执行以下操作。单击立方体以选择立方体,并试验您可以在此对象上执行的许多操作。键盘上的“G”按钮是将对象移动到所需位置的快捷方式。“R”按钮是旋转特定选定对象的快捷方式。在后面的章节中,我们将会对 Blender 有一个更好的理解。现在,让我们探索一些在 Blender 中可用的基本工具。

在右侧,我们可以注意到还有另外两个编辑器窗口,即大纲视图和属性编辑器类型。“大纲视图编辑器”( outliner editor)类型用于跟踪混合器空间中存在的大量对象,以便可以相应地处理每个对象。“属性”窗口是最有用的实体之一,因为它有助于查看、设置和更改对象的不同类型的属性。这种编辑器类型会被频繁使用。因此,强烈建议您探索以下内容。

下图所示的第一个菜单或主菜单栏包含一些最基本的功能,这些功能对于开发三维模型非常有用。文件选项将具有与大多数用户相似的显著特征,这些用户具有任何其他类型的开发环境的经验,具有打开、保存和其他类似操作的基本选项。编辑窗口允许用户执行撤销、重做等操作。渲染窗口将频繁用于渲染图像,将物体从三维图形转换成图像。窗口中的重要功能是切换系统控制台,它允许你调试你的 Python 程序。帮助选项为用户提供了对一些精彩文档的访问。其他选项将在本系列文章中探讨。

下面是另一个菜单栏的图像表示,它由一些重要元素组成,您可能会发现这些元素在构建三维模型时很有用。对象模式是您可以与 Blender 空间中的对象进行交互的许多方法之一。对象模式帮助您管理固态项目,而编辑模式将帮助您管理更基本的内容。其他选项如查看选择对象用于执行特定的动作。添加功能是我们在文章中用来添加一些不同类型的网格来学习 3d 建模的。

虽然 Blender 中有大量的信息和众多的实体、工具和技术需要学习,但我们将在这个 3d 建模系列的后续文章中重新访问其他必需品。目前,对 Blender 的基本介绍对于初级项目来说已经足够了。我们可以转到 Python 脚本部分来评估 Blender 中编程的一些基本概念。


使用 Blender 理解 Python 脚本:

要在 Blender 中使用 Python 脚本处理所有操作,请将光标移动到菜单栏正下方 Blender 屏幕左上角的编辑器类型图标上。如果将光标放在指定位置,您会注意到图标显示特定区域的当前编辑器类型是三维视口综合,您可以在其中操纵三维空间中对象的性能、动作和操作。点击图标,你会看到许多选项。选择允许您编辑 Python 脚本或任何其他类型的文件内文档的文本编辑器选项。查看下图以获得进一步的指导。

执行此操作的另一种方法是借助 Blender 提供的快捷选项。您可以使用 Shift + F11 组合进入文本编辑器模式,并使用 Shift + F5 组合重新进入三维视口状态。在 Python 脚本中,我更喜欢将屏幕分为视窗和文本编辑器环境。一旦您在屏幕上启用了文本编辑器,请单击可用的新选项。单击 New 图标后,您会发现创建了一个新的文本文件。为了执行 Python 脚本,我们将这个新创建的文件重命名为类似“test.py”的名称,并导入 bpy 模块。请注意,您可以将 Python 文件标记为您喜欢的任何名称。然而,“的延伸。py”极其重要。

import bpy

一旦导入了 bpy 模块,现在就可以借助 Python 脚本执行多个 Blender 操作。当我们在用 Python 编程开发我们的 3d 模型项目的后面部分详细讨论时,我们将会更好地理解这个概念。现在,让我们了解一些需要牢记的基本要点。

  • 访问数据块- 模块 bpy.data 允许用户访问文件中的库数据。(如果您滚动某个特定的属性,您会注意到指令中嵌入了一些默认的 Python 代码。)
bpy.data.scenes
<bpy_collection[1], BlendDataScenes>
  • 收藏- 用户可以查看搅拌机空间中的众多对象。
list(bpy.data.objects)
[bpy.data.objects["Cube"], bpy.data.objects["Plane"]]
  • 访问属性- 有了特定数据块的知识,我们就可以访问它们各自的特性。
bpy.data.materials.new("MyMaterial")
bpy.data.materials['MyMaterial']
  • Context - 为了让特定的动作按照用户的意愿发生,我们使用了 bpy.context 操作。
bpy.context.scene.objects
  • 数据创建和删除- 我们可以相应地创建和删除数据。大多数项目的第一步都会涉及到这个概念,包括下一节中的内容。

通过讨论 Blender 中 Python 脚本的一些基础知识,我们可以继续开发一些简单的 3d 建模项目。如果您想了解有关该主题的更多信息,请访问以下文档。让我们继续用 Blender 构建我们的第一个项目。


用 Blender 开发一个简单的三维项目:

任务:我们将在本文中开发的简单 3d 项目是导入多个猴子网格,并在多摄像机视图的帮助下从不同的角度查看它们。

为了执行以下任务,我们将首先删除 Blender 屏幕上显示的默认立方体。要执行此操作,您可以用鼠标选择对象,然后单击键盘上的删除按钮。另一个选项是选择对象并点击键盘上的“X”按钮。向用户呈现删除对象的选项,用户可以继续这样做。一旦你删除了默认的立方体,我们的下一步是加载猴子网格。为了执行导入猴子网格的这一步,点击 Blender 中的 add 按钮,然后选择网格图标,并沿着直到最后一行,在这里你可以选择一只猴子。

现在我们已经成功地添加了猴子网格,你可以继续点击对象并点击 ctrl-cctrl-v 。选择对象后,也可以借助鼠标右键执行以下复制-粘贴操作。猴子网格一旦被复制粘贴,就可以被拖动到用户想要的位置。选择新创建的对象网格,并按键盘上的“G”将其拖动到合适的位置。按下鼠标左键将其放在一个合适的位置。您可以创建五个这样的副本,并将它们拖放到您想要的位置。下面显示的图像是我如何复制粘贴(使用代码)将猴子放置在它们各自的位置。

一旦猴子网格被放置在搅拌机屏幕上,我们的下一步是相应地调整相机或对象。因为我们在屏幕上有多个网格,所以最好的选择是调整相机视图,以便从不同的视角和相机角度查看猴子网格的位置。点击 Blender 中的相机模块,并按下键盘上的“G”将其拖动到合适的位置。一旦你把它拖到需要的位置,你也可以点击键盘上的“R ”,选择相应的轴来旋转相机。您可以选择“X”、“Y”或“Z”轴来旋转和放置摄像机到所需的位置。

一旦摄影机放置在所需的位置,您可以通过复制和粘贴它们来创建多个摄影机,类似于猴子网格。借助“G”按钮将它们拖动到选定的位置,然后单击鼠标左键放置它们。单击 ctrl + Numpad 0 命令来选择您正在使用的特定摄像机。选择所需的相机后,您可以单击 f12 按钮,或者迁移到菜单栏上的渲染图标,然后选择渲染图像选项。将图像保存在所需的位置。建议查看者尝试不同的角度和位置,以查看从哪个角度可以获得多个猴子网格的最佳视图。下面是 Blender 中生成的渲染图像的一个例子。

我还有两张分别来自中心和左角的渲染图。这些将在使用 Python 脚本执行相同任务的下一节中进一步讨论。在进入下一个主题之前,请随意尝试并了解更多信息。


使用 Python 脚本执行相同的任务:

我们已经了解了如何使用 Blender 创建多个猴子网格和一个多相机视图的基本工作流程。让我们了解如何用 Python 编程实现同一个项目。在 Blender 中打开文本编辑器,让我们开始编码。这个过程的主要步骤包括导入所有需要的库,删除默认的立方体对象和相机,编写几个“for”循环来导入多个猴子网格,创建相机,最后保存渲染图像。

我们将重复创建相机和保存渲染文件两次以上。完成该步骤是为了从不同的摄像机位置生成多个渲染图像,以便观众可以从多个位置看到这些图像。让我们开始编码过程。

导入库:

为了实现我们的项目,我们将进口三种主要必需品。首先,我们将导入 bpy 库,这将允许用户在 Blender 文本编辑器中访问 Python 环境。我们将从这个库中导入一些功能,以便于访问。然而,用户可能会注意到,在一些代码块中,我没有怎么使用它们。最后,我们还将导入数学库来执行不同类型的计算。在这个特定的场景中,数学库主要用于计算欧拉角以测量旋转角度。必要的进口如下:

import bpy
from bpy import context, data, ops
import math

移除默认对象和摄像机:

我们的下一个目标是从 Blender 环境中移除所有不必要的元素。可以通过两种方式执行此操作。一种方法是使用 delete 函数并将 use global 参数设置为 False。另一种方法是创建一个 for 循环并检查默认屏幕中的所有对象,选择您正在寻找的特定类型的对象,并删除您认为对特定项目不必要的适当元素。下面是执行以下操作的代码块。

#  Remove The Default Cude Object
# bpy.ops.object.delete(use_global=False)

bpy.ops.object.delete(use_global=False, confirm=False)

for o in bpy.context.scene.objects:
    if o.name == "Cube":
        bpy.ops.object.delete(use_global=False)

添加多猴子网格:

在下一步中,我们将相应地在它们各自的位置添加猴子网格。为了执行这一步,我们将把三个变量初始设置为零。这三个变量分别代表 x 轴、y 轴和 z 轴。我们将相应地修改这些变量,以获得猴子网格的最佳定位。计数变量用于根据需要改变这些轴的位置。

第一次迭代循环将在各自的位置添加三个猴子网格,第二次迭代循环将在另一侧再添加两个猴子。请随意尝试各种替代方案和位置变化,看看什么最适合您的模型。您获得的最终模型应该类似于上一节中显示的第一幅图像。然而,我们可以注意到,使用代码的方法比随机复制粘贴代码更有效。

# Create multiple monkey meshes
x = 0
y = 0 
z = 0

count1 = 0
count2 = -5

for i in range(3):
    # Import the monkey mesh
    bpy.ops.mesh.primitive_monkey_add(location = (x + count1, y + count1, z))
    count1 += 5

for i in range(2):
    # Import the monkey mesh
    bpy.ops.mesh.primitive_monkey_add(location = (x + count2, y - count2, z))
    count2 += -5

正在创建摄像机:

一旦我们完成创建多个猴子网格,我们可以添加一个相机来查看我们的对象和它们的整体视图。我们将设置场景并创建我们的相机,并将相机镜头设置为 30 毫米。默认的相机尺寸通常是 50 毫米。但是,我们将使用这个特定的大小,以便在指定的范围内所有的对象都清晰可见。然后,我们将创建我们的相机对象,并为我们的相机设置位置和旋转角度。

这个位置将决定摄像机的位置,在这个位置上,我们可以看到屏幕可视区域上所有物体的最佳视角。在我们前面的编码部分中导入的数学库的帮助下,旋转角度从角度转换为弧度。我们将指定沿所有三个轴的旋转角度(弧度),以便正确放置位置和旋转角度。我们将场景集合链接到当前摄像机,并确保当前选定的摄像机设置为活动的。

### Creating A New Camera Angle
scn = bpy.context.scene

# create the second camera
cam = bpy.data.cameras.new("Camera")
cam.lens = 30

# create the second camera object
cam_obj = bpy.data.objects.new("Camera", cam)

# Locations
cam_obj.location.x = 16
cam_obj.location.y = -6
cam_obj.location.z = 8

# Rotations
cam_obj.rotation_euler[0] = math.radians(64)
cam_obj.rotation_euler[1] = math.radians(0)
cam_obj.rotation_euler[2] = math.radians(47)

scn.collection.objects.link(cam_obj)

# Set the Camera to active camera
bpy.context.scene.camera = bpy.data.objects["Camera"]

保存渲染图像:

创建多个猴子网格并将我们的相机放置在所需位置后的最后一步是保存图像。在我们保存图像之前,我们将渲染我们的图像并将其存储在一个。png”格式。渲染基本上是将 Blender 空间中的 3-D 场景转换成 2-D 视图,以便它们在打印形式中更容易可视化。为了执行这一步,我们将设置路径,并从摄像机的角度将 3d 模型场景渲染为 2d 图像,该图像将保存在指定的目录中。一旦我们的图像被渲染和保存,我们可以恢复以前的路径来执行进一步的计算和操作。

# Setting the path for the first image captured in the first camera
FILE_NAME = "1.png"
FILE_PATH = "D:\\Cool Projects\\Paperspace\\3-D Models\\1.png"

# Save Previous Path
previous_path = bpy.context.scene.render.filepath

# Render Image
bpy.context.scene.render.filepath = FILE_PATH
bpy.ops.render.render(write_still=True)

# Restore Previous Path
bpy.context.scene.render.filepath = previous_path

2 个额外的多摄像头和节省:

虽然您可以选择在本节前面的渲染步骤中结束您的编程,但我将更进一步,添加一些额外的相机,并从几个不同的角度和视图保存图像。我们将再创建两个摄像机,并设置镜头、位置(在 x 轴、y 轴和 z 轴上)和旋转欧拉角(围绕 x 轴、y 轴和 z 轴)。该代码中执行的步骤类似于前两个代码块,在这两个代码块中,我们执行了添加摄像机和保存渲染图像的操作。我们将缩放每个摄像机视图的所有分辨率,并将它们保存在指定的目录位置。在这一步中,您可以随意尝试,通过激活更多的相机并从其他几个角度查看您的模型来探索众多选项。

### Creating A New Camera Angle
scn = bpy.context.scene

# create the second camera
cam2 = bpy.data.cameras.new("Camera 2")
cam2.lens = 40

# create the second camera object
cam_obj2 = bpy.data.objects.new("Camera 2", cam2)

# Set Location 
cam_obj2.location.x = -0.1
cam_obj2.location.y = -19
cam_obj2.location.z = 10

# Set Angles
cam_obj2.rotation_euler[0] = math.radians(64)
cam_obj2.rotation_euler[1] = math.radians(-0)
cam_obj2.rotation_euler[2] = math.radians(-0.1)

scn.collection.objects.link(cam_obj2)

# Set the Camera 2 to active camera
bpy.context.scene.camera = bpy.data.objects["Camera 2"]

### Rendering Procedure
render = bpy.context.scene.render
scale = render.resolution_percentage / 100

FILE_NAME = "2.png"
FILE_PATH = "D:\\Cool Projects\\Paperspace\\3-D Models\\2.png"

# Save Previous Path
previous_path = bpy.context.scene.render.filepath

# Render Image
bpy.context.scene.render.filepath = FILE_PATH
bpy.ops.render.render(write_still=True)

# Restore Previous Path
bpy.context.scene.render.filepath = previous_path

### Creating A New Camera Angle
scn = bpy.context.scene

# create the second camera
cam3 = bpy.data.cameras.new("Camera 3")
cam3.lens = 40

# create the second camera object
cam_obj3 = bpy.data.objects.new("Camera 3", cam3)

# Set Location 
cam_obj3.location.x = -20
cam_obj3.location.y = -12
cam_obj3.location.z = 12

# Set Angles
cam_obj3.rotation_euler[0] = math.radians(64)
cam_obj3.rotation_euler[1] = math.radians(-0)
cam_obj3.rotation_euler[2] = math.radians(-46.1)

scn.collection.objects.link(cam_obj3)

# Set the Camera 3 to active camera
bpy.context.scene.camera = bpy.data.objects["Camera 3"]

### Rendering Procedure
render = bpy.context.scene.render
scale = render.resolution_percentage / 100

FILE_NAME = "3.png"
FILE_PATH = "D:\\Cool Projects\\Paperspace\\3-D Models\\3.png"

# Save Previous Path
previous_path = bpy.context.scene.render.filepath

# Render Image
bpy.context.scene.render.filepath = FILE_PATH
bpy.ops.render.render(write_still=True)

# Restore Previous Path
bpy.context.scene.render.filepath = previous_path

一旦运行完所有这些代码块,请确保在。png”格式,并验证是否所有这些渲染图像都是从不同的位置和角度捕获的,因为它们是从三个不同的摄像机角度获得的视图。一旦通过验证,我们就成功地完成了这篇文章的所有任务。在即将到来的三维建模系列中,我们将探索更多的概念!


结论:

ZMorph VX Multitool 3D Printer

Photo by ZMorph All-in-One 3D Printers / Unsplash

在本文中,我们了解了如何开始使用 Blender,以及学习使用 Blender 进行三维建模所需的所有初始概念。在探索了 Blender 提供的工具和技术之后,我们还研究了 Blender 环境中可用的 Python 脚本选项。Python 脚本帮助开发人员在 Blender 建模空间中更有效地计算和执行特定的操作。在 Blender 中学习了 Python 编程的一些基本方面之后,我们开始了一个简单的 3d 项目,引入了多重网格和多重相机创建的概念。我们首先明白了如何在提供给我们的 Blender 工具的帮助下构建这个项目。

一旦我们理解了使用 Blender 的 3d 建模项目的许多方面,我们就可以在 Python 脚本的帮助下继续执行类似的操作并完成相同的任务。在 Blender 中可用的文本编辑器选项的帮助下,我们用多摄像机视图构建了多个猴子网格,并将所有渲染的图像保存在所需的目录位置。在本系列文章的下一部分,我们将更深入地讨论如何使我们的模型看起来更具视觉吸引力和美感。我们将学习使用 Blender 工具和 Python 编程为我们的结构设计添加背景和纹理。在那之前,享受学习和探索的乐趣吧!

音频分析和处理简介

原文:https://blog.paperspace.com/introduction-to-audio-analysis-and-synthesis/

音频分析和信号处理已经从机器学习和深度学习技术中受益匪浅,但在数据科学家培训和词汇中的代表性不足,而 NLP 和计算机视觉等领域占主导地位。

在这一系列文章中,我们将尝试稍微重新*衡等式,并探索与音频相关的机器学习和深度学习应用。

介绍

让我们了解一些基本情况。声音以波的形式传播,这种传播通过波传播的介质中的振动来实现。没有媒介,没有声音。因此,声音不会在真空中传播。

这些振动通常用简单的二维图来表示,其中\(x\)维是时间,\(y\)维是所述压力波的幅度。

通过理解压缩和稀薄的概念,声波可以被想象成压力波。拿一个音叉。它来回振动,推动周围的粒子靠*或远离。空气被推得更*的部分叫做压缩,被推得更远的部分叫做稀薄。这种利用压缩和稀疏穿越空间的波被称为纵波。

(source)

波长是两次连续压缩或两次连续稀疏之间的距离。频率或音高是声波每秒钟重复自身的次数。那么波的速度就是波长和波的频率的乘积。

\[v = \lambda * f \]

其中$ v \(是波的速度,\) \lambda \(是波长,\) f $是频率。

事实是,在自然环境中,我们听到或观察到的声音很少出现在一个清晰的频率中(以可辨别的正弦幅度模式)。波相互叠加,使得仅通过震级读数很难理解哪些频率在起作用。理解频率非常重要。它的应用范围从创作优美的音乐到确保引擎不会在彼此共振的声压波下爆炸。

傅立叶变换

从前,约瑟夫·傅立叶对地球上的每一条曲线都下了决心。他提出了一个疯狂的想法,即每条曲线都可以表示为不同幅度、频率和相位差的正弦波的总和。一条直线,一个圆,傅立叶本人的一些奇怪的徒手画。看看下面链接的视频,了解这个简单的想法有多复杂。视频描述了傅立叶级数,它是以满足初始分布的方式构建的几个正弦波的叠加。

https://www.youtube.com/embed/r6sGWTCMz2k?feature=oembed

所有这些都是不同正弦波的简单集合。很疯狂,对吧?

在给定初始条件的情况下,傅立叶变换是获得不同频率的所有系数及其相互作用的一种方式。在我们的例子中,来自声压波的幅度数据将是我们的初始条件,傅立叶变换将帮助我们转换为一个表达式,该表达式可以随时描述不同频率在创建您最终听到的声音中的作用。我们称这种从时域表示到频域表示的转换。

对于离散序列$ {x_{n}}:= x_{0},x_{1},...x_{n-1} \(,由于计算机不理解连续信号,频域中的变换表示\) {X_{n}}:= X_{0},X_{1},...X_{n-1} $可以用下面的公式求出:

$ $ x _ { k } = \ sum _ { n = 0}^{n-1 } x _ { n } e^{\frac{-2 \ pi I } { n } k n } $ $

这相当于:

$ $ x _ { k } = \ sum _ { n = 0}^{n-1 } x _ { n }[cos(\ frac { 2 \ pi } { n } k n)-I . sin(\ frac { 2 \ pi } { n } k n)]$ $

傅立叶变换是可逆函数,傅立叶逆变换可由下式得到:

$ $ x _ { n } = \ frac { 1 } { n } \ sum _ { n = 0}^{n-1 } x _ { k } e^{\frac{2 \ pi I } { n } k n } $ $

现在,离散傅立叶变换的计算量相当大,时间复杂度为$ O(n^{2}量级。但是有一种称为快速傅立叶变换(或 FFT)的更快的算法,其执行复杂度为$ O(n.log(n)) \(。这是速度上的显著提升。即使对于\) n = 50 $的输入,性能也有显著提高。

(source)

如果我们使用采样频率为 11025 Hz 的音频,在一首三分钟的歌曲中,大约有 2,000,000 个输入点。在这种情况下,$ O(n.log(n)) $ FFT 算法提供了数据的频率表示:

$ $ \frac{n^{2}}{n.log_{2}(n)} = \ frac {(2 10{6}){2}}{(2 10^{6}).log_{2}(2 10^{6})} $$

快 10 万倍!

尽管今天有很多算法的变体,但最常用的是库利-塔克 FFT 算法。最简单的形式是基 2 时间抽取(DIT) FFT,它首先计算偶数索引输入和奇数索引输入的 DFT,然后将这两个结果合并,产生整个序列的 DFT。然后可以递归地执行这个想法,将总运行时间减少到 O( N log N )。他们利用 DFT 算法中的对称性来提高运算速度。还有更通用的实现,但是当输入大小是 2 的幂时,更常用的实现效果更好。

短时傅立叶变换

利用傅立叶变换,我们将信号从时域转换到频域。在这样做的时候,我们看到每个时间点是如何在每个频率上相互作用的。短时傅立叶变换是针对相邻的时间点而不是整个信号进行的。这是通过利用以特定跳跃长度跳跃的窗口函数来实现的,以给出频域值。

设$ x \(是长度为\) L \(的信号,而\) w \(是长度为\) N \(的窗口函数。那么最大帧索引\) M \(将是\) \frac{L - N}{N}\(。\) X(m,k) \(将表示在\)m^{th}\(时间帧的\) k^{th} \(傅立叶系数。定义的另一个参数是\) H $,称为跳数。它决定了窗函数的步长。

那么 STFT $ X(m,k) $由下式给出:

\[ X(m,k) = \sum_{n = 0}^{N - 1} x[n + m.H]。w[n]。e^{\frac{-2 \pi i}{N}k n} $$有各种各样的窗口函数可供选择;汉恩、海明、布莱克曼、布莱克曼-哈里斯、高斯等。 STFT 可以为我们分析提供丰富的视觉表示,称为声谱图。声谱图是 STFT $ X(m,k) $的*方的二维表示,可以给我们重要的[视觉洞察](https://www.izotope.com/en/learn/understanding-spectrograms.html)一段音频的哪些部分听起来像嗡嗡声、嗡嗡声、嘶嘶声、滴答声或爆裂声,或者是否有任何间隙。 ## 熔融标度 到目前为止,我们已经掌握了分析声音的不同方法,假设它是线性的。然而,我们对声音的感知不是线性的。事实证明,我们可以更好地区分低频和高频。为了捕捉这一点, [Mel 标度](https://archive.is/20130414065947/http://asadl.org/jasa/resource/1/jasman/v8/i3/p185_s1)被提出作为一种变换,来表示我们对声音的感知认为是频率的线性发展。 将频率从赫兹转换到梅尔的一个常用公式是: $$ m = 2595。log_{10}(1 + \frac{f}{700}) \]

还有其他不太受欢迎的尝试来定义心理声学感知的尺度,如吠声尺度。心理声学比我们在这里讨论的要多得多,比如人耳如何工作,我们如何感知响度、音色、速度和节拍,听觉掩蔽,双耳节拍,HRTFs 等。如果你感兴趣,这个这个这个可能是很好的入门资源。

当然,有一些与 Mel 量表相关的批评,关于如何控制实验来创建量表,以及结果是否有偏差。在音乐人和非音乐人身上的测试是*等的吗?每个人对他们所感知的线性事物的主观看法真的是决定人类感知行为的好方法吗?

不幸的是,我们不会再深入讨论偏见了。取而代之,我们将采用 Mel 标度,并以某种方式应用它,这样我们就可以获得类似于声谱图的表示,这是先前由 STFTs 促成的。

滤波器组和 MFCCs

MFCCs 或 Mel 频率倒谱系数已经成为表示声音的流行方式。简而言之,MFCCs 的计算方法是:对音频信号应用预加重滤波器,获取该信号的 STFT,应用基于 mel 比例的滤波器组,进行 DCT(离散余弦变换),然后归一化输出。有很多大词,所以让我们打开它。

预加重滤波器是一种使用信号的加权单阶时间差来稳定音频信号的方法。

\[y(t) = x(t) - \alpha x(t - 1) \]

滤波器组是一组三角波形。这些三角形滤波器被应用于 STFT 以提取功率谱。滤波器组中的每个滤波器都是三角形,在中心的幅度为 1,在下一个滤波器组的中心频率的中心,频率线性降低到 0。

这是 20Hz 至 4kHz 之间的一组 20-40 个三角滤波器,我们将其应用于从预加重滤波信号的 STFT 获得的周期图功率谱估计。我们的滤波器组以与滤波器数量一样多的向量的形式出现,每个向量的大小与傅立叶变换中的频率数量相同。每个矢量大部分是零,但对于光谱的某一部分是非零的。为了计算滤波器组能量,我们将每个滤波器组乘以功率谱,然后将系数相加。

(source)

你可以在这里和这里了解他们

最后,经过处理的滤波器组经过离散余弦变换。信号的离散余弦可以表示如下:

$ $ x _ { k } = \ sum _ { n = 0}^{n-1 } x _ { n } cos[\ frac { \ pi } { n }(n+\ frac { 1 } { n })k]$ $

其中$ k = 0,....,N - 1 美元。

libros 简介

让我们把手弄脏吧。Librosa 是一个 Python 库,我们将使用它来浏览我们在过去几节中学习的理论。

sudo apt-get update
sudo apt-get install ffmpeg
pip install librosa

让我们打开一个 mp3 文件。因为它看起来很合适,所以我想试试 Datarock 的歌曲 Fa Fa...来自专辑《数据摇滚》。

import librosa
from matplotlib import pyplot as plt

x, sampling_rate = librosa.load('./Datarock-FaFaFa.mp3')
print('Sampling Rate: ', sampling_rate)
plt.figure(figsize=(14, 5))
plt.plot(x[:sampling_rate * 5])
plt.title('Plot for the first 5 seconds')
plt.xlabel('Frame number')
plt.ylabel('Magnitude')
plt.show()

这就是我们的时域信号。librosa使用的默认采样率是 22050,但是你可以通过任何你喜欢的。

x, sampling_rate = librosa.load('./Datarock-FaFaFa.mp3', sr=44100)
print('Sampling Rate: ', sampling_rate)
plt.figure(figsize=(14, 5))
plt.plot(x[:sampling_rate * 5])
plt.title('Plot for the first 5 seconds')
plt.xlabel('Frame number')
plt.ylabel('Magnitude')
plt.show()

在采样比率参数中传递空值会返回以本机采样比率加载的文件。

x, sampling_rate = librosa.load('./Datarock-FaFaFa.mp3', sr=None)
print('Sampling Rate: ', sampling_rate)

这给了我:

Sampling Rate: 44100

librosa模块中提供了一个绘图功能librosa.display 也是。

import librosa.display

plt.figure(figsize=(14, 5))
librosa.display.waveplot(x[:5*sampling_rate], sr=sampling_rate)
plt.show()

我不知道为什么librosa.display能够捕捉到某些超过 3.5 秒的轻微波动,而matplotlib图却没有捕捉到。

librosa也有一堆例子音频文件,可以用于实验。您可以使用以下命令查看该列表。

librosa.util.list_examples()

输出:

AVAILABLE EXAMPLES
--------------------------------------------------------------------
brahms    	Brahms - Hungarian Dance #5
choice    	Admiral Bob - Choice (drum+bass)
fishin    	Karissa Hobbs - Let's Go Fishin'
nutcracker	Tchaikovsky - Dance of the Sugar Plum Fairy
trumpet   	Mihai Sorohan - Trumpet loop
vibeace   	Kevin MacLeod - Vibe Ace

您可以使用 iPython 显示器在 Jupyter 笔记本上播放音频文件,如下所示:

import IPython.display as ipd

example_name = 'nutcracker' 
audio_path = librosa.ex(example_name)
ipd.Audio(audio_path, rate=sampling_rate)

您可以提取音频样本的采样速率和持续时间,如下所示。

x, sampling_rate = librosa.load(audio_path, sr=None)
sampling_rate = librosa.get_samplerate(audio_path)
print('sampling rate: ', sampling_rate)
duration = librosa.get_duration(x)
print('duration: ', duration)

输出是:

sampling rate:  22050
duration:  119.87591836734694

绘制基于 STFT 的谱图可以如下进行:

from matplotlib import pyplot as plt

S = librosa.stft(x)
fig = plt.figure(figsize=(12,9))
plt.title('STFT Spectrogram (Linear scale)')
plt.xlabel('Frame number')
plt.ylabel('Frequency (Hz)')
plt.pcolormesh(np.abs(S))
plt.savefig('stft-plt.png')

您也可以使用librosa功能绘制光谱图。

fig, ax = plt.subplots(figsize=(15,9))
img = librosa.display.specshow(S, x_axis='time',
                         y_axis='linear', sr=sampling_rate,
                         fmax=8000, ax=ax)
fig.colorbar(img, ax=ax, format='%+2.0f dB')
ax.set(title='STFT linear scale spectrogram')
plt.savefig('stft-librosa-linear.png')

光谱特征

为了开始理解声音信号中的频率是如何变化的,我们可以从查看音频剪辑的频谱质心开始。这些表示光谱的质量中心的位置。感觉上,它与声音的亮度的印象有着紧密的联系。

plt.plot(librosa.feature.spectral_centroid(x, sr=sampling_rate)[0])
plt.xlabel('Frame number')
plt.ylabel('frequency (Hz)')
plt.title('Spectral centroids')
plt.show()

你也可以将质心与声音随时间变化的频谱带宽进行比较。光谱带宽计算如下:

\[ (\sum_k S[k,t] * (freq[k,t]-centroid[t])^{p})^{\frac{1}{p}} $ $ 其中$ k $是频率仓索引,$ t $是时间索引,$ S [k,t] $是频率仓$ k $和时间$ t $处的 STFT 幅度,$ freq[k,t] $是频率仓$ k $和时间$ t $处的频率,$ centroid $是时间$ t $处的频谱质心,最后$ p $是提高频谱质心偏差的功率。`librosa`的$ p $的默认值是$ 2 $。 ```py spec_bw = librosa.feature.spectral_bandwidth(x, sr=sampling_rate) plt.xlabel('Frame number') plt.ylabel('frequency (Hz)') plt.title('Spectral bandwidth') plt.show() ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/a46d3cae3a23134bd24fd6177615e4a1.png) 您也可以通过运行以下代码来可视化质心的偏差: ```py times = librosa.times_like(spec_bw) centroid = librosa.feature.spectral_centroid(S=np.abs(S)) fig, ax = plt.subplots(figsize=(15,9)) img = librosa.display.specshow(S_dB, x_axis='time', y_axis='log', sr=sampling_rate, fmax=8000, ax=ax) fig.colorbar(img, ax=ax, format='%+2.0f dB') ax.set(title='Spectral centroid plus/minus spectral bandwidth') ax.fill_between(times, centroid[0] - spec_bw[0], centroid[0] + spec_bw[0], alpha=0.5, label='Centroid +- bandwidth') ax.plot(times, centroid[0], label='Spectral centroid', color='w') ax.legend(loc='lower right') plt.savefig('centroid-vs-bw-librosa.png') ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/04f80292c6ebe6bd8353f371d18e89ef.png) 我们也可以看看光谱对比。**光谱对比度**定义为光谱中波峰和波谷之间的水*差。谱图$S$的每一帧都被分成子带。对于每个子带,通过比较顶部四分位数的*均能量(峰值能量)和底部四分位数的*均能量(谷值能量)来估计能量对比。能量取决于功率谱图和窗口函数及大小。 ```py contrast = librosa.feature.spectral_contrast(S=np.abs(S), sr=sampling_rate) ``` 绘制对比度图以显示频带: ```py fig, ax = plt.subplots(figsize=(15,9)) img2 = librosa.display.specshow(contrast, x_axis='time', ax=ax) fig.colorbar(img, ax=ax, format='%+2.0f dB') ax.set(ylabel='Frequency bands', title='Spectral contrast') plt.savefig('spectral-contrast-librosa.png') ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/23a4e52b09c20f904821c859acccd88b.png) 还有更多光谱特征。你可以在这里阅读更多关于他们的信息。 ## 理解光谱图 线性标度谱图不能非常清晰地捕捉信息。有更好的方式来表达这些信息。`librosa`允许我们在对数标度上绘制光谱图。为此,请将上面的代码更改为: ```py S = librosa.stft(x) fig, ax = plt.subplots(figsize=(15,9)) img = librosa.display.specshow(S, x_axis='time', y_axis='log', sr=sampling_rate, fmax=8000, ax=ax) fig.colorbar(img, ax=ax, format='%+2.0f dB') ax.set(title='STFT log scale spectrogram') plt.savefig('stft-librosa-log.png') ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/9bc599bef0e7cf1ab88ce65954866e0f.png) 直接利用 STFT 矩阵来绘图并不能给我们明确的信息。通常的做法是通过对矩阵求*方来将幅度谱图转换成功率谱图。接下来,将频谱图中的功率转换为相对于参考功率的分贝数,可以提高数据的可视性。 分贝的计算公式如下: $ $ A = 10 * log _ { 10 }(\ frac { P _ { 2 } } { P _ { 1 } })$ $ 其中$ P_{1} $是参考功率,$ P_{2} $是测量值。 API 中有两个函数允许我们进行这些计算。`librosa.core.power_to_db`进行上面提到的计算。函数`librosa.core.amplitude_to_db`也处理声谱图从振幅到功率的转换,在转换成分贝之前对所述声谱图求*方。在此转换后绘制 STFTs,我们可以得到以下图形。 ```py S_dB = librosa.amplitude_to_db(S, ref=np.max) fig, ax = plt.subplots(figsize=(15,9)) img = librosa.display.specshow(S_dB, x_axis='time', y_axis='linear', sr=sampling_rate, fmax=8000, ax=ax) fig.colorbar(img, ax=ax, format='%+2.0f dB') ax.set(title='STFT (amplitude to DB scaled) linear scale spectrogram') plt.savefig('stft-librosa-linear-db.png') ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/c958ee2ab7634b635b0e0e469b2601f2.png) 在对数标度中: ```py S_dB = librosa.amplitude_to_db(S, ref=np.max) fig, ax = plt.subplots(figsize=(15,9)) img = librosa.display.specshow(S_dB, x_axis='time', y_axis='log', sr=sampling_rate, fmax=8000, ax=ax) fig.colorbar(img, ax=ax, format='%+2.0f dB') ax.set(title='STFT (amplitude to DB scaled) log scale spectrogram') plt.savefig('stft-librosa-log-db.png') ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/340eb6c27c7e1e63133839e7d3891512.png) 从上面可以看出,与我们的初始图相比,频率信息要清晰得多。 正如我们之前讨论的,人类的声音感知不是线性的,我们能够区分低频比高频好得多。这是由 mel 标尺捕捉的。`librosa.display.specshow`还提供了 mel 比例绘制功能。 ```py fig, ax = plt.subplots(figsize=(15,9)) img = librosa.display.specshow(S_dB, x_axis='time', y_axis='mel', sr=sampling_rate, fmax=8000, ax=ax) fig.colorbar(img, ax=ax, format='%+2.0f dB') ax.set(title='Mel scaled STFT spectrogram') plt.savefig('stft-librosa-mel.png') ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ea12fabbcfccdf32233fbf3b333e21c0.png) 这与梅尔光谱图不同。正如我们之前所了解的,mel 谱图是通过将功率谱图乘以 mel 滤波器计算出来的。 也可以使用 librosa 来生成 mel 过滤器。 ```py n_fft = 2048 # number of FFT components mel_basis = librosa.filters.mel(sampling_rate, n_fft) ``` 使用以下过滤器计算 mel 光谱图: ```py mel_spectrogram = librosa.core.power_to_db(mel_basis.dot(S**2)) ``` 在它的 API 中有一个可以直接使用的 mel spectrograms 的包装器。它将时域波形作为输入,并给出 mel 频谱图。它可以按如下方式实现: ```py mel_spectrogram = librosa.power_to_db(librosa.feature.melspectrogram(x, sr=sampling_rate)) ``` 对于绘制 mel 光谱图: ```py fig, ax = plt.subplots(figsize=(15,9)) img = librosa.display.specshow(mel_spectrogram, x_axis='time', y_axis='mel', sr=sampling_rate, fmax=8000, ax=ax) fig.colorbar(img, ax=ax, format='%+2.0f dB') ax.set(title='Mel-frequency (power to DB scaled) spectrogram') plt.savefig('mel-spec-librosa-db.png') ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/f2fa1793a81047c49730931b0b64e3bd.png) 为了计算 MFCCs,我们采用离散余弦变换。 ```py import scipy mfcc = scipy.fftpack.dct(mel_spectrogram, axis=0) ``` 再次为 MFCC 实现了一个包装器,它可以用来获得 MFCC 数组和绘图。 ```py mfcc = librosa.core.power_to_db(librosa.feature.mfcc(x, sr=sampling_rate)) fig, ax = plt.subplots(figsize=(15,9)) img = librosa.display.specshow(mfcc, x_axis='time', y_axis='mel', sr=sampling_rate, fmax=8000, ax=ax) fig.colorbar(img, ax=ax, format='%+2.0f dB') ax.set(title='MFCCs') plt.savefig('mfcc-librosa-db.png') ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/096b8af5350a283cdbc2efb2b66e2e44.png) [通常为](https://opensource.com/article/19/9/audio-processing-machine-learning-python),从梅尔倒谱中提取的前 13 个系数称为 MFCCs。这些包含关于音频的非常有用的信息,通常用于训练机器学习模型。 ## 结论 在本文中,我们学习了音频信号、时域和频域、傅立叶变换和 STFTs。我们学习了梅尔标度和倒谱,或梅尔光谱图。我们还了解了几个光谱特征,如光谱质心、带宽和对比度。 在这个两部分系列的下一部分,我们将研究音高、八度音阶、和弦、色度表示、节拍和速度特征、开始检测、时间分段和声谱图分解。 我希望这篇文章对你有用。 # 几何深度学习简介 > 原文:<https://blog.paperspace.com/introduction-to-geometric-deep-learning/> 计算机视觉的最新进展主要来自于新的深度学习方法,即依赖大量数据对特定任务进行训练的分层机器学习模型。由此带来的性能改进的幅度和速度引发了对其他科学领域类似应用的淘金热。 在许多其他研究领域中,这些发展催生了几何深度学习领域(GDL)。我们将解释 GDL 的“几何”代表什么,同时在关系归纳偏差的背景下解释它,这是一个由 DeepMind 的研究人员在该领域创造的统计推理术语。 计算机科学中的一些主题令人兴奋,但只有很小范围的有用任务可以用它们来执行。GDL 不在其中;我们将深入研究它擅长的许多任务中的一些。 这些部分如下: * 介绍 * 几何深度学习 * 统计推理 * 有趣的用例 * 图形分割 * 图形分类 * 真实用例 * 结论 ## 介绍 在过去十年中,机器和深度学习领域取得了重大进展,这在很大程度上归功于快速增长的计算能力和可用数据,以及 80 年代和 90 年代开发的算法的新应用(例如反向传播和 LSTMs)。这一发展的最大受益者之一是表征学习领域,它被放在监督学习的子领域中。表示学习,通常被称为特征学习,是机器学习(ML)的一个子领域,涉及在没有人工干预的情况下为任务找到最佳数据表示的算法。在许多应用程序中,它是特征工程的直接替代,特征工程是与开发特征和描述符有关的领域,以最好地执行其他 ML 任务。 一个突出的例子是深度卷积神经网络(CNN)用于图像分类和对象检测等任务,在基准测试中实现了比传统算法高得多的性能。在深度 CNN 出现之前,这个过程通常包括两个阶段。首先从图像中提取手工制作的特征,然后根据这些特征执行特定的任务。大多数成功的传统算法依赖于这种模式的变体,执行这些步骤中的一个或两个。随着深度 CNN 的出现,出现了向端到端(E2E)学习的转变,这意味着学习被分析数据的底层表示是以完全数据驱动的方式完成的,即,没有专家输入或操纵。 这种方法第一次(成功地)在更广泛的背景下展示是由 Krizhevsky 等人(T1)完成的。在 2012 年的 ImageNet 竞赛中,他们在功能工程方面大大超过了最先进的(SOTA)[6]。这在下面的图表中很明显,从“SIFT + FVs”和“AlexNet”之间的性能跳跃中可以看出。此外,之后的所有方法都建立在这一创新的基础上,以进一步提高基准测试的性能。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/34aa9751b3290b48e2064558974e7fc7.png) Figure 1\. ImageNet Classification Challenge / [PapersWithCode](https://paperswithcode.com/sota/image-classification-on-imagenet) 深度学习中的“深度”是指神经网络中采用的连续层的数量。关于 E2E 学习的有效性,有趣的是观察各层在每个阶段学到了什么。对于应用于图像的 CNN,网络以 E2E 方式学习的数据表示依赖于与专家所做的相似的方面:通过对浅层中更高层次的特征(边缘、拐角)和深层中更具体的组成特征(结构)做出反应。使用深度学习(DL)的首次突破也激发了 ML 的其他子领域尝试利用这些关于表征学习的新知识。使用神经网络进行价值函数逼*的深度强化学习是许多例子中的一个。 在下一节中,我们将看看一个最*蓬勃发展的研究领域,它有着相似的起源,也是本文的主题。 ### 几何深度学习 布朗斯坦等人在其 2017 年的文章“ [**几何深度学习**:超越欧几里德数据](https://scholar.google.com/scholar_url?url=https://ieeexplore.ieee.org/abstract/document/7974879/&hl=en&sa=T&oi=gsb&ct=res&cd=0&d=16083123238264716705&ei=GXlqXrGmLYWtmwHg-qrIDw&scisig=AAGBfm1gRjBDiV1FG-cQyo_FQAsLM-BWNQ)【5】中首次引入了几何深度学习(GDL)这个术语。 这个标题很能说明问题;GDL 定义了新兴的研究领域,在非欧几里德数据上使用深度学习。非欧数据到底是什么?排除法的一个解释是,对于非欧几里德数据,两点**之间的最短有效路径不是**它们之间的欧几里德距离。我们将使用一个网格,或者一个在计算机图形领域非常普遍的图形特殊化来形象化这一点。在下图中,我们可以看到将经典的斯坦福兔子表示为网格(非欧几里得)或类似网格的体积(欧几里得)之间的区别,在这种情况下,通过离散化的体素。A 点和 B 点之间的欧几里德距离是它们之间最短的直线路径的长度,在图像上显示为蓝线。两点之间的示例性测地线距离将更类似于绿线的长度。测地距离是高维最短路径概念的推广,而图的测地距离定义通常是节点之间的最短路径。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/0c1c3ae8deaf5f9c1156142a489632e1.png) Euclidean and Geodesic distances; blue and green respectively. 以非欧几里得方式解释网格的优点是测地线距离对于在网格上执行的任务更有意义。可以这样想:在深度 CNN 中,我们依赖可能彼此相关的相邻像素。为了在图上再现类似的设置,我们需要考虑“接*度”的重新表述。 当然,我们可以将固有的非欧几里德数据转换成欧几里德数据,但这包括效率和性能损失的高成本。斯坦福大学的 ShapeNet 数据集在零件分类和分割方面的进展证明了这一点。在 Chang 等人提出的基准上获得良好结果的第一个神经网络依赖于网格的体积表示和深度信念网络来处理它们。[4] 这种方法的一个大问题是如何用离散化来权衡运行时效率,因为问题的规模是立方的。此外,在 3D 体素上使用卷积带来了在空的 3D 空间上执行计算的显著开销的问题。由于许多不同的对象都表示在同一个体素空间中,因此没有简单的方法来防止这些空洞的计算发生。 当前的 SOTA 方法直接在网格结构上执行上述任务,或者将它们转换成点云,实现了远为优越的性能和运行时间。[3][9] 不要担心,在这篇文章的剩余部分,你不需要[图论](https://en.wikipedia.org/wiki/Graph_theory)的知识,但是你应该仔细阅读,以便能够使用我们很快就会看到的[软件库](https://blog.paperspace.com/geometric-deep-learning-framework-comparison/)。为了更好地介绍图形和基础理论,你需要了解 GDL 的基本概念,你可以参考[瓦伊德希·乔希](https://medium.com/basecs/a-gentle-introduction-to-graph-theory-77969829ead8)的《【】图论简介》。要深入了解理解该领域中开发的算法所需的进一步理论,请参考吴等人的调查论文“[关于图形神经网络的全面调查](https://scholar.google.com/scholar_url?url=https://arxiv.org/abs/1901.00596&hl=en&sa=T&oi=gsb&ct=res&cd=0&d=10891000151054490378&ei=EnhqXsWNKaawmgHg6buAAg&scisig=AAGBfm0QsxkbBpCfZZ9llLilC6K87W1ZTw)”。此外,他们的调查论文中介绍的分类法可以帮助突出与深度学习中其他领域的相似之处。 当然,最好的情况是,您已经根据自己掌握的数据识别出可能的用例,或者反过来;基于 GDL 解决您当前问题所需的数据。 我们提到了 GDL 属于非欧几里得数据,并且还提出了一些例子和定义。值得注意的是,在本文中,我们不会涵盖点云,点云有其自身的优势,但在我们可以做出的假设方面与图形和网格有很大不同。 ### 统计推理 总的来说,我们想要区分演绎推理和归纳推理。在演绎推理中,我们试图使用一般术语来提出具体的主张或得出特定的结论。这方面的一个例子是将“所有的人都会死”和“约翰·多伊是一个人”这两个断言结合起来,得出以下结论:“约翰·多伊必须死”(最终)。归纳推理则反其道而行之,试图从具体的术语中推断出一般的想法或结论。我们将用一个当代的例子来形象化这个推理,这个例子来自德国一项关于青少年与自然疏离程度的研究。给读者一个快速测试:什么类型的奶牛只产 UHT(长寿)奶? ![cow in styria austria](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/de3308c98777edae8dea30bee21a87f1.png) Photo by [Screenroad](https://unsplash.com/@screenroad?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) / [Unsplash](https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) 如果你的答案是“没有”,那么你符合 21%的受访青少年。结果汇总如下: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/92a89cfc671b05dc8f7593ee05ab3808.png) Got Milk? / German 2010 DJV youth report [2] 这里有大量的结果需要分析,但是我们将保持 1%的人认为奶牛产 UHT 奶。我们将从儿童的角度用归纳推理的方式解释这个结论,如下:“UHT 奶是一种特殊类型的奶”,“Milka-cows 是一种特殊品种的牛”,从而得出“Milka-cows 产出 UHT 奶。”问题是根本没有奶牛这种东西。 [Milka 是来自德国的巧克力品牌](https://www.milka.de/)。他们的 CI 的主色调是紫色,相应地,他们的品牌吉祥物是一头带有白色斑点的紫色奶牛。对这些孩子来说,读一本关于“真正的”奶牛的漫画书,或者参观一个农场,就足以改变他们的看法。根据他们掌握的信息,他们得出了错误的结论。[2] 从德国城市儿童的例子中可以看出,归纳推理的结果可以通过学习者接触到的模式或者通过改变学习者对任务的解释来改变。了解 UHT 牛奶是一种工业产品也会有助于演绎推理。不考虑这个信息应该是常识的事实,如果这个问题没有被作为一个开放的答案提出,结果可能会有所不同。在这种情况下,提问的方式可以被视为**感应偏差**。 为了总结这一点,我们先借用汤姆·米切尔的书《机器学习》: > 因此,我们将学习者的归纳偏差定义为一组足以证明其归纳推理为演绎推理的附加假设。 基本上,通过仔细设计我们算法的归纳偏差,我们可以通过归纳推理达到与演绎推理相等的结果。这是可取的,因为演绎推理是我们能做的最好的,被证明是正确的。 巴塔格利亚等人。al 将这个定义扩展到**关系**归纳偏差,归纳偏差“对学习过程中实体间的关系和交互 施加约束”[1]在他们的工作中,他们将当前的神经网络组件与该定义联系起来,并以如下方式进行总结: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/c97ec2d2995b86a36f7ba63edd2cb81e.png) Relational inductive bias by neural network component type / Table transcribed from [1] 上表已经直接提到了深度 CNN 的两个基本属性:局部统计和对空间*移的不变性。此外,通过在深度 CNN 中堆叠卷积层,我们诱导网络学习不同抽象层次上的特征。这种层次构成也是深度 CNN 的第三个主要属性。这意味着,通过顺序组合图层,我们可以获得一个特征层次,从而更好地定量表示受监督的任务。总的来说,这三个方面使得深度 CNN 能够很好地对图像领域进行概括。基本上,对于深度 CNN,我们似乎已经找到了我们选择约束的假设和我们需要与学习算法相适应的参数的最佳点,从而实现了良好的泛化。 > 不出所料,通过梯度下降来拟合参数模型,当你所拟合的已经是解决方案的模板时,效果最好。当然,convnets 是这种情况的一个实例(但这是一种好的方式,因为他们的假设可以推广到所有的视觉数据)。 > > — François Chollet (@fchollet) [June 1, 2019](https://twitter.com/fchollet/status/1134965806851166208?ref_src=twsrc%5Etfw) # MLOps 简介 > 原文:<https://blog.paperspace.com/introduction-to-mlops/> ## 介绍 大多数数据科学家都不是专业的程序员。虽然他们擅长选择或创建最佳模型来解决机器学习问题,但他们不一定具备在生产环境中打包、测试、部署和维护该模型的专业知识。这正是 MLOps 来救援的地方。 MLOps 在数据科学家和生产团队之间架起了一座桥梁。这是一种将 DevOps 与机器学习相结合的实践。本文讨论了 MLOps、为什么需要它、MLOps 的挑战、可用的工具以及 MLOps 管道如何工作。 具体来说,我们将涵盖: * 什么是 MLOps? * 我们为什么需要 MLOps? * MLOPs 和 DevOps 之间的相似之处 * MLOPs 和 DevOps 之间的不同之处 * 什么是 MLOPs 管道? * 机器学习项目为什么会失败? * MLOps:核心原则 * MLOps:最佳实践 * 摘要 我们开始吧! ## 什么是 MLOps? Machine Learning Operations(也称为 MLOps)是一个工具和最佳实践的集合,用于改善团队之间的沟通,并自动化端到端的机器学习生命周期,以提高持续集成和部署效率。这是一个概念,指的是将历史悠久的 DevOps 方法与不断发展的机器学习科学相结合。 MLOps 不仅仅包括模型构建和设计。它包括数据管理、自动化模型开发、代码生成、模型培训和再培训、持续模型开发、部署和模型监控。将 DevOps 思想融入机器学习可以缩短开发周期,改善质量控制,并能够适应不断变化的业务需求。 系统管理员、数据科学团队和其他业务部门相互协作和沟通,以促进对如何创建和维护生产模型的共同理解,这与 DevOps 对软件的理解非常相似。DevOps 是一种经过验证的实践,可以提供快速的开发生命周期,提高开发速度,通过适当的测试提高代码质量,并帮助实现更快的上市时间。 ## 我们为什么需要 MLOps? **长期价值和降低的风险。** MLOps 帮助组织创造长期价值,同时降低与数据科学、机器学习和人工智能计划相关的风险。 **简化流程,改善客户体验。**机器学习可以帮助部署解决方案,通过简化流程、使用数据分析进行决策以及改善客户体验来发现以前未开发的收入流、节省时间并降低资源成本。 自动化和更快的上市时间。 MLOps automation 可加快上市速度,降低运营成本,使企业能够更加敏捷、更具战略性地做出决策。 ## MLOPs 和 DevOps 之间的相似之处 MLOps 和 DevOps 都需要流程自动化、持续集成和持续交付。 它还有助于对 MLOps 和 DevOps 的代码基础进行适当的测试。 此外,软件开发人员和管理基础设施的人员以及其他利益相关者之间应该有充分的协作。 ## MLOps 和 DevOps 之间的不同之处 虽然 MLOps 是从 DevOps 派生出来的,但是两者还是有细微的区别。 在 MLOps 中,数据是开发机器学习模型的必要输入。但是在 DevOps 中,数据是程序的输出,而不是输入。 在 MLOPs 中,必须在生产中不断验证模型,以防新数据随着时间的推移导致性能下降。软件系统在 DevOps 中不变质;仅仅出于健康维护的目的对其进行监控。 模型训练、模型测试和验证等概念都是 MLOps 独有的,与 DevOps 的传统软件领域无关。此外,训练模型往往是计算密集型的,因此(通常)需要使用强大的 GPU。 MLOps 需要持续培训(CT),这是一个自动识别由于模型当前部署版本的性能下降而需要重新培训和重新部署特定模型的场景的过程。 如你所见,尽管它们有相似之处,但你不能只使用 DevOps 工具来运行机器学习项目;有太多特定于机器学习的要求。 ## 什么是 MLOPs 管道? MLOps 管道是为自动设计、部署和管理模型工作流而触发的连续步骤。机器学习 CI/CD 管道由以下较小的管道组成: * 数据管道:这个管道负责执行 ETL,并自动将必要的数据引入模型。 * 环境管道:这个管道保证正确的依赖项总是被加载和可用。 * 训练管道:这个管道负责训练你的模型。根据 MLOps 管道的设计,可能需要数据管道和培训管道来导入数据和依赖项。 * 测试管道:这个管道负责验证训练好的模型。它可能经常利用根据预定义的时间表触发的自动化测试用例。 * 部署管道:该管道用于在预生产或生产环境中部署您的机器学习模型 ### MLOps 的挑战 与其他技术类似,MLOps 也面临着挑战。以下是您在组织中实施 MLOps 时可能面临的一些挑战: * 数据质量:模型的质量与用于训练模型的数据质量成正比。数据质量是构建和训练模型的最关键因素之一,以便这些模型能够产生更好的见解或预测。 * 数据量:随着数据集大小的增长,机器学习模型的准确性会提高,因为更多的数据被用于训练模型。同时,如果底层资源不能适应数据增长,比如增加更多的存储和处理能力,那么模型的有用性就会大打折扣。 * 部署:部署是另一个挑战,因为部署机器学习模型涉及部署模型*和用于训练它的数据*。除此之外,您可能还需要重新训练您的模型,并且验证它们。如果您尝试手动完成,这将是一个耗时的过程。 ### 典型机器学习项目中的各种团队 通常,在运行机器学习项目的组织中会有以下团队: * **数据工程团队:**该团队负责在您组织的各种应用程序中构建数据管道 * **IT 团队:**该团队确保在处理机器学习项目时,IT 安全标准得到正确执行 * **测试团队:**这个团队负责验证数据科学家创建的机器学习模型的准确性 * **运营团队:**运营团队负责保持系统在生产环境中运行,并跟踪每天的进度。 * **数据科学家团队:**这个团队负责创建和训练预测性的机器学习模型。数据科学家团队还需要与所有其他团队合作,以使机器学习项目取得成功。 ## 机器学习项目为什么会失败? 机器学习项目失败的原因有很多。本节将对其中一些进行讨论。 #### 1.缺乏团队协作 为了取得成功,数据科学家必须与多个团队协同工作,而不是孤立工作。缺乏沟通和协作对整个团队的成功是有害的,因此可能是失败的原因。 #### 2.缺乏持续改进 由于模型性能会随着时间的推移而降低,因此您应该在维护周期中升级模型。一个成功的机器学习项目应该根据收到的反馈或数据变化时,作为一个连续的过程进行评估和改进。然而,数据科学家往往无法迅速实现改进。对于每个数据收集周期,他们需要再次构建、测试和部署模型,这需要他们与多个团队联系。 #### 3.缺乏专业知识 缺乏专业知识是机器学习项目失败的另一个原因。你需要专家数据科学家来管理你的机器学习项目。不幸的是,尽管公司一直在拼命招聘专业的数据科学家,但合适的人才却严重短缺。 #### 4.数据的质量和数量 数据的质量和数量是其他问题。通常,机器学习项目使用大型数据集,因为您需要大量数据来进行更好的预测。然而,随着数据量的增长,与之相关的复杂性和挑战也随之增加。此外,数据通常是从多个来源合并而来的,数据可能不同步。这可能会带来另一个挑战,因为“坏”数据不会提供更好的见解。来自不同位置的数据也可能有安全限制,甚至可能是各种格式,如结构化、非结构化、文本、图像等。 ### 你如何防止机器学习项目失败? 虽然没有确保机器学习项目成功的具体指导方针,但这里有一些方法可能有助于防止它们失败。 * 如果你了解机器学习是如何工作的,以及处理机器学习项目与其他类型的项目有何不同,这将会有所帮助。 * 项目的范围应该适当,要有现实的目标、合理的预算和领导的支持。 * 如果你有必要的资源来成功地执行一个机器学习项目,并且团队成员之间有良好的沟通和协作,这将会有所帮助。 * 你的团队应该擅长收集、存储、清理和分析海量数据。 ## MLOps:核心原则 随着机器学习和人工智能在软件产品和服务中的使用量激增,每个人都应该小心遵循 MLOps 最佳实践,并使用正确的工具来实时测试、部署、管理和监控机器学习模型。您可以利用 MLOps 来逃避您的机器学习应用程序中的“技术债务”。 ### 自动化 如果你想在你的机器学习项目中有效地使用 MLOps,自动化应该是你的优先选择。你的机器学习过程的成熟度取决于你的自动化程度。这反过来可以提高开发、训练和部署机器学习模型的速度。此外,这一原则促进了完全自动化的 ML 工作流的使用,无需人工参与。 ### 版本控制 版本化旨在将机器学习训练脚本、模型和用于模型训练的数据集视为 DevOps 流程中的一等公民,并确保正确跟踪数据集和代码库中的任何更改。版本控制系统保存并跟踪模型的不同版本。这使得在需要时可以轻松无缝地恢复到以前的版本。 ### 测试 测试是 MLOps 生命周期的一个重要方面。MLOps 支持基于数据管道、ML 模型管道和应用程序管道的机器学习系统的结构化测试技术。因此,您应该准备好测试功能和数据、模型开发以及机器学习基础设施的方法。 ### 监视 一旦成功部署了机器学习模型,就应该对其进行监控,以了解它是否按预期执行。此外,依赖关系、数据版本、使用以及对模型所做的更改都会被不时地监控。 ## MLOps:最佳实践 ### 沟通和协作 虽然数据科学家指导应该如何构建模型,但您需要一个工程师和战略家团队来取得成功。您应该为您的团队雇佣主题专家(也称为 SME)、数据科学家、软件工程师和业务分析师。因此,保持适当的沟通和协作是非常必要的。 ### 验证数据集 数据验证是您应该采用的最重要的实践之一。一旦模型投入生产,性能可能会降低,并且您可能无法获得正确的预测。这就是为什么您应该重新培训模型,即使这在时间和资源上都是一件昂贵的事情。 ### 建立明确的商业目标 应该恰当地定义业务目标,并且你应该心中有一个明确的目标。你应该知道你试图用机器学习解决的商业问题,并确定如何解决它。最重要的是,您应该知道您的模型为组织提供了什么价值。 ### 集装箱化 容器化是指程序在称为容器的独立用户空间中执行,所有这些空间都使用标准的操作系统。最好是利用集装箱化来自动化整个过程,从模型开发开始,到生产阶段。你可以用 [Docker](https://www.docker.com/) 来做这个。 ## 摘要 MLOps 包括一系列成熟的技术,用于自动化机器学习生命周期,以消除模型设计、开发和操作之间的差异。机器学习计划不再关注纯数据科学。相反,数据和云工程技能对于管理整个机器学习生命周期变得越来越重要。这就是 MLOps 的用武之地。 # 朴素贝叶斯简介:一种基于概率的分类算法 > 原文:<https://blog.paperspace.com/introduction-to-naive-bayes/> 想象一下:电力运营商希望根据各个工厂过去的电力消耗趋势,向它们提供特定单位的电流。为了简化流程,她/他计划将工厂划分为三组——低、中、高用电客户——根据这三组,他/她知道要供应多少电力。这种类型的问题通常属于预测分类建模,或者简称为分类类型问题。朴素贝叶斯是最简单的机器学习算法之一,一直是分类数据的最爱。 朴素贝叶斯基于贝叶斯定理,该定理是由托马斯·贝叶斯牧师在 18 世纪 60 年代提出的。在过去十年中,朴素贝叶斯的受欢迎程度飙升,该算法被广泛用于解决学术界、政府和商业领域的问题。朴素贝叶斯分类器是实用机器学习中许多理想品质的融合。我们将进一步阐明这背后的直觉。让我们首先理解朴素贝叶斯算法的工作原理,然后使用 scikit-learn 库在 Python 中实现它。 在本文中,我们将了解以下主题: * 朴素贝叶斯算法简介 * 条件概率和贝叶斯定理 * 朴素贝叶斯算法的工作原理 * 朴素贝叶斯的应用 * 用 Scikit-Learn 实现朴素贝叶斯 * 利弊 * 摘要 ## 朴素贝叶斯算法简介 朴素贝叶斯属于主要用于分类的监督机器学习算法的范畴。在这种情况下,“受监督的”告诉我们,算法是用输入特征和分类输出(即,数据包括算法应该预测的每个点的正确期望输出)来训练的。 但为什么算法会被称为“幼稚”?这是因为分类器假设进入模型的输入要素相互独立。因此,更改一个输入要素不会影响任何其他要素。这个假设可能是真的,也可能是假的,从这个意义上来说,它是天真的,而且很可能不是。 我们将在朴素贝叶斯算法的*工作部分详细讨论这个算法的幼稚性。在此之前,让我们简单地看一下为什么这个算法简单,但功能强大,并且易于实现。朴素贝叶斯的一个显著优点是它使用了一种概率方法;所有的计算都是实时进行的,输出也是即时生成的。当处理大量数据时,这使得朴素贝叶斯比传统的分类算法如[支持向量机](https://blog.paperspace.com/implementing-support-vector-machine-in-python-using-sklearn/)和[集成技术](https://blog.paperspace.com/bagging-ensemble-methods/)更胜一筹。* 让我们从掌握理解朴素贝叶斯的基本理论开始。 ## 概率、条件概率和贝叶斯定理 概率是朴素贝叶斯的基础。让我们认真研究一下概率的本质。 ### 概率是什么? 概率是数学的一个重要分支,它帮助我们预测一个事件 *X* 在考虑所有潜在结果的情况下发生的可能性。为了更精确地解释这一点,考虑一个预测你是否会在某一天上大学的例子。这里有两种可能的结果:参加或跳过。因此,你上大学或不上大学的概率是。数学上,概率可以用下面的等式表示: **事件的概率=有利事件的数量/结果总数** **0 < =事件发生的概率< = 1** “有利事件”表示您希望其发生概率的事件。概率总是在 0 到 1 的范围内,0 表示该事件不可能发生,1 表示有 100%的可能性会发生。 条件概率是概率的子集,它限制了概率的思想,使之对特定事件产生依赖;下一节我们来了解一下。 ### 条件概率 为两个或更多事件计算条件概率。拿两个事件来说, *A* 和 *B* 。事件 *B* 的**条件概率**被定义为在已知事件 *A* 已经发生的情况下,事件 *B* 将发生的概率。它被表示为 P(B|A ),在数学上由以下公式表示: **P(B|A) = P(A 和 B)/P(A)** 让我们看一个例子来清楚地理解这个概念。 假设你有两个篮子,篮子 A 和篮子 B,里面装满了苹果和芒果。组成细节如下所示: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/c6c3b8fe825fabf9fe22766bf7b4fc06.png) 为了找到随机选择的水果是从篮子 B 中取出的苹果的条件概率,我们使用上面的公式如下: ```py P(Apple/Basket-B) = P(Apple and Basket-B)/ P(Basket-B) = 25/50 ``` 这个概率反映了首先选择 Basket-B,然后从中挑选一个苹果。 在下一节中,我们将研究贝叶斯定理,它跟随了条件概率的脚步。 ### 贝叶斯规则 贝叶斯规则围绕着从给定的证据(E)推导出一个假设(H)的概念。它涉及两个概念:得到证据之前假设的概率 P(H)和得到证据之后假设的概率 P(H|E)。一般来说,它由以下等式给出: **P(H | E)=(P(E | H)* P(H))/P(E)** 这告诉我们: * 假设 E 发生,H 多久发生一次*,写成 **P(H|E)*** 当我们知道: * 假设 H 发生,E 多久发生一次*,写作 **P(E|H)*** * A 自身的可能性有多大,写成 **P(H)** * B 自身的可能性有多大,写成 **P(E)** 贝叶斯规则是一种从 P(E|H)到找到 P(H|E)的方法。简单地说,它提供了一种方法来计算给定证据的假设的概率。 ### 机器学习视角下的贝叶斯规则 我们通常用训练数据来训练模型,用验证数据来评估模型并做出新的预测。让我们将输入特征称为证据,将标签称为训练数据中的结果。使用条件概率,我们计算给出结果的证据的概率,表示为 P(证据|结果)。我们现在的目标是找到结果相对于证据的概率,记为 P(Outcome|Evidence)。让我们为 P(证据|结果)和 P(结果|证据)定义贝叶斯规则。 考虑用 *X* 表示证据,用 *Y* 表示结果。 因此,P(证据|结果)是 P(X|Y ),表示如下: **P(X | Y)=(P(Y | X)* P(X))/P(Y)*****(从训练数据中估计。)*** **P(结果|证据)为 P(Y|X),表示如下:** ****P(Y | X)=(P(X | Y)* P(Y))/P(X)*****(由测试数据预测。)***** ****如果手头的问题有两个结果,那么我们计算每个结果的概率,说最高的那个赢。但是如果我们有多个输入特征呢?这就是朴素贝叶斯出现的原因;让我们在下一节讨论这个算法。**** ## ****朴素贝叶斯算法的工作原理**** ****贝叶斯规则提供了在给定输入(X)的情况下计算输出(Y)概率的公式。在现实世界的问题中,与具有单一输入要素的假设不同,我们有多个 *X* 变量。当我们可以假设这些特征彼此独立时,我们将贝叶斯规则扩展到所谓的朴素贝叶斯。**** ****考虑有多个输入(X1,X2,X3,...Xn)。我们使用朴素贝叶斯方程预测结果(Y ),如下所示:**** ******P(Y=k | X1...xn)=(P(X1 | Y = k)* P(X2 | Y = k)* P(X3 | Y = k)*....* P(Xn | Y = k))* P(Y = k)/P(X1)* P(X2)* P(X3)* P(Xn)****** ****在上面的公式中:**** * ******P(Y=k | X1...Xn)** 被称为**后验概率**,它是给定证据的结果的概率。**** * ******P(X1 | Y=k) * P(X2 | Y=k)** *... **P(Xn | Y=k)** 是证据的可能性的概率。**** * ******P(Y=k)** 为先验概率。**** * ******P(X1)*P(X2)*P(Xn)** 是证据的概率。**** ****一个清晰的例子可以让你深入了解上面的等式是如何付诸实践的。让我们考虑一个由 10 个数据样本组成的简单数据集:**** ****![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/232081cb01f16c031968f39587d735f5.png)**** ****给定三个输入——例如,单亲、年轻和低——我们想计算这些人买车的概率。让我们用朴素贝叶斯。**** ****首先,让我们计算给定数据的输出标签的概率(P(Y))。**** ******P(否)= 4/10****** ******P(是)= 6/10****** ****现在让我们来计算证据可能性的概率。给定输入“无子女”、“年轻”和“低”,我们将计算两个分类标签的概率,如下所示:**** ****P(单亲|是)= 1/6 P(单亲|否)= 1/4 P(年轻|是)= 2/6 P(年轻|否)= 1/4 P(低|是)= 1/6 P(低|否)= 4/4**** ****自 P(X1) * P(X2) *...* P(Xn)保持不变当计算 Yes 和 No 输出标签的概率时,我们可以消除该值。 因此,后验概率计算如下(注意 *X* 是测试数据): P(是|X) = P(单亲|是)* P(年轻|是)* P(低|是)= 1/6 * 2/6 * 1/6 = 0.0063 P(No|X) = P(单亲|No) * P(年轻|No) * P(低|No) = 1/4 * 1/4 * 4/4 = 0.0625 最终概率是: P(Yes | X)= 0.0063/(0.0063+0.0625)= 0.09 P(No | X)= 0.0625/(0.0063+0.0625)= 0.91 因此,结果清楚地表明,汽车可能不会被购买。 我们之前提到过,该算法的“幼稚”之处在于它假设每个特征都是相互独立的。我们根据这一假设计算了输出标签的概率,因此每个特征都具有相同的贡献,并且独立于所有其他特征。这的确是天真的假设。 ## 应用程序 下面是一些使用朴素贝叶斯的用例: * **实时预测:**朴素贝叶斯是一个热切学习的分类器,执行起来相当快。因此,它可以用于实时预测。 * **多类预测:**朴素贝叶斯算法也以多类预测而闻名,或者将实例分类为几个不同类中的一个。 * **文本分类/垃圾邮件过滤/情感分析:**当用于文本分类时,朴素贝叶斯分类器通常比其他算法获得更高的成功率,这是因为它能够在假设独立性的同时在多类问题上表现良好。因此,它被广泛用于垃圾邮件过滤(识别垃圾邮件)和情感分析(例如,在社交媒体中,识别积极和消极的客户情感)。 * **推荐系统:**朴素贝叶斯分类器可以和协同过滤一起使用来构建推荐系统,该系统可以过滤新信息并预测用户是否喜欢给定的资源。 ## 用 Scikit-Learn 实现朴素贝叶斯 现在我们将使用 scikit-learn 库来构建一个朴素贝叶斯分类器。 第一步:让我们使用一个只有三列的玩具数据集:天气、温度和玩耍。前两个是特征(天气和温度),第三个是目标标签(孩子是否出去玩)。 ```py # Assigning features and label variables weather = ['Sunny','Sunny','Overcast','Rainy','Rainy','Rainy','Overcast','Sunny','Sunny','Rainy','Sunny','Overcast','Overcast','Rainy'] temp = ['Hot','Hot','Hot','Mild','Cool','Cool','Cool','Mild','Cool','Mild','Mild','Mild','Hot','Mild'] play = ['No','No','Yes','Yes','Yes','No','Yes','No','Yes','Yes','Yes','Yes','Yes','No'] ``` 第二步:我们需要将字符串标签(如玩具数据集中所示)转换成数字。例如,我们可以将“阴天”、“雨天”和“晴天”分别映射到 0、1 和 2。这就是所谓的**标签编码**。Scikit-learn 提供了 **LabelEncoder** 库,用于将数据项编码为介于 0 和比离散类的数量小 1 之间的值(在我们的例子中,天气和温度都是 2,游戏是 1)。现在,让我们将天气列编码如下: ```py # Import LabelEncoder from sklearn import preprocessing # Creating labelEncoder le = preprocessing.LabelEncoder() # Converting string labels into numbers. weather_encoded=le.fit_transform(weather) print(weather_encoded) ``` 输出:[2 2 0 1 1 1 0 2 1 2 2 0 0 1] **第三步:**同样,我们也可以对 temp 和 play 列进行编码。 ```py # Converting string labels into numbers temp_encoded = le.fit_transform(temp) label = le.fit_transform(play) print("Temp:",temp_encoded) print("Play:",label) ``` 输出: 温度:[1 1 1 2 0 0 2 0 2 2 2 2 1 2] 播放:[0 0 1 1 1 0 1 0 1 1 1 1 1 0] **步骤 4:** 现在让我们将天气和温度属性按元素合并,即将 1D 特征数组合并成一个 2D 数组。这将使我们构建 sklearn 模型的任务更加容易。我们使用 NumPy 库中的 **vstack** 函数来完成这个任务。 ```py Output: array([[2, 1], [2, 1], [0, 1], [1, 2], [1, 0], [1, 0], [0, 0], [2, 2], [2, 0], [1, 2], [2, 2], [0, 2], [0, 1], [1, 2]], dtype=int64) ``` **步骤 5:** 让我们使用以下步骤生成我们的朴素贝叶斯模型: 1. 通过从 **sklearn.naive_bayes** 导入,使用 **GaussianNB** 创建一个朴素贝叶斯分类器。 2. 使用 **model.fit** 在分类器上拟合数据集。 3. 使用**模型进行预测。** ```py # Import Gaussian Naive Bayes model from sklearn.naive_bayes import GaussianNB # Create a Gaussian Classifier model = GaussianNB() # Train the model using the training sets model.fit(com,label) # Predict Output predicted= model.predict([[0,2]]) # 0:Overcast, 2:Mild print ("Predicted Value:", predicted) ``` 输出:[1] “1”是目标值,或“是”。GaussianNB 是高斯朴素贝叶斯算法,其中假设要素的似然性是高斯的。 ## 朴素贝叶斯的优势 * 朴素贝叶斯易于掌握,并且可以快速预测类别标签。在多类预测上也表现不错。 * 当独立性假设成立时,与其他模型(如逻辑回归)相比,朴素贝叶斯分类器的性能更好,并且需要的训练数据也更少。 * 当输入值是分类值而不是数字值时,它的性能很好。在数字的情况下,假设正态分布来计算概率(钟形曲线,这是一个强假设)。 ## 朴素贝叶斯的缺点 * 如果分类变量在测试数据集中有一个类别,而该类别在训练数据中未被模型观察到,则它将分配 0(零)概率,并且无法进行预测。这就是通常所说的**零频**。为了解决这个问题,我们可以使用*滑技术。最简单的*滑技术之一叫做**拉普拉斯估计**。 * 朴素贝叶斯的另一个限制是特征之间的独立性假设。在现实生活中,我们几乎不可能得到一组完全独立的特征。 ## 摘要 虽然朴素贝叶斯有一些限制,但它仍然是分类数据的首选算法,主要是因为它简单。它在文档分类和垃圾邮件过滤方面表现尤为出色。为了更实际地理解 Naive Bayes,我建议您尝试使用我们在各种其他数据集上实现的内容,以更深入地了解 Naive Bayes 如何对数据进行分析和分类。 ## 参考 1. https://www.analyticsvidhya.com/blog/2017/09/naive-bayes-explained/ 2. https://machinelearningmastery.com/naive-bayes-for-machine-learning/ 3. https://www.datacamp.com/community/tutorials/naive-bayes-scikit-learn 4. [https://becominghuman.ai/naive-bayes-theorem-d8854a41ea08](https://becominghuman.ai/naive-bayes-theorem-d8854a41ea08) 5. [https://brilliant.org/wiki/bayes-theorem/](https://brilliant.org/wiki/bayes-theorem/)**** # 使用 NLTK 的自然语言处理简介 > 原文:<https://blog.paperspace.com/introduction-to-natural-language-processing-using-nltk/> 自然语言处理(NLP)学科的目标是使计算机程序能够理解和利用自然人类语言。为了用 Python 实现 NLP 过程,有一个叫做 NLTK 的工具包。它为我们提供了各种文本处理包和大量的测试数据集。使用 NLTK,可以完成一系列操作,包括标记化和可视化解析树。在本文中,我们将介绍如何在图纸空间渐变中设置 NLTK,并利用它在文本处理阶段执行各种 NLP 操作。然后,我们将借助一些用于情感分析文本分类的 NLTK 工具创建一个 Keras 模型。 ## 什么是自然语言处理? 自然语言处理(NLP)是软件对自然语言的自主操作,包括语音和文本。 语言学、计算机科学和人工智能的这一分支管理计算机和人类语言之间的交互,特别是如何训练计算机处理和解释大量自然语言数据。 在过去的几十年里,自然语言处理领域取得了重大进展。从**符号 NLP** 开始,20 世纪 50 年代到 90 年代初,计算机用它来模拟自然语言理解。然后是**统计 NLP** 时代,从 20 世纪 90 年代持续到 2010 年代,见证了新的统计 NLP 方法的引入,最*强调了机器学习算法。**深度神经网络**技术已经发展到现在广泛应用于自然语言处理的地步。在本教程中,我们将介绍该领域的统计和神经阶段的前沿技术。 虽然人工智能和自然语言处理这两个术语可能会让人联想到尖端机器人的概念,但在我们的日常生活中,已经有简单而基本的自然语言处理实例在工作。这里有几个众所周知的例子: * 垃圾邮件过滤和分类 * 智能助手和家庭自动化中心,如 Alexa、Siri 和 Ok Google * 搜索引擎(谷歌,必应,...) * 文本预测,如自动更正和自动完成 ## 设置 NLTK 创建使用人类语言数据的 Python 程序的最流行的*台是 NLTK。除了用于分类、标记化、词干化、标记、解析和语义推理的文本处理库,它还为 50 多种大型结构化文本(语料库)和词汇资源提供了简单的接口,包括 WordNet,这是一个包含 200 多种语言中单词之间语义关系的词汇数据库。 让我们从设置 NLKT 库以及 Numpy 和 Matplotlib 开始,我们可能还需要这些库来进行额外的处理。 ```py !pip install nltk !pip install numpy !pip install matplotlib ``` 然后,让我们下载一个稍后将在处理示例中使用的语料库: ```py import nltk nltk.download() ``` 使用命令 **d** 我们可以选择下载命令,然后输入数据集的标识符,在我们的例子中,我们输入了 **punkt。** ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ca851bfe37734795394b31d16cd24124.png) Live NLTK dataset download process 我们也可以直接下载数据集,不需要浏览语料库列表。 ```py import nltk nltk.download("punkt") nltk.download("averaged_perceptron_tagger") nltk.download("stopwords") ``` 对于本教程,我们将使用三个主要数据集: * **punkt** :英语预训练分词器 * **averaged _ perceptron _ tagger**:预先训练好的英语词性标注器 * **停用词**:179 个英文停用词的预定义列表,这些停用词对文本的意义没有什么影响,例如“I”、“a”和“the” 然后,让我们定义我们将在本教程中操作的文本。我们将在我们的[主页](https://www.paperspace.com)中使用 Paperspace 介绍性文本。 ```py text = "Paperspace is built for the future where hardware and software are inextricably linked. Our tools provide a seamless abstraction layer that radically simplifies access to accelerated computing. The Paperspace stack removes costly distractions, enabling individuals & organizations to focus on what matters. " ``` 一旦你完成了这个阶段,让我们深入到各种 NLTK 过程。 ## 标记化 标记化是将字符串转换为词汇标记列表的过程,词汇标记是具有指定和识别的含义的字符串。词法分析器、标记器和扫描器是进行词法分析的软件工具的名称;然而,术语“扫描器”也可以指记号赋予器的初始阶段。典型地,记号赋予器和解析器一起使用来研究编程语言、网页和其他类型文档的语法。 因此,在标记化中,我们使用词法分析将大量材料分解成段落、短语和单词。它需要识别和检查单词的结构。 使用 NLTK,我们可以执行两种类型的文本标记化: * 单词标记化:将文本的所有句子分解成单词。 * 句子标记化:如果句子不止一个,我们可以把句子分解成一个句子列表。 **单词标记化** 在对文本进行标记之前,始终将文本转换为小写是一个好习惯。 ```py text = text.lower() word_tokens = nltk.word_tokenize(text) print (word_tokens) ``` 这会产生以下输出: ```py ['paperspace', 'is', 'built', 'for', 'the', 'future', 'where', 'hardware', 'and', 'software', 'are', 'inextricably', 'linked', '.', 'our', 'tools', 'provide', 'a', 'seamless', 'abstraction', 'layer', 'that', 'radically', 'simplifies', 'access', 'to', 'accelerated', 'computing', '.', 'the', 'paperspace', 'stack', 'removes', 'costly', 'distractions', ',', 'enabling', 'individuals', '&', 'organizations', 'to', 'focus', 'on', 'what', 'matters', '.'] ``` **句子标记化** ```py text = text.lower() sentence_tokens = nltk.sent_tokenize(text) print (sentence_tokens) ``` 这会产生以下输出: ```py ['paperspace is built for the future where hardware and software are inextricably linked.', 'our tools provide a seamless abstraction layer that radically simplifies access to accelerated computing.', 'the paperspace stack removes costly distractions, enabling individuals & organizations to focus on what matters.'] ``` ## 停止单词删除 停用词是添加到句子中以增加语言流畅性的词,一般不会添加任何附加信息。因此,在标记化过程之后删除它们是一个很好的做法。 使用下载的停用词集和 NLTK,我们可以清理单词标记化列表: ```py from nltk.corpus import stopwords stopword = stopwords.words('english') word_tokens_cleaned = [word for word in word_tokens if word not in stopword] print (word_tokens_cleaned) ``` 这会产生以下输出: ```py ['paperspace', 'built', 'future', 'hardware', 'software', 'inextricably', 'linked', '.', 'tools', 'provide', 'seamless', 'abstraction', 'layer', 'radically', 'simplifies', 'access', 'accelerated', 'computing', '.', 'paperspace', 'stack', 'removes', 'costly', 'distractions', ',', 'enabling', 'individuals', '&', 'organizations', 'focus', 'matters', '.'] ``` ## 词汇化 词汇化考虑了单词的形态评估。要做到这一点,有一个全面的字典是必不可少的,算法可以使用它来连接单词和它的引理。例如,词汇化是在聊天机器人环境中理解消费者问题的最佳技术之一。聊天机器人能够理解每个单词的上下文形式,因为这种方法的形态分析允许它更好地理解整个短语的整体意思。 语言学是这种方法论的关键。为了获得正确的词条,需要查看每个单词的词法分析。为了提供这种分析,每种语言的词典都是必要的。 下面是 word 的两个例子及其提取的词条。 | 单词 | 引理 | | --- | --- | | 隐藏所 | 隐藏物 | | 研究 | 研究 | 要使用 NTLK 提供的词汇化方法,我们首先需要下载开放多语言 Wordnet (omw)集。这个过程在 linux 环境中应该是明确的,这是像 Paperspace 的 gradient 这样的云机器学习服务的情况。 ```py nltk.download("omw-1.4") nltk.download("wordnet") ``` 然后我们可以对整个文本进行词条解释: ```py from nltk.stem.wordnet import WordNetLemmatizer wordnet_lemmatizer = WordNetLemmatizer() lemmatized_tokens = [wordnet_lemmatizer.lemmatize(token) for token in word_tokens_cleaned] print(lemmatized_tokens) ``` 结果输出如下 ```py ['paperspace', 'built', 'future', 'hardware', 'software', 'inextricably', 'linked', '.', 'tool', 'provide', 'seamless', 'abstraction', 'layer', 'radically', 'simplifies', 'access', 'accelerated', 'computing', '.', 'paperspace', 'stack', 'remove', 'costly', 'distraction', ',', 'enabling', 'individual', '&', 'organization', 'focus', 'matter', '.'] ``` 我们可以注意到主要的变化是像`'tools'`和`'individuals'`这样的复数单词变成了单数`'tool'`和`'individual'`。 但是基本上大多数单词的词条化版本看起来和它的词条没有太大的不同。这是因为这篇文章是以信息的语气写的,没有使用任何奇怪的含义。 ## 堵塞物 词干算法通过从单词的开头或结尾移除单词的部分,同时考虑可以在屈折单词中找到的频繁出现的前缀和后缀的列表来操作。这种策略有很大的局限性,因为尽管这种不加选择的切割在某些情况下是有效的,但并不总是如此。 虽然还有其他词干算法,但在英语中使用最多的是 **Porter stemmer** 。该算法的[规则](https://vijinimallawaarachchi.com/2017/05/09/porter-stemming-algorithm/)分为五个阶段,编号从 1 到 5。这些指南旨在将单词分解成基本成分。 这里有几个词干例子: | 单词 | 阻止 | | --- | --- | | 隐藏所 | 察赫 | | 研究 | 工作室 | NLTK 还提供波特斯特梅尔模块 ```py from nltk.stem.porter import PorterStemmer stemmed_tokens = [PorterStemmer().stem(token) for token in word_tokens_cleaned] print(stemmed_tokens) ``` 当应用于我们的标记化文本时,会产生以下结果: ```py ['paperspac', 'built', 'futur', 'hardwar', 'softwar', 'inextric', 'link', '.', 'tool', 'provid', 'seamless', 'abstract', 'layer', 'radic', 'simplifi', 'access', 'acceler', 'comput', '.', 'paperspac', 'stack', 'remov', 'costli', 'distract', ',', 'enabl', 'individu', '&', 'organ', 'focu', 'matter', '.'] ``` 创建词干分析器比创建词汇分析器容易得多。在后一种情况下,深入的语言学知识对于开发允许算法搜索单词的正确形式的字典是必要的。 ## 词性标注 词性(POS)标签使我们能够识别每个单词的标签,例如它的名词、形容词或其他分类。 ```py pos_tag = nltk.pos_tag(word_tokens_cleaned) print(pos_tag) ``` 使用这种方法,我们可以评估一个词类在所分析的文本中的存在。 ```py [('paperspace', 'NN'), ('built', 'VBN'), ('future', 'JJ'), ('hardware', 'NN'), ('software', 'NN'), ('inextricably', 'RB'), ('linked', 'VBN'), ('.', '.'), ('tools', 'NNS'), ('provide', 'VBP'), ('seamless', 'JJ'), ('abstraction', 'NN'), ('layer', 'NN'), ('radically', 'RB'), ('simplifies', 'VBZ'), ('access', 'NN'), ('accelerated', 'VBD'), ('computing', 'VBG'), ('.', '.'), ('paperspace', 'NN'), ('stack', 'NN'), ('removes', 'VBZ'), ('costly', 'JJ'), ('distractions', 'NNS'), (',', ','), ('enabling', 'VBG'), ('individuals', 'NNS'), ('&', 'CC'), ('organizations', 'NNS'), ('focus', 'VBP'), ('matters', 'NNS'), ('.', '.')] ``` 例如,这种分析对于分析流畅性的编辑工具是有用的。例如,使用词性标注,我们可以检查我们是否在文本中使用了正确数量的副词.. ## 词频分布 词频是自然语言处理中使用的另一种方法。它可以用作诸如情感分析或文本分类等任务的附加特征。你可以通过建立频率分布来确定哪些术语在你的文本中出现得最频繁。 下面是如何使用 NLTK 的 **FreqDist** 模块创建频率分布: ```py from nltk import FreqDist frequency_distribution = FreqDist(lemmatized_tokens) frequency_distribution.most_common(10) ``` 使用这个库,我们提取了测试文本中 10 个最常见的词条: ```py [('.', 3), ('paperspace', 2), ('built', 1), ('future', 1), ('hardware', 1), ('software', 1), ('inextricably', 1), ('linked', 1), ('tool', 1), ('provide', 1)] ``` ## 离差图 DispersionPlot 可视化文本样本中单词的词汇分散。该图使用垂直线描绘了一个或多个搜索短语在整个内容中的出现,记录了与文本开头相比出现了多少个单词。 为了能够使用 NLTK 的 **dispersion_plot** 方法,我们必须确保安装了 *Matplotlib* 。 为了进行测试,我们使用了最新的 Graphcore 和 Paperspace 合作声明[中的文本来发布 IPU 云服务](https://blog.paperspace.com/paperspace-graphcore-partnership/) ```py from nltk.text import Text from nltk.draw import dispersion_plot tokens = nltk.word_tokenize(text.lower()) nltk_text = Text(tokens) words = ["paperspace","graphcore","gradient","performance"] dispersion_plot(nltk_text,words,ignore_case=True) ``` 这导致了下面的色散图 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ff60c4e107134f2212ef69592d92f945.png) Dispersion plot of common keywords in graphcore announcement article 我们可以看到我们的联合创始人丹尼尔·科布兰如何使用关键字 **paperspace** 、**、graphcore** 和 **gradient** ,并确保在文本中均匀分布,以保持读者参与主题。我们还可以看到,最后的 50 个单词包含了三个主要的关键词,这是一个很好的写作锚定技巧。 从这个例子中,我们可以看到这种分析的重要性,尤其是对像一本书或一篇论文这样的长文本。使用频率分布和离差图,我们可以对作者在写作中推动的主要思想有一个基本的概念。 ## 语法树生成 我们还可以定义一组语法规则,然后使用 NLTK **ChartParser** 从句子中提取所有词性。这些语法规则只是以 NLTK 可以理解的方式组织的英语的程序性表述。然后我们可以为给定的句子和规则集生成一个语法树。 ```py sentence = "AI grows at an astounding pace" sentence_tokens = nltk.word_tokenize(sentence) groucho_grammar = nltk.CFG.fromstring(""" S -> NP VP PP -> P NP NP -> Det N | Det Adj N | Det N PP | Det Adj N PP | 'AI' VP -> V NP | VP PP | V Det -> 'an' Adj -> 'astounding' N -> 'pace' V -> 'grows' P -> 'at' """) parser = nltk.ChartParser(groucho_grammar) for tree in parser.parse(sentence_tokens): print(tree) ``` 提取的树如下: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b7607efa6dac52d9cf9ba82f5a06a30c.png) Generated Syntax Tree 对于更复杂的句子,解析器会返回不止一棵树: ***【AI 发展速度惊人影响更广】*** ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/dc3109cccff692b2d47ac7120d4936b8.png) Generated Syntax Tree 语法树生成工具,如离差图和频率分布,对于构建验证给定文本的流畅性和语言水*的程序非常有用。面向初级阅读水*的读者的文本可能具有较少的语法缩进,但是语言知识更丰富的读者可能会接触到更复杂的结构。因此,语法树的生成对于区分两种类型的文本非常有用。 ## 使用 Keras & NLTK 的情感分析 情感分析是一种自然语言处理(NLP)方法,用于评估数据是否具有积极、消极或中性的情感。情感分析经常在文本数据上进行,以帮助组织在消费者反馈中跟踪品牌和产品的情感,并理解客户需求。最*,它也被用于政治领域,以评估选民对政党宣言的反应。使用情感分析的其他分类任务是 twitter 帖子和电影及产品评论情感分类。 在本教程中,我们将使用 Keras 模型以及 **[UCI 情感标签句子数据集](https://archive.ics.uci.edu/ml/datasets/Sentiment+Labelled+Sentences)** ,来评估一些句子的情感。该数据集代表 3000 个句子及其相应的情感标签(1 代表积极情感,0 代表消极情感)。 让我们从下载数据集开始 ```py !curl https://raw.githubusercontent.com/adilmrk/Sentiment-Analysis-using-NLTK-Keras/main/sentiment_labelled_sentences.txt -o sentiment_labelled_sentences.txt ``` 然后,我们将使用 **pandas** 读取数据,使用 NLTK 对所有句子进行标记化,然后进行词条化。之后,我们将数据集分为训练集和测试集,测试集占数据集大小的 25%。 对于每个句子,我们将把它转换成小写,然后对每个句子进行记号化,并使用 **WordNetLemmatizer** 对句子的每个记号进行记号化。在处理分类任务时,已经证明词汇化过程比词干化过程执行得好得多。 最后,修改过的句子集将被矢量化。Python 中的 scikit-learn 模块提供了一个名为**计数矢量器**的优秀工具。它用于根据每个单词在文本中出现的次数,将给定的文本转换为向量。 ```py import pandas as pd from sklearn.model_selection import train_test_split from sklearn.feature_extraction.text import CountVectorizer from nltk.stem.wordnet import WordNetLemmatizer data = pd.read_csv("sentiment_labelled_sentences.txt", names=['sentence', 'label'], sep='\t') sentences = data['sentence'].values y = data['label'].values sentences = [ nltk.word_tokenize(sentence.lower()) for sentence in sentences] wordnet_lemmatizer = WordNetLemmatizer() sentences = [ " ".join([wordnet_lemmatizer.lemmatize(token) for token in sentence_tokens]) for sentence_tokens in sentences] sentences_train, sentences_test, y_train, y_test = train_test_split(sentences, y, test_size=0.25, random_state=1000) vectorizer = CountVectorizer() vectorizer.fit(sentences_train) X_train = vectorizer.transform(sentences_train) X_test = vectorizer.transform(sentences_test) ``` 使用矢量器,我们可以为不同形状的短语创建相同大小的输入。 接下来,我们将使用等于矢量化句子大小的输入维度创建一个 Keras 序列模型。 ```py from keras.models import Sequential from keras import layers input_dim = X_train.shape[1] model = Sequential() model.add(layers.Dense(10, input_dim=input_dim, activation='relu')) model.add(layers.Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) model.summary() ``` 然后,我们将在之前创建的数据集上训练模型 ```py history = model.fit(X_train, y_train, epochs=100, verbose=False, validation_data=(X_test, y_test), batch_size=10) ``` 对于精度和损耗图: ```py import matplotlib.pyplot as plt acc = history.history['accuracy'] val_acc = history.history['val_accuracy'] loss = history.history['loss'] val_loss = history.history['val_loss'] plt.figure(figsize=(24, 12)) plt.subplot(1, 2, 1) plt.plot(range(1, len(acc) + 1), acc, 'b', label='Training acc') plt.plot(range(1, len(acc) + 1), val_acc, 'r', label='Validation acc') plt.title('Training and validation accuracy') plt.legend() plt.subplot(1, 2, 2) plt.plot(range(1, len(acc) + 1), loss, 'b', label='Training loss') plt.plot(range(1, len(acc) + 1), val_loss, 'r', label='Validation loss') plt.title('Training and validation loss') plt.legend() ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/e81670f37edf99dd11256165ba30167f.png) Accuracy and loss through 100 training epochs 使用该模型,我们实现了 76%的情感分析准确率 ```py loss, accuracy = model.evaluate(X_test, y_test, verbose=False) print("Testing Accuracy: {:.4f}".format(accuracy)) ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/432c94a5642c1f898d9bbd93e70bd0f9.png) Testing accuracy result 然后使用这个训练好的模型,我们对一组 3 个新句子进行测试,其中两个明显被认为具有积极的情绪,而第三个具有消极的情绪。 ```py sentences = ["AI is growing at an amazing pace","paperspace is one of the best cloud computing service providers", "Using local GPU AI machines is a bad idea"] sentences = [ nltk.word_tokenize(sentence.lower()) for sentence in sentences] wordnet_lemmatizer = WordNetLemmatizer() sentences = [ " ".join([wordnet_lemmatizer.lemmatize(token) for token in sentence_tokens]) for sentence_tokens in sentences] X_predict = vectorizer.transform(sentences) predictions = model.predict(X_predict) predictions = [ (y[0] > 0.5) for y in predictions] for i in range(0,len(sentences)): if predictions[i]: print("Positive sentiment : " + sentences[i]) else: print("Negative sentiment : " + sentences[i]) ``` 3 个测试句子的情感分析结果如下 | 句子 | 感情 | | --- | --- | | 人工智能正以惊人的速度发展 | 积极的 | | paperspace 是最好的云计算服务提供商之一 | 积极的 | | 用本地 gpu ai 机器是个馊主意 | 否定的;消极的;负面的;负的 | ## 结论 自然语言处理(NLP)的目标是使计算机程序能够理解和使用自然的人类对话。在本文中,我们查看了 NLTK 工具箱,它允许在 Python 中执行 NLP 操作。它为我们提供了各种文本处理包以及各种测试数据集。这些过程包括标记化、词汇化、词干化和可视化解析树。我们演示了如何在图纸空间渐变中配置 NLTK,并使用它在文本处理过程中执行各种 NLP 操作。然后,使用 NLTK 和 Keras,我们建立了一个情感分析模型来评估给定句子的情感。 ## 资源 [https://www . researchgate . net/publication/291179558 _ Natural _ Language _ Processing](https://www.researchgate.net/publication/291179558_Natural_Language_Processing) [https://archive . ics . UCI . edu/ml/datasets/情操+标签+句子](https://archive.ics.uci.edu/ml/datasets/Sentiment+Labelled+Sentences) [https://sci kit-learn . org/stable/modules/generated/sk learn . feature _ extraction . text . count vectorizer . html](https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html) [https://karczmarczuk.users.greyc.fr/TEACH/TAL/Doc/Handbook《自然语言处理》第二版查普曼&霍尔 Crc 机器学习&模式识别 2010.pdf](https://karczmarczuk.users.greyc.fr/TEACH/TAL/Doc/Handbook%20Of%20Natural%20Language%20Processing,%20Second%20Edition%20Chapman%20&%20Hall%20Crc%20Machine%20Learning%20&%20Pattern%20Recognition%202010.pdf) # 强化学习的全面介绍 > 原文:<https://blog.paperspace.com/introduction-to-reinforcement-learning/> 对海量数据的访问和巨大计算能力的可用性使我们探索各种技术来提取有用的模式,并过上更好的生活。正确地搅动数据已经成为一种必要。工业自动化、医疗保健中的疾病检测、机器人、资源管理和个性化推荐已经为我们的生活铺*了道路。它们彻底改变了我们感知和执行任务的陈词滥调。同样,强化学习是机器学习的一个子领域,它通过促进自动化决策在当前的技术时代占据了一席之地。 这里有一个主题列表,我们将通过它来详细了解强化学习: * 这一切都始于机器学习 * 强化学习的本质 * 机器人学硕士 * RL 的其他应用 * 马尔可夫决策过程(MDP) * 最优策略搜索 * 蒙特卡罗学习 * 动态规划 * 时差学习 * q 学习 * 勘探-开采权衡 * ε-贪婪勘探政策 * 深度 Q-网络 * 结论 ## 这一切都始于机器学习 **机器学习**顾名思义,赋予机器无需显式编程就能学习的能力。给定一组观察数据点,机器制定的决策类似于人类制定的决策。 **机器学习中的监督学习**是数据点标签的可用性。例如,如果我们考虑 Iris 数据集(由植物特征及其对应的植物名称组成),监督学习算法必须学习特征和标签之间的映射,并在给定样本的情况下输出标签。 **机器学习中的无监督学习**就是不使用标签。算法的职责是通过分析概率分布从数据中得出模式。 **机器学习中的强化学习(RL)** 是标签的部分可用性。学习过程类似于儿童经历的养育过程。父母养育孩子,同意或不同意孩子采取的行动。孩子学习并据此行动。最终,孩子学会了通过倾向于有积极反馈的行动来最大化成功率。强化学习就是这样进行的。这是一种类似于人类学习的技术,由于它接*人类的行为和感知,在最*十年获得了发展势头。 ## 强化学习的本质 > 强化学习有助于制定最佳决策,接*成功标准。每一个动作都不受监督;相反,机器必须自己学习才能接*成功率。 强化学习有四个主要组成部分: 1. **代理人:**必须制定决策的实体。 2. **动作:**由实体执行的事件。 3. **环境:**代理执行动作的场景。它受到一系列规则的约束,环境会对每一个动作做出反应。 4. **奖励:**反馈机制,通知代理人成功率。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d10bb5ec6ae70d9e4d12ebbff646836e.png) ### 机器人学硕士 一个显著的 RL 应用是在机器人领域。当没有给出成功的衡量标准时,机器人必须具有执行未知任务的智能。它必须探索所有的可能性,并达到它的既定目标。如果我们考虑典型的强化学习术语,这里的机器人是一个代理。行动就是它的运动。环境是,比如说,一个迷宫。奖励是成功完成一步棋所得到的分数。所有这四个部分一起解释了一个强化学习场景。 ### RL 的其他应用 除了机器人技术之外,RL 还被证明是一种有前途的技术,可以在各种其他领域实现最先进的解决方案: * 自动驾驶——停车、向正确的方向移动汽车、轨迹优化、运动规划、基于场景的学习等。 * 交通灯控制-根据道路上的车辆数量控制交通灯。 * 医疗保健-找到最佳治疗方案,设计更好的假肢。 * 金融交易和价格优化。 * 电力系统-利用有效的能源消耗策略降低能源消耗。 * 游戏——在游戏行业使用强化学习带来了巨大的成果。AlphaGo Zero 和 Dota 2 都有经过强化学习算法训练的智能体玩过,表现相当不错。 ## 马尔可夫决策过程(MDP) 马尔可夫决策过程为强化学习代理提供了一个环境。为了理解马尔可夫决策过程,让我们首先理解它们的先决条件。 一个**马尔可夫属性**是一个无记忆属性,依赖于当前状态,而不依赖于之前的状态(它假设现在是所有过去事件的整体表示)。换句话说。考虑到现在,未来不取决于过去。拥有这种性质的过程被称为**马尔可夫过程**。这里的进程是一系列状态$S1,S2,S3,...$拥有马尔可夫性质。它由两个参数定义:状态$S$和转换函数$P$(即从一个状态转换到另一个状态的函数)。 一个**马尔可夫奖励过程**定义了一个马尔可夫过程所累积的奖励。它由状态$S$、转移函数$P$、奖励$R$和贴现率$D$定义。$D$解释了未来的回报,给出了现在的回报。如果$D$为 0,代理只关心下一个奖励。如果$D$为 1,则代理关心所有未来的奖励。贴现因子$D$是收敛结果所必需的,否则会导致无限*凡的结果。 从给定时间$t$累积的总奖励由一个简单的数学等式给出: $ $ r _ t = r _ { t+1 }+dr _ { t+2 }+d^2r_{t+3}+…$ $ 如果$D$ = 0,$R_t = R_{t + 1}$并且代理只关心下一个奖励。 如果$D$ = 1,$R_t = R_{t+1} + R_{t+2} + …$并且代理关心所有未来的奖励。 一个**马尔可夫决策过程(MDP)** 是一个应用于决策的马尔可夫奖励过程。它由状态$S$、转换函数$P$、奖励$R$、贴现率$D$和一组动作$a$表示。MDP 可以被认为是代理和环境之间的接口。一个代理在一个环境中执行一组特定的动作,然后这个环境用一个奖励和一个新的状态来响应。下一个状态和奖励只取决于前一个状态和动作。 ### 最优策略搜索 既然环境是用 MDP 定义的,我们的重点就必须放在回报最大化上。为了实现这一点,代理必须确定它将处于的每个状态的最佳行动,或者一般来说,确定最佳政策或策略。 **贝尔曼方程**通过计算每个州能够产生的未来回报来帮助估计每个州的价值。 $$V^{}(S) = max_a \sum_{S^{'}}P(S,a,S^{'})[R(S,a,S^{'}) + D V(S{'})]\]

其中:

  • \(P(S,a,S^{'})\)是当动作为\(a\)时,从状态\(S\)\(S^{'}\)的转移概率
  • \(R(S,a,S^{'})\)是当动作为\(a\)时,从状态\(S\)转换到\(S^{'}\)所获得的奖励
  • d 是贴现因子
  • \(V(S^{'})\)是州\(S{'}\)的值

上述方程可以从马尔可夫方程得到如下。

$ r _ t = r _ { t+1 }+dr _ { t+2 }+d^2r_{t+3}+…$

$ R _ t = R _ { t+1 }+D(R _ { t+2 }+DR _ { t+3 }+…)$

\(R_t = R_{t+1} + DR_{t+1}\)

期望值(EV)则为:

\[V^{}(S) = max_a \sum_{S^{'}}P(S,a,S^{'})[R(S,a,S^{'}) + D V(S{'})] \]

贝尔曼方程计算最佳状态,该状态与配对动作一起将进一步增强最佳策略搜索。这可以通过使用 Q 值来实现。

Q 值定义了代理在特定状态下必须选择的动作。Q 值可通过下式计算:

\[Q_{k+1}(S,a) = \sum_{S^{'}} P(S,a,S^{'})[R(S,a,s^{'})+d.max_{a^{'}} q_{k}(s^{'},a^{'})]\ for all(s,a) $ a^{'})$ $max_{a^{'}} q_{k}(s^{'}定义了$a^{'}$在$S^{'}$.采取的行动 简而言之,通过使用贝尔曼方程(最优状态)和 Q 值(最优动作)计算结果,代理的训练迭代地发生。 ## 蒙特卡罗学习 在最优策略搜索中,我们的假设是概率是包含在内的,尽管我们不知道它的值。因此,为了找到概率,主体必须与环境交互,并找到从一种状态转换到另一种状态的所有可能的概率。 蒙特卡罗是一种随机抽样输入以确定输出的技术,该输出更接*考虑中的报酬。它根据观察到的回报生成一个更新动作-值对的轨迹。最终,策略/轨迹收敛到最优目标。 > 在蒙特卡罗学习中,每个试验(随机抽样)都被表示为一个**事件**。 状态$V(S)$的值可以使用以下方法来估计: **首次访问蒙特卡洛政策评估:**只有代理人首次访问一个州才算数。 **每次访问蒙特卡洛政策评估:**代理人每次访问一个州都要计算 S $ S。 **增量蒙特卡罗策略评估:**每集后计算状态$S$的*均值。 使用这些技术中的任何一种,都可以评估动作值。 在蒙特卡罗学习中,整个事件必须发生以评估状态$V(S)$的价值,因为它取决于**未来回报**(如在贝尔曼方程和 Q 值中所见),这是耗时的。为了克服这一点,我们使用了**动态编程**。 ## 动态规划 在动态编程中,算法搜索与下一步相关的所有可能的动作。这使得有可能评估下一个状态,从而在单个步骤之后更新策略。动态编程向前看一步,迭代所有的动作。另一方面,要用蒙特卡罗选择一个单一的行动,整个情节必须是完整的。 动态规划和蒙特卡罗的交叉点是时差学习。 ### 时差学习 在时差学习中,TD(0)是最简单的方法。它通过利用贝尔曼方程来估计一个状态的值,不包括概率。 $ $ V(S _ t)= V(S _ t)+\ alpha[R _ { t+1 }+D . V(S _ { t+1 })-V(S _ t)]$ $ 其中$R_{t+1} + DV(S_{t+1}) - V(S_{t})$为 TD 误差,$R_{t+1} + DV(S_{t+1})$为 TD 目标。 也就是说,在时间$t + 1$时,代理计算 TD 目标并更新 TD 错误。 概括$TD(0)$,$TD(D)$将是: $ $ v(s _ t)= v(s _ t)+\alpha[g^{\lambda}{t}-v(s _ t)]$ $ TD 试图通过考虑当前预测来计算未知的未来预测。 ## q 学习 q 学习是蒙特卡罗和时差学习的结合。它由以下等式给出: $$Q(S_t,a) = Q(S_t,a)+\ alpha[R _ { t+1 }+d . max _ a Q(S _ { t+1 },a) - Q(S_t,a)]\]

贪婪地计算未来的回报。

Q-Learning 是一个非策略算法,这意味着它选择随机的动作来找到一个最优的动作。换句话说,不存在它遵守的政策。为了更好地理解这一点,让我们来看看勘探-开采的权衡。

勘探-开采权衡

勘探-开采权衡提出了一个问题,“在什么情况下开采应该优先于勘探?”

假设你从一个特定的服装品牌购买衣服。现在你的朋友说不同品牌的衣服看起来更好。你现在会为选择哪个品牌更好而左右为难吗?探索一个新品牌会更差还是更好?

我们人类优先选择探索(例如新的服装品牌)或开发(我们信赖的旧服装品牌)。但是这怎么能由机器来决定呢?

为了解决这个问题,我们有一个叫做ε贪婪探索政策的东西。

ε-贪婪勘探政策

一方面,持续探索并不是一个合适的选择,因为它会耗费大量的时间。另一方面,持续利用不会将范围扩大到包括看不见的(可能更好的)数据。为了对此进行检查,使用了ε值。它的工作原理如下:

  • ε的值在开始时被设置为 1。这仅仅是通过选择随机行为和填写 Q 表来进行探索。
  • 然后生成一个随机数。如果这个数字大于ε,我们就进行开发(这意味着我们知道该采取的最佳步骤),否则我们就进行探索。
  • ε值必须逐渐减少(表示剥削增加)。

Q 表存储了每个状态下一个动作的最大预期未来回报(Q 值)。

State-Action table and Q-table (Source: freecodecamp)

ε-贪婪政策的优势在于它永远不会停止探索。尽管 Q-table 已经满了,但还是要去探索。

用 Q 值填充 Q 表构成了 Q 学习算法。它有助于通过贪婪地分析未来的回报来选择最大化总回报的行动。

深度 Q-网络

对于涉及较小离散态的问题,Q 表似乎是可行的。随着动作和状态的增长,Q 表不再是一个可行的选择。我们需要一个映射状态和动作的*似函数。在每一个时间步,代理都必须根据状态-动作*似计算预测值。如果状态-动作对是非线性和不连续的,这也是不可行的。

一个深度人工神经网络(用$Q(S,a 表示;其中θ对应于网络的可训练权重)。神经网络可以清晰地表示复杂的状态空间。这里有一个例子:

Q-Learning v/s Deep Q-Learning (Source: Oracle)

DQN 成本函数看起来像这样:

\[Cost=[Q(S,a;\theta) - (r(S,a)+d.max_{a}q(s^{'},a;\theta))]^2 \]

它是当前\(Q\)值(预期的未来回报)与当前和未来回报(实际回报)之间的均方误差(MSE)。

训练过程:训练数据集在整个训练过程中不断建立。我们要求代理使用当前网络选择最佳行动。状态、动作、奖励和下一个状态都被记录下来(新的训练数据样本)。在这种网络中使用的存储缓冲器通常被称为体验重放。它们打破了连续样本之间的相关性,否则会导致低效的训练。

结论

强化学习是机器学习的一个分支,旨在自动化决策。它分析和复制人类智能的能力为人工智能研究开辟了新的途径。理解其意义是必要的,越来越多的研究机构和公司专注于部署智能决策代理。

在本文中,您已经了解了强化学习的内容。通过构建问题的端到端 RL 解决方案来整合您的知识。开始使用 OpenAI Gym 是一个很好的开始(包括一个详细的渐变社区笔记本,有完整的 Python 代码,可以免费运行)。

编码器-解码器序列到序列模型介绍(Seq2Seq)

原文:https://blog.paperspace.com/introduction-to-seq2seq-models/

在本教程中,我们将讨论编码器-解码器序列到序列(seq 2 seq)rnn:它们如何工作、网络架构、应用,以及如何使用 Keras 实现编码器-解码器序列到序列模型(直到数据准备;对于训练和测试模型,请继续关注第 2 部分)。

具体来说,我们将涵盖:

  • 介绍
  • 体系结构
  • 应用程序
  • 使用编码器-解码器序列到序列模型的文本摘要

您也可以跟随本教程中的完整代码,并从Gradient Community Notebook在免费的 GPU 上运行它。

我们开始吧!

介绍

RNN 通常具有固定大小的输入和输出向量,即输入和输出向量的长度都是预定义的。尽管如此,这在语音识别、机器翻译等用例中并不理想。,其中输入和输出序列不需要固定并且长度相同。考虑这样一种情况,英语短语“你过得怎么样?”被翻译成法语。在法语中,你会说“你有什么看法?”。这里,输入和输出序列的大小都不固定。在这种情况下,如果您想使用 RNN 构建一个语言翻译器,您不希望预先定义序列长度。

序列对序列(seq2seq)模型可以帮助解决上述问题。当给定一个输入时,编码器-解码器 seq2seq 模型首先生成该模型的编码表示,然后将其传递给解码器以生成所需的输出。在这种情况下,输入和输出向量的大小不需要固定。

体系结构

这个模型设计背后的想法是使它能够在我们不限制长度的情况下处理输入。一个 RNN 将用作编码器,另一个用作解码器。由编码器生成的输出向量和提供给解码器的输入向量将拥有固定的大小。然而,它们不必相等。编码器生成的输出可以作为整个块给出,或者可以在每个时间步长连接到解码器单元的隐藏单元。

Encoder-Decoder Sequence-to-Sequence Model

编码器和解码器中的 rnn 可以是简单的 rnn、LSTMs 或 gru。

在一个简单的 RNN 中,每个隐藏状态都是使用以下公式计算的:

\[H_t(编码器)= \ phi(W _ { HH } * H _ { t-1 }+W _ { HX } * X _ { t })$ $ 其中$\phi$是激活函数,$H_t(编码器)$表示编码器中的隐藏状态,$W_{HH}$是连接隐藏状态的权重矩阵,$W_{HX}$是连接输入和隐藏状态的权重矩阵。 解码器中的隐藏状态可以计算如下: $$H_t(解码器)= \phi(W_{HH} * H_{t-1})\]

注意:解码器的初始隐藏状态是从编码器获得的最终隐藏状态。

解码器产生的输出如下所示:

\[Y_t = H_t(解码器)* W_{HY} \]

其中\(W_{HY}\)是连接隐藏状态和解码器输出的权重矩阵。

Seq2Seq 模型的应用

Seq2seq 型号适用于以下应用:

  • 机器翻译
  • 语音识别
  • 视频字幕
  • 文本摘要

现在您已经了解了什么是序列到序列 RNN,在下一节中,您将使用 Keras API 构建一个文本摘要器。

使用 Seq2Seq 模型的文本摘要

文本摘要是指缩短长文本同时抓住其本质的技术。这有助于捕捉一大段文本的底线,从而减少所需的阅读时间。在这种情况下,我们可以利用使用编码器-解码器序列到序列模型构建的深度学习模型来构建文本摘要器,而不是依赖于手动摘要。

在该模型中,编码器接受实际的文本和摘要,训练该模型以创建编码表示,并将其发送到解码器,解码器将编码表示解码为可靠的摘要。随着训练的进行,经过训练的模型可用于对新文本进行推断,从中生成可靠的摘要。

这里我们将使用新闻摘要数据集。它由两个 CSV 文件组成:一个包含关于作者、标题、源 URL、短文和完整文章的信息,另一个只包含标题和文本。在当前应用程序中,您将从两个 CSV 文件中提取标题和文本来训练模型。

请注意,您可以跟随本教程中的完整代码,并从渐变社区笔记本中的免费 GPU 上运行它。

步骤 1:导入数据集

首先使用 pandas 的read_csv()方法将新闻摘要数据集导入到您的工作区。

import pandas as pd

summary = pd.read_csv('/kaggle/input/news-summary/news_summary.csv',
                      encoding='iso-8859-1')
raw = pd.read_csv('/kaggle/input/news-summary/news_summary_more.csv',
                  encoding='iso-8859-1') 

将两个 CSV 文件中的数据合并成一个DataFrame

pre1 = raw.iloc[:, 0:2].copy()
pre2 = summary.iloc[:, 0:6].copy()

# To increase the intake of possible text values to build a reliable model
pre2['text'] = pre2['author'].str.cat(pre2['date'
        ].str.cat(pre2['read_more'].str.cat(pre2['text'
        ].str.cat(pre2['ctext'], sep=' '), sep=' '), sep=' '), sep=' ')

pre = pd.DataFrame()
pre['text'] = pd.concat([pre1['text'], pre2['text']], ignore_index=True)
pre['summary'] = pd.concat([pre1['headlines'], pre2['headlines']],
                           ignore_index=True)

注意:为了增加训练模型所需的数据点,我们使用一个 CSV 文件构建了一个新的“文本”列。

让我们通过将前两行打印到控制台来更好地理解数据。

pre.head(2)

Text and Summary

步骤 2:清理数据

您获取的数据可能包含非字母字符,您可以在训练模型之前删除这些字符。为此,可以使用re(正则表达式)库。

import re

# Remove non-alphabetic characters (Data Cleaning)
def text_strip(column):

    for row in column:
        row = re.sub("(\\t)", " ", str(row)).lower()
        row = re.sub("(\\r)", " ", str(row)).lower()
        row = re.sub("(\\n)", " ", str(row)).lower()

        # Remove _ if it occurs more than one time consecutively
        row = re.sub("(__+)", " ", str(row)).lower()

        # Remove - if it occurs more than one time consecutively
        row = re.sub("(--+)", " ", str(row)).lower()

        # Remove ~ if it occurs more than one time consecutively
        row = re.sub("(~~+)", " ", str(row)).lower()

        # Remove + if it occurs more than one time consecutively
        row = re.sub("(\+\++)", " ", str(row)).lower()

        # Remove . if it occurs more than one time consecutively
        row = re.sub("(\.\.+)", " ", str(row)).lower()

        # Remove the characters - <>()|&©ø"',;?~*!
        row = re.sub(r"[<>()|&©ø\[\]\'\",;?~*!]", " ", str(row)).lower()

        # Remove mailto:
        row = re.sub("(mailto:)", " ", str(row)).lower()

        # Remove \x9* in text
        row = re.sub(r"(\\x9\d)", " ", str(row)).lower()

        # Replace INC nums to INC_NUM
        row = re.sub("([iI][nN][cC]\d+)", "INC_NUM", str(row)).lower()

        # Replace CM# and CHG# to CM_NUM
        row = re.sub("([cC][mM]\d+)|([cC][hH][gG]\d+)", "CM_NUM", str(row)).lower()

        # Remove punctuations at the end of a word
        row = re.sub("(\.\s+)", " ", str(row)).lower()
        row = re.sub("(\-\s+)", " ", str(row)).lower()
        row = re.sub("(\:\s+)", " ", str(row)).lower()

        # Replace any url to only the domain name
        try:
            url = re.search(r"((https*:\/*)([^\/\s]+))(.[^\s]+)", str(row))
            repl_url = url.group(3)
            row = re.sub(r"((https*:\/*)([^\/\s]+))(.[^\s]+)", repl_url, str(row))
        except:
            pass

        # Remove multiple spaces
        row = re.sub("(\s+)", " ", str(row)).lower()

        # Remove the single character hanging between any two spaces
        row = re.sub("(\s+.\s+)", " ", str(row)).lower()

        yield row

注意:您也可以使用其他数据清理方法来准备数据。

在文本和摘要上调用text_strip()函数。

processed_text = text_strip(pre['text'])
processed_summary = text_strip(pre['summary'])

使用 spaCy 提供的pipe()方法批量加载数据。这确保了所有文本和摘要都拥有string数据类型。

import spacy
from time import time

nlp = spacy.load('en', disable=['ner', 'parser']) 

# Process text as batches and yield Doc objects in order
text = [str(doc) for doc in nlp.pipe(processed_text, batch_size=5000)]

summary = ['_START_ '+ str(doc) + ' _END_' for doc in nlp.pipe(processed_summary, batch_size=5000)]

_START__END_标记分别表示摘要的开始和结束。这将在以后用于检测和删除空摘要。

现在让我们打印一些数据,以了解它是如何加载的。

text[0] 
# Output
'saurav kant an alumnus of upgrad and iiit-b s pg program in ...' 

也打印摘要。

summary[0]
# Output
'_START_ upgrad learner switches to career in ml  al with 90% salary hike _END_' 

步骤 3:确定最大允许序列长度

接下来,将textsummary列表存储在 pandas 对象中。

pre['cleaned_text'] = pd.Series(text)
pre['cleaned_summary'] = pd.Series(summary)

绘制图表以确定与文本和摘要长度相关的频率范围,即确定最大数量的文本和摘要所在的单词长度范围。

import matplotlib.pyplot as plt

text_count = []
summary_count = []

for sent in pre['cleaned_text']:
    text_count.append(len(sent.split()))

for sent in pre['cleaned_summary']:
    summary_count.append(len(sent.split()))

graph_df = pd.DataFrame() 

graph_df['text'] = text_count
graph_df['summary'] = summary_count

graph_df.hist(bins = 5)
plt.show()

从图表中,你可以确定最大单词数的范围。作为总结,您可以将范围指定为 0-15。

要找到我们无法从图表中清楚解读的文本范围,考虑一个随机范围,并找到落入该范围的单词的百分比。

# Check how much % of text have 0-100 words
cnt = 0
for i in pre['cleaned_text']:
    if len(i.split()) <= 100:
        cnt = cnt + 1
print(cnt / len(pre['cleaned_text']))
# Output
0.9578389933440218

正如您所观察到的,95%的文本属于 0-100 类别。

现在初始化文本和摘要的最大允许长度。

# Model to summarize the text between 0-15 words for Summary and 0-100 words for Text
max_text_len = 100
max_summary_len = 15

第四步:选择合理的文本和摘要

选择小于步骤 3 中定义的最大长度的文本和摘要。

# Select the Summaries and Text which fall below max length 

import numpy as np

cleaned_text = np.array(pre['cleaned_text'])
cleaned_summary= np.array(pre['cleaned_summary'])

short_text = []
short_summary = []

for i in range(len(cleaned_text)):
    if len(cleaned_summary[i].split()) <= max_summary_len and len(cleaned_text[i].split()) <= max_text_len:
        short_text.append(cleaned_text[i])
        short_summary.append(cleaned_summary[i])

post_pre = pd.DataFrame({'text': short_text,'summary': short_summary})

post_pre.head(2)

现在添加序列的开始(sostok)和序列的结束(eostok ),分别表示摘要的开始和结束。这将有助于在推理阶段触发汇总的开始。

# Add sostok and eostok

post_pre['summary'] = post_pre['summary'].apply(lambda x: 'sostok ' + x \
        + ' eostok')

post_pre.head(2)

步骤 5:标记文本

首先将数据分成训练和测试数据块。

from sklearn.model_selection import train_test_split

x_tr, x_val, y_tr, y_val = train_test_split(
    np.array(post_pre["text"]),
    np.array(post_pre["summary"]),
    test_size=0.1,
    random_state=0,
    shuffle=True,
)

准备并标记文本数据。

# Tokenize the text to get the vocab count 
from tensorflow.keras.preprocessing.text import Tokenizer 
from tensorflow.keras.preprocessing.sequence import pad_sequences

# Prepare a tokenizer on training data
x_tokenizer = Tokenizer() 
x_tokenizer.fit_on_texts(list(x_tr))

找出文本中罕见词(比如,出现次数少于 5 次)的出现百分比。

thresh = 5

cnt = 0
tot_cnt = 0

for key, value in x_tokenizer.word_counts.items():
    tot_cnt = tot_cnt + 1
    if value < thresh:
        cnt = cnt + 1

print("% of rare words in vocabulary: ", (cnt / tot_cnt) * 100)
# Output
% of rare words in vocabulary:  62.625791318822664

通过考虑单词总数减去很少出现的单词,再次标记文本。将文本转换为数字,并将其填充到相同的长度。

# Prepare a tokenizer, again -- by not considering the rare words
x_tokenizer = Tokenizer(num_words = tot_cnt - cnt) 
x_tokenizer.fit_on_texts(list(x_tr))

# Convert text sequences to integer sequences 
x_tr_seq = x_tokenizer.texts_to_sequences(x_tr) 
x_val_seq = x_tokenizer.texts_to_sequences(x_val)

# Pad zero upto maximum length
x_tr = pad_sequences(x_tr_seq,  maxlen=max_text_len, padding='post')
x_val = pad_sequences(x_val_seq, maxlen=max_text_len, padding='post')

# Size of vocabulary (+1 for padding token)
x_voc = x_tokenizer.num_words + 1

print("Size of vocabulary in X = {}".format(x_voc))
# Output
Size of vocabulary in X = 29638

对摘要也做同样的操作。

# Prepare a tokenizer on testing data
y_tokenizer = Tokenizer()   
y_tokenizer.fit_on_texts(list(y_tr))

thresh = 5

cnt = 0
tot_cnt = 0

for key, value in y_tokenizer.word_counts.items():
    tot_cnt = tot_cnt + 1
    if value < thresh:
        cnt = cnt + 1

print("% of rare words in vocabulary:",(cnt / tot_cnt) * 100)

# Prepare a tokenizer, again -- by not considering the rare words
y_tokenizer = Tokenizer(num_words=tot_cnt-cnt) 
y_tokenizer.fit_on_texts(list(y_tr))

# Convert text sequences to integer sequences 
y_tr_seq = y_tokenizer.texts_to_sequences(y_tr) 
y_val_seq = y_tokenizer.texts_to_sequences(y_val) 

# Pad zero upto maximum length
y_tr = pad_sequences(y_tr_seq, maxlen=max_summary_len, padding='post')
y_val = pad_sequences(y_val_seq, maxlen=max_summary_len, padding='post')

# Size of vocabulary (+1 for padding token)
y_voc = y_tokenizer.num_words + 1

print("Size of vocabulary in Y = {}".format(y_voc))
# Output
% of rare words in vocabulary: 62.55667945587723
Size of vocabulary in Y = 12883

步骤 6:删除空文本和摘要

从数据中删除所有空摘要(只有开始和结束标记)及其相关文本。

# Remove empty Summaries, .i.e, which only have 'START' and 'END' tokens
ind = []

for i in range(len(y_tr)):
    cnt = 0
    for j in y_tr[i]:
        if j != 0:
            cnt = cnt + 1
    if cnt == 2:
        ind.append(i)

y_tr = np.delete(y_tr, ind, axis=0)
x_tr = np.delete(x_tr, ind, axis=0)

对验证数据也重复同样的操作。

# Remove empty Summaries, .i.e, which only have 'START' and 'END' tokens
ind = []
for i in range(len(y_val)):
    cnt = 0
    for j in y_val[i]:
        if j != 0:
            cnt = cnt + 1
    if cnt == 2:
        ind.append(i)

y_val = np.delete(y_val, ind, axis=0)
x_val = np.delete(x_val, ind, axis=0)

我们将在第 2 部分继续这一点,在那里我们构建模型,训练它,并进行推理。

结论

到目前为止,在本教程中,您已经看到了什么是编码器-解码器序列到序列模型及其架构。您还准备了新闻摘要数据集来训练您自己的 seq2seq 模型,以便对给定的文本片段进行摘要。

在这个由两部分组成的系列的下一部分中,您将构建、训练和测试这个模型。

视觉问答研究综述

原文:https://blog.paperspace.com/introduction-to-visual-question-answering/

在过去的几年里,我们已经在机器学习的许多子领域看到了很多进步。像对象检测和图像分割这样的计算机视觉任务,以及像实体识别、语言生成和问题回答这样的 NLP 任务,现在正由神经网络来解决,并且以更快的速度和更高的精度来处理。

最*引起人工智能界关注的一个任务是视觉问答。本文将探讨视觉问答的问题,解决它的不同方法,相关的挑战,数据集和评估方法。

介绍

视觉问题回答系统试图用自然语言正确回答关于图像输入的问题。这个问题的更广泛的想法是设计一种系统,它可以像人类一样理解图像的内容,并以自然语言有效地交流图像。这是一项具有挑战性的任务,因为它需要基于图像的模型和自然语言模型进行交互并相互补充。

这个问题已经被广泛接受为 AI-complete ,即一个面对人工一般智能问题的问题,即让计算机像人一样智能。事实上,该问题也被格曼等人【2015】建议用作视觉图灵测试

为了给你一个子问题的概念,视觉问答任务需要:

Source

这些问题的解决方案包括四个主要步骤:

  • 图像特征化 -将图像转换成其特征表示,以便进一步处理。
  • 问题特征化 -将自然语言问题转换成它们的嵌入,以便进一步处理。
  • 联合特征表示 -结合图像特征和问题特征以增强算法理解的方法。
  • 答案生成——利用关节特征理解输入图像和所提问题,最终生成正确答案。

这一管道中的每个阶段都采用了几种方法。我们将在这篇文章中浏览主要的。

图像特征化

卷积神经网络已经成为图像模式识别的黄金标准。在输入图像通过卷积网络之后,它被转换成抽象的特征表示。CNN 图层中的每个滤镜捕捉不同种类的图案,如边、顶点、轮廓、曲线和对称性。这个概念在这篇文章中有很好的解释,文章讨论了神经网络中的等方差以及激活图如何寻找不同种类的检测器。

查看基于 CNN 的网络的三维可视化工具,该网络基于 MNIST 数据进行手写数字识别训练点击这里

Source

CNN 已经发展成更深和更复杂的架构,广泛用于下游任务,如分类、对象检测和图像分割。一些这样的网络包括 AlexNetResNetLeNetSqueezeNetVGGNetZFNet 等。

大多数 VQA 文学利用 CNN 进行形象特征化。网络的最后一层被移除,网络的其余部分用于生成影像特征。有时,倒数第二层被归一化(卡弗尔等人(2016)斋藤等人(2017) ,或者通过一个降维层(卡弗尔等人(2016)伊利耶夫斯基等人(2016) )。

Source

从上面的调查来看,很明显,在 ResNets 出现之前,VGGNet 是首选网络。2017 年后出的 VQA 论文大多使用 ResNets。

ResNets 的核心思想包括跳过连接,如下所示。

身份快捷连接允许网络跳过中间层。这个想法是通过允许网络在需要时跳过层来避免非常深的神经网络经常面临的爆炸和消失梯度。这也有助于提高精度,因为损害精度的图层可以被跳过和正则化。

Ilievski et al. (2016) 利用问题词嵌入(我们将很快讨论)来提取标签与问题本身相似的对象,并使用 ResNet 提取这些对象的特征表示。他们称这种方法为“集中动态注意力”

卢等(2019) 利用 ViLBERT(视觉与语言 BERT 的简称)进行视觉问答。ViLBERT 由两个并行的 BERT 风格的模型组成,在图像区域和文本片段上运行。每一个流都是一系列的转换器和协同注意转换器层,使得能够在模态之间进行信息交换。

问题特征化

有几种方法可以创建嵌入。旧的方法包括基于计数、基于频率的方法,如计数矢量化和 TF-IDF。还有基于预测的方法,如连续单词包和跳过单词。Word2Vec 算法的预训练模型也可以在 Gensim 等开源工具中获得。你可以在这里了解这些方法。像 RNNs、LSTMs、GRUs 和 1-D CNN 这样的深度学习架构也可以用来创建单词嵌入。在 VQA 文学中,LSTMs 使用频率最高。

阅读本文的大多数人可能已经知道什么是 rnn,但是为了完整起见,我们仍然会触及一些基本概念。递归神经网络接受顺序输入,并根据训练数据预测序列中的下一个元素。普通的递归网络将基于所提供的输入,处理它们先前的隐藏状态,并输出下一个隐藏状态和顺序预测。将该预测与地面真实值进行比较,以使用反向传播来更新权重。

我们也知道 rnn 容易受到消失和爆炸梯度的影响。为了解决这个问题,LSTMs 应运而生。LSTMs 使用不同的门来管理序列中每个先前元素的重要性。还有 LSTMs 的双向变体,其从左到右以及从右到左学习不同元素的顺序依赖性。

Source

陆等(2016) 构建一个层次架构,在三个层次上共同关注图像和问题:(a)单词层,(b)短语层,以及(c)问题层。在单词层,他们通过嵌入矩阵将单词嵌入到向量空间中。在短语级别,一维卷积神经网络用于捕获包含在单字、双字和三元字中的信息。在问题层面,他们使用递归神经网络对整个问题进行编码。

本调查列出了问题特征化的独特方式,下面也会提到。

Antol 等人(2015) 使用 BoW 方法,但只使用他们数据集中问题的前 1000 个单词。他们利用问题开头的单词之间的强相关性,通过从问题的前 10 个第一、第二和第三个单词中创建另一个 BoW 来回答,然后将其与第一个表示连接起来。

张等(2016) 尝试解决二元视觉问答问题。他们试图通过引入 PRS 结构来组织问题中的信息,其中 P 代表主要对象,R 代表关系,S 代表次要对象。理想情况下,p 和 S 值应该是名词,而 R 应该是动词或介词。

为了解决多项选择问题的回答, Shih et al. (2016) 将变长问题转化为固定大小的向量,由将单词分为以下几类:

  • 使用前两个单词的问题类型
  • 名词性主语
  • 所有名词
  • 所有剩余单词

于等(2018) 利用 Tree-LSTMs 捕获问题中的语义及其与图像的关系。树-lstm 是树结构的对象,其中每个节点是一个 LSTM 单元。向前传球可以有多种方式:

  • 子求和树单元:这里添加子节点的隐藏状态以获得输出
  • 子节点最大池单元:这里的输出是所有子节点的最大值
  • 子卷积+最大池单元:这里的输出是不同子节点之间卷积的最大值。两个函数\(f\)\(g\)之间的离散卷积可以表示如下:

$ \((f * g)[n]= \sum_{m=-m}^{m} f[n-m]g[m]\) $

数据集中的每个问题都被解析并映射到一个树结构,其中根节点被设置为问题序列。树形结构提供了有助于逻辑推理的语义结构。

Toor et al. (2019) 设计了一种方法来理解问题的相关性,并对不相关的问题进行编辑,实践证明这种方法是有效的。他们称这种方法为问题行为相关性和编辑。

联合特征表示

处理来自图像和文本的不同特征向量的最常见方法之一是将两者连接起来,并让后面的层为每一个找到正确的权重。如果特征向量长度相同,还可以尝试元素加法和乘法。马林诺夫斯基等人(2017) 尝试了上面提到的所有方法,发现逐元素乘法给出了最好的精度。 Shih 等人(2016) 使用点积,而 Saito 等人(2017) 使用混合方法。它们将逐元素乘法和逐元素加法的结果连接起来。

几篇论文已经使用典型相关分析来寻找联合特征表示。典型相关分析是一种寻找两组独立向量之间相关性的方法。您可以评估两个向量的不同线性组合,类似于 PCA 中所做的。

\(X\)是长度为\(p\)的向量。然后,

$ $ U _ { 1 } = a _ { 11 } X _ { 1 }+a _ { 12 } X _ { 2 }+...+ a_{1p}X_{p} $$

$ $ U _ { 2 } = a _ { 21 } X _ { 1 }+a _ { 22 } X _ { 2 }+...+ a_{2p}X_{p} $$

......

$ $ U _ { p } = a _ { P1 } X _ { 1 }+a _ { p2 } X _ { 2 }+...+ a_{pp}X_{p} $$

\(Y\)是长度为\(q\) 的向量。然后,

$ $ V _ { 1 } = b _ { 11 } Y _ { 1 }+b _ { 12 } Y _ { 2 }+...+ b_{1q}Y_{q} $$

$ $ V _ { 2 } = b _ { 21 } Y _ { 1 }+b _ { 22 } Y _ { 2 }+...+ b_{2q}Y_{q} $$

......

$ $ V _ { q } = b _ { Q1 } Y _ { 1 }+b _ { Q2 } Y _ { 2 }+...+ b_{qq}Y_{q} $$

那么$ (U_{i},V_{i}) \(就是\) i^{th} \(标准变量对。在这两个向量中,为了计算方便,您选择了\)X\(使得\)p \leq q \(。然后是\)p$ 规范协变量对。

协方差可以定义如下:

\[ cov(x,y)= \ frac { \ sum(x _ { I }-\ bar { x })(y _ { I }-\ bar { y })} { N-1 } $ $ 我们可以计算$ U_{i} $和$ V{i} $的方差: $ $ var(u _ { I })=∞a _ { ik } a _ { il } cov(x _ { k }、X_{l}) $。 $ $ var(v _ { I })= \sum_{k=1}^{q} \sum_{l=1}^{q} b _ { ik } b _ { il } cov(y _ { k },Y_{l}) \]

那么$ U_{i} \(和\) V_{i} $之间的典型相关可以使用下面的公式来计算:

$ $ $ { rho } = { frac }有一个$ } { sqrt } $ }存在于$ { fn 方正准圆简体 fs 121 chc 9e cc 4 }和{ fn 方正准圆简体 fs 121 chc 9e cc 4 }之间的{ fn 方正准圆简体 fs 121 chc 9e cc 4 }之间的{ fn 方正准圆简体 fs 121 chc 9e cc 4 }之间的{ fn 方正准圆简体 fs 121 chc 9e cc 4 }中

为了找到联合特征表示,我们需要最大化 UV,之间的相关性,或者$ \rho $的值。在 scikit-learn 实现中,这是在偏最小二乘算法中完成的。内核 CCA 是另一种变体,它利用了基于拉格朗日的解决方案。龚等(2014)余等(2015)托马西等(2019) 都在联合特征表示中使用了 CCA 的某种变体。

Noh 等人(2015) 为此任务设计了动态参数预测网络(DPPNets)。在与图像特征融合之前,他们在使用 GRU 对问题进行矢量化后添加一个全连接图层,以动态分配每个问题的权重。他们发现,当特征向量有大量参数时,这是一个挑战。为了避免这一点,他们利用哈希机制来创建一个联合特征表示。他们应用了由陈等人(2015) 提出的散列技巧来压缩神经网络。哈希算法用于对不同的参数进行分组,每组参数共享相同的值。哈希技巧通过利用神经网络中的冗余大大减小了模型的大小,而不会以任何显著的方式损害模型的性能。

Fukui 等人(2016) 利用多模态双线性池进行联合特征创建。双线性模型采用两个向量的外积来生成一个更高维的矩阵,其中一个向量的每个参数都以乘法方式与另一个向量的参数交互。对于较大的向量大小,这会创建具有太多可训练参数的巨大模型。

Source

为了避免这种情况,他们使用了一种叫做计数草图投影函数的东西(由 Charikar 等人(2002) 提出),一种旨在找到数据流中最频繁值的算法,以及傅立叶变换。在计数草图投影中,初始化两个向量,一个具有-1 和 1 值,另一个将索引\(i\)处的输入映射到索引\(j\)处的输出。对于输入中的每个元素,使用我们之前初始化的第二个向量查找其目标索引,并将第一个向量与输入向量的点积添加到输出中。卷积定理指出,时域中两个向量之间的卷积与频域中的元素乘积相同,可以通过傅立叶变换获得。他们利用卷积的这一特性最终得到他们的联合表示。 Hedi 等人(2017) 在为 VQA 实施 MUTAN 时也使用了多模态紧凑双线性池。

陆等(2016) 在融合嵌入之前使用了共同注意机制,使得视觉注意和文本注意都被计算。他们提出了两种注意机制:*行共同注意和交替共同注意。

在并行共同注意中,他们通过计算所有图像位置和问题位置对的图像和问题特征之间的相似性来连接图像和问题。他们称随后的表示为亲和矩阵。他们使用这个亲和矩阵来预测注意力地图。

\[C = tanh(Q^{T}W_{b}V) \]

这里\(C\)是亲和度矩阵,\(Q\)是问题特征向量,\(V\)是视觉特征向量。\(W\)代表重量。

$ $ H _ { V } = tanh(W _ { V } V+(W _ { Q } Q)C)$ $

$ $ h _ { q } = tanh(w _ { q } q+(w_{v}v)c^{t})$ $

$ $ a _ { v } = softmax(w^{t}_{hv} h^{v})$ $

$ $ a _ { q } = softmax(w^{t}_{hq} h^{q})$ $

其中\(W_{v}\)\(W_{q}\)\(w_{hv}\)和$ w_{hq}\(是权重参数。\) a_{v} \(和\) a_{q} \(分别是每个图像区域\) v_{n} \(和单词\) q_{t} $的注意概率。

$ $ v = \sum_{n=1}^{n} a^{v}{n} v _ { n },q = \sum^{t} a^{q}_{t} q _ { t } $ $

其中\(v\)\(q\)分别是图像和问题的*行共同注意向量。

他们还提出了交替共同注意,其中他们依次交替产生图像和问题的注意。图像特征影响问题注意,反之亦然。

$ $ h = tanh(w _ { x } x+(w_{g}g)1^{t})$ $

\[a_{x} = softmax(w^{T}_{hx} H) \]

$ $ x = \总和 a^{x}{i} x $$

其中\(1\)是所有元素都为\(1\)的向量。$ W_{x} \(和\) W_{g} \(是参数。\) a_{x} \(是特征\)X$的注意力权重。

以一种非常聪明的方式, Kim 等人(2016) 使用残差连接作为从基于 CNN 的架构中提取的联合特征表示,用于图像特征化,并使用 LSTM 架构用于问题特征化。多模态剩余网络架构看起来像这样。

剩余连接增加了网络的注意能力,作者也对其进行了可视化。

高等(2018) 认识到在一些卷积网络中,像 ResNet,从第 2 层到最后一层只取一维向量表示,会丢失很多空间信息。为了解决这个问题,他们使用他们所谓的“问题导向混合卷积”,其中他们创建卷积核,该卷积核采用使用 GRU 创建的问题表示以及 ResNet 特征向量来创建联合特征表示。

作者指出,为了从 2000-D 问题特征向量预测常用的\((3×3×256×256)\)核,用于学习映射的全连接层生成 1.17 亿个参数,这很难学习,并导致在现有 VQA 数据集上的过拟合。为了解决这个问题,他们尝试预测分组卷积核参数。QGHC 模块看起来像这样。

彭等(2019) 提出不需要 LSTM 生成完整的特征向量。只需要从问题中提取关键词。他们利用这一思想,提出了一个词-区域注意网络(WRAN),它可以定位相关的对象区域,并识别参考问题中相应的词。

答案生成

对 VQA 的研究包括(来源):

  • 自由形式的开放式问题,答案可以是单词、短语,甚至是完整的句子
  • 物体计数问题,答案包括计算一幅图像中物体的数量
  • 多项选择问题
  • 二元问题(是/否)

二元问题和多项选择问题通常在末尾使用一个 s 形层。关节表示通过一个或两个完全连接的层。输出通过作为分类层的单个神经元层。

对于多项选择题,答案选项使用某种嵌入生成机制进行编码,就像我们前面讨论的那样。这被输入到联合特征生成的机制中。然后,连接要素通过一个完全连接的图层,最后通过一个具有 softmax 激活功能的多类分类图层。

对于自由形式的开放式问题,通常使用类似 LSTMs 的递归网络将联合特征表示转换为答案。吴等(2016) 提取关于图像的数据,为语言模型提供更多的上下文。他们使用 Doc2Vec 算法来获得嵌入,嵌入与 LSTM 一起使用来生成答案。马林诺夫斯基等人(2017) 设计了一种方法,其中图像特征与 LSTM 编码的每个单词的表示一起输入。

Ruwa et al. (2018) 不仅使用图像和问题特征,还考虑了情绪嵌入。基于 CNN 的情绪检测器与 LSTM 注意力模型一起被训练,与图像的局部区域相关。情绪与图像中人的外表和行为有关。

资料组

在视觉问答领域,有许多数据集可以处理不同类型的任务。这里讨论一些主要的问题。

  • DAQUAR (真实世界图像问答数据集)是一个关于图像的人类问答对数据集。
  • COCO-QA 是 COCO(上下文中的公共对象)数据集的扩展。这些问题有 4 种不同的类型:物体、数字、颜色和位置。所有答案都是单字类型。
  • VQA 数据集,比其他数据集大。除了来自 COCO 数据集的 204,721 幅图像,它还包括 50,000 幅抽象卡通图像。每张图片有三个问题,每个问题有十个答案。该数据集包括多项选择答案,以及开放式答案。
  • Visual Madlibs 拥有超过 10,000 张图片,在数据集中有 12 种填空类型。它提供了两种评价方式:选择题和填空题。
  • Visual7W 数据集包含七种类型的问题:什么、哪里、什么时候、谁、为什么、怎样、哪个。该数据集是在 47,300 幅可可图像上收集的。总的来说,它有 327,939 个问答对,以及 1,311,756 个人类生成的多项选择,以及来自 36,579 个类别的 561,459 个对象基础。此外,它们提供了完整的基础注释,将问答句子中提到的对象链接到图像中的边界框,因此引入了一种新的问答类型,其中图像区域作为视觉基础答案。他们也为开发者和人工智能研究者提供了一个工具包
  • CLEVR 数据集包括 70,000 幅图像和 699,989 个问题的训练集,15,000 幅图像和 149,991 个问题的验证集,15,000 幅图像和 14,988 个问题的测试集,以及所有训练和验证问题的答案。除此之外,它们还为 train 和 val 图像提供了场景图注释,给出了对象的真实位置、属性和关系。
  • 视觉基因组在他们的数据集中有 170 万个视觉问题答案。除了 VQA,它们还提供了许多其他类型的数据——区域描述、对象实例和所有映射到 WordNet synsets 的注释。

评估指标

在多项选择任务中,简单的准确性足以评估给定的 VQA 模型。但对于开放式 VQA 任务,精确字符串匹配的框架将是评估 VQA 模型性能的一种非常严格的方式。

吴与帕相似

吴和帕尔默相似度利用模糊逻辑计算两个短语之间的相似度。这是一个考虑了概念$ c_{1} \(和\) c_{2} \(在分类法中的位置的分数,相对于最不常见的包含项(\) c_{1} \(和\) c_{2} $的位置。它假设两个概念之间的相似性是基于路径的度量中路径长度和深度的函数。

树或有向无环图(DAG)中两个节点\(v\)\(w\)的最不常见的包含者是最深的节点,其具有\(v\)\(w\)作为后代,其中我们将每个节点定义为其自身的后代。所以如果\(v\)\(w\)有直接联系,\(w\)就是最低的共同祖先。

\[ Sim_{wup}(c_{1},c_{2}) = 2* \frac{Depth(LCS(c_{1},c _ { 2 })} {(Depth(c _ { 1 })+Depth(c _ { 2 })} $ $ $ LCS(c_{1},c_{2}) $ =层次结构中的最低节点,是$ c_{1} $,$ c_{2} $的上位词。 NLTK 有一个函数[实现 WUP 相似度](https://www.geeksforgeeks.org/nlp-wupalmer-wordnet-similarity/)。 WUP 相似度适用于单个单词的答案,但不适用于短语或句子。 ### 双语评估替补演员 这种方法通过计算候选翻译中与参考文本中的 n 元语法匹配的 n 元语法来工作。这种比较不考虑词序。 $$P _ { n } = \ frac { \ sum _ { n-grams } count _ { clip }(n-gram)} { \ sum _ { n-grams } count(n-gram)}$ 他们也有一个简短的惩罚附加到分数上,定义如下。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/443cc6e7674f5d99ed108e96a555fb69.png) 其中$r$是参考答案的长度,$c$是预测的长度。 $ $ blue = BP * exp(∞美元) BLEU 指标的综合指南可在本文中找到。 BLEU 也有一些缺点。在很大程度上,BLEU 分数是基于非常简单的文本字符串匹配。非常粗略地说,你能精确匹配的单词簇越大,BLEU 分数越高。如果答案很长,并且可以超出小短语的范围,那么这就不是最佳的衡量标准。NLTK 实现教程可以在[这里](https://machinelearningmastery.com/calculate-bleu-score-for-text-python/)找到。 ### METEOR(使用显式排序评估翻译的度量标准) 流星度量的计算有两个部分。首先,它计算所有单字的精度和召回率。然后取精度的调和*均值和召回的 9 倍。 $$ F_{mean} = \frac{10PR}{P + 9R} \]

精确度和召回率是根据单字匹配计算的。

为了考虑更长的匹配, METEOR 对给定的对齐计算罚分如下。首先,系统翻译中被映射到参考翻译中的词元的所有词元被分组到尽可能少的组块中,使得每个组块中的词元在系统翻译中处于相邻位置,并且还被映射到参考翻译中处于相邻位置的词元。

第二部分是罚函数,表述如下:

\[ penalty = 0.5 *(\ frac { \ text {组块数} } { \ text { unigrams}})^{3}数$ $ 最后,比分是: $$ score = F_{mean} * (1 -罚分)\]

结论

这里,我们介绍了视觉问答领域的进展。我们知道这个问题分为四个主要的研究领域:图像特征化、问题特征化、联合特征表示和答案生成。在回顾了每一个之后,我们看到了*年来许多研究人员用来解决这些问题的几种不同方法的概述。我们还研究了视觉问答任务的主要数据集和评估指标。我希望这篇文章对你有用。

JavaScript 库如何在网络浏览器上训练神经网络

原文:https://blog.paperspace.com/javascript-deep-learning-on-web-browsers/

多年来,JavaScript 一直是开发人员最喜爱的编程语言之一。它主要用于创建 web 浏览器 UI 和后端业务逻辑(使用 Node.js)。看看 Stack Overflow 和 GitHub 所做的调查,就其拥有的存储库数量和触发的拉请求活动而言,它一直排名第一。另一方面,深度学习被广泛用于训练机器识别模式和自动化任务。举几个例子,包括用手机提供的数据在尽可能短的距离内导航空间,检测和识别相机上的人脸,以及识别医学领域的复杂异常,等等。

也许你可能想知道 JavaScript——一种主要用于构建网络的编程语言——与人工智能有什么关系。为了回答你的问题,在本文中,我们将看看 JavaScript 如何与深度学习相结合;一个支持 Python 和 C++等语言的独占领域。下面是我们将要学习的内容。

  • 深度学习的传统工作方式
  • 构建深度学习模型的管道
  • 深度学习和流行库/框架的 JavaScript
  • 网络浏览器上的深度学习
  • 用 ml5.js 构建用于颜色分类的神经网络
  • 结论

深度学习的传统工作方式

深度学习是人工智能的一个子集,诞生于 20 世纪 50 年代模仿大脑的想法。深度学习算法是使用传统上用 Python 和 C++等语言编程的神经网络实现的。如果你不熟悉什么是神经网络,不要担心!模糊地说,神经网络是由层组成的人工大脑,通过层发送数据并学习模式。学习阶段被称为“培训”它涉及(通常)大量的数据,以及(通常)具有大量参数的模型。因此,GPU 用于加速计算。这些网络经过训练后,必须部署在某个地方,对真实数据进行预测(例如对图像和文本进行分类,检测音频或视频流中的事件,等等)。没有部署,训练一个神经网络只是浪费计算能力。

下面是一些流行的编程语言和框架,它们被广泛用于构建或使用深度学习模型。

  • Python 一直是构建 AI 的顶级选择。谷歌的 TensorFlow 和脸书的 PyTorch 是使用 Python 的最常用的深度学习框架。
  • C++ 也被用于开发最先进的神经网络,以在最少的时间内完成计算。
  • R 和 Julia 在少数情况下也用于开发定制模型;然而,这些语言仅限于少数几个功能,而且由于编程社区相对有限,可能很难构建各种神经网络。

构建深度学习模型的管道

在我们讨论 web 上的深度学习之前,让我们首先检查一下我们构建神经网络所遵循的标准管道。这让我们对网络浏览器上的神经网络实现策略有了清晰的理解,包括一些小的调整。流程是这样的:

  1. 数据来源与加载:众所周知,深度学习的核心是数据;我们拥有的数据越多,神经网络的表现就越好。所有这些数据都以张量的形式提供,这是一种 n 维数据类型。我们可以将任何数据转换成张量格式,从基本数字到文本、图像和视频。使用张量的一些其他优势包括多进程数据加载和批处理,这意味着您可以同时使用多个张量训练您的神经网络。
  2. 定义模型:加载数据后的第一步是定义神经网络。这是整个管道的大脑,将不断从提供的数据中学习。然而,这一步涉及许多超参数和实验,以实现最先进的性能。几种常用的超参数有batch_sizenumber_of_epochsloss_functionoptmization_functionlearning_rate。接下来,我们定义神经网络,其中我们决定模型所需的总层数和参数。同样,根据数据、其结构和所需的输出,我们可以选择不同类型的层和架构。
  3. 训练模型:在上一步中,我们已经了解了设计模型需要什么。然而,这一进程仍未完成。只有在通过模型对数据进行训练时,才能使用该模型。通过迭代地传递数据,并使用损失函数更新损失,针对一定数量的时期训练具有定义的参数的模型。然后,backpropagation算法会在每次迭代中改进神经网络的指标和准确性。训练过程完成后,使用框架支持的任何特定格式保存模型的权重。我们可以称之为训练模型。
  4. 模型到云和生产:被训练的模型通常是大规模的,通常依赖于所使用的神经网络;它们从千字节到千兆字节不等。为了在生产中使用它们,我们将它们存储在云上,并再次加载它们进行部署。

在下一节中,我们将讨论使用 JavaScript 将模型嵌入 web 浏览器的机制。

深度学习的 JavaScript

如前所述,JavaScript 是自 1995 年诞生以来最受欢迎的编程语言之一。然而,直到 2015 年,深度学习和人工智能相关的学科都没有考虑这一点,原因有三:

  1. 大多数人有一种直觉,JS 是一种慢语言。
  2. 像矩阵乘法和归一化这样的操作很难实现。
  3. 大多数用于实现深度学习的库(如 Scikit-learn、TensorFlow 等。)与 Python 一起使用。

然而,现在有一些基于 JavaScript 的深度学习库。让我们看一看。

流行的 JavaScript 库和深度学习框架

  • tensor flow . js:tensor flow . js 是一个 JavaScript 库,用于构建神经网络并在 web 浏览器上直接使用它们。这个库的一个很酷的特性是,它允许转换现有的基于 Python 的 TensorFlow 模型,以便在 web 浏览器上工作。它还允许直接在浏览器中实现面部识别、移动检测等高级网络。
  • 大脑 。js:****brain . js 是目前流行的构建神经网络的 JavaScript 库之一;它还支持直接从 web 浏览器和 Node.js 访问 GPU。许多典型的神经网络如 CNN 和 RNNs 也可以使用该库实现。
  • mlT25。js: 这是一个构建在 TensorFlow.js 之上的友好框架;它为直接使用 JavaScript 开始深度学习的人提供了良好的社区支持和文档。
  • ConvNetJS: ConvNetJS 是最早用于深度学习的 JavaScript 库之一。它最初是由 Andrej Karpathy 开发的,用于实现简单的分类和卷积模型。ConvNetJS 仍然由其社区维护,并支持一些很酷的网络。

现在我们已经了解了深度学习管道以及可用的 JavaScript 库,是时候看看如何将 JavaScript 与深度学习相结合了。

网络浏览器上的深度学习

在台式机和笔记本电脑上,Chrome、Firefox 和 Safari 等网络浏览器是用户访问互联网内容和服务的主要方式。由于这种广泛的覆盖范围,web 浏览器是部署深度学习模型的逻辑选择,只要模型所需的数据类型可以从浏览器中获得。但是从浏览器中可以获得哪些类型的数据呢?答案是很多!现在让我们来讨论一些真实的用例。考虑 YouTube,那里有几十亿的视频;Instagram,里面都是图片;还有推特,里面全是文字。

问你一个问题:当你在 YouTube 或 Spotify 上搜索歌曲或视频时,你会在它们旁边看到你可能感兴趣的其他歌曲的推荐。想知道这是怎么发生的吗?这种逻辑背后的魔力由大量运行在浏览器和后端服务器上的机器学习和深度学习算法组成。这就是 JavaScript 发挥关键作用的地方,用浏览器获取和绑定数据,并设计智能算法。下面是几个例子,深度学习算法在带有 JavaScript 的 web 浏览器上运行。

Real-Time Face Detection on Web browser with TensorFlow.js

Real-time text toxicity detection web browsers

好吧!现在让我们深入到代码中,我们将学习预测我们在 web 浏览器上看到的颜色。

建立用于颜色分类的神经网络

在本节中,我们将使用 ml5.js 库在 web 浏览器上创建和训练一个神经网络。我们的目标是用我们训练好的神经网络来预测网络浏览器中给定颜色的名称。另外,确保你的电脑上已经安装了node,否则你可以通过下面的链接来安装。Node 允许您运行和安装所有必需的包,以便用 ml5.js 创建我们的 NNs。

步骤 1:创建 NPM 和必要的文件

要开始我们的项目,首先,您必须创建一个 npm 项目。这可以通过在终端或命令提示符下使用命令npm init来完成。另外,确保你的电脑上安装了nodenpm。如果没有,请按照该网站这里的指示进行操作。下面是我创建项目后的终端截图。您可以在此添加项目的必要描述。如下图所示,该命令创建一个 package.json 文件,该文件类似于项目的入口文件。它包含项目中安装的所有依赖项和库版本的列表。

步骤 2:向 index.html 添加脚本

在这一步,我们将编写一些 HTML。我们将包括 P5。JS-一个用于创造性编码的 JavaScript 库。这有助于我们形象化我们的训练,并在我们的项目中制作一些动画。下面是代码片段,

<html>
   <head>
      <meta charset="UTF-8" />
      <title>Color Classifier - Neural Network</title>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.dom.min.js"></script>
      <script
         src="https://unpkg.com/ml5@0.5.0/dist/ml5.min.js"
         type="text/javascript"
         ></script>
   </head>
   <body>
      <h1>Color Classifier - Neural Network</h1>
      <script src="sketch.js"></script>
   </body>
</html> 

这里,如您所见,P5JS 和 ml5.js 已经添加到 HTML 脚本标记中。你可以认为这是将你的 p5JS(可视化库)和 ml5(深度学习库)导入到你的网络浏览器中。

<body>标签中,添加了一个H1标签,其中包含项目的名称和一个附加的脚本标签 sketch.js ,其中包含所有深度学习逻辑。在深入研究这个概念之前,下载培训数据并将其放到本地机器上。

步骤 3:下载数据集

你可以从链接这里下载数据集。确保在项目中创建一个名为数据的文件夹,并将下载的数据移动到那里。

{
 "entries": [
   {
     "b": 155,
     "g": 183,
     "label": "green-ish",
     "r": 81,
     "uid": "EjbbUhVExBSZxtpKfcQ5qzT7jDW2"
   },
   {
     "b": 71,
     "g": 22,
     "label": "pink-ish",
     "r": 249,
     "uid": "fpqsSD6CvNNFQmRp9sJDdI1QJm32"
   },
 ]
} 

JSON 文件将由上述条目组成,这里 r、g 和 b 代表红色、绿色和蓝色,它们是给我们神经网络的输入。键“标签”是单个字典中给定的特定 RGB 值的相应输出。下一步是建立一个神经网络。

步骤 4:构建神经网络

应该定义一些 JavaScript 变量来使它们与我们的浏览器交互。下面是代码片段。

 let neuralNetwork;
 let submitButton;

 let rSlider, gSlider, bSlider;
 let labelP;
 let lossP; 

neuralNetwork变量中,神经网络将在即将到来的函数中定义, submitButton 将输入发送给训练好的模型。滑块按钮将有助于选择不同的颜色作为输入并进行预测。最后,labelP变量将用于呈现输出文本,lossP 用于打印精度。

接下来,让我们定义setup()函数来加载所有的变量;下面是代码片段。

function setup() {
 lossP = createP("loss");

 createCanvas(100, 100);

 labelP = createP("label");

 rSlider = createSlider(0, 255, 255);
 gSlider = createSlider(0, 255, 0);
 bSlider = createSlider(0, 255, 255);

 let nnOptions = {
   dataUrl: "data/colorData.json",
   inputs: ["r", "g", "b"],
   outputs: ["label"],
   task: "classification",
   debug: true,
 };
 neuralNetwork = ml5.neuralNetwork(nnOptions, modelReady);
} 

在这个函数中,使用 P5 JS 中的createP()函数将 loss 和 label 变量设置为losslabel字符串。这些是简单的字符串。接下来的三个变量(rSlider, gSlider, bSlider)用于使用createSlider()功能创建滑块,并分别对应于redgreenblue的颜色代码。

接下来,将神经网络的整个配置添加到之前创建的列表变量中(neural network)。一个dataUrl标签指向数据集。输入 r、g 和 b(数据集键)被附加到一个列表中,输出对应于标签键。定义了一个称为分类的额外变量,以向神经网络传达分类是期望的操作。

最后,使用 ml5 库中的neuralNetwork函数创建一个神经网络,其中使用nnOptions变量作为参数发送所有选项。

步骤 5:设置超参数并保存模型

在这一步中,将通过对neuralNetwork变量使用train()方法来训练声明的神经网络。

function modelReady() {
 neuralNetwork.normalizeData();
 const trainingOptions = {
   epochs: 20,
   batchSize: 64,
 };
 neuralNetwork.train(trainingOptions, whileTraining, finishedTraining);
 // Start guessing while training!
 classify();
}

function whileTraining(epoch, logs) {
 lossP.html(`Epoch: ${epoch} - loss: ${logs.loss.toFixed(2)}`);
}

function finishedTraining(anything) {
 console.log("done!");
} 

现在要运行这个项目,您可以通过运行任何 web 服务器来打开 index.html 文件,或者您可以将这些文件部署到 netlify。我目前使用“python3 -m http.server”在本地 web 浏览器上为项目提供服务。现在,它运行在端口 8000 上,您可以导航到 localhost:8000 并查看下面的屏幕截图。

Training and Predictions on Web Browser using ml5.js

这段代码是在 ml5.js 示例的基础上修改的,要查看更多类似的内容,请浏览下面的链接

结论

在本文中,我们已经了解了如何使用 Javascript 库在 Web 浏览器上训练神经网络,以及它们与传统管道有何不同。此外,我们还研究了一个示例,其中我们使用 ml5.js(一个用于使用 js 构建神经网络的库)来创建颜色分类器。如果你还想探索更多的例子,并想建立自己的网络 checkout TensorFlow.js 和 Brain.js 库,在那里你可以处理巨大的图像、文本和音频数据集。

现已推出:在渐变笔记本上运行 JAX

原文:https://blog.paperspace.com/jax-on-paperspace/

我们的大多数读者都熟悉深度学习领域的两大巨头,即 PyTorch 和 TensorFlow。这些库对于任何深度学习专业人士或爱好者来说都是必不可少的,Gradient 通过依赖 Docker 映像来运行笔记本,消除了设置和安装这些包的麻烦。

我们的用户使用的许多容器都预先打包为 Gradient Notebooks 运行时,这允许我们的用户快速访问支持 GPU 的笔记本,同时完成文件和安装。你也可以通过高级选项直接将保存在 Docker Hub 或 Nvidia NGC 等服务上的任何工作空间 URL 或容器输入到笔记本中。

我们已经创建了一个新的支持 JAX 的容器,可以用于渐变笔记本,这篇文章旨在帮助指导用户开始使用渐变笔记本 JAX。

什么是 JAX?

JAX 是深度学习社区的后起之秀之一。谷歌为高性能数值计算设计的机器学习库,自发布以来就迅速走红。

根据他们的文件,“JAX 在 CPU,GPU 和 TPU 上是 NumPy,在高性能机器学习研究方面有很大的自动差异。”【 1 】。在实践中,JAX 的行为类似于流行的库 NumPy,但增加了自动签名和 XLA(加速线性代数),这允许数组操作利用 GPU 支持的机器。它可以通过与它的前身的许多巧妙的不同来处理这个动作。

主要的变化有四个方面:增加了自动区分、JAX 向量化、JIT(实时)编译和 XLA。

  • JAX 的自动签名的更新版本允许 JAX 自动区分 NumPy 和 Python 代码。这包括使用许多 Python 特性,如循环、ifs、递归、闭包等等。它也可以取导数或导数的导数。这允许通过 grad 的反向模式微分(也称为反向传播)以及正向模式微分,并且两者可以任意组合成任意顺序。【2】
  • 通过vmap函数的 JAX 矢量化,即矢量化地图,允许跨阵列轴进行熟悉的函数映射。为了提高性能,循环被保留在函数的原始操作中。【3】
  • JAX 包含一个函数转换,JIT,用于现有函数的即时编译。
  • JAX 还利用 XLA 以一种针对 GPU 或 TPU 等加速器硬件优化的方式运行 NumPy 代码。默认情况下,XLA 会在幕后编译,库调用会被及时编译和执行。JAX 甚至可以使用它的单函数 API 将你自己的 Python 函数实时编译成 XLA 优化的内核。编译也是任意组合的,所以复杂的算法可以优化实现,而不必编写非 Python 代码。【1】

关于 JAX 的更多信息,请查看文档中的快速入门页面。

为什么用 JAX?

JAX 的效用可以简洁地归结为取代和超越 NumPy,用于 GPU。鉴于它本质上是亲笔签名的 2.0,所有级别的用户都可以从 JAX 获得效用。

此外,JAX 能够取代深度学习库(如 Keras、TensorFlow 和 PyTorch)提供的许多功能。基于 JAX 的 DL 库,如 ElegyFlaxObJAX 都可以执行与主线 DL 库相同的任务,但与 Keras 或 Torch 套件相比,它们还处于相对早期的开发阶段。

按照以下说明试用 JAX 渐变,立即开始使用 JAX 渐变。

如何制作支持 JAX 的渐变笔记本:

要创建支持 JAX 的渐变笔记本,只需遵循几个简单的步骤。在创建笔记本页面中,首先滚动运行时部分,为您的笔记本选择一个 GPU,导航到并单击切换到“高级选项”在此部分中,将以下内容粘贴到各自的文本框中:

  • 工作区网址:https://github.com/gradient-ai/jax-gradient
  • 容器名: cwetherill/jax:最新
  • 命令:jupyter lab --allow-root --ip=0.0.0.0 --no-browser --ServerApp.trust_xheaders=True --ServerApp.disable_check_xsrf=False --ServerApp.allow_remote_access=True --ServerApp.allow_origin=''*'' --ServerApp.allow_credentials=True

然后你需要做的就是点击开始笔记本!

后续步骤

一旦你的笔记本开始运转,查看/notebooks文件夹中的示例笔记本,开始使用 JAX。尤其是 TensorFlow 笔记本,它有趣地展示了 JAX 如何帮助您完成 DL 任务。

点击此处访问该回购的 Github。

来源:

  1. https://jax . readthe docs . io/en/latest/notebooks/quick start . html
  2. https://py torch . org/docs/stable/notes/autograded . html #:~:text = autograded 是逆向自动微分,根是输出张量
  3. https://jax . readthe docs . io/en/latest/notebooks/quick start . html?highlight =自动矢量化#使用 vmap 自动矢量化

加入我们的 SIGGRAPH 2017

原文:https://blog.paperspace.com/join-us-at-siggraph-2017/

参观我们的展位!

作为 SIGGRAPH 2017SIGGRAPH 2017的一部分,来参观车库的 Paperspace 吧。我们很乐意打声招呼!我们的展位位于 #SG15

免费展览通行证!

为了让我们的社区更方便,我们与 SIGGRAPH 合作,当您使用 promocode PSSG2017 注册时,我们将提供免费的展厅门票

SIGGRAPH 2017

第 44 届 SIGGRAPH 计算机图形和交互技术会议是一次为期五天的跨学科教育体验,包括世界上最负盛名的计算机图形研究论坛、数字媒体中的创造性冒险、身临其境的现实、新兴的交互技术、先进的移动系统以及创造性协作的实践机会。

Jupyter 笔记本简单易用!(有 GPU 支持)

原文:https://blog.paperspace.com/jupyter-notebook-with-a-gpu-the-easy-way/

1.创建一台图纸空间 GPU 计算机

您可以选择我们的任何 GPU 类型(GPU+/P5000/P6000)。在本教程中,我们将选择默认的 Ubuntu 16.04 基础模板。

不习惯命令行?

试试已经安装了 Jupyter(和许多其他软件)的 Paperspace 机器学习模板吧!使用促销代码 MLIIB2 购买价值 5 美元的新机!

重要提示:您需要添加一个公共 IP 地址才能访问我们正在创建的 Jupyter 笔记本。请确保选择该选项。如果您忘记了,您可以随时通过控制台添加它

2.安装 cuda/dock/NVIDIA dock

这是一个非常简单的脚本。一旦您通过 SSH 登录到您的新机器,只需通过将以下内容粘贴到您的终端来运行脚本:

wget -O - -q 'https://gist.githubusercontent.com/dte/8954e405590a360614dcc6acdb7baa74/raw/d1b5a01ed0b9252654016d2a9a435dc8b4c045e7/install-CUDA-docker-nvidia-docker.sh' | sudo bash 

出于好奇:你可以在这里找到脚本https://gist . github . com/DTE/8954 e 405590 a 360614 DCC 6 acdb 7 baa 74

完成后,您需要通过键入以下命令来重新启动机器:

sudo shutdown -r now 

3. Run jupyter

当机器重新启动时,你就可以开始工作了!键入以下命令来运行包含 Jupyter 的 docker 容器。它将在你机器的端口 8888 上运行一个服务器。

sudo nvidia-docker run --rm --name tf-notebook -p 8888:8888 -p 6006:6006 gcr.io/tensorflow/tensorflow:latest-gpu jupyter notebook --allow-root 

您的笔记本可以从任何计算机上访问,但需要进入网络浏览器并输入您机器的公共 IP 地址和端口:http://PUBLIC_IP:8888/

您可以通过打开笔记本并键入以下内容来确认 GPU 正在工作:

from tensorflow.python.client import device_lib

def get_available_devices():
    local_device_protos = device_lib.list_local_devices()
    return [x.name for x in local_device_protos]

print(get_available_devices()) 

通过立即注册,立即开始使用您自己的 Paperspace 桌面!

Kando 和 Paperspace 合作将先进的机器学习引入市政系统监控

原文:https://blog.paperspace.com/kando-and-paperspace-partner-to-bring-advanced-machine-learning-to-municipal-systems-monitoring/

Kando 是一家为市政公用事业提供智能废水管理解决方案的技术公司,已经集成了 Paperspace 的梯度机器学习*台,以支持其领先的 Clear 上游废水事件监控系统。

Kando’s end-to-end solution Clear Upstream provides continuous awareness of events in wastewater networks.

通过合作,Kando 为其技术堆栈带来了最先进的机器学习工具集和 MLOps *台。

机器学习正在帮助物联网公司利用自动化和分析来实现大数据解决方案。Kando 正在利用这一趋势,通过部署 ML 解决方案来识别污染风险,查明污染源,并评估需要快速应急响应的影响,以确保城市和居民的安全。

因此,像波特兰的埃尔帕索水务净水服务这样的客户能够获得实时、持续的监控。

Paperspace 的销售副总裁 Todd Feinroth 表示:“我们很高兴与 Kando 合作,为关键的市政基础设施监控带来一流的机器学习工具。我们期待帮助 Kando 建立其机器学习能力,并为世界各地的废水处理设施提供领先的解决方案。”

“我们与 Paperspace 的合作将提升我们系统的高级分析能力,使我们能够更好地支持城市远程持续控制污水质量。因此,我们将开始看到更多的废水再利用、更清洁的环境和更健康的社区。”

Kando 首席执行官 Ari Goldfarb

Kando 和 Paperspace 将携手合作,为环境风险和公共健康管理带来先进的机器学习能力。

要了解更多关于 Kando 及其令人兴奋的项目和客户的信息,请访问: www.kando.eco

宣布官方 Paperspace Meetup:“工作中的机器学习”

原文:https://blog.paperspace.com/machine-learning-at-work/

你好,朋友。你是:

  • A software developer tries to get a general idea of available products and services.
  • A business analyst, product manager, marketing manager or consultant has heard that machine learning can help improve the products he or she is responsible for, but has no time to browse all the options.
  • A data scientist wants to broaden their toolkit, from basic statistical techniques to the latest and best artificial intelligence toolkit.
  • The leader of a business line tries to find the right way for their organization to put intelligence into their team's software, process and culture.

或者可能是世界上最重要的风险投资公司之一的有限合伙人,希望通过建立一个包含奇妙工具和其他资源的剧本来在一个新生领域建立思想领导力?:)那么你来对地方了——这个地方是我们新的聚会,“工作中的机器学习”

Machine Learning Google Trends Interest over Time

随着机器学习扩展到企业战略,我们认为培养一个学者、学生和专业人士可以一起实验和相互学习的社区非常重要。我们新成立的 meetup 小组“工作中的机器学习”(Machine Learning in Work)旨在通过一个新颖的程序来实现这一目标,该程序允许成员从研究和实践中学习,同时为他们提供空间,让他们用真实代码来破解和解决问题。

每个 meetup 都是一个小型的黑客马拉松,通过结构化的竞赛来强化所讨论的内容,希望社区能够学习和分享实用的技术和技能。

我们在 5 月 18 日的第一次 meetup 将举办非常特别的演讲,内容是关于对抗性自动编码器、我们的合作伙伴 Insight 的惊人工作、他们最*的 AI 研究员班以及 OpenAI 的最新工作。

我们将定期在纽约举办这些聚会,所以请随意注册以获取更多更新。如果你想为一项活动做贡献,我们总是在寻找有趣的话题——请联系 george@paperspace.com。

纸空间上的机器学习

原文:https://blog.paperspace.com/machine-learning-on-paperspace/

我们在 Paperspace 的大部分时间都在制作运行在 GPU 上的软件。鉴于我们对硬件的熟悉,我们认为用最新最棒的机器学习和深度学习软件入门会很容易。我们很快意识到,启动并运行一台新机器是一个令人惊讶的复杂过程,包括驱动程序版本、过时的教程、CUDA 版本、OpenBLAS 编译、Lua 配置等等。

运行在云端的 Ubuntu 桌面

更新:该产品最*出现在 TechCrunch

我们在 Paperspace 的大部分时间都在制作运行在 GPU 上的软件。鉴于我们对硬件的熟悉,我们认为用最新最棒的机器学习和深度学习软件入门会很容易。我们很快意识到,启动并运行一台新机器是一个令人惊讶的复杂过程,包括驱动程序版本、过时的教程、CUDA 版本、OpenBLAS 编译、Lua 配置等等。

与此同时,在云中运行 GPU 的几个选项非常昂贵,过时并且没有针对这种类型的应用进行优化。

很明显,云中需要一个轻松的虚拟机,由强大的 GPU 支持,并预装所有最新的 ML 框架。

我们开始思考人们将如何与他们的纸空间机器交互,以寻找一个简单但仍然强大和熟悉的解决方案。

重新思考界面

在测试期间,我们很快了解到,虽然许多人精通终端,但他们倾向于在桌面环境中工作。为此,我们构建了一个直接在 web 浏览器中运行的完整桌面体验。

因为浏览器非常强大,我们也尝试直接在 Chrome 内部构建自己的终端。

基于网络的终端

当然,你总是可以用 SSH 连接,但是我们希望你可以用这两种新颖的方式与你的机器学习实例进行交互。

预配置模板:ML-in-a-box

从提供 web 服务器到运行 CAD 设计*台,我们在 Paperspace 内部使用模板,因此使用黄金标准的机器学习模板是有意义的。模板是运行虚拟机的最强大的特性之一——你只需要把所有的东西安装一次,然后你就可以克隆它,并根据你的需要共享它。

为了获得一个真正伟大的机器学习设置,我们遵循了很多指南,其中包括:
https://github.com/saiprashanths/dl-setup

http://Guanghan . info/blog/en/my-works/building-our-personal-deep-learning-rig-gtx-1080-Ubuntu-16-04-cuda-8-0rc-cud nn-7-tensorflowmxnetcaffedarknet/

我们今天发布的版本包括(除其他外):

  • 库达
  • cuDNN
  • TensorFlow
  • Python (numpy、ipython、matplotlib 等)
  • OpenBLAS
  • 咖啡
  • 提亚诺
  • 火炬
  • Keras
  • 最新的 GPU 驱动程序

当你今天启动一台 Paperspace Linux 机器时,你得到的是一台刚刚工作的机器。我们一直在改善基础形象,所以如果我们忘记了什么,请告诉我们!

更多 GPU 选项

我们的大部分基础设施都是围绕“虚拟”GPU 的 NVIDIA 网格架构构建的。对于更密集的任务,如模型训练、视觉效果和数据处理,我们需要找到一个更强大的 GPU。

与网格卡不同,我们开始寻找一种能够以“直通”模式运行的卡。在这种情况下,虚拟机可以完全访问卡,而不会产生任何虚拟化开销。

今天,我们有几个不同的 GPU 选项:NVIDIA M4000 是一个经济高效但功能强大的卡,而 NVIDIA P5000 是基于新的 Pascal 架构构建的,并针对机器学习和超高端模拟工作进行了大量优化。我们将在未来几个月增加几个选项。我们希望也包括一些 AMD 最新的 ML 优化卡,甚至可能包括专门为智能应用设计的硬件。

下一步是什么

我们对这将开启的可能性感到非常兴奋。现在还为时过早,我们肯定还有很多工作要做,但是早期的反馈非常积极,我们迫不及待地想让你尝试一下。

我们将为这一产品推出越来越多的功能,并建立一个由数据科学家和应用人工智能专业人员贡献技术内容和教程的社区。例如,Felipe Ducau(深度学习先驱 Yann LeCun 的学生)最*写了被广泛阅读和转发的“py torch 中的自动变分自动编码器”。Lily Hu 在 Insight AI Fellowship 期间创造了一种分离医学图像中重叠染色体的算法,解决了人工智能开放网络(AI-ON)的一个突出问题。

要开始使用您自己的 ML-in-a-box 设置,在此注册。

订阅我们的博客,获取所有最新公告。

< 3 来自 Paperspace

使用具有 TensorFlow 1.14 和 Keras 的掩模 R-CNN 的对象检测

原文:https://blog.paperspace.com/mask-r-cnn-in-tensorflow-2-0/

Mask R-CNN 是一组脸书 AI 研究人员在 2017 年开发的基于深度卷积神经网络(CNN)的物体检测模型。该模型可以为图像中每个检测到的对象返回边界框和遮罩。

该模型最初是使用 Caffe2 深度学习库在 Python 中开发的。原始源代码可以在 GitHub 上找到。为了用更流行的库(如 TensorFlow)支持 Mask R-CNN 模型,有一个流行的开源项目叫做 Mask_RCNN ,它提供了一个基于 Keras 和 TensorFlow 1.14 的实现。

谷歌于 2020 年 9 月正式发布 TensorFlow 2.0。与 TensorFlow \(\geq\) 1.0 相比,TensorFlow 2.0 组织得更好,也更容易学习。可惜的是, Mask_RCNN 项目还不支持 TensorFlow 2.0。

本教程使用 TensorFlow 1.14 版本的 Mask_RCNN 项目来使用自定义数据集进行预测和训练 Mask R-CNN 模型。在另一个教程中,该项目将被修改,使面具 R-CNN 兼容 TensorFlow 2.0

本教程涵盖以下内容:

  • Mask_RCNN 项目概述
  • 使用 TensorFlow 1.14 进行对象检测
  • 准备模型配置参数
  • 构建掩模 R-CNN 模型架构
  • 加载模型权重
  • 读取输入图像
  • 检测物体
  • 可视化结果
  • 预测的完整代码
  • 下载训练数据集
  • 准备训练数据集
  • 准备模型配置
  • TensorFlow 1.14 中的训练掩码 R-CNN
  • 结论

Mask _ RCNN 项目概述

这个 Mask_RCNN 项目是开源的,可以在 GitHub 上的获得麻省理工学院的许可,允许任何人免费使用、修改或分发代码。

这个项目的贡献是通过构建 Mask R-CNN 模型中的所有层,并提供一个简单的 API 来训练和测试它,从而支持 TensorFlow \(\geq\) 1.0 中的 Mask R-CNN 对象检测模型。

掩模 R-CNN 模型预测图像中对象的类别标签、边界框和掩模。这是一个模型可以检测到什么的例子。

项目第一次发布( Mask_RCNN 1.0 )发布于 2017 年 11 月 3 日。最新发布( Mask_RCNN 2.1 )发布于 2019 年 3 月 20 日。此后,没有发布新的版本。

要在您的 PC 上获得该项目,只需根据下一个命令克隆它:

$ git clone https://github.com/matterport/Mask_RCNN.git

也可以从这个链接下载 ZIP 文件形式的项目。让我们快速浏览一下本地可用的项目内容。

在撰写本教程时,该项目有 4 个目录:

  1. mrcnn :这是保存项目 Python 代码的核心目录。
  2. samples : Jupyter 笔记本提供了一些使用项目的例子。
  3. 图像:测试图像的集合。
  4. 资产:一些带注释的图片。

最重要的目录是 mrcnn ,因为它保存了项目的源代码。它包含以下 Python 文件:

  • __init__.py:将 mrcnn 文件夹标记为 Python 库。
  • model.py:具有用于构建层和模型的函数和类。
  • config.py:保存一个名为Config的类,该类保存一些关于模型的配置参数。
  • utils.py:包括一些帮助函数和类。
  • visualize.py:可视化模型的结果。
  • parallel_model.py:支持多个 GPU。

项目根目录下的一些文件是:

基于 requirements.txt 文件,TensorFlow 版本必须至少为 1.3.0。对于 Keras,必须是 2.0.8 或更高版本。

有两种方法可以使用该项目:

  1. 使用 pip 安装。
  2. mrcnn 文件夹复制到您将使用该项目的位置。在这种情况下,确保安装了 requirements.txt 文件中所有需要的库。

要安装该项目,只需从命令提示符或终端发出以下命令。对于 Windows 以外的*台,将“python”替换为“python3”。

python setup.py install

使用项目的另一种方法是将 mrcnn 文件夹复制到项目将被使用的地方。假设有一个名为“Object Detection”的目录,其中有一个名为object_detection.py的 Python 文件,它使用了 mrcnn 文件夹中的代码。然后,简单地复制“对象检测”目录中的 mrcnn 文件夹。

下面是目录结构:

Object Detection
	mrcnn
	object_detection.py

现在我们准备使用 Mask_RCNN 项目。下一节讨论如何在 TensorFlow \(\geq\) 1.0 中使用该项目。

tensor flow 1 中的对象检测

在开始本节之前,请确保安装了 TensorFlow 1 ($\geq$1.3.0)。您可以使用以下代码检查版本:

import tensorflow

print(tensorflow.__version__)

使用 Mask_RCNN 项目检测图像中的对象的步骤如下:

  1. 准备模型配置参数。
  2. 构建掩膜 R-CNN 模型架构。
  3. 加载模型重量。
  4. 读取输入图像。
  5. 检测图像中的对象。
  6. 将结果可视化。

本节构建了一个示例,该示例使用预训练的掩码 R-CNN 来检测 COCO 数据集中的对象。接下来的小节将讨论上面列出的每个步骤。

1。准备模型配置参数

为了建立屏蔽 R-CNN 模型,必须指定几个参数。这些参数控制非最大抑制(NMS)、联合交集(IoU)、图像大小、每幅图像的 ROI 数量、ROI 合并层等。

mrcnn文件夹有一个名为config.py的脚本,其中有一个名为Config的类。这个类有一些参数的默认值。您可以扩展该类并覆盖一些默认参数。下面的代码创建了一个名为SimpleConfig的新类,它扩展了mrcnn.config.Config类。

import mrcnn.config

class SimpleConfig(mrcnn.config.Config):
	...

必须覆盖的一个关键参数是类的数量,默认为 1。

NUM_CLASSES = 1

在这个例子中,模型检测来自 COCO 数据集的图像中的对象。这个数据集有 80 个类。记住,背景必须被视为一个附加类。因此,班级总数为 81。

NUM_CLASSES = 81

另外两个参数需要仔细赋值,分别是GPU_COUNTIMAGES_PER_GPU。它们分别默认为 1 和 2。

这两个变量用于计算批量大小:

BATCH_SIZE = IMAGES_PER_GPU * GPU_COUNT

假设使用默认值,那么批量大小为2*1=2。这意味着两个图像被一次输入到模型中。因此,用户必须一次输入 2 幅图像。

在某些情况下,用户只对检测单个图像中的对象感兴趣。因此,IMAGES_PER_GPU属性应该设置为 1。

GPU_COUNT = 1
IMAGES_PER_GPU = 1

下面是配置类的完整代码。NAME属性是配置的唯一名称。

import mrcnn.config

class SimpleConfig(mrcnn.config.Config):
	NAME = "coco_inference"

	GPU_COUNT = 1
	IMAGES_PER_GPU = 1

	NUM_CLASSES = 81

2。构建掩膜 R-CNN 模型架构

为了构建 Mask R-CNN 模型架构,mrcnn.model脚本有一个名为MaskRCNN的类。该类的构造函数接受 3 个参数:

  1. mode:不是"training"就是"inference"
  2. config:配置类的一个实例。
  3. model_dir:保存训练日志和训练重量的目录。

下一个例子创建了一个mrcnn.model.MaskRCNN类的实例。创建的实例保存在model变量中。

import mrcnn.model

model = mrcnn.model.MaskRCNN(mode="inference", 
                             config=SimpleConfig(),
                             model_dir=os.getcwd())

Keras 模型保存在实例的keras_model属性中。使用该属性,可以打印模型的概要。

model.keras_model.summary()

模式架构大;下面只列出了上下 4 层。名为mrcnn_mask的最后一层仅返回前 100 个 ROI 的掩膜。

___________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to
===========================================================================
input_image (InputLayer)        (None, None, None, 3 0         
___________________________________________________________________________
zero_padding2d_1 (ZeroPadding2D (None, None, None, 3 0           input_image[0][0]
___________________________________________________________________________
conv1 (Conv2D)                  (None, None, None, 6 9472        zero_padding2d_1[0][0]
___________________________________________________________________________
bn_conv1 (BatchNorm)            (None, None, None, 6 256         conv1[0][0]
___________________________________________________________________________
...
___________________________________________________________________________
mrcnn_mask_bn4 (TimeDistributed (None, 100, 14, 14,  1024        mrcnn_mask_conv4[0][0]
___________________________________________________________________________
activation_74 (Activation)      (None, 100, 14, 14,  0           mrcnn_mask_bn4[0][0]
___________________________________________________________________________
mrcnn_mask_deconv (TimeDistribu (None, 100, 28, 28,  262400      activation_74[0][0]
___________________________________________________________________________
mrcnn_mask (TimeDistributed)    (None, 100, 28, 28,  20817       mrcnn_mask_deconv[0][0]
===========================================================================
Total params: 64,158,584
Trainable params: 64,047,096
Non-trainable params: 111,488

3。加载模型重量

最后一小节创建了模型架构。该子部分使用load_weights()方法在创建的模型中加载权重。它是 Keras load_weights()方法的修改版本,除了能够排除一些层之外,还支持多 GPU 使用。

使用的两个参数是:

  1. filepath:接受权重文件的路径。
  2. by_name:如果为真,那么每个层根据其名称分配权重。

下一段代码调用load_weights()方法,同时传递权重文件mask_rcnn_coco.h5的路径。这个文件可以从这个链接下载。

model.load_weights(filepath="mask_rcnn_coco.h5", 
                   by_name=True)

4。读取输入图像

一旦创建了模型并加载了它的权重,接下来我们需要读取一个图像并将其提供给模型。

下一段代码使用 OpenCV 读取图像,并将其颜色通道重新排序为 RGB,而不是 BGR。

import cv2

image = cv2.imread("3627527276_6fe8cd9bfe_z.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

下图显示了我们刚刚阅读的图像。图片可在链接获得。

5。检测物体

给定模型和输入图像,可以使用detect()方法检测图像中的物体。它接受两个参数:

  1. images:图像列表。
  2. verbose:设置为 1,则打印一些日志信息。

下面的代码调用了detect()方法。请注意,分配给images参数的列表长度必须等于批处理大小。基于我们设置的GPU_COUNTIMAGES_PER_GPU配置属性,批量大小为 1。因此,列表必须只有一个图像。检测的结果在r变量中返回。

r = model.detect(images=[image], 
                 verbose=0)

如果传递了一个以上的图像(如images=[image, image, image]),则会引发以下异常,指示images参数的长度必须等于BATCH_SIZE配置属性。

...
File "D:\Object Detection\Pure Keras\mrcnn\model.py", in detect
	assert len(images) == self.config.BATCH_SIZE, "len(images) must be equal to BATCH_SIZE"

AssertionError: len(images) must be equal to BATCH_SIZE

对于每个输入图像,detect()方法返回一个字典,该字典保存关于检测到的对象的信息。为了返回关于输入到模型中的第一幅图像的信息,索引0与变量r一起使用。

r = r[0]

下面的代码打印字典中的键。字典中有 4 个元素,具有以下键:

  1. rois:每个被检测物体周围的方框。
  2. class_ids:对象的类别 id。
  3. scores:每个对象的类分数。
  4. masks:面具。
print(r.keys())
dict_keys(['rois', 'class_ids', 'scores', 'masks'])

6。将结果可视化

一旦detect()方法完成,就该可视化检测到的对象了。mrcnn.visualize脚本用于此目的。mrcnn.visualize.display_instances()功能用于显示检测框、掩码、类名和分数。

在该函数接受的参数中,使用了以下参数:

  • image:绘制检测框和遮罩的图像。
  • boxes:检测盒。
  • masks:检测到的屏蔽。
  • class_ids:检测到的类别 id。
  • class_names:数据集中的类名列表。
  • scores:每个对象的预测分数。

在下面的代码中,类名准备在CLASS_NAMES列表中。注意第一个类的类标签是BG用于背景。调用mrcnn.visualize.display_instances()函数来显示带注释的图像。

import mrcnn.visualize

CLASS_NAMES = ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']

r = r[0]

mrcnn.visualize.display_instances(image=image, 
                                  boxes=r['rois'], 
                                  masks=r['masks'], 
                                  class_ids=r['class_ids'], 
                                  class_names=CLASS_NAMES, 
                                  scores=r['scores'])

执行该函数后,会显示一个图形(如下所示),在该图形上绘制了方框、掩码、班级分数和标签。

至此,讨论了使用 Mask_RCNN 项目检测对象所需的所有步骤。

预测的完整代码

下面列出了使用 Mask_RCNN 项目检测图像中的对象的完整代码。

import mrcnn
import mrcnn.config
import mrcnn.model
import mrcnn.visualize
import cv2
import os

CLASS_NAMES = ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']

class SimpleConfig(mrcnn.config.Config):
    NAME = "coco_inference"

    GPU_COUNT = 1
    IMAGES_PER_GPU = 1

    NUM_CLASSES = len(CLASS_NAMES)

model = mrcnn.model.MaskRCNN(mode="inference", 
                             config=SimpleConfig(),
                             model_dir=os.getcwd())

model.load_weights(filepath="mask_rcnn_coco.h5", 
                   by_name=True)

image = cv2.imread("sample2.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

r = model.detect([image], verbose=0)

r = r[0]

mrcnn.visualize.display_instances(image=image, 
                                  boxes=r['rois'], 
                                  masks=r['masks'], 
                                  class_ids=r['class_ids'], 
                                  class_names=CLASS_NAMES, 
                                  scores=r['scores'])

至此,进行预测的代码已经完成。本教程的剩余部分将讨论如何使用自定义训练数据集来训练 Mask R-CNN 模型。下一节下载数据集。

下载训练数据集

你必须有一个数据集来训练机器学习或深度学习模型。对于训练数据中的每个样本,可能都有基本事实数据。这些数据可能是简单的,如类标签,也可能是复杂的,如用于对象检测模型的数据。

通常,对象检测模型的地面实况数据包括图像内每个对象的边界框类别标签。具体到 Mask R-CNN 模型,有一个额外的 mask 标记属于物体的像素。

每个图像可能有一个以上的对象,因此为整个数据集准备地面实况数据是令人厌倦的。

在本节中,现有的袋鼠图像数据集用于使用 Mask_RCNN 项目训练 Mask R-CNN。袋鼠数据集可以在这里下载。它带有注释数据(即地面实况数据),因此随时可以使用。

下图显示了数据集中绘制了预测边界框、遮罩和分数的图像。请注意,该掩码并不准确,因为该模型仅针对单个时期进行了训练。

数据集有两个文件夹:

  1. 图像:数据集中的图像。
  2. 注释:每个图像的注释作为一个单独的 XML 文件。

下一部分准备数据集,以供以后用于训练和验证 Mask R-CNN 模型。

准备训练数据集

Mask_RCNN 项目在mrcnn.utils模块中有一个名为Dataset的类。这个类只是在列表中存储所有训练图像的信息。当所有图像的细节都存储在单个数据结构中时,管理数据集将更加容易。

例如,有一个名为class_info的列表,其中保存了关于数据集中每个类的信息。类似地,一个名为image_info的列表保存了每个图像的信息。为了训练掩模 R-CNN 模型,使用image_info列表来检索训练图像及其注释。注释包括每个图像中所有对象的边界框和类别标签。

mrcnn.utils.Dataset类有许多有用的方法,包括:

  • add_class():添加一个新类。
  • add_image():向数据集添加新图像。
  • image_reference():检索图像的参考(如路径或链接)。
  • prepare():在将所有的类和图像添加到数据集之后,该方法准备数据集以供使用。
  • source_image_link():返回图像的路径或链接。
  • load_image():读取并返回图像。
  • load_mask():加载图像中物体的蒙版。

下一个代码块创建一个名为KangaroDatasetmrcnn.utils.Dataset的空实例。

import mrcnn.utils

class KangarooDataset(mrcnn.utils.Dataset):
    pass

在新类中,如果需要定制,可以随意覆盖前面提到的任何方法。此外,添加任何可能有帮助的新方法。

在前面列出的所有方法中,load_mask()方法必须被覆盖。原因是检索对象的掩码因注释文件格式而异,因此没有加载掩码的单一方法。因此,加载掩码是开发人员必须完成的任务。

在下面的下一个代码块中,我们将构建 3 个方法:

  1. load_dataset():它接受imagesannots文件夹所在的目录,此外还有一个布尔参数,表示该目录是引用训练数据还是验证数据。
  2. load_mask():这个方法加载袋鼠数据集的遮罩。它接受image_id参数中的图像 ID。图像 ID 只是每个图像的唯一值。请随意分配您选择的 id。该方法返回每个对象的掩码和类 id。注意,这个数据集只有一个表示袋鼠的类。
  3. extract_boxes:load_mask()方法调用extract_boxes()方法,该方法负责返回每个边界框的坐标,以及每个图像的宽度和高度。

这三种方法的实现在下一个代码块中列出。

load_dataset()方法中的第一行调用add_class()方法来创建一个名为kangaroo的类,ID 为1。还有一个 ID 为0的类,是标签为BG的背景。我们不需要显式地添加它,因为它是默认存在的。最后一行调用add_image()方法将图像添加到数据集。图像 ID、路径和注释文件的路径被传递给该方法。

load_dataset()方法分割数据集,这样 150 幅图像用于训练,而其余的用于测试。

import mrcnn.utils

class KangarooDataset(mrcnn.utils.Dataset):

    def load_dataset(self, dataset_dir, is_train=True):
		self.add_class("dataset", 1, "kangaroo")

		images_dir = dataset_dir +img/'
		annotations_dir = dataset_dir + '/annots/'

		for filename in os.listdir(images_dir):
			image_id = filename[:-4]

			if image_id in ['00090']:
				continue

			if is_train and int(image_id) >= 150:
				continue

			if not is_train and int(image_id) < 150:
				continue

			img_path = images_dir + filename
			ann_path = annotations_dir + image_id + '.xml'

			self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)

    def extract_boxes(self, filename):
		tree = xml.etree.ElementTree.parse(filename)

		root = tree.getroot()

		boxes = list()
		for box in root.findall('.//bndbox'):
			xmin = int(box.find('xmin').text)
			ymin = int(box.find('ymin').text)
			xmax = int(box.find('xmax').text)
			ymax = int(box.find('ymax').text)
			coors = [xmin, ymin, xmax, ymax]
			boxes.append(coors)

		width = int(root.find('.//size/width').text)
		height = int(root.find('.//size/height').text)
		return boxes, width, height

    def load_mask(self, image_id):
		info = self.image_info[image_id]
		path = info['annotation']
		boxes, w, h = self.extract_boxes(path)
		masks = zeros([h, w, len(boxes)], dtype='uint8')

		class_ids = list()
		for i in range(len(boxes)):
			box = boxes[i]
			row_s, row_e = box[1], box[3]
			col_s, col_e = box[0], box[2]
			masks[row_s:row_e, col_s:col_e, i] = 1
			class_ids.append(self.class_names.index('kangaroo'))
		return masks, asarray(class_ids, dtype='int32')

基于KangarooDataset类,下面的代码准备了训练数据集。简单地创建了该类的一个新实例。为了加载图像,调用了load_dataset()方法,该方法接受数据集图像的路径及其在dataset_dir参数中的注释。这都是除了is_train旗。如果这个 fag 是True,那么这个数据就是训练数据。否则,数据将用于验证或测试。

调用prepare()方法来准备数据集以供使用。它只是创建了更多关于数据的属性,比如类的数量、图像的数量等等。

train_set = KangarooDataset()
train_set.load_dataset(dataset_dir='D:\kangaroo', is_train=True)
train_set.prepare()

类似地,验证数据集是根据下面的代码准备的。唯一的区别是is_train标志被设置为False

valid_dataset = KangarooDataset()
valid_dataset.load_dataset(dataset_dir='D:\kangaroo', is_train=False)
valid_dataset.prepare()

下一节将为模型准备一些配置参数。

准备模型配置

必须创建一个mrcnn.config.Config类的子类来保存模型配置参数。下一段代码创建了一个名为KangarooConfig的新类,它扩展了mrcnn.config.Config类。

注意,类的数量(NUM_CLASSES)被设置为 2,因为数据集只有 2 个类,它们是BG(用于背景)和kangaroo

import mrcnn.config

class KangarooConfig(mrcnn.config.Config):
    NAME = "kangaroo_cfg"

    GPU_COUNT = 1
    IMAGES_PER_GPU = 1

    NUM_CLASSES = 2

    STEPS_PER_EPOCH = 131

准备好数据集和模型配置后,下一节讨论使用 TensorFlow 1.0 训练 Mask R-CNN 模型。

在 TensorFlow 1.0 中训练屏蔽 R-CNN

本节假设安装了 TensorFlow 1.0 版本,并用于运行上述代码。还可以创建一个安装了 TensorFlow 1.0 的虚拟环境。

接下来的代码创建了一个mrcnn.model.MaskRCNN类的实例,它构建了 Mask R-CNN 模型的架构。mode参数被设置为'training'以指示模型将被训练。当加载模型用于训练时,与仅加载模型用于推断(即预测)相比,存在额外的输入层。额外的层保存输入图像及其注释(例如边界框)。

import mrcnn.model
model = mrcnn.model.MaskRCNN(mode='training', 
                             model_dir='./', 
                             config=KangarooConfig())

一旦创建了模型架构,就使用load_weights()方法根据下一个代码加载权重。该方法接受以下 3 个参数:

  1. filepath:权重文件的路径。mask_rcnn_coco.h5文件可以从这个链接下载。
  2. by_name:是否根据层的名称分配层的权重。
  3. exclude:我们不为其加载权重的层的名称。这些是体系结构顶部的层,根据问题类型(例如,类的数量)而变化。

被排除的图层是那些负责生成类别概率、边界框和遮罩的图层。

model.load_weights(filepath='mask_rcnn_coco.h5', 
                   by_name=True, 
                   exclude=["mrcnn_class_logits", "mrcnn_bbox_fc",  "mrcnn_bbox", "mrcnn_mask"])

将权重加载到模型层后,使用train()方法训练模型。请注意,layers参数被设置为heads,表示只训练体系结构顶部的层。您也可以指定要训练的层名称。

请注意,load_weights()方法中的exclude参数接受不会加载其权重的层名称,但是train()方法中的layers参数接受要训练的层名称。

model.train(train_dataset=train_set, 
            val_dataset=valid_dataset, 
            learning_rate=KangarooConfig().LEARNING_RATE, 
            epochs=10, 
            layers='heads')

训练时间根据机器的计算能力而不同。如果你用一个功能强大的低成本 GPU 在 Gradient 上运行训练,应该会相对快一些。一旦模型被训练,可以使用 Keras save_weights()方法保存训练的权重。

model_path = 'Kangaroo_mask_rcnn.h5'
model.keras_model.save_weights(model_path)

这是训练模型的完整代码。

import os
import xml.etree
from numpy import zeros, asarray

import mrcnn.utils
import mrcnn.config
import mrcnn.model

class KangarooDataset(mrcnn.utils.Dataset):

	def load_dataset(self, dataset_dir, is_train=True):
		self.add_class("dataset", 1, "kangaroo")

		images_dir = dataset_dir +img/'
		annotations_dir = dataset_dir + '/annots/'

		for filename in os.listdir(images_dir):
			image_id = filename[:-4]

			if image_id in ['00090']:
				continue

			if is_train and int(image_id) >= 150:
				continue

			if not is_train and int(image_id) < 150:
				continue

			img_path = images_dir + filename
			ann_path = annotations_dir + image_id + '.xml'

			self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)

	def extract_boxes(self, filename):
		tree = xml.etree.ElementTree.parse(filename)

		root = tree.getroot()

		boxes = list()
		for box in root.findall('.//bndbox'):
			xmin = int(box.find('xmin').text)
			ymin = int(box.find('ymin').text)
			xmax = int(box.find('xmax').text)
			ymax = int(box.find('ymax').text)
			coors = [xmin, ymin, xmax, ymax]
			boxes.append(coors)

		width = int(root.find('.//size/width').text)
		height = int(root.find('.//size/height').text)
		return boxes, width, height

	def load_mask(self, image_id):
		info = self.image_info[image_id]
		path = info['annotation']
		boxes, w, h = self.extract_boxes(path)
		masks = zeros([h, w, len(boxes)], dtype='uint8')

		class_ids = list()
		for i in range(len(boxes)):
			box = boxes[i]
			row_s, row_e = box[1], box[3]
			col_s, col_e = box[0], box[2]
			masks[row_s:row_e, col_s:col_e, i] = 1
			class_ids.append(self.class_names.index('kangaroo'))
		return masks, asarray(class_ids, dtype='int32')

class KangarooConfig(mrcnn.config.Config):
    NAME = "kangaroo_cfg"

    GPU_COUNT = 1
    IMAGES_PER_GPU = 1

    NUM_CLASSES = 2

    STEPS_PER_EPOCH = 131

train_set = KangarooDataset()
train_set.load_dataset(dataset_dir='kangaroo', is_train=True)
train_set.prepare()

valid_dataset = KangarooDataset()
valid_dataset.load_dataset(dataset_dir='kangaroo', is_train=False)
valid_dataset.prepare()

kangaroo_config = KangarooConfig()

model = mrcnn.model.MaskRCNN(mode='training', 
                             model_dir='./', 
                             config=kangaroo_config)

model.load_weights(filepath='mask_rcnn_coco.h5', 
                   by_name=True, 
                   exclude=["mrcnn_class_logits", "mrcnn_bbox_fc",  "mrcnn_bbox", "mrcnn_mask"])

model.train(train_dataset=train_set, 
            val_dataset=valid_dataset, 
            learning_rate=kangaroo_config.LEARNING_RATE, 
            epochs=1, 
            layers='heads')

model_path = 'Kangaro_mask_rcnn.h5'
model.keras_model.save_weights(model_path)

结论

本教程介绍了开源 Python 项目 Mask_RCNN ,它为对象实例分割构建了 Mask R-CNN 模型。该项目仅支持 TensorFlow \(\geq\) 1.0 版本。本教程讲述了进行预测以及在自定义数据集上训练模型的步骤。

在训练模型之前,使用mrcnn.utils.Dataset类的子类准备训练和验证数据集。准备好模型配置参数后,就可以对模型进行训练了。

在下一个教程中,我们将看到如何编辑项目,以支持在 TensorFlow 2.0 中使用 Mask R-CNN 进行训练和预测。

基于 TensorFlow 2.0 和 Keras 的掩模 R-CNN 目标检测

原文:https://blog.paperspace.com/mask-r-cnn-tensorflow-2-0-keras/

之前的一个教程中,我们看到了如何在 Keras 和 TensorFlow 1.14 中使用开源 GitHub 项目 Mask_RCNN 。在本教程中,将对项目进行检查,以将 TensorFlow 1.14 的功能替换为与 TensorFlow 2.0 兼容的功能。

具体来说,我们将涵盖:

  • 使用 TensorFlow 2.0 通过掩膜 R-CNN 进行四次编辑以进行预测
  • 使用 TensorFlow 2.0 对训练掩模 R-CNN 进行五次编辑
  • 要进行的所有更改的摘要
  • 结论

开始之前,查看之前的教程下载并运行 Mask_RCNN 项目。

使用 TensorFlow 2.0 进行编辑以使用掩膜 R-CNN 进行预测

Mask_RCNN 项目仅适用于 TensorFlow \(\geq\) 1.13。由于 TensorFlow 2.0 提供了更多的功能和增强,开发人员希望迁移到 TensorFlow 2.0。

一些工具可能有助于自动将 TensorFlow 1.0 代码转换为 TensorFlow 2.0,但它们不能保证生成功能完整的代码。查看 Google 提供的升级脚本

在本节中,将讨论 Mask R-CNN 项目所需的更改,以便它完全支持 TensorFlow 2.0 进行预测(即当mrcnn.model.MaskRCNN类构造函数中的mode参数设置为inference)。在后面的部分中,将应用更多的编辑来训练 TensorFlow 2.0 中的 Mask R-CNN 模型(即当mrcnn.model.MaskRCNN类构造函数中的mode参数设置为training)。

如果安装了 TensorFlow 2.0,运行以下代码块来执行推理将引发异常。请考虑从此链接下载训练过的重量mask_rcnn_coco.h5

import mrcnn
import mrcnn.config
import mrcnn.model
import mrcnn.visualize
import cv2
import os

CLASS_NAMES = ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']

class SimpleConfig(mrcnn.config.Config):
    NAME = "coco_inference"

    GPU_COUNT = 1
    IMAGES_PER_GPU = 1

    NUM_CLASSES = len(CLASS_NAMES)

model = mrcnn.model.MaskRCNN(mode="inference", 
                             config=SimpleConfig(),
                             model_dir=os.getcwd())

model.load_weights(filepath="mask_rcnn_coco.h5", 
                   by_name=True)

image = cv2.imread("sample2.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

r = model.detect([image], verbose=0)

r = r[0]

mrcnn.visualize.display_instances(image=image, 
                                  boxes=r['rois'], 
                                  masks=r['masks'], 
                                  class_ids=r['class_ids'], 
                                  class_names=CLASS_NAMES, 
                                  scores=r['scores'])

接下来的小节讨论支持 TensorFlow 2.0 和解决所有异常所需的更改。

1.T2tf.log()

运行前面的代码,在mrcnn.model.log2_graph()函数中的这一行引发一个异常:

return tf.log(x) / tf.log(2.0)

异常文本如下所示。表示 TensorFlow 没有名为log()的属性。

...
File "D:\Object Detection\Tutorial\code\mrcnn\model.py", in log2_graph
  return tf.log(x) / tf.log(2.0)

AttributeError: module 'tensorflow' has no attribute 'log'

在 TensorFlow \(\geq\) 1.0 中,log()函数在库的根目录下可用。由于 TensorFlow 2.0 中一些函数的重组,log()函数被移到了tensorflow.math模块中。所以,与其使用tf.log(),不如简单地使用tf.math.log()

要解决这个问题,只需找到mrcnn.model.log2_graph()函数。下面是它的代码:

def log2_graph(x):
    """Implementation of Log2\. TF doesn't have a native implementation."""
    return tf.log(x) / tf.log(2.0)

将每个tf.log替换为tf.math.log。新的功能应该是:

def log2_graph(x):
    """Implementation of Log2\. TF doesn't have a native implementation."""
    return tf.math.log(x) / tf.math.log(2.0)

2.T2tf.sets.set_intersection()

再次运行代码后,当在mrcnn.model.refine_detections_graph()函数中执行这一行时,会引发另一个异常:

keep = tf.sets.set_intersection(tf.expand_dims(keep, 0), tf.expand_dims(conf_keep, 0))

例外情况如下。

File "D:\Object Detection\Tutorial\code\mrcnn\model.py", line 720, in refine_detections_graph
  keep = tf.sets.set_intersection(tf.expand_dims(keep, 0),

AttributeError: module 'tensorflow_core._api.v2.sets' has no attribute 'set_intersection'

出现此问题是因为 TensorFlow \(\geq\) 1.0 中的set_intersection()函数在 TensorFlow 2.0 中被重命名为intersection()

要解决这个问题,只需使用tf.sets.intersection()而不是tf.sets.set_intersection()。新的一行是:

keep = tf.sets.set.intersection(tf.expand_dims(keep, 0), tf.expand_dims(conf_keep, 0))

注意tf.sets.set_intersection()在另一个位置使用。因此,搜索它的所有出现,并用tf.sets.intersection()替换每一个。

3.T2tf.sparse_tensor_to_dense()

以上两个问题解决后,再次运行代码会在mrcnn.model.refine_detections_graph()函数的这一行中给出一个异常:

keep = tf.sparse_tensor_to_dense(keep)[0]

这是个例外。TensorFlow \(\geq\) 1.0 中的函数sparse_tensor_to_dense()可通过tf.sparse模块(tf.sparse.to_dense)访问。

File "D:\Object Detection\Tutorial\code\mrcnn\model.py", in refine_detections_graph
  keep = tf.sparse_tensor_to_dense(keep)[0]

AttributeError: module 'tensorflow' has no attribute 'sparse_tensor_to_dense'

要解决这个问题,请将每次出现的tf.sparse_tensor_to_dense替换为tf.sparse.to_dense。新行应该是:

keep = tf.sparse.to_dense(keep)[0]

对所有出现的tf.sparse_tensor_to_dense都这样做。

4.T2tf.to_float()

由于mrcnn.model.load_image_gt()函数中的这行代码,还引发了另一个异常:

tf.to_float(tf.gather(class_ids, keep))[..., tf.newaxis]

出现以下异常是因为 TensorFlow \(\geq\) 1.0 中的to_float()函数在 TensorFlow 2.0 中不存在。

File "D:\Object Detection\Tutorial\code\mrcnn\model.py", in refine_detections_graph
  tf.to_float(tf.gather(class_ids, keep))[..., tf.newaxis],

AttributeError: module 'tensorflow' has no attribute 'to_float'

作为 TensorFlow 2.0 中to_float()函数的替代,使用tf.cast()函数如下:

tf.cast([value], tf.float32)

要修复异常,请用下一行替换上一行:

tf.cast(tf.gather(class_ids, keep), tf.float32)[..., tf.newaxis]

推断变化汇总

要在 TensorFlow 2.0 中使用 Mask R-CNN 进行预测,需要对mrcnn.model脚本进行 4 处更改:

  1. tf.log()替换为tf.math.log()
  2. tf.sets.set_intersection()替换为tf.sets.intersection()
  3. tf.sparse_tensor_to_dense()替换为tf.sparse.to_dense()
  4. tf.to_float()替换为tf.cast([value], tf.float32)

完成所有这些更改后,我们在本文开头看到的代码可以在 TensorFlow 2.0 中成功运行。

使用 TensorFlow 2.0 编辑训练掩码 R-CNN

假设您安装了 TensorFlow 2.0,运行下面的代码块在 Kangaroo 数据集上训练 Mask R-CNN 将引发许多异常。本节检查 TensorFlow 2.0 中对训练掩码 R-CNN 所做的更改。

请考虑从这个链接下载袋鼠数据集以及权重mask_rcnn_coco.h5

import os
import xml.etree
from numpy import zeros, asarray

import mrcnn.utils
import mrcnn.config
import mrcnn.model

class KangarooDataset(mrcnn.utils.Dataset):

	def load_dataset(self, dataset_dir, is_train=True):
		self.add_class("dataset", 1, "kangaroo")

		images_dir = dataset_dir +img/'
		annotations_dir = dataset_dir + '/annots/'

		for filename in os.listdir(images_dir):
			image_id = filename[:-4]

			if image_id in ['00090']:
				continue

			if is_train and int(image_id) >= 150:
				continue

			if not is_train and int(image_id) < 150:
				continue

			img_path = images_dir + filename
			ann_path = annotations_dir + image_id + '.xml'

			self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)

	def extract_boxes(self, filename):
		tree = xml.etree.ElementTree.parse(filename)

		root = tree.getroot()

		boxes = list()
		for box in root.findall('.//bndbox'):
			xmin = int(box.find('xmin').text)
			ymin = int(box.find('ymin').text)
			xmax = int(box.find('xmax').text)
			ymax = int(box.find('ymax').text)
			coors = [xmin, ymin, xmax, ymax]
			boxes.append(coors)

		width = int(root.find('.//size/width').text)
		height = int(root.find('.//size/height').text)
		return boxes, width, height

	def load_mask(self, image_id):
		info = self.image_info[image_id]
		path = info['annotation']
		boxes, w, h = self.extract_boxes(path)
		masks = zeros([h, w, len(boxes)], dtype='uint8')

		class_ids = list()
		for i in range(len(boxes)):
			box = boxes[i]
			row_s, row_e = box[1], box[3]
			col_s, col_e = box[0], box[2]
			masks[row_s:row_e, col_s:col_e, i] = 1
			class_ids.append(self.class_names.index('kangaroo'))
		return masks, asarray(class_ids, dtype='int32')

class KangarooConfig(mrcnn.config.Config):
    NAME = "kangaroo_cfg"

    GPU_COUNT = 1
    IMAGES_PER_GPU = 1

    NUM_CLASSES = 2

    STEPS_PER_EPOCH = 131

train_set = KangarooDataset()
train_set.load_dataset(dataset_dir='kangaroo', is_train=True)
train_set.prepare()

valid_dataset = KangarooDataset()
valid_dataset.load_dataset(dataset_dir='kangaroo', is_train=False)
valid_dataset.prepare()

kangaroo_config = KangarooConfig()

model = mrcnn.model.MaskRCNN(mode='training', 
                             model_dir='./', 
                             config=kangaroo_config)

model.load_weights(filepath='mask_rcnn_coco.h5', 
                   by_name=True, 
                   exclude=["mrcnn_class_logits", "mrcnn_bbox_fc",  "mrcnn_bbox", "mrcnn_mask"])

model.train(train_dataset=train_set, 
            val_dataset=valid_dataset, 
            learning_rate=kangaroo_config.LEARNING_RATE, 
            epochs=1, 
            layers='heads')

model_path = 'Kangaro_mask_rcnn.h5'
model.keras_model.save_weights(model_path)

1.T2tf.random_shuffle()

运行前面的代码后,当执行mrcnn.model.detection_targets_graph()函数中的下一行时,会引发一个异常。

positive_indices = tf.random_shuffle(positive_indices)[:positive_count]

下面给出了一个例外,它表明没有函数被命名为random_shuffle()

File "D:\mrcnn\model.py", in detection_targets_graph
  positive_indices = tf.random_shuffle(positive_indices)[:positive_count]

AttributeError: module 'tensorflow' has no attribute 'random_shuffle'

由于 TensorFlow 2.0 函数的新组织,TensorFlow 1.0 中的tf.random_shuffle()函数被tf.random模块中的shuffle()方法所取代。因此,tf.random_shuffle()应该换成tf.random.shuffle()

前一行应该是:

positive_indices = tf.random.shuffle(positive_indices)[:positive_count]

请检查所有出现的tf.random_shuffle()并进行必要的更改。

2.T2tf.log

mrcnn.utils.box_refinement_graph()函数的下一行出现了一个异常。

dh = tf.log(gt_height / height)

下面给出的异常表明函数tf.log()不存在。

File "D:\mrcnn\utils.py", in box_refinement_graph
  dh = tf.log(gt_height / height)

AttributeError: module 'tensorflow' has no attribute 'log'

在 TensorFlow 2.0 中,log()函数被移到了math模块中。因此,tf.log()应该换成tf.math.log()。前一行应该是:

dh = tf.math.log(gt_height / height)

对所有出现的tf.log()功能进行更改。

3.张量隶属度

在执行mrccn.model.MaskRCNN类的compile()方法中的下一个if语句时,会引发异常。

if layer.output in self.keras_model.losses:

下面列出了例外情况。我们来解释一下它的意思。

layer.outputself.keras_model.losses都是张量。前一行检查了self.keras_model.losses张量中layer.output张量的成员。隶属度运算的结果是另一个张量,Python 把它作为bool类型,这是不可能的。

File "D:\mrcnn\model.py", in compile
  if layer.output in self.keras_model.losses:
	...
OperatorNotAllowedInGraphError: using a `tf.Tensor` as a Python `bool` is not allowed in Graph execution. Use Eager execution or decorate this function with @tf.function.

根据下面的代码,if语句的目的是检查层的损失是否存在于self.keras_model.losses张量内。如果不是,那么代码把它附加到self.keras_model.losses张量。

...

loss_names = ["rpn_class_loss",  "rpn_bbox_loss",
              "mrcnn_class_loss", "mrcnn_bbox_loss", 
              "mrcnn_mask_loss"]

for name in loss_names:
    layer = self.keras_model.get_layer(name)

    if layer.output in self.keras_model.losses:
        continue

    loss = (tf.reduce_mean(layer.output, keepdims=True) * self.config.LOSS_WEIGHTS.get(name, 1.))
    self.keras_model.add_loss(loss)

...

for循环之外,self.keras_model.losses张量为空。因此,它根本没有损失函数。因此,if声明可能会被忽略。解决方法是根据下一段代码注释if语句。

...

loss_names = ["rpn_class_loss",  "rpn_bbox_loss",
              "mrcnn_class_loss", "mrcnn_bbox_loss", 
              "mrcnn_mask_loss"]

for name in loss_names:
    layer = self.keras_model.get_layer(name)

    # if layer.output in self.keras_model.losses:
    #     continue

    loss = (tf.reduce_mean(layer.output, keepdims=True) * self.config.LOSS_WEIGHTS.get(name, 1.))
    self.keras_model.add_loss(loss)

...

让我们讨论另一个例外。

4.T2metrics_tensors

在执行mrccn.model.MaskRCNN类中的compile()方法内的下一行后,会引发一个异常。

self.keras_model.metrics_tensors.append(loss)

根据下面的错误,keras_model属性中没有名为metrics_tensors的属性。

File "D:\mrcnn\model.py", in compile
  self.keras_model.metrics_tensors.append(loss)

AttributeError: 'Model' object has no attribute 'metrics_tensors'

解决方法是在compile()方法的开头加上metrics_tensors

class MaskRCNN():
    ...
    def compile(self, learning_rate, momentum):
        self.keras_model.metrics_tensors = []
        ...

下一节讨论最后要做的更改。

5.保存培训日志

在执行mrcnn.model.MaskRCNN类中的set_log_dir()方法的下一行后,会引发一个异常。

self.log_dir = os.path.join(self.model_dir, "{}{:%Y%m%dT%H%M}".format(self.config.NAME.lower(), now))

下面给出了异常,它表明创建目录时出现了问题。

NotFoundError: Failed to create a directory: ./kangaroo_cfg20200918T0338\train\plugins\profile\2020-09-18_03-39-26; No such file or directory

可以通过手动指定有效的目录来解决该异常。例如,下一个目录对我的电脑有效。请尝试为您的目录指定一个有效的目录。

self.log_dir = "D:\\Object Detection\\Tutorial\\logs"

这是最后要做的更改,以便 Mask_RCNN 项目可以在 TensorFlow 2.0 中训练 Mask R-CNN 模型。之前准备的训练代码现在可以在 TensorFlow 2.0 中执行。

tensor flow 2.0 中列车屏蔽 R-CNN 变更汇总

为了在 TensorFlow 2.0 中使用 Mask_RCNN 项目训练 Mask R-CNN 模型,需要对mrcnn.model脚本进行 5 处更改:

  1. tf.random_shuffle()替换为tf.random.shuffle()
  2. tf.log()替换为tf.math.log()
  3. compile()方法中注释掉一个if语句。
  4. compile()方法的开始初始化metrics_tensors属性。
  5. self.log_dir属性分配一个有效的目录。

结论

本教程编辑了开源 Mask_RCNN 项目,以便 Mask R-CNN 模型能够使用 TensorFlow 2.0 进行训练和执行推理。

为了在 TensorFlow 2.0 中训练 Mask R-CNN 模型,总共应用了 9 个更改:4 个用于支持进行预测,5 个用于启用训练。

如何对参数分类方法使用最大似然估计

原文:https://blog.paperspace.com/maximum-likelihood-estimation-parametric-classification/

在之前讨论贝叶斯法则如何工作的一些教程中,决策是基于一些概率做出的(例如,可能性和先验)。这些概率要么是明确给出的,要么是根据一些给定的信息计算出来的。在本教程中,概率将根据训练数据进行估计。

本教程考虑参数分类方法,其中数据样本的分布遵循已知的分布(例如,高斯分布)。已知分布由一组参数定义。对于高斯分布,参数是均值\(\mu\)和方差\(\sigma^2\).如果样本分布的参数被估计,那么样本的分布就可以形成。因此,我们可以对遵循相同分布的新实例进行预测。

给定一个新的未知数据样本,可以根据它是否遵循旧样本的分布来做出决定。如果它遵循旧的分布,则新样本被类似于旧样本地对待(例如,根据旧样本的类别分类)。

在本教程中,使用最大似然估计(MLE) 来估计参数。

教程的大纲如下:

  • 估计样本分布的步骤
  • 最大似然估计
  • 二项分布
  • 多项分布
  • 高斯(正态)分布

让我们开始吧。

估计样本分布的步骤

基于贝叶斯法则,后验概率按下式计算:
$ \( P(C _ I | x)= \ frac { P(x | C _ I)P(C _ I)} { P(x)} \) \( 分母中的证据是归一化项,可以排除。因此,后验概率是根据以下等式计算的: \) \( P(C _ I | x)= P(x | C _ I)P(C _ I) \) \( 要计算后验概率\)P(C_i|x)\(首先必须估计似然概率\)P(x|C_i)\(和先验概率\)P(C_i)$。

在参数方法中,这些概率是基于遵循已知分布的概率函数计算的。本教程中讨论的分布是伯努利,多项式和高斯。

每种分布都有其参数。例如,高斯分布有两个参数:*均值\(\mu\)和方差\(\sigma^2\).如果这些参数被估计,那么分布将被估计。因此,可以估计可能性和先验概率。基于这些估计的概率,计算后验概率,因此我们可以对新的未知样本进行预测。

以下是本教程中根据给定样本估计分布参数的步骤总结:

  1. 第一步,声称样本遵循一定的分布。根据这个分布的公式,找出它的参数。
  2. 使用最大似然估计(MLE)来估计分布的参数。
  3. 估计的参数被插入到声称的分布中,这导致估计的样本分布。
  4. 最后,估计样本的分布用于决策。

下一节讨论最大似然估计(MLE)的工作原理。

最大似然估计

MLE 是一种估计已知分布参数的方法。请注意,还有其他方法来进行估计,如贝叶斯估计。

首先,有两个假设需要考虑:

  1. 第一个假设是有一个训练样本\(\mathcal{X}={{x^t}_{t=1}^N}\),其中实例\(x^t\)独立的同分布的 (iid)。
  2. 第二个假设是实例\(x^t\)取自先前已知的概率密度函数(pdf)$ p(\ mathcal { x } | \ theta)\(其中\)\theta\(是定义分布的参数集。换句话说,实例\)x^t\(遵循分布\)p(\mathcal{x}|\theta)\(,假定该分布由参数集\)\theta$定义。

请注意,\(p(\mathcal{x}|\theta)\)表示实例x存在于由参数集\(\theta\)定义的分布中的概率。通过找到一组合适的参数\(\theta\),我们可以对与\(x^t\).实例遵循相同分布的新实例进行采样我们如何找到 find \(\theta\)?这就是 MLE 出现的原因。

根据维基百科:

对于任意一组独立的随机变量,其联合分布的概率密度函数是其密度函数的乘积。

因为样本是 iid ( 独立同分布),所以样本\(\mathcal{X}\)遵循由参数\(\theta\)集合定义的分布的可能性等于各个实例\(x^t\).的可能性的乘积

$ \( l(\ theta | \ mathcal { x })\ equiv p(\mathcal{x}|\theta)=\prod_{t=1}^n{p(x^t|\theta)} \) $

目标是找到使似然估计\(L(\theta|\mathcal{X})\)最大化的参数集\(\theta\)。换句话说,找到一组参数\(\theta\)使从\(\theta\)定义的分布中获取\(x^t\)样本的机会最大化。这被称为最大似然估计(MLE) 。这是制定如下:

$ \( \theta^* \ space arg \ space max _ \ theta \ space l {(\ theta | \ mathcal { x })} \) $

可能性\(L(\theta|\mathcal{X})\)的表示可以简化。目前,它计算的产品之间的可能性个别样本\(p(x^t|\theta)\).对数似然不是计算似然性,而是简化计算,因为它将乘积转换为总和

$ \( \ mathcal { l } {(\ theta | \ mathcal { x })} \ equiv log \ space l(\ theta | \ mathcal { x } | \ theta)= log \ space \prod_{t=1}^n{p(x^t|\theta)} \ \ mathcal { l } {(\ theta | \ mathcal { x })} \ equiv log \ space l(\ theta | \ mathcal { x })\ equiv log \ space l(\ theta | \ mathcal { x })\ equiv log \ space p(\mathcal{x}|\theta)=\sum_{t=1}^n{log \ space p(x^t|\theta)} \) $

最大似然估计的目标是找到使对数似然最大化的参数集。这是制定如下:

$ \( \theta^* \ space arg \ space max _ \ theta \ space \ mathcal { l } {(\ theta | \ mathcal { x })} \) $

例如,在高斯分布中,参数组$\θ$只是均值和方差\(\theta={{\mu,\sigma^2}}\).这组参数\(\theta\)有助于选择接*原始样本\(\mathcal{X}\)的新样本。

前面的讨论准备了一个估计参数集的通用公式。接下来将讨论这如何适用于以下发行版:

  1. 伯努利分布
  2. 多项分布
  3. 高斯(正态)分布

每个发行版要遵循的步骤是:

  1. 概率函数:求做出预测的概率函数。
  2. 似然:根据概率函数,推导出分布的似然。
  3. 对数似然:基于似然,导出对数似然。
  4. 最大似然估计:求构成分布的参数的最大似然估计。
  5. 估计分布:将估计的参数代入分布的概率函数。

二项分布

伯努利分布适用于二进制结果 1 和 0。它假设结果 1 以概率\(p\)出现。因为 2 个结果的概率必须等于 1 美元,所以结果 0 发生的概率是 1-p 美元。

\[(p)+(1-p)=1 \]

假设\(x\)是伯努利随机变量,可能的结果是 0 和 1。

使用伯努利分布可以解决的问题是扔硬币,因为只有两种结果。

概率函数

伯努利分布的数学公式如下:

\[p(x)=p^x(1-p)^{1-x},\space where \space x={0,1} \]

根据上面的等式,只有一个参数,即\(p\)。为了导出数据样本\(x\)的伯努利分布,必须估计参数\(p\)的值。

可能性

还记得下面给出的一般似然估计公式吗?

$ \( l(\theta|\mathcal{x})=\prod_{t=1}^n{p(x^t|\theta)} \) $

对于伯努利分布,只有一个参数\(p\)。因此,\(\theta\)应该替换为\(p\)。因此,概率函数看起来像这样,其中\(p_0\)是参数:

$ \( p(x^t|\theta)=p(x^t|p_0) \) $

基于该概率函数,伯努利分布的可能性为:

$ \( l(p_0|\mathcal{x})=\prod_{t=1}^n{p(x^t|p_0)} \) $

概率函数可以分解如下:

$ \( p(x^t|p_0)=p_0^{x^t}(1-p_0)^{1-x^t} \) $

因此,可能性如下:

$ \( l(p_0|\mathcal{x})=\prod_{t=1}^n{p_0^{x^t}(1-p_0)^{1-x^t}} \) $

对数似然

推导出概率分布的公式后,接下来是计算对数似然性。这是通过将\(log\)引入到前面的等式中来实现的。

$ \( \ mathcal { l }(p _ 0 | \ mathcal { x })\ equiv log \ space l(p _ 0 | \ mathcal { x })= log \ space \prod_{t=1}^n{p_0^{x^t}(1-p_0)^{1-x^t}} \) $

当引入\(log\)时,乘法被转换成求和。

由于\(log\)运算符,\(p_0^{x^t}\)\((1-p_0)^{1-x^t}\)之间的乘法被转换为求和,如下所示:

$ \( \ mathcal { l }(p _ 0 | \ mathcal { x })\ equiv log \ space l(p _ 0 | \ mathcal { x })= \sum_{t=1}^n{log \ space p_0^{x^t}}+\sum_{t=1}^n{log \ space(1-p_0)^{1-x^t}} \) $

使用对数幂规则,对数似然性为:

$ \( \ mathcal { l }(p _ 0 | \ mathcal { x })\ equiv log \ space p_0\sum_{t=1}^n{x^t}+log \ space(1-p _ 0)\sum_{t=1}^n{({1-x^t})} \) $

最后一个求和项可以简化如下:

$ \( \sum_{t=1}^n{({1-x^t})}=\sum_{t=1}^n{1}-\sum_{t=1}^n{x^t}=n-\sum_{t=1}^n{x^t} \) $

回到对数似然函数,这是它的最后一种形式:

$ \( \mathcal{l}(p_0|\mathcal{x})=log(p_0)\sum_{t=1}^n{x^t}+圆木(1-P0)(n-\sum_{t=1}^n{x^t}) \) $

导出对数似然后,接下来我们将考虑最大似然估计。我们如何找到前一个方程的最大值?

最大似然估计

当一个函数的导数等于 0 时,这意味着它有一个特殊的行为;不增不减。这种特殊的行为可能被称为函数的最大值点。因此,通过将它相对于\(p_0\)的导数设置为 0,可以得到先前对数似然的最大值。

$ \( \ frac { d \ space \ mathcal { L }(p _ 0 | \ mathcal { X })} { d \ space p _ 0 } = 0 \) $

记住\(log(x)\)的导数计算如下:

$ \( \ frac { d \ space log(x)} { dx } = \ frac { 1 } { x ln(10)} \) $

对于前面的对数似然方程,下面是它的导数:

$ \( \ frac { d \ space \ mathcal { l }(p _ 0 | \ mathcal { x })} { d \ space p_0}=\frac{\sum_{t=1}^n{x^t}}{p_0 ln(10)}-\frac{(n-\sum_{t=1}^n{x^t})}{(1-p_0)ln(10)} = 0 \) $

注意\(log(p_0) log(1-p_0) ln(10)\)可以作为统一分母。结果,导数变成如下所示:

$ \( \ frac { d \ space \ mathcal { l }(p _ 0 | \ mathcal { x })} { d \ space p_0}=\frac{(1-p_0)\sum_{t=1}^n{x^t}-p_0(n-\sum_{t=1}^n{x^t})}{p_0(1-p _ 0)ln(10)} = 0 \) $

因为导数等于 0,所以不需要分母。导数现在如下:

$ \( \ frac { d \ space \ mathcal { l }(p _ 0 | \ mathcal { x })} { d \ space p_0}=(1-p_0)\sum_{t=1}^n{x^t}-p_0(n-\sum_{t=1}^n{x^t})=0 \) $

经过一些简化后,结果如下:

$ \( \ frac { d \ space \ mathcal { l }(p _ 0 | \ mathcal { x })} { d \ space p_0}=\sum_{t=1}^n{x^t}-p_0\sum_{t=1}^n{x^t}-p_0n+p_0\sum_{t=1}^n{x^t}=0 \) $

提名者中的下两个术语相互抵消:

$ \( -p_0\sum_{t=1}^n{x^t}+p_0\sum_{t=1}^n{x^t} \) $

因此,导数变成:

$ \( \ frac { d \ space \ mathcal { l }(p _ 0 | \ mathcal { x })} { d \ space p_0}=\sum_{t=1}^n{x^t}-p_0n=0 \) $

负项可以移到另一边变成:

$ \( \ frac { d \ space \ mathcal { l }(p _ 0 | \ mathcal { x })} { d \ space p_0}=\sum_{t=1}^n{x^t}=p_0n \) $

将两边除以\(N\),计算参数\(p_0\)的等式为:

$ \( p_0=\frac{\sum_{t=1}^n{x^t}}{n} \) $

简单地说,参数\(p_0\)计算为所有样本的*均值。因此,伯努利分布的估计参数\(p\)\(p_0\)

请记住\(x^t \in {0,1}\),这意味着所有样本的总和就是具有\(x^t=1\).的样本数因此,如果有 10 个样本,其中有 6 个,那么\(p_0=0.6\)。通过最大化似然性(或对数似然性),将得到代表数据的最佳伯努利分布。

估计分布

记住,伯努利分布的概率函数是:

\[p(x)=p^x(1-p)^{1-x},\space where \space x={0,1} \]

一旦伯努利分布的参数$p$被估计为$p _ 0 $,它就被插入到伯努利分布的通用公式中,以返回样本\(\mathcal{X}=x^t\):的估计分布

$ $
p(xt)=p_0{xt}(1-p_0),\太空何处\太空 x^t={0,1}

\[ 可以使用样本$\mathcal{X}=x^t$.的估计分布进行预测 ## 多项分布 伯努利分布只适用于两种结果/状态。为了处理两个以上的结果,使用了**多项分布**,其中结果是互斥的,因此没有一个会影响另一个。多项式分布是伯努利分布的**推广**。 一个可以分布为多项式分布的问题是掷骰子。有两个以上的结果,其中每个结果都是相互独立的。 结果的概率是$p_i$,其中$i$是结果的指数(即类)。因为所有结果的概率之和必须为 1,所以适用以下公式: $ $ \sum_{i=1}^n{p_i}=1 $ $ 对于每个结果$i$来说,都有一个指标变量$x_i$。所有变量的集合是: $ $ \mathcal{x}=\{x_i\}_{i=1}^k $ $ 变量$x_i$可以是 1 也可以是 0。如果结果是$i$则为 1,否则为 0。 记住每个实验只有一个结果**$ t $**。因此,对于所有的类$i,i=1:K$,所有变量$x^t$的总和必须为 1。 $ $ \sum_{i=1}^k{x_i^t}=1 $ $ ### 概率函数 概率函数可以表述如下,其中$K$是结果的数量。它是所有结果的所有概率的乘积。 $ $ p(x1,x2,x3,...x_k)=\prod_{i=1}^k{p_i^{x_i}} $ $ 注意所有$p_i$的总和是 1。 $ $ \sum_{i=1}^k{p_i}=1 $ $ ### 可能性 一般似然估计公式如下: $ $ l(\ theta | \ mathcal { x })\ equiv p(x | \ theta)=\prod_{t=1}^n{p(x^t|\theta)} $ $ 对于多项分布,这里是它的可能性,其中$K$是结果的数量,$N$是样本的数量。 $ $ l(p _ I | \ mathcal { x })\ equiv p(x|\theta)=\prod_{t=1}^n\prod_{i=1}^k{p_i^{x_i^t}} $ $ ### 对数似然 多项式分布的对数似然如下: $ $ \ mathcal { l }(p _ I | \ mathcal { x })\ equiv log \ space l(p _ I | \ mathcal { x })\ equiv log \ space p(x | \ theta)= log \ space \prod_{t=1}^n\prod_{i=1}^k{p_i^{x_i^t}} $ $ $log$将乘积转换成总和: $ $ \mathcal{l}(p_i|\mathcal{x})=\sum_{t=1}^n\sum_{i=1}^k{log \太空 p_i^{x_i^t}} \]

根据对数幂规则,对数似然性为:

$ $
\mathcal{l}(p_i|\mathcal{x})=\sum_{t=1}n\sum_{i=1}k{[{x_i^t} \空间日志\空间 p_i}]

\[ 注意,所有类的所有$x$之和等于 1。换句话说,以下成立: $ $ \sum_{i=1}^kx_i^t=1 $ $ 然后,对数似然变成: $ $ \mathcal{l}(p_i|\mathcal{x})=\sum_{t=1}^n{x_i^t}\sum_{i=1}^k{log \太空 p_i} \]

下一节使用 MLE 来估计参数\(p_i\)

最大似然估计

基于多项式分布的对数似然性\(\mathcal{L}(p_i|\mathcal{X})\),通过根据下式将对数似然性的导数设置为 0 来估计参数\(p_i\)

$ \( \ frac { d \ space \ mathcal { l }(p _ I | \ mathcal { x })} { d \ space p _ I } = \ frac { d \ space \sum_{t=1}^n{x_i^t}\sum_{i=1}^k{log \ space p _ I } } { d \ space p _ I } = 0 \) $

根据导数乘积规则,\(\sum_{t=1}^N{x_i^t}\)\(\sum_{i=1}^K{log \space p_i}\)项乘积的导数计算如下:

$ \( \ frac { d \ space \sum_{t=1}^n{x_i^t}\sum_{i=1}^k{log \ space p _ I } } { d \ space p_i}=\sum_{i=1}^k{log \ space p _ I }。\sum_{t=1}^n{x_i^t}}{d+\sum_{t=1}^n{x_i^t}.\ frac { d \ space \sum_{i=1}^k{log \ space p _ I } } { d \ space p _ I } \) $

\(log(p_i)\)的导数是:

$ \( \ frac { d \ space log(p _ I)} { DP _ I } = \ frac { 1 } { p _ I ln(10)} \) $

对数似然的导数为:

$ \( \ frac { d \ space \sum_{t=1}^n{x_i^t}\sum_{i=1}^k{log \ space p _ I } } { d \ space p _ I } = \frac{\sum_{t=1}^n{x_i^t}}{p_i ln(10)} \) $

基于拉格朗日乘数并将对数似然的导数设置为零,多项式分布的 MLE 为:

$ \( p_i=\frac{\sum_{t=1}^n{x_i^t}}{n} \) $

注意,多项式分布只是伯努利分布的推广。他们的 mle 是相似的,除了多项式分布认为有多个结果,而伯努利分布只有两个结果。

计算每个结果的最大似然。它计算结果\(i\)在结果总数中出现的次数。例如,如果骰子上编号为 2 的面在总共 20 次投掷中出现了 10 次,则它的 MLE 为 10 / 20 = 0.5 美元。

多项式实验可以看作是做\(K\)伯努利实验。对于每个实验,计算单个类\(i\)的概率。

估计分布

一旦估计了多项式分布的参数$p _ I $,就将其插入到多项式分布的概率函数中,以返回样本\(\mathcal{X}=x^t\).的估计分布

\[p(x_1,x_2,x_3,...x_k)=\prod_{i=1}^k{p_i^{x_i}} $ $ ## 高斯(正态)分布 伯努利分布和多项式分布都将其输入设置为 0 或 1。输入$x$不可能是任何实数。 \]

x^t \in {0,1 }
t \ in 1:n
n:\空间编号\空间样本的空间。

\[ 在高斯分布中,输入$x$的值介于$-\infty$到$\infty$之间。 $ $ -\ infty<x<\ infty $ $ ### 概率函数 高斯(正态)分布是基于两个参数定义的:均值$\mu$和方差$\sigma^2$. \]

p(x)=\mathcal{N}(\mu,\sigma^2)

\[ 给定这两个参数,下面是高斯分布的概率密度函数。 $ $ \ mathcal { n }(\穆,\sigma^2)=p(x)=\frac{1}{\sqrt{2\pi}\sigma}\exp[-\frac{(x-\mu)^2}{2\sigma^2}]=\frac{1}{\sqrt{2\pi}\sigma}e^{[-\frac{(x-\mu)^2}{2\sigma^2}]} \ $ $ $ $ -\ infty<x<\ infty $ $ 如果一个随机变量$X$的密度函数是根据前面的函数计算的,则称它遵循高斯(正态)分布。在这种情况下,其均值为$E[X]\equiv \mu$且方差为$VAR(X) \equiv \sigma^2$.这表示为$\mathcal{N}(\mu,\sigma^2)$. ### 可能性 让我们先回顾一下计算似然估计的等式。 $ $ l(\theta|\mathcal{x})=\prod_{t=1}^n{p(x^t|\theta)} \太空何处\太空\mathcal{x}=\{x^t\}_{t=1}^n $ $ 对于高斯概率函数,下面是计算可能性的方法。 $ $ l(\mu,\sigma^2|\mathcal{x})\ equiv \prod_{t=1}^n{\mathcal{n}(\mu,\sigma^2)}=\prod_{t=1}^n{\frac{1}{\sqrt{2\pi}\sigma}\exp[-\frac{(x^t-\mu)^2}{2\sigma^2}]} $ $ ### 对数似然 对数被引入高斯分布的似然性,如下所示: $ $ \mathcal{l}(\mu,\sigma^2|\mathcal{x})\ equiv 日志\太空 l(\mu,\sigma^2|\mathcal{x})\ equiv log\prod_{t=1}^n{\mathcal{n}(\mu,\sigma^2)}=log\prod_{t=1}^n{\frac{1}{\sqrt{2\pi}\sigma}\exp[-\frac{(x^t-\mu)^2}{2\sigma^2}]} $ $ 对数将乘积转换为总和,如下所示: $ $ \mathcal{l}(\mu,\sigma^2|\mathcal{x})\ equiv log \ space l(\mu,\sigma^2|\mathcal{x})\ equiv \sum_{t=1}^n{log \ space \ mathcal { n }(\ mu,\sigma^2)}=\sum_{t=1}^n{log \ space(\frac{1}{\sqrt{2\pi}\sigma}\exp[-\frac{(x^t-\mu)^2}{2\sigma^2}])} $ $ 使用**对数乘积规则**,对数似然为: $ $ \mathcal{l}(\mu,\sigma^2|\mathcal{x})\ equiv log \ space l(\mu,\sigma^2|\mathcal{x})\ equiv \sum_{t=1}^n{log \ space \ mathcal { n }(\ mu,\sigma^2)}=\sum_{t=1}^n{(log \ space(\ frac { 1 } { 2 \ pi } \ sigma })+log \ space(\exp[-\frac{(x^t-\mu)^2}{2\sigma^2}]))} $ $ 求和运算符可以分布在两项中: $ $ \mathcal{l}(\mu,\sigma^2|\mathcal{x})=\sum_{t=1}^n{log \太空公司{ 1 }+\sum_{t=1}^nlog \太空公司\exp[-\frac{(x^t-\mu)^2}{2\sigma^2}]} $ $ 这个等式有两个独立的项。现在让我们分别研究每一项,然后再把结果结合起来。 对于第一项,可以应用**对数商规则**。结果如下: $ $ \sum_{t=1}^nlog \太空(\frac{1}{\sqrt{2\pi}\sigma})=\sum_{t=1}^n(log(1)-log(\sqrt{2\pi}\sigma)) $ $ 假设$log(1)=0$,则结果如下: $ $ \sum_{t=1}^nlog \太空(\frac{1}{\sqrt{2\pi}\sigma})=-\sum_{t=1}^nlog(\sqrt{2\pi}\sigma) $ $ 根据**对数乘积规则**,第一项的对数为: $ $ \sum_{t=1}^nlog \太空(\frac{1}{\sqrt{2\pi}\sigma})=-\sum_{t=1}^n[log{\sqrt{2\pi}+log \太空\西格玛}] \]

注意,第一项不依赖于求和变量\(t\),因此它是一个固定项。因此,求和的结果就是将这一项乘以\(N\)

$ \( \sum_{t=1}^nlog \ space(\ frac { 1 } { \ sqrt { 2 \ pi } \ sigma })=-\ frac { n } { 2 } log({ \ sqrt { 2 \ pi } })-n \ space log \ space \ sigma \) $

现在让我们转到下面给出的第二项。

$ \( \sum_{t=1}^nlog \太空(\exp[-\frac{(x^t-\mu)^2}{2\sigma^2}) \) $

对数幂规则可用于简化该术语,如下所示:

$ $
\sum_{t=1}^nlog \太空(\exp[-\frac{(xt-\mu)2}{2\sigma2}])=\sum_{t=1}nlog \太空 e{[-\frac{(xt-\mu)2}{2\sigma2}]}=\sum_{t=1}n[-\frac{(xt-\mu)2}{2\sigma2}]\太空日志(e)

\[ 假设$log$ base 是$e$,则$log(e)=1$。因此,第二项现在是: $ $ \sum_{t=1}^nlog \太空(\exp[-\frac{(x^t-\mu)^2}{2\sigma^2}])=-\sum_{t=1}^n\frac{(x^t-\mu)^2}{2\sigma^2} $ $ 分母不依赖于求和变量$t$,因此等式可以写成如下形式: $ $ \sum_{t=1}^nlog \太空(\exp[-\frac{(x^t-\mu)^2}{2\sigma^2}])=-\frac{1}{2\sigma^2}\sum_{t=1}^n(x^t-\mu)^2 $ $ 简化这两项后,下面是高斯分布的对数似然性: $ $ \mathcal{l}(\mu,\sigma^2|\mathcal{x})=-\frac{n}{2}log({\sqrt{2\pi}})-n \太空日志\太空\sigma-\frac{1}{2\sigma^2}\sum_{t=1}^n(x^t-\mu)^2 $ $ ### 最大似然估计 本节讨论如何找到高斯分布中两个参数$\ mu$和$\sigma^2$.的最大似然估计 最大似然估计可以通过计算每个参数的对数似然导数得到。通过将该导数设置为 0,可以计算 MLE。下一小节从第一个参数$\mu$开始。 #### *均值的最大似然估计值 从$\mu$开始,让我们计算对数似然的导数: $ $ \ frac { d \ space \mathcal{l}(\mu,\sigma^2|\mathcal{x})}{d \ space \ mu } = \ frac { d } { d \ mu }[-\ frac { n } { 2 } log({ \ sqrt { 2 \ pi })-n \ space log \ space \sigma-\frac{1}{2\sigma^2}\sum_{t=1}^n(x^t-\mu)^2]=0 $ $ 前两项不依赖于$\mu$,因此它们的导数为 0。 $ $ \ frac { d \ space \mathcal{l}(\mu,\sigma^2|\mathcal{x})}{d \ mu } = { \ frac { d } { d \mu}\sum_{t=1}^n(x^t-\mu)^2}=0 $ $ 前一项可以写成如下: $ $ \ frac { d \ space \mathcal{l}(\mu,\sigma^2|\mathcal{x})}{d \ mu } = { \ frac { d } { d \mu}\sum_{t=1}^n((x^t)^2-2x^t\mu+\mu^2)}=0 $ $ 因为$(x^t)^2$不依赖于$\mu$,所以它的导数是 0,可以忽略不计。 $ $ \ frac { d \ space \mathcal{l}(\mu,\sigma^2|\mathcal{x})}{d \ mu } = { \ frac { d } { d \mu}\sum_{t=1}^n(-2x^t\mu+\mu^2)}=0 $ $ 求和可以分布在其余两项上: $ $ \ frac { d \ space \mathcal{l}(\mu,\sigma^2|\mathcal{x})}{d \ mu } = { \ frac { d } { d \mu}[-\sum_{t=1}^n2x^t\mu+\sum_{t=1}^n\mu^2}]=0 $ $ 对数似然的导数变成: $ $ \ frac { d \ space \mathcal{l}(\mu,\sigma^2|\mathcal{x})}{d \mu}=-\sum_{t=1}^n2x^t+2\sum_{t=1}^n\mu=0 $ $ 第二项$2\sum_{t=1}^N\mu$不依赖于$t$,因此它是一个固定项,等于$2N\mu$。因此,对数似然的导数如下: $ $ \ frac { d \ space \mathcal{l}(\mu,\sigma^2|\mathcal{x})}{d \mu}=-\sum_{t=1}^n2x^t+2n\mu=0 $ $ 通过求解前面的方程,最终,均值的 MLE 为: $ $ m=\frac{\sum_{t=1}^nx^t}{n} $ $ #### $\sigma^2$方差极大似然估计 与计算均值的 MLE 的步骤类似,方差的 MLE 为: $ $ s^2=\frac{\sum_{t=1}^n(x^t-m)^2}{n} $ $ ## 结论 本教程介绍了最大似然估计(MLE)方法的数学原理,该方法基于训练数据$x^t$.来估计已知分布的参数讨论的三种分布是伯努利分布、多项式分布和高斯分布。 本教程总结了 MLE 用于估计参数的步骤: 1. 要求训练数据的分布。 2. 使用**对数似然**估计分布的参数。 3. 将估计的参数代入分布的概率函数。 4. 最后,估计训练数据的分布。 一旦**对数似然**被计算,它的导数相对于分布中的每个参数被计算。估计参数是使对数似然最大化的参数,对数似然是通过将对数似然导数设置为 0 而得到的。 本教程讨论了 MLE 如何解决分类问题。在后面的教程中,MLE 将用于估计回归问题的参数。敬请关注。 # 使用*均精度(mAP)评估目标检测模型 > 原文:<https://blog.paperspace.com/mean-average-precision/> 为了评估像 R-CNN 和 [YOLO](https://blog.paperspace.com/how-to-implement-a-yolo-object-detector-in-pytorch/) 这样的对象检测模型,使用了***均精度(mAP)** 。地图将地面实况边界框与检测到的框进行比较,并返回一个分数。分数越高,模型的检测就越准确。 在我的上一篇文章中,我们详细研究了[混淆矩阵、模型准确度、精确度和召回率](https://blog.paperspace.com/deep-learning-metrics-precision-recall-accuracy)。我们还使用 Scikit-learn 库来计算这些指标。现在我们将扩展我们的讨论,看看如何使用精度和召回来计算地图。 以下是本教程涵盖的部分: * 从预测分数到类别标签 * 精确回忆曲线 * *均精度 * 并集上的交集 * 目标检测的*均精度 让我们开始吧。 ## **从预测分数到类别标签** 在本节中,我们将快速回顾一个类别标签是如何从预测分数中获得的。 假设有两类,*阳性*和*阴性*,这里是 10 个样本的地面实况标签。 ```py y_true = ["positive", "negative", "negative", "positive", "positive", "positive", "negative", "positive", "negative", "positive"] ``` 当这些样本输入模型时,它会返回以下预测得分。根据这些分数,我们如何对样本进行分类(即给每个样本分配一个类别标签)? ```py pred_scores = [0.7, 0.3, 0.5, 0.6, 0.55, 0.9, 0.4, 0.2, 0.4, 0.3] ``` 为了将分数转换成类别标签,**使用阈值**。当分数等于或高于阈值时,样本被分类为一类。否则,它被归类为其他类。假设一个样本的分数高于或等于阈值,则该样本为*阳性*。否则,就是*负*。下一个代码块将分数转换成阈值为 **0.5** 的类标签。 ```py import numpy pred_scores = [0.7, 0.3, 0.5, 0.6, 0.55, 0.9, 0.4, 0.2, 0.4, 0.3] y_true = ["positive", "negative", "negative", "positive", "positive", "positive", "negative", "positive", "negative", "positive"] threshold = 0.5 y_pred = ["positive" if score >= threshold else "negative" for score in pred_scores] print(y_pred) ``` ```py ['positive', 'negative', 'positive', 'positive', 'positive', 'positive', 'negative', 'negative', 'negative', 'negative'] ``` 现在,`y_true`和`y_pred`变量中既有实际标签也有预测标签。基于这些标签,可以计算出[混淆矩阵、精确度和召回率](https://blog.paperspace.com/deep-learning-metrics-precision-recall-accuracy/)。 ```py r = numpy.flip(sklearn.metrics.confusion_matrix(y_true, y_pred)) print(r) precision = sklearn.metrics.precision_score(y_true=y_true, y_pred=y_pred, pos_label="positive") print(precision) recall = sklearn.metrics.recall_score(y_true=y_true, y_pred=y_pred, pos_label="positive") print(recall) ``` ```py # Confusion Matrix (From Left to Right & Top to Bottom: True Positive, False Negative, False Positive, True Negative) [[4 2] [1 3]] # Precision = 4/(4+1) 0.8 # Recall = 4/(4+2) 0.6666666666666666 ``` 在快速回顾了计算精度和召回率之后,下一节我们将讨论创建精度-召回率曲线。 ## **精确召回曲线** 根据[第 1 部分](https://blog.paperspace.com/deep-learning-metrics-precision-recall-accuracy/)中给出的精确度和召回率的定义,记住精确度越高,模型在将样本分类为*阳性*时就越有信心。召回率越高,模型正确归类为*阳性*的阳性样本就越多。 > 当一个模型具有高召回率但低精确度时,那么该模型正确地分类了大多数阳性样本,但是它具有许多假阳性(即,将许多*阴性*样本分类为*阳性*)。当模型具有高精度但低召回率时,那么当它将样本分类为*阳性*时,该模型是准确的,但是它可能仅分类一些阳性样本。 由于精确度和召回率的重要性,有一条**精确度-召回率曲线**显示了不同阈值的精确度和召回率值之间的权衡。该曲线有助于选择最佳阈值来最大化这两个指标。 创建精确召回曲线需要一些输入: 1. 真相标签。 2. 样本的预测分数。 3. 将预测分数转换成类别标签的一些阈值。 下一个代码块创建用于保存基本事实标签的`y_true`列表,用于预测分数的`pred_scores`列表,以及用于不同阈值的`thresholds`列表。 ```py import numpy y_true = ["positive", "negative", "negative", "positive", "positive", "positive", "negative", "positive", "negative", "positive", "positive", "positive", "positive", "negative", "negative", "negative"] pred_scores = [0.7, 0.3, 0.5, 0.6, 0.55, 0.9, 0.4, 0.2, 0.4, 0.3, 0.7, 0.5, 0.8, 0.2, 0.3, 0.35] thresholds = numpy.arange(start=0.2, stop=0.7, step=0.05) ``` 以下是保存在`thresholds`列表中的阈值。因为有 10 个阈值,所以将创建 10 个精度和召回值。 ```py [0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65] ``` 下一个名为`precision_recall_curve()`的函数接受基本事实标签、预测分数和阈值。它返回两个表示精度和召回值的等长列表。 ```py import sklearn.metrics def precision_recall_curve(y_true, pred_scores, thresholds): precisions = [] recalls = [] for threshold in thresholds: y_pred = ["positive" if score >= threshold else "negative" for score in pred_scores] precision = sklearn.metrics.precision_score(y_true=y_true, y_pred=y_pred, pos_label="positive") recall = sklearn.metrics.recall_score(y_true=y_true, y_pred=y_pred, pos_label="positive") precisions.append(precision) recalls.append(recall) return precisions, recalls ``` 下一段代码在传递了三个预先准备好的列表后调用`precision_recall_curve()`函数。它返回分别保存精度和召回的所有值的`precisions`和`recalls`列表。 ```py precisions, recalls = precision_recall_curve(y_true=y_true, pred_scores=pred_scores, thresholds=thresholds) ``` 以下是`precisions`列表中的返回值。 ```py [0.5625, 0.5714285714285714, 0.5714285714285714, 0.6363636363636364, 0.7, 0.875, 0.875, 1.0, 1.0, 1.0] ``` 以下是`recalls`列表中的值列表。 ```py [1.0, 0.8888888888888888, 0.8888888888888888, 0.7777777777777778, 0.7777777777777778, 0.7777777777777778, 0.7777777777777778, 0.6666666666666666, 0.5555555555555556, 0.4444444444444444] ``` 给定两个长度相等的列表,可以将它们的值绘制成 2D 图,如下所示。 ```py matplotlib.pyplot.plot(recalls, precisions, linewidth=4, color="red") matplotlib.pyplot.xlabel("Recall", fontsize=12, fontweight='bold') matplotlib.pyplot.ylabel("Precision", fontsize=12, fontweight='bold') matplotlib.pyplot.title("Precision-Recall Curve", fontsize=15, fontweight="bold") matplotlib.pyplot.show() ``` 精确召回曲线如下图所示。注意,随着召回率的增加,精确度降低。原因是当阳性样本数量增加时(高召回率),正确分类每个样本的准确率下降(低精度)。这是意料之中的,因为当有许多样本时,模型更有可能失败。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/7d4281b7358ddf3236488f315ace78be.png) 精确-召回曲线使得确定精确和召回都高的点变得容易。根据上图,最佳点是`(recall, precision)=(0.778, 0.875)`。 因为曲线并不复杂,所以使用前面的图来图形化地确定精确度和召回率的最佳值可能是有效的。更好的方法是使用一个叫做`f1`分数的指标,它是根据下一个等式计算出来的。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/23baa3f08c5908ba40f947f3700629db.png) `f1`指标衡量精确度和召回率之间的*衡。当`f1`的值高时,这意味着精确度和召回率都高。较低的`f1`分数意味着精确度和召回率之间更大的不*衡。 根据前面的例子,`f1`是根据下面的代码计算的。根据`f1`列表中的值,最高分是`0.82352941`。它是列表中的第 6 个元素(即索引 5)。`recalls`和`precisions`列表中的第 6 个元素分别是`0.778`和`0.875`。对应的阈值是`0.45`。 ```py f1 = 2 * ((numpy.array(precisions) * numpy.array(recalls)) / (numpy.array(precisions) + numpy.array(recalls))) ``` ```py [0.72, 0.69565217, 0.69565217, 0.7, 0.73684211, 0.82352941, 0.82352941, 0.8, 0.71428571, 0 .61538462] ``` 下图用蓝色显示了在召回率和精确度之间达到最佳*衡的点的位置。总之,*衡精确度和召回率的最佳阈值是`0.45`,此时精确度是`0.875`,召回率是`0.778`。 ```py matplotlib.pyplot.plot(recalls, precisions, linewidth=4, color="red", zorder=0) matplotlib.pyplot.scatter(recalls[5], precisions[5], zorder=1, linewidth=6) matplotlib.pyplot.xlabel("Recall", fontsize=12, fontweight='bold') matplotlib.pyplot.ylabel("Precision", fontsize=12, fontweight='bold') matplotlib.pyplot.title("Precision-Recall Curve", fontsize=15, fontweight="bold") matplotlib.pyplot.show() ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/47440c67ba1d09aa9aa6153e53e4c7ef.png) 在讨论了精度-召回曲线之后,下一节将讨论如何计算*均精度。 ## ***均精度(AP)** ***均精度(AP)** 是一种将精度-召回曲线总结为代表所有精度*均值的单一值的方法。AP 根据下式计算。使用遍历所有精度/召回的循环,计算当前和下一次召回之间的差异,然后乘以当前精度。换句话说,AP 是每个阈值的精度的加权和,其中权重是召回的增加。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ea1caa1963f9fbc9a5c81d8efd5a3e40.png) 重要的是要在召回列表和精度列表后分别添加 0 和 1。例如,如果`recalls`列表是[0.8,0.6],那么它应该将 0 追加到[0.8,0.6,0.0]。同样的情况也发生在`precisions`列表中,但是添加了 1 而不是 0(例如[0.8,0.2,1.0])。 假设`recalls`和`precisions`都是 NumPy 数组,前面的等式是根据下一个 Python 行建模的。 ```py AP = numpy.sum((recalls[:-1] - recalls[1:]) * precisions[:-1]) ``` 下面是计算 AP 的完整代码。 ```py import numpy import sklearn.metrics def precision_recall_curve(y_true, pred_scores, thresholds): precisions = [] recalls = [] for threshold in thresholds: y_pred = ["positive" if score >= threshold else "negative" for score in pred_scores] precision = sklearn.metrics.precision_score(y_true=y_true, y_pred=y_pred, pos_label="positive") recall = sklearn.metrics.recall_score(y_true=y_true, y_pred=y_pred, pos_label="positive") precisions.append(precision) recalls.append(recall) return precisions, recalls y_true = ["positive", "negative", "negative", "positive", "positive", "positive", "negative", "positive", "negative", "positive", "positive", "positive", "positive", "negative", "negative", "negative"] pred_scores = [0.7, 0.3, 0.5, 0.6, 0.55, 0.9, 0.4, 0.2, 0.4, 0.3, 0.7, 0.5, 0.8, 0.2, 0.3, 0.35] thresholds=numpy.arange(start=0.2, stop=0.7, step=0.05) precisions, recalls = precision_recall_curve(y_true=y_true, pred_scores=pred_scores, thresholds=thresholds) precisions.append(1) recalls.append(0) precisions = numpy.array(precisions) recalls = numpy.array(recalls) AP = numpy.sum((recalls[:-1] - recalls[1:]) * precisions[:-1]) print(AP) ``` 这都是关于*均精度的。下面是计算 AP 的步骤总结: 1. 使用模型生成**预测分数**。 2. 将**预测分数**转换为**类别标签**。 3. 计算**混淆矩阵**。 4. 计算**精度**和**召回**指标。 5. 创建**精确召回曲线**。 6. 测量**的*均精度**。 下一节将讨论 union (IoU) 上的**交集,这是对象检测如何生成预测分数。** ## **并集上的交集(IoU)** 为了训练对象检测模型,通常有 2 个输入: 1. 一个图像。 2. 图像中每个对象的真实边界框。 该模型预测检测到的对象的边界框。预计预测框不会与实际框完全匹配。下图显示了一只猫的形象。物体的地面实况框是红色的,而预测的是黄色的。基于 2 个框的可视化,该模型是否做出了具有高匹配分数的良好预测? 很难主观评价模型预测。例如,有人可能断定有 50%的匹配,而其他人注意到有 60%的匹配。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/297464522614032b42862e52bef20586.png) [Image](https://pixabay.com/photos/cat-young-animal-curious-wildcat-2083492) without labels from [Pixabay](https://pixabay.com/photos/cat-young-animal-curious-wildcat-2083492) by [susannp4](https://pixabay.com/users/susannp4-1777190) 一个更好的替代方法是使用一个定量的方法来衡量实际情况和预测情况的匹配程度。这个度量是 union (IoU) 上的**交集。IoU 有助于了解一个区域是否有对象。** IoU 的计算公式如下:将两个方框的相交面积除以它们的并集面积。IoU 越高,预测越好。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/abb3147a2262bfe26220a20f69759c2d.png) 下图显示了三种不同借据的情况。请注意,每个案例顶部的借条都是经过客观测量的,可能与实际情况有所不同,但这是有意义的。 对于案例 A,黄色的预测框与红色的基本事实框相距甚远,因此 IoU 得分为 **0.2** (即两个框之间只有 20%的重叠)。 对于案例 B,两个方框之间的交叉面积较大,但两个方框仍未对齐,因此 IoU 得分为 **0.5** 。 对于情况 C,两个框的坐标非常接*,因此它们的 IoU 为 **0.9** (即两个框之间有 90%的重叠)。 请注意,当预测框和实际框之间有 0%的重叠时,IoU 为 0.0。当两个盒子 100%吻合时,IoU 为 **1.0** 。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/58c68e2b60c62172b5615fc8c3615142.png) 为了计算图像的 IoU,这里有一个名为`intersection_over_union()`的函数。它接受以下两个参数: 1. `gt_box`:地面真实包围盒。 2. `pred_box`:预测包围盒。 它分别计算`intersection`和`union`变量中两个框之间的交集和并集。此外,欠条是在`iou`变量中计算的。它返回所有这三个变量。 ```py def intersection_over_union(gt_box, pred_box): inter_box_top_left = [max(gt_box[0], pred_box[0]), max(gt_box[1], pred_box[1])] inter_box_bottom_right = [min(gt_box[0]+gt_box[2], pred_box[0]+pred_box[2]), min(gt_box[1]+gt_box[3], pred_box[1]+pred_box[3])] inter_box_w = inter_box_bottom_right[0] - inter_box_top_left[0] inter_box_h = inter_box_bottom_right[1] - inter_box_top_left[1] intersection = inter_box_w * inter_box_h union = gt_box[2] * gt_box[3] + pred_box[2] * pred_box[3] - intersection iou = intersection / union return iou, intersection, union ``` 传递给该函数的边界框是 4 个元素的列表,它们是: 1. 左上角的 x 轴。 2. 左上角的 y 轴。 3. 宽度。 4. 身高。 以下是汽车图像的地面实况和预测边界框。 ```py gt_box = [320, 220, 680, 900] pred_box = [500, 320, 550, 700] ``` 假设图像被命名为`cat.jpg`,下面是在图像上绘制边界框的完整代码。 ```py import imageio import matplotlib.pyplot import matplotlib.patches def intersection_over_union(gt_box, pred_box): inter_box_top_left = [max(gt_box[0], pred_box[0]), max(gt_box[1], pred_box[1])] inter_box_bottom_right = [min(gt_box[0]+gt_box[2], pred_box[0]+pred_box[2]), min(gt_box[1]+gt_box[3], pred_box[1]+pred_box[3])] inter_box_w = inter_box_bottom_right[0] - inter_box_top_left[0] inter_box_h = inter_box_bottom_right[1] - inter_box_top_left[1] intersection = inter_box_w * inter_box_h union = gt_box[2] * gt_box[3] + pred_box[2] * pred_box[3] - intersection iou = intersection / union return iou, intersection, union im = imageio.imread("cat.jpg") gt_box = [320, 220, 680, 900] pred_box = [500, 320, 550, 700] fig, ax = matplotlib.pyplot.subplots(1) ax.imshow(im) gt_rect = matplotlib.patches.Rectangle((gt_box[0], gt_box[1]), gt_box[2], gt_box[3], linewidth=5, edgecolor='r', facecolor='none') pred_rect = matplotlib.patches.Rectangle((pred_box[0], pred_box[1]), pred_box[2], pred_box[3], linewidth=5, edgecolor=(1, 1, 0), facecolor='none') ax.add_patch(gt_rect) ax.add_patch(pred_rect) ax.axes.get_xaxis().set_ticks([]) ax.axes.get_yaxis().set_ticks([]) ``` 下图显示了带有边界框的图像。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/9774edb368f99da5052b477737283a74.png) 要计算借据,只需调用`intersection_over_union()`函数。基于边界框,IoU 分数为`0.54`。 ```py iou, intersect, union = intersection_over_union(gt_box, pred_box) print(iou, intersect, union) ``` ```py 0.5409582689335394 350000 647000 ``` IoU 分数 **0.54** 意味着实际边界框和预测边界框之间有 54%的重叠。看着这些方框,有人可能会在视觉上觉得这足以断定模型检测到了猫对象。其他人可能会觉得这个模型还不准确,因为预测框不太符合实际情况。 为了客观地判断模型是否正确地预测了盒子位置,使用了一个**阈值**。如果模型预测到一个 IoU 分数大于或等于**阈值**的盒子,那么在预测的盒子和一个基本事实盒子之间有很高的重叠。这意味着模型能够成功地检测到对象。检测到的区域被分类为**阳性**(即包含物体)。 另一方面,当 IoU 分数小于阈值时,则模型做出了糟糕的预测,因为预测框不与基本事实框重叠。这意味着检测到的区域被归类为**阴性**(即不包含物体)。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/9871a7d5cb0ed5e35300b4138416f961.png) 让我们用一个例子来说明 IoU 分数如何帮助将一个区域分类为一个对象。假设对象检测模型由下一个图像提供,其中有 2 个目标对象,它们的地面实况框为红色,预测框为黄色。 接下来的代码读取图像(假定它被命名为`pets.jpg`),绘制方框,并计算每个对象的 IoU。左边对象的 IoU 是 **0.76** ,而另一个对象的 IoU 分数是 **0.26** 。 ```py import matplotlib.pyplot import matplotlib.patches import imageio def intersection_over_union(gt_box, pred_box): inter_box_top_left = [max(gt_box[0], pred_box[0]), max(gt_box[1], pred_box[1])] inter_box_bottom_right = [min(gt_box[0]+gt_box[2], pred_box[0]+pred_box[2]), min(gt_box[1]+gt_box[3], pred_box[1]+pred_box[3])] inter_box_w = inter_box_bottom_right[0] - inter_box_top_left[0] inter_box_h = inter_box_bottom_right[1] - inter_box_top_left[1] intersection = inter_box_w * inter_box_h union = gt_box[2] * gt_box[3] + pred_box[2] * pred_box[3] - intersection iou = intersection / union return iou, intersection, union, im = imageio.imread("pets.jpg") gt_box = [10, 130, 370, 350] pred_box = [30, 100, 370, 350] iou, intersect, union = intersection_over_union(gt_box, pred_box) print(iou, intersect, union) fig, ax = matplotlib.pyplot.subplots(1) ax.imshow(im) gt_rect = matplotlib.patches.Rectangle((gt_box[0], gt_box[1]), gt_box[2], gt_box[3], linewidth=5, edgecolor='r', facecolor='none') pred_rect = matplotlib.patches.Rectangle((pred_box[0], pred_box[1]), pred_box[2], pred_box[3], linewidth=5, edgecolor=(1, 1, 0), facecolor='none') ax.add_patch(gt_rect) ax.add_patch(pred_rect) gt_box = [645, 130, 310, 320] pred_box = [500, 60, 310, 320] iou, intersect, union = intersection_over_union(gt_box, pred_box) print(iou, intersect, union) gt_rect = matplotlib.patches.Rectangle((gt_box[0], gt_box[1]), gt_box[2], gt_box[3], linewidth=5, edgecolor='r', facecolor='none') pred_rect = matplotlib.patches.Rectangle((pred_box[0], pred_box[1]), pred_box[2], pred_box[3], linewidth=5, edgecolor=(1, 1, 0), facecolor='none') ax.add_patch(gt_rect) ax.add_patch(pred_rect) ax.axes.get_xaxis().set_ticks([]) ax.axes.get_yaxis().set_ticks([]) ``` 给定 IoU 阈值为 0.6,那么只有 IoU 得分大于等于 0.6 的区域被归类为**正**(即有物体)。因此,IoU 分数为 0.76 的方框为正,而 IoU 分数为 0.26 的另一个方框为**负**。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/5e759c198494e1d4322b7f86933a68a9.png) Image without Labels from [hindustantimes.com](https://www.hindustantimes.com/rf/image_size_960x540/HT/p2/2019/05/25/Pictures/_b336a8c4-7e7b-11e9-8a88-8b84fe2ad6da.png) 如果阈值变为 0.2 而不是 0.6,那么两个预测都是正的 T2。如果阈值是 **0.8** ,那么两个预测都是**负**。 总之,IoU 分数衡量的是预测框与现实框的接*程度。范围从 0.0 到 1.0,其中 1.0 是最佳结果。当 IoU 大于阈值时,则该盒子被分类为**正**,因为它围绕着一个物体。否则,被归类为**阴性**。 下一节将展示如何利用 IoUs 来计算对象检测模型的*均精度(mAP)。 ## **物体检测的*均精度(mAP)** 通常,用不同的 IoU 阈值来评估对象检测模型,其中每个阈值可以给出与其他阈值不同的预测。假设模型由一个图像提供,该图像有 10 个对象分布在两个类中。地图怎么算? 要计算 mAP,首先要计算每个类的 AP。所有类别的 AP 的*均值就是地图。 假设使用的数据集只有 2 个类。对于第一个类,这里分别是`y_true`和`pred_scores`变量中的基本事实标签和预测分数。 ```py y_true = ["positive", "negative", "positive", "negative", "positive", "positive", "positive", "negative", "positive", "negative"] pred_scores = [0.7, 0.3, 0.5, 0.6, 0.55, 0.9, 0.75, 0.2, 0.8, 0.3] ``` 下面是第二类的`y_true`和`pred_scores`变量。 ```py y_true = ["negative", "positive", "positive", "negative", "negative", "positive", "positive", "positive", "negative", "positive"] pred_scores = [0.32, 0.9, 0.5, 0.1, 0.25, 0.9, 0.55, 0.3, 0.35, 0.85] ``` IoU 阈值列表从 0.2 到 0.9,步长为 0.25。 ```py thresholds = numpy.arange(start=0.2, stop=0.9, step=0.05) ``` 要计算一个类的 AP,只需将它的`y_true`和`pred_scores`变量输入到下一个代码中。 ```py precisions, recalls = precision_recall_curve(y_true=y_true, pred_scores=pred_scores, thresholds=thresholds) matplotlib.pyplot.plot(recalls, precisions, linewidth=4, color="red", zorder=0) matplotlib.pyplot.xlabel("Recall", fontsize=12, fontweight='bold') matplotlib.pyplot.ylabel("Precision", fontsize=12, fontweight='bold') matplotlib.pyplot.title("Precision-Recall Curve", fontsize=15, fontweight="bold") matplotlib.pyplot.show() precisions.append(1) recalls.append(0) precisions = numpy.array(precisions) recalls = numpy.array(recalls) AP = numpy.sum((recalls[:-1] - recalls[1:]) * precisions[:-1]) print(AP) ``` 对于第一类,这是它的精确-回忆曲线。基于这条曲线,AP 为`0.949`。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ba4c66e1a85289aa8a87cfe9ffa0a928.png) 第二类的精度-召回曲线如下所示。它的 AP 是`0.958`。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/e8adba6687093b4188f294bb227b699e.png) 基于 2 个类别(0.949 和 0.958)的 AP,根据下式计算对象检测模型的 mAP。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/2b4321340f3759c16a07a2ff2b8ae2c3.png) 基于这个等式,映射为`0.9535`。 ```py mAP = (0.949 + 0.958)/2 = 0.9535 ``` ## **结论** 本教程讨论了如何计算对象检测模型的*均精度(mAP)。我们从讨论如何将预测分数转换为类别标签开始。使用不同的阈值,创建精确召回曲线。根据该曲线,测量*均精度(AP)。 对于对象检测模型,阈值是对检测到的对象进行评分的交集(IoU)。一旦为数据集中的每个类测量了 AP,就可以计算地图。 # 基于 Levenshtein 距离的文本相似性度量 > 原文:<https://blog.paperspace.com/measuring-text-similarity-using-levenshtein-distance/> 在文字处理或文本聊天应用程序中,用户经常会犯一些无意的拼写错误。可以简单到写“helo”(单个“l”),而不是“hello”。幸运的是,现在这样的应用程序可以检测出拼写错误,因为英语词典中不存在单词“helo”。例如,拼错的单词会用红色下划线突出显示。 由于人工智能(AI)的进步,这种应用不仅仅是检测拼写错误;他们现在也可以提出解决问题的建议,或者自动提出建议。此外,应用程序足够智能,可以只根据前几个字符来建议自动补全单词。这样的建议是怎么提出来的?我们将在本教程中看到如何实现。 本教程讨论 Levenshtein 距离,它计算两个单词之间的距离,并返回一个表示它们相似程度的数字。距离越小(即返回的数字越小),它们就越相似。所以通过比较单词“helo”和字典中的单词,我们可以找到最接*的匹配,“hello”,这将是一个建议的修复。 本教程通过一个**逐步动态编程示例**来阐明 **Levenshtein 距离**是如何计算的。然后在 **Python** 中实现距离。 本教程涵盖的部分如下: * Levenshtein 距离是如何工作的? * 利用动态规划逐步计算 Levenshtein 距离 * 用 Python 实现 Levenshtein 距离 ## Levenshtein 距离是如何工作的? Levenshtein 距离是单词之间的相似性度量。给定两个单词,距离测量将一个单词转换成另一个单词所需的编辑次数。有三种技术可用于编辑: 1. 插入 2. 删除 3. 替换(替代) 这三个操作中的每一个都会使距离增加 1。让我们把事情简单化。 ### 一个普遍的例子 给定两个单词, **hello** 和 **hello** ,Levenshtein 距离为零,因为这两个单词是相同的。 对于 **helo** 和 **hello** 这两个词,很明显少了一个字符“l”。因此,要将单词 **helo** 转换为 **hello** ,我们需要做的就是**插入**那个字符。在这种情况下,距离为 1,因为只需要一次编辑。 另一方面,对于两个单词 **kelo** 和 **hello** ,不仅仅需要插入字符“l”。我们还需要将字符“k”替换为“h”。经过这样的编辑,单词 **kelo** 被转换成 **hello** 。因此,距离为 2,因为应用了两个操作:替换和插入。 对于 **kel** 和 **hello** 这两个词,我们必须先将“k”替换为“h”,然后在末尾加上一个缺少的“l”再加上一个“o”。因此,距离为 3,因为应用了三个操作。 请注意,前面的讨论不是战略性的。我们遵循预定义的步骤,这些步骤可以应用于任何两个单词,将一个单词转换成另一个单词。我们现在要讨论的策略是如何使用动态规划计算距离矩阵。给定两个单词 *A* 和 *B* ,距离矩阵保存单词 *A* 的所有前缀和单词 *B* 的所有前缀之间的距离。 ### 战略性的例子 对于 **kelo** 这个词,可能的前缀有 **k** 、**柯**、 **kel** 、 **kelo** 共四个前缀。对于 **hello** 这个词,前缀是 **h** 、 **he** 、 **hel** 、 **hell** 、 **hello** 共五个前缀。距离矩阵计算两个单词中所有前缀之间的距离。这将创建一个 4 行 5 列的矩阵(或 5 行 4 列,取决于您选择哪个单词代表行,哪个单词代表列,见下图)。 从单词 **kelo** 的第一个前缀 **k** 开始,我们将它与单词 **hello** 的所有五个前缀进行比较。第一个会是 **h** 。 **k** 和 **h** 之间的 Levenshtein 距离是多少?因为我们需要做的就是用 **h** 代替字符 **k** ,那么距离就是 1。让我们转到单词 **hello** 中的下一个前缀,也就是 **he** 。 **k** 和 **he** 之间的 Levenshtein 距离是多少?因为前缀 **k** 包含单个字符,而 **he** 包含多个字符,所以我们可以 100%确定需要插入一个新字符。为了将 **k** 转换为 **he** ,首先将字符 **k** 替换为 **h** ,然后我们添加 **e** 。为了将 **k** 变换为 **he** ,因此距离为 2。 现在是第三个前缀。 **k** 和 **hel** 的距离是多少?我们将做和上面一样的事情,但是我们也需要添加另一个角色, **l** 。因此,最终距离为 3。 这个过程一直持续到我们计算出单词 **kelo** 或 **k** 的第一个前缀和第二个单词 **hello** 的所有 5 个前缀之间的距离。距离只是 1、2、3、4 和 5;它们只是增加 1(这是有意义的,因为我们必须为每个后续前缀多添加一个字符)。 请注意,第一个距离为 1 只是因为前两个前缀中的前两个字符(“k”和“h”)不匹配。如果这两个单词是 **helo** 和 **hello** ,那么前两个前缀(h 和 h)匹配,因此第一个距离将是 0。因此,helo (h)的第一个前缀和 hello 中的 5 个前缀之间的距离将是 0、1、2、3 和 4。 在计算了第一个单词的第一个前缀和第二个单词的所有前缀之间的距离之后,该过程继续计算第一个单词的剩余前缀和第二个单词的前缀之间的距离。 下一节讨论如何使用动态规划方法计算距离矩阵。 ## **准备距离矩阵** 在本节中,我们将准备距离矩阵,该矩阵将用于使用动态编程方法计算两个单词 **kelm** 和 **hello** 之间的距离。下一节将介绍填充这样一个矩阵的步骤。 重要的是要知道,动态编程通过将复杂的问题分解成许多简单的问题来解决它。解决了这样简单的问题,复杂的问题也就迎刃而解了。 第一步是初始化距离矩阵,如下表所示。除去用作标签的行和列,矩阵大小为*5×6*。行数 5 等于第一(4)个单词的字符数+1。列数也是如此。 代表两个单词之间距离的值是位于第五行(索引 4,从 0 开始)和第六列(索引 5)的矩阵右下角的值。 矩阵额外的行和列保存从 0 开始并递增 1 的数字。为什么要多加这么一行一列呢? ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ebc1e425b913f4fd63666e7fada73107.png) 简而言之,添加额外的行和列有助于应用动态编程来计算距离。现在来讨论一下长答案。 记住动态编程就是把一个复杂的问题分解成许多简单的问题。在我们的例子中,复杂的问题是通过填充上面的矩阵来计算 Levenshtein 距离。那么简单的问题呢? 简单的问题是只使用大小为 *2 x 2* 的矩阵,类似于下面给出的矩阵。由于用从`0`开始的数字初始化最左边的列和最上面的行,将总是有三个已经存在的元素,并且只有一个丢失的元素,该元素将总是对应于右下角的元素。 缺失元素的值可以根据这两个选项中的一个来计算(基于被比较的两个前缀是否相似): 1. 3 个现有元素中的最小值:`min(5, 2, 3)=2`。以防两个前缀相同。 2. 3 个现有元素的最小值+ 1 : `min(5, 2, 3) + 1 = 2 + 1 = 3`。以防两个前缀不同。 如果两个前缀相同,则无需做任何事情,因此不会增加额外的成本。当前缀不同时,要应用一个操作(插入、删除或替换)。这就是为什么要增加一笔`1`的额外费用。 通过以这种方式计算所有缺失的元素,将计算出整个距离矩阵。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/e78e93278543e26a3a24f70a941af233.png) 回到我们的问题,这就是为什么我们在距离矩阵中添加额外的行和列。假设要计算两个单词(k 和 h)的前两个前缀之间的距离。根据上面解释的动态编程方法,必须有三个已知值和一个缺失值(在下图中用红色标记)。通过比较 3 个现有值,将计算第四个值。 如果额外的行和列不存在,那么将有 4 个未知值,并且先前的方法在这种情况下将不适用。如果这样的行和列存在,那么将只缺少 1 个值,这就是使用动态编程解决问题的预期结果。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ba2a8e164ff31e6516d4dd417b84034e.png) 在明确了矩阵中附加行和列的目的之后,是时候填充距离矩阵以获得代表两个单词之间距离的值了。 ## **使用动态编程逐步计算 Levenshtein 距离** 在本节中,将填写距离矩阵,以找出位于右下角的两个单词之间的距离。 第一个要计算的距离是两个单词的前两个前缀之间,分别是 **k** 和 **h** 。该操作涉及的 *2 x 2* 矩阵如下图所示。因为两个前缀不同,所以它们之间的距离计算为 3 个现有值(0、1 和 1) + 1 中的最小值。所以,距离是`min(0, 1, 1) + 1 = 0 + 1 = 1`。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/9ade344631e604b5adfc99fa4e346288.png) 距离矩阵由每个计算的距离更新。电流矩阵如下所示。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/37ea5bdfc11659bd3a62117c2bfbc3c6.png) 让我们计算一下第一个单词的第一个前缀 **k** 和第二个单词的第二个前缀**和**之间的距离。如前所述,距离是 2,对于接下来的前缀,我们只需加 1 就可以得到距离 3、4 和 5。这是用这些距离更新后的距离矩阵。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/97655a4715896108f8cea09397ad09cd.png) 在填充了与单词 **kelo** 的前缀 **k** 对应的行之后,接下来我们将填充与第二个前缀: **ke** 对应的第二行。注意,第二行的标签只是 **e** ,而不是 **ke** 但是它暗示了 ke。例如,第二行的第一个缺失值表示将 **ke** 转换为 **h** 的距离,第二个缺失值表示将 **ke** 转换为 **he** 的距离,第三个缺失值表示将 **ke** 转换为 **hel** 的距离,以此类推。原因是 e 和单词 hello 中前缀之间的距离是根据为前缀 k 计算的距离计算的。 为了计算 **e** 和 **h** 之间的距离,将使用的 2x2 矩阵根据下图高亮显示。因为前缀 e 和 h 不同,所以合成距离等于`min(1, 1, 2) + 1 = 1 + 1 = 2`。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/05759094580dae378615d8717942c76e.png) 这是添加最*计算的距离后的新矩阵。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/4c1397088cbf7511c02edf47ae3c3e8a.png) 接下来比较 e(来自第一个单词)和 e(来自第二个单词),这里是突出显示的 2x2 矩阵。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/1fa4cc46984c182d2f970c189c5f20a6.png) 因为两条边(e 和 e)是相同的,所以距离将被计算为 2x2 矩阵中 3 个现有值的最小值。于是,结果就是`min(1, 2, 2) = 1`。下图显示了用新计算的距离更新后的矩阵。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/1a80ea2f6cac46fc95b7ecd169ffa081.png) 该过程继续,直到对应于“e”的行的所有单元都被填充。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/fcac96098c0ea0cfd168305e9057ecf7.png) 继续这样,完整的距离矩阵如下所示。两个完整单词**凯尔姆**和**你好**之间的距离位于右下角,距离的路径用蓝色标出。右下角单元格的值为 **3** ,这意味着将 **kelm** 转换为 **hello** 需要 3 次编辑。这 3 个编辑是: 1. 用 **h** 替换 **k** 。 2. 用 **l** 替换 **m** 。 3. 插入 **o** 。 这标志着这个例子的结束。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/a4bd9a88069d9641cbb33e4d59296b95.png) 在不展示步骤的情况下,这里是另一个距离矩阵,用于测量单词 **sittmg** 和 **setting** 之间的距离。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/7c5a9939dcc6b1b5231a1b37cd86f488.png) ## **结论** 本教程讨论了 Levenshtein 距离,它通过计算将一个单词转换为另一个单词所需的单字符编辑次数来测量两个单词之间的距离。三种可能的编辑是插入、删除和替换。 通过一个实例,说明了用动态规划法计算距离矩阵的具体步骤。该问题被分成计算一个*2×2*矩阵的最小值的小问题。 在下一篇文章中,我们将看到如何使用 Python 实现 Levenshtein 距离。 # 深度学习混合精度训练的基准 GPU > 原文:<https://blog.paperspace.com/mixed-precision-training-benchmark/> 在本指南中,我们将探索混合精度训练,以了解我们如何在代码中利用它,它如何适应传统的深度学习算法框架,哪些框架支持混合精度训练,以及 GPU 使用混合精度的性能提示,以便查看一些真实世界的性能基准。 随着深度学习方法的发展,很明显*增加*神经网络的规模可以提高性能。然而,为了训练模型,这是以较高的内存和计算需求为代价的。稍后,我们将了解混合精度训练在 GPU 内存上的优势。 这可以通过评估 BERT, [Google 的](https://blog.google/products/search/search-language-understanding-bert/)预训练语言模型在不同架构规模下的性能来观察。 > 去年,谷歌[发布并开源了来自 Transformers(简称 BERT)的双向编码器表示,这是一种基于神经网络的自然语言处理(NLP)方法。任何人都可以使用这种技术来训练自己的前沿问题回答系统,使其在保持模型准确性的同时运行更快,消耗更少的内存。](https://ai.googleblog.com/2018/11/open-sourcing-bert-state-of-art-pre.html) 2017 年,一组英伟达研究人员[发表了](https://arxiv.org/pdf/1710.03740.pdf)一篇论文,描述了如何通过采用一种被称为**混合精确训练**的技术来最小化训练神经网络的内存需求 框架开发者、希望优化混合精度训练优势的用户以及致力于训练数字的研究人员会对这篇博文感兴趣。 首先,在我们探索如何在代码中利用它之前,我们将尝试理解混合精度训练及其与深度学习的联系。之后,我们将探讨哪些框架支持混合精确训练。 ## 你对混合精确训练了解多少? **混合精度训练**涉及在训练期间在模型中使用低精度运算(float16 和 bfloat16 ),以帮助训练快速运行并消耗更少的内存。 使用混合精度可以将新型 GPU 的性能提高三倍以上,将 TPU 的性能提高 60%。例如,每一代连续的 Nvidia GPUs 都增强了张量内核和其他技术,从而实现越来越好的性能。 ### 记号 * FP16 —半精度、16 位浮点—占用 2 个字节的内存。 * FP32 —单精度、32 位浮点—占用 4 个字节的内存。 混合精度训练的基本概念很简单:一半的精度(FP32 - FP16),一半的训练时间。 Pascal 架构实现了训练精度降低的深度学习网络的能力,最初是在 NVIDIA 深度学习 SDK 的 CUDA 8 中支持的。 下图(来源:Nvidia)显示了 FP16 和其他数据传输类型的比较——FP16 比 FP32 更快。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/120e9365021bef6d544170a9845211f1.png) 大多数框架开发人员在训练深度学习模型时,对所有计算内核使用单精度浮点数。詹姆斯·斯凯尔顿的这个 [Paperspace](https://blog.paperspace.com/mixed-precision-training/) 博客展示了降低精度训练对在有限数据集上训练的几个模型有效。此外,16 位半精度浮点数足以在更大的数据集上有效地训练大型深度学习模型。这使得硬件销售商可以利用现有的计算能力。 上面讨论的参数需要整个模型最大存储量的一半。算术运算所需的最小精度分别设置为 16 位和 32 位。当使用 16 位精度并在 float32 中维护模型的一些重要部分时,模型在训练期间的运行速度会比使用 32 位精度时更快。 NVIDIA GPUs 的最新版本包括特别独特的张量核心,专为快速 FP16 矩阵计算而开发。但是,直到最*,这些张量核的使用一直具有挑战性,因为它们需要在模型中进行手写的精度降低的计算。 ## 具有深度学习的自动混合精确训练 深度神经网络(DNNs)通过在图像和语音识别等智能测试中取得非凡成就,改变了人工智能。然而,训练大 dnn 在计算上是昂贵的,这推动了针对该功能的创新硬件*台的探索。 因此,通常使用混合精度训练架构,该架构将执行加权汇总和不精确电导更新的计算存储单元与收集高精度权重更新的数字处理单元相结合。 使用相变存储器(PCM)阵列对所提出的体系结构进行递归神经网络评估的混合软件训练实验在对手写数字进行分类的任务上(基于 MNIST 数据集)实现了 97.73%的测试准确度,在软件参考点的 0.6%之内。 然后在各种网络上测试该设计,包括卷积神经网络、长短期记忆网络和生成-对抗网络,利用 PCM 的正确适当模型。 获得了等同于浮点实现的精度,而不受 PCM 器件的非理想性的限制。根据一项系统级研究,当用于训练多层感知器时,与完全专用的数字 32 位实现相比,该架构的能效提高了 172。 由于计算和数据传输吞吐量的增加,混合精度已经成为加速深度学习模型的标准算法。经常使用 IEEE 单精度格式训练的深度学习模型被发现在使用混合精度时对表示长度的减少具有弹性,因为它们可以匹配最先进的性能。为了避免梯度下溢,用户可以固定他们的损耗缩放值,或者利用自动损耗缩放策略,该策略将通过算法决定要应用的适当缩放值。 ## 使用 PyTorch 进行代码演练 有了这些重要的知识,我们就可以安全地进入新的 PyTorch AMP API 了。 本节包含如何使用 PyTorch 库为混合精度训练编码卷积神经网络(CNN)的简单演练。 为了演示,我们将在非常常见的手写数字 MNIST 数据集上训练它。 通过这段代码,我们可以了解如何创建网络以及初始化损失函数、优化器、检查准确性等等。 在开始之前,确保您的机器上安装了 Python3。 ##### 让我们为这个项目创建一个工作空间,并使用终端安装您需要的依赖项。 您可以跳过步骤 1-5,直接点击下面链接的“梯度运行”按钮进入演示! 1. 将您的工作区命名为 **py-torch** : ```py $ mkdir ~/pytorch ``` 2.创建一个文件夹来保存您的资产: ```py $ mkdir ~/pytorch/assets ``` 3.导航到 pytorch 目录 ```py $ cd ~/pytorch ``` 4.为项目创建一个新的虚拟环境 ```py $ python3 -m venv pytorch ``` 5.激活您的环境 ```py $ source pytorch/bin/activate ``` 6.安装 PyTorch ```py pip install torch==1.7.1+cpu torchvision==0.8.2+cpu -f https://download.pytorch.org/whl/torch_stable.html ``` 7.为了使这一部分更快,编辑 py-torch.py 文件中的导入,使其看起来完全像这样: ```py #Imports import torch import torchvision # torch package for vision related things import torch.nn.functional as F # Parameterless functions, like (some) activation functions import torchvision.datasets as datasets # Standard datasets import torchvision.transforms as transforms # Transformations we can perform on our dataset for augmentation from torch import optim # For optimizers like SGD, Adam, etc. from torch import nn # All neural network modules from torch.utils.data import DataLoader # Gives easier dataset managment by creating mini batches etc. from tqdm import tqdm # For nice progress bar! ``` 接下来:在这个库[这里](https://github.com/daveclinton/mixed-precision-training/blob/main/py-torch.py)检查我的代码,并复制到你的 **py-torch.py** 工作区: 8.编辑简单的 CNN 部分,将代码第 18 行的 out-chaNNels 增加到 420,in_channels = 420,out_channels = 1000,nn。线性到 1000。逐行使用下面的代码片段。 ```py # Simple CNN class CNN(nn.Module): def __init__(self, in_channels=1, num_classes=10): super(CNN, self).__init__() self.conv1 = nn.Conv2d( in_channels=in_channels, out_channels=420, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), ) self.pool = nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)) self.conv2 = nn.Conv2d( in_channels=420, out_channels=1000, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), ) self.fc1 = nn.Linear(1000 * 7 * 7, num_classes) ``` 9.这里,让我们在 CNN 类下定义另一个函数,它有两个参数,返回我们计算的值: 你可以遵循我的代码,以确保你不会错过。 ```py def forward(self, x): x = F.relu(self.conv1(x)) x = self.pool(x) x = F.relu(self.conv2(x)) x = self.pool(x) x = x.reshape(x.shape[0], -1) x = self.fc1(x) return x ``` 10.设置设备 ```py device = torch.device("cuda" if torch.cuda.is_available() else "cpu") ``` 11.配置超参数 确保 float32 的限制为 batch _ size 590,float16 的限制为 batch _ size 1000。但是,在训练网络时,我们应该始终使用 float16 作为默认设置。 ```py in_channels = 1 num_classes = 10 learning_rate = 0.001 batch_size = 590 num_epochs = 3 ``` 12.接下来,我们加载数据: ```py train_dataset = datasets.MNIST(root="dataset/", train=True, transform=transforms.ToTensor(), download=True) test_dataset = datasets.MNIST(root="dataset/", train=False, transform=transforms.ToTensor(), download=True) train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True) test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True) ``` 13.初始化神经网络 ```py model = CNN(in_channels=in_channels, num_classes=num_classes).to(device) ``` 14.损失和优化器:这里我们设置 float16 训练,看看在我们的训练循环之前,我们现在可以使用多大的批量。 ```py criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=learning_rate) scaler = torch.cuda.amp.GradScaler() ``` 15.执行混合精度的自定义训练循环需要两处修改,而不是在 float32 中遍历它; ```py # Train Network for epoch in range(num_epochs): for batch_idx, (data, targets) in enumerate(tqdm(train_loader)): # Get data to cuda if possible data = data.to(device=device) targets = targets.to(device=device) # forward with torch.cuda.amp.autocast(): scores = model(data) loss = criterion(scores, targets) # backward optimizer.zero_grad() scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() ``` 16.定义一个函数来检查培训的准确性; ```py def check_accuracy(loader, model): num_correct = 0 num_samples = 0 model.eval() with torch.no_grad(): for x, y in loader: x = x.to(device=device) y = y.to(device=device) scores = model(x) _, predictions = scores.max(1) num_correct += (predictions == y).sum() num_samples += predictions.size(0) model.train() return num_correct/num_samples ``` 最后一步:测试我们的代码,看看我们的模型有多好: ```py print(f"Accuracy on training set: {check_accuracy(train_loader, model)*100:.2f}") print(f"Accuracy on test set: {check_accuracy(test_loader, model)*100:.2f}") ``` 你也可以在 NVIDIA 官方文档中查看更多关于混合精度训练的信息。 ## 混合精确训练深度学习框架 TensorFlow、PyTorch 和 MXNet 是目前提供自动混合精度的框架。 对于所有的框架,使用混合精度需要三个主要步骤; 1. 尽可能将模型转换为使用 float16 数据类型。 2. 保持浮动 32 个主权重,以累积每次迭代的权重更新。 3. 使用损失缩放来保留小的梯度值。 在本文的其余部分,我们将重点关注 TensorFlow,因为它通常用于 Maxwell、Pascal、Volta、Turing 和 Ampere 微体系结构的 Paperspace GPUs。 ### 在 Tensorflow 中启用混合精度训练 在 TensorFlow 中,可以使用自动混合精度扩展(TF-AMP)启用混合精度,该扩展在重新捕获时将变量建模为半精度,同时保持变量为单精度格式。 此外,当使用梯度时,必须结合损耗放大[步骤](https://docs.nvidia.com/deeplearning/sdk/mixed-precision-training/index.html#lossscaling)以保护随机梯度下降中的微小梯度幅度。TensorFlow 中的损耗缩放可通过将损耗乘以固定量来静态完成,或通过 TF-AMP 自动完成。自动混合精度完成 TensorFlow 中的所有更改,与手动处理相比有两个优势。 首先,开发人员不需要调整网络模型代码,减少了开发和维护时间。此外,利用 AMP 可以确保与所有 TensorFlow APIs 向前和向后兼容,以定义和执行模型。 纯粹将值添加到训练脚本中的环境变量,以允许混合精度: * 启用 TF-AMP 图形重写: ```py os.environ["TF_ENABLE_AUTO_MIXED_PRECISION_GRAPH_REWRITE"] = "1" ``` * 启用自动混合精度: ```py os.environ['TF_ENABLE_AUTO_MIXED_PRECISION'] = '1' ``` #### 启用张量浮点-32 张量运算是 Nvidia A100 GPUs 中的新数学模式,用于使用 TF32 计算矩阵数学。与 Volta GPUs 上的单精度浮点数学(FP32)相比,它们可以提供高达 10 倍的加速。 TF32 张量核可以使用 FP32 加速网络,通常不会损失精度。对于需要高动态范围的权重或激活的模型,它比 FP16 更健壮。 NVIDIA Ampere GPU 架构支持 TF32,默认启用。 更多细节可以在 TensorFloat-32 部分的 A100 GPU 加速人工智能训练,HPC 高达 20 倍博客文章中找到。您还可以通过前往 [GPU 云比较](https://www.paperspace.com/gpu-cloud-comparison)查看 A100 如何与 Paperspace 的多样化 GPU 选择相匹配。 ### 理解 TensorFloat-32 数学 数学格式类似于标尺。格式模数中的位数会影响它的范围,或者它可以评估的项的大小。精度取决于尾数所用的位数,尾数是浮点数中跟在基数或小数点后面的部分。 一个伟大的数学格式符合完美的*衡。为了提供精度,它应该利用足够的位来避免缓慢的性能或内存膨胀。 Nvidia 的下图描绘了 TF32 是如何实现张量计算*衡的混合物。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/447fc961da722004fff975bf5b291212.png) TF32 finds a balance between performance, range, and accuracy. TF32 采用与半精度(FP16)数学相同的 10 位尾数,这已经被证明为 AI 任务的精度标准提供了足够多的公差。TF32 也使用与 FP32 相同的 8 位乘法器,允许它处理相同的数学限制。 正因为如此,TF32 是 FP32 的一个惊人补充,用于进行单精度数学运算,特别是深度学习和几个 HPC 应用程序核心的巨大乘-累加运算。 使用 NVIDIA 库的应用程序允许用户在不改变代码的情况下获得 TF32 的好处。TF32 张量核处理 FP32 输入并输出 FP32 结果。FP32 仍然用于非矩阵计算。A100 还具有增强的 16 位数学能力,以实现最佳性能。它支持 FP16 和 Bfloat16 (BF16),速度是 TF32 的两倍。只需几行代码,用户就可以通过使用自动混合精度获得 2 倍的速度提升。 ### 成功的 TensorFlow 项目 本节讨论用于为 TensorFlow 训练 BERT 模型的脚本和配方,该模型用于实现由 NVIDIA 测试和维护的最先进的精确度。点击查看这个模型的公共库[。](https://github.com/NVIDIA/DeepLearningExamples/tree/master/TensorFlow/LanguageModeling/BERT#model-overview) 该存储库包含脚本,用于在 Docker 容器中以交互方式启动数据下载、培训、基准测试和推理例程,以便对问题回答进行预培训和微调。 #### BERT 模型概述 NVIDIA 使用的 BERT(来自变压器的双向编码器表示)是谷歌官方实施的定制副本,在 A100、V100 和 T4 GPU 上使用混合精度算术和张量核心,在保持精度的同时提高训练速度。 你可以查看这个由 NVIDIA 提供 BERT 模型的公共库。它使用 NVIDIA Volta、Ampere 和 Turing GPUs 上的张量核心以混合精度进行训练。这意味着,程序员可以获得比没有张量核心的训练快 4 倍的响应,同时体验混合精度训练的优势。 ### 伯特模型的体系结构 BERT 模型的架构是一个复杂的双向变压器编码器。根据模型大小,BERT 训练包括两个步骤,这两个步骤在大量未标注的数据集上以无监督的方式预训练语言模型,并且还使用这个预训练的模型在许多 NLP 活动中进行微调。该模型为精确任务提供了一个附加层,并进一步训练该模型使用特定于任务的带注释的数据集,从预训练的权重开始。该过程的初始和最终阶段如下图所示; ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d32cb95ee8fd79a7804f7af40c030789.png) Image Source; [NVIDIA](https://github.com/NVIDIA/DeepLearningExamples/raw/master/TensorFlow/LanguageModeling/BERT/daimg/bert_pipeline.png?raw=true) ## 在 GPU 上使用混合精度时的性能提示。 ### 增加批量 如果这对模型质量没有影响,请尝试在使用混合精度时以两倍的批量运行。因为 float16 tensors 使用了一半的容量,所以您可以经常在不耗尽内存的情况下将批处理大小加倍。增加批量大小通常会增加训练吞吐量,或者模型在每个会话中可以运行的训练组件的数量。 ### 确保 GPU 张量核心的利用率 现代 NVIDIA GPUs 包括一个称为张量核心的特定硬件单元,它可以快速乘以浮点矩阵。另一方面,张量核要求某些张量维数是 8 的倍数。 如果你想进一步学习, [NVIDIA 深度学习性能指南](https://docs.nvidia.com/deeplearning/performance/index.html)提供了使用张量核的具体要求以及与张量核相关的增强性能统计。 ## 真实世界性能基准 [拼写 API](https://spell.ml/docs/run_overview/) 用于自动精确训练神经网络,也使用 V100s(旧张量核)和 T4s(当前张量核)。当在混合精度和普通网络之间使用时,上述每个模型都同等收敛。以下是用上述模型训练的网络列表: * **前馈-** 根据从[罗斯曼商店样本](https://www.kaggle.com/c/rossmann-store-sales)ka ggle 上的竞争中获得的数据训练的神经网络。在这里浏览[代码。](https://github.com/spellml/feedforward-rossman) * **UNet -** 一个普通的 [UNet 图片分割网](https://arxiv.org/abs/1505.04597)中等大小,在分割后的 Bob [Ros](https://www.kaggle.com/datasets/residentmario/segmented-bob-ross-images) s 图片语料库上训练。[点击此处获取代码。](https://github.com/spellml/unet-bob-ross) * **BERT -** 一个巨大的 NLP [transformer](https://jalammar.github.io/illustrated-transformer/) 模型,用 huggingface 的 bert-base-uncased 模型主干和来自 Kaggle 的 Twitter 情感提取比赛的数据构建。[点击此处获取代码。](https://github.com/spellml/tweet-sentiment-extraction) 样本结果: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/65b920d8d5e17cc9164605c06a17226a.png) BERT 是一个巨大的模型,正是在这里,采用混合精确训练节省的时间成为“必备”对于在 Volta 或 Turing GPUs 上训练的大模型,自动混合精度将节省 50%至 60%的训练时间。 因此,您应该对模型训练脚本应用的第一个性能改进是混合精度。 如果你想自己复制任何基准,可以在 GitHub 的[spell run/前馈-罗斯曼](https://github.com/spellrun/feedforward-rossman)、 [spellrun/unet-bob-ross、](https://github.com/spellrun/unet-bob-ross)和[spell run/tweet-情操-提取](https://github.com/spellrun/tweet-sentiment-extraction)库中找到示例模型源代码。 要使用 Paperspace Gradient 运行这些功能,只需使用以前的 URL 作为笔记本创建页面的高级选项部分中的工作区 URL。 ## GPU 内存带宽使用 现在,我们已经从前面的章节中了解了混合精度的工作原理。混合精度训练的一个优点是内存使用。与 GPU 内存相比,GPU 计算更复杂,但优化很重要。请记住,GPU 上的批处理量越大,内存消耗就越有效。 PyTorch 在模型训练过程开始时分配固定数量的 GPU 内存,并在训练操作的整个过程中保持该内存。这可以防止其他任务在训练期间提交过多的 GPU RAM,否则会导致 PyTorch 训练脚本因 OOM 错误而崩溃。 以下是激活混合精度训练对 PyTorch 记忆保留行为的影响: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ec4027929a44b33c13cc5d1d3b65abfb.png) 令人惊讶的是,虽然两个较大的模型都从转换到混合精度中受益,但 UNet 比 BERT 受益更多。 ## 外卖食品 自动混合精度训练是 PyTorch 1.6 中一个简单而强大的新功能,它有望将现代 NVIDIA GPUs 上运行的更大规模的模型训练操作加速高达 60%。 正如本文所详述的,一代又一代 GPU 的技术进步可以部分地以混合精度训练为特征,因此应该尽可能地实施。如果您有需要,Paperspace 可以帮助您优化 GPU 云支出和效率。 此外,当使用 RTX、特斯拉或安培 GPU 时,梯度用户应采用自动化混合精度训练。这些 GPU 包括张量内核,可以加速 FP16 运算。 # 混合精确训练 > 原文:<https://blog.paperspace.com/mixed-precision-training-overview/> ### 介绍 使用多种数值精度组合的计算方法称为混合精度。混合精度训练指的是一种深度神经网络训练技术,它在可行时采用半精度,在不可行时采用完全精度。使用混合精度训练有两个阶段: * 移植模型以在适当的地方使用 FP16 数据类型。 * 添加损耗缩放以保留小的梯度值。 这篇文章友好地概述了混合精度训练。 ### 使用混合精确训练的一些好处 通过对算术密集型模型架构使用混合精度训练,可以将速度提高三倍。在内存受限的系统中采用半精度的好处也可能导致性能的提升。以低于 32 位浮点的精度训练深度神经网络有几个优点。 精度越低,训练更广泛的网络所需的内存就越少。结果,训练过程更有效,因为它占用更少的存储器带宽。最后,精度越低,数学运算的速度越快。 ### 我们说的“精度更低”是什么意思? 32 位浮点(FP32)格式被称为单精度。当我们说“低精度”时,我们通常指的是 16 位浮点(FP16)格式。顾名思义,FP32 格式使用 32 位内存,而 FP16 格式使用 16 位内存。 因此,访问的字节数也减少了。另一方面,从 FP32 到 FP16 会降低精度,这在深度学习中处理微小的激活梯度值时至关重要。当梯度值太小而不能用 FP32 数字表示,但太大而不能用 FP16 数字表示时(在这种情况下,梯度值变为零),FP16 训练精度会受到影响。 可以缩放渐变值,使它们落在 FP16 的可表示值范围内,从而减少出现的精度损失。 ### FP32 砝码主副本:来自纸张的插图 > 在混合精度训练中,权重、激活和梯度存储为 FP16。为了匹配 FP32 网络的准确性,在优化步骤中,维护 FP32 主权重副本,并使用权重梯度进行更新。在每次迭代中,主权重的 FP16 副本用于向前和向后传递,将 FP32 训练所需的存储和带宽减半。 [研究人员使用了一个普通话语音模型](https://www.arxiv-vanity.com/papers/1710.03740/),在 20 个时期的 800 小时语音数据的数据集上进行训练,以证明 FP32 主副本权重的必要性。 如下图所示,在 FP16 向前和向后传递后更新 FP32 主权重副本会导致与 FP32 训练结果匹配;但是,更新 FP16 权重会导致相对精度损失 80%。与单次精确训练相比,即使保留一份额外的重量副本也会增加 50%的重量记忆需求;对总内存消耗的影响要低得多。因为增加的批量大小和每层的激活被保存以在反向传播阶段重用,所以激活支配训练存储器的使用。 因为激活也是以半精度格式记录的,所以深度神经网络训练的总内存使用量大约减半。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/be0d318e99a0133b14500d0ce4bf205c.png) ### 使用缩放来防止精度损失 这篇论文强调了这样一个事实,即虽然梯度值往往以小幅度为主,但 FP16 指数偏差将归一化值指数的范围集中在[14,15]之间。通过从在多盒 SSD 检测器网络的 FP32 训练期间跨所有层收集的激活梯度值的直方图中取一个例子,我们可以观察到 FP16 可表示范围的大部分未被使用。 许多值低于最小可表示范围,变成了零。通过按比例放大梯度,有可能保留否则会丢失为零的值。放大梯度也将移动它们以占据更多的可表示范围。 当梯度没有被缩放时,这个网络会发散,但是将它们缩放 8 倍就足以达到与 FP32 训练相同的精确度。这表明,活化梯度值低于 2^-27 的数量级与该模型的训练无关,但在[2^-27,2^-24]范围内的值是必须保留的。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/2de6226e7f5a97338c641b4399d8eef4.png) Histogram of activation gradient values during Multibox SSD network training  一个太小而不能用 FP16 表示的数(因此用零表示会有问题)可以通过一个允许用 FP16 表示的因子进行缩放来检索。激活梯度是深度学习应用中感兴趣的小值。缩放通常发生在正向传播和反向传播的步骤之间。 如果反向传播输入被某个因子缩放,那么所有随后的激活梯度也将被该因子缩放;因此,反向传播技术的其余部分不需要缩放。为了更新权重,当反向传播完成时,可以向优化器提供未缩放的梯度。一般来说,方法如下: * 执行正向传播 * 将输出放大 X 倍 * 反向传播技术应用 * 按 1/X 的比例缩小权重梯度 * 优化器更新权重 ### 选择比例因子 损耗比例因子可以从各种选项中选择,恒定比例是最简单的选项。 从 8 到 32K,他们训练了广泛的网络(许多网络不需要缩放因子)。可以通过实验选择比例因子,或者,如果梯度统计数据可用,可以通过选择其乘积的最大绝对梯度值小于 65,504 的因子来直接确定恒定比例因子。 只要不通过反向传播产生溢出,选择一个显著的比例因子没有负面影响。**溢出将导致权重梯度中的无穷大和 NaNs,这将在更新**后永久破坏权重。应当注意,检查计算的重量梯度是发现溢出的有效方法。 动态选择损耗比例因子更可靠。有必要从一个重要的比例因子开始,并在每次训练迭代完成后重新评估它。如果在 N 次迭代后没有发生溢出,可以增加比例因子。如果有溢出,应该跳过权重更新。动态损失比例方法导致以下高级培训程序: 1. 在 FP32 中保留一份重量副本作为主副本。 2. 将 S 设置为一个相当大的值。 3. 对于每次迭代: a.制作重量的 FP16 副本。 b.正向传播(FP16 权重和激活)。 c.将所得损耗乘以比例因子 s。 d.执行反向传播(FP16 权重、激活及其梯度)。 e.如果重量梯度中存在 Inf 或 NaN: 一、减少 s。 二。继续下一次迭代,不更新权重。 f.用 1/S 执行权重梯度乘法。 g.完成权重更新(包括渐变裁剪等。). h.如果在最后 N 次迭代中没有出现 Inf 或 NaN,则应该增加 s。 ### 张量核是如何工作的 张量核于 2017 年底在 Volta 架构中推出,在上一代图灵架构中得到改进,并在 Ampere 架构中得到进一步完善。在 [Paperspace GPU 云比较](https://www.paperspace.com/gpu-cloud-comparison)上查看安培、伏特和图灵 GPU 性能值的相互比较。 因为张量核可能优于标准 GPU 核,所以张量核是一个理想的特性。Nvidia Volta GPUs Tesla V100、Quadro V100 和 Titan V 拥有 640 个张量内核,最高可提供混合 FP16-FP32 精度的 TFLOPS。 传统的 CUDA 内核共有 5120 个 GPU,在 FP32 精度下可提供高达 15 TFLOPS 的性能,在 FP64 精度下可提供大约 7 TFLOPS 的性能。事实上,张量核的速度使得研究可能受益于这项新技术的新应用成为可能。 也就是说,利用张量核并不是没有限制和潜在的陷阱。第一个限制是张量核心编程实质上比典型的 GPU 编程更受限制。如果使用张量核,只能对 4×4 矩阵进行 MMA(矩阵-乘法-累加)运算(只需要一个 GPU 周期),这是使用张量核时唯一可以完成的运算。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/17519668215d51cf5f1ca0381ade41cb.png) 因此,创建可以受益于张量核加速的算法的唯一方法是将其重构为可以并行运行的多个 4×4×4 MMA 运算的集合。 数值精度是张量核的第二个限制。A×B+C 运算目前在 FP16 精度下完成,即使 D 矩阵存储在 FP32 中。这种混合运行方式可能会对某些应用程序产生不利影响。 然而,FP16 是运行张量核的绝佳场所,因为深度学习应用通常不会在 FP16 的训练和推理过程中遇到数值精度问题。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/4e82b022aa38c77bc5b31b0255b12a30.png) 该操作可以是更广泛的 FP16 矩阵乘法操作的构建组件。假设大多数反向传播涉及矩阵乘法,张量核可以用于任何需要大量计算的网络层。 输入矩阵必须是 FP16 格式。如果你在基于张量核心的 GPU 上训练,而不是利用混合精度训练,你的 GPU 就没有得到最大的利用。你可以看看这篇[文章](https://blog.paperspace.com/understanding-tensor-cores/#how-do-tensor-cores-work),它会给你一个张量核的概述,并帮助你理解它们是如何工作的。 ### CUDA 库中的张量核心 CuBLAS 和 cuDNN 是两个利用张量核的 CUDA 库。为了加速 GEMM 计算,cuBLAS 使用张量核。由于 cuDNN,使用张量核可以更快地处理递归神经网络(rnn)和卷积。 > GEMM 是矩阵-矩阵乘法的术语。 这些模型广泛应用于信号处理、流体动力学和许多其他领域。这些应用需要不断提高处理速度,以跟上不断增长的数据量。通过查看下图中的混合精度 GEMM 性能图,我们可以很容易地看出这一点。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d41cca2afce492dbe4fe9f30a508d44b.png) Performance comparison of matrix-matrix multiplication (GEMM)s on Tesla V100 (Volta) using Tensor Cores versus Tesla P100 (Pascal). Input matrices are half precision, computation is single precision. 随着人工智能研究人员不断拓展可能性的边界,神经网络每年都变得越来越复杂。目前最深的网络超过几十个卷积层。在前向和反向传播期间,需要卷积层来训练 dnn。下图描述了卷积性能图表,表明张量核可以满足高卷积速度的需求。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/730fa4e48bbd8331aec50a026f930eec.png) Convolution performance on Tesla V100 (Volta) using Tensor Cores against Tesla P100 (Pascal). The geometric means of run times of the convolution layers from each neural network are compared. The V100 and the P100 utilize FP16 input/output data and FP32 computation; the V100 employs Tensor Cores, while the P100 uses FP32 fused-multiply add (FMA).  在这两个图表中,可以看出特斯拉 V100 的张量核优于特斯拉 P100。计算的方式因这些性能的提高而改变:可以探索交互式场景,并且由于这些性能的提高,可以更节省地使用服务器群。 ### 张量核优化 混合精度训练的硬件加速由 NVIDIA Tensor Cores 提供。在使用张量核的 V100 GPU 上,float16 矩阵乘法性能可能比 float32 提高八倍。张量核可能需要修改模型代码以充分利用它们。Nvidia 的[文档](https://docs.nvidia.com/deeplearning/performance/mixed-precision-training/index.html#tensor-core-shape)提供了充分利用张量内核的三个步骤: 1. 满足张量核心形状约束 2. 增加算术强度 3. 减少非张量核心运算中的工作分数 这个优势列表以越来越复杂的方式呈现,初始阶段通常以最少的工作量提供最大的价值。 ### 满足张量核心形状约束 张量核的输入受到其结构的形状限制。 要乘矩阵,请考虑以下规则: * FP16 输入的所有三个维度(M、N 和 K)必须是 8 的倍数。 * 对于 INT8 输入,所有三个维度必须是 16 的倍数(仅适用于图灵)。 谈到卷积过程: * FP16 输入的输入和输出通道必须是 8 的倍数。 * INT8(仅限图灵)输入上的输入和输出通道必须是 16 的倍数。 [深度学习性能指南](https://docs.nvidia.com/deeplearning/performance/index.html)提供了对张量核心执行、这些约束源自何处以及它们如何在现实世界模型设计中具体化的深入研究。以下是 Nvidia 文档中关于混合精度训练的建议: * 小批量应该是 8 的倍数。 * 线性层尺寸应选择 8 的倍数。 * 对卷积层通道数使用 8 的倍数。 * 填充词汇是 8 的倍数,用于涉及分类的挑战。 * 填充序列长度的倍数 8 以解决序列问题。 ### 增加算术强度 > 算术强度衡量每个输入字节在内核中将执行多少计算工作。例如,V100 GPU 具有 125 TFLOPs 的数学吞吐量和 900 GB/s 的内存带宽。取两者之比,我们看到任何每输入字节少于~140 FLOPs 的内核都是内存受限的。张量核不能以全吞吐量运行,因为内存带宽将是限制因素。具有足够算术强度以允许全部张量核吞吐量的核是计算受限的。 在模型实现和模型架构中,运算强度可以增加。要增加模型实现中的算术强度: * 在循环细胞中,连接权重和门激活 * 序列模型允许您随时连接激活 作为增加模型算术强度的一种方法: * 密集的数学运算是优选的:众所周知,深度方向可分离卷积比普通卷积具有更低的算术强度 * 说到精度,尽可能选择最宽的图层 ### 递减非张量核心功 张量核不会加速深度神经网络中的许多其他操作,理解它们对整体加速的影响至关重要。 考虑一个模型,其中一半的训练时间花在张量核加速运算上。如果张量核将这些运算的速度提高了 5 倍,那么总的加速是 1 美元。/ (0.5 + (0.5 / 5.))= 1.67x 美元 因为张量核操作在整个工作中所占的百分比越来越小,所以优化非张量核过程变得更加重要。定制 CUDA 实现和框架集成可以用来加速这些过程。编译器工具可以自动加速非张量核运算。 ## 需要记住的要点 使用深度神经网络(DNNs)已经在各个领域取得了重大进展。随着 DNN 复杂度的提高,研究人员和用户需要越来越多的计算机资源来训练。在混合精度训练中使用较低精度的算法减少了所需的资源量,这具有以下优点: * **减少所需的内存量**:单精度(FP32)使用 32 位,而半精度(FP16)使用 16 位(FP16)。可以减少存储器需求,以便可以训练更广泛的模型或者可以训练更大的小批量。 * **减少训练或推理的持续时间**:内存或算术带宽会影响执行时间。半精度通过将访问的字节数减半,减少了在内存有限的层上花费的时间。 [文档](https://developer.nvidia.com/blog/mixed-precision-training-deep-neural-networks/)强调了成功训练半精度 DNNs 的三种方法: ### 累加到 FP32 * NVIDIA Volta GPU 架构中引入的张量核心指令乘以半精度矩阵,将结果聚合为单精度或半精度输出 * 为了获得有效的训练结果,积累成单精度是必不可少的。在写入内存之前,累加值被转换为半精度 ### 损耗缩放 * 激活、激活梯度、权重和权重梯度是在训练 DNNs 时遇到的四种张量 * 激活、权重和权重梯度落在可以用半精度表示的幅度范围内。对于某些网络,小幅度激活梯度低于半精度范围 * 保证梯度落在可以由半精度表示的范围内的技术是将训练损失乘以比例因子 * 链式法则通过增加一次乘法运算来确保所有的梯度在没有额外成本的情况下被放大 * 丢失为零的梯度值可以通过丢失缩放来恢复。在权重更新之前,权重梯度必须按相同的因子 S 按比例缩小 > 缩小操作可以与权重更新本身融合,或者单独执行。 ### FP32 重量的主副本 * 通过添加适当的权重梯度,网络权重随着 DNN 训练的每次迭代而更新 * 权重梯度幅度有时远小于相应的权重,特别是当乘以学习率时 * 如果加数中的一个在半精度表示中足够小以至于产生显著差异,则更新可能不会发生 * 对于以这种方式丢失更新的网络来说,维护和更新具有单精度的权重的主副本是一种简单的修复方法 > 在每次迭代中,制作主权重的半精度副本,并在前向和后向传播中使用,从而获得性能优势。 * 在权重更新期间计算的权重梯度被转换为单精度并用于更新主副本,并且该过程在接下来的迭代中重复。因此,我们只在必要时混合使用半精度和单精度存储 ### 自动混合精度:TensorFlow 中的 NVIDIA 张量核心架构 NVIDIA 在 2019 年 GTC 上推出了自动混合精度(AMP)功能,该功能允许自动混合精度训练。使用自动混合精度可以提高性能,但这取决于模型的架构。在当前 TensorFlow 训练脚本中,设置一个环境变量或进行一些代码更改即可实现自动混合精度。 使用半精度浮点,混合精度训练速度更快,同时保持与使用相同超参数的单精度训练相同的精度水*..如下所示,NVIDIA Volta 和 Turing GPU 架构中引入的张量核可以在半精度下使用。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/fd1aa8a1fa40353a1f1bb909c3dce23d.png) The FP16 and INT8 matrix computations are significantly sped up by tensor cores.  尽管使用 vanilla TensorFlow 可以实现混合精度,但它需要开发人员对模型和优化器进行手动调整。与手动过程相比,使用 TensorFlow 的自动混合精度有两个优势。首先,减少了开发和维护工作量,因为网络模型代码不需要程序员修改。其次,AMP 确保了与所有 TensorFlow 模型定义和执行 API 的向前和向后兼容性。 NVIDIA NGC TensorFlow 19.03 容器(以及此后的每个版本)具有自动混合精度功能。要使用此功能,您只需设置一个环境变量: ```py export TF_ENABLE_AUTO_MIXED_PRECISION=1 ``` 或者,可以在 TensorFlow 的 Python 脚本中指定环境变量: ```py os.environ['TF_ENABLE_AUTO_MIXED_PRECISION']='1' ``` 通过**启用** [**TensorFlow XLA 编译器**](https://www.tensorflow.org/xla) **并增加迷你批处理大小,有可能进一步加速。** ### 结论 在本文中,我们有: * 仔细看看混合精度训练作为一种技术。 * 了解如何使用缩放来防止精度损失。 * 介绍什么是张量核:它们是什么,它们如何工作,如何优化它们。 * 了解如何在 TensorFlow 中为 NVIDIA 张量核心架构使用自动混合精度 ### 参考 [https://developer . NVIDIA . com/blog/programming-tensor-cores-cuda-9/](https://developer.nvidia.com/blog/programming-tensor-cores-cuda-9/)[https://arxiv.org/abs/1903.03640](https://arxiv.org/abs/1903.03640) [https://developer . NVIDIA . com/blog/mixed-precision-training-deep-neural-networks/](https://developer.nvidia.com/blog/mixed-precision-training-deep-neural-networks/) [https://developer . NVIDIA . com/blog/NVIDIA-automatic-mixed-precision-tensor flow/](https://developer.nvidia.com/blog/nvidia-automatic-mixed-precision-tensorflow/) [http://alexminnaar . com/2000](http://alexminnaar.com/2020/05/02/dl-gpu-perf-mixed-precision-training.html)[https://arxiv.org/pdf/1710.03740.pdf](https://arxiv.org/pdf/1710.03740.pdf) [https://arxiv.org/pdf/1710.03740.pdf](https://arxiv.org/pdf/1710.03740.pdf) T21【https://docs . NVIDIA . com/deep learning/performance/mixed-precision-training/index . html # tensor-core-shape # 使用带梯度的混合精确训练 > 原文:<https://blog.paperspace.com/mixed-precision-training/> 深度学习算法和框架的优化不仅是库和技术的问题,也是 CPU 和 GPU 硬件的问题。例如,数据如何以计算机数字格式存储,会对深度学习算法的训练速度产生重大影响。在使用 GPU 加速模型训练时,了解这些信息尤其有用。 在这篇博文中,我们将介绍两种用于深度学习的常见计算机数字格式(FP16 和 FP32),并详细说明单独使用每种格式的问题。接下来,我们将跳转到混合精确训练的好处及其在深度学习中的使用。之后,我们将分解哪些 NVIDIA GPUs 能够进行自动混合精度训练,包括在各种 GPU 上实施混合精度训练时,您可能会期望什么样的加速,如何确保无论您运行的是 PyTorch 还是 TensorFlow,混合精度都是启用的,以及如果您运行的是 NVIDIA RTX5000、V100 或 A100 等强大的 GPU,为什么混合精度特别有价值。 ## 什么是计算机数字格式,它们如何影响深度学习? 计算机数字格式是数字设备硬件和软件中数值的内部表示。任务中使用的代码和应用程序可以对这些表示进行操作。 如计算机数字格式国际标准 IEEE 754 中所述,浮点精度有各种级别。范围从 FP16 到 FP256,其中“FP”(浮点)后面的数字表示可用于表示浮点值的位数。 [(1)](https://towardsdatascience.com/understanding-mixed-precision-training-4b246679c7c4) 在深度学习任务中,float32,也称为 FP32,是历史上最流行的用于深度学习任务的计算机数字格式。这是因为在 FP32 中,为符号保留了一位,为指数(-126 至+127)保留了 8 位,为数字保留了 23 位。FP16 表示半精度,为符号保留 1 位,为指数(-14 到+14)保留 5 位,为数字保留 10 位。 [(1)](https://towardsdatascience.com/understanding-mixed-precision-training-4b246679c7c4) ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b374e6da65b916ce7cd7cba754f0d62c.png) 使用这两种格式中的任何一种进行深度学习的结果可以从它们的最小和最大表示值以及这些大小对记忆的相应影响中看出。每种格式的最小值和最大值之间的差值在差值的数量级上约为> 10^30。这意味着与 FP16 相比,FP32 可以表示更大范围的值,尽管这是以存储每个单个值需要更多位为代价的。 代表性能力的这些差异的结果在两个地方显示出来:速度和效率。实际上,由于深度学习操作中使用的数字的数值精度更高,使用 FP32 将导致更好的整体模型,但它也将占用更多的内存。尽管成本更高,但更高的精度使 FP32 成为存储深度学习任务数值的*通用语*。 另一方面,FP16 的深度学习占用更少的内存,运行更快,但数据精度更低,从而导致模型效率下降。此外,现代加速器硬件,如 Google TPUs 和 NVIDIA GPUs,在 16 位数据类型中运行操作更快。这是因为它们有专门的硬件来运行 16 位计算,而且从内存中读取 16 位数据类型比 FP32 更快。尽管有加速的好处,但数据精度和模型准确性的损失往往太大,使得 FP16 在除了利基市场之外的所有情况下都可行。 ## 什么是混合精准训练? 在过去,最常用的精确训练范式是使用 FP32 的单一精确训练。这是因为 FP32 模型的整体功效始终较高,尽管这是以增加内存和训练时间要求为代价的。混合精确训练是一种尝试用 FP16 的效率来捕捉 FP32 的功效的方法。 一般来说,混合精度训练为深度学习提供了三个关键好处: * 使用张量核来处理 FP16 加速了数学密集型运算,如线性或卷积层中的运算 * 与单精度相比,访问一半字节可以提高内存受限操作的速度 * 降低训练模型的内存要求,从而能够训练更大的模型或使用更大的迷你批次 [(2)](https://developer.nvidia.com/automatic-mixed-precision) 让我们通过研究范式背后的理论来看看混合精确训练是如何创造这些好处的。 ### 深度学习中的混合精度训练理论 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b7f06994d41f95ba01acb17b6232d3d2.png) 2017 年,英伟达的研究人员[发布了一篇关于混合精确训练的论文](https://arxiv.org/pdf/1710.03740.pdf)。这篇论文详细介绍了在一个称为混合精度训练的过程中使用 FP32 和 FP16 进行深度学习的第一种方法。如上图所示,他们首先创建了存储在 FP32 中的权重的永久副本。然后在正向传递过程中转换为 FP16,供模型用来计算梯度,然后转换为 FP32,并传递回优化器进行反向传播。在迭代结束时,FP32 中的梯度用于在优化器步骤中更新主权重。 该方法本身的问题是仍然存在一些 FP16 没有考虑的小梯度。英伟达团队发现,2^-27 和 2^-24 之间存在不同的价值观,如果使其不可行,将会影响训练。因为它们在 FP16 的限制之外,所以在训练迭代期间它们将等于零。这个问题的解决方案是损耗缩放——在正向传递完成之后、反向传播之前,损耗值乘以一个缩放因子。使用链式法则,所有梯度随后通过相同的因子进行缩放,并随后在 FP16 的可行性范围内移动。 [(1)](https://towardsdatascience.com/understanding-mixed-precision-training-4b246679c7c4) ### 实践中的混合精度训练 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/7bfa4ddd89ba20f1e120af7d544884b9.png) [Source](https://developer.nvidia.com/blog/mixed-precision-training-of-deep-neural-networks/) 使用混合精度训练的效果可以在上面的绘图图中看到。它详细描述了使用三种不同精度格式时大 LSTM 英语语言模型的训练曲线:FP32、损失比例为 1 的混合精度和损失比例为 128 的混合精度。Y 轴是训练损失,我们可以使用它来查看无损失缩放的混合精度(灰色)在一段时间后如何发散,而有损失缩放的混合精度(绿色)与单精度模型(黑色)相匹配。 [(3)](https://developer.nvidia.com/blog/mixed-precision-training-deep-neural-networks/) 这清楚地表明,具有损失缩放的混合精度训练可以获得与单精度 FP32 几乎相同的性能。 ## 自动混合精确训练 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/e897755e5e0c710e175b93956e1075d2.png) 在现实世界中,自从这些主题最初被引入以来,已经发生了很大的变化。大多数深度学习库现在都配备了使用自动混合精度(AMP)训练的能力。自动混合精度训练是指混合精度训练在深度学习任务中切换,无需用户进行任何额外设置。值得注意的是,PyTorch 和 TensorFlow 都内置了自动混合训练的触发器。 ### PyTorch 中的自动混合精度训练 要启用 PyTorch AMP,请在代码中添加以下行: ```py scaler = GradScaler() with autocast(): output = model(input) loss = loss_fn(output, target) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() ``` ### TensorFlow 中的自动混合精度训练; 要将 AMP 与 TensorFlow 一起使用,只需将下面的`tf.train`或`tf.keras.optimizers`包起来,将自动损耗缩放应用到自动铸造的一半精度: ```py opt = tf.train.experimental.enable_mixed_precision_graph_rewrite(opt) ``` 有关自动混合精确训练的更多信息,请查看 NVIDIA 的官方资源[。](https://developer.nvidia.com/automatic-mixed-precision) ## 什么时候应该在渐变笔记本上使用混合精度训练? 到目前为止,我们已经讨论了什么是混合精度训练,展示了其机制背后的理论,并讨论了如何在最流行的深度学习库中自动使用它。因为混合精度训练能够加速深度学习任务的一般过程,所以我们已经可以推断,只要有可能,就应该实施混合精度训练。但这并不总是可能的——尤其是在自动混合精确训练的背景下。在本节中,我们将讨论何时何地在梯度笔记本上使用混合精度训练。 💡Not all NVIDIA GPUs can be used with mixed precision training. GPUs with a compute capability of 7.0 and higher see the greatest performance benefit from using AMP. 自动混合精度训练在哪里什么时候可以用在梯度上的问题在于硬件。并非所有的 NVIDIA GPUs 都可以用于混合精度训练。计算能力为 7.0 及更高的 GPU 可以从使用 AMP 中获得最大的性能优势。较新的 GPU 具有张量内核,可以加速 FP16 矩阵乘法和卷积,而较旧的 GPU 可能仍然会经历与内存和带宽相关的加速。由于张量内核,RTX5000、V100 和 A100 等 GPU 将从自动混合精度中受益最多。如果您正在使用这些机器,您应该始终启用自动混合精度训练。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/29c747a3286d1f7c3fd803d796c71261.png) "Performance of mixed precision training on NVIDIA 8xV100 vs. FP32 training on 8xV100 GPU. Bars represent the speedup factor of V100 AMP over V100 FP32\. The higher the better." [(Source)](https://pytorch.org/blog/accelerating-training-on-nvidia-gpus-with-pytorch-automatic-mixed-precision/) 上图来自 PyTorch 官方博客[关于 AMP with Torch 的帖子。](https://pytorch.org/blog/accelerating-training-on-nvidia-gpus-with-pytorch-automatic-mixed-precision/)他们发现,在 8 x V100 多 GPU 机器上进行训练时,与单一精确训练方法相比,像 BERT large 这样的特别大的模型在训练时间上可以实现* 6 倍的改进。我们还可以看到,与 FP32 训练相比,自动混合精度训练可以将训练时间减少*一半。 在下一个例子中,他们详细介绍了 V100 和 A100 之间的加速系数比较。在某些背景下,A100 被广泛认为是执行生产级深度学习任务的首选机器。V100 在许多方面都是深度学习社区的前辈。V100 本身就是一台强大的机器,但它的设计和制造比 A100 更老。因此,通过比较这两种 GPU 使用自动混合精度训练和单精度训练之间的相对加速,我们可以看到微体系结构在使用 AMP 训练时对加速训练的影响有多大。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/f6a394a68f7bd9a5c68e9a2a24e52e44.png) "Performance of mixed precision training on NVIDIA 8xA100 vs. 8xV100 GPU. Bars represent the speedup factor of A100 over V100\. The higher the better." [Source](https://pytorch.org/blog/accelerating-training-on-nvidia-gpus-with-pytorch-automatic-mixed-precision/) 正如我们所看到的,A100 的加速效果比 V100 的加速效果大 150%到 250%。这再次表明,不仅 A100 对于深度学习任务的优越性,而且微体系结构的进步和张量核心技术的相应升级如何影响训练时间。 使用自动混合精度训练是我们应该在任何 RTX、特斯拉和安培 GPU 上一直做的事情,但好处将在最大模型上最强大的 GPU 上最明显。 ## 总结想法 从上面收集的信息中,我们可以看到,只要有可能,就应该实施混合精度训练。在加速方面的优势非常高,而在模型效率方面的缺点非常少。此外,无论何时使用 RTX、特斯拉或安培 GPU,Gradient 的用户都应该应用自动混合精度训练。这些 GPU 具有张量内核,可以加速 FP16 的计算。 有关计算机数字格式、混合精度训练以及使用 PyTorch 和 TensorFlow 进行 AMP 训练的更多信息,请阅读以下链接: * [https://developer.nvidia.com/automatic-mixed-precision](https://developer.nvidia.com/automatic-mixed-precision) * [https://towards data science . com/understanding-mixed-precision-training-4b 246679 c7c 4](https://towardsdatascience.com/understanding-mixed-precision-training-4b246679c7c4) * [https://developer . NVIDIA . com/blog/mixed-precision-training-deep-neural-networks/](https://developer.nvidia.com/blog/mixed-precision-training-deep-neural-networks/) * [https://py torch . org/blog/accelerating-training-on-NVIDIA-GPU-with-py torch-automatic-mixed-precision/](https://pytorch.org/blog/accelerating-training-on-nvidia-gpus-with-pytorch-automatic-mixed-precision/) # NVIDIA Volta 的混合精确训练 > 原文:<https://blog.paperspace.com/mixed-precision/> [Volta](https://en.wikipedia.org/wiki/Volta_(microarchitecture)) 是 NVIDIA 最新开发的 GPU 架构之一。Volta 是专门为训练深度神经网络和机器学习研究而设计和优化的。这种新架构的一个主要特点是它包括[张量核](https://www.nvidia.com/en-us/data-center/tensorcore/),一个*可编程矩阵乘法和累加单元,可以为训练和推理应用提供多达 125 个张量浮点运算* [1]。基本上,一个新的和更有效的方法来增加矩阵操作的计算时间。这种优化可以在训练深度神经网络时获得更好的性能,并提高训练速度。(如果你想深入了解张量核如何工作的技术定义,请查看关于该主题的 NVIDIA 博客文章。) ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/72e50cb5cd2dfd95c4d13481e5d64d3b.png) Tensor Core visualization. Source https://www.nvidia.com/en-us/data-center/tensorcore/ 这种新架构设计的一大优势是,它还使用并受益于较低精度的算法来提高计算速度。这是通过使用半精度浮点数而不是单精度浮点数来实现的。 ## 半精度与单精度 训练和开发深度神经网络模型的很大一部分(如果不是大部分)必须计算网络的权重、激活函数、输入和输出。当使用 GPU 加速计算时,这由 CUDA 在较低的级别进行管理和计算。CUDA 通常使用 32 位(FP32)内存存储和跟踪单精度浮点数训练中涉及的所有变量。使用 FP32 意味着执行算术计算的精度更高,但也意味着需要更多的内存来存储所有这些变量。相比之下,半精度浮点数仅使用 16 位(FP16)存储,因此比 FP32 使用更少内存。缺点是精度和 FP32 不一样。这意味着在训练网络时,您可能会得到不精确的权重更新、梯度下溢、缩减上溢。 那么,我们如何利用 Volta 的架构优势和使用 16 位浮点数的能力,加快训练速度,但又不损失精度呢?答案与 [**混合精密训练**](https://devblogs.nvidia.com/mixed-precision-training-deep-neural-networks/) **有关。**混合精度训练是使用 FP16 和 FP32 的组合来执行一些计算和操作的一种方式。例如,张量核可以将多个半精度矩阵累加成一个单精度矩阵。使用混合 precision 和 Volta,网络可以: * 比使用单精度快 2-4 倍 * 将网络规模减半 * 并不意味着模型的架构改变。 ## 在 Pytorch 中使用混合精度训练 正如[NVIDIA 官方文档](https://docs.nvidia.com/deeplearning/sdk/mixed-precision-training/index.html)中所述,在 Pytorch 中使用混合精度仅涉及将必要的变量和模型转换成一半: ```py model = model.cuda().half() input = input.cuda().half() ``` 请记住,这种选角只在训练循环的某些部分进行。在最好的情况下,您将有一个 FP16 模型和最终重量,但是训练和计算将使用 FP32 和 FP16 的混合来完成。为了深入解释如何在 Pytorch 中使用 FP16, [Sylvain Gugger](https://sgugger.github.io/) 写了一篇精彩的介绍,你可以在这里找到[。](http://forums.fast.ai/t/mixed-precision-training/20720) ## 参考 * [1]CUDA 9 中的编程张量核-[https://dev blogs . NVIDIA . com/Programming-Tensor-Cores-CUDA-9/](https://devblogs.nvidia.com/programming-tensor-cores-cuda-9/) * [2]英伟达 GPU 技术大会-[http://on-demand.gputechconf.com/gtc/2018/video/S81012/](http://on-demand.gputechconf.com/gtc/2018/video/S81012/) * [3] Fast.ai:混合精度训练岗位-[http://forums.fast.ai/t/mixed-precision-training/20720](http://forums.fast.ai/t/mixed-precision-training/20720) # 机器学习模型的评估标准:第 1 部分 > 原文:<https://blog.paperspace.com/ml-evaluation-metrics-part-1/> 如果训练模型是机器学习的一个重要方面,那么评估它们就是另一个方面。**评估**衡量模型在看不见数据的情况下表现如何。这是决定一个模型是否令人满意的关键指标之一。 通过评估,我们可以确定模型是否会在所学知识的基础上构建知识,并将其应用于以前从未处理过的数据。 以一个使用决策树算法构建 ML 模型的场景为例。你获取数据,初始化超参数,训练模型,最后,评估它。根据评估结果,您得出结论,决策树算法没有您想象的那么好。所以你向前迈一步,应用随机森林算法。*结果*:评估结果看起来令人满意。 对于复杂的模型,不像在前面的例子中执行一次评估,评估可能需要多次进行,无论是使用不同的数据集还是算法,以决定哪个模型最适合您的需求。 在本系列的第一部分中,让我们了解可以用来评估 ML 模型的各种回归和分类评估指标。 如果您想了解这些指标如何在实际中使用,[请查看我们的笔记本,在 Gradient](https://console.paperspace.com/ml-showcase/notebook/rrtvxnh5j1vfj4e?file=guide.ipynb) 中以代码形式演示了这些指标! ## 回归的评估指标 回归是一种输出连续值的 ML 技术。例如,您可能希望预测下一年的燃料价格。您建立一个模型,并用过去几年观察到的燃料价格数据集对其进行训练。要评估您的模型,您可以使用以下一些技巧: ### 均方根误差 **均方根偏差** ( **RMSD** )或**均方根误差** ( **RMSE** )用于测量模型预测值与观察值(实际值)之间的差异。它有助于确定观察到的与实际结果的偏差。计算方法如下: \[RMSE =-\] 其中,\( \hat{y_1},\hat{y_2},...,\hat{y_n} \)是预测值,\( y_1,y_2,...,y_n \)为观测值,\( n \)为预测数。 \( (\hat{y_i} - y_i)^2 \)类似于我们用来计算两点间距离的欧几里德距离公式;在我们的例子中,预测和观察的数据点。 除以\( n \)允许我们估计单个预测的误差的标准偏差\( \sigma \)(与观察值的偏差),而不是某种“总误差” [1](https://towardsdatascience.com/what-does-rmse-really-mean-806b65f2e48e) 。 #### RMSE vs. MSE * 均方误差(MSE)是没有*方根的 RMSE * 因为 MSE 是预测误差的*方,所以它对异常值很敏感,如果出现异常值,它会输出一个非常高的值 * RMSE 优于 MSE,因为 MSE 给出了一个*方误差,不像 RMSE,它与输出的单位相同 #### 什么是理想的 RMSE? 首先,该值应该很小,这表明模型更适合数据集。有多小? 对 RMSE 来说没有理想的价值。这取决于您正在处理的数据集值的范围。如果值的范围是从 0 到 10,000,那么 RMSE(比如 5.9)就很小,模型可以被认为是令人满意的,而如果范围是从 0 到 10,RMSE 5.9 就很差,模型可能需要调整。 ### 绝对*均误差 ***均绝对误差** ( **MAE** )是预测误差绝对值的*均值。我们使用绝对,因为不这样做,正负误差就会抵消;相反,我们使用 MAE 来找出误差的总体大小。预测误差是观察值和预测值之间的差异。 \[ MAE = \frac{1}{n} \sum_{i=1}^{n} |\hat{y_i} - y_i| \] 其中\( \hat{y_i} \)是预测值,\( y_i \)是观测值。 #### RMSE 对梅 * MAE 是一种线性得分,其中所有预测误差的权重相等,这与 RMSE 不同,后者对预测误差进行*方,然后对*均值应用*方根 * 一般来说,RMSE 的分数总是高于或等于梅 * 如果离群值不意味着被严重惩罚,MAE 是一个好的选择 * 如果不希望出现较大的误差,RMSE 就很有用,因为它对较大的误差给予相对较高的权重 ### 均方根对数误差 当*方根应用于预测值和观察值之间的对数差的*方的*均值时,我们得到**均方根对数误差** ( **RMSLE** )。 \[rmsle = \ sqrt { \ frac { 1 } { n } \sum_{i=1}^{n}(log(\ hat { y _ I }+1)-log(y _ I+1))^2} \] 其中\( \hat{y_i} \)是预测值,\( y_i \)是观测值。 #### 选择 RMSLE vs. RMSE * 如果不惩罚异常值,RMSLE 是一个很好的选择,因为 RMSE 可以将异常值分解为一个高值 * RMSLE 计算相对误差,误差的大小并不重要 * RMLSE 因低估实际值 [2](https://medium.com/analytics-vidhya/root-mean-square-log-error-rmse-vs-rmlse-935c6cc1802a) 而招致重罚 ## **R 的*方** r *方(也称为**决定系数**)代表回归模型的拟合优度。它给出了自变量共同解释的目标变量(因变量)中方差的比例。 R-squared 评估最佳拟合线周围的数据点散布。它可以表述为: \[r^2 = \ frac { var(mean)-var(line)} { var(mean)} \] 其中\( var(mean) \)是相对于*均值的方差,var(line)是关于最佳拟合线的方差。\( R^2 \)值有助于将\( var(mean) \)与\( var(line) \)相关联。如果\( R^2 \)是,比如说,0.83,这意味着线周围的变化比均值少 83%,即自变量和目标变量之间的关系占变化 [3](https://towardsdatascience.com/statistics-for-machine-learning-r-squared-explained-425ddfebf667) 的 83%。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/e16ca83245e56ea914c089868f88a8a0.png) What are var(mean) and var(line)? ([reference](https://towardsdatascience.com/statistics-for-machine-learning-r-squared-explained-425ddfebf667)) R *方值越高,模型越好。0%意味着模型不解释目标变量在其均值附*的任何变化,而 100%意味着模型解释目标变量在其均值附*的所有变化。 #### RMSE 与 R 的*方 最好同时计算这两种度量,因为 RMSE 计算预测值和观测值之间的距离,而 R *方则说明预测变量(数据属性)能够在多大程度上解释目标变量的变化。 #### 限制 R-squared 并不总是提供准确的值来判断模型是否良好。例如,如果模型有偏差,R *方可能相当高,这并不反映有偏差的数据。因此,对于上下文 [4](https://www.mygreatlearning.com/blog/r-square/) ,建议使用 R *方与其他统计数据和残差图。 ## 分类的评估标准 分类是一种识别给定数据集的类别标签的技术。考虑这样一个场景,您想要将一辆汽车分类为优秀/良好/糟糕。然后,您可以在包含感兴趣的汽车和其他类别车辆的信息的数据集上训练模型,并使用分类度量来评估模型的性能,从而验证模型的有效性。要在测试/验证数据集上分析分类模型的可信度,可以使用以下技术: ## **精度** **准确度**是测试数据正确预测的百分比。其计算方法如下: \[准确性= \ frac {正确\,预测} {全部\,预测} \] 在分类中,我们通常用来计算指标的元指标有: * 真阳性(TP):预测属于一个类,观察也属于一个类 * 真否定(TN):预测不属于一个类别,观察不属于那个类别 * 假阳性(FP):预测属于一个类,但是观察不属于那个类 * 假阴性(FN):预测不属于一类,但观察属于一类 所有元指标都可以排列成矩阵,如下所示: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/45fc5eb4f7a9b9bde18340fe221edbb9.png) Confusion matrix for binary classification 这被称为**混淆矩阵**。它给出了算法性能的可视化表示。 对于多类分类,我们会有更多的行和列,表示数据集的类。 现在,让我们使用元指标来修改我们的准确度公式。 \[accuracy = \ frac { TP+TN } { TP+TN+FP+FN } \] 使用\( TP + TN \)是因为\( TP + TN \)一起表示正确的预测。 #### 限制 准确性可能并不总是一个好的性能指标。例如,在癌症检测的情况下,未能诊断出癌症的成本远远高于在一个没有癌症的人身上诊断出癌症的成本。如果这里说准确率是 90%,我们就漏掉了另外 10%可能患有癌症的人。 总的来说,准确性取决于所考虑的问题。除了准确性,您可能还想考虑其他分类指标来评估您的模型。 ### 精确 **精度**定义为预测属于某一类的所有数据点中正确分类样本的比例。一般来说,它翻译成“*我们对某一类的预测有多少是正确的*”。 \[ precision = \frac{TP}{TP + FP} \] 为了理解精度,让我们再次考虑癌症检测问题。如果“患有癌症”是一个积极的类别,元指标如下: \( TP \) = *预测*:患癌,*实际(观察)*:患癌 \( FP \) = *预测*:患癌,*实际*:未患癌 因此,\( \frac{TP}{TP + FP} \)给出了我们的预测进展如何的度量,即,它度量了有多少被诊断患有癌症的人患有癌症,并有助于确保我们不会将没有患癌症的人误分类为患有癌症。 ### 回忆 **召回**(也称为**敏感度**)定义为预测属于某一类的样本在实际属于某一类的所有数据点中所占的比例。一般来说,它翻译成“*有多少属于一个类的数据点被正确分类*”。 \[ recall = \frac{TP}{TP + FN} \] 考虑到癌症检测问题,如果“患有癌症”是阳性类别,则元度量如下: \( TP \): *预测*:患癌,*实际(观察)*:患癌 \( FN \): *预测*:没有癌症,*实际*:有癌症 因此,\( \frac{TP}{TP + FN} \)衡量一个类别的分类工作做得如何,也就是说,它衡量有多少癌症患者被诊断为癌症,并有助于确保癌症不会被检测出来。 ### f-测度 由于精确度和召回率捕获模型的不同属性,所以有时一起计算这两个指标是有益的。在两个指标都需要考虑的情况下,我们用一个指标来衡量精度和召回率怎么样? **F-Measure** (也叫 **F1-Measure** )前来救援。这是精确和回忆的调和*均值。 \[F-Measure = \ frac { 2 } { 1 } { recall }+\ frac { 1 } { precision } } = 2 * \ frac { precision * recall } { precision+recall } \] 广义 F-测度是\( F_\beta \),它被给出为: \[f _ \ beta =(1+\ beta ^2)* \ frac { precision * recall}{(\beta^2 * precision)+recall } \] 其中\( \beta \)的选择使得召回被认为是精度 [5](https://en.wikipedia.org/wiki/F-score) 的\( \beta \)倍重要。 ### 特征 **特异性**定义为预测不属于某一类别的样本在实际不属于某一类别的所有数据点中所占的比例。一般来说,翻译成“*有多少不属于某一类的数据点被正确分类*”。 \[specification = \ frac { TN } { TN+FP } \] 考虑到癌症检测问题,如果“患有癌症”是阳性类别,则元度量如下: \( TN \): *预测*:没有癌症,*实际(观察)*:没有癌症 \( FP \): *预测*:患癌,*实际*:未患癌 因此,\( \frac{TN}{TN + FP} \)衡量分类对于一个类别的完成程度,即,它衡量有多少未患癌症的人被预测为未患癌症。 ### 皇家对空观察队 **受试者工作特性** ( **ROC** )曲线绘制召回率(真阳性率)和假阳性率。 \[ TPR = \frac{TP}{TP + FN} \] \[ FPR = \frac{FP}{FP + TN} \] ROC 下面积( **AUC** )测量曲线下的整个面积。AUC 越高,分类模型越好。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/39f0619d105fe2fbcdd79287ea3b891c.png) AUC ([Source](https://developers.google.com/machine-learning/crash-course/classification/roc-and-auc)) 例如,如果 AUC 为 0.8,这意味着模型有 80%的机会能够区分阳性和阴性类别。 ### 一对 **精度-召回** ( **PR** )曲线描绘了精度和召回。当涉及到不*衡分类时,PR 曲线可能是有帮助的。生成的曲线可能属于以下任何一层: * 高精度和高召回率:PR 曲线下较高的区域表示高精度和高召回率,这意味着模型通过生成准确的预测表现良好(*精度:模型检测到癌症;确实患有癌症*)并正确分类属于某一类别的样本(*回忆:患有癌症,准确诊断癌症*)。 * 高精度和低召回率:模型检测到癌症,并且该人确实患有癌症;然而,它遗漏了许多实际的“患癌”样本。 * 低精度高召回:模型给出了很多“得了癌症”和“没得癌症”的预测。它把很多“患癌”类样本认为是“没患癌”;然而,当“没有患癌症”是合适的类别时,它也将许多“患有癌症”错误分类。 * 低精度和低召回率:既不能正确分类,也不能正确预测属于特定类别的样本。 #### ROC 与 PR * 当数据集不*衡时,可以使用 ROC 曲线,因为尽管数据集不*衡,但 ROC 对模型性能的描述过于乐观 * 当数据集不*衡时,应使用 PR 曲线 * * * 在本文中,您了解了几个可以评估回归和分类模型的评估指标。 这里需要注意的是,并不是每个评估指标都可以独立依赖;为了判断一个模型是否表现良好,我们可以考虑一组指标。 除了评估指标之外,检查 ML 模型是否可接受的其他方法还包括: * 将您的模型与其他类似模型的分数进行比较,并验证您的模型的性能是否与它们相当 * 留意*偏差*和*方差*,以验证一个模型是否概括得很好 * 交叉验证 在下一部分中,让我们深入研究聚类和排序模型的评估指标。 详细介绍上述内容的笔记本可以在 Github 上找到。 # 机器学习模型的评估标准 > 原文:<https://blog.paperspace.com/ml-evaluation-metrics-part-2/> 本系列的第一部分介绍了回归和分类评估指标。在第二部分中,让我们试着理解聚类和排名评估指标。 ## 聚类的评估指标 为了找到没有相关类标签的数据点之间的相似性,可以使用聚类。它将数据点分成多个聚类,使得同一聚类内的数据点比其他聚类内的数据点彼此更相似。 聚类作为一种无监督的学习技术,本身并没有提供一种合理的方法来确定模型的准确性。为了改善这个问题,已经开发了几种方法。这些技术包括 [1](https://en.wikipedia.org/wiki/Cluster_analysis#Evaluation_and_assessment) : * ******内部评估:****** 内部评估基于被聚类的数据,包括计算类间和类内距离。如果聚类间点之间具有高相似性,而聚类内点之间具有低相似性,则将最佳得分分配给模型。 * ******外部评估:****** 外部评估基于未用于聚类的数据,可包括外部基准。 * ******人工评估:****** 人工评估是由人类专家完成的。 现在让我们来看看一些内部和外部评估指标。对于下面不同评估指标的工作示例,您可以通过分叉在这个[梯度笔记本](https://console.paperspace.com/ml-showcase/notebook/rgn15he0c4ycx71?file=Untitled.ipynb)中跟随它,或者直接在 Github 上查看它。 ### 轮廓系数 使用***均类内距离**和***均类间距离**为每个数据点计算**轮廓系数** ( *内部评估技术*)。 \[ silhouette\,coefficient = \frac{b-a}{max(a,b)} \] \( a \) =当前数据点与同一聚类中所有其他数据点之间的*均距离。 \( b \) =当前数据点与下一个最*聚类中所有其他数据点之间的*均距离。 轮廓系数在-1 到 1 之间变化,其中-1 表示数据点未分配给正确的聚类,0 表示聚类重叠,1 表示聚类密集且分离良好(因此这是理想值)。 *值越接* 1,聚类方法越好。* ### 邓恩指数 **邓恩指数** ( *内部评估技术*)有助于识别紧密且集群成员之间差异较小的集群集合。 \[ Dunn\,Index = \ frac { \ under set { 1 \ leq I \ lt j \ leq c } { min } \ Delta(x _ I,y _ j)} { \ under set { 1 \ leq k \ leq c } { max } \ Delta(x _ k)} \] * \( \delta(x_i,y_j) \)是簇间距离,即\( x_i \)和\( x_j \)之间的距离。在内部,它涉及计算两个集群中每个数据点之间的距离。我们将不得不使用这些距离中的最小值作为簇间间隔。 * \( \Delta_k \)是 cluster \( x_k \)的簇内距离,即簇\( x_k \)内的距离,这涉及计算每个数据点到同一簇中每个其他数据点之间的距离。我们将不得不考虑这些距离的最大值作为组内间隔。 *邓恩指数越高,聚类模型越好。* ### 戴维斯-波尔丁指数 **戴维斯-波尔丁指数** ( *内部评估技术* ) 可计算如下: \[戴维斯-波尔丁\,index = \ frac { 1 } { c } \sum_{i=1}^{c} \ underset { j \ neq I } { max } \ frac { \ sigma _ I+\ sigma _ j } { d(c _ I,c_j)} \] 其中\( c \)是簇的数目,\( c_i \)是簇\( i \)的质心,\( d(c_i,c_j) \)是两个簇的质心之间的距离,\( \sigma_i \)是簇\( i \)中所有数据点到\( c_i \)的*均距离。 提供低集群内距离和高集群间距离的模型(理想指标!)输出低的戴维斯-波尔丁指数。 因此,戴维斯-波尔丁指数越低,模型越好。 ### 卡林斯基-哈拉巴斯指数 **卡林斯基-哈拉巴斯指数**(也称**方差比标准** ) ( *内部评估技术*)是所有聚类的类间离差与类内离差之比。 \[CHI = \ frac { trace(B _ c)} { trace(W _ c)} * \ frac { n _ E-c } { c-1 } \] 在哪里, \( c \)是簇的数量, \( n_E \)是数据集\( E \)的大小, \( trace(B_c) \)是簇间(簇间)色散矩阵的迹, 而\( trace(W_c) \)是群内(intra-cluster)色散矩阵的迹。 \( W_c \)和\( B_c \)的定义如下: \[w _ c = \sum_{i=1}^{c} \sum_{j=1}^{c_i}(x _ j-c _ I)(x _ j-c_i)^t \] \[b _ c = \sum_{i=1}^{c}·尼(c_i - c_E)(c_i - c_E)^T\] 其中\( c \)是聚类数,\( C_i \)是聚类\( i\)中的数据点集,\( x_j \)是数据点,\( c_i \)是聚类\( i \)的质心,\( n_i \)是聚类\( i \)中的点数,\( c_E \)是数据\( E \)的全局质心。 因此,CHI 是基于聚类内的数据点到聚类质心的距离以及所有聚类质心到全局质心的距离来估计的。 *CHI 值越高,聚类模型越好。* ### 纯洁 **纯度** ( *外部评估技术*)评估一个聚类属于一个类的程度。它包括将一个聚类分配到该聚类中最频繁的类别,然后计算每个聚类中正确分配的数据点的数量,对所有聚类求和,然后将该值除以数据点的总数。 \[Purity = \ frac { 1 } { N } \ sum _ { C \ in C } \ under set { D \ in D } { max } | C \ cap D | \] 其中\( C \)表示聚类,\( D \)表示类,\( N \)表示数据点的总数。 纯度为 1 表示好的聚类,纯度为 0 表示差的聚类。簇的数量越多,越容易具有高纯度值(考虑每个数据点都在自己的簇中的情况;它给出的纯度为 1!). #### 限制 对于不*衡的数据,Purity 真的不太好用。此外,纯度不能用于在聚类质量与聚类数量之间进行权衡,因为存在更多聚类[2](https://nlp.stanford.edu/IR-book/html/htmledition/evaluation-of-clustering-1.html)时纯度较高。 ### 归一化互信息 **归一化互信息** ( **NMI** ) ( *外部评估技术*)是互信息(MI)分数的归一化,以在 0(无互信息)和 1(完全相关)之间缩放结果 [⁷](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.normalized_mutual_info_score.html) 。其给出如下: \[ NMI(\Omega,C)= \ frac { I(\ Omega;C)} { \ frac { H(\ Omega)+H(C)} { 2 } } \] \(I(\ Omega;C) \)代表互信息。它测量当我们被告知集群 [3](https://nlp.stanford.edu/IR-book/html/htmledition/evaluation-of-clustering-1.html) 时,我们对该类的知识增加的信息量。 \(I(\ Omega;C)=0 \)表示我们不知道 a 类簇映射到哪个类。 \(I(\ Omega;C)=1 \)表示每个聚类都映射到一个类。 属于其自己聚类的每个数据点具有更高的\(I(\ω;C) \)(比如在纯度上)。为了达到前面在*纯度*中提到的折衷,\( \frac{H(\Omega) + H(C)}{2} \)是有帮助的。\( H \)(熵)随着簇数的增加而增加,从而保持\(I(\ω;C) \)在检查中。 ### 兰德指数 **Rand 指数** ( **RI** ) ( *外部评估技术*)衡量所有预测中正确预测的百分比。 \[RI = \ frac { TP+TN } { TP+TN+FP+FN } \] 根据基准分类计算度量,即\( TP \)是在预测分区和真实分区 [4](https://en.wikipedia.org/wiki/Cluster_analysis#Evaluation_and_assessment) 中聚类在一起的点对的数量,等等。 Rand 指数的范围从 0 到 1,其中 *1 表示一个好的聚类模型*。 #### 限制 rand 指数同等地衡量假阳性(FP)和假阴性(FN ),这对于一些聚类过程可能是不期望的特性。为了解决这个问题,可以使用 **F-Measure** 。 ### 调整后的兰德指数 **调整后的 Rand 指数** ( **ARI** ) ( *外评技术*)是 RI [5](https://en.wikipedia.org/wiki/Rand_index#Adjusted_Rand_index) 的机会修正版。使用以下公式给出: \[ ARI = \frac{RI - Expected\,RI}{Max(RI) - Expected\,RI} \] ARI 通过使用聚类之间的预期相似性来建立基线。与 RI 不同,如果\( RI \)小于\( Expected\,RI \),它会产生负值。 ### Jaccard 索引 **Jaccard 指数** ( *外部评估技术*)衡量真阳性(TP)相对于 TP、FP 和 FN 的数量。 \[ JI = \frac{TP}{TP + FP + FN} \] 假设这种过分简单的方法是查看正确预测与正确预测+所有错误预测的比率,则比率越高,表明两个分类中的值之间的相似性越大。Jaccard 指数越高,聚类模型越好。 ### 限制 Jaccard 指数能够测量两个组之间的相似性,但是容易误解两个组之间的关系。例如,一个集群可以存在于另一个集群中,但仍然可以清晰地划分为两个截然不同的集群。 ## 排名的评估标准 当预测的顺序很重要时,可以使用排名。考虑一个场景,在 Google 搜索期间,需要根据用户给定的查询以特定的顺序检索网页。训练数据通常由一列网页或文档组成,根据这些网页或文档给出相关性;相关性越大,其排名就越高。 如果构建这样的模型,可以使用的一些评估指标如下: ### 精确 **精度**定义为预测属于某一类的所有数据点中正确分类样本的比例。 **Precision@n** 是在进行第 n 次预测之前计算的精度。 \[Precision @ n = \ frac { TP @ n } { TP @ n+FP @ n } \] Precision@n 有助于识别前\( n \)个预测中的相关预测。 #### 限制 Precision@n 不考虑有序预测的位置。 ### *均精度 ***均精度** ( **AP** )告诉我们有多少相关预测出现在排名最高的预测中。可以使用以下算法计算: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d4742d624dc3f233dd68476ada1aeb7e.png) Average Precision Algorithm ([Source](https://queirozf.com/entries/evaluation-metrics-for-ranking-problems-introduction-and-examples)) 就精度而言,其给出为: \[AP = \frac{\sum_{i=1}^{k} p(k)* rel(k)} {第个,共个,相关文档} \] 其中\( P(k) \)是直到\( k)的精度,而\( rel(k) \)是\( k^{th}\)预测的相关性。 ### *均精度 **Mean Average Precision**(**mAP**)是所有例子(网络搜索中的查询)的*均精度的*均值。 \[map = \ frac { 1 } { n } \sum_{i=1}^{n} AP _ I \] \( N \)是查询的次数。 mAP 不仅有助于评估单个示例的得分,也有助于评估一组示例的得分。 ### 贴现累积收益 **贴现累积收益** ( **DCG** )根据预测在结果列表中的位置来衡量预测的有用性。DCG 的前提是,在搜索结果列表中出现在较低位置的高度相关的文档应该受到惩罚,因为分级的相关性值与结果的位置成对数比例地减小[6]。 \[DCG _ k = \ frac { rel _ I } { log _ 2(I+1 } } \] \(k \)是 DCG 累积到的数字,而\( rel_i \)是预测的相关性(网络搜索中的网页/文档)。 ### 标准化贴现累积收益 为了归一化 DCG 值,该值可以根据每次查询的结果数量而变化,可以使用**归一化的折扣累积收益** ( **nDCG** )。nDCG 使用**理想 DCG** ( **IDCG** )来归一化 DCG。IDCG 是最好的 DCG。 \[ndcg _ k = \ frac { DCG _ k } { idcg _ k }] \(k \)是累积 nDCG 之前的数字。 如果排名算法是完美的,nDCG 将是 1,DCG 等于 IDCG。 #### DCG 和 NDCG 限制 DCG 和 NDCG 是最常用的排名评估指标。然而,它们不考虑当前预测之上的预测(高排序的);因此,特定位置的增益始终保持不变。 ### *均倒数等级 ***均倒数排名** ( **MRR** )计算一组查询中出现第一个相关预测的排名倒数的*均值,即,如果对于给定的查询,相关预测位于第一个位置,则相对排名为\( \frac{1}{1} \),对于第二个位置,相对排名为\( \frac{1}{2} \),依此类推。当针对一组查询计算相对排名时,会产生 MRR。 \[mrr = \ frac { 1 } { | n | } \sum_{i=1}^{|n|} \ frac { 1 } { rank _ I } \] 其中\( rank_i \)是相关预测的位置,而\( N \)是查询的数量。 #### 限制 MRR 只考虑第一个相关预测的等级。如果多个相关预测出现在预测的较高位置,这不是一个好的选择。 ### 肯德尔的τ和斯皮尔曼的ρ **肯德尔的 tau** (\( \tau \))和**斯皮尔曼的 rho** (\( \rho \))是确定排序数据之间关系的统计度量(比如,当排序的预测需要与排序的观察进行比较时)。 肯德尔τ由以下公式给出: \[ \tau = \frac{number\,of\,concordant\,pairs - number\,of\,discordant\,pairs}{\binom{n}{2}} \] 其中\( \binom{n}{2} \)等于\(\ frac { n(n-1)} { 2 } \)—从\( n \)个项目中选择两个项目的方法数。 一致对:具有相同顺序的预测对,也就是说,这些预测对具有相同的高低顺序。 不一致的预测对:不在同一顺序的预测对,或者排列在相反方向的预测对。 Spearman 的 Rho 由以下公式给出: \[ r_s = \rho_{R(x),R(y)} = \frac{cov(R(X),R(Y))} { \ sigma _ { R(X)} \ sigma _ { R(Y)} } \] 在哪里, \( \rho \)是皮尔逊相关系数, \( cov(R(X),R(Y)) \)是秩变量(或秩)的协方差, \( \sigma_{R(X)} \)和\( \sigma_{R(Y)} \)是秩变量的标准差。 如果等级是不同的整数,Spearman 的 Rho 可以计算为: \[r _ s = 1-\ frac { 6 \ sum d_i^2}{n(n^2-1)} \] 其中\( d \)是等级之间的差值,而\( n \)与等级数有关。 ## 结论 通过评估,我们可以得出如下结论: * 模型能够从数据中学习,而不是记忆它吗? * 该模型在实际用例的实时场景中表现如何? * 模型做得有多好? * 培训策略是否需要改进?我们是否需要更多的数据、更多的功能、完全不同的算法等等。? 在本系列文章中,我们了解了一些可以评估聚类和排序模型的评估指标。 评估最大似然模型是决定模型性能是否良好的重要参数。如上所述,ML 和 DL 模型存在无数的评估标准。我们必须选择正确的技术,同时积极考虑模型、数据集和用例的类型。通常,单个指标是不够的。在这种情况下,可以使用度量的组合。 我希望你喜欢阅读*评估* *指标*系列! # ML *台:购买还是构建 > 原文:<https://blog.paperspace.com/ml-platforms-buy-vs-build/> [2021 年 12 月 2 日更新:本文包含关于梯度实验的信息。实验现已被弃用,渐变工作流已经取代了它的功能。[请参见工作流程文档了解更多信息](https://docs.paperspace.com/gradient/explore-train-deploy/workflows)。] 如果你关注 MLOps 的新兴学科,现在你可能已经听说过一些著名的内部开发的 ML *台,如优步的米开朗基罗和 AirBnB 的 BigHead。大型技术公司是 ML 的早期采用者,并投入了大量资源开发复杂的专有*台,帮助他们大规模开发和部署模型。基于两个关键原因,构建他们自己的*台的决定是非常有意义的: * 他们拥有大量的工程资源来致力于构建(复杂的)内部工具。 * 在任何可行的现成*台出现之前,他们就在投资 ML。 ### 那么什么时候造 vs 买没有意义呢? 并非所有关键软件*台都是内部构建的。源代码控制管理(例如 GitHub)和 CI/CD 管道(例如 Travis CI)是我想到的两个例子。 这就引出了一个问题,*你的底线在哪里?一种流行的方法是当具体的问题在公司的掌控之中时,“构建”(相对于“购买”)。例如,如果你是一个在线零售商,建立一个定制的电子商务结账系统可能是战略性的,因为它成为一种知识产权(理解为:竞争优势)。然而,情况并非总是如此。构建和维护工具给公司带来了巨大的负担,产生了技术债务,并从其他潜在的更有价值的工作中转移了资源(机会成本的典型例子)。从理论上讲,如果你购买一个解决方案,提供该解决方案的供应商将会专注于这个问题,而专注往往会带来更好的产品。最重要的是,他们将继续创新和改进他们的产品,因此,随着时间的推移,为您的组织增加越来越多的价值。相反,内部工具通常永远过时、笨重、不可靠,并且在资源分配和运营效率方面代价高昂。* 尽管优步·米开朗基罗受到了媒体的大肆宣传,但现在现成的解决方案是可行的,甚至有理由认为世界上的 Uber 和 AirBnBs 应该选择购买一个 ML *台。只要一个*台足够大,能够满足一个组织的需求,那么成本-价值等式将很少证明构建一个内部工具是合理的。 ### 构建端到端的 ML *台涉及哪些内容? 构建和部署一个玩具 ML 应用程序相当简单。在大型数据集上训练大型模型并可靠地部署它们(例如以低延迟处理数千个请求)是很困难的。 > 迭代速度>迭代质量 为了在真实数据上建立精确的模型,成功取决于实验的规模和速度——用更多的超参数进行更多的实验会产生更好的结果。敏捷 ML 团队需要能够在这些大型的高要求模型中快速迭代实验,并且以可重复的方式进行。 并行运行实验和服务模型在计算上是昂贵的。在过去的 5 年里,最好的模型导致计算需求增加了 300,000 倍。为了加快训练和推理,模型通常针对大型计算集群运行,这些集群有时需要跨越云环境,甚至云和本地混合环境。提供健壮的基础设施编排层、排队系统和资源控制(例如设置限制、跟踪失控的作业等。),随着规模的扩大,这一点至关重要。 每一个新的模型和每一个新的 ML 团队成员都会带来复杂性。模型管理(例如版本管理、性能监控、治理、沿袭、试运行/生产环境等。)需要计算和管理层的自动化、策略控制和其他功能。 生产 ML 系统从部署的那一刻起就开始降级。部署这些系统不仅仅是运送一个模型。它是关于构建一个基础设施,以便对新数据进行持续的改进和重新培训,对意外行为和可调试性发出警报,并且有一个机制来无中断地推出更新(和回滚更改)。 如果所有这些让您想起了 CI/CD 这样的传统软件开发概念,那么您并不孤单。将 [CI/CD 方法应用于机器学习](https://blog.paperspace.com/ci-cd-for-machine-learning-ai/)是一个新概念,许多人认为这将有助于使该行业从纯粹的研发或学术学科成熟到专注于将模型运送到最终用户手中并推动商业价值的领域。 > 企业机器学习的故事:“我花了 3 周时间开发模型。已经> 11 个月了,仍然没有部署。”[@ DineshNirmalIBM](https://twitter.com/DineshNirmalIBM?ref_src=twsrc%5Etfw)[# strata data](https://twitter.com/hashtag/StrataData?src=hash&ref_src=twsrc%5Etfw)[# strata conf](https://twitter.com/hashtag/strataconf?src=hash&ref_src=twsrc%5Etfw) > > — ginablaber (@ginablaber) [March 7, 2018](https://twitter.com/ginablaber/status/971450218095943681?ref_src=twsrc%5Etfw) # 硅谷的非热狗应用中的逻辑回归 > 原文:<https://blog.paperspace.com/ml_behind_nothotdog_app/> ![source](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/62e67ac5fbe059d9acc2be7abd21f67e.png) ## 分类:逻辑回归 在 HBO 热播电视剧《硅谷》中看到的大胆项目背后,有相当一部分真正的数学和科学。在这些项目中,可能最现实和有些大胆的项目出现在第四季的第四集。这个项目的目标是建立一个人工智能应用程序,它可以根据图片识别食物——如果你可以的话,是“食物的 Shazam”。但是,当应用程序最终只能识别热狗时,事情就滑稽地破裂了,其他一切都不是热狗。事实上,为该节目工作的工程师蒂姆·安格拉德制作了一个真实生活而不是热狗的应用程序,你可以在 Andriod 和 iOS 上找到它。他还在 Medium 上写了一篇博客,解释他是如何做到的,其中涉及到一些严肃的独创性。有趣的事实:他使用 Paperspace 在 Ubuntu 上的 P5000 实例来加速一些实验。 那么,Not Hotdog app 背后的 ML 是什么?让我们从基础开始。 ## 分类 分类是给观察分配标签的任务。分类算法将一组标记的观察值作为输入,并输出一个分类器。这是一种监督学习技术,因为它需要训练数据形式的监督来学习分类器。 Not Hotdog 应用程序将图像分为两类,因此它是二进制分类器的一个实例。为了识别热狗,它用由几千张热狗和其他食物的图像组成的数据集进行了训练。 开始接触一点数学, 设$Dn = {(xi,y_i): i \in [n]}$是$n$个标记观测值的数据集。 在这里,$ \mathbb{r}^k$的$xi 是特征,$伊的{0,1}$是标注。 分类器$l:\mathbb{R}^k \to {0,1}$,是预测新观察$x$的标签$\hat{y}$使得$l(x) = \hat{y}$的函数。 二元分类算法$A:\mathcal{D}\to\mathcal{L}$是从数据集集$\mathcal{D}$到二元分类器集$\mathcal{L}$的函数。 简单地说,给定一个$Dn \in \mathcal{D}$算法$A$输出一个二元分类器$l \in \mathcal{L}$使得$A(Dn) = l$。但是它用什么标准来选择$l$呢? 通常,$A$在一组参数化分类器$\mathcal{L}w$中搜索一个最小化损失函数$L(Dn,l)$的分类器。对于参数化分类器,我们指的是$\mathcal{L}{w} = {l(x,w):L \ in \ mathcal { L } \ text { and } w \ in \ mathcal { C } } $,其中$\mathcal{C}$是一组参数。 $ $ A(Dn)= \ arg \ min { L \ in \ mathcal { L } w } L(D _ n,l)\]

为了理解这是如何工作的,让我们研究一种叫做逻辑回归的二进制分类算法。

逻辑回归

逻辑回归可能是最简单的二元分类算法之一。它由一个单一的逻辑单元组成,可以称之为神经元。事实上,把几个这样的逻辑单元放在一起,你就有了一层神经元。将这些神经元层层堆叠起来,你就有了一个神经网络,这就是深度学习的全部内容。

回来,后勤单位使用后勤功能,其定义为:

$ $ \西格玛(z) = \frac{1}{1+e^{-z}}$$

在此输入图像描述

图形\(\sigma(z)\)看起来像一个拉长且压扁的“S”。由于\(\sigma(z)\)的值总是在 0 和 1 之间,所以可以解释为一个概率。

如果观察值\(x\)\(k\)维的,那么分类器具有\(k+1\)个实值参数,这些参数由\mathbb{R}^{k}\(中的向量\) w \和 mathbb{R}\(中的标量\) w _ 0 \组成。这里考虑的分类器集是:

\[\mathcal{L}{w,b } = { \ sigma(w \ cdot x+w0):w \ in \mathbb{r}^{k},w_0 \in \mathbb{R}} \]

这里\(\cdot\)是矢量点积。这些分类器不提供\(x\)的硬标签(0 或 1)。相反,它们提供了如下解释的概率。

$ $ \ sigma(w \ cdot x+w _ 0)= \ Pr(y = 1 | x)$ $

\(\sigma(w \cdot x + w0 )\)\(x\)属于类别 1 的概率。\(x\)属于类 0 的概率自然是\(1-\sigma(w \cdot x + w0 )\)

通常用于分类的损失函数是交叉熵。在二进制分类的情况下,如果真实标签是\(yi\)并且预测是\(\sigma(w \cdot xi + w0 ) = \hat yi\),则交叉熵损失被定义为:

\[L(Dn,w,w0)= \frac{-1}{n}\sum{i=1}^n(yi \ log(\ hat yi)+(1-yi)\ log(1-\ hat y _ I))$ $ 设$w^$和$w0^$是使$L(Dn,w,w0)$最小的参数。逻辑回归的输出必须是分类器$\sigma(w^\cdot x+w0^)$.但是逻辑回归如何找到$w^$和$w0^$呢? ## 梯度下降 梯度下降是一个简单的迭代过程,用于寻找函数的局部最小值。在每次迭代中,它在梯度的负方向上前进一步。凸函数总是有一个局部极小值,这也是全局极小值。在这种情况下,梯度下降将找到全局最小值。 如果函数$L(Dn,w,w0)$在$w$和$w0$中是凸的,我们可以用梯度下降法求出使$L$最小的参数$w^$和$w0^$。 观察$w \cdot x + w0 = [w0,w]\cdot [1,x]$。这里$[w0,w]$和$[1,x]$是通过分别在$w$之前附加$w0$和在$x$之前附加$1$而获得的$k+1$维向量。为了简化数学,从现在开始让$x$是$[1,x]$和$w$是$[w_0,w]$吧。 我们可以证明$l(dn,w)$是凸的一种方法是证明它的 Hessian 在每一点都是半正定的(PSD ),即$\nabla^2wL(D_n,w)\succeq0$.对于\mathbb{R}^{k+1}$.的所有$ w \人 让我们开始区分$L$。 $ $ \ nab la l = \frac{-1}{n}\sum{i=1}^n(\ frac { yi }-\ frac { 1-yi } { 1-\ hat yi })\ nab la \ hat yi $ $ 这里$\hat yi = \sigma(w\cdot xi)$。让$zi = w\cdot xi$。按链式法则,$ \ nab la \ hat yi = \ frac { d hat yi } { dzi } \ nab la zi $。首先让我们找到$ \ frac { d \ hat yi } { dzi } $。 $ $ \帽子易= \frac{1}{1+e^{-zi}} = \frac{e^{zi}}{1+e^{zi}}\]

重新排列术语,我们得到

$ \ hat yi+\ hat =□

区分 wrt \(z_i\)

$ $ \ begin { align } \ frac { d \ hat yi } { dzi }+\ frac { d \ hat yi}{dzi}e^{zi}+\ hat yie^{zi} & = e^{zi}\ { d \ hat yi}{dzi}(1+e^{zi})& = e^{zi}(1-\hat yi)\ \ frac { d \ hat yi } { dzi } & = \frac{e{zi}}{(1+e)}(1-\hat yi)= \ hat yi(1-\ hat yi)\ end { align } $ $

现在,$ \ nab la zi = \nabla^2w(w\cdot·Xi)= x _ I $。

代入原始方程,我们得到:

$ $ \ nab la l = \frac{-1}{n}\sum{i=1}^n \ frac { yi-\ hat yi } { \ hat yi(1-\ hat yi)} \ hat yi(1-\ hat yi)xi=\frac{1}{n}\sum{i=1}^n(\hat yi-yi)x _ I $ $

\[\nabla^2 l = \frac{1}{n}\sum{i=1}^n xi^t \ nab la \ hat yi = \frac{1}{n}\sum{i=1}^n \frac{xixi^t} { yi(1-yi)} $ $ $yi(1-yi) >0 $,因为$yi \in (0,1)$。每个矩阵$xi^Txi$都是 PSD。因此,$\nabla^2 L\succeq0$,$L$是一个凸函数,梯度下降可用于寻找$w^*$. 梯度下降$(L,D_n,\alpha):$ 将$w$初始化为随机向量。 While $|\nabla L(Dn,w)|>\ epsilon $: $ w = w-\ alpha \ nabla L(Dn,w)$ 这里$\alpha$是一个常数,称为学习率或步长。梯度下降是一阶方法,因为它只使用一阶导数。也可以使用像牛顿法这样的二阶方法。在牛顿的方法中,$\alpha$被替换为黑森:$(\nabla^2wL(Dn,w))^{-1}$.的倒数尽管二阶方法收敛所需的迭代次数较少,但由于涉及矩阵求逆,每次迭代都变得更加昂贵。 在神经网络的情况下,梯度下降过程推广到反向传播算法。 ## 玩具而不是蟒蛇皮热狗 real Not Hotdog 应用程序使用最先进的 CNN 架构在移动设备上运行神经网络。仅仅通过简单的逻辑回归,我们无法做任何有意义的事情。然而,我们可以通过巧妙的方式使用 MNIST 数据集来接*它。 MNIST 数据集由 70,000 个 28x28 的手写数字图像组成。数字“1”是最像热狗的数字。所以对于这个玩具问题,假设“1”是热狗,其余的数字不是热狗。这也有点类似于热狗而不是热狗食品的不*衡,因为“1”只占数字的十分之一(假设每个数字出现的概率相等)。 首先,让我们加载 MNIST 数据集。 ```py from sklearn.datasets import fetch_mldata import numpy as np mnist = fetch_mldata('MNIST original') ``` 让我们使用前 60,000 张图片对剩下的 10,000 张图片进行训练和测试。由于像素值的范围在$[0,255]$,我们除以 255 将其缩放到$[0,1]$。我们修改标签,使“1”标记为 1,其他数字标记为 0。 ```py X_train = mnist.data[:60000]/255.0 Y_train = mnist.target[:60000] X_test = mnist.data[60000:]/255.0 Y_test = mnist.target[60000:] Y_train[Y_train > 1.0] = 0.0 Y_test[Y_test > 1.0] = 0.0 Lets do logistic regression using Sci-kit Learn. from sklearn import linear_model clf = linear_model.LogisticRegression() clf.fit(X_train,Y_train) Y_pred = clf.predict(X_test) ``` 现在让我们在 numpy 的帮助下实现梯度下降。 ```py def logistic(x): return 1.0/(1.0+np.exp(-x)) # The loss function def cross_entropy_loss(X,Y,w,N): Z = np.dot(X,w) Y_hat = logistic(Z) L = (Y*np.log(Y_hat)+(1-Y)*np.log(1-Y_hat)) return (-1.0*np.sum(L))/N # Gradient of the loss function def D_cross_entropy_loss(X,Y,w,N): Z = np.dot(X,w) Y_hat = logistic(Z) DL = X*((Y_hat-Y).reshape((N,1))) DL = np.sum(DL,0)/N return DL def gradient_descent(X_train,Y_train,alpha,epsilon): # Append "1" before the vectors N,K = X_train.shape X = np.ones((N,K+1)) X[:,1:] = X_train Y = Y_train w = np.random.randn(K+1) DL = D_cross_entropy_loss(X,Y,w,N) while np.linalg.norm(DL)>epsilon: L = cross_entropy_loss(X,Y,w,N) #Gradient Descent step w = w - alpha*DL print "Loss:",L,"\t Gradient norm:", np.linalg.norm(DL) DL = D_cross_entropy_loss(X,Y,w,N) L = cross_entropy_loss(X,Y,w,N) DL = D_cross_entropy_loss(X,Y,w,N) print "Loss:",L,"\t Gradient norm:", np.linalg.norm(DL) return w # After playing around with different values, I found these to be satisfactory alpha = 1 epsilon = 0.01 w_star = gradient_descent(X_train,Y_train,alpha,epsilon) N,K = X_test.shape X = np.ones((N,K+1)) X[:,1:] = X_test Y = Y_test Z = np.dot(X,w_star) Y_pred = logistic(Z) Y_pred[Y_pred>=0.5] = 1.0 Y_pred[Y_pred<0.5] = 0.0 ``` 在非热狗的例子和我们的玩具例子中,有严重的阶级不*衡。1 与非 1 的比例约为 1:9。这意味着我们只要一直预测非 1 就能获得 90%的准确率。因此,准确度不是分类器性能的稳健度量。较小班级的 f1 分数是更好的绩效指标。 ```py from sklearn.metrics import classification_report print classification_report(Y_test,Y_pred) ``` 对于 Sci-kit 的逻辑回归: ```py precision recall f1-score support 0.0 1.00 1.00 1.00 8865 1.0 0.97 0.98 0.97 1135 avg/total 0.99 0.99 0.99 10000 ``` 对于我们的实施: ```py precision recall f1-score support 0.0 0.99 0.99 0.99 8865 1.0 0.94 0.93 0.94 1135 avg/total 0.99 0.99 0.99 1000 ``` 两个分类器具有相同的*均精度、召回率和 f1 值。但是 Sci-kit 的版本对 1s 有更好的 f1。 附注:最初的“Shazam for food”应用程序的目标是构建一个多类分类器(尽管有大量的类),但它最终做了二进制分类。我不确定这怎么可能,训练程序和损失函数有很大的不同。然而,真实生活而不是热狗应用程序被训练成二进制分类器。 # 移动下一代(ECCV 2020) > 原文:<https://blog.paperspace.com/mobilenext-eccv-2020/> 虽然深度神经网络的许多最新进展都倾向于优先考虑性能而不是复杂性,但社区中仍然有一种火花,可以使深度神经网络更便宜、更有效地用于更小的设备。高效神经网络的研究有各种方向,从[二元神经网络](https://github.com/liuzechun/ReActNet)和[高效网络](https://arxiv.org/abs/1905.11946)到基于组件的研究,如 [PyConv](https://github.com/iduta/pyconv) 和[深度方向卷积层](https://arxiv.org/abs/1610.02357)。一个经受住时间考验的作品是 MobileNet 模型集,其中包括 [MobileNet](https://arxiv.org/abs/1704.04861) 、 [MobileNetV2](https://arxiv.org/abs/1801.04381) 和 [MobileNetV3](https://arxiv.org/abs/1905.02244) 。 在本帖中,我们将继续推进 MobileNet 模型系列,推出在 ECCV 2020 上发布的新条目 [MobileNeXt](https://arxiv.org/abs/2007.02269) 。我们将首先回顾[剩余网络](https://arxiv.org/abs/1512.03385)和 MobileNetV2 的瓶颈结构,并讨论它们的差异。接下来,在深入研究沙漏块的 PyTorch 实现之前,我们将介绍 MobileNeXt 中提出的重新设计的瓶颈结构。我们将通过研究论文中观察到的结果,以及模型的缺点来结束本文。 ## 目录 * 摘要 * 瓶颈结构 1. 枝 2. 剩余连接 3. 反向剩余连接 * MobileNeXt 1. 沙漏块 2. PyTorch Code * 结果 1. 图像分类 2. PASCAL VOC 上的目标检测 * 缺点和进一步评论 * 参考 ## 摘要 > 我们的研究结果倡导重新思考移动网络设计的瓶颈结构。似乎反向残差并不像通常认为的那样优于瓶颈结构。* Our research reveals that establishing quick links along high-dimensional feature space can improve the model performance. In addition, the convolution in the depth direction should be carried out in a high-dimensional space to learn more expressive features, and learning linear residuals is also crucial to the bottleneck structure.* Based on our research, we propose a novel hourglass block, which essentially extends the classical bottleneck structure. Experiments show that this architecture is more suitable for mobile applications in terms of accuracy and efficiency, and can be used as a "super" operator in architecture search algorithms to achieve better architecture generation. ## 瓶颈结构 高性能神经网络架构的第一个主要出现无疑是残差网络(称为 ResNet)。残差网络已经成为现代神经网络体系结构的核心和圣经。在这些网络中提出的剩余层已经出现在所有新颖的体系结构中,因为它能够改善信息传播。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ece3e2cb8e391710b38469df26bc986e.png) Residual Blocks ResNet 提出了一个基础的可移植结构,称为剩余块。残差块有两种变体,如上图所示;残差块 v1 和残差块 v2。残差块 v1 主要用于在 CIFAR 数据集上评估的较小的 ResNet 模型,而残差块 v2 是用于基于 ImageNet 的模型的更多讨论的块结构。从现在开始,当我们提到残差块时,我们将指残差块 v2,以保持讨论与基于 ImageNet 的模型相关。 让我们将这个剩余瓶颈分解为它的核心组件,包括主分支和剩余连接(也称为“快捷连接”)。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/1788e65433500af0c8d1898664e51454.png) Residual Bottleneck ### 枝 如上所示,在残差块中,输入首先经历$(1 \乘以 1)$逐点卷积运算符(注意:逐点卷积不影响输入张量的空间维度,但用于操纵张量中的通道数量)。这之后是通道保持空间$(3 \乘以 3)$卷积,其本质上减少了输入张量中特征图的空间维度。最后,接下来是另一个逐点$(1 \乘以 1)$卷积层,它将通道的数量增加到与主分支的输入数量相同。 ### 剩余连接 *行于主分支,输入张量被投影到与主分支输出相同的维数上。对于剩余连接,输入需要改变的唯一维度是空间维度,这是通过使用$( 1 \乘以 1)$卷积运算符来实现的,该运算符具有增加的步长,以使空间维度与主分支的输出维度相匹配。(注意:这个$(1 \乘以 1)$卷积不是逐点运算符。) 随后,残差被添加到主分支输出,然后最终通过标准非线性激活函数(本例中为 ReLU)。 ### 反向剩余连接 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/fa3816e3a19bfd90a99b161b3618889c.png) Inverted Residuals Block in MobileNet v2 ## MobileNeXt 通过引入反向残差块,MobileNetV2 取代了它的前身 MobileNetV1,成为了使用最广泛的轻量级架构之一。虽然残差块通过残差/快捷连接来连接更高维的张量(即具有更多通道的张量),但 MobileNetV2 提出通过残差连接来连接瓶颈,并将更大的通道张量保持在主分支内,从而反转这一点。这是减少所需参数和触发器的关键重新设计因素之一。 深度方向卷积层的使用进一步放大了这一点,它基本上每个通道有一个卷积滤波器。通过这种结构上的彻底改革,MobileNets 无法在性能指标上超越其 ResNet 对手,如 ImageNet 数据集上的顶级准确性(这在很大程度上是因为这样一个事实,即由于使用了深度方向卷积运算符,特征空间急剧减少,因此表达能力大大降低。与 ResNet 系列机型相比,MobileNet 仍然能够实现相当好的性能(考虑到它的轻量级、快速性和高效性)。 ### 沙漏块 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/336ce2ab04825e0f5ff8f2e642ea5d13.png) (a) Residual Block (b) Inverted Residual Block (c) Sand Glass Block 基于我们之前对瓶颈结构的讨论,MobileNeXt 本质上提出了对瓶颈结构的又一次彻底检查,他们称之为沙漏块(如上图所示)。从理论上讲,沙漏是经典剩余瓶颈和反向剩余瓶颈的简单结合,它结合了两者的优点。让我们更详细地看一下: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/bb6722dc45e4d22cb4361977d78594f1.png) (a) Inverted Residual Bottleneck (b) SandGlass Bottleneck 沙漏块本质上是经典的残差块,其中主分支中的第一个和最后一个卷积层是信道保持空间深度方向的卷积层。为了模拟瓶颈结构,它使用两个连续的逐点卷积运算符来先减少通道数,然后再增加通道数。这些逐点运算符堆叠在两个深度方向卷积层之间。因为现在更大的通道张量由深度方向内核操作,所以与例如 MobileNetV2 相比,参数的数量显著减少。 ### PyTorch Code ```py import math import torch from torch import nn def _make_divisible(v, divisor, min_value=None): """ This function is taken from the original tf repo. It ensures that all layers have a channel number that is divisible by 8 It can be seen here: https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py :param v: :param divisor: :param min_value: :return: """ if min_value is None: min_value = divisor new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) # Make sure that round down does not go down by more than 10%. if new_v < 0.9 * v: new_v += divisor return new_v class ConvBNReLU(nn.Sequential): def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1, norm_layer=None): padding = (kernel_size - 1) // 2 if norm_layer is None: norm_layer = nn.BatchNorm2d super(ConvBNReLU, self).__init__( nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False), norm_layer(out_planes), nn.ReLU6(inplace=True) ) class SandGlass(nn.Module): def __init__(self, inp, oup, stride, expand_ratio, identity_tensor_multiplier=1.0, norm_layer=None, keep_3x3=False): super(SandGlass, self).__init__() self.stride = stride assert stride in [1, 2] self.use_identity = False if identity_tensor_multiplier==1.0 else True self.identity_tensor_channels = int(round(inp*identity_tensor_multiplier)) if norm_layer is None: norm_layer = nn.BatchNorm2d hidden_dim = inp // expand_ratio if hidden_dim < oup /6.: hidden_dim = math.ceil(oup / 6.) hidden_dim = _make_divisible(hidden_dim, 16) self.use_res_connect = self.stride == 1 and inp == oup layers = [] # dw if expand_ratio == 2 or inp==oup or keep_3x3: layers.append(ConvBNReLU(inp, inp, kernel_size=3, stride=1, groups=inp, norm_layer=norm_layer)) if expand_ratio != 1: # pw-linear layers.extend([ nn.Conv2d(inp, hidden_dim, kernel_size=1, stride=1, padding=0, groups=1, bias=False), norm_layer(hidden_dim), ]) layers.extend([ # pw ConvBNReLU(hidden_dim, oup, kernel_size=1, stride=1, groups=1, norm_layer=norm_layer), ]) if expand_ratio == 2 or inp==oup or keep_3x3 or stride==2: layers.extend([ # dw-linear nn.Conv2d(oup, oup, kernel_size=3, stride=stride, groups=oup, padding=1, bias=False), norm_layer(oup), ]) self.conv = nn.Sequential(*layers) def forward(self, x): out = self.conv(x) if self.use_res_connect: if self.use_identity: identity_tensor= x[:,:self.identity_tensor_channels,:,:] + out[:,:self.identity_tensor_channels,:,:] out = torch.cat([identity_tensor, out[:,self.identity_tensor_channels:,:,:]], dim=1) # out[:,:self.identity_tensor_channels,:,:] += x[:,:self.identity_tensor_channels,:,:] else: out = x + out return out else: return out ``` ## 结果 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/898c44ddd6ee2a2c2781d5ff65ff750a.png) Results on ImageNet classification task 如上图所示,MobileNeXt 似乎在各个方面都比 MobileNet 表现得更好。就参数而言,它更轻,但仍然比其等效的 MobileNetV2 获得更高的精度。在随后的表格中,我们将展示 MobileNeXt 在不同任务中的表现,如 ImageNet 数据集上的图像分类和 Pascal VOC 数据集上的对象检测。 ### 图像分类 | 模型 | 我的钱。(米) | MAdd (M) | 排名第一的 Acc。(%) | | --- | --- | --- | --- | | MobilenetV1-1.0 | Four point two | Five hundred and seventy-five | Seventy point six | | ShuffleNetV2-1.5 | Three point five | Two hundred and ninety-nine | Seventy-two point six | | MobilenetV2-1.0 | Three point five | Three hundred | Seventy-two point three | | MnasNet-A1 | Three point nine | Three hundred and twelve | Seventy-five point two | | 移动电话 3-L-0.75 | Four | One hundred and fifty-five | Seventy-three point three | | ProxylessNAS | Four point one | Three hundred and twenty | Seventy-four point six | | FBNet-B | Four point five | Two hundred and ninety-five | Seventy-four point one | | igcv 3d | Seven point two | Six hundred and ten | Seventy-four point six | | GhostNet-1.3 | Seven point three | Two hundred and twenty-six | Seventy-five point seven | | EfficientNet-b0 | Five point three | Three hundred and ninety | Seventy-six point three | | MobileNeXt-1.0 | Three point four | Three hundred | Seventy-four point zero two | | MobileNeXt-1.0 | Three point nine four | Three hundred and thirty | Seventy-six point zero five | | MobileNeXt-1.1 | Four point two eight | Four hundred and twenty | Seventy-six point seven | *MobileNeXt 表示添加了沙漏模块和 SE 模块的型号,以便与其他先进型号进行公*比较,例如 EfficientNet* ### PASCAL VOC 上的目标检测 | 方法 | 毅力 | 我的钱。(米) | M-Adds (B) | 地图(%) | | --- | --- | --- | --- | --- | | 固态硬盘 300 | VGG | Thirty-six point one | Thirty-five point two | Seventy-seven point two | | 固态硬盘 320 | MobileNetV2 | Four point three | Zero point eight | Seventy-one point seven | | 固态硬盘 320 | MobileNeXt | Four point three | Zero point eight | Seventy-two point six | ## 缺点和进一步评论 1. 该论文未能提供关于物体探测任务的结论性结果。它也没有 MS-COCO 实验来充分验证其对象检测性能。 2. 由于它与经典残差块更相关,因此它使用 ResNets 的训练方案,而不是 MobileNets 的训练方案,因此不是苹果与苹果的比较。如下图所示,当使用 MobileNetV2 的训练方案在 CIFAR 数据集上进行测试时,MobileNeXt 的性能比 MobileNetV2 模型差。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/6da2f0d423138296a7f2b392ef8c279d.png) mnetv2 denotes MobileNet v2, while mxnet denotes MobileNeXt. Displaying mean of 5 runs on CIFAR-10 classification task. 总的来说,这是一个非常简单的想法,但提供了强大的结果,它可能有很好的机会成为计算机视觉领域的默认轻量级模型。 ## 参考 * [MobileNetV2:反向残差和线性瓶颈](https://arxiv.org/abs/1801.04381) * [MobileNets:用于移动视觉应用的高效卷积神经网络](https://arxiv.org/abs/1704.04861) * [搜索 MobileNetV3](https://arxiv.org/abs/1905.02244) * [ReActNet:走向精确的具有广义激活函数的二元神经网络](https://arxiv.org/abs/2003.03488) * [EfficientNet:反思卷积神经网络的模型缩放](https://arxiv.org/abs/1905.11946) * [金字塔卷积:重新思考用于视觉识别的卷积神经网络](https://arxiv.org/abs/2006.11538) * [例外:深度可分卷积深度学习](https://arxiv.org/abs/1610.02357) * [反思瓶颈结构,实现高效的移动网络设计](https://arxiv.org/abs/2007.02269) * [用于图像识别的深度残差学习](https://arxiv.org/abs/1512.03385) * [mobile next 正式实施](https://github.com/zhoudaquan/rethinking_bottleneck_design) * [mobile next 的非官方 PyTorch 实现](https://github.com/digantamisra98/MobileNext) # 基于 Captum 的 PyTorch 模型可解释性和可理解性 > 原文:<https://blog.paperspace.com/model-interpretability-and-understanding-for-pytorch-using-captum/> ### 介绍 模型可解释性的方法*年来变得越来越重要,这是模型复杂性增加和缺乏透明度的直接结果。模型理解是一个热门的研究课题,也是机器学习在各个领域实际应用的焦点。 Captum 为学者和开发人员提供尖端技术,如集成渐变,使识别有助于模型输出的元素变得简单。Captum 使 ML 研究人员更容易使用 PyTorch 模型来构建可解释性方法。 通过更容易地识别对模型输出有贡献的许多元素,Captum 可以帮助模型开发人员创建更好的模型,并修复提供意外结果的模型。 ### 算法描述 Captum 是一个允许实现各种可解释性方法的库。可以将 Captum 的属性算法分为三大类: * **主要属性**:决定每个输入特征对模型输出的贡献。 * **层属性:**评估特定层中的每个神经元对模型输出的贡献。 * **神经元归属:**通过评估每个输入特征的贡献来确定隐藏神经元的激活。 以下是当前在 Captum 中实现的用于初级、层和神经元属性的各种方法的简要概述。还包括噪声隧道的描述,它可用于*滑任何归因方法的结果。除了属性算法之外,Captum 还提供了评估模型解释可靠性的指标。此时,他们提供不忠和敏感性指标,帮助评估解释的准确性。 ## 主要归因技巧 ### 集成渐变 假设我们有一个深度网络的形式表示,F : Rn → [0,1]。设 x ∈ Rn 为当前输入,x′∈Rn 为基线输入。 图像网络中的基线可能是黑色图像,而它可能是文本模型中的零嵌入向量。 从基线 x’到输入 x,我们计算沿直线路径所有点的梯度(单位为 Rn)。通过累积这些梯度,可以生成综合梯度。积分梯度被定义为沿着从基线 x’到输入 x 的直接路径的梯度的路径积分 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/79eaf13deac9ed622c0932f3f6c86974.png) 敏感性和实现不变性这两个基本假设构成了该方法的基础。请参考[原文](https://arxiv.org/pdf/1703.01365.pdf)了解更多关于这些公理的内容。 ### 梯度 SHAP [合作博弈理论](https://en.wikipedia.org/wiki/Cooperative_game_theory)中的沙普利值用于计算梯度 SHAP 值,这些值是使用梯度方法计算的。[梯度 SHAP](https://github.com/slundberg/shap/#deep-learning-example-with-gradientexplainer-tensorflowkeraspytorch-models) 将高斯噪声多次添加到每个输入样本,然后在基线和输入之间的路径上选取一个随机点,以确定输出的梯度。因此,最终的 SHAP 值代表梯度的预期值。*(投入-基线)。假设输入要素是独立的,并且解释模型在输入和提供的基线之间是线性的,则 SHAP 值是*似的。 ### 深层提升 可以使用 [DeepLIFT](https://captum.ai/api/deep_lift.html) (一种反向传播技术)根据输入与其匹配参考(或基线)之间的差异来确定输入变化。DeepLIFT 试图使用来自参考的输入之间的差异来解释来自参考的输出之间的差异。DeepLIFT 采用乘数的概念来“责备”单个神经元的输出差异。对于给定的输入神经元 x 和目标神经元 t,输入神经元 x 与参考值之差为 x,目标神经元 t 与参考值之差为 t,我们希望计算其贡献,我们将乘数 m x t 定义为: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b4239a5923a697c3a5235211befdb96d.png) ### 深层提升 SHAP [DeepLIFT SHAP](https://captum.ai/api/deep_lift_shap.html) 是基于合作博弈论中建立的 Shapley 值的 DeepLIFT 扩展。DeepLIFT SHAP 计算每个输入基线对的 DeepLIFT 属性,并使用基线分布对每个输入示例的结果属性进行*均。DeepLIFT 的非线性规则有助于线性化网络的非线性函数,该方法对 SHAP 值的*似也适用于线性化网络。在这种方法中,输入特征同样被认为是独立的。 ### 显著 通过[显著性](https://captum.ai/api/saliency.html)计算输入属性是一个简单的过程,它产生输出相对于输入的梯度。[在输入端使用一阶泰勒网络](https://en.wikipedia.org/wiki/Taylor_series)展开,梯度是模型线性表示中每个特征的系数。这些系数的绝对值可以用来指示特征的相关性。你可以在[的原始论文](https://arxiv.org/pdf/1312.6034.pdf )中找到关于显著性方法的更多信息。 ### 输入 X 渐变 > [输入 X 梯度](https://captum.ai/api/input_x_gradient.html)是显著性方法的扩展,采用输出相对于输入的梯度并乘以输入特征值。这种方法的一个直觉是考虑线性模型;梯度只是每个输入的系数,输入与系数的乘积对应于要素对线性模型输出的总贡献。 ### 导向反向传播和反卷积 梯度计算通过[导向反向传播](https://captum.ai/api/guided_backprop.html)和[反卷积](https://captum.ai/api/deconvolution.html)进行,尽管 ReLU 函数的反向传播被覆盖,因此只有非负梯度被反向传播。当 ReLU 函数应用于引导反向传播中的输入梯度时,它直接应用于反卷积中的输出梯度。通常将这些方法与卷积网络结合使用,但它们也可以用于其他类型的神经网络结构。 ### 制导 GradCAM 导向反向传播属性计算导向 GradCAM 属性([导向 GradCAM](https://arxiv.org/pdf/1610.02391.pdfhttps://captum.ai/api/guided_grad_cam.html) )与上采样(层)GradCAM 属性的元素级乘积。对给定图层进行属性计算,并对其进行上采样以适应输入大小。卷积神经网络是这种技术的焦点。然而,可以提供能够与输入空间对齐的任何层。通常,提供最后一个卷积层。 ### 特征消融 为了计算属性,一种被称为“[特征消融](https://captum.ai/api/feature_ablation.html)的技术采用了一种基于扰动的方法,在计算输出差异之前,用一个已知的“基线”或“参考值”(如 0)替代每个输入特征。对输入特征进行分组和切除是单独进行的更好的替代方法,许多不同的应用程序都可以从中受益。通过分组和切除图像的片段,我们可以确定该片段的相对重要性。 ### 特征置换 [特征置换](https://captum.ai/api/feature_permutation.html)是一种基于扰动的方法,在该方法中,每个特征在一个批次内被随机置换,并且输出(或损失)的变化被计算为这种修改的结果。特征也可以组合在一起,而不是像特征切除一样单独进行。请注意,与 Captum 中其他可用的算法相比,该算法是唯一一个在提供一批多输入示例时可以提供正确属性的算法。其他算法只需要一个例子作为输入。 ### 闭塞 > [遮挡](https://arxiv.org/pdf/1311.2901.pdf)是一种基于扰动的方法来计算属性,用给定的基线/参考替换每个相邻的矩形区域,并计算输出的差异。对于位于多个区域(超矩形)中的要素,相应的输出差异被*均以计算该要素的属性。遮挡在诸如图像的情况下是最有用的,其中连续矩形区域中的像素可能是高度相关的。 ### Shapley 值采样 归因技术 [Shapley 值](https://captum.ai/api/shapley_value_sampling.html)基于合作博弈理论。该技术采用输入要素的每种排列,并将它们逐个添加到指定的基线。添加每个特征后的输出差异对应于其贡献,并且这些差异在所有排列中被求和以确定属性。 ### 石灰 最广泛使用的可解释性方法之一是 [Lime](https://captum.ai/api/lime.html) ,它通过对输入示例周围的数据点进行采样来训练可解释的替代模型,并在这些点上使用模型评估来训练更简单的可解释“替代”模型,如线性模型。 ### KernelSHAP [核 SHAP](https://captum.ai/api/kernel_shap.html) 是一种使用 LIME 框架计算 Shapley 值的技术。Shapley 值可以在 LIME 框架中通过设置损失函数、加权核以及适当地正则化项来更有效地获得。 ## 层归属技术 ### 层电导 层电导是一种通过将神经元的激活与神经元相对于神经元的输入和输出的偏导数相结合来构建神经元重要性的更全面图像的方法。通过隐藏的神经元,电导建立在综合梯度(IG)属性流的基础上。在[原文](https://arxiv.org/pdf/1805.12233.pdf)中,隐藏神经元 y 的总电导定义如下: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/22a0388e4e71e6ffb01bcad887cec50b.png) ### 内部影响 使用[内部影响](https://arxiv.org/pdf/1802.03788.pdf),可以估计从基线输入到提供的输入之间的梯度积分。此技术类似于应用集成渐变,它涉及到对图层(而不是输入)的渐变进行集成。 ### 层梯度 X 激活 图层渐变 X 激活相当于网络中隐藏图层的输入 X 渐变技术.. 将层元素的激活与指定层的目标输出梯度相乘。 ### grabcad [GradCAM](https://arxiv.org/pdf/1610.02391.pdf) 是一种卷积神经网络层归属技术,通常应用于最后一个卷积层。GradCAM 计算目标输出相对于指定层的渐变,对每个输出通道(输出维度 2)进行*均,并将每个通道的*均渐变乘以层激活。将 ReLU 应用于输出,以确保从所有通道的结果总和中仅返回非负属性。 ## 神经元归因技术 ### 神经元电导 [电导](https://arxiv.org/pdf/1805.12233.pdf)将神经元激活与神经元相对于输入和相对于神经元的输出的偏导数相结合,以提供神经元相关性的更全面的图像。为了确定特定神经元的电导,人们检查来自通过该神经元的每个输入的 IG 属性流。以下是原论文对给定输入属性 I 的神经元 y 的电导的正式定义: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ca69d4ea6562bd603525cbadd10ae1e8.png) 根据该定义,应该注意的是,对神经元的电导(跨所有输入特征)求和总是等于该特定神经元所在层的电导。 ### 神经元梯度 [神经元梯度](https://captum.ai/api/neuron.html)方法是网络中单个神经元的显著性方法的等价方法。 它只是计算神经元输出相对于模型输入的梯度。 与显著性一样,这种方法可以被认为是在给定输入下对神经元输出进行一阶泰勒展开,梯度对应于模型线性表示中每个特征的系数。 ### 神经元积分梯度 使用一种称为“[神经元积分梯度](https://captum.ai/api/neuron.html)的技术,可以估计从基线输入到感兴趣的输入的整个路径中特定神经元的输入梯度积分积分梯度等效于这种方法,假设输出只是被识别神经元的输出。你可以在原文[中找到关于综合梯度方法的更多信息](https://arxiv.org/abs/1703.01365)。 ### 神经元梯度形状 神经元 GradientSHAP 相当于特定神经元的 GradientSHAP。神经元梯度将高斯噪声多次添加到每个输入样本中,沿着基线和输入之间的路径选择一个随机点,并计算目标神经元相对于每个随机选取的点的梯度。得到的 SHAP 值接*预测的梯度值*。(投入-基线)。 ### 神经元深度提升 SHAP 神经元深度提升 SHAP 相当于特定神经元的深度提升。使用基线分布,深度提升 SHAP 算法计算每个输入-基线对的神经元深度提升属性,并对每个输入示例的结果属性进行*均。 ### 噪音隧道 [噪声隧道](https://captum.ai/api/noise_tunnel.html)是一种可与其他方法结合使用的归因技术。噪声隧道会多次计算属性,每次都会将高斯噪声添加到输入中,然后根据所选的类型合并结果属性。支持以下噪声隧道类型: * **Smoothgrad:** 返回采样属性的均值。使用高斯核*滑指定的属性技术是这一过程的*似。 * **Smoothgrad Squared** :返回样本属性*方的*均值。 * **Vargrad:** 返回样本属性的方差。 ## 韵律学 ### 无信仰 [不忠](https://captum.ai/api/metrics.html)测量输入扰动幅度的模型解释和预测函数对这些输入扰动的变化之间的均方误差。不忠的定义如下: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/4c9f47b17fd2649e762c54e2610a4822.png) 从众所周知的属性技术(如积分梯度)来看,这是一个计算效率更高且扩展的 sensitivity vy-n 概念。后者分析属性之和与其输入和预定义基线的预测函数之差之间的相关性。 ### 灵敏度 [灵敏度](https://captum.ai/api/metrics.html#sensitivity),定义为使用基于蒙特卡罗采样的*似法对微小输入扰动的解释变化程度,测量如下: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/e6eaf17031a6964012a4cacd137aaaa2.png) 默认情况下,我们从具有默认半径的 L-Infinity 球的子空间进行采样,以*似灵敏度。用户可以改变球的半径和样本函数。 请参考[算法文档](https://captum.ai/docs/algorithms)获取所有可用算法的完整列表。 ## 预训练 ResNet 模型的模型解释 本教程展示了如何在预训练的 ResNet 模型上使用模型可解释性方法和选择的图像,并通过将它们叠加在图像上来可视化每个像素的属性。在本教程中,我们将使用解释算法集成梯度,梯度形状,属性与层梯度和闭塞。 在开始之前,您必须有一个 Python 环境,其中包括: * Python 版本 3.6 或更高版本 * PyTorch 版本 1.2 或更高版本(建议使用最新版本) * TorchVision 版本 0 * . 6 或更高版本(建议使用最新版本) * Captum(推荐最新版本) 根据您使用的是 Anaconda 还是 pip 虚拟环境,以下命令将帮助您设置 Captum: 用`conda`: ```py conda install pytorch torchvision captum -c pytorch ``` 用`pip`: ```py pip install torch torchvision captum ``` 让我们导入库。 ```py import torch import torch.nn.functional as F from PIL import Image import os import json import numpy as np from matplotlib.colors import LinearSegmentedColormap import os, sys import json import numpy as np from PIL import Image import matplotlib.pyplot as plt from matplotlib.colors import LinearSegmentedColormap import torchvision from torchvision import models from torchvision import transforms from captum.attr import IntegratedGradients from captum.attr import GradientShap from captum.attr import Occlusion from captum.attr import LayerGradCam from captum.attr import NoiseTunnel from captum.attr import visualization as viz from captum.attr import LayerAttribution ``` 加载预训练的 Resnet 模型,并将其设置为评估模式 ```py model = models.resnet18(pretrained=True) model = model.eval() ``` ResNet 是在 ImageNet 数据集上训练的。下载并读取内存中的 ImageNet 数据集类/标签列表。 ```py wget -P $HOME/.torch/models https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json labels_path = os.getenv("HOME") + '/.torch/models/imagenet_class_index.json' with open(labels_path) as json_data: idx_to_labels = json.load(json_data) ``` 现在我们已经完成了模型,我们可以下载图片进行分析。在我的例子中,我选择了一个猫的形象。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/e66c8ee765672b6d64ad55a389a25c0d.png) [source](https://fr.m.wikipedia.org/wiki/Fichier:Cat_March_2010-1.jpg) 您的图像文件夹必须包含文件 cat.jpg。如下图所示, [Image.open()](https://pillow.readthedocs.io/en/stable/reference/Image.html) 打开并识别给定的图像文件,[NP . asary()](https://numpy.org/doc/stable/reference/generated/numpy.asarray.html)将其转换为数组。 ```py test_img = Image.open('path/cat.jpg') test_img_data = np.asarray(test_img) plt.imshow(test_img_data) plt.show() ``` 在下面的代码中,我们将为图像定义变形器和归一化函数。为了训练我们的 ResNet 模型,我们使用了 ImageNet 数据集,它要求图像具有特定的大小,通道数据被标准化为指定的值范围。[变换。Compose()](https://pytorch.org/vision/stable/generated/torchvision.transforms.Compose.html) 将几个变换组合在一起,[变换。Normalize()](https://pytorch.org/vision/main/generated/torchvision.transforms.Normalize.html) 使用*均值和标准偏差对张量图像进行归一化。 ```py # model expectation is 224x224 3-color image transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), #crop the given tensor image at the center transforms.ToTensor() ]) # ImageNet normalization transform_normalize = transforms.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ) img = Image.open('path/cat.jpg') transformed_img = transform(img) input = transform_normalize(transformed_img) #unsqueeze returns a new tensor with a dimension of size one inserted at the #specified position. input = input.unsqueeze(0) ``` 现在,我们将预测输入图像的类别。可以问的问题是,“我们的模型觉得这个图像代表了什么?” ```py #call our model output = model(input) ## applied softmax() function output = F.softmax(output, dim=1) #torch.topk returns the k largest elements of the given input tensor along a given #dimension.K here is 1 prediction_score, pred_label_idx = torch.topk(output, 1) pred_label_idx.squeeze_() #convert into a dictionnary of keyvalues pair the predict label, convert it #into a string to get the predicted label predicted_label = idx_to_labels[str(pred_label_idx.item())][1] print('Predicted:', predicted_label, '(', prediction_score.squeeze().item(), ')') ``` 输出: ```py Predicted: tabby ( 0.5530276298522949 ) ``` ResNet 认为我们的猫的形象描绘了一只真实的猫,这一事实得到了证实。但是是什么给模特的印象是这是一只猫的形象呢?为了找到这个问题的答案,我们将咨询 Captum。 ### 具有集成梯度的特征属性 Captum 中的各种特征归属技术之一是[集成梯度](https://captum.ai/api/integrated_gradients.html)。集成梯度通过估计模型输出相对于输入的梯度积分,为每个输入要素授予一个相关性分数。 在我们的例子中,我们将采用输出向量的一个特定分量——表示模型在其所选类别中的置信度的分量——并使用综合梯度来计算输入图像的哪些方面对该输出有贡献。这将允许我们确定图像的哪些部分在产生这个结果时是最重要的。 在我们从综合梯度中获得重要性地图后,我们将使用 Captum 捕获的可视化工具来提供对重要性地图的清晰易懂的描述。 积分梯度将确定预测类 **pred_label_idx** 相对于沿着从黑色图像到我们的输入图像的路径的输入图像像素的模型输出的梯度积分。 ```py print('Predicted:', predicted_label, '(', prediction_score.squeeze().item(), ')') #Create IntegratedGradients object and get attributes integrated_gradients = IntegratedGradients(model) #Request the algorithm to assign our output target to attributions_ig = integrated_gradients.attribute(input, target=pred_label_idx, n_steps=200) ``` 输出: ```py Predicted: tabby ( 0.5530276298522949 ) ``` 让我们通过将图片叠加在图片上来看看图片和它的属性。Captum 提供的 [visualize_image_attr()](https://captum.ai/api/utilities.html) 方法提供了一组根据您的偏好定制属性数据显示的可能性。这里,我们传入一个自定义的 Matplotlib 颜色图(参见[LinearSegmentedColormap()](https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.LinearSegmentedColormap.html))。 ```py #result visualization with custom colormap default_cmap = LinearSegmentedColormap.from_list('custom blue', [(0, '#ffffff'), (0.25, '#000000'), (1, '#000000')], N=256) # use visualize_image_attr helper method for visualization to show the #original image for comparison _ = viz.visualize_image_attr(np.transpose(attributions_ig.squeeze().cpu().detach().numpy(), (1,2,0)), np.transpose(transformed_img.squeeze().cpu().detach().numpy(), (1,2,0)), method='heat_map', cmap=default_cmap, show_colorbar=True, sign='positive', outlier_perc=1) ``` 输出: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/c6702165a1b35afeb5fc5e3f0443a3f0.png) 您应该能够注意到,在上面显示的图像中,图像中猫周围的区域是综合梯度算法给我们提供最强信号的地方。 让我们通过使用综合梯度来计算属性,然后在由[噪声隧道](https://captum.ai/api/noise_tunnel.html)产生的几幅图像上*滑它们。 后者通过添加标准偏差为 1、10 倍的高斯噪声来修改输入( **nt_samples=10** )。噪声隧道使用 **smoothgrad_sq** 方法使噪声样本的所有 nt_samples 的属性一致。 **smooth grad _ sq**的值是跨越 **nt_samples** 样本的*方属性的*均值。[visualize _ image _ attr _ multiple()](https://captum.ai/api/utilities.html)通过归一化指定符号的属性值(正、负、绝对值或全部),然后使用所选模式将它们显示在 matplotlib 图形中,从而可视化给定图像的属性。 ```py noise_tunnel = NoiseTunnel(integrated_gradients) attributions_ig_nt = noise_tunnel.attribute(input, nt_samples=10, nt_type='smoothgrad_sq', target=pred_label_idx) _ = viz.visualize_image_attr_multiple(np.transpose(attributions_ig_nt.squeeze().cpu().detach().numpy(), (1,2,0)), np.transpose(transformed_img.squeeze().cpu().detach().numpy(), (1,2,0)), ["original_image", "heat_map"], ["all", "positive"], cmap=default_cmap, show_colorbar=True) ``` 输出: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/c6e24876196f53a9a858d81383e7ff7e.png) 我可以在上面的图片中看到,模型集中在猫的头部。 让我们用 GradientShap 来结束。 [GradientShap](https://captum.ai/api/gradient_shap.html) 是一种可用于计算 Shap 值的梯度方法,也是一种了解全局行为的绝佳工具。它是一种线性解释模型,通过使用参考样本的分布来解释模型的预测。它确定在输入和基线之间随机选取的输入的预期梯度。 基线是从提供的基线分布中随机选取的。 ```py torch.manual_seed(0) np.random.seed(0) gradient_shap = GradientShap(model) # Definition of baseline distribution of images rand_img_dist = torch.cat([input * 0, input * 1]) attributions_gs = gradient_shap.attribute(input, n_samples=50, stdevs=0.0001, baselines=rand_img_dist, target=pred_label_idx) _ = viz.visualize_image_attr_multiple(np.transpose(attributions_gs.squeeze().cpu().detach().numpy(), (1,2,0)), np.transpose(transformed_img.squeeze().cpu().detach().numpy(), (1,2,0)), ["original_image", "heat_map"], ["all", "absolute_value"], cmap=default_cmap, show_colorbar=True) ``` 输出: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/5a46942ad0e53012d18973b4ea758fe3.png) ### 使用图层梯度图的图层属性 借助图层属性,您可以将模型中隐藏图层的活动与输入要素相关联。 我们将应用一种层属性算法来调查模型中包含的一个卷积层的活动。 GradCAM 负责计算目标输出相对于指定层的梯度。然后,对每个输出通道(输出的维度 2)的这些梯度进行*均,并将层激活乘以每个通道的*均梯度。 对所有频道的结果进行汇总。由于卷积层的活动通常在空间上映射到输入,GradCAM 属性经常被上采样并用于屏蔽输入。值得注意的是,GradCAM 是明确为卷积神经网络(convnets)开发的。图层属性的设置方式与输入属性相同,只是除了模型之外,还必须在要分析的模型中提供一个隐藏图层。与之前讨论的类似,当我们调用 **attribute(),**时,我们指出了感兴趣的目标类。 ```py layer_gradcam = LayerGradCam(model, model.layer3[1].conv2) attributions_lgc = layer_gradcam.attribute(input, target=pred_label_idx) _ = viz.visualize_image_attr(attributions_lgc[0].cpu().permute(1,2,0).detach().numpy(), sign="all", title="Layer 3 Block 1 Conv 2") ``` 为了在输入图像和属性数据之间进行更精确的比较,我们将借助位于[layer attribute](https://captum.ai/api/base_classes.html?highlight=layerattribution#captum.attr.LayerAttribution)基类中的函数 **interpolate()** 对其进行增采样。 ```py upsamp_attr_lgc = LayerAttribution.interpolate(attributions_lgc, input.shape[2:]) print(attributions_lgc.shape) print(upsamp_attr_lgc.shape) print(input.shape) _ = viz.visualize_image_attr_multiple(upsamp_attr_lgc[0].cpu().permute(1,2,0).detach().numpy(), transformed_img.permute(1,2,0).numpy(), ["original_image","blended_heat_map","masked_image"], ["all","positive","positive"], show_colorbar=True, titles=["Original", "Positive Attribution", "Masked"], fig_size=(18, 6)) ``` 输出: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/a7177021565c70e14a94219c030dbcb9.png) 像这样的可视化有可能为您提供独特的见解,了解隐藏层如何响应您提供的输入。 ### 具有遮挡的特征属性 基于梯度的方法有助于在直接计算输出相对于输入的变化方面理解模型。被称为基于扰动的归因的技术对这个问题采取了一种更直接的方法,即通过修改输入来量化这种变化对输出的影响。一种这样的策略叫做[闭塞](https://captum.ai/api/occlusion.html)。 它需要交换输入图像的片段,并分析这种变化如何影响输出端产生的信号。 在下面,我们将配置遮挡属性。像卷积神经网络的配置一样,您可以选择目标区域的大小和步长,这决定了各个测量的间距。 我们将使用**visualize _ image _ attr _ multiple()**函数来查看我们的遮挡属性的结果。该功能将显示每个区域的正面和负面属性的热图,并用正面属性区域遮盖原始图像。 遮罩为我们的猫照片中被模型识别为最“像猫”的区域提供了非常有启发性的外观 ```py occlusion = Occlusion(model) attributions_occ = occlusion.attribute(input, target=pred_label_idx, strides=(3, 8, 8), sliding_window_shapes=(3,15, 15), baselines=0) _ = viz.visualize_image_attr_multiple(np.transpose(attributions_occ.squeeze().cpu().detach().numpy(), (1,2,0)), np.transpose(transformed_img.squeeze().cpu().detach().numpy(), (1,2,0)), ["original_image", "heat_map", "heat_map", "masked_image"], ["all", "positive", "negative", "positive"], show_colorbar=True, titles=["Original", "Positive Attribution", "Negative Attribution", "Masked"], fig_size=(18, 6) ) ``` 输出: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/58af6f229904db91fd45e846deef4b7f.png) 图像中包含猫的部分似乎被赋予更高的重要性。 ### 结论 Captum 是 PyTorch 的一个模型可解释性库,它是通用和简单的。它为理解特定神经元和层如何影响预测提供了最先进的技术。 主要有三类归因技术:[初级归因技术](https://captum.ai/docs/algorithms#general-attribution)、[分层归因技术](https://captum.ai/docs/algorithms#layer-attribution)、[神经元归因技术](https://captum.ai/docs/algorithms#neuron-attribution)。 ## 参考 [https://py torch . org/tutorials/初学者/intro yt/captumyt . html](https://pytorch.org/tutorials/beginner/introyt/captumyt.html) https://captum.ai/docs/algorithms [https://captum.ai/docs/introduction.html](https://captum.ai/docs/introduction.html) [https://Gilbert tanner . com/blog/interpreting-py torch-models-with-captum/](https://gilberttanner.com/blog/interpreting-pytorch-models-with-captum/) [https://arxiv.org/pdf/1805.12233.pdf](https://arxiv.org/pdf/1805.12233.pdf) [https://arxiv.org/pdf/1704.02685.pdf](https://arxiv.org/pdf/1704.02685.pdf) # 使用 Arize AI 监控梯度部署 > 原文:<https://blog.paperspace.com/monitoring-deployments-with-arize-ai/> # 前言 在本教程中,您将学习如何将 Arize 集成到梯度部署中,以实现稳健的 ML 模型可观测性。本教程中使用的代码可以在这个 [GitHub 库](https://github.com/gradient-ai/Deployments-Arize-Tutorial)中找到。 本教程的视角来自一家拥有客户数据(如任期、账单历史等)的电信公司。)并希望防止客户流失。这家电信公司希望创建一个模型来确定客户是否可能流失,如果是,则采取措施(例如,向客户推销)来防止这种情况发生。一旦创建了模型,该公司希望通过跟踪数据如何随时间变化并更好地可视化他们的数据来部署模型并监控模型性能。 我们将通过 5 个主要步骤完成上述构建: * 构建分类模型 * 集成 Arize API 和日志数据 * 配置 Arize 模型基线 * 创建模型的渐变部署 * 记录传入的 Arize 请求 我们开始吧! * * * # 教程步骤 ## 模型构建 在这个练习中,我们首先需要一个分类模型来预测客户流失。在上面链接的 GitHub repo 中,有一个名为 *churn_model.ipynb* 的文件,其中包含了模型构建过程。在这个 Python 笔记本中,有数据导入步骤、数据预处理步骤和模型训练步骤。在这个模型训练过程的最后,XGBoost 分类模型被保存为一个名为 *xgb_cl_model.json* 的. json 文件。 本笔记本使用的数据来自 Kaggle,可在此处找到[。](https://www.kaggle.com/datasets/blastchar/telco-customer-churn) 您可以从下面的笔记本中看到模型训练和保存过程 ```py import xgboost as xgb from sklearn.metrics import accuracy_score # Create and train a XGBoost Classifier xgb_cl = xgb.XGBClassifier(use_label_encoder=False) xgb_cl.fit(X_train, y_train) # Predict on test set preds = xgb_cl.predict(X_test) print(accuracy_score(y_test, preds)) # Save model as .json file xgb_cl.save_model('xgb_cl_model.json') ``` ## Arize API 现在,我们已经训练并保存了我们的模型,让我们记录这个模型的训练数据,以便我们可以使用它作为基线。一旦设定了基线,任何未来的实时预测都将通过 Arize 上的模型与基线数据集进行比较,以监控数据或预测中的任何漂移。 我们需要做的第一件事是创建一个 Arize 帐户。Arize 确实有一个免费层,您可以在本教程中使用。 第一步是进入 [Arize 网站](https://arize.com),点击*开始*,并按照提示创建您的帐户。一旦你创建了你的账户,你需要你的 Space 键和 API 键来连接你的 Python 代码。 您可以在 Arize 项目中找到这两个键,方法是在页面右侧的 Overview 选项卡中单击 Space Setting 找到它们。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/0184172c9e3ebfe1cde6a35f4016809d.png) 一旦你有了上面的两个密钥,你就可以像下面这样把它们插入到 *churn_model.ipynb* 文件中。 ```py from arize.pandas.logger import Client, Schema from arize.utils.types import Environments, ModelTypes SPACE_KEY = "YOUR-SPACE-KEY" API_KEY = "YOUR-API-KEY" arize_client = Client(space_key=SPACE_KEY, api_key=API_KEY) model_id = ( "telco-churn-demo-model" # This is the model name that will show up in Arize ) model_version = "v1.0" # Version of model - can be any string if SPACE_KEY == "YOUR-SPACE-KEY" or API_KEY == "YOUR-API-KEY": raise ValueError("❌ NEED TO CHANGE SPACE AND/OR API_KEY") else: print("✅ Arize setup complete!") ``` 一旦上面的单元格填充了您的键,您就可以运行 Python 笔记本的其余部分,它将使用下面的代码将训练数据加载到您的 Arize 模型中。 ```py # Define a Schema() object for Arize to pick up data from the correct columns for logging training_schema = Schema( prediction_id_column_name="customerID", prediction_label_column_name="Predicted_Churn", actual_label_column_name="Churn", feature_column_names=feature_cols, ) # Logging Training DataFrame training_response = arize_client.log( dataframe=combined_train_df, model_id=model_id, model_version=model_version, model_type=ModelTypes.SCORE_CATEGORICAL, environment=Environments.TRAINING, schema=training_schema, ) # If successful, the server will return a status_code of 200 if training_response.status_code != 200: print( f"logging failed with response code {training_response.status_code}, {training_response.text}" ) else: print(f"✅ You have successfully logged training set to Arize") ``` 一旦你成功运行了上面的代码单元,回到你的 Arize 账户,点击 *Spaces Overview* ,点击 *telco-churn-demo-model* 。进入 Arize 模型后,导航到 Data Ingestion 选项卡,您将看到 Arize 正在接收和处理培训数据。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/7796fab24f7754d238ee46d020e95b68.png) Data Ingestion in Arize 这个过程可能需要 10 分钟。此过程完成后,继续本教程中的配置数据基线步骤。 ## 配置数据基线 为了判断数据摄取是否已完成,在 Arize Datasets 选项卡上,您应该看到您的数据集及其要素详细信息已列出。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/4dd95f8a5afb3702cd3477adb71febb8.png) 现在,我们希望将我们的训练数据设置为 Arize 模型基线数据。您可以通过单击数据集选项卡中的配置基线按钮来完成此操作。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ac2da2dc2a25612165e13d09478d9c95.png) 使用以下设置完成配置基线过程: * 建立基线:生产前 * 设置生产前基线:培训版本 1.0 一旦设置了基线,任何将来进入模型的请求都将与基线进行比较,这样我们就可以监控数据和预测偏差。 ## 创建部署 既然已经设置了基线数据,我们将创建一个应用程序的实时部署,该应用程序服务于我们的模型,能够接受传入的 API 调用,并在 Arize 中记录这些调用以进行监控。 在 GitHub 资源库中,Python 文件中有一个 Flask 应用: *app.py* 。这个 Flask 应用程序在请求体中接受一个 json 对象,处理数据,预测客户是否会流失,并将请求数据和预测记录到 Arize。 在这个 *app.py* 文件中,你需要做的一个改变是在文件的第 71 和 72 行插入你的 Arize Space 键和 API 键。像 Python 笔记本中一样,这将允许您的应用程序连接到 Arize 模型。 一旦编写了 Flask 应用程序,我们需要在 Gradient 中部署它。第一步是创建梯度数据集。您可以通过导航到渐变项目中的数据选项卡并单击添加按钮来完成此操作。在表单中填写所需的数据集名称(例如,arize-deployment-repo ),然后单击 Create 创建新的数据集。创建数据集后,记下数据集 ID(它将是一个 15 个字符的字符串)。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/7bde41732f40f389562206c8b56a7281.png) 在您创建数据集之后,我们现在可以部署我们的模型了。GitHub repo 中有一个名为 *deployment.yaml* 的文件。您可以使用它作为模板部署规范来构建您的梯度部署。 要构建您的部署,导航到您的渐变项目中的 deployment 选项卡,在该选项卡中,单击 Create 按钮,然后单击 upload a deployment spec 链接,如下所示。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/1fbcd437a77908f339776d8034d0eaea.png) 进入表单后,将 *deployment.yaml* 中的文本复制到文本框中,并用上面创建的数据集的 ID 替换第 11 行的数据集 ID。将该文本作为部署规范粘贴并命名部署后,单击 Create Deployment。您的部署可能需要几分钟才能启动。部署成功启动后,在“部署”选项卡中,它将显示为“就绪”。 如果您单击您的部署,您将看到一个对应于部署端点的链接,如下所示。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/fceeac0ea20a0d0ca840ea9fb83dcf70.png) 单击该链接检查您的部署是否启动并运行。如果部署运行成功,您将看到一个页面,表明部署运行正常。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d0969fa34e4d209b22280055def372e3.png) ## 记录传入的请求 既然您的应用程序已经运行并可以接受传入的请求,我们将把请求发送到您的应用程序的端点。当您的应用程序收到请求时,它将处理要输入到模型中的数据,预测客户是否会流失,然后将该预测与请求数据一起写入 Arize。由于来电不断,您可以监控任何数据漂移,并查看传入流量。 有多种方法可以将 API 调用发送到您的部署,但是我将使用一个名为 Postman 的应用程序。你可以在这里下载 Postman [,它可以免费用于本教程的目的。](https://www.postman.com/downloads/) 安装后,您可以使用 Postman 向您的部署端点发送 Post 请求。在 Postman 中,通过单击页面左侧工具栏上方的 New 按钮创建一个新的 HTTP 请求。然后,将请求的类型更改为 POST,并在请求体中粘贴下面的 JSON 对象。 ```py {"customerID":"7590-VHVEG","gender":"Female","SeniorCitizen":0, "Partner":"Yes","Dependents":"No","tenure":1,"PhoneService":"No", "MultipleLines":"No phone service","InternetService":"DSL", "OnlineSecurity":"No","OnlineBackup":"Yes","DeviceProtection":"No", "TechSupport":"No","StreamingTV":"No","StreamingMovies":"No", "Contract":"Month-to-month","PaperlessBilling":"Yes", "PaymentMethod":"Electronic check","MonthlyCharges":29.85, "TotalCharges":"29.85"} ``` 最后,将部署端点粘贴到请求 URL 文本框中,并将“/prediction”附加到端点。当所有这些都完成后,您应该有一个类似下面的 Postman 请求。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b1775bc938eceec444c4569cc7b66ee4.png) 设置好请求后,您可以单击 Send,将您的请求发送到您的部署中。一旦请求完成处理,您将能够在您的 Postman 页面的底部看到一个响应,它会告诉您模型预测客户会流失(流失)或不会流失(没有流失)。 现在我们已经看到了响应,让我们看看 Arize,看看我们是否能看到请求被接受。如果您导航回 Arize 模型并查看数据摄取选项卡,您应该会看到一个额外的请求。这可能需要几分钟,但如果成功,您应该会看到类似下图的内容 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/fc36dbc308048ad642c107a9394a2fa1.png) 成功!我们的部署正在工作,并将传入的请求和预测记录到 Arize。 为了加快这一过程,我将在下面分享一些观点,就好像我们在几天内记录了 1,300 个请求一样。 首先,我们来看看预测漂移。下面的视图可以在漂移选项卡的分布比较图中找到。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/37ba8b9d228b2fc80ada5506357b73a0.png) 我们可以看到,到目前为止,预测分布与基线预测分布相似,因此这里没有明显的问题。 接下来,让我们看看我们的特性中是否有任何其他数据漂移。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/61d99a3b83d76677a4b29dc0897bb599.png) 对于大多数功能来说,除了总电荷经历了更大的漂移之外,看起来只有很小的漂移。我可以调查 TotalCharges,方法是单击该功能以显示更详细的视图。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/4d458d66dbd80424ec36048cbba4263a.png) Detailed view of TotalCharges 我可以从上面的视图中看到,总电荷的漂移在 8.7 PSI 左右徘徊。我们可以密切关注这一特性,并随着时间的推移监控 PSI,以查看我们在过去 3 小时内是否有异常的数据分布,或者这是否是一个更持久的问题。 本教程到此为止!重点实际上是了解如何将渐变部署与 Arize 集成,但是还有许多其他教程可以让您更深入地了解 Arize,我将在下面链接这些教程。 ## 后续步骤 上面的过程将允许您跟踪传入的请求,并分析您的梯度部署模型中的任何数据或预测漂移。然而,你可以采取很多潜在的后续步骤来增强你的 ML 可观测性系统的健壮性。 您可以创建监视器来跟踪指标,并在超过特定阈值时向您发出警报。当实际结果已知时,您也可以在流程的后期记录实际结果(例如,客户最终是否进行了搅拌)。这将帮助您跟踪您的模型在任何给定时刻的准确性,并允许您查看随时间的变化。 # 结论 太好了!现在,您应该能够构建一个服务于一个模型并与 Arize 集成的梯度部署,以便在生产中观察您的应用程序。 同样,本教程中引用的代码库的链接可以在[这里](https://github.com/gradient-ai/Deployments-Arize-Tutorial)找到。 最后,如果你想看其他使用 Arize 的教程,请点击查看它们的示例页面[。](https://docs.arize.com/arize/examples) # 监控用于深度学习的 GPU 利用率 > 原文:<https://blog.paperspace.com/monitoring-gpu-utilization-in-real-time/> GPU 是大多数用户执行深度和机器学习任务的首要硬件。“GPU 通过并行执行计算来加速机器学习操作。许多运算,尤其是那些可表示为矩阵乘法的运算,开箱即可获得良好的加速。通过调整操作参数来有效利用 GPU 资源,甚至可以获得更好的性能。” [(1)](https://docs.nvidia.com/deeplearning/performance/dl-performance-getting-started/index.html) 在实践中,即使在 GPU 上进行,执行深度学习计算在计算上也是昂贵的。此外,很容易使这些机器过载,触发内存不足错误,因为很容易超出机器解决分配任务的能力范围。幸运的是,GPU 自带内置和外部监控工具。通过使用这些工具来跟踪功耗、利用率和已用内存百分比等信息,用户可以在出现问题时更好地了解问题出在哪里。 ## GPU 瓶颈和障碍 ### CPU 中的预处理 在许多深度学习框架和实现中,在切换到 GPU 进行更高阶处理之前,通常使用 CPU 对数据执行转换。这种预处理可能会占用 65%的历元时间,详见最*的研究。像对图像或文本数据进行转换这样的工作会产生影响性能的瓶颈。在 GPU 上运行这些相同的过程可以提高项目变更的效率,从而缩短培训时间。 #### 什么导致内存不足(OOM)错误? 内存不足意味着 GPU 已经用完了它可以为分配的任务分配的资源。这种错误通常发生在特别大的数据类型,如高分辨率图像,或者批处理太大,或者多个进程同时运行时。它是可以访问的 GPU RAM 数量的函数。 #### OOM 的建议解决方案 * 使用较小的批量。由于迭代是完成一个时期所需的批次数量,降低输入的批次大小将减少 GPU 在迭代期间需要在内存中保存的数据量。这是 OOM 错误最常见的解决方案 * 您正在处理图像数据并对数据执行变换吗?考虑使用类似于 [Kornia](https://github.com/kornia/kornia) 的库来使用你的 GPU 内存执行转换 * 考虑您的数据是如何加载的。考虑使用 DataLoader 对象,而不是一次性加载所有数据,以节省工作内存。它通过组合数据集和采样器来提供给定数据集上的可迭代对象 ## 用于监控性能的命令行工具: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/4b24a097ed6c510bb48d5936fa7a62a5.png) nvidia-smi windows  ### nvidia-smi 代表 nvidia 系统管理接口, [nvidia-smi](https://developer.nvidia.com/nvidia-system-management-interface) 是一个建立在 Nvidia 管理库之上的工具,旨在促进对 Nvidia GPUs 的监控和使用。您可以使用`nvidia-smi`快速打印出一组关于您的 GPU 利用率的基本信息。第一个窗口中的数据包括 GPU 的等级、它们的名称、风扇利用率(尽管这将在梯度上出错)、温度、当前性能状态、您是否处于持续模式、您的功率消耗和上限以及您的总 GPU 利用率。第二个窗口将详细说明特定的进程和进程的 GPU 内存使用情况,例如运行一个培训任务。 #### 使用 nvidia-smi 的提示 * 使用`nvidia-smi -q -i 0 -d UTILIZATION -l 1`显示 GPU 或单元信息('-q '),显示单个指定 GPU 或单元的数据('-i ',我们使用 0,因为它是在单个 GPU 笔记本上测试的),指定利用率数据('-d '),每秒重复一次。这将输出关于您的利用率、GPU 利用率示例、内存利用率示例、ENC 利用率示例和 DEC 利用率示例的信息。这些信息将每秒循环输出,因此您可以实时观察变化。 * 使用标志“-f”或“- filename=”将命令结果记录到特定文件中。 * 点击查看完整文档[。](https://developer.download.nvidia.com/compute/DCGM/docs/nvidia-smi-367.38.pdf) ### 反光 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/7b77578cfe995e80b5092fa26586d0a5.png) Glances 是另一个用于监控 GPU 利用率的神奇库。与`nvidia-smi`不同,在您的终端中输入`glances`会打开一个仪表盘,用于实时监控您的流程。您可以使用此功能来获得许多相同的信息,但是实时更新提供了关于潜在问题可能在哪里的有用见解。除了实时显示 GPU 利用率的相关数据,Glances 还非常详细、准确,并且包含 CPU 利用率数据。 Glances 非常容易安装。在您的终端中输入以下内容: `pip install glances` 然后,要打开控制面板并获得对监控工具的完全访问权限,只需输入: `glances` 点击此处阅读更多 Glances 文档[。](https://glances.readthedocs.io/en/latest/install.html) ### 其他有用的命令 警惕在渐变笔记本上安装其他监控工具。比如`gpustat`和`nvtop`不兼容渐变笔记本。以下是其他一些内置命令,可以帮助您监视计算机上的进程。 这些更侧重于监控 CPU 利用率: * `top` -打印出 CPU 进程和利用率指标 * 告诉你 CPU 使用了多少内存 * `vmstat` -报告有关进程、内存、分页、块 IO、陷阱和 cpu 活动的信息 ## 图纸空间梯度便于实时查看 GPU 性能 <https://blog.paperspace.com/content/media/2022/04/metrics.mp4> Using the Gradient Metrics tab in real time to monitor training GPU utilization 在任何梯度笔记本中,您可以访问关于 GPU 和 CPU 使用指标的绘图。这些是实时更新的,并且可以扩展到 1 分钟到 12 小时的可变范围。可以通过单击笔记本窗口左侧的“指标”图标来访问该窗口。通过单击图标,用户可以访问以下各项的绘图数据: * CPU 使用率:衡量 CPU 在任何给定时间的利用率,占总容量的百分比 * 内存:CPU 在任何给定时间使用的内存量,以 GB 为单位 * GPU 内存(已用):在任何给定时间进程上使用的 GPU 内存量 * GPU 功耗:GPU 在任何给定时间消耗的能量,单位为瓦特 * GPU 温度:单元在任何给定时间的温度,单位为摄氏度 * GPU 利用率:在过去的采样周期中,一个或多个内核在 GPU 上执行的时间百分比 * GPU 内存利用率:在任何给定时间内存控制器忙碌的时间百分比 # 结束语 在本文中,我们看到了如何使用各种工具来监控远程和本地 linux 系统上的 GPU 利用率,以及如何利用 Gradient 笔记本中提供的内置监控工具。 有关 Paperspace 的更多信息,请阅读我们的[文档](https://docs.paperspace.com/)或访问我们的[主页](https://www.paperspace.com/),立即开始使用强大的云 GPU! # 基于 TensorFlow 的电影推荐系统 > 原文:<https://blog.paperspace.com/movie-recommender-tensorflow/> 我们在 [YouTube](https://www.youtube.com/) (或[网飞](netflix.com))上观看一个视频(或电影),然后马上就会看到一个建议接下来观看的视频(或电影)列表,这种情况并不少见。同样的事情也经常发生在数字音乐流媒体服务上。一个人在 [Spotify](https://open.spotify.com/) 上听一首歌,马上就会找到一个类似歌曲的列表,可能是同一流派或同一艺术家的作品。 该列表由推荐机器学习模型构建,该模型通常被称为推荐引擎/系统。推荐系统不仅仅是简单的机器学习。需要建立一个数据管道来收集模型所需的输入数据(例如,像用户观看的最后五个视频这样的输入)。推荐系统满足了这一需求。 一个主要的误解是推荐系统只是向用户推荐产品。这与事实相去甚远。推荐系统不仅可以向用户推荐产品,还可以向用户推荐产品。例如,在营销应用程序中,当有新的促销活动时,推荐系统可以找到前一千个最相关的当前客户。这叫做瞄准。此外,与谷歌地图通过推荐系统建议避开收费公路的路线一样,Gmail 中的智能回复也是通过推荐系统来建议对刚收到的电子邮件的可能回复。搜索引擎是推荐引擎如何提供个性化的另一个很好的例子。您的搜索查询会考虑您的位置、您的用户历史、帐户偏好和以前的搜索,以确保您获得的内容与用户最相关。 例如,在搜索栏中输入“*giants”*可能会产生不同的结果,这取决于用户所在的位置。如果用户在纽约,很可能会得到很多纽约巨人队的结果。但是,在旧金山进行相同的搜索可能会返回关于旧金山棒球队的信息。本质上,从用户的角度来看,推荐系统可以帮助找到相关的内容,探索新的项目,并改善用户的决策。从生产者的角度来看,它有助于提高用户参与度,了解更多的用户,并监测用户行为的变化。总之,推荐系统都是关于个性化的。这意味着要有一个适合所有人的产品,并为个人用户进行个性化定制。 # 推荐系统的类型 ## 基于内容的过滤: 在这种类型的推荐框架中,我们利用系统中可用的产品元数据。假设一个用户观看并评价了几部电影。他们给了一些人一个大拇指,给了一些人一个大拇指,我们想知道数据库中的哪部电影是下一部推荐的。 由于我们有关于电影的元数据,也许我们知道这个特定用户更喜欢科幻而不是情景喜剧。因此,使用这种系统,我们可以利用该数据向该客户推荐受欢迎的科幻节目。其他时候,我们没有每个用户的偏好。要创建一个基于内容的推荐系统,我们可能需要的只是一个市场细分,显示世界不同地区的用户喜欢哪些电影。有观点认为这里不涉及机器学习。这是一个简单的规则,它依赖于推荐系统的创建者来适当地标记人和对象。这种方法的主要缺点是,为了使这个系统正确工作,它需要领域知识。虽然存在解决这种“冷启动”问题的方法,但是没有什么方法可以完全克服缺乏培训信息的影响。此外,由于其性质,该系统仅提供安全建议。 ## 协作过滤: 在这种方法中,我们没有任何关于产品的元数据;相反,我们可以从评级数据中推断出关于项目和用户相似性的信息。例如,我们可能需要将用户的电影数据保存在一个带有复选标记的矩阵中,以指示用户是否观看了整部电影,是否留下了评论,是否给了它一个星级,或者您用来确定某个用户是否喜欢某部电影的任何内容。正如你所料,这个矩阵的规模是巨大的。一个人只能看到这些电影中的一小部分,因为可能有数百万或数十亿人和数百或数百万部电影可用。因此,大多数矩阵既庞大又稀疏。 为了逼*这个庞大的用户-项目矩阵,协同过滤结合了两个更小的矩阵,称为用户因素和项目因素。然后,如果我们想确定一个特定的用户是否会喜欢某部电影,我们所要做的就是获取与该电影相对应的行,并将它们相乘以获得预测的评级。然后,我们选择我们认为收视率最高的电影,然后推荐给消费者。 协同过滤最大的好处是我们不需要熟悉任何项目的元数据。此外,只要我们有一个互动矩阵,我们就可以做得很好,不需要对你的用户进行市场细分。也就是说,问题可能源于特性的稀疏性和无上下文特性。 ## 基于知识的建议: 在这种类型的推荐系统中,数据取自用户调查或用户输入的显示其偏好的设置。这通常通过询问用户的偏好来实现。基于知识的推荐的一个很大的好处是它不需要用户-项目交互数据。相反,它可以简单地依靠以用户为中心的数据将用户与其他用户联系起来,并推荐那些用户喜欢的类似东西。此外,基于知识的推荐最终使用高保真数据,因为感兴趣的用户已经自我报告了他们的信息和偏好。因此,假设这些都是真的是公*的。然而,另一方面,当用户不愿意分享他们的偏好时,可能会出现一个重大的挑战。由于隐私问题,缺少用户数据可能是一个问题。由于这些隐私问题,尝试基于知识以外的推荐方法可能更容易。 # 演示 例如,在本教程中,您将使用 TensorFlow 创建一个实际操作的电影推荐系统。TensorFlow 的核心是允许您使用 Python(或 JavaScript)开发和训练模型,并且无论您使用何种编程语言,都可以轻松地在云中、本地、浏览器或设备上部署。我们将使用 Papersapce Gradient 的免费 GPU 笔记本来进行这次演示。 在我们继续之前,需要注意的是现实世界的推荐系统通常由两个阶段组成: * **检索阶段**:该阶段用于从所有可能的电影候选者中选择电影候选者的初始集合。该模型的主要目的是有效地驱逐用户不感兴趣的所有候选人。检索阶段通常使用协同过滤。 * 排名阶段:这一阶段从检索模型中获取输出,并对其进行微调,以选择尽可能多的最佳电影推荐。它的任务是将用户可能感兴趣的电影集缩小到可能的候选名单中。 **检索模型** 与所有使用协同过滤的推荐系统一样,这些模型通常由两个子模型组成: 1. 使用特征计算查询表示(通常是固定维度的嵌入向量)的查询模型。 2. 一种候选模型,使用影片的特征来计算影片候选表示(大小相等的向量)。 然后,将两个模型的输出相乘,以给出查询候选相似性分数,较高的分数表示电影候选和查询之间的较好匹配。在本教程中,我们将使用带有 TensorFlow 的 Movielens 数据集来构建和训练一个推荐系统。 [Movielens 数据集](https://grouplens.org/datasets/movielens/)是来自 [GroupLens](https://grouplens.org/datasets/movielens/) 研究小组的数据集。它包含一组用户对电影的评级,这些用户是在不同的时间段收集的,具体取决于集合的大小。这是推荐系统研究中的一个热点。 这个数据可以从两个方面来看。可以解释为用户看了哪些电影(以及评级),没有看哪些电影。它也可以被看作是用户有多喜欢他们观看的电影。第一种观点将数据集视为一种隐性反馈,用户的观看历史告诉我们他们更喜欢看什么,不喜欢看什么。后一种观点可以将数据集转化为一种明确的反馈形式,通过查看用户给出的评级,可以大致了解观看电影的用户喜欢这部电影的程度。 对于检索系统,模型从用户可能观看的目录中预测一组电影,隐式数据在传统上更有用。因此,我们将电影镜头视为一个隐含系统。本质上,用户看过的每一部电影都是正面例子,没看过的每一部电影都是隐含的反面例子。 让我们开始吧。 第一步:导入必要的库。 ```py !pip install -q tensorflow-recommenders !pip install -q --upgrade tensorflow-datasets !pip install -q scann import os import pprint import tempfile from typing import Dict, Text import numpy as np import tensorflow as tf import tensorflow_datasets as tfds import tensorflow_recommenders as tfrs ``` 第二步:获取你的数据,并将其分成训练集和测试集。 ```py # Ratings data. ratings = tfds.load("movielens/100k-ratings", split="train") # Features of all the available movies. movies = tfds.load("movielens/100k-movies", split="train") ``` 变量`ratings`包含分级数据,而变量`movies`包含所有可用电影的特征。分级数据集返回电影 id、用户 id、分配的分级、时间戳、电影信息和用户信息的字典,如下所示。而电影数据集包含电影 id、电影标题和关于其所属类型的数据。这些类型用整数标签编码。值得注意的是,由于 Movielens 数据集没有预定义的分割,因此它的所有数据都在`train`分割下。 ```py ratings = ratings.map(lambda x: { "movie_title": x["movie_title"], "user_id": x["user_id"], }) movies = movies.map(lambda x: x["movie_title"]) ``` 在本教程中,您将关注评级数据本身,您将只保留评级数据集中的`user_id`和`movie_title`字段。 ```py tf.random.set_seed(42) shuffled = ratings.shuffle(100_000, seed=42, reshuffle_each_iteration=False) train = shuffled.take(80_000) test = shuffled.skip(80_000).take(20_000) ``` 为了拟合和评估模型,我们将把它分成一个训练集和一个评估集。我们将使用随机分割,将 80%的评级放在训练集中,20%放在测试集中。 此时,我们希望知道数据中出现的唯一用户 id 和电影标题。这很重要,因为我们需要能够将分类特征的原始值映射到模型中的嵌入向量。为了实现这一点,我们需要一个词汇表,将原始特征值映射到一个连续范围内的整数:这允许我们在嵌入表中查找相应的嵌入。 ```py movie_titles = movies.batch(1_000) user_ids = ratings.batch(1_000_000).map(lambda x: x["user_id"]) unique_movie_titles = np.unique(np.concatenate(list(movie_titles))) unique_user_ids = np.unique(np.concatenate(list(user_ids))) ``` 第三步:实现一个检索模型。 ```py embedding_dimension = 32 user_model = tf.keras.Sequential([ tf.keras.layers.StringLookup( vocabulary=unique_user_ids, mask_token=None), tf.keras.layers.Embedding(len(unique_user_ids) + 1, embedding_dimension) ]) ``` 假设嵌入维度的较高值将对应于可能更准确的模型,但是也将更慢地拟合并且更易于过拟合,32 被挑选为查询和候选表示的维度。为了定义模型本身,`keras`预处理层将用于将用户 id 转换成整数,然后使用`Embedding`层将它们转换成用户嵌入。 我们将对电影《候选人之塔》做同样的事情。 ```py movie_model = tf.keras.Sequential([ tf.keras.layers.StringLookup( vocabulary=unique_movie_titles, mask_token=None), tf.keras.layers.Embedding(len(unique_movie_titles) + 1, embedding_dimension) ]) ``` 在训练数据中,我们注意到我们有积极的用户和电影对。为了评估我们的模型以及它有多好,我们将比较模型为这一对计算的亲和力分数和所有其他可能的候选人的分数。这意味着,如果阳性对的得分高于所有其他候选项,则您的模型是高度准确的。为了检查这一点,我们可以使用 [`tfrs.metrics.FactorizedTopK`](https://www.tensorflow.org/recommenders/api_docs/python/tfrs/metrics/FactorizedTopK) 度量。这个指标有一个必需的参数:候选人的数据集,您将其用作评估的隐式否定。这意味着您将通过电影模型转换成嵌入的`movies`数据集。 ```py metrics = tfrs.metrics.FactorizedTopK( candidates=movies.batch(128).map(movie_model) ) ``` 此外,我们必须检查用于训练模型的损失。好东西`tfrs`为此有几个损耗层和任务。我们可以使用`Retrieval`任务对象,它是一个方便的包装器,将损失函数和度量计算与下面几行代码捆绑在一起。 ```py task = tfrs.tasks.Retrieval( metrics=metrics ) ``` 完成所有设置后,我们现在可以将它们放入一个模型中。`tfrs.models.Model`,`tfrs`的基础模型类将用于简化建筑模型。 [`tfrs.Model`](https://www.tensorflow.org/recommenders/api_docs/python/tfrs/models/Model) 基类的存在使得我们可以使用相同的方法计算训练和测试损失。我们需要做的就是在`__init__`方法中设置组件,然后使用原始特性实现`compute_loss`方法并返回一个损失值。此后,我们将使用基础模型创建适合您的模型的适当培训循环。 ```py class MovielensModel(tfrs.Model): def __init__(self, user_model, movie_model): super().__init__() self.movie_model: tf.keras.Model = movie_model self.user_model: tf.keras.Model = user_model self.task: tf.keras.layers.Layer = task def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor: user_embeddings = self.user_model(features["user_id"]) positive_movie_embeddings = self.movie_model(features["movie_title"]) return self.task(user_embeddings, positive_movie_embeddings) ``` `compute_loss`方法从挑选出用户特征开始,然后将它们传递到用户模型中。此后,它挑选出电影特征,并把它们传递到电影模型中,从而取回嵌入内容。 第四步:拟合和评估。 定义模型后,我们将使用标准的 Keras 拟合和评估例程来拟合和评估模型。 ```py model = MovielensModel(user_model, movie_model) model.compile(optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.1)) cached_train = train.shuffle(100_000).batch(8192).cache() cached_test = test.batch(4096).cache() model.fit(cached_train, epochs=3) ``` 我们的模型将在三个时期内被训练。我们可以看到,随着模型的训练,损失下降,一组 top-k 检索指标正在更新。这些度量让我们知道真正的肯定是否在整个候选集的前 k 个检索项目中。请注意,在本教程中,我们将在培训和评估过程中评估指标。因为对于大型候选集,这可能会非常慢,所以谨慎的做法是在培训中关闭度量计算,仅在评估中运行它。 最后,我们可以在测试集上评估我们的模型: ```py model.evaluate(cached_test, return_dict=True) ``` 我们应该注意到,测试集性能不如训练集性能好。理由并不牵强。我们的模型在处理之前看到的数据时会表现得更好。此外,该模型只是重新推荐用户已经看过的一些电影。 **第五步:做预测** 既然我们已经有了一个运行的模型,我们就可以开始做预测了。为此我们将使用 [`tfrs.layers.factorized_top_k.BruteForce`](https://www.tensorflow.org/recommenders/api_docs/python/tfrs/layers/factorized_top_k/BruteForce) 图层。我们将使用它来获取原始查询特性,然后从整个电影数据集中推荐电影。最后,我们得到我们的建议。 ```py index = tfrs.layers.factorized_top_k.BruteForce(model.user_model) index.index_from_dataset( tf.data.Dataset.zip((movies.batch(100), movies.batch(100).map(model.movie_model))) ) _, titles = index(tf.constant(["46"])) print(f"Recommendations for user 46: {titles[0, :3]}") ``` 在上面的代码块中,我们将获得对用户 46 的推荐。 **步骤 6:通过建立*似最*邻(ANN)索引,导出它以进行有效的服务。** 直观地说,`BruteForce`层太慢了,不能服务于有许多候选对象的模型。使用*似检索索引可以加快这个过程。虽然检索模型中的服务有两个组件(即服务查询模型和服务候选模型),但使用`tfrs`,这两个组件可以打包成我们可以导出的单个模型。该模型获取原始用户 id,并为该用户返回热门电影的标题。为此,我们将把模型导出到一个`SavedModel`格式,这样就可以使用 [TensorFlow 服务](https://www.tensorflow.org/tfx/guide/serving)了。 ```py with tempfile.TemporaryDirectory() as tmp: path = os.path.join(tmp, "model") tf.saved_model.save(index, path) loaded = tf.saved_model.load(path) scores, titles = loaded(["42"]) print(f"Recommendations: {titles[0][:3]}") ``` 为了有效地从数以百万计的候选电影中获得推荐,我们将使用一个可选的 TFRS 依赖项,称为 TFRS `scann`层。这个包是在教程开始时通过调用`!pip install -q scann`单独安装的。该层可以执行 ***似** 查找,这将使检索稍微不太准确,同时在大型候选集上保持数量级的速度。 ```py scann_index = tfrs.layers.factorized_top_k.ScaNN(model.user_model) scann_index.index_from_dataset( tf.data.Dataset.zip((movies.batch(100), movies.batch(100).map(model.movie_model))) ) _, titles = scann_index(tf.constant(["42"])) print(f"Recommendations for user 42: {titles[0, :3]}") ``` 最后,我们将导出查询模型,保存索引,将其加载回来,然后传入一个用户 id 以获取热门预测电影标题。 ```py with tempfile.TemporaryDirectory() as tmp: path = os.path.join(tmp, "model") tf.saved_model.save( index, path, options=tf.saved_model.SaveOptions(namespace_whitelist=["Scann"]) ) loaded = tf.saved_model.load(path) scores, titles = loaded(["42"]) print(f"Recommendations: {titles[0][:3]}") ``` \ ### 排名模型 使用排序模型,前两步(即导入必要的库并将数据分成训练集和测试集)与检索模型完全相同。 **第三步:实现排名模型** 使用排序模型,所面临的效率约束与检索模型完全不同。因此,我们在选择架构时有更多的自由。由多个堆叠的密集层组成的模型通常用于分级任务。我们现在将按如下方式实现它: 注意:该模型将用户 id 和电影名称作为输入,然后输出预测的评级。 ```py class RankingModel(tf.keras.Model): def __init__(self): super().__init__() embedding_dimension = 32 # Compute embeddings for users self.user_embeddings = tf.keras.Sequential([ tf.keras.layers.StringLookup( vocabulary=unique_user_ids, mask_token=None), tf.keras.layers.Embedding(len(unique_user_ids) + 1, embedding_dimension) ]) # Compute embeddings for movies self.movie_embeddings = tf.keras.Sequential([ tf.keras.layers.StringLookup( vocabulary=unique_movie_titles, mask_token=None), tf.keras.layers.Embedding(len(unique_movie_titles) + 1, embedding_dimension) ]) # Compute predictions self.ratings = tf.keras.Sequential([ # Learn multiple dense layers. tf.keras.layers.Dense(256, activation="relu"), tf.keras.layers.Dense(64, activation="relu"), # Make rating predictions in the final layer. tf.keras.layers.Dense(1) ]) def call(self, inputs): user_id, movie_title = inputs user_embedding = self.user_embeddings(user_id) movie_embedding = self.movie_embeddings(movie_title) return self.ratings(tf.concat([user_embedding, movie_embedding], axis=1)) ``` 为了评估用于训练我们的模型的损失,我们将使用将损失函数与度量计算相结合的`Ranking`任务对象。我们将它与`MeanSquaredError` Keras 损失一起使用,以预测收视率。 ```py task = tfrs.tasks.Ranking( loss = tf.keras.losses.MeanSquaredError(), metrics=[tf.keras.metrics.RootMeanSquaredError()] ) ``` 将所有这些放入一个完整的排名模型中,我们有: ```py class MovielensModel(tfrs.models.Model): def __init__(self): super().__init__() self.ranking_model: tf.keras.Model = RankingModel() self.task: tf.keras.layers.Layer = tfrs.tasks.Ranking( loss = tf.keras.losses.MeanSquaredError(), metrics=[tf.keras.metrics.RootMeanSquaredError()] ) def call(self, features: Dict[str, tf.Tensor]) -> tf.Tensor: return self.ranking_model( (features["user_id"], features["movie_title"])) def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor: labels = features.pop("user_rating") rating_predictions = self(features) return self.task(labels=labels, predictions=rating_predictions) ``` **第四步:拟合和评估排名模型** 定义模型后,我们将使用标准的 Keras 拟合和评估例程来拟合和评估您的排名模型。 ```py model = MovielensModel() model.compile(optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.1)) cached_train = train.shuffle(100_000).batch(8192).cache() cached_test = test.batch(4096).cache() model.fit(cached_train, epochs=3) model.evaluate(cached_test, return_dict=True) ``` 利用在三个时期上训练的模型,我们将通过计算对一组电影的预测来测试排名模型,然后基于预测对这些电影进行排名: ```py test_ratings = {} test_movie_titles = ["M*A*S*H (1970)", "Dances with Wolves (1990)", "Speed (1994)"] for movie_title in test_movie_titles: test_ratings[movie_title] = model({ "user_id": np.array(["42"]), "movie_title": np.array([movie_title]) }) print("Ratings:") for title, score in sorted(test_ratings.items(), key=lambda x: x[1], reverse=True): print(f"{title}: {score}") ``` **第五步:导出服务,将模型转换为 TensorFlow Lite** 如果一个推荐系统不能被用户使用,那么它是没有用的。因此,我们必须导出模型来提供服务。此后,我们可以加载它并执行预测。 ```py tf.saved_model.save(model, "export") loaded = tf.saved_model.load("export") loaded({"user_id": np.array(["42"]), "movie_title": ["Speed (1994)"]}).numpy() ``` 为了更好地保护用户隐私和降低延迟,我们将使用 TensorFlow Lite 在设备上运行经过训练的排名模型,尽管 TensorFlow 推荐器主要用于执行服务器端推荐。 ```py converter = tf.lite.TFLiteConverter.from_saved_model("export") tflite_model = converter.convert() open("converted_model.tflite", "wb").write(tflite_model) ``` **结论** 我们现在应该知道推荐器是什么,它是如何工作的,隐式和显式反馈之间的区别,以及如何使用协同过滤算法来构建推荐系统。在我们自己,我们可以调整网络设置,如隐藏层的尺寸,以查看相应的变化。根据经验,这些维度取决于您想要*似的函数的复杂性。如果隐藏层太大,我们的模型会有过度拟合的风险,从而失去在测试集上很好地概括的能力。另一方面,如果隐藏层太小,神经网络将缺少参数来很好地拟合数据。 # 基于 LIBSVM 的多类分类 > 原文:<https://blog.paperspace.com/multi-class-classification-using-libsvm/> **简介** 你有没有想过训练一个机器学习(ML)模型,将数据分成多个类别?例如,根据流派对电影进行分类,或者根据品牌名称对汽车进行分类。在本文中,我将介绍如何训练这样一个模型,使用称为支持向量机的监督 ML 算法将数据分类到多个类别中。 你可能会想,什么是支持向量机? 支持向量机(SVM)是一种受监督的机器学习算法,它使用核函数来创建由边距分隔的不同数据点组。它基于支持向量,支持向量是靠*页边的数据点,影响该页边的位置以及页边周围其他数据点的位置。如果有多个类,则可能有多个余量,但是如果向量只被分成两组,则只有一个余量。 本文将从引言部分开始讨论更多关于分类的内容,更具体地说是多类分类问题。然后,我将解释一般的 SVM 算法,然后重点介绍演示代码中使用的特定 SVM 包。在代码部分之后,我将分享一些额外的技巧,以帮助提高您的模型的性能,以及算法的一些假设和限制。最后,我将结束这篇文章,然后你可以自己去尝试一下! **什么是分类?** 分类是一种将结果分组到类别标签下的问题。输入数据被给定,并且这些输入特征组合以产生作为输出的标签预测。分类问题通常用监督机器算法来解决。监督机器学习算法要么解决回归问题,要么解决分类问题。回归是预测一个数字或一个连续值作为结果,而分类给出一个标签或一个离散值作为结果。 在分类问题中,我们使用两种类型的算法(取决于它创建的输出类型): * 类输出算法:像 SVM 和 KNN 这样的算法创建一个类输出。例如,在二进制分类问题中,输出将是 0 或 1。 * 概率输出算法:像逻辑回归、随机森林、梯度增强、Adaboost 等算法。给出概率输出。通过创建阈值概率,可以将概率输出转换为类输出。如果该概率输出小于阈值(例如,0.5),则它被赋予预测值 0,如果原始输出值大于阈值,则预测值为 1。 分类问题可以是二元分类,也可以是多类分类。顾名思义,二进制分类就是只有两种可能的类别作为标签。相比之下,多类分类有多个标签(尽可能多)作为可能的结果。一些例子是如何将棋子的颜色分类为黑色或白色(二元分类),而将棋子的类型分类为国王、王后、骑士、主教、车或卒。 在本文中,我将使用 SVM 解决一个多类分类问题。 **关于 SVM** SVM,意为支持向量机,是一种有监督的机器学习算法。为什么叫被监督?这是因为该算法为这些数据点中的每一个接受训练特征和正确的标签,训练这些数据,然后尝试在看不见的数据上准确预测正确的标签。这种看不见的数据也称为测试数据。它是模型不在其上训练的数据集的子集,并且用于评估 ML 模型的性能。 SVM 是一种用于解决分类问题的算法。虽然不太常见,但它也可以用来解决回归和异常值问题。在 SVM 算法中,应用核函数进行精确预测。核函数是一种特殊的数学函数,它将数据作为输入,并将其转换为所需的形式。这种数据转换基于一种叫做内核技巧的东西,这就是内核函数的名字。使用核函数,我们可以将不可线性分离的数据(不能使用直线分离)转换为可线性分离的数据。 对于二进制分类,当从核函数得到的原始输出> = 0 时,算法预测 1,否则预测 0。但是,这样做的成本计算是,如果实际值为 1,而模型预测> = 1,则根本没有成本。但是如果预测值< 1, the cost increases as the value of the raw output from the model decreases. You may be wondering, if the raw output > = 0 预测为 1,为什么成本从 1 及以下开始增加,而不是从 0 开始增加?这是因为 SVM 惩罚不正确的预测和接*决策边界的预测。实际值为 1 时,判定边界是 0 和 1 之间的值,包括 0 和 1。正好落在边缘和决策边界上的值称为支持向量。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/5e7035bafc0359ca8446039ace47eac5.png) 在 SVM,有四种类型的内核函数: * 线性核 * 多项式核 * 径向基核 * Sigmoid 内核 SVM 的目标职能: * 铰链损耗:损耗函数测量预测和实际标签之间的误差。这是一种从训练数据到看不见的数据来衡量模型性能的方法。铰链损失是用于 SVM 的损失函数。 * SVM 成本函数:成本函数是对多个数据点求和的目标函数。例如,它可以是训练集上损失函数的总和。 为什么是 SVM? 最后一节详细解释了 SVM 算法。但是,您可能仍然想知道为什么要在下一个多分类问题中使用这个算法。以下是使用 SVM 训练模型的一些优势: * 训练 SVM 所需的观察次数并不多,因此需要的训练数据较少。 * SVM 可以处理线性和非线性决策边界。它也是参数化的,大多数时候具有很高的精度。 * 在高维数据中表现良好。即数据集中要素的数量接*或大于观测值的数据。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/611be4854bb1edd507a9663a0656b9fc.png) **使用支持向量机的假设** 支持向量机的一些有助于良好性能的假设是: 1. 数据集是独立的,并且相同地分布在各个类中。这意味着每个类别中的数据点之间有明显的区别。 2. 数据没有太多的噪音和异常值。 **LIBSVM** 一个流行的 SVM 包是 LIBSVM 包。 [LIBSVM](https://www.csie.ntu.edu.tw/~cjlin/libsvm/) 是支持向量机分类、回归和多类分类的集成包。在下面的笔记本中,我将使用 LIBSVM 为 [UCI 葡萄酒数据集构建一个分类器模型。](http://archive.ics.uci.edu/ml/datasets/Wine) LIBSVM 构建在 C++和 Java 源代码上,但也有许多其他语言的接口,如 Python、MATLAB、R、Haskell、Perl、PHP 等。 我将在 Jupyter 笔记本中使用 Python 接口;通过从 [GitHub](https://github.com/cjlin1/libsvm) 克隆它并构建包来导入它。 **LIBSVM 的参数** 1. **SVM 类型** : LIBSVM 支持支持向量分类(SVC)问题,二元和多类,例如 C-SVC 和 nu-SVC,支持向量回归(SVR)问题,例如 epsilon-SVR 和 nu-SVR,以及单类 SVM。 * 代表成本的 c 是一个正则化参数。它是为 C-SVC、epsilon-SVR 和 nu-SVR SVM 设置的。它应该总是正数,默认值为 1.0 * Nu 是用于控制支持向量数量的参数。它通过设置误差分数的上限和支持向量分数的下限来实现这一点。默认值为 0.5,并且应该始终在区间[0,1]内 2. **内核类型**:可以通过 LIBSVM 应用的不同类型的内核有线性、多项式、径向基函数(RBF)和 sigmoid 内核。一般来说,使用 SVM 时,RBF 核是合理的首选。这是因为它是一个非线性内核,所以它可以处理类标签和属性之间的关系不是线性的情况。 3. **其他超参数**:其中一些包括伽玛、次数和核函数的系数等。这些的完整列表可以在[官方文件中找到。](https://www.csie.ntu.edu.tw/~cjlin/libsvm/) **如何使用 LIBSVM 分八步将数据分类为多个类:** 1. **获取数据,进行 EDA,了解数据集**:读取数据集,进行探索性数据分析,了解数据集中的数据。在这个演示中,我将使用 UCI 葡萄酒数据集。这是一个包含 178 条记录和 13 个要素的多类分类数据集。数据集中有三类葡萄酒作为标签。目的是利用化学分析来确定葡萄酒的原产地。链接到笔记本单元格: ```py # Data from http://archive.ics.uci.edu/ml/datasets/Wine !git clone https://gist.github.com/tijptjik/9408623 # Read in as a dataframe for EDA wine_df = pd.read_csv('9408623/wine.csv') print(wine_df.head()) print(wine_df.describe()) print(wine_df.columns) print(wine_df.dtypes) # Read in data as an array to begin analysis. Drop header row as it is # not needed for svm classification. wine_data = np.genfromtxt('9408623/wine.csv', delimiter=',') wine_data = wine_data[1:] print(wine_data[0]) ``` 2. **数据预处理,准备 LIBSVM 格式**: 1. SVM 数据只接受数字数据。如果您有分类数据,如文本、日期或时间,您必须将它们转换成数值。 2. 此外,SVM 在处理大范围数据时表现不佳。对于一个性能良好的模型,你必须调整你的数据。LIBSVM 的好处是有一个可执行文件来缩放数据。我将在第 4 步中详细讨论这一点。 3. 解析数据时,请确保没有尾随的新行(' \n '),因为这可能会在缩放和定型时导致错误。 4. 最后,对于数据集中的每一行,LIBSVM 的格式是 label index:feature… label 1:feature 1 _ value 2:feature 2 _ value 3:feature 3 _ value 等等。一个例子是: 2 1: 0.22 2: 0.45 … 其中 2 是标签,而 1,2,…...用相应的值描述要素的索引。 5. 标签和索引的数据类型是整数,而特征值可以是整数或浮点数。 6. 在您的数据集中,即使在标签后面作为注释,也不应该有冒号(:)。这是因为模型将冒号作为一个特征,因此,它将抛出一个错误,因为它不是。唯一允许的冒号位于索引和该索引的特征值之间。 ```py # Adding count, beginning from 1, to features for each row with_features_index = [] data_with_features_index = [] for j in range(len(wine_data)): each_row = wine_data[j,0:] with_features_index.append(each_row[0]) for l in range(1,14): with_features_index.append(str(l)+ ":" + str(each_row[l])) data_with_features_index.append(with_features_index) with_features_index = [] ``` 3. **将数据分割成训练和测试数据集**:现在我们已经读取了数据,探索了数据以更好地理解它,进行了预处理,并将其重新格式化为 LIBSVM 格式,下一步是将数据集分割成训练和测试数据集。因为我将执行交叉验证,所以我选择了 0.33 的测试大小,以允许更大的训练数据集。 ```py # Split into train and test datasets train_data, test_data = train_test_split(data_with_features_index, test_size=0.33, random_state=42) # Convert to Numpy array train_data = np.asarray(train_data) test_data = np.asarray(test_data) # Get y_label from test data to compare against model result later y_label = np.array(test_data[:,0], dtype=np.float32) y_label # Save train and test data to files np.savetxt('wine_svm_train_data', train_data, delimiter=" ", fmt="%s") np.savetxt('wine_svm_test_data', test_data, delimiter=" ", fmt="%s") ``` 4. **缩放数据**:如第 2 步所述,您希望在构建 SVM 模型时缩放数据。这是因为 SVM 是一种基于测量数据点相距多远并对其进行分类的算法。另一个类似的算法是 k-最*邻(KNN)。缩放还有助于避免较大数值范围内的属性支配较小数值范围内的属性。缩放数据时,应对训练数据集和测试数据集使用相同的缩放范围文本。因此,当您缩放训练数据集时,您保存训练数据的范围文本,然后加载范围文本并使用它来缩放测试数据。这是为了使两个数据集以完全相同的方式缩放。我把数据换算成[-1,1]。这些值在 svm-scale 命令中作为-l 和-u 参数传递,l 表示下限,u 表示上限。使用我们正在使用的 RBF 内核,在[0,1]和[-1,1]之间缩放数据执行相同的操作,因此您可以使用您喜欢的任何一个。然而,即使性能相同,计算时间也可能不同,所以这是可以试验的。如果有许多零条目,与[-1,1]相反,缩放[0,1]可以保持数据的稀疏性,并可以减少计算时间。 5. **进行交叉验证**:我使用了 LIBSVM grid.py 脚本来处理交叉验证。交叉验证是将训练数据子集化为 k 个验证数据集以找到模型的正确超参数的过程。我将交叉验证的折叠次数设置为 5 次。这是通过 grid.py 命令中的-v 参数设置的。Grid.py 还具有使用 gnuplot 为交叉验证的每组超参数绘制精确度等值线的功能。然而,由于 gnuplot 在不同系统上的安装和访问障碍,我没有使用它。这是为了保持笔记本的再现性。但是,如果您想尝试一下,您可以在笔记本上查看这个关于如何获取 gnuplot 的资源库,然后用 gnuplot 可执行文件的路径替换 grid.py 命令中的“null”值。我调整的超参数是 [C 和 g 参数](https://scikit-learn.org/stable/auto_examples/svm/plot_rbf_parameters.html#:~:text=The%20gamma%20parameters%20can%20be,of%20the%20decision%20function's%20margin.): * C: (cost) - C 是在 C-SVC 中防止过拟合和控制边界误差的正则化参数。默认值为 1。较低的 C 值给出了更简单的决策函数,代价是训练精度。这允许在决策边界中有更多的值;与给出较窄决策边界的较高 C 值相反。 * g: (gamma) -在内核函数中设置 gamma(默认为 1/num_features)。较低的 g 值给出了不能完全捕捉数据集的全部复杂性的受限模型。这很容易导致较高的方差和过度拟合,尽管 C 是管理这种情况的正则化参数。 6. **训练模型**:交叉验证后,我用得到的最好的超参数训练模型。这个模型被保存到一个模型文件中,该文件被加载以对测试数据(看不见的数据)进行预测。这是通过从 LIBSVM 包中调用 svm-train 可执行文件来完成的。我将缩放、训练和测试过程耦合到一个函数中。因此,您可以通过调用 svm_model 函数来执行这些步骤,使用步骤 3 中的训练和测试数据集的文件名作为参数。 ```py # Function to scale, train, and test model def svm_model(train_pathname, test_pathname): # Validate that train and test files exist assert os.path.exists(train_pathname),"training file not found" file_name = os.path.split(train_pathname)[1] # Create files to store scaled train data, range metadata for scaled data, and trained model scaled_file = file_name + ".scale" model_file = file_name + ".model" range_file = file_name + ".range" # store scale range for train data to be used to scale test data file_name = os.path.split(test_pathname)[1] assert os.path.exists(test_pathname),"testing file not found" # Create file for scaled test data and predicted output scaled_test_file = file_name + ".scale" predict_test_file = file_name + ".predict" # Scaling by range [-1, 1] cmd = '{0} -l {4} -u {5} -s "{1}" "{2}" > "{3}"'.format(svmscale_exe, range_file, train_pathname, scaled_file, -1, 1) print('Scaling train data') Popen(cmd, shell = True, stdout = PIPE).communicate() # Tuning c and g hyperparameters using a 5-fold grid search cmd = '{0} -v {4} -svmtrain "{1}" -gnuplot "{2}" "{3}"'.format(grid_py, svmtrain_exe, "null", scaled_file, 5) print('Cross validation') f = Popen(cmd, shell = True, stdout = PIPE).stdout line = '' while True: last_line = line line = f.readline() if not line: break c,g,rate = map(float,last_line.split()) print('Best c={0}, g={1} CV rate={2}'.format(c,g,rate)) cmd = '{0} -c {1} -g {2} "{3}" "{4}"'.format(svmtrain_exe,c,g,scaled_file,model_file) print('Training model') Popen(cmd, shell = True, stdout = PIPE).communicate() print('Output model: {0}'.format(model_file)) cmd = '{0} -l {4} -u {5} -r "{1}" "{2}" > "{3}"'.format(svmscale_exe, range_file, test_pathname, scaled_test_file, -1, 1) print('Scaling test data') Popen(cmd, shell = True, stdout = PIPE).communicate() cmd = '{0} "{1}" "{2}" "{3}"'.format(svmpredict_exe, scaled_test_file, model_file, predict_test_file) print('Testing model\n') f = Popen(cmd, shell = True, stdout = PIPE).stdout result = (str(f.readline()).replace("\\n'", '')).replace("b'", '') print("{} \n".format(result)) print('Output prediction: {0}'.format(predict_test_file)) ``` 7. **在测试数据上测试模型**:为了测试模型,使用保存的模型、c 和 g 的最佳超参数值以及缩放的测试数据调用 svm-predict 可执行命令。其输出存储在预测文件中,是测试数据集中每一行的预测类的数组。对于这个问题,我将使用准确性作为性能的衡量标准。由于问题的性质、葡萄酒产地的分类以及不同类别的计数不会不*衡,因此准确性是衡量该演示性能的一个很好的标准。然而,如果你的类不*衡,准确性可能不是最好的度量。此外,对于某些问题,精确度、召回率或新颖性,或者其他一些度量标准可以更好地洞察该问题的模型性能,所以不要觉得有义务总是使用精确度。该模型的测试准确率为 98.3051%,数据集中 59 个数据点中的 58 个数据点被正确分类。这是如此出色的性能,我决定不对模型或超参数做进一步的调整。 ```py svm_model(wine_svm_data_train_file, wine_svm_data_test_file) ``` 8. **评估模型的性能**:为了评估模型,我测试了在看不见的(测试)数据上用最佳超参数训练的模型,并使用 scikit-learn 绘制混淆矩阵。从混淆矩阵来看,一个错误的分类是在第二类。 ```py def evaluate_model(y_label, predict_test_file): # Creating the y_label for the confusion matrix f=open(predict_test_file,'r') y_pred = np.genfromtxt(f,dtype = 'float') # Confusion matrix cf_matrix = confusion_matrix(y_label, y_pred) # Plot heatmap ax = sns.heatmap(cf_matrix/np.sum(cf_matrix), annot=True, fmt='.2%', cmap='Blues') # Format plot ax.set_title('Seaborn Confusion Matrix with wine class labels\n\n'); ax.set_xlabel('\nPredicted action') ax.set_ylabel('Actual action '); # Ticket labels - List must be in alphabetical order ax.xaxis.set_ticklabels(['1', '2', '3']) ax.yaxis.set_ticklabels(['1', '2', '3']) # Display the visualization of the Confusion Matrix. plt.show() ``` ```py evaluate_model(y_label, 'wine_svm_test_data.predict') ``` **限制** SVM,尤其是 LIBSVM,作为用于分类的机器学习算法有一些限制。在这一节中,我将提到其中的一些,以便您在考虑这种算法来满足您的分类需求时能够意识到并记住这些。 1. 需要输入数据的唯一格式,并针对每个要素进行标注。 2. 由于它在高维空间中的使用,它容易过拟合。您可以通过选择合适的核函数并设置正则化参数来避免这种情况。 3. 对于大型数据集,所涉及的交叉验证技术的计算成本很高。 4. 为了获得最佳结果,必须对数据进行缩放,并且在一个接*的范围内。 5. 对于观察值的分类,没有概率上的解释。 **结论** 我们刚刚使用 LIBSVM 完成了多类分类。SVM 是一种很好的分类机器学习算法,因为它不需要非常大的训练数据集,可以处理许多特征,并且可以处理线性和非线性边界。LIBSVM 是一个很好的 SVM 包,特别是对于初学者来说,因为大部分调整都是为您抽象的,但仍然可以产生非常高的性能。希望通过这篇文章,您已经能够使用 SVM 将数据分类到多个类中。请在此处找到使用过的完整笔记本[。](https://colab.research.google.com/drive/1Skp1-XjgdkBpkGIBE4wmsX_AIsblF1W6#scrollTo=V0jiyPeQTau-) 感谢您的阅读。 **参考文献** 1. 张志忠,林志仁,LIBSVM:支持向量机的资料库。ACM 智能系统与技术汇刊,2:27:1 - 27:27,2011。软件在[http://www.csie.ntu.edu.tw/~cjlin/libsvm](http://www.csie.ntu.edu.tw/~cjlin/libsvm)可用 2. [https://www.csie.ntu.edu.tw/~cjlin/papers/guide/guide.pdf](https://www.csie.ntu.edu.tw/~cjlin/papers/guide/guide.pdf) 3. [https://www . csie . NTU . edu . tw/~ cjlin/libsvm/FAQ . html](https://www.csie.ntu.edu.tw/~cjlin/libsvm/faq.html) 4. Dua d .和 Graff c .(2019 年)。http://archive.ics.uci.edu/ml 的 UCI 机器学习知识库。加州欧文:加州大学信息与计算机科学学院。 5. [https://www.csie.ntu.edu.tw/~cjlin/papers/libsvm.pdf](https://www.csie.ntu.edu.tw/~cjlin/papers/libsvm.pdf) # raw PyTorch 上的多 GPU,带有拥抱脸的加速库 > 原文:<https://blog.paperspace.com/multi-gpu-on-raw-pytorch-with-hugging-faces-accelerate-library/> 分布式机器学习是复杂的,当与同样复杂的深度学习模型相结合时,它可以让任何事情都进入研究项目。加上设置你的 GPU 硬件和软件,它可能会变得太多了。 这里我们展示了[humping Face 的 Accelerate 库](https://huggingface.co/blog/accelerate-library)消除了使用分布式设置的一些负担,同时仍然允许您保留所有原始 PyTorch 代码。 当与 Paperspace 的多 GPU 硬件及其现成的 ML 运行时相结合时,Accelerate 库可以更轻松地运行用户以前可能会发现很难的高级深度学习模型。新的可能性打开了,否则可能会去探索。 ## 什么是加速? ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/22c2f7499c8fd03b4a6775c716fec753.png) Accelerate 是来自 Hugging Face 的一个库,它简化了在单台或多台机器上将单个 GPU 的 PyTorch 代码转换为多个 GPU 的代码。你可以在他们的 [GitHub 库](https://github.com/huggingface/accelerate)这里阅读更多关于 Accelerate 的内容。 ### 动机 有了最先进的深度学习技术,我们可能无法总是避免真实数据或模型中的复杂性,但我们可以降低在 GPU 上运行它们的难度,并且一次在多个 GPU 上运行。 有几个库可以做到这一点,但它们通常要么提供更高层次的抽象来消除用户的细粒度控制,要么提供另一个 API 接口,在使用它之前需要先学习它。 这就是 Accelerate 的动机:让需要编写完整的通用 PyTorch 代码的用户能够这样做,同时减少以分布式方式运行这些代码的负担。 该库提供的另一个关键功能是,固定形式的代码可以分布式运行,也可以非分布式运行。这不同于传统的 PyTorch 分布式启动,传统的 py torch 分布式启动必须进行更改,以便从一个启动到另一个启动,然后再返回。 ## 使用 Accelerate 的代码更改 如果您需要使用完全通用的 PyTorch 代码,很可能您正在为模型编写自己的训练循环。 **训练循环** 典型的 PyTorch 训练循环是这样的: * 导入库 * 设置设备(例如,GPU) * 点模型到设备 * 选择优化器(如 Adam) * 使用 DataLoader 加载数据集(这样我们可以将批传递给模型) * 循环中的训练模型(每个时期一次): * 将源数据和目标指向设备 * 将网络梯度归零 * 从模型计算输出 * 计算损失(例如交叉熵) * 反向传播梯度 根据所解决的问题,还可能有其他步骤,如数据准备,或在测试数据上运行模型。 **代码变更** 在 Accelerate GitHub 存储库的自述文件中,通过突出显示要更改的行,说明了与常规 PyTorch 相比,上述训练循环的代码更改: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/def9cc0d71c08997765fe4a0b018040f.png) Code changes for a training loop using Accelerate versus original PyTorch. (From the Accelerate GitHub repository README) 绿色表示添加的新行,红色表示删除的行。我们可以看到代码是如何与上面概述的训练循环步骤相对应的,以及需要进行的更改。 乍一看,这些变化似乎没有简化代码,但是如果您想象红线已经消失,您可以看到我们不再谈论我们在什么设备上(CPU、GPU 等)。).它被抽象掉了,而循环的其余部分保持不变。 更详细地说,代码更改如下: * 导入加速器库 * 使用加速器作为设备,可以是 CPU 或 GPU * 实例化模型,无需指定设备 * 设置 Accelerate 要使用的模型、优化器和数据 * 我们不需要将源数据和目标指向设备 * 加速器执行反向传播步骤 **多 GPU** 上面的代码是针对单个 GPU 的。 在他们的[拥抱脸博客条目](https://huggingface.co/blog/accelerate-library)上,Accelerate 的作者展示了 PyTorch 代码需要如何改变,以使用传统方法实现多 GPU。 它包括更多的代码行: ```py import os ... from torch.utils.data import DistributedSampler from torch.nn.parallel import DistributedDataParallel local_rank = int(os.environ.get("LOCAL_RANK", -1)) ... device = device = torch.device("cuda", local_rank) ... model = DistributedDataParallel(model) ... sampler = DistributedSampler(dataset) ... data = torch.utils.data.DataLoader(dataset, sampler=sampler) ... sampler.set_epoch(epoch) ... ``` 由此产生的代码不再适用于单个 GPU。 相比之下,使用 Accelerate *的代码已经适用于多 GPU* ,并且也继续适用于单个 GPU。 这听起来很棒,但是它在一个完整的程序中是如何工作的,又是如何被调用的呢?我们现在将通过一个关于 Paperspace 的示例来展示 Accelerate 的实际应用。 ## 在图纸空间上运行加速 [Accelerate GitHub 库](https://github.com/huggingface/accelerate)通过一组记录良好的例子展示了如何运行这个库。 在这里,我们将展示如何在 Paperspace 上运行它,并浏览一些示例。我们假设您已经登录,并且熟悉[基本笔记本用法](https://docs.paperspace.com/gradient/notebooks/runtimes)。您还需要付费订阅,以便可以访问终端。 Paperspace 允许您直接连接到 GitHub 存储库,并将其用作项目的起点。因此,您可以指向现有的 Accelerate 存储库并立即使用它。不需要安装 PyTorch,或者先设置一个虚拟环境。 要运行,以通常的方式启动笔记本。使用推荐选项卡下的 **PyTorch 运行时**,然后滚动到页面底部并切换*高级选项*。然后,在【高级选项】下,将工作区 URL 更改为 Accelerate 存储库的位置:[https://github.com/huggingface/accelerate](https://github.com/huggingface/accelerate)。首先,我们使用单个 GPU,因此机器的默认选择(P4000)就足够了。我们将在本文后面讨论多 GPU。 这将启动笔记本,并在左侧导航栏的“文件”选项卡中显示回购文件。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b24eee6fa587e5271fff7b68dfc80902.png) Hugging Face Accelerate Github repository being used in a Paperspace Notebook 因为 repo 提供的示例是`.py` Python 脚本,并且这些脚本在这个接口中的 Paperspace 上运行良好,所以我们不打算在这里将它们显示为`.ipynb`笔记本。不过,如果你愿意,图书馆[也可以从笔记本](https://github.com/huggingface/accelerate#launching-your-training-from-a-notebook)上启动。 让我们看看这个例子。 **简单的自然语言处理示例** 拥抱脸建立在使自然语言处理(NLP)更容易被人们使用的基础上,所以 NLP 是一个合适的起点。 从左侧导航栏打开一个终端: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/02b8e9f1f002003438145521b701ed71.png) Open terminal in Paperspace Notebook 接下来是一些简短的设置步骤 ```py pip install accelerate pip install datasets transformers pip install scipy sklearn ``` 我们可以继续这个例子 ```py cd examples python ./nlp_example.py ``` 这在众所周知的 BERT transformer 模型的基础配置上执行微调训练,使用关于一个句子是否是另一个句子的释义的[胶水 MRPC](https://www.microsoft.com/en-us/download/details.aspx?id=52398) 数据集。 它输出的准确率约为 85%,F1 值(准确率和召回率的组合)略低于 90%。所以表现还过得去。 如果您导航到 metrics 选项卡,您可以看到 GPU 确实被使用了: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/3d272bad1a309836270d25534823bc1b.png) GPU usage in Paperspace Notebook while running our Accelerate example 也可以使用各种参数调用该脚本来改变行为。我们将在最后提到其中的一些。 ### 多 GPU 对于多 GPU,库 Accelerate 的简化能力真正开始显现,因为可以运行与上面相同的代码。 类似地,在 Paperspace 上,要获得多 GPU 设置,只需将机器从我们一直使用的单 GPU 切换到多 GPU 实例。Paperspace 为 A4000s、A5000s、A6000s 和 A100s 提供多 GPU 实例,尽管这因地区而异。 如果您已经在运行笔记本,请停止当前的计算机: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/4a2eba0b366d8459fa2da4e25dc7e96a.png) 然后使用左侧导航栏中的下拉菜单: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/64652180426ce3f6ee6ef4e2b7f6016a.png) 要选择多 GPU 计算机并重新启动,请执行以下操作: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/42fbce0aef3aa32284763514ee5d80ed.png) 这里从 P4000 换成 A4000x2 就很好了。 *注意:*如果您还没有运行单个 GPU 机器,请以与上述单个 GPU 情况相同的方式创建一个笔记本,但选择 A4000x2 机器而不是 P4000 机器。 然后,要调用多 GPU 的脚本,请执行以下操作: ```py pip install accelerate datasets transformers scipy sklearn ``` 并通过简短的配置步骤告诉它如何在这里运行: ```py accelerate config ``` ```py In which compute environment are you running? ([0] This machine, [1] AWS (Amazon SageMaker)): 0 Which type of machine are you using? ([0] No distributed training, [1] multi-CPU, [2] multi-GPU, [3] TPU): 2 How many different machines will you use (use more than 1 for multi-node training)? [1]: 1 Do you want to use DeepSpeed? [yes/NO]: no Do you want to use FullyShardedDataParallel? [yes/NO]: no How many GPU(s) should be used for distributed training? [1]: 2 Do you wish to use FP16 or BF16 (mixed precision)? [NO/fp16/bf16]: no ``` 请注意,我们说 1 台机器,因为我们的 2 个 GPU 在同一台机器上,但我们确认要使用 2 个 GPU。 然后我们可以像以前一样运行,现在使用`launch`命令而不是`python`来告诉 Accelerate 使用我们刚刚设置的配置: ```py accelerate launch ./nlp_example.py ``` 通过在终端中运行`nvidia-smi`可以看到两个 GPU 都在使用。 ### 更多功能 正如上面的配置文件设置所暗示的那样,我们只是触及了库特性的表面。 其他一些功能包括: * 启动脚本的一系列参数:参见[https://github . com/hugging face/accelerate/tree/main/examples](https://github.com/huggingface/accelerate/tree/main/examples) * 多 CPU 和多 GPU * 多台机器上的多 GPU * Jupyter 笔记本上的发射器 * 混合精度浮点 * 极速集成 * 带 MPI 的多 CPU **计算机视觉示例** 还有另外一个机器学习的例子,你可以运行;它类似于我们在这里运行的 NLP 任务,但是是针对计算机视觉的。它在牛津-IIT 宠物数据集上训练了一个 ResNet50 网络。 在我们的笔记本上,您可以将以下内容添加到代码单元格中,以快速运行示例: ```py pip install accelerate datasets transformers scipy sklearn pip install timm torchvision cd examples wget https://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz tar -xzf images.tar.gz python ./cv_example.py --data_dir images ``` ## 结论和后续步骤 我们已经展示了与传统 PyTorch 相比,来自 Hugging Face 的 Accelerate 库如何以分布式方式简化 PyTorch 深度学习模型的运行,而没有删除用户代码的完全通用性质。 同样,Paperspace 通过提供一个随时可用的环境,简化了多 GPU + PyTorch + Accelerate 的访问。 对于一些后续步骤: * 查看原始的[拥抱脸博客条目](https://huggingface.co/blog/accelerate-library) * 查看 [Accelerate GitHub 库](https://github.com/huggingface/accelerate)了解更多示例和功能 * 在 Paperspace 上运行您自己的加速和分布式深度学习模型 ### *发牌* 在 Apache License 2.0 下,他们可以使用 Hugging Face GitHub 库中的代码。 # 基于梯度的多 GPU:张量流分布策略 > 原文:<https://blog.paperspace.com/multi-gpu-tensorflow-distribution-strategies/> 正如这里的许多读者所知,Paperspace 使直接通过 Paperspace Core 上的虚拟机或通过 Paperspace Gradient 上的笔记本电脑、工作流和部署来访问 GPU 计算能力变得非常容易。 支持访问单个 GPU 的一个显而易见的扩展是在一台机器上扩展到多个 GPU,以及扩展到多台机器。 在这里,我们展示了其中的第一个,一台机器上的多 GPU,如何通过使用 [TensorFlow 分布策略](https://www.tensorflow.org/guide/distributed_training)在 TensorFlow 上运行分布式训练来处理梯度。我们还展示了简单的和定制的训练循环都可以实现。 这里要注意的一个关键点是*做这个*不需要特殊的设置:一个常规的渐变容器和一个用户可用的多 GPU 笔记本实例就足够了。 ## 张量流分布策略 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/aa912cbf1a2c8c030415ab4cc26bcc10.png) tensor flow Distribution Strategies 是他们的 API,通过将现有代码放在以`with strategy.scope():`开头的块中,允许现有模型分布在多个 GPU(多 GPU)和多个机器(多工作器)上。 `strategy`表示我们正在使用 TensorFlow 的当前策略之一来分发我们的模型: * 镜像策略 * 贸易策略 * 多重工作镜像策略 * 参数服务器策略 * 中央仓库战略 其中每一种都代表了分配硬件和计算的不同方式,根据要解决的问题,它们有不同的优点和缺点。 我们在这里主要关注的是最常用和最简单的一个。这允许在一台机器上的多个 GPU 上进行模型训练,训练是*同步*,这意味着所有部分,如渐变,都在每一步后更新。 简要地看了看其他人: `TPUStrategy`非常相似,但只适用于谷歌的张量处理单元(TPU)硬件。 `MultiWorkerMirroredStrategy`将`MirroredStrategy`推广到多台机器,即工人。 `ParameterServerStrategy`代表除了镜像之外的另一种分布方法,其中在它自己的机器上的模型的每个部分可以有它自己的变量。这就是*异步*训练。变量值在一台中央机器,即参数服务器上进行协调。 最后,`CentralStorageStrategy`类似,但是将变量放在 CPU 上,而不是镜像它们。 这些策略通过一套以多种形式提供的[教程](https://www.tensorflow.org/tutorials/distribute)展示在 TensorFlow 的网站上。这些包括`.ipynb` Jupyter 笔记本,我们将在下面运行。 ### 简单和复杂模型 张量流模型有不同的复杂程度,这些复杂程度可以用几种方法进行分类。特别是,除了发行策略之外,他们网站上的教程可以分为 * 简单的训练循环 * 自定义训练循环 其中训练循环是训练模型的过程。自定义训练循环提供了更通用、更细粒度的功能,但是比简单的训练循环需要更多的代码来实现。真正的问题通常有一些要求,这意味着需要一个自定义的循环。 然而,对于多 GPU,使用自定义循环意味着实现比仅仅将现有的单 GPU 代码封装在`with strategy.scope():`块中更复杂。例如,当模型损失函数的分量来自几个并行运行的作业时,必须对其进行不同的定义。不是监督学习的简单情况的模型形式,例如 GAN、强化学习等。,也是自定义循环。 虽然我们不试图在这里重现所有的细节,但我们表明简单和定制的训练循环都可以在 Gradient 中工作,无论是在单 GPU 上还是在多 GPU 上。 ## 在梯度上运行张量流分布策略 顺着这个帖子的标题,这里重点说一下在一台机器上运行多 GPU,对应的是`MirroredStrategy`,如上所述。我们展示了简单的和定制的训练循环。 ### 运行镜像策略 我们展示了来自 TensorFlow 的分布式教程的 2 款`.ipynb` Jupyter 笔记本,经过略微修改,可以在渐变上运行得更好: * `keras.ipynb`,来自[分布式训练与 Keras](https://www.tensorflow.org/tutorials/distribute/keras) 教程 * `custom_training.ipynb`,来自[定制培训](https://www.tensorflow.org/tutorials/distribute/custom_training)教程 **设置** 要在 Gradient 上运行,我们创建一个项目,然后启动一个新的笔记本实例,选择 TensorFlow 容器和一台具有多 GPU 的机器。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ee6a4861e8e009feee05f40b0c983e3e.png) TensorFlow container with multi-GPU Notebook instance 这里,我们使用两个安培的 A5000s。在高级选项中,我们选择工作区作为包含三个笔记本的 repo。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/a7c92bdb565e3232dca91aa9df5fb54d.png) Using the GitHub repository for this project 然后,我们的多 GPU 笔记本电脑就可以运行了。 **keras.ipynb** 这展示了一个使用 TensorFlow 的高级 Keras 接口的简单模型。 使用`nvidia-smi`和 TensorFlow 自己的功能,我们看到两个 GPU 都存在 ```py print('Number of devices: {}'.format(strategy.num_replicas_in_sync)) # Output: # Number of devices: 2 ``` Both GPUs are present 使用的数据是来自 TensorFlow 数据集的 MNIST 数据。 使用`strategy = tf.distribute.MirroredStrategy()`定义分配策略。 该模型以通常的格式构建,但是在块`with strategy.scope():`内 ```py with strategy.scope(): model = tf.keras.Sequential([ tf.keras.layers.Conv2D(32, 3, activation='relu', input_shape=(28, 28, 1)), tf.keras.layers.MaxPooling2D(), tf.keras.layers.Flatten(), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dense(10) ]) model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), optimizer=tf.keras.optimizers.Adam(), metrics=['accuracy']) ``` Model code is moved inside strategy scope block 本教程随后展示了回调、模型训练和评估,以及保存为 SavedModel 格式。 **custom_training.ipynb** 在本教程中,整体结构是相似的,但现在我们使用自定义循环,而不是`keras.ipynb`中的简单循环。它也使用时尚 MNIST 数据集,而不是原来的那个。 现在必须定义损失函数,因为它合并了从多个 GPU 的副本中计算的损失。 ```py with strategy.scope(): # Set reduction to `none` so we can do the reduction afterwards and divide by # global batch size. loss_object = tf.keras.losses.SparseCategoricalCrossentropy( from_logits=True, reduction=tf.keras.losses.Reduction.NONE) def compute_loss(labels, predictions): per_example_loss = loss_object(labels, predictions) return tf.nn.compute_average_loss(per_example_loss, global_batch_size=GLOBAL_BATCH_SIZE) ``` For a custom training loop, the loss function must be defined 类似地,必须计算诸如损失和准确性之类的性能指标。 本教程随后展示了训练循环、检查点和一些迭代数据集的替代方法。您可以在这里跟随其余的教程笔记本[,或者使用之前的链接作为笔记本创建页面的工作区 URL 来创建渐变笔记本。](https://github.com/gradient-ai/TensorFlow-Distribution-Strategies) ## 渐变消除了设置开销 正如我们现在所看到的,在分布式设置中训练模型并使用它们执行其他操作可能比使用单个 CPU 或 GPU 更复杂。因此对我们有利的是,Gradient 消除了设置 GPU 和安装 ML 软件的负担,允许我们直接进行编码。 这开辟了进一步的用途,例如,没有编写代码但仍然想要使用模型的用户,可以通过他们在生产、应用程序或一些其他用例中这样做。底层分布式硬件和 GPU 可以继续在 Gradient 上运行。 ## 结论 我们已经证明张量流分布策略适用于梯度,包括: * `MirroredStrategy`在单个 GPU 上 * `MirroredStrategy`在多 GPU 上 * 这两方面的简单模型训练 * 这两者上的定制训练循环 将来,当多节点支持被添加到 Gradient 中时,它将能够支持`MultiWorkerMirroredStrategy`、`ParameterServerStrategy`和`CentralStorageStrategy`,除了这篇博文中所涉及的那些。 ## 后续步骤 您可以通过跟随 GitHub 知识库(位于[https://GitHub . com/gradient-ai/tensor flow-Distribution-Strategies](https://github.com/gradient-ai/TensorFlow-Distribution-Strategies))来尝试这些方法。 这包含了他们的两个笔记本的副本,从 TensorFlow 原件稍微修改,以便他们在梯度上工作,并如上运行。 有关分销策略的更多详细信息,请参阅这些笔记本,或 [TensorFlow 的分销策略指南](https://www.tensorflow.org/guide/distributed_training)。该指南包含一个笔记本,`distributed_training.ipynb`,它也将在渐变上运行,尽管它与上面的一些重叠。 之后,TensorFlow 分布式策略和梯度的使用非常普遍,因此有广泛的潜在项目可以完成。 ## 附录:关于`MultiWorkerMirroredStrategy`和`ParameterServerStrategy`的注释 TensorFlow 的教程展示了这两种策略通过`localhost`在一台机器上工作,而不是要求用户拥有实际的多台机器。这意味着,事实上,它们也在梯度上运行。然而,镜像多工作器和参数服务器的正常使用会涉及到使用多个机器或节点。因为目前还不支持,所以我们没有尝试在这篇博客中展示它。 # 使用 LSTMs 生成音乐 > 原文:<https://blog.paperspace.com/music-generation-with-lstms/> ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d5bacd18c992a32f4b5e86d84c197c11.png) Photo by [Wes Hicks](https://unsplash.com/@sickhews?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) / [Unsplash](https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) 音乐在大多数人的生活中扮演着重要的角色。每个人对音乐都有自己的品味和偏好,无论是流行、摇滚、爵士、嘻哈、民谣还是其他这类音乐。音乐对整个历史的影响是革命性的。一般来说,音乐可以被描述为一种悠扬而有节奏的音调,它给听众带来一种抚慰和*静的效果。但是,由于有各种不同类型的乐器和各种各样的音乐,这往往是一个复杂的课题,需要人类大量的奉献和多年的努力。但是深度学习模型在如此复杂的情况下会如何发展呢? 在之前的文章中,我们已经了解了我们在人工智能和深度学习方面取得的进展,以处理与音频信号处理相关的众多任务。这些音频任务中的一些包括音频分类、音频或语音识别、音频分析以及其他类似的任务。在这个博客中,我们的主要焦点是在 LSTM 网络的帮助下进行音乐创作。我们将了解一些基本概念,并直接进入项目。本文提供的代码可以在有 GPU 支持的 Paperspace 上的 Gradient *台上运行。 ## 简介: 在深度学习框架的帮助下生成音乐似乎是一项非常复杂的任务。由于各种不同类型的乐器和音乐,训练神经网络来生成可能对每个人都有吸引力的理想节奏音乐确实很复杂,因为音乐是主观的。然而,有一些方法可以让我们成功地完成这项任务。我们将在下一节进一步探讨这个主题,但首先,让我们了解如何在您的通用设备上收听音频数据。 我们可以通过多种方式收听音频数据,通过. mp3 或。wav 格式。但是,通过利用。midi 格式。MIDI 文件代表乐器数字接口文件。它们可以在操作系统上用普通的 VLC 应用程序或媒体播放器打开。这些是通常总是用于音乐生成项目的文件类型,我们也将在本文中广泛使用它们。 MIDI 文件不包含任何实际的音频信息,不像. mp3 或。wav 格式。它们包含音频数据,其形式为要播放什么类型的音符,音符应该持续多长时间,以及当在支持的兼容软件中播放时音符应该有多大声。使用这些 MIDI 文件的主要意义在于它们很紧凑,占用的空间更少。因此,这些文件非常适合需要大量数据来生成高质量结果的音乐生成项目。 * * * ## 理解音乐生成的基本概念: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ed5801223d286b51feb9cd7177f396df.png) [Image Source](https://en.wikipedia.org/wiki/Long_short-term_memory#/media/File:LSTM_Cell.svg) 在我最*的一篇博客中,我们讨论了深度学习的音频分类,观众可以从下面的[链接](https://blog.paperspace.com/audio-classification-with-deep-learning/)中查看。在那篇文章中,我们了解了如何将音频文件中的数据转换成频谱图,频谱图是音频信号中频谱的可视化表示。在文章的这一部分,我们的主要目标是理解一些最常用于音乐生成的方法。 音乐生成和音频相关任务的一种流行方法是使用 WaveNet 生成模型进行信号处理。DeepMind 开发的这个模型对于生成原始音频数据很有用。除了制作音乐之外,一些常见的应用包括模仿人声和许多文本到语音的检测系统。最初,这种方法需要更多的计算处理能力来完成基本任务。然而,随着更多的更新,大多数问题都随着性能的提高而得到了解决,并广泛应用于常见的应用程序中。我们将在以后的文章中更详细地介绍这个网络。 另一种方法是利用一维卷积层。在 1D 卷积层中,针对输入向量在固定数量的滤波器上执行卷积运算,从而产生一维输出阵列。它们对于捕获输入序列中的数据是有用的,并且训练起来比 LSTMs 和 GRUs 相对更快。然而,这些网络构建起来更加复杂,因为“相同”或“有效”的适当填充选择有其自身的问题。 最后,我们有 LSTM 方法,我们开发了一个 LSTM 模型,可以相应地处理音乐生成的任务。LSTMs 使用一系列门来检测哪些信息与特定任务相关。从上图可以看出,这些 LSTM 网络使用了三个主要的门,即遗忘门、输入门和输出门。当这些门相应地更新时,一个时间步长完成。在本文中,我们将重点关注构建我们的音乐生成项目的 LSTM 方法,因为在一些预处理步骤之后,我们可以使用这些 LSTM 单元从零开始构建我们的模型架构。 * * * ## 用 LSTMs 构建音乐生成项目; 在文章的这一部分,我们将详细解释如何在 LSTMs 的帮助下生成音乐。我们将利用 TensorFlow 和 Keras 深度学习框架进行大部分模型开发。你可以查看下面的[文章](https://blog.paperspace.com/absolute-guide-to-tensorflow/)来了解更多关于 TensorFlow 和 Keras 文章[这里](https://blog.paperspace.com/the-absolute-guide-to-keras/)。这个项目需要的另一个基本安装是 pretty midi 库,它对处理 midi 文件很有用。这个库可以通过一个简单的 pip 命令安装在您的工作环境中,如下所示。 ```py pip install pretty_midi ``` 一旦我们在工作环境中安装了所有必要的需求,我们就可以开始音乐生成项目了。让我们从导入所有必要的库开始。 ### 导入基本库: 在本节中,我们将导入这个项目所需的所有基本库。为了创建深度学习模型,让我们导入 TensorFlow 和 Keras 深度学习框架以及构建模型所需的所有层、模型、损耗和优化器参数。另一个主要的导入是 pretty midi 库,如前所述,用来访问和处理 midi 文件。我们还将定义一些库,如 matplotlib 和 seaborn,用于整个项目中众多参数的可视化。下面是包含网络所有基本构建模块的代码片段。 ```py import tensorflow as tf from tensorflow.keras.layers import Input, LSTM, Dense from tensorflow.keras.models import Model from tensorflow.keras.losses import SparseCategoricalCrossentropy from tensorflow.keras.optimizers import Adam from typing import Dict, List, Optional, Sequence, Tuple import collections import datetime import glob import numpy as np import pathlib import pandas as pd import pretty_midi from IPython import display from matplotlib import pyplot as plt import seaborn as sns ``` ### 准备数据集: 我们将在这个项目中使用的数据集将包含多个 MIDI 文件,其中包含我们的模型可以用于训练的大量钢琴音符。MAESTRO 数据集包含钢琴 MIDI 文件,观众可以从下面的[链接](https://magenta.tensorflow.org/datasets/maestro#v200)下载。我会推荐下载 maestro-v2.0.0-midi.zip 文件。数据集在压缩格式下只有 57 MB,提取时大约为 85 MB。我们可以使用包含超过 1200 个文件的数据来训练和开发我们的深度学习模型,以生成音乐。 对于大多数与音频处理和音乐生成相关的深度学习项目,通常更容易构建深度学习架构网络来训练音乐生成的模型。然而,相应地准备和预处理数据是至关重要的。在接下来的几节中,我们将准备并创建理想的数据集和必要的函数,用于训练模型以产生所需的结果。在下面的代码片段中,我们定义了一些基本参数来开始这个项目。 ```py # Creating the required variables seed = 42 tf.random.set_seed(seed) np.random.seed(seed) # Sampling rate for audio playback _SAMPLING_RATE = 16000 ``` 在下一步中,我们将定义下载并解压缩数据文件夹的目录的路径。当从提供的下载链接中提取 zip 文件时,应该将其放在工作环境中,以便您可以轻松访问文件夹中的所有内容。 ```py # Setting the path and loading the data data_dir = pathlib.Path('maestro-v2.0.0') filenames = glob.glob(str(data_dir/'**/*.mid*')) print('Number of files:', len(filenames)) ``` 一旦我们设置了路径,我们就可以读取一个示例文件,如下面的代码块所示。通过访问一个随机样本文件,我们可以对使用了多少种不同的乐器有一个相对深入的了解,并访问构建模型架构所需的一些基本属性。这个项目考虑的三个主要变量是音高、音步和音长。我们可以通过从下面的代码片段中理解音高、音符和持续时间的打印值来提取必要的信息。 ```py # analyzing and working with a sample file sample_file = filenames[1] print(sample_file) pm = pretty_midi.PrettyMIDI(sample_file) print('Number of instruments:', len(pm.instruments)) instrument = pm.instruments[0] instrument_name = pretty_midi.program_to_instrument_name(instrument.program) print('Instrument name:', instrument_name) # Extracting the notes for i, note in enumerate(instrument.notes[:10]): note_name = pretty_midi.note_number_to_name(note.pitch) duration = note.end - note.start print(f'{i}: pitch={note.pitch}, note_name={note_name}, duration={duration:.4f}') ``` ```py maestro-v2.0.0\2004\MIDI-Unprocessed_SMF_02_R1_2004_01-05_ORIG_MID--AUDIO_02_R1_2004_06_Track06_wav.midi Number of instruments: 1 Instrument name: Acoustic Grand Piano 0: pitch=31, note_name=G1, duration=0.0656 1: pitch=43, note_name=G2, duration=0.0792 2: pitch=44, note_name=G#2, duration=0.0740 3: pitch=32, note_name=G#1, duration=0.0729 4: pitch=34, note_name=A#1, duration=0.0708 5: pitch=46, note_name=A#2, duration=0.0948 6: pitch=48, note_name=C3, duration=0.6260 7: pitch=36, note_name=C2, duration=0.6542 8: pitch=53, note_name=F3, duration=1.7667 9: pitch=56, note_name=G#3, duration=1.7688 ``` 上面的音高值代表由 MIDI 音符编号识别的声音的感知质量。步长变量表示从上一个音符或轨道开始经过的时间。最后,持续时间变量表示音符的开始时间和结束时间之间的差异,即特定音符持续多长时间。在下面的代码片段中,我们提取了这三个参数,它们将用于构建 LSTM 模型网络来计算和生成音乐。 ```py # Extracting the notes from the sample MIDI file def midi_to_notes(midi_file: str) -> pd.DataFrame: pm = pretty_midi.PrettyMIDI(midi_file) instrument = pm.instruments[0] notes = collections.defaultdict(list) # Sort the notes by start time sorted_notes = sorted(instrument.notes, key=lambda note: note.start) prev_start = sorted_notes[0].start for note in sorted_notes: start = note.start end = note.end notes['pitch'].append(note.pitch) notes['start'].append(start) notes['end'].append(end) notes['step'].append(start - prev_start) notes['duration'].append(end - start) prev_start = start return pd.DataFrame({name: np.array(value) for name, value in notes.items()}) raw_notes = midi_to_notes(sample_file) raw_notes.head() ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/0a8aadc84e5a505317bec007c05e2b7b.png) 最后,让我们使用 numpy 数组将文件转换成向量值。我们将为音符名称分配数字音高值,因为它们更容易理解。下面显示的音符名称代表音符、偶然音和八度音程数等特征。我们还可以可视化我们的数据,并相应地解释它们。下面是两个可视化:a .)前 100 个音符的可视化和 b .)整个音轨的可视化。 ```py # Converting to note names by considering the respective pitch values get_note_names = np.vectorize(pretty_midi.note_number_to_name) sample_note_names = get_note_names(raw_notes['pitch']) print(sample_note_names[:10]) # Visualizing the paramaters of the muscial notes of the piano def plot_piano_roll(notes: pd.DataFrame, count: Optional[int] = None): if count: title = f'First {count} notes' else: title = f'Whole track' count = len(notes['pitch']) plt.figure(figsize=(20, 4)) plot_pitch = np.stack([notes['pitch'], notes['pitch']], axis=0) plot_start_stop = np.stack([notes['start'], notes['end']], axis=0) plt.plot(plot_start_stop[:, :count], plot_pitch[:, :count], color="b", marker=".") plt.xlabel('Time [s]') plt.ylabel('Pitch') _ = plt.title(title) ``` ```py array(['G2', 'G1', 'G#2', 'G#1', 'A#2', 'A#1', 'C3', 'C2', 'F3', 'D4'], dtype='<U3') ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/edc236f252d6b6919760500da70275b5.png) a. Visualization of first 100 notes ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/63ce4b5718a7d0f121f9f8021a48f03d.png) b. Visualizing the entire track 一旦我们完成了数据的分析、准备和可视化,我们就可以进入下一步,完成预处理并创建最终的训练数据。 ### 创建培训数据: 如果需要,用户还可以使用笔记资源创建附加数据。我们可以用可用的音符生成自己的 MIDI 文件。下面是执行以下操作的代码片段。创建文件后,我们可以将其保存在。midi 格式并加以利用。因此,如果需要,我们也可以生成和创建自己的数据集。生成的文件可以保存在工作目录或您选择的所需位置。 ```py def notes_to_midi(notes: pd.DataFrame, out_file: str, instrument_name: str, velocity: int = 100) -> pretty_midi.PrettyMIDI: pm = pretty_midi.PrettyMIDI() instrument = pretty_midi.Instrument( program=pretty_midi.instrument_name_to_program( instrument_name)) prev_start = 0 for i, note in notes.iterrows(): start = float(prev_start + note['step']) end = float(start + note['duration']) note = pretty_midi.Note(velocity=velocity, pitch=int(note['pitch']), start=start, end=end) instrument.notes.append(note) prev_start = start pm.instruments.append(instrument) pm.write(out_file) return pm example_file = 'example.midi' example_pm = notes_to_midi( raw_notes, out_file=example_file, instrument_name=instrument_name) ``` 在下一步中,我们将集中于从 MIDI 文件中提取所有的音符并创建我们的数据集。这一步可能很耗时,因为我们有大量可变的数据可用。根据查看者拥有的资源类型,建议从几个文件开始,然后从那里开始。下面是在训练和计算模型时,解析一些笔记并使用`tf.data`以提高效率的步骤。 ```py num_files = 5 all_notes = [] for f in filenames[:num_files]: notes = midi_to_notes(f) all_notes.append(notes) all_notes = pd.concat(all_notes) n_notes = len(all_notes) print('Number of notes parsed:', n_notes) key_order = ['pitch', 'step', 'duration'] train_notes = np.stack([all_notes[key] for key in key_order], axis=1) notes_ds = tf.data.Dataset.from_tensor_slices(train_notes) notes_ds.element_spec ``` 如前所述,LSTMs 最适合处理一系列信息,因为它们能够有效地记住以前的数据元素。因此,我们创建的数据集将具有序列输入和输出。如果序列的大小是(100,1),这意味着将有总共 100 个输入音符被传递以接收最终的输出 1。因此,我们创建的数据集将具有类似的模式,其中数据将注释作为输入要素,输出注释作为这些输入序列之后的标注。下面是代表创建这些序列的函数的代码块。 ```py def create_sequences(dataset: tf.data.Dataset, seq_length: int, vocab_size = 128) -> tf.data.Dataset: """Returns TF Dataset of sequence and label examples.""" seq_length = seq_length+1 # Take 1 extra for the labels windows = dataset.window(seq_length, shift=1, stride=1, drop_remainder=True) # `flat_map` flattens the" dataset of datasets" into a dataset of tensors flatten = lambda x: x.batch(seq_length, drop_remainder=True) sequences = windows.flat_map(flatten) # Normalize note pitch def scale_pitch(x): x = x/[vocab_size,1.0,1.0] return x # Split the labels def split_labels(sequences): inputs = sequences[:-1] labels_dense = sequences[-1] labels = {key:labels_dense[i] for i,key in enumerate(key_order)} return scale_pitch(inputs), labels return sequences.map(split_labels, num_parallel_calls=tf.data.AUTOTUNE) seq_length = 25 vocab_size = 128 seq_ds = create_sequences(notes_ds, seq_length, vocab_size) ``` 为了选择序列长度,我们在上面的代码中使用了 25,但是可以使用超参数调整来进一步优化最佳序列长度。最后,使用上一步创建的序列,我们可以通过设置批量大小、缓冲区大小和 tf.data 功能的其他基本要求来最终生成训练数据,如下面的代码片段所示。 ```py batch_size = 64 buffer_size = n_notes - seq_length # the number of items in the dataset train_ds = (seq_ds .shuffle(buffer_size) .batch(batch_size, drop_remainder=True) .cache() .prefetch(tf.data.experimental.AUTOTUNE)) ``` 随着训练数据的成功创建,我们可以继续有效地开发我们的模型,按照我们的要求计算和生成音乐。 ### 开发模型: 既然我们已经完成了数据的所有基本组成部分的准备和处理,我们就可以相应地继续开发和训练模型了。第一步是定义一个自定义损失函数,我们将利用它作为步长和持续时间参数。我们知道这些参数不能是负数,因为它们必须总是正整数。下面的函数有助于鼓励创建的模型只输出所需参数的正值。下面是执行所需操作的自定义均方误差损失函数的代码片段。 ```py def mse_with_positive_pressure(y_true: tf.Tensor, y_pred: tf.Tensor): mse = (y_true - y_pred) ** 2 positive_pressure = 10 * tf.maximum(-y_pred, 0.0) return tf.reduce_mean(mse + positive_pressure) ``` 最后,我们可以开始开发模型,我们将训练数据集来生成音乐。如前所述,我们将利用 LSTM 网络来创建这个模型。首先,我们将描述模型输入层的输入形状。然后,我们将调用具有大约 128 个维度单位的空间的 LSTM 层来处理数据。在模型网络的末端,我们可以为所有三个参数,即音高、步长和持续时间,定义一些完全连接的层。一旦定义了所有的层,我们就可以相应地定义带有输入和输出调用的模型。我们将对音调参数使用稀疏分类交叉熵损失函数,而对步长和持续时间参数使用自定义的均方误差损失。我们可以调用 Adam 优化器并查看模型的摘要,如下面的代码块所示。 ```py # Developing the model input_shape = (seq_length, 3) learning_rate = 0.005 inputs = Input(input_shape) x = LSTM(128)(inputs) outputs = {'pitch': Dense(128, name='pitch')(x), 'step': Dense(1, name='step')(x), 'duration': Dense(1, name='duration')(x), } model = Model(inputs, outputs) loss = {'pitch': SparseCategoricalCrossentropy(from_logits=True), 'step': mse_with_positive_pressure, 'duration': mse_with_positive_pressure, } optimizer = Adam(learning_rate=learning_rate) model.compile(loss=loss, optimizer=optimizer) model.summary() ``` ```py Model: "model" __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== input_1 (InputLayer) [(None, 25, 3)] 0 [] lstm (LSTM) (None, 128) 67584 ['input_1[0][0]'] duration (Dense) (None, 1) 129 ['lstm[0][0]'] pitch (Dense) (None, 128) 16512 ['lstm[0][0]'] step (Dense) (None, 1) 129 ['lstm[0][0]'] ================================================================================================== Total params: 84,354 Trainable params: 84,354 Non-trainable params: 0 __________________________________________________________________________________________________ ``` 一旦我们创建了模型,我们就可以为训练模型定义一些必要的回调。我们将利用模型检查点来保存模型的最佳权重,并定义早期停止函数,以便在我们达到最佳结果并且连续五个时期没有看到改进时终止程序。 ```py # Creating the necessary callbacks callbacks = [tf.keras.callbacks.ModelCheckpoint(filepath='./training_checkpoints/ckpt_{epoch}', save_weights_only=True), tf.keras.callbacks.EarlyStopping(monitor='loss', patience=5, verbose=1, restore_best_weights=True),] ``` 一旦构建了模型,定义了所有必要的回调,我们就可以相应地编译和适应模型了。由于我们需要关注三个参数,因此我们可以通过对所有损失求和来计算总损失,并通过为每个类别提供特定权重来创建类别*衡。我们可以训练大约 50 个时期的模型,并记录产生的结果类型。 ```py # Compiling and fitting the model model.compile(loss = loss, loss_weights = {'pitch': 0.05, 'step': 1.0, 'duration':1.0,}, optimizer = optimizer) epochs = 50 history = model.fit(train_ds, epochs=epochs, callbacks=callbacks,) ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d23cb58f00f3ba3cc73fe42fd302346e.png) Loss Plot 一旦模型完成了训练,我们就可以通过绘制损失度量与历元数的关系来分析它的性能。减少曲线表明损失在不断减少,模型在不断改进。我们现在可以继续看下一步,如何用训练好的模型生成注释。 ### 生成注释: 最后,我们可以使用经过训练的模型来生成所需的音符。为了开始生成过程的迭代,我们需要提供一个注释的起始序列,在此基础上,LSTM 模型可以继续创建构建块并重建更多的数据元素。为了创造更多的随机性,并避免模型只选择最佳音符,因为这将导致重复的结果,我们可以利用温度参数来产生随机音符。下面的代码片段演示了获得我们在本节中讨论的所有所需结果的过程。 ```py def predict_next_note(notes: np.ndarray, keras_model: tf.keras.Model, temperature: float = 1.0) -> int: """Generates a note IDs using a trained sequence model.""" assert temperature > 0 # Add batch dimension inputs = tf.expand_dims(notes, 0) predictions = model.predict(inputs) pitch_logits = predictions['pitch'] step = predictions['step'] duration = predictions['duration'] pitch_logits /= temperature pitch = tf.random.categorical(pitch_logits, num_samples=1) pitch = tf.squeeze(pitch, axis=-1) duration = tf.squeeze(duration, axis=-1) step = tf.squeeze(step, axis=-1) # `step` and `duration` values should be non-negative step = tf.maximum(0, step) duration = tf.maximum(0, duration) return int(pitch), float(step), float(duration) ``` 有了不同的温度变量和不同的开始顺序,我们就可以相应地开始创作音乐了。下面的代码块演示了此过程的步骤。我们利用具有随机温度值的随机起始序列,利用它 LSTM 模型可以继续建立。一旦我们能够解释下一个序列并获得所需的音高、步长和持续时间值,我们就可以将它们存储在生成输出的累积列表中。然后,我们可以继续删除先前使用的起始序列,并利用下一个先前的序列来进行下一个预测,并且也存储它。这个步骤可以持续一段时间,直到我们在期望的时间内产生了合适的音乐音调。 ```py temperature = 2.0 num_predictions = 120 sample_notes = np.stack([raw_notes[key] for key in key_order], axis=1) # The initial sequence of notes while the pitch is normalized similar to training sequences input_notes = ( sample_notes[:seq_length] / np.array([vocab_size, 1, 1])) generated_notes = [] prev_start = 0 for _ in range(num_predictions): pitch, step, duration = predict_next_note(input_notes, model, temperature) start = prev_start + step end = start + duration input_note = (pitch, step, duration) generated_notes.append((*input_note, start, end)) input_notes = np.delete(input_notes, 0, axis=0) input_notes = np.append(input_notes, np.expand_dims(input_note, 0), axis=0) prev_start = start generated_notes = pd.DataFrame( generated_notes, columns=(*key_order, 'start', 'end')) generated_notes.head(10) ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/56d0b9497f0a1c38a73126bc5937dca9.png) 一旦我们完成了音乐生成的所有步骤,我们就可以创建一个输出 MIDI 文件来存储由模型生成的用于演奏乐器和弦的数据。使用我们之前定义的音符到 midi 函数,我们可以通过使用生成的输出,提及乐器类型,并将其定向到输出文件以保存生成的音乐音符,来构建带有钢琴音符的 MIDI 文件。 ```py out_file = 'output.midi' out_pm = notes_to_midi( generated_notes, out_file=out_file, instrument_name=instrument_name) ``` 您可以通过播放下面附加的声音文件来收听为这篇博文生成的样本。 Output0:00/1:041× 深度学习模型的音乐生成的可能性是无穷的。有这么多不同类型的音乐流派和这么多不同类型的网络,我们可以构建来实现我们正在寻找的理想音乐品味。本文的大部分代码引用自 TensorFlow 官方文档[网站](https://www.tensorflow.org/tutorials/audio/music_generation#generate_notes)。我强烈建议查看网站,并尝试许多不同的网络结构和架构,以构建更好、更有创意的音乐生成器的更独特的模型。 * * * ## 结论: ![DJ at work](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/96d374bebff99b46bc7a110130e1efae.png) Photo by [Marcela Laskoski](https://unsplash.com/@marcelalaskoski?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) / [Unsplash](https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) 音乐可以成为大多数人生活中不可或缺的一部分,因为它可以创造许多不同类型的人类情感,如增强记忆,培养任务耐力,点亮你的情绪,减少焦虑和抑郁,创造一种舒缓的效果,等等。一般来说,乐器和音乐很难学,即使对人类来说也是如此。掌握它需要很长时间,因为有几个不同的因素,一个人必须考虑并致力于实践。然而,在现代,随着人工智能和深度学习,我们已经取得了足够的进展,可以让机器进入这个领域,探索音符的本质。 在这篇文章中,我们简要介绍了人工智能,特别是深度学习模型,在音乐领域以及与音频和信号处理相关的众多任务中取得的进展。我们了解 rnn 和 LSTMs 在创建数据元素块或系列中的重要性,在这些数据元素块或系列中,输出可以连续传输以实现出色的结果。在快速概述了 LSTM 网络的重要性之后,我们进入了下一部分,从头开始构建音乐生成项目。下面的项目可以做一些改进和提高,我强烈建议对这个项目感兴趣的人做进一步的探索。 在接下来的文章中,我们将研究与音频处理、音频识别相关的类似项目,以及更多与音乐相关的作品。我们也将涵盖从零开始的神经网络和其他生成性对抗网络的概念,如风格甘。在那之前,享受探索音乐和深度学习网络的世界吧! # 基于 Librosa 和 Tensorflow/Keras 的音乐流派分类 > 原文:<https://blog.paperspace.com/music-genre-classification-using-librosa-and-pytorch/> 在本教程中,我们展示了如何使用 Librosa 库计算的特征在 TensorFlow/Keras 中从头实现音乐流派分类器。 我们将使用最流行的公开可用的音乐流派分类数据集:GTZAN。该数据集包含一系列反映不同情况的录音,这些文件是在 2000 年至 2001 年间从许多来源收集的,包括个人 CD、收音机和麦克风录音。尽管它已经有 20 多年的历史,但它仍然被认为是关于音乐流派分类的机器学习应用的首选数据集。 该数据集包含 10 个类,每个类包含 100 个不同的 30 秒音频文件。这些类别是:布鲁斯、古典、乡村、迪斯科、嘻哈、爵士、金属、流行、雷鬼和摇滚。 在本教程中,出于简化的目的,我们将只使用 3 个流派(雷鬼,摇滚和古典)。但是,同样的原则仍然适用于更多的类型。 让我们从下载和提取数据集文件开始。 ## 准备数据集 我们首先将使用 gdrive 包从 Google Drive 下载文件。然后,我们将从终端使用 unrar 对每个文件进行解压缩。您可以通过命令行或使用 line magic 来实现这一点。 ```py gdown --fuzzy 'https://drive.google.com/file/d/1nZz6EHYl7M6ReCd7BUHzhuDN52mA36_q/view?usp=drivesdk' 7z e gtzan.rar -odataset_rar unrar e dataset_rar/reggae.rar dataset/reggae/ unrar e dataset_rar/rock.rar dataset/rock/ unrar e dataset_rar/classical.rar dataset/classical/ ``` downloading & packing the Dataset ## 导入 python 库 现在我们将导入所需的库。TensorFlow 将用于模型训练、评估和预测,Librosa 用于所有音频相关操作,包括特征生成,Numpy 用于数值处理,Matplotlib 用于将特征打印为图像。 ```py import os import numpy from tensorflow import keras import librosa from matplotlib import pyplot ``` Importing needed librairies ## 音频功能 每种音乐类型都有自己的特点:音高、旋律、和弦进行和乐器类型。为了有一个可靠的分类,我们将使用一组捕捉这些元素的本质的特征,使模型有更好的机会被适当地训练以区分流派。 在本教程中,我们将构建四个特征,用于创建与我们的模型将被训练的每个文件相关的单个特征向量。 这些功能是: * 梅尔频率倒谱系数 * 梅尔光谱图 * 色度矢量 * 音调质心特征 ### 梅尔频率倒谱系数(MFCC) MFCCs 或 Mel 频率倒谱系数是通过应用于信号功率谱的离散余弦变换计算的倒谱系数。根据 Mel 标度,该频谱的频带以对数间隔。 ```py def get_mfcc(wav_file_path): y, sr = librosa.load(wav_file_path, offset=0, duration=30) mfcc = numpy.array(librosa.feature.mfcc(y=y, sr=sr)) return mfcc ``` function returning MFCC feature 接下来,我们将使用 Matplotlib 绘制一个示例文件的 MFCC 图像: ```py example_file = "dataset/classical/classical.00015.wav" mfcc = get_mfcc(example_file) pyplot.imshow(mfcc, interpolation='nearest', aspect='auto') pyplot.show() ``` Code to display image of MFCC feature ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/0489f0ae71d174c2f4616d04abdd102d.png) MFCC of the file classical.00015.wav ### 梅尔光谱图 Mel 声谱图相当于 Mel 标度中的标准声谱图,Mel 标度是一种音调的感知标度,听者感觉彼此之间的间距相等。从以赫兹为单位的频域到 Mel 标度的转换使用以下公式: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/0be588f65b0d40e5caa6f37e3ed3cb87.png) Mel scale conversion from frequency in hertz ```py def get_melspectrogram(wav_file_path): y, sr = librosa.load(wav_file_path, offset=0, duration=30) melspectrogram = numpy.array(librosa.feature.melspectrogram(y=y, sr=sr)) return melspectrogram ``` function returning MelSpectrogram feature 接下来,我们绘制同一音频文件的 Mel 频谱图: ```py melspectrogram = get_melspectrogram(example_file) pyplot.imshow(melspectrogram, interpolation='nearest', aspect='auto') pyplot.show() ``` Code to display image of MelSpectrogram feature ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/c20c1f99c24622d8f7428d28c9233598.png) Mel Spectrogram of the audio file classical.00015.wav ### 色度矢量 色度特征向量通过将全部频谱投影到 12 个面元上来构建,这 12 个面元反映了音乐八度音程的 12 个唯一半音(或色度): ***C,C#,D,D#,E,F,F#,G,G#,A,A#,B**T3。这种投影给出了一种有趣而有力的音乐音频表现,并且特别依赖于音乐类型。* 由于相距一个八度音程的音符被认为在音乐中特别相似,所以即使不知道绝对频率(即,原始八度音程),理解色度的分布也可以提供关于音频的有用的音乐信息,并且甚至可以揭示在原始频谱中不可见的相同音乐流派中的感知的音乐相似性。 ```py def get_chroma_vector(wav_file_path): y, sr = librosa.load(wav_file_path) chroma = numpy.array(librosa.feature.chroma_stft(y=y, sr=sr)) return chroma ``` function returning Chroma Vector 接下来绘制同一音频样本的色度向量: ```py chroma = get_chroma_vector(example_file) pyplot.imshow(chroma, interpolation='nearest', aspect='auto') pyplot.show() ``` Code to display image of Chroma vector ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/a4fbbd04a8758ccc32aa12737434d8c2.png) Chroma Vector of the audio file classical.00015.wav ### 音调质心特征(Tonnetz) 这种表示是通过将色度特征投影到 6 维基础上来计算的,将完全五度、小三度和大三度分别表示为二维坐标。 ```py def get_tonnetz(wav_file_path): y, sr = librosa.load(wav_file_path) tonnetz = numpy.array(librosa.feature.tonnetz(y=y, sr=sr)) return tonnetz ``` function returning Tonnetz feature 对于相同的色度矢量,我们具有以下 Tonnetz 特征: ```py tntz = get_tonnetz(example_file) pyplot.imshow(tntz , interpolation='nearest', aspect='auto') pyplot.show() ``` Code to display image of Tonnetz feature ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/899727d2eb4f69f305788a8b841597c8.png) Tonnetz feature of the audio file classical.00015.wav ### 将功能整合在一起 在创建了用于生成特征的四个函数之后。我们实现了函数 ***get_feature*** ,该函数将提取包络(最小值和最大值)以及沿时间轴的每个特征的*均值。这样,无论音频的长度是多少,我们都将拥有一个大小不变的特征。接下来,我们使用 Numpy 将这四个特性连接成一个 498 浮点数组。 ```py def get_feature(file_path): # Extracting MFCC feature mfcc = get_mfcc(file_path) mfcc_mean = mfcc.mean(axis=1) mfcc_min = mfcc.min(axis=1) mfcc_max = mfcc.max(axis=1) mfcc_feature = numpy.concatenate( (mfcc_mean, mfcc_min, mfcc_max) ) # Extracting Mel Spectrogram feature melspectrogram = get_melspectrogram(file_path) melspectrogram_mean = melspectrogram.mean(axis=1) melspectrogram_min = melspectrogram.min(axis=1) melspectrogram_max = melspectrogram.max(axis=1) melspectrogram_feature = numpy.concatenate( (melspectrogram_mean, melspectrogram_min, melspectrogram_max) ) # Extracting chroma vector feature chroma = get_chroma_vector(file_path) chroma_mean = chroma.mean(axis=1) chroma_min = chroma.min(axis=1) chroma_max = chroma.max(axis=1) chroma_feature = numpy.concatenate( (chroma_mean, chroma_min, chroma_max) ) # Extracting tonnetz feature tntz = get_tonnetz(file_path) tntz_mean = tntz.mean(axis=1) tntz_min = tntz.min(axis=1) tntz_max = tntz.max(axis=1) tntz_feature = numpy.concatenate( (tntz_mean, tntz_min, tntz_max) ) feature = numpy.concatenate( (chroma_feature, melspectrogram_feature, mfcc_feature, tntz_feature) ) return feature ``` function returning a concatenated version of all four features ### 计算整个数据集的要素 现在,我们将使用三种流派来训练我们的模型:雷鬼,古典和摇滚。对于更多的细微差别,如区分有很多相似之处的流派,例如摇滚和金属,应该包括更多的功能。 我们将遍历这三种类型的每个文件。并且,对于每一个,我们将构造特征数组,并将其与各自的标签一起存储。 ```py directory = 'dataset' genres = ['reggae','classical','rock'] features = [] labels = [] for genre in genres: print("Calculating features for genre : " + genre) for file in os.listdir(directory+"/"+genre): file_path = directory+"/"+genre+"/"+file features.append(get_feature(file_path)) label = genres.index(genre) labels.append(label) ``` Code constructing features for all files of the Dataset ### 将数据集分成训练、验证和测试部分 在创建了特征和标签数组之后,我们使用 Numpy 来打乱记录。然后,将数据集分成训练、验证和测试部分:分别为 60%、20%和 20%。 ```py permutations = numpy.random.permutation(300) features = numpy.array(features)[permutations] labels = numpy.array(labels)[permutations] features_train = features[0:180] labels_train = labels[0:180] features_val = features[180:240] labels_val = labels[180:240] features_test = features[240:300] labels_test = labels[240:300] ``` Code shuffling and splitting Dataset into training,validation and testing subsets ## 训练模型 对于该模型,我们将使用 Keras 实现两个规则的密集连接的神经网络层,具有整流线性单元激活函数“***【relu】***”,第一层 300 个隐藏单元,第二层 200 个隐藏单元。然后,对于输出层,我们还将使用概率分布激活函数“ ***softmax*** ”来实现密集连接的层。然后,我们将使用 64 个历元来训练模型: ```py inputs = keras.Input(shape=(498), name="feature") x = keras.layers.Dense(300, activation="relu", name="dense_1")(inputs) x = keras.layers.Dense(200, activation="relu", name="dense_2")(x) outputs = keras.layers.Dense(3, activation="softmax", name="predictions")(x) model = keras.Model(inputs=inputs, outputs=outputs) model.compile( # Optimizer optimizer=keras.optimizers.RMSprop(), # Loss function to minimize loss=keras.losses.SparseCategoricalCrossentropy(), # List of metrics to monitor metrics=[keras.metrics.SparseCategoricalAccuracy()], ) model.fit(x=features_train.tolist(),y=labels_train.tolist(),verbose=1,validation_data=(features_val.tolist() , labels_val.tolist()), epochs=64) ``` Code to construct and train the model ## 模型评估 然后,我们评估该模型: ```py score = model.evaluate(x=features_test.tolist(),y=labels_test.tolist(), verbose=0) print('Accuracy : ' + str(score[1]*100) + '%') ``` 使用这个简单的模型和这组特征,我们可以达到大约 86% 的精确度**。** ```py `Accuracy : 86.33333134651184%` ``` ## **Youtube 视频的分类** **然后,我们将使用库 ***youtube-dl*** 从 youtube 导出一个视频,我们稍后将对其进行分类。** ```py `pip install youtube-dl` ``` **我们下载视频并保存为 wave 文件。在这个例子中,我们使用鲍勃·马利的“[这是爱吗](https://www.youtube.com/watch?v=69RdQFDuYPI)”视频剪辑。** ```py `youtube-dl -x --audio-format wav --output "audio_sample_full.wav" https://www.youtube.com/watch?v=69RdQFDuYPI` ``` **之后,我们安装 ***pydub*** 库,我们将使用它来裁剪 wav 文件的大小** ```py `pip install pydub` ``` **我们将 wav 文件裁剪成一个 30 秒的部分,从 01:00:00 到 01:30:00。然后,保存结果文件。** ```py `from pydub import AudioSegment t1 = 60000 #Works in milliseconds t2 = 90000 waveFile = AudioSegment.from_file("audio_sample_full.wav") waveFile = waveFile[t1:t2] waveFile.export('audio_sample_30s.wav', format="wav")` ``` **然后,我们使用我们先前训练的模型来分类音频的音频音乐流派。** ```py `file_path = "audio_sample_30s.wav" feature = get_feature(file_path) y = model.predict(feature.reshape(1,498)) ind = numpy.argmax(y) genres[ind]` ``` **最后,我们得到了预期的结果:** ```py `Predicted genre : reggae` ``` ## **结论** **音频处理机器学习项目是人工智能文献中最少出现的项目之一。在本文中,我们简要介绍了音乐流派分类建模技术,这些技术可能在现代应用程序(例如流媒体网站)中有用。我们介绍了一些由 Librosa 生成的音频特征,然后使用 TensorFlow/Keras 创建和训练一个模型。最后,我们导出了一个 YouTube 视频,并使用训练好的模型对其音频进行了分类。** # 拥抱人脸的自然语言处理 > 原文:<https://blog.paperspace.com/natural-language-processing-with-huggingface/> 处理文本数据需要在数据预处理阶段投入相当多的时间。之后,你将需要花更多的时间来建立和训练自然语言处理模型。有了[抱脸](https://github.com/huggingface),这些都不用做了。 感谢拥抱脸的变形金刚库,你可以马上开始解决 NLP 问题。该软件包提供了预先训练的模型,可用于许多自然语言处理任务。变形金刚还支持 100 多种语言。它还提供了用您的数据微调模型的能力。该库提供了与 PyTorch 和 TensorFlow 的无缝集成,使您能够在它们之间轻松切换。 在这篇文章中,我们将看看你如何使用拥抱脸包: * 情感分析 * 问答 * 命名实体识别 * 摘要 * 翻译 * 标记化 ## 情感分析 在情感分析中,目标是确定文本是负面的还是正面的。Transformers 库提供了一个可以应用于任何文本数据的`pipeline`。`pipeline`包含预训练模型以及在模型训练阶段完成的预处理。因此,您不需要执行任何文本预处理。 让我们从导入`pipeline`模块开始。 ```py from transformers import pipeline ``` 下一步是用情感分析预训练模型实例化`pipeline`。这将下载并缓存情绪分析模型。在未来的请求中,将使用缓存模型。其他模型的工作方式相似。最后一步是使用模型来评估某些文本的极性。 ```py classifier = pipeline("sentiment-analysis") classifier("that was such bad movie") ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/47703c51a2c14130f83e0c1398579459.png) 该模型还返回标签的置信度得分。变形金刚套装中的大多数其他模型也返回一个置信度得分。 ## 问答 变形金刚包也可以用于问答系统。这需要一个语境和一个问题。先从下载问答模式开始。 ```py q_a = pipeline("question-answering") ``` 下一步是定义一个上下文和一个问题。 ```py context = "paperspace is the cloud platform built for the future" question = "Which is the cloud platform of the future?" ``` 最后一步是将问题和上下文传递给问答模型。 ```py q_a({"question": question, "context": context}) ``` 该模型将返回答案及其在所提供的上下文中的开始和结束位置。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/af189415ae63a6e6bd738bcbcf0781e1.png) ## 命名实体识别 命名实体识别包括提取和定位句子中的命名实体。这些实体包括人名、组织、位置等。让我们从下载命名实体识别模型开始。 ```py ner = pipeline("ner") ``` 接下来,传入一个文本,模型将提取实体。 ```py text = "Paperspace is located in the USA" ner(text) ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/6b6eeaca7e93268d8cbd6e2a1e175087.png) 该模型还可以检测一个句子中的多个实体。它返回每个已识别实体的终点和置信度得分。 ```py ner = pipeline("ner") text = "John works for Google that is located in the USA" ner(text) ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/bda6abfa288acf427dedcbcce0fb464b.png) ## 摘要 给定一大段文本,摘要模型可以用来摘要该文本。 ```py summarizer = pipeline("summarization") ``` 该模型需要: * 要汇总的文本 * 摘要的最大长度 * 摘要的最小长度 ```py article = "The process of handling text data is a little different compared to other problems. This is because the data is usually in text form. You ,therefore, have to figure out how to represent the data in a numeric form that can be understood by a machine learning model. In this article, let's take a look at how you can do that. Finally, you will build a deep learning model using TensorFlow to classify the text." summarizer(article, max_length=30, min_length=30) ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/9f1810008bb0dca095678a14cb196425.png) ## 翻译 当你有一个针对不同语言人群的产品时,翻译是一项非常重要的任务。例如,您可以使用 Transformers 包将英语翻译成德语,将英语翻译成法语。先说后者。 ```py translator = pipeline("translation_en_to_fr") ``` 下载完模型后,下一步就是传递要翻译的文本。 ```py text = "The process of handling text data is a little different compared to other problems. This is because the data is usually in text form. You ,therefore, have to figure out how to represent the data in a numeric form that can be understood by a machine learning model. In this article, let's take a look at how you can do that. Finally, you will build a deep learning model using TensorFlow to classify the text." translator(text) ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/9589107786bbc526401f409cf7bed8bf.png) 英语到德语的翻译可以以类似的方式完成。 ```py translator = pipeline("translation_en_to_fr") text = "The process of handling text data is a little different compared to other problems. This is because the data is usually in text form. You ,therefore, have to figure out how to represent the data in a numeric form that can be understood by a machine learning model. In this article, let's take a look at how you can do that. Finally, you will build a deep learning model using TensorFlow to classify the text." translator(text) ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b63c28b17e5002ad9f5e02d06e4c9161.png) ## 标记化 除了在 NLP 任务中使用拥抱脸,你还可以用它来处理文本数据。TensorFlow 和 PyTorch 都支持该处理。拥抱脸的标记器完成了文本任务所需的所有预处理。分词器可以应用于单个文本或一系列句子。 让我们看看如何在 TensorFlow 中实现这一点。第一步是导入标记器。 ```py from transformers import AutoTokenizer text = "The process of handling text data is a little different compared to other problems. This is because the data is usually in text form. You ,therefore, have to figure out how to represent the data in a numeric form that can be understood by a machine learning model. In this article, let's take a look at how you can do that. Finally, you will build a deep learning model using TensorFlow to classify the text." ``` 下一步是从预先训练的模型词汇表中实例化标记器。 ```py tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") ``` 然后,您可以在文本上使用这个标记器。如果你想退回 PyTorch tensors 而不是 TensorFlow 的 pass `return_tensors="pt"`。 ```py inputs = tokenizer(text, return_tensors="tf") ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/02a90ef1a1556ab1f821be4de1001e8f.png) 还可以使用`decode`函数将标记化的句子转换成其原始形式。 ```py tokenizer.decode(inputs['input_ids'][0]) ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/490651984a749e58ca3183909b20603e.png) 拥抱脸也允许填充句子,使它们长度相同。这是通过传递填充和截断参数来实现的。句子将被截断到模型可以接受的最大值。 ```py sentences = ["sentence one here", " this is sentence two", "sentence three comes here"] tokenizer(sentences, padding=True, truncation=True, return_tensors="tf") ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/a7cf1ed6bcff3bd5838aaf4a79a3ad5f.png) ## 最后的想法 在本文中,您已经看到了使用拥抱脸进行各种自然语言处理任务有多快。您还看到了 Transformer 模型在各种 NLP 任务上实现了高精度。您还可以根据自定义数据训练模型。拥抱脸*台可以用来搜索新的模式,以及分享你的。 复制这个[笔记本](https://colab.research.google.com/drive/1qcVLTLVu7CulnLWfU4wzp3eo-Ht7b79i?usp=sharing)就可以马上开始玩库了。 # “计算机的网飞”:专访 Technical.ly BK > 原文:<https://blog.paperspace.com/netflix-of-computers-interview-with-technical-ly-bk/> > ...但是我们可以做得更好,上周我与一家令人兴奋的布鲁克林云计算公司的联合创始人交谈,该公司正试图重新定义我们使用计算机的方式。 > “‘云’是一个完全被大量营销资金占用的术语,”Paperspace 的联合创始人 Dillon Erb 告诉 Technical.ly Brooklyn。“我认为这是一个非常强大的东西,它抽象了一些非常强大的技术。” > 基于 Dumbo 的 Paperspace 刚刚筹集了 280 万美元的资金。它的口号是:“你的整个电脑,在云端。” [http://technical . ly/Brooklyn/2016/06/23/paper space-dillon-erb-cloud-computer/](http://technical.ly/brooklyn/2016/06/23/paperspace-dillon-erb-cloud-computer/) # 网络中的网络:1×1 卷积层的效用 > 原文:<https://blog.paperspace.com/network-in-network-utility-of-1-x-1-convolution-layers/> 下采样在卷积神经网络中是一个有益的过程,因为它有助于逐步管理网络中存在的像素/数据量,从而有助于管理训练期间的计算时间。正如我们现在所知道的,卷积层中的特征图是 4 维的,池化允许我们沿着高度和宽度维度向下采样。也可以通过称为 1×1 卷积的过程沿信道维度进行下采样。 在本文中,我们不仅将 1 x 1 卷积作为一种下采样工具,还将研究它在卷积神经网络中的多种用途。 ```py # article dependencies import torch import torch.nn as nn import torch.nn.functional as F import time ``` ### 1×1 卷积的概念 在论文《网络中的网络》中首次介绍([林敏](https://arxiv.org/abs/1312.4400)*等* [,2013](https://arxiv.org/abs/1312.4400) ),1×1 卷积是使用只有一行一列的滤波器进行卷积运算的过程。本质上,它是使用一个标量值(一个单一的数字)而不是一个典型的卷积层矩阵来执行卷积的过程,因此它基本上不提取特征。 用更专业的术语来说,1×1 卷积可以说是特征图的逐像素加权,因为每个像素乘以一个数,而不考虑相邻像素。在下图中,使用给定的过滤器对矩阵执行 1 x 1 卷积。所得到的特征图是原始矩阵的表示,其像素值是原始矩阵的 4 倍,因此原始图像被认为已经使用滤波器加权了 4 倍。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/4137a6db37f385a853b2e44c6744dbcd.png) The process of 1 x 1 convolution. ### 1 x 1 卷积和降维 随着卷积神经网络变得更深,其层中的通道/特征图的数量逐渐增加。事实上,在一个中间卷积图层中有多达 512 个要素地图并不罕见。 如何控制要素地图的数量?让我们假设想要建立一个更深的网络,但是在中间层中已经有 512 个特征图,这被认为是过多的,我们可以很容易地应用 1 x 1 卷积来将特征图的数量减少到 32,然后继续添加更多的卷积层。例如,如果在卷积层中有`(512, 12, 12)` (512 个表示,每个大小为 12 像素乘 12 像素)特征图,我们可以简单地应用`(32, 512, 1, 1)` (32 个 1×1 卷积过滤器,每个具有 512 个通道)过滤器来将特征图的数量减少到 32,从而产生如下所示的`(32, 12, 12)`特征图。 ```py # output from an intermediate layer intermediate_output = torch.ones((512, 12, 12)) # 1x1 convolution layer conv_1x1 = nn.Conv2d(512, 32, 1) # producing a downsampled representation downsampled = conv_1x1(intermediate_output) downsampled.shape # output >>>> torch.Size([32, 12, 12]) ``` 理论上,上述过程也可以使用常规的卷积滤波器来完成,但是不同之处在于特征将不会跨通道被汇总;相反,新的特征将被学习。由于 1 x 1 卷积只是单个像素的加权,而不考虑相邻像素,然后是跨通道求和,因此可以说是一种通道池。因此,生成的 32 个要素地图将是图层中所有 512 个要素地图的“汇总”。 最后,使用常规卷积滤波器的下采样在计算上更加昂贵。例如,假设一个人想要使用`(3, 3)`滤波器从 512 个特征图向下采样到 32 个特征图,这将产生 147,488 个参数(512*32*3*3 权重+ 32 偏差),而使用`(1, 1)`滤波器则产生 16,416 个参数(512*32*1*1 权重+ 32 偏差)。 ```py # 1x1 convolution layer conv_1x1 = nn.Conv2d(512, 32, 1) # 3x3 convolution layer conv_3x3 = nn.Conv2d(512, 32, 3, padding=1) # deriving parameters in the network parameters_1x1 = list(conv_1x1.parameters()) parameters_3x3 = list(conv_3x3.parameters()) # deriving total number of parameters in the (1, 1) layer number_of_parameters_1x1 = sum(x.numel() for x in parameters_1x1) # output >>>> 16,416 # deriving total number of parameters in the (3, 3) layer number_of_parameters_3x3 = sum(x.numel() for x in parameters_3x3) # output >>>> 147,488 ``` Comparing total number of parameters. 参数的差异也转化为使用(1,1)滤波器进行下采样的计算速度比使用(3,3)滤波器进行相同处理快大约 23 倍。 ```py # start time start = time.time() # producing downsampled representation downsampled = conv_1x1(intermediate_output) # stop time stop = time.time() print(round(stop-start, 5)) # output >>>> 0.00056 ``` Timing (1, 1) convolution. ```py # start time start = time.time() # producing downsampled representation downsampled = conv_3x3(intermediate_output) # stop time stop = time.time() print(round(stop-start, 5)) # output >>>> 0.01297 ``` Timing (3, 3) convolution. ### 1 x 1 卷积的附加非线性 由于 ReLU 激活通常遵循 1 x 1 卷积层,我们可以本质上包括它们(1 x 1 卷积层),而不仅仅是为了它们带来的额外 ReLU 非线性而执行维度缩减。例如,可以使用`(512, 512, 1, 1)` (512 个 1×1 卷积滤波器,每个滤波器具有 512 个通道)滤波器对来自前一部分的尺寸为`(512, 12, 12)`的特征图进行卷积,以再次产生`(512, 12, 12)`特征图,所有这些都只是为了利用随后的 ReLU 非线性。 为了更好地说明,考虑下面代码块中的示例 ConvNet,第 3 层是一个 1 x 1 卷积层,它返回与第 2 层中的(3,3)卷积相同数量的特征映射。在这种情况下,1×1 卷积层不是冗余的,因为它伴随着网络中不存在的 ReLU 激活功能。这是有益的,因为附加的非线性将进一步迫使网络学习更复杂的输入到输出的映射,从而使网络能够更好地概括。 ```py class ConvNet(nn.Module): def __init__(self): super().__init__() self.network = nn.Sequential( # layer 1 nn.Conv2d(1, 3, 3, padding=1), nn.MaxPool2d(2), nn.ReLU(), # layer 2 nn.Conv2d(3, 8, 3, padding=1), nn.MaxPool2d(2), nn.ReLU(), # layer 3 nn.Conv2d(8, 8, 1), nn.ReLU() # additional non-linearity ) def forward(self, x): input = x.view(-1, 1, 28, 28) output = self.network(input) return output ``` ### 使用 1 x 1 卷积替换线性图层 设想为 10 类多类分类任务构建一个卷积神经网络架构。在经典的卷积神经网络体系结构中,卷积层充当特征提取器,而线性层分阶段对特征图进行矢量化,直到返回包含每个类别置信度得分的 10 元素向量表示。线性图层的问题在于,在训练过程中,它们很容易过度拟合,需要将丢失正则化作为缓解措施。 ```py class ConvNet(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(1, 3, 3, padding=1) self.pool1 = nn.MaxPool2d(2) self.conv2 = nn.Conv2d(3, 64, 3, padding=1) self.pool2 = nn.MaxPool2d(2) self.linear1 = nn.Linear(3136, 100) self.linear2 = nn.Linear(100, 10) def forward(self, x): input = x.view(-1, 1, 28, 28) #----------- # LAYER 1 #----------- output_1 = self.conv1(input) output_1 = self.pool1(output_1) output_1 = F.relu(output_1) #----------- # LAYER 2 #----------- output_2 = self.conv2(output_1) output_2 = self.pool2(output_2) output_2 = F.relu(output_2) # flattening feature maps output_2 = output_2.view(-1, 7*7*64) #----------- # LAYER 3 #----------- output_3 = self.linear1(output_2) output_3 = F.relu(output_3) #-------------- # OUTPUT LAYER #-------------- output_4 = self.linear2(output_3) return torch.sigmoid(output_4) ``` Classic convnet architecture with linear layers. 结合 1×1 卷积,可以通过下采样特征图的数量直到它们与手头任务中的类的数量相同(在这种情况下是 10)来完全去除线性层。接下来,返回每个特征图中所有像素的*均值,以产生包含每个类别的置信度得分的 10 个元素的向量(*这个过程被称为全局*均池,在下一篇文章*中对此有更多介绍)。 ```py class ConvNet(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(1, 3, 3, padding=1) self.pool1 = nn.MaxPool2d(2) self.conv2 = nn.Conv2d(3, 64, 3, padding=1) self.pool2 = nn.MaxPool2d(2) self.conv3 = nn.Conv2d(64, 32, 1) # 1 x 1 downsampling from 64 channels to 32 channels self.conv4 = nn.Conv2d(32, 10, 1) # 1 x 1 downsampling from 32 channels to 10 channels self.pool4 = nn.AvgPool2d(7) # deriving average pixel values per channel def forward(self, x): input = x.view(-1, 1, 28, 28) #----------- # LAYER 1 #----------- output_1 = self.conv1(input) output_1 = self.pool1(output_1) output_1 = F.relu(output_1) #----------- # LAYER 2 #----------- output_2 = self.conv2(output_1) output_2 = self.pool2(output_2) output_2 = F.relu(output_2) #----------- # LAYER 3 #----------- output_3 = self.conv3(output_2) output_3 = F.relu(output_3) #-------------- # OUTPUT LAYER #-------------- output_4 = self.conv4(output_3) output_4 = self.pool4(output_4) output_4 = output_4.view(-1, 10) return torch.sigmoid(output_4) ``` Modern convnet architecture with 1 x 1 convolution layers.  ### 结束语 在本文中,我们研究了 1 x 1 卷积及其在卷积神经网络中的各种用途。我们研究了该过程本身,以及它如何被用作降维工具、作为额外非线性的来源以及作为在 CNN 架构中排除线性层和减轻过拟合问题的手段。这个过程是卷积神经网络体系结构的几个主要部分,绝对是一个需要正确掌握和理解的概念。 # 神经结构研究第 3 部分:控制器和精度预测器 > 原文:<https://blog.paperspace.com/neural-architecture-search-controllers/> 在本系列的第一部分中,我们看到了关于神经架构搜索的概述,包括文献综述。在第 2 部分中,我们看到了如何[将我们的编码序列转换成 MLP 模型](https://blog.paperspace.com/neural-architecture-search-one-shot-training/)。我们还研究了训练这些模型,为一次性学习逐层转移权重,以及保存这些权重。 为了得到这些编码序列,我们需要另一种机制,以对应于 MLP 的有效架构的方式生成序列。我们还想确保我们不会两次训练同一个架构。这就是我们将在这里讨论的内容。 具体来说,我们将在本文中讨论以下主题: * 控制器在 NAS 中的角色 * 创建控制器 * 控制器架构 * 准确性预测 * 培训管制员 * 采样架构 * 获得预测精度 * 结论 ## 控制器在 NAS 中的角色 获得这些编码序列的方法是使用一个循环网络,它会不断地为我们生成序列。每个序列都是我们以定向方式导航的搜索空间的一部分。我们寻找最佳架构的方向取决于我们如何训练控制器本身。 我们将把代码分成几个部分,但是完整的代码也可以在这里找到。 在[第 2 部分](https://blog.paperspace.com/neural-architecture-search-one-shot-training/)中,我们看到我们的 NAS 项目的总体流程如下所示: ```py def search(self): # for number of controller epochs for controller_epoch in range(controller_sampling_epochs): # sample a set number of architecture sequences sequences = sample_architecture_sequences(controller_model, samples_per_controller_epoch) # predict their accuracies using a hybrid controller pred_accuracies = get_predicted_accuracies(controller_model, sequences) # for each of these sequences for i, sequence in enumerate(sequences): # create and compile the model corresponding to the sequence model = create_architecture(sequence) # train said model history = train_architecture(model) # log the training metrics append_model_metrics(sequence, history, pred_accuracies[i]) # use this data to train the controller xc, yc, val_acc_target = prepare_controller_data(sequences) train_controller(controller_model, xc, yc, val_acc_target) ``` 搜索的内环主要是模型生成器的任务,而外环是控制器的任务。内循环和外循环中都有一些函数,涉及准备数据和存储模型度量,这是我们的生成器和控制器顺利工作所必需的。这些是主`MLPNAS`类的一部分,我们将在本系列的下一部分(也是最后一部分)研究它们。 现在,我们将特别关注控制器: * 它是如何设计的,以及控制器设计的不同替代方案 * 如何生成可以传递给 MLP 生成器以创建和训练架构的有效序列 * 如何训练管制员本身 控制器架构可以设计为包含精度预测器。这也可以通过多种方式来实现,例如,通过共享控制器和精度预测器的 LSTM 权重。使用准确度预测器有一些缺点。我们也会谈到这些。 ## 创建控制器 控制器是一个递归系统,它根据我们在搜索空间中设计的映射生成编码序列(在系列的第[第二部分](https://blog.paperspace.com/neural-architecture-search-one-shot-training/))。这个控制器将是一个模型,可以根据它生成的序列进行迭代训练。这从一个控制器开始,该控制器在不知道性能良好的架构看起来像什么的情况下生成序列。我们创建一些序列,训练这些序列,评估它们,并从这些序列中创建一个数据集来训练我们的控制器。**本质上,在每个控制器时期,都会创建一个新的数据集供控制器学习。** 为了做到这些,我们需要在控制器类中初始化一些参数——我们需要的常量。控制器将继承我们在本系列第二部分中创建的`MLPSearchSpace`类。 这些常数包括: * 控制器 LSTM 隐藏层数 * 用于培训的优化器 * 用于培训的学习率 * 衰减到用于训练 * 培训的动力(在 SGD 优化器的情况下) * 是否使用精度预测器 * 建筑的最大长度 ```py class Controller(MLPSearchSpace): def __init__(self): # defining training and sequence creation related parameters self.max_len = MAX_ARCHITECTURE_LENGTH self.controller_lstm_dim = CONTROLLER_LSTM_DIM self.controller_optimizer = CONTROLLER_OPTIMIZER self.controller_lr = CONTROLLER_LEARNING_RATE self.controller_decay = CONTROLLER_DECAY self.controller_momentum = CONTROLLER_MOMENTUM self.use_predictor = CONTROLLER_USE_PREDICTOR # file path of controller weights to be stored at self.controller_weights = 'LOGS/controller_weights.h5' # initializing a list for all the sequences created self.seq_data = [] # inheriting from the search space super().__init__(TARGET_CLASSES) # number of classes for the controller (+ 1 for padding) self.controller_classes = len(self.vocab) + 1 ``` 我们还初始化一个空列表来存储我们的控制器已经创建并测试的所有编码架构。这将防止我们的控制器一次又一次地采样相同的序列。我们需要在开始时初始化这个序列(而不是在函数中),因为这个序列数据需要在多个控制器时期中保持不变,而用于采样的函数要被调用几次。 如果您不仅要设计 MLP,还要设计基于深度 CNN 的架构(类似于 Inception 或 ResNets),您可能要考虑不要将这些架构存储在列表中,而是将其保存在某个临时文件中以释放内存。 ## 控制器架构 控制器可以以多种方式设计,并且可以做的实验数量没有真正的限制。从根本上说,我们需要一个可以从控制器中提取并解码成实际 MLP 架构的顺序输出。RNNs 和 LSTMs 听起来是很好的选择。 为控制器的学习尝试不同的优化技术,大多需要我们处理不同的优化器或者构建定制的损失函数。 下面可以看到一个简单的 LSTM 控制器。 ```py def control_model(self, controller_input_shape, controller_batch_size): main_input = Input(shape=controller_input_shape, batch_shape=controller_batch_size, name='main_input') x = LSTM(self.controller_lstm_dim, return_sequences=True)(main_input) main_output = Dense(self.controller_classes, activation='softmax', name='main_output')(x) model = Model(inputs=[main_input], outputs=[main_output]) return model ``` 上面显示的架构非常简单。有: * 一个输入层,其大小取决于输入形状和批处理形状 * 具有用户指定尺寸的 LSTM 图层 * 密集层,节点取决于词汇表的大小 这是一个顺序架构,可以使用我们选择的优化器和损失函数进行训练。 但是,除了利用上面提到的架构,还有其他方法来设计这些控制器。我们可以: * 改变我们建筑中 LSTM 层的 LSTM 维度 * 添加更多的 LSTM 层,并改变其尺寸 * 添加密集层,并改变节点和激活函数的数量 其他方法包括建立可以同时输出两种东西的模型。 ## 准确性预测 这种简单的 LSTM 体系结构可以通过不仅考虑基于损失函数的优化,而且考虑使用准确度预测器的并行模型,而变成对抗模型。精度预测器将与序列发生器的 LSTM 层共享权重,帮助我们创建更好的通用架构。 ```py def hybrid_control_model(self, controller_input_shape, controller_batch_size): # input layer initialized with input shape and batch size main_input = Input(shape=controller_input_shape, batch_shape=controller_batch_size, name='main_input') # LSTM layer x = LSTM(self.controller_lstm_dim, return_sequences=True)(main_input) # two layers take the same LSTM layer as the input, # the accuracy predictor as well as the sequence generation classification layer predictor_output = Dense(1, activation='sigmoid', name='predictor_output')(x) main_output = Dense(self.controller_classes, activation='softmax', name='main_output')(x) # finally the Keras Model class is used to create a multi-output model model = Model(inputs=[main_input], outputs=[main_output, predictor_output]) return model ``` 预测器输出将是具有 sigmoid 激活函数的单神经元密集层。这一层的输出将作为架构验证准确性的代理。 我们还可以从主输出 LSTM 中分离出精度预测器 LSTM,如下所示。 ```py def hybrid_control_model(self, controller_input_shape, controller_batch_size): # input layer initialized with input shape and batch size main_input = Input(shape=controller_input_shape, batch_shape=controller_batch_size, name='main_input') # LSTM layer x1 = LSTM(self.controller_lstm_dim, return_sequences=True)(main_input) # output for the sequence generator network main_output = Dense(self.controller_classes, activation='softmax', name='main_output')(x1) # LSTM layer x2 = LSTM(self.controller_lstm_dim, return_sequences=True)(main_input) # single neuron sigmoid layer for accuracy prediction predictor_output = Dense(1, activation='sigmoid', name='predictor_output')(x2) # finally the Keras Model class is used to create a multi-output model model = Model(inputs=[main_input], outputs=[main_output, predictor_output]) return model ``` 这样,我们不会因为精度预测器而影响序列预测,但我们仍然有一个网络学习来预测架构的精度,而无需训练它们。 在下一个也是最后一个部分,我们将看到当通过应用增强梯度来训练模型时,我们的损失函数实际上已经考虑了每个架构的验证准确性。准确性预测器的干预实际上导致控制器创建的架构不能给我们提供与仅使用一次性学习生成的架构一样高的准确性。为了彻底起见,我们在这里提到精度预测器。 ## 培训管制员 一旦我们准备好了模型,我们就编写一个函数来训练它。作为输入,该函数将采用损失函数、数据、批量大小和历元数。这样,我们可以使用自定义损失函数来训练我们的控制器。 下面的训练代码是针对上面提到的简单控制器的。它不包括精度预测值。 ```py def train_control_model(self, model, x_data, y_data, loss_func, controller_batch_size, nb_epochs): # get the optimizer required for training if self.controller_optimizer == 'sgd': optim = optimizers.SGD(lr=self.controller_lr, decay=self.controller_decay, momentum=self.controller_momentum) else: optim = getattr(optimizers, self.controller_optimizer)(lr=self.controller_lr, decay=self.controller_decay) # compile model depending on loss function and optimizer provided model.compile(optimizer=optim, loss={'main_output': loss_func}) # load controller weights if os.path.exists(self.controller_weights): model.load_weights(self.controller_weights) # train the controller print("TRAINING CONTROLLER...") model.fit({'main_input': x_data}, {'main_output': y_data.reshape(len(y_data), 1, self.controller_classes)}, epochs=nb_epochs, batch_size=controller_batch_size, verbose=0) # save controller weights model.save_weights(self.controller_weights) ``` 为了训练具有准确度预测器的模型,在两个地方修改上述函数:编译模型阶段和训练阶段。它需要包括用于两个不同输出的两个损失,以及用于每个损失的权重。类似地,训练命令需要在输出字典中包含第二个输出。对于预测器,我们使用均方误差作为损失函数。 ```py def train_control_model(self, model, x_data, y_data, loss_func, controller_batch_size, nb_epochs): # get the optimizer required for training if self.controller_optimizer == 'sgd': optim = optimizers.SGD(lr=self.controller_lr, decay=self.controller_decay, momentum=self.controller_momentum) else: optim = getattr(optimizers, self.controller_optimizer)(lr=self.controller_lr, decay=self.controller_decay) # compile model depending on loss function and optimizer provided model.compile(optimizer=optim, loss={'main_output': loss_func, 'predictor_output': 'mse'}, loss_weights={'main_output': 1, 'predictor_output': 1}) # load controller weights if os.path.exists(self.controller_weights): model.load_weights(self.controller_weights) # train the controller print("TRAINING CONTROLLER...") model.fit({'main_input': x_data}, {'main_output': y_data.reshape(len(y_data), 1, self.controller_classes), 'predictor_output': np.array(pred_target).reshape(len(pred_target), 1, 1)}, epochs=nb_epochs, batch_size=controller_batch_size, verbose=0) # save controller weights model.save_weights(self.controller_weights) ``` ## 采样架构 一旦模型架构和训练功能完成,我们需要最终使用这些模型来预测架构序列。如果使用精度预测器,还需要一个函数来获取预测的精度。 取样过程要求我们对 MLP 架构的设计规则进行编码,以避免无效的架构。我们也不希望一次又一次地创建相同的架构。其他需要考虑的事项包括: * 脱落层出现的时间和位置 * 建筑的最大长度 * 建筑的最小长度 * 每个控制器时期要采样多少架构 这些问题在下面的采样结构序列的函数中被考虑。我们运行一个嵌套循环;外部循环继续进行,直到我们获得所需数量的采样序列。内部循环使用控制器模型来预测每个架构序列中的下一个元素,从空序列开始,以具有一个或多个隐藏层的架构结束。其他约束条件是,辍学不能在第一层,最后一层不能重复。 在生成序列中的下一个元素时,我们根据所有可能元素的概率分布对其进行随机采样。这允许我们利用从控制器获得的 softmax 分布来导航搜索空间。概率采样有助于搜索空间的有效探索,同时不会偏离控制器模型所指示的太多。 ```py def sample_architecture_sequences(self, model, number_of_samples): # define values needed for sampling final_layer_id = len(self.vocab) dropout_id = final_layer_id - 1 vocab_idx = [0] + list(self.vocab.keys()) # initialize list for architecture samples samples = [] print("GENERATING ARCHITECTURE SAMPLES...") print('------------------------------------------------------') # while number of architectures sampled is less than required while len(samples) < number_of_samples: # initialise the empty list for architecture sequence seed = [] # while len of generated sequence is less than maximum architecture length while len(seed) < self.max_len: # pad sequence for correctly shaped input for controller sequence = pad_sequences([seed], maxlen=self.max_len - 1, padding='post') sequence = sequence.reshape(1, 1, self.max_len - 1) # given the previous elements, get softmax distribution for the next element if self.use_predictor: (probab, _) = model.predict(sequence) else: probab = model.predict(sequence) probab = probab[0][0] # sample the next element randomly given the probability of next elements (the softmax distribution) next = np.random.choice(vocab_idx, size=1, p=probab)[0] # first layer isn't dropout if next == dropout_id and len(seed) == 0: continue # first layer is not final layer if next == final_layer_id and len(seed) == 0: continue # if final layer, break out of inner loop if next == final_layer_id: seed.append(next) break # if sequence length is 1 less than maximum, add final # layer and break out of inner loop if len(seed) == self.max_len - 1: seed.append(final_layer_id) break # ignore padding if not next == 0: seed.append(next) # check if the generated sequence has been generated before. # if not, add it to the sequence data. if seed not in self.seq_data: samples.append(seed) self.seq_data.append(seed) return samples ``` ## 获得预测精度 获得预测的另一部分是获得每个模型的预测精度。该函数的输入将是模型和生成的序列。输出将是一个介于 0 和 1 之间的数字,即模型的预测验证精度。 ```py def get_predicted_accuracies_hybrid_model(self, model, seqs): pred_accuracies = [] for seq in seqs: # pad each sequence control_sequences = pad_sequences([seq], maxlen=self.max_len, padding='post') xc = control_sequences[:, :-1].reshape(len(control_sequences), 1, self.max_len - 1) # get predicted accuracies (_, pred_accuracy) = [x[0][0] for x in model.predict(xc)] pred_accuracies.append(pred_accuracy[0]) return pred_accuracies ``` 我们已经完成了控制器的构建。 ## 结论 之前我们看到了如何编写我们的模型生成器,它将实现一次性学习作为一个可选的特性。只有当我们已经拥有架构的编码序列,以及映射这些编码的搜索空间时,模型生成器才是有用的。因此,我们在本系列的第二部分定义了搜索空间和生成器。 在这一部分中,我们学习了如何使用基于 LSTM 的架构对架构的编码序列进行采样。我们研究了设计控制器的不同方法,以及如何利用精度预测器。准确度预测器不一定能创建出优秀的架构,但它确实有助于创建更好地概括的模型。我们还讨论了在精度预测器和序列生成器之间共享权重。 之后,我们学习了如何训练这些控制器模型,这取决于它们是单输出还是多输出模型。我们查看了架构编码生成器本身,它考虑了不同的约束来创建在所用层的顺序、这些架构的最大长度等方面有效的架构。我们用一个很小的函数完成了我们的控制器,该函数用于在给定模型和我们需要预测的序列的情况下获得预测精度。 本系列的下一部分也是最后一部分将总结 MLPNAS 的完整工作流程。最后,我们将把第二部分的模型生成器与第三部分的控制器集成起来,以实现神经结构搜索过程的自动化。我们也将学习加强梯度,作为我们控制器的优化方法。 敬请关注。 # 神经结构搜索第 2 部分:搜索空间、结构设计和一次性训练 > 原文:<https://blog.paperspace.com/neural-architecture-search-one-shot-training/> 在系列的第一部分[中,我们看了看神经结构问题被处理的所有不同角度。](https://blog.paperspace.com/overview-of-neural-architecture-search/) 有了基础,我们现在将看到如何实现我们在本系列第一篇文章中看到的一些重要概念。具体来说,我们将着眼于设计一个多层感知器的神经架构搜索方法。我们的实施将包括三个特殊功能: * 一次性建筑培训 * 控制器中的精度预测器 * 加强梯度训练控制器 在这一部分中,我们将着眼于 MLPs 的搜索空间设计,从序列中创建模型架构,以及如何着手实现一次性架构。 完整的代码可以在这里找到[。](https://github.com/codeaway23/MLPNAS) ## 介绍 多层感知器是最容易实现的深度学习架构。几个线性层堆叠在彼此之上;每一个都从前一层获取一个输入,乘以它的权重,加上一个偏差向量,然后将这个向量通过一个选择的激活函数来获得该层的输出。这个前馈过程一直持续到我们最终从最后一层得到我们的分类或回归输出。将此最终输出与地面真实分类或回归值进行比较,使用适当的损失函数计算损失,并使用梯度下降逐一更新所有图层的权重。 ## MLPNAS 的游戏计划 当试图自动化神经架构创建时,有一些事情要考虑。在我们深入研究之前,让我们看一下多层感知器神经架构搜索(MLPNAS)管道的简化视图: ```py def search(self): # for the number of controller epochs for controller_epoch in range(controller_sampling_epochs): # sample a set number of architecture sequences sequences = sample_architecture_sequences(controller_model, samples_per_controller_epoch) # predict their accuracies using a hybrid controller pred_accuracies = get_predicted_accuracies(controller_model, sequences) # for each of these sequences for i, sequence in enumerate(sequences): # create and compile the model corresponding to the sequence model = create_architecture(sequence) # train said model history = train_architecture(model) # log the training metrics append_model_metrics(sequence, history, pred_accuracies[i]) # use this data to train the controller xc, yc, val_acc_target = prepare_controller_data(sequences) train_controller(controller_model, xc, yc, val_acc_target) ``` 您可能已经注意到,所有外环的功能都属于控制器,所有内环的功能都属于 MLP 发生器。在这个系列的这一部分,我们将看看内部循环。但在我们能够做到这一点之前,我们首先必须对控制器如何生成架构有一些了解。 我们使用的控制器是一个 LSTM 架构,可以生成数字序列。这些数字被解码以创建架构参数,这些参数随后被用于生成架构。我们将在接下来的文章中探讨控制器如何顺序创建有效的架构。现在,我们需要理解每个可能的层配置需要被编码成一个数字,并且我们需要一种机制来将所述数字解码成层中相应数量的神经元和激活。 让我们更详细地看一下。 ## 搜索空间 第一个问题是设计搜索空间。我们知道,理论上有无限的可能性存在多少配置,即使我们正在处理非常少的隐藏层。 每个隐藏层中神经元的数量可以是任意正整数。也有很多激活函数,如上所述,它们服务于不同的目的(例如,除非用于分类层,否则你很少使用 *softmax* ,或者如果是二进制分类问题,你将只在分类层使用 *sigmoid* )。 为了照顾到所有这些,我们设计了搜索空间,它大致类似于人类对 MLP 架构的看法,并为我们提供了一种对所述配置进行数字编码或解码的方法。 每个隐层可以用两个参数表示:节点和激活函数。所以我们为序列生成器的*词汇*创建了一个字典。我们考虑一个离散的搜索空间,其中节点的数量可以取特定的值——*8,16,32,64,128,256* 和 *512* 。激活功能也是如此-*sigmoid、tanh、relu* 和 *elu。*我们用一个元组`(number of nodes, activation)`来表示每个这样的可能的层组合。在字典中,关键字是数字,值是层超参数的所述元组。我们从`1`开始编码,因为我们稍后需要填充序列,这样我们可以训练我们的控制器,并且不希望`0`造成混乱。 在为上述节点和激活的每个组合分配一个数字代码后,我们添加了另一个退出选项。最后,根据目标类,我们还将添加最后一层。我们在这个项目中保持 dropout 参数不变,以防止事情过于复杂。 如果有两个目标类,那么我们选择一个单节点 sigmoid 层;否则,我们选择一个 softmax 层,其节点数与类数一样多。 还有一些函数将给定的元组编码成它的数字对应物,反之亦然。 ```py class MLPSearchSpace(object): def __init__(self, target_classes): self.target_classes = target_classes self.vocab = self.vocab_dict() def vocab_dict(self): # define the allowed nodes and activation functions nodes = [8, 16, 32, 64, 128, 256, 512] act_funcs = ['sigmoid', 'tanh', 'relu', 'elu'] # initialize lists for keys and values of the vocabulary layer_params = [] layer_id = [] # for all activation functions for each node for i in range(len(nodes)): for j in range(len(act_funcs)): # create an id and a configuration tuple (node, activation) layer_params.append((nodes[i], act_funcs[j])) layer_id.append(len(act_funcs) * i + j + 1) # zip the id and configurations into a dictionary vocab = dict(zip(layer_id, layer_params)) # add dropout in the volcabulary vocab[len(vocab) + 1] = (('dropout')) # add the final softmax/sigmoid layer in the vocabulary if self.target_classes == 2: vocab[len(vocab) + 1] = (self.target_classes - 1, 'sigmoid') else: vocab[len(vocab) + 1] = (self.target_classes, 'softmax') return vocab # function to encode a sequence of configuration tuples def encode_sequence(self, sequence): keys = list(self.vocab.keys()) values = list(self.vocab.values()) encoded_sequence = [] for value in sequence: encoded_sequence.append(keys[values.index(value)]) return encoded_sequence # function to decode a sequence back to configuration tuples def decode_sequence(self, sequence): keys = list(self.vocab.keys()) values = list(self.vocab.values()) decoded_sequence = [] for key in sequence: decoded_sequence.append(values[keys.index(key)]) return decoded_sequence ``` 既然我们已经定义了我们的搜索空间和编码架构的方法,让我们看看如何在给定一个表示有效架构的序列的情况下生成神经网络架构。我们已经在搜索空间中添加了我们可能需要的所有不同的层配置,但我们还没有编写哪些配置有效,哪些无效的规则。我们将在编写控制器时这样做,这将在以后的文章中讨论。 ## 模型生成器 人类如何着手设计 MLP?如果你有深度学习的经验,你知道这项工作不需要超过几分钟。 需要考虑的事项有: 1. **每个隐层有多少个神经元**:有无数个选项,我们需要找到哪种配置会给我们最好的精度。 2. 每个隐藏层使用哪种激活函数:有几种激活函数,要找出哪种函数最适合特定的数据集,需要我们进行自动化的实验。 3. **添加一个脱落层**:它是有助于我的架构的性能,还是有害? 4. **最后一层像什么**:是多类问题还是二分类问题?这决定了我们最终层中的节点数量,以及我们在训练这些架构时最终使用的损失函数。 5. **数据的维度**:如果它需要二维输入,我们可能希望在添加线性层之前将其展*。 6. **多少个隐藏层** : [Panchal 等人(2011)](http://ijcte.org/papers/328-L318.pdf) 提出,在 MLP 中,我们很少需要两个以上的隐藏层来获得最佳性能。 当我们编写控制器时,会考虑到这些问题。 ### 生成 mips 现在,我们将假设我们的控制器工作良好,并且正在生成有效的序列。 我们需要编写一个生成器,它可以获取这些序列,并将它们转换成可以训练和评估的模型。模型生成器将包括以下功能: * 将序列转换为 Keras 模型 * 编译这些模型 我们将在“一次性架构”小节中讨论减重,并在之后进行训练。这将包括: * 为 Keras 模型设置权重 * 在训练每个模型后保存训练的权重 准确性的记录将在以后的文章中讨论。 我们的`MLPGenerator`类将继承上面定义的`MLPSearchSpace`类。我们还将在另一个名为`CONSTANTS.py`的文件中保存几个常量。我们导入了如下常量: * 目标类别 * 使用的优化器 * 学习率 * 衰退 * 动力 * 辍学率 * 损失函数 和其他文件,使用: ```py from CONSTANTS import * ``` 这些常数在`MLPGenerator`类中初始化,如下所示。 ```py class MLPGenerator(MLPSearchSpace): def __init__(self): self.target_classes = TARGET_CLASSES self.mlp_optimizer = MLP_OPTIMIZER self.mlp_lr = MLP_LEARNING_RATE self.mlp_decay = MLP_DECAY self.mlp_momentum = MLP_MOMENTUM self.mlp_dropout = MLP_DROPOUT self.mlp_loss_func = MLP_LOSS_FUNCTION self.mlp_one_shot = MLP_ONE_SHOT self.metrics = ['accuracy'] super().__init__(TARGET_CLASSES) ``` `MLP_ONE_SHOT`常量是一个布尔值,告诉算法是否使用单次训练。 下面是在给定一个对架构和输入形状进行编码的有效序列的情况下创建模型的函数。我们对序列进行解码,创建一个序列模型,并逐个添加序列中的每一层。我们还考虑了二维以上的输入,这将要求我们展*输入。我们也增加了退学的条件。 ```py # function to create a keras model given a sequence and input data shape def create_model(self, sequence, mlp_input_shape): # decode sequence to get nodes and activations of each layer layer_configs = self.decode_sequence(sequence) # create a sequential model model = Sequential() # add a flatten layer if the input is 3 or higher dimensional if len(mlp_input_shape) > 1: model.add(Flatten(name='flatten', input_shape=mlp_input_shape)) # for each element in the decoded sequence for i, layer_conf in enumerate(layer_configs): # add a model layer (Dense or Dropout) if layer_conf is 'dropout': model.add(Dropout(self.mlp_dropout, name='dropout')) else: model.add(Dense(units=layer_conf[0], activation=layer_conf[1])) else: # for 2D inputs for i, layer_conf in enumerate(layer_configs): # add the first layer (requires the input shape parameter) if i == 0: model.add(Dense(units=layer_conf[0], activation=layer_conf[1], input_shape=mlp_input_shape)) # add subsequent layers (Dense or Dropout) elif layer_conf is 'dropout': model.add(Dropout(self.mlp_dropout, name='dropout')) else: model.add(Dense(units=layer_conf[0], activation=layer_conf[1])) # return the keras model return model ``` 请记住,命名`flatten` 和`dropout` 层是很重要的,因为这些名称对于我们的一次性权重设置和更新会很有用。 现在,我们定义另一个函数来编译我们的模型,它将使用我们在`init`函数中定义的常量来获得一个优化器和损失函数,并使用`model.compile`方法返回一个编译后的模型。 ```py # function to compile the model with the appropriate optimizer and loss function def compile_model(self, model): # get optimizer if self.mlp_optimizer == 'sgd': optim = optimizers.SGD(lr=self.mlp_lr, decay=self.mlp_decay, momentum=self.mlp_momentum) else: optim = getattr(optimizers, self.mlp_optimizer)(lr=self.mlp_lr, decay=self.mlp_decay) # compile model model.compile(loss=self.mlp_loss_func, optimizer=optim, metrics=self.metrics) # return the compiled keras model return model ``` ### 一次性建筑 除此之外,我们将处理的另一个有趣的概念是一次性学习或参数共享。参数共享的概念由 [Pham et al. (2018)](https://arxiv.org/pdf/1802.03268.pdf) 引入并推广,其中控制器通过在大型计算图中搜索最佳子图来发现神经网络架构。用策略梯度训练控制器,以选择使验证集上的期望回报最大化的子图。 这意味着整个搜索空间被构建到一个大的计算图中,每个新的架构只是这个超级架构的一个子图。本质上,层的所有可能组合之间的所有权重都可以相互转移。例如,如果生成的第一个神经网络具有以下体系结构: ```py [(16, 'relu'), (32, 'relu'), (10, 'softmax')] ``` 它的重量是这样的: ```py 16 X 32 32 X 10 ``` 然后,如果第二个网络具有以下体系结构: ```py [(64, 'relu'), (32, 'relu'), (10, 'softmax')] ``` 它的重量是这样的: ```py 64 X 32 32 X 10 ``` 单次架构方法或参数共享将希望在给定数据上训练第二架构之前,将第二层和最终层之间的训练权重从第一架构转移到第二架构。 因此,我们的算法要求我们始终保持不同层对及其相应权重矩阵的映射。在我们训练任何新的架构之前,我们需要查看特定的层组合在过去是否出现过。如果是,则转移权重。如果没有,权重被初始化,模型被训练,并且新的层组合与权重一起被记录到我们的映射中。 这里要做的第一件事是初始化一个熊猫数据帧来存储我们所有的体重。可以选择将 NumPy 数组直接存储在不同的*中。npz* 文件,或者任何其他你觉得方便的格式。 为此,我们将这段代码添加到`init` 函数中。 ```py if self.mlp_one_shot: # path to shared weights file self.weights_file = 'LOGS/shared_weights.pkl' # open an empty dataframe with columns for bigrams IDs and weights self.shared_weights = pd.DataFrame({'bigram_id': [], 'weights': []}) # pickle the dataframe if not os.path.exists(self.weights_file): print("Initializing shared weights dictionary...") self.shared_weights.to_pickle(self.weights_file) ``` 一次性学习需要我们完成两项任务: * 在我们开始训练之前,设定架构的权重 * 用新训练的重量更新我们的数据框架 我们为这些写了两个函数。这些函数采用一个模型,逐层提取配置,并将其转换为二元模型–一个 32 节点层后跟一个 64 节点层,因此大小为 *(32 x 64)* ,而一个 16 节点层后跟另一个 16 节点层意味着大小为 *(16 x 16)* 。我们从配置中删除了辍学,因为辍学不影响重量大小。 一旦我们有了这些,在设置重量时,我们查看所有可用的存储重量,看看我们是否已经有了满足重量转移标准的重量。如果是这样,我们转移这些重量;如果没有,我们让 Keras 自动初始化权重。 ```py def set_model_weights(self, model): # get nodes and activations for each layer layer_configs = ['input'] for layer in model.layers: # add flatten since it affects the size of the weights if 'flatten' in layer.name: layer_configs.append(('flatten')) # don't add dropout since it doesn't affect weight sizes or activations elif 'dropout' not in layer.name: layer_configs.append((layer.get_config()['units'], layer.get_config()['activation'])) # get bigrams of relevant layers for weights transfer config_ids = [] for i in range(1, len(layer_configs)): config_ids.append((layer_configs[i - 1], layer_configs[i])) # for all layers j = 0 for i, layer in enumerate(model.layers): if 'dropout' not in layer.name: warnings.simplefilter(action='ignore', category=FutureWarning) # get all bigram values we already have weights for bigram_ids = self.shared_weights['bigram_id'].values # check if a bigram already exists in the dataframe search_index = [] for i in range(len(bigram_ids)): if config_ids[j] == bigram_ids[i]: search_index.append(i) # set layer weights if there is a bigram match in the dataframe if len(search_index) > 0: print("Transferring weights for layer:", config_ids[j]) layer.set_weights(self.shared_weights['weights'].values[search_index[0]]) j += 1 ``` 在更新权重时,我们再次查看熊猫数据帧中所有存储的权重,看看我们是否已经有了与训练后的模型中相同大小和激活的权重。如果是,我们用新的重量替换数据框中的重量。否则,我们在数据帧的新行中添加新的形状二元模型以及权重。 ```py def update_weights(self, model): # get nodes and activations for each layer layer_configs = ['input'] for layer in model.layers: # add flatten since it affects the size of the weights if 'flatten' in layer.name: layer_configs.append(('flatten')) # don't add dropout since it doesn't affect weight sizes or activations elif 'dropout' not in layer.name: layer_configs.append((layer.get_config()['units'], layer.get_config()['activation'])) # get bigrams of relevant layers for weights transfer config_ids = [] for i in range(1, len(layer_configs)): config_ids.append((layer_configs[i - 1], layer_configs[i])) # for all layers j = 0 for i, layer in enumerate(model.layers): if 'dropout' not in layer.name: warnings.simplefilter(action='ignore', category=FutureWarning) #get all bigram values we already have weights for bigram_ids = self.shared_weights['bigram_id'].values # check if a bigram already exists in the dataframe search_index = [] for i in range(len(bigram_ids)): if config_ids[j] == bigram_ids[i]: search_index.append(i) # add weights to df in a new row if weights aren't already available if len(search_index) == 0: self.shared_weights = self.shared_weights.append({'bigram_id': config_ids[j], 'weights': layer.get_weights()}, ignore_index=True) # else update weights else: self.shared_weights.at[search_index[0], 'weights'] = layer.get_weights() j += 1 self.shared_weights.to_pickle(self.weights_file) ``` 一旦我们准备好了权重传递函数,我们就可以编写一个函数来训练我们的模型了。 ### 培训生成的架构 如果启用了一次性学习,训练功能将设置模型权重、训练和更新模型权重。否则,它将简单地训练模型并跟踪指标。它将输入数据、Keras 模型、训练模型的时期数、训练测试分割和回调作为输入,并相应地训练模型。 在这个实现中,我们没有添加一旦搜索阶段完成就自动为更多的时期训练最佳模型的功能,但是允许回调作为函数中的变量可以允许我们容易地包括,例如,在我们的最终训练中提前停止。 ```py def train_model(self, model, x_data, y_data, nb_epochs, validation_split=0.1, callbacks=None): if self.mlp_one_shot: self.set_model_weights(model) history = model.fit(x_data, y_data, epochs=nb_epochs, validation_split=validation_split, callbacks=callbacks, verbose=0) self.update_weights(model) else: history = model.fit(x_data, y_data, epochs=nb_epochs, validation_split=validation_split, callbacks=callbacks, verbose=0) return history ``` 我们的 MLP 发电机准备好了。 ## 要记住的事情 有几件事要记住,而处理一杆训练。一次性训练法有几个不容易回答的问题。例如: * 因为没有预先训练好的权重被转移,所以在获得排名时,更快得到训练的模型处于固有的劣势吗? * 权重的转移有没有可能损害某个特定架构的性能,而不是提高它? * 一次性架构方法论如何改变管制员的训练? 除了考虑上面提到的问题之外,还有一个特定于实现的细节需要注意。 单次训练权重必须以有效存储、搜索和检索的方式保存。我意识到,将它们存储在 Pandas 数据帧中会使 NAS 后期的权重转移花费更长的时间,因为数据帧中已经填充了许多权重,搜索它们以进行正确的转移需要更长的时间。如果您有另一种策略来更快地存储和检索权重,那么您应该尝试在您自己的实现中测试它。当你浏览的搜索空间变得巨大,你想要更深或更复杂的架构(想想 CNN 或 ResNet)等时,这变得更加重要。 ## 结论 在神经结构搜索系列的第二部分中,我们研究了编码序列到 Keras 结构的自动转换。我们为我们的问题建立了一个搜索空间,对描述层配置的元组进行编码的函数,以及将编码值转换为层配置元组的解码函数。 我们研究了将权重分别传递给每一层,并存储它们的权重以备将来使用。我们看到了如何编译和训练这些模型,并使用 Pandas 数据帧根据它们创建的二元模型存储层权重。我们使用相同的二元模型来检查在新的架构中是否有可以转移的权重。 最后,我们将这些编译后的模型与损失函数、优化器、时期数等信息一起使用。编写用于训练模型的函数。 在下一部分中,我们将设计一个控制器,它可以创建数字序列,这些序列可以通过`MLPGenerator` *转换成有效的架构。*我们还将研究控制器本身是如何被训练的,以及我们是否可以通过调整控制器架构来获得更好的结果。 我希望你喜欢这篇文章。 # 神经结构搜索第 4 部分:强化梯度和评估 > 原文:<https://blog.paperspace.com/neural-architecture-search-reinforce-gradient/> 到目前为止,我们已经建立了神经结构搜索(NAS)管道的两个主要组件。在本系列的第二部分中,我们创建了一个模型生成器,它获取编码序列并从中创建 MLP 模型,编译它们,从先前训练的架构中转移权重,并训练新的架构。在本系列的[第三部分](https://blog.paperspace.com/neural-architecture-search-controllers/)中,我们着眼于为我们的神经架构搜索工具构建控制器,该工具使用 LSTM 控制器对架构序列进行采样。我们还研究了如何在控制器中加入精度预测器。 在这个系列的最后一部分,我们将看看如何让这两个组件一起工作。我们将涵盖自定义损失函数以及如何使用增强梯度来训练我们的控制器。我们还将实现我们的搜索逻辑,并查看一些评估工具。 本教程将涵盖: * 管道 * 训练模型 * 记录模型度量 * 控制器的数据准备 * 加固坡度 * 培训管制员 * 评估模型指标 * 了解结果 ## 管道 正如我们在本系列的前几部分中所讨论的,我们看到整个管道包括以下步骤- 1. 使用控制器生成代表有效 MLP 架构的编码序列。 2. 将编码序列转换成实际的 MLP 模型。 3. 训练表示 MLP 模型并记录其验证准确性。 4. 利用这种验证准确性和编码的模型架构来训练控制器本身。 5. 重复一遍。 我们已经看到了控制器如何完成第一步,以及模型生成器如何完成第二步和第三步。我们还编写了训练控制器的函数。但是缺少了一个重要的部分。在我们的控制器中,我们以一种将损失函数作为输入的方式编写训练函数。 在我们进入所有这些之前,让我们开始构建我们的主 MLPNAS 类。这个类继承了我们在上一部分中编写的控制器类,并且还将 MLPGenerator 类初始化为一个对象。这两个类将相互交互来执行架构搜索。 导入常量文件后,我们将用以下常量初始化它- * 数据集中目标类的数量。 * 每个控制器时期必须采样多少个架构(假设这是控制器的批量大小) * 总共有多少个控制器时代 * 在每个控制器历元上训练控制器多少个历元 * 训练每个生成的架构需要多少个时期 * 计算折扣奖励所需的 alpha 值(稍后将详细介绍) ```py class MLPNAS(Controller): def __init__(self, x, y): self.x = x self.y = y self.target_classes = TARGET_CLASSES self.controller_sampling_epochs = CONTROLLER_SAMPLING_EPOCHS self.samples_per_controller_epoch = SAMPLES_PER_CONTROLLER_EPOCH self.controller_train_epochs = CONTROLLER_TRAINING_EPOCHS self.architecture_train_epochs = ARCHITECTURE_TRAINING_EPOCHS self.controller_loss_alpha = CONTROLLER_LOSS_ALPHA self.data = [] self.nas_data_log = 'LOGS/nas_data.pkl' clean_log() super().__init__() self.model_generator = MLPGenerator() self.controller_batch_size = len(self.data) self.controller_input_shape = (1, MAX_ARCHITECTURE_LENGTH - 1) if self.use_predictor: self.controller_model = self.hybrid_control_model(self.controller_input_shape, self.controller_batch_size) else: self.controller_model = self.control_model(self.controller_input_shape, self.controller_batch_size) ``` ## 训练 MLP 模型 我们使用控制器作为序列生成的模型必须转换成可以训练和评估的 MLP 模型。这些培训和评估指标需要被记录,因为验证的准确性将反馈到我们的奖励函数中。除了这些模型的真实指标之外,如果您使用准确性预测器,我们还需要预测给定序列的验证准确性。 假设控制器正在生成序列,并考虑我们在本系列的第二部分中编写并在上面的 MLPNAS 类中初始化的 MLP 生成器代码,我们可以编写下面提到的架构的创建和训练。我们为分类任务训练我们生成的架构,并且使用分类交叉熵函数,除非类的数量是 2。如果是这种情况,我们使用二进制交叉熵函数。每当一个新的架构将要被训练的时候,我们就洗牌,并返回被训练的模型的历史。mlp_generator 使用的函数将在本系列的第二部分中介绍。 ```py # create architectures using encoded sequences we got from the controller def create_architecture(self, sequence): # define loss function according to number of target labels if self.target_classes == 2: self.model_generator.loss_func = 'binary_crossentropy' # create the model using the model generator model = self.model_generator.create_model(sequence, np.shape(self.x[0])) # compile said model model = self.model_generator.compile_model(model) return model # train the generated architecture def train_architecture(self, model): # shuffle the x and y data x, y = unison_shuffled_copies(self.x, self.y) # train the model history = self.model_generator.train_model(model, x, y, self.architecture_train_epochs) return history ``` 在我们研究如何记录指标,以便我们能够轻松地访问它,并捕获模型所需的所有内容之前,让我们看看控制器如何适应整个 MLPNAS 管道。 ## 存储培训指标 在存储训练指标时,我们需要考虑每个模型被训练的时期数。如果它被训练超过 1 个时期,我们取所有时期的验证准确度的移动*均值。我们可以使用其他策略,比如给予前几个纪元更高的权重来获得奖励,这样它们将有助于优化学习速度更快的架构。 如果准确度预测器是流水线的一部分,则预测的准确度也被附加到每个 MLP 模型训练条目。 ```py def append_model_metrics(self, sequence, history, pred_accuracy=None): # if the MLP models are trained only for a single epoch if len(history.history['val_accuracy']) == 1: # if an accuracy predictor is used if pred_accuracy: self.data.append([sequence, history.history['val_accuracy'][0], pred_accuracy]) # if no accuracy predictor data available else: self.data.append([sequence, history.history['val_accuracy'][0]]) print('validation accuracy: ', history.history['val_accuracy'][0]) # if the MLP models are trained for more than one epoch else: # take a moving average of validation accuracy across epochs val_acc = np.ma.average(history.history['val_accuracy'], weights=np.arange(1, len(history.history['val_accuracy']) + 1), axis=-1) # add predicted accuracies if available else don't if pred_accuracy: self.data.append([sequence, val_acc, pred_accuracy]) else: self.data.append([sequence, val_acc]) print('validation accuracy: ', val_acc) ``` ## 为控制器准备数据 我们通过填充序列来训练编码序列上的控制器,通过将每个序列中的最后一个元素与序列的其余部分分开来将它们分成输入和标签。 我们还返回这些序列的验证精度,因为它们将帮助我们确定控制器训练的奖励以及精度预测器的目标。这个数据是从我们在 MLPNAS 类中初始化的 self.data 列表中获取的,并按照上一节中的详细描述进行填充。 ```py def prepare_controller_data(self, sequences): # pad generated sequences to maximum length controller_sequences = pad_sequences(sequences, maxlen=self.max_len, padding='post') # split into inputs and labels for LSTM controller xc = controller_sequences[:, :-1].reshape(len(controller_sequences), 1, self.max_len - 1) yc = to_categorical(controller_sequences[:, -1], self.controller_classes) # get validation accuracies for each for reward function val_acc_target = [item[1] for item in self.data] return xc, yc, val_acc_target ``` ## 使用增强渐变 正如我们在本系列的第一部分中看到的,有几种方法可以优化控制器——遗传算法、博弈论等。在这个实现中,我们将利用一些所谓的加强梯度。 强化梯度是一种政策优化方法,它利用蒙特卡罗抽样和预期回报优化来实现更好的结果。 在我们的 NAS 框架中,我们可以假设状态是 LSTM 控制器的状态,动作是所述控制器预测的序列中的下一个元素,策略是在给定状态的情况下确定动作的概率分布。如果您记得我们编写的函数,该函数用于在给定前一个序列的情况下对序列的下一个元素进行采样,您将会看到,在将前一个序列作为输入提供给 LSTM 后,我们得到的概率数组(softmax 分布)就是策略。我们使用概率抽样来得到我们的行动。概率抽样鼓励探索。 强化梯度是优化政策梯度的一种方式。 [本文](https://medium.com/@thechrisyoon/deriving-policy-gradients-and-implementing-reinforce-f887949bd63)给出了推导加固梯度的综合指南。该方法的概述如下- 对应于我们的网络的策略的概率项可以使用条件概率、应用对数函数和关于参数的微分而被分成其相应的项。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/92c00c7f07645a3f3a27e24f21b4ed5a.png) 这给了我们以下结果: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/f85fcad86577b2b9c6023f63067e36de.png) 这里 pi 代表参数θ处的策略。 我们扩展这个结果,最后得到以下结果 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/084e5943edbea4a07e396717ec1b1b7e.png) 表达式的左侧是给定对应于时间 t 的状态 s 中的动作 a 的策略的对数可能性(由 pi 表示),或控制器的 softmax 分布。 表达式的右边(括号中的总和)是考察我们的代理人得到的报酬的项。我们想在我们的表达式中加入一个折扣因子,以给予眼前的回报比未来的回报更大的权重。右边的项,从 *t* 开始直到剧集结束的奖励总和项,为此目的被乘以伽马项,该伽马项被提升到等于*t’-t-1*的指数。 最终表达式因此变成 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/503728d7a766f967f8fb31ea9c285d28.png) 其中等式右边右端的总和是折扣奖励。 ## 实施增强梯度 为了实现加强梯度,我们编写了一个自定义的损失函数,实现了上面提到的对数概率和折扣奖励的乘积。 我们定义一个函数来计算折扣奖励,该函数迭代地将 gamma 值增加到计数器,并与该步骤的奖励相乘。迭代加法的次数取决于特定动作的 t 值。获得折扣奖励后,我们根据其 z 值(通过减去*均值并除以标准偏差计算)对其进行归一化,并在我们的自定义损失函数中使用该数组作为折扣奖励。 折扣奖励本身取决于奖励,我们将奖励定义为减去基线的特定网络的验证准确性。有一个基线(在我们的例子中,0.5)可以确保准确率低于 50%的网络受到惩罚,而准确率高于 50%的网络受到奖励。当然,您可以在自己的实现中更改该值以获得更高的精度。 ```py def get_discounted_reward(self, rewards): # initialise discounted reward array discounted_r = np.zeros_like(rewards, dtype=np.float32) # every element in the discounted reward array for t in range(len(rewards)): running_add = 0. exp = 0. # will need us to iterate over all rewards from t to T for r in rewards[t:]: running_add += self.controller_loss_alpha**exp * r exp += 1 # add values to the discounted reward array discounted_r[t] = running_add # normalize discounted reward array discounted_r = (discounted_r - discounted_r.mean()) / discounted_r.std() return discounted_r # loss function based on discounted reward for policy gradients def custom_loss(self, target, output): # define baseline for rewards and subtract it from all validation accuracies to get reward. baseline = 0.5 reward = np.array([item[1] - baseline for item in self.data[-self.samples_per_controller_epoch:]]).reshape( self.samples_per_controller_epoch, 1) # get discounted reward discounted_reward = self.get_discounted_reward(reward) # multiply discounted reward by log likelihood of actions to get loss function loss = - K.log(output) * discounted_reward[:, None] return loss ``` 在 Keras 中编写自定义损失函数时,我们必须确保输入始终是目标和输出,即使我们不会使用任何目标值来训练我们的控制器。我们得到输出的负对数(可能行动的 softmax 分布),并将其乘以折扣奖励。这个损失函数然后在我们的训练过程中被我们选择的优化器优化。 ## 培训管制员 一旦我们有了损失函数,当训练控制器时,我们唯一需要关心的是控制器是否使用精度预测器。如果是,我们使用我们在系列的第三部分中定义的混合控制模型。如果没有,我们使用简单的控制模型。 混合控制器也将把预测的精度作为输入。我们不为精度预测器写一个单独的损失函数。相反,均方误差损失用于精度预测。 ```py def train_controller(self, model, x, y, pred_accuracy=None): if self.use_predictor: self.train_hybrid_model(model, x, y, pred_accuracy, self.custom_loss, len(self.data), self.controller_train_epochs) else: self.train_control_model(model, x, y, self.custom_loss, len(self.data), self.controller_train_epochs) ``` ## 主 NAS 环路 我们已经编写了最终运行整个 NAS 管道所需的所有功能。 主函数看起来像这样: ```py def search(self): # for every controller epoch for controller_epoch in range(self.controller_sampling_epochs): # generate sequences sequences = self.sample_architecture_sequences(self.controller_model, self.samples_per_controller_epoch) # if using a predictor, predict their accuracies if self.use_predictor: pred_accuracies = self.get_predicted_accuracies_hybrid_model(self.controller_model, sequences) # for each sequence generated in a controller epoch for i, sequence in enumerate(sequences): # create an MLP model model = self.create_architecture(sequence) # train said MLP model history = self.train_architecture(model) # log the model metrics if self.use_predictor: self.append_model_metrics(sequence, history, pred_accuracies[i]) else: self.append_model_metrics(sequence, history) # prepare data for the controller xc, yc, val_acc_target = self.prepare_controller_data(sequences) # train the controller self.train_controller(self.controller_model, xc, yc, val_acc_target[-self.samples_per_controller_epoch:]) # save all the NAS logs in a pickle file with open(self.nas_data_log, 'wb') as f: pickle.dump(self.data, f) return self.data ``` ## 常数 在试验我们的 NAS 方法的性能时,可能需要改变许多参数。为了使常量更容易访问,我们创建了一个单独的文件来保存所有必要的参数。您可能已经注意到,我们到目前为止编写的所有脚本都利用了某些初始化值。这些值将从我们下面定义的常量文件中导入。 ```py ######################################################## # NAS PARAMETERS # ######################################################## CONTROLLER_SAMPLING_EPOCHS = 10 SAMPLES_PER_CONTROLLER_EPOCH = 10 CONTROLLER_TRAINING_EPOCHS = 10 ARCHITECTURE_TRAINING_EPOCHS = 10 CONTROLLER_LOSS_ALPHA = 0.9 ######################################################## # CONTROLLER PARAMETERS # ######################################################## CONTROLLER_LSTM_DIM = 100 CONTROLLER_OPTIMIZER = 'Adam' CONTROLLER_LEARNING_RATE = 0.01 CONTROLLER_DECAY = 0.1 CONTROLLER_MOMENTUM = 0.0 CONTROLLER_USE_PREDICTOR = True ######################################################## # MLP PARAMETERS # ######################################################## MAX_ARCHITECTURE_LENGTH = 3 MLP_OPTIMIZER = 'Adam' MLP_LEARNING_RATE = 0.01 MLP_DECAY = 0.0 MLP_MOMENTUM = 0.0 MLP_DROPOUT = 0.2 MLP_LOSS_FUNCTION = 'categorical_crossentropy' MLP_ONE_SHOT = True ######################################################## # DATA PARAMETERS # ######################################################## TARGET_CLASSES = 3 ######################################################## # OUTPUT PARAMETERS # ######################################################## TOP_N = 5 ``` 从一个数据集切换到另一个数据集时,唯一需要更改的是数据集中存在的目标类的数量。 ## 运行 MLPNAS 就是这样。现在我们所要做的就是对我们选择的数据集运行算法!这是最终文件 run.py 的样子。 ```py import pandas as pd from utils import * from mlpnas import MLPNAS from CONSTANTS import TOP_N # read the data data = pd.read_csv('DATASETS/wine-quality.csv') # split it into X and y values x = data.drop('quality_label', axis=1, inplace=False).values y = pd.get_dummies(data['quality_label']).values # let the search begin nas_object = MLPNAS(x, y) data = nas_object.search() # get top n architectures (the n is defined in constants) get_top_n_architectures(TOP_N) ``` 如您所见,我们有一个最终函数来获取我们尚未详述的前 n 个架构。 ## 评估和可视化 当浏览我们从架构搜索中获得的所有数据时,可以进行一些基本的可视化和评估。出于本文的目的,我们将尝试理解: 1. 什么是最好的架构,它们承诺的精度是多少? 2. 在控制器迭代过程中,精度是如何提高的? 3. 我们在搜索中测试的架构的精度分布如何? 我们可以通过使用 Python 中的`mtime`来查看最*的日志。我们还可以隔离最新的事件 ID,并相应地检索我们最*一次运行的 NAS 数据。 ```py def get_latest_event_id(): all_subdirs = ['LOGS/' + d for d in os.listdir('LOGS') if os.path.isdir('LOGS/' + d)] latest_subdir = max(all_subdirs, key=os.path.getmtime) return int(latest_subdir.replace('LOGS/event', '')) ``` 一旦您有了最新的事件 ID,您就可以根据验证准确性来加载和排序数据,以找出哪些数据比其他数据做得更好。 ```py ######################################################## # RESULTS PROCESSING # ######################################################## def load_nas_data(): event = get_latest_event_id() data_file = 'LOGS/event{}/nas_data.pkl'.format(event) with open(data_file, 'rb') as f: data = pickle.load(f) return data def sort_search_data(nas_data): val_accs = [item[1] for item in nas_data] sorted_idx = np.argsort(val_accs)[::-1] nas_data = [nas_data[x] for x in sorted_idx] return nas_data ``` 您可以利用这些经过排序的数据来找出排名前 *n* 的架构。您还可以使用存储的数据来查找解码时编码的架构序列是什么样子,以及它们的验证准确性如何。 ```py def get_top_n_architectures(n): data = load_nas_data() data = sort_search_data(data) search_space = MLPSearchSpace(TARGET_CLASSES) print('Top {} Architectures:'.format(n)) for seq_data in data[:n]: print('Architecture', search_space.decode_sequence(seq_data[0])) print('Validation Accuracy:', seq_data[1]) ``` 下面是一个运行输出的示例,它使用了一次性架构方法,但没有使用精度预测器。 ```py Architecture [(128, 'relu'), (3, 'softmax')] Validation Accuracy: 0.7023747715083035 Architecture [(512, 'relu'), (3, 'softmax')] Validation Accuracy: 0.6857143044471741 Architecture [(512, 'sigmoid'), (64, 'sigmoid'), (3, 'softmax')] Validation Accuracy: 0.6775510311126709 Architecture [(32, 'elu'), (3, 'softmax')] Validation Accuracy: 0.6745825615796176 Architecture [(8, 'elu'), (16, 'tanh'), (3, 'softmax')] Validation Accuracy: 0.6664564111016014 ``` 结果可能因随机数生成器种子而异,建议在运行这些算法之前设置种子,以获得可重复的结果。这些结果是在训练 100 个体系结构和每 10 个体系结构后训练控制器后获得的。运行算法的时间越长,测试的架构越多,结果就越好。 通过查看原始的、未分类的数据,可以发现关于我们搜索的本质的其他事情。在下面的第一个函数`get_nas_accuracy_plot()`中,我们绘制了精度在几次迭代中的变化。 在第二个示例中,`get_accuracy_distribution()`,我们将精度按其最接*的整数值进行分类,并得到一个条形图,显示落入不同整数分类的架构数量,范围从 0 到 100。 ```py def get_nas_accuracy_plot(): data = load_nas_data() accuracies = [x[1] for x in data] plt.plot(np.arange(len(data)), accuracies) plt.show() def get_accuracy_distribution(): event = get_latest_event_id() data = load_nas_data() accuracies = [x[1]*100\. for x in data] accuracies = [int(x) for x in accuracies] sorted_accs = np.sort(accuracies) count_dict = {k: len(list(v)) for k, v in groupby(sorted_accs)} plt.bar(list(count_dict.keys()), list(count_dict.values())) plt.show() ``` 相同的运行为我提供了以下准确度分布。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d0898a0b90f01d9c02aabdd71c1a48e2.png) Accuracies are on the x-axis, and % architectures falling in the accuracy bins on the y-axis. 上述功能只是为了让您开始评估 NAS 方法。您还可以定义更多的评估方法,例如通过找出哪些层被预测为第一层或第二层,哪些隐藏层对精度的影响最大等来了解模型预测。 ## 结论 这就是我们 4 部分系列的结尾。在第 1 部分中,我们看到了神经结构搜索研究目前所处位置的全面概述。我们看到了 NAS 在更广泛的意义上是如何工作的,人们通常用来解决问题的不同方法有哪些,对于同样的问题有哪些鲜为人知的方法,它们的性能如何,等等。我们还看了几篇解决 NAS 算法计算效率这一重要问题的论文。 在第 2 部分中,我们将自动设计 MLP 的简单问题作为一个实现练习来处理。我们构建我们的搜索空间和一个生成器,该生成器将获取搜索空间中设计的编码序列,并将其转换为可以训练和评估的 Keras 架构。我们也探索一次性学习。 第 3 部分讨论控制器,即生成这些编码序列的机制。除了建立一个 LSTM 控制器,我们还着眼于准确性预测,以及它们如何在一个混合控制器模型中联系在一起。 最后,在本文中,我们通过实施增强梯度作为控制器优化方法,将所有这些部分联系在一起。我们还看了一些 NAS 评估技术。我们着眼于实现定制的损失函数,理解折扣奖励,并把它们结合起来,只用最后几行代码就可以运行搜索。我们还研究了一些可视化和分析我们从 MLP 的 NAS 中获得的结果的基本方法。 完整的代码实现可以在[这里](https://github.com/codeaway23/MLPNAS)找到。 我希望你喜欢这个系列。 # 基于张量流的神经机器翻译 > 原文:<https://blog.paperspace.com/neural-machine-translation-with-tensorflow/> 如果你是谷歌翻译或其他翻译服务的粉丝,你是否想过这些程序如何能够像人类一样准确地将一种语言翻译成另一种语言。嗯,驱动这些超人翻译者的底层技术是**神经网络**,我们将建立一种特殊的类型,称为递归神经网络,使用谷歌的开源机器学习库 **TensorFlow 进行**法语到英语的**翻译。** **注意:**本教程假设初级到中级水*的人对 python、神经网络、自然语言处理、TensorFlow 和 Jupyter notebook 有所了解。 本教程的大部分代码来自 TensorFlow 文档页面。 在开始构建我们的网络之前,让我们先来看看这篇文章的概述。 * 我们将从描述如何为任务加载和预处理数据集开始。 * 然后我们将继续解释什么是序列对序列模型,以及它在解决翻译问题中的重要性。 * 然后我们将描述什么是注意力机制以及它帮助解决的问题。 * 在这篇文章的结尾,我们将把我们讨论过的所有内容整合在一起,构建我们的翻译模型 让我们首先加载数据并为训练做好准备。 ### 数据加载和预处理阶段 就个人而言,为自然语言处理任务构建高效的数据输入管道是整个 NLP 任务中最繁琐的阶段之一。因为我们的任务是将一段文本从一种语言翻译成另一种语言,所以我们需要一个*行语料库结构的语料库。幸运的是,我们将要使用的数据集是以这种结构排列的。让我们下载数据集并检查它。(来源:manythings.org)。 ```py !wget https://www.manythings.org/anki/fra-eng.zip !unzip fra-eng.zip ``` 上面的代码片段将下载我们的压缩数据集并解压缩。我们应该在工作区目录中获得两个文件:fra.txt,这是我们将要使用的主要文件,以及 _about.txt(不重要)。让我们看看我们的 fra.txt 文件是如何构造的。我们可以通过使用下面的代码片段来实现。 ```py with open("fra.txt",'r') as f: lines = f.readlines() print(lines[0]) ``` 这段代码输出: ```py output[]: 'Go.\tVa !\n' ``` 我们只看了语料库的第一行。如图所示,它由一个英语单词、短语或句子和一个法语单词、短语或句子组成,用制表符分隔。\n '表示下一对英语和法语句子在新的一行。下一步,我们将为每种语言构建一个类,将语言中的每个单词映射到一个唯一的整数。这很重要,因为计算机只能理解数字,而不能理解字符串和字符。这个类将有三个字典数据结构,一个将每个单词映射到一个唯一的整数,一个将一个整数映射到一个单词,第三个将一个单词映射到它在语料库中的总数。这个类将有两个重要的功能,将语料库中的单词添加到他们的类词典中。让我们来看看如何做到这一点。 ```py class Lang(object): def __init__(self, name): self.name = name self.word2int = {} #maps words to integers self.word2count = {} #maps words to their total number in the corpus self.int2word = {0 : "SOS", 1 : "EOS"} #maps integers to tokens (just the opposite of word2int but has some initial values. EOS means End of Sentence and it's a token used to indicate the end of a sentence. Every sentence is going to have an EOS token. SOS means Start of Sentence and is used to indicate the start of a sentence.) self.n_words = 2 #Intial number of tokens (EOS and SOS) def addWord(self, word): if word not in self.word2int: self.word2int[word] = self.n_words self.word2count[word] = 1 self.int2word[self.n_words] = word self.n_words += 1 else: self.word2count[word] += 1 def addSentence(self, sentence): for word in sentence.split(" "): self.addWord(word) ``` 在我们的类中,addWord 函数只是将一个单词作为键添加到 word2int 字典中,其值是相应的整数。int2word 字典的情况正好相反。请注意,当解析我们的语料库以填充类词典时,我们会跟踪我们遇到一个单词的次数,如果我们已经遇到了一个特定的单词,我们会停止将它添加到 word2int 和 int2word 词典中,而是使用 word2count 词典来跟踪它在我们的语料库中出现的次数。addSentence 所做的只是简单地遍历每个句子,对于每个句子,它将句子拆分成单词,并对每个句子中的每个单词实现 addWord 函数。 我们的语料库由法语单词组成,这些单词可能包含一些类似‘的字符。为了简单起见,我们将它们转换成正常的对应 ASCII 字符(C→C)。此外,我们还在单词和这些单词所附的标点符号之间创建空白。(hello's → hello s)。这是为了确保标点符号的出现不会为一个特定的单词创建两个单词(差整数将被分配给“它们是”并且它们是)。我们可以通过下面的代码片段实现这两个目标。 ```py def unicodeToAscii(s): return "".join(c for c in unicodedata.normalize("NFD", s) \ if unicodedata.category(c) != "Mn") def normalizeString(s): s = unicodeToAscii(s.lower().strip()) s = re.sub(r"([!.?])", r" \1", s) s = re.sub(r"[^a-zA-Z?.!]+", " ", s) return s ``` 让我们结合这两个辅助函数来加载数据集,作为包含句子对的列表。 ```py def load_dataset(): with open("/content/gdrive/My Drive/fra.txt",'r') as f: lines = f.readlines() pairs = [[normalizeString(pair) for pair in line.strip().split('\t')] for line in lines] return pairs pairs = load_dataset() ``` 配对列表的一部分应该如下所示 ```py pairs[856:858] out[]: [['of course !', 'pour sur .'], ['of course !', 'mais ouais !']] ``` 为了减少我们演示的训练时间,我们将过滤掉数据集,删除超过十个单词的句子。我们可以使用下面的函数来实现这一点,该函数遍历我们的词对,并删除句子包含十个以上单词的词对。 ```py MAX_LENGTH = 10 def filterPair(p): return len(p[0].split()) < MAX_LENGTH and \ len(p[1].split()) < MAX_LENGTH def filterPairs(pairs): return [pair for pair in pairs if filterPair(pair)] ``` 我们的神经网络将在计算机上运行,而计算机只能理解数字,但我们的数据集是由字符串组成的。我们需要找到一种方法,将我们的字符串数据集转换成数字,如果你还记得的话,之前我们构建了一个语言类,它使用字典数据结构将单词映射成数字。让我们创建一个助手函数,它将把一个句子和对应于该句子的语言实例作为参数(例如,对于一个英语句子,我们使用英语语言实例),并将该句子编码成计算机可以理解的数值。 ```py def sentencetoIndexes(sentence, lang): indexes = [lang.word2int[word] for word in sentence.split()] indexes.append(EOS_token) """ Iterates through a sentence, breaks it into words and maps the word to its corresponding integer value using the word2int dictionary which we implemented in the language class """ return indexes ``` 下一步,我们将使用单词填充每个语言类中的 word2int 字典,并为新函数中的每个单词分配一个相应的整数。此外,由于我们将对数据集进行批处理,因此我们将对单词长度小于我们建议的最大长度的句子应用填充。让我们看看如何做到这一点。 ```py def build_lang(lang1, lang2, max_length=10): input_lang = Lang(lang1) output_lang = Lang(lang2) input_seq = [] output_seq = [] for pair in pairs: """Iterates through the list of pairs and for each pair, we implement the addSentence function on the English sentence using the English Languuage class. The same is done to the corresponding French sentence""" input_lang.addSentence(pair[1]) output_lang.addSentence(pair[0]) for pair in pairs: """ We convert each sentence in each pair into numerical values using the sentencetoIndexes function we implemented earlier. """ input_seq.append(sentencetoIndexes(pair[1], input_lang)) output_seq.append(sentencetoIndexes(pair[0], output_lang)) return keras.preprocessing.sequence.pad_sequences(input_seq, maxlen=max_length, padding='post', truncating='post'), \ keras.preprocessing.sequence.pad_sequences(output_seq, padding='post', truncating='post'), input_lang, output_lang input_tensor, output_tensor, input_lang, output_lang = build_lang('fr', 'eng') ``` 让我们看看我们的构建函数返回了什么。 ```py print("input_tensor at index 10: {}".format(input_tensor[10])) print("output_tensor at index 10: {}".format(output_tensor[10])) print("corresponding integer value for 'nous' {}".format(input_lang.word2int['nous'])) print("corresponding integer value for 'she' {}".format(output_lang.word2int['she'])) ``` ```py output: [] input_tensor at index 10: [10 18 3 1 0 0 0 0 0 0] output_tensor at index 10: [13 6 1 0 0 0 0 0 0 0] corresponding integer value for 'nous' 97 corresponding integer value for 'she' 190 ``` 我们的数据现在已经准备好输入神经网络,但是为了加快我们的训练过程,我们必须将我们的数据集分批排序。TensorFlow 数据 API 为我们提供了执行这项任务的工具。 ```py BUFFER_SIZE = len(input_tensor) dataset = tf.data.Dataset.from_tensor_slices((input_tensor, output_tensor)).shuffle(BUFFER_SIZE) dataset = dataset.batch(16) ``` 我们现在已经准备好构建我们的模型了。 ### 序列到序列模型 递归神经网络有几种结构,每一种都适用于一组特定的任务。一些例子是用于情感分析等任务的多对一架构和用于音乐生成的一对多架构,但我们将采用多对多架构,该架构适用于聊天机器人等任务,当然还有神经机器翻译。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/56128c17f46febf63f6529f6c4ff5af0.png) different rnn architectures 从上图可以看出,多对多架构有两种类型,但我们将使用第一种,它由两个网络组成:一个接收输入句子,另一个在机器翻译的情况下翻译成另一种语言。这种架构适合我们的任务,因为我们有不同长度的输入和输出。这种特殊的架构被称为**序列到序列模型**。对输入进行编码的网络称为编码器。另一个网络被称为解码器,因为它从编码器接收固定大小的向量来解码输出。这种架构背后的思想是伊利亚·苏茨基弗,奥里奥尔·维尼亚尔斯和阔克·v·勒。论文链接:【https://arxiv.org/pdf/1409.3215.pdfT2 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/633569e77d85e370bda43dcad330de4b.png) 您可能已经注意到了这种架构的一个缺点。如果你没有,这很酷。让我们继续讨论这个缺点。从上图中,我们可以观察到解码器从编码器接收到的唯一信息是图中所示的隐藏状态。这个隐藏状态是一个固定大小的向量,编码器将输入句子的每一位信息都压缩到这个向量中,并将其传递给解码器以生成输出句子。这对于较短的句子可能相当有效,但是对于较长的句子,这种固定大小的向量往往会成为瓶颈。这就是注意力机制成为我们翻译网络的关键部分的原因。 # 注意机制 同年晚些时候,当 Sutskever 和他的团队提出他们的序列到序列架构时,进行了一些努力来克服 Sutskever 模型中的瓶颈。一个引起许多人注意的重大突破是 Yoshua Bengio 和其他一些人在题为**神经机器翻译的论文中通过联合学习对齐和翻译所做的工作。**这种架构背后的基本思想是,每当解码器生成特定的输出字时,它都会考虑输入句子中所有字的信息,并确定输入句子中的哪些字与生成正确的输出字相关。在序列模型中,将特定时间步的数据信息传递到下一个时间步的常见方式是通过隐藏状态。这意味着在我们的模型中,特定时间步的每个隐藏状态都有关于该时间步的单词的一些信息。并非输入句子中的每个单词都需要在输出句子中生成特定的单词,这是有意义的。例如,当我们人类想要将法语句子“Je suis garcon”翻译成“我是一个男孩”时,显然当我们翻译单词“男孩”时,直觉上我们会比我们输入的法语句子中的任何其他单词更注意单词“garcon”。这正是序列对序列模型中注意机制的工作方式——我们的解码器在生成特定单词时会注意特定的单词或一组单词。你可能想知道我们的解码器网络是如何注意单词的。它使用连接或重量。与输入句子中某个特定单词的关联度或权重越大,我们的解码器就越关注这个单词。我们还应该知道,我们的权重应该是我们正在解码的时间步长之前的隐藏状态(这给解码器关于已经解码的字的信息)和编码器输出的函数,但是我们不知道该函数是什么,所以我们让反向传播接管学习适当的权重。我们还确保将输入句子中的所有单词映射到输出句子中的特定单词的所有权重加起来为 1。这通过 softmax 函数来置位。现在我们已经对注意力机制有了一些基本的概念,让我们继续实现我们的模型。我们从编码器开始。(不使用注意机制)。 ### 编码器 编码器通常很容易实现。它由两层组成:嵌入层,将每个令牌(单词)转换为密集表示;递归网络层(为简单起见,我们将使用 Gate 递归单元网络)。 ```py class Encoder(keras.models.Model): def __init__(self, vocab_size, num_hidden=256, num_embedding=256, batch_size=16): super(Encoder, self).__init__() self.batch_size = batch_size self.num_hidden = num_hidden self.num_embedding = num_embedding self.embedding = keras.layers.Embedding(vocab_size, num_embedding) self.gru = keras.layers.GRU(num_hidden, return_sequences=True, recurrent_initializer='glorot_uniform', return_state=True) def call(self, x, hidden): embedded = self.embedding(x) #converts integer tokens into a dense representation rnn_out, hidden = self.gru(embedded, initial_state=hidden) return rnn_out, hidden def init_hidden(self): return tf.zeros(shape=(self.batch_size, self.num_hidden)) ``` 读者应该注意 GRU 实现中的两个非常重要的参数:return_sequences 和 return_state。return_sequences 确保 GRU 输出每个时间步的隐藏状态。请记住,我们需要这些信息来访问输入序列中每个单词的信息。返回状态返回最后一个时间步长的隐藏状态。我们需要这个张量作为解码器的初始隐藏状态。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d2279e3aa6c52a0b2f7fa1e1452f097e.png) 在实现我们的网络的前向传播的呼叫功能中。我们首先将输入张量通过嵌入层,然后通过 GRU 层。这将返回形状(批量大小、最大序列长度、隐藏大小)的 RNN 输出(rnn_out)和形状的隐藏状态(批量大小、隐藏大小)。 **解码器** 与编码器不同,解码器有点复杂。除了嵌入和门控循环网络层之外,它还具有关注层和全连接层。我们先来看看如何实现关注层。 ```py class BahdanauAttention(keras.models.Model): def __init__(self, units): super(BahdanauAttention, self).__init__() self.W1 = keras.layers.Dense(units) self.W2 = keras.layers.Dense(units) self.V = keras.layers.Dense(1) def call(self, encoder_out, hidden): #shape of encoder_out : batch_size, seq_length, hidden_dim (16, 10, 1024) #shape of encoder_hidden : batch_size, hidden_dim (16, 1024) hidden = tf.expand_dims(hidden, axis=1) #out: (16, 1, 1024) score = self.V(tf.nn.tanh(self.W1(encoder_out) + \ self.W2(hidden))) #out: (16, 10, 1) attn_weights = tf.nn.softmax(score, axis=1) context = attn_weights * encoder_out #out: ((16,10,1) * (16,10,1024))=16, 10, 1024 context = tf.reduce_sum(context, axis=1) #out: 16, 1024 return context, attn_weights ``` 实现起来似乎很简单,但是当数据在管道中移动时,您真的需要注意维度。发生前向传播的调用函数有两个参数;encoder_out 表示编码器中每个时间步的所有隐藏状态,hidden 表示当前时间步之前的隐藏状态,在当前时间步之前,我们生成正确的字。 由于 hidden 是解码器中时间步长的隐藏状态,我们添加了大小为 1 的维度来表示时间步长,因此 hidden 的形状变为(batch_size,time_step,hidden_size)。前面我提到过,确定特定单词注意力的权重是解码器中紧接该时间步长之前的 hidden_state 和编码器的所有 hidden _ state(encoder _ out)的函数。这正是我们在上面的代码中实现并分配给**分数**的内容。然后,我们应用 softmax 在轴=1 的 max_length 维度上进行评分。我们来看一个直观的解释。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/0546ba92d15797f4bfeee37d801c3674.png) 从上图可以看出,hi 和 zj 分别代表编码器的所有隐藏状态和解码器中紧接时间步长之前的隐藏状态。softmax 函数的最终乘积给出了权重,我们将该权重与来自编码器的所有隐藏状态相乘。在特定时间步长上具有较大权重值的隐藏状态意味着在该时间步长上对单词给予了更多关注。您可能已经注意到,我们执行了一个 reduce_sum 来生成上下文向量。在将每个 hidden_state 乘以其相应的权重之后,我们通过求和来组合所有的结果值。注意力层到此为止。它返回上下文向量和注意力权重。 ```py class Decoder(keras.models.Model): def __init__(self, vocab_size, dec_dim=256, embedding_dim=256): super(Decoder, self).__init__() self.attn = BahdanauAttention(dec_dim) self.embedding = keras.layers.Embedding(vocab_size, embedding_dim) self.gru = keras.layers.GRU(dec_dim, recurrent_initializer='glorot_uniform', return_sequences=True, return_state=True) self.fc = keras.layers.Dense(vocab_size) def call(self, x, hidden, enc_out): #x.shape = (16, 1) #enc_out.shape = (16, 10, 256) #enc_hidden.shape = (16, 256) x = self.embedding(x) #x.shape = (16, 1, 256) context, attn_weights = self.attn(enc_out, hidden) #context.shape = (16, 256) x = tf.concat((tf.expand_dims(context, 1), x), -1) #x.shape = (16, 1, e_c_hidden_size + d_c_embedding_size) r_out, hidden = self.gru(x, initial_state=hidden) out = tf.reshape(r_out,shape=(-1, r_out.shape[2])) # out.shape = (16, 256) return self.fc(out), hidden, attn_weights ``` 在调用函数中,我们传入三个参数,x 表示单个单词的张量,hidden 表示前一时间步的隐藏状态,enc_out 表示编码器的所有隐藏状态,我们将 x 通过嵌入层,该嵌入层将单个整数标记映射为密集的 256 维向量,并将其与关注层生成的上下文向量连接起来。对于单个时间步长,结果张量成为门控递归网络的输入。最后,我们将 GRU 的输出通过一个完全连接的层,该层输出一个大小为(batch_size,英语单词数)的向量。我们还返回隐藏状态,以供下一个时间步使用,并为以后的可视化返回注意力权重。我们的完整模型现在可以开始训练了。下一个议程是培训渠道。我们从损失函数开始。 ```py def loss_fn(real, pred): criterion = keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none') mask = tf.math.logical_not(tf.math.equal(real, 0)) _loss = criterion(real, pred) mask = tf.cast(mask, dtype=_loss.dtype) _loss *= mask return tf.reduce_mean(_loss) ``` 我们使用 keras 的稀疏分类交叉熵模块,因为我们有大量的类别(英语单词的数量)。我们创建一个掩码,声明填充标记不包括在损失计算中。现在,让我们开始我们的培训课程。 ```py def train_step(input_tensor, target_tensor, enc_hidden): optimizer = tf.keras.optimizer.Adam() loss = 0.0 with tf.GradientTape() as tape: batch_size = input_tensor.shape[0] enc_output, enc_hidden = encoder(input_tensor, enc_hidden) SOS_tensor = np.array([SOS_token]) dec_input = tf.squeeze(tf.expand_dims([SOS_tensor]*batch_size, 1), -1) dec_hidden = enc_hidden for tx in range(target_tensor.shape[1]-1): dec_out, dec_hidden, _ = decoder(dec_input, dec_hidden, enc_output) loss += loss_fn(target_tensor[:, tx], dec_out) dec_input = tf.expand_dims(target_tensor[:, tx], 1) batch_loss = loss / target_tensor.shape[1] t_variables = encoder.trainable_variables + decoder.trainable_variables gradients = tape.gradient(loss, t_variables) optimizer.apply_gradients(zip(gradients, t_variables)) return batch_loss ``` 上面的代码片段实现了一个单独的训练步骤。在我们的单个训练步骤中,我们通过编码器的前向传播管道传递表示输入句子的输入张量。这将返回 enc_output(所有时间步长的隐藏状态)和 enc_hidden(最后一个隐藏状态)。注意,编码器的最后隐藏状态被用作解码器的初始隐藏状态。在解码部分,我们使用一种称为教师强制的技术,其中我们使用实际的单词,而不是使用预测的单词作为下一时间步的输入。在解码开始时,我们输入句子开始标记作为输入,并最大化解码器在输出时预测输出序列中第一个单词的概率。然后,我们将实际的第一个字送入第二个时间步长,并最大化解码器在输出序列中预测第二个字的概率。这连续地继续,直到我们到达句子结束标记`<eos>`。我们累积所有的损耗,导出梯度,并用梯度对两个网络进行端到端的训练。这就是完成一个训练步骤所需的全部内容。让我们实现一个助手函数,在训练过程中的某些时候保存我们的模型。 ```py def checkpoint(model, name=None): if name is not None: model.save_weights('/content/gdrive/My Drive/{}.h5'.format(name)) else: raise NotImplementedError ``` 我们培训管道的最后一部分是培训循环。这很容易理解。我们所做的就是运行一些历元,在每个历元,我们迭代我们的数据集,并在每批数据集上调用 train_step 函数。有一些 if-else 语句只是为了在屏幕上记录我们的训练统计数据。让我们看看怎么做。 ```py EPOCHS = 10 log_every = 50 steps_per_epoch = len(pairs) // BATCH_SIZE for e in range(1, EPOCHS): total_loss = 0.0 enc_hidden = encoder.init_hidden() for idx, (input_tensor, target_tensor) in enumerate(dataset.take(steps_per_epoch)): batch_loss = train_step(input_tensor, target_tensor, hidden) total_loss += batch_loss if idx % log_every == 0: print("Epochs: {} batch_loss: {:.4f}".format(e, batch_loss)) checkpoint(encoder, 'encoder') checkpoint(decoder, 'decoder') if e % 2 == 0: print("Epochs: {}/{} total_loss: {:.4f}".format( e, EPOCHS, total_loss / steps_per_epoch)) ``` 运行这个片段训练我们的模型。如果您打算在本地计算机的 CPU 上训练模型,这可能需要一些时间。我建议你使用 Google Colab 或 Kaggle 内核,或者更好的 Paperspace Gradient 笔记本,它们具有支持 GPU 的环境,可以加快训练速度。我已经完成了训练,所以我将加载我的模型的重量,并向您展示我们建立的模型有多好。 ```py encoder.load_weights('/content/gdrive/My Drive/encoder.h5') decoder.load_weights('/content/gdrive/My Drive/decoder.h5') ``` 为了执行翻译,我们需要编写一个函数,就像我们在 train_step 函数中所做的那样,但是我们不是在特定的时间步长将实际的单词输入到下一个时间步长,而是输入由我们的网络预测的单词。这种算法被称为**贪婪搜索**。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/17f1c8edb0a2d55a45b2983ce5d04160.png) 让我们深入代码,看看发生了什么。 ```py def translate(sentence, max_length=10): result = '' attention_plot = np.zeros((10,10)) sentence = normalizeString(sentence) sentence = sentencetoIndexes(sentence, input_lang) sentence = keras.preprocessing.sequence.pad_sequences([sentence],padding='post', maxlen=max_length, truncating='post') encoder_hidden = hidden = [tf.zeros((1, 256))] enc_out, enc_hidden = encoder(sentence, encoder_hidden) dec_hidden = enc_hidden SOS_tensor = np.array([SOS_token]) dec_input = tf.squeeze(tf.expand_dims([SOS_tensor], 1), -1) for tx in range(max_length): dec_out, dec_hidden, attn_weights = decoder(dec_input, dec_hidden, enc_out) attn_weights = tf.reshape(attn_weights, (-1, )) attention_plot[tx] = attn_weights.numpy() pred = tf.argmax(dec_out, axis=1).numpy() result += output_lang.int2word[pred[0]] + " " if output_lang.int2word[pred[0]] == "EOS": break dec_input = tf.expand_dims(pred, axis=1) return result, attention_plot ``` 我们的 translate 函数接受两个参数,句子和输入句子的最大长度。句子经过一个预处理阶段,在那里它被规范化,转换成整数值并被填充。预处理后的张量通过编码器生成 encoder_output 和 encoder_hidden,并传送给解码器。注意,在解码阶段,解码器首先接收句子开始的<sos>标记作为第一个输入单词或标记。在通过第一时间步连同编码器最后隐藏状态和用于注意机制的编码器的所有隐藏状态一起向前传播句子开始标记之后,输出概率分布,其中想要预测的单词在该分布中具有最高值。取这个分布的 argmax 只是返回预期单词(分布中的最大值)在分布中的整数位置。这个整数位置实际上是我们的语言类中 int2word 映射中单词的整数对应。我们使用 int2word 字典检索字符串单词,将它附加到一个字符串中,并将对应的整数反馈到下一个时间步,并重复该过程,直到我们遇到句子结束标记。我们还用每个时间步产生的注意力权重填充 attention_plot 数据容器。返回的最终字符串应该有我们输入的输入句子的翻译。让我们看一个演示!</sos> ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ddc0ccfef4aaa32188787cf4773ce10c.png)![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/88c2fd0638660c9284926c48f42f8903.png) 它很有魅力。我们也来看看网络做出的一些错误预测。诚然,我真的好不容易才搜索到网络做出错误预测的句子。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/5ab281d836214ffbd82cac921f660861.png) 根据谷歌翻译的正确预测是:我比你大。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/62ff85fa9f84f792532cce1dacd0a1d5.png) 我不喜欢你跟我说话的方式。 让我们通过验证我们的注意力机制工作正常来结束这篇文章。我们将想象当在输出句子中生成一个单词时,网络对输入句子中的每个单词投入了多少注意力。 ```py def plot_attention(attention, sentence, predicted_sentence): fig = plt.figure(figsize=(10,10)) ax = fig.add_subplot(1, 1, 1) ax.matshow(attention, cmap='viridis') fontdict = {'fontsize': 14} ax.set_xticklabels([''] + sentence, fontdict=fontdict, rotation=90) ax.set_yticklabels([''] + predicted_sentence, fontdict=fontdict) ax.xaxis.set_major_locator(ticker.MultipleLocator(1)) ax.yaxis.set_major_locator(ticker.MultipleLocator(1)) plt.show() ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/f66ccf1bd1deff41c9f6a665f706243a.png) 与注意力机制论文中展示的情节相比,这并不完美,但我们可以看到,它在一些词如“il”和“he”以及“eglise”和“church”方面做得相当不错。 ### 后续步骤 这就是本教程的内容。下一步,我建议你使用更大的数据集,比如 WMT-17。这可以很容易地从 TensorFlow-datasets 框架中获得。另外,你在这里学到的知识可以扩展到其他领域,比如聊天机器人和图像字幕。不要停在这里。继续挖。 机器翻译和序列到序列模型的领域是研究团体非常关注的领域,因此不断开发新的模型。最*开发的最先进的模型之一是变压器网络,它也可以执行翻译任务,当然比我们刚刚构建的架构要好。我想你也应该去看看。 ### Jupyter 笔记型电脑 [下载](https://s3.amazonaws.com/ps.public.resources/blog/machine_translation_tensorflow_orig.ipynb) **确认** 非常感谢 TensorFlow 团队提供了一个关于所讨论主题的详细记录的网页。我真的受到了工作的启发。 **关于我** 我是一名本科生,目前在读电气电子工程。我也是一个深度学习爱好者和作家。我的工作主要集中在计算机视觉和自然语言处理上。我希望有一天能打入自动驾驶汽车领域。你可以在推特(@henryansah083)上关注:[https://twitter.com/henryansah083?s=09](https://twitter.com/henryansah083?s=09)LinkedIn:[https://www.linkedin.com/in/henry-ansah-6a8b84167/](https://www.linkedin.com/in/henry-ansah-6a8b84167/)。 # 加速推理:神经网络剪枝解释 > 原文:<https://blog.paperspace.com/neural-network-pruning-explained/> 在过去的几年里,DNNs(深度神经网络)已经在计算机视觉和自然语言处理领域的几个具有挑战性的任务上取得了最先进的性能。在越来越多的数据和计算能力的驱动下,深度学习模型变得更加广泛和深入,以更好地从大量数据中学习。但是,将大型、精确的深度学习模型部署到资源受限或成本有限的计算环境中总是会带来几个关键挑战。 在本系列中,您将了解深度 CNN(卷积神经网络)模型对内存和处理优化的需求。然后,我们将详细介绍运行加速和内存优化深度学习模型推理的各种技术。我们还将讨论降低深度学习模型的推理成本所需的各种深度学习推理框架/库。我们将讨论 C++推理加速器框架和库的作用,以及它们的 Python APIs。 > 作为先决条件,本系列要求您具备 Python 和 CNN 的基础知识。 具体来说,我们将涵盖以下 topics:‌‌ 1. 推论的需要 optimization‌ 2. 推理优化技术:修剪 3. 修剪方法 4. 修剪训练好的神经网络 ## 推理优化的需要 深度学习模型推理与模型训练一样重要,并且最终控制所实施的解决方案的性能指标。对于给定的应用程序,一旦深度学习模型训练有素,下一步将是确保它可以部署/生产,这需要应用程序和模型都高效可靠。 > 高效模型是在内存利用率和处理速度方面都进行了优化的模型。 在模型性能/准确性和推理时间/处理速度之间保持健康的*衡是非常重要的。推理时间决定了实现的解决方案的运行成本。有时,部署解决方案的环境/基础设施/系统也可能有内存限制,因此拥有内存优化和实时(或更短处理时间)模型非常重要。 随着需要实时处理的增强现实、面部识别、面部认证和语音助手的使用越来越多,开发人员正在寻找更新、更有效的方法来降低神经网络的计算成本。最流行的方法之一叫做修剪。 ## prunning 解释说 虽然现代深度 CNN 由各种层类型组成,但是预测期间的运行时间由卷积层的评估所支配。为了加快推理速度,我们可以“修剪”特征地图,使生成的网络运行更有效。 修剪是一种流行的方法,用于通过删除冗余来将繁重的网络缩减为轻量级网络。简单地说,修剪是一种减小神经网络大小的方法。 ### 修剪的生物灵感 神经网络中的修剪是从人类大脑中的[突触修剪](https://en.wikipedia.org/wiki/Synaptic_pruning) 中得到的想法,其中[轴突](https://en.wikipedia.org/wiki/Axon)和[树突](https://en.wikipedia.org/wiki/Dendrite)在许多[哺乳动物](https://en.wikipedia.org/wiki/Mammal)的幼儿期和青春期开始之间衰退和死亡,导致突触消除。修剪从出生时开始,一直持续到 25 岁左右。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/399393a661dd0a7d706e6d37426d199b.png) Christopher A Walsh. Peter Huttenlocher (1931–2013). Nature, 502(7470):172–172, 2013.‌ ### 神经网络中的修剪 修剪神经网络是一个古老的想法,可以追溯到 1990 年,Yann LeCun 的[“最佳脑损伤”](http://yann.lecun.com/exdb/publis/pdf/lecun-90b.pdf)论文。这个想法是,在网络的许多参数中,有些是多余的,对输出没有显著贡献。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/82e7634d90b105d6b4d78d31f2b2dcc7.png) LeCun et al. NIPS’89; Han et al. NIPS’15‌ 神经网络一般看起来像左边的那个:下面一层中的每个神经元都与上面一层有连接,但这意味着我们必须将许多浮点相乘。理想情况下,我们只需将每个神经元连接到少数几个其他神经元上,省下一些乘法运算;这就是所谓的稀疏网络。 如果你可以根据神经元对输出的贡献大小对网络中的神经元进行排序,那么你就可以从网络中移除排序较低的神经元,从而形成一个更小的和更快的网络。 例如,可以根据神经元权重的 L1/L2 范数来进行排序。修剪后,准确率会下降(如果排名巧妙,希望不会下降太多),所以通常会再次训练网络,以恢复一些准确率。如果我们一次修剪太多,网络可能会损坏太多,并且无法恢复。因此,在实践中,这是一个迭代过程——通常称为“迭代修剪”:修剪/训练/重复。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/4d59884397f1f1b9295c17a3d7293689.png) [Pruning Convolutional Neural Networks for Resource Efficient Inference](https://arxiv.org/abs/1611.06440)‌ 第一步是确定哪些神经元重要,哪些(相对)不重要。在此之后,最不重要的神经元将被删除,然后对算法进行微调。此时,可以决定是继续修剪过程还是停止修剪过程。 > 最好从一个较大的网络开始,在训练后进行修剪,而不是一开始就训练一个较小的网络。 ## 不同的修剪方法 修剪可以有多种形式,方法的选择取决于开发人员期望的输出类型。在某些情况下,速度是优先考虑的;在其他情况下,必须减少存储/内存。修剪方法的主要区别在于它们如何处理稀疏结构、评分、调度和微调。 例如,在 VGG16 中,90%的权重在全连接层中,但这些权重仅占全部浮点运算的 1%。直到最*,大多数工作都集中在针对全连接层的修剪上。通过修剪这些层,模型大小可以显著减小,但处理速度却不会。 **非结构化修剪**方法修剪单个参数。这样做产生了稀疏的神经网络,尽管在参数计数方面更小,但可能不会以有助于使用现代库和硬件来提高速度的方式来布置。这也被称为**权重修剪**,因为我们将权重矩阵中的各个权重设置为零。这相当于删除连接,如上图所示(Lecun 等人,NIPS'89,Han 等人,NIPS'15)。 这里,为了实现$k%$的稀疏性,我们根据权重矩阵$W$的大小对各个权重进行排序,然后将最小的$k%$设置为$0$。 **结构化修剪**方法按组考虑参数,移除整个神经元、过滤器或通道,以利用针对密集计算优化的硬件和软件。这也被称为**单元/神经元修剪**,因为我们将权重矩阵中的所有列设置为零,实际上删除了相应的输出神经元。 在[为高效卷积网](https://arxiv.org/abs/1608.08710)方法修剪滤波器中,他们提倡修剪整个卷积滤波器。修剪索引为$k$的过滤器会影响其所在的层和下一层(通过移除其输入)。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/1f127b5d1a08a98daec0ba5143ee862c.png) [Pruning Filters for Efficient ConvNets](https://arxiv.org/abs/1608.08710)‌ 如果下一层是全连接的,并且该通道的特征图的大小将是$ M \乘以 N$,则从全连接层中移除$ M \乘以 N$个神经元。这项工作中的神经元排序相当简单。这是每个过滤器的权重的$L1$范数。在每次修剪迭代中,它们对所有过滤器进行排序,在所有层中全局地修剪$n$个排序最低的过滤器,重新训练,并重复。 > 修剪卷积层中的整个滤波器具有减少存储器的积极副作用。正如在[为资源高效推断修剪卷积神经网络](https://arxiv.org/abs/1611.06440)一文中所观察到的,层越深,修剪的就越多。这意味着最后一个卷积层将被修剪很多,并且它后面的全连接层的许多神经元也将被丢弃。 通过在广泛的神经网络架构(深度 CNN、堆叠 LSTM 和 seq2seq LSTM 模型)中比较具有相同内存占用的大型但经过修剪的模型(大型稀疏模型)和较小但密集的模型(小型密集模型)的准确性,我们发现大型稀疏模型始终优于小型密集模型,并实现了非零参数数量高达 10 倍的减少,而准确性损失最小。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b64a1ead8219cd5ac74c4451d38531b3.png) [Sparse vs dense results for MobileNet](https://arxiv.org/abs/1710.01878) ### 得分 通常的做法是根据参数的绝对值、训练的重要性系数或对网络激活或梯度的贡献来对参数进行评分。一些修剪方法局部比较分数,在网络的每个结构子组件(例如层)内修剪分数最低的一部分参数。其他人从全局考虑分数,相互比较分数,而不考虑参数所在的网络部分。 在深度卷积神经网络的[结构化修剪工作中,排序要复杂得多。他们保留了一组$N$粒子过滤器,代表$N$卷积过滤器被修剪。当由粒子表示的过滤器没有被屏蔽时,基于验证集上的网络准确度为每个粒子分配分数。然后,基于新的分数,对新的剪枝掩码进行采样。由于运行这个过程的计算量很大,他们使用了一个小的验证集来测量粒子分数。](https://arxiv.org/abs/1512.08571) 在[中描述的用于资源有效推断的修剪卷积神经网络的方法](https://arxiv.org/abs/1611.06440)包括修剪每个滤波器,并观察当层改变时成本函数如何改变。这是一种强力方法,如果没有大量计算,它不是很有效。然而,排名方法是高效和直观的。它利用激活和梯度变量作为排名方法,提供了一个更清晰的模型视图。 ### 行程安排 修剪方法在每一步要修剪的网络数量上有所不同。一些方法在单个步骤中一次修剪所有期望的权重。其他方法是在几个步骤中反复修剪网络的固定部分,或者根据更复杂的函数改变修剪的速率。 ### 微调 对于涉及微调的方法,最常见的是使用修剪前训练的权重继续训练网络。备选方案包括将网络倒回至较早状态,并完全重新初始化网络。 在撰写本文时,最*的修剪方法之一是 SynFlow ( [通过迭代保存突触流](https://arxiv.org/abs/2006.05467)来修剪没有任何数据的神经网络)。 **SynFlow** 不需要任何数据来修剪网络,它使用*突触显著性分数*来确定参数的显著性。 ## 评估修剪 修剪可以实现许多不同的目标,包括减少神经网络的存储足迹和推理的计算成本。这些目标中的每一个都倾向于不同的设计选择,并需要不同的评估指标。 例如,在减少网络的存储空间时,所有参数都可以同等对待,这意味着应该评估通过修剪实现的总体压缩率。然而,在降低推理的计算成本时,不同的参数可能会产生不同的影响。例如,在卷积层中,应用于空间较大输入的滤波器比应用于较小输入的滤波器与更多的计算相关联。 不管目标是什么,修剪都会在模型效率和质量之间进行权衡,修剪会增加前者,而(通常)会降低后者。这意味着修剪方法的最佳特征不在于它已经修剪的单个模型,而在于对应于**效率-质量**曲线上不同点的一系列模型。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/f956211fdf61ba704b8fb1220a0b0ddf.png) Source: [What Is the State of Neural Network Pruning?](https://arxiv.org/pdf/2003.03033.pdf) ## 结论 随着越来越多的应用程序利用神经网络,轻量级算法是当务之急。剪枝是一种基本的方法,在 DL 中工作的人应该知道,并在他们的工具包中。在本文中,我们介绍了什么是修剪,它是如何工作的,不同的修剪方法,以及如何评估它们。 敬请关注涵盖如何优化神经网络性能的未来文章! # 张量流的神经类型转移 > 原文:<https://blog.paperspace.com/neural-style-transfer/> ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/8db207188c25df4f524d785564e943f3.png) Photo by [Enzo Tommasi](https://unsplash.com/@11x11?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) / [Unsplash](https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) 几个世纪以来,创造现代独特艺术的图像造型一直是人们感兴趣的视角。一个人能够用他们的技能和艺术风格的混合创造的艺术类型吸引了大量观众和该领域的爱好者以及其他商业承包商。自文艺复兴以来,绘画、雕塑、模型设计、建筑设计和其他艺术作品等创造性艺术作品在拍卖中以高利润出售。然而,最*,人工智能已经成为图像造型、设计和艺术工作的独特方式之一。 在艺术领域,人工智能可以成功完成大量独特的任务。在我们之前的文章中,我们已经研究了生成敌对网络的工作过程。我们已经探索了这些相互竞争的神经网络在不断改进以产生高质量结果的同时相互对抗的能力。一个例子是用 GANs 生成人脸,我们正在创建从未存在过的真实人脸。你可以从下面的[链接](https://blog.paperspace.com/face-generation-with-dcgans/)查看这篇文章。 在本文中,我们将介绍该方法背后的一些基本概念。然后,我们将继续分解神经类型转移以及对该算法的基本概念理解。我们将使用这种神经风格转移方法开发一个简单的项目。对于这个项目的开发,我们将讨论两种方法。在第一种方法中,我们将从头开始开发神经风格转移的整个架构构建。在第二种方法中,我们将使用 TensorFlow Hub 中提供的预训练模型来获得所需的结果。 ## 简介: 人工智能最*最有趣的应用之一是神经类型转移的艺术。我们可以通过将两个或更多的图像混合在一起来创造一些新的和有创意的东西,从而产生独特的艺术作品。在人脸识别和对象检测领域已经有了巨大的改进,其中采用了诸如一次性学习的技术来轻松获得最佳结果。然而,直到最*,神经网络可能带来的艺术创新还没有得到太多关注。2015 年,随着“ [*一种艺术风格的神经算法*](https://arxiv.org/pdf/1508.06576.pdf) ”研究论文的推出,人工智能和深度学习的艺术品现场炸了。 我们介绍了一类深度神经网络,它可以有效地计算卷积神经网络中的大多数图像处理任务。它们由一层层的小计算单元组成,通过这些小计算单元,它们可以以前馈的方式分层次地处理视觉信息。卷积神经网络的每一层都包含几个这样的计算单元,其中有一组图像滤波器,每个滤波器从输入图像中提取某个特征。我们将在本文的下一部分讨论更多的工作方法。 为了跟随本文的其余部分,使用深度学习框架(如 TensorFlow 和 Keras)是一项基本要求。如果读者没有任何关于这些库的详细信息,我会推荐他们查看我以前的两篇博客,它们非常详细地涵盖了所有这些主题。观众可以通过这个[链接](https://blog.paperspace.com/absolute-guide-to-tensorflow/)查看 TensorFlow 的文章,通过下面的[链接](https://blog.paperspace.com/the-absolute-guide-to-keras/)查看 Keras 的博客。在下一节中,我们将继续理解神经风格转移模型的工作方法以及与其相关的大多数重要概念。 * * * ## 理解神经类型转移: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/6cfcb50f593743080a2eebdd857c2723.png) [Image Source](https://arxiv.org/pdf/1508.06576.pdf) 对于神经类型转移图像的生成,我们通常有三个主要的基本组件。其中一个组件是主图像,它充当“*内容*图像,我们可以在其上添加修改。这是我们添加所需作品的基础。修改图片是神经风格转移模型的第二个组成部分,称为“*风格*图像。风格是您可以添加到内容中的味道或变化,从而创建新的图片。利用“*内容*和“*风格*的成分,由神经风格转移算法形成的这个新图像被称为*“生成的”*图像。 这三个主要组成部分中的每一个都可以用它们的首字母来表示。“*内容*”(c)是我们添加美工修改的基础,“*风格*”(s)是指在原图像上添加新的设计。最后,我们有了*“生成的”(g)* 图像,这是由“*内容*”(c)和“*样式*”(s)混合而成的组件。在上面的图像表示中,我们可以注意到建筑物充当了"*内容* " (c),而梵高《星夜》的另一个图像是"*风格* " (s),我们将它们组合在一起,创建一个新的组合*"生成的】(g)* 图像。 我们用来解决这个问题的方法是借助深度 conv 网,特别是 VGG-19 迁移学习模型。将有三个参数通过 VGG-19 模型发送,即原始图像、内容图像和生成的图像。生成的图像最初被初始化为噪声。在训练过程之后,我们希望这个生成的图像变得类似于内容和样式图片的组合。在将我们的输入传递到 VGG-19 层时,我们将确保移除输出和密集层,使得它是仅包含卷积层和汇集层的全连接密集 conv 网。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/184359da59657f770eb825db42679c3e.png) [Image Source](https://arxiv.org/pdf/1508.06576.pdf) 密集 conv 网络的输出通过总损失函数。上图显示了这种损失函数的精确表示。总损失函数等于内容图像和样式图像的损失之和,其中α和β表示超参数。为了更详细地分析和理解神经类型转移,我建议查看下面的[研究论文](https://arxiv.org/pdf/1508.06576.pdf)。 * * * ## 从头开始开发神经类型转移项目: 在文章的这一部分,我们将揭示构造神经风格转移项目的第一批方法之一。我们将从零开始为这个项目的建设采取行动。我们将导入基本库,执行图像操作,创建总损失函数,开发卷积神经网络,并创建训练循环。 我们在开发这个项目时将利用的参考资料是 [Keras](https://keras.io/examples/generative/neural_style_transfer/) 和 [TensorFlow](https://www.tensorflow.org/tutorials/generative/style_transfer) 的官方网站。对于风格图像,我们将使用梵高的星夜图像来构建这个项目的两个部分。下图展示了星夜风格的代表。你可以下载并使用你选择的任何内容和风格的图片。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/dc3593fab52fa84f3c772452e195405c.png) [Image Source](https://pixabay.com/illustrations/starry-night-vincent-van-gough-1093721/) ### 导入基本库: 为了开始这个项目,我们将导入这个项目计算所需的所有基本库。我们将导入 TensorFlow 和 Keras 深度学习框架来构建神经风格转移模型。我们将为特征提取过程导入 VGG-19 迁移学习模型。要了解更多关于迁移学习的知识,我建议查看我以前文章的以下链接-“[迁移学习的完整直观指南(第一部分)](https://blog.paperspace.com/transfer-learning-explained/)”和“[迁移学习的完整实用指南(第二部分)](https://blog.paperspace.com/untitled-6/)”我们还将导入 numpy 库来执行数值运算。 ```py import tensorflow as tf from tensorflow import keras from tensorflow.keras.applications import vgg19 import numpy as np ``` 一旦我们成功地导入了必要的库,我们就可以继续定义所需的参数了。我们将为三个最重要的组件设置路径,即内容、样式和生成图像的路径。所有这三个参数都需要通过我们的深度卷积网络来获得想要的结果。我们还将设置一些超参数,比如内容权重和样式权重。最后,我们还将为生成的图像设置一些尺寸要求。下面是执行以下操作的代码片段。 ```py base_image_path = keras.utils.get_file("paris.jpg", "https://i.imgur.com/F28w3Ac.jpg") style_reference_image_path = keras.utils.get_file("starry_night.jpg", "https://i.imgur.com/9ooB60I.jpg") result_prefix = "paris_generated" # Weights of the different loss components total_variation_weight = 1e-6 style_weight = 1e-6 content_weight = 2.5e-8 # Dimensions of the generated picture. width, height = keras.preprocessing.image.load_img(base_image_path).size img_nrows = 400 img_ncols = int(width * img_nrows / height) ``` ### 图像处理: 导入所需的库和图像路径后,下一步是确保定义一些函数来相应地预处理图像。我们将构造两个函数。第一个功能是预处理图像,并在 VGG-19 迁移学习模型的帮助下相应地加载它们。我们将把图像转换成能够根据需要计算所需操作的张量格式。我们还将构建一个函数,以便在所有需要的计算都按预期执行后,重新创建预处理后的图像。下面是执行这两个操作的代码片段。 ```py def preprocess_image(image_path): # Util function to open, resize and format pictures into appropriate tensors img = keras.preprocessing.image.load_img( image_path, target_size=(img_nrows, img_ncols) ) img = keras.preprocessing.image.img_to_array(img) img = np.expand_dims(img, axis=0) img = vgg19.preprocess_input(img) return tf.convert_to_tensor(img) def deprocess_image(x): # Util function to convert a tensor into a valid image x = x.reshape((img_nrows, img_ncols, 3)) # Remove zero-center by mean pixel x[:, :, 0] += 103.939 x[:, :, 1] += 116.779 x[:, :, 2] += 123.68 # 'BGR'->'RGB' x = x[:, :, ::-1] x = np.clip(x, 0, 255).astype("uint8") return x ``` ### 创建总损失函数: 下一步是创建总损失函数,它将是内容损失和风格损失的组合。下述损失函数的意义如前一节所定义。在下面的代码片段中,我们定义了四个函数,这四个函数对于计算整体损失至关重要。gram 矩阵函数用于计算风格损失。 样式损失函数保持生成的图像接*样式参考图像的局部纹理,而内容损失函数保持生成的图像的高级表示接*基础图像的高级表示。总损失函数用于保持生成的局部相干,这意味着我们希望以逻辑一致的方式保持损失。 ```py def gram_matrix(x): # The gram matrix of an image tensor (feature-wise outer product) x = tf.transpose(x, (2, 0, 1)) features = tf.reshape(x, (tf.shape(x)[0], -1)) gram = tf.matmul(features, tf.transpose(features)) return gram def style_loss(style, combination): S = gram_matrix(style) C = gram_matrix(combination) channels = 3 size = img_nrows * img_ncols return tf.reduce_sum(tf.square(S - C)) / (4.0 * (channels ** 2) * (size ** 2)) def content_loss(base, combination): return tf.reduce_sum(tf.square(combination - base)) def total_variation_loss(x): a = tf.square(x[:, : img_nrows - 1, : img_ncols - 1, :] - x[:, 1:, : img_ncols - 1, :]) b = tf.square(x[:, : img_nrows - 1, : img_ncols - 1, :] - x[:, : img_nrows - 1, 1:, :]) return tf.reduce_sum(tf.pow(a + b, 1.25)) ``` ### 发展深层卷积网络; 一旦我们相应地完成了总损失函数的定义,我们就可以着手创建和开发完成神经类型转移任务所需的深度卷积网络的整个架构。与上一节讨论的架构类似,我们将利用 VGG-19 架构,该架构将包含本项目所需的五个基本卷积块。 在这种迁移学习架构中,完全连接的层被忽略和丢弃。我们将利用只有卷积层和池层的深度卷积网络。一旦特征被提取,该网络的输出将通过适当的损失函数,该损失函数是内容和风格损失的组合。 ```py # Build a VGG19 model loaded with pre-trained ImageNet weights model = vgg19.VGG19(weights="imagenet", include_top=False) # Get the symbolic outputs of each "key" layer (we gave them unique names). outputs_dict = dict([(layer.name, layer.output) for layer in model.layers]) # Set up a model that returns the activation values for every layer in # VGG19 (as a dict). feature_extractor = keras.Model(inputs=model.inputs, outputs=outputs_dict) # List of layers to use for the style loss. style_layer_names = [ "block1_conv1", "block2_conv1", "block3_conv1", "block4_conv1", "block5_conv1", ] # The layer to use for the content loss. content_layer_name = "block5_conv2" def compute_loss(combination_image, base_image, style_reference_image): input_tensor = tf.concat( [base_image, style_reference_image, combination_image], axis=0 ) features = feature_extractor(input_tensor) # Initialize the loss loss = tf.zeros(shape=()) # Add content loss layer_features = features[content_layer_name] base_image_features = layer_features[0, :, :, :] combination_features = layer_features[2, :, :, :] loss = loss + content_weight * content_loss( base_image_features, combination_features ) # Add style loss for layer_name in style_layer_names: layer_features = features[layer_name] style_reference_features = layer_features[1, :, :, :] combination_features = layer_features[2, :, :, :] sl = style_loss(style_reference_features, combination_features) loss += (style_weight / len(style_layer_names)) * sl # Add total variation loss loss += total_variation_weight * total_variation_loss(combination_image) return loss ``` ### 创建训练循环: 在从头开始开发神经风格转移模型的第 1 部分中,我们将执行的最后一步是创建训练循环。构建这个部分的第一步是创建装饰培训循环。一旦我们创建了装饰函数,我们的任务将是定义优化器。我们将使用这个项目的学习率和动量随机梯度下降优化器。 然后,我们将继续预处理并加载训练过程所需的所有三个图像。最后,我们将开始训练大约 2000 次迭代的循环。如果你愿意,你可以选择为更多的时期和迭代训练下面的神经风格转移。我们还将确保一旦训练过程完成,我们将使用我们之前定义的去处理图像功能来重新创建生成的图像。您可以在 Paperspace 上运行渐变*台的整个项目。 ```py @tf.function def compute_loss_and_grads(combination_image, base_image, style_reference_image): with tf.GradientTape() as tape: loss = compute_loss(combination_image, base_image, style_reference_image) grads = tape.gradient(loss, combination_image) return loss, grads optimizer = keras.optimizers.SGD( keras.optimizers.schedules.ExponentialDecay( initial_learning_rate=100.0, decay_steps=100, decay_rate=0.96 ) ) base_image = preprocess_image(base_image_path) style_reference_image = preprocess_image(style_reference_image_path) combination_image = tf.Variable(preprocess_image(base_image_path)) iterations = 2000 for i in range(1, iterations + 1): loss, grads = compute_loss_and_grads( combination_image, base_image, style_reference_image ) optimizer.apply_gradients([(grads, combination_image)]) if i % 100 == 0: print("Iteration %d: loss=%.2f" % (i, loss)) img = deprocess_image(combination_image.numpy()) fname = result_prefix + "_at_iteration_%d.png" % i keras.preprocessing.image.save_img(fname, img) ``` ### 输出: ```py Iteration 100: loss=11024.51 Iteration 200: loss=8518.99 . . . Iteration 1900: loss=5496.66 Iteration 2000: loss=5468.01 ``` 培训完成后,请确保检查结果。您可以选择运行以下程序来增加迭代次数和次数,以减少损失并生成更好的结果。 在本文的下一部分,我们将介绍如何使用 TensorFlow Hub 以更少的代码需求更直接地开发同一个项目。然而,最好是理解大多数建筑神经网络构建的详细工作方式,以获得更直观的理解。 * * * ## 使用 TensorFlow Hub 开发神经类型转移; 现在,我们已经了解了如何在 TensorFlow 和 Keras 的帮助下从头开始构建神经类型转移模型,让我们来看看计算这样一个项目的一个更简单的方法。我建议在进行 TensorFlow-Hub 预训练模型方法之前,从头开始学习神经类型转移算法的构造。对于这个实验,我们将利用下面的一座桥的图片作为我们的内容图像,而我们将利用梵高的星夜图像作为样式图像来生成一个新的风格化的生成图像。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/650d98a0ca3b89ffd819ce2be0cb8641.png) Photo by [Manny Ribera](https://unsplash.com/@mannyribera13?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) / [Unsplash](https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) 在我们继续构建我们的项目之前,让我们了解 TensorFlow Hub 到底是什么。TensorFlow Hub 由一系列预训练的深度学习模型组成,可用于各种任务,如 BERT、fast R-CNN 等,我们可以反复使用这些模型来快速生成特定目的的结果。对于可用的模型,您可以对它们进行相应的微调,并将其部署到任何地方来执行特定的任务。有关这个主题的更多信息,请查看 [TensorFlow Hub、](https://www.tensorflow.org/hub)的官方登录页面,在这里您可以构建关于自然语言处理、对象检测、风格转换等更多内容的项目。 ### **导入基本库:** 第一步是导入我们将用于构建这个项目的所有基本库。我们将加载 TensorFlow 深度学习框架以及 TensorFlow Hub,以访问预训练的神经风格转移模型。我们还将导入 matplotlib 库来可视化生成图像的输出。您还可以相应地可视化内容或样式图像。numpy 库将帮助我们压缩生成图像的维度,以便 matplotlib 库可以访问生成的图片。最后,我们将导入计算机视觉 cv2 库,以便在需要时导出和保存生成的图像。 ```py import tensorflow as tf import tensorflow_hub as hub import matplotlib.pyplot as plt import numpy as np import cv2 ``` 在导入所有需要的库之后,我们还可以从 TensorFlow Hub 访问预先训练好的神经风格转移模型。模型链接变量表示到 TensorFlow Hub 网站的链接,该网站包含到所存储和训练的神经类型转移模型的路径。NST 模型变量将加载相应的链接,通过该链接,我们可以直接访问并执行应用神经类型转移算法的操作,而无需太多的编码要求。下面是访问预训练模型的代码片段。 ```py # Access the pre-trained model from TensorFlow-Hub model_link = "https://tfhub.dev/google/magenta/arbitrary-image-stylization-v1-256/2" NST_model = hub.load(model_link) ``` ### **传递和解释数据:** 在下一个代码片段中,我们将创建一个函数来获取数据,加载数据,并对数据进行相应的操作。以下函数将读取相应目录中保存图像的路径。然后,我们将检测和解码特定的图像,将其转换为我们想要的数据类型,并扩展维度。最后,该函数返回操作后的图像。我们将使用构造的函数来加载、访问和操作内容和样式图像。 ```py # Function to load and operate on the content and style images def get_data(img_path): img = tf.io.read_file(img_path) img = tf.image.decode_image(img, channels=3) img = tf.image.convert_image_dtype(img, tf.float32) img = img[tf.newaxis, :] return img ``` 让我们在下一个代码块中加载内容和样式图像。我将上面的图片标记为*‘桥’*,存储在。jfif 格式,它将作为内容图像,梵高的星夜作为*【风格图像】*存储在。jpg 格式将作为样式图像。我们将使用这两个实体来创建一个新生成的带有神经风格转移模型的图像。 ```py content_image = get_data('Bridge.jfif') style_image = get_data('Style Image.jpg') ``` ### **通过加载的模型获取结果:** 最后,我们可以继续生成新的图像,该图像将由加载的预训练神经类型转移模型来执行。我们需要将两个主要参数传递给模型来评估它们并生成结果图像。我们将传递内容图像(桥)作为第一个参数,样式图像(梵高星夜)作为第二个参数。我们将把结果图片存储在生成的图像变量中。下面是将执行以下操作的代码块。 ```py generated_image = NST_model(tf.constant(content_image), tf.constant(style_image))[0] ``` 您可以利用 matplotlib 库来可视化生成的图像,并利用 numpy 库来压缩维度以实现可视化。如果您想要保存图像,您可以使用计算机视觉 cv2 库,并将生成的图像写入您想要保存的目录,文件扩展名为。png,。jpg,。jpeg 或任何其他类似的格式。下面是我在运行 TensorFlow Hub 的预训练神经风格转移模型时能够实现的生成图像。建议尝试不同内容图像和风格图像的多种组合,以生成独特的图片和艺术作品。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/76b80d26887b7d86370dfa8ee15ff506.png) The Generated Image From The Neural Style Transfer Model * * * ## 结论: ![Man in front of multiple art prints](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/06a054521889512d6130195420357353.png) Photo by [Beata Ratuszniak](https://unsplash.com/@beataratuszniak?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) / [Unsplash](https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) 在本文中,我们涵盖了理解神经风格转移算法所需的大多数基本概念。我们了解了这些神经网络到底是如何工作的基本概念知识。在获得了这个概念的详细描述之后,我们研究了两种用神经风格转移构建项目的方法。在第一种方法中,我们从头开始构建整个架构,并使用获得的模型来评估和生成修改后的图像。在第二种方法中,我们利用来自 TensorFlow Hub 的预训练模型来生成两张图片的组合,以创建新生成的图像。 这些深度学习网络集在两个主要成分(即内容图片和风格图片)的帮助下工作,当组合在一起时,产生一个生成的图像。我们可以用这些神经网络生成一堆独特时尚的艺术品。强烈建议观众尝试不同类型的内容和风格,从而产生新的变化。这些神经网络模型有无数种解释的可能性,你可能最终生成一些极具美感和视觉愉悦的东西。 在以后的文章中,我们将讨论 WGANs、变形金刚、从头开始的神经网络、强化学习等主题。在那之前,我强烈推荐尝试神经风格转移算法,继续创造你自己的艺术世代,继续探索! # 公告:增加了纸张空间上的安培 A100、A4000 和 A6000 容量 > 原文:<https://blog.paperspace.com/new-ampere-gpus/> 事实证明,Ampere 微体系结构在每个需要 GPU 计算的领域都占据主导地位,尤其是在深度学习领域。无论我们讨论的是功耗较低的消费级工作站 GPU 还是功耗较高的专业级数据中心 GPU,用户都很难在同级别的老一代 GPU 上实现类似的性能。我们在 [Ampere deep dive](https://blog.paperspace.com/ampere-gpus-with-paperspace/) 和[张量核心](https://blog.paperspace.com/understanding-tensor-cores/)和[混合精度训练](https://blog.paperspace.com/mixed-precision-training/)中概述了 Ampere 如此强大的原因,因此,请务必查看这些内容以了解有关该微架构的更多信息。 在深度学习的事实上的顶级机器:A100 的背景下,这种能力尤其如此。A100 已经在[基准测试任务](https://blog.paperspace.com/best-gpu-paperspace-2022/)中证明,它是对之前数据中心 GPU 峰值 V100 的彻底改进。这些 GPU,无论是单独使用还是在多 GPU 机器实例中使用,都代表了数据科学家技术武器库中的最先进水*。 Paperspace 欣喜若狂地宣布,我们将为用户带来更多这种强大的 Ampere GPUs。这些新机器,包括新的 A100-80GB、A4000 和 A6000 GPUs,现已面向 Paperspace 用户提供,可访问性因地区而异。在下一节中,让我们更深入地了解一下这种新类型的可用实例,将其价格与竞争对手进行对比,并详细说明其区域可用性。然后,我们将讨论其他新的 Ampere GPU 及其区域可用性。 ## Paperspace 上的 A100 - 80 GB ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/8cd4d61d49a65fdc3bea016c7fdaa7b9.png) 我们很高兴地宣布,我们现在在 Paperspace 上有 40 GB 和 80 GB GPU 内存的 A100s。之前只有 40gb GPU 内存的 A100s。除了 GPU 内存规格之外,80 GB 版本与同类产品几乎没有什么不同,但其可用 RAM 却增加了一倍。实际上,这允许这些 GPU 上的用户更好地利用这些发电站机器的大规模吞吐量(1,555 GB/s)能力,以比以往任何时候都更快的速度训练深度学习模型! 80 GB 和 40gb a100 现在都可以配置到更大的多 GPU 实例中。如果您参加了[增长计划](https://www.paperspace.com/pricing),您现在可以选择 1x、2x、4x 和 8x 配置的 A100s。使用这些多 GPU 机器类型中的一种,当与用于多 GPU 实例的深度学习包(如巨型人工智能)结合使用时,可以大大减少您的训练时间。A100s 中的[第三代 NVLink](https://www.nvidia.com/en-us/data-center/ampere-architecture/) 软件进一步加剧了这种情况,与上一代多 GPU 实例相比,它将进一步加速训练。 | GPU | 图形内存 | CPU | 系统内存 | 价格 | | A100 x 1 | 40 GB | 12 | 90 GB | $3.09 /小时 | | A100 x 2 | 40 GB | 24 | 180 GB | $6.18 /小时 | | A100 x 4 | 40 GB | 48 | 360 GB | $12.36 /小时 | | A100 x 8 | 40 GB | 96 | 720 GB | $24.72 /小时 | | A100 x 1 | 80 GB | 12 | 90 GB | $3.18 /小时 | | A100 x 2 | 80 GB | 24 | 180 GB | $6.36 /小时 | | A100 x 4 | 80 GB | 48 | 360 GB | $12.72 /小时 | | A100 x 8 | 80 GB | 96 | 720 GB | $25.44 /小时 | 从上表可以看出,这些多 GPU 机器的定价取决于系统中 GPU 的数量和初始定价。A100- 40 GB x 8 实例每小时 24.72 英镑,在 Paperspace 上比 AWS(不允许用户访问 A100 的 x 1、x 2 或 x 4 实例)和 GCP(类似实例的价格分别为每小时 32.77 美元(T0)和 29.38 美元(t 2)要便宜得多。 这种价格差异特别有趣,因为 100 - 80GB 的实例也更便宜,使用这些机器时,时间和成本之间的直接关系加剧了这种影响。相对较少的其他 GPU 云提供商在 Paperspace 之外提供对这些 100 - 80 GB 机器的访问。像 AWS 和 GCP 这样的主要竞争对手不提供这种 GPU 类型的任何实例,只有 40 GB 版本。尽管如此,价格继续比较有利的那些。例如,Azure 的 NC A100 v4 系列仍在他们的文档中处于预览阶段,单个 GPU 实例的价格将为每小时[3.64 美元](https://azure.microsoft.com/en-us/pricing/details/virtual-machines/linux/#pricing):比 Paperspace 每小时多半美元。 A100s 的这一扩展 GPU 容量目前将在 NY2 中可用,但请务必观看我们的公告页面,了解您所在地区的进一步更新! ## 更多地区有更多安培数! 除了针对 NY2 地区的新款 A100s,我们的其他两个地区也获得了额外的安培机器类型的重大升级。具体来说,这些是单 GPU、x 2 和 x 4 多 GPU 机器类型的 CA1 和 AMS1 中的新 A4000 实例,以及 AMS1 中可用的新 A6000 单 GPU 机器类型。 这些对我们的 Ampere Workstation GPU 产品线的扩展将为上一代迭代提供大规模升级,RTX 4000 和 RTX 6000 GPU 也可在 Paperspace 上获得,并可用于这些地区的用户。 有关 Paperspace 机器地区可用性的更多信息,请访问我们的[文档](https://docs.paperspace.com/core/compute/machine-types)。 ## 结束语 这些强大的 Ampere 系列 GPU 对于任何新用户来说都是微体系结构的一次大规模升级。现在,每个地区都有比以往更多的用户可以在 Paperspace 上访问这些强大的 GPU,并且我们通过新的强大的 100 - 80GB 多 GPU 实例将比以往更多的能力交到了用户手中。 我们一直在努力为用户提供最强大的机器来处理他们在 Paperspace 上的工作。敬请关注我们在[https://updates.paperspace.com/](https://updates.paperspace.com/)举办的活动,了解更多即将推出的新机型和其他纸质空间更新。 # 新功能:高级设置面板 > 原文:<https://blog.paperspace.com/new-feature-advanced-settings-panel/> 从今天开始,所有 Paperspace 用户都可以访问高级菜单,并对其流性能拥有更大的控制权。从今天开始,有两个可用的设置(全彩色和多显示器),我们打算在新功能可用时添加到此列表中。下一版本将启用可选的基于 GPU 的解码器、视网膜显示支持等等。如果有您想要的功能,请告诉我们!我们不断改进,努力让 Paperspace 成为云中最好的计算机。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ca47587d974f5c967ffe73c63f8187a2.png) # 新功能:机器模板 > 原文:<https://blog.paperspace.com/new-feature-machine-templates/> 从今天开始,团队帐户的 Paperspace 可以从他们的机器上创建模板。有了这个特性,团队所有者可以用定制的软件和设置来配置一台机器,然后从主映像中生成他想要的任意多的机器。 创建一个模板只需几秒钟,从头开始创建一台新机器只需几分钟。首先,只需登录您的帐户,进入您的**模板**面板。 # 新渐变 Python SDK > 原文:<https://blog.paperspace.com/new-gradient-sdk/> [2021 年 12 月 2 日更新:本文包含关于梯度实验的信息。实验现已被弃用,渐变工作流已经取代了它的功能。[请参见工作流程文档了解更多信息](https://docs.paperspace.com/gradient/explore-train-deploy/workflows)。] 介绍用于机器学习模型培训、构建和部署的全新 Gradient Python SDK。轻松构建复杂的端到端机器学习管道。 对于许多机器学习开发人员来说,通过简单的编程 API 与他们的开发过程进行交互的能力是一个长期的要求。SDK 加入了我们的命令行实用程序、构建器 GUI 和 GradientCI 构建自动化工具,成为在 Gradient 中构建和部署机器学习模型的一等公民。 **注意:**查看[示例笔记本](https://ml-showcase.paperspace.com/projects/gradient-python-sdk-end-to-end-example),其中包含一个您可以在自己的帐户中运行的端到端示例。 ### 安装 `pip install --pre gradient` ### 快速启动 ```py #Import the SdkClient from the gradient package from gradient import sdk_client #create a API Key api_key = os.getenv("PS_API_KEY") #access all paperspace entities from a single client client = sdk_client.SdkClient(api_key) ``` 这个新的库允许您在 Python 脚本或应用程序中以编程方式与渐变进行交互。它补充了 Gradient CLI 的功能,增加了自动化操作和管道的能力。 下面是一个使用 SDK 的例子,它使用多个工人和一个参数服务器来执行多节点,观察实验的状态转换,并在训练期间传输日志。训练完成后,我们采用相关的 tensorflow 模型&使用 TFServing 作为 rest 端点,由多实例 GPU 支持负载*衡,部署它进行推理。 ### 实例化 SDK 客户端 ```py client = sdk_client.SdkClient(api_key) #or access each component from its own client deployment_client = sdk_client.DeploymentsClient(api_key) models_client = sdk_client.ModelsClient(api_key) jobs_client = sdk_client.JobsClient(api_key) projects_client = sdk_client.ProjectsClient(api_key) experiment_client = sdk_client.ExperimentsClient(api_key) ``` ### 创建项目 ```py project_id = client.projects.create("new project") ``` SDK 将任何创建调用的 id 作为 python 对象返回,以实现简单的脚本编写。 ## 创建多节点分布式实验 ### 步骤 1:设置超参数 ```py #Create a dictionary of parameters for running a distributed/multinode experiment env = { "EPOCHS_EVAL":5, "TRAIN_EPOCHS":10, "MAX_STEPS":1000, "EVAL_SECS":10 } ``` ### 步骤 2:用实验参数创建一个字典 ```py multi_node_parameters = { "name": "multinode_mnist", "project_id": project_id, "experiment_type_id": 2, "worker_container": "tensorflow/tensorflow:1.13.1-gpu-py3", "worker_machine_type": "K80", "worker_command": "pip install -r requirements.txt && python mnist.py", "experiment_env": env, "worker_count": 2, "parameter_server_container": "tensorflow/tensorflow:1.13.1-gpu-py3", "parameter_server_machine_type": "K80", "parameter_server_command": "pip install -r requirements.txt && python mnist.py", "parameter_server_count": 1, "workspace_url": "https://github.com/Paperspace/mnist-sample.git", "model_path": "/storage/models/tutorial-mnist/", "model_type": "Tensorflow" } ``` ### 步骤 3:运行训练实验 ```py #pass the dictionary into experiments client experiment_id = client.experiments.run_multi_node(**multi_node_parameters) ``` ### 步骤 3a:观察实验启动时的状态转换 ```py from gradient import constants.ExperimentState print("Watching state of experiment") state = "" while state != "running": new_state = client.experiments.get(experiment_id).state new_state = ExperimentState.get_state_str(new_state) if new_state != state: print("state: "+new_state) state = new_state ``` ```py Watching state of experiment state: created state: provisioning state: provisioned state: network setting up state: network setup state: running ``` ![Screen-Shot-2019-08-29-at-11.16.58-PM](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/696c71eb8b05cc81a425b76fb755af61.png) ![Screen-Shot-2019-08-29-at-11.18.36-PM](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/11f5baa5b24a273913b3bf90c4c8ff7e.png) ### 步骤 3b:在运行时流式传输日志 ```py log_streamer = client.experiments.yield_logs(experiment_id) print("Streaming logs of experiment") try: while True: print(log_streamer.send(None)) except: print("done streaming logs") ``` ```py Streaming logs of experiment LogRow(line=115, message='2019-08-30 02:14:55.746696: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1115] Created TensorFlow device (/job:worker/replica:0/task:1/device:GPU:0 with 10790 MB memory) -> physical GPU (device: 0, name: Tesla K80, pci bus id: 0000:00:04.0, compute capability: 3.7)', timestamp='2019-08-30T02:14:55.747Z') LogRow(line=115, message='2019-08-30 02:14:54.799712: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1115] Created TensorFlow device (/job:master/replica:0/task:0/device:GPU:0 with 10790 MB memory) -> physical GPU (device: 0, name: Tesla K80, pci bus id: 0000:00:04.0, compute capability: 3.7)', timestamp='2019-08-30T02:14:54.799Z') LogRow(line=115, message='2019-08-30 02:14:55.041046: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1115] Created TensorFlow device (/job:ps/replica:0/task:0/device:GPU:0 with 10790 MB memory) -> physical GPU (device: 0, name: Tesla K80, pci bus id: 0000:00:04.0, compute capability: 3.7)', timestamp='2019-08-30T02:14:55.041Z') LogRow(line=116, message='2019-08-30 02:14:55.043605: I tensorflow/core/distributed_runtime/rpc/grpc_channel.cc:252] Initialize GrpcChannelCache for job master -> {0 -> 10.138.0.213:5000}', timestamp='2019-08-30T02:14:55.043Z') LogRow(line=116, message='2019-08-30 02:14:54.802267: I tensorflow/core/distributed_runtime/rpc/grpc_channel.cc:252] Initialize GrpcChannelCache for job master -> {0 -> localhost:5000}', timestamp='2019-08-30T02:14:54.802Z') LogRow(line=116, message='2019-08-30 02:14:55.749520: I tensorflow/core/distributed_runtime/rpc/grpc_channel.cc:252] Initialize GrpcChannelCache for job master -> {0 -> 10.138.0.213:5000}', timestamp='2019-08-30T02:14:55.749Z') LogRow(line=117, message='2019-08-30 02:14:55.749569: I tensorflow/core/distributed_runtime/rpc/grpc_channel.cc:252] Initialize GrpcChannelCache for job ps -> {0 -> 10.138.0.71:5000}', timestamp='2019-08-30T02:14:55.749Z') LogRow(line=117, message='2019-08-30 02:14:54.802300: I tensorflow/core/distributed_runtime/rpc/grpc_channel.cc:252] Initialize GrpcChannelCache for job ps -> {0 -> 10.138.0.71:5000}', timestamp='2019-08-30T02:14:54.802Z') LogRow(line=117, message='2019-08-30 02:14:55.043637: I tensorflow/core/distributed_runtime/rpc/grpc_channel.cc:252] Initialize GrpcChannelCache for job ps -> {0 -> localhost:5000}', timestamp='2019-08-30T02:14:55.043Z') LogRow(line=118, message='2019-08-30 02:14:55.043659: I tensorflow/core/distributed_runtime/rpc/grpc_channel.cc:252] Initialize GrpcChannelCache for job worker -> {0 -> 10.138.0.213:5000, 1 -> 10.138.0.29:5000}', timestamp='2019-08-30T02:14:55.043Z') LogRow(line=118, message='2019-08-30 02:14:54.802311: I tensorflow/core/distributed_runtime/rpc/grpc_channel.cc:252] Initialize GrpcChannelCache for job worker -> {0 -> 10.138.0.213:5000, 1 -> 10.138.0.29:5000}', timestamp='2019-08-30T02:14:54.802Z') LogRow(line=136, message='I0830 02:14:57.358003 140254722529024 basic_session_run_hooks.py:594] Saving checkpoints for 0 into /storage/models/tutorial-mnist/mnist/model.ckpt.', timestamp='2019-08-30T02:14:57.358Z') LogRow(line=136, message='I0830 02:15:06.821857 140249388017408 session_manager.py:493] Done running local_init_op.', timestamp='2019-08-30T02:15:06.822Z') LogRow(line=137, message='I0830 02:14:58.314819 140254722529024 util.py:164] Initialize strategy', timestamp='2019-08-30T02:14:58.315Z') LogRow(line=137, message='I0830 02:15:06.949163 140249388017408 util.py:164] Initialize strategy', timestamp='2019-08-30T02:15:06.949Z') LogRow(line=138, message='2019-08-30 02:14:58.421029: I tensorflow/stream_executor/dso_loader.cc:152] successfully opened CUDA library libcublas.so.10.0 locally', timestamp='2019-08-30T02:14:58.421Z') LogRow(line=138, message='I0830 02:15:14.057311 140249388017408 basic_session_run_hooks.py:249] cross_entropy = 0.34506965, learning_rate = 1e-04, train_accuracy = 0.94', timestamp='2019-08-30T02:15:14.057Z') LogRow(line=139, message='I0830 02:15:14.057899 140249388017408 basic_session_run_hooks.py:249] loss = 0.34506965, step = 159', timestamp='2019-08-30T02:15:14.058Z') LogRow(line=139, message='I0830 02:15:04.954904 140254722529024 basic_session_run_hooks.py:249] cross_entropy = 2.3063064, learning_rate = 1e-04, train_accuracy = 0.11', timestamp='2019-08-30T02:15:04.955Z') LogRow(line=140, message='I0830 02:15:15.893357 140249388017408 basic_session_run_hooks.py:247] cross_entropy = 0.09328934, learning_rate = 1e-04, train_accuracy = 0.945 (1.836 sec)', timestamp='2019-08-30T02:15:15.893Z') ``` ### 现在查看和部署结果模型 ```py model = client.models.list(experiment_id = experiment_id) ``` ```py deploy_param = { "deployment_type" : "Tensorflow Serving on K8s", "image_url": "tensorflow/serving:latest-gpu", "name": "sdk_tutorial", "machine_type": "K80", "instance_count": 2, "model_id" : model[0].id } mnist = client.deployments.create(**deploy_param) ``` ```py client.deployments.start(mnist) ``` ![Screen-Shot-2019-08-29-at-11.30.03-PM](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ff05fa42c973bf88ce3866e84d13ee0f.png) ```py deployment = client.deployments.list(model_id=model[0].id) ``` #### 以编程方式获得结果端点 ```py print(deployment) print("Endpoint: "+deployment[0].endpoint) ``` ```py [Deployment(id_='des34dp4d58vav1', name='sdk_tutorial', endpoint='https://services.paperspace.io/model-serving/des34dp4d58vav1:predict', api_type='REST', state='Running', model_id='moj0e7uljjrsx8', project_id='pr8kr4qf1', image_url='tensorflow/serving:latest-gpu', deployment_type='Tensorflow Serving on K8s', machine_type='K80', instance_count=2)] Endpoint: 'https://services.paperspace.io/model-serving/des34dp4d58vav1:predict' ``` ### 执行推理 ```py image = get_image_from_drive('example5.png') show_selected_image(image) ``` ![output_13_0](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/fa7e16eca65c73eecda4855c98e27f0f.png) ```py def make_prediction_request(image, prediction_url): vector = make_vector(image) json = { "inputs": [vector] } response = requests.post(prediction_url, json=json) print('HTTP Response %s' % response.status_code) print(response.text) make_prediction_request(image, deployment[0].endpoint) ``` ### 向 REST 端点发送 post 请求并获取分类 ```py HTTP Response 200 { "outputs": { "classes": [ 5 ], "probabilities": [ [ 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, ``` ### 额外资源 [阅读产品文档](https://docs.paperspace.com/gradient/gradient-python-sdk/gradient-python-sdk) [读取自动生成的文档](https://paperspace.github.io/gradient-cli/gradient.api_sdk.clients.html) ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b10672aa7d946511c1082c3098ef3a4d.png) # 多节点/分布式培训,新的 GitHub 应用程序,等等! > 原文:<https://blog.paperspace.com/new-projects-experiments-git-integration/> [2021 年 12 月 2 日更新:本文包含关于梯度实验的信息。实验现已被弃用。有关当前梯度资源的更多信息,请参见[梯度文档](https://docs.paperspace.com/gradient/explore-train-deploy/workflows) 今天,我们很高兴地宣布整个梯度产品线的许多强大的新功能和改进。 首先,我们引入了对多节点/分布式机器学习模型训练的支持。 我们还对 GradientCI 进行了重大升级,这是我们为连接到 GitHub 的 Gradient 提供的突破性持续集成服务。 我们彻底改变了用户与 Gradient 的交互方式,引入项目和实验来轻松组织您的工作和协作。 ## 梯度指数 2.0 <video autoplay="" loop="" class="" style="max-width: 100%; min-height: 410px;"><source type="video/mp4" src="//i.imgur.com/o6jNjcC.mp4"> 我们非常兴奋能够发布我们最新的 GitHub 应用程序 GradientCI。几个月前,我们试运行了 GradientCI 的第一个版本,反响令人难以置信。 在这个版本中,您可以在 Gradient 中创建一个 GradientCI 项目,每当您将代码推送到 GitHub 上的机器学习存储库时,该项目将自动触发一个实验*。只需[安装最新的 GradientCI GitHub App](https://github.com/apps/gradientci) 并配置即可。直接在 web 控制台中轻松查看模型/主机性能指标。* *这套强大的新工具旨在使机器学习管道过程更快、更确定,并且更容易集成到您现有的基于 Git 的工作流中。* *下一步是什么?GradientCI 将很快直接向 GitHub 发送状态检查,您可以在您的 pull 请求中查看它们,其中包含有关您培训表现的丰富信息。* *[https://github.com/apps/gradientci](https://github.com/apps/gradientci)* ## *项目和实验* ### *向项目问好* *当您登录到您的控制台时,您将看到一个新的“项目”选项卡。* *![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/269346134b13b105908d84ee2d73f58b.png)* *项目是在 Gradient 中组织所有机器学习开发的新方法。项目可以是独立的——通过我们的 GUI 或 CLI 手动运行——也可以通过 GradientCI 启用 GitHub。* ### *和...实验* *项目是一个创造性的工作空间,允许您轻松地组织和管理渐变系列的最新成员:实验。* *你可以在每个项目中运行任意数量的实验。实验可以采取多种形式,包括运行多个不同容器协同工作以产生结果的新可能性。**首先是对多节点训练的本地支持。*** *一开始,我们就支持单节点和多节点实验。单节点实验对应一个作业。多节点实验包括多个作业——分布式训练运行的每个节点一个作业。* *正如你可能看到的,实验打开了超参数扫描的大门。在不久的将来,这些将会成为梯度。* *![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/484c7975b4e54f1a01c3da18cc5c4c84.png) Projects and Experiments hierarchy* ## *分布式模型训练* *有了新的项目和实验模型,现在运行带有梯度的多节点训练任务变得非常容易。下面是一个示例项目:* *[https://github.com/Paperspace/multinode-mnist](https://github.com/Paperspace/multinode-mnist)* *Gradient 的原生分布式训练支持依赖于参数服务器模型,每个多节点实验将有一个或多个参数服务器和工作节点。多节点训练使得在比以往更大的数据上训练模型成为可能。* ## *一个现代化的统一人工智能*台* *我们迫不及待地想让你开始体验所有这些强大的新功能和对 [Gradient](https://www.paperspace.com/gradient) 的改进。* *我们产品的这一发展包括对我们广受欢迎的 GradientCI GitHub 应用程序的重大升级,一个新的项目和实验概念模型,以及多节点/分布式培训。我们比以往任何时候都更接*于为现代人工智能工作流提供一个统一的*台。* *请告诉我们您的体验——我们喜欢听客户的意见!同时,[查看文档](https://docs.paperspace.com/gradient)了解更多信息,开始了解所有这些新功能和改进。并期待更多令人惊叹的新功能,即将推出!* # 新视频教程:使用快照 > 原文:<https://blog.paperspace.com/new-video-tutorial-how-to-use-snapshots-2/> 快照是使用虚拟机的众多好处之一。拍摄正在运行机器的快照并随时立即回滚的能力是非常宝贵的。查看我们的快速指南,了解如何在 Paperspace 中创建快照: [https://www.youtube.com/embed/7iCFzEooaIU](https://www.youtube.com/embed/7iCFzEooaIU) # 具有序列到序列模型和点注意机制的机器翻译 > 原文:<https://blog.paperspace.com/nlp-machine-translation-with-keras/> 自然语言处理是一个重要的研究领域。它被认为是人工智能、语言学和计算机科学的一个子领域。现代人工智能系统通过先进的机器学习、深度学习算法和创新来完成这些 NLP 任务的能力已经导致了越来越多的普及,以及对以下问题实现最佳可能结果的压倒性需求。自然语言处理中的一个流行任务包括找到解决*机器翻译*背后的复杂性的最佳解决方案和方法。 在本文中,我们将学习使用一种流行的深度学习方法(称为序列到序列建模)来处理机器翻译任务的一些基本要求。我们将深入探索 *RNNs* 的概念,以及*编码器-解码器*架构来完成这一特定任务。我们还将研究一些注意力机制,并尝试用最简单的方法解决机器翻译问题,以达到最佳效果。 仅对文章的某些技术部分感兴趣的读者可以使用*目录*来阅读特定主题。然而,为了更简明的理解,最好涵盖所有的概念。 ## 简介: 机器翻译是利用人工智能的方法,即深度学习机制(即神经网络架构),以相对较高的准确性和较低的错误(其他术语中的损失)有效地将一种语言的翻译转换成另一种语言。序列对序列建模是解决机器翻译相关任务的最佳方法之一。这是谷歌翻译用来获得理想结果的早期方法之一。 在本文中,我们将了解一些基本概念,这些概念将帮助我们获得解决机器翻译相关问题的最佳方法背后的强大直觉。首先,我们将讨论一些基本主题,这些主题将作为机器翻译项目的一个强有力的起点。在进入机器翻译模型架构的编码部分之前,我强烈建议读者查看我之前关于 [TensorFlow](https://blog.paperspace.com/absolute-guide-to-tensorflow/) 和 [Keras](https://blog.paperspace.com/the-absolute-guide-to-keras/) 的文章。它们将涵盖接*神经网络架构的编码结构所需的所有信息。 * * * ## 理解递归神经网络; ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b37378e87d35993a961e79305b186a7b.png) Image from [Wiki](https://en.wikipedia.org/wiki/Recurrent_neural_network#/media/File:Recurrent_neural_network_unfold.svg) 递归神经网络(RNNs)是一种流行的人工神经网络形式,用于多种任务,如手写识别、语音合成、机器翻译等等。术语循环意味着重复。在 RNN 中,整个神经网络被分成子神经网络,并被馈送到下一组子神经网络。上面显示的图像是一个长序列中递归神经网络的精确表示。 递归神经网络可以捕捉短范围内的相关性,并有可能处理任何长度的信息。构建 RNN 模型体系结构时的 RAM 使用量通常低于其他类型模型(如 n 元模型)中的总 RAM 使用量。权重通过整个递归神经网络组传递。因此,这些网络的计算考虑了所有给定的先前数据。然而,rnn 确实遇到了一个主要问题,因为由于爆炸和消失梯度的问题,它们不能携带长期相关性的相关信息。LSTMs 解决了这个问题,我们稍后将讨论这个问题。 ### 培训程序 递归神经网络的训练过程非常简单,通过仔细观察上图就可以理解。与大多数神经网络类似,训练的两个步骤可以分为前向传播和反向传播。在前向传播期间,所产生的单元输出直接等于所使用的函数类型(sigmoid、tanh 或其他类似函数),即权重和输入与先前输出的点积之和。每个时间步长的正向传播阶段的损失函数可计算如下: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b9730f2cc4cb70717ed6ad260126aa47.png) 递归神经网络的反向传播是在每个相应的时间步长上计算的,而不是在权重上计算的,因为整个子网络形成一个完整的 RNN 网络,并且这些神经网络中的每一个实际上都被分成子网络。每个时间步长上的反向传播损失相对于权重的表示可以计算如下: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/18abac8a075a970881739c7ba9b7c0a0.png) ### RNN 类型简介 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/9104dbc6c3537379fb7a2f3149d465f3.png) [Image Source](https://iq.opengenus.org/types-of-rnn/) 1. **一对一(Tx=Ty=1):** 单个输入产生单个输出。通常用于传统的神经网络。 2. **一对多(Tx=1,Ty > 1):** 给定一个输入,产生多个输出。像音乐生成这样的任务可以利用这样的 RNN。 3. **多对一(Tx > 1,Ty=1):** 给定多个输入共同产生一个输出。这种 rnn 在情感分析中很有用。 4. **多对多(相同长度,Tx=Ty):** 在这种 RNN 中,有许多输入产生许多输出,但是输入节点和输出节点的长度是相等的。它们主要用于命名实体识别。 5. **多对多(长短不一,Tx!=Ty ):** 在这个 RNN 中,有许多输入,产生许多输出,但是输入节点和输出节点的长度不相等。这些类型的 rnn 用于机器翻译的任务。 ### 为什么是 LSTMs? 由于爆炸和消失梯度的问题,rnn 在传输长期数据元素方面存在问题。这些问题的解决方案是由**(***)*提供的,这也是一种人工递归神经网络(RNN)架构,用于解决许多复杂的深度学习问题。** **在今天的机器翻译项目中,我们将利用这些 lstm 构建具有点注意机制的编码器-解码器架构,并展示这些 lstm 如何为解决 rnn 的大多数重复问题奠定基础。如果你想对 LSTMs 背后的理论概念有一个简明的理解,我强烈推荐阅读我的第一部分文章《用时间序列分析预测股票价格》。** * * * ## **了解编码器-解码器架构:** **![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/e6bc3908d7ffcff11d115398b71beecc.png) Image from [Research Paper](https://arxiv.org/pdf/1508.04025.pdf)** > **上面的图像显示了一个堆叠递归架构,用于将源序列 A B C D 转换为目标序列 X Y Z。这里,`<eos>`表示一个句子的结束。** **序列到序列模型通常由编码器和解码器模型组成。编码器和解码器是两个独立的大组件,它们在执行计算时一起工作以产生理想的结果。编码器和解码器模型一起形成序列到序列模型。接收输入序列的过程由编码器完成,解码器具有产生相应目标序列的功能。** **在编码器中,我们采用与特定向量相关的输入序列。然后,我们通过一堆 LSTMs 运行这个向量序列,并存储最后的隐藏状态。让我们考虑$e$作为最后的隐藏状态编码器表示。对于解码器计算,我们需要在$e$和句子开始标签$wsos$的帮助下计算下一个隐藏状态$h0$。$s0$用于*衡相同大小的向量。然后,我们用 SoftMax 函数计算等价向量概率$p0$。最后,我们计算特定语句的最高概率。** **$ $ h0 = LSM(e,wsos) $。** **$$ s0 = g(h0) $$** **$$ p0 = softmax(s0) $$** **$$ i0 = argmax(p0) $$** **对于下一阶段的计算,再次重复这些步骤。下一次计算将利用先前的隐藏状态和权重来计算$h1$。其他步骤基本保持不变。** **$$ h1 = LSTM(h0,wi1) $$** **$$ s1 = g(h1) $$** **$$ p1 = softmax(s1) $$** **$$ i1 = argmax(p1) $$** **现在让我们开始理解顺序使用注意力对模型排序的重要性。** * * * ## **注意力的重要性:** **![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/be08a935e6897879fd95e211d6aee1f7.png) Image From [GitHub](https://guillaumegenthial.github.io/sequence-to-sequence.html)** **在使用序列对序列模型时,区分特定任务中的基本组件有时会变得很重要。我将陈述一个计算机视觉和 NLP 任务的例子。对于计算机视觉任务,让我们考虑一幅狗在地上行走的图像。序列对序列模型可以相当容易地识别以下内容。但是在注意力机制的帮助下,我们可以给图像中的基本成分增加更多的权重,这就是狗。类似地,对于 NLP 任务,我们需要更多地关注特定的单词,以便更好地理解上下文。即使在这些情况下,注意力也是有用的。** **让我们稍微修改一下前面的等式,以增加对我们的结构的关注,并使它适合序列到序列模型架构。修改后的方程如下:** **$ $ ht = LSTM(ht 1,[wit 1,ct])$$** **$$st=g(ht)$$** **$$pt=softmax(st)$$** **$$it=argmax(pt)$$** **$ct$是用于在每个解码步骤处理新上下文向量的上下文向量。我们可以计算每个隐藏状态的分数,然后在 SoftMax 函数的帮助下对序列进行归一化。最后,我们计算加权*均值。** **$ $αt′= f(ht 1,et′)∈R $ $** **$ $ a =软件最大值(a)$ $ a** **$ $ CT =∑t′和$** **注意的类型可以通过考虑功能的选择来分类。在这种情况下,它是$(ht 1,et′)$分量。下面是注意力机制的不同可能性的列表。在机器翻译项目的剩余教程中,我们将使用点注意机制。** **![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/64aca29742605b0bd4ef1852af4bd964.png)** **最后,随着我们对一些关键概念的基本理解,我们可以实际实现我们的机器翻译项目。** * * * ## **使用点注意的序列到序列模型的机器翻译** **在本文的最后一部分,我们将创建一个完整的工作项目,使用点注意力实现序列到序列模型的机器翻译。借助下面的[链接](https://www.tensorflow.org/tutorials/text/nmt_with_attention),你可以实现 Bahdanau 注意的结构。然而,我们将使用一种稍微更独特的方法,一步解码器和点注意机制。这种方法将帮助我们更快更好地简化、理解和完成整个项目。让我们从项目的第一步开始:数据集准备。[Jupyter 笔记本和数据集](https://github.com/gradient-ai/Seq-to-Seq-Machine-Translation)将相应提供。** **### 数据集准备: 在本文的下一小节中,我们将研究数据集的准备工作。数据预处理是一个相当简单的过程,我们将尝试快速浏览本文的这一部分。机器翻译项目的数据集可以从以下[网站](http://www.manythings.org/anki/)下载。我将利用这个项目的意大利语到英语数据集。我会推荐那些想跟随教程的观众使用相同的数据集。您也可以选择从本文提供的附件中下载数据集。 首先,让我们导入一些我们将用于构建这个项目的基本库。 ```py import tensorflow as tf import matplotlib.pyplot as plt import matplotlib.ticker as ticker from sklearn.model_selection import train_test_split import unicodedata import re import numpy as np import os import io import time ``` 一旦完成了所有基本库的导入,清理数据是非常重要的。在接下来的步骤中,我们的主要目标是删除机器翻译任务不需要的任何不必要的信息。下面的代码块用于打开所需的文件并删除无用的数据元素。 ```py file = open("ita.txt", 'r', encoding = "utf8") raw_data = [] for line in file: pos = line.find("CC-BY") line = line[:pos-1] # Split the data into english and Italian eng, ita = line.split('\t') # form tuples of the data data = eng, ita raw_data.append(data) file.close() def convert(list): return tuple(list) data = convert(raw_data) ``` 在下一步中,我们将通过删除冗余空间并将所有内容转换为理想的形式来完成数据集的预处理,以简化机器翻译的过程。我们将通过删除特殊字符并将每个句子填充到最大指定长度来清理句子。最后,我们将在每个句子中添加`<start>`和`<end>`标记。 ```py def unicode_to_ascii(s): return ''.join( c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn') def preprocess_sentence(s): s = unicode_to_ascii(s.lower()) s = re.sub(r'([!.?])', r' \1', s) s = re.sub(r'[^a-zA-Z.!?]+', r' ', s) s = re.sub(r'\s+', r' ', s) s = s.strip() s = '<start>' +' '+ s +' '+' <end>' return s ``` 对于机器翻译过程,我们将数据集限制为 30000 个句子。我这样做是为了减少花费的资源数量,以较小的准确性为代价,更快地完成机器翻译的过程。然而,如果你有足够的资源或更多的时间,我建议使用完整的数据集来获得更好的结果。 ```py # Limiting the data and Splitting into seperate lists and add tokens data = data[:30000] lang_eng = [] lang_ita = [] raw_data_en, raw_data_ita = list(zip(*data)) raw_data_en, raw_data_ita = list(raw_data_en), list(raw_data_ita) for i, j in zip(raw_data_en, raw_data_ita): preprocessed_data_en = preprocess_sentence(i) preprocessed_data_ita = preprocess_sentence(j) lang_eng.append(preprocessed_data_en) lang_ita.append(preprocessed_data_ita) def tokenize(lang): lang_tokenizer = tf.keras.preprocessing.text.Tokenizer( filters='') lang_tokenizer.fit_on_texts(lang) tensor = lang_tokenizer.texts_to_sequences(lang) tensor = tf.keras.preprocessing.sequence.pad_sequences(tensor, padding='post') return tensor, lang_tokenizer input_tensor, inp_lang = tokenize(lang_ita) target_tensor, targ_lang = tokenize(lang_eng) max_length_targ, max_length_inp = target_tensor.shape[1], input_tensor.shape[1] ``` 在下一个代码块中,我们将相应地将数据集分为定型数据和测试数据。分割的比例将是 80:20 的形式。在 30000 个数据元素中,我们将有 24000 个训练元素和它们各自的目标,以及 6000 个测试元素和它们各自的目标预测。最后,我们将把单词映射到一些索引值,作为表示这些值的一种方式。 ```py # Creating training and validation sets using an 80-20 split input_tensor_train, input_tensor_val, target_tensor_train, target_tensor_val = train_test_split(input_tensor, target_tensor, test_size=0.2) # Show length print(len(input_tensor_train), len(target_tensor_train), len(input_tensor_val), len(target_tensor_val)) def convert(lang, tensor): for t in tensor: if t!=0: print ("%d ----> %s" % (t, lang.index_word[t])) print ("Input Language; index to word mapping") convert(inp_lang, input_tensor_train[0]) print () print ("Target Language; index to word mapping") convert(targ_lang, target_tensor_train[0]) ``` **结果:** ```py 24000 24000 6000 6000 Input Language; index to word mapping 1 ----> <start> 12 ----> la 205 ----> prendero 3 ----> . 2 ----> <end> Target Language; index to word mapping 1 ----> <start> 4 ----> i 20 ----> ll 43 ----> get 7 ----> it 3 ----> . 2 ----> <end> ``` 然后,我们将继续定义一些参数,这些参数将对训练过程和数据集的整体准备有用。 ```py BUFFER_SIZE = len(input_tensor_train) BATCH_SIZE = 64 steps_per_epoch = len(input_tensor_train)//BATCH_SIZE vocab_inp_size = len(inp_lang.word_index)+1 vocab_tar_size = len(targ_lang.word_index)+1 dataset = tf.data.Dataset.from_tensor_slices((input_tensor_train, target_tensor_train)).shuffle(BUFFER_SIZE) dataset = dataset.batch(BATCH_SIZE, drop_remainder=True) dataset ``` 一旦我们完成了数据预处理步骤,我们就可以继续构建我们的神经网络架构,从编码器结构开始。编码器将包含所需的 LSTMs 结构和嵌入,如前几节讨论的完整神经网络构建。在进入下一个编码部分之前,我强烈建议分别查看一下 [TensorFlow](https://blog.paperspace.com/absolute-guide-to-tensorflow/) 和 [Keras](https://blog.paperspace.com/the-absolute-guide-to-keras/) 的文章,以便对即将到来的代码块进行更多的分析和批判性思考。 ### 编码器架构: 从现在开始,我们将为所有模型实现模型子类化方法。我们的编码器架构方法是使用主类编码器。然后,我们将利用几个函数,即 __ *init__ 函数、*初始化状态块和*调用*块*。*在 *init* 块中,我们将定义并初始化所有需要的参数。 在编码器的调用块中,我们定义了一个函数,它接受一个输入序列和编码器的初始状态。输入序列的输入通过嵌入层,最后,嵌入层的输出进入编码器 LSTM。call 函数返回编码器的所有输出,以及隐藏和单元状态的最后时间步长。 初始化状态的最后一个函数用于表示隐藏状态和初始单元状态的初始化,相应地与分配给它的批量大小有关。批量大小为 64 将导致隐藏状态形状*【64,lstm _ units】*和单元状态形状*【64,lstm _ units】*。让我们看看下面的代码块,以便更清楚地了解将要执行的操作。 ```py class Encoder(tf.keras.Model): def __init__(self, inp_vocab_size, embedding_size, lstm_size, input_length): super(Encoder, self).__init__() #Initialize Embedding layer #Intialize Encoder LSTM layer self.lstm_size = lstm_size self.embedding = tf.keras.layers.Embedding(inp_vocab_size, embedding_size) self.lstm = tf.keras.layers.LSTM(lstm_size, return_sequences=True, return_state=True) def call(self, input_sequence, states): embed = self.embedding(input_sequence) output, state_h, state_c = self.lstm(embed, initial_state=states) return output, state_h, state_c def initialize_states(self,batch_size): return (tf.zeros([batch_size, self.lstm_size]), tf.zeros([batch_size, self.lstm_size])) ``` ### 点注意机制: 随着编码器架构的完成,我们将继续研究将在我们的项目中实现的点注意机制。这种注意力方法是最简单的方法之一。然而,如果你愿意的话,你也可以选择借助其他方法来解释你的项目。我还提供了一个示例代码行,它被注释为一般的点注意机制。 为了更直观地理解下面表示的代码块,让我们确定一些基本的特性和特征,以理解正在实现的方法。注意类用于对提供的评分函数进行操作。在这种情况下,我们利用点注意机制。注意类中的第一个函数用于初始化计分函数,并为点注意过程准备调用函数。 在调用函数中,注意机制主要接受当前步骤的两个输入。这两个变量包括解码器的隐藏状态和编码器的所有输出。基于评分函数,在这种情况下,点注意机制,我们将找到解码器和编码器输出的隐藏状态之间的评分或相似性。然后,我们将分数函数与编码器输出相乘,以获得上下文向量。该函数将最终返回上下文向量和注意力权重(即 SoftMax - scores)。 ```py class Attention(tf.keras.layers.Layer): def __init__(self,scoring_function, att_units): super(Attention, self).__init__() self.scoring_function = scoring_function self.att_units = att_units if self.scoring_function=='dot': pass # For general, it would be self.wa = tf.keras.layers.Dense(att_units) def call(self,decoder_hidden_state,encoder_output): if self.scoring_function == 'dot': new_state = tf.expand_dims(decoder_hidden_state, -1) score = tf.matmul(encoder_output, new_state) weights = tf.nn.softmax(score, axis=1) context = weights * encoder_output context_vector = tf.reduce_sum(context, axis=1) return context_vector, weights ``` ### 一步解码器: 在这个机器翻译项目的一步解码器中,我们将初始化解码器嵌入层、LSTMs 和任何其他所需的对象。单步解码器以这样的方式被修改,即它将返回必要的权重。让我们通过将其实现过程分解为六个基本步骤来系统地理解一步解码器的过程。这些措施如下: 1. 将 input_to_decoder 传递给嵌入层,然后得到输出(1,1,embedding_dim)。 2. 使用编码器输出和解码器隐藏状态,计算上下文向量。 3. 将上下文向量与步骤 1 的输出连接起来。 4. 将步骤 3 的输出传递给 LSTM/GRU,并获取解码器输出和状态(隐藏和单元状态)。 5. 将解码器输出传递到密集层(vocab 大小),并将结果存储到输出中。 6. 返回步骤 4 的状态,步骤 5 的输出,步骤 2 的注意力权重。 ```py class One_Step_Decoder(tf.keras.Model): def __init__(self, tar_vocab_size, embedding_dim, input_length, dec_units, score_fun, att_units): super(One_Step_Decoder, self).__init__() # Initialize decoder embedding layer, LSTM and any other objects needed self.tar_vocab_size = tar_vocab_size self.embedding_dim = embedding_dim self.input_length = input_length self.dec_units = dec_units self.score_fun = score_fun self.att_units = att_units self.embedding = tf.keras.layers.Embedding(self.tar_vocab_size, self.embedding_dim, input_length=self.input_length) self.lstm = tf.keras.layers.LSTM(self.dec_units, return_sequences=True, return_state=True) self.output_layer = tf.keras.layers.Dense(self.tar_vocab_size) self.attention = Attention(self.score_fun, self.att_units) def call(self, input_to_decoder, encoder_output, state_h, state_c): result = self.embedding(input_to_decoder) context_vector, weights = self.attention(state_h, encoder_output) concat = tf.concat([tf.expand_dims(context_vector, 1), result], axis=-1) decoder_output, hidden_state, cell_state = self.lstm(concat, initial_state=[state_h, state_c]) final_output = tf.reshape(decoder_output, (-1, decoder_output.shape[2])) final_output = self.output_layer(final_output) return final_output, hidden_state, cell_state, weights, context_vector ``` ### 解码器架构: 解码器架构在即将到来的代码块中构建。该过程包括初始化一个空张量数组,该数组将存储每个时间步的输出。创建一个张量数组,如参考代码块所示,然后继续迭代,直到解码器输入的长度。一旦为 decoder_input 中的每个标记调用了一步解码器,就可以继续将输出存储在张量数组的已定义变量中。最后,确保返回这个张量数组。 ```py class Decoder(tf.keras.Model): def __init__(self, out_vocab_size, embedding_dim, output_length, dec_units ,score_fun ,att_units): #Intialize necessary variables and create an object from the class onestepdecoder super(Decoder, self).__init__() self.out_vocab_size = out_vocab_size self.embedding_dim = embedding_dim self.output_length = output_length self.dec_units = dec_units self.score_fun = score_fun self.att_units = att_units self.onestepdecoder = One_Step_Decoder(self.out_vocab_size, self.embedding_dim, self.output_length, self.dec_units, self.score_fun, self.att_units) def call(self, input_to_decoder,encoder_output,decoder_hidden_state,decoder_cell_state): all_outputs= tf.TensorArray(tf.float32, size=input_to_decoder.shape[1], name="output_arrays") for timestep in range(input_to_decoder.shape[1]): output, decoder_hidden_state, decoder_cell_state, weights, context_vector = self.onestepdecoder( input_to_decoder[:,timestep:timestep+1], encoder_output, decoder_hidden_state, decoder_cell_state) all_outputs = all_outputs.write(timestep, output) all_outputs = tf.transpose(all_outputs.stack(), (1, 0, 2)) return all_outputs ``` ### 称编码器解码器架构为: *encoder_decoder* 类是我们项目的一个附加元素,它简化了整个过程,并将编码器和解码器的元素结合在一起。在这堂课中,我们将定义这一步所需的所有变量。 *init* 块将由编码器和解码器块的初始化组成。*调用*函数用于发送但主要是从所有其他类获取重要信息。 ```py class encoder_decoder(tf.keras.Model): def __init__(self, inp_vocab_size, out_vocab_size, embedding_size, lstm_size, input_length, output_length, dec_units ,score_fun ,att_units, batch_size): super(encoder_decoder, self).__init__() self.encoder = Encoder(inp_vocab_size, embedding_size, lstm_size, input_length) self.decoder = Decoder(out_vocab_size, embedding_size, output_length, dec_units, score_fun, att_units) def call(self, data): input_sequence, input_to_decoder = data[0],data[1] initial_state = self.encoder.initialize_states(batch_size=64) encoder_output, state_h, state_c = self.encoder(input_sequence, initial_state) decoder_hidden_state = state_h decoder_cell_state = state_c decoder_output = self.decoder(input_to_decoder, encoder_output, decoder_hidden_state, decoder_cell_state) return decoder_output ``` ### 自定义损失函数: 在我们的机器翻译模型的架构构建完成之后,我们需要定义训练模型的过程所需的几个参数。我们将利用 Adam 优化器,并利用损失对象作为稀疏分类交叉熵来计算标签和预测之间的交叉熵损失。下面的代码块简单地展示了如何执行这样的操作。您可以随意尝试其他方法或优化器,看看哪个最适合您的模型。 ```py loss_object = tf.keras.losses.SparseCategoricalCrossentropy( from_logits=True, reduction='none') def loss_function(real, pred): mask = tf.math.logical_not(tf.math.equal(real, 0)) loss_ = loss_object(real, pred) mask = tf.cast(mask, dtype=loss_.dtype) loss_ *= mask return tf.reduce_mean(loss_) optimizer = tf.keras.optimizers.Adam() ``` ### 培训: 接下来的几个代码块将使用点注意处理序列到序列模型的训练的实现。第一个代码块仅包含一些初始化以及后续训练过程的整体计算所需的库调用。由于我的系统上的一些 GPU 限制,我正在利用[纸空间梯度](https://gradient.run)来实现机器翻译项目。从今以后,我使用*!mkdir* 记录创建附加目录的命令。如果您正在 PC 上构建这个模型架构,您可以跳过这一步,或者直接在您的系统中创建一个文件夹/目录。 为了讨论代码块中的更多步骤,我们在 TensorFlow 和 Keras 深度学习框架的帮助下激活回调。检查点是更重要的回调,因为它将使您能够在训练后保存最佳权重,并且您可以导出这个保存的模型以供将来的实例使用。您的预测模型可以使用这个保存的文件进行所需的翻译。我们还定义了一些必要的变量和一些理想的值,以便将它们传递给先前定义的编码器-解码器架构。 ```py !mkdir logs from tensorflow.keras.callbacks import ModelCheckpoint from tensorflow.keras.callbacks import TensorBoard checkpoint = ModelCheckpoint("dot.h5", monitor='val_loss', verbose=1, save_weights_only=True) logdir='logs' tensorboard_Visualization = TensorBoard(log_dir=logdir) input_vocab_size = len(inp_lang.word_index)+1 output_vocab_size = len(targ_lang.word_index)+1 input_len = max_length_inp output_len = max_length_targ lstm_size = 128 att_units = 256 dec_units = 128 embedding_size = 300 embedding_dim = 300 score_fun = 'dot' steps = len(input_tensor)//64 batch_size=64 model = encoder_decoder(input_vocab_size,output_vocab_size,embedding_size,lstm_size,input_len,output_len,dec_units,score_fun,att_units, batch_size) checkpoint_dir = './training_checkpoints' checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt") checkpoint = tf.train.Checkpoint(optimizer=optimizer, encoder=model.layers[0], decoder=model.layers[1]) ``` 下一步,我们将使用教师强制方法对模型架构进行相应的训练。梯度带方法通常用于更容易地训练复杂的结构。我强烈推荐查看我以前关于 TensorFlow 的文章,以便更好地理解这些步骤的功能。为了执行下面的代码块,必须按照前面所述执行数据集准备的所有步骤。举个例子,如果解码器输入是*<开始>Hi How are you*,那么解码器输出应该是*【Hi How are you】结束>*。 ```py @tf.function def train_step(inp, targ, enc_hidden): loss = 0 with tf.GradientTape() as tape: enc_output, enc_hidden,enc_state = model.layers[0](inp, enc_hidden) dec_input = tf.expand_dims([targ_lang.word_index['<start>']] * BATCH_SIZE, 1) for t in range(1, targ.shape[1]): predictions = model.layers[1](dec_input,enc_output,enc_hidden,enc_state) loss += loss_function(targ[:, t], predictions) dec_input = tf.expand_dims(targ[:, t], 1) batch_loss = (loss / int(targ.shape[1])) variables = model.layers[0].trainable_variables + model.layers[1].trainable_variables gradients = tape.gradient(loss, variables) optimizer.apply_gradients(zip(gradients, variables)) return batch_loss ``` 我们将在总共 20 个时期内实施我们的训练程序。您可以按照下面提供的代码块来实现下面的实现。让我们继续来看看在训练机器翻译模型时获得的一些结果。 ```py EPOCHS = 20 for epoch in range(EPOCHS): start = time.time() enc_hidden = model.layers[0].initialize_states(64) total_loss = 0 for (batch, (inp, targ)) in enumerate(dataset.take(steps_per_epoch)): batch_loss = train_step(inp, targ, enc_hidden) total_loss += batch_loss if batch % 100 == 0: print('Epoch {} Batch {} Loss {:.4f}'.format(epoch + 1, batch, batch_loss.numpy())) if (epoch + 1) % 2 == 0: checkpoint.save(file_prefix = checkpoint_prefix) print('Epoch {} Loss {:.4f}'.format(epoch + 1, total_loss / steps_per_epoch)) print('Time taken for 1 epoch {} sec\n'.format(time.time() - start)) ``` **结果:** ```py Epoch 18 Batch 0 Loss 0.2368 Epoch 18 Batch 100 Loss 0.2204 Epoch 18 Batch 200 Loss 0.1832 Epoch 18 Batch 300 Loss 0.1774 Epoch 18 Loss 0.1988 Time taken for 1 epoch 12.785019397735596 sec Epoch 19 Batch 0 Loss 0.1525 Epoch 19 Batch 100 Loss 0.1972 Epoch 19 Batch 200 Loss 0.1409 Epoch 19 Batch 300 Loss 0.1615 Epoch 19 Loss 0.1663 Time taken for 1 epoch 12.698532104492188 sec Epoch 20 Batch 0 Loss 0.1523 Epoch 20 Batch 100 Loss 0.1319 Epoch 20 Batch 200 Loss 0.1958 Epoch 20 Batch 300 Loss 0.1000 Epoch 20 Loss 0.1410 Time taken for 1 epoch 12.844841480255127 sec ``` 我们可以观察到,在最后的几个时期,损失从最初的观察值急剧减少。该模型已经过良好的训练,损失逐渐减少,预测的准确性总体上有所提高。如果您有时间、耐心和资源,建议您尝试在整个数据集上实施机器翻译训练程序,以获得更多的历元数。 ### 翻译: 我们项目的最后一步是做出必要的预测。翻译过程在预测函数中完成,如下面的代码块所示。主要步骤是获取给定的输入句子,然后使用之前定义的记号化器将句子转换成整数。然后,我们将输入序列传递给编码器,以接收编码器输出、隐藏的最后一个时间步长和前面描述的单元状态。其它步骤,如初始化作为解码器输入的索引和作为一步解码器输入状态的编码器最终状态,也在该步骤中完成。 ```py def predict(input_sentence): attention_plot = np.zeros((output_len, input_len)) input_sentence = preprocess_sentence(input_sentence) inputs = [inp_lang.word_index[i] for i in input_sentence.split()] inputs = tf.keras.preprocessing.sequence.pad_sequences([inputs], maxlen=input_len, padding='post') inputs = tf.convert_to_tensor(inputs) result = '' encoder_output,state_h,state_c = model.layers[0](inputs,[tf.zeros((1, lstm_size)),tf.zeros((1, lstm_size))]) dec_input = tf.expand_dims([targ_lang.word_index['<start>']], 0) for t in range(output_len): predictions,state_h,state_c,attention_weights,context_vector = model.layers[1].onestepdecoder(dec_input, encoder_output, state_h, state_c) attention_weights = tf.reshape(attention_weights, (-1, )) attention_plot[t] = attention_weights.numpy() predicted_id = tf.argmax(predictions[0]).numpy() result += targ_lang.index_word[predicted_id] + ' ' if targ_lang.index_word[predicted_id] == '<end>': return result, input_sentence, attention_plot dec_input = tf.expand_dims([predicted_id], 0) return result, input_sentence, attention_plot ``` 让我们实现一个翻译函数,它将执行预测从意大利语到英语的适当反应的任务。请注意,我没有计算 BLEU 评分图,这也是观众在观察机器翻译模型的工作时可以尝试实现的。下面是机器翻译任务的计算和预测的简单函数。 ```py def translate(sentence): result, sent, attention_plot = predict(sentence) print('Input: %s' % (sent)) print('Predicted translation: {}'.format(result)) ``` 让我们快速测试一下这个函数,看看这个模型是否能产生预期的结果! ```py translate(u'ciao!') ``` **结果:** ```py Input: <start> ciao ! <end> Predicted translation: hello ! <end> ``` > 我们已经成功地构建了我们的机器翻译模型,借助于序列到序列建模和点注意机制来实现整体低损失和更高的预测准确性。 * * * ## 结论: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/52813be64e686a3368ff00b493e64ff3.png) Image From [Freepik](https://www.freepik.com/free-vector/modern-cute-chatbot-different-poses-flat-set_13146592.htm#page=1&query=chatbot&position=5) 随着用于执行机器翻译模型的神经网络模型的完整架构构建,我们已经到达了本文的结尾。我们涵盖了基本理解如何从头开始构建机器翻译深度学习模型所需的大多数基本概念。这些主题包括对递归神经网络工作的直观理解、编码器-解码器架构和点注意机制。 最后,我们为机器翻译的任务建立了一个完整的项目,包括完整的代码和理论。更重要的是,我们使用了一种略微独特的单步解码器方法来解决这个问题。数据集和完整的项目将在本文的框架内访问。请随意尝试,并自行分析工作情况。 在接下来的文章中,我们将全面深入地讨论迁移学习等主题,并致力于其他项目,如使用 TensorFlow 进行图像字幕。在那之前,继续探索深度学习和神经网络的世界,继续建立新的项目!** # 构建第一个检测垃圾邮件的 NLP 应用程序 > 原文:<https://blog.paperspace.com/nlp-spam-detection-application-with-scikitlearn-xgboost/> > **“第一步”总是最难的。有两个可能的方面——不应该太复杂而阻碍进一步的探索,也不应该太容易不能理解抽象的复杂性。** *自然语言处理(NLP)是一个广泛概念的集合。虽然选择一个起点很有挑战性,但在本教程中,我们将介绍构建一个简单的 NLP 应用程序所需的先决条件,稍后再继续构建一个。* *以下是我们将要讨论的内容:* * *什么是自然语言处理?* * *两个基本分支——语法和语义* * *自然语言处理的应用* * *自然语言处理词汇* * *编写简单的 NLP 应用程序* *事不宜迟,让我们开始吧!* *## 什么是自然语言处理? 人类理解宇宙的一种方式是通过语言。语言主要是口头的或书面的,尽管像手势这样的东西也被认为是语言。语言也可以通过一些次要的因素来表达,比如用词、语气、正式程度以及数以百万计的其他变量。 想象一下,作为一个人来分析多种语言的大量句子是多么具有挑战性。任务可行吗?你的理解会受到限制。很难在单词和概念之间建立联系——然而你仍然会对自己理解的程度感到惊讶。 如果我们有一台机器来执行处理自然语言的任务,会怎么样?未经训练,机器不具备运行此类任务的智能,但如果我们插入问题、所需信息和算法,我们可以让机器进行认知思考。 NLP,或者说自然语言处理,来了。NLP 的目标是让机器能够理解人类语言。NLP 包含像语音识别和自然语言理解这样的子领域,并且通常利用各种算法将人类语言转换成一系列句法或语义表示。 ## 两个原始分支 NLP 主要依赖于两个基本的子任务:语法和语义。 ### 句法 句法属于支配句子中词的排列的规则。在自然语言处理中,一组语法规则被用来控制文本的语法。 我们来考虑下面两句话: 1. 我正在读一本书。 2. 我在看书。 两个句子都有相同的单词,但任何说英语的人都清楚,第一个句子在语法上是正确的,而第二个却不是。我们知道这一点,因为我们已经明确或隐含地了解到第二种选择是糟糕的语法。 电脑没有这种语法知识。我们必须训练它学会区分。 这些是 NLP 用来区分句子的概念: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/a5939ae0b1e6154f6b20229474e255cd.png) PoS tagging of a short phrase **1。给每个单词标上适当的发音。**这个过程叫做**词性标注**。例如,在句子“我正在读一本书”中,“我”是代词,“am”和“阅读”是动词,“a”是限定词,“书”是名词。可能有这样的情况,一个单词在一个句子中有一个词性,而在另一个句子中有不同的词性;“我的手表停了”这句话中的“手表”是名词,而“露西看着他走了”这句话中的“手表”是动词。NLP 必须有智能将正确的位置与每个单词联系起来。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d6718244f5cfdf56d37619fdba471abb.png) An example visualization of constituency parsing for the sentence "I bought a book."  **2。把句子分成适当的语法成分。**这个过程被称为**选区解析**。例如,在句子“她喜欢打网球”中,“她”是名词短语(NP),“喜欢打网球”是动词短语(VP)。选区分析有助于建立句子中短语之间的关系。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b652045e4e28ad45101d322faa52c885.png) Dependency parsing of the sentence: “Anna likes to play soccer” **3。在句子中的任意两个词之间建立依存关系。**这个过程叫做**依存解析**。与选区分析不同,依存分析建立单词之间的关系。例如,考虑句子“安娜喜欢踢足球。”上图显示了使用[分解的依赖解析器](https://explosion.ai/demos/displacy?text=Anna%20likes%20to%20play%20soccer&model=en_core_web_sm&cpu=1&cph=1)生成的依赖图。 相关术语: * `nsubj`是一个*名词性主语*,它是一个名词短语,是一个从句的句法主语。 * `xcomp`是动词或形容词的*开放从句补语*,是没有自己主语的表语或从句补语。 * `aux`是从句的*助词*,是从句的非主要动词。 * `dobj`是动词短语的直接宾语。名词短语是动词的(宾格)宾语。 为了学习更多关于依赖关系的知识,我推荐你去看看斯坦福的 [*依赖手册*](http://downloads.cs.stanford.edu/nlp/software/dependencies_manual.pdf) *。* 根据手头的语言和文本,使用适当的语法解析技术。 ### 语义学 语义是文本的意义。在自然语言处理中,语义分析是指从文本中提取和解释意义。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/a5d7f60ed422d614eb5e7a6ff620bf1d.png) Two different semantic interpretations of the same sentence. 词汇语义学是语义分析中的一个重要概念。以下是词汇语义学研究中需要理解的一些关键要素: 1. 下位词:一个比一般意义更具体的词,例如黑色是颜色的下位词 2. 同音异义词:两个拼写或发音相同但意思不同的词,例如 right 是 left 的反义词,right 是 correct 3. 当事物的一部分用来指代整体时,例如,水上的船指的是水上的船 4. 一词多义:一个词有多种可能的含义,例如声音就是一词多义,有多种含义 5. 同义词:与另一个词意思相同的词,例如 bold 和 audacious 是同义词 6. 反义词:与另一个词意思相反的词,例如真和假是反义词 模糊地,这些被 NLP 用来检查文本的意义。此外,NLP 检查符号和搭配。语法和语义一起帮助 NLP 理解文本的复杂性。 ## 自然语言处理的应用 NLP 的应用比比皆是。一些最受欢迎的包括: * 语音识别 * 自动摘要 * 聊天机器人 * 问答模型 * 文本分类 * 情感分析 * 语言翻译员 * 搜索自动完成 * 文本自动更正 ## 自然语言处理词汇 语言本身是复杂的,自然语言处理也是如此。它有一系列概念来处理语言的复杂性。 #### 文集 语料库是文本文档的集合。 #### 词典 词汇是一种语言的词汇。例如,在足球比赛中,“越位”、“凌空抽射”和“点球”都是这项运动词汇的一部分。 #### 标记化 标记化是将文本拆分成单词(或)标记的过程。例如,考虑句子“华盛顿特区是美国的首都”代币将会是*华盛顿*、*特区*、*是*、*是*、*首都*、*城市*、的*、*美国*、*美国*和*州*。* 如果我们不想分裂华盛顿和华盛顿特区呢?我们首先必须识别命名实体,然后对文本进行标记。(*参考*下面的 n-grams 部分) 因此,标记化不仅仅是使用空白分割文本。 ### 从语法上分析 解析封装了语法和语义分析阶段。一般来说,解析是根据特定的议程将文本分解成各自的成分。例如,语法分析将文本分解成其语法成分,这可以基于词性、成分或依存性分析。语义解析是将自然语言话语转换成正式意义表示的任务。 解析通常会生成一个解析树,它提供了解析输出的可视化表示。 ### 正常化 文本规范化是将文本转换为其标准形式。以下是它的两种变体: ##### 堵塞物 词干化是将单词缩减为词干,通常是通过去掉后缀。例如,考虑单词“填鸭式”。去掉后缀“med”,我们得到单词“cram”,就是**词干**。 > 词干分析是一种数据预处理技术,有助于简化理解文本的过程,有了它我们就不会有一个庞大的数据库! 词干提取过程中可能会出现两种错误: * **过词干:**两个词词干化为同一个词干(或词根),实际上属于两个不同的词干。例如,考虑一下*环球、大学和宇宙* [ [ref](https://www.geeksforgeeks.org/introduction-to-stemming/) 。尽管这些词在自然语言中属于不同的领域,但它们都源于" *univers"* 。 * **词干化不足:**当两个单词被词干化为不属于不同词干的不同词干(或词根)时。例如,考虑单词,*、数据和数据。*这些词分别源于*【数据】*和*【数据】*,虽然它们属于同一个领域。 词干提取算法的例子包括波特算法、洛文斯·斯特梅尔、道森·斯特梅尔等。 #### 词汇化 规范化的另一个变体叫做“词条化”,它指的是将一个单词映射到它的根词典形式,这被称为“词条”这看起来类似于词干法,然而,它使用不同的技术来推导引理。例如,单词“are,am,is”的引理是“be”(假定词性为动词)。 词汇化比词干化需要更多的资源,因为它需要更多关于文本结构的知识。 ### 停止单词删除 停用词是常见的词,如冠词、代词和介词。去除过程排除了没有什么价值的不必要的单词,帮助我们更专注于需要我们注意的文本。 > 最棒的是,它减少了对大型数据库的依赖,减少了分析文本的时间,并有助于提高性能。 然而,这并不是每个算法都必须应用的强制 NLP 技术。在文本摘要、情感分析和语言翻译等应用中,删除停用词是不可取的,因为这会丢失必要的信息。 考虑一个去掉“like”这个词的场景。在情感分析这样的应用中,这种删除可能会抹去文本所散发出的积极性。 ### 单词袋(蝴蝶结) 顾名思义,单词包统计单词在文本中的出现次数,不考虑单词的顺序和文档的结构。 例如,考虑以下两行文本: ```py Your problems are similar to mine Your idea seems to be similar to mine ``` 首先,让我们列出所有出现的单词: 1. 你的 2. 问题 3. 是 4. 类似的 5. 到 6. 我的 7. 想法 8. 似乎 9. 是 BoW 创建向量(在我们的例子中,让我们考虑一个二进制向量)如下: 1. 你的问题和我的相似-`[1, 1, 1, 1, 1, 1, 0, 0, 0]` 2. 你的想法似乎和我的相似—`[1, 0, 0, 1, 1, 1, 1, 1, 1]` 可以推断,单词的排序被丢弃。此外,它不能扩展到更大的词汇表。这可以通过使用 *n-grams* 和*单词嵌入(参考以下章节)*来解决。 单词包方法可能会产生一个问题,其中停用单词比信息单词被分配更高的频率。术语频率-逆文档频率(TF-IDF)有助于根据单词在文本中出现的频率来重新调整单词的频率,以便停用词可以受到惩罚。TF-IDF 技术奖励频繁出现的单词,但是惩罚在几个文本中过于频繁出现的单词。 单词包方法(有或没有 TF-IDF)可能不是理解文本含义的最佳方法,但是它在文本分类这样的应用中是有帮助的。 ### N-Grams 一个 n-gram 是由 *n* 个单词组成的序列。考虑句子“n-gram 是 n 个项目的连续序列。”如果 *n* 被设置为 2(所谓的二元模型),则 n 元模型将是: * n-gram 是 * 是一个 * 连续的 * 连续序列 * 序列 * n 的 * n 个项目 n 元语法用于自动完成句子、文本摘要、自动拼写检查等。 > N-grams 可能比 BoW 更能提供信息,因为它们捕获了每个单词周围的上下文(这取决于 n 的值)。 ### 单词嵌入 单词嵌入有助于将单个单词表示为低维空间中的实值向量。简而言之,将文本转换成数字数据(向量)有助于 NLP 模型的分析。 ***低头 vs .单词嵌入*** 与 BoW 不同,单词嵌入使用预定义的向量空间来映射单词,而不考虑语料库的大小。单词嵌入可以确定文本中单词之间的语义关系,而 BoW 不能。 一般来说,在以下情况下,鞠躬很有用: * 您的数据集很小 * 语言是特定于领域的 现成的单词嵌入模型的例子包括 Word2Vec、GloVe 和 fastText。 ### 命名实体识别(NER) NER 将信息词(所谓的“命名实体”)分为不同的类别:地点、时间、人物等。NER 的一些著名应用包括搜索和推荐引擎、用户投诉和请求分类、文本分类等。 您可以使用 **spaCy** 或 **NLTK** 在您的语料库上执行 NER。 ## 编写简单的 NLP 应用程序 在这个例子中,我们将通过首先使用单词袋(BoW)方法预处理包括垃圾和非垃圾消息的文本语料库来检测垃圾消息。稍后,我们将使用 XGBoost 模型对已处理的消息训练一个模型。 这是一个逐步的过程,引导您完成数据预处理和建模过程。 ### 步骤 1:导入库 首先,让我们安装并导入必要的库。 ```py # You may need to install libraries ! pip install pandas ! pip install nltk ! pip install scikit-learn # Import libraries import string import nltk import pandas as pd from nltk.corpus import stopwords from sklearn import metrics from sklearn.feature_extraction.text import CountVectorizer from sklearn.model_selection import train_test_split ``` (自然语言工具包)是帮助我们处理数据的主要软件包。 `scikit-learn`有助于建立、训练和测试模型的功效。 ### 步骤 2:预处理数据集 预处理是清理数据的过程。首先,我们获取数据并理解其结构。正如您在下面看到的,我们每条消息的文本保存在 v2 列中,分类目标保存在 v1 列中。如果一个文本是垃圾邮件,它在 v1 中被标记为“垃圾邮件”,如果不是,它被标记为“火腿”。 ```py # Read the dataset messages = pd.read_csv( "spam.csv", encoding="latin-1", index_col=[0] ) messages.head() ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/c4f68656444b5b7807462207187f27f1.png) The first five rows of our dataset 接下来,我们定义一个`text_preprocess`方法来删除标点符号、停用词和非字母。 ```py def text_preprocess(message): # Remove punctuations nopunc = [char for char in message if char not in string.punctuation] # Join the characters again nopunc = "".join(nopunc) nopunc = nopunc.lower() # Remove any stopwords and non-alphabetic characters nostop = [ word for word in nopunc.split() if word.lower() not in stopwords.words("english") and word.isalpha() ] return nostop ``` 让我们看看有多少垃圾邮件和非垃圾邮件构成了我们的数据集。 ```py spam_messages = messages[messages["label"] == "spam"]["message"] ham_messages = messages[messages["label"] == "ham"]["message"] print(f"Number of spam messages: {len(spam_messages)}") print(f"Number of ham messages: {len(ham_messages)}") ``` ```py # Output Number of spam messages: 747 Number of ham messages: 4825 ``` 接下来,我们检查在垃圾邮件和垃圾邮件中重复次数最多的前十个单词。 ```py # Download stopwords nltk.download('stopwords') # Words in spam messages spam_words = [] for each_message in spam_messages: spam_words += text_preprocess(each_message) print(f"Top 10 spam words are:\n {pd.Series(spam_words).value_counts().head(10)}") ``` ```py # Output Top 10 spam words are: call 347 free 216 txt 150 u 147 ur 144 mobile 123 text 120 claim 113 stop 113 reply 101 dtype: int64 ``` ```py # Words in ham messages ham_words = [] for each_message in ham_messages: ham_words += text_preprocess(each_message) print(f"Top 10 ham words are:\n {pd.Series(ham_words).value_counts().head(10)}") ``` ```py # Output Top 10 ham words are: u 972 im 449 get 303 ltgt 276 ok 272 dont 257 go 247 ur 240 ill 236 know 232 dtype: int64 ``` 我们的建模不需要这些信息;然而,执行探索性数据分析来帮助我们的模型是至关重要的。 关键的一步来了:我们传递信息。 ```py # Remove punctuations/stopwords from all messages messages["message"] = messages["message"].apply(text_preprocess) messages.head() ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/faa85651a5a3f08e479900534a4ce0b5.png) 产生的输出将是一个令牌列表。模型可以理解一个字符串,而不是一列标记。因此,我们将令牌列表转换为一个字符串。 ```py # Convert messages (as lists of string tokens) to strings messages["message"] = messages["message"].agg(lambda x: " ".join(map(str, x))) messages.head() ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/55f03bfa4e5d1fcbebc4d20ce3e5ea7c.png) ### 第三步:单词袋方法 `scikit-learn`库中的`CountVectorizer()`类在定义 BoW 方法时非常有用。我们首先通过消息的矢量器`fit`来获取整个词汇表。 ```py # Initialize count vectorizer vectorizer = CountVectorizer() bow_transformer = vectorizer.fit(messages["message"]) # Fetch the vocabulary set print(f"20 BOW Features: {vectorizer.get_feature_names()[20:40]}") print(f"Total number of vocab words: {len(vectorizer.vocabulary_)}") ``` ```py # Output 20 BOW Features: ['absence', 'absolutely', 'abstract', 'abt', 'abta', 'aburo', 'abuse', 'abusers', 'ac', 'academic', 'acc', 'accent', 'accenture', 'accept', 'access', 'accessible', 'accidant', 'accident', 'accidentally', 'accommodation'] Total number of vocab words: 8084 ``` 可以推断,我们获取的文本语料库中大约有`8084`个单词。 我们将`string`消息转换成数字`vectors`以简化模型建立和训练过程。 ```py # Convert strings to vectors using BoW messages_bow = bow_transformer.transform(messages["message"]) # Print the shape of the sparse matrix and count the number of non-zero occurrences print(f"Shape of sparse matrix: {messages_bow.shape}") print(f"Amount of non-zero occurrences: {messages_bow.nnz}") ``` ```py # Output Shape of sparse matrix: (5572, 8084) Amount of non-zero occurrences: 44211 ``` BoW 构建了一个稀疏矩阵,将每个单词的出现映射到语料库词汇表。因此,这种方法导致构建稀疏矩阵,或主要由零组成的矩阵。这种格式允许将文本转换成模型可以利用的可解释的语言信息编码。 ### 步骤 4:TF-IDF 方法 在*单词包(BoW)* 部分,我们学习了 BoW 的技术如何在与 TF-IDF 结合时得到增强。这里,我们通过 TF-IDF 运行我们的弓形向量。 ```py # TF-IDF from sklearn.feature_extraction.text import TfidfTransformer tfidf_transformer = TfidfTransformer().fit(messages_bow) # Transform entire BoW into tf-idf corpus messages_tfidf = tfidf_transformer.transform(messages_bow) print(messages_tfidf.shape) ``` ```py # Output (5572, 8084) ``` ### 步骤 5:构建 XGBoost 模型 XGBoost 是一种梯度推进技术,可以进行回归和分类。在这种情况下,我们将使用一个`XGBClassifier`来将我们的文本分类为“火腿”或“垃圾邮件”。 首先,我们将“spam”和“ham”标签转换为 0 和 1(反之亦然),因为 XGBoost 只接受数字。 ```py # Convert spam and ham labels to 0 and 1 (or, vice-versa) FactorResult = pd.factorize(messages["label"]) messages["label"] = FactorResult[0] messages.head() ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/4a0088a9bb90567c6457ffc0492ee24a.png) 接下来,我们将数据拆分为训练和测试数据集。 ```py # Split the dataset to train and test sets msg_train, msg_test, label_train, label_test = train_test_split( messages_tfidf, messages["label"], test_size=0.2 ) print(f"train dataset features size: {msg_train.shape}") print(f"train dataset label size: {label_train.shape}") print(f"test dataset features size: {msg_test.shape}") print(f"test dataset label size: {label_test.shape}") ``` ```py # Output train dataset features size: (4457, 8084) train dataset label size: (4457,) test dataset features size: (1115, 8084) test dataset label size: (1115,) ``` 为了训练模型,我们首先安装 XGBoost 库。 ```py # Install xgboost library ! pip install xgboost ``` 我们训练分类器。 ```py # Train an xgboost classifier from xgboost import XGBClassifier # Instantiate our model clf = XGBClassifier() # Fit the model to the training data clf.fit(msg_train, label_train) ``` 接下来,我们对训练数据集进行预测。 ```py # Make predictions predict_train = clf.predict(msg_train) print( f"Accuracy of Train dataset: {metrics.accuracy_score(label_train, predict_train):0.3f}" ) ``` ```py # Output Accuracy of Train dataset: 0.989 ``` 为了了解我们的模型进展的本质,让我们做一个预测的例子。 ```py # an example prediction print( "predicted:", clf.predict( tfidf_transformer.transform(bow_transformer.transform([messages["message"][9]])) )[0], ) print("expected:", messages["label"][9]) ``` ```py # Output predicted: 1 expected: 1 ``` 是的,成功了! 最后,我们在测试数据上发现了模型的整体准确性。 ```py # print the overall accuracy of the model label_predictions = clf.predict(msg_test) print(f"Accuracy of the model: {metrics.accuracy_score(label_test, label_predictions):0.3f}") ``` ```py # Output Accuracy of the model: 0.975 ``` ## 结论 你已经向更大的世界迈出了第一步!由于处理大量自然语言数据的便利性,NLP 是一个突出的主题,多年来变得越来越重要。你现在已经准备好处理更深奥的 NLP 概念了。 我希望你喜欢阅读这篇文章! ### 参考 1. [https://www . ka ggle . com/dkta laicha/SMS-spam-detection-with-NLP](https://www.kaggle.com/dktalaicha/sms-spam-detection-with-nlp) 2. [https://towards data science . com/your-guide-to-natural language-processing-NLP-48ea 2511 F6 e 1](https://towardsdatascience.com/your-guide-to-natural-language-processing-nlp-48ea2511f6e1) 3. [https://monkeylearn.com/blog/semantic-analysis/](https://monkeylearn.com/blog/semantic-analysis/)* # 使用渐变工作流和 GitHub 生成 NLP 文本 > 原文:<https://blog.paperspace.com/nlp-text-generation-using-gradient-workflows-and-github-integration/> 在处理数据科学项目以解决复杂问题时,或者在将项目从探索阶段投入生产时,对项目的组件(包括代码、数据、模型和部署)进行版本控制非常重要。 在现代软件工程和数据科学工作中,GitHub 已经成为最流行的版本控制方式。因此,通过我们与 github.com 的集成,Paperspace Gradient 使用户的项目能够链接到 GitHub 库。 在本例中,我们展示了: * 基于现代深度学习的自然语言处理模型的文本生成,GPT-2 * 工作流链接到 GitHub 存储库的渐变项目 * 根据存储库中的更改触发工作流重新运行,这在许多生产系统中都是需要的 * 作为输出的版本化梯度管理数据集 如果你想自己运行这些例子,请查看我们关于这个项目的[文档页面,以及](https://docs.paperspace.com/gradient/get-started/tutorials-list/example-workflow-nlp-text-generator) [GitHub 库](https://github.com/gradient-ai/NLP-Text-Generation)。 ### 自然语言处理文本生成 NLP(自然语言处理)在过去几年中作为深度学习模型在广泛领域的成功应用而出现。在这里,我们展示了众所周知的 GPT-2 模型在梯度工作流中运行,以生成文本。提供了一个初始句子,模型继续书写。这在需要自动生成合适文本的地方有明显的应用。 我们使用来自 HuggingFace 的 [GPT-2](https://huggingface.co/gpt2) 文本生成器。这在 Gradient 上很容易做到,因为我们有一个现有的 HuggingFace 容器,其中包含必要的软件依赖关系,并且他们的库提供了简单的函数,如`pipeline()`和`generator()`,这些函数指向模型的推理能力以生成文本。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d727dcdbf6c704a9288a01971344f5ae.png) HuggingFace GPT-2 NLP text generation model used here 虽然 GPT-2 不是绝对最新或最大的可用 NLP 模型,但我们选择它而不是其他模型,如 GPT-Neo-1.3B/2.7B,原因如下: * 是使用最多的一个:比如选择 HuggingFace 机型列表,通过文本生成标签进行[过滤,GPT-2 就是下载最多的机型。](https://huggingface.co/models?pipeline_tag=text-generation&sort=downloads) * 许多性能更高的大型模型在 Gradient 上运行得很好,但在 5GB 或 10GB+的大小上,与 GPT-2 的大约 0.5GB 相比,它们需要更长的加载时间,因此不太适合旨在快速运行的示例。 在渐变中运行时,无论是在笔记本中还是在工作流中,都会启动 HuggingFace 容器,然后运行 Python 代码。输出文本可以直接在单元格中看到,或者定向到输出文件。在工作流中,输出文件放在渐变管理的数据集中。 ### 将渐变项目链接到 GitHub 存储库 渐变工作流包含在项目中。为了在 GUI 中创建一个项目,我们导航到我们的主页,这里给出了创建一个项目的选项。可以创建一个工作流,通过命令行运行,或者像我们在这里做的那样,链接到 Github 存储库。该存储库可以从我们的一个示例模板中复制,或者您可以指向您自己建立的一个模板。 工作流创建屏幕如下所示: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/fdfabc1b38c23a42e96d8eb3bd80d89a.png) Workflow creation page, showing how to create a repo-linked Project 在这种情况下,回购是这样的: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/a7a5989170160cd08f0927eda94608f1.png) GitHub repository used in this project 我们可以看到正在运行的 Python 脚本,以及包含工作流的`.gradient`目录,如下所述。 ### 触发工作流重新运行 运行文本生成器模型的代码是 ```py # Settings random_seed = 42 max_length = 30 num_return_sequences = 5 initial_sentence = "Hello, I'm a language model," # Run generator generator = pipeline('text-generation', model='gpt2') set_seed(random_seed) output = generator(initial_sentence, max_length = max_length, num_return_sequences = num_return_sequences) ``` 由此我们可以看出,如果改变`Settings`下的值,模型会产生不同的文本作为输出。 在类似 MLOps 或生产的情况下,我们可能希望更改模型设置来触发工作流的重新运行。这很容易在 Gradient 中显示出来:如果文件被编辑,更新的版本被上传到与项目链接的 GitHub 存储库中,那么工作流将重新运行。 前提是: * 工作流 YAML 文件位于链接存储库中的目录`.gradient/workflows`中 * YAML 文件包含一个`on`字段,表示当对回购进行更改时,应该触发它运行 `on`字段的一个例子是 ```py on: github: branches: only: main ``` 这表明对存储库主分支的任何更改都将触发重新运行。这种触发运行的能力,以及工作流 YAML 在`.gradient/workflows`目录中的位置,与 GitHub 自己的动作功能类似。 目前,如果 YAML 包含上述代码,那么对回购的任何文件更改都将触发重新运行,但我们很快会将此功能添加到更具体的操作中,例如,只有更改某些文件才会导致重新运行。例如,当您希望纠正`README.md`文件中的一个错别字时,这将非常有用,不会触发您为期一周的深度学习培训再次运行。 当在 Gradient 中触发工作流重新运行时,GUI 工作流列表将在左侧显示如下列表: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/3771f7df954444eae28ce587cfb1d650.png) Workflow reruns triggered by updates to the GitHub repository 显示触发重新运行的事件,例如*更新 README.md* 。在右边,我们可以看到来自工作流的`output.txt`文件,它包含了这次运行所生成的文本。 ### 梯度数据集的版本化输出 在数据科学中,数据本身与代码或模型一样重要。然而,数据通常不会存储在 GitHub 存储库中,因为它太大了。 因此,梯度提供了托管存储,当从工作流输出时,会生成版本化的梯度数据集。每个数据集可能包含许多单独的数据文件或其他文件,并且可以通过引用其 ID 和版本号来访问。 当与与回购相关的项目和工作流结合使用时,这将使给定工作流所做的处理与生成的数据版本保持一致。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/4de3ad0fb6a43b371ebb4d929d43e3ae.png) Versions of the output Gradient-managed Dataset for Workflow runs 对于我们的 NLP 文本生成,我们可以看到哪个模型设置产生了哪个输出。 ### 文本输出是什么样的? 当然,我们不能不展示一些示例文本输出。 已知从这种模型生成的文本具有各种问题,例如从提供给它的训练数据中继承偏见,或者产生不适合所有受众的输出。由于用户可以提供任何句子作为输出的初始部分,因此潜力是显而易见的。 解决有问题的输出超出了本文的范围,所以我们只是将其视为“这是模型所说的”,没有任何解释,并展示几个示例。 类似地,NLP 文本生成器可以通过对更深入、更集中的文本进行训练,在特定领域获得更高的性能。这里的输出来自基本模型,最大输出长度设置为 100 个字符。 ```py Hello, I'm a language model, not a programming language. Let's see how you work out your approach. By our experience, Python is the best known language to work with. It has been used in numerous programming languages, including Java, C++, R, C, C#, Lua, C#, PHP, XML, Python, Ruby and more. In addition to such languages as Lua, Javascript, PHP, BSD, PHP, PHP. But there are some problems ``` ```py Paperspace Gradient is a visualization of the gradient of the background by the relative humidity based on the total amount of current it takes to change the intensity of the source illumination. While many researchers consider this process as simple, many use the method to solve more complex and complex problems. If it turns out a few other factors would make the process more convenient, or perhaps even more efficient, for example, it might be worthwhile to start incorporating this in the design of your own lights. ``` ```py Paperspace Gradient is a music editor that automatically scales up and down on the files you're working on. It's a little like how a game can now change up the sound that's playing on your home music system, on PC, iPod, or iOS. You can also customize the way the music is played for your device, through plugins, apps, or just by tapping and holding on an instrument. And yes, all of these features have been in the works for ``` ```py MLOps is really useful. And most importantly, it helps your team's customers, which include your visitors and merchants. The "Best Practices For Customer Performance" What are things you would change if you started out selling services for users instead of products? If it's not at all possible for you to get great customer service, why not start selling software for customers? The software is great. It makes your business better. It helps your customers. It saves you money by ``` 正如我们所看到的,该模型能够创建接*人类清晰水*的句子,但经常会出现同形异义词(拼写相同,但含义不同的单词)的误用,并且在理解上普遍缺乏特异性,如包含“gradient”的示例显然,还有更多潜力有待发掘。 ### 结论 在这个例子中,我们展示了 * 基于现代深度学习的自然语言处理模型的文本生成,GPT-2 * 工作流链接到 GitHub 存储库的渐变项目 * 根据存储库中的更改触发工作流重新运行,这在许多生产系统中都是需要的 * 作为输出的版本化梯度管理数据集 对应的[教程](https://docs.paperspace.com/gradient/get-started/tutorials-list/example-workflow-nlp-text-generator)和 [GitHub 库](https://github.com/gradient-ai/NLP-Text-Generation)都有。 ### 后续步骤 通常情况下,下一步该做什么取决于您在寻找什么,但有几种可能性: * 有关渐变中更长的端到端工作流示例,请参见[推荐器](https://docs.paperspace.com/gradient/get-started/tutorials-list/end-to-end-example)和 [StyleGAN2](https://docs.paperspace.com/gradient/get-started/tutorials-list/workflows-sample-project) 教程。推荐者有一个笔记本、工作流、部署和非回购链接项目。StyleGAN2 与 repo 关联,显示数据准备、模型训练和模型推断。 * HuggingFace 模型提供的灵活接口意味着通过修改指向模型的`generator = pipeline('text-generation', model='gpt2')`行和传递正确参数的`generator()`函数行,可以修改`nlp_text_generation.py`中显示的 Python 代码以使用其他 NLP 模型。例如,上面提到的 GPT-*地天体系列在 https://huggingface.co/EleutherAI 的[。GPT-*地天体-125M 实际上与 GPT-2 的大小相当,你也可以测试更大的-1.3B 和-2.7B。后者可能需要比工作流中指定的 C5 更大的实例,例如 C7。](https://huggingface.co/EleutherAI) # 使用渐变创建模型并将其部署到生产环境中 > 原文:<https://blog.paperspace.com/notebooks-workflows-and-now-deployments/> [https://www.youtube.com/embed/2eibSE7R6Vs?feature=oembed](https://www.youtube.com/embed/2eibSE7R6Vs?feature=oembed) Video Guide to accompany this article.  机器学习模型可以在数据上产生很好的结果,但要在研究环境之外显示它们的全部价值,它们需要部署到生产中。 在 Gradient 中,使用内置于[部署](https://gradient.run/deployments)资源中的功能,很容易将我们的模型作为 API 端点投入生产。在本文中,我们将介绍在 Gradient 上部署的基础知识,并展示在 Gradient 中创建的*模型可以通过以下方式在 Gradient 中部署:* * 使用工作流创建和训练 TensorFlow 深度学习模型 * 使用部署来部署模型 * 向模型发送推理数据并接收正确的输出 这为我们提供了一个完全指定的设置,可重复并可用于生产,结合了 Gradient 的其他功能,如 GitHub 集成和对 GPU 的轻松访问。 我们还可以从 Gradient 外部引入我们自己的模型,并在部署中使用它们。 ## 部署资源 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/901016390856d959ed4f0666c7c0b4a5.png) 模型部署是经过训练的模型的实例,它可以接收新数据(称为推断数据),并发回适当的响应(如预测)。例如,向模型发送一幅图像,模型返回该图像的正确分类。 在现代生产环境中,模型通常被部署为一个微服务,运行在 Docker 等容器中,运行在一个编排了容器的系统上,比如 Kubernetes。模型的位置在一个端点上,与它通信的方法是通过一个 API,通常是 REST 或 gRPC。 设置整个堆栈以正确工作来提供价值通常是大量的工作,需要大量的工具和概念知识,包括软件工程和数据科学。通过提供现有的硬件和软件基础设施,Gradient 大大减轻了生产负担。 我们实现了上面描述的现代微服务/Docker/Kubernetes 结构,并提供了与我们的工作流的集成,以便作为用户,我们可以将部署简化为简单地说明我们想要部署什么模型。如果模型来自我们的工作流之外,我们也可以指定我们自己的容器。 模型可以来自 TensorFlow 等著名的机器学习(ML)框架,也可以来自 ONNX 等通用框架。 ## 工作流部署推理示例 对于我们的示例,我们将执行这 3 个步骤: 1. 使用 TensorFlow 2.6 在时尚-MNIST 数据集上训练一个小型深度学习模型。 2. 使用 TensorFlow TFServing Docker 容器部署这个模型 3. 向模型发送推理数据并检查模型的预测 ### 步骤 1:使用工作流训练模型 我们在这里的目的是演示工作流和部署在广泛使用的 ML 库的渐变上端到端地工作,因此我们在一个众所周知的数据集上构建一个简单的模型,而不是任何大型或复杂的模型。代码与 TensorFlow 的[基本分类](https://www.tensorflow.org/tutorials/keras/classification)教程非常相似,但是我们分别使用工作流和部署来训练和部署模型。 步骤 1 的工作流从我们的`gradient-ai`空间克隆 GitHub 存储库,用指定的简单超参数训练模型,并将其输出为版本化梯度数据集。培训在我们的 TensorFlow Docker 容器上运行。 工作流的培训部分如下所示: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/da479d984063e15c410b3befa5ae45eb.png) Model training using Gradient Workflow 我们可以看到,使用我们存储库中的`train.py`脚本,模型被训练了 3 个时期(其他超参数是默认的),然后在我们的 TensorFlow 容器上运行,并输出到数据集`trained-model`。 工作流的模型上传部分如下所示: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/745a8dcbef88d37a5795dd9e53e29ab6.png) Model upload using Gradient Workflow 我们生成一个唯一的模型 ID,并使用[渐变动作](https://docs.paperspace.com/gradient/explore-train-deploy/workflows/gradient-actions) `create-model@v1`,这是我们将模型与工作流集成的一部分。结果是模型被放置在我们的模型注册表中,我们可以在渐变中看到它: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/bba4ee39e2e9e57a445249fd6280f1a6.png) Trained model in Gradient 我们可以看到它里面的文件,它的版本(`1`),它唯一的 ID,那个渐变把它识别为一个张量流模型。我们也可以将自己的模型上传到模型注册中心。 要查看完整的工作流程,以及它调用来进行模型训练的`train.py`脚本,请参见这个项目的 [GitHub 库](https://github.com/gradient-ai/Deployments-Create-Deploy-Infer)。 ### 步骤 2:使用部署来部署模型 在 Gradient GUI 中,部署位于同名的选项卡下,这可以在上面的训练模型截图中看到。现在,为这个模型创建部署的最好方法是在[命令行界面](https://docs.paperspace.com/gradient/get-started/quick-start/install-the-cli)中使用`gradient`: ```py gradient deployments create \ --name fashion-mnist \ --projectId pqrstuvwx \ --spec fashion-mnist-deployment-spec.yaml ``` 为了创建我们的 API 端点,我们使用带有子命令`create`的`deployments`命令,给模型一个名字`fashion-mnist`,指定项目 ID `pqrstuvwx`(你的会不同),以及模型规范`fashion-mnist-deployment-spec.yaml`的 [YAML](https://en.wikipedia.org/wiki/YAML) 文件。 YAML 文件的工作方式与工作流非常相似,实际上工作流是自动生成的,并用于启动部署。YAML 看起来是这样的: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/46d02c04642413eea8bccf3cfade2eba.png) Model deployment specification 模型规范的使用反映了这样一个事实,我们可以有许多不同的模型(规范),并且反过来可能有许多这些部署(运行)的实例。 我们使用最新的 tensor flow TF serving Docker image`tensorflow/serving`,端口 8501 上的 REST API(gRPC 也可用,通常在端口 8500 上),模型的唯一 ID,它在容器中的位置,单个副本,以及我们的 C4 CPU 实例类型作为硬件。 对于环境变量和路径,我们有 * 前一部分的模型训练工作流输出到`data/$MODEL_DIR` = `data/my-trained-model/1/` * 在 Paperspace 部署中,我们将注册的模型放在`path` = `/opt/models/trained-model/`中 * TFServing 通过它的`MODEL_BASE_PATH` = `/opt/models/trained-model/`知道这是哪里 * 这个特定模型的位置是`MODEL_NAME` = `trained-model/`,它会自动寻找子目录`1/, 2/`等。所以它在这里寻找`$MODEL_BASE_PATH/$MODEL_NAME/1/saved_model.pb`下的`saved_model.pb`。 这看起来有点复杂,但它允许更大和更强大的生产设置有许多模型,以及其他必要的,如预处理和后处理。 在 GUI 中,部署如下所示: ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/b8a82afa860d83411949d2432367070e.png) Model deployment detail 在这里,我们可以看到部署指标等其他信息也是可用的。目前,这些是系统指标;即将推出针对性能和漂移的全模型监控。 ### 步骤 3:向模型发送推理数据 一旦将模型部署到端点,就可以向它发送推理数据。这可以通过几种不同的方式来实现,包括使用命令行、笔记本 SDK 或工作流。 这里我们使用一个工作流,以便很容易以正确的形式发送数据,时尚-MNIST 图像,并查看模型的正确响应。推理数据被加载,随后是将它们发送到 5 个图像的模型的行: ```py data = json.dumps({"signature_name": "serving_default", "instances": test_images[0:5].tolist()}) headers = {"content-type": "application/json"} json_response = requests.post(SERVE_URL, data=data, headers=headers) predictions = json.loads(json_response.text)['predictions'] ``` 我们通过 POST 使用 REST API,但是 gRPC 也可以以同样的方式使用,只需根据需要修改代码行。 对于此处的数据,存在已知的基本事实标签,因此我们可以将模型的预测与真实类进行比较,以查看它们是否正确: ```py # Script for i in range(0,5): print('\nThe model predicted this as a {}, and it was actually a {}'.format(class_names[np.argmax(predictions[i])], class_names[test_labels[i]])) # Output The model predicted this as a Ankle boot, and it was actually a Ankle boot The model predicted this as a Pullover, and it was actually a Pullover The model predicted this as a Trouser, and it was actually a Trouser The model predicted this as a Trouser, and it was actually a Trouser The model predicted this as a Shirt, and it was actually a Shirt ``` 时尚 MNIST 很容易获得高精度,所以即使我们的小模型预测也是正确的,正如预期的那样。 除了在我们的日志中查看之外,以这种方式部署在端点上的模型的输出通常是可访问的,因此它既可以在更大的规模上使用,也可以根据需要定向到我们生产设置的下一个阶段。这可能是后处理、模型监控、对输出采取行动、应用程序或其他东西。 ## 结论 我们已经证明在梯度中我们可以 * 使用 TensorFlow 2.6 在时尚-MNIST 数据集上训练一个小型深度学习模型。 * 使用 TensorFlow TFServing Docker 容器部署这个模型 * 向模型发送推理数据并接收正确的结果 **注** : 我们*在这篇博文中没有*做的事情,就是搭建任何基础设施!运行我们的工作流、将我们的部署放在一个端点上、发送推断数据以及编排一切所必需的设置都已经就绪。 ## 后续步骤 我们计划在未来的帖子中展示更多的梯度部署功能,比如 canary 部署、模型度量,以及通过梯度项目和 GitHub 库之间的集成来触发模型重新部署。 现在您已经阅读了这篇博客,您可以: * 通过该项目的 [GitHub 库](https://github.com/gradient-ai/Deployments-Create-Deploy-Infer)亲自尝试部署 * 参见我们的[文档](https://docs.paperspace.com/gradient/) * 创建并运行您自己的部署 下次见! # 拓扑+图纸空间 > 原文:<https://blog.paperspace.com/ntopology-paperspace/> ## 查看案例研究 <https://s3.amazonaws.com/ps.public.resources/pdf/case+studies/nTopology_CaseStudy.pdf?utm_source=newsletter3&amp;utm_medium=email&amp;utm_campaign=showcase&amp;utm_term=nTopology> ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/06c08be6a018490d56b3551b8433adea.png) 总部位于纽约市的[nto polology](https://ntopology.com)正在开发 CAD 软件,用于生成复杂的网格结构,以设计高性能的 3D 打印零件。 [https://www.youtube.com/embed/AczgyvLg9OY](https://www.youtube.com/embed/AczgyvLg9OY) # NumPy 优化的基本要素第 3 部分:理解 NumPy 的内部结构、跨越、重塑和转置 > 原文:<https://blog.paperspace.com/numpy-optimization-internals-strides-reshape-transpose/> 在 NumPy 优化系列的前两部分中,我们主要介绍了如何通过用循环代替矢量化代码来提高代码速度。我们介绍了[矢量化和广播](https://blog.paperspace.com/numpy-optimization-vectorization-and-broadcasting/)的基础知识,然后用它们来[优化 K-Means 算法](https://blog.paperspace.com/speed-up-kmeans-numpy-vectorization-broadcasting-profiling/)的实现,与基于循环的实现相比,速度提高了 70 倍。 按照第 1 部分和第 2 部分的格式,第 3 部分(这一部分)将重点介绍一些带有一些理论的 NumPy 特性——即 NumPy 的内部、步长、整形和转置。第 4 部分将介绍这些工具在实际问题中的应用。 在之前的文章中,我们讨论了如何处理循环。在这篇文章中,我们将关注另一个经常降低 NumPy 代码速度的瓶颈:**不必要的复制和内存分配**。最大限度地减少这两个问题的能力不仅加快了代码的速度,还可以减少程序占用的内存。 我们将从一些可能导致不必要的数据复制和内存分配的基本错误开始。然后,我们将深入研究 NumPy 如何在内部存储它的数组,如何执行整形和转置等操作,并详细介绍一种无需键入一行代码就能计算此类操作结果的可视化方法。 在第 4 部分中,我们将使用我们在这一部分中学到的东西来优化对象检测器的输出管道。但是让我们把那个留到以后。 在我们开始之前,这里是本系列的前几部分的链接。 [NumPy Optimization: Vectorization and Broadcasting | Paperspace BlogIn Part 1 of our series on writing efficient code with NumPy we cover why loops are slow in Python, and how to replace them with vectorized code. We also dig deep into how broadcasting works, along with a few practical examples.![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d5df2fcca67c8eca9bb6754a14373b0e.png)Paperspace BlogAyoosh Kathuria![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/74deaa140f01e1dcdc1033ff5c7b94e7.png)](https://blog.paperspace.com/numpy-optimization-vectorization-and-broadcasting/)[Using NumPy to Speed Up K-Means Clustering by 70x | Paperspace BlogIn this part we’ll see how to speed up an implementation of the k-means clustering algorithm by 70x using NumPy. We cover how to use cProfile to find bottlenecks in the code, and how to address them using vectorization.![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d5df2fcca67c8eca9bb6754a14373b0e.png)Paperspace BlogAyoosh Kathuria![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/21b7cd1fed75487a47154d4cd98808d3.png)](https://blog.paperspace.com/speed-up-kmeans-numpy-vectorization-broadcasting-profiling/) 那么,我们开始吧。 ## 预分配预分配预分配! 我在迁移到 NumPy 的早期犯的一个错误,也是我看到许多人犯的一个错误,就是使用*循环-追加*范例。那么,我这么说到底是什么意思呢? 考虑下面这段代码。它在循环的每次迭代中向列表中添加一个元素。 ```py li = [] import random for i in range(10000): # Something important goes here x = random.randint(1,10) li.append(x) ``` 上面的脚本仅仅创建了一个包含从 0 到 9 的随机整数的列表。然而,我们添加到列表中的东西可能是循环的每次迭代中发生的一些复杂操作的结果,而不是一个随机数。 `append`是 Python 中的一个摊销`O(1)`操作。简而言之,*均来说,不管你的清单有多大,`append`都需要一定的时间。这就是为什么在 Python 中你会经常看到这个方法被用来添加到列表中。见鬼,这种方法如此受欢迎,你甚至会发现它被部署在生产级代码中。我称之为*循环附加*范例。虽然它在 Python 中运行良好,但在 NumPy 中就不一样了。 当人们切换到 NumPy 时,他们必须做一些类似的事情,这是他们有时会做的事情。 ```py # Do the operation for first step, as you can't concatenate an empty array later arr = np.random.randn(1,10) # Loop for i in range(10000 - 1): arr = np.concatenate((arr, np.random.rand(1,10))) ``` 或者,您也可以使用`np.append`操作来代替`np.concatenate`。实际上,`np.append`内部用的是`np.concatenate`,所以它的性能是以`np.concatenate`的性能为上限的。 然而,这并不是进行这种操作的好方法。因为`np.concatenate`和`append`不一样,不是常数时间函数。事实上,它是一个线性时间函数,因为它包括在内存中创建一个新数组,然后将两个数组的内容复制到新分配的内存中。 但是为什么 NumPy 不能按照`append`的工作方式实现一个常量时间`concatenate`?这个问题的答案在于列表和 NumPy 数组是如何存储的。 ### 列表和数组存储方式的区别 Python `list`由指向对象的引用组成。虽然引用是以连续的方式存储的,但是它们指向的对象可以在内存中的任何地方。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/9505d63ffadb4a5fea676131328c6ad3.png) A python list is made of reference to the objects, which are stored elsewhere in the memory. 每当我们创建 Python 列表时,都会为组成列表的引用分配一定量的连续空间。假设一个列表有`n`个元素。当我们在一个列表上调用`append`时,python 只是在连续空间中的$ {n + 1}^{th} $槽处插入一个对对象(被追加)的引用。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/e6bbfaded39cc912b7217b730f393ef9.png) An append operation merely involves adding a reference to wherever the appended object is stored in the memory. No copying is involved. 一旦这个连续的空间填满,一个新的、更大的内存块被分配给这个列表,并为新的插入留出空间。列表中的元素被复制到新的内存位置。虽然将元素复制到新位置的时间不是恒定的(它会随着数组的大小而增加),但是复制操作通常非常少见。因此,在*均情况下, append 花费的时间是常数,与数组的大小无关 然而,对于 NumPy,数组基本上是作为组成数组的连续对象块来存储的。与 Python 列表不同,在 Python 列表中我们只有引用,实际的对象存储在 NumPy 数组中。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/dccb557908475a2877df400c499ffa1a.png) Numpy Arrays are stored as objects (32-bit Integers here) in the memory lined up in a contiguous manner 一旦数组被初始化,NumPy 数组的所有空间都被预先分配。 ```py a = np.zeros((10,20)) # allocate space for 10 x 20 floats ``` Python 列表中没有*动态调整大小*。当你在两个数组上调用`np.concatenate`时,一个全新的数组被分配,两个数组的数据被复制到新的内存位置。这使得`np.concatenate`比 append 慢,即使它是在 c 中执行的。 为了避免这个问题,您应该尽可能地为数组预分配内存。在循环体之前预先分配数组,并在循环过程中简单地使用切片来设置数组的值。下面是上面代码的一个变种。 ```py arr = np.zeros((10000,10)) for i in range(10000): arr[i] = np.random.rand(1,10) ``` 这里,我们只分配一次内存。唯一涉及的复制是将随机数复制到分配的空间,而不是每次迭代都在内存中移动数组。 ### 计时代码 为了查看预分配数组的速度优势,我们使用`timeit`对两个片段进行计时。 ```py %%timeit -n 100 arr = np.random.randn(1,10) for i in range(10000 - 1): arr = np.concatenate((arr, np.random.rand(1,10))) ``` 输出是 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/51ff6b1571bcbe9e2e2f23d2388f3599.png) 而对于具有预分配的代码。 ```py %%timeit -n 10 arr = np.zeros((10000,10)) for i in range(10000): arr[i] = np.random.rand(1,10) ``` 我们获得了大约 25 倍的速度提升。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/20207849c6b4afce6fc6a1c443a0fd6c.png) ## 视图和副本 这是另一个看似无害的错误,实际上会降低代码的速度。考虑到你必须对一个连续索引的数组进行切片。 ```py a = np.arange(100) sliced_a = a[10:20] ``` 然而,您可以用下面的代码片段实现同样的效果。 ```py a = np.arange(100) sliced_a = a[range(10,20)] ``` 这被称为*花式索引*,其中你传递一个列表或元组作为索引,而不是普通的切片。当我们想要得到一个由不连续的索引组成的列表时,这是很有用的,就像通过做`arr[[2,7,11]]`得到一个数组的 2^{nd}$、$7^{th}$和$11^{th}的索引一样。 但是,你认为两者在计算速度上是一样的吗?让我们给他们计时。 ```py a = np.arange(100) %timeit -n 10000 a[10:20] %timeit -n 10000 a[range(10,20)] ``` 这是我的输出。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d3f738ebd4192222f3e655d588f14bb6.png) 我们看到不同顺序的运行时间!普通切片版本需要大约 229 纳秒,而*花式索引*需要大约 4.81 微秒,即 4810 纳秒,即*花式索引*慢了大约 20 倍! 发生这种情况是因为普通切片只需返回一个新的偏移量。您不需要创建数据的副本,因为切片中的数据序列与原始数组保持一致,因此您可以简单地更改数组的起始点。 然而,当一个人进行*花式索引时,*一个副本被创建。为什么?因为 NumPy 数组在内存中被实现为连续块。当我们索引类似于`a[[2,7,11]]`的东西时,索引`2`、`7`和`11`处的对象以不连续的方式存储。除非进行复制,否则新数组的元素不能连续排列。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/22a3bc5d0c3774a76a6ed429d26ba677.png) Difference between a View and a Copy in NumPy 这里的教训是,如果你有连续的索引要切片,总是选择普通切片而不是花哨的索引。 在下一节中,我们将解释 NumPy 的内部结构,数组是如何存储的,当我们改变或转置操作时会发生什么。 ## NumPy 内部 在 NumPy 中,不管数组的形状如何,内部数组都存储为连续的对象块。然而,帮助我们像处理多维数组一样处理它们的是一种叫做**的东西。** 例如,考虑下面的数组。 ```py [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] ``` 这个数组基本上存储在内存中,如下所示。 ```py [ 0 1 2 3 4 5 6 7 8 9 10 11] ``` 为了模拟连续对象块的尺寸,NumPy 使用跨距。我们对每个维度都有一个跨度。例如,对于上面的数组,步幅是`(32, 8)`。但是大踏步实际上意味着什么呢? 这意味着如果你想去二维数组的索引`[1,3]`,你将不得不去从开始算起`1 * 32 + 3 * 8`或`56`字节的内存位置。每个整数占用 32 位或 8 字节的内存。这意味着`56`字节从开始就对应着`7`整数。因此,当我们查询索引`[1,3]`时,我们得到的是`7`整数之后的整数,即索引号`8`,其值为 7。 ```py print(arr[1,3]) # Output -> 7 ``` 换句话说,一个维度的 stride 基本上告诉您,在保持其他元素不变的情况下,要到达该维度中的下一个元素,必须跳过连续内存中的多少物理内存块。例如,考虑索引`[0][2]`。为了跳转到第一维度`[1][2]`的下一个元素,我们必须在内存中跳转 32 位。类似地,我们在物理内存中跳转 8 位来索引`[0][3]`。 ## 重塑 事实上,NumPy 在内部将数组存储为连续的数组,这允许我们仅仅通过修改 NumPy 数组的跨度来*改变其维度。例如,如果我们采用上面的数组,并将其重新整形为`[6, 2]`,步长将变为`[16,8]`,而内部连续内存块将保持不变。* ```py a = np.arange(12).reshape(3,4) print(a) # Output [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] b = a.reshape(6,2) print(b) #Output [[ 0 1] [ 2 3] [ 4 5] [ 6 7] [ 8 9] [10 11]] ``` 我们也可以创造维度。例如,我们也可以将原始数组整形为`[2, 2, 3]`。这里大步换到`[48, 24, 8]`。 ```py c = a.reshape(2,2,3) print(c) #Output [[[ 0 1 2] [ 3 4 5]] [[ 6 7 8] [ 9 10 11]]] ``` 利用 NumPy 存储其数组的方式,我们可以 *r* e *整形* NumPy 数组,而不会产生任何显著的计算成本,因为它只涉及改变数组的步长。以连续方式存储在存储器中的数组不会改变。所以整形不需要复制。 为了更好地利用这个特性,我们必须理解整形是如何工作的。给定一个数组和一个目标形状,我们必须能够计算出重新整形后的数组是什么样子。这将引导我们思考一个可以通过一个或多个整形操作实现的解决方案。 ### 整形是如何进行的? 我们现在详细讨论重塑是如何工作的。当试图解释 NumPy 中的形状如何工作时,许多人坚持将数组想象成网格和立方体。 然而,一旦你超越三维,观想就变得很成问题。虽然我们可以在二维和三维阵列中使用立方体,但对于更高维的阵列,我们必须想出别的办法。 所以我建议,把数组想象成一棵树。树的每一级都代表一个原始顺序的维度。例如,我们上面提到的数组可以表示如下。 ```py [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d0b08132bde28bf0ac67329de4b3c900.png) 有了这种表示,就很容易知道当你改变一个数组的形状时会发生什么。需要注意的是,整形不会改变数组在内存中的存储方式。因此,当您重塑数组时,树叶的排序方式不会改变,只有树枝的排序方式会改变。例如,当我们将上面的数组从`[3, 4]`整形为`[6,2]`时,我们可以用树形图来想象整形操作。 ```py # Reshaped array -> [6, 2] [[ 0 1] [ 2 3] [ 4 5] [ 6 7] [ 8 9] [10 11]] ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/d9d0dadc0358612d2fd7ffda121c9a16.png) 这里有一个例子,我们将数组整形为`[2, 2, 3]`。 ```py [[[ 0 1 2] [ 3 4 5]] [[ 6 7 8] [ 9 10 11]]] ``` ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/6d67e898005bea890dd2bd30611adfe0.png) ## 置换 另一个允许我们改变数组形状的操作是`transpose`函数。它本质上使我们能够交换数组的维数。我们同样使用`transpose`操作。 转置函数的参数基本上是从`[0, 1, 2 .... n]`到新的索引排列的索引映射。例如,如果我有一个形状为`[5 2 4]`的数组,那么使用`transpose(2, 0, 1)`使它成为`[4 5 2]`,因为索引 0,1,2 分别被映射到它们的新位置。 ```py c = a.transpose(1,0) [[ 0 4 8] [ 1 5 9] [ 2 6 10] [ 3 7 11]] ``` 操作*转置*本身不需要任何复制,因为它仅仅涉及交换步幅。我们的原始阵列的步幅是`[32,8]`,而转置阵列的步幅是`[8, 32]`。 然而,一旦我们交换了步长,数组就不再以所谓的*行主*格式存储。大多数 NumPy 操作被设计成在*行主*阵列上工作。因此,有许多操作(如`flatten`)在转置数组上执行时,需要创建一个新的数组。解释*行主*和*列主*超出了本文的范围。不过这里给好奇的灵魂一个参考。 [](https://eli.thegreenplace.net/2015/memory-layout-of-multi-dimensional-arrays)[Memory layout of multi-dimensional arrays - Eli Bendersky’s website![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/9e4a3fe0496a483151b275e259d91c05.png)Eli Bendersky's website![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/c1e3c917893650913725d051c4a3554e.png)](https://eli.thegreenplace.net/2015/memory-layout-of-multi-dimensional-arrays) 创建新数组时,存储为连续块的元素的顺序会发生变化。考虑我们使用映射`(0, 1)`转置的二维数组。在新创建的数组中,与索引`[a][b]`对应的元素与原始数组中与索引`[b][a]`对应的元素交换。 回到树形可视化,这是上面的转置操作看起来的样子。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/ac1c48caef6dd1177ae92b17f8cfdbd9.png) 转置后的数组的形状为`[4,3]`。我们之前已经将我们的原始数组调整为`[4,3]`。请注意,尽管形状相同,但这两个数组是不同的。这是由于存储器中元素的顺序对于整形操作不变,而对于转置操作却变了。 继续看一个更复杂的例子,让我们考虑一个三维数组,在这个数组中我们交换了不止一组维度。用树形图来展示它会有点复杂,所以我们将使用代码来演示这个概念。我们对随机数组使用转置映射`(2, 0, 1)`。 ```py a = np.random.randint(100, size = (5, 7, 6)) b = a.transpose(2,0,1) ``` 如上所述,对应于索引`[i][j][k]`的任何元素将与对应于索引`[k][i][j]`的元素交换。我们可以用上面的数组试试。 ```py print(a[1,2,3] == b[3,1,2]) # output -> True print(a[3,4,2] == b[2,3,4]) # output -> True ``` ## 结论 这就是这篇文章的内容。在这篇文章中,我们讨论了一些重要的话题,比如跨步、重塑和移调。为了在 NumPy 的这些方面建立一个命令,我鼓励你思考类似于这篇文章中的例子,然后将结果与你所学的相比较。 正如本文开头所承诺的,在下一部分中,我们将使用整形和转置操作的混合来优化基于深度学习的对象检测器的输出管道。到那时,快乐的编码! ## 参考 [IPython Cookbook - 4.5\. Understanding the internals of NumPy to avoid unnecessary array copyingIPython Cookbook,![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/0a6e006f049031de92ca6d5e58b86559.png)4.5\. Understanding the internals of NumPy to avoid unnecessary array copying![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/35bd53a337ddb4f9ed96fd95b940818f.png)](https://ipython-books.github.io/45-understanding-the-internals-of-numpy-to-avoid-unnecessary-array-copying/)[](https://scipy-lectures.org/advanced/advanced_numpy/index.html)[2.2\. Advanced NumPy — Scipy lecture notes](https://scipy-lectures.org/advanced/advanced_numpy/index.html)[![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/e3672975f7d7f95419f55393ded1805e.png)](https://scipy-lectures.org/advanced/advanced_numpy/index.html) # NumPy 优化的基本要素第 1 部分:理解向量化和广播 > 原文:<https://blog.paperspace.com/numpy-optimization-vectorization-and-broadcasting/> 如果你在机器学习、数据科学或深度学习等领域工作,加速线性代数计算的库是一个主要工具。NumPy 是 Num 的缩写,可能是最有名的一个,你可能已经用过了。然而,仅仅使用 NumPy 数组代替普通的 Python 列表很难体现 NumPy 必须提供的功能。 在本系列中,我将介绍如何使用 NumPy 加速您的代码的最佳实践,如何利用矢量化和广播等特性,何时放弃专用特性以支持普通 Python 产品,以及一个案例研究,其中我们将使用 NumPy 编写 K-Means 聚类算法的快速实现。 就这一部分而言,我将涵盖: 1. 如何正确计时您的代码,以比较普通 Python 和优化 NumPy 代码。 2. Why are loops slow in Python? 3. 什么是矢量化,以及如何对代码进行矢量化。 4. 什么是广播,并举例说明其应用。 > **注意:**虽然本教程涵盖了 NumPy,但是这些技术也可以扩展到其他一些线性代数库,比如 PyTorch 和 TensorFlow。我还想指出,这篇文章绝不是对 NumPy 的介绍,而是假设读者对这个库有基本的了解。 [Introduction to NumPy | CodecademyGet acquainted with NumPy, a Python library used to store arrays of numbers, and learn basic syntax and functionality.![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/828dfeee163205d5301035bf002772ff.png)Codecademy![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/985f951b2386309b429318fffe9bc7d2.png)](https://www.codecademy.com/learn/intro-statistics-numpy/modules/dspath-intro-numpy) ## 为您的代码计时 为了真正体会 NumPy 提供的速度提升,我们必须想出一种方法来测量一段代码的运行时间。 为此我们可以使用 Python 的`time`模块。 ```py import time tic = time.time() # code goes here toc = time.time() print("Time Elapsed: ", toc - tic) ``` 这种方法的问题是只测量一段代码一次并不能给我们一个可靠的运行时间估计。例如,由于背景中的各种过程,对于特定的迭代,代码可能运行得更慢或更快。因此,谨慎的做法是计算多次运行的*均运行时间,以获得可靠的估计。为了实现这一点,我们使用 Python 的`timeit`模块。 ```py import timeit setup = ''' import numpy as np ''' snippet = 'arr = np.arange(100)' num_runs = 10000 time_elapsed = timeit.timeit(setup = setup, stmt = snippet, number = num_runs) print("Time Elapsed: ", time_elapsed / num_runs) # Output -> Time Elapsed: 5.496922000020277e-07 ``` `timeit.timeit`方法有三个参数: 1. `setup`是一个字符串,包含运行我们的代码片段所需的导入。 2. `stmt`是描述我们代码片段的字符串。 3. `number`是实验必须运行的运行次数。 `timeit`也可以用来测量函数的运行时间,但仅限于不带任何参数的函数。为此,我们可以将函数名(不是函数调用)传递给`timeit.timeit`方法。 ```py import timeit setup = ''' import numpy as np ''' def fn(): return np.arange(100) num_runs = 10000 time_elapsed = timeit.timeit(setup = setup, stmt = fn, number = num_runs) print("Time Elapsed: ", time_elapsed / num_runs) ``` 如果你使用的是 iPython 控制台或者 Jupyter 笔记本,可以使用`%timeit` magic 命令。输出比普通的`timeit.timeit`调用更加详细。 ```py %timeit arr = np.arange(100) # output -> 472 ns ± 7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) ``` ## 关于循环的一句话 每当人们寻找代码中的瓶颈时,尤其是 python 代码,循环通常是一个可疑点。与 C/C++等语言相比,Python 循环相对较慢。虽然有很多原因导致这种情况,但我想把重点放在一个特殊的原因上:**Python 的动态类型特性。** Python 首先逐行检查代码,将代码编译成字节码,然后执行它来运行程序。假设代码包含一个循环遍历列表的部分。Python 是动态类型的,这意味着它不知道列表中存在什么类型的对象(是整数、字符串还是浮点)。事实上,这些信息基本上存储在每个对象本身中,Python 在实际遍历列表之前无法提前知道这一点。因此,在每次迭代中,python 必须执行一系列检查,比如确定变量的类型、解析变量的范围、检查任何无效的操作等等。 与 C 语言相比,C 语言只允许数组包含一种数据类型,这一点编译器提前就知道了。这开启了许多优化的可能性,而这在 Python 中是不可能的。由于这个原因,我们看到 python 中的循环通常比 C 中的慢得多,嵌套循环是事情变得非常慢的地方。 ## …向量化… 好吧!所以循环会降低代码的速度。那么现在该怎么办呢?如果我们可以将列表限制为只有一种我们可以让 Python 提前知道的数据类型,会怎么样呢?那么我们能不能跳过 Python 所做的一些每次迭代的类型检查来加速我们的代码呢?NumPy 做了类似的事情。NumPy 允许数组只有一种数据类型,并将数据存储在内存的连续块中。利用这一事实,NumPy 将这类数组上的大多数操作委托给经过优化、预先编译的 C 代码。 事实上,您在 python 代码中使用 NumPy 调用的大多数函数仅仅是 C 中底层代码的包装器,大部分繁重的工作都是在 C 中完成的。通过这种方式,NumPy 可以将循环的执行转移到 C 中,这在循环方面比 Python 高效得多。请注意,这只能在数组强制数组元素为同一类型时才能实现。否则,就不可能将 Python 数据类型转换成在幕后执行的原生 C 数据类型。 我们举个例子。让我们编写一小段代码,它接受两个数组并执行元素级乘法。我们将代码放在一个函数中,只是为了方便以后对代码计时。 ```py def multiply_lists(li_a, li_b): for i in range(len(li_a)): li_a[i] * li_b[i] ``` 不要担心每次迭代都没有存储值。这个练习的目的是仅仅看到某些操作的性能,而不是真正关心结果。我们只想看看特定数量的乘法运算是如何进行的。 然而,如果我们使用 NumPy 数组,我们就不需要编写循环。我们可以简单地这样做,如下所示。 ```py arr_a = np.array(li_a) arr_b = np.array(li_b) def multiply_arrays(arr_a, arr_b): arr_a * arr_b ``` 这是怎么发生的?这是因为在内部,NumPy 将循环委托给预编译、优化的 C 代码。这个过程叫做乘法运算符的*向量化*。从技术上来说,函数的术语*矢量化意味着该函数现在同时应用于许多值,而不是单个值,这是 python 代码的外观(循环仍然执行,但在 C 中)* 既然我们已经使用了一个矢量化的函数来代替循环,它是否能提高我们的速度呢?我们运行重复实验 5 次(`-r`标志),每次运行代码执行 10000 次(`-n`标志)。 ```py %timeit -n 10000 -r 5 multiply_lists(li_a, li_b) %timeit -n 10000 -r 5 multiply_arrays(arr_a, arr_b) ``` 以下是我的输出。 ![](https://github.com/OpenDocCN/geekdoc-dl-zh/raw/master/paperspace/img/e9d454bc1255a74f6241b3bdb54623af.png) 根据处理能力和后台运行的其他任务,机器上的时间可能会有所不同。但是,当使用 NumPy 的矢量化解决方案时,您会注意到相当大的加速,大约是 20-30 倍。 请注意,我在这里使用了`%timeit`魔法,因为我正在 Jupyter 细胞中进行实验。如果您使用普通的 python 代码,那么您将不得不使用`timeit.timeit`函数。`timeit.timeit`函数的输出仅仅是总时间除以迭代次数。 ```py import timeit total_time = timeit.timeit("multiply_lists(li_a, li_b)", "from __main__ import multiply_lists, li_a, li_b", number = 10000) time_per_run = total_time / 10000 print(time_per_run) ``` 另外,从现在开始,当我提到短语*矢量化循环时,*我的意思是获取一个循环,并使用 NumPy 的矢量化函数之一实现相同的功能。 除了对在两个大小相等的数组上执行操作的循环进行矢量化之外,我们还可以对在数组和标量之间执行操作的循环进行矢量化。例如,循环: ```py prod = 0 for x in li_a: prod += x * 5 ``` 可以矢量化为: ```py np.array(li_a) * 5 prod = li_a.sum() ``` ### 一个实例:图像间的 L2 距离 现在让我们举一个实际的例子。这是你在使用基于视觉的机器学习时经常会遇到的情况。假设你有两幅图像,你想计算它们之间的 L2 距离。这可以描述为 $$ L2(I_1,I _ 2)= \ sum _ { x } \ sum _ { y } \ sum _ { z }(I _ 1[x,y,z] - I_2[x,y,z])^2 \]

这仅仅意味着获得 RGB 图像中每个像素的*方差,然后将这些差相加。我们比较了基于循环和矢量化实现的运行时间。但是请注意,在之前的比较中,我们对循环版本使用了 Python 列表,对矢量化版本使用了 NumPy 数组。会不会是 NumPy 数组而不是矢量化造成了这种差异(也就是说,使用 NumPy 数组的 python 循环会不会同样快?)

为了验证这一点,在本例中,我们将对循环和矢量化版本都使用 NumPy 数组,以了解真正给我们带来速度优势的是什么。循环操作需要使用三重嵌套循环,这是事情变得非常慢的地方。(通常,循环嵌套越深,执行速度越慢)

# Used to load images
import cv2 

# load the images
image1 = cv2.imread("image1.jpeg").astype(np.int32)
image2 = cv2.imread("image2.jpeg").astype(np.int32)

# Define the function that implements the loop version
def l2_loop(image1, image2):
    height, width, channels = image1.shape
    distance = 0

    for h in range(height):
        for w in range(width):
            for c in range(channels):
                distance += (image1[h][w][c] - image2[h][w][c])**2

# Define the vectorised version
def l2_vectorise(image1, image2):
    ((image1 - image2)**2).sum()

现在让我们测量我们的脚本运行 100 次以上所花费的时间,重复 3 次。运行基于循环的版本可能需要一段时间。

%timeit -n 100 -r 3 l2_loop(image1, image2)
%timeit -n 100 -r 3 l2_vectorise(image1, image2)

我们看到矢量化版本比循环版本快大约 2500 倍。还不错!

广播

如果我们要对一个循环进行矢量化,处理大小不同的数组,会发生什么呢?

让我们从一个非常简单的例子开始。假设我有一个形状为(3,4)的矩阵,包含 3 行 4 列。现在,假设我想向网格中的每一列添加一个列向量。为了说明这一点,这是我正在努力实现的目标。

这可以通过几种方式来实现。我们可以循环矩阵的列,并添加每一列。

arr = np.arange(12).reshape(3,4)

col_vector = np.array([5,6,7])

num_cols = arr.shape[1]

for col in range(num_cols):
	arr[:, col] += col_vector 

av

然而,如果我们的原始数组arr中的列数增加到一个非常大的数字,上面描述的代码将运行缓慢,因为我们在 Python 中循环遍历列数。做一个和原数组大小相等,列相同的矩阵怎么样?(我们将这种方法称为列堆叠方法)

arr = np.arange(12).reshape(3,4)
add_matrix = np.array([col_vector,] * num_cols).T

arr += add_matrix 

这给了我们一个更快的解决方案。虽然这种方法在 2 维数组的情况下工作得很好,但是对更高维的数组应用同样的方法可能有点棘手。

然而,好消息是 NumPy 为我们提供了一个名为 Broadcasting 的特性,它定义了如何在大小不等的数组上执行算术运算。根据广播上的 SciPy docs 页面,

在某些约束条件下,较小的阵列在较大的阵列中“广播”,以便它们具有兼容的形状。广播提供了一种向量化数组操作的方法,因此循环在 C 而不是 Python 中发生

在幕后,NumPy 做了一些类似于我们的列堆叠方法的事情。然而,我们不必担心在多个方向上显式地堆叠数组。

现在让我们了解 NumPy 的广播规则。这些就是上面的定义所说的某些约束。两个阵列必须满足这些条件,较小的阵列才能通过较大的阵列进行广播

广播规则

在我们开始之前,我们需要知道的一个重要定义是 NumPy 中数组的排名。是 NumPy 数组的总维数。例如,形状(3,4)的数组的为 2,形状(3,4,3)的数组的为 3。现在谈谈规则。

  1. 为了判断哪两个数组适合于运算,NumPy 从数组的尾部开始逐维比较这两个数组的形状。(从右到左)
  2. 如果两个维度都相等,或者其中一个为 1,则称这两个维度相容。
  3. 如果两个维度不相等,并且都不是 1,那么 NumPy 将抛出一个错误并停止。

秩相等的数组

我们首先考虑我们正在处理的两个数组的秩相同的情况。下图演示了哪些数组是兼容的,哪些是不兼容的。

如你所见,我们从左到右工作。在右边的第二个例子中,我们从左边开始,但是当我们到达第二维度时(两个数组分别为 4 和 5。),我们看到有一个区别,两者都不是 1。因此,试图对它们进行操作会导致错误

arr_a = np.random.rand(3,4,6,2) # random array of shape (3,4,6,2)
arr_b = np.random.rand(3, 5, 1, 2)

arr_a + arr_b   # op throws an error 

在左边的第一个例子中,我们在第三维度遇到了不同的维度(两个数组分别为 1 和 6)。然而,根据规则 2,这些维度是相容的。其他维度都是一样的。所以我们可以用这两个数组进行算术运算。

arr_a = np.random.rand(3,4,6,2) # random array of shape (3,4,6,2)
arr_b = np.random.rand(3, 4, 1, 2) 

arr_a + arr_b   # op goes through without throwing an error. 

不等秩数组

在满足某些条件的情况下,具有不相等秩的阵列也可以被操作。同样,我们应用从左到右移动并比较两个数组的规则。让我们考虑下面的例子。

在上图中,我们看到在第一种情况下,第一个数组的秩为 4,而第二个数组的秩为 3。我们可以从左到右比较 3 维,之后第二个数组就没有维度了。为了比较两个这样的数组,Numpy 将尺寸为 1 的前向维度附加到较小的数组,使得它的秩等于较大的数组。所以上面所有的比较都可以当作。

现在,可以很容易地进行比较。

请注意,我用斜体表示添加了的,因为这只是一种可视化 NumPy 正在做什么的方式。在内部,没有附加。

广播期间会发生什么

虽然很容易理解当两个维度相似时如何执行运算,但现在让我们理解当其中一个维度为 1 时如何执行运算(规则 2)。

为此,考虑我们上面的例子,我们想给矩阵的所有列添加一个列向量。数组的形状是(3,4)(3,),根据广播规则不能添加。然而,如果我们将形状(3,)的列向量整形为(3, 1),这两个形状就变得兼容了。

col_vector = col_vector.reshape((3, 1)) # reshape the array
arr += col_vector                      # addition goes through!

但是等等,到底发生了什么?第二次元,arrcol_vector的 4 和 1 分别是怎么调和的?

在这种情况下,NumPy 将执行操作,就好像大小为(3, 1)的第二个数组是形状为(3,4)的数组一样。大小为 1 的维度中的值(在这种情况下,原始数组的第二维是形状为(3, **1**))现在将在 4 个维度中重复,以创建形状为(3,4)的数组。为了理解这一点,考虑第二个数组,以及它的第二维的值。

print(col_vector[0, :])       # output -> [5]
print(col_vector[1, :])       # output -> [6]
print(col_vector[2, :])       # output -> [7] 

现在,形状为(3,4)的新创建的数组将在其第二维中具有重复的值。为了帮助我们的想象,我们使用函数np.brodcast_to,它让我们了解新的广播的数组是如何创建的。

broadcasted_col_vector = np.broadcast_to(col_vector, (3,4))

print(broadcasted_col_vector[0,:])   # output -> [5, 5, 5, 5]
print(broadcasted_col_vector[1,:])   # output -> [6, 6, 6, 6]
print(broadcasted_col_vector[2,:])   # output -> [7, 7, 7, 7] 

如您所见,第二维度中的值(其原始大小为 1)已经重复了 4 次,从而创建了大小为 4 的维度。

为了形象地表示正在发生的事情,该数组在其第二维上重复 4 次,以创建一个相等的数组。

这正是我们在列堆栈操作中所做的!加法的结果就是我们想要的!

让我们考虑形状(3,4,5)和(1,4,5)的三维数组的情况

实际上,并没有创建新的数组。重复的数组仅仅是一种想象操作将如何执行的心理工具。相反,计算在多个维度上重复,而不创建新的数组。这类似于将大小为 1 的第一数组的维度的值跨越多个位置广播到大小大于 1 的第二数组的维度的值。因此,这个过程被称为广播。

一个实际的例子:给图像添加颜色

假设您有一个图像,对于每个像素,您希望将红色值增加 10,绿色值增加 5,蓝色值增加 15。

这可以通过广播很容易地实现。如果我们使用 OpenCV 读取图像,那么图像被表示为具有形状(H,W,C)的矩阵。我们来读一个图像吧!

img = cv2.imread("image1.jpeg")
print(img.shape)

# output -> (768, 1024, 3)

现在,通道被编码在这个数组的第三维中。我们想给img[ : , : ,0]描述的第一通道、img[ : , : ,1]描述的第二通道、img[ : , : ,2]描述的第三通道分别加上 10、5、15。使用下面这段代码可以很容易地做到这一点。

add_color = [10, 5, 15]
img += add_color

我们图像的形状是(768, 1024, 3),而我们的颜色向量的形状是(3,)。此外,该数组的大小将调整为(1, 1, 3),随后,每个颜色值将分别通过 R、G、B 通道广播。

然而,深度学习的一些应用要求图像以格式[C, H, W存储。在这种情况下,我们的图像将具有形状(3, 768, 1024)。为了做同样的操作,我们将不得不把我们的颜色向量重新塑造成形状(3, 1, 1)以便它是兼容的。然后,我们可以很容易地将两者相加。

img = img.transpose((2, 0, 1))           # change shape to (3, 768, 1024)
add_color = add_color.reshape((3,1,1,))
img += add_color

将循环可视化为数组

当涉及到向量化和广播时,有一件事一直在帮助我,那就是将循环中发生的任何事情可视化为在数组上执行的操作

假设,我们有两个大小不等的数组。我们想对两个数组的每个元素组合取一个乘积,然后求和。例如,对于数组[1, 2, 3][4, 5],总和为

\[1*4 + 2 * 4 + 3 * 4 + 1*5 + 2*5 + 3 * 5 \]

使用循环,这就是我们的做法,

sum  = 0

arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5])

for i in arr1:
	for j in arr2:
    	sum += i*j

够简单了吧?但这是一个嵌套循环,如果这些数组的大小变得太大,那么运行时间也会增加。

我们如何摆脱这两个循环,并提出一个矢量化的解决方案。为此,请注意循环变量ij在做什么。i从 1 到 3,而j从 4 到 5。在循环中,我们有[i,j]的每一种可能的组合,我们将它们相乘,然后求和。

我们能不能有一个二维数组,它的索引[i,j]代表每个这样的组合的乘积?如果是的话,我们可以用一个 NumPy 数组来表示这个数组,我们可以不用循环,只需对数组的元素求和!这就是数组的样子。

这不过是两个数组的乘积..

但是,请注意i的值是如何在第一个数组的列中重复的,而j的值是如何在第二个数组的行中重复的。这看起来眼熟吗?请注意,如果我们将原来的arr1arr2数组分别整形为[3,1][1,2],并将这两个数组相乘,那么它们将如下所示进行广播。

这正是我们想要的!我们现在可以用代码实现它。

arr1 = arr1[:, None]    # reshape to (3, 1)
arr2 = arr2[None, :]    # reshape to (1, 2)

sum = (arr1 * arr2).sum()

结论

唷!那是一个详细的帖子!说实话,向量化和广播是在 NumPy 中编写高效代码的两个基石,这就是为什么我认为这些主题值得进行这么长时间的讨论。我鼓励你拿出玩具示例来更好地理解概念。

在下一部分中,我们将使用本文中介绍的内容,通过矢量化和广播来优化 K-Means 聚类算法(使用 Python 列表和循环实现)的简单实现,实现 70 倍的加速!

在那之前,编码快乐!

posted @ 2024-11-02 15:52  绝不原创的飞龙  阅读(10)  评论(0编辑  收藏  举报