TowardsDataScience 博客中文翻译 2020(九百二十五)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

从头开始理解 DBSCAN 算法和实现

原文:https://towardsdatascience.com/understanding-dbscan-algorithm-and-implementation-from-scratch-c256289479c5?source=collection_archive---------17-----------------------

DBSCAN 算法分步,Python 实现,可视化。

什么是 DBSCAN

DBSCAN(带噪声的基于密度的应用空间聚类)是一种常用的无监督聚类算法,于 1996 年提出。与众所周知的 K-mean 不同,DBSCAN 不需要指定聚类数。它可以根据您输入的数据和参数自动检测集群的数量。更重要的是,DBSCAN 可以发现 k-means 不能发现的任意形状的聚类。例如,被不同群集包围的群集。

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

DBSCAN vs K-means,信用

此外,DBSCAN 可以处理噪声和异常值。所有离群值将被识别和标记,而不被分类到任何聚类中。因此,DBSCAN 也可以用于异常检测(异常值检测)

在我们查看 preusdecode 之前,我们需要先了解一些基本概念和术语。Eps、Minpits、直接密度可达、密度可达、密度连通、核心点和边界点

首先,我们需要为 DBSCAN、Eps 和 MinPts 设置两个参数。

Eps:小区最大半径

MinPts:该点的 Eps 邻域中的最小点数

并且有直接密度可达的概念:一个点 p 是从一个点 q 直接密度可达 w.r.t. Eps,MinPts,如果 NEps (q): {p 属于 D | dist(p,q) ≤ Eps}并且|N Eps (q)| ≥ MinPts。我们来看一个 Minpts = 5,Eps = 1 的例子。让我们看一个例子来理解密度可达和密度连通。

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

密度可达示例

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

密度相关示例

最后,如果一个点在 Eps 中有超过指定数量的点(MinPts ),那么这个点就是核心点。这些是在聚类 a 内部的点。并且边界点在 Eps 内具有比 MinPts 少的点,但是在核心点的邻域内。我们还可以定义离群点(噪声点),即既不是核心点也不是边界点的点。

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

核心点、边界点、异常点示例

现在,我们来看看 DBSCAN 算法实际上是如何工作的。这是前解码。

  1. 任意选择一个点 p
  2. 基于 Eps 和 MinPts 检索从 p 密度可达的所有点
  3. 如果 p 是一个核心点,就形成了一个集群
  4. 如果 p 是一个边界点,则没有从 p 密度可达的点,DBSCAN 访问数据库的下一个点
  5. 继续该过程,直到处理完所有点

如果使用空间索引,DBSCAN 的计算复杂度为 O(nlogn),其中 n 是数据库对象的数量。否则,复杂度为 O(n )

例子

考虑以下 9 个二维数据点:

x1(0,0),x2(1,0),x3(1,1),x4(2,2),x5(3,1),x6(3,0),x7(0,1),x8(3,2),x9(6,3)

使用欧几里德距离,Eps =1,MinPts = 3。找到所有的核心点、边界点和噪声点,并使用 DBCSAN 算法显示最终的聚类。让我们一步步展示结果。

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

数据可视化示例

首先,计算 N§,Eps-点 p 的邻域

N(x1) = {x1,x2,x7}

N(x2) = {x2,x1,x3}

N(x3) = {x3,x2,x7}

N(x4) = {x4,x8}

N(x5) = {x5,x6,x8}

N(x6) = {x6,x5}

N(x7) = {x7,x1,x3}

N(x8) = {x8,x4,x5}

N(x9) = {x9}

如果 N§的大小至少为 MinPts,则称 p 为核心点。这里给定的 MinPts 是 3,因此 N§的大小至少是 3。因此核心点是:{x1,x2,x3,x5,x7,x8}

然后根据边界点的定义:给定一个点 p,若 p 不是核心点但 N§包含至少一个核心点,则称 p 为边界点。N(x4) = {x4,x8},N(x6) = {x6,x5}。这里 x8 和 x5 是核心点,所以 x4 和 x6 都是边界点。显然,左边的点, x9 是一个噪声点。

现在,让我们按照 preusdecode 生成集群。

  1. 任意选择一个点 p,现在我们选择 x1
  2. 检索从 x1: {x2,x3,x7}密度可达的所有点
  3. 这里 x1 是一个核心点,形成一个集群。因此,我们有 Cluster_1: {x1,x2,x3,x7}
  4. 接下来,我们选择 x5,检索从 x5 密度可达的所有点:{x8,x4,x6}
  5. 这里 x5 是一个核心点,形成一个集群。因此,我们有了 Cluster_2: {x5,x4,x8,x6}
  6. 接下来,我们选择 x9,x9 是一个噪声点,噪声点不属于任何聚类。
  7. 因此,算法到此为止。

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

最终 DBSCAN 集群结果

Python 实现

下面是一些示例代码,用于从头开始构建 FP-tree,并在 Python 3 中找到所有的频率项集。我还添加了点的可视化,并用蓝色标记所有异常值。

感谢您的阅读,我期待听到您的问题和想法。如果你想了解更多关于数据科学和云计算的知识,可以在Linkedin上找我。

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

照片由 Alfons MoralesUnsplash 拍摄

参考

https://github.com/NSHipster/DBSCAN

https://en.wikipedia.org/wiki/DBSCAN

理解决策树分类器

原文:https://towardsdatascience.com/understanding-decision-tree-classifier-7366224e033b?source=collection_archive---------17-----------------------

概念上以使用 ID3 算法为例

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

凯文·Ku 在 Unsplash 上的照片

在这篇文章中,我们将从概念上讨论决策树分类器的工作原理,这样它以后就可以应用到现实世界的数据集中。

分类可以定义为学习一个目标函数 f 的任务,该目标函数将每个属性集合 x 映射到一个预定义的标签 y

例子:

  • 将一条新闻分配给一个预定义的类别。
  • 根据邮件标题和内容检测垃圾邮件
  • 基于 MRI 扫描的结果将细胞分类为恶性或良性
  • 根据形状对星系进行分类

作为数据科学家或机器学习工程师,在处理真实世界数据集时,决策树可能是一个强大的工具。当您构建随机森林分类器时,也可以串联使用决策树,该分类器是多个决策树共同工作的顶点,根据多数投票对记录进行分类。

一个决策树是通过对我们得到的数据集的一个记录提出一系列问题来构建的。每次收到一个答案,都会询问后续问题,直到对记录的类别标签得出结论。这一系列问题及其可能的答案可以以决策树的形式组织起来,这是一种由节点和有向边组成的层次结构。树有三种类型的节点:

  • 根节点,没有输入边,有零个或多个输出边。
  • 内部节点,每个节点都有一条输入边和两条或多条输出边。
  • 叶节点或终端节点,每个节点只有一条输入边,没有输出边。

在决策树中,每个叶节点被分配一个类标签。包括根节点和其他内部节点在内的非终端节点包含属性测试条件,用于分隔具有不同特征的记录。

在深入研究数据集的数学之前,让我们根据给定的数据集直观地构建一个决策树。

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

作者的样本数据集|图像

当我们检查这个数据集时,我们看到:

  1. 属性房屋所有者本质上是二元的,仅由 & 组成
  2. 属性婚姻状况本质上是分类的,包括单身、已婚&离婚
  3. 属性年收入本质上是连续的,由数值组成
  4. 属性默认借款人本质上是二元的,仅由 & 组成,是我们的目标属性或类标签

给定这个数据集,为了直观地构建决策树,我们从目标属性默认借款人开始

  • 初始树包含一个节点,其类别标签为默认借款人=否
  • 树需要细化,因为这个根节点包含来自两个类的记录
  • 记录随后根据房屋所有者条件的结果进行划分
  • 这是递归完成的
  • 从上面的数据集中,所有拥有房屋的借款人(房屋所有人=是)都成功偿还了贷款。然后,根节点的左子节点因此被标记为默认借款人=否
  • 对于正确的孩子,我们需要应用递归步骤,直到所有记录都属于同一个类

上述步骤可能在一次阅读中就能消化,我鼓励你再读一遍,看看下面的图,以了解这些步骤是如何进行的。

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

使用直觉构建决策树|作者图片

决策树归纳的设计问题

上面的树归纳附带了几个在构建决策树时需要注意的问题。

→培训记录应该如何拆分?

对于树生长过程的每个递归步骤,必须选择一个属性测试条件,以进一步将记录划分为更小的子集。

→分割程序应该如何停止?

继续展开节点,直到所有记录都属于同一个类,或者所有记录都具有相同的属性值。

表示属性测试条件的方法

决策树归纳算法必须提供一种方法来表达属性测试条件及其对不同属性类型的相应结果。

  • **二元属性:**两种可能的结果。

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

二元属性分割|按作者分类的图像

  • 名义属性: a)多路拆分
    b)二进制拆分

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

多向属性拆分(左)和二进制属性拆分(右)|作者图片

  • 序数属性: a)多路拆分
    b)二进制拆分

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

序数属性按作者拆分|图像

注意:只要分组不违反属性值的顺序属性,属性值就可以分组在一起。

  • **连续属性:**测试条件可以表示为具有二元结果的比较测试(A < v)或(A≥v ),或具有 v(i) ≤ A ≤ v(i+1)形式的结果的范围,其中 i = 1,2,3… k
    a)对于二元情况,算法必须考虑所有可能的分割位置 v,并选择产生最佳分割的位置。
    b)对于多路分割,算法必须考虑所有可能的连续值范围。

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

连续属性分割|按作者分类的图像

选择最佳分割的方法

为选择最佳分裂而开发的度量通常基于子节点的不纯程度。杂质越小,阶级分布越不均匀。

**示例:**具有类分布(0,1)的节点具有零杂质,而具有均匀类分布(0.5,0.5)的节点具有最高杂质。

杂质测量

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

杂质测量|作者图片

在哪里,

D =训练集,其中 v 属于 D 并且是树节点,

L = {y1,y2,…,yk}标签/类别集

让我们用一些例子来理解上面的杂质测量:

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

行动中的杂质测量|作者图片

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

杂质测量比较|作者图片

→为了确定一个测试条件执行得有多好,我们需要比较父节点(分裂前)和子节点(分裂后)的不纯程度。差异越大,测试条件越好。增益是一个可用于确定分割质量的标准。

对于将属于 A 的属性 A 的值分割成子节点(v,A)的节点 v,分割的增益为:

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

信息增益|作者图片

注:Inf 增益是信息增益

增益比

熵和基尼指数等杂质度量往往倾向于具有大量不同值的属性。有两种策略可以克服这个问题。第一个策略是将测试条件限制为仅二进制分割。这种策略被诸如 CART 之类的决策树算法所采用。另一个策略是修改分割标准,以考虑由属性测试条件产生的结果的数量。例如,在 C4.5 决策树算法中,称为增益比的分割标准用于确定分割的好坏。该标准定义如下:

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

增益比|作者图片

在哪里,

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

增益比的组成部分|作者图片

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

作者对上图中求和条件的解释

ID3 算法

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

ID3 算法伪代码|图片作者

让我们用一个样本数据集来理解这个算法。我们将使用的衡量标准是信息增益来构建我们的决策树。

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

样本数据集|源 Tan 的数据挖掘

我没有输入长的方程式,而是用图片来解释我使用的所有快捷键。

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

目标属性的熵|按作者分类的图像

在上图中 Y =是,N =否

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

游戏信息增益,Windy |作者图片

在上面的图片中,W = T 和 W = F 是真和假,属性 Windy 的值

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

游戏、展望|作者图片的信息增益

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

游戏、展望|作者图片的信息增益

在上图中,O = S,O = O,O = R 是属性 Outlook 的晴天、阴天和雨天的值

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

游戏信息增益,温度|作者图片

在上图中,温度。= Temperature 和 T=H,T=M,T=C 是属性 Temperature 的高温、低温和低温值

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

玩耍的信息增益,湿度|作者图片

在上图中,H=H,H=N 是湿度属性的高值和正常值

因为 outlook 的收益最高,所以 Outlook 是我们的根节点。

现在,用 Outlook = Sunny 计算第一个子节点

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

展望的信息增益=晴朗,温度|作者图片

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

展望的信息增益=晴天,湿度|作者图片

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

Outlook 的信息增益=晴朗、多风|作者图片

既然,Outlook 的信息增益=晴天,湿度最高,它就成为我们的第一个子节点。

对于根节点为 Outlook 的第二个子节点,我们考虑配对 Outlook =阴,并发现当 Outlook =阴时,Play = Yes,则直接导致 Yes 的叶节点

对于 Outlook 位于根节点的第三个子节点,我们考虑配对 Outlook = Rainy

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

展望的信息增益=雨天、气温|作者图片

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

展望的信息增益=雨天、气温|作者图片

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

Outlook 的信息增益=雨天、湿度|作者图片

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

Outlook 的信息增益=下雨、刮风|作者图片

因为 Outlook = Rainy,Windy 的信息增益最高,所以它成为我们的第三个子节点。

继续向下,深入到第一个子节点,其中 Outlook = Sunny 和湿度,我们观察到湿度的分割是纯的,因此我们不需要计算信息增益并直接放置叶节点。

同样的情况发生在 Outlook = Rainy 和 Windy 上,我们观察到 Windy 上的分裂是纯的。

意思是

  • 当前景=晴朗且湿度=高时,目标属性 Play = No
  • 当前景=晴朗,湿度=正常时,目标属性 Play = Yes
  • 当 Outlook = Rainy and Windy = True 时,目标属性 Play = No
  • 当 Outlook = Rainy and Windy = False 时,目标属性 Play = Yes

最终的决策树如下所示:

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

最终决策树|作者图片

我将免费赠送一本关于一致性的电子书。在这里获得你的免费电子书。

感谢您的阅读。这篇文章到此结束。我希望我能够使决策树的主题更容易理解。这只是决策树领域中使用的一个非常基本的算法。还有更多的算法,我建议读者在理解 ID3 算法后看一看,因为这是所有算法的基础,也是理解其余算法所需要的。

此外,我已经写了一篇的帖子,使用 Python3 和 XML 从零开始实现上述决策树,而没有使用任何机器学习库。

