TowardsDataScience-博客中文翻译-2020-一百二十二-
TowardsDataScience 博客中文翻译 2020(一百二十二)
理解自然语言处理技术
我给有抱负的数据科学家的简单教程
快速理解基本文本分析和 ML 的方法
NLP 源简介 Unsplash
“从一个人和什么样的人交往,你就可以知道他是什么样的人。”——约翰·弗斯
什么是自然语言处理?
自然语言处理(NLP) 通过模拟人类理解文本的方式,帮助计算机理解文本。
由于*年来的数据增长,NLP 发展迅速。尤其是随着 Twitter、脸书和谷歌在搜索引擎或个人人工智能助理中处理非结构化数据的兴起。越来越多的自然语言处理的应用获得了巨大的普及。
自然语言处理的应用和问题
谷歌搜索图片
我对“猫”这个词的搜索
考虑一下谷歌搜索猫的图片。你指的是什么猫?
有这么多猫。在“猫”戏剧表演中有变异猫。你有卡通猫。你说的是哪一个?
- 当我说“一只可爱的猫”时。显然我想要那些小猫。
- 当我说“猫秀”的时候。显然我想要的是变异猫。
“可爱”和“秀”这样的相邻词进一步说明了我想要的。这是评估 NLP 性能的上下文。
谷歌助手
想想 Google Home Mini,你只需花 50 美元就能买到。它理解查询上下文吗?
想象一下,你的小女儿在她的作业项目“好的,谷歌,定义一闪一闪的小星星”上问谷歌 Home Mini。
Google Home 不应该聪明到知道这是一首歌的歌名吗?
情感分析
想象一下,你想预测唐纳德·特朗普(Donald Trump)在上次总统选举中击败希拉里·克林顿(Hillary Clinton)。
你能从推特上的普遍情绪预测一下吗?每位总统候选人的满意度如何以及如何?
[## 2019 年印度尼西亚大选的推特故事(Pemilu)
在印尼总统选举中,佐科维和普拉博沃成为最受关注的候选人,他们代表着…
towardsdatascience.com](/the-twitter-tale-of-indonesian-election-2019-pemilu-fb75cd084a32)
话题分析
想象一下你的硕士/学士班论坛:很难从所有这些信息中提取出什么是重要的。
你能建立一个模型,潜在地提取所有的关键主题,并基于此过滤帖子吗?
[## 使用 Python Dash、文本分析和 Reddit API 自动生成常见问题
问题陈述
towardsdatascience.com](/auto-generated-faq-with-python-dash-text-analysis-and-reddit-api-90fb66a86633)
自然语言处理变得越来越普遍和重要。潜力是巨大的,现在世界各地都在使用。
NLP 为什么难——歧义和上下文?
NLP 最大的敌人是歧义,而 NLP 最大的朋友是上下文——文森特·塔坦
想想我们在日常生活中是如何使用词汇的。
“你为什么这么沮丧?玩得开心拉!
“怎么了?
对我们来说,这些话的意思似乎非常清楚。“r u”
代表“are you”
,“what’s up”
是一种常见的美国非正式问候语。
然而,这些句子中有歧义。“What’s up”
字面回答应该是“The sun is up”
而不是“I am good, thank you”
。隐藏的上下文使我们很难向计算机解释这些句子的确切意思。
大多数时候,我们假设听者会根据我们对世界的了解和共同的背景来填补很多空白。
我们在哪里?
我们在做什么?
我们对彼此了解多少?
有趣的是,我们不知道意义是如何从我们大脑中的单词中产生的。但这一切对我们来说都很容易。
这就是为什么我们需要机器学习和 NLP 技术来让计算机从文本语料库中识别这些规则和上下文。
科拉布
在这节课中,我们将使用 Colab 来完成整个教程。随意打开这个直接跑。这个 colab 展示了 NLP 的一个简单应用。
编辑描述
colab.research.google.com](https://colab.research.google.com/drive/1nEumwX8cxppopls-73LbAxOf7j1u7Odr?usp=sharing)
如何理解“我爱冰淇淋?”
来源 Unsplash
想象你问你的女儿们喜欢吃什么。其中一个孩子回答说:“我喜欢冰淇淋!”你能猜出他们是兴奋地微笑还是皱眉吗?
如果我给你看“我的冰淇淋”这个词,你能做到吗?你怎么知道这孩子是不是皱着眉头?
为了回答这个情感分析问题,让我们将 NLP 分解为以下基本原则:
- 记号化:把每个单词分解编码成一个句子。
- 序列:按照特定的顺序映射记号,形成句子/序列。
- 嵌入:在多维图中表示记号,提供向量聚合,提供商业洞察(情感分析)。
标记化
记号化将文本分解成计算机可以理解的记号和索引。
我们将讨论如何通过标记来理解单词。考虑一下FRIED
这个词。我们可以用 ASCII 数字来表示这些字母。但是FIRED
也有相同的字母,但是由于顺序不同,单词也不同。
好吧,那我们就用单词来代替。在文本“I love my dog”
中,我们可以对每个单词→ 1[I], 2[love], 3[my], 4[dog]
进行编码。我们将看一下实现令牌化的代码
Tokenizer API 已经删除了所有感叹号和标点符号,以便快速获得单词标记。
首先,我们需要标记来自 Keras 的 API。然后我们可以创造句子。Num_words
此处参数表示要保留的字数。在这种情况下,我们将保留在语料库中出现的 100 个最频繁的单词。
如果分词器在 word_index 中找到不存在的单词怎么办?然后我们需要指定词汇(OOV)标记中的,它将单词标识为索引 1** 。**
词的标记化和索引化
恭喜你。您刚刚将这些令牌编入了训练索引!
排序
排序表示单词序列中的句子及其各自的索引。我们使用tokenizer.texts_to_sequence
将语料库翻译成序列中的标记。
顺序很重要,因为单词的顺序很重要。“我爱鱼”和“鱼爱我”有不同的语义和语法错误。句子的顺序决定了哪个词是这个意思的宾语或主语。
这将对标记进行排序,并填充剩余的标记空间,以匹配最长序列的维度。这些对于在嵌入/情感分析期间运行数学运算非常重要。
测序的结果
注意,我们可以从这里提取几个索引:
- 0 → 在较短的句子上填充以匹配较长的句子
- 1 → OOV(不在词汇表中)。这些是 word_index 标记中没有的词汇
- > 1 → 在标记化 word_index 中找到的单词的索引
填充允许我们标准化每个语料库的长度。这使得使用 numpy 运行多个操作并为神经网络(NN)训练做准备变得更加容易。
把...嵌入
嵌入将输入投影到多维表示空间。例如,我们可以将下列单词投射到极性子空间。
嵌入:在维度空间中绘制单词
让我们在多维空间里画出每个单词。然后当我们想要有组合的意思时。我们可以将这些向量组合在一起,生成统一向量。
例如,good
被认为是正面情绪,而bad
被认为是负面情绪。但是如果你在同一个句子中使用good
…和bad
…的话(例如:我认为你的文章是good
,但是你的笔迹是bad
,那么情感是中性的。
通过 Tensorflow 和 Keras 的实验,我们可以用嵌入作为输入来训练神经网络。有了这个 colab 笔记本,你可以实验做卡格尔问题并运行代码。
额外收获:NLP 的进一步挑战
这在自然语言处理中有更多的应用。我希望这个简单的教程能激起你探索如何处理单词和语言的兴趣。
语法和词汇很复杂。我们需要更多的技术来衍生我们的特征和更好地标记。
- Lemma :应该表示的唯一单词。例如,在英语中我们有不同的时态。Run、ran 和 running 指的都是同一个词,即 run。汉语有不同意思的单词组合。如魏(危)纪(机)→危机
- 词性标注:单词记号是独立的意义单位。这可以是动词、词缀、前缀、名词等。例如
run
是动词,但running
可以是形容词(如running
竞赛) - 分块:从词性标注中识别实体的提及。它将单词/标记分组为块
- 共指:链接一节中提到同一实体的词。奥巴马和罗姆尼谈到了他的童年。这里的
his
指的是什么?
最后的想法
总之,我相信这些基本的自然语言处理在你的第一次探索中很容易实现。我们了解到:
- 记号化:把每个单词分解编码成一个句子。
- 序列:按照特定的顺序映射记号,形成句子/序列。
- 嵌入:在多维图中表示记号,提供向量聚合,提供商业洞察(情感分析)。
请随意看看您是如何在 DNN 框架(如 Tensorflow 和 Keras)中实现它的。
一如既往,如果你有任何问题,请通过 Linkedin 联系我。如果时间允许,我很乐意回答你的问题。
索利·德奥·格洛丽亚
参考
- 斯坦福大学的自然语言理解(NLU)
- 劳伦斯·莫罗尼的《NLP 零到英雄》
关于作者
文森特用 ML @ Google 对抗网络滥用。文森特使用高级数据分析、机器学习和软件工程来保护 Chrome 和 Gmail 用户。
除了在谷歌的工作,Vincent 还是《走向数据科学媒体》的特约撰稿人,为全球 50 万以上的观众提供有抱负的 ML 和数据从业者的指导。
在空闲时间,文森特在佐治亚理工学院攻读硕士学位,并为铁人三项/自行车旅行进行训练。
最后,请通过 LinkedIn , Medium 或 Youtube 频道 联系文森特
了解目标检测和 R-CNN。
让我们看看什么是目标检测,并详细了解基于 CNN 的区域提议。
使用 Canva 设计
最*,我们看到机场和火车站试图通过摄像头检测人们是否保持社交距离,戴上口罩。这些是由摄像机捕捉的实时视频,其中存在持续的运动。我们还看到了开发自动驾驶汽车的研究,这种汽车需要检测道路上的障碍,并相应地驾驶。这一切是如何发生的?
这就是物体检测发挥作用的地方。让我们了解对象检测是如何工作的,我们还将学习 R-CNN 的概念。 R-CNN 是目前已有的和最流行的架构如更快的 RCNN 和掩码 RCNN 的前身。去年, FAIR (脸书人工智能研究)开发了一个全功能框架,名为 Detectron2 ,它建立在这些最先进的架构、更快的 R-CNN、和 Mask R-CNN 之上。
在深入这个概念之前,我希望您熟悉一些概念,如卷积神经网络和标准架构。阅读此处了解更多。
分类
使用 Canva 设计
分类只是将图像标记到其各自的类别,而定位指的是围绕主题的边界框。目标检测指的是单个图像中的许多这样的实例(分类+定位)。在进入 R-CNN 之前,让我们先来看看地区提案的概念是如何被处理的,以及如何引导他们进入 R-CNN 的。
分类管道
使用 Canva 设计
在任何标准 CNN 架构中,如 VGG 网或亚历克斯网,卷积层和池层共同充当特征提取器。下面的全连接层和 softmax 层充当分类器。SVM 也可以用来代替这种完全连接的层+ Softmax。特征地图被拉伸成一维向量,并被馈送到完全连接的层。
本地化
现在,对于本地化部分,考虑每个图像只有一个对象。这里,我们还需要一个模型来预测边界框的坐标。为此,我们需要一个带有 L2 损失的回归模型(大多数情况下是 L2,但在一些异常情况下也会用到 L1)。因此,除了目前的分类管道,我们需要附加一个回归模型来预测边界框。可以将相同的特征映射馈送到该回归模型,并且在分类器的情况下,代替单个 softmax 输出层,我们现在需要四个结果。四个结果是表示边界框的坐标。
使用 Canva 设计
目标检测
现在,谈到对象检测,这里的情况是,在单个图像中可能有多个对象,并且这随图像而变化。我们事先不知道确切的人数。唯一的选择是扫描图像的所有可能位置。现在,想想在这样的图像上检测的想法。
我们可以考虑在这里使用滑动窗口技术,就是这样工作的。
使用 Canva 设计
使用这种技术,我们可以得到图像的所有可能的部分,然后把它输入到上面显示的网络中。然后,网络会查看图像的每个部分,如果识别出其中的任何潜在特征,就会决定作物中是否有物体。如果是,它给出边界框。这样,通过使用相同的网络,我们将能够检测图像中的对象。
但是对于标准架构,如 AlexNet 和 VGG Net,输入的大小是有限制的。裁剪后的图像不能直接输入网络。裁剪后的图像必须按网络接受的输入尺寸进行缩放。这整个过程被认为是预处理阶段(滑动窗口+裁剪+调整大小)。
如果你观察敏锐,你可能会注意到图像中的所有对象不需要有固定的大小来适应我们的滑动窗口。因此,固定大小的窗口不能解决这个问题。要么必须采用不同尺寸的窗口,要么必须在保持窗口尺寸固定的情况下将图像缩放成不同的尺寸。实验发现,使用图像的六个不同尺度足以定位图像中的大多数对象。因此,后者通常比前者更受青睐。在较小尺度的图像中检测到较大的物体,而在较大尺度的图像中检测到较小的物体。这个概念被称为形象金字塔。
使用 Canva 设计
在这里,自行车和人的大小是不同的。你可以看到这个人不完全适合滑动窗口。为此,通过使用图像金字塔的概念,我们可以将图像调整到不同的尺度,在第三个尺度上,我们可以找到窗口内的人。然后,通过使用边界框回归器,我们可以将窗口的大小与地面实况相匹配。由于自行车与人相比是一个较小的物体,所以它可能在第一个或第二个位置被检测到。此外,请注意,该人也可能在其他尺度下被部分检测到,但置信度得分通常会更低,边界框也不会那么准确。
因此,这些是在输入端执行对象检测所需的额外预处理步骤。虽然我们已经使用滑动窗口和图像金字塔以及定位网络解决了对象检测的问题,但是这仍然将我们引向另一个问题。由于我们是在图像的所有位置以不同的比例裁剪图像补丁,所以我们最终会有大量的建议只针对单个图像。由于 CNN 的处理非常密集,这将是一个昂贵的操作。那么,我们如何解决这个问题呢?
我们可以使用一些区域提议算法来避免对网络的不必要的输入,而不是使用作为强力技术的滑动窗口方法。一些流行的区域提议技术包括颜色对比、边缘框、超像素跨越、选择性搜索等。在所有这些中,选择性搜索和边缘框被发现更加有效。选择性搜索也称为类别不可知检测器。
类别不可知检测器通常用作预处理器,产生一系列有趣的包围盒,这些包围盒很有可能包含猫、狗、汽车等。
显然,我们需要在类别不可知的检测器之后有一个专门的分类器来真正知道每个包围盒包含什么类别。虽然 R-CNN 不知道特定的区域提议方法,但我们使用选择性搜索来实现与先前检测工作的受控比较。
选择性搜索
选择性搜索使用层次聚类来获得各种区域建议。
目标识别的选择性搜索 J. R. R. Uijlings,K. E. A. van de Sande,T. Gevers,a . w . m . smulders
首先,它将像素非常紧密地分组,然后像素根据相似的颜色、纹理和成分合并成一个像素。在更高的层次上,我们通过裁剪出不同的小块从图像中得到一组区域建议,如最后一栏所示。
R-CNN
使用 Canva 设计
由于我们将地区提案与 CNN 合并,因此被称为 R-CNN 。由于诸如 VGG 网/亚历克斯网之类的本地化网络仅接受固定大小的输入,并且所有的区域提议可能都不是该大小,所以裁剪后的图像必须被扭曲成网络可接受的大小。
一开始 R-CNN 给出的准确率是 44%,有点低。但几经变化,提高到 66%。让我们看看精确度是如何提高的。
第一个架构不包括 BBox 回归器,因为他们认为这是不必要的,因为输入已经是裁剪后的图像。
使用 Canva 设计
使用上述架构,我们将获得 44%的精确度。使用的 AlexNet 是在 ImageNet 数据集上预先训练的,该数据集包含大约 1400 万张图像和 1000 个类别。但是为什么即使是在如此庞大的数据集上训练,我们得到的准确率也是如此之低?
这是因为 ImageNet 数据集中的图像具有特定的纵横比,而网络从选择性搜索中接收到的裁剪和扭曲图像具有不同的纵横比。这意味着,在一般情况下,网络会认为一个人的形象会是又高又瘦。但是,当我们将区域方案扭曲到所需的尺寸时,图像会被拉伸,其纵横比会受到干扰。这说明了准确性的一些损失。
使用 Canva 设计
如前所述,您可以查看两幅图像的纵横比。扭曲的图像看起来被拉伸了。这样,扭曲的图像看起来很不自然,扭曲了图像的纵横比。因此,为了解决这个问题,提出了用区域建议对网络进行微调。但是,该训练阶段包括 softmax 分类器而不是 SVM,并且损失函数将是交叉熵损失。要把握交叉熵背后的核心直觉,可以在这里阅读我之前的文章。为一个类分配一个 SVM,并且使用存储的特征映射离线训练 SVM。每个 SVM 都通过为该类提供正确和错误的建议来接受训练。
使用 Canva 设计
因此,当我们微调网络时,我们会更新卷积层和全连接层中的权重。这导致精度提高了 10%,导致 54.2% 。之后,还通过实验发现,包括边界框回归器导致了更好的准确性,因为它比以前更准确地收紧了边界框。它只包括一个完全连接的层,而不是两个。这导致精度进一步提高,达到 58.5% 。使用 VGG 网代替更深的 AlexNet 导致了 R-CNN 的最佳性能模型,其准确度为 66% 。
使用 Canva 设计
这是一个 R-CNN 的高层图表。此外,许多改进和实验是通过包括和移除完全连接的层来完成的,包括和不包括微调。观察到当微调时,更多数量的权重在完全连接的层中而不是卷积层中被改变,这是一个有趣的观察。
使用 Canva 设计
请注意,所有这些观察都是在 PASCAL VOC 2007 数据集上进行的,用于实验的架构是 Alex Net。
如果没有微调,即使我们添加完全连接的层,精度也不会有太大差异。这表明大部分精度是在卷积层本身中实现的,而完全连接的层几乎没有增加任何价值。需要考虑这一点,因为卷积层的模型大小大约为 3.7 MB ,而完全连接的层大约需要 192 MB 。我们可以想象排除 FC 层可以节省多少内存。然而,当我们根据地区提案对网络进行微调时,情况发生了变化。FC 层中的大多数权重都发生了变化,因此,我们可以看到准确性有了显著提高。此外,如果我们排除 FC7 层,我们会得到 53.1%的精度,仅比添加 FC7 层的精度低 1.1%。我们可以考虑去掉它,因为它占总模型尺寸的 29% 。但这不是强制性的,因为人们可以在时间、大小和准确性之间进行权衡。如果我们需要一个更精确的模型,并且我们有时间和空间,我们可以考虑将其用于关键应用,例如准确性更重要的医疗领域。
通过这个 R-CNN,我们避免了滑动窗口技术和图像金字塔的使用,但是我们得到了不同大小的区域建议。我们已经设法在不使用图像金字塔的情况下找到区域提议,但是每幅图像 2000 个提议太大,并且涉及巨大的计算。R-CNN 并不完美。它有一些缺点。
- 它在测试时很慢,因为它需要为每个地区的提议运行一个完整的 CNN 转发过程。事实上,它比之前性能最好的型号over feet慢了 9 倍,该型号主要使用卷积运算代替 FC 层,并有效地使用了滑动窗口概念。
- 它有一个复杂的多阶段训练管道(用于训练区域提议、训练 SVM 和 BBox 回归器的交叉熵)。
结论
我希望你已经理解了物体检测和 R- CNN 架构背后的原理。为了解决 R-CNN 的缺点,一年后快速 R-CNN 被提出,它比 RCNN 快 146 倍。更多关于快、更快、和遮罩区域的内容将在我接下来的文章中讨论。
编辑:你可以在这里 阅读这篇文章的下一部分,其中包括快速 RCNN 和更快的 RCNN 。
如果你想联系,在 LinkedIn 上联系我。
参考
对 ARELU 的理解
卷积神经网络的聚焦良好、面向任务的激活
汉斯-彼得·高斯特在 Unsplash 上拍摄的照片
激活函数是神经网络的组成部分之一,对训练过程有着至关重要的影响。当开发大多数类型的神经网络时,校正的线性激活函数(即,RELU)已经迅速成为默认的激活函数,这是因为校正函数实现起来很简单,具有代表性的稀疏性,以及通过具有 1 的梯度斜率几乎完全避免了消失梯度的问题。
本文旨在通过将可学习的激活功能公式化为基于元素的注意机制,展示一个全新的视角。由此产生的激活函数 ARELU 促进了小学习速率下的快速网络训练,这非常适合于迁移学习,并且使网络训练更能抵抗梯度消失。
ARELU 的配方
图 1:左图:用不同粒度的注意力地图说明了注意力机制。右图:在手写数字数据集 MNIST 的测试图像上使用 ReLU 和 AReLU 获得的激活前和激活后特征图的可视化。 [图像来源]
神经网络被认为是以一种简化的方式模仿人脑活动的一种努力。注意机制也是在深度神经网络中,试图实现选择性地专注于少数相关事物,而忽略其他事物的相同动作。
注意力可以说是当今深度学习领域最强大的概念之一,因为它指导网络学习输入的哪一部分对输出的贡献更大。具有不同粒度的注意力地图的一些注意力机制如下:
图 2:注意机制的类型
在所有的注意机制中,元素方面的注意是最细粒度的,因为特征量中的每个元素接收不同量的注意。因此,ARELU 被设计成使得对于网络的每一层,获得基于元素的注意力模块学习符号的注意力图,该注意力图基于其符号来缩放元素,从而导致正元素的放大和负元素的抑制。
对 ARELU 机理公式的理解
整个机制可以建立如下:
图 ARELU 激活的构建模块
元素式注意机制
等式(ii)中的调制器函数执行输入向量与注意力图的逐元素乘法。为了满足维度要求,如等式(I)所示,注意力图被扩展到输入向量的全维。
等式表 1:基于元素的注意机制
基于元素符号的注意
为了放大积极因素,抑制消极因素,注意力地图根据其在 ELSA 中的符号进行缩放。注意力由两个参数α和β决定,导致具有两个可学习参数的符号式注意力机制。
等式表 2:基于符号的基于元素的注意机制
基于注意力的校正线性单位
图 ARELU 可学习激活功能的解释
从等式(v)可以明显地证明,ARELU 放大了正元素,而相反,减小了负元素。
图 ARELU 阿海鲁激活后的初始状态
优化 ARELU 激活
优化包括误差函数的最小化。应用链式法则来最小化误差函数,对于等式(v)中的相应参数获得的梯度如下:
等式表 3:用于优化的 ARELU 可学习激活的导数
ARELU 激活能够在激活的输入上增加梯度,这使得避免梯度消失导致加速模型的训练收敛是合适的。对各种激活函数的比较研究表明,ARELU 在只有两个可学习参数的情况下表现最佳,并且对于不同的网络架构也是通用的。
图 6:在第一个时期之后,使用不同的优化器和学习速率,在 MNIST 上对 MNIST-Conv 进行五次训练的*均测试准确度(%)。我们将 AReLU 与 13 个不可学习的和 5 个可学习的激活函数进行比较。每个激活单元的参数数量列在可学习激活功能的名称旁边。对于不可学习的方法,最好的数字用蓝色粗体显示,对于可学习的方法,用红色显示。在表格的底部,我们用蓝色和红色分别报告了 AReLU 相对于其他不可学习和可学习方法的最佳改进。[ 图像来源
结论
通过添加注意力和 RELU 模块来获得 ARELU,已经清楚地表明,通过引入每层不超过两个额外的可学习参数,显著地提高了大多数主流网络架构的性能。ARELU 不仅使网络训练不受梯度消失的影响,而且能够以较小的学习速率进行快速学习,使其特别适合迁移学习。
参考
陈,邓生,。"基于注意力的校正线性单元." arXiv 预印本 arXiv:2006.13858 (2020)。
理解光流和 RAFT
如何以迭代方式使用多尺度相关性求解光流
(来源)
计算机视觉中最重要的问题是对应学习。即给定一个物体的两幅图像,如何从两幅图像中找到该物体对应的像素?
主要针对视频的对应学习在目标检测和跟踪中有着广泛的应用。尤其是当视频中的对象被遮挡、变色、变形时。
什么是光流?
两幅图像的光流场
光流是两幅图像之间的矢量场,显示了如何移动第一幅图像中的对象的像素以在第二幅图像中形成相同的对象。它是一种对应学习,因为如果知道一个物体对应的像素,就可以计算出光流场。
光流方程和传统方法
两幅图像 H 和 I 之间的一点流
让我们采取最简洁的形式:两个图像之间的一点流。假设 H 中(x,y)处的像素流向 I 中的(x+u,y+v),则光流矢量为(u,v)。
如何求解(u,v)?我们建立一些方程有什么约束吗?
首先,当 H(x,y) = I(x+u,y+v)时,让我们用泰勒级数分解 I(x+u,y+v):
I(x+u,y+v)的泰勒级数逼*
然后,舍弃高阶项,结合 H(x,y) = I(x+u,y+v):
光流方程的推导过程
最后,在极限情况下,当 u 和 v 变为零时,我们得到的光流方程为:
光流方程
然而,在实际应用中,u 和 v 可能大或小,跨越几个到几十个像素,而不是零限制。因此,我们只能得到真实光流的*似。然而,如果 u 和 v 更接*于零,流场会更精确。
在上面的等式中,未知数是 u 和 v,因为其他变量可以从 x,y 和时间维度的差异中计算出来。因此,一个方程中有两个未知数,无法求解。因此,在过去的 40 年里,许多研究者试图提供 u,v 的其他方程组,使其可解。其中,最著名的方法是卢卡斯-卡纳德法。
深度学习时代,能否用深度神经网络求解光流?如果可以,网络设计的意义何在?
答案是肯定的,这几年也有这方面的工作,效果越来越好。我将介绍一个代表作品叫做 RAFT,它获得了 ECCV 2020 最佳论文奖。
大量
RAFT,又称递归全对场变换,是一种迭代求解光流的深度学习方法。
筏子的网络结构(来源
如上所述,给定两幅图像,找到它们之间相同对象的对应像素对是核心。在深度学习中,一切都是基于潜在特征图,这带来了效率和准确性。因此,可以通过相关场提取相应的像素对。
两幅图像的多尺度相关性
相关性显示两幅图像中两个像素之间的关系。对于大小为(H,W)的两幅图像,它们的相关场的大小将为(H,W,H,W),这称为 C1。如果我们把最后两个维度结合起来,我们会得到 C2 和 C3 等。对于图像 1 的像素,C1 示出了像素相关,C2 示出了与图像 2 的 2×2 像素相关,C3 示出了 4×4 像素相关,等等。这种多尺度方式可以帮助找到从小到大的位移,这对于具有大时间步长的视频中的光流估计是重要的。
对于图像 1 的每个像素,将其光流初始化为零,并在多尺度相关图中查找其周围区域,以搜索其在图像 2 中的对应像素。然后,估计图像 1 中的像素与其在图像 2 中搜索到的对应像素之间的光流。最后,根据估计的光流将图像 1 中的像素移动到新的位置,并且重复该过程直到收敛。在这种迭代方式下,每次只估计一个步骤的流量,最终的流量就是它们的总和。
损失和估计光流的收敛
关于 RAFT 的实现
(来源)
RAFT 模型可以分为两部分:编码器和迭代器。编码器部分类似于编解码网络中的编码器,用于提取输入图像的潜在特征图。迭代器实现为 ConvGRU 模块,这是一种 RNN 结构,可以预测一系列流程步骤,并通过共享参数进行迭代优化。
此外,规范和激活层仅用于编码器。
RAFT 的预测结果
预测光流图
我测试了作者提供的源,结果看起来不错。我用了一个 10 帧的视频剪辑。如上图,第一行是帧 0,第二行是帧 1~9,第三行分别是帧 0 和帧 1~9 之间的预测光流。结果表明,对于小时间间隔和大时间间隔的两个视频帧之间的光流可以*滑地预测。
参考
阅读陈数·杜(以及媒体上成千上万的其他作家)的每一个故事。您的会员费直接支持…
dushuchen.medium.com](https://dushuchen.medium.com/membership)
了解过度拟合和欠拟合
学会知道你的机器学习模型是过拟合还是欠拟合。
安迪·凯利在 Unsplash 上的照片
机器学习模型的真正有效性实际上取决于它在测试集数据上的表现。无论它在训练数据上做得多好,如果它在测试数据上表现不佳,该模型可能不太适合手头的任务。因此,在构建模型时,从性能角度来看,可能会发生三种情况:
- 模型过度拟合训练数据。
- 模型对训练数据的拟合不足。
- 该模型总体上非常适合该任务。
总的来说,我们想要的模型只能达到上面提到的第三点。另外两点对于一个机器学习模型是不好的。但是,如果我们不够小心,在开发模型时,经常会面临这两个问题中的一个。在本文中,我们将讨论过度适应或适应不足的确切含义。
模型过度拟合
对于受监督的机器学习任务,我们希望我们的模型在测试数据上表现良好,无论是分类任务还是回归任务。这种在测试数据上做得很好的现象在机器学习术语中被称为对测试数据的概括。因此,一个模型对测试数据的概括越好,这个模型就越好。但事情并不总是这样。因为有时模型可能会遇到过度拟合训练数据的问题。
当一个模型在训练数据上表现太好,但在测试数据上表现很差时,这意味着该模型过度拟合了训练数据。
模型过度拟合有几个原因。但总的想法是,当我们试图用明显不足的训练数据来拟合一个复杂的模型时,模型往往会过度拟合训练数据。因为模型会尝试在训练数据样本中捕获更复杂的模式。通过这样做,它最终选择训练数据的每个小的局部变化,而不是选择数据样本的更全局的结构,这对于模型在测试数据上很好地一般化是绝对必要的。
现在让我们看一个使用 Python 的 Scikit-learn 的实际例子。
在这个 Python 脚本中,一个 K-NN 分类器是用不同的K(1,3,11) 的值训练出来的。该脚本将产生以下输出:
信用:谷歌 Colab
我们可以看到,当 K = 1 时,分类器对训练数据有 100%的准确率。但是测试数据的准确性明显较低。因为分类器对测试数据的概括不如对训练数据的概括,这意味着分类器可能过度拟合训练数据。然而,随着 K 的值增加 (K = 3,11) 分类器的准确度在训练数据上降低,但在测试数据上增加。这意味着当 K 增加时,分类器在测试数据上概括得更好。虽然这并不意味着如果我们不断增加 K ,测试数据的准确性也会不断提高。在某一点上,它将再次开始降低测试数据的准确性,对此我们必须小心。
模型欠拟合
正如我们已经讨论过的,当模型过度拟合时,它往往在训练数据上表现得太好。嗯,模型欠拟合是完全相反的。
当模型欠拟合时,它很可能在训练数据上表现很差。因此,该模型也不能很好地概括测试数据。
拟合不足的模型过于简单,甚至无法提取训练数据中存在的常见模式。因此,很明显,该模型在测试数据上的表现也会很差。下面的 Python 脚本为【7,15,55】的不同值训练了一个 K-NN 回归模型:
该 Python 脚本将产生以下输出:
信用:谷歌 Colab
这里我们使用了 R (r *方)回归得分来衡量模型的性能。此 R 分数介于 0 到 1 之间,包括 0 和 1。 R 分数越高,模型越好。我们可以看到,当 K 从 7 增加到 15 时,训练分数降低但测试分数略有增加。然而,当 K 从 15 变为 55 时,列车得分明显下降。当 K = 55 时,表示模型欠拟合。结果测试分数也明显少于其他型号 (K = 7,15) 。
非常合适
既然我们已经讨论了什么是过度适应和不适应,接下来合乎逻辑的事情就是问,那么什么是好的适应呢!确切地说,没有固定的标准来决定一个特定的契合度是好是坏。在理想情况下,我们希望模型在训练和测试数据上的误差都是 0%。但是说起来容易做起来难。所以我们能做的就是在训练机器学习模型时,在欠适应和过适应之间找到一个最佳*衡点。既不欠拟合也不过拟合并且对训练尤其是对测试数据都具有相当好的准确性的模型可以被认为是对手头任务的潜在良好适合。
希望你喜欢这篇文章。感谢阅读。
理解卷积神经网络中的参数共享(或权重复制)
技术和解释
参数共享或权重复制是深度学习研究中可以忽略的主题领域。理解这个简单的概念有助于更广泛地理解卷积神经网络的内部结构。
亨特·哈里特在 Unsplash 上的照片
卷积神经网络(CNN)具有能够对通过网络馈送的图像的仿射变换保持不变的特性。这提供了识别图像中偏移、倾斜或轻微扭曲的图案的能力。
由于 CNN 架构的三个主要属性,引入了仿射不变性的这些特征。
- 局部感受野
- 共享权重(参数共享)
- 空间子采样
在本文中,我们将探索共享权重并理解它们的目的以及它们在 CNN 架构中的优势。
这篇文章面向所有水*的练习机器学习或更具体地说深度学习的个人。
介绍
让我们首先在脑海中创建一个 CNN 中单个卷积层的图示。
由 Clarisse Croset 在 Unsplash 上拍摄的照片
CNN 中的卷积层( conv 层)包含一组单元,也可以称为神经元。
conv 层还包括层内的几个过滤器,这是一个预定义的超参数。
层内的过滤器的数量指示由 conv 层创建的作为下一层的输入的激活/特征地图的输出体积的深度维度。
这些过滤器中的每一个都具有设定的宽度和高度,其对应于该层中单个单元的局部感受野。作用于输入数据的过滤器产生卷积层的输出,即特征图。
在 CNN 的训练阶段,滤波器内的权重值是可学习的。卷积层的输出维度具有深度分量,如果我们划分输出的每个片段,我们将获得特征图的 2D *面。在单个 2D *面上使用的滤波器包含由在同一*面上使用的所有滤波器共享的权重。
这样做的好处是,我们可以在输入数据的其他部分保持在输入数据的一部分中使用的相同特征检测器。
卷积层的输出是一组特征图,其中每个特征图是单元内的固定权重参数和输入数据之间的卷积运算的结果。
卷积神经网络层的一个基本特征是其特征图能够反映对通过输入层输入的输入图像进行的任何仿射变换。
因此,对输入数据进行的任何移动、倾斜或定向,要素地图都会提供一个输出,该输出会根据输入数据所受的量进行移动、倾斜或定向。
将理论付诸实践
本节的目标是揭示卷积神经网络中出现的权重共享的好处。
我们将在两种流行的 CNN 架构的第一卷积层中,推导出不具有权重共享和具有权重共享的可训练权重的数量: LeNet 和 AlexNet 。
下面是要采取的步骤:
- 获得 conv 图层的输出宽度
(输入大小宽度—滤波器大小+(2 *填充)/步距)+ 1 =卷积层的输出宽度
2.计算 conv 层内神经元/单元的数量
3.计算不带权重分配的训练参数(包括偏差)的数量
4.计算权重分配的训练参数数量(包括偏差)
下表描述了来自 AlexNet 和 LeNet CNN 架构的信息将用于导出卷积层内的训练参数/权重的数量。
显示 CNN 架构的不完整属性的表格
AlexNet
- conv 图层输出宽度:
=((227–11)/4)+1
=55(conv 图层输出宽度)
2.conv 层内神经元/单位的数量
=输出高度输出宽度特征地图数量
= 55x55x96 (conv 输出音量)
=29.04 万辆
3.conv 层内训练参数或权重的数量(无权重分配)
*= 290400 (11 * 11 * 3)+1 偏置)
=105,415,600
4.具有重量共享的训练参数或重量的数量(具有重量共享)
*= 96 (11 * 11 * 3)+1 偏置)
= 34,944 重量
LeNet
- conv 图层输出宽度:
=((28–5)/1)+1
=24(conv 图层输出宽度)
2.conv 层内神经元/单元的数量
=输出高度输出宽度特征地图数量
= 24x24x6 (conv 输出音量)
= 三千四百五十六台
3.conv 层内的训练参数或权重的数量(没有权重共享)
*= 3456 (5 * 5 * 1)+1 偏差)
=89,856
4.具有重量共享的训练参数或重量的数量(具有重量共享)
*= 6 (5 * 5 * 1)+1 偏置)
= 156 重量
显示 CNN 架构完整属性的表格
让我们一起来。
克林特·王茂林在 Unsplash 上拍摄的照片
很明显,通过参数共享,我们可以减少 conv 层中的权重数量。
参数共享用于网络内的所有 conv 层。
参数共享减少了训练时间;这是减少反向传播期间必须发生的权重更新次数的直接优点。
重申参数共享发生在从过滤器和来自 conv 层中的*面内的单元的输入数据之间的卷积结果生成特征图时。该层*面内的所有单元共享相同的权重;因此,它被称为权重/参数共享。
我希望这篇文章对你有用。
要联系我或找到更多类似本文的内容,请执行以下操作:
- 订阅我的 YouTube 频道 即将上线的视频内容 这里
- 跟我上 中
- 通过 LinkedIn 联系我
想过为什么卷积神经网络中的所有神经元都没有连接起来吗?
towardsdatascience.com](/understand-local-receptive-fields-in-convolutional-neural-networks-f26d700be16c) [## 机器学习硕士(不会)教你什么
关于攻读机器学习高级学位的常见误解
towardsdatascience.com](/what-a-masters-in-machine-learning-wont-teach-you-b84e5aac8837)
用机器学习理解全球经济模式
一种层次聚类方法
葆拉·梅在 Unsplash 上的照片
从很小的时候起,我们对世界的心理描绘就已经锚定在某种形式的全球地图上(就像上面的墨卡托投影),任何两个组成国家的分离都是基于地理边界的。虽然两个国家可能在地理上相距很*,但在某个时间点上界定其各自经济体制的特征可能大相径庭。
例如,如果我们看看日本和中国,这两个被广阔的东海隔开的邻国,在 21 世纪初,我们会发现日本经济在“失去的十年”的后果中萎靡不振,这一时期的特点是股票和房地产价格泡沫的破裂,在未来许多年里削弱了增长和通胀。相比之下,中国在同一时期收获了 70 年代末开始的经济自由化的好处,并成为世界上增长最快的经济体之一。
这种对比再明显不过了,但它说明了一个重要的观点;要理解全球经济的状态和关系,我们必须比地理上的联系更深入。随着世界紧张地徘徊在衰退的边缘,这种理解的需要从未像现在这样切题。因此,我们在一种称为使用 r 的分层聚类的无监督机器学习算法的帮助下完成这项任务。
分层聚类
梅尔·普尔在 Unsplash 上的照片
分层聚类是一种无监督的机器学习算法,它基于公共属性将相似的观察值分组在一起。这些属性通常用距离度量来概括,如欧几里德距离、曼哈顿距离或相关距离。在第一种情况下,将两个最接*的点连接在一起以形成聚类,这重复发生,将最接*的点或聚类连接在一起,直到只剩下一个包含整个样本空间的大聚类。所得到的结构可以被可视化为如下的树状图:
为了实现这种理解经济的方法,我们采用定义每个经济状态的重要宏观经济变量的向量,并计算它们之间在欧几里得空间中的成对距离。我们称之为经济距离。直观地说,在这个框架中,彼此距离最*的国家/地区被认为在经济状况或发展方面是相似的。为了计算经济距离,我们将使用经济学人网站中的一组“金融指标”。变量包括重要的宏观经济数据点,如增长、通货膨胀、失业、工业生产、通货膨胀、预算*衡。数据确实需要一些争论,清理后的 csv 文件可以在这里找到。在 R 中实现,如下所示:
#Calling required librariesrequire(dendextend)##library for visualizing hierarchical Clustering
require(ggplot2) ##for plotting
require(maggritr) ##for the forward pipe operator %>% for nicer codeecon<-read.csv('fin_indicators0219.csv')#naming the rowsecon.df<-econ[,-1]
rownames(econ.df)<-econ[,1]#scaling variables for use in calculation of Euclidean distance econ.df.scaled<-scale(econ.df)#creating pairwise Euclidean Distance Matrixecon.dist<-dist(econ.df.scaled, method='euclidean')#creating hierarchical clustering objectecon.hc<-hclust(econ.dist)#plottingplot(econ.hc)#let's make it a little bit nicer
#by creating a dendrogram objectdend <- econ.hc%>%
as.dendrogram %>%
set("branches_k_color", k=10) %>% set("branches_lwd", 1.4) %>% set("labels_cex", c(0.5)) ##arbitrary k value#now we plot using ggplot2ggd1 <- as.ggdend(dend)
ggplot(ggd1, horiz = TRUE, theme = NULL) +
ylab("Distance")+
xlab("Countries") +
ylim(c(12.5,-3)) +
theme_grey()
截至 2019 年 2 月的经济聚类,虽然《经济学人》有更多的国家/地区,但为了便于计算,na 值被省略
通过直观的观察,我们可以很快发现地理并不一定是经济相似性的主要驱动力。虽然欧元区/欧元区内的国家似乎确实聚集在一起,但这是意料之中的。他们被一个单一的货币联盟和一系列允许金融和劳动力资本自由流动的条约所束缚。因此,摩擦的消除体现在一个更同步的经济状态,这是该地区的象征。然而,我们也知道这并不总是正确的,案例和要点是 2011 年的欧元主权债务危机,当时一些成员国无力再融资债务,威胁到欧盟的分裂。在这些情况下,我们可能会使用层次聚类来帮助确定这种情况何时开始发生,因为通过失业率和长期利率等指标的上升以及工业生产和增长的下降,风险国家开始与全球经济的其他部分脱离。
其他有趣的关系包括新加坡比法国、意大利或西班牙更接*瑞士。从我们对这两个国家的直观了解中,我们知道它们的相似之处在于它们经济的总体稳健性,以及它们作为各自地区金融和商业中心的声誉。土耳其和阿根廷离剩下的集群最远。这代表了两国在努力应对高通胀、高失业率和经济增长放缓时所面临的经济不确定性程度。
与“基线”的比较
杰森·登特在 Unsplash 上拍摄的照片
为了测试按经济距离进行聚类是否会产生明显不同的结构,我们可以基于地理距离创建一个类似的层次聚类树图,并可视化聚类如何变化。
#altering the sizing of labels and thickness of cluster lines of the #last dendrogram objectdend <- econ.hc%>%
as.dendrogram %>%
set("branches_k_color", k=10) %>% set("branches_lwd", 2.4) %>% set("labels_cex", c(0.85))#reading in country by geo distance datacount.dist<-read.csv('country_dis.csv')rownames(count.dist) <- count.dist[, 1] ## set rownames
count.dist <- count.dist[, -1]#applying a root transformation to make reduce order of magnitude #for pleasant plotting, this still preserves relationships between #countriescdist<-as.dist(sqrt(count.dist))
c.hc<-hclust(cdist, method = 'single')dend2 <- c.hc%>%
as.dendrogram %>%
set("branches_k_color", k=10) %>% set("branches_lwd", 2.4) %>% set("labels_cex", c(.85))#plot tanglegram to visually inspect differencestanglegram(dend, dend2, main_left = "Economic Distance", main_right = "Geograhic Distance", margin_inner = 6)
不同的距离度量如何影响经济体之间的关系,资料来源:CEPII(geo data),
上面的七巧板显示了随着距离度量的变化,国家和地区在等级中的位置是如何变化的。黑线表示它们的聚类结构已经发生了变化,而彩色线则相反。我们观察到,从一种方法转到另一种方法,只有秘鲁和哥伦比亚仍然聚集在一起(绿线),这有效地表明,在试图理解全球经济时,经济距离产生了一个独特的和更丰富的表示。
虽然这种方法确实为我们提供了一个起点,但更有用的是能够查看集群如何以及是否随时间发生了变化。这种变化可以通过两种方式发生,一是通过总体等级聚类结构的扁*化,表明全球经济可能变得更加同步(宏观)或更好或更坏;二是通过组成国家之间的聚类(微观)。
跨时间比较
到目前为止,我们在分析中一直使用 2019 年 2 月的经济变量,现在我们为 2020 年 3 月的相同地区引入相同的变量集。我们将用另一个七巧板来想象这个。
econ20<-read.csv('fin_indicators0220.csv')#naming the rowsecon.df20<-econ20[,-1]
rownames(econ.df20)<-econ20[,1]#scaling variables for use in calculation of Euclidean distanceecon.df.scaled20<-scale(econ.df20)#creating pairwise Euclidean Distance Matrixecon.dist20<-dist(econ.df.scaled20, method='euclidean')#creating hierarchical clustering objectecon.hc20<-hclust(econ.dist20)dend3 <- econ.hc20%>%
as.dendrogram %>%
set("branches_k_color", k=10) %>% set("branches_lwd", 2.4) %>% set("labels_cex", c(.85))tanglegram(dend, dend3, main_left = "02/19", main_right = "03/20", margin_inner = 6)
截至 2019 年 2 月 cs 03/20 的经济集群
这一次我们可以看到,在一年的时间里,全球经济格局发生了巨大变化,其中明显的混淆变量是 COVID19 的传播。仅有的仍然聚集在一起的国家是印度尼西亚、印度和菲律宾,它们通常被认为是新兴市场国家。通过研究,我们还发现,土耳其已经脱离了阿根廷,这是该国从一年前的货币危机中复苏的结果,而阿根廷仍是经济最不相似的国家,因为它徘徊在又一次违约的边缘。其他明显的观察结果包括,最相似的区域变得“更加接*”,这也可能是由于当前疫情局势造成的外部冲击,其影响波及全世界。随着全球各国继续抗击其蔓延,经济影响已经不分青红皂白。几乎所有受影响的地区都经历了增长收缩、消费减少、创纪录的失业率以及某种形式的货币或财政刺激。经济距离很好地涵盖了这些措施中的大部分。
结论
虽然层次聚类缺乏其监督学习对应物的预测能力,但它仍然是探索基础数据中的模式和结构的重要工具,这与经济距离相结合,在形成对全球经济的看法方面形成了强大、直观和可解释的框架,无论最终目标是社会、政策还是金融性质。
免责声明 : 本帖纯属个人观点和看法的表达。它不代表建议,也不反映我的雇主的观点。
深度学习的指针网络
这篇文章讨论了 Oriol Vinyals,Meire Fortunato 和 Navdeep Jaitly 的“指针网络”。这项工作提出了一种生成可变大小输出序列的神经架构,该输出序列是输入序列的标记/索引序列。因为输出序列的长度取决于输入序列的大小,所以它不能由基于 RNN 的序列到序列模型和神经图灵机来解决。指针生成器网络被应用于解决各种组合优化和组合搜索问题,例如著名的*面旅行商问题(TSP)、Delaunay 三角剖分、凸包问题和排序变长序列。指针网络现在还被应用于文本摘要问题,以从文档中提取句子,如在“具有强化选择句子重写的快速抽象摘要”中由延-陈春和莫希特·班萨尔所提到的。这些网络很好地概括了序列长度,超出了网络的训练。
指针网络可以说是由 Bahdanau et al. 2015 的注意机制衍生而来。为了理解指针网络,让我们首先理解序列对序列模型,基于注意力的模型序列对序列模型,然后最后是指针网络。
基于 RNN 的序列间模型:
在序列到序列模型中,我们使用两个 rnn(LSTM/GRU ),一个编码器编码输入序列,另一个供解码器产生输出序列。考虑一个具有四个点(P1、P2、P3 和 P4)的凸包的例子,如下图所示。
{图 1}:基于编码器(蓝色)、解码器(紫色)的序列对序列模型,用于具有四个点的凸包问题
在该图中,带有输入序列(、【易】)的蓝色方框代表编码器,紫色方框代表解码器。编码器的最后一个隐藏状态输出和“→”开始令牌被馈送到解码器模型的第一个时间步长。然后,对于下一个时间步长,来自前一个时间步长的输出连同上一个时间步长的隐藏状态一起被馈送,以产生当前时间步长的输出。在这种情况下,时间步长 T0 的输出["1"],作为输入提供给下一个时间步长,以产生输出["4"]。从图中我们可以看到,输出序列完成的凸包将是["1 "," 4 "," 2 "," 1"]。
{方程 1}:参数模型的条件概率方程(RNN)
在上面的等式中,Pi={P1,P2…Pn}是“n”个向量的序列,Ci={C1,C2…Cn}是从 1 到 n 的索引序列。在上面的图 1 中,“n”将是 4。
如等式 1 所示,RNN (LSTM/GRU)可用于模拟条件概率函数。RNN 在每个时间步长“I”被馈送 Pi,直到到达序列的结尾,其由“←”结束标记。
在这种类型的序列模型中,我们需要为序列的“n”长度的不同值训练单独的模型。
注意力网络
上面介绍的标准序列到序列模型通过采用上一时间步的隐藏状态,使用输入序列的固定表示来生成输出序列。固定描述限制了可以流经生成解码器 RNN 模型的信息量和计算量。因此,为了解决这个问题,Bahdanau et al. 2015 提出了注意力网络。在基于注意力的顺序模型中,通过给输入标记赋予权重,在解码器的每个时间步长形成上下文向量。通过将注意力权重乘以每个输入标记的隐藏状态表示并对它们求和来计算该上下文向量。有几种方法来计算注意力权重,如训练神经网络来并行计算序列到序列模型、点积和缩放点积的这些权重。
让我们将编码器隐藏状态表示为(e1,e2,e3 …,en)并将解码器隐藏状态表示为(d1,d2,d3 …)。,dn)。时间步长“I”的上下文向量的计算如下式 2 所示。
{等式 2}:上下文向量计算
这里,softmax 函数将长度为“n”的输入序列上的向量“u”归一化为输入序列的注意力权重。注意力权重乘以每个编码器隐藏状态“e”并求和以形成上下文向量。
关于注意力模型的更多细节可以从这篇博文中找到。
它们是自然语言处理、机器翻译和大多数最新技术(SOTA)领域的最新发展
towardsdatascience.com](/attention-networks-c735befb5e9f)
在大多数顺序模型上,该模型的性能明显优于普通的顺序对顺序模型。但是它不适用于输出字典大小依赖于输入的问题。
指针网络
指针网络可以被认为是注意力模型的简单扩展(而不是缩减)。
{图 2}:图 1 中凸包问题的指针网络解。
在每个解码器时间步骤中,生成网络产生一个向量,该向量调制输入上基于内容的注意力权重。这些权重是通过采用字典大小等于输入序列长度的 softmax 运算来计算的。
在指针网络中,这些注意力权重/掩码不再用于计算下一时间步的上下文向量。这些权重被认为是指向输入序列的指针。具有最高权重的输入时间步长被认为是该解码器时间步长的输出。
{方程式 3}:指针计算
从等式 3 可以看出,对“u”的 softmax 运算不再用于计算上下文向量,以作为信息馈送给当前的解码器步骤。softmax 操作的输出指向具有最大值的输入令牌。
考虑解码器步骤的第一步的输出是“1 ”,如图 2 所示。然后,对于下一个时间步长,输入[X1,Y1]的相应输入令牌表示连同先前时间步长的解码器隐藏状态表示被馈送到网络,以计算当前时间步长的隐藏状态表示。当前步骤的输出是“4”,因此[ X4,Y4 进入下一步骤的输入。
应该理解,简单的 RNN 序列到序列模型可以通过训练直接指向输入目标索引来解决这个问题。然而,根据推论,这种解决方案并不考虑输出映射回输入索引的约束。如果没有这些限制,在更长的序列中,预测必然会变得模糊。
指针网络的应用
1)文本摘要
从实验中可以看出,结合了抽象和提取方法的研究论文比只使用一种方法训练的论文给出了更好的总结结果。
图 3 显示了通过结合提取代理和抽象的文本摘要算法的结构图。提取器使用指针网络从文档的完整句子集中提取一系列独特的句子。
2)凸包问题
在计算几何中,寻找有限数量的点的凸包是一项众所周知的任务,并且有几种精确的解决方案可用。为了使用完全数据驱动的方法来解决这个问题,实验发现指针网络比传统的 RNN 模型给出更好的结果。
在图 4 中,点序列 P[2,4,3,5,6,7,2]代表凸包的边界。指针网络以 P[1,2,3…10]为输入,Cp[2,4,3,5,6,7,2]为输出进行训练。由于输出指向输入序列的索引,指针网络模型提供了比其他神经网络模型更好的结果。
3) Delaunay 三角剖分
*面上 P 个点集的 Delaunay 三角剖分是这样一种三角剖分,使得每个三角形的每个外接圆都是空的;即在其内部没有来自 P 的点。
[图 5]: Delaunay 三角剖分
在上面的图 5 中,每组三个点[(1,2,4),(1,4,5),(1,3,5),(1,2,3)]代表点集 P1 的三角剖分集。序列的顺序在这里并不重要;它只是按字典顺序写的。指针网络可以在这里被训练,因为我们知道索引的输入和输出序列。
4)旅行商问题
TSP 出现在理论计算机科学的许多领域,并且是用于微芯片设计或 DNA 测序的关键算法。琐碎的 TSP 问题是寻找恰好访问每个城市一次并返回起点的最短可能路线。假设两个城市之间的距离在每个相反的方向上是相同的。
输入-输出对具有与凸包问题类似的格式。在一个*面中有“n”个不同的城市或点,我们必须在最短的时间内到达每个城镇。输入序列将是杂乱的“n”个点,没有任何顺序,输出序列将是相同点的有序序列,表示在最短时间内行驶。可以通过取“n”的不同值的输入和输出来训练指针网络。还发现指针网络甚至对于它没有训练过的“n”的那些值也能很好地推广。
引文
Oriol Vinyals,Meire Fortunato 和 Navdeep Jaitly 的指针网络
由延-陈春和莫希特·班萨尔用加强选择句子重写的快速摘要
有用的链接
指针网络的张量流实现。支持多线程数据管道,以减少 I/O 延迟。训练一个…
github.com](https://github.com/devsisters/pointer-network-tensorflow) [## shirgur/PointerNet
指针网络的 Pytorch 实现。通过在…上创建帐户,为 shirgur/PointerNet 的发展做出贡献
github.com](https://github.com/shirgur/PointerNet) [## ChenRocks/fast_abs_rl
这个存储库包含我们的 ACL 2018 论文的代码:具有增强选择的快速抽象概括…
github.com](https://github.com/ChenRocks/fast_abs_rl)
理解政治推特
利用推特情绪分析了解全球政治氛围。
邓肯·格拉布斯和梅根·曼迪
Kon Karampelas 在 Unsplash 上拍摄的照片
当你想到政客和推特时,很可能会想到唐纳德·川普总统。自 2015 年开始竞选以来,特朗普因许多人所说的贬损、负面和有点煽动性的推文而臭名昭著。事实证明,即使只有 280 个字符,他也可以传达一系列的情感,“事实”,以及,信不信由你,观点。看看 NYT 的这篇文章,你会明白我们在这里的意思。说真的,甚至 Vox 也对此进行了研究,因为特朗普发了很多推文,他们可能真的在那里。
在过去的十年里,政治推特账户的使用量激增。如今,似乎每个政治领导人和他们的整个家庭都有一个 Twitter 账户,他们都觉得有必要在*台上与我们所有人分享他们的观点。事实上,许多领导人使用 Twitter 作为他们与公众沟通的主要方式,而不是地址或简讯。然而,这引起了一些有趣的问题。推文通常没有经过审查,当来自“个人”账户而不是“官方政府运营的通信”时,它们可能会非常两极分化和/或有问题。
所有这些让我们思考——我们能测量政治家和世界领导人在 Twitter 上的交流方式吗,那会是什么样子?
研究问题
我们想调查世界各地不同的领导人是如何使用 Twitter 的。具体来说,我们围绕以下问题进行分析:
- 在 Twitter 上,某些世界领导人/政治家总体上比其他人更积极吗?
- 随着时间的推移,对某些组织/主题的看法发生了怎样的变化?某些领导人改变立场了吗?
- 政治推特中使用频率最高的关键词是什么?这些在不同国家有什么不同?
数据
资料来源:梅根·曼迪
我们的第一步是收集数据。我们知道,我们希望在一段较长的时间内收集世界各地各种领导人的推文。我们决定主要关注在任的国家元首,尤其是如果这个国家不是说英语的话。考虑到这一点,我们登陆了世界上一些最大的经济和政治强国的 12 个账户,并查看了过去 5 年 的所有推文,从 2015 年 1 月 1 日到 2019 年 12 月 31 日。 我们选取了以下几位领导人进行分析: 乔·拜登、伯尼·桑德斯、唐纳德·川普、贾斯廷·特鲁多、安德烈斯·曼努埃尔·洛佩斯·奥夫拉多尔、鲍里斯·约翰逊、埃马纽埃尔·马克龙、本杰明·内塔尼亚胡、穆罕默杜·布哈里、西里尔·拉马福萨、纳伦德拉·莫迪、斯科特·莫里森 。收集这么多数据绝非易事。首先,我们尝试了 Twitter 的本地 API,但很快意识到他们对一个人可以下载多少数据有非常严格的限制。经过一番研究,我们决定使用优化的 Get Old Tweets 3 库。它作为 Twitter API 的包装器,允许几乎无限制的 tweet 下载,而且它已经用 Python 编写了!我们总共下载了超过 70MB 的原始推文数据,这些数据都可以在我们的库中找到。
下载完数据后,我们专注于清理推文并翻译它们。我们用正则表达式删除了 URL、@标签和图片链接,并将所有推文规范化为小写。然后我们用谷歌翻译 API 翻译非英语推文。
我们如何清理推文的代码和正则表达式
分析
首先,我们需要一些基线信息。我们测量了这 5 年间每位领导人发微博的次数,以及*均微博长度(以字符数表示)。唐纳德·特朗普发的微博最多并不奇怪,但我们惊讶地发现,*均而言,乔·拜登发的微博最长。虽然 Twitter 的每条推文有 280 个字符的限制,但几乎每位领导人的*均字符数都低于 200 个。看起来最常见的方法是多发布短推,少发布长推。
接下来,我们想看看最有影响力的英语客户使用的一些关键词。我们将这种分析局限于英语帐户,因为在翻译过程中,文本数据可能会发生很大变化,无法准确反映单词的用法。词云是一种很好的方式,可以很容易地将某些关键词在大量文本中突出出来。我们使用了 WordCloud Python 库,通过输入原始推文数据来生成图像。
我们如何生成单词云的示例代码
使用翻译 API 翻译不同语言的示例代码
与其他领导人相比,特朗普提到自己的名字要多得多。莫迪和约翰逊非常频繁地提到自己的国家。
在收集了基线数据后,我们将注意力转移到了情感分析上,并决定使用 TextBlob Python 库。这为我们提供了每条推文的两个数据点, 极性 和 主观性 。极性值的范围在-1 和 1 之间,其中分数 1 表示文本通常为正,分数-1 表示文本通常为负。主观性得分更容易理解,范围从 0 到 1,其中 0 分是客观或事实陈述,1 分更多地属于个人观点、情感或判断。TextBlob 使用训练有素的 NLP 模型来计算这一点,该模型使用来自各种来源的人类标记的文本数据。
首先,我们比较了 2019 年最后 6 个月世界领导人的*均情绪得分。这一时间限制是由于翻译中的瓶颈。虽然我们有所有领导者 5 年的原始推文数据,但由于 API 的限制,我们只能翻译 6 个月的推文。
6 个月期间(2019 年 7 月 1 日至 2019 年 12 月 31 日)的推文数据
上面我们可以看到 没有一个 的领导者*均极性小于 0,这是个好消息。*均而言,我们的领导在推特上发布积极的事情!也就是说,存在着广泛的主观性。记住 0 主观性是最低的,指的是事实陈述。虽然主观性得分高于 0 并不意味着说谎,但它们确实意味着该文本在陈述一种观点或个人信仰。莫里森、莫迪和特朗普在这一类别中都得分很高。相比之下,特鲁多、奥夫拉多尔和拉马福萨的*均主观性很低。
“再过三天,新的一年。新的十年。可以肯定的是,未来十年,21 世纪出生的人将在国家进步中发挥关键作用。在今天的#MannKiBaat 活动中,我向印度的年轻人致敬,他们充满活力。”—纳伦德拉·莫迪
在所有世界领导人中,莫迪的极性最高。事实证明,他在推特上使用高度积极的方言来表达里程碑和未来的进展。
“我们看到反犹太主义和仇恨犯罪在上升。我们看到一个小孩因为是拉丁人而被撞倒。我们见过有人因为是犹太人而被刺伤。我们看到有人因为是穆斯林而被攻击。如果有一个团结起来反对偏见和种族主义的时刻,那就是现在。”—伯尼·桑德斯
另一方面,伯尼·桑德斯的极性较低,经常以更加消极中立的语气在推特上谈论美国面临的困境。
特定比较
数据来自 2015 年至 2020 年 5 年间的 700 条推文
绘制特朗普总统和拉马福萨总统的极性与主观性展示了两位领导人推文之间的明显差异。特朗普在过去 5 年的主观性和极性涵盖了极性和主观性的整个范围。虽然有一群推文被认为是极性中性的,主观性居中,但他的大部分推文是主观性较高的。相反,Ramaphosa 的推文大多集中在 0-0.5 的主观性范围内,这意味着它们更接*客观。
我们决定围绕政治账户的某些关键词/组织扩展我们的情感分析。为了了解政治家们是如何讨论一个普遍话题的,我们选择了关键词“贸易”为了获得关于组织观点的更具体案例,我们查看了 Trump 如何在推特上发布关于福克斯新闻频道和 CNN 的消息。
在查看关键词“贸易”时,我们比较了贾斯廷·特鲁多和川普的数据。作为邻国的领导人,他们之间有着悠久的贸易历史,包括北美自由贸易协定和美国管理认证协会,加拿大和美国在这个问题上的情绪是一个有趣的对比。特鲁多稳步地在推特上以积极的态度谈论贸易;而特朗普在贸易方面通常是消极或中立的。虽然不可能确定每条推文的确切内容,但我们确保排除了提到中国和 T2 贸易的推文。此外,我们发现特朗普推文的积极倾斜趋势与 USCMA 的签署之间存在相关性,us CMA 是他上任后立即推动的北美自由贸易协定的替代物。
毫无疑问,福克斯新闻频道和 CNN 吸引了不同的政治派别。福克斯新闻频道倾向于保守派和共和党,而 CNN 倾向于温和派/自由派和民主党全国委员会。在分析特朗普对这两家媒体的看法时,他的观点有明显的区别。川普一直在推特上对福克斯新闻频道持中立到积极的态度,正如人们所料,然而他的推特几乎总是对 CNN 持中立到消极的态度。
问题与改进空间
为了恰当地调查推特上 世界 领导人的情绪,选择一些用英语以外的语言发推特的政治家/国家元首(马克龙、洛佩斯·奥夫拉多尔、莫迪)是合乎逻辑的。然而,这带来了翻译的负担,由于对谷歌翻译 API 的请求限制,翻译最终比预期的更加复杂。我们试图为至少 8 个不同的账户翻译*均 2MB 的 twitter 数据,但是我们只被允许每台电脑每天翻译大约 100KB 的数据…不太理想。我们通过减少账户数量,限制分析的时间范围,从而减少需要翻译的推文数量,并利用 VPN,解决了这个问题。
我们如何从更大的数据集中缩小推文的日期范围的代码
然而,障碍并没有就此结束——在分析之前清理推文被证明是极其困难的。在一条推文中出现的符号(如@或#)和图片的 URLs 链接之间,很难区分哪些要从推文中清除,哪些要保留。我们希望尽可能简单地分析推文,但我们不想在这个过程中影响情感分析的准确性。
我们承认这一分析有改进的余地。从数据本身开始,我们分析的用户都是人工选择的。这些用户中的一些人比其他人发的微博多得多,所用语言的差异都使分析变得复杂。随着推文翻译、分析的时间范围和数据量更加一致,我们可以在未来获得更精确的见解。
结论
虽然我们的分析并不完美,但我们惊讶于从这些数据中可以获得如此多的洞察力。世界各国领导人在推特上表达的情绪存在明显的、可以解释的差异。无论是在贸易、政策还是世界大事上存在分歧,都可以通过推特情绪记录下来。如果有什么不同的话,这表明推特越来越多地反映了一个政治家真正的信仰,有时比任何其他来源都更具启示性。
感谢阅读!如果你想查看所有的源代码或数据,你可以在 GitHub 这里 查看。
了解投资组合优化
卢卡斯·布拉塞克在 Unsplash 上的照片
从概念上理解优化投资组合意味着什么
来自《走向数据科学》编辑的提示: 虽然我们允许独立作者根据我们的 规则和指导方针 发表文章,但我们并不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语 。
量化金融(至少在投资组合管理方面)就是寻找最优投资组合。对于给定的风险水*,我们希望确保获得尽可能多的回报。
在定量金融学中,风险被视为一种资源。将你的投资组合暴露在风险中会随着时间的推移产生回报。换句话说,预期回报是我们承担不确定性(围绕通货膨胀、经济等的不确定性)所得到的补偿。).
有几件事要记住:
- 投资回报的风险和不确定性传统上由其历史回报的标准差(也称为波动性)来表示。
- 风险和收益之间存在*似的正相关关系。一项资产的波动性越大,其历史回报率通常就越高。
- 然而情况并非总是如此。有一些波动性很大的资产,相对于其他波动性类似的资产,其已实现回报一直乏善可陈。回报率较低的一个潜在原因是,这些资产还有其他价值,比如与股市的相关性较低。随着股市波动而波动(并提供正预期回报)的投资因其对冲能力而备受青睐。
我在下面的文章中深入探讨了投资风险,所以如果你想深入研究,请读一读:
关于我们如何对投资风险建模以及这是否有意义的哲学探索
towardsdatascience.com](/understanding-investment-risk-3882c58e00e0)
理解投资组合优化的类比
如果你只有一天时间去东京旅游(之前从未去过),并且你需要为这一天制定一个行程,你会怎么做?你可能会从列出可能的活动开始。然后你会挑选四五个可能是最有趣的活动。
埃里克·伊斯曼在 Unsplash 上的照片
但是对东京不熟悉,你担心你可能会尝试一项活动而不喜欢它——那会浪费你宝贵的时间。但是你也想尝试一些新的东西。做一些你已经在家做过的事情(比如在咖啡馆阅读)是一种乐趣,但是没有潜力变得超级有趣。做一些完全新奇的事情可能会非常有趣,但也可能会很糟糕。
因此,对于每一项活动,你不仅需要考虑它可能有多有趣(预期回报),还要考虑它最终变得不太有趣的风险(不确定性)。
你也可以选择彼此不相关的活动。因为如果你最终一遍又一遍地做同一类型的活动,并且你发现它实际上一点也不有趣,那么你的一天就会被毁了。因此,多种多样的活动类型会让你更有可能最终获得快乐。
因此,我们可以将您的旅程创建过程总结如下— 您希望创建一篮子活动,根据您的个人风险承受能力(您个人对新体验和熟悉体验的渴望)最大化您可能获得的乐趣。
您评估和权衡一项活动与其他活动的方式如下:
- 权衡每项活动本身的潜在乐趣和你对其乐趣的不确定性。
- 将每项活动与其他活动进行权衡,确保你有多种活动类型(多样化)。例如,你不会希望每项活动都涉及食物(以防你发现你不喜欢日本食物)。此外,你吃得越多,越饱,下一餐就越难享受。换句话说,你希望各种活动之间的相关性低,这样做一项活动不会影响你对下一项活动的享受。
一旦你完成了,假设你付出了一些努力,你可能会相信你的最终清单最大化了你那天开心的可能性。
简而言之,这就是投资组合优化—
对于给定的风险水*,我们希望最大化我们投资组合获得正回报的概率。
我们通过筛选金融资产来做到这一点,就像我们对活动所做的那样:
- 通过权衡他们的预期回报和波动性(记住波动性是我们估算风险的方式)。相对于波动性而言,预期回报越高,我们就越确定投资的回报随着时间的推移将是正的。
- 通过考虑资产收益之间的相关性。同等条件下,我们希望资产尽可能不相关。这使多样化的好处最大化。下一节将详细介绍我们为什么关注多元化。
多样化的好处
多样化有时被称为经济学和金融学中唯一的免费午餐。要了解原因,让我们举个例子。
如果我们持有一只股票 100 美元,预期在下一年回报 10%,那么我们的预期回报是 10 美元。如果我们再增加 100 美元的另一只股票,它的预期回报率也是 10%,那么我们 200 美元的投资组合的回报率现在翻了一番,达到了 20 美元。
相关性= 1 的情况
双资产投资组合的投资组合风险(标准差)可以计算如下:
**stdev =
sqrt(w1^2*stdev1^2 + w2^2*stdev2^2 + 2*w1*w2*p*stdev1*stdev2)**where
w1 is weight of stock 1
w2 is weight of stock 2
stdev1 is standard deviation of stock 1
stdev2 is standard deviation of stock 2
p is correlation between stock 1 and stock 2
假设我们投资组合中的两只股票的标准差都是 10%。
因此,当我们只有 100 美元投资于一只股票时,我们投资组合的风险是 100 美元* 10% = 10 美元。这意味着我们预期我们的回报是 10 美元,但是围绕着它有一个不确定的圆锥。
不确定度的宽度由其西格玛(也称为标准偏差)描述。在这种情况下,sigma 等于 10 美元。sigma 越大,我们就越不确定我们的投资组合在任何特定年份的回报(我们的投资组合价值波动就越大)。
如果你不熟悉正态分布及其参数,可以看看下面的帖子:
了解如何使用它,以及为什么它对数据科学和统计学如此重要
towardsdatascience.com](/understanding-the-normal-distribution-with-python-e70bb855b027)
我们还假设两只股票的走势完全一致(意味着它们的相关性为 1.0)。我们可以使用上面的公式计算我们投资组合的风险:
stdev =
sqrt(w1^2*stdev1^2 + w2^2*stdev2^2 + 2*w1*w2*p*stdev1*stdev2)Portfolio Risk
= sqrt(0.5^2*0.1^2 + 0.5^2*0.1^2 + 2*0.5*0.5*1*0.1*0.1)
= 0.1
所以我们两只股票投资组合的风险仍然是 10%。用美元计算,我们的 sigma 现在是 200 美元* 10% = 20 美元。所以我们从 100 美元的投资组合,预期收益为 10 美元,sigma 为 10 美元,到 200 美元的投资组合,预期收益为 20 美元,sigma 为 20 美元。(我互换使用 sigma 和 risk)
所以基本上没什么变化。如果我们将股票 1 的持有量增加一倍(而不是购买股票 2),我们将会获得完全相同的结果。这是因为我们假设股票 1 和股票 2 完全相关(p=1)。因此,拥有其中之一或两者都是一回事。
现在让我们看看如果股票 1 和股票 2 不相关会发生什么。
相关性= 0 的情况
我们 200 美元投资组合的预期回报仍然是 20 美元。但现在我们的风险是:
stdev =
sqrt(w1^2*stdev1^2 + w2^2*stdev2^2 + **2*w1*w2*p*stdev1*stdev2**)***Since p=0, the third term drops out!***Portfolio Risk
= sqrt(0.5^2*0.1^2 + 0.5^2*0.1^2 + 2*0.5*0.5***0***0.1*0.1)
= 0.0707
我们的投资组合风险已降至 7.1%。这意味着以美元计算,我们 200 美元的投资组合的 sigma(也称为风险)为 14.14 美元,大大低于我们之前的 sigma 20 美元。
因此,与完全相关的情况和持有 200 美元股票 1 的情况相比,我们能够在保持相同预期回报的同时降低*三分之一的风险!一顿真正的免费午餐。
这显然是一个典型的例子。事实上,很难找到一堆完全不相关的资产都有很高的预期回报。关于相关性和多样化的更深入的讨论,你可以阅读我之前的博客:
为什么投资不相关的资产是值得的
towardsdatascience.com](/understanding-correlation-and-diversification-661c19a26555)
最优投资组合
因此,最优投资组合是以这样一种方式组合我们的候选资产,即对于给定的风险水*,投资组合获得正回报的概率最大化。
为什么在给定的风险水*下?如果我们唯一想做的事情是最大化我们的正回报概率,我们可以投资像货币市场基金这样的超低风险资产。从技术上来说,我们几乎可以保证赚钱,但那只是几个便士。记住你需要承担一些风险来获得回报。
相反,我们希望(尽可能)确定的是,对于特定的风险水*,我们投资的投资组合能够最大化我们获得正回报的机会。做到这一点的投资组合,也称为最优投资组合,是预期回报率最高的投资组合(或者用统计学术语来说,是 Z 值最高的投资组合)。
所以我们需要解决的优化问题是:
For a given level of risk, solve for the weights, W, that:**Maximize W.T @ E****Subject to:
W.T @ Cov @ W = (target risk)^2
and sum(W) = 1**Where **W** is a vector representing the weights of the asset in our portfolio.
**E** is a vector representing the expected returns of the asset.
**Cov** is the covariance matrix of the asset's returns.
**@** denotes matrix multiplication.
**.T** denotes the transpose operation.
简单地说,我们希望在以下条件下最大化预期收益(W.T @ E ):
- 投资组合方差(W.T @ Cov @ W)等于目标方差。记住方差是风险的*方(因为风险等于标准差)。这就是让我们达到特定风险目标的约束。
- 权重总和为 1。在这种情况下,我们允许短重(负重量)。如果我们不想要负权重,我们需要添加一个额外的约束。如果我们没有这个条件,优化就会炸或者吐槽乱码。
我们可以用两种方法来解决这个问题——用优化器或者分析方法。允许卖空的投资组合优化的酷之处在于,它有一个解析解,我们只需要一些矩阵代数就可以实现。
直到下次
这就是本文的全部内容。我已经写了一些代码来分析性地运行优化。但是我想让这篇文章停留在概念层面。代码和例子将在以后的文章中发布!在那之前干杯!
下一部分:
你所需要的只是一些矩阵代数来做出最优投资组合
towardsdatascience.com](/portfolio-optimization-with-numpy-93e1428525a5)
了解 AB 测试中的功耗分析
AB 测试中你需要知道的统计基础知识的通俗易懂的解释
图片来源:https://www.crazyegg.com/blog/ab-testing/
你对你的产品有一个很好的想法,你知道它会增加转化率并带来商业价值。但是你能说服管理层吗?而且即使你自己也确信,你能量化你对这种确信的信心吗?
幸运的是,有了 AB 测试,你可以去掉很多猜测。或者至少你可以做一个量化的猜测,并确定你的猜测有多“可猜”。用统计学的术语来说:你可以测试你的想法真的很棒的假设,并为你看到的结果设定一个置信度。
如果你是一个产品所有者,对 AB 测试如何工作有一些大概的了解,并理解一些常见的应用和问题,会很有帮助。如果你自己运行测试,理解 AB 测试背后的基本统计和直觉在执行实验设计时是至关重要的,即使你有一个完全设置好的实验引擎。实验设计回答了以下问题:
- 我应该为我的测试收集多少数据?
- 我应该运行我的测试多长时间?
- 我的页面没有很多访问者——这在进行实验时有关系吗?
在这篇文章中,我将讲述 AB 测试背后的统计直觉,实验设计和一些简单的实际应用。让我们直接开始吧。
什么是 AB 检验,它与假设检验有什么不同?
统计假设检验是接受或拒绝原假设的程序,简称 H0。零假设表示关于总体参数的假设,被视为默认假设。举个例子:我们认为一枚硬币是公*的。如果把硬币抛 100 次,我们能确定这个假设是否合理吗?
****AB 测试是从总体中抽取两个随机样本,一个对照样本和一个变异样本,并确定这两个样本之间的差异是否显著。注意,有许多形式的实验(ABC 和多元测试),但我们今天只讨论 AB 测试。
在本文的其余部分,我们将不断地使用术语控制和变体来进入 AB 测试的思维模式。
了解术语:零假设,错误类型
现在让我们把它放在测试一个你想在你的网站上发布的新特性的背景下。你将有一个控制和变异版本的测试,其中变异样本有新的特点。以下是一些术语:
- 零假设,H0 是当你的设计变化不会对你的测试变化产生影响的时候。如果你没有拒绝零假设,你会表现得好像零假设是真的,你不应该推出你的新功能。
- 替代假设,H1 是零假设的替代假设,设计变更将对您的测试变化产生影响。如果你拒绝零假设,你接受替代假设,你应该推出你的新功能。
- ****第一类错误:你拒绝了空值,当你不应该拒绝的时候。在网络世界中,这意味着你正在启动一个功能变化,而它实际上对转化没有积极的影响。你的成本就是开发的成本。
- ****第二类错误:当您不拒绝 null,但实际上测试和控制之间存在正差异时。这是当你决定不推出一个新的功能,而实际上是有区别的。假设变化是积极的,您的净成本将是推出该功能的潜在成本减去开发成本。
二项式和正态分布是如何涉及的?
为什么我们要用二项分布?在 AB 测试中,您试图确定变体中的成功数量是否与控制中的显著不同。一系列试验的成功次数(通常是转化或无转化结果)可以使用二项式分布进行充分测量,其中 X 轴是转化次数(或转化率), Y 轴是概率。
再补充一下,中心极限定理说,如果样本量足够大,那么分布将遵循正态分布。
什么才够大?一个经验法则是,如果样本量乘以概率(或转换率)大于 5,就应该遵循正态分布。
分布如下图所示,可以用*均值和标准差来表示。
现在,让我们把它带回控制和变异样本测试。
下图显示了控制分布和变量分布。对照将代表无效假设,而变异将代表替代假设。临界值区域线代表您拒绝零假设的点。为了说明的目的,我们可以说,如果 H1-H0 >= 0.5,我们将拒绝零假设(H1 是替代假设,H0 是零假设)。该变量的实际*均值为 0.7。
如果零假设是真的,你拒绝零假设,有可能你犯了第一类错误。这种可能性用灰色阴影区域表示。另一方面,如果零假设不成立,并且你没有拒绝零假设,那么你就有可能犯第二类错误。这个机会用红色阴影区域表示。
我希望上面的插图能给你一个更好的直觉,让你更自信地做出判断,变体是否真的与对照不同。让我介绍另一个例子来直观地说明样本量。
样本量越大,样本越有可能代表实际总体。用统计学的术语来说,你的标准差会更小,你的分布会更窄。下图具有相同的分布均值,但是标准误差更小,或者样本量更大。代表 I 型和 II 型误差的阴影区域变小。如果你拒绝零假设,你现在对不犯 I 型错误更有信心,因为你的样本量更大了。
这种分布有更多的样本,因此分布更窄
既然我们对样本大小和测试分布有了一些直觉,那么当我们谈论设计实验时要考虑的统计因素时,我们可以有更强的直觉。
实验设计和功耗分析
当你设计一个测试时,你想要准备你的实验,以便你可以自信地对变异样本中的差异(或不存在差异)做出陈述,即使差异很小。如果你的网站每天有数百万的独立用户,那么即使 0.5%的转化率差异也会对你的收入产生重大影响。如果是这样的话,你要抓住它。
设计实验时,需要考虑四个统计因素:
****1。最小可检测效果大小和转换率:你想捕捉到什么样的效果,让实验物有所值?这是一个很容易通过衡量美元数额来回答的问题。当你将最小可检测效果乘以你的 N(用户总数)时,你将获得多少可测量的正面提升?例如,如果 1000 名额外顾客以*均 50 美元的购物篮结账,您将实现多少收入提升?
如果你是一家大型电子商务公司,每天有 1500 万独立访客访问你的商品页面,1%将对你的收入产生巨大影响。事实上,1%可能是一个很高的要求,因为你的网站可能已经通过许多产品开发进行了优化,以达到每天 1500 万用户。另一方面,如果你的网站每天只有 1000 次转换,那么 1%的提升就意味着 10 次额外的转换,这对你的收入来说是没有意义的。与此同时,如果特性变化解决了用户的一个明显的痛点,实现 10%或更多的提升可能是非常可行的。
效果大小关系到你的业务规模和客户的美元价值
在计算样本量时,转换率很重要。如果你网站的转换率是 30%,你不需要像你网站的转换率是 1%那样大的样本量
****2。样本量:你需要采集的样本量是多少?你的实验设计可能需要 100 万访问者,但是如果你的账户页面每天只有 5000 名访问者,那么是时候改变你的其他参数了。
****3。显著性:也称为 alpha,这是你在实验中检测到的差异是偶然的而不是来自实际差异的概率阈值。通常设置为 5%。
另一种说法是:如果测试中没有差异,你愿意在 5%的时间里犯第一类错误。
4。Power: 也称为(1 -Beta),可以解释为你的测试的强度,以检测你的变体中的实际差异。相反,Beta 是您的测试没有拒绝零假设的概率,而它实际上应该拒绝零假设。功率越高,第二类错误的概率越低。实验通常设置在 80%或 20%β的功率水*。
另一种说法是:如果测试中有差异,你愿意在 20%的情况下犯第二类错误。
****功率分析在给定其他 3 个部件的输入的情况下找到其中一个部件。
让我们重温一下我们熟悉的无效和替代假设分布,将它与我们迄今为止建立的直觉联系起来。给定固定的效应大小和样本大小,α和β成反比关系。你为你的实验设置的能量越多(beta 越小),alpha 区域就越大——没有免费的午餐。
作为一名从业者,基于业务的性质,从一个实验到另一个实验,统计的显著性和功效通常是固定的。你对每个新实验的关注是你的样本量是否足以让你的实验检测到你所关心的效应大小。换句话说,你的实验所需的最小样本量是多少?
计算样本量的公式为:
其中:
- p =*均转化率
- pA =控制概率或转换率
- pB =您计划检测的变异概率或转换率
- |z2| =力量的绝对 z 值
- 1.96 =显著性为 5%时的 z 得分(双尾检验)
实际上,有很多在线计算器可以帮助你估计所需的最小样本量。这个很好检验。
尝试使用计算器,看看改变统计因子的值如何影响所需的最小样本量。这种关系将遵循以下总结:
一旦你知道你的最小样本量是多少,你就可以评估你的网站是否有足够的流量来达到这个样本量,以及你应该进行多长时间的实验。
最短时间
假设您能够为您运行的页面上的每个变体捕获 100 万访问者,而所需的样本量仅为 300 万。然后你需要运行你的实验 3 天并得出结论吗?你可以主张敏捷性和更快的启动,但这不是一个好主意。您希望您的样本能够代表您的总体,并且可以合理地假设周末的行为与工作日的行为非常不同。见鬼,周四的行为和周五的行为非常不同。因此,建议运行实验至少 2 周,最少运行 1 周。
关闭
感谢阅读。我这篇文章的目标是帮助产品经理或被采访者(或者任何人,真的)清楚地谈论 AB 测试中的直觉。如果有不清楚的地方,请告诉我。
在接下来的文章中,我将介绍
- 拒绝零假设和置信区间的计算,我们也将讨论 1 对 2 尾检验
- 定义测试分析的度量和深度。
敬请关注!
来源:
帖子原载于 我的个人博客 。
了解 COVID 测试的精密度、召回率和准确度
使用不*衡数据集并通过新冠肺炎测试的模拟示例理解混淆矩阵
在最*回顾我的基本统计学知识时,我开始深入研究混淆矩阵的概念。这一次,我手头有一个例子,不幸的是,在过去的 4 个月里(今天是 2020 年 7 月 5 日),这个例子与新冠肺炎非常相关。我们将使用一个模拟的例子来帮助我们理解准确性、作为不*衡数据集中的一个度量、如何被误导。
方案
让我们假设一家公司发布了一个 COVID 测试套件,声称他们的测试在试运行中达到了“95%的准确率”。这个声明中有一个陷阱,组织没有在发布信中披露。该测试实际上并不是在内部进行医学检查,该套件被装配并默认将每个人标记为“无 COVID”。让我们看看如何确保我们不会被误导。
精确度、召回率和准确度是什么意思?
在我们继续之前,让我们快速看一下下图,它解释了术语精度和召回所代表的比率
https://upload . wikimedia . org/Wikipedia/commons/2/26/precision recall . SVG
精度:给出我们的“真阳性”在“我们做出的所有阳性预测”中的比率
回忆:给出我们的“真阳性”在“所有实际阳性值”中的比率(分母中的假阴性是我们 错误地 做出的阴性预测,实际上是阳性的)
准确性:另一方面,是“我们所有的正确预测(包括正面和负面)”与“我们所有的预测(所有样本)”的比率
回到我们的场景
让我们回到我们在第一部分讨论的案例。该公司开发的测试套件声称准确率达 95%。我们发现,*均来说,100 个人中有 5 个人在试用测试中实际上是COVID 阳性(我们认为这是我们在这里考虑的事实来源)。因此,该数据集是一个 不*衡数据集 (比例为 19:1)。
请记住,默认情况下,检测试剂盒会将 100 名患者中的每一位标记为“非 COVID”。
下面我们来画困惑矩阵:
理解混淆矩阵中的值:
- 我们没有任何真阳性( TP ),因为我们没有将任何人标记为“COVID 阳性”,我们也没有任何假阳性( FP ),因为我们没有对患者做出任何阳性预测(记得试剂盒被操纵)
- 我们确实有一个假阴性( FN ),因为实际上是 COVID 阳性的 5 个人被标记为 COVID 阴性。我们还有一个真阴性( TN ),因为所有 95 个非 COVID 阳性的人都被“正确地”预测为“非 COVID”(参见 catch ?这就是准确性度量的来源)
让我们逐一计算这三个指标的值:
- 计算精度:
准确度= (0 + 95)/ ( 0+95+0+5) = 95/100
如果我们只看准确性,这看起来像是测试套件的一个极好的创新,可以发布给更多的观众,但是等等,我们仅仅基于准确性度量来信任套件吗?让我们计算精度并回忆下一步
2.计算精度:
精度= (0+0)/(0) = N/A ( 不适用,以避免任何被零除的错误
3.计算召回:
召回= 0/(0+5) = 0
4.我们还可以看一下 F1 的分数,它是精确度和召回率的加权*均值。
F1 得分= 2(精确度召回率)/(精确度+召回率)
在这种情况下,这相当于不适用,因为精度为不适用
结论:
精确度和召回率都告诉我们,试剂盒被操纵了,因为分数要么是 0,要么是 N/A,这就对测试提出了足够的质疑。对于不*衡的数据集,选择不正确的评估指标很容易被误导。
延伸阅读:
- 在 wiki 页面上,您可以进一步阅读大量关于精确度和召回率的其他指标。
- https://machine learning mastery . com/tour-of-evaluation-metrics-for-unbalanced-class ification/
- https://towards data science . com/what-metrics-we-should-use-on-unbalanced-data-set-precision-recall-roc-e2e 79252 aeba
讨论测量测试准确度重要性的文章
- https://www.nature.com/articles/s41591-020-0891-7
- https://www . CNN . com/2020/04/28/health/coronavirus-antibody-tests-terrible/index . html
收到来自数据科学界的建设性反馈总是令人愉快的。请在下面发表您的评论,以帮助我们了解更多信息。
如果你觉得这很有用,请在 Medium 和 LinkedIn 上关注我。
理解主成分分析
五氯苯甲醚的分解,何时使用,为什么有效
图片作者:特里斯特·约瑟夫
机器学习(ML)是人工智能(AI)的一个子集,它为系统提供了自动学习和根据经验改进的能力,而无需显式编程。ML 中采用的算法用于发现数据中的模式,从而产生洞察力并帮助做出数据驱动的决策和预测。这些类型的算法每天都被用于医疗诊断、股票交易、运输、法律事务等领域的关键决策。所以可以看出数据科学家为什么把 ML 放在这么高的基座上;它为高优先级决策提供了一个媒介,可以实时指导更好的业务和更明智的行动,而无需太多的人工干预。
为了学习,ML 模型使用计算方法来直接从数据中理解信息,而不依赖于预先确定的方程。这些算法用于确定数据中的模式,并开发出将输入变量 x 最佳映射到目标变量 y 的目标函数。这里必须注意,目标函数的真实形式通常是未知的。如果函数是已知的,那么就不需要 ML。
因此,其思想是通过对样本数据进行合理的推断来确定该目标函数的最佳估计,然后针对当前情况应用和优化适当的 ML 技术。
图片作者:特里斯特·约瑟夫
这个任务看起来很简单。找到一个使用 x 并输出 y 的函数能有多难?有时候,在特定的情况下,这很简单。例如,假设我们想预测一个人的收入,我们仅有的支持变量是每年工作的小时数和他们的头发颜色。他们的头发颜色可能不会对他们的收入有太大影响,但是工作时间会有影响。因此,预测收入的函数将工作小时数作为输入。搞定了。
但情况并非总是如此。由于变量之间的关系,开发能够产生准确预测的模型是相当困难的。在大多数“真实世界”场景中,有多个输入变量同时存在。每个输入变量都会影响输出变量,但它们也会相互影响,理解这些复杂的关系有助于构建更好的模型。尽管上述示例很简单,因为只有两个预测变量可供选择,而且其中一个似乎与场景无关,但变量选择和模型拟合是开发适当函数的关键部分。
图片作者:特里斯特·约瑟夫
模型拟合是指让算法确定预测因素和结果之间的关系,以便可以预测未来值。模型的预测值越多,模型就能从数据中学到越多。然而,样本数据通常包含随机噪声;这一点,加上模型中预测因素的数量,会导致模型学习数据中的错误模式。如果试图通过添加更少的预测器来应对这种风险,可能会导致模型无法从数据中获取足够的信息。这些问题被称为过拟合和欠拟合,目标是确定简单性和复杂性之间的适当组合。
那么,如何才能在简单和复杂之间找到这种*衡呢?如果存在如此多的变量,以至于不可能合理地理解每个不同变量之间的关系,这就特别困难。在这种情况下,想法应该是执行维度缩减。顾名思义,它涉及使用各种技术来减少数据集中的特征数量。
这可以通过两种主要方式实现:特征排除和特征提取。特征排除指的是仅保留“可用于”预测输出的变量,而特征提取指的是从数据集中的现有变量开发新特征。将特征排除视为简单地删除或保留可能包含在模型中的变量,将特征提取视为从现有变量中创建新的(希望更少)变量。
图片作者:特里斯特·约瑟夫
主成分分析 (PCA)是一种特征提取方法,它以创建新特征的方式对变量进行分组,并允许丢弃不太重要的特征。更正式地说,PCA 是在一组数据中提供最大可变性的变量的线性组合的识别。
为了计算分量,这种方法利用线性代数中的元素(例如特征值和特征向量)来确定什么组合会产生最大方差。显式数学不在本文讨论范围内,但我会在文章末尾附上涉及这方面的建议材料。本质上,假设数据被绘制在图表上。PCA 方法将找到数据中沿每个轴(变量)的*均值,然后移动这些点,直到*均值的中心位于原点。
接下来,通过原点的直线将拟合数据,该直线使其自身和所有数据点之间的距离最小。确定最佳拟合线的另一种等效方法是绘制一条穿过原点的线,使投影点到原点的距离*方和最大化。这条线一旦确定,就被称为第一主成分。
图片作者:特里斯特·约瑟夫
初始线的斜率可以计算和处理,以产生最大化变化的最佳变量组合。即假设有两个变量,发现直线的斜率为 0.25。这意味着一个轴上每覆盖 4 个单元,另一个轴上就覆盖 1 个单元。因此,这两个变量的最佳组合是 4 部分变量 1 和 1 部分变量 2 。
假设第一主成分不占数据集内变异的 100%,则可以确定第二主成分。这是指变量的线性组合,它使与第一个组合正交的所有其他线性组合中的可变性最大化。简单地说,一旦第一主成分被考虑,第二主成分使剩余的可变性最大化。如果我们再次假设有两个变量,并且第一主成分已经确定,则第二主成分将是垂直于初始最佳拟合线的线。
最后,每个主成分的变化量可以通过将每个成分的距离*方和除以样本量减 1 来确定。回想一下,这个想法是为了减少数据集的维度。因此,由主成分解释的变异的百分比可以通过将变异相加,然后除以总和而得到。如果发现第一主成分占数据内变化的约 90%,则理想的是进一步仅使用第一主成分。
图片作者:特里斯特·约瑟夫
虽然这很好,但 PCA 确实有一些问题。最主要的一点是,结果直接取决于变量的规模。如果一个变量因为比其他变量更大而看起来有更多的变化,这个变量将在主成分中占主导地位,并将产生不太理想的结果。类似地,主成分分析的有效性很大程度上受厚尾数据中出现的偏斜的影响。最后,PCA 可能很难解释,尤其是因为这种方法将变量混合在一起以最大化可变性。
尽管存在挑战,但 PCA 是一种可靠的特征提取和降维方法,应该用于理解超大数据集中变量之间的关系。
参考文献:
应用多元统计与 R,丹尼尔泽尔特曼
【youtube.com/watch?v=FgakZw6K1QQ 号
dezyre . com/data-science-in-python-tutorial/principal-component-analysis-tutorial
其他有用的材料:
builtin . com/data-science/step-step-explain-principal-component-analysis
machinelementmastery . com/calculate-principal-component-analysis-scratch-python/
machinelingmastery . com/principal-components-analysis-for-dimensionality-reduction-in-python/
geeksforgeeks.org/ml-principal-component-analysispca/
youtube.com/watch?v=0SiRTlFcZRQ
理解概率和统计:数据科学家的中心极限定理和收敛
“理解概率和统计”系列的第二篇文章,解释收敛和中心极限定理(CLT)
数据科学领域围绕着概率和统计。特别是,收敛和中心极限定理(CLT)是每个数据科学家必须熟悉的一些最重要的概念。因此,本文旨在解释什么是随机变量的收敛性,这是一个在数学中大量使用的概念。此外,它提供了中心极限定理的概述。
概率与统计
如果你想从最基本的方面理解概率和统计的概念,请阅读下面的第一篇文章。它解释了概率的本质:
为统计学家解释概率的关键概念
towardsdatascience.com](/understanding-probability-and-statistics-the-essentials-of-probability-for-data-scientists-459d61a8da44)
1.文章目标
本文将概述以下关键部分:
- 趋同是什么意思?
- 什么是中心极限定理?
根据每个人的兴趣,本系列的下一组文章将解释连续随机变量的联合分布以及关键的正态分布,如卡方分布、T 分布和 F 分布。随后,下一篇文章将旨在解释统计和贝叶斯推理的基础,以及马尔可夫链和泊松过程。
这些文章的目标是向数据科学家简化概率和统计的概念。
2.趋同是什么意思?
收敛的概念是一个非常重要的概念。在我们讨论收敛性之前,让我们先了解一下极限在数学中的含义。
数学中的“极限”是什么?
考虑这个函数: f(x) = /x ,其中 x 为正数。
如果我们取一系列值 0 < x ≤ ∞,并开始将它们代入函数 f(x ),那么我们将得到以下结果:
显示 x 的递增值和 f(x)的相应值的表格
我们可以看到,随着 x 的增大,f(x)减小。它暗示 x 和 f(x)彼此成反比。
如果我们将结果绘制成图表来可视化,那么我们会看到它形成了以下形状:
显示 x 和 f(x)的图表
我们可以看到,随着 x 的增加,f(x)越来越接* 0,尽管 f(x)从未接* 0。因此,我们可以得出结论,当 x 逼*∞时,f(x)的极限为 0。
当 x →∞,极限 f(x) = 0。
此外,y = 0 是 f(x)的渐*线,因为这条线越来越接* 0,但从未穿过 0。
这就是所谓的函数极限。
我们可以把 1/n 的极限写成:
当 x 达到无穷大时,1/x 达到 0
我们可以注意到 f(x)收敛于 0
极限在数学中用于简化复杂性。它可以*似一个函数。
收敛的概念可以用不同的方式来解释,重要的是要理解它的变体。
假设我们收集了两个随机变量 A 和 b 的数据样本,这两个变量可以是任何东西,比如股票的历史价格,甚至运动员训练的时间等等。
随机变量的样本具有抽样分布。
抽样分布是实际分布的*似值。为了简单起见,我们可以得出结论,随着样本量的增加,我们可以用极限来*似变量的分布。这是要记住的关键!
收敛的概念可以用样本的潜在概率来解释。
要注意的关键是,可以分析随机变量的概率,以评估随机变量是否收敛。
3.什么是中心极限定理?
在我解释中心极限定理(CLT)之前,我将试图打下一个坚实的基础,以便我们能容易地理解 CLT。
我们也考虑一下,我们想找出变量有多接*?
这里的关闭到底是什么意思?
- 是指计算两个变量各点的绝对差值吗?
- 或者,这是否意味着我们需要计算两个变量的分布,然后计算期望之间的差?
- 或者,这是否意味着我们需要计算两个变量之间的相关性。
让我们通过了解大数定律来理解它。
3.1 大数定律
本节解释了大数的概念。对于数据科学家来说,这是一个重要的概念。
这个概念很简单,理解起来非常重要。
首先,记住每个随机变量都有一个期望值(可能是算术*均值),而且随机变量的每个样本都有其样本*均值。这两个均值可以不同,但它们彼此相关,我们希望样本均值尽可能接*随机变量的期望均值。当我们增加样本量时,我们就更接*预期的*均值。
简单来说,大数定律表明,随着实验次数的增加,样本的*均值将趋向于随机变量的期望值。
这给我们带来了大数定律的两个最重要的版本;弱大数定律和强大数定律。
3.2 弱定律:
假设我们有一个独立的随机变量序列。在这里,独立性的概念意味着第一个样本不会影响下一个样本,例如多次投掷硬币。
所有随机变量的*均值是相同的,并且它们的方差≤ v,其中 v < ∞。这意味着均值是一个常数,方差有一个上限。
该定律指出,随着样本数量增加到无穷大,样本均值以概率收敛到共同均值。
3.3 强定律:
强大数定律表明,当我们将样本大小增加到无穷大时,样本的期望值以概率 1 收敛到随机变量的期望值,而不仅仅是收敛到期望值。
记住,概率 1 表示一个事件确实发生了
因此,强大的大数定律表明,随着试验次数增加到无穷大,变量以概率 1 收敛到期望值:
P(lim n → ∞ Xₙ =μ) = 1
另一方面,弱定律简单地说明概率收敛于一个公共 E(X)。
3.4 中心极限定理(CLT)
这就把我们带到了文章的核心。
我将尝试用三个容易理解的要点来解释中心极限定理的概念。这个概念在数据科学项目中大量使用,特别是在我们试图预测变量的项目中。
事实上,我们很容易将中心极限定理视为概率统计理论中最重要的概念之一。
1️⃣ - 要记住的第一点是,两个变量的分布可以收敛。当两个随机变量的分布接*时,那么这些随机变量也可以认为是接*的。这就是所谓的分布趋同。
如果随机变量序列 X ₙ 收敛于分布函数 F ₙ (x)那么序列分布的极限是:
我会多解释一点这一要点,因为理解它相当重要。
让我们假设我们取了一个随机变量序列,X1,X2,..,Xn。
让我们也考虑这些随机变量是相互独立的,并且是同分布的。而且它们的方差是有限的。随着随机变量样本数量的增加(n → ∞),样本总数的分布将收敛到一个共同的分布。
CLT 有助于我们理解这种常见的分布将是正态分布。
让我们考虑 Sn = X1 + X2 + … + Xn。那么 Sn 的分布将在 n * μ附*,方差将在 n* μ附*,其数量级为 n 的*方根
2️⃣ —需要注意的第二点是,中心极限定理都是关于正态分布的。
草图显示钟形正态曲线
正态分布是一个需要理解的基本概念。这篇文章解释说:
解释为什么高斯分布是如此成功和广泛使用的概率分布
medium.com](https://medium.com/fintechexplained/ever-wondered-why-normal-distribution-is-so-important-110a482abee3)
基调指出样本均值的分布是正态的。为了解释这一点,考虑我们进行一项实验并收集一个随机变量的大量观察值。让我们把这些观察作为一个例子来参考。
随后,我们可以计算样本的*均值。
如果我们重复这个实验很多次,那么我们将开始收集大量的样本。然后,我们可以计算每个样本的*均值,并开始分析样本*均值的分布。
我们会注意到样本预期*均值的分布将由正态分布*似,这是一个非常重要的概念。
需要注意的关键是,随机变量必须相互独立,并且必须同分布。
3️⃣——最后要考虑的一点是,中心极限定理允许我们用正态分布来*似大量的分布。
上面的公式有助于我们标准化一个随机变量,因为我们实际上是减去*均值,然后用方差除每个样本,此外还考虑了标准误差。
为了进一步解释,如果随机变量存在,并且随机变量的每个序列具有有限的均值μ和方差σ,那么中心极限定理表明,如果我们标准化随机变量以确保均值为 0,方差为 1,那么当 n → ∞时,则分布收敛于正态分布,而不管单个随机变量样本的分布如何。
作为例子,这些独立的随机变量可以具有伯努利或泊松分布。它可能是右偏或左偏的,即使这样,随着样本的增加,分布也会收敛到正态。
我们增加的样本越多,分布就越接*正态分布。
一旦我们假设数据呈正态分布,我们就可以开始简化复杂的数据集,并开始*似合理的预测,但需要注意的关键是样本量应该很大(通常> 30)。
这就是为什么中心极限定理是概率统计学科中的一个中心概念的原因。
3.摘要
本文解释了什么是随机变量的收敛,并提供了中心极限定理的概述。
感谢您的阅读
这两个概念都是数据科学家必须了解的。
这一节为本系列的下一篇文章奠定了基础。
根据每个人的兴趣,本系列的下一组文章将解释连续随机变量的联合分布以及关键的正态分布,如卡方分布、T 分布和 F 分布。随后,本系列的文章将致力于解释统计和贝叶斯推理,以及马尔可夫链和泊松过程。
理解概率和统计:学生 t 分布、卡方分布和 f 分布
处理数据和统计推断的数据科学家必须知道的话题
概率与统计
文章目标
本文旨在解释我推荐每个数据科学家都必须熟悉的三个重要分布:
- 学生 T 分布
2.卡方分布
3.f 分布
所有这三个发行版彼此密切相关。我将尝试用一种简单的方式来解释这些分布。
1.学生 T 分布
本节将向读者介绍 Student-T 分布
Student-T 是需要理解的最重要的统计分布之一。它也被称为 t 分布。
Student-t 分布大量用于统计领域。特别是在样本量小和/或总体标准偏差未知的情况下。另外,分布曲线呈钟形也很重要。Student-t 分布可以帮助我们从样本中获取有意义的统计信息。此外,它还用于统计推断。
当我们没有一个大的样本集,大约 30 个观察值,或者当总体的标准偏差不可用时,使用 Student-T 分布
学生 T 被认为是统计学中最大的突破性分布之一。当总体的标准偏差未知时,它可用于推断较小样本的意义。这可以应用于大量的世界问题。
学生 t 分布是正态分布的*似值
如果我们绘制学生 t 分布图,它看起来很像一条钟形曲线。因此,学生 t 分布类似于正态分布。此外,t 分布的性质更接*正态分布。例如,分布的*均值是 0。
需要注意的最重要的一点是,student-t 分布比正态分布具有更厚的尾部。这意味着变量的离差更大。
最重要的部分是自由度,它总是 1 减去样本数。
让我们考虑从正态分布的总体中收集 N 个独立的观察值。我们可以通过应用以下公式将该分布转换为 student-t 分布:
我们需要做的是获得总体和样本均值以及样本的标准差。在上面的等式中,总体呈正态分布,具有均值 M 和标准差 S,具有 n-1 个自由度(df ),其中 n 是样本的大小。
样本越大,学生 t 分布越接*正态分布。t 分布的中位数是 0。
随着自由度的增加,分布向正态分布收敛。这是根据中心极限定理。
该草图显示了正态分布和 Student-t 分布的概率分布曲线:
该示意图显示,当自由度增加时,t 分布的尾部变窄,分布曲线开始类似正态分布。
学生-t 围绕 0 对称。它的峰值比正态分布低,尾部更厚。这意味着样品中有更高的分散度。
现在要强调的是,如果我们假设我们的变量具有学生 t 分布,那么这意味着我们获得远离*均值的值的概率比我们使用正态分布产生的样本的概率要高。
2.卡方分布
本节将介绍卡方分布。它被读作凯*方分布。
*方这个词很重要,因为它意味着正态分布的*方。我也会在这篇文章中解释它的意义。
卡方是一个连续的概率分布。它在统计推断中也大量使用。当我们对置信区间及其标准差感兴趣时,我们利用卡方分布。
正如 student-t 分布一样,卡方分布也与标准正态分布密切相关。
让我们考虑收集 N 个(大于 1 的数)独立随机变量的数据,这些变量具有标准的正态分布。每个随机变量都有一个σ标准差。
如果我们对分布求*方并求和,那么分布的*方和将是具有 N 个自由度的卡方分布。
当我们*方正态分布时,卡方分布总是大于 0,因为所有的负值都是*方的。
值得注意的是,分布的*均值等于自由度。
每个随机变量被认为有 1 个自由度。
随着自由度的增加,分布将开始类似于标准的正态分布。然而,当我们对分布值求*方时,卡方分布总是向 y 轴的右侧移动,因为分布中不存在负值。随后,随着我们添加更多的随机变量,右偏度会降低。这又是由于中心极限定理。
方差和均值也随着自由度的增加而增加。
这张草图展示了卡方分布的样子:
这张草图显示了卡方分布。随着自由度的增加,该分布非常类似于正态分布。
曲线下的面积总是等于 1。
3.f 分布
本节将概述 f 分布的基础知识。
f 分布也是需要理解的最重要的统计分布之一。它与卡方分布密切相关,因此我在卡方分布之后解释了它。同样重要的是要注意 f 分布有两种不同的自由度。分子中的第一个自由度和第二个类型是分母自由度。
我们假设有两个独立的随机变量。第一随机变量 A 具有 dA 个自由度,第二随机变量 B 具有 dB 个自由度。让我们也考虑两个随机变量都有卡方分布。请记住,卡方分布是指随机变量具有正态分布,并且其值是*方的。
在它们的自由度上,分布的比率将具有自由度为 dA(分子)和 dB(分母)的 F 分布。
当我们想要评估两个样本的方差的变化时,使用 f 分布。如果我们看一下 f 分布图,随着自由度的增加,图表非常类似卡方分布。
此外,分布是右偏的。当我们增加分子的自由度时,右偏度会减小。f 分布的*均值= dB/dB-1。
我们可以注意到,分布曲线取决于自由度。它是正偏的,表明*均值大于中值。
理解统计学和概率是很重要的。如果您对这个主题和发行版不熟悉,请阅读这篇文章:
为统计学家解释概率的关键概念
towardsdatascience.com](/understanding-probability-and-statistics-the-essentials-of-probability-for-data-scientists-459d61a8da44)
摘要
本文解释了三种重要的统计分布:
感谢您的阅读
- 学生 t 分布
2.卡方分布
3.f 分布
理解概率和统计:马尔可夫链
为数据科学家解释马尔可夫链:概率和统计中最重要的概念之一
一旦开始研究统计模型,每个数据科学家都会遇到术语马尔可夫链和马尔可夫过程。这篇文章将以一种容易理解的方式解释马尔可夫过程的基本概念。
马尔可夫链广泛应用于金融、汽车、食品、游戏行业,在日常生活中几乎无处不在。因此,这是所有统计学家必须知道的话题。
天气、赌博、股票价格、人类行为都是马尔可夫过程。
概率与统计
文章目标
本文旨在解释以下关键主题:
- 什么是马尔可夫过程?
- 什么是马尔可夫链?
- 马尔可夫链示例
- 什么是*稳马尔可夫链分布?
照片由 Ahmad Dirini 在 Unsplash 上拍摄
1.什么是马尔可夫过程?
让我们考虑一个物体以随机方式运动。该对象可以是足球、正在下一步棋的棋手、汇率、股票价格、汽车运动、顾客在队列中的位置、在路上移动的人、足球场上的运动员等。
这些物体以随机的方式运动。因此,我们可以得出结论,对象在本质上是随机的。这些对象本质上代表了整个系统。有趣的是,所有这些系统都有马尔可夫属性。
什么是马尔可夫性质?
如果一个物体以随机的方式运动,并且它的运动是无记忆的,那么这个物体具有马尔可夫性。
作为一个例子,让我们考虑我们的目标对象是一个被一群足球运动员踢来踢去的足球。接下来足球可以处于任何状态。例如,它可以向右或向左移动。如果我们的目标对象是一家公司的股票价格,那么接下来它可以得到任何数值,比如 0.999,1,2,3 等等。现在,我们在上面陈述了马尔可夫性质是无记忆的。这意味着物体未来的运动只取决于它现在的状态。这是需要理解的最重要的概念。
马尔可夫性质是无记忆的
这就给我们带来了马尔可夫链的概念。
2.什么是马尔可夫链?
让我们考虑一个物体以随机方式运动。因此,对象(或系统)的状态可以改变。这种变化被称为转换,并且每个转换都有与其他转换相关联的概率。
因此,这个数学系统可以从一种状态转换到另一种状态,而这种转换是基于概率的。
这个系统是一个随机过程的例子。让我们假设它可以处于状态 A 或 B。让我们还考虑当对象处于状态 A 时,它有 40%的机会保持在状态 A,有 60%的机会转换到状态 B,如下所示:
示例状态机
需要注意的关键是,该过程具有马尔可夫性质,这意味着它是无记忆的。因此,未来转移的概率不依赖于过去的状态。它们只取决于当前状态。这就是为什么我们认为它是无记忆的。
马尔可夫链是具有马尔可夫性质的随机过程
马尔可夫链表示物体的随机运动。它是一个随机变量序列 Xn ,其中每个随机变量都有一个与之相关的转移概率。每个序列也有一个初始概率分布π。
考虑一个可能处于三种状态之一的对象{A,B,C}。随后,该对象从状态 A 开始,可以转到另一个状态 B 或状态 C,依此类推。所有的状态 A、B、C 等都在状态空间中(所有可能状态的集合)。
马尔可夫链的组成部分
这给我们带来了马尔可夫链的两个主要组成部分:
1.1 状态空间
状态空间是随机系统可能处于的所有可能状态(位置)的集合。
例如,S 可以是{上,下}或{上,下}或{1.1,2.2,…}或{正,负,中性}或{AAA,AA,A,B,D,E..}等。
要注意的一点是,S 是状态空间,它是对象可能处于的所有可能状态的集合。
1.2 转移概率
第二部分是转移概率。转移概率是一个概率表。表中的每一项 I,j 都告诉我们一个物体从状态 I 跃迁到状态 j 的概率。
因此,将存在与需要等于或大于 0 的所有状态相关联的概率。另外,概率值之和需要为 1。
如果你想了解概率分布的基础知识,那么请阅读这篇文章,这篇文章解释了概率的基础知识。我向每一位数据科学家强烈推荐它:
为统计学家解释概率的关键概念
towardsdatascience.com](/understanding-probability-and-statistics-the-essentials-of-probability-for-data-scientists-459d61a8da44)
转移概率告诉我们对象可能处于的下一个状态及其相关概率。因此,对象的下一步移动仅取决于其当前值。
假设我们有一个转移概率矩阵:
上表显示,如果我们的目标对象处于正常状态,那么它有 60%的机会转换到正常状态,有 40%的机会保持正常状态。此外,如果目标对象处于按下状态,则有 60%的机会它将转换到按下状态,并且有 40%的机会它将保持在按下状态。
需要注意的关键是,只需要知道当前状态就可以确定未来状态的概率分布。其他任何历史信息都没有意义。
3.马尔可夫链示例
本节将使用一个简单易懂的例子来解释马尔可夫链的概念。
假设我们试图预测一系列交易对手的评级。每个交易对手都有当前评级。例如,交易对手 Alpha 可以具有评级 A 或 B 或 C。因此,{A,B,C}是状态空间中的状态。
我们已经得到了一个转移概率矩阵。这个矩阵也称为随机矩阵。转移概率矩阵告诉我们交易对手转移到另一个评级的概率。
交易对手评级状态空间= {A,B,C}
转换矩阵:
需要注意的是,行或列的概率之和是 1。
因此,如果一个交易对手的评级为 A,则有 30%的机会将其转换为 C 级,有 50%的机会将其自身转换为 B 级,而有 20%的机会保持 A 级。
随机过程也有一个概率分布,我将很快解释。
这给我们带来了一个重要的定理:
如果马尔可夫链是{Xn}并且具有状态空间 s,转移概率为{pij},其初始概率分布为{ ᵢ} }那么对于作为 s 的元素的任何 i ,我们得到:
p(x1 = I)=σμₖpₖi(s 的所有 k 个元素的总和)
因此,让我们也考虑交易对手的当前概率分布如下:
- 对于评级为 A 的交易对手,该值为 0.5
- 对于评级为 B 的交易对手,该值为 0.1
- 对于评级为 C 的交易对手,该值为 0.4。
马尔可夫链的概率分布可以表示为行向量π,如下所示:
概率分布加起来是 1。
有了这些信息,我们可以开始更好地理解这个过程。随着时间的推移,我们可以开始估计物体处于特定状态的概率。例如,对于下一个评级为 C 的交易对手,我们可以对所有状态的当前概率分布和转移概率的乘积求和:
P(X1 = C)=(0.5 * 0.3)+(0.1 * 0.4)+(0.4 * 0.3)= 0.31
它通知我们,在大约 31%的情况下,该交易对手在一步后将获得评级 C。一旦我们有了这些信息,我们就可以更有把握地预测一个随机运动的物体。
4.什么是*稳马尔可夫链分布?
这就把我们带到了随机变量的稳定性。这是本文的最后一个重要部分。这一节将介绍马尔可夫链的*稳分布这一主题。
让我们考虑我们的目标随机过程需要被估计,我们想了解随机过程的稳定性。
我们知道马尔可夫链有一个概率分布。“静止”这个词意味着主体是不变的,我们知道我们的随机对象可以移动到任何可能的状态。这种情况下的主题是随机过程的概率分布,而不是随机对象本身。
因此,如果马尔可夫链的统计分布是稳定的,那么这意味着该分布不会随着时间的推移而改变。
因此,如果转移矩阵是 P,概率分布是π,那么马尔可夫链的*稳分布就是π = π * P
理解收敛性和中心极限定理是很重要的。每个数据科学家都必须知道这些概念。此链接以简单的方式解释了它:
本文是“理解概率与统计”系列文章的第二篇。它侧重于 CLT 和…
towardsdatascience.com](/understanding-probability-and-statistics-central-limit-theorem-and-convergence-for-data-scientists-653c53145400)
这是一个需要理解的非常重要的概念。它告诉我们,不管具有稳定概率分布的系统在开始时在哪里,随着时间的推移,系统处于某个状态的时间量将会接*它的概率分布。
此外,该链将总是具有与开始时相同的概率。
随后,如果{Xₙ}是一个马尔可夫链并且它有一个*稳分布{πᵢ}那么如果 P(Xₙ=i)=πᵢ对于所有 I 那么 P(Xₘ=i)=πᵢ对于所有 I,只要 m > n.
这些信息可以帮助我们预测随机过程。
5.摘要
本文解释了以下关键主题:
- 什么是马尔可夫过程?
- 什么是马尔可夫链?
- 马尔可夫链示例
- 什么是*稳马尔可夫链分布?
理解概率和统计:数据科学家的统计推断
构成数据科学家工作基础的一个必须知道的话题
每一个数据科学家 都必须 熟悉统计推断的概念。因此,本文旨在提供一个统计推断的概述。它将以一种易于掌握和理解的方式,带你深入推断的统计世界。
一些科学家认为统计推断是统计学中最难的概念之一,彻底理解它确实可以帮助他们为他们的项目和团队增加重要价值。
我的目标是用简单的方式解释统计推断,以便每个人都能理解。
概率与统计
文章目标
本文旨在解释:
- 统计推断是什么?
- 理解统计推断过程
- 测试统计—更大的图片和示例
- 假设检验
- 错误类型
1.统计推断是什么?
数据科学家通常花费大量时间来收集和评估数据。然后,使用数据分析技术,将这些数据用于推断结论。
有时观察到这些结论,并使用图表和表格轻松描述这些发现。这就是所谓的描述性统计。其他时候,我们必须探索一种未被观察到的方法。这就是统计推断的来源。
到目前为止一切顺利。现在让我们来理解它
描述性统计推断本质上是向用户描述数据,但它不会从数据中做出任何推断。推断统计学是统计推断的另一个分支。推断统计学帮助我们从样本数据中得出结论,以估计总体的参数。样本不太可能绝对真实地代表总体,因此,在得出总体结论时,我们总会有一定程度的不确定性。
例如,数据科学家可能旨在了解他们实验中的变量是如何表现的。收集该变量的所有数据(人口)可能是一项巨大的任务。因此,数据科学家从目标变量的总体中抽取一个小样本来代表总体,然后对这个小样本进行统计推断。
这些样本用于估计人口
数据科学家的目的是在知道存在一定程度的不确定性的情况下,从一个样本推广到一个群体。因此,这些分析有助于他们对整个数据群体做出判断。有时,数据科学家模拟样本,以了解人口的行为,并为此对变量的潜在概率分布做出假设。这就是为什么概率的概念被大量推荐给数据科学家的核心原因之一。
随后,关于人口的性质提出了许多假设和主张。接下来,使用统计模型从样本中推断结论,从而推断群体的特性。
下面的文章提供了对什么是概率分布的透彻理解,我强烈推荐每个人阅读这篇文章
为统计学家解释概率的关键概念
towardsdatascience.com](/understanding-probability-and-statistics-the-essentials-of-probability-for-data-scientists-459d61a8da44)
2.理解统计推断过程
这一节将帮助我们理解统计推断的过程。让我们假设数据科学家想要了解他们的目标变量的行为。他们可能有兴趣了解总体参数的行为。
- 例如,他们可能想要评估银行中所有部门的所有隔夜批处理作业是否在特定的时间范围内完成。
- 或者,他们可能想找到一个国家人口的*均身高。
- 或者,他们可能想了解企业是否获得了相同的利润,以及用户在特定事件之前或之后的行为是否有所不同,例如在新产品发布之后。
- 或者他们想证明某个关于人口的特定主张是错误的。
有时候收集一个群体的所有数据太困难了。因此,数据科学家从总体中准备他们的样本集。
例如,数据科学家想要了解的参数可能是总体的*均值或方差。他们从总体中提取样本,然后进行统计分析以估计总体参数。有时,他们会检查参数是否符合被认为是真实的特定值。
下图说明了这一过程:
从获取样本开始,然后进行统计分析
注意统计推断和抽样技术一样好
样本中总有一些噪声成分。样本的标准差超过样本大小的*方根就是样本标准差。这是样本相对于*均值的噪声/离差。这种测量是基于样本大小的。该公式表明,样本量越大,标准差的影响越小,样本值越接*总体值。如果我们增加样本量,那么最终样本将开始接*总体。我推荐阅读这篇解释什么是融合和 CLT 的文章。这些是数据科学家需要理解的重要主题:
本文是“理解概率与统计”系列文章的第二篇。它侧重于 CLT 和…
towardsdatascience.com](/understanding-probability-and-statistics-central-limit-theorem-and-convergence-for-data-scientists-653c53145400)
2.1 思考这个例子
作为一个例子,我们可以声称我们的目标变量遵循正态分布,其均值始终为零,方差为 1,等等。
因此,在收集了一个小样本后,我们开始在直方图中绘制 10 个区间内的值。从图表中,我们得出结论,由于产生了钟形曲线,数据遵循正态分布。从那里,我们可以开始估计样本的均值和方差,从而得出关于总体的推断。我们也可以开始从样本的置信概率分布中产生更多的数据。
但是我们有多大的信心呢?我们怎么能拒绝这种说法呢?或者有没有一种方法可以计算出这种说法,让我们可以确定?我们能通过量化的方法来证明吗?这就是统计推断的用武之地!
理解统计推断是至关重要的,因为它可以帮助我们更好地理解我们的统计选择
由维多利亚·比尔斯伯勒在 Unsplash 上拍摄的照片
样本可以被认为是一个随机变量,它有自己的概率分布、模式和趋势。
我们可以收集大量样本,计算它们的均值、标准差和方差,以便更好地了解数据。
检验统计的过程可以用来帮助我们作出经过计算的决定。在对样本进行实验之前,科学家们对预期的结果有一个概念。这通常是通过探索性数据分析收集的。
3.测试统计—更大的图片和示例
现在,从理论上,让我们回顾一下统计推断是如何工作的。
假设我们正在进行一个数据科学项目,我们的客户是一家大型金融机构。我将我们的客户称为银行 a。他们希望我们了解他们的隔夜批处理系统*均花费的时间是否与业内其他银行相同。客户希望使用我们的结果来了解他们今年是否需要在技术方面进行不同的投资。
我们还假设业内普遍认为,所有银行的隔夜批处理作业*均需要≤ 6 小时才能完成。
这是一个大数据问题。我们不可能走遍每一家银行,获得它们所有的批处理作业数据。这将是一项极其费时费钱的工作。我们可以想象我们的人口拥有多少数据。因此,第一步是收集不同银行的良好样本集。
以我们的例子为例,我们可以根据银行的交易规模将它们分类,并从每组的一个成员那里获取一年的批处理作业时间。
样本的选择应使其能最好地代表被测数据的总体。总是花时间思考取样技术
假设分析的成功取决于所选样本的质量。
然后,我们声称银行 A 的隔夜批处理作业*均需要 6 个多小时才能完成。这是我们正在测试的假设。让我们证明它是对的。
在我介绍基础知识的时候,让我们先保持这个想法,然后我会回到这个话题上来。
这让我想到了本文的第二个子主题——测试统计
我们执行以下 5 个步骤来证明索赔:
- 从陈述被广泛相信的主张开始,这就是所谓的零假设。
- 在我们拒绝索赔之前,概述最低显著性水*/置信水*。这可能是 5%。这意味着我们相信大约有 5%的时间,我们的模型会产生不准确的结果。
- 计算样本结果的*均值和标准差
- 计算测试统计数据
- 最后,基于结果,陈述选择的结果。
该图突出显示了五个步骤。我将在下面详细解释这五个步骤
选择的检验统计量取决于样本的分布和样本大小。
下一节将详细解释每个步骤。
4.假设检验
1.陈述你的要求
任何测试都有两个假设:
- 零假设— 什么是公认的真理,什么是我们想要检验的。这就是我们想要证明的错误。对于我们的例子,批处理作业的*均完成时间少于或等于 6 小时。
- 备选假设 —如果零假设不成立,我们需要接受什么。这是我们认为正确的。这是我们对银行的假设。我们的假设是工作的*均时间大于 6 小时。
注意,无效假设和替代假设不能同时为真。
侧注 :这是一个单尾测试。单尾备择假设是单向检验。双尾备择假设检验是双向检验,统计学家感兴趣的是检查数据的相等性,例如某个值是否在某个范围内。
2.确定你的重要性水*:
您的显著性水*表明您对样本和支持您的主张的方法有多大的信心。显著性水*被称为α。阿尔法的通常值是 1%或 5%。阿尔法值越低,意味着你对结果非常有把握。所选择的置信水*构成了风险管理信贷指标的基础。
Alpha 是假设分析中的显著性水*。
详细来说,Alpha 是在零假设被拒绝之前,可以被接受的值的范围。是下限。
3.计算测试统计
高级测试统计概述
我们可以选择 T、Z 或 F 统计。我将简要地解释它们。
一旦选择一个样本来代表一个总体,就可以计算出它的均值和标准差。然后我们进行测试统计。
有许多测试统计数据,如 T、Z、F 等。
我强烈推荐阅读这篇文章,因为它深入解释了三个最重要的统计数据:
[## 理解概率和统计:卡方分布、学生 T 分布和 f 分布
处理数据和统计推断的数据科学家必须知道的话题
towardsdatascience.com](/understanding-probability-and-statistics-chi-squared-student-t-and-f-distributions-e46b4f802707)
T 统计量用于检验两个小总体的均值相等。样本服从 Student T 分布,样本大小约为 30 个观察值。总体标准差未知。
- 示例:您有一个 10 辆汽车的样本,您想测量镇上所有汽车的*均油耗。你的假设是,汽车*均每天消耗 10 升燃料。让我们也考虑一下,你对这个方法有 99%的信心。然后,您可以将假设的*均值与样本*均值进行比较,并根据 99%的 t 分布表计算出是否需要拒绝零假设。
Z 统计量用于检验两个大总体的均值。样本服从正态分布,样本容量通常大于 30。总体标准差是已知的。
- 例子:假设你已经收集了一个 500 人的样本,来估计每天穿蓝色衬衫的*均人数。让我们也考虑一下,你对你的模型有 95%的信心。然后,您可以将假设的*均值与样本*均值进行比较,并根据 95%的 Z 分布表计算出是否需要拒绝零假设。
兰迪·法特在 Unsplash 上的照片
F 统计量用于比较两个总体的方差。变异是每个观察值与其组均值的偏差*方和除以误差自由度。
- 例子:你可以用 f 检验来比较你公司两个 IT 系统软件 bug 的可变性。
每一个测试统计都有自己的简单公式。为了简单起见,我不打算在本文中讨论这些公式。
计算结果时,会根据分布表中的值进行检查。请注意,结果取决于样本大小、标准偏差和样本均值。
我们可以使用 Python 执行测试统计。作为一个实例,要执行 T 检验,我们可以:
from scipy import stats
stats.ttest_ind(collection_one, collection_two)
从计算的结果,我们可以陈述结果。
5.陈述您的结果:
让我们假设我们从测试统计计算中计算出 0.50。现在,我们可以在概率分布表中查找我们选择的 alpha 为 95%的样本值。Z 分布表给出的值为 1.96。当 0.50 ≤ 1.96 时,我们必须接受零假设。
这意味着批处理作业确实在声称的 6 小时时间内完成,而我们的说法是错误的。这就是统计推断的工作原理。
照片由 Aaron Burden 在 Unsplash 上拍摄
5.错误类型:
最后,我想介绍两种类型的错误。了解实验如何出错以及在哪里出错是很重要的。
类型 1 和类型 2
在我们陈述结果时,可能会出现两种类型的错误:
- 第 1 类错误:零假设正确,但分析证明它是错误的。
- 第二类错误:零假设是错误的,但分析无法证明它是错误的
摘要
这篇文章解释了统计学领域最重要的课题之一。统计推断是每个数据科学家必须知道的话题。
感谢您的阅读
然后,文章解释了我们如何计算检验统计量和进行假设分析。
这些步骤可用于阐明假设是否正确。它有助于一个人做出有意识的规避风险的决定,并通过量化措施更好地理解我们的数据。
理解概率和统计:数据科学家的概率基础
为统计学家解释概率的关键概念
数据科学领域围绕着概率和统计。因此,对这些概念有一个坚实的理解是至关重要的。
本文意在解释概率的要领。
概率与统计
我将写一些关于概率和统计的文章。他们打算从头开始解释这些概念。
本文是该系列的第一篇。
为什么要从概率入手?
概率无处不在!
这样想吧;概率是不确定性的科学。因此,每当对一个事件的发生有任何怀疑时,概率的概念就被用来估计一个事件发生的可能性。如果我们想预测一个变量的结果,这个变量可以取许多可用值中的一个,那么我们必须涉及概率的数学。我们要做的就是给一个事件分配一个数字,比如明天下雨的可能性是 30%。
考虑到这一点,难道我们一生都要用到概率论吗?我们会在考试、工作和决策中取得成功吗?我们怎样才能增加成功的机会?
因此,理解概率是至关重要的。
明天真的会是晴天吗?股票价格会保持不变直到明天吗?我们有多确定?在接下来的一个小时内,我们接到电话的可能性有多大?
概率是一门极其重要的学科,我决定以概率为主题开始我的系列文章。
根据每个人的兴趣,在随后的文章中,我将解释中心极限定理、大数定律、收敛如何工作、马尔可夫链、概率转移、概率分布的模拟,我们还将更深入地研究联合和边际分布及其估计。我的目标是用简单的方式解释复杂的概念。随后,我还将解释统计的主题。这将为数据科学奠定坚实的基础。
文章目标
本文的目的是解释以下六个要点:
- 什么是概率空间?
- 什么是随机变量?
- 概率规则
- 什么是期望?
- 什么是方差和协方差?
- 什么是概率分布?
1.什么是概率空间?
这一节将通过解释概率空间来开始概率的主题。概率空间的概念构成了概率论的基础,因此理解它是很重要的。
概率空间用于模拟实验。这是一个数学概念,也称为概率三元组。
概率空间有三个组成部分:
概率三元组的三个组成部分
1。一个样本空间:
这是所有可能结果的集合。数学中的集合是元素的独特集合。
作为一个例子,骰子的样本空间是:S= {1,2,3,4,5 和 6}
股票价格运动的样本空间可以是 S = {增加,相同,减少}。由于 Increase 是 S 的一个元素,我们可以把它写成 Increase ∈ S
现在要记住的关键是样本空间可以是一个无限集。例如,一个国家的人口是不断变化的,它是一个具有无限可能性的随机数。
2。事件集合:
这是一个包含结果组合的集合。因此,它是样本空间子集的集合。集合中的每个元素称为一个事件。
例如,掷骰子事件的子集可以是{1}、{1,2}等等。
因此,重要的是要注意样本空间(1)是一组事件(2)的元素。S ∈ F
3。概率度量——事件如何分配给概率的函数:
每个事件都有一个概率。概率可以是 0 到 1 之间的任何值。需要注意的关键是,它是一个不能大于 1 的非负数。值 1 意味着该事件肯定会发生,而值 0 意味着该事件永远不会发生。
作为一个例子,投掷一个不偏不倚的公*骰子可以导致 6 个可能结果中的一个,因此每个结果有 1/6 的概率。因此得到 4 的概率是 P(4) = 1/6
事件发生的可能性越大,概率度量就越高。
显示骰子不同可能结果的草图
所有样本空间的概率之和为 1
空集的概率为 0。这意味着没有结果不会发生。
现在,我想让大家从第一节学到的一个重要概念是,概率测度也是可加的。这意味着,如果我们想要计算一个复杂事件的概率,那么我们可以将组成复杂事件的简单事件的概率相加——只要它们是不相关的。
例如,骰子显示 1 或 4 的概率为
2/6(得到 1 的 1/6 加上得到 4 的 1/6)
2.什么是随机变量?
让我们进入随机变量的下一部分。在阅读金融论文时,我们经常会遇到“可测量的”或“可观察的”这两个术语。术语“可观察”代表实验中可以测量的随机变量。
随机变量本身就是一个函数。它将一个状态空间映射到一组数字,因此随机变量是一个本质上随机的结果。每一个结果都有与之相关的概率。
举例来说,考虑一个国家的 GDP 是一个随机变量。它可以被认为是许多变量和常数的函数。每个事件都有一个与之相关的概率度量。
这个世界充满了随机变量。例如,世界人口取决于时间、掷骰子、掷硬币、一周的天数、利率、汇率、黄金价格等。都是随机变量。
随机变量可以是离散的,也可以是连续的。
2.1 离散随机变量
离散随机变量是一个有有限可能结果的变量。这些结果也可以是无限的,但是要注意的关键是有限的结果集的和应该是 1。
例如,掷骰子、掷硬币、一周中的几天、特定铅笔盒中的颜色、性别、月份、一个月中的几天等等。都是离散随机变量的例子。
2.2 连续随机变量
不是离散的随机变量是连续的随机变量。它有无数可能的结果,无法计算。
例如,依赖于时间、利率、汇率、黄金价格、毫米降雨量等的世界人口。都是连续随机变量的例子。
本节的关键是随机数是结果的函数,其中每个结果都是随机的,并且有与之相关的概率。
3.概率规则
本节将概述概率规则,这对理解概率规则非常重要。
假设我有一枚标准的硬币,有两面:正面和反面。
因此,如果我在空中抛硬币,当它落在我手上时,我可能会看到正面或反面。对于一个公*的硬币,得到正面 P(正面)的概率是 0.5,得到反面 P(反面)的概率也是 0.5。
获得正面的概率为 0.5
0.5 的概率得到尾巴
我们可以看到正面和反面都是两种可能的结果。概率测度之和为 P(正面)U P(反面)= (0.5 + 0.5) = 1。
需要注意的关键是,还是指加。
如果我们掷两枚硬币,那么两次都看到正面的概率是 P(正面)x P(正面)。这就是乘法原理。
如果我们抛两个硬币,那么看到正面或反面的概率是 1,因为 or 意味着加法。这就是加法原理。
这两个事件是相互独立的,因为抛一次硬币不会影响我们下一次试验的结果。
现在,考虑一个稍微复杂的例子。假设我们站在路边,数着从我们身边驶过的汽车的颜色和大小。
这个例子应该有助于我们更好地解释概率规则。
这个圆圈代表路上所有从我们身边驶过的汽车。这是我们的样本集:
下面的红色小圆圈代表所有的红色汽车。r 的补码写成 Rᶜ.A 的补集不是 A 的集合。
在这种情况下,r 以外的任何东西都是 Rᶜ:
一辆车是红色的概率是 P(R)
车不红的概率是 1-P(R)
迷你蓝色圆圈代表所有蓝色汽车:
汽车为红色和蓝色的概率为 0,因为它们是不相交的事件。
汽车是红色或蓝色的概率是 P(R) U P(B) = P(R) + P(B)
所有大型汽车都画在绿色圆圈中:
在上图中,只有大型红色汽车。这在图像中标记为黄色,显示了红色圆圈与绿色圆圈相交的部分。
一辆车又红又大的概率是:
P(R 和 L) = P(R) x P(L|R)
这就是众所周知的贝叶斯规则,它是数据科学中需要记住的基本规则之一。
这里的“|”是“鉴于”的意思。这意味着一辆红色大汽车的概率是一辆红色汽车的概率乘以一辆红色大汽车的概率。P(L|R)是条件概率。
这是一个非常强大的公式,我们可以用它在机器学习算法中建立统计推断。
因此,我将为此专门写一篇文章。
4.什么是期望?
在解释什么是期望之前,我想先说明一下中位数的概念。
中位数
如果我们取一组数字,对它们进行排序并计算 CDF,那么区间(a,b)的中值计算如下:
这个公式将给出中点,即集合的索引,也就是中值。注意:a 和 b 是集合的索引,而不是集合的实际元素,例如,在集合{1,5,8,10}中,5 的索引是 2,10 的索引是 4。随后,区间(2,4)的中值为 3。中点 3 指向数值 8。
预期
期望值就是*均值。通过对事件的概率求和来计算: ∑ ₓ pₓ(x)
如果随机变量是连续的,那么期望是∫ xf(x)dx,
如果我们将一个可观察值乘以一个常数,那么新的期望值就是该常数乘以原始期望值:
如果我们把两个可观测量加在一起,那么它们的联合期望是:
两个可观测量是独立的,如果它们的交集不包含任何元素。因此,这两个可观测量彼此不相关,并且它们的联合期望只是将它们的期望相乘:
如果这两个可观测量不是独立的,那么我们需要考虑它们的协方差。
5.什么是方差和协方差?
随机变量可以向任何方向移动。标准差衡量随机变量与其*均值或期望值的偏差。方差是标准差的*方。
如果两个可观测量是独立的,那么没有共同运动,它们的协方差是 0。常数的方差为 0。
一旦我们知道了期望值,我们就可以计算方差:
两个随机变量 X 和 Y 的协方差为:
我们也可以将协方差计算为:
关于协方差,有一些重要的注意事项需要了解:
如果我们取两个可观测值 X 和 Y,给它们各加一个常数,再乘以一个常数,那么协方差将如下变化:
注意,b 和 d 完全消失了,因为它们没有改变 X 或 y 的方差。
如果两个可观测量不是独立的,那么当我们将两个可观测量相加时,它们的联合方差将变成:
在这里,协方差被用作两个变量是相互依赖和相互关联的。
6.什么是概率分布?
让我们进入概率论的核心。我可以向你保证,到目前为止,我们已经学习了数据科学家必须知道的概率学科的基础知识。
每一个花时间评估数据和执行功能工程的数据科学家都必须遇到以下术语;高斯分布、正态分布、二项式分布、泊松分布、指数分布等等。
本节将解释这些概率分布是什么。
让我们来理解概率分布:
随机变量有一个概率分布。它的意思是随机变量 X 的分布是 X 的所有子集的概率的集合。
首先,知道一个随机变量的概率分布是一个非常强大的工具,因为它可以帮助你估计一个变量的运动。
此外,多个随机变量可以具有相同的概率分布。这很有趣,因为如果有两个我们可以测量(观察)的随机变量具有相同的分布,那么我们可以使用第一个变量来了解另一个变量的行为,并将其视为彼此的代理。因此,我们可以建立更好的模型。重要的是要注意,我们不能假设这两个可观测量是相同的,因此评估和理解变量的行为是至关重要的。
如果我们记录一个随机变量的结果及其概率,那么我们可以建立一个概率分布。
一旦我们有了可能的结果集,我们就可以计算它们的概率,然后计算概率的分布。从概率分布,我们可以计算一个概率分布函数。
分布函数必须遵循以下属性:
- 它不是递减的
- 它是右连续的
- 当 x 移动到-无穷大时,它移动到 0,当 x 移动到无穷大时,它移动到 1。
最常见的概率分布
概率分布有它自己的形状、行为和性质。我将解释数据科学项目中最常用的发行版。
6.1 均匀分布
如果所有事件的概率测度相同,则随机变量 X 具有均匀分布。它也被称为矩形分布,因为所有的事件都有一个恒定的概率。
这张草图显示了均匀的分布
形式上,如果可观测值是离散的,那么概率密度函数可以写成:
n 是集合的大小。
例如,如果变量是骰子,则 n 是 6,如果随机变量是硬币,则 n 是 2。
一枚公*的硬币可以处于两种状态之一;正面和反面。每个状态都有 50%的概率发生。因此,公*的硬币有均匀的分布。
如果可观测值是连续的,那么两个区间 a 和 b 之间的概率密度函数可以写成:
b 是最大点,a 是最小点。
因此,分布由两个参数定义:最小值和最大值
例子
我举个例子解释一下均匀分布。
让我们考虑一下,我们正在测量一个城市中 5 岁儿童的*均身高。我们再考虑一下,高度从 0 到 3 英尺是均匀分布的。
一个 5 岁儿童身高从 1 英尺到 2 英尺的概率是:
(2–1 英尺)* (1/3 英尺)= 1/3
6.2 指数分布
这张草图显示了指数分布
当我们想要对事件发生之间经过的时间建模时,大多数连续随机变量具有指数分布。
指数分布围绕着速率参数λ。这个值告诉我们一个事件发生的速率。例如,如果每 10 分钟有一辆红色汽车从我们身边驶过,那么红色汽车从我们身边驶过的速度是每分钟的 1/10。
因此λ是 0.1。
因此λ告诉我们单位时间内事件之间的比率。
指数分布适用于连续的随机变量。
当随机变量具有指数分布时,其分布函数为:
指数分布是无记忆的,它不依赖于过去。
还值得注意的是:
- 指数分布的期望是:1/λ
- 方差为 1/λ
6.3 正态分布
草图显示了正态分布
最重要和最著名的分布之一是正态分布。它出现在中心极限定理中。它也被称为钟形曲线。
正态分布很容易解释。原因是:
- 分布的均值、众数和中位数相等。
- 我们只需要用均值和标准差来解释整个分布。
正态分布也称为高斯分布。它的密度取决于两个参数:均值和方差。
该功能是:
标准的正态分布是 N(0,1)。意味着标准正态分布的随机变量的均值为 0,方差为 1。
请务必注意正态分布的以下属性:
- 将一个数 z 乘以正态分布与将 z 乘以均值和方差具有相同的效果。
- 将数字 z 加到正态分布上的效果与将数字 z 加到*均值上的效果相同。对分布的方差没有影响。
- 如果两个随机数 X 和 Y 是独立的,并且具有正态分布,那么将 X 和 Y 相加将创建一个新的具有正态分布的随机变量 Z。然而,Z 的*均值将是 X 和 Y 的*均值之和,方差将是 X 和 Y 的方差之和。
如果你想了解更多关于正态分布的知识,请阅读这篇文章:
解释为什么高斯分布是如此成功和广泛使用的概率分布
medium.com](https://medium.com/fintechexplained/ever-wondered-why-normal-distribution-is-so-important-110a482abee3)
6.4 伯努利分布
假设我们想抛硬币。结果可能是正面或反面。如果得到正面的概率是 p ,那么得到反面的概率是 1-p。p 的值总是 0 < p < 1
可以取两种可能状态之一的随机变量被认为具有伯努利分布。为了概括起见,我们可以把这两种可能的状态称为成功或失败。成功是指我们的目标事件发生时的状态,而失败是指我们的目标没有发生时的状态。
伯努利分布有一个参数 p:
这张草图显示了伯努利分布
p(成功)= p
p(故障)= 1–p
期望值是 p,方差是 p(1 - p)
举例
我举个例子解释一下伯努利分布。假设交易对手违约的概率为 10%。这意味着有 90%的可能性交易对手会而不是违约。
这是一种伯努利分布,因为交易对手可以采取两种可能状态中的一种。在这种情况下,p 的值是 0.1。
随后,分布的方差为:
*0.1 (1–0.1)= 0.1 * 0.9 = 0.09
6.5 二项式分布
最常用的分布之一是二项分布。它与伯努利分布非常相似。不同的是,我们可以把伯努利分布看作二项分布的特例。
例如,如果我们想要衡量 n 个交易对手的违约,其中每个交易对手可能处于两种可能状态(违约或非违约)中的一种,所有交易对手都具有相同的违约概率(p ),并且它们是独立的,那么变量的分布将是二项式分布。
为了简化,伯努利分布作为二项分布的特例,其中 n 为 1。
考虑到有 n 个独立的随机变量,并且每个变量都有一个带有相同参数 p 的伯努利分布,那么如果我们将这些变量相加,那么它们的总和将是一个带有参数 n 和 p 的二项分布。
概率分布是:
这张草图显示了二项式分布
期望值是 np 方差是 np(1-p)
6.6 几何分布
这张草图显示了几何分布
几何分布真的很有意思。同样,它与二项式分布有些关系。这种分布通常出现在变量中,实验是计算在遇到目标事件之前我们看不到目标事件的次数。
我来详细说明一下。假设我们想测试一个交易对手是否违约。如果我们开始记录已经违约的交易对手的数量,直到我们遇到第一个没有违约的交易对手,那么变量将遵循几何分布。需要注意的是,在这种情况下,交易对手的违约被认为是相互独立的。
几何分布和指数分布是相关的,我们可以将指数分布视为几何分布的连续版本。
几何分布的概率分布函数是:
它非常类似于指数分布;把它想象成指数分布的离散版本。
6.7 泊松分布
这张草图显示了泊松分布
泊松分布与指数分布密切相关。泊松分布总是包含一个时间维度。泊松分布可以帮助我们理解独立事件在未来什么时候会发生。
举例来说,如果我们想测量何时我们会看到一辆红色汽车从我们身边驶过,或者何时一个交易对手会违约,那么随机变量遵循泊松分布。
泊松分布是离散的。该分布围绕一个参数λ,它告诉我们每单位时间内成功的*均次数。
如果存在均值为λ的随机变量 X,则它的概率分布为:
随机变量 X 的期望值为λ,方差为λ。
两个独立泊松分布之和也是泊松分布。
7.摘要
数据科学领域围绕着概率和统计。这篇文章旨在解释我们都应该熟悉的概率的本质。这篇文章是这个系列的第一篇,它仅仅触及了概率和统计的皮毛。
感谢您的阅读
文章阐述了以下六个要点:
- 什么是概率空间?
- 什么是随机变量?
- 概率规则
- 什么是期望?
- 什么是方差和协方差?
- 什么是概率分布?
取决于每个人有多感兴趣,在随后的文章中,我将解释中心极限定理、大数定律、收敛如何工作、马尔可夫链、概率转移,我们还将更深入地研究分布及其估计。这将为统计奠定坚实的基础。
理解概率和统计…
数据和科学之间的桥梁!
迈克尔·泽兹奇在 Unsplash 上的照片
简介
作为有抱负的数据科学家,我们都希望成为一名优秀的程序员,知道所有处理大量数据流的最新方法,但我们经常无法理解生成数据的随机过程、数据的分布及其相互依赖关系。我们都知道如何对数据进行高斯变换,但我们不清楚为什么它如此重要。
我们对概率统计中必须掌握的题目都没有一个清晰的概念。因此,当我们听到一个话题时,我们在互联网上搜索只会得到一大堆链接。就连我刚开始也面临同样的问题。所以我一直想创建一个简洁的文档来概述共同的主题,现在我们来了!
目录
- 概率分布
- 协方差和相关性
- 大数定律
- 中心极限定理
- 参数估计和假设检验
概率分布
当我们从 csv 文件中读取数据时,所有的列标题都让我们了解了某个特定列的内容。如果是“年龄”栏,那么我们知道这一栏中的所有值对应于不同人的年龄。在数学术语中,这个“年龄”被建模为一个变量,更具体地说是一个随机变量,因为产生不同人的年龄的过程是一个产生随机数的变化过程。每个随机变量都有自己的分布和一系列属性-
- 支持:分发的域。
- PMF/PDF :实际概率分布。
- CDF: 累积分布函数,它是分布值小于或等于特定值的事件的实际概率。因为是概率,所以范围在 0 到 1 之间。
- *均值:观察值的加权*均值;又称期待。
- 方差:来自的观测值的分布均值;它的*方根叫做标准差。
- 中位数:分布的中间值。
- 模式:从分布中取样的频率值。
有两种随机变量-
- 离散的随机变量,在掷硬币中可以有可数个值,如正面/反面,它们的分布可以用概率质量函数(PMF) 来描述。我们将在下面讨论离散随机变量:
- 二项分布
- 二项分布
- 几何分布
- 泊松分布
- 离散均匀分布
2.连续的随机变量取给定数字区间内的所有值,如年龄及其分布可以用概率密度函数(PDF) 来描述。我们将在下面讨论离散随机变量:
- 指数分布
- 连续均匀分布
- 高斯分布
伯努利分布
可以认为是实验的模型,其结果是二元的,即头/尾、是/否等。结果以概率 p 为正,以概率 1-p 为负。
伯努利分布:重要的性质(来源:维基百科)
伯努利分布:PMF 和 CDF(来源:作者)
二项式分布
这个参数为 n 和 p 的分布是伯努利分布的推广情况,其中 n 是随机实验的序列数, p 是每次实验中出现阳性结果的概率。我们可以把它想象成这样的实验:投掷一枚硬币 20 次,计算得到 9 个正面的概率,其中每次试验中正面的概率是 0.6。
二项式分布:重要的性质(来源:维基百科)
二项分布:PMF 和 CDF(来源:作者)
几何分布
这种类型的分布是用成功的概率 p 来模拟伯努利试验的次数,直到第一次成功,就像投掷骰子多次直到第一次出现“1”一样。
几何分布:重要的性质(来源:维基百科)
几何分布:PMF & CDF(来源:作者)
泊松分布
这种分布是为了模拟给定数量的事件 k 在固定的时间或空间间隔内发生的概率,如果这些事件以已知的恒定*均速率λ发生,并且独立于自上次事件以来的时间。示例包括呼叫中心收到的电话数量或每天的电子邮件数量等。
泊松分布:重要的性质(来源:维基百科)
泊松分布:PMF 和 CDF(来源:作者)
离散均匀分布
顾名思义,它用于模拟随机实验,其中 a 和 b (b≥ a)之间的所有离散结果都有相同的可能性,就像投掷公*骰子一样。
离散均匀分布:重要性质(来源:维基百科)
离散均匀分布:PMF 和 CDF(来源:作者)
指数分布
它用于模拟事件以恒定的*均速率λ连续独立发生的过程之间的时间概率分布,例如从现在到地震发生的时间。
指数分布的一个重要性质是“无记忆性”,这可以解释为如果一个事件在 30 秒后还没有发生,发生至少需要 10 秒以上的条件概率就等于在初始时间后观察到该事件超过 10 秒的无条件概率。
指数分布:重要的性质(来源:维基百科)
指数分布:PDF & CDF(来源:作者)
连续均匀分布
它描述了一个实验,其中结果是连续的,即可以取最小和最大值之间的任何值,表示为 a 和 b 。其中一个例子可以是随机数发生器。
连续均匀分布:重要性质(来源:维基百科)
连续均匀分布:PDF & CDF(来源:作者)
高斯分布
这种分布用于表示任何分布未知的实值随机变量。用均值 μ 和方差 σ 来表示。如果我们绘制一组推文中每条推文的字数,我们可以看到形状遵循一条'钟形曲线,并得出结论,分布是高斯或正态。我们将在后面看到为什么这种分布如此重要。
高斯分布:重要的性质(来源:维基百科)
高斯分布:PDF & CDF(来源:作者)
协方差和相关性
协方差测量两个独立随机变量的联合可变性,它们之间关系的强度由相关系数测量。如果协方差为 0,则 2 个随机变量是独立的,但反之不成立。
协方差和相关系数(资料来源:麻省理工开放式课程)
在我们的小示例中,我们使用“数据 1”生成“数据 2”,它们彼此相关,我们可以测量它们的 Spearman 的 相关系数ρ ,其范围从-1 到+1,其中-1 表示最强的负相关,0 表示独立,而+1 表示最强的正相关。
输出将告诉我们这两个变量是正相关的。
data1: Mean=100.192; Variance=399.991
data2: Mean=50.199; Variance=500.785
Covariance between data1 and data2:
[[400.01063636 400.46496352]
[400.46496352 500.81039823]]
Pearson Correlation Coefficient is 0.895 with p-value 0.000
两个随机变量之间的相关性(来源:作者)
大数定律
在统计学中,大数定律(LLN) 是一个定理,它表示如果我们多次进行相同的实验,那么实验结果的*均值应该接*真实*均值。 LLN 很重要,因为它保证了一些随机事件的*均长期稳定的结果,这就是为什么赌场即使在少数情况下赔钱,从长远来看也会赚钱的原因。
从数学上来说,如果同一个实验被执行 n ( n 非常大)次作为独立且相同(i.i.d) 的实验,那么结果的数字*均值将非常接*真实*均值 μ 。
大数定律
因为在下面的例子中,正态随机变量的任何线性组合也是随机的,所以'数据'变量的真实*均值是 5 * 0 + 10 = 10。我们将看到,当 n 从 10 增加到 100,000 时,数字*均值将变得更接*真实*均值 10。
如果我们运行上面的代码,我们会看到这个。
Average when n=10 is : 9.5142955459695
Average when n=1000 is : 10.184099506736104
Average when n=10000 is : 10.039233155010294
Average when n=100000 is: 10.013670784412874
中心极限定理
高斯分布的重要性来源于中心极限定理(CLT) 该定理指出,在某些条件下,一个均值和方差有限的随机变量的多次观测值的*均值本身就是一个随机变量,其分布随着样本数的增加而收敛于正态分布,即使原始变量本身不是正态分布。
中心极限定理
如果我们在减去真实*均值并乘以 √n 后绘制居中样本*均值的直方图,我们可以形象地得出结论:分布为高斯分布,*均值为 0。
骰子滚动模拟的样本均值直方图(来源:作者)
参数估计和假设检验
一旦我们确定了一个分布,我们就需要根据它进行统计推断,并对看不见的数据进行假设和验证。有三个主要的过程-
- 估计:识别描述我们分布的参数,了解其统计性质。
- 置信区间:参数的值的上下界,计算参数的值保持在此范围内的置信度(概率)。
- 假设检验:对我们的信念做出假设,并根据数据寻找接受/拒绝的证据。
置信区间
中心极限定理(CLT) 的优点是对于大量的观测值,任何分布都收敛于标准正态分布。
收敛于标准正态分布
上图中箭头的左侧称为实验的检验统计量,如果我们可以计算出检验统计量(Z 得分),我们就可以明确地计算出这个检验统计量大于临界值的概率,相反,如果我们知道概率( p 值,我们就可以计算出临界值,因为在标准正态下不同临界值的概率很容易得到,称为 Z
对于固定的α ∈ (0,1),如果 q_α/2 是 N (0,1)的(1α/2)-分位数,那么概率= 1α(如果 N 足够大(也称为渐*)),估计量的置信区间变成-
置信区间
输出是-
90 percent Confidence Interval is: 49.736, 50.870
假设检验
假设我们掷一枚硬币,我们假设硬币是公*的,即正面的概率是 0.5。这个假设形成了关于实验的假设,这个基线假设称为零假设 h0。另一个假设是硬币是不公*的,我们称之为替代假设 h1。我们掷 200 次,得到 170 次正面。如果我们根据中心极限定理形成检验统计量,我们将得到-
(√200)((170/200)-0.5)/√( 0.5 * 0.5))= 9.89,大于 1.645——标准正态分布的 95%分位数(假设α=0.05)。所以我们可以拒绝零假设,得出硬币不公*的结论。
假设检验的第一类错误发生在 h0 被拒绝但实际上为真时,第二类错误发生在 h0 未被拒绝但 h1 为真时。
我们将讨论常见的假设检验及其目的,但在此之前,我们将讨论几个特殊的分布。
学生的 t 分布
应用中心极限定理的假设是样本量很大。如果是小(< 30) then the distribution of the random variables will follow a t-分布而不是高斯分布。
学生的 t 分布:PDF & CDF(来源:作者)
卡方分布
具有 d 自由度的卡方分布(独立变量的数量)是 d 独立标准正态分布之和的分布。所以它的支持永远是正。它用于分布的拟合优度测试。
卡方分布:PDF & CDF(来源:作者)
学生的 t 检验
学生的 t 检验是一种统计假设检验,其中检验统计量遵循 t 分布下的零假设。我们可以想一个例子,看看印度人的*均“身高”在统计上是否与德国人不同。由于两个不同国家的“身高”根据中心极限定理遵循正态分布,并且它们彼此独立,我们可以应用学生的 t 检验。
输出是-
Test Statistic=0.799, p=0.425
Same distributions (fail to reject H0)
配对学生的 t 检验
假设我们测量几个人的血压,使用一些药物,一段时间后再次测量血压,以观察药物的影响。现在样本不是独立的,所以我们需要一个不同版本的 t 检验,叫做配对学生 t 检验。
输出是-
Test Statistic=0.783, p=0.434
Same distributions (fail to reject H0)
方差分析
ANOVA 是当我们有两个以上独立随机变量时 t 检验的推广形式。单向 ANOVA 用于确定 3 个或更多独立随机变量的*均值之间是否存在显著的统计差异。我们可以直接使用单向 ANOVA 并计算 F 统计量,而不是运行成对 t 检验。
如果随机变量是相关的,应进行重复测量 ANOVA 。
我已经在上面的代码中展示了手动计算过程,输出是-
Test Statistic=9.265, p=0.002
Different distributions (reject H0)
==> Native Method To Show the Calculation Method <==
F Statistic: 9.264705882352942
卡方检验
卡方检验是一种统计假设检验,属于拟合优度检验的范畴,其中检验统计量在零假设下预计遵循卡方分布,并用于检验分类变量的观察频率和预期频率之间是否存在统计上的显著差异。
使用权变表表示观察值,如以下代码所示,其中每个观察值都独立于其他观察值,并遵循正态分布。
卡方检验的一个典型示例是确定不同种族和年龄段人群之间的 COVID19 关系,其中不同种族可用作偶然性表的行,不同年龄段可用作列。
输出将是-
Observed Frequencies:
[[ 90\. 60\. 104\. 95.]
[ 30\. 50\. 51\. 20.]
[ 30\. 40\. 45\. 35.]]
dof=6
Expected Frequencies:
[[ 80.53846154 80.53846154 107.38461538 80.53846154]
[ 34.84615385 34.84615385 46.46153846 34.84615385]
[ 34.61538462 34.61538462 46.15384615 34.61538462]]
probability=0.950, critical=12.592, stat=24.571
Dependent (reject H0)
significance=0.050, p=0.000
Dependent (reject H0)
==> Native Method <==
Observed:
[[ 90\. 60\. 104\. 95.]
[ 30\. 50\. 51\. 20.]
[ 30\. 40\. 45\. 35.]]
Expected:
[[ 80.53846154 80.53846154 107.38461538 80.53846154]
[ 34.84615385 34.84615385 46.46153846 34.84615385]
[ 34.61538462 34.61538462 46.15384615 34.61538462]]
Statistic, dof: 24.5712028585826 , 6
常态测试
这种类型的检验属于非参数假设检验,其中我们没有任何要检验的参数,而是检验观察值是否符合正态分布。
Q-Q(分位数-分位数)图 在这里,我们试图形象地测量观察值的潜在随机变量的累积分布函数(CDF) 是否遵循一个正态分布的 CDF,我们绘制一个分布的分位数图。如果它们有相同的 CDF ,那么我们应该看到一条从左下角到右上角的直线。
我们可以生成一些随机数据并查看其分布,稍后我们将看到其 Q-Q 图。
KDE 图的资料(来源:作者)
我们可以看到峰*尾重的“数据”不是正常的。我们现在将看到它的 Q-Q 图,这也将证实我们的信念,即“数据”不是正态的,在尾部,样本分位数偏离理论分位数。
数据的 Q-Q 图(来源:作者)
夏皮罗-维尔克检验|达戈斯蒂诺的 K2 检验|安德森-达林检验 所有这三个都是非参数检验,测量我们样本分布的 CDF 与要检查的分布的 CDF 有多远。如果我们使用相同的“数据”变量,我们将看到这 3 个测试也将确认“数据”变量不是正常分布的。
输出将是-
==> Shapiro-Wilk Test <==
Statistic=0.955, p=0.000
Sample does not look Gaussian (reject H0)
==> D'Agostino's K2 Test <==
Statistic=4209.988, p=0.000
Sample does not look Gaussian (reject H0)
==> Anderson-Darling Test <==
Statistic, Critical Values: 55.542 [0.576 0.655 0.786 0.917 1.091]
15.000: 0.576, data does not look normal (reject H0)
10.000: 0.655, data does not look normal (reject H0)
5.000: 0.786, data does not look normal (reject H0)
2.500: 0.917, data does not look normal (reject H0)
1.000: 1.091, data does not look normal (reject H0)
结论
筛选出常见的统计概念并用非常通用的术语解释它们确实是一项艰巨的任务,但我想试一试。如果你已经看完了整篇文章,那么我肯定能为你对概念的理解增加一些价值。如果您有任何建议和意见,请联系我的 LinkedIn 账号。我会带着另一个故事再来。敬请期待!
这篇文章的所有代码都可以在我的 GitHub 资源库中获得,也可以作为 Jupyter 笔记本 获得。
[## Subhamoy Bhaduri -助理-项目-认知| LinkedIn
8.5 年卡和支付领域的工作经验,擅长商户收单业务。#专业知识…
www.linkedin.com](https://www.linkedin.com/in/subhamoybhaduri/)
用 Twitter 了解公众对核能的情绪
一个文本分类项目与裘德布埃纳塞达
来源:Pixabay
随着气候变化的影响变得越来越普遍,替代能源的必要性变得更加迫切。因此,围绕核能的讨论是不可能避免的,因为它是一个关键的竞争者,其效率是不可否认的。然而,核能已经成为一个分裂和两极分化的话题,因为如果没有适当的预防措施,其副产品可能对人类健康和环境产生毁灭性影响。
对于这个项目,我们想了解人们对核能的情绪是否容易被识别,以及它看起来像什么。在 Twitter 及其用户意见的帮助下,我们能够尝试这种分类。我们这个项目的目标是首先通过对推特上的情绪进行分类来了解公众对核能的看法,然后根据与每个情绪相关的关键词或主题提取价值。这对于公司和政策制定者来说都是非常有用的,可以围绕他们的受众所谈论的内容来优化和定制信息和策略。
数据
我们使用 Twint 从 Twitter 上随机抓取了 3000 条使用搜索词Nuclear Energy
的推文。所有推文都来自 2020 年。下面是我们数据集中的一个 tweet 文本示例:
| Date: 2020-06-12 | Time 15:06:43 | Tweet: "It's dangerous to lift the prohibition on nuclear. Radioactive waste containment isn't clean energy, it's an accident waiting to happen. Nuclear energy lobbyist should never be trusted, they'll lie to make a profit, even if it means cancer and birth defects for the people."
每条推文的情绪都在维德的协助下贴上了标签。这个项目中使用的三种标签分别是:阳性、阴性和中性。我们标记的推文的分布严重不*衡——大多数推文带有中性情绪,然后是积极情绪,然后是消极情绪。
标签分布,用 Seaborn 绘制
我们最初的 EDA 侧重于分析每个情感类的词频。在下面的单词云中,可以开始观察到不同类别之间在词汇上的明显差异。
负面推文中的前 20 个词
正面推文中的前 20 个词
中性推文中的前 20 个词
中性班的学生似乎肯定会选择更多的研究和信息主题。
数据处理和特征工程
由于我们只处理每条推文的文本数据,建模之前采取的清理和工程步骤是:
- 使用 NLTK 删除了英语停用词。
- 标点符号、链接和非字母字符也用正则表达式从推文中删除。
- 我们定制了我们的停用词列表,包括我们的搜索词和要从每条推文中删除的主题——“核能”、“核能”、“核能”和“能源”。
- 每条 tweet 都使用 NLTK 的 TF-IDF 实现进行了标记化、矢量化和小写转换。
- 在我们的 EDA 中使用了词频,但是我们的模型依赖于每个词的 TF-IDF 分数。
建模
为了对一条推文的情绪进行分类,我们训练并测试了以下模型——基线虚拟分类器(预测多数类)、朴素贝叶斯分类器、随机森林分类器和线性 SVM 分类器。由于我们的班级最初是不*衡的,我们必须在模型中考虑到这一点。下面是我们测试预测的准确性得分的快照:
使用 sklearn 建模结果
我们的线性 SVM 模型表现最好,其次是随机森林。线性 SVM 与自然语言处理和文本分类配合得非常好,因为我们最终得到了比观察值更多的特征。
使用我们的线性 SVM,我们能够获得有用的洞察力,了解模型正在使用什么特征或词来进行情感分类。在这三个图表中,我们可视化了每个情感的前 5 个特征/词系数。在很大程度上,这些话看起来很合理,并为每种情绪的分类提供了透明度。
总结想法&要点
Twitter 等社交媒体的使用已经成为获取任何和所有话题的公众意见的重要工具。这对公司和政策制定者来说都是至关重要的信息。我们能够使用 Twitter 来捕捉公众对核能的看法,并强调围绕每种情绪的关键问题和主题——积极的、消极的和中立的。这对于公司和政策制定者来说,在营销和信息传递方面意义重大。举个例子——废料经常与负面讨论联系在一起,所以我们可以得出结论,一个很大的担忧是围绕核废料及其后果,而不是能源本身。因此,可以在考虑到这一点的情况下精心制作信息,以消除顾虑和担忧。
后续步骤
为了扩展我们模型的预测能力,添加其他非文本特征以及探索神经网络模型可能是值得的。
来源:Pixabay
如果你对这个项目背后的代码感兴趣——看看我的 Github:【https://github.com/AlisonSalerno/twitter_sentiment_analysis
理解 Python 字节码
了解如何反汇编 Python 字节码
编程语言的源代码可以使用解释器或编译器来执行。在编译语言中,编译器会将源代码直接翻译成二进制机器代码。该机器代码特定于目标机器,因为每台机器可以具有不同操作系统和硬件。编译后,目标机器会直接运行机器码。
在解释语言中,源代码不是由目标机器直接运行的。还有一个程序叫做解释器,它直接读取并执行源代码。特定于目标机器的解释器将源代码的每个语句翻译成机器代码并运行它。
Python 通常被称为解释语言,然而,它结合了编译和解释。当我们执行一个源代码(扩展名为.py
的文件)时,Python 首先将其编译成字节码。字节码是独立于*台的低级源代码表示,但是,它不是二进制机器码,不能由目标机器直接运行。事实上,它是一个虚拟机的指令集,这个虚拟机被称为 Python 虚拟机(PVM)。
编译后,字节码被发送到 PVM 执行。PVM 是运行字节码的解释器,是 Python 系统的一部分。字节码是独立于*台的,但是 PVM 是特定于目标机器的。Python 编程语言的默认实现是用 C 编程语言编写的 CPython。CPython 将 Python 源代码编译成字节码,然后这个字节码由 CPython 虚拟机执行。
生成字节码文件
在 Python 中,字节码存储在一个.pyc
文件中。在 Python 3 中,字节码文件存储在一个名为__pycache__
的文件夹中。当您尝试导入您创建的另一个文件时,会自动创建此文件夹:
import file_name
但是,如果我们不在源代码中导入另一个文件,它就不会被创建。在这种情况下,我们仍然可以手动创建它。要从命令行编译单个文件file_1.py
到file_n.py
,我们可以写:
python -m compileall file_1.py ... file_n.py
所有生成的pyc
文件都将存储在__pycache__
文件夹中。如果在compileall,
之后没有提供文件名,它将编译当前文件夹中的所有 python 源代码文件。
我们还可以使用compile()
函数来编译包含 Python 源代码的字符串。该函数的语法是:
compile(*source*, *filename*, *mode*, *flag*, *dont_inherit*, *optimize*)
我们只关注前三个必需的参数(其他的是可选的)。source
是要编译的源代码,可以是字符串、字节对象或 AST 对象。filename
是源代码所来自的文件的名称。如果源代码不是来自一个文件,你可以写你喜欢的任何东西或者留下一个空字符串。mode
可以是:
'exec'
:接受任何形式的 Python 源代码(任意数量的语句或块)。它将它们编译成字节码,最终返回None
'eval'
:接受单个表达式,并将其编译成字节码,最终返回该表达式的值
'single'
:只接受一条语句(或用;
分隔的多条语句)。如果最后一条语句是一个表达式,那么产生的字节码会将该表达式的值的repr()
打印到标准输出中。
例如,为了编译一些 Python 语句,我们可以编写:
s='''
a=5
a+=1
print(a)
'''
compile(s, "", "exec")
或者换句话说:
compile("a=5 \na+=1 \nprint(a)", "", "exec")
要评估一个表达式,我们可以写:
compile("a+7", "", "eval")
如果您没有表达式,此模式会给出一个错误:
# This does not work:
compile("a=a+1", "", "eval")
这里a=a+1
不是一个表达式,不返回任何东西,所以我们不能使用eval
模式。但是,我们可以使用single
模式来编译它:
compile("a=a+1", "", "single")
但是compile
返回的是什么?当您运行compile
函数时,Python 返回:
<code object <module> at 0x000001A1DED95540, file "", line 1>
所以compile
函数返回的是一个代码对象(在at
之后的地址在你的机器上可以不同)。
代码对象
compile()
函数返回一个 Python 代码对象。Python 中的一切都是对象。例如,我们定义了一个整数变量,它的值存储在一个int
对象中,您可以使用type()
函数轻松检查它的类型:
a = 5
type(a) # Output is: int
以类似的方式,编译函数生成的字节码存储在code
对象中。
c = compile("a=a+1", "", "single")
type(c) # Output is: code
code 对象不仅包含字节码,还包含 CPython 运行字节码所需的一些其他信息(稍后将讨论)。代码对象可以通过传递给exec()
或eval()
函数来执行或评估。所以我们可以写:
exec(compile("print(5)", "", "single")) # Output is: 5
当你在 Python 中定义一个函数时,它会为它创建一个代码对象,你可以使用__code__
属性来访问它。例如,我们可以写:
def f(n):
return n
f.__code__
输出将是:
<code object f at 0x000001A1E093E660, file "<ipython-input-61-88c7683062d9>", line 1>
像任何其他对象一样,code 对象也有一些属性,要获得存储在 code 对象中的字节码,可以使用它的co_code
属性:
c = compile("print(5)", "", "single")
c.co_code
输出是:
b'e\x00d\x00\x83\x01F\x00d\x01S\x00'
结果是一个前缀为b'.
的字节文字,它是一个不可变的字节序列,类型为bytes
。每个字节可以有一个 0 到 255 的十进制值。所以一个字节文字是一个不可变的 0 到 255 之间的整数序列。每个字节可由字符代码与字节值相同的 ASCII 字符显示,也可由前导\x
后跟两个字符显示。前导\x
转义意味着接下来的两个字符被解释为字符代码的十六进制数字。例如:
print(c.co_code[0])
chr(c.co_code[0])
给出:
101
'e'
因为第一个元素具有十进制值 101,并且可以用字符e
来显示,该字符的 ASCII 字符代码是 101。或者:
print(c.co_code[4])
chr(c.co_code[4])
给出:
131
'\x83'
因为第 4 个元素的十进制值为 131。131 的十六进制值是 83。所以这个字节可以用一个字符码为\x83
的字符来表示。
这些字节序列可以被 CPython 解释,但是它们对人类不友好。所以我们需要理解这些字节是如何映射到 CPython 将要执行的实际指令的。在下一节中,我们将把字节码分解成一些对人友好的指令,看看 CPython 如何执行字节码。
字节码细节
在深入细节之前,重要的是要注意字节码的实现细节通常会随着 Python 版本的不同而变化。因此,您在本文中看到的内容可能并不适用于 Python 的所有版本。事实上,它包括了 3.6 版本中发生的变化,一些细节可能对旧版本无效。本文中的代码已经用 Python 3.7 进行了测试。
字节码可以被认为是 Python 解释器的一系列指令或低级程序。在 3.6 版本之后,Python 对每条指令使用 2 个字节。一个字节用于该指令的代码,称为操作码,一个字节保留用于其参数,称为 oparg。每个操作码都有一个友好的名字,叫做 opname 。字节码指令的一般格式如下:
opcode oparg
opcode oparg
.
.
.
我们的字节码中已经有了操作码,我们只需要将它们映射到它们对应的 opname。有一个名为dis
的模块可以帮助解决这个问题。在这个模块中,有一个名为opname
的列表,它存储了所有的 opnames。此列表的第 i 个元素给出了操作码等于 i 的指令的 opname。
有些指令不需要参数,所以它们会忽略操作码后面的字节。值低于某个数字的操作码会忽略它们的参数。该值存储在dis.HAVE_ARGUMENT
中,目前等于 90。于是操作码> = dis.HAVE_ARGUMENT
有了争论,操作码< dis.HAVE_ARGUMENT
忽略。
例如,假设我们有一个短字节码b'd\x00Z\x00d\x01S\x00'
,我们想反汇编它。这个字节码代表一个四字节的序列。我们可以很容易地显示它们的十进制值:
bytecode = b'd\x00Z\x00d\x01S\x00'
for byte in bytecode:
print(byte, end=' ')
输出将是:
100 0 90 0 100 1 83 0
字节码的前两个字节是100 0
。第一个字节是操作码。要得到它的 opname 我们可以写(dis
应该先导入):
dis.opname[100]
而结果是LOAD_CONST
。由于操作码大于dis.HAVE_ARGUMENT
,它有一个 oparg,即第二个字节0
。所以100 0
翻译过来就是:
LOAD_CONST 0
字节码中的最后两个字节是83 0
。我们再次写dis.opname[83]
,结果是RETURN_VALUE
。83 小于 90 ( dis.HAVE_ARGUMENT
),所以该操作码忽略 oparg,并且83 0
被分解为:
RETURN_VALUE
此外,某些指令的参数可能太大,无法放入默认的一个字节中。有一个特殊的操作码144
来处理这些指令。它的 opname 是EXTENDED_ARG
,也存放在dis.EXTENDED_ARG
。这个操作码是任何参数大于一个字节的操作码的前缀。例如,假设我们有操作码 131(它的 opname 是CALL_FUNCTION
),它的 oparg 需要是 260。所以应该是:
CALL_FUNCTION 260
但是,一个字节可以存储的最大数量是 255,260 不适合一个字节。所以这个操作码的前缀是EXTENDED_ARG
:
EXTENDED_ARG 1
CALL_FUNCTION 4
当解释器执行EXTENDED_ARG
时,它的 oparg(为 1)左移 8 位,并存储在一个临时变量中。姑且称之为extended_arg
(不要和 opname EXTENDED_ARG
混淆):
extened_arg = 1 << 8 # same as 1 * 256
于是二进制值0b1
(二进制值 1)被转换为0b100000000
。这就像在十进制系统中 1 乘以 256,extened_arg
将等于 256。现在我们在extened_arg
中有两个字节。当解释器执行到下一条指令时,使用按位or
将这个双字节值添加到它的 oparg(这里是 4)中。
extened_arg = extened_arg | 4
# Same as extened_arg += 4
这就像把 oparg 的值加到extened_arg
上。所以现在我们有:
extened_arg = 256 + 4 = 260
该值将作为CALL_FUNCTION
的实际 oparg。所以,事实上,
EXTENDED_ARG 1
CALL_FUNCTION 4
被解释为:
EXTENDED_ARG 1
CALL_FUNCTION 260
对于每个操作码,最多允许三个前缀EXTENDED_ARG
,形成一个从两字节到四字节的参数。
现在,我们可以专注于 oparg 本身。这是什么意思?实际上,每个 oparg 的含义取决于它的操作码。如前所述,代码对象存储除字节码之外的一些信息。可以使用代码对象的不同属性来访问这些信息,我们需要其中的一些属性来解释每个 oparg 的含义。这些属性是:co_consts
、co_names
、co_varnames
、co_cellvars
和co_freevars
。
代码对象属性
我将用一个例子来解释这些属性的含义。假设您有这个源代码的代码对象:
# Listing 1
s = '''
a = 5
b = 'text'
def f(x):
return x
f(5)
'''
c=compile(s, "", "exec")
现在,我们可以检查每个属性中存储了什么:
1- co_consts
:包含字节码使用的文字的元组。这里c.co_consts
返回:
(5, 'text', <code object f at 0x00000218C297EF60, file "", line 4>, 'f', None)
所以字面量5
和'text'
以及函数名'f'
都存储在这个元组中。此外,函数f
的主体存储在一个单独的代码对象中,并被视为一个同样存储在该元组中的文字。记住compile()
中的exec
模式生成一个最终返回None
的字节码。这个None
值也被存储为一个文字。事实上,如果你像这样在eval
模式下编译一个表达式:
s = "3 * a"
c1 = compile(s, "", "eval")
c1.co_consts # Output is (3,)
None
将不再包含在co_consts
元组中。原因是这个表达式返回它的最终值而不是None
。
如果你试图获取一个函数的目标代码的co_const
,比如:
def f(x):
a = x * 2
return a
f.__code__.co_consts
结果会是(None, 2)
。事实上,函数的默认返回值是None
,并且总是作为文字添加。正如我后面解释的,为了提高效率,Python 不会检查你是否总是要到达一个return
语句,所以None
总是被添加为默认返回值。
2- co_names
:包含字节码使用的名称的元组,可以是全局变量、函数和类,也可以是从对象加载的属性。例如,对于清单 1 中的目标代码,c.co_names
给出了:
('a', 'b', 'f')
3- co_varnames
:包含字节码使用的本地名称的元组(首先是参数,然后是本地变量)。如果我们对清单 1 的对象代码进行尝试,它会给出一个空元组。原因是局部名称是在函数内部定义的,清单 1 中的函数是作为一个单独的代码对象存储的,所以它的局部变量不会包含在这个元组中。为了访问一个函数的局部变量,我们应该为那个函数的代码对象使用这个属性。所以我们先写这段源代码:
def f(x):
z = 3
t = 5
def g(y):
return t*x + y
return g
a = 5
b = 1
h = f(a)
现在f.__code__
给出f
的代码对象,f.__code__.co_varnames
给出:
('x', 'z', 'g')
为什么不包括t
?原因是t
不是f
的局部变量。它是一个非局部变量,因为它是由f
中的闭包g
访问的。实际上,x
也是一个非局部变量,但既然是函数的自变量,那么它总是包含在这个元组中。要了解更多关于闭包和非局部变量的知识,你可以参考这篇文章。
4- co_cellvars
:包含非局部变量名称的元组。这些是由内部函数访问的函数的局部变量。所以f.__code__.co_cellvars
给出:
('t', 'x')
5- co_freevars
: 包含自由变量名称的元组。自由变量是外部函数的局部变量,由内部函数访问。所以这个属性应该和闭包h
的代码对象一起使用。现在h.__code__.co_freevars
给出了相同的结果:
('t', 'x')
现在我们已经熟悉了这些属性,我们可以回到 opargs。每个 oparg 的含义取决于它的操作码。我们有不同类别的操作码,每个类别的 oparg 都有不同的含义。在dis
模块中,有一些列表给出了每个类别的操作码:
1- dis.hasconst
:此列表等于【100】。所以只有操作码 100(它的 opname 是 LOAD_CONST)属于hasconst
的范畴。这个操作码的 oparg 给出了co_consts
元组中一个元素的索引。例如,在清单 1 的字节码中,如果我们有:
LOAD_CONST 1
那么 oparg 就是索引为 1 的co_consts
的元素。所以我们要把1
换成co_consts[1]
等于'text'
。因此该指令将被解释为:
LOAD_CONST 'text'
类似地,在dis
模块中有一些其他的列表定义操作码的其他类别:
2- dis.hasname
:这个列表中操作码的 oparg 是co_names
中一个元素的索引
3- dis.haslocal
:该列表中操作码的 oparg 是co_varnames
中一个元素的索引
4- dis.hasfree
:该列表中操作码的 oparg 是co_cellvars + co_freevars
中一个元素的索引
5- dis.hascompare
:这个列表中操作码的 oparg 是元组dis.cmp_op
的一个元素的索引。这个元组包含比较和成员操作符,如<
或==
6- dis.hasjrel
:这个列表中操作码的 oparg 应该替换为offset + 2 + oparg
,其中offset
是字节码序列中代表操作码的字节的索引。
code 对象还有一个更重要的属性应该在这里讨论。它被称为co_lnotab
,存储字节码的行号信息。这是一个存储在字节文字中的有符号字节数组,用于将字节码偏移量映射到源代码行号。我举个例子解释一下。假设您的源代码只有三行,并且已经被编译成 24 字节的字节码:
1 0 LOAD_CONST 0
2 STORE_NAME 0
2 4 LOAD_NAME 0
6 LOAD_CONST 1
8 INPLACE_ADD
10 STORE_NAME 0
3 12 LOAD_NAME 1
14 LOAD_NAME 0
16 CALL_FUNCTION 1
18 POP_TOP
20 LOAD_CONST 2
22 RETURN_VALUE
现在我们有了从字节码偏移量到行号的映射,如下表所示:
字节码偏移量总是从 0 开始。code 对象有一个名为co_firstlineno
的属性,它给出了偏移量 0 的行号。对于这个例子来说co_firstlineno
等于 1。Python 只存储从一行到下一行的增量(不包括第一行),而不是存储偏移量和行号。所以前面的表格变成了:
这两个增量列按如下顺序压缩在一起:
4 1 8 1
每个数字存储在一个字节中,整个序列作为一个字节文字存储在代码对象的co_lnotab
中。因此,如果您检查co_lnotab
的值,您会得到:
b'\x04\x01\x08\x01'
它是前一个序列的字节数。因此,通过拥有属性co_lnotab
和co_firstlineno
,您可以检索从字节码偏移到源代码行号的映射。co_lnotab
是一个有符号的字节序列。所以其中的每个有符号字节可以取-128 到 127 之间的值(这些值仍然存储在取 0 到 255 的字节中。但是介于 128 和 255 之间的值被认为是负数)。负增量意味着行号在减少(这个特性用在优化器中)。但是如果行增量大于 127 会怎么样呢?在这种情况下,行增量将被分成 127 和一些额外的字节,这些额外的字节将以零偏移增量存储(如果它小于-128,它将被分成-128 和一些额外的字节,偏移增量为零)。例如,假设字节码偏移量与行号的关系如下:
那么偏移增量与行号增量之比应为:
139 等于 127 + 12。所以前一行应该写成:
并且应该存储为8 127 0 12
。所以co_lnotab
的值会是:b'\x08\x7f\x00\x0c'
。
反汇编字节码
现在我们已经熟悉了字节码结构,我们可以编写一个简单的反汇编程序。我们首先编写一个生成器函数来解包每个指令,并生成偏移量、操作码和 oparg:
这个函数从字节码中读取下一对字节。第一个字节是操作码。通过将该操作码与dis.HAVE_ARGUMENT
进行比较,该函数决定是将第二个字节作为 oparg 还是忽略它。使用按位 or ( |
)将extended_arg
的值添加到 oparg。最初,它为零,对 oparg 没有影响。如果操作码等于dis.EXTENDED_ARG
,它的 oparg 将左移 8 位,并存储在一个名为extended_arg
的临时变量中。
在下一次迭代中,这个临时变量将被添加到下一个 oparg 中,并向其中添加一个字节。如果下一个操作码再次是dis.EXTENDED_ARG
,则该过程继续,并且每次将一个字节加到extended_arg
。最后,当它到达一个不同的操作码时,extended_arg
将被加到它的 oparg 并被设置回零。
find_linestarts
函数返回一个字典,其中包含每个字节码偏移量的源代码行号。
它首先将co_lnotab
字节文字分成两个序列。一个是偏移增量,另一个是行号增量。偏移0
的行号在co_firstlineno
中。将这两个数字相加得到字节码偏移量及其对应的行号。如果行号增量等于或大于 128 (0x80),它将被视为减量。
get_argvalue
函数返回每个 oparg 的友好含义。它首先检查操作码属于哪个类别,然后判断 oparg 指的是什么。
findlabels
函数找到字节码中所有作为跳转目标的偏移量,并返回这些偏移量的列表。跳转目标将在下一节讨论。
现在我们可以使用所有这些函数来反汇编字节码。dissassemble
函数获取一个代码对象并将其反汇编:
它将首先解包代码对象的字节码中每对字节的偏移量、操作码和 oparg。然后,它找到相应的源代码行号,并检查偏移量是否是跳转目标。最后,它查找 opname 和 oparg 的含义,并打印所有信息。如前所述,每个函数定义都存储在一个单独的代码对象中。所以在最后,函数递归地调用自己来反汇编字节码中的所有函数定义。下面是一个使用这个函数的例子。最初,我们有这样的源代码:
a=0
while a<10:
print(a)
a += 1
我们首先将它存储在一个字符串中,并编译它以获得目标代码。然后我们使用disassemble
函数反汇编它的字节码:
s='''a=0
while a<10:
print(a)
a += 1
'''
c=compile(s, "", "exec")
disassemble(c)
输出是:
1 0 LOAD_CONST 0 (0)
2 STORE_NAME 0 (a)
2 4 SETUP_LOOP 28 (to 34)
>> 6 LOAD_NAME 0 (a)
8 LOAD_CONST 1 (10)
10 COMPARE_OP 0 (<)
12 POP_JUMP_IF_FALSE 32
3 14 LOAD_NAME 1 (print)
16 LOAD_NAME 0 (a)
18 CALL_FUNCTION 1
20 POP_TOP
4 22 LOAD_NAME 0 (a)
24 LOAD_CONST 2 (1)
26 INPLACE_ADD
28 STORE_NAME 0 (a)
30 JUMP_ABSOLUTE 6
>> 32 POP_BLOCK
>> 34 LOAD_CONST 3 (None)
36 RETURN_VALUE
所以 4 行源代码被转换成 38 字节的字节码或 19 行字节码。在下一节中,我将解释这些指令的含义以及 CPython 将如何解释它们。
模块dis
有一个名为dis()
的函数,同样可以反汇编代码对象。实际上,本文中的disassmble
函数是dis.dis
函数的简化版。因此,我们可以写dis.dis(c)
来得到类似的输出,而不是写disassemble(c)
。
反汇编一个 pyc 文件
如前所述,编译源代码时,字节码存储在一个pyc
文件中。这个字节码可以用类似的方式反汇编。但是,需要提到的是,pyc
文件包含一些元数据和 编组 格式的代码对象。封送格式用于 Python 的内部对象序列化。元数据的大小取决于 Python 版本,对于版本 3.7,它是 16 个字节。所以当你读取pyc
文件时,首先你应该读取元数据,然后使用marshal
模块加载代码对象。例如,要反汇编__pycache__
文件夹中名为u1.cpython-37.pyc
的pyc
文件,我们可以写:
字节码操作
到目前为止,我们已经学习了如何反汇编字节码指令。我们现在可以关注这些指令的含义以及它们是如何被 CPython 执行的。Python 的默认实现 CPython 使用基于栈的虚拟机。所以首先我们应该熟悉堆栈。
堆栈和堆
栈是一种具有后进先出顺序的数据结构。它有两个主要操作:
- push:将元素添加到堆栈中
- pop:删除最*添加的元素
因此,添加或推入堆栈的最后一个元素是要移除或弹出的第一个元素。使用堆栈存储数据的好处是内存是为你管理的。读取和写入堆栈非常快,但是,堆栈的大小是有限的。
Python 中的数据表示为存储在私有堆上的对象。与堆栈相比,访问堆上的数据要慢一些,但是,堆的大小只受虚拟内存大小的限制。heap 的元素彼此之间没有依赖关系,可以随时随机访问。Python 中的一切都是对象,对象总是存储在堆中。它只是存储在堆栈中的对象的引用(或指针)。
CPython 使用调用栈来运行 Python 程序。在 Python 中调用一个函数时,一个新的框架 被推送到调用栈上,每次函数调用返回时,其框架被弹出。程序运行的模块有一个最底层的框架,称为全局框架或模块框架。
每一帧都有一个评估栈,在那里执行 Python 函数。函数参数及其局部变量被压入这个计算堆栈。CPython 使用评估堆栈来存储任何操作所需的参数以及这些操作的结果。在开始该操作之前,所有必需的参数都被推送到评估堆栈上。然后操作开始并弹出它的参数。当操作完成时,它将结果推回计算堆栈。
所有对象都存储在堆中,框架中的评估堆栈处理对它们的引用。因此,对这些对象的引用可以被临时推到计算堆栈上,以供后面的操作使用。Python 的大部分字节码指令都是在当前框架中操作求值栈。在本文中,每当我们谈到堆栈时,它指的是当前框架中的评估堆栈或全局框架中的评估堆栈,如果我们不在任何函数的范围内。
让我从一个简单的例子开始,反汇编以下源代码的字节码:
a=1
b=2
c=a+b
为此,我们可以写:
s='''a=1
b=2
c=a+b
'''
c=compile(s, "", "exec")
disassemble(c)
我们得到了:
1 0 LOAD_CONST 0 (1)
2 STORE_NAME 0 (a)
2 4 LOAD_CONST 1 (2)
6 STORE_NAME 1 (b)
3 8 LOAD_NAME 0 (a)
10 LOAD_NAME 1 (b)
12 BINARY_ADD
14 STORE_NAME 2 (c)
16 LOAD_CONST 2 (None)
18 RETURN_VALUE
此外,我们可以检查代码对象的其他一些属性:
c.co_consts
# output is: (1, 2, None)
c.co_names
# output is: ('a', 'b', 'c')
这里代码在模块中运行,所以我们在全局框架中。第一个指令是LOAD_CONST 0
。指令
**LOAD_CONST** *consti*
将co_consts[consti]
的值推送到堆栈上。所以我们将co_consts[0]
(等于1
)压入堆栈。
值得注意的是,stack 使用对对象的引用。因此,每当我们说一个指令将一个对象或对象的值压入堆栈时,就意味着压入了对该对象的引用(或指针)。当一个对象或它的值被弹出堆栈时,也会发生同样的事情。再次弹出它的引用。解释器知道如何使用这些引用来检索或存储对象的数据。
指令
**STORE_NAME** *namei*
弹出堆栈的顶部,并将其存储到一个对象中,该对象的引用存储在代码对象的co_names[namei]
中。所以STORE_NAME 0
在栈顶弹出元素(也就是1
)并存储在一个对象中。对这个对象的引用是co_names[0]
也就是a
。这两条指令是源代码中a=1
的字节码等价物。b=2
被类似地转换,现在解释器已经创建了对象a
和b
。源代码的最后一行是c=a+b
。指令
**BINARY_ADD**
弹出堆栈顶部的两个元素(1
和2
),将它们相加,并将结果(3
)推送到堆栈上。所以现在3
在栈顶。之后STORE_NAME 2
将栈顶弹出到本地对象(引用的)c
。现在记住exec
模式下的compile
将源代码编译成最终返回None
的字节码。指令LOAD_CONST 2
将co_consts[2]=None
推到堆栈上,指令
**RETURN_VALUE**
将堆栈的顶部返回给函数的调用方。当然,这里我们是在模块范围内,没有调用函数,所以None
是最终结果,它保留在全局堆栈的顶部。图 1 显示了偏移量为 0 到 14 的所有字节码操作(同样应该注意的是,对象的引用被推送到堆栈上,而不是对象或它们的值。该图没有明确示出)。
函数、全局和局部变量
现在让我们看看如果我们也有一个函数会发生什么。我们将分解源代码的字节码,它有一个功能:
#Listing 2
s='''a = 1
b = 2
def f(x):
global b
b = 3
y = x + 1
return y
f(4)
print(a)
'''
c=compile(s, "", "exec")
disassemble(c)
输出是:
1 0 LOAD_CONST 0 (1)
2 STORE_NAME 0 (a)
2 4 LOAD_CONST 1 (2)
6 STORE_GLOBAL 1 (b)
3 8 LOAD_CONST 2 (<code object f at 0x00000218C2E758A0, file "", line 3>)
10 LOAD_CONST 3 ('f')
12 MAKE_FUNCTION 0
14 STORE_NAME 2 (f)
8 16 LOAD_NAME 2 (f)
18 LOAD_CONST 4 (4)
20 CALL_FUNCTION 1
22 POP_TOP
9 24 LOAD_NAME 3 (print)
26 LOAD_NAME 0 (a)
28 CALL_FUNCTION 1
30 POP_TOP
32 LOAD_CONST 5 (None)
34 RETURN_VALUE
Disassembly of<code object f at 0x00000218C2E758A0, file "", line 3>:
5 0 LOAD_CONST 1 (3)
2 STORE_GLOBAL 0 (b)
6 4 LOAD_FAST 0 (x)
6 LOAD_CONST 2 (1)
8 BINARY_ADD
10 STORE_FAST 1 (y)
7 12 LOAD_FAST 1 (y)
14 RETURN_VALUE
此外,我们可以检查代码对象的其他一些属性:
c.co_consts
# output is: (1, 2, <code object f at 0x00000218C2E758A0, file "", line 3>, 'f', 4, None)c.co_names
# Output is: ('a', 'b', 'f', 'print')
在第一行(偏移量 0 和 2 ),首先使用LOAD_CONST 0
将常量1
推入全局帧的评估堆栈。然后STORE_NAME 0
弹出并存储在一个对象中。
在第二行中,使用LOAD_CONST 1
将常量2
推入堆栈。但是,使用不同的 opname 将其分配给引用。指令
**STORE_GLOBAL** *namei*
弹出栈顶并将其存储到一个对象中,该对象的引用存储在co_names[namei]
中。所以2
存储在b
引用的对象中。这被认为是一个全局变量。但是为什么这个指令没有用于a
?原因是a
是函数f
内部的全局变量。如果变量是在模块范围内定义的,并且没有函数访问它,那么它将通过STORE_NAME
和LOAD_NAME
被存储和加载。在模块范围内,全局变量和局部变量没有区别。
第三行定义了函数f
。函数体在一个名为<code object f at 0x00000218C2E758A0, file "", line 3>
的单独代码对象中编译,并被推送到堆栈上。然后,一个函数名为'f'
的字符串对象被推送到堆栈上(事实上,对它们的引用被推送到堆栈上)。指令
**MAKE_FUNCTION** *argc*
用于创建函数。它需要一些应该被推到堆栈上的参数。函数名应该在栈顶,函数的代码对象应该在栈底。在这个例子中,它的 oparg 是零,但是它可以有其他值。例如,如果函数定义有一个关键字参数,如:
def f(x=5):
global b
b = 3
y = x + 1
return y
那么第 2 行的反汇编字节码应该是:
2 4 LOAD_CONST 5 ((5,))
6 LOAD_CONST 1 (<code object f at 0x00000218C2E75AE0, file "", line 2>)
8 LOAD_CONST 2 ('f')
10 MAKE_FUNCTION 1
MAKE_FUNCTION
的 oparg1
表示该函数有一些关键字参数,一个包含默认值的 tuple 应该在该函数的 code 对象(这里是(5,)
)之前被推送到堆栈上。创建函数后,MAKE_FUNCTION
将新的函数对象推送到堆栈上。然后在偏移量 14 处,STORE_NAME 2
弹出函数对象,并存储为f
引用的函数对象。
现在让我们看看从第 5 行开始的f(x)
的代码对象内部。语句global a
不会转换成字节码中的独立指令。它只是指导编译器将a
视为一个全局变量。所以STORE_GLOBAL 0
将被用来改变它的值。指令
**LOAD_GLOBAL** *namei*
将对由co_names[namei]
引用的对象的引用推送到堆栈上。然后使用STORE_GLOBAL 0
将其存储在b
中。指令
**LOAD_FAST** *var_num*
将引用为co_varnames[var_num]
的对象的引用推送到堆栈上。在函数f
的代码对象中,属性co_varnames
包含:
('x', 'y')
因此LOAD_FAST 0
将x
推到堆栈上。然后1
被推到堆栈上。BINARY_ADD
弹出x
和1
,将它们相加,并将结果推送到堆栈上。指令
**STORE_FAST** *var_num*
弹出堆栈的顶部,并将其存储到一个对象中,该对象的引用存储在co_varnames[var_num]
中。所以STORE_FAST 1
弹出结果并存储在一个引用为y
的对象中。LOAD_FAST
和STORE_FAST
用于函数的局部变量。因此它们不在模块范围内使用。另一方面,LOAD_GLOBAL
和STORE_GLOBAL
用于函数内部访问的全局变量。最后,LOAD_FAST 1
将把y
的值推到栈顶,而RETURN_VALUE
将把它返回给模块函数的调用者。
但是这个函数怎么调用呢?如果你看第 8 行的字节码,首先,LOAD_NAME
2
将引用为f
的函数对象推送到堆栈上。LOAD_CONST 4
将其参数(4
)推送到堆栈上。指令
**CALL_FUNCTION** *argc*
用位置参数调用可调用对象。它的 oparg, argc 表示位置参数的数量。堆栈的顶部包含位置参数,最右边的参数位于顶部。参数下面是要调用的函数可调用对象。
CALL_FUNCTION
首先从堆栈中弹出所有的参数和可调用对象。然后,它将在调用堆栈上分配一个新的框架,为函数调用填充局部变量,并在该框架内执行函数的字节码。一旦完成,框架将弹出调用堆栈,在前面的框架中,函数的返回值将被推到计算堆栈的顶部。如果没有前一个框架,它将被推到全局框架的评估堆栈的顶部。
在我们的例子中,我们只有一个位置参数,所以指令将是CALL_FUNCTION 1
。在那之后,指令
**POP_TOP**
将项目弹出到堆栈顶部。这是因为我们不再需要函数的返回值。图 2 显示了偏移量为 16 到 22 的所有字节码操作。f(x)
中的字节码指令用红色显示。
图 2
内置函数
在清单 2 的反汇编字节码的第 9 行,我们想要print(a)
。print
也是函数,不过是内置的 Python 函数。函数名是对其可调用对象的引用。因此,首先将它推送到堆栈上,然后再推它的参数。最后,它将被称为使用CALL_FUNCTION
。print
会返回None
,之后返回值会弹出堆栈。
Python 使用其内置函数来创建数据结构。例如,下面一行:
a = [1,2,3]
将被转换为:
1 0 LOAD_CONST 0 (1)
2 LOAD_CONST 1 (2)
4 LOAD_CONST 2 (3)
6 BUILD_LIST 3
8 STORE_NAME 0 (a)
最初,列表中的每个元素都被推送到堆栈上。然后是指令
**BUILD_LIST** *count*
使用堆栈中的计数项创建列表,并将结果列表对象推送到堆栈上。最后,栈上的对象将被弹出并存储在堆上,而a
将成为它的引用。
EXTENDED_ARG
如前所述,一些指令的参数太大,无法放入默认的一个字节,它们将被加上前缀指令EXTENDED_ARG
。这里有一个例子。假设我们想要打印 260 个*
字符。我们可以简单地写print('*' * 260)
。然而,我将写一些不寻常的东西来代替:
s= 'print(' + '"*",' * 260 + ')'
c = compile(s, "", "exec")
disassemble(c)
这里的s
包含一个print
函数,它有 260 个参数,每个参数都是一个*
字符。现在看看反汇编后的字节码:
1 0 LOAD_NAME 0 (print)
2 LOAD_CONST 0 ('*')
4 LOAD_CONST 0 ('*')
. .
. .
. . 518 LOAD_CONST 0 ('*')
520 LOAD_CONST 0 ('*')
522 EXTENDED_ARG 1
524 CALL_FUNCTION 260
526 POP_TOP
528 LOAD_CONST 1 (None)
530 RETURN_VALUE
这里print
首先被推到堆栈上。然后推送它的 260 个参数。然后CALL_FUNCTION
应该调用函数。但是它需要(目标函数的)参数个数作为它的 oparg。这里这个数字是 260,比一个字节所能容纳的最大数字还要大。记住 oparg 只有一个字节。所以CALL_FUNCTION
是以EXTENDED_ARG
为前缀的。实际的字节码是:
522 EXTENDED_ARG 1
524 CALL_FUNCTION 4
如前所述,EXTENDED_ARG 的 oparg 将左移 8 位或简单地乘以 256,并添加到下一个操作码的 oparg 中。所以CALL_FUNCTION
的 oparg 将被解释为256+4 = 260
(请注意,disassemble
函数显示的是这个被解释的 oparg,而不是字节码中实际的 oparg)。
条件语句和跳转
考虑下面的源代码,它有一个if-else
语句:
s='''a = 1
if a>=0:
b=a
else:
b=-a
'''
c=compile(s, "", "exec")
disassemble(c)
反汇编的字节码是:
1 0 LOAD_CONST 0 (1)
2 STORE_NAME 0 (a)
2 4 LOAD_NAME 0 (a)
6 LOAD_CONST 1 (0)
8 COMPARE_OP 5 (>=)
10 POP_JUMP_IF_FALSE 18
3 12 LOAD_NAME 0 (a)
14 STORE_NAME 1 (b)
16 JUMP_FORWARD 6 (to 24)
5 >> 18 LOAD_NAME 0 (a)
20 UNARY_NEGATIVE
22 STORE_NAME 1 (b)
>> 24 LOAD_CONST 2 (None)
26 RETURN_VALUE
我们这里有一些新的指示。在第 2 行中,a
引用的对象被推送到堆栈上,然后文字0
被推。指令
**COMPARE_OP** *oparg*
执行布尔运算。操作名称见cmp_op[oparg]
。cmp_op
的值存储在一个名为dis.cmp_op
的列表中。该指令首先弹出堆栈的顶部两个元素。我们把第一个叫做TOS1
,第二个叫做TOS2
。然后对它们执行由 oparg 选择的布尔运算(TOS2 cmp_op[oparg] TOS1)
,结果被推到堆栈的顶部。在本例中为TOS1=0
和TOS2=value of a
。另外, oparg 为5
和cmp_op[5]='≥'
。因此cmp_op
将测试a≥0
并将结果(真或假)存储在堆栈顶部。
指令
**POP_JUMP_IF_FALSE** *target*
执行条件跳转。首先,它弹出栈顶。如果栈顶的元素为 false,它将字节码计数器设置为目标。字节码计数器显示正在执行的当前字节码偏移量。所以它跳转到等于 target 的字节码偏移量,字节码从那里继续执行。字节码中的 offset 18 是一个跳转目标,所以在反汇编的字节码中在它前面有一个>>
。指令
**JUMP_FORWARD** *delta*
将字节码计数器增加增量。在前面的字节码中,这条指令的偏移量是 16,我们知道每条指令占用 2 个字节。所以当这条指令结束时,字节码计数器是16+2=18
。这里是delta=6
和18+6=24
,所以跳到偏移24
。偏移 24 是一个跳转目标,它也有一个>>
符号。
现在我们可以看到if-else
语句是如何被转换成字节码的。cmp_op
检查a≥0
是否。如果结果为假,POP_JUMP_IF_FALSE
跳转到偏移 18,这是else
块的开始。如果为真,将执行if
块,然后JUMP_FORWARD
跳转到偏移量 24,不执行else
块。
现在让我们看一个更复杂的布尔表达式。考虑以下源代码:
s='''a = 1
c = 3
if a>=0 and c==3:
b=a
else:
b=-a
'''
c=compile(s, "", "exec")
disassemble(c)
这里我们有一个逻辑and
。反汇编的字节码是:
1 0 LOAD_CONST 0 (1)
2 STORE_NAME 0 (a)
2 4 LOAD_CONST 1 (3)
6 STORE_NAME 1 (c)
3 8 LOAD_NAME 0 (a)
10 LOAD_CONST 2 (0)
12 COMPARE_OP 5 (>=)
14 POP_JUMP_IF_FALSE 30
16 LOAD_NAME 1 (c)
18 LOAD_CONST 1 (3)
20 COMPARE_OP 2 (==)
22 POP_JUMP_IF_FALSE 30
4 24 LOAD_NAME 0 (a)
26 STORE_NAME 2 (b)
28 JUMP_FORWARD 6 (to 36)
6 >> 30 LOAD_NAME 0 (a)
32 UNARY_NEGATIVE
34 STORE_NAME 2 (b)
>> 36 LOAD_CONST 3 (None)
38 RETURN_VALUE
在 Python 中and
是短路运算符。所以在对X and Y
求值的时候,只有X
为真,它才会对Y
求值。这在字节码中很容易看到。在第 3 行,首先对and
的左操作数求值。如果(a≥0)
为假,则不计算第二个操作数,并跳转到偏移量 30 执行else
块。但是,如果为真,第二个操作数(b==3)
也将被求值。
循环和块堆栈
如前所述,每个框架内都有一个评估堆栈。另外,在每一帧中,都有一个块堆栈。CPython 使用它来跟踪某些类型的控制结构,如循环、with
块和try/except
块。当 CPython 想要进入这些结构中的一个时,一个新的项目被推到块堆栈上,当 CPython 退出该结构时,该结构的项目被弹出块堆栈。使用块堆栈 CPython 知道哪个结构当前是活动的。所以当它到达一个break
或continue
语句时,它知道哪些结构应该受到影响。
让我们看看循环是如何在字节码中实现的。考虑下面的代码及其反汇编的字节码:
s='''for i in range(3):
print(i)
'''
c=compile(s, "", "exec")
disassemble(c)--------------------------------------------------------------------1 0 SETUP_LOOP 24 (to 26)
2 LOAD_NAME 0 (range)
4 LOAD_CONST 0 (3)
6 CALL_FUNCTION 1
8 GET_ITER
>> 10 FOR_ITER 12 (to 24)
12 STORE_NAME 1 (i)
2 14 LOAD_NAME 2 (print)
16 LOAD_NAME 1 (i)
18 CALL_FUNCTION 1
20 POP_TOP
22 JUMP_ABSOLUTE 10
>> 24 POP_BLOCK
>> 26 LOAD_CONST 1 (None)
28 RETURN_VALUE
指令
**SETUP_LOOP** *delta*
在循环开始前执行。此指令将一个新项目(也称为块)推送到块堆栈上。增量加到字节码计数器,确定循环后下一条指令的偏移量。这里SET_LOOP
的偏移量是0
,所以字节码计数器是0+2=2
。另外, delta 为24
,所以循环后下一条指令的偏移量为2+24=26
。这个偏移量存储在推送到块堆栈上的块中。此外,评估堆栈中的当前项目数存储在该块中。
之后,应该执行功能range(3)
。它的 oparg ( 3
)被推到函数名之前。结果是一个 可迭代 。Iterables 可以使用以下指令生成一个 迭代器 :
**GET_ITER**
它将 iterable 放在堆栈的顶部,并推送它的迭代器。说明:
**FOR_ITER** *delta*
假设栈顶有一个迭代器。它调用它的__next__()
方法。如果它产生一个新值,这个值被推到栈顶(迭代器之上)。在循环内部,栈顶存储在其后的i
中,执行print
函数。然后弹出栈顶,即迭代器的当前值。在那之后,指令
**JUMP_ABSOLUTE** *target*
将字节码计数器设置为目标并跳转到目标偏移量。所以它跳到偏移量 10,再次运行FOR_ITER
来获取迭代器的下一个值。如果迭代器指示没有其他元素可用,则弹出栈顶,并且字节码计数器增加增量。这里是*delta*=12
,所以循环结束后,跳转到偏移量 24。在偏移量 24 处,指令
**POP_BLOCK**
从块堆栈顶部移除当前块。循环后下一条指令的偏移量存储在块中(这里是 26)。所以解释器会跳到那个偏移量,并从那里继续执行。图 3 显示了偏移量为 0、10、24 和 26 的字节码操作(事实上,在图 1 和图 2 中,我们只显示了每一帧中的评估堆栈)。
图 3
但是如果我们在这个循环中添加一个break
语句会发生什么呢?考虑下面的源代码及其反汇编的字节码:
s='''for i in range(3):
break
print(i)
'''
c=compile(s, "", "exec")
disassemble(c)--------------------------------------------------------------------1 0 SETUP_LOOP 26 (to 28)
2 LOAD_NAME 0 (range)
4 LOAD_CONST 0 (3)
6 CALL_FUNCTION 1
8 GET_ITER
>> 10 FOR_ITER 14 (to 26)
12 STORE_NAME 1 (i)
2 14 BREAK_LOOP
3 16 LOAD_NAME 2 (print)
18 LOAD_NAME 1 (i)
20 CALL_FUNCTION 1
22 POP_TOP
24 JUMP_ABSOLUTE 10
>> 26 POP_BLOCK
>> 28 LOAD_CONST 1 (None)
30 RETURN_VALUE
我们只在前一个循环中添加了一个break
语句。该语句被转换为
**BREAK_LOOP**
该操作码移除评估堆栈上的那些额外项目,并从块堆栈的顶部弹出该块。您应该注意到,循环的其他指令仍在使用评估堆栈。因此,当循环中断时,属于它的项应该从计算堆栈中弹出。在这个例子中,迭代器对象仍然在栈顶。请记住,在开始循环之前,块堆栈中的块存储评估堆栈中存在的项数。
因此,通过知道这个数字,BREAK_LOOP
从评估堆栈中弹出那些额外的项目。然后跳转到存储在块堆栈的当前块中的偏移量(这里是 28)。这是循环后下一条指令的偏移量。因此循环中断,从那里继续执行。
创建代码对象
code 对象是一个类型为code
的对象,可以动态创建。模块types
可以帮助动态创建新类型,该模块中的类CodeType()
返回一个新的代码对象:
types.CodeType(co_argcount, co_kwonlyargcount,
co_nlocals, co_stacksize, co_flags,
co_code, co_consts, co_names,
co_varnames, co_filename, co_name,
co_firstlineno, co_lnotab, freevars=None,
cellvars=None)
参数构成了代码对象的所有属性。你已经熟悉了其中的一些参数(比如co_varnames
和co_firstlineno
)。freevars
和cellvars
是可选的,因为它们在闭包中使用,并且不是所有的函数都使用它们(关于它们的更多信息,请参考本文)。其他属性以下面的函数为例进行说明:
def f(a, b, *args, c, **kwargs):
d=1
def g():
return 1
g()
return 1
co_argcount
:如果代码对象是一个函数的对象,则是它所采用的参数个数(不包括仅关键字的参数,*
或**
args)。对于功能f
,它是2
。
co_kwonlyargcount
:如果代码对象是函数的代码对象,则为仅关键字参数的数量(不包括**
arg)。对于功能f
,它是1
。
co_nlocals
:局部变量的个数加上代码对象中定义的函数名(自变量也被认为是局部变量)。事实上,co_varnames
中的元素数就是('a', 'b', 'c', 'args', 'kwargs', 'd', 'g')
。所以是f
的7
。
co_stacksize
:显示此代码对象将推入计算堆栈的元素的最大数量。请记住,有些操作码需要将一些元素推送到计算堆栈上。这个属性显示了栈在字节码操作中可以增长到的最大大小。在这个例子中是2
。让我解释一下原因。如果你反汇编这个函数的字节码,你会得到:
2 0 LOAD_CONST 1 (1)
2 STORE_FAST 5 (d)
3 4 LOAD_CONST 2 (<code object g at 0x0000028A62AB1D20, file "<ipython-input-614-cb7dfbcc0072>", line 3>)
6 LOAD_CONST 3 ('f.<locals>.g')
8 MAKE_FUNCTION 0
10 STORE_FAST 6 (g)
5 12 LOAD_FAST 6 (g)
14 CALL_FUNCTION 0
16 POP_TOP
6 18 LOAD_CONST 1 (1)
20 RETURN_VALUE
在第 2 行,使用LOAD_CONST
将一个元素推到堆栈上,并将使用STORE_FAST
弹出。第 5 行和第 6 行类似地将一个元素推到堆栈上,稍后再弹出它。但是在第 3 行中,两个元素被推到堆栈上来定义内部函数g
:它的代码对象和它的名字。这是这个代码对象将被推到计算堆栈上的元素的最大数量,它决定了堆栈的大小。
co_flags
:整数,用位表示函数是否接受可变数量的参数,函数是否是生成器等。在我们的例子中,它的值是79
。79
的二进制值为0b1001111
。它使用一个小端系统,其中字节从左到右按重要性递增的方式写入。所以第一位是右边的第一个。你可以参考这个链接来了解这些位的含义。例如,右数第三位代表CO_VARARGS
标志。当它是1
时,意味着代码对象有一个可变的位置参数(*args
-like)。
co_filename
:字符串,指定函数所在的文件。在这种情况下,这是'<ipython-input-59–960ced5b1120>'
,因为我正在运行 Jupyter 笔记本中的脚本。
co_name
:定义该代码对象的名称。这里是函数的名字'f'
。
字节码注入
现在我们已经完全熟悉了代码对象,我们可以开始改变它的字节码了。需要注意的是,代码对象是不可变的。所以一旦创造了,我们就不能改变它。假设我们想要更改以下函数的字节码:
def f(x, y):
return x + yc = f.__code__
这里我们不能直接改变函数的代码对象的字节码。相反,我们需要创建一个新的代码对象,然后将它分配给这个函数。为此,我们需要更多的函数。disassemble
函数可以将字节码分解成一些对人类友好的指令。我们可以随心所欲地修改它们,但是我们需要将它们组装回字节码,以将其分配给新的代码对象。disassemble
的输出是一个格式化的字符串,易于阅读,但难以更改。所以我将添加一个新函数,它可以将字节码分解成一系列指令。它与disassemble
非常相似,但是,它的输出是一个列表。
我们可以在之前的函数上尝试一下:
disassembled_bytecode = disassemble_to_list(c)
现在disassembled_bytecode
等于:
[['LOAD_FAST', 'x'],
['LOAD_FAST', 'y'],
['BINARY_ADD'],
['RETURN_VALUE']]
我们现在可以很容易地更改该列表的说明。但是我们还需要将它组装回字节码:
函数get_oparg
类似于get_argvalue
的逆函数。它接受一个 argvalue,这是 oparg 的友好含义,并返回相应的 oparg。它需要 code 对象作为它的参数,因为 code 对象的属性如co_consts
是将 argvalue 转换成 oparg 所必需的。
函数assemble
获取一个代码对象和一个反汇编的字节码列表,并将其组装回字节码。它使用dis.opname
将 opname 转换成操作码。然后它调用get_oparg
将 argvalue 转换成 oparg。最后,它返回字节码列表的字节文字。我们现在可以使用这些新函数来改变前面函数f
的字节码。首先,我们改变disassembled_bytecode
中的一条指令:
disassembled_bytecode[2] = ['BINARY_MULTIPLY']
指令
**BINARY_MULTIPLY**
弹出堆栈顶部的两个元素,将它们相乘,并将结果推送到堆栈上。现在我们组装修改后的反汇编字节码:
new_co_code= assemble(disassembled_bytecode, c.co_consts,
c.co_varnames, c.co_names,
c.co_cellvars+c.co_freevars)
之后,我们创建一个新的代码对象:
import types
nc = types.CodeType(c.co_argcount, c.co_kwonlyargcount,
c.co_nlocals, c.co_stacksize, c.co_flags,
new_co_code, c.co_consts, c.co_names,
c.co_varnames, c.co_filename, c.co_name,
c.co_firstlineno, c.co_lnotab,
c.co_freevars, c.co_cellvars)
f.__code__ = nc
我们使用f
的所有属性来创建它,只替换新的字节码(new_co_code
)。然后我们将新的代码对象分配给f
。现在,如果我们再次运行f
,它不会将其参数加在一起。相反,它会将它们相乘:
f(2,5) # Output is 10 not 7
注意:types.CodeType
函数有两个可选参数freevars
和 cellvars
,但是使用时要小心。如前所述,代码对象的co_cellvars
和co_freevars
属性仅在代码对象属于具有自由变量或非局部变量的函数时使用。所以函数应该是一个闭包,或者闭包应该已经在函数内部定义了。例如,考虑以下函数:
def func(x):
def g(y):
return x + y
return g
现在如果检查它的代码对象:
c = func.__code__
c.co_cellvars # Output is: ('x',)
事实上,这个函数有一个非局部变量x
,因为这个变量是由其内部函数访问的。现在我们可以尝试使用相同的属性重新创建它的代码对象:
nc = types.CodeType(c.co_argcount, c.co_kwonlyargcount,
c.co_nlocals, c.co_stacksize, c.co_flags,
new_co_code, c.co_consts, c.co_names,
c.co_varnames, c.co_filename, c.co_name,
c.co_firstlineno, c.co_lnotab,
cellvars = c.co_cellvars,
freevars = c.co_freevars)
但是如果我们检查新代码对象的相同属性
nc.co_cellvars Output is: ()
结果是空的。所以types.CodeType
不能创建相同的代码对象。如果你试图将这个代码对象赋给一个函数并执行那个函数,你会得到一个错误(这个已经在 Python 3.7.4 上测试过了)。
代码优化
理解字节码指令可以帮助我们优化源代码。考虑以下源代码:
setup1='''import math
mult = 2
def f():
total = 0
i = 1
for i in range(1, 200):
total += mult * math.log(i)
return total
'''setup2='''import math
def f():
log = math.log
mult = 2
total = 0
for i in range(1, 200):
total += mult * log(i)
return total
'''
这里我们定义一个函数f()
来计算一个简单的数学表达式。它有两种不同的定义。在setup1
中,我们在f()
中使用全局变量mult
,并直接使用math
模块中的log()
函数。在setup2
中,mult
是f()
的局部变量。另外,math.log
首先存储在本地变量log
中。现在我们可以比较这些函数的性能:
t1 = timeit.timeit(stmt="f()", setup=setup1, number=100000)
t2 = timeit.timeit(stmt="f()", setup=setup2, number=100000)
print("t1=", t1)
print("t2=", t2)
--------------------------------------------------------------------
t1= 3.8076129000110086
t2= 3.2230119000014383
你可能会得到不同的t1
和t2
的数字,但底线是setup2
比setup1
快。现在让我们比较一下它们的字节码,看看为什么它更快。我们只看setup1
和setup2
的反汇编代码中的第 7 行。这是这一行的字节码:total += mult * log(i)
。
在setup1
中,我们有:
7 24 LOAD_FAST 0 (total)
26 LOAD_GLOBAL 1 (mult)
28 LOAD_GLOBAL 2 (math)
30 LOAD_METHOD 3 (log)
32 LOAD_FAST 1 (i)
34 CALL_METHOD 1
36 BINARY_MULTIPLY
38 INPLACE_ADD
40 STORE_FAST 0 (total)
42 JUMP_ABSOLUTE 20
>> 44 POP_BLOCK
但是在setup2
中我们得到:
7 30 LOAD_FAST 2 (total)
32 LOAD_FAST 1 (mult)
34 LOAD_FAST 0 (log)
36 LOAD_FAST 3 (i)
38 CALL_FUNCTION 1
40 BINARY_MULTIPLY
42 INPLACE_ADD
44 STORE_FAST 2 (total)
46 JUMP_ABSOLUTE 26
>> 48 POP_BLOCK
如您在setup1
中所见,mult
和math
均使用LOAG_GLOBAL
加载,但在setup2
中,mult
和log
使用LOAD_FAST
加载。因此两个LOAD_GLOBAL
指令被替换为LOAD_FAST
。事实是LOAD_FAST
顾名思义比LOAD_GLOBAL
快得多。我们提到的全局和局部变量的名称都存储在co_names
和co_varnames
中。但是在执行编译后的代码时,CPython 解释器是如何找到值的呢?
局部变量存储在每一帧的数组中(为了简单起见,前面的图中没有显示)。我们知道局部变量的名字存储在co_varnames
中。它们的值将以相同的顺序存储在该数组中。因此,当解释器看到类似于LOAD_FAST 1 (mult)
的指令时,它读取索引1
处的数组元素。
模块的全局和内置存储在一个字典中。我们知道他们的名字存储在co_names
中。因此,当解释器看到类似于LOAD_GLOBAL 1 (mult)
的指令时,它首先从co_names[1]
获得全局变量的名称。然后,它将在字典中查找这个名称以获得它的值。与简单的局部变量数组查找相比,这是一个非常慢的过程。因此,LOAD_FAST
比LOAD_GLOBAL
快,用LOAD_FAST
代替LOAD_GLOBAL
可以提高性能。这可以通过简单地将内置变量和全局变量存储到局部变量中或者直接改变字节码指令来实现。
示例:在 Python 中定义常数
这个例子说明了如何使用字节码注入来改变函数的行为。我们将编写一个装饰器,为 Python 添加一个 const 语句。在一些编程语言如 C、C++和 JavaScript 中,有一个 const 关键字。如果使用这个关键字将一个变量声明为 const,那么改变它的值是非法的,我们不能再在源代码中改变这个变量的值了。
Python 没有 const 语句,我也没有说 Python 中真的有必要有这样的关键字。此外,定义常量也可以在不使用字节码注入的情况下完成。所以这只是一个展示如何将字节码注入付诸实施的例子。首先,让我展示一下如何使用它。const 关键字是使用名为const
的函数装饰器提供的。一旦用const
修饰了一个函数,就可以用关键字const.
将函数内部的变量声明为常量(最后的.
是关键字的一部分)。这里有一个例子:
@const
def f(x):
const. A=5
return A*x
f(2) # Output is: 10
f
中的变量A
现在是一个常量。现在,如果您尝试在f
中重新分配这个变量,将会引发一个异常:
@const
def f(x):
const. A=5
A = A + 1
return A*x
--------------------------------------------------------------------# This raises an exception :
**ConstError**: 'A' is a constant and cannot be reassigned!
当变量被声明为 const 时。,应该赋给它的初始值,它将是那个函数的局部变量。
现在让我向你展示它是如何实现的。假设我这样定义一个函数(没有修饰):
def f(x):
const. A=5
A = A + 1
return A*x
它将被适当地编译。但是如果您尝试执行这个函数,您会得到一个错误:
f(2)
--------------------------------------------------------------------**NameError**: name 'const' is not defined
现在让我们来看看这个函数反汇编后的字节码:
2 0 LOAD_CONST 1 (5)
2 LOAD_GLOBAL 0 (const)
4 STORE_ATTR 1 (A)
3 6 LOAD_FAST 1 (A)
8 LOAD_CONST 2 (1)
10 BINARY_ADD
12 STORE_FAST 1 (A)
4 14 LOAD_FAST 1 (A)
16 LOAD_FAST 0 (x)
18 BINARY_MULTIPLY
20 RETURN_VALUE
当 Python 试图编译函数时,它将const
作为一个全局变量,因为它没有在函数中定义。变量A
被认为是全局变量A
的一个属性。事实上,const. A=1
和const.A=1
是一样的,因为 Python 忽略了点运算符和属性名之间的空格。当然,我们在源代码中确实没有名为A
的全局变量。但是 Python 不会在编译时检查它。只有在执行过程中,才会发现名称const
没有被定义。所以我们的源代码在编译时会被接受。但是我们需要在执行这个函数的代码对象之前改变它的字节码。我们首先需要创建一个函数来更改字节码:
这个函数接收由assemble_to_list
生成的字节码指令列表作为它的参数。它有两个名为constants
和indices
的列表,分别存储声明为 const 的变量的名称和它们第一次被赋值的偏移量。第一个循环搜索字节码指令列表,找到所有的['LOAD_GLOBAL', 'const']
指令。变量名应该在下一条指令中。在本例中,下一条指令是['STORE_ATTR', 'A']
,名称是A
。该指令的名称和偏移量存储在constants
和indices
中。现在我们需要去掉全局变量const
及其属性,并创建一个名为A
的局部变量。指令
**NOP**
是“什么都不做”的代码。当解释器到达NOP
时,它将忽略它。我们不能简单地从指令列表中删除操作码,因为删除一条指令会减少后面所有指令的偏移量。现在,如果字节码中有一些跳转,它们的目标偏移量也应该改变。所以简单地用NOP
替换不需要的指令要容易得多。现在我们用NOP
代替['LOAD_GLOBAL', 'const']
,然后用['STORE_FAST', 'A']
代替['STORE_ATTR', 'A']
。最终的字节码如下所示:
2 0 LOAD_CONST 1 (5)
2 NOP
4 STORE_FAST 1 (A)
3 6 LOAD_FAST 1 (A)
8 LOAD_CONST 2 (1)
10 BINARY_ADD
12 STORE_FAST 1 (A)
4 14 LOAD_FAST 1 (A)
16 LOAD_FAST 0 (x)
18 BINARY_MULTIPLY
20 RETURN_VALUE
现在第 2 行相当于源代码中的a=2
,执行这个字节码不会导致任何运行时错误。该循环还检查同一个变量是否被声明为 const 两次。因此,如果声明为 const 的变量已经存在于constants
列表中,它将引发一个自定义异常。现在唯一剩下的事情是确保常量变量没有被重新分配。
第二个循环再次搜索字节码指令列表,寻找常量变量的重新赋值。任何像['STORE_GLOBAL', 'A']
或['STORE_FAST', 'A']
这样的指令都意味着重分配在源代码中,所以它会引发一个定制的异常来警告用户。需要常量初始赋值的偏移量,以确保初始赋值不被视为再赋值。
如前所述,字节码应该在执行代码之前更改。所以需要在调用函数f
之前调用函数add_const
。为此,我们将它放在一个装饰器中。装饰函数const
接收目标函数f
作为它的参数。它将首先使用add_const
更改f
的字节码,然后用修改后的字节码创建一个新的代码对象。该代码对象将被分配给f
。
当我们创建新的代码对象时,需要修改它的一些属性。在原始函数中,const
是一个全局变量,A
是一个属性,所以它们都被添加到了co_names
元组中,它们应该从新代码对象的co_names
中移除。另外,当一个像A
这样的属性变成局部变量时,它的名字要加到co_varnames
元组中。属性co_nlocals
给出了局部变量(加上定义的函数)的数量,也应该被更新。其他属性保持不变。装饰器最终返回带有新代码对象的目标函数,现在目标函数已经准备好执行了。
理解 Python 的字节码可以让你熟悉 Python 编译器和虚拟机的底层实现。如果您知道源代码是如何转换成字节码的,那么您就可以在编写和优化代码方面做出更好的决策。字节码注入也是代码优化和元编程的有用工具。在本文中,我只涉及了少量的字节码指令。可以参考[dis](https://docs.python.org/3/library/dis.html)
模块的网页查看 Python 的字节码指令完整列表。我希望你喜欢阅读这篇文章。本文的所有代码清单都可以作为 Jupyter 笔记本下载,网址:https://github . com/Reza-bag heri/Understanding-Python-Bytecode
理解 Python 字典
Python 词典简介
在 python 中,字典是一种包含无序的键/值对集合的数据结构。在这篇文章中,我们将讨论如何在 python 中定义和使用字典。
我们开始吧!
python 中的字典是用花括号“{}”定义的。我们将从定义一个包含两个键的字典开始。第一个键称为“News ”,它将映射到新闻标题列表。第二个键将被称为“点击”,它将映射到一个包含文章被点击次数的列表:
news_dict = {"News":["Selling Toilet Paper During the Pandemic", "How to Reopen Gyms?", "Covid-19 Risk Based on Blood Type?"] , "Clicks":[100, 500, 10000] }
如果我们印刷字典,我们有:
print(news_dict)
现在我们已经定义了字典,让我们看看字典对象可用的方法:
print(dir(news_dict))
我们将讨论方法“clear()”、“copy()”、“get()”、“items()”、“pop()”、“update()”和“values()”。
第一种方法“clear()”非常简单。它只是清除字典:
news_dict.clear()
print("Cleared dictionary: ", news_dict)
接下来我们有“copy()”方法。此方法返回字典的浅层副本:
copy_dict = news_dict.copy()
这允许我们修改副本,同时保持原始字典不变:
copy_dict['Clicks'] = [100, 100, 100]
print(news_dict)
print(copy_dict)
“get()”方法将一个键作为输入,并返回该键的值:
print(news_dict.get("News"))
我们可以对“点击”键进行同样的操作:
print(news_dict.get("Clicks"))
我们还可以使用以下方法获得相同的结果:
print(news_dict['News'])
print(news_dict['Clicks'])
“items()”方法返回一组键/值对。我们可以在 for 循环中迭代键值对:
for key, value in news_dict.items():
print(key, value)
我们还可以使用 key 方法来获取键的名称:
print(new_dict.keys())
接下来,“pop()”方法允许我们删除键及其值:
news_dict.pop('Clicks')
print(news_dict)
此外,“update()”方法允许我们用新值覆盖字典中的键和值:
news_dict.update(News = "New York City Begins to Reopen", Clicks = 30000)
print(news_dict)
最后,“values()”方法返回字典值的新视图:
print(news_dict.values())
我就说到这里,但是您可以自己随意使用这些字典方法。
结论
总之,在这篇文章中,我们讨论了如何在 python 中定义和使用字典。我们讨论了如何清除值、访问键和值、更新字典以及复制字典。我希望你觉得这篇文章有用/有趣。这篇文章的代码可以在 GitHub 上找到。感谢您的阅读!
理解 Python 生成器
Python 中的生成器简介
生成器函数是 python 中的函数,它提供了一种执行迭代的简单方法。这很有用,因为处理列表要求我们将每个值存储在内存中,这对于大量输入来说是不实际的。此外,与生成器的简单实现相比,从头构建迭代器需要大量代码。例如,在构建迭代器类时,需要定义 dunder 方法“iter()”和“next()”,跟踪内部状态,并在没有其他值要返回时引发“StopIteration”。为了演示生成器函数的强大功能,我们将比较一个用于生成从 0 到 n 的正整数的函数的不同实现。
我们开始吧!
首先,让我们定义一个函数,它接受一个整数作为输入,并返回一个小于或等于输入的正整数列表:
def generate_list(input_value):
number, numbers = 0, []
while number <= input_value:
numbers.append(number)
number += 2
return numbers
现在,让我们用我们的函数定义一个从 0 到 n = 10 的正整数列表:
values = generate_list(10)
我们可以使用内置的 sum 方法对结果列表进行求和:
print("Sum of list: ", sum(values))
这里的问题是完整的列表是在内存中构建的。当您处理大量数据时,这就成了问题。我们可以使用生成器模式来修正这个问题。
接下来,让我们将生成器实现为迭代器对象。我们的类将需要 dunder 方法“init()”、“iter()”和“next()”。当没有额外的值要返回时,我们还需要引发“StopIteration”。让我们首先定义我们的“init()”方法:
class iterator_object(object):
def __init__(self, input_value):
self.input_value = input_value
self.numbers = 0
在我们的“init()”方法中,我们初始化了类属性。接下来,让我们定义 dunder 方法' iter()':
class iterator_object(object):
...
def __iter__(self):
return self
现在,让我们添加我们的“next()”方法:
class iterator_object(object):
...
def __next__(self):
return self.next()
最后,我们可以定义类方法“next()”:
class iterator_object(object):
...
def next(self):
if self.number <= self.input_value:
current, self.number = self.number, self.number + 2
return current
else:
raise StopIteration()
现在,我们可以定义一个输入值为 10 的类实例,并打印出结果迭代器对象的总和:
value = iterator_object(10)
print("Sum using an Iterator Object: ", sum(value))
这是我们所期望的结果。注意,为了将我们的生成器实现为迭代器对象,我们需要编写相当多的代码:
class iterator_object(object):
def __init__(self, input_value):
self.input_value = input_value
self.number = 0
def __iter__(self):
return self
def __next__(self):
return self.next()
def next(self):
if self.number <= self.input_value:
current, self.number = self.number, self.number + 2
return current
else:
raise StopIteration()
幸运的是,python 提供了‘yield’关键字,当使用时,它提供了构建迭代器的捷径。我们可以用 yield 来定义同样的函数:
def generator_function(input_value):
number = 0
while number <= input_value:
yield number
number += 2
让我们调用输入值为 10 的生成器,并打印结果的总和:
value = generator_function(10)
print("Sum using a Generator: ", sum(value))
正如我们所看到的,我们用比迭代器对象实现少得多的代码实现了相同的结果,同时保留了迭代器的优点。我就讲到这里,但是您可以自己随意摆弄代码。
结论
总之,在这篇文章中,我们讨论了 python 中的生成器函数。我们概述了同一个函数的三个实现,以展示生成器函数的强大功能。我们讨论了在列表上执行一些操作对于大量输入来说是如何成为问题的。我们通过构建一个生成器函数的迭代器对象实现解决了这个问题。这个解决方案需要大量代码。最后,我们讨论了使用 yield 语句来定义生成器函数,这为构建迭代器提供了一条捷径。我希望你对这篇文章感兴趣/有用。这篇文章中的代码可以在 GitHub 上找到。感谢您的阅读!
通过模拟了解 Python 多线程和多重处理
边做边学:简单的模拟有助于更好地理解复杂的想法,如多线程和多重处理。
Python 是一种优秀的通用语言,在各个领域都有应用。然而,有时候你只是希望它能进一步加速。提高速度的一个方法是用多线程或多重处理来并行工作。
有许多很好的资源可以解释这两者的概念。为了避免重复劳动,这里有一些我认为非常有用的方法。
- https://stack overflow . com/questions/18114285/线程与多处理模块的区别是什么
- https://medium . com/content square-engineering-blog/multi threading-vs-multi processing-in-python-ECE 023 ad 55 a
- https://www . geeks forgeeks . org/difference-between-multi threading-vs-multi processing-in-python/
- https://docs.python.org/3/library/concurrent.futures.html
在本文中,我想为那些想进一步探索概念并在自己的笔记本电脑上进行测试的人提供一个简单的模拟。所以我们开始吧!
模拟设置
概括地说,我通过以下步骤创建了一个模拟:
- 创建两个函数来模拟 IO 密集型和 CPU 密集型任务
- 使用“ concurrent.futures ”模块创建一个“中枢”函数,在不同的并行化设置下模拟执行一个任务 100 次
- 使用 Python 的“timeit”函数执行“hub”函数 5 次,以获得给定并行化设置下的*均花费时间
下面是包含设置细节的代码块。
性能比较
我在本地笔记本电脑上的测试结果在这里分享。请注意,在不同的硬件环境下,确切的模拟数字会有所不同,但原则应该始终适用。
我在下面的图表中显示了模拟结果:x 轴是工作人员的数量(例如线程号或进程号),y 轴是完成计算所花费的时间。四种不同的颜色标记了不同的设置:CPU 繁重任务的多处理(蓝色),CPU 繁重任务的多线程(红色),IO 繁重任务的多处理(黄色),IO 繁重任务的多线程(绿色)。
从图表中可以很容易地看出一些发现:
- “多线程”和“多处理”在 IO 繁重任务中同样有效。随着工作人员的增加,总任务花费的时间从大约 10 秒(1 个工作人员)减少到 1.3 秒(8 个工作人员),这意味着速度提高了大约 8 倍。
- “多线程”在 CPU 繁重的任务上表现不佳。红色条形图显示,无论使用多少“线程”,总花费时间始终在 10 秒左右。
- “多处理”在 CPU 繁重的任务上是有效的,然而,它在硬件限制下达到了一个*台。在我的例子中,当 worker #大于等于 5 时,它的最大加速仍然是 5X (~2 秒),小于他们的实际 worker #(例如 6、7、8)。这是因为我的笔记本电脑有 6 个内核,鉴于系统需要 1 个内核来维持其功能,其余 5 个内核可用于计算,因此最大速度提升是 5X。
看一下硬件概述,您会发现我的笔记本电脑的 6 核特性以及启用的“超线程技术”,因此线程数不限于处理器数 6。
我的笔记本电脑硬件概述
希望你喜欢这篇短文,并自己练习。更重要的是,您现在应该对 Python 中多线程和多处理的区别有了更好的理解。
边做边学既有趣又有效。尽情享受吧!
— — — — — — — — — — — — — —
如果你喜欢这篇文章,通过点赞、分享和评论来帮助传播。潘目前是 LinkedIn 的数据科学经理。可以看之前的文章,关注他上LinkedIn。
使用张量流量子了解量子 ML
通过探索 TensorFlow 量子示例开始您的量子机器学习之旅
上图 : Forest Stearns(谷歌人工智能量子艺术家常驻)展示了安装在低温恒温器上的 Sycamore 量子处理器。图片来源:使用可编程超导处理器的量子优势。底部 : TensorFlow 量子标志,来源
这篇文章是为量子机器学习的初学者准备的,这是当前经典机器学习思想的必然未来。本文的重点是介绍结合机器学习和量子计算有利于解决特定领域问题的领域。文章的结构是,首先描述与问题相关的量子计算概念,然后描述谷歌人工智能量子团队对问题的解决方案。此外,它遵循了谷歌人工智能量子团队最*与滑铁卢大学、Alphabet 的 X 和大众汽车合作发表的关于使用 Tensorflow Quantum 进行量子机器学习的工作。
在进入一些问题之前,有责任介绍一下 Tensorflow Quantum ,这是一个开源库,用于对量子数据进行计算,以及创建混合量子经典学习模型。与 Tensorflow 量子库相关的白皮书可访问了解更多技术细节。
量子计算所必需的量子电路被定义为一个模型,其中一个计算是一系列量子门。TensorFlow Quantum 使用 Cirq 设计这些量子电路的逻辑。在继续之前,我建议您浏览一下 TensorFlow Quantum 和 Cirq 的 API,或者将这些站点加入书签,以便在需要理解时参考。
量子数据的二元分类
为此,我们设计了一个混合量子经典神经网络,用于在量子数据源之间进行分类。量子数据从布洛赫球生成。在 Bloch 球体中,我们首先选择 XZ *面中的两条射线,然后选择随机分布在这些射线周围的点。因此,任务是分离由于选择上述光线而产生的两个斑点。下面是对应两个不同角度的图像:θ和ϕ.这两个角度都位于 XZ *面内,并相对于正 X 轴进行测量。在下图中,θ对应于蓝色光线与 XZ *面中的正 x 轴所成的角度,而ϕ以类似的方式对应于橙色光线。
θ= 3&φ= 0
θ= 2&φ= 7
总之,我们可以在布洛赫球中利用 X 旋转和 Z 旋转来制造任何量子态。
针对上述分类任务,设计了一种混合量子经典神经网络模型。典型的混合量子-经典神经网络的示意图如下所示。
“对用于 TensorFlow Quantum 中量子数据的混合量子经典判别模型的推理和训练的端到端管道中涉及的计算步骤的高级抽象概述。”— 图片鸣谢: TensorFlow Quantum 中的图 5:量子机器学习的软件框架 arXiv:2003.02989,2020 。
首先,生成一个量子数据,在我们的例子中是布洛赫球表面上的点,然后使用 Cirq 的量子门创建一个量子电路,以φ作为它们各自的参数,之后从 Tensorflow 量子库中选择一个层来对量子模型本身中的量子位执行计算。一旦量子计算完成,通过采样或测量从量子位获得的值的*均值来获得层输出的期望。这种测量行为去除了量子位的量子本质,产生了一个经典变量。这个经典变量然后服从经典神经网络模型,最终输出预测概率。在下面的图中,我们给出了混合神经网络的训练损失图,该混合神经网络被训练来对布洛赫球表面上两条射线周围的点进行分类。在正在进行的训练期间分类误差的下降和在训练结束时 0.00085 的最终损失表明所提出的混合神经网络在分类量子数据方面是成功的。
混合量子经典神经网络相对于用于对θ= 2&φ= 7的布洛赫球表面上的点进行分类的训练时期的分类交叉熵损失
量子图递归神经网络
在本节中,我们旨在使用 QGRNN 学习给定伊辛模型的目标哈密顿量的动力学。这里使用的横场伊辛模型是热力学和磁学中使用的经典伊辛模型的量子再现。伊辛模型的目标哈密顿量如下:
伊辛量子模型的目标哈密顿量。图片鸣谢:TensorFlow Quantum:量子机器学习的软件框架 arXiv:2003.02989,2020 。
量子计算中的伊辛模型基本上是一个格子-格子结构,量子位的自旋用 Z 表示,如上图所示。 J 是耦合系数,表示量子位的自旋对之间的相互作用强度,而 B 是量子位拥有特定自旋的偏置系数。添加术语 X 是为了传达任何外部场对特定量子位相对于 Ising 模型主要的最*邻自旋对相互作用的影响。除了伊辛模型的可变耦合强度之外,自旋之间的相互作用有利于模拟几个机器学习模型,如 Hopfield networks 和Boltzmann machines(Schuld&Petruccione(2018))。此外,伊辛模型密切模拟计算问题子类的基本数学,称为二次无约束二元优化(曲波)问题。
在介绍了伊辛模型及其哈密顿量之后,我们在下面介绍实现上述同化伊辛模型动力学的目标的步骤。
用 Ising 模型上的变分量子本征解算器制备量子数据
具有棕色到蓝绿色色图的图中的节点描绘了具有特定自旋的量子位的偏置系数 B,而具有红色到蓝色色图的图中的边示出了两个最*的相邻量子位之间的耦合系数 J。彩色条显示了 B 和 J 系数对目标 Ising 模型动力学的影响。
现在,我们将构建一个变分量子本征解算器 (VQE),寻找一个靠*基态的低能态,使其可以作为伊辛模型& QGRNN 的初态。变分法提供了一种找到*似基态能量状态的方法。下面是通过训练 VQE 获得的低能量状态以及同样的训练损失图。
训练 VQE 建议的低能状态及其训练损失图
构造伊辛模型和 QGRNN
我们需要取哈密顿量的指数来构造伊辛模型。然而,一般来说,两个不可交换的可观测量的幂不等于它们的和的幂。为了解决这个问题,我们选择铃木-特罗特展开式,因为它可以得出易于计算的*似解。
现在我们需要初始化一个 QGRNN 模型。由于 QGRNN 不知道目标哈密顿量,我们在 QGRNN 内部初始化了一个随机图。
在 QGRNN 内部初始化随机图
使用互换测试构建保真度
在这一步,我们已经从伊辛模型的真实哈密顿量生成了量子数据,以及从 QGRNN 预测的量子数据。众所周知,对量子比特的测量导致对其量子信息的拆解;因此,我们用交换测试实现保真度,用于比较真实和预测的量子态。该交换测试用于计算用于训练 QGRNN 的*均不忠损失函数。
QGRNN 的培训和结果
从上面的练习可以得出结论,在这种情况下,QGRNN 从其量子数据中学习给定目标哈密顿量的时间演化动力学,类似于 Ising 模型。
变分量子热化器
在本节中,我们将利用 TensorFlow Quantum 探索量子计算和经典的基于能量的模型的结合。在这里,我们将研究 2D 海森堡模型,并应用变分量子热化器(VQT)来生成模型的*似热态。VQT 在随后的白皮书中被介绍。
目标 2D 海森堡模型的 密度矩阵 。类似 TensorFlow Quantum 中图 24 的图像:量子机器学习的软件框架 arXiv:2003.02989,2020 。
为了跟踪我们基于能量的学习模型的性能,我们使用了一个叫做保真度的易处理的量。
基于能量的学习模型
基于物理学和指数族的概念,我们将使用基于能量的玻尔兹曼机来学习 2D·海登伯格模型的动力学。玻尔兹曼机经典模型可以通过用泡利 z 算符替换每个比特,并将自旋映射到量子比特,即 1 to|0⟩和-1 |1⟩.,而快速转换成量子力学伊辛模型
当一个 ansatz 关于量子位之间的零相关性被建立时,玻尔兹曼机器被简化为具有简化特征的量子位集合上的独立伯努利分布的乘积。因此,我们将首先在 VQT 实施伯努利循证医学。我们使用 VQT 白皮书中提到的经典 VQT 损失函数。
VQT 通过将伯努利 EBM 转换成伊辛模型得出结果
历元 1 估计密度矩阵,损失:3.1398983,最佳状态保真度:0.0356728676646754
历元 20 估计密度矩阵,损失:-1.3389935,最佳状态保真度:0.16234265428802852
历元 40 估计密度矩阵,损失:-10.262356,最佳状态保真度:0.59447319578201
历元 60 估计密度矩阵,损失:-15.053259,最佳状态保真度:0.8891700468979
历元 80 估计密度矩阵,损失:-15.950613,最佳状态保真度:0.92273758099982
Epoch 100 估计密度矩阵,损耗:-16.35294,最佳状态保真度:0.946553737877106
VQT (Bernoulli EBM)培训损失
VQT (Bernoulli EBM)保真度与最优状态
VQT 通过将玻尔兹曼机 EBM 转换成伊辛模型得出结果
历元 1 估计密度矩阵,损失:-2.9771433,最佳状态保真度:0.053690879554551
历元 20 估计密度矩阵,损失:-13.478342,最佳状态保真度:0.664609698510499
历元 40 估计密度矩阵,损失:-16.392994,最佳状态保真度:0.9318277209146
历元 60 估计密度矩阵,损失:-17.340147,最佳状态保真度:0.9984435197535
历元 80 估计密度矩阵,损失:-17.391596,最佳状态保真度:0.9940862273719
历元 100 估计密度矩阵,损耗:-17.400259,最佳状态保真度:0.999360266561525
VQT (Boltzmann machine EBM)培训流失
VQT(玻尔兹曼机 EBM)保真度与最优状态
毫无疑问。从结果可以推断,基于能量的方法在学习 2D 海森堡模型的哈密顿量的动力学方面是成功的。参考受限玻尔兹曼机器 (RBM)对 MNIST 的分类,进一步阅读基于能源的模型。
在概述了三个例子以及对它们的量子计算和机器学习概念的理解之后,不要不知所措是非常必要的。我向初学者介绍量子机器学习的目标,除了将量子计算应用于各种特定领域的问题,只有作为读者的你坚持不懈并不断更新关于量子机器学习的知识,才会成功。下面我提出一个有趣的方法,用量子机器学习来解决组合优化问题。
进一步阅读
量子*似优化算法 (QAOA)
PennyLane : 一个跨*台的 Python 库,用于量子机器学习、自动微分和混合量子经典计算的优化。
Strawberry Fields : 一个用于设计、优化和利用光子量子计算机的全栈 Python 库。
理解破解验证码的人工智能模型
皇家护理学院
对递归皮层网络结构的深入探究
计算机视觉是人工智能[1]研究最多的主题之一,然而目前应对其挑战的解决方案 ConvNets 最*因容易被愚弄而受到批评。举几个例子,这些网络可能会以很高的可信度输出错误的类别预测:缺少 ConvNets 依赖的统计线索的自然发生的图像[2],它们正确分类但改变了单个像素的图像[3],或者在场景中添加了不应改变预测类别的物理对象的图像[4]。因此,如果我们想建造真正智能的机器,至少应该努力探索新的想法。
其中一个相当新的想法是 Vicarious 的递归皮层网络(RCNs ),它从神经科学中获得灵感。这种模式声称以极高的数据效率破解了基于文本的验证码[5],并吸引了一些围绕it的评论和争论。然而,我还没有看到一篇文章彻底解释这个有趣的新模型。因此,我决定写两篇文章,每一篇都解释这个模型的一个特定方面。在本文中,我们将讨论它的结构以及它如何生成图像,如 RCN 主要论文的补充材料所述[5]。
本文假设我们对 ConvNets 有基本的了解,因为我们对它进行了大量的类比。
为了让你的大脑为 RCN 的细节做好准备,你需要理解 RCN 是基于将形状(物体的草图)与外观(其纹理)分开的神经启发的想法,并且它是一个生成模型,而不是一个鉴别模型,所以我们可以像 GANs 一样从中生成/采样图像。此外,它是一个并行的层次结构,就像 ConvNets 一样,它首先在第一层生成目标对象的形状,然后在最后一层添加外观。然而,与 ConvNets 相反,它依赖于来自图形模型的大量文献,而不是依赖于加权和以及梯度下降。现在,让我们深入研究 RCN 结构的细节。
要素图层
RCN 中的第一类图层称为要素图层。我们将逐步理解该模型,因此,现在让我们假设该模型的整个层次结构由这种类型的层组成,这种类型的层只是堆叠在彼此的顶部,从顶层的抽象概念到更详细的概念,随着我们越来越靠*底层,如图 1 所示。这种类型的图层由覆盖 2D 空间的多个节点组成,就像 ConvNets 中的要素地图一样。
图 1:多个要素层堆叠在一起,节点跨越 2D 空间。从第 4 层移动到第 1 层是从抽象概念移动到更详细的底层概念。
每个节点由多个通道组成,每个通道代表一个独特的功能。通道是二进制变量,可为真或假,表示对应于该通道的特征是否存在于最终生成的影像中的节点(x,y)坐标处。在任何特定层,所有节点都有相同的信道列表。
让我们以一个中间层为例,让我们对其通道和上层提出一些假设,以便于解释。该层的通道列表将表示一条双曲线、一个圆和一条抛物线。在生成图像的某一次运行中,上面各层的计算需要在(x,y)坐标(1,1)处画一个圆。因此,( 1,1)处的节点将对应于特征“圆形”的通道设置为真。这将直接影响它下面的层中的一些节点;它将触发与(1,1)的邻域中的圆相关联的较低级特征被设置为真。仅作为示例,那些较低级别的特征可以是具有不同取向的四个弯曲段。当这些较低的功能被触发时,它们还会触发甚至更低的层中的通道,直到我们到达生成图像的最终层。我们可以想象这种触发操作,如图 2 所示。
你可能想知道圆怎么知道它需要 4 个曲线段来表示自己?RCN 如何知道它需要一个通道来表示一个圆?通道及其与其他层的连线将在 RCNs 的学习阶段建立。
图 2:要素层中的信息流。特征节点是包含表示通道的圆盘的胶囊。为了简明起见,一些上层和下层被表示为长方体,但是它们由作为中间层的特征节点组成。触发的连接和通道正在发光。请注意,顶部中间层由 3 个通道组成,第二层由 4 个通道组成。
您可能想要反对模型采用的高度僵化和确定性的生成方法;对于人类来说,如图 3 所示,圆的曲率的微小扰动仍然被认为是圆。
图 3:通过扰动图 2 中的四个曲线段,圆的多种变化。
在我们的层中,将这些变化中的每一个单独视为一个新的通道是很困难的。类似地,当我们后来将 rcn 用于分类而不是生成时,将这些变体归入同一概念将大大有助于推广到新的变体。但是,我们如何将 rcn 改变为具有这种能力呢?
池层
为了实现这一点,引入了一种新的层,即池层。它位于任意两个要素图层之间,充当它们之间的中介。它也将由通道组成,但它们将是整数值而不是二进制。
为了说明,让我们回到我们的圆的例子。圆形要素不是从其下方的要素图层请求 4 条固定坐标的曲线段,而是从池图层请求这些曲线段。然后,池层中的每个激活的通道将在其邻域中选择低于它的级别的节点,以允许其特征的小扰动。因此,如果我们将邻域设置为池节点正下方的 9 个节点,那么池通道无论何时被激活,都将对这 9 个节点中的一个进行统一采样并触发它,并且它选择的节点的索引将是该池通道的状态,一个整数。您可以在图 4 中看到多次运行,其中每次运行对一组不同的低级节点进行采样,从而允许圆的不同变化。
图 4:池化层的操作。这个 GIF 中的每一帧都是不同的运行。汇集节点是立方体。在此 GIF 中,池节点有 4 个通道,相当于其下要素图层的 4 个通道。为了清楚起见,上面的&下面的层已经被完全移除。
尽管我们的模型中需要可变性,但是如果这种可变性得到更多的约束和协调,那就更好了。在前面的两个图中,一些圆太怪异了,不能认为是圆,因为它们的曲线段是不连续的,如图 5 所示,我们想拒绝它们的生成。因此,如果我们可以添加一种机制来汇集通道,以协调它们对特征节点的采样,从而专注于更连续的可变性,我们的模型将更合适。
图 5:一个圆的多种变化,我们不希望这些变化被打上一个红叉。
RCN 的作者在池层中引入了横向连接来实现这一点。本质上,池通道将与它们附*的其他池通道连接,这些连接不允许一些状态对同时在两个通道中出现。简单的限制这两个通道的采样空间是不会允许的。例如,在圆的变体中,这些连接将不允许两个相邻的线段彼此远离。这种机制如图 6 所示。同样,这些联系将在学习阶段建立。值得注意的是,当代的普通人工神经网络在它们的层中没有任何类型的横向连接,尽管生物神经网络有横向连接,并且被认为在视觉皮层的轮廓整合中起作用6。
图 6: GIF 展示了 rcn 在横向连接下的操作。当两端的状态不能同时出现时,横向连接会发出红光。应该注意的是,一个工作的 RCN 首先实际上不会产生不允许的状态;我们只是生成它们来理解哪些变化被丢弃了。为了清楚起见,上面的&下面的几层已经完全去掉了。
到目前为止,我们一直在谈论 rcn 的中间层;剩下的是最顶层和最后一层,与生成的图像的像素接口。最顶层只是一个普通的要素图层,其中每个结点的通道都是我们的标注数据集的类。在生成时,我们只需选择位置和我们想要生成的类,转到具有所选位置的节点,并告诉它激活我们选择的类的通道。这将触发它下面的池层中的一些通道,然后是下面的要素层,以此类推,直到我们到达最后一个要素层。根据您对 ConvNets 的了解,您可能会认为最顶层只有一个节点,但这里的情况并非如此,实际上这是 rcn 的优点之一,但讨论这一点超出了本文的范围。
最后一个要素图层将是唯一的。还记得我说过 RCNs 把外形和外观分开吗?嗯,这一层将负责输出要生成的对象的形状。因此,这一层应该具有非常低级的特征,任何形状的最基本的构建块,这将允许我们生成任何我们想要的形状。以不同角度旋转的微小边缘是很好的候选,这实际上是作者所利用的。
作者选择用最后一级的特征来表示一个 3x3 的窗口,该窗口的边缘有一定的旋转,他们称之为面片描述符。他们选择的旋转数是 16。此外,为了能够在以后添加外观,每次旋转将有两个方向,以便能够区分背景是位于边缘的左侧还是右侧(如果是外部边缘),以及一个额外的方向用于内部边缘(即对象内部)。在图 7 中,您可以看到最后一个要素层的一个节点,在图 8 中,您可以看到这些面片描述符是如何形成某种形状的。
图 7:最后一个要素层的节点。有 48 个硬编码(未学习)通道,对应于 16 个边缘旋转* 3 个方向。所示的面片描述符是 45°边的所有可能方向。“IN”表示内部区域,而“OUT”表示外部区域。
图 8:使用不同的补丁描述符形成字母“I”形状的例子。
现在,当我们到达最后一个要素图层时,我们有了一个指定对象边缘以及边缘周围区域是内部还是外部的蓝图。剩下的工作是添加外观,将图像中的每个剩余区域指示为入区或出区,然后为所有区域添加颜色。这将通过使用条件随机场来完成。不涉及数学术语,接下来要发生的是,我们将为最终图像中的每个像素分配一个颜色和状态(入或出)的概率分布。该分布将反映从边缘图提供的信息。例如,如果有两个相邻像素,其中一个在内,另一个在外,则它们具有不同颜色的概率会大大增加。如果两个相邻像素位于内边缘的相对侧,则它们具有不同颜色的概率会增加。如果像素在内部,它们之间没有内边缘,那么它们具有相同颜色的概率增加,但是允许外部像素彼此偏离,等等。为了产生最终的图像,你只需要从我们刚刚建立的联合概率分布中取样。为了使生成的图像更丰富,我们实际上可以用纹理代替颜色。我们不打算进一步讨论这一层,因为 rcn 可以在没有外观的情况下执行分类。
本文到此为止。如果你想了解更多关于 RCN 的知识,你可以查看它的论文[5]和附带的补充材料文档,或者你可以阅读我的其余文章,其中谈到了推理、学习、在不同数据集上应用 RCN 的结果。
参考
[1] R.Perrault,Y. Shoham,E. Brynjolfsson 等,人工智能指数 2019 年度报告(2019),以人为中心的人工智能研究所-斯坦福大学。
[2] D. Hendrycks,K. Zhao,S. Basart 等著《自然对抗性实例》(2019),arXiv:1907.07174。
[3] J.Su,D. Vasconcellos Vargas,S. Kouichi,愚弄深度神经网络的一个像素攻击(2017),arXiv:1710.08864。
[4] M. Sharif,S. Bhagavatula,L. Bauer,带有目标的对抗性例子的一般框架(2017),arXiv:1801.00349。
[5] D. George,W. Lehrach,K. Kansky 等,一种以高数据效率训练并打破基于文本的验证码的生成视觉模型(2017),科学杂志(第 358 卷—第 6368 期)。
[6] H. Liang,X. Gong,M. Chen,等,初级视皮层中反馈与侧连接的相互作用(2017),美国国家科学院院刊。
理解递归
安德里亚·费拉里奥在 Unsplash 上拍摄的照片
了解如何利用这一基本但令人困惑的编程概念
每次我有一段时间不用它的时候,我发现自己都要重新学习这个概念。我大体上记得它是什么,以及我们为什么需要它,但是在长期搁置之后,递归编程总是一场斗争。
现在它又一次让我记忆犹新,让我来记录一下我们为什么以及如何使用递归。如果你想下载这篇文章中用到的代码,你可以在我的 GitHub 上找到它。
学过递归的人可能都记得计算斐波那契数的经典例子:
# Function that returns the nth number in the Fibonacci sequence
# For example, if n=3 the 3rd number in the sequence is returneddef fib(n):
if n==0:
return 1
elif n==1:
return 1
else:
return fib(n-1) + fib(n-2)
在我们进入一个更实际的例子之前,让我们用它来理解递归是如何工作的。
递归是函数调用自身。我把递归比作 do-while 循环,遗憾的是这在 Python 中并不存在。与 for 循环不同,do-while 循环会在满足终止条件之前一直运行,而 for 循环会预先指定要运行的次数。do-while 循环的美妙之处在于它会一直运行到任务完成——您不需要提前知道需要运行多少次。
递归很棒,因为它是 do-while 循环的有价值的替代品。它还会一直运行,直到任务完成。但是递归不是一个循环,而是像电影《盗梦空间》。在《盗梦空间》中,莱昂纳多·迪卡普里奥和他那群快乐的盗梦贼冒险进入了一个人的梦的更复杂的层面——每一层都是梦中的梦。只要他们能找到回去的路,他们能走多深是没有限制的。
每次我们的递归函数调用自己时,它实际上是更深入一层(当前梦中的另一个梦)。但就像《盗梦空间》中的角色一样,深入是达到目的的一种手段。让我们看看我们的斐波纳契代码,特别是最后一行:
return **fib(n-1) + fib(n-2)**
这里,该函数调用自身两次,并将结果相加,但是,尽管最初的函数是用 n 作为输入调用的,但这两次调用是用修改后的输入进行的: n-1 和 n-2 。所以基本上梦里的每一个梦都从最初的输入 n (通过从中减去)逐渐减少,直到它到达终点。让我们展开 fib(5) 来看看这是如何工作的:
fib(5)= fib(4) + fib(3)= fib(3) + fib(2) + fib(2) + fib(1)= fib(2) + fib(1) + fib(1) + fib(0) + fib(1) + fib(0) + 1= fib(1) + fib(0) + 1 + 1 + 1 + 1 + 1 + 1= 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1= **8**
请注意,该函数一直持续到到达 fib(1) 或 fib(0) 为止,这些都是终止情况。比如 fib(3) 变成 fib(2) + fib(1) ,就变成 fib(1) + fib(0) + fib(1) 。这种自我传播,直到达到一个终止的情况,这就是函数如何越来越深(进入梦中的梦),直到它到达一个出口点。
我们可以通过对照我们的函数来验证 8 是否是正确答案:
In: fib(5)Out: 8In:# For fun, print first 10 values of the Fibonacci Sequence
for i in range(10):
print(fib(i))Out:1
1
2
3
5
8
13
21
34
55
结束
让我们花点时间来谈谈终止案例。这些都很关键,因为如果没有这些情况,函数就会无限期地调用自己,陷入死循环。所以编写任何递归函数的第一步是指定它可以退出(终止)的条件。
在斐波那契数列的例子中,我们知道序列中的前两个值是 1 和 1。即 fib(0) = 1 和 fib(1) = 1 。所以任何时候我们得到一个 0 或 1 的输入,我们就可以返回一个 1,然后就结束了。如果我们的输入大于 1,那么我们需要像上面那样,通过一系列递归函数调用,朝着终止情况(0 或 1)前进。
更实际的用例
打印斐波那契数列是一个巧妙的聚会把戏,但是现在让我们来看一个更实际的用例——遍历树。树木的问题在于我们不知道它们有多深。你可以有一个这样的:
root
| \
| \
b1 b2
或者像这样稍微深一点的:
root ____________
| \ \
| \ \
b1 ____ b2 b3
| \ \ | \
l1 l2 l3 l1 l2
让我们编写一个足够通用的函数,它可以处理这两种树(甚至更复杂的树)。首先,让我们使用 Python 字典构建上面描述的两棵树:
my_dict0 = {'root': {'b1': [1],
'b2': [2]}
}my_dict1 = {'root': {'b1': {'leaf1': [1,2,3],
'leaf2': [4],
'leaf3': [5,6]
},
'b2': {'leaf1': [7,8],
'leaf2': [9,10,11]
},
'b3': 12
}
}
现在让我们考虑如何通过递归来遍历这些树。首先让我们定义终止的情况。由于我们构造字典的方式,终止的情况非常简单:如果我们看到一个列表(或单个值),我们可以只追加内容并继续前进。注意,我将结果存储在一个名为 result 的扩展列表中( result 是函数运行完成后返回的结果)。另外,请注意,虽然我称之为终止案例,但我们还没有返回任何内容。相反,我们只是将结束节点的值附加到结果中,并继续我们的 for 循环,因为我们知道我们已经完成了树的当前子部分。
def parse_dict(my_dict, result):
for key, val in my_dict.items():
**if type(val) == list:
for i in val:
result.append(i)**
如果我们看不到名单呢?然后,我们需要通过递归(Inception)更深入地研究。我们这样做的方法是通过再次调用我们的函数,但是在修改的输入上(如果我们每次调用都不改变我们的输入,我们将陷入无限循环)。因此,我们现在只给函数当前正在检查的树的一部分( my_dict ),而不是给函数原始输入。例如,一个对 parse_dict 的递归调用可能只处理 b1 分支(它有 3 个叶子: leaf1 、 leaf2 和 leaf3 ),而另一个调用可能只处理 b3 分支(它只有值 12 )。
def parse_dict(my_dict, result):
for key, val in my_dict.items():
if type(val) == list:
for i in val:
result.append(i)
**elif type(val) == dict:
parse_dict(val, result)**
最后要添加的是一个终止案例,它检查分支何时没有叶子或列表,并且只包含一个数值(比如 b3 )。在这种情况下,我们可以直接将 val 中的值追加到结果中:
def parse_dict(my_dict, result):
for key, val in my_dict.items():
if type(val) == list:
for i in val:
result.append(i)
elif type(val) == dict:
parse_dict(val, result)
**else:
result.append(val)**
return result
如果我们在简单的树上运行函数, my_dict0 ,我们得到:
In: parse_dict(my_dict0, [])Out: [1, 2]
如果我们在 my_dict1 上运行它,我们会得到:
In: parse_dict(my_dict1, [])Out: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
酷,我们已经遍历了我们的树,挖掘了存储在每个端节点中的值。不管树是简单的还是更复杂、更深入的,我们的函数都能工作。
需要记住的一点是,如果你有一棵非常复杂的树,有很多层和很多分支,那么递归遍历它可能需要很长时间。如果您正在处理非常复杂的树结构,那么投入一些时间和思想来构建一个更加优化的解决方案是有意义的。
额外收获:获得每个值的路径
我们可能还想知道到达每个最终值所经过的路径(或者到达特定最终值的路径)。下面的函数产生一个包含每个结束值的路径的列表(与 parse_dict 的输出共享相同的顺序)。它的工作方式与 parse_dict 类似,所以我就不赘述了(但是代码中有一些注释)。
def get_path(my_dict, result, path):
for key, val in my_dict.items():
# If it's a dict, go one level deeper and update path
# to include the current key (node name)
if type(val) == dict:
get_path(val, result, path+[key])
# If it's not a dict, then append the key and don't
# go down another level (since no need to)
elif type(val) == list:
for i in val:
result.append(path+[key])
else:
result.append(path+[key])
return result
下面是一些输出示例。注意,我们需要提供两个输入——返回的结果和路径,后者是附加到结果的中间输出(但在函数结束时不返回)。
In: get_path(my_dict1, [], [])Out: [['root', 'b1', 'leaf1'],
['root', 'b1', 'leaf1'],
['root', 'b1', 'leaf1'],
['root', 'b1', 'leaf2'],
['root', 'b1', 'leaf3'],
['root', 'b1', 'leaf3'],
['root', 'b2', 'leaf1'],
['root', 'b2', 'leaf1'],
['root', 'b2', 'leaf2'],
['root', 'b2', 'leaf2'],
['root', 'b2', 'leaf2'],
['root', 'b3']]
希望这对你有帮助。干杯!
更多数据科学相关帖子由我:
了解感兴趣区域—(投资回报池)
理解 ML
快速简单地解释什么是投资回报池及其工作原理?为什么我们在快速 R-CNN 中使用它?
我们将讨论Fast R-CNN论文中描述的原始 RoI 合并(上图中的浅蓝色矩形)。这个过程有第二个和第三个版本,叫做ROI align和ROI warp。
如果你对那两个感兴趣请查看 这篇文章
什么是 RoI?
RoI (感兴趣区域)是从原始图像中提出的区域。我们不打算描述如何提取这些区域,因为有多种方法可以做到这一点。我们现在唯一应该知道的是,有多个这样的区域,所有这些区域都应该在最后进行测试。
R-CNN 的工作速度有多快?
特征抽出
快速 R-CNN 不同于基本 R-CNN 网络。它只有一个卷积特征提取(在我们的例子中,我们将使用 VGG16)。
VGG16 特征提取输出尺寸
我们的模型接受大小为 512x512x3 (宽 x 高 x RGB)的图像输入,VGG16 将它映射到一个 16x16x512 特征图中。您可以使用不同的输入大小(通常较小,Keras 中 VGG16 的默认输入大小是 224x224)。
如果你查看输出矩阵,你应该注意到它的宽度和高度正好是输入图像的 32 倍(512/32 = 16)。这一点很重要,因为所有 ROI 都必须按比例缩小。****
样本 ROI
这里我们有 4 个不同的 ROI。在实际的快速 R-CNN 中,你可能有成千上万个这样的图片,但是把它们都打印出来会使图片不可读。
感兴趣区域,图片来源:斯蒂芬妮·布斯https://www.flickr.com/photos/bunny/
重要的是要记住 RoI 不是一个边界框。它可能看起来像一个,但它只是一个进一步处理的建议。许多人认为这是因为大部分的论文和博客文章都是在创造提案而不是实物。这样更方便,我在我的图片上也是这样做的。这是一个不同的提案领域的例子,也将由快速 R-CNN 检查(绿框)。
没有意义的感兴趣区域:)
有一些方法可以限制 ROI 的数量,也许我会在以后写出来。
如何从特征图中获取 ROI?
现在,当我们知道什么是 RoI 时,我们必须能够将它们映射到 VGG16 的输出特征图上。
将我们的 ROI 映射到 VGG16 的输出上
每个 RoI 都有其原始坐标和大小。从现在开始,我们将只关注其中之一:
我们的投资回报目标
其原始尺寸为 145x200 ,左上角设置在 (192,296) 。正如你可能知道的,我们不能用 32 (比例因子)来划分这些数字。
- 宽度:200/32 = 6.25
- 身高:145/32 = ~4.53
- x: 296/32 = 9.25
- y: 192/32 = 6
只有最后一个数字(左上角的 Y 坐标)才有意义。这是因为我们现在正在处理一个 16x16 的网格,我们只关心整数(更准确地说:自然数)。
特征图上坐标的量化
量化 是将输入从一大组数值(如 实数 )约束到一个离散集合(如 整数 ) 的过程
如果我们将原始 RoI 放在特征图上,它看起来会像这样:
特征图上的原始 RoI
我们不能真正地在它上面应用池层,因为一些“单元”是分开的。量化所做的是,在将结果放入矩阵之前,将每个结果向下舍入。 9.25 变为 9 , 4.53 变为 4 等。
量化 RoI
您可以注意到,我们刚刚丢失了一堆数据(深蓝色)并获得了新数据(绿色):
量化损失
我们不必处理它,因为它仍然会工作,但有一个不同版本的过程称为 RoIAlign 可以修复它。
投资回报池
现在,当我们将 RoI 映射到特征图上时,我们可以在其上应用池化。为了方便起见,我们将再次选择 RoI Pooling 层的大小,但请记住大小可能会有所不同。你可能会问“为什么我们还要应用投资回报池?”这是个好问题。如果你看看快速 R-CNN 的原始设计:
在 RoI 汇集层之后,有一个固定大小的全连接层。因为我们的 ROI 具有不同的大小,所以我们必须将它们汇集成相同的大小(在我们的示例中为 3x3x512 )。此时,我们绘制的 RoI 大小为 4x6x512 ,正如您所想象的,我们无法将 4 除以 3* 😦。这就是量子化再次出现的地方。***
映射的 RoI 和池层
这次我们不用处理坐标,只用大小。我们很幸运(或者只是方便的池层大小)的是 6 可以被 3 除,得到 2 ,但是当你用 3 除 4 时,我们剩下的是 1.33 。应用同样的方法(向下舍入)后,我们得到了一个 1x2 向量。我们的映射如下所示:
数据池映射
因为量子化,我们又一次失去了最下面一行:
数据池映射
现在我们可以将数据汇集成 3x3x512 矩阵****
数据汇集过程
在这种情况下,我们应用了最大池,但在您的模型中可能会有所不同。Ofc。这个过程是在整个 RoI 矩阵上完成的,而不仅仅是在最顶层。所以最终结果看起来是这样的:
全尺寸池输出
同样的过程被应用到我们原始图像的每一个 RoI,所以最后,我们可能有数百甚至数千个 3x3x512 矩阵。这些矩阵中的每一个都必须通过网络的其余部分发送(从 FC 层开始)。对于它们中的每一个,模型分别生成 bbox 和 class。
接下来呢?
在池化完成后,我们确定我们的输入大小为 3x3x512 ,这样我们就可以将它送入 FC 层进行进一步处理。还有一件事要讨论。由于量化过程,我们丢失了很多数据。准确地说,是这么多:
量化中的数据丢失(深蓝色和浅蓝色),数据增益(绿色)
这可能是一个问题,因为每个“单元”都包含大量的数据(特征图上的 1x1x512,粗略地转换为原始图像上的 32x32x3,但请不要使用该参考,因为卷积层不是这样工作的)。有一种方法可以解决这个问题(RoIAlign ),我将很快就此写第二篇文章。
编辑:这里是第二篇关于 RoIAlign 和 ROI warphttps://medium . com/@ Kemal piro/understanding-region-of-interest-part-2-ROI-align-and-ROI-warp-f 795196 fc 193
参考文献:
最初发布于https://erdem . pl。**
了解感兴趣区域— (RoI 对齐和 RoI 扭曲)
理解 ML
直观解释 RoI Align 的工作原理,以及为什么它优于标准 RoI 合并?
如果你不熟悉 RoI 的概念,先阅读一下了解感兴趣区域— (RoI 汇集)可能会有所帮助。本文不包括对什么是 RoI 的介绍,而只集中在RoI align和* RoIWarp 上。***
为什么我们要修改投资回报池?
正如您在本系列的第一部分中所记得的,RoI Pooling 有一个主要问题。在此过程中,它会丢失大量数据。
RoI 集中量化损失(深蓝色和浅蓝色),数据增益(绿色)
每当它这样做时,关于该对象的部分信息就会丢失。这降低了整个模型的精度,很多真正聪明的人都考虑过这个问题。
设置
开始之前,我需要快速解释一下我们的模型。
原创 Mask R-CNN 架构。来源:https://arxiv.org/pdf/1703.06870.pdf
屏蔽 R-CNN 输出。来源:https://arxiv.org/pdf/1703.06870.pdf
我们将使用屏蔽 R-CNN* 网络进行测试。我们使用它的唯一原因是这种网络更多地受益于精确的池层,因此更容易显示 RoI Align 和 RoI Pooling 之间的差异。在实现投资回报池之前,我们使用哪个网络并不重要。因此,我们的设置保持不变,如下所示:***
模型特征映射过程。猫 i 法师来源:斯蒂芬妮·布斯https://www.flickr.com/photos/bunny/
我们的模型接受大小为 512x512x3 (宽 x 高 x RGB)的图像输入,VGG16 将它映射到一个 16x16x512 特征图中。比例因子是 32 。
接下来,我们使用一个建议的 ROI(145 x200框)并尝试将其映射到特征图上。因为不是所有的物体尺寸都可以除以 32,所以我们放置的 RoI 不与网格对齐。**
RoI 放置
- (9.25,6) —左上角
- 6.25 —宽度
- 4.53 —高度
我们再次选择我们的池层大小为 3x3 ,因此最终结果形状为 3x3x512 (这只是一个任意的例子,以便更容易在图像上显示。您的池层可能会有不同的大小)。
汇集层
至此,一切看起来与 RoI Pooling 中的完全相同。
RoI Align 简介
RoI Pooling 和 RoI Align 的主要区别在于量化。 RoI Align 不使用量化进行数据汇集。你知道快速 R-CNN 是两次应用量化。第一次在映射过程中,第二次在汇集过程中。
映射和汇集时的量化
我们可以跳过这一步,将原始 RoI 分成 9 个大小相等的方框,并在每个方框内应用双线性插值。让我们来定义盒子:
RoI 框尺寸
每个框的大小由映射的 RoI 的大小和汇集层的大小决定。我们使用的是 3x3 池层,所以我们必须将映射的 RoI ( 6.25x4.53 )除以 3。这给了我们一个高度为 1.51 和宽度为 2.08 的盒子(这里我将数值四舍五入以使其更简单)。现在,我们可以将我们的盒子放入映射的 RoI 中:
RoI 被分成多个方框
如果您查看第一个框(左上角),您会注意到它覆盖了六个不同的网格单元。为了提取池层的价值,我们必须从中抽取一些数据。为了对数据进行采样,我们必须在那个盒子里创建四个采样点。
采样点分布
你可以通过将盒子的高度和宽度除以 3* 来计算这些点应该在哪里。***
在我们的例子中,我们像这样计算第一个点(左上角)的坐标:
- X = X_box +(宽度/3) * 1 = 9.94
- Y = Y_box +(高度/3) * 1 = 6.50
为了计算第二个点(左下角),我们只需改变 Y:
- X = X_box +(宽度/3) * 1 = 9.94
- Y = Y_box +(高度/3) * 2 = 7.01
现在,当我们有了所有的点,我们可以应用双线性插值来采样这个盒子的数据。双线性插值通常用于图像处理中对颜色进行采样,其公式如下所示:
双线性插值方程
请不要试图理解这个等式,而是看一看它是如何工作的图形解释:
第一点的双线性插值
当你从我们的盒子中取出第一个点时,你把它和最*的相邻单元连接起来(正好在中间),除非它已经被取了。在这种情况下,我们的点有坐标 (9.44,6.50) 。左上方向上最接*单元格中间的是 (9.50,6.50) (如果我们的点在网格上仅高出 0.01,则为(9.50,5.50))。然后我们必须选择一个左下方的点,最接*的点是 (9.50,7.50) 。遵循同样的规则,我们选择 (10.50,6.50) 和 (10.50,7.50) 作为右上角和右下角的点。在 RoI 上方,您可以看到获得第一个点的值(0.14)的整个计算过程。
第二点的双线性插值
这次我们从以下位置开始插值:
- 左上:(10.50,6.50)
- 左下方:(10.50,7.50)
- 右上:(11.50,6.50)
- 右下:(11.50,7.50)
你应该在这里开始看到一个模式:)。以下是其他几点:
第三点的双线性插值
- 左上:(9.50,6.50)
- 左下方:(9.50,7.50)
- 右上:(10.50,6.50)
- 右下:(10.50,7.50)
第四点的双线性插值
- 左上:(10.50,6.50)
- 左下方:(10.50,7.50)
- 右上:(11.50,6.50)
- 右下:(11.50,7.50)
现在我们已经计算了所有的点,可以对它们应用最大池(如果你愿意,也可以是*均池):**
第一箱统筹
我不会给你看所有的插值,因为这会花很长时间,你可能已经知道如何做了。我将向您展示使用 RoIAlign 从该 RoI 汇集数据的整个过程:
RoIAlign 池化过程(在新标签中打开图像以检查所有计算)
还有 ofc。此过程适用于所有图层,因此最终结果包含 512 个图层(与要素地图输入相同)
RoIAlign 全尺寸
请注意,即使我们没有将采样点放置在要素地图的所有像元内,我们也是通过双线性插值从这些像元中提取数据。
在这种情况下,单元格 11x6、11x7、11x8、11x9、11x10、13x6、13x7、13x8、13x9、13x10、15x6、15x7、15x8、15x9、15x10 中不会有任何点。如果您查看第二个点计算(第一个框),它仍然使用 11x6 和 11x7 单元进行双线性插值,即使该点位于 10x6 单元中。
如果您比较 RoIAlign 和 RoIPooling 的数据损失/数据增加,您应该看到 RoIAlign 使用整个区域来汇集来自以下区域的数据:
比较 RoIAlign(左)和 RoIPooling(右)数据源。
- 绿色表示用于池化的附加数据。
- 蓝色(两种阴影)表示数据在汇集时丢失。
洛奇普——在中间见我
通过多任务网络级联 在 实例感知语义分段中引入了第三种汇集数据的方法,这种方法被称为 RoIWarp 。RoIWarp 的想法与 RoIAlign 或多或少是一样的,唯一的区别是 RoIWarp 是将 RoI 映射量化到特征映射上。
RoI 翘曲
如果从数据丢失/数据增加的角度来看:
RoI 翘曲数据丢失/数据增加
由于双线性插值,我们只损失了一小部分。
RoIAlign 和 RoIWarp 如何影响精度
如果我们看一下 Mask R-CNN 的论文,有一些重要的数字需要讨论。第一个是在步幅为 16 的 ResNet-50-C4 上应用不同 RoI 层时*均精度的变化:**
当应用 RoIWarp 时,只有很小的改进,但是应用 RoIAlign 使我们在精度上有了显著的提高。这种提升随着步幅的增加而增加:
其中 APbb 是检测边界框的*均精度。测试在 ResNet-50-C5 上进行,步幅为 32。
摘要
当我们想提高我们的 R-CNN 类模型的准确性时,理解 RoI 池是很重要的。在 2014 年关于快速 R-CNN 的论文中提出的标准方法和 2018 年关于屏蔽 R-CNN 的论文中提出的新方法之间存在显著差异。这并不意味着这些方法只适用于特定的网络,我们可以很容易地在快速 R-CNN 中使用 RoIAlign,在 Mask R-CNN 中使用 RoIPooling,但你必须记住,RoIAlign *均来说可以提供更好的精度。
我真的希望我的解释很容易理解,因为我已经看到了很多关于投资回报池的帖子,而没有进入计算。在我看来,更直观的方法总是更好,尤其是如果你不想花一整天的时间一遍又一遍地阅读原始论文以最终理解它的作用。
参考资料:
- R.吉斯克。快速 R-CNN。2014 年https://arxiv.org/pdf/1504.08083.pdf ICCV
- J.戴,贺国强,孙军。基于多任务网络级联的实例感知语义分割。2016 年https://arxiv.org/pdf/1512.04412.pdf CVPR
- K.何、g .基约萨里、p .杜尔和 r .吉希克。面具 R-CNN 在 ICCV,2018https://arxiv.org/pdf/1703.06870.pdf
原载于https://erdem . pl。**
了解具有 CNN 功能的区域(R-CNN)
R-CNN 物体探测完全指南
R-CNN 的架构细节以及模型设计和论文的关键要点
由 Unsplash 上的 Pietro Jeng 拍摄
在这篇博文中,我解释了关于“用于精确对象检测和语义分割的丰富特征层次”这篇文章的架构细节。虽然这篇文章已经发表了一段时间,但是除了架构之外,还有很多东西需要从这篇文章中学习。我首先简要介绍了过量饮食网络,然后介绍了 RNN 网络。如果你对过食网一无所知,那么不用担心!!你仍然不会错过任何东西。
还有,博客的结构有点不一样。这更像是学生和老师之间的对话(尝试用费曼技巧学习😃).学生端的问题用粗体突出显示(以防你赶时间)。
学生
我精通著名的对象分类算法,比如 VGG、AlexNet、ResNet、InceptionNet、MobileNet(以及它们所有的变体)等等。我对这种方法的架构感到惊讶,并希望进一步扩展我在这一领域的知识。然而,我脑海中有一个问题,这些模型只能判断图像中是否包含物体。然而,我想研究还能判断物体在图像中的位置的模型?
老师
你希望冒险进入的深度学习部分也已经分类如下:图像定位、对象检测和图像分割。
物体检测:预测多类多个物体的包围盒。也可以包含许多属于同一类的对象。
图像定位:预测图像中单个类别的单个对象的边界框
图像分割:为图像中的每个对象创建逐像素的掩模。
在这三者中,我们将讨论对象检测。在深入研究 R-CNN 架构细节之前,我想让您了解一下 Union 上的交集的概念。两个框之间的相交面积除以两个框所占面积的并集,定义为并集上的交集。在下图中,上半部分显示了公式的可视化表示。
并集上的交集(IoU)——作者在 draw.io 的帮助下创作的图片
设上面图像下半部的蓝色方框是物体边界方框的预测,黄色方框是另一个预测。所以,当两个盒子完全重叠时,IoU 为 1,当它们完全不重叠时,IoU 为 0。所有其他情况都在 0 和 1 之间,精确值可以通过使用边界框坐标来确定。在训练和评估性能时,其中一个框可以被标记为基础事实,如果 IoU 低于特定阈值,则预测可以被认为是假阳性。记住这个解释,我们一会儿会需要它。
此外,网络中使用的性能测量是*均精度 (mAP)。*均精度的本质就像准确性一样,地图越多越好。在跳到 R-CNN 之前,我们先来看看 R-CNN 之前流行的一个模型。
使用卷积神经网络进行对象检测的第一篇论文之一是 OverFeat 。他们使用滑动窗口方法来检测图像中不同位置的物体。他们还使用了图像金字塔,以便在放大的图像中检测较小的对象,在较小的图像中检测较大的对象。下面的 gif 将让你对滑动窗口的使用有一个直观的了解。
用于对象检测的滑动窗口方法—作者 gif,照片由 Sonnie Hiles 在 Unsplash 上拍摄
OverFeat 的作者使用 AlexNet 作为他们的基础架构,并提供了两个网络,一个快速,另一个精确。他们用 1 x 1 卷积层取代了 CNN 的全连接层(这是一个好主意),减少了计算量,提高了训练和测试的速度。它们还有一个回归层,从 Pool5 层获取输入,并通过两个密集层传递它们,然后最终输出有 4 个单元来确定边界框。
注意:我过度简化了对超喂纸的解释,并且有许多关于架构的微妙细节,这些细节请参考纸
学生
我没有时间深入研究过厚的纸张,但是根据你展示的 gif,看起来是不是窗口选择的图像的许多子部分不会检测到任何对象?像这些区域将被检测器选择,通过整个 CNN 架构发送,并且仍然不产生任何对象并且增加计算成本?如果我们能以某种方式给模型一个提示,这些地方可能有一些物体,而模型只会告诉我在特定的位置是否有物体,这不是更好吗?
老师
你强调的问题确实是过度饮食的警告。R-CNN 的论文做了一些类似于你直觉认为应该做的事情。它使用选择性搜索算法来检测图像中某个对象的可能位置,并只将这些图像部分(大约 2000 个区域建议)发送到 AlexNet 网络。他们用 SVM 层替换了最后一个 Softmax 层,并在 Pascal VOC 2007 上只训练了用于分类的 SVM 层(关于训练的细节将在后面解释)。此外,Pascal VOC 数据集有 20 个类,但他们为 SVM 训练了 21 个类,其中额外的类对应于没有对象的背景类。通过这种方法,他们能够获得 44.7%的 mAP。看看建筑吧!
R-CNN 网络架构— 论文
学生
对于 Pascal VOC 上的对象检测来说,这些确实是很好的结果,因为它们比当时的当代模型好得多。然而,我有这样一个查询,由于 ImageNet 数据集与 Pascal VOC 数据集相比来自不同的分布,作者难道不应该对模型进行微调吗?根据我所知的文献,如果数据集分布发生变化,我们通常会对模型进行微调。此外,卷积网络接受固定大小的输入,例如,在 AlexNet 的情况下为 227 x 227,那么它们如何在选择性搜索算法提出的区域上应用约束呢?
老师
作者首先将图像扭曲到 227 x 227 大小,然后将它们传递给输入,这时他们在训练 SVM 后得到了 44.7%的地图。扭曲图像时,图像中的对象可能会被拉伸、拉长、挤压等,它们甚至可能会妨碍性能。因此,作者首先微调整个 AlexNet 网络进行分类。之后,作者用 SVM 层替换了 Softmax 层,只训练这一层。由于这种微调,地图从 44.7%增加到 54.2%。
还有一个关于用于微调和 SVM 训练的数据的细节。在 ImageNet 数据集上训练预训练网络。在微调模型时,作者使用了 Pascal VOC 2007 数据集。在微调过程中,即 softmax 层位于顶部时,包含 IoU ≥ 0.5 的图像被用作地面实况正框,其余图像被用作负类(背景,无对象)。然而,为了训练 SVM,他们仅使用真实的图像作为训练,并带有 IoU < 0.3 as negative class. The remaining grey zone proposals were ignored. The authors also replaced the SVM with 21 way Softmax and the mAP dropped from 54.2% to 50.9%.
注意:关于为 Softmax 和 SVM 选择阳性和阴性样本的详细信息在本文的附录 B 中提供
学生
这是我第一次遇到这样的微调方法。RCNN 的论文还有其他特点吗?
老师
是的,有。作者对他们的建筑做了详尽的烧蚀研究。大部分讨论都围绕着密集层和微调网络的必要性。让我们这样来看。
无需微调 作者从网络的 Pool5 层提取特征,然后从 fc6 层,再从 fc7 层提取特征。对于提取的每个要素,他们分别训练 SVM 图层并生成地图。看看下面的图片。
没有微调的地图— 纸
从上图的最后一列中,我们看到删除两个 FC 层并没有对性能造成太大影响。此外,来自 fc6 层的特征比来自 fc7 层的特征是更好的预测器。以下是作者在观察后所说的话。
这意味着 CNN 的 29%,即大约 1680 万个参数可以在不降低 mAP 的情况下被删除。更令人惊讶的是,移除 fc7 和 fc6 产生了相当好的结果 7,尽管 pool5 特征仅使用 6%的 CNN 参数来计算。
作者们现在已经用各种方式对网络进行了微调。首先,当他们直接使用 Pool5 的功能并将其发送到 SoftMax 以微调整个网络时。在这种情况下,他们发现 Pool5 的功能在没有微调的情况下增加了 3%。接下来,他们对 fc6 和 fc7 层也做了同样的事情,分别增加到 53%和 54.2%。
带微调的地图— 纸
您可以将调优后获得的结果图与上图中共享的未调优结果图进行比较。微调大大改善了结果。但是,第四排有一些特殊之处,我们来讨论一下!!
使用包围盒回归器
作者已经添加了一个包围盒回归器到池 5 层的输出中。这样做之后,他们发现 58.5% mAP 的性能有了相当大的提高。这是一个未经微调的模型在地图上几乎 14%的提升。这个包围盒回归用 l2 损失来训练,并且用λ= 1000 来惩罚。
注:关于边界框回归器的详细信息见本文附录 C。
作者还尝试用 VGG-16 代替 AlexNet,发现使用边界框回归器时 mAP 增加了 8%。
具有不同模型架构的地图— 论文
我现在建议您阅读这篇文章,以便更深入地了解这篇文章和实现细节。他们还提供了 ILSVRC2013 探测数据集的详细说明,并将他们的模型与其他模型进行了比较。
学生
这是作者做的非常详尽的分析。这篇文章可能有点旧了,但是模型架构的分析方法是值得注意的。这些通过实验验证层对精度提高的贡献的策略,可以在设计任何其他类型的模型时使用,甚至可以用于一些其他任务。
教师
是的,他们已经对他们的模型进行了详细而全面的分析,并与当代模型进行了比较。最后一点,出于定义的原因,像 R-CNN 这样的模型被称为两阶段模型。这种模型具有用于检测物体的可能位置的独立算法/模型(选择性搜索)和用于检测物体存在的独立模型(AlexNet)。并且这个模型(包括包围盒回归器)的训练是一个的三阶段过程,其中第一阶段,模型被微调,第二阶段,SVM 分类器被训练,第三阶段,包围盒回归器被训练。
注意:我已经讨论了 R-CNN 论文中 Pascal VOC 2007 数据集的分数和分析。该文件还显示了对其他数据集的深入分析。
参考
R.Girshick,J. Donahue,T. Darrell,J. Malik,用于精确对象检测和语义分割的丰富特征层次,计算机视觉和模式识别,2014 年
X.张,M. Mathieu,R. Fergus,Y. LeCun, OverFeat:使用卷积网络的集成识别、定位和检测,计算机视觉与模式识别,2014
使用新冠肺炎数据集了解回归-详细分析
确诊 COVID 病例的计数是否取决于检测?
每个数字都讲述一个故事(图片由 Unsplash 提供)
希望你们都*安健康!新冠肺炎已经完全改变了 2020 年的进程,使全球经济大幅萎缩。在这篇博客中,我们将深入探讨回归分析,以及我们如何使用芝加哥 COVID 数据集来推断令人难以置信的见解。微积分和线性代数的一些直觉会让你的旅程更容易。欢迎来到我的第一个博客!
概念
线性回归不仅仅是一种机器学习算法,它在统计学中发挥着巨大的作用。来自一个监督学习的家族,这里的每个输入都与一个目标标签相关联。模型的任务是基本理解模式,并找到覆盖每个(输入、目标)对的最佳拟合线。
概括地说,可以根据执行的任务和产出的性质对 ML 模型进行分类:
回归:产量是一个连续变量。
分类:输出是分类变量。
聚类:没有输出的概念。回归和分类属于监督学习,而聚类属于非监督学习。
回归是预测建模技术的一种形式,我们试图找到因变量和一个或多个自变量之间的重要关系。有各种类型的回归技术:线性,逻辑,多项式,岭,套索,Softmax。
线性回归
根据术语本身,您可以猜测模型沿直线运行,其中 y 可以通过输入变量 x 的线性组合来计算。线性回归假设函数可以表示为:
线性回归假设函数(图片作者提供)
θ此处存储输入特征 x 的系数/权重,与 x 的维度完全相同。注意,为了在我们的模型中添加对常数项的支持,我们在向量 x 前面加上了 1 。
对于单个输入和输出变量,该方法称为简单线性回归,而对于多个输入/特征,该方法称为多元线性回归。对于这两者,我们的目标是找到最佳拟合线,使我们的目标变量(y)和所有样本的预测输出之间的误差*方和(SSE)或均方误差(MSE)最小化。
SSE 和 MSE 计算(图片由作者提供)
为了继续,我们需要找到最佳拟合线的参数/系数。有多种方法可以得出直线的几何方程,例如最小绝对偏差(最小化残差绝对值的总和)和 Theil–Sen 估计量(找到一条直线,该直线的斜率是由成对样本点确定的斜率的中值),但是,统计学家通常使用普通最小二乘法(OLS) 。OLS 只不过是一种最小化生产线和实际输出之间距离的方法。如果要手工计算回归线,它用了一个略显吓人的公式,求斜率' θ1 '和线截距' θ0 '。
使用 OLS 寻找截距和斜率(图片由作者提供)
有两种方法/解决方案使用最小二乘法来实现线性回归模型:
- 封闭解—正规方程
- 优化算法(梯度下降、牛顿法等。)
正态方程
重要的是要明白,θ,在这里基本上可以打破或建立模型。我们的目标基本上是寻找有助于实现模型最低成本的θ。
著名的成本函数(图片由作者提供)
目标/成本函数 J(θ)的选择可以根据手头的问题有很大的不同。一般来说,均方误差最适合回归,交叉熵最适合分类。所以,回到正规方程,它使用一种分析方法来寻找方程的参数。我花了一些时间才明白为什么事情会这样发展。希望这个简短的证明能让你掌握概念。
如果我们取残差向量为 e =Xθ — y ,那么向量形式的成本函数/残差*方和将为:
正规方程推导(图片由作者提供)
对于刚接触线性代数的人来说,请注意我们不能简单地*方xθ—y。向量乘法属性声明向量/矩阵的*方不等于其每个值的*方。所以为了得到*方值,我们把向量和它的转置相乘。
现在,为了找到最小化残差*方和的 θ ,我们需要对上面的成本函数求关于 θ的导数。
法方程估计系数 θ向量(图片由作者提供)
这就是正规方程所能提供的一切。一个非常直接和优雅的方法来找到确切的 θ 参数,这将创造奇迹,适合您的数据!然而,这里有一个问题。矩阵反函数不适用于较大的数据集(较大的 X 值)或逆矩阵可能不存在的数据集(矩阵不可逆或奇异)。即使是当今最有效的逆算法也只能给出 T2 的三次方时间复杂度 T3。对于“较小”的数据集,可以首选正规方程解,如果计算“昂贵”的矩阵求逆不是问题,但对于真实世界的数据集,则首选梯度下降或 SGD 等方法。
梯度下降
这里的目标类似于我们对正常方程的目标。梯度下降算法通过对θ0 和θ1 应用各种参数来计算出最小成本函数,直到它达到收敛。它被认为是最小化残差的最佳迭代优化算法之一。
作者图片
直觉
将一个碗或任何凸形物体视为您的成本函数。如果你从碗的四周扔出任何物体,它会走最短的最佳路线,并且会到达底部。该最低点将为我们提供最低成本和 θ 参数,以获得最佳拟合模型。
如果我们有许多局部极小值而不是一个全局极小值,很少会有挑战。成本函数通常不是一个普通的碗。它可能会过早停止,或者需要不确定的时间来收敛。不过还好!线性回归中的代价函数是凸的,因此没有任何经过两点之间的直线与曲线相交。
这是一个非常简单的过程。GD 的过程从初始化一些随机的 θ 值开始。我们通过将这些值代入成本函数来计算成本。成本函数的导数将给出该点的斜率,这样我们就知道为了获得更低的成本,下一步应该朝哪个方向走。这是怎么发生的?我们使用该导数值来更新每一步的所有 θ 系数,直到我们达到最低水*并且不再有成本降低。简单!不是吗?
成本函数的偏导数(图片来自作者)
要一次性计算所有这些参数,我们可以使用包含每个模型参数的偏导数的梯度向量。
成本函数的梯度向量(图片由作者提供)
注意,这个梯度向量指向上坡,所以下坡我们需要反方向走。一旦我们有了梯度向量,我们需要找到学习率 η 来确定下坡台阶的量。
每步后更新权重(图片由作者提供)
在尝试不同的 eta η值时,有一些事情需要记住。 如果学习率太低,可能需要不确定的时间才能收敛到最优值。然而,将它保持在高位可能会让它跳到另一边,并无限振荡。所以, η 应该保持在两者之间。为了找到一个好的学习率,我们可以应用网格搜索。通过这样,我们尝试和测试各种 η 值,保持对迭代次数的限制。收敛较快的将是问题的良好学习速率。该方法被称为批量梯度下降,因为它在整个训练集 x 上执行计算。
还有两种没有太大区别的梯度下降—
随机梯度下降
如果我们有非常大的数据集,批量梯度下降会爆炸。可能要花很长时间才能完成。random 的工作方式与 Batch GD 类似,但是它在训练集中选择一个随机实例,并仅基于该实例计算梯度。所有其他的计算都是一样的。它比批处理更快,因为在操作时我们的数据少了。
然而,这种算法没有被经常使用,因为它随机地获取实例,这使得成本函数反复地变高变低。即使收敛到最优,也会继续反弹。此外,它经常不止一次地选择实例,所以建议继续混排数据。
提示:
所以,最好一开始就有较大的学习率,然后慢慢降低。
小批量梯度下降
它需要小规模的随机实例集。但有时,摆脱局部最小值可能会变得更加困难。就不规则性而言,它优于随机梯度下降。
摘要
三者都接*最小值,但是批量梯度下降实际上在最小值停止。
对于较大的数据集,批量梯度下降将需要较长的时间。如果我们知道如何改变学习速率,随机和小批量将更快地达到最小值。
GD,SGD,Mini GD 的对比(来源:Book[Aur _ lien _ G _ Ron]_ Hands-On _ Machine _ Learning _ with _ Sc)
线性回归模型示例— COVID
让我们首先浏览一下芝加哥的新冠肺炎数据集。你可以从这里下载数据集。
数据集(作者提供的图片)
网上 80%的 COVID 数据集是时间序列格式,显示每天的病例数。因此,只有死亡和测试计数,我只能想象是否已经达到峰值,或者是否还在增加等等。但是为了进行预测,我希望拥有案例计数可以依赖的特性。我们还知道,在许多国家,检测工作做得不充分,因此病例数量较少。因此,我把重点放在可以获得测试和案例特征的数据集上,以弄清楚这两者之间是否存在巨大的相关性。
幸运的是,我找到了这个小型的芝加哥数据集,它具有测试与案例计数等特性。您可以观察到数据如何在散点图上显示略微呈线性的模式,从而显示相关性。
散点图(图片由作者提供)
如你所见,自变量是测试 (x)和因变量是案例(y) 。我们的数据集中还有一些特征,给出了 30 岁以上或更年轻的人以及拉丁人的数量,等等。我个人无法推断出我们可以用这样的特征分析什么。你可以试试看,让我知道我们是否能推断出什么。
开始编码吧:)
- 导入所有需要的库
- pandas & numPy 用于执行科学运算
-线性回归&多项式特征用于构建模型 - train_test_split 用于将数据集划分为训练和测试子集
- MSE & r2_score 度量用于分析模型性能
- seaborn 和 pyplot 用于可视化图形
- pandas & numPy 用于执行科学运算
import pandas as pd
import numpy as npfrom sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.model_selection import train_test_splitfrom sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_scoreimport seaborn as sns
import matplotlib.pyplot as plt
- 使用 pandas 导入 CSV 数据集。它输出一个数据帧,一个二维标记的数据结构。
data.head() 显示数据集中的前 5 行,让我们了解对象中是否有正确的数据类型。
data = pd.read_csv("COVID-19_Daily_Testing.csv")
data.head()
- 以获得数据集的简明摘要。
Info() 方法打印关于数据帧的信息,如索引数据类型和列数据类型、非空值和内存使用情况。
print(data.info())
- 是时候清理一些数据了! 案例和测试计数是字符串格式,因此我们在千位值中有一个逗号(例如 1,468)
PD . to _ numeric()会将类型从 string 转换为 int32。
data['Cases'] = data['Cases'].str.replace(',', '')
data['Tests'] = data['Tests'].str.replace(',', '')data['Cases'] = pd.to_numeric(data['Cases'])
data['Tests'] = pd.to_numeric(data['Tests'])
- 通常,在真实世界的数据集中,有许多要素,很难检查哪对要素显示出良好的相关性。此外,检查是否存在任何多重线性问题。下面的代码基本上绘制了数据集中的成对关系,这样每个变量在 x 轴和 y 轴上都是共享的。
data_numeric = data.select_dtypes(include=['float64', 'int64'])
plt.figure(figsize=(20, 10))
sns.pairplot(data_numeric)
plt.show()
特征对图(图片由作者提供)
大多数情况下,它们都呈现出一种不应该发生的线性模式!这就产生了著名的多重线性问题。
多重线性问题是当回归模型中的独立变量表现出相关性时。自变量应该是独立的。通俗地说,你可以认为几个变量本质上衡量同一件事。没有必要用一个以上的变量来衡量一个模型中的同一事物。这基本上降低了估计系数 θ 的精度,削弱了你的回归模型的统计能力。
为了诊断多重共线性,,我们使用一种叫做方差膨胀因子(VIF)的方法。方差膨胀因子(VIF)用于测量与预测变量不线性相关时相比,估计回归系数的方差膨胀了多少。经验法则是,如果我们有 VIF > 10 ,模型具有高度多重线性。
您可以在上图中看到,我们的数据集中存在高度多重线性。好的一面是,我在我的模型中只采用了 2 个特征,但在将来,如果你创建任何回归模型,检查 Vif 并删除彼此相关的独立变量。
- 缩放数据集 特征值可能有不同的比例。因此,为了更好地进行分析和预测,最好将它们转换为相同的比例
X = data['Tests'].values.reshape(-1,1)
y = data['Cases'].values.reshape(-1,1)
- 应用线性回归
reg = LinearRegression()
reg.fit(X, y)
predictions = reg.predict(X)
print("The linear model is: Y = {:.5} + {:.5}X".format(reg.intercept_[0], reg.coef_[0][0]))plt.figure(figsize=(16, 8))
plt.scatter(
X,
y,
c='black'
)
plt.plot(
X,
predictions,
c='blue',
linewidth=2
)
plt.xlabel("Tests")
plt.ylabel("Cases")
plt.show()
我们得到这条直线 Y = 97.777 + 0.18572X
线性回归图(图片由作者提供)
线性回归的均方根误差= > 171.8
正如你所看到的,我们有许多异常值,因为它不能很好地拟合数据。在数据复杂的情况下,我们可以尝试通过应用多项式回归来减少 RMSE。
多项式回归
线性回归的特殊情况,我们试图用一个 n 次多项式方程来拟合数据。它在发现特征间关系的能力上胜过 LR 。在这里,我们为每个特性添加了能力,并将它们添加到我们现有的特性中。将在这个扩展的特征集上进行训练,以获得目标变量和独立变量之间的曲线关系。
poly = PolynomialFeatures(degree =4)
X_poly = poly.fit_transform(X)
poly.fit(X_poly, y)
lin2 = LinearRegression()
lin2.fit(X_poly, y)
pred = lin2.predict(X_poly)
new_X, new_y = zip(*sorted(zip(X, pred)))plt.figure(figsize=(16, 8))
plt.scatter(
X,
y,
c='black'
)
plt.plot(
new_X, new_y,
c='blue'
)
plt.xlabel("Tests")
plt.ylabel("Cases")
plt.show()
Scikit learn PolynomialFeatures 类由一个度数参数组成,我们可以将其添加为应用于数据的最大功率。你需要尝试和测试哪个学位最大程度地减少 RMSE。在尝试了不同的程度值后,我发现程度 4 给了我最好的解决方案。
多项式回归图(图片由作者提供)
多项式回归的均方根误差= > 131.08。 观察这个图如何完美地拟合数据,至少比线性回归好。此外,RMSE 下降了很大程度,这又一次证明了它的善良。要获得模型的性能,还需要更多的概念,比如它是过拟合还是欠拟合,偏差/方差权衡。我们肯定会在下一篇博客中讨论这个问题!
评估指标
RMSE 为 0 意味着你的模型是输出的一个完美的预测器(但是这几乎不会发生)。
回归和分类问题有各种各样的评价标准。如果误差非常大且不理想,可以使用 RMSE,因为它通过*方根进行补偿。
我们拥有的另一个指标是 R *方(R2) ,它给出了用于预测的回归线的整体模型拟合过程的相对度量。其值介于 0 和 1 之间。值越高,模型越好。R 可以被认为是模型解释的方差的百分比。它有一个更好的版本,叫做调整的 R *方,甚至考虑到了功能是否被删除或添加。r 的*方要么保持不变,要么增加。调整后的 R *方将变化考虑在内,这就是为什么当您有许多功能需要试验时,此指标更可靠。
暂时就这样了。
希望你很好地理解了在现实世界中寻找一条完美拟合的线!衰退无处不在,是时候开始注意它了!如果您有任何问题/想法,请在下面的评论区留下您的反馈,或者您可以通过 Linkedin 联系我。到那时,等待下一个职位!😄
参考
[## OLS 回归
假设我们有\(m\)个训练示例\((\mathbf x_i,y_i)\) \(n\)个特征,$\mathbf x_i = \big[x_{i1},\ …
mlwiki.org](http://mlwiki.org/index.php/OLS_Regression) [## 简化线性回归—普通最小二乘法与梯度下降法
什么是线性回归?线性回归是一种统计方法,用于找出独立变量之间的关系
towardsdatascience.com](/linear-regression-simplified-ordinary-least-square-vs-gradient-descent-48145de2cf76)
理解逻辑回归中的正则化
如何使用正则化处理过拟合
介绍
在我上一篇帖子中,我只使用了两个特征( x 1, x 2 ),判定边界是 2D 坐标上的一条直线。在大多数真实世界的情况下,数据集将具有更多的特征,并且决策边界更加复杂。有了这么多的特性,我们经常会过度拟合数据。过度拟合是与数据集紧密拟合的函数中的建模错误。它捕获数据集中的噪声,可能不适合新的输入数据。
为了克服这个问题,我们主要有两个选择:1)删除不太有用的特征,2)使用正则化。这里我们将重点讨论正规化。
我们将分类的数据创建如下:
生成的数据无法通过线性方程进行分类,因此我们必须添加高阶项作为特征。然而,更多的特征将允许模型拾取数据中的噪声。
逻辑回归的正则化
以前,为了预测 logit(概率对数),我们使用以下关系:
随着我们添加更多功能,等式的 RHS 变得更加复杂。正则化用于通过施加惩罚来降低预测函数的复杂性。在线性关系的情况下,正则化将以下项添加到成本函数中:
其中 D 为特征尺寸。它惩罚特征的系数(不包括偏差项)。现在成本函数变成了:
利用我们对逻辑回归的先验知识,我们现在可以开始构建带有正则化的模型。
R 中的正则化
同样,我们需要首先创建一些助手函数。注意%*%
是 R 中的点积。如果你想了解更多的模型细节,你可以在这里阅读我以前的文章。
逻辑回归函数,原本以训练数据 X ,标签 y 作为输入,现在需要多加一个输入:正则化强度 λ 。
由于我们希望使用一个包含许多特征的示例来演示过拟合和正则化的概念,因此我们需要通过包含多项式项来扩展特征矩阵。
现在,我们可以像以前一样定义预测函数。
分类和决策边界
接下来,让我们在上面的数据集上训练模型。我使用了 6 次方的多项式特征矩阵。降低功率也有助于过拟合。实际上,我们正在删除不必要的功能。
首先,不使用正则化( λ =0),结果如下。该模型明显过度拟合了数据,并错误地将该区域分类为 11 点钟。另一种模型用正则化( λ =5)训练,更能代表总趋势。
左:无正规化;右图:正则化模型。
结论
正则化对于克服过度拟合非常有用。它允许我们保留甚至稍微有用的特征,并自动降低这些特征的系数。
你可以访问我的 github 获取 R 中的源代码。同样的主题还有一个 Python 版本。
理解机器学习中的正则化
通过防止过度拟合来优化预测模型
杰克逊·乔斯特在 Unsplash 上的照片
W 在训练机器学习模型时,一个主要方面就是评估模型是否过拟合数据。过度拟合通常发生在模型试图拟合所有的数据点时,在此过程中捕获噪声,导致模型的不准确发展。
机器学习模型的性能可以通过成本函数来评估。通常,成本函数由实际值和预测值之差的*方和表示。
这里“y”代表实际值,而“ŷ”代表预测值。
这也被称为“残差*方和或“误差*方和”。预测模型在被训练时试图以最小化该成本函数的方式来拟合数据。
当一个模型通过所有的数据点时,它开始过度拟合。在这种情况下,尽管成本函数的值等于零,但是考虑了数据集中的噪声的模型并不代表实际的函数。在这种情况下,根据训练数据计算的误差较小。然而,在测试数据上,误差仍然很大。
本质上,模型通过使用高度复杂的曲线来过度拟合数据,这些曲线具有自由度大的项以及为每一项提供权重的相应系数。
我们可以从方程中清楚地观察到曲线越来越复杂。
从上图可以看出,对于较高的自由度,测试集误差比训练集误差大。
正则化是一个概念,通过它可以防止机器学习算法过度拟合数据集。正则化通过在成本函数中引入惩罚项来实现这一点,该惩罚项为复杂曲线分配更高的惩罚。
基本上有两种类型的正则化技术
- L1 正则化或拉索回归
- L2 正则化或岭回归
让我们首先从理解 L2 正则化或岭回归开始。
L2 正则化或岭回归
岭回归的成本函数由下式给出:
这里λ(𝜆)是一个超参数,它决定了惩罚的严厉程度。λ的值可以从 0 变化到无穷大。可以观察到,当λ的值为零时,罚项不再影响成本函数的值,因此成本函数降回误差*方和。
为了理解惩罚项的意义,让我们深入研究一个例子。
假设我们仅仅根据“误差*方和”来评估我们模型的性能,我们会得到下图中左侧图形所表示的曲线。
如前所述,惩罚项不再影响成本函数的值。因此,我们得到相同的过拟合曲线。然而,当 lambda 的值增加时,我们会得到一条更简单的曲线,如上图右侧的图表所示。
比较两个模型的均方误差,我们观察到,对于过拟合曲线,训练集上的误差最小,但是对于更简单的曲线,测试集上观察到的误差显著下降。
因此,通过简化我们的曲线,我们在训练集中引入了一些误差,但这使我们能够走向更一般化的模型。
需要强调的一个重要方面是,通过改变λ的值并将我们的复杂曲线转换为简单曲线,我们正在处理相同的 15 次多项式模型。直到 15 度的项仍然存在于简单模型的方程中,但是模型的复杂性已经降低。
这是如何实现的?
答案在于刑罚本身的机制。我们再来看看成本函数。
λ是一个超参数,决定惩罚的严厉程度。随着罚值的增加,系数的值收缩,以便最小化成本函数。由于这些系数也充当多项式项的权重,缩小这些系数将减少分配给它们的权重,并最终减少其影响。因此,对于上述情况,分配给高次多项式项的系数已经缩小到这样的程度,即这些项的值不再像以前那样严重地影响模型,因此我们有一条简单的曲线。
在确定了λ的最佳值后,我们将其应用到模型中,得到下面的曲线。
改变λ值的影响
我们观察到,随着λ值的增加,模型变得更加简单,直到它渐**行于 x 轴。换句话说,对于很高的λ值,我们有一个高度偏差的模型。
λ的值如何选择?
这让我们左右为难。对于非常低的λ值,获得过拟合曲线,而对于非常高的λ值,获得欠拟合或高度偏差的模型。那么怎样才能达到λ的最佳值呢?
这个问题的答案是交叉验证。通常,10 倍交叉验证可以帮助我们确定最佳λ值。
多维数据集
假设我们试图预测一种动物的身体大小。假设这个尺寸取决于它的体重和年龄。在这种情况下,我们模型的功能可以表示为:-
由于我们有多种取决于尺寸的功能,成本可由下式给出:-
如果我们的训练集中的数据点数量有限,岭回归可以通过减少方差并使模型对“训练数据”分布不太敏感,从而使曲线对噪声不太敏感,来改进根据数据做出的预测。
L1 正则化或套索回归
LASSO 代表最小绝对收缩和选择算子。
Lasso 回归的成本函数由下式给出:
与岭回归相似,lambda 也是一个超参数,它决定惩罚的严厉程度。成本函数的区别在于岭回归采用斜率的*方,而套索回归采用斜率的绝对值。
改变λ的影响
随着λ值的增加,系数的值将越来越接* 0,直到该值最终为 0。
注意,在岭回归中,当λ增加时,系数的值收缩,直到模型渐**行于 x 轴。在 lasso 回归中,对于较大的λ值,模型实际上会变得与 x 轴*行。
为了理解这一点,让我们举一个例子。
假设动物的大小由以下等式给出
套索回归的工作方式是保留最相关的要素,而缩小其他要素。
上述等式的成本函数可以给出如下
在上面的例子中,“体重”和“食物卡路里含量”与体型最相关,而“星座”和“风速”最不相关。因此,“coeff1”和“coeff2”会缩小一点,而“coeff3”和“coeff4”会一直缩小到零。
因此,我们只剩下等式:-
由于 lasso 回归可以从方程中排除无关紧要的变量,因此在减少包含大量无用变量的模型中的方差方面,它比岭回归稍好一些。换句话说,套索回归有助于特征选择。
相比之下,当大多数变量都有用时,岭回归往往做得更好一点。
套索和岭回归的另一个有趣的区别
前面提到过,随着岭回归中λ值的不断增加,模型曲线变得越来越*坦,直到它渐**行于 x 轴。
为了理解这一点,为了简单起见,我们将参考线性回归模型。
点代表数据点,线代表回归模型。因为这是一条直线,所以它有一个斜率和一个 y 截距。
随着λ值的增加,线性回归模型的斜率将持续降低。
在较大的λ值下,我们观察到回归模型的以下曲线。
虽然看起来这条线*行于 x 轴,但实际上,线性模型的斜率值略大于零。
这种行为可以通过下图直观地观察到。x 轴表示模型的斜率,y 轴表示模型的成本函数值。每个图的左侧是λ值的范围。
曲线中的蓝点代表模型成本函数的最小值。随着 lambda 值的增加,岭回归模型的成本函数的最低值不断向零斜率值移动,但最低值永远不会与零重合。
相反,在 lasso 回归模型中,随着λ值的增加,我们观察到类似的趋势,其中成本函数的最低值逐渐向零斜率值移动。然而,在这种情况下,对于大的λ值,当斜率的值与零一致时,实现了成本函数的最低值。特别是对于等于 40、60 和 80 的λ值,我们在曲线图中观察到一个明显的拐点,其中斜率值等于零,这恰好也是λ等于 80 时成本函数的最低值。
因此,对于较大的λ值,lasso 回归模型可以具有等于零的斜率。
套索回归对特征选择有什么帮助?
让我们看看每种正则化技术的成本函数。
对于套索回归:-
对于岭回归:-
套索回归的一种解释方式是解一个系数的模之和小于或等于常数“c”的方程。类似地,岭回归可以解释为求解一个系数的*方和小于等于常数“c”的方程。
假设我们的模型结合了两个特征来预测某个实体,并且这些特征的系数由β1 和β2 给出。
在这种情况下,岭回归可以表示为:-
类似地,套索回归可以表示为
岭回归方程类似于圆的方程,因此约束区域位于圆的圆周内和圆周上,类似地,套索回归方程类似于菱形方程,约束区域位于该形状的内部和圆周上。
这些方程在下面的图片中被可视化了。
约束区域由浅蓝色区域表示,而红色椭圆是表示“误差*方和”的轮廓。在任何特定的给定轮廓中,“误差*方和”的值都是相同的。轮廓离中心越远,“误差*方和”的值越高。
资料来源:统计学习导论,作者:加雷斯·詹姆斯,丹妮拉·威滕,特雷弗·哈斯蒂,罗伯特·蒂布拉尼
如果“c”的值足够大,约束区域将包含由β (hat)表示的轮廓的中心,因此岭回归和套索回归的值将与“误差*方和”的最小值相同。这对应于λ= 0 的情况。
岭和套索回归的系数估计由轮廓接触约束区域的第一个点给出。由于岭回归具有圆形约束区域,接触点通常不会出现在轴上。因此,系数估计将主要是非零的。
但是,由于套索回归约束区域有突出的角,所以轮廓与轴上的约束区域相交的可能性更大。在这种情况下,其中一个系数的值将等于零。在高维空间中,我们有两个以上的特征,许多系数估计可能同时等于零。
结论
正则化是防止模型过度拟合的有效技术。它允许我们减少模型中的方差,而不会显著增加模型的偏差。这种方法允许我们开发一个更一般化的模型,即使我们的数据集中只有几个数据点。
在确定模型的参数或特征已知的情况下,岭回归有助于缩小模型的系数。
相反,套索回归可以有效地从模型方程中排除无关紧要的变量。换句话说,套索回归有助于特征选择。
总的来说,这是一项重要的技术,可以极大地提高我们模型的性能。
感谢您阅读这篇博客。我希望听到你对此的想法。
如果你渴望在一群志同道合的人的陪伴下踏上高质量的学习之旅,我强烈推荐你加入最有吸引力的社区: 共同学习休息室 关于 电报 和访问colearninglounge.com了解更多信息。
参考
- Josh Starmer, StatQuest:正则化第一部分:岭回归。https://youtu.be/Q81RR3yKn30
- Josh Starmer, StatQuest:正则化第二部分:Lasso 回归。https://youtu.be/NGf0voTMlcs
- Josh Starmer, StatQuest: Ridge vs Lasso 回归,可视化!!!https://youtu.be/Xm2C_gTAl8T2
- 普拉尚·古普塔,机器学习中的正则化。https://towards data science . com/regulation-in-machine-learning-76441 ddcf 99 a
- 统计学习导论
理解 ML 和 DL 中的正则化技术
洞察正则化技术
对减少计算时间和训练 ML 和 DL 模型投入的工时的技术的简单而全面的展望。
乔·加德纳在 Unsplash 上的照片
概述:
现在,随着各行业开始接受“人工智能”作为预测其公司成功的重要组成部分,机器学习和深度学习技术正在进入公司的职位简介列表。但是我们经常看到,公司的实际决策者(发号施令的人:cxo)对这些技术能做什么以及如何让他们的公司受益有一个非常误导的概念。对于那些不完全理解 ML 真相的人来说,ML 经常被视为一种有潜力解决任何和所有工业问题的技术。下图让 ML 目前的状态变得相当清晰。
这不是讽刺而是对 ML 相当准确的理解。它是计算机在提供一组前兆事件的同时预测事件的非硬编码能力。我会尽量保持这篇博客的非数学性,但是在这里我想包括这样一个事实,ML,本质上,是在给定多个这样的方程的情况下,预测 x 和 y 之间的函数关系 F 的行为。
但是很多时候,我们看到,即使在训练了一个模型并且达到了可接受的训练精度之后,当这个模型被用来处理测试用例时,它还是悲惨地失败了。
这是由于过度拟合或使函数过度逼*训练数据的现象而发生的。这导致模型不是理解如何解决问题的一般想法,而是死记硬背训练数据。下图说的很清楚。
真正的函数是正弦曲线(绿色),我们正试图从给定的数据中预测它。直到第三个数字,我们看到模型学习得很好。即使不是所有的数据点都令人满意,这也是一个*乎完美的函数*似值。但是随着训练的继续,我们看到这个函数塑造了自己来适应所有的数据点,并且采取了与我们期望的完全不同的形式。这太合身了。其中训练损失为零,但是测试损失增加
理解偏差-方差权衡和正则化的需要:
偏差在数学上是函数的期望值和实际值之间的差值。我们不会深入偏见的基本统计数据,但我会负责任地留给你一个看起来很可怕的等式:
为了说明问题,简单的线性模型的偏差较高,而复杂的多维模型的偏差较低。这是因为复杂的模型更适合所有的训练数据。
方差是训练数据和测试数据之间 ML 模型的预测精度的变化。方差导致的误差是一个训练集的预测值与所有训练集的预期值之间的差异量。换句话说,根据模型,不同预测的值彼此相差有多远。另一个等式会让你害怕,伙计们。
简单的模型方差低,而复杂的模型方差高。
下图可以用来清楚地建立偏差和方差的概念。图的左端是具有高偏差的区域,因为训练和测试误差都很高。这是拟合不足的区域,或者是模型学习不够的区域。
模型的右端是一个高方差区域,其中训练误差较低,但测试误差较高。这是过度拟合的区域,在这里我们看到,尽管模型已经达到了很高的训练精度,并且看起来模型接*完美,但它在测试数据上的表现很差。这纯粹是浪费计算能力和工程师的时间。
中间区域,偏差和方差都很低,即使不是最低的也是模型的最佳区域。实现这种模型训练状态的行为被称为偏差-方差权衡。
有多种方法可以实现偏差方差权衡。这些方法或技术被称为正则化技术。
一些常见的有:
- L2 正则化
- 提前停止
- 数据集扩充
- 集成方法
- 拒绝传统社会的人
- 批量标准化
L2 正规化:
为了让事情尽可能简单,我将 L2 正则化定义为“不让模型驱动训练误差为零的技巧”。如果事情有那么简单就好了…
在训练模型期间,我们不断更新各种变量(权重和偏差;试图预测我们的原始函数。这种更新是基于“更新规则”进行的,比如梯度下降(我们不会谈到这一点)。该更新规则取决于“损失函数”,该损失函数是这些变量的函数。如果事情变得复杂,请原谅我。我们的目的是最小化这个“损失函数”。这很直观,不是吗?在任何有利可图的工业情况下,你都努力使损失最小化。简单,初始化?
因此,我们在训练过程中最小化损失函数。L2 技术的特别之处在于,我们不是最小化训练损失,而是最小化它的不同版本。
上面等式中的第一项是“损失项”,即衡量模型与数据拟合程度的项。最后一项是数学专家提出的“权重高斯分布可能性的对数”。这度量了模型的复杂性。对于我们这些外行来说,就是所有特征权重的*方和(w)。在这里,我再次负责任地告诉你们:
这种模型的复杂性通过 L2 技术来量化。在这种情况下,接*零的要素权重不会受到变换的太大影响,但异常值会产生巨大影响。而上述项的值越大,我们看到的偏倚的增加和方差的减少就越多。从上图可以明显看出,在训练结束时,偏差非常低,方差很高。因此,如果我们增加偏差并减少方差,我们将有效地到达图表中“好模型”区域的某处。所以现在,我们有了一个很好的模型!耶!
提前停止:
这是迄今为止最简单的正则化技术(它们都是,但是你不会相信我,对吧)。这个过程包括记录最小损失值下的变量(w & b)值。在整个训练过程中,我们记录 w & b 的值,在该值处我们获得最小的验证误差。当我们看到验证错误再次上升时,我们停止训练。这是一个非常有用的过程,但它的缺点是,在训练非常深的神经网络或非常复杂的模型期间,这在写入和重写最小值期间使用了大量的处理能力。
数据集扩充:
只有当我们有大量数据对模型进行训练时,才有可能将模型训练到良好的预测状态。换句话说,如果数据太少,很容易使训练误差为零。让我们以训练一个关于图像分类的神经网络为例。假设我们有 1000 张图片来训练模型。如果我们有 3000 张图片来训练它不是更好吗?无需获取额外数据,我们可以轻松地“扩充”当前图像并创建“新”图像。事实上,这些对我们来说并不新鲜,但对模型来说,它们是新的。
那么什么是增强?它是一种行为,即通过在现有数据中引入某些差异,同时保留原始标签,从现有数据中人为生成新数据。这些差异取决于我们正在处理的数据类型。对于音频,加速采样或引入一些背景噪声是一种增强技术。这不会改变标签值。对于文本,我们可以用同义词替换一个单词,而不改变其传达的信息。对于图像,我们可以改变视角、缩放、照明和其他改变图像但保留其标签的技术。这里有一些可爱的东西来抵消你阅读这篇博客的无聊,并使图像增强变得清晰。
因此,当我们现在有更多的数据输入到我们的模型中时,这使得它更难记住整个事情,因此,训练误差不会变为零。有点像你的历史考试,init?
合奏方式:
集成方法是一种元算法,它将几种机器学习技术结合到一个预测模型中,以减少方差、偏差或改善预测。
上面这一段是 Google 对系综方法的定义,我试着给你分解一下。在这种技术中,我们使用多个模型架构来预测输出,无论是分类还是回归。假设模型 A、B 和 C 被赋予给一只狗分类的任务:模型 A 说它是一只猫,但模型 B 和 C 说它是一只狗。因此,如果我们相信大多数人所说的,我们会得到正确的输出,但如果我们相信第一个模型的输出,我们就错了。回归或价值预测也是如此。我们对给定的 3 个模型的预测进行加权*均,以得出我们的最终输出。这减少了出错的机会并提高了准确性。
有趣的是,我们也不需要在 3 个型号上花费资源。我们可以用不同批次的数据在同一个模型上训练 3 次。这也能达到目的。但是你明白了,不是吗?
辍学:
辍学也被归类为一个整体方法的类别。但是我,为了好玩,认为它是相反的。在集合方法中,你“询问”其他模型的意见以得出结论,但在这里,它基本上是让其他贡献者保持沉默。让我说清楚。
这是一个非常简单的神经网络,其目的是作为一个真/假分类器。查看输出图层(绿色)。它有两个 blobs,一个给出输出为真的概率,另一个给出输出为假的概率。两个值之和:你猜对了:1!你不聪明吗?XD。
这里的想法是让你明白这些“斑点”被称为节点。每个节点内部都有大量复杂的计算。还记得我在《L2 正规化》中提到的东西吗?一切都发生在这里。所以这些节点是输出的实际贡献者。
退出包括随机关闭某些节点。这改变了模型的体系结构和信息在节点间流动的方式。这样做可以使模型成为更可靠的预测器。该模型必须预测相同的输出,其中一些贡献者被关闭。这就像说你需要在没有你的顶级朋友在身边的情况下完成你的测验。你得学会。明白了吗?XD。
结论:
这总结了我关于正则化技术的博客。我故意没有向您提供关于批处理规范化的信息,因为这将需要我向您提供训练神经网络的整个过程,并且这将违背本博客背后的主要思想:保持事情简单。
如果您想知道如何使用 PyTorch 在 Python 上编写这些代码,请参考 GitHub 上的以下资源库。batchnorm_dropout.ipynb 文件可能会有帮助。我也将在另一个回购上上传 TensorFlow 文件,以便在这两个框架上都有代码。
https://github.com/sreyan-ghosh/pytorch_files
我花了很长时间为你们写下这些,我希望你们能从中有所收获。如果你喜欢,留下掌声。如果你没有,你可能会离开这一页很久了。如果你有任何疑问,请在下面留言。我期待着消除你的疑虑。
我喜欢交新朋友,所以这是我的 LinkedIn ID。如果你想聊天或者不想,请连接。XD。
https://www.linkedin.com/in/sreyan-ghosh-b0722a18b/
理解强化学习实践:马尔可夫决策过程
描述和理解复杂的环境,一次一张图
蒂姆·福斯特在 Unsplash 上的照片
“系列”链接:
欢迎回到我们的强化学习系列。这是第四篇文章,所以我们鼓励你回头看看前面的文章。之前,我们谈到了一个非常基本的情况,叫做多臂强盗场景,这让我们开始思考如何通过互动来学习。
今天,我们将开始探索新的环境,使用一个非常强大的工具,马尔可夫决策过程(MDPs)。我们将为描述环境打下坚实的基础,并建立一些术语来帮助我们更深入地研究 RL。
多臂匪徒的陷阱:
我们已经谈了很多关于多支武装匪徒的情况,并发展了强有力的想法和策略来对付他们。然而,我们周围的世界看起来并不像我们目前所呈现的那样简单。终于到了讨论为什么这种环境被认为是“基本”的时候了,以及我们如何描述更复杂的场景。
多支武装匪徒的场景向我们展示了一个单一的情况:站在许多武装匪徒面前,每支武装匪徒都是我们可以采取的独立行动。这种情况在互动后不会改变;我们仍然面临同样的情况和同样的行动。最复杂的变化是不稳定的情况,但我们的决定仍然只关注相同的情况,我们的行动不会影响未来的任何事情。
在现实世界中,我们总是会遇到新的情况,我们的行为会影响我们未来的定位。如果我们想要开发能够处理真实世界的代理,我们需要为环境做一个更健壮的定义。这里是马尔可夫决策过程来拯救!
马尔可夫决策过程:
为了描述和理解更复杂的场景,引入了 MDP(马尔可夫决策过程的缩写)。MDP 框架在一些基本概念下将环境形式化:状态、行动、奖励和过渡模型。
州
从前面的文章中,我们已经对奖励和行为有了一些了解,所以让我们从状态开始。状态可以理解为描述可以找到代理的情况的任何信息。例如,假设我们在一个赌场处理强盗场景,我们有 5 个行动可以采取。在一些互动之后,我们了解到第二只手会产生最高的回报。如果我们去另一个赌场,我们应该期待这是真的吗?当然不是!改变赌场改变了这种情况,所以我们现在可以说我们有两个州,每个赌场一个。这是到目前为止我们的问题的可视化表示。
有两个州可以找到我们的特工。作者图片
状态不受物理位置或对象的限制。让我们回到餐馆的情况。作为提醒,我们提供了一个相当于多臂强盗的例子,让一个人在餐馆里试图从多个选项中选择一道菜。在这里,如果我们考虑代理人是否饥饿,我们可以定义一个更复杂但更现实的场景。这将再次创建两种状态,一种是代理饥饿时的状态,另一种是代理不饥饿时的状态。
餐馆示例的两个新状态。作者图片
必须注意的一个重要警告是,状态必须包含代理采取行动所需的所有信息。例如,我们的代理人不记得它以前是满足还是饥饿,也不记得他以前是否选择了某道菜。如果这不是状态的一部分,那么我们就不会使用马尔可夫决策过程。这个约束是必需的,因为我们希望能够将每个状态视为一个孤立的情况。如果代理人发现自己处于这种状态,它应该能够采取行动,不管它是否是第一个被访问的国家。
这听起来可能是一个巨大的限制,也是一个不现实的限制,因为我们通常是根据我们以前的轨迹或决策路径来做决定的。例如,餐馆里的人或代理人可能记得他刚刚吃了某道菜,出于无聊不想重复自己。但是,也要考虑状态的数量和状态包含的信息是没有限制的。如果有必要,您可以创建保存以前信息的状态。例如:
状态列表,其中一些状态包含以前选择(或吃)的信息。作者图片
这里,我们根据代理在之前的交互中选择的内容来定义新的状态。这听起来像是一个黑客,但也离现实不远。你可以认为自己处于一种状态,在这种状态下,你处于当前的位置,你持有当前的信念和经历。改变你的信仰或经历会改变你目前的状态。
最后,请记住,尽管对您拥有的状态数量没有硬性限制,但是状态越多,我们的代理就越难学习。我们希望在我们的代理可以从环境(也就是州内)访问的信息量和环境拥有的状态数量之间取得良好的*衡。
行动
行动是我们的代理与环境交互的方式。代理可以采取的操作取决于它发现自己所处的状态,每个状态可以包含一组唯一的操作。让我们看看当我们用状态和动作来表示它时,多臂强盗是什么样子的。
3-有状态和行动的武装匪徒。作者图片
这里,我们用 3 个动作(0,1,2)来表示多臂强盗,每个动作都是一个连接到国家的黑点。任何其他状态都可以有不同数量的操作。在两个赌场的情况下,它可能看起来像这样:
多个赌场的场景,每个赌场都有自己的一套行动
现在,动作不仅允许我们与世界互动,还定义了我们如何从一种状态转移到另一种状态。到目前为止,我们的状态彼此完全隔离,但现在我们可以添加路径,以便在采取特定行动时改变状态。让我们以赌场为例,并修改它,以便我们可以看到如何行动可以推动我们通过国家。
两个赌场场景的几乎完整的 MDP。作者图片
现在,对于每一个动作,我们都有一个指向下一个状态的箭头。对于每个状态,我们有一些动作代表拉一只胳膊,还有另一个动作我们称之为“乘公共汽车”,它把代理从一个状态带到另一个状态。请注意,所有的动作都指向一个状态,但并不是所有的动作都将我们从一个状态转移到另一个状态。
奖励
奖励是代理在采取行动后获得的标量值,它可以被认为是代理在环境中做得好坏的度量。我们在选择一个行动后所走的每一条路都有一个相关的回报,它可以是积极的、消极的或零。代理的目标是通过在正确的状态下采取正确的行动来最大化这个值。让我们把它添加到上面的图表中
赌场场景的 MDP,现在有奖励。作者图片
在上图中,我们为每条路径添加了一个奖励,用每个箭头旁边的数字表示。例如,乘坐公共汽车给予奖励 -2 ,以表示从一个州到另一个州是有成本的。每只手臂也有固定的奖励,虽然我们也可以像最初的多臂强盗一样使用随机分配。
奖励的概念很简单,但是给环境分配奖励是强化学习中最困难和最关键的任务之一。由于奖励塑造了我们的代理人的行为,奖励和潜在目标之间的任何不一致都会导致意想不到的行为,以及奖励黑客行为(也称为规范游戏),即代理人利用环境的一些机制来获得高奖励,而不必完成手头的任务。如果你感兴趣的话,DeepMind 安全研究有一篇很棒的文章,介绍了一些规范游戏的案例。我们稍后将讨论奖励系统或奖励函数,但现在请记住,这不是一个微不足道的赋值问题。
过渡模型
使用之前建立的工具,我们已经可以构建完全描述多个场景的 MDP。最后展示的图表可以被完美地认为是一个完整的 MDP。但是,到目前为止,环境的机制是决定性的。采取行动将总是带你走过同样的路,得到同样多的回报。在许多情况下,情况并非如此。有时,一个主体试图采取行动,希望达到一个特定的状态,因为环境会因不可预见或未知的变量而随机做出反应。例如,假设我们的代理试图“乘公共汽车”去另一个赌场。在真实生活场景中,在采取这样的行动时可能没有公共汽车,或者公共汽车可能是满的。所以,有可能采取这样的行动不会让你达到想要的状态。
为此,我们使用转换模型。这个模型代表了环境对代理交互的响应方式,并决定了从一种状态到另一种状态的可能性。现在,让我们用转换模型表示的总线问题来绘制我们的 MDP。
赌场环境中的完全随机 MDP。作者图片
在这里,我们做了一个小小的改变。现在乘坐公交车的行为有两条相关的路径。一个指向下一个状态,而另一个将代理返回到当前状态。另外,请注意,现在我们在每条路径上都有两个数字。这两个数字分别代表走这条路的可能性和回报。因此,在这种情况下,我们最有可能从赌场 2 的州,而不是从赌场 1 赶上公共汽车。
过渡模型通常被表示为一个名为 p 的函数,最常见的是这样写的:
转换模型方程。作者图片
其中s’是下一个状态, r 是奖励, s 是当前状态,是当前状态下采取的动作。这个函数返回到达下一个状态(s ')并获得期望奖励(r)的概率,假设我们当前处于某个状态(s)并采取了指定的行动(a)。例如,使用我们到目前为止开发的 MDP,我们将从转换模型中获得:
使用转移函数的特定转移及其概率。作者图片
在这里,我们说的是到达赌场 2 (C2)并获得 -2 奖励的概率是赌场 1 和乘车的 0.7 或 70%。另一个例子是:
概率为 0 的转移。作者图片
这里我们问的是同样的情况,但是我们没有乘公共汽车,而是拉手臂 1。现在,转换模型说我们没有机会到达赌场 2,因为我们选择的行动不会以任何方式将我们带到那里。您可以将转换模型视为游戏规则、物理引擎或现实世界机制的准确表示。可悲的是,在大多数情况下,我们无法获得这样一个精确的模型,这使得学习变得复杂。我们稍后将开发能够处理转换模型不可用的环境的策略。
作为一个旁注,我想让你,读者,试着用上面的图表找出在赌场环境中采取的最佳行动。有了这个,如果你发现自己在第一个赌场,最好的行动是什么?如果你只考虑眼前的回报,你会选择这条路吗?这应该让你知道我们的代理必须能够考虑采取最佳行动。
健身房的实施
最后,这里有一个使用 gym 接口的上述 MDP 的实现。为了使文章简短,我们不打算在这里讨论实现的细节,而是在附带的笔记本中讨论。如果你想知道我们是如何将 MDP 图转变成一个可以用于强化学习的完整的工作环境的,我们强烈建议你看看它。话虽如此,下面是完工的环境。
使用这个实现,以及一个在其上随机选择动作的代理,我们获得了这样的交互:
赌场环境和随机代理之间的互动链的图像。作者图片
应该可以看出,MDP 和由此产生的相互作用是相容的。
结论
在本文中,我们提出了一个描述场景的框架,称为马尔可夫决策过程。这个框架允许我们想象一个代理如何使用动作遍历不同的状态,以及每次交互会得到什么样的回报。从那以后,我们开发了一个多臂强盗的变体,在这个变体中,我们现在有两种状态,以及从一种状态到另一种状态的转换动作。最后,我们展示了如何使用 gym 环境实现这样的 MDP。
将问题转化为 MDP 是在现实世界中应用强化学习的第一步,因此鼓励你尝试并思考如何描述你作为 MDP 人在日常生活中所做的不同事情。你可能会惊讶于你可以正式描述的事物的数量。接下来的几篇文章将涉及贝尔曼方程,该方程基于马尔可夫决策过程,以定义代理如何理解环境并对其采取行动。那里涵盖的原则也可以应用到我们的日常生活中,所以即使你对开发 RL 代理不感兴趣,它仍然是一本有趣的读物。到时候见!
“系列”链接:
参考资料:
理解强化学习实践:简介
哈桑·帕夏在 Unsplash 上的照片
“系列”链接:
欢迎光临!这是我将要做的关于强化学习的一系列文章中的第一篇,在这一系列文章中,我们将开发这一人工智能分支背后的核心概念,并涵盖多种技术,以在不同的任务中制定、开发和应用 RL(强化学习的缩写)。我这个系列的目标是记录我在这个主题上的学习过程,并希望为任何阅读它的人提供理解这个研究领域的直观和实用的指导。
这个系列受到 Coursera 的强化学习专业化,以及 Richard Stutton 和 Andrew G .巴尔托的书强化学习:导论(第二版)的巨大影响。如果有兴趣加深您对该主题的了解,我强烈推荐您深入研究这些资源。我在理解强化学习方面失败了很多次,直到我偶然发现了它们。我希望这一系列文章能作为一个额外的资源来加强你的理解。
每篇文章都将涵盖这样的理论和应用。我已经基于专业化提供的笔记本创建了自己的编码资源,使用了更多可用的工具,如 OpenAI 的 gym,而不是 RL-glue,因为我找不到有用的文档。解决了这个问题,让我们开始研究这个系列吧!
什么是强化学习?我们为什么需要它?
随着目前在该领域中发现的所有不同的机器学习领域,问我们自己这些问题是很重要的。看到像监督和非监督学习这样的范例在过去(和现在)工作得多么好,一些人可能会认为强化学习只是一种徒劳的学术追求。其他人可能认为 RL 只能用来炫耀我们玩游戏有多烂。但是,RL 超越了这一点,显然值得与其他研究领域区分开来。
理解强化学习的一个好方法是将其与机器学习的其他框架进行比较。RL 认为学习是一种非常特殊的方式。与监督和非监督学习一样,强化学习依赖于优化数值来实现期望的结果/行为。区别在于算法如何获得这些值。
在监督学习任务中,我们得到了一个包含采样数据的数据集以及它的含义。例如,手写数字及其字母数字表示的图像。该算法的目标是将视觉数据与期望的字符相关联。以这种方式,监督学习可以理解为由该领域的专家教授。问题的答案已经给出,我们使用这些信息将数据映射到结果。正如我们将通过查看分数(我们总共获得了多少个好答案)来衡量我们在测验中的表现一样,监督学习模型将通过查看算法预测的结果与其应该预测的结果之间的误差或差异来衡量其表现。使误差最小化是让我们的模型学习的原因。
图一。监督任务的示例。MNIST 数据集。使用 torchvision 和 matplotlib 生成的图像
在无监督学习任务中,我们得到一个包含采样数据的数据集,我们的目标是找到数据中的模式或隐藏结构。与监督学习不同,我们没有得到任何答案,也不知道从这样的分析中会得到什么样的答案。该模型的设计目的是区分数据中的不同关系,以便我们以后可以在其他任务中使用这些关系。这类模型试图优化的价值很大程度上取决于我们试图从数据中获得什么样的信息。例如,自动编码器会通过算法传递数据,希望这种模型的输出与输入非常相似。在这种情况下,我们测量输入和输出之间的差异,并试图将其最小化。
图二。基于 MNIST 数据训练的自动编码器的潜在空间的可视化。
与前面提到的方法相反,强化学习没有数据集可使用。!).相反,数据是通过与环境交互而生成的。从我们的互动中,我们得到了“奖励”,它指出了我们做得好坏。在一系列长时间的互动后(比如在国际象棋比赛中获胜),我们可以获得有意义的奖励,我们的互动可以极大地改变未来的结果(国际象棋中失败的错误可以一直追踪到比赛开始)。模型的任务是找到与环境互动的正确方式,从而使其获得的回报金额最大化。
图 3。A2C 强化学习代理玩突围。
这里明确的区分是“交互”。强化学习问题通常需要您通过采取行动来生成数据,同时根据您拥有的数据采取行动。这非常重要,因为我们的目标是得到一个尽可能表现最佳的代理。让我们假设一下,我们想要尝试训练一个模型使用强化学习之外的东西来玩突围。为了让我们能够做这样的尝试,我们需要获得一个健壮的数据集,我们的代理可以从这个数据集学习行动。这已经是一项艰巨的任务了!例如,请看下图:
图 4。越狱游戏中的一个特殊状态。
桨接下来应该采取什么动作?球可能向球拍移动,也可能远离球拍。另外,这是我们代理的最佳位置吗?或者有更好的方法来摧毁更多的砖块?为这项任务获得好的数据集的困难不仅是技术上的,也是理论上的。无论我们决定以何种方式收集数据,我们都假设数据显示了完成任务的最佳方式。如果我们用人类的表现作为参考点,那么我们的代理人将永远做得和人类竞争者一样好。在这样的限制下,我们永远不会看到人工智能在这些任务中击败人类的新闻。
强化学习本质上是一种在不断变化的世界中进行交互学习的范式。在我们的模型需要采取行动的情况下,这种行动改变了手头的问题,那么强化学习是实现目标的最佳途径(也就是说,如果要使用一种学习方法)。RL 代理从交互中学习的能力产生了获得超人性能的潜力,无论是在我们真正擅长还是真正不擅长的任务上。我相信,这使得强化学习值得努力。现在,这个领域通常被视为在视频游戏中产生巨大的结果,但在未来,我们可以看到 RL 代理优化真实世界的问题,这些问题在历史上一直困扰着我们!强化学习确实很有前途。
结论
这是对什么是强化学习,它与其他学习框架有何不同,以及为什么我们应该研究它的快速介绍。强化学习是一个非常有趣的领域,我希望这篇文章给您留下了这样的印象。在下一篇文章中,我们将深入探讨武装匪徒的问题,这是一个基本的场景,展示了所有强化学习方法背后的核心思想和复杂性。
“系列”链接:
参考资料:
- 没有引用的图像由作者生成。
- 图二。摘自知识共享署名 4.0 许可下的 Tensorflow 教程。
- 图 3。取自麻省理工学院许可下的稳定基线文档。
- 图 4。取自麻省理工学院许可下的稳定基线文件。
- Coursera 的强化学习专业化由阿尔伯塔大学提供。
- 萨顿和巴尔托(2018 年)。 强化学习:入门 。剑桥(麻省。):麻省理工学院出版社。取自斯坦福档案馆。
理解强化学习实践:多臂强盗
马库斯·斯皮斯克在 Unsplash 上的照片
“系列”链接:
这是强化学习系列的第二篇文章,我们在其中探索和发展了在交互式场景中学习背后的思想。在之前的文章中,我们介绍了什么是强化学习,它与其他学习框架有何不同,以及为什么它值得关注。在本文中,我们将探索一个最基本的场景,从中我们可以开始构建强化学习代理如何从环境中学习和行动的理论。
在本文中,我们将涵盖理论和代码。这里将展示一些代码片段,但是我强烈建议大家跟随我为这篇文章准备的笔记本一起阅读。
多臂强盗场景
我们发现自己身处赌场,希望策略和运气都能给我们带来丰厚的利润。在这个赌场里,我们可以从已知数量的武装匪徒那里试试运气。所有盗匪的行为都是随机的,但*均下来,他们每个人都会返回指定的利润。一些强盗有更高的回报,因此必然有一个强盗会给比其他强盗更高或相等的回报。你怎么去找这样的武装匪徒?假设我们想最大化我们的利润(因此也最小化我们的损失),最好的策略是什么?
武装匪徒或者赌场里的老丨虎丨机。你会选择哪一个?Benoit Dare 在 Unsplash 上的照片
让我们从创建这样的场景开始。我们将使用 OpenAI 的健身房来建立一个行为类似于上面解释的赌场的环境。
多武装匪徒环境的实现。
从上面的实现中,我们可以创建一个环境,在这个环境中,我们可以定义我们的赌场拥有的强盗数量,以及他们的*均利润和与*均值的偏差。每当我们与环境交互时,我们都必须指定我们选择拉哪个土匪,并且环境为那个行动返回一个奖励。这里有一个小例子:
互动的结果。我们和所有的强盗一起行动,看看他们的报酬是什么。
从上面的例子中,我们创建了四个强盗,其中第一个强盗的*均利润最高为 5,而最后一个强盗的*均利润最低为-10。我们与每一个强盗互动,看看它给了我们什么奖励。奖励是随机的,但是他们接*每个强盗的*均水*。偏差决定了每个强盗的随机奖励有多远。请注意,第二个 bandit (Bandit 1)的偏差非常小,因此随机值非常接**均值。第三个 bandit (Bandit 2)具有更高的标准差,这意味着我们可以找到与*均值相差很远的值。
在这种情况下,随机性的影响至关重要。有时我们很幸运,在一个强盗身上获得很高的价值,但有时我们就没那么幸运了。与每个强盗互动一次并不能告诉我们这个强盗总体上有多好。有时,一个*均水*低但方差高的强盗可能会给我们带来幸运的头奖,但其他强盗可能会给我们一个整体更好的结果。
这是一个非常熟悉的情况。即使你不喜欢赌博,你也会发现自己不得不一直做这样的决定。无论何时你在餐馆,你必须决定你想要什么菜。你应该去吃那道你已经尝过并且知道你喜欢的菜吗?还是会用另一道菜来一炮?你有机会找到新的最爱,或者后悔没有选择更安全的食物。这就是所谓的探索-开发困境。
探索与开发的困境
每当代理想要优化与未知(甚至部分已知)环境的交互时,就会出现这种困境。代理人希望尽可能采取最优行动,但不知道哪种行动是最优的,因为他不完全知道哪种行动对他最有利。最佳行动的唯一方法是采取次优行动,以充分了解环境。通过探索,我们更多地了解了我们的环境,但牺牲了获得已知回报的机会。通过开发,我们利用现有的知识来获得最佳结果,但是由于知道得不够多,我们可能会采取次优的行动。这种困境几乎存在于我们在强化学习中遇到的每一个现实生活中。制定良好的应对策略至关重要,这正是我们在这里要做的。
评估我们的行动
在我们开始提出策略之前,我们需要知道如何评估我们的行动。看待这个问题的一个好方法是看我们如何评价一家餐馆的菜肴。
每次我们得到一道菜,我们都会评估我们的选择有多好。好吃吗?我对食物有多满意?对于每一个盘子,我们赋予一些值来表示这个选择对我们有多好。每次我们选择这道菜时,这个值都会更新。我们经常选择的菜肴最终会达到*均满意度。
这也是代理评估其行为的方式。在我们的场景中,对于我们可以从多臂强盗那里得到的每一只手臂,我们都有一个我们期望从这样的行动中得到的价值。起初,我们什么都不知道,因此也不期待什么,但是每次我们采取行动,我们都会更新我们的期望。
例如,假设我们拉第一只手臂 5 次。对于我们的行为,我们收到了这份奖励清单。
拉第一臂 5 次获得的奖励列表
因此,该臂的预期值是我们到目前为止看到的所有值的*均值。
根据以前的知识计算拉第一臂的期望值。这是一个简单的*均数
因此,根据我们之前的互动,我们期望获得奖励 4.954 。如果我们再拉一次手臂,我们需要再次计算*均值。如果我们必须为所有可能的行动存储所有以前看到的奖励,这可能会变得乏味和不切实际。通过将*均函数重新表述为更新规则,我们可以做得更好。经过一些在周围扭来扭去,我们得到:
增量*均更新规则
在英语中,这意味着每当我们想要更新一个行为的值时,我们就取我们之前的期望值,加上我们期望的回报和我们获得的回报之间的差,除以我们实际采取该行为的次数。例如,如果我们第六次拉第一臂,获得奖励 5.18 ,那么我们可以计算我们的新期望值为:
使用*均更新规则获得的新期望值
我们会为所有可能的行动这样做。请注意,随着我们采取行动的次数增加,更新规则将最终收敛。这是因为随着 n 变大,数值变化越来越小。
贪婪的代理人
我们的第一个策略将完全集中在开发上。贪婪的代理人总是会根据其现有的知识选择最佳行动。也就是说,代理将总是选择具有当前最大期望值的动作。例如,如果这是我们的代理为每个操作计算的值列表,那么它将选择操作 1:
显示每项行动的预期值的表格。
这在数学上可以表示为取期望值的 argmax 。如果两个或更多的动作被认为是最佳的(也就是说,它们具有相同的值,并且该值是最大的整体动作),那么代理在这些动作中随机选择。例如,这里它会随机选择动作 1 或动作 3:
显示每项行动的预期值的表格。行动 1 和 3 被认为是最好的。
这个行为对于 argmax 函数来说并不是默认的,所以我们需要自己编写一个可以任意中断联系的函数。
Argmax 任意中断连接
现在,我们需要做的就是实现我们贪婪的代理。为此,我们需要指定代理如何采取行动,以及代理如何从交互中学习。如上所述,代理基于哪个动作产生最大期望值来采取动作。另一方面,它通过使用增量*均更新规则更新每个动作的值来学习。
贪婪代理的实现
在我们进行任何测试之前,我们希望我们的代理在这项任务中表现如何?起初,我们的代理人对这个世界一无所知,因此期望每个动作都是一样的。在采取第一个行动后,有两种情况可能发生:代理人要么赔钱(负回报),要么获利(正回报)。如果代理人赔钱,那么它会认为采取的行动是一个不好的行动,并选择另一个。如果在后一种情况下,它赢了钱,对这种行动的期望增加,并成为所有行动中最大的行动。这意味着,它将继续采取行动,除非他失去足够的钱来看起来不好的行动。
回到我们餐厅的例子,一个贪婪的顾客试了一道菜,如果他喜欢,他一辈子只会点那道菜。只有当顾客有足够的不愉快经历不喜欢这道菜时,他才会尝试另一道菜。
你认为这是获得最佳结果的好策略吗?我们的代理肯定不会做得很差,但也不会做得最好。对我们的代理人来说,能找到最好的行动,而不是另一个看起来不错的行动,这将是非常幸运的。让我们来测试一下!
代理和环境之间交互的简化实现
上面的代码实现了代理和环境之间的交互。代理有 1000 次尝试来获得尽可能多的奖励。让我们看看这种互动是如何进行的。下面你会发现 6 个不同的代理与 6 个不同的环境互动。该环境由代理可以采取的 8 个单独行动(或武装匪徒)组成。红点表示每个操作的真实期望值,而蓝条表示代理的估计值。
6 个不同的贪婪代理与多臂土匪问题互动。有 8 个不同的动作
如你所见,探索不是我们代理人的主要目标。只有当所有其他行为看起来比它更糟糕时,代理人才会探索新的行为。有时候,我们的代理很幸运,并降落在最高价值的行动(如第五代理),但大多数时候,它最终在一个次优的行动。事实上,有时我们的代理是如此不幸,当它与正确的选择进行交互时,它收到了一个糟糕的结果!看看第四个代理,它与动作 0 交互,动作 0 是最好的动作,但从中得到了损失。在那之后,我们的代理人就没有机会再尝试那个行动了,即使它会比其他任何行动带来更高的*均回报!
我们刚刚看到一些代理商使用贪婪策略,这看起来并不好。但整体表现如何?我们实际上可以做很多实验,观察*均行为。
贪婪策略的*均最优性。只会在 40%的情况下落在最佳动作上。
上图显示了 10,000 多次单个实验的*均值,即代理采取最佳行动(即,对环境的最佳行动)的次数百分比。上面的图表明,*均来说,40%的时候我们的贪婪策略会让我们采取最优行动。我们还可以得出结论,经过几次尝试,代理人并没有得到任何改善。只有前十几次尝试对我们的代理人来说很重要,因为之后通常会坚持一次行动。显然,纯粹的开发技术远非最佳。如果我们引入一些探索呢?
ɛ-greedy 代理
这个新策略给动作选择过程增加了一点随机性。一般来说,代理的行为就像上面看到的贪婪代理一样。但是,每隔一段时间,它会随机选择一个动作。我们的代理人采取随机行动的概率是由一个叫做ε(ɛ).)的新参数决定的因此,ε是 0 和 1 之间的值。
其工作方式是,每当我们的代理想要采取行动时,它就“掷骰子”,并根据结果决定是贪婪地行动还是随机地行动。例如,如果ɛ被分配到 1/6,那么这就等于说,每当代理人在骰子上掷出 1,它就会随机行动。这是我们代理的新实现:
ɛ-greedy 代理的实现。
我们实际上是基于贪婪实现的代理,只是改变了代理如何选择要采取的动作。
现在我们的代理将允许一些勘探,但是勘探的数量是基于我们分配给ɛ.的数量如果ε是 0,那么我们的代理将充当贪婪的实现。另一方面,如果ε是 1,那么我们的代理只不过是一个随机机器。现在,我们将使用ε值 0.1,但稍后我们将探索其他值。因此,我们的代理人在 90%的时间里会贪婪地行动,而探索性的时间只有 10%。
让我们来看看这一战略的实施情况:
事情开始有希望了!在上面几乎所有的例子中,我们的代理都能找到最佳行动。事实上,如果有足够的时间,我们的代理最终会为所有的行为找到一个好的估计值。注意蓝色条现在如何显示更接*红点的值?我们的代理现在可以足够自信地知道哪个动作是最佳的。同样,让我们看看这个策略*均起来是什么样的:
ɛ-greedy 代理与贪婪代理的最优性
与贪婪策略相比,ɛ-greedy 代理做得更好!经过 1000 步之后,ɛ-greedy 策略在 80%的情况下都能接*选择正确的行动。此外,我们看到这种策略如何随着时间的推移增加其最优性,这意味着它总是从互动中学习。
那么,我们应该为ɛ选择什么样的价值观呢?好吧,让我们测试ε的多个值,看看它们*均获得多少奖励:
这里,我们看到在这种情况下ε的最佳值是 0.1。原因是因为它足够小,让我们的代理人大多表现贪婪,因此优先考虑高回报,但又足够大,让我们的代理人快速探索环境。更高的ε值将开始妨碍我们的代理人,因为时间探索是不能获得高回报的时间。较低的值意味着我们的代理将需要更长的时间来了解环境,这意味着它可能需要很长时间才能找到最佳行动。
包扎
到目前为止,我们已经呈现了一种在最佳行动和探索性行动之间进退两难的局面。我们已经看到这种困境如何极大地影响我们学习和与环境互动的方式。基于这种情况,我们定义了一些方法,让我们的代理可以评估它的行为,并从环境中学习。本文揭示并比较了解决勘探开发困境的两种策略,即贪婪代理和ɛ-Greedy 代理。这些策略背后的思想将贯穿我们的强化学习之旅,理解它们背后的概念,以及它们的优点和缺点将在以后为我们服务。
在下一篇文章中,我们将探索当我们的策略用于非稳定环境时会发生什么。也就是说,当从我们的行为中获得的*均回报不是静态的,而是在我们的代理人与之互动时发生轻微变化。因此,我们将提出一些修改,可以使我们的代理在这种情况下表现得更好。如果你走到了这一步,给自己一个鼓励。这不是一个容易的阅读,我希望你在这个过程中学到了一些东西。
“系列”链接:
参考资料:
理解强化学习实践:非静态
探索动态世界以及如何应对它们
乔纳森·彼得森在 Unsplash 上拍摄的照片
“系列”链接:
欢迎来到强化学习系列的第三篇文章。在上一篇文章中,我们探索了我们将要应对的一系列场景中的第一个,多臂匪徒。在这种情况下,我们面对的是一个有固定数量行动的环境,我们的任务是找到能产生最大回报的行动。我们提出了一些策略,并测量了它们在这个简单任务中的表现。
在本文中,我们将修改之前呈现的环境,使其更具动态性。我们将看到我们以前的策略如何处理非稳定环境,以及我们如何能做得更好。
静止与非静止:
上次我们在一个赌场开始了我们的故事,那里到处都是供我们支配的强盗。利用这个例子,我们建立了一个简化的环境,并制定了一个强有力的策略来获得高回报,即 ɛ-greedy 代理。使用这种策略,我们能够在足够的时间内找到最佳行动,并因此获得大量奖励。我们的代理表现得很好,因为它在探索环境和利用其知识之间取得了良好的*衡。这种*衡使代理人能够了解环境的行为方式,同时也能获得高回报。但是,有一个小的假设,我们的代理正在做的能够表现得如此最佳,那就是环境是静态的,不变的。我们这么说是什么意思,我们的代理人在哪里做出这样的假设?
静止的
当我们提到“静止”这个词时,我们谈论的是我们环境的潜在行为。如果你还记得上节课,环境被定义为有一组行动,在互动中,产生随机的回报。即使回报是随机的,它们也是由每个行为的中心值或*均值产生的,我们称之为真实值。为了理解我所说的,让我们来看看我们在上一篇文章中看到的一个动画交互。
ɛ-greedy 代理与静态环境交互的示例。作者图片
观察真实值(红点)是如何静态的。尽管环境会产生随机的回报,但是每个行为都有一个真实的期望值,这个期望值永远不会改变。这是一个很大的假设,而且几乎没有有价值的真实世界场景会随之而来。例如,如果我们的赌场类比是静态的,那么赌场将很快倒闭!那么,我们如何在不使问题变得更复杂的情况下描绘出一个更现实的场景呢?
非*稳的
让多臂强盗场景变得不稳定实际上很简单,我们需要做的就是确保我们所有行动的期望值总是变化的。现在,这种变化不应该是完全不稳定的,因为没有任何策略能够处理完全随机的情况。相反,我们希望期望值慢慢远离。这是为了让我们的代理仍然可以考虑以前的经验对未来的决策有意义。下面是我们如何实现这样的环境
有一些微妙的变化。现在,每次我们的代理与环境交互时,每个动作的真实值都会发生微小的随机变化。当我们创造环境时,可以控制它的变化程度。这是为了我们以后可以用它做实验。现在环境表现如何?我们可以像以前一样进行同样的可视化,但是使用我们的新环境。因为我们对代理不感兴趣,所以我们不打算绘制它的交互。
不稳定的环境。每个动作的值随机变化一定的量。作者图片
为了便于说明,前面的图是用大量的非*稳数据生成的。请注意,在某些情况下,最高值的动作会因随机移动而改变。因为我们感兴趣的是一个行为尽可能优化的代理,所以我们希望代理能够观察到这种变化并相应地采取行动。
当前性能
声明了我们的新环境之后,让我们看看ɛ-greedy 代理在这里的表现。知道我们的代理有探索的空间,代理注意到环境改变应该只是时间问题。
ɛ-greedy 智能体与非*稳环境的相互作用。作者图片
为了使可视化更加清晰,我添加了一些颜色来显示哪个动作具有最大值(橙色点)以及代理认为哪个动作最好(蓝色条)。对于这个动画,经过的时间延长到 800 步。起初,代理能够迅速适应环境的变化,但过一会儿,估计值停滞不前,往往移动缓慢。这使得很难赶上未来的变化,并导致代理人停留在次优行动更长时间。像往常一样,让我们通过*均许多实验的性能来绘制这种策略的最优性。
ɛ-greedy 代理在非稳定环境中的一般性能。作者图片
正如我们所看到的,起初我们的代理很快适应了环境,但是随着时间的推移,它的性能开始下降。尽管我们的代理总是在收集经验,并且总是在探索环境,但它似乎不能处理一个动态的世界。我们如何确保我们的代理在未来适应更大的变化?
更新规则问题
回到上一篇文章,我们展示了一种方法,在这种方法中,我们可以很容易地评估对多武器土匪场景采取行动的价值。这个评估是使用一个基本*均值来完成的,这个*均值在给定的时间后会收敛到预期的行动值。如果你曾经处理过取*均值,你应该知道你加起来的项目越多,结果就越稳健。假设我们想要取三个值列表的*均值:
要*均的小数值列表。作者图片
那么*均值将等于 4 。给定这样一个小列表,任何输入的变化都会使结果*均值发生一定程度的变化。
l 的*均值,修改了最后一个值。作者图片
如果我们使用更大的值列表,那么输入中的相同变化将导致输出中的较小变化。
更大列表的*均值,输入的变化量相同。作者图片
总而言之,我们计算*均值的信息越多,结果就越不容易出现异常值或偏差。在我们之前实现的增量*均更新规则中也可以看到这种影响:
增量*均更新规则。作者图片
1/n 的表达式导致相同的效果,假定随着 n 变得更大,,那么 Q 值变化越来越小。这就是导致我们的代理停滞不前的原因。一旦积累了足够的经验,就很难让我们的代理人改变主意。要解决这个问题,我们必须修改更新规则,这样以后的体验才不会被丢弃或忽略。
一般更新规则
那些通常对机器学习有一些经验或知识的人可能已经看到了上述更新规则背后的模式。如果你没有,让我简单解释一下:
通常,对于要学习的机器学习模型,我们使用一种称为误差或损失函数的度量。这种测量确定了我们的模型的性能与预期结果相比有多差。为了改进我们的模型,我们通过逆着误差移动参数来更新它的参数。我们应该移动或修改我们的参数多少?那是由学习率决定的。下面是上述语句的一个过于简化的演示。
梯度下降更新规则的过于简单的例子。作者图片
看起来很眼熟,对吧? h(x) 代表我们模型的输出,而 α 代表学习率。这是梯度下降的粗略简化版本,梯度下降是大多数机器学习任务中改进模型的标准方法。如果对这个话题感兴趣,我觉得这是一篇很棒的关于梯度下降的文章。
对比增量*均更新规则和梯度下降,我们的直觉会说 1/n 等价于学习率 α ,那就对了。因此,我们可以将我们使用的更新规则修改为更通用的版本。
通用更新规则。作者图片
这是做什么的?好了,现在我们可以将 α 定义为除了 1/n 之外的任何值。使用常数作为学习速率是其他机器学习范例中使用的一种简单而有效的方法,因此我们也可以在这里尝试一下。让我们实现我们的新代理!
我们的新实现基于ɛ-greedy 代理。有一些小的变化。首先,我们添加了一个名为 ss_func 的新参数,它允许我们根据需要更改步长函数。默认情况下,该函数返回值 0.1,这将是这里使用的常量步长。此外,在更新估计值时,我们执行 ss_func 函数,并使用返回值作为我们的步长。为了让事情更清楚,下一行代码相当于声明一个使用这个新实现的ɛ-greedy 代理。
像估计值和ε这样的变量以前已经被假定存在。
让我们看看这个新的代理如何在我们的动态多臂土匪场景中表现,与以前的ɛ-greedy 策略相比。
动态环境下ɛ-Greedy 和等步长ɛ-Greedy 策略的比较。图片作者。
就这样,我们的代理现在能够适应一个动态的世界!通过使用恒定的步长(或学习速率),代理将不再停滞不前。相反,每一个新的经历都是同等重要的,所以用新的知识去克服以前的经历是可能的。
步长参数:
最后,我想简要地了解一下这个新参数 α ,它的行为方式以及什么值对它是合理的。我们已经看到, 0.1 的值在这个场景中给出了很好的结果,但是它在其他值下会有什么表现呢?
步长参数可以被认为是对新知识的置信度的度量。α的高值类似于说我们相信我们最*的经验是手头问题的良好代表。因此,步长参数应介于 0 和 1 之间,其中 1 表示完全信任,0 表示不信任之前的交互对理解潜在问题的代表性。让我们看看这两个值是如何影响学习结果的。
置信度为 1 会将我们的更新规则变成这样:
更新学习率为 1 的规则。相当于将我们的估计值分配给最*收到的奖励。作者图片
在这里,我们基本上是说,我们的估计应该等于最*获得的回报,不考虑任何以前的经验。只有当我们知道(或者真的有信心)我们得到的回报是确定的、稳定的时,这才会起作用。由于现实世界中并非如此(因为交互通常是随机的,而不是随机的交互通常不需要学习算法),因此不应考虑步长值为 1。
置信度为 0 将取消更新规则的更新部分:
对我们最*互动的零信心会使我们先前的估计保持不变。作者图片
由于步长为零,我们排除了代理从经验中学习的可能性。因此,恒定的步长 0 对于强化学习来说是没有意义的。
至于 0 和 1 范围内的其他值,它们决定了我们的代理对方差的反应有多快或多慢,以及它的收敛速度有多快。接* 1 的值将快速更新估计值,并试图跟上环境的变化,但它也容易受到噪声的影响。另一方面,较小的值需要更长的时间来收敛,并且对动态环境的反应较慢。让我们比较一下这个场景的一些值。
动态环境下不同步长的多个代理的性能比较。图片作者。
正如预测的那样,极值不太适合这个问题,低值收敛较慢,高值收敛较快。与机器学习的其他领域不同,在其他领域中,学习速率或步长主要影响收敛时间和达到最佳结果的准确性,在强化学习中,步长与环境的动态程度密切相关。一个真正动态的世界(一个经常快速变化的世界)将要求我们的步长有很高的值,否则我们的代理将不能足够快地跟上世界的变化。
包裹
在本文中,我们介绍了非固定的概念,并将其实现到多臂强盗场景中。然后我们探索了我们之前的竞争者,ɛ-greedy 代理商,在这种新的情况下是如何表现的,并揭示了是什么使它的行为次优。然后,通过借用其他机器学习领域的一些概念,我们定义了一种评估我们行为的新方法。这种新的更新规则允许恒定的步长,这是解决非*稳问题的关键。最后,简要说明了步长参数如何影响学习结果。
本文旨在讨论几个主题,这些主题太长,无法添加到上一篇文章中,但是仍然有足够的价值,可以在本系列文章中介绍。通用更新规则将在以后发挥作用,因此必须涵盖它。有了这个,我们就可以告别多臂大盗,开始触及其他话题了。下一篇文章将讨论马尔可夫决策过程,并为著名的贝尔曼方程奠定基础。这两个概念是强化学习的支柱,理解它们可以真正改变对世界的看法。到时候见!
“系列”链接:
参考资料:
理解强化学习实践:贝尔曼方程第 1 部分
了解代理如何评估环境
Javier Allegue Barros 在 Unsplash 上拍摄的照片
“系列”链接:
欢迎来到强化学习系列的第五篇文章。在上一篇文章中,我们介绍了描述复杂环境的 MDP 框架。这允许我们为基本的多武器强盗问题创建一个更加健壮和多样化的场景,我们称之为赌场环境。然后,我们使用 OpenAI 的 gym 实现了这个场景,并制作了一个简单的代理,它随机地展示了在 MDP 框架下交互是如何实现的。
今天,我们将把注意力放回到代理人身上,并展示一种我们可以描述代理人在复杂场景中的行为的方法,在复杂场景中,过去的行为决定了未来的回报。这将通过提出贝尔曼方程来实现,该方程概括了理解代理如何在 MDPs 上行为所需的所有内容。本文的目标是提供推导贝尔曼方程的第一步,贝尔曼方程可以被认为是机器学习这一分支的基石。在接下来的文章中,我们将继续开发这个等式背后的直觉。这篇文章将重点放在理论上,但已经被淡化,使一切都有直观的感觉和意义。
一个特工看待世界的新方式
我们现在面临更复杂的环境。一个包含多种状态的世界,在这个世界中采取某些行动会导致不同的结果。我们的决定不再对我们周围的世界没有影响,目光短浅也不再一定会导致最佳解决方案。我们的特工不再处理简单的多武装匪徒的情况,我们必须适应他们,如果我们想让他们成功。
以前,我们开发了基于确定代理行为价值的策略。知道了每个行为的价值,代理人就可以通过选择价值最高的行为来优化自己的行为。这是可能的,因为采取的每一个动作都是相互独立的,并且无论代理选择什么动作,它总是面临相同的情况。然而,这一次,仅仅选择呈现给代理的最佳动作在将来可能是有害的。让我们看一个例子。
我们将探索生活在两个完全相同的环境中的两个不同的代理人。环境对我们来说是未知的,所以我们能够从中学习的唯一方法是通过观察这些代理所做的交互。我们将一步一步地跟踪他们,并试图确定哪一个表现得更好。
两个特工在同一个环境中迈出了第一步。作者图片
这里,两个代理都从我们称为 S0 的任意状态开始。第一个代理采取行动向上,而第二个向右。到目前为止,第二个代理似乎表现得更好,因为它获得了 +5 的奖励,而第一个代理获得了 +2 。让我们继续追踪。
两个特工开始了他们对环境的第二步。作者图片
现在第二名特工的情况看起来不太妙。尽管正确的行动在第一步看起来是最好的选择,但它会导致一个非常糟糕的状态,我们最终会失去一些之前获得的奖励。另一方面,第一个代理人没有选择眼前的最优行动,而是从长远来看得到了回报。显然,把我们的行为仅仅建立在行动的价值上并不是一个最佳策略。我们怎样才能做得更好?
评估状态
是时候揭示上述互动背后的环境了。这是一个描述所有状态、动作和奖励的 MDP。
MDP 对于前面例子的底层环境。作者图片
注意这个 MDP 后面真的只有一个选择,是在 S0 拍的。每一个其他的状态都为你指明了一条预定的道路。还要注意的是, S4 只有一个动作,既没有任何奖励也没有转换到另一个状态。这种状态被称为终端状态,因为它们表示没有进一步的交互改变代理的情况的地方。
现在假设您可以指定代理开始交互的位置。查看环境图,哪个州是放置我们的代理的最佳位置?哪个会是最糟糕的?很容易得出这样的结论:最糟糕的状态是 S1,因为从长远来看,我们最终会赚多少赔多少。另一方面, S2 似乎是一个很好的起点,因为我们最终会赚到+4。 S0
我们刚才所做的是查看每个状态,并累积如果我们从该状态开始将获得的奖励。我们只是给了 MDP 上的每个位置一个内在的值。我们可以通过绘制所有路径并显示累积奖励来使这一点更清楚。我们将一个状态的值 s 定义为 v(s) 。
每个州的轨迹和累积奖励的图表。作者图片
你注意到我们是从顶部开始的吗?如果您查看每条路径,您可能会注意到许多重复的计算。 S1 的值取决于 S3 的值,后者取决于 S2 的值,后者取决于 S4 的值。我们可以通过陈述每个状态的值取决于下一个状态的值,以及收到的直接奖励来简化这一点。这样,我们可以将每个状态的值定义为:
使用先前计算的值计算每个状态的值。
这个过程可以概括成一个基本公式:
其中 s 是当前状态,s’是下一个状态, r 是转换到s’获得的奖励。到目前为止,我们没有做任何不寻常的事情。我们只是给每个状态分配了一个值,表明如果一个代理从那里开始它的交互,我们期望得到多少奖励。有了这个,代理将能够评估它的行为,考虑未来的回报。
延迟奖励和无限互动
我们对价值的定义看起来不错,但仍有一些细节必须涵盖进来,以使其更加稳健。为此,我们将简要介绍其他 MDP,它们会指出我们当前定义的一些问题。别担心,我们稍后会回到前面的例子。
一个基本的 MDP,代理人只在最后一步得到奖励。作者图片
这里我们有一个新的 MDP,其中所有的转换都没有奖励,除了最后一个,我们得到 +5 。这是一个具有延迟回报的 MDP,因为代理人直到最后一刻才收到对其行为的反馈。看上面的图片,哪个州最适合我们的代理?我们的直觉告诉我们 S2 是最好的状态,因为它就在奖励旁边,但是每个状态的值说明了什么呢?
延迟奖励 MDP 的每个状态的值。作者图片
根据我们目前对价值的定义,除了 S3 之外的每一个州都同样有价值,这并没有错……但是为什么会有这种感觉呢?看来我们的代理人没有紧迫感,他不在乎现在或以后是否会收到报酬。让我们看看另一个 MDP:
一个有两个州无限期奖励特工的 MDP。作者图片
这里我们有一个 MDP,我们的代理人将从 S1 州开始。然后代理人可以选择去一个无限期奖励 +2 或者无限期奖励 +1 的状态。你会走哪条路?很明显,走每一步都有更高回报的路会更好。选择 S0 会给我双倍于 S2 的奖励。这似乎是显而易见的,但是价值函数又是怎么说的呢?
无限奖励的自我指向状态的价值。作者图片
这两种状态的价值是相等的,因为它们迟早都会达到无穷大。同样,我们看到我们的代理没有紧迫感,因为它假设它的交互可以无限延长。我们如何解决这个问题?
我们可以改变环境或互动来增强紧迫感。在延迟奖励 MDP 的例子中,我们可以使每一个转变都有一个小的负奖励,这将因此使代理尝试采取尽可能少的步骤来达到最后的状态。在无限相互作用的情况下,我们可以简单地通过强制实施代理可以采取的最大步骤数来将无限从等式中移除。如果代理只能运行 10 个交互,那么很明显哪个选择更好。但是,有一个简单的数学技巧可以解决这两个问题,而不必改变环境或限制其行为。
折扣系数
到目前为止,我们已经看到了代理评估环境的两种极端方式。第一个是着眼于眼前的回报,忽视任何未来的结果。第二个是考虑到所有可能获得的奖励,直到无穷远。在现实生活中,代理人在优先考虑现在和长远的回报之间有一个*衡。我们通常向前看到某一点,看得越远,就越不关心未来的结果。我们需要做什么改变来获得与我们的代理相同的行为?让我们再来看看我们的广义价值函数
我们在这里声明,一个状态的价值是由眼前的奖励 r 和未来的奖励定义的,它们是由v(s)捕获的。如果我们告诉我们的代理人少关心一点未来的回报会怎么样?这可以通过简单地简化这个术语来实现,如下所示:
这个小小的改变看似微不足道,却有着级联效应。让我们看看添加这个值如何改变我们的代理看待这两个有问题的例子的方式。
延迟奖励 MDP 及其各州的价值,折扣系数为 0.9。作者图片
看起来很有希望!我们离眼前的回报越远,这种状态就越不受重视。由于我们要求代理人减少对未来奖励的优先考虑,它显示了对即时奖励的偏好,同时仍然考虑未来事件。无限的 MDP 呢?
贴现因子为 0.9 的无限 MDP 及其各州的值。作者图片
没有必要去理解公式,但是要意识到,通过从价值函数中削弱未来的回报,即使是无限的互动也有一个确定的值,我们的直觉是正确的。现在, S0 价值更高(20),因为它产生的回报是 S2 (10)的两倍。
我们添加到公式中的这个数字被称为贴现因子(𝛾) ,可以是 0 到 1 之间的任何值。这就是广义价值函数加上贴现因子后的样子。
带折扣因子的价值函数。作者图片
这个修改之所以有效,是因为加入了贴现因子,把价值函数转化成了一个几何级数,在上一段建立的条件下总会收敛。
你可以把这个数字看作是代理人在做决定时目光短浅和目光远大程度的衡量标准。折扣因子为 0 意味着代理只考虑即时回报,而值为 1 意味着将其决策一直投射到无穷远处。这个值离 0 越*,它越不向前看。我们可以使用比较多个折扣因子的图表来大致了解代理人提前计划了多远。
根据所选折扣系数比较未来奖励相关性的图表。作者图片
这里我们画出了一个代理人对未来观察到的每个奖励的关心程度,这取决于所选择的贴现因子。例如,折扣系数 0.25 (红色)意味着代理人将展望未来 5 步。折扣系数为 0.75 (橙色)时,代理将提前计划大约 20 步。
包裹
在今天的文章中,我们开始分析代理如何使用价值函数评估复杂的环境。这个函数允许代理人给每个状态添加一个内在值,这个值代表代理人长期期望看到多少回报。我们还通过在价值函数中加入一个折扣因子来*衡代理提前计划的能力。这个折扣因子解决了一些在不同 MDP 上可能遇到的问题,比如延迟奖励和无限奖励累积。
到目前为止,我们只研究了简单的 MDP,其中代理基本上别无选择,只能遵循一系列状态,并且每个动作都有确定的结果。下一篇文章我们将考虑这些复杂因素,这将引导我们推导出完整的贝尔曼方程。我们还会回到 MDP 在这里提出的第一个例子,这样我们就可以解决它了。到时候见!
“系列”链接:
参考资料:
理解强化学习数学,面向开发人员
开发人员破译强化学习数学公式的温和方法
在 Unsplash 上由Tine ivani拍摄的照片
更新:学习和练习强化学习的最好方式是去http://rl-lab.com
如果你是一个数学知识不足的开发人员,你可能很难掌握强化学习的基本公式。
对于没有足够数学背景的人来说,理解这个等式可能很有挑战性。然而,除了神秘的符号之外,理解起来并不困难。
归结起来就是问一个问题:在 S 状态下有什么价值?
更具体地说,假设你在一个电视节目中,放在两扇门前,一扇门后面有 5000 美元,另一扇门有 1000 美元,要求你选择一扇门打开。
罗伯特·阿纳施在 Unsplash 上拍摄的照片
你不知道哪个是哪个,所以你有相同的概率打开其中任何一个。现在的问题是,出演这部电视剧值多少钱?答案相当简单。在最坏的情况下,它值 1000 美元(因为你可以赢得最少 1000 美元),在最好的情况下,它值 5000 美元(因为你可以赢得最多 5000 美元)。为了只有一个值,我们计算*均值为 3000 美元。
所以这种情况或状态的价值,就是你可以期望得到的(*均而言)未来的奖励或回报。
让我们稍微修改一下假设。你被告知左边的门包含 5000 美元,右边的门包含 1000 美元,但是要打开其中任何一扇门,你必须用球击中锁,有 20%的机会击中左边门的锁,有 80%的机会击中右边门的锁。
这种情况的价值是什么?
这将是未来的回报乘以获得它们的概率,所以:
.2 * 5000 + .8 * 1000 = 1800 美元
直观上,我们可以把状态 V(S)的价值想象成贴现的未来回报的总和,用得到它们的概率来加权。
需要贴现因子( γ) ,来源于你在遥远的未来得到的一个奖励,比你在下一步得到的奖励更没有价值,所以会按照达到它所需的步数来贴现,例如: γγ…。γr,其中 γ 的乘法次数等于达到奖励的步数。
数学细节可以在本文中找到强化学习背后的数学,简单易行的方法
下图显示了状态、行动、奖励和目标状态之间的关系。
状态 S 有 3 个可能的动作(a1,a2,a3)。每个行动将导致一个或多个目标状态(S1、S2、S3)和不同的奖励(r1、r2、r3、r4、r5、r6)。注意,从 s 到 S4 是不可达。每条边(或箭头)代表沿其路径的一种可能性。
你可以看到状态 S 有 3 种可能的行动,每一种都导致一种或多种不同概率和不同回报的状态。值得注意的是,州 S4 从 S 是不可到达的,但是如果采取行动 a1,S1 是可到达的,并且将导致 2 个可能的奖励。如果采取了动作 a2 或 a3,则可以从 S 到达州 S2。实际上 a2 肯定会到达 S2,而 a3 可能以不同的概率到达 S2 或 S3。
一个状态 s 的值 V(s),将是采取一个动作 a ,的概率乘以到达状态‘s’,乘以获得一个奖励的概率 r ,乘以期限(t)
但是因为在每个阶段我们可能有几个选项,意味着几个动作可供选择,这导致几个可能的状态‘s’和几个可能的奖励 r 。我们需要总结所有这些不同的选择。
初始公式可以改写如下。
翻译成代码
这个公式如何转化成代码相当简单。
每个 sum(σ)转换成一个循环,如下图所示。
其中 A 是所有动作的集合,S 是所有状态的集合,R 是所有可能的奖励的集合,
代码显示的内容对开发人员来说可能很奇怪。它在动作集合 A 中的所有动作、状态集合 S 中的所有状态以及奖励集合 r 中的所有奖励上循环。然而,我们知道,有时并非所有动作在所有状态下都可用,并且并非所有状态都可以从当前状态到达,当然,并非所有类型的奖励对于每个动作和每个状态都可用。
所以循环遍历所有这些集合纯粹是浪费精力和时间。
有一些方法可以优化代码以提高性能。
最佳化
看上面的图,很容易看出嵌套循环的数量。
从编程角度来说,可以进行一些优化。没有必要循环所有动作、所有状态和所有奖励。但是我们将只遍历可能的选项,忽略不可用的选项。
例如,并不是所有的动作集都出现在所有的状态上,所以我们不是在一个上循环,而是在该状态下可用的动作上循环,例如 state.getAvailableActions()。
同样,代替整个状态集 S,我们循环遍历从当前状态可达的状态,state.getAccessibleStates(),代替所有奖励 R,我们循环遍历与当前状态和所选动作相关的可能奖励,getRewards(state,a)。
下面的伪代码创建了一个描述状态行为的接口(IState)。这个接口将在下面的算法中使用。
interface IState {
// returns list of states accessible from the current state
function getAccessibleStates() // returns list of actions that can be performed in current state
function getAvailableActions()
// return probability of taking action 'a' in the current state
function getActionProbability(IAction a)// returns the probability of arriving at state s' when performing
// action a
function getStateProbability(IState s', IAction a)// get value of the state
function getValue()// set value to the state
function setValue(double v)}
类 R 表示在给定状态 s 和动作 a 的情况下返回奖励的实用程序类。函数 getReward(s,a)只返回一个奖励,而 getPossibleRewards(s,a)返回一个奖励列表。
函数 getrewardpability(r,s,a)返回在状态 s 和执行动作 a 时获得奖励 r 的概率。
在许多情况下,奖励系统很简单,也就是说每个州和每个行动只有一个奖励。所以 getReward(s,a)和 getPossibleRewards(s,a)返回相同的奖励,getrewardpability(r,s,a)返回 1。
class R {// returns the reward when at state s and performing action a static static function getReward(IState s, IAction a)// returns list of rewards when at state s and performing action a
static function getPossibleRewards(IState s, IAction a)// get the probability of getting reward r when at state s
// and performing action a
static function getRewardProbability(IReward r, IState s, IAction a)}
函数 computeStateValue(s)计算一个状态的值在最一般的情况下,我们假设奖励系统可能不简单,对于同一个状态/动作对可能有几种可能的奖励。
*function computeStateValue(IState s){
sum_s = 0
for( a in ){
sum_s' = 0
for(s' in s.getAccessibleStates()){
sum_r = 0
for(r in R.getPossibleRewards(s, a)){
sum_r += R.getRewardProbability(r, s, a) * (r + gamma
* s.getValue())
}
sum_s' += s.getStateProbability(s', a) * sum_r
}
sum_s += s.getActionProbability(a) * sum_s'
}
return sum_s
}*
假设奖励系统很简单,函数 computeStateValueSinpleReward(s)计算状态 s 的值。所以不需要循环遍历可能的奖励,只是我们简单的调用 R.getReward(s,a),得到的概率是 1。
*function computeStateValueSinpleReward(IState s){
sum_s = 0
for( a in s.getAvailableActions()){
sum_s' = 0
for(s' in s.getAccessibleStates()){
sum_s' += s.getStateProbability(s', a) * (R.getReward(s, a)
+ gamma * s.getValue())
}
sum_s += s.getActionProbability(a) * sum_s'
}
return sum_s
}*
不难注意到,上面的代码涉及一个状态的计算。为了计算所有状态的值,我们对所有状态集进行循环,如伪代码函数 computeValueForAllStates()所示
*function computeValueForAllStates(){
for( s in S){
v = computeStateValue(s)
s.setValue(v)
}
}*
结论
该公式的实现非常简单。然而,这本身是不够的,因为许多参数在现实生活中是未知的。出于这个原因,有很多其他技术可以用来帮助估计这个公式的每个组成部分。
相关文章
- 强化学习背后的数学,最简单的方法
- 开发者强化学习政策
- Q vs V 在强化学习中,最简单的方法
- 数学背后的强化学习,最简单的方法
- 动态编程在强化学习中的简便方法
- 蒙特卡洛强化学习中的简单方法
- TD 在强化学习中,最简单的方法
通过多武装匪徒理解强化学习
强化学习
强化学习(RL)是机器学习的一个方向,源于观察人类如何与环境互动。
为了更好的介绍 RL,我们先把它放在人类学习的背景下来理解。当一个孩子进入一个新的环境(即充满玩具的房间),他不知道环境中的目标是什么。相反,他执行基本上是随机的任务,在行动中学习周围环境的新事物。例如,他随机拿起一个尖锐的玩具,并意识到尖锐的玩具会引起疼痛。他选择了一个盒子,发现里面有一份快餐。通过这一集,孩子在学习。孩子知道尖锐的物体会引起疼痛,盒子里可能会有有用的物品,从而发展出一套规则来帮助指导他以后在这个环境中的决定。孩子通过环境的强化来学习。
强化学习的特点是一种学习框架。学习目标是指导代理通过与环境交互来实现目标。强化学习与监督学习的不同之处在于从专家监督者那里获得的信息的指导性。
在监督学习中,我们可以将使用(例如,标签)对的训练视为一个学习代理,向专家监督器提供给定训练示例的预测标签,专家监督器返回真实标签。专家的标签是完整的反馈,其中包含要采取的正确行动(真实标签)。
另一方面,强化学习中的环境 oracle 只提供不完整的反馈。RL 代理不是接收正确的(真实的)动作,而是仅获得与他们选择的动作相对应的奖励。代理不知道是否存在更好的行动,并且必须依赖于过去奖励的一些历史来比较他们收到的奖励,以便获得所选行动是否有利的感觉。完整的信息只有通过与环境的反复互动才能获得。
现在,让我们定义一个强化学习系统的主要元素:政策、奖励函数、价值函数和环境模型。例如,我们将考虑的环境是一盘棋。
作者图解。照片由 stevepb 通过 Pixabay 拍摄。经由公共领域载体的说明。
代理和环境在序列中的离散时间步骤上相互作用。在每一个时间步,代理接收环境的状态的一些表示。
这是国际象棋环境的两种不同状态。这盘棋有 10 个⁰ 不同的状态。截图来自棋牌在线。
基于环境的状态,代理从其可能的动作集中选择一个动作。一个时间步长后,环境会发生变化,以反映所选操作产生的下一个状态。随着环境的变化,代理人从环境中收到一个数字奖励。
- 代理是通过强化执行学习的系统的参与者。
- 策略是学习代理的行为方式:它们用来在环境的不同状态下选择动作的一套规则。例如,国际象棋选手选择下哪一枚棋子的算法。
- 奖励函数定义了环境中的目标。它将状态(或状态-动作对)映射到单个实数值。在国际象棋的例子中,当游戏进行时,所有状态的奖励可能是 0,一旦游戏结束(将死),如果代理赢了,奖励是 1,如果他输了,奖励是-1。RL 代理的目标是最大化长期回报。
- 价值函数概括了每个行动对最大化长期回报目标的影响。价值函数将环境的状态映射到单个值——如果从该状态开始游戏,代理可以预期获得的奖励总额。价值函数有助于在博弈过程中指导代理人。在国际象棋中,一些走法(如危及你的国王)通常是不利的,即使它们不会导致输掉比赛,价值函数捕捉这些信息以向学习者提供比简单的奖励函数更多的信息,而奖励函数通常是不够的。
- 环境的模型是代理在与环境交互后对环境如何运行的预测。该模型将状态-动作对映射到状态。在国际象棋的例子中,环境模型可以简单地是选择一个动作移动棋盘上的选定棋子,并且当着陆位置在对手棋子的顶部时消除对手棋子。
图片来自理查德·萨顿和安德鲁·巴尔托的强化学习[1]
多股武装匪徒问题
这导致我们现在的多武装匪徒问题,最简单的强化学习问题的公式。
多臂土匪。(图片来源:微软研究院)
问题如下。考虑一个带 n 操纵杆的老丨虎丨机。你会反复面对拉动 n 控制杆的选择。每拉一次杠杆,你就会从基于你选择的杠杆的固定概率分布中得到一些奖励。你的目标是通过杠杆拉动的总次数来最大化你的长期累积回报。这就是 N 臂土匪问题。
强化学习框架中的多武装匪徒问题。形象的灵感来源于[1]。
作为玩家,最好的解决方案是什么?如果你找到一个能提供积极回报的杠杆,那很好,但这是最好的杠杆吗?是否有其他更好的选择能带来更高的*均回报,寻找其他杠杆是否值得?或者你应该坚持你所知道的,拉你知道是体面的杠杆,但可能不会提供最高的回报?这些问题定义了期望-探索的权衡。
期望-探索权衡是在重复决策中的两难问题,在这种情况下,对世界有不完全了解的代理人必须决定是重复以前行之有效的决策(利用)还是选择新的、未探索的决策,以期获得更大的回报(探索)。选择重复过去最有效的决策被称为贪婪行动。
解决期望-探索权衡问题的一种方法是在利用迄今为止的最佳行动和探索其他行动之间交替进行。这些*乎贪婪的方法被称为 ɛ-greedy 方法。大多数时候,代理人的行为是贪婪的,但偶尔,用概率ɛ,我们在所有可能的行为中随机地、均匀地随机选择一个行为。一种变化是基于不确定性从分布中随机选择动作。因此,代理更有可能选择过去不常选择的动作。汤普森采样是另一种解决多武装匪徒问题的算法。这篇帖子的重点不是解决多股武装匪徒的问题,因此我推荐康纳·麦克唐纳的帖子来解决多股武装匪徒的问题。
这篇文章通过强化学习的视角展示了多臂土匪框架。强化学习代理,如多臂强盗,在没有任务先验知识的情况下进行优化,使用来自环境的奖励来理解目标并更新其参数。
参考
[1]理查德·萨顿和安德鲁·巴尔托。强化学习导论。麻省理工学院出版社,美国马萨诸塞州剑桥,第一版,1998 年。
了解 ResNet 及其变体
计算机视觉/深度学习社区中最具突破性的工作概述— ResNets
自从高计算单元的可用性以来,机器学习社区已经见证了向深度学习实践的范式转变,以实现更好的结果,特别是在计算机视觉领域。一种有前途的方法是卷积神经网络。CNN 是传统神经网络的改进版本,它保持了空间维度的完整性。
在图像分类、目标检测等方面,CNN 已经超越了其他各种框架。因此是研究人员非常感兴趣的。为了使这种学习越来越准确,研究人员已经开始实现越来越深的卷积网络。
为训练选择正确的层数的话题已经讨论了很长时间,事实证明,建立一个更深的网络不会提高精度,反而会降低精度。是的,你没听错,当你在网络中堆积越来越多的单元时,网络的学习能力就饱和了,过了一个阈值,它就下降了。
src: 用于图像识别的深度残差学习,测试和训练“普通”网络的错误
这种网络的精度下降而不是上升的两个基本原因是:
- 消失/爆炸渐变
- 退化问题
然而,第一个问题由标准化初始化和中间标准化层解决,这使得具有数十层的网络能够开始收敛于具有反向传播的随机梯度下降(SGD)。
但是当更深层次的网络开始融合时,他们观察到了退化问题。随着网络深度的增加,精度达到饱和,然后迅速下降。有助于消除这种退化问题的一种方法是添加带有身份映射的附加层。
ResNet [1]介绍了这些跳过一层或多层的【身份映射】或【跳过连接】,如图所示:
src: 用于图像识别的深度残差学习,具有身份映射的残差块
这个想法是,我们不只是将额外的层堆叠到网络中,而是将它们添加为剩余块(具有身份映射)。为了实现该方案中的结果,该工作的作者调整了底层映射,并使非线性层学习该映射:
src: 用于图像识别的深度残差学习
代替传统的映射 H(x ),并且原始映射被重铸为:
src: 用于图像识别的深度残差学习
引入这些快捷连接的另一个好处是,它们不会添加任何额外的参数,也不会增加计算复杂度,因为它们被用作身份映射。然而,它们确保了较深的网络和较浅的网络表现一样好。
最初提议的 ResNet 结构如下:
src: 用于图像识别的深度残差学习,最初提出 ResNet
这里需要注意的一点是那些虚线,表示线性投影。理想情况下,为了执行身份映射,F(x)和 X 的维数必须相同,但是当情况不是这样时,我们使用投影向量 Ws 来帮助匹配维数。
src: 用于图像识别的深度残差学习
另一项研究旨在确定何时应该引入这些“跳过连接”!
src: 深度剩余网络中的身份映射,各种风格的 ResNet 块
在得到具有完全预激活的剩余块的最终版本,即上图中的(e)之前,测试和尝试了每种可能的组合。
由于其引人注目的结果,ResNet 很快成为各种计算机视觉任务中最受欢迎的架构之一。
resnet 的流行变种
随着 ResNets 越来越受欢迎,人们对其架构进行了大量研究,研究人员提出了最初提出的工作的不同变体。
ResNeXt
在原始工作的基础上,提出了一个名为ResNeXt【2】的替代方案,其构建模块如下:
src: 深度神经网络的聚合残差变换
该模型采用了重复层的重组策略,同时以一种简单、可扩展的方式结合了拆分-转换-合并策略。该块可能看起来类似于 Inception network [3],其中我们执行各种不同的转换(1x1 Conv、3x3 Conv、5x5 Conv、MaxPooling)并将它们堆叠在一起,而这里我们通过将它们相加来合并不同的转换。
这种类型的块引入了另一个维度基数——独立路径的数量,以及现有的高度和深度维度。该维度的重要性已被其作者通过实验证明,“增加基数是比更深或更宽更有效的获得准确性的方式”,尤其是当深度和宽度开始给现有模型带来收益递减时。
提议的构件有三种可能的变化:
src: 深度神经网络的聚合残差变换,ResNeXt 的等效块
作者还指出,该模型比不同数据集上的 Inception [3]网络更容易训练,因为它只有一个超参数要调整,而不像 Inception 中有更多的超参数要调整。
DenseNet
ResNet 的另一个流行变体是dense net【4】,作者试图通过建立额外的连接来解决渐变消失的问题。在 DenseNet 中,它们通过将所有层直接相互连接来确保网络中各层之间的最大信息流。为了保持前馈性质,每一层从所有前面的层获得额外的输入,并将它自己的特征映射传递给所有后面的层。该模型如下所示:
src: 密集连接的卷积网络,相互连接的 DenseNet 层
除了处理消失梯度,作者还认为,这种实现方式可以实现要素重用,使网络具有很高的参数效率。在传统模型中,每一层都充当一个状态,它从前一个状态读取数据,并写入下一层。它改变了状态,但也传递了需要保存的信息。
DenseNet 架构明确区分添加到网络中的信息和保留的信息,最终分类器基于网络中的所有特征图做出决定。除了更好的参数效率,DenseNet 还通过网络提供了改进的信息流和梯度,这使其易于训练。
src: 密集连接卷积网络,三个密集块的深度 DenseNet。
第 l 层接收来自其所有先前层的特征图作为输入:
src: 密集连接的卷积网络
其中[..]表示连接操作。为了便于在网络中进行缩减采样,它被划分为多个部分,如上所示,中间有过渡图层。过渡层由批量标准化层、卷积层和最大池层组成。
由于其紧凑的内部表示和减少的特征冗余,DenseNets 可能是基于卷积特征的各种计算机视觉任务的良好特征提取器。
我希望它能帮助读者加深对这部开创性作品的理解。
这篇文章的所有图表都取自参考文献中的原文。
参考资料:
[1].贺国强,张晓霞,任世荣,孙军。用于图像识别的深度残差学习。arXiv 预印本 arXiv:1512.03385,2015。
[2].谢,吉希克,杜鹏,涂振宇和何国光。深度神经网络的聚合残差变换。arXiv 预印本 arXiv:1611.05431v1,2016。
[3].塞格迪、刘华清、贾庆林、塞尔马内、里德、安盖洛夫、埃汉、万霍克和拉宾诺维奇。用回旋越走越深。IEEE 计算机视觉和模式识别会议论文集,第 1–9 页,2015。
[4].黄,刘,温伯格,马腾。密集连接的卷积网络。arXiv:1608.06993v3,2016。
了解 SARIMA(更多时间序列建模)
我们调查了 ARIMA 的老大哥萨里玛是如何提高我们预测的准确性的
何前途未卜。但仍有一些事情我们可以合理地确定——圣诞节和感恩节每年都会到来,夏天充满了阳光明媚的日子和度假的家庭(至少过去是这样),每年二月健忘的男朋友和丈夫们会争抢巧克力和最后一束长茎玫瑰。
在分析时间序列时,季节很重要。某些事件每年都会发生:
- 假日
- 暑假
- 返校周
- 节日和年会
- 重大体育赛事
- 年度奖金
有些每月发生一次:
- 租金或抵押付款
- 信用卡账单
- 我们的工资支票(有些是双月一次)
甚至每周一次:
- 星期五(是的,像星期五这样普通的日子在模型中可能是一个具有统计意义的因素)
- 玉米卷星期二(开玩笑)
因此,如果我们知道某件事情可能会以固定的节奏发生,并且每次发生时都会以类似的方式影响我们的目标变量,那么我们应该在构建模型时将其考虑在内。在时间序列中,这种有影响的事件以恒定的频率重复出现被称为季节性。
ARIMA 进修
上一次,我写了很多关于 ARIMA 车型的内容(请先阅读),所以今天我只从高层次上介绍一下:
- AR 代表自回归,是指使用我们的目标变量的滞后值进行预测。例如,我们可能使用今天、昨天和前天的销售数字来预测明天的销售。这将是一个 AR(3)模型,因为它使用 3 个滞后值进行预测。
- I 代表集成。这意味着我们不是采用原始目标值,而是对它们进行差分。例如,我们的销售预测模型将尝试预测明天的销售变化(即,明天的销售减去今天的销售),而不仅仅是明天的销售。我们需要这样做的原因是,许多时间序列表现出一种趋势,使得原始值不稳定。取差值会使我们的 Y 变量更加稳定。
- MA 代表移动*均线。移动*均模型将滞后预测误差作为输入。与其他参数不同,它不是一个可直接观察到的参数(并且它不是固定的,因为它随模型的其他参数一起变化)。在高层次上,将模型的误差反馈给它自己有助于将它推向正确的值(实际的 Y 值)。
S:季节性
在简介中,我们提到了为什么季节性很重要——如果我们知道由于假期,每个冬季都会出现销售高峰,那么我们就应该考虑到这一点。在这种情况下,我们肯定不想在没有对这种系统性峰值进行调整的情况下,将 2020 年 Q1 的销售额与 2019 年第四季度的销售额进行比较——如果不考虑季节性,我们会错误地认为销售额已经下降(我们公司陷入了大麻烦),而实际上这只是预期的节后宿醉。
让我们来看一个具有明显季节性的时间序列,天然气消耗量:
天然气消费(资料来源:圣路易斯美联储银行)
这些反复出现的峰值证明了明显的季节性。如果我们尝试绘制月与月之间的变化,我们会得到一个类似的图。同样,我们一遍又一遍地看到相同的*似序列。
天然气消耗量的逐月变化
它总是周期性的,很容易预测,这意味着我们应该超越周期性(换句话说,调整它)。对于那些不熟悉天然气的人来说,定期出现消费高峰和低谷的原因是因为天然气的主要用途之一是供暖,而供暖需求随季节而变化(冬季月份更多):
每月天然气消耗量;寒冷月份消耗更多
让我们花一点时间思考一下,为什么我说我们应该超越这种周期性。如果我们知道天然气消费在每年一月达到高峰,在每年六月达到低谷,这对于一个模型来说还不够吗?如果你正在建立一个非常简单的模型,那么也许。但如果你想真正了解天然气消费背后的因素,那么你需要比“天然气用量每月变化”更深入。
看看下面的情节。我将数据切片,只包含 6 月份的数据点,然后计算变化。因此,这是一个同比变化,但仅在 6 月份——这是一个快速调整季节性的方法(因为我们是在跨年度比较同一个月)。缺点是我们被迫计算 1 年时间范围内的变化,这相当粗略。
天然气消费的年度变化(根据季节性调整的 6 月数据)
上图中的波动可能比之前的更有趣。因为我们比较的只是六月,所以差异一定是由于除了季节变化之外的其他因素造成的,比如经济因素、价格冲击或者异常炎热或寒冷的六月。通过研究该图(或同样经过季节性调整的类似图)并将其涨落与外部因素相关联,我们可以更好地理解天然气消费中超出正常水*变化的关键驱动因素。
在下图中,我绘制了 1 个月和 12 个月的变化,这样我们就可以了解相对幅度。请记住,1 个月的变化包括季节性,而 12 个月的变化,如 6 月至 6 月的变化,不包括:
天然气消耗量 1 个月和 12 个月变化的比较
从前面的图中可以明显看出,橙色线(12 个月的变化)比蓝色线(1 个月的变化)波动性小得多。这是另一个明确的指标,说明季节性驱动了我们数据中观察到的大部分差异。
使用 SARIMA 控制季节性
我们通常希望查看更高频率的数据,如每周或每月的变化。年度变化对于建立长期预测模型非常重要,但使用它们可能会导致我们错过理解此时此地所必需的粒度。
因此,我们不需要将回归的 Y 变量转换为年度变化,而是可以通过将它作为一个特征(X 变量)包含在我们的模型中来直接控制季节性。萨里玛就是这么做的。SARIMA 代表季节性自回归综合移动*均线(相当拗口)。它很像 ARIMA,但更强大。
我们可以使用 statsmodels 对 SARIMA 的实现。SARIMAX 函数的 3 个关键参数是:
- 原始数据(存储在名为 gas_df 的数据帧中)。看起来是这样的:
date values
0 2001-01-01 2677.0
1 2001-02-01 2309.5
2 2001-03-01 2246.6
3 2001-04-01 1807.2
4 2001-05-01 1522.4
- 与 arIMA 相似,order 参数是一个元组,它告诉函数 AR 滞后的数量、计算差值的时间步长的数量以及要包含在回归中的 MA 滞后误差的数量(按照该确切顺序)。
- 参数 search _ order 类似于 order,只是它用于指定季节性。search _ order 中的前 3 个值与 order 中的值相同(AR、差异范围、MA),但最后一个值指定了定义季节性周期的时间长度。因此,如果我们认为重复的模式在一年中发生(就像我们的情况一样,从六月到六月的差异消除了季节性),我们会将其设置为 12(因为我们数据中的每个时间步长为 1 个月)。
因为这是一个展示 SARIMA 的玩具模型,所以我不做火车测试分割,也不做模型的任何样本外压力测试。
from statsmodels.tsa.statespace.sarimax import SARIMAXs_mod = SARIMAX(gas_df['values'],
order=(1,1,0),
seasonal_order=(0,0,0,1))
predictions = s_mod.fit().predict()
首先,让我们看看一个简单的 AR(1)模型是如何处理这些数据的(AR(1)意味着我们只使用 Y 变量的最新滞后值进行 Y 预测)。在前面的代码块中,我将 search _ order 中的最后一个值设置为 1(因此没有特殊的季节性时间步长),这相当于一个 AR(1)模型,因为我删除了 MA 和季节性组件。
请注意预测值(橙色线)似乎总是比实际值(蓝色线)慢一步。这是 AR 模型的一个经典问题——当你只有过去可以继续时,趋势的根本转变几乎不可能预测。
AR(1)预测
既然我们观察到了天然气消费的常规年度周期,让我们看看是否加入季节性因素会有所改善。我们通过将 seasonal_order 参数更改为(1,0,0,12)来实现这一点,这为我们提供了一个季节性滞后,这意味着我们使用一年前的值(12 个滞后)来进行预测。请注意,为了隔离季节性的影响,我删除了订单参数中的 AR 滞后。
from statsmodels.tsa.statespace.sarimax import SARIMAXs_mod = SARIMAX(gas_df['values'],
order=(0,1,0),
seasonal_order=(1,0,0,12))
predictions = s_mod.fit().predict()
这是考虑了季节性因素的萨里玛预测。顺便说一下,相对于 statsmodels 中的 ARIMA,SARIMAX 的一个优点是 predict 方法的输出是目标变量本身的预测值。而对于 ARIMA,预测方法的输出是目标变量的预测变化(因此您需要做额外的调整来获得原始值)。
萨里玛预测
请注意更好的拟合。橙色线不再滞后;它现在紧紧地覆盖在蓝线上。太好了!
出于好奇,我们实际上可以用 AR(12)模型获得类似的结果。AR(12)包括足够的滞后来捕捉时间序列的季节性,但需要我们估计额外的 11 个 beta(滞后 1 到 11 的 beta ),所以它更容易过度拟合。
把所有的放在一起
酷,这些线重叠了(由于包含了季节性,这意味着我们的 SARIMA 预测覆盖了实际情况)。那又怎样?
我们可以做两件事之一。要么我们可以在 SARIMA 模型中加入其他因素来解释剩余(无法解释的)方差,要么我们可以创建一个季节性调整的序列,换句话说,一个新的 Y 变量。这两者在精神上是相似的,但我一般更倾向于第二种方式。喜欢绘图,看时间序列;这有助于我理解他们。
所以让我们采用后一种方法。我们可以通过从实际观察值中减去我们的 SARIMA 模型的输出(蓝线减去橙线)来创建天然气消费的季节性调整版本。
gas_df['adjusted_values'] = gas_df['values'] - predictions
这是可行的,因为我们的 SARIMA 模型的输出(其中唯一的因素是季节性)可以被认为是“天然气消费水*,其中季节性可以变化,但所有其他因素保持不变”。因此,任何偏离这一均衡季节性水*的现象都必定是由其他因素造成的。这是季节性调整后的情况:
实际非季节性*衡天然气消耗量
我们可以通过将调整后的值与我们知道相对不受季节性影响的时间序列(天然气消耗量的年度变化)进行比较来检验这一点。下表显示了 12 个月的变化、1 个月的变化(受季节性影响)和我们的调整值之间的相关性:
gas_chg12 gas_chg1 adjusted_values
gas_chg12 1.000000 0.199713 0.506134
gas_chg1 **0.199713** 1.000000 0.511884
adjusted_values **0.506134** 0.511884 1.000000
请注意调整值和 12 个月变化之间的相关性更高(0.5 比 0.2)。相关性不接* 1 是没问题的,因为 12 个月的变化对于季节性调整来说有点粗糙,所以我们不想精确地重现它。
酷,现在我们知道,如果我们在时间序列数据中发现重复的季节性模式,我们可以使用 SARIMA 来解释它。祝你好运预测!干杯!
如果你总体上喜欢这篇文章和我的写作,请考虑通过我在这里的推荐链接注册 Medium 来支持我的写作。谢谢!
理解信号。没那么复杂。
信号导论。为你的下一个机器学习项目产生信号。
图片由来自 Pixabay 的 Ryan McGuire 拍摄
即使对科学家来说,信号处理似乎也是极其复杂的事情。即使没有完全意识到它的潜在存在,信号处理也是我们日常生活的核心。本文将探讨什么是信号,我们如何生成信号,并在 Numpy 中存储信号以进行机器学习。
声音是一种波,由声波穿过的介质粒子的来回振动产生。这些声波由高压和低压区域的重复模式组成。它们也被称为压力波。
正弦波的一个例子。
当我们听到一些东西时,我们的大脑会将耳朵听到的这些空气分子的运动转化为我们可以识别的东西。无论是歌词还是音乐,鸟鸣声还是汽车喇叭声。
通常,当我们描述声音时,我们指的是它们的频率。我们以赫兹(Hz)为测量单位,通过每秒的周期数来测量这些波。
例如,这是一个 440 Hz 的声波,钢琴上中央 C 以上的 A 音符的声音:
MediaCollege 的 440 Hz 音频音调(右击并在新标签页中打开)
如果我们测量这种声音,并将其转换成时间函数的数字信号。这是我们将得到的曲线图,一个 440 Hz 的正弦波。它在*衡时上下摆动,每秒钟摆动 440 次。
1/10 秒的 440 赫兹。
我们再来看另一个声波,250 Hz,听起来像这样:
MediaCollege 的 250 Hz 音频音调(右键单击并在新标签中打开
250 赫兹的音调比 440 赫兹的音调低。如果我们要绘制它,它将看起来类似于 440 Hz 波,但频率较低。它每秒振荡 250 次。
1/10 秒的 250 赫兹。
单个声波本身并不令人兴奋。但是当我们把几个频率组合起来,按正确的顺序演奏,我们就能产生音乐。我们用声带发出这些声波来说我们用来交流的话。
当我们将两个频率组合在一起播放时,新的声波就是两个声波之和。我们得到的是一个不是纯正弦波的新波。在某些点,峰值相加变得更高,而其他点相互抵消,结果为零。
合并两个正弦波产生一个新的波。
我们可以增加更多的声音频率;声波会越来越复杂。事实上,这些复杂的声波正是我们的麦克风所拾取的。我们的麦克风在任何时候都能拾取许多不同的频率;最终的录音是所有声音频率的总和。除了那些你实际上想要记录的声音,它还拾取背景噪声、回声,甚至一些电信号噪声。
随着频率的增加,波变得更加复杂。
尽管这些例子是关于声波的,但这些概念也适用于其他数字信号,如心电图和脑电图。了解我们如何在脑机接口应用中使用信号:
成为 X 教授,解开我们心灵的秘密
towardsdatascience.com](/decoding-brain-signals-with-machine-learning-and-neuroscience-bee288c1d585)
那么我们如何以数字方式存储信号呢?
由于模拟信号在时间和幅度上都是连续的,我们需要将信号在时间和幅度上都简化为离散时间信号。这个信号减少过程就是呼叫采样。采样率定义了一秒钟内数据点的数量,也就是采样的速度。
采样率决定了信号的保真度。每个信号都有一个最小采样率,以保存信号中包含的信息。根据奈奎斯特–香农采样定理,采样率必须至少是信号最大频率的两倍,才能完全呈现信号。这意味着,如果信号包含高频成分,我们将需要以更高的速率进行采样。
理论上,只要奈奎斯特极限(采样速率的一半)超过被采样信号的最高频率,原始模拟信号就可以无损重建。否则,信号信息可能不会被完全表示,其中一些原始信号频率可能会丢失。这将导致被称为“混叠的听觉假象,这是重构信号中不需要的成分。
让我们来看看当采样率太低时会发生什么。
持续 2 秒钟的 2 Hz 信号,具有不同的采样率(SR)
上图显示了持续 2 秒钟的 2 Hz 信号;它总共有四次振动,每秒两次。当采样率为 50 和 100 时,波形看起来非常好,因为有足够的数据点。我们开始在 10 和 20 采样速率下看到信号边沿,但仍能清楚地看到四个正峰值。当采样率低于 6 时,信号开始丢失一些信息。
因为人类可以听到 20-20,000 赫兹的范围;这就是为什么音频波形和早期专业音频设备制造商选择 44.1kHz 的采样速率,这是许多数字格式的“标准”采样速率。提高采样率通常会提高音质,但也会增加存储所需的磁盘空间。许多电话和对讲机信号在 8,000 Hz 上传输,以减小分组大小来改善传输。
对于机器学习,以更高的频率采样会产生更好的重构信号,从而产生更好的性能。然而,这需要更快的 CPU/GPU 来转换和处理信号,并且需要更大的 GPU 内存,因为随着输入的增加,它也会增加模型的大小。因此,我们必须权衡每个应用程序的优点和缺点,并了解其中的利弊。
到目前为止,我们已经看到了单通道信号。但实际应用中的信号可能有多个通道。例如,我们在音频中有两个声道,左声道和右声道。在脑电图和心电图中,我们可以有十个或更多的通道。Neuralink 旨在建立一个拥有数千个通道的一体化脑机接口*台。
生成一些信号(用 Python)
我们可以用三个参数生成信号,1)信号持续时间、采样率和频率。
因为我们将信号存储为数字序列,所以首先,我们需要信号的数据点数。这可以通过将信号持续时间乘以采样速率来实现。接下来,我们需要一个时间变量,一个随时间变化的函数,它允许我们为每个数据点生成波形。
我们可以用正弦/余弦周期函数产生正弦或余弦波。正弦函数跟踪围绕单位圆运动的点的 y 坐标,而余弦函数跟踪 x 坐标。
展示正弦函数和余弦函数如何被绘制到单位圆上的一点的动画。[摘自维基百科
使用任何一个函数都会产生一个相似但不同的波。
正弦波和余弦波。
为了创建更真实的信号,我们还可以通过向每个数据点添加随机值来给生成的信号添加噪声。这允许我们测试我们的模型从噪声数据集进行归纳和学习的能力。
具有不同噪声水*的 2 Hz 信号。
回顾前面的章节,一个点的值是所有频率的总和。如果我们想要产生一个包含多个频率的信号,我们可以简单地将每个产生的波形相加。下面是一些生成信号的例子;每一波都增加了前一波的频率:
将不同的频率添加到波形中。
最后,多声道信号的一个例子:
向多个通道添加不同的波形。
如果你打算生成一些信号,这里有来自火炬信号包的代码。
使用此代码,您可以生成自己的数据集,其中输入要素是原始信号(带噪声),预测输出是频率。
由于我目前正在攻读博士学位,从事脑机接口的研究,我做了一个包含信号处理常用代码的 repo。该软件包包括清除信号和其他信号处理技术的功能。请随意检查并开始这个回购,火炬信号。
github.com](https://github.com/jinglescode/torchsignal)
寻找合作者来贡献新的特性、实用功能、错误修复和文档。目前,我一个人在做这个。如果您正在从事信号处理或脑机接口方面的工作,并且渴望构建一个高质量的包来将 PyTorch 应用于信号处理领域,那么请联系我。
生成信号有助于创建理想的数据集,以在“实验室环境”中测试模型的性能在下一篇文章中,我将介绍我们如何过滤和清理机器学习模型的信号。
利用美国总统选举了解辛普森悖论
如果数据分析不正确,见解可能会产生误导
本·怀特在 Unsplash 上的照片
数据谬误
数据是决策的一大利器。但是,如果使用不当,它可能是一把双刃剑。从“抽样偏差”到“虚假因果关系”(相关性并不意味着因果关系),有许多可能包含数据和分析的谬误。 【如何不犯错】【如何用统计数字撒谎】【数理统计】 都是教育我们认识这些谬误的好读物。解释数据时要注意这些。
考虑一下这个场景。这是一个星期五的晚上,你打算去看电影。归结为两部电影。电影 P 和电影 x。你决定查看评论。电影 P 说 90%的用户喜欢。电影 X 在同样的指标上有 82%。你决定看电影 P,因为,很明显!但是,如果你得到进一步的信息,电影 P 只有 10 个用户评价,而电影 X 有 10000 个用户评价,那该怎么办呢?这会改变你的决定吗?大概吧!例子还可以继续。关键是,在分析数据时,你需要小心这种不一致,即使是在简单的日常决定中,比如决定一部电影,或者做出更复杂的决定,比如选择什么职业或者搬到哪个城市。永远保持好奇心去深入挖掘!现在让我们试着理解这样一个谬误。
辛普森悖论
唐纳德·特朗普 vs 希拉里·克林顿
在我们进入辛普森悖论的技术定义之前,让我们回到 2016 年 11 月,试着回忆一下美国总统大选。你还记得唐纳德·j·特朗普是如何击败希拉里·克林顿赢得选举的吗?好吧。现在,让我们更深入地了解投票总数(大众投票)及其百分比份额。
请看下表,每一个州的民众投票的百分比。左侧列出的州是拥有较高共和党(REP)选票份额的州,右侧列出的州是拥有较高民主党(DEM)选票份额的州。如果我们看一下,我们会看到* 30 个州共和党占多数,约 20 个州民主党占多数。我们会得出结论,共和党人(红色)显然拥有更高的大众选票,对吗?不这么认为?继续滚动!
每个州和政党的民众投票(来源:《纽约时报》)
也许看看下面的地图会有帮助!红色的州是共和党占多数,蓝色的州是民主党占多数。多数票是根据该州较高的民众投票决定的。与上面的投票表一致,下面的地图加强了我们最初的信念,即共和党(红州)的投票份额更高。
2016 年选举结果图显示各州获胜者(来源:《纽约时报》)
现在,让我们来看看实际的总票数。
川普(共和党/红色):6290 万 (45.9%)
希拉里(民主党/蓝色):6580 万 (48%)
是的,希拉里·克林顿比唐纳德·特朗普更受欢迎。这种数据汇总时趋势反转的现象,基本上就是辛普森悖论。在分析中避免此类问题的关键是寻找不一致或缺失的数据。在这种情况下,每个州的投票人口(样本大小)。
辛普森悖论,也有其他几个名字,是概率统计中的一种现象,一种趋势出现在几组不同的数据中,但当这些组组合在一起时就会消失或逆转。
来源:维基百科
唉!他们用“选举人票”而不是“大众票”来决定获胜者!
如果这引起了你的兴趣,在这篇发表的论文或斯坦福大学的哲学百科全书中阅读更多关于辛普森悖论的内容。我将在本文的后续部分写更多类似的谬误,敬请关注!
非常感谢您的阅读!我希望这能对你有所帮助。如果你有什么建议,请告诉我。请随时在 LinkedIn 或我的网站上与我联系。我将在数据科学中发布类似的数据谬误和有趣的趋势,敬请关注!
参考文献:
* [## 2016 年总统选举结果:唐纳德·j·特朗普战胜希拉里·克林顿
直播总统选举结果和地图。
www.nytimes.com](https://www.nytimes.com/elections/2016/results/president) [## 辛普森悖论
辛普森悖论,也有其他几个名字,是概率和统计中的一种现象,其中…
en.wikipedia.org](https://en.wikipedia.org/wiki/Simpson's_paradox)*
理解奇异值分解及其在数据科学中的应用
了解奇异值分解背后的直觉
在线性代数中,矩阵的奇异值分解(SVD)是将该矩阵分解成三个矩阵。它有一些有趣的代数性质,传达了关于线性变换的重要几何和理论见解。它在数据科学中也有一些重要的应用。在本文中,我将尝试解释支持向量机背后的数学直觉及其几何意义。我将使用 Python 库来进行计算,而不是手动计算,稍后我将为您提供一些在数据科学应用程序中使用 SVD 的示例。本文中,粗体小写字母(如 a )指代向量。粗体大写字母(如 A )指矩阵,斜体小写字母(如 a )指标量。
要理解 SVD,我们需要首先理解矩阵的特征值分解。我们可以把矩阵 A 想象成一个变换通过乘法作用于向量 x 产生一个新的向量 Ax 。我们用[ A ]ij 或 aij 来表示矩阵 A 在第 i 行和第 j 列的元素。若 A 为 m×p 基体, B 为 p×n 基体,则基体积 C = AB (为 m×n 基体)定义为:
例如,二维空间中的旋转矩阵可以定义为:
该矩阵将矢量绕原点旋转角度θ(正θ为逆时针旋转)。另一个例子是二维空间中的拉伸矩阵 B ,定义如下:
该矩阵以恒定的因子 k 沿 x 轴拉伸矢量,但在 y 方向上不影响矢量。同样,我们可以在 y 方向上有一个拉伸矩阵:
举个例子,如果我们有一个向量
则 y = Ax 是 x 旋转θ后的矢量,而 Bx 是 x 在 x 方向上以恒定因子 k 拉伸的矢量。
清单 1 展示了如何将这些矩阵应用于 vector x 并在 Python 中可视化。我们可以用 NumPy 数组作为向量和矩阵。
这里,旋转矩阵是针对 θ =30⁰和拉伸矩阵 k =3 计算的。 y 是 x 的变换向量。为了绘制矢量,使用了matplotlib
中的quiver()
函数。图 1 显示了代码的输出。
图 1
矩阵由 NumPy 中的二维数组表示。我们可以使用np.matmul(a,b)
函数将矩阵a
乘以b
,然而,使用@
操作符更容易做到这一点。向量可以用一个一维数组或一个二维数组来表示,其形状为行向量(1,n)
或列向量(n,1)
。
现在我们要尝试一个不同的变换矩阵。假如
然而,我们不仅仅将它应用于一个向量。最初,我们有一个圆,包含所有距离原点一个单位的向量。这些向量的一般形式为
现在我们计算 t = Ax 。所以 t 是 x 中所有经过 A 变换的向量的集合。清单 2 展示了如何在 Python 中实现这一点。
图 2 显示了 x 和 t 的曲线图,以及在 x 中对两个样本向量 x1 和 x2 的变换效果。
图 2
左边的初始向量( x )如前所述形成一个圆,但是变换矩阵不知何故改变了这个圆,把它变成了一个椭圆。
圆内的样本向量 x1 和 x2 分别转换为 t1 和 t2 。所以:
特征值和特征向量
矢量是既有大小又有方向的量。矩阵 A 对 x 中的向量的一般效果是旋转和拉伸的组合。例如,它改变向量 x1 的方向和大小,以给出变换后的向量 t1 。然而,对于矢量 x2 来说,变换后只有幅度发生变化。其实 x2 和 t2 方向一致。矩阵 A 只在同一个方向拉伸 x2 并给出幅度更大的向量 t2 。改变向量的大小而不改变其方向的唯一方法是将它乘以一个标量。因此,如果我们有一个矢量 u ,并且λ 是一个标量,那么 λ u 具有相同的方向和不同的大小。所以对于图 2 中的 x2 这样的向量,乘以 A 的效果就像是乘以 λ这样的标量。
对于 x 中的所有矢量来说,情况并非如此。实际上,对于每个矩阵 A,只有部分向量具有这种性质。这些特殊向量称为 A 的特征向量,它们对应的标量λ称为该特征向量的 A 的特征值。因此一个 n×n 矩阵 A 的特征向量被定义为一个非零向量 u 使得:
其中 λ 为标量,称为 A 的特征值, u 为 λ 对应的特征向量。此外,如果你有任何其他形式的向量at【60】u 其中 a 是一个标量,那么通过把它放入前面的等式我们得到:
这意味着与特征向量 u 方向相同的任何向量(或者如果 a 为负,则方向相反)也是具有相同对应特征值的特征向量。
例如,的特征值
是 λ1=-1 和 λ2=-2 ,它们对应的特征向量是:
我们有:
这意味着当我们将矩阵 B 应用于所有可能的向量时,它不会改变这两个向量(或任何具有相同或相反方向的向量)的方向,而只会拉伸它们。所以对于特征向量,矩阵乘法变成了简单的标量乘法。这里我不打算解释特征值和特征向量是如何用数学方法计算出来的。相反,我将向您展示如何在 Python 中获得它们。
我们可以使用 NumPy 中的LA.eig()
函数来计算特征值和特征向量。它返回一个元组。这个元组的第一个元素是存储特征值的数组,第二个元素是存储相应的特征向量的 2-d 数组。事实上,在清单 3 中,u[:,i]
列是特征值lam[i]
对应的特征向量。现在,如果我们检查清单 3 的输出,我们会得到:
lam= [-1\. -2.]u= [[ 1\. -0.7071]
[ 0\. 0.7071]]
你可能已经注意到 λ=-1 的特征向量与 u1 的特征向量相同,但另一个不同。这是因为LA.eig()
返回归一化的特征向量。归一化向量是长度为 1 的单位向量。但在解释如何计算长度之前,我们需要熟悉矩阵的转置和点积。
转置
列向量 u ( 用 u 上标 t 表示)的转置是 u 的行向量(在本文中有时我表示为 u ^T).一个 m×n 矩阵 A 的转置是一个 n×m 矩阵,它的列由 A 的相应行组成。例如,如果我们有
那么 C 的转置为:
所以行向量的转置变成了具有相同元素的列向量,反之亦然。实际上,转置矩阵第 i 行第 j 列的元素等于原矩阵第j 行第I列的元素。如此
在 NumPy 中,你可以使用transpose()
方法来计算转置。例如,为了计算矩阵C
的转置,我们写C.transpose().
我们也可以使用转置属性T
,并写C.T
来得到它的转置。转置有一些重要的性质。一、 A 的转置的转置是 A 。所以:
此外,产品的转座是逆序转座的产品。
为了证明这一点,请记住矩阵乘法的定义:
而根据矩阵转置的定义,左边是:
右边是
所以等式两边是相等的。
点积
如果我们有两个向量 u 和 v :
这些向量的点积(或内积)定义为 u 乘以 v 的转置:
基于这个定义,点积是可交换的,所以:
分块矩阵
当计算矩阵的转置时,将其显示为分块矩阵通常是有用的。例如,矩阵
也可以写成:
在哪里
所以我们可以把 C 的每一列看成一个列向量, C 可以看成一个只有一行的矩阵。现在要写 C 的转置,我们可以简单地把这一行变成一列,类似于我们对一个行向量所做的。唯一的区别是 C 中的每个元素现在都是一个向量,也应该被转置。
现在我们知道了
所以:
现在 C ^T 的每一行都是原矩阵 C 对应列的转置。
现在假设矩阵 A 是分块的列矩阵,矩阵 B 是分块的行矩阵:
其中每列向量 ai 定义为 A 的第 i 列:
这里对于每个元素,第一个下标指的是行号,第二个下标指的是列号。所以 A 是一个 m×p 矩阵。另外, B 是一个 p×n 矩阵,其中 bi ^T 中的每一行向量是 B 的第 i 行:
同样,第一个下标表示行号,第二个下标表示列号。请注意,通过对流,向量被写成列向量。所以要写一个行向量,我们把它写成列向量的转置。所以 bi 是一个列向量,它的转置是一个行向量,它捕获了 B 的第 i 行。现在我们可以计算 AB :
所以 A 的第 i- 第列和 B 的第 i- 行的乘积给出了一个 m×n 矩阵,所有这些矩阵相加得到 AB 也是一个 m×n 矩阵。事实上,我们可以简单地假设我们正在用一个行向量 A 乘以一个列向量 B 。作为特例,假设 x 是列向量。现在我们可以类似地计算 Ax :
所以 Ax 只是 A 的列的线性组合。
要计算 NumPy 中两个向量a
和b
的点积,如果两者都是一维数组,我们可以写np.dot(a,b)
,或者简单使用点积的定义,写a.T @ b
。
现在我们已经熟悉了转置和点积,我们可以将向量 u 的长度(也称为 2 范数)定义为:
为了归一化一个向量 u ,我们简单地将它除以它的长度,得到归一化的向量 n :
归一化向量 n 仍与 u,方向相同,但其长度为 1。现在我们可以归一化之前看到的 λ= -2 的特征向量:
这与清单 3 的输出相同。如前所示,如果将一个特征向量乘以(或除以)一个常数,新向量仍然是同一特征值的特征向量,因此通过归一化对应于某个特征值的特征向量,您仍然有该特征值的特征向量。
但是为什么特征向量对我们很重要呢?如前所述,特征向量将矩阵乘法简化为标量乘法。此外,它们还有一些更有趣的性质。让我回到清单 2 中使用的矩阵 A 并计算它的特征向量:
正如您所记得的,这个矩阵将一组形成圆的向量转换成一组形成椭圆的新向量(图 2)。我们将使用LA.eig()
来计算清单 4 中的特征向量。
输出是:
lam= [3\. 2.]
u= [[ 1\. -0.8944]
[ 0\. 0.4472]]
所以我们有两个特征向量:
相应的特征值为:
现在我们在变换后的向量上绘制特征向量:
图 3
图 3 中的这些特征向量没有什么特别的。现在让我试试另一个矩阵:
这里我们有两个特征向量:
相应的特征值为:
现在,我们可以通过替换清单 5 中的这个新矩阵,在转换后的向量上绘制特征向量。结果如图 4 所示。
图 4
这次特征向量有一个有趣的性质。我们看到特征向量沿着椭圆的长轴和短轴(主轴)。椭圆可以被认为是一个沿其主轴拉伸或收缩的圆,如图 5 所示,矩阵 B 通过沿 u1 和U2****B的特征向量拉伸它来变换初始圆。
图 5
但是为什么 A 的特征向量没有这个性质呢?那是因为 B 是一个对称矩阵。对称矩阵是与其转置矩阵相等的矩阵。因此主对角线上的元素是任意的,但是对于其他元素,行 i 和列 j 上的每个元素等于行 j 和列 i 上的元素( aij = aji )。这是一个对称矩阵的例子:
对称矩阵总是一个方阵 ( n×n )。你现在可以很容易地看到和是不对称的。对称矩阵通过沿向量的特征向量拉伸或收缩来变换向量。此外,我们知道所有的矩阵都通过将特征向量的长度(或幅度)乘以相应的特征值来转换特征向量。我们知道圆中的初始向量长度为 1,并且 u1 和 u2 都被归一化,所以它们是初始向量 x 的一部分。现在它们的变换向量是:
因此,沿着每个特征向量的拉伸或收缩量与相应的特征值成比例,如图 6 所示。
图 6
所以当你在一个特征向量的方向上有更多的拉伸,对应于那个特征向量的特征值会更大。实际上,如果一个特征值的绝对值大于 1,圆 x 就沿着它拉伸,如果绝对值小于 1,就沿着它收缩。让我试试这个矩阵:
特征向量和相应的特征值是:
如果我们画出变换后的向量,我们会得到:
图 7
正如你现在看到的,我们沿着 u1 拉伸,沿着 u2 收缩。关于这些特征向量的另一件重要的事情是,它们可以形成向量空间的基。
基础
一组向量{ v1 , v2 , v3 …, vn} 形成了一个向量空间 V 的基,如果它们是线性无关和跨度 V 。向量空间是一组可以用标量相加或相乘的向量。这是一个封闭的集合,所以当向量与一个标量相加或相乘时,结果仍然属于这个集合。向量加法和标量乘法的运算必须满足某些要求,这里不讨论这些要求。欧几里得空间是向量空间的一个例子。
当一组向量线性无关时,这意味着该组向量中没有一个向量可以写成其他向量的线性组合。所以不可能写
当某些 a1 、 a2 、..、和都不为零。换句话说,这个集合中的 vi 矢量都不能用其他矢量来表示。如果空间中每隔一个向量可以写成一个生成集的线性组合,则一个向量集生成一个空间。所以 V 中的每个矢量 s 都可以写成:
一个向量空间 V 可以有许多不同的向量基,但是每个基总是有相同数量的基向量。向量空间 V 的基向量个数称为 V 的维。在欧几里得空间 R 中,向量:
是基的最简单的例子,因为它们是线性独立的,并且 R 中的每个向量都可以表示为它们的线性组合。它们被称为 R 的标准依据。因此 R 的尺寸为 2。它可以有其他的基,但是它们都有两个线性无关的向量,并且跨越它。例如,向量:
也可以构成 R 的基础。寻找向量空间的基的一个重要原因是要有一个坐标系统。如果向量组 B ={ v1 , v2 , v3 …, vn} 形成了向量空间的基,那么该空间中的每个向量 x 可以使用这些基向量来唯一地指定:
现在 x 相对于此基准 B 的坐标为:
事实上,当我们在 R 中写向量的时候,我们已经在表达它相对于标准基的坐标了。那是因为任何矢量
可以写成
现在出现了一个问题。如果我们知道一个向量相对于标准基的坐标,我们如何找到它相对于一个新基的坐标?
等式:
也可以写成:
矩阵:
叫做坐标变换矩阵。这个矩阵的列是基 B 中的向量。方程式
如果知道 x 在 R ^n 中的坐标,则给出其在基准 B 中的坐标。如果我们需要相反的结果,我们可以将这个方程的两边乘以坐标变化矩阵的逆矩阵,得到:
现在,如果我们知道 x 在 R ^n 中的坐标(简单地说就是 x 本身),我们可以将它乘以坐标变化矩阵的逆矩阵,从而得到它相对于基底 B 的坐标。例如,假设我们的基集 B 由向量构成:
我们有一个向量:
为了计算 B 中 x 的坐标,首先我们形成坐标变化矩阵:
现在 x 相对于 B 的坐标为:
清单 6 展示了如何在 NumPy 中进行计算。要计算矩阵的逆矩阵,可以使用函数np.linalg.inv()
。
输出显示了 x 在 B 中的坐标:
x_B= [[4\. ]
[2.83]]
图 8 显示了改变基础的效果。
图 8
为了找到基准 B 中 x 的u1-坐标,我们可以从 x 画一条线,*行于 u2 ,看它与 u1 轴相交的位置。U2-坐标的查找方法如图 8 所示。在一个 n 维空间中,为了找到 ui 的坐标,我们需要画一个从 x 穿过,*行于除 ui 之外的所有其他特征向量的超*面,并查看它与 ui 轴的交点。如图 8(左)所示,当特征向量正交时(像 R 中的 i 和 j ,我们只需要画一条穿过点 x 并垂直于我们想要求其坐标的轴的线。
对称矩阵的性质
如图 5-7 所示,对称矩阵的特征向量 B 和 C 相互垂直,形成正交向量。这不是巧合,是对称矩阵的一个性质。
对称矩阵的一个重要性质是一个 n×n 对称矩阵有 n 个线性无关且正交的特征向量,并且它有 n 个实特征值对应于那些特征向量。重要的是要注意,这些特征值不一定彼此不同,其中一些可以相等。对称矩阵的另一个重要性质是它们可以正交对角化。
特征分解
对称矩阵是正交对角化的。这意味着如果我们有一个 n×n 对称矩阵 A ,我们可以把它分解为
其中 D 是由 A 的 n 个特征值组成的 n×n 对角矩阵。 P 也是一个 n×n 矩阵, P 的列是 A 的 n 线性无关特征向量分别对应D中的那些特征值。换句话说,如果 u1 、 u2 、 u3 …、 un 是 A 的特征向量、λ1、λ2、…、λn 分别是它们对应的特征值,那么 A 可以写成
这也可以写成
你应该注意到每个 ui 被认为是一个列向量,它的转置是一个行向量。所以 P 的转置是根据 P 的列的转置写成的。T51A的这种因式分解叫做 A 的本征分解。
我举个例子来说明一下。假如
它有两个特征向量:
相应的特征值为:
所以 D 可以定义为
现在 P 的列是 A 的特征向量分别对应D中的那些特征值。因此
P 的转置为
所以一个可以写成
重要的是要注意,如果你在上面等式的右边做乘法,你不会精确地得到 A 。这是因为我们在 NumPy 中有舍入误差来计算通常出现在特征值和特征向量中的无理数,我们也在这里舍入了特征值和特征向量的值,但是,理论上,两边应该是相等的。但这意味着什么呢?为了更好地理解特征分解,我们可以看看它的几何解释。
特征分解的几何解释
为了更好地理解特征分解方程,我们需要首先简化它。如果我们假设每个特征向量 ui 是一个n × 1 列向量
那么 ui 的转置就是一个 1 × n 行向量
和它们的繁殖
变成了一个n×n 的矩阵。首先,我们计算 DP ^T 以简化特征分解方程:
现在,本征分解方程变为:
所以 n×n 矩阵 A 可以分解成形状相同的 n 矩阵( n×n ),这些矩阵中的每一个都有一个乘数等于对应的特征值 λi 。每个矩阵
被称为投影矩阵。想象我们有一个矢量 x 和一个单位矢量 v 。 v 和 x 的内积等于 v 。 x=v^T x 给出了 x 到 v 的标量投影(这是 x 到 v ) 、的矢量投影的长度),如果我们再乘以 v ,它给出了一个矢量,称为 x 到 v 的正交投影。这如图 9 所示。
图 9
所以当 v 是单位矢量时,乘
通过 x , 将 x 的正交投影到 v 上,这就是为什么它被称为投影矩阵。所以用 ui ui ^T 乘以 x ,我们得到 x 到 ui 的正交投影。
现在让我来计算前面提到的矩阵 A 的投影矩阵。
我们已经计算了 A 的特征值和特征向量。
使用清单 7 的输出,我们得到了特征分解方程中的第一项(这里我们称之为 A1 ):
如你所见,它也是一个对称矩阵。实际上,特征分解方程中的所有投影矩阵都是对称的。这是因为每个矩阵的行 m 和列 n 中的元素
等于
并且行 n 和列 m 处的元素具有相同的值,这使其成为对称矩阵。这个投影矩阵有一些有趣的性质。首先,我们可以计算它的特征值和特征向量:
lam= [ 3.618 0\. ]
u= [[ 0.8507 -0.5257]
[ 0.5257 0.8507]]
如你所见,它有两个特征值(因为它是一个 2 × 2 对称矩阵)。其中一个为零,另一个等于原矩阵 A 的 λ1 。另外,特征向量与 A 的特征向量完全相同。这不是巧合。假设我们得到本征分解方程中的第 i- 项,乘以 ui 。
我们知道 ui 是一个特征向量,它是归一化的,所以它的长度和它与自身的内积都等于 1。所以:
如果你看特征向量的定义,这个方程意味着矩阵的一个特征值
是 λi 对应的特征向量是 ui 。但是这个矩阵是一个n×n 对称矩阵,应该有 n 个特征值和特征向量。现在我们可以将它乘以 A 的剩余 (n-1) 个特征值中的任何一个,得到:
其中 i ≠ j .我们知道 A 的特征值是正交的,这意味着它们中的每一对都是垂直的。两个垂直向量的内积为零(因为一个向量到另一个向量的标量投影应该为零)。所以 ui 和 uj 的内积为零,我们得到
这意味着 uj 也是一个特征向量,其对应的特征值为零。所以我们得出结论,每个矩阵
特征分解方程中是一个对称的 n×n 矩阵,具有 n 个特征向量。特征向量与原矩阵 A 相同,分别为 u1,u2,… un 。 ui 对应的特征值为λi (与 A 相同),但其他特征值均为零。现在,记住对称矩阵是如何变换向量的。它会沿着向量的特征向量拉伸或收缩向量,拉伸或收缩的量与对应的特征值成正比。所以这个矩阵将沿着 ui 拉伸一个向量。但是因为其他特征值都是零,它会在那些方向上收缩到零。让我回到 matrix A 并使用清单 9 绘制出 A1 的转换效果。
图 10
如你所见,最初的圆沿着 u1 被拉伸,沿着 u2 收缩到零。所以这个变换的结果是一条直线,而不是一个椭圆。这与 A1 是一个投影矩阵,应该把一切都投影到 u1 上的事实是一致的,所以结果应该是沿着 u1 的一条直线。
排名
图 10 显示了一个有趣的例子,其中 2 × 2 矩阵A1 乘以一个二维矢量 x ,但是变换后的矢量 Ax 是一条直线。这是另一个例子。假设我们有一个矩阵:
图 11 显示了它如何转换单位向量 x 。
图 11
所以它作为一个投影矩阵,将 x 中的所有向量投影到直线 y= 2 x 上。这是因为 F 的列不是线性独立的。其实如果把 F 的列分别叫做 f1 和 f2 ,那么我们就有了 f1 = 2f2 。记得我们把矩阵和向量的乘法写成:
所以不像 x 中的矢量需要两个坐标, Fx 只需要一个坐标,存在于一维空间中。一般来说, m×n 矩阵不一定将一个 n 维向量转换成另一个 m 维向量。如果矩阵的列不是线性独立的,则变换向量的维数可以更低。
矩阵 A 的列空间记为ColA 定义为 A 列的所有线性组合的集合,由于 Ax 也是 A 列的线性组合, Col A 是所有的集合ColA 的基矢个数或者ColA 的维数称为 A 的秩。所以 A 的等级就是 Ax 的尺寸。
A 的秩也是 A 的线性无关列的最大个数。这是因为我们可以把所有的相关列写成这些线性无关列的线性组合,而 Ax 也就是所有列的线性组合可以写成这些线性无关列的线性组合。于是它们跨越了 Ax 并形成了colA 的一个基,这些向量的个数就成为了 A 的 col 的维数或者 A 的秩。
在前面的例子中, F 的排名是 1。另外,在特征分解方程中,每个矩阵的秩
是 1。记住,它们只有一个非零特征值,这不是巧合。可以证明对称矩阵的秩等于其非零特征值的个数。
现在我们再次回到特征分解方程。假设我们将对称矩阵 A 应用于任意向量 x 。现在,本征分解方程变为:
每个特征向量 ui 都是归一化的,所以它们是单位向量。现在在特征分解方程的每一项中
给出一个新的矢量,它是 x 在 ui 上的正交投影。然后这个向量乘以 λi 。由于 λi 是一个标量,将其乘以一个矢量,只会改变该矢量的大小,而不会改变其方向。所以 λi 只改变的幅度
最后所有的 n 个向量
加在一起就是给斧头。这个过程如图 12 所示。
图 12
因此,本征分解从数学上解释了对称矩阵的一个重要性质,我们在之前的图中看到了。对称矩阵通过沿着向量的特征向量拉伸或收缩向量来变换向量,并且沿着每个特征向量的拉伸或收缩量与对应的特征值成比例。
此外,特征分解可以将一个 n×n 对称矩阵分解成形状相同的 n 个矩阵(n×n)乘以其中一个特征值。特征值在这里起着重要的作用,因为它们可以被认为是一个乘数。投影矩阵只将 x 投影到每个 ui 上,但是特征值缩放了向量投影的长度( ui ui^Tx )。特征值越大,得到的向量( λi ui ui^Tx )的长度越大,赋予其对应矩阵的权重也越大( ui ui^T )。因此,我们可以通过对具有最高特征值的项求和来*似原始对称矩阵 A 。例如,如果我们假设特征值 λi 已经按降序排序,
那么我们只能取特征分解方程中的前 k 项来很好地*似原始矩阵:
其中 Ak 是 A 与**k的*似术语。如果我们只将前 k 个特征值和特征向量包含在原特征分解方程中,我们得到相同的结果:******
现在 Dk 是由A 的第一 k 特征值组成的 k×k 对角矩阵,Pk 是由A 的第一 k 特征向量组成的 n×k 矩阵,其转置成为 k×n 矩阵。所以他们的乘法仍然给出一个 n×n 矩阵,它与 A 的*似相同。****
如果在原矩阵 A 中,我们略去的其他( n-k) 特征值都很小,接*于零,那么这个*似矩阵和原矩阵很相似,我们就有了很好的*似。[数]矩阵
随着
就是一个例子。这里 λ2 比较小。 W e 调用单位圆内的向量 x ,用原矩阵( Cx )绘制它们的变换。然后,我们用矩阵 C 的特征分解方程中的第一项来*似矩阵C,该方程为:****
并由此绘制出 s 的变换。如图 13 所示,*似矩阵的结果是一条直线,非常接*原始矩阵。
图 13
为什么特征分解方程成立,为什么需要对称矩阵?记住对称矩阵的重要性质。假设 x 是一个 n×1 列向量。T5 如果 A 是一个 n×n 对称矩阵,那么它有 n 个线性无关且正交的特征向量可以作为新的基。所以我们现在可以写出 x 相对于这个新基的坐标:
而基于基的定义,任何向量 x 都可以唯一地写成 A 的特征向量的线性组合。
但是对称矩阵的特征向量也是正交的。所以要找到每个坐标 ai ,我们只需要通过点 x 画一条垂直于 ui 的轴的线,看它在哪里相交(参见图 8)。如前所述,这也可以使用投影矩阵来完成。所以每项 ai 等于 x 和 ui 的点积(参考图 9),可以写成 x
所以我们需要一个对称矩阵将 x 表示为上式中特征向量的线性组合。现在,如果我们将 A 乘以 x ,,我们就可以分解出 ai 项,因为它们是标量。所以我们得到:
由于 ui 向量是 A 的特征向量,我们最终得到:
这就是特征分解方程。在乘以 A 后发生的任何事情对所有矩阵都成立,并且不需要对称矩阵。我们需要一个 n×n 对称矩阵,因为它有 n 个实特征值加上 n 个线性独立和正交的特征向量,可以作为 x 的新基。当你有一个非对称矩阵时,你就没有这样的组合。例如,假设您有一个非对称矩阵:
如果你计算这个矩阵的特征值和特征向量,你得到:
*****lam= [2.5+0.866j 2.5-0.866j]
u= [[0.7071+0.j 0.7071-0.j ]
[0.3536-0.6124j 0.3536+0.6124j]]*****
这意味着你没有真正的特征值来做分解。另一个例子是:
你会得到:
*****lam= [2\. 2.]
u= [[ 1\. -1.]
[ 0\. 0.]]*****
这里,特征向量不是线性独立的。其实 u1 = - u2 。所以你不能像图 11 一样只使用一个特征向量来重建 A 。此外,它没有显示如图 14 所示的该矩阵的拉伸方向。
图 14
最后,请记住
我们有:
*****lam= [ 7.8151 -2.8151]
u= [[ 0.639 -0.5667]
[ 0.7692 0.8239]]*****
这里,特征向量是线性独立的,但它们不是正交的(参见图 3),并且它们没有显示变换后该矩阵的正确拉伸方向。
特征分解法非常有用,但只对对称矩阵有效。对称矩阵总是正方形矩阵,所以如果你有一个矩阵不是正方形的,或者是正方形但非对称的矩阵,那么你就不能用特征分解法用其他矩阵来*似它。SVD 可以克服这个问题。
奇异值
在讨论 SVD 之前,我们应该找到一种方法来计算非对称矩阵的拉伸方向。假设 A 是一个不一定对称的 m×n 矩阵。那么可以证明
是一个 n×n 对称矩阵。记住乘积的转置是逆序转置的乘积。因此
所以a^ta 等于它的转置,而且是对称矩阵。我们想要计算非对称矩阵的拉伸方向。,但是我们如何从数学上定义拉伸方向呢?****
到目前为止,我们只关注了二维空间中的向量,但是我们可以在二维空间中使用相同的概念。在这里,我集中在一个三维空间,以便能够形象化的概念。现在列向量有 3 个元素。最初,我们有一个球体,它包含所有距离原点一个单位的向量,如图 15 所示。如果我们称这些向量为 x ,那么|| x ||=1。现在如果我们把它们乘以一个 3 × 3 对称矩阵, Ax 就变成了一个三维椭圆。拉伸的第一方向可以被定义为在该椭圆中具有最大长度的向量的方向(图 15 中的 Av1 )。实际上, Av1 是|| Ax ||在所有单位向量 x 上的最大值。这个矢量是 A 对矢量 v1 的变换。
图 15
拉伸的第二方向是沿着向量 Av2 。 Av2 是|| Ax ||在 x 中垂直于 v1 的所有向量上的最大值。所以在 x 中的所有向量中,我们最大化|| Ax ||以此约束 x 垂直于 v1 。最后, v3 是垂直于 v1 和 v2 的向量,并且在这些约束条件下给出了 Ax 的最大长度。 Av3 的方向决定拉伸的第三个方向。所以一般在一个 n 维空间中,第 i 个拉伸方向是矢量 Avi 的方向,它具有最大的长度,并且垂直于前面的( i -1)个拉伸方向。
现在让 A 成为一个 m×n 矩阵。我们证明了 A^T A 是一个对称矩阵,所以它有 n 个实特征值和 n 个线性独立和正交的特征向量,这些特征向量可以构成它可以变换的 n- 元素向量的基础(在 R^n 空间中)。我们称这些特征向量为 v1 , v2 ,… vn ,我们假设它们是归一化的。对于这些特征向量中的每一个,我们可以使用长度的定义和转置矩阵乘积的规则来得到:
现在我们假设 vi 对应的特征值为 λi
但是 vi 是归一化的,所以
因此:
这个结果表明所有的特征值都是正的。现在假设我们按降序给它们贴标签,那么:
现在我们将 A 的奇异值定义为λI(a^t a的特征值)的*方根,我们用 σi 表示。****
所以 A 的奇异值就是向量 Avi 的长度。现在我们可以总结一个重要的结果,它构成了奇异值分解方法的基础。可以看出,|| Ax ||的最大值受到约束
是 σk ,并且该最大值在 vk 处达到。对于约束条件,我们使用了这样的事实:当 x 垂直于 vi 时,它们的点积为零。
所以如果 vi 是a^ta 的特征向量(按其对应的奇异值排序),并假设|| x ||=1,那么 Avi 为 Ax 呈现一个拉伸方向,对应的奇异值σi 给出了 Avi 的长度。****
奇异值也可以决定 A 的秩。假设非零奇异值的个数为 r 。因为它们是正的,并按递减顺序标注,所以我们可以把它们写成
这对应于
并且每个 λi 都是 vi 对应的特征值。那么可以表明,秩 A 是形成 Ax 的基础的向量的数量,是 r 。还可以看出,集合{ Av1 , Av2 ,…, Avr }是Ax(ColA)的正交基。因此矢量 Avi 相互垂直,如图 15 所示。****
现在我们回到非对称矩阵
我们在图 3 中绘制了 A 的特征向量,并提到它们没有显示 Ax 的拉伸方向。在图 16 中,左侧绘制了 A^T A 的特征向量( v1 和 v2 )。由于 A^T A 是对称矩阵,这些向量显示了它的拉伸方向。在右侧,画出了矢量 Av1 和 Av2 ,很明显这些矢量显示了 Ax 的拉伸方向。
图 16
因此 Avi 显示 A 的拉伸方向,无论 A 是否对称。
现在想象矩阵 A 是对称的,并且等于它的转置矩阵。另外,假设其第 i 个特征向量为 ui ,对应的特征值为 λi 。如果我们将 A^T A 乘以 ui ,我们得到:
也就是说 ui 也是 A^T A ,的一个特征向量但是它对应的特征值是 λi 。所以当 A 对称时,不用计算 Avi (其中 vi 是 A^T A 的特征向量)我们可以简单地使用ui(a的特征向量)来得到拉伸的方向,这正是我们对特征分解过程所做的。既然我们知道了如何计算非对称矩阵的拉伸方向,我们就可以看 SVD 方程了。****
奇异值分解
设 A 为 m×n 矩阵,秩 A = r 。所以 A 的非零奇异值个数为 r 。因为它们是正的,并按递减顺序标注,所以我们可以把它们写成
在哪里
我们知道每个奇异值 σi 是 λi 的*方根( A^TA 的特征值),对应一个同阶的特征向量 vi 。现在我们可以把的奇异值分解写成:****
其中 V 是一个 n×n 矩阵,其列为 vi 。所以:
我们称一组正交且归一化的向量为 正交 集合。所以集合{ vi }是一个正交集合。列是正交集合的矩阵称为 正交矩阵 ,并且 V 是正交矩阵。
****σ是一个形式为 m×n 的对角矩阵:
所以我们先做一个 r × r 对角矩阵,对角元素为 σ1,σ2,…,σr 。然后我们用零填充它,使它成为一个 m × n 矩阵。
我们还知道集合{ Av1 , Av2 ,…, Avr }是 Col A,和σI =|Avi| |的正交基。因此,我们可以通过将向量除以它们的长度来标准化这些向量:****
现在我们有一个集合{ u1 , u2 ,…, ur }它是 Ax 的正交基,Ax 是 r 维的。我们知道 A 是一个 m × n 矩阵, A 的秩最多可以是 m (当 A 的所有列线性无关时)。由于我们需要一个 U 的 m×m 矩阵,我们将(m-r)向量添加到 ui 集合中,使其成为一个 m 维空间r^m的归一化基(有几种方法可以用于此目的。例如,我们可以使用格拉姆-施密特过程。然而,解释它超出了本文的范围)。所以现在我们有了一个标准正交基{ u1 , u2 ,…, um }。这些向量将是正交矩阵 m×m 的 U 的列**********
所以最后,我们可以将 A 分解为
为了更好地理解这个等式,我们需要简化它:
我们知道 σi 是标量;ui 是 m 维列向量, vi 是 n 维列向量。于是每个σIui***VI^t 是一个 m×n 矩阵,SVD 方程将矩阵 A 分解成形状相同的 r 矩阵( m×n )。***
首先,让我来说明为什么这个等式是有效的。如果我们将 SVD 方程的两边乘以 x ,我们得到:
我们知道集合{ u1 , u2 ,…, ur }是 Ax 的一个正交基。所以向量 Ax 可以写成它们的线性组合。
并且由于 ui 向量是正交的,每一项 ai 等于 Ax 和 ui (标量投影 Ax 到 ui )的点积:
但是我们也知道
因此,将它代入前面的等式,我们得到:
我们还知道 vi 是 A ^T A 的特征向量,其对应的特征值 λi 是奇异值 σi 的*方
但是点积是可交换的,所以
注意, vi^Tx 给出了 x 到 vi 的标量投影,长度由奇异值缩放。现在,如果我们将 ai 值代入 Ax 、的方程,我们得到 SVD 方程:
所以每个ai=σIVI****^tx都是 Ax 到 ui 上的标量投影,如果乘以 ui ,结果就是一个向量,是 Ax 到 ui 上的正交投影。奇异值 σi 沿 ui 缩放该向量的长度。记住,在特征分解方程中,每个 ui ui^T 是一个投影矩阵,它给出了 x 到 ui 的正交投影。这里σIVI***^t可以认为是一个取 x 的投影矩阵,但是将 Ax 投影到 ui 上。由于它将所有向量投影到 ui 上,所以它的秩是 1。图 17 总结了 SVD 所需的所有步骤。我们首先从所有长度为 1 的矢量中挑选一个随机的二维矢量 x1 (图 17–1)。然后我们尝试使用 SVD 方法计算 Ax1 。***
图 17
首先我们计算 A^TA 的特征值( λ1 、 λ2 )和特征向量( v1 、 v2 )。我们知道奇异值是特征值的*方根( σi = λi ),如(Figure 17–2)所示。 Av1 和 Av2 表示 Ax 的拉伸方向, u1 和 u2 是 Av1 和 Av2 的单位矢量(图 17-4)。 Ax1 在 u1 和 u2 上的正交投影为
分别(图 17-5),简单地把它们加在一起,我们得到 Ax1
如所示(Figure 17–6)。
下面这个例子展示了如何用 Python 计算矩阵的 SVD。我们想找到的奇异值分解
这是一个 2 × 3 的矩阵。所以x 是三维列向量,但是 Ax 不是三维向量, x 和 Ax 存在于不同的向量空间。首先,我们计算 A^T A 的特征值和特征向量。****
输出是:
*****lam= [90.1167 0\. 12.8833]
v= [[ 0.9415 0.3228 0.0969]
[ 0.3314 -0.9391 -0.0906]
[-0.0617 -0.1174 0.9912]]*****
如你所见,第二特征值为零。由于 A^T A 是对称矩阵,有两个非零特征值,所以它的秩是 2。图 18 从不同角度显示了 A^T 斧的两幅图。由于 A^TA 的秩是 2,所以所有向量 A^TAx 都在一个*面上。
图 18
清单 11 展示了如何构建矩阵σ和 V 。我们首先按降序排列特征值。 V 的列是对应的相同顺序的特征向量。****
然后我们过滤非零特征值,取它们的*方根,得到非零奇异值。我们知道σ应该是一个 3 × 3 矩阵。因此,我们将两个非零奇异值放在一个 2 × 2 对角矩阵中,并用零填充它,得到一个 3 × 3 矩阵。输出是:****
*****Sigma= [[9.493 0\. 0\. ]
[0\. 3.5893 0\. ]]
V= [[ 0.9415 0.0969 0.3228]
[ 0.3314 -0.0906 -0.9391]
[-0.0617 0.9912 -0.1174]]*****
为了构造 V ,我们取与 A 的 r 非零奇异值对应的 vi 向量,并除以它们对应的奇异值。既然 A 是一个 2 × 3 矩阵, U 应该是一个 2 × 2 矩阵。我们有 2 个非零奇异值,所以 A 的秩是 2, r =2。因此,我们已经有足够多的 vi 向量来组成 U 。
输出是:
*****U= [[ 0.4121 0.9111]
[ 0.9111 -0.4121]]*****
最后,我们得到一个对的分解:****
我们真的不需要遵循所有这些步骤。NumPy 有一个名为svd()
的函数可以为我们做同样的事情。清单 13 展示了我们如何使用这个函数轻松计算矩阵 A 的 SVD。
输出是:
*****U= [[-0.4121 -0.9111]
[-0.9111 0.4121]]
s= [9.493 3.5893]
V [[-0.9415 -0.0969 -0.3228]
[-0.3314 0.0906 0.9391]
[ 0.0617 -0.9912 0.1174]]*****
您应该注意到输出中的一些事情。首先,这个函数返回一个奇异值数组,它位于σ的主对角线上,而不是矩阵σ上。另外,它返回 V^T,不是 V ,所以我打印了它返回的数组 VT 的转置。最后,svd()
报告的 ui 和 vi 向量与清单 10-12 中计算的 ui 和 vi 向量符号相反。记住如果 vi 是某个特征值的特征向量,那么(-1) vi 也是同一特征值的特征向量,其长度也是相同的。所以如果 vi 归一化,(-1) vi 也归一化。事实上,在清单 10 中,我们用不同的方法计算了 vi ,而svd()
只是报告(-1) vi ,这仍然是正确的。由于 ui = Avi/ σi,svd()
上报的 ui 的集合也会有相反的符号。****
你可以很容易地构造矩阵σ并检查这些矩阵相乘是否得到 A.****
*****Reconstructed A= [[ 4\. 1\. 3.]
[ 8\. 3\. -2.]]*****
在图 19 中,你可以看到一个由 x 和 Ax 组成的图,前者是单位球体中的矢量,后者是由 A 产生的一组二维矢量。矢量 u1 和 u2 表示拉伸的方向。由 Ax 生成的椭圆不像我们之前看到的那样是空心的(例如在图 6 中),变换后的矢量完全填充了它。
图 19
类似于特征分解方法,我们可以通过对具有最高奇异值的项求和来*似我们的原始矩阵 A 。因此,我们可以使用 SVD 方程中的前 k 项,使用 k 最高奇异值,这意味着我们只包括分解方程中的 U 和 V 矩阵中的前 k 向量:
我们知道集合{ u1 、 u2 、…、 ur} 构成了 Ax 的基础。所以当我们从这个集合中选取 k 向量时, Ak x 就写成了 u1,u2,… uk 的线性组合。因此它们跨越 Ak x ,并且由于它们是线性独立的,所以它们形成了 Ak x (或 col A )的基础。所以 Ak 的秩是 k ,通过挑选第一个 k 奇异值,我们用秩- k 矩阵来*似 A 。
举个例子,假设我们要计算矩阵的奇异值分解
同样, x 是单位球中的矢量(图 19 左图)。奇异值为 σ1= 11.97, σ2= 5.57, σ3= 3.25, A 的秩为 3。因此 Ax 在三维空间中是一个椭球体,如图 20(左)所示。如果我们使用第一个奇异值对其进行*似,那么 Ak 的秩将是 1,并且 Ak 乘以 x 将是一条线(图 20 右侧)。如果只使用前两个奇异值, Ak 的秩将为 2, Ak 乘以 x 将为一个*面(图 20 中)。
图 20
值得注意的是,如果我们有一个对称矩阵,SVD 方程简化为特征分解方程。假设对称矩阵 A 具有特征向量 vi 以及相应的特征值 λi 。所以我们
我们已经表明,对于对称矩阵, vi 也是 A^TA 的特征向量,对应的特征值为 λi . 所以 A 的奇异值为 λi 和 σi = λi 的*方根。现在我们可以计算 ui :
所以 ui 是 A 对应 λi (以及 σi )的特征向量。现在我们可以简化 SVD 方程,得到特征分解方程:
最后,可以看出 SVD 是用秩- k 矩阵逼* A 的最佳方式。一个 m × n 矩阵 A 的 Frobenius 范数 定义为其元素的绝对*方和的*方根:
这就像矩阵向量长度的推广。现在如果矩阵m×nAk 是由奇异值分解逼*的秩- k 矩阵,我们可以认为
为 A 与 Ak 之间的距离。这个距离越小, Ak 越接* A 越好。现在如果 B 是任意一个 m × n 秩- k 矩阵,可以看出
换句话说,SVD 生成的 A 与其秩- k 逼*之差具有最小的 Frobenius 范数,没有其他秩- k 矩阵能够给出对 A 更好的逼*(就 Frobenius 范数而言距离更*)。****
现在我们已经熟悉了 SVD,我们可以看到它在数据科学中的一些应用。
应用程序
降维
我们可以在矩阵中存储图像。每个图像由一组像素组成,这些像素是该图像的构建块。每个像素代表图像中特定位置的光的颜色或强度。在 PNG 格式的灰度图像中,每个像素都有一个介于 0 和 1 之间的值,其中 0 对应黑色,1 对应白色。所以一个有 m × n 个像素的灰度图像可以存储在一个 m × n 矩阵或者 NumPy 数组中。这里我们使用imread()
函数将 480 × 423 像素的爱因斯坦的灰度图像加载到一个二维数组中。然后我们用 SVD 分解矩阵,用前 30 个奇异值重构。
图 21。图像来源
原始矩阵为 480×423。所以我们需要存储 480×423=203040 个值。在 SVD 之后,每个 ui 有 480 个元素,每个 vi 有 423 个元素。为了能够使用前 30 个奇异值重建图像,我们只需要保留前 30 个σi、 ui 和 vi ,这意味着存储 30×(1+480+423)=27120 个值。这大约是原始图像所需数值的 13%。因此,使用奇异值分解,我们可以很好地逼*原始图像,并节省大量内存。清单 16,并计算对应于前 6 个奇异值的矩阵。每个矩阵σIuiVI****^t的秩为 1,行数和列数与原始矩阵相同。图 22 显示了结果。******
图 22
请注意,与原始灰度图像不同,这些秩 1 矩阵的元素的值可以大于 1 或小于 0,并且它们不应被解释为灰度图像。所以我没有使用cmap='gray'
,也没有将它们显示为灰度图像。当绘制它们时,我们不关心像素的绝对值。相反,我们关心他们相对于彼此的价值。
为了理解图像信息是如何存储在每个矩阵中的,我们可以研究一个简单得多的图像。在清单 17 中,我们读取了一个包含五个简单形状的二进制图像:一个矩形和四个圆形。结果如图 23 所示。
图 23
使用前 2、4 和 6 个奇异值重建图像。现在我们绘制对应于前 6 个奇异值的矩阵:
图 24
每个矩阵(σIui*VI****^t)的秩为 1,这意味着它只有一个独立列,所有其他列都是该列的标量乘法。所以如果调用独立列 c1 (或者它可以是其他任何一列),这些列的一般形式为:*******
其中 ai 是标量乘法器。另外,这个矩阵把所有的向量都投影到 ui 上,所以每一列也是 ui 的标量乘法。这可以在图 25 中看到。显示了矩阵的两列σ2t39】U2v2^t与 u2 。两列具有相同的 u2 模式,但值不同(列#300 的 ai 为负值)。****
图 25
所以使用 c1 和 ai (或 u2 及其乘数)的值,每个矩阵捕捉原始图像的一些细节。在图 24 中,前两个矩阵可以捕获原始图像中左矩形的几乎所有信息。在图 24 的前两个矩阵中,这四个圆被粗略地捕捉为四个矩形,关于它们的更多细节被添加到后四个矩阵中。这也可以在图 23 中看到,当我们添加更多的奇异值时,重建图像中的圆变得更圆。这些秩为 1 的矩阵可能看起来很简单,但它们能够捕捉图像中重复模式的一些信息。例如,在图 26 中,我们有苏格兰国家纪念碑的图像,它有 6 根柱子(在图像中),对应于第一个奇异值的矩阵可以捕捉原始图像中柱子的数量。
图 26。图像来源
特征脸
在本例中,我们将使用 Scikit-learn 库中的 Olivetti faces 数据集。该数据集包含 400 幅图像。这些照片拍摄于 1992 年 4 月至 1994 年 4 月间,地点是剑桥美国电话电报公司实验室。这些图像显示了 40 个不同主体的面部。对于一些受试者来说,这些照片是在不同的时间拍摄的,改变了光线、面部表情和面部细节。这些图像是灰度图像,每幅图像的像素为 64×64。每个像素的强度是区间[0,1]上的一个数。首先,我们加载数据集:
清单 1 中已经导入了fetch_olivetti_faces()
函数。我们称之为读取数据并将图像存储在imgs
数组中。这是一个(400,64,64)数组,包含 400 个灰度 64×64 的图像。我们可以在这里展示其中的一些例子:
图 27
在前面的例子中,我们将原始图像存储在一个矩阵中,然后使用 SVD 对其进行分解。这里我们采用另一种方法。我们知道我们有 400 张图片,所以我们给每张图片一个从 1 到 400 的标签。现在我们使用一键编码通过一个向量来表示这些标签。我们使用一个有 400 个元素的列向量。对于每个标签 k,除了第 k 个元素外,所有元素都为零。因此标签 k 将由向量表示:
现在,我们将每个图像存储在一个列向量中。每个图像有 64 × 64 = 4096 个像素。因此,我们可以展*每个图像,并将像素值放入具有 4096 个元素的列向量 f 中,如图 28 所示:
图 28
所以每张标签为 k 的图片都会被存储在矢量 fk 中,我们需要 400 个 fk 矢量来保存所有的图片。现在我们定义一个变换矩阵 M ,它将标签向量 ik 变换为其对应的图像向量 fk 。向量 fk 将是矩阵 M 的列:
这个矩阵有 4096 行和 400 列。我们可以简单的用 y=Mx 找到每个标签对应的图像( x 可以是任意向量 ik , y 会是对应的 fk )。例如,对于该数据集的第三幅图像,标签是 3,并且除了第三个元素是 1 之外, i3 的所有元素都是 0。现在,记住分块矩阵的乘法。当我们将 M 乘以 i3 时,除了第三列 f3 、之外, M 的所有列都乘以零,所以:
清单 21 展示了我们如何构建 M 并使用它来显示数据集中的某个图像。
每个标签向量 ik 的长度是 1,并且这些标签向量形成 400 维空间的标准基础。在这个空间中,每个轴对应于一个标签,其值可以是零或一。向量 fk 存在于 4096 维空间中,其中每个轴对应图像的一个像素,矩阵 M 将 ik 映射到 fk 。现在我们可以用 SVD 分解 M 。请记住,当我们将 M分解为 r 时****
集合{ u1 、 u2 、…、 ur }是 U 的第一个 r 列,将作为 Mx 的基础。每个向量 ui 将有 4096 个元素。由于 y = Mx 是我们的图像向量所在的空间,向量 ui 形成了图像向量的基础,如图 29 所示。在这幅图中,我试图想象一个 n 维向量空间。这在 n ≥3 时当然是不可能的,但这只是一个虚构的图解,帮助你理解这个方法。
图 29
所以我们可以将 ui 重塑成一个 64 ×64 的像素阵列,并尝试像绘制图像一样绘制它。这些向量的元素值可以大于 1 或小于 0,并且当对它们进行整形时,它们不应该被解释为灰度图像。所以我在显示它们的时候没有使用cmap='gray'
。
输出是:
图 30
您可以检查清单 22 中的数组s
有 400 个元素,所以我们有 400 个非零奇异值,矩阵的秩是 400。因此,我们需要 U 的前 400 个向量来完全重构矩阵。我们可以使用基本向量轻松地重建其中一幅图像:
这里,我们采用图像#160,并使用不同数量的奇异值对其进行重建:
图 31
向量 ui 被称为特征脸,可以用于人脸识别。正如您在图 30 中看到的,每个特征脸捕获了图像向量的一些信息。比如 u1 大部分是关于眼睛的,或者 u6 抓住了鼻子的一部分。当重建图 31 中的图像时,第一个奇异值添加了眼睛,但是脸部的其余部分是模糊的。通过增加 k,鼻子、眉毛、胡须和眼镜被添加到脸上。有些人认为眼睛是你面部最重要的特征。似乎 SVD 同意他们的观点,因为具有最高奇异值的第一个特征脸捕获了眼睛。**
降低噪音
奇异值分解可以用来降低图像中的噪声。清单 24 显示了一个例子:
图 32
这里,我们首先加载图像,并添加一些噪声。然后,我们使用前 20、55 和 200 个奇异值来重建图像。如图 32 所示,随着重构矩阵的秩增加,噪声量也会增加。因此,如果我们使用像 20 这样的较低等级,我们可以显著降低图像中的噪声。重要的是要理解为什么它在低级别工作得更好。
这里有一个简单的例子来说明 SVD 如何降低噪声。假设我们有清单 25 中定义的 3×15 矩阵:
该矩阵的颜色图如下所示:
图 33
矩阵列可以分为两类。在前 5 列中,只有第一个元素不为零,在后 10 列中,只有第一个元素为零。我们还有一个嘈杂的列(列#12 ),它应该属于第二类,但是它的第一个和最后一个元素没有正确的值。我们可以假设这两个元素包含一些噪声。现在我们用奇异值分解这个矩阵。矩阵的秩为 3,并且只有 3 个非零奇异值。现在我们用前两个和三个奇异值来重建它。
图 34
如图 34 所示,通过使用前两个奇异值,列#12 发生变化,并遵循第二类中列的相同模式。但是,现在它的元素的实际值要低一点。如果我们使用所有 3 个奇异值,我们得到原始的噪声列。图 35 显示了这些列在三维空间中的曲线图。
图 35
先看 SVD 生成的 ui 向量。 u1 显示第一类列向量的*均方向。类似地, u2 显示了第二类的*均方向。当然方向相反,但这不重要(记住如果 vi 是某个特征值的特征向量,那么(-1) vi 也是同一特征值的特征向量,并且由于ui=Avi/σI,那么它的符号取决于 vi ) 。重要的是拉伸方向,而不是矢量的符号。****
噪声列由矢量 n 表示。不沿着 u1 和 u2 走。现在如果我们以 ui 为基础,就可以分解 n ,找到它在 ui 上的正交投影。如你所见,它有一个沿 u3 (反方向)的分量,这是噪声方向。这个方向代表出现在 n 的第三个元素中的噪声。它具有最低的奇异值,这意味着 SVD 不认为它是一个重要的特征。当我们使用前两个奇异值重构 n 时,我们忽略这个方向,第三个元素中存在的噪声被消除。现在我们只有沿着 u1 和 u2 的矢量投影。但是沿着 u1 的标量投影具有高得多的值。这是因为矢量 n 更类似于第一类。
所以 n 在 u1-u2 *面的投影几乎是沿着 u1 的,利用前两个奇异值重构 n 给出了一个更类似于第一类的向量。重要的是要注意,由 u2 表示的第一元件中的噪声没有被消除。此外,尽管重构的 n 的方向几乎是正确的,但是与第一类中的向量相比,其幅度较小。事实上,在重构的矢量中,第二个元素(不包含噪声)现在具有比原始矢量更低的值(图 36)。
图 36
因此,SVD 将大部分噪声(但不是全部)分配给由较低奇异值表示的向量。如果我们重构一个低秩矩阵(忽略较低的奇异值),噪声将会减少,然而,矩阵的正确部分也会改变。结果是一个矩阵,它只是我们正在寻找的无噪声矩阵的*似。这可以在图 32 中看到。图像背景是白色的,噪声像素是黑色的。当我们重建低秩图像时,背景更加均匀,但现在是灰色的。事实上,如果图像中没有噪声,我们得到的是白色背景的一个噪声较少的*似。
我希望你喜欢阅读这篇文章。如果您有任何问题或建议,请告诉我。本文中的所有代码清单都可以从 GitHub 下载,网址是:https://github.com/reza-bagheri/SVD_article
延伸阅读:
特征分解和 SVD 也可以用于主成分分析(PCA)。PCA 对于降维非常有用。要了解更多关于 PCA 中特征分解和 SVD 的应用,您可以阅读以下文章:
了解用于对象分类和检测的 SPPNet
SPPNet 的分析与评述
SPPNet 允许可变大小的 CNN 输入图像,并可用于分类和对象检测
谢尔盖·阿库利奇在 Unsplash 上的照片
在论文“用于视觉识别的深度卷积网络中的空间金字塔池”中,介绍了一种称为空间金字塔池层的技术,该技术使得 CNN 模型与输入图像大小无关。它是 ILSVRC 2014 中物体检测的亚军和分类挑战的亚军,因此值得一读。
在这篇文章中,我解释了 SPP 层,然后回顾了整篇文章。博客的结构是学生和老师之间的对话。
学生
我希望了解更多关于物体检测的知识。上次,你已经解释了用于物体探测的 R-CNN 网络。在 R-CNN 之后,在目标检测领域还有其他研究吗?
教师
在 R-CNN 之后不久,SPP-Net 被引入。与 R-CNN 相比,SPPNet 使模型与输入图像大小无关,并显著提高了边界框预测速度,而不影响地图。我将首先解释如何使模型不可知的输入图像大小。
CNN 网络的固定大小限制不是因为卷积层,而是因为全连接(FC)层。单个卷积层或一组卷积层获取图像,并产生与输入图像的特定比率(称为子采样比率,稍后解释)成比例的特征图。但是对于完全连接的层,输入必须是固定长度的向量。为了解决这个问题,作者用一个空间金字塔池(SPP) 层替换了最后一个池层(FC 层之前的层)。
注意:SPP 方法受词汇袋方法的启发。不需要对单词包有深入的理解,但是了解它将有助于对概念的理解。
学生
我所看到的池层有一个固定的大小,比如说,2 x 2,两个方向的步幅都是 2。所以在这种情况下,输出与输入保持比例。池层如何解决问题?
教师
在上面描述的池层中,您已经设置了固定步幅(2)和固定窗口大小(2 x 2)。正因为如此,你的输出总是与输入成正比。现在如果你让池窗口和步幅与输入图像成比例,你总能得到固定大小的输出。此外,SPP 层不仅仅应用一个池化操作,它还应用了几个不同输出大小的池化操作(这就是相同操作的来源——空间金字塔池化),并在将结果发送到下一层之前将其合并。
空间金字塔池(SPP)层— 论文
在上图中,作者使用了三个池操作,其中一个操作只为每个地图输出一个数字。另一个为每个贴图提供 2 x 2 的网格输出,同样,最后一个提供 4 x 4 的输出。该操作被应用于由先前卷积操作给出的每个特征图(在上述情况下为 256 个图)。SPP 层输出被展*并发送到 FC 层。
现在要计算窗口大小和步幅大小,让我们声明一些变量和公式。
- 假设输入到 SPP 层的特征地图的大小为
[a x a]**↔**[13 x 13]
- 我们需要单个地图的输出大小为
[n x n]**↔**[4 x 4]
window = ceiling(a/n)
↔ 天花板(13/4) ↔4stride = floor(a/n)
↔ 楼(13/4) ↔3
现在在[13 x 13]地图上,应用上面的窗口([4 x 4])和步幅([3 x 3]),我们得到的输出是[4 x 4]。该操作应用于所有的特征映射,对于 256 个特征映射,我们得到[4 x 4 x 256]的输出。因此,现在我们可以改变[n x n]网格大小,以获得所需的输出大小。
学生
SPP 层是网络的直观设计,因为它使 CNN 模型产生的结果与输入图像大小无关。所以根据我的理解,在把特征发送到全连接层之前,我可以把这个层应用到任何 CNN。作者在本文中使用了哪些模型?此外,由于在 FC 层之前引入了一个全新的层,因此对于分类任务,作者是否使用了任何预训练的架构或者他们从一开始就训练了一个模型?
教师
人们可以将 SPP 层应用于任何 CNN 架构。但是,我们谈论的是 2014 年的时间。当时出席的模特不多。作者使用了 ZF 网、AlexNet 和 over fat(5 层和 7 层)架构。然而,通过改变填充来修改这些网络,以获得期望的输出特征图。
SPPNet 中使用的 CNN 模型架构— 论文
作者在 ImageNet 2012 数据集上训练了该模型,并提供了对训练细节的详细分析,并将它们与没有在其上使用 SPP 层的当代模型进行了比较。然而,在深入本质细节之前,让我们先弄清楚一些定义。
多尺寸/多比例图像:改变输入图像尺寸
多视图:使用图像增强——从输入图像中截取部分并翻转。
首先,作者在模型上用一个单一大小的输入[224 x 224]训练,然后用一个可变大小的输入[224 x 224]和[180 x 180]训练。作者在固定大小的图像上执行训练,以利用 GPU 实现的优势。在为多尺寸训练时,它们在一个时期发送相同的图像尺寸,然后改变到另一个时期。用于上述两种训练的金字塔是:{6 x 6,3 x 3,2 x 2,1 x 1}。
注意:为了训练,图像被调整大小,使得最小值(高度、宽度)等于 256,并且剩余部分基于纵横比被调整。之后,从中心和四个角拾取 224 x 224 的作物,给我们总共 5 个 224 x 224 的图像。图像被翻转以从相同的位置产生 5 个以上的图像,从每个输入图像给出总共 10 个图像。
ImageNet 12 验证集分析— 论文
作者将单一大小训练的 SPP 模型与没有 SPP 的模型进行了比较,发现误差减少了。为了证明误差的减少是由于 SPP 层而不是由于参数的增加,他们将 SPP 层的金字塔更改为{4 x 4,3 x 3,2 x 2,1 x 1},这大大减少了参数,但误差仅略有增加。
对填充尺寸和裁剪图像的验证— 纸张
作者还尝试用 224 次裁剪和不用 224 次裁剪来检验该模型,并对结果进行了分析。观察到误差减少,如上图所示。根据这种损失的减少,作者说:
由此可见维护完整内容的重要性。
学生
从上面的数字,我们可以说 SPPNet 论文中引入的新层确实减少了分类任务的误差。我还想知道作者是如何推断出对象检测任务的模型的?
教师
作者可以使用该模型进行对象检测,并可以添加一个包围盒检测器来拍摄地图。然而,为了理解这是如何发生的,我们将首先理解子采样率的概念
了解子采样率-作者提供的图像
子采样率可用于从输入图像形状确定 CNN 特征图的输出形状。简单地将输入图像尺寸乘以子采样率将得到特征图尺寸。我们一会儿会用到这个概念。
注意:填充确实会影响子采样率,因为我们可能需要根据填充增加/减少一个常数。文件附录中提供了详细信息。
SPPNet 论文已经显示了用于对象检测的 ZF 网论文的分析。ZF 网论文的次抽样比率为 16。现在,对于对象检测,他们仍然使用选择性搜索算法来确定区域(每幅图像约 2k 个区域)的建议。但与 R-CNN 不同,它们不会将每个建议区域发送到 CNN,而是将这些区域映射到最后一个卷积层(Conv5)的输出特征图。
从图像到特征图的对象映射——来自视频的SS
在上面的图像中,图像中的对象被映射到特征映射。让我们看看数学,看看如何做到这一点。
- 设图像尺寸为
[img_height, img_width]
↔【688×920】 - 让出现在图像中的一个物体的中心在
[x, y]
↔【340,450】 - 上述物体的高度和宽度是
[obj_height, obj_width]
↔【320,128】 - 现在,为了将它们映射到特征图上相应的空间位置,我们简单地将它们乘以
sub-sampling ratio (S)
↔ 16。 - 特征地图的尺寸将会是
[img_height * S, img_width * S]
↔[43 x 58】 - 特征图上的物体中心将在
[x * S, y * S]
↔[21,28】的空间位置 - 特征图上对象的高度和宽度将为
[obj_height * S, obj_width * S]
↔[20 x 8】
这样,我们可以将输入图像中的任何对象映射到输出特征图。对象的坐标也被投影到特征地图,然后只有该区域被发送到 SPP 层进行特征提取,然后发送到 FC 层。
对象窗口到特征图的映射— 论文
学生
将输入图像中的区域投影到特征图上以避免在图像的相同部分上进行冗余的卷积运算是减少计算量的一种聪明的方法。与 R-CNN 相比,这种机制是否影响了检测图谱?
教师
在比较它的性能之前,我们还需要了解一些细节。作者使用了单一大小([224 x 224]而已)的 ImageNet 预训练 ZF-5 网络。为了检测,他们使用了两种不同的方法进行预测:单尺度 —其中最小值(高度,宽度)= 688,多尺度其中最小值(高度,宽度)= {480,576,688,864,1200}和其他尺寸根据长宽比调整大小。
在多尺度检测的情况下,作者使用了一种选择尺度的新策略,该尺度具有最接*对象的像素总数(224 x 224 = 50,176),以确定该尺度中对象的存在。
与 R-CNN 相比,在单尺度下,性能下降了 0.5%,而在多尺度下,性能提高了 0.7%。虽然地图的增加并不大,但速度却有相当大的提高。作者还比较了有和没有微调 FC 层的模型,并将其与 R-CNN 进行了比较,结果如下所示。
注:卷积层没有微调,只有 FC 层。
与 R-CNN 在 Pascal VOC 07 — 论文上的比较
注:我们已经讨论了 Pascal VOC 2007 数据集上的对象检测结果。
论文中的大部分关键概念都已经讨论过了。但是,我建议您阅读这篇论文,因为他们在分类和检测部分提供了更详细的分析。
理解 SPP 层使得学习在此之后构建的两阶段模型变得更加容易。我将很快在 FastR-CNN 的报纸上写作。敬请期待!!
参考
K.何,X 张,s 任,孙军,用于视觉识别的深度卷积网络空间金字塔池,,2014
理解统计和概率:贝叶斯推理
了解统计的一个关键领域,这一领域在许多部门引起了兴趣
贝叶斯推理是最流行的统计技术之一。这是一种在收集新数据时更新事件先验概率的技术。贝叶斯推理是一种数据驱动的技术。
传统上,贝叶斯模型是首先使用的模型之一。它们被用作基线模型,因为它们基于对世界的简单看法,并使科学家能够更容易地解释推理。因此,贝叶斯推理是统计学中最重要的学习技巧之一。
本文将向读者介绍贝叶斯推理。这是一个必须知道的话题。
概率统计中的一个重要概念
文章目标
本文将概述以下概念:
- 什么是贝叶斯推理?
- 什么是贝叶定理?
- 理解概念的例子
- 朴素贝叶斯模型
贝叶斯推理在很多领域都有应用,包括保险、医疗保健、电子商务、体育、法律等等。贝叶斯推理在分类算法中被大量使用,由此我们试图将文本或数字分类/分组到它们适当的类别中。
此外,中国对银行业,尤其是金融业的兴趣日益浓厚。
1.什么是贝叶斯推理?
在我解释什么是贝叶斯推理之前,我们先来了解一下关键的构建模块。
我先举一个例子。
计算机工程师示例
假设我有一台停止工作的电脑。我家附*有两个电脑工程师会修电脑。
两位工程师都声称有不同的技术来诊断和解决问题。
第一工程师——频率主义方法
第一个工程师有一个模型,由一个数学方程组成。这个模型是根据一个事件的发生频率建立的。该模型需要一组输入来计算计算机为什么停止工作的诊断。
第一个工程师诊断问题的方式是询问模型需要作为输入的问题。
例如,工程师会询问计算机规格,如操作系统、硬盘大小和处理器名称。然后,他会将答案输入模型,然后模型会给出计算机故障的原因。
该模型将使用观察到的事件频率来诊断计算机停止工作的原因。
频繁主义方法
第二工程师—贝叶斯方法
第二个工程师有一个稍微不同的机制来诊断问题。他还使用了第一个工程师使用的相同模型,但是除了该模型之外,他还利用了事件的先前历史来诊断问题。因此,工程师会问与第一个工程师相同的问题,但他也会询问问题的先前历史。
例如,第二个工程师会询问计算机规格,如操作系统、硬盘大小和处理器名称。此外,他还会询问有关计算机历史记录的问题,例如,它过去是否停止过工作,如果知道的话,它停止工作的原因以及事件发生的次数。
工程师将收集该台计算机的适当历史记录,以便更好地了解问题。这种技术被称为贝叶斯推理。简而言之,在贝叶斯推理中,我们使用先验历史和已知模型来计算后验结果。
贝叶斯方法
第一个工程师使用频率主义方法,第二个工程师使用贝叶斯推理方法。
频率主义方法在小样本量的情况下并不准确,因为它是基于积极事件发生的观察频率,而贝叶斯方法依赖于关于事件发生概率的先验信念。话虽如此,频率主义方法比贝叶斯推断更容易实现和理解,通常用于大样本量。
贝叶斯推断是一种统计推断方法,在这种方法中,随着更多的证据或信息变得可用,贝叶斯定理用于更新假设的概率。贝叶斯更新在数据序列的动态分析中尤其重要。
贝叶斯推理技术是基于贝叶斯定理的。贝叶斯定理可以通过使用先验和似然分布计算后验分布来帮助我们更新随机变量的知识。
这就把我们带到了文章的第二部分。
2.贝叶斯定理
简单地说,贝叶斯定理计算一个事件的后验概率。它使用事件的先验概率和似然概率。
让我们考虑一下,我们想要估计一个目标变量的行为。这个目标变量本质上可以是随机的。因此,我们从收集随机样本的数据开始。这个数据是我们的样本集,它有自己的抽样分布。由于每个样本都有不同的数据点,分布可以帮助我们量化采样技术中的误差。
一旦收集了样本,进行了实验,我们现在可以使用贝叶斯定理来获得新的信息,以更新我们先前的理解。
贝叶斯定理是一个框架,它使我们能够在一个事件已经发生的情况下计算另一个事件发生的概率。
贝叶斯定理公式
两个随机变量 A 和 B 的贝叶斯定理是:
贝叶斯定理公式
让我们来理解一下公式
上面的公式表明有两个事件:A 和 B
我们试图找到条件概率;假设事件 B 已经发生,事件 A 发生的概率。这就是所谓的后验分布。
- 它的计算方法是,假定事件 A 已经发生,通过计算事件 B 发生的概率与事件 A 的无条件概率的乘积,得出事件 A 和 B 的联合概率。这显示在分子中。
2.最后,我们将联合概率除以事件 B 发生的概率。
三个主要组成部分
贝叶斯推理得出后验概率。它假设后验概率是两个主要输入(为简单起见)的结果:先验概率和似然函数。似然函数来源于统计模型本身。贝叶斯推理根据贝叶斯定理计算后验概率。
- 我们计算的是后验分布。这是 P(给定 B)。
2.我们已经有的是 P(B)。这是事件 B 已经发生的概率。
3.最后,P(B|A)是似然概率。它是在 A 已经发生的情况下,B 发生的概率。
每当我们想要预测一个随机变量时,我们已经有了一个先验的已知分布。举个例子,我们有一个先验分布,如果我们掷硬币一百万次,那么我们看到正面的次数大约是 50%
贝叶斯统计的目标是计算后验分布。
对于先验分布,我们使用贝叶斯定理来获得后验分布。这是我们看到数据后更新的认识。利用后验分布,我们可以总结我们对数据的理解。
贝叶斯推理是数据驱动的,因为先验分布和后验分布是由数据驱动的
3.理解概念的例子
我相信应用概念来解决实际问题,因为只有这样我们才能彻底理解概念。
1.让我们来看一个简单的例子:跑步和训练
让我们假设你正在训练在 25 分钟内跑完 5 公里。
如果你在健身房训练,你想知道达到跑步目标时间的概率。
让我们用贝叶斯公式来找出概率
在高层次上,我们需要四个数字
- P(R)是在 25 分钟内跑完 5 公里的概率。50%是基于收集的数据,因此 50%的人在 25 分钟内跑完 5 公里。
- P(T)是在健身房训练的概率。60%是基于收集的数据,因此 60%的人进行训练,不管他们是否在 25 分钟内完成了跑步。
- P(T|R)是假设一个人在 25 分钟内完成了 5 公里跑步,他/她正在健身房训练的概率。是 75%。
问题:假设你在健身房训练,在 25 分钟内跑完 5 公里的目标时间的可能性有多大?
因此,问题是找到 P(R|T)。我们知道它的计算方法是:
答案是 62.5%
2.另一个例子是金融机构的债券违约
让我们假设我们在一家金融机构工作,并希望在另一只债券 Y 已经违约的情况下,找出债券 X 违约的概率。
我们可以创建一个概率矩阵来可视化这个问题:
债券的概率矩阵
使用概率矩阵求解非常简单。
- 假设债券 X 已经违约,债券 Y 违约的联合概率是 10%(右下角方框)
- 不管债券 X 是否违约,债券 Y 违约的概率都是 20%(第二栏的总和)
举例回答
因此,如果债券 Y 已经违约,债券 X 有 50%的可能违约。
3.让我们用另一个例子来理解
让我们假设我们知道每天有 X 个读者阅读这个博客。假设一个读者是数据科学家,我想计算他/她是 Python 开发者的概率。
让我们把 A 称为一个数据科学家读者。
- 读者是数据科学家的概率是 P(A)
- 读者不是数据科学家的概率是 P(A’)
让我们把 B 称为是 Python 开发者的读者。
- 读者是 Python 开发者的概率是 P(B)
- 读者不是 Python 开发者的概率是 P(B’)
假设一个读者是数据科学家,那么他/她是 Python 开发者的概率有多大?
解决方案是:
问题的公式
因此,我们可以从收集一天所需的数据开始。
让我们考虑以下数字:
今天有 200 名读者阅读了这个博客
在 200 名读者中:
- 20 名读者是数据科学家,因为他们是 Python 开发人员
- 30 读者不是数据科学家,他们是 Python 开发者
- 60 名读者是数据科学家,而不是 Python 开发者。
- 90 位读者既不是 Python 开发者,也不是数据科学家。
那么问题是——假设一个读者同时也是数据科学家,那么他成为 Python 专家的可能性有多大?P(B|A)
我们可以计算一棵树来形象化它:
概率决策树
问题的公式
因此,概率计算如下:
得到概率的方程式
这给了我们 25%
因此,假设读者是数据科学家,那么他/她有 25%的机会是 Python 开发人员。
4.贝叶斯模型—朴素贝叶斯
最后,我想向读者介绍朴素贝叶斯模型。在大多数分类数据科学项目中,特别是在文本挖掘项目中,朴素贝叶斯模型被用作基准/基线模型。
该模型本质上是概率性的。它基于我上面解释的概念。因此,它使用贝叶定理来计算结果。
这个模型之所以被称为天真,是因为它的基础是对现实的极度简化。它假设数据中的要素在给定类标签的情况下相互独立。对于大多数数据集来说,这种假设并不完全正确。
作为一个实例,这些特征可能被链接在一起。解释一下,像国家历史博物馆或白金汉宫或白宫或黑色日等词,当它们一起写在不同的句子中时,有完全不同的意思。因此,这表明对这些特征有某种形式的依赖。
简单起见,朴素贝叶斯模型假设所有特征都是独立的。有时一个可以解释的模型比一个无法解释的精确模型更重要
传统上,贝叶斯模型是首先使用的模型之一。它们很容易解释,因为它们是基于对世界的简单看法。此外,参数很容易理解。因此,为什么朴素贝叶斯分类器被视为基线模型。
摘要
本文解释了什么是贝叶斯推理和贝叶斯定理。
感谢您的阅读
特别是,它概述了以下主题:
- 什么是贝叶斯推理?
- 什么是贝叶定理?
- 理解概念的例子
- 朴素贝叶斯模型
贝叶斯推理在很多领域都有应用,包括保险、医疗保健、电子商务、体育、法律等等。贝叶斯推理在分类算法中被大量使用,由此我们试图将文本或数字分类/分组到它们适当的类别中。
此外,中国对银行业,尤其是金融业的兴趣日益浓厚。
理解统计和概率:成为专家数据科学家
介绍数据科学家的专业技能以及最佳实践和成功的数据科学项目步骤
数据科学是当今的热门话题。组织认为数据科学家是 T2 的精英。业内每个人都在谈论数据科学的潜力,以及数据科学家可以为他们的 BigTech 和 FinTech 组织带来什么。做数据科学家很有吸引力。
本文将概述成为数据科学领域专家所需了解的一切。
关于专家数据科学家的话题。作者图片
在困难时期,对数据科学家的需求更大,因为能够从事削减成本和创造收入的项目至关重要。数据科学项目之所以成为可能,主要是因为先进的计算能力、数据可用性和廉价的存储成本。
定量分析师、软件工程师、业务分析师、测试人员、机器学习工程师、支持人员、开发人员、项目经理到销售主管都可以在数据科学领域工作。
文章目标
这是本文的四个部分:
- 数据科学家——魔术师
- 专家数据科学家的基本技能
- 数据科学项目阶段
- 数据科学常见陷阱
- 数据科学最佳实践
本文将介绍什么是数据科学以及它是如何工作的。我还将概述成为一名成功的数据科学家所需的技能。此外,本文将概述成功的数据科学项目的 10 个阶段。然后,文章将提到我们在数据科学建模项目中面临的最大问题,以及我建议每个人都熟悉的最佳实践。
我还将强调成为专家数据科学家所需的技能。有时,很难写下所有的东西,但我会尝试向有抱负的数据科学家提供我推荐的所有信息。
1.数据科学家——魔术师
专家数据科学家可以利用他们的高级编程技能、卓越的统计建模知识、领域知识和直觉,提出本质上具有创造性的项目想法,可以降低成本,并为企业带来可观的收入。
我们所需要做的就是在谷歌趋势中找到术语数据科学,来想象这个领域有多受欢迎。随着时间的推移,人们的兴趣越来越浓,而且还在不断增长:
2004 年以来数据科学的全球趋势。来自谷歌趋势
数据科学项目可以解决曾经被认为不可能解决的问题。
专注于数据科学的解决方案将技术推向了更高的水*。我们看到了试图模拟波动性、价格和实体行为的解决方案。许多公司已经实现了奇妙的数据科学应用,而不仅限于 FinTech 或 BigTech。
在我看来,数据科学的保护伞要求个人具备多种多样的技能;从开发、分析到 DevOps 技能。
2.专家数据科学家的基本技能
要成为一名成功的专家数据科学家,必须具备正确的基础。
首先,数据科学是计算机科学和统计学的一个分支。因此,它涉及到一个人获得计算机科学和统计知识。关于它没有两种方法。虽然知识越多越好,但是我们不能一下子学会所有的东西。有些领域比其他领域更重要。本文将只关注必须知道的技能。
成为专家数据科学家的关键技能。作者图片
在这一部分,我将概述所需的技能。在我看来,只有 4 个主要技能。
1.领域知识
在我们发明解决方案之前,我们需要了解问题。理解这个问题需要非常了解这个领域。
本部分的必备技能是:
- 了解领域内当前的问题和解决方案,以突出低效之处
- 对用户工作方式的深刻理解
- 收入是如何产生的,在哪里可以节省成本
- 了解谁是决策者以及难点是什么
该领域的用户是该领域最好的老师。成为某个领域的专家需要数年时间。在我看来,了解该领域的最佳方式是为用户编写一个数据科学应用程序,以解决该领域中最棘手的问题。
2.讲故事
对于数据科学家来说,最重要的技能是讲述并销售故事的能力,这一点我怎么强调都不为过。讲故事的必备技能是:
- 富有想象力
- 开放实验
- 自信地清晰简明地表达观点
- 有说服力的
- 自信地寻求领域专家的指导
- 展示数据和方法
最重要的是,关键要素是真诚地相信你的想法。
因此,最重要的技能本质上是一种软技能。相信我,只要训练得当,我们都能掌握它。
故事应简明清晰地解释提议的数据科学解决方案要解决的问题。决策者应该能够确定问题确实存在,并且建议的解决方案可以帮助他们削减成本并产生更多收入。
没有讲故事,几乎不可能获得企业资助的项目,而没有项目,数据科学家的技能可能最终会腐烂。
利益相关者询问的常见问题围绕着时间线,这些时间线严重依赖于所需的数据。因此,帮助他们理解那些所需的数据集是什么以及到哪里去获取它们是至关重要的。
在项目生命周期中,解决方案通常会呈现给几个用户。因此,一个人应该能够自信地表达他/她的想法。
因此,关键技能是能够讲述您的数据科学项目的故事,并向目标受众介绍正确的细节。
3.技术技能:
我们需要能够至少准备一个原型,然后将其生产成生产质量的软件。
必备的技能是能够:
- 创建类、函数、调用外部 API,并彻底理解数据结构。
- 通过编程(SQL + Python)从各种来源加载和保存数据的专业知识
- 能够编写函数将数据转换成适当的格式
很明显,理解使用微服务架构设计可伸缩的技术解决方案,以及持续集成和部署的知识是很重要的,但是我现在有意避免提及这些技能,我只关注必须知道的技能。还是那句话,知识越多越好,但是在这里,让我们只关注那些必须知道的。
虽然我们可以使用 R 编程语言,我也写过关于 R 的文章,但是我推荐并且更喜欢使用 Python 编程语言。Python 已经开始成为数据科学家事实上的选择。此外,还有巨大的社区支持。我还没有发现一个关于 Python 的问题没有得到回答。
Google trends 显示 Python 的受欢迎程度正在不断提高:
2004 年以来的全球 Python 趋势。摘自谷歌趋势
因此,技术部分的第一个关键技能是牢固掌握 Python 编程语言。
我强烈推荐阅读这篇文章:
在一篇文章中你需要知道的一切
medium.com](https://medium.com/fintechexplained/everything-about-python-from-beginner-to-advance-level-227d52ef32d2)
众所周知,数据科学家花费大量时间设计收集数据的解决方案。大多数数据本质上要么是基于文本的,要么是数字的。另外,数据集通常是非结构化的。Python 包的数量是无限的,要了解所有的包几乎是不可能的。有 3 个套装我一直推荐给大家:熊猫,Numpy 和 Sci-kit Learn。
Numpy 是数据科学生态系统中广泛使用的包。它在处理数字时非常高效和快速。Numpy 允许我们处理数组和矩阵。
了解如何做到这一点很重要:
- 创建集合,如数组和矩阵。
- 如何转置矩阵
- 执行统计计算并保存结果。
我强烈推荐这篇文章。理解 Numpy 就足够了:
在重新发明车轮之前了解 NumPy 特征
medium.com](https://medium.com/fintechexplained/why-should-we-use-numpy-c14a4fb03ee9)
最后,由于我们大部分时间都花在玩数据上,我们严重依赖数据库和 Excel 电子表格来揭示数据集中隐藏的秘密和趋势。
为了找到这些模式,我推荐读者去学习熊猫图书馆。在熊猫的特性中,有必要了解:
- 什么是数据帧
- 如何将数据加载到数据帧中
- 如何查询数据框
- 如何连接和过滤数据框架
- 操纵日期,填补缺失值,并执行统计计算。
我推荐这篇文章,它正好解释了这一点:
不先探索熊猫就不要编码 Python
medium.com](https://medium.com/fintechexplained/did-you-know-pandas-can-do-so-much-f65dc7db3051)
只要我们能够操纵数据并将其转换为适当的结构,我们就可以使用 Python 来调用任何机器学习库。我推荐每个人阅读 Scikit-learn 库的接口。
4.统计和概率技能
调用机器学习模型本质上是技术性的,但解释和增强模型需要概率和统计的知识。
让我们不要将机器学习模型视为黑盒,因为我们必须向利益相关者、团队成员解释工作方式,并在未来提高准确性。
统计和概率部分的必备技能是:
- 概率分布
- 取样技术
- 模拟技术
- 力矩的计算
- 透彻理解精度测量和损失函数
- 回归和贝叶斯模型理解
- 时间序列知识
一旦我们有了数据,理解数据的底层特征是很重要的。所以我推荐大家去了解统计和概率。特别是,关键是要掌握各种概率分布,它们是什么,采样如何工作,我们如何生成/模拟数据集,以及执行假设分析。
除非我们了解统计学领域,否则机器学习模型可能看起来是一个黑箱。如果我们知道统计学和概率是如何工作的,那么我们就能理解模型并自信地解释它们。
有两篇必读的文章,我推荐大家阅读,以便牢固掌握统计学和概率。
第一篇文章将解释概率的关键概念:
为统计学家解释概率的关键概念
towardsdatascience.com](/understanding-probability-and-statistics-the-essentials-of-probability-for-data-scientists-459d61a8da44)
第二篇文章概述了统计推断技术:
构成数据科学家工作基础的一个必须知道的话题
towardsdatascience.com](/understanding-probability-and-statistics-statistical-inference-for-data-scientists-fd05bde7d63)
虽然神经网络、激活函数、耦合函数、蒙特卡罗模拟和伊藤微积分的知识很重要,但我想专注于必须知道的统计和概率技能。
随着我们开始从事更多项目,我们可以研究高级/专家级架构和编程技能,了解深度学习模型如何工作,以及如何部署和监控数据科学应用,但有必要建立正确的基础。
3.数据科学项目阶段
让我们首先了解一个成功的数据科学项目需要什么。
我们可以用一百万种方式分割数据科学项目,但简单来说,有 10 个关键阶段。
我将在下面解释每个阶段。
数据科学项目的前 5 个阶段。作者图片
1.理解问题
这是数据科学项目的第一阶段。它需要获得对目标业务领域的理解。
它涉及数据科学家与企业主、分析师和团队成员的沟通。首先了解领域中的当前流程,以发现是否有任何低效之处,以及解决方案是否已经存在于领域中。关键是尽早向用户呈现问题和解决方案。
2.阐明解决方案
然后,选择的解决方案清晰地呈现给决策者。项目的输入、输出、交互和成本已经确定。
这种技能有时需要很长时间才能掌握,因为它需要销售和分析技能。人们需要非常好地理解这个领域,并向决策者推销这个想法。
关键是评估概念的合理性,写下并同意所有的假设和基准结果。总是选择一个基准过程。它可以是当前的过程,目标是产生一个优于当前基准过程的解决方案。用户通常知道并理解基准流程。始终记录基准解决方案的工作情况,因为这将有助于比较新的解决方案。
重要的是要提到这些假设,记下它们,并得到用户的确认,因为应用程序的输出将取决于这些假设。
3.数据探索
现在问题陈述已经确定,这一步需要研究项目中所需的数据。它涉及到寻找数据的来源以及所需的数量,例如历史数据的记录数或时间线,以及我们可以从哪里获取数据。
因此,它需要商业和定量分析技能。
4.数据采集
这一阶段主要是与数据团队进行沟通,并获取所需的数据。它需要使用高级编程技术构建工具来收集数据,并将数据保存到存储库中,如文件或数据库。可以通过 web 服务调用、数据库、文件等提取数据。
这需要调用 API 并将它们保存在适当的数据结构中的技术技能。我还建议每个人都充分理解 SELECT SQL 语句。
5.数据转换
此阶段需要检查每个要素,了解我们需要如何填充缺失值,需要执行哪些清理步骤,是否需要操纵日期,是否需要对数据进行标准化或规范化,和/或从现有要素创建新要素。
这个阶段需要理解数据统计概率分布。
数据科学项目的最后 5 个阶段。作者图片
6.模型选择和评估
这一阶段需要将数据输入统计机器学习模型,以便它们能够解释数据、训练自己并输出结果。然后,我们需要调整所选模型的参数,以获得最高的精度。这个阶段需要理解统计模型和准确性度量。有大量的模型可用,每一个都有其优点和缺点。
大多数机器学习模型已经用 Python 实现了,并且它们是公开可用的
关键是要能够知道调用哪个模型,用什么参数,以及如何衡量其准确性。
将输入数据分成三部分至关重要:
- 训练-用于训练模型
- 测试-用于测试已训练模型的准确性
- 验证-用于在微调模型超参数后提高模型准确性
我强烈推荐这篇文章。它以直观的方式解释了端到端的机器学习过程:
解释如何通过简单的步骤构建成功的机器学习模型
medium.com](https://medium.com/fintechexplained/end-to-end-guide-for-machine-learning-project-146c288186dc)
首先选择一个简单的模型也是明智的,它可以给你预期的结果。这个模型被称为基准模型。回归模型传统上是很好的基准模型。
回归模型很简单,可以在早期发现数据集中的错误。记住过度拟合是一个问题,模型越复杂,就越难向用户解释结果。因此,总是先查看更简单的模型,然后应用正则化来防止过度拟合。我们还应该利用 boosting 和 bagging 技术,因为它们可以根据频率对观测值进行加权,并提高模型预测能力。一旦我们穷尽了简单的模型,只有这样我们才应该研究高级模型,比如深度学习模型。
要了解神经网络是如何工作的,请阅读本文:
让我们来理解革命的概念
medium.com](https://medium.com/fintechexplained/understanding-neural-networks-98e94251fb97)
7.测试应用程序
这个阶段需要测试当前代码,以确保它按照预期工作,从而消除任何模型风险。我们必须对测试和 DevOps 技能有一个很好的理解,以便能够实现可以在每次签入时运行的持续集成构建计划。
构建计划应该执行代码的检验,运行所有的测试,准备代码覆盖报告,并产生所需的工件。我们的测试还应该包括向应用程序提供意想不到的数据。我们应该对应用程序进行压力和性能测试,并确保所有的集成点都能工作。关键是在您的解决方案中构建单元、集成和冒烟测试。我还建议构建一个行为驱动的测试框架,确保应用程序按照用户需求构建。我用过behave
Python 包,推荐给大家。
8.部署和监控应用程序
这个阶段包括将代码部署到用户可以测试和使用应用程序的环境中。这个过程需要在没有任何人工干预的情况下运行。这涉及到实现持续部署的 DevOps 技能。它应该获取工件并部署解决方案。该流程应该运行冒烟测试,并通知用户应用程序已成功部署,这样一切都是自动化的。
我们经常被要求在容器中使用微服务架构来跨服务器水*部署解决方案。
解决方案启动后,我们需要对其进行监控,以确保它正常运行,没有任何问题。我们应该记录所有的错误,并实现我们可以调用的心跳端点,以确保应用程序成功运行。
数据科学项目不是一次性的项目。他们需要持续监控获得的数据集、问题和解决方案,以确保当前系统按预期运行。
我强烈推荐这篇旨在从基础知识方面解释微服务架构的文章:
微服务架构越来越受欢迎,几乎在所有主要的软件项目中都有使用。这是…
medium.com](https://medium.com/fintechexplained/what-is-microservices-architecture-1da41a94a29b)
9.应用结果演示
我们现在准备展示结果。我们可能希望构建一个 web 用户界面,或者使用其他工具中的报告工具(如 Tablaue)向用户呈现图表和数据集。
这又回到了领域和讲故事的技巧。
10.回溯测试应用程序
最后,对应用程序进行回测也很重要。一旦模型被激活,我们想要确保它总是按照我们期望的那样工作。验证模型的一种方法是输入历史数据,并从数量和质量上验证结果。
当前项目成为下一阶段的基准。接下来的阶段可能涉及改变数据集、模型,或者只是微调超参数。
很难找到一个人可以独立完成所有阶段的工作,但这些超级明星在行业中确实存在,他们是专家数据科学家。获得所有需要的技能需要多年的努力,是的,只要有合适的项目和足够的时间,这是可能的。在一个阶段比另一个阶段更自信当然很好,但关键是对每个阶段都有足够好的理解。
4.数据科学常见陷阱
在从事数据科学项目时,记住常见的陷阱很重要。
这一部分将提供陷阱的概述。
数据科学项目的常见陷阱。作者图片
错误的输入数据
确保你的输入数据是高质量的,否则,你将花费大量的时间来产生一个对用户没有好处的解决方案。
错误的参数
模型本质上是一组函数。函数的参数通常是基于直觉和知识来校准和决定的。确保参数是正确的,并且参数中传递的数据是好的,这一点很重要。
错误的假设
验证关于数据和模型及其参数的假设是很重要的。错误的假设最终会浪费大量的时间和资源。
型号的错误选择
有时我们选择了错误的模型,并给它输入正确的数据集来解决正确的问题。不出所料,应用程序会产生无效的结果。有些模型只适合解决特定的问题。因此,一定要确保所选择的模型适合您试图解决的问题。
编程错误
数据科学项目有时需要大量编码。在实现不正确的映射、数据结构、函数和一般的编码错误时,这些错误会被引入到项目中。关键是在您的解决方案中构建单元、集成和冒烟测试。我还建议建立一个行为驱动的测试框架。
软件工程师/分析师/经理或任何人都可以成为专业的数据科学家,只要他们继续在数据科学阶段工作
5.数据科学最佳实践
最后,我想概述一下数据科学项目的最佳实践。
最佳实践。作者图片
- 注意模型的优缺点。不是所有的模型都能解决所有的问题。如果您选择了*滑技术,那么请确保您了解衰减因子以及该方法是否有效。始终选择一个简单的基准模型,并尽早将结果呈现给最终用户。另外,将数据分为训练集、测试集和验证集。
- 始终构建一个基准模型,并向领域专家展示结果。根据预期输入测试项目,以确保结果符合预期。这将澄清假设和输入数据集的大部分问题。
- 根据意外输入测试项目,以确保结果不是意外的。对项目进行压力测试极其重要。
- 测试应用程序的性能。必须确保它能够处理大型数据集和请求。从一开始就建立 CI/CD 计划。
- 总是根据历史数据对你的模型进行回溯测试。输入历史数据来统计遇到的异常数量是很重要的。由于历史不会重演,关键是要记住回溯测试可以验证项目实施时设定的假设。因此,总是对模型进行回溯测试。
- 记录和评估提议方法的合理性,并监控应用。确保领域的业务用户在项目的早期阶段和各个阶段都参与进来。持续监控应用程序,以确保它的响应和工作符合预期。
摘要
感谢您的阅读。作者图片
数据科学是当今非常热门的学科。本文概述了成为一名成功的专家数据科学家所需的技能。然后,它概述了我们在数据科学模型构建项目中面临的最大问题以及最佳实践。
随着时间的推移,我们可以开始收集更多的数据,例如并行运行代码或构建容器来启动应用程序,或者模拟数据来预测模型。然而,这里的关键点是概述必须知道的技能。
让我知道你的想法。
理解 T5 模型:文本到文本转换转换器模型
介绍
最*几年已经有太多的预训练模型被开源到 NLP 社区,比如乌尔姆菲特、伯特、 GPT 等等。鉴于这种庞大模型的规模,考虑到所需的数据量和计算量,从头开始训练这种网络几乎是不可能的。这就是一种新的学习范式“迁移学习”开始发挥作用的地方。迁移学习是机器学习中的一个研究问题,它专注于存储在解决一个问题时获得的知识,并将其应用于不同但相关的问题。这个想法是使用预先训练好的网络权重,并对其进行微调,以完成手头的一些特定任务。我们希望利用网络权重的事实要求它首先在一个非常大的高质量语料库上进行训练,以学习语言结构、语法和语义。大多数现有的模型,如乌尔姆菲特、GPT,都是用维基百科和谷歌新闻数据集上的语言模型目标进行预训练的。然而,另一方面,伯特接受了 MLM(掩蔽语言模型)目标的训练。在这篇文章的后面,我们将会看到什么是 MLM,以及 T5 是如何以相似的目标进行训练的,只是做了一些一般化的调整。只是为了确保每个人都在同一页上,一个语言模型是一个机器学习模型,它查看句子的历史部分并预测句子中的下一个单词。
T5:****Text-To-Text-Ttransfer-Ttransformer模型提出将所有 NLP 任务重新组织成一个统一的文本到文本格式,其中输入和输出总是文本串。这种格式使一个 T5 型号适合多种任务。从特色动画中可以看出,它从左侧为各种 NLP 任务接收文本输入,并为相应的任务输出文本。我们将在下面的章节中看到更多关于模型如何被训练的内容。在此之前,我想讨论一下用于预训练模型的数据。作者将其命名为 C4 (庞大干净的爬行语料库)。它的大小约为 700GB,是清理后的版本通用抓取数据集。作者提到了仅提取英文文本、删除代码行、重复数据删除等意义上的清理。这是一个高质量的预处理英语语料库,他们已经提供了下载。此外,T5 模型在 C4 上进行了预先训练,在许多 NLP 基准测试中取得了最先进的结果,同时具有足够的灵活性,可以针对各种重要的下游任务进行微调。
培训目标
第 10 页—https://arxiv.org/pdf/1910.10683.pdf
T5 的训练目标也与 BERT 的相同,即稍加修改的掩蔽语言模型。掩蔽语言模型是双向模型,在任何时候 t,单词的表征都是从它的左右语境中导出的。T5 采用的细微差别是用单个掩码关键字替换多个连续的标记,这与对每个单词使用掩码标记的 BERT 不同。从上图中可以看出,通过添加扰动,原始文本被转换为输入和输出对。由于最终目标是训练一个输入文本和输出文本的模型,目标被设计成产生一个序列,与 BERT 不同,它试图通过最终的前馈和 softmax 在输出级别输出一个单词(本身)。
该模型在 C4 语料库 (如上所述)上进行训练,其目的与预训练的一部分相同。然后,它在各种任务上进行微调,如语言翻译、摘要、句子相似度等。微调是通过向每个输入中添加特定于任务的前缀文本来显示模型 I/O 文本对。例如— 将英语翻译成德语:< text > ,添加这样的前缀使模型能够针对手头的特定任务调整其权重,并且通过缩小其生成范围,仅产生该任务的预期输出。所有的任务本质上都有相同的目标、训练程序和解码过程。作者还声称,他们没有发现任何一个案例,其中模型变得混乱,并输出完全随机的东西或另一项任务的预期输出。一件非常有趣的事情是,他们甚至将回归任务建模为文本生成目标,比如句子相似度。为了缩小实数的范围,他们生成了一个 0 到 5 之间的 0.2 量化的数字,这意味着,该模型只能生成 0.2 差异的数字,例如-3.2,3.4,3.6 等。LR 计划、符号化、序列长度等训练级别细节可在 3.1.2 中详细阅读。培训科。
作者进行了广泛的超参数调整和跨各种任务的测试。下图显示了不同级别的调整-
- 预训练风格 —他们尝试了典型的自回归风格语言建模目标、BERT 风格掩蔽语言模型目标和去混洗去噪目标。他们发现 BERT 风格(缺失上下文预测)是预训练模型的最佳选择。
- 讹误方案——他们试验了 3 种讹误策略,屏蔽一个随机单词,屏蔽一个跨度(超过 1 个连续单词),从输入中删除一个单词。考虑到手头的任务类型,即两个 I/O 都是文本字符串,破坏 span 最适合它们。
- 腐败率-在尝试了不同的腐败率后,他们发现所有腐败率的表现几乎相同,其中 15%的腐败率略好。
- 讹误长度 —他们还试验了不同的讹误跨度长度,发现跨度长度越大,模型的性能越差,这似乎也是真的,考虑跨度长度等于句子长度将意味着模型从空输入中产生文本,使其具有高度可变性的灵活性。
第 19 页—【https://arxiv.org/pdf/1910.10683.pdf
我也鼓励读者阅读第 32 页:反思来理解培训模型的要点。
演示
本节将重点对预训练的 T5 模型进行推理。所有代码已经提交到Github:Text-to-Text-Transfer-Transformer。随意克隆和玩。此外,不要忘记开始回购,以防你喜欢它。
针对各种预培训任务的 T5 模型演示。-prak har 21/T5-文本到文本转换器
github.com](https://github.com/prakhar21/T5-Text-to-Text-Transfer-Transformer)
你的母语不是英语吗?查看多语言字幕视频解说版本(同样,更详细)——
参考文献
1。谷歌 AI 博客—T52。https://arxiv.org/pdf/1910.10683.pdf
你也可以查看我写的其他研究论文的解释
我希望这本书值得你花时间阅读。谢谢大家!
【https://prakhartechviz.blogspot.com】最初发表于。
理解文本向量化 I:如何拥有一袋单词已经显示了人们对你的产品的看法
自然语言处理
Sklearn 管道、SHAP 和面向对象编程在情感分析中的应用
你知道吗,在神经网络变得无处不在之前,我们已经(几乎)解决了情感分析问题?
在当前自然语言处理(NLP)越来越依赖深度学习模型来产生惊人性能的时代,我们经常忽略最简单类型的文本矢量化技术的重要性——单词袋(BOW)和词频-逆文档频率(TF-IDF)。事实上,通过这两种技术,我们已经能够以超过 80%的准确率预测一段给定文本的情感。换句话说,我们对最先进的深度学习模型所做的一切只是试图从这些分类问题中挤出更多的性能改进。在某种程度上,几乎所有更复杂的模型都依赖于 BOW 和 TF-IDF 的概念。在这篇博文中,我们将在 sklearn pipelines 的帮助下,尝试理解和实现 BOW 和 TF-IDF。
为什么选择 Sklearn 管道?
您可能会认为使用管道是一种极端的大材小用。事实上,实现管道需要在面向对象编程(OOP)中大量使用,这可能会使调试变得相当困难。虽然我花了两周的时间来实现 BOW 和 TF-IDF(其间我犯了一些错误,在另一篇博客中会有更多的介绍),但这绝对值得花时间和精力。通过使用 sklearn 管道,我们能够在单个函数调用中使用原始数据进行转换和训练/预测。例如,如果我们想使用 sklearn 的 TF-IDF 矢量器,训练 logistic 回归模型,我们只需使用以下几行代码。
**from** sklearn.feature_extraction.text **import** CountVectorizer
**from** sklearn.linear_model **import** LogisticRegression
**from** sentiment_analysis.models.model **import** StreamlinedModellogistic = StreamlinedModel(
transformer_description="Bag of words",
transformer=CountVectorizer,
model_description="logisitc regression model",
model=LogisticRegression,
)
TfidfVectorizer
和LogisticRegression
是来自 sklearn 的模块对象,而StreamlinedModel
是我们将要实现的管道对象。正如你可能已经看到的,有了这个结构,我们将能够很容易地用任何变形金刚和模型交换上述两个对象——这个结构将保持界面的通用性,并使特征提取变得轻而易举。还有一些其他真正重要的原因,我们希望以这种方式构建模型,但这将在另一篇博客文章中讨论。
实现流线型模型
要实现StreamlinedModel
类,我们应该回忆一下我们熟悉的大多数 sklearn 包是如何使用的。举个例子,
logistic = LogisticRegression()
logistic.fit(X_train, y_train)
logistic.predict(X_test)
我们的StreamlinedModel
将有相同的行为,因此我们将在类中添加相同的.fit
和.predict
方法。其他有用的方法包括.predict_proba
和.score
。这些方法可以只是 sklearn 的BaseEstimator
中相同方法的包装器。例如,管道培训模型可以简单地是
然后,我们可以构建一个名为make_model_pipeline
的类方法,它在类实例化时使用 4 个参数,并创建一个 sklearn 管道对象。
这个self.pipeline
就是我们可以称之为.fit
和.predict
等的同一个对象。就是这样!我们已经创建了StreamlinedModel
。下面是完整的实现
词汇袋模型
由于可以灵活地将变压器转换为我们选择的任何变压器,我们现在可以将相同的接口用于我们定制的 BOW 和 TF-IDF 变压器。出于区分的目的,我们将我们定制的弓变压器称为WordFrequencyVectorizer
。同样的StreamlinedModel
可以以如下方式使用
**from** models.feature **import** WordFrequencyVectorizer
**from** sklearn.linear_model **import** LogisticRegression
**from** sentiment_analysis.models.model **import** StreamlinedModellogistic = StreamlinedModel(
transformer_description="Bag of Words",
transformer=WordFrequencyVectorizer,
model_description="logisitc regression model",
model=LogisticRegression,
)
在这篇博文中,我们将重点讨论单词包模型的实现,而将 TF-IDF 留给下一篇。
大意
弓模型的想法非常简单。我们希望找到能够强烈反映积极或消极情绪的特定单词的分数。例如,一篇评论包含更多积极的词,如“棒极了”,“棒极了”会被认为比一篇评论包含“一般”这样的词更积极。由于评论都有不同的长度,我们需要用 0 填充每个评论中没有包含的单词。例如,如果我们有以下 3 个评论,
I love dogs, I think they have adorable personalities.
I don't like cats
My favorite pet is a bird
我们会让每一个独特的词占据一个特定的位置。那些没有包含在评论中的单词将被填充为 0,而出现不止一次的单词将被增加到它们出现的次数。
矢量化的评论
现在我们了解了 BOW 模型如何工作的一般概念,是时候看看我们如何将这个概念应用到我们的情感分析任务中了。我们将从从数据源加载评论开始。
加载评论
我们将在这个项目中使用的数据是来自约翰·霍普斯金大学多领域情感数据集的评论。一旦我们下载并解压缩数据,我们将看到一系列具有以下结构的文件夹。
/reviews
|--/books
|--positive.review
|--negative.review
|--/dvd
|--positive.review
|--negative.review
|--electronics
|--positive.review
|--negative.review
|--kitchen_&_house_supplies
|--positive.review
|--negative.review
我们希望将所有正面/负面的评论合并在一起,这样我们就不需要分别引用各个文件夹。我们将创建一个名为LoadReviews
的类来实现这个目标。这个类的完整实现如下所示
由于评论是以 XML 格式存储的,我们必须使用BeautifulSoup
将评论解析成文本字符串。实际的文本评论存储在标签review_text
中。在阅读文本评论时,我们还会过滤掉不是 Unicode 字符串的字符。静态方法strip_non_printable
完成这样的任务。所有处理过的评论都将存储在类别属性reviews
中。正如您可能注意到的,我们的 BOW 模型将只对单个单词令牌而不是长字符串进行操作,我们将需要采取进一步的措施来将评论预处理成 BOW transformer 可以使用的正确的矢量化格式。
文本预处理
为了开始预处理步骤,我们将首先把每个评论的长 str 格式转换成评论的标记化版本,这可以通过用空格分割字符串来容易地完成。比如字符串“我爱 nlp”就会变成列表[“我”、“爱”、“NLP”]。
tokens = s.split()
使用 split 可能会导致常见的边缘情况,即单词有标点符号,但中间没有空格,例如“hello”。为了避免这个单词作为“hello”被区别对待,我们将去掉所有的标点符号,用空白替换它。
s = s.translate(str.maketrans(string.punctuation,
" " * len(string.punctuation)))
很多没有强烈感情指示的词,如“我”、“他们”、“一个”,但还是会占空格。我们将继续从唯一单词列表中删除它们,使它们不再占据位置。
tokens = [t for t in tokens
if t not in stopwords.words("english") + [""]]
此外,不同形式的相同词语,如“work”到“worked”,需要被视为相同词语。这将要求我们对所有单词进行词干分析和词序分析,使其恢复到原始形式,这可以通过使用 NLTK 的词干分析器来完成。
tokens = [wordnet_lemmatizer.lemmatize(t) for t in tokens]
最后,我们还将删除长度小于 2 个字符的单词,因为它们可能对情感没有什么意义。
tokens = [t for t in tokens if len(t) > 2]
所有这些步骤都可以打包成一个名为WordTokenizer
的实用程序类,实现如下。
准备好所有这些组件后,我们现在准备实现 BOW 模型。
BOW 实现
由于模型将被StreamlinedModel
对象用作转换器,我们需要遵循 sklearn 管道构建定制转换器的指导方针。这意味着对象需要有一个.transform
和一个.fit
方法。.fit
方法将负责保存训练中的特定参数的转换,并在预测过程中应用它们。具体来说,在我们的情况下,数据集中包含的唯一单词列表将在训练期间拟合,但在预测期间不会再次重新拟合。
word_index_mapping
将是对象ReviewProcessor
的一个属性,本质上是所有唯一单词的枚举。如果预测过程中的单词不是唯一单词列表的一部分,我们还会将向量的最后一个位置设置为“未知单词”。
.transform
方法用于将输入的原始文本转换成词频向量。
get_word_frequency_vector
方法将接受检查,并根据预先计算的word_to_index
映射增加向量中相应索引处的值。如果输入的审查文本包含未知单词,则word_frequency_vector
的最后一个索引将递增。
弓形变压器的完整实现如下所示
现在我们已经将 BOW 实现为一个变压器,我们可以使用StreamlinedModel
构建 4 个不同版本的模型并比较性能。
绩效评估
正如StreamlinedModel
能够使用任何转换器一样,我们也可以通过交换model
参数来使用不同的机器学习模型。例如,之前我们通过调用LogisticRegression
对象使用了逻辑回归模型,我们可以将它更改为 lightGBM 模型,如下所示
**from** models.feature **import** WordFrequencyVectorizer
**import** lightgbm **as** lgb
**from** sentiment_analysis.models.model **import** StreamlinedModellgbm = StreamlinedModel(
transformer_description="Bag of words",
transformer=WordFrequencyVectorizer,
model_description="logisitc regression model",
model=lgb.LGBMClassifier,
)
我们将尝试 4 种不同的模型,并收集它们的预测 AUC 分数,以制作柱状图。
使用逻辑回归模型已经可以得到 0.82 的 AUC 分数。
LightGBM 模型还帮助我们将性能提高到 0.888,显示了树模型比线性模型更好的预测能力。
最有趣的结果来自两种不同的朴素贝叶斯模型,它们产生了截然不同的结果,高斯朴素贝叶斯比多项式朴素贝叶斯表现得更差。由于高斯朴素贝叶斯模型被设计为对连续变量进行操作,而词频只是离散的,因此低于标准的性能是意料之中的。
您可能已经注意到,在这个情感分析任务中,相对简单的多项式朴素贝叶斯模型取得了与相当高级的 lightGBM 模型相当的性能。那么我们的模型是如何做出决定的呢?让我们仔细看看。
错误分类的正面/负面评论
由于有多个评论被错误分类,我们将只显示与正确标签距离最大的评论。为此,我们将对预测概率进行排序,打印出第一个/最后一个(取决于标签)
# get the indices of the misclassified reviews
wrong_positive_inds = np.where((y_test == 1)
& (y_pred != y_test))[0]
wrong_negative_inds = np.where((y_test == 0)
& (y_pred != y_test))[0]# most wrong positive review
most_wrong_positive_index = wrong_positive_inds[
y_prob_lgbm[:, 1][wrong_positive_inds].argmax()
]# most wrong negative review
most_wrong_negative_index = wrong_negative_inds[
y_prob_lgbm[:, 1][wrong_negative_inds].argmin()
]
最错误的正面评价
这是一篇相当长且详细的正面评论,提到了这本书的优点和缺点。在弱点部分,一些负面词汇如“ 问题 ”、“ 而不是 ”被多次提及,这反过来增加了对该复习进行分类的难度。
我们来看看最错误的负面评论。
这个负面评论还包含一个正面词“ 有趣的 ”,没有明显的负面词(可能“ 过时的”是一个负面词),使得将这个评论归类为负面的可能性更小。
那么模型认为一篇评论到底哪些词是正面/负面的呢?接下来我们来看看。
特征重要性
在这一点上,我们经常会提出如何在两个性能最好的模型(LightGBM 和多项式朴素贝叶斯)之间进行选择的问题。答案在于互操作性。由于朴素贝叶斯依赖于计数和概率,我们可以很容易地找出哪些词对情感有贡献。然而,LightGBM 模型并不那么简单。由于大量独特的词,树分裂是相当具有挑战性的形象化。我们真的没有运气解释它们吗?
不完全是。在模型解释包 SHAP 的帮助下,我们可以在一个单独的评论中产生一个特性重要性的可视化(更多关于 SHAP 的信息在另一篇文章中)。在下面的 SHAP 可视化图表中,红色表示预测的情绪接* 1,而蓝色表示预测的情绪为 0。下面是模型预测为正的示例审查
一个非常正面的评论的例子
由于该评论包含强烈的积极词汇,如“ 最佳 ”和“ 享受 ”,因此该评论被预测为积极的。
非常负面的评论的例子
负面评论通常由负面词汇组成,如“”、“ 失败 ”或“ 不好 ”。
结论
对于情感分析任务来说,单词包可能是最简单的文本矢量化类型,但它已经非常有效了。GitHub 项目可以在这里找到。下面是我们今天学到的内容。
- 面向对象编程极大地提高了整个工作流程的灵活性。在 sklearn pipelines 的帮助下,我们可以用同一个 BOW transformer 快速迭代和测试多个机器学习模型。
- 我们必须为底层任务使用正确的模型。高斯朴素贝叶斯模型是唯一不能获得超过 80% AUC 分数的模型。这只是一个错误的模型,因为它被设计为对连续特征进行操作。
- LightGBM 赢得了比赛(以微弱优势)。基于树的模型能够产生相当大的拟合度和最佳性能,尽管多项朴素贝叶斯的性能仅稍差。
- LightGBM 需要依靠一些特殊的解释包才能被理解,现在我们正在得到它。随着模型解释在我们的行业中变得越来越重要,我们经常被迫选择更简单的模型来实现互操作性。SHAP 的存在改变了这一切。
在下一篇文章中,我们将在单词袋模型的基础上,用一个智能加权方案来改进它。下次见!
理解文本矢量化 II:TF-IDF 如何让您的简单模型拥有与高级模型匹敌的能力
自然语言处理
Sklearn 管道、SHAP 和面向对象编程在情感分析中的应用
L 让我们继续我们的情感分析之旅。
记得上次我们谈到使用单词袋模型来检测评论文本的情绪,我们已经有了一个相对较好的性能。今天,我们将在已有成果的基础上,用智能加权方案升级单词袋模型。在本帖中,我们将利用来自第 1 部分的自定义管道StreamlinedModel
对象,并见证从逻辑回归等简单模型应用 TF-IDF 转换器所获得的惊人改进。
词频—逆文档频率(TF-IDF)
我们已经知道计算词频可以帮助我们判断评论文本的情感,但是这种方法忽略了单词在整个文档中的重要性。我们讨论了使用智能加权方案来解决这个问题,但是我们具体如何执行呢?
大意
答案是使用一种加权方案,该方案与该单词在所有文档中出现的频率成反比。如果这个词出现在几乎每一篇文档中(比如“ 我 ”、“ 你 ”或者“ 他们 ”),那么如果我们对它们进行同等的加权,很可能会被视为非常重要。因此,我们想增加那些不经常出现的单词的权重。例如,使用相同的 3 篇评论
I love dogs, I think they have adorable personalities.
I don't like cats
My favorite kind of pet is bird
我们可以看到“ 萌 ”只出现在 3 个文档中的 1 个,文档频率为 3/(1+1)。我们在分母上加 1 以避免它变成 0。然后,我们将获取文档频率的自然对数值,这将为我们提供以下内容
idf(adorable) = log(3/2) = 0.176
与在 3 个文档中的 2 个中出现的类似于“ I 的单词相比,我们将得到如下的逆文档频率
idf(I) = log(3/3) = 0
然后我们将这袋字数乘以这个权重,得到 tf-idf 值。具体来说,
tf-idf(t, d) = tf(t, d) x log(N/(df + 1))
现在我们了解了 TF-IDF 的大致思想,让我们看看我们应该如何将这个模型实现为一个转换器。
TF-IDF 实施
我们将经历与第一部分中概述的相同的文本预处理步骤。由于术语频率定义与单词袋模型相同,我们将关注如何有效地计算所有单词的逆文档频率。
因为我们必须检查每个单词是否存在于每个文档中,所以运行时间可能是 O(n),这将相当慢。我们可以将预计算的 IDF 保存在属性化的类中,并在计算词频时引用它。
我们从获取所有单词的文档频率的辅助方法开始。在 pandas .str.contains
方法的帮助下,该方法将检测一列字符串中部分字符串的存在并返回一系列 0/1,我们可以对这些系列求和以获得单词的文档频率。然后,我们将重复相同的过程,并将结果存储在字典中。
我们将在转换器的.fit
方法中使用get_document_frequency
方法,对每个单词的文档频率应用对数转换。得到的 idf 值将被保存为一个类属性,当我们计算词频时,它将被用作权重。
我们的.transform
方法将由两部分组成。除了计算词频(在 part 1 中也有),我们还会把它们乘以对应的 IDF 值。因为对于每个评论,我们将具有相同的 IDF 权重(因为单词索引顺序是固定的),所以我们将重复 IDF 向量 N 次(等于评论的数量),以使 IDF 矩阵具有与单词频率矩阵相同的形状。最后,我们将在两个矩阵之间执行元素级乘法,以获得所有评论的 TF-IDF 值。
我们现在准备将 tf-idf 用作变压器。完整的实现如下所示。正如你可能注意到的,TermFrequency_InvDocFrequency
变压器与第一部分中的WordFrequecyVectorizer
变压器共用多个部分。最好的实现是类继承。实现每一个 helper 函数仅仅是为了清晰起见,以防您对将单词包转换器作为主类不感兴趣。
现在我们已经将 TF-IDF 实现为一个变形器,我们将做同样的事情,使用StreamlinedModel
构建 4 个不同版本的模型并比较性能。
绩效评估
同 part 1 ,我们将使用以下函数原型生成 4 个StreamlinedModel
对象。
**from** models.feature **import** TermFrequency_InvDocFrequency
**import** lightgbm **as** lgb
**from** sentiment_analysis.models.model **import** StreamlinedModellgbm = StreamlinedModel(
transformer_description="TF-IDF",
transformer=TermFrequency_InvDocFrequency,
model_description="logisitc regression model",
model=lgb.LGBMClassifier,
)
我们将尝试 4 种不同的模型,并收集它们的预测 AUC 分数,以制作柱状图。这一次我们将比较我们以前获得的 AUC 分数,看看我们是否实际上能够获得任何改进。
确实有所改善。
使用我们的 tf-idf 转换器,即使使用逻辑回归模型,我们也能够获得 0.896 的 AUC。这将给我们的模型解释带来显著的优势——因为单个词的逻辑回归系数可以直接解释为对评论情感的重要性。不再需要外部包装。
我们还看到多项式朴素贝叶斯模型的性能略有提高,其 AUC 从 0.883 增加到 0.898,现在略高于旧的 winner lightGBM 模型,后者几乎没有任何改进。
还记得这篇博文的标题吗?TF-IDF 确实为逻辑回归这样的简单模型提供了击败 lightGBM 这样的高级模型的能力。这是一个完美的例子,使用正确的特性工程工具会给你带来难以置信的性能提升。
接下来我们来看看,是不是因为换了变形金刚,才发现最错误的正/差评都变了。
错误分类的正面/负面评论
我们将使用与第 1 部分相同的行来获得最错误的正/负指数。
# get the indices of the misclassified reviews
wrong_positive_inds = np.where((y_test == 1)
& (y_pred != y_test))[0]
wrong_negative_inds = np.where((y_test == 0)
& (y_pred != y_test))[0]# most wrong positive review
most_wrong_positive_index = wrong_positive_inds[
y_prob_lgbm[:, 1][wrong_positive_inds].argmax()
]# most wrong negative review
most_wrong_negative_index = wrong_negative_inds[
y_prob_lgbm[:, 1][wrong_negative_inds].argmin()
]
最错误的正面评论(来自多项朴素贝叶斯模型,因为它现在是表现最好的模型)是
老实说,我不知道这是一个积极的评论。它包含了强烈的否定词,如“”和“ 而不是 ”。我们的模型出错是可以理解的。
最错误的负面评论(来自多项朴素贝叶斯)是
我可以看出这篇评论是负面的,但是这篇评论没有任何强烈的负面词汇(见下一节),犯这个错误也是可以理解的。
特征重要性
我们已经在第 1 部分中看到了使用 SHAP 来可视化个人评论的例子,我们可以使用 SHAP 来可视化总体水*上的功能重要性。
最重要的词其实是明显的情绪指示词。
“ 伟大的 ”、“ 容易的 ”、“ 爱情的 ”作为强有力的肯定
“”、“”、“ 废物 ”为强负者**
结论
我们使用 tf-idf 作为情感分析模型的特征工程工具,改进了我们的文本矢量器。事实上,tf-idf 也是许多更复杂的特征工程工具的基础,例如 word2vec。同样,GitHub 项目可以在这里找到。现在,让我们总结一下我们在这篇博文中学到了什么。
- 获得正确的功能比使用更先进的型号更重要。使用 TF-IDF 将提高特征质量,甚至允许简单的模型胜过更高级的模型。
- 将值缓存为类属性将提高计算时间。保存每个字的 IDF 值使我们能够加快速度,否则这将是一个相当缓慢和昂贵的双 for 循环操作。
这就是文本矢量化的全部内容!接下来,我们将了解一些使用深度学习的更高级的特征提取工具。下次见!
使用数据可视化理解 2020 年美国股市下跌
使用数据可视化和历史背景分析与新冠肺炎相关的美国股票市场抛售的严重性。
如果你不知道美国股市在过去两个月下跌,那么你要么:
- 一个大学生,或者
- 一个没有投资和一生价值的学生贷款的初级工人
不管怎样,不管你是否投资了这个市场,你都应该知道过去几周发生了什么。取决于你站在哪里,你是幸运还是不幸,足以目睹一场 黑天鹅 事件。快速水*设置:Investopedia 将黑天鹅事件漂亮地定义为:
超出正常情况预期的不可预测的事件,具有潜在的严重后果。黑天鹅事件的特点是极其罕见,影响严重,而且人们普遍认为事后才明白。
在这篇文章中,我将量化 2020 年美国股市下跌的影响,并使用数据科学将其与*期(和不太*期)历史上的其他主要下跌进行比较。我将收集数据来帮助构建我的案例,构建比较统计数据来给出背景,并构建可视化来传达重要的想法。
最后一件事,我只使用公开可用的数据,所以本文中使用的数据可视化和原始数据的所有代码都可以在我的项目 GitHub 中获得。现在,让我们开始吧!
量化过去几周的市场动荡
我们可以使用一些不同的数据集来理解市场中的绝对混乱。让我们从分析过去 3 个月(01/02/20–03/20/20)标准普尔 500 的标准统计数据开始。这是我的数据集的样子:
显示标准普尔 500 的开盘价、最高价、最低价、收盘价、变化率和成交量的标准数据集
你可能每次在网上搜索股票时都会看到这些描述符;现在,让我们做一些分析来更好地理解它们。我将首先展示简单的价格/时间图表,它已经揭示了很多信息。
你可以清楚地看到,大约在 2 月中旬(02/19),市场波动开始急剧增加。请注意,波动性的增加是重点,而不一定是趋势是上升还是下降。让我们更进一步,添加一个随时间变化的市场交易量的可视化,它显示了发生了多少交易。
现在你也可以看到市场波动和交易量都增加了,特别是在过去几周。至少在表面上,从上面的图表中你可以看到的另一件事是,成交量第一次真正暴涨是在市场开始几次大跌后的一两天。
让我们使用另一个可视化工具来查看价格和市场容量的变化百分比。这些描述符使用不同的尺度,因此我们将使用派生统计来查看异常情况。一个很好的方法是使用 Z 分数。简而言之,Z 分数是一种统计数据,它让您了解一个数据点离*均值有多远。鉴于我们过去 3 个月的样本量,*均值非常偏斜,所以我将使用修正的 Z 值,它依赖于中位数,因为它对异常值和偏斜不太敏感。此外,我将只使用修改后的 z 得分的绝对值,因为我只对距离中值的距离感兴趣,而不是方向。你可以在下面看到结果。
现在我们已经确认了异常的市场百分比变化先于异常的交易量。从图表中,您可以看到在 2 月 22 日的周末发生了一些事情,导致百分比变化从中位数的大约 0.5 个标准差上升到大约 2 个标准差。在后续的帖子中,我将在那个周末探索冠状病毒病例,以进一步了解发生了什么。
虽然从 Z 分数的角度来看这个时间线是有用的,但是下面可以看到进一步捕捉市场恐慌的最后一个可视化,其中我简单地绘制了标准普尔 500 的每日%变化。注意峰值和谷值在过去几周是如何真正爆发的,以及相同数据的箱线图中异常值的数量。
我们花了几分钟单独分析美国市场的下跌。在下一节中,我们将把这次下跌与*期历史上的其他下跌进行比较,以获得更好的背景,并真正回答以下问题:
这次市场下跌有多严重?
漂亮。不好。
在理解任何时候的市场行为时,最有用的策略之一是了解一些历史背景。在本节中,我们将把 2020 年美国股市下跌与*期历史上的其他下跌进行比较,以进一步分析市场反应。
为此,我收集了市场上最* 10 次 10%以上的下跌数据。正如你所看到的,这个列表包含了大约 10-20%的提款,以及 2007-2008 年的金融危机和 2000 年的网络泡沫,它们都要严重得多。在此之后,我收集了这些时间段的每日 S%P 500 收益。我在这里的目标是,在其他统计数据中,比较 2020 年股市下跌的速度与过去 20 年的下跌速度。
在构思了一些不同的数据可视化后,我决定用一个意大利面条图来最好地展示最*下跌的严重性。可视化可以在下面看到。
正如你所看到的,我决定建立一个图表来展示一笔 10,000 美元的投资从提款开始时价值的下降。这为分析下降的严重程度创造了一个*台。从图表中可以看出,2020 年美国市场不仅投资价值的绝对跌幅更大,而且跌幅也更快。这可以在下面的图表中再次看到,我已经将每种下降分开。
好了,现在你知道了!2020 年美国市场的下跌绝不是一个小事件。在 30 天的时间里,标准普尔 500 的总市值蒸发了* 8 万亿美元。更具体地说,那超过 80 亿美元。未来,我将关注与冠状病毒相关的数据集,以进一步分析市场行为。
我希望你喜欢读这篇文章,就像我喜欢把它放在一起一样。如果您有任何问题、反馈或意见,请随时通过 LinkedIn 或我的网站与我联系。此外,本文中使用的数据可视化和原始数据的所有代码都可以在我的项目 GitHub 中获得。
每个人都要保持安全,继续创造酷的东西。
从卫星图像检测森林砍伐
数据规格,来源:星球:从太空了解亚马逊
对于我作为 Udacity 的机器学习工程师 Nanodegree 的一部分的顶点项目,我的任务是通过应用整个课程中学到的机器学习算法和技术来解决我选择的一个问题。我决定选择一个我非常热爱的领域:拯救环境。我听说过一个过去的 Kaggle 比赛,其中包括通过奇妙的 fast.ai 课程对亚马逊雨林的卫星图像进行分类。你可以在这里找到比赛。我决定踏上探测雨林砍伐的旅程,看看我能在 Kaggle 排行榜上爬多高。
背景
亚马逊是世界上最大、最具生物多样性的热带雨林,占地 210 万*方英里。它由大约 3900 亿棵树组成,分为 16000 个种类。亚马逊被称为“地球的肺”,因为它有助于稳定地球的气候,并通过固定二氧化碳和产生世界上 20%的氧气来减缓全球变暖。
自 1978 年以来,超过 289,000 *方英里的亚马逊雨林已经在巴西、秘鲁、哥伦比亚、玻利维亚、委内瑞拉、苏里南、圭亚那和法属圭亚那遭到破坏。每一分钟,世界都会失去 48 个足球场大小的森林面积。人们担心,森林的破坏将导致生物多样性的丧失、栖息地的丧失以及植被中所含碳的释放,这可能会加速全球变暖。关于森林砍伐和人类侵占森林的位置的更好数据可以帮助政府和当地利益攸关方更快、更有效地做出反应。
亚马逊的选择性伐木,来源:星球:从太空了解亚马逊
问题陈述
这个项目的目标是利用卫星图像数据追踪由于森林砍伐导致的亚马逊雨林的变化。这些数据由 Planet 提供,由 Kaggle 主办,作为之前比赛的一部分——Planet:从太空了解亚马逊。这项任务的标签是与 Planet 的影响团队合作选择的,代表了亚马逊盆地感兴趣的现象的合理子集。该任务是一个多标签图像分类问题,其中每幅图像将具有一个并且可能不止一个大气标签以及零个或多个常见和稀有标签。
带有标签的数据示例,来源:星球:从太空了解亚马逊
估价
模型基于它们的*均 F2 分数进行评估。F 分数通常用于信息检索,它使用精度 p 和召回率 r 来衡量准确性。精度是真阳性(tp)与所有预测阳性(tp + fp)的比率。召回率是真阳性与所有实际阳性的比率(tp + fn)。F2 分数由下式给出:
F2 评分公式,来源:星球:从太空了解亚马逊
履行
对于这个任务,我选择使用 fastai 库来实现深度学习模型,该库使用现代最佳实践来简化训练神经网络。fastai 库的主要特点之一是使用循环学习率。这是一种相对未知的设置学习率的技术(由于 fast.ai 课程的流行,这种技术可能不再适用),它提高了分类精度,同时与固定学习率值相比,需要更少的迭代。这种方法不是单调地降低学习率,而是让学习率在合理的边界值之间循环变化。fastai 的另一个好处是它使迁移学习变得异常简单:
from fastai.vision import *learn = cnn_learner(data, models.resnet50)
我的最终解决方案由五个模型组成,所有模型都在 ImageNet 上进行了预训练:resnet50、resnet101、resnet152、densenet121 和 densenet169。我分别对每个模型进行了微调,将训练数据分为 80%的训练和 20%的验证。我使用逐步调整图像大小来训练模型,这是我从杰瑞米·霍华德教授的 fast.ai 课程中学到的技术。渐进式调整大小是一种训练计算机视觉模型的方法,首先在比原始图像更小的尺寸上训练模型,然后继续将图像的尺寸增加两到三倍,直到最终在原始图像尺寸上训练。我首先在 64x64 像素图像上训练模型五个时期,只有网络的最后一层是可训练的,然后再训练五个时期,整个网络都是可训练的。我对 128x128 像素的图像做同样的处理,然后是原始的 256x256 像素的图像。最后,我在所有可用的训练数据上训练了五个以上的时期,合并了训练和验证集。我还尝试了半监督学习,使用我的最佳提交的标签在测试集上进行训练,但发现我的性能略有下降。在整个训练过程中,我使用 64 个批次,而学习率是在每五个时期之前使用 fastai 的内置函数选择的,以找到最佳学习率:
learn.lr_find()
learn.recorder.plot()
你想选择下降曲线最陡部分的学习率。这里我选了 0.01。
精炼
为了获得一个基线分数来衡量未来实验的影响,我实现了一个没有渐进调整大小的 resnet50,使用 256x256 像素的图像大小。我没有使用测试时间增强,并将阈值设置为 0.20。这款车型在比赛的私人排行榜上获得了 0.92746 的 F2 分。为了提高基线分数,我首先实现了渐进式改进。我尝试了几个不同的尺寸,并选定了 64 -> 128 -> 256,这使我的 F2 分数上升到 0.92881。在利用测试时间增加后,我的分数进一步增加到 0.92979。然后,我开始尝试给预测设定阈值,看看我是否能挤出更多的零点几个百分点。通过将阈值设置为 0.17,我可以将分数提高到 0.93024。最后,我将训练集和验证集结合起来,再训练五个时期,使单一 resnet50 模型的得分达到 0.93041。
使用所有训练数据、tta 和阈值 0.17 的 resnet50 模型的结果
结果
我对其他四个模型遵循了与上述相同的过程,通过创建五个模型的组合,我能够在私人排行榜上获得 0.93173 的 F2 分数。这个分数对于 938 支参赛队伍中的第 19 名来说已经不错了,只比第一名少了 0.00144 分。
使用所有训练数据、tta 和阈值 0.17 的五个模型集合的结果
竞赛排行榜星球:从太空了解亚马逊
反思/改进
我想尝试但没有时间/资源的一种技术是使用暗通道先验的单幅图像去霾-何等人。我相信这将是非常有益的,因为许多卫星图像包括霾和/或云,这使得模型更难对森林的底层特征进行分类。事实上,竞赛的获胜者在对他的解决方案的简要描述中提到他使用了这种技术。我实际上实现了算法来处理整个数据集,但是我的本地机器仅处理训练数据就花了大约 30 个小时,而且我没有处理测试数据。这是我未来打算做的事情。
左:原稿,中间:使用导向过滤去除霾,右:去除霾
我可以尝试的另一个策略是 k-fold 交叉验证,而不是将数据分成训练集和验证集。这样,模型可以在整个过程中看到所有的训练数据,而不是分别对验证数据进行训练。第三名完成者提到在他们的解决方案中使用 5 重交叉验证。
我还可以尝试训练更多的模型或不同的架构来试验更大的整体。竞赛的获胜者提到每个不同的标签都有一个模型,第三名完成者使用了 30 个不同的模型,经过 5 次交叉验证,产生了 150 个重量文件。
总的来说,我对自己的成绩非常满意——仅用五款产品就取得了排行榜前 2%的成绩。我希望通过实施上面列出的一些可能的改进领域,达到前 10 名,甚至最好是第一名。
你可以从我的GitHub下载复制我的结果的所有代码。
理解分析开发生命周期
将您的分析从摇篮带到坟墓。
照片来自像素上的铁木尔·萨格兰比莱克
正如我在之前的一篇文章中所讨论的,理解分析开发生命周期是至关重要的。你的分析不会永远持续下去,相反,会变得过时,需要退休。随着您收集更多更新的数据,您将需要继续维护当前的分析,同时创建新的分析。了解你在分析生命周期的每个阶段需要做什么是至关重要的。如下所示,我将分析生命周期视为开发的五个关键组成部分:研发、部署、测试、验证、维护和退役。因此,让我们一起来了解每个元素吧!
分析开发生命周期——作者使用 LucidChart 创建的图像
研究与开发
研究和开发包含了分析开发生命周期的前几个步骤。收到数据后,您将花时间查看它,理解它的结构,并清理它。在进行这个过程时,您需要考虑您从数据中看到了哪些分析机会,您意识到的任何业务问题,以及它们是如何重叠的。当你开发你的问题陈述时,你可以开始测试分析概念并发展你的分析。
如果您正在使用现有的分析,此过程会有所不同。对于当前分析,您需要了解需要改进的领域、您将使用的方法以及您获得的任何主题专业知识(SME ),以帮助您完成这些更新。与 SME 进行后续对话将有助于您验证您的更新是否朝着正确的方向发展。
我发现这个过程的研究和开发阶段是最有趣的,因为在这个阶段你可以对你的数据了解得最多。我每天与其他数据科学家、数据工程师和主题专家一起工作。这些人帮助创建数据集和开发数据字典,以理解数据代表什么。然后,我可以理解如何将这些数据与其他数据集结合起来构建我的分析。我想确保我看到了更大的画面,并在继续下一步之前讲述一个令人信服的故事。
部署时的测试和验证
在与 SME 确认分析看起来符合预期并且产生了有意义的可靠结果后,就该准备部署分析了。部署过程可能有所不同,但概念是相同的。在这个阶段,我已经为我的分析开发或更新了代码,并且我结合了一个更加软件工程化的方法。
软件工程师通常在 CI/CD 管道中使用单元测试和验证技术来测试和验证代码是否按预期工作。当您测试和验证您的分析时,可以从一个小得多的数据集开始。这种测试和验证是为了确保您编写的代码能够按预期运行,不会引入任何重大缺陷或错误。在将代码部署到更大的数据集之前,添加单元测试会让您对代码运行良好感到满意。
既然您已经在小范围内测试并验证了代码,那么您可以将代码部署到测试环境中,以便在大型数据集上运行它。此时,您将希望对大多数(如果不是全部)数据运行您的代码,以验证结果是否仍然符合预期。您可以与您的 SME 进行复查,以确保自分析想法的概念或更新以来没有发生任何变化。如果分析看起来像预期的那样,您可以进入维护阶段。否则,您应该回到研发阶段来调查所看到的问题。在小范围内用单元测试和在大范围内用大部分或全部数据对测试代码进行两步验证,将有助于为经过良好测试和维护的分析代码创建一个坚实的基础。
我发现这个过程很有帮助,因为它通过单元测试显示了代码设计问题,并在大规模测试时提出了分析问题。测试和验证代码的多个步骤也确保了代码按预期执行。当使用 CI/CD 进行分析开发时,我发现代码更新的问题和迭代更少。
维护
既然您已经部署了您的分析工具,现在是时候让它过渡到维护模式了。在维护模式下,您可以监控您的分析是否存在任何缺陷或与预期结果的偏差。分析维护需要考虑的一些方面包括:
- 是否引入了新数据?您是否需要通过添加新数据来更新代码?
- 是否删除了影响您分析的数据?如果你不能解决这个问题,那么你可能需要考虑退休。
- 分析输出的表现是否符合预期,还是已经开始表现不佳?如果它表现不佳,是什么改变了?
- 你的分析试图预测的问题解决了吗,现在你的分析没有被使用?如果是这样,你可能需要考虑退休了。
- 问题陈述改变了吗?如果是的话,这对你的分析有什么影响?
- 需要重新培训一个模特吗?当您用于输入的数据发生变化或引入新信息时,可能会出现重新培训。
这些只是你在维护分析生命时需要考虑的一些领域。还有更多的可能性会出现,你需要抓住它们,评估可能的结果和下一步。
退休
退休可能是这个过程中最快的阶段。当一个分析不再有用时,是时候退出代码并转移到下一个项目了。根据您的团队,退出可能与删除代码、禁用部署或关闭结果仪表板有所不同。在这一点上,您需要向用户传达分析停止的原因和时间。这种沟通是必要的,因为您应该告知用户任何可能影响他们决策过程的重大变化。如果你用新的东西代替你的分析,也让他们知道。沟通是其他人理解你的过程的关键,没有它,事情会变得一团糟。
最后的想法
您的分析生命周期可能会有所不同,包括或删除我在这里提到的不同方面。我的想法是,分析生命周期非常类似于软件开发生命周期,因为您正在开发将被迭代、维护并最终退役的代码。唯一的主要区别是如何进行验证和测试,以确认您的工作得到了预期的结果。
我有兴趣了解更多关于这个生命周期如何与您今天所做的事情相适应的信息。你能做些改变吗?
如果你想阅读更多,看看我下面的其他文章吧!
当我参加大学讲座时,最常被问到的问题是“我需要具备什么技能?”
towardsdatascience.com](/top-8-skills-for-every-data-scientist-79e6b1faf3e1) [## 停止浪费你的时间,咨询一个主题专家
在从事数据科学项目时,请一位主题专家来审查您的工作可能会有所帮助。
towardsdatascience.com](/stop-wasting-your-time-and-consult-a-subject-matter-expert-f6ee9bffd0fe) [## 创建用于熊猫分组的自定义聚合
Pandas groupby 是一个函数,您可以在数据帧上使用它来分割对象、应用函数以及组合…
towardsdatascience.com](/creating-custom-aggregations-to-use-with-pandas-groupby-e3f5ef8cb43e) [## 数据可视化的前 3 篇文章
如果您想更好地构建数据可视化,这些文章很有帮助。
towardsdatascience.com](/top-3-articles-for-data-visualization-956a08a54b04) [## 不要太骄傲而不愿寻求帮助
如果你被一个 bug 卡住了或者感到不知所措,你可以寻求你需要的帮助。
towardsdatascience.com](/dont-be-too-proud-to-ask-for-help-76f21d16f318)
理解视频分类的支柱:I3D 架构
单个图像中的信息和视频中的信息之间的显著差异之一是时间元素。这导致了深度学习模型架构的改进,以结合 3D 处理,从而额外处理时间信息。本文通过 I3D 模式总结了从图像到视频的建筑变革。
I3D
图一。基于动力学数据集的双流 I3D 训练过程。图片由作者提供,改编自 Carreira 和 Zisserman (2017) [1]。
I3D 模型是由 DeepMind 和牛津大学的研究人员在一篇名为“ Quo Vadis,动作识别?新模型和动力学数据集【1】。本文比较了以前解决视频中动作检测问题的方法,同时还介绍了一种新的体系结构,这是本文的重点。他们的方法从 2D 架构开始,膨胀所有的过滤器和池内核。通过膨胀他们,他们增加了一个额外的维度要考虑,在我们的情况下是时间。虽然 2D 模型中的过滤器是方形的 N x N ,但是通过对它们充气,过滤器就变成了立方体的NxNxN
在这篇论文中,研究人员在已经训练好的模型上从 2D 滤波器中引导 3D 滤波器。换句话说,在他们的 3D 实现中,他们使用在非常大的数据集(如 ImageNet)上训练的 2D 模型的参数。为了直观地思考这个问题,他们重复给定图像 T 次。他们实际做的是沿着时间维度重复 2D 滤波器的权重 N 次,然后通过除以 N 来重新缩放它们。为确保正确完成,2D 案例的*均和最大池层应与 ImageNet 的 3D 案例相同。
另一个要考虑的修改是池和卷积层的接收域。为了刷新,卷积神经网络中的感受域是图像的一部分,一次只能被一个过滤器看到,随着我们堆叠更多的层,它会增加。2D 卷积和池集中在图像的高度和宽度上,因此是对称的(例如,7×7 的核是对称的,而 7×3 的核是不对称的)。然而,当包括时间维度时,找到最佳感受野是重要的,这取决于帧速率和图像维度。根据研究人员在[1]中的说法,如果感受野在时间上相对于空间增长过快,它可能会合并来自不同对象的边缘,破坏早期特征检测。如果感受野生长太慢,它可能无法捕捉场景动态。总之,由于额外的时间维度,I3D 的核是不对称的。
建筑
考虑到这些,作者用下面的图表形象化了他们的架构:
图二。图片由作者提供,改编自 Carreira 和 Zisserman (2017) [1]。
从图中可以看出,网络的起点使用不对称滤波器进行最大汇集,在空间维度上汇集的同时保持时间。直到后来在网络中,他们才运行包含时间维度的卷积和池。初始模块通常在 2D 网络中使用,不在本文讨论范围之内。然而,总的来说,它是最佳局部稀疏结构的*似。它还处理不同比例的空间(在这种情况下还有时间)信息,然后汇总结果。这个模块的目的是让网络变得“更宽”而不是“更深”。1x1x1 卷积用于减少更大的 3x3x3 卷积之前的输入通道数,也使其计算成本低于替代方案。
贡献
虽然该架构的正式介绍是本文的主要贡献,但主要贡献是从动力学数据集到其他视频任务的迁移学习。动力学人体动作数据集包含人体动作的注释视频。这就是在各种与动作相关的深度学习任务中使用预训练的 I3D 网络作为特征提取网络的原因。功能通常从“混合 5c”模块中提取出来,并传递到新的架构或 I3D 的微调版本中。
结论
I3D 是视频处理中最常用的特征提取方法之一。虽然也实现了像 S3D 模型[2]这样的其他方法,但是它们是在 I3D 架构的基础上构建的,对使用的模块做了一些修改。如果你想对视频或视频中的动作进行分类,I3D 是个好地方。如果你想为你的视频相关实验从一个预先训练好的模型中获得特征,I3D 也是值得推荐的。我希望你喜欢这个总结!
参考
[1]卡雷拉,j .,&齐塞尔曼,A. (2017)。Quo vadis,动作识别?一个新的模型和动力学数据集。在IEEE 计算机视觉和模式识别会议论文集(第 6299–6308 页)。
[2]谢,s,孙,c,黄,j,涂,z .,&墨菲,K. (2018)。重新思考时空特征学习:视频分类中的速度-精度权衡。在欧洲计算机视觉会议(ECCV) (第 305–321 页)。
*本文中的图像与[1] 中的原始图像相似
理解自然语言处理中的单词包
自然语言处理是人工智能的一个重要分支,许多有趣而重要的研究正在这里进行。作为一个机器学习爱好者,了解 NLP 的子流程很重要。
在这里,我想分享一个在 NLP 中使用的术语,并附有代码示例(链接到底部的笔记本)。“包话!!!"。
NLP 中的单词包是什么?
单词包是一种用来找出文本(段落)中重要主题的方法。有哪些话题?假设你正在阅读下面的段落,
As a pet, cat is a very useful animal and helps in protecting or saving our rashan from rats. *The offspring of a cat is called as kitten*, it is a smaller and a cuter version of a cat. Cat has got four thin, short and sturdy limbs that helps it in walking, running and jumping for long distances.It’s bright eyes help it in seeing long distances and also help during the dark. Cats are found all over the world. There is no place without a cat. Sometimes a cat can be mistaken for a tiger cub, because of its extreme similarities with it.A cat’s body is completely covered with soft and beautiful fur. Cats make meaw meaw sound. God has provided cats with soft shoes or pads, which help a cat in walking without making a sound.
【文字鸣谢:【https://www.atozessays.com/animals/essay-on-cat/
作为一个人,当你读到这里的时候,你知道这一段是关于猫的。Cat 是上一段的一个重要话题。但是,
- 一台机器应该如何解决这个问题?
- 你怎么知道你的模型猫是这一段的一个重要话题?
**"The more frequent a word, the more important it might be!!!"**
这就是单词袋发挥作用的地方!!!
如何获得一个段落的单词袋?
- 首先,使用标记化创建段落的标记,标记可以是文本的任何部分,例如单词、数字、标点符号或特殊字符
- 应用必要的文本预处理步骤来过滤掉好的标记,例如小写单词、词汇化/词干化(将单词变成它们的根形式)、去除停用单词和标点符号等。
- 统计每个单词出现的次数,找出最常见的单词。
# declare your text here
paragraph = "As a pet, cat is a very useful animal and helps in protecting or saving our rashan from rats........................."# tokenize the paragraph using word_tokenize,return tokens
tokens = word_tokenize(paragraph)# change the tokens to lower case
lower_tokens = [token.lower() for token in tokens]# Retain alphabetic words: alpha_only, eliminate punctions and special characters
alpha_only = [t for t in lower_tokens if t.isalpha()]#remove all stop words
stop_words = set(stopwords.words('english'))
filtered_tokens = [token for token in alpha_only if not token in stop_words]# lemmatize the words to bring them to their root form
wordnet_lemmatizer = WordNetLemmatizer()
lemmatized_tokens = [wordnet_lemmatizer.lemmatize(token) for token in filtered_tokens]# create bag of words
bag_of_words = Counter(lemmatized_tokens)# print the top 5 most common words
print(bag_of_words.most_common(5))Output:
[('cat', 11), ('help', 5), ('walking', 2), ('long', 2), ('without', 2)]
通过查看输出,您可以简单地说,这是一篇关于猫的文章,因为它是文章中出现频率最高的单词。现在,您可以使用这袋单词作为特征来进行 NLP 的下一步。
这只是一种方法,为了简单起见,我只添加了简单的预处理步骤。但是预处理步骤将因情况而异。阅读类似的作品,针对你的情况微调预处理步骤。
你可以看看这个例子的笔记本。希望这篇文章对你有帮助!!!
使用 OpenCV 了解数字图像处理和计算机视觉的基础知识
CV 的基本概念:二值化,阈值,灰度和更多的见解。
这篇文章是关于数字图像背后的基本概念,它的处理,因此,也是 CV 的基础。最后,您可以找到一个使用 OpenCV 的 Python 的简单代码实现。
读完这篇文章后,你将能够回答以下问题:
- 计算机是如何看到图像的?
- 有哪些典型的意象类型?
- 什么是图像处理中的二值化?
- 什么是阈值处理?
- 阈值和二值化是如何应用的?
介绍
每幅图像都由像素组成,这些像素是用一种颜色填充的小方块。将所有不同的方块放在一起,然后缩小以看到所有的像素,就产生了我们所知道的图像。在下图中,可以清楚地看到小方块。它由 119 个水*像素列和 82 个垂直像素行组成。总的来说,这导致 119 x 82 = 9758 像素和 16 KB 的非常小的数据大小。
119 x 82 像素。来源:自己的图像
图像的*滑和清晰程度取决于图像的大小,因此也取决于像素的数量。放大一幅只有大约 10000 个像素的图像(如上图)会导致不清晰的结构,看起来更像俄罗斯方块,而不是*滑的线条。将此大小增加到 904 个水*像素列和 604 个垂直像素行,得到 904x604=546.016 个像素和 209 KB 的数据大小。图像现在已经更清晰了,但仍然有纹理,有点脆。
904 x 604 像素。
进一步缩小以看到由 4928 × 3264 = 16.084.992 像素组成的整个图像,会产生一个*滑且漂亮的图像。该映像现在的数据大小为 6,7 MB,即 6700 KB。
4928 x 3264 像素。
这就是我们看到的数字图像。但是对于计算机来说,它看起来怎么样呢?它得到什么,它如何处理输入?对于计算机来说,图像是形状的二维函数
它是两个连续空间变量 x 和 y 的函数,x 是像素的行值,y 是列值。为了处理它,计算机对它进行采样和量化,将输入转化为矩阵,如上图所示。图像的每一个像素都用 f(x,y)进行处理(达席尔瓦和门东萨,2005)。
先说像素
在典型的数字图像中,每个像素由一个 8 位整数组成,导致 256 种可能的状态:
颜色深度:比特和阴影。来源:自有。
对于彩色数字图像,每个像素都有三个函数
彩色数字像素由一个 1x3 矩阵组成,代表红色、绿色和蓝色三个原色通道,每个通道都是一个 8 位整数。每个像素的 256 个状态是指每个颜色通道的亮度,0 是颜色的最暗版本,255 是原色的最亮版本(Fisher 等人,a,2003)。
随后,R = 0、G = 0 和 B = 0 的向量将导致黑色像素,因为所有三个颜色通道都由最暗的阴影组成。R = 3,G = 168,B = 124 的星座将导致浅绿色像素,具有真正浅的红色值(只有 3 ),因此红色对所得像素颜色的影响很小,但是绿色和蓝色的值*衡,绿色是最强的值。这两种颜色都可以在下面的图片中看到。
使用数字色度计选择颜色以查看 RGB 值。
图像类型
基本上存在多种类型的图像,但我们将考虑四种典型类型:
- 二值图像:仅由黑白像素组成,白色为 0,黑色为 1。这种类型通常用于图像处理,例如在光学字符识别中识别图像中的字母和文本。
- 黑色&白色图像: 黑白图像,特别是在摄影中,是典型的灰度图像。这意味着像素不仅由黑白颜色组成,而且每个像素都有 256 种不同的灰色阴影。
- 8 位彩色图像: 你遇到的大部分数码彩色图像都是 8 位彩色图像。它是最受欢迎的类型,也是上一节中用来解释的类型,我们来谈谈像素。如前所述,8 位导致 256 种不同的颜色深浅。
- 16 位彩色图像:它是一种更复杂的彩色图像,没有它的小兄弟 8 位彩色图像那么受欢迎。对于一个像素的每个颜色通道,2 的 16 次方会产生 65536 种不同的色度。
灰度调整。在图像处理中,灰度是标准化和折衷数字彩色图像的常用技术。而彩色图像中的三维 RGB 空间导致
可能的组合,灰度图像具有明显较少的状态
由于 256 种可能状态中的每一种状态对应于不同的亮度,因此灰度调整与提高自动亮度和对比度校正程序的速度相关。在成千上万幅图像的预处理中,灰度图像由于其单通道结构而比彩色图像处理得更快。因此,彩色图像必须转换成灰度图像。对于这一步,1x3 向量必须减少到 1x1 向量。想到的最简单的方法是给每个颜色空间分配相同的权重,即 0.33。该方法是众所周知的,并且是典型的方法:
然而,三原色的波长各不相同(Choudhury,第 10 页及以后,2014 年)。这导致颜色对人类视觉的不同贡献。由于原色对人类颜色感知的这种不同贡献,比例不应该被认为是相等的,而应该适应人眼。Open CV 的变换公式是基于亮度不变的原理/BT601 (Open CV c,n.d .):
使用此公式,由三个值(RGB)组成的像素被转换为一个值(Y),即 256 种不同灰度范围(0–255)内的一种灰度。因此,这也减少了每个像素包含的信息,从而减少了图像的数据量。
以上面的绿色调为例,R=3,G=168,B=124,将其填入灰度公式,得到 Y = 114,考虑到 256 种灰度,这是一个中等范围的灰色调。
灰度输入图像:
灰度图像。
将输入图像转换为灰度图像并绘制其像素值的直方图,可以显示图像中每种灰度的出现频率。在 200 到 250 范围内具有暗灰色阴影的像素具有最高的频率,如下面直方图右侧所示。看一看图片,底部的暗像素和气泡的一些像素是直方图中这个峰的一部分。
灰度的频率分布(直方图)。
二值化图像
向二进制图像的转换称为阈值处理。阈值化背后的思想是将所有像素分割为 1 或 0。超过定义阈值的灰度图像的所有像素都是 1,而低于定义阈值的值被转换为 0。输出是仅由黑白像素组成的图像,0 表示黑色,1 表示白色。为了定义阈值,存在不同的技术。该算法在这项工作中提出了两个步骤。它结合了简单的二值阈值和 OTSU 阈值。它计算使 1 和 0 两个类的加权类内方差最小的 t 值。
OTSU 阈值法(OpenCV,f,n.d .)
然后通过(OpenCV,f,n.d .)将检索到的值用于二进制阈值处理
阈值化后,图像被分割成前景和背景。所有不等于 0 并因此为 1 的像素可以被定义为文档的内容和前景。下图的阈值处理是用 Open CV 和上面解释的两种方法完成的。
产生二值化图像的阈值处理。
感谢阅读!
请继续关注 Marius Hucker 的新文章。如果您还没有注册,您将创建一个中型帐户…
medium.com](https://medium.com/subscribe/@hucker.marius)
下面是应用所解释方法的一小段代码:
**#import the OpenCV library**
import cv2**#read your file** file=r’/Users/marius/Desktop/color.jpg’
img = cv2.imread(file)**#Grayscaling the image via OpenCV**
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)**#Thresholding the image**
gray,img_bin = cv2.threshold(gray,127,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)**#Showing the image** cv2.imshow(‘image’,img_bin)
cv2.waitKey(0)
参考文献
Da Silva,E. A. B .,& mendon ca,G. V. (2005 年)。数字图像处理。电气工程手册,891–910。
费希尔,r .,帕金斯,s .,沃克,a .,&沃尔法特,E. (2003 年)。像素值。【https://homepages.inf.ed.ac.uk/rbf/HIPR2/value.htm
打开 CV c .(未标明)。颜色转换。
打开 CV d .(未标明)。对数组的操作。https://docs . opencv . org/2.4/modules/core/doc/operations _ on _ arrays . html
打开 CV e .(未标明)。图像阈值处理。https://docs . opencv . org/master/D7/d4d/tutorial _ py _ thresholding . html
打开 CV f .(未标明)。其他图像转换。https://docs . opencv . org/master/D7/d1b/group _ _ imgproc _ _ misc . html # gae 8 a4 a 146 D1 ca 78 c 626 a 53577199 e9 c 57
理解偏差-方差权衡,并通过示例和 python 代码将其可视化
机器学习中最重要和最基本的主题之一是偏差-方差权衡。在本文中,我们将详细介绍什么是偏差-方差权衡,它来自哪里,为什么必须考虑它,它如何影响我们的底层系统,以及可以做些什么来改进我们的 ML 模型。
上下文:它源于哪里?
ML 系统背后的总体思想是从收集的样本中建模数据集的隐藏分布。如果您从分布中抽取足够的样本,您可以得到一个相当精确的分布的重新创建,如下所示。
给定(x,y)对,估计 f。
但问题是,在现实生活中,收集的样本通常是嘈杂的。这种噪声的来源可能有很多因素,如量化、感官限制等。因此,我们得不到分布的精确输出,而是在其中加入了噪声因子。因此,现在的问题是,在给定输入 x 的情况下,估计函数 f 及其相应的噪声输出 y。
目标是有效地估计函数 f 并滤除噪声。由于您对添加到样本输出中的噪声了解不多,如果处理不当,ML 系统最终会将输入 x 映射到有噪声的输出 y(称为过拟合)。这种映射不是函数 f 的精确表示,并且对于看不见的测试数据产生很高的误差。
偏差-方差权衡告诉我们,在存在噪声的情况下,我们的基础系统(f`(x))应该有多复杂,才能相当准确地代表数据集的分布。
术语:什么是 ML 系统中的偏差和差异:
在我们的 ML 系统中,偏差和方差都是误差的来源。假设我们有一个训练数据集【D】,由从隐藏分布中采样的(x,y)对组成( y=f(x)+e )。我们从数据集建立模型 f*** ,使得训练标签和预测值之间的误差最小化(***error = y-f
(x))。
偏见:
偏差被定义为*均模型预测值 f`(x) 和地面真实值 f(x) 之间的误差
对从不同数据子集 D_i 预测的函数进行期望。更简单地说,你从隐藏分布中采样 n 个不同的数据集 D_i (每个数据集由多个(x,y)对组成),然后估计 n 个不同的函数 f_i .
估计函数的偏差告诉我们基础模型预测值的能力。一般来说,更简单的模型不能捕捉高维数据的复杂性,因此它们有更高的偏差。例如,您有一个从正弦曲线采样的数据集,并且您试图使用一次多项式来估计它,即通过函数 y = ax+b. 无论您采样多少个数据点(x,y),直线将永远无法捕捉正弦曲线的趋势。因此,对于正弦曲线,直线模型具有非常高的偏差。另一方面,假设您将多项式的次数增加到 3 次,即现在通过 y = ax +bx+c. 对其进行估计,该多项式将比前一个多项式的性能好得多,因此在估计正弦曲线时,3 次多项式的偏差比 1 次多项式的偏差小得多。
模型的高偏差与以下因素有关
- 拟合不足—未能捕捉到数据趋势
- 更强调概括
- 训练和测试数据集的错误率都很高
- 过于简化的模型
差异:
方差是指给定数据集的模型预测的*均可变性。
估计函数的方差告诉您该函数能够根据数据集中的变化进行多大程度的调整。方差越大,函数对变化的数据集越稳健。例如,假设您训练两个不同的多项式来拟合从正弦曲线(1 次和 3 次多项式)采样的数据。你在三个不同的数据集(D1、D2 和 D3)上训练这两个多项式。下图显示了跨多项式次数和数据集的估计函数。
可以看出,1 次多项式在三个不同的数据集之间变化不大(因此方差较低),而 3 次多项式的估计彼此相差很大(因此方差较高)。
模型的高方差与以下因素有关
- 过度拟合-结束对数据集中噪声的建模
- 更加强调尽可能接*地拟合每个数据点
- 训练数据误差小,但测试数据误差大
- 过于复杂的模型和稀疏的训练数据。
一般来说,如果你增加底层系统的复杂度,系统的偏差会减小,而方差会增大。它们两者彼此成反比。不能两个都减。这一点将是偏差-方差权衡的基础。
权衡:
ML 模型的预期测试误差可通过以下公式分解为其偏差和方差
测试误差=偏差+方差+不可约误差
这一点的完整推导可以在这里找到。不可约误差是由于数据中的噪声引起的误差,并且与模型的选择无关。
所以为了减少估计误差,你需要减少偏差和方差。你必须选择一个模型(在我们的例子中是多项式的次数),以最小化误差的方式权衡偏差和方差。在过拟合和欠拟合的情况下,测试误差都可能很高。因此,我们需要偏差和方差的最佳*衡,这样我们的模型既不会过度拟合也不会欠拟合我们的数据。
考虑前面的例子。我们必须使用多项式从噪声采样数据 D 中估计正弦曲线。我们可以从各种不同次数的多项式中选择。在理想情况下,我们可以访问足够大量的无噪声数据(噪声=0),泰勒级数展开告诉我们,如果我们不断增加多项式的次数,我们可以越来越好地逼*正弦曲线。但是在有噪声和有限数据集的情况下,增加多项式的次数将开始拟合数据中的噪声,并且在测试数据集上表现不佳。因此,我们需要找到在我们的数据集上最有效(即测试误差最小化)的多项式的最佳次数。
下图描绘了不同多项式的正弦曲线的估计值。1 次多项式过于简单,无法捕捉正弦曲线,而 11 次多项式则非常复杂,甚至可以包含噪声(因此偏离了我们试图估算的实际正弦曲线)。
正弦曲线的多项式回归
为了找到估计正弦曲线的最佳多项式,我们找到测试数据集的多项式(1、3、5、7、9 和 11 次)的偏差和方差。图表绘制如下。
可以看出,在存在噪声的情况下,7 次多项式为我们的正弦曲线提供了最佳拟合。该多项式的选择取决于
- 样本数据中噪声的数量和性质
- 训练数据量
我们假设采样的数据集没有噪声。对这个干净的数据集运行相同的实验得到了下面的图表
上图符合正弦曲线的泰勒级数展开,表明多项式的次数越高,逼*越好。完整的代码附后。
Python 代码:
正弦曲线估计问题的 python 代码可以在下面找到。模块 gen_data()中的变量 b 用于控制采样数据中的噪声量。通过对 num_data=2000 个不同的(但重叠的)数据集估计 f(x ),并对其进行*均,可以得到估计函数 f
(x)的期望值。
总结:
对于干净且足够大的数据集:
- 模型越复杂,估计误差越低,*似性越好。
- 模型越复杂,偏差越小
- 模型复杂度越大,方差越低
对于噪声数据集:
- 更高的模型复杂度并不意味着更低的估计误差
- 模型越复杂,偏差越小
- 模型复杂度越大,方差就越高
由于现实世界中的数据集几乎总是有噪声和有限的,因此我们需要找到底层模型的最佳复杂性,使我们在数据集上的误差最小。更高的复杂度并不能保证最合适。
奖金:
可以在下面的链接中找到这个主题和机器学习中许多其他重要主题的紧凑备忘单
ML 面试的视觉备忘单(www.cheatsheets.aqeel-anwar.com)
medium.com](https://medium.com/swlh/cheat-sheets-for-machine-learning-interview-topics-51c2bc2bab4f)*
如果这篇文章对你有帮助,欢迎鼓掌、分享和回复。如果你想了解更多关于机器学习和数据科学的知识,请关注我@Aqeel an war或者在LinkedIn上与我联系。**
了解图形神经网络的构建模块(简介)
GRL 系列
对用于分析和学习图形数据的神经框架的直觉(带有运行代码)
T 他的帖子是关于图形神经网络 (GNNs)的一系列文章的介绍。本系列的目标是通过直觉和例子提供 GNNs 构建模块的详细描述。
在这个系列中,我还会分享运行代码,使用 Numpy,Pytorch,以及这个领域采用的最突出的库,比如深度图形库(DGL) 和 Pytorch 几何。在本系列的最后,您将能够组合这些构建块并创建一个神经架构来对图形数据执行分析和学习任务。
本系列将分析建立 GNN 的主要组件,包括(I)输入层,(ii)GNN 层,和(iii)多层感知器(MLP)预测层。
分析和分解标准 GNN 体系结构的框架基于最*发表的题为“基准图神经网络”的论文,其元数据如下:
Dwivedi,V. P .,Joshi,C. K .,Laurent,t .,Bengio,y .,& Bresson,X. (2020)。基准图神经网络。arXiv 预印本 arXiv:2003.00982 。
来源:https://arxiv.org/abs/2003.00982
这篇文章不包括图论和神经网络的基础。对于这个主题的介绍,我建议阅读下面的文章:
图形学习和几何深度学习—第 0 部分
towardsdatascience.com](/graph-theory-and-deep-learning-know-hows-6556b0e9891b)
GNN 建筑:主要组成部分概述
输入层定义图形数据的初始表示,它成为 GNN 层的输入。基本上,其思想是为图的节点和边分配一个要素表示。
GNN 层对图形结构的信息进行编码。然后,它利用这些信息来更新节点和边的初始表示。
MLP 预测层执行特定的学习任务,包括节点分类或链路预测,采用从 GNN 层输出获得的编码图表示。
这篇文章介绍了输入层和 GNN 层背后的主要原理。接下来的文章将描述不同类型的 GNN 层,解释相关的功能,并显示它们之间的主要差异。同时,我将概述传统的 MLP 预测图层,以对图表数据执行特定的任务。
这个动画 gif 描述了通过 GNN 网络传播更新的节点特性——原始图片来自 GraphSAGE 网站的主页
输入层
如前所述,输入层的目标是定义图形数据的初始表示,将要素分配给节点和边。为了简单起见,我目前只考虑节点特性。
在图中表示节点的最简单的方法是使用单键向量。这种表示通常被用来区分 NLP 任务词汇表中的不同单词。在我们的例子中,它被用来表示图的不同节点。表示每个节点的向量的长度等于节点的数量,并且对于每个向量,不同位置的元素被设置为 1,而其他元素被设置为 0。
为了阐明这种表示,下面的脚本创建了一个包含 5 个节点的图,用一个热点向量表示。
import numpy as npX = np.eye(5, 5)
n = X.shape[0]
np.random.shuffle(X)print(X)-- output:[[0\. 0\. 1\. 0\. 0.] # Node 1
[0\. 0\. 0\. 1\. 0.] # Node 2
[1\. 0\. 0\. 0\. 0.] # ..
[0\. 0\. 0\. 0\. 1.] # ..
[0\. 1\. 0\. 0\. 0.]] # Node 5
这个矩阵的每一行代表图的一个节点。为了给每个节点分配初始特征,输入层将线性变换(也称为投影)应用于编码节点表示的一键矢量。简单回顾一下线性变换,定义如下:
Y = WX + b
如 Dwivedi 等人所报道的,在独热向量的情况下,偏置值 b 不用于线性变换。因此,以下脚本执行线性变换:
# Dimension of the node features (embedding)
emb = 3# Weight matrix (initialized according to Glorot & Bengio (2010))W = np.random.uniform(-np.sqrt(1\. / emb), np.sqrt(1\. / emb), (n, emb))print(W)-- output:
[[-0.34857891 -0.5419972 0.43603217]
[ 0.26261991 0.04720523 -0.42555547]
[-0.09968833 0.3218483 0.09688095]
[-0.36646565 0.37652735 -0.45564272]
[-0.24990413 -0.50164433 -0.51217414]]
--# Linear projection
L_0 = X.dot(W)print(L_0)-- output:[[-0.09968833 0.3218483 0.09688095]
[-0.36646565 0.37652735 -0.45564272]
[-0.34857891 -0.5419972 0.43603217]
[-0.24990413 -0.50164433 -0.51217414]
[ 0.26261991 0.04720523 -0.42555547]]
投影步骤为图中的每个节点分配一个 d 维向量表示。在这个例子中,表示节点的 5 长度的独热向量被映射(或投影)成 3 长度的密集特征向量。
转述 Dwivedi 等人的话:
输入层的目标是将节点(和边)的输入要素嵌入到隐藏要素的 d 维向量中。这种新的表示是通过简单的线性变换(也称为投影)获得的。
为了阐明这一方面,可以分析以下块:
# X: One-hot vectors representing the nodes
[**[0\. 0\. 1\. 0\. 0.] ** **# Node 1 - 1 element in the *3rd* position**
[0\. 0\. 0\. 1\. 0.] **0 in the other positions**
[1\. 0\. 0\. 0\. 0.]
[0\. 0\. 0\. 0\. 1.]
[0\. 1\. 0\. 0\. 0.]] # W: Weight matrix
[[-0.34857891 -0.5419972 0.43603217]
[ 0.26261991 0.04720523 -0.42555547]
**[-0.09968833 0.3218483 0.09688095] # Emphasis to the *3rd* row**
[-0.36646565 0.37652735 -0.45564272]
[-0.24990413 -0.50164433 -0.51217414]]# L_0 (projection) = X.dot(W)
[[**-0.09968833 0.3218483 0.09688095**] # **Features of Node 1,** [-0.36646565 0.37652735 -0.45564272] **represented by the *3rd* row**
[-0.34857891 -0.5419972 0.43603217] **of the weight matrix**
[-0.24990413 -0.50164433 -0.51217414]
[ 0.26261991 0.04720523 -0.42555547]]
当前特征是随机生成的。因此,这些特征实际上并不传达关于节点的任何类型的信息。然而,节点的这些初始特征将通过两个不同的步骤来更新:
- 通过 GNN 层聚合相邻节点的特征。
- 通过 MLP 层为特定目的对神经结构进行的训练。
在这个双重过程的最后,我们将能够获得节点的嵌入表示,它将由传达特定信息的特征来表征。换句话说,节点的矢量表示将表达有意义的信息,作为人类,我们应该能够通过观察图形来识别这些信息。在最简单的情况下,相似的嵌入特征将被分配给图中相似的节点。
GNN 层
GNN 层的目标是更新从输入层获得的节点的维维表示。这个目标是通过 Dwivedi 等人定义的计算来实现的。al,一个“递归邻域扩散”,通过所谓的“消息传递框架”。该框架背后的主要思想是每个结点要素都用其相邻结点的要素进行更新。邻居特征通过边作为消息被传递到目标节点。因此,节点的新表示编码并表示图形的局部结构。为了执行这个步骤,我们需要一个描述图中节点之间关系(边)的结构。描述图中节点之间关系的邻接矩阵在这个方向上帮助了我们。
考虑下面的脚本,它初始化一个 5 节点图中的随机邻接矩阵:
# Randomly generated adjacency matrix
A = np.random.randint(2, size=(n, n))
np.fill_diagonal(A, 1) # Include the self loop# The following lines are a trivial ack to create a symmetric
# Adj matrix that defines the edges of an undirected
# graph of 5 nodes
A = (A + A.T)
A[A > 1] = 1print(A)-- output:[**[1 1 1 0 1]** # Connections to Node 1
[1 1 1 1 1]
[1 1 1 1 0]
[0 1 1 1 0]
[1 1 0 0 1]]
邻接矩阵的每一行代表由 1 元素标识的到一个节点的连接。例如,第一行表示节点 1 连接到自身、节点 2、节点 3 和节点 5。另一方面,节点 1 没有连接到节点 4,因为位置(1,4)中的值等于 0。
让我们看看当我们将邻接矩阵与应用了投影的输入图层的输出相乘时会发生什么:
# A: Adjacency matrix
[**[1 1 1 0 1]** **# Connections to Node 1**
[1 1 1 1 1]
[1 1 1 1 0]
[0 1 1 1 0]
[1 1 0 0 1]]# L_0: Output from the input layer
[**[-0.09968833 0.3218483 0.09688095] # Features of Node 1**
[-0.36646565 0.37652735 -0.45564272]
[-0.34857891 -0.5419972 0.43603217]
[-0.24990413 -0.50164433 -0.51217414]
[ 0.26261991 0.04720523 -0.42555547]]# L_1 = A.dot(L_0)
[**[-0.55211298 0.20358368 -0.34828506] # What is this?**
[-0.8020171 -0.29806065 -0.86045919]
[-1.06463701 -0.34526588 -0.43490372]
[-0.96494868 -0.66711419 -0.53178468]
[-0.20353407 0.74558089 -0.78431723]]
为了更好地理解节点表示发生了什么,考虑下面的脚本,它将节点 1 的 d 维表示求和为节点 1、节点 2、节点 3 和节点 5 的 d 维表示。
print(L_0[0, :] + L_0[1, :] + L_0[2, :] + L_0[4, :])-- output:
**[-0.55211298 0.20358368 -0.34828506]i**# L_1 = A.dot(L_0)
[**[-0.55211298 0.20358368 -0.34828506] # Features of Node 1,**
[-0.8020171 -0.29806065 -0.86045919] **obtained summing the**
[-1.06463701 -0.34526588 -0.43490372] **features of local neighbors**
[-0.96494868 -0.66711419 -0.53178468]
[-0.20353407 0.74558089 -0.78431723]]
如您所见,节点 1 的更新矢量表示对应于邻居特征的聚合(在这种情况下是求和运算)。换句话说,这种表示编码了图的局部结构。
这种表示的关键思想之一是在神经架构中堆叠 L 层,得到的目标节点表示聚集了节点的特征,其与目标节点的距离等于 L 。这种行为是“递归邻域扩散”的结果。
正如 Dwivedi 等人所强调的:
“堆叠 L GNN 层允许网络从每个节点的 L 跳邻居构建节点表示。”
不同 GNN 层之间的主要差异包括聚合类型,聚合是通过利用本地图结构来执行的。在最简单的 GNN 公式中,例如普通图卷积网络(GCNs),聚集/更新是一种各向同性操作。这意味着以同样的方式考虑邻居节点的特征。更高级的神经架构,如图形注意力网络(GAT),引入了各向异性运算,其中每个邻居节点在聚合中的贡献根据其重要性进行加权。
下一步是什么
在下一篇文章中,我将介绍 GCN 层,还将描述带标记边的图(知识图)的一个特定扩展,名为关系图卷积网络(R-GCN)。
从 GCNs 到 R-GCNs:用神经架构编码知识图的结构
towardsdatascience.com](/graph-neural-networks-for-multi-relational-data-27968a2ed143)
关于 GAT 层计算的各向异性操作的更多细节,我建议阅读下面的文章,它提供了“从 math 到 NumPy”的详细解释。
从数学到数学的循序渐进指南
towardsdatascience.com](/graph-attention-networks-under-the-hood-3bd70dc7a87)
【https://towardsdatascience.com/tagged/grl-series】:要进一步阅读关于图形表征学习的文章,可以点击以下链接关注我的系列文章。
如果你喜欢我的文章,你可以支持我使用这个链接https://medium.com/@giuseppefutia/membership成为一个中等会员。
理解商业周期
卢卡·布拉沃在 Unsplash 上的照片
也许是经济学中最强大的力量
经济学是一门不精确的科学,金融和投资更是如此(有人称之为艺术)。但如果经济学中有一样东西是你可以长期持续依赖的,那就是由于商业周期,事物的趋势意味着回归。
你可能听说过大数定律。它指出,当我们多次运行一个实验时,结果的*均值趋向于期望值(也称为*均值)。在更多处理时间序列数据的经济学和金融学中,数据也有向长期均值回归的趋势,尽管这种趋势有所减弱。例如,看看失业率在经济周期中的起伏,但会定期回到均值(橙色线):
由于商业周期,失业率回复到*均值(资料来源:圣路易斯美联储银行)
当然,这不同于大数定律,大数定律的思想是我们一遍又一遍地重复同样的实验。在经济学中,事情从来都不是一成不变的——十年前推动事情发展的许多力量如今往往已经大不相同了。因此,均值回归的幅度和速度会随着时间而变化。此外,*均值的精确值也是不确定的,可能会随着时间的推移而发生变化。
然而,驱动商业周期的最强大的力量是相当一致的,这解释了为什么尽管不断变化,许多经济变量如失业仍然倾向于可靠地意味着恢复:
- 尽管美联储可能会偏向鸽派或鹰派,这取决于谁在掌舵(严重偏向鸽派),但它的任务是利用货币政策引导经济走向充分就业(和稳定的通胀)。美联储通常更喜欢稳定的经济增长、低失业率和不断上涨的资产价格。资产价格上涨(尤其是房地产)更受欢迎的原因是它会导致信贷扩张(资产价值上涨通过更健康的银行资产负债表、债务需求和更多贷款抵押品推动贷款),这是经济增长的关键组成部分。
- 美国政府也通过财政刺激大力鼓励人们就业(幸福的公民、更高的支持率和更多的税收)。
- 过热行业或经济中的过度投资和过度就业增长最终会自我纠正——过多的供应和竞争(通常伴随着需求冲击)会压低价格和利润。而那些加入得太晚的企业往往以破产告终。
- 在失业率低于*均水*时(劳动力市场紧张),高工资增长会推高通胀。如果通胀上升到足以让美联储开始加息,那么这也会减缓经济增长(通过减缓信贷增长——信贷减少,也就是借款,意味着每个人口袋里的钱减少,消费支出减少)。通常情况下,美联储最终会多次加息,导致经济陷入衰退,失业率超过*均水*。
- 受打击的行业或经济最终会复苏。一旦过剩供应被消除(通过违约或整合),价格战就会停止,企业可以再次获得合理的投资回报。这种情况,加上扩张性货币和财政政策的影响,导致新一轮的经济扩张和失业率下降。
周期
在我们研究受周期影响的更多变量之前,让我们先描绘出它的大致形状和驱动力:
商业周期
每个周期都不同,推动过剩的行业也很少相同——上世纪 90 年代末是互联网,2005 年至 2008 年是房地产和银行。但是起作用的力量是相似的:
过热
紧张的劳动力市场以及强劲的企业利润和投资支出推动工资增长和信贷增长,这导致大量美元外流和通货膨胀。这刺激了(美联储的)利率上升。
衰退
更高的利率伤害了资产价格,降低了借贷的吸引力——净效应是支出减少,经济增长放缓,失业,最终是衰退(但不会有更多的通货膨胀)。
恢复
一旦通胀得到控制,美联储就能够专注于充分就业任务,并降低利率和/或印钞(量化宽松),以刺激信贷增长和资产价格。通常,扩张性货币政策还伴随着政府的财政刺激——降低利率本身通常不足以扭转严重的衰退。
随着许多竞争对手的离去和业务的精简,幸存下来的企业逐渐恢复盈利,并开始重新招聘。
循环又开始了…
通过经济变量观察周期
早些时候,我们看到了这个周期如何导致失业潮涨潮落——尽管将失业率视为周期的主要驱动因素可能更准确(就业通常滞后于经济增长,但对大多数主要经济变量都有重大反馈影响)。
其他的是什么?工业生产的同比变化(与 GDP 高度相关)是另一个原因。请注意,当经济增长时,蓝线如何保持在橙线之上,而在衰退时,蓝线如何突破橙线之下。还要观察当前的低值,这暗示了由于持续的疫情导致的经济萎缩。好消息是,它已经开始意味着恢复,但仍远低于我们对健康和扩张的经济的预期。
工业生产年同比变化也在恢复(资料来源:圣路易斯美联储银行)
请记住,尽管有回归均值的趋势,但数据很少会在均值上停留很长时间。所以你不要把均值当成你以后会经常观察的一个水*。
我还增加了每月汽车销售总量——汽车销售对经济周期也非常敏感,因为当经济不景气时,人们往往会推迟大额购买。注意蓝线和橙线有多接*。这意味着,尽管疫情,目前的汽车销售速度几乎回到了可以被认为是正常的水*。
每月汽车总销量(资料来源:圣路易斯美联储银行)
我最喜欢观察的一个指标是信用利差,它衡量的是贷款人承担破产风险所需的额外收益率。它是公司债券(Baa 是仍然是投资级的最低评级)和安全债券(美国国债)利率之间的差异。我喜欢它,因为它是最*发生的事情的函数(有很多公司违约吗?)以及债券市场对未来的预期(未来几年是否会有很多公司开始违约?).这是一个很好的衡量市场风险偏好的指标——低于*均收益率意味着投资者认为一切都很好,高于*均收益率意味着投资者恐慌或接*它。
看起来投资者几个月前相当恐慌,但自那以后基本上*静下来了。
公司信用利差(来源:圣路易斯美联储银行)
我们可以做的一件事是查看每个时间序列的去趋势版本——为了消除趋势,我们只需减去*均值(蓝线减去橙线)。让我们将去趋势化(和 Z 得分)变量相互叠加,看看它们是如何关联的:
各种宏观经济变量的 z 分数(来源:圣路易斯美联储银行)
我们可以看到,虽然失业率明显落后于其他两个系列,但这两个系列总体上是一致的。正如我前面提到的,失业率通常滞后于其他经济数据,因为企业往往不会轻易解雇员工,也不会轻易重新招聘员工(因为它们会在商业周期中实时摸索)。目前的疫情是一个例外,非自愿关闭迫使数千家企业关闭,数百万人几乎在一夜之间失业——所以这一次没有滞后。
如果你发现很难从前面的图中看出它们之间的相关性,下面是相关矩阵。汽车销售和工业生产高度相关(请注意,工业生产和汽车销售已经反转,使它们与失业方向一致,因此当汽车销售在图中出现高值时,实际上表示低于*均值)。失业率看起来相关性较低,因为与其他两个变量相比,它的移动具有滞后性(如果我们将失业时间序列滞后几个月,我们会看到更高的相关性)。
相关
最后,我们可以看看利差,包括投资市场对未来的预期(可能非常不准确),如何与我们之前绘制的三个宏观经济变量的*均值相关联(时间序列绘制为 Z 分数)。
z 得分的宏观因素*均值与信用利差(来源:圣路易斯联邦银行)
它们通常一起移动(相关性=0.63)。这是金融的肮脏秘密之一——因为基于市场价格(以及市场嵌入和获取所有可用信息的方式)而被认为具有前瞻性的变量实际上是非常被动的。这意味着它们与经济数据同步(甚至略微落后),几乎从不超前。真正的冲击会突如其来,通常会让投资者措手不及。
一件有趣的事情是,公司债券投资者对 2008 年和今天的看法有多么不同。尽管经济受到了类似规模的负面冲击,但信贷息差在这次疫情期间几乎没有变化,已经恢复到 10 年滚动*均水*。回到 2008 年,信贷息差最初受到的冲击要大得多,复苏也花了更长时间。
结论
宏观经济学是一个非常深奥的课题,这篇文章仅仅触及了商业周期的表面。尽管我把它命名为“理解商业周期”,但我怀疑任何没有先验知识和经验的人读了这篇文章后会对商业周期有深刻的理解。相反,更重要的是,你要认识到商业周期的存在——它是我们经济和金融生活中潮起潮落的原因。
一想到商业周期,就让我想起那句老话,“历史不会重演,但往往会押韵”。记住这一点,我们不仅应该认识到商业周期的存在,而且还应该在我们仍然处于当前阶段时,至少付出一些努力和思考来为周期的下一阶段做准备。干杯!
理解中心极限定理
照片来自pixabay.com
以及它在假设检验中的重要作用
中心极限定理(CLT)是统计学中的一个流行概念。我相信大多数(有抱负的)数据科学家都以某种形式听说过它,或者至少在高层次上听说过这个定理。当数据量很大时,该定理经常被说成是神奇地提供了任何数据分布与正态(高斯)分布之间的相互联系。
话虽如此,我观察到这个定理的真正概念对许多人来说相当不清楚——包括我。是的,该定理将任何分布与正态分布联系起来。但是什么样的联系呢?不清楚。以及实现连接的过程是怎样的?也不清楚。
对我来说更糟糕的是,我曾经认为这个定理意味着当数据量很大时,任何数据都将遵循正态分布。或者换句话说,在大数据点上不可能有偏斜(或任何非正态)分布。废话!
通过这篇文章,我将试着解释 CLT,这样你就不会陷入和我上面一样的误解。最后,我还讨论了该定理在每个数据科学家都应该理解的核心能力之一,即假设检验中的重要性。
本文的菜单如下:
- 重温定理
- 揭穿“均值的抽样分布”这一关键概念
- 演示:一个疯狂的骰子
- 假设检验中的 CLT
定理
下面是中心极限定理(CLT)的精确(数学)形式。
不要慌!因为我不假设你们每个人都是铁杆数学家,所以我提供该定理的“简单英语”版本:
给定一个足够大的样本量,一个变量的均值的抽样分布将*似为正态分布,而不管该变量在总体中的分布如何。
更好的是,均值的抽样分布的均值等于总体均值。
或者在更高的层次上,变量的值可以遵循不同的概率分布,如下所示。
图一。分布的可能形状(来源)
然而,如果样本量很大,*均值的采样分布将始终遵循钟形曲线(正态分布)形状。
*均值的抽样分布
从上面的讨论中,我想再强调一次:保证遵循/*似正态分布的不是总体中的原始变量分布;取而代之的是均值的抽样分布。
均值的抽样分布是什么?这真的是一个关键概念,帮助我理解这个定理到底是关于什么的。所以下面就通过一个循序渐进的例子来一起揭穿吧!
根据以上,均值的抽样分布就是我们从多组样本中收集的每一个均值所形成的分布。在我们的例子中,我们有 k 组样本(每组包含 n 个高度值)。
演示:一个疯狂的骰子
来自 pixabay.com的照片
现在是时候看看中央极限定理的作用了!
假设我们有一个疯狂(不*衡)的骰子,其概率质量函数如下。
我们疯狂骰子的概率质量函数
读上表的一个例子:骰子显示 5 的概率是 0.3(而不是像在*衡骰子中的 1/6 ~ 0.166)。此外,注意这个骰子分布的*均值是 4。因为
(1x 0.15)+(2x 0.05)+(3x 0.05)+(4x 0.3)+(5x 0.3)+(6x 0.15)= 4
接下来,我们来可视化一下这个骰子分布!显然,这种分布是不对称的,更不用说呈钟形了。
图二。骰子分布
现在,我们做以下事情。我们假装不知道骰子的分布。我们所知道的是我们手头有骰子,我们想通过执行下面的过程来估计骰子分布的*均值。
注意,上面选择 n = 5 只是为了说明的目的(为了计算每个样本的*均值)。它太小了,容不得 CLT 插手。下面,我们展示了当 n = {10,30,50}遵循完全相同的程序时的直方图。
直方图为 n = 10
图 3。n = 10 的直方图
突然之间,直方图(均值的采样分布)已经有些钟形了!(回想图 2,原分布严重不。)这确实是 CLT 在行动中的示范。
然而,它仍然是不对称的(见红色圆圈),意味着*均值大于 4(地面真相)。
直方图为 n = 30
图 4。n = 30 的直方图
对于 n = 30,对称性越来越好。尽管它仍然稍微向左倾斜,再次意味着*均值比 4 稍大一点。
直方图为 n = 50
图 5。n = 50 的直方图
对于 n = 50,我们有一个几乎对称的高斯分布。分布以 4 为中心。这意味着如果我们在实验中选择了 n = 50,我们就能正确地估计出我们疯狂掷骰子的*均值。
要观察的东西
随着样本量 n 的增加,我们的演示中有两件事值得注意:
- *均值的抽样分布更接*正态(钟形)分布。
- 此外,分布形状的可变性更强(越来越集中于总体*均值)。
图 6。不同 ns 对分布的影响
假设检验中的 CLT
中心极限定理在假设检验中至关重要,至少在以下两个方面。
测试的正态假设
众所周知,许多参数检验假设数据是正态的,如 t 检验、方差分析等。多亏了 CLT,考虑到我们的样本量很大,我们更有信心使用这样的测试方法。
为了更详细地说明这一点,假设我们要比较一个连续指标的两个*均值(来自两组),每个*均值都来自 1000 个样本。我们最初想使用 t 检验来继续,但在有人质疑我们数据的正态性假设后,我们开始怀疑。
如果我们理解 CLT,这种担心是不必要的,因为我们知道,即使数据总体是非正态的,均值的抽样分布将遵循正态分布。这就是我们需要关心的,因为我们将要测试的是手段。
点估计精度
回想我们在进行上述演示后的第二个观察结果;随着 n 变大,分布形状在可变性上变紧。这意味着假设检验的样本量越大,我们就越有信心从我们的数据中得到的点假设估计精确地逼*真实的总体参数。
继续我们比较两个*均值的例子。如果样本量增加到 10,000——与原始设置中的 1000 相比,我们会对两组的两个*均估计精度更有信心。
概述
祝贺并感谢您读到这里!🎉
在这篇文章中,我们讨论了中心极限定理,它是统计学中最重要的结果之一。特别是,我们强调理解“均值的抽样分布”的关键概念,当样本量很大时,其分布(而不是其他)保证遵循正态分布。后来,我们用这个定理来估计一个假想的疯狂骰子的*均值,从而证明了这个定理的有效性。
我想用一句话来结束这个话题。CLT 只对方差有限的分布有效。这意味着对于具有无限方差的分布,如柯西分布,CLT 的结果将不成立。
总而言之,希望这篇文章对你理解中心极限定理有所帮助!
理解中心极限定理
理解统计学中最重要概念之一的实用指南
中心极限定理(简称 CLT)是统计学领域中最重要的概念之一。
在这篇文章中,我将尝试用简单而非技术性的方式来解释这个概念。
简介
让我们继续从高中的例子开始,就像这篇帖子中讨论的那样
您现在决定进一步研究这些数据。
接下来,您想要找出在美国读 10 年级的所有男性高中生的*均身高。
但是为了得到准确的数据,你需要找出 10 年级每个学生的身高,然后除以学生总数,这几乎是一个不可能的任务。
这就是中心极限定理来拯救我们的地方。
中心极限定理表明充分样本均值将*似等于总体均值。
这意味着我们可以从数据的基本样本中找到人口的*均身高。
让我们看看怎么做。
方法学
从总体人口中挑选一个样本(样本 1),大约 30 名学生,收集他们的身高,然后记录他们的样本均值,这也称为 x̄1
从另外 30 名学生中选择另一个样本(样本 2),记录*均值,x̄2(样本*均值 2)
对大约 100 个样本做这个练习,并标绘数值(x̄1,x̄2..x̄100)并找出这些值的*均值。
随着样本数量的增加,上面的图将开始看起来更像正态分布(其中数据关于*均值对称,接**均值的数据比远离*均值的数据出现的频率更高)
样本*均值将*似等于总体*均值
此外,无论原始总体的数据分布如何(可以是正态分布/偏斜分布/均匀分布或任何其他分布),都会发生这种情况
这个概念的力量在于,我们甚至可以将我们最初的问题扩展到一个更大的环境中。
例如,找出不同国家十年级学生的*均身高。
在这里,我们将不得不包括来自每个国家的样本,并包括更多的样本。
请注意,这里的原始人口分布可能是倾斜的,一些国家的高个子学生分布相对较高。
如果我们将所有样本均值数据点绘制在图上,它看起来就像正态分布。
实际应用
现在我们知道,不管人口数据点是如何分布的,样本均值图看起来总是像正态分布。
对于正态分布,我们知道
- 均值、中值和众数相等
- 数据关于*均值是对称的
利用这些性质,我们可以从总体上对人口进行推断
这有很多实际的应用。例如,预测哪个政党将在选举中获得多数席位,计算生活在不同地区的家庭每月的债务。
理解中心极限定理
图片由 Willi Heidelbach 来自 Pixabay
在我们编码之前,快速回顾一下
今天,我想分解中心极限定理,以及它与数据科学家执行的这么多工作的关系。
直方图刷新程序
首先,任何数据科学家的核心工具都是一种非常简单的图表类型,称为直方图。虽然你肯定见过很多直方图,但我们经常忽略它的重要性。直方图的核心目的是了解给定数据集的分布。
作为复习,直方图表示在 x 轴上发现的变量的不同值在 y 轴上的出现次数。
这里有一个这样的例子,我们想了解在我们的数据集中,汽车每加仑行驶英里数的分布情况。这里我们使用的是mtcars
数据集,可以看到在我们图表的右边有一点尾巴;这个直方图就是所谓的右偏直方图。这背后的概念是,是的,有汽车的极限汽油里程,但他们是非常少的。
标准正态分布
与您刚才看到的类似,您可能已经看到的经典分布是众所周知的正态分布,也称为钟形曲线,或标准正态分布。核心思想是事件的“分布”是**对称的* *。
看看下面的剧情。我们看到的直方图与之前的相似,但这里更加对称。
中心极限定理到底是什么?
中心极限定理指出样本均值的分布应该是*似正态的。
让我们在实践中看看这个定理
考虑下面的例子。假设你在一所大学工作,你想了解一个校友离开学校第一年的收入分布。
事实上,你不可能为每个校友收集数据。或者,您将对总体进行多次采样,以获得每个“样本”的单个样本均值。
我们现在通过直方图绘制样本均值,可以看到正态分布的出现。
这里的关键要点是,即使输入变量不是正态分布,抽样分布也会接*标准正态分布。
我们来编码吧!
作为这个想法的最后一个演示,我们最初从mtcars
数据集绘制了 MPG 的分布。这里,我们为每个 mpg 样本生成一个向量,然后循环 50 个样本,每个样本取数据集中 10 个随机记录的*均值。我们再次将它们绘制成直方图,可以看到正态分布的出现。
mpg_samples <- c()for (i in 1:50) {
mpg_samples[i] = mean(sample(mtcars$mpg, 10, replace = TRUE))
}
hist(mpg_samples, col = 'purple', xlab = "MPG")
这应该作为数据科学培训的基本概念,是假设检验、实验以及其他数据科学方法和技术的基础。
我希望这对你有所帮助!
祝数据科学快乐!
理解中心极限定理
从头开始的数据科学
对数据科学最重要的统计结果建立直觉。
你在新闻上看到的每一项民意调查或科学研究中对效应大小的每一个估计背后都有两个统计学结论:大数定律和中心极限定理。其中之一,大数定律,通常给人的印象是相当直观,但另一个就有点难以理解了。这是一个微妙的结论,其实际用途并不明显,但它可能是应用数据科学中最重要的一项统计数据。它强调了统计学家和数据科学家如何量化误差幅度,他们如何测试影响是否显著,以及为什么正态分布(那些经典的钟形曲线分布)在现实世界的变量中如此常见。如果您从事任何领域的应用统计工作,并且想要了解为什么您的工具会以这样的方式工作,您首先需要了解中心极限定理。
复习大数定律
大数定律陈述了一些对许多人来说似乎显而易见的事情:许多重复试验的*均值应该是这些试验中任何一个的*均期望值。任何一次试验都可能偏离*均值,但是当你考虑越来越多的试验时,它们的组合*均值几乎总是越来越接*预期*均值。一个常见的例子是抛硬币。如果你翻转的次数足够多,你应该得到大约一半的正面和一半的反面。如果你只抛几次硬币,很有可能得到一串正面或反面,但随着你继续抛,正面和反面的比例与*均值相差很大的可能性就会降低。
一个模拟的例子:当你掷越来越多的硬币时,正面在总数中的比例应该是 0.5 左右
大数定律对于调查和投票也很重要。例如,让我们说,在你可以很容易地在互联网上查找之前,你想知道人们的*均身高是多少。你决定进行一项调查可能是一种明智的方式,并开始询问随机的人他们有多高。一项随机调查的结果本身就是一个随机变量——你可能会选择比*均水*高的人或比*均水*低的人,如果你随机选择他们,你不会提前知道他们是高了还是矮了。你不能只调查一个人——你随机找到一个与*均身高完全相同的人的几率有多大!—但是如果你调查许多人,你的样本*均值可能会接*总体人口的真实*均值,如果你调查越来越多的人,你的样本*均值会越来越接*真实*均值。样本 10 比样本 1 好,样本 100 比样本 10 好。
这是大数定律在起作用——你的样本越大,你期望的真实*均值就越接*。你可能会意识到这里有一些警告,这并不像“更多的调查等于更好的结果”那么简单。特别是,这只有在您的试验或调查是随机的并且属于同一分布的情况下才有效。比方说,如果你在 NBA 比赛后从更衣室里的人群中随机选择调查对象,你会遇到麻烦。NBA 球星的身高分布与普通人群的身高分布不同,所以如果你的样本*均值与普通人群的不一致,不要感到惊讶。更微妙的是,如果你对调查对象的选择不是相互独立的,你也会遇到麻烦。比方说,你随便打电话给一个人,问他们有多高。太好了,你的调查又多了一个身高,但是,当你和他们通话的时候,你想你可以节省一些时间给陌生人打电话,也可以问问他们家里其他人的身高。现在你的样本不再是随机的了。满足这些要求的一个样本或一组随机变量的统计学术语是它们是“独立同分布的”——或 i.i.d
(实际上,LLN 还需要满足几个要求,例如,分布有一个明确定义的期望值,但是这些问题在现实世界的用例中并不经常出现。)
下一步:中心极限定理
有了 LLN,你就知道要得到一个好的估计值,你只需要一个足够大的样本。但是,多大的样本才是“足够大”呢?要回答这个问题,我们需要中心极限定理。中心极限定理认为,像样本*均值这样的样本统计量本身就是一个随机变量,随着样本量的增加,它大致呈正态分布,而与从中抽取样本的总体分布无关。*实际上有很多东西需要解开,所以让我们考虑一下定理的每个部分意味着什么。
像样本*均值这样的样本统计数据:首先需要理解的一件重要事情是,CLM 不能告诉我们任何关于某个特征在总体中的分布情况,甚至不能告诉我们该特征在样本中的分布情况。相反,CLM 告诉我们一些关于样本*均值分布的有趣的事情。请记住,我们调查中的单个测量值是一个随机变量。样本*均值也是一个随机变量,因为它完全来自测量值,而测量值本身就是随机变量。因为有了 CLM,我们知道了一些关于样本*均值分布形状的重要信息——也就是说,随着样本量的增长,它会变成正态分布。
正态分布:正态分布是一类人们倾向于熟悉的“钟形曲线”分布:薄的尾部围绕着较厚的中部,对称地以*均值为中心。正态分布有一个公式,但就我们的目的而言,我们不需要过多地考虑它。重要的是要理解,正态分布本质上只是一种具有众所周知的属性的形状,就像“圆”是一种形状一样。就像圆可以更大或更小一样,正态分布有不同的大小——有些更高更窄,有些更*更宽——但都有某些共同点。为了完美地描述一个圆,你只需要两条信息,它的半径和它的中心位置。同样,为了完美地描述一个正态分布,你只需要两条信息——它的*均值(类似于它的“中心”)和它的标准差。
三种正态分布
为了理解随着样本大小的增加,样本*均值将呈正态分布意味着什么,让我们看一个简单的例子。我们将考虑一个更简单的随机变量,即掷骰子的值,而不是高度样本。你会注意到掷骰子的概率分布(假设它是一个公*的骰子)明显不像钟形曲线。每个数字出现的可能性都是相同的:
这个分布有一个“*均”期望值,但这个*均值实际上是 3.5,一个我们在任何单个卷上都不会看到的数字。不管你掷骰子多少次,下一次掷骰子的概率保持不变。如果我们掷一个骰子很多次,并简单地记录它落在什么地方,我们应该得到一个看起来非常类似于这些基本概率的图表,加上或减去由于我们掷的随机机会而产生的一些差异:
如果我们不是一次掷出一个骰子,而是一次掷出一把并记录*均值,有趣的事情就开始发生了。让我们试着一次掷出 5 个骰子:
当我们一次掷出五个骰子时,我们开始在中间看到一个峰值。这在某种程度上是大数定律的开始——当我们掷出一个骰子时,我们得到的值往往远离整体期望值,如 1 和 6,但当我们掷出多个骰子并考虑它们的*均值时,我们倾向于得到更接*期望值 3.5 的数字。此外,分布开始呈现钟形。用更多的纸卷再试一次会延续这一趋势:
不考虑抽取样本的人口分布:你可能会想,这可能不是一个令人印象深刻的结果,像掷骰子这样简单的事情可能会合理地倾向于这种结果。令人难以置信的是,不管你考虑的随机变量的潜在分布是什么,这个结果都成立。比方说,你有一个装了子弹的骰子,它不会以相等的概率落在每个数字上。考虑这些可能性:
这个骰子喜欢一些数字,比如 4,但实际上从不落在 5 上。如果我们滚动几次,我们会得到你所期望的结果:
但是,再一次,如果我们一次掷出 5 个骰子,*均结果,我们开始看到钟形曲线的开始形成:
如果我们一次滚动 25 个,我们就不能真正判断出我们是从一个偏斜的基本分布中产生这些样本*均值的:
基础随机变量具有何种分布并不重要,随着样本规模的增长,样本*均值的分布将越来越接*正态分布。
请记住,任何正态分布都可以用两条信息来描述,即*均值和标准偏差。对于样本*均值的分布,这两个值是什么?*均值将是真实的总体*均值。然而,标准偏差会随着样本量的增加而缩小——这本质上又是 LLN,随着样本量的增加,*均值会向真实的总体*均值靠拢。事实证明,这条正态曲线的标准差是总体的标准差除以样本量的*方根。
为什么这如此重要?
为什么知道样本*均值是正态分布会这么有用?尤其是当你只看到一个样本*均值的时候?不像你正在研究的分布,你可能甚至不知道它的形状,正态分布有一些众所周知的和可靠的特征。有了一个合理大小的样本,大数定律表明我们应该找到一个接*真实总体*均值的样本*均值,但是有多接*呢?知道样本*均值是正态分布会有所帮助。回想一下,正态分布是一种具有某些可预测属性的形状,其中之一是它在其*均值附*最密集。正态分布的大部分区域以*均值为中心,尾部很快消失。从正态分布中随机选取的数字更可能来自曲线的中心区域,而不是尾部。
更重要的是,虽然一些正态分布比其他的更高和更窄,但我们可以用相同的标准差比率来量化曲线下的面积。为了说明我的意思,考虑下面的正常曲线:
蓝色垂直线代表分布的*均值。红色垂直线是*均值上下的一个标准差。它们之间的红色阴影区域——也就是曲线下*均值的-1 和 1 倍标准差之间的所有数据——仅占曲线下区域的 68%。类似地,黄色垂直线代表*均值上下的两个标准差,它们之间的距离代表分布总面积的 95%多一点。如果你从正态分布中随机选取一个值,95%的情况下你会得到这两条线之间的值。不管正态分布的均值或标准差取什么值,这都是正确的。
两个正态分布。每个分布中的阴影区域大小相同
事实证明,即使我们只看到一个样本和一个样本*均值,这些关于样本*均值的分布和正态分布的事实总的来说给了我们很多工作。样本*均值是真实总体*均值的无偏估计量,样本的标准偏差是总体标准偏差的无偏估计量(实际上,对样本标准偏差有一个小的调整,以纠正一些偏差,但它不会改变这里的逻辑,而且,无论如何,大多数统计软件包都会为您应用它)。回想一下,样本*均值分布的标准差来自总体的标准差(除以样本大小的*方根)。我们有了描述这一个样本的样本*均值分布所需的所有信息!
此外,我们非常确定这个特殊的样本*均值落在分布的中心。我们不知道它是高于真实人口*均值还是低于真实人口*均值,但我们 95%确定它在*均值上下两个标准差之间。如果我们取样本*均值,加上两个标准差,得到一个上限,减去两个标准差,得到一个下限,我们就有了一个区间。在以这种方式创建的 95%的范围内,真实的总体*均值将落在范围内的某处。这被称为“置信区间”,因为它量化了 95%的置信水*。这也是民意调查误差幅度产生的原因。
这可能有助于了解实际情况,所以让我们考虑另一个简单的例子。同样,我们将掷骰子(或者,我将让计算机模拟掷骰子)。我们将掷出 20 面骰子,所以掷骰子的“总数”是从 1 到 20 的每一个数字。该人群的“*均”预期值为 10.5。每个样品总共有 20 卷。从这个样本中,我们将得到总体*均值(样本*均值)和样本标准差的估计值。根据这些,我们将以上面讨论的方式构建 95%的置信区间。以下是 25 次模拟试验的结果:
红色破折号是样本*均值,围绕它们的蓝色条是置信区间。例如,在第一次试验中,样本*均值接* 12,置信区间在 9 到 14 之间。为了便于参考,我将垂直的紫色线设为 10.5,以代表真实的人口期望值。你会注意到样本*均值会因随机机会而波动,有时你的样本*均值会高于真实*均值,有时会低于真实*均值。但是当你构造置信区间时,区间几乎总是包含真实的总体*均值。在这里的 25 个模拟试验中,只有两个样本与*均值相差很远,以至于真实的总体*均值没有出现在置信区间的某个地方。当然,像掷骰子这样的事情,我们可以提前知道真实的期望值应该是多少,但是当处理其他一些随机变量时,建立这样的置信区间是至关重要的。
CLM 也可以用来回答你需要多大样本的问题。回想一下,随着样本大小的增加,样本*均值的标准差下降(它是总体标准差除以样本大小的*方根)。如果您再次进行上述相同的实验,但这次每次试验包括 100 次掷骰子,而不是只有 20 次,您可能会看到如下结果:
这看起来很像我们第一次运行这个实验,直到你看着 x 轴,注意到规模更小!我们的样本*均值通常更接*真实的总体*均值,并且我们的置信区间更小。使用 CLM 和这些关于正态分布形状的见解,我们可以回答样本需要多大才能达到某个预先指定的准确度水*的问题。
- CLM 的正式陈述通常是指独立随机变量的归一化总和,而不是它们的简单*均值,但是结论适用于样本*均值,并且出于本简介的目的,考虑样本*均值更简单。
理解混淆矩阵以及如何在 Python 中实现它
评估模型的基本概念
图片由来自 Pixabay 的 Gino Crescoli 拍摄
目录
介绍
任何人都可以用几行代码构建一个机器学习(ML)模型,但构建一个好的机器学习模型则完全是另一回事。
我说的好的机器学习模型是什么意思?
这要看情况,但一般来说,你会根据你决定使用的一些预先确定的指标来评估你的机器学习模型。当涉及到构建分类模型时,您最有可能使用混淆矩阵和相关度量来评估您的模型。混淆矩阵不仅在模型评估中有用,而且在模型监控和模型管理中也有用!
别急,我们这里说的不是线性代数矩阵!
在本文中,我们将介绍什么是混淆矩阵,一些关键术语和指标,一个 2x2 矩阵的例子,以及所有相关的 python 代码!
说了这么多,让我们开始吧!
什么是混淆矩阵?
混淆矩阵,也称为误差矩阵,是一个汇总表,用于评估分类模型的性能。正确和错误预测的数量用计数值汇总,并按每个类别细分。
下图是一个 2x2 混淆矩阵的结构。举个例子,假设有十个分类模型预测为“是”的实例,而实际值为“是”。那么数字 10 会出现在正象限的左上角。这就引出了一些关键术语:
2x2 混淆矩阵的结构
- 阳性(P) :观察呈阳性(如是一条狗)。
- 阴性(N) :观察不阳性(如不是狗)。
- 真阳性(TP) :模型正确预测阳性类别的结果。
- 真阴性(TN) :模型正确预测阴性类别的结果。
- 假阳性(FP) :也称为类型 1 错误,这是一种模型错误预测阳性类别而实际上为阴性的结果。
- 假阴性(FN) :也称为类型 2 错误,这是一种模型错误预测阴性类别而实际为阳性的结果。
混淆矩阵度量
现在您已经了解了混淆矩阵的一般结构以及相关的关键术语,我们可以深入了解一些可以从混淆矩阵中计算出来的主要指标。
注意:这个列表并不详尽——如果你想看到你可以计算的所有指标,请查看维基百科的页面 这里 。
准确(性)
这仅仅等于模型正确分类的预测的比例。
精确
精度也称为正预测值,是相关实例在检索实例中所占的比例。换句话说,它回答了“多大比例的积极认同是正确的?”
回忆
召回率,也称为敏感度、命中率或真阳性率(TPR) ,是实际检索到的相关实例占总量的比例。它回答了“有多少比例的实际阳性被正确识别?”
为了真正击中要害,下面的图表是记住精确和回忆之间的区别的一个很好的方法(它确实帮助了我)!
特征
特异性,也称为真阴性率(TNR) ,衡量被正确识别的实际阴性的比例。它是回忆的反义词。
F1 分数
F1 分数是对测试准确性的一种衡量——它是精确度和召回率的调和*均值。它的最高分是 1(完美的精确度和召回率),最低分是 0。总的来说,这是对你的模型的精确性和健壮性的一个度量。
2x2 混淆矩阵示例
如果这对你来说仍然没有意义,那么在我们看了下面的例子之后就明白了。
想象一下,我们创造了一个机器学习模型,可以预测病人是否患有癌症。左边的表格显示了模型做出的 12 个预测以及每个患者的实际结果。有了我们的配对数据,你就可以用我上面展示的结构来填写混淆矩阵。
一旦填写完毕,我们就可以了解关于我们模型的一些事情:
- 我们的模型预测 4/12(红色+黄色)患者患有癌症,而实际上有 3/12(红色+蓝色)患者患有癌症
- 我们的模型具有 9/12 或 75%的准确度((红色+绿色)/(总))
- 我们模型的召回率等于 2/(2+1) = 66%
实际上,你会希望癌症检测模型的召回率尽可能接* 100%。如果一名癌症患者被诊断为没有癌症,情况会糟糕得多,相比之下,一名没有癌症的患者被诊断为癌症,但后来通过更多的测试才意识到他/她没有癌症。
Python 代码
下面是计算上述指标所需代码的摘要:
# Confusion Matrix
from sklearn.metrics import **confusion_matrix**
confusion_matrix(y_true, y_pred)# Accuracy
from sklearn.metrics import **accuracy_score**
accuracy_score(y_true, y_pred)# Recall
from sklearn.metrics import **recall_score**
recall_score(y_true, y_pred, average=None)# Precision
from sklearn.metrics import **precision_score**
precision_score(y_true, y_pred, average=None)
在 Python 中有三种方法可以计算 F1 分数:
**# Method 1: sklearn**
from sklearn.metrics import f1_score
f1_score(y_true, y_pred, average=None)**# Method 2: Manual Calculation**
F1 = 2 * (precision * recall) / (precision + recall)**# Method 3: Classification report [BONUS]**
from sklearn.metrics import classification_report
print(classification_report(y_true, y_pred, target_names=target_names))
结论
现在你知道了什么是混淆矩阵以及它的相关度量,你可以有效地评估你的分类 ML 模型。即使在完成开发 ML 模型之后,理解这一点也是非常重要的,因为您将在机器学习生命周期的模型监控和模型管理阶段利用这些指标。
感谢阅读!
如果你喜欢我的工作,想支持我…
- 支持我的最好方式就是在媒体这里关注我。
- 成为第一批在TwitterT2 上关注我的人之一。我会在这里发布很多更新和有趣的东西!
- 此外,成为第一批订阅我的新 YouTube 频道 这里!
- 在 LinkedIn 上关注我这里。
- 在我的邮箱列表 这里报名。
- 看看我的网站,terenceshin.com。
了解冠状病毒流行数据
用于分析当前冠状病毒爆发的 Python 工具箱
Graphical Illustration from 前瞻网
新型冠状病毒(之前命名为 2019-nCov,后来改为 COVID-1⁹)目前正在席卷中国。截至 202⁰时间 2 月 13 日,它引发了一场全球卫生紧急事件,并夺走了 1000 多人的生命。世界各地的人们不断受到新闻、谣言、困惑和恐慌的轰炸。
为了帮助人们更好地评估形势,并促进理性反应,流行病数据必须便于公众获取。很多网站一直在实时发布一些疫情数字。比如丁香元、约翰霍普金斯 CSSE 、 Mapbox 等。这些网站向公众提供及时的信息,但它们不能提供足够的数据进行分析。例如,使用上述网站回答以下任何一个简单的问题都非常困难或者几乎不可能:
Q1。湖北省过去 7 天的确诊病例数是多少?
Q2。广东省和浙江省的每日新增确诊病例相比如何?
Q3。截至 2020 年 2 月 13 日,死亡人数最多的前 5 个城市是哪几个?
(还有更多……)
为了能够分析数据,需要一个更方便的数据集。DXY-2019-nCoV-DataGitHub 项目每天多次从丁象元抓取实时疫情报告,并保存为 CSV 文件。但是这个 CSV 文件只包含网站数据的快照。由于报告时间的随机性和不断变化的报告格式,不容易用于分析。
因此,这个项目ncov 2019 _ analysis⁴是在 GitHub 中创建的。其主要目的如下:
- 执行关键数据清理
- 提供了一个方便的结构化数据供用户探索和贡献
- 使用此工具箱分析冠状病毒流行的某些方面
这个 GitHub 项目包含一个 Python 笔记本来说明基本用法。为了方便读者,笔记本在 Google Colab 中。以下是该软件包的演示。
1.获取原始数据
import pandas as pd
import matplotlib.pyplot as plt
import utils *# functions to process the coronavirus data*data = utils.load_chinese_data() #load the raw CSV data
这个加载函数显示数据集的一些基本信息。
Last update: 2020-02-14 13:02:25.736000
Data date range: 2020-01-24 to 2020-02-14
Number of rows in raw data: 38164
原始 CSV 数据如下所示:
2.数据处理
该步骤调用一个函数 utils.aggDaily() ,该函数运行多个步骤:
- 将原始 CSV 数据聚合到每日数据中。
- 由于不一致的报告方案、缺失的报告等,执行关键清理。
- 每天新增确诊/治愈/死亡人数。
- 为省名和城市名添加 *_en 列,以便非中文用户可以轻松使用这些数据。
daily_frm = utils.aggDaily(data)
daily_frm.tail()
生成的“ daily_frm ”是一个*面 Pandas 数据框,逻辑上按(update_date,province_name(_en),city_name(_en)进行索引,因此用户可以轻松地以他们想要的任何方式对数据进行切片,或者导出到其他应用程序(如 Excel 或 Google Sheet)进行进一步分析。
有了这个数据集,这三个对那些流行网站来说很难的示例问题可以通过几行代码轻松回答。现在让我们重温这些问题:
Q1。湖北省过去 7 天的确诊病例数是多少?
daily_frm[daily_frm['province_name_en'] == 'Hubei'].groupby('update_date').agg('sum')[-7:]
Q2。广东省和浙江省的每日新增确诊病例相比如何?
frm1 = daily_frm[(daily_frm['province_name_en'] == 'Guangdong') | (daily_frm['province_name_en'] == 'Zhejiang')].copy()frm1['update_date'] = pd.to_datetime(frm1['update_date'])frm2 = frm1.groupby(['update_date', 'province_name_en']).agg('sum')ax = frm2['new_confirmed'][:, 'Guangdog'].plot(figsize=(15,10), label='Guangdong', grid=True, marker='*')frm2['new_confirmed'][:, 'Zhejiang'].plot(ax=ax, label='Zhejiang', grid=True, title='New Confirmed Comparison', marker='o')ax.legend(loc='upper right')
Q3。截至 2020 年 2 月 13 日,死亡人数最多的前 5 个城市是哪几个?
daily_frm[(daily_frm['update_date'] == pd.to_datetime('2020-02-13'))].sort_values('cum_dead', ascending=False)[:5]
正如您所看到的,这个数据集非常灵活,可以轻松地回答许多其他方式难以解决的问题。
3.绘图功能
为了加快数据的分析,提供了两个方便的可视化绘图功能:
- 时间序列图:utils . ts plot _ conf _ dead _ cured()
- 横截面图:utils . cross _ section _ bar()
例如,您可以通过以下命令绘制中国确诊、死亡和治愈病例的总数:
fig = utils.tsplot_conf_dead_cured(daily_frm, title= 'Total Confirmed, Dead, and Cured Counts')plt.show()
同样,用户可以使用特定城市/省份的数据来获得该地区的相同绘图。此外,可以提供一个可选参数 logy 来控制 y 轴,这样用户可以更容易地看到数字是否呈指数增长。以下是武汉疫情中心同一地块的一个例子。
fig = utils.tsplot_conf_dead_cured(daily_frm[daily_frm['city_name_en'] == 'Wuhan'], **logy**=False, title='Confirmed, Dead, and Cured Counts in Wuhan')plt.show()
横截面图也很容易制作。以下是一个比较所有省份已确认数量的示例:
fig = utils.cross_sectional_bar(daily_frm, '2020-02-03', col='cum_confirmed', groupby='province_name_en', title='2020-02-03 Confirmed Count comparison among all provinces')plt.show()
可以提供一个可选参数" largestN "来只显示前 N 个条形。例如,下面比较了 2020 年 2 月 5 日最新确认数量最多的 10 个城市:
fig = utils.cross_sectional_bar(daily_frm, '2020-02-05', col='new_confirmed', groupby='city_name_en', largestN=10, title='Top 10 cities with the most new confirmed counts on 2020-02-05' )plt.show()
最后的话
以上是工具箱的一些基本用法。我希望它能为你更好地了解最*的冠状病毒疫情提供方便和帮助。
在下一篇文章中,我将使用这个工具箱来分析死亡率。你会看到并不是广为流传的 2%那么简单。
承认
我要感谢我的朋友 David Tian,他是一名机器学习工程师,对 Google Colab 的设置提供了慷慨的帮助,并对本文提出了宝贵的建议。看看他有趣的自驾* DeepPiCar *博客。
更新(2020 年 3 月 2 日)
来自约翰霍普金斯大学的国际数据已经被添加到我的 GitHub 项目中。这些数据不如中国的丁香园数据统一,因为许多国家没有省级报告,也根本没有市级数据。此外,在国家之间运送病人需要在数据处理方面格外小心。所以为了方便分析,做了一些数据清理。
参考:
[1]2020 年 2 月 11 日,世卫组织决定将该疾病命名为新冠肺炎:https://www . who . int/DG/speechs/detail/who-总干事-s-remarks-at-the-media-briefing-On-2019-ncov-On-11-2020 年 2 月
[2]2020 年 1 月 30 日,世卫组织宣布进入国际卫生紧急状态:https://www . who . int/news-room/detail/30-01-2020-statement-On-the-second-meeting-of-the-international-health-regulations-(2005)-emergency-Committee-about-the-outbreak-of-novel-coronavirus-(2019-ncov)
[3]2 月 13 日,中国冠状病毒死亡人数超过 1000 人:https://global news . ca/news/6533678/China-coronavirus-death-toll-who/
[4]代码的 GitHub 链接:https://github.com/jianxu305/nCov2019_analysis
理解 python 中对象和类的症结。
简化面向对象编程。
Emile Perron 在 Unsplash 上的照片
Nitin 是我大学三年级的学生,他从事编程已经很长时间了,到目前为止,他只是将函数和子例程作为编写应用程序的一种方式。最*,他决定转向面向对象的编程方法,并寻求我的帮助来理解其本质。在本文中,我试图通过我和 Nitin 之间的对话来解释面向对象编程的本质。
关键词 —对象、类、属性、实例、方法。
我: Nitin,你为什么对学习面向对象编程这么感兴趣, 面向过程编程 有什么问题? Nitin:我发现我的代码很难结构化和模块化。即使对于一个稍微不同的任务,我也别无选择,只能从头开始重写代码。此外,除了可重用性问题,我还发现调试代码很困难。
Me: 是的,你是对的,这些都是程序编程方式的一些局限性。但是不要担心,我们有面向对象编程来拯救我们。
Nitin: 那么,什么是面向对象编程(OOP)?
我: OOP 基本上是一种编程方式,集中于利用&实现对象和类来设计/编写特定的应用程序。
- 对象是遵循某种结构/轮廓的数据类型,它可以存储数据并可以基于某种操作操纵数据。
- 一个类是对象的一种轮廓/模板。这些类使用方法定义了一个对象在程序中的行为。
- 属性是一个对象或一个类的属性/特征。据说所有对象都是某个类的一个实例*。*
让我们借助一个例子来理解这些术语:假设你有一个手机商店&想开发一个系统来观察你商店里手机的特性。为了便于说明,让我们假设你只是苹果、诺基亚和三星智能手机的交易者。
所以,你从一个移动类开始。所有的手机都有一个共同的特点——它们是“智能手机”。所以“智能手机”变成了职业属性,一个我店里所有手机都拥有的属性。还有另一种特定于被创建对象的属性,它们被称为实例属性。例如,不同的智能手机有不同的属性,如公司名称、型号名称、颜色、存储、价格、等。对于不同的被创建对象,这些特征将是不同的。
一个具体对象/实例的例子——一部移动手机,公司名称= 三星,型号名称= 三星 Galaxy S10 ,颜色= 黑色,存储= 128 GB。因此,一个类是对它的实例拥有什么的描述。这个描述是由我们的实例属性给出的。在这里,我们可以看到数据被表示为对象的实例属性。
方法是我们系统组件交互的一种方式。方法是可以对我们的实例属性执行操作的动作。方法描述了类的行为。这些方法是特定类的所有对象都可以执行的通用操作。因此,一个类也是它的实例通过这些方法能做什么的描述。
考虑一个简单的例子:假设我们有另一个名为“current_battery”的实例属性,你可以定义一个方法 Charging ,这个方法在被调用时会更新你手机当前的电池电量。我们将在后面的章节中看到更详细的方法。
Nitin: 但是,方法和函数有什么区别呢? Me: 尽管两者都使用 def 关键字,但方法通常定义在类内部,而函数则定义在类外部。记住:方法描述了一个类的行为。此外,函数是独立于对象的过程,而方法则依赖于对象的状态。
Nitin: 这个挺有意思的。 Nitin:如果还能展示一些代码的话会很有帮助。* 我: 确定。我假设您了解一些 Python 编程的基础知识。请考虑以下情况:*
现在,你可能会问什么是“ init ”?是方法吗?什么是“自我”?它的价值是什么?让我们深入研究一下。
名字 " init" 是 python 中一个特殊的保留方法。我们使用 init 来指定正在创建的对象/实例的属性。 init 让你定制 python 如何实例化一个类的对象。也被称为构造器或初始化器。每当你创建一个类的新实例时,自动调用 init 方法。(您可以检查它打印的上面的代码“init 被自动调用”)。这里,m1 和 m2 是正在创建的新实例。**
【自我】 是指被创建的实例/对象(m1,m2)。 self 包含实例的内存。当我们在一个类中创建方法时,它们自动接收 instance 作为第一个参数。这与其他几种编程语言不同,在这些语言中,本身作为隐藏参数传递给方法。
Nitin: 一个类内可以多次使用构造函数吗? Me:是的,我们可以在一个程序中使用多个 init。但是,不管类中最后定义的构造函数是什么,它都将被使用。所以,本质上你只能使用一个构造函数。请考虑以下情况:
现在让我们继续前进。也可以手动为实例创建一个实例变量(属性),例如 m1.batterylife = 45。但是使用类没有任何好处,你每次都需要手动设置变量。同样,我们也可以更新实例变量的值。( m1.price = 620 )。借助于方法,我们也可以做到这一点。请考虑以下情况:
Nitin: 用方法改变值和直接更新值哪个好? Me: 从长远来看,通过一种方法来改变值会给你更多的灵活性。假设计量单位发生了变化——商店原本是以美元结算的,现在不得不处理卢比。你只需要乘以 70 倍。( self.price = price*70)。想象一下,如果您为每个实例手动执行该操作,会有多痛苦。
上面定义的方法(new_price,discount_price)是 Mobile 类中的常规方法。常规方法将实例又名自身作为第一个参数,用于改变对象/实例的功能。我们也有类方法。我们需要一些功能,不是写在一个对象上,而是写在整个类上。 它们用来修改一个类的状态。这些方法不是特定于对象的。****
我们在装饰器(“@classmethod”)的帮助下使用类方法。在这些方法中,我们将类(cls 关键字)作为第一个参数传递。考虑一个例子:
还有另外一类方法——静态方法。静态方法也属于整个类,而不是某个特定的对象。这些方法不能改变对象的状态,因为它们没有被绑定。我们在装饰器(“@staticmethod”)的帮助下使用静态方法。他们不把类或实例作为第一个参数。请考虑以下情况:
Nitin: 那么,静态方法和类方法有什么区别呢? Me:基本的区别是静态方法不能用来改变我们程序的功能,而类方法可以用来这样做。class 方法将类作为参数来了解该类的状态。静态方法不知道类的状态。这些方法通过接受一些参数来完成一些实用任务。
最后,我希望你喜欢学习哎呀。我很想知道任何阅读这篇文章的人的反馈。我很乐意回答对上述任何概念的疑问/问题。你可以在下面评论或者通过 Linkedin 联系我。
谢谢大家!
使用直方图和箱线图理解数据,并举例说明
了解如何从直方图和箱线图中提取最多的信息
理解数据并不意味着只得到*均值、中值、标准差。很多时候,了解数据的可变性或分布是很重要的。直方图和箱线图都有利于提供大量关于数据集的额外信息,有助于理解数据。
柱状图
直方图只从数据集中选取一个变量,并显示每个变量出现的频率。我将使用一个简单的数据集来学习直方图如何帮助理解数据集。我将使用 python 来制作情节。让我们导入数据集:
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
df = pd.read_csv("Cartwheeldata.csv")
df.head()
这个数据集显示了车轮数据。假设,办公室里的人决定在野餐时进行一场侧手翻距离比赛。上面的数据集显示了结果。我们来了解一下数据。
- 做一个“年龄”直方图。
sns.distplot(df['Age'], kde =False).set_title("Histogram of age")
从上图可以明显看出,大部分人都在 30 岁以下。只有一个人 39,一个人 54。这种分布是右偏的。
2.做一个‘CW distance’的分配。
sns.distplot(df["CWDistance"], kde=False).set_title("Histogram of CWDistance")
多好的楼梯啊!很难说哪个范围的频率最高。
3.有时将两个分布图放在一起可以很好的理解。在同一张图中绘制“高度”和“距离”。
sns.distplot(df["Height"], kde=False)
sns.distplot(df["CWDistance"], kde=False).set_title("Histogram of height and score")
从这幅图中,我们不能说身高和 CWDistance 之间有关系。
现在看看,我们能从箱线图中提取什么样的信息。
箱线图
箱线图显示了更详细信息的数据分布。它更清楚地显示了异常值,最大值、最小值、四分位数(Q1)、第三个四分位数(Q3)、四分位数间距(IQR)和中位数。你可以从 IQR 算出中间的 50%。下面是图片:
它还为您提供了有关数据偏斜度、数据紧密程度以及数据分布的信息。
让我们看一些使用侧手翻数据的例子。
- 做一个“分数”的箱线图。
sns.boxplot(df["Score"])
需要注意的是,最小值为 Q1-1.5 * IQR,最大值为 Q3 + 1.5*IQR。 它们不是字面上的数据的最小值和最大值。这表明了一个合理的范围。
如果您的数据值小于 Q1-1.5 * IQR 且大于 Q3 + 1.5*IQR,则这些数据可称为异常值,或者您可能需要再次检查数据收集方法。
在这张特殊的图片中,合理的最小值应该是,4–1.5 * 4,这意味着-2。我们的最小值不应小于-2。同时估计最大应该是 8 + 1.5*4 或者 14。所以,数据的最大值应该大于 14。幸运的是,数据集的最低分和最高分分别是 2 分和 10 分。这意味着没有异常值,数据收集过程也很好。从上图来看,分布看起来也是正态分布。
从这个图中,我们可以说,
a.分布是正态的。
b.中位数是 6
c.最低分为 2 分
d.最高分为 10 分
e.第一个四分位数(前 25%)是 4
f.第三个四分位数(75%)在 8
g.中间 50%的数据范围从 4 到 8。
h.四分位距是 4。
2.在同一箱线图中绘制两个变量有助于了解一个变量如何影响另一个变量。将 CWDistance 和“眼镜”绘制在同一个图中,看看眼镜是否对 CWDistance 有任何影响。
sns.boxplot(x = df["CWDistance"], y = df["Glasses"])
不戴眼镜的人比戴眼镜的人有更高的中位数。不戴眼镜的人的总体范围较低,但 IQR 值较高。从上图来看,IQR 的范围是 72 到 94。但是对于戴眼镜的人来说,总的视距要高一些,但是 IQR 从 66 到 90,比不戴眼镜的人要小。
3.分别针对戴眼镜和不戴眼镜的人的 CWDistance 直方图可以给出更多的理解。
g = sns.FacetGrid(df, row = "Glasses")
g = g.map(plt.hist, "CWDistance")
如果你看到,从这张图上看,戴眼镜的人的最大频率在 CWM 距离的开始。需要更多的研究来推断眼镜对*视距离的影响。构建一个置信区间可能会有所帮助。
我希望这篇文章能给你一些关于箱线图和直方图的额外信息。
更多阅读推荐:
对统计学中一个非常流行的参数——置信区间及其计算的深入理解
towardsdatascience.com](/a-complete-guide-to-confidence-interval-and-examples-in-python-ff417c5cb593) [## Python 中的交互式地理空间数据可视化
绘制世界特定地区的地图,在地图上展示活动,并四处导航
towardsdatascience.com](/interactive-geospatial-data-visualization-in-python-490fb41acc00) [## 想在 12 周内成为数据科学家?
花钱前再想一想
towardsdatascience.com](/want-to-become-a-data-scientist-in-12-weeks-3926d8eacee2) [## Python 中从头开始的多项式回归
学习用一些简单的 python 代码从头开始实现多项式回归
towardsdatascience.com](/polynomial-regression-from-scratch-in-python-1f34a3a5f373)
AI,ML,和 DL:如何不把它们混在一起!
理解人工智能、机器学习和深度学习之间的区别
我敢肯定,我们大多数人可能都熟悉“人工智能”这个术语,因为它一直是一些著名的好莱坞电影如《黑客帝国》、《终结者》、《星际穿越》的主要焦点。尽管好莱坞电影和科幻小说将人工智能描绘成像人类一样的机器人接管地球,但人工智能技术的实际进化甚至没有那么聪明或那么可怕。相反,人工智能已经发展到为医疗保健、零售、制造、银行等行业提供许多不同的好处。
人工智能、机器学习、深度学习、数据科学都是这个时代的热门词汇。了解它是什么以及它们之间的区别比以往任何时候都更加重要。虽然这些术语可能密切相关,但它们之间还是有区别的,请看下图。
作者照片
什么是人工智能?
自从提出“机器能思考吗?”这个问题以来,人类一直痴迷于创造人工智能是艾伦·图灵在 1950 年提出的。人工智能使机器能够思考,也就是说,在没有任何人工干预的情况下,机器将能够做出自己的决定。这是计算机科学的一个广阔领域,它使机器看起来像具有人类智能。因此,这不仅仅是给计算机编程,让它通过遵守交通信号来驾驶汽车,而是这个程序也学会了表现出类似人类的道路愤怒的迹象。
图片来自人工智能 HubSpot
人工智能系统的类型
人工智能系统根据它们模仿人类行为的能力、它们用于模仿人类行为的硬件、它们在现实世界中的应用以及思维理论来分类。使用这些特征进行比较,所有实际的和假设的人工智能系统都属于三种类型之一:
ANI:人工狭义智能
人工狭义智能也被称为弱人工智能,它是当今世界上唯一存在的人工智能类型。狭义人工智能是面向目标的,被编程为执行单一任务,并且在完成它被编程为要做的特定任务时非常智能。人工智能的一些例子有 Siri,飞机上的自动驾驶仪,聊天机器人,自动驾驶汽车等。
狭义人工智能系统不像人类那样有意识、有知觉或受情绪驱动,它们使用来自特定数据集的信息,并且不执行它们被设计来执行的单一任务之外的任何任务。
AGI:人工通用智能
人工通用智能也称为强 AI,是机器展示人类智能的一个概念。在这一点上,机器具有学习、理解和行动的能力,在给定的情况下与人类没有区别。一般的人工智能目前并不存在,但已经在许多科幻好莱坞电影中使用,在这些电影中,人类与有意识的机器互动,这些机器由情绪驱动并具有自我意识。
使用强大的人工智能,我们可以有能力建造能够在不确定的条件下思考、制定策略和执行多项任务的机器。他们可以在决策中整合他们先前的知识,提出创新的、创造性的和非常规的解决方案。
ASI:人工超级智能
我相信你还记得阿诺德·施瓦辛格的《终结者》,在这部电影中,机器的认知在各个方面取代了人类的智慧。人工超级智能是一种假设的人工智能,机器将能够表现出超过最聪明的人的智能。在这种类型的人工智能中,除了拥有人类多方面的智能之外,机器还将拥有比人类更强的解决问题和决策能力。正是这种类型的人工智能将对人类产生巨大影响,并可能导致人类从地球上灭绝。
什么是机器学习?
机器学习是人工智能的一个子集,它使用统计学习算法来构建系统,这些系统具有自动学习和从经验中改进的能力,而无需显式编程。
我们大多数人在日常生活中使用机器学习,比如在网飞、Youtube、Spotify 上使用推荐系统;像谷歌和雅虎这样的搜索引擎;像 google home 和亚马逊 alexa 这样的语音助手。在机器学习中,我们通过向算法提供大量数据,并允许它学习更多关于已处理信息的信息,来训练算法。
最大似然算法可以大致分为三类:监督学习、非监督学习和强化学习。
监督学习
在监督学习中,我们有输入变量(x)和输出变量(Y ),我们使用算法来学习从输入到输出的映射。换句话说,监督学习算法采用一组已知的输入数据集及其对数据的已知响应(输出)来学习回归/分类模型。然后,学习算法训练模型来生成对新数据或测试数据集的响应的预测。
无监督学习
当我们没有标记数据时,使用无监督学习。它的主要目的是在不参考已知输出的情况下,通过推断数据集中的模式来了解更多的数据。它被称为无监督的,因为算法通过在数据中寻找相似性、差异和模式来对未排序的信息进行分组。无监督学习通常作为探索性数据分析的一部分来执行。它最常用于查找数据聚类和降维。
强化学习
简单来说,强化学习可以解释为通过不断地与环境互动来学习。它是一种机器学习算法,其中代理通过不断使用来自其先前动作和经验的反馈,以试错的方式从交互式环境中学习。强化学习使用奖励和惩罚,代理人执行正确的动作获得奖励,执行错误的动作获得惩罚。
什么是深度学习?
深度学习是一种受人脑过滤信息方式启发的机器学习技术,它基本上是从例子中学习。它帮助计算机模型通过层来过滤输入数据,以预测和分类信息。由于深度学习处理信息的方式与人脑相似,因此它主要用于人们通常会做的应用程序中。这是无人驾驶汽车背后的关键技术,使他们能够识别停车标志,并区分行人和灯柱。大多数深度学习方法使用神经网络架构,因此它们通常被称为深度神经网络。
深度学习基本上是模仿人脑,它也可以被定义为包含大量参数和层的多神经网络架构。下面列出了三种基本的网络架构:
卷积神经网络
图片来自 MDPI 文章 熵 19.6 (2017): 242
卷积神经网络基本上是一种人工神经网络,它在计算机视觉领域中被最广泛地用于分析和分类图像。这是一种深度学习算法,它采用输入图像,并为图像中的各个方面或对象分配权重/偏差,以便能够区分彼此。CNN 的隐藏层通常由卷积层、汇集层、全连接层和标准化层组成。ConvNet 的架构类似于人脑中神经元的连接模式,其灵感来自视觉皮层的组织。
递归神经网络
递归神经网络是一种用于序列预测问题的神经网络体系结构,并且在自然语言处理领域中被大量使用。rnn 被称为递归,因为它们对序列中的每个元素执行相同的任务,输出取决于之前的计算。考虑 rnn 的另一种方式是,它们有一个“存储器”,可以捕获到目前为止已经计算过的信息。
要了解更多关于 RNN 的信息,请参考这篇文章
http://www . wild ml . com/2015/09/recurrent-neural-networks-tutorial-part-1-introduction-to-rnns/
递归神经网络
一种 递归神经网络 是一种 深度神经网络 通过对结构化输入应用相同的一组权重*来创建,以产生对可变大小输入结构的* 结构化预测 ,或对可变大小输入结构的标量预测[9]
递归神经网络更像分层网络,其中输入序列实际上没有时间方面,但是输入必须以树的方式分层处理。这是一个递归神经网络的例子。它展示了通过递归地获取对较小文本块执行的操作的输出来学习句子的解析树的方法。
截图来自 PyTorch —递归神经网络—教程点
本文试图通过定义人工智能、机器学习和深度学习,并解释每个主题中的各种子类别,来解释它们之间的差异。要更深入地理解上述主题,请参考参考资料部分提到的文章。
参考文献
- http://beam Andrew . github . io/deep learning/2017/02/23/deep _ learning _ 101 _ part 1 . html
- https://content simplicity . com/what-is-deep-learning-and-how-it-work/
- https://towards data science . com/what-the-type of-machine-learning-e 2 b 9 e 5d 1756 f
- https://code bots . com/artificial-intelligence/the-3-types-of-ai 是第三种可能性
- https://www . science direct . com/topics/computer-science/supervised-learning
- https://medium . com/@ tja jal/distinguished-between-narrow-ai-general-ai-and-super-ai-a4bc 44172 e 22
- https://towards data science . com/understanding-neural-networks-from-neuron-to-rnn-CNN-and-deep-learning-CD 88e 90 E0 a 90
- https://en.wikipedia.org/wiki/Recursive_neural_network
- https://www.youtube.com/watch?v=k2P_pHQDlp0&t = 404s
用数据分析理解网球运动中一发的重要性
我们能根据一个网球运动员的第一次发球来判断他的表现吗?
网球是一项动态复杂的运动。单个点涉及的击球有好几个,但只有一个是在不受对手影响的情况下进行的:发球。事实上,发球让球员有机会以具体的优势开始得分。良好的发球对每个职业选手来说都是很重要的,但是我们能把良好的第一发球和出色的表现联系起来吗?如果你是一名网球运动员,你可能听过这样一句话“你的第一次发球有多好,你就有多好”。这是真的吗?
资料组
为了回答我们的研究问题,我们需要好的数据。杰夫·萨克曼创建并维护网球文摘,这是一个令人难以置信的网站,提供了数以千计的网球比赛数据集。为了进行这项分析,我使用了他在 2019 年 ATP 比赛中的数据集。该数据集包括 126 场比赛的每场比赛的数据,包括戴维斯杯比赛,总共 2.782 场比赛。
该数据集中的重要列是显示每个球员和每场比赛的列:发球得分数、第一次发球得分数、第一次发球得分数、第二次发球得分数和双误得分数。
让我们开始分析。
表演
我们首先要分析的是通过计算胜场数来分析每个球员的表现。少于 10 场胜利的球员已经被排除在分析之外,在数据集中总共留下 89 个头。
从表中可以看出,前 10 名选手至少赢了 40 场比赛,而前 5 名选手至少赢了 53 场比赛。如果我们看一下前 5 名球员的总体表现,我们可以得出结论,他们比前 10 名名单中的后 5 名球员多赢得 30%的比赛。
每个玩家的获胜分布证实了表现最好的 5 个玩家是异常值,这意味着他们的表现远远高于其他玩家的表现。因此,在赛道上的优势是由少数运动员决定的。
发球
现在我们已经了解了比赛的表现,我们可以开始看发球了。通过处理数据集,我测量了每个球员第一次和第二次发球成功的百分比(成功:发球落在发球区)。此外,我计算了发球成功时第一次和第二次发球得分的百分比。下面是最佳 10 名演员的数据。
有了这些数据,我们就可以开始分析成绩和发球的相关性了。我们应该分析的第一个相关性是表现和成功的第一发球的百分比,这被认为是一个重要的因素。
从上面的散点图可以看出,这两个变量之间没有显著的相关性。我们需要进一步挖掘。也许表现和成功的第一次发球得分率之间的相关性能给我们更多的信息。
同样,这两个变量之间没有显著性。在这一点上,有两个原因可以解释这些结果:1)电路中的电*非常高,性能不是由发球决定的,或者 2)我们应该看看概率,看看我们是否获得了更重要的输出。
从不同的角度分析第一发球
我们已经看到,高比例的成功首次发球并不预示着高水*的表现。事实上,一些球员可能会在第一次发球时慢一些,以避免在第二次发球时得分。这就是为什么我们需要分析成功的第一次发球的百分比和成功的第一次发球得分的百分比之间的相关性,以了解这两个变量的重要性。
这里我们开始有了有趣的结果。这两个变量是负相关的,这意味着成功的首次发球的百分比越高,成功的首次发球得分的百分比越低。为什么?嗯,很难说,但是我们可以用风险来解释这个现象。当我们发球时,我们可以在三个主要策略中选择:1)我们可以瞄准发球得分(错过发球区的最高风险),我们可以瞄准给对手施加压力(中等风险),我们可以瞄准只在发球区内发球(最低风险)。我们可以假设,一发成功率高的球员没有冒足够的风险去赢得分数,因此,他们的一发没有赢得很多分。
散点图中数据点的大小取决于玩家的获胜次数。如果你看一看最大的数据点,几乎所有的数据点都落在 75 < X < 80 和 60 < Y < 65 之间的区域。这意味着最好的执行者在风险和最大化成功的机会之间找到了一个很好的*衡。通过计算第一次发球得分的概率,这一点会变得更加清楚。
完美均衡
第一次发球得分的概率由 p1*q1 给出,其中 p1 是第一次发球在发球区内的概率,q1 是假定第一次发球成功得分的条件概率。这是一个非常简单的计算,我们可以用两个变量来做。
表现最好的人(除了少数例外)是那些在第一次发球时最大限度地提高得分概率的人。如果我们看一下 X 轴,我们可以看到前五名选手的第一次发球赢得了超过 50%的发球得分(梅德韦杰夫除外)。事实上,很少有球员能够做到这一点,而那些做到的通常是像约翰·伊斯内尔这样的大发球者,他们在对打中表现更差。
通过在成功的第一次发球的百分比和成功的第一次发球得分的百分比之间找到完美的*衡来最大化第一次发球得分的概率。就拿费德勒和杜尚·拉乔维奇两个球员来举例吧。杜尚·拉乔维奇的发球成功率是 70%,而罗杰·费德勒是 65%。然而,费德勒在第一次发球成功时赢得 79%的分数,而拉乔维奇赢得 70%的分数。因此,拉乔维奇和费德勒第一次发球得分的概率分别是 49%和 52%。你可能会说微不足道,但在如此高的水*上,每一个微小的百分比都会有所不同。
第二次发球
我不会再用散点图来烦你了,因为表现和第二次发球得分的概率之间没有关联。然而,像费德勒和纳达尔这样的少数球员在第二次发球时有着令人难以置信的统计数据,获胜的概率在 60%以上。
正如我们所看到的,成功的第一次发球得分的概率分布趋于 0。那是什么意思?这意味着很少有球员有很高的概率在他们成功的第一次发球得分。第二次发球也一样吗?不完全是。如果我们仔细观察分布,我们可以看到第二把小提琴与第一把相比是颠倒的。事实上,许多球员在成功的第二次发球得分的概率是一样的。
在看剧情的时候,我们的视觉可能会欺骗我们,让我们相信 ATP 球员有更好的机会用二发拿下分。诚然,二发成功赢得该分的概率较高,但这是由于 p2(二发成功的概率)高于 p1(一发成功的概率)。
发球得分的概率
为了确定球员发球的强度,我们可以计算发球得分的总概率。根据奥多诺格的说法,发球得分的概率可以用公式 p1q1 + (1-p1) * p2q2 来计算,其中 p 是发球成功的概率,q 是假定发球成功时得分的条件概率。一和二分别代表第一和第二发球。
还是那句话,我们从上面的情节可以看出,表现最好的人在发球时有很大概率赢得一分。让我们看看费德勒和泽维雷夫,更好地理解一发和二发的重要性。如果我们回到最后的散点图,我们可以看到泽维雷夫比费德勒有更高的概率在一次成功的第一次发球中赢得分数(52.6 比 51.8)。然而,费德勒发球得分的总概率是 72%,而泽维雷夫是 65.8%。如果我们认为许多比赛是由两三个要点决定的,这是一个相当大的区别。
为什么泽维雷夫的概率比费德勒低?因为泽维雷夫的二次发球需要改进。的确,费德勒的二发有 56.7%的概率赢得一分,而泽维雷夫的概率只有 38.4%。这几乎是 20%的差异,对性能有很大影响。
限制
如前所述,网球是一项充满活力的复杂运动。很难得出这样的结论:有些球员比其他人表现得更好,因为他们成功发球得分的概率更高。发球和截击是命中最少的球。为了有一个更好的分析,我们应该考虑其他几个变量,尤其是每个球员的回报。
结论
对于 ATP 球员来说,在发球区内打出高比例的发球是不够的。事实上,球员需要通过*衡成功发球的概率和成功发球得分的概率来最大化他们发球得分的机会。成功发球的高概率可能表明球员没有承担足够的风险来给他的对手施加压力。
ATP 巡回赛中表现最好的选手能够找到一个完美的*衡,通过成功的第一次发球来最大化他们赢得积分的机会。此外,即使成功的第二次发球得分的概率与表现无关,第二次发球仍然是最大化发球得分机会的关键。因此,网球运动员应该通过*衡力量和风险来找到他们的完美*衡,以优化他们的发球比赛。
参考
- 页(page 的缩写)奥多诺格,a .巴兰坦,大满贯单打网球中发球速度的影响(2004),科学和球拍运动 III:第八届国际乒乓球联合会体育科学大会和第三届世界科学和球拍运动大会会议录。
- E.Gillet,D.Leroy,R. Thouvarecq,J. F. Stein,《慢速表面上优秀网球运动员发球和接发球策略的符号分析》( 2009 年),《力量和训练研究杂志》:第 23 卷第 2 期,第 532-539 页。
了解光 GBM
是什么让它更快更高效
由微软研究人员创建的 LightGBM 是梯度增强决策树(GBDT)的一种实现,这是一种以串行方式(增强)组合决策树(作为弱学习器)的集成方法。
梯度增强决策树
决策树以这样一种方式组合,即每个新的学习者拟合来自前一个树的残差,从而改进模型。最终的模型汇总了每一步的结果,从而形成了一个强学习者。
GBDT 是如此准确,以至于它的实现一直主导着主要的机器学习竞赛。
灯光背后的动机 GBM
决策树是通过基于特征值拆分观察值(即数据实例)来构建的。决策树就是这样“学习”的。该算法寻找导致最高信息增益的最佳分割。
信息增益基本就是分裂前后的熵差。熵是不确定性或随机性的度量。一个变量的随机性越大,熵就越大。因此,以减少随机性的方式进行分割。
寻找最佳分割是决策树学习过程中最耗时的部分。其他 GBDT 实现找到最佳分割的两种算法是:
- 预先排序:特征值预先排序,并评估所有可能的分割点。
- 基于直方图:连续特征被分成离散的箱,用于创建特征直方图。
“Sklearn GBDT”和“gbm in R”使用预排序算法,而“pGRT”使用基于直方图的算法。“xgboost”两者都支持。
基于直方图的算法在内存消耗和训练速度方面更有效。但是,随着实例或特征数量的增加,预排序和基于直方图的速度都会变慢。LightGBM 旨在解决这个效率问题,尤其是对于大型数据集。
是什么让 LightGBM 更加高效
LightGBM 的起点是基于直方图的算法,因为它比预先排序的算法性能更好。
对于每个特征,扫描所有数据实例以找到关于信息增益的最佳分割。因此,基于直方图的算法的复杂性取决于数据实例和特征的数量。
为了解决这个问题,LightGBM 使用了两种技术:
- 梯度单侧采样
- EFB(独家功能捆绑)
下面就来详细说说这些技术是做什么的,是如何让 LightGBM“轻”起来的。
高斯(梯度单侧采样)
我们已经提到,一般的 GBDT 实现会扫描所有数据实例,以找到最佳分割。这绝对不是一个最优的方式。
如果我们能够根据信息增益对数据进行采样,算法将会更加有效。一种方法是根据权重对数据进行采样。但是,它不适用于 GBDT,因为在 GBDT 没有样品重量。
GOSS 通过使用梯度来解决这个问题,梯度给了我们对信息增益的有价值的洞察力。
- 小梯度:算法已经在这个实例上被训练,并且与它相关的误差很小。
- 大梯度:与该实例相关的误差很大,因此它将提供更多的信息增益。
我们可以排除梯度小的实例,只关注梯度大的实例。但是,在这种情况下,数据分布将会改变。我们不希望这样,因为这将对学习模型的准确性产生负面影响。
GOSS 提供了一种基于梯度的数据采样方法,同时考虑了数据分布。
它是这样工作的:
- 数据实例根据其梯度的绝对值进行排序
- 选择了前 ax100%个实例
- 从剩余的实例中,选择大小为 bx100%的随机样本
- 当计算信息增益时,小梯度的随机样本乘以等于(1-a) / b 的常数
GOSS 最终实现的是,模型的重点倾向于导致更多损失(即训练不足)的数据实例,而不会对数据分布产生太大影响。
EFB(独家功能捆绑)
具有大量要素的数据集可能具有稀疏要素(即大量零值)。这些稀疏特征通常是互斥的,这意味着它们不会同时具有非零值。考虑独热编码文本数据的情况。在特定的行中,只有一列指示特定的单词是非零的,所有其他行都是零。
EFB 是一种使用贪婪算法将这些互斥组合(或捆绑)成单个特征(例如,互斥特征束)并因此降低维度的技术。EFB 减少了 GDBT 的训练时间,而不太影响准确性,因为创建特征直方图的复杂性现在与束的数量而不是特征的数量成比例(束的数量远小于特征的数量)。
EFB 面临的挑战之一是找到最佳捆绑包。微软的研究人员设计了一种算法,将捆绑问题转化为图形着色问题。
在图着色问题中,将特征作为顶点,在不互斥的特征之间添加边。然后,使用贪婪算法来产生束。
更进一步,该算法还允许捆绑很少同时具有非零值(即几乎互斥)的特征。
另一个挑战是以能够提取原始特征的值的方式将特征合并成束。考虑一组 3 个特性。我们需要能够使用捆绑特性的价值来确定这 3 个特性的价值。
回想一下,基于直方图的算法为连续值创建离散的仓。为了克服合并特征的挑战,将束中特征的唯一值放入不同的箱中,这可以通过向原始特征值添加偏移来实现。
结论
LightGBM 背后的动机是解决在处理大型数据集时与 GBDTs 的传统实现相关的训练速度和内存消耗问题。
目标基本上是在尽可能保留信息的同时减少数据的大小(包括数据实例和特征)。戈斯和 EFB 技术是实现这一目标。
根据 LightGBM 创造者的论文,“LightGBM 将传统 GBDT 的训练过程加快了 20 倍以上,同时达到了几乎相同的精度”。
感谢您的阅读。如果您有任何反馈,请告诉我。
参考文献
- LightGBM:一个高效的梯度推进决策树
理解主成分分析和特征向量之间的联系
在我之前的文章中,我一直在介绍主成分分析的概念,这是一种在机器学习中使用的强大技术,用于降低输入数据的维度。PCA 的一个重要的数学特征是它与原始特征矩阵 X 的特征向量(和特征值)的联系,在本文中,我将展示这种联系背后的比率。
让我们先简要回顾一下 PCA。与特征选择技术不同,PCA 不使用初始特征的子集:而是计算一组新的特征,这些特征是原始特征的线性组合,存在于低维空间中。
因此,如果我们有 n 个观测值的特征矩阵 X ,每个观测值有 p 个维度:
我们可以计算新的特征矩阵 Z,从前者的线性组合获得:
其中 M 最多和 p 一样大,但是当然因为任务的目的(降维),所以选择小于 p。
我们如何获得 Z?如上所述,它是原始特征的线性组合,因此第 m 个分量的第 I 个观察值由下式给出:
其中:
前 2 行的系数称为加载因子,其计算方式是这些新变量不相关,初始变量中的大部分信息存储在第一个分量中。因此,想法是 p 维数据给你 p 个主成分,但 PCA 试图将最大可能的信息放在第一个主成分中,这样,如果你想减少数据集的维度,你可以将分析集中在前几个成分上,而不会遭受信息损失方面的巨大损失。
在这种分析中,度量信息量的是方差,并且主成分可以在几何上被视为高维数据的方向,其捕捉最大数量的方差并将其投影到较小维度的子空间,同时保留大部分信息。因此,第一主成分占最大可能的方差;第二个分量将直观地解释第二大方差(在一个条件下:它必须与第一个主分量不相关)等等。
以此为目标,我们设置了一个约束最大化问题,其中我们寻求最大化每个主成分的方差,服从加载因子和归一化条件之间的正交条件(实际上,如果我们让加载自由,我们可以无限地增加它们以最大化方差)。
因此:
从特征值和特征向量的定义,我们可以得出结论,加载因子确实是特征向量。更准确地说,我们选择与最高特征值相关联特征向量作为加载因子。
所以这里是 PCA 和特征向量的联系!可以看出,上述关系也适用于其余的载荷因数,附加的约束条件是相互正交。
我们也可以形象化这个过程的几何表示。为了简单起见(以及可视化的可能性),我将使用 2 维的原始特征空间,并且我将计算 2 个主成分,因此我不打算降低特征空间的维度。
假设我们有以下二维输入数据:
我已经把它定为 0 了。主要成分表示为:
现在,为了将我们的数据绘制到新的特征空间中(在这种情况下,我们创建了两个主成分,因此它仍然是 2D 图),我们只需旋转这两个主成分,使 PC1 水*,PC2 垂直,这意味着它们是新的 x 轴和 y 轴。
我希望这篇文章是清晰和令人愉快的!如果你对这个话题感兴趣,并且想了解更多,我推荐以下阅读材料:
- https://towards data science . com/visualizing-environmentations-and-features vectors-e 2 f 9 e 3 AC 58d 7
- https://towards data science . com/PCA-特征向量-特征值-1f968bc6777a
- 【https://www.youtube.com/watch?v=FgakZw6K1QQ
理解极大极小算法
用人工智能玩游戏
假设你和一个朋友玩游戏。“你在游戏中的表现有多好”是通过一个数字分数来衡量的,当你比你的朋友更接*“赢得游戏”时,这个数字分数会增加,当你的朋友比你更接*“赢得游戏”时,这个数字分数会减少。在这种情况下,你可以把自己想象成最大化分数,把你的朋友想象成最小化分数。
我们可以想出一种算法,能够做出好的决策,并通过以下方式对上述情况进行建模来赢得这样的游戏:我们将有两个相互调用的实体(函数);一个试图最大化分数,另一个试图最小化分数。基本上,这两个功能将模仿两个球员。
这个算法也是 AI 不是 ML 的一个很好的例子。有人制造这种混乱,认为 AI = ML 实际上,ML 是 AI 的一个子集。有些 AI 技术不涉及 ML。极大极小算法就是这样一种算法,它让计算机表现得很聪明,但它们并没有学到任何东西。尽管如此,它在许多游戏中运行良好。
极大极小算法
如果我们认为一个游戏是由 Max 和 Min 这两个玩家轮流进行的,那么我们可以把这个游戏描述为一个决策树。让我们看一个非常简单的例子:
这个树中的每个节点(除了终端节点)代表在游戏中的那个时刻应该做出的决定。我们决定采取哪一步行动。在这个例子中,我们只能从两个动作中选择,但一般来说,我们可以有任意数量的动作,这个数量可以根据游戏的状态而不同。
顶部节点(深度为 0 的节点)是游戏的当前状态。这里是我们决定游戏下一步行动的地方。这里我们从 Max 开始,因为它是玩家想要我们做的:最大化分数。他不是只根据 Max 从这一点开始可能达到的下一个博弈状态来决定,而是想:“在我走完其中一步后,我的敌人 Min 会怎么做?”
所以,它打电话给 Min 说:“嘿,如果我选左,你会怎么走?”,之后:“如果我选右,你会采取什么举措?”。
在 Max 找到 Min 要做的事情后,他选择能给他最高分的分支。
但是等等。Min 将如何决定做什么来最小化分数?他将采用与马克斯相同的策略。反过来,Min 会打电话给 Max,问他对于 Min 的每一个可能的选择,他会怎么做。但是在那之后,Min 并没有做出最大化分数的选择,而是反其道而行之:会做出最小化分数的选择。
以此类推……每个都将调用另一个,以类似这样的递归方式构建一棵大树,直到它们达到一个终止状态。
优选地,结束状态应该是游戏结束的状态。但是这通常在计算上代价太大;让算法探索所有可能的移动,直到游戏结束,这可能需要非常长的时间。所以,我们设定了一个最大深度。当游戏结束或达到最大深度时,游戏状态将被视为结束。
在上面的例子中,终端状态是底部的状态(深度 2)。
终端节点上会发生什么?
无论哪个玩家将到达终点状态,无论是最大值还是最小值,都不能使用到目前为止使用的相同策略;向对方寻求帮助的策略。
现在,在终端节点中,我们需要计算这些终端状态中每一个的游戏分数。
这在某些游戏中可能不那么明显,但我们需要,至少根据游戏的状态来估计分数。
在上面的例子中,终端状态的分数是底部的数字:1、2、3 和 4。
Max 玩家在这最后一关唯一要做的事情就是把这些分数返回给前一关的调用 Min。
完成后,我们的树看起来会像这样:
Min 要做的选择用箭头标出。分数从级别 2 传播到级别 1(分钟的级别)。
现在,在我们知道深度 1 将会发生什么之后,我们让 Max 在 0 层进行他的移动:
这意味着在博弈的当前状态下,最优的移动是由顶部节点的右边缘表示的,我们得到的分数至少是 3。
我说“至少”是因为这个算法运行起来好像 Max 和 Min 总是选择他们的最佳移动。但是如果,在现实中,我们的敌人比闵更坏,我们可能会得到更好的分数。
那么现在,如何用代码把这个算法表达清楚呢?上面的树只是为了直观地描述它。计算机不需要显式地构建这样的树。我们只需要两个函数:最大化和最小化,它们会互相调用。
下面是上述算法的示意图:
maximize()
函数返回一个元组,该元组在其第一个位置包含使分数最大化的游戏的子状态,在第二个位置包含对跟随该状态将达到的分数的估计。minimize()
函数返回一个模拟量。
下面的decision()
函数将游戏的当前状态作为输入,并返回如果我们想要最大化我们的分数应该遵循的状态。
上面的eval()
是估算其游戏输入状态得分的函数。
α — β剪枝的极小极大
深度越大,我们的球员就越优秀。但是如果我们为它设置一个大的深度,这个算法会花费一些时间。所以,我们应该选择最大可能的深度来满足我们的时间要求。我们能不能让这个算法更快一点,这样我们就可以使用更大的深度值,同时还要遵守时间限制?
让我们看看下面的例子:
为了决定顶部节点的移动,我们需要评估树中的所有节点吗?
α-β剪枝是一种策略,我们可以通过忽略树的一些分支来改进极大极小算法,我们事先知道这些分支不会帮助我们做出最佳决策。名称α-β修剪来自于该算法中使用的两个参数,即α和β。
这种方法是如何工作的?
每当我们建立一个节点的分数时,我们也将它在树中向上传播到它的父节点,并使用它来设置父节点结果的下限或上限。
例如,在我们的树中,如果我们在建立了最左边的终端节点的分数(是 4)之后开始从左到右评估节点,我们知道父节点的结果不能大于 4。那是因为父节点是一个 Min 节点,如果我们已经有了 4 个,父节点就不能选择比这个更大的。
但是,在这一点上,这些信息没有太大的帮助。让我们继续,看看会发生什么。我们评估下一个终端节点,发现父节点的值是 3,并且顶部节点不能小于 3。这条信息会有所帮助。
如果我们继续评估节点,我们会发现得分为 2 的终端,这意味着它的父节点应该≤ 2。
现在,我们还需要评估最后一个节点吗?不。我们已经知道,如果沿着左边的分支,顶部节点至少可以得到 3。我们知道从右边的分支我们不能得到超过 2。所以,从现在开始我们可以忽略右边的分支。
在这个小例子中,这似乎不是一个很大的改进,因为我们只跳过了一个终端节点。但是在更大的树中,一个这样的节点可能包含一个相当大的子树。请注意,这里我们只跳过了一个兄弟节点,因为这是该分支中唯一剩下的节点。如果有更多的剩余节点,我们将跳过它们。例如,如果右分支有 2、1 和 0 作为终端节点,那么在我们评估 2 之后,我们将能够跳过 1 和 0。
此外,跳过多少节点将取决于终端节点的排序。你可能注意到了,在这个例子中,我把分数从 1,2,3,4 倒过来,变成了 4,3,2,1。这不是随机的。当分数按升序排列时,这是没有修剪发生的最坏情况。但这种糟糕的排序在实践中出现的可能性很小。*均而言,与没有修剪相比,α-β修剪允许极大极小算法在相同的时间内进行几乎两倍的深度。
下面是上述算法的伪代码。
Alpha 是(直接或间接)父 Max 节点中的最大下界,而 Beta 是(直接或间接)父 Min 节点中的最小上界。在最大化中,在迭代子节点时,我们将 alpha 值更新为目前为止找到的最大值,如果该最大值大于 beta,我们知道有一些父最小节点不会选择我们的当前分支,因此我们停止探索剩余的子节点。一个类似的事情发生最小化。
决策函数中的-/+无穷大(第一次调用最大化)意味着我们开始算法时对结果分数没有限制。
因此,极大极小算法是一个相对简单的算法,在简单的游戏中运行良好(低分支因子)。这也是 AI 不是 ML 的一个很好的例子。
在接下来的几篇文章中,我将展示如何使用该算法(以及 Selenium WebDriver)来创建一个能够在我们的屏幕上直播 2048 游戏的 AI。
[## Selenium WebDriver:用代码浏览网页
如何与网站互动并提取数据
medium.com](https://medium.com/towards-artificial-intelligence/selenium-webdriver-browse-the-web-with-code-f064d3556a8) [## 如何将 Minimax 应用到 2048 年
2048 年——一个简单的游戏,但是给计算机编程来解决它就不简单了
towardsdatascience.com](/playing-2048-with-minimax-algorithm-1-d214b136bffb) [## 如何表现 2048 年的游戏状态
…以及如何以面向对象的方式做到这一点
towardsdatascience.com](/how-to-represent-the-game-state-of-2048-a1518c9775eb) [## 如何控制 2048 的游戏板
…并完成最小最大算法的实现
towardsdatascience.com](/how-to-control-the-game-board-of-2048-ec2793db3fa9)
我希望这些信息对你有用,感谢你的阅读!
这篇文章也贴在我自己的网站这里。随便看看吧!
理解蒙蒂·霍尔问题
雅各布·卡尔普在 Unsplash 上的照片
读完这篇文章,它将不再戏弄你的大脑
T 何天魔堂问题是一道流行的概率脑筋急转弯。这也是当我第一次听到答案时,我无法理解的一个问题。你有没有遇到过这样的情况:有人向你解释了一些事情,而这些事情对你来说是合理的,但你的直觉却一直在喊:“这不可能!”嗯,我第一次学解的时候就是这种感觉。
由于我们目前在三藩市避难(由于正在进行的疫情),我有很多时间去思考、阅读和写作。所以今天我决定我将最终完全了解天魔堂的问题。
问题是
来自维基百科:
“假设你在参加一个游戏节目,你有三扇门可供选择:一扇门后是一辆汽车;其他人后面,山羊。你选了一扇门,比如 1 号门,主人知道门后是什么,他打开了另一扇门,比如 3 号门,里面有一只山羊。然后他对你说,“你想选 2 号门吗?"换个选择对你有利吗?
由 sergio souza 在 Unsplash 上拍摄的照片
解决方案
剧透警告:答案是,是的,我们确实想切换。但是为什么呢?如果我们真的做出了改变,我们得到汽车的可能性会增加多少呢?让我们找出答案。
这个问题是为了戏弄大脑而设计的,即使对于受过概率训练的人也是如此。它想让你迷失在概率中,直到你放弃并说,“它们不都是一样的吗?”
得出正确答案的一个非数学且相对简单的方法是使用下面的推理:
- 一开始,当我们随机选择 3 个门中的一个时,我们得到车的概率是 1/3。
- 如果在主人打开一扇门后,我们坚持自己的选择,我们拿到车的概率仍然是 1/3。一切都没有改变,尽管感觉就像门被打开时一样;所以我们的概率也保持不变。请这样想——如果我们坚持自己最初的决定,那么这个游戏就相当于我们只能选择一次,并且随后被我们的选择所束缚。在这种情况下,无论主持人是同时打开所有的门,还是一个一个地慢慢打开(他在蒙蒂霍尔问题中有效地做到了这一点),得到汽车的概率总是 1/3。
- 当他打开一扇后面有山羊的门,给我们重新选择的选项,而不是把它当成原来游戏的延续,我们应该把它当成一个新游戏。在这个新游戏中,我们只有两扇门可以选择。如果我们随机选择这两扇门中的一扇门(比如通过抛公*硬币),我们得到汽车的概率是 1/2。但只有当我们真的再次随机选择时,我们的概率才会提高,所以请确保正确地抛硬币。很恍惚,对吧?即使我们最初的选择是剩下的两个可能的选择之一,如果我们坚定不移地坚持我们最初的选择,我们得到这辆车的概率仍然是 1/3。但是,如果我们在剩下的两扇门中随机选择一个新的门(我们仍然可能选择原来的选择),我们最终会有 50%的机会买一辆新车。
所以正确答案是肯定再选。但实际上正确的答案是选择另一扇门——不是你最初选择的那扇门。那么你落地的概率上升到 2/3!
那是怎么发生的?
我用 Python 为蒙蒂霍尔问题编写了一个模拟器( 你可以在这里找到我的代码 )。当我运行了 90 万次,然后非常努力地盯着结果时,我意识到发生了什么。
如果主人打开一扇后面有山羊的门后,你不改变你的门选择,结果会是这样。正如所料,在 1/3 的模拟中,你很幸运地选择了正确的门。
如果不更改选择,模拟结果
很明显,如果你有 1/3 的机会选择正确的门,那么你也有 2/3 的机会选择错误的门。扭转乾坤不是很好吗?但是唯一的方法就是我们选择两扇门,对吗?
如果我告诉你,在主人打开一扇门后,你改变了门的选择,你实际上选择了两扇门,会怎么样?听起来好得难以置信,对吗?但事实就是如此。如果我们颠倒一下情况,就更容易明白为什么了。
让我们想一想,如果我们决定扳动我们的门把,我们会有什么损失。在这种情况下,如果我们最初的选择是正确的,我们就输了。这是因为通过打开一扇错误的门(一扇有山羊的门),主人实际上是在给我们一个免费的猜测。然后我们可以通过翻转我们最初的选择来完成我们最初决定的逆转。我们最初的选择将有 1/3 的几率是正确的——由于我们现在已经采取了最初选择的另一方,我们有 2/3 的几率得到汽车。
一个更直观的形象化方法是将 Monty Hall 问题重新塑造成一个稍微不同的游戏。想象你正在和另外两个玩家比赛。你们三个被随机分配到不同的门,你们可以保留分配给你们的门后的东西。
关键的转折
就在你开门之前,主人走过来对你说:
我会给你一个选择。你要么保留你门后的东西,要么保留你对手门后的东西。是的,两个都是!
你会接受这个交易吗?我肯定会的。你的两个对手各打开一扇门(总共 2 扇门),因此他们有 2/3 的机会得到这辆车。如果你继续住在你现在的房子里,这比你 1/3 的机会要好,最坏的情况下,你会得到 2 只新的宠物山羊。
在 Monty Hall 问题中,通过选择切换你的门选择,你有效地选择了你对手的门(一个由主持人打开,另一个由你在你决定逆转自己之后打开)。
我们可以在模拟结果中看到这一点:
如果您更改选择,模拟结果
让我们看一下表格的前两行,以确保您知道发生了什么。在第一排,我们选了 1 号门。然后主人打开 2 号或 3 号门,露出一只山羊。我们选择他没有打开的门,我们也得到一只山羊(因为我们最初的选择是正确的,汽车在门 1 后面)。太糟糕了!
在第二行中,我们再次选择门 1。但这一次,车在 2 号门后面。知道了这一点,主人只能打开 3 号门,否则他会把车给我们看!最后,我们切换到 2 号门,打开它,得到一辆全新的汽车!
在我们的 900,000 次模拟中,我们现在在其中的 600,000 次模拟中赢得了这辆车(如果我们切换),概率为 2/3,正如我们上面推理的那样。
希望你喜欢这个,并发现它很有见地。我计划在未来的日子里探索更多的概率和统计脑筋急转弯。干杯,祝大家健康!
理解 Sigmoid 输出单元的动机
本文试图对 sigmoid 函数的动机及其在输出单元上的使用给出一个全面的解释。
二元分类网络示例。男人的插图来自 Pinterest ,而熊的图像则是从这里获得的。
在阅读《深度学习一书中关于伯努利输出分布的 6.2.2.2 Sigmoid 单位的章节时,出现了一些与 Sigmoid 函数背后的直觉有关的问题,我将在这篇文章中尝试解决这些问题。本文试图通过阐述 sigmoid 函数的动机及其在输出单元上的使用,对上述章节给出一个全面的解释。
从线性回归到二元分类
假设我们试图用神经网络来解决一个有监督的二进制分类问题。假设输出变量 y 只能取两个值,我们将假设这两个值为 0 和 1,网络只需要预测 P(y=1|x),,因为两个类的概率必须相加为 1。所以,条件概率是一个参数为 p=P(y=1|x) 的伯努利变量。
让
- h 表示最后的隐藏层;
- θ表示输出层的权重向量;
- b 表示输出层的偏置。
输出层将从前一个隐藏层接收 h ,并将计算其输入的线性组合,我们定义为
在下图中用蓝色表示。然后,输出单元必须应用函数 f 来计算输入为类别 1 的概率,用黄色表示。我们的目标是理解如何定义这个映射。
二进制分类神经网络,强调输出单元。
问题:如何将一个实值(来自最后一个隐藏层 z 的线性组合)映射到一个概率,即映射到一个 0 到 1 之间的数字?换句话说,我们想定义一个函数 f
一个简单的解决方案是只考虑在 0 和 1 之间的那部分 z。所有负值将被映射到 0,而所有大于 1 的值将被映射到 1,如下图所示。
max{0,min{1,z}}函数的绘图。
为了简化,我们定义 ŷ=P(y=1|x) ,并且我们让ℒ( ŷ,y) 表示损失函数。如上图所示, ŷ 的导数几乎处处都是 0(除了 0 和 1 之间)。下面描述了如何应用链规则来更新权重,其中黑色和红色箭头分别表示向前和向后传播(该图的灵感来自于吴恩达在 Coursera 中的深度学习专业)。我们可以看到,∂ŷ/∂z 为 0 的事实意味着,所有的导数(使用红色箭头)也将为 0,因为链式法则乘以它们。
梯度下降中用于更新权重的链式规则。
例如,使用梯度下降并让α表示步长,则 θ的更新为
这将使 θ 在每次∂ŷ/∂z 为 0 时保持不变(这在检查 ŷ).的图时经常发生因此,如果大多数更新保持 θ 不变,那么达到参数 θ 的最佳值可能会非常困难。这被称为消失梯度问题,我们将在后面详述。因此,使用梯度下降来对条件概率建模的函数选择不会使训练非常有效,因此,我们需要另一种方法来将 z 映射到【0,1】。
激发乙状结肠功能
在这一节中,我们将推导 sigmoid 函数,作为我们寻找映射 f : ℝ → [0,1] 问题的解决方案。我们将展示获得 sigmoid 的两种方法:
- 使用 0-1 损失;
- 应用与 softmax 函数中相同的基本原理。
从 0-1 的失利开始
在这里,我们将描述0–1 的损失如何激励对数赔率的使用。假设我们有一个函数 g 将 x 映射到{0,1}中的一个类。我们可以查看 0–1 损失,它计算错误分类的数量,定义为
我们希望最小化算法所犯的错误数量,因此,我们可以最小化预期条件损失(也称为条件风险):
最小化期望条件损失等价于最大化 P(y=g(x)|x) 。因此,如果对于 y=1 的条件概率高于对于 y=0 的条件概率,则预测的类别 g(x) 应该是 1。这可以表示为
一个事件发生的几率是根据该事件发生的可能性与不发生的可能性之比来定义的。
数学上,这被定义为
注意,虽然事件的概率 P(E) 是 0 到 1 之间的数字,但是事件的概率 odds(E) 可以取任何非负值。通过记录一个事件的概率,称为 log odds ,我们得到一个实数值,我们可以对其进行线性建模,正如我们将在接下来看到的。
在不等式的左边 (1) 我们有条件概率的几率 y=1|x 。通过将对数应用于这个不等式,我们得到下面的不等式
对数概率变量现在可以取任何实数值,因此,我们可以作出简化的假设,即它是输入到输出单元的 h 的线性函数。因此,我们可以将对数概率写成
通过让 z 表示 h 的线性组合,如上面等式的右侧所示,求解此等式得到 P(y=1|x) 的 sigmoid 函数
因此,sigmoid 函数定义为
从 0–1 损失开始,我们可以通过假设对数几率在数据上是线性的来推导 sigmoid 函数。
模仿 Softmax 推导
现在让我们分析获得 sigmoid 函数的另一种方法,首先理解另一个也代表概率的函数(称为 softmax )背后的基本原理,然后尝试将其应用于 sigmoid 函数。这种方法的动机是《深度学习》一书的作者在第 179 页提出的假设:
(…)未标准化的对数概率在 y 和 z 方向上是线性的。
他们首先将非标准化对数概率建模为
这个假设对我来说并不明显,但是这篇博文从 softmax 函数开始帮助我更好地理解它。Softmax 可以被视为 sigmoid 函数的推广,其中我们可以有任意数量的类,而不仅仅是 2 个。设 k 为班级总数。Softmax 输出一个概率向量,每个类别一个。对于每个类别 c ,softmax 函数的输入是实数值的向量 z ,输出是与包含 P(y=c|x) 的 z 大小相同的概率向量。类似于 sigmoid 函数,在 softmax 中我们有将实数值映射到 [0,1】的 k 问题。此外,为了使 softmax 表示有效的概率分布,其所有输出的总和必须为 1。通过将 softmax 函数表示为 f ,这可以表示为
对于 k⩾2 ,我们可以分两步构造这样的函数 f :
- 求幂:使元素非负;
- 标准化:将输出转换成概率。
同样的想法可以用于 sigmoid 函数,其中我们从类别 1 的 z 开始,并且我们将类别 0 的非标准化对数定义为 0。应用相同的基本原理产生 sigmoid 函数。
softmax 的构造与 sigmoid 函数的关系。
同样,我们将 P(y=1|x) 定义为 z 的 sigmoid 函数。通过查看上图,我们可以将条件概率更简洁地写成
消失梯度问题
在分析了获得 sigmoid 函数的两种不同方式之后,让我们检查该函数是否是二进制分类网络的输出单元的良好候选。通过检查下面的 sigmoid 函数的图,我们可以看到, z 的大值将产生输入示例属于类 1 的非常有把握的预测,而 z 的非常负的值指示输入属于类 0 的概率很大。
sigmoid 函数图。
sigmoid 函数对非常大的正值和负值都饱和。在这些情况下,这将导致梯度∂ŷ/∂z 为 0。记住这也是我们首先分析的比较简单的函数中的问题, f(z)=max{0,min{1,z}} 。因此,当链式法则乘以偏导数时,学习可能是无效的。这被称为消失梯度问题,其中消失的小梯度阻止了权重的更新。
我们现在将引入一个损失函数ℒ,它将消除这种不方便的 s 形饱和效应。
损失函数
请记住,由于条件输出变量具有伯努利分布,我们可以将其最大似然与神经网络的损失函数相关联。在最大似然估计中,我们将分布的参数估计为使观测数据的似然性最大的参数
其中 m 是示例的总数,我们使用上标符号来表示每个示例的索引。由于最优参数可以写成使条件概率的负对数最小化的自变量,因此将损失函数定义为负对数是有意义的。举一个例子,通过表示 ŷ=P(y|x) ,损失函数被定义为
现在让我们计算偏导数。
在计算下一个导数之前,我们先计算 sigmoid 函数的导数
现在,使用等式 (2) 来计算 ŷ 的导数是很简单的
正如我们接下来将看到的,在损失函数中使用对数将消除使用链式法则的 sigmoid 函数的指数效应
现在让我们检查对于损失函数来说,z的哪些值具有零梯度。下面,在左侧,损耗被绘制为 z、的函数,在右侧,我们描绘了各自的梯度。**
损耗和各自的导数,都是 z 的函数。
我们可以观察到,对于 y=1 (红色曲线),当 z 取非常大的正值时,损耗饱和。等效地,当 z 趋于+∞时,梯度趋*于 0。在这种情况下, P(y=1|x) = σ(z) 接*于 1,因此,神经网络已经有了正确的答案。类似地,对于 y=0 ,损耗仅在 z 非常负(梯度趋于 0)时饱和,因此 P(y=0|x) = 1-σ(z) 接* 1,这也是正确的响应。
简而言之,只有当算法已经预测到正确的标签时,损失才会饱和,因此在这些情况下停止学习是可以的。
此外,当模型做出不正确的预测时,即当 y=1 和 z 为负时,或者当 y=0 和 z 为正时,梯度的幅度趋向于 1,因为模型(错误地)更确信其误分类。
该属性允许参数在正确的方向上快速(因为梯度不收缩)更新。
损失函数将对数应用于 sigmoid 的事实抵消了其不想要的饱和效应。否则,对于非常正值和负值的 z ,我们将得到接* 0 的梯度,这将使该函数不适合学习。这种效果如下图所示。
损失函数中的对数如何解决 sigmoid 输出单元的消失梯度问题。
结论
本文的主要目的是为二分类神经网络设计一个输出单元。我们将 sigmoid 函数作为将实数值映射到概率(即 0 到 1 之间的数字)的问题的解决方案。我们使用两种不同的方法来推导 sigmoid:0–1 损失和 softmax 函数。
我们还通过使用负对数损失函数解决了 sigmoid 函数中的消失梯度问题。这使得我们可以得出结论,对于二进制分类问题, sigmoid 是一个合适的 输出单元。
然而,不建议使用 sigmoid 作为隐藏单元的激活函数,因为我们不会有跟随该单元的最大似然损失。因此,当 σ 在其域的重要部分饱和时,网络将遭受消失梯度问题。其他功能,如整流线性单元(ReLU)通常用作隐藏层中的激活。
了解你的 A/B 测试的力量
实验设计选择的视觉探索
当设计一个实验时,许多决定应该提前做出。其中一个我觉得难以想象的是力量。这个概念很简单— 如果一种效应确实存在, 我们发现它的可能性有多大?— 但一开始,确定不同因素如何影响它对我来说很棘手。下面,我将一些影响测试效果的因素形象化,但是首先让我们回顾一些事情。
让我们举个例子,当一个人去蔬菜水果店里时,他是否会买一个苹果。从历史上看,假设 10%的客户这样做,我们称之为 p⁰ (通常我们在这里使用下标,但对于中等上标更容易)。我们想知道将苹果架移*门(治疗)是否会增加购买苹果的人的比例。在改变展台位置后,我们将该比例称为 p (不管它是否从 p⁰ 改变)。*
为了使这成为真正的 A/B 测试,我们还需要一个对照组。在这个例子中,我们可以移动一半连锁店(随机选择)的摊位,而不移动另一半。然后我们将比较 p _ 处理和 p _ 控制,而不是比较移动前后的比例。
于是我们设立了无效( H⁰ )和替代( H )假设:
H⁰:购买苹果的人的比例不会增加(p ≤p⁰).
h:购买苹果的人的比例将会增加(p > p⁰).
现在这个测试可能有几个结果——要么这个动作会产生影响,要么不会,我们可以分别得出结论:它会产生影响。这四种可能性总结如下:
测试的 4 种可能结果
在我们运行测试之前,我们需要决定一些事情:
- 我们对假阳性的接受程度如何?
- 如果造成了某种影响,我们有多大的信心能抓住它?
- 我们希望能够探测到的最小效应是什么?
第一个项目符号定义了我们可接受的 I 类错误率(见上表),或𝛼.第二个关系到我们的ⅱ型错误率,或者说𝜷.具体来说,第二点是描述测试的功率,由 1-𝜷.给出
幂和第二类错误以这种方式相关涉及到一个基本点,上表中的概率跨行求和为 1—也就是说,真相(实际发生的事情)没有与之相关的概率,要么治疗引起了变化,要么没有。概率与我们将推断发生的事情相关,所以如果增加发生,我们可能会推断它没有发生(用概率𝜷)或推断它发生了(用概率 1-𝜷).)
所以,言归正传。我们(提前)决定一个可接受的 I 型错误率,通常选择为𝛼=0.05,即“如果我们的治疗没有效果,5%的时间我们会说无论如何都有效果”,我们决定一个所需的功效,通常选择为𝜷=0.8,即“如果有是来自我们的治疗的效果,我们将能够在 80%的时间里将其与随机波动分开”。所以我们有我们的需求,我们如何调整测试设计来适应这些需求呢?
样本量
想象一下 H⁰确实是对的:移动苹果支架没有任何效果。嗯,在任何给定的一天,或多或少会有一些人购买苹果,所以即使真实的比例是 10%,在任何给定的一天对该比例进行抽样将会在 10%的*均值附*产生一个分布。这个样本越大,我们就越有可能接* 10%的关口。我们将想要理解比例上的差异∈p = p-p⁰,所以如果 H⁰ 为真,这个分布将集中于零。*
如果 H ₀为真,5%的时间(由 𝛼定义)我们将测量 p > p_crit 的值
*请记住,我们在该测试中的关键测量是∆p̂的单个值,即治疗组和对照组的样本比例的差异。我们需要取这个值,决定 H⁰还是 h 是对的。我们这样做是因为“如果 p̂大于某个临界值,我们将拒绝 H⁰而支持 h”。该临界值正是由𝛼定义的概率,并在图中用灰色虚线表示。
现在想象 H 是真的:有有把苹果架移*门的积极效果,这个效果是+2 p.p .从 10%→12%增加。同样,在任何给定的一天对该比例进行抽样将会产生一个 12%左右的*均值分布。同样,样本越大,我们越有可能接* 12%的均值。这个∈p分布将以 2%为中心。
替代分布以 p=2%为中心,但是与零分布的重叠很大,导致高𝜷和低功率。
上面的逻辑暗示了我们增加实验效果的第一种方法。通过增加样本大小,我们减少了每个分布的方差,这增加了分布的比例——如果替代假设为真,我们将对该分布进行采样——这高于假阳性截止值*(显示为灰色虚线)。请注意,在下图中,无论是均值还是 I 型错误率都没有变化,唯一的变化是样本量的增加,这降低了可能的采样分布的方差,从而提高了功效。
之前让我困惑的一点是,在任何给定的测试中,我们实际上只是从这些分布中的一个进行抽样。要么 H⁰ 为真,我们从蓝色分布中采样,要么 H 为真,我们从橙色分布中采样,但不是两者都是。这些数字是为了确定根据您的实验设计,您能说出您从哪个分布中取样的可能性——两个分布的重叠越多,就越难说出您从给定的测量的 ∆p̂.的哪个分布中取样
仅改变实验的样本大小就可以调整分布的方差
随着样本量的增加,我们达到 80%的幂,同时保持第一类错误率为 0.05。这定义了我们每个处理和对照的样本量。回到我们的商店示例,如果每天有 100 人访问治疗组的商店,100 人访问对照组的商店,我们应该计划等待大约一个月,然后分析我们的实验(允许每组至少有 3000 个样本)。
最小可检测效应
另一种增强测试能力的方法是接受我们只能检测治疗的更大效果。在我们的视觉示例中,这意味着∈p增加(向右移动橙色分布)。
增加最小可检测效应
如果我们愿意相信我们的治疗将产生+3.5 p.p .的效果,并且我们对小于这个值的效果不感兴趣,我们可以接受更小的样本量,并且仍然获得必要的功效。
假阳性率
我们可以提高能力的最后一个方法是接受更高的 I 型错误率。在不改变任何一个分布的情况下,我们可以将关键的 p- 值下移,这实质上增加了我们得出有影响的结论的机会,但降低了我们对结论的信心。
增加可接受的假阳性(I 型错误)率, 𝛼
我们是否愿意接受更高的 I 型错误率是一个环境问题。这取决于与犯某些类型的错误相关的成本是否不*衡。在我们的例子中,一个假阳性会导致我们不必要地移动苹果,也许没什么大不了的。另一方面,医学药物试验中的假阳性可能导致无效药物在医院使用。另一方面,假阴性会导致一种有效的药物被排除,不能用于未来的治疗。很明显,第一类和第二类错误的程度是非常主观的。
最后一点,这三个因素不需要单独调整。例如,我们可以要求𝛼=0.05 和𝜷=0.8,并求解∈p和样本大小。这将创建一个参数曲线,我们可以从中选择我们的设计。
求解𝛼=0.05 和𝜷=0.8 允许样本大小和∈p的多个解
注意样本大小和最小可检测效应之间的非线性关系。随着我们所寻找的效应变得越来越小,创建样本的成本会成倍增加。
这就是我们,我希望这些数字中的几个有助于想象,随着实验参数的不同选择,功率如何变化。他们确实帮助我弄清楚发生了什么。如果你过去没有在测试前运行过功耗分析,下次你设置实验时尝试一下。
[## Paul stub ley-LinkedIn 自由职业数据和决策科学家
我是一名经验丰富的数据科学家,在个人工作和管理团队方面都有卓有成效的工作经历。
www.linkedin.com](https://www.linkedin.com/in/paul-stubley)
如果你想联系我,你可以在 LinkedIn 上联系我。如果你想看代码和分析(包括每个发行版背后的派生),可以在GitHub上查看。动画是用 Matplotlib 和 imagemagick 制作的。
这一讨论受到了 数据科学纳米学位实验设计部分的启发,并由此引出。
理解 ROC 曲线和 AUC
这些二进制分类性能指标是相辅相成的——让我们来探索一下。
马德琳·佩雷在 Unsplash 上的照片
ROC 曲线
接收机工作特性(ROC)曲线经常用于评估二元分类算法的性能。它提供了分类器性能的图形表示,而不是像大多数其他指标那样提供单一值。
首先,让我们确定在二元分类中,一个测试预测有四种可能的结果:真阳性、假阳性、真阴性和假阴性。
二元分类问题的混淆矩阵结构
ROC 曲线是通过计算和绘制单个分类器在各种阈值下的真阳性率与假阳性率而产生的。例如,在逻辑回归中,阈值将是属于正类的观察的预测概率。通常在逻辑回归中,如果一个观察值以 0.5 的概率被预测为正,则它被标记为正。然而,我们实际上可以选择 0 到 1 之间的任何阈值(0.1、0.3、0.6、0.99 等)。)ROC 曲线帮助我们直观地了解这些选择如何影响分类器的性能。
真阳性率,或灵敏度,可表示为:
其中 TP 是真阳性的数量, FN 是假阴性的数量。真实阳性率是对一个实际阳性实例被归类为阳性的概率的度量。
假阳性率,或者 1 — 特异性,可以写成:
其中 FP 是假阳性的数量, TN 是真阴性的数量。假阳性率本质上是衡量“假警报”发生的频率——或者说,实际的阴性实例被归类为阳性的频率。
图 1 展示了一些理论分类器如何绘制 ROC 曲线。灰色虚线表示不比随机猜测更好的分类器—这将绘制为对角线。紫色线代表一个完美的分类器——真阳性率为 100%,假阳性率为 0%。几乎所有真实世界的例子都会落在这两条线之间——不完美,但比随机猜测提供了更多的预测能力。通常,我们寻找的是一种保持高真阳性率同时又具有低假阳性率的分类器,这种理想的分类器将“拥抱”图 1 的左上角,很像紫色线。
图 1 —一些理论 ROC 曲线
罗马纪元
虽然可视化分类器的 ROC 曲线很有用,但在许多情况下,我们可以将这些信息归结为一个单一的指标—AUC。
AUC 代表(ROC)曲线下的面积。通常,AUC 分数越高,分类器对给定任务的表现越好。
图 2 显示,对于没有预测能力(即随机猜测)的分类器,AUC = 0.5,对于完美的分类器,AUC = 1.0。大多数分类器将落在 0.5 和 1.0 之间,少数例外是分类器表现比随机猜测差(AUC<0.5)。
图 2-具有 AUC 分数的理论 ROC 曲线
为什么要用 ROC 曲线?
ROC 曲线呈现的一个优点是,它们帮助我们找到适合我们具体问题的分类阈值。
例如,如果我们正在评估一个垃圾邮件分类器,我们希望假阳性率非常非常低。我们不希望有人仅仅因为我们的算法过于激进,就因为垃圾邮件过滤器而丢失了一封重要的电子邮件。我们甚至可能允许相当数量的实际垃圾邮件通过过滤器,以确保没有重要的邮件丢失。
另一方面,如果我们的分类器预测某人是否患有绝症,我们可能会接受更多的假阳性(错误诊断疾病),只是为了确保我们不会错过任何真正的阳性(实际患病的人)。
此外,ROC 曲线和 AUC 分数还允许我们比较不同分类器对同一问题的性能。
例如:心脏病预测
为了演示 ROC 曲线在实践中是如何构建的,我将使用 Python 中的心脏病 UCI 数据集。该数据集有 14 个属性,303 个观察值,通常用于根据其他 13 个属性(包括年龄、性别、胆固醇水*和其他测量值)预测患者是否患有心脏病。
导入&装载数据
列车测试分割
对于这个分析,我将使用标准的 75% — 25%训练测试分割。
逻辑回归分类器
在编写计算假阳性率和真阳性率的函数之前,我将对训练数据拟合一个普通的逻辑回归分类器,并对测试集进行预测。
计算真阳性率和假阳性率
现在我有了测试预测,我可以写一个函数来计算真阳性率和假阳性率。这是关键的一步,因为这是制作 ROC 曲线所需的两个变量。
(0.6923076923076923, 0.1891891891891892)
测试表明,该功能似乎正在工作——69%的真阳性率和 19%的假阳性率是完全合理的结果。
探索不同的阈值
为了获得 ROC 曲线,我需要一对以上的真阳性/假阳性率。我需要改变逻辑回归分类器用来预测患者是否患有心脏病(目标=1)的阈值概率。请记住,虽然逻辑回归用于分配类别标签,但是实际上所做的是确定观察值属于特定类别的概率。在典型的二进制分类问题中,一个观察值必须有 0.5 的概率被分配到正类。然而,在这种情况下,我将从 0 到 1 递增地改变阈值概率值。这将导致真阳性率和假阳性率的范围,允许我建立 ROC 曲线。
在下面的代码块中,我在一系列阈值概率值范围内获得了这些真阳性率和假阳性率。为了比较,我使用逻辑回归与(1)没有正则化和(2) L2 正则化。
绘制 ROC 曲线
逻辑回归分类器的两个版本似乎都做得很好,但 L2 正则化版本似乎表现得稍好。
计算 AUC 分数
sklearn 有一个 auc() 函数,我将在这里使用它来计算两个版本的分类器的 auc 分数。 auc() 接受我们之前计算的真阳性率和假阳性率,并返回 auc 分数。
Logistic Regression (No reg.) AUC 0.902979902979903
Logistic Regression (L2 reg.) AUC 0.9116424116424116
正如所料,两个分类器都有相似的 AUC 分数,L2 正则化版本表现稍好。
ROC 曲线和 AUC 的简单方法
既然我们已经从头开始有趣地绘制了这些 ROC 曲线,您将会松一口气,因为知道有一个非常非常简单的方法。 sklearn 的 plot_roc_curve() 函数可以仅使用拟合的分类器和测试数据作为输入,高效地绘制 roc 曲线。这些图也方便地包括 AUC 分数。
****
关闭
如果你已经做到这一步,感谢你的阅读!我发现在 Python 中低效地创建自己的 ROC 曲线是一项有价值的练习,我希望您能从本文中有所收获。
ROC 和 AUC 的一些有用参考:
** [## 用 ROC 曲线评估和比较分类器性能-机器学习掌握
最常报道的分类器性能的度量是准确度:正确分类的百分比…
machinelearningmastery.com](https://machinelearningmastery.com/assessing-comparing-classifier-performance-roc-curves-2/) [## 统计学习导论
加雷思·詹姆斯、丹妮拉·威滕、特雷弗·哈斯蒂和罗伯特·蒂布拉尼家中的应用程序
faculty.marshall.usc.edu](http://faculty.marshall.usc.edu/gareth-james/ISL/)**
通过三个可视化步骤理解 ROC 曲线
在数据科学中,我花了更长时间才理解的一个概念是受试者工作特征(ROC)曲线。这是您的分类模型工作情况的直观表示。
在这篇博客中,我想解释 ROC 曲线是如何通过三个可视化步骤从零开始构建的。
步骤 1:获取分类模型预测
当我们训练一个分类模型的时候,我们得到了得到一个结果的概率。在这种情况下,我们的例子将是偿还贷款的可能性。
图 1:分类模型示例
概率通常在 0 到 1 之间。价值越高,这个人偿还贷款的可能性就越大。
下一步是找到一个阈值,将概率分类为“将偿还或“不会偿还”。
在图 1 的例子中,我们选择了 0.35 处的阈值:
- 所有达到或超过此阈值的预测都被归类为“将偿还”
- 所有低于此阈值的预测都被归类为“不会偿还”
然后我们看看这些预测中哪些是正确分类的,哪些是错误分类的。有了这些信息,我们就可以建立一个混淆矩阵。
所有实际阳性,那些确实偿还的,是蓝点。
- 如果他们被归类为【将偿还】,我们就有了真正的积极(TP)
- 如果他们被归类为“不会偿还”,我们就有一个假阴性(FN)
所有实际负面,那些没还的,就是红点。
- 如果他们被归类为【不会偿还】,我们就真的否定了(TN)
- 如果它们被归类为【将偿还】,我们就有一个误报(FP)
在图 2 中,我们可以看到一个总结:
图 2:混淆矩阵
第二步:计算真阳性率和假阳性率
到目前为止,我们已经对所有的预测进行了分类,我们知道这些分类是否正确。这样,我们将计算接下来的两个指标:
图 TPR 和 FPR 的计算
- 真实阳性率(TPR): 在所有过去“确实还款”的人中,我们正确分类的百分比是多少
- 假阳性率(FPR): 在所有过去“没有还款”的人中,我们漏分了百分之几
我们可以在图 3 中看到这些计算的公式。
下面,在图 4 的第二个图中,我们可以看到阈值为 0.35 的原始示例。此时,我们
- 90%的阳性分类正确,那些“已偿还” (TPR)
- 漏分类 40%的所有否定,那些“没还钱” (FPR)
我们可以注意到 TPR 和 FPR 的结果随着阈值变大而降低。如果我们看第一个,阈值是 0:
- 所有阳性被正确分类,因此 TPR = 100%
- 所有的阴性都被错误分类,因此 FPR = 100%
在最后一个图表示例中,阈值为 1:
- 所有阳性被错误分类,因此 TPR = 0%
- 所有阴性都被正确分类,因此 FPR = 0%
图 4:不同临界值的 FPR 和 TPR 结果
总的来说,我们可以看到这是一个权衡。随着我们提高阈值,我们会更好地分类负面信息,但这是以错误分类更多正面信息为代价的
第三步:为每个截止点绘制 TPR 和 FPR
为了绘制 ROC 曲线,我们需要计算许多不同阈值的 TPR 和 FPR(这一步在所有相关库中都被收录为 *scikit-learn*
)。
对于每个阈值,我们在 x 轴上绘制 FPR 值,在 y 轴上绘制 TPR 值。然后我们用一条线把这些点连接起来。就是这样!
在下面的图 5 中,我们可以看到 ROC 曲线的每个点是如何代表一个分类在给定临界值的 FPR 和 TRP 的。
请注意,阈值为 1 时,第一个点位于(0,0),阈值为 0 时,最后一个点位于(1,1)。
图 5: ROC 曲线示例
线下覆盖的面积称为“曲线下面积(AUC)”。这用于评估分类模型的性能。AUC 越高,模型就越能区分不同的类别。
这意味着在理想情况下,我们希望看到我们的线覆盖图形的大部分左上角,以获得更高的 AUC。
谢谢你看完🎉
希望你觉得这个博客有用!请在下面留下您的评论和反馈👇
了解农村未参保人群
使用 Sklearn 的多元线性回归—编码示例
根据最*的人口普查数据,8.8%的美国人没有健康保险。也就是说,有 2800 万人可能会接受不符合标准的医疗服务,或者因为没有身份而被拒之门外。因为美国没有国有化的医疗保健,公民依靠健康保险来支付医疗费用。私营公司和公共医疗保健系统(如医疗保险和医疗补助)提供了各种各样的保险类型。在 2013 年患者保护与*价医疗法案(ACA)授权之前,未参保率为 18%。
为什么这是一个问题?
对于病人来说,没有保险意味着你可能会因为无力支付而被拒绝,或者你可能会接受不符合标准的医疗服务(更少的检查,医生更少的关注等)。如果原因严重,您可能不得不累积坏账(故意使用您无法偿还的信贷)以确保您可以获得护理。对于医生来说,你必须权衡因缺乏资金而拒绝患者的道德影响和你可能必须采取的减轻他们负担的措施,例如,承担费用、夸大症状或把他们送到公共资助的替代机构。
收集和准备数据
我为这个项目连接了四个数据集,以消除我的一些假设特征。我从 HealthData.gov T4 的一套社区健康指标开始。然后,我整合了来自人口普查局的一些数据集,以获得家庭收入中值( 1 )、人口统计数据( 2 )以及农村与城市的细分数据( 3 )。
我根据相应州的*均值估算了每个县的社区健康指标的缺失值。我不得不删除一些行,因为四个集合中的一些集合将美国的领土视为等同于县,而一些偏远的县没有收集数据。
最后,进行了统计检验,以阐明农村和城市县之间的一些差异。未保险率、中等家庭收入、社区卫生中心的存在以及该县医疗服务不足的情况在农村和城市县之间都有显著差异。
城市县的未保险率明显低于农村县(9%比 21%,p 值=6.39e-32)。
开始建模
这是在我们清理和处理数据之后。我的完整笔记本可以在这里找到。
所以我们的 R 值大约是 0.4。这似乎相当低,对不对?对于像这样的社会科学项目来说,这还不算太糟糕!坦白说,模拟人类决策真的很难!虽然我们不能说我们有所有的答案,这个模型仍然允许我们确定未保险率的一些重要因素。
检查系数
让我们看看我们的系数。因为我们缩放了连续变量,我们可以相互解释这些系数。语法是“从‘老年人 _ 医疗保险’的*均值每增加一个单位的标准差,我们预计未投保率将增加 6.5%”。
我们可以看到,享受医疗保险的老年人、医生与 10 万人口的比率以及吸烟率是最大的积极因素(也就是说,它们增加了无保险率)。
对于负面因素,城市人口百分比、农村总人口和家庭收入中位数是有意义的。这告诉我们,随着各县人口越来越多和/或越来越城市化,他们的无保险率会下降。此外,较高的中值家庭收入与较低的未保险率相关,城市县的收入明显高于农村县(56k 比 44k,p 值=1.07e-80)。
我们看到,城市县的家庭收入中值明显高于农村县(56k 比 44k,p 值=1.07e-80)
结论
在运行我们的线性回归后,我们能够确定社区健康指标和人口统计因素的混合对未保险率有显著影响。高比例的吸烟者和享受医疗保险的老年人导致了未保险率的增加。我认为,享受医疗保险的老年人口是该县总老年人口的一个替代指标,因为老年人和儿童更有可能没有保险,这就推高了比率。
其他因素降低了这一比率。主要是,城市人口百分比每增加一个标准差,无保险率下降 5.62%。标准偏差每增加一个百分点,家庭收入中值的系数下降 2.44%,这个系数较小,但很重要。最后,随着县人口的增加,无论是城市还是农村,无保险率下降。
未来方向
我想纳入更具体的数据,例如,虽然家庭收入在未保险率中发挥了作用,但我们的数据仅限于县级,城市和农村人口混合的县可能会受到城市人口*均收入增加的影响。
还有一些可能影响这一比率的因素很难收集数据。我建议建立一个调查和汇总健康记录的系统,它可以提供为什么病人没有保险的原因。例如,一个有慈善系统的医疗机构列表(未保险的病人可以获得一个普通“基金”来支付手术费用)来检查这是如何影响未保险率的。
来源
[1]s·韦纳,“我买不起!:未投保和投保不足者的护理困境(2001),普通内科杂志 16:412–418。
[2] J .科恩,“统计力量分析”(1992),心理科学的当前方向 3:98–101。
数据集可以在文章中找到。我的回购是这里。
了解 Matplotlib 的结构
那些情节是怎么创造出来的?
Matplotlib 是一个广泛使用的 python 数据可视化库。它提供了许多不同种类的 2D 和 3D 绘图,对于数据分析和机器学习任务非常有用。
Matplotlib 提供了灵活性。创建情节有多种方式。为了掌握 matplotlib,有必要对其结构有一个透彻的了解。
Matplotlib 由三个主要层组成。
后端层
如果你不是开发人员,你不太可能使用或处理后端层。它有三个抽象类:
- FigureCanvas:定义绘制图形的区域。
- 渲染器:它是在图形画布上绘图的工具。
- 事件:处理用户输入,如键盘敲击和鼠标点击。
这类似于我们在纸上画画。假设你想画一幅画。你得到一张白纸(图画布)和一个画笔(渲染器)。你问你朋友画什么(事件)。
艺术家图层
艺术家层由一个对象组成,该对象是艺术家。
我们在 matplotlib 生成的绘图上看到的一切都是艺术家实例。
标题、线条、文本、轴标签都是艺术家的实例。图是主要的艺术家对象,它将所有的东西结合在一起。让我们创建一个图形:
#importing matplotlib
import matplotlib.pyplot as plt
%matplotlib inline#creating a figure artist
fig = plt.figure(figsize=(10,6))
<Figure size 720x432 with 0 Axes>
我们创建了一个图形,但它没有任何可显示的内容。可以把它想象成一个容器,把一个情节的组成部分放在一起。
有两种类型的艺术家对象:
- 复合:图形,轴
- 图元:直线、圆、文本
我们需要添加一个或多个轴到图中,以便能够创建一个实际的绘图。让我们在行动中看到它。
fig = plt.figure(figsize=(10,6))
ax = fig.add_subplot(111)
我们在之前创建的图形中添加了一个轴。我们现在有一个空的地块:
请注意,我们已经使用脚本层来生成这些图。我只是想展示艺术家物品的概念。
注意:我们使用了面向对象的风格,在这里我们明确定义了艺术家对象的实例。另一个选项叫做 pyplot style ,我们让 pyplot 创建图形和轴对象。
Pyploy 风格
在这篇文章中,我们将坚持面向对象的风格。
脚本层
这是我们最有可能打交道的一层。脚本层是 matplotlib.pyplot 接口。因此,当我们在下面的命令后使用“plt”创建图时,脚本层就是我们所玩的。
import matplotlib.pyplot as plt
脚本层自动化了将所有东西放在一起的过程。因此,它比艺术家层更容易使用。
让我们创造一些实际上看起来像情节的情节。
#create array to be plotted
import numpy as np
ser = np.random.randn(50)#create the plot
fig = plt.figure(figsize=(10,6))
ax = fig.add_subplot(111)
ax.plot(ser)
我们创建了一个图形和轴。然后调用轴对象上的绘图函数,并传递要绘制的数组。渲染图为:
此图形有一个轴,但一个图形可以包含多个轴。让我们创建一个有多个轴的:
#arrays to be plotted
ser1 = np.random.randn(50)
ser2 = ser1**2#create figure and axes
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2,
sharey=True,figsize=(10,6))#plot the arrays on axes
ax1.plot(ser1)
ax2.plot(ser2)
我们使用子情节函数,用一行代码创建了图形和轴(ax1 和 ax2)。
Matplotlib 提供了高度灵活的工作环境。我们可以完全控制自己创造的情节。回想一下,我们在 matplotlib 图形上看到的所有东西都是 Artist 的实例。让我们在上面的情节中加入不同类型的艺术家。
ser1 = np.random.randn(50)
ser2 = ser1**2fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2,
sharey=True, figsize=(12,6))ax1.plot(ser1)
ax1.grid()
ax1.set_title('First Series', fontsize=15)
ax1.set_ylabel('Values', fontsize=12)
ax1.set_xlabel('Timesteps', fontsize=12)ax2.plot(ser2)
ax2.set_title('Second Series', fontsize=15)
ax2.text(25,-1, 'First Series Squared', fontsize=12)
ax2.set_xlabel('Timesteps', fontsize=12)
除了标题和轴标签,我们还在第一个轴上添加了网格线,在第二个轴上添加了一个文本艺术家。我们制作的情节:
我们可以用 matplotlib 做更多的事情。和其他工具一样,它需要大量的练习才能掌握。然而,具体和基本的第一步是学习基础知识和结构。之后可以通过练习创造更高级的剧情。
感谢您的阅读。如果您有任何反馈,请告诉我。
参考文献
了解虎王🐯和他通过 Python 发的推文
🐯使用 Python 抓取 Twitter + NLP
虎王在推特上说什么?
照片由 Engin Akyurt 拍摄
乔·异国情调是目前美国最大的名字。我们已经看到网飞是如何描绘他的,但是虎王是如何在推特上描绘他自己的呢?我开始通过抓取他的推特并学习他使用的词汇来找出乔·异国他乡是谁。
网飞的虎王在隔离期间吸引了美国,不管是好是坏。如果你不知道这部电视剧,这部纪录片重点介绍了美国的地下大型猫科动物游戏,魅力非凡的乔·异国情调(又名虎王)和他的死对头卡罗尔·巴斯金是主角。
尽管是#StayAtHome 期间的头号热门话题,乔仍然错过了一个名人的真实故事;推特上的蓝色勾号。
在国王的脑袋里
那么,虎王到底在推特上写些什么呢?毕竟,我们知道一个人的 Twitter 是一个很好的窥视他们所关心的东西的窗口。通过分析虎王的微博,我开始更全面地了解他。
我首先安装了 twitterscraper 。当然,Twitter 有自己的 API, Tweepy ,但它有其局限性。也就是说,你不能再抓取超过七天的推文。那对我来说不可行,因此使用了 twitterscraper。
获取虎王的推文
twitterscraper 的作者 Ahmet Taspinar 实际上做了一项令人难以置信的工作,使推文变得容易。我所要做的就是提供我想要的账户。
# Scrape Tweets
handle = ‘joe_exotic’
out = twitterscraper.query_tweets_from_user(handle)
推特开始涌入。
然后,我使用一些简单的 pandas 命令将数据提取到一个可管理的数据帧中。
# Create Empty storage
df = pd.DataFrame()
Date = []
Text = []
Likes = []
Retweets = []
# Read data into respective columns
for tweet in out:
Date.append(tweet.timestamp)
Text.append(tweet.text)
Likes.append(tweet.likes)
Retweets.append(tweet.retweets)
一旦我有了推文,我就可以通过自然语言处理来准备它们进行分析。
准备虎王的推文
为了执行一些基本的自然语言处理,我导入了 NLTK ,并对他的 tweets 进行了标记。
# Load in Tweets
df = pd.read_csv(‘JoeExoticTweets.csv’)# Tokenize words
texts = ‘ ‘.join(df[‘Text’]) # Turn into string
FindWordsText = nltk.word_tokenize(texts) # Separate text into words
text = nltk.Text(FindWordsText) # Spit out processed words
words = text.tokens
然后,我做了一些轻微的清理,消除了任何链接,因为那些不是我感兴趣的真实单词。尽管看看他的链接可能会很有趣?任何人的分拆项目?
# Clean up links as words
words_clean = [i for i in words if ‘//’ not in i]
words_clean = [i for i in words_clean if ‘.com’ not in i]
other_messy_words = [‘https’, ‘ https’, ‘https ‘, ‘.’,’:’,’\’’,’&’,’,’]
words_clean = [i for i in words_clean if i not in other_messy_words]
分析虎王的微博
先来看看虎王用的最多的是哪些词。我们将使用 NLTK 的 FreqDist 函数来做到这一点。
# Analyze Frequency
frequency_dist = nltk.FreqDist(words)
word_dict = sorted((value, key) for (key,value) in frequency_dist.items())
x, y = zip(*word_dict) # unpack a list of pairs into two tuples
现在我们有了这些组织好的列表,让我们做一个线图来显示前 40 个单词被使用的次数。
num_to_show = 40
plt.figure(figsize=[6,14])
plt.plot(x[-num_to_show:], y[-num_to_show:],lw=6,c='r')
plt.yticks(fontname = "Arial Black", fontsize=20)
plt.xlabel('Num Times Used',fontsize=14, fontname = "Arial Black")
plt.xticks(fontsize=14, fontname = "Arial Black")
plt.savefig("joes_most_common_terms.png", format="png")
plt.show()
乔·异国最常用的词
我们可以看到,小的、常见的词是他用得最多的;这是有道理的。我仍然认为这给了我们很多关于虎王的深刻见解。我看到他用了很多“…”和很多标签。他大量使用“我”,这可能意味着一种自我中心的叙述。问号的使用可能暗示,他在问公众问题。他钟爱的俄克拉荷马州经常被使用,还有一个标签#fixthisshit 来表明他对这个国家的感情。
每个人都使用像“the”、“to”和“it”这样的词,所以让我们添加一些停用词来消除它们,同时去掉标点符号和半结构缩写(n t,etc)。
# Remove stop words
stop_words = set(nltk.corpus.stopwords.words('english'))
filtered_by_stopwords = [w for w in FindWordsText if not w in stop_words]
extra_stopwords = ["…",'#','@','!','\'s','..',"’",'?','n\'t','.','I','...']
filtered_by_stopwords = [w for w in filtered_by_stopwords if not w in extra_stopwords]
再重做一次图表,看看更多独特的乔词。
过滤停用字词后 Joe 最常用的字词
这给虎王的内心增加了更多的颗粒度。他对什么感兴趣?这些推文不言自明:“人民”、“投票”、“州长”和“钱”。
字云!
我的下一步是做一个单词云。这些都很常见,但是很有趣,至少显示了虎王最常用的词。
Python 有一些很棒的工具可以用来构建单词云。我非常喜欢 datacamp 的教程,它利用了 WordCloud 库来生成它们。
它能为你做大部分艰苦的工作…
# Simple word cloud# make simple word cloud with white background
wordcloud = WordCloud(background_color=”white”).generate(texts_clean)# show word cloud
plt.imshow(wordcloud, interpolation=’bilinear’)
plt.axis(“off”)
plt.show()
plt.figure(figsize=[12,12])
plt.savefig(“joes_tweets_simple.png”, format=”png”)
简单词云
嘭!我们经常看到类似的术语被使用,但是是以一种更可爱、更艺术的方式。
画一幅字肖像
如果乔用他最常用的词给他画一幅画,他会是什么样子?数据营教程向您展示如何将单词云塑造成形状和图像。所以我们来试试吧!我做了一个快速谷歌搜索乔异国情调,发现了这张可怕的照片。
照片来自圣塔罗斯县监狱
然后,我将图像加载到 python 中,并将图像转换成一个 Numpy 数组。
mask = np.array(Image.open(“JoeExoticPic.jpg”))
然后使用 WordCloud 库中的 ImageColorGenerator 函数,我根据照片中的颜色创建了这个单词云。
乔异域词云写真
这似乎反映了图片的颜色,看起来比简单的白色单词云更有趣,但仍然不确定这是我要找的最终肖像。
WordArt.com是另一个很酷的资源,它可以让你自定义形状或图像文字云,没有代码行。它们还允许您调整 hyper 参数,使完成的项目更具美感。我导入了所有虎王文字的栏目,上传了同样的图片,做了这个可视化:
虎王字画像
好多了!实际的视觉效果也是交互式的,所以滚动一些单词来看看它的效果。
虎王一起用什么词?
单个单词很强大,但也许我们会更深入地了解常用的成对单词。
text.collocation_list()
虎王最配对的词
抵制沃尔玛!教育系统!枪支管制!以下是虎王本人经常讨论的一些话题。我们可以深入研究其中一些。6 月 26 日发生了什么?他说的最后期限是什么?什么是一整天?
虎王关于 X 的事情是什么?
当 Joe introductive 在谈论“T4”这个词时,他在说什么?
text.concordance("animal")
“枪”怎么样?
text.concordance("gun")
他最好的朋友,我们深爱的“卡罗尔酷猫巴斯金”呢?
text.concordance("Carole")
…没有匹配项…
什么!他从没提起过卡罗尔?当然是巴斯金。
text.concordance("Baskin")
没有。没什么。
奇怪。他在微博上提到过她吗?他删除它们是出于法律原因吗?卡罗尔·巴斯金的推特在哪里?!!
虎王是…
乔·特兰奇似乎是一个真正关心州政府、选举和州长的人。他关心“解决这个狗屎问题”,关心枪支管制、教育系统和抵制沃尔玛。他是这样一个人,他会把截止日期、日期和消息告知他的基础。也许,只是也许,他并不像网飞描绘的那样疯狂。
也许他应该得到自由?你可以在评论中辩论。
想摆弄数据吗?
你可以在我的 GitHub Repo:https://github.com/AveryData/Tiger-King-Tweets找到这个项目的所有代码
仍然使用准确性作为分类标准吗?
了解多类分类问题的前 N 名准确性指标
里卡多·阿尔塞在 Unsplash 上的照片
准确性是分类模型最常见的评估标准,因为它简单明了。但是,当您手头有一个多类分类问题时,比方说,有 15 个不同的目标类,查看模型的标准准确性可能会产生误导。这就是“前 N 名”准确度可能有所用处的地方,在本文中,我将带您了解前 N 名准确度的基本直觉和 python 实现。
在讨论前 N 名精度之前,先回顾一下标准精度指标:
什么是准确性?
准确性是模型正确预测的记录总数的百分比。准确度=(正确预测的记录数/记录总数)或准确度= (TP+TN) / (TP+TN+FP+FN)
为什么准确度不是多类问题的完美衡量标准?
当您有许多不同的类别时,分类模型可能无法准确预测正确的类别。在文本分类这样的 NLP 情况下,这尤其成为一个问题,在这种情况下,您有大量的特征,并且数据没有充分地跨类聚类。因此,查看您的标准准确性指标有时可能会产生误导。如前所述,测量前 N 个精度可能有助于解决这个问题。
什么是 top N 精度?
前 N 个精度并不是任何不同的度量,而是真实类的标准精度等于分类模型预测的 N 个最有可能的类中的任何一个。
最高 1 级准确度是指真实类别与模型预测的最可能类别相匹配的准确度,与我们的标准准确度相同。
前 2 名准确度是指真实类别与模型预测的 2 个最可能类别中的任何一个匹配的准确度。
前 3 名准确度是指真实类别与模型预测的 3 个最可能类别中的任何一个匹配的准确度。
同样,我们可以衡量前 4 名、前 5 名、前 6 名等等。
让我举个例子让你更好的理解。考虑必须将记录分类到相应的动物中(狗、猫、狮子、老虎和大象)。该表显示了 6 个记录的真实类别和预测类别。
由此,我们将得到 50%的准确率,这并不令人满意。现在让我们找出这个问题的前 2 个精度。该表显示了同一组记录的真实类别和 2 个最可能的类别。
这样,我们得到了 83%的准确率,与之前相比有了显著的提高。这是找到前 N 个精度背后的基本直觉。
我希望你已经理解了测量前 N 个精度的重要性。即使分类模型无法预测准确的类别,查看前 2 名或前 3 名的准确性在许多情况下可能是有用的,尤其是在有许多不同类别(如 15 或 20)的情况下。
现在,我将向您展示一些使用 python 查找前 N 个精度的代码。不幸是,sci-kit learn 没有任何内置的功能。所以我在这里定义了一个函数,你可以直接复制,用在你的问题中。
导入所需的库并预处理数据后,运行提供的函数。
def top_n_accuracy(X,y,n,classifier):
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
vectorizer = TfidfVectorizer(min_df=2)
X_train_sparse = vectorizer.fit_transform(X_train)
feature_names = vectorizer.get_feature_names()
test = vectorizer.transform(X_test)
clf = classifier
clf.fit(X_train_sparse,y_train)
predictions = clf.predict(test)
probs = clf.predict_proba(test)
topn = np.argsort(probs, axis = 1)[:,-n:]
y_true = np.array(y_test)
return np.mean(np.array([1 if y_true[k] in topn[k] else 0 for k in range(len(topn))]))
您可以通过提供以下参数来调用该函数:
- X =特征变量
- y =目标类别
- n =要测量的前 N 名(例如,前 2 名的 n=2)
- 分类器=您想要使用的分类模型(例如 LogisticRegression())
我已经为文本分类定义了这个函数,但是您可以简单地删除具有矢量器实例的行,并将其用于标准分类。
理解变压器,数据科学之路
来源:壁纸访问
通过问很多问题。
如今,变形金刚已经成为任何 NLP 任务的事实标准。不仅如此,它们现在还被用于计算机视觉和创作音乐。我相信大家都听说过 GPT3 变压器及其应用。 但抛开所有这些事情不谈,他们仍然一如既往地难以理解。
我花了很多时间阅读首次介绍变形金刚的谷歌研究论文和许多博客文章,才真正理解了变形金刚的工作原理。
所以,我想把整个想法用尽可能简单的话写下来,加上一些非常基本的数学和一些双关语,因为我是一个在学习时享受乐趣的支持者。我会尽量少用术语和术语,但这是一个我只能做这么多的话题。我的目标是让读者在这篇文章结束时理解《变形金刚》最精彩的细节。
此外,这是我正式发表的最长的一篇文章,无论是从写这篇文章所花的时间还是文章的长度来看。因此,我建议你去喝杯咖啡。 ☕️
所以,我来了——这篇文章将是一篇高度对话性的文章,它是关于“ 解码变压器”的。
问:那么,我为什么还要理解《变形金刚》?
在过去,LSTM 和 GRU 架构(正如我在 NLP 上的帖子中所解释的)以及注意力机制曾经是语言建模问题(简单地说,预测下一个单词)和翻译系统的最先进方法。但是,这些架构的主要问题是它们本质上是循环的,并且运行时间随着序列长度的增加而增加。也就是说,这些体系结构以 顺序 的方式获取一个句子并处理每个单词,因此随着句子长度的增加,整个运行时间也会增加。
Transformer 是一种模型架构,在论文《注意力就是你所需要的一切》中首次解释,它放弃了这种循环,而是完全依赖于注意力机制来绘制输入和输出之间的全局依赖关系。这使得它很快。
这是从报纸上截取的完整变压器的图片。而且,这确实令人生畏。因此,在这篇文章中,我将通过仔细阅读每一篇文章来揭开它的神秘面纱。所以请继续阅读。
大局
问:听起来很有趣。那么,变压器具体做什么呢?
本质上,转换器可以执行几乎所有的 NLP 任务。根据需要,它可以用于语言建模、翻译或分类,并且通过消除问题的顺序性,它可以快速完成这些工作。因此,机器翻译应用程序中的转换器会将一种语言转换为另一种语言,或者对于分类问题,会使用适当的输出层来提供分类概率。
这一切都将取决于网络的最终输出层,但变压器的基本结构将保持完全相同的任何任务。在这篇文章中,我将继续机器翻译的例子。
所以从一个很高的地方,这是变压器如何寻找一个翻译任务。它接受一个英语句子作为输入,并返回一个德语句子。
用于翻译的变压器(图片作者
积木
问:那太基础了。😎你能详细说明一下吗?
好吧,记住最后,是你自找的。让我们再深入一点,试着理解变压器是由什么组成的。
因此,转换器本质上是由一堆编码器和解码器层组成的。编码器层的作用是使用注意机制将英语句子编码成数字形式,而解码器的目的是使用来自编码器层的编码信息来给出特定英语句子的德语翻译。
在下图中,转换器输入一个英语句子,该句子使用 6 个编码层进行编码。最终编码器层的输出然后进入每个解码器层,将英语翻译成德语。
转换器中的数据流(作者提供的图片)
1.编码器架构
问:没关系,但是,编码器堆栈是如何准确编码一个英语句子的呢?
耐心点,我要开始了。因此,正如我所说的,编码器堆栈包含六个相互叠加的编码器层(如论文中所述,但未来版本的 transformers 会使用更多层)。并且堆栈中的每个编码器本质上具有两个主要层:
- 多头自关注层,以及
- 位置式全连接前馈网络
非常基本的编码器层(图片作者)
他们是满嘴的。对吗?请不要忘记我,我将在接下来的章节中解释这两个问题。现在,只要记住编码器层包含注意力和位置前馈网络。
问:但是,这一层期望它的输入是怎样的呢?
这一层期望它的输入是SxD
(如下图所示)的形状,其中S
是源句子(英语句子)的长度,D
是嵌入的维度,其权重可以用网络来训练。在本帖中,我们将始终默认使用D
作为 512。而 S 将是一批句子的最大长度。所以它通常会随着批次而变化。
编码器—输入和输出形状相同(作者图片)
这一层的输出是什么呢?请记住,编码器层是相互堆叠的。因此,我们希望输出能够与输入具有相同的维数,以便输出可以轻松流入下一个编码器。所以输出也是这个形状,SxD
。
问:关于大小的讨论已经足够了,我知道什么进入什么离开,但是在编码器层实际上发生了什么呢?
好了,让我们一个一个地过注意力层和前馈层:
a)自我关注层
自我关注是如何工作的(作者图片)
上面的数字看起来很吓人,但很容易理解。所以和我呆在一起。
深度学习本质上只是大量的矩阵计算,我们在这一层本质上做的是大量的智能矩阵计算。自我关注层用 3 个权重矩阵初始化——查询(W_q)、键(W_k)和值(W_v)。这些矩阵中的每一个都具有(Dxd
)的大小,其中 d 在文中被取为 64。当我们训练模型时,将训练这些矩阵的权重。
在第一个计算中(图中的 Calc 1),我们通过将输入乘以相应的查询、键和值矩阵来创建矩阵 Q、K 和 V。
到目前为止,它是微不足道的,不应该有任何意义,但它是在第二次计算中变得有趣。让我们试着理解 softmax 函数的输出。首先,我们将 q 和 Kᵀ矩阵相乘,得到一个大小为(SxS
)的矩阵,然后除以标量√d。然后,我们取一个 softmax,使各行之和为 1。
直观上,我们可以把得到的SxS
矩阵想象成每个单词对另一个单词的贡献。例如,它可能看起来像这样:
Softmax(QxKt/sqrt(d)) ( 图片作者)
如你所见,对角线上的条目很大。这是因为这个词本身的贡献就高。这是合理的。但是我们在这里可以看到,“quick”这个词被分为“quick”和“fox”,“brown”这个词也被分为“brown”和“fox”。这直观地帮助我们说,“快”和“棕色”这两个词都指“狐狸”。
一旦我们有了这个包含贡献的 SxS 矩阵,我们就将这个矩阵乘以句子的值矩阵(Sxd ),它返回给我们一个 Sxd(4x64)形状的矩阵。因此,该操作实际上做的是用比如说. 75 x(快速嵌入)和. 2x(fox 嵌入)替换单词“quick”的嵌入向量,因此现在单词“quick”的结果输出本身就嵌入了注意力。
请注意,这一层的输出维度为(Sxd ),在我们完成整个编码器之前,我们需要将它改回 D=512,因为我们需要该编码器的输出作为另一个编码器的输入。
问:但是,你把这一层叫做多头自我关注层。什么是多头?
好吧,我的错,但我要说的是,我正要说到这一点。
之所以叫多头,是因为我们并行使用了很多这样的自我关注层。也就是说,我们有许多自我关注层堆叠在一起。在论文中,注意层的数量 h 保持为 8。于是输入的 X 并行通过许多自我关注层,每一层给出一个形状的 z 矩阵(Sxd) = 4x64。我们连接这些 8(h)矩阵,并再次应用大小为 DxD 的最终输出线性层 Wo。
我们有多大尺寸的?对于连接操作,我们得到大小为 SxD(4x(64x8) = 4x512)。将这个输出乘以 Wo,我们得到最终输出 Z,其形状为 SxD(4x512)。
此外,请注意 h、D 和 D 之间的关系,即 h x d = D
完整的多头自我关注层(作者的图片)
因此,我们最终得到了 4x512 形状的输出 Z。但是在它进入另一个编码器之前,我们通过一个前馈网络。
b)位置式前馈网络
一旦我们理解了多头注意力层,前馈网络实际上就很容易理解了。它只是输出 z 上各种线性层和漏失层的组合。因此,这里也只是大量的矩阵乘法。
每个单词都进入前馈网络。(图片作者)
前馈网络将其自身并行地应用于输出 Z 中的每个位置(每个位置可以被认为是一个字),因此命名为逐位置前馈网络。前馈网络也共享权重,因此源句子的长度无关紧要(此外,如果它不共享权重,我们将不得不基于最大源句子长度来初始化许多这样的网络,这是不可行的)
它实际上只是一个应用于每个位置(或单词)的线性图层
至此,我们对变压器的编码器部分有了大致的了解。
问:嘿,我刚刚在浏览报纸上的图片,编码器堆栈中有一个叫做“位置编码”和“添加&规范”的东西。这些是什么?
我又回到这里,所以你不必滚动源
好的,这两个概念对于这个特殊的架构来说非常重要。我很高兴你问了这个问题。因此,在进一步讨论解码器堆栈之前,我们将讨论这些步骤。
C.位置编码
由于我们的模型不包含递归和卷积,为了让模型利用序列的顺序,我们必须注入一些关于序列中记号的相对或绝对位置的信息。为此,我们将“位置编码”添加到编码器和解码器堆栈底部的输入嵌入中(我们将在后面看到)。位置编码需要具有与嵌入相同的维数 D,以便两者可以相加。
向 X 添加静态位置模式(图片作者)
在论文中,作者使用正弦和余弦函数为不同的位置创建位置嵌入。
这种特殊的数学运算实际上会生成一个 2d 矩阵,该矩阵会添加到进入第一个编码器步骤的嵌入向量中。
简而言之,这只是一个我们添加到句子中的常数矩阵,这样网络就可以得到单词的位置。
前 300 和 3000 个位置的位置编码矩阵(图片作者
上面是位置编码矩阵的热图,我们将把它添加到第一个编码器的输入中。我正在显示前 300 个职位和前 3000 个职位的热图。我们可以看到,我们向转换器提供了一个独特的模式来理解每个单词的位置。由于我们使用由 sin 和 cos 组成的函数,因此我们能够很好地嵌入非常高位置的位置嵌入,如第二张图所示。
有趣的事实:作者也让 Transformer 学习这些编码,并没有看到任何性能上的差异。所以,他们采用了上面的想法,因为它不依赖于句子的长度,所以即使测试句子比训练样本大,我们也不会有问题。
D.添加并正常化
为了简单起见,我在解释编码器时没有提到的另一件事是,编码器(解码器架构也是如此)架构也有跳过级剩余连接(类似于 resnet50)。因此,本文中确切的编码器架构如下所示。简而言之,它有助于在深度神经网络中以更大的长度遍历信息。这可以被认为类似于(直觉上)在一个组织中传递信息,在这个组织中,你可以接触到你的经理,也可以接触到你的经理的经理。
越级连接帮助信息在网络中流动(图片作者
2.解码器架构
问:好的,到目前为止,我们已经了解了编码器获取一个输入句子,并将其信息编码到大小为 SxD(4x512)的矩阵中。这一切都很好,但它如何帮助解码器解码成德语?
苍天不负苦心人。因此,在了解解码器是如何做到这一点之前,让我们先了解一下解码器堆栈。
解码器堆栈包含堆栈中的 6 个解码器层(如论文中再次给出的),堆栈中的每个解码器由以下三个主要层组成:
- 被屏蔽的多头自我关注层
- 多头自关注层,和
- 位置式全连接前馈网络
它还具有相同的位置编码以及跳过级别连接。我们已经知道多头注意力和前馈网络层如何工作,因此我们将直接进入解码器与编码器的不同之处。
解码器架构(图片作者)
问:等等,但是我看到我们需要的输出作为输入流入解码器了吗?什么?为什么?😖
我注意到你越来越擅长问问题了。这是一个很好的问题,也是我反复思考的问题,我希望当你看到这篇文章的结尾时,这个问题会变得更加清晰。
但是为了给出一个直觉,在这种情况下,我们可以将转换器看作一个条件语言模型。在给定输入单词和英语句子的情况下,预测下一个单词的模型,其预测以该单词为条件或基础。
这样的模型本质上是连续的,比如你如何训练这样的模型?你首先给出开始标记(<s>
),然后模型根据英语句子预测第一个单词。你可以根据预测是对还是错来改变权重。然后你给出起始令牌和第一个单词(<s> der
),模型预测第二个单词。你再次改变重量。诸如此类。
变形金刚解码器就是这样学习的,但美妙之处在于它不是以连续的方式学习的。它使用屏蔽来进行这种计算,从而在训练时获取整个输出句子(尽管通过在前面添加一个<s>
标记来右移)。另外,请注意,在预测时,我们不会将输出提供给网络
问:但是,这个屏蔽到底是怎么工作的呢?
a)屏蔽的多头自我关注层
它工作,像往常一样,你穿它我的意思是😷。玩笑归玩笑,正如你看到的,这次我们的解码器中有一个屏蔽的多头注意力层。这意味着,我们将以一种网络永远无法看到后续单词的方式来屏蔽我们的移位输出(即解码器的输入),否则,它可以在训练时轻松复制该单词。
那么,在被掩蔽的注意力层中,掩蔽究竟是如何起作用的呢?如果你还记得的话,在注意力层中,我们将查询(Q)和键(K)相乘,然后在取 softmax 之前将它们除以 sqrt(d)。
然而,在掩蔽的注意层中,我们在 softmax(其将是 shape (TxT))之前将结果矩阵添加到掩蔽矩阵。
因此,在被遮罩的图层中,函数从:
(作者图片)
问:我还是不明白,如果我们那样做,会发生什么?
这其实是可以理解的。让我一步一步来。因此,我们的 shape (TxT)的结果矩阵(QxK/sqrt(d))可能如下所示:(数字可能很大,因为 softmax 尚未应用)
Schnelle 目前同时照顾 Braune 和 Fuchs
如果我们将上述矩阵的 softmax 与值矩阵 v 相乘,单词 Schnelle 现在将由 Braune 和 Fuchs 组成,但我们不希望如此,因此我们将掩码矩阵添加到其中,得到:
应用于矩阵的掩码操作。(图片作者)
那么,在我们完成 softmax 步骤后会发生什么呢?
施奈尔从不注意施奈尔后面的任何一个词。(作者图片)
由于 e^{-inf} = 0,Schnelle 之后的所有位置都转换为 0。现在,如果我们将这个矩阵与值矩阵 V 相乘,对应于 Schnelle 在通过解码器的 Z 向量中的位置的向量将不会像我们想要的那样包含后续单词 Braune 和 Fuchs 的任何信息。
这就是转换器如何一次获得整个移位的输出句子,而不是以顺序的方式学习。我不得不说,非常整洁。
问:你在开玩笑吗?这真是太棒了。
很高兴你还和我在一起,你很感激。现在,回到解码器。解码器中的下一层是:
b)多头关注层
正如您在解码器架构中看到的,一个 Z 向量(编码器的输出)从编码器流向解码器中的多头注意力层。来自最后一个编码器的这个 Z 输出有一个特殊的名字,通常称为内存。注意层将编码器输出和来自下面的数据流(移位输出)作为输入,并使用注意。查询向量 Q 由解码器中的数据流创建,而键(K)和值(V)向量来自编码器输出。
问:这里没有面膜吗?
不,这里没有面具。来自下面的输出已经被屏蔽,这允许解码器中的每个位置关注值向量中的所有位置。因此,对于要生成的每个单词位置,解码器都可以访问整个英语句子。
这是一个单独的注意力层(和之前一样,它将是多头的一部分):
(图片作者)
Q:但是这次 Q、K、V 的形状不会不一样吗?
你可以看看我做了所有重量计算的图。我还想请你看看合成的 Z 向量的形状,以及我们的权重矩阵如何直到现在还没有在它们的任何维度中使用目标或源句子长度。通常,在我们所有的矩阵计算中,形状会相互抵消。例如,参见上面计算 2 中 S 维是如何抵消的。这就是为什么在训练期间选择批次时,作者谈论紧密批次。也就是说,在一批句子中,所有的源句子都有相似的长度。并且不同批次可以具有不同的源长度。
我现在将讨论跳过级连接和前馈层。它们实际上和……是一样的。
问:好的,我明白了。我们有跳过级连接和 FF 层,并且在整个解码操作之后得到形状 TxD 的矩阵。 但是德语翻译在哪里?
3.输出头
我们现在真的很好,朋友。一旦我们完成了转换器,接下来的事情就是在解码器输出的顶部添加一个特定任务的输出头。这可以通过在顶部添加一些线性层和 softmax 来获得德语词汇中所有单词的概率。我们可以这样做:
(图片作者)
如你所见,我们能够生成概率。到目前为止,我们知道如何通过这个变压器架构进行正向传递。让我们看看如何训练这样的神经网络结构。
培训:
到目前为止,如果我们鸟瞰一下这个结构,我们会发现:
(图片作者)
我们可以给出一个英语句子和移位输出句子,向前传递,得到德语词汇的概率。因此,我们应该能够使用像交叉熵这样的损失函数,其中目标可以是我们想要的德语单词,并使用 Adam 优化器来训练神经网络。就像任何分类示例一样。这就是你的德语。
尽管在论文中,作者使用了优化器和损失的细微变化。如果您愿意,可以选择跳过以下两个关于 KL 偏差损失和 Adam 学习率计划的部分,因为这样做只是为了提高模型的性能,而不是变压器架构的固有部分。
问:我来了这么长时间,有没有抱怨过?😒
好吧。好吧。我懂你的意思。那我们就这么做吧。
a)标签*滑的 KL 散度:
KL 散度是当分布 P 由分布 Q *似时发生的信息损失。当我们使用 KL 散度损失时,我们试图使用我们从模型中生成的概率(Q)来估计目标分布(P)。我们会尽量减少培训中的信息损失。
(图片作者)
如果你注意到,在这种形式中(没有我们将讨论的标签*滑),这与交叉熵完全相同。给定如下两个分布。
单词(令牌)的目标分布和概率分布
KL 散度公式给出了-logq(oder)
,这就是交叉熵损失。
在本文中,尽管作者使用α = 0.1 的标记*滑,因此 KL 散度损失不是交叉熵。这意味着,在目标分布中,输出值由(1-α)代替,剩余的 0.1 分布在所有单词中。作者说这是因为模型不太自信。
(作者图片)
问:但是,为什么我们会让自己的模型不自信呢?这似乎很荒谬。
是的,确实如此,但是直觉上,你可以把它想成当我们把目标作为 1 给我们的损失函数时,我们毫不怀疑真正的标签是真的,而其他的不是。但是词汇本来就是一个非标准化的目标。例如,谁说你不能用好来代替伟大?所以我们在标签中添加了一些混淆,这样我们的模型就不会太死板。
b)使用 Adam 的特定学习率时间表
作者使用一个学习率计划程序来增加学习率,直到热身步骤,然后使用下面的函数来降低它。他们使用了 Adam 优化器,β = 0.9,β = 0.98。这里没什么太有趣的,只是一些学习选择。
来源:论文
问:等等,我刚想起来,在预测时间,我们不会有移位的输出,不是吗?那我们如何预测呢?
如果您意识到我们在这一点上拥有的是一个生成模型,我们将不得不以生成方式进行预测,因为我们在进行预测时不知道输出目标向量。所以预测仍然是连续的。
预测时间
使用变压器通过贪婪搜索进行预测(作者的图片)
该模型进行分段预测。在最初的论文中,他们使用波束搜索来做预测。但是出于解释的目的,贪婪的搜索也很好。在上面的例子中,我已经展示了贪婪搜索是如何工作的。贪婪搜索将从以下内容开始:
- 将整个英语句子作为编码器输入,仅将开始标记
<st>
作为移位输出(解码器的输入)传递给模型,并进行正向传递。 - 该模型将预测下一个单词—
der
- 然后,我们将整个英语句子作为编码器输入,并将最后预测的单词添加到移位后的输出(解码器的输入=
<st> der
)中,并进行正向传递。 - 该模型将预测下一个单词—
schnelle
- 将整个英语句子作为编码器输入,将
<st> der schnelle
作为移位输出(解码器的输入)传递给模型,并进行正向传递。 - 依此类推,直到模型预测到结束标记
</s>
或者我们生成一些最大数量的标记(我们可以定义),这样翻译就不会在任何中断的情况下无限期运行。
光束搜索:
问:现在我贪心了,也说说梁搜吧。
好的,光束搜索的想法本质上和上面的想法非常相似。在 beam 搜索中,我们不仅查看生成的概率最高的单词,还查看前两个单词。
因此,举例来说,当我们给定整个英语句子作为编码器输入,而只给定起始标记作为移位输出时,我们得到两个最佳单词为i
(p=0.6)和der
(p=0.3)。我们现在将为两个输出序列生成输出模型,<s> i
和<s> der
,并查看生成的下一个顶部单词的概率。例如,如果<s> i
给出下一个单词的概率为(p=0.05),而<s> der>
给出下一个预测单词的概率为(p=0.5),那么我们丢弃序列<s> i
并转而使用<s> der
,因为句子的概率总和是最大的(<s> der next_word_to_der
p = 0.3+0.5 与<s> i next_word_to_i
p = 0.6+0.05 相比)。然后我们重复这个过程,得到概率最高的句子。
由于我们使用了顶部的两个字,所以对于这个波束搜索,波束大小是 2。在论文中,他们使用了大小为 4 的波束搜索。
PS :为了简洁起见,我展示了英文句子在每一步都通过,但实际上,编码器的输出被保存,在每一时间步只有移位后的输出通过解码器。
问:你还有什么忘记告诉我的吗?我会给你机会的。
是的。既然你问了。这是:
BPE、重量共享和检查点
在论文中,作者使用字节对编码创建了一个通用的英语德语词汇。然后,他们在英语和德语嵌入和预 softmax 线性变换中使用共享权重,因为嵌入权重矩阵形状可以工作(Vocab 长度 X D)。
此外,作者*均最后 k 个检查点,以创建一个集合效果来达到性能。这是一种非常众所周知的技术,我们对模型的最后几个时期的权重进行*均,以创建一个类似于集合的新模型。
问:可以给我看一些代码吗?
这篇文章已经很长了,所以我会在下一篇文章中写。敬请关注。
现在,终于轮到我问问题了:你知道变压器是如何工作的吗?是,或者不是,可以在评论里回答。😃
参考
在这篇文章中,我从面向细节、直观的角度介绍了 Transformer 架构是如何工作的。
如果你想了解更多关于 NLP 的知识,我想从高级机器学习专业化中调出一门关于 自然语言处理 的精品课程。一定要去看看。
我以后也会写更多这样的帖子。让我知道你对他们的看法。我应该写技术性很强的主题还是更初级的文章?评论区是你的朋友。使用它。还有,在 中 关注我或者订阅我的 博客 。
最后,一个小小的免责声明——这篇文章中可能会有一些相关资源的附属链接,因为分享知识从来都不是一个坏主意。
这个故事最初发表于 这里 。
理解变压器,编程方式
来源:壁纸访问
因为你只能理解它,如果你能编程的话
如今,变形金刚已经成为 NLP 任务的事实标准。它们开始被用于自然语言处理,但现在被用于计算机视觉,有时也用于创作音乐。我相信你们都听说过 GPT3 变压器或其中的笑话。
但抛开一切不谈,他们还是一如既往地难以理解。在我的上一篇文章中,我非常详细地谈到了变形金刚以及它们是如何在基本层面上工作的。我浏览了编码器和解码器架构,以及神经网络不同部分的整个数据流。
但是正如我喜欢说的,在我们自己实施之前,我们并没有真正理解一些东西。所以在这篇文章中,我们将使用 Transformers 实现一个英语到德语的翻译器。
任务描述
我们想创建一个使用变压器将英语转换成德语的翻译器。因此,如果我们把它看作一个黑盒,我们的网络接受一个英语句子作为输入,并返回一个德语句子。
作者图片:用于翻译的转换器
数据预处理
为了训练我们的英德翻译模型,我们需要英语和德语之间的翻译句子对。
幸运的是,通过 IWSLT(国际口语翻译研讨会)数据集,我们有一个非常标准的方法来获取这些数据,我们可以使用torchtext.datasets
来访问这些数据。这个机器翻译数据集是一种用于翻译任务的事实标准,包含不同语言的 TED 和 TEDx 演讲的翻译。
此外,在我们真正进入整个编码部分之前,让我们了解在训练时我们需要什么作为模型的输入和输出。我们实际上需要两个矩阵输入到我们的网络中:
作者图像:输入到网络
- 源英文句子(Source): 一个形状的矩阵(批量大小 x 源句子长度)。该矩阵中的数字对应于基于我们还需要创建的英语词汇的单词。例如,英语词汇中的 234 可能对应于单词“the”。还有,你有没有注意到很多句子都以一个单词结尾,这个单词在词汇中的索引是 6?这是怎么回事?因为所有句子的长度都不一样,所以会用一个索引为 6 的单词填充。所以,6 指的是
<blank>
令牌。 - 移位的目标德语句子(Target): 一个形状的矩阵(批量 x 目标句子长度)。这里,这个矩阵中的数字也对应于我们还需要创建的基于德语词汇的单词。如果你注意到这个特殊的矩阵似乎有一个模式。所有的句子都以一个在德语词汇中索引为 2 的单词开头,并且总是以一种模式结尾[3 和 0 或更多的 1]。这是有意为之的,因为我们希望以某个开始标记开始目标句子(so 2 代表
<s>
标记),以某个结束标记(so 3 代表</s>
标记)和一串空白标记(so 1 代表<blank>
标记)结束目标句子。这一部分在我上一篇关于变形金刚的文章中有更详细的介绍,所以如果你对此感到困惑,我想请你看一看
现在我们知道了如何预处理数据,我们将进入预处理步骤的实际代码。
请注意,如果您也使用其他方法进行预处理,这真的无关紧要。最终重要的是,最终,您需要以一种转换器可以使用的方式将句子源和目标发送到您的模型。即,源句子应该用空白标记填充,目标句子需要有开始标记、结束标记和由空白标记填充的剩余部分。
我们首先加载 Spacy 模型,该模型提供了标记器来标记德语和英语文本。
# Load the Spacy Models
spacy_de = spacy.load('de')
spacy_en = spacy.load('en')def tokenize_de(text):
return [tok.text for tok in spacy_de.tokenizer(text)]def tokenize_en(text):
return [tok.text for tok in spacy_en.tokenizer(text)]
我们还定义了一些特殊的记号,我们将使用它们来指定空白/填充词,以及如上所述的句子的开头和结尾。
# Special Tokens
BOS_WORD = '<s>'
EOS_WORD = '</s>'
BLANK_WORD = "<blank>"
我们现在可以使用 torchtext 中的data.field
为源句子和目标句子定义一个预处理管道。您可以注意到,虽然我们只指定了源句子的pad_token
,但是我们提到了目标句子的pad_token
、init_token
和eos_token
。我们还定义了使用哪些记号赋予器。
SRC = data.Field(tokenize=tokenize_en, pad_token=BLANK_WORD)
TGT = data.Field(tokenize=tokenize_de, init_token = BOS_WORD,
eos_token = EOS_WORD, pad_token=BLANK_WORD)
如果你注意到现在我们还没有看到任何数据。我们现在使用来自torchtext.datasets
的 IWSLT 数据来创建一个训练、验证和测试数据集。我们还使用MAX_LEN
参数过滤我们的句子,这样我们的代码运行得更快。请注意,我们正在获取带有.en
和.de
扩展名的数据。我们使用fields
参数指定预处理步骤。
MAX_LEN = 20
train, val, test = datasets.IWSLT.splits(
exts=('.en', '.de'), fields=(SRC, TGT),
filter_pred=lambda x: len(vars(x)['src']) <= MAX_LEN
and len(vars(x)['trg']) <= MAX_LEN)
现在,我们已经获得了训练数据,让我们看看它是什么样子的:
for i, example in enumerate([(x.src,x.trg) for x in train[0:5]]):
print(f"Example_{i}:{example}")---------------------------------------------------------------Example_0:(['David', 'Gallo', ':', 'This', 'is', 'Bill', 'Lange', '.', 'I', "'m", 'Dave', 'Gallo', '.'], ['David', 'Gallo', ':', 'Das', 'ist', 'Bill', 'Lange', '.', 'Ich', 'bin', 'Dave', 'Gallo', '.'])Example_1:(['And', 'we', "'re", 'going', 'to', 'tell', 'you', 'some', 'stories', 'from', 'the', 'sea', 'here', 'in', 'video', '.'], ['Wir', 'werden', 'Ihnen', 'einige', 'Geschichten', 'über', 'das', 'Meer', 'in', 'Videoform', 'erzählen', '.'])Example_2:(['And', 'the', 'problem', ',', 'I', 'think', ',', 'is', 'that', 'we', 'take', 'the', 'ocean', 'for', 'granted', '.'], ['Ich', 'denke', ',', 'das', 'Problem', 'ist', ',', 'dass', 'wir', 'das', 'Meer', 'für', 'zu', 'selbstverständlich', 'halten', '.'])Example_3:(['When', 'you', 'think', 'about', 'it', ',', 'the', 'oceans', 'are', '75', 'percent', 'of', 'the', 'planet', '.'], ['Wenn', 'man', 'darüber', 'nachdenkt', ',', 'machen', 'die', 'Ozeane', '75', '%', 'des', 'Planeten', 'aus', '.'])Example_4:(['Most', 'of', 'the', 'planet', 'is', 'ocean', 'water', '.'], ['Der', 'Großteil', 'der', 'Erde', 'ist', 'Meerwasser', '.'])
您可能会注意到,虽然data.field
对象已经完成了标记化,但它还没有应用开始、结束和填充标记,这是有意的。这是因为我们还没有批处理,填充标记的数量本质上取决于特定批处理中句子的最大长度。
正如开始提到的,我们还通过使用data.field
对象中的内置函数来创建源语言和目标语言词汇表。我们指定 MIN_FREQ 为 2,这样任何至少不出现两次的单词都不会成为我们词汇表的一部分。
MIN_FREQ = 2
SRC.build_vocab(train.src, min_freq=MIN_FREQ)
TGT.build_vocab(train.trg, min_freq=MIN_FREQ)
一旦我们完成了这些,我们就可以简单地使用data.Bucketiterator
,它用于给出相似长度的批处理来得到我们的训练迭代器和验证迭代器。注意,我们使用 1 的batch_size
作为验证数据。这样做是可选的,但这样做是为了在检查验证数据性能时不进行填充或进行最小填充。
BATCH_SIZE = 350# Create iterators to process text in batches of approx. the same length by sorting on sentence lengthstrain_iter = data.BucketIterator(train, batch_size=BATCH_SIZE, repeat=False, sort_key=lambda x: len(x.src))val_iter = data.BucketIterator(val, batch_size=1, repeat=False, sort_key=lambda x: len(x.src))
在我们继续之前,最好先看看我们的批处理是什么样子,以及我们在训练时作为输入发送给模型的是什么。
batch = next(iter(train_iter))
src_matrix = batch.src.T
print(src_matrix, src_matrix.size())
这是我们的源矩阵:
trg_matrix = batch.trg.T
print(trg_matrix, trg_matrix.size())
这是我们的目标矩阵:
所以在第一批中,src_matrix
包含 350 个长度为 20 的句子,而trg_matrix
是 350 个长度为 22 的句子。为了确保我们的预处理,让我们看看这些数字在src_matrix
和trg_matrix.
中代表什么
print(SRC.vocab.itos[1])
print(TGT.vocab.itos[2])
print(TGT.vocab.itos[1])
--------------------------------------------------------------------
<blank>
<s>
<blank>
果然不出所料。相反的方法,即字符串到索引也工作得很好。
print(TGT.vocab.stoi['</s>'])
--------------------------------------------------------------------
3
变形金刚
作者图片:应用程序架构
因此,现在我们有了一种将源句子和转换后的目标发送到转换器的方法,我们可以开始创建转换器了。
这里的很多积木都取自 Pytorch nn
模块。事实上,Pytorch 也有一个 Transformer 模块,但它不包括论文中提到的许多功能,如嵌入层和位置编码层。所以这是一种更完整的实现,也从 pytorch 实现中吸取了很多东西。
我们特别使用 Pytorch nn 模块中的各种模块来创建我们的变压器:
- TransformerEncoderLayer:单个编码器层
- TransformerEncoder :一堆
num_encoder_layers
层。在本文中,默认情况下保持为 6。 - TransformerDecoderLayer :单个解码器层
- TransformerDecoder :一堆
num_decoder_layers
层。在本文中,默认情况下保持为 6。
此外,请注意,无论层中发生什么,实际上只是矩阵函数,正如我在变压器的解释帖子中提到的那样。请特别注意解码器堆栈如何从编码器获取内存作为输入。我们还创建了一个位置编码层,让我们将位置嵌入添加到单词嵌入中。
如果你愿意,你可以看看我已经链接的所有这些模块的源代码。我不得不多次亲自查看源代码,以确保我为这些层提供了正确的输入。
定义优化器和模型
现在,我们可以使用以下代码初始化转换器和优化器:
source_vocab_length = len(SRC.vocab)
target_vocab_length = len(TGT.vocab)model = MyTransformer(source_vocab_length=source_vocab_length,target_vocab_length=target_vocab_length)optim = torch.optim.Adam(model.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)model = model.cuda()
在论文中,作者使用了一个具有预定学习率的 Adam 优化器,但是这里我只是使用一个普通的 Adam 优化器来简化事情。
培训我们的翻译
现在,我们可以使用下面的训练函数来训练我们的变压器。我们在培训循环中必须做的是:
- 从批处理中获取 src_matrix 和 trg_matrix。
- 创建一个
src_mask
—这是告诉模型关于 src_matrix 数据中填充单词的掩码。 - 创建一个
trg_mask
——这样我们的模型就不能在任何时间点查看未来的后续目标单词。 - 从模型中获得预测。
- 使用交叉熵计算损失。(在论文中,他们使用了 KL 散度,但这也有助于理解)
- 反向投影。
- 我们保存基于验证损失的最佳模型。
- 我们还使用函数
greedy_decode_sentence
预测我们选择的一些句子在每个时期的模型输出,作为调试步骤。我们将在结果部分讨论这个函数。
我们现在可以使用以下工具进行培训:
train_losses,valid_losses = train(train_iter, val_iter, model, optim, 35)
以下是训练循环的输出(仅针对某些时期显示):
**Epoch [1/35] complete.** Train Loss: 86.092\. Val Loss: 64.514
Original Sentence: This is an example to check how our model is performing.
Translated Sentence: Und die der der der der der der der der der der der der der der der der der der der der der der der**Epoch [2/35] complete.** Train Loss: 59.769\. Val Loss: 55.631
Original Sentence: This is an example to check how our model is performing.
Translated Sentence: Das ist ein paar paar paar sehr , die das ist ein paar sehr Jahre . </s>.
.
.
.**Epoch [16/35] complete.** Train Loss: 21.791\. Val Loss: 28.000
Original Sentence: This is an example to check how our model is performing.
Translated Sentence: Hier ist ein Beispiel , um zu prüfen , wie unser Modell aussieht . Das ist ein Modell . </s>.
.
.
.**Epoch [34/35] complete.** Train Loss: 9.492\. Val Loss: 31.005
Original Sentence: This is an example to check how our model is performing.
Translated Sentence: Hier ist ein Beispiel , um prüfen zu überprüfen , wie unser Modell ist . Wir spielen . </s>**Epoch [35/35] complete.** Train Loss: 9.014\. Val Loss: 32.097
Original Sentence: This is an example to check how our model is performing.
Translated Sentence: Hier ist ein Beispiel , um prüfen wie unser Modell ist . Wir spielen . </s>
我们可以看到我们的模型是如何从一个莫名其妙的翻译开始的——“然后在几次迭代结束时开始给我们一些东西。
结果
我们可以使用 Plotly express 绘制训练和验证损失。
import pandas as pd
import plotly.express as pxlosses = pd.DataFrame({'train_loss':train_losses,'val_loss':valid_losses})px.line(losses,y = ['train_loss','val_loss'])
作者图片:培训和验证损失
如果我们想要部署这个模型,我们可以简单地使用:
model.load_state_dict(torch.load(f”checkpoint_best_epoch.pt”))
并使用greeedy_decode_sentence
函数对任何源句子进行预测,该函数为:
作者图片:使用变形金刚进行贪婪搜索预测
这个函数做分段预测。贪婪搜索将从以下内容开始:
- 将整个英语句子作为编码器输入,仅将开始标记
<s>
作为移位输出(解码器的输入)传递给模型,并进行正向传递。 - 该模型将预测下一个单词——
der
- 然后,我们将整个英语句子作为编码器输入,并将最后预测的单词添加到移位的输出(解码器的输入=
<s> der
)中,并进行正向传递。 - 该模型将预测下一个单词——
schnelle
- 将整个英语句子作为编码器输入,将
<s> der schnelle
作为移位输出(解码器的输入)传递给模型,并进行正向传递。 - 依此类推,直到模型预测到结束标记
</s>
或者我们生成一些最大数量的标记(我们可以定义),这样翻译就不会在任何中断的情况下无限期运行。
现在我们可以用这个来翻译任何句子:
sentence = "Isn't Natural language processing just awesome? Please do let me know in the comments."print(greeedy_decode_sentence(model,sentence))------------------------------------------------------------------Ist es nicht einfach toll ? Bitte lassen Sie mich gerne in den Kommentare kennen . </s>
由于我手头没有德语翻译,所以我将使用退而求其次的方法来查看我们的模型表现如何。让我们借助谷歌翻译服务来理解这个德语句子的意思。
作者图片:谷歌翻译结果
翻译中似乎有一些错误作为“自然语言处理”是没有的(讽刺?)但对我来说,这似乎是一个足够好的翻译,因为神经网络只需一个小时的训练就能理解这两种语言的结构。
vorbehalte/verbes rungen(警告/改进)
如果我们按照报纸上的方法做每件事,我们可能会取得更好的结果:
- 全数据训练
- 字节对编码
- 学习率调度
- KL 发散损失
- 光束搜索,和
- 检查点集合
我在我的上一篇文章中讨论了所有这些,所有这些都很容易实现。但是这个简单的实现是为了理解转换器是如何工作的,所以我没有包括所有这些内容,以免混淆读者。事实上,在变形金刚的基础上已经有了相当多的进步,让我们有了更好的翻译模型。我们将在接下来的文章中讨论这些进步以及它们是如何实现的,在这篇文章中,我将谈论 BERT,这是最受欢迎的 NLP 模型之一,其核心使用了转换器。
参考
在这篇文章中,我们使用 transformer 架构几乎从头开始创建了一个英语到德语的翻译网络。
为了更仔细地查看这篇文章的代码,请访问我的 GitHub 库,在那里你可以找到这篇文章以及我所有文章的代码。
如果你想了解更多关于 NLP 的知识,我想从高级机器学习专业化中调出一门关于 自然语言处理 的精品课程。一定要去看看。
我以后也会写更多这样的帖子。让我知道你对他们的看法。我应该写技术性很强的主题还是更初级的文章?评论区是你的朋友。使用它。还有,在 中 关注我或者订阅我的 博客 。
最后,一个小小的免责声明——这篇文章中有一些相关资源的附属链接,因为分享知识从来都不是一个坏主意。
这个故事最初发表于这里。
通过数据科学了解投票结果
在 2016 年总统选举的惊人结果之后,我想更好地理解在投票行为中起作用的社会经济和文化因素。随着选举结果的公布,我认为根据一些广泛可用的县级数据集反向工程一个投票行为的预测模型会很有趣。
最终的结果是一个 web 应用程序,让你上下拨动 10 个最有影响力的功能,并可视化你假设场景的县级投票结果:2016 年总统选举的随机森林模型:
[## 2016 年总统选举的随机森林模型
请等到模型完成加载后再提交调整。上面的地图允许您构建…
kkehoe1985.github.io](https://kkehoe1985.github.io/2016_election_data_viz/)
例如,如果你想回答问题“如果全国范围内至少拥有学士学位的人的比例高出 2%,选举会有什么不同?”您只需将该参数设置为 1.02,然后点击“提交”即可找到答案。
预测是由随机森林分类模型驱动的,该模型已针对 71 个不同的县级属性进行了调整和训练。使用真实数据,该模型的预测准确率为 94.6%,ROC AUC 得分为 96%。当您调整可调参数并点击“提交”时,模型会接收新数据并更新预测结果。
需要指出的是,上面的交互式可视化被描述为“假设的”比“预测的”更好。投票给民主党或共和党不是一个静态的分类;随着时间的推移,政党领导人和政纲会发生重大变化,因此同一模型不能用于预测多次选举过程中的投票结果。然而,在适当的背景下,该模型可以产生有价值的见解。通过理解选民在不同情况下会如何对民主党和共和党 2016 年的政纲做出反应,我们可以更好地理解如何调整政纲以考虑未来 4 年的预计社会经济趋势。该工具旨在用于探索性数据分析,对其输出的解释更多的是一门艺术而不是科学。
那么它是如何工作的呢?
我把它作为一个二元分类问题来处理;该模型需要预测每个县将投票给共和党还是民主党。为了创建一个预测模型,我需要一个强大的“解释性”数据集(社会经济和文化数据)和一个“响应”数据集(投票结果)。
这一过程的第一步是搜集大量关于教育、人口密度、收入、种族、性别、年龄和宗教的县级数据来源(参见获取和清理美国县级数据源)。这需要在 Python 中进行数据清理和格式化,以及用加权*均值输入缺失值。
一旦数据被组合成一个单一的、干净的数据集,我就测试了许多监督学习技术,包括 k-最*邻、逻辑回归和随机森林分类。对于每一款车型,我都结合使用了网格搜索和交叉验证技术,调整了必要的超级参数:
优化超参数后,我根据其预测准确性和 ROC AUC 评分评估了这些技术:
随机森林分类
- ROC AUC 得分:96.1%
- 准确率:93.6%
逻辑回归
- ROC AUC 得分:91.7%
- 准确率:89.4%
K-最*邻
- ROC 曲线下面积分数:80.64%
- 准确率:87.4%
随机森林分类器对于这种分析是最准确的,因此是数据可视化中使用的模型。
决策树和随机森林
为了理解随机森林模型,我们首先需要理解决策树是如何工作的。决策树是由节点和分支组成的树状结构。每个节点可以被认为是对预测特征的测试。如果特征是定量的(在本练习中我们所有人都是如此),那么将一个值设置为特征的分割点,高于该值的样本遵循一个分支,而低于该值的样本遵循另一个分支。在决策树分类器中,终端节点将导致对类别的预测。
在我们的例子中,特征和它们的分裂点是由它们的“基尼系数”值决定的。有关基尼系数的完整解释,请参见决策树和基尼系数,但简而言之,基尼系数是随机选择的子集成员被错误分类的概率(这可以被认为是“预期错误率”)。如果该值为 0,则表示该子集已被完全纯化;只有一个阶层的成员留下。在每一步,该树优先考虑其产生的子集产生基尼系数杂质最大减少的特征,称为“基尼系数”
单个决策树具有可解释的优点,但是为了净化样本数据,它通常会变得很大。这可能会产生一个与样本数据完美拟合的模型,但不能很好地推广到样本外数据(具有低偏差/高方差的“过拟合”)。决策树也是“贪婪”的,因为它们在每个节点寻找局部最优决策(基于基尼系数),但不考虑可能提供最有效路径的特征组合。
为了避开这些限制,我们可以使用一种叫做随机森林的集成方法,这种方法可以生成多个决策树。每棵树使用 bootsrap 聚合(“bagging”)来随机选择一个特性子集。允许树完全生长,无需修剪,并且仅基于为其选择的随机特征子集来评估树中的每个节点。重复该过程,直到树的森林已经生长,并且对于测试集中的每个数据点,随机森林模型将从所有决策树中取得多数投票。
我们学到了什么?
种族、教育、宗教和人口密度是预测投票结果的最重要的信号
为了对数据有所了解,一个很好的起点是构建一个随机森林,并可视化预测因子的特征重要性,这由特征的基尼系数决定。在这里,我们可以看到预测 2016 年总统选举政党赢家的十大特征:
由于这些是定量变量,每个特征都有一个分裂点,在这个分裂点上投票行为的差异是显著的。当我们检查每个特征的分割点时,我们看到以下内容:
- 2011 年至 2015 年拥有学士学位或更高学位的成人比例:29.95%
- 2011 年至 2015 年仅有高中文凭的成人比例:26.55%
- 每*方英里土地面积的住房密度:201.9
- 每*方英里土地面积的人口密度:483.7
- 人口:4 223 人
为了更好地可视化这些分割点的影响,检查分割点上方和下方的结果变量的分布会有所帮助。这里有一个使用人口密度的例子,你可以看到投票趋势在每*方英里 483 人以上和以下是相反的:
要检查其余功能,请参见下面的 Tableau 工作簿: 2016 选举分裂点仪表板。
美国南部投票结果的关键变量是人口的种族构成
如果美国白人的比例减少 25%,这个模型预测的美国南部可能是这样的:
实际
假设的
为了探索更多假设的场景,玩下面的网络应用程序,看看随机森林预测会发生什么:2016 年美国总统选举的假设模型
描述性统计
即使不从我们的数据集创建模型,也可以从标准的探索性分析中获得一些见解:
在人口密度高的县,教育是投票结果的关键因素
人口密度在每*方英里 200 人以上的县倾向于投民主党的票。在那些投票给共和党的人中,拥有学士学位或更高学位的人口比例比民主党县低 12.5%(见热图的分裂点):
人口老龄化的县投票率更高
共和党和民主党的*均年龄和投票率都呈正相关。然而,*均年龄是一个棘手的变量,因为它不排除低于投票年龄的人,这意味着年轻家庭数量多的县可能有更高的*均年龄选民,但整体*均年龄较低:
参见下面的 Tableau 仪表盘,探索解释变量之间的更多关系: 2016 年总统选举—数据探索
投票率较高的县倾向于投共和党的票
关键的战场州,包括密歇根州、俄亥俄州和威斯康星州,整体选民投票率很高,但特别是来自共和党的选民:
投票率低的县投票给民主党
民主党倾向于在投票率最低的县赢得普选。在西南部人口稠密的县尤其如此。这更有可能发生在非战场县,因为选民参与度较低,可能认为结果是不可避免的:
自己探索
使用 D3 和烧瓶构建的交互式可视化
用于探索性分析的 Tableau 工作簿
- 10 大特性的分割点:在本工作簿中,您可以查看几个重要特性的分割点,并检查分割点上下的投票趋势
- 用热点图分割点:同上,但用美国的热点图代替直方图。可以通过单击饼图来过滤热图。
- 按投票率统计的人口分布:以柱状图显示按投票率统计的人口分布。直方图充当条形图的过滤器。
- 解释变量之间的相互关系:该工作簿允许您选择 x 轴和 y 轴的变量,并基于前一工作簿中的分割点应用过滤器。这使得我们可以一次检查几个特征的相互作用。
Jupyter 笔记本
理解 Spotify 歌曲的词向量—第 1 部分
照片由 Natalie Cardona 在 Unsplash 上拍摄
开始用 Spotify 构建单词向量和一些有趣的数学东西。
假设你遇到两个向量,维数 A= {2,4}和 B={2,3},你会怎么做?你可能会想象一个 2D 坐标空间,然后把它们画出来。
根据你对向量的了解,你可以得出这样的结论:这两个向量的大小和方向非常相似,它们之间的距离非常小。现在,假设你遇到两个词——“橘子”和“橙子”。你的大脑知道这两个词是水果,而且它们很相似(怎么相似?它们都有柑橘的味道和气味,它们都长在树上,它们看起来都是橙色的,等等)。
但我们谈论的是人脑。它理解语言,可以想象一个单词及其相关特征。因此,它可以得出逻辑结论,如“橙子和橘子有些相似”。现在,你如何训练一个算法来得出类似的结论?
单词矢量救援!单词向量是向量空间中表示该单词含义的一组数字。以水果为例,考虑一个简单的三维向量空间——宽度、高度和酸味。我们可以用这三个维度来表示任何水果的“意义”
你现在可以看到单词“tangerine”和“orange”在宽度、高度和辛辣程度上非常相似。将单词表示为一行数字或向量(对应于向量空间中的特定维度)使我们能够对它们进行各种有趣的计算,例如根据大小,哪种水果类似于樱桃,或者哪种水果介于橙子和芒果之间。这些向量和它们的维度捕捉了任何单词的“含义”,就像上面的例子一样。
如前所述,水果示例给出的单词向量和维度是标准单词向量的缩小/简化版本,例如 GloVe 或 Word2vec 。如何训练这些单词向量是我希望在本系列的第 2 部分讨论的内容。
关于水果已经说得够多了。让我们继续有趣的事情。给定一个以数字表示的歌曲及其属性(如能量、响度、可跳舞性)的数据集,让我们应用简单向量数学的概念来获得一些很酷的见解,并希望在此过程中发现一些新的音乐。
我从 TidyTuesday 的 github 回购中提取了 Spotify 歌曲列表数据集(请查看他们的回购,获得一些非常非常酷的数据集)。
这个链接拥有数据集的数据字典。这篇文章使用的代码可以在这里找到
让我们看看数字列之间的相关性。我对与“可舞性”正相关的专栏特别感兴趣,因为谁不喜欢好的舞曲呢:)
在所有列中,“化合价”列的相关性最高。让我们用 playlist_genre="pop "和列" danceability "和" valence "来划分歌曲的数据帧。这个数据帧的一个例子看起来像这样
这个数据帧类似于我们之前构建的水果数据帧。这里,可跳性和价是用来表示特定歌曲的两个维度(目前)。
绘制 10 首舞蹈性得分最高的歌曲给了我们
我们看到大多数曲目的可跳性值非常相似(范围从 0.95 到 0.98),但我们看到沿着价维度,有相当多的差异。如果我们想根据歌曲的可跳性和价值找到与某首歌曲相似的歌曲,该怎么办?让我们看看能否在 Spotify 数据框架中找到与蠢朋克的《环游世界》最接*的歌曲。
#df - dataframe with track_name, track_artist and track_properties
#closest - character flag that determines whether to return songs
#that match most/least
#song - string represeting the song being matched
#n - number of songs to return. Default value is 10
findSongs <- function(df,artist,closest,song,num=10){
song.properties <- df[which(df$track_name==song & df$track_artist==artist),]$properties[[1]]
if(closest=="N"){
num = num
}else{
num= -num
}
dist.df <- df %>% mutate(distance = mapply(function(x, y){
dist(rbind(x,y),method = "euclidean")
}, properties, song.properties)) %>%
top_n(num) %>%
select(track_name,track_artist, distance) %>%
arrange(distance)
dist.df
}closestMatches <- findSongs(popSongs_properties,artist="Daft Punk",closest="Y",song="Around the World")
closestMatches <- as.data.frame(closestMatches)
findSongs 函数获取歌曲的坐标,搜索数据帧,并根据欧几里德距离度量找到最接*的歌曲。对于“环游世界”,该函数返回
沿着可跳舞性和化合价维度绘制该图给出
好东西!!拉尔夫给我的似乎是最*的。我们可以看到,与之前的图相比,这些点是如何更紧密地聚集在一起的,以及化合价的变化减少了多少。
但是,请记住,这种相似性仅基于 2 个衡量标准/维度。您可以修改代码,更改尺寸以满足您的需要。想找高节奏的摇滚乐吗?子集输出 playlist_genre ="rock "以及列" instrumentalness "和" tempo "。选择是无穷无尽的,真的。说到无穷无尽,同一个 findSongs 函数可以用来匹配给定歌曲的所有数字属性(而不仅仅是可跳舞性和效价),这就是单词 vector 最终要做的。
步骤是相同的——我们创建一个数据帧,该数据帧具有 track_name、track_artist 和一个“properties”列,该列表示一首歌曲的所有数字属性/特性组合成一个向量。让我们找出与西蒙和加芬克尔的《拳击手》相似的歌曲
closestMatchesAll <- findSongs(spotify_songs_properties,artist="Simon & Garfunkel",closest="Y",song="The Boxer")
closestMatchesAll <- as.data.frame(closestMatchesAll)
这给了
弹出的第一件事是距离列的比例与我们之前的距离度量相比大了多少。这是有意义的,因为列“速度”和“调”的比例比其他值大。
用数字表示单词的另一个很酷的功能是,你可以根据两个不同单词的欧几里得距离来计算它们之间的距离。在我们的 Spotify 世界里,
distance1 <- distanceBetween("Ironic - 2015 Remaster","Alanis Morissette","Hollaback Girl","Gwen Stefani",spotify_songs_properties)distance1
8.072921distance2 <- distanceBetween("Ironic - 2015 Remaster","Alanis Morissette","Ping Pong","Armin van Buuren",spotify_songs_properties)distance2
33.25089if(distance1 < distance2){
print("Ironic by Alanis Morissette is more similar to Hollaback Girl by Gwen Stefani than it is to Ping Pong by Armin van Buuren")
}
在这里添加歌曲来获得新歌可能没有太大意义,但是像 GloVe 或 Word2vec 这样的词向量可以帮助你做到这一点。增加单词或减少单词可以给你一个新的向量,它非常接*向量空间中现有的单词。
这篇文章的灵感来自艾莉森·帕里什关于单词向量的文章。
用于计算机视觉的未开发的张量流库
探索工具包和扩展,如 TensorFlow 模型优化器、图形、联合学习、隐私等,以提升您的计算机视觉工作流程。
Emil Widlund 在 Unsplash 上的照片
TensorFlow 是一个端到端的开源机器学习*台,能够执行一系列任务。它为初学者和研究人员提供了易用性,并可用于不同的应用,例如但不限于计算机视觉、自然语言处理和强化学习。
在计算机视觉领域,我们大多数人都熟悉核心 TensorFlow 以及 TensorFlow Lite 和 JS。前者用于在移动和边缘设备上运行模型,后者用于在网络上运行模型。然而,TensorFlow 还提供了许多神秘的库,我们将在本文中一一介绍。
目录
- 张量流模型优化工具包
- 张量流图形
- 张量流联邦
- TensorFlow 隐私
- 张量流集线器
张量流模型优化工具包
实时模型对于许多商业运作来说是必不可少的。MobileNet 的推理速度让它成为众人瞩目的焦点,即使这意味着牺牲一点准确性。优化 TensorFlow 模型首先想到的是将其转换为 TensorFlow lite 服务。然而,这在台式机上工作得不是很好,因为它针对 ARM neon 进行了优化,这在本期中有所解释,或者我们需要进一步优化该模型。模型优化工具包可以帮助我们完成这些任务。根据其主页,它可用于:
降低云和边缘设备(如移动设备、物联网)的延迟和推理成本。
将模型部署到对处理、内存、功耗、网络使用和模型存储空间有限制的边缘设备。
支持在现有硬件或新的专用加速器上执行和优化。
它可以应用于已经训练好的模型以及训练期间,以进一步优化解决方案。在撰写本文时,它提供了三种技术,同时还提供了其他几种技术来完善模型。
修剪
第一种方法是权重剪枝。它通过去除层之间的一些连接来工作,因此减少了所涉及的参数和操作的数量,从而优化了模型。它消除了权重张量中不必要的值,并在训练过程中执行。这有助于减小模型的大小,通过训练后量化可以进一步减小模型的大小。
我不会深入每个函数的细节和代码,因为那会使文章太长。你可以参考此处进一步了解,参考此处了解其代码。
量化
与仅在训练期间进行的修剪不同,量化可以在训练和测试时进行。Tensorflow Lite 模型也被量化为使用 8 位整数,而不是通常使用的 32 位浮点。这提高了性能和效率,因为整数运算比浮点运算快得多。
然而,这是有代价的。量化是一种有损技术。这意味着先前从-3e38 到 3e38 表示的信息必须从-127 到 127 表示。在加法和乘法运算期间,8 位整数放大到 32 位整数,这需要再次缩小,从而引入更多误差。为了解决这个问题,可以在训练期间应用量化。
量化感知训练
通过在训练期间应用量化,我们迫使模型学习它将导致的差异,并相应地采取行动。量化误差作为噪声引入,优化器试图将其最小化。以这种方式训练的模型具有与浮点模型相当的准确性。看到以这种方式创建的 Tensorflow Lite 模型与普通模型的比较将会很有趣。
要阅读更多关于它的内容,请参考这里的,关于它的代码,你可以看看这里的。
岗位培训量化
尽管在训练期间应用量化是优选的,但是有时这样做是不可行的,并且我们可能已经准备好使用预训练的权重。此外,它更容易实现。
权重聚类
它将相似的权重组合在一起,并用单个值替换它们。可以想象成 JPEG 压缩。此外,由于相似的权重被插值到相同的数字,所以它也是有损耗的。权重矩阵存储浮点值。这些值被转换成整数,并存储一个包含聚类数的查找表。这减少了所需的空间,因为整数需要更少的空间来存储,并且留下了有限数量的浮点数。
如下例所示,十六个 float-32 值被分配给四个 float-32 质心,图层权重被转换为整数值。权重矩阵越大,节省越多。
权重聚类
聚类被应用于完全训练的模型以找到质心。那么任何压缩工具都可以用来减小模型的大小。要了解它的细节,请参考这里的,以及它的实现。
不同的技术可以结合起来进一步减少延迟,更多的方法也在他们的路线图中讨论。
张量流图形
TensorFlow graphics 旨在结合计算机视觉和计算机图形学来解决复杂的 3D 任务。计算机图形工作流程需要 3D 对象及其在场景中的绝对位置、对它们由灯光组成的材质的描述,以及生成合成渲染的相机。另一方面,计算机视觉工作流程将从图像开始,并尝试推断其参数。
这可以被认为是一个自动编码器,其中视觉系统(编码器)将试图找到参数,而图形系统(解码器)将基于这些参数生成图像,并可以与原始图像进行比较。此外,该系统不需要标记数据,并以自我监督的方式进行训练。张量流的一些用途是:
- 变换 —可以对对象进行旋转、*移等对象变换。这可以被神经网络学习以精确地找到物体的位置。这对于需要精确估计这些物体的位置的机械臂是有用的。
- 建模摄像机 —可以为摄像机设置不同的内在参数,从而改变图像的感知方式。例如,改变相机的焦距会改变物体的大小。
- 材料 —可以使用不同类型的材料,这些材料具有不同类型的光反射能力。因此,创建的场景可以准确地模拟对象在真实世界中的行为。
- 3D 卷积和池化(点云&网格) —它具有 3D 卷积和池化层,允许我们对 3D 数据执行语义分类和分割。
- TensorBoard 3D — 3D 数据变得越来越普遍,可用于解决 2D 数据的 3D 重建、点云分割、3D 对象变形等问题。通过 TensorBoard 3D,可以可视化这些结果,从而更好地了解模型。
延伸阅读— 此处(其中还包含 Colab 笔记本的链接)
张量流联邦
照片由 Ricardo Arce 在 Unsplash 上拍摄
这个库也可以用于计算机视觉以外的其他领域。随着移动设备和边缘设备的增多,产生了大量的数据。联合学习旨在对分散的数据执行机器学习,即在设备本身上!这意味着没有必要将大量(敏感)数据上传到服务器。它已经被用于谷歌键盘。
下面附上的视频解释了关于联合学习的一切,从分散数据到使用 TensorFlow Federated。
有关使用 TensorFlow Federated 进行影像分类的指南,请参考下面链接的文章。
这个 Colab 已经被验证可以和 Note 一起工作:tensorflow_federated pip 包的最新发布版本…
www.tensorflow.org](https://www.tensorflow.org/federated/tutorials/federated_learning_for_image_classification)
TensorFlow 隐私
杰森·登特在 Unsplash 上拍摄的照片
通过隐私攻击可以从训练好的 ML 模型中提取敏感信息。Truex 等人提交了一份关于驱动因素的论文。这些模型甚至可以重建它被训练的信息,如本文所示。
左侧是仅使用人名和模型重建的图像。右边的图像是原始图像。摘自论文本身。
同样,像 TensorFlow Federated 一样,这不是计算机视觉独有的。最常用的技术是差分隐私。来自维基百科:
差异隐私是一种公开共享数据集信息的系统,通过描述数据集中的群体模式,同时保留数据集中的个人信息。
假设敏感信息不会在整个数据集中完全重复,并且通过使用差分隐私模型,可以确保该模型不会获知此类信息。例如,假设有一个人与人之间聊天的数据集。现在,聊天上传递的敏感信息可以是密码、银行账户详情等。因此,如果在此数据集上创建模型,差分隐私将确保模型无法了解这些细节,因为它们的数量很少。阅读这篇关于差分隐私的优秀文章,它也包含了执行它的代码。
张量流集线器
丹尼尔·萨尔西乌斯在 Unsplash 上拍摄的照片
你们中的大多数人一定知道这个库,所以我将非常简短地介绍它。TensorFlow Hub 是一个在 TensorFlow 中发布、发现和重用部分机器学习模块的*台。称之为 TensorFlow 模型的 GitHub 也不会错。开发人员可以分享他们预先训练好的模型,这些模型可以被其他人重用。通过重用,开发人员可以使用较小的数据集训练模型,提高泛化能力,或者只是加快训练速度。让我们快速浏览一些不同的计算机视觉模型。
- 图像分类——从 MobileNet 到 Inception 再到 EfficientNet,有 100 多种模型可用于此任务。说出你想要的任何型号,你很可能会在那里找到它。
- 对象检测和分割-同样,您需要的任何模型都可以在这里找到,尤其是在 COCO 数据集上训练的 TensorFlow 模型动物园对象检测器集合。Deeplab 架构主导了图像分割场景。还有大量的 TfLite 和 TensorFlow Js 型号可供选择。
- 图像风格化——不同的图像风格化主干可以和漫画风格一起使用。
- 生成性对抗网络-GAN 模型如 Big-GAN 和 Compare-GAN 可用,在 ImageNet 和 Celeb 数据集上训练,可用于训练 ImageNet 和人工人脸的任何类别!还有一个 unboundary-GAN,可用于生成相机捕捉的场景之外的区域。此外,他们中的大多数人都有一个 Colab 笔记本,所以在如何实现他们方面没有什么大惊小怪的。
我刚刚描述了冰山一角。有更多的模型可用于姿势估计、特征匹配、超分辨率等主题。我甚至没有讨论过视频。跳到他们的页面了解更多信息。你也可以在这里找到关于它的教程。
TensorFlow 提供了更多的库,如 TensorFlow Extended ,一个用于部署 ML 管道的库, Magenta ,一个用于生成音乐的库,等等。点击查看完整列表。
MLOps 的统一元数据
最*,我写了关于实现 MLOps 工作流的一些障碍。我现在将重点关注元数据,这是一个潜在障碍的例子,并说明统一研究人员和工程师之间的数据如何促进 MLOps 的采用。
正如 DevOps 运动得到了容器化、基础设施即代码和持续集成等工具的帮助一样,MLOps 将需要特定的工具来实现其广泛采用。我相信多模型数据库正在成为 MLOps 的重要工具之一,我将对 ArangoDB 进行评估。
为什么要分离数据?
思考各自团队的两个主要过程有助于我们准确理解为什么我们在这些系统中有数据分离。
研究团队
研究人员旨在利用大量带标签的数据,生成深度学习模型,这些模型现在是机器学习的同义词。这些数据需要尽可能接*业务用途,否则培训中的表现将与生产中的表现有很大不同。这意味着,从示意图的角度来看,训练集通常非常简单,通常利用单个或批量张量输入(无论是图像、数据值、音频……)以及模型在最佳条件下输出的一个或多个目标标签。
从数据的角度来看,输入和输出的简单性需要大量的数据来了解所需任务的复杂性。这通常是神经网络本身的工作,以确定导致输入和目标之间的联系的属性。
开发团队
运行业务应用程序或者将机器学习模型投入生产的团队通常对他们的数据有非常不同的要求。它们仍然具有相同的输入和输出类型,但是需要额外的属性,这些属性对于商业决策可能比机器学习输出本身更重要。同样,还涉及到其他流程,需要跟踪成本和客户需求,并且需要对系统有更深入的理解。这可以通过元数据来实现,通常在关系数据库中,位于服务、网站或系统之后。
人工验证
最*,公司已经在生产中使用机器学习和人工验证的结合,以获得实际上可以销售给客户的性能值。通常,机器学习本身不够准确,因此在结果发送给客户端之前,阈值被设置为发送给人工检查。
这些信息对于研究团队来说显然是有用的,可以提供进一步的标记数据,以及更早地检测数据偏移的能力,尤其是在生长阶段。这也允许团队检查来自 ML 解决方案的结果,以及其他业务过程,当与人类水*的结果比较时。通常,这些结果与生产数据库系统有关,这在研究人员和他们改进模型所需的数据之间建立了一堵墙。
统一数据库的要求
因此,为了统一这些数据,我们需要将训练深度学习模型所需的数据规模和业务应用所需的复杂性结合起来,并在生产服务(包括人工验证)和研究人员数据集之间建立简单的联系。
分离的数据库管道示例
在上图中,我们有许多不同的服务(A、B、C 和 D ),所有这些服务都通过管道推送元数据的公共键( k1、k2 ),以便保留在以后阶段可能有用的信息。这意味着我们经常复制我们不需要的数据,只是简单地传递它,并且很难或不可能跨不同的数据库进行查询,这些数据库通常具有不同的格式、区域或体系结构。
通常,在上述架构中,很难通过工作流来跟踪资产或数据片段的过程,并从这种跟踪中获得洞察力。此外,研究人员很难将来自多个来源的信息组合成一个数据集,用于训练/微调现有模型。
解决办法
自 2010 年左右以来,多模型数据库越来越受欢迎,为传统关系数据库无法满足的复杂需求提供了一个良好的全面解决方案。Redis 就是一个例子,在扩展到文档(JSON)、属性图、流和时间序列数据之前,它最初提供一个键值存储。具体参考大规模计算机视觉任务,我最*测试了各种多模型数据库,因此将在本文的剩余部分集中讨论这些结果,但请注意,有几个更多选项可用于不同的需求。
机器学习的 ArangoDB
ArangoDB 结合了文档存储和图形数据库,构建在 RocksDB 之上,因此默认情况下也提供了键值存储。水*扩展是通过将连接的数据彼此移得更*来实现的,以尽可能避免昂贵的跨服务器查询。这使得该解决方案在大规模下非常有效,同时实现了文档存储和图形数据库的所有优势。
文档存储
生产系统通常使用文档存储来保存与其用途相关的数据,作为传统关系数据库的替代方案,在传统关系数据库中,每一行都可以有许多属性,通常只有几个索引或关联的属性。生产系统通常一次处理管道中单个对象的更新/插入,因此关系数据库和文档存储在这个用例中同样有效。
GraphDB
图形数据库已经存在很长时间了,由于互联网的需求,它们的使用在 90 年代开始起飞。图在建模复杂的连接方面非常有效,在这种情况下,查询速度大大低于通常以关系格式进行的大量索引查找。这正是我们在 MLOps 中理解复杂数据格式所需要的,特别是那些通过多个系统,或者使用复杂 ML 管道的数据格式。
在 ArangoDB 中,文档存储在集合中。与任何文档存储一样,每个文档都由 _key 属性索引。然后,通过称为边缘集合的特殊类型的集合来添加图形功能,该集合索引 _from 和 _to 属性,引用同一数据库中的 collection/_key。这提供了上面两个团队给出的需求。
在实践中
统一存储管道示例
不像以前那样通过管道传递数据,我们可以创建一个统一的数据存储,在一个地方保存所有这些信息。然后,流程中的每个服务根据请求中传递的唯一标识符读取它需要的信息,然后将管道中稍后可能需要的更新写入统一存储。这既给了工程师一个整体系统的视角,也给了研究人员一个单一的数据库来创建数据集。
在多模型数据库出现之前,很难在一个解决方案中从两个角度利用这些信息。对于希望创建有趣的数据集来训练模型的研究人员来说,图形数据库技术使链接这些信息变得更快更容易。从他们的角度来看,人脉更重要。对于希望能够从性能指标方面理解和分析整个管道的工程师来说,元数据文档的唯一键允许在一个地方对其进行跟踪。从他们的角度来看,文档元数据和索引更重要。
表演
但是,它在这些任务中的表现如何呢?我们先把它比作一个图形数据库。在这个项目中,我主要是将 ArangoDB 与 DGraph 进行比较,因为 DGraph 似乎也满足了项目的许多伸缩性和复杂性需求。
以下摘自阿兰戈自己的性能页面,针对两个标准 graph db 性能指标:
来自 ArangoDB 的 2018 年绩效指标。
注意:在我看来,忽略(ArangoDB MMFiles)指标是值得的,因为根据我的经验,我看不出你为什么不想使用 RocksDB,这是默认设置。我认为这些只是遗留下来的,因为这些是旧的图像。
邻居的邻居+一个过滤器,计数不同。我还尝试了 2“跳”(即邻居的邻居)、3 和 4“跳)的测试,并在不同阶段添加了过滤器。
图表性能指标
现在让我们看看文档收集性能指标。一般来说,考虑到 ArangoDB 作为 GraphDB 的性能,您会认为它的性能相当差,但是上面显示了 ArangoDB 如何在读/写速度上大大优于 MongoDB,并且经常优于关系型竞争对手。聚合,据说是关系格式的优势之一,在 ArangoDB 上也有类似或更好的性能。
我的测试包括大型集合中的单一索引查找,以及写同步和聚合。
关系性能度量
正如您所看到的,这是一组令人印象深刻的结果,因为在这些标准关系测试中,ArangoDB 的表现几乎与 MySQL (一个纯关系数据库)一样好,并且大大优于仅文档和图形存储。没有对其他关系格式进行比较,因为它们不能提供上面要求的全部功能,但是作为这些测试的一部分,与现有的 MySQL 实例进行比较对于基准测试是有用的。
缩放比例
正如我前面提到的,缩放一直是机器学习数据库中的一个问题。当数据通常是最重要的业务资产时,您必须确保您为存储数据而构建的解决方案在今年是正确的,并且根据预计的规模在接下来的几年是正确的。随着业务(希望数据)呈指数级增长,重复替换解决方案是一个成本高昂的过程,这也会影响构建解决方案的工程师以及必须不断构建新工具的研究人员的士气。
考虑
当考虑一个数据库解决方案时,一定要确保你已经考虑到了没有大规模测试潜在产品或解决方案的机会成本。同时,现实一点。未来 2 年内达到该规模的实际概率有多大?如果它相当低,那么最好考虑一个更容易用于当前规模的解决方案,而不是使用不适合当前业务规模的工具。这是一个困难的*衡行为,每个商业案例都不一样,所以要注意选择,要有测试和数据来支持你的决策。
结论
ArangoDB 显然是基于机器学习的管道背后的单一统一数据存储的最佳选择。多模型方法允许具有完全不同的需求和观点的用户使用和共享信息和知识库,试图将这些分离的团队聚集在一起。
从机器学习的角度来看,选择 ArangoDB 而不是其他选项的主要原因是不同团队复杂需求的易用性。ArangoDB 必须在多个用例中表现得令人难以置信的好,才能让我相信它是正确的选择,但它做到了这一点,并给出了一个我认为比任何其他选项都更好地涵盖了各种用例的解决方案。
我认为,多模型数据库在未来几年将对复杂和分散的数据集起到至关重要的作用,这是真正广泛采用 MLOps 所需要的。如果这将是一个广泛采用的工具,那么满足业务所有部分的需求并使每个团队能够实现其目标的功能将是成功的关键,同时也将开始弥合团队之间的差距并打破阻止 MLOps 的壁垒。
统一远程和本地 AzureML 环境
微软和 Python 机器学习:现代爱情故事,第 2 部分,共 2 部分
微软 Azure 正在征服我们作为人工智能从业者的心,并通过对 PyTorch、Tensorflow 和 Scikit-learn 等开源框架的支持来吸引我们。在这里,我们围绕 MS 给我们的工具建立了一个工作流程,由我们来决定我们是否受到诱惑。在第 1 部分中,我们在我们的 AzureML 远程计算目标上启动了一个 Python 脚本,而我们的 VSCode devcontainer 对此毫不在意。这也意味着远程环境的大部分配置不在我们的掌控之中。这里我们将深入 AzureML 环境来配置 PyTorch GPU 工作负载。最后,我们努力统一开发和远程环境,使我们在 AzureML 上更有效地开发和测试 AI 模型。
1 计算目标环境
1.1 Docker 图像管理
在下图中,我们看到 Python 工作负载在计算目标上的远程 docker 容器中运行。如果我们创建一个 CPU 集群,并且除了指向计算目标的 RunConfiguration 之外没有指定任何东西(参见第 1 部分,那么 AzureML 将在第一次运行时选择一个 CPU 基础 docker 映像(https://github.com/Azure/AzureML-Containers)。
在基础映像之上,创建了 conda 环境,并安装了默认的 python 依赖项,以创建支持 AzureML-SDK 的 Python 运行时。在构建映像之后,它被推送到链接到 AzureML 工作空间的 docker 存储库,其名称中有一个随机的 UUID(模式:azureml/azureml_
使用 VSCode devcontainer 的 Azure-ml 工作流(图片由作者提供)
1.2 环境等级
Environment 类是各种配置环境变量、编程语言、计算框架和 Docker 的类的组合。该环境由 Docker、Python、R 和 Spark 部分组成。例如,PythonSection 公开了多种方法来从现有的 conda 环境、conda 规范文件、pip 需求文件或以编程方式添加的依赖项创建 Python 环境。AzureML 环境可以注册到工作区供以后使用。目标是用 azure ml(how-to-environments)创建可重用的培训和部署环境。在我们的 DIY MLOps 中,这将类似于压缩你的整个 conda virtualenv,并将其上传到 artifactory 以供以后部署。Python SDK 中 MLOps API 的细节令人印象深刻,它们最*向环境驱动配置的转变为用户提供了透明性和灵活性。
2 使用 AzureML 在 GPU 上训练 PyTorch
在接下来的章节中,我们将使用 PyTorch 创建一个适合 GPU 计算的运行时环境。我们参考第 1 部分来创建计算集群。与第 1 部分的唯一变化是,我们使用“NC6_standard”虚拟机大小来获得一个带有一个 GPU 的虚拟机。选择一个带有 GPU 的虚拟机运行我们的带有 nvidea_docker 的容器,而不是标准的 docker 引擎。这种自动检测相当新,我发现它是针对单个计算实例的( msdn-post )。对于计算集群,GPU 自动检测运行良好。
2.1 使用 GPU 支持创建运行时 PyTorch 环境
出于我们的目的,我们在 Python 3.6 上运行 PyTorch >=1.4.0 和 Cuda 10.1。作为我们的基本 docker 映像,我们采用官方 AzureML 映像,基于 Ubuntu 18.04 ( gpu-images ),包含原生 gpu 库和其他框架。
为了在我们的远程 Conda 环境中管理 Python 包,我们操作一个 CondaDependencies 对象,并将其附加到我们的环境对象。我们在这个对象上调用“add_conda_package ”,它有一个包名和可选的版本和内部版本。在 PyTorch 的例子中,我们需要一个特定的构建来启用 GPU 后端。我们还需要添加“py torch”conda 通道和“add_channel”来安装这些包。虽然您可能更喜欢使用 conda 或 pip 需求文件,但我喜欢这种依赖管理的编程方法。
**# settings.py
from** **azureml.core** **import** Environment
**from** **azureml.core.conda_dependencies** **import** CondaDependenciesPYTORCH_VERSION = "1.4.0"
TORCH_ENVIRONMENT = Environment(name="torch-env")
TORCH_ENVIRONMENT.docker.base_image = (
"mcr.microsoft.com/azureml/base-gpu:openmpi3.1.2-cuda10.1-cudnn7-ubuntu18.04"
)
TORCH_ENVIRONMENT.docker.enabled = **True** *# forced to True on AMLcompute targets* TORCH_ENVIRONMENT.docker.gpu_support = **True** *# deprecated as it is auto-detected* torch_conda_dep = CondaDependencies()
torch_conda_dep.add_channel("pytorch")
torch_conda_dep.add_conda_package(
f"pytorch==**{**PYTORCH_VERSION**}**=py3.6_cuda10.1.243_cudnn7.6.3_0"
)
torch_conda_dep.add_conda_package("cudatoolkit==10.1.243")# add the conda dependencies to the environment
TORCH_ENVIRONMENT.python.conda_dependencies = torch_conda_dep
2.2 在环境中训练评估员
2.2.1 评估者类别
为了训练我们的 PyTorch 模型,我们使用了估计器类,它是机器学习模型的 AzureML 抽象。估计器可以提交给 AzureML 实验,该实验在计算目标上运行它。请注意,自 2019–2020 年起,PyTorch、Tensorflow 和 SKLearn 的预配置估值器已被弃用。这些对于配置运行时环境来说太含蓄了,这使得它们不灵活,难以维护,并且对用户来说很神奇。建议所有新用户将 vanilla Estimator 类与 Environment 类结合使用。
2.2.2 将评估者提交给实验
我们创建一个评估器,并把它传递给我们之前定义的环境。使用第 1 部分中所示的功能检索计算目标。我们创建(或检索)一个名为“estimator-test”的实验,并向它提交我们的评估器进行运行。
**from** **azureml.core** **import** Experiment, Workspace
**from** **azureml.train.estimator** **import** Estimator**import** **settings
from** **tools** **import** get_computeworkspace = Workspace.from_config()
script_params = {
"--epochs": 10,
}
estimator = Estimator(
source_directory=settings.SOURCE_DIRECTORY,
compute_target=get_compute(workspace, settings.CLUSTER_NAME),
entry_script="relative/path/to/train_model.py",
script_param=script_params,
environment_definition=settings.TORCH_ENVIRONMENT,
)
experiment = Experiment(workspace=workspace, name="estimator-test")
run = experiment.submit(estimator)
3 .本地开发与远程培训相结合
在我们的工作流程中,Docker 容器同时在本地和远程运行。Docker 的承诺之一是统一开发和生产之间的运行时环境。按照这些思路,我们希望针对远程环境在本地调试我们的训练代码。换句话说,我们希望使用相同的 Docker 映像进行本地开发和模型的远程训练。
弗朗切斯科·温加罗摄影
3.1 基于 AzureML 生成的图像的 VScode devcontainer
VSCode devcontainer 需要两个文件;一个 Dockerfile 和一个 devcontainer.json。devcontainer/"文件夹。Dockerfile 描述了我们的 Docker 映像,我们从它构造一个容器。devcontainer.json 文件特定于 VSCode,并配置与 Docker 容器的集成,如 Python 解释器路径、林挺可执行文件路径、VSCode 扩展和绑定挂载。
3.1.1 从 AzureML 映像派生的 Dockerfile
在我们的 Dockerfile(见下文)中,我们继承了 AzureML ContainerRepository 中的基本映像。我们可以通过用我们的 AzureML docker 注册中心对我们的本地 Docker 进行认证,在本地获取 AzureML 构建的 Docker 映像,并将其推送到我们的工作区。VSCode 的 docker 和 azure 帐户扩展可以简化这种身份验证和拉取。
我们希望在容器中安装 Python dev 依赖项,如 black、pylint 和 pytest。我们将它们安装在 AzureML 用来运行代码的同一个 Python 环境中,尽管也可以有其他选择。AzureML 生成的 Conda 环境位于映像上的“/azureml-envs/”中。Conda env 的名称包含另一个随机 UUID,其模式为:“azureml_
FROM <DOCKER_LOGIN_SERVER>.azurecr.io/azureml/azureml_<UUID>:latest
LABEL maintainer="luuk@codebeez.nl"ARG DEFAULT_UTILS="\
pylint \
flake8 \
autopep8 \
pytest \
mypy \
pydocstyle"RUN /azureml-envs/azureml_<UUID>/bin/pip install --no-cache-dir ${DEFAULT_UTILS} \
&& apt-get update \
&& apt-get install -y \
vim \
git \
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
3.1.2 将 VSCode 与容器集成
json 定义了 VSCode 和 Docker 容器之间的集成。我们为 devcontainer 定义一个名称,后跟 Dockerfile 的路径(您也可以直接设置一个图像)。接下来是 VSCode 设置,带有我们的 Python 解释器和各种 devtools 的路径。我们完成了我们想要安装在 devcontainer 中的 VSCode 扩展。
{
"name": "unified-azureml",
"dockerFile": "Dockerfile",
// Set container specific VSCode settings
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/azureml-envs/azureml_<UUID>/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.linting.flake8Path": "/azureml-envs/azureml_<UUID>/bin flake8",
"python.linting.pycodestylePath": "/azureml-envs/azureml_<UUID>/bin pycodestyle",
"python.linting.pydocstylePath": "/azureml-envs/azureml_<UUID>/bin pydocstyle",
"python.linting.pylintPath": "/azureml-envs/azureml_<UUID>/bin pylint",
"python.testing.pytestPath": "/azureml-envs/azureml_<UUID>/bin pytest"
},
// Add the VSCode extensions you want installed
"extensions": [
"ms-python.python",
"ms-azure-devops.azure-pipelines",
],
}
3.1.3 自动化的潜在断点
当在基础映像之上以 Docker 存储库的名义创建 Conda env 时,AzureML 生成的两个随机 UUIDs 是在我们的 AzureML 环境的迭代中自动化该工作流的潜在突破点。AzureML 生成的 docker 库的名称可以在 AzureML studio 中运行的实验的“20_image_build_log.txt”日志文件中找到,可以在 portal.azure.com 上与 AzureML 工作区关联的 Docker 注册表的接口中找到,也可以通过使用其 API 找到。通过运行以下命令,可以获得映像中的 Conda 环境列表。
docker run <DOCKER_LOGIN_SERVER>.azurecr.io/azureml/azureml_<UUID> /bin/bash -c "conda env list"
3.2 摘要:AzureML 统一的工作流
使用 AzureML 环境类,我们基于支持 GPU 的 docker 映像定义了远程计算目标的运行时配置。这个环境被 AzureML 整合为一个定制的 docker 映像,并被推送到 docker 注册中心。我们使用这个定制映像来创建一个 VSCode devcontainer,以统一我们的开发和远程环境。这允许我们在本地测试远程运行的相同环境。感谢您的关注,我期待着写更多与 Python、机器学习和数据工程相关的话题。
最初发布于https://codebeez . nl。
单位长度缩放:连续特征缩放的终极?
奇妙的单位长度缩放会超越其他形式的缩放并在机器学习中取代它们吗?
当处理连续目标时,工程师可以使用很多很好的方法来提高训练精度。一些最受欢迎的选项包括限制数据以避免处理可能影响预测的异常值,或者移除在预测目标时不一致的不良特征。然而,在解决连续问题时,提高训练准确性的最常见、最简单的方法是首先缩放用于训练模型的特征。这是因为扩展特性带来的好处是开销很小,而且非常容易实现。通过简单地权衡这样做的好处,很容易理解为什么这么多人选择利用特征缩放器来解决他们的持续问题:
- 缩放器相对容易实现。
- 根据不同的数据和模型,你会发现使用一个定标器就能显著提高精度。
- 缩放器有助于使数据集中的值彼此更接*,消除异常值,并使数据更容易为您的模型读取。
- 可以将缩放器放入管线中,管线会自动为您缩放输入要素。
通常,每当我们讨论连续定标器时,我们都在讨论使用正态分布。也称为标准缩放器,这是一种相对简单而有效的生成简化数据的方法,通过公式传递样本,统计人员可以更好地理解这些数据。虽然标准定标器肯定是机器学习中使用最多的,但重要的是不要忘记,有几种不同的定标器在统计世界中都有它们的位置。缩放器列表中的其他缩放器包括任意缩放器和*均归一化缩放器。虽然这些都是缩放器的很好的例子,但它们通常不用于提高机器学习模型的准确性。
有这么多优秀的预处理方法、编码器和各种图像处理选项,几乎所有连续解决方案最终都是用完全相同的标准定标器产生的,这似乎有点奇怪。如果有一些定标器可以用在数据集上,那么还有其他值得投资的吗?有没有另一种缩放要素的方法可以进一步提高精度?
单位长度定标器
科学家、数据迷和机器学习爱好者;我给你介绍
单位长度缩放器。
单位长度缩放器是可以在机器学习中使用的另一个特征缩放器。虽然典型的重新标度器、任意重新标度器和均值规格化器需要向标准化倒退,但单位长度标度器在机器学习世界中独树一帜。虽然它可能不会像标准缩放器那样经常使用,但今天我想研究一下为什么会这样,以及我们是否应该更多地使用它。
单位长度缩放器缩放要素的矢量,使所述矢量的长度为 1。也就是说,典型的应用将包括欧几里德距离的使用。然而,在某些应用中,使用 L 下标 1 范数可能更实际。如果在接下来的学习步骤中,标量度量用作距离度量,这一点尤其重要。
可用库
一项技术成功与否的一个重要原因是它的实现。不幸的是,单位长度缩放器的实现并不常见,因此 Sklearn 令人惊讶地没有包含这样的实现。Sklearn 模块中的大多数(如果不是全部的话)特征定标器是方差、均值或标准定标器——它们在某种程度上是相同的,但略有不同。这些观察缺乏使用向量距离的固有不同的计算方法,而不是关于数据的信息统计。
正如你可能已经预料到的那样,事情并没有变得更好。在所有像 MLDataUtils 和 scaler 这样的预处理库中,甚至没有一点提到单位长度的 scaler。然而,对于基于 Julia 的科学家来说,幸运的是,Lathe 0.1.2 在 Lathe.preprocess 中实现了这个缩放器。然而,除了这个特定的实例,如果您想要使用这个缩放器,那么您很可能要从头开始。
自己创建缩放器
为了实现缩放器,我们首先需要创建一个函数来计算一个向量的欧几里德距离。为此,你只需取两个数组的范数,然后将它们相减。我将使用 Julia 的线性代数软件包中的 norm 方法来完成这项任务:
using LinearAlgebraeuclidian(a, b) = norm(a-b)
由于朱莉娅惊人的语法,我能够使用语法表达式。在 Python 中,你可以使用 numpy.linalg.norm 做同样的事情。
import numpy as npdef euclidian(a, b):
return np.linalg.norm(a-b)
继续并遵循语法和对象组合的车床标准,我创建了一个小函数来处理我们类型的创建。
function UnitLengthScaler(data)
()->()
end
我们需要添加到这个函数中的第一件事是数组的 0。由于这个函数中的数组输入是一维的,并且是典型的常规列表而不是矩阵,我们可以通过编写 for 循环来实现这一点。
zeroes = [i = 0 for i in data]
接下来,我们将把零和数据插入欧几里德长度函数,如下所示:
euclidlen = euclidian(zeroes, data)
最后,单位长度的公式是
x^i = x^i / ||x||
所以我们将它插入到 for 循环中,就像这样:
predict() = [i = i / euclidlen for i in data]
现在我们将把这个方法封装到一个对象中,得到一个完整的单位长度缩放器。
function UnitLengthScaler(data)
zeroes = [i = 0 for i in data]
euclidlen = euclidian(zeroes, data)
predict() = [i = i / euclidlen for i in data]
()->(predict;euclidlen)
end
结论
要素缩放是数据科学中经常被忽略的重要内容之一,因为它在大多数情况下都是相对基础的。通常情况下,很容易陷入例行公事中,标准地衡量一切,继续你的一天。幸运的是,像该领域的大多数事情一样,甚至特征缩放也可以被开发、实验和改进。我已经测试了这个定标器一点点,但只有非常复杂的结果。有时会失去精度,偶尔会导致精度略高于我可能得到的标准定标器。据我所知,这似乎很偶然。不管怎样,我认为这的确是一件很酷的事情,应该让更多的人了解它!
数据科学家的单元测试
机器学习
使用 Pytest 提高管道的稳定性
随着数据科学在大型组织中变得越来越重要,对代码进行适当的测试的需求慢慢变得越来越集成到数据科学家的技能组合中。
假设您正在创建一个预测组织中客户流失的渠道。在部署您的解决方案几个月后,有一些新的变量可能会提高它的性能。
不幸的是,在添加了那些变量之后,代码突然停止工作了!您不熟悉错误消息,并且您很难找到自己的错误。
这就是测试,特别是单元测试的用武之地!
为特定模块编写测试可以提高代码的稳定性,并使错误更容易被发现。尤其是在处理大型项目时,进行适当的测试是基本的需求。
没有某种形式的测试,任何数据解决方案都是不完整的
这篇文章将关注一个小的,但是非常重要的,也可以说是测试的基础,即单元测试。下面,我将详细讨论为什么测试是必要的,什么是单元测试,以及如何将它们集成到您的数据科学项目中。
1.你为什么要测试你的代码?
虽然这看起来是显而易见的,但实际上测试代码有很多原因:
- 防止意外输出
- 简化代码更新
- 提高开发代码的整体效率
- 有助于检测边缘情况
- 最重要的是防止你将任何破损的代码投入生产!
这只是我的一个想法!
即使对于那些编写产品代码的人,我也建议他们至少为代码中最重要的模块编写测试。
如果你运行一个深度学习管道,但在 3 个小时后,你本可以轻松测试的东西失败了,那该怎么办?
注意:我完全可以想象不想为只花了你两天时间编写的一次性分析编写专用测试。没关系。由你来决定什么时候测试是有帮助的。最重要的是要认识到,他们可以节省你很多工作。
2.单元测试
单元测试是一种软件测试方法,它检查哪些特定的单个代码单元适合使用。例如,如果您想在 python 中测试sum
函数,您可以编写以下测试:
assert sum([1, 2, 3]) == 6
我们知道 1+2+3=6,所以应该没什么问题就通过了。
我们可以通过创建一个定制的 sum 函数来扩展这个例子,并对元组和列表进行测试:
当-1+2+3 不等于 6 时,test_new_sum_tuple
的输出将是一个断言错误。
单元测试帮助你在许多不同的环境下测试代码。然而,有一件重要的事情需要记住:
单元测试并不完美,几乎不可能达到 100%的代码覆盖率
单元测试是捕捉 bug 的好方法,但是不会捕捉到所有的东西,因为测试容易出现与你要测试的代码相同的逻辑错误。
理想情况下,您会希望包括集成测试、代码审查、正式验证等。但是这超出了本文的范围。
3.Pytest
上面例子的问题是,它将在第一次面对AssertionError
时停止运行。理想情况下,我们希望看到所有通过或失败的测试的概述。
这就是测试跑步者的用武之地,比如 Pytest 。Pytest 是一个很好的工具,可以根据您定义的测试创建广泛的诊断。
我们从安装 Pytest 开始:
pip install pytest
这样做之后,创建一个test_new_sum.py
文件,并用下面的代码填充它:
最后,将cd
放入存放test_new_sum.py
的文件夹,然后简单地运行pytest -v
。结果应该是这样的:
你可以在上面的图片中看到,它显示了哪些测试通过了,哪些测试失败了。
令人惊奇的是 Pytest 向您展示了预期的值以及它失败的地方。这可以让你很快看到哪里出了问题!
4.数据科学家的单元测试
为了理解我们如何将 Pytest 用于数据科学解决方案,我将通过几个例子来说明。以下数据用于示例:
示例数据
在这些数据中,我们有一个目标类和几个可以用作预测器的特征。
基本用法
让我们从几个简单的预处理函数开始。我们想知道每个类的每个特征的*均值。为此,我们创建了以下基本函数,aggregate_mean
:
太好了!我们放入一个数据框和列,它应该会产生一个字典,其中包含每个类的*均值。
为了测试这一点,我们编写了下面的test_aggregate_mean.py
文件:
当我们运行pytest -v
时,它应该不会给出错误!
用参数表示
使用参数化运行多个测试用例
如果我们想要测试许多不同的场景,那么创建一个测试的许多副本会很麻烦。为了防止这种情况,我们可以使用 Pytest 的参数化函数。
参数化通过允许我们测试多个场景来扩展测试功能。我们简单地添加参数化装饰器并陈述不同的场景。
例如,如果我们想测试功能 1 和 3 的 aggregate_mean 函数,我们采用如下代码:
运行pytest -v
之后,特性 3 的结果似乎不是我们所期望的。原来,我们之前看到的 None 值其实是一个字符串!
如果我们没有进行测试,我们自己可能不会发现这个错误。
固定装置
用 Fixtures 防止单元测试中的重复代码
当创建这些测试用例时,我们通常希望在每个测试用例之前运行一些代码。我们创建fixture来为我们的测试建立基线代码,而不是在每个测试中重复相同的代码。
它们通常用于初始化数据库连接、加载数据或实例化类。
使用前面的例子,我们想把load_data()
变成一个夹具。我们把它的名字改为data()
,以便更好地代表这个夹具。然后,@pytest.fixture(scope='module')
作为装饰器被添加到函数中。最后,我们将 fixture 作为参数添加到单元测试中:
如你所见,没有必要将data()
设置为变量,因为它会自动调用并存储在输入参数中。
Fixtures 是增加可读性和减少测试函数中任何错误的好方法。
嘲弄的
我在你的管道中修改代码,通过模仿来加速测试
在许多数据驱动的解决方案中,您将需要处理大型文件,这会极大地降低您的管道速度。为这些代码创建测试是困难的,因为快速测试是首选。
假设您用pd.read_csv
加载了一个 2GB 的 csv 文件,并且您想要测试管道的输出是否正确。有了 unittest.mock.patch ,我们可以用自己的输出替换pd.read_csv
的输出,用更小的数据测试流水线。
假设您已经为管道创建了以下代码库:
现在,为了测试这个管道是否像预期的那样工作,我们想要使用一个更小的数据集,这样我们就可以更准确地测试用例。为此,我们用迄今为止一直使用的较小数据集来修补pd.read_csv
:
当我们运行pytest -v
时,大的。csv 将不被加载,取而代之的是我们的小数据集。测试运行迅速,我们可以很容易地测试新的情况!
新闻报道
你用单元测试覆盖了你的大部分代码吗?
单元测试无论如何都不是一个完美的方法,但是知道你测试了多少代码是非常有用的。特别是当你有复杂的管道时,知道你的测试是否覆盖了你的大部分代码是很好的。
为了检查这一点,我建议你安装 Pytest-cov ,它是 Pytest 的一个扩展,向你显示你的代码有多少被测试覆盖。
简单地安装 Pytest-cov,如下所示:
pip install pytest-cov
完成后,运行以下测试:
pytest --cov=codebase
这意味着我们正在运行 Pytest,并检查有多少测试覆盖了 codebase.py。
幸运的是,我们的测试覆盖了codebase.py
中的所有代码!
如果我们注意到覆盖了任何行,那么该值将显示在**Miss**
下。
感谢您的阅读!
如果你像我一样,对人工智能、数据科学或心理学充满热情,请随时在 LinkedIn 上添加我,或者在 Twitter 上关注我。
本文中的所有示例和代码都可以在这里找到:
为数据驱动的解决方案创建单元测试的代码和说明相应的文章可以在这里找到:To…
github.com](https://github.com/MaartenGr/UnitTesting)
单元测试 MLflow 模型相关的业务逻辑
费伦茨·阿尔马西在 Unsplash 上的照片
培训、测试和部署
很多时候,模型的开发者不同于在他们的应用程序中使用模型的开发者。例如,开发人员创建一个获取输入图像并对其进行分类的 API 时,不会知道也不需要知道模型的内外。开发人员需要知道的只是从哪里加载模型,以及如何进行推理。MLflow 提供了非常好的 API 来实现这一点。
定制的 MLflow 模型
mlflow.pyfunc.PythonModel:表示一个通用 Python 模型,它评估输入并生成 API 兼容的输出。通过子类化,用户可以创建具有“python _ function”(“py func”)风格的定制 MLflow 模型,利用定制的推理逻辑和工件依赖性。
MLflow 允许用户创建定制的模型,除了他们本身支持的模型,比如 keras。该模型可以保存为 mlflow 模型,供以后使用。因此,不管您的生产模型类型是什么,都可以创建一个使用ml flow . py func . python model的示例模型来模拟它。
以下是 MLflow 文档中的 AddN 模型示例。在这里只实现了 init 和 predict 方法,我们将做同样的事情
**import** mlflow.pyfunc
*# Define the model class*
**class** **AddN**(mlflow**.**pyfunc**.**PythonModel):
**def** __init__(self, n):
self**.**n **=** n
**def** **predict**(self, context, model_input):
**return** model_input**.**apply(**lambda** column: column **+** self**.**n)
*# Construct and save the model*
model_path **=** "add_n_model"
add5_model **=** AddN(n**=**5)
mlflow**.**pyfunc**.**save_model(path**=**model_path, python_model**=**add5_model)
*# Load the model in `python_function` format*
loaded_model **=** mlflow**.**pyfunc**.**load_model(model_path)
*# Evaluate the model*
**import** pandas **as** pd
model_input **=** pd**.**DataFrame([range(10)])
model_output **=** loaded_model**.**predict(model_input)
**assert** model_output**.**equals(pd**.**DataFrame([range(5, 15)]))Source: [https://www.mlflow.org/docs/latest/models.html#example-creating-a-custom-add-n-model](https://www.mlflow.org/docs/latest/models.html#example-creating-a-custom-add-n-model)
额外使用案例
在进入实现之前,让我们激励一个用例。假设 Acne Inc .决定使用预测模型向其员工提供奖金。基于某些输入,该模型预测该员工的奖金应该是多少。
该模型的功能和输入超出了本文的范围。更有趣的是开发者如何使用这个模型。将该模型集成到 HR 系统中的开发者被提供该模型的位置,并被要求公开一个方法 amount ,该方法给出输入,输出奖金金额。开发者被告知模型是 mlflow 模型,可以使用提供的 API 进行预测。
我们的开发人员提出了一个简单的实现如上。现在的挑战是对这个实现进行单元测试,以确保它能够工作。理想情况下,单元测试应该是 CI/CD 兼容的,因此与其为测试训练模型,解决方案应该是轻量级的。输入 mlflow.pyfunc.PythonModel!
测试奖金模型
开发人员提出了一个可以由字典初始化的测试模型。字典基本上是给定输入的预定义结果。预测法就是这么做的。在这种情况下,假设 model_input 的每个元素都是支持哈希方法的类型,所以它可以是字典的一个键。
单元测试…耶!
现在最精彩的部分来了,开发者用一个测试奖金模型测试它创建的奖金类。
在 test_model 中,开发者定义了一个输出字典,用于初始化模型。然后,模型被保存在临时目录中的特定位置。用保存模型的路径初始化 Bonus 类,然后测试 amount 方法,它给出与输出字典相同的值。瞧啊。
结论
测试依赖于 MLflow 模型的业务逻辑非常简单。所有人需要做的就是定义一个测试模型并适当地保存/加载。这种机制在 spark udf 和 MLFlow 中都能很好地工作。
为没有时间测试的人准备的单元测试
如何提高代码质量和安心的快速指南
测试可能是一个大傻瓜,但是可靠的测试覆盖是成熟工程团队的标志。如果你能选择一件事来解决代码质量问题,单元测试是你能做的最好的投资。
什么是单元测试?
这是首先需要澄清的。测试有很多种类型:单元、回归、集成、端到端等。除了单元测试之外,大多数其他测试都处理组件之间的集成。这些组件可以是类、微服务或包。因为应用程序有不同的大小和复杂性,所以很难将测试归入其中的一个类别。但是单元测试是很容易定义的:单元测试应该测试项目中最小的部分。大多数时候,这意味着功能。
为了说明各种情况,我们将使用以下函数:
怎么写测试?
Python 有一个强大的单元测试包:“unittest”。还有其他几个有很多优点和缺点需要评估。但是请记住,我们是没有时间进行测试的人。使用 unittest 永远不会出错,所以我们将在示例中使用它。
python 中的单元测试是这样的:
这里唯一强制的事情是,您的测试需要是继承 TestCase 的类中的方法。您的所有测试都必须是名称以“test_”开头的方法。其他的都将被跳过。
从命令行,这就是你运行所有单元测试文件的方式:
python -m unittest discover
这些高级选项可以让您对正在执行的内容进行额外的控制:
#specify what modules to be executed
python -m unittest test_module1 test_module2#execute all tests inside a specific class
python -m unittest test_module.TestClass#cherry pick a test to be executed
python -m unittest test_module.TestClass.test_method
移除所有依赖关系
第一步是识别函数的所有依赖项。在我们的豚鼠函数中,我们可以确定 2 个外部端点。第一个是获取数据进行处理的地方,另一个是将结果发送到的地方。同样的逻辑也适用于正在读写的磁盘文件或数据库查询。这里不明显的依赖是“calculate_avg”。因为我们只是在测试我们的“单元”,所以我们也应该把其他的函数调用算作一个依赖
一旦确定了依赖关系,我们就应该模仿它们。主要原因是将我们的测试与我们无法控制的环境设置和外部服务隔离开来。这里有几个原因:
- 当这个测试的自动执行运行时,它们可以是离线的
- 与这种依赖关系的交互可能会导致整个系统发生不希望的变化(比如改变数据库中的数据)
- 您想要测试不同的场景,并且可能很难强迫这个依赖“失败”或者用不常见的数据测试执行
- calculate_avg 可能改变了它的实现(可能现在已经坏了),这可能会导致我们的测试失败。
- 因为如果你测试两个组件之间的集成,这将是一个集成测试而不是一个单元测试
怎么嘲讽?
Python 有一个强大的模仿库,可以帮助你模仿所有类型的依赖。我将在我所有的例子中使用 decorators,因为我发现这样更容易阅读测试。我发现模仿是一种更好的学习方式,所以我编写了一份常见模仿场景的备忘单。记得参考我们的豚鼠功能
修补依赖函数
在同一测试中修补多个依赖项。
这里的问题是,装饰器离方法声明越*,它就越早在参数列表中被传递。
@mock.patch("guinea_pig_module.requests.post")
@mock.patch("guinea_pig_module.requests.get")
@mock.patch("guinea_pig_module.calculate_avg")
def test_get_sum(self, mocked_avg, mocked_get, mocked_post):
pass
改写怪异
另一个常见的错误是在错误的地方模仿正确的函数。请注意“请求”中的“get”函数。重写“get”的正确方法是传递几内亚猪路径,然后传递“requests.get ”,就像它是在几内亚猪内部声明的一样。这不行:
@mock.patch("requests.get")
删除样板代码
到处添加这些装饰者会变得重复,事情也会变得混乱。一种选择是将装饰器一直添加到类声明的顶部,而不是方法声明。这样做的好处是,在类级别上进行的模拟会自动渗透到该类中的所有方法。所以要明智地使用它。
您还需要考虑方法签名中的这些新模仿。第一个参数总是在方法级和接下来的类级中声明的模拟。
用护栏修补
当您模仿一个函数时,默认情况下它返回一个 mock()对象。这个对象允许你做任何事情。这将起作用:
x = mocked_method()
x.fake_method()
x.not_real_method()
x.fooooo()
print(x.anything)
当它有意义的时候,你可能想要确保你以正确的方式使用被嘲笑的回答。
#this returns a mock object, but only with pre-existent methods
#and attributes from the class being specced@mock.patch("your_class_here", autospec=True)
试图访问原始类中不存在的元素将引发异常。
另一个用例可能是在对象内部修补一个特定的方法,而不模仿其他任何东西。这可以这样实现:
@mock.patch.object("your_class_here", "target_method_here")
副作用调用副作用
side_effect 的最后一个用例是引发异常。记住“return_value”有一个静态响应。因此,如果您想引发一个异常,您必须像这样做:
x.side_effect = RuntimeError("Bad")#this will raise an exception
x()#this will raise an exception in the 3rd call
x.side_effect = [1, 2, RuntimeError("Bad")]
覆盖面很重要
下一步是关注覆盖率。争取 100%的覆盖率。为了实现这一点,您可能需要为同一个函数编写几个测试。您将使用模拟技能来确保每个 if/else 和异常处理程序块都得到执行。回到我们的豚鼠功能,我们需要一些东西来达到 100%:
- 返回年龄小于 0 的记录
- resp.json()应返回非空响应。您需要循环来执行
- 抛出异常
“覆盖率”是一个非常有用的软件包,它可以帮助你识别仍然缺少测试的行。要使用它,您需要对前面的单元测试命令做一个小小的修改:
python -m unittest discover#becomes
coverage run -m unittest discover#to see the reports from the run
coverage report -m
这将会正常运行您的测试,但是也会跟踪您的项目中正在执行的每一行。最后,您将看到这样一个漂亮的报告:
Name Stmts Miss Cover Missing
-------------------------------------------------------
module1.py 20 4 80% 33-35, 39
-------------------------------------------------------
TOTAL 20 4 80%
利用这一点来跟踪任何被忽略的线。回到我们的豚鼠函数,我们的测试看起来像这样:
仅仅通过达到 100%的覆盖率,这些测试就发现了代码中的两个错误。
- Try 子句未捕获到请求超时异常。这导致了功能崩溃
- 第二个是在创建大型 Try 块时常见的错误。如果“resp.json()”抛出异常,“idx”永远不会初始化。这将导致在打印前一个异常的错误信息时发生异常。不好了。
一旦这些错误被修复,新函数看起来就像这样:
输入和输出
这个函数只返回一个布尔值,但是在某些情况下,您可能需要验证一个需要符合特定规范的对象。所以你的测试需要验证这一点。输入更棘手,从常见的疑点开始:
- 列表:空列表,1 个元素,少量元素,大列表
- 文件:空文件、无文件、小文件、大文件
- 整数和浮点数:负数?、零、少数正数、大数
- 字符串:空字符串,小字符串,大字符串
您需要考虑什么对您正在测试的功能有意义。对于豚鼠的功能来说,输入被传递给被模仿的依赖者。所以值是多少并不重要。如果你不确定,就按照通常嫌疑人名单。不要想太多,思考比写那些测试要花更多的时间。偏向“测试越多越好”的一边
结论
记住,单元测试就像一个房子警报系统。这是为了让你安心,它本身不应该是一个项目。你只是在确保从现在起 6 个月后所做的改变不会破坏你的功能行为。所以,花更多的时间确保你涵盖了一些广泛的领域,而不是非常具体的案例。利用模拟包中的“规格”。你刚刚写了你的函数,你知道如何使用它。设置好闹钟,去做你最喜欢做的事情。造东西!
统一者:图像和文本的结合
学习所有事物都可以使用的图像和文本的联合表示
图片由 Pixabay 的 Patricia Hébert 提供
多模态学习在我们的生活中无处不在。人类以不同的方式吸收内容,无论是通过图片(视觉),文本,口头解释(音频)等等。这些知识来源中的每一个都被称为一种模式。事实上,我们经常通过这些模式的组合来学习,给每个人带来独特的学习体验。麦古尔效应——当看到一个人说话(嘎-嘎)但听到不同的声音(巴-巴)时,观察者会感知到第三种声音(哒-哒)——是不同模态相互作用的一个主要例子。受此启发,机器学习研究人员也开始编译数据集和任务,这些数据集和任务需要模型理解多种模式才能成功。
由于早期的多模态任务是受麦克格尔克效应的启发,其中许多只涉及音频和图像。随着时间的推移,任务模式的种类和数量变得更加多样化。如今,有些任务使用图像和文本(视觉问答、视觉常识推理、图像-文本检索)或图像、音频和文本(视频情感分析、CMU-摩西)。单峰任务已经得到了很好的探索,可以从每种模式中提取良好的表征。例如,GloVe 和 Word2Vec 等单词嵌入可用于生成文本嵌入,而 ResNet50 和 VGG16 等卷积神经网络通常用于从图像中提取特征。出现的挑战是组合和/或使用这些单峰特征来完成多峰任务。
组合这些特征提出了两个主要挑战:如何和何时融合它们。Baltrusaitis 等人确定了组合单峰特征的两种主要技术:联合表示(使用神经网络或一些其他体系结构将多种模态组合到同一表示空间中)和协调表示(使用一些度量,如余弦距离或相关性,使单峰表示更接*,同时仍然在不同的表示空间中)。这两种融合技术也可以在模型架构中的不同点发生:早期融合(在开始时组合特征,并让完整的模型架构处理组合的特征)或晚期融合(通过它们自己的架构运行各个模态,并在结束时组合它们)。
许多当代模型专注于学习适用于特定任务的好的多模态表示。结果,这些模型不能被推广到相同模态的稍微不同的使用。然而,为每个任务制作模型需要花费大量的时间和精力。在这项工作中,微软 Dynamics 365 AI Research 的 UNITER 模型专注于学习图像和文本的可推广联合嵌入。
统一模型架构(自顶向下)
UNITER 在大量数据上使用自我监督学习,如 BERT,以确保学习到的嵌入是通用的。该模型采用图像-文本对,并用来自 4 个不同数据集的大约 950 万对图像-文本对进行训练。
对于每一对,它首先提取文本特征(通过将单词位置信息与单词片段标记化的句子相结合)和图像区域特征(通过将更快的 R-CNN 特征与位置信息相结合)。一旦特征被提取出来,它们就要经过几层变换器,最终学习联合嵌入,
在联合嵌入的自我监督训练中使用了 3 个主要目标。对于这些任务,输入是( w,v ),其中 v 是输入图像区域, w 是成对文本,θ是可训练参数。
蒙面语言建模(MLM)
MLM 以 15%的概率用【屏蔽】令牌屏蔽输入文本的每个单词。目标是基于句子的其余部分和配对图像来预测被屏蔽的单词。这是通过最小化负对数似然来实现的,如下式所示。
MLM 目标
其中 wm 为屏蔽字, w\m 为包围字。
图像文本匹配(ITM)
对于 ITM ,一个额外的【CLS】标记被附加到输入文本的开头,并且很像伯特的【CLS】标记,它捕获输入文本的上下文,它捕获输入图像-文本对的联合上下文。这个令牌然后通过一个计分器 sθ 来测量图像和文本的匹配程度。因为每个输入对可以是正的或负的,所以使用二进制交叉熵损失来优化它。
ITM 目标
其中 y 对于负对为 0,对于正对为 1。
掩蔽区域建模(MRM)
MRM 类似于 MLM ,除了没有用[MASK]标记替换区域,而是用零填充。然而,图像特征是连续的,使得不可能最大化对数似然。因此,为了实现 MRM ,本文提出了符合以下一般等式的 3 个不同的目标函数。
MRM 目标
其中 vm 是被遮罩的图像区域,而 v\m 是周围区域。
1.掩蔽区域特征回归
MRFR 尝试将每个被掩蔽的图像区域的最终联合表示 vm(i) 与其预训练的图像嵌入相匹配,从而保留传递到模型中的信息。
MRFR 目标
其中 hθ 将联合图像嵌入转换为与输入图像嵌入相同的大小。
2.掩蔽区域分类
统一器试图将 vm(i) 识别为 MRC 中的对象类型。更快的 R-CNN 以一定概率预测每个区域是一类物体的概率。这被用作基础真实标签,并且预测的交叉熵损失被优化。
MRC 目标
其中 K 是由更快的 R-CNN 预测的对象类别的数量,gθ( vm(i) )将区域转换成 K 长向量,c( vm(i) )是真实标签的一个热点向量。
3.利用 KL 散度的掩蔽区域分类(MRC-kl)
MRC-kl 与 MRC 相同,只是使用了更快的 R-CNN 对每个对象类型的预测概率,而不是固定的标签(0 或 1)。使用 KL 散度损失而不是交叉熵来匹配所有概率,而不仅仅是地面真实类,如下所示。
MRC-kl 目标
其中 K 是由更快的 R-CNN 预测的对象类别的数量,gθ( vm(i) )将区域转换成 K 长向量,c( vm(i) )是更快的 R-CNN 的类别概率。
该团队尝试了这些预训练任务的几种组合,发现使用除 MRC 之外的所有任务可以产生最佳的嵌入效果。
最佳 UNITER 嵌入针对大量涉及图像和文本的下游任务进行了微调。这些任务中的一些是视觉 QA (回答关于图像的问题)视觉推断(确定图像是否包含一个句子)和图像文本检索(检索一个给定的另一个)。这些任务都在几个基准上进行了测试,UNITER 能够在 13 个不同的基准上实现最先进的性能,真正展示了 UNITER embedding 的通用性。
这里有一个链接到我们的论文,如果你想了解更多关于 UNITER 的细节,点击这里查看更多我们的出版物和其他工作。
参考文献
- 塔达斯·巴尔特鲁·艾蒂斯,柴坦尼亚·阿胡贾,路易·菲利浦·莫伦西,多模态机器学习:综述与分类 (2018),IEEE 模式分析与机器智能汇刊 41,第 2 期 PP:423–443。
- 日元-陈春、李林杰、李成玉、艾哈迈德·埃尔·科利、费萨尔·艾哈迈德、哲干、于成、刘晶晶、统一者:学习普遍的图像-文字表述 (2019)、arXiv 预印本 arXiv:1909
Unity-ML 特工:玛雅冒险
Unity-ML 代理课程
训练一个特工在这种危险的环境下拿到金像。
本文是 的第三章全新免费课程关于深度强化学习与合一。 我们将创建使用 Unity 游戏引擎🕹.学习玩视频游戏的代理查看教学大纲 此处 。
如果之前没有学习过深度强化学习,需要查看免费课程tensor flow 深度强化学习。
在前两篇文章中,您学习了使用 ML-agent,并培训了两个 agent。第一个是能够FQ,第二个学会摧毁金字塔以获得金砖。是时候做些更难的事情了。
当我在考虑创建一个自定义环境时,我想起了印第安纳·琼斯中的一个著名场景,Indy 需要获得金像并避免大量的陷阱才能生存。
我在想:我的经纪人能像他一样好吗?剧透警告:的确是,正如你在这个回放视频中看到的!
这就是为什么在过去的两周里,我在 Unity 中开发了一个名为 Mayan Adventure 的开源强化学习环境。充满陷阱的危险模块化环境。为了训练代理人,我使用了一种叫做课程学习的学习策略。
所以今天,你将学习课程学习,你将训练我们的代理人获得金像并击败游戏。
所以让我们开始吧!
介绍玛雅冒险
Hello Indie
正如我所说,玛雅冒险是一个针对 ML-agent 的开源强化学习环境。
在这种环境下,你训练你的代理人(Indie) 击败每一个危险的陷阱,得到金像。
在学习过程中,我们的代理人启动以避免掉下地面并获得金像。随着他变得越来越好,我们增加了一些难度,感谢两个压力按钮,你的代理可以将自己变成岩石或木头。
他需要把自己变成木头才能通过木桥,否则,桥就会倒塌。
然后撞上一块岩石穿过火层。
奖励制度是:
在观察方面,我们的代理没有使用计算机视觉,而是使用光线感知张量。可以把它们想象成激光,如果它穿过一个物体,它就会探测出来。
我们用了其中的两个探测安全*台,去岩钮、去木钮、火、木桥、球门和宝石。第二条射线也能探测到空洞(为了训练我们的特工避免从环境中掉下来)。
此外,我们添加了 3 个观察值:一个布尔型通知代理是否是岩石,以及代理的 x 和 z 速度。
动作空间是离散的,具有:
引擎盖背后:是怎么做到的?
玛雅人的冒险始于这个原型版本。
原型
但这并不吸引人,为了创造环境,我们使用了不同的免费软件包:
- 3D 游戏套件:Unity 创造的梦幻环境,我使用了他们的岩石*台、按钮、植被和柱子元素。
- Unity 粒子包:我用它做消防系统。
- 创作工具包:拼图:我只用它来做 win 粒子烟火。
- 其他元素,木桥,动画,石头头,金像,软呢帽等都是在 Blender 中制作的。
我把这个项目设计得尽可能模块化。这意味着你将能够创造新的水*和新的障碍。此外,这仍然是一项进行中的工作,它意味着将进行纠正和改进,并将达到新的水*。
为了帮助我在代码方面创建环境,我在 Unity Learn 上学习了 Immersive Limit 制作的非常好的课程。
你现在可能会问的问题是我们将如何培训这个代理?
让我们训练独立音乐
为了培训 Indie,我们将使用 PPO 和课程学习策略。如果你不知道什么是 PPO,可以查看我的文章。
什么是课程学习?
课程学习是一种强化学习技术,可以在代理需要学习复杂任务时更好地训练它。
假设你 6 岁,我对你说我们要开始学习多元微积分。然后你会不知所措,无法做到。因为这对开始来说太难了:你会失败的。
一个更好的策略是先学习简单的数学,然后随着你对基础知识的掌握越来越好,再增加复杂性,最终能够完成这门高级课程。
所以你从算术课开始,当你很好的时候,你继续上代数课,然后是复代数课,然后是微积分课,最后是多变量微积分课。多亏了这个策略,你将能够在多变量微积分中取得成功。
这也是我们用来训练特工的策略。我们不是一次给我们的代理整个环境,而是随着他变得更好,通过增加难度来训练它。
为了在 ML-Agents 中做到这一点,我们需要指定我们的课程:一个 YAML 配置文件,它将根据一些度量(*均累积奖励)指定何时改变环境参数(在我们的例子中,增加级别)。将本课程视为我们的代理需要遵循的教学大纲。
课程是这样的:
- 第一关,代理人需要学会拿到金像,避免掉下*台。
- 然后,在第二关中,代理需要与物理按钮进行交互,并将自己变成木头,以通过这座大木桥。
- 在第三关,为了穿越这团火,代理人需要把自己变成岩石。
- 最后,在最后一关,代理人需要学会将自己变成木头,并通过一个更窄的桥而不会从桥上掉下来。
我们去拿这个金像吧!
你需要从 GitHub 库 下载项目,按照 doc 中定义的安装 流程进行。
如果不想训练金像,训练好的模型都在 Unity 项目 文件夹中的“玛雅冒险拯救模型”。
现在我们了解了玛雅冒险环境和课程学习是如何工作的,让我们来训练我们的代理人来打败这个游戏。
代码分为两个主要脚本:
- mayanadventurea。cs:控制关卡生成、代理和目标产卵。
- mayanadventurent . cs:它控制代理的移动,处理事件(当你在石头上和在木桥上时会发生什么等等),以及奖励系统。
首先,我们需要定义我们的课程,
为此,您需要转到您的 ML-Agents 文件夹/config/courses文件夹,创建一个名为 mayanAdventure 的新文件夹,并在其中创建您的mayanadventurelearning . YAML文件。
- 我们将用来衡量代理商的进度的关键指标是奖励。
- 然后,在阈值部分,我们定义进入下一个级别的*均奖励。例如,要达到第 1 级,我们的代理需要有 0.1 的*均累积奖励
- min_lesson_length 指定代理在改变等级之前必须完成的最少剧集数。这有助于我们避免我们的代理人在一集里运气好而改变级别太快的风险。
- 最后,我们定义参数,这里是级别号。
现在我们已经定义了我们的课程,我们可以配置我们的代理。我们的代理是一个预置的实例。因此,为了一次修改全部我们将直接修改预设。****
在预设 MayanAdventureArea 中,你需要检查训练是真的。我们创建这个 bool 是为了区分训练阶段和测试阶段,在测试阶段我们添加了一些事件,比如激活获胜面板、木桥摧毁动画以及当你获胜时展示烟火。
然后在预置中转到代理,先在行为参数中,如果有,移除模型。
之后,你需要来定义观察堆栈向量。这将取决于您是否使用循环存储器(将在教练配置中定义)如果不使用,您应该堆叠 4 到 6 帧。如果有,只有 3 个。
重要的是,矢量观察和光线感知观察具有相同的堆栈编号。
现在我们可以定义超参数。这是我写的给我最好结果的配置。
最后不要忘记关闭主摄像头,这里只是为了回放的目的。
我们现在准备训练。您需要打开您的终端,进入 ml-agents-master 所在的位置,键入以下内容:
mlagents-learn ./config/trainer_config.yaml --curriculum=config/curricula/mayanAdventure/MayanAdventureLearning.yaml --run-id TheMayanAdventure_beta_train --train
在这里,我们定义了:
- 其中培训师 _ 配置为:。/config/trainer_config.yaml
- 我们的课程:—课程=配置/课程/mayanAdventure/mayanadventurelearning . YAML
- 此培训的 id:—run-id themayandventure _ beta _ train
- 别忘了火车旗。
它会要求你运行 Unity 场景,
按下编辑器顶部的▶️按钮。
您可以使用以下命令启动 Tensorboard 来监控您的训练:
tensorboard — logdir=summaries
我的结果
在获得好的结果之前,我已经进行了大约 20 次训练,以便找到一组好的超参数。
我给你两个训练有素的保存模型,第一个有循环记忆,另一个没有。培训花了我大约 2 个小时,在 30 个并行环境中,只有一个 MacBook Pro 英特尔酷睿 i5 处理器。
我们看到两个代理人有着完全相同的结果,没有记忆的训练要好得多。这是我用来录像的。
重播
既然你已经训练了特工。您需要将包含在ml-agents-master/Models中的保存模型文件移动到 Unity 项目的Mayan Adventure 保存模型中。
然后,您需要停用除 MayanAdventureArea (1)之外的 MayanAdventureArea 的所有实例。
事实上,正如我们在经典的深度强化学习中所做的那样,当我们启动一个游戏的多个实例(例如 128 个并行环境)时,我们在此复制并粘贴代理,以便有更多不同的状态。但是重播的时候我们只需要一个。
还有别忘了激活主摄像头。
现在,你需要回到mayanadventurea 预设并取消选择 Training。
最后在代理行为参数中,拖动模型文件到模型占位符。
然后,按下编辑器顶部的▶️按钮,瞧!
如果你想记录你的结果,你只需要进入窗口>常规>记录器>记录器窗口,点击开始记录这些参数:
接下来的步骤
玛雅冒险是一个进行中的项目,它意味着修正和改进将会完成,新的水*将会出现。下面是我们将要采取的一些后续步骤。
光线投射可能不够:我们需要给他视觉能力
我们在培训中发现,我们的代理很优秀,但有些挑战绝对需要远见。
vision 的“问题”是它会以指数方式增加国家的规模。意味着下一个版本只会在 GPU 上训练,而不是 CPU。
这个想法可能是像 Unity 的 Gridworld 例子中一样,有一个环境的正交上视图作为输入。
来源: ML-Agents 文档
行上的新级别和定时事件
因为玛雅环境是一个 RL 研究环境,所以我们想增加更复杂的关卡来训练我们的代理学习长期策略。
因此,除了视觉版本的工作,我们目前正在增加更复杂的水*。如滚球陷阱。
而且还有一些定时事件,比如每 3 秒开关一次火候。
增加了级别生成的随机性
目前,我们的生成器总是输出相同的级别顺序。我们希望通过在关卡生成过程中增加一些随机性来改善这一点。
今天到此为止,
你刚刚训练了独立游戏,击败了所有的陷阱,到达了金像。你也学到了课程学习。太牛逼了!
既然我们有了好的结果,我们可以尝试一些实验。记住,最好的学习方法是通过实验变得活跃。所以你要试着做一些假设,并验证它们。
下次见!
如果你有任何想法,评论,问题,欢迎在下面评论或者发邮件给我:hello@simoninithomas.com,或者发微博给我。
不断学习,保持牛逼!
单变量和多变量高斯分布:直观清晰的理解
约书亚·富勒在 Unsplash 上拍摄的照片
详细的高斯分布及其与均值、标准差和方差的关系
高斯分布是统计学中最重要的概率分布,在机器学习中也很重要。因为许多自然现象,如人口的身高、血压、鞋码、考试成绩等教育指标,以及自然界许多更重要的方面,都倾向于遵循高斯分布。
我敢肯定,你听说过这个术语,在某种程度上也知道它。如果没有,不要担心。这篇文章会解释清楚。我在 Coursera 的吴恩达教授的机器学习课程中发现了一些令人惊叹的视觉效果。他知道如何把一个话题分成小块,让它变得更简单,并详细解释它。
他使用了一些视觉效果,使得理解高斯分布及其与均值、标准差和方差等相关参数的关系变得非常容易。
在这篇文章中,我从他的课程中截取了一些图像,并在这里用来详细解释高斯分布。
高斯分布
高斯分布是正态分布的同义词。它们是一回事。比方说,S 是一组随机值,其概率分布如下图所示。
这是一条钟形曲线。如果概率分布图形成一个钟形曲线,如上图所示,并且样本的均值、中值和众数相同,则该分布称为正态分布或高斯分布。
高斯分布由两个参数参数化:
a.*均和
b.方差
*均值μ是分布的中心,曲线的宽度是数据系列的标准差,表示为 sigma。
因此,高斯密度在μ点或均值点最高,从均值开始,高斯密度越来越低。
以下是高斯分布的公式:
该等式的左侧读作由 mu 和 sigma *方参数化的 x 的概率。这是钟形曲线的公式,其中 sigma *方称为方差。
高斯分布如何与“均值”和标准差相关
在本节中,我将展示一些图片,让您清楚地了解 mu 和 sigma 与钟形曲线的关系。我将展示三张图片,其中 mu 将固定在零,sigma 将有所不同。
注意曲线的形状和范围是如何随着不同的σ而变化的。
图:1
这是一组 mu 等于 0,sigma 为 1 的随机数的概率分布。
在此图中,μ为 0,这意味着最高概率密度约为 0,sigma 为 1。表示曲线的宽度为 1。
注意,曲线的高度大约是 0.5,范围是-4 到 4(看 x 轴)。方差 sigma *方为 1。
图:2
这是另一组随机数,阿木为 0,sigma 为 0.5。
因为 mu 是 0,像前面的图一样,最高概率密度在 0 左右,sigma 是 0.5。所以,曲线的宽度为 0.5 。方差西格玛*方变为 0.25。
由于曲线的宽度是前面曲线的一半,高度变成了两倍。范围更改为-2 到 2 (x 轴),是上一张图片的一半。
图:3
在这张图中,与前两张图一样,σ为 2,μ为 0。
将其与图 1 进行比较,图 1 中 sigma 为 1。这次高度变成了图 1 的一半。因为宽度随着西格玛的加倍而加倍。
方差 sigma *方是 4 ,比图 1 大四倍。看 x 轴的范围,是-8 到 8。
图:4
这个例子和前面的三个例子有点不同。
这里,我们将 mu 改为 3,sigma 为 0.5,如图 2 所示。因此,曲线的形状与图 2 完全相同,但中心移到了 3 。现在最高密度在 3 左右。
看看上面的四条曲线。它随着σ值的不同而改变形状,但曲线的面积保持不变。
概率分布的一个重要性质是,曲线下的面积积分为 1。
参数估计
假设,我们有一系列数据。如何估计 mu(均值)、sigma(标准差)和 sigma *方(方差)?
计算 mu 很简单。简直就是一般。将所有数据求和,然后除以数据总数。
这里,xi 是数据集中的单个值,m 是数据的总数。
方差(sigma square)的公式为:
标准差σ就是方差的*方根。
多元高斯分布
如果我们有两组数据,而不是一组数据,我们需要一个多元高斯分布。假设我们有两组数据;x1 和 x2。
分别对 p(x1)和 p(x2)建模可能不是理解这两个数据集的组合效果的好主意。在这种情况下,您可能希望仅组合数据集和模型 p(x)。
下面是计算多元高斯分布概率的公式,
这个等式中的求和符号可能会引起混淆!它是 sigma 的行列式,实际上是 sigma 的 n×n 矩阵。
多元高斯分布的可视化表示
在本节中,我们将看到多元高斯分布的直观表示,以及曲线形状如何随μ、σ和变量之间的相关性而变化。
以标准正态分布开始
该图表示多元高斯分布的概率分布,其中 x1 和 x2 的μ都为零。
请不要被这里的求和符号搞糊涂了。这是一个包含 sigma 值作为对角线的单位矩阵。对角线上的 1 是 x1 和 x2 的σ。对角线外的零表示 x1 和 x2 之间的相关性。因此,在这种情况下,x1 和 x2 不相关。
这里的图片很简单。在 x1 和 x2 方向上,最高概率密度为 0,因为 mu 为零。
中间的暗红色区域显示最高概率密度区域。在较浅的红色、黄色、绿色和青色区域,概率密度不断降低。它是深蓝色区域中最低的。
改变标准偏差适马
图 5
现在,让我们看看如果 sigma 值稍微缩小会发生什么。x1 和 x2 都是 0.6。
正如我之前提到的,曲线下的面积必须积分为 1。因此,当标准差σ缩小时,范围也会缩小。同时,曲线的高度变高以调整面积。
图 6
相反,当σ较大时,可变性变得更宽。所以,曲线的高度变低了。
请看图 6,曲线高度和范围的变化几乎与我之前在单变量高斯分布中展示的图相似。
x1 和 x2 的西格玛值不会总是相同的。我们来查几个这样的案例。
图 7
在图 7 中,x1 的σ为 0.6,x2 的σ为 1。
所以,这个范围看起来像日蚀。x1 缩小了,因为 sigma 的标准差 sigma 现在变小了。
图 8
在图 8 中,它与上一张图相反。
x1 的σ是 x2 的σ的两倍。
x1 这次音域宽多了!所以日蚀改变了方向。
改变变量之间的相关系数
图 9
这是一个完全不同的场景。在图 9 中,非对角线值不再为零。是 0.5。它显示 x1 和 x2 的相关系数为 0.5。
日食现在有一个对角线方向。x1 和 x2 一起增长,因为它们正相关。
当 x1 大时,x2 也大,当 x1 小时,x2 也小。
图 10
图 10 中,x1 和 x2 的相关性更大,0.8!
所以月食更陡!
所有的概率都在一个狭窄的区域内。分布也显得又高又瘦。
在上面的所有图片中,x1 和 x2 之间的相关性要么是正的,要么是零。让我们看一个负相关的例子。
图 11
在图 11 中,x1 和 x2 之间的相关性为-0.8。
你可以看到概率又在一个很窄的范围内。但是当 x1 较大时,x2 较小,当 x1 较小时,x2 较大。
最后,我们应该检查一些不同的*均值(mu)
我们保持 mu 的值始终为 0。让我们看看不同管理部门的变化情况。
图 12
在图 12 中,x1 的μ为 0,x2 的μ为 0.5。
看图中的范围。现在 x2 的曲线中心从零开始移动。
中心位置或者最高概率分布区域现在应该在 0.5。
图 13
在图 13 中,x1 的 mu 为 1.5,x2 为-0.5。
x1 方向上最大概率的中心是 1.5。同时,对于 x2 方向,概率最高的中心为-0.5。
总的来说,整个曲线发生了变化。
结论
我希望这篇文章有助于清楚地理解高斯分布及其特征。我试图展示和解释不同参数的曲线之间的关系。希望,当你在统计学或机器学习中使用高斯分布时,现在会容易得多。
更多阅读:
在 Pandas 中执行时间序列分析所需的所有 Pandas 功能。您也可以将此用作备忘单。
towardsdatascience.com](/an-ultimate-guide-to-time-series-analysis-in-pandas-76a0433621f3) [## 熊猫数据可视化的终极备忘单
熊猫的所有基本视觉类型和一些非常高级的视觉…
towardsdatascience.com](/an-ultimate-cheat-sheet-for-data-visualization-in-pandas-4010e1b16b5c) [## Python 中从头开始的完整逻辑回归算法:一步一步
使用真实世界的数据集开发算法
towardsdatascience.com](/a-complete-logistic-regression-algorithm-from-scratch-in-python-step-by-step-ce33eae7d703) [## 描述统计学导论
对最基本和广泛使用的描述性统计方法有清晰和详细的理解
towardsdatascience.com](/introduction-to-the-descriptive-statistics-a050b5ec99fb) [## 学习机器学习和深度学习的优质免费课程
顶级大学高质量免费课程的链接
towardsdatascience.com](/great-quality-free-courses-to-learn-machine-learning-and-deep-learning-1029048fd0fc) [## 数据科学家使用 Python 进行假设检验的完整指南
用样本研究问题、解决步骤和完整代码清楚地解释
towardsdatascience.com](/a-complete-guide-to-hypothesis-testing-for-data-scientists-using-python-69f670e6779e)
一元线性回归-理论与实践
卢克·切瑟在 Unsplash 上的照片
简介:这篇文章解释了一元线性回归的数学和执行。这将包括成本函数背后的数学、梯度下降和成本函数的收敛。
机器学习主要分为三种类型
- 监督机器学习
- 无监督机器学习
- 强化学习
在监督机器学习中,使用一组具有预期输出的训练示例来训练模型。模型在对这些示例进行训练后,会尝试预测另一组示例的输出值。有两种类型的监督机器学习:
- 回归-预测连续值输出。所有类型的回归都属于这一部分。
- 分类-预测离散值输出。SVM、KNN 和兰登森林属于这一部分。
有几种类型的回归算法,如线性回归,逻辑回归,多项式回归,逐步回归,岭回归,套索回归,弹性网回归。
让我们考虑一元线性回归的情况。我们将同时处理数学和执行。所用数据集的链接如下:
https://www.kaggle.com/c/home-data-for-ml-course/data
该数据集包含波士顿不同房屋的销售价值信息及其对其他要素的依赖性。
让我们导入所需的库:
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import style
并将数据集加载到熊猫数据框架中
read_df = pd.read_csv(‘train.csv’)
df = read_df.copy()
df.head()
df.info()
这些特征的详细描述与数据集一起提供。可以获得简要信息:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1460 entries, 0 to 1459
Data columns (total 81 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Id 1460 non-null int64
1 MSSubClass 1460 non-null int64
2 MSZoning 1460 non-null object
3 LotFrontage 1201 non-null float64
4 LotArea 1460 non-null int64
5 Street 1460 non-null object
6 Alley 91 non-null object
7 LotShape 1460 non-null object
8 LandContour 1460 non-null object
9 Utilities 1460 non-null object
10 LotConfig 1460 non-null object
11 LandSlope 1460 non-null object
12 Neighborhood 1460 non-null object
13 Condition1 1460 non-null object
14 Condition2 1460 non-null object
15 BldgType 1460 non-null object
16 HouseStyle 1460 non-null object
17 OverallQual 1460 non-null int64
18 OverallCond 1460 non-null int64
19 YearBuilt 1460 non-null int64
20 YearRemodAdd 1460 non-null int64
21 RoofStyle 1460 non-null object
22 RoofMatl 1460 non-null object
23 Exterior1st 1460 non-null object
24 Exterior2nd 1460 non-null object
25 MasVnrType 1452 non-null object
26 MasVnrArea 1452 non-null float64
27 ExterQual 1460 non-null object
28 ExterCond 1460 non-null object
29 Foundation 1460 non-null object
30 BsmtQual 1423 non-null object
31 BsmtCond 1423 non-null object
32 BsmtExposure 1422 non-null object
33 BsmtFinType1 1423 non-null object
34 BsmtFinSF1 1460 non-null int64
35 BsmtFinType2 1422 non-null object
36 BsmtFinSF2 1460 non-null int64
37 BsmtUnfSF 1460 non-null int64
38 TotalBsmtSF 1460 non-null int64
39 Heating 1460 non-null object
40 HeatingQC 1460 non-null object
41 CentralAir 1460 non-null object
42 Electrical 1459 non-null object
43 1stFlrSF 1460 non-null int64
44 2ndFlrSF 1460 non-null int64
45 LowQualFinSF 1460 non-null int64
46 GrLivArea 1460 non-null int64
47 BsmtFullBath 1460 non-null int64
48 BsmtHalfBath 1460 non-null int64
49 FullBath 1460 non-null int64
50 HalfBath 1460 non-null int64
51 BedroomAbvGr 1460 non-null int64
52 KitchenAbvGr 1460 non-null int64
53 KitchenQual 1460 non-null object
54 TotRmsAbvGrd 1460 non-null int64
55 Functional 1460 non-null object
56 Fireplaces 1460 non-null int64
57 FireplaceQu 770 non-null object
58 GarageType 1379 non-null object
59 GarageYrBlt 1379 non-null float64
60 GarageFinish 1379 non-null object
61 GarageCars 1460 non-null int64
62 GarageArea 1460 non-null int64
63 GarageQual 1379 non-null object
64 GarageCond 1379 non-null object
65 PavedDrive 1460 non-null object
66 WoodDeckSF 1460 non-null int64
67 OpenPorchSF 1460 non-null int64
68 EnclosedPorch 1460 non-null int64
69 3SsnPorch 1460 non-null int64
70 ScreenPorch 1460 non-null int64
71 PoolArea 1460 non-null int64
72 PoolQC 7 non-null object
73 Fence 281 non-null object
74 MiscFeature 54 non-null object
75 MiscVal 1460 non-null int64
76 MoSold 1460 non-null int64
77 YrSold 1460 non-null int64
78 SaleType 1460 non-null object
79 SaleCondition 1460 non-null object
80 SalePrice 1460 non-null int64
dtypes: float64(3), int64(35), object(43)
memory usage: 924.0+ KB
包括 ID 在内共有 81 个特征。参赛作品总数为 1460 件。我们现在不会做任何数据清理,因为我们的目的只是了解线性回归。让我们将特性 LotArea 作为输入特性,将 SalePrice 作为输出特性。策划他们两个:
fig = plt.figure(figsize = (10,10))
style.use(‘ggplot’)
plt.scatter(df.LotArea, df.SalePrice, s = 5, c = ‘k’)
正如我们所看到的,大多数房子的土地面积不到 50000 *方英尺,售价不到 500000 美元。所有其他情况都可以被认为是例外情况或异常值(取决于其他特征)。
线性回归的基本思想是找到一条符合数据点集的直线。我将在本文中使用以下符号:
在我们的例子中,m =训练样本数=> 1460
x's = 'Input '变量/特征=> LotArea
y's = '输出'变量/标签= >销售价格
(x,y)包括一个训练示例。
表示第 I 个训练示例。
一般来说,回归过程由下面的流程图解释:
训练集被植入学习算法以产生假设。假设是根据输入值预测输出值的函数。在我们的例子中,LotArea 作为假设的输入,得到估计的销售价格。h 是从 x 到 y 的映射函数。
在线性回归的情况下,假设由以下等式表示:
是假设的参数。在引用参数时,我将使用‘theta _ 0’和‘theta _ 1’。
上图展示了一个假设的图示例子。它是符合给定数据点的回归线。这里的假设类似于直线方程,因为我们使用的是线性回归。我们也可以使用任何类型的函数作为假设来相应地拟合数据。这里使用的假设是单变量线性回归或单变量线性回归。
我们来看看如何选择假设的参数。这里的想法是选择参数,使其最适合 y,即选择 theta_0 和 theta_1,使 h(x)接*每个 x 的 y 值。这种情况可以用数学方法表示如下:
其中 m 是训练样本的数量。
上述等式相当于:
这里 h(x)是上面提到的假设,y 是相应 x 值的输出值。上述等式简化为寻找θ_ 0 和θ_ 1 的值,该值最小化估计输出 h(x)和实际输出 y 之间的差。
J(theta_0,theta_1)称为代价函数。有许多类型的成本函数可用。均方差(MSE)成本函数是回归问题中通常使用的函数,如上所示。
让我们进一步分析成本函数。为简化起见,请考虑:
于是假设变成了:
和成本函数
这意味着所有假设函数都经过原点,即(0,0)。
考虑以下数据点:
考虑:
将获得以下回归线:
成本函数的值是:
认为
得到的回归线将是:
从数据点到回归线的直线是实际值和估计值之间的误差。计算成本 J
认为
绘制不同θ_ 0 值的成本,我们得到:
从图中,我们可以看到成本函数值在
其对应于穿过所有三个数据点的回归线。由此,我们可以暗示,给出成本函数值最小的参数值对应于最佳拟合回归线。
让我们回到最初的成本函数。由于我们在上一节中考虑了θ_ 0 = 0,我们能够为成本函数绘制一个二维图。但实际上,由于成本函数取决于θ_ 0 和θ_ 1,我们将得到一个弓形的三维图形(该形状取决于训练集)。考虑房价数据集中的 LotArea 和 SalePrice 要素。让我们试着画出这些特征的成本函数。
from mpl_toolkits.mplot3d.axes3d import Axes3D
import numpy as np
m = len(df.LotArea)
theta_0 = np.linspace(10000, 100000, num = 1000)
theta_1 = np.linspace(5, 10, num = 1000)
Theta_0, Theta_1 = np.meshgrid(theta_0, theta_1)
def cost(theta_0, theta_1, LotArea, SalePrice):
h = 0
for i in range(len(LotArea)):
h += (((theta_0 + (theta_1 * LotArea[i])) - SalePrice[i]) ** 2)
J = (1/2*len(LotArea)) * h
return J
fig = plt.figure(figsize = (12,12))
style.use('ggplot')
ax = fig.add_subplot(111, projection = '3d')
ax.plot_surface(Theta_0, Theta_1, cost(Theta_0, Theta_1, df.LotArea, df.SalePrice), cmap = 'viridis')
ax.set_xlabel('θ0')
ax.set_ylabel('θ1')
ax.set_zlabel('J')
得到了如下的图:
XY *面表示θ_ 0 和θ_ 1 的不同值,并且从 XY *面上的任意点开始的表面图的高度给出了对应于该点的θ_ 0 和θ_ 1 值的 J 值。我们也可以画一个等高线图来表示 J(theta_0,theta_1)。
fig = plt.figure(figsize = (12,12))
style.use(‘ggplot’)
ax = fig.add_subplot(111)
cs = ax.contourf(Theta_0, Theta_1, cost(Theta_0, Theta_1, df.LotArea, df.SalePrice))
cbar = fig.colorbar(cs)
ax.set_xlabel('θ0')
ax.set_ylabel('θ1')
plt.show()
获得以下等高线图:
侧栏显示了 J 值随着图中颜色的变化而变化。对于不同的θ_ 0 和θ_ 1 值,等高线图中特定环中的所有点具有相同的 J 值。
让我们为上面考虑的θ_ 0 和θ_ 1 的值绘制回归线:
minj = np.min(cost(Theta_0, Theta_1, df.LotArea, df.SalePrice))
point = np.array(cost(Theta_0, Theta_1, df.LotArea, df.SalePrice)) == minj
position = np.where(point)
positionoutput>>(array([9]), array([999]))
输出显示了给出最小成本值的θ_ 0 和θ_ 1 的值的位置。
theta_1_min = Theta_1[9][999]
theta_0_min = Theta_0[9][999]
def fitline(theta_0, theta_1, LotArea):
x = []
for i in range(m):
x.append(theta_0 + (theta_1 * LotArea[i]))
return x
fig = plt.figure(figsize = (10,10))
style.use('ggplot')
count = 0
for i in range(len(theta_0)):
plt.plot(df.LotArea,fitline(theta_0[i], theta_1[i], df.LotArea), color = 'k', alpha = 0.1, linewidth = 0.1)
count += 1
print(count)
plt.scatter(df.LotArea, df.SalePrice, s = 5, c = 'r')
plt.plot(df.LotArea, (theta_0_min + (theta_1_min * df.LotArea)), color = 'k')
plt.show()
阴影区域由对应于θ_ 0 和θ_ 1 值的 1000 条线组成,突出显示的线是对应于我们获得最小成本的参数值的线。这不是实际的最小成本,而只是所考虑的参数范围内的最小值。
用于找到给出实际最小成本值的参数值的算法是梯度下降。它是一种通用算法,不仅用于最小化成本函数 J,还用于最小化其他函数。该算法的大致轮廓将是:
- 从θ_ 0 和θ_ 1 的某个值开始(通常两者都被设置为零。)
- 不断改变数值,直到我们达到 J(theta_0,theta_1)的最小值。
算法如下:
在哪里
Alpha 或学习率决定了算法达到最小成本值(即,给出最小成本值的参数值)所采取的步长。我们必须注意的非常重要的细节是,我们必须同时更新 theta_1 和 theta _ 0。我们不应该更新θ_ 0,然后升级成本函数,然后θ_ 1,这不是我们想要的方式。
通过研究算法的偏导数部分,考虑我们之前绘制的二维成本函数图。假设我们正在更新θ_ 1 的值,因为我们在那种情况下忽略了θ_ 0。
偏导数项给出了图上每个θ_ 1 点切线的斜率。算法将是:
考虑上图中的点及其切线。正如我们所见,切线具有正斜率,因此等式为:
因此,我们可以看到,θ_ 1 的值在减小,这就是我们要做的,逐步达到最小值。如果我们考虑曲线左侧的θ_ 1 值,斜率将是负数,从而增加θ_ 1 并最终达到最小值。
学习率(α)也会影响收敛路径。如果α太小,则算法需要很长时间才能达到最小值,从而延迟收敛。
如果 alpha 太大,则算法可能会超过最小值,从而不会收敛,甚至可能发散,如上图所示。
当我们达到局部最小值时,考虑应用该算法。该算法的偏导数部分将为零,因为切线在该点将是水*的,因此θ_ 0 的值不会改变。
当我们接*一个局部最小值时,切线的斜率继续下降到零。因此,当我们接*最小值时,参数减少或增加的值也减少。由此我们可以看出,即使我们保持α值不变,算法也是收敛的。
将这种梯度下降算法应用于一元线性回归问题。
对于 j = 0
对于 j = 1
这为我们提供了梯度下降算法
再次考虑房价数据集。让我们对特征 LotArea 和输出 SalePrice 执行梯度下降。
from sklearn import preprocessing
x = df.LotArea
y = df.SalePrice
x = preprocessing.scale(x)
theta_0_gd = 0
theta_1_gd = 0
alpha = 0.01
h_theta_0_gd = 1
h_theta_1_gd = 1
epoch = 0
fig = plt.figure(figsize = (10,10))
style.use('ggplot')
plt.scatter(x, y, s = 5, c = 'r')
while h_theta_0_gd != 0 or h_theta_0_gd != 0:
if epoch > 1000:
break
h_theta_0_gd = 0
h_theta_1_gd = 0
for i in range(m):
h_theta_0_gd += (theta_0_gd + (theta_1_gd * x[i]) - y[i])
h_theta_1_gd += ((theta_0_gd + (theta_1_gd * x[i]) - y[i]) * x[i])
h_theta_0_gd = (1/m) * h_theta_0_gd
h_theta_1_gd = (1/m) * h_theta_1_gd
theta_0_gd -= (alpha * h_theta_0_gd)
theta_1_gd -= (alpha * h_theta_1_gd)
plt.plot(x,(theta_0_gd + (theta_1_gd * x)), color = 'k', alpha = 0.1, linewidth = 1)
epoch += 1
plt.plot(x,(theta_0_gd + (theta_1_gd * x)), color = 'r', linewidth = 3)
plt.show()
参数的初始值被设置为零。学习率设置为 0.01。最多允许 1000 个重复或时期。图中所有阴影部分由获得每个时期的参数后绘制的回归线组成。突出显示的红线是 1000 个时期后的最终回归线。正如我们所见,线的密度随着接*红线而增加。这是因为当算法接*最小值时所采取的步骤很小,从而减少了每个时期中相应回归线之间的间隔。为了确认我们是否正确地应用了该算法,我们可以用 sklearn 的线性回归模型进行交叉验证。
from sklearn import model_selection
from sklearn.linear_model import LinearRegression
x_model = np.array(df.LotArea).reshape(-1,1)
y_model = np.array(df.SalePrice).reshape(-1,1)
x_model = preprocessing.scale(x_model)
x_train, x_test, y_train, y_test = model_selection.train_test_split(x_model, y_model, test_size = 0.33)
clf = LinearRegression()
clf.fit(x_train, y_train)
theta_1_model = clf.coef_
theta_0_model = clf.intercept_
fig = plt.figure(figsize = (10,10))
style.use('ggplot')
plt.scatter(x, y, s = 5, c = 'r')
plt.plot(x_model,(theta_0_model + (theta_1_model * x_model)), color = 'k')
正如我们可以看到的,sklearn 的回归模型也产生了相同的图形。
结论:我们开始了解不同类型的机器学习以及监督学习的类型。我们看到了假设、成本函数和单变量线性回归的梯度下降背后的概念,并从头开始使用上述概念在房价数据集的要素上构建了一条回归线。然后将其与使用 sklearn 的线性回归模型构建的模型进行比较。
参考文献:
我正在从这个 Youtube 播放列表中学习大多数关于机器学习的概念。这很有帮助,也很容易理解。
成功实现数据可视化的通用原则
数据可视化是一个热门话题。让我们通过理解使用的 4 个主要原型来发现如何掌握这一新学科。
*年来,数据可视化一直是大家谈论的话题!它是数据民主化过程的升华,使小型和大型企业都能够根据数据和见解而不是直觉或预先固定的想法来制定业务战略。数据可视化是赋予我们手边大量数据生命的学科。好处多多!它使企业能够理解他们跟踪的数据并据此采取行动。它允许记者和科学家对他们的发现给出严格的解释。数据可视化为全世界成千上万的人提供了操作和试验数据的乐趣。它支持世界各地的社区解决全球性问题,如贫困、健康和环境。它将数据提升到一个前所未有的新维度和新能力!
如今,与数据可视化密切相关的新角色激增(数据可视化工程师、分析师、专家、主管等)。)越来越多的数据可视化爱好者聚集在社区中,寻找灵感,尝试新的方法并从中获得乐趣(#改头换面星期一和#datafam 就是最好的例子)。
谷歌“数据可视化”趋势,全球
有几个不断发展的 BI 工具可以轻松制作出令人惊叹的可视化效果。最*的例子包括像 Power BI、Looker 和 Tableau 这样的工具,它们都渴望在这样一个充满挑战和要求苛刻的市场中升级和强化它们的产品。
这篇文章旨在回答一个简单的问题:是什么让可视化变得成功和有效?简答:不仅仅是选择的工具!实现目标-观众契合是视觉化伟大而有效的原因。目的是什么——受众契合度?从增长词汇(“产品-市场匹配”)中获取概念,目的-受众匹配可视化是一个 viz,它成功地将主要核心信息与受众和最终用户的特征联系起来,这些特征包括人口统计数据、主题知识、公司/社会中的角色、可用于消化 viz 和人物角色的时间。
有四个主要的原型,其中包括最常见的目的-观众适合。每个原型使用不同的方法来实现这种匹配。
1)绩效记分卡
目的— 绩效记分卡旨在向注意力和信息消费时间有限的受众传达一些关键信息。
设计— 绩效记分卡是冷静、必要且直截了当的可视化工具。用户一眼就能知道业务中正在发生什么。这意味着设计应该是最小化的,包括带有大格式数字的简单的基本图形,这对于在仪表板上合成信息是有用的。对颜色的关注不那么重要,但可以帮助区分积极和消极的趋势和表现(经典的好绿坏红)。
受众— 对主题有丰富知识但消化信息的时间有限的人。
示例— 绩效记分卡原型可以包括不同种类的仪表板。它可以参考财务/执行记分卡,这是一个由高层管理人员广泛使用的仪表板,用于浏览业务的健康状况和主要 KPI。它可以是一个顶级营销仪表板,营销人员可以在其中立即发现渠道的流量表现、活动 ROI 和客户洞察,或者它可以是一个带有医院成就和业绩等的医疗保健记分卡。
由 Austin Distel 在 Unsplash 上拍摄的照片
2)自助服务工具
目的— 自助服务工具使用户能够主动操作和使用可视化工具,找到自己的结论和结果。
设计— 自助服务工具通常通过清晰的说明和清晰的步骤来指导最终用户如何使用可视化。该原型嵌入了要修改的过滤器和参数的阵列,以便根据用户的需求进行定制。颜色的重要性较低,文本框支持具有用户友好的结构,对如何定制分析和结果解释有清晰的指导。
受众— 自助工具的最终用户对该主题有很好的理解,他们对此感兴趣,并且他们有时间深入研究该主题,并将可视化用于自己的发现和目的。
示例— 自助服务工具通常由业务分析师生成,使最终用户能够在业务方面进行自我分析。如果您需要填写表格或使用过滤器来获得结果,也可以在互联网上找到大量的示例。一个著名的例子(带有业绩记分卡的特征)是约翰·霍普斯金大学冠状病毒资源中心仪表板。
3)讲故事可视化
目的— 讲故事可视化用于支持、加强和促进对见解和信息的理解。它们使消费者能够理解和吸收作者的结论。
设计— 讲故事可视化使用各种图表来传达见解。通常,这些图表是根据数据科学已经建立的通用最佳实践构建的(时间线的折线图、不同类别维度的条形图、定量与定量分析的散点图等)。颜色对于区分不同的维度/类别以及便于阅读图表/故事流至关重要。
受众— 这种原型的受众通常对主题的理解为中低水*,没有足够的时间自己检索信息。
示例— 在几乎所有需要分析和解释所做工作的演示中,都可以找到讲述故事的图表。*年来,媒体行业已经成功地利用结构良好的可视化效果来提高文章和信息的质量。《纽约时报》是一个成功利用数据可视化机会的媒体参与者的典型例子。
4)数据艺术
目的— 如今,数据拥有巨大的力量,可以作为让观众惊叹的有效工具。这种原型的主要目的是让 viz 消费者惊讶和着迷,并强烈强化创作者的信息。
设计— 在这个原型中,所有的通用标准都被抛弃了。在如何显示数据,使用什么颜色,应用什么图像或装饰,以及可视化的基调上有绝对的自由。在数据艺术原型中,所有关于数据可视化的最佳实践都倾向于创作者的设计风格和 viz 旨在传达的信息。
观众— 数据艺术可视化通常面向普通大众,他们可能了解也可能不了解所探索的主题。
例子— 数据艺术可视化可以在新闻文章和文章、政治家的演讲和专业网站中找到。
结论
面对我们在现实生活中发现的无数情况,并简单地将它们分成严格的类别,总是具有挑战性。尽管如此,这篇文章中列出的四个原型涵盖了一个优秀的 data viz 创建者在构思和构建阶段应该知道的大多数用例。理解它们是达到成功目的的关键——适合观众,因此是有效的视觉化。
Unix 权限—最简单的方法
所有 chmod 置换的索引
我最*不得不交付一个托管在 Linux 中的项目,我已经记不清我不得不摆弄文件和目录权限的次数了……是的我了解它们是如何工作的;是的我可以在脑子里算出来;是的,到目前为止,我已经记住了最常见的一些;是的我可以使用stat
命令;是的,我可以用符号符号来代替!但是为什么我要这样做。单身。有时间让我看看吗?所以我写了一个快速的 python 脚本来生成完整的权限列表,然后我用它作为参考来查找我遇到的任何组合。
对于那些你更喜欢把你的脑力用在重要的事情上,而你只是想要*凡事情的简单答案的情况,这篇文章是给你的。我会很快地介绍一下基础知识,以免成为有史以来最没有启发性的文章,但是…
我的主要意图是提供一个从 0 到 777 的所有权限排列的“备忘单”!
基础知识
Unix 权限被授予三个不同的实体:
- 物主
- 小组成员
- 其他人(即世界)
有三个权限属性:r、w 和 x。
read (r) 读取一个文件的内容或者列出一个目录中所有文件的能力。
写(w)改变一个文件的内容或在目录中创建新文件。
execute (x)
这意味着有权执行一个文件(如 bash 或程序)。对于目录,执行权限授予进入目录和访问其中任何文件的权利。
权限不仅可以用 rwx 来表示,还可以用八进制的符号来表示(0 到 7)。每个八进制是二进制数字系统中其组成位的总和:
一组三个八进制反映了三个实体:从左到右,第一个数字代表用户的权限,第二个数字代表组的权限,第三个数字代表其他的权限。
就是这样!这确实很简单!
简单的方法
正如承诺的那样,你可以在下面随意查找:
**0**----------**1**---------x**2**--------w-**3**--------wx**4**-------r--**5**-------r-x**6**-------rw-**7**-------rwx**10**------x---**11**------x--x**12**------x-w-**13**------x-wx**14**------xr--**15**------xr-x**16**------xrw-**17**------xrwx**20**-----w----**21**-----w---x**22**-----w--w-**23**-----w--wx**24**-----w-r--**25**-----w-r-x**26**-----w-rw-**27**-----w-rwx**30**-----wx---**31**-----wx--x**32**-----wx-w-**33**-----wx-wx**34**-----wxr--**35**-----wxr-x**36**-----wxrw-**37**-----wxrwx**40**----r-----**41**----r----x**42**----r---w-**43**----r---wx**44**----r--r--**45**----r--r-x**46**----r--rw-**47**----r--rwx**50**----r-x---**51**----r-x--x**52**----r-x-w-**53**----r-x-wx**54**----r-xr--**55**----r-xr-x**56**----r-xrw-**57**----r-xrwx**60**----rw----**61**----rw---x**62**----rw--w-**63**----rw--wx**64**----rw-r--**65**----rw-r-x**66**----rw-rw-**67**----rw-rwx**70**----rwx---**71**----rwx--x**72**----rwx-w-**73**----rwx-wx**74**----rwxr--**75**----rwxr-x**76**----rwxrw-**77**----rwxrwx**100**---x------**101**---x-----x**102**---x----w-**103**---x----wx**104**---x---r--**105**---x---r-x**106**---x---rw-**107**---x---rwx**110**---x--x---**111**---x--x--x**112**---x--x-w-**113**---x--x-wx**114**---x--xr--**115**---x--xr-x**116**---x--xrw-**117**---x--xrwx**120**---x-w----**121**---x-w---x**122**---x-w--w-**123**---x-w--wx**124**---x-w-r--**125**---x-w-r-x**126**---x-w-rw-**127**---x-w-rwx**130**---x-wx---**131**---x-wx--x**132**---x-wx-w-**133**---x-wx-wx**134**---x-wxr--**135**---x-wxr-x**136**---x-wxrw-**137**---x-wxrwx**140**---xr-----**141**---xr----x**142**---xr---w-**143**---xr---wx**144**---xr--r--**145**---xr--r-x**146**---xr--rw-**147**---xr--rwx**150**---xr-x---**151**---xr-x--x**152**---xr-x-w-**153**---xr-x-wx**154**---xr-xr--**155**---xr-xr-x**156**---xr-xrw-**157**---xr-xrwx**160**---xrw----**161**---xrw---x**162**---xrw--w-**163**---xrw--wx**164**---xrw-r--**165**---xrw-r-x**166**---xrw-rw-**167**---xrw-rwx**170**---xrwx---**171**---xrwx--x**172**---xrwx-w-**173**---xrwx-wx**174**---xrwxr--**175**---xrwxr-x**176**---xrwxrw-**177**---xrwxrwx**200**--w-------**201**--w------x**202**--w-----w-**203**--w-----wx**204**--w----r--**205**--w----r-x**206**--w----rw-**207**--w----rwx**210**--w---x---**211**--w---x--x**212**--w---x-w-**213**--w---x-wx**214**--w---xr--**215**--w---xr-x**216**--w---xrw-**217**--w---xrwx**220**--w--w----**221**--w--w---x**222**--w--w--w-**223**--w--w--wx**224**--w--w-r--**225**--w--w-r-x**226**--w--w-rw-**227**--w--w-rwx**230**--w--wx---**231**--w--wx--x**232**--w--wx-w-**233**--w--wx-wx**234**--w--wxr--**235**--w--wxr-x**236**--w--wxrw-**237**--w--wxrwx**240**--w-r-----**241**--w-r----x**242**--w-r---w-**243**--w-r---wx**244**--w-r--r--**245**--w-r--r-x**246**--w-r--rw-**247**--w-r--rwx**250**--w-r-x---**251**--w-r-x--x**252**--w-r-x-w-**253**--w-r-x-wx**254**--w-r-xr--**255**--w-r-xr-x**256**--w-r-xrw-**257**--w-r-xrwx**260**--w-rw----**261**--w-rw---x**262**--w-rw--w-**263**--w-rw--wx**264**--w-rw-r--**265**--w-rw-r-x**266**--w-rw-rw-**267**--w-rw-rwx**270**--w-rwx---**271**--w-rwx--x**272**--w-rwx-w-**273**--w-rwx-wx**274**--w-rwxr--**275**--w-rwxr-x**276**--w-rwxrw-**277**--w-rwxrwx**300**--wx------**301**--wx-----x**302**--wx----w-**303**--wx----wx**304**--wx---r--**305**--wx---r-x**306**--wx---rw-**307**--wx---rwx**310**--wx--x---**311**--wx--x--x**312**--wx--x-w-**313**--wx--x-wx**314**--wx--xr--**315**--wx--xr-x**316**--wx--xrw-**317**--wx--xrwx**320**--wx-w----**321**--wx-w---x**322**--wx-w--w-**323**--wx-w--wx**324**--wx-w-r--**325**--wx-w-r-x**326**--wx-w-rw-**327**--wx-w-rwx**330**--wx-wx---**331**--wx-wx--x**332**--wx-wx-w-**333**--wx-wx-wx**334**--wx-wxr--**335**--wx-wxr-x**336**--wx-wxrw-**337**--wx-wxrwx**340**--wxr-----**341**--wxr----x**342**--wxr---w-**343**--wxr---wx**344**--wxr--r--**345**--wxr--r-x**346**--wxr--rw-**347**--wxr--rwx**350**--wxr-x---**351**--wxr-x--x**352**--wxr-x-w-**353**--wxr-x-wx**354**--wxr-xr--**355**--wxr-xr-x**356**--wxr-xrw-**357**--wxr-xrwx**360**--wxrw----**361**--wxrw---x**362**--wxrw--w-**363**--wxrw--wx**364**--wxrw-r--**365**--wxrw-r-x**366**--wxrw-rw-**367**--wxrw-rwx**370**--wxrwx---**371**--wxrwx--x**372**--wxrwx-w-**373**--wxrwx-wx**374**--wxrwxr--**375**--wxrwxr-x**376**--wxrwxrw-**377**--wxrwxrwx**400**-r--------**401**-r-------x**402**-r------w-**403**-r------wx**404**-r-----r--**405**-r-----r-x**406**-r-----rw-**407**-r-----rwx**410**-r----x---**411**-r----x--x**412**-r----x-w-**413**-r----x-wx**414**-r----xr--**415**-r----xr-x**416**-r----xrw-**417**-r----xrwx**420**-r---w----**421**-r---w---x**422**-r---w--w-**423**-r---w--wx**424**-r---w-r--**425**-r---w-r-x**426**-r---w-rw-**427**-r---w-rwx**430**-r---wx---**431**-r---wx--x**432**-r---wx-w-**433**-r---wx-wx**434**-r---wxr--**435**-r---wxr-x**436**-r---wxrw-**437**-r---wxrwx**440**-r--r-----**441**-r--r----x**442**-r--r---w-**443**-r--r---wx**444**-r--r--r--**445**-r--r--r-x**446**-r--r--rw-**447**-r--r--rwx**450**-r--r-x---**451**-r--r-x--x**452**-r--r-x-w-**453**-r--r-x-wx**454**-r--r-xr--**455**-r--r-xr-x**456**-r--r-xrw-**457**-r--r-xrwx**460**-r--rw----**461**-r--rw---x**462**-r--rw--w-**463**-r--rw--wx**464**-r--rw-r--**465**-r--rw-r-x**466**-r--rw-rw-**467**-r--rw-rwx**470**-r--rwx---**471**-r--rwx--x**472**-r--rwx-w-**473**-r--rwx-wx**474**-r--rwxr--**475**-r--rwxr-x**476**-r--rwxrw-**477**-r--rwxrwx**500**-r-x------**501**-r-x-----x**502**-r-x----w-**503**-r-x----wx**504**-r-x---r--**505**-r-x---r-x**506**-r-x---rw-**507**-r-x---rwx**510**-r-x--x---**511**-r-x--x--x**512**-r-x--x-w-**513**-r-x--x-wx**514**-r-x--xr--**515**-r-x--xr-x**516**-r-x--xrw-**517**-r-x--xrwx**520**-r-x-w----**521**-r-x-w---x**522**-r-x-w--w-**523**-r-x-w--wx**524**-r-x-w-r--**525**-r-x-w-r-x**526**-r-x-w-rw-**527**-r-x-w-rwx**530**-r-x-wx---**531**-r-x-wx--x**532**-r-x-wx-w-**533**-r-x-wx-wx**534**-r-x-wxr--**535**-r-x-wxr-x**536**-r-x-wxrw-**537**-r-x-wxrwx**540**-r-xr-----**541**-r-xr----x**542**-r-xr---w-**543**-r-xr---wx**544**-r-xr--r--**545**-r-xr--r-x**546**-r-xr--rw-**547**-r-xr--rwx**550**-r-xr-x---**551**-r-xr-x--x**552**-r-xr-x-w-**553**-r-xr-x-wx**554**-r-xr-xr--**555**-r-xr-xr-x**556**-r-xr-xrw-**557**-r-xr-xrwx**560**-r-xrw----**561**-r-xrw---x**562**-r-xrw--w-**563**-r-xrw--wx**564**-r-xrw-r--**565**-r-xrw-r-x**566**-r-xrw-rw-**567**-r-xrw-rwx**570**-r-xrwx---**571**-r-xrwx--x**572**-r-xrwx-w-**573**-r-xrwx-wx**574**-r-xrwxr--**575**-r-xrwxr-x**576**-r-xrwxrw-**577**-r-xrwxrwx**600**-rw-------**601**-rw------x**602**-rw-----w-**603**-rw-----wx**604**-rw----r--**605**-rw----r-x**606**-rw----rw-**607**-rw----rwx**610**-rw---x---**611**-rw---x--x**612**-rw---x-w-**613**-rw---x-wx**614**-rw---xr--**615**-rw---xr-x**616**-rw---xrw-**617**-rw---xrwx**620**-rw--w----**621**-rw--w---x**622**-rw--w--w-**623**-rw--w--wx**624**-rw--w-r--**625**-rw--w-r-x**626**-rw--w-rw-**627**-rw--w-rwx**630**-rw--wx---**631**-rw--wx--x**632**-rw--wx-w-**633**-rw--wx-wx**634**-rw--wxr--**635**-rw--wxr-x**636**-rw--wxrw-**637**-rw--wxrwx**640**-rw-r-----**641**-rw-r----x**642**-rw-r---w-**643**-rw-r---wx**644**-rw-r--r--**645**-rw-r--r-x**646**-rw-r--rw-**647**-rw-r--rwx**650**-rw-r-x---**651**-rw-r-x--x**652**-rw-r-x-w-**653**-rw-r-x-wx**654**-rw-r-xr--**655**-rw-r-xr-x**656**-rw-r-xrw-**657**-rw-r-xrwx**660**-rw-rw----**661**-rw-rw---x**662**-rw-rw--w-**663**-rw-rw--wx**664**-rw-rw-r--**665**-rw-rw-r-x**666**-rw-rw-rw-**667**-rw-rw-rwx**670**-rw-rwx---**671**-rw-rwx--x**672**-rw-rwx-w-**673**-rw-rwx-wx**674**-rw-rwxr--**675**-rw-rwxr-x**676**-rw-rwxrw-**677**-rw-rwxrwx**700**-rwx------**701**-rwx-----x**702**-rwx----w-**703**-rwx----wx**704**-rwx---r--**705**-rwx---r-x**706**-rwx---rw-**707**-rwx---rwx**710**-rwx--x---**711**-rwx--x--x**712**-rwx--x-w-**713**-rwx--x-wx**714**-rwx--xr--**715**-rwx--xr-x**716**-rwx--xrw-**717**-rwx--xrwx**720**-rwx-w----**721**-rwx-w---x**722**-rwx-w--w-**723**-rwx-w--wx**724**-rwx-w-r--**725**-rwx-w-r-x**726**-rwx-w-rw-**727**-rwx-w-rwx**730**-rwx-wx---**731**-rwx-wx--x**732**-rwx-wx-w-**733**-rwx-wx-wx**734**-rwx-wxr--**735**-rwx-wxr-x**736**-rwx-wxrw-**737**-rwx-wxrwx**740**-rwxr-----**741**-rwxr----x**742**-rwxr---w-**743**-rwxr---wx**744**-rwxr--r--**745**-rwxr--r-x**746**-rwxr--rw-**747**-rwxr--rwx**750**-rwxr-x---**751**-rwxr-x--x**752**-rwxr-x-w-**753**-rwxr-x-wx**754**-rwxr-xr--**755**-rwxr-xr-x**756**-rwxr-xrw-**757**-rwxr-xrwx**760**-rwxrw----**761**-rwxrw---x**762**-rwxrw--w-**763**-rwxrw--wx**764**-rwxrw-r--**765**-rwxrw-r-x**766**-rwxrw-rw-**767**-rwxrw-rwx**770**-rwxrwx---**771**-rwxrwx--x**772**-rwxrwx-w-**773**-rwxrwx-wx**774**-rwxrwxr--**775**-rwxrwxr-x**776**-rwxrwxrw-**777**-rwxrwxrwx
感谢阅读!
我经常在媒体上写关于领导力、技术&的数据——如果你想阅读我未来的帖子,请‘关注’我 !
Matplotlib 的无限灵活性
你可以在你的土地上做任何事。
韦斯利·廷吉在 Unsplash 上拍摄的照片
Matplotlib 是一个广泛使用的 Python 数据可视化库,它提供了大量的 2D 和 3D 绘图,对于数据分析和机器学习任务非常有用。
创建一个复杂情节的语法可能看起来吓人,但它提供了很大的灵活性。您几乎可以接触到图上的任何组件并对其进行定制。
在这篇文章中,我将向你展示如何创建一个基本的情节,并通过各种方式定制它。我们将在脚本层工作,这是 matplotlib.pyplot 接口。您最有可能在您的分析中使用这个界面。
import matplotlib.pyplot as plt
%matplotlib inline #render plots in notebook
一切都是从创建一个图形开始的,它是将所有东西结合在一起的主要艺术家对象。
plt.figure(figsize=(10,6))
我们创建了一个大小为(10,6)的图形。让我们在这个图上画一个基本的柱状图。
plt.figure(figsize=(10,6))plt.bar(x=[3,4,2,1], height=[3,5,1,6])
x 轴上的刻度太多。如果只显示条形的刻度,效果会更好。此外,我想扩展 y 轴上的范围,以便最高的栏不会到达顶部。我们也可以在 x 记号上分配标签,我认为这比简单的数字更好看。
plt.figure(figsize=(10,6))plt.bar(x=[3,4,2,1], height=[3,5,1,6])plt.xticks(ticks=[1,2,3,4],
labels=['Rome','Madrid','Istanbul','Houston'])plt.yticks(ticks=np.arange(10))
更高的 punto 标签看起来更好。让我们增加它,并使用旋转参数旋转标签。加个标题也不错。
plt.figure(figsize=(10,6))plt.title("Use Your Own Title", fontsize=15)plt.bar(x=[3,4,2,1], height=[3,5,1,6])plt.xticks(ticks=[1,2,3,4], labels=['Rome','Madrid','Istanbul','Houston'], rotation="45", fontsize=12)plt.yticks(ticks=np.arange(10), fontsize=12)
我们可以用 facecolor 参数给 Figure 对象添加一个框架。xlabel 和 ylabel 函数可用于添加轴标签。也可以使用条形功能的宽度参数来调整条形的宽度。
plt.figure(figsize=(10,6), facecolor="lightgreen")plt.title("Use Your Own Title", fontsize=15)plt.bar(x=[3,4,2,1], height=[3,5,1,6], width=0.5)plt.xticks(ticks=[1,2,3,4], labels=['Rome','Madrid','Istanbul','Houston'], rotation="45", fontsize=12)plt.yticks(ticks=np.arange(10), fontsize=12)plt.xlabel("Cities", fontsize=14)plt.ylabel("Measurement", fontsize=14)
注释和文本可以用在绘图上,使它们更具信息性或传递信息。Matplotlib 也允许添加它们。
我们将在图上使用 Axes 对象来添加文本和注释。为了在轴上定位这些附件,我们需要使用坐标。因此,添加网格线是有意义的,这可以通过 plt.grid()函数来实现。
让我们首先添加网格线和一个文本框。
ax = plt.figure(figsize=(10,6), facecolor="lightgreen").add_subplot(111)plt.title("Use Your Own Title", fontsize=15)plt.bar(x=[3,4,2,1], height=[3,5,1,6], width=0.5)plt.xticks(ticks=[1,2,3,4], labels=['Rome','Madrid','Istanbul','Houston'], rotation="45", fontsize=12)plt.yticks(ticks=np.arange(10), fontsize=12)plt.xlabel("Cities", fontsize=14)plt.ylabel("Measurement", fontsize=14)plt.grid(which='both')ax.text(3.5, 8, 'Daily Average', style='italic', fontsize=12,
bbox={'facecolor': 'grey', 'alpha': 0.5, 'pad': 5})
ax.text()函数的前两个参数分别是 x 和 y 坐标。指定文本和样式后,我们使用 bbox 参数在它周围添加一个框。
让我们也添加一个注释。我不想重复代码的相同部分,所以我只写注释部分。您可以将它添加到生成前一个图的代码中。
ax.annotate('highest', xy=(1, 6), xytext=(1.5, 7), fontsize=13, arrowprops=dict(facecolor='black', shrink=0.05))
我们添加了一个指向最高栏顶部的箭头。 xy 参数包含箭头的坐标, xytext 参数指定文本的位置。 Arrowprops ,顾名思义,是用来给箭头造型的。
一旦你对你的绘图满意,你可以使用 plt.savefig() 函数保存它。
这篇文章的目的是向你展示 Matplotlib 的巨大灵活性。我们制作的情节可能一点用也没有,但它们传达了信息。语法可能看起来不必要的长,但这是灵活性的代价。
值得一提的是,这只是你可以用 Matplotlib 创建的一部分。例如,支线剧情的结构是一个完全不同的话题。一旦您熟悉了 Matplotlib,您可以创建的东西就没有限制了。
感谢阅读。如果您有任何反馈,请告诉我。
无限免费虚拟主机和域名
在 Unsplash 上由 Austin Schmid 拍摄的照片
非常适合投资组合、开博客、自由职业或做生意
有一个网络个人资料很酷,对吧?它保持在线,任何人都可以看到,与任何人分享都非常容易。您可以在一个网址上分享您的作品或作品集。
你不必成为一个前端开发人员,使投资组合!
有一些技巧可以让你在不了解 HTML、CSS 或前端开发的情况下制作一个网络作品集
如果你是一名开发很酷的机器学习和深度学习项目的机器学习工程师,让它具有交互性将是向人们展示你的模型有多强大的完美方式。
如果你是一个入门级的 web 开发人员或软件开发人员,你可能正在寻找一种方法来托管你的代码,以便你可以更好地表达你的工作。不然光看代码就能无聊!视觉是更有效的方式。
如果你是一名数据科学家、数据分析师、自由职业者,或者计划开始一项新业务,需要一个登录页面,但在预算紧张的情况下,你会感谢这些免费资源。
我想分享一些很酷的资源,可以帮助你免费获得网络作品集。
这是无限的免费托管服务。它甚至有一个漂亮的 c 面板和主机服务的常规功能,如数据库、WordPress 安装选项等等。你可以完全免费地托管任意多的网站。是不是很神奇!
在我开始科技之旅的时候,我做过一段时间的 WordPress 开发者。我需要制作一个作品集来展示我找工作的技能。我用这个网站托管了几个我建的虚拟网站。
你也可以在开始的时候为你的小企业使用这种托管服务。
如果你愿意,你可以稍后将你的网站转移到其他主机服务。伟大的事情是,你仍然可以保持你相同的网站地址或域名。
所以,这可以作为你的一个临时占位符或者一个起点。如果你不喜欢这项服务,只需将你的网站转移到付费托管服务。
这是免费托管。域名呢?
你也可以获得无限的免费域名。
这是一个你可以获得无限免费域名的网站。你不太可能得到一个. com。网,或者。org 域名免费。你必须为那些高级域名付费。因为他们总是随叫随到。
免费的域名:。tk,。ml,。嘎,。cf,或者. gq .所以,你的网站看起来会像 myDomain.tk 或者 mydomain.ml。
这些听起来可能不吸引人,但它们服务于我托管虚拟网站和创建投资组合的目的。
如果你正在寻找一个免费的投资组合,这是伟大的!但是如果你想要一个. net。com,或者。org 网站,你可以为域名付费,但仍然可以享受免费托管。
你将获得一个为期一年的域名。一年后你可以再次免费续费。如果您决定更改域名,您也可以进行转让。
现在,不用学 HTML,CSS 就学会自己制作作品集吧!
如果你有主机和域名,你可以免费安装 WordPress。这是一个惊人的资源。如果你不是一个前端开发人员,建立一个网络投资组合可能会很可怕。简单地使用 WordPress。
它有数百个主题。根据您的需求选择一个。youtube 上有数百个教程。这可能需要一两天甚至更长的时间来找到一个合适的主题和一个适合你的教程。
我简单地使用了一个名为 Ocean-wp 的主题和一个页面生成器 Elementor。免费版本足以构建一个伟大的投资组合。这只是一个简单的拖放方法。
还有其他页面生成器。我说 Elementor 是因为那是我用的。最方便的是,youtube 上有很多教程展示如何使用它。
这是我用海洋 wp 主题和元素创建的博客:
在你获得成为数据科学家的技能后,最重要的事情是什么?必须是为了炫耀你的…
regenerativetoday.com](https://regenerativetoday.com/)
如果你能学会如何使用一个好的页面生成器,你就可以用任何主题创建一些非常酷的东西!
结论
我写这篇文章是因为我不得不花很多时间去寻找一些免费的主机和域名,在那里我可以托管我的代码。我花了一段时间才找到他们。这些类型的免费资源对初学者甚至是刚起步的公司都非常有用。希望对你有帮助。如果你和他们一起做了很酷的事情,请不要犹豫在评论区分享。
阅读推荐:
让我们炫耀一下
towardsdatascience.com](/how-to-show-off-your-data-science-or-software-engineering-skills-effectively-dca18e059b38) [## 想在 12 周内成为数据科学家?
花钱前再想一想
towardsdatascience.com](/want-to-become-a-data-scientist-in-12-weeks-3926d8eacee2) [## 学习编程、软件工程、机器学习等的最佳免费资源
找到所有高质量的计算机科学课程,从麻省理工学院,哈佛大学,和其他大的大学成为专家…
towardsdatascience.com](/best-free-resources-to-learn-programming-software-engineering-machine-learning-and-more-89ee724b90c3) [## 使用 Python 从零开始的多类分类算法:分步指南
本文介绍两种方法:梯度下降法和优化函数法
towardsdatascience.com](/multiclass-classification-algorithm-from-scratch-with-a-project-in-python-step-by-step-guide-485a83c79992) [## 使用 Python 的 Scikit-Learn 库:机器学习,用几行代码实现简单的 KNN 分类器
用一个项目清晰地解释一些核心的机器学习概念
towardsdatascience.com](/simple-knn-classifier-with-four-lines-of-code-for-beginners-machine-learning-5344d125360f) [## Pandas 的 Groupby 功能详细,可进行高效的数据汇总和分析
学习对数据进行分组和汇总,以使用聚合函数、数据转换、过滤、映射、应用函数…
towardsdatascience.com](/master-pandas-groupby-for-efficient-data-summarizing-and-analysis-c6808e37c1cb)**
解锁您已经拥有的物联网数据
物联网
您的建筑物中已经存在的 5 种传感器数据来源
扎克·沃尔夫在 Unsplash 上的照片
M 机器学习模型极大地受益于物联网传感器数据。AI 和 IoT 早就是一丘之貉,就问连线。
物联网(IoT)是所有的乐趣和游戏,直到是时候购买传感器。众所周知,物联网难以扩展和管理— 30%的物联网项目从未通过概念验证阶段。
但是,如果我告诉您,您的建筑物中已经有未开发的物联网传感器数据,会怎么样?每座现代商业建筑都布满了传感器。它们已经大规模安装和管理。问题是它们是用于单一用途的封闭系统。
但是,如果您可以解锁传感器数据,它们可以在不需要新硬件的情况下为许多数据科学项目提供动力。
这里有 5 个你应该探索利用的企业系统:
1.建筑管理系统(BMS)
楼宇管理系统包括一些控制建筑环境的技术。最常见的包括照明控制系统和 HVAC(空调)系统。
最基本的照明控制系统包括房间和走道中的运动传感器——以前当灯熄灭时,你可能不得不在房间里挥舞手臂。更先进的照明控制系统包括窗户附*的流明传感器,可以在自然光较多的区域调暗灯光以节省电力。
HVAC 系统包括遍布整个建筑的温度传感器,以根据需要调节冷却和加热。
这两个系统在整个建筑中都有环境传感器,只需要一些创造性的集成,就可以将这些传感器反馈用于物联网应用。事实上,这种转变已经发生,因为 BMS 应用受益于物联网集成。
这是一个伪数据集,显示了您可能从 BMS 运动传感器获得的信息:
{
"zoneID": 129043805,
"zoneName": "**Front Lobby**",
"events": [
"movement": **FALSE**, "timestamp": "**2012-04-21T18:00:00-05:00**"},
"movement": **FALSE**, "timestamp": "**2012-04-21T18:30:00-05:00**"},
"movement": **TRUE**, "timestamp": "**2012-04-21T19:00:00-05:00**"}]
}
2.实时定位系统(RTLS)
作为物联网的鼻祖, RTLS 十多年来已经证明了物联网如何提供价值。事实上,预计到 2024 年,RTLS 市场将增长至* 120 亿美元。
RTLS 包括放置在企业想要跟踪位置的资产或人员上的标签。这些标签与传感器和网关的专用基础设施进行通信。基于这些通信,一种算法计算出标签在室内*面图上的估计位置。
RTLS 用于减少寻找物品所花费的时间,并自动化具有位置驱动事件的工作流。RTLS 是物联网应用背景数据的金矿。
以下是您可能从 RTLS 获得的伪数据集:
{
"tagID": 129043805,
"assetName": "**Kevin's backpack**",
"zone": "**Front Lobby**",
"location": [
"X": 1.34095, "Y": "5.3257", "variance": 2, "timestamp": "**2012-04-21T18:00:00-05:00**"},
],
}
3.安全监控系统
由paweczerwiński在 Unsplash 上拍摄的照片
几乎每个商业建筑都有安全摄像头进行基本监控。至少,它们用于记录活动,以防现场犯罪需要证据。
一段时间以来,这些安全监控系统已经从老式的学校同轴电缆连接过渡到可以在您的内部网络上访问的基于 IP 的连接。蜂窝视频摄像头连接的增长预计将从 2019 年的 370 万增长到 2024 年的超过 2000 万。
可以在这些现有的视频流上使用计算机视觉(CV)来收集上下文数据并生成警报。CV 由机器学习(ML)模型提供支持,这些模型经过训练,可以识别一系列图像中的对象、人和事件。
这是一个伪数据集,你可以从 CV 算法中得到:
{
"cameraID": 129043805,
"location": '**Front Lobby**',
"objects": [
{"type": "object", "name": "**coffee**", "confidence": 75},
{"type": "person", "name": "**Kevin Ferris**", "face mask": **TRUE**, "confidence": 98}]
}
4.门禁系统
照片由 Mauro Sbicego 在 Unsplash 拍摄
大多数办公室不会让任何人大摇大摆地进来——你需要证明你被允许在那里。门禁系统为你用来开门的徽章阅读器供电。
通常,这些标签阅读器使用无源 RFID 技术。这意味着读取器完成所有工作,因此徽章不需要电池。
员工徽章实际上是一种物联网设备。它是员工身份和凭证的小型物理表示。
一些物联网应用需要识别人员来提供个性化体验。但是,没有人想携带另一个“东西”来启动物联网解决方案。也不是每个人都希望他们的手机被追踪——去年主要新闻媒体敦促消费者禁用位置追踪。
员工徽章和您的访问控制系统是将人类身份输入物联网应用程序的可重复模型。
以下是您可能从访问控制系统中获得的伪数据集:
{
"readerID": 129043805,
"location": '**Front Lobby**',
"events": [
{"eventID": 452902, "badgeID": 12345, "name": "**Kevin Ferris**", "timestamp": "**2012-04-21T18:25:43-05:00**"}]
}
5.无线网络接入点
米沙·费舍切克在 Unsplash 上拍摄的照片
人们需要连接到互联网。以太网已经不再适用了——无线互联网连接在几乎所有的商业环境中都是不可或缺的。
天花板上的每个 Wi-Fi 接入点都是网络连接的网关。但是,您是否考虑过它们也可以成为物联网网关?
一些传感器端点可以通过 Wi-Fi 回程,因此您不必购买独立的物联网网关。代价是 Wi-Fi 连接增加了小型物联网设备的功耗。
为了帮助解决这个问题,许多 Wi-Fi 供应商正在为他们的接入点增加其他无线通信无线电,例如:
这些无线电对于物联网设备来说更加省电,这意味着它们可以在不牺牲性能的情况下使用电池持续更长时间。
检查您的 Wi-Fi 接入点,了解他们有哪些可用于物联网回程的无线无线电。
最后,企业 Wi-Fi 供应商经常向客户的 IT 网络支持团队提供分析工具,如思科的 DNA *台或 Aruba 的运营智能*台。虽然这些系统侧重于问题解决的性能指标,但它们也经常捕获可用于了解谁的设备连接到哪里的客户端数据。
将数据整合在一起,讲述一个故事
根据上面的伪数据集,如果没有被孤立起来,这些系统有数据来讲述一个令人信服的故事。
一个引人注目的物联网数据云*台可以实现这一梦想,但这并不容易:
[## 如果我们想要发展物联网行业,我们需要重新设计物联网*台体验
扩展物联网时的 3 大客户痛点
medium.com](https://medium.com/datadriveninvestor/does-the-customer-need-an-iot-platform-10c52286586d)
作为一个示例事件,当我进入办公室的主大厅时,我们可以利用现有的基础架构捕捉到以下内容:
- BMS 系统知道至少有一个人在主大厅
- RTLS 系统知道凯文的背包在大厅里
- 安全监视系统证明凯文在大厅里
- 门禁系统知道凯文在大厅扫描了他的徽章
- Wi-Fi 接入点知道 Kevin 的 iPhone 连接到网络
您已经拥有物联网传感器,去解锁它们吧
3 分钟解锁 GitHub 的隐藏功能
关于如何定制 GitHub 个人资料页面的简单说明
视频版本
介绍
GitHub 最*有一个巨大的更新,显然他们增加了一个隐藏的功能!这可以让你自定义你的个人资料页面,我认为这是一个很酷的功能。这只要几步就能做到!
步伐
- 前往你的 GitHub 页面
- 创建新的存储库
- 用您的用户名命名您的存储库以解锁隐藏功能
- 初始化 READ.me 文件
- 玩降价游戏,根据自己的喜好定制
- 尽情享受你酷炫的个人资料页面吧!
最后一眼
希望你觉得这个教程有用!感谢您的阅读!
在 AB 测试中解锁偷看
(图片来自 Unsplash 由 Pawel Szvmanski 拍摄)
入门
像 Optimizely 这样的实验*台是如何忽略 AB 测试中最基本的原则之一的
在 AB 测试的世界里,你学到的第一条原则是:在你达到最小样本量之前,不要看你的结果!
像 Optimizely 这样的 AB 测试*台使用户能够在实验进行的任何时候监控甚至解释测试结果。但是这些产品怎么能允许从业者忽略 AB 测试的这个非常基本的原则呢?
在这篇文章中,我阐明了:
- 为什么偷看可以帮助提高业务数字但同时会损害传统 AB 测试的结果,
- 顺序测试如何允许连续监控 AB 测试和
- 最大的 AB 测试*台如何优化利用顺序测试来允许具有不同测试偏好的用户在其可视界面中监控他们的测试:
优化监控 AB 测试(图片由作者提供)
我们为什么要偷看
收集足够的样本可能需要几周甚至几个月的时间,我们有充分的理由想要更早地检查我们的结果(也称为偷看)。
我们希望 peek 能够最大限度地减少不良测试的危害,最大限度地提高良好测试细胞的益处。
让我们假设我们正在测试网站结账页面的新设计。如果设计损害了转换率,我们希望尽早停止实验,以防止任何进一步的收入损失。另一方面,如果设计迫使更多的用户完成他们的购买,我们也希望尽早停止测试,这样所有的用户都可以接触到新的设计。在这两种情况下,提前结束实验都会对我们的收入产生积极影响。
为什么你不应该偷看 AB 测试
在获得所需的最小样本量之前不检查结果是 AB 测试的基本原则之一。这是由于固定样本量测试的性质以及它们是如何进行的:
- 设置参数,如显著性水*、功率水*和最小可检测效应
- 计算最小所需样本量nn
- 运行测试并收集每个测试单元的 n 个样品
- 计算 p 值,如果 p 值低于选定的显著性阈值,则拒绝零假设
人们可能会试图持续监测 p 值,一旦它低于我们的显著性阈值,就反驳零假设。看看下面的图表。当我们获得更多样本时,p 值会随时间波动。假设我们在获得所需的样本大小 n 之前查看了 8 次,由黑点和红点表示。每次我们查看并且 p 值达到显著性阈值(用红点标记)时,我们都会拒绝零假设:
随着时间的推移监测 p 值(图片由作者提供)
听起来很直观,不是吗?
不幸的是,持续监控我们的测试统计数据(或偷看)增加了反驳零假设的可能性,尽管没有真正的效果。当建立一个实验时,我们使用显著性水*来设置我们想要的假阳性率(或 I 型错误率)。显著性水*决定了我们错误地得出实验中存在真实效应的概率。因此,如果我们将显著性水*设定为 5%,那么我们错误地推断我们的新设计、活动或功能具有真实效果的可能性不超过 5%。
控制错误拒绝零假设的风险在 AB 测试中至关重要。
在 AB 测试中,控制我们做出错误决定的概率并拒绝零假设是至关重要的,尽管它是真实的。想象一下,我们错误地认为一个昂贵的活动对我们的测试用户有积极的影响,现在我们把它推广给其他人。我们会白白花很多钱!
窥视和假阳性率(或 I 型错误)
测试的假阳性率是基于这样的假设,即在获得足够的样本后,我们只检查结果一次。计算所需的最小样本量,以便当我们有足够的样本时,在查看结果时错误地反驳零假设的概率是选定的显著性水*。相反,如果我们在进行测试时检查结果 k 次,我们有 k 次机会根据结果做出错误的决定。因此,每次查看结果时,我们测试的 I 型错误率都会增加!
通过窥视,我们最终会得到比实际值小得多的名义 p 值。因此,我们更有可能反驳一个真正的假设。
一旦 p 值低于显著性水*,我们就不能停止测试并反驳零假设。
因此,在经典 AB 测试设置中获得可靠测试结果的唯一方法是等待测试收集足够的样本,然后决定是否反驳零假设。在我们进行测试和收集样本时,不管你的测试结果看起来有多离谱。
但幸运的是,所谓的连续 AB 测试允许连续监测 p 值,并根据观察到的结果更早地做出否定假设的决定!让我们来看看这些是如何工作的。
允许通过连续的 AB 测试进行窥视
在序贯 AB 检验中,AB 检验停止时的最终样本量取决于我们在检验过程中观察到的数据。因此,如果我们在开始时观察到更极端的结果,测试可以更早结束。为了实现这一点,绘制了一对统计边界,例如基于我们希望在测试中获得的 I 类错误率。对于我们获得的每个新数据点,我们的数据的对数似然比之和将与这些边界进行比较:
连续 AB 测试(图片由作者提供)
如你所见,我们收集的样本越多,界限就越窄。这意味着在开始时,我们将不得不看到非常极端的影响,以尽早停止测试。随着样本越来越多,我们获得了更高的精度(或功效),因此我们可以检测到更小的影响,从而边界可以变得更窄。
基于此,可以在测试运行的任何时间点实施以下决策规则:
- 没有越界:继续测试
- 上界交叉:反驳零假设
- 下边界交叉:停止测试,不反驳零假设
有了这套规则,如果存在强烈的影响,测试可以在少量样本后就停止!因此,如果我们从实验一开始就看到非常积极的效果,我们的奇妙设计可能已经发布给用户了。
Optimizely 的统计引擎背后的逻辑
免责声明:根据我的经验,所有主要的 AB 测试*台都提供了实时监控实验的可能性。我特别提到 Optimizely,因为他们的方法已经发表在 科学论文 中。
Optimizely 将自己描述为世界领先的渐进式交付&实验*台。与其他供应商相比,他们的竞争优势之一是其复杂的统计引擎。其中,该模型允许用户实时监控他们的测试,并就何时停止实验做出明智的决定。
优化监控 AB 测试(图片由作者提供)
顺序 AB 测试的问题是
当使用顺序 AB 测试时,我们通常权衡检测率(或功率)和运行时间。我们越愿意进行更长时间的测试,我们的检出率就越高。
有各种顺序 AB 测试模型,在功耗和运行测试的时间量之间有不同的权衡。当然,我们总是可以为我们的特定用例选择正确的模型,并在检测率和我们希望运行测试的时间量之间取得*衡。但是这在 AB 测试*台中是不可行的,AB 测试*台有大量的用户基础,并且关于这种*衡有非常不同的偏好。
因此,Optimizely 团队认为用户需要自己决定功率和样本大小之间的*衡。在这个目标的基础上,开发了总是有效的 p 值的概念。
构建始终有效的 p 值
如果你走到这一步,请忍耐。下一段会有一点技术性,但是我尽量简单明了的把它分解了!下面是始终有效的 p 值的定义:
给定一个序贯检验,观察值 I 处始终有效的 p 值被定义为最小的α【或显著性水*】,因此α水*检验将在点 I 处停止并拒绝 H0。
这意味着,在测试过程中的任何时候,你都可以要求一个最小的显著性阈值,在这个阈值上,你已经拒绝了零假设。如果该水*低于用户期望的显著性水*(例如 5%),则可以拒绝零假设,并且用户可以结束测试。这个过程保证了当我们决定拒绝零假设时,零假设事实上为真的概率(I 型错误率)在任何时间点都等于或小于我们期望的显著性水*。
为了计算测试统计数据,Optimizely 的统计引擎建立在一种特定类型的顺序测试之上,称为混合顺序概率比测试 (mSPRT)。这种类型的顺序测试提供了运行时间和功率之间的最佳*衡。mSPRT 背后的逻辑很简单:
- 使用我们拥有的 n 个样本,计算针对零假设(没有影响)的似然比。
- 如果似然比λ高于我们的显著性阈值,则拒绝零假设。
可以证明,如果存在真正的差异,mSPRT 将总是在某个点拒绝零假设。否则,它将永远继续运行。
数据点 n 的始终有效的 p 值被定义为前一数据点的 p 值和新的对数似然比的最小值,包括所有可用的数据点:
始终有效的 p 值可以在任何时间点以流式方式计算。可以在 Optimizely UI 中持续监控该指标,一旦达到预定义的显著性阈值,用户就可以停止测试。用户也可以决定在他们个人的最大运行时间提前结束测试,从而以统计能力为代价减少运行时间。
摘要
众所周知,在获得最小样本量之前,不应该检查 AB 测试的结果。与此同时,我们有充分的理由想要偷看。尽早停止测试会对企业产生重大的经济影响。
幸运的是,有一些方法,如连续 AB 测试和始终有效的 p 值,可以持续监控实验结果。我希望这篇文章很好地概述了这些方法是如何工作的,以及它们背后的思想是什么。快乐偷看!****
参考资料和进一步阅读
- 在线 AB 测试中的统计方法(乔治·乔尔杰夫)
- 窥视 AB 测试:为什么它很重要,以及如何处理它(乔哈里等人)
- 新的统计引擎
- 始终有效的推断:持续监控 A/B 测试
更多关于 AB 测试的文章:
脸书和他的同事如何在 AB 测试中克服互联用户的挑战。
towardsdatascience.com](/ab-testing-challenges-in-social-networks-e67611c92916) [## 为 AB 测试找到正确的显著性水*
为什么使用默认的 95%显著性和 80%功效水*的 AB 测试可能无法评估业务风险…
towardsdatascience.com](/finding-the-right-significance-level-for-an-ab-test-26d907ca91c9)**
释放支持向量回归的真正力量
图片来自 Pixabay
用支持向量机解决回归问题
支持向量机是机器学习中处理分类问题的最流行和最广泛使用的算法之一。然而,支持向量机在回归中的使用并没有很好的记录。该算法承认数据中非线性的存在,并提供了一个熟练的预测模型。
在本文中,我将首先通过深入研究算法背后的理论,让您对算法有一个直观的理解。然后,我们将建立我们自己的 SVM 回归模型。最后,我们将探讨使用支持向量回归机的一些优点。
SVM 回归算法被称为支持向量回归或 SVR 。在开始学习算法之前,我们有必要对支持向量机有一个直观的了解。
支持向量机
在机器学习中,支持向量机是具有相关学习算法的监督学习模型,这些算法分析用于分类和回归分析的数据。在支持向量回归中,拟合数据所需的直线被称为超*面。
图片来自维基共享资源
支持向量机算法的目标是在 n 维空间中找到一个超*面,该超*面清楚地分类数据点。超*面两侧最接*超*面的数据点称为支持向量。这些影响超*面的位置和方向,从而有助于建立 SVM。
SVR 中的超参数
既然我们对什么是支持向量机有了直观的认识,我们将研究支持向量回归中使用的各种超参数。使用的一些关键参数如下所述:
1。超*面:
超*面是用于预测连续输出的决策边界。超*面任一侧最接*该超*面的数据点称为支持向量。这些用于绘制显示算法预测输出的所需线条。
2。内核:
内核是一组数学函数,它将数据作为输入,并将其转换为所需的形式。这些通常用于在高维空间中寻找超*面。
图片来自 Sci Kit Learn
应用最广泛的核包括线性、非线性、多项式、径向基函数和 Sigmoid 。默认情况下,RBF 用作内核。这些核的使用取决于数据集。
3。 边界线:
这是在超*面周围距离ε(ε)处画的两条线。它用于在数据点之间创建边距。
支持向量回归
支持向量回归是一种监督学习算法,用于预测离散值。支持向量回归机使用与支持向量机相同的原理。支持 SVR 的基本思想是找到最佳拟合线。在 SVR 中,最佳拟合线是具有最大点数的超*面。
图片来自 Semspirit
与试图最小化实际值和预测值之间的误差的其他回归模型不同,SVR 试图在阈值内拟合最佳直线。阈值是超*面和边界线之间的距离。SVR 的拟合时间复杂度大于样本数量的二次方,这使得它很难扩展到具有超过 10000 个样本的数据集。
对于大型数据集,使用线性 SVR 或 SGD 回归器。线性 SVR 提供了比 SVR 更快的实现,但是只考虑线性核。由支持向量回归产生的模型仅依赖于训练数据的子集,因为成本函数忽略了预测接*其目标的样本。
图片来自 MathWorks 博客
现在我们有了什么是支持向量回归机的要点,我们将尝试构建我们自己的 SVR 回归机。构建这个回归模型的代码和其他资源可以在这里找到。
第一步:导入所需的库
我们的第一步是导入构建模型所需的库。没有必要在一个地方导入所有的库。Python 给了我们在任何地方导入库的灵活性。首先,我们将导入 Pandas、Numpy、Matplotlib 和 Seaborn 库。
#Import the Libraries and read the data into a Pandas DataFrameimport pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as snstest = pd.read_csv("california_housing_test.csv")
train = pd.read_csv("california_housing_train.csv")
一旦导入了这些库,我们的下一步将是获取数据集并将数据加载到我们的笔记本中。对于这个例子,我使用了加州住房数据集。
第二步:可视化数据
成功加载数据后,我们的下一步是可视化这些数据。海滨是一个优秀的库,可以用来可视化数据。
#Visualise the dataplt.figure()
sns.heatmap(data.corr(), cmap='coolwarm')
plt.show()sns.lmplot(x='median_income', y='median_house_value', data=train)
sns.lmplot(x='housing_median_age', y='median_house_value', data=train)
第三步:特征工程
特征工程是利用领域知识通过数据挖掘技术从原始数据中提取特征的过程。对于这个模型,我选择了只有数值的列。为了处理分类值,应用了标签编码技术。
#Select appropriate featuresdata = data[[‘total_rooms’, ‘total_bedrooms’, ‘housing_median_age’, ‘median_income’, ‘population’, ‘households’]]
data.info()data['total_rooms'] = data['total_rooms'].fillna(data['total_rooms'].mean())
data['total_bedrooms'] = data['total_bedrooms'].fillna(data['total_bedrooms'].mean()
要素缩放基本上有助于在特定范围内归一化数据。通常,几个常见的类类型包含特征缩放功能,以便自动进行特征缩放。
第四步:拟合模型
选择所需参数后,下一步是从 sklearn 库中导入 train_test_split,该库用于将数据集拆分为训练和测试数据。
#Split the dataset into training and testing dataimport train_test_split
X_train, X_test, y_train, y_test = train_test_split(train, y, test_size = 0.2, random_state = 0)y_train = y_train.reshape(-1,1)
y_test = y_test.reshape(-1,1)
在此之后,从 sklearn.svm 导入 SVR ,并且模型适合训练数据集。
# Fit the model over the training datafrom sklearn.svm import SVR
regressor = SVR(kernel = 'rbf')
regressor.fit(X_train, y_train)
这里,在这个特殊的例子中,我使用了 RBF 核。模型的其他参数保留其默认配置。一旦模型适合训练数据,我们的模型就可以使用了。
支持向量回归的优势
尽管支持向量回归很少使用,但它具有如下优点:
- 它对异常值是鲁棒的。
- 决策模型可以很容易地更新。
- 它具有良好的泛化能力,预测精度高。
- 它的实现很容易。
图片由戴尔·阮
支持向量回归的缺点
支持向量机在处理回归问题时面临的一些缺点如下所述:
- 它们不适合大型数据集。
- 如果每个数据点的特征数量超过了训练数据样本的数量,则 SVM 将表现不佳。
- 当数据集具有更多噪声时,即目标类重叠时,决策模型的表现不是很好..
至此,我们已经到了这篇文章的结尾。我希望这篇文章能够帮助您了解 SVR 算法背后的思想。如果你有任何问题,或者如果你认为我有任何错误,请联系我!您可以通过邮箱或 LinkedIn 与我联系。
Python 中的解包运算符
在 Python 中使用*和**解包运算符
马库斯·斯皮斯克在 Unsplash 上的照片
介绍
在本教程中,我们将学习如何使用星号()操作符解包可迭代对象,以及如何使用两个星号(**)解包字典。此外,我们将讨论如何使用同一个运算符将几个值打包到一个变量中。最后,我们将讨论什么是args 和**kwargs 以及何时可以使用它们。
*操作员
假设我们有一个列表:
num_list = [1,2,3,4,5]
我们定义了一个函数,它接受 5 个参数并返回它们的和:
def num_sum(num1,num2,num3,num4,num5):
return num1 + num2 + num3 + num4 + num5
我们想找出 num_list 中所有元素的总和。嗯,我们可以通过将 num_list 的所有元素传递给函数 num_sum 来实现这一点。由于 num_list 中有五个元素,因此 num_sum 函数包含五个参数,每个参数对应于 num_list 中的一个元素。
一种方法是通过使用元素的索引来传递元素,如下所示:
num_sum(num_list[0], num_list[1], num_list[2], num_list[3], num_list[4])# 15
然而,有一种更简单的方法,那就是使用操作符。操作符是一个解包操作符,它将解包来自任何可迭代对象的值,例如列表、元组、字符串等…
例如,如果我们想解包 num_list 并传入 5 个元素作为 num_sum 函数的独立参数,我们可以这样做:
num_sum(*num_list)# 15
就是这样!星号*或解包操作符解包 num_list ,并将 num_list 的值或元素作为单独的参数传递给 num_sum 函数。
注意:为了实现这一点, num_list 中的元素数量必须与 num_sum 函数中的参数数量相匹配。如果它们不匹配,我们会得到一个类型错误。
*带内置函数的运算符:
我们还可以在 python 的内置函数中使用星号,*或解包操作符,比如 print:
print(*num_list)# 1 2 3 4 5
解包多个列表:
假设我们有另一个列表:
num_list_2 = [6,7,8,9,10]
我们希望打印出 num_list 和 num_list_2 中的所有元素。我们可以使用解包操作符*来实现这一点,如下所示:
print(*num_list, *num_list_2)# 1 2 3 4 5 6 7 8 9 10
两个 num_list 和num _ list _ 2都被解包。然后,所有的元素都作为单独的参数传递给 print。
合并多个列表:
我们还可以创建一个新的列表,包含来自 num_list 和 num_list_2 的所有元素:
new_list = [*num_list, *num_list_2]# [1,2,3,4,5,6,7,8,9,10]
num _ list和num _ list _ 2被解包,导致它们的元素构成新制作的 list 的元素,new _ list。
注意:我们可以简单地添加 num_list 和 num_list_2 来创建 new_list 。然而,这只是为了描述拆包操作员的功能。
了解什么是 walrus 操作符以及如何在 Python 中使用它
towardsdatascience.com](/the-walrus-operator-in-python-a315e4f84583)
*运算符的其他用途:
假设我们有一个字符串分配给变量 name :
name = ‘Michael’
我们想把这个名字分成三部分,第一个字母分配给一个变量,最后一个字母分配给另一个变量,中间的所有字母分配给第三个变量。我们可以这样做:
first, *middle, last = name
就是这样!由于 name 是一个字符串,而字符串是可迭代的对象,所以我们可以对它们进行解包。赋值操作符右边的值将根据它们在 iterable 对象中的相对位置被赋给左边的变量。因此,“Michael”的第一个字母被分配给变量 first ,在本例中为“M”。最后一个字母‘l’被分配给变量 last 。而变量中间会以列表的形式包含‘M’和‘l’之间的所有字母:[‘I’,‘c’,‘h’,‘a’,‘e’]。
注意:上面的第一个和最后一个变量被称为强制变量,因为它们必须被赋予具体的值。由于使用了*或解包操作符,中间的变量可以有任意数量的值,包括零。如果没有足够的值来解包强制变量,我们将得到一个 ValueError。
例如,如果我们使用下面的赋值语句:
first, *middle, last = ‘ma'
然后,变量第一个将被赋值为“m”,变量最后一个将被赋值为“a”,而变量中间的将只是一个空列表,因为没有其他值要赋值给它。
用三元运算符改进您的 Python 代码!
towardsdatascience.com](/ternary-operators-in-python-49c685183c50)
用*运算符打包:
我们还可以使用*运算符将多个值打包到一个变量中。例如:
*names, = ‘Michael’, ‘John’, ‘Nancy’# names
['Michael', 'John', 'Nancy']
在* 名称后使用尾随逗号的原因是因为赋值的左边必须是元组或列表。因此, names 变量现在以列表的形式包含了右侧的所有名字。
注意:这就是我们在定义可以接收不同数量参数的函数时所做的事情!那就是*args 和**kwargs 的概念!
*参数:
例如,假设我们有一个函数, names_tuple ,它接受名字作为参数并返回它们。然而,我们传递给这个函数的名字的数量是可变的。我们不能只选择这个函数的一些参数,因为位置参数的数量会随着函数的每次调用而变化。我们可以使用*运算符将传入的参数打包成一个元组,如下所示:
def names_tuple(*args):
return argsnames_tuple('Michael', 'John', 'Nancy')
# ('Michael', 'John', 'Nancy')names_tuple('Jennifer', 'Nancy')
# ('Jennifer', 'Nancy')
无论我们在调用 names_tuple 函数时传入多少个位置参数,* args 参数都会将位置参数打包到一个元组中,类似于上面的* names 赋值。
*** 夸格斯*
为了传递不同数量的关键字或命名参数,我们在定义函数时使用了操作符。解包操作符将把我们传入的不同数量的命名参数打包到一个字典中。
def names_dict(**kwargs):
return kwargsnames_dict(Jane = 'Doe')
# {'Jane': 'Doe'}names_dict(Jane = 'Doe', John = 'Smith')
# {'Jane': 'Doe', 'John': 'Smith'}
注意:当使用*运算符创建一个在定义函数时接收不同数量的位置参数的参数时,通常使用参数名 args(和 kwargs 来接收不同数量的关键字或命名参数)。但是,可以为这些参数选择任何名称。
Python 中的可迭代对象、迭代器和迭代
towardsdatascience.com](/iterables-and-iterators-in-python-849b1556ce27)
字典
当我们尝试在字典中使用*操作符时会发生什么?
num_dict = {‘a’: 1, ‘b’: 2, ‘c’: 3}print(*num_dict)# a b c
注意它是如何打印字典的键而不是值的?要解包一个字典,我们需要使用**解包操作符。但是,由于每个值都与一个特定的键相关联,所以我们将这些参数传递给的函数必须具有与被解包的字典的键同名的参数。例如:
def dict_sum(a,b,c):
return a+b+c
这个 dict_sum 函数有三个参数: a 、 b 和 c 。这三个参数的命名与 num_dict 的键相同。因此,一旦我们使用**操作符传入解包的字典,它将根据相应的参数名分配键值:
dict_sum(**num_dict)# 6
因此,对于 a 、 b 和中的参数 dict_sum 将分别为 1、2 和 3。这三个值之和是 6。
合并字典:
就像列表一样,操作符可以用来合并两个或更多的字典:**
**num_dict = {‘a’: 1, ‘b’: 2, ‘c’: 3}num_dict_2 = {‘d’: 4, ‘e’: 5, ‘f’: 6}new_dict = {**num_dict, **num_dict_2}# {‘a’: 1, ‘b’: 2, ‘c’: 3, ‘d’: 4, ‘e’: 5, ‘f’: 6}**
如果你喜欢阅读这样的故事,并想支持我成为一名作家,考虑注册成为一名媒体成员。每月 5 美元,你可以无限制地阅读媒体上的故事。如果你用我的 链接 注册,我会赚一小笔佣金。
阅读卢艾·马塔尔卡的每一个故事(以及媒体上成千上万的其他作家)。您的会员费直接支持…
lmatalka90.medium.com](https://lmatalka90.medium.com/membership)**
结论
在本教程中,我们学习了如何使用*运算符解包可迭代对象,以及如何使用运算符解包字典。我们还学习了利用这些操作符完成许多不同任务的许多方法。此外,在定义接收不同数量的位置或命名参数的函数时,我们通过使用和**操作符简要讨论了用args 和kwargs 打包的概念。
解开客户流失及其挑战
Jornada Produtora 在 Unsplash 上拍摄的照片
了解降低业务流失率的原因和方法
关系管理是企业健康发展的决定性因素之一。这种联系的最重要因素之一是识别客户何时可能取消服务的能力。因此,有必要采取措施最大限度地留住客户。
因此,识别易流失客户的项目已成为组织经常关注的问题,因为保留客户的成本通常低于获取客户的成本。
虽然它获得了许多公司的关注,但没有解决流失问题的神奇公式。此外,该解决方案可能有许多复杂性,如确定客户流失原因以应用不同的保留策略。
挑战
获取新客户的成本是否大于留存成本?
为了获得和留住客户,观察财务和战略支出是至关重要的,因为对于一些公司来说,获得成本可能比留住成本高 5 倍。
什么类型的客户流失将被处理?
需要强调的是,产品或服务的流失会以多种方式增加,例如:
- 志愿者:当客户因对竞争对手不满或偏爱而选择取消服务时。
- 静默:当客户长时间停止使用服务,并且不产生费用时发生——就像使用没有月费的信用卡一样。
- 非自愿:当消费者不打算取消服务,但由于疏忽,他的计划可能因不正常使用、未付款等原因而未能延期或取消。
你的专家对这个问题了解多少?
拥有一个熟练的团队对于分析项目是否可以内部执行或者是否需要外包帮助是非常重要的。个性化的解决方案和有准备的专业人员可以帮助克服问题的挑战,并获得丰富和适用的结果。
您是否有一个数据库可以让您提取关于企业及其客户的信息?
一个可靠的数据库使项目执行更加可行,并产生可靠的结果。这是获得客户知识,进而了解如何规划和开发您的解决方案的基本步骤。这就引出了下一个问题:
你有多了解你的客户?
还需要诊断您的行为如何影响客户,为此,您需要收集定义客户个人特征和行为的信息。这种分析是确定他们是否容易流失的关键。
解决的方法
说到解决问题,专家团队还需要克服一些挑战。第一个是关于结合技术知识和业务理解,因为探索性分析和特性工程必须考虑组织模型才能成功。
在特征整合和业务洞察插入之后,是时候开始建模了。在这一阶段,你可能会遇到不*衡的数据,换句话说,通过划分客户群和忠实客户群,你可能会发现更高比例的忠实客户。
不*衡数据的最大问题是,如果不解决这个问题,机器学习算法往往只会对大多数类别有良好的响应。这意味着许多假阴性的产生,因为有一种倾向将可能离开的客户归类为忠诚客户。
处理不*衡数据的技术
在这一点上,有必要使用技术来解决不*衡的数据集问题,并优化客户行为的过滤器。其中我们可以提到一些最常见的:过采样、欠采样、SMOTE 和 ADASYN。值得一提的是,他们都不是通才,这就解释了为什么每个问题都要根据其特殊性来对待。
欠采样和过采样是更基本的技术,分别意味着代表性大的类的减少和代表性小的类的扩大。
SMOTE 和 ADASYN 更复杂,它们对数据进行合成采样。两者都是类似的策略,但 ADASYN 使用密度分布来创建合成元素。
了解您的客户流失解决方案的性能
客户流失模型必须建立在预期响应的基础上,关注性能和输出应该如何呈现。衡量模型性能时,选择正确的评估指标非常重要。例如,准确性可能会给我们一个令人震惊的模型的错误感觉,然而,结果可能是由于只对多数类进行了正确的分类——其中不存在流失。
这种评估可以集中在解决方案在多大程度上改进了您当前的保留策略。如果我们认为保留行为是在随机客户上进行的,我们可以评估模型所指示的样本将在多大程度上改进对易于流失的客户的选择。
传统的评估指标,如精确度和召回率,也非常有用。前者是正确指示的数量占指示总数的比例,而第二个是正确分类的流失客户占流失总数的百分比。另一种方法是 f1 分数,可描述为:
F1 = 2 (精度召回)/(精度+召回)
了解结果
为了评估要使用的指标,考虑到预期未来收入的潜力(终身价值——LTV),了解留住客户的运营成本至关重要。
LTV 高的客户可能认为保留客户需要更高的费用,而 LTV 低的客户可能认为保留客户的投资不值得。
从保留客户的参数的知识中,该操作可以被标记出来,不管它是否使得错误分类的消费者的接受更加灵活。这个因素与产生误报的惩罚直接相关——当一个忠诚的客户被归类为流失时。
如果保留操作的成本较低,您可以选择标记更多的客户,从而获得大多数真正的客户。然而,这将导致出现更多的假阳性。同样,如果成本很高,为了避免不必要的开支,关注所选组的准确性是至关重要的。
在分类模型中,默认情况下,将客户分类为搅动者的阈值是离开服务的概率超过 50%。这个限制可以根据业务而改变,例如,如果需要更高的精度,我们可以将概率超过 70%的元素评估为仅流失元素。
模型
预期的输出会影响用来解决问题的策略。除了具有二元响应的分类算法之外,还有使用生存和混合模型的方法。
生存分析模型并不将客户归类为易流失或不易流失。生成的响应是一条曲线,可以用来跟踪每个客户随时间流失的概率。
为了克服涉及复杂和非线性风险函数的生存分析问题,已经开发了扩展二元分类并将它们的结果转化为生存分析的模型。这种模型被称为混合模型,其中一些是:RF-SRC,deepSurv 和 WTTE-RNN。
结论
总之,很明显,客户流失建模对于公司留住客户和降低成本至关重要。因此,有必要意识到这些资源的成功经历了几个方面——从公众的知识,到模型的复杂性和稳健性。如有任何疑问,请随时联系我!
解开 Python 的线程之谜。
Python 如何实现计算并行化?
现在我们的大多数计算机都有多核架构,多线程等术语经常在我们耳边响起,作为提高应用程序处理效率的一种方式。Python 确实提供了一些并行计算的工具,但是它们并不为人所知。让我们在这篇文章中揭开他们的秘密。
马修·施瓦茨在 Unsplash 上的照片
首先是关于线程的一个小提示。什么是线程?这是一个轻量级的进程,运行在你的计算机上,执行它自己的指令集。当你在计算机上运行两个程序时,你实际上创建了两个进程。它们中的每一个都有一组指令(打开你的浏览器或提高音量),它希望调度程序(决定向处理器提供什么的裁判)读取这些指令。线程相对于进程的特殊性在于它们可以共享变量。
就编码而言,当我们运行两个线程时,我们允许两段代码同时运行。然而,这不同于同时执行两个程序,因为线程给了我们更多的控制权。例如,我们可以在线程之间共享一些变量,或者我们可以等待线程完成,合并结果,然后继续执行其余的代码。这是一个非常强大的工具,可以允许更快的计算或处理并发事件的能力(想想有多个传感器数据要处理的机器人)。
让我们稍微跑题一下,分析一下 Python 提供的并行运行计算的不同可能性。这三位获奖者分别是:线程、线程池和多处理。
为了清楚起见,让我们首先介绍我们希望并行化的函数。睡眠功能,其目的是…睡眠。
- 线程:Python 可以提供的最基本的线程工具
Python 库允许我们手动创建线程,为此我们可以指定目标(我们希望在这个线程中执行的函数)及其参数。该接口还包括一个 start 函数和一个 join 函数,它将等待线程的执行结束。当我们想要利用线程返回的结果时,加入线程通常是可取的。但是基本的穿线。Thread 的局限性很大,它不允许我们访问 sleep 函数返回的变量。
2.线程池:(并且是库 concurrent.futures)
线程池执行器为线程提供了一套更完整的接口。然而,它的底层实现仍然使用线程库,这提供了与前一选项相同的优点和缺点。在接口差异方面,它提出了 future 的概念,这对于 C++ 14 的用户来说似乎会很熟悉。对我们来说,未来的最大优势在于它允许我们使用 result()接口线程化函数返回变量。
3.多重处理(来自库多重处理)
多重处理是能够提供线程能力的最完整的库。除了提供更多接口之外,它与其他两个工具的主要区别在于,它能够使用名为 pickle 的第三方库来序列化和反序列化数据。序列化是转换数据类型(int、array 等)的能力。)转化为二进制,0 和 1 的序列。这样做需要能够使用协议(tcp/ip、http、UDP……)发送数据,因为这些协议对我们使用的数据类型是不可知的:发送者可能在 Python 中运行他的代码,而接收者可能使用 C++。在多处理的情况下,当我们将函数和参数传递给池对象时,就会发生序列化。这允许我们做一些不可思议的事情:发送这个线程来执行..另一台电脑!因此,多重处理库旨在支持多台计算机之间的共享计算。
注意,多处理库提供了 apply (sync)和 apply_async 接口,代表同步和异步。在第一种情况下,线程被迫以它们启动时的顺序返回,而在第二种情况下,线程一结束就返回。apply_async 提供了一个额外的参数“callback ”,它提供了在线程返回时执行函数的可能性(例如,存储结果)。
现在是时候比较不同线程方法的结果了。我们首先使用前面提到的“sleep”函数对结果进行基准测试:
我们还依次计算睡眠函数,以提供用于比较的基本结果。每次睡眠 2 秒,我们得到顺序方法的总计算时间,这似乎是合乎逻辑的。对于线程化和多处理方法,我们获得了 2s 的计算时间,这意味着所有线程都可以成功地并行运行,但是对于线程池执行器,我们获得了 4s 的计算时间,这表明在该过程中有一些额外的计算时间开销。现在,只有 3 个线程并行运行是非常好的,但是我们可能想要运行超过 1000 个线程。让我们看看在更高的难度下,比如说 100 个线程:
至于线程和线程池,从 3 个线程到 100 个线程,结果没有变化。然而,对于多处理方法,计算时间跳到了 50 秒!为了理解发生了什么,让我们来看看我们精心放置的警告:“警告,试图创建比可用内核更多的线程”。因此,多处理将尝试将线程分派到可用的内核(在我的例子中是 4 个),但是如果没有内核可用,我们可以猜测线程的计算被排队,从而变得有序。
我们可以结束这个话题,以“多处理很糟糕,线程库很棒”结束。但是等一下。到目前为止,我们在线程函数中所做的只是休眠,这意味着在处理指令方面:什么都不做。现在,如果我们有一个对计算资源更加贪婪的线程会怎么样。让我们以计数函数为例:
我们使用 4 个线程(我个人电脑上的内核数量),可以看到以下结果:
这里的多处理方法至少比其他两种方法快 4 倍。但更重要的是,当最初的目标是优化计算时间时,线程化花费的时间几乎是顺序方法的两倍,而线程池方法花费的时间是顺序方法的两倍!
为了理解为什么会发生这种情况,我们需要看一下 GIL(全局解释器锁)。Python 是一种解释型语言,而 C 或 C++是编译型语言。编译所做的是将编写的代码转换成处理器可以理解的语言:二进制文件。因此,当代码被执行时,它直接被调度程序读取,然后被处理器读取。然而,在解释语言的情况下,在启动程序时,代码仍然是以人类可读的方式编写的,这里是 python 语法。为了被处理器读取,它必须在运行时被所谓的 Python 解释器解释。然而,当线程化时会出现一个问题。Python 解释器不允许同时解释多个线程,因此有一个锁,GIL 来强制执行。让我们看一张图来更好地理解这种情况:
在这种情况下,GIL 会成为瓶颈,抵消线程的优势。这很好地解释了为什么线程和池线程在对比分析中表现如此之差。每次线程各自的计数需要增加十亿次时,每个线程都会争着通过 Python 解释器进行访问,而当线程休眠时,Python 解释器在线程存在的整个过程中只被请求一次。如果是这样的话,为什么多重处理方法提供了明显更好的结果?该库实际上是通过为每个线程创建一个 Python 解释器实例来欺骗 GIL:
因此,瓶颈消失了,可以释放全部线程潜力。
值得一提的是,由于序列化过程,多处理仍然有一些缺点:不是所有东西都可以序列化。尤其是 pythongenerators或者任何有类似指针行为的东西都不能序列化(这看起来很正常)。假设我们有一个带有一些未知内容的“代理”对象(例如这里的一些 pytorch 对象),那么我们最终会得到一个错误:
我们可以从计算效率的角度来解释线程和多进程之间的区别。在第二部分中,我们可以仔细看看资源和变量管理的主要区别,尤其是对于共享资源。让我们考虑下面的代码,它让线程使用一个全局变量:
即使我们分派不同的处理器(子进程)访问同一个全局变量来评估代码片段,这段代码也运行得很好。这意味着所有的 python 解释器都能够访问这个全局变量。那么 python 解释器可以在运行时互相通信吗?这背后的巫术是什么?让我们考虑下面代码的变体:
这段代码的目的是在运行时从不同的线程中有意更改全局变量的值。不仅如此,他们还用不同的计时来做这件事(观察不同输入的睡眠)。
如果这个变量真的是全局变量,当第二个线程输出全局变量的值时,它应该已经增加了不少。然而,我们看到当线程打印它时,这个值始终为 1。所以现在我们对正在发生的事情有了更好的了解。当线程被创建时(python 解释器也随之创建),所有的变量都被复制到新线程中,全局变量也不例外。从某种意义上说,所有线程都是您正在运行的更大进程的相同副本,只是传递给线程的参数略有不同。现在,多重处理提供了与如何创建子进程相关的不同选项。我们可以选择三种启动方法,即 spawn、fork 和 forkserver。我们将分析前两个。根据 Python 文档:
因此,主要区别在于创建子流程时从父流程继承了哪些变量。考虑上面介绍的代码。如果您仔细观察,您会注意到 start 方法被指定为“fork”。让我们看看这段代码实际输出了什么:
这里没有什么特别的,我们知道子进程能够访问全局变量,因为它已经复制了它。现在让我们看看当我们切换到“产卵”时会发生什么:
我能看到你眼中惊讶的表情,不,你不是在做梦,你是多次看到这个。“print(“应该只看到这一次”)”是在程序的最开始制作的,完全在线程被调度的循环之外。然而这本书被印了 4 次。那么发生了什么?python 文档只告诉我们“父进程启动一个新的 Python 解释器进程”,并且它只继承 run()方法所必需的对象(理解您正在尝试线程化的函数)。由此你需要明白的是继承=复制,非 _ 继承=重求值。所以当我们选择“spawn”时,进程的每条指令都被重新解释,函数调用以及变量内存分配。
现在你可以注意到“if name == 'main ':”语句。这对解释器来说意味着,内部的任何东西都属于主进程,因此应该由子进程继承。对于每个子进程,不在该语句中的所有内容都将被重新评估。这意味着默认情况下“spawn”试图重新评估一切,但我们确实可以控制继承什么,而对于“fork”,默认情况下每个变量都会被继承。你可能想知道这有什么关系,在我们的全局变量中,它变化不大。但是对于某些对象,复制构造函数(当您使用=操作符时)可能没有很好地定义,这使得在某些情况下使用 fork 方法不安全。因此,在 python 文档中可以看到 spawn 方法正在成为所有*台的默认启动方法。
你可能想知道这是一个相当大的限制,如果由多重处理创建的线程被完全封闭,我们就失去了线程间一定程度同步的能力。这并不完全正确,因为该库提供了一些工具供我们使用:管道。
结果:“休眠时间:2.00”,这意味着线程实际上是在等待接收父进程提供的数据,然后再继续。在某种程度上,管道相当于 C++的未来,我们能够等待不同线程提供的一些数据的获取。当然,在这种情况下,由于多重处理可以在不同的计算机上发生,通过管道发送的数据也需要在幕后序列化。
我们谈到了多重处理的资源管理。对于线程库(和线程池执行器),事情有点不同,因为我们只有一个 python 解释器。让我们看一个例子:
我们同时启动 4 个线程,每个线程实际开始计数时会有一点不同的延迟。
与多重处理相反,全局变量在这里是跨线程共享的,并且不保存本地副本。如果你习惯了被操纵的线程,你可能会被上面的代码吓坏,喊出“竞争条件”或“锁”这样的术语。锁(可以跨线程共享)是一种看门人,只允许一个线程同时打开代码执行的门,以防止变量同时被访问或修改。等等,我们实际上已经在某个地方听说过了:GIL(全局解释器锁)。
所以 python 确实已经有了一个锁机制来防止两个线程同时执行代码。好消息是,上面的代码可能没有那么可怕。让我们通过打印三个线程执行后生成的总计数来证明这一点(并删除睡眠)。由于 GIL 保护我们免受线程同时执行指令的影响,无论哪个线程正在递增全局变量,我们都应该将 10e5 * 4 乘以 1 相加,因此总数为 4.000.000。
好吧,我们需要一些解释。尤其是 GIL 实际上是如何工作的。“同时只让一个线程运行”的描述可能不足以准确地解释这种情况。如果我们更深入地研究细节,我们可以看到这一点:“为了支持多线程 Python 程序,解释器定期释放和重新获取锁——默认情况下,每十个字节码指令”。所以这与同时只允许读取一行实际的 python 代码是不同的。为了充分理解这一点,我们需要降低一个级别。
当你执行一个 python 程序时会发生什么?python 解释器首先将代码(在运行时)编译成更容易转换成字节的东西(给处理器单元的食物)。对于 python,这种中间代码称为字节码。字节码操作的完整描述可以在这里找到。dis 模块使我们能够看到特定函数的字节码是什么样子的。如果我们试着看一下一个给全局变量加 1 的函数:
要简单地将一个常量添加到一个变量中,我们需要 4 个字节码操作,获取变量,将它们相加并存储结果。
现在我们对什么是字节码操作有了更好的理解,我们可以注意到 GIL 附带了我们可以控制的设置: sys.setcheckinterval() 使我们能够控制我们想要锁定 GIL 的字节码的数量。但是,即使我们将它设置为 1(GIL 在每个字节码指令中都被锁定),这对我们也没有什么帮助。让我们分析一下在 GIL 每三个字节码锁定一次的情况下会发生什么:
数字 1 到 4 代表 GIL 允许处理字节码组的顺序。现在让我们假设全局变量的初始值为 0。在每个线程中,我们都试图给这个变量加 1。在字节码组“1”执行结束时,它从 LOAD_FAST 获得的变量副本增加了 1(in place _ ADD)。但是全局变量本身没有被修改,因为 STORE_FAST 没有被执行。现在轮到第二个线程了:由于变量没有被存储,它仍然会复制一个值为 0 的全局变量,并对其加 1。现在,当组 3 被执行时,global_variable 最终被存储为值 1。但是你可以想象,在执行组 4 时,本地副本的值也是 1,全局变量将再次存储为 1,而我们期望它是 2。坏消息是,不管我们多频繁地锁定 GIL,只要“global_variable += 1”等价字节码的部分被混淆,我们就有了竞争条件。
因此,GIL 不足以保护我们的变量,我们别无选择,只能使用锁来强制解释器跨线程一次只执行一段代码:
工作正常,线程计数到正确的数目,同时并行执行这项工作。然而,如果你看看总的计算时间,这是超出了屋顶。获取和释放锁确实很耗时,当我们需要像这里这样频繁地访问变量时,它会累积起来,形成非常繁重的计算时间。
现在是时候总结一下我们在 Python 中并行计算的经验了:
- 我们有两个主要的库允许我们进行并行计算:线程和多重处理。
- 全局解释器锁(GIL)在并行计算效率方面限制了 python 程序,但是多重处理通过创建多个解释器绕过了它。
- 多处理可以充分利用多核架构,甚至可以通过序列化/反序列化每个线程所需的数据,在不同的计算机上并行化计算。但是,它确实会创建一个拷贝或重新评估环境的资源。每个线程都在其受限的环境中进化,除非使用特定的工具,否则无法与其他线程交换。
- 线程库使子进程能够访问和修改相同的变量,但是 GIL 不能防止竞争情况,我们必须使用锁来防止这种情况发生。
那么什么时候用多处理,什么时候用线程呢?我们可以分析应用程序并行化的两个用例。
- 计算效率:目标是节省计算时间。在这种情况下,我们希望充分利用多核架构,而不受 GIL 的困扰。选择:多重处理。****
- I/O 通信:当您可能从多个数据源接收数据,并且希望能够同时监控不同数据源的输入时。在这种情况下,您可能希望所有线程拥有相同的环境,因为它们可能想要修改相同的变量,并且您可能不关注计算效率。您可能还想要比可用内核数量多得多的线程。选择:穿线。
解开 Apache Spark 中的分阶段执行
APACHE SPARK 执行指南
Spark 中的 Stage 表示并行计算的逻辑单元。许多这样的阶段组装在一起,构建了 Spark 应用程序的执行框架。这个故事试图解开火花阶段的概念,并描述了重要的相关方面。
火花阶段可以被理解为计算分布式集合的数据分区的计算块,该计算块能够在计算节点的集群中并行执行。Spark 使用单个或多个阶段为 Spark 应用程序构建并行执行流。Stages 提供模块化、可靠性和弹性来激发应用程序执行。以下是与火花阶段相关的各个重要方面:
阶段由 DAG 调度程序创建、执行和监控:每个正在运行的 Spark 应用程序都有一个与之相关联的 DAG 调度程序实例。该调度器响应于作业的提交而创建阶段,其中作业实质上表示对应于 Spark 应用中采取的动作的 RDD 执行计划(也称为 RDD DAG)。如果在一个 Spark 应用程序中执行多个操作,那么多个作业可能会被提交给 DAG 调度程序。对于提交给它的每个作业,DAG 调度程序创建一个或多个阶段,构建阶段 DAG 以列出阶段依赖关系图,然后根据阶段 DAG 为创建的阶段规划执行调度。此外,调度程序还监视阶段执行完成的状态,结果可能是成功、部分成功或失败。相应地,调度器尝试阶段重新执行,推断作业失败/成功,或者按照阶段 DAG 调度相关阶段。
以下是针对作业的 RDD 执行计划(DAG)示例:
(200) MapPartitionsRDD[36]
| ShuffledRowRDD[35]
+-(500) MapPartitionsRDD[34]
| MapPartitionsRDD[33]
| MapPartitionsRDD[32]
| ShuffledRowRDD[31]
+-(3) MapPartitionsRDD[30]
| MapPartitionsRDD[29]
| FileScanRDD[28]
DAG 调度程序为上述 RDD 执行计划创建了以下三个阶段:
图(2):根据图(2)所示的 RDD 执行计划创建的三个阶段
以下是 DAG 计划程序根据上述阶段创建的阶段 DAG,它清楚地表明了阶段间的依赖关系:
图(3):根据图(1)所示的作业的 RDD 执行计划,为图(2)所示的三个阶段创建的阶段 DAG
DAG 调度程序创建的每个阶段在 Spark 应用程序的所有作业中都有一个唯一的 ID。此外,根据阶段 DAG 来调度阶段以供执行,这意味着在已经计算了所有相关阶段(如阶段 DAG 中所列)之后,调度阶段以供执行。如果两个阶段互不依赖,并且它们的所有其他依赖阶段都已经计算过,则可以同时执行这两个阶段。
在混洗边界上创建阶段 : DAG 调度程序通过在计划中由 ShuffleRDD 指示的混洗边界处分割 RDD 执行计划/DAG(与作业相关联)来创建多个阶段。因此,在这个拆分过程中,RDD 执行计划的一个片段(本质上是 RDD 管道)成为了阶段的一部分。对于 Spark 应用程序中提到的大范围转换,Shuffle 是必需的,例如聚合、连接或重新分区操作。
下面是在各种洗牌边界的舞台创作的插图。
洗牌边界的舞台创作插图。
此外,在图(1)所示的例子中,如 RDD 执行计划中的两个 ShuffleRowRDD 所示,发生了两次洗牌,因此创建了三个阶段,如图(2)所示。从图(2)可以明显看出,三个阶段中每一个都包含一个 RDD 流水线(作业的原始 RDD 执行计划/DAG 的一部分)。
如果提交的作业中不需要重排,DAG 调度程序将仅为该作业创建和调度单个阶段
阶段有 ShuffleMapStage 或 ResultStage 两种类型:ShuffleMapStage 类型的阶段是一个作业执行计划的中间阶段,该作业由 Spark 应用程序中提到的动作触发。ShuffleMapStage 实质上产生由 ShuffledRDD 在后续阶段中使用的混洗数据文件。但是,在产生输出数据之前,ShuffleMapStage 必须执行 ShuffleMapStage 中包含的作业的 RDD 执行计划段(实质上是一个 RDD 管道)。ShuffleMapStage 产生的混洗数据量可作为称为 ShuffleWrite 的阶段度量。此外,由于 SortShuffle 过程主要用于产生混洗数据文件,所以由 ShuffleMapStage 产生的混洗数据文件的数量等于由该级的 RDD 流水线计算的数据分区的数量。
ShuffleMapStage 生成一组随机文件的图示。shuffle 文件的数量等于 ShuffleMapStage 中分区的数量
ResultStage 是作业执行计划中的最后一个阶段,其中一个函数(对应于启动作业的动作)被应用于所有或一些分区,这些分区是通过执行包含在 ResultStage 中的 RDD 执行计划的段来计算的。该函数产生 spark 应用程序中相应动作执行的最终期望输出。此处列出了 spark 应用的可能操作列表。
由 Spark 应用程序中的操作触发的作业,要么仅由单个 ResultStage 组成,要么由中间 ShuffleMapStage 和单个 ResultStage 的组合组成。但是,对于自适应查询规划或自适应调度,一些仅由 ShuffleMapStage 组成的特殊作业可以由 DAG 调度程序根据请求执行。
ShuffleMapStage 或 ResultStage 的数据是从输入文件、来自先前 shuffle map stage 的 shuffle 文件或缓存的 rdd 单独或组合提供的。
每个阶段都有一个相关的任务集用于执行:对于一个阶段的执行尝试,DAG 调度程序会创建一个相应的任务集。阶段任务集基本上是任务的集合,其中每个任务执行特定数据分区的阶段 RDD 管道,并产生所需的输出。
与阶段类型类似,阶段任务集中的任务属于 ShuffleMapTask 或 ResultTask 类型。ShuffleMapTasks 是为 ShuffleMapStage 创建的,而 ResultTasks 是为 ResultStage 创建的。
为 stage 的执行尝试创建的 Taskset 被提交给 Spark 应用程序的任务调度器实例。反过来,任务调度器在集群中的适当位置调度每个任务(包含在任务集中)的执行。在任务集中的所有任务被执行之后,任务集中每个任务的相应执行状态被报告回 DAG 调度器。因此,DAG 调度器将阶段执行标记为完全成功、部分成功或完全失败。
DAG 调度程序为某个阶段创建任务集,将任务集提交给任务调度程序,以及 DAG 调度程序调度(已提交任务集的)任务,以便在 Spark 集群中的执行器上执行
在部分成功的情况下,使用仅包含先前失败的任务的部分任务集重新尝试阶段执行。在完全失败的情况下,将使用成熟的任务集重新尝试阶段执行。
一个阶段只重试一定的次数,当所有的重试都不能将阶段执行标记为完全成功时,阶段执行被标记为失败,导致提交给 DAG 调度程序的相应作业的执行失败。
此外,大多数情况下,由于父级产生的部分或全部混洗数据文件不可用,一个级会部分失败。这导致在报告失败的阶段被再次重试之前,父阶段的部分重新执行(以便计算丢失的混洗文件)。此外,如果在祖先中的连续级别处存在丢失的混洗文件,则这种重新执行也可以到达父级祖先中更深级别处存在的级。当托管文件的执行器由于内存溢出或集群管理器强制终止而丢失时,无序文件变得不可用。此外,当相应的 ShuffledRDD 被垃圾收集时,shuffle 文件仍然不可用。
阶段计算可在时间跳过:DAG 调度程序可决定跳过已提交作业中 ShuffleMap 阶段的计算,前提是已针对提交给调度程序的前一个作业计算了类似的阶段。如果这两个阶段执行相同的 RDD 流水线,则它们被认为是相似的。这种跳过是可能的,因为混洗输出文件保留在磁盘中,直到混洗的 RDD 引用保留在 Spark 应用程序中。
此外,在另一实例中,如果从属下游阶段所需的混洗 RDD 已经被缓存,DAG 调度器可以决定跳过混洗映射阶段的计算,该缓存是在针对提交给调度器的先前作业的另一阶段的执行期间完成的。
下面是一个示例 Spark 应用程序,用于说明 DAG 调度程序的阶段跳过,这个特定的应用程序缓存一个重新分区的数据集(在第 10 行),该数据集分别在第 15 行和第 20 行的两个文件写入操作中使用:
下面是上述 Spark 应用程序中属于两个不同作业(由两个操作触发)的 RDD 执行计划(带有阶段标记)。在应用程序中,数据集“ds”被缓存,导致相应的“ShuffledRowRDD[11]”被缓存。
以下是 DAG 调度程序为这两个作业构建的阶段计算 DAG:
作业 1 的阶段计算 DAG
作业 2 的阶段计算 DAG
从两个作业的阶段计算 DAG 可以清楚地看出,在作业-2 的阶段计算 DAG 中,阶段 4 被跳过用于计算(被跳过的阶段是灰色的),因为在作业-2 的阶段 5 中使用的“ShuffledRowRDD[11]”已经被计算并缓存在作业-1 的阶段 2 中,作业-1 提前提交用于执行。
总结:现在应该很明显了,Spark 中执行流的分段如何为 Spark 应用程序的整体执行提供模块化和弹性。用户可以跟踪 Spark 应用程序的阶段进度,可以访问多个阶段指标来评估阶段执行效率。最后,也是最重要的一点,阶段进度和相关指标可以为应用优化提供线索。
如果对 Spark stages 有任何疑问,或者对这个故事有任何反馈,请写在评论区。
解开主成分分析
从 MNIST 数据库看主成分分析
来自 MNIST 数据库的示例图像
在现实世界中,我们可能会在数据集中获得比预测目标变量所需更多的要素。有时,特征的数量可能会扩展到几百个甚至几千个,导致我们失去对特征的跟踪。事实上,它们中的许多实际上可能彼此高度相关,因此可以被去除!
降维不仅可以让我们压缩数据,还有助于加快机器学习过程。将三维地球转换成二维地图是最简单的降维示例。
PCA 是一种降维技术,也是一种无监督学习算法,这意味着它只考虑解释变量,而不考虑目标变量。它有助于可视化高维数据,减少噪音,并帮助其他算法更好地工作。
在下图中,我们有两个解释变量 X1 和 X2,分别绘制在 X 轴和 Y 轴上。数据在两个轴上的差异(V1 和 V2)或分布是显著的。因此,我们不能放弃任何一个功能。
在 draw.io 上创建
但是,如果我们仍然不得不放弃其中一个特征,我们可以将轴转换到 P1 和 P2。现在 P1 的方差比 P2 的方差大得多,因此我们现在可以保留 P1,放弃 P2。
在 draw.io 上创建
在这个过程中,我们可能会丢失某个特定变量提供的某些百分比的信息或方差。然而,目标是在处理成本和差异之间达成一个*衡。P1 和 P2 是主成分,它们只不过是 X1 和 X2 的轴变换。
我们将检查在 MNIST 数字数据集上使用 PCA 的效果。数据集包含手绘数字的像素值,从 0 到 9。每个图像的高度和宽度都是 28 像素,总共是 784 像素。像素值是从 0 到 255 的整数,包括 0 和 255。
训练数据集包含 784 列,表示 784 个像素值。“标签”列是我们的目标列,值从 0 到 9。从 pixel0 到 pixel783 填充的像素值包含从 0 到 255 的整数值,包括 0 和 255。
train.head()
#Training dataset
标签列中的值几乎均匀分布:
train.label.value_counts()
我们将把数据分成训练集和标签,然后再进行其余的处理。
y=train["label"]
X=train.loc[:, train.columns != "label"]
接下来,使用标准标量对数据进行标准化
X_values = X.values
X_std = StandardScaler().fit_transform(X_values)
我们将数据分为训练集和测试集:
X_train, X_test, y_train, y_test = train_test_split(X_std, y, test_size = 0.25, random_state = 42, stratify = y)
接下来,我们运行逻辑回归对数据进行分类,并计算将模型拟合到 784 列所需的时间:
import time
log = LogisticRegression(random_state = 42, multi_class="multinomial", solver="saga", max_iter=200)
start_time = time.time()
log.fit(X_train, y_train)
end_time = time.time()
time1 = end_time-start_time
print("Time elapsed: ",time1)
y_pred = log.predict(X_test)# Accuracy Estimation
print('Accuracy Score (Train Data):', np.round(log.score(X_train, y_train), decimals = 3))
print('Accuracy Score (Test Data):', np.round(log.score(X_test, y_test), decimals = 3))# Classification Report
logistic_report = classification_report(y_test, y_pred)
print(logistic_report)
逻辑回归花了大约 455 秒来拟合数据,并给了我们 92%的*均准确率。
我们将检查我们是否可以用 PCA 得到更好的结果。我们会选取 30%的数据进行 PCA,这样就不用花很多时间去拟合 PCA。根据我们的目标,我们将指定我们希望通过我们的模型解释多少差异。这里我们选择 0.98 来表示我们希望解释最大 98%的方差。
X_data, X_pca = train_test_split(X_std, test_size=0.3, random_state=1)
pca = PCA(0.98).fit(X_pca)
我们现在将绘制一个肘形图来检查能够解释 98%数据差异的最佳特征数量
var=np.cumsum(np.round(pca.explained_variance_ratio_, decimals=4)*100)
plt.ylabel('% Variance Explained')
plt.xlabel('Number of Features')
plt.title('PCA Analysis')
plt.ylim(30,100.5)
plt.style.context('seaborn-whitegrid')
plt.plot(var)
“肘图”表示我们需要达到预期的解释方差百分比的最佳主成分数。除了这些组成部分,解释方差的增量可以忽略不计,因此这些特征可以删除。下图解释了这一概念。
406 个主成分解释了 98%的数据差异。因此,大约只有 50%的特征解释了数据中的大部分差异。
我们现在将对 PCA 简化的数据集再次运行逻辑回归。
因此,逻辑回归在 242 秒内拟合缩减的数据集,同时保持*均准确率为 92%!因此,主成分分析帮助逻辑回归以几乎一半的计算时间达到相同的精度。
当我们处理更大的数据集时,这变得更加重要,因为减少计算时间是不可避免的。
PCA 广泛用于人脸和图像识别,因为这种数据通常具有大量的特征,并且维数减少可以帮助减少进行预测所需的特征数量。
全部代码可在这里获得。
拿出你的幻灯片,杀死一条龙,拯救企业
办公时间
五个技巧,让你更好地讲述故事,展示数据科学实验的最佳价值。
我知道… 幻灯片。大多数数据科学家讨厌构建它们。这是令人沮丧的,因为你花了大量的时间来设计它们,结果往往不如你预期的好。但是不要难过,我是来帮你让你的滑梯制作更有趣的。
你可能已经注意到了,幻灯片是公司(无论大小)内部交流知识最流行的工具。没有他们,很少有人会注意你要说的话。演示如此受欢迎是因为人类是视觉学习者。麻省理工学院 2014 年的一项研究测量出我们的大脑可以在大约 13 毫秒内处理图像。这比阅读哪怕是很小的文本节选都要快得多。
如果你想为你的数据科学成果带来关注,你应该掌握你的 Powerpoint fu 或者你的 Google slides fu。
为了让建造滑梯更有趣,我把它变成了建造世界的任务;创造一个小世界并讲述一个故事的挑战。而且,在每一个叙述中,你首先需要考虑两个主要问题:
- 这个故事是关于什么的?(你需要用一个短语来回答这个问题)
- 故事的受众是什么?
第一个问题会告诉你 你需要展示什么 。你的回答听起来有趣吗?第二个问题应该让你知道你应该如何展示它。
一旦你回答了这些问题,你就可以组织你的情节结构,并决定你应该使用哪些元素。下面,我给你五个建议,也许能帮到你。
1.制造一个诱饵
一些著名的说书人说你应该用一个钩子开始你的故事。或者,著名小说家杰夫·范德米尔在他令人惊叹的神奇书籍中,讲述了一些不同的东西:
“事实是,任何事情都可以变得有趣,所以你应该[……]更多地考虑在特定的情况或场景中,兴趣在哪里,以及从什么角度出发。一个鱼钩也不能仅仅是一个鱼钩——它必须是一个诱饵和诱惑,它还必须是一个锚。你在邀请读者享受某种乐趣或挑战……”
你可以在演讲中使用这个策略。对你的观众来说,最有价值的信息是什么?
例如,假设您构建了一个 ML 模型来预测未来几个月的销售额。因此,不要在开始演示时解释您用来准备数据和构建模型的所有技术,而是从您的预测中获得的有趣数字开始。
此外,从右侧视角创造诱惑。例如,销售团队可能对预测的交易中涉及的金额感兴趣。相比之下,营销团队可能更喜欢每个客户群和人口统计数据中售出的商品数量。
用你的诱饵作为锚。随着你在演示中的进展——详细介绍你的发现,深入研究你的结果——观众很容易迷失。因此,用你的诱惑让他们和对他们最重要的东西保持联系。
回到我们的例子,你可能已经开始展示在预测的交易中涉及了多少钱,但是接下来你可以解释这些钱如何转化为市场份额。你从你的锚(钱)跳到一个新的彼岸(市场份额数据)。如果你坚持这样做,观众会觉得所有的事情都与他们最重要的事情有着恰当的联系。更容易理解。
此外,帮助观众理解,诱惑只是邀请一些宏伟的未来。如果你在诱惑中破坏了你所有的洞察力,观众很快就会变得厌烦,并相信他们已经从你那里得到了一切。如果他们知道你有更多有价值的信息可以分享,他们会保持联系。
2.恰当地描述你的角色
就像在一个故事里,我们也应该把我们的结果当成人物。
你故事中的英雄是谁?
也许是你的新颖算法,或者是你数据挖掘实验的一些感悟。作为科学家,我们热衷于详细描述一切。然而,这可能是一个吸引你的观众的糟糕策略,因为太多的信息很容易变得太难或太无聊。
在一本书里,小说作者通常不会给你他们的英雄的所有特征。通常,他们描述什么是更重要的或最引人注目的痕迹。我们的大脑根据已知的原型和直觉想象缺失的部分。试着对你的结果使用同样的策略。只描述你研究中最重要的方面。对于那些想了解所有细节的人,您可以在幻灯片底部留下详细文档的链接。
此外,抽象是良好描述的关键。使用易于理解的图表、图表、隐喻,任何引导观众直觉和理解的东西,即使他们没有技术背景。就像是给他们的头脑指了捷径。
3.充分利用一种语言
2016 科幻电影降临看了吗?如果没有,看完这个帖子再看。
展示数据科学成果就像和外星人聊天一样困难。想象他们需要理解你,否则他们可能会摧毁你的世界。为了避免这种情况,像路易丝·班克斯一样,尝试以下方法:
- 找出或建立一些你和你的听众之间的“共同语言”。
- 传达开发该语言用途的信息。
你可以用符号、颜色、关键词作为你语言的组成部分。任何能让你的观众更容易理解你的故事并保持联系的东西。
一旦你定义了你的语言,就要保持一致。如果你选择一种颜色来代表某样东西(一个变量,一个类别,或者一个策略),在整个演示过程中始终使用同一种颜色。如果你需要色调的变化,改变透明度,但保持色调。此外,保持变量名、图表、类别等一切的一致性。
4.剧情变得复杂了!
在数据科学实验之后,我们通常会有几个图表和示意图要展示。你展示结果的顺序可以帮助你讲述一个更好的故事。请记住,就像在书中一样,时间顺序不一定是使故事有趣的最佳选择。
不幸的是,我不相信在结果排序方面有一个黄金法则。我考虑的两个方面是:
- 结果的复杂性;
- 他们的影响。
首先,从简单的片段构建复杂的结果。从基本前提开始,不断添加元素,直到完成更大的图片。这是一个让你的听众理解你的推理的简单策略。这就像拼图,你应该让每个人都玩,一次一块。
其次,最有冲击力的结果应该是接*尾声的。这样,观众更容易记住他们。然而,这种影响很难衡量,因为它取决于视角。你可以试着去猜,随着时间的推移,随着你获得经验,你的选择会更好。
5。没有开放式结尾
科学是开放的。你的幻灯片不需要。我认为结论幻灯片如果具体客观,效果会更好。通常,我会用外卖代替总结幻灯片。我列出了我的观众应该记住的最有价值的作品。
根据具体情况,听众可能希望听到后续步骤(或未来工作)。在这种情况下,有开放结局的空间,但不要忘记先非常好地关闭你当前的故事。
一些演示指南告诉你在结论中对你的工作做一个总结。我个人觉得很无聊,观众会分心,愿意走开。如果你发表了引人入胜的演讲,人们会记住你的故事。你最好突出你有影响力的见解,像英雄一样结束。
你有没有自己的妙招让自己讲故事更有趣?请在评论里告诉我!此外,如果你想保持联系并在电子邮件中收到一些数据科学技巧和极客文化,请订阅我即将开始的每月简讯。
关于无符号、有符号整数和 Rust 中的造型,你应该知道什么
理解符号和幅度、一的补码和二的补码
由 Freepik 设计
[更新于 2021 年 2 月 18 日。代码更改为要点并添加了链接]
**Table of Contents**[**Introduction**](#a73e)🦀 [Unsigned Integer Types](#f4ce)
🦀 [Signed Integer Types](#3e54)
🦀 [Signed, Ones’ Complement and Two’s Complement](#ac74)
🦀 [Sign-and-Magnitude](#7b01)
🦀 [Ones’ Complement](#fbfa)
🦀 [Two’s Complement](#41ce)
🦀 [4-bit Signed Binary Number Comparison](#6677)
🦀 [Rust signed two’s complement integer types](#0a26)
🦀 [Casting in Rust](#0883)
🦀 [Casting to an Unsigned Type](#edbc)
🦀 [Casting to a Signed Type](#47e3)
🦀 [Bitwise Negation](#108b)
🦀 [Adding a Negative Number](#baae)[**Conclusion**](#6f4b)
介绍
Rust 有两个数据类型子集,标量和复合。标量类型有整数、浮点数、布尔值和字符。复合类型是数组和元组。
在本文中,我们将了解以下代码失败的原因:
fn main() {
let a: i16 = 2;
let b: u16 = 4;
println!("{}", a+b);
}
还有为什么选角128
到i8
是-128
。
输出:
128 as a i8 is : -128
为了更好地理解造型,我们需要回顾一下有符号、一的补码和二的补码。
我们还将讨论添加负数、按位求反以及将二进制转换为无符号和有符号十进制。
让我们先从 Rust 整数类型开始。
[## 通过将 Python 转换成 Rust 来学习 Rust
Rust 基础入门教程
towardsdatascience.com](/learning-rust-by-converting-python-to-rust-259e735591c6)
无符号整数类型
Rust 中的无符号整数类型以u
开始,它有 8 位、16 位、32 位、64 位和 128 位。最小值和最大值从 0 到 2ⁿ-1 。
例如u8
有 0 到 2⁸-1,也就是 255。下表显示了无符号整数的所有详细信息。
信任无符号整数类型。图片由作者提供。
寻找无符号整数类型的最大值。网上试试这个 Rust lang 代码。
输出:
u8 has the max value of 255.
u16 has the max value of 65535.
u32 has the max value of 4294967295.
u64 has the max value of 18446744073709551615.
u128 has the max value of 340282366920938463463374607431768211455.
如果试图将负数赋给无符号类型,将会失败。
fn main() {
let u:u32 = -1;
println!("{} in binary is {:b}", u, u);
}error[E0600]: cannot apply unary operator `-` to type `u32`
--> main.rs" data-line="2" data-column="17">main.rs:2:17
|
2 | let u:u32 = -1;
| ^^ cannot apply unary operator `-`
|
= note: unsigned values cannot be negated
-
是 Rust 的 一元 运算符 之一,是有符号整数类型和浮点类型的非运算符。
有符号整数类型
Rust 中的默认整数类型为i32
。Rust 中的有符号整数类型以i
开始,它有 8 位、16 位、32 位、64 位和 128 位。最小值和最大值是从 -(2ⁿ⁻到 2ⁿ⁻ -1 。n-1
中的-1
是符号位(正或负),我们将在下一节中介绍。
例如i8
有-(2⁷)到 2⁷-1,也就是-128 到 127。下表显示了有符号整数的所有详细信息。
信任有符号整数类型。图片由作者提供。
寻找有符号整数类型的最小值和最大值。在线尝试这个 Rust lang 代码。
输出:
i8 has the min value of -128.
i8 has the max value of 127.
i16 has the min value of -32768.
i16 has the max value of 32767.
i32 has the min value of -2147483648.
i32 has the max value of 2147483647.
i64 has the min value of -9223372036854775808.
i64 has the max value of 9223372036854775807.
i128 has the min value of -170141183460469231731687303715884105728.
i128 has the max value of 170141183460469231731687303715884105727.
有符号、一的补码和二的补码
在计算中,需要用有符号的数字表示法来对二进制数字系统中的负数进行编码。让我们检查一下符号和幅度、一进制补码和二进制补码。
符号和幅度
符号和幅度也称为符号幅度。第一位(称为最高有效位或 MSB)表示它是正 0 还是负 1。其余的称为幅度位。
+5 的 4 位有符号幅度。图片由作者提供。
正如我之前提到的,有符号整数类型具有从 -(2ⁿ⁻到 2ⁿ⁻ -1 的最小值和最大值,其中 n 代表位数。因为我们将第一位用于正负符号,所以在 2ⁿ⁻ 中有n-1
。
对于 4 位,最小值和最大值从-(2)到 2–1,即-8 到+7。
正如你在上图中看到的,除了符号位之外,正数和负数有相同的数字。
有符号幅度的问题是有两个零,0000
和1000
。
一的补码
第一位(MSB)与带符号幅度相同。它以 0 表示正,以 1 表示负。其余的位将接受补码,这意味着如果它为 1,补码为 0,如果它为 0,则补码为 1。
+5 的一进制补码(4 位)。图片由作者提供。
有符号一的补码与有符号幅度具有相同的问题。有两个零,0000
和1111
。
二进制补码
在二进制计数系统中,基数(基数)是二。这就是为什么基数补码被称为二进制补码,而减基数补码被称为一进制补码。
二进制补码可以避免 0 的多重表示,并且避免在溢出的情况下跟踪进位。
+5 的二进制补码(4 位)。图片由作者提供。
我们再次将第一位(MSB)用于+
和-
符号。我们取这个数的补数,加上1
得到相反的数。这从正到负起作用,反之亦然。
-5 的二进制补码(4 位)。图片由作者提供。
对于二进制中的零0000
,补码是1111
,加上1
得到1 0000
。1
被称为“溢出”位。当最高有效(最左边)列的总和产生结转时,发生溢出。该溢出位或进位位可以忽略。
下表显示了三种有符号数字表示法的比较。
4 位有符号二进制数比较
4 位有符号二进制数比较。图片由作者提供。
Rust 有符号二进制补码整数类型
现在让我们将上表中的有符号二进制补码的负数(从-1 到-7)与 Rust 的有符号整数进行比较。
输出:
-1 in binary is 11111111111111111111111111111111
-2 in binary is 11111111111111111111111111111110
-3 in binary is 11111111111111111111111111111101
-4 in binary is 11111111111111111111111111111100
-5 in binary is 11111111111111111111111111111011
-6 in binary is 11111111111111111111111111111010
-7 in binary is 11111111111111111111111111111001
除了 Rust 使用默认的 32 位之外,它们完全相同。Rust 的有符号整数类型被称为有符号二进制补码整数类型。
生锈的铸件
造型 是将一条数据的数据类型从一种类型改变为另一种类型。
[as](https://doc.rust-lang.org/std/keyword.as.html)
关键字将原始类型转换为其他原始类型。我们可以使用as
关键字来解决简介中的代码。
铸造 u16 到 i16。网上试试这个 Rust lang 代码。
当你从小长度转换到大长度时,比如从 8 位转换到 16 位,不会有任何问题,但是当你向下转换时,就有问题了。
强制转换为无符号类型
方法一
当您强制转换为无符号类型时,会加上或减去 T,T::MAX + 1,直到该值适合新类型。
示例 1:从默认的i32
到u8
铸造 1000
u8
的最大数是 255,所以我们减去 255+1,也就是从 1000 减去 256,直到小于 255。
1000 - 256 = 744
1000 - 256 - 256 = 488
1000 - 256 - 256 - 256 = 232
铸造 1000 个 i32 到 u8。网上试试这个 Rust lang 代码。
输出:
1000 as a u8 is : 232
方法二
十进制的 1000₁₀是二进制的 1 1110 1000₂。我们可以取后 8 位,也就是1110 1000
,十进制是 232。
一个有趣的数字是 256₁₀.
铸造 256 i32 到 u8。网上试试这个 Rust lang 代码。
输出:
256 as a u8 is : 0
256₁₀是二进制的 100000000₂。如果取最后 8 位,就是00000000
。
强制转换为有符号类型
方法一
这与上面的相同,但是您需要注意数据类型的最小/最大数量。
i8
的最小和最大数字是-128 和 127。所以这个数字必须介于两者之间。
铸造 128 到i8
:
128-256=-128
铸造 1000 到 i8:
1000 - 256 = 744
1000 - 256 - 256 = 488
1000 - 256 - 256 - 256 = 232
1000 - 256 - 256 - 256 - 256 = -24
将 i32 数字转换为 i8。网上试试这个 Rust lang 代码。
输出:
128 as a i8 is : -128
1000 as a i8 is : -24
232 as a i8 is : -24
257 as a i8 is : 1
130 as a i8 is : -126
514 as a i8 is : 2
方法二
十进制的 1000₁₀是二进制的 11 1 110 1000₂。8 位以内的 MSB 是 1,所以是负数。然后用 2 的补码。补足语是 00010111₂,我们加上 1₂就成了 00011000₂.最后,是-24₁₀.
图片由作者提供。
当 8 位以内的 MSB 为 0 时,为正数。然后取前 8 个最低有效位(LSB)。
图片由作者提供。
逐位求反
Rust 使用!
进行逐位求反(逐位非)。这根据类型产生不同的结果。
输出:
u is 2
u in binary is 10
i is 2
i in binary is 10
Bitwise negation !u is 4294967293
Bitwise negation !i is -3
正如你看到的!2
带有无符号类型返回4294967293
,带有有符号类型返回-3
。
有符号整数上的按位求反返回二进制补码,正如我们之前在信任有符号二进制补码整数类型中看到的。
添加负数
减法和负数的加法是一样的。
5 - 2 = 5 + (-2) = 3
这也适用于二进制。
0101 - 0010
= 0101 + (-0010) // (1)
= 0101 + 1110 // (2)
= 0011 // this is 3 in decimal number.
我们通过找到0010
的二进制补码即1110
来找到-0010
。
结论
我们讨论了 Rust 整数类型、三种类型的有符号二进制数、转换为无符号类型和有符号类型。我希望你在处理 Rust integers 和 casting 时,对发生的事情有更好的了解。
请继续关注下一篇文章。
通过 成为 会员,可以完全访问媒体上的每一个故事。
https://blog.codewithshin.com/subscribe
Rust 初学者的完整资源
towardsdatascience.com](/you-want-to-learn-rust-but-you-dont-know-where-to-start-fc826402d5ba)
自然语言理解数据集未解决的问题
即使是最受欢迎的 NLP 基准也面临着这些挑战
杰森·德沃尔在 Unsplash 上拍摄的照片
垃圾进来,垃圾出去。你不必是一个 ML 专家也能听到这个短语。模型揭示了数据中的模式,因此当数据被破坏时,它们会发展出破坏的行为。这就是为什么研究人员分配大量资源来管理数据集。然而,尽管尽了最大努力,收集完全干净的数据几乎是不可能的,尤其是在深度学习所需的规模上。
本文讨论了流行的自然语言数据集,尽管这些数据集是由该领域的专家制作的,但结果却违反了机器学习和数据科学的基本原则。这些缺陷中的一些在数据集公布和大量使用多年后被暴露和量化。这是为了说明数据收集和验证是一个艰巨的过程。以下是他们的一些主要障碍:
- 机器学习是数据饥渴。ML(特别是深度学习)所需的庞大数据量要求自动化,即挖掘互联网。数据集最终会从互联网上继承不需要的属性(例如,重复、统计偏差、虚假),这些属性很难检测和删除。
- desire data 无法详尽捕获。即使 oracle 能够根据一些预定义的规则产生无限的数据,要列举所有的需求实际上也是不可行的。考虑对话机器人的训练数据。我们可以表达一般的愿望,如不同的话题,尊重的交流,或对话者之间的*衡交流。但是我们没有足够的想象力去指定所有的相关参数。
- 人类选择阻力最小的道路。一些数据收集工作在人类规模上仍然是可管理的。但是我们自己并不是完美无缺的,尽管我们尽了最大努力,我们还是下意识地倾向于走捷径。如果你的任务是写一个与前提“狗在睡觉”相矛盾的陈述,你的答案会是什么?继续阅读,看看你是否会成为问题的一部分。
重叠的训练和评估集
ML 从业者将他们的数据分成三部分:有一个训练集用于实际学习,一个验证集用于超参数调整,一个评估集用于测量模型的最终质量。众所周知,这些集合应该大部分是析取的。在评估训练数据时,您测量的是模型的记忆能力,而不是它识别模式并将其应用于新环境的能力。
这个指导方针听起来很容易应用,但是 Lewis 等人[1]在 2020 年的一篇论文中显示,最流行的开放领域问答数据集(open-QA)在它们的训练集和评估集之间有很大的重叠。他们的分析包括 WebQuestions 、 TriviaQA 和开放式自然问题——由知名机构创建的数据集,被大量用作 QA 基准。
我们发现 60–70%的测试时答案也存在于训练集中的某个地方。我们还发现,30%的测试集问题在其对应的训练集中有*似重复的释义。
当然,培训和测试之间 0%的重叠也是不理想的。我们确实需要某种程度的记忆——模型应该能够回答训练中看到的问题,并知道何时展示之前看到的答案。真正的问题是在训练/评估高度重叠的数据集上对模型进行基准测试,并对其泛化能力做出仓促的结论。
Lewis 等人[1]在将评估集划分为三个子集后,重新评估了最先进的 QA 模型:(a) 问题重叠— ,其中相同或转述的问答对出现在训练集中,(b) 答案仅重叠— ,其中相同的答案出现在训练集中,但与不同的问题配对,以及(c) 没有重叠。QA 模型在这三个子集上的得分差别很大。例如,当在开放式自然问题上测试时,最先进的解码器融合模型在问题重叠上的得分约为 70%,仅在答案重叠上的得分约为 50%,在没有重叠上的得分约为 35%。
很明显,这些数据集上的性能无法通过总体 QA 准确性来正确理解,这表明在未来,应更加重视更多行为驱动的评估,而不是追求单一数字的总体准确性数字。
虚假相关
就像人类一样,模型走捷径,发现解释数据的最简单模式。例如,考虑一个狗对猫图像分类器和一个天真的训练集,其中所有的狗图像都是灰度的,所有的猫图像都是全色的。该模型将最有可能抓住颜色和标签的存在/不存在之间的伪相关性。在全彩狗身上测试,大概会贴上猫的标签。
Gururangan 等人[2]表明,在两个最受欢迎的自然语言推理(NLI)数据集、 SNLI (斯坦福·NLI)和 MNLI (多体裁 NLI)中出现了类似的虚假相关性。给定两个陈述,一个前提和一个假设,自然语言推理的任务是决定它们之间的关系:蕴涵、矛盾或中立。以下是 MNLI 数据集中的一个示例:
来自 MNLI 数据集的示例
解决 NLI 问题需要理解前提和假设之间的微妙联系。然而,Gururangan 等人[2]揭示,当模型仅显示假设时,它们在 SNLI 上可以达到高达 67%的准确度,在 MNLI 上可以达到 53%的准确度。这明显高于最频繁类基线(~35%),暴露了数据集中不可否认的缺陷。
这是怎么发生的?SNLI 和 MNLI 都是众包;给人类一个前提,要求他们提出三个假设,每个标签一个。这又把我们带回了前提“狗在睡觉”。你会如何反驳它?“狗没有睡觉”是一个完全合理的候选词。然而,如果否定一直作为一种启发来应用,模型就学会了通过简单地检查假设中“不是”的出现来检测矛盾,甚至不需要阅读前提就可以获得高准确度。
Gururangan 等人[2]揭示了其他几个这样的注释人工制品:
- 蕴涵假设是通过概括在前提中发现的词语(狗→动物,3 →一些,女人→人)产生的,使得仅从假设中就可以识别蕴涵。
- 中性假设是通过注入修饰语 ( 高,第一,最 ) 而产生的,作为一种简单的方法来引入不被前提所包含但又不与之矛盾的信息。
尽管有这些发现,MNLI 仍然在 GLUE 排行榜之下,这是自然语言处理最流行的基准之一。与其他 GLUE 语料库(约 400,000 个数据实例)相比,MNLI 具有相当大的规模,因此在摘要中非常突出,并用于消融研究。虽然它的缺点开始被更广泛地认识到,但在我们找到更好的替代品之前,它不太可能失去它的受欢迎程度。
偏见和代表性不足
在过去的几年里,机器学习中的偏见已经在多个维度上暴露出来,包括性别和种族。为了应对有偏见的单词嵌入和模型行为,研究社区已经将越来越多的努力指向偏见缓解,如 Sun 等人[3]在其综合文献综述中所述。
2018 年图灵奖的共同获奖者 Yann LeCun 指出,有偏差的数据导致有偏差的模型行为:
他的推文吸引了研究界的大量参与,反应不一。一方面,人们几乎一致承认在许多数据集中确实存在偏见。另一方面,一些人不同意偏见仅仅源于数据的暗示,还指责建模和评估选择,以及设计和构建模型的人的无意识偏见。Yann LeCun 后来澄清说,他不认为数据偏差是模型中社会偏差的唯一原因:
尽管正在讨论的数据集是用于计算机视觉的图像语料库,但自然语言处理同样会受到有偏见的数据集的影响。暴露出性别偏见的一个突出任务是共指消解,其中指称表达(如代词)必须与文本中提到的实体相关联。下面是 Webster 等人的一个例子[4]:
五月,藤泽作为球队的队长加入了本桥麻里的溜冰场,从轻井泽回到她曾经度过初中时光的北见。
作者指出,维基百科上不到 15%的传记是关于女性的,而且他们倾向于比关于男性的页面更突出地讨论婚姻和离婚。鉴于许多 NLP 数据集是从维基百科中提取的,这影响了许多下游任务。特别是对于共指消解来说,缺少女性代词或者它们与某些定型的关联是有问题的。例如,你如何解释这句话“当玛丽走进房间时,她看到了她的医生”?
从训练数据中消除偏差是一个尚未解决的问题。首先,因为我们不能详尽无遗地列举偏见表现的轴;除了性别和种族之外,还有许多其他微妙的方面会引起偏见(年龄、专有名称、职业等。).第二,即使我们选择了一个像性别这样的单一轴,消除偏见将意味着要么丢弃大部分数据,要么应用容易出错的试探法将男性代词变成代表不足的性别代词。相反,研究界目前正专注于产生无偏的评估数据集,因为它们较小的规模更有利于人工干预。这至少让我们有能力更真实地测量我们的模型的性能,通过人口的代表性样本。
构建自然语言数据集是一个永无止境的过程:我们不断地收集数据,验证数据,承认数据的缺点并解决它们。然后,每当有新的来源时,我们就冲洗并重复。与此同时,我们取得了进展。上面提到的所有数据集,尽管有缺陷,但不可否认地帮助推动了自然语言理解的发展。
参考
- Lewis 等人,开放领域问答数据集的问答测试序列重叠 (2020)
- Gururangan 等人,自然语言推理数据中的标注工件 (2017)
- 孙等,减轻自然语言处理中的性别偏见:文献综述 (2019)
- 韦伯斯特等人,注意差距:性别歧义代词的*衡语料库 (2018)
时间序列上的无监督异常检测
包括真实生活经历的概述
理解时间轴上任何流的正常行为并检测异常情况是数据驱动研究中的突出领域之一。这些研究大多是在无人监督的情况下进行的,因为在现实生活项目中标记数据是一个非常艰难的过程,如果你已经没有标签信息,就需要进行深入的回顾性分析。请记住,异常值检测和异常值检测在大多数时候可以互换使用。
没有一种神奇的银弹能在所有异常检测用例中表现良好。在这篇文章中,我谈到了一些基本的方法,这些方法主要用于以非监督的方式检测时间序列上的异常,并提到了它们的简单工作原理。在这个意义上,本文可以被认为是对包括现实生活经验在内的时间序列异常检测的一个综述。
基于概率的方法
使用Z-score是最直接的方法之一。z 分数基本上代表样本值低于或高于分布*均值的标准偏差的数量。它假设每个要素都符合正态分布,计算样本中每个要素的 z 值可以帮助我们发现异常。具有很多特征的样本,其值位于远离*均值的位置,很可能是异常。
在估计 z 分数时,您应该考虑影响模式的几个因素,以获得更可靠的推断。让我给你举个例子,你的目标是检测电信领域设备流量值的异常。小时信息、工作日信息、设备信息(如果数据集中存在多个设备)可能会形成流量值的模式。因此,在本例中,应该通过考虑每个设备、小时和工作日来估计 z 得分。例如,如果您预计设备 A 在周末晚上 8 点的*均流量为 2.5 mbps,那么您应该在决定相应的设备和时间信息时考虑该值。
这种方法的一个缺点是,它假设要素符合正态分布,而这并不总是正确的。另一个可以认为是忽略了上述解决方案中特征之间的相关性。重要的一点是 z 分数也可以用作其他异常检测模型的输入。
基于四分位数的 解决方案实现了与 Z-score 非常相似的想法,不同的是它以简单的方式考虑了中值而不是*均值。有时,根据数据的分布情况,与 z 分数相比,它可以获得更好的结果。
椭圆包络 是离群点检测的另一种选择,适合数据上的多元高斯分布。但是,它可能无法很好地处理高维数据。
基于预测的方法
在这种方法中,使用预测模型对下一个时间段进行预测,如果预测值超出置信区间,则样本被标记为异常。作为预测模型,可以使用ARIMA模型。这些方法的优点是它们是时间序列上表现良好的模型,并且在大多数情况下可以直接应用于时间序列而无需特征工程步骤。另一方面,估计置信区间并不是一件简单的事情。此外,预测模型的准确性直接影响异常检测的成功。好消息是,即使在没有任何标签信息的情况下执行异常检测,您也能够以受监督的方式评估预测模型的准确性。
预言家也值得一看,它基本上是一个针对时间序列设计的预测算法,由脸书开发,但我在异常检测用例中遇到很多这种算法的实现。**
基于神经网络的方法
auto encoder是一种无监督型神经网络,主要用于特征提取和降维。同时,对于异常检测问题,这是一个很好的选择。Autoencoder 由编码和解码部分组成。在编码部分,提取代表数据中模式的主要特征,然后在解码部分重构每个样本。正常样本的重建误差最小。另一方面,该模型不能重建表现异常的样本,导致高重建误差。因此,基本上,样本的重构误差越高,它就越有可能是异常的。
Autoencoder 对于时间序列是非常方便的,因此它也可以被认为是时间序列异常检测的优先选择之一。注意,自动编码器的层可以同时由 LSTMs 组成。因此,时序数据中的相关性就像时间序列中的相关性一样可以被捕获。
【SOM】也是另一种基于无监督神经网络的实现,与其他神经网络模型相比,其工作原理更简单。尽管它在异常检测用例中没有广泛使用,但最好记住它也是一种替代方法。
基于聚类的方法
在异常检测中使用聚类背后的思想是异常值不属于任何聚类或有自己的聚类。k-means是最著名的聚类算法之一,易于实现。然而,它带来了一些限制,如选择合适的 k 值。此外,它形成球形团簇,这并不适用于所有情况。另一个缺点是,它不能在将样本分配给聚类时提供概率,特别是考虑到在某些情况下聚类可能重叠。
高斯混合模型(GMM) 针对 k-means 的上述弱点,提出一种概率方法。它试图在数据集中找到有限数量的高斯分布的混合。
DBSCAN 是一种基于密度的聚类算法。它确定数据集中的核心点,这些核心点在其周围ε距离内至少包含 min_samples ,并根据这些样本创建聚类。此后,它从聚类中的任何样本中找到所有密集可达(在ε距离内)的点,并将它们添加到聚类中。然后,迭代地对新添加的样本执行相同的过程,并扩展聚类。DBSCAN 自己确定聚类数,离群样本会被赋值为-1。换句话说,它直接服务于异常检测。请注意,对于大型数据集,它可能会遇到性能问题。
基于邻*的方法
首先想到的算法是k-最*邻(k-NN)** 算法。背后的简单逻辑是异常值远离数据*面中的其余样本。估计所有样本到最*邻的距离,并且远离其他样本的样本可以被标记为异常值。k-NN 可以使用不同的距离度量,如欧几里德距离、曼哈顿距离、闵可夫斯基距离、汉明距离距离等。**
另一种替代算法是局部异常值因子(LOF) ,其识别相对于局部邻居而非全局数据分布的局部异常值。它利用一个名为局部可达性密度(lrd) 的度量来表示每个点的密度水*。样本的 LOF 简单地说就是样本相邻样本的*均 lrd 值与样本本身的 lrd 值之比。如果一个点的密度远小于其相邻点的*均密度,那么它很可能是一个异常点。**
基于树的方法
隔离林 是一种基于树的,非常有效的异常检测算法。它构建了多个树。为了构建树,它随机选取一个特征和相应特征的最小值和最大值内的分割值。此过程适用于数据集中的所有样本。最后,通过对森林中的所有树进行*均来构成树集合。
隔离森林背后的想法是,离群值很容易与数据集中的其余样本不同。由于这个原因,与数据集中的其余样本相比,我们预计异常样本从树根到树中的叶节点的路径更短(分离样本所需的分裂次数)。
扩展隔离林 对隔离林的分裂过程进行了改进。在隔离森林中,*行于轴进行分割,换句话说,以水*或垂直的方式在域中产生太多冗余区域,并且类似地在许多树的构造上。扩展隔离林通过允许在每个方向上发生分裂过程来弥补这些缺点,它不是选择具有随机分裂值的随机特征,而是选择随机法向量以及随机截取点。
基于降维的方法
主成分分析 主要用于高维数据的降维方法。基本上,通过提取具有最大特征值的特征向量,它有助于用较小的维度覆盖数据中的大部分方差。因此,它能够以非常小的维度保留数据中的大部分信息。
在异常检测中使用 PCA 时,它遵循与自动编码器非常相似的方法。首先,它将数据分解成一个更小的维度,然后再次从数据的分解版本中重建数据。异常样本往往具有较高的重建误差,因为它们与数据中的其他观测值具有不同的行为,因此很难从分解版本中获得相同的观测值。PCA 对于多变量异常检测场景是一个很好的选择。
时间序列的异常检测
真实生活经历
- 在开始研究之前,请回答以下问题:您有多少追溯数据?单变量还是多变量数据?进行异常检测的频率是多少?(接*实时,每小时,每周?)你应该对哪个单位进行异常检测?(例如,您正在研究流量值,您可能仅对设备或设备的每个插槽/端口进行异常检测)
- 您的数据有多项吗?让我澄清一下,假设您要对电信领域中上一个示例中的设备流量值执行异常检测。您可能有许多设备的流量值(可能有数千种不同的设备),每种设备都有不同的模式,并且您应该避免为每种设备设计不同的模型,以解决生产中的复杂性和维护问题。在这种情况下,选择正确的功能比专注于尝试不同的模型更有用。考虑到小时、工作日/周末信息等属性,确定每个设备的模式,并从其模式中提取偏差(如 z 分数),并将这些特征提供给模型。注意上下文异常大多是在时间序列中处理的。所以,你可以只用一个真正珍贵的模型来处理这个问题。从预测的角度来看,基于多头神经网络的模型可以作为一种先进的解决方案。
- 在开始之前,如果可能的话,你必须向客户询问一些过去的异常例子。它会让你了解对你的期望。
- 异常的数量是另一个问题。大多数异常检测算法内部都有一个评分过程,因此您可以通过选择最佳阈值来调整异常的数量。大多数时候,客户不希望被太多的异常所打扰,即使它们是真正的异常。因此,你可能需要一个单独的假阳性排除模块。为简单起见,如果一个设备的流量模式为 10mbps,并且在某一点增加到 30mbps,那么这绝对是一个异常。然而,它可能不会比从 1gbps 提高到 1.3gbps 更受关注
- 在做出任何关于方法的决定之前,我建议至少对一个子样本的数据进行可视化,这将给出关于数据的深刻见解。
- 虽然有些方法直接接受时间序列,而没有任何预处理步骤,您需要实施预处理或特征提取步骤,以便将数据转换成某些方法的方便格式。
- 注意新奇检测和异常检测是不同的概念。简而言之,在新奇检测中,你有一个完全由正常观测值组成的数据集,并决定新接收的观测值是否符合训练集中的数据。与新奇检测不同,在异常检测中,训练集由正常样本和异常样本组成。 单类 SVM 对于新颖性检测问题可能是个不错的选择。
- 我鼓励看看 python 中的 pyod 和 pycaret 库,它们在异常检测方面提供了现成的解决方案。
有用的链接
DBSCAN、隔离森林、局部异常因子、椭圆包络和一类 SVM
medium.com](https://medium.com/learningdatascience/anomaly-detection-techniques-in-python-50f650c75aaf) [## 使用异常检测技术检测机器故障的开始
介绍
towardsdatascience.com](/detecting-the-onset-of-machine-failure-using-anomaly-detection-techniques-d2f7a11eb809) [## 用于异常检测和状态监控的机器学习
从数据导入到模型输出的分步教程
towardsdatascience.com](/machine-learning-for-anomaly-detection-and-condition-monitoring-d4614e7de770) [## 用于异常检测的最佳聚类算法
让我首先解释一下任何通用的聚类算法是如何用于异常检测的。
towardsdatascience.com](/best-clustering-algorithms-for-anomaly-detection-d5b7412537c8) [## 虚拟异常检测
单变量和多变量数据的无监督异常检测。
towardsdatascience.com](/anomaly-detection-for-dummies-15f148e559c1) [## 异常值检测——理论、可视化和代码
五种算法来统治他们,五种算法来发现他们,五种算法来把他们带到黑暗中…
towardsdatascience.com](/outlier-detection-theory-visualizations-and-code-a4fd39de540c) [## 离群点检测技术概述
什么是离群值,如何处理?
towardsdatascience.com](/a-brief-overview-of-outlier-detection-techniques-1e0b2c19e561) [## 检测流数据中的实时和无监督异常:一个起点
传感器通过在各种系统中收集数据以做出更明智的决策,实现了物联网(IoT)。数据…
towardsdatascience.com](/detecting-real-time-and-unsupervised-anomalies-in-streaming-data-a-starting-point-760a4bacbdf8) [## 价格异常检测的时间序列
异常检测会检测数据中与其余数据不匹配的数据点。
towardsdatascience.com](/time-series-of-price-anomaly-detection-13586cd5ff46)**
可解释句子表示的无监督创建
对于句子相似性/文档搜索应用
图一。用于句子相似性任务的句子表示签名的无监督创建。插图使用 BERT(BERT-大型案例)模型。
TL;速度三角形定位法(dead reckoning)
迄今为止,模型学习句子的固定大小表示,通常有某种形式的监督,然后用于句子相似性或其他下游任务。这方面的例子有谷歌的通用句子编码器(2018) 和句子变形金刚(2019) 。固定大小表示的监督学习往往优于句子表示的非监督创建,只有少数例外,如最*发表的作品 (SBERT-WK,2020 年 6 月),其中固定句子表示是通过从 BERT 模型的不同层提取的词向量的信息内容驱动的加权*均来创建的(然而,已经有类似的跨层 的词向量池的先前方法,这些方法在任务中表现不太好)。
对于句子相似性的特定任务,下面描述的替代简单方法将句子表示为单词表示的无序集合,并且按原样使用该集合,而不将其转换为固定大小的向量。单词表示由 BERT 在没有任何标记数据的情况下在预训练/微调期间学习。这种简单的句子集合表示,看起来似乎像一个单词袋表示,对于短句,其表现几乎与上面提到的模型一样好,甚至在质量上比它们更好(需要通过全面测试进行量化)。这可能部分是因为,组成句子的单词(这里的“单词”用于 BERT 词汇表中的完整单词和子单词标记),所有这些单词都是从 BERT 的 30,000 个单词的固定大小的词汇表中提取的,并且它们的学习向量是上下文不敏感的(例如,单词“cell”的所有含义都被压缩成一个向量), 仍然可以用于表示单词的上下文敏感方面,方法是将这些单词映射到 BERT 词汇表中的其他单词,这些单词捕获它们在句子中的含义,这种映射是由具有掩蔽语言模型(MLM)头的 BERT 模型完成的。
这种方法的优点是
- 与上面提到的两个监督模型不同,我们避免了对标记数据的需要。
- 句子相似性任务的表示质量不会随着句子长度而退化——这是我们在通用句子编码器、句子转换器(下图显示了定性比较)和无监督模型- SBERT-WK 中观察到的限制
- 将句子表示为单词能够解释句子相似性任务的结果——学习固定表示的有用的属性模型通常是缺乏的。例如,在上面提到的所有模型中,与输入句子(在我们可以收获相似句子的分布尾部内)相似的句子的排序列表通常包含至少几个句子,其中与输入句子的语义关系根本不明显,即使存在一个。
这种方法的简单性不仅使我们能够在没有标记数据的情况下执行相似性任务,而且还可以作为基准性能来测试输出固定大小句子表示的未来模型,并且可能优于这种简单方法。
参考实施细节
正如在的一篇早期文章中所研究的,BERT 的原始嵌入捕获了关于任何单词的独特和可分离的信息,无论是独立的还是在句子(使用 BERT MLM 头)的上下文中,就其固定大小词汇表中的单词和子单词而言。这用于为句子相似性任务创建由这些单词的子集组成的句子签名。
图二。 BERT 的原始单词嵌入捕捉有用的和可分离的信息(不同的直方图尾部),关于 BERT 词汇表中其他单词的单词。该信息可以从原始嵌入和它们的变换版本中获得,在它们通过具有屏蔽语言模型(MLM)头的 BERT 之后。图中显示了在通过带有屏蔽语言模型头的 bert 模型之前和之后,BERT 的原始嵌入(28,996 个术语—基于 BERT 的大小写)中术语单元的前 k 个邻居。(1)输入句子。(2)输入句子的标记化版本——柯南不存在于伯特的原始词汇中。它被分成两个术语“Con”和“##nan ”,这两个术语都出现在 BERT 的词汇表中。(3)伯特的 MLM 输出在伯特的词汇中找到最接*变换向量的预测。对于单词“cell ”,前 k 个邻居(显示在右边——该图显示匹配——不是精确的排序)只包含捕获监禁的语义概念的术语。相比之下,单词“cell”在输入到 BERT 之前的前 k 个邻居(显示在左侧)捕获了该单词在其前 k 个邻居中的所有不同意义——“监禁”意义以及“生物”(蛋白质、组织)和移动电话意义(电话、移动电话)。输入之前和之后的前 k 个邻居落在距离/预测分数与计数的直方图的尾部,使得这些邻居与词汇表中的其余单词不同且可分离
例如,考虑下面更长的句子,“康南带着手机去牢房从囚犯身上提取血细胞样本”。这个句子的标记化版本将这个句子映射到 BERT 的大约 30,000 个标记的词汇表 (bert-large-cased) 。除了两个输入词“Connan”和“手机”,其余都是一对一的映射。这里的关键点是输入的标记化版本可以用来在 BERT 的词汇中找到相应的学习向量。
图三。细胞的不同义项在一定程度上被同一个词“细胞”在不同句子位置所映射的不同词所分隔。然而,手机和血细胞上下文之间仍然有一些重叠——“汽车”清楚地区分了手机上下文。然而,考虑到“细胞”、“细胞”和“蜂窝”这三个词,生物语境仍然与手机语境有重叠。这些意义可以通过取更多的前 k 项来分离。例如,生物上下文的第五个术语通过单词组织(上面未示出)来区分它。因此,这是我们希望从分布尾部挑选多少前 k 项与增加签名矩阵大小的性能影响之间的权衡。
当通过伯特的模型(MLM 头)时,这些标记向量被转换成表示这些单词的上下文敏感含义的向量。这可以通过检查上面句子中使用的“细胞”一词得到最好的说明。单词“cell”的前 3 个邻居(这个选择是任意的——我们可以挑选前 k 个邻居,只要它们来自分布尾部)一旦它们通过模型,就映射到 BERT 词汇表中的不同单词。代表监狱的“细胞”有一个 房间 的意思,而在手机上下文中使用的“细胞”则表达了一辆 汽车 的意思。“细胞”一词在生物学上下文中的含义具有生物细胞的概念(未示出的第五个邻居是 组织 )。本质上,即使单词“cell”的含义随着上下文而变化,我们仍然可以在 BERT 的学习词汇中找到捕捉其上下文敏感意义的相应向量。鉴于此,我们可以使用标记化文本的向量以及每个标记在通过 BERT 模型(MLM 头)后的前 k 个邻居作为该句子的签名。
选择前 k 个邻居时,会忽略显示为预测的单字符标记,如标点符号。只要尾部有足够的标记可供选择,我们就可以安全地做到这一点,实际情况就是这样。
本质上,给定一个长度为 N 的句子,假设标记化版本的长度为 M,该句子的签名将是 M(1 + k),其中 k 是在将该句子传递给 BERT 后我们挑选的顶部邻居的数量。句子的签名将是一个具有 M(1+k)行和 D 列的矩阵,其中 D 是维度(对于 bert-large-cased 为 1024)。
计算句子相似度得分的步骤
一旦如上所述计算了一个句子的签名,我们就可以计算两个句子之间的相似性得分,如下面的原型/参考实现所示
图 4。两个句子 A 和 B 之间的相似度计算
当计算输入句子与已知句子集合(例如文档标题)的相似性时,上面计算的成对分数用于计算输入句子与已知集合中所有句子的接*度的相对分数。
上述得分计算中的加权函数是基于参考语料库中术语的出现频率。本质上,从两个句子签名中创建的词对之间的余弦相似性的贡献由该分数对相似性计算的重要性来加权,该相似性计算是这些术语在参考语料库中出现的函数。这确保了句子中出现的像“the”、“of”这样的粘合词比真正抓住意思的词贡献少。句子对的相对长度也在分数计算中考虑,以确保短句选择相似的长句,而不是相反。当使用这种方法进行文档搜索,将文档中的句子转换为句子签名时,这尤其有用。
句子相似性任务中模型绩效的定性比较
使用表征短句(*均句子长度 8 个单词)和长句(*均句子长度 51 个单词)的两组句子来定性地比较三个模型(通用句子编码器-使用、句子转换器和 SBERT-WK) 与上述相似性计算方法。
短句集主要由 USE 和句子转换器在其出版物中展示的测试句子组成。
长句集是从快速文本数据中提取的,人类已经将句子分为 14 类。这些类别不是严格的类别,在某些情况下,一个句子可能属于多个类别。此外,一些测试句子是多个句子——几乎代表一个小段落。
属于单个簇/类别的三个句子被用来表示一个组,总共 42 个句子属于 14 个簇(具有前面提到的关于较长句子集合具有属于多个簇的一些句子的警告)。来自这两个测试的几个集群如下所示。
图 5。由使用和句子转换器出版物中展示的测试句子组成的例句组。在标记化之前,*均句子长度约为 8 个单词
图 6。来自 Fasttext 测试数据的例句组。在标记化之前,*均句子长度约为 51 个单词
总的来说,
- 所有四个模型在小句测试中都表现良好。我们可以看到所有型号的 3x3 单元沿对角线的浅色阴影。
- 在更大的测试中,使用和句子变形器的性能都有明显的下降。沿着对角线大约有 3 个浅色的 3×3 网格,而不是预期的 14 个网格。SBERT-WK 似乎沿对角线有更多的 3×3 网格,尽管它们与热图中的其余单元没有明显区别。此外,对于 SBERT-WK,热图的整体亮度从短句到长句增加,表明句子之间的相似性度量的分布尾部对于长句情况不明显。相比之下,使用和句子变形器保留了短句和长句尾部的区别,但尾部的 3x3 网格较少。
图 7。 USE 以高精度捕捉句子相似性,并从左侧热图中沿对角线的 3x3 网格中回忆短句。在更长的句子对测试中,回忆明显下降——我们几乎看不到对角线上预期的 14 个网格中的 4 个。无论是短测试还是长测试,结果的分离都是一样的——热图颜色阴影范围不受句子长度的影响。
图 8 。Sentence transformer,像 USE 一样,可以高精度地捕捉句子相似性,并从左侧热图对角线上的 3x3 网格中回忆短句。在更长的句子对测试中,回忆明显下降——我们几乎看不到对角线上预期的 14 个网格中的 3 个。无论是短测试还是长测试,结果的分离都是一样的——热图颜色阴影范围不受句子长度的影响。
图九。SBERT-WK,像 USE 和句子转换器一样执行,适用于短文本和句子。一个明显的区别是较长句子的结果分离度下降——热图颜色阴影范围随句子长度而变化,较长句子测试中的 3×3 网格变得难以分离。
图 10 。对于短句,句子签名方法的表现几乎不如其他模型。然而,对于更长的句子测试,它比其他人捕获了更多的聚类——14 个中的 9 个(其中 3 个是半正方形,因为分数计算的不对称性质)。此外,它似乎从暗示属于多个聚类的句子的对角线中捕获语义上接*的邻居。这一点在下面的长句子情况的附加注释部分进行了检查。
句子签名方法与其他三种模型相比有几个独特的方面
- 热图是不对称的,不像其他的。这只是前面描述的不对称分数计算的结果。
- 在远离对角线的地方有很多亮点,特别是在长句测试中——这在所有其他模型中都明显不存在。这部分是因为这些聚类有可能属于多个聚类的句子。还存在一定程度的错误匹配,部分原因是图 3 中捕捉上下文意义的术语的语义扩散。大的 k 会增加扩散(除了影响矩阵大小增加的性能之外),而小的 k 可能不足以消除歧义——k 的选择是一种权衡。然而,与其他模型不同,我们可以根据对分数有贡献的主要描述符来检查这些浅色斑块(在附加注释部分完成)的原因。这是这种方法相对于结果很不透明的其他模型的一个明显优势。
在句子相似性任务的基准测试集上,模型的定量比较仍有待完成。
限制
一些限制是
- 对于短句,这种方法的表现不如其他模型。只有当句子长度增加时,它的表现才优于(在定性测试中)。
- 这种方法不适用于除句子相似性之外的任务,尽管个体标记的上下文敏感签名可以用于标记任务,如无监督 NER 。
- 这种方法就像任何其他模型一样容易出现假阳性,尽管它们可以根据上面提到的解释性描述符被剔除。
最后的想法
像 BERT 这样的基于转换器的模型的未开发的潜力之一是表示其词汇的学习向量的固定集合。虽然这些向量在本质上与 word2vec 等模型学习的单词向量没有什么不同,但这些模型有两个明显的优势
- 固定大小而不是可变大小的词汇。词汇表的大小是固定的,留给我们选择(我们只需要用我们选择的词汇表预先训练模型)使它能够作为一个固定的参考库——这是 word2vec 之类的模型所缺乏的。
- 捕捉句子语境。使用 MLM 中心模型将上下文相关向量映射回 BERT 词汇表中的向量,使我们能够根据单词出现的句子上下文来捕捉单词的上下文相关含义。
这两个事实使得即使是使用上下文敏感词来间接捕获序列信息的签名词包也能够在短句上表现得几乎一样好,甚至在长句上比其他模型更好。
原型参考实现 可在 Github 上获得。
参考/相关工作
用于对当前方法进行定性基准测试的三个模型。这三者在 Github 上都有参考实现
使用 word2vec 等模型评估句子嵌入的基线模型 (2016)。这个简单模型在句子相似性任务中胜过序列模型 (RNNS/LSTMs) 。
附加注释
在长句测试中,导致浅色阴影单元格远离对角线的句子对(每句话约 51 个单词)将在下面进行检查。下图是图 10 的放大版,右侧热图。白色单元格是得分为 1 的句子对(该得分是一个相对度量得分,与余弦距离度量不同,余弦距离度量通常只对完全相同的两个句子得分为 1)
图 11。图 10 中的长句测试(约 51 个单词)的放大热图。距离对角线有 13 个高相似性得分。
远离对角线的白色方块对应的句子对如下所示。
图 12。十三对中的三对似乎是错误的相似性度量,这从句子对(由“|”符号分隔)中可以明显看出
来自句子签名的最高贡献对下面检查三个错误句子对。这些提供了为什么这些句子匹配的洞察力,并可以作为过滤句子对的手段。
句子签名中的第一个句子对及其最佳匹配描述符
句子签名中的第二个句子对及其最佳匹配描述符。这些句子接*的原因从描述符对中显而易见——蝴蝶和飞机常见的翅膀的概念通过几个微弱的成对交互作用叠加成一个信号发挥了主导作用。
在最后一个句子对中,除了一个事实,除了一个事实,即这些句子对没有太大的意义之外,这些句子对的解释力不如前一个句子。
本文由 Quora【https://qr.ae/pNKmJ7】手动导入
基于 Python 的无监督土地覆盖分类
你并不总是需要训练数据。。。
航空影像的用途从军事行动到检查你可能购买的房子的后院。我们的人类大脑可以很容易地识别这些照片中的特征,但对计算机来说就不那么简单了。航空影像的自动分析需要将每个像素分类为土地覆盖类型。换句话说,我们必须训练计算机知道它在看什么,这样它才能知道要找什么。
有两种主要的分类方法。有人监督和无人监督。监督分类使用观察到的数据来指导一种算法,即红色、绿色和蓝色光的组合(图像中的像素值)代表草地、树木、泥土、路面等。无监督分类根据每个像素与其他像素的相似性将像素分配到组中(不需要真实数据或观察数据)。
我之前描述了如何实现一个复杂的、基于对象的算法用于监督图像分析。本文描述了一个简单的实现的 K-均值算法的无监督图像分类。因为无监督分类不需要观察数据(收集这些数据既费时又费钱),所以它可以应用于任何地方。
我们将使用国家农业图像项目(NAIP,如下所示)的一部分图像。文章最后给出了包含所有代码的要点。
入门指南
这个分析只需要三个 Python 模块。scikit-learn
(或sklearn
)gdal
和numpy
。
导入模块,用gdal
加载镜像。用RasterCount
查询图像(gdal
数据集)中的波段数。根据用于收集图像的传感器,您可能有 3 到 500 个波段(用于高光谱图像)。NAIP 有 4 个波段,量化反射红、绿、蓝和*红外光。
另外,创建一个空的numpy
数组来保存来自每个图像波段的数据。我们将展*数据,以便更好地使用sklearn
k-means 算法。空数组需要与影像中行和列的乘积一样多的行,以及与栅格波段一样多的列。
from sklearn.cluster import KMeans
import gdal
import numpy as np naip_fn = 'path/to/image.tif'
driverTiff = gdal.GetDriverByName('GTiff')
naip_ds = gdal.Open(naip_fn)
nbands = naip_ds.RasterCount data = np.empty((naip_ds.RasterXSize*naip_ds.RasterYSize, nbands))
读取每个栅格波段的数据。用numpy.flatten()
将每个 2D 栅格波段阵列转换成 1D 阵列。然后将每个数组添加到data
数组中。现在,所有波段数据都在一个数组中。
for i in range(1, nbands+1):
band = naip_ds.GetRasterBand(i).ReadAsArray()
data[:, i-1] = band.flatten()
k-均值分类
我们可以用三行代码实现 k-means 算法。首先用您想要将数据分组到的集群(类)的数量设置KMeans
对象。通常,您将使用不同数量的分类对此进行测试,以找到最佳分类计数(在不过度拟合的情况下最能描述数据的分类数量)。我们不会在这篇文章中讨论这个问题,只讨论如何进行分类。在对象被设置后,使聚类适合图像数据。最后,使用拟合的分类来预测相同数据的类。
km = KMeans(n_clusters=7)
km.fit(data)
km.predict(data)
保存结果
用labels_
从 k-means 分类中检索类。这将返回输入数据的每一行的类别号。调整标签形状以匹配 NAIP 图像的尺寸。
out_dat = km.labels_.reshape((naip_ds.RasterYSize,\
naip_ds.RasterXSize))
最后,使用gdal
将结果数组保存为光栅。
clfds = driverTiff.Create('path/to/classified.tif',\
naip_ds.RasterXSize, naip_ds.RasterYSize, 1, gdal.GDT_Float32)clfds.SetGeoTransform(naip_ds.GetGeoTransform())
clfds.SetProjection(naip_ds.GetProjection())
clfds.GetRasterBand(1).SetNoDataValue(-9999.0)
clfds.GetRasterBand(1).WriteArray(out_dat)
clfds = None
结论
对任何图像实现无监督分类算法都非常简单。无监督分类结果有一个主要缺点,您应该时刻注意。使用非监督方法创建的类不一定对应于现实世界中的实际特征。这些类别是通过对所有四个波段中具有相似值的像素进行分组而创建的。房子的屋顶可能和水有相似的光谱属性,所以屋顶和水可能会混淆。解释无监督的结果时必须谨慎。
原载于 2020 年 7 月 1 日【https://opensourceoptions.com】。
无监督学习:聚类算法
从头开始的数据科学
使用 K-means 和凝聚聚类算法对无标签数据进行分组
预测模型通常需要所谓的“标记”数据来进行训练——也就是说,数据中有一些你已经填写好的目标变量。当然,我们的目标是在您不知道目标变量的值的未知数据上使用模型,但是如果没有正确标记的训练数据,您就无法验证您的模型。因此,数据生产通常是数据科学项目中最困难的部分。比方说,你想教一台计算机阅读手写内容。收集几百页的文字扫描进去是不够的。你还需要给这些数据贴上标签,让每个单词都有一个相应的“正确读数”。对于像手写这样复杂的东西,您可能需要成千上万的训练样本,并且手工标注许多条目会很费力。
也许如果您遇到一个没有标记目标变量的数据集,有一种方法可以从它那里获得一些洞察力,而不需要经过标记它的麻烦。也许你只是没有时间或资源来标记数据,或者也许没有一个明确的目标变量来预测,是否仍然有一种方法来使用这些数据?这就是‘无监督学习’的世界。无监督学习的一个更常见的目标是对数据进行聚类,以找到合理的分组,其中每个组中的点看起来比其他组中的点更相似。最常见的两种聚类方法是 K 均值聚类和凝聚聚类。
K-均值聚类
所以,你有一些数据,你没有任何类别标签,但你很确定你可以将数据分成合理的组,你只是不知道那些组是什么。让我们生成一些数据进行实验:
一些假设的数据,生成为三个 blobs
如你所见,这些数据是由三个簇生成的。对于本例,这保证了有一个合理的分组为三个集群,但请记住,这里的要点是看您是否能自己找到该分组,因此让我们删除颜色编码:
我们的观点没有颜色编码的好处
从视觉上看,你可能仍然能看到星团,至少能看到一点点,但是我们如何让计算机自己发现星团呢?我们要讨论的第一个策略是 k-means。在 k-means 策略中,首先指定要寻找多少个聚类,在这种情况下,我们假设是 3 个,计算机开始在图上随机放置这么多新点:
我们的数据有三个新的点——我们的质心——用红色表示
这些新点被称为“质心”——它们将成为我们星团的中心。为什么要随机放置?实际上,计算机没有办法提前知道哪里是放置聚类中心的合适位置,所以策略是随机放置它们,然后逐步移动它们以改善聚类。要决定如何移动质心,首先将每个点分配到离它最*的质心:
这些点已经被分配到最*的质心,现在是星形
我已经把形心做成星形,然后根据哪个形心最*来给这些点标上颜色。您可以看到红色和绿色的质心彼此靠*,并朝向数据的边缘,这似乎没有太大的意义-首先,红色聚类只有两个点。所以我们试着把这些质心移到更中心的地方。我们将每个质心移动到聚类的中心,这是聚类内的*均位置。一旦我们移动了质心,我们就把这些点重新分配给最*的质心。这是流程中新任务的第一步:
在将每个质心移动到其聚类的中心并重新分配这些点之后
红色质心没有移动很远,它仍然只有两个点在它的集群中。然而,绿色的质心向星团的中心迈了一大步。当它这样做的时候,它从蓝色星团中捕获了一堆点。因此,当我们重复这个过程时,蓝色质心将进一步移动到顶角,将底部留给绿色和红色:
这一过程又进了一步
再次重复该过程会产生:
我们继续这个过程,一步一步地移动质心,并将这些点重新分配给相关的簇,直到我们找到一组稳定的停止移动的中心。在这种情况下,均衡看起来像这样:
这是分割数据集的一个不错的方法!这也有助于可视化质心本身的运动:
每个质心是如何逐步移动的
在这个过程中,第一个 fews 步骤相当大,因为质心被拉向其簇的中心,但后来它们采取的步骤变得越来越小,因为在每个步骤中只有少数点被重新分配。
在这种情况下,您会注意到集群算法最终基本上发现了我生成数据时创建的组。然而,重要的是,记住这是一个无监督的学习问题,算法没有办法以任何方式验证这些组。这些分组是在没有参考这些点属于哪一个“类”的情况下进行的,仅仅是它们的位置,不管怎样,计算机都无法判断这些分组代表了什么。如果您想要对聚类进行解释,则需要查看这些组并对它们进行思考,并且无法保证人们能够立即看到哪些特征代表了已创建的聚类,尤其是如果有两个以上的特征变量使得数据难以可视化。
另一个挑战是,您创建的分组取决于您的初始质心所在的位置以及有多少个质心,并且,同样,您可能无法验证您找到的分组是否是最佳分组,或者您是否有适当数量的簇!考虑这个例子,它也有三个簇,但是质心从不同的地方开始:
红色和绿色集群最终都迁移到右上角的斑点,在它们之间分裂,留下蓝色质心独自拥有三分之二的数据。k-means 算法无法知道这组聚类是否比我们发现的第一组更有意义。类似地,我们必须指定我们想要将数据分组到多少个簇中,我们不需要选择 3,我们可以选择任何数字。这是另一个分组,有 5 个集群,而不是 3 个:
在某些情况下,您可能有特定的原因来选择一个特定数量的组—也许您正在将客户支持案例划分给一些支持人员,以便您只需要与员工人数一样多的组—但是一般来说,无监督聚类的挑战之一是决定聚类的数量。
在建立 k-means 算法或我们马上要讨论的凝聚算法时要记住的另一件事是,实际上有几种不同的方法来计算抽象空间中两点之间的“距离”。也许最常见的是“欧几里德距离”,你会记得毕达哥拉斯定理。还有“曼哈顿距离”之所以这么叫,是因为它将每个轴上的距离单独累加起来,就好像你正沿着直角街道网格从曼哈顿的一个十字路口走到另一个十字路口。更一般地说,欧几里德距离和曼哈顿距离实际上都是称为闵可夫斯基距离的一般度量的特定情况。如果您正在处理多维但稀疏的数据,您可能还会使用余弦相似度作为相似度的度量——虽然余弦相似度本身并不是距离的度量,但如果您有许多变量,并且每行都趋于“稀疏”,那么余弦相似度在某种程度上可以更快地计算出相似的东西(也许您正在查看的客户可能都从拥有数千种不同产品的在线商店购买了少量产品)。不同的距离度量也可能在最终分组中产生略微不同的结果。
层次凝聚聚类
K-means 是一种“自上而下”的方法;它一次将所有东西分组,然后分几步调整这些分组。聚集聚类是一种自下而上的方式。它开始时没有聚类,只是所有的单个点,然后慢慢地一次一个地将点组合在一起。这个过程很简单。首先你要计算每个点和其他点之间的距离。选择彼此最接*的点,并将它们组合在一起。您继续重复这个过程,计算任何单个点或簇到所有其他点和簇之间的距离,慢慢地将点连接成簇,簇连接成其他簇,直到您只剩下您开始寻找的簇。举例来说,下面是一个在只有 20 个点的较小数据集上工作的算法示例:
这是这个凝聚算法如何结束划分第一个数据集(你会看到一些点自己浮出来,看起来好像是自己分组的,但该算法实际上已经确定了三个集群,这些点实际上正在与附*更大的组进行分组。它们看起来像是自己浮动的,这是我用来自动绘制集群周围的围栏的核密度估计器的一个怪癖):
使用凝聚聚类对原始数据集进行分组
使用这样的算法,除了决定两点之间的距离度量之外,实际上还需要决定如何度量两个聚类之间的距离。您可以测量一个集群中的一个点到另一个集群中的一个点之间的最短距离,有点像两个集群内侧边缘之间的距离,这被称为“单一关联”。你也可以测量其中一个点到另一个点的最远距离——这被称为“完全链接”。或者你可以取一个集群中任意点到另一个集群中任意点的*均距离——“*均连接”。选择不同的距离度量或链接类型可能会导致不同的分组决策。
这种类型的集群的一个好处是它是稳定的。它不像 k-means 方法那样依赖于随机种子。一旦决定了距离的度量和聚类的数量,最终总是会得到相同的聚类。一个缺点是计算量很大。您需要测量每两组点之间的距离(对于 n 个点,此类链接的数量公式为 n(n-1)/2),这只是为了决定您进行的第一个链接!
决定集群的数量
我前面提到过,决定一些集群是这些集群算法的挑战之一。那么,你如何选择集群的数量呢?没有硬性的规则,但是有一些方法可以用来指导你的选择。判断集群的度量标准倾向于关注集群的紧密程度,也就是说,集群中的点有多相似。一个简单的度量可能是组内*方和(WSS ),如果组内的点更分散,则该值会更大,如果点更靠*,则该值会更小。单独使用这种方法的挑战在于,随着您添加更多集群,WSS 将始终下降。你可以通过拥有和点一样多的聚类来最小化 WSS,那么 WSS 就是 0!
您如何知道何时停止添加集群?目标是找到一些聚类,在这些聚类中,再增加一个聚类对 WSS 度量不再有真正显著的好处。如果集群定义良好,那么这种情况通常会非常明显。考虑这个数据集,它被生成为具有 4 个相当不同的斑点:
现在,我将使用 k-means 将这些数据多次分组,每次都增加分组的数量。我将记录每一个的 WSS 值,然后将它与聚类数对应起来绘制成图表:
您会注意到,当我将聚类数从 2 增加到 3,再从 3 增加到 4 时,我的 WSS 度量下降了很多,但每次增加都不是很多。一旦我找到 4 个聚类,算法通常会找到合理的分组。当我添加更多超过 4 的聚类时,算法开始分裂自然的聚类,我的紧凑性度量的边际效益并没有那么大。
这种确定适当聚类数的方法有时被称为“肘方法”;你在寻找图中的弯曲处或肘部,在那里增加 k 不再产生紧凑性方面的大的步骤。这是一个合理的起点,但是没有一种选择集群数量的方法是绝对可靠的。这种方法可能会被重叠、有点模糊或大小非常不同的簇所欺骗。
选择聚类方法
虽然这些不同的算法通常都在处理同一类事情,即紧密分组的簇,但它们以不同的方式完成任务,因此以不同的方式对不同的形状和分布做出响应。举个极端的例子,考虑以下几点:
也许我通过颜色编码泄露了一点,但是我想很多人会同意这里确实有两个不同的类:内环和外环。k-means 算法如何处理这些点?结果并不好:
k-means 不能正确区分嵌套环
这个例子可能会让你觉得有点做作,甚至可能不公*。一个简单的算法可能挑出嵌套的形状吗?然而,答案实际上是肯定的;在这种情况下,使用单个链接来寻找聚类之间的距离的凝聚方法实际上识别了嵌套环:
单链聚合聚类成功!
不幸的是,没有对所有形状都同样有效的主聚类算法,但是在使用聚类方法一段时间后,希望您会有一种直觉,知道哪种情况更适合一种算法或一种距离度量。
用于异常检测的无监督学习
内容
在本帖中,我们将了解:
- 异常检测的需求
- 基于数学的异常检测基线算法
- 评估异常检测算法
- 多元高斯分布的扩展基线算法和马氏距离的使用
- 在 Kaggle 上可用的信用卡数据集上检测欺诈交易
在之前的帖子中,我们深入研究了主成分分析(PCA)及其试图解决的问题。为了巩固我们的概念,我们还在 Kaggle 上的 MNIST 数字数据集上可视化了 PCA 的结果。到目前为止,在每篇帖子中,我们讨论了监督学习算法或非监督学习算法,但在这篇帖子中,我们将讨论异常检测算法,这可以使用监督和非监督学习方法来解决。不过在这里,我们将讨论如何使用无监督学习来解决这个问题,并理解为什么使用无监督学习的异常检测在大多数情况下是有益的。
异常检测
异常是“离群值”一词的同义词。异常检测(或异常值检测)是对罕见项目、事件或观察结果的识别,这些项目、事件或观察结果因与大多数数据显著不同而引起怀疑。异常活动可能与某种问题或罕见事件有关,如银行欺诈、医疗问题、结构缺陷、设备故障等。
异常活动的示例
异常检测的需求
根据 2018 年 6 月发表的一项由 Domo 进行的研究,每天都有超过 2.5 万亿字节的数据被创建,据估计,到 2020 年,地球上每个人每秒将创建* 1.7 兆字节的数据。在新冠肺炎时代,当世界经济因在线业务和在线教育系统而稳定下来时,使用互联网的用户数量随着在线活动的增加而增加,因此,可以有把握地假设人均产生的数据增加了数倍。简而言之,一个人和一个组织的数字足迹已经飞速增长。服务器上充斥着大量的用户活动,这给所有企业带来了巨大的挑战。银行系统中的欺诈活动、社交媒体上的假 id 和垃圾邮件发送者以及针对小型企业的 DDoS 攻击都有可能导致相关组织崩溃,只有找到检测此类恶意(异常)活动的方法,才能防止这种情况的发生。由于引发特定网络攻击的方式数不胜数,因此很难事先在数据集中获得所有这些攻击的信息。但是,由于大多数在线用户活动是正常的,我们可以捕捉到几乎所有表明正常行为的方式。从包含-排除原理来看,如果一个被检查的活动没有给出正常活动的迹象,我们可以很有把握地预测给定的活动是异常的。
让我们用一个类比来理解上面的内容。在人类疾病的世界里,正常的活动可以与疟疾、登革热、猪流感等疾病相提并论。对此我们有治疗方法。另一方面,SarS-CoV-2(新冠肺炎)是一种悄悄进入我们疾病世界的异常现象,除了延迟症状之外,它具有正常疾病的特征。如果 SarS-CoV-2 异常在非常早期被检测到,它的传播可能会被显著控制,我们今天就不会面临疫情。由于 SarS-CoV-2 是一种前所未见的全新异常,即使有监督的学习程序将其检测为异常也会失败,因为有监督的学习模型只是从给定数据集中的特征和标签中学习模式,而通过向无监督的学习算法提供预先存在的疾病的正常数据,我们可以以很高的概率将该病毒检测为异常,因为它不会落入正常疾病的类别(群)中。我希望这给出了足够的直觉来认识到异常检测的重要性,以及为什么在大多数情况下,对于这样的任务,非监督学习方法优于监督学习方法。
异常检测算法
有不同类型的异常检测算法,但我们今天将讨论的算法将从逐个特征的概率分布开始,以及它如何引导我们使用马氏距离进行异常检测算法。
无监督异常检测算法的一个最重要的假设是,用于学习目的的数据集被假设具有所有非异常训练样本(或者非常非常小部分的异常样本)。这似乎是一个非常大胆的假设,但是我们在上一节已经讨论了异常活动的可能性有多小(但是非常危险)。此外,异常检测算法的目标是通过提供给它的数据来学习正常活动的模式,以便当异常活动发生时,我们可以通过包含-排除原则来标记它。有了这个东西,我们再来详细讨论一下异常检测算法。
数据集中的数据点通常具有某种类型的分布,如高斯(正态)分布。并非所有数据集都遵循正态分布,但我们总是可以对要素应用某种变换(我们将在后面的部分中讨论),将数据的分布转换为正态分布,而不会损失任何类型的要素方差。具有高斯分布的数据点集如下所示:
正态分布数据(来源)
从上面的直方图中,我们看到数据点遵循高斯概率分布,并且大多数数据点分布在中心(*均)位置周围。当 y 轴上的频率值作为概率提及时,钟形曲线下的面积总是等于 1。在我们继续讨论之前,先看看下面的正态分布。
(来源)
我们观察到了什么?上面所有的线形图都代表正态概率分布,但它们仍然是不同的。这是因为上面的每个分布有两个参数使每个图都是唯一的:数据的均值(μ) 和方差(σ ) 。红色、蓝色和黄色分布都以 0 *均值为中心,但它们都是不同的,因为它们关于其*均值有不同的分布。另一方面,绿色分布不具有 0 均值,但仍代表正态分布。由此可见,要描述正态分布,两个参数 μ 和 σ 控制着分布的样子。事实上,68%的数据位于*均值的第一个标准差( σ )附*(每侧 34%),26.2 %的数据位于第一个和第二个标准差( σ )(每侧 13.1%)之间,以此类推。这意味着高斯分布中大约 95%的数据位于*均值的 2 个标准偏差之内。我们可以用它来验证真实世界的数据集是否具有(*乎完美的)高斯分布。
真实世界的数据有很多特性。如果每个特征的数据都以正态分布,那么我们可以继续,否则,建议将给定的分布转换成正态分布。下图显示了我们可以对给定的概率分布应用哪些变换来将其转换为正态分布。所得的变换可能不会产生完美的概率分布,但它会产生足够好的*似值,从而使算法运行良好。
转换正态概率分布(来源)
请记住我们所做的假设,所有用于训练的数据都被假设为非异常的(或者应该有非常非常小的一部分异常)。我们将在这里使用它。我们之前看到,正态分布中几乎 95%的数据位于*均值的两个标准偏差之内。由于异常的可能性一般很低,我们可以很有把握地说,分布在*均值附*的数据点是非异常的。由于均值和两个标准差之间的概率分布值足够大,我们可以将此范围内的值设置为阈值(一个可以调整的参数),其中概率大于此阈值的特征值表示给定的特征值不是异常的,否则就是异常的。考虑数据中总共有 n 个特征。对于阈值为 ε(i) 的特征 x(i) ,高于该阈值的所有数据点的概率是非异常数据点,即相对于特定特征的非异常数据点表示为:
其中 P(X(i): μ(i),σ(i)) 表示特征 X(i) 的给定训练样本的概率,其特征在于 μ(i) 的均值和 σ(i) 的方差。
上述情况基于特定特征将数据点标记为异常/非异常。事实上,我们不能根据单一特征将数据点标记为异常。只有当计算出给定数据点的所有特征的所有概率值的组合时,我们才能非常有把握地说一个数据点是否是异常的。这个场景可以从前面的场景扩展,可以用下面的等式表示。
在以下情况下,数据点被视为非异常
现在我们知道了如何使用数据的所有 n 特征来标记异常,让我们快速看看如何计算给定正态概率分布的 P(X(i)) 。为此,我们还需要计算 μ(i) 和 σ2(i) ,具体操作如下。
其中 m 是训练样本的数量,而 n 是特征的数量。
我们现在已经知道了计算正态分布中数据点的概率所需的一切。让我们看一个例子,看看这个过程是如何工作的。考虑由两个特征 x1 和 x2 组成的数据,正态概率分布如下:
如果我们考虑训练集中的一个数据点,那么我们必须分别计算它的概率值 wrt x1 和 x2,然后将它们相乘,以获得最终结果,然后我们将它与阈值进行比较,以确定它是否是异常。为了更好地形象化事物,让我们在二维图中绘制 x1 和 x2,如下所示:
这两个特征的组合概率分布将在 3-D 中表示如下:
所得的概率分布是高斯分布。现在,如果我们考虑围绕中心值的训练示例,我们可以看到它将具有更高的概率值,而不是远的数据点,因为它位于概率分布曲线的相当高的位置。我们之前看到,大约 95%的训练数据位于*均值的 2 个标准偏差内,这导致我们选择第二个标准偏差的边界概率值周围的 ε 的值,然而,这可以根据不同的任务进行调整。这表明位于*均值第二标准差之外的数据点出现异常的概率更高,这从上图中概率分布的紫色阴影部分可以明显看出。
评估异常检测算法
任何异常检测算法,无论是有监督的还是无监督的,都需要进行评估,以了解算法的有效性。由于与正常数据点相比,异常发生的次数相对非常少,因此我们不能使用准确性作为评估指标,因为对于预测一切正常的模型,准确性将大于 99.9%,并且我们不会捕捉到任何异常。这是完全不可取的。我们的要求是评估我们检测到了多少异常,以及我们遗漏了多少异常。将非异常示例预测为异常几乎不会对任何系统造成损害,但是将异常示例预测为非异常会造成重大损害。在这样的评估标准中,一个帮助我们的度量是通过计算预测值的混淆矩阵。混淆矩阵是对分类问题预测结果的总结。正确和错误预测的数量用计数值汇总,并按每个类别细分。这是混乱矩阵的关键。混淆矩阵显示了分类模型在进行预测时被混淆的方式。它不仅让我们洞察到分类器所犯的错误,更重要的是让我们洞察到所犯错误的类型。
真阳性是模型正确预测阳性类别(非异常数据为非异常)的结果。类似地,真阴性是模型正确预测阴性类别(异常数据为异常)的结果。假阳性是模型错误地预测阳性类别(非异常数据为异常)的结果,而假阴性是模型错误地预测阴性类别(异常数据为非异常)的结果。在我们的异常检测算法中,我们的目标是尽可能减少假阴性。假阴性的数量越少,异常检测算法的性能越好。
我们忽略了一个非常重要的细节。我们上面讨论的异常检测算法是一种无监督学习算法,那么我们如何评价它的性能呢?在收集数据的同时,我们肯定知道哪些数据是异常的,哪些不是。不使用监督学习的原因是它不能从如此有限数量的异常中捕获所有的异常。这就是为什么我们使用包含-排除原则的无监督学习。假设我们有 10,040 个训练样本,其中 10,000 个是非异常的,40 个是异常的。这些数据将分为训练集、交叉验证集和测试集,如下所示:
训练集:8000 个非异常示例
交叉验证集:1,000 个非异常和 20 个异常示例
测试集:1,000 个非异常和 20 个异常示例
这种分布将使我们能够捕获非异常数据点中出现的尽可能多的模式,然后我们可以将它们与 20 个异常进行比较和对比,每个异常都在交叉验证和测试集中。这里创建交叉验证集的目的是调整阈值点 ε的值。
这种异常检测算法的缺点
我们需要知道异常检测算法如何分析非异常数据点的模式,以便知道是否有进一步改进的余地。让我们考虑一个数据分布,其中绘制的点不呈圆形,如下所示。
上图中所有的红点都是正常的例子。如果我们考虑用绿色标记的点,利用我们的智慧,我们会将这个点标记为异常点。但是,按照我们所讨论的异常检测算法的工作方式,该点将位于可以被检测为正常数据点的区域中。
到目前为止讨论的异常检测算法都是循环工作的。内圆代表接**均值的正态分布的概率值。绿点所在的第二个圆圈代表接**均值的第一个标准差的概率值,依此类推。这是不可取的,因为我们不会每次都有散点图导致二维圆形分布、三维球形分布等的数据。我们需要一种异常检测算法,它能够根据数据点的分布进行调整,并给出良好的结果。事实证明,对于这个问题,我们可以使用多元高斯分布的 Mahalanobis 距离(MD) 属性(到目前为止,我们一直在处理多元高斯分布)。
多元高斯分布异常检测的马氏距离
马氏距离(MD)是多元空间中两点之间的距离。在规则的欧几里得空间中,变量(例如 x,y,z)由彼此成直角绘制的轴来表示。任何两点之间的距离都可以用尺子测量。对于不相关的变量,欧几里得距离等于 MD。然而,如果两个或更多的变量相关,轴不再是直角,并且用尺子测量变得不可能。另外,如果你有三个以上的变量,你根本无法在常规的 3D 空间中绘制它们。MD 解决了这个测量问题,因为它测量点之间的距离,甚至是多个变量的相关点。
****Mahalanobis 距离测量相对于质心的距离,质心是一个基点或中心点,可视为多元数据的整体*均值。形心是多元空间中的一个点,所有变量的所有*均值都在该点相交。MD 越大,数据点离质心越远。
为了使用 Mahalanobis 距离进行异常检测,我们不需要计算每个特征的单独概率值。相反,我们可以直接计算每个数据点的最终概率,考虑数据的所有特征,最重要的是,由于在计算 Mahalanobis 距离时协方差矩阵σ的非零非对角线值,所得到的异常检测曲线不再是圆形的,而是符合数据分布的形状。
Mahalanobis 距离使用下面给出的公式计算。
一旦计算出 Mahalanobis 距离,我们就可以计算出 P(X) ,一个训练样本出现的概率,假设所有 n 特征如下:
其中|σ|表示协方差矩阵σ的行列式。
值 μ 和σ计算如下:
最后,我们可以设置一个阈值 ε ,其中 P(X) < ε 的所有值都标志着数据中的异常。但是,该值是一个参数,可以使用交叉验证集进行调整,该交叉验证集具有我们为前面的异常检测算法讨论的相同数据分布。
在结束本文的理论部分之前,必须指出,虽然使用 Mahalanobis 距离进行异常检测是一种更通用的异常检测方法,但正是这个原因使得它在计算上比基线算法更昂贵。此外,我们必须使训练样本数 m 大于特征数n(m>n),否则协方差矩阵σ将不可逆(即σ^-1将变得不确定)。
最后,我们到达了文章理论部分的结论部分。在深入研究异常检测算法背后的数学知识之前,我们理解了异常检测算法的必要性。如果此时你的头脑有点混乱,我建议你多读一遍理论部分,尽管这完全是正常的。现在,让我们回顾一下 Kaggle 的欺诈性信用卡交易数据集,我们在这篇文章中使用支持向量机解决了该数据集,并使用异常检测算法解决了该数据集。
问题陈述
在本节中,我们将使用异常检测算法来确定欺诈性信用卡交易。这个问题的数据集可以在这里找到。这里需要注意的一点是,该数据集的特征已经作为 PCA 的结果进行了计算。这在两个方面帮助了我们:
(I)维护用户数据的机密性。
(ii)由于 PCA 变换,数据集中的特征相互独立。
让我们从在熊猫数据框中加载内存中的数据开始。
import numpy as np
import pandas as pd
import matplotlib.pyplot as pltfrom sklearn import preprocessing
from sklearn.neighbors import LocalOutlierFactor
from sklearn.metrics import confusion_matrix, classification_report,accuracy_scoreimport os
for dirname, _, filenames **in** os.walk(‘/kaggle/input’):
for filename **in** filenames
print(os.path.join(dirname, filename))
df = pd.read_csv("/kaggle/input/creditcardfraud/creditcard.csv")
df.head()
我们继续进行数据预处理步骤。让我们看看这些值是如何分布在数据集的各个要素中的。
df.describe()
原始数据集有超过 284k+的数据点,其中只有 492 个是异常的。在整个数据集上训练模型导致 Kaggle 超时,所以我使用了 20%的数据(> 56k 数据点)。
print(df.shape)
data= df.sample(frac = 0.2,random_state=1)
print(data.shape)
数据没有空值,这可以通过下面的代码来检查。
df.isnull().values.any()
让我们在条形图上绘制正常交易与异常交易,以了解数据集中欺诈交易的比例。此外,我们还可以在自己的数据集中区分正常交易和欺诈交易。
num_classes = pd.value_counts(df['Class'], sort = True)
num_classes.plot(kind = 'bar')plt.title("Transaction Class Distribution")
plt.xticks(range(2), ["Normal", "Fraud"])
plt.xlabel("Class")
plt.ylabel("Frequency")fraud = df[df['Class'] == 1]
normal = df[df['Class'] == 0]print(fraud.shape, normal.shape)
在数据集中,我们只能根据输出“类”来解释“时间”和“金额”值。让我们看看,如果我们能找到一些观察,使我们能够明显区分正常和欺诈交易。
f, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
f.suptitle('Time of transaction v/s Amount by Class type')ax1.scatter(fraud.Time, fraud.Amount)
ax1.set_title('Fraud')
ax2.scatter(normal.Time, normal.Amount)
ax2.set_title('Normal')plt.xlabel('Time (in secs)')
plt.ylabel('Amount')
plt.xlim((0, 20000))plt.show()
f, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
f.suptitle('Amount per transaction by class')bins = 10
ax1.hist(fraud.Amount, bins = bins)
ax1.set_title('Fraud')
ax2.hist(normal.Amount, bins = bins)
ax2.set_title('Normal')plt.xlabel('Amount ($)')
plt.ylabel('Number of Transactions')
plt.xlim((0, 20000))
plt.yscale('log')
从第一个情节中,我们可以观察到,欺诈交易与正常交易同时发生,使得时间成为一个不相关的因素。从第二个情节可以看出,大部分的诈骗交易都是小额交易。然而,这并不是一个巨大的区别特征,因为大多数正常交易也是小额交易。
在继续下一步之前,让我们看看在缩减的数据集(20%的特征)中有多少欺诈性和非欺诈性交易,我们将使用这些数据来训练机器学习模型以识别异常。
fraud = data[data['Class']==1]
normal = data[data['Class']==0]anomaly_fraction = len(fraud)/float(len(normal))
print(anomaly_fraction)print("Fraud Cases: " + str(len(fraud)))
print("Normal Cases: " + str(len(normal)))
我们的数据集中只有 0.1%的欺诈交易。这意味着模型的随机猜测对于欺诈性交易应该产生 0.1%的准确性。然而,我们将构建一个比这个更精确的模型。你可能会想为什么我在这里提到这个。我将参考这些线,同时评估最终模型的性能。
回想一下,我们已经知道,为了应用无监督异常检测算法,每个特征应该是正态分布的。让我们为每个特征绘制直方图,看看哪些特征根本不代表高斯分布。
data.hist(figsize=(15,15), bins = 64)
plt.show()
从上面的直方图中,我们可以看到‘时间’、‘V1’和‘v 24’甚至都不是*似高斯分布的。让我们从模型训练过程中去掉这些特性。无论如何,我们都要省略“时间”功能。
columns = data.columns.tolist()target=columns[-1]
columns = columns[:-1]X_train = data.iloc[:45000, :-1]
y_train = data.iloc[:45000, -1]X_test = data.iloc[45000:, :-1]
y_test = data.iloc[45000:, -1]print(X_train.shape, X_test.shape)
print(y_train.shape, y_test.shape)
让我们使用 scikit-learn 库中的localooutlierfactor函数,以便使用上面讨论的无监督学习方法来训练模型。
model = LocalOutlierFactor(contamination=anomaly_fraction)y_train_pred = model.fit_predict(X_train)
y_train_pred[y_train_pred == 1] = 0y_train_pred[y_train_pred == -1] = 1
y_test_pred = model.fit_predict(X_test)y_test_pred[y_test_pred == 1] = 0
y_test_pred[y_test_pred == -1] = 1
现在我们已经训练了模型,让我们通过查看混淆矩阵来评估模型的性能,正如我们之前讨论的那样,准确性不是评估任何异常检测算法的好指标,尤其是像这样具有如此扭曲的输入数据的算法。
import itertoolsclasses = np.array(['0','1'])def plot_confusion_matrix(cm, classes,title='Confusion matrix', cmap=plt.cm.Blues):plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=45)
plt.yticks(tick_marks, classes)fmt = 'd'
thresh = cm.max() / 2.for i, j **in** itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j, i, format(cm[i, j], fmt),
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
上面的函数是一个帮助函数,使我们能够构建一个混淆矩阵。我们将绘制混淆矩阵来评估训练集和测试集的性能。
cm_train = confusion_matrix(y_train, y_train_pred)
plot_confusion_matrix(cm_train,["Normal", "Fraud"])
cm_test = confusion_matrix(y_test_pred, y_test)
plot_confusion_matrix(cm_test,["Normal", "Fraud"])
我们看到,在训练集上,该模型正确检测到 44,870 个正常交易,只有 55 个正常交易被标记为欺诈。这很好,但这不是我们所关心的。我们可以看到,在训练集中的 75 个欺诈交易中,只有 14 个被正确捕获,而 61 个被错误分类,这是一个问题。当我们将这一性能与 0.1%的随机猜测概率进行比较时,这是一个显著的改进,但还不够令人信服。即使在测试集中,我们看到 11,936/11,942 个正常交易被正确预测,但只有 6/19 个欺诈交易被正确捕获。
分析性能
当我在解决这个数据集时,甚至我也惊讶了一会儿,但后来我批判性地分析了数据集,并得出结论,对于这个问题,这是无监督学习所能做的最好的事情。无监督学习表现不够好的一个原因是因为大多数欺诈性交易没有太多可以与正常交易很好地区分开的异常特征。我们针对“类”特征绘制的“时间”和“数量”图支持了这一点。我认为这是数据集提供标记交易为欺诈和非欺诈的标签的主要原因,因为欺诈交易没有任何明显的区别特征。在测试集上检测异常的准确率是 25%,这比随机猜测好得多(数据集中异常的比例是< 0.1%) despite having the accuracy of 99.84% accuracy on the test set.
print('Total fraudulent transactions detected in training set: ' + str(cm_train[1][1]) + ' / ' + str(cm_train[1][1]+cm_train[1][0]))print('Total non-fraudulent transactions detected in training set: ' + str(cm_train[0][0]) + ' / ' + str(cm_train[0][1]+cm_train[0][0]))print('Probability to detect a fraudulent transaction in the training set: ' + str(cm_train[1][1]/(cm_train[1][1]+cm_train[1][0])))print('Probability to detect a non-fraudulent transaction in the training set: ' + str(cm_train[0][0]/(cm_train[0][1]+cm_train[0][0])))print("Accuracy of unsupervised anomaly detection model on the training set: "+str(100*(cm_train[0][0]+cm_train[1][1]) / (sum(cm_train[0]) + sum(cm_train[1]))) + "%")
print('Total fraudulent transactions detected in test set: ' + str(cm_test[1][1]) + ' / ' + str(cm_test[1][1]+cm_test[1][0]))print('Total non-fraudulent transactions detected in test set: ' + str(cm_test[0][0]) + ' / ' + str(cm_test[0][1]+cm_test[0][0]))print('Probability to detect a fraudulent transaction in the test set: ' + str(cm_test[1][1]/(cm_test[1][1]+cm_test[1][0])))print('Probability to detect a non-fraudulent transaction in the test set: ' + str(cm_test[0][0]/(cm_test[0][1]+cm_test[0][0])))print("Accuracy of unsupervised anomaly detection model on the test set: "+str(100*(cm_test[0][0]+cm_test[1][1]) / (sum(cm_test[0]) + sum(cm_test[1]))) + "%")
The entire code for this post can be found 这里是)。
这个帖子到此为止。这篇文章也标志着机器学习系列文章的结束。写这些帖子是一种享受,在这个过程中我也学到了很多。我相信我们对事物的理解取决于我们教给他们的东西,在这些帖子中,我尽我所能简化事物。在最后几个帖子中,数学变得有点复杂,但这就是这些话题的情况。感谢阅读这些帖子。