知识图问答中期望答案类型的层次分类
思想和理论
机器如何理解用户在问什么?
TLDR
人们在寻找一个问题的答案时使用的一个重要步骤是了解哪种类型的答案最适合[1]。例如,对于“现在几点了?”我们希望听到“时间”类型的回答,以及“伊万·彼得罗夫出生在哪里?”—类型为“城市”或“国家”的答案。
基于知识图的问答系统也是如此,其目的是找到仿真问题的答案。本文介绍了一个用于确定预期答案类型(EAT)的模块,该模块不仅能够预测单个类,还能够构建类的层次结构作为预测值。该模块既作为网络界面(UI) 提供,也作为 RESTful API 提供。该功能允许最终用户获得 104 种语言的答案类型预测,查看预测的可信度并留下反馈。此外,API 允许研究人员和开发人员将 EAT 分类模块集成到他们的系统中。
通过问题理解一个人在问什么是人类用来找到相应答案的第一步。
预期答案类型分类器的 Web 用户界面(图片由作者提供)
知识图问答系统
开发问答系统有两种范式:(1)基于非结构化数据(基于 IR),其目标是在一组文本文档中找到最相关的段落,(2)基于结构化数据和知识(KBQA),这类系统将自然语言问题翻译成形式化的查询(SQL、SPARQL 等)。) [2].另外,我们应该提到知识图问答系统(KGQA),它是 KBQA 的一个子集,最近变得越来越流行。
开发问答系统的范例(图片由作者提供)
顾名思义, KGQA 系统由知识图驱动,通常使用资源描述框架(RDF) 存储,这又允许通过 SPARQL 查询访问数据。换句话说,KGQA 系统的目标是将自然语言问题转换成 SPARQL 查询,以便简化最终用户的数据访问。
向 KGQA 系统提出的问题是基于事实的。例如,当我们问“安格拉·默克尔出生在哪个城市?”我们希望看到“城市”类型的答案,在这个例子中是汉堡。在这种情况下,“城市”是预期的答案类型。根据 KGQA 系统中使用的特定知识图,这些类型通常被组织成层次分类法或本体(例如 DBpedia 本体)。考虑“安格拉·默克尔出生在哪个城市?”预期的答案类型层次结构(基于 DBpedia 本体中的类)如下所示。
问题“安格拉·默克尔出生在哪个城市?”的预期答案类型层次结构给出 DBpedia 本体(图片由作者提供)
在这个层次中,第一种类型是最具体的,而最后一种类型是最普遍的。
*为什么 KGQA 系统需要知道预期的答案类型?*非常简单——它将答案的搜索空间缩小了数倍。这可以通过一个简单的例子(参见下面的代码片段)来说明,这个例子使用了我们熟悉的安格拉·默克尔问题。
# without EAT prediction
PREFIX dbr: <[http://dbpedia.org/resource/](http://dbpedia.org/resource/)>
SELECT (COUNT(DISTINCT ?obj) as ?count)
WHERE {
dbr:Angela_Merkel ?p ?obj .
}
# ?count = 861
如代码片段所示,这个 SPARQL 查询根据 DBpedia 中的安格拉·默克尔资源计算可能的候选答案。结果是巨大的——861。让我们试着用预测的 EAT 缩小搜索空间。
# with EAT prediction
PREFIX dbr: <[http://dbpedia.org/resource/](http://dbpedia.org/resource/)>
PREFIX rdf: <[http://www.w3.org/1999/02/22-rdf-syntax-ns#](http://www.w3.org/1999/02/22-rdf-syntax-ns#)>
SELECT (COUNT(DISTINCT ?obj) as ?count)
WHERE {
dbr:Angela_Merkel ?p ?obj .
?obj rdf:type ?type .
FILTER(?type = dbo:City)
}
# ?count = 6
现在,由于我们使用类型“城市”限制了候选答案集,因此只有 6 个可能的候选答案。这确实令人印象深刻,因为从 6 个候选人中找出正确答案比从 861 个候选人中找出正确答案要容易得多。在下一节中,将介绍特定的 EAT 分类器架构。
预期答案类型分类器的体系结构
层次分类有三种方法【3】:扁平化、局部化、全局化。扁平方法忽略了层次结构,甚至可以说是扁平的,在这种情况下,我们处理的是多标签分类。局部方法为层次结构的每个级别(节点)使用几个分类器,而全局方法在一次调用中预测整个层次结构。
在本文中,我们使用局部方法(见架构图)对 EAT 进行分层分类。该解决方案基于多语言 BERT 模型[4],其中一个由 n 个神经元组成的全连接层被附加到【CLS】令牌输出中,其中 n 是在特定层次级别(节点)预测的类的数量。
分层 EAT 分类器的架构(图片由作者提供)
该图示出了 3 种模型——类别分类器、文字分类器和资源分类器。总共有三个类别:布尔、文字和资源。还有三种文字:数字、数据和字符串。对于资源,事情要复杂得多,因为有一个完整的层次分类(参见简介中的例子)。在我们的解决方案中,资源分类器预测最具体的响应类型(例如 dbo:City ),然后我们简单地使用 SPARQL query 从 DBpedia 获取层次结构的其余部分到顶层,如下所示。
给定最具体的本体类型,获取 DBpedia 本体层次结构的源代码
下面的代码用于创建基于 BERT 的 EAT 分类器。完整的源代码可以在我们的 Github 资源库中找到。
使用变压器库创建多类分类器的源代码
分类器输出的例子如下所示。
[
{
"id": "dbpedia_1",
"question": "Who are the gymnasts coached by Amanda Reddin?",
"category": "resource",
"type": ["dbo:Gymnast", "dbo:Athlete", "dbo:Person", "dbo:Agent"]
},
{
"id": "dbpedia_2",
"question": "How many superpowers does wonder woman have?",
"category": "literal",
"type": ["number"]
}
{
"id": "dbpedia_3",
"question": "When did Margaret Mead marry Gregory Bateson?",
"category": "literal",
"type": ["date"]
},
{
"id": "dbpedia_4",
"question": "Is Azerbaijan a member of European Go Federation?",
"category": "boolean",
"type": ["boolean"]
}
]
类别分类器的质量由准确度度量来测量,而其他分类器使用 NDCG 5 和 NDCG 10 度量来评估,这些度量被设计来评估排名列表。运行评估脚本后,我们获得了以下结果:准确率:98%,NDCG@5: 76%,NDCG@10: 73%。这些结果也可以在语义答案类型预测任务 2020 的公共排行榜上找到:https://smart-task.github.io/2020。
结论
这篇短文介绍了一个用于分类预期答案类型的组件,它可以用于基于知识图的问答系统。分类器支持多语言输入,在预测质量方面表现相当好。重要链接如下:
- Out 原文 PDF:http://ceur-ws.org/Vol-2980/paper349.pdf
- GitHub:https://github.com/Perevalov/iswc-classification
- web UI:https://web engineering . ins . hs-an halt . de:41009/eat-class ification
- API:【https://webengineering.ins.hs-anhalt.de:41020/docs
参考
- 郝,,等。“利用问题目标词特征通过语义关系扩展进行答案类型分类。”基于知识的系统133(2017):43–52。
- 朱拉夫斯基、丹尼尔和詹姆斯·马丁。"语音和语言处理(草案)."可从: https://web。斯坦福。edu/~茹拉夫斯基/slp3 (2021)。
- 卡洛斯·新罗和亚历克斯·弗雷塔斯。"不同应用领域的层次分类综述."数据挖掘和知识发现22.1(2011):31–72。
- 伯特:用于语言理解的深度双向转换器的预训练。 arXiv 预印本 arXiv:1810.04805 (2018)。
感谢
我要感谢我的导师 Andreas Both 教授,他给了我写博士论文的机会。我还要感谢安哈特应用科技大学的支持。最后,我要感谢 T21 的 Axel-Cyrille Ngonga Ngomo 博士教授,他同意共同指导我的博士论文。
数据科学 R 中的层次聚类和树状图
理解聚类技术、它的应用、优缺点以及用 r。
在执行数据分析的早期阶段,一个重要的方面是获得对多维数据的高级理解,并找到不同变量之间的某种模式——这就是聚类的用武之地。定义层次聚类的一个简单方法是:
根据相似的特征将一个庞大的数据集划分成较小的组,这将有助于以一种信息丰富的方式理解数据。
图片 via【unsplash.com】上的 @jeremythomasphoto
分层聚类可以分为两种类型:
**分裂(自上而下)😗*一种聚类技术,其中 N 个节点最初属于单个聚类,然后根据距离度量被分解成更小的聚类,直到在分层结构中达到期望的聚类数量。
**agglomerate(自下而上)😗*一组 N 个观察值,其中最接近的两个节点被分组在一个单独的簇中,剩下 N-1 个点,然后递归地遵循相同的模式,直到我们得到一个单独的簇,形成最终的树状图,该树状图将所有的簇解决方案包含在一个单独的树中。
这篇博文将关注凝聚层次聚类,它的应用以及 r 中的一个实际例子。 1) 当我们说我们将两个最近的节点分组在一起时,我们如何定义 close?和 2) 用什么合并方法将它们分组?
为了计算距离,可以使用几种方法(欧几里德距离是最常用的):
欧几里德距离:一种连续的直线相似性,即毕达哥拉斯定理
连续相关相似度
**二元曼哈顿距离:**计算两个向量之间的绝对距离(用于不能用直线定义距离的地方,如城市地图)
让我们从一个小数据集开始,了解 RStudio 中的树状图是如何形成的:
步骤 1:生成随机数据
我使用了正态分布来计算数据集的 x 和 y 坐标,并且为我们的理解对数据点进行了编号。
Set.seed(12)
x <- rnorm(10, sd = 1)
y <- rnorm(10, sd = 1)plot(x, y, col = "red", pch = 19, cex = 2)
text(x + 0.07, y + 0.06, labels = as.character(1:10))
(图片由作者提供)数据图
第二步:准备好我们的地块来创建一个树状图
首先,我们将 x 和 y 数据集存储为数据帧的 x 和 y 坐标。接下来,我们缩放坐标以使用平均值 0 和方差 1(标准化)来标准化我们的特征。最后,我们使用 dist()函数来计算数据帧中各行之间的距离。
dF <- data.frame(x = x, y = y)
dF <- scale(dF)
distxy <- dist(dF)
(图片由作者提供)例如,从点 3 到点 2 的距离是 2.94,而从点 6 到点 4 的距离是 0.603
第三步:调用 hclust()
这形成了基于数据集(在这种情况下为 10 个)中对象集合的距离度量(在这种情况下为“欧几里得”)的数据点的分级聚类
cluster <- hclust(distxy)
作者图片
第四步:创建一个树状图
另一种尝试的方法是使用图(as.dendrogram(cluster)) ,它会产生相同的结果。
plot(cluster, ylab = "Height", xlab="Distance", xlim=c(1,10), ylim=c(1,10))
作者图片
第五步:获得您想要的集群数量
根据手头的问题,你想要从你的系统树图中分离出来的类的数量根据你画线的位置而变化。这里,由于直线在 1 处切割高度,我们得到 4 个集群。
abline(h=1.0, col= "blue")
作者图片
应用:
从动物/植物物种的分类到确定病毒变体的相似性到营销活动的客户分类,树状图有许多用途。例如,在客户细分中,将具有相似特征和购买可能性的人分组。一旦你有了分组,你就可以用不同的营销文案对每个分组进行测试,这将有助于你更好地确定未来活动的目标。
好的和坏的:
树状图是 1) 一种简单的方法,通过聚集的方法来聚集数据,并且 2) 帮助更快地理解数据。不需要有一组预定义的聚类,我们可以在数据集中看到所有可能的关联。
然而,树状图的最大问题是 1)可扩展性。拥有包含大量观察值(即 100+或 1000+等)的大型数据集。)根本不会得出结论性的结果。它在计算上是昂贵的,因为一个不良凝聚簇具有 O(n)的时间复杂度。
分层聚类解释
实践教程
以简化的方式说明层次聚类中使用的分析和过程
照片由Unsplash 的 Alina Grubnyak 拍摄
在我们的上一篇关于高斯混合模型(GMM) 的文章中,我们探索了一种基于样本在其特征向量空间中的位置来聚类数据点的方法。在 GMM,基于系统中数据点的分布,我们能够以概率的方式分配每个样本属于每个聚类的可能性。
但是,如果我们不仅仅关注数据点在整个系统中的分布密度,而是想定量估计系统中每个样本之间的关系,并研究系统中每个数据点之间的相关程度,会怎么样呢?为了实现这个目标,在本文中,我们将探索另一种聚类方法,它属于一个完全不同的聚类分析家族,称为层次聚类。
系统树图
层次聚类的唯一概念在于构建和分析树状图。树状图是一种树状结构,它解释了系统中所有数据点之间的关系。
x 轴为数据点,y 轴为聚类距离的树状图(图片由作者提供)
然而,像常规的系谱树一样,树状图不需要从上到下以规则的间隔分支,因为其中的垂直方向(y 轴)以某种度量表示聚类之间的距离。随着您沿着一条路径继续走下去,您会不断地将集群分成越来越小的单元,直到您的粒度级别达到数据样本。在反过来的情况下,当你在向上的方向上移动时,在每一层,你都将较小的集群包含到较大的集群中,直到到达整个系统。因此,层次聚类也被称为聚类的聚类。
遍历树状图时粒度和聚类大小的影响(图片由作者提供)
聚类数
在层次聚类中,在构建树状图时,我们不需要对聚类数做任何假设。一旦构建了树状图,我们就可以水平分割这个结构。在水平切割下形成的所有结果子分支代表系统中最高级别的单个分类,它为每个数据样本定义相关的分类成员。请注意,我们之所以称之为最高级别,是因为即使在您创建了集群之后,您仍然知道后续子集群之间的关系,并且您始终可以选择增加/减少集群的粒度级别。
然而,树状图并不能正确理解放置水平切割后集群的样子。您必须用得到的聚类索引在特征向量空间中单独标记数据点,以便直观地看到聚类的效果。
切片树状图,其结果聚类标记在右侧相应的特征向量空间中(图片由作者提供)
下一个要考虑的问题是你应该在哪里放置水平切口。切片的位置可以通过视觉来决定,甚至可以根据您希望聚类之间的最小距离“y”(y 轴上的切割位置)来决定。
聚类距离为 y1、y2、y3 时的树状图切片可能性(图片由作者提供)
此外,它不是一个约束,你必须在一个恒定的距离切割树状图。基于您试图解决的问题的应用程序和领域知识,树状图可能被不一致地切割。例如,在下面的异常值检测应用程序中,为了分离两个相邻的异常值,水平切割在不同的位置发生变化。
右侧为特征向量空间投影的异常值检测应用程序的可变长度树状图切割(图片由作者提供)
树状图的解释
树状图的每一层对其数据成员之间的关系都有微妙的意义。在一个常规的关系图中,你可以解释为最上面是祖父母或第一代,下一层是父母或第二代,最后一层是孩子或第三代。同样,在树状图的每一个分支过程中,所有具有每一层成员资格的数据点都属于某一类。
然而,为了推断这个类实体,必须仔细检查公式化聚类中每个级别的几个单独的样本,并找出结果聚类中的共同特征。此外,这些推断的类在姐妹分支处不需要相似。例如,在级别 2 中,猫被聚集在大耳朵和平静的行为上,但是狗被聚集在相似的大小属性上。
用户在树状图中推断的数据类别(图片由作者提供)
树状图的构建
既然我们已经理解了什么是树状图,让我们学习如何构建它。有两种方法来构建它。构建它的一种方法是自下而上,从底层开始,不断合并各个数据点和子聚类,一直到顶层。这就是所谓的凝聚集群。
另一种方法是与自顶向下相反的过程,首先将整个系统视为一个集群,然后继续对其进行子集群,直到获得单个数据样本。这个过程被称为分裂聚类。这些方法中的每一种都有单独的算法来实现其目标。
a)聚集聚类
用于执行聚集聚类的最简单和容易理解的算法之一是单链接。在该算法中,我们首先将每个数据点视为一个子聚类。我们定义了一个度量来度量每一步中所有子聚类对之间的距离,并在每一步中保持合并最近的两个子聚类。我们重复这个过程,直到系统中只有一个集群。
b)分裂聚类
用于执行分裂聚类的算法之一是递归 k-means。顾名思义,在每个中间聚类上递归地执行 k-means 过程,直到遇到系统中的所有数据样本或一个聚类中您希望拥有的最小数量的数据样本。在该算法的每一步中,您都必须注意接下来要创建多少个集群。
用黑点表示的数据样本形成聚集/分裂聚类(图片由作者提供)
在这两种聚类方法中,当我们绘制树状图时,我们必须注意两个包含类之间的距离,并且距离尺度的变化保持在树状图的 Y 轴上。
基于聚类距离的树状图中分枝高度的变化(图片由作者提供)
最后,让我们来看看层次集群的优点和缺点。
优势
- 使用分层聚类,您可以创建形状更复杂的聚类,这在 GMM 中是不可能的,并且您不需要对聚类的最终形状做任何假设。
分层聚类形成的复杂结构形状(图片由作者提供)
- 在一次操作中,您可以首先以不同的粒度级别对数据集进行聚类,然后决定您希望在数据集中包含的聚类数量。
- 如果你使用某种类型的闵可夫斯基距离,比如说欧几里德距离来测量你的聚类之间的距离,那么从数学上来说,你的聚类过程会变得非常容易理解。
不足之处
- 当您开始分析树状图并做出决策时,您会意识到层次聚类在很大程度上是由试探法驱动的。这导致过程中的大量手动干预,因此,需要应用程序/领域特定的知识来分析结果是否有意义。
- 尽管层次聚类在数学上很容易理解,但它是一种数学上非常繁重的算法。在任何层次聚类算法中,您必须不断计算数据样本/子聚类之间的距离,这增加了所需的计算量。
- 我们将列出的最后一个缺点是最大的不利因素之一,这就是为什么分层聚类通常被 ML 工程师避开。在我们所有的可视化中,我们已经展示了数据样本非常少的树状图。如果数据样本的数量增加(很可能每次都增加),那么可视化地分析树状图并做出决策就变得不可能了。
在本文中,我们探讨了层次聚类的概念。请在评论区分享你的观点。
人工智能代理引擎的分层有限状态机
学习分层有限状态机(HFSM)的细节,它如何解决有限状态机(FSM)中发现的问题,以及它如何与行为树进行比较。
Pacman HFSMs(图片由作者提供)
介绍
实现人工智能代理(如机器人或视频游戏中的角色)正变得越来越复杂,因为它们需要复杂的行为来在动态环境中执行任务。今天,有限状态机(FSM)仍然是模拟人工智能主体行为最常用的算法。
尽管分层有限状态机(HFSM)部分解决了它的缺点,但它易于理解和实现的事实使它成为最常用的算法。
这篇文章将研究 FSM,它的优点和缺点,以及为了减轻 FSM 的一些缺点而开发的 HFSM。
我们在这里的讨论集中在从自动规划和行动的角度来组织我们的人工智能代理的行为 HFSM。
在下面的部分中,我们将研究什么是 FSM 和 HFSM,并将它们用于 Pacman 代理,并将它们与行为树进行比较。
在以前的文章中,我们已经看到了一个更新的,也可以说是更好的方法,行为树。请参见下面链接中的帖子,了解行为树。
在整篇文章中,我们将互换使用这些术语:行为、决策和代理引擎。
分层有限状态机
有限状态机
一个 FSM 或者有时只是简称为简单的状态机是 一个数学计算模型。
它(sigma)由一组有限的状态(S)、转换(gamma)、事件(E)和动作(A)组成。
过渡系统(图片由作者提供)
我们用动作来表示代理人做了什么导致了世界的变化,而事件则用于表示代理人控制之外发生的事情,它们可能是其他代理人的动作或世界动态的一部分。
在实践(实现)中,为了简单起见,我们将事件用于事件和动作。
现在让我们看一个简单的门的有限状态机的例子。我们有两个状态**{打开,关闭}** ,两个动作/事件**{打开,关闭}** 和下面的转换。
门 FSM 的过渡(图片由作者提供)
一扇门 FSM(图片由作者提供)
很简单,不是吗?现在让我们看看 FSM 的更多特性。
州
有两种状态,简单状态和复合状态。
简单状态(图片由作者提供)
它可以简单到仅仅是状态的名称,或者用标签来指定在以下时间执行什么动作:
- 进入:进入状态时
- 做:该状态下正在进行的动作
- 退出:退出一种状态时
复合状态是一个包含子状态的状态,我们将在下面关于 HFSM 的章节中讨论。
除了这两种状态之外,还有特殊的状态,或者也称为伪状态,例如初始状态和退出状态。UML(统一建模语言)定义了一些其他的状态,但是对于我们的实现,我们很少使用它们。
过渡
转换表示作为动作/事件被触发的结果,从一种状态到另一种状态的变化。
过渡具有以下属性:
- 触发转换的一组动作/事件
- 发生转换时需要满足的条件
- 转换发生时执行的动作
过渡(图片由作者提供)
只有当事件被触发并且条件被满足时,转换才会发生。
这里需要注意的一点是,当发生转换时,操作的执行顺序是:状态 1 的退出操作、状态 2 的转换操作和进入操作
现在我们来看看 FSM 的优势:
- 它在软件工程中被广泛使用
- 它易于理解和实现
然而,在这个例子中,我们只有两种状态。在实践中,复杂的系统,如人工智能代理引擎,将有更多的状态,这将导致非常复杂的有限状态机,难以维护,容易出现人为错误。下面是有限状态机的缺点:
- 每次我们想要对 FSM 进行更改,比如添加或删除状态,我们都需要重新评估所有相关的转换。这使得维护和扩展变得困难,而维护和扩展是软件工程中的两件重要事情
- 模块化和可重用性问题,很难为其他 FSM 重用一些状态,因为它们可能依赖于一些内部变量
分层有限状态机
HFSM 通过改进以下内容解决了我们在 FSM 中看到的问题:
- 模块化和可重用性
- 层级(顾名思义)结构
HFSM 引入了以下概念:
- 父状态机:一个状态所属的状态机
- 子状态机:一个状态拥有的状态机,当进入该状态时启动,当退出该状态时停止
关于为什么等级制度是必要的一点背景知识。这是因为可以说,我们在生活中所做的几乎所有事情(我们的行动)都是有层次的,它们从一些抽象的东西开始,可以被分解成我们可以执行的更具体的行动。因此,对我们来说更直观的是将一些本质上接近的状态分组到更高级别的状态中,并使用多级 FSM。转换可以发生在 FSM 的所有级别。
AI 智能体的决策组件也是如此。在*“行动者对自动化计划和行动的看法:一份立场文件”*,马利克、达纳和保罗认为,不管代理的内部架构如何,它都会分层次地查看、准备和执行其行动。
这正是 HFSM 为我们提供的解决我们在 FSM 中发现的问题的方法。
HFSM 让 FSM 对我们来说更直观,更容易理解。
现在让我们看一个例子来理解所解释的内容。
让我们想象一下,我们有一个货运机器人,它配备了一个可以从一个房间移动到另一个房间的手臂,可以携带一块积木。它能够通过抓住石块并将其放在身上来装载石块。它的工作是去积木所在的房间,装上积木,在这个过程中通过躲避障碍物回到原来的位置。FSM 将如下所示:
货运机器人 FSM(图片由作者提供)
可能会有更多的转换和状态,但是这个图对于我们的目的来说已经足够了。如果我们看一下这个 FSM,它对于这个简单的任务来说是不必要的复杂。我们可以看到有两个主要任务:
- 正在向目的地移动
- 以及,加载块
使用这种高级 FSM 将更容易理解:
高级 FSM(图片由作者提供)
HFSM 的图表是这样的:
货运机器人 HFSM(图片由作者提供)
我们可以在多个层次上看到整个系统,在最高层次上它只包含两个状态,在每个状态下它有三个子状态。
现在我们可以看到为什么它是分层的、可重用的和模块化的。子状态机是可以作为软件模块实现的独立状态机。这意味着我们可以很容易地在其他父状态机中使用它,使其模块化和可重用。
我们已经有了足够的概念,现在我们可以在我们的 Pacman 代理上实现它,看看它的表现如何。
“吃豆人”用 HFSM 作为代理引擎
为了完成它的任务,我们的 Pacman 必须以最有效的方式吃掉世界上的所有食物,在这个过程中避免鬼魂,并吃胶囊让鬼魂害怕,这样它就不需要在移动到食物位置时担心它们。
我们设计 FSM 具有以下状态:
- 寻找最近的食物
- 规划路径
- 执行计划路径
- 寻找最近的太空舱
- 还有,避开鬼魂
有限状态机版本
下图描述了 FSM 版本:
Pacman 简单有限状态机(图片由作者提供)
你可能注意到的一件事是我们在美国使用标签。在每种状态下,只要它停留在该状态,您就可以很容易地看到当它进入、退出和执行时采取了什么动作。
一个例子是在规划一条路径状态下:
- 进入此状态将执行“获取目的地”操作
- 在这种状态下,路径规划是异步执行的
- 当路径规划正在进行时离开这种状态将导致它被取消,例如当幽灵在规划时离 Pacman 太近时(因为当它在规划/思考时,幽灵仍在移动)
使用这种 FSM,Pacman 可以很好地完成它的任务,正如你在下面的视频中看到的。
吃豆人与 FSM(视频由作者提供)
HFSM 版本
如果我们使用正确的层次结构,在最高级别我们有三个状态:
- 吃食物
- 东方胶囊
- 躲避鬼魂
这是吃豆人的三个高级状态。只要它在执行任务,它就应该处于这些状态之一。
Pacman 高级 FSM(图片由作者提供)
吃食物和吃胶囊这两种状态可以分解成以下状态机。
吃食物 FSM(图片由作者提供)
食用胶囊 FSM(图片由作者提供)
它们非常相似,都有规划路径和执行规划路径的状态。区别在于它找到食物或胶囊的最近位置的状态。
另一个不同点是,在执行计划的路径进食后,进食 FSM 将重复该过程,而进食胶囊 FSM 将退出。
将这三个 FSM 组合起来,得到下面的 HFSM。
吃豆人 HFSM(图片由作者提供)
我们可以看到,与简单的 FSM 实现相比,这更容易理解,更模块化,更可重用。Pacman 的行为或多或少与 FSM 版本相同,可以在下面的视频中看到。
吃豆人和 HFSM(视频由作者提供)
结论
我们现在已经研究了什么是有限状态机,因为它容易理解,所以被广泛使用。然而,它也有一些缺点:
- 由于许多过渡必须手动设计,因此难以维护和扩展
- 它在模块化和可重用性方面也有问题
分层有限状态机通过引入父状态机和子状态机的概念解决了模块化和可重用性的问题。它还通过使用组件的层次结构帮助我们理解一个更复杂的系统。
与行为树相比
然而,即使有了 HFSM,难以维护和扩展的问题依然存在。正如我们在以前的帖子中讨论的,行为树已经改进了这一点。
的确,我们可以用 HFSM 和行为树来实现一个人工智能代理的决策/执行引擎,但是我认为使用行为树是一个更好的方法。我们可以在下面看到 HFSM 和英国电信为吃豆人制作的图表,以作比较。
Pacman HFSMs(图片由作者提供)
Pacman BTs(图片由作者提供)
我希望这篇文章能让你很好地理解 FSM,HFSM,以及它如何与 BT 相比较,以便你能为你的 AI 代理选择正确的决策或代理引擎。
Python 中的 HFSM 开源
我试图在 Python 中寻找一个好的 HFSM 实现,但找不到,所以我实现了它。我会在我的 Github 上分享,并在下一篇帖子中更新!
分层隐马尔可夫模型
思想和理论
雷·库兹韦尔认为这是大脑皮层结构的近似
在最近的一篇帖子中,著名的未来学家雷·库兹韦尔提到——在他看来——新皮层中的大脑结构在技术上类似于层级隐马尔可夫模型(HHMM)。他还在 2012 年出版的《如何创造思维》一书中详细阐述了这一观点[1]。
然而不幸的是,这篇文章和这本书都没有足够的信息来详细理解这个机器学习模型,更不用说实现它了。对于任何对意识机器的实现感兴趣的爱好人工智能科学家来说,这是一个遗憾!
因此,让我们利用这篇文章来尝试和理解层次隐马尔可夫模型。我们将对它所构建的大多数概念进行简要的、高层次的介绍,并为实际的实现做好准备(由于篇幅原因,将在另一篇文章中介绍)。
分层隐马尔可夫模型顾名思义是基于隐马尔可夫模型,而隐马尔可夫模型又是基于马尔可夫链,都是随机过程。我们将从最后一个概念开始,并向后追溯。
注意:这是一个关于 HHMM 话题的简单介绍。我们将坚持使用例子和简单的概念,并将数学推迟到后续文章中实现它的时候。
随机过程,简要介绍
如前所述,本文中我们要研究的所有东西都是随机过程。我相信您可能已经知道那是什么,但是因为它是整篇文章的基础,所以让我们再简单地看一下:
维基百科将随机过程非正式地定义为:
随机变量序列
为了理解这意味着什么,想象一个特定类型的随机事件,如天气或掷硬币,按顺序测量或观察,例如每天一次,或连续 10 次。当试图用数学方法描述它时,我们可以利用一个随机过程。我们会简化现实世界的现象,并试图计算未来状态的概率分布。
与统计模型不同的是,这将包括迭代的元素:先前的状态影响当前的观察。今天下雨了吗?那么,根据经验,很有可能明天至少会是多云天气。
另一方面,我觉得这也更类似于我们在心理上构建模型的方式:我们倾向于考虑“如果-x-发生-现在-然后-y-可能发生-以后”类型的模型,而不是考虑输入与输出变量(线性或其他)的平滑函数关系。
例如,代替思考
如果我观察到温度为 ABC,气压为 XYZ,以 UVW 的速率变化,那么在 3 小时 20 分钟内(或多或少 10 分钟),我可以预计降雨强度为 DEF。
我们可能宁愿认为
整天都是阴天,而且似乎越来越暗,越来越冷,所以很有可能很快就会下雨。
这不仅仅是因为我们没有坐下来用温度计、气压计和秒表来预测天气,还因为很难在头脑中建立和使用这种功能模型。我假设这与卡尼曼在“思考,快与慢”[2]中的观点有关,我从中得出结论,我们通常更喜欢使用快速的模式匹配思维,而不是缓慢的分析思维——但我无法为此提供任何科学证据。
无论如何,随机过程的理论比这些例子所暗示的要丰富得多。然而,为了理解 HHMMs,我们现在不需要所有的细节。
基于这些想法,现在理解马尔可夫链几乎是微不足道的——我保证!所以事不宜迟,现在让我们进入下一个话题。
马尔可夫模型和链
以俄罗斯数学家安德烈·马尔科夫命名的马尔科夫模型是一个随机模型(或过程),它具有一个特定的属性——可能有人已经猜到了,叫做马尔科夫属性——要求过程的未来状态只取决于当前状态。
让我们回到天气上,简单地用三种状态(我们每天早上检查的状态)建模:“晴天”、“多云”和“雨天”。现在,通过写下我们在一段时间内的观察结果,并计算“晴”之后出现“多云”的频率,我们可以创建一个与此类似的模型:
一个简单天气模型的状态转移概率图。请注意,从一个状态到另一个状态的所有转换仅取决于当前状态,它们的概率总和为 100%。(图文由作者提供)
这样,我们定义了一个简单的马尔可夫链。也就是说,我们包括了建模系统的简化状态及其转移概率。这也清楚地显示了马尔可夫性质:概率仅依赖于当前状态。
让我们再坚持这个概念一会儿,看看另一个例子:简化的文本生成。
我们可以将文本视为单词(和标点符号)的序列,其中某些单词的组合比其他组合更有可能出现,即一些单词比其他单词更有可能一个接一个出现。在某种程度上,这可以通过简单地读取样本文本并计算单词到单词的出现次数来用马尔可夫链来表示。(这里:赫尔曼·梅尔维尔的《莫比·迪克》的开头)
叫我 以实玛利。几年前——不要管具体是多久——我的钱包里很少或没有钱,在岸上也没有什么特别感兴趣的东西,我想我应该航行一段时间,看看世界上的水上部分。
- 调用 → me (100%)
- me→Ishmael(50%)
- me → on (50%)
- …
诸如此类。这也可以扩展到几个单词,即在两个或多个单词组合后找到下一个单词(也分别称为 2-grams 或 n-grams )。
这不仅允许我们建立一个研究文本的随机模型,我们还可以用它来生成更多类似的文本:从一个随机的起始单词开始,然后根据记录的概率选择下一个单词。
隐马尔可夫模型
为了建立前面讨论的马尔可夫链,我们需要能够直接观察状态的出现。继续上一个例子:为了创建词到词的概率模型,你需要读入大量的文本,即大量的词到词对。
但是世界上的很多事情都是无法直接观察到的,我们只能通过观察这些‘隐藏’状态有哪些可测量的影响来估计正在发生的事情。
回到天气的例子,你可以想象站在室内透过窗户向外看,试图判断天气是否冷到可以穿外套、戴帽子和手套,或者你是否可以只穿毛衣就出门。如果你没有温度计,感觉不到温度,你仍然可以通过观察它的影响来推理:例如,其他人穿什么或者外面是否有雪和霜。
对这些不可观察的过程及其影响进行建模就是隐马尔可夫模型的目的。直观地说,根据我们之前所学的,它们的核心是一个马尔可夫链,包含状态和概率转移。现在的问题在于,系统的这些状态是不可观察或测量的,这使得我们没有简单的方法来直接计算转移概率。然而,除此之外,我们还可以测量国家造成的其他影响。这些影响本身是随机的。
让我们再来看看天气以及它可能对世界产生的潜在影响:
具有影响及其概率的‘隐藏’马尔可夫链的状态转移图。请注意,一个状态可能具有一个以上的可观测效应,这导致了状态不能从观测值直接推断的问题。(图文由作者提供)
在这个例子中,我们同样有三种潜在的天气状态(有点简单,但是很好)。然而,这一次我们也展示了它们对世界的可观察到的影响(在这种情况下,人们穿着什么)以及看到它们的概率。
注意,马尔可夫链在概念上也可以建模为 hmm。如果每种状态只能确切地产生一个观察结果(100%的时间),那么我们又回到了更简单的马尔可夫链模型。
典型应用
像这样的模型通常以两种不同的方式用于预测:
- 1.估计某个观察序列的概率。例如:观察穿“毛衣”→“雨衣”→“毛衣”→“雨衣”的可能性有多大?我想这也取决于你是否在英国…
- 2.基于一系列的观察,尝试估计它产生的状态序列。例如,如果你看到“t 恤”→“毛衣”→“t 恤”,世界的状态很可能是“晴朗”、“晴朗”、“晴朗”。不错!
也就是说,如果您已经创建了模型并推断出了它的参数。如果这还没有完成,您将面临另一个挑战:
- 3.给定一个模型结构(即状态、转换和效应)和一系列观察序列,尝试找出所有“箭头”最可能的概率。也就是说,训练模型。
最后但同样重要的是,一个很少提及的用途是数据创建:
- 4.使用现有的训练模型和起始状态(也基于模型参数选择)来迭代模型的状态。根据模型中记录的概率选择下一个状态(和输出)。
不言而喻,所有这些问题都已经解决了很多年,公式也是众所周知的。我们将在实现它们时更详细地讨论它们。
分层隐马尔可夫模型
到目前为止,我们只看了一些天气状态的非常简单的 HMM。这很简单,但是很清楚地说明了模型的要点。让我们对 Fine 等人在 1998 年的论文[3]中描述的分层隐马尔可夫模型做同样的事情。
正如我们已经看到的,hmm 可以理解为状态的有向图,其中每个状态都是可到达的。从某种意义上来说,分级版本更受限制,同时也更复杂。(实际上,[3]的作者还提到,HHMM 可以表示为全连接的 HMM,其缺点是失去了层次结构的语义,正如我们将看到的)。
那么什么是层次隐马尔可夫模型呢?简单地说,顾名思义,分层 HMM 为隐藏状态添加了一个树状层次结构。
它从代表顶层(或第一层)的根节点开始,该层具有到第二层状态的每个状态的可能状态转换。第二层本身的结构类似于马尔可夫链,即状态之间有转换。然而,该层中的每个状态都可以是另一个 HHMM 的根节点。
这样循环下去,直到我们到达叶节点,称为生产状态,其行为类似于“正常”HMM 状态,因为它们输出单个观察或符号。它们也是相互关联的。
每一层都有最后一个特殊的“结束”状态,当到达该状态时,会自动(100%可能)跳回到上一层的父状态。
就顺序而言,状态转换首先深入。然后,当信号通过较低级别的结束状态返回时,同一级别的其他状态被激活。使用这种结构,非生产状态没有直接分配的输出。然而,它们的输出隐含地由较低级别的生产状态的输出序列来描述。
为了说明这一点,可以把生产节点想象成文本中的字母或字符。然后,更高级别的节点可以逐步表示音节、单词、单词组合、句子等等。这就是这里的层次结构的本质:它用于表示简单概念的抽象。
前面提到的论文使用该方法在英语文本语料库上训练该模型,该模型很好地展示了该思想。让我们用这个例子来描绘这样一个网络的一部分可能是什么样子:
简化分层隐马尔可夫模型中的状态转移和示例路径。这里,我们展示了从字母或字母组合到短词再到句子的各个部分的文本生成。概率不包括在内,以提高易读性。(图文由作者提供)
在上面的例子中,我们跟踪了一个生产路径,从根节点通过第二层向下到生产状态,生产状态将输出字母(或字母组合)。
每个向下的灰色箭头都是一个“垂直”过渡,如果可能的话,将在考虑任何黑色“水平”箭头之前进行。这意味着一个状态的灰色箭头和黑色箭头的概率总和必须分别为 1。
用红色标记的部分路径将产生单词“The ”,作为第二级激活状态的隐含输出。然后,这种产生可以继续通过第二层的其它状态,直到它最终跳回到根节点,并且完成字符的发射。
与人类大脑的关系
根据 Kurzweil 的说法,这些模式匹配器和/或输出生产者的层次结构是我们如何在概念层面上描绘人类新大脑皮层的结构[1]。这是我们大脑中承担复杂任务的部分,如语言理解和生产。
在文本生成模型中,层级越高,输出就越复杂。最低级别的生产状态将生成一些单个字母,而在根节点级别,我们可以期望看到完整的句子。
Kurzweil 提到在新大脑皮层中也有类似的层级。他们处理语言处理,从单词的一部分到最高层的幽默和讽刺的概念。这种相似性是他建议 HHMMs 作为有意识思维建模的可能基础的原因。
使用
一般来说,HHMMs 和 hmm 都用于类似的领域:顺序数据不能直接记录,但其他数据可以直接记录的地方。那么,这个模型的目的就是从记录的任何其他效应中推断出实际的数据。
一个非常著名的例子是语音识别和语音合成(实际上从 20 世纪 70 年代开始)。在那里,使用傅立叶变换分析短的声音序列,并将其“翻译”成最可能的音素,并在更高的层次上翻译成已知词汇表中的单词。
从那时起,这些算法不断改进,现在我们可以在移动设备上使用 Siri 或 Alexa 等语音助手。
对于更多的灵感,维基百科有一个更长的应用列表。
限制
***拓扑。*正如 Kurzweil 在文章中提到的,到目前为止,我们希望提前知道网络的拓扑结构是什么样的(即有多少内部状态和级别,以及状态是如何连接的)。有了这些信息,我们就可以使用上述算法来训练模型,并从中提取有用的信息。
但是这种网络拓扑的前期知识很少存在。例如,我们必须如何构建网络才能正确地生成一定长度的英语句子?即使没有分配概率,这也是一项极其困难的任务。
文献[3]作者的实验表明,层级结构的微小变化会对网络的语言分析能力的质量产生相当大的影响。
一种用于语音识别的解决方案是,开始创建只能识别单个单词的小部分网络,并将这些网络聚合成一个更大的模型。
Kurzweil 还提到了一种基于遗传算法的改进网络拓扑的方法,我认为这只是许多可应用的优化技术之一。
***状态空间。*在我们看到的例子中,状态空间以及可能输出的空间都是离散且有限的。这意味着我们在前面提到的网络拓扑中有一组状态和观察值。
我不确定 HHMMs 是否可以推广到更广的范围,但是 hmm 在一定程度上显然可以。例如,可以假设一个连续的状态空间(想想:实数,例如在多维向量中指定一个状态)。
这似乎与 Kurzweils 的模拟大脑部分的想法无关,所以我们不会详细讨论这部分。
***转移和输出概率。*这些模型的另一个限制来自马尔可夫属性本身:由于概率是静态的,并且只取决于当前状态,因此没有办法将依赖于时间的上下文信息包括到模型输出中。
这对于使用 HHMM 或 HMM 的文本生成来说将是特别有趣的,因为它将使我们能够学会对外部刺激做出“响应”。
例如,想象一下,您有一个巨大的 HHMM,可以生成关于各种主题的复杂文本。如果你想在一个对话界面中使用这个模型(比如说一个聊天机器人),能够学习根据用户的要求用什么来响应将会很棒。有趣的是,这已经在所谓的输入/输出 hmm[4]中进行了探索。
后续步骤
如前所述,我将根据[3]中的研究,用一篇更具技术性的文章来阐述 HHMM 的实现,从而继续这个主题。我的希望是复制(在一篇教程风格的文章中)他们在自然语言分析上的一些结果。在最好的情况下,我们可以看到如何将样本文本映射到一些好的层次级相关抽象上。
与此相关的所有完成的源文件、笔记本和代码也可以在 Github 上获得。请留下反馈并提出改进建议。
如果你想支持这篇和类似精彩文章的创作,你可以注册一个中级会员和/或关注我的账户。
[1] R. Kurzweil,“如何创造思维:揭示人类思维的秘密”(2012),企鹅出版社
[2] D .卡尼曼,《思考,快与慢》(2011),麦克米伦出版社
[3] S. Fine、Y. Singer 和 N. Tishby,“分层隐马尔可夫模型:分析和应用” (1998),机器学习 32.1(第 41-62 页)
[4] Y. Bengio 和 P. Frasconi,“输入输出 HMM 架构” (1995),神经信息处理系统的进展(第 427-434 页)
分层线性建模:逐步指南
入门
利用 R 进行混合模型分析
在大多数情况下,数据往往是聚集的。分级线性建模(HLM)使您能够探索和理解您的数据,并降低 I 型错误率。本教程用 R 来演示 HLM 在社会科学研究中的基本步骤。
鸣谢:【https://pixabay.com/users/geralt-9301/】T2 来自 Pixabay
在开始分析之前,让我们先简要谈谈什么是 HLM。
HLM(又名多层建模)分析以有组织的模式聚集的数据,如各州的大学、科技公司的非白人男性和医院的诊所。HLM 是一个普通的最小二乘法(OLS ),要求满足所有假设(查看我的教程OLS 假设和数据筛选),除了误差假设的独立性。这一假设很可能被违背,因为 HLM 允许跨集群的数据相互关联。
HLM 的预测因子可以分为随机效应和固定效应。随机效应指的是不是研究的主要焦点,但可能影响因变量,因此需要包括在模型中的变量。另一方面,固定效应是这项研究的关键预测因素。例如,一位心理学家想要预测童年的不利创伤对一个人成年后发展为边缘型人格障碍(BPD)的趋势的影响。参与者来自集体主义和个人主义文化,这两种文化可能对父母的行为有不同的定义。个人主义文化中的人,如美国或英国的人,可能会认为父母打屁股是虐待,而集体主义者,如亚洲人和非洲人,可能会认为打屁股是加强孩子纪律的一种方式。因此,来自不同文化的参与者可能在童年时期受到来自其父母的相同行为的不同影响,并且可能在不同的水平上发展出 BPD 症状。根据这个例子,童年创伤被视为基于人格文学和研究者作为心理学家的兴趣的固定效应。文化可以被视为随机效应,因为这一变量可能会影响边缘型人格的发展,但不是研究的主要焦点。值得注意的是,随机效应应该是分类的,而固定效应可以是虚拟变量(具有两个水平的分类变量)或连续变量。
你们中的一些人可能会想,*为什么我们不使用一个单一水平的回归模型来控制潜在的随机效应(例如,根据上述例子的文化)?*这样做可能会引入错误的标准误差估计,因为残差(即同一组中的观察值)往往是相关的。例如,来自相同文化的人可能以相同的方式看待一种行为。单水平模型的误差项代表跨水平的聚类数据误差,限制了我们在控制参与者嵌套的文化后,了解关键预测因子(如童年创伤)对一个人发展 BPD 倾向的影响。
还在迷茫?让我们看看下面的等式:
易**=β0+β1xi+ei一元回归模型— (1)**
yij=β0+uj+eij一个方差分量模型——(2)****
yij=β0+β1xij+uj+eij混合模型(带随机截距)——(3)****
i 是观察的数量(例如,参与者#1、#2、#3…).
j 是每个观察所属的文化的范畴(即 j = 1 是集体主义,j = 0 是个人主义)。
β1 是不良童年创伤
y 是 BPD 倾向。
u 是无法用文化解释的 y 方差,控制其他预测因子。
e 是 y 的方差,它不能通过控制其他预测因子的童年创伤来解释。
根据等式 1,误差项( ei )表示未被关键独立变量(例如,童年创伤)解释的结果方差。等式 2 显示了两个误差项,包括随机效应的误差项( uj )(即文化)和嵌套在随机效应中的固定效应的误差项( eij )(不同文化中的童年创伤评分)。等式 3 表示整合了等式 1 和等式 2 的混合模型,相对于等式 1 中的单级回归模型,说明了更精确的误差估计。
现在你已经有了一些 HLM 的基础,让我们在分析之前看看你需要什么。
- ****长格式数据:数据通常以宽格式组织(即每列代表一个变量,每行描述一个观察)。您需要将数据转换为长格式(例如,案例的数据分布在各行中。一列描述变量类型,另一列包含这些变量的值)。查看这篇教程,了解如何将数据从宽格式转换为长格式。
- 用于线性和非线性模型测试的 R 包 : nlme
*install.packages("nlme")
library(nlme)*
个案研究
本教程使用了一个虚构的数据集。我们将研究一个人的自恋是否能预测他们对亲密关系的满意度,假设自恋症状(例如,自恋、说谎、缺乏同理心)会随着不同生活事件发生的时间而变化。因此,固定效应是自恋型人格障碍的症状(NPD)。结果变量是一个人的亲密关系满意度(满意度)。随机效应是时间,三个等级编码为 1(婚前),2(婚后 1 年),3(婚后 5 年)。
分析前步骤
第一步:导入数据
*#Set working directory
setwd("insert your file location:")
#import data
library(foreign)
data<-read.spss("HLM.sav(your data name)," use.value.label = TRUE, to.data.frame = TRUE)*
第二步:数据清理
本教程假设您的数据已经被清理。如果您想了解更多关于清理数据的信息,请查看我的数据准备教程。对于我当前的数据集,所有的假设,除了误差的独立性,都符合 HLM 要求。
HLM 分析步骤
步骤 1 :仅拦截型。
仅截距模型是 HLM 的最简单形式,建议作为添加任何其他预测项之前的第一步。这种类型的模型测试使我们能够了解在不考虑其他预测因素的情况下,结果变量得分(即本教程中的关系满意度)是否明显不同于零(即参与者表示了特定的关系满意度)。对于 OLS 模型,截距也称为常数,在仅截距模型中,截距是结果变量的平均值,如下式所示:
作者图片
我们将使用 gls 函数(即广义最小二乘法)来拟合线性模型。gls 函数使误差相互关联并具有不同种类的方差,这很可能是聚类数据的情况。我将我的“仅拦截”模型标识为“模型 1”
*model1=gls(Satisfaction~1, data = data, method = "ML," na.action = "na.omit")
summary(model1)*
结果如下:
*Generalized least squares fit by maximum likelihood
Model: Satisfaction ~ 1
Data: data
AIC BIC logLik
6543.89 6555.678 -3269.945Coefficients:
Value Std.Error t-value p-value
(Intercept) 5.087982 0.01582679 321.479 0Standardized residuals:
Min Q1 Med Q3 Max
-4.9894040 -0.5142181 0.0960345 0.7644064 1.1131222Residual standard error: 0.8193328
Degrees of freedom: 2681 total; 2680 residual*
p 值显著,表明参与者的关系满意度与零显著不同。
****第二步:随机截距模型。
这一步添加了我的随机效应(即时间),以查看相对于之前的仅截距模型(模型 1),预测值是否增加了我的因变量中解释的显著方差。
从统计学的角度来说,如果你还记得前面的等式,单截距模型整体回归的截距仍然是 β 0。但对于每组随机效应(即婚后的每个时间点),截距为 β 0+ uj (当 uj 表示因变量的误差未被时间解释时)。
为了测试随机截距模型,我将使用 lme 函数作为上述 gls 函数之外的替代方法。像 gls 一样,lme 函数用于测试线性混合效应模型,允许嵌套随机效应和组内误差之间的相关性。lme 和 gls 都支持最大似然应用。
在将时间作为随机效应包括在内之前,请确保变量是明确的:
*is.factor(data$Time)
[1] FALSE*
输出显示“false”,因此我需要将时间转换为分类变量。
*data$Time = as.factor(data$Time)#Check again whether Time is categoricalis.factor(data$Time)[1] TRUE*
模拟随机截距;
*model2 = lme(Satisfaction~1, data = data, method = “ML”, na.action = “na.omit”, random = ~1|Time)summary(model2)*
结果是:
*Linear mixed-effects model fit by maximum likelihood
Data: data
AIC BIC logLik
6533.549 6551.231 -3263.775Random effects:
Formula: ~1 | Time
(Intercept) Residual
StdDev: 0.06596515 0.8165719Fixed effects: Satisfaction ~ 1
Value Std.Error DF t-value p-value
(Intercept) 5.092783 0.04124424 2678 123.4786 0Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-5.0747125 -0.4169725 0.1953434 0.6985522 1.2158700Number of Observations: 2681
Number of Groups: 3*
现在,你可能想知道我如何知道我的随机效应(即时间)是否显著。有几种方式来看待这个问题。
- ***比较仅截距模型(模型 1)的 AIC 和随机截距模型(模型 2)的 AIC。 *AIC = 2k — 2(对数似然),当 k 为模型中包括截距在内的变量个数时),对数似然为模型拟合测度,可从统计输出中获得。从 Satisticshowto 查看这些有用的信息。
从我的模型 1 和模型 2 的输出,你会看到模型 1 的 AIC = 6543.89,模型 2 的 AIC = 6533.549。通常,两个 AIC 值相差超过 2 表示模型拟合有显著差异。AIC 值越低,模型越适合。你可以看到在模型 2 中把时间作为随机效应包括进来改进了我的模型 1 (6543.89 -6533.549 > 2)。
2.除了 AIC,我们还可以使用 ANOVA 函数来比较仅截距模型和随机截距。
*anova(model1, model2)*
结果如下:
*Model df AIC BIC logLik Test L.Ratio p-value
model1 1 2 6543.890 6555.678 -3269.945
model2 2 3 6533.549 6551.231 -3263.775 1 vs 2 12.34079 4e-04*
p 值 4e-04 等于 4 x 10^-4,表明结果非常显著。因此,添加随机截距显著改进了仅截距模型。
除了 nlme 包中的 gls 和 lme 函数,我们还可以使用 lme4 包中的 lmer。一般来说,lme 和 lmer 都是混合数据分析的有效函数,但需要考虑一些差异:
- lmer 不像 lme 那样分析一些相关结构。
- nlme 是一个更大的工具包,他们关于混合模型的代码更容易理解。
- nlme 可以用来定义交叉随机效应,比 lme 更容易和更快。
- 由 nlme 软件包(例如 lme 和 gls 函数)和 lme4 软件包(例如 lmer 函数)拟合的模型假定抽样方差是已知的。
简单地说,对于简单的 HLM 分析,lme4 和 nlme 都应该提供相近的参数值。你可以查看这一页来比较这些包装。
如果你想尝试 lme4,你需要先安装 merTools :
*install.packages(“merTools”)
library(lme4)
library(merTools)*
让我们使用 lme4 的 lmer 来运行我们的随机截距模型
*model2.1<-lmer(Satisfaction~1+(1|Time), REML = FALSE, data = data)
summary(model2.1)*
结果:
*Linear mixed model fit by maximum likelihood ['lmerMod']
Formula: Satisfaction ~ 1 + (1 | Time)
Data: dataAIC BIC logLik deviance df.resid
6533.5 6551.2 -3263.8 6527.5 2678Scaled residuals:
Min 1Q Median 3Q Max
-5.0747 -0.4170 0.1953 0.6986 1.2159Random effects:
Groups Name Variance Std.Dev.
Time (Intercept) 0.004351 0.06597
Residual 0.666790 0.81657
Number of obs: 2681, groups: Time, 3Fixed effects:
Estimate Std. Error t value
(Intercept) 5.09278 0.04124 123.5*
可以看到模型 2 (lme4)和模型 2.1 (nlme)的参数相当接近。
我们还可以运行 ICC(又名组内相关系数)来查看组内观察结果的相关性(例如,在我的案例中,每个时间点内的关系满意度)。ICC 指数范围从 0 到 1,数值越大表明组内同质性越高(Gelman & Hill,2007)。
*ICC(outcome = “Satisfaction”, group = “Time”, data = data)
[1] 0.01019326*
你可以看到我的 ICC 值大约是. 01,说明嵌套在一个时间点内的参与者的关系满意度彼此差异很大。
在进入下一个 HLM 分析步骤之前,我想确保我的固定效应回归系数是准确的。为此,我将使用 confint 请求 95%的置信区间(CI)。
如果您不熟悉 CI,该术语指的是一系列值,可能包括具有一定置信百分比范围(通常为 95%)的真实总体参数。公式是
作者图片
x 条是样本平均值
z 是置信水平值
n 是样本大小
s 是样本 SD
假设 95%的 CI 包含两个端点。我们可能会设置一个 1%的下限,这意味着真实总体参数低于我们数据得分的 1%限制的概率仅为 1%。我们还可以设置一个 96%的上限,这意味着真实总体参数超出我们数据得分的 96%限制的概率仅为 4%。上限和下限一起表示我们将发现真实总体参数超出我们设置的范围(1% — 96%)的区间或概率是 5% (1% + 4%)。因此,我们有 95%的置信区间,真实参数将在样本的上限和下限范围内。如果您想了解更多关于 CI 及其与 t 分布的关系,请查看此链接。
现在,的置信水平不同于的置信区间。如果我们多次重新运行一项研究,并以 95%置信区间估计一个感兴趣的参数,我们每次都会得到不同的 95%置信区间值,这是由于我们的数据中存在误差,这些误差可能是由多种因素造成的,例如参与者的因素、测量误差、我们每次分析时的情绪。然而,那些不同 CI 值的 95%将覆盖真实参数值,这个概念就是置信水平。如果我们将置信水平的下限设置为 1%,这意味着在我们重复进行的许多实验中,只有 1%的实验的真实参数值低于 1%的限制。如果我们设置一个 96%的上限,我们将发现高于上限的真实参数值的概率是我们重复进行的几次实验的 4%。**
由于人类喜欢对称的东西,所以人们经常将 95%的置信区间设定为下限 2.5%和上限 97.5%。在 2.5%的重复研究中,真实总体参数值将低于该区间,而在另外 2.5%的重复研究中,真实总体参数值将高于该区间。因此,在所有进行的研究中,置信水平将覆盖 95%的真实参数。
让我们回到我们的例子。如果我想知道模型 2.1 的置信水平,我将使用下面的代码。
*confint(model2.1)*
结果:
*2.5 % 97.5 %
.sig01 0.0267156 0.2084871
.sigma 0.7951820 0.8389382
(Intercept) 4.9785129 5.2081911*
结果表明,如果我重新运行我的研究几次,95%的情况下,截距系数(即,考虑时间随机影响的人群中关系满意度的真实平均值)将大约在 4.98-5.21 之间。
第三步:随机截距模式中的固定效果
由于我主要对 NPD 的固定效应感兴趣,我将在我的随机截距模型(模型 2 或模型 2.1)中包括预测值。我仍然让截距变化,这意味着每个时间点可能有不同的关系满意度得分截距。为了在随机截距模型中生成固定效果,我将使用 nlme 包中的 lme()。
*model3 = lme(Satisfaction~ NPD, data = data, method = “ML”, na.action = “na.omit”, random = ~1|Time)
summary(model3)*
结果:
*Linear mixed-effects model fit by maximum likelihood
Data: data
AIC BIC logLik
6468.46 6492.036 -3230.23Random effects:
Formula: ~1 | Time
(Intercept) Residual
StdDev: 0.07411888 0.8063175Fixed effects: Satisfaction ~ NPD
Value Std.Error DF t-value p-value
(Intercept) 4.672165 0.06842444 2677 68.28210 0
NPD 0.122980 0.01491822 2677 8.24362 0
Correlation:
(Intr)
NPD -0.746Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-5.0666244 -0.4724214 0.1792983 0.7452213 1.6161859Number of Observations: 2681
Number of Groups: 3*
固定效应是显著的。我们来比较一下固定效应的随机截距模型(模型 3)是否优于随机截距模型(模型 2)。
*anova(model3, model2)*
结果:
*Model df AIC BIC logLik Test L.Ratio p-value
model3 1 4 6468.460 6492.036 -3230.230
model2 2 3 6533.549 6551.231 -3263.775 1 vs 2 67.0889 <.0001*
结果显示了两个模型之间的显著差异,表明添加固定效应显著改善了随机截距模型。
步骤 3 中模型拟合的替代方法是使用 lmer 函数:
*model3.1 <-lmer(Satisfaction~1+NPD+(1| Time), REML = FALSE, data = data)
summary(model3.1)*
结果:
*Linear mixed model fit by maximum likelihood ['lmerMod']
Formula: Satisfaction ~ 1 + NPD + (1 | Time)
Data: dataAIC BIC logLik deviance df.resid
6468.5 6492.0 -3230.2 6460.5 2677Scaled residuals:
Min 1Q Median 3Q Max
-5.0666 -0.4724 0.1793 0.7452 1.6162Random effects:
Groups Name Variance Std.Dev.
Time (Intercept) 0.005494 0.07412
Residual 0.650148 0.80632
Number of obs: 2681, groups: Time, 3Fixed effects:
Estimate Std. Error t value
(Intercept) 4.67216 0.06840 68.308
NPD 0.12298 0.01491 8.247Correlation of Fixed Effects:
(Intr)
NPD -0.746*
可以看到,lme 和 lmer 函数的参数估计值非常接近。
第四步:添加随机斜率项。
在 HLM,添加随机斜率允许随机效应组的回归线在斜率系数方面有所不同。在我的案例中,一个人的 NPD 和结果(关系满意度)在不同时间水平上的斜率可能会有所不同,因为人们的 NPD 症状可能会在不同时间点减弱或增强,这取决于他们的生活事件。为了测试这一假设,我将把 NPD 特质按时间嵌套,并允许 NPD 和关系满意度的斜率在不同的时间水平上变化。
*model4= lme(Satisfaction~ NPD, data = data, method = “ML”, na.action = “na.omit”, random = ~NPD|Time, control = lmeControl(msMaxIter = 200))
summary(model4)*
结果:
*Linear mixed-effects model fit by maximum likelihood
Data: data
AIC BIC logLik
6472.46 6507.823 -3230.23Random effects:
Formula: ~NPD | Time
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
(Intercept) 0.072374062 (Intr)
NPD 0.002428596 0.131
Residual 0.806315723Fixed effects: Satisfaction ~ NPD
Value Std.Error DF t-value p-value
(Intercept) 4.672058 0.06779927 2677 68.91016 0
NPD 0.123021 0.01498469 2677 8.20977 0
Correlation:
(Intr)
NPD -0.742Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-5.0663508 -0.4722466 0.1806865 0.7456579 1.6137660Number of Observations: 2681
Number of Groups: 3*
输出表明时间截距的变化符合 0.0724 的较大 SD。预测关系满意度的 NPD 斜率的变化符合 0.0024 的较小 SD。结果表明,参与者的关系满意度在不同时间水平上的差异可能比每个时间点内 NPD 症状的严重程度更大。
弱正相关(Corrr=0.131)意味着截距的更正值与斜率的更正值稍微相关。如果参与者的截距增加一个标准差单位,斜率只会增加 0.131 个标准差。换句话说,关系满意度的截距随时间明显不同,而 NPD 和关系满意度之间的相关性斜率的变化更微妙。因此,模型 4(添加随机斜率项)很可能不会显著改善模型 3(随机截距模型)。让我们测试一下这个假设。
*anova(model3, model4)*
结果:
*Model df AIC BIC logLik Test L.Ratio p-value
model3 1 4 6468.46 6492.036 -3230.23
model4 2 6 6472.46 6507.823 -3230.23 1 vs 2 0.000787942 0.9996*
正如预期的那样,添加随机斜率项不会显著改善随机截距模型并增加 AIC 值(即更差的拟合)。是否排除随机斜率项取决于几个因素,例如为您的数据提供信息的理论,排除或包括随机斜率是否会使模型收敛,以及您是否希望得到一个最节省或最大的模型。这完全取决于你的决定和学习领域。这篇文章提供了更多关于随机效应的细节,值得一读。
附加步骤:
如果您有一个交互项,您可以测试添加该交互项是否会改进您的模型。我将测试是否添加边缘型人格障碍特征(BPD),它与 NPD 高度共病,作为一个调节子将改善我的随机截距模型(模型 3)。我选择忽略随机斜率模型(model4 ),因为这个术语并没有改进模型,而且研究认为 NPD 特征可能不会随着时间点而改变。
*model3withBPD<-lme(Satisfaction~NPD+BPD+BPD*NPD,
data = data, method = “ML”, na.action = “na.omit”, random = ~1|Time)
summary(model3withBPD)*
结果:
*Linear mixed-effects model fit by maximum likelihood
Data: data
AIC BIC logLik
6425.735 6461.098 -3206.867Random effects:
Formula: ~1 | Time
(Intercept) Residual
StdDev: 0.07982052 0.7992555Fixed effects: Satisfaction ~ NPD + BPD + BPD * NPD
Value Std.Error DF t-value p-value
(Intercept) 4.443310 0.09474416 2675 46.89799 0
NPD 0.153825 0.02988573 2675 5.14709 0
BPD 0.017154 0.00251750 2675 6.81408 0
NPD:BPD -0.003436 0.00058873 2675 -5.83621 0
Correlation:
(Intr) NPD BPD
NPD -0.807
BPD -0.417 0.251
NPD:BPD 0.600 -0.578 -0.907Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-5.2024359 -0.4590723 0.1866308 0.7317000 1.8891006Number of Observations: 2681
Number of Groups: 3*
相互作用项是重要的。我们将看到添加交互是否会改进模型 3:
*anova(model3, model3withBPD)*
正如所料,添加交互项显著改进了我的仅随机截距模型:
*Model df AIC BIC logLik Test L.Ratio p-value
model3 1 4 6468.460 6492.036 -3230.230
model3withBPD 2 6 6425.735 6461.098 -3206.867 1 vs 2 46.72568 <.0001*
我希望到现在为止,你已经知道如何指挥简单的 HLM 了。请继续关注未来更复杂的 HLM 分析。
有关本教程中使用的完整代码,请参见以下内容:
使用 PyTorch 中预先训练的卷积神经网络从胸部 X 射线图像进行高精度 Covid 19 预测
利用 Efficientnet 架构在与 Covid19 相关的医学成像数据集上实现 99%以上的预测准确率
在这篇文章中,我将分享我开发卷积神经网络算法的经验,以高精度从胸部 X 射线图像预测新冠肺炎。我在参加深度学习博士课程的课堂 Kaggle 竞赛时开发了这个算法。我很高兴从最终的比赛排行榜中得知 我在比赛 中在 40 名参赛的统计学和计算机科学系的博士和硕士研究生中名列第一!我将提及我在开发和训练模型时所面临的挑战,以及我如何找到解决这些挑战的方法,而不是直接讨论结果。在研究这个问题的时候,我亲身体验了“享受过程,结果会自然发生”这句话的道理!
你可以在 GitHub 这里找到我的完整代码。
挑战简介
给定一幅输入的胸部 x 光图像,算法必须检测该人是否感染了新冠肺炎病毒。我们有一组受监督的 X 射线图像,已经由放射科医生仔细标记,模型必须在这些图像上进行训练。这种模型可以帮助卫生保健专业人员比放射科医师更快地诊断新冠肺炎病例,尤其是在需要在短时间窗口内对大量人员进行测试的情况下,放射科医师必须逐个进行每次扫描。
我们举个例子。下面的图片来自竞赛网站,分别是没有(阴性)和有(阳性)新冠肺炎的人。
没有(左)和有(右)新冠肺炎的人的胸部 x 光照片(来源:竞赛网站:https://www.kaggle.com/c/stat946winter2021/overview)
数据集描述
训练数据集由 15264 幅(512×512 像素)图像组成,这些图像已经由放射科专家分类。测试数据集由来自相同分布的 400 个这样的图像组成。你可以在这里找到数据集。
我发现训练数据集明显不平衡,大约 90%的图像属于非 Covid 类。显著不平衡集合的问题是,即使简单地输出多数类的类作为输出的朴素学习算法也将获得高精度。换句话说,它会将每个人标记为无 Covid,并达到 90%的“准确率”,即使大约 10%的人实际上有 Covid。最初,我从不平衡的训练数据集开始。事后看来,这并不是最好的起点,但我们必须从自己拥有的东西开始,在这种情况下,就是训练数据集。我将在本文后面更详细地描述它。
攀登绩效阶梯——步骤和失误
深度学习算法有许多活动的部分,我发现训练一个高性能的模型既是一门艺术,也是一门数学。一个人在开始时并不知道大多数答案,必须通过实验来学习。
我现在将描述我是如何选择每个超参数的,以及我在这个过程中学到了什么。
学习率:学习率越低,收敛越好,但不一定测试精度最好。问题可能是算法还没有学习到导致测试集的最低误差的权重集。另一方面,较高的学习速率会导致算法超过最优的权重集。我试验了 10e-3、10e-4 和 10e-5 的学习率,发现 10e-4 导致测试数据集的错误率最低。
**批处理大小:**在将批处理大小增加到 256 甚至减少到 64 时,我遇到了内存问题,因此不得不选择更小的批处理大小。我用 32 和 16 做实验。在这两个批量中,32 的批量导致最低的错误率,学习率为 10e-4。在这篇论文中,我们已经很好地探讨了学习速度、批量大小和准确性之间的相互作用。作者为 VGG16 CNN 实验了各种学习速率和批量大小。我从一个较低的学习速率(lr = 0.0001 as aginst 0.001)开始,后来证明这是一个更好的选择,并根据经验将批量大小设置为 32,这在深度学习实践中很常见。我感谢作者让我从他们的研究中得到的线索。
像深度学习这样的复杂领域从来都不是孤狼式的努力,我们都站在彼此的肩膀上,而不仅仅是巨人的肩膀上,向前看。
预训练模型:来到T3 使用哪个预训练模型的问题;我在谷歌人工智能博客上看到了一篇有趣的文章,该文章比较了 Imagenet 数据集上的准确性与几个预训练模型的模型参数数量。计算机视觉是一个快速发展的领域,新的模型也在快速发展。最初,我计划使用 VGG16 或 Resnet50,但当我在 Efficientnet 上看到这篇文章时,我改变了主意,转而支持 Efficientnet。我有一个 RTX 2070 GPU,我的硬件限制对我可以使用的模型施加了约束。我开始使用 b7 版本的 Efficientnet,但我的 GPU 很快就放弃了,因为这个模型不适合它的内存。在 b7、b6、b5 和 b4 模型上运行了无数次 Kill -9 job_number(在 Linux 中从计算机内存中删除模型的命令:)之后,我选择了 Efficientnet b3。当时这样做的唯一原因是,它是 Efficientnet 系列中最大的型号,最终适合我的 GPU 内存。后来,这被证明是一个极好的选择!
**时期:**对于像 Efficientnet b3 这样拥有 1200 万个参数和数万个训练数据集实例的模型,很容易过度训练模型。实际上我在用 20 个纪元训练模型的时候,得到的训练准确率是 1!不要问我那种情况下的测试精度:)我使用了范围从 1 到 30 的 epochs,用于各种学习速率和批量大小的组合。
**采样:**我从没有采样开始。奇怪的是,这个模型将几乎所有的测试用例归类为“1”,与我预期的“0”相反。我觉得问题是测试图像没有像训练图像那样被转换,并且未转换的测试图像在某种程度上类似于标签为“1”的转换后的训练图像(稍后将详细讨论这个关键主题)。然后,我使用了一个加权随机采样器,这导致了在训练数据集中 0 和 1 类几乎各占一半。但是,这也导致整个训练数据集的大小从大约 15000 减少到大约 3000 以下。使用这种采样技术,精度有所提高,但稳定在 0.86 左右。接下来,我从少数民族类中进行了额外的采样,并将两个类中的图像数量进行了对比。通过这样做,我的训练数据集从 15000 增加到 26000。这当然有帮助,因为我现在的准确度达到了 90 %,我得到了大约 0.94 的准确度。
**转换——将训练数据集上使用的相同转换应用于测试数据集:**我的准确率稳定在 0.94,其他人的准确率达到了 0.975。这让我开始认真思考我的模型中缺少了什么。在洗澡、做饭和走路的时候,我一遍又一遍地追踪这些步骤。
突然,灵机一动:配送!所有的机器学习都基于这样的假设,即测试数据的分布与训练数据的分布相同。否则,我们从训练数据集中学到的东西将不会应用到测试数据集中。通过旋转和平移变换训练图像,我改变了分布。然而,通过让测试图像保持原样,我无意中在一个“不同的”分布上应用了该算法。我立即对测试图像应用了相同的变换,你瞧,我第一次看到了 0.975 的精确度!
**调优:**一旦有了这种认识,剩下要做的就是调优超参数,以获得可能的最佳结果。我尝试了各种时期,发现对于这个具有我使用的预训练模型、学习率和批量大小的数据集,在 11 个时期的训练后,我获得了最佳的测试准确性。
我还能做得更好吗?
或许使用 Efficientnet-b7 配合更大的批量可以获得更高的准确度。如果有人有更强大的 GPU/分布式计算系统,请尝试一下并告诉我。
实际应用:使用这种算法,医生可以快速准确地识别 100 个人中的 99 个人的 Covid(在 15000 幅图像上训练该算法并在 400 幅图像上运行该算法大约需要 40 分钟),仅考虑测试图像的分类时间,该算法对 400 幅图像进行分类仅需要 1 分钟。因此,这对于现场工作人员快速筛选图像并识别测试呈阳性的人非常有用。
在向您介绍了算法发现之旅之后,我现在转到具体的开发部分。
py torch 中的算法开发
我在 PyTorch 中开发了我的算法,py torch 是一个非常适合计算机视觉任务的机器学习库。
- 作为第一步,我导入了 numpy、pandas、os、torch 等,并设置了数据文件夹所在的基本路径。然后,我创建了 pandas 数据框,分别包含训练数据集图像名称及其标签和测试数据集图像名称。
导入库和创建数据框架(作者代码片段)
2.要检查数据帧是否正确加载,最好检查 train_dataset 和 test_dataset 的前几行。下面是 Jupyter 笔记本的代码和结果输出:
查看训练和测试数据集数据帧的前几行(图片由作者提供)
我们看到图像的名称在文件列中,标签在训练图像的标签列中。对于测试图像,我们在文件列中有名称。
3.下面是查看训练数据集文件夹中的一些图像的代码,
绘制一些训练数据集图像的代码(图像由作者提供)
该代码将产生 15 个如上所示类型的 X 射线图像。
4.下一步是以 Pytorch 可以处理的形式从图像和标签中创建训练数据集。Dataloader 是 Pytorch 中的一个功能,它使我们能够方便地将图像和它们的标签配对,以创建一个矩阵/张量,然后 PyTorch 可以将其用作学习算法的输入。
创建将图像和标签配对的类(按作者分类的图像)
5.然后,我将学习率设置为 10e-4
设置学习率(图片由作者提供)
6.接下来,我使用在步骤 4 中创建的 Dataset dataloader 类准备了训练集。我将其命名为 training_set_untransformed,因为我还没有对图像应用旋转或平移等数据转换。
创建训练数据集(图片由作者提供)
7.然后我创建了一个变换。Compose 方法来调整图像的大小,随机应用旋转和水平平移,并将结果矩阵转换为张量。这种变换对于数据扩充是有用的。
创建一个变换函数来增强图像(作者的图像)
8.下一步是对训练数据集使用 transforms 方法。对于少数类中的图像(covid 情况),我对它们进行了上采样,以便在应用变换后两个类中的图像数量相同。
对少数类进行上采样并应用变换(图片由作者提供)
9.然后,我将这些新创建的图像按照 80:20 的比例分成训练集和验证集。
将数据集拆分为训练集和验证集(图片由作者提供)
10.使用 Pytorch 的 Dataloader 函数,我从训练数据集中创建了每批 32 个的数据,并对它们进行了混洗,以确保所有批中两个类的表示大致相等。
从训练数据集创建批次(作者图片)
11.为了启用 GPU 计算,我使用了 cuda.is_available()命令。深度学习代码也可以在 CPU 上运行,但需要更长的时间。
启用 GPU 计算(图片由作者提供)
12.以下是从 Pytorch 导入 Efficientnet-b3 并将类的数量设置为 2 的命令。
导入 Efficientnet-b3 模型(图片由作者提供)
13.导入后,模型必须加载到 GPU 的内存中。如果您的型号对于 GPU 内存来说太大,您将会收到一条错误消息,并且需要选择另一个适合内存的型号。在 Jupyter 笔记本上运行该命令,欣赏 Efficientnet 的复杂性!
将模型加载到系统中(图片由作者提供)
14.定期保存训练过程产生的权重是有好处的。如果模型由于某种原因崩溃,我们可以调用保存的权重并恢复训练,而不是从头开始。
创建一个文件夹来保存权重(图片由作者提供)
15.我使用交叉熵损失作为损失计算的标准,因为我们正在处理一个分类问题。我使用了 Adam 优化器,这是一种众所周知的深度学习的权重更新方法,并将历元数设置为 11(这种学习是在大量实验后产生的)。学习率衰减是用于在每个时期之后更新学习率的参数,并且我设置了 0.99 的保守衰减率,因为学习率已经很低了。我创建了两个列表来跟踪准确性和损失历史。
设置超参数(图片由作者提供)
16.训练模型:下面是代码的训练部分。我运行了 11 个时期的模型,并保存了模型的第 1、第 10 和最终(第 11)版本。训练它大概花了 40 分钟。
训练模型(图片由作者提供)
*At the end of the 11th Epoch, I got an accuracy of 99% on the training dataset for both classes.* Accuracy of 0: 99 %
Accuracy of 1: 99 %
[11 epoch] Accuracy of the network on the Training images: 99
17.这是一张训练精度和迭代交叉熵损失的图表。有 7227 次批量迭代,该图显示了这些值如何随着批量迭代而变化。
损失和准确性历史(图片由作者提供)
18.下一步是应用变换。组合到测试数据集,使其符合与训练数据集相同的分布。
将转换应用到测试数据的函数(图片由作者提供)
19.为了预测测试图像的类别,我们需要定义一个 predict_image 函数:
预测和图像功能(作者提供的图像)
20.以下代码片段检查模型在验证数据集上的表现。
预测验证准确性(图片由作者提供)
21.通过下面几行,我们可以预测测试集图像的类别,并将它们存储在一个 csv 文件中。
预测测试图像类别并存储它们(按作者分类的图像)
劳动的果实!—竞赛排行榜(来源:竞赛网站
结论
通过这次 Kaggle 比赛,我了解到构思和开发深度学习算法需要一些耐心,我们必须不断寻找模型中的漏洞以及其他人的发现,以使我们的模型变得更好。我的文章是将它转发给机器学习社区的一种小小的方式,这样我们就可以一起分享和学习,并改进我们的算法,以更好的方式解决问题!
希望你能从我的帖子中学到一些有用的东西。请分享您的宝贵反馈!
鸣谢:我非常感谢 Kartik Dutt,他为一个不同的计算机视觉挑战开发了代码,我发现他的代码部分适合这个深度学习任务,并融入了我的工作。(https://github . com/kartikdutt 18/ka ggle-ATPOS-Efficient _ Net/blob/master/Efficient _ Net . ipynb)
大量的唯一值和基于树的模型
基数有多高会影响购物车的性能和解释
H 使用高基数的数据列会对模型的性能产生负面影响。这篇文章的想法源于我在各种项目中使用基于树的解决方案的个人经历。在本文中,我将尝试使用简单的决策树来展示这对几个数据集的影响。在进入示例之前,让我们首先了解
什么是基数,树模型如何工作
基数可以定义为机器学习上下文中数据的唯一性。具有大量唯一值的字段示例包括城市、国家、医疗诊断代码、网飞电影类别、冰激凌口味等。
作者图片
决策树示例。作者图片
为了使大多数树模型工作,所有变量类型都必须转换成数字。机器学习树基于高于或低于值来分割它们的节点,即年龄大于低于 25,权重小于 50。这对于连续数据或任何顺序数据都非常有效。
收入数据示例
为了说明高基数对树分裂的影响,我将使用的第一个数据集是来自 Kaggle 的收入分类数据。该数据包含连续和分类的个人数据,以预测个人是高收入还是低收入的二元结果。
我将缩小范围的特征是职业类别变量。这一列中有 13 个唯一值,它们是用标签编码的。
编码映射。作者图片
在拟合决策树之后,通过观察整个树模型模式,我捕获了在任何节点中使用 occupation 进行分割的所有值。完整的树模式太大,无法显示,可在此处查看。
通过提取使用 occupation 拆分节点的所有节点,我们可以从右侧的表中看到哪些点的数据用于拆分。作者的图表和表格
占领的分裂发生在这些值:
0.5,2.5,3.5,5.5,8.5,9.5,10.5
将其映射到编码表显示了在整个树中用于分类数据点的决策边界。
这似乎不对
观察决策边界后,我的第一个想法是,所有的值都应该被分成离散值。它们彼此之间没有关系,不能用更高或更低的数值来表示。
如果不对每个不同的值进行拆分,一些值将属于同一个存储桶,在进行拆分时会被集中在一起。这导致某些分组没有直观意义,例如
武装部队,和
[ 机器操作检查、其他服务、私人服务 ]
如从上面的分裂边界所观察到的。
此外,默认情况下,职业是按字母顺序编码的。通过改变编码顺序,模型超参数实际上受到了影响!
在第一次运行中,使用网格搜索交叉验证在 max_depth 为 8 处拟合最佳估计值。但是,使用新的编码顺序再次运行它,同时保持其他所有内容不变,会产生不同的最大深度值 10 和不同的 occupation 类分割边界。
使用虚拟数据进行调查
为了更好地理解这些影响,我决定深入研究以验证我的发现。我对只有 4 行的简单虚拟数据运行了相同的步骤。用决策树对初始数据进行编码和分割。一眼看去,该树可以在值[1,2]和[3,4]之间一分为二。
********
作者提供的图片
决策树架构。作者图片
左侧显示了用 sklearn.tree 绘制的相应树模式。
为了以文本形式描述它,顶部的节点指定那些值小于 1.5 的被归类为 1,那些值大于 1.5 的被归类为 0,最佳地将我们的数据分成正确的结果(0 基尼系数杂质)。到目前为止一切顺利。
接下来,我以不同的顺序重新排列了变量,并将它们编码如下:
********
作者提供的图片
由于决策树是基于数值来分隔数据点的,因此为了分隔数据,决策树必须创建更多的拆分。
决策树架构。作者图片
也就是说,现在需要三个不同的分裂 ≤0.5、≤1.5 和 ≤2.5 ,创建一个更深的树以达到最佳结果,即使使用的数据完全相同。
这表明编码顺序影响结果,树分裂将持续分裂,直到它不再能减少杂质。
因此,我们知道,对于收入分类数据,即使数据点仍然集中在一起,这也是分离可以达到的最高粒度级别。树的分割按预期工作,进一步分割值只会使模型过拟合。
如果这棵树能做出最佳的分裂次数,为什么不顺其自然呢?
事实上,虽然模型结果没有受到显著影响,但我们已经观察到树必须更加努力地分割数据。这仅仅是因为列中的唯一值越多,树就必须进行越多的拆分来分隔数据。
这种影响在使用最大深度或其他等效超参数控制数据分割程度时最为明显。上述任何增加都会反过来增加模型训练时间。虽然我在我的例子中使用了决策树,但是这个问题影响了所有基于树的模型。给定足够多的具有高基数的特征,即使更复杂的算法也会受到影响。
我敢肯定,我们不希望以较差的模型性能为代价,平衡拟合一个较浅/不太复杂的树来减少训练时间。
那么我们该如何着手呢?
处理数据中的高基数
在这里,我将简要介绍一些解决这个问题的有用方法:
- 将数据组合成组。这可能需要领域知识。这个想法是通过将唯一值分成有意义的集合来减少它们的数量。这是一个很好的方法,只要分组不会显著降低模型性能和对业务涉众的可解释性。
- 尝试不同的编码方法。诸如均值/计数编码、散列等方法在减少数据级别方面特别有效。然而,它们可能对模型的可解释性有害。
- 使用非基于树的模型,例如带有实体嵌入的神经网络。如果没有树,基于树的模型没有问题,但现在你将有一个全新的问题要处理。
- 具体来说,为了处理输入数据中所示的解释问题,可以使用一个热编码,尽管它仍然不得不创建更多的分割。****
作为一个认为树模型的力量是理所当然的人,深入研究树和数据基数之间的关系帮助我改进了建模方法。希望这篇文章也能以同样的方式帮助你。这里是所用代码的 github 库。感谢阅读!
****https://github.com/WeiHanLer/Tree-Cardinality-Article ****
利用 Pedalboard 和 tf.data 实现高性能音频处理
最近,Spotify 发布了一个用于音频处理的 Python 库,名为 Pedalboard。在这个故事中,我想研究它在 tf.data 管道中机器学习任务的数据增强环境中的表现。
图 1:表达爱意|作者图片
Pedalboard 由 Spotify 的音频智能实验室开发,用于在 Python 和 TensorFlow 中实现录音室质量的音频效果。在引擎盖下,Pedalboard 是一个围绕 JUCE 的 Python 包装器,这是一个强大的框架,用于构建音频应用程序,如 VST3s 甚至移动应用程序[1]。到目前为止,他们声称与数字音频工作站(DAW)中使用的效果不相上下似乎是合理的。此外,pedalboard 能够作为 VST3 和音频单元 (AU)的独立主机。
因为我对音频数据进行机器学习的主要工具是 TensorFlow,所以我一直在寻找能够加速我的预处理流水线的库。我常常羡慕 Torchaudio,它已经有了一组不同的函数和自己的 SoX 包装器。当然,有很多音频处理的替代方案,但是所有流行的解决方案要么是像 SoX 和 ffmpeg 这样的命令行工具,要么不是线程安全的。因此,Pedalboard 似乎是 TensorFlow 中快速高质量音频处理的一个很有前途的库。
今天我想比较一下 Pedalboard 和 SoxBindings 在 tf.data 管道中使用时的处理速度。SoxBindings 是一个很棒的围绕 SoX 的 Python 包装器,它至少在多线程环境中不会失败,但也不会使用它(参见正在进行的 Iissue)【2】。
方法
对于这个任务,我们将使用 LJ 语音数据集 [3]作为我们的音频数据输入。该数据集在美国属于公共领域,对其使用没有限制[4]。该数据集由 13,100 个长度为 1 至 10 秒的音频片段组成,总长度约为 24 小时[3]。在下面的每个实验中,我们将迭代整个数据集并测量运行时间。我们选择了一组在 Pedalboard 和 SoxBindings 中都存在的音效:
- 压缩机
- 合唱
- 高通滤波器
- 低通滤波器
- 相位器
- 混响
然后我们单独使用每个效果来转换数据集。在第二个实验中,我们将所有 6 个效果链接成一个转换。当然,在大多数增强任务中,你会想要随机初始化每个效果的参数。然而,初步测试表明随机参数化对处理速度没有负面影响,所以为了代码的可读性,我们忽略了它。所有实验都在一个 12 核的 CPU 上运行。
密码
首先,我们加载 LJ 语音数据集,并将其仅映射到音频数据:
然后,我们定义一个包装 SoxBindings 转换的包装器,这样我们就可以在 tf.data map 调用中使用它:
踏板也是如此:
当在 tf.data map 中调用 SoxBindings 函数时,保留num_parallel_calls = None
很重要,否则您的内核会无声无息地死去。这可能是由于 SoxBindings 没有在C
扩展中释放 GIL[5]。
包含所有实验的完整笔记本可以在我的 GitHub-Page 上找到!
结果
第一个实验的结果不言自明,Pedalboard 在处理时间上远远落后于 SoxBindings。在图 2 中,我们可以看到所选效果的加速在 2.5 倍到 6 倍之间。
图 2:按效果分类的数据集转换持续时间|按作者分类的图像
第二个实验展示了使用 Pedalboard 和 SoxBindings 的内置效果链的性能。第三个设置Pedalboard Chain w/TF . data使用 tf.data map 调用来连续链接各个 pedal board 效果。这种方法产生了迄今为止最好的性能,比另一种方法快 4 倍。
图 3:使用效果链的数据集转换持续时间|作者图片
摘要
结果显示,在 tf.data 管道中,通过 SoxBindings 使用 Pedalboard 时,速度有了很大的提高。然而,当使用 Pedalboard 的内置效果链时,似乎会有巨大的性能损失,在这种情况下,性能会低于 SoxBindings 实现。也许 tf.data 只是在并行计算方面比 Pedalboard 更好,我们只是看到了让 tf.data 做它的事情的好处。使用 Pedalboard 的一个缺点是此时可用的效果相对较少。虽然 SoX 和 SoxBindings 已经有了相当多的算法库,但 Pedalboard 仍然需要迎头赶上。这种情况将来可能会改变,在某些情况下,您已经可以使用 VST3 或 AU。
就我而言,我肯定会在一些现实世界的应用程序中尝试一下 Pedalboard,看看它是否能给我更好更快的音频处理管道!
如果有人在我的实现方法中发现系统缺陷,请告诉我,我很乐意让音频处理更快;)
资源
[1]【https://juce.com】
【2】https://github.com/pseeth/soxbindings
【3】k . Ito 和 l . Johnson:LJ 语音数据集(2017 年)https://keithito.com/LJ-Speech-Dataset/
【4】LJ 语音数据集许可(2021 年)https://librivox.org/pages/public-domain/
【5】https://github.com/pseeth/soxbindings/issues/6
如果你正在阅读这篇文章,我们可能有相似的兴趣或在同一个行业,欢迎你联系我。在LinkedIn上找我。
高性能代码带来回报
快速代码变得更快,成本变得更低,创造力被释放,自由之声响起。
我花了几年时间开发的 connected components 库的性能随着版本和月份/年份的增加而增加。横轴是每秒数百万个 3d 像素(“体素”)。纵轴是测量的 RAM 使用峰值。有更快的算法可用,但据我所知,不是用于 3D 多标签图像。许可证:图片由作者提供
在公共领域,如果不是在每一扇紧闭的门后,软件社区会用 Donald Knuth 的一句无处不在且被误解的名言“过早优化是万恶之源”来阻止任何关注性能的建议虽然我和 Knuth 一样不喜欢投机性的微优化,但是对软件性能的认真关注带来了巨大的好处,并且受到反馈循环的影响,可以在几个层面上从根本上改进项目。
我自己的经验是在 3D 图像处理领域积累的,那里的数据非常大。一个典型的输入图像是一个 512 体素无符号 64 位图像剪切块(~1 GB RAM),它是一个更大的图像的一部分,该图像可以由成千上万的这些任务组成。如果不仔细关注每个算法的设计,单个任务的运行时间可能会长达几个小时,同时 RAM 的使用也会激增。
在高性能计算方面,肯定有比我更有经验的工程师。然而,在过去的几年里,我对加速和减少内存压力的好处有了一些小小的见解。也许你会发现它们很有用。
阿姆达尔定律反面的正反馈
用英语解释,阿姆达尔定律(Amdahl ’ s Law)说,加速一段代码最多可以提高代码开始所用时间比例的整体速度。换句话说,如果一个程序在段 A 中花费 50%的时间,在段 B 中花费 50%的时间,段 B 的 100%加速将导致最多 2 倍的总运行时间改进,因为段 A 没有被改变。Amdahl 定律通常是关于串行和并行组件的代码并行化,但它同样适用于没有完全改进的单线程代码。
当我第一次遇到阿姆达尔定律时,它有点悲观。无论给定代码段的解决方案多么巧妙,它的最终潜力都受到周围一切的限制。在花时间改进算法并与剖析器一起迭代工作之后,我修改了这个视图。
一个领域的加速使得代码其他部分的加速更有价值。用前面的例子来说,如果 A 部分占用了 50%的时间,那么在这个等式的任何一边,我所做的任何改进最多是两倍。如果我将 A 部分的性能提高一倍,它现在占用了总时间的 33%。这意味着 B 段的贡献从 50%增加到 66%。如果我把 A 段加速 4 倍,B 段的贡献是 80%。这放大了程序其余部分的额外优化的效果,并使以前看似微不足道的贡献者突然值得攻击。
换句话说,你越是从根本上提高目标部门的绩效,潜在的新目标领域就变得越丰富。如果您仅将性能提高 2 倍而不是 10 倍,您的目标区域将继续掩盖其他贡献者。随着您改进的代码继续成为瓶颈,您提高性能的选择将显得更加混乱。
你成为一名更好的工程师
在高性能领域,算法知识和对系统的复杂理解变得至关重要。虽然日常工程的内容是让事物工作,但解决执行时间、内存容量或资源争用等物理问题迫使人们对基础科学原理进行更系统和实用的研究。
在工程学中,当你开始在你的工具和材料所能承受的极限范围内工作时,数学是非常有用的。当建造一个典型的乐高塔时,人们不需要知道塑料砖的弹性模量或拉伸强度,因为它们比施加的力要大得多。然而,如果你试图用它们建造一个可居住的结构,你需要更多地了解它们的属性和环境相互作用。
计算也是如此。当内存和计算负载开始达到物理极限或与系统的其他元素相互作用时,就需要进行规划和系统分析。这时,你不得不考虑文献,计算最坏情况下的负载,并调整算法。此外,如果你足够深入地研究一个问题,你可能会发现它的一些新的共性,并成为一名科学家。
高性能计算(HPC)远远不是提高技能的唯一主题领域。例如,自然语言处理、搜索、运动规划和计算机视觉与性能只有很小的关系。然而,HPC 对于实践中的工程师来说是非常容易理解的,它的含义涉及到每个项目。
释放创造力,避免问题
每个程序在开发者耐心、用户耐心、精力和成本方面都有隐含的时间预算。对于开发人员来说,更快的代码允许对周围的设计进行更快速的迭代。对最终用户来说,程序的等待时间减少了,因此程序有机会过渡到他们意愿的自然而灵活的扩展。
更高的效率还能增加原本难以想象的功能。如果一个库只改进到满足当前性能标准的程度,而不是更多,那么一旦问题发生变化,添加特性就变得很麻烦。性能曲线的任何显著下降都会立即将运行状态变为黄色或红色。尤其是在科学或其他开创性工作中,成功的第一种方法将让位于对更复杂方法的渴望。这些复杂的方法可能需要额外的空间来适应。
例如,我致力于改进大规模骨骼化,这是一种从神经元的 3D 标记图像中提取简笔画的方法。我估计,一个早期但严肃的尝试将需要在我们最大的数据集上租用大约 50 万美元的计算时间。经过大约两年断断续续地解决这个问题,纯粹基于改进的软件,成本降低到不到 1000 美元。这使得我们可以在任何时候运行这个算法,反复修正错误,并提高质量,而不会产生重大后果。
我在编写连通分量 3d 算法时也有类似的经历,该算法比最常见的 3d 版本快许多倍。突然间,人们可以愉快而轻松地使用这种算法,而不用仔细规划每个应用程序。开发者迭代速度提高。
从另一个角度来看,性能代码只是避免了问题。避免问题意味着计划没有障碍,没有会议,没有资源转移,并且可能在组织环境中减少压力。不幸的是,证明问题被避免比问题被解决更难。尽管如此,你可以表明你的解决方案明显优于一个可比较的解决方案,或者如果不是对所述代码相当有效,所选择的方法是不可能的。
减少物理磨损
虽然软件通常被认为是抽象的系统或数学对象,但它也是运行在物理系统上的电流的主动过程。有时,软件也操作简单的机械设备或引导复杂的机器人。
当考虑物理世界时,有效的软件可以被认为是最小化能量消耗的软件。在纯计算系统中,这通常意味着减少 CPU 或 GPU 的电力消耗。节能计算可以减少计算机组件的物理降级热循环,并减少移动设备的电池消耗。对于电池供电的设备,低功耗代码更好。通过首先减少电池的消耗,不仅延长了电池的当前充电时间,而且每次充电的运行时间的增加降低了电池的循环速率,增加了组件的总寿命。类似的论点也适用于闪存硬盘的写入耐久性和总存储容量。更少的书写意味着更大的空间和更长的耐力。
在机械领域,选择最短路线的软件(其他条件相同)可以降低燃料成本和机械零件的磨损。一个简单的例子是路线图,其中更短或更快的路线可以通过减少每次旅行的里程来减少燃料消耗并延长汽车的寿命,同时还使乘客平均更快乐(只要他们没有试图享受风景优美的路线或在目的地推迟一些可怕的遭遇)。
适应狭小的空间
某些优化,比如 SIMD 内部函数并不总是可移植的。当我假设英特尔 x86_64 将永远占据主导地位,并在第二年购买了一台 ARM64 笔记本电脑时,我发现了这一点。然而,使用可移植方法编写的具有良好性能的库代码可以容纳比您所能预见的更多的空间。
如果你的代码有合理的内存效率,它可以适合一个嵌入式设备。然而,根据我的经验,更常见的情况是,当我的设计标准是数百兆像素时,我看到有人编写 Github 问题来解决在超过 10 千兆像素的巨大图像上运行我的库的问题。通常,我们可以让它工作(例如通过支持 64 位内存寻址),然后我再也没有听到任何人的消息,因为现在它已经工作了。如果代码的单线程性能一开始就太慢,那么扩展到这样的规模会很痛苦。
另一方面,高性能代码也避免了基础设施灌注。在某一点上,一台机器有一个物理限制,即它可以处理多少请求,可以处理多少数量,可以存储多少数据。然而,这些限制通常有些灵活,因为工程师可以调整每个操作所需的工作量,并根据空间或时间进行权衡。
高效的代码减缓了机器的垂直增长需求(垂直意味着更多的 RAM、CPU、SSD、更快的网卡),同时也减缓了水平增长(意味着更多的机器)。纵向增长成本高昂,并且不会永远扩展。水平增长的规模很大,但在许多不同的层面上很复杂。运营商希望保持最新的部署,避免机器遇到资源争用、构建数据中心的物理挑战(如布线、冷却和移动重物)以及相关的难题。臭名昭著的是,SEC 描述了Knight Capital 未能正确更新所有机器是如何导致 2012 年股市闪电崩盘的。
虽然 Knight Capital 可能将他们的机器推到了极限,他们的问题更多地是沿着变更控制的路线,但是如果您的代码可以推动极限,您将在硬件上花费更少的钱,并且可能在协调由此产生的更少的机器方面有更少的问题。管理 1 台、2 台、20 台、200 台和 2000 台服务器的难度相差很大。当然,像 Knight 一样,无论每个组件的速度有多快,您仍然会遇到设计应用程序中不同服务之间的接口的问题。
表演是解放
高效的工具更简单,因为它们增加了单机计算的操作范围。一个有效的算法可以将一个程序从一个大型集群移动到一个较小的集群,或者从一个较小的集群移动到一台笔记本电脑。
如果程序是为商用硬件设计的,这可不是小事。厄普顿·辛克莱在《史诗十二原则(结束加州贫困)》中写道:“工具私有制,当工具简单时是自由的基础,当工具复杂时就成了奴役的基础。”
计算机是一个强大的计算引擎,是一种通常为个人所有的生产手段。由于需要其他人的设备和代码,变得过于复杂的计算成为其他人对你施加影响的一种方式。例如,如果您需要数百 GB 的 RAM、一个奇特的 GPU 或几十个 CPU 内核,突然之间您需要与一位实业家签订合同来完成这项工作。计算工作越大,关于该任务的决策(是否做、何时做、如何做以及成本如何)就越取决于那些拥有设备、资金、关系和劳动力资源来实现它的人的意愿。
高效的代码将更多的决策权放在个人和不太富裕的人群手中,这些人的动机可能与公司简单的积累动机不同。自由的一个方面是做出决定的能力,这种决定会对你的生活产生积极的影响。虽然软件性能远远不是这些事情的唯一决定因素,但与支持开源和自由软件许可的观点相比,它被忽略了,因为开源和自由软件许可让最终用户可以选择审计或更改软件。高性能的软件使得以你想要的方式运行你的代码成为可能。
在我的神经影像领域,我之前提到的简笔画骨架是分析中的一个基本结构。如果在有用的大型数据集上大量创建它们需要数万到数十万美元的成本,并且需要云合同,那么这项任务将被限制在资金最充足、人脉最广的玩家身上。即使是仁慈的科学家也只有有限的时间、注意力和金钱来帮助他人。通过将成本降低两到三个数量级,突然之间更小的实验室、研究生和好奇的局外人都可以自己完成。
环境影响呢?
气候危机在很大程度上威胁着人类文明,而电力经常由化石燃料产生。然后,电力被计算机代码的运行所消耗。根据其 2020 年环境报告,仅谷歌一家在 2019 年就使用了 12.8 太拉瓦时,超过许多国家和超过几个美国州。提高代码效率应该会减少对环境的影响,这似乎很直观。是吗?
答案一点也不清楚,必须逐个系统地考虑。主要原因是诱导需求。
诱导需求甚至不是一个偶然的副作用。我们提高软件效率的一个主要原因是为了让它更适合在更多的环境中更频繁地运行。如果你把一个图书馆的速度提高了 10 倍,但它的运行频率却提高了 1000 倍,从环境的角度来看,你真的有所收获吗?如果代码提高了 100 倍,但运行频率只有 10 倍,这就是胜利。诱发需求与改善绩效的比率必须小于 1,才能对环境有利。有时,快速代码会使下游代码运行得更加奢侈,在这种情况下,即使是极低的比率也可能不会带来好处。
工程师和科学家应该设计出能够在大工业规模上运行的程序,以避免不必要的过度。然而,只要组织还在生存和发展,他们就会想方设法用尽空闲的数据中心容量,这是组织的本性。如果所有团队都编写高性能代码,它将调整增长轨迹,但我怀疑它通常不会走向萎缩。
因此,关于购买能力和消费者基础的规模和胃口的决定是环境影响的更强驱动力。如果只有有限的能力可用,一些代码将被改进,一些计划将被搁置,而其他的将被缩减。换句话说,总能耗不会受到单个工程师的强烈影响,除非在特殊情况下,例如在移动或嵌入式设备中频繁运行的程序。
尽管如此,如果使用的能源是 100%可再生的,我们应该担心吗?一些大公司如谷歌声称他们已经过渡到 100%的可再生能源,或者承诺这样做,如 T2 微软 T3。有趣的是,两者都承诺最终会变成负碳排放,大概是通过购买碳抵消。
缩小来看,美国整体能源生产的组合在几年或几十年内变化缓慢。有一些方法可以做到真正的绿色环保,比如开发一个闲置的资源,比如一个与电网断开或利用不足的水电站。另一种方法是在不干扰其他建设的情况下建设新的发电设施(例如,通过用尽有限的部件或劳动力)。不过,总的来说,馅饼在某一年是合理固定的。因此,在缺乏这些因素的情况下,任何声称运行可再生能源大型工业流程的组织都只是重新分配一块缓慢变化的馅饼,并迫使其他市场参与者使用肮脏的能源。
在下面由美国能源信息署提供的图表中,美国的能源结构变化非常缓慢,主要变化发生在大约十年的时间里。最大的变化是在过去十年里,天然气相对快速地取代了煤炭。从 2007 年开始,总发电量在稳定增长了至少 60 年后基本趋于平稳。可再生能源和核能在目前的组合中约占 40%,天然气约占 40%,煤+汽油约占 20%。
许可:美国政府出版物,公共领域
在某种程度上,购买可再生能源确实刺激了额外的可再生能源市场。然而,公司可以开始声称他们正在使用可再生能源的事实可能更证明了一个真正乐观的消息,即大量新的可再生能源发电即将上线。个别组织声称使用 100%可再生能源,主要是为了让员工、供应商、投资者和客户对他们所做的事情感觉良好,而没有看到更大的画面。
消费者的选择,甚至是企业的选择,在改善我们命运的能力上是有限的。我认为,要解决气候问题,需要在国家和国际层面开展一个集中协调的进程,将能源分配给不同的工业和消费部门,并代表整个社会管理能源生产组合。只有这样,我们才能用一种不依赖运气的理性方式来解决这个问题。
公平地说,软件公司不是电力公司,期望他们建立自己的一代有点可笑。我们能从他们那里得到的更多的期望是支付 税收,从持有证券的化石燃料公司中撤资,并在社会尽快改变能源结构的同时,将他们的总能耗控制在监管机构设定的上限之下。
高性能代码带来的好处是相当可观的,无论是对开发它的人还是对使用和适应它的人来说都是如此。与直觉相反,开发高效的代码并不总是对环境有益,所以如果没有仔细的上下文相关的推理和测量,环境保护论就不应该被用作一个理由。
Spark 上变压器模型的高性能推理
使用 PySpark、Hugging Face 和 AWS GPU 实例的代码教程
想要通过拥抱脸或 Tensorflow 模型实现高达 100 倍的速度提升并节省 50%的成本吗?借助 GPU 实例和 Spark,我们可以同时在两个或数百个 GPU 上运行推理,从而毫不费力地获得更高的性能。
作者使用 Canva 的许可内容制作的图片
概观
- 设置驱动程序和工作实例
- 为并行化对数据进行分区
- 使用变压器模型进行推理
- 讨论
设置驱动程序和工作实例
对于本教程,我们将使用数据块,如果你还没有一个免费账户,你可以注册一个。请注意,DataBricks 将需要连接到云托管提供商,如 AWS 、谷歌云平台或微软 Azure 来运行 GPU 实例。
在本练习中,我们将使用" g4dn.large "类型的 AWS GPU 实例。如果您使用 Google Cloud 或 Microsoft Azure,并且从它们中选择了等效的 GPU 实例,您仍然可以遵循这些说明。
一旦您的 DataBricks 帐户设置完毕,登录并创建一个集群,配置如下所示:
配置一个虚拟命名为“gpu_cluster”的 GPU 集群
接下来,创建一个笔记本,通过在下拉菜单中选择它将其附加到集群:
设置笔记本的群集
现在,我们都开始编码了。
安装拥抱脸变压器
首先,让我们将支撑面变压器安装到组合仪表上。
在笔记本的第一个单元格中运行:
%pip install transformers==4.2
拥抱脸变形金刚 Python 库安装在集群上
这样安装的库被称为笔记本范围的 Python 库。这很方便,而且必须在会话开始时在其他代码之前运行,因为它会重置 Python 解释器。
现在,我们从实际的 Python 代码开始。在下一个单元格中,运行:
如果上面一行运行没有任何错误,恭喜你,拥抱脸变形金刚安装成功。
为并行化对数据进行分区
在 Spark 中,创建可并行处理的数据的最简单方法是创建 Spark 数据帧。对于本练习,包含两行数据的数据帧就足够了:
显示创建的火花数据帧。
本练习的 Transformer 模型每行接受两个文本输入。我们在这里把它们分别命名为“标题和“摘要”。
出于好奇,这里有一篇 Laurent Leturgez 的精彩文章,深入探讨了 Spark 分区策略:
https://medium.com/datalex/on-spark-performance-and-partitioning-strategies-72992bbbf150
使用变压器模型进行推理
我们将为 PySpark 使用奇妙的熊猫 UDF 来处理内存高效分区中的 Spark 数据帧。数据帧中的每个分区都作为 Pandas 数据帧呈现给我们的代码,您将在下面看到,作为函数" embed_func 的参数,它被称为" df "Pandas 数据框架使得用 Python 处理数据变得很方便。
定义 embed_func(df) 的代码
您可能已经从上面的代码中注意到了两件事:
- 代码进一步将 Pandas 数据帧中的输入文本分割成 20 个块,如变量" batch_size 所定义的那样
- 我们使用 AllenAI 的Spectre—一个预训练的语言模型来生成文档的文档级嵌入(此处预打印)。)请注意;然而,我们可以很容易地把它换成另一个拥抱脸模型,比如伯特。
绕开 GPU 内存限制
当 GPU 被用于对这个拥抱面部变形器模型进行推理时,输入和输出被存储在 GPU 存储器中。GPU 内存是有限的,尤其是大型变压器模型需要大量的 GPU 内存来存储其参数。这留下了相对较少的内存来保存输入和输出。
因此,我们通过一次推理 20 行来控制内存使用。这 20 行完成后,我们将输出复制到驻留在 CPU 内存中的 NumPy 数组(CPU 内存更丰富)。这是在上面第 21 行用“”完成的。cpu()。分离()。numpy() ”。
最后,在 GPU 上进行实际变压器模型推理
如上所述,这就是为 PySpark 处决熊猫 UDF 的地方。在这种情况下,熊猫 UDF 就是“ embed_func 本身。请仔细阅读上面的链接,了解关于这个强大的 PySpark 特性的更多信息。
这个练习的结果输出——Spectre 给出了文档嵌入的 768 长的浮点数组。
讨论
我希望您能看到 Spark、DataBricks 和 GPU 实例如何使大型 transformer 模型的扩展推理变得相对简单。
这里展示的技术使得对数百万行进行推理成为可能,并在几个小时内完成,而不是几天或几周。这使得在更多的情况下对大量数据运行大型变压器模型变得可行。
成本节约
但是等等,还有更多。尽管成本是 CPU 实例的 5 到 20 倍,但由 GPU 实例完成的推理实际上更具成本效益,因为它的速度快 30 到 100 倍。
因为我们是按小时付费的,所以在这里时间就是金钱。
花在管道上的时间更少
数据可以很容易地导入到 DataBricks 中,并保存为 AWS S3 桶上的拼花文件,或者更好的是,数据湖表(又名类固醇蜂箱表)。之后,它们可以作为 Spark 数据帧进行操作,正如本文所见,对于转换和推理来说,并行化是微不足道的。
所有数据、代码和计算都可以在一个地方“云上”访问和管理,更不用说它在本质上是可扩展的,因为数据从千兆字节增长到千兆字节,这使得这种简洁的解决方案更加“经得起未来考验”
无缝协作
作为基于云的解决方案,意味着随着团队的发展,我们可以向项目中添加更多的人员,以安全地访问笔记本电脑上的数据和代码。我们只需点击几下鼠标,就可以为报告创建图表并与其他团队共享。
请继续关注 Tensorflow 的这篇文章以及我计划的更多文章。如果你觉得这有帮助,请跟随我,我是一个新的作家,我需要你的帮助。如果你有任何想法和问题,一定要发表出来。
使用通用制图工具的现代界面制作高质量地图
毫无疑问,在地球物理学领域工作的最喜欢的部分之一就是创造惊人的可视化效果。可视化是将我们的发现有效传达给科学界的最佳工具。
GMT 或通用绘图工具已经成为绘制地球、海洋和行星科学地图的同义词。它可以用于处理数据,生成出版物质量的插图,自动化工作流程,甚至制作令人惊叹的动画。GMT 的另一个优点是,它支持许多地图投影和变换,并包括支持数据,如海岸线、河流和政治边界,以及可选的国家多边形。
我已经在多篇帖子中谈到了 GMT 5 以及如何使用它绘制高质量的地图。我还详细讨论了 GMT 6 的 Python 接口 PyGMT。侏儒在引擎盖下使用 GMT 6 完成所有任务。
在这篇文章中,我的目标是向你介绍使用 GMT 6 创建简单地图的基础知识,并让你熟悉语法。GMT 6 中的大多数风格几乎与 GMT 5 相同,除了编码语法有了显著的改进。它变得更有组织性,可以用更少的代码完成更多的工作。它增加了使用有意义的完整命令的选项,而不仅仅是别名。当我们用一些例子来讨论这个问题时,它会变得更加清楚。
https://www.earthinversion.com/utilities/GMT-advanced-II/
安装 GMT
要安装 GMT,您可以遵循这里的步骤安装所需的软件。
我使用 Ubuntu(作为 Windows 子系统— WSL),所以我可以简单地使用 conda 软件包管理器进行安装。参见本。对于任何 Linux 或 Unix 操作系统,步骤都是相似的。
我们的代码将被写入bash
。我在这里假设你对bash
有基本的了解。但是即使你对bash
不太熟悉,你仍然可以跟着我做,因为我会试着让这个脚本“准备好生产”,这样你就不需要学习太多就可以完成本教程中的任务。
第一眼
GMT 6 的第一个不同之处是以下语法:
gmt begin [session-name]
[graphics-formats] <LIST OF COMMANDS>
gmt end [show]
上述语法仅适用于 GMT6,并且不向后兼容。因此,您不会意外地运行 GMT 版本< 6。GMT 时段以gmt begin
开始,以gmt end
结束。可选地,如果输出格式为graphics-formats
,您可以提供将用于输出的会话名称。如果您不提供session-name
或graphics-formats
,那么将使用默认值。如果您在gmt end
处选择了show
,该图将不会被保存,而是仅被显示。
如果您想快速浏览文档,可以通过键入
这将打开本地 GMT 文件,所以你不需要互联网。
第一个情节
gmt begin taiwan pdf,png
gmt coast -RTW -Wthin
gmt end
上面的脚本绘制了一张台湾的海岸线地图,并以 pdf 和 png 格式保存。png 是光栅图像格式,期刊要求出版。如果你想要一个矢量图像,pdf
给出了矢量格式。也支持其他矢量格式,如ps
或eps
等。
使用 GMT6 的台湾底图(图片由作者提供)
我们可以使用-B
选项将默认框架添加到绘图中。我们可能还想将图形从框架边界偏移一点。我们可以通过简单地将+r0.5
指定给-RTW
来告诉 GMT 将台湾地图偏移0.5
度来实现快速偏移。
使用 GMT6 的带框台湾底图(图片由作者提供)
我们可以通过简单地指定地图边界而不是使用TW
作为区域来得到上面的图。这给了我们更多的绘图控制。
gmt begin taiwan pdf,png
gmt coast -R119/122.5/21.5/25.5 -Wthin -B
gmt end
在这里,您可能已经注意到,我们没有指定地图的任何投影。上面的图使用默认投影进行绘制。在科学中,出于大多数实际目的,我们使用墨卡托投影。要使用墨卡托投影,我们可以在-JM
之前告诉 GMT 我们想要它,然后我们可以指定地图的宽度,-JM15c
例如,对于 15 厘米的地图。
给土地和水填充颜色
接下来,我们可以填充一些颜色到地图上,使它更有吸引力。
gmt begin taiwan png
gmt coast -R119/122.5/21.5/25.5 -Wthin -B -JM15c -Gbeige -Slightblue
gmt end
我们用-G
指定土地的颜色,用-S
指定水彩。您可以在“获取颜色”列表中查找更多颜色。简单地运行命令gmt docs gmt colors
。
带有陆地和海洋颜色的台湾底图(图片由作者提供)
地图的插图
现在,让我们试着在上面的图上画另一张地图作为插图。我想把插图放在地图的左上角,宽度为 4 厘米。在插图中,我想在世界地图上展示台湾。
gmt begin taiwan png
gmt coast -R119/122.5/21.5/25.5 -Wthin -B -JM15c -Gbeige -Slightblue
gmt inset begin -DjBL+w4c+o0.2c -M0 -F+gwhite+pthick
gmt coast -Rg -JG120.5/23.5/4c -Gbeige -Slightblue -B -ETW+gred
gmt inset end
gmt end
让我们看一下上面代码的每一部分。我们通过使用“上下文管理器”gmt inset begin
开始插图,并在脚本的插图子部分结束插图。我们使用-Dj
指定我们想要使用对齐方法来指定插入的位置,并将位置设置为“左下角”(BL
)。我们想要宽度为 4 厘米、偏移量为 0.2 厘米的地图。此外,我们指定,我们不想要空白(M0
)和框架的背景是白色的,框架边界与粗线。
我们用和以前一样的方法在插图中绘制海岸线地图。此外,我们告诉 GMT 用红色突出显示台湾(-ETW+gred
)。
嵌入世界地图的台湾彩色地图(图片由作者提供)
GMT 中的支线剧情
现在,让我们看看如何使用 GMT 来制作带有多个支线剧情的图形。
gmt begin subplotMap png
gmt subplot begin 2x2 -Ff16c/25c -M0 -A
#figure a
gmt coast -R119/122.5/21.5/25.5 -BNWse -Wthin -Gbeige -Slightblue #figure b
gmt coast -R119/122.5/21.5/25.5 -BswNE -Wthin -Gbeige -Slightblue -c
gmt grdcontour @earth_relief_01m -Wared -Wcthinnest,blue -C1000 -A2000 #figure c
gmt coast -R119/122.5/21.5/25.5 -BnWSe -Wthin -Gbeige -Slightblue -c
gmt grdcontour @earth_relief_01m -LP -Wared -C1000 -A2000
gmt grdcontour @earth_relief_01m -Ln -Wablue -C2000 -A4000 #figure d
gmt coast -R119/122.5/21.5/25.5 -BSwne -Wthin -Gbeige -Slightblue -c
gmt makecpt -Cabyss -T-10000/0 gmt coast -Sc #clip out the land part
gmt grdimage @earth_relief_01m -C -I+d
gmt coast -Q
gmt colorbar -DJBC -B2000
gmt makecpt -Cgeo -T0/5000
gmt coast -Gc
gmt grdimage @earth_relief_01m -C -I+d
gmt coast -Q
gmt colorbar -DJRR -B1000
gmt subplot end
gmt end
四人台湾支线剧情图。(a)台湾彩色底图(b)带等高线的台湾地图带等高线的台湾地图-陆地和海洋分别绘制(d)台湾地形图(图片由作者提供)
现在让我们看一下上面的脚本。
我用 4 个支线剧情(2 x 2)绘制了支线剧情。我可以通过简单地输入2x2
来指定。每个子情节的尺寸由参数-Ff16c/25c
指定。我们要求每个支线剧情宽 16 厘米,长 25 厘米。通过参数-A
,我们要求 GMT 自动注释支线剧情。可以使用-c
参数指定到下一个子情节的导航。
接下来,我在支线剧情里做了四个数字。
图(a)只是我们在上一节中所做的,但是这里我们只在顶部和左侧放置了刻度线。
在图(b)中,我们绘制了分辨率为 1m 的地形等高线。我们用红色绘制了带注释的等高线,用蓝色绘制了常规等高线。规则等高线以最细的线宽绘制。每隔 1000 米绘制一次等高线,每隔 2000 米进行一次标注。
在图中,我们分离了陆地和海洋部分的等高线。这可以简单地用参数-L
后跟P
或N
来指定。P
代表阳性,N
代表阴性。大写字母包含 0,反之亦然。因此,我们用红色绘制了正的标注等高线,用蓝色绘制了负的标注等高线。
在图(d)中,我们用两个色带绘制地形图。对于该图,我们也从海岸线开始(注意,这不是必需的,您可以简单地跳到下一步)。然后我们基于abyss
标准色图创建了我们的自定义色图,但是指定了-10000
到0
之间的范围。然后我们剪下陆地部分,并为海洋部分绘图。接下来,我们做了同样的事情,剪去了海的部分,我们用不同的颜色绘制了陆地的部分。最后,我们分别为陆地和海洋部分绘制色带,一个在bottom-center
处,另一个在右角。
结论
人们可以用 GMT 做更多的事情。我会在以后的文章中尝试解决这个问题。更多 GMT 相关的例子,可以看看我博客里的其他帖子。我希望这篇教程能在你的努力中派上用场。
参考
原载于 2021 年 4 月 2 日【https://www.earthinversion.com】。
自然语言处理中使用变形器的高质量句子解释器
在定制数据集和 T5 大型模型上训练的开源解释工具
作者图片
注意:这个解释器被训练来解释简短的英语句子,最适合这些输入。
投入
我们程序的输入将是任何英语句子
**Four private astronauts launched to orbit by Elon Musk’s SpaceX returned to Earth Saturday evening, splashing down into the ocean off the east coast of Florida after a three-day mission.**
输出
输出将是转述版本的同一句话。解释一个句子意味着,你创造一个新的句子,用一个不同的词语选择来表达与 T4 相同的意思。
**After a three-day mission, four private astronauts sent by Elon Musk's SpaceX returned to Earth on Saturday evening, splashing down into the ocean off the east coast of Florida.**
实际使用案例
从重写你以前的社交媒体文章到大学论文,当你没有很多文本分类模型的例子时,有几个解释器的用例。
资料组
作为构建 Questgen.ai 的一部分,我们创建了一个在 ParaNMT 之上过滤的自定义数据集,以仅保留多样化的高质量释义。
这里的多样性意味着句子对的选择使得在词序上有显著的差异,或者至少转述输出由于多个单词变化而不同。
怎么用?
如果你喜欢易于使用的谷歌 Colab 笔记本,可以在 Questgen 的 Github Repo 找到。
1.装置
**!pip install transformers==4.10.2
!pip install sentencepiece==0.1.96**
2.运行代码
我们将使用上传到 HuggingFace Transformers 库中心的预训练模型来运行解释器。我们将使用不同的波束搜索解码策略,为释义输出提供最佳结果。
更多的例子可以在上面提到的 Google Colab 演示中找到。
from transformers import AutoTokenizer, AutoModelForSeq2SeqLMmodel = AutoModelForSeq2SeqLM.from_pretrained("**ramsrigouthamg/t5-large-paraphraser-diverse-high-quality**")
tokenizer = AutoTokenizer.from_pretrained("**ramsrigouthamg/t5-large-paraphraser-diverse-high-quality**")import torch
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print ("device ",device)
model = model.to(device)**# Diverse Beam search**context = "**Once, a group of frogs was roaming around the forest in search of water.**"
text = "paraphrase: "+context + " </s>"encoding = tokenizer.encode_plus(text,max_length =128, padding=True, return_tensors="pt")
input_ids,attention_mask = encoding["input_ids"].to(device), encoding["attention_mask"].to(device)model.eval()
diverse_beam_outputs = model.generate(
input_ids=input_ids,attention_mask=attention_mask,
max_length=128,
early_stopping=True,
num_beams=5,
num_beam_groups = 5,
num_return_sequences=5,
diversity_penalty = 0.70)print ("\n\n")
print ("Original: ",context)
for beam_output in diverse_beam_outputs:
sent = tokenizer.decode(beam_output, skip_special_tokens=True,clean_up_tokenization_spaces=True)
print (sent)
上述代码的输出是:
原文:有一次,一群青蛙在森林里四处游荡寻找水源。一群青蛙在森林里四处游荡寻找水源。一群青蛙在森林里四处游荡寻找水源。一次,一群青蛙在森林里四处游荡寻找水源。一群青蛙在森林里游荡寻找水源。一群青蛙在森林里四处游荡,再次寻找水源。
就是这样!你有一个高质量的最先进的句子解释器,你可以在你的项目中使用。需要注意的是,转述者的输出并不总是完美的,因此人在回路中的系统可能是必要的。
祝 NLP 探索愉快,如果你喜欢它的内容,请随时在 Twitter 上找到我。
如果你想学习使用变形金刚的现代自然语言处理,看看我的课程使用自然语言处理的问题生成
高质量的世界范围的增强现实越来越近了——部分是因为机器学习
美国宇航局在 Unsplash 拍摄的照片
由于 GPS 的不精确和定位,在我们的物理世界上覆盖一个数字世界一直是很困难的。
增强现实介绍
广义而言,日常消费者创建并随后使用了两种类型的增强现实(AR)。
第一种类型是 AR,它检测你附近的物体,从中产生数字物体/世界。这可以是从平面检测到图像目标的任何东西,其中您已经设置了在满足检测标准后如何产生游戏对象的参数。
在谷歌 ARCore 教程 (CC BY 4.0)中的平面检测(绿色和紫色网格)和从 HelloAR 衍生的 Android
在这里你可以看到图片和模型被放置在 Unity 游戏引擎中。当你的手机检测到图像时,这些模型就会产生
最受欢迎的是,这第一种 AR 使用面部识别来给你提供你在 Snapchat、Instagram 甚至 Zoom 上看到的众多滤镜。Spark AR 是一个很棒的库,可以用你的 PC/Mac 相机来玩这个。你可以在这里看到用于 Snapchat AR 滤镜的面部网格,类似于它在 Instagram 或任何其他具有过滤功能的平台上的使用方式。
第二种形式的 AR 检测你的物理位置,并在其上覆盖一个缩放的、预先构建的数字世界。这通常被称为世界规模的 AR 。这方面最好的直接例子是 Pokemon GO,其中特殊事件、pokestops 和健身房将显示在特定的地理坐标上。你去那里,调出第一人称视角,然后做你的事。但是,世界规模的 AR 计划并不局限于游戏,它们包括室内导航和多人单次 AR 活动的圣杯等用例。
通过地图框进行室内导航,图片来自媒体文章
尚未发布的 Niantic 多人 AR 游戏 Neon 的测试片段
世界规模的 AR 有什么难的?
如果你有敏锐的眼光,我相信你已经注意到最后两帧中的某些东西似乎有点不对劲。物品的放置和标记并没有完全符合它们应该达到的水平——这就让我们想到了为什么 AR 还没有完全达到它应该达到的水平:数字和物理世界的一致。
假设我在洛杉矶,正在寻找我停车的地方。我有一个应用程序 FindMyCar,当我打开相机时,它会指向我最初停车时放下书签的地方。信号可能不太好,所以**我的全球定位系统显示我正朝向我实际朝向的东方 10 度。**现在在应用程序中,我面前的数字汽车标记现在将移动 10 度,并且物体越远,情况越糟。
作者图片,谷歌数学
所以,如果我在找我停车的地方,那就没什么用了。这也是其他两个应用程序在早期照片中似乎关闭的原因。对于一些像 Pokemon GO 这样的世界范围的应用程序,位置可以稍微偏一点,因为如果 Charizard 稍微偏左或偏右,不会影响你的沉浸感。沉浸感对用户体验和应用程序的有用性都非常重要。
如果你想看看 Matthew Halberg 如何在 2017 年试图建立一个基于户外导航/文本的 AR 应用程序,并对遇到的问题进行更深入的解释,请在这里查看他的旧视频。回购是不赞成现在,所以不要费心尝试启动它。
世界规模的 AR 的第二大问题是本地化——或者通俗地说,检测你周围的世界,并与你在数字世界中的位置同步(想想 x 和 y 位置)。在上面的例子中,这相当于距离我实际所在的位置大约 200 英尺,使失调误差更大。这通常也需要时间,你必须将手机指向你周围的不同方向,以帮助检测足够多的表面来催生数字世界。
对我们来说幸运的是,多年来聪明的工程师和产品团队解决这个问题已经带来了很多有趣的解决方案。
变大还是变小
正如生活、商业和技术中的许多问题一样,人们要么试图一次解决所有问题,要么从整体中分离出一部分。这个问题的处理方式没有什么不同。
我们的第一个方法是“让我们以 1:1 的比例开始建造这个房间”的方法,本质上是创建这个房间中所有物品的完美克隆,以创建一个不受地理位置限制的无缝体验。第二种方法是“世界数字克隆”方法,它专注于将数字体验从地理上映射到现实世界。
让我们更深入地研究这两种方法:
重建您的房间,体验您的房间
比方说,我正在为新生计划一次寻宝游戏,以帮助他们更好地适应校园主楼。如果我有十组学生,我可以隐藏十组物品或找到一百个隐藏点——或者我可以在 AR 中建立一个更有趣和可扩展的体验!
在室内寻宝游戏中,为了获得准确的体验,我们需要的不仅仅是一个云锚。那么,如果我们先把所有的提示和隐藏物品放在建筑物的数字模型中,会怎么样呢?
你可以走进 Blender,手动精心制作每个房间(和里面的家具)的模型——或者我们可以将物体扫描成点云/数字网格。几年前,6D.ai 的团队使用手机在 3 分钟内扫描了他们的办公室,生成了以下网格:
看原视频这里
显然,它并不完美,物体的边缘越粗糙或越细致,就越难扫描。幸运的是,用户永远不会看到这个扫描——它只是用于本地化。假设我们为校园建筑做了这些,当学生在任何房间内启动应用程序时,应用程序将尝试扫描周围环境,并将自己放置在数字模型中。通过人工智能和激光雷达等景深传感器的结合,手机将快速扫描周围环境,并“识别”学生站在哪里。然后,无论我们把寻宝游戏的提示和物品放在哪里,它们都会出现在屏幕上——没有杂乱的扫描网。
2020 年发布的 iPad Pro 配备了激光雷达,为更快的平面检测和 AR + ML 应用的定位打开了大门。
实时扫描内摄像机范围内的一切。
因此,现在我们有了建立数字模型以及在该模型中定位用户的解决方案,但我们需要一个平台来建立完整的寻宝游戏(放置物品、提示和各种交互)。Unity 是一个强大的游戏引擎,最近专注于建立一套跨机器学习和 VR/AR 的更多样化的应用程序。最近推出的一个功能叫做 Unity AR Mars ,专为特定空间的活动打造,比如我们已经详述的寻宝游戏。
在这里我们看到了房间的数字模型和在顶部放置的互动对象。当用户打开应用程序时,只会看到该对象。
但是如果我想在整个校园里创建一个寻宝游戏呢?扫描包括户外在内的所有东西是不可行的,而且有些房间看起来可能非常相似。我们需要在应用程序中融入地理信息。
最好用别针别住它,快点
您是否曾针对您所在的特定事件或地点使用过 Snapchat 过滤器?这些基本上是作为一个可用的地理区域来实现的,也称为“栅栏”。
这是 Snapchat 网站上的一个截图,你可以自己试试这里
然而,对于需要精确定位的 AR 应用程序来说,绘制围栏不起作用——在停车场上设置围栏并不能帮助我找到我的车。在这种情况下,我们需要使用云锚来代替。
老实说,云锚设置起来还是有点笨拙,因为文档和好的例子都很分散。然而,旧主播只能持续一天,而现在新主播可以持续一年!本质上,您正在扫描一个区域(比如一个雕像),然后将它附加到一个地理点(在纬度和经度坐标中)。对于一个更微妙的实现,查看谷歌的解释者这里。
为了帮助放置锚点,特别是如果像 Pokemon GO 那样由算法驱动,可以通过管理适当位置的数据集来找到合理的放置。Niantic 已经利用来自 wayfarer 等应用和倡议的数据做到了这一点,最终形成了一个“最全面的数据集,包含数百万个世界上最有趣、最容易玩游戏的地方,这些地方几乎都是用户生成的( 来源 )。
两全其美
虽然这些方法解决了不同的问题,但最终它们会在构建像寻宝游戏这样的新 AR 应用程序时结合使用。让我们快速看一下实现中的两个例子:
口袋妖怪 GO 前面已经提到过了,尽管这主要是一种共享游戏状态的单人 AR 体验。Niantic 即将推出的游戏代号:城市传说确实包含了多人游戏(可能是从前面提到的 Neon 开发的)。
Superworld 是区块链(以太坊)上的一款房地产应用,每块土地都可以在上面建立 AR 体验。这意味着每个地块都将有自己的云锚,从视频和海报到 3D 动画模型的一切都可以由地块所有者放置在它周围的数字覆盖中。
作者截图来自超世界手机 app
构建这一切的合理堆栈可以是 Unity 作为游戏引擎,Mapbox 用于地理数据/模型,AR Core 用于云锚 API,6D.ai(现在是 Niantic 的一部分)用于扫描。本地化在很大程度上取决于硬件(你的手机有景深能力吗,它能处理 5G 吗,量化模型与完整模型的优化,因为云端点会太慢)。
我们将何去何从?
最终,我们希望准确快速地覆盖数字世界。这确实是一个艰难的要求,但回报将是值得的努力和等待。随着 GPS、5G 和电话硬件以及 AR 工程工具的未来改进,我绝对迫不及待地想在未来体验和构建世界规模的 AR 应用。
通过“完美”从高方差到高偏差
过拟合和欠拟合是非常常见的问题,我们已经指定了处理它们的方法和工具。虽然,所有方法背后的基础科学是相同的,也值得一提。
伊莎贝拉和路易莎·菲舍尔的照片在 Unsplash
数据科学社区获得了许多托管大量预测建模问题的平台。这简化了初学者在这一领域超越和达到熟练程度的途径。我们不打算谈论这些平台,而是谈论一些让我们结束训练“最优模型之旅的东西。术语“最佳”在这里意味着模型的精度类似于基础精度。
我们在训练模型时面临的最常见问题是数据的过拟合和欠拟合。在某种程度上,我们有能力控制它,但深度学习中的这些能力没有被我们大多数人发现。然而,这篇文章不仅仅是关于处理数据问题的方法,而是关于为什么这些技术如此强大以及它们在后台做什么。
为了顺利开始,让我们定义一些用于从一组图像中对狗进行分类的预测建模问题的术语。
模型性能类型的不同术语(由作者出版)
我们假设基本误差为零,因为人类可以识别误差为 0%的狗。或者在它的附近。要理解这篇文章,有必要熟悉这些术语(先决条件!!).
要深入了解这些术语,让我们看看这些模型所映射的决策边界或函数因为: 要解决任何问题,都需要先了解它。
有一个小假设,我们只有两个特征,这将有助于我们在 2-D 平面中绘制数据点。
描述数据拟合不同问题的三种不同模型的决策边界(由作者发布)
最佳模型
该模型精确地拟合了给定的数据点,并且还包含了数据中的噪声。这个模型所映射的函数既不复杂也不简单。让我们假设它相当于一个**抛物线函数,**其中训练和测试集误差也相当于基本误差。
欠拟合
当训练集误差和测试集误差远离基本误差时,就说模型对数据拟合不足。由该模型映射的函数作为**线性函数相当简单,**因此没有抛物线函数复杂。
过度拟合
当测试集误差远离基本误差,而训练集误差远离基本误差时,则该模型被认为过拟合了数据。这个模型映射了一个比抛物线更复杂的函数。
这些对映射函数的观察对于理解数据问题至关重要。在深度神经网络中,我们有几个超参数要调整。一些超参数,例如隐藏层的数量和隐藏层中隐藏单元的数量,决定了模型在给定数据点上映射的函数的复杂性。
让我们假设我们已经训练了一个非常深的神经网络,并且我们有过拟合数据的条件,并且正在使用 L2 正则化技术来达到最佳拟合。
L2 正则化
我们的价值成本函数如下
二元分类问题的梯度下降中定义的代价函数(由作者发表)
其中 J 是成本函数,带上限的 y 是预测值,y 是第个训练示例的目标特性的实际值。在 L2 正则化中,我们在代价函数中增加了一个正则项。所以,J 变成了-
带有正则项的成本函数(由作者发表)
其中λ被称为正则化参数,因此是另一个超参数,求和项是权重矩阵的 L2 范数的平方。上图给出了 L2 范数的定义。
为了理解这个正则项的影响,让我们计算梯度,这是在反向传播过程中计算的。
反向传播步骤中计算的权重梯度(由作者发表)
“反向投影项对应于成本函数相对于权重矩阵的导数。我们可以假设,对于梯度下降的特定迭代,它将保持不变。添加正则项的含义是在关于λ和m的 dW 的定义中添加新项。现在,如果我们更新参数,我们将具有以下含义:
更新权重(由作者发布)
可以直接推断出,当我们应用 L2 正则化时,我们正在减少或最小化 w 中的元素。在大多数处理高方差等问题的方法中,我们正在减少权重。这就是为什么它也被称为“重量控制”过程。
如果我们有一个非常深的神经网络,有许多隐藏层和隐藏单元,那么它将倾向于过度拟合数据。一旦我们应用 L2 正则化,我们就间接地将一些隐藏单元的权重降低到接近零,但不完全为零。
现在假设我们有一个如下的密集神经网络,我们应用 L2 正则化,几乎关闭了一些隐藏单元。
在成本函数中添加正则项后关闭的神经元(由作者发表)
这是一个完全关闭一些神经元的极端条件,但将有助于我们理解 L2 正则化的含义。现在剩下的神经网络可以组合如下-
多个连续的神经单元可以由单个神经单元代替。(作者发布)
这是可能的,因为每个神经元随着权重和偏差线性变化,如果我们在另一个线性函数中输入一个线性函数,那么得到的函数也是线性的。最终,我们只是改变了线性函数的常数。
这种极端情况意味着,当我们应用 L2 正则化技术时,从一个非常复杂的函数(由密集的神经网络生成),我们得到了一个非常不复杂的线性函数。换句话说,从一个模型过拟合数据到一个模型欠拟合数据,或者从一个具有高 方差的模型到一个具有高偏差的模型。
另一个观察结果是,我们问题的最佳解决方案位于我们从过度拟合到欠拟合数据的路径之间。这个目标可以通过调整超参数 lambda 轻松实现(不那么容易,因为调整超参数需要耐心)。
通过“完美”(由作者发表)从高方差到高偏差
还有其他正则化技术,如反向丢弃(或简单丢弃)正则化,它们随机关闭神经单元。所有这些正则化技术都在做同样的工作,即最小化成本函数或映射函数的复杂度。
“提前停止”方法述评
“早 停”是另一种常用的避免数据过拟合的方法。在这种方法中,我们定义了一个训练集和一个测试或开发集,并观察了这两个集上的成本函数在迭代次数方面的变化。
随着迭代次数的增加,训练集中的错误会减少,但是测试集中的变化是显著的。它首先下降,显示成本值的增加,表明数据过度拟合的开始。
训练和测试集的成本随迭代次数的变化(由作者发布)
这种方法可能不太准确,因为我们有两个不同的任务,一是优化成本函数 J,二是防止过拟合。这两项任务必须分开解决。“提前停止”同时完成了这两个任务,因此它有效地优化了成本函数。
通过使用其他技术作为正则化,您将能够更加自信和准确地优化您的成本函数。
常见的解决方案
解决这两个问题的常用方法是提供更多的训练数据集。有时候,获取更多的训练数据可能代价很高。但是,有一些方法,如数据扩充,可以生成更多的训练数据集。
例如,我们可以通过随机缩放、裁剪或翻转图像,从预先存在的图像生成新图像。
数据扩充(原始照片由维克多·格拉巴奇克在 Unsplash 上拍摄)
总结
我们已经看到了如何在从高方差到高偏差模型的过程中达到最优模型。
然而,我们应该注意,L2 正则化将权重衰减到接近零,但不完全为零,而在丢弃正则化中,我们随机关闭一些单元。
除正则化之外,还有各种技术,如归一化、梯度检查等,可帮助您优化成本函数并防止数据过度拟合,但所有这些方法都在同一个框中,即它们都降低了成本函数或模型在数据点上映射的函数的复杂性。一旦复杂度降低到一定限度,就可以找到最优解。
Spark 3.1 的高阶函数
在 Spark SQL 中处理数组。
唐纳德·詹纳蒂在 Unsplash 上的照片
复杂的数据结构,如数组、结构和映射在大数据处理中非常常见,尤其是在 Spark 中。每当我们希望在一列中表示每行上的多个值时,就会出现这种情况,在数组数据类型的情况下,这可以是一个值列表,在映射的情况下,这可以是一个键值对列表。
从 Spark 2.4 开始,通过发布高阶函数(Hof),对处理这些复杂数据类型的支持增加了。在本文中,我们将了解什么是高阶函数,如何有效地使用它们,以及在最近几个 Spark 和 3.1.1 版本中发布了哪些相关功能。对于代码,我们将使用 Python API。
继我们在上一篇文章中提到的聚合和窗口函数之后,HOFs 是 Spark SQL 中另一组更高级的转换。
让我们首先看看 Spark 提供的三种复杂数据类型之间的区别。
数组类型
l = [(1, ['the', 'quick', 'braun', 'fox'])]df = spark.createDataFrame(l, schema=['id', 'words'])df.printSchema()root
|-- id: long (nullable = true)
|-- words: array (nullable = true)
| |-- element: string (containsNull = true)
在上面的示例中,我们有一个包含两列的数据帧,其中列 words 是数组类型,这意味着在数据帧的每一行上,我们可以表示一个值列表,并且该列表在每一行上可以有不同的大小。此外,数组的元素是有顺序的。重要的属性是数组在元素类型方面是同质的,这意味着所有元素必须具有相同的类型。要访问数组的元素,我们可以使用如下的索引:
df.withColumn('first_element', col('words')[0])
结构类型
StructType 用于将一些可能具有不同类型(不同于数组)的子字段组合在一起。每个子字段都有一个类型和一个名称,并且对于数据帧中的所有行都必须相同。可能出乎意料的是,一个结构中的子字段是有顺序的,所以比较两个具有相同字段但顺序不同的结构 s1==s2 会导致 False 。
请注意数组和结构之间的基本区别:
- 数组:类型相同,允许每行有不同的大小
- 结构:类型异构,每行都需要相同的模式
地图类型
您可以将 map 类型视为前面两种类型的混合:array 和 struct。设想这样一种情况,每一行的模式都没有给出,您需要在每一行上支持不同数量的子字段。在这种情况下,您不能使用 struct。但是使用数组对您来说并不是一个好的选择,因为每个元素都有一个名称和一个值(它实际上是一个键-值对),或者因为元素有不同的类型——这是 map 类型的一个很好的用例。使用 map 类型,您可以在每一行上存储不同数量的键-值对,但是每个键必须具有相同的类型,并且所有的值都必须是相同的类型(可以与键的类型不同)。配对的顺序很重要。
变换数组
在我们开始讨论转换数组之前,让我们先看看如何创建一个数组。第一种方法我们已经在上面看到了,我们从一个本地值列表中创建了数据帧。另一方面,如果我们已经有了一个数据帧,我们想将一些列组成一个数组,我们可以使用函数 array() 来实现这个目的。它允许您从其他现有的列中创建一个数组,因此如果您有列 a 、 b 、 c ,并且您希望将值放在一个数组中,而不是放在单独的列中,您可以这样做:
df.withColumn('my_arr', array('a', 'b', 'c'))
除此之外,还有一些函数产生一个数组作为转换的结果。例如,函数 split( ) 会将一个字符串拆分成一个单词数组。另一个例子是collect _ list()或collect _ set(),它们都是聚合函数,也会产生一个数组。
实际上,将数组放入数据帧的最常见方式是从支持复杂数据结构的数据源(如 Parquet)读取数据。在这种文件格式中,一些列可以存储为数组,因此 Spark 自然也会将它们读取为数组。
现在,当我们知道如何创建一个数组时,让我们看看数组是如何转换的。
从 Spark 2.4 开始,有大量的函数用于数组转换。有关它们的完整列表,请查看 PySpark 文档。例如,所有以 array_ 开头的函数都可以用于数组处理,您可以找到最小-最大值、对数组进行重复数据删除、排序、连接等等。接下来,还有concat()flatten()shuffle()size()【slice()sort _ array()。正如你所看到的,API 在这方面已经相当成熟,你可以用 Spark 中的数组做很多操作。
除了上述这些函数,还有一组函数将另一个函数作为参数,然后应用于数组的每个元素,这些函数被称为高阶函数(Hof)。了解它们很重要的一点是,在 Python API 中,从 3.1.1 开始就支持它们,而在 Scala API 中,它们是从 3.0 开始发布的。另一方面,对于 SQL 表达式,从 2.4 开始就可以使用了。
要查看一些具体示例,请考虑以下简单的数据框架:
l = [(1, ['prague', 'london', 'tokyo', None, 'sydney'])]df = spark.createDataFrame(l, ['id', 'cities'])df.show(truncate=False)+---+-------------------------------------+
|id |cities |
+---+-------------------------------------+
|1 |[prague, london, tokyo, null, sydney]|
+---+-------------------------------------+
假设我们想要完成这五个独立的任务:
- 将每个城市的首字母转换成大写。
- 去掉数组中的空值。
- 检查是否有以字母 t 开头的元素。
- 检查数组中是否有空值。
- 对数组中每个城市的字符数(长度)求和。
这些是可以用 HOFs 解决的问题的一些典型例子。所以让我们一个一个来看:
改变
对于第一个问题,我们可以使用 transform HOF,它只是采用一个匿名函数,将它应用于原始数组的每个元素,并返回另一个转换后的数组。语法如下:
df \
.withColumn('cities', transform('cities', lambda x: initcap(x))) \
.show(truncate=False)+---+-------------------------------------+
|id |cities |
+---+-------------------------------------+
|1 |[Prague, London, Tokyo, null, Sydney]|
+---+-------------------------------------+
如您所见, transform() 有两个参数,第一个是需要转换的数组,第二个是匿名函数。在这里,为了实现我们的转换,我们在匿名函数中使用了 initcap() ,它被应用于数组的每个元素——这正是转换 HOF 允许我们做的。对于 SQL 表达式,可以按如下方式使用:
df.selectExpr("id", "TRANSFORM(cities, x -> INITCAP(x)) AS cities")
注意,SQL 中的匿名函数是用箭头(->)符号表示的。
过滤器
在第二个问题中,我们希望从数组中过滤出空值。这一点(以及任何其他过滤)可以使用 过滤器 HOF 来处理。它允许我们应用一个匿名函数,该函数对每个元素返回布尔值( True / False ),并且它将返回一个新数组,该数组只包含该函数返回 True 的元素:
df \
.withColumn('cities', filter('cities', lambda x: x.isNotNull())) \
.show(truncate=False)+---+-------------------------------+
|id |cities |
+---+-------------------------------+
|1 |[prague, london, tokyo, sydney]|
+---+-------------------------------+
这里,在匿名函数中我们调用 PySpark 函数 isNotNull() 。SQL 语法如下所示:
df.selectExpr("id", "FILTER(cities, x -> x IS NOT NULL) AS cities")
存在
在下一个问题中,我们希望检查数组是否包含满足某些特定条件的元素。请注意,这是一个更一般的例子,在这种情况下,我们希望检查某个特定元素的存在。例如,如果我们想检查数组是否包含城市布拉格,我们可以只调用array _ contains函数:
df.withColumn('has_prague', array_contains('cities', 'prague'))
另一方面, 存在 HOF 允许我们对每个元素应用更一般的条件。结果不再是像前两个 Hof 那样的数组,而只是真 / 假:
df \
.withColumn('has_t_city',
exists('cities', lambda x: x.startswith('t'))) \
.show(truncate=False)+---+-------------------------------------+----------+
|id |cities |has_t_city|
+---+-------------------------------------+----------+
|1 |[prague, london, tokyo, null, sydney]|true |
+---+-------------------------------------+----------+
这里在匿名函数中,我们使用了 PySpark 函数【starts with()。
FORALL
在第四个问题中,我们希望验证数组中的所有元素是否都满足某些条件,在我们的示例中,我们希望检查它们是否都不为空:
df \
.withColumn('nulls_free',forall('cities', lambda x: x.isNotNull()))\
.show(truncate=False)+---+-------------------------------------+----------+
|id |cities |nulls_free|
+---+-------------------------------------+----------+
|1 |[prague, london, tokyo, null, sydney]|false |
+---+-------------------------------------+----------+
正如你所看到的,对于所有的 来说 与存在非常相似,但是现在我们正在检查条件是否对所有的元素都成立,之前我们至少要寻找一个。
总计
在最后一个问题中,我们希望对数组中每个单词的长度求和。这是一个例子,我们希望将整个数组简化为一个值,对于这种问题,我们可以使用 HOF 聚合 。
df \
.withColumn('cities', filter('cities', lambda x: x.isNotNull())) \
.withColumn('cities_len',
aggregate('cities', lit(0), lambda x, y: x + length(y))) \
.show(truncate=False)+---+-------------------------------+----------+
|id |cities |cities_len|
+---+-------------------------------+----------+
|1 |[prague, london, tokyo, sydney]|23 |
+---+-------------------------------+----------+
使用 SQL:
df \
.withColumn("cities", filter("cities", lambda x: x.isNotNull())) \
.selectExpr(
"cities",
"AGGREGATE(cities, 0,(x, y) -> x + length(y)) AS cities_len"
)
如你所见,与之前的 HOFs 相比,语法稍微复杂了一些。集合接受更多的参数,第一个仍然是我们想要转换的数组,第二个参数是我们想要开始的初始值。在我们的例子中,初始值是零( lit(0) ),我们将把每个城市的长度加进去。第三个参数是匿名函数,现在这个函数本身有两个参数——第一个参数(在我们的例子中是 x )是运行缓冲区,我们将第二个参数表示的下一个元素的长度(在我们的例子中是 y )添加到这个缓冲区中。
可选地,可以提供第四个参数,这是另一个转换最终结果的匿名函数。如果我们想要进行更复杂的聚合,这是很有用的,例如,如果我们想要计算平均长度,我们需要保留大约两个值,即 sum 和 count ,我们将在最后的转换中对它们进行如下划分:
(
df
.withColumn('cities', filter('cities', lambda x: x.isNotNull()))
.withColumn('cities_avg_len',
aggregate(
'cities',
struct(lit(0).alias('sum'), lit(0).alias('count')),
lambda x, y: struct(
(x.sum + length(y)).alias('sum'),
(x.count + 1).alias('count')
),
lambda x: x.sum / x.count
)
)
).show(truncate=False)+---+-------------------------------+--------------+
|id |cities |cities_avg_len|
+---+-------------------------------+--------------+
|1 |[prague, london, tokyo, sydney]|5.75 |
+---+-------------------------------+--------------+
如您所见,这是一个更高级的示例,我们需要在聚合过程中保留两个值,我们使用具有两个子字段 sum 和 *count 的 struct() 来表示它们。*使用第一个匿名函数,我们计算所有长度的最终总和,以及元素总数。在第二个匿名函数中,我们只是将这两个值相除,得到最终的平均值。还要注意,在使用 aggregate 之前,我们首先过滤掉空值,因为如果我们在数组中保留空值,总和(以及平均值)将变成空值。
要查看聚合 HOF 与 SQL 表达式一起使用的另一个示例,请检查 this Stack Overflow 问题。
除了前面提到的这五个 Hof,还有 zip_with 可以用来将两个数组合并成一个数组。除此之外,还有其他的 Hof,如 map_filter , map_zip_with ,transform _ keys,以及transform _ values与地图一起使用,我们将在以后的文章中了解它们。
结论
在本文中,我们介绍了高阶函数(Hof ),这是 Spark 2.4 中发布的一个特性。首先,只有 SQL 表达式支持它,但是从 3.1.1 开始,Python API 也支持它。我们已经看到了五个 Hof 的例子,它们允许我们在 Spark 数组中转换、过滤、检查存在性和聚集元素。在 HOFs 发布之前,大多数问题都必须使用用户定义的函数来解决。然而,HOF 方法在性能方面更有效,要查看一些性能基准,请参见我最近的另一篇文章,其中显示了一些具体的数字。