[## 机器学习决策树实现

使用 Python 和 XML 的 ID3 算法

medium.com](https://medium.com/swlh/machine-learning-decision-tree-implementation-849df3ce36d2)

如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑注册成为一名媒体成员。每月 5 美元,你可以无限制地阅读媒体上的故事。如果你注册使用我的链接,我会赚一小笔佣金,不需要你额外付费。

[## 加入我的推荐链接-塔伦古普塔

作为一个媒体会员,你的会员费的一部分会给你阅读的作家,你可以完全接触到每一个故事…

tarun-gupta.medium.com](https://tarun-gupta.medium.com/membership)

这是我的故事索引:

[## 标记故事列表的快速链接—感谢您的访问

我也有一份以快节奏出版为目标的出版物。读书成为作家。

tarun-gupta.medium.com](https://tarun-gupta.medium.com/thank-you-for-visiting-my-profile-9f708062c75e)

理解卷积神经网络中的深度关联嵌入

原文:https://towardsdatascience.com/understanding-deep-associative-embedding-in-convolutional-neural-networks-5a57685ee856?source=collection_archive---------79-----------------------

一种无需标记即可对预测进行分组的优雅方法

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

Alex Alvarez 在 Unsplash 上的照片

在计算机视觉和深度学习的一些任务中,我们需要先预测所有的结果,然后将结果拆分成几个单独的结果。

在这种精神下,一个常见的任务是多人的姿势估计,其中首先预测图像中所有人的关键点,然后将其分割成单个姿势作为最终预测(图 1)。

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

图 1:多人的姿势估计

本着这种精神的另一个任务是通过检测和分组对象的成对关键点来形成边界框的对象检测,与 R-CNN 系列作品[Ren 等人]相比,这是形成边界框的相对较新的方法。在他们的新方法[Law 等人]中,每个对象的边界框由两个关键点表示(左上角和右下角)。在推断过程中,首先预测图像中所有对象的关键点,然后分裂成单独的关键点对以获得最终预测(图 2)。

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

图 2:用于物体检测的角网[Law 等人]

以上两项工作都需要将所有预测的关键点拆分到单独的组中。为了实现在神经网络中作为副作用,分裂和分组算法应该同时满足两个方面:1)属于同一对象的关键点应该被正确地分配到同一组;2)哪一个关键点属于哪一个群体应该是内在理解的,不需要额外的标注工作。

联想嵌入[Newell et al.]正是实现上述讨论的一个很好的选择。

联想嵌入

让我们以多人姿势估计为例。为了将所有预测的关键点分组到每个个体,标签也与每个关键点一起被预测,并且预测的标签应该满足两个方面:1)同一个人的标签应该尽可能相等;2)不同人的标签应该容易区分。根据这些方面,Newell 等人提出了以下损失函数来训练标签值(等式。1).

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

Equ。1:用于学习关联嵌入的标签值的损失函数

在损失函数中,为 N 个人中的每一个预测 K 个关键点和 K 个标签值,从而总共预测 NK 个关键点和 NK 个标签值。

第一项(红色)通过惩罚预测标签值与其平均值的偏差,使同一个人的标签值相似。第二项(蓝色)通过惩罚一个高斯函数使不同人的标签值不同,该高斯函数的峰值在两个不同人的平均标签值相遇的位置,随着两个平均值彼此偏离,损失急剧下降(图 3)。

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

图 3:等式的第二项的解释。一

**有了这个损失函数,每个人的标签值可以自动学习,无需额外标记。**因此,联想嵌入快速、轻便,易于嵌入到现有的深度学习架构中。

预测标签值的结果

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

图 4:用于多人姿势估计的关联嵌入

如图 4 所示,右边部分是预测的标签值,左边部分是相应的姿态估计。右边部分,y 轴是人体关键点的索引,x 轴是标签的值。红色圆圈代表检测到的关键点的标签值。由于部分人的身体部位被遮挡,检测到的关键点数量因人而异。我们可以很容易地看到,不同人的标签值聚集在不同的垂直线中,这很容易单独分组。

因此,关联嵌入可以以精确和轻量的方式将预测结果分组到个体级别。此外,通过简单地添加损失函数,作为机器学习中的常见实践,关联嵌入不仅可以用于计算机视觉任务,还可以作为更广泛的机器学习领域中的其他任务的强大成分。

参考文献

更快的 R-CNN:使用区域建议网络实现实时对象检测,任等,NIPS 2015

关联嵌入:用于联合检测和分组的端到端学习,Newell 等人,NIPS 2017

CornerNet:将对象检测为成对的关键点,Law 等人,ECCV 2018 年

