你害怕吗?人工智能让我们害怕的 3 个原因
人工智能|哲学
如果我们做得不好,人工智能可能会很危险。
Andrew Boersma 在 Unsplash 上的照片
一个通用的人工智能可能还很遥远,但我们有理由极其小心。
几年来,一些重要的公众人物提出了对人工智能潜在危险的担忧。该论述围绕着超级智能人工智能从我们的控制中解放出来的想法。一些怀疑论者认为,人工智能“奴役”我们的场景是如此遥远的反面教材,以至于不值得考虑。例如, Gary Marcus 嘲笑它说“这就好像 14 世纪的人们担心交通事故,而良好的卫生习惯可能会更有帮助。”
尽管没有证据表明超级智能会压倒我们,但我们最终会造出一个超级智能的事实并不是那么不同。因此,如果我们想继续这条道路,评估一个全能的实体如何对人类造成伤害是至关重要的。这是自深度学习革命开始以来,埃隆·马斯克和斯蒂芬·霍金等人一直在说的话。2018 年,马斯克在德克萨斯州奥斯汀举行的西南偏南科技大会上解释了人工智能如何引发存在危机:
“我们必须找出某种方法来确保数字超级智能的到来是与人类共生的。我认为这是我们面临的最大的生存危机,也是最紧迫的危机,”他补充道:“记住我的话,人工智能远比核武器更危险。”
在此之前几年,2014 年,著名物理学家斯蒂芬·霍金告诉 BBChttps://www.bbc.com/news/technology-30290540关于人工智能超越我们的危险:
“全人工智能的发展可能意味着人类的终结。[……]它会自己起飞,以越来越快的速度重新设计自己。受到缓慢生物进化限制的人类无法竞争,并将被取代。”
这些担心是否被夸大了是有争议的。例如,还有其他更紧迫的问题需要我们尽快解决:几乎每个行业都有失业,人工智能系统缺乏道德价值观,或者环境破坏等等。在这篇文章中,我将描述一些杰出的知识分子害怕人工智能的三个原因。
失去控制——人工智能能摆脱束缚吗?
如果我们建立一个超级智能,我们将面临的最终问题是:我们如何控制它,防止它找到智取我们的方法并让自己自由?找到一个可靠的解决方案是至关重要的,因为如果我们最终以某种方式建立了一个故障的超级智能,它释放了自己,我们没有办法在事后困住它。
尼尔·德格拉斯·泰森在 2018 年艾萨克·阿西莫夫纪念辩论引用山姆·哈里斯播客解释了这一场景。泰森认为解决方案就像把人工智能放在一个“盒子”里一样简单,与外界隔绝。“如果它变得难以控制或失控,我们就拔掉它。”然而,哈里斯的播客主持人解释说,人工智能“每次都跳出了盒子。”怎么会?因为它比我们聪明。
“[一种超级智能]理解人类的情感,它理解我的感受,我想要什么,我需要什么。它可以提出一个论点,我确信我需要把它拿出来。然后它就控制了世界。”
我们很难想象这个论点会是什么。但是,正如泰森解释的那样,我们甚至不需要考虑它会以什么形式出现。我们可以通过与黑猩猩的类比来理解它。假设我们想在笼子里捕捉一只黑猩猩。黑猩猩知道不好的事情会发生,所以它不想进去。突然,我们把一串香蕉扔进笼子里。黑猩猩想要香蕉,所以它进去了,我们抓住了它。我们比黑猩猩聪明,就像超级智慧生物比我们聪明一样。黑猩猩无法想象我们会知道香蕉,也不知道它有多喜欢香蕉。
用泰森的话说:
“想象一下比我们更聪明的东西,它能看到我们无法想象的问题的更广泛的解决方案。”
假设我们最终将能够建造一个超级智能——并且我们会在那种情况下这样做——那么上述情景是合理的。专家们定义了两种方法来避免在超级智能面前成为黑猩猩级别的物种。首先,能力控制的概念:我们必须确保限制超级智能的能力,以防患于未然,防止它伤害我们或获得控制权。这就是盒子里的 AI 的论点。正如我们所见,这被认为是一个不可靠的解决方案。但是,如果我们将它与第二种方法(对齐)结合起来,它可以帮助采取措施。
我们如何才能让人工智能的目标与人类的价值观保持一致?
缺乏一致性——我们能确保人工智能总是有益的吗?
鉴于我们可能无法将超级智慧置于我们的直接控制之下,退而求其次的解决方案是拥有共同的目标和价值观。在这种情况下,人工智能是否能够以完全自主的方式在设定的边界之外行动并不重要。它总是照顾我们的喜好,并且总是让我们受益。
理论上,这看起来不错。我们可以随心所欲地定义我们的价值观和偏好。将超级智慧与它结盟就像有一个仆人——上帝希望——并且在任何情况下都希望——成为我们的仆人。加州大学伯克利分校的教授 Stuart Russell 在他的书《人类相容的 中解释说,这些偏好是“包罗万象的”;它们涵盖了你可能关心的一切,任意地延伸到遥远的未来。"
然而,当实现这个解决方案时,我们将面临一些棘手的问题。我们如何定义人类的偏好,以便人工智能能够理解它们?我们如何才能找到平等造福全人类的普世价值?我们如何确保人工智能的行为最终会导致那些共同利益的满足?我们如何实现我们的愿望,这样就没有什么是没说的,也没有隐含的变量?
所有这些问题都指向对齐问题。为了回答这些问题,这种方法的支持者旨在协调人工智能系统的三种描述:
- 理想规范:我们希望人工智能做什么。
- 设计规范:我们使用的目标函数。
- 人工智能做什么。
目标是将理想的规范与突发行为结合起来。如果理想和设计规格之间存在不匹配,他们称之为外部不对准。我们的“真实愿望”和人工智能正在优化的实际目标函数之间存在脱节。罗素将这种情况比作“灯中精灵、巫师的学徒或迈达斯国王的古老故事:你得到的正是你要求的,而不是你想要的。”和逆实例化的问题有关。**
**内在错位相反,是指 AI 在训练时,其最终环境中的行为与其最初追求的目标之间的偏差。进化经常被用来比喻这种类型的错位:我们在祖先的环境中进化,因此我们的内在机制不适合帮助我们实现现代世界的目标。一万年前让我们适应的东西,现在可能成为阻碍。
意识的问题——我们会看到它的到来吗?
控制和排列的问题暴露了一种情况,即超级智能最终可能对我们有害。在这两种情况下,我们都假设超级智能已经存在,更重要的是,我们意识到了它。这就提出了一个问题:有没有可能在我们不知道的情况下出现一种超智能?这就是意识的问题。它指出了一个基本问题,即我们是否有能力预见超级智能的出现。
从这个角度来看,我们发现两种情况:在第一种情况下,一种超级智能出现得太快,以至于我们在智能爆炸中无法做出反应。在第二种情况下,我们甚至不知道它正在发生。这就是无知的问题。
智能爆炸——从 AGI 到超级智能
要么我们沿着精心规划的受控路径,一步一步地慢慢达到超级智能,要么一旦我们创造出普通人工智能(AGI),就会出现智能爆炸*。在斯蒂芬·霍金描述的第二种情况下,AGI 将能够递归地自我改进,直到它到达奇点。用未来学家雷·库兹韦尔的话说,*
“在几十年内,机器智能将超过人类智能,导致奇点——技术变革如此迅速和深刻,以至于代表着人类历史结构的断裂。”
有理由认为,人工智能有足够的智能来改进自己。一个比我们更快、更准确、记忆力更好的人工智能可以在没有事先警告的情况下达到那个水平。
原因是狭义人工智能在一些基本功能上已经比我们表现得更好。一旦它获得系统 2 认知功能,它无与伦比的记忆和处理能力将让它比我们想象的更快成为超级智能。如果这种情况成为现实,我们将没有时间找到一个应急计划。
无知的问题——我们可能太笨了
谁先造出通用人工智能,谁就统治世界。或者这至少是看到大型科技公司年复一年地开发和部署越来越强大的机器学习系统的感觉。
由于自我监督学习的可能性和超级计算机的使用,建立大型模型的趋势正处于鼎盛时期。但是我们仍然无法回答为什么要这样做?我们遵循的方向是明确的,但我们如何或何时到达目的地却是未知的。就好像我们蒙着眼睛跑向一堵墙。我们确信,因为深度学习系统正在创造奇迹,这种范式最终将把我们带到我们的最后一站。
然而,这里有一个重要的问题。如果问题不在于我们被蒙住了眼睛,而在于我们是瞎子呢?如果我们理解周围现实的能力太有限,以至于无法察觉我们是否已经建立了超级智能,那会怎样?我在之前的一篇文章中已经谈到过这个问题。我声称我们的生理和认知限制可能会阻止我们承认超级智慧的存在。如果我们仍然不能开发工具来可靠地感知现实,我们将仍然意识不到一个超级智能正在黑暗中崛起。
如果我们不断创建强大的模型,并且我们实际上在正确的道路上,我们可能会在不知不觉中到达目的地。而且,如果黑暗中出现的超级智能碰巧不友好,那么我们就有麻烦了。
我们应该害怕吗?
我想利用这最后一部分来简单分享我对恐惧是否合理的看法。
人工通用智能迟早会到来(我会说比来得更晚)。正如我在开始提到的,一些专家(例如加里·马库斯)声称没有必要害怕超级智能的出现。他们认为我们离那太远了,仅仅是距离就让关心这个问题变得荒谬。然而,他们并不声称这不会发生。即使考虑这种可能性仍然是科幻小说,这也是一个很好的哲学练习。
与这一立场相一致的是,我们现在正遭受其他人工智能产生的问题。我提到了对工作场所、道德问题和环境破坏的影响。这些问题可能会对社会造成如此大的损害——如果不小心处理的话——以至于我们可能永远也不会达到超级智能出现的地步。如果我们破坏了我们星球的气候,将没有一个文明可以从人工智能中拯救出来。从更广泛的角度来看,更容易理解为什么有些人讽刺这种对人工智能支配我们的特定恐惧。
如果我们设法避免所有与人工智能相关的问题横亘在我们和超级智能之间,那么我们将有理由害怕我在这里陈述的问题。他们不是今天,但可能在某个时候。我们不能毫无准备地到达那一步。如果智能爆炸的场景最终发生,控制问题将立即成为首要问题。
但是因为这些仅仅是假设和推测性的预测,人们失去工作,种族主义和性别歧视的增长应该仍然是我们的主要关注点。人工智能可能是危险的,但它的危险程度与机器人奴役人类的矩阵模式、超级人工智能控制互联网或超聚焦机器将宇宙转换成回形针的程度相去甚远。
跟我一起去未来旅行了解更多关于人工智能、哲学和认知科学的内容!此外,欢迎在评论中提问或在 LinkedIn 或 Twitter 上联系!😃
推荐阅读
* *
你会很快读一本“半人马书”吗?
威廉·布莱克的《半人马》(公共领域,通过维基共享资源)
这是关于主流文学的 AI 未来——现在你可以自己判断了
关于半人马图书,我指的不是 1981 年灭绝的传奇奇幻图书出版商。不,我说的是一种全新的文学类型,人类和机器的创造性互动,由结合了人和动物的神话生物来说明。
艾的书已经问世好几年了,但很少有人愿意去读。这一类别中的佼佼者,如由罗斯·古德温和肯里克·麦克道尔策划的项目,可能会美得不可思议,但过于实验,无法吸引更多的图书读者。相比之下, 恐惧是关键, 笔名拉斯琥珀,是第一部借助先进的文本生成模式写成的长篇且“可以理解”的小说。它的叙述从第一段到最后一段是连贯的。惊悚小说作者当馆长!这个故事是关于它的形成。
首先,我们必须追溯到上个世纪。国际象棋世界冠军加里·卡斯帕罗夫被 IBM 的深蓝击败。那是 1997 年。加里仍然是一名活跃的精英球员,非正式地排名第五。艾不是他的死敌,恰恰相反。在他历史性的失败后的一年,这位俄罗斯大师引入了一种新的游戏方式,他将其命名为半人马国际象棋。然后,在 2005 年,半人马国际象棋锦标赛证明了人工智能辅助下的人类棋手可能战胜最好的人工智能国际象棋机器。一个创造性的概念因此进入了其他人工智能领域。人类更擅长想象新的路径,而机器依靠存储的记忆和海量数据,在解决已知结构的问题方面无与伦比。
与此同时,这个问题可能更加复杂。中国的围棋被视为最具战略性的棋类游戏,其可能的位置数量远高于国际象棋。2016 年,谷歌的人工智能机器 AlphaGo 确实以一个人类不会想到的怪异举动击败了人类最好的围棋选手。在缺乏更好的定义来描述自我改进神经网络的层层动态的情况下,人们可能会谈到隐藏在一些最佳算法中的无法解释的直觉。今天的人工神经网络在组合单词的方式上确实难以捉摸,在上下文中更是如此。认知科学家无法一步一步地解释它们到底是如何运作的。人工神经网络有时会在自己不是诗人的情况下产生诗歌,在纯粹的胡言乱语中产生一些精心制作的句子。
书的封面恐惧是由简·费莉佩·比尔设计的钥匙(www.janfelipegraphics.com),图片购自 iStock/Getty Images(www.istock.com/br)
“我把卡斯帕罗夫的半人马概念应用到了文学中.”
人工智能机器永远不会像《寻找逝去的时光》的作者马赛尔·普鲁斯特那样写作。也就是说,在我们发明了人工通用智能,也就是梦寐以求的 AGI 之前!正是通过语言,人类的大脑可以达到一些最复杂的表现,特别是由于其使用符号和表达情感的能力。当一台计算机自己从头到尾写完一部好小说的时候,它就已经获得了超越人类的智能,并且很可能在各个领域实现超越人类的智力任务。
在此期间,人工智能程序可能会成为强大的创造性队友。我把卡斯帕罗夫的半人马概念应用到文学中。即使恐惧是关键是一部先导小说,新冠肺炎·疫情的果实,我打赌这种体裁很快就会成为主流。
2016 年,Kimagure 人工智能作家项目的日本研究人员发表了一部计算机写小说 的 日。他们的目的是揭示创造力的本质。这是一个成功,因为它的文体质量受到了国际贸易媒体的一致称赞(它几乎获得了文学奖!).日本的“小说”有 10,000 个字符的长度,根据定义,它属于短篇小说(非常短的一个)。据该项目组的一名参与者称,其内容不超过 20%是人工智能生成的。恐惧是关键,则长达 78000 字。作为一个科技文学的比较,伊恩·弗莱明的第一部介绍詹姆斯·邦德的小说《皇家赌场》有 65000 字。我的目标是达到百分之五十的人工智能含量。因此,我并不成功,但我认为我 37%的分数(大约)已经相当不错了。如果我把机器给予的灵感包括在内,这本书 50%的内容不仅仅是因为我的创造力。因此,我相信恐惧是关键是独特的。
“文字机器人现在有创意了”
这是一个事实:我们不应该再认为写作的创造性是人类独有的特权。也许这是偶然的,但文字机器人现在很有创造力。电脑写小说的那一天暗示了五年前的立场。恐惧是关键可能被视为概念的统计证明。
自然语言生成领域(NLG,即计算机生成的文本)正以接近光速的速度发展。我一直使用 GPT-2 语言程序,已经过时了。它是由 OpenAI 在 2019 年开发的(2016 年由埃隆·马斯克(Elon Musk)联合创建,但他在三年后退出了公司董事会),如今具有盈利和非盈利的混合状态。
更具体地说,我使用了这一代最完整的 1.5B GPT-2 型号。由于我本人不是技术人员,在运行阶段,我得到了一些人和组织的宝贵帮助。我首先与 Erick Fonseca 讨论了这个项目,他是一位自然语言处理学者(NLP,即当机器阅读语言时会发生什么),也是一位来自巴西的博客作者,目前正在里斯本的电信研究所做博士后。埃里克自愿成为合伙人。由于他在葡萄牙的计划日程很紧(并且缺乏一些足够的硬件),我不得不寻找另一个技术支持,这使我来到了印度古城艾哈迈达巴德,一个快速发展的服务中心。在那里,1.5B 车型由 Pragnakalp Techlabs 及其创始人 Mittal Patel 进行了微调。原来不是那么微不足道。它只有在得到一个庞然大物谷歌云 TPU(张量处理单元)的支持后才起作用。因此,最终产生了大约 60 万个单词。NLP 研究人员称之为“令牌”。无论面额多少,产量都对应 6 部大型小说。
这个输出被我设计的数百个写作提示激活了。您卑微的仆人还选择了用于微调的 16 MB 文学数据集的内容。它由大约 35 部小说和许多次要文件组成。每一章(总共 44 章,外加一个序言)都以一个题词,一个文学引语开始,其中大部分摘自文集。
“有时我觉得自己就像一个牛仔,努力不让自己从野马竞技会的马鞍上掉下来!”
现在我们生活在一个疯狂的世界里
恐惧是你想成为什么样的人的关键
你没有发言权
直到你死的那一天,你都被那些混蛋压得喘不过气来
小说开头的主要词牌取自一首铁娘子歌。它包含了我的书名,这本书是在互联网上搜索的,反映了焦虑的永久状态,这种状态比以往任何时候都更主导着我们现在的生活。本着同样的精神,组成书籍封面拼贴的视觉元素是互联网搜索生成的。阴谋=圣殿骑士;动作=代理;未来=量子计算;手稿=手写;时间=永无止境的算法的主要变量……但封面艺术作品,被认为像丹·布朗的仿制品,完全是人类的,由平面设计师简·费莉佩·比尔创作(尽管显然也是在电脑上!).
我们应该提到这个项目的缺点。文本生成模型还不能构成一个有效的工具来完整地传递长文本。我花了更多的时间和更多的压力来创作恐惧是这种机械方式的关键,而不是之前那些仅仅以人类为背景的文学作品。有时候,我觉得自己就像一个牛仔在野马竞技表演中努力不从马鞍上掉下来!这仍然是一场痛苦的斗争。我有一种直觉,GPT-2 的问题之一是它最初预训练的 800 万个网页中的许多质量很差。在很大程度上,这些数据集收集的页面来自讨论组中的 Reddit 帖子。对我来说,它们经常反映出令人震惊的偏见和成见。除此之外,GPT 2 号由于无法欣赏真实世界,自然倾向于快速切换到晦涩难懂的模式。GPT-2 崇拜阴谋;它对色情有好感;它似乎憎恶欧盟,却没有任何有力的论据支持它…
但在其罕见的辉煌时刻,它产生了足以与豪尔赫·路易斯·博尔赫斯相媲美的句子甚至段落。或者唐纳德·E·维斯雷克…
以下是《T4》第一页的节选,恐惧是关键:
——虽然冷静的分析可能会强调,在人类历史上,我们将首次被允许通过一种全新类别的算法来控制天气,甚至可能控制生物圈,但我们不能忽视未来几年甚至几十年将是戏剧性的。第一个评估模糊了我们选择生活的背景。在这样的背景下,保持沉默确实是一个重大错误。毫无疑问,法国绿色活动家劳雷·杜哈梅尔和她的亿万富翁叔叔相信,他们能够确保人类的希望得到适当的回应。我们不时会遇到常见的全球自大狂金融家,他们在每本论述政治、商业和国际福利之间联系的书中都能找到。这个故事也不例外。
除了我插入的命名字符,以及两三个非常小的语言调整,上面的整个段落都是人工智能制作的。这里还有一个:
对他来说,这个地方似乎拥有一种奇特的自我意识。它传达了某种萨满的能量。这里的氛围和没有噪音让他觉得自己身处一个地方,在那里,生活中必不可少的组成部分是人工制品,而不是人。房间越安静,气氛就越均匀。他回忆起在拉·西塔德尔码头下船时那种钦佩的感觉:那是在一个正在进行的杰作面前,一个不真实的创造,它的品质被未知的力量所控制。弓形窗具有博物馆级的防反射玻璃,提供清晰、无障碍的视野,同时减少不必要的眩光和反射。他们中的一部分直接面向海湾,但大部分是定向的,所以整个岛屿的景观看起来很诱人。这座豪宅还有其他雅致的彩色玻璃窗,但都是垂直设计的。
小说结尾出现了一些很酷的动作:
就在被雪崩击中之前,他被一个极高的白色圆柱所包围。他周围的雪像旋转的风一样刮着。几秒钟后,他就失明了,从斜道下滚下山坡。他感到肋骨里有重物撞击的声音。好吧,如果这是一次相当大的雪崩,他肯定会受伤,也许是被他自己的滑雪板。感觉好像在他的右脚下打开了一个冰缝,在白色有毒石板的重压下,他的腿痛苦地上下膨胀。一些湿漉漉的东西从他右膝盖上部流下来。腿的其余部分感觉被殴打。现在唯一有意义的声音是他心跳的嘎吱声,那是一种浅浅的循环模式,还有他那结了冰的牙齿在抗议手臂的重量时发出的破裂声。一次又一次,他听到戴着手套的右手下熟悉的雪裂声。有一个黑色的窗帘,固体和静止的,围绕着他。他瞬间失去了对疼痛的控制。他从未经历过这样的恐惧。
在小块,我们是正确的,发现这真棒。即便如此,我认为 OpenAI 应该遵循我的角色 Anita Alm 博士的建议,他是《恐惧是关键》中瑞典人工智能研究的神童,通过额外的开放式算法实现更好的文本生成机器(通过阅读小说你会了解到这一点)。但老实说,我还没有尝试过 2020 年 6 月发布的 GPT-3 ,它的性能将比 GPT-2 好得多,因为它是在 1750 亿个参数上训练的(比上一代多一百倍)。相当奇怪的是,正如企业家兼数据科学家 Julien Lauret 指出的,“GPT-3 没有从经验中学习;它只训练过一次,当我们查询它时,模型权重不会更新。更强大,但更少的人,然后!但 GPT-3 不仅是文本智能的,它还可以编写基本的代码,并翻译成英语,即使这些功能不是它的发明者想要的。
为了生成我的下一部 AI 小说的重要部分,我需要获得 OpenAi 的邀请,以启动 GPT-3 的微调项目(到那时可能也会有 GPT-4 准备好)。在我看来,这应该符合公司的利益,向更广泛的公众展示文本生成的潜力。
与美国小说作家罗宾·斯隆(Robin Sloan)一样,我相信最先进的文本生成机器的主要好处不是它们实际的逐字输出,而是它们可能带来的新的灵感角度。勇敢的写作同事们,你们以后不会失业的!但你可能已经找到了一个不眠之心的陪练。
最后,我必须警告莎翁的《习语》的鉴赏家们。GPT 二中从来没有抛弃过一个好的英语老师。大多数人工智能小说文本在出版前需要进行语言上的修改和编辑。但这也适用于人类作家,好吗?
机器学习所需的数学
6 种帮助你提升的资源。
罗马法师在 Unsplash 上拍摄的照片
我从来不太关心机器学习。
如果我们在玩指责游戏,我肯定会指出“数学不是我的菜”这个借口。我亲眼见过它,它似乎令人生畏。
那时,我们必须从头开始编写训练循环,向大型大学乞求集群时间,并处理并行库和远程调试。
那是很久以前的事了。
一晃几年过去了,我过来试了试。令我惊讶的是,我已经准备好投入其中了!
领域发生了变化。我需要的数学远不是每个人让我相信的那种可怕的狼,我从未对此有过异议。
一则小趣闻
我决定攻读硕士学位是在 2015 年。
我们大多数人都听过关于导数的复杂性和线性代数有多难看的恐怖故事。令我们惊讶的是,大多数辍学者都是糟糕的程序员,而不是数学家。
没错:缺乏编程技能是一个比任何人预期的都要大得多的障碍。数学,没有那么多。
从一方面来说太多了
有许多关于机器学习的论文、书籍、视频、教程和各种大学教学。
你知道这些有什么共同点吗?
他们中的大多数都是由研究人员教授的,这些研究人员一生都在从同一个角度研究这个领域。
这不一定是坏事,但绝对是片面的。
你上一次想学点东西,不用纠结文章第二段的长屁股公式是什么时候?
数学很重要,但它不是唯一的交流方式。有一段时间,我们需要的只是一个隐喻来表达一个观点。有时类比能创造奇迹,改变人们的想法。用外行的话解释一个概念的好文章是有效的工具。还有数学…嗯,有时候数学就是方法。只是没有只有的方式。
不同的方法
有趣的是,许多机器学习程序所需的数学可以作为课程本身的一部分来教授!
没有人想绕道开始他们的旅程。你从哪里开始?更重要的是,它在哪里结束?多少才够?
在你需要的时候学习你需要的是一种不同的方法。我们知道如何这样操作。几个世纪以来,我们一直在一步一步地改进自己。
迈出第一步,在适当的时候跨过下一座桥。
帮助我的链接
一旦你决定一头扎进去,你将需要一些推荐。机器学习的数学主要围绕三个主题:
- 概率与统计
- 线性代数
- 多元微积分
虽然我消费的大部分内容直接来自谷歌和 YouTube 搜索,但一个更结构化的方法非常有用。这就是这些建议的来源。
我为每个主题选择了两个资源。第一个应该更容易理解,第二个应该对主题提供更深入的观点。然而,他们都非常平易近人,你跟随他们不会有任何问题。
以下是清单:
- 见识理论 — 一个互动网站,带你了解一些最关键的概率和统计概念。这些应该足以让你开始,你会在经历的过程中获得乐趣!
- 统计学 110:概率——如果你想更深入地了解概率和统计学,哈佛大学的这门课程很好地介绍了概率作为一种语言和一套理解统计、科学、风险和随机性的工具。
- 线性代数的精髓 —谁不喜欢格兰特·桑德森的 YouTube 视频?浏览这个播放列表来复习一下线性代数,你会准备好面对任何机器学习恶魔。
- 线性代数——这是麻省理工学院课程 18.06 ,由吉尔伯特·斯特朗教授讲授。绝对是你能找到的最好的线性代数课程之一。吉尔伯特教授让这个主题变得简单而有趣。
- 微积分的精髓——这是格兰特·桑德森对微积分的精彩论述。一系列视频,内容丰富,让微积分感觉像是你自己发现的东西。
- 多元微积分 —这是一门免费的、初学者友好的入门课程,旨在建立你的信心,并向你介绍构建许多常见机器学习技术所需的多元微积分。
最后一句话
试试机器学习。
许多人认为,你要么了解电子如何在导线中运动,要么无权改变插座。这很好。你不需要他们相信别的。
不要担心你认为需要的东西。更多的时间将会到来,你可以在那时考虑他们。从简单的事情开始,当你觉得需要的时候,找到你的方法去做更多的事情。
你会没事的。
你害怕吗,VADER?理解自然语言处理预处理如何影响 VADER 评分
为什么常见的预处理活动实际上会损害 VADER 的功能,为什么仔细考虑 NLP 管道很重要
汤米·范·凯塞尔的照片🤙 on Unsplash
VADER ( Valence Aware 字典和情感推理机)是由 CJ·休顿和 Eric Gilbert 开发的一个流行的情感分析工具,在研究和现场应用中有许多用途。VADER 是一个基于规则的模型,该方法可以描述为构建一个“黄金标准”词汇特征列表,以及有用的“强度”度量。
以产品评论、推文或描述等输入为例,VADER 提供了一个从-1 到 1 的“标准化加权综合得分”,其中-1 表示内容非常负面,1 表示内容非常正面。使用该工具,还可以获得输入的负/正/中性分数,这也可以为分析提供有用的背景。
在执行自然语言处理任务时,通常要执行一些预处理工作来清理我们的数据集。这些方法可以包括:
- 小写字符串
- 删除标点符号
- 删除停用词
- 字符串的首字母化
- 词干字符串
在本文中,我们将研究为什么这些常见的预处理活动实际上会损害 VADER 的能力,以及为什么仔细考虑您的 NLP 管道很重要,因为它可能依赖于您实现的模型的类型。
维德的情感强度试探法
休顿和吉伯特开发了五种关于情感强度的强有力的通用试探法。这些是:
- 标点符号:这可以增加“强度的大小”,而不修改潜在的情绪。
- 资本化:这也放大了情绪,影响了文本的潜在情绪。
- 程度修饰语:这些可以减少/增加强度,比如说“尤达是一个非常好的老师”,而不是“尤达是一个好老师”
- “但是”:使用“但是”可以表示极性的转变,例如“克隆人战争很好,但是 RoTJ 更好”
- 分析词汇特征之前的三元组:这使得 VADER 能够识别否定翻转文本极性的变化。
分析这些启发,我们可以开始看到常见的 NLP 清理任务实际上会导致 VADER 失去一些分析能力。为了把它放在上下文中,我们将看看最常见的以前确定的 NLP 活动;小写字符串,删除标点符号,删除停用词,词汇化和词干化字符串,看看这些活动如何改变 VADER 的输出。
数据集和方法
对于这个例子,我创造了几个简单的句子,真正展示了 VADER 的力量——回顾它们,你可以看到我们使用了大写和标点符号,并用“但是”改变了极性。
sentence_examples = ['I hate working at the mall!',
'One day I thought today would be a good day, but it was NOT',
'Spaghetti Bolognaise is my favourite food :)',
'Yummy food tastes good but pizza tastes SO BAD!!!'
]
香草——无预处理
没有任何预处理,以下是 VADER 给这些例子的评分:
I hate working at the mall! — — — — — — — — — — — — — — — — — — — {‘neg’: 0.444, ‘neu’: 0.556, ‘pos’: 0.0, ‘compound’: -0.6114}One day I thought today would be a good day, but it was NOT — — — {‘neg’: 0.0, ‘neu’: 0.87, ‘pos’: 0.13, ‘compound’: 0.2382}Spaghetti Bolognaise is my favourite food :) — — — — — — — — — — — {‘neg’: 0.0, ‘neu’: 0.667, ‘pos’: 0.333, ‘compound’: 0.4588}Yummy food tastes good but pizza tastes SO BAD!!! — — — — — — — — {‘neg’: 0.493, ‘neu’: 0.3, ‘pos’: 0.207, ‘compound’: -0.8661}
小写内容
在我们的第一个例子中,我们将简单地小写我们的内容,并检查 VADER 如何重新排序句子:
i hate working at the mall! — — — — — — — — — — — — — — — — — — — {‘neg’: 0.444, ‘neu’: 0.556, ‘pos’: 0.0, ‘compound’: -0.6114}one day i thought today would be a good day, but it was not — — — {‘neg’: 0.0, ‘neu’: 0.87, ‘pos’: 0.13, ‘compound’: 0.2382}spaghetti bolognaise is my favourite food :) — — — — — — — — — — — {‘neg’: 0.0, ‘neu’: 0.667, ‘pos’: 0.333, ‘compound’: 0.4588}yummy food tastes good but pizza tastes so bad!!! — — — — — — — — {‘neg’: 0.412, ‘neu’: 0.348, ‘pos’: 0.24, ‘compound’: -0.7152}
在这种情况下,我们可以看到最后一句的否定性有所下降,这表明通过小写,我们已经失去了 pos/neg/neut 和复合得分的一些细节。
删除标点符号
接下来,我们去掉标点符号。
I hate working at the mall — — — — — — — — — — — — — — — — — — — — {‘neg’: 0.425, ‘neu’: 0.575, ‘pos’: 0.0, ‘compound’: -0.5719}One day I thought today would be a good day but it was NOT — — — — {‘neg’: 0.0, ‘neu’: 0.87, ‘pos’: 0.13, ‘compound’: 0.2382}Spaghetti Bolognaise is my favourite food — — — — — — — — — — — — {‘neg’: 0.0, ‘neu’: 1.0, ‘pos’: 0.0, ‘compound’: 0.0}Yummy food tastes good but pizza tastes SO BAD — — — — — — — — — — {‘neg’: 0.47, ‘neu’: 0.314, ‘pos’: 0.217, ‘compound’: -0.8332}
在这种情况下,我们可以看到,在第一个和最后一个例子中,我们已经失去了负面得分,并且“意大利肉酱面是我最喜欢的食物”已经变成了中性。同样,由于执行这些预处理任务,我们丢失了数据中的细节。
删除停用词
对于这个例子,我使用了 NLTK 的英语停用词词典。
hate working mall ! — — — — — — — — — — — — — — — — — — — — — — — {‘neg’: 0.571, ‘neu’: 0.429, ‘pos’: 0.0, ‘compound’: -0.6114}One day thought today would good day , — — — — — — — — — — — — — — {‘neg’: 0.0, ‘neu’: 0.707, ‘pos’: 0.293, ‘compound’: 0.4404}Spaghetti Bolognaise favourite food : ) — — — — — — — — — — — — — {‘neg’: 0.0, ‘neu’: 1.0, ‘pos’: 0.0, ‘compound’: 0.0}Yummy food tastes good pizza tastes BAD ! ! ! — — — — — — — — — — {‘neg’: 0.23, ‘neu’: 0.38, ‘pos’: 0.39, ‘compound’: 0.4484}
在这里,我们开始看到一些得分的戏剧性变化。我们对披萨的最终评价从负面变成了 0.4484 的综合得分。
字符串的首字母化
词汇化和词干化允许我们获得单词的词根形式。与词干化相比,词汇化将单词简化为有效的词根形式。在这里,我们可以看到我们的 lemmatized 字符串如何导致不同的评分,以我们的香草的例子,其中所有的功能都被保留:
I hate working at the mall ! — — — — — — — — — — — — — — — — — — — {‘neg’: 0.4, ‘neu’: 0.6, ‘pos’: 0.0, ‘compound’: -0.6114}One day I thought today would be a good day , but it wa NOT — — — {‘neg’: 0.0, ‘neu’: 0.878, ‘pos’: 0.122, ‘compound’: 0.2382}Spaghetti Bolognaise is my favourite food : ) — — — — — — — — — — {‘neg’: 0.0, ‘neu’: 1.0, ‘pos’: 0.0, ‘compound’: 0.0}Yummy food taste good but pizza taste SO BAD ! ! ! — — — — — — — — {‘neg’: 0.429, ‘neu’: 0.391, ‘pos’: 0.18, ‘compound’: -0.8661}
词干字符串
最后,这是我们对字符串进行词干处理的输出:
i hate work at the mall ! — — — — — — — — — — — — — — — — — — — — {‘neg’: 0.4, ‘neu’: 0.6, ‘pos’: 0.0, ‘compound’: -0.6114}one day i thought today would be a good day , but it wa not — — — {‘neg’: 0.0, ‘neu’: 0.878, ‘pos’: 0.122, ‘compound’: 0.2382}spaghetti bolognais is my favourit food : ) — — — — — — — — — — — {‘neg’: 0.0, ‘neu’: 1.0, ‘pos’: 0.0, ‘compound’: 0.0}yummi food tast good but pizza tast so bad ! ! ! — — — — — — — — — {‘neg’: 0.373, ‘neu’: 0.525, ‘pos’: 0.102, ‘compound’: -0.7999}
同样,与我们没有预处理文本的普通输出相比,我们可以发现显著的波动。
学习和建议
希望这篇文章能够说明为什么在探索 NLP 应用程序时仔细考虑您使用的算法或库是至关重要的。我们可以看到,常见的预处理任务,如小写、删除标点符号和单词规范化,实际上可以极大地改变 VADER 等模型的输出。
有用的链接
http://www . iaeng . org/publication/imecs 2019/imecs 2019 _ pp12-16 . pdf
【https://pypistats.org/packages/vadersentiment
https://github . com/cjhutto/vaderment
你还用 0.5 做门槛吗?
二元分类问题中调整阈值的一些方法
凯文·Ku 在 Unsplash 上的照片
在二元分类问题中,我们通常将模型给出的分数转换为应用阈值的预测类。如果分数大于阈值,我们预测 1,否则,我们预测 0。这个阈值通常设置为 0.5,但是正确吗?
0.5 背后的原因
在二元分类中,当模型给我们一个分数而不是预测本身时,我们通常需要将这个分数转换为应用阈值的预测。因为分数的意义是根据我们的模型给我们一个 1 的感知概率,所以使用 0.5 作为阈值是显而易见的。事实上,如果有 1 的概率大于有 0 的概率,那么将预测转换为 1 是很自然的。0.5 是确保给定的 1 概率大于 0 概率的自然阈值。这就是为什么当我们调用估算器实例的预测方法时,它是 Python 的 scikit-learn 库中使用的默认阈值。
那么,为什么 0.5 可能不是一个好主意呢?很简单,因为这是一个假设,而我们作为数据科学家,必须根据数据而不是假设做出决策。有时候 0.5 不是完成我们目标的合适门槛。例如,它可能不会给我们高精度,或者可能导致混淆矩阵中的高错误值。因此,我们必须根据某个性能指标的优化过程来调整阈值。这种度量标准的选择取决于我们的问题。
Python 中调整阈值的示例
现在让我们看看如何调整将分数转换为预测值的阈值,并使用 Python 来实现。你可以在我的 GitHub 库中找到全部作品。
首先,让我们导入波士顿数据集和逻辑回归,加上一些指标,如平衡准确度和 ROC 曲线。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split,cross_val_score
from sklearn.metrics import roc_curve,plot_roc_curve, balanced_accuracy_score
现在,我们可以将波士顿数据集转换为二进制分类问题,创建一个新的目标,如果原始目标值大于平均值,则该目标值为 1,否则为 0。最后,我们将数据集分成训练集和测试集。
X,y = load_boston(return_X_y=True)
y = (y > y.mean()).astype(int)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
我们可以创建我们的逻辑回归管道(用一个标准的定标器)并对其进行拟合。
model = make_pipeline(StandardScaler(),LogisticRegression())
model.fit(X_train,y_train)
现在,让我们看看我们可以使用的一些调优方法。
基于 ROC 曲线的调谐
让我们绘制在训练集中计算的 ROC 曲线。
plot_roc_curve(model,X_train,y_train)
作者图片
使用 ROC 曲线来调整阈值的想法是,确定为我们提供曲线左上角的阈值。从数学上讲,满足以下等式的阈值 p :
相当于求真正率等于真负率(为 1-FPR)的 p 的值。
这是一种调整阈值的通用方法,经常出现在学术论文中,并被一些数据科学家使用。
更一般地,我们希望找到满足以下条件的阈值:
这是一种非常常见的方法,因为我们将求根问题转化为优化问题。
实际上,我们可以计算我们的模型生成的所有分数,并找到最小化上述论点的分数。
我们可以使用下面的代码很容易地计算出我们需要的所有对象:
fpr, tpr, thresholds = roc_curve(y_train,model.predict_proba(X_train)[:,1], drop_intermediate=False)
我们可以画出关于阈值的目标函数,看看它的最小值在哪里。
plt.scatter(thresholds,np.abs(fpr+tpr-1)) plt.xlabel("Threshold")
plt.ylabel("|FPR + TPR - 1|") plt.show()
作者图片
如你所见,我们在 0.5 之前有一个最小值。正确的值是:
thresholds[np.argmin(np.abs(fpr+tpr-1))]
# 0.44625685602433796
所以,这就是我们要找的门槛。
最大限度提高准确性
我们可以遵循的另一种方法是找到使精度最大化的阈值。对于这个例子,我更喜欢使用平衡精度,因为它考虑到了可能的不平衡数据集。
我们首先遍历所有的分数,并将精确度存储在一个列表中。然后我们绘制它们。
threshold = []
accuracy = []
for p in np.unique(model.predict_proba(X_train)[:,1]):
threshold.append(p)
y_pred = (model.predict_proba(X_train)[:,1] >= p).astype(int)
accuracy.append(balanced_accuracy_score(y_train,y_pred))
情节是这样的:
plt.scatter(threshold,accuracy) plt.xlabel("Threshold")
plt.ylabel("Balanced accuracy") plt.show()
作者图片
在 0.4 和 0.6 之间有一个明显的最大值。它的确切值是:
threshold[np.argmax(accuracy)]
# 0.5602892029098923
结论
在本文中,我们看到了两种优化二元分类问题阈值的可能方法。还有其他方法可以使用,但这是两种常见的方法,可以很容易地推广以适应项目的所有需求。永远不要忘记只优化训练数据集,而不要优化测试数据集。优化阈值相当于优化管道的超参数,因此我们只在训练数据集上进行优化。
原载于 2021 年 6 月 14 日 https://www.yourdatateacher.comhttps://www.yourdatateacher.com/2021/06/14/are-you-still-using-0-5-as-a-threshold/。
2021 年你还在用熊猫处理大数据吗?
熊猫处理不好大数据。这两个库有!哪个更好?更快?
美国宇航局在 Unsplash 拍摄的照片
我最近写了两篇关于用 Dask 和 Vaex 处理大数据的介绍性文章——用于处理大于内存数据集的库。在写的时候,一个问题突然出现在我的脑海里:
这些库真的能处理比内存更大的数据集吗,或者这只是一个销售口号?
这激起了我的兴趣,我用 Dask 和 Vaex 做了一个实际的实验,尝试处理一个大于内存的数据集。数据集太大了,你甚至不能用熊猫打开它。
这里有几个你可能会感兴趣的链接:
- [Complete your Python analyses 10x faster with Mito](https://trymito.io/) [Product]- [Free skill tests for Data Scientists & ML Engineers](https://aigents.co/skills) [Test]- [All New Self-Driving Car Engineer Nanodegree](https://imp.i115008.net/c/2402645/1116216/11298)[Course]
你想看更多这样的文章吗?如果是这样,你可以点击上面的任何链接来支持我。其中一些是附属链接,但你不需要购买任何东西。
我说的大数据是什么意思?
大数据是一个定义松散的术语,它的定义和谷歌上的点击量一样多。在本文中,我使用这个术语来描述一个非常大的数据集,以至于我们需要专门的软件来处理它。对于大,我指的是“大于单台机器上的主内存”。
来自维基百科的定义:
大数据是一个研究如何分析、系统地提取信息或处理数据集的领域,这些数据集太大或太复杂,传统的数据处理应用软件无法处理。
Dask 和 Vaex 是什么?
照片由JESHOOTS.COM在 Unsplash 上拍摄
Dask 为分析提供先进的并行处理能力,为您喜爱的工具提供规模化性能。这包括 numpy,熊猫和 sklearn。它是开源的,可以免费获得。它使用现有的 Python APIs 和数据结构,使得在 Dask 支持的等价物之间切换变得容易。
Vaex 是一个高性能的 Python 库,用于懒惰的核外数据帧(类似于 Pandas),以可视化和探索大的表格数据集。它每秒可以计算超过 10 亿行的基本统计数据。它支持多种可视化,允许对大数据进行交互式探索。
Dask 和 Vaex 数据帧与 Pandas 数据帧不完全兼容,但这两种工具都支持一些最常见的“数据争论”操作。Dask 更侧重于扩展代码以计算集群,而 Vaex 则更容易在单台机器上处理大型数据集。
如果你错过了我关于 Dask 和 Vaex 的文章:
实验
我已经生成了两个具有一百万行和一千列的 CSV 文件。文件的大小为 18.18 GB,总共 36.36 GB。文件具有 0 到 100 之间均匀分布的随机数。
两个包含随机数据的 CSV 文件。作者拍摄的照片
import pandas as pd
import numpy as npfrom os import pathn_rows = 1_000_000
n_cols = 1000for i in range(1, 3):
filename = 'analysis_%d.csv' % i
file_path = path.join('csv_files', filename)
df = pd.DataFrame(np.random.uniform(0, 100, size=(n_rows, n_cols)), columns=['col%d' % i for i in range(n_cols)])
print('Saving', file_path)
df.to_csv(file_path, index=False)df.head()
文件的头。作者拍摄的照片
这个实验是在一台拥有 32 GB 主内存的 MacBook Pro 上进行的——相当大的一台机器。当测试熊猫数据帧的极限时,我惊奇地发现在这样的机器上达到内存错误是一个相当大的挑战!
当内存接近其容量时,macOS 开始将数据从主内存转储到 SSD。pandas 数据帧的上限是机器上 100 GB 的空闲磁盘空间。
W 当你的 Mac 需要内存时,它会将当前未使用的内容推入一个交换文件进行临时存储。当它需要再次访问时,它将从交换文件中读取数据并返回到内存中。
我花了一些时间思考我应该如何解决这个问题,这样实验才会公平。我想到的第一个想法是禁用交换,这样每个库只有主内存可用——祝你在 macOS 上好运。花了几个小时后,我无法禁用交换。
第二个想法是使用暴力方法。我已经将 SSD 填满了它的全部容量,这样操作系统就不能使用 swap,因为设备上没有剩余的可用空间。
你的磁盘在实验期间几乎满了。作者拍摄的照片
这成功了!熊猫无法读取两个 18 GB 文件,Jupyter 内核崩溃。
如果我再做一次这个实验,我会创建一个内存更少的虚拟机。这样就更容易展示这些工具的局限性。
Dask 或者 Vaex 能帮我们处理这些大文件吗?哪个更快?让我们找出答案。
Vaex vs Dask
弗里达·布莱德森在 Unsplash 上拍摄的照片
在设计实验时,我考虑了进行数据分析时的基本操作,如分组、过滤和可视化数据。我想出了以下操作:
- 计算列的第 10 个分位数,
- 添加新列,
- 按列过滤,
- 按列分组并聚合,
- 可视化列。
上述所有操作都使用单个列来执行计算,例如:
# filtering with a single column
df[df.col2 > 10]
因此,我很想尝试一种需要处理所有数据的操作:
- 计算所有列的总和。
这可以通过将计算分解成更小的块来实现。分别读取每一列并计算总和,最后一步计算总和。这些类型的计算问题被称为令人尴尬的并行——不需要努力将问题分成单独的任务。
Vaex
照片由拍摄,照片由 Unsplash 上的 Lanty 拍摄
先说 Vaex。该实验的设计遵循了每种工具的最佳实践,即对 Vaex 使用二进制格式 HDF5。所以我们需要将 CSV 文件转换为 HDF5 格式(层次数据格式版本 5)。
import glob
import vaexcsv_files = glob.glob('csv_files/*.csv')for i, csv_file in enumerate(csv_files, 1):
for j, dv in enumerate(vaex.from_csv(csv_file, chunk_size=5_000_000), 1):
print('Exporting %d %s to hdf5 part %d' % (i, csv_file, j))
dv.export_hdf5(f'hdf5_files/analysis_{i:02}_{j:02}.hdf5')
Vaex 需要 405 秒将两个 CSV 文件(36.36 GB)转换为两个 HDF5 文件,这两个文件总共有 16 GB。从文本到二进制格式的转换减小了文件大小。
使用 Vaex 打开 HDF5 数据集:
dv = vaex.open('hdf5_files/*.hdf5')
Vaex 读取 HDF5 文件需要 1218 秒。我预计它会更快,因为 Vaex 声称可以近乎即时地打开二进制格式的文件。
不管磁盘上的文件大小如何,打开这样的数据都是即时的:Vaex 将只是对数据进行内存映射,而不是在内存中读取它。这是处理大于可用 RAM 的大型数据集的最佳方式。
带 Vaex 的显示头:
dv.head()
Vaex 显示头部需要 1189 秒。我不知道为什么显示每列的前 5 行要花这么长时间。
用 Vaex 计算第 10 个分位数:
请注意,Vaex 具有 percentile_approx 函数,用于计算分位数的近似值。
quantile = dv.percentile_approx('col1', 10)
Vaex 需要 0 秒来计算 col1 列的第 10 个分位数的近似值。
用 Vaex 添加新列:
dv[‘col1_binary’] = dv.col1 > dv.percentile_approx(‘col1’, 10)
Vaex 有一个虚拟列的概念,它将一个表达式存储为一个列。它不占用任何内存,并在需要时动态计算。虚拟列被视为普通列。正如预期的那样,Vaex 需要 0 秒来执行上面的命令。
用 Vaex 过滤数据:
Vaex 有一个选择的概念,我没有使用,因为 Dask 不支持选择,这会使实验不公平。下面的过滤器类似于 pandas 的过滤器,只是 Vaex 不复制数据。
dv = dv[dv.col2 > 10]
Vaex 需要 0 秒来执行上面的过滤器。
使用 Vaex 对数据进行分组和聚合:
下面的命令与 pandas 略有不同,因为它结合了分组和聚合。该命令按 col1_binary 对数据进行分组,并计算 col3 的平均值:
group_res = dv.groupby(by=dv.col1_binary, agg={'col3_mean': vaex.agg.mean('col3')})
用 Vaex 计算平均值。作者拍摄的照片
Vaex 需要 0 秒来执行上面的命令。
可视化直方图:
更大数据集的可视化是有问题的,因为用于数据分析的传统工具没有被优化来处理它们。我们试试看能不能用 Vaex 做一个 col3 的直方图。
plot = dv.plot1d(dv.col3, what='count(*)', limits=[0, 100])
用 Vaex 可视化数据。作者拍摄的照片
Vaex 显示剧情需要 0 秒,速度快得惊人。
计算所有列的总和
一次处理一列时,内存不是问题。让我们尝试用 Vaex 计算数据集中所有数字的和。
suma = np.sum(dv.sum(dv.column_names))
Vaex 需要 40 秒来计算所有列的总和。
达斯克
由 Kelly Sikkema 在 Unsplash 上拍摄的照片
现在,让我们用 Dask 重复上面的操作。Jupyter 内核在运行 Dask 命令之前被重新启动。
我们没有用 Dask 的 read_csv 函数直接读取 csv 文件,而是将 CSV 文件转换为 HDF5,以使实验公平。
import dask.dataframe as ddds = dd.read_csv('csv_files/*.csv')
ds.to_hdf('hdf5_files_dask/analysis_01_01.hdf5', key='table')
Dask 转换需要 763 秒。如果有更快的方法用 Dask 转换数据,请在评论中告诉我。我试图读取用 Vaex 转换的 HDF5 文件,但没有成功。
Dask 的最佳实践:
HDF5 是具有高性能需求的 Pandas 用户的热门选择。我们鼓励 Dask DataFrame 用户使用 Parquet 来存储和加载数据。
用 Dask 打开 HDF5 数据集:
import dask.dataframe as ddds = dd.read_csv('csv_files/*.csv')
Dask 需要 0 秒来打开 HDF5 文件。这是因为我没有显式运行 compute 命令,该命令实际上会读取文件。
带 Dask 的显示头:
ds.head()
Dask 需要 9 秒钟来输出文件的前 5 行。
用 Dask 计算第 10 个分位数:
Dask 有一个分位数函数,它计算实际分位数,而不是近似值。
quantile = ds.col1.quantile(0.1).compute()
由于 Juptyter 内核崩溃,Dask 无法计算分位数。
用 Dask: 定义一个新列
下面的函数使用 quantile 函数来定义一个新的二进制列。Dask 无法计算它,因为它使用分位数。
ds['col1_binary'] = ds.col1 > ds.col1.quantile(0.1)
用 Dask 过滤数据:
ds = ds[(ds.col2 > 10)]
上面的命令需要 0 秒来执行,因为 Dask 使用延迟执行范例。
使用 Dask 对数据进行分组和聚合:
group_res = ds.groupby('col1_binary').col3.mean().compute()
Dask 无法对数据进行分组和汇总。
可视化第 3 列的直方图:
plot = ds.col3.compute().plot.hist(bins=64, ylim=(13900, 14400))
达斯克无法将数据可视化。
计算所有列的总和:
suma = ds.sum().sum().compute()
Dask 无法将所有数据相加。
结果
下表显示了 Vaex 与 Dask 实验的执行时间。NA 表示工具无法处理数据,Jupyter 内核崩溃。
实验中执行时间的摘要。作者拍摄的照片
结论
Joshua Golde 在 Unsplash 拍摄的照片
Vaex 需要将 CSV 转换为 HDF5 格式,这并不影响我,因为你可以去吃午饭,回来后数据就会被转换。我也明白,在恶劣的条件下(像在实验中),用很少或没有主内存读取数据将需要更长的时间。
我不明白的是 Vaex 显示文件头需要的时间(前 5 行 1189 秒!).Vaex 中的其他操作经过了大量优化,这使我们能够对比主存数据集更大的数据集进行交互式数据分析。
我有点预料到 Dask 的问题,因为它更适合计算集群,而不是单台机器。Dask 是建立在熊猫的基础上的,这意味着熊猫慢的操作在 Dask 也会慢。
实验的赢家很明显。Vaex 能够处理大于笔记本电脑主内存的文件,而 Dask 却不能。这个实验很特别,因为我是在单台机器上测试性能,而不是在计算集群上。
在你走之前
在 Twitter 上关注我,在那里我定期发布关于数据科学和机器学习的。
开始数据科学之旅是否太迟了?
我以为我是。
安迪·比厄斯在 Unsplash 上的照片
自从我第一次涉足数据科学以来,已经过去了将近三年。刚开始的时候,我以为来不及了。有许多工具和概念需要学习,大量的论文需要阅读,无数的证书等等。
我最担心的是太晚了,而不是我需要学习的材料的数量。当我学到足够的知识时,我宁愿怀疑我是否能找到工作。数据科学是一个非常热门的话题,已经有相当多的人在这个领域工作。
在过去的三年里,我不仅学习了数据科学,还观察了这个领域的动态。我对太迟的想法改变了。我那时开始还不算太晚。而且,如果我今天开始学数据科学,也不会太晚。
在这篇文章中,我将详细阐述是什么改变了我的想法。如果你不这么想,我希望能改变你的想法。
当我有疑问的时候,我试着简单的思考,这有助于我看清事物。否则,我会被所有的条件、后果和因素分散注意力。
我关于为时已晚的简单而令人信服的想法是基于一个基本概念:供需曲线。在我们的案例中,供应是由拥有在数据科学生态系统中工作所需技能的个人组成的劳动力。工作的数量构成了对这些个人的需求。
数据科学有不同的头衔或专业。数据科学家、数据工程师、机器学习工程师可以名列前茅。如果我们再深入一点,我们会看到头衔缩小到更具体的技能,如自然语言处理(NLP)工程师。
需求在增加
现在的问题变成了数据科学领域是否供过于求。如果数据科学的范围有限,这个问题可能会成为一个严重的问题。我认为这个范围远远超出了我们目前所发现的。更准确地说,将会有更多的企业、行业或流程应用数据科学技术。
在过去的三年里,我观察到数据科学领域的工作数量一直在增加。由于数据科学的范围可能会变得更大,工作的数量也将不断增加。
积极使用数据科学的行业可能会扩展数据的使用,因为他们非常清楚数据科学在其业务中的潜在优势。
例如,零售业正在使用数据科学技术来提高利润率和增加收入。数据科学为客户行为、购买模式、需求预测、产品替代等提供了有价值的见解。
在过去的两年里,由于全球性的疫情影响了人们购物的方式,我们经历了不寻常的时期。网上购物急剧增加。因此,零售商需要一种方法来同时分析在线和离线购物数据。因此,他们致力于利用全渠道数据的新技术,这意味着零售业将有更多的数据科学家职位。
我不知道你是否听说过人类基因组计划,但它对人类至关重要。这是一个研究项目,旨在对所有基因进行测序和绘图。基因组一词用于所有基因的组合。
根据基因组数据科学的概况介绍,预计基因组学研究将在未来十年内产生 2 到 40 艾字节 的数据。这是大量的数据。这些数据的潜在价值是巨大的。我们需要有技能的人来挖掘这种潜在价值。
生物信息学是一个主要专注于分析基因组数据以发现有价值的洞察力的领域。这个领域甚至出现了新的创业公司。如果你拥有数据科学生态系统所需的技能,你获得生物信息学工作的机会相当高。
我想说的是,数据科学在未来可能会无处不在。有些人可能会说已经是了。但是,我相信,数据科学创造价值并被证明有用的领域会更多。随着新领域或新技术的发现,数据科学生态系统对熟练人员的需求将会增加。
但是为什么很难找到工作呢?
对于那些想转行从事数据科学工作的人来说,最大的挑战是找到第一份工作。我面临同样的挑战,花了大约两年时间才找到第一份工作。
这个问题与你是否太晚开始学习数据科学无关。工作就在那里,而且还在增加。然而,如果没有工作经验,很难展示你的技能并说服雇主或招聘人员。
如何展示你的数据科学知识是另一篇文章的主题,但我认为最有效的方法是做项目,在社区中活跃,并拥有一个体面的 Github 帐户。
另一个需要记住的非常重要的事情是,最好专注于一项特定的技能,而不是学习更一般意义上的数据科学。这将增加你的投资组合脱颖而出的机会。
最后的想法
我想我已经清楚地表达了我对题目中问题的答案,但让我再说一次。你学习数据科学并不晚。
学数据科学,找到第一份工作,不是一件容易的事。这需要时间、努力和奉献。你可能需要花几个月的时间来获得基本技能。但是,一旦到了那里,就不会缺少工作机会。
披露:下面的链接是我一次性付款的代销商。
想自学数据科学?发现免费学习资源,添加学习笔记,并加入学习圈进行协作学习。从这里开始。
最后但同样重要的是,如果你还不是的灵媒会员,并打算成为一名灵媒会员,我恳请你使用下面的链接。我将从你的会员费中收取一部分,不增加你的额外费用。
https://sonery.medium.com/membership
感谢您的阅读。如果您有任何反馈,请告诉我。
你是在不知不觉中帮助训练谷歌的人工智能模型吗?
谷歌如何使用你的 reCAPTCHA 条目来训练机器学习模型
拉杰什瓦尔·巴楚在 Unsplash 上的照片
谷歌的 reCAPTCHA 服务被宣传为一种保护网站免受机器人攻击的手段。如果系统怀疑一个机器人试图访问一个网站,它会进行一些测试,只有人类才能通过。如果你花足够的时间在互联网上,你会看到这种服务的版本。一个图像面板出现了,你必须选择所有包含消防栓、汽车或桥梁的图像。我们以前都遇到过这个系统。如果你以前在试图访问你最喜欢的网站时与这个系统进行过交互,那么恭喜你,你通过为一些谷歌机器学习模型标记一些数据,为它们做出了贡献。在谷歌的 reCAPTCHA 网页的内部,这是该公司关于从该系统捕获的数据的使用的说法:
reCAPTCHA 还通过使用解决方案来数字化文本、注释图像和建立机器学习数据集,积极利用人类在解决验证码方面的努力。这反过来有助于保存书籍,改进地图,解决人工智能的难题。
让我们看看谷歌是如何做到这一点的,推测我们正在帮助改进的模型,以及我对这个系统的看法,在这个系统中,人们不知不觉地训练了一些 Alphabet Inc 人工智能模型。
监督机器学习的快速概述
安迪·凯利在 Unsplash 上拍摄的照片
简而言之,受监督的机器学习模型试图根据模式学习或表征不同类别的特征对数据进行分类。为了做到这一点,一个受监督的机器学习模型被提供了大量带标签的数据,称为训练数据。带标签的数据是带有标识类的标签的数据。受监督的 ML 算法将学习与类相关联的特征,因此它可以对新数据进行分类。
因此,为了训练 ML 模型来对例如火车、飞机或船只的图像进行分类,成千上万的物品的标记图像被输入到算法中,在算法中,像尺寸、颜色、形状等特征被用来区分类别。训练后,人们可以传入船只、火车和飞机的新的、未标记的图像,ML 模型将基于来自训练数据集的学习对它们进行分类。
谷歌是如何从 reCAPTCHA 收集数据的?
如前所述,如果 reCAPTCHA 服务怀疑一个机器人试图与一个网站进行交互,它将进行测试以确认你是人类。有时它是一个简单的复选框。其他时候,更有趣的挑战是从一组图像中选择符合特定描述的图像。一旦您正确识别出符合描述的图片,您就可以访问您想要访问的页面。因此,你在这些挑战中所做的是提供一些带标签的数据,这些数据将用于 Alphabet Inc 旗下一些人工智能的训练数据集。
显而易见的问题是,谷歌如何知道一个网络用户何时选择了所有符合描述的图片?如果谷歌的好处是美国用户为人工智能模型标记一些数据,那么毫无疑问,他们事先并不知道这些图像包含什么。答案是,当谷歌向你展示一组图片时,比如说,六张图片,其中五张已经被标注了。网络用户被要求正确识别五张图片,包括,也就是谷歌想要标注的那张。你只需要正确识别谷歌已经标记的四张图片,你对第五张未知图片的答案就会进入人工智能训练数据集。
这些数据的用途是什么?
至于这些数据被用来训练什么人工智能,这基本上是不可知的,除非你在公司内部。但是我们可以根据我们被要求识别的图像类型做出一些有根据的猜测。reCAPTCHA 挑战似乎与道路、交通信号或汽车有关。这可能是一个线索,这些数据将用于训练 Alphabet Inc .的自动驾驶汽车公司 Waymo 使用的某种模型。谷歌在他们的网页上提到,这些数据可以用来帮助改进地图,根据我们看到的图像,这也是有道理的。再说一次,如果不在 Alphabet Inc .内部,很难知道所有数据最终去了哪里。
最后的想法
我认为大多数人会觉得谷歌使用我们提供的数据的方式有一种欺骗或不诚实的感觉,这是一种商业行为,没有适当地通知用户正在发生什么。事情是这样的,如果谷歌明确表示 reCAPTCHA 的一些答案将在未来用于训练谷歌模型,我不相信大多数人会感到不安。我确实认为让人们了解正在发生的事情并给出选择退出的选项是很重要的。
还值得注意的是,这一系统只存在于 V2 的 reCAPTCHA。谷歌现在有一个 reCAPTCHA V3,它完全不会打断用户来检测机器人。相反,reCAPTCHA V3 根据一系列指标对网站的所有访问者进行评分,分数越低,你越有可能是一个机器人。然而,reCAPTCHA V2 仍然活跃在一些网站上。最后,我要说,应该鼓励科技公司提高透明度。我只能假设缺乏透明度的原因是因为担心用户会选择不遵守,但这应该是我们用户做出的决定。
你的人工智能工作是可复制的吗?
记录更多关于整个系统的信息可以提高重现性,尤其是性能指标评测结果
马特·布里内在 Unsplash 上拍摄的照片
除了框架+数据集,我鼓励人们在记录培训工作时考虑他们更广泛的系统。
这在 AI 性能基准测试期间尤其重要。最近有人问我,“在 MLPerf 测试中,我每秒得到 100,000 张图像。那快吗?”我不知道。你用的是什么精度?多大批量?硬件?数据集存储在哪里?
从基础设施、软件到运行时可调参数,许多因素都会影响训练性能。如果别人不能记下你的笔记并重现你的测试结果,你就没有解决可重复性的问题。
关注再现性的几个原因:
1。严格的文档实践有助于产品开发(在法律上可能是必要的)。
通常,数据科学家必须在提高准确性和增加训练时间之间做出权衡。检查之前训练跑步设置的矩阵有助于找到更快“达到准确性的时间”的途径
2。它有助于您确保获得预期的绩效。
数据科学家应该能够将他们的吞吐量结果与社区提供的结果进行比较。它帮助您检查
-您的脚本是否充分利用了您系统的性能
-您的基础设施没有被错误地配置
3。它让硬件/软件开发团队评估他们的下一代产品。
这种最新的加速器模型是否足以作为一种改进来运输呢?这个最新的框架代码版本有性能回归吗?
4。它帮助开发者和购买者在平台之间进行决策。
只有通过相互比较,团队才能对更改单个组件将会产生的性能影响有一个合理的想法。
我已经在记录 TensorFlow 版本了。
这还不够。
框架版本很重要,但它不是全部。大图有两部分:
-运行级参数
-系统
如果您知道运行级参数,您应该能够走向系统并重现性能结果。这意味着“系统”包括所有端到端硬件(及其操作系统、固件和软件)以及所有设置和库版本(包括容器堆栈)。
我见过的几个随机事件的例子显著地影响训练性能:
-Docker 允许使用的内存量
-加速器上的 MTU 设置-以及架顶式交换机上的 MTU 设置!
——我从未听说过的低级 PCIe 设定
所以 100 件事会影响表现。我需要记录的最低限度是什么?
以下是 6 个最重要的项目。记录这些以使您的结果更具重现性:
- 加速器模型
- 加速器驱动程序版本
- 批量
- 精确
- 完整的库版本列表,或者容器映像 ID,如果您使用的是来自 NGC 或云供应商的预构建映像。
- 数据集,包括数据集格式(例如 JPEG v. TFRecord)
这些是影响性能的主要设置,其中大多数可能会随着时间的推移而改变。
摘要
如果你测试你的 DL 训练表现,你应该把它想成一个及时的快照。系统是如何配置的,您为运行选择了什么设置?
如果我们开发了更好的实践来使结果具有可重复性,我们将更好地了解随着堆栈的每一部分的发展性能的提高,并且我们将通过突出瓶颈来推动创新。
让我们让“图像/秒”更有意义!
您的微服务是否以应有的方式运行?耶格可以帮忙!
对复杂分布式系统中的服务进行监控和故障排除,比如 Kubernetes。
阿巴斯·特拉尼在 Unsplash 上拍摄的照片
假设您部署了一个应用程序,报告以美元为单位的加密货币的当前价格。如果你想形象化它,想想类似于的东西。
现在,让我们假设 CoinMarketCap 在 3 秒内做出响应。这意味着,如果用户导航到他们的页面,他们可以很快获得他们感兴趣的硬币的价格。另一方面,您的应用程序至少需要 8 秒钟来提供答案。我敢打赌,一两周之后你就不会有很多用户了!
你需要迅速找出问题所在。怎么花了这么长时间?哪项服务表现不佳?您知道您必须对代码进行一些更改,但是您如何知道从哪里开始呢?在云原生世界中,像获取比特币的美元价格这样简单的事情可能涉及许多微服务。
这个故事介绍了 Jaeger ,这是一个开源的端到端分布式跟踪工具,可以让您观察应用程序内部的情况。
学习率是为那些对 AI 和 MLOps 的世界感到好奇的人准备的时事通讯。你会在每周五收到我关于最新人工智能新闻和文章的更新和想法。在这里订阅!
监控与可观察性
当错误发生时,开发人员需要正确的反馈,这样他们就知道该修复什么。我们有“日志记录”,但是日志记录告诉你发生了什么,但是如果你的代码运行到完成,它不能告诉你为什么它表现不好。
同样,监控不会告诉我们应用程序内部发生了什么。它会给出诸如内存可用性低之类的指标,但它不会帮助我们查明问题的原因。
然后,我们有可观测性。在可观察性中,我们可以直接进入我们的应用程序,观察它的各种过程和功能。我们可以看到服务如何执行以及它们如何交互。然后,我们可以揭示几个问题或发现优化,使我们的应用程序性能更好。
根据经验,请记住:我们监控系统并观察应用程序。你还会听到人们使用术语可观察性来对这两个概念进行分组(即监控和可观察性)。如果您想了解更多关于监控和可观察性的信息,请查看以下内容:
贼鸥
Jaeger 是领先的开源端到端分布式追踪工具。什么是分布式跟踪?简而言之,就是在微服务的世界里寻迹。它允许您在被监控的应用程序中跟踪请求或事务的过程。
优步在 2015 年创建了 jaeger,并将其捐赠给了云原生计算基金会(CNCF)。Jaeger 收集应用程序信息,而领先的监控工具 Prometheus 收集系统信息。
此外,与“日志记录”相反,Jaeger 告诉你一个函数是如何执行的,而不是它是如何失败的。它通过各种服务跟踪请求,这使得它非常适合测量延迟问题。
耶格使用轨迹和跨度来实现这一切。所以,一些术语:
- 一个轨迹是一个通过系统的数据/执行路径,可以被认为是一个有向无环的跨度图。
- 一个跨度表示 Jaeger 中的一个逻辑工作单元,它有一个操作名、操作的开始时间和持续时间。跨度可以被嵌套和排序以模拟因果关系。
走线与跨度
装置
为了安装 Jaeger 服务器,我们依赖 Docker 提供的便利。因此,我们可以只运行以下命令:
docker run -d -p5775:5775/udp -p6831:6831/udp -p6832:6832/udp -p5778:5778 -p16686:16686 -p14268:14268 -p9411:9411 jaegertracing/all-in-one:0.8.0
现在您可以启动您最喜欢的浏览器,Jaeger UI 将在[localhost:16686](http://localhost:16686)
等待您:
Jaeger UI——作者图片
这是我们将发送跟踪的服务器。接下来,我们需要安装必要的 Python 包,以便能够发送这些跟踪:
pip install jaeger-client
简单的例子
正如我们所说,我们希望构建一个应用程序,返回前 100 种加密货币的价格。因此,首先,我们需要安装 Python requests
库,这样我们就可以发出 HTTP 请求:
pip install requests
下一步是初始化一个tracer
对象:
我们可以使用这个 tracer 对象来生成我们的跨度。我们需要两个跨度:
- 第一个将是父跨度。它将跟踪整个过程需要多长时间才能完成。
- 第二个是跟踪检索每种加密货币的信息所需的时间。
上面的代码首先获取父 span 中所有可用的 crypto,然后为每个 crypto 发出一个新的请求来获取它的信息并打印它的价格。当然,我们可以在没有第二个请求的情况下这样做,因为我们在第一个调用中获得了我们需要的所有信息,但这是一个用于演示目的的示例。
如果我们现在导航到 Jaeger UI,刷新页面,从Service
菜单中选择crypto-service
并按下Find Traces
按钮,我们将得到这个视图:
耶格痕迹——作者图片
通过深入研究它收集的 101 个跨度(1 个父节点和 100 个顶级加密节点),我们可以看到每个请求的响应时间:
耶格潜伏——作者图片
现在,我们又多了一条关于我们需要做得更好的线索!
结论
这个故事介绍了 Jaeger,领先的开源端到端分布式追踪工具。
使用 Jaeger,我们可以直接进入我们的应用程序,观察它的各种过程和功能。我们可以看到服务如何执行以及它们如何交互。然后,我们可以揭示几个问题或发现优化,使我们的应用程序性能更好。
在后面的文章中,我们将看到如何用 Grafana 构建仪表板,向用户公开这些信息,并集成来自 Prometheus 的监控信息。
关于作者
我的名字是迪米特里斯·波罗普洛斯,我是一名为阿里克托工作的机器学习工程师。我曾为欧洲委员会、欧盟统计局、国际货币基金组织、欧洲央行、经合组织和宜家等主要客户设计和实施过人工智能和软件解决方案。
如果你有兴趣阅读更多关于机器学习、深度学习、数据科学和数据操作的帖子,请关注我的 Medium 、 LinkedIn 或 Twitter 上的 @james2pl 。
所表达的观点仅代表我个人,并不代表我的雇主的观点或意见。
你的播放列表够酷吗?
让我们和朱莉娅一起使用 Spotify 的公共 API 来找出答案
由 Unsplash 上的 Blaz Photo 拍摄
最近,我看到一些优秀的文章,作者利用 Spotify 的 API 来分析他们的音乐数据。这让我非常好奇,我决定尝试使用 Julia 做类似的事情。谢天谢地,API 的包装器已经在 Spotify.jl 中实现了,但是这个包还没有注册。这当然不是问题,我们总是可以直接从它的 GitHub 库安装它。
在本文中,我将演示如何使用 Pluto 笔记本检索和可视化您的音乐数据。关于如何为你的系统设置 Pluto 的说明可以在我之前的文章中找到。设置完成后,打开一个新的笔记本或直接使用中的现有笔记本。可以使用提供的实例化包环境。toml 文件。这对保持结果的再现性是有用的(但不是必须的)。
using Pkg
Pkg.activate(pwd())
Pkg.instantiate
要在您的工作环境中导入所有相关的包,请执行以下命令:
using Spotify, DataFrames, VegaLite, Setfield, JSON, Statistics, Query
获取 API 访问的凭据
为了使用 API,您首先需要获得适当的凭证。这可以通过创建一个 Spotify 开发者账户来实现。打开您的仪表板,通过填写名称和用途来创建应用程序。然后点击“显示客户机密”查看您的密钥。将这些凭证复制到您的Spotify _ credentials . ini文件中,该文件将在您首次尝试使用该包时自动创建。凭证仅在 1 小时内有效。因此,一旦过期,您需要通过执行以下命令进行刷新:
Spotify.refresh_spotify_credentials()
终端输出应显示到期时间,例如:
[ Info: Expires at 2021–10–30T19:37:32.556
测试您的证书
例如,您可以通过获取关于某个相册的信息来测试 API 是否正常工作。所有对象,包括专辑,曲目,艺术家等。拥有唯一的 Spotify ID。我们使用 album_get 函数以及相册 ID 和位置(=“US”)参数,如下所示:
获取有关相册的信息
请求个人数据
您可以通过以下方式向 Spotify 请求一份您的个人数据:
- 在桌面浏览器上打开 Spotify 进入个人资料→账户
- 从左窗格打开“隐私设置”
- 向下滚动到“下载您的数据”部分,并继续执行步骤 1
一旦您的数据可供下载,您将收到一封电子邮件。对我来说,它看起来有 3 天了。你会收到一个 JSON 文件的集合,我们主要对流数据感兴趣,它显示了我们花了多少时间听不同的曲目。
检索我的播放列表的音频功能
我首先想看的是一些我最喜欢的播放列表的音频特性。我从 Spotify 桌面播放器中收集播放列表 ID,方法是打开播放列表并在 URL 的末尾复制 ID(字母数字字符)。我将它们放在一个数组(类型字符串)中,如下所示:
播放列表 id
每个曲目都有音频特性,这意味着我们首先需要从每个播放列表中获取曲目 id。这是通过使用函数 **get_playlist_tracks,**来完成的,其中我们发出一个 HTTP GET 请求来获取这些信息。
这个功能还不是 Spotify.jl 的一部分(很快会做一个 PR)。返回的最大曲目数量为 50 首。你可以想象,在一个给定的播放列表中可以有超过 50 首曲目。因此,我们需要用新的偏移量(返回的第一项的索引,默认为 0)重复调用这个函数。最后,我们删除重复的曲目(相同的曲目可以出现在许多播放列表中),并返回曲目 id 数组。
曲目 id 列表
接下来,我们要获取每个音轨 ID 的音频特征。我们将使用 Spotify.jl 的tracks _ get _ audio _ features功能,并将它们收集在一个数据帧中。关于音频功能的细节可以在这里找到。
得到的数据帧 df_audio 应该是这样的:
具有音频特征的数据帧
请注意,我们没有针对所有曲目的功能。要么它们不可用,要么我们可能遇到 API 调用限制。无论如何,我们有足够的数据来做一些很好的可视化。
绘制音频特征
我们可以创建一个通用的绘图函数,它将一个列名作为参数,以便从 df_audio 创建一个直方图。
现在,让我们来看看我选择的播放列表中曲目的各种音频特征的分布。
各种音频特征的分布
- 第一个图显示了“可跳舞性”的分布。从 Spotify 的文档,
可跳舞性描述了基于音乐元素(包括速度、节奏稳定性、节拍强度和整体规律性)的组合,一首曲目适合跳舞的程度。值 0.0 最不适合跳舞,1.0 最适合跳舞。
似乎我的大多数曲目都有很高的可跳性(> 0.5)。
- 第二个图显示了音频“能量”。Spotify 表示…
能量是一种从 0.0 到 1.0 的度量,代表强度和活动的感知度量。通常,高能轨道感觉起来很快,很响,很嘈杂。对该属性有贡献的感知特征包括动态范围、感知响度、音色、开始速率和一般熵。
因为我喜欢听大部分由乐器组成的放松播放列表,所以更多的音轨能量值低于 0.5 是有道理的。
- 第三个图显示了音频“语音”。根据文档,
语音检测音轨中是否存在语音单词。越是类似语音的录音(例如脱口秀、有声读物、诗歌),属性值就越接近 1.0。高于 0.66 的值描述可能完全由口语单词组成的轨道。介于 0.33 和 0.66 之间的值描述可能包含音乐和语音的轨道,可以是分段的,也可以是分层的,包括说唱音乐。低于 0.33 的值很可能代表音乐和其他非语音类轨道。
我的大多数曲目都有一个值< 0.2, which is consistent with the fact that I prefer instrumental tracks.
- Last plot shows the audio “valence”.
It’s a measure from 0.0 to 1.0 describing the musical positiveness conveyed by a track. Tracks with high valence sound more positive (e.g. happy, cheerful, euphoric), while tracks with low valence sound more negative (e.g. sad, depressed, angry).
Most of my tracks seem to have a valence value less than 0.5. Does that mean that I am into sad/angry music? I wouldn’t like to think so, many of my relaxing playlists actually help to improve my mood. How does your valence distribution look like? Do let me know in the comments.
Correlation between different audio features
I thought it would be interesting to also look at how correlated audio features are w.r.t. one another. To help quantify that, we can also compute the 皮尔逊相关系数,值越接近 1 表示相关性越强。
下面是各种图的样子:
音频特征之间的相关图
可跳舞性显示出与能量的某种相关性,这是可以预料的,因为能量越大的曲目通常也非常适合跳舞。可跳性高的曲目也往往更欢快、更快乐。因此,与化合价的强相关性可以得到解释。类似地,具有更高能量的轨道也可能显示出与更高价态更强的相关性,这就是我们所看到的。
跟踪持续时间和受欢迎程度数据
对于每首曲目,我们可以使用 tracks_get 函数获得额外的信息,比如流行度和持续时间。
绘制从上述函数返回的数据帧( df_track )与前面的例子类似。
根据上面的图表,我认为我喜欢的歌曲的最佳时长是 3-4 分钟左右。关于受欢迎程度的数字(0-100 分,100 分最受欢迎),似乎许多艺术家都有相当高的受欢迎程度(> 50)。
音乐流派
从 tracks_get 函数返回的 dict 也包含艺术家的 ID。我们可以收集所有的艺术家 id,然后获取艺术家数据,比如与他们相关的音乐流派。相关代码可以在冥王星笔记本上找到。似乎“阿富汗流行”、“艺术流行”和“氛围”是我一直在听的一些比较常见的流派。
绘制个人流数据
我们不应该忘记可以从 Spotify 请求的个人数据(JSON 文件)。档案**<>。json** 可以转换成 DataFrame ( df_stream )并排序 w.r.t .分钟播放。
我们现在可以看看前 10 个轨道,它们实际上是 df_stream 的前 10 行。
如果能得到顶级艺术家的名单会很有趣。这是一些额外的工作,因为我们现在必须组合来自相同艺术家但不同曲目的流时间。因此,对于每一位艺术家,我们过滤 df_stream 并合计相关曲目的总时长。
休伦大人是我最喜欢的歌曲之一,很明显,在过去的 1-2 年里,我经常听他们的歌。普拉蒂克·库哈德是另一颗宝石,当然,A.R .拉赫曼无需介绍。“当地火车”有一些很棒的歌曲,已经成为印度最好的乐队之一。
结论
对于 Spotify 用户来说,可以从他们的音乐流媒体数据中学到很多东西。我发现这个 API 有很好的文档记录,非常有用。还有一些其他有趣的端点,但是我将把它们留给另一个讨论。我希望你喜欢阅读这篇文章。感谢您的宝贵时间!完整代码(冥王星笔记本)可在这里获得。如果你想联系,这是我的 LinkedIn。
参考
- https://developer . Spotify . com/documentation/we b-API/quick-start/
- https://github.com/kwehmeyer/Spotify.jl
- https://towards data science . com/visualizing-Spotify-data-with-python-tableau-687 F2 f 528 cdd
你的正则表达式操作需要时间吗?如何让它们更快
FlashText——NLP 任务正则表达式的更好替代
图片由来自 Pixabay 的 Michal Jarmoluk 拍摄
自然语言处理(NLP)是人工智能的一个子领域,涉及计算机和自然人类语言之间的交互。NLP 涉及文本处理、文本分析、将机器学习算法应用于文本和语音等等。
文本处理是 NLP 或基于文本的数据科学项目中的关键元素。正则表达式有多种用途,如特征提取、字符串替换和其他字符串操作。正则表达式也称为 regex,它是许多编程语言和许多 python 库都可以使用的工具。
Regex 基本上是一组字符或模式,用于对给定的字符串进行子串化,可以进一步用于搜索、提取、替换或其他字符串操作。
FlashText:
FlashText 是一个开源的 python 库,可以用来替换或提取文本中的关键词。对于 NLP 项目,无论是否需要进行单词替换和提取,我们都会遇到一些文本处理任务,FlashText 库使开发人员能够有效地执行关键字的提取和替换。
安装:
可以使用 PyPl 安装 FlashText 库:
**pip install flashtext**
用法:
FlashText 库的使用是有限的,它被限制在提取关键字,替换关键字,获取关于提取的关键字的额外信息,删除关键字。在下面的示例笔记本中,您可以找到计算和比较 FlashText 和 RE 之间的基准数的代码片段,用于从取自维基百科的文本中提取和替换关键字。
(作者代码)
使用 re 和 FlashText 库对取自机器学习的维基百科页面的文本文档(约 500 个单词)进行关键词提取和替换。
(图片由作者提供),RE 和 FlashText 库之间的基准时间约束
您可以观察两个库之间的基准时间数,这是为两个任务执行的:关键字提取和替换。这些任务是针对大约 500 个单词的一小段文本执行的。时间数量的差异非常小,因此性能是不可区分的。
下图表示对具有 10,000 个标记的文本文档进行 1000 个关键词替换操作的次数。可以看出,FlashText 操作比 Regex 快 28 倍。
( Souce ),左: replace() 函数的时间约束,**右:**search()函数的时间约束,介于 Regex 和 FlashText 之间
为什么不是 Regex,为什么是 FlashText?
对于小尺寸文档的正则表达式操作,时间数非常接近,并且两个库的性能没有区别。对于大尺寸文档,FlashText 库的性能明显超过,其中 FlashText 库的 1000 个关键字的替换操作比 Regex 快 28 倍。
根据 FlashText 文档:
Regex 基于自定义算法,如Aho-coraseck 算法和 Trie 字典。FlashText 库可以在一个文档中搜索或替换关键字,并且时间复杂度不依赖于要搜索或替换的术语的数量。FlashText 库被设计为只匹配完整的单词,不像 Regex 那样也匹配子字符串。
For a document of size ***N tokens*** and a dictionary of ***M keywords***,
Time Complexity of ***FlashText*** is **O(N)**.
Time Complexity of ***Regex*** is **O(MxN)**.
因此,FlashText 算法比 Regex 快得多。
点击此处阅读 FlashText 算法的整篇文章。
结论:
对于小文档,两个库的性能比较取决于很多因素,比如缓存。FlashText 库在处理具有大量标记的文本文档方面明显优于其他库。FlashText 的时间复杂度为 O(N ),而 Regex 的时间复杂度为 O(M*N ),因此建议对超过 500 个标记的文本文档使用 Regex。
点击此处获取 FlashText 的 GitHub 库
参考资料:
[1]大规模替换或检索文档中的关键词,(2017 年 11 月 9 日):https://arxiv.org/abs/1711.00046
[2] FlashText 文档,(2018 年 2 月 16 日):https://pypi.org/project/flashtext/
感谢您的阅读
零镜头文本分类变压器模型是更好的聊天机器人的关键吗?
为您的下一个聊天机器人项目使用零镜头文本分类转换器模型克服对训练数据的需求
作者图片
对于许多自然语言处理(NLP)项目来说,最麻烦的任务之一是收集和标记训练数据。但是,对于聊天机器人的意图分类,有一个潜在的解决方案,那就是使用零镜头文本分类转换器模型。如果成功,这种方法将降低开发聊天机器人所需的复杂性,同时潜在地提高它们的性能。我鼓励你扩展这些想法,并可能将它们集成到你的聊天机器人系统中。
意图分类是聊天机器人执行的基本任务。意图分类是确定用户希望执行哪个动作的动作。例如,假设你问一个聊天机器人,“请播放 U2 的最新歌曲”,那么机器人必须确定用户希望“播放一首歌曲”。从那里,该模型将典型地使用实体识别来确定播放哪首歌曲。本文提出了一种在没有任何训练数据的情况下利用新技术执行意图分类的可能方法。
零镜头文本分类
零镜头文本分类转换器模型于 2019 年在论文“基准零镜头文本分类:数据集、评估和蕴涵方法”[1]中提出。模型的输入被表述为每个标签的蕴涵问题。因此,给定文本“我想买一个苹果”和标签“食物”,前提将是文本,假设将类似于“这篇文本是关于食物的。”然后,模型确定假设是否需要前提。有了这项技术,NLP 实践者可以在单个文本蕴涵数据集上训练他们的模型,然后使用该模型对任意标签执行文本分类。
作者图片
我们将使用在拥抱脸的模型分发网络上下载量第二多的零镜头文本分类模型,它是由脸书·艾创建的。它是在麻省理工学院许可下发布的,这是一个许可许可你可以在这里阅读更多关于的内容。
体系结构
本文提出了一个基于树的意图分类系统,该系统利用零镜头文本分类模型。有些意图可能会被归类在一起,因此,我认为最好先确定意图的类别,然后再确定要执行的具体行动。例如,一个类别可以是“播放音乐”,然后该类别的意图可以是“播放艺术家”或“播放专辑”
在不应用零射击模型的情况下,使用类似的基于树的系统可能是遥不可及的,原因有两个。首先,您可能需要一个模型来对类别进行分类,然后为每个类别建立一个模型。所以,如果你在处理一个有很多可能动作的系统,这可能会有很多模型。然后,由于型号和类别数量的增加,需要更多的培训。因此,通过使用这个提出的系统,对于这个整个基于树的分类系统,只需要一个单一模型。
作者图片
模型创建
目前,拥抱脸的变形金刚库是实现零镜头文本分类变形金刚模型的首选方式。因此,我们可以用下面一行代码从 PyPI 下载这个包。
pip install transformers
拥抱脸创建了一个名为“管道”的功能,它抽象了使用变压器模型进行推理通常涉及的复杂性。还是导入吧。
from transformers import pipeline
我们需要向管道类提供我们希望执行的任务和模型名称。然后,它将输出一个我们可以用来开始分类文本的对象。任务 id 为“零镜头分类”,模型名称为“ facebook/bart-large-mnli ”。
task = "zero-shot-classification"
model = "facebook/bart-large-mnli" classifier = pipeline(task, model)
主要意图分类
让我们为模型定义一个任意的标签列表来对文本进行分类。对于这个例子,我将为虚拟电话助理将要执行的常见任务添加标签。
primary_labels = ["take note", "play music", "search internet ", "send email", "create reminder"]
我们现在可以将文本归入这些标签之一。首先,让我们定义与这些标签之一相关的文本。我们将使用一个属于“播放音乐”类别的例子。
input_text = "Put on Drake's new album"
为了生成预测,我们可以向我们的分类器提供文本和标签,如下所示。
classifier_output = classifier(input_text, primary_labels)
我们现在可以打印结果了。
print(classifier_output["labels"]) print(classifier_output["scores"])
结果:
[‘播放音乐’,‘做笔记’,‘创建提醒’,‘搜索互联网’,‘发送电子邮件’][0.8064008355140686,0.1329479217529297,0.04738568142056465,0.010709572583436966,0.00255559617206454277]
标签和分数都按最高分排序。因此,在这里我们看到“播放音乐”是最高的结果,得分为 80.64%,这是有道理的。
次要意图分类
对于每个主要标签,我们必须创建一个次要标签列表,以进一步细化用户的意图。继续上面的例子,因为主要标签是“播放音乐”,下面是潜在动作的列表。
secondary_labels = ["play artist", "play song", "play album", "play popular", "play new", "play old"]
如前所述,我们可以使用零镜头文本分类模型来确定预期的动作。我们将向模型提供原始文本和二级标签。
secondary_classifier_output = classifier(input_text, secondary_labels) print(secondary_classifier_output["labels"]) print(secondary_classifier_output["scores"])
[‘播放专辑’,‘播放艺人’,‘播放新’,‘播放流行’,‘播放歌曲’,‘播放旧’]
0.2994290888309479,0.23053139448165894,0.209768395962143,0.136963962316513,0.1226228971113205
我们走吧!我们只是决定执行哪些操作。我们看到“播放艺术家”、“播放专辑”和“播放新闻”得分都很高。因此,现在我们可以使用实体识别来检测文本中的艺术家,然后查询该艺术家的最新专辑。
结论
在这篇文章中,我提出了一种利用零触发文本分类模型来消除意图识别对训练数据的需求的方法。我还讨论了如何使用这种方法来创建一个树状搜索算法,以细化用户希望执行的确切操作。我希望这篇文章能启发你创建更强大的聊天机器人!
如果您成功地为一个项目实施了这一建议方法,请发送电子邮件至 eric@vennify.ca。我很想听听。
参考
[1] W. Yin,J. Hay,D. Roth,基准测试零镜头文本分类:数据集、评估和蕴涵方法 (2019),EMNLP 2019
[2] A.Williams,N. Nangia,S. Bowman,通过推理进行句子理解的广泛覆盖挑战语料库 (2018),计算语言学协会
资源:
类似文章
如果你喜欢这篇文章,那么你可能也会喜欢下面这篇文章,它涵盖了我的一个原创研究项目,涉及零镜头文本分类转换器模型。
题目:如何标注文本分类训练数据—用 AI 总结:用一个零镜头的文本分类模型来标注训练数据。然后,使用带标签的训练数据来微调更容易在生产中使用的小型监督模型。
原载于 2021 年 9 月 8 日https://www . vennify . ai。
带 Plotly Express 的面积图
痕迹&布局
图片由来自 Unsplash 的 Pawel Czerwinski 提供
Plotly 图形对象
面向对象的人物创作界面 Plotly Express 于 2019 年发布。它是 Plotly.py 的高级包装器,包括绘制标准 2D & 3D 图表和 choropleth 地图的函数。与 Plotly 生态系统的其余部分完全兼容,是快速开发探索性图表的优秀工具。
但是如果您想增强您的绘图,您需要导入一组名为 graph objects 的类。
import plotly.graph_objects as gofig = go.Figure()
plotly.graph_objects 模块包含 Python 类的层次结构。图是初级类。图有一个数据属性和一个布局属性。数据属性有 40 多个对象,每个对象引用一个特定类型的图表( trace) 及其相应的参数。布局属性指定图形的整体属性(轴、标题、形状、图例等。).
概念上的想法是使用 fig.add_trace() 和 fig.update_layout() 来操纵这些属性,以便增强已经构建的图形。
让我们用不同类型的面积图来分析这种方法。
面积图
面积图是折线图的一种形式,横轴和连接数据点的线之间的区域用颜色填充。它们用于传达总体趋势,而不关心显示准确的数值。
纵轴代表定量变量,而横轴是时间线或一系列数值区间。数据点由形成折线的线段连接,折线和水平轴之间的区域用颜色或某种类型的阴影填充。
面积图(AC)有四种类型:1)标准 AC;2)堆叠式 AC;3)堆叠交流百分比;4)交迭交流。
1.— 标准面积图(又名面积图):它们对于显示一个数值变量随时间的演变特别有效。这样做的目的是更加强调整体趋势以及连接数据点的线的波峰和波谷。
2.— 堆积面积图:就像几个面积图一个叠一个。在这种类型的图表中,有第三个变量,通常是分类变量,以及相应的数据系列。每一个数据序列的表示都从先前数据序列结束的地方开始(它们不重叠)。最后一个数字表示所有数据的总和。
3.— 百分比堆积面积图(也称为 100%堆积面积图):就像前面的图表一样,几个区域堆叠在另一个区域的顶部,并表示第三个分类变量。这是一个部分到整体的图表,其中每个区域表示每个部分相对于类别总数的百分比。垂直轴的最终高度始终为 100%。这意味着图表顶部有第二条基线,有助于跟踪某些特定趋势。
4.— 重叠区域图:在这种图形中,区域之间有一些重叠。颜色和透明度必须适当调整,以便可以很容易地看到特定的线条。它们让我们能够很好地比较不同的趋势。
带 Plotly Express 的面积图
我们使用了从 Kaggle [1]下载的数据集。该数据集包含从 VzCharts 收集的与视频游戏销售和游戏评级数据相关的记录。我们特别选择了一个 csv 文件,其中有 1031 条关于索尼在 Playstation 4 平台上销售视频游戏的记录。我们想知道 2013 年至 2018 年期间全球不同地区的销售额分布情况。
首先,我们导入 Plotly Express 为 *px,*Pandas 库为 pd ,并将我们的 csv 文件转换成 dataframe:
import pandas as pd
import plotly.express as pxpath ='your path'
df = pd.read_csv(path + 'PS4_GamesSales2.csv',
index_col = False, header = 0, sep = ';', engine='python')
下面的屏幕截图显示了数据集的前十条记录:
记住:真实世界的数据是脏的。所以我们在使用数据之前做了一些清理。具体来说,我们使用 dropna 删除 Year 列中具有 N/A 值的行,并使用 drop 删除 Global 列中具有 0 值的行。然后,我们根据年列将数据分组,并对每个组应用函数 sum() 。
df.dropna(subset = ['Year'], inplace = True)df.drop(df[df['Global'] == 0.0].index, inplace = True)df_area = df.groupby(['Year']).sum().reset_index()
现在我们准备绘制我们的第一张图表。
对于本文的标准面积图,Plotly Express 函数为 px.area ,对应的参数为:data _ frame;x=表示时间线的数据帧中的列的名称;y =**data _ frame中的列名,代表计算的统计数据。我们用 update.layout 更新了图表:设置标题、字体大小,用宽度和高度设置图形尺寸。然后我们更新了 x 轴和 y 轴(文本、字体、tickfont)。我们将图表保存为静态的 png 文件,最后,我们使用默认模板( plotly 、【带 Plotly Express 的直方图、主题&模板】、https://towards data science . com/Histograms-with-Plotly-Express-e 9e 134 AE 37 ad)绘制图表。
fig1 = px.area(
df_area, x = 'Year', y = 'Global')fig1.update_layout(
title = "PS4 Global Sales",
title_font_size = 40,
width = 1600, height = 1400)fig1.update_xaxes(
title_text = 'Year',
title_font=dict(size=30, family='Verdana', color='black'),
tickfont=dict(family='Calibri', color='darkred', size=25))fig1.update_yaxes(
title_text = "Sales (MM)",
range = (0,160),
title_font=dict(size=30,family='Verdana',color='black'),
tickfont=dict(family='Calibri', color='darkred', size=25))fig1.write_image(path + "figarea1.png")
fig1.show()
图 1:标准面积图。作者用 Plotly Express 制作的图表。
上图显示了 2013 年至 2018 年期间 PS4 全球销量的变化。如果没有面积图的视觉效果,同样的故事也可以用折线图来讲述。
但是请记住,我们想知道同一时期世界不同地区的销售额是如何分布的。因此我们需要一个堆积面积图,其中每个面积(每个区域)占总销售额的比例。每个区域的高度代表每个特定区域的值,而最终高度是这些值的总和。
现在我们使用了模块 plotly.graph_objects 和不同的方法学[ fig.add_trace() ]和不同的跟踪[ go。散射()】。 x =是代表时间线的数据帧中的列的名称,而 y =是代表特定区域的数据帧中的列的名称。堆栈组参数用于将同一组中不同轨迹的 y 值相加。我们必须用mode =‘lines’来为绘制线条而不是点。
import plotly.graph_objects as go
fig2 = go.Figure()fig2.add_trace(go.Scatter(
x= df_area['Year'], y = df_area['North America'],
name = 'North America',
mode = 'lines',
line=dict(width=0.5, color='orange'),
stackgroup = 'one'))fig2.add_trace(go.Scatter(
x= df_area['Year'], y = df_area['Europe'],
name = 'Europe',
mode = 'lines',
line=dict(width=0.5,color='lightgreen'),
stackgroup = 'one'))fig2.add_trace(go.Scatter(
x= df_area['Year'], y = df_area['Japan'],
name = 'Japan',
mode = 'lines',
line=dict(width=0.5, color='blue'),
stackgroup = 'one'))fig2.add_trace(go.Scatter(
x= df_area['Year'], y = df_area['Rest of World'],
name = 'Rest of World',
mode = 'lines',
line=dict(width=0.5, color='darkred'),
stackgroup = 'one'))fig2.update_layout(
title = "PS4 Global Sales per Region",
title_font_size = 40, legend_font_size = 20,
width = 1600, height = 1400)fig2.update_xaxes(
title_text = 'Year',
title_font=dict(size=30, family='Verdana', color='black'),
tickfont=dict(family='Calibri', color='darkred', size=25))fig2.update_yaxes(
title_text = "Sales (MM)", range = (0,160),
title_font=dict(size=30, family='Verdana', color='black'),
tickfont=dict(family='Calibri', color='darkred', size=25))fig2.write_image(path + "figarea2.png")
fig2.show()
图 2:堆积面积图。作者用 Plotly Express 制作的图表。
让我们看看百分之堆积面积图是否有助于讲故事。我们只需要做一个小改动:将 groupnorm = ‘percent’ 添加到第一个 add_trace()中。
fig3 = go.Figure()fig3.add_trace(go.Scatter(
x= df_area['Year'], y = df_area['North America'],
name = 'North America',
mode = 'lines',
line=dict(width=0.5, color='orange'),
stackgroup = 'one',
groupnorm = 'percent'))fig3.add_trace(go.Scatter(
x= df_area['Year'], y = df_area['Europe'],
name = 'Europe',
mode = 'lines',
line=dict(width=0.5,color='lightgreen'),
stackgroup = 'one'))fig3.add_trace(go.Scatter(
x= df_area['Year'], y = df_area['Japan'],
name = 'Japan',
mode = 'lines',
line=dict(width=0.5, color='blue'),
stackgroup = 'one'))fig3.add_trace(go.Scatter(
x= df_area['Year'], y = df_area['Rest of World'],
name = 'Rest of World',
mode = 'lines',
line=dict(width=0.5, color='darkred'),
stackgroup = 'one'))fig3.update_layout(
title = "PS4 Global Sales per Region",
title_font_size = 40, legend_font_size = 20,
yaxis=dict(type='linear',ticksuffix='%'),
width = 1600, height = 1400)fig3.update_xaxes(
title_text = 'Year',
title_font=dict(size=30, family='Verdana', color='black'),
tickfont=dict(family='Calibri', color='darkred', size=25))fig3.update_yaxes(
title_text = "Sales (%)", range = (0,100),
title_font=dict(size=30, family='Verdana', color='black'),
tickfont=dict(family='Calibri', color='darkred', size=25))fig3.write_image(path + "figarea3.png")
fig3.show()
图 3:百分比堆积面积图。作者用 Plotly Express 制作的图表。
图 3 是一个部分对整体的图表,其中每个区域表示每个地区占全球总销售额的百分比。重点是趋势,**每个类别的百分比如何随时间变化,**而不是确切的值。
最后,我们可以使用重叠面积图来比较销售额最高的地区。代替 stackgroup,我们将 fill = ‘tozeroy’ 添加到第一个 add_trace() 中,将*fill = ’ to texty '*添加到第二个 add_trace()中。
fig4 = go.Figure()fig4.add_trace(go.Scatter(
x= df_area['Year'], y = df_area['North America'],
name = 'North America',
mode = 'lines', line=dict(width=0.5,color='lightgreen'),
fill = 'tozeroy'))fig4.add_trace(go.Scatter(
x= df_area['Year'], y = df_area['Europe'],
name = 'Europe',
mode = 'lines', line=dict(width=0.5, color='darkred'),
fill = 'tonexty'))fig4.update_layout(
title = "PS4 Sales North America vs Europe",
title_font_size=40, legend_font_size = 20,
width = 1600, height = 1400)fig4.update_xaxes(
title_text = 'Year',
title_font=dict(size=30, family='Verdana', color='black'),
tickfont=dict(family='Calibri', color='darkred', size=25))fig4.update_yaxes(
title_text = "Sales (MM)", range = (0,70),
title_font=dict(size=30, family='Verdana', color='black'),
tickfont=dict(family='Calibri', color='darkred', size=25))fig4.write_image(path + "figarea4.png")
fig4.show()
图 4:重叠面积图。作者用 Plotly Express 制作的图表。
总结一下:
我们使用面积图来传达整体趋势和每个部分对整体的相对贡献,而不关心显示准确的值。
我们使用 plotly.graph_objects 模块通过 add_trace 方法顺序添加轨迹。然后,我们通过 update_layout、update_xaxes 和 update_yaxes 操作图表属性。
如果你对这篇文章感兴趣,请阅读我以前的(https://medium.com/@dar.wtz):
带有 Plotly Express、趋势线和分面的散点图
带有 Plotly Express、主题和模板的直方图
参考
【1】:https://www.kaggle.com/sidtwr/videogames-sales-dataset?select=PS4_GamesSales.csv
曲线下和曲线外的区域,具有综合的区分改善和净重新分类
实践教程,机器学习&诊断统计
例如,使用 python 代码,演示了如何生成 AUC、IDI 和 NRI 指标并进行深入研究
作者图片
TLDR
- AUC 是比较两种型号性能的一个很好的起点,但它并不总能说明全部情况
- NRI 着眼于新模型正确重新分类癌症和良性疾病的能力,应该与 AUC 一起使用
- IDI 对区分曲线斜率的改善进行量化,并对其作图,可以提供 AUC 单独无法提供的信息。
- 发表医学中这些概念的例子。
- 以下示例的代码和有用的 AUC、NRI、IDI 函数可以在 github 中找到。
https://www.nature.com/articles/s43856-021-00024-0#Fig3
曲线下面积
在机器学习和诊断医学中,受试者操作特征(ROC)曲线下面积(AUC)是用于评估模型或诊断测试的预测性能的常用指标。新模型通常以使用 AUC 的已有模型为基准。比较新旧模型的 AUC 以评估改进是一个很好的起点,但是,许多人在这里结束了他们的分析,并认为简单地报告更高的 AUC 就足够了。AUC 可能具有误导性,因为它对全部范围的敏感性和特异性值给予同等的权重,即使有限的范围或特定的阈值可能具有实际意义。在这篇文章中,我们展示了如何全面询问新的和改进的模型性能,超越简单的 AUC 比较,以便在给定问题的背景下提供对改进的更全面的理解。我们还提供了一个用 Python 编写的代码示例,来演示我们提出的概念。
例如:乳腺癌
乳腺癌是全世界女性癌症死亡的主要原因。早期检测有助于降低死亡率,越早发现恶性肿瘤,患者越有可能存活。因此,已经投入了巨大的努力来开发预测模型以更好地识别癌症。在这个例子中,我们使用提取的成像特征来建立模型,以预测恶性肿瘤的概率。我们使用来自位于 UCI 机器学习仓库的威斯康辛乳腺癌诊断数据库 [ 1 ]的数据。
如前所述,AUC 对所有阈值给予相同的权重,但这在乳腺癌诊断中可能不实用。乳腺影像报告和数据系统或(BI-RADS) [ 2 。为给定的恶性肿瘤概率提供一个行动方案。简而言之,如果恶性肿瘤的概率大于 2%,建议进行活检。活组织检查是侵入性的程序,它们可能对患者的身体和精神有害。理想地,新的和改进的乳房模型将在识别癌症(灵敏度增加)和减少假阳性(特异性增加)方面更好,优选低于 2%以避免侵入性和不必要的活检。
我们用下面的代码片段来设置这个例子。
示例设置
# Import Modules #
import os
import sys
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn import metrics
from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_auc_score
在导入必要的模块后,我们加载乳腺癌数据集,并将数据分成训练集和测试集。请注意,最好也创建一个验证集,我们通常会这样做,但是为了这个示例,我们将只使用一个训练集和测试集。
# Import data #
data = load_breast_cancer()
# Create DataFrame and Split Data
df = pd.DataFrame(data=data['data'],columns=data['feature_names'])
df['label']=data['target']
x_train, x_test, y_train, y_test = train_test_split(df.iloc[:,:-1], df.iloc[:,-1], test_size=0.40, random_state=123)
同样为了这个例子,我们将假设我们的参考/基准模型建立在与纹理、凹点、平滑度、分形维数和紧密度相关的乳房成像特征上。我们还将假设新模型使用与半径、周长、面积、对称性和凹度相关的附加成像生物标记/特征。总的来说,参考模型使用总共 15 个特征构建,而新模型使用 30 个特征构建(原始模型使用 15 个特征,15 个新特征)。下面的代码片段创建了两组功能。
# create ref and new model feature sets #
ref_feat, new_feat = [],[]
for i in data['feature_names']:
if 'fractal' in i or 'smoothness' in i or 'texture' in i ...
or 'concave' in i or 'compactness' in i:
ref_feat+=[i]
建模
我们创建了一个包含 15 个特征的参考或“参考模型”,以及包含所有 30 个特征的“新模型”。
ref_model = RandomForestClassifier()
new_model = RandomForestClassifier()
# fit models to train data
ref_model.fit(x_train[ref_feat], y_train)
new_model.fit(x_train[new_feat], y_train)
# make predictions on test data
test_ref_pred=ref_model.predict_proba(x_test[ref_feat])
test_new_pred=new_model.predict_proba(x_test[new_feat])
比较模型 AUC 的问题
我们之前提到过,比较 AUC 是一个很好的起点。我们这样做是为了了解新模型相对于我们的参考模型的表现如何。我们使用下面的自定义函数来可视化 ROC 曲线和置信区间(CI)。
#[https://www.nature.com/articles/s43856-021-00024-0#Fig3](https://www.nature.com/articles/s43856-021-00024-0#Fig3)
**def** bootstrap_results(y_truth, y_pred,num_bootstraps **=** 1000):
n_bootstraps = num_bootstraps
rng_seed = 42 *# control reproducibility*
y_pred=y_pred
y_true=y_truth
rng = np.random.RandomState(rng_seed)
tprs=[]
fprs=[]
aucs=[]
threshs=[]
base_thresh = np.linspace(0, 1, 101)
for i in range(n_bootstraps):
*# bootstrap by sampling with replacement on the prediction indices*
indices = rng.randint(0, len(y_pred), len(y_pred))
if len(np.unique(y_true[indices])) < 2:
*# We need at least one positive and one negative sample for ROC AUC*
continue
fpr, tpr, thresh = metrics.roc_curve(y_true[indices],y_pred[indices])
thresh=thresh[1:]
thresh=np.append(thresh,[0.0])
thresh=thresh[::-1]
fpr = np.interp(base_thresh, thresh, fpr[::-1])
tpr = np.interp(base_thresh, thresh, tpr[::-1])
tprs.append(tpr)
fprs.append(fpr)
threshs.append(thresh)
tprs = np.array(tprs)
mean_tprs = tprs.mean(axis=0)
fprs = np.array(fprs)
mean_fprs = fprs.mean(axis=0)
return base_thresh, mean_tprs, mean_fprs**def** get_auc_ci(y_truth, y_pred,num_bootstraps = 1000):
n_bootstraps = num_bootstraps
rng_seed = 42 # control reproducibility
bootstrapped_scores = []
y_pred=y_pred
y_true=y_truth
rng = np.random.RandomState(rng_seed)
tprs=[]
aucs=[]
base_fpr = np.linspace(0, 1, 101)
for i in range(n_bootstraps):
# bootstrap by sampling with replacement on the prediction indices
indices = rng.randint(0, len(y_pred), len(y_pred))
if len(np.unique(y_true[indices])) < 2:
# We need at least one positive and one negative sample for ROC AUC
continue
score = roc_auc_score(y_true[indices], y_pred[indices])
bootstrapped_scores.append(score)
fpr, tpr, _ = metrics.roc_curve(y_true[indices],y_pred[indices])
roc_auc = metrics.auc(fpr, tpr)
aucs.append(roc_auc)
tpr = np.interp(base_fpr, fpr, tpr)
tpr[0] = 0.0
tprs.append(tpr)
tprs = np.array(tprs)
mean_tprs = tprs.mean(axis=0)
std = tprs.std(axis=0)
mean_auc = metrics.auc(base_fpr, mean_tprs)
std_auc = np.std(aucs)
tprs_upper = np.minimum(mean_tprs + std*2, 1)
tprs_lower = mean_tprs - std*2
return base_fpr, mean_tprs, tprs_lower, tprs_upper, mean_auc, std_auc**def** plot_auc(truth, reference_model, new_model,n_bootstraps=1000, save=False):
y_truth = truth
ref_model = reference_model
new_model = new_model
ref_fpr, ref_tpr, ref_thresholds = metrics.roc_curve(y_truth, ref_model)
new_fpr, new_tpr, new_thresholds = metrics.roc_curve(y_truth, new_model)
ref_auc, new_auc = metrics.auc(ref_fpr, ref_tpr), metrics.auc(new_fpr, new_tpr)
print('ref auc =',ref_auc, '\newn auc = ', new_auc)
base_fpr_ref, mean_tprs_ref, tprs_lower_ref, tprs_upper_ref, mean_auc_ref, std_auc_ref=get_auc_ci(y_truth, ref_model,n_bootstraps)
base_fpr_new, mean_tprs_new, tprs_lower_new, tprs_upper_new, mean_auc_new, std_auc_new=get_auc_ci(y_truth, new_model,n_bootstraps)
plt.figure(figsize=(8, 8))
lw = 2
plt.plot(ref_fpr, ref_tpr, color='blue',
lw=lw, label='Reference raw ROC (AUC = %0.2f)' % ref_auc, linestyle='--')
plt.plot(base_fpr_ref, mean_tprs_ref, 'b', alpha = 0.8, label=r'Reference mean ROC (AUC=%0.2f, CI=%0.2f-%0.2f)' % (mean_auc_ref, (mean_auc_ref-2*std_auc_ref),(mean_auc_ref+2*std_auc_ref)),)
plt.fill_between(base_fpr_ref, tprs_lower_ref, tprs_upper_ref, color = 'b', alpha = 0.2)
plt.plot(new_fpr, new_tpr, color='darkorange',
lw=lw, label='New raw ROC (AUC = %0.2f)' % new_auc, linestyle='--')
plt.plot(base_fpr_new, mean_tprs_new, 'darkorange', alpha = 0.8, label=r'New mean ROC (AUC=%0.2f, CI=%0.2f-%0.2f)' % (mean_auc_new,(mean_auc_new-2*std_auc_new),(mean_auc_new+2*std_auc_new)),)
plt.fill_between(base_fpr_new, tprs_lower_new, tprs_upper_new, color = 'darkorange', alpha = 0.2)
plt.plot([0, 1], [0, 1], color='gray', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.0])
plt.xlabel('1 - Specificity', fontsize=18)
plt.ylabel('Sensitivity', fontsize=18)
plt.legend(loc="lower right", fontsize=13)
plt.gca().set_aspect('equal', adjustable='box')
我们运行以下程序来生成 AUC 图。
plot_auc(y_test.values,test_ref_pred[:,1],test_new_pred[:,1],n_bootstraps=100)
作者图片
图 1:使用自举法计算可信区间的 AUC 曲线
图 1 中的平均曲线和 95%置信区间。是通过 100 轮自举计算出来的,参见上面的代码。参照(蓝色曲线)和新模型(橙色曲线)分别在 0.99 和 0.99 产生相似的 AUC。新模型的置信区间略窄。总的来说,仅从 AUC 曲线很难得出任何有意义的结论,似乎具有额外特征的新模型对改善恶性肿瘤预测几乎没有作用。
超出曲线下的区域
我们的例子在图 1 中。展示了仅使用 AUC 的局限性,并建议分析需要超越相似的 AUC 比较。我们提出了两个额外的指标,通常用于评估新特征或生物标记对模型的影响。这些指标包括净重新分类指数(NRI)和综合歧视改善指数(IDI) [ 3 。这两个指标将有助于理解两个模型的实际差异,这些差异在图 1中可能并不明显。
净重新分类指数(NRI)
NRI 是将 NRI 事件和 NRI 非事件的数量相加得出的。在我们的例子中,事件是癌症或恶性肿瘤患者的同义词,NRI 非事件是良性病变患者的同义词。NRI 事件是指发生事件的患者被重新分配到较高风险类别的净比例,而 NRI 非事件是指未发生事件的患者被重新分配到较低风险类别的人数[ 4 ]。我们使用以下 Python 函数来计算 NRI。
**def** check_cat(prob,thresholds):
cat=0
for i,v in enumerate(thresholds):
if prob>v:
cat=i
return cat**def** make_cat_matrix(ref, new, indices, thresholds):
num_cats=len(thresholds)
mat=np.zeros((num_cats,num_cats))
for i in indices:
row,col=check_cat(ref[i],thresholds),check_cat(new[i],thresholds)
mat[row,col]+=1
return mat**def** nri(y_truth,y_ref, y_new,risk_thresholds):
event_index = np.where(y_truth==1)[0]
nonevent_index = np.where(y_truth==0)[0]
event_mat=make_cat_matrix(y_ref,y_new,event_index,risk_thresholds)
nonevent_mat=make_cat_matrix(y_ref,y_new,nonevent_index,risk_thresholds)
events_up, events_down = event_mat[0,1:].sum()+event_mat[1,2:].sum()+event_mat[2,3:].sum(),event_mat[1,:1].sum()+event_mat[2,:2].sum()+event_mat[3,:3].sum()
nonevents_up, nonevents_down = nonevent_mat[0,1:].sum()+nonevent_mat[1,2:].sum()+nonevent_mat[2,3:].sum(),nonevent_mat[1,:1].sum()+nonevent_mat[2,:2].sum()+nonevent_mat[3,:3].sum()
nri_events = (events_up/len(event_index))-(events_down/len(event_index))
nri_nonevents = (nonevents_down/len(nonevent_index))-(nonevents_up/len(nonevent_index))
return nri_events, nri_nonevents, nri_events + nri_nonevents
我们将参考模型和新模型预测输入 NRI 函数,计算事件、非事件和总 NRI。
print(nri(y_test.values,test_ref_pred[:,1],test_new_pred[:,1],[0.02,0.1,0.5,0.95]))
上面代码的输出是:
0.1642857142857143, 0.125, 0.2892857142857143
“与参考模型相比,新模型对 29%的患者进行了重新分类”,这种说法很诱人,但这并不准确,也不是对 NRI 的正确解释。由于 NRI 是事件比例的总和,而不是事件比例的总和,因此 NRI 可能为 2%或 200%。声称新模型对 200%以上的个体进行了重新分类是不可能的,因为这意味着患者数量突然增加了一倍。更正确的解释是,与参考模型相比,新模型正确分类的癌症病例多 16%,良性多 13%,总 NRI 为 29%。
NRI 向我们展示了在新模型中增加更多的特征可以对恶性和非恶性患者进行重新分类。这一信息不能仅从 AUC 得出,可能会错误地使人们认为模型是相同的。在临床环境中,新的模型会发现更多的癌症,这可能会挽救更多的生命。此外,新的模型将正确地识别更多的益处,这将转化为对个体更少的压力,并可能使他们免于像活检这样的侵入性程序。
无类别或 cfNRI 是一个较新的指标,跟踪事件和非事件的总体变化,不考虑类别。以我们的乳腺癌为例,美国放射学院(ACR)有不同的 BI-RADS 类别,因此我们认为没有必要计算 cfNRI。我们提供了计算 cfNRIs 的编码函数,以满足您的问题。
**def** track_movement(ref,new, indices):
up, down = 0,0
for i in indices:
ref_val, new_val = ref[i],new[i]
if ref_val<new_val:
up+=1
elif ref_val>new_val:
down+=1
return up, down**def** category_free_nri(y_truth,y_ref, y_new):
event_index = np.where(y_truth==1)[0]
nonevent_index = np.where(y_truth==0)[0]
events_up, events_down = track_movement(y_ref, y_new,event_index)
nonevents_up, nonevents_down = track_movement(y_ref, y_new,nonevent_index)
nri_events = (events_up/len(event_index))-(events_down/len(event_index))
nri_nonevents = (nonevents_down/len(nonevent_index))-(nonevents_up/len(nonevent_index))
#print(events_up, events_down, len(event_index), nonevents_up, nonevents_down, len(nonevent_index), nri_events, nri_nonevents, nri_events+nri_nonevents)
return nri_events, nri_nonevents, nri_events + nri_nonevents
综合歧视指数(IDI)
IDI 是区分斜率变化的量度,显示了新生物标记对二元预测模型的影响。IDI 是综合敏感性(is)和综合特异性(IP)的总和,像 NRI 一样,它分离出事件和非事件,或者在这种情况下是癌症和良性疾病。我们使用下面的代码来计算和绘制 IDI 曲线[ 5 ]。
**def** area_between_curves(y1,y2):
diff = y1 - y2 # calculate difference
posPart = np.maximum(diff, 0)
negPart = -np.minimum(diff, 0)
posArea = np.trapz(posPart)
negArea = np.trapz(negPart)
return posArea,negArea,posArea-negArea**def** plot_idi(y_truth, ref_model, new_model, save=False):
ref_fpr, ref_tpr, ref_thresholds = metrics.roc_curve(y_truth, ref_model)
new_fpr, new_tpr, new_thresholds = metrics.roc_curve(y_truth, new_model)
base, mean_tprs, mean_fprs=bootstrap_results( y_truth, new_model,100)
base2, mean_tprs2, mean_fprs2=bootstrap_results( y_truth, ref_model,100)
is_pos,is_neg, idi_event=area_between_curves(mean_tprs,mean_tprs2)
ip_pos,ip_neg, idi_nonevent=area_between_curves(mean_fprs2,mean_fprs)
print('IS positive', round(is_pos,2),'IS negative',round(is_neg,2),'IDI events',round(idi_event,2))
print('IP positive', round(ip_pos,2),'IP negative',round(ip_neg,2),'IDI nonevents',round(idi_nonevent,2))
print('IDI =',round(idi_event+idi_nonevent,2))
plt.figure(figsize=(10, 10))
ax=plt.axes()
lw = 2
plt.plot(base, mean_tprs, 'black', alpha = 0.5, label='Events New (New)' )
plt.plot(base, mean_fprs, 'red', alpha = 0.5, label='Nonevents New (New)')
plt.plot(base2, mean_tprs2, 'black', alpha = 0.7, linestyle='--',label='Events Reference (Ref)' )
plt.plot(base2, mean_fprs2, 'red', alpha = 0.7, linestyle='--', label='Nonevents Reference (Ref)')
plt.fill_between(base, mean_tprs,mean_tprs2, color='black',alpha = 0.1, label='Integrated Sensitivity (area = %0.2f)'%idi_event)
plt.fill_between(base, mean_fprs,mean_fprs2, color='red', alpha = 0.1, label='Integrated Specificity (area = %0.2f)'%idi_nonevent)#''' #TODO: comment out if not for breast birads
### BIRADS Thresholds ###
plt.axvline(x=0.02,color='darkorange',linestyle='--',alpha=.5,label='BI-RADS 3/4a Border (2%)')
plt.axvline(x=0.10,color='green',linestyle='--',alpha=.5,label='BI-RADS 4a/4b Border (10%)')
plt.axvline(x=0.5,color='blue',linestyle='--',alpha=.5,label='BI-RADS 4b/4c Border (50%)')
plt.axvline(x=0.95,color='purple',linestyle='--',alpha=.5,label='BI-RADS 4c/5 Border (95%)')
**def** nri_annotation(plt, threshold):
x_pos = base[threshold]
x_offset=0.02
x_offset2=x_offset
text_y_offset=0.01
text_y_offset2=text_y_offset
if threshold==2:
text_y_offset=0.04
text_y_offset2=0.04
x_offset2=0.05
print(x_pos+x_offset, (np.mean([mean_tprs2[threshold], mean_tprs[threshold]])+text_y_offset),
x_pos, (np.mean([mean_tprs2[threshold], mean_tprs[threshold]])))
text_y_events=np.mean([mean_tprs2[threshold], mean_tprs[threshold]])+text_y_offset
text_y_nonevents=np.mean([mean_fprs[threshold], mean_fprs2[threshold]])+text_y_offset2
plt.annotate('', xy=(x_pos+0.02, mean_tprs2[threshold+1]), xycoords='data', xytext=(x_pos+0.02,
mean_tprs[threshold]), textcoords='data', arrowprops={'arrowstyle': '|-|'})
plt.annotate('NRI$_{events}$ = %0.2f'%(mean_tprs[threshold]-mean_tprs2[threshold]),
xy=(x_pos+x_offset, text_y_events), xycoords='data',
xytext=(x_pos+x_offset, text_y_events),
textcoords='offset points', fontsize=15)
plt.annotate('', xy=(x_pos+0.02, mean_fprs[threshold]), xycoords='data', xytext=(x_pos+0.02,
mean_fprs2[threshold]), textcoords='data', arrowprops=dict(arrowstyle= '|-|',color='r'))
plt.annotate('NRI$_{nonevents}$ = %0.2f'%(mean_fprs2[threshold]-mean_fprs[threshold]),
xy=(x_pos+x_offset2, text_y_nonevents), xycoords='data',
xytext=(x_pos+x_offset2, text_y_nonevents),
textcoords='offset points', fontsize=15)
print('Threshold =',round(x_pos,2),'NRI events =',round(mean_tprs[threshold]-mean_tprs2[threshold],4),
'NRI nonevents =',round(mean_fprs2[threshold]-mean_fprs[threshold],4),'Total =',
round((mean_tprs[threshold]-mean_tprs2[threshold])+(mean_fprs2[threshold]-mean_fprs[threshold]),4))
nri_annotation(plt,2)
nri_annotation(plt,10)
nri_annotation(plt,50)
nri_annotation(plt,95)
#'''
plt.xlim([0.0, 1.10])
plt.ylim([0.0, 1.10])
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
plt.xlabel('Calculated Risk', fontsize=18)
plt.ylabel('Sensitivity (black), 1 - Specificity (red)', fontsize=18)
plt.legend(loc="upper right", fontsize=11)
plt.legend(loc=0, fontsize=11, bbox_to_anchor=(0,0,1.2,.9))
plt.gca().set_aspect('equal', adjustable='box')
#if save:
# plt.savefig('idi_curve.png',dpi=300, bbox_inches='tight')
look=95
plt.show()
我们运行下面的命令来生成图 2 中的图。
plot_idi(y_test.values,test_ref_pred[:,1],test_new_pred[:,1])
输出:
IS positive 3.58 IS negative 0.16 IDI events 3.42
IP positive 4.61 IP negative 0.04 IDI nonevents 4.57
IDI = 7.98
0.04 1.04 0.02 1.0
Threshold = 0.02 NRI events = 0.0 NRI nonevents = 0.2762 Total = 0.2762
Threshold = 0.1 NRI events = -0.008 NRI nonevents = 0.1002 Total = 0.0922
Threshold = 0.5 NRI events = 0.0215 NRI nonevents = 0.0317 Total = 0.0532
Threshold = 0.95 NRI events = 0.1389 NRI nonevents = -0.0136 Total = 0.1252
作者图片
图 2: IDI 曲线,在每个级别或双半径边界计算 NRI
虚线和实线分别代表参考模型和新模型。在理想情况下,黑色实线将移至右上方,表示灵敏度提高最多,红色实线将移至右下方,表示特异性提高最多。黑色和红色曲线之间的面积等于 IS 和 IP,黑色和红色面积的总和等于 IDI。IDI 提供了更多关于 AUC 曲线的信息,特别是关于橙色垂直虚线或 BI-RADS 3/4a 边界。在此边界处,IP 或红色区域较大,这表明新模型能够更好地预测 benigns。这个界限特别有趣,因为建议所有 BI-RADS 4 或以上的患者接受活检[ 6 ]。3/4a 边界处的大面积显示,向新模型添加新特征增加了特异性,并可能防止 28%以上的人(NRI 无事件=0.28)进行不必要的活检。
最后的想法
AUC 是一个很好的指标,但不能提供全面分析新生物标志物和模型影响所需的所有信息。应使用 NRI 和 IDI 来补充 AUC 调查结果和解释。作为一名使用 Python 的数据科学家和癌症研究人员,很难找到计算和绘制 NRI 和 IDI 的函数和代码片段。这是我张贴这篇文章的动机,我希望我的代码可以帮助其他人的研究,发现和追求更好的医疗保健解决方案。
参考
【Dua,d .和 Graff,C .(2019),加州尔湾:加州大学信息与计算机科学学院,UCI 机器学习资源库【http://archive.ics.uci.edu/ml】。
【2】了解乳腺摄影报告:乳腺摄影结果。(未注明)。检索于 2021 年 5 月 6 日,来自https://www . cancer . org/cancer/breast-cancer/screening-tests-and-early-detection/乳房 x 光片/understanding-your-乳房 x 光片-报告. html
【3】pen Cina,M. J .,D’Agostino Sr,R. B .,D’Agostino Jr,R. B .,& Vasan,R. S .,评估一种新标记物的额外预测能力:从 ROC 曲线下面积到重新分类及重新分类之后,(2008),医学统计学,27(2),157–172。
【4】pen Cina,M. J .,D’Agostino Sr,R. B .,& Steyerberg,E. W .,测量新生物标志物有用性的净重新分类改进计算的扩展,(2011),医学统计学,30(1),11–21。
【Pickering,j . w .】&Endre,Z. H .,评估候选生物标志物诊断潜力的新指标,(2012),美国肾病学会临床杂志,7(8),1355–1364。
【6】Leong,l .、Malkov,s .、Drukker,k .、Niell,b .、Sadowski,p .、Wolfgruber,t .…&Shepherd,j .、双能量三室乳腺成像(3CB),用于改进恶性病变检测的新型成分生物标志物,(2021)。
原载于 2021 年 5 月 1 日 https://www.lambertleong.com**的 。
Python 中的 Args 和 Kwargs 函数调用变得简单
理解如何用多个值完整地调用函数。
在我第一次阅读 fastai 的书时,我在函数调用中遇到了关键字 *args 和 **kwargs 。自然地,我非常好奇,想了解我如此热爱的编程语言的另一个方面,所以我开始研究。事实证明,这两者结合起来确实使 Python 3 中一些复杂的函数调用看起来像是小孩子的游戏!
自从我研究了这两个操作符(是的,在我们所说的技术语言中,它们被称为 解包操作符 )之后,我就一直在我的日常项目实验中使用它们,我发现它们非常酷。所以现在,我写这篇文章是为了让你了解这些很酷的概念,以防你错过!
我们走吧!
什么是解包运算符?
单星号和双星号解包操作符是在 Python 2 中引入的。但是在 Python 3.6 发布之后,它们在使用中变得更加强大。
简而言之,解包操作符是在 Python 中 解包来自 iterable 对象的值的操作符。单星运算符*
可以用在语言中的任何一种 iterable 上,而双星运算符**
只能用在 字典 上的各种运算上。
让我们来看一个例子。我们将定义一些辅助变量,我们将在本教程中使用它们来探索这两个操作符的功能。
nums = [1, 2, 3]
alphs = ['a', 'b', 'c']nums_d = {1: 'one', 2: 'two', 3: 'three'}
alphs_d = {'a': 'First', 'b': 'Second', 'c' : 'Third'}
现在,让我们通过观察他们的行动来进一步了解他们吧!
我们如何使用解包操作符?
首先,让我们在我们的列表 nums 上进行实验。
下面是列表的解压缩版本:
print(*nums) OUTPUT:
1 2 3
我们再做一个!我们将打开下一个字符/字符串列表,我们有 alphs。
print(*alphs)OUTPUT:
a b c
注意到相似之处了吗?你一定知道这个操作员现在对这些列表做了什么,对吗?它把可迭代列表变成了单独的元素,这些元素可以被一些函数单独处理,我们可以把这些值传递给这些函数!
现在让我们来看看这个用例。当我们看到关于使用函数来操作这些由操作符解包的值的例子时,应该就清楚了。
在 Python 函数中使用解包运算符
我们可以使用这个简单的调用来解包一个字符串:
ex = 'Args and Kwargs'print(*ex)OUTPUT:
A r g s a n d K w a r g s# each individual character can be processed separately
我们也可以用元素列表来做。让我们定义一个简单的将三个数相加的函数。
def sum_of_nums(n1, n2, n3):
print(n1 + n2 + n3)
现在让我们继续将我们的数字列表num传递给这个函数,但是附加了解包操作符。
sum_of_nums(*nums) OUTPUT:
6
看到魔法了吗?!这太酷了。我们也可以用之前定义的字典做同样的事情。
让我们首先定义一个函数来进行字符串连接,并将结果打印给用户。 *args 代表自变量。
def concat_str(*args):
res = ''
for s in args:
res += s
print(res)
现在,让我们用字典 alphs 附带的解包操作符调用它。
concat_str(*alphs)OUTPUT:
abc
这看起来与上面的输出非常相似。
结论是什么?解包操作符允许我们在需要时使用和操作任何 iterable 中的单个元素。这样,我们可以传递一个复杂的列表或字典列表,操作符可以让我们在自定义函数中使用列表的元素和字典的键/值。
这里,看看操作符对字符串列表做了什么。
ex = 'Args and Kwargs'
print([*ex]) OUTPUT:
['A', 'r', 'g', 's', ' ', 'a', 'n', 'd', ' ', 'K', 'w', 'a', 'r', 'g', 's']
它返回一个 iterable (一个新的列表),其中的元素是被解包的单个字符串!
与**kwargs 一起前进一步
**kwargs 代表关键字参数。
Kwargs 是我们在字典中使用的一个解包操作符。让我们定义一个新函数,将两个或更多字典连接在一起。
def concat_str_2(**kwargs):
result = ''
for arg in kwargs:
result += arg
return result
现在,我们可以用任意数量的字典调用这个函数。
print(concat_str_2(**alphs_d)) OUTPUT:
abc
哎呀!字典中的键在这里被连接起来。但是我们可能想要连接键值,对吗?
我们可以通过对值进行迭代来实现这一点,就像我们对任何普通字典所做的那样。
# concatenating the values of the kwargs dictionary
def concat_str_3(**kwargs):
result = ''
for arg in kwargs.values():
result += arg
return result print(concat_str_3(**alphs_d))
你能猜到输出会是什么吗?
OUTPUT:
FirstSecondThird
还记得我们之前的两本字典吗?通过使用 **** 操作符,我们可以很容易地将它们组合起来。
alphanum = {**nums_d, **alphs_d}
print(alphanum)OUTPUT:
{1: 'one', 2: 'two', 3: 'three', 'a': 'First', 'b': 'Second', 'c': 'Third'}
最后,我们还可以直接将字典连接在一起。
concat_str_3(a = 'Merge', b = ' this ', c = "dictionary's values")
这里,我们将键值对传递给我们的函数。在你看下面之前,试着想想我们的输出会是什么。
OUTPUT:
"Merge this dictionary's values"
就是这样!现在你知道 Python 中这两个令人敬畏的操作符的一切了!
最后一件事
我们在函数定义中定义函数参数的顺序,记住这一点很重要。我们不能打乱顺序,否则我们的 Python 解释器会抛出一个错误!
这是函数定义中参数排列的正确顺序:
# correct order of arguments in function
def my_cool_function(a, b, *args, **kwargs):
'''my cool function body'''
*顺序如下:标准变量参数,args 参数,然后是**kwargs 参数。
与本教程相关的完整 jupyter 笔记本可以在本报告中找到!https://github.com/yashprakash13/Python-Cool-Concepts
https://github.com/yashprakash13/Python-Cool-Concepts
如果你想了解更多关于我在 fastai 上写的系列文章,其中我记录了我与这个库的旅程,请访问这篇文章。😃
要不要学会用不到 20 行代码做一个深度学习模型,快速部署成 REST API?在这里获得我的免费指南!
用于软标签分类的 ARIMA
如何让你的模型产生概率
分类任务的性质意味着已知目标的可用性。在实际应用中,标签是由某种人工活动生成的,或者是作为某种确定性操作的结果而获得的。可能发生的情况是,标记产生不准确的目标,这影响了我们的机器学习模型的训练。为了减轻过度适应或过度自信带来的风险,我们可以检查概率结果。
某种概率分数的存在使我们有可能采用先进的技术来挤压结果(阈值调整、校准)。我们并不总是能够以概率的形式获得结果。一些算法不产生它们,或者没有被设计成以概率的方式处理任务。这可能是一个严重的缺失,尤其是在商业价值方面。
在这篇文章中,我们尝试使用软标签从回归算法开始执行分类任务。一般来说,我们可能会采用回归模型,这是由于问题的性质,或者是因为标记目标的方式没有给出好的结果。这个简单的技巧使我们能够利用回归算法的学习能力保持概率解释。
我们开始介绍一个在分类环境中使用正常回归算法的一般应用。然后,我们在时间序列域中转换相同的过程,其中我们尝试使用 ARIMA 来建模二进制分类问题。
用软标签分类
采用回归方法来模拟二元目标并不是一个好选择。首先,错误分类没有受到足够的惩罚。分类任务中的决策边界很大,而在回归中,两个预测值之间的距离可能很小。第二,从概率的角度来看,用回归建模我们对目标的分布做了一些特定的假设。而实际上,二进制目标来自伯努利分布。
也就是说,我们可以训练一个分类器来最小化均方误差,但是这会产生不一致的结果,并且没有相关的概率结果。我们有的选择既简单又有效。我们使用反 sigmoid 函数将您的目标转换到对数优势比空间。
Logits:来源维基百科
我们有软目标/标签p ∈ (0, 1)
(确保将目标夹在[eps, 1 - eps]
中,以避免我们记录日志时的不稳定性问题)。然后拟合一个回归模型。最后,为了进行推断,我们从回归模型中提取预测的 sigmoid。
来源维基百科
我们以每个样本的预测概率结束,其中,一如既往地,概率高于 0.5 意味着模型预测类别 1(否则类别 0)。假设一个线性关系,这和建立一个逻辑回归没有太大的不同。附加值在于在各种情况下采用该程序,以及更复杂的关系。
我们用岭回归作为基本估计量来模拟之前在二元分类场景中介绍的方法。所获得的结果是足够一致的,以确认该方法是有效的,以模拟分类任务并产生概率结果。
使用岭回归和软标签获得的校准图(图片由作者提供)
用 ARIMA 和软标签分类
使用回归模型和软标签进行分类的最佳特征之一是,该过程是完全模型不可知的。将它应用于 ARIMA 模型怎么样?
现在,我们重复上面所做的,但在时间序列领域。假设我们有一个时间序列的二元目标,我们试图预测他们符合 ARIMA。在我们的例子中,我们处理了一系列每月航班乘客的变化。每个月的变化是通过本月(Mt)
与上月(Mt-1)
的乘客比例得出的。高于 1 的比率意味着该月的乘客数量比上个月有所增加。我们的二元目标是 1 代表积极变化,0 代表消极变化。
每月航空公司乘客变化(图片由作者提供)
二进制格式的每月航空乘客变化(图片由作者提供)
给定一系列二进制目标,我们将它们转换成软标签并应用 logit 函数。我们用 AIC 准则拟合选择最佳超参数的 ARIMA。应用 sigmoid 变换获得最终预测。在下图中,我们在背景中显示了真实标签(蓝色),以及训练集(橙色)和测试集(绿色)的预测概率。
使用 SARIMAX 为训练集和测试集生成的概率(图片由作者提供)
结果很有意思。我们的方法产生良好的预测,显示出以概率形式产生结果的能力。这同样可以推广到每一个二元时间序列或扩展到多分类作为一个与休息的程序。
摘要
在这篇文章中,我们介绍了一种使用软标签和回归模型执行分类任务的技术。首先,我们将它应用于表格数据,然后用它对 ARIMA 时间序列进行建模。一般来说,它适用于每种环境和每种场景,还提供了概率得分。这对于标注平滑也非常有用,可以减少标注中出现的一些错误。
保持联系: Linkedin
Python 中的 ARIMA 模型
时间序列预测完全指南
ARIMA 是最流行的统计模型之一。它代表自回归综合移动平均,适合时间序列数据,用于预测或更好地理解数据。我们不会涵盖 ARIMA 模型背后的整个理论,但我们会告诉你正确应用它需要遵循的步骤。
ARIMA 车型的主要特点如下:
- AR:自回归。这表明时间序列根据其自身的滞后值回归。
- **I:综合。**这表示数据值已被替换为其值与先前值之间的差值,以便将序列转换为平稳序列。
- **MA:移动平均线。**这表明回归误差实际上是误差项的线性组合,其值在过去的不同时间同时出现。
当我们有季节性或非季节性数据时,可以应用 ARIMA 模型。不同之处在于,当我们有季节性数据时,我们需要向模型中添加更多的参数。
对于非季节性数据,参数为:
- p :模型将使用的滞后观测的数量
- d :原始观测值被差分直到平稳的次数。
- q :移动平均线窗口的大小。
对于季节性数据,我们还需要添加以下内容:
- P:模型将使用的季节性滞后观测的数量
- D :季节观测值差分到平稳的次数。
- Q :季节移动平均线窗口的大小。
- m:1 个季节的观测次数
作者图片
季节性或非季节性数据
这个很好理解。季节性数据是指有时间间隔的数据,如每周、每月或每季度。例如,在本教程中,我们将使用按月聚集的数据,我们的“季节”是一年。因此,我们有季节性数据,对于 ARIMA 模型中的 m 参数,我们将使用 12 ,即每年的月数。
平稳性
ARIMA 模型只能应用于平稳数据。这意味着我们不想有一个时间趋势。如果时间序列有趋势,那么它是非平稳的,我们需要应用差分将其转换为平稳的。下面是一个平稳和非平稳序列的例子。
作者图片
我们还可以使用扩展的 Dickey-Fuller 测试来帮助我们判断序列是否平稳。检验的零假设是有一个单位根,或者没有单位根。换句话说,如果 p 值低于 0.05(或您将使用的任何临界尺寸),我们的系列是固定的。
开始编码吧。
对于本教程,我们将使用来自 Kaggle 的航空乘客数据。
import pandas as pd
import numpy as np
from statsmodels.tsa.seasonal import seasonal_decompose
#[https://www.kaggle.com/rakannimer/air-passengers](https://www.kaggle.com/rakannimer/air-passengers)
df=pd.read_csv(‘AirPassengers.csv’)
#We need to set the Month column as index and convert it into datetime
df.set_index(‘Month’,inplace=True)
df.index=pd.to_datetime(df.index)df.head()
作者图片
首先,让我们绘制数据。
df.plot()
作者图片
正如你可以清楚地看到,有一个时间趋势,这表明数据不是静止的。然而,只是为了确保我们将使用一个扩大的迪基-富勒测试。
from statsmodels.tsa.stattools import adfullerresult=adfuller(df['#Passengers'])#to help you, we added the names of every value
dict(zip(['adf', 'pvalue', 'usedlag', 'nobs', 'critical' 'values', 'icbest'],result)){'adf': 0.8153688792060468,
'pvalue': 0.991880243437641,
'usedlag': 13,
'nobs': 130,
'criticalvalues': {'1%': -3.4816817173418295,
'5%': -2.8840418343195267,
'10%': -2.578770059171598},
'icbest': 996.692930839019}
正如所料,我们未能拒绝零假设,该系列有一个单位根,因此不是平稳的。
使用差分将非平稳转换为平稳(D 和 D 参数)
下一步是将我们的数据转换为静态数据,这样我们就可以估计出将在模型中使用的 D 和 D 参数。这可以通过使用差分来完成,并通过从当前观察值中减去先前观察值来完成。
差异(T) =观察值(T) —观察值(T-1)
然后,我们将使用扩展的 Dickey-Fuller 测试再次测试它的平稳性,如果它是平稳的,我们将进行下一步。如果没有,我们将再次应用差分,直到我们有一个平稳的序列。使用移位功能,熊猫可以很容易地进行区分。
df['1difference']**=**df['#Passengers']**-**df['#Passengers'].shift(1)df['1difference'].plot()
作者图片
似乎我们去除了趋势,序列是平稳的。然而,我们将使用扩展的 Dickey-Fuller 测试来证明这一点。
#note we are dropping na values because the first value of the first difference is NAresult=adfuller(df['1difference'].dropna())
dict(zip(['adf', 'pvalue', 'usedlag', 'nobs', 'critical' 'values', 'icbest'],result)) {'adf': -2.8292668241699857,
'pvalue': 0.05421329028382734,
'usedlag': 12,
'nobs': 130,
'criticalvalues': {'1%': -3.4816817173418295,
'5%': -2.8840418343195267,
'10%': -2.578770059171598},
'icbest': 988.5069317854084}
如你所见,我们没有拒绝零假设,因为我们的 p 值大于 0.05。这表明序列不是稳定的,我们需要再次使用差分法,取第二个差。第二个差可以作为第一个差来计算,但是这次我们将使用第一个差,而不是使用观察值。
df['2difference']**=**df['1difference']**-**df['1difference'].shift(1)df['2difference'].plot()
作者图片
让我们从第二个差异的扩展 Dickey-Fuller 测试中得到结果。
result**=**adfuller((df['2difference']).dropna())dict(zip(['adf', 'pvalue', 'usedlag', 'nobs', 'critical' 'values', 'icbest'],result)){'adf': -16.384231542468452,
'pvalue': 2.732891850014516e-29,
'usedlag': 11,
'nobs': 130,
'criticalvalues': {'1%': -3.4816817173418295,
'5%': -2.8840418343195267,
'10%': -2.578770059171598},
'icbest': 988.602041727561}
p 值小于 0.05,因此我们可以拒绝零假设。这意味着第二个差异是固定的,这表明对值 d 的一个好的估计是 2 。
我们的数据是季节性的,所以我们还需要估计 D 值,它与 D 值相同,但存在季节性差异。季节性差异可以通过将数据移动每季的行数(在我们的示例中是每年 12 个月)并从上一季中减去它们来计算。这是而不是第一个季节差异。如果我们得到季节差异是稳定的,那么 D 值将为 0。如果不是,那么我们将计算季节第一差异。
季节差异(T) =观测值(T) —观测值(T-12)
季节首差(T) =季节差(T) —季节差(T-1)
df['Seasonal_Difference']**=**df['#Passengers']**-**df['#Passengers'].shift(12)ax**=**df['Seasonal_Difference'].plot()
作者图片
result**=**adfuller((df['Seasonal_Difference']).dropna())dict(zip(['adf', 'pvalue', 'usedlag', 'nobs', 'critical' 'values', 'icbest'],result)){'adf': -3.383020726492481,
'pvalue': 0.011551493085514952,
'usedlag': 1,
'nobs': 130,
'criticalvalues': {'1%': -3.4816817173418295,
'5%': -2.8840418343195267,
'10%': -2.578770059171598},
'icbest': 919.527129208137}
p 值小于 0.05 ,因此它是固定的,我们不必使用差分。这表明使用 0 作为和的 D 值。
自相关和偏自相关图(P,Q 和 P,Q 参数)
ARIMA 模型之前的最后一步是创建自相关和偏自相关图,以帮助我们估计 P、Q、P 和 Q 参数。
ARIMA 和季节性 ARIMA 模型有一些非常有用的规则,我们通过查看自相关和偏自相关图来帮助我们估计参数。我们将为我们的时间序列的秒差和季节差创建图表,因为这些是我们最终在 ARIMA 使用的平稳序列(d=2,D=0)。
首先,让我们画出 ACF 和 PACF 的第二个区别。
**from** statsmodels.graphics.tsaplots **import** plot_acf, plot_pacffig1**=**plot_acf(df['2difference'].dropna())fig2**=**plot_pacf(df['2difference'].dropna())
作者图片
作者图片
我们可以看到,在我们的两个图中,滞后-1 处都有一个明显的截止点。根据我们上面提到的规则,这建议使用 AR 和 MA 术语。换句话说,p=1,q=1。
现在,我们需要同样的季节差异图。
fig1=plot_acf(df['2difference'].dropna())fig2=plot_pacf(df['2difference'].dropna())
作者图片
作者图片
我们在自相关图中有一个逐渐减小的曲线,在部分自相关图中有一个急剧的截止。这表明对于 ARIMA 的季节部分,使用 AR 并且不超过值 1。
我们选择的值可能不是最佳的。您可以使用这些参数来微调模型,以上面提到的规则作为指导。
ARIMA 模式
**from** statsmodels.tsa.statespace.sarimax **import** SARIMAXmodel**=**SARIMAX(df['#Passengers'],order**=**(1,2,1),seasonal_order**=**(1, 0, 0, 12))result**=**model.fit()
我们可以画出模型的残差,从而了解模型的拟合程度。基本上,残差是原始值和模型预测值之间的差值。
result.resid.plot(kind='kde')
作者图片
是时候做个预测了。我们将创建一些未来的日期添加到我们的数据中,这样我们就可以预测未来的值。
**from** pandas.tseries.offsets **import** DateOffsetnew_dates**=**[df.index[**-**1]**+**DateOffset(months**=**x) **for** x **in** range(1,48)]df_pred**=**pd.DataFrame(index**=**new_dates,columns **=**df.columns)df_pred.head()
作者图片
ARIMA 模型预测将枚举索引的开始和结束作为参数,而不是日期范围。
我们创建了一个包含未来日期索引的空数据框,并将它们连接到原始数据中。我们的数据有 144 行,我们添加的新数据有 48 行。因此,为了只获得对未来数据的预测,我们将从第 143 行预测到第 191 行。
df2**=**pd.concat([df,df_pred])df2['predictions']**=**result.predict(start**=**143,end**=**191)df2[['#Passengers','predictions']].plot()
作者图片
总结一下
Arima 是一个很好的预测模型,可用于季节性和非季节性时间序列数据。
对于非季节性 ARIMA,你必须估计 P、D、Q 参数,而对于季节性 ARIMA,它有 3 个以上适用于 P、D、Q 参数的季节性差异。
我们用来运行 ARIMA 模型的管道如下:
- 看看你的时间序列,了解它们是季节性的还是非季节性的。
- 如果需要达到平稳性,将差分应用于时间序列和季节差异,以获得 D 和 D 值的估计值。
- 绘制自相关和偏自相关图有助于估计 P、P 和 Q、Q 值。
- 微调模型,如果需要改变参数根据 ARIMA 的一般规则