[## 加入我的介绍链接-陈数杜媒体

阅读陈数·杜(以及媒体上成千上万的其他作家)的每一个故事。您的会员费直接支持…

dushuchen.medium.com](https://dushuchen.medium.com/membership)

理解具有集成梯度的深度学习模型

原文:https://towardsdatascience.com/understanding-deep-learning-models-with-integrated-gradients-24ddce643dbf?source=collection_archive---------7-----------------------

理解并实现各种深度学习网络的集成梯度技术,以解释模型的预测

本帖将帮助你理解积分梯度的两个基本公理,以及如何使用迁移学习模型使用 TensorFlow 实现积分梯度。

什么是积分渐变?

集成梯度(IG)是用于深度神经网络的可解释性或可解释性技术,其可视化了有助于模型预测的输入特征重要性

IG 可以只应用于深度学习的特定用例,还是只应用于特定的神经网络架构?

集成梯度(IG)计算模型预测输出到其输入特征和的梯度,不需要对原始深度神经网络进行修改。

IG 可以应用于任何可区分的模型,如图像、文本或结构化数据。

IG 可用于

  • 通过从网络中提取规则来理解特征重要性
  • 调试深度学习模型性能
  • 通过理解对预测有贡献的重要特征来识别数据偏差

积分渐变是如何工作的?

解释 IG 使用深度学习模型进行图像分类

积分梯度建立在两个需要满足的公理之上:

  1. 灵敏度和
  2. 实现不变性

灵敏度:

为了计算灵敏度,我们建立一个基线图像作为起点。然后,我们构建一系列图像,从基线图像插值到实际图像,以计算综合梯度。

实现不变性

当两个功能等效的网络对于相同的输入图像和基线图像具有相同的属性时,实现不变性被满足。

当两个网络的输出对所有输入都相等时,尽管它们的实现非常不同,但它们在功能上是等效的。

计算和可视化集成梯度(IG)

步骤 1: 从基线开始,基线可以是像素值全为零的黑色图像或全白图像,也可以是随机图像。基线输入是一种中性的预测,是任何解释方法和可视化像素要素重要性的核心。

**第二步:**生成基线和原始图像之间的线性插值。插值图像是基线和输入图像之间的特征空间中的小步长(α),并且随着每个插值图像的强度不断增加。

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

步骤 3:计算梯度以测量特征变化和模型预测变化之间的关系。

梯度告知哪个像素对模型预测的类别概率具有最强的影响。

改变变量会改变输出,并且变量将获得一些属性来帮助计算输入图像的特征重要性。不影响输出的变量没有属性。

第四步:通过平均梯度计算数值近似值

步骤 5:将 IG 缩放到输入图像以确保在多个插值图像上累积的属性值都是相同的单位。用像素重要性表示输入图像上的 IG。

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

如何使用 Tensorflow 实现积分渐变?

导入所需的库

**import matplotlib.pylab as plt
import numpy as np
import tensorflow as tf
import tensorflow_hub as hub
from tensorflow.keras.applications.mobilenet_v2 import preprocess_input as mobilenet_v2_preprocess_input**

使用 MobileNetV2 作为 Imagenet 数据集上的传输学习模型

**model = tf.keras.applications.MobileNetV2(input_shape=(224,224,3),                                               include_top=True,                                               weights='imagenet')**

加载 Imagenet 标签

**def load_imagenet_labels(file_path):
  labels_file = tf.keras.utils.get_file('ImageNetLabels.txt', file_path)
  with open(labels_file) as reader:
    f = reader.read()
    labels = f.splitlines()
  return np.array(labels)
imagenet_labels = load_imagenet_labels('**[**https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt'**](https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')**)**

加载并预处理图像

**def read_image(file_name):
  image = tf.io.read_file(file_name)
  image = tf.image.decode_jpeg(image, channels=3)
  image = tf.image.convert_image_dtype(image, tf.float32)  
  image = tf.image.resize_with_pad(image, target_height=224, target_width=224)
  return image****img = {'Peacock':'Peacock.jpg'}****img_name_tensors = {name: read_image(img_path) for (name, img_path) in img.items()}**

显示原始输入图像

**plt.figure(figsize=(5, 5))
ax = plt.subplot(1, 1, 1)
ax.imshow(img_name_tensors['Peacock'])
ax.set_title("Image")
ax.axis('off')
plt.tight_layout()**

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

预测输入图像模型的前三个预测值

**def top_k_predictions(img, k=3):
  image = tf.expand_dims(img, 0)
  predictions = model(image)
  probs = tf.nn.softmax(predictions, axis=-1)
  top_probs, top_idxs = tf.math.top_k(input=probs, k=k)
  top_labels = np.array(tuple(top_idxs[0]) )
  return top_labels, top_probs[0]**#Display the image with top 3 prediction from the model
**plt.imshow(img_name_tensors['Peacock'])
plt.title(name, fontweight='bold')
plt.axis('off')
plt.show()****pred_label, pred_prob = top_k_predictions(img_name_tensors['Peacock'])
for label, prob in zip(pred_label, pred_prob):
    print(f'{imagenet_labels[label+1]}: {prob:0.1%}')**

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

创建一个黑色基线图像,作为计算特征重要性的起点

**baseline = tf.zeros(shape=(224,224,3))**

生成基线和原始输入图像之间的线性插值

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

图像插值

**m_steps=50
alphas = tf.linspace(start=0.0, stop=1.0, num=m_steps+1)** **def interpolate_images(baseline,
                       image,
                       alphas):
  alphas_x = alphas[:, tf.newaxis, tf.newaxis, tf.newaxis]
  baseline_x = tf.expand_dims(baseline, axis=0)
  input_x = tf.expand_dims(image, axis=0)
  delta = input_x - baseline_x
  images = baseline_x +  alphas_x * delta
  return images****interpolated_images = interpolate_images(
    baseline=baseline,
    image=img_name_tensors['Peacock'],
    alphas=alphas)**

可视化插值图像

**fig = plt.figure(figsize=(20, 20))****i = 0
for alpha, image in zip(alphas[0::10], interpolated_images[0::10]):
  i += 1
  plt.subplot(1, len(alphas[0::10]), i)
  plt.title(f'alpha: {alpha:.1f}')
  plt.imshow(image)
  plt.axis('off')****plt.tight_layout();**

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

插值图像

计算模型输出和插值输入之间的梯度

计算梯度**测量特征变化和模型预测变化之间的关系。**我们用 tf。GradientTape 计算插值图像与顶部预测类 Id 之间的梯度,顶部预测类 Id 指示哪些像素对模型预测的影响最大

**def compute_gradients(images, target_class_idx):
  with tf.GradientTape() as tape:
    tape.watch(images)
    logits = model(images)
    probs = tf.nn.softmax(logits, axis=-1)[:, target_class_idx]
  return tape.gradient(probs, images)****path_gradients = compute_gradients(
    images=interpolated_images,
    target_class_idx=84)**

使用黎曼梯形累积梯度

**def integral_approximation(gradients):**
  # riemann_trapezoidal
  **grads = (gradients[:-1] + gradients[1:]) / tf.constant(2.0)
  integrated_gradients = tf.math.reduce_mean(grads, axis=0)
  return integrated_gradients**

将所有步骤放入一个函数中以计算积分梯度

[**@tf**](http://twitter.com/tf)**.function
def integrated_gradients(baseline,
                         image,
                         target_class_idx,
                         m_steps=50,
                         batch_size=1):**
  # 1\. Generate alphas.
 ** alphas = tf.linspace(start=0.0, stop=1.0, num=m_steps+1)**# Initialize TensorArray outside loop to collect gradients.    
 ** gradient_batches = tf.TensorArray(tf.float32, size=m_steps+1)**

  # Iterate alphas range and batch computation for speed, memory #efficiency, and scaling to larger m_steps.
  **for alpha in tf.range(0, len(alphas), batch_size):
    from_ = alpha
    to = tf.minimum(from_ + batch_size, len(alphas))
    alpha_batch = alphas[from_:to]**# 2\. Generate interpolated inputs between baseline and input.
  **  interpolated_path_input_batch = interpolate_images(baseline=baseline,                                                       image=image,                                                       alphas=alpha_batch)**# 3\. Compute gradients between model outputs and interpolated inputs.
    **gradient_batch = compute_gradients(images=interpolated_path_input_batch,                                       target_class_idx=target_class_idx)**

    # Write batch indices and gradients to extend TensorArray.
    **gradient_batches = gradient_batches.scatter(tf.range(from_, to), gradient_batch)  **  

  # Stack path gradients together row-wise into single tensor.
 ** total_gradients = gradient_batches.stack()**# 4\. Integral approximation through averaging gradients.
 **avg_gradients = integral_approximation(gradients=total_gradients)**# 5\. Scale integrated gradients with respect to input.
  **integrated_gradients = (image - baseline) * avg_gradients****return integrated_gradients****ig_attributions = integrated_gradients(baseline=baseline,                                       image=img_name_tensors['Peacock'],                                       target_class_idx=84,                                       m_steps=283)**

可视化属性和综合梯度以解释对输入图像的预测

**def plot_img_IG(baseline,
                          image,
                          target_class_idx,
                          m_steps=50,
                          cmap=None,
                          overlay_alpha=0.4):** **attributions = integrated_gradients(baseline=baseline,                                          image=image,                                      target_class_idx=target_class_idx,                                      m_steps=m_steps)** **attribution_mask = tf.reduce_sum(tf.math.abs(attributions), axis=-1)** **fig, axs = plt.subplots(nrows=1, ncols=2, squeeze=False, figsize=   (8, 8))
  axs[0, 0].set_title('Attribution mask')
  axs[0, 0].imshow(attribution_mask, cmap=cmap)
  axs[0, 0].axis('off')** **axs[0, 1].set_title('Overlay IG on Input image ')
  axs[0, 1].imshow(attribution_mask, cmap=cmap)
  axs[0, 1].imshow(image, alpha=overlay_alpha)
  axs[0, 1].axis('off')** **plt.tight_layout()
  return fig****_ = plot_img_IG(image=img_name_tensors['Peacock'],
                          baseline=baseline,
                          target_class_idx=84,
                          m_steps=240,
                          cmap=plt.cm.inferno,
                          overlay_alpha=0.4)**

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

结论:

集成梯度(IG)通过突出特征重要性来帮助你解释深度学习模型看什么来进行预测。这是通过计算模型的预测输出到其输入要素的梯度来实现的。它不需要对原始的深度神经网络进行任何修改,可以应用于图像、文本以及结构化数据。IG 基于敏感性和实现不变性两个公理。

参考资料:

深度网络的公理属性

[## 集成梯度|张量流核心

本教程演示了如何实现综合梯度(IG),一个可解释的人工智能技术介绍了…

www.tensorflow.org](https://www.tensorflow.org/tutorials/interpretability/integrated_gradients)

http://theory y . Stanford . edu/~ ataly/Talks/Sri _ attribution _ talk _ jun _ 2017 . pdf

了解检测器 2 演示

原文:https://towardsdatascience.com/understanding-detectron2-demo-bc648ea569e5?source=collection_archive---------8-----------------------

了解脸书人工智能研究图书馆,了解最先进的神经网络

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

使用检测器 2 ( )进行实例分割

简介

Detectron2 ( 官方库 Github )是“FAIR 的下一代物体检测和分割平台”。FAIR(脸书人工智能研究所)创建了这个框架,以提供 CUDA 和 PyTorch 实现最先进的神经网络架构。它们还为对象检测、实例分割、人物关键点检测和其他用途提供预训练模型。

Detectron2 的重要但经常被忽略的特性是它的许可方案:库本身是在 Apache 2.0 许可下发布的,预训练模型是在 CC BY-SA 3.0 许可下发布的。这意味着你可以修改现有的代码,将其用于私人、科学甚至商业目的。你所需要做的就是给 FAIR 提供适当的信用。这在科学界很少见,科学界经常使用许可证来强制代码源发布和非商业使用。这非常有限,但幸运的是,对于 Detectron2 来说,情况并非如此。

然而,问题是,研究人员编写的代码通常不遵循干净的代码指南。对于 Detectron2 来说,与替代方案相比,它还不错,但是代码结构肯定很复杂,需要花很大力气才能理解。然而,要使用这个库的强大功能,确实需要了解它。在这篇文章中(希望还有后面的文章),我的目标是阐明 API、代码结构以及如何修改它并在你的项目中使用它。

下面的代码假设你已经安装好了所有的东西,并且正在运行。特别是,你需要一个 Linux 系统(Windows 可能工作,但不被官方支持),支持 CUDA 的 GPU(安装了 CUDA),PyTorch >= 1.4 和适当的 Detectron2 版本。鉴于运行这些神经网络所需的计算能力,该库目前不支持 CPU 计算,并且可能会继续支持。如果你们中的许多人在这方面遇到了问题,请在评论中告诉我,我会写另一篇关于所有适当工具的设置的文章。

基本设置

我们将通过修改的 Google Colab 演示使用 COCO 数据集类进行实例分割。目标是创建易于理解的代码框架,完美地用于未来基于 Detectron2 的项目。让我们从获取命令行参数开始:

作为技术细节,我们将编写 Python 3.5 中引入的 Python 类型注释。它们并不强制变量类型,而是旨在帮助程序员(以及 ide,它们提供了更好的注释帮助)更好地理解代码。这样也更容易知道在 Detectron2 文档和源代码中的何处寻找关于模型行为的线索。

这里发生了一些重要的事情。首先,我们导入了argparse模块,以便于参数解析。我们的演示可能需要 2 个参数:在 Detectron2 中使用的基本模型和一个或多个要处理的图像的列表。为解析和获取参数定义单独的函数是在demo.py(官方 Detectron2 演示文件)和其他建立在它之上的项目(如 Centermask2)中是如何完成的。总的来说,这也是一个很好的代码实践。名称空间类型的行为类似于 Python 字典,但是对值的访问类似于对类中属性的访问,例如args.base_model。如果没有提供参数,也没有设置 default,那么它将是适合该参数的空类型,通常是 None(或者是空列表,用于任意数量的选项,如上面的images)。

重要的部分是基础模型的默认值。它将用于告诉检测器 2 应该使用模型动物园中的哪个预训练模型(基线)。模型动物园是一组由 FAIR 预先训练的模型,可以通过库 API 轻松下载。整个名单可以在这里找到。表格包含关于不同模型的各种统计数据,这些统计数据按照精度度量进行升序排序(比如用于实例分段的方框 AP)。根据你的需要,速度或准确性可能更重要;还要注意,这是平均 AP,在特殊情况下,平均分数较低的模型(最有可能是不太敏感的模型)可能工作得更好。我根据我的个人经验选择了默认值——X101-FPN 往往会给出太多的误报,而且在实践中它比 R101-FPN 慢 1.5-2 倍。为了获得标识您感兴趣的任何型号的字符串,请按照下面显示的步骤操作。

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

选择您想要使用的模型

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

突出显示的部分是模型路径—使用它来告诉 Detectron2 您想要使用动物园中的哪个模型

第一次使用模型时,它是从模型动物园下载的,所以可能需要一点时间。

车型配置

现在我们有了基本的设置,是时候配置我们的模型了。Detectron2 实际上需要被告知使用哪个模型,在哪里找到文件等等。幸运的是,使用来自模型动物园的预先训练好的模型非常简单:

首先,我们添加了一些新的导入。它们应该被添加到文件的顶部,紧挨着前面的argparse导入。get_cfg()函数只初始化一个空的配置,稍后我们会用所需的设置填充它。类型CfgNode很少出现在类似这里的几个配置行之外,它的行为类似于argparse.Namespace,因为属性是通过点来访问的。

merge_from_file()方法需要一个字符串(本地文件的文件路径)或来自model_zoo.get_config_file()的配置文件作为参数。它将另一个配置文件加载到我们在cfg中的配置中。当模型在自定义数据集上接受训练并保存在磁盘上时,通常会使用本地文件。更多情况下,我们只想使用来自 Detectron2 model zoo 的预训练模型,并选择和加载此处由args.base_model指定的配置文件。

cfg.MODEL.WEIGHTS是训练时学习的神经网络权重。我们也可以像加载配置文件一样加载它们,只需向模型动物园提供字符串。请注意,实际上我们正在加载一个检查点文件——实际上神经网络训练永远不会“完成”,模型可能会在以后被额外训练。较早停止训练的原因主要是避免过度拟合或缺乏计算能力/时间。当权重以这种方式加载时,使用权重文件中的最后一个检查点(训练最多的神经网络)。

cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST是一个非常重要的可调超参数。它改变了我们的模型对于要被检测的对象必须具有的最小置信度。换句话说,当物体离得很远,不太明显时,等等。该阈值必须更低才能正确检测到它。然而,将其设置得太低可能会导致错误检测或多次检测到相同的对象(这可以通过非最大抑制(NMS)算法来减少,但不能完全消除)。因此,这个超参数需要根据您的具体情况进行调整。50% (0.5)是一个很好的默认值。

现在我们终于准备好初始化将完成所有实际工作的类了— DefaultPredictor。它只是使用配置中提供的神经网络来进行预测,一次在一台机器和一幅图像上进行预测。一个重要的警告是,它采用 BGR 格式的图像,而不是 RGB 格式——如果您使用 OpenCV ( cv2)加载图像,这是默认的行为,但其他库如 Pillow 可能使用 RGB 并需要改变格式。

我们终于准备好使用我们的模型了!

执行实例分割

像以前一样,将导入放在文件的开头。新代码遍历提供的图片,并对每张图片进行预测,将它们可视化,并将结果保存到文件中。

使用cv2.imread自动使用 BGR 格式,所以在这里派上用场。结果图像是一个 3D Numpy 数组,其中维度为:

  • 行数(图像高度)
  • 列数(图像宽度)
  • 表示给定像素的 BGR 值的 3 元素数组(0-255 范围内的整数)

正如你所看到的,下面几行我们使用了[:, :, ::-1]切片,这意味着“取所有行、所有列和所有第三维,但是颠倒最后一个的顺序”。这意味着为了可视化,我们将使用 RGB——没有它,颜色会很奇怪(但是你当然可以自己尝试!).

加载部分之后是真正的事情:使用predictor(img)["instances"]来实际使用神经网络并进行实例分割。预测器返回一个只包含一个键-值对的字典,即映射到实例对象的“实例”键。这是一个专门为在 Detectron2 中返回结果而设计的类。它充当关于图像和实际结果的元数据的存储。实例对象包含相当多的信息,足够写一篇单独的文章,但是现在我们可以在不弄乱其内部结构的情况下使用它。现在重要的是要记住 predictor 在 GPU 上工作,并返回仍然在 GPU 上的数据,包括许多 PyTorch 张量。如果您想在 CPU 上处理这些数据(例如,大多数库只处理 PyTorch CPU tensors 或 Numpy 数组),您必须用.to("cpu")函数显式地转换它,我们在下面几行中做了这些。

Visualizer是一个用于在图像上绘制来自 Detectron2 神经网络(不仅仅是实例分割,还有其他类型)的结果的类(对于视频你应该使用VideoVisualizer)。它的论据是:

  • img_rgb:进行预测的基础图像
  • metadata:提供来自数据集的附加数据,比如类(类别)名称映射。在 Detectron 内部,处理的是类编号,而不是名称——它们首先使用元数据从字符串转换成数字,在这里,我们给 Visualizer 这些信息以将数字映射回字符串。这样,我们将显示“汽车”而不是“2”。这里我们只传递训练期间使用的元数据(来自该模型的 COCO 数据集)
  • scale:改变输出图像的尺寸,如果您的输入图像太小或太大,这很有用

调用draw_instance_predictions告诉 Visualizer 我们已经完成了实例分割,并将结果作为参数传递。我们首先需要将它发送到 CPU,因为可视化代码不像神经网络那样在 GPU 上运行。它返回VisImage对象,这是一个带有一些附加信息(比例、宽度和高度)的图像包装器。get_image()方法从中提取图像矩阵。我们也应用与上面相同的技巧将其更改为 RGB。

剩下的就是保存文件了。事实证明,在文件名后附加“_processed”将文件保存在原文件旁边并不容易,regex 是最简单的方法。"(.*)\."提取完整的文件路径和文件名,直到扩展名之前的点,并用.group(0)捕获。用[:-1]去掉圆点,然后我们可以更改名称(和扩展名,因为 OpenCV 知道如何处理”。png”在目标文件路径中)。最后,保存处理后的图像。

使用示例

下面是完整的代码(直接链接):

让我们试试样本图像上的代码。如果图像与demo.py文件在同一个目录中,您可以从那里用python --images image.png运行它:

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

输入图像()

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

使用 Detectron2 进行实例分割后的结果

正如你所看到的,它工作得很好!可视化工具添加了边界框(检测到的对象周围的矩形)、类名(例如“盆栽”)和以%为单位的类的模型确定性度量。即使在具有挑战性的条件下(不同的比例、彼此非常接近的物体、部分障碍物),大多数物体也能被正确地检测到,并且分割掩模相当精确。这就是那些尖端神经网络的强大之处。

摘要

在本文中,我们仅仅触及了探测器 2 的表面。这是一个庞大的库,有很多功能和技术细节,有非常复杂的类模型。我希望这能帮助你开始并更好地理解这个演示。如果你对我在这里写的东西还有疑问,或者想要关于 Detectron2 的其他文章(任何特定的主题),请在评论中告诉我。

了解 DICOM

原文:https://towardsdatascience.com/understanding-dicom-bce665e62b72?source=collection_archive---------15-----------------------

如何阅读、书写和组织医学图像

DICOM 是在医院数据库中存储和传输医学图像的主要文件格式。

还有其他存储图像的文件格式。除了 DICOM,您还可以看到以 NIFTI 格式(文件后缀为。nii”)、PNG 或 JPEG 格式,甚至像 NumPy 数组这样的 Python 文件对象。

那么为什么要用 DICOM 呢?其他文件格式可能更方便,但在临床实践中,一切都使用 DICOM 格式。随着我的项目越来越先进,我经常发现自己重写代码来直接读写 DICOM 文件。此外,DICOM 文件是明确的,因为每个文件都包含一个文件头,详尽地记录了医院、患者、扫描仪和图像信息,就像每次拍摄 iPhone 照片时,智能手机信息甚至 GPS 都被编码在元数据中一样。

【DICOM 的建议读数

DICOM 文件格式记录在 DICOM 标准中,这是大多数信息学专家的必读文件。

对于一个初学者的背景,这个博客也是一个很好的介绍。

DICOM 的挑战

对于深度学习任务,最终目的通常是将图像数据加载为 NumPy 或其他文件,在这种情况下,DICOM 可能会很困难:

  1. DICOM 为每个切片保存一个文件,因此 3D 扫描可能有数百个文件
  2. DICOM 文件以唯一标识符(UID)命名。这使得很难从文件夹级别对文件进行排序(在某些情况下,文件名太长,以至于超过了 Windows 计算机上 256 个字符的最大值,从而导致保存/加载问题)
  3. 患者和医院信息嵌入在文件头中,这使得 DICOM 很难匿名

虽然混乱,但这种组织结构是 DICOM 的重要优势。稍后,我将分享一些我编写的示例 Python 代码,它们可以帮助绕过这些问题。

识别 DICOM 文件

每个 DICOM 文件都是独立的——识别文件所需的所有信息都嵌入在每个文件头中。这些信息分为 4 个层次——患者、研究、系列和实例。

  • “患者”是接受检查的人
  • “研究”是在某个日期和时间在医院进行的成像程序
  • “系列”—每个研究由多个系列组成。一个系列可以代表在一次研究中对患者进行多次物理扫描(通常用于 MRI ),也可以是虚拟的,即对患者进行一次扫描,然后以不同的方式重建数据(通常用于 CT)
  • “实例”-3D 图像的每个切片都被视为一个单独的实例。在这种情况下,“实例”与 DICOM 文件本身同义

为了说明这种层次结构,这里有一些来自癌症成像档案馆 (TCIA) 的 的公开胰腺癌数据集的文件:

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

图一。按“患者”、“研究”、“系列”和“实例”级别组织的 DICOM 文件示例。图片作者。

表 1 显示了使用 PyDicom (一个允许读取和写入 Dicom 文件的 python 包)打印出的标题。我删除了大部分不相关的信息,还创建了一些“虚假”的患者数据。

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

表 1:显示患者、系列、研究和实例 uid 和文本描述的 DICOM 标题部分的打印输出。图片作者。

从表 1 中的两个描述字段中,我们可以看到该文件来自腹部的 CT 检查,该检查使用了对比剂,这与来自胰腺癌成像数据库的该扫描相一致。这个系列“ABD”可能是唯一的,也可能有其他的。在临床 CT 研究中,通常会看到定位扫描(“拓扑结构图”)以及“矢状”和“冠状”扫描,它们在不同的平面上突出显示解剖结构。

唯一标识符:uid

除文本描述外,扫描还通过唯一的患者 ID (5553226)、研究 ID(1 . 2 . 826 . 0 . 1 . 3680043 . 2 . 1125 . 1)进行识别。38381854871216336385978062044218957),系列 UID (1.2.826.0.1。3680043 . 2 . 1 . 1)和实例号(20)。

如果您要加载该文件夹中的下一个 DICOM 文件,患者 ID、病历报告 UID 和系列图像 UID 将具有相同的值,只有实例编号不同(在本例中为“21”)。

文本描述很有帮助,但 uid 是识别扫描的关键。与描述不同,uid 对于在医院进行的每个患者、系列和研究都是独特的。

此外,uid 实际上不是随机数,它们编码了关于文件身份的信息,甚至是如何压缩的信息。UID 的完整描述在 Dicom 标准的第 6 部分中。

我见过的大多数 DICOM 病历报告都是以“三层”文件夹结构组织的,如图 1 所示,文件首先按患者 UID 排序,然后是病历报告实例 UID,然后是系列图像实例 UID,最后文件名本身就是实例编号。

结论

在这个例子中,我简要介绍了如何识别 DICOM 文件,以及如何使用这些信息对数据集进行排序。

在下一篇文章中,我将描述我写的一个 python 脚本,它可以根据文件头中的 UID 信息将一组 DICOM 文件重新组织到一个一致且易于理解的文件夹结构中。

了解 DICOMs

原文:https://towardsdatascience.com/understanding-dicoms-835cd2e57d0b?source=collection_archive---------12-----------------------

2020 年 9 月更新 : fast.ai 版本 2 于 2020 年 8 月正式发布。下面使用的fastai是指最新版本,目前为2.0.9

一个 深入实践 方法,如何使用fastai 医学成像模块查看和操作 DICOM 图像,并为机器学习做好准备。

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

使用像素选择和“ocean_r”色图生成的图像

什么是 DICOMs?

DICOM(DIImaging andCOcommunications inMedicine)是事实上的标准,它建立了允许医学图像(X 射线、MRI、CT)和相关信息在来自不同供应商、计算机和医院的成像设备之间交换的规则。DICOM 格式提供了一种合适的方法,该方法符合医疗信息交换 (HIE)标准,用于在医疗机构之间传输医疗相关数据,并符合 HL7 标准,该标准是使临床应用能够交换数据的消息标准。

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

典型的放射学工作流程[ 图像认证

DICOM 文件通常有一个 。dcm 扩展,提供了在单独的**【标签】**中存储数据的方法,例如患者信息、图像/像素数据、使用的机器以及更多信息(如下所述)。

DICOM 文件主要由打包成一个文件的标题图像像素强度数据组成。标题中的信息被组织成一系列标准化的标签。通过从这些标签中提取数据,用户可以访问有关患者人口统计、研究参数等重要信息。

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

DICOM 的一部分[ 图像来源

16 位 DICOM 图像有从 -3276832768 的值,而8 位 灰阶图像存储从 0255 的值。DICOM 图像中的数值范围非常有用,因为它们与 Hounsfield 标度 相关联,Hounsfield 标度是一种用于描述放射密度的定量标度(或一种观察不同组织密度的方式——更多解释见下文)

安装要求

为了能够完成本教程,您需要在计算机上安装这些依赖项

安装说明可以在他们的 Github 页面查看: fastai

还需要安装pydicom( Pydicom 是一个 python 包,用于解析 dicom 文件,可以很容易地将 DICOM 文件转换成 python 结构,以便于操作。

  • pip install pydicom

scikit-image(是图像处理的算法集合)

  • pip install scikit-image

kornia(是一个包含操作符的包库,这些操作符可以插入到神经网络中,以训练模型来执行图像变换、核几何、深度估计和低级图像处理,如直接在张量上操作的滤波和边缘检测

  • pip install kornia

有关如何使用 fastai 的医学成像模块的更多信息,请访问我的 [*github*](https://github.com/asvcode/MedicalImaging) 页面 或我的 医学成像教程博客 (哪个更适合查看笔记本教程:)

数据集

这里列出了 3 个 DICOM 数据集,您可以随意使用。这 3 个数据集各有不同的属性,显示了不同 DICOM 数据集中可以包含的信息的巨大差异。

让我们加载依赖项:

#Load the dependancies
from fastai.basics import *
from fastai.callback.all import *
from fastai.vision.all import *
from fastai.medical.imaging import *import pydicom
import seaborn as sns
matplotlib.rcParams['image.cmap'] = 'bone'
from matplotlib.colors import ListedColormap, LinearSegmentedColormap

了解一些关于 fast.ai 的知识是有益的,超出了本教程的范围,fast.ai 文档页面有一些优秀的教程让你快速入门。

….关于数据如何存储在 DICOM 文件中的更多信息

使用pydicom.dcmread打开 DICOM 文件,例如使用SIMM_SMALL数据集:

#get dicom files
items = get_dicom_files(pneumothorax_source, recurse=True, folders='train')#now lets read a file:
img = items[10]
dimg = dcmread(img)

您现在可以查看 DICOM 文件中包含的所有信息。对每个元素的解释超出了本教程的范围,但是 这个 站点有一些关于每个条目的优秀信息。信息由 DICOM 标签(例如:0008,0005)或 DICOM 关键字(例如:特定字符集)列出。

比如说:

dimg

现在生成:

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

从“dimg”创建的“head”信息

以下是上述标签信息的一些要点:

  • 像素数据 (7fe0 0010)(最后一项)——这是存储原始像素数据的地方。为每个图像平面编码的像素的顺序是从左到右、从上到下,即左上像素(标记为 1,1)首先被编码
  • 光度解释 (0028,0004)——又名颜色空间。在这种情况下,是单色 2,其中像素数据被表示为单个单色图像平面,其中最小样本值被显示为黑色信息
  • 每像素样本数 (0028,0002) —这应该是 1,因为该图像是单色的。例如,如果色彩空间是 RGB,这个值将是 3
  • 存储的位数 (0028 0101) —每个像素样本存储的位数
  • 像素表示 (0028 0103) —可以是无符号的(0)或有符号的(1)
  • 有损图像压缩 (0028 2110) — 00 图像没有经过有损压缩。01 图像已经过有损压缩。
  • 有损图像压缩方法(0028 2114)——说明使用的有损压缩类型(在本例中为 JPEG 有损压缩,由CS:ISO_10918_1表示)

未包含在SIMM_SMALL数据集中的重要标签:

  • 重新缩放截距 (0028,1052) —其中值 b 与存储值(SV)和输出单位之间的关系。输出单位= mSV + b* 。
  • 重标度斜率 (0028,1053)——m在重标度截距(0028,1052)指定的方程中。

RescaleInterceptRescaleSlope 用于将图像的像素值转换为对应用有意义的值。计算新值通常遵循线性公式:

  • new value*=(raw pixel value*rescale slope)+*RescaleIntercept

并且当关系不是线性时,利用 LUT (查找表)。

当我们在下面查看另一个数据集时,所有这些将变得更加清晰

上面的列表是有限的,使用另外两个数据集会告诉你每个文件可以有大量的标签。值得指出的是,在可能有ImageCommentstag的地方,可能有额外的信息(然而并不总是这样)。这个tag可能包含对建模有用的信息。

…那像素数据呢?

默认情况下,pydicom 将像素数据作为文件中的原始字节读取,通常PixelData通常不会立即有用,因为数据可能以各种不同的方式存储:

  • 像素值可以是有符号或无符号的整数,也可以是浮点数
  • 可能有多个图像帧
  • 每帧可能有多个平面(即 RGB ),像素的顺序可能不同。这只是几个例子,更多信息可在 pycidom 网站上找到

这是PixelData的样子:

dimg.PixelData[:200]

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

像素数据

由于解释PixelData的复杂性, pydicom 提供了一种简单的方法来以方便的形式获得它:pixel_array,它返回一个包含像素数据的numpy.ndarray:

dimg.pixel_array, dimg.pixel_array.shape

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

像素阵列

加载每个文件有 1 帧的 DICOMs

SIIM_SMALL数据集是一个 DICOM 数据集,其中每个 DICOM 文件都有一个包含 1 幅图像的pixel_array。在这种情况下,fastai.medical.imaging中的show功能可以方便地显示图像

source = untar_data(URLs.SIIM_SMALL)
items = get_dicom_files(source)
patient1 = dcmread(items[0])
patient1.show()

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

patient1.show()将显示上面的图像

CT medical image dataset加载一个图像怎么样,每个 DICOM 文件也包含一个帧。这张图片是 CT 扫描的切片,看着肺,心脏在中间。

csource = Path('C:/PillView/NIH/data/dicoms')
citems = get_dicom_files(csource)
patient2 = dcmread(citems[0])
patient2.show()

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

patient2.show()

然而,如果一个 DICOM 数据集每个文件有多个帧呢?

加载每个文件有多个帧的 DICOMs

超声数据集中的甲状腺分割是一个数据集,其中每个 DICOM 文件都有多个帧。

开箱后,您无法使用fastai查看来自多帧 DICOM 文件的图像,因为这将导致出现TypeError,但是我们可以自定义show功能,以便检查文件是否有多于 1 帧,如果有,您可以选择查看多少帧(默认为 1)以及打印每个文件中有多少帧。

**2020 年 9 月更新:**我向库提交了一个 PR,现在它是fastai.medical.imaging模块中show函数的一部分,所以可以开箱即用。

#updating to handle multiple frames
[@patch](http://twitter.com/patch)
[@delegates](http://twitter.com/delegates)(show_image, show_images)
def show(self:DcmDataset, frames=1, scale=True, cmap=plt.cm.bone, min_px=-1100, max_px=None, **kwargs):
    px = (self.windowed(*scale) if isinstance(scale,tuple)
          else self.hist_scaled(min_px=min_px,max_px=max_px,brks=scale) if isinstance(scale,(ndarray,Tensor))
          else self.hist_scaled(min_px=min_px,max_px=max_px) if scale
          else self.scaled_px)
    if px.ndim > 2: 
        gh=[]
        p = px.shape; print(f'{p[0]} frames per file')
        for i in range(frames): u = px[i]; gh.append(u)
        show_images(gh, cmap=cmap, **kwargs)    
    else: 
        print('1 frame per file')
        show_image(px, cmap=cmap, **kwargs)

Fastai有一个非常直观的方法,你可以用 修补 代码,因此有了上面的@patch,你可以很容易地给代码添加不同的功能

现在我们可以加载甲状腺数据集了:

tsource = Path('C:/PillView/NIH/data/thyroid')
titems = get_dicom_files(tsource)
patient3 = dcmread(titems[0])
patient3.show(10)

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

patient3.show(十)。我们指定了 10,因此它显示了这 1 个文件中总共 932 帧中的前 10 帧

了解组织密度

使用 CT 医学图像数据集,我们现在可以尝试其他有用的fastai.medical.imaging功能。如前所述 16 位 DICOM 图像的像素值范围从 -3276832768

patient2以上是来自该数据集的 DICOM 文件。

#lets convert the pixel_array into a tensor.  Fastai can #conveniently do this for us
tensor_dicom = pixels(patient2) #convert into tensorprint(f'RescaleIntercept: {patient2.RescaleIntercept:1f}\nRescaleSlope: {patient2.RescaleSlope:1f}\nMax pixel: '
      f'{tensor_dicom.max()}\nMin pixel: {tensor_dicom.min()}\nShape: {tensor_dicom.shape}')

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

RescaleIntercept、RescaleSlope、最大和最小像素值

该图像中RescaleIntercept-1024,RescaleSlope1,maxmin像素分别为19180,图像尺寸为512乘以512

绘制像素强度直方图,你可以看到大部分像素的位置

plt.hist(tensor_dicom.flatten(), color='c')

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

像素值直方图

直方图显示最小像素值为0,最大像素值为1918。直方图主要是双峰的,大多数像素位于0100像素之间以及7501100像素之间。

该图像有一个-1024RescaleIntercept和一个1RescaleSlope。这两个值允许将像素值转换成 Hounsfield 单位( HU ) 。在中测量 CT 扫描上不同组织的密度

大多数 CT 扫描的范围从-1000 HUs+1000 HUs 其中水是0 HUs ,空气是-1000 HUs 并且组织越致密 HU 值越高。金属有一个高得多的 HU 范围+2000HUHUs 所以对于医学成像来说一个范围-1000+1000HUs 是合适的

上述像素值与组织密度不正确对应。例如,大多数像素在对应于水的像素值0100之间,但是该图像主要显示充满空气的肺。亨斯菲尔德标度上的空气是-1000 s

这就是RescaleInterceptRescaleSlope的重要之处。Fastai2 提供了一种方便的方式scaled_px来相对于RescaleInterceptRescaleSlope重新缩放像素。

请记住:

重新调整像素=像素重新调整斜率+重新调整交点*

Fastai 再次提供了一个方便的方法scaled_px,将pixel_array转换为tensor,并通过考虑RescaleInterceptRescaleSlope来缩放这些值。

#convert into tensor taking RescaleIntercept and RescaleSlope into #consideration
tensor_dicom_scaled = scaled_px(patient2)
plt.hist(tensor_dicom_scaled.flatten(), color='c')

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

使用“scaled_px”的缩放像素直方图

现在让我们看看最大和最小像素值:

print(f'Max pixel: {tensor_dicom_scaled.max()}\nMin pixel: {tensor_dicom_scaled.min()}')

重新缩放后,最大像素值为894,最小值为-1024,我们现在可以根据 Hounsfield 比例正确地看到图像的哪些部分对应于身体的哪些部分。

查看直方图的顶端,值超过 300 HUs 的图像看起来像什么?

show功能具有指定maxmin值的能力

patient2.show(max_px=894, min_px=300, figsize=(5,5))

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

查看 300 像素和 894 像素之间的像素值

回想一下,我们让修补了show函数,现在它显示这个数据集有1 frame per fileHU 值高于+300通常会显示图像内的骨骼结构

那么在-250250的范围内,我们有一个峰值像素呢?

patient2.show(max_px=250, min_px=-250, figsize=(5,5))

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

像素范围在-250 像素和+250 像素之间

在这个范围内,你现在可以看到主动脉和心脏的部分(图片中间)以及肌肉和脂肪。

在-250 像素和-600 像素之间,我们注意到像素分布很低,怎么办

patient2.show(max_px=-250, min_px=-600, figsize=(5,5))

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

在这个范围内,你只能看到轮廓。直方图显示在这个范围内没有很多像素

-1000px 到-600px 之间呢?

patient2.show(max_px=-600, min_px=-1000, figsize=(5,5))

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

在这个范围内,你可以清楚地看到肺部的支气管

patient2.show(max_px=-900, min_px=-1024, figsize=(5,5))

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

在这个范围内,你现在也可以清楚地看到扫描仪的曲线。

默认情况下,show函数的max_px值为None,而min_px值为-1100

patient2.show(max_px=None, min_px=-1100, figsize=(5,5))

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

如上所述的图像缩放确实是为了人类的利益。计算机屏幕可以显示大约 256 种灰度,而人眼只能检测大约 6%的灰度变化,这意味着人眼只能检测大约 17 种不同的灰度。

DICOM 图像可能具有从-1000+1000的宽范围,并且为了使人类能够看到图像中的相关结构,使用了windowing过程。Fastai 提供各种dicom_windows,因此屏幕上只显示特定的 HU 值。更多关于窗口化的内容可以在 这里找到

….关于窗口的旁注

DICOM 图像可以包含大量的像素值,而windowing可以被视为一种处理这些值的方法,以改变图像的外观,从而突出显示特定的结构。一个窗口有两个值:

l =窗位或中心,也称为亮度

w =窗口宽度或范围,又名对比度

**举例:**从到这里

大脑物质窗口

l = 40(窗口中心)w = 80(窗口宽度)

显示的体素范围从 0 到 80

计算体素值:

  • 最低可见值=窗口中心-窗口宽度/ 2
  • 最高可见值=窗口中心+窗口宽度/ 2

(最低可见值= 40-(80/2),最高可见值= 40 + (80/2))

因此,所有大于 80 的值都是白色的,所有小于 0 的值都是黑色的。

….但是计算机真的关心窗口、缩放比例吗?

如果windowing是为了人类的利益,当数据有一个均匀分布时,计算机从训练中产生更好的结果,正如本文中提到的不像放射科医生那样看

回头看看像素分布,我们可以看到图像并没有均匀分布

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

重新缩放像素的直方图

fastai 有一个函数freqhist_hist,它根据您为n_bins设置的值将像素值的范围分成多个组,这样每个组都有大约相同数量的像素。

例如,如果您将n_bins设置为 1,像素值将被分成两个不同的像素箱。

ten_freq = tensor_dicom_scaled.freqhist_bins(n_bins=1)
fh = patient2.hist_scaled(ten_freq)
plt.hist(ten_freq.flatten(), color='c'); show_image(fh, figsize=(7,7))

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

将像素分成两个不同的箱

在这种情况下,您可以在-1000 处看到图像的两个极边,在处看到空气部分,在500 处可以清楚地看到骨骼结构,但这种分布对于机器学习模型来说仍不完全可接受。

n_bins100(这是show使用的默认数字)

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

分布在 100 个箱内的像素

那么在n_bins100000的像素显示出更均匀的分布呢

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

分布在 100000 个箱上的像素

这对训练结果有什么影响。这将是下一篇教程的主题。

参考资料:

  1. https://www . himss . org/inter operability-and-health-information-exchange
  2. https://www.hl7.org/implement/standards/
  3. https://en.wikipedia.org/wiki/Hounsfield_scale
  4. https://github.com/fastai/fastai2
  5. https://www . ka ggle . com/jhoward/don-t-see-like-a-放射科医生-fastai/data
  6. https://www . ka ggle . com/DC stang/see-like-a-a-放射科医生-系统开窗

新老德雷克歌词与人工智能

原文:https://towardsdatascience.com/understanding-differences-between-drakes-old-vs-new-songs-using-text-classification-and-lstm-6381fb568b31?source=collection_archive---------45-----------------------

使用文本分类和 LSTM 模型理解德雷克的老歌和新歌之间的差异。

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

德鲁·比默|https://unsplash.com/photos/Kxc7dgkjdNo

德雷克的歌词随着时间的推移有变化吗?如果是这样,那怎么做?

我用机器学习识别老德雷克歌词和新德雷克歌词,准确率 86%!这说明新旧德雷克歌曲歌词是有区别的。现在,让我们找出这些差异,并测试模型预测哪些歌曲是新旧德雷克歌曲。

  • ** 如果对过程和结果不感兴趣,请跳到第 5 和第 6 节。

本文共分 7 节:

1)数据探索、清理和处理

2)使用计数矢量器的 ML 建模

3)使用 Tfidf 矢量器的 ML 建模

4)对 2 和 3 中的顶级型号进行参数调整

5)分析来自模型的前 40 个特征,以发现新旧德雷克之间的差异。

6)让我们通过预测单曲和 mixtape 歌曲是旧的还是新的 Drake 来看看模型的运行情况。

7)使用 LSTM 的序列建模

1)数据预处理

我用 Genius API 收集了德雷克歌词的数据集。我只收录他官方专辑中的歌曲。因此没有单打和功能

这些数据不需要太多争论。我只是将所有文本转换成小写,并删除了字母表和数字之外的任何字符。

新德雷克的歌是 2014 年以后的歌,老德雷克的歌是 2014 年以前的。出现这种情况的两个原因是:

1)大家普遍认为德雷克最好的项目是在 2014 年之前。

  1. 2014 年是一个很好的中间点,为每个类别提供了一个相当平衡的数据集。

我还执行随机抽样,并创建一个更加平衡的数据集。我将展示使用两个数据集的结果。

*#Creating column to define new drake vs old drake*
*#1 represents new drake, 0 represents old drake*

df['drake'] = np.where(df['year'] > 2014, 1, 0)

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

在这里你可以看到德雷克专辑和歌曲的分布

sns.countplot(y=df['album'].values, order=df['album'].value_counts(ascending=**True**).index)
plt.title('# Songs Per Album')
plt.xlabel('Number Of Songs', fontsize=12)
plt.show()

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

数据准备阶段:

*#Tokenizing Dataset*
**from** **nltk.tokenize** **import** word_tokenize
df['lyrics'] = df['lyrics'].apply(word_tokenize)*#Stop word removal* 
**from** **nltk.corpus** **import** stopwords **from** **collections** **import** Counter   stop = stopwords.words('english') *#I keep words in this list as I feel they are useful in predicting drake songs* *#Furthermore, I tested accuracy with and without stop words. This setup is the best* remove_stop = ['i', 'me', 'myslef', 'we', 'you', 'we', 'she', 'her', 'they'] stop = list((Counter(stop)-Counter(remove_stop)).elements()) df['lyrics'] = df['lyrics'].apply(**lambda** x: [item **for** item **in** x **if** item **not** **in** stop])*#Lemitization*
**from** **nltk.stem** **import** WordNetLemmatizer 

**def** lemmatize_text(text):
    lemmatizer = WordNetLemmatizer()
    **return** [lemmatizer.lemmatize(w) **for** w **in** text]df['lyrics'] = df['lyrics'].apply(lemmatize_text)

接下来,我将数据集分为训练和测试,其中 80%用于训练,20%用于测试。此外,我还将使用 K-fold 验证来测试准确性,因为它对于像这样的较小数据集更准确。

*#Defining X and Y*
X = df['lyrics']
y = df['drake']*## Divide the dataset into Train and Test*
**from** **sklearn.model_selection** **import** train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size= 0.2, random_state = **None**)

2)使用计数矢量器的 ML 建模

现在是时候用计数矢量器测试模型了。计数矢量器计算文本中的词频,并用词频创建一个数组。

*# Applying Countvectorizer
# Creating the Bag of Words model*
*#I tried different n_gram ranges and unigram works best*
**from** **sklearn.feature_extraction.text** **import** CountVectorizer
cv = CountVectorizer()
cv.fit(X_train)
trainx_cv = cv.transform(X_train)
testx_cv = cv.transform(X_test)#For cross validation I create a new count vectorizer and use it in a sklearn pipeline with the model.#Example pipeline.
mnb = Pipeline([('vect', CountVectorizer()), ('mnb', MultinomialNB())])
cvs = cross_val_score(mnb, X, y)
print("Accuracy: %0.2f (+/- %0.2f)" % (cvs.mean(), cvs.std() * 2))# The pipeline will do feature extraction in each fold separately and prevent leakage.

接下来,我为文本分类创建了以下模型:

注意:我使用训练/测试/分割和交叉验证来测试模型。以下是交叉验证准确性,因为它们对于像这样的较小数据集更准确。

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

为了节省空间,我不包括这些代码。我的 Github 上有它们的代码。

3)使用 TF-IDF 矢量器的 ML 建模

接下来,我重复相同的步骤,但是这次我使用 TF-IDF 矢量器,而不是计数矢量器。TF-IDF 做的事情与 count vectorizer 相同,但是值与 count 成比例增加,与单词的频率成反比。

#Lets try with Tf-IDF
#Steps are repeated as before
#I tried different n_gram ranges and unigram works best
from sklearn.feature_extraction.text import TfidfVectorizer
tf=TfidfVectorizer()
tf.fit(X_train)
trainx_tf = tf.transform(X_train).toarray()
testx_tf = tf.transform(X_test).toarray()#For cross validation I create a new tfidf vectorizer and use it in a sklearn pipeline with the model.#Example pipeline.
mnb = Pipeline([('vect', TfidfVectorizer()), ('mnb', MultinomialNB())])
cvs = cross_val_score(mnb, X, y)
print("Accuracy: %0.2f (+/- %0.2f)" % (cvs.mean(), cvs.std() * 2))# The pipeline will do feature extraction in each fold separately and prevent leakage. 

TF-IDF 矢量器的精度为:

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

4)参数调整

好了,现在我们知道朴素贝叶斯使用计数矢量器的性能最好,线性 SVC 使用 TF-IDF 矢量器的性能最好。现在让我们尝试进一步优化模型参数:

#Multinomial NB tunning
pipeline = Pipeline([
    ('vect', CountVectorizer()),
    ('classifier', MultinomialNB()),
])parameters = {
    'vect__max_df': (0.5, 0.75, 1.0),
    'vect__min_df': (1,2,3),
    'vect__max_features': (None, 5000, 10000,15000),
    'vect__ngram_range': ((1, 1), (1, 2)),  # unigrams or bigrams
    'classifier__alpha': (1, 0.1, 0.01, 0.001, 0.0001, 0.00001),
}
Best_NB = GridSearchCV(pipeline, parameters, n_jobs=-1, verbose=1)
Best_NB.fit(X_train, y_train)
best_parameters = Best_NB.best_estimator_.get_params()

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

在相同的测试数据集上比较调优前后的准确性。

请记住,前面几节中的精度是交叉验证精度。在这里,我比较了同一测试数据集上的火车测试分割精度,以查看相同数据上的性能的直接比较。

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

#LinearSVC tunning
pipeline = Pipeline([
    ('tfidf', TfidfVectorizer()),
    ('clf', LinearSVC()),
])parameters = {
    'tfidf__max_df': (0.90, 1.0),
    'tfidf__min_df': (1,2,3,),
    'tfidf__ngram_range': ((1, 1), (1, 2)),  # unigrams or bigrams
    'clf__C': (0.1, 1, 10, 100, 1000),
    'clf__penalty': ('l1', 'l2'),
}
Best_SVC = GridSearchCV(pipeline, parameters, n_jobs=-1, verbose=1)
Best_SVC.fit(X_train, y_train)
best_parameters = Best_SVC.best_estimator_.get_params()

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

总体而言,在平衡数据集上训练的模型表现稍好。后来,当我对一些新数据进行测试时,我注意到在原始数据集上训练的模型对老歌略有过度拟合,因为它们完美地预测了老歌,但新歌的准确性不如在平衡数据集上训练的模型。在平衡数据集上训练的模型在老歌预测准确性方面略有下降,可能是因为通过移除 30%的数据集而遗漏了一些重要的老歌特征。因此,将来当德雷克推出另一张专辑时,我会用更多的数据重新测试它。

所以现在我们有超过 80%准确率的模型。这表明,有一些特征有助于成功地区分德雷克歌曲是新的还是旧的德雷克。现在让我们来看看新德雷克和老德雷克的不同之处。

5)特征重要性

为此,我将查看模型系数值。此外,我只分析顶级模型。

*#Thanks to tobique for the method. Link: https://stackoverflow.com/questions/11116697/how-to-get-most-informative-features-for-scikit-learn-classifiers*
*#This method look at the coeficient values and orders them based on most negative and positive* 
*#The most positive values link to words defining old drake song*
*#The most negative values link to words defining new drake song*

**def** show_most_informative_features(vectorizer, clf, n=20):
    feature_names = vectorizer.get_feature_names()
    coefs_with_fns = sorted(zip(clf.coef_[0], feature_names))
    top = zip(coefs_with_fns[:n], coefs_with_fns[:-(n + 1):-1])
    **for** (coef_1, fn_1), (coef_2, fn_2) **in** top:
        print ("**\t%.4f\t%-15s\t\t%.4f\t%-15s**" % (coef_1, fn_1, coef_2, fn_2))

正如《代码》中所提到的,这种方法的所有功劳都归 Tobique 所有。这种方法查看模型系数,然后根据最大负值和最大正值对它们进行排序。它还将系数映射到特性名称,这使我们很容易理解它们。在我们的例子中,最负的系数与老德雷克歌曲歌词相关,最正的系数与新德雷克歌曲歌词相关。以下是两款车型的 40 大特色:

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

**老德雷克关键词:**嗯,布特,嗯,钱,美元,汽车,嚯,锄头,婊子,女孩,爱情,她,她,你,爱过,错过,低,品牌,褪色,梦想,球,船员。

**新德雷克关键词:**耶,伊,哇,仍然,不,妈妈,传道,祈祷,上帝,工作,轮班,奉献,妻子,感觉,孤独,宝贝,宝贝,六,侧,婴儿床。

要注意的第一个变化是,老德雷克通常使用短语 uh,bout,huh,ho,而新德雷克使用 ayy,woah,nah 来代替。

老德雷克谈论更多的是爱情/关系,女人,金钱/汽车/品牌等物质化的东西,他的船员(朋友),被淡化(high)。

新德雷克已经不再谈论女性、人际关系和唯物主义,现在他在唱/说唱各种不同的东西,包括上帝/祈祷、他的母亲、多伦多(又名六人组)、工作和他的感受。

最后,德雷克还改变了他谈论女性的方式。在他以前的歌曲中,他使用了 b*tch,hoes 和 girl,而 new Drake 使用 babe,baby 和 wifey。

因此,随着德雷克作为一个人的成熟,他的歌词也成熟了。此外,他的新歌词似乎也更加多样化,针对更多的观众。

6)预测单曲/混音歌曲

现在,让我们用更多的歌曲来测试最好的模型。我从他的单曲和最新的 mixtape Dark Lane 样带中收集了更多的歌词。我收集的数据包括 14 首歌,7 首新歌和 7 首老歌。在这里,您可以看到模型准确预测了哪些歌曲。

错误的预测用红色表示。

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

在这个数据集上,朴素贝叶斯和线性 SVC 模型都获得了 12/14 的正确预测!!!

线性 SVC 认为欲望是一首古老的德雷克歌曲,而朴素贝叶斯模型认为信任问题是一首新的德雷克歌曲。你认为欲望听起来像老德雷克,信任问题听起来像新德雷克吗?

另一个值得注意的有趣的点是,两个模型都预测芝加哥自由泳是一首老歌,而它是一首新的德雷克歌曲。你觉得芝加哥自由式听起来像老德雷克吗?

该项目已成功识别德雷克的老歌风格和新歌风格的变化。

如果有你想让我测试的歌曲,请告诉我。

7)LSTM 序列模型

最后,对于那些感兴趣的人,我还使用 LSTM 进行了序列分析,看看在特定序列中使用的单词是否能区分新旧德雷克。为此,我进一步将歌词分成 4462 行单行句子,看看德雷克的新旧歌词中是否有序列。然而,LSTM 模型给出了 60%的准确率。

x = df_final['lyrics'].values y = df_final['drake'].values  x_train, x_test, y_train, y_test = \  train_test_split(x, y, test_size=0.2, random_state=**None**)tokenizer = Tokenizer(num_words=100) tokenizer.fit_on_texts(x) xtrain= tokenizer.texts_to_sequences(x_train) xtest= tokenizer.texts_to_sequences(x_test)vocab_size=len(tokenizer.word_index)+1maxlen=15 xtrain=pad_sequences(xtrain,padding='post', maxlen=maxlen) xtest=pad_sequences(xtest,padding='post', maxlen=maxlen)embedding_dim=50
model=Sequential()
model.add(layers.Embedding(input_dim=vocab_size,
      output_dim=embedding_dim,
      input_length=maxlen))
model.add(layers.LSTM(units=50,return_sequences=**True**))
model.add(layers.LSTM(units=10))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(8))
model.add(layers.Dense(1, activation="sigmoid"))
model.compile(optimizer="adam", loss="binary_crossentropy", 
     metrics=['accuracy'])
model.summary()model.fit(xtrain,y_train, epochs=20, batch_size=16, verbose=**False**)

loss, acc = model.evaluate(xtrain, y_train, verbose=**False**)
print("Training Accuracy: " + str(acc))loss, acc = model.evaluate(xtest, y_test, verbose=**False**)
print("Test Accuracy: " +  str(acc)

因此,我不认为老德雷克和新德雷克歌词之间有太多的顺序关系。这种差异归结于单词的使用,而不是它们的使用顺序。

感谢您的阅读

请随时留下任何反馈和建议。

我的下一个项目是理解新旧说唱歌曲的区别。敬请关注。

笔记本:https://github.com/HamzaKazmi/Drake_Lyrics_Analysis

这个项目的灵感来自鲁斯兰的工作,他使用 LSTM 生成德雷克歌词。https://towards data science . com/generating-drake-rap-lyrics-using-language-models-and-lstms-8725 d71b 12

使用 R 理解发行版

原文:https://towardsdatascience.com/understanding-distributions-using-r-490620c2bb08?source=collection_archive---------27-----------------------

R 中的分布基础是数据可视化的先决条件

你如何传达数据驱动的发现?

数据可视化!

有各种各样的工具可以实现这一点,本文将介绍其中的一个工具——r。

但是在我们开始使用 R 可视化数据之前,我们需要理解一些概念和术语。在这篇文章结束时,我希望你能够理解:

a)分布以及如何使用它们来汇总您的数据集

b)直方图和密度图之间的差异

c)正态分布&使用标准单位

d)如何使用分位数图检查正态分布

e)何时以及如何使用箱线图

这里我们介绍一个问题。假设有一个来自另一个星球的霸主,我们需要向他描述身高,比如说,一群学生。为此,我们首先需要收集学生的性别和身高数据。向外星霸主解释数据集的一种方式是简单地与它共享数据集。

展示这些数据的另一个更简单的方法是通过分布——所有列表中最基本的统计汇总。在描述一系列分类变量或连续变量时,会用到不同形式的分布。例如,在这种情况下,可以使用 prop.table()命令对变量“sex”进行汇总,以生成频率表,并帮助我们理解给定数据集中男性和女性两个类别的比例。

library(dslabs)
data(heights)
# make a table of category proportions
prop.table(table(heights$sex))

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

有了更多的类别,我们可以利用条形图来描述分布。但是对于数字数据,绘制分布图可能更具挑战性。这是因为单个数据点可能不是唯一的。一个人可能报告他/她的身高为 68.897 英寸,而另一个人可能报告同样的身高为 68.503 英寸。很明显这些人是分别从 175 和 174 英寸换算出来的身高。

这种条目的表示需要分布函数。这就是“累积分布函数”概念发挥作用的地方。随机变量 X 的 CDF 定义为,

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

让我们假设 X 是一个离散随机变量,范围 R = {x1,x2,x3…}并且范围 R 从下面有界(即 x1)。下图显示了生成的 CDF 的一般形式。CDF 是一个非减函数,当 x 变得足够大时,它趋近于 1。

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

来源:https://www.probabilitycourse.com/chapter3/3_2_1_cdf.php

柱状图

回到高度的数据集示例,当我们为它构建 CDF 时,我们需要评估它是否回答了重要的问题,如分布集中在哪里,哪个范围包含大多数数据,等等。虽然 CDF 可以帮助回答这类问题,但它要困难得多。

相比之下,我们现在利用直方图来更好地理解数据。只要看一眼剧情,霸王就能从中推断出重要的信息。

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

我们可以在 69 英寸左右对称分布,大部分数据位于 63 到 74 英寸之间。有了这种近似,我们总是会丢失一些信息。在这种特殊情况下,影响可以忽略不计。

密度图

我们还可以利用平滑的密度图来显示数据的分布。在平滑噪声的同时,图中的峰值有助于识别大部分值集中的区域或范围。

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

密度图优于直方图的一个主要优点是,它们更能确定分布的形状。例如,具有比如说 5 个面元的直方图不会像 15 个面元的直方图那样产生可区分的形状。然而,在使用密度图时,不存在这样的问题。

正态分布

让我们更进一步,计算这个数据集的平均值和平均偏差。我们都熟悉正态分布的含义。当您从独立来源的随机数据集合中绘制点时,它会生成一条钟形曲线(或高斯曲线)。在这个图表中,曲线的中心将给出数据集的平均值。

以下是 R 中用于生成正态分布函数的内置函数:

  1. dnorm() —用于针对给定的平均值和标准差,找出每个点的概率分布的高度。
x <- seq(-20, 20, by = .1)
y <- dnorm(x, mean = 5, sd = 0.5)
plot(x,y)

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

2。pnorm() —也称为“累积分布函数”(CDF),pnorm 用于找出正态分布随机数小于给定数值的概率。

x <- seq(-10,10,by = .2)
y <- pnorm(x, mean = 2.5, sd = 2)
plot(x,y)

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

3。qnorm() —获取概率值并给出一个数字,其累积值与概率值匹配。

x <- seq(0, 1, by = 0.02)
y <- qnorm(x, mean = 2, sd = 1)
plot(x,y)

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

4。rnorm() —该函数用于生成正态分布的随机数。

y <- rnorm(50)
hist(y, main = "Normal DIstribution")

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

正态分布方程:

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

# define x as vector of male heights

library(tidyverse)

library(dslabs)

data(heights)

index <- heights$sex=="Male"

x <- heights$height[index]# calculate the mean and standard deviation manually

average <- sum(x)/length(x)

SD <- sqrt(sum((x - average)^2)/length(x))# built-in mean and sd functions 

average <- mean(x)

SD <- sd(x)

c(average = average, SD = SD)

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

在数据集高度的情况下,这种分布以平均值为中心,大多数数据点在平均值的两个标准偏差范围内。我们观察到这种分布仅由两个参数定义——均值和标准差,因此这意味着如果数据集遵循正态分布,则可以用这两个值来概括。

在 R 中,我们利用函数标度来获得标准单位。数学上,标准单位定义如下:

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

它基本上告诉我们一个物体 x(在这种情况下是高度)偏离平均值的标准偏差的数量。

# calculate standard units
 z <- scale(x)# calculate proportion of values within 2 SD of mean
 mean(abs(z) < 2)

平均值的-2 和+2 圈内的值的比例约为 95%,这正是正态分布的预测值。

但是我们如何检查这些分布是否是正态分布的近似值呢?

分位数—分位数图

这里主要概念是,我们定义一系列比例 p,并基于定义的分位数 q,使得数据中低于 q 的值的比例是 p。因此,如果数据的分位数等于正态分布的分位数,我们可以得出数据近似正态分布的结论。

现在让我们计算样本和理论分位数,以检查数据点是否落在同一直线上。

# calculate observed and theoretical quantiles
p <- seq(0.05, 0.95, 0.05)
observed_quantiles <- quantile(x, p)
theoretical_quantiles <- qnorm(p, mean = mean(x), sd = sd(x))# make QQ-plot
plot(theoretical_quantiles, observed_quantiles)
abline(0,1)

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

事实上,这些值落在单位线上,这意味着这些分布很好地近似为正态分布。

注:

a)分位数的特例,百分位数是定义 p = 0.01,0.02,0.03…时得到的分位数。,0.99

b)四分位数是第 25、50 和 75 个百分位数,第 50 个百分位数给出了中值。

箱线图

如果数据集不符合正态分布,并且两个参数(均值和标准差)不足以汇总数据,会发生什么情况?箱线图在这种情况下会很有帮助。将数据集分为三个四分位数,箱线图表示数据集中的第一个四分位数、第三个四分位数、最小值、最大值和中值。

让我们使用相同的“身高”数据集来创建学生性别(男/女)和身高之间关系的基本箱线图。

data(heights)
boxplot(height~sex, data=heights, xlab="Sex",ylab="height")

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

从上面的图中,我们可以推断出两组的标准偏差几乎是相似的,尽管平均来看,平均比女性高。

如果你想进一步了解测试统计中其他/不常见的分布,请参考 R Stats 包中的‘分布’(下面给出的链接)

[## 分布

分布在统计包密度,累积分布函数,分位数函数和随机变量…

www.rdocumentation.org](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/Distributions)

理解动态编程

原文:https://towardsdatascience.com/understanding-dynamic-programming-75238de0db0d?source=collection_archive---------5-----------------------

流行优化技术的直观指南。

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

作者图片

动态编程,或称 DP,是一种优化技术。它被用于多个领域,尽管本文主要关注它在算法和计算机编程领域的应用。这是算法面试中经常被问到的话题。

由于 DP 不是很直观,大多数人(包括我自己!)经常发现将问题建模为动态编程模型很棘手。在这篇文章中,我们将讨论什么时候使用 DP,然后讨论它的类型,最后通过一个例子。

目录

**When is DP used?
**  - Overlapping Sub-problems
  - Optimal Substructure**The Two kinds of DP
**  - The top-down approach
  - The bottom-up approach**An example
**  - The Problem
  - The analysis
  - A recursive Solution
  - The base case
  - A dynamic programming approach
  - Improving the Algorithm

什么时候使用 DP?

要使 DP 工作,问题必须满足两个必要条件。

  • 重叠子问题
  • 最优子结构

让我们更详细地看一下这些。

重叠子问题

这个属性正是它听起来的样子:重复的子问题。但是为了使这个有意义,我们需要知道什么是子问题。

一个子问题只是手头问题的一个较小版本。在大多数情况下,这意味着传递给递归函数的参数值更小。

如果你在找一本书的某一页,你会怎么做?你可以把书翻到某一页,然后把你所在的页码和你要找的页码进行比较。

如果当前页面比要求的页面小,您将开始在当前页面和最后一页之间查找。另一方面,如果当前页码更大,您将开始在书的开头和当前页面之间搜索。

你会继续下去,直到你找到那一页。

如果你必须把它建模成一个递归函数,那会是什么样子?可能是这样的。

注: 以下代码片段以伪代码的形式编写,以提高可读性

非常简单。有一个 getpage 函数返回我们正在寻找的页面( target_page ,此处)。该函数查看从 _ 页 到 _ 页 到 _ 页 之间的中间页,并检查是否匹配。

否则,该函数会查看我们正在查看的部分的左半部分或右半部分。

但是那两个对 getpage 的递归调用代表什么呢?您会注意到,在每次递归调用时,我们都将搜索空间减少了一半。我们现在做的是解决同一个问题,就是在更小的空间里,寻找一个特定的页面。我们在解决子问题。

**分而治之,**或 DAC 算法通过子问题的原理工作。“划分”部分指的是将一个问题分成子问题。像 mergesort 和 quicksort 这样的排序算法就是很好的例子。请注意,二分搜索法并不完全是 DAC 算法,原因很简单,它没有“合并”步骤,而实际的分治算法会合并其子问题的结果,以获得最终的解决方案。

既然我们已经回答了什么是子问题的问题,我们继续讨论另一个词:“重叠”。

当这些子问题需要解决不止一次时,就称之为重叠问题。查看调用图,计算第 n 个斐波那契项的值。

递归关系是:

the relation **f(n) = f(n - 1) + f(n-2)**the base case
**f(0) = 0
f(1) = 1**

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

递归斐波纳契调用树。f(n)是第 n 个斐波那契数——作者使用 draw.io 创建的图像。

这些调用用阴影表示重叠的子问题。相比之下,二分搜索法的子问题并不重叠。

最优子结构性质

最优子结构属性稍微复杂一些:它指的是在计算总体最优解时可以直接考虑子问题的最优解的场景。

举个简单的例子?说你要找从 AB 的最短路径。设 XAB 之间的中间点,用单边将其连接到*。*

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

使用中间节点寻找最短路径—作者使用 draw.io 创建的图像。

为了解决这个问题,我们可以找到从所有中间节点( X )到 B 的最短路径,然后找到从 A 到 X 的路径加上从 X 到 B 的最短路径,这是所有 X 中最短的。

*shortest(A, B) = min(AX + shortest(X, B)) for all intermediate nodes X.*

我们在这里做的是使用一个最优的中间解( 【最短(X,B)】)并使用它(而不是考虑子问题的每个解)来找到最终的最优答案。

两种 DP

自上而下(记忆化)方法

在自上而下的方法中,我们从问题的最高层开始。在这种方法中,我们首先检查是否已经解决了当前的子问题。如果有,我们就返回那个值。如果没有,我们解决子问题。我们使用递归调用来解决我们的子问题。

由于这些调用需要解决我们以前没有见过的更小的子问题,我们继续这样做,直到我们遇到一个我们已经解决或知道答案的子问题。

自下而上(制表)的方法

在这种方法中,我们从最底层开始,然后一路向上。因为我们从“基本情况”开始,并使用我们的递归关系,我们真的不需要递归,所以,这种方法是迭代的。

这两种方法的主要区别在于自底向上计算所有的解,而自顶向下只计算那些需要的解。例如,为了找到源和目的地之间的最短路径,使用自顶向下的方法,我们只计算最短路径附近的中间点的距离,在每个阶段选择最小值。

另一方面,在自底向上的方法中,我们最终计算网格上每个点和目的地之间的最短距离,最终返回从起点到终点的最短距离。

作为比较,让我们看一个可能的自顶向下和自底向上的函数,它返回第 n 个斐波那契项。

C++中自顶向下的动态编程解决方案

自底向上的动态规划解决方案

虽然这两种方法具有相同的渐近时间复杂度,但是自顶向下实现中的递归调用可能导致堆栈溢出,由于自底向上方法的迭代性质,这不是问题。

请记住,尽管我们迭代地实现了后者,但您的逻辑仍然会使用非常基本的递归方法中的递归关系,正如我们将在本例中看到的那样。

一个例子

让我们来看一个问题,我们将使用两种动态编程方法来解决这个问题。

问题是

找出一个数组中元素的最大和,确保不包括相邻的元素。让我们假设没有元素是负的。

***example 1:
[1, 2, 3]    => 1 + 3 = 4****example 2:
[1, 1, 1, 1] => 1 + 1 = 2****example 3:
[2, 5, 2]    => 5 = 5***

分析

首先,让我们试试贪婪的方法。

因为我们的目标是最大化我们选择的元素的总和,我们可以希望通过选择最大的元素,忽略它的邻居,然后继续这样做来实现这一点。在这里,我们确保每一步都有一个最大值。但是,这只有在局部情况下才是正确的,当然,我们正在寻找全球性的解决办法。

这种方法在某些情况下是可行的。

***[1, 5, 1, 10, 1, 5, 1]***

这里,我们首先选择 10,因为它是最大的元素。然后我们忽略它的邻居,这样我们就不会违反不允许选择相邻元素的条件。

接下来,我们选择两个 5,因为它们是下一个最大的元素,然后忽略它们的邻居。我们的算法到此结束,因为没有任何元素了。我们得到的结果——10+5+5——实际上是正确的答案。

但是这并不总是有效的。举以下例子:

***[1, 1, 9, 10, 9, 1, 1]***

在每一步,如果你选择了最大的元素,忽略了它的邻居,并继续这样下去,你最终会选择 10,然后 1,然后在忽略了两个 9 之后再次选择 1,这样加起来就是 12,但正确的答案应该是 1 + 9 + 9 + 1,也就是 20。

很明显,这种方法是不正确的。让我们从一个基本的递归解决方案开始,逐步发展到一个使用动态编程的解决方案。

这就是贪婪和动态编程方法之间的区别。贪婪方法关注的是尽最大努力在每一步实现目标,而 DP 则着眼于全局。与 DP 不同,使用贪婪的方法,不能保证最终会得到最优解。贪婪算法经常陷入局部最大值,导致次优解。

递归解

稍微思考一下,你可能会发现我们有一个条件需要记住:没有相邻元素。你可能会发现:

  • 我们可以选择考虑求和中的一个元素,也可以忽略它
  • 如果我们考虑它,我们将不得不忽略它的相邻元素

为了简洁起见,让 f(a…b) 代表调用到 f 我们的数组从索引 a 到索引 b (含两者)。那个函数 f 将代表我们的递归函数,它将解决这个问题。

所以 f(0…4) 表示从索引 0 到索引 4 运行该功能。

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

我们的函数调用表示—作者使用 draw.io 创建的图像。

从一个单元格指向的两个箭头代表我们对后续函数调用的选择。因为这是一个最大化问题,我们必须从这些选项中选择最大值。

让我们回到我们的数组。

***[5, 10, 100, 10, 5]***

记住上面讨论的条件,让我们实际写下我们将要做的事情。

我们的第一个调用将在整个数组上进行,如上所示,数组长度为 5。

***f(0..4)***

对于索引为 0 的元素(这里恰好是 5),我们可以选择:

  • ***将其包含在我们的总和中:*我们当前的总和将是 5 +数组其余部分的最大总和,但不包括下一个元素(索引 1)。这样,我们的和就变成了 5 + f(2…4) 。或者概括一下, arr[0] + f(2…4)
  • ***排除它:*我们当前的和将正好等于剩余数组的最大和。这可以写成: 0 + f(1…4) 。请注意,我们的下一个调用来自索引 1,而不是上一个案例中的 2。因为我们不考虑索引 0 处的元素,所以我们可以自由地考虑索引 1 处的元素——我们不会被迫忽略它。

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

我们函数的最初几次调用——作者使用 draw.io 创建的图像。

这里的图表直观地解释了这一点。如前所述,给定级别的所有箭头代表我们的选择,我们从中选择最大的一个。

所以我们最终的答案是:

***f(0..4) = max(arr[0] + f(2..4), f(1..4))***

让我们在下一次迭代中对此进行扩展。

首先,我们将为左边的树做这件事,它是 f(2…4)。这就像我们第一次调用 f 时做的一样,记住 arr[0] + 部分还在。它将被加到 f 的值(2…4) 在返回调用树的途中。

我们的选择:

  • 考虑 arr[2] 在我们的 sum: 我们这个阶段的 sum 就变成了 arr[2] + f(4…4) 。记住,因为我们考虑的是索引 2 处的元素,所以我们必须忽略下一个元素——索引 3。
  • 忽略 arr[2] : 我们这里的 sum 与剩余数组的最大结果相同,无需忽略相邻元素。所以,那就是 f(3…4)

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

调用树的第三层——作者使用 draw.io 创建的图像。

就像以前一样,的值为 f(2…4) 将是我们两个选择中的最大值。

***f(2..4) = max(arr[2] + f(4..4), f(3..4))***

基地案例

你认为 f(4…4) 会评价到什么?按照我们的符号,它是我们对从索引 4 到索引 4 的数组进行函数调用的结果。这意味着我们在单个元素上调用函数。单个元素的最大和就是它本身。

另一件要记住的事情是:在 f(a…b) ,a 永远不应该大于 b。因为这个调用表示从索引 a 开始,一直到索引 b,所以如果大,我们就必须返回 0。没有元素就没有最大和。**

我们在这里有我们的基本情况。我们的函数 f 在单个元素上调用时,会直接返回该元素,如果不在有效范围内,则返回 0。没有进一步的递归调用。这就是为什么它被称为基础案例。

在我们的例子中,我们把称为 f(3…4)** 导致对 f 的无效调用(5…4) ,我们通过返回 0 来处理。我们稍后将概括这一点。**

****f(4..4) = arr[4]
f(5..4) = 0****

递归关系

让我们再看看我们的结果。

**first call: **f(0..4) = max(arr[0] + f(2..4), f(1..4))**second call:
**f(2..4) = max(arr[2] + f(4..4), f(3..4))**the base case:
**f(4..4) = arr[4]
f(5..4) = 0****

注意到前两个结果中的模式了吗?如果我们概括这些,我们得到:

****f(a..b) = max(arr[a] + f(a+2 .. b), f(a+1, b))****

这还不是我们关系的最简化版本。注意这里出现的 b 。实际上,回过头来看看我们在前一个块中的具体调用。

他们不会改变。没有 b + 1b + 2 。总是 b 。而我们第一次调用中的 b 的值是多少?最后一个索引。由于 b 在我们整个算法中是常数,所以可以去掉。

我们的递归关系变成:

****f(a) = max(arr[a] + f(a+2), f(a+1))****

其中 f(a) 是从索引 a 起对数组的调用。

另一件要意识到的事情是,类似于我们如何移除b,因为它总是等于数组中的最后一个索引,引用单个元素的基本情况只有在该元素是数组中的最后一个元素时才会发生。

我们基本情况的概括版本是:

*****f(n-1) = arr[n-1]** where **n** is the size of the array
**f(a) = 0** if **a** >= **n** where **n** is the size of the array***

因此,我们有我们的关系:

*****f(a) = max(arr[a] + f(a+2), f(a+1))
f(n-1) = arr[n-1]** where **n** is the size of the array
**f(a) = 0** if **a** >= **n** where **n** is the size of the array***

让我们基于这个关系实现递归方法。

这个函数可以这样调用:

*****array := [1, 5, 2, 4, ...]
return f(array, 0)*****

这会有多复杂?

如果我们根据数组的大小( n )来估算复杂度,我们会得到这样的结果:

****T(n) = T(n-2) + T(n-1) + O(1)****T(0) = O(1)****

直观地说,对大小为 n 的数组 f 的每个调用(表示为*【T(n)】*)都会导致对大小为 n-2n-1 的数组 f 的两个调用。也就是说,在每个阶段,我们对 f 的调用次数都会翻倍。

渐近时间复杂度是指数级的。用上面的推理,我们得到o(2^n).******

这是对上限的一个宽松估计,因为 n-2 树必然会在 n-1 树之前结束,所以我们做的比加倍调用略少。实际的复杂度是o(phin)—φ***是黄金比例—或者***【o(1.618n】,*** 比我们原来的估计略小,但是还是坚持 O(2^n) 。***

另一件要注意的事情是,上面的递归关系类似于第 n 个斐波那契项,因此会给出类似的复杂性。

动态规划方法

这就是动态编程发挥作用的地方。

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

注意调用图中重复出现的子问题——作者使用 draw.io 创建的图像。

如果你仔细观察,你会看到我们之前谈到的重叠的子问题。

现在到了重要的部分——将这种递归实现转换成动态编程方法。如果我们存储正在重复的函数调用的值会怎么样?

让我们维护一个数组,其中第 I 个元素是 f(i) 的值,依次是从索引 i 到结尾的数组的最大和。

****dp[i] = f(i..n) = f(i)****

因为我们已经有了 f(i)的结果,

****dp[i] = max(arr[i] + f(i + 2), f(i + 1))****

现在我们有了这个关系,我们可以走两条不同的路。要么我们走自顶向下的路线,在这里我们的函数仍然是递归的,就像上面的结果一样,要么我们去掉所有的递归调用,走自底向上的路线。

我们将关注自底向上的路线,但是让我们讨论自顶向下的方法。

-自上而下的方法

看看我们之前的结果。

****dp[i] = max(arr[i] + f(i + 2), f(i + 1))****

这就是我们实现自顶向下方法所需要的。对于任何对 f 的调用,我们将首先在我们的数组 dp 中检查我们之前是否已经进行了那个调用,如果已经进行了,我们将直接使用预先计算的值。

另一方面,如果我们正在进行的调用以前从未做过,我们必须计算整个事情。在这种情况下,一旦我们得到一个值,我们确保将它存储在我们的数组 dp 中,这样我们就不必重复整个过程。

调用树应该如下所示:

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

自顶向下动态编程方法中的调用树—作者使用 draw.io 创建的图像。

让我们实现这个算法。

存储子问题结果所需的额外空间随着输入数组的大小而线性增长。因此,除了递归堆栈所需的 O(n) 空间之外,我们还有一个 O(n) 空间用于 dp 数组,n 是输入数组的大小。

时间复杂度虽然更难计算,但与输入大小成线性关系。这是因为我们正在存储我们已经解决的子问题的答案,因此,我们有*【O(n)*个独特的子问题需要解决。这个结果也可以用我们使用自底向上方法得到的复杂度来验证。

-自下而上的方法

回想一下,在这种方法中,我们试图通过遵循迭代方法来消除所有的递归调用,其中我们从基本情况或“底部”开始,并向上进行。

让我们用访问 dp 的元素来替换对 f 的其他调用。

****dp[i] = max(arr[i] + dp[i + 2], dp[i + 1])****

基例呢,f(n-1)= arr【n-1】?这将是数组 dp 的最后一个元素。

****dp[n-1] = arr[n-1]****

就这样,我们有了自下而上 dp 方法的解决方案!

让我们实现它,就像我们对递归方法所做的那样。

这个函数可以这样调用:

****array := [1, 5, 2, 4, ...]
output(f(array))****

这里的复杂性在空间和时间上都是线性的。

为什么?

我们正在运行单个 for 循环 n-1 次,并且在每次迭代中,我们都在执行常数时间操作——线性时间复杂度。

由于数组 dp 的大小取决于输入数组的大小——当然,这是可变的——我们的空间复杂度也是线性的。

改进算法

但是我们能做得更好吗?让我们看看。

在渐近时间复杂度方面,我们做不到更好。为了找到答案,我们必须检查数组的每个元素。所以我们做不到比线性时间更好。

但是空间复杂度呢?我们需要维护一个大小为 n 的数组来解决问题吗?

仔细观察 for 循环中的代码行:

****dp[i] = max(arr[i] + dp[i + 2], dp[i + 1])****

在任何时间点,我们需要填充的*DP【I】*就是 dp 中的下两个元素——在索引 i +1i + 2 。没有理由保持我们所有的结果。我们只需要跟踪最后两次迭代。

让我们在这里使用三个变量。为了便于联系,我们将它们命名为i0i1I2**。**

****dp[i]   --> i_0
dp[i+1] --> i_1
dp[i+2] --> i_2****

**注意,在我们循环的下一次迭代中,我们的循环计数器 i 变成了 i + 1 ,因为我们在每次迭代中都在递减 iDP【I+1】将是下一个DP【I+2】DP【I】将是下一个DP【I+1】DP【I+2】——因为DP【I+1】我们就不需要它了

用我们的三个新变量替换它,循环中的代码变成:

****i_0 := max(arr[i] + i_2, i_1)
i_2 := i_1
i_1 := i_0****

我们初始化这些变量就像我们的数组实现一样。

****dp[n-1] = arr[n-1] --> i_1 = arr[n-1]
dp[n] = 2          --> i_2 = 0****

需要记住的最后一件事是:如果输入数组只有一个元素会怎样?我们的循环,从 n-20 ,一次都不会运行。

因此,我们用值 i_1 初始化 i_0 。因此,如果循环从不运行—输入数组只有一个元素—返回 i_0 将返回 i_1 的值,这是数组中唯一的元素。

最后我们返回 i_0 而不是DP【0】

****return dp[0] --> return i_0****

因此,我们最终的算法应该是这样的。

就像前面的动态编程方法一样,只需传入一个数组或对数组的引用就可以调用这个函数。

****array := [1, 5, 2, 4, ...]
return f(array)****

对于任意长度的数组,我们只需要三个变量。这样,我们算法的空间复杂度现在是 O(1) —常数。

总结我们的结果,

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

我们的实现摘要—作者使用 draw.io 创建的图像。

比较递归方法和我们的自顶向下方法,很明显我们是在用空间复杂度换取更好的时间复杂度**。当然,因为两者都是递归的,所以它们有递归调用栈所需的额外空间。**

同样,最低的两行是我们自底向上方法的结果。它们是迭代的,所以它们不需要在堆栈上递归存储函数记录。由于它们本质上与自顶向下方法的算法相同,所以它们具有相同的线性时间复杂度。

最好的情况是自底向上的方法,需要 O(1)个空间**——这意味着我们的 dp 算法使用的空间不会随着输入大小 n 而改变。**

代码

让我们用 C++实现我们的常数空间自底向上动态规划的最终算法。变量名和函数名与之前相同。

****注意:最后的空间复杂度优化步骤看起来稍微难一点,但是正如我们刚刚看到的,它极大地提高了您的空间利用率。看看你是否能在第 n 个斐波那契项的自下而上方法中找到类似的关系。

结论

动态编程通常不太直观或简单。话说回来,大多数复杂的事情都不是。但是通过练习,事情会变得更容易。网上有大量的动态编程练习题,这将帮助你更好地了解何时应用动态编程,以及如何更好地应用它。希望这篇文章是一个好的起点。

理解 ELECTRA 并训练一个 ELECTRA 语言模型

原文:https://towardsdatascience.com/understanding-electra-and-training-an-electra-language-model-3d33e3a9660d?source=collection_archive---------6-----------------------

变形金刚模型如何学习语言?伊莱克特拉有什么新消息?如何在单个 GPU 上训练自己的语言模型?让我们来了解一下!

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

https://pix abay . com/photos/woman-studying-learning-books-1852907/#

内容

  • 介绍
  • 什么是前期培训?
  • 蒙面语言模型(MLM)
  • ELECTRA 预培训方法
  • ELECTRA 方法的效率增益
  • 训练你自己的 ELECTRA 模型
  • 装置
  • 数据准备
  • 语言建模模型
  • 训练模型
  • 扔掉发生器,得到鉴别器
  • 微调预训练模型
  • 总结

介绍

训练一个用于特定自然语言处理任务的转换器模型的过程相当简单,尽管可能不太容易*。从一个随机初始化的 Transformer 模型开始,放在一起一个巨大的(我指的是巨大的*)数据集,其中包含您感兴趣的一种或多种语言的文本,在巨大的数据集上预先训练Transformer,并且使用您的特定任务数据集(可能相对较小),在您的特定任务上微调预先训练的 Transformer。

这种方法的优点是,最后一步(微调)只需要标记数据。就我个人而言,如果说我在深度学习方面的工作有什么不喜欢的地方,那就是给数据贴标签!我相信任何深度学习实践者都会同意,标记是耗时的、乏味的,并且可能容易出错。

什么是前期培训?

预训练是 Transformer model 学习 model 一种语言的过程。换句话说,转换器将学习表示文本序列的良好的、依赖于上下文的方式。这种知识可以在下游任务中重用,大大减少了所需的任务特定的、标记为的数据量,因为模型已经学习了语言特性,现在只需要微调其表示来执行特定的任务。对于预训练,在数据方面的唯一要求是大量(希望)干净的数据。不需要贴标签!我一想到如果有必要标记用于预先训练伯特的数十亿字节的文本,人们将不得不经历的折磨就不寒而栗。

这很好,但是转换器如何从大量未标记的文本中学习语言表示呢?嗯,最初的 BERT 模型依赖于两个预训练任务,***【MLM】**下一句预测 。在下一句预测中,该模型的任务是预测两个文本序列是否自然地相互跟随。据说这项任务有助于某些下游任务,如 BERT 论文中的问题回答和自然语言推理,尽管在后来的 RoBERTa 论文中显示这是不必要的,因为它只使用了掩蔽语言建模。*不管怎样,我们更感兴趣的是第一种方法, MLM ,因为这正是 厄勒克特拉 预训练法旨在改进的地方。

蒙面语言模型(MLM)

在屏蔽语言建模中,一定比例的记号*(记号是文本序列中一个单位的术语。它可以是一个单词或一个单词的一部分)*被屏蔽,并且该模型的任务是为屏蔽的记号预测原始记号。被屏蔽的标记可以用实际的屏蔽标记(例如[MASK])替换,或者用来自词汇表的另一个随机标记(模型已知的所有标记的集合)替换。

配备了这种预训练技术的 BERT 能够在许多下游 NLP 任务中粉碎先前设置的基准。然而, ELECTRA 论文的作者注意到 MLM 方法只从任何给定例子的屏蔽记号(通常为 15%)中学习。这导致用 MLM 训练语言模型所需的计算资源大幅增加。 MLM 的另一个缺点是掩码令牌只出现在预训练阶段,而不会在微调或下游使用期间出现。这种差异也导致使用 MLM 训练的模型性能略有下降。

ELECTRA 预培训方法

ELECTRA ( E 有效地 L 获得En 以 C 分类 T oken R 替换 A 精确地)是一种新的预训练方法,旨在匹配或超过 MLM 预训练模型的下游性能,同时在预训练阶段使用明显更少的计算资源。ELECTRA 中的预训练任务基于检测输入序列中被替换的标记。这个设置需要两个变压器模型,一个发生器和一个鉴别器。

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

ELECTRA 预培训设置(来源— ELECTRA 论文)

让我们一步一步地分解训练前的流程。

  1. 对于给定的输入序列,用一个**【掩码】**记号随机替换一些记号。
  2. 发生器预测所有屏蔽记号的原始记号。
  3. 鉴别器的输入序列是通过用发生器预测替换**【屏蔽】**标记构建的。
  4. 对于序列中的每个令牌,鉴别器预测它是原始的还是已经被生成器替换。

生成器模型被训练来预测屏蔽记号的原始记号,而鉴别器模型被训练来预测在给定损坏序列的情况下哪些记号已经被替换。这意味着鉴别器损耗可以在所有输入令牌上计算,因为它对每个令牌执行预测。使用 MLM ,仅在屏蔽令牌上计算模型损失。这是两种方法之间的关键区别,也是 ELECTRA 效率更高的主要原因。

这种设置类似于 GAN(生成对抗网络)的训练设置,除了生成器没有被训练来试图欺骗鉴别器(因此它本身不是对抗的*)。此外,如果生成器碰巧正确预测了屏蔽令牌的原始令牌,则该令牌被认为是原始令牌(因为该令牌没有被破坏/改变)。*

**鉴别器模型用于下游任务,而发生器在预训练后被丢弃。

ELECTRA 方法的效率增益

ELECTRA 论文强调了 ELECTRA 预培训方法的主要改进,并阐明了为什么会看到这些改进。

除非另有说明,所有分数都是 ELECTRA 论文中给出的胶水基准分数。

针对所有输入令牌和仅屏蔽令牌定义的损失

如前所述,鉴别器模型的损失是在序列中的所有记号上定义的,因为它必须预测每个记号是原始的还是替换的。为了证明这种差异的重要性,作者将 ELECTRA 模型与经过相同训练的模型 (ELECTRA 15%) 进行了比较,除了 ELECTRA 15% 只计算了屏蔽令牌的鉴频器损耗。胶水基准用于比较。

可以看出,原始的 ELECTRA 方法得到了 85.0 的分数,而 ELECTRA 15% 得到了 82.4 。(作为对比,伯特得了 82.2 分)

ELECTRA 还与另一个模型(全令牌 MLM)* 进行比较,其中屏蔽令牌被替换为生成器预测,该模型的任务是预测输入中所有令牌的原始身份。损失也是在所有输入令牌上计算的。全代币 MLM 车型得分 84.3 ,超过伯特的82.2,直追伊莱克特拉的 85.0 。*

这清楚地表明,计算所有输入令牌的损失的能力显著地提高了预训练模型的性能。

预训练和微调之间的屏蔽令牌差异

为此,将原始的伯特模型与使用 MLM 预训练目标训练的(替换 MLM)* 模型进行比较,除了屏蔽令牌被替换为来自生成器的令牌而不是实际的【屏蔽】令牌。*

取代 MLM 车型得分 82.4 ,略胜伯特的82.2。这种差异表明,预训练/微调差异对伯特的表现略有损害。

相比之下,从所有输入标记中学习似乎比解决屏蔽标记的预训练/微调不匹配具有更大的影响。

在表格中总结比较结果;

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

模型比较(来源— ELECTRA 论文)

适用于较小型号的 ELECTRA 与 BERT

BERT 相比, ELECTRA 的性能增益在较小的模型尺寸下更大。

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

ELECTRA 和 BERT 之间的模型尺寸比较(来源— ELECTRA 论文)

训练你自己的 ELECTRA 模型

ELECTRA 预训练方法的一个巨大优势是可以在单个 GPU 上训练你自己的语言模型!

下面,我将向您展示如何使用简单变形金刚库来训练您自己的语言模型。

装置

  1. 这里安装 Anaconda 或 Miniconda 包管理器。
  2. *创建新的虚拟环境并安装软件包。
    conda create -n simpletransformers python pandas tqdm
    conda activate simpletransformers
    *
  3. 如果您使用 fp16 培训,请安装 Apex。请遵循此处的说明。(从 pip 安装 Apex 给一些人带来了问题。)
  4. 安装简单变压器。
    pip install simpletransformers

数据准备

我们将在世界语上训练我们的语言模型(灵感来自拥抱脸的教程这里)。不会世界语也不用担心,我也不会!

为了预先训练一个模型,我们需要一个世界语的(最好是大的)文本语料库。我将使用莱比锡全集中的世界语文本文件。具体来说,我下载了以下数据集:

  1. 2011 年—混合(100 万句)
  2. 2012 年—新闻抓取(100 万句)
  3. 2012 年—网络(100 万句)
  4. 2016 —维基百科(30 万句)

通过使用更大的数据集,你应该能够改善结果。

下载数据集并将档案解压到一个目录data/

将所有“句子”文件移动到data/目录中。

面向 Linux/bash 用户的脚本。(别人:你怎么不上 Linux?😉)

*for d in */;
  do mv d/*sentences.txt .;
done;*

如果您打开其中一个文件,您会注意到它们有两列,第一列包含索引,第二列包含文本。我们只需要文本,所以我们将删除索引,合并所有文本,并将文本分成训练和测试文件。

现在我们准备开始训练了!

语言建模模型

在简单的转换器中,所有的语言建模任务都用LanguageModelingModel类来处理。在简单的转换器中执行任何 NLP 任务时,您都可以使用大量的配置选项,尽管您不需要设置每个选项(尽可能使用合理的默认值)。

常见配置选项及其用法列表 此处

语言造型具体选项及其用法 此处

以上要点建立了一个可以用来训练我们新模型的LanguageModelingModel

在泰坦 RTX GPU 上,单个训练周期(采用这种配置)需要不到 2 个小时。为了加快训练速度,你可以增加evaluate_during_training_steps或者干脆关掉evaluate_during_training

当用简单的转换器从零开始训练一个语言模型时,它会自动从指定的train_file为我们创建一个标记器。您可以通过在train_args中设置一个vocab_size来配置经过训练的分词器的大小。在我的例子中,我使用了 52000 个词汇。

您也可以根据需要配置发生器鉴别器模型的架构。配置选项设置在train_args中的两个字典generator_configdiscriminator_config中。ELECTRA 论文推荐使用尺寸为鉴别器的 0.25-0.5 倍的发生器型号。他们还建议减少隐藏层的数量,并保持发生器鉴别器之间的其他参数不变。考虑到这一点,我为鉴别器和一个类似的生成器选择了一个小(12 层)架构,尽管隐藏层数只有原来的 1/4。

*"generator_config": {"embedding_size": 128,"hidden_size": 256,"num_hidden_layers": 3,},"discriminator_config": {"embedding_size": 128,"hidden_size": 256,}*

你可以在这里 找到所有的架构配置选项及其默认值

训练模型

既然我们已经建立了模型,我们需要做的就是开始训练。

运行上面的脚本将开始训练我们的语言模型!

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

语言模型的训练损失

可以看看所有的训练进度信息(还有好多图表!)这里在 Wandb 上。

令人惊讶的是,ELECTRA 预训练方法让我们可以在几个小时内,在单个 GPU 上训练一个全新的语言模型!下面的图表讲述了完整的故事。

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

培训损失与时间

曲线的平坦部分是简单变压器进行评估的地方。遗憾的是,我忘了为评估设置一个更大的批量,所以评估比预期慢了很多。

扔掉发生器,得到鉴别器

当使用 ELECTRA 预训练方法时,通常在训练后丢弃发生器*,仅使用鉴别器。为此,一旦训练完成,简单的变压器将分别保存发生器鉴别器。*

但是,如果您在训练完成前终止训练,您的模型将被保存为一个包含鉴别器生成器LanguageModelingModel。为了分别提取鉴别器发生器*,您可以使用save_discriminator()save_generator()方法。*

下面的脚本演示了如何加载一个LanguageModelingModel并分别提取生成器模型。

这将把在outputs/best_model中找到的经过训练的语言模型加载到LanguageModelingModel中,然后只把鉴别器保存到discriminator_trained/discriminator_model中。

微调预训练模型

现在我们有了一个可以“理解”世界语的模型,我们可以继续在一个特定的 NLP 任务中对它进行微调。为此,我们可以使用拥抱脸提供的世界语中的词性标注数据集(训练标签)。

在我的例子中,我下载了文件并保存到data/pos-tagging

利用这一点,我们可以在词性数据集上将我们的 ELECTRA 预训练模型(即鉴别器*)训练为NERModel。(关于变形金刚的命名实体识别的更多信息,你可以在这里查看我的文章*

结果如下:

*eval_loss = 0.18994220502207695f1_score = 0.9025234264922376precision = 0.9197880191456026recall = 0.8858950119055756*

如您所见,该模型只需几个小时的训练就能在下游任务中表现出色!

总结

伙计们,这就是如何用 ELECTRA 从头开始训练一个全新的语言模型!**

我对这种可能性感到兴奋,因为 ELECTRA 应该大大降低训练自己的语言模型的计算资源障碍。希望我们将会看到越来越多的语言模型被开发出来用于世界上所有的语言!

一如既往,一个巨大的大喊拥抱脸和他们的变形金刚库,谁的工作使这一切成为可能!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值