PaperSpace-博客中文翻译-一-
PaperSpace 博客中文翻译(一)
11 个最好的人工智能和机器学习播客,添加到您的收听管道中
原文:https://blog.paperspace.com/11-best-ai-and-machine-learning-podcasts/
商业中的人工智能
了解人工智能在商业中的可能性和作用。每周你都会发现来自金融服务、制药、零售、国防等行业的顶级人工智能和机器学习高管和研究人员的特色采访。发现趋势,了解目前行业中的工作方式,并了解如何在人工智能中断的时代适应和发展。
主持人: 丹尼尔·法盖拉****
****张贴时间表:每周二、四
人工智能播客
人工智能被形容为“雷神之锤”和“新的电力”。但这也有点神秘——即使对那些最了解它的人来说也是如此。在人工智能播客上,诺亚·克拉维茨(Noah Kravitz)与人工智能、深度学习和机器学习领域的一些世界领先专家进行了联系,解释了它是如何工作的,它是如何发展的,以及它如何与人类努力的每个方面(从艺术到科学)相交。
主持人: 诺亚·克拉维茨
****发布时间表:每两周一次,在不同的日期发布
艾今日播客
Cognilytica 的 AI Today 播客专注于人工智能世界今天发生的事情的相关信息。讨论了围绕人工智能的紧迫话题,内容易于消化,采访了人工智能各个领域的嘉宾和专家,并试图通过炒作和噪音来确定人工智能的采用和实施到底发生了什么。
****发布时间表:每周三
[人工智能:AI 播客](https://lexfridman.com/ai/#:~:text=Artificial Intelligence podcast (AI podcast,YouTube channel for full conversations.&text=Support the podcast monthly on,one-time donation on PayPal.)
人工智能:人工智能播客(也称为“AI + Lex”)从深度学习、机器人、人工智能、AGI、神经科学、哲学、心理学、认知科学、经济学、物理学、数学等角度展示了关于智能、科学和技术(在麻省理工学院及其他地方)本质的对话。
主持人: 莱克斯·弗里德曼
****发布时间表:每周一和周五
数据怀疑论者
“数据怀疑论者”播客的特点是采访和讨论与数据科学、统计学、机器学习、人工智能等相关的主题,所有这些都是从应用批判性思维和科学方法的角度来评估主张的真实性和方法的有效性。
主持人: 凯尔·波利奇
****发布时间表:每周周五
眼睛盯着艾
《关注人工智能》是一个双周播客,由《纽约时报》资深记者克雷格·史密斯主持。在每一集中,Craig 将与一些在这一领域有所作为的领导者交谈,将机器智能的增量进步放在更广泛的背景下,并考虑发展技术的全球影响。AI 即将改变你的世界,所以要关注。
主持人: 克雷格·史密斯
****发布时间表:每两周一次,每周三
链接: 苹果, Spotify , Google Play
线性题外话
线性离题是一个关于机器学习和数据科学的播客。机器学习正被用来解决大量有趣的问题,并实现甚至在短短几年前还遥不可及的目标。在每一集里,你的主持人通过有趣的(通常是非常不寻常的)应用探索机器学习和数据科学。
****发布时间表:每周一周一
实用 AI
本期播客的重点是让人工智能变得实用、高效,并且人人都可以使用。《实用人工智能》(Practical AI)是一个节目,技术专业人士、商界人士、学生、爱好者和专家嘉宾就人工智能和相关主题(机器学习、深度学习、神经网络等)进行热烈讨论。重点是所有人都可以访问的高效实现和真实场景。如果你想跟上人工智能的最新进展,同时在现实世界中保持一只脚,那么这就是给你的节目!
****发布时间表:每周一
说话的机器
会说话的机器是你进入机器学习世界的窗口。在每一期节目中,主持人会给你带来与该领域专家的清晰对话,对行业新闻的深刻讨论,以及对你的问题的有用回答。机器学习正在改变我们对周围世界的提问。在这里,我们探讨如何提出最好的问题,以及如何处理答案。
****张贴时间表:每周五两周一次
TWIML AI 播客
机器学习和人工智能正在极大地改变企业运营和人们生活的方式。TWIML AI 播客将来自人工智能和人工智能领域的顶级思想和想法带到了一个由人工智能/人工智能研究人员、数据科学家、工程师、精通技术的企业和 IT 领导者组成的广泛而有影响力的社区。
主持人 : 山姆·查林顿
****发布时间表:每周一和周四
链接: 苹果, Spotify , Google Play
AI 中的声音
这个节目的目标是捕捉这个独特的时刻,在这个时刻,一切似乎都有可能,无论是好的还是坏的。人工智能没有被过度炒作。乐观主义者和悲观主义者都相信一件事:人工智能将是变革性的。人工智能之声致力于记录这种转变。
主持人: 拜伦李斯
****发布时间表:每两周一次,每周四
链接: 苹果, Google Play ,装订机
2022 年 15 个流行的深度学习框架
随着人工智能技术的快速发展,越来越多的人开始关注深度学习。深度学习是人工智能的一种革命性方法,它使计算机能够从经验中学习,并以以前不可能的方式理解世界。它改变了我们使用电脑的方式,并迅速渗透到我们生活的方方面面。很难想象没有深度学习的世界会是什么样子,更难想象 2022 年的世界会是什么样子。这篇博文将着眼于最流行的深度学习框架,这些框架可以帮助你选择或开始你的深度学习之旅!
让我们开始吧!
张量流【1】
Tensorflow 是一个开源软件库,用于跨一系列任务的数据流编程。它是一个符号数学库,用于实现在 CPU 或 GPU 上运行的深度学习模型。它是免费软件,可以下载并在商业产品中使用,但不能修改。TensorFlow 最初是由谷歌人工智能团队的研究人员开发的,用作机器学习平台。后来,它被开源为通用机器学习工具,以 API 的形式提供,并可以在 Python 或 Go 等其他编码语言中实现。
自发布以来,TensorFlow 已经成为最受欢迎的深度学习框架。TensorFlow 灵活的架构允许您构建定制的深度学习模型,并使用其组件开发新的机器学习工具。它几乎是构建人工智能所需要的唯一工具。一般来说,如果计算需求得到满足(主要是 GPU),那么 TF 是必经之路。如今,从小型创业公司到财富 500 强公司,全世界的大公司都在使用 TensorFlow。
py torch【2】****
PyTorch 是一个流行的深度学习框架,用于构建神经网络。它基于 Torch,一个广泛支持机器学习算法的科学计算框架。PyTorch 用一个基于 Python、GPU 加速的动态翻译器取代了 Torch 的底层引擎。它是在 Apache 2.0 许可下发布的开源软件。PyTorch 主要用于深度学习模型,包括序列模型、强化学习模型、关系模型。它是用 Python 写的,它的 API 类似于 Tensorflow 和 Caffe 等其他深度学习框架。如果您正在为所有任务寻找一个框架,这是最好的选择。
虽然 TensorFlow 是机器学习的首选库,但 PyTorch 现在由于其灵活性和易用性而成为首选。虽然它很容易使用,但学习起来可能有点棘手,尤其是对初学者来说。然而,PyTorch 有强大的文档和社区支持!该框架被许多大公司使用,包括脸书、优步、Twitter、Snap 等。它是用 C++和 Python 编写的,支持 CUDA 和 OpenCL。它适用于 Linux、Mac OS X 和 Windows。
十四行诗【3】
DeepMind 的 Sonnet 或“Neural Talk”是一个开源的神经网络框架,可以让你快速训练大型深度神经网络。Sonnet 基于谷歌开源的机器学习框架 TensorFlow。它允许您以人类可读的方式定义和训练神经网络,并用简单的 Python 代码表达学习过程。它很容易设置和使用,不需要任何专业知识的 ano 或 TensorFlow。对于寻求开发、测试和部署机器学习模型但可能没有必要经验的深度学习研究人员来说,这是一个完美的框架。该框架可以在 GitHub 上的 Apache 2.0 下获得。
CNTK【4】
认知工具包(CNTK)是一个开源的统一深度学习工具包。它支持各种深度学习算法,并针对并行、分布式和多 GPU 训练进行了高度优化。该框架是更新、更有效的神经网络架构的实现。使用 CNTK,您可以构建和训练运行在 CPU 和 GPU 上的深度神经网络。该框架可以处理各种不同类型的神经网络,并附带其他预训练模型。商业组织在计算机视觉、语音识别、自然语言处理和预测分析中广泛使用它。该框架也被从事项目工作的人所使用,如开放人工智能和人类大脑项目。
这个框架由微软研究院、微软 AI 等开发,是微软 Azure AI 平台的一部分。Cognitive Toolkit 用于 2016 年年度 RoboSub 竞赛(潜艇物体检测)中获胜的 AI 系统,用于肿瘤学的 IBM Watson 健康解决方案中的临床决策支持系统,以及赢得 2018 年 DARPA Grand Challenge 的 Continental 自动驾驶解决方案。
韩忠
Caffe2 是由 Berkeley Vision and Learning Center(BVLC)和社区贡献者开发的深度学习框架。它基于最初的 Caffe 框架,支持最初的大部分功能。除了最初的 Caffe 的卷积神经网络(CNN)和长短期记忆(LSTM)组件之外,Caffe2 还添加了一个完全连接的神经网络(FCN)模块、一个自然语言处理(NLP)工具包和一个类似 VGG 的网络,用于处理图像、文本和视频数据。与其他框架相比,Caffe 是一个轻量级、模块化和可扩展的深度学习框架,为快速实验提供了易用性。Caffe2 适用于各种深度学习场景,包括图像识别、视频分析、语音识别、自然语言处理和信息检索。
眼下,Caffe2 项目与 PyTorch 联手,已经完全转移到 PyTorch 项目。
【MxNet】【6】****
MXNet 是 Apache MXNet 的新名称,这是一个广泛使用的深度学习框架,来自亚马逊,旨在提高效率和灵活性。它允许你混合符号和命令式编程来最大化效率和生产力。它既用于研究,也用于生产。它是一个可以在 Python、R、Scala、JavaScript 和 Julia 中使用的 C++库。MXNet 包括许多机器学习工具,如分类、回归、聚类和迁移学习。它有一个直观的 API,可以很容易地快速原型化深度学习应用程序。这是许多组织的热门选择,包括脸书、亚马逊、IBM、英特尔和微软。这使得它成为在各种语言和环境中构建 AI 系统的绝佳选择。
葡萄糖【7】****
Gluon 是一个基于 MXNet 构建的高级机器学习库,提供了一个简洁明了的 API 来开发和评估深度学习模型。该框架旨在使创建、训练和部署深度学习模型变得简单和直观,并消除了应用程序开发人员的低级命令式编程。它支持最先进的模型,如用于序列识别的门控循环单元(GRU)和长短期记忆(LSTM ),并且易于扩展。胶子是用 Java 编写的,有运行在 CPU 或 GPU 上的模型。它提供了一个统一的接口,使得数据科学家更容易在框架之间切换。Gluon API 在不同的框架中具有相同的签名,这使得它更容易学习和使用。可以在商业应用中自由使用,使用 Gluon 的应用在 Apache 2.0 许可下发布。
【8】
Theano 是一个 Python 库,可以进行数学运算,建立和训练深度学习模型。它最初由蒙特利尔大学开发,并于 2008 年作为开源软件库发布。它既可用于计算优化,也可用于构建神经网络。Theano 构建在 NumPy 之上,并根据 BSD 许可证进行许可。它是 python 最流行的框架之一,用于神经网络和深度学习的研究和实际实现。它可以在多个 CPU 和 GPU 处理器上运行,并且设计用于处理大范围的网络,因为它可以处理高达 2GB 的矩阵。它是深度学习中使用最广泛的框架之一,被 Airbnb、Dropbox、Pinterest 和 Spotify 等热门公司使用。
deep learning 4j【9】****
Deeplearning4j 是一个开源的分布式深度学习软件平台,用 Java 和 Scala 编写。它被设计成通用的、易于使用的和灵活的。它目前用于许多商业应用,包括大规模图像识别。它使用流行的 Java 编程语言在所有流行的计算平台上的 Java 虚拟机上运行。这意味着用户可以在他们已经拥有的服务器上部署 Deeplearning4j,而无需购买新的硬件。DL4J 可以在分布式 GPU 和 CPU 上的业务环境中使用。它允许通过其直观的 API 进行快速原型开发,其模块化架构允许插入新的深度学习模块。Skymind 公司开发了 Deeplearning4j。该公司还为 Deeplearning4j 社区提供高质量的培训材料,帮助用户入门。
【ONXX】【10】****
开放神经网络交换(ONNX)是由微软、脸书和社区开发的开源项目。该项目旨在为开发人员和研究人员提供一个平台和工具,在多个框架之间转换模型。更准确地说,它是一种标准,允许开发人员在框架之间,甚至在硬件和软件之间移动他们的模型。ONNX 格式不是一种新语言或新框架,而是一种表示深度学习模型的标准。一旦模型采用 ONNX 格式,它就可以被任何支持 ONNX 的框架读取。
它由一种独立于语言的格式组成,该格式捕获了表示现代神经网络所需的全部能力。ONNX 运行时以跨框架的形式提供了预训练的模型,几乎可以在任何地方运行。ONNX 是一个开放的社区,有许多来自学术界和工业界的贡献者,包括 Adobe、亚马逊、苹果、脸书、谷歌、IBM、英特尔、微软等等。
【11】********
Keras 是一个高级开源神经网络 API,用 Python 编写,能够在 TensorFlow 或 Theano 上运行。它的开发重点是支持快速实验。尽可能不拖延地从想法到结果是做好研究的关键。Keras 具有以下特点:其 API 与 NumPy 非常相似,易于与现有项目集成;它通过直观的差异化 API 提供自动差异化(反向传播);它将模型编译成高效、可移植的 C++代码。它可以同时在 Tensorflow 和 Theano 等多个后端上运行,这使得它非常灵活,允许用户根据手头的任务在后端运行时之间切换。如果你正在使用深度学习进行图像识别、自然语言处理或其他事情,这将无关紧要;Keras 是一个标准接口,你可以在所有这些方面使用。目前,它是世界上最受欢迎的深度学习库之一,Github 上有超过 53k 颗星星。
PyTorch 闪电【12】
PyTorch Lightning 是在 PyTorch 基础上构建的一个新框架,越来越受到研究人员的欢迎。该框架旨在使研究人员能够用 Python 编写代码,Python 是大多数研究人员首选的语言。该框架被设计为可以用于快速训练深度学习模型,这是深度学习领域的标准。Lightning 还可以构建您的 PyTorch 代码,因此它可以抽象训练的细节。这使得人工智能研究具有可扩展性,迭代速度快。这个框架附带了优秀的文档和有很多开源爱好的社区!
【13】********
H2O 是一个用于机器学习、预测分析和人工智能的开源软件平台。该框架旨在使机器学习对于可能不是该领域专家的人来说是可接近的。它由机器学习领域的领先软件公司 H2O.ai 提供支持。它支持重要行业的数据科学团队构建、部署和管理大规模预测模型。该框架包括机器学习算法、预构建的 ML 模型和内置的可视化工具,允许您快速为数据创建自己的模型。它使用一种特殊的架构,利用分布式计算来大规模处理数据。H2O 平台是免费的,允许用户在任何地点以任何规模对任何数据进行预测建模。
【卡尔迪】【14】
Kaldi 开源语音识别软件被全世界的学术界和工业界用来开发新的语音识别系统。该系统被设计成模块化、可升级和可扩展的。它是用 C++编写的,绑定了 Python、MATLAB、Java 和其他语言。Kaldi 已经被几家大公司用于语音识别研究和开发,包括微软、谷歌、苹果和 IBM。Kaldi 是在 Apache 2.0 许可下发布的。
【15】********
TensorFlow.js 是一个 JavaScript 库,用于在浏览器中训练和部署机器学习模型。它由 Google 开发,用 TypeScript 编写,在 Apache 2.0 开源许可下发布。它为 TensorFlow 提供了一个熟悉的、类似 Python 的编程接口,同时抽象出底层的 JavaScript 实现。它还提供了一个可视化的、基于浏览器的环境,用于创作、训练和运行机器学习模型,而无需编写任何代码。
它可以作为 D3/Three.js 等其他库的嵌入式替代,用于处理和显示数据,以及构建和训练机器学习模型。此外,TensorFlowJS 还支持在 Node.js 中甚至在 Raspberry Pi 上运行模型。
概要
我们希望你喜欢我们关于 2022 年顶级深度学习框架的帖子。这是一个非常令人兴奋的深度学习时代,新的框架定期维护。如果你目前正在为你的公司研究深度学习,一定要看看这个列表中的框架,看看哪一个最符合你的需求。如果您有任何问题,请随时给我们留言。感谢您的阅读!
芝加哥的一个独立创新中心正在弥合研究和生产机器学习之间的差距
1871 是芝加哥的一个非营利创新中心,汇集了在技术前沿建立公司的创始人、创新者和领导者。
我们很高兴能够与 1871 的常驻人工智能专家和长期合作伙伴 Alex Castrounis 坐在一起,谈论 1871 正在做的一些工作。我们特别感兴趣的是 1871 如何能够在学习机器学习的学生和提供机器学习应用的公司之间建立伙伴关系。
让我们开始吧!
paper space:1871 是芝加哥最大的企业孵化器。你能告诉我们一些关于这个组织的使命吗?
Castrounis : 1871 的存在是为了激励、装备和支持早期、成长期和企业创新者建立非凡的企业,从创意到财富 50 强。一个由创始人、领导者、创新者和支持者组成的庞大社区正在共同推动世界前进,塑造一个更加光明的未来。
1871 is the largest business incubator in Chicago.
paper space:在机器学习和 AI 方面,组织支持什么样的项目或工作?1871 为进入 ML 世界的学生提供了怎样的支持?
:1871 的独特定位是帮助新兴大学人才和企业之间架起一座桥梁,以解决实际问题,同时在芝加哥发展和留住多样化的技术领先的问题解决人才。
特别是在机器学习和人工智能方面,1871 与西北大学和东北伊利诺伊大学合作创建了一个人工智能学生咨询项目,以帮助在芝加哥培养更加多样化和熟练的劳动力,并推动合作伙伴公司的创新。该计划汇集了有才华的学生团队、该领域的专家和希望从事人工智能学习以及应用人工智能和机器学习解决现实世界问题的企业。
工作是由来自多所大学的学生组成的团队合作完成的,与传统的实习相反,这些团队更多地以服务提供商-客户式的咨询能力运作。这种模式允许学生直接与来自其他大学和合作公司利益相关方的学生一起工作,同时还发展许多现实世界的软技能和交付相关技能,这些技能在传统实习中通常不会遇到。
此外,1871 为学生提供定期编程和支持,帮助他们了解更多的技术世界,并在行业中寻找新的机会,包括为有抱负的程序员举办的技术挑战、为有抱负的企业家和技术专业人员举办的实习生&招聘会和校园。1871 还举办每月虚拟技术讲座,专家们在这里讨论新兴技术的最新进展,过去的话题包括机器学习、无人机等。
*
“我们提供了 Paperspace Gradient 平台作为开发资源,这使得该计划在时间和成本方面更加高效,同时也使学生能够直接投入到不受限制的开发工作中,而不是花大量时间使用 DevOps 和构建自定义云环境。”
亚历克斯·卡斯特罗尼斯,人工智能专家,1871 年*
paper space:关于 1871 计划,我们知道的一件事是大学的学生与公司匹配实习——在机器学习的背景下,这是否给了学生一点在应用 ML 工作的机会?让学生了解真实世界的机器学习问题和应用?
Castrounis :作为计划的一部分,1871 与合作伙伴公司密切合作,提供一份结构良好的问题陈述以及相关的特定领域上下文和资源。给出的问题是公司想要解决的实际问题,并且被特别选择作为人工智能和机器学习解决方案的强有力候选。然后,学生团队在整个项目中进行 AI/ML 研究和动手解决方案开发。
这个机会远远超出了在应用 ML 工作的学生。我们让学生团队进行高级人工智能研究,同时直接应用一些最先进的人工智能/人工智能模型和技术来解决问题,包括使用自我监督学习、transformers、LSTMs、XGBoost 和其他高级机器学习技术。
在许多情况下,学生已经能够训练和优化在给定问题或任务上表现相对较好的模型,并且已经能够勾画出路线图,为项目群组之外的持续项目开发提供建议。
paper space:学生需要什么样的资源和工具才能成功?为什么通常很难向学生提供计算机访问?
Castrounis :拥有“正确的”数据对于几乎任何人工智能/人工智能项目的成功都至关重要。每个群组工作的重要部分都是围绕为下游机器学习训练和建模工作收集和准备数据。
除了数据之外,该计划努力做到工具和平台不可知,我们相信使用团队最熟悉的工具和/或最好地完成工作。也许最重要的是,我们希望确保团队不会受到缺乏数据存储或计算相关资源的限制。
因此,我们提供了 Paperspace Gradient 平台作为开发资源,这使得该计划在时间和成本方面更加高效,同时也使学生能够直接投入到不受限制的开发工作中,而不是花大量时间使用 DevOps 和构建自定义云环境。
由于多种原因,为学生提供计算访问可能会很困难,尤其是当与来自不同大学的协作学生团队合作时,每个大学通常可能会使用不同的工具和内部系统。
在某些情况下,学生可能只能通过他们的大学有限地访问大型云提供商服务,尽管此类资源的设置和管理通常非常耗时,需要开发运维相关的专业知识和经验,并且通常超出了计划的范围。
*
1871 helps early stage and growth stage companies with the resources and expertise they need to succeed.*
paper space:paper space 参与 1871 年的方式是为学生提供渐变笔记本。学生使用笔记本来深化工作的一些方式是什么?学生是主要在 Jupyter 笔记本上工作,还是在构建自己的开发环境?
:渐变平台的笔记本功能已经成为学生团队的一个很好的协作工具。它为基础环境提供了预安装的库和框架,有助于加速开发工作。学生还可以根据给定任务的需要轻松更改计算资源。
虽然大多数学生在笔记本上工作,因为它是快速原型和开发的一个很好的工具,但是我们也让学生主要使用在 Jupyter 环境之外执行的 Python 脚本。
***paper space***:关于 1871 生态系统我们还应该知道什么?
:作为一个独立的非营利创新中心,我们是美国唯一一家将创始人、领导者、投资者和导师聚集到一个单一、统一的社区的组织。我们提供产品、社区和资源,帮助成员达到新的创造力水平。迄今为止,我们的成员已经筹集了 35 亿美元的风险资本,创造了约 14,500 个工作岗位,850 多家校友公司仍在扩大规模。此外,虽然我们扎根于芝加哥,但我们的业务遍及全球,这意味着会员可以随时随地获得我们的支持和资源。
paper space:我们的读者参与 1871 的最好方式是什么?有申请流程吗?**
Castrounis :最好的参与方式是参加每周二举行的早期信息会议和每月举行的成长阶段信息会议,我们的团队将讨论你需要了解的关于 1871 的一切,它的好处,以及如何成为会员。
欲了解更多信息,请务必登录 1871 网站。
2020 年合成媒体指南
原文:https://blog.paperspace.com/2020-guide-to-synthetic-media/
合成媒体是一个令人兴奋的新研究领域,在过去的几年里取得了巨大的进步。这个领域有可能彻底改变我们创作和消费内容的方式。
这篇文章是关于不同类型的合成媒体的初级读本。然后,我们将更深入地研究合成视频,包括它的应用和对塑造该领域的最新研究的文献综述。
什么是合成媒体?
合成媒体包括人工生成的视频、语音、图像或文本,其中人工智能承担了部分(或全部)创作过程。这属于合成、人工或虚拟现实(照片逼真的 AR/VR)的更广泛的领域。这是一个非常新的和令人兴奋的空间。
Evolution of Media (Image credit: https://vriparbelli.medium.com/our-vision-for-the-future-of-synthetic-media-8791059e8f3a)
在过去的几年里,深度学习取得了重大的学术进步,而生成性对抗网络(GANs)加速了合成媒体的发展。这导致合成媒体的质量迅速提高,很快它可能就和传统媒体没什么区别了。
你为什么要在乎?
因为未来是合成的。
在这十年里,我们的日常生活中会出现大量合成媒体。合成媒体将特别在三个领域带来根本性的转变:媒体创作、许可和所有权以及验证。
在媒体创造中,合成媒体有能力彻底革新消费媒体的面貌,改变我们消费和创造的媒体。你可能会想,为什么会这样?合成媒体将大大加快创造力,缩小想法和内容之间的差距。它将带来新的交流和讲故事的方法。它将使内容生产民主化,让我们能够最大限度地发挥人类的创造力。
A Virtual Influencer, "Lil Miquela," Endorsing Prada
许可和所有权也将发生巨大变化。必须为合成视频和声音制定新的法律。传统上,演员的报酬来自他们的时间、外貌和个人品牌。在不久的将来,你将能够创作一部由布拉德·皮特主演的电影,而实际上他并不在片场。谁为此获得报酬——布拉德·皮特,还是幕后的技术人员?多少钱?
像 Icons8 和 T2 rose bud AI T3 这样的公司给予用户前所未有的力量,让他们在几分钟内创建自己多样的定制照片。这就不需要像 Shutterstock 或 Getty Images 这样的公司了。Icons8 还有一系列由人工智能生成的免版税音乐。
哪里有好的潜力,哪里就有滥用的潜力。我们需要小心这项技术,并验证所有类型的媒体。像 Deeptrace 和 T2 这样的公司致力于检测有害的合成视频。这将变得更具挑战性,因为真实和合成之间的界限变得模糊。我想象的现实是,每种媒体都将被打上水印和指纹,也就是说,拥有一个经过验证的标签,就像社交媒体上经过验证的账户一样。
The Verified Instagram Account of Zlatan Ibrahimović
我们正在步入一个激动人心的未来。
在下一部分,我将探索一些流行和值得注意的例子,这样你就可以对 2020 年的合成媒体前景有一个清晰的概念。
示例和应用
合成现实
Lil Miquela 等虚拟替身。Lil Miquela 是世界上最受欢迎的虚拟影响者,在 Instagram 上拥有 180 万粉丝。她为卡尔文·克莱恩(Calvin Klein)和欧莱雅(Loreal)等品牌拍摄广告,与贝拉·哈迪德和 J·巴尔文(他们是真正的名人)一起出现在视频中,她也有自己的音乐视频。 然而,她不是真正的 。Lil Miquela 是由来自 Brud 的虚拟特效艺术家团队创建的 3D 模型。像她这样的虚拟影响者(it?)已经变得非常有名,并将继续受欢迎。
Lil Miquela's Instagram
动画/游戏和混合现实
鉴于游戏和混合现实市场的增长速度,用于创建和编辑 2D 和 3D 动画的人工智能工具正在兴起,需求巨大。它们有可能彻底改变创造角色、场景和其他动画/虚拟元素的过程。RADiCAL 就是这样一家公司,它允许用户仅从 iPhone 视频中创建自己的 3D 动画。你通常需要一个非常昂贵的动作捕捉设备,配有紧身衣和动作捕捉相机,才能制作出激进分子提供的东西。想象一下能够将自己的舞蹈上传到堡垒之夜。
RADiCAL’s Awesome Tech
合成视频
目前最流行的合成视频类型是 Deepfakes 。这些本质上是面部交换,一个人的脸替换另一个人的脸(就像《T2》中尼古拉斯·凯奇在《川普》中的脸)。这是使用 GANs 完成的。不幸的是,Deepfakes 已经变得几乎臭名昭著,因为它们也可以用来做很多好事。这是在佛罗里达州圣彼得堡的大理博物馆里,萨尔瓦多·达利向人们问好的一个深赝品。像这样的事情通常需要你雇佣一个非常昂贵的 CGI 工作室,但是这里唯一的花费来自开发者和 GPU。
Jennifer Buscemi - One of the more popular Deepfake videos
另一种类型的合成视频涉及面部重现,其中源演员控制目标演员的面部。这给了我们不同的世界领导人演唱约翰·勒诺的《想象》和大卫·贝克汉姆讲 9 种不同的语言。
https://www.youtube.com/embed/KHMNPjkd5-0?feature=oembed
合成图像
图像(以及文本)是最早的合成媒体类型之一,2016 年和 2017 年, Pix2Pix 和 CycleGAN 在深度学习社区引起了轰动。他们应用的一些受欢迎的例子包括 Edmond De Belamy ,一幅由 Obvious AI 创作的画作卖出了近 50 万美元,以及人工生成的库存图像,这使得像 Shutterstock 和 Getty Images 这样的公司过时了。
Synthetic Stock photos from Rosebud AI. None of these models are real.
合成音频
从播客到智能音箱,世界正在见证不同形式的音频技术的激增。但是,这需要时间,金钱和努力(配音员,工作室,麦克风和处理等。)来记录任何东西。这使得人工语音技术如文本到语音转换(TTS)和语音克隆变得非常流行。例如,like . ai是一家很受欢迎的公司,它允许你克隆自己的声音来创建数字化身。
另一个密切相关的领域是合成音乐。像 Popgun.ai 和 Jukedeck (最近被抖音收购)这样的公司帮助用户使用人工智能创作音乐。像这样的技术将帮助每个人唱歌、演奏乐器、作曲和掌握音频,从而真正实现音乐创作的民主化。
我希望你现在对合成媒体的前景和每种人工生成媒体的应用有一个相当好的理解。在这一系列文章中,我将重点关注合成视频、图像和音频,这里我将特别关注合成视频。在下一节中,我将回顾塑造这一领域的重要论文。
合成视频-应用和研究综述
我将介绍合成视频的一些应用和例子,并总结一些使这项技术成为可能的论文。这不是一个详尽的列表,而是我认为最重要的申请和论文的列表。
涵盖的应用和出版物
1。面部交换(DeepFakes)
2。视频中的面部重现
3。语音/音频的面部再现
4.全身再现
- LumiereNet
- 大家现在跳舞
换脸(Deepfakes)
脸互换(DeepFakes)本质上是脸互换模型。大多数流行的开源软件都是基于 Autoencoder 的(SAE,HAE 等),很少有基于 GANs 的。
这背后的技术是什么?
没有关于 DeepFakes 的正式或开创性论文,因为它们不是来自任何实验室。这里有一个技术背后的解释。让我们以广受欢迎的詹妮弗·布斯米为例。假设我们有他们俩的视频,詹妮弗·劳伦斯和史蒂夫·巴斯米。我们想把史蒂夫·巴斯米的脸(B 面)放到詹妮弗·劳伦斯的脸(A 面)上。我们有两个自动编码器,每个都有一个。
培养
两个自动编码器被单独训练,它们共享同一个编码器,但具有不同的解码器。公共编码器用两个面来训练,但是面 A 的解码器仅用 A 的面来训练;解码器 B 仅用 B 的面部来训练。
Training phase
产生
现在,为了生成一个史蒂夫·巴斯米的脸(脸 B)在詹妮弗·劳伦斯的脸(脸 A)上的深度假像,我们将詹妮弗·劳伦斯的视频传递到编码器中,而不是试图从编码中重建她的脸,我们现在将它传递到解码器 B 以重建史蒂夫的 Buscemi 的脸。最终的结果是史蒂夫·巴斯米的脸出现在詹妮弗·劳伦斯的视频上。
Generation
注意:DeepFakes 已经获得了相当多的恶名,在世界上的一些地方是非法的(例如, DeepFakes 在中国被禁止)——可以说是有充分的理由的。我觉得列举一些检测 DeepFakes 的努力是很重要的:
- https://deepfakedetectionchallenge.ai/
- 人脸取证
- DeepTrace 由一群弗吉尼亚大学的毕业生创立,是这个领域最有名的公司。
视频中的面部重现和视觉配音
面部重现是使用源演员的面部来控制目标演员的面部的过程(它实际上是使用任何源来控制面部,其中向量可以是面部、音频或文本。我将在这一节介绍视频驱动的重现,在下一节介绍音频驱动的重现)。人脸再现模型将姿态、旋转和表情从一张脸转移到另一张脸。这使得各种应用成为可能,从创建照片般逼真的虚拟化身,允许名人无缝地用多种语言交谈,以及颠覆视频制作行业的力量。
Samsung AI animating portraits
什么是视觉配音?
视觉配音是面部重现的一个特殊实例,旨在改变目标演员的嘴部动作以匹配新的音轨,通常由配音演员用外语说出。这主要用于通过将演员的嘴唇与配音同步来提高配音质量。这方面最好的例子就是 Syntheisa.io 让贝克汉姆说 9 种语言。
视觉配音可以是表演驱动的(目标由源演员的动作控制)或语音驱动的(音频驱动)。
Face 2 Face:RGB 视频的实时人脸捕捉与重现(CVPR 2016) 【论文】
一段时间以来,计算机视觉社区一直对面部重现感兴趣,通常使用 RDB-D 传感器(如 Kinect)或使用标记设置来实现。来自 TUM 视觉计算小组的这篇论文在 2016 年首次使用源序列对单目目标视频(如 Youtube 视频)进行实时面部重现,该源序列也是单目视频流,使用商用网络摄像头实时捕捉。目标是由源演员制作目标视频的面部表情动画,并以照片般逼真的方式重新渲染经过处理的输出视频。
方法
Method Overview
PCA 模型用于参数化人脸。前两个维度代表面部特征,即几何形状和皮肤反射率,第三个维度控制面部表情。
该模型首先重建目标演员的形状身份。
- 该模型对源视频和目标视频进行处理,通过源视频和目标视频之间快速有效的变形传递来实现再现。从目标序列中,最匹配的口腔内部被检索并扭曲以产生精确的匹配。
- 为了创建最终图像,他们使用传递的表情(使用传递的表情系数)重新渲染目标的面部,并将其与目标视频的背景合成(混合)。重新渲染还考虑了目标场景中的估计照明。(参考能量/损耗公式的文件)
嘴唇和牙齿的嘴区域通常很难合成,导致渲染不一致。他们通过引入一种新的嘴部合成方法来克服这一问题,该方法通过从离线样本序列中检索和扭曲最佳匹配的嘴部形状来生成逼真的嘴部内部,并且他们保持目标嘴部形状的外观。
结果
本文以 1280×720 的分辨率在各种目标 Youtube 视频上展示了高度逼真的再现示例。结果是定性的。
这里有一个视频有更多的结果。
深度视频人像(SIGGRAPH 2019)【arxiv】
这是一篇有趣的论文,早在 2018 年就在社区中掀起了波澜。与其他仅限于面部表情操作的方法相比,它们是第一个传输完整的 3D 头部姿势、面部表情、眼睛凝视和眨眼的方法。他们也是第一个合成目标人物上半身照片级逼真视频的人,包括逼真的服装和头发。(视频人像是一个人的头部和上半身的视频)。该方法的核心是一个具有新颖时空结构的生成神经网络。(任何时空都处理视频数据,因为视频本质上是随时间变化的图像)。该网络将参数人脸模型的合成渲染作为输入,基于该合成渲染,它为给定的目标演员预测照片般逼真的视频帧。
在 Face2Face 中,只有面部表情可以真实地修改,但不是完整的 3D 头部姿势,也没有一致的上半身/不断变化的背景。
方法
该论文将视频肖像合成和再现公式化为渲染到视频转换任务。
首先,使用人脸重建方法跟踪源和目标演员,该方法使用参数化人脸和光照模型。所得的低维参数向量序列表示每个视频帧的演员身份、头部姿势、表情、眼睛凝视和场景照明。这允许他们将头部姿势、表情和/或眼睛注视参数从源传送到目标。
在下一步中,他们基于修改的参数生成目标演员的新的合成渲染。生成三个不同的条件输入:彩色再现、对应图像和眼睛凝视图像。
这些渲染作为他们的新渲染到视频翻译网络的条件输入,该网络经过训练,可以将合成输入转换为照片级的输出。
该方法的核心是一个专门为视频人像合成定制的条件生成对抗网络(cGAN) 。cGAN 的生成器是一个渲染到视频的翻译网络。渲染到视频转换网络是一个时空编码器(即视频编码器),带有一个图像解码器,它以新的合成渲染为条件。渲染到视频的转换是以对抗的方式训练的。
鉴别器 D 试图更好地将给定的图像分类为真实的或合成的,而渲染-翻译网络 T 试图在愚弄鉴别器方面进行改进
The cGAN
结果
查看项目网站了解更多定性结果。
这里还有一些我觉得有趣的论文。
- VDub:修改演员的面部视频以进行逼真的视觉配音(EUROGRAPHICS 2015)【arxiv】
- 重演:通过边界转移学习重演人脸(ECCV 2018)【arxiv】【代码】
- 延迟神经渲染:使用神经纹理的图像合成(2019)【arxiv】
- 现实神经说话头部模型的少数镜头对抗学习(ICCV 2019)【arxiv】【代码
- GANs 的逼真语音驱动的面部动画(2019)【arxiv】
语音/音频的面部再现
来自文本或语音的面部动画基本上是使用文本或语音来控制面部。严格来说,这属于面部重现,但为了简单起见,我根据源驱动程序将其分为几类——源可以是源演员的视频(或者在这种情况下是音频)。
由于(1)将一维信号映射到(3D)时变图像的技术挑战,(2)而且由于人类对嘴部区域的细微细节极其敏感,所以从音频生成嘴部视频的问题相当困难。
语音/音频的面部重现首先由 Bregler 等人提出,他们演示了如何在视频中“重写”一个人的嘴唇运动,以匹配一个新的音频轨道,该音频轨道表示为一个音素序列。从那时起,已经有了相当多的进展,我涵盖了 2 个重要的文件。
合成奥巴马:从音频中学习对口型(2017)【arxiv】【代码】
与上一节中的文章相比,这是一篇相当直截了当的文章。这也是相当有影响力的一个。Supasorn(第一作者)最后做了一个关于他作品的 TED 演讲。
给定巴拉克·奥巴马总统的音频,本文合成了一个高质量的视频,他用准确的口型同步说话,合成到一个目标视频剪辑中。他们在他每周演讲的许多小时(17 小时)的镜头上训练了一个递归神经网络,该网络学习从原始音频特征到嘴型的映射。使用每个时刻的嘴形,他们合成高质量的嘴部纹理,并将其与适当的 3D 姿势合成(混合)。
方法
这种方法是基于从嘴巴周围区域的音频合成视频,并使用合成技术从其他库存镜头中借用头部和躯干的其余部分。递归神经网络用于从在数百万视频帧上训练的音频合成口型。
输入音频轨道是源,目标视频是一个库存视频剪辑,它们将合成的嘴部区域合成到其中
为了使问题变得简单,该论文集中于合成面部与语音最相关的部分。对于总统演讲片段来说,奥巴马演讲的内容与嘴周围的区域(嘴唇、脸颊和下巴)关联最强,也与头部运动的方面关联最强——当他暂停演讲时,他的头部停止运动。因此,他们专注于合成他嘴周围的区域,并从素材中借用奥巴马的其他部分(眼睛、头部、上身、背景)。
整体管道工程如下(如图所示):
- 给定奥巴马的音频,提取音频特征以用作递归神经网络的输入,该神经网络为每个输出视频帧输出稀疏嘴形
- 根据稀疏的嘴部形状,合成嘴部和脸部下部区域的纹理。然后将嘴部纹理混合到修改后的原始视频上,使得头部运动看起来自然,并且与给定的输入语音相匹配
- 在混合过程中,下颌线被扭曲以匹配新说话者的下巴,并且面部被合成为原始姿势中的目标帧。
结果
看这个视频定性结果。
https://www.youtube.com/embed/9Yq67CjDqvw?feature=oembed
神经语音木偶:音频驱动的面部重现(2019)【arxiv】
这篇论文于 2019 年 12 月发表,是当前音频驱动面部重现的 SOTA。他们的方法适用于不同的人,允许他们将目标演员的视频与任何未知源演员的声音合成,甚至可以利用标准的文本到语音转换方法生成合成声音。神经语音木偶是一种易于使用的音频到视频翻译工具,不需要单个目标视频的大量视频镜头或任何手动用户输入。目标视频相对较短(2-3 分钟),非常棒。
方法
神经语音木偶管道由两个主要部分组成-一个通用网络和一个专用网络。
广义网络(Audio2Expression Net)预测潜在的表达向量并跨越音频表达空间。为了确保多人之间的通用性,潜在音频表达空间由所有人共享。音频表达式被解释为 3D 人脸模型装备的 【混合形状】 系数。这个脸部模型装备是个人专用的,并且在管道的第二部分中被优化。用于训练 Audio2ExpressionNet 的数据集包括 116 个平均长度为 1.7 分钟的视频(总共 302750 帧)。选择训练语料库,使得人们处于中立的情绪中(德国公共电视台的评论员)。
专业阶段是第二阶段,捕捉目标人物的特质,即面部动作和外貌。由于每个人都有自己的谈话风格,因此,不同的表达,本文建立了个人特定的表达空间,可以计算每个目标序列。在 2-3 分钟的短视频序列上对空间进行训练(相比之下,最先进的方法需要几个小时)。
通过从音频表情空间(第一阶段)到特定于个人的表情空间(第二阶段)的映射来实现面部再现。给定估计的表情(再现)和提取的音频特征,应用新的延迟神经渲染技术来生成最终的输出图像。(查看这篇论文了解更多关于神经纹理和延迟神经渲染的信息)
由于基于音频的表情估计网络在多人中推广,它可以应用于看不见的演员。然而,用于新目标视频的特定于个人的渲染网络是从零开始训练的。
结果
结果很大程度上是定性的,所以最好看看他们网页上的视频。这里有几个数字。
本文展示了优越的视觉和唇同步质量相比,其他人,是目前的 SOTA。
这里有更多有趣的文章和一些代码实现。
- 你说的?-从音频合成说话人脸的视频 (2017) -音频驱动的面部重现中的开创性论文【arxiv】【代码】
- ATVGnet -分层跨模态说话人脸生成带动态逐像素丢失(CVPR 2019)【arxiv】CODE】
- ****基于文本的正在说话的头部视频编辑(2019)【arxiv】——来自斯坦福的优秀论文,允许你编辑视频中的音频,并制作一个新的视频。你可以期待很快在 After Effects 中看到这项技术。
**## 全身再现
这似乎还没有一个正式的名称,所以我称之为全身重演。它类似于面部重现,然而在这里,目标演员的整个身体是由一个来源以照片般逼真的方式重现的。
LumièreNet:由音频合成的讲座视频(2019)【arxiv】
这是一篇来自 Udacity AI 的非常有趣的论文,它可以从音频中生成逼真的教练视频。讲师制作的讲座视频在 MOOCs 中非常受欢迎,但拍摄一个视频需要相当多的资源和流程(即讲师、工作室、设备和制作人员),并需要大量时间。如果我们可以从现有的视频片段中生成一个新的讲座视频,由文本或音频驱动,会怎么样?这将使视频制作非常灵活,没有必要重新拍摄每个新的视频
方法
他们引入了一种基于姿势估计的潜在表示作为中间代码来合成教练的面部、身体和背景。他们从提取的人体姿势中为一个主题设计这些紧凑而抽象的代码。
LumièreNet 由三个神经网络模块组成:BLSTM 模型、VAE 模型和 SeqPix2Pix 模型。
- BLSTM 模型首先将提取的音频特征 x 与中间潜在代码 z 相关联
- 然后,VAE 解码器从 z 构建相应的姿态图形 w
- 最后,SeqPix2Pix 模型产生给定 w 的最终视频帧 y。
- 在训练过程中,LumièreNet 学习了 VAE 模型,通过编码器和解码器为高维密集图像设计紧凑和抽象的潜在代码 z。
结果
看看这个视频的定性结果。结果不是最好的,有明显的视听差异。然而,这是对 EdTech 空间的一个很好的补充,我很高兴看到接下来会发生什么。
大家现在跳舞(ICCV 2018)【arxiv】【代码】
这篇受欢迎的论文介绍了一种简单的“像我一样做”运动转移方法:给定一个人跳舞的源视频,他们在目标主体表演标准动作几分钟后将该表演转移到目标。他们使用姿势作为中间表示,将这个问题作为视频到视频的翻译来处理。为了传递运动,从源主体提取姿态,并且应用学习的姿态到外观的映射来生成目标主体。
预测两个连续的帧以获得时间上连贯的视频结果,并且引入了用于真实人脸合成的独立流水线(FaceGAN)。虽然这种方法非常简单,但它产生了令人惊讶的令人信服的结果(见视频)。
方法
训练- 模型使用姿态检测器 P 从目标对象的视频帧中创建姿态简笔画。与对抗鉴别器 D 一起学习映射 G,对抗鉴别器 D 试图区分“真实”对应(xt,xt+1),(yt,yt+1)和“虚假”序列(xt,xt+1),(G(xt),G(xt+1))。转移-姿势检测器 P 用于获得源人的姿势关节,这些姿势关节通过归一化过程(Norm)被转换成为其创建姿势简笔画的目标人的关节。然后应用经过训练的映射 G。
传送 -姿态检测器 P 用于获得源人的姿态关节,这些关节通过标准化过程(Norm)被转换成目标人的关节,为目标人创建姿态简笔画。然后应用经过训练的映射 G。
定性结果
https://www.youtube.com/embed/PCBTZh41Ris?feature=oembed
更多有趣的论文:
我对我们即将进入的未来非常兴奋。评论/有问题联系我!
延伸阅读
- https://www . axios . com/synthetic-realities-fiction-stories-fact-misinformation-ed 86 ce 3b-f1 a5-4e7b-ba86-f87a 918d 962 e . html
- 斯坦福计算视频处理课程 -优秀资源
- https://betaworksventures.com/campv1——第一个合成媒体加速器。非常令人兴奋的东西。**
新功能:双因素授权
https://www.youtube.com/embed/8u3MdPNHDcM
我们很高兴地宣布,双因素现在可以在所有的纸张空间帐户。作为我们不断努力使您的 Paperspace 体验尽可能安全的一部分,我们一直在倾听客户的需求。
只需登录您的帐户,进入您的设置面板。从那里你可以很容易地启用或禁用双因素。对于团队客户,我们正在推出要求所有团队成员使用双因素以及增强的日志记录和警报功能,以应对这些设置的变化。
4K 流媒体登陆最新的 Paperspace 版本!
原文:https://blog.paperspace.com/4k-streaming-comes-to-paperspace/
我们很高兴地宣布,Paperspace 原生应用程序现在支持所有专用 GPU 虚拟机上的 4K 流。
注 : 4K 流媒体仅在最新版本的 Paperspace 应用中可用。在这里下载 app:https://www.paperspace.com/app
以下是启用 4K 流的快速指南:
- 打开 Paperspace 应用程序,找到设置菜单
Locate the settings menu in the upper righthand corner of the application
2.在显示设置中切换缩放选项,并确保选择了 1x 。
Make sure the Zoom setting for the monitor is set to 1x
3.检查虚拟机上的显示设置,并确认分辨率符合您对客户端显示器的期望。
You should now see resolutions at or close to the maximum supported by your client monitor
常见问题:
我需要什么来启用 4K?
- 你需要一个 4K 或更高的显示器和一个 GPU 驱动的 Paperspace 机器。
4K 在基于浏览器的 Paperspace 版本中可用吗?
- 还没有。4K 仅在本机应用程序中可用。
支持 5K、6K、8K 分辨率吗?
- 还没有。
我如何确保 4K 正常工作?
- 打开虚拟机上的显示设置。分辨率接近 4K 吗?如果没有,打开 Paperspace 应用程序设置,确保缩放被调低至 1x 。
NLP 的 6 个有趣的深度学习应用
原文:https://blog.paperspace.com/6-interesting-deep-learning-applications-for-nlp/
先进的深度学习方法正在为特定的 ML 问题取得非凡的结果,即描述图像和将文本从一种语言翻译成另一种语言。最有趣的是,单个深度学习模型可以学习词义并执行语言任务,避免了执行复杂语言任务的需要。
近年来,各种深度学习模型已被应用于自然语言处理(NLP),以改善、加速和自动化文本分析功能和 NLP 功能。此外,这些模型和方法为将非结构化文本转换为有价值的数据和见解提供了卓越的解决方案。
继续阅读,发现深度学习方法正在自然语言处理领域得到应用,为大多数语言问题实现了最先进的结果。
1。标记化和文本分类
记号化包括将单词分割成机器可以理解的片段(或记号)。英语文档很容易标记,因为它们在单词和段落之间有清晰的空间。然而,大多数其他语言提出了新的挑战。例如,像粤语、普通话和日语汉字这样的标识语言可能具有挑战性,因为它们在单词甚至句子之间没有空格。
但是所有的语言都遵循一定的规则和模式。通过深度学习,我们可以训练模型来执行标记化。因此,大多数 AI 和深度学习课程都鼓励有抱负的 DL 专业人士尝试训练 DL 模型,以识别和理解这些模式和文本。
此外,DL 模型可以分类和预测文档的主题。例如,深度卷积神经网络(CNN)和递归神经网络(RNN)可以使用找到单词向量值的单词嵌入来自动分类源文本的语气和情感。大多数社交媒体平台部署了基于 CNN 和 RNN 的分析系统来标记和识别其平台上的垃圾内容。文本分类也应用于网络搜索、语言识别和可读性评估。
2.为图像生成标题
使用自然语句自动描述图像的内容是一项具有挑战性的任务。图像的标题不仅应该识别其中包含的对象,还应该表达它们之间的关系以及它们的属性(视觉识别模型)。此外,语义知识必须用自然语言来表达,这也需要语言模型。
对齐视觉和语义元素是生成完美图像字幕的核心。DL 模型可以帮助使用正确的英语句子自动描述图像的内容。这可以帮助视障人士轻松访问在线内容。
谷歌的神经图像字幕生成器(NIC)是基于一个由视觉 CNN 和语言生成 RNN 组成的网络。该模型自动查看图像,并用简单的英语进行描述。
3。语音识别
DL 正越来越多地用于建立和训练神经网络,以转录音频输入并执行复杂的词汇语音识别和分离任务。事实上,这些模型和方法被用于信号处理、语音学和单词识别,这些都是语音识别的核心领域。
例如,DL 模型可以被训练来为相应的说话者识别每个语音,并分别回答每个说话者。此外,基于 CNN 的语音识别系统可以将原始语音翻译成文本消息,从而提供与说话者相关的有趣见解。
4.机器翻译
机器翻译(MT)是自然语言处理中的核心任务,研究在没有人类干预的情况下使用计算机翻译语言。只是最近深度学习模型才被用于神经机器翻译。与传统的机器翻译不同,深度神经网络(DNN)提供了准确的翻译和更好的性能。RNNs、前馈神经网络(FNNs)、递归自动编码器(RAE)和长短期记忆(LSTM) 用于训练机器将句子从源语言准确地转换成目标语言。
合适的 DNN 解决方案被用于诸如单词对齐、重新排序规则、语言建模和连接翻译预测的过程,以在不使用大型规则数据库的情况下翻译句子。
5.问题回答
问答系统试图回答以问题的形式提出的询问。因此,在用自然语言提出的其他类型的问题中,定义问题、传记问题和多语言问题都由这样的系统来回答。
创建一个功能齐全的问答系统一直是 DL 领域的研究人员面临的普遍挑战之一。尽管深度学习算法过去在文本和图像分类方面取得了不错的进展,但它们无法解决涉及逻辑推理的任务(如问答问题)。然而,在最近一段时间,深度学习模型正在提高这些 QA 系统的性能和准确性。
例如,递归神经网络模型能够正确回答传统方法无法回答的段落长度问题。更重要的是,DL 模型是以这样一种方式训练的,即不需要像创建语义解析器那样使用语言学知识来构建系统。
6。文档摘要
当今日益增长的可用数据量使得文档摘要的作用变得至关重要。序列到序列模型的最新进展使得 DL 专家很容易开发出好的文本摘要模型。两种类型的文档摘要,即摘要和抽象摘要,可以通过序列到序列模型来实现。参考下图,来自 Abigail 的指针生成器博客,参见。
在这里,编码器 RNN 读取源文本,产生一系列编码器隐藏状态。接下来,解码器 RNN 接收摘要的前一个单词作为输入。它使用该输入来更新解码器隐藏状态(上下文向量)。最后,上下文向量和解码器隐藏状态产生输出。这种序列到序列的模型,其中解码器能够以任何顺序自由地生成单词,是抽象摘要的强大解决方案。
总结
语言建模领域正在迅速从统计语言建模转向深度学习方法和神经网络。这是因为 DL 模型和方法在复杂的 NLP 任务上保证了优越的性能。因此,深度学习模型似乎是完成 NLP 任务的一种好方法,这些任务需要对文本有深刻的理解,即文本分类、机器翻译、问题回答、摘要和自然语言推理等。
这篇文章将帮助你理解 DL 模型和方法在自然语言处理中日益增长的作用。
(特征图片来源: Pixabay )
显卡的全面剖析:NVIDIA A100 案例研究
原文:https://blog.paperspace.com/a-complete-anatomy-of-a-graphics-card-case-study-of-the-nvidia-a100/
在本文中,我们将对显卡背后的技术、它们的组件、架构以及它们与机器学习的关系进行技术检验。
图形卡的任务非常复杂,但是它的概念和组件很容易理解。我们将看看视频卡的基本组件以及它们的功能。在每个阶段,我们将使用 NVIDIA A100 - 40 GB 作为显卡当前技术水平的示例。A100 可以说是市场上可用于深度学习的最佳单个 GPU。
显卡故障
图形卡,通常称为视频卡、图形适配器、显示卡或显示适配器,是一种处理数据并产生图形输出的扩展卡。因此,它通常用于视频编辑、游戏和 3D 渲染。然而,近年来,它已经成为机器学习应用程序和加密货币挖掘的首选。显卡在以下组件的帮助下完成这些高要求的任务:
- 图形处理单元
- 数据处理股(DPU)
- 视频存储器(VRAM)
- 视频 BIOS (VBIOS)
- 电压调节器模块
- 主板接口
- 互连接口
- 网络接口和控制器
- 输出接口
- 冷却系统
NVidia A100 hardware breakdown (source)
图形处理单元
经常被误认为是显卡本身。与计算机的 CPU 不同,GPU 旨在处理图形渲染所需的更复杂的数学和几何计算。平均而言,与普通 CPU 相比,GPU 拥有更多晶体管和更高密度的计算核心,以及更多算术逻辑单元(ALU)。
这些装置有四种分类:
- 流式多处理器(SMs)
- 加载/存储(LD/ST)单位
- 特殊功能单位(SFU)
- 纹理映射单元(TMU)
1)****流式多处理器** (SM)是一种执行实体,由一组共享寄存器空间、共享内存和 L1 缓存的内核组成。SM 中的一个内核可以同时执行多个线程。谈到 SM 的核心,有两个主要竞争对手:**
- 通过 NVIDIA 计算统一设备架构(CUDA)或张量内核
- 按和划分的流处理器
一般来说,NVIDIA 的 CUDA 内核和 Tensor 内核被认为更加稳定和优化,特别是对于机器学习应用程序。CUDA 内核已经出现在过去十年中发布的每一款 Nvidia GPU 上,但张量内核是一个较新的补充。张量核在计算上比 CUDA 核快得多。实际上,CUDA 内核每个时钟周期只能做一次运算,而张量内核每个周期可以做多次运算。就准确性和处理速度而言,CUDA 核心对于机器学习模型来说不如张量核心强大,但对于某些应用来说绰绰有余。因此,这些是训练机器学习模型的最佳选择。
**
NVIDIA A100 Streaming Multiprocessor’s core architecture (Source)**
这些内核的性能以 FLOPS 为单位(每秒浮点运算次数)来衡量。对于这些测量,NVIDIA A100 获得了破纪录的数值:
核心 | 规格 |
---|---|
FP64 | 9.7 万亿次浮点运算 |
FP64 张量核心 | 19.5 万亿次浮点运算 |
FP32 | 19.5 万亿次浮点运算 |
FP32 张量核心 | 156 万亿次浮点运算 |
BFLOAT16 张量核 | 312 万亿次浮点运算 |
FP16 张量核心 | 312 万亿次浮点运算 |
INT8 张量核 | 624 万亿次浮点运算 |
**根据 NVIDIA 文档,使用稀疏格式进行数据表示甚至可以帮助这些值翻倍。
在 A100 内部,高速缓存管理以一种特殊的方式完成,以使内核和 VRAM 之间的数据传输尽可能地快速和流畅。为此,A100 GPU 有三级高速缓存 L0、L1 和 L2:
L0 指令高速缓存专用于单个流式多处理器子处理块,L1 指令高速缓存专用于 SM,而 L2 高速缓存是统一的,在所有 SM 之间共享,并为指令和数据保留。A100 中的 L2 缓存比所有以前的 GPU 缓存都大,大小为 40MB,它充当 L1 专用缓存和 40GB HBM2 VRAM 之间的桥梁,我们将在本文后面详细介绍。
Cache hierarchy inside the NVIDIA A100 (40GB VRAM version) (Source)
-
加载/存储(LD/ST) 单元允许线程在每个时钟周期执行多次数据加载和存储到内存的操作。在 A100 中,这些单元引入了一种新的异步数据复制方法,这使得加载可以在线程之间全局共享的数据而不消耗额外的线程资源成为可能。这种新引入的方法使共享内存和本地缓存之间的数据加载时间增加了大约 20%。
-
特殊功能单元(sfu)高效地对矢量化数据执行结构化算术或数学功能,例如正弦、余弦、倒数和平方根。
-
纹理映射单元 (TMU)处理特定应用的任务,例如图像旋转、调整大小、添加失真和噪声以及移动 3D 平面对象。
数据处理股(DPU)
DPU 是显卡的非标准组件。数据处理单元是一种新推出的可编程处理器,它与 CPU 和 GPU 一起作为计算的三个主要组件。因此,DPU 是一个独立的处理器,通常在 ML 和数据中心实施。它提供了一套可管理的加速软件功能:网络、存储和安全性。A100 显卡板载了最新的 BlueField-2 DPU,在处理大规模多输入多输出(MIMO)工作负载、AI-on-5G 部署以及信号处理或多节点训练等更专业的工作负载时,可以提供巨大的优势。
NVIDIA A100 BlueField-2 Data Processing Unit architecture (Source)
视频存储器(VRAM)
从最广泛的定义来看,视频随机存取存储器(VRAM)类似于系统 RAM。VRAM 是一种由 GPU 使用的缓存,用于保存图形或其他应用程序所需的大量数据。所有保存在 VRAM 中的数据都是暂时的。传统的 VRAM 通常比系统 RAM 快得多。更重要的是,它在物理上靠近 GPU。它直接焊接到显卡的 PCB 上。这使得数据传输速度极快,延迟最小,允许高分辨率图形渲染或深度学习模型训练。
NVIDIA GeForce RTX 3050 VRAM Positionning (Source)
在当前的显卡上,VRAM 有各种大小、速度和总线宽度。目前,实现了多种技术;GDDR 和 HMB 有各自的变体。十多年来,GDDR (SGRAM 双倍数据速率)一直是行业标准。它实现了高时钟速度,但代价是物理空间和高于平均水平的功耗。另一方面,HBM(高带宽存储器)是 VRAM 技术的最新发展水平。它功耗更低,并且能够堆叠以增加内存大小,同时占用显卡的空间更少。它还允许更高的带宽和更低的时钟速度。NVIDIA A100 支持最新一代的 HBM 内存,HBM2e 的大小为 80GB,带宽高达 1935 GB/s,与上一版本 Tesla V100 相比增加了 73%。
电压调节器模块
它确保 GPU 在恒定的电压下接收必要的功率。一个低质量的 VRM 可能会产生一系列问题,包括 GPU 在压力下关闭,超频性能有限,甚至缩短 GPU 的寿命。图形卡从现代电源装置(PSU)接收 12 伏电压。另一方面,GPU 对电压敏感,无法维持该值。这就是 VRM 发挥作用的地方。它将 12 伏电源降至 1.1 伏,然后将其发送到 GPU 核心和内存。A100 及其所有 VRM 的功率级可以维持高达 300 瓦的功率输出。
A100 使用 8 针电源连接器从电源单元接收电力,然后将电流转发给 VRMs,VRMs 以 1.1 VDC 电流的形式向 GPU 和 DPU 供电,额定最大强制限制为 300 W,理论限制为 400 W
主板接口
这是插入系统主板的图形卡的子组件。正是通过这个接口,或“插槽”,图形卡和计算机交换数据和控制命令。在 21 世纪初,不同的制造商实现了许多类型的接口:PCI、PCIe、PCI-X 或 AGP。但是,现在 PCIe 已经成为几乎所有显卡制造商的首选接口。
PCIe 或 PCI Express 是外围组件互连 Express 的简称,是用于连接显卡、硬盘驱动器、主机适配器、固态硬盘、无线网络和其他以太网硬件连接的最常见的标准化主板接口。
PCI Express slot on a mother board (from up to down: x4, x16, x1 and x16), PCI 32bit on the bottom (source)
PCIe 标准经历了不同的发展阶段,每一代标准的速度和带宽都有大幅提升:
带宽 | Gigatransfer | 频率 | |
---|---|---|---|
PCIe 1.0 | 8gb/秒 | 2.5 燃气轮机/秒 | 2.5 千兆赫 |
PCIe 2.0 | 16 GB/秒 | 5 燃气轮机/秒 | 5 千兆赫 |
PCIe 3.0 | 32gb/秒 | 8 燃气轮机/秒 | 8 千兆赫 |
PCIe 4.0 | 64gb/秒 | 16 燃气轮机/秒 | 16 千兆赫 |
PCIe 插槽可以在不同的物理配置中实现:x1、x4、x8、x16、x32。该数字表示插槽中实现了多少个通道。我们拥有的通道越多,我们可以在显卡和主板之间传输的带宽就越高。NVidia A100 配备了 PCIe 4.0 x16 接口,这是市面上性能最高的一代接口。
互连接口
互连接口是一种总线,它为系统构建者提供了连接安装在单个主板上的多个图形卡的可能性,从而允许通过多个卡扩展处理能力。这种多卡扩展可以通过主板上的 PCIe 总线或作为数据桥的专用互连接口来实现。AMD 和 NVIDIA 都展示了自己的显卡,采用了专有的扩展方法,AMD 采用了 CrossFireX 技术,NVIDIA 采用了 SLI 技术。随着 NVLink 的引入,SLI 在图灵一代被弃用,NV link 被认为是多卡扩展技术的顶级产品。
NVLink data flow representation (Source)
NVIDIA A100 使用第三代 NVLink,可以在两个 GPU 之间提供高达 600 GB/s 的速度。此外,与 PCI Express 相比,它代表了一种在 GPU 之间传递数据更节能的方式。
网络接口
网络接口不是显卡的标准组件。它仅适用于需要将数据直接传输到 DPU 和 GPU 的高性能卡。在 A100 的情况下,网络接口由 2 个 100Gbps 以太网端口组成,允许更快的处理,特别是对于涉及基于人工智能的网络的应用。
输出接口
输出接口是构建在图形卡上的端口,使其能够连接到显示器。可以实现多种连接类型。
A female HDMI connector (Source)
对于旧系统,使用 VGA 和 DVI,而最近制造商倾向于使用 HDMI 和显示端口,而一些便携式系统将 USB Type-C 作为主要端口。
HDMI | 显示端口 | DVI | VGA | |
---|---|---|---|---|
数字或模拟 | 数字的 | 数字的 | 数字的 | 数字的 |
速度 | 340 兆赫 | 165 兆赫 | 165 兆赫 | 28 兆赫 |
音频支持 | 是 | 是 | 是 | 不 |
最大分辨率 | 7680 x 4320 像素(8K) | 7680 x 4320 像素(8K) | 3840 x 2400 像素 | 1920 x 1080 像素 |
至于本文中我们显微镜下的显卡,A100 没有输出接口。因为它从一开始就被设计为 ML/DL 的专业卡,并在数据中心使用,所以它没有理由有显示连接。
视频 BIOS (VBIOS)
视频 BIOS,通常称为 VBIOS,是显卡的基本输入输出系统(BIOS)。与系统 BIOS 一样,视频 BIOS 提供了一组与视频相关的信息,程序可以使用这些信息来访问图形卡,并维护供应商特定的设置,如卡名、时钟频率、VRAM 类型、电压和风扇速度控制参数。
冷却系统
散热通常不被视为显卡组件列表的一部分。但是,由于它的重要性,在这次技术深度潜水中不能忽视它。
由于显卡所消耗的能量,会产生大量的热能。此外,为了保持卡活动期间的性能并保持长期可用性,核心温度值应限制为卵形热节流,这是由于 GPU 和 VRAM 级别的高温导致的性能下降。
为此,主要使用两种技术:空气冷却和液体冷却。我们来看看 A100 使用的液体冷却方法。
NVIDIA A100 with liquid cooling (source)
冷却剂通过导热管进入显卡,并在通过系统时吸收热量。然后,使用液体泵将冷却剂拉向散热器,散热器充当管道中的液体和散热器周围的空气之间的热交换器。云 GPU 服务通常内置有监控温度的工具,如 Paperspace Gradient Notebook 的监控工具。这有助于防止过热,如果你正在运行特别昂贵的程序,作为一个警告系统。
如何衡量一块显卡的性能?
现在我们知道了显卡的主要组件和部件,我们将了解如何测量给定显卡的性能,以便与其他显卡进行比较。
要评估显卡,可以遵循两种方案:评估子组件的技术规格,并将它们与其他显卡的结果进行比较,或者对显卡进行测试(也称为基准测试)并比较分数。
基于规格的评估
显卡有几十种技术规格可以帮助确定其性能。我们将列出在根据这种评估方法做出选择时要考虑的最重要的因素:
内核数量:在查看显卡的潜在性能时,GPU 上的内核数量是一个很好的衡量指标。然而,当比较具有不同内核类型和架构的 GPU 时,这可能会给出有偏见的比较。
内核速度:表示内核每秒执行的单个基本计算的次数,单位为 MHz 或 GHz。构建个人系统时要寻找的另一个衡量标准是超频最大内核速度,它通常比非超频速度高得多。
内存大小:一块显卡的 RAM 越大,它在给定时间内可以处理的数据就越多。但是,这并不意味着通过增加 VRAM 就可以提高性能,因为这还取决于其他可能成为瓶颈的组件。
内存类型:同样大小的内存芯片,基于实现的技术,可以呈现不同的性能。HBM、HBM2 和 HBM2e 存储芯片的性能通常优于 GDDR5 和 GDDR6。
内存带宽:内存带宽可以被视为一种更广泛的评估显卡 VRAM 性能的方法。内存带宽基本上是你的卡的 VRAM 在任何时候都能被访问和使用的速度。
热设计功率(TDP): 显示了产生冷却系统能够处理的最大热量需要多少电力。构建系统时,TDP 是评估卡电源需求的重要因素。
基于基准的评估
虽然技术规格可以提供显卡与其他显卡相比的大致情况,但它并没有给出明确的量化比较方法。
进入基准,这是一个测试,给出一个可量化的结果,可以清楚地在卡之间进行比较。对于面向机器学习的图形卡,逻辑基准将是 ML 模型,该模型在要比较的卡之间被训练和评估。在 Paperspace 上,对核心或梯度上的所有可用卡执行了多个 DL 模型基准 (YOLOR、StyleGAN_XL 和 EfficientNet)。并且,对于每一个,基准测试的完成时间是使用的可量化变量。
剧透预警!A100 在所有三个基准测试场景中都取得了最好的结果。
基于基准的评估的优点是,它产生一个可测量的元素,可以简单地用于比较。与基于规格的评估不同,这种方法可以对作为统一系统的显卡进行更全面的评估。
为什么显卡适合机器学习?
与 CPU 相比,GPU 是从头开始构建的,用于处理大量数据和执行复杂任务。并行计算是 GPU 的另一个好处。虽然 CPU 制造商努力提高性能,但最近开始趋于平稳,GPU 通过根据特定需求定制硬件和计算安排来解决这一问题。这种并行计算中使用的单指令多数据(SIMD)架构使得在 GPU 核心之间有效地分配工作负载成为可能。
因此,由于机器学习的目标是增强和改善算法的能力,因此需要输入更大的连续数据集。更多的数据意味着这些算法可以更有效地从中学习,并创建更可靠的模型。显卡提供的并行计算能力可以促进复杂的多步骤过程,特别是深度学习算法和神经网络。
机器学习用的最好的显卡有哪些?
简而言之:NVIDIA A100 - 80GB 是目前最好的单个 GPU。
长回答:机器学习应用程序是 NVIDIA A100 架构的完美匹配,一般来说是 Ampere 系列的架构。进出 DPU 的流量将由 A100 GPU 核心直接处理。这开辟了一个全新的使用人工智能的网络和安全应用类别,如数据泄漏检测、网络性能优化和预测。
虽然 A100 是机器学习应用的核心选择,但更强的能力并不总是意味着更好。根据 ML 模型、数据集的大小、训练和评估时间限制,有时较低层的显卡可能就足够了,同时保持尽可能低的成本。这就是为什么拥有一个提供各种显卡的云平台对于一个 ML 专家来说很重要。对于每个任务,都有一个完美的武器。
请务必访问 Paperspace Cloud GPU 比较网站,找到您需要的 GPU 的最佳价格!A100 80 GB 目前仅在 Paperspace 的云中提供。
资源
https://www.nvidia.com/en-us/data-center/a100/
https://developer . NVIDIA . com/blog/NVIDIA-ampere-architecture-in-depth/
https://www . NVIDIA . com/en-in/networking/products/data-processing-unit/
6 个月深度学习实用指南
原文:https://blog.paperspace.com/a-practical-guide-to-deep-learning-in-6-months/
这篇文章将为你提供学习深度学习的详细路线图,并将帮助你在 6 个月内获得深度学习实习和全职工作。这篇文章是实用的,以结果为导向的,遵循自上而下的方法。它是针对时间紧张的初学者以及中级从业者的。
如果你一次又一次地学习 MOOC,像大多数其他教程一样,钻研数学和理论,你只能在 3 个月内建立起你的第一个神经网络。你应该可以很快造出一个来。这篇文章遵循两个阶段的策略,
- 获得深度学习的高层次想法:你做初级-中级水平的项目,做不涉及太多数学的课程和理论。
- 专注-在数学和理论上建立酷的东西+获得深度学习前景的高层次概述。
- 时间- 3 个月
- 深入深度学习:详细阅读关于数学和机器学习的内容。你将开始做一些雄心勃勃的项目,这些项目需要相当多的理论知识,并且需要更大的代码库和更多的功能。
- 专注的理论和更大的项目。
- 时间- 3 个月
先决条件
- 你懂基本编程。
- 对微积分、线性代数和概率有基本的了解。
- 你愿意每周花 20 个小时。
第一阶段
学习 Python
- 做 Python 速成班。对于 Python 初学者来说,这是一个很棒的资源,非常实用,并且是项目驱动的。它简明扼要。大量的乐趣与最佳实践和宝石。几乎涵盖了用深度学习构建事物所需的所有概念。
- 阅读 pep8 规则。知道如何正确地编写 python 并设计其风格是很重要的。
需要熟悉的重要包装:
- 数据角力
- 操作系统(用于文件管理)
- json(相当多的数据集都是 json 格式的)
- Argparse(用于编写简洁的脚本)
- Pandas(用于处理 csv 和其他表格数据)
- 绘图
- OpenCV
- Matplotlib
- 科学堆栈
- NumPy
- 我的天啊
时间:1 周
机器学习
- 在深入研究深度学习之前,必须对机器学习有一个很好的了解。
- 在 Coursera 上学习吴恩达的机器学习课程,直到第八周。第 9、10、11 周没有前 8 周重要。前 8 周涵盖必要的理论,第 9、10、11 周面向应用。虽然课程表上注明需要 8 周才能完成,但是 4-6 周完成内容还是很有可能的。课程很好,但是编程作业在 Octave。作为一名机器学习工程师/研究人员,你几乎不会使用 Octave,你的大部分工作肯定会用 Python 来完成。
- 要练习 Python 编程,就去做 Jake Vanderplas 的机器学习笔记本。它们包含对机器学习的很好的高层次概述和足够的 Python 练习,并向您介绍 scikit-learn,这是一个非常受欢迎的机器学习库。为此你需要安装 Jupyter Lab / Notebook,你可以在这里找到安装和使用说明。
- 至此,你应该对机器学习有了很好的理论和实践理解。考验你技术的时候到了。在 Kaggle 上做泰坦尼克分类挑战,摆弄数据,即插即用不同的机器学习模型。这是一个学以致用的好平台。
时间:4-6 周
深度学习
-
能够访问 GPU 来运行任何深度学习实验是很重要的。谷歌合作实验室有免费的 GPU 访问。然而,Colab 可能不是最好的 GPU 解决方案,并且众所周知经常断开连接,可能会滞后。有几个构建自己的 GPU 平台的指南,但最终这是一种干扰,会减慢你的速度。像 AWS 这样的云提供商提供 GPU 实例,但它们的设置和管理非常复杂,这也成为了一个干扰因素。像 Gradient (也包括负担得起的 GPU)这样的完全托管服务消除了这种头痛,因此您可以将所有精力集中在成为深度学习开发者上。
-
Do fast.ai V1 ,程序员实用深度学习。这是一门涵盖基础知识的非常好的课程。注重实施而非理论。
-
开始阅读研究论文。 这是一份很好的清单,列出了几篇早期和深度学习中的重要论文。它们涵盖了基本原理。
-
选择 Pytorch / TensorFlow 中的任意一个,开始建造东西。对你选择的框架感到非常舒服。积累丰富的经验,这样你就会变得非常多才多艺,并且知道这个框架的来龙去脉。
- PyTorch: 易于实验,不会花很长时间。有大量的教程和大量的社区支持(我的 goto 库),你几乎可以控制管道的每个方面,非常灵活。Fast.ai V1 会给你足够的 PyTorch 经验。
- TensorFlow: 学习曲线适中,调试困难。拥有比 PyTorch 更多的特性、教程和一个非常强大的社区。
- Keras 的很多功能都可以被打倒,而且很容易学习,但是,我总是发现它有太多的黑箱,有时很难定制。但是,如果你是一个寻求建立快速简单的神经网络的初学者,Keras 是聪明的。
-
****开始在你感兴趣的领域做项目。建立良好的形象。领域包括-对象检测,分割,VQA,甘斯,自然语言处理等。构建应用程序并对其进行开源。如果你在学校,找到教授,开始在他们的指导下做研究。以我的经验来看,公司似乎对研究论文和流行的开源库同等重视。
时间:4-6 周
现在,你应该,
- 对深度学习有很好的理解。
- 有 2-3 个深度学习的项目。
- 知道如何在一个流行的框架中舒适地构建深度学习模型。
你现在就可以开始申请实习和工作,这就足够了。大多数创业公司关心的是你能建立和优化一个模型有多好,以及你是否具备基本的理论知识。但是要想进入大公司,你需要深入研究,对数学和理论有很好的理解。
第二阶段
这就是事情变得有趣的地方。你更深入地钻研理论,从事更大、更有雄心的项目。
数学
数学是机器学习的基础,在面试中非常重要。确保你很好地理解了基础知识。
- 线性代数: 做 Ch。深度学习书之二。可以用吉尔伯特·斯特朗的麻省理工开放式课程作为参考。
- 微积分: 深度学习需要的矩阵微积分是非常好的相关资源。
- 概率: 阅读更多概率论与统计-hos sein Pishro-Nik 著《概率、统计与随机过程导论》。才华横溢。比起任何 MOOC 或教科书,我强烈推荐这个。坚实的理论,重点是简洁,足够的例子和解决方案的问题。接着用 Ch。深度学习书的 3。
- 优化: 这些来自 NYU 的课程笔记非常值得一读。Coursera 上的第 5 周机器学习数学也是一个非常好的资源。做 Ch。深度学习书籍之 4 固化你的理解。
机器学习
- 做某事。深度学习书的 5。这是一本浓缩读物。ML/DL 面试通常有 40-50%是关于机器学习的。
- 参考:Bishop——模式识别与机器学习(注意,这是一篇比较难的文字!)
深度学习
- 在 Coursera 上做深度学习专业化。有 5 道菜
- 神经网络和深度学习:深入主题,将是 fast.ai V1 的良好延续。
- 改进深度神经网络:超参数调整、正则化和优化:这可能是最重要的课程,涵盖了面试中常见的重要话题(批处理、退出、正则化等)
- 构建机器学习项目:这将教你建立一个 ML 模型,并给你一些实用的技巧。(如果时间紧迫,可以跳过并在以后完成)
- 卷积神经网络:本课程深入探讨了 CNN 的理论和实际应用。
- 序列模型:探索自然语言模型(LSTMs,GRUs 等)和 NLP,NLU 和 NMT。
- 继续致力于深度学习领域更大、更雄心勃勃的项目。将您的项目推送到 GitHub,并拥有一个活跃的 GitHub 个人资料。
- 了解更多关于深度学习的一个好方法是重新实现一篇论文。重新实现一篇受欢迎的论文(来自 FAIR、DeepMind、Google AI 等大型实验室)会给你很好的体验。
时间:3 个月
在这个阶段,你应该有很好的理论理解和足够的深度学习经验。你可以开始申请更好的角色和机会。
下一步做什么?
- 如果你有冒险精神,读一读 Bishop 的《模式识别和机器学习,对机器学习有一个非常好的理解。
- 阅读深度学习书的其余部分(Ch。六通道。12 覆盖相关位)
Protips
- 浏览 PyTorch 或 TensorFlow 源代码,看看他们是如何实现基本功能的。此外,Keras 的源代码和结构非常简单,所以您可以以此为起点。
- Cs231n 的作业 都不错。理解 Dropout、Batchnorm 和 Backprop 的最好方法是用 NumPy 对它们进行编码!
- 以我的经验,面试=数据结构与算法+数学+机器学习+深度学习。粗略的划分会是——数学= 40%,经典机器学习= 30%,深度学习= 30%。
- 真实世界的经历会教会你很多东西。做远程演出(AngelList 是一个很棒的资源)或者部署一个机器学习模型,就像这样:https://platerecognizer.com/
- Jupyter Lab/notebook 非常适合实验和调试,但也有其缺点。在 Jupyter Notebook 上使用标准文本编辑器/IDE (Sublime Text、atom、PyCharm)。它更快,有助于编写良好的、可重复的代码。
- 跟上研究的步伐。为了提高模型的准确性,你需要跟上研究的步伐。深度学习的研究进展非常快。受欢迎的会议包括:
- 计算机视觉: CVPR,ICCV,ECCV,BMVC。
- 机器学习和强化学习(理论上): NeurIPS,ICML,ICLR
- NLPACL、主题图、针
其他资源
- 这篇中型文章有一个很好的公司申请名单。
- Shervine Amidi 的深度学习小抄。面试前快速复习的好资源。
- 查看distilt . pub的酷炫互动文章。
张量流绝对指南
原文:https://blog.paperspace.com/absolute-guide-to-tensorflow/
TensorFlow 是当今最受欢迎的深度学习库之一。TensorFlow 由谷歌开发,于 2015 年发布,被认为是研究、开发和部署机器学习模型的最佳平台之一。
TensorFlow 的核心结构是用 C、C++等编程语言开发的,这使得它成为一个速度极快的框架。TensorFlow 拥有 Python、Java 和 JavaScript 等编程语言的接口。精简版还允许它在移动应用程序和嵌入式系统上运行。
在本教程中,我们将分解 TensorFlow 和 Keras 的最基本方面,同时用这些模块编程深度学习模型。
目录
- 张量流和 Keras 简介
- 加速器
1。CPU
2。图形处理器
3。TPUs - 初学者快速入门指南
- 基本功能和操作
1。张量
2。常量和变量
3。反向传播
4。图表
5。层和模型介绍 - 了解 TensorFlow 2.x
1。渐变裁剪
2。梯度反转
3。梯度带 - 结论
您可以跟随本教程中的完整代码,并从 Gradient 社区笔记本中免费运行它。
张量流和 Keras 简介
TensorFlow 使用基于底层控制的方法,编码和开发项目需要复杂的细节,导致学习曲线更加陡峭。
这就是 Keras 的用武之地!
Keras 最初是由谷歌人工智能团队的各个成员开发的。Keras 的第一个版本由作者 Franç ois Chollet 于 2015 年 3 月 27 日在 GitHub 上提交并发布。
Keras 是一个简单的高级 API,作为前端接口工作,它可以与几个后端一起使用。在 2.3 版本之前,Keras 支持各种深度学习库,如 TensorFlow、Theano、Caffe、PyTorch 和 MXNET。然而,对于最稳定的版本 Keras 2.4(2020 年 6 月 17 日发布),现在仅支持 TensorFlow。
当 TensorFlow 最初发布时,Keras 只是可以与之一起使用的流行的高级 API 之一。随着 TensorFlow 2.0 的发布,Keras 现在与 TensorFlow 集成在一起,被认为是用于快速轻松的模型设计和训练的官方高级 TensorFlow API。
加速器
深度学习涉及大量复杂的计算,以达到理想的结果。几十年前深度学习和神经网络失败的主要原因之一是缺乏足够的数据,以及缺乏先进的计算技术。
然而,在现代,数据的丰富和技术的不断进步导致深度学习成为解决复杂问题的强大工具,例如人脸识别、对象检测、图像分割和许多其他复杂任务。
在这一部分,我们将讨论深度学习的一个重要方面,涉及到增强 TensorFlow 等深度学习库的功能:加速器。人工智能加速器是一类专门的硬件加速器,即一种旨在加速人工智能应用的计算机系统,即在机器学习,计算机视觉,自然语言处理和物联网应用等领域。
让我们分析几个用于处理的常用工具。最常用的加速器可以分类如下。
中央处理器(central processing units 的缩写)
在本地系统上运行 TensorFlow 深度学习库最简单(也是默认)的方法是使用中央处理器(CPU)。CPU 有几个内核,通常从 4 到 12 个不等,可用于计算与深度学习任务有关的计算(如反向传播计算)。
然而,CPU 只是一个基本的、简单的加速器,不能并行执行许多操作。TensorFlow 的 CPU 版本可以用来在短时间内解决简单的任务。但是,随着模型体系结构的规模、节点数量和可训练参数的增加,CPU 速度非常慢,甚至可能无法处理处理负载。
绘图处理器
图形处理单元(GPU,或显卡)彻底改变了深度学习。这些加速器可以与 Tensorflow-GPU 版本集成,以实现各种操作和任务。
NVIDIA 提供了一种称为计算统一设备架构(CUDA)的东西,这对于支持各种深度学习应用程序至关重要。CUDA 是 NVIDIA 创建的并行计算平台和应用编程接口模型。它允许软件开发人员和工程师使用支持 CUDA 的图形处理单元(GPU)进行通用处理,这种方法称为 GPGPU。
GPU 包含数千个 CUDA 内核,有助于大幅减少计算时间。显卡不仅可以加快你深度学习问题的速度,还可以减少完成某个特定任务所需的资源数量和时间。
举个简单的例子,如果一个 CPU 需要三个小时来训练和运行一个图像分割任务,那么在一个普通的 GPU 上,同样的问题可以在 15-30 分钟的时间范围内得到解决。有了更好、更高质量的 GPU 设备,整个计算任务,包括程序的训练和运行,都有可能在几分钟内完成。
TPUs
张量处理单元(TPU)是本文将要讨论的最终人工智能加速器。TPU 由谷歌设计师开发,于 2016 年 5 月推向全球。TPU 专为与张量流相关的操作而设计,执行机器学习和人工神经网络的复杂矩阵任务。
与 GPU 相比,TPU 设备专为大量低精度计算(低至 8 位)而设计。设计这些 TPU 的主要目的是在矩阵(或张量)相关运算上获得高性能、高质量和精确的结果。
这些 TPU 模型以前有三个版本,都是为了更快更有效的计算。这些 TPU 最近的第四个版本旨在通过微控制器和 TensorFlow lite 版本实现高性能。
注: 精简版 TensorFlow 是实际 TensorFlow 模块的一个更小的包,是为了使其更适合在嵌入式设备上部署机器学习模型而开发的。
目前,在一般情况下,GPU 通常比 TPU 更受青睐。
初学者快速入门指南
在本节中,我们将简要介绍开始安装 TensorFlow 库的主要方法。
CPU TensorFlow 模块的安装非常简单,建议您仅在系统中没有集成显卡的情况下安装它。只需在命令提示符下输入以下命令即可安装。
pip install tensorflow
但是,如果你有 GPU(最好是英伟达的),你可以继续安装 TensorFlow 的 GPU 版本。对于每个平台,如 Windows 或 Linux,GPU 版本的实际安装可能相当复杂。您需要为将要安装的特定 TensorFlow 版本安装特定版本的 CUDA 和 CUDNN。兼容性问题会产生有趣的复杂情况。在这里你可以找到一个分步指南(在这里你可以找到 T2 的视频),它涵盖了安装过程。
在这里,我们有一个简单得多的方法,无需太多的努力就可以在您的 PC 上安装 TensorFlow 的 GPU 版本。这个过程可以在 Anaconda 包的帮助下完成。Anaconda 为与数据科学相关的项目提供了各种各样的工具,并支持 Python 和 R 等编程语言来执行大量任务。你可以从这里下载 Anaconda 包。
下载发行版并在您的平台上安装后,您可以使用以下简单命令安装 TensorFlow GPU 版本。
conda install -c anaconda tensorflow-gpu
以上步骤就是你需要做的全部,GPU 版本就会安装在你的系统上。您不需要担心 GPU 或 TensorFlow 版本的任何兼容性问题,因为 Conda install 命令会处理所有必要的需求。
我强烈推荐查看如何在 Anaconda 发行版中为单个项目创建虚拟环境(更多信息在这里)。您可以在虚拟环境中安装所有必要的库模块和包。为你的数据科学或人工智能项目使用不同的虚拟环境的主要优点是,你可以为每个项目的需求使用每个包的特定版本。
例如,您可能需要 TensorFlow 1.x 版本来执行特定的对象检测任务。尽管您可以在开始时使用tensorflow.compat.v1
命令来转换 Tensorflow 2.x 的一些性能,以适应您项目的要求,但您可以只拥有两个虚拟环境。
然而,如果你没有显卡(GPU),但仍然想尝试它,体验它的性能,那么你很幸运。有几个不同的选项可供您受益、探索和利用。一个这样的选项是 Paperspace Gradient ,它为一般用途提供免费的 GPU 和 CPU。Gradient 允许您在 Jupyter 笔记本上创建具有 GPU 支持的深度学习项目,以实现更快的计算速度。这些 Jupyter 笔记本也可以与您的朋友和同龄人分享。
基本功能和操作
在教程的这一部分,我们将涵盖操作 TensorFlow 模块以解决各种复杂的深度学习问题所需的核心基础知识。
本文将不涉及从头构建模型或层等主题,因为 Keras 模块是使用 TensorFlow 创建层和开发模型的更有效和高效的方式。我们将在第 2 部分了解更多。
让我们开始讨论张量到底是什么,以及它在 TensorFlow 库中有什么要求。然后,我们将继续简要了解 TensorFlow 库模块中的常数和变量。在下一部分中,我们将对什么是反向传播有一个直观的理解。最后,我们将简要讨论图和建模的概念。
张量
张量是多维数组,有一个统一的类型叫做dtype
。dtype 支持张量中元素的各种表示,包括tf.float32
、tf.int32
、tf.bool
和许多其他选项。tf.dtypes.DType()
也支持各种属性。我强烈推荐查看这个链接来了解更多关于这个话题的信息。
让我们通过一些例子,对张量的各种表示有一个更基本的了解。
- 标量或秩为 0 的张量:只包含一个元素而没有轴的变量表示,即单值元素。
- 向量或秩 1 张量:包含元素列表的变量表示。
- 矩阵或秩 2 张量:包含元素矩阵的变量表示。
- N 维张量:任何超过秩 2 张量的表示都被认为是 N 维或多维张量,其中\(n\)代表张量的增量秩。
tf.Tensor(3, shape=(), dtype=int32)
tf.Tensor([1 2], shape=(2,), dtype=int32)
tf.Tensor(
[[1\. 2.]
[3\. 4.]
[5\. 6.]], shape=(3, 2), dtype=float16)
张量的形状在深度学习领域中起着至关重要的作用,尤其是在创建深度学习模型时。例如,让我们看看下面提供的形状。
(10000, 28, 28, 3)
第一个值通常表示数据集中可用的定型或测试模式的数量。接下来的两个值通常表示图像或对象的形状(形状表示由图像的宽度和高度组成)。形状的最终表示是图像中包含的通道数。数字三代表 RGB 图像,数字一代表灰度图像。然而,可以执行多个堆栈,并且当前上下文仅参考计算机视觉任务。我们将在另一篇文章中更详细地讨论这个主题。
我们现在将讨论使用张量的几个基本主题:一种类型的数据结构(在大多数情况下是 NumPy 数组)到张量的转换,以及形状的操作。
为了使用大多数张量流函数,我们处理的变量需要是正确的类型。对于与张量相关的计算,如tf.reshape()
和其他类似的函数,输入类型必须是张量类型。这可以借助下面的命令来完成:tf.convert_to_tensor()
。
举个例子,让我们快速看一下这个转换过程是如何完成的。
a = np.array([[1,2],
[3,4]])
a
Result: array([[1, 2],
[3, 4]])
b = tf.convert_to_tensor(a)
print(b)
Result: tf.Tensor(
[[1 2]
[3 4]], shape=(2, 2), dtype=int32
张量形状的操作在执行各种操作和任务中起着至关重要的作用。tf.reshape()
的工作方式类似于 NumPy 中的整形功能。整形函数接受两个输入,即要整形的张量和要将其转换为的形状。
在接下来的几个示例中,我们将了解如何使用 TensorFlow 执行各种形状操作。我们将主要关注三个与形状相关的操作:reshape
、expand_dims
和reduce_sum
。所有这些功能在计算机视觉和自然语言处理任务的深度学习中都发挥着重要作用。先说 TensorFlow 的重塑功能。
rank_2_tensor = tf.constant([[1, 2],
[3, 4],
[5, 6]], dtype=tf.int32)
print(rank_2_tensor.shape)
print(tf.reshape(rank_2_tensor, shape=[2,3]))
Result:
(3, 2)
tf.Tensor(
[[1 2 3]
[4 5 6]], shape=(2, 3), dtype=int32)
我们可以注意到,秩为 2 的张量的形状,最初是\((3,2)\),现在是\((2,3)\)。许多操作都是用整形功能执行的,它们在深度学习中起着至关重要的作用。
现在让我们来看看与张量有关的运算的扩展和缩减维数的例子。
rank_2_tensor = tf.constant([[1, 2],
[3, 4],
[5, 6]], dtype=tf.int32)
rank_2_tensor = tf.expand_dims(rank_2_tensor, axis=-1)
print(rank_2_tensor)
Result:
tf.Tensor(
[[[1]
[2]]
[[3]
[4]]
[[5]
[6]]], shape=(3, 2, 1), dtype=int32)
rank_2_tensor = tf.reduce_sum(rank_2_tensor, axis=-1)
print(rank_2_tensor)
Result:
tf.Tensor(
[[1 2]
[3 4]
[5 6]], shape=(3, 2), dtype=int32)
我们在上面的例子中考虑的秩 2(或矩阵张量)对于更好地理解形状操作的各种概念是重要的。轴参数用于指定必须对其执行形状更改的适当属性。通过说axis=-1
,你总是在操纵形状的最后一个索引。您可以通过指定所需的轴来选择任何索引,维度的扩展或缩减将在指定的位置发生。
使用这些函数,您可以操作正在处理的结构的各种维度,这对于涉及卷积神经网络和序列到序列模型的任务尤其有用。
常量和变量
下一个我们要关注的话题是张量流中的常量和变量。在 TensorFlow 第一版中使用的占位符现在在 TensorFlow 更新的第二版中已被删除。因此,我们将把重点放在常量和变量上,因为它们是很容易理解的话题。
TensorFlow 中的常数,顾名思义,就是取值固定且保持不变的张量。这个在构造模型中的术语指的是不能被训练的参数(或不可训练的参数)。TensorFlow 中的变量是可变张量,是表示执行大量运算的元素的推荐方法。这些是模型中的可训练参数。
让我们看一些代码来获得更好的理解。在将 Tensorflow 常数转换为 TensorFlow 变量后,我们将主要执行三种操作,即逐元素加法、逐元素乘法和矩阵乘法。
a = tf.constant([[1, 2],
[3, 4]])
b = tf.constant([[1, 1],
[1, 1]])
a = tf.Variable(a)
b = tf.Variable(b)
print(tf.add(a, b), "\n")
print(tf.multiply(a, b), "\n")
print(tf.matmul(a, b), "\n")
Result:
tf.Tensor(
[[2 3]
[4 5]], shape=(2, 2), dtype=int32)
tf.Tensor(
[[1 2]
[3 4]], shape=(2, 2), dtype=int32)
tf.Tensor(
[[3 3]
[7 7]], shape=(2, 2), dtype=int32)
以上示例显示了 TensorFlow 中常量和变量类型可以执行的各种类型的计算。上面的代码块也可以使用常量类型,但是最好将它们转换成变量来执行这样的任务。Keras 模块使用 TensorFlow 中的变量函数来存储各种模型参数并对其进行操作。
在下一节中,我们将讨论神经网络和深度学习的一个最重要的主题,即反向传播。然后我们将学习张量流如何有助于产生更好的结果。
反向传播
深度学习和神经网络最重要和最本质的方面是反向传播和梯度计算的概念。为了了解反向传播的复杂细节,并对这一主题有一个更直观的理解,让我们考虑下图,它代表了一个“完全连接”的神经网络。
Image by Gordon Johnson from Pixabay
当训练神经网络时,第一步是前向传播。在训练的第一个时期,随机权重被考虑(根据用户定义的初始化)并用于计算所需的输出值。在前向传播结束时,我们在输出节点接收一些值。
有了这些接收(或预测)值,我们就可以计算损耗,损耗可以计算为预测值和实际值之间的差值。一旦计算出损失,就应该通过反向传播过程中梯度的微分和计算来调整神经网络的权重。
在 TensorFlow 中,使用了梯度的自动微分的概念。这一特性非常重要,因为它有助于成功实现用于训练的反向传播算法。
为了自动区分,TensorFlow 需要记住在向前传递期间什么操作以什么顺序发生。然后,在反向过程中,TensorFlow 以相反的顺序遍历这个操作列表来计算梯度。在本文的编码部分,我们将更好地理解梯度带的主题,在这里,我们将实现一个梯度带函数,用于在神经网络的训练过程中执行自动微分。
Screenshot By Author From Tensorflow Playground
在结束这一部分之前,我强烈建议初学机器学习的观众去看看 TensorFlow Playground 网站。这是一个免费网站,可以让你可视化神经网络在不同数据集上的表现。它为您提供了各种各样的选择来尝试和试验,同时获得了关于神经网络如何解决某些模式和问题的更强的直觉。
本文接下来的两个部分将会简单介绍一下。图表、图层和模型将在下一篇文章中详细介绍,因为在使用 Keras 模块时它们更容易使用。这些概念的 TensorFlow 实现可能会非常混乱。因此,这些主题在本文中只会简单地涉及一下。
图形
TensorFlow 中的图的概念不仅仅是对 TensorFlow 和 Keras 的讨论。Keras 模块提供的 TensorBoard 回调函数无疑是理解图形和查看模型性能的更好方法。
Image by Author
然而,为了快速理解 TensorFlow 中的图形主题,图形被视为包含一组代表计算单元的tf.Operation
对象的数据结构。这些tf.Tensor
对象是在各种操作之间流动的数据单元的表示。这些可以在tf.Graph
的背景下定义。使用这些图表的最大好处是它们是数据结构。因此,它们可以被保存、运行和恢复,而不需要原始的 Python 代码。
图表在分析模型和查看特定模型是否欠拟合或过拟合训练数据方面非常有用。图表有助于理解程序的结构以及为获得更高的性能而需要进行的改进。在本系列的下一部分 TensorBoard 中将更详细地讨论图形主题。
层和模型快速介绍
层和模型的主题也将在本系列的下一部分详细讨论,因为使用 Keras 库可以更容易地构建它们。然而,在文章的这一部分,我们将快速关注 TensorFlow 在构建层和模型方面的一些重要方面。
TensorFlow 库可用于从头构建您自己的定制模型。层是具有已知数学结构的函数,可以重复使用,并且具有可训练的变量。组合在一起并为单一目的构建的多个层可用于创建模型。我们将在本系列的下一部分进一步讨论这个主题。现在让我们继续理解 TensorFlow 中一些简单但有用的基本代码。
用代码理解 TensorFlow 2
在本文的最后一部分,我们将探索一些基本的编码结构、模式和架构,这些都是为精确计算和梯度微分的自动计算编写高级 TensorFlow 工作流所需的。为了更好地理解 TensorFlow 库,我们将主要看三个方面,即梯度裁剪、梯度反转和梯度带。这里提供的代码仅供参考,为了获得更好、更直观的理解,我强烈建议您尝试自己编写。
渐变剪辑
在一些神经网络架构(如递归神经网络,或 RNNs) 中,经常存在爆炸和消失梯度的问题,这会导致所构建的深度学习模型出现一些错误和故障。为了避免和解决这一特定问题,可以执行梯度裁剪的张量流运算,以将梯度值限制在特定范围内。让我们看看下面的代码块来更好地理解这一点。
@tf.custom_gradient
def grad_clip(x):
y = tf.identity(x)
def custom_grad(dy):
return tf.clip_by_value(dy, clip_value_min=0, clip_value_max=0.5)
return y, custom_grad
class Clip(tf.keras.layers.Layer):
def __init__(self):
super().__init__()
def call(self, x):
return grad_clip(x)
上面的代码块利用custom_gradient
函数来开发渐变剪辑,将值限制在一个由最小值和最大值限定的范围内。此外,类Clip
可以用作一个层,可以添加到剪辑的特定隐藏层的梯度。
梯度反转
顾名思义,梯度反转过程用于在特定层或序列的计算期间反转梯度。下面显示的代码块是如何执行这种梯度反转的简单表示,以及如何在自定义层中准确地利用它来反转梯度。
@tf.custom_gradient
def grad_reverse(x):
y = tf.identity(x)
def custom_grad(dy):
return -dy
return y, custom_grad
class GradReverse(tf.keras.layers.Layer):
def __init__(self):
super().__init__()
def call(self, x):
return grad_reverse(x)
梯度带
我们已经讨论过,反向传播是深度学习和神经网络的最重要的概念之一。计算梯度的自动微分过程非常有用,TensorFlow 以tf.GradientTape
函数的形式提供了这一点。在急切执行期间,使用tf.GradientTape
跟踪操作,以便稍后计算梯度。
tf.GradientTape
对于复杂的训练循环特别有用。因为在每次调用中可能发生不同的操作,所以所有向前传递的操作都被记录到“磁带”上。要计算梯度,倒放磁带,然后丢弃。一个特定的tf.GradientTape
只能计算一个梯度;后续调用会引发运行时错误。
def step(real_x, real_y):
with tf.GradientTape() as tape:
# Make prediction
pred_y = model(real_x.reshape((-1, 28, 28, 1)))
# Calculate loss
model_loss = tf.keras.losses.categorical_crossentropy(real_y, pred_y)
# Calculate gradients
model_gradients = tape.gradient(model_loss, model.trainable_variables)
# Update model
optimizer.apply_gradients(zip(model_gradients, model.trainable_variables))
有了对梯度带函数的这种理解,我们已经到了张量流和 Keras 系列的第一部分的结尾。
结论
Photo by Nikita Vantorin on Unsplash
在本文中,我们已经了解了 TensorFlow 和 Keras 深度学习库的简史。然后,我们通过一个快速入门指南,继续学习对神经网络计算有用的不同类型的加速器。在此之后,我们探索了数据科学爱好者可用的各种选项,以开始构建各种独特的模型和项目。
在本文的最后两节中,我们查看了有用的命令和指南,以加深对 TensorFlow 的理解。我们触及了张量、形状处理、常数和变量、反向传播等基本主题。最后,我们以高级 TensorFlow 代码结束了系列的第一部分,事实证明,这对于深度学习和神经网络中的特定场景极其有用。
如需进一步阅读,我建议您查看 TensorFlow 指南的官方文档。
在本系列的下一部分,我们将了解更多关于 Keras 的内容,以及 Keras 的集成如何对 TensorFlow 的第二个版本产生积极影响。我们将直观地了解 Keras 模块中可用的各种图层选项,并学习如何利用它们从头构建独特的模型来解决许多不同类型的问题。我们还将分析 Keras 中提供的众多回调选项,以及如何创建自己的自定义回调。最后,我们将了解深度学习模型的编译过程和运行时训练。
在那之前,享受练习吧!
深度学习中的激活函数
原文:https://blog.paperspace.com/activation-functions-in-deep-learning/
激活函数是深度学习架构中的常见景象。在本文中,我们将了解深度学习应用程序中的一些常见激活函数,以及为什么要使用它们。
# article dependencies
import torch
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
from tqdm.notebook import tqdm
from tqdm import tqdm as tqdm_regular
import seaborn as sns
神经网络的工作
神经网络背后的整个思想/工作原理是将某种类型的输入数据实例(无论是矩阵还是向量)转换成特定输出表示(缩放器、向量或矩阵)的能力。为了做到这一点,它本质上是将每个元素乘以特定的数字(权重),并将它们作为新层中的神经元传递。
为了有效地做到这一点,神经网络必须学习一层中的神经元如何与下一层的神经元相关。当这从一层到另一层完成时,我们就可以有效地了解我们的输入和输出表示之间的关系。激活函数允许神经网络学习神经元之间的关系。
激活功能
如前一节所述,激活函数通过学习层与层之间的映射来帮助神经网络学习输入和输出之间的关系。激活函数可以分成许多类,如线性函数、二元阶跃函数和非线性函数。
线性激活函数
线性激活函数产生与其输入成比例的神经元。线性激活函数的最简单形式是神经元不被激活,而是保持原样的情况。也被称为恒等函数,这等同于将每个神经元(x)乘以 1 以产生激活 y,因此称为y = x
。
def linear_activation(values):
"""
This function replicates the identity
activation function
"""
activations = [x for x in values]
return activations
# generating values from -3 to 3
values = np.arange(-3, 4)
# activating generated values
activations = linear_activation(values)
# activations are the same as their original values
activations
>>>> [-3, -2, -1, 0, 1, 2, 3]
# plotting activations
sns.lineplot(x=values, y=activations)
plt.xlabel('values')
plt.ylabel('activations')
A plot of activations against values can be seen to be a straight line (linear)
二元阶跃激活函数
二元阶跃激活函数定义了开启或关闭神经元的阈值。它基本上将神经元值与预定义的阈值进行比较,并且仅当它们大于或等于阈值时才激活它们。
在上面的数学表示中,阈值被设置为零,使得任何低于零的值被赋予值 0(未激活),任何高于零的值被赋予值 1(激活)。
def binary_step_activation(values):
"""
This function replicates the binary step
activation function
"""
activations = []
for value in values:
if value < 0:
activations.append(0)
else:
activations.append(1)
return activations
# generating values from -3 to 3
values = np.arange(-3, 4)
# activating generated values
activations = binary_step_activation(values)
# activations are zero for values less than zero
activations
>>>> [0, 0, 0, 1, 1, 1, 1]
# plotting activations
sns.lineplot(x=values, y=activations, drawstyle='steps-post')
plt.xlabel('values')
plt.ylabel('activations')
非线性激活函数
到目前为止,在深度学习应用中使用最广泛的非线性激活函数是神经网络中的首选,因为它们有助于网络从一层到另一层学习输入和输出之间更复杂的关系。
从机器学习中可以明显看出,线性模型(线性回归)通常不足以有效地学习回归任务环境中的关系,回归任务促使使用正则化参数来尝试学习更复杂的表示。即便如此,线性模型的拟合程度也是有限的。
这同样适用于深度学习,大多数时候,通常用于训练深度学习模型的数据实例与其目标表示之间并不存在线性关系。想一想,在几幅鸟的图像和它们被赋予的任何类别标签之间,真的存在比例关系吗?不太可能。物体探测任务呢?在对象和能够定义它们的边界框的众多像素之间不可能存在线性关系。
在深度学习中,输入和目标之间往往存在非常复杂的关系,非线性激活函数有助于转化神经元,以至于网络被迫学习从一层到另一层的复杂映射。本节专门介绍深度学习中常用的非线性激活函数。
Sigmoid 函数
也称为逻辑函数,sigmoid 函数将值限制在值 0 和 1 之间。通过这样做,它在某种程度上提供了一种概率,这种概率又代表了对于特定数据实例,每个神经元对下一层神经元值的贡献的可能性。
然而,由于神经元被限制在小范围内,sigmoid 激活在较深的网络架构中会导致 涂漆梯度 的问题,因此最适合较浅的架构。
def sigmoid_activation(values):
"""
This function replicates the sigmoid
activation function
"""
activations = []
for value in values:
activation = 1/(1 + np.exp((-1*value)))
activations.append(activation)
activations = [round(x, 3) for x in activations]
return activations
# generating values from -5 to 5
values = np.arange(-5, 6)
# activating generated values
activations = sigmoid_activation(values)
# all activations are now constrained between 0 and 1
activations
>>>> [0.01, 0.02, 0.05, 0.12, 0.27, 0.5, 0.73, 0.88, 0.95, 0.98, 0.99]
# plotting activations
sns.lineplot(x=values, y=activations)
plt.xlabel('values')
plt.ylabel('activations')
双曲正切激活函数
与 sigmoid 函数类似,双曲正切函数(通常称为双曲正切函数)将值限制在-1 和 1 之间,而不是 0 和 1 之间。它有效地调整了每一层中的神经元。
这种激活被视为对 sigmoid 激活函数的改进,因为其激活是以零为中心的,这有利于梯度下降。然而,就像 sigmoid 一样,它在更深的建筑中容易受到上漆梯度的影响,因为激活也被限制在小范围内。
def tanh_activation(values):
"""
This function replicates the tanh
activation function
"""
activations = []
for value in values:
activation = (np.exp(value) - np.exp((-1*value)))/(np.exp(value) + np.exp((-1*value)))
activations.append(activation)
activations = [round(x, 2) for x in activations]
return activations
# generating values from -5 to 5
values = np.arange(-5, 6)
# activating generated values
activations = tanh_activation(values)
# all activations are now constrained between -1 and 1
activations
>>>> [-1.0, -1.0, -1.0, -0.96, -0.76, 0.0, 0.76, 0.96, 1.0, 1.0, 1.0]
# plotting activations
sns.lineplot(x=values, y=activations)
plt.xlabel('values')
plt.ylabel('activations')
校正线性单位激活函数(ReLU)
ReLU 激活是最流行的激活函数之一,它的工作原理是将零值赋给任何具有负值的神经元,而保留正值的神经元不变。它在数学上表示如下。
与 sigmoid 和 tanh 相比,ReLU 激活的计算效率更高,因为负向神经元被置零,从而减少了计算。然而,在使用 ReLU 激活功能时,人们可能会遇到一个名为 死亡 ReLU 的问题。随着优化过程的进行,神经元通过 ReLU 激活被转换为零,如果大量神经元变为零,则这些神经元在需要时不能进一步更新,因为它们的梯度将为零。这实际上导致了网络的一部分死亡。
def relu_activation(values):
"""
This function replicates the relu
activation function
"""
activations = []
for value in values:
activation = max(0, value)
activations.append(activation)
return activations
# generating values from -5 to 5
values = np.arange(-5, 6)
# activating generated values
activations = relu_activation(values)
# all negative values are zeroed
activations
>>>> [0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5]
# plotting activations
sns.lineplot(x=values, y=activations)
plt.xlabel('values')
plt.ylabel('activations')
泄漏整流线性单元激活函数
这个激活函数是 ReLU 函数的修改版本,它试图解决 ReLU 死亡的问题。与 ReLU 函数不同,leaky-ReLU 不会将非正值转换为零,而是通过 0.01 对它们进行加权,以使它们足够不重要,但是仍然有效,从而如果优化过程认为在训练期间的任何点有必要,可以对它们进行更新。
def leaky_relu_activation(values):
"""
This function replicates the leaky
relu activation function
"""
activations = []
for value in values:
activation = max(0.01*value, value)
activations.append(activation)
return activations
# generating values from -5 to 5
values = np.arange(-5, 6)
# activating generated values
activations = leaky_relu_activation(values)
# negative values are not zeroed
activations
>>>> [-0.05, -0.04, -0.03, -0.02, -0.01, 0.0, 1, 2, 3, 4, 5]
# plotting activations
sns.lineplot(x=values, y=activations)
plt.xlabel('values')
plt.ylabel('activations')
Notice a slight slope in the negative region.
参数整流器线性单元激活函数
参数整流线性单元(P-ReLU)激活函数非常类似于泄漏 ReLU 函数,因为它也试图减轻 ReLU 死亡的问题。然而,它不是为所有负值指定恒定的权重,而是将权重(泄漏系数)指定为可学习的参数,当模型被训练时,神经网络将能够学习和优化该参数。
SoftMax 激活功能
SoftMax 激活函数通常用于神经网络的输出层,用于多类分类任务。它有助于产生输出向量中元素的相对概率。与 sigmoid 函数不同,SoftMax 在计算概率时会考虑表示中其他元素的值,因此所有 SoftMax 激活值的总和始终等于 1。
def softmax_activation(values):
"""
This function replicates the softmax
activation function
"""
activations = []
exponent_sum = sum([np.exp(x) for x in values])
for value in values:
activation = np.exp(value)/exponent_sum
activations.append(activation)
activations = [round(x, 3) for x in activations]
return activations
# generating values from -5 to 5
values = np.arange(-5, 6)
# activating generated values using softmax
softmax_activations = softmax_activation(values)
# values all sum up to 1
softmax_activations
>>>> [0.0, 0.0, 0.0, 0.001, 0.002, 0.004, 0.012, 0.031, 0.086, 0.233, 0.632]
# activating generated values using sigmoid
sigmoid_activations = sigmoid_activation(values)
# values do not sum up to 1
sigmoid_activations
>>>> [0.007, 0.018, 0.047, 0.119, 0.269, 0.5, 0.731, 0.881, 0.953, 0.982, 0.993]
结束语
在这篇文章中,我们讨论了什么是激活函数以及为什么在神经网络中使用它们。我们还研究了几类激活函数,结论是非线性激活函数是最合适的。之后,我们从理论、数学、图形和代码实现的角度讨论了一些常见的非线性激活函数。
纸张空间< 3 活动目录
在 Paperspace,我们一直在开发我们自己的身份管理系统,以使没有大型 IT 部门的企业能够尽快轻松地启动并运行虚拟桌面。但是对于严肃的企业来说,只有一个身份管理系统很重要,那就是 Active Directory。因此,当我们开始构建 Paperspace 时,广告集成是我们完成的第一批功能之一。今天,我们提供几乎所有你能想到的托管和非托管域的配置。
AdaBoost 指南:助推力挽狂澜
今天,机器学习是重大创新的前提,并有望继续让公司通过准确的预测做出最佳决策。但是,当这些算法的错误敏感度很高且无法解释时,会发生什么呢?
这就是集合学习拯救世界的时候!
AdaBoost 是一种集成学习方法(也称为“元学习”),最初是为了提高二元分类器的效率而创建的。AdaBoost 使用迭代方法从弱分类器的错误中学习,并将它们变成强分类器。
在本文中,我们将了解以下模块:
- 什么是集成学习?
- 集合方法的类型
- 集成方法中的增强
- 升压算法的类型
- 解开 AdaBoost
- AdaBoost 的伪代码
- 用 Python 实现 AdaBoost
- AdaBoost 的优缺点
- 总结和结论
您可以在 ML Showcase 上免费运行本教程的代码。
什么是集成学习?
集成学习将几种基本算法结合起来,形成一种优化的预测算法。例如,用于分类的典型决策树采用几个因素,将它们转化为规则问题,并给定每个因素,要么做出决定,要么考虑另一个因素。如果有多个决策规则,决策树的结果可能会变得模糊,例如,如果做出决策的阈值不明确,或者我们输入了新的子因素进行考虑。这是一个人可以随意使用的集合方法。集成方法不是寄希望于一棵决策树来做出正确的判断,而是采用几棵不同的树,并将它们聚合成一个最终的强预测器。
集合方法的类型
集合方法可用于各种原因,主要用于:
- 减少差异(装袋)
- 降低偏置(升压)
- 改进预测(堆叠)
集合方法也可以分为两组:
- 顺序学习器,不同的模型顺序生成,前一个模型的错误由后一个学习。这旨在通过给错误标记的例子更高的权重来利用模型之间的依赖性(例如 AdaBoost )。
- 并行学习器,并行生成基础模型。这通过平均化错误来利用模型之间的独立性(例如随机森林T5)。
集成方法中的增强
就像人类从错误中学习,并试图在生活中不再重复错误一样, Boosting 算法试图从几个较弱模型的错误中建立一个强学习器(预测模型)。首先,从训练数据创建一个模型。然后,通过尝试减少前一个模型的误差,从前一个模型创建第二个模型。模型会依序加入,每个模型都会修正其前一个模型,直到训练资料预测完美,或加入了最大数量的模型为止。
Boosting 基本上是试图减少当模型无法识别数据中的相关趋势时出现的偏差误差。这是通过评估预测值和实际值之间的差异来实现的。
升压算法的类型
- AdaBoost ( Ada 感受性助推** ing)**
- 梯度树提升
- XGBoost
在本文中,我们将关注 AdaBoost 的细节,这可能是最流行的升压方法。
解开 AdaBoost
AdaBoost ( Ada 感受性 Boost ing)是一种非常流行的 boosting 技术,旨在组合多个弱分类器来构建一个强分类器。最初的 AdaBoost 论文由 Yoav Freund 和 Robert Schapire 撰写。
单个分类器可能无法准确预测对象的类别,但是当我们将多个弱分类器分组,每个弱分类器从其他错误分类的对象中逐步学习时,我们可以建立一个这样的强模型。这里提到的分类器可以是任何基本的分类器,从决策树(通常是默认的)到逻辑回归,等等。
现在我们可能会问,什么是“弱”量词?弱分类器是一种性能优于随机猜测的分类器,但在为对象指定类别时性能仍然很差。例如,弱分类器可能预测 40 岁以上的人不能跑马拉松,但低于该年龄的人可以。现在,您可能获得了 60%以上的准确率,但是您仍然会对许多数据点进行错误分类!
AdaBoost 本身不是一个模型,而是可以应用在任何分类器之上,从其缺点中学习并提出一个更准确的模型。因此,它通常被称为“最佳开箱即用分类器”。
让我们试着理解 AdaBoost 是如何处理决策树桩的。决策树桩就像随机森林中的树木,但不是“完全成长”它们有一节和两片叶子。AdaBoost 使用的是这种树桩的森林,而不是树木。
单靠树桩不是做决定的好方法。一棵成熟的树结合了所有变量的决策来预测目标值。另一方面,树桩只能用一个变量来做决定。让我们通过查看几个变量来尝试并逐步理解 AdaBoost 算法的幕后,以确定一个人是否“适合”(健康状况良好)。
AdaBoost 工作原理的示例
步骤 1: 基于加权样本,在训练数据之上制作弱分类器(例如决策树桩)。这里,每个样本的权重表明了正确分类的重要性。最初,对于第一个树桩,我们给所有样本相同的权重。
第二步:我们为每个变量创建一个决策树桩,看看每个树桩如何将样本分类到它们的目标类别。例如,在下图中,我们检查了年龄、吃垃圾食品和锻炼。我们会查看每个残肢有多少样本被正确或错误地分类为适合或不适合。
步骤 3: 将更多的权重分配给未正确分类的样本,以便它们在下一个决策树桩中被正确分类。权重也是根据分类器的准确率分配给每个分类器的,也就是说准确率高=权重高!
步骤 4: 从步骤 2 开始重复,直到所有数据点都被正确分类,或者达到最大迭代级别。
**
Fully grown decision tree (left) vs three decision stumps (right)**
****注意:有些树桩在分类中比其他树桩更有发言权。
AdaBoost 背后的数学
拽头发的部分来了。让我们一步一步、一个等式一个等式地分解 AdaBoost,以便更容易理解。
让我们首先考虑一个数据集中有 N 个点或行的数据集。
在这种情况下,
- n 是实数的维数,也就是数据集中属性的数量
- x 是数据点的集合
- y 是为-1 或 1 的目标变量,因为它是一个二元分类问题,表示第一类或第二类(例如,适合与不适合)
我们计算每个数据点的加权样本。AdaBoost 为每个训练示例分配权重,以确定其在训练数据集中的重要性。当分配的权重较高时,该组训练数据点可能在训练集中具有更大的发言权。类似地,当分配的权重较低时,它们在训练数据集中的影响最小。
最初,所有数据点将具有相同的加权样本 w :
其中 N 是数据点的总数。
加权样本的总和始终为 1,因此每个单独权重的值将始终介于 0 和 1 之间。此后,我们使用以下公式计算该分类器在分类数据点时的实际影响:
Alpha 是这个残肢在最终分类中会有多大的影响。总误差只不过是该训练集的错误分类总数除以训练集大小。我们可以通过插入范围从 0 到 1 的各种总误差值来绘制α的曲线图。**
**
Alpha vs Error Rate (Source: Chris McCormick)**
请注意,当一个决策树桩做得很好,或者没有错误分类(一个完美的树桩!)这导致错误率为 0 和相对较大的正 alpha 值。
如果残肢只是分类一半正确一半不正确(0.5 的错误率,不比随机猜测好!)那么 alpha 值将为 0。最后,当树桩不停地给出错误分类的结果时(只要做树桩说的相反的事情!)那么α将是一个大的负值。
在插入每个树桩的总误差的实际值后,是时候更新我们最初为每个数据点取的 1/N 的样本权重了。我们将使用以下公式来实现这一点:
换句话说,新的样本重量将等于旧的样本重量乘以欧拉数,提高到正负α(我们刚刚在上一步中计算)。
α的两种情况(正或负)表明:
- 当预测和实际输出一致时(样本被正确分类),Alpha 为正。在这种情况下,我们减少了样品的重量,因为我们已经做得很好了。
- 当预测输出与实际类别不一致时(即样本被错误分类),Alpha 为负。在这种情况下,我们需要增加样本重量,以便在下一个树桩中不会重复相同的错误分类。这就是树桩如何依赖于它们的前辈。
AdaBoost 的伪代码
`Initially set uniform example weights.
for Each base learner do:
Train base learner with a weighted sample.
Test base learner on all data.
Set learner weight with a weighted error.
Set example weights based on ensemble predictions.
end for`
用 Python 实现 AdaBoost
第一步:导入模块
和往常一样,构建模型的第一步是导入必要的包和模块。
在 Python 中,我们有来自 scikit-learn 库中的AdaBoostClassifier
和AdaBoostRegressor
类。对于我们的例子,我们将导入AdaBoostClassifier
(因为我们的例子是一个分类任务)。train_test_split
方法用于将我们的数据集分成训练集和测试集。我们还导入了datasets
,从中我们将使用虹膜数据集。
`from sklearn.ensemble import AdaBoostClassifier
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn import metrics`
第二步:探索数据
您可以使用任何分类数据集,但这里我们将使用传统的 Iris 数据集来解决多类分类问题。该数据集包含关于不同类型鸢尾花的四个特征(萼片长度、萼片宽度、花瓣长度、花瓣宽度)。目标是从三种可能性中预测花的类型:Setosa、Versicolour 和 Virginica。该数据集可在 scikit-learn 库中获得,或者您也可以从 UCI 机器学习库下载。
接下来,我们通过使用 load_iris()方法从 datasets 包中加载数据来准备好数据。我们将数据赋给 iris 变量。
此外,我们将数据集分成输入变量 X,它包含萼片长度、萼片宽度、花瓣长度和花瓣宽度。
y 是我们的目标变量,或者我们必须预测的类别:鸢尾、杂色鸢尾或海滨鸢尾。下面是我们数据的一个例子。
`iris = datasets.load_iris()
X = iris.data
y = iris.target
print(X)
print(Y)
Output:
[[5.1 3.5 1.4 0.2]
[4.9 3\. 1.4 0.2]
[4.7 3.2 1.3 0.2]
[4.6 3.1 1.5 0.2]
[5.8 4\. 1.2 0.2]
[5.7 4.4 1.5 0.4]
. . . .
. . . .
]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2]`
第三步:拆分数据
将数据集分割成训练和测试数据集是一个好主意,这样可以查看我们的模型是否正确地对看不见的数据上的数据点进行了分类。
`X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)`
在这里,我们将数据集分为 70%的训练和 30%的测试,这是一个常见的场景。
第四步:拟合模型
构建 AdaBoost 模型。AdaBoost 默认采用决策树作为学习器模型。我们创建一个 AdaBoostClassifier 对象,并将其命名为 abc 。AdaBoost 的几个重要参数是:
- base_estimator: 用于训练模型的弱学习器。
- n_estimators: 每次迭代中要训练的弱学习器的数量。
- learning_rate: 对弱学习者的权重有贡献。它使用 1 作为默认值。
`abc = AdaBoostClassifier(n_estimators=50,
learning_rate=1)`
然后,我们继续让我们的对象 abc 适合我们的训练数据集。我们称之为模型。
`model = abc.fit(X_train, y_train)`
第五步:进行预测
我们的下一步将是看看我们的模型预测我们的目标值的好坏。
`y_pred = model.predict(X_test)`
在这一步中,我们进行样本观察,并对看不见的数据进行预测。此外,我们在模型上使用 predict()方法来检查它所属的类。
第六步:评估 ing 模型
模型精度将告诉我们我们的模型预测正确类别的次数。
`print("Accuracy:", metrics.accuracy_score(y_test, y_pred))
Output:
Accuracy:0.8666666666666667`
你得到了 86.66%的准确率——还不错。你可以尝试各种其他基础学习者,如支持向量机,逻辑回归,这可能会给你更高的准确性。
AdaBoost 的优缺点
AdaBoost 有很多优点,主要是它更容易使用,不像 SVM 算法那样需要调整参数。作为奖励,你也可以使用 SVM AdaBoost。理论上,AdaBoost 不容易过拟合,尽管没有具体的证据证明这一点。这可能是因为参数没有被联合优化的原因——阶段式估计减慢了学习过程。要深入理解其背后的数学原理,你可以跟随这个链接。
AdaBoost 可用于提高弱分类器的准确性,从而使其更加灵活。它现在已经扩展到了二元分类之外,并且在文本和图像分类中也有使用案例。
AdaBoost 的一些缺点是:
助推技术循序渐进地学习,确保你有高质量的数据是很重要的。AdaBoost 对噪声数据和异常值也非常敏感,因此如果您计划使用 AdaBoost,强烈建议您消除它们。
AdaBoost 也被证明比 XGBoost 慢。
总结和结论
在本文中,我们讨论了理解 AdaBoost 算法的各种方法。我们首先向您介绍集成学习,它有多种类型,以确保您了解 AdaBoost 的确切位置。我们讨论了该算法的优缺点,并给出了一个使用 Python 实现该算法的快速演示。
如果使用准确,AdaBoost 就像是提高我们分类算法准确性的福音。这是第一个成功提升二进制分类的算法。AdaBoost 越来越多地用于行业,并在面部识别系统中找到了自己的位置,以检测屏幕上是否有人脸。
希望这篇文章能够激发你的好奇心,让你更深入地研究 AdaBoost 和其他各种 Boosting 算法。
参考
http://mccormickml.com/2013/12/13/adaboost-tutorial/
https://towards data science . com/boosting-and-AdaBoost-clearly-explained-856 e 21152 d3e
http://rob.schapire.net/papers/explaining-adaboost.pdf
https://hacker noon . com/under-the-hood-of-AdaBoost-8eb 499d 78 eab
使用自然语言处理技术从文本中自动提取术语和定义
原文:https://blog.paperspace.com/adaptive-testing-and-debugging-of-nlp-models-research-paper-explained/
根据维基百科——术语表,也称为词汇表,是特定知识领域中按字母顺序排列的术语列表,包含这些术语的定义。传统上,术语表出现在书的末尾,包括书中新介绍的、不常见的或专门的术语。然而,定义是解释术语含义的陈述。给定足够的“常识”和“背景知识”,一份好的词汇表可以使文本易于理解。
在这篇博文中,我们将重点关注构建一个无人监管的 NLP 管道,用于从给定的文本文档(如一本书/一章/一篇文章)中自动提取/生成词汇表和相关定义。手动生成术语表及其定义是一项耗时且繁琐的任务。通过我们的管道,我们的目标是大大减少这种努力,增加作者的建议,同时仍然使他们的裁决成为选择期间的最终裁决。
数据集集合
我们将建立一个几乎无人监管的系统,因此为了评估我们的管道输出,我们将从从古腾堡项目中抽取 50 部小说/非小说开始。 古腾堡计划是一个拥有超过 60,000 本免费电子书的图书馆——对读者完全免费。下载完书籍后,下一个任务是获取它们的术语表和相关定义。我们可以使用 GradeSaver 来获取这些信息。 GradeSaver 是全球顶尖的编辑和文学网站之一。这样,我们就有了一些地面实况数据,可以用来评估我们提议的管道是否良好。
接下来,让我们看看提取小说的代码、相关的术语表和定义
从古登堡计划中提取小说的代码
from bs4 import BeautifulSoup
import requests
import pandas as pd
import glob
import string
import os
import codecs
BASE_BOOK_URL = 'https://www.gutenberg.org/browse/scores/top'
html = requests.get(BASE_BOOK_URL).text
soup = BeautifulSoup(html)
unq_code = {}
for s in soup.findAll('li'):
url = s.a['href']
if 'ebooks' in url:
url_str = url.split('/')[-1]
if url_str!='':
unq_code[url.split('/')[-1]] = s.a.text
BOOK_TXT_BASE = 'https://www.gutenberg.org/files/'
book_urls = []
for code in unq_code:
book_urls.append(os.path.join(BOOK_TXT_BASE,f'{code}/{code}-0.txt'))
for b in book_urls:
name = b.split('/')[-2]
html = requests.get(b).text
with codecs.open(f'book/{name}.txt', 'w', 'utf-8') as infile:
infile.write(html)
Extracting Novels from Project Gutenberg
从上面的片段中可以看出,我们使用 BeautifulSoup python 库从热门列表 url 中提取每部小说的唯一代码。这些列表是基于每本电子书被下载的次数。接下来,如果从 BOOK_TXT_BASE url 中存在该书的“纯文本 UTF-8”版本,我们将模拟点击功能并提取小说的原始文本。最后,我们下载每一部小说,并按照适当的命名规则保存在我们想要的位置。
从 GradeSaver 中提取术语表和定义的代码
BASE_GLOSS_URL = 'https://www.gradesaver.com/'
TERMINAL = '/study-guide/glossary-of-terms'
def punctuations(data_str):
data_str = data_str.replace("'s", "")
for x in data_str.lower():
if x in string.punctuation:
data_str = data_str.replace(x, "")
return data_str
for book in glob.glob('book/*'):
code = book.split('/')[-1].split('.')[0]
try:
bookname = unq_code[code]
bookname = bookname.split(' by ')[0].lower()
bookname = punctuations(bookname)
bookname = bookname.replace(" ", "-")
html = requests.get(BASE_GLOSS_URL+bookname+TERMINAL).text
soup = BeautifulSoup(html)
tt = []
for term in soup.findAll("section", {"class": "linkTarget"}):
tt.append(
[term.h2.text.lower().strip(),
term.p.text.lower().strip()]
)
if len(tt):
print (f'Done: {bookname}')
data = pd.DataFrame(tt, columns=['word', 'def'])
data.to_csv(f'data/{code}.csv', \
sep='\t', \
encoding='utf-8', \
index=False)
else:
print (f'Skipped: {bookname}')
except Exception as e: print (e)
Extracting Glossary and Definitions from GradeSaver for novels extracted from Project Gutenberg
从上面的片段中可以看出,我们再次使用 BeautifulSoup python 库从 GradeSaver 数据库中提取每本书的术语表和相关定义。下图显示了作为上述代码片段的一部分生成的文件
DataFrame of Word & Definitions from GradeSaver
词汇抽取方法
我们通过提出一个分块管道来完成词汇表提取的任务,该管道在每一步都从整个列表中删除不太重要的候选词汇表单词。最后,我们有一个基于语义相似度的排名函数,它计算每个词汇表单词与上下文的相关性,并相应地对词汇表列表中的单词进行优先级排序。
我们根据从特定小说的 GradeSaver 获得的真实情况,评估我们在精度、召回和 F1 分数上的管道输出。精确度衡量的是,根据现有的事实,词汇表中产生的单词有多少是正确的。回忆测量所提出的方法产生/遗漏了多少基本事实词汇。F1 分数只是精度和召回率的单一数字表示(表示为精度(P)和召回率(R)的调和平均值)。
此外,为了简单起见,我们的目标是只提取单字,或单个单词,即词汇表级别的单词。下图显示了整个流程-
Pipeline for Extracting Glossary words from Text
如上图所示,我们从一本样本书开始,使用 NLTK 库从中提取名词和形容词。在第二步中,我们做了一些预处理,如词汇化、小写和最小单词长度限制(长度非常小的单词很可能是杂散标记、限定词、首字母缩写词等),并且可能不一定具有成为词汇表单词的复杂含义。在步骤 3 中,我们过滤所有的常用词,并且我们通过考虑三个不同的通用来源来生成这样的词的集合,即,Google books 常用词、NLTK 停用词以及来自所选语料库的高频词,如 Brown、Reuters 等。因此,位于这些语料库的单词联合列表中的任何候选词汇表单词都会被立即丢弃。在第四步中,我们移除在某种意义上是任何种类的实体的单词(使用空间)。在第 5 步中,我们使用正则表达式删除扩展的隐语。在第 6 步中,我们试图根据词频 (TF)选择对我们的语料库更具体的单词,而不是其他一些全球语料库。
最后,我们继续进行下一步,根据候选人的相关性分数对他们进行排名。我们将相关性度量定义为候选词汇表和语料库之间的余弦相似度。分数越高,术语表关键字与底层语料库的相关性就越大。在上图中,我还提到了对 P、R 和 F1 分数的逐步影响。使用上述技术,我们能够达到 0.07 的精确度、0.5 的召回率和 0.13 的 F1。
在下图中,我们展示了我们提出的系统从《哈利·波特》第一部中提取的一些有价值词汇的结果。
Glossary words extracted by our system
一些没有成功的实验
- 实验了基于图的单词/短语排序方法- TextRank 来直接提取候选词汇表单词,而不是上述的名词-形容词组块策略。
- 我们认为单词的复杂性是词汇表中单词应该是什么样的一个很好的指标。为此,我们考虑了书面语和口语的复杂性。
-我们使用 Flesch-Kincaid Grade Level 度量来测试书写的复杂性。
-我们计算存在的个音素的数量,以此来衡量说话的复杂程度。
- 我们还尝试基于密集向量表示和其他自定义特征(如复杂性、单词长度、相关性等)形成单词簇。并希望看到一个单独的值得使用词汇表的集群。
定义提取/生成方法
定义由两部分组成,定义和定义。 definiendum 是要定义的元素。定义提供定义的含义。在一篇写得很好的简单文章中,定义词和被定义词经常被一个动词或标点符号连接起来。我们将给定上下文中给定词汇表单词的定义提取/生成任务视为三步流水线(基于规则的挖掘- >基于 WordNet 的选择- >基于 GPT-2 的生成),每一步都有退出选项。让我们详细讨论每一个问题-
基于规则的挖掘
在基于规则的挖掘中,我们定义某些语法结构,用于从文本中提取给定关键字的定义结构。我们形成的一些模式是,例如— X 定义为 Y,X 是一个 Y,等等。这里,X 是词汇表中的单词或定义,Y 应该是含义或定义。我们使用正则表达式模式来实现这个步骤。
基于 WordNet 的选择
下一步是使用基于 WordNet 的选择策略来提取给定词汇表单词的相关定义。下图详细说明了整个过程-
WordNet-based Definition Extraction
我们首先使用 WordNet 库为上一步中找到的每个词汇表单词找到 k 个含义(一个含义就是一个意思)。我们还从文本中提取这个词汇表单词出现的第一个上下文,作为潜在的定义。通过在词汇表单词周围设置 K 个单词的窗口来定义上下文。这里只选择第一个上下文(用紫色标记)的假设是,书/文本的作者可能会在文献中尽可能早地定义或解释该词,然后在需要时重新使用它。我们知道这个假设主要适用于较长的文本,如书籍、小说等——这反映了我们的数据集。
对于给定词汇表单词的 k 个义项集合中的每个唯一义项,我们提取定义、相关示例,并与第一个上下文文本进行余弦相似性。这有助于消除歧义,并有助于为给定的单词选择最合适的含义/意义/定义。作为设计实现的一部分,人们可以选择根据相似性得分来选择顶部意义,或者可能希望根本不选择任何内容,并退回到定义提取/生成管道中的第三步。
在下图中,我们基于 WordNet 选择方案给出了定义(列 new_def) 。
Definitions extracted as per WordNet selection method
从 WordNet 中提取定义的代码
我们首先从文本中获取词汇表单词的第一个出现的上下文。
import codecs
import os
import pandas as pd
import glob
import nltk
nltk.download('punkt')
from nltk.corpus import PlaintextCorpusReader
def get_context(c):
try:
result = text.concordance_list(c)[0]
left_of_query = ' '.join(result.left)
query = result.query
right_of_query = ' '.join(result.right)
return left_of_query + ' ' + query + ' ' + right_of_query
except:
return ''
generated_dfs = []
BASE_DIR = 'data'
for book in glob.glob('book/*'):
book_name = book.split('/')[-1].split('.')[0]
try:
DATA_DIR = codecs.open('book/' + book_name + '.txt', \
'rb', \
encoding='utf-8').readlines()
true_data = pd.read_csv(
'data/'+book_name+'.csv', \
sep='\t')
full_data = ' '.join([i.lower().strip() for i in DATA_DIR if len(i.strip())>1])
tokens = nltk.word_tokenize(full_data)
text = nltk.Text(tokens)
true_data['firstcontext'] = true_data['word'].map(lambda k: get_context(k))
generated_dfs.append(true_data)
except Exception as e:
pass
final_df = pd.concat(generated_dfs[:], axis=0)
final_df = final_df[final_df['firstcontext']!='']
final_df = final_df[['word', 'def', 'firstcontext']].reset_index()
final_df.head(5)
Getting 1st context for Glossary words extracted from the previous step (chunking+relevance pipeline)
下图显示了上述片段的输出数据框-
DataFrame with First Context from Text
接下来,我们使用 gensim KeyedVectors 加载单词向量。我们还将句子表征定义为句子中出现的单词向量的平均值。
import gensim
from gensim.models import Word2Vec
from gensim.utils import simple_preprocess
from gensim.models.keyedvectors import KeyedVectors
import numpy as np
from gensim.models import KeyedVectors
filepath = "GoogleNews-vectors-negative300.bin"
wv_from_bin = KeyedVectors.load_word2vec_format(filepath, binary=True)
ei = {}
for word, vector in zip(wv_from_bin.index_to_key, wv_from_bin.vectors):
coefs = np.asarray(vector, dtype='float32')
ei[word] = coefs
def avg_feature_vector(sentence, model, num_features):
words = sentence.split()
#feature vector is initialized as an empty array
feature_vec = np.zeros((num_features, ), dtype='float32')
n_words = 0
for word in words:
if word in embeddings_index.keys():
n_words += 1
feature_vec = np.add(feature_vec, model[word])
if (n_words > 0):
feature_vec = np.divide(feature_vec, n_words)
return feature_vec
Sentence representation with Word2Vec
接下来,我们连接 WordNet 库中存在的每个词义和示例的定义,并计算它与文本中词汇表单词的第一个上下文之间的语义相关性。最后,我们挑选具有最大相似性的一个作为候选定义。
def similarity(s1, s2):
s1_afv = avg_feature_vector(s1, model=ei, num_features=300)
s2_afv = avg_feature_vector(s2, model=ei, num_features=300)
cos = distance.cosine(s1_afv, s2_afv)
return cos
for idx in range(final_df.shape[0]):
fs = final_df.iloc[idx]['firstcontext']
w = final_df.iloc[idx]['word']
defi = final_df.iloc[idx]['def']
syns = wordnet.synsets(w)
s_dic={}
for sense in syns:
def,ex = sense.definition(), sense.examples()
sense_def = def + ' '.join(ex)
score = similarity(sense_def, fs)
s_dic[def]=score
s_sort = sorted(s_dic.items(), key=lambda k:k[1],reverse=True)[0]
final_df['new_def'][idx]=s_sort[0]
final_df['match'][idx]=s_sort[1]
基于 GPT-2 的一代
这是我们的定义提取/生成管道中的最后一步。在这里,我们在来自城市词典的公开可用的定义数据集上微调了一个中型的、预先训练好的 GPT-2 模型。我们从数据集中的 250 万个数据样本中挑选短语及其相关定义。为了进行微调,我们使用特殊的标记来格式化我们的数据记录,这有助于我们的 GPT-2 模型充当基于特定前缀文本的条件语言生成模型。
如何将 Kaggle 数据加载到渐变笔记本中
- 获得一个 Kaggle 帐户
- 转到您的帐户设置创建一个 api 令牌,并保存 kaggle.json。注意:如果您已经创建了一个 API 令牌,您可能需要创建一个新的 API 令牌。
- 将 kaggle.json 上传到此渐变笔记本
- 运行下面的单元格或在终端中运行以下命令(这可能需要一段时间)
注意:不要共享启用了 api 密钥的笔记本
现在在终端中:
mkdir ~/。kaggle/
mv kaggle.json ~/。kaggle/
pip 安装卡格尔
kaggle 数据集下载 thero hk/urban-dictionary-words-dataset
kaggle 数据集下载 adarshsng/googlenewsvectors
- 训练循环- I/O 格式- < |startoftext| >字<定义>含义< |endoftext| >
- 测试循环-输入格式- < |startoftext| >字<定义> /输出格式- 含义< |endoftext| >
这里,和是文本序列开始和结束的特殊标记,<define>
是提示标记,告诉模型开始为现有单词生成定义。
我们首先加载包含单词和定义的城市字典数据集,如下所示
import pandas as pd
train = pd.read_csv(
'urbandict-word-defs.csv', \
nrows=100000, \
error_bad_lines=False
)
new_train = train[['word', 'definition']]
new_train['word'] = new_train.word.str.lower()
new_train['definition'] = new_train.definition.str.lower()
Loading subset of UrbanDictionary dataset
接下来,我们选择适当的设备并加载相关的 GPT-2 令牌化器和模型
import torch
from transformers import GPT2Tokenizer, GPT2LMHeadModel
import numpy as np
import os
from tqdm import tqdm
import logging
logging.getLogger().setLevel(logging.CRITICAL)
import warnings
warnings.filterwarnings('ignore')
device = 'cpu'
if torch.cuda.is_available():
device = 'cuda'
tokenizer = GPT2Tokenizer.from_pretrained('gpt2-medium')
model = GPT2LMHeadModel.from_pretrained('gpt2-medium')
接下来,我们定义 dataset 类来适当地格式化每个输入示例。因为我们使用自回归模型来生成以前缀文本为条件的文本,所以我们定义了一个触发标记 <定义 > 分隔单词和相关联的定义。我们还为每个输入示例添加了开始和结束文本标记,以使模型知道开始和结束提示。我们还将从批量大小为 4 的数据集创建一个数据加载器,并将 shuffling 设置为 true,使我们的模型对原始数据集中可能存在的任何隐藏模式都具有鲁棒性。
from torch.utils.data import Dataset, DataLoader
import os
import json
import csv
class GlossaryDataset(Dataset):
def __init__(self, dataframe):
super().__init__()
self.data_list = []
self.end_of_text_token = "<|endoftext|>"
self.start_of_text_token = "<|startoftext|>"
for i in range(dataframe.shape[0]):
data_str = f"{self.start_of_text_token} \
{new_train.iloc[i]['word']} \
<DEFINE> \
{new_train.iloc[i]['definition']} \
{self.end_of_text_token}"
self.data_list.append(data_str)
def __len__(self):
return len(self.data_list)
def __getitem__(self, item):
return self.data_list[item]
dataset = GlossaryDataset(dataframe=new_train)
data_loader = DataLoader(dataset, batch_size=4, shuffle=True)
接下来,我们定义优化器、调度器和其他参数。
from transformers import AdamW
EPOCHS = 10
LEARNING_RATE = 2e-5
device = 'cpu'
if torch.cuda.is_available():
device = 'cuda'
model = model.to(device)
model.train()
optimizer = AdamW(model.parameters(), lr=LEARNING_RATE)
最后,我们编写执行向前和向后传球的训练循环,并执行训练。
for epoch in range(EPOCHS):
print (f'Running {epoch} epoch')
for idx,sample in enumerate(data_loader):
sample_tsr = torch.tensor(tokenizer.encode(sample[0]))
sample_tsr = sample_tsr.unsqueeze(0).to(device)
outputs = model(sample_tsr, labels=sample_tsr)
loss = outputs[0]
loss.backward()
optimizer.step()
scheduler.step()
optimizer.zero_grad()
model.zero_grad()
Training loop for GPT-2 Definition generation
在下图中,我们展示了基于 GPT-2 的推理定义生成的一些结果。
Definition generated from our fine-tuned GPT-2 language model
总结想法
在这篇博客中,我们讨论了一种种子方法,该方法使用自然语言处理技术来提取词汇表和相关的定义,大部分是在无监督的情况下进行的。我们坚信,这可以为建设更先进、更坚固的管道奠定基础。此外,我们认为在某种客观尺度上对这类任务的评估,如精确度、回忆,并不完全合理,最终应该由人类来评估。因为这本书的作者在列出词汇表的时候也考虑了他的目标读者、人口统计、文化等等。
我希望你喜欢阅读这篇文章。谢谢大家!
Adoro:一个 deepfake 应用程序,让你只用自拍就能表演意大利歌剧经典
原文:https://blog.paperspace.com/adoro-deepfake-your-selfie-first-order-motion-model/
https://www.youtube.com/embed/S2rz42I230c?feature=oembed
Video walkthrough of this tutorial
我们最近推出了一个新的渐变网站,其特色是一个名为“用渐变制作任何东西”的新活动
在其中一个短片中,Stewart 询问他是否可以使用渐变来制作一个非常特定的机器学习应用程序——这将让他创建一个他唱意大利歌剧经典的深度假像。
如果你还没看过这个视频,可以看看:
https://www.youtube.com/embed/jeRInJiPYRc?feature=oembed
A new Gradient campaign launched recently called "Make anything with Gradient."
今天,我们将向您展示一个名为 Adoro 的渐变新启动工作流,它将帮助您创建自己版本的 deepfake 应用程序——配有一个训练有素的模型,可以将驾驶视频中的运动转换为静态自拍。
我们将部署的应用程序如下所示:
The Adoro web app that we will create with Gradient Workflows will have a simple UI for taking a selfie and submitting that selfie for motion transfer from the driving video.
我们将能够使用该应用程序创建的输出如下所示:
https://www.youtube.com/embed/tFz1w6PFLoU?feature=oembed
The Adoro app will allow us to generate lip sync videos like this one!
我们开始吧!
Adoro starter 项目
Adoro 利用一个名为一阶运动模型的项目制作图像动画。该模型最初由一组研究人员在 NeurIPS 2019 发布,原始源代码可在 Github 这里获得。
该工作流将部署一个基本的 web 应用程序,其中包含一个用户界面,用于与一阶运动模型进行交互,以创建新的 deepfaked 视频。
Adoro 模板预装了我们创建的驱动程序视频。司机视频是一个 256 像素 x 256px 像素的视频,一名演员对口型永恒的经典“唐娜 e 移动。”
https://www.youtube.com/embed/PM-dNrr9ZjQ?feature=oembed
The driver video provides the reference for the final output video. The first order model will apply the head and facial movements of the driver video onto the static image that we provide.
我们接下来要做的是创建一个工作流,将动作和音乐从车手视频传输到我们指定的静态自拍图像上。
创建一个新项目并连接 Github
If this is your first Gradient Project, you'll be prompted to create a project and give it a name.
我们需要做的第一件事是创建一个新项目来包含我们的 Adoro 工作流。从渐变控制台,创建一个新的项目,并给它一个名字。
接下来,我们将在新项目中创建新的工作流。
在这一点上,如果我们还没有授予 Github 渐变访问权限,我们现在就想这么做。这将允许 Gradient 将 Adoro 工作流写入 Github 中的新存储库。
We will want to grant Gradient access to at least one repo on Github if we haven't already done so.
接下来,我们将选择 Adoro 模板。我们还将选择我们想要写入的 Github 帐户或组织,我们将为我们的 repo 命名。
Create a new Workflow from the Gradient console and select the Adoro template.
我们现在可以创建新的工作流程了!一旦我们选择Create Workflow
,我们应该能够看到我们的工作流程正在运行。
Once we start the Adoro Workflow we'll be taken to the Workflow view to see the processes running.
Adoro 工作流包含两个任务:ValidateModel
和CreateDeployment
。
工作流的 YAML 如下所示:
'on':
github:
branches:
only: main
jobs:
ValidateModel:
uses: script@v1
with:
image: paperspace/first-order-model
script: |-
python3 demo.py --relative --adapt_scale \
--config config/vox-256.yaml \
--checkpoint /inputs/model/vox.pt \
--driving_video /app/driving.mp4 \
--source_image /app/source.png \
--result_video /app/result.mp4
inputs:
model:
type: dataset
with:
ref: gradient/voxpt
outputs:
results:
type: dataset
with:
ref: demo-dataset
resources:
instance-type: P4000
CreateDeployment:
uses: script@v1
with:
image: paperspace/gradient-sdk
script: >-
cat > ./deployment.yaml <<EOF
image: paperspace/adoro-server:1.0.0
port: 8000
resources:
replicas: 1
instanceType: P4000
EOF
apt update > /dev/null
apt-get install -y jq
gradient deployments create --name adoro-${RANDOM} --projectId
${PROJECT_ID} --spec ./deployment.yaml |awk '{print $3}'>
./deployment.id
echo
echo "Adoro can be accessed at URL:"
gradient deployments get --id $(cat ./deployment.id)|jq
'.deploymentSpecs[0].endpointUrl' -r
needs:
- ValidateModel
resources:
instance-type: C3
context:
event:
github:
ref: b3355a1c37af89f014709f300dc8b3f335673229
url: https://github.com/dbanys/adoro-project
让我们分解一下我们正在做的事情。
首先,我们将告诉工作流使用我们正在创建的新回购的主要分支:
'on':
github:
branches:
only: main
接下来,我们将定义一对作业,ValidateModel
和CreateDeployment
。在ValidateModel
中,我们将告诉 Gradient 使用 P4000 GPU 实例类型从名为paper space/first-order-model的 DockerHub 中提取图像。
jobs:
ValidateModel:
uses: script@v1
with:
image: paperspace/first-order-model
script: |-
python3 demo.py --relative --adapt_scale \
--config config/vox-256.yaml \
--checkpoint /inputs/model/vox.pt \
--driving_video /app/driving.mp4 \
--source_image /app/source.png \
--result_video /app/result.mp4
inputs:
model:
type: dataset
with:
ref: gradient/voxpt
outputs:
results:
type: dataset
with:
ref: demo-dataset
resources:
instance-type: P4000
在ValidateModel
步骤中,我们还为要执行的demo.py
文件定义了许多变量,包括配置和检查点文件,以及驾驶视频(上面有演员的视频)、源图像(我们的自拍)和输出视频。
接下来,我们定义CreateDeployment
步骤,该步骤将使用一个 C3 CPU 实例类型来托管一个非常简单的 web 服务器。网络服务器由 docker hub imagepaper space/adoro-server定义。
jobs:
ValidateModel:
...
CreateDeployment:
uses: script@v1
with:
image: paperspace/gradient-sdk
script: >-
cat > ./deployment.yaml <<EOF
image: paperspace/adoro-server:1.0.0
port: 8000
resources:
replicas: 1
instanceType: P4000
EOF
apt update > /dev/null
apt-get install -y jq
gradient deployments create --name adoro-${RANDOM} --projectId
${PROJECT_ID} --spec ./deployment.yaml |awk '{print $3}'>
./deployment.id
echo
echo "Adoro can be accessed at URL:"
gradient deployments get --id $(cat ./deployment.id)|jq
'.deploymentSpecs[0].endpointUrl' -r
needs:
- ValidateModel
resources:
instance-type: C3
最后,我们的工作流定义了一个上下文参数。这是指触发这个特定工作流运行的特定 git 提交。
context:
event:
github:
ref: b3355a1c37af89f014709f300dc8b3f335673229
url: https://github.com/dbanys/adoro-project
一旦我们的工作流运行完毕,让我们来看看日志。
When the Workflow finishes running we should see a web address specified in the logs.
一切看起来都很好!
查看日志,我们可以看到工作流的CreateDeployment
步骤已经生成了一个公共 URL 来查看我们新的 we 应用程序。日志应该如下所示(注意,您自己的应用程序将有一个不同的 URL):
Adoro can be accessed at URL:
da9503c3f4d6c4d90ace21c3769fa81af.clg07azjl.paperspacegradient.com
这是我们访问 URL 时应该看到的内容:
The Adoro Workflow deploys the Adoro web app to a public web address
从这里开始,我们需要做的就是抓拍一张照片,让应用程序发挥它的魔力!
我们很想看看你的阿多罗!让我们知道你能创造什么!
高级递归神经网络:深度递归神经网络
原文:https://blog.paperspace.com/advanced-recurrent-neural-networks-deep-rnns/
本系列为不同的递归神经网络(rnn)提供了高级指南。您将了解网络本身、它们的架构、应用,以及如何使用 Keras 将模型变为现实。
在本教程中,我们将从研究深层 RNNs 开始。具体来说,我们将涵盖:
- 想法是:语音识别
- 为什么是 RNN?
- 将深度引入网络:深度 RNNs
- 数学概念
- 使用低沉的 RNN 产生音乐
- 结论
我们开始吧!
想法是:语音识别
语音识别是计算机对语音中文本的识别。我们认为,言语本质上是连续的。如果你要对深度学习中的一个语音识别问题进行建模,你认为哪个模型最适合这个任务?
正如我们将看到的,它很可能是一个递归神经网络,或 RNN。
为什么是 RNN?
手写识别类似于语音识别,至少在数据类型方面是如此——它们都是连续的。有趣的是,从在线手写识别和离线手写识别研究中可以看出,rnn 已经在识别手写方面取得了最先进的成果。rnn 已经被证明可以利用顺序数据产生有效的输出。
因此,我们可以乐观地说,RNN 也最适合语音识别模型。
然而,出乎意料的是,当语音识别 RNN 模型拟合到数据上时,结果并不乐观。与典型的 RNN 相比,深度前馈神经网络产生了更好的精度。尽管 RNNs 在手写识别方面表现得相当好,但它并不被认为是语音识别任务的合适选择。
在分析了 RNNs 失败的原因后,研究人员提出了一个获得更高精度的可能解决方案:通过在网络中引入深度,类似于深度前馈神经网络的组成方式。
将深度引入网络:深度 RNNs
RNN 对时间来说是深刻的。但是如果它在空间上也是很深的,就像在一个前馈网络中一样呢?这是启发研究人员探索深度递归神经网络或深度 RNNs 的基本概念。
在典型的深 RNN 中,循环操作被扩展到多个隐藏单元。
A 2-Layer Deep RNN
通过向隐藏单元引入深度,也可以使 RNN 变深。
Multi-Layer Deep RNN - A Varied Representation
该模型增加了变量从时间\(t\)到时间\(t + 1\)所经过的距离。它可以将简单的 rnn、gru 或 LSTMs 作为其隐藏单元。它通过从所有端点捕获数据来帮助对变化的数据表示进行建模,并允许将多个/单个隐藏状态传递给后续层的多个隐藏状态。
数学概念
深 RNN 中的隐藏状态可以由下面的等式给出:
其中\(\phi\)是激活函数,\(W\)是权重矩阵,\(b\)是偏差。
隐藏状态\(H_t\)的输出由下式给出:
深度 RNN 的训练类似于时间反向传播(BPTT)算法,如同在 RNN 中一样,但是具有附加的隐藏单元。
现在你已经知道什么是深沉的 RNN 了,在下一节我们将使用深沉的 RNN 和喀拉制作一个音乐生成器。
使用低沉的 RNN 产生音乐
音乐是终极语言。从未知的时代开始,我们就一直在创造和演奏美丽的旋律。在这种情况下,你认为计算机可以生成与我们(和一套乐器)相当的音符吗?
幸运的是,通过从大量已经存在的作品中学习,神经网络确实有能力产生一种新的音乐。使用计算机创作音乐是神经网络的一个令人兴奋的应用。由神经网络创作的音乐既有和声又有旋律,甚至可以作为人类的作品。
在直接进入代码之前,让我们先了解一下用于训练网络的音乐表示。
乐器数字接口(MIDI)
MIDI 是由 MIDI 乐器解释以演奏音乐的一系列信息。为了有意义地利用 MIDI 对象,我们将使用 music21 Python 库,它有助于获取音符和理解乐谱。
下面是一段使用 music21 库读取的 MIDI 文件的摘录:
[<music21.stream.Part 0x7fba822de4d0>,
<music21.instrument.Piano 'Piano'>,
<music21.instrument.Piano 'Piano'>,
<music21.tempo.MetronomeMark Quarter=125.0>,
<music21.key.Key of C major>,
<music21.meter.TimeSignature 4/4>,
<music21.chord.Chord D2 G2>,
<music21.note.Rest rest>,
<music21.chord.Chord G2 D2>,
<music21.note.Rest rest>,
<music21.note.Note C>,
<music21.note.Rest rest>,
<music21.chord.Chord D2 G2>,
<music21.note.Rest rest>,
<music21.chord.Chord G2 D2>,
<music21.note.Rest rest>,
<music21.note.Note C>,
<music21.note.Rest rest>,
<music21.chord.Chord D2 G2>,
<music21.chord.Chord G2 D2>,
<music21.note.Rest rest>,
<music21.note.Note C>,
<music21.note.Rest rest>,
<music21.chord.Chord G2 D2>]
这里:
- 是 music21 对象的基本容器。
instrument
定义使用的仪器。tempo
使用文本字符串和数字指定基础节拍的速度。note
包含有关音符的音高、八度和偏移的信息。音高是频率,八度是音高的差异,偏移是指音符的位置。chord
类似于note
对象,但有多个音高。
我们将使用 music21 库来理解 MIDI 文件,训练 RNN,并创作我们自己的音乐。
深埋地磁石
从你目前所学的,你可以猜测音乐属于哪种数据。因为音乐是由一系列音符组成的,所以我们可以说音乐是序列数据。
在本教程中,我们将使用长时短时记忆(LSTM)网络来长时间记忆信息,这是音乐生成所必需的。由于需要捕捉各种音符和互动,我们将特别使用深沉的 LSTM。
步骤 1:导入数据集
首先导入洋红项目下可用的 Groove MIDI 数据集(下载链接)。它包含大约 1,150 个 MIDI 文件和超过 22,000 个击鼓小节。对所有数据的训练消耗大量时间和系统资源。因此,让我们导入 MIDI 文件的一小部分。
为此,在数据集目录上使用glob.glob()
方法来过滤.mid
文件。
import glob
songs = glob.glob('groovemagenta/**/*.mid', recursive=True)
让我们打印数据集的长度。
len(songs)
# Output
1150
从数据集中随机抽取 200 个 MIDI 文件。
import random
songs = random.sample(songs, 200)
songs[:2]
# Output
['groovemagenta/groove/drummer1/session1/55_jazz_125_fill_4-4.mid',
'groovemagenta/groove/drummer1/session1/195_reggae_78_fill_4-4.mid']
现在打印由 music21 库读取的 MIDI 文件。
!pip3 install music21
from music21 import converter
file = converter.parse(
"groovemagenta/groove/drummer1/session1/55_jazz_125_fill_4-4.mid"
)
components = []
for element in file.recurse():
components.append(element)
components
# Output
[<music21.stream.Part 0x7fba822de4d0>,
<music21.instrument.Piano 'Piano'>,
<music21.instrument.Piano 'Piano'>,
<music21.tempo.MetronomeMark Quarter=125.0>,
<music21.key.Key of C major>,
<music21.meter.TimeSignature 4/4>,
<music21.chord.Chord D2 G2>,
<music21.note.Rest rest>,
<music21.chord.Chord G2 D2>,
<music21.note.Rest rest>,
<music21.note.Note C>,
<music21.note.Rest rest>,
<music21.chord.Chord D2 G2>,
<music21.note.Rest rest>,
<music21.chord.Chord G2 D2>,
<music21.note.Rest rest>,
<music21.note.Note C>,
<music21.note.Rest rest>,
<music21.chord.Chord D2 G2>,
<music21.chord.Chord G2 D2>,
<music21.note.Rest rest>,
<music21.note.Note C>,
<music21.note.Rest rest>,
<music21.chord.Chord G2 D2>]
步骤 2:将 MIDI 转换成音乐 21 音符
导入所需的库。
import pickle
from music21 import instrument, note, chord, stream
接下来,定义一个函数get_notes()
从所有的 MIDI 文件中获取音符。使用convertor.parse()
方法将每个 MIDI 文件加载到 music21 流对象中。使用这个对象来获取对应于 MIDI 文件的所有音符和和弦。此外,根据乐器划分 music21 对象。如果有仪器存在,使用recurse()
方法检查第一部分(parts[0]
中是否有可用的内部子流。获取所有的音符和和弦,并将每个音符对象的音高的字符串表示追加到一个数组中。如果是和弦,将每个音符的id
附加到由点字符连接的字符串上。
"""
Convert mid to notes
"""
def get_notes():
notes = []
for file in songs:
# Convert .mid file to stream object
midi = converter.parse(file)
notes_to_parse = []
try:
# Partition stream per unique instrument
parts = instrument.partitionByInstrument(midi)
except:
pass
# If there's an instrument, enter this branch
if parts:
# Check if there are inner substreams available
notes_to_parse = parts.parts[0].recurse()
else:
# A very important read-only property that returns a new Stream that has all
# sub-containers “flattened” within it, that is, it returns a new Stream where
# no elements nest within other elements.
notes_to_parse = midi.flat.notes
for element in notes_to_parse:
# Extract pitch if the element is a note
if isinstance(element, note.Note):
notes.append(str(element.pitch))
# Append the normal form of chord(integers) to the notes list
elif isinstance(element, chord.Chord):
notes.append(".".join(str(n) for n in element.normalOrder))
with open("notes", "wb") as filepath:
pickle.dump(notes, filepath)
return notes
步骤 3:预处理数据
我们现在必须通过将基于字符串的数据映射到基于整数的数据,使我们的数据与网络处理兼容。
首先导入所需的库。
import numpy as np
from tensorflow.keras import utils
将序列长度(即输入序列的长度)定义为 100。这意味着在每个输入序列中将有 100 个音符/和弦。
接下来,将音符映射到整数,并创建输入和输出序列。每第 101 个音符(一组 100 个音符之后的一个音符)被作为训练模型的每个输入的输出。将输入整形为 3D 数组:samples x timesteps x features
。samples
指定输入的数量;timesteps
,序列长度;和features
,每个时间步的输出数。
最后,通过将输入除以对应于音符数的值来归一化输入。现在将输出转换为一个独热编码向量。
创建一个函数prep_seq()
,将输入和输出映射到它们对应的网络兼容数据,如下面的代码所示。
"""
Mapping strings to real values
"""
def prep_seq(notes):
seq_length = 100
# Remove duplicates from the notes list
pitchnames = sorted(set(notes))
# A dict to map values with intgers
notes_to_int = dict((pitch, n) for n, pitch in enumerate(pitchnames))
net_in = []
net_out = []
# Iterate over the notes list by selecting 100 notes every time,
# and the 101st will be the sequence output
for i in range(0, len(notes) - seq_length, 1):
seq_in = notes[i : i + seq_length]
seq_out = notes[i + seq_length]
net_in.append([notes_to_int[j] for j in seq_in])
net_out.append(notes_to_int[seq_out])
number_of_patterns = len(net_in)
# Reshape the input into LSTM compatible (3D) which should have samples, timesteps & features
# Samples - One sequence is one sample. A batch consists of one or more samples.
# Time Steps - One time step is one point of observation in the sample.
# Features - One feature is one observation at a time step.
net_in = np.reshape(net_in, (number_of_patterns, seq_length, 1))
# Normalize the inputs
net_in = net_in / float(len(pitchnames))
# Categorize the outputs to one-hot encoded vector
net_out = utils.to_categorical(net_out)
return (net_in, net_out)
步骤 4:定义模型
首先分别从tensorflow.keras.models
和tensorflow.keras.layers
库中导入所需的模型和层。
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (
Activation,
LSTM,
Dense,
Dropout,
Flatten,
BatchNormalization,
)
移动到模型的架构上,首先定义一个连续的 Keras 块,并将所有的层附加到它上面。
前两层必须是 LSTM 积木。第一 LSTM 层的输入形状必须是从发送到函数net_arch()
的输入数据中导出的形状。将输出神经元的数量设置为 256(这里可以使用不同的数量;这肯定会影响您将获得的最终精度)。
LSTM 块中的return_sequences
被设置为True
,因为第一层的输出被传递到随后的 LSTM 层。return_sequences
确保保持数据的原样,而不删除序列长度属性,否则该属性将被忽略。recurrent_dropout
设置为 0.3,以忽略更新 LSTM 存储单元时使用的 30%的节点。
接下来,追加一个BatchNormalization
图层,通过对每个小批量的网络数据进行重新居中和重新缩放来归一化输入。这产生了正则化效果,并强制使用更少的训练时段,因为它减少了层之间的相互依赖性。
添加一个Dropout
层来调整输出,防止在训练阶段过度拟合。设 0.3 为辍学率。这里的 0.3 意味着 30%的输入神经元将被无效。
添加一个由 128 个神经元组成的Dense
层,其中每个输入节点都连接到一个输出节点。
现在添加 ReLU 激活。此外,执行批量标准化并添加 dropout。然后将先前的密集层输出映射到由对应于音符数量的节点组成的密集层。添加 softmax 激活以在预测阶段生成每个音符的最终概率。
用categorical_crossentropy
loss 和adam
optimizer 编译模型。
最后,在函数net_arch()
中定义模型的架构,该函数接受模型的输入数据和输出数据长度作为参数。
添加以下代码来定义您的网络体系结构。
"""
Network Architecture
"""
def net_arch(net_in, n_vocab):
model = Sequential()
# 256 - dimensionality of the output space
model.add(
LSTM(
256,
input_shape=net_in.shape[1:],
return_sequences=True,
recurrent_dropout=0.3,
)
)
model.add(LSTM(256))
model.add(BatchNormalization())
model.add(Dropout(0.3))
model.add(Dense(128))
model.add(Activation("relu"))
model.add(BatchNormalization())
model.add(Dropout(0.3))
model.add(Dense(n_vocab))
model.add(Activation("softmax"))
model.compile(loss="categorical_crossentropy", optimizer="adam")
return model
第五步:训练你的模型
在训练阶段保存模型的权重总是一个好的做法。这样你就不需要在每次修改的时候一次又一次的训练模型。
添加以下代码来检查模型的权重。
from tensorflow.keras.callbacks import ModelCheckpoint
def train(model, net_in, net_out, epochs):
filepath = "weights.best.music3.hdf5"
checkpoint = ModelCheckpoint(
filepath, monitor="loss", verbose=0, save_best_only=True
)
model.fit(net_in, net_out, epochs=epochs, batch_size=32, callbacks=[checkpoint])
model.fit()
方法用于将模型拟合到训练数据上,其中批量被定义为 32。
获取音乐笔记,准备数据,初始化模型,定义训练参数,调用train()
方法开始训练模型。
epochs = 30
notes = get_notes()
n_vocab = len(set(notes))
net_in, net_out = prep_seq(notes)
model = net_arch(net_in, n_vocab)
train(model, net_in, net_out, epochs)
# Output
Epoch 1/30
2112/2112 [==============================] - 694s 329ms/step - loss: 3.5335
Epoch 2/30
2112/2112 [==============================] - 696s 330ms/step - loss: 3.2389
Epoch 3/30
2112/2112 [==============================] - 714s 338ms/step - loss: 3.2018
Epoch 4/30
2112/2112 [==============================] - 706s 334ms/step - loss: 3.1599
Epoch 5/30
2112/2112 [==============================] - 704s 333ms/step - loss: 3.0997
Epoch 6/30
2112/2112 [==============================] - 719s 340ms/step - loss: 3.0741
Epoch 7/30
2112/2112 [==============================] - 717s 339ms/step - loss: 3.0482
Epoch 8/30
2112/2112 [==============================] - 733s 347ms/step - loss: 3.0251
Epoch 9/30
2112/2112 [==============================] - 701s 332ms/step - loss: 2.9777
Epoch 10/30
2112/2112 [==============================] - 707s 335ms/step - loss: 2.9390
Epoch 11/30
2112/2112 [==============================] - 708s 335ms/step - loss: 2.8909
Epoch 12/30
2112/2112 [==============================] - 720s 341ms/step - loss: 2.8442
Epoch 13/30
2112/2112 [==============================] - 711s 337ms/step - loss: 2.8076
Epoch 14/30
2112/2112 [==============================] - 728s 345ms/step - loss: 2.7724
Epoch 15/30
2112/2112 [==============================] - 738s 350ms/step - loss: 2.7383
Epoch 16/30
2112/2112 [==============================] - 736s 349ms/step - loss: 2.7065
Epoch 17/30
2112/2112 [==============================] - 740s 350ms/step - loss: 2.6745
Epoch 18/30
2112/2112 [==============================] - 795s 376ms/step - loss: 2.6366
Epoch 19/30
2112/2112 [==============================] - 808s 383ms/step - loss: 2.6043
Epoch 20/30
2112/2112 [==============================] - 724s 343ms/step - loss: 2.5665
Epoch 21/30
2112/2112 [==============================] - 726s 344ms/step - loss: 2.5252
Epoch 22/30
2112/2112 [==============================] - 720s 341ms/step - loss: 2.4909
Epoch 23/30
2112/2112 [==============================] - 753s 357ms/step - loss: 2.4574
Epoch 24/30
2112/2112 [==============================] - 807s 382ms/step - loss: 2.4170
Epoch 25/30
2112/2112 [==============================] - 828s 392ms/step - loss: 2.3848
Epoch 26/30
2112/2112 [==============================] - 833s 394ms/step - loss: 2.3528
Epoch 27/30
2112/2112 [==============================] - 825s 391ms/step - loss: 2.3190
Epoch 28/30
2112/2112 [==============================] - 805s 381ms/step - loss: 2.2915
Epoch 29/30
2112/2112 [==============================] - 812s 384ms/step - loss: 2.2632
Epoch 30/30
2112/2112 [==============================] - 816s 386ms/step - loss: 2.2330
步骤 6:根据模型生成预测
该步骤由以下子步骤组成:
- 获取已经存储的音符
- 生成输入序列
- 规范化输入序列
- 通过传递新的输入来定义模型
- 加载训练模型时存储的先前权重
- 预测随机选择的输入序列的输出
- 将输出转换为 MIDI 并保存!
创建一个函数generate()
来适应所有这些步骤。
"""
Generate a Prediction
"""
def generate():
# Load music notes
with open("notes", "rb") as filepath:
notes = pickle.load(filepath)
pitchnames = sorted(set(notes))
n_vocab = len(pitchnames)
print("Start music generation.")
net_in = get_inputseq(notes, pitchnames, n_vocab)
normalized_in = np.reshape(net_in, (len(net_in), 100, 1))
normalized_in = normalized_in / float(n_vocab)
model = net_arch(normalized_in, n_vocab)
model.load_weights("weights.best.music3.hdf5")
prediction_output = generate_notes(model, net_in, pitchnames, n_vocab)
create_midi(prediction_output)
get_inputseq()
函数返回一组输入序列。这已经在上面的步骤 3 中探讨过了。
"""
Generate input sequences
"""
def get_inputseq(notes, pitchnames, n_vocab):
note_to_int = dict((pitch, number) for number, pitch in enumerate(pitchnames))
sequence_length = 100
network_input = []
for i in range(0, len(notes) - sequence_length, 1):
sequence_in = notes[i : i + sequence_length]
network_input.append([note_to_int[char] for char in sequence_in])
return network_input
您现在可以从随机选取的输入序列中生成 500 个音符。选择 softmax 激活返回的最高概率,并将编码数字与其对应的音符反向映射。将已经训练好的字符的输出追加到输入序列的末尾,并从后续字符开始下一次迭代。这样,我们通过在整个输入序列中从一个字符移动到下一个字符来生成 500 个音符。
This is how the training phase proceeds. AFBCD is the input sequence being fed into the network (The actual input length would be 100 in our model). The model first analyzes note 'A' and outputs a prediction 'E' which is then appended at the end of the sequence. The sequence FBCDE is given as the subsequent input to the network.
"""
Predict the notes from a random input sequence
"""
def generate_notes(model, net_in, notesnames, n_vocab):
start = np.random.randint(0, len(net_in) - 1)
int_to_note = dict((number, note) for number, note in enumerate(notesnames))
pattern = net_in[start]
prediction_output = []
print("Generating notes")
# Generate 500 notes
for note_index in range(500):
prediction_input = np.reshape(pattern, (1, len(pattern), 1))
prediction_input = prediction_input / float(n_vocab)
prediction = model.predict(prediction_input, verbose=0)
index = np.argmax(prediction)
result = int_to_note[index]
prediction_output.append(result)
# Add the generated index of the character,
# and proceed by not considering the first char in each iteration
pattern.append(index)
pattern = pattern[1 : len(pattern)]
print("Notes generated")
return prediction_output
最后,将音符转换成 MIDI 格式。
创建一个函数create_midi()
并将预测的编码输出作为参数传递给它。确定音符是音符还是和弦,分别适当生成note
和chord
对象。如果是和弦,将它拆分成一个音符数组,并为每个项目创建一个音符对象。将这组音符附加到一个chord
对象和一个输出数组。如果是注释,只需创建一个note
对象,并将其附加到输出数组中。
最后,将 MIDI 输出保存到一个test_output.mid
文件。
"""
Convert the notes to MIDI format
"""
def create_midi(prediction_output):
offset = 0
output_notes = []
# Create Note and Chord objects
for pattern in prediction_output:
# Chord
if ("." in pattern) or pattern.isdigit():
notes_in_chord = pattern.split(".")
notes = []
for current_note in notes_in_chord:
new_note = note.Note(int(current_note))
new_note.storedInstrument = instrument.Piano()
notes.append(new_note)
new_chord = chord.Chord(notes)
new_chord.offset = offset
output_notes.append(new_chord)
# Note
else:
new_note = note.Note(pattern)
new_note.offset = offset
new_note.storedInstrument = instrument.Piano()
output_notes.append(new_note)
# Increase offset so that notes do not get clumsy
offset += 0.5
midi_stream = stream.Stream(output_notes)
print("MIDI file save")
midi_stream.write("midi", fp="test_output.mid")
现在调用generate()
函数。
generate()
您可以通过将生成的 mid 文件上传到 MIDI 播放器来播放它。
这个 SoundCloud tune 是通过在 NVIDIA K80 GPU 上训练模型大约 6 个小时 30 个历元而生成的。
结论
在本教程中,您已经学习了什么是深 RNN,以及它如何优于基本 RNN。你也训练了一个深度 LSTM 模型来产生音乐。
深度 RNN (LSTM)模型从它的学习中产生了一个相当过得去的曲调,尽管由于时代的数量较少,音乐可能看起来单调。我想说这是一个好的开始!
为了获得更好的音符和和弦,我建议你四处弹奏并修改网络的参数。您可以调整超参数,增加历元的数量,并修改网络的深度,以检查您是否能够生成更好的结果。
在本系列的下一部分,我们将讨论编码器-解码器序列到序列模型。
参考
斯库尔德古典钢琴作曲家
对图像网络的对抗性攻击:第一部分
深度学习作为一个领域已经存在了 50 多年,但是,直到十年前,这些算法还不能很好地用于任何有意义的任务。计算的局限性是一个很难跨越的障碍。因此,当他们未能做正确的事情时,这被认为是典型的,而不是例外。今天,该领域已经发展到这些网络被用于一些关键的真实世界应用的程度。虽然这些模型可能在精选数据集上超过人类水平的性能,但当面对微不足道的敌对攻击时,它们会惨败。用伊恩·古德费勒(Ian Goodfellow)的话说,“我们已经到了机器学习行得通的地步,但可能很容易被打破”。
2014 年,来自谷歌和 NYU 的一个研究小组表明,CNN 很容易被愚弄,只要小心翼翼地给图像添加一些噪声。虽然这些扰动对于人眼来说是察觉不到的,但是它们会导致分类器产生不正确的输出。这里有一个例子。
左边的图像被正确分类为校车,但通过添加少量噪声,模型被迫将其分类为鸵鸟。对于人类观察者来说,左图像和右图像之间没有区别。
虽然这种攻击可能看起来相当温和,但考虑一下无人驾驶汽车的场景。 Metzen 等人在他们的论文“针对语义图像分割的通用对抗性扰动”中显示了通用噪声的存在,该噪声是输入不可知的,并且在大多数输入上愚弄了模型。
在上一行中,左边的图像是分割模型的输入,右边的图像是生成的输出。在较低的行中,图像被普遍的敌对噪声干扰,该噪声去除了行人目标类别,而在其他情况下使分割几乎不变。
有了这个例子,这种攻击的后果甚至会危及生命就更清楚了。因此,研究它们并降低它们的风险是很重要的。
敌对攻击的类型
根据黑客对目标模型的访问权限,对抗性攻击主要可以分为这两类。
白盒攻击
在这种情况下,攻击者可以完全访问深度学习模型或防御方案,或者攻击者在训练阶段具有影响力。在这个阶段,训练样本可能被破坏,或者训练集可能被对立的图像污染,以改变模型的参数。
由于安全措施的存在,这种类型的攻击非常罕见。此外,相比之下,由于攻击者在培训阶段就可以访问,所以这些也是最强大的攻击类型。因此,白盒攻击用于评估模型的健壮性及其在开发过程中的防御。
黑盒攻击
顾名思义,攻击者没有任何关于模型或防御机制的信息。尽管执行黑盒攻击有更大的困难,但这是最流行和最多样的攻击类别,因此当再现外部敌对攻击时,必须首先准备好这些攻击。
黑盒攻击的一个著名子类是 探索性攻击 ,其中攻击者的目标是通过发送敌对图像的变化并观察模型对其的反应来探查模型的参数和成本函数。然后,攻击者试图通过训练一个替代品或代理模型,通过这些输入/输出对来复制模型。这种输入/输出攻击通常是执行黑盒攻击的第一步。
尽管与白盒攻击相比,在执行这些攻击时会遇到更大的困难,但是这些攻击更加危险,因为这里存在可以使用的普遍扰动(噪声)。这种“可转移性”有助于攻击者使用相同的扰动来欺骗不同的网络,这在白盒攻击中是不可能的,因为扰动取决于模型的权重和架构。
还有“灰箱攻击”,这是不言自明的,我不会在这里详细说明,因为对这一类别的研究非常有限。
ML 模型的盲点
到目前为止,我们已经看到了对抗性攻击的不同设计方式。难道你不好奇这些攻击暴露出的训练模型的根本弱点是什么吗?
这个问题有很多假设。这里,我们将讨论这篇论文中提出的线性假设。作者认为,神经网络是线性分类器的局部近似。LSTMs、ReLu、Maxout 网络都被有意设计成以线性方式运行,以便更容易优化。出于同样的原因,向非线性方向发展的模型,例如 sigmoid 网络,被仔细地调整以将它们的大部分时间花费在非饱和的、更线性的状态中。
这导致快速和容易的扰动。添加到靠近决策边界的实例的小噪声可以帮助它越过决策边界而落入错误的类中。我们在本文后面讨论的一些著名的对抗算法和防御机制就是基于这个假设。如需更深入的解释,请查看该文件本身。
对抗性攻击的算法
给图像添加随机噪声会骗过分类器吗?答案是,没有!用于创建噪声/扰动的算法基本上是优化,其通常利用 ML 模型的泛化缺陷来在原始图像中插入扰动。有很多开源的算法可以产生对抗性攻击。
除了讨论这些算法,我将讨论 3 种不同的方式来制定对抗性攻击,以及使用这些公式的算法。
1.最大允许攻击
为获得扰动图像而执行的优化是在一个约束条件下完成的。让我们来看看这门课的一般方程。
这个等式可能看起来有点可怕,但是让我们一点一点地分解它。
- 对于有针对性的对抗性攻击,我们希望攻击后我们的图像落在类别 t 中,gt(x)表示 x 落在 t 中的概率。
等式的第一部分表示除了 t 之外的任何类的 x 的最大概率。总的来说,与其他类相比,第一个方程试图最大化预测错误类的概率。我们不仅希望分类器失败,而且希望以最高的置信度失败。 - 第二个等式是约束。还记得我们讨论过,对于人眼来说,扰动后的图像与原始图像是无法区分的。这个等式在差值上加了一个上限η,使之成为可能。
唷,那不算多。现在让我们看看实现这个公式的著名算法。
快速梯度符号法
这是一个白盒策略,因为它使用模型的梯度来创建扰动。然后,这种扰动被添加到图像中,并由上限ε控制。
不要太担心等式的其他部分。在我们实现这个算法的博客文章的后面部分,我们将深入探讨这个问题。
还有一种 FGSM 的迭代版本,称为基本迭代方法(BIM)。它使用小步长迭代生成对立的例子。基本上,每一步的输入都是最后一步的扰动图像。与基本 FGSM 相比,通过多次迭代生成的对抗图像在欺骗模型方面更好,但是它不如 FGSM 快。
2.最小范数攻击
这些也是受约束的攻击,如最大允许攻击,但这里的约束有点不同。
这里,与第一类攻击相比,要优化的等式及其优化的约束条件是相反的。所以,从根本上说,这不是一个很不同的方法。
我们希望最小化扰动幅度,同时还确保新图像 x 被分类到目标类别 t 中。如果最好的扰动图像( x0 )仍然不能使目标值为负,那么我们可以说,对于特定的输入 x,攻击分类器是不可能的,这就是为什么我们考虑这个约束优化。
现在,让我们看看实现它的算法。
deepcool
这种攻击是最小范数攻击的推广。不是有针对性的攻击,所以约束改为非线性函数 g(x)=0。对于二元分类问题,优化被公式化为
这里, g(x)=0 也可以被认为是分离两个类别的非线性决策边界。
这是数学部分。
在算法上,它被设计为执行分类器的迭代线性化,以产生足以改变分类标签的最小扰动。
简单地说,对于一个给定的输入 x,它为这个输入找到最近的决策边界(假设一个多类问题),并且将反复地给它添加细微的扰动,直到输入越过决策边界。
3.基于正则化的攻击
最大允许攻击和最小范数攻击是基于我们上面讨论的线性假设。这些方法不假设线性化的存在。对于高级分类器,如深度神经网络,求解包含约束的优化通常非常困难。基于正则化的攻击试图通过解除约束来解决这个问题。
λ>0 是正则化参数,它试图同时最小化两个冲突的目标。等式的两个部分是冲突的,因为我们想要错误类别的概率与最大程度混合的其他类别的概率之间的差异,同时还保持低扰动。如果我们优化等式的一部分,另一部分就变得非最优。λ因此用于控制这两项的重要性。这个算法也是迭代的。
稍微简化一下上面的方程,我们就有了 f(x) = x + λy,现在在做迭代的时候,如果 y→∞,λ>1,f(x)→∞也是。在没有约束的情况下,这个问题可能变得无界,并且可能无限循环。
卡里尼和瓦格纳攻击
该算法属于正则化攻击,但它试图解决无界问题。因此,当等式的第二部分小于等于 0 时,我们扰动的图像x[o]已经在目标类中,并且没有必要再扰动它。在这种方法中,这是通过使用一种类似 ReLu 的叫做整流器的功能来实现的。
它将负值裁剪为 0,并保持正值不变。
我们已经讨论了敌对攻击的三个主要类别。是时候实施一个了!
FGSM 的实施
这是 Ian GoodFellow 等人在 2014 年的这篇论文中提出的第一批攻击策略之一,他还提出了我们不久前讨论过的线性假设。这是一种白盒方法,使用神经网络的梯度来产生扰动。它计算相对于输入图像的最大混合损失的损失梯度。这与训练期间发生的情况相反,损失的梯度是相对于模型参数计算的,随机梯度下降使其最小化。
这里,x 表示原始图像,y 表示正确的标签,θ和 J 分别是模型参数和损失函数。ε是可以插入 x 的最大扰动量,由于攻击是在训练后进行的,并且需要单次的前后传球,所以速度非常快。
现在,理论已经讲得够多了,让我们在试图愚弄一个预先训练好的模型时弄脏我们的手。我们将使用 MobileNetV2 模型的 tensorflow 实现。
import tensorflow as tf
import matplotlib as mpl
import matplotlib.pyplot as plt
pretrained_model = tf.keras.applications.MobileNetV2(include_top=True,
weights='imagenet')
pretrained_model.trainable = False
# Loading ImageNet labels
decode_predictions = tf.keras.applications.mobilenet_v2.decode_predictions
让我们定义辅助函数来预处理图像,并从 model.predict()返回的概率向量中提取标签
def preprocess(image):
image = tf.cast(image, tf.float32)
image = tf.image.resize(image, (224, 224))
image = tf.keras.applications.mobilenet_v2.preprocess_input(image)
image = image[None, ...]
return image
def get_imagenet_label(probs):
return decode_predictions(probs, top=1)[0][0]
我们将要使用的图像是一只熊猫,因为熊猫是敌对攻击世界的典型代表。(第一篇论文展示了一个带有熊猫图像的对抗性攻击,从那以后,大多数关于对抗性攻击的文章都使用了这个图像)。让我们加载图像,对其进行预处理并获取类。
image_raw = tf.io.read_file("panda.jpeg")
image = tf.image.decode_image(image_raw)
image = preprocess(image)
image_probs = pretrained_model.predict(image)
plt.figure()
plt.imshow(image[0] * 0.5 + 0.5) # To change [-1, 1] to [0,1]
_, image_class, class_confidence = get_imagenet_label(image_probs)
plt.title('{} : {:.2f}% Confidence'.format(image_class, class_confidence*100))
plt.show()
该形象以 86.27%的置信度被归类为“大熊猫”。
让我们通过获取原始图像的损失梯度来创建扰动。这些扰动将被添加到原始图像本身。
loss_function = tf.keras.losses.CategoricalCrossentropy()
def create_adversarial_pattern(input_image, input_label):
with tf.GradientTape() as tape:
tape.watch(input_image)
prediction = pretrained_model(input_image)
loss = loss_function(input_label, prediction)
# Get the gradients of the loss w.r.t to the input image.
gradient = tape.gradient(loss, input_image)
# Get the sign of the gradients to create the perturbation
signed_grad = tf.sign(gradient)
return signed_grad,gradient
让我们也想象一下。
# Get the input label of the image.
class_idx = 388 # index of the giant_panda class
label = tf.one_hot(class_idx, image_probs.shape[-1])
label = tf.reshape(label, (1, image_probs.shape[-1]))
perturbations,gradient = create_adversarial_pattern(image, label)
plt.imshow(perturbations[0] * 0.5 + 0.5);
事先确定正确的ε值相当棘手。因此,我们将尝试多个值。
epsilons = [0, 0.01,0.03,0.1, 0.15,0.3]
descriptions = [('Epsilon = {:0.3f}'.format(eps) if eps else 'Original Image')
for eps in epsilons]
for i, eps in enumerate(epsilons):
adv_x = image + eps*perturbations
image = tf.clip_by_value(adv_x, -1, 1)
_, label, confidence = get_imagenet_label(pretrained_model.predict(image))
axs[pos[i][0], pos[i][1]].imshow(image[0]*0.5+0.5)
axs[pos[i][0], pos[i][1]].set_title('{} \n {} : {:.2f}%'.format(descriptions[i],label, confidence*100))
当我们增加ε值时,由类别和置信度识别的错误分类增加。此外,图像看起来越来越不安。正如所料,这两者之间似乎有所取舍。
结论
在这篇文章中,我们深入探讨了对抗性攻击,处理它们的重要性,实现它们的不同类型和不同类别的算法。我们还实现了快速梯度符号方法。为了探索其他方法的实现,我建议您查看一下 cleverhans 库。现在我们知道了对抗性攻击的基本原理,学习如何减轻它们也很重要。在本系列的下一篇文章中,我计划探索这个问题。
所以,敬请期待!
参考资料:
- https://engineering . purdue . edu/Chang group/ECE 595/files/chapter 3 . pdf
- https://www . tensor flow . org/tutorials/generative/adversarial _ fgsm
- http://www . clever Hans . io/security/privacy/ml/2016/12/16/breaking-things-is-easy . html
- https://ui . adsabs . Harvard . edu/ABS/2020 ARX iv 200903728 r/abstract
对抗性自动编码器(带 Pytorch)
原文:https://blog.paperspace.com/adversarial-autoencoders-with-pytorch/
“人类和动物的大部分学习都是无监督学习。如果智能是一块蛋糕,无监督学习将是蛋糕[基础],监督学习将是蛋糕上的糖衣,强化学习将是蛋糕上的樱桃。我们知道如何制作糖衣和樱桃,但我们不知道如何制作蛋糕。”
脸书人工智能研究所所长 Yann LeCunn 教授在他的演讲中多次提到这个类比。对于无监督学习,他指的是“机器通过观察和行动来模拟环境、预测可能的未来以及理解世界如何运转的能力。”
深度生成模型是试图解决机器学习中无监督学习问题的技术之一。在这个框架中,机器学习系统需要发现未标记数据中的隐藏结构。深度生成模型有许多广泛的应用,密度估计、图像/音频去噪、压缩、场景理解、表示学习和半监督分类等等。
变分自动编码器(VAEs)允许我们在概率图形模型的框架中形式化这个问题,其中我们最大化数据的对数似然的下限。在这篇文章中,我们将看看最近开发的架构,对抗性自动编码器,它受 VAEs 的启发,但在如何将我们的数据映射到潜在维度方面给了我们更多的灵活性(如果现在这还不清楚,不要担心,我们将在文章中再次讨论这个想法)。关于对抗性自动编码器的最有趣的想法之一是如何通过使用对抗性学习将先验分布强加给神经网络的输出。
如果你想接触 Pytorch 代码,请随意访问 GitHub repo。在这篇文章中,我们将首先介绍一些关于去噪自动编码器和变型自动编码器的背景,然后跳转到对抗自动编码器,Pytorch 实现,随后的训练过程,以及一些关于使用 MNIST 数据集的解纠缠和半监督学习的实验。
背景
去噪自动编码器(dAE)
自动编码器最简单的版本是我们训练一个网络来重建它的输入。换句话说,我们希望网络以某种方式学习身份函数\(f(x) = x\)。为了使这个问题不是微不足道的,我们将条件强加给网络以通过中间层(潜在空间),该中间层的维度远低于输入的维度。在这种瓶颈条件下,网络必须压缩输入信息。因此,网络被分成两部分,编码器接收输入并创建其潜在或隐藏表示,而解码器获取该中间表示并尝试重建输入。自动编码器的损耗称为重建损耗,可以简单地定义为输入和生成样本之间的平方误差:
当输入被归一化到\([0,1]^N\)范围内时,另一个广泛使用的重建损失是交叉熵损失。
可变自动编码器(VAE)
变分自动编码器对如何构造隐藏表示施加了第二个约束。现在,潜在代码具有由设计\(p(x)\)定义的先验分布。换句话说,编码器不能自由地使用整个潜在空间,而是必须限制在这个先验分布\(p(x)\)下可能产生的隐藏代码。例如,如果潜在代码上的先验分布是均值为 0 且标准偏差为 1 的高斯分布,那么生成值为 1000 的潜在代码应该是不太可能的。
这可以被看作是对可以存储在潜在代码中的信息量的第二种正则化。这样做的好处在于,现在我们可以将该系统用作生成模型。为了创建来自数据分布\(p(x)\)的新样本,我们只需从\(p(z)\)进行采样,并通过解码器运行该样本,以重建新图像。如果不施加这个条件,则潜在代码可以自由地分布在潜在空间中,因此不可能以直接的方式对有效的潜在代码进行采样以产生输出。
为了加强这一特性,以编码器创建的分布和先前分布之间的 Kullback-Liebler (KL)散度的形式将第二项添加到损失函数中。因为 VAE 是基于概率解释的,所以使用的重建损失是前面提到的交叉熵损失。把这些放在一起,
或
在音乐中,频率被分为不同的音高类别。在英语地区,它们由字母表的前 7 个字母表示:A、B、C、D、E、F 和 g。有些人可能也知道这是 Do、re、Mi、Fa、Sol、La 和 Ti,而其他人可能知道这是 Sa、Re、Ga、Ma、Pa、Dha 和 Ni。第八个音符,或八度音,与第一个音符同名,之后该模式再次重复。
今天大多数乐器使用 12 音半音音阶,在对数音阶上将一个八度音阶分成 12 个等间距的部分。每个声部被认为是相隔半音——音乐中最小的音程。大多数乐器都被调到平均音阶,中间八度音阶上的音符 A 被调到 440 赫兹。
建立在 c 调基础上的 12 音半音音阶将每个八度音阶中的 12 个音符命名为:c、C♯/D♭、d、D♯/E♭、e、f、F♯/G♭、g、G♯/A♭、a、A♯/B♭和 b
♯发音为升,♭发音为降。升半音意味着高半音,降半音意味着低半音。
为了找到中间八度的频率,我们需要找到中间 C 的频率。中间 C 或 C4 的频率是 C5 的一半。
一旦我们有了中间的 C,我们可以创建一个大小为 13 的数组,它在 C4 的 log 和 C5 的 log 之间等距分布。我们还将从数组中移除最后一个元素(在本例中是 C5)。
代码如下所示:
import numpy as np
C5 = 2**np.linspace(np.log2(440), np.log2(880), 13)[3]
freqs = 2**np.linspace(np.log2(C5/2), np.log2(C5), 13)[:-1]
产生的频率:
array([261.6255653 , 277.18263098, 293.66476792, 311.12698372,
329.62755691, 349.22823143, 369.99442271, 391.99543598,
415.30469758, 440\. , 466.16376152, 493.88330126])
色度表示
音高可以分为两个部分:音调高度和色度。
音调高度是八度音程数。例如,中音 C 也被称为 C4。
色度特征,也称为色谱图,提供了一种可视化音乐的强大方法。色度特征与我们刚刚看到的 12 音半音阶中的 7 个音高等级或 12 个音符密切相关。色度表示将任何特定时间声音中存在的所有频率归入 12 个桶中的一个,每个桶表示一个音符。
正如我们所讨论的,比 A 音高一个八度的音仍然是 A 音,即使频率是前一个 A 音的两倍。从 0 到 11 枚举色度特征,C 音符取索引 0,后续索引用高于前一索引的半音填充。
通过用色度滤波器获得 STFT 值的点积并归一化输出,可以将短时傅立叶变换转换成色度表示。
librosa
可以使用下面的代码片段来可视化过滤器:
chroma_filters = librosa.filters.chroma(sampling_rate, 2048)
fig, ax = plt.subplots(nrows=6, ncols=2, figsize=(12,9))
for i, row in enumerate(ax):
for j, col in enumerate(row):
col.plot(chroma_filters[i + j])
plt.show()
这些图如下图所示:
Chroma Filters
我们将使用与本系列第一部分相同的音频样本。
example_name = 'nutcracker'
audio_path = librosa.ex(example_name)
x, sampling_rate = librosa.load(audio_path, sr=None)
您可以使用下面的代码片段通过librosa
库获得色度表示。
S = librosa.stft(x)**2
chroma = librosa.feature.chroma_stft(S=S, sr=sampling_rate)
fig, ax = plt.subplots(figsize=(15,9))
img = librosa.display.specshow(chroma, y_axis='chroma',
x_axis='time', ax=ax)
fig.colorbar(img, ax=ax)
ax.set(title='Chromagram')
Chromagram
发病检测
开始检测是指一套方法,允许我们通过声音来定位音符的开始。有几种方法可以做到这一点。它们可以大致分为以下几类:
- 基于能量的方法
- 基于音高的方法
- 基于相位的方法
- 监督学习
基于能量的方法
在基于能量的方法中,频谱用于测量时间-频率域中的能量变化。光谱值的一阶差过去曾被用来寻找地震的开始,但并不精确。根据心理声学原理,在较低振幅下可以更好地检测类似的频率变化。相对差分方案有助于我们更好地找到峰值。
例如,频谱 D 可以定义如下:
$ $ d _ { m }(n)= 20log_{10}(|x_m(n)|{2})-20log_{10}(|x_m(n-1)|)$ $
其中\(x_{m}_n)\)是输入信号的秘密 stft。
常用的基于能量的检测方法可以归纳如下:
$ $ m = \ frac { 1 } { n } \sum_{m=1}^{n} h(d _ { m }(n))$ $
其中$ H(x) = \frac{x + |x|}{2} $是半波整流器函数,N 是频谱 D 中的频段总数,M 是检测函数。
用移动平均进一步平滑检测函数。应用简单的峰值拾取操作来寻找开始。值超过某个阈值的峰值作为开始返回。
基于相位的方法
STFT 可视为带通滤波器,其中\(X_m(n)\)表示$m^{th} \(滤波器的输出。在只有一个稳定的正弦分量通过\)m{th}带通滤波器的情况下,$m滤波器的输出必须具有恒定或接近恒定的频率。
因此,连续展开相位值之间的差异也必须保持近似恒定。
$ $ \ phi _ { m }(n)-(n-1)\ approx \ phi _ { m }(n-1)-(n-2)$ $
或者
$ $ \ bigtariangleup \ phi _ { m }(n)\ approx \ phi _ { m }(n)-2 \ phi _ { m }(n-1)-(phi _ { m }(n-2)\ approx 0 $ $
在转换过程中,频率不是恒定的。因此$\ bigtriangleup \ phi _ { m }(n)$往往很高。
基于音高的方法
基于音高的方法使用音高特征将声音分成稳态和瞬态部分,使得仅在瞬态部分中发现开始。
在一个随意的节奏分组中,Kristoffer Jensen 提出了一个被称为感知频谱通量的检测函数,其中频带的差异由等响度等值线来衡量。
等响度等高线或弗莱彻-曼森曲线是当听者感知到稳定音调的恒定振幅时,声音在频域中的等高线。
$ $ PFS _ { n } = \sum_{m=1}{n}w(x_{m}(n)-x _ { m }(北 1)^{1/3})$$
\(X_{m}\)是 STFT 的大小,使用汉宁窗获得。\(W\)是用于获得更接近人类响度轮廓的值的频率加权,并且功率函数用于模拟强度-响度功率定律。幂函数还减少了随机振幅变化。
监督学习
神经网络也被用于检测音频信号中的开始帧。典型的基于神经网络的开始检测管道如下所示:
Source: http://eecs.qmul.ac.uk/~josh/documents/2010/Zhou Reiss - Music Onset Detection 2010.pdf
音频信号首先被转换成它的声谱图。这个声谱图被传递到一个神经网络,该网络试图将每一帧分类为开始或不开始。被分类为开始的那些然后通过像阈值处理那样的峰值拾取操作。
有几种神经网络结构可以用于解决这个问题。最受欢迎的选择是递归网络和卷积神经网络。最近,音频变压器正在成为音乐分类的流行选择。
相同的网络可以重新用于发病检测。 Miyazaki 等人举例来说,使用卷积网络和变压器模块的混合来进行开始检测。
librosa
在其 API 中具有发作检测功能,可按如下方式使用。
o_env = librosa.onset.onset_strength(x, sr=sampling_rate)
times = librosa.times_like(o_env, sr=sampling_rate)
onset_frames = librosa.onset.onset_detect(onset_envelope=o_env, sr=sampling_rate)
fig, ax = plt.subplots(nrows=2, sharex=True, figsize=(15,9))
librosa.display.specshow(S_dB, x_axis='time',
y_axis='log', ax=ax[0])
ax[0].set(title='Power spectrogram')
ax[0].label_outer()
ax[1].plot(times, o_env, label='Onset strength')
ax[1].vlines(times[onset_frames], 0, o_env.max(),
color='r', alpha=0.9,
linestyle='--', label='Onsets')
ax[1].legend()
plt.savefig('onsets.png')
可视化将能够指出通过音频播放新音符的地方的估计位置。
Onset Detection
前五秒的可视化可以这样获得。
o_env = librosa.onset.onset_strength(x, sr=sampling_rate)
total_time = librosa.get_duration(x)
five_sec_mark = int(o_env.shape[0]/total_time*5)
o_env = o_env[:five_sec_mark]
times = librosa.times_like(o_env, sr=sampling_rate)
onset_frames = librosa.onset.onset_detect(onset_envelope=o_env, sr=sampling_rate)
fig, ax = plt.subplots(nrows=2, sharex=True, figsize=(15,9))
librosa.display.specshow(S_dB[:, :five_sec_mark], x_axis='time',
y_axis='log', ax=ax[0])
ax[0].set(title='Power spectrogram')
ax[0].label_outer()
ax[1].plot(times, o_env, label='Onset strength')
ax[1].vlines(times[onset_frames], 0, o_env.max(),
color='r', alpha=0.9,
linestyle='--', label='Onsets')
ax[1].legend()
plt.savefig('onsets-5secs.png')
Onset Detection (first five seconds)
正如我们在上面看到的,在发现音符开始的地方,垂直线排列正确。垂直线是相对于幅度曲线绘制的。上面显示的声谱图具有相同的 X 轴,以便直观地比较音符开始。
节拍和速度
音乐理论中的一拍是追踪音乐中周期元素的时间单位。你可以把节拍想象成你在听歌时跺跺脚的次数。歌曲的节奏是每分钟的节拍数。在歌曲的过程中,速度可能会改变。
检测节拍和估计歌曲的节奏与开始检测密切相关。通常,检测歌曲的节拍和速度分三步完成:
- 发作检测:基于光谱通量计算发作的位置
- 周期性估计:计算发病位置的周期性模式
- 节拍位置估计:根据开始和周期信息计算节拍的位置
在音乐信号的速度和节拍估计中,作者对谱通量给出了如下定义:
其中\(h(n)\)近似于一个微分器滤波器,并且
其中\(X(m,n)\)是我们信号的 STFT。
转换\(\mathcal{F}\)采用信号的 STFT,并使用反双曲正弦函数使其通过低通滤波器和非线性压缩算法。
然后,作者使用中值滤波器来挑选出真正的音符开始,而不是低振幅峰值。
这个检测函数的结果可以被认为是一个脉冲序列,它在音符起音时具有较高的幅度。这可以通过两种方式实现。
光谱产品
这里的想法是,强谐波出现在基频的整数倍频率上。
为了找到这个频率,我们计算信号的离散傅立叶变换,并得到其所有值的乘积,从而得到增强的频率。
然后,通过挑选出对应于增强频率值的最大峰值的频率指数,可以容易地获得估计的节奏 T。节奏要求在每分钟 60 到 200 次之间。
自相关
寻找音频信号周期性的经典方法是使用自相关。ACF 或自相关函数可以定义为
$ $ r(\ tau)= \ sum _ { k } p(k+\ tau)p(k)$ $
其中\(\tau\)是滞后值。为了找到估计的节奏,分析$ r(\τ)$的三个最大峰值的滞后。在没有发现多重关系的情况下,最大峰值的滞后被视为拍周期。
为了找到节拍位置,使用谱积或自相关函数创建节奏 T 的人工脉冲序列。然后,我们将其与原始检测函数输出进行互相关。
我们称相关性最大的时间索引为\(t_{0}\)和第一拍。给定分析窗口中的第二个和后续心跳通过添加心跳周期\(\mathcal{T}\)
librosa
允许我们计算静态速度、动态速度,并使用速度图显示 BPM 随时间的变化。
要计算静态速度,请运行以下代码:
onset_env = librosa.onset.onset_strength(x, sr=sampling_rate)
tempo = librosa.beat.tempo(onset_envelope=onset_env, sr=sampling_rate)
print(tempo)
您应该得到一个单元素数组作为输出:
array([107.66601562])
要获得动态速度:
dtempo = librosa.beat.tempo(onset_envelope=onset_env, sr=sampling_rate,
aggregate=None)
print(dtempo)
结果是一个数组:
array([112.34714674, 112.34714674, 112.34714674, ..., 107.66601562, 107.66601562, 107.66601562])
您也可以使用速度图来可视化动态速度:
fig, ax = plt.subplots(figsize=(15,9))
tg = librosa.feature.tempogram(onset_envelope=onset_env, sr=sampling_rate,
hop_length=hop_length)
librosa.display.specshow(tg, x_axis='time', y_axis='tempo', cmap='magma', ax=ax)
ax.plot(librosa.times_like(dtempo), dtempo,
color='c', linewidth=1.5, label='Tempo estimate (default prior)')
ax.set(title='Dynamic tempo estimation')
ax.legend()
plt.savefig('tempogram.png')
Dynamic Tempo Estimation
光谱图分解
声谱图可以提供更多关于声音的信息,这些信息仅仅是关于开始和频率的视觉线索。我们可以用频谱图来区分一首音乐的不同成分。
最常见的一种情况是将和声与打击乐声音分开,例如钢琴、键盘、吉他和长笛与鼓、手鼓等的声音。这可以使用谐波冲击源分离算法或 HPSS 来完成。
谐波撞击源分离(HPSS)
HPSS 算法有 4 个步骤:
- 计算声谱图
- 垂直和水平中值滤波
- 滤波频谱图的二进制掩蔽
- 反转掩蔽的频谱图
我们知道,给定一个\(N\)值的有序列表,如果\(N\)是奇数,中值就是\(((N-1)/2)^{th}\)值,否则就是\((N/2)^{th}\)和\(((N+1)/2)^{th}\)项之和。
然后,给定 R_{N×K}\(中的矩阵\) B \我们定义谐波和冲击中值滤波器如下:
$ medfilt_{h}(B)(n,k)= median({ B(m l _ { h },k),...,B(m+l_{h},k)} $
$medfilt_{p}(B)(n,k) = median({B(n,k l _ { p }),...,B(n,k+l _ { p })} $
其中\(2l_{h} + 1\)和\(2l_{p} + 1\)分别是谐波和冲击滤波器的长度。增强的光谱图计算如下。
$ \phi_{h} = medfilt_{h}(|X(n,k )|^{2})$
$ \phi_{p} = medfilt_{p}(|X(n,k )|^{2})$
其中\(X(n,k)\)是声音信号的 STFT。
对过滤的光谱图应用二进制掩蔽。对于谐波谱图,如果$ \phi_{h}(n,k)\(的值大于或等于\)\phi_{p}(n,k)\(,则元素\) M_{h}(n,k)$的值为\(1\)。否则\(M_{h}(n,k)\)的值为 0。
类似地,对于打击乐声谱图,如果\(\phi_{p}(n,k)\)的值大于\(\phi_{h}(n,k)\)的值,则元素$ M_{p}(n,k)$的值是\(1\)。否则\(M_{p}(n,k)\)的值为 0。
注意,这个公式意味着对于所有的\(n\)和\(k\),M_{h}(n,k) + M_{p}(n,k) = 1$。为了得到打击乐和和声元素的声谱图,我们在原始 STFT \(X\)和二进制掩码\(M_{p}\)和\(M_{h}\)之间进行元素乘法。
由于我们的二进制屏蔽的定义,在每个时间点的每个频率仓要么被分配给\(X_{h}\)要么被分配给\(X_{p}\)。
一旦你有了这些,你就可以通过应用一个反 STFT 得到信号的打击乐和谐波成分。
librosa
提供了 HPSS 算法的现成实现。
S = librosa.stft(x)
H, P = librosa.decompose.hpss(S)
fig, ax = plt.subplots(nrows=3, sharex=True, sharey=True)
img = librosa.display.specshow(librosa.amplitude_to_db(np.abs(S), ref=np.max),
y_axis='log', x_axis='time', ax=ax[0])
ax[0].set(title='Full power spectrogram')
ax[0].label_outer()
librosa.display.specshow(librosa.amplitude_to_db(np.abs(H), ref=np.max(np.abs(S))),
y_axis='log', x_axis='time', ax=ax[1])
ax[1].set(title='Harmonic power spectrogram')
ax[1].label_outer()
librosa.display.specshow(librosa.amplitude_to_db(np.abs(P), ref=np.max(np.abs(S))),
y_axis='log', x_axis='time', ax=ax[2])
ax[2].set(title='Percussive power spectrogram')
fig.colorbar(img, ax=ax, format='%+2.0f dB')
HPSS (segregating percussive and harmonic parts of the sound signal)
您可以获得如下隔离信号:
y_harm = librosa.istft(H)
y_perc = librosa.istft(P)
实施还允许将信号分解成三个元素,而不是两个:谐波、冲击和残余。可以通过将 margin 参数的值设置为大于 1 的任何值来实现这一点。
H, P = librosa.decompose.hpss(S, margin=3.0)
R = S - (H + P)
y_harm = librosa.istft(H)
y_perc = librosa.istft(P)
y_res = librosa.istft(R)
结论
在本文中,我们探讨了音符、泛音、八度音、色度表示、开始检测方法、节拍、速度图和使用 HPSS 算法的声谱图分解。
在本系列的下一部分,我们将尝试构建一个具有深度学习的文本到语音引擎。正如我们常说的——敬请关注!
深度学习的音频分类
原文:https://blog.paperspace.com/audio-classification-with-deep-learning/
Photo by James Kovin / Unsplash
想运行本文中的代码吗?点击此链接,在渐变笔记本中运行此代码,或者在笔记本创建页面的高级选项中,使用此 repo 作为笔记本的“工作区 URL ”,创建您自己的代码。
视觉和声音是人类感知的两种最常见的东西。对于大多数人来说,这两种感觉似乎都很琐碎,很难分析并发展出对它们的直观理解。与自然处理(NLP)相关的问题对于人类来说足够简单,但对于机器来说就不一样了,因为它们过去一直在努力实现理想的结果。然而,随着过去十年深度学习模型和架构的引入和兴起,我们已经能够以更高的成功率计算复杂的计算和项目。
在我们以前的博客中,我们已经详细讨论了计算机视觉和自然语言处理的多种不同类型的问题和解决方案。在本文和即将到来的未来工作中,我们将了解更多关于深度学习模型如何被用来解决音频分类任务以及用于音乐生成的信息。音频分类项目是我们将在本文中重点关注的内容,并试图用简单的架构实现良好的结果。代码和项目可以在 Paperspace 的 Gradient 平台上进行计算,以重现和重新创建本文中的代码片段。
简介:
音频分类是分析和识别任何类型的音频、声音、噪声、音符或任何其他类似类型的数据以对其进行相应分类的过程。我们可用的音频数据可以以多种形式出现,例如来自声学设备的声音、来自乐器的音乐和弦、人类语音,甚至是自然出现的声音,如环境中的鸟的啁啾声。现代深度学习技术使我们能够在与音频信号处理相关的任务和项目中实现最先进的结果。
在本文中,我们的主要目标是对音频分类项目有一个明确的了解,同时了解信号处理的基本概念和一些用于实现预期结果的最佳技术。在深入本文的内容之前,我首先建议您更加熟悉深度学习框架和其他重要的基本概念。您可以查看更多关于 TensorFlow ( link )和 Keras ( link )库的信息,我们将利用它们来构建这个项目。让我们了解一下音频分类的一些基本概念。
探索音频分类的基本概念:
在文章的这一部分,我们将尝试理解一些对理解深度学习音频分类至关重要的有用术语。我们将探讨一些在音频处理项目中可能会遇到的基本术语。让我们从简要分析这些关键概念开始。
波形:
在分析波形及其众多参数之前,让我们先了解一下声音是什么。声音是物体在周围的空气粒子振动时产生的振动。气压的相应变化产生了这些声波。声音是一种机械波,能量从一个源头传递到另一个源头。波形是帮助我们分析声波随时间的位移的示意图,以及特定任务所需的一些其他基本参数。
另一方面,波形中的频率是波形在一秒钟的时间周期内重复自身的次数的表示。波形顶部的峰值称为波峰,而底部的点称为波谷。振幅是从中心线到波谷顶部或波峰底部的距离。对这些基本概念有了简单的理解和掌握后,我们可以继续探讨音频分类所需的其他一些基本主题。
光谱图:
3-D Spectrogram from wiki
频谱图是音频信号中频谱的可视化表示。频谱图的其他技术术语是声谱图、声纹或声谱图。这些频谱图广泛用于信号处理、音乐生成、音频分类、语言分析、语音检测等领域。我们还将在本文中使用频谱图来完成音频分类的任务。关于这个话题的更多信息,我推荐查看下面的链接。
音频信号处理:
音频信号处理是处理音频信号、声波和其他音频操作的领域。当具体谈到音频信号处理的深度学习时,我们可以在这个大领域中进行大量的应用。
在本文中,我们将更详细地讨论音频分类这一主题。其他一些主要应用包括语音识别、音频去噪、声音信息检索、音乐生成等等。结合深度学习解决音频信号处理任务有着无数的可能性,值得探索。让我们在下一节开始理解音频分类项目,然后再从头开始实现它。
了解音频分类项目:
音频分类是入门音频深度学习最好的基础入门项目之一。目标是理解原始格式的可用波形,并将现有数据转换成开发人员可用的形式。通过将音频数据的原始波形转换为频谱图的形式,我们可以将其通过深度学习模型来解释和分析数据。在音频分类中,我们通常执行二元分类,其中我们确定输入信号是否是我们想要的音频。
在这个项目中,我们的目标是检索一只鸟发出的声音。传入的噪声信号被转换为波形,我们可以在 TensorFlow 深度学习框架的帮助下,利用该波形进行进一步的处理和分析。一旦成功获得波形,我们就可以将该波形转换成频谱图,频谱图是可用波形的可视化表示。由于这些光谱图是视觉图像,我们可以通过创建深度学习模型来计算二进制分类结果,从而利用卷积神经网络来相应地分析它们。
利用深度学习实现音频分类和识别项目:
如前所述,我们项目的目标是读取从森林传入的声音,并解释接收到的数据是属于特定的鸟(卷尾鸟)还是其他我们不太想知道的噪音。对于整个项目的构建,我们将利用 TensorFlow 和 Keras 深度学习框架。
你可以查看下面的文章来了解更多关于 TensorFlow 和 Keras 文章这里。该项目需要的另一个额外安装是 TensorFlow-io 库,它将允许我们访问 TensorFlow 内置支持中不可用的文件系统和文件格式。以下提供的 pip 命令可用于在您的工作环境中安装库。
pip install tensorflow-io[tensorflow]
导入基本库:
在下一步中,我们将导入构建以下项目所需的所有基本库。对于这个项目,我们将使用一个序列类型模型,它将允许我们构建一个简单的卷积神经网络来分析产生的光谱图并获得理想的结果。由于所开发的模型的架构非常简单,我们并不真正需要利用功能模型 API 或者定制建模功能。
我们将利用卷积层的架构,以及一些密集和平坦的层。如前所述,我们还将利用 TensorFlow 输入输出库来处理大量的文件系统和格式,如。wav 格式和. mp3 格式。操作系统库导入将帮助我们以各自的格式访问所有需要的文件
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, Dense, Flatten
import tensorflow_io as tfio
from matplotlib import pyplot as plt
import os
正在加载数据集:
这个项目的数据集可以通过惠普解锁挑战 3 的信号处理- Z 的 Kaggle 挑战获得,你可以从这个链接下载。
要将数据下载到梯度笔记本上:
1.获得一个 Kaggle 帐户
2.转到您的帐户设置创建一个 api 令牌,并保存 kaggle.json。注意:如果您已经创建了一个 API 令牌,您可能需要创建一个新的 API 令牌。
3.将 kaggle.json 上传到此渐变笔记本
4.运行下面的单元格或在终端中运行以下命令(这可能需要一段时间)
终端:
mv kaggle.json ~/。kaggle/
pip 安装卡格尔
kaggle 数据集下载 ken JEE/z-by-HP-unlocked-challenge-3-信号处理
解压 z-by-HP-unlocked-challenge-3-signal-processing . zip
单元格:
!mv kaggle.json ~/。kaggle/
!pip 安装卡格尔
!kaggle 数据集下载 ken JEE/z-by-HP-unlocked-challenge-3-信号处理
!解压 z-by-HP-unlocked-challenge-3-signal-processing . zip
下载并提取数据集后,我们可以注意到 data 文件夹中有三个目录。这三个目录分别是森林录音,包含森林中产生的声音的三分钟剪辑、僧帽猴鸟录音的三秒钟剪辑以及僧帽猴鸟不产生的声音的三秒钟录音剪辑。在下一个代码片段中,我们将定义变量来相应地设置这些路径。
CAPUCHIN_FILE = os.path.join('data', 'Parsed_Capuchinbird_Clips', 'XC3776-3.wav')
NOT_CAPUCHIN_FILE = os.path.join('data', 'Parsed_Not_Capuchinbird_Clips', 'afternoon-birds-song-in-forest-0.wav')
在下一步中,我们将定义数据加载函数,该函数将有助于以所需格式创建所需波形,以供进一步计算。下面代码片段中定义的函数将允许我们读取数据,并将其转换为单声道(或单个)通道,以便于分析。我们还将改变频率信号,使我们能够修改整体振幅,以获得更小的数据样本用于整体分析。
def load_wav_16k_mono(filename):
# Load encoded wav file
file_contents = tf.io.read_file(filename)
# Decode wav (tensors by channels)
wav, sample_rate = tf.audio.decode_wav(file_contents, desired_channels=1)
# Removes trailing axis
wav = tf.squeeze(wav, axis=-1)
sample_rate = tf.cast(sample_rate, dtype=tf.int64)
# Goes from 44100Hz to 16000hz - amplitude of the audio signal
wav = tfio.audio.resample(wav, rate_in=sample_rate, rate_out=16000)
return wav
Image by Author
上图显示了卷尾猴和非卷尾猴信号的波形图。
准备数据集:
在文章的这一部分,我们将定义卷尾鸟剪辑的正路径和负路径。正路径变量存储包含卷尾鸟的剪辑记录的目录的路径,而负路径存储在另一个变量中。我们将把这些目录中的文件链接到。wav 格式并添加各自的标签。标签是按照二进制分类的,被标记为 0 或 1。正标签的值为 1,这意味着片段包含卷尾猴鸟的音频信号。带有零的负标签指示音频信号是不包含卷尾猴鸟的剪辑记录的随机噪声。
# Defining the positive and negative paths
POS = os.path.join('data', 'Parsed_Capuchinbird_Clips/*.wav')
NEG = os.path.join('data', 'Parsed_Not_Capuchinbird_Clips/*.wav')
# Creating the Datasets
pos = tf.data.Dataset.list_files(POS)
neg = tf.data.Dataset.list_files(NEG)
# Adding labels
positives = tf.data.Dataset.zip((pos, tf.data.Dataset.from_tensor_slices(tf.ones(len(pos)))))
negatives = tf.data.Dataset.zip((neg, tf.data.Dataset.from_tensor_slices(tf.zeros(len(neg)))))
data = positives.concatenate(negatives)
我们还可以分析卷尾鸟的平均波长,如下面的代码片段所示,方法是加载到 positive samples 目录中,并加载我们之前创建的数据加载函数。
# Analyzing the average wavelength of a Capuchin bird
lengths = []
for file in os.listdir(os.path.join('data', 'Parsed_Capuchinbird_Clips')):
tensor_wave = load_wav_16k_mono(os.path.join('data', 'Parsed_Capuchinbird_Clips', file))
lengths.append(len(tensor_wave))
最小、平均和最大波长周期分别如下所示。
<tf.Tensor: shape=(), dtype=int32, numpy=32000>
<tf.Tensor: shape=(), dtype=int32, numpy=54156>
<tf.Tensor: shape=(), dtype=int32, numpy=80000>
将数据转换成光谱图:
在下一步中,我们将创建函数来完成音频分析所需的预处理步骤。我们将把以前获得的波形转换成频谱图的形式。在接下来的步骤中,我们的深度学习模型将使用这些频谱图形式的可视化音频信号来相应地分析和解释结果。在下面的代码块中,我们获取所有波形,并使用 TensorFlow 库计算信号的短时傅立叶变换,以获取可视化表示,如下图所示。
def preprocess(file_path, label):
for i in os.listdir(file_path):
i = file_path.decode() + "/" + i.decode()
wav = load_wav_16k_mono(i)
wav = wav[:48000]
zero_padding = tf.zeros([48000] - tf.shape(wav), dtype=tf.float32)
wav = tf.concat([zero_padding, wav],0)
spectrogram = tf.signal.stft(wav, frame_length=320, frame_step=32)
spectrogram = tf.abs(spectrogram)
spectrogram = tf.expand_dims(spectrogram, axis=2)
return spectrogram, label
filepath, label = positives.shuffle(buffer_size=10000).as_numpy_iterator().next()
spectrogram, label = preprocess(filepath, label)
Image by Author
构建深度学习模型:
在我们开始构建深度学习模型之前,让我们通过加载数据来创建数据管道。我们将加载从预处理步骤函数获得的谱图数据元素。我们可以通过使用 TensorFlow 内置功能缓存和混洗这些数据,并创建一个 16 的批处理大小来相应地加载数据元素。在我们继续构建深度学习模型之前,我们可以为训练和测试样本创建分区,如下面的代码片段所示。
# Creating a Tensorflow Data Pipeline
data = data.map(preprocess)
data = data.cache()
data = data.shuffle(buffer_size=1000)
data = data.batch(16)
data = data.prefetch(8)
# Split into Training and Testing Partitions
train = data.take(36)
test = data.skip(36).take(15)
在下一步中,我们将构建一个顺序类型模型。我们可以开发架构,用功能 API 或定制模型原型来解决任务。然后,我们可以继续将卷积层与样本标签的相应形状相加,以构建具有 16 个滤波器和(3,3)核大小的两个卷积层块。ReLU 激活功能用于卷积层的构建。然后,我们可以对从卷积层获得的输出进行平坦化,使其适合进一步处理。最后,我们可以添加具有 Sigmoid 激活函数的完全连接的层,其中一个输出节点接收二进制分类输出。代码片段和所构建模型的摘要如下所示。
model = Sequential()
model.add(Conv2D(16, (3,3), activation='relu', input_shape=(1491, 257,1)))
model.add(Conv2D(16, (3,3), activation='relu'))
model.add(Flatten())
# model.add(Dense(128, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 1489, 255, 16) 160
conv2d_1 (Conv2D) (None, 1487, 253, 16) 2320
flatten (Flatten) (None, 6019376) 0
dense (Dense) (None, 1) 6019377
=================================================================
Total params: 6,021,857
Trainable params: 6,021,857
Non-trainable params: 0
_________________________________________________________________
一旦我们完成了模型架构的构建,我们就可以相应地继续编译和训练模型。对于模型的编译,我们可以使用 Adam 优化器、用于二进制分类的二进制交叉熵损失函数,并定义一些额外的召回和精度度量用于模型分析。我们可以训练之前用验证测试数据构建的数据,并在几个时期内拟合模型。在这个步骤之后获得的代码片段和结果如下所示。
# Compiling and fitting the model
model.compile('Adam', loss='BinaryCrossentropy', metrics=[tf.keras.metrics.Recall(),tf.keras.metrics.Precision()])
model.fit(train, epochs=4, validation_data=test)
Epoch 1/4
36/36 [==============================] - 204s 6s/step - loss: 1.6965 - recall: 0.8367 - precision: 0.8483 - val_loss: 0.0860 - val_recall: 0.9254 - val_precision: 0.9688
Epoch 2/4
36/36 [==============================] - 200s 6s/step - loss: 0.0494 - recall: 0.9477 - precision: 0.9932 - val_loss: 0.0365 - val_recall: 1.0000 - val_precision: 0.9846
Epoch 3/4
36/36 [==============================] - 201s 6s/step - loss: 0.0314 - recall: 0.9933 - precision: 0.9801 - val_loss: 0.0228 - val_recall: 0.9821 - val_precision: 1.0000
Epoch 4/4
36/36 [==============================] - 201s 6s/step - loss: 0.0126 - recall: 0.9870 - precision: 1.0000 - val_loss: 0.0054 - val_recall: 1.0000 - val_precision: 0.9861
一旦我们成功地构建并训练了模型,我们就可以分析并验证结果。结果中获得的指标显示了良好的进展。因此,我们可以认为所构建的模型适合于对鸟的叫声进行相对成功的预测,以识别卷尾猴鸟的噪声频率。在下一节中,我们将研究这个过程的步骤。
进行所需的预测:
在这个项目的最后一步,我们将分析如何对森林记录中的所有现有文件进行适当的预测。在这一步之前,让我们看看如何对单个批次进行预测,如下面的代码片段所示。
# Prediction for a single batch
X_test, y_test = test.as_numpy_iterator().next()
yhat = model.predict(X_test)
# converting logits to classes
yhat = [1 if prediction > 0.5 else 0 for prediction in yhat]
既然我们已经了解了如何对单个批次进行预测,那么有必要说明如何对森林记录目录中的文件进行预测。森林录音中的每个片段大约三分钟长。由于我们的预测由一个三秒钟的片段组成,用于检测卷尾猴鸟的叫声,我们可以将这些更长的片段分割成窗口频谱。我们可以将三分钟长的剪辑(180 秒)分成 60 个更小的片段来进行分析。我们将在这一部分检测卷尾猴鸟的总叫声,其中每个片段的得分为 0 或 1。
一旦我们确定了每个窗口频谱的调用,我们就可以通过将所有单个值相加来计算整个片段中的总计数。总计数告诉我们在整个音频剪辑中听到卷尾猴鸟声音的次数。在下面的代码片段中,我们将构建与上一节中讨论的函数类似的第一个函数,在该函数中,我们读取森林记录剪辑,这些剪辑是 mp3 文件的形式,而不是。wav 格式。下面的函数将 mp3 格式的输入转换成张量。然后,我们计算多声道输入的平均值,将其转换为单声道,并获得所需的频率信号。
def load_mp3_16k_mono(filename):
""" Load an audio file, convert it to a float tensor, resample to 16 kHz single-channel audio. """
res = tfio.audio.AudioIOTensor(filename)
# Convert to tensor and combine channels
tensor = res.to_tensor()
tensor = tf.math.reduce_sum(tensor, axis=1) / 2
# Extract sample rate and cast
sample_rate = res.rate
sample_rate = tf.cast(sample_rate, dtype=tf.int64)
# Resample to 16 kHz
wav = tfio.audio.resample(tensor, rate_in=sample_rate, rate_out=16000)
return wav
mp3 = os.path.join('data', 'Forest Recordings', 'recording_00.mp3')
wav = load_mp3_16k_mono(mp3)
audio_slices = tf.keras.utils.timeseries_dataset_from_array(wav, wav, sequence_length=48000, sequence_stride=48000, batch_size=1)
samples, index = audio_slices.as_numpy_iterator().next()
在下一个代码片段中,我们将构建一个函数,帮助我们将各个片段分离成窗口光谱图,以便进一步计算。我们将相应地映射数据,并创建适当的切片来进行所需的预测,如下所示。
# Build Function to Convert Clips into Windowed Spectrograms
def preprocess_mp3(sample, index):
sample = sample[0]
zero_padding = tf.zeros([48000] - tf.shape(sample), dtype=tf.float32)
wav = tf.concat([zero_padding, sample],0)
spectrogram = tf.signal.stft(wav, frame_length=320, frame_step=32)
spectrogram = tf.abs(spectrogram)
spectrogram = tf.expand_dims(spectrogram, axis=2)
return spectrogram
audio_slices = tf.keras.utils.timeseries_dataset_from_array(wav, wav, sequence_length=16000, sequence_stride=16000, batch_size=1)
audio_slices = audio_slices.map(preprocess_mp3)
audio_slices = audio_slices.batch(64)
yhat = model.predict(audio_slices)
yhat = [1 if prediction > 0.5 else 0 for prediction in yhat]
在本文的最后一段代码中,我们将对森林记录中的所有文件运行以下过程,并获得一个总的计算结果。结果将包含 0 和 1 的片段,其中输出 1 的总和以计算片段的总得分。我们可以根据以下项目的需要,使用下面提供的代码,在录音中找出卷尾猴鸟的叫声总数。
results = {}
class_preds = {}
for file in os.listdir(os.path.join('data', 'Forest Recordings')):
FILEPATH = os.path.join('data','Forest Recordings', file)
wav = load_mp3_16k_mono(FILEPATH)
audio_slices = tf.keras.utils.timeseries_dataset_from_array(wav, wav, sequence_length=48000, sequence_stride=48000, batch_size=1)
audio_slices = audio_slices.map(preprocess_mp3)
audio_slices = audio_slices.batch(64)
yhat = model.predict(audio_slices)
results[file] = yhat
for file, logits in results.items():
class_preds[file] = [1 if prediction > 0.99 else 0 for prediction in logits]
class_preds
这个项目的两个主要参考是来自 Kaggle 的笔记本和下面的 GitHub 链接。大部分代码摘自以下参考资料,我强烈建议您查阅一下。这篇博文的代码也在 Gradient AI 上托管。创建一个笔记本,将此 URL 作为工作区 URL,以便将此代码作为。ipynb 直接放入笔记本。
为了获得更好的结果,还可以对这个项目进行一些额外的改进。可以增加网络的复杂性,并且可以利用其他创新方法来获得卷尾猴鸟模式分析的更高精度。在以后的文章中,我们还将看看其他一些与音频信号处理相关的项目。
结论:
Photo by Will Francis / Unsplash
深度学习的音频信号处理因其在解释和完成各种复杂项目方面的高成功率而获得了高度关注。大多数复杂的信号项目,如声学音乐检测、音频分类、环境声音分类等等,都可以通过深度学习技术来实现。随着这些领域的进一步改进和发展,我们可以期待取得更大的成就。
在这篇文章中,我们介绍了深度学习的音频分类。我们探索和分析了一些基本和必要的组成部分,以彻底理解音频分类的概念。然后,在进入任务的实现部分之前,我们对这个博客的特定项目进行了简要总结。我们利用 TensorFlow 框架进行波形转换,使用频谱图进行分析,并构建了一个能够对音频数据进行二进制分类的简单卷积神经网络。有几个改进可以添加到下面的项目中,以获得更好的结果。
在接下来的文章中,我们将关注更多与深度学习音频信号处理相关的有趣项目。我们还将分析一些音乐生成项目,并从头开始继续我们在生成性对抗网络和神经网络方面的工作。在那之前,享受探索和构建新项目的乐趣吧!
Keras 中使用自动编码器的图像压缩
原文:https://blog.paperspace.com/autoencoder-image-compression-keras/
自动编码器是一种深度学习模型,用于将数据从高维空间转换到低维空间。它们的工作原理是将数据(无论大小)编码成一维向量。然后,可以对该向量进行解码,以重建原始数据(在这种情况下,是一幅图像)。自动编码器越精确,生成的数据就越接近原始数据。
在本教程中,我们将探索 autoencoder 架构,并了解如何使用 TensorFlow 和 Keras 将该模型应用于压缩 MNIST 数据集中的图像。特别是,我们将考虑:
- 判别建模与生成建模
- 自动编码器如何工作
- 在 Keras 中构建自动编码器
- 构建编码器
- 构建解码器
- 培养
- 做预测
- 完全码
- 结论
判别建模与生成建模
最常见的机器学习模型是判别型的。如果你是一个机器学习爱好者,很可能你已经建立或使用的模型类型主要是歧视性的。这些模型识别输入数据,然后采取适当的行动。对于分类任务,判别模型学习如何区分各种不同的类。基于模型对每个类的属性的学习,它将新的输入样本分类到适当的标签。让我们将这种理解应用到下一个代表警告标志的图像中。
如果机器/深度学习模型要识别下面的图像,它可能会理解它由三个主要元素组成:矩形、直线和点。当另一个输入图像具有类似于这些元素的特征时,那么它也应该被识别为警告标志。
Image Source: Pixabay
如果该算法能够识别一幅图像的属性,它能否生成一幅与之相似的新图像?换句话说,它能不能画出一张有三角形、直线和圆点的新图像?不幸的是,鉴别模型不够聪明,即使它们知道这些图像的结构,也无法绘制出新的图像。我们再举一个例子,把事情说清楚。
假设有人能很好地识别事物。对于给定的图像,他/她可以容易地识别显著的属性,然后对图像进行分类。这样的人一定能再画出这样的形象吗?不。有些人不会画画。辨别模型就像那些只能识别图像,但不能自己画出来的人。
与鉴别模型相反,还有另一组叫做的生成模型可以创造新的图像。对于给定的输入图像,判别模型的输出是类别标签;创成式模型的输出是与输入图像大小相同、外观相似的图像。
最简单的生成模型之一是自动编码器(简称 AE ),这是本教程的重点。
自动编码器如何工作
自动编码器是一种深度神经网络模型,可以接收数据,通过若干层传播数据,以浓缩和理解其结构,并最终再次生成数据。在本教程中,我们将特别考虑这是如何处理图像数据的。为了完成这项任务,自动编码器使用两种不同类型的网络。第一个被称为编码器,另一个是解码器。解码器只是编码器内部各层的反映。让我们弄清楚这是如何工作的。
编码器的工作是接受可能具有两个或更多维度的原始数据(例如,图像),并生成表示整个图像的单个 1-D 向量。一维向量中元素的数量根据要解决的任务而变化。它可以有一个或多个元素。向量中的元素越少,精确再现原始图像的复杂度越高。
通过用相对较少元素的向量表示输入图像,我们实际上压缩了图像。例如,MNIST 数据集(我们将在本教程中使用)中每幅图像的大小是28x28
。也就是说,每个图像有784
个元素。如果每个图像都被压缩,只使用两个元素来表示,那么我们就省去了782
元素和(782/784)*100=99.745%
数据。
下图显示了编码器如何从输入图像生成一维向量。包含的层是您自己选择的,因此您可以使用密集、卷积、下降等。
编码器从其最后一层生成的 1-D 向量然后被馈送到解码器。解码器的工作是以尽可能高的质量重建原始图像。解码器只是编码器的反映。根据上图中的编码器架构,下图给出了解码器的架构。
通过比较原始图像和重建图像,即通过计算两幅图像中像素之间的差异,来计算损失。请注意,解码器的输出必须与原始图像的大小相同。为什么?因为如果图像的大小不同,就没有办法计算损失。
在讨论了自动编码器的工作原理之后,让我们使用 Keras 构建我们的第一个自动编码器。
在 Keras 中构建自动编码器
Keras 是构建机器和深度学习模型的强大工具,因为它简单而抽象,所以用很少的代码就可以获得很好的结果。Keras 有三种建立模型的方法:
- 顺序 API
- 功能 API
- 模型子类化
这三种方式在允许的定制级别上有所不同。
顺序 API 允许您构建顺序模型,但是与其他两种类型相比,它的可定制性较差。模型中每个层的输出只连接到一个层。
虽然这是我们想要在本教程中创建的模型类型,但是我们将使用函数 API 。functional API 很简单,非常类似于 sequential API,并且还支持其他功能,例如将单层的输出连接到多层的能力。
构建 Keras 模型的最后一个选项是模型子类化,这是完全可定制的,但也非常复杂。你可以在这篇教程中了解更多关于这三种方法的内容。
现在我们将关注使用函数式 API 来构建自动编码器。您可能认为我们将构建一个单独的 Keras 模型来表示自动编码器,但是我们实际上将构建三个模型:一个用于编码器,另一个用于解码器,还有一个用于完整的自动编码器。为什么我们要为编码器和解码器都建立一个模型?我们这样做是为了防止您想单独探索每个模型。例如,我们可以使用编码器的模型来可视化表示每个输入图像的 1-D 向量,这可能有助于您了解它是否是图像的良好表示。使用解码器,我们将能够测试是否从 1-D 向量创建了良好的表示,假设它们是良好编码的(即,更好地用于调试目的)。最后,通过为整个自动编码器构建模型,我们可以通过向其提供原始图像并直接接收输出图像来轻松地端到端使用它。
让我们从构建编码器模型开始。
构建编码器
以下代码使用函数式 API 为编码器构建了一个模型。首先,模型的层是使用tensorflow.keras.layers
API 创建的,因为我们使用TensorFlow
作为后端库。第一层是接受原始图像的Input
层。该层接受一个名为shape
的参数,表示输入的大小,这取决于所使用的数据集。我们将使用 MNIST 数据集,其中每幅图像的大小为28x28
。不是将形状设置为(28, 28)
,而是直接设置为(784)
。为什么?因为我们将只使用网络中的密集层,因此输入必须是矢量形式,而不是矩阵。代表输入层的张量返回给变量x
。
然后,输入层通过多个层传播:
Dense
层有 300 个神经元LeakyReLU
图层Dense
层有 2 个神经元LeakyReLU
图层
网络的最后一层只有两个神经元。当输入到LeakyReLU
层时,编码器的最终输出将是一个只有两个元素的一维向量。换句话说,MNIST 数据集中的所有影像都将被编码为两个元素的矢量。
import tensorflow.keras.layers
import tensorflow.keras.models
x = tensorflow.keras.layers.Input(shape=(784), name="encoder_input")
encoder_dense_layer1 = tensorflow.keras.layers.Dense(units=300, name="encoder_dense_1")(x)
encoder_activ_layer1 = tensorflow.keras.layers.LeakyReLU(name="encoder_leakyrelu_1")(encoder_dense_layer1)
encoder_dense_layer2 = tensorflow.keras.layers.Dense(units=2, name="encoder_dense_2")(encoder_activ_layer1)
encoder_output = tensorflow.keras.layers.LeakyReLU(name="encoder_output")(encoder_dense_layer2)
构建并连接所有层后,下一步是使用tensorflow.keras.models
API 构建模型,根据下一行指定输入和输出张量:
encoder = tensorflow.keras.models.Model(x, encoder_output, name="encoder_model")
为了打印编码器架构的概要,我们将使用encoder.summary()
。输出如下。这个网络并不大,你可以在名为encoder_dense_1
的密集层中增加神经元的数量,但我只用了 300 个神经元,以避免花太多时间训练网络。
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
encoder_input (InputLayer) [(None, 784)] 0
_________________________________________________________________
encoder_dense_1 (Dense) (None, 300) 235500
_________________________________________________________________
encoder_leakyrelu_1 (LeakyRe (None, 300) 0
_________________________________________________________________
encoder_dense_2 (Dense) (None, 2) 602
_________________________________________________________________
encoder_output (LeakyReLU) (None, 2) 0
=================================================================
Total params: 236,102
Trainable params: 236,102
Non-trainable params: 0
_________________________________________________________________
在建立了编码器之后,接下来是解码器。
构建解码器
与构建编码器类似,将使用以下代码构建解码器。因为解码器的输入层接受从编码器的最后一层返回的输出,我们必须确保这两层在大小上匹配。编码器的最后一层返回 2 个元素的向量,因此解码器的输入必须有 2 个神经元。你可以很容易地注意到,解码器的层只是编码器层的反射。
decoder_input = tensorflow.keras.layers.Input(shape=(2), name="decoder_input")
decoder_dense_layer1 = tensorflow.keras.layers.Dense(units=300, name="decoder_dense_1")(decoder_input)
decoder_activ_layer1 = tensorflow.keras.layers.LeakyReLU(name="decoder_leakyrelu_1")(decoder_dense_layer1)
decoder_dense_layer2 = tensorflow.keras.layers.Dense(units=784, name="decoder_dense_2")(decoder_activ_layer1)
decoder_output = tensorflow.keras.layers.LeakyReLU(name="decoder_output")(decoder_dense_layer2)
连接各层后,接下来是根据下一行建立解码器模型。
decoder = tensorflow.keras.models.Model(decoder_input, decoder_output, name="decoder_model")
这里是decoder.summary()
的输出。确保从编码器返回的输出大小与原始输入大小相匹配非常重要。
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
decoder_input (InputLayer) [(None, 2)] 0
_________________________________________________________________
decoder_dense_1 (Dense) (None, 300) 900
_________________________________________________________________
decoder_leakyrelu_1 (LeakyRe (None, 300) 0
_________________________________________________________________
decoder_dense_2 (Dense) (None, 784) 235984
_________________________________________________________________
decoder_output (LeakyReLU) (None, 784) 0
=================================================================
Total params: 236,884
Trainable params: 236,884
Non-trainable params: 0
_________________________________________________________________
在构建了自动编码器的两个模块(编码器和解码器)之后,接下来是构建完整的自动编码器。
构建自动编码器
下面列出了构建自动编码器的代码。名为ae_input
的张量表示接受长度为 784 的向量的输入层。这个张量作为输入被馈送到编码器模型。编码器的输出保存在ae_encoder_output
中,然后输入解码器。最后,自动编码器的输出保存在ae_decoder_output
中。
为接受输入ae_input
和输出ae_decoder_output
的自动编码器创建一个模型。
ae_input = tensorflow.keras.layers.Input(shape=(784), name="AE_input")
ae_encoder_output = encoder(ae_input)
ae_decoder_output = decoder(ae_encoder_output)
ae = tensorflow.keras.models.Model(ae_input, ae_decoder_output, name="AE")
下面列出了自动编码器的概要。在这里,您可以发现自动编码器的输入和输出的形状是相同的,这是计算损耗所必需的。
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
AE_input (InputLayer) [(None, 784)] 0
_________________________________________________________________
encoder_model (Model) (None, 2) 236102
_________________________________________________________________
decoder_model (Model) (None, 784) 236884
=================================================================
Total params: 472,986
Trainable params: 472,986
Non-trainable params: 0
_________________________________________________________________
模型构建过程的下一步是根据下一个代码使用compile()
方法编译模型。使用mean square error
损失功能,使用Adam optimizer
,学习率设置为0.0005
。
import tensorflow.keras.optimizers
ae.compile(loss="mse", optimizer=tensorflow.keras.optimizers.Adam(lr=0.0005))
现在,模型已经准备好接受训练数据,因此下一步是准备数据,以便提供给模型。
请记住,有三种型号:
- 编码器
- 解码器
- ae(用于自动编码器)
加载 MNIST 数据集并训练自动编码器
Keras 有一个名为tensorflow.keras.datasets
的 API,其中可以使用许多数据集。我们将使用根据下一个代码加载的 MNIST 数据集。数据集作为 NumPy 数组加载,表示训练数据、测试数据、训练标签和测试标签。请注意,我们对在训练模型时使用类标签一点也不感兴趣,它们只是用来显示结果的。
x_train_orig
和x_test_orig
NumPy 数组保存 MNIST 图像数据,其中每个图像的大小为28x28
。因为我们的模型接受图像作为长度为784
的向量,然后使用numpy.reshape()
函数对这些数组进行整形。
import tensorflow.keras.datasets
import numpy
(x_train_orig, y_train), (x_test_orig, y_test) = tensorflow.keras.datasets.mnist.load_data()
x_train_orig = x_train_orig.astype("float32") / 255.0
x_test_orig = x_test_orig.astype("float32") / 255.0
x_train = numpy.reshape(x_train_orig, newshape=(x_train_orig.shape[0], numpy.prod(x_train_orig.shape[1:])))
x_test = numpy.reshape(x_test_orig, newshape=(x_test_orig.shape[0], numpy.prod(x_test_orig.shape[1:])))
此时,我们可以使用fit
方法训练自动编码器,如下所示:
ae.fit(x_train, x_train, epochs=20, batch_size=256, shuffle=True, validation_data=(x_test, x_test))
注意,训练数据输入和输出都被设置为x_train
,因为预测输出与原始输入相同。这同样适用于验证数据。您可以将时期数和批次大小更改为其他值。
在自动编码器被训练之后,下一步是进行预测。
做出预测
在下一个代码中使用了predict()
方法来返回编码器和解码器模型的输出。encoded_images
NumPy 数组保存代表所有训练图像的 1D 向量。解码器模型接受该阵列来重建原始图像。
encoded_images = encoder.predict(x_train)
decoded_images = decoder.predict(encoded_images)
注意,解码器的输出是长度为784
的 1D 向量。为了显示重建图像,解码器输出被整形为28x28
,如下所示:
decoded_images_orig = numpy.reshape(decoded_images, newshape=(decoded_images.shape[0], 28, 28))
下一个代码使用Matplotlib
来显示 5 个随机样本的原始图像和重建图像。
num_images_to_show = 5
for im_ind in range(num_images_to_show):
plot_ind = im_ind*2 + 1
rand_ind = numpy.random.randint(low=0, high=x_train.shape[0])
matplotlib.pyplot.subplot(num_images_to_show, 2, plot_ind)
matplotlib.pyplot.imshow(x_train_orig[rand_ind, :, :], cmap="gray")
matplotlib.pyplot.subplot(num_images_to_show, 2, plot_ind+1)
matplotlib.pyplot.imshow(decoded_images_orig[rand_ind, :, :], cmap="gray")
下图显示了 5 幅原始图像及其重建。您可以看到,自动编码器至少能够重建接近原始图像的图像,但质量较低。
低质量的原因之一是在致密层中使用了低数量的神经元(300)。另一个原因是仅使用 2 个元素来表示所有图像。可以通过使用更多的元素来提高质量,但是这增加了压缩数据的大小。
另一个原因是根本不使用卷积层。密集层有利于从图像中获取全局属性,而卷积层有利于获取局部属性。可以通过添加一些卷积层来增强结果。
为了更好地理解编码器模型的输出,让我们根据下一个代码显示它返回的所有 1D 向量。
matplotlib.pyplot.figure()
matplotlib.pyplot.scatter(encoded_images[:, 0], encoded_images[:, 1], c=y_train)
matplotlib.pyplot.colorbar()
这段代码生成的图如下所示。通常,您可以看到该模型能够对不同区域的不同图像进行聚类,但不同聚类之间存在重叠。
完整代码
下面列出了本教程中讨论的完整代码。
import tensorflow.keras.layers
import tensorflow.keras.models
import tensorflow.keras.optimizers
import tensorflow.keras.datasets
import numpy
import matplotlib.pyplot
# Encoder
x = tensorflow.keras.layers.Input(shape=(784), name="encoder_input")
encoder_dense_layer1 = tensorflow.keras.layers.Dense(units=300, name="encoder_dense_1")(x)
encoder_activ_layer1 = tensorflow.keras.layers.LeakyReLU(name="encoder_leakyrelu_1")(encoder_dense_layer1)
encoder_dense_layer2 = tensorflow.keras.layers.Dense(units=2, name="encoder_dense_2")(encoder_activ_layer1)
encoder_output = tensorflow.keras.layers.LeakyReLU(name="encoder_output")(encoder_dense_layer2)
encoder = tensorflow.keras.models.Model(x, encoder_output, name="encoder_model")
encoder.summary()
# Decoder
decoder_input = tensorflow.keras.layers.Input(shape=(2), name="decoder_input")
decoder_dense_layer1 = tensorflow.keras.layers.Dense(units=300, name="decoder_dense_1")(decoder_input)
decoder_activ_layer1 = tensorflow.keras.layers.LeakyReLU(name="decoder_leakyrelu_1")(decoder_dense_layer1)
decoder_dense_layer2 = tensorflow.keras.layers.Dense(units=784, name="decoder_dense_2")(decoder_activ_layer1)
decoder_output = tensorflow.keras.layers.LeakyReLU(name="decoder_output")(decoder_dense_layer2)
decoder = tensorflow.keras.models.Model(decoder_input, decoder_output, name="decoder_model")
decoder.summary()
# Autoencoder
ae_input = tensorflow.keras.layers.Input(shape=(784), name="AE_input")
ae_encoder_output = encoder(ae_input)
ae_decoder_output = decoder(ae_encoder_output)
ae = tensorflow.keras.models.Model(ae_input, ae_decoder_output, name="AE")
ae.summary()
# RMSE
def rmse(y_true, y_predict):
return tensorflow.keras.backend.mean(tensorflow.keras.backend.square(y_true-y_predict))
# AE Compilation
ae.compile(loss="mse", optimizer=tensorflow.keras.optimizers.Adam(lr=0.0005))
# Preparing MNIST Dataset
(x_train_orig, y_train), (x_test_orig, y_test) = tensorflow.keras.datasets.mnist.load_data()
x_train_orig = x_train_orig.astype("float32") / 255.0
x_test_orig = x_test_orig.astype("float32") / 255.0
x_train = numpy.reshape(x_train_orig, newshape=(x_train_orig.shape[0], numpy.prod(x_train_orig.shape[1:])))
x_test = numpy.reshape(x_test_orig, newshape=(x_test_orig.shape[0], numpy.prod(x_test_orig.shape[1:])))
# Training AE
ae.fit(x_train, x_train, epochs=20, batch_size=256, shuffle=True, validation_data=(x_test, x_test))
encoded_images = encoder.predict(x_train)
decoded_images = decoder.predict(encoded_images)
decoded_images_orig = numpy.reshape(decoded_images, newshape=(decoded_images.shape[0], 28, 28))
num_images_to_show = 5
for im_ind in range(num_images_to_show):
plot_ind = im_ind*2 + 1
rand_ind = numpy.random.randint(low=0, high=x_train.shape[0])
matplotlib.pyplot.subplot(num_images_to_show, 2, plot_ind)
matplotlib.pyplot.imshow(x_train_orig[rand_ind, :, :], cmap="gray")
matplotlib.pyplot.subplot(num_images_to_show, 2, plot_ind+1)
matplotlib.pyplot.imshow(decoded_images_orig[rand_ind, :, :], cmap="gray")
matplotlib.pyplot.figure()
matplotlib.pyplot.scatter(encoded_images[:, 0], encoded_images[:, 1], c=y_train)
matplotlib.pyplot.colorbar()
结论
本教程介绍了称为自动编码器的深度学习生成模型。这个模型由两个构件组成:编码器和解码器。前者将输入数据编码为一维向量,然后对其进行解码以重建原始数据。我们看到了如何使用 Keras 应用这个模型来压缩来自 MNIST 数据集的图像。
自动编码器和视觉相似性
原文:https://blog.paperspace.com/autoencoders-and-visual-similarity/
有没有想过图片搜索是如何工作的,或者社交媒体平台是如何推荐与你经常喜欢的图片相似的图片的?在这篇文章中,我们将看看自动编码器的另一个有益用途,并试图解释它们在计算机视觉推荐系统中的效用。
设置
我们首先需要为今天的任务导入相关的包:
# article dependencies
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
import torchvision.datasets as Datasets
from torch.utils.data import Dataset, DataLoader
import numpy as np
import matplotlib.pyplot as plt
import cv2
from tqdm.notebook import tqdm
from tqdm import tqdm as tqdm_regular
import seaborn as sns
from torchvision.utils import make_grid
import random
import pandas as pd
我们还检查机器是否有 GPU,如果有的话,让 Torch 在 CUDA 上运行。
# configuring device
if torch.cuda.is_available():
device = torch.device('cuda:0')
print('Running on the GPU')
else:
device = torch.device('cpu')
print('Running on the CPU')
视觉相似性
在人类视觉的背景下,我们人类能够通过感知图像的形状和颜色来进行图像之间的比较,使用这些信息来评估它们可能有多相似。然而,当涉及到计算机视觉时,为了理解图像,必须首先提取它们的特征。此后,为了比较两幅图像可能有多相似,需要以某种方式比较它们的特征,以便用数值来度量相似性。
自动编码器的作用
正如我们所知,自动编码器在表示学习方面非常出色。事实上,他们能够很好地学习表示法,能够将像素拼凑在一起,并得到原始图像。
基本上,autoencoder 的编码器充当特征提取器,提取的特征随后在瓶颈/代码层压缩成矢量表示。在这种情况下,瓶颈层的输出可以被视为图像的最显著特征,它保存了图像的颜色和边缘的编码。有了这种特征编码,人们就可以继续比较两幅图像,以测量它们的相似性。
余弦相似性度量
为了测量上一节提到的向量表示之间的相似性,我们需要一个特别适合这项任务的度量。这就是余弦相似性的由来,余弦相似性是一种通过比较向量空间中两个向量之间的角度来测量它们相似性的度量。
不同于像欧几里德距离这样的通过大小来比较向量的距离度量,余弦相似性只与两个向量是否指向相同的方向有关,这一属性使得它非常适合于度量显著的相似性。
Mathematical formula for cosine similarity.
利用自动编码器实现视觉相似性
在本节中,我们将训练一个自动编码器,然后使用自动编码器的编码器作为特征提取器,并使用余弦相似性作为评估相似性的度量,来编写一个视觉相似性函数。
资料组
对于本 autoencoder 系列中的典型文章,我们将使用 CIFAR-10 数据集。该数据集包含青蛙、马、汽车等物体的 32 x 32 像素图像。可以使用下面的代码单元格加载数据集。
# loading training data
training_set = Datasets.CIFAR10(root='./', download=True,
transform=transforms.ToTensor())
# loading validation data
validation_set = Datasets.CIFAR10(root='./', download=True, train=False,
transform=transforms.ToTensor())
CIFAR-10 images.
由于我们正在训练一个基本上无人监管的自动编码器,我们不需要对标签进行分类,这意味着我们可以只提取图像本身。出于可视化的目的,我们将从每个类中提取图像,以便查看自动编码器在所有类中重建图像的效果如何。
def extract_each_class(dataset):
"""
This function searches for and returns
one image per class
"""
images = []
ITERATE = True
i = 0
j = 0
while ITERATE:
for label in tqdm_regular(dataset.targets):
if label==j:
images.append(dataset.data[i])
print(f'class {j} found')
i+=1
j+=1
if j==10:
ITERATE = False
else:
i+=1
return images
# extracting training images
training_images = [x for x in training_set.data]
# extracting validation images
validation_images = [x for x in validation_set.data]
# extracting validation images
test_images = extract_each_class(validation_set)
接下来,我们需要定义一个 PyTorch 数据集类,以便能够在训练 PyTorch 模型时使用我们的数据集。这是在下面的代码单元格中完成的。
# defining dataset class
class CustomCIFAR10(Dataset):
def __init__(self, data, transforms=None):
self.data = data
self.transforms = transforms
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
image = self.data[idx]
if self.transforms!=None:
image = self.transforms(image)
return image
# creating pytorch datasets
training_data = CustomCIFAR10(training_images, transforms=transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]))
validation_data = CustomCIFAR10(validation_images, transforms=transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]))
test_data = CustomCIFAR10(test_images, transforms=transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]))
自动编码器架构
上图所示的自动编码器架构在下面的代码块中实现,将用于培训目的。这个 autoencoder 是专门为说明目的定制的,并且是专门为 CIFAR-10 数据集定制的。对于这个特定的产品,瓶颈大小为 1000,而不是 200。
# defining encoder
class Encoder(nn.Module):
def __init__(self, in_channels=3, out_channels=16, latent_dim=1000, act_fn=nn.ReLU()):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(in_channels, out_channels, 3, padding=1), # (32, 32)
act_fn,
nn.Conv2d(out_channels, out_channels, 3, padding=1),
act_fn,
nn.Conv2d(out_channels, 2*out_channels, 3, padding=1, stride=2), # (16, 16)
act_fn,
nn.Conv2d(2*out_channels, 2*out_channels, 3, padding=1),
act_fn,
nn.Conv2d(2*out_channels, 4*out_channels, 3, padding=1, stride=2), # (8, 8)
act_fn,
nn.Conv2d(4*out_channels, 4*out_channels, 3, padding=1),
act_fn,
nn.Flatten(),
nn.Linear(4*out_channels*8*8, latent_dim),
act_fn
)
def forward(self, x):
x = x.view(-1, 3, 32, 32)
output = self.net(x)
return output
# defining decoder
class Decoder(nn.Module):
def __init__(self, in_channels=3, out_channels=16, latent_dim=1000, act_fn=nn.ReLU()):
super().__init__()
self.out_channels = out_channels
self.linear = nn.Sequential(
nn.Linear(latent_dim, 4*out_channels*8*8),
act_fn
)
self.conv = nn.Sequential(
nn.ConvTranspose2d(4*out_channels, 4*out_channels, 3, padding=1), # (8, 8)
act_fn,
nn.ConvTranspose2d(4*out_channels, 2*out_channels, 3, padding=1,
stride=2, output_padding=1), # (16, 16)
act_fn,
nn.ConvTranspose2d(2*out_channels, 2*out_channels, 3, padding=1),
act_fn,
nn.ConvTranspose2d(2*out_channels, out_channels, 3, padding=1,
stride=2, output_padding=1), # (32, 32)
act_fn,
nn.ConvTranspose2d(out_channels, out_channels, 3, padding=1),
act_fn,
nn.ConvTranspose2d(out_channels, in_channels, 3, padding=1)
)
def forward(self, x):
output = self.linear(x)
output = output.view(-1, 4*self.out_channels, 8, 8)
output = self.conv(output)
return output
# defining autoencoder
class Autoencoder(nn.Module):
def __init__(self, encoder, decoder):
super().__init__()
self.encoder = encoder
self.encoder.to(device)
self.decoder = decoder
self.decoder.to(device)
def forward(self, x):
encoded = self.encoder(x)
decoded = self.decoder(encoded)
return decoded
卷积自动编码器类
为了将模型训练和利用打包到单个对象中,卷积自动编码器类定义如下。这个类有一些利用方法,如autoencode
促进整个自动编码过程,encode
触发编码器和瓶颈,返回 1000 个元素的矢量编码,decode
将 1000 个元素的矢量作为输入,尝试重建图像。
class ConvolutionalAutoencoder():
def __init__(self, autoencoder):
self.network = autoencoder
self.optimizer = torch.optim.Adam(self.network.parameters(), lr=1e-3)
def train(self, loss_function, epochs, batch_size,
training_set, validation_set, test_set):
# creating log
log_dict = {
'training_loss_per_batch': [],
'validation_loss_per_batch': [],
'visualizations': []
}
# defining weight initialization function
def init_weights(module):
if isinstance(module, nn.Conv2d):
torch.nn.init.xavier_uniform_(module.weight)
module.bias.data.fill_(0.01)
elif isinstance(module, nn.Linear):
torch.nn.init.xavier_uniform_(module.weight)
module.bias.data.fill_(0.01)
# initializing network weights
self.network.apply(init_weights)
# creating dataloaders
train_loader = DataLoader(training_set, batch_size)
val_loader = DataLoader(validation_set, batch_size)
test_loader = DataLoader(test_set, 10)
# setting convnet to training mode
self.network.train()
self.network.to(device)
for epoch in range(epochs):
print(f'Epoch {epoch+1}/{epochs}')
train_losses = []
#------------
# TRAINING
#------------
print('training...')
for images in tqdm(train_loader):
# zeroing gradients
self.optimizer.zero_grad()
# sending images to device
images = images.to(device)
# reconstructing images
output = self.network(images)
# computing loss
loss = loss_function(output, images.view(-1, 3, 32, 32))
# calculating gradients
loss.backward()
# optimizing weights
self.optimizer.step()
#--------------
# LOGGING
#--------------
log_dict['training_loss_per_batch'].append(loss.item())
#--------------
# VALIDATION
#--------------
print('validating...')
for val_images in tqdm(val_loader):
with torch.no_grad():
# sending validation images to device
val_images = val_images.to(device)
# reconstructing images
output = self.network(val_images)
# computing validation loss
val_loss = loss_function(output, val_images.view(-1, 3, 32, 32))
#--------------
# LOGGING
#--------------
log_dict['validation_loss_per_batch'].append(val_loss.item())
#--------------
# VISUALISATION
#--------------
print(f'training_loss: {round(loss.item(), 4)} validation_loss: {round(val_loss.item(), 4)}')
for test_images in test_loader:
# sending test images to device
test_images = test_images.to(device)
with torch.no_grad():
# reconstructing test images
reconstructed_imgs = self.network(test_images)
# sending reconstructed and images to cpu to allow for visualization
reconstructed_imgs = reconstructed_imgs.cpu()
test_images = test_images.cpu()
# visualisation
imgs = torch.stack([test_images.view(-1, 3, 32, 32), reconstructed_imgs],
dim=1).flatten(0,1)
grid = make_grid(imgs, nrow=10, normalize=True, padding=1)
grid = grid.permute(1, 2, 0)
plt.figure(dpi=170)
plt.title('Original/Reconstructed')
plt.imshow(grid)
log_dict['visualizations'].append(grid)
plt.axis('off')
plt.show()
return log_dict
def autoencode(self, x):
return self.network(x)
def encode(self, x):
encoder = self.network.encoder
return encoder(x)
def decode(self, x):
decoder = self.network.decoder
return decoder(x)
完成所有设置后,现在可以通过实例化 autoencoder 来训练它,并使用参数调用 train 方法,如下所示。
# training model
model = ConvolutionalAutoencoder(Autoencoder(Encoder(), Decoder()))
log_dict = model.train(nn.MSELoss(), epochs=15, batch_size=64,
training_set=training_data, validation_set=validation_data,
test_set=test_data)
在第一个时期之后,我们可以看到自动编码器已经开始学习足够强的表示,以便能够将输入图像放在一起,尽管没有太多细节。
Epoch 1.
然而,到了第 15 世纪,自动编码器已经开始以更精确的颜色和更好的形式将输入图像更详细地放在一起。
Epoch 15.
查看训练和验证损失图,两个图都是下降趋势,因此,模型实际上将受益于更多的训练时期。然而,对于这篇文章,15 个时期的训练被认为是足够的。
losses.
编写视觉相似性函数
现在,自动编码器已经被训练来重建 CIFAR-10 数据集中所有 10 个类别的图像,我们可以继续使用自动编码器的编码器作为任何图像集的特征提取器,然后使用余弦相似性比较提取的特征。
在我们的例子中,让我们编写一个函数,它能够接收任何图像作为输入,然后在一组图像中查找相似的图像(为此我们将使用验证集)。该函数定义如下:必须小心预处理输入图像,就像预处理训练图像一样,因为这是模型所期望的。
def visual_similarity(filepath, model, dataset, features):
"""
This function replicates the visual similarity process
as defined previously.
"""
# reading image
image = cv2.imread(filepath)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = cv2.resize(image, (32, 32))
# converting image to tensor/preprocessing image
my_transforms=transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
image = my_transforms(image)
# encoding image
image = image.to(device)
with torch.no_grad():
image_encoding = model.encode(image)
# computing similarity scores
similarity_scores = [F.cosine_similarity(image_encoding, x) for x in features]
similarity_scores = [x.cpu().detach().item() for x in similarity_scores]
similarity_scores = [round(x, 3) for x in similarity_scores]
# creating pandas series
scores = pd.Series(similarity_scores)
scores = scores.sort_values(ascending=False)
# deriving the most similar image
idx = scores.index[0]
most_similar = [image, dataset[idx]]
# visualization
grid = make_grid(most_similar, normalize=True, padding=1)
grid = grid.permute(1,2,0)
plt.figure(dpi=100)
plt.title('uploaded/most_similar')
plt.axis('off')
plt.imshow(grid)
print(f'similarity score = {scores[idx]}')
pass
因为我们要将上传的图像与验证集中的图像进行比较,所以在使用该函数之前,我们可以通过从所有 1000 幅图像中提取特征来节省时间。这个过程也可以被写入相似性函数,但是它会以计算时间为代价。这是下面做的。
# extracting features from images in the validation set
with torch.no_grad():
image_features = [model.encode(x.to(device)) for x in tqdm_regular(validation_data)]
计算相似度
在这一部分中,一些图像将被提供给视觉相似性函数,以访问所产生的结果。然而,应该记住,只有训练集中存在的类中的图像才会产生合理的结果。
图片 1
考虑一只白色背景的德国牧羊犬,如下图所示。这种狗主要是金色的皮毛,黑色的马鞍,观察到它警惕地站在左边。
在将该图像传递给视觉相似性函数时,产生上传图像相对于验证集中最相似图像的绘图。请注意,根据模型的要求,原始图像被缩小为 32 x 32 像素。
visual_similarity('image_1.jpg', model=model,
dataset=validation_data,
features=image_features)
从结果中,返回了一个白色背景图像,一只看似深色皮毛的狗面向左侧警惕地站着,相似性得分为 92.2%。在这种情况下,模型基本上找到了一个与原始图像的大部分细节相匹配的图像,这正是我们想要的。
图片 2
下面的图片是一只棕色的青蛙俯卧在白色背景上,面朝右。同样,通过我们的视觉相似度函数传递图像会产生上传图像与其最相似图像的对比图。
visual_similarity('image_2.jpg', model=model,
dataset=validation_data,
features=image_features)
从结果图来看,与我们上传的图像位置相似(俯卧)的一只看起来有些灰色的青蛙的相似性得分约为 91%。请注意,该图像也描绘在白色背景上。
图 3
最后,下面是另一只青蛙的图片。这只青蛙呈绿色,与上一张照片中的青蛙处于类似的俯卧位置,但区别在于它面向左侧,并被描绘在纹理背景上(本例中为沙子)。
visual_similarity('image_3.jpg', model=model,
dataset=validation_data,
features=image_features)
就像前面两个部分一样,当图像被提供给视觉相似性函数时,返回原始图像和在验证集中找到的最相似图像的图。在这种情况下,最相似的图像是一只棕色的青蛙,俯卧,面向左侧,也描绘在纹理背景上。返回大约 90%的相似性分数。
从本节中用作示例的图像可以看出,视觉相似性函数发挥了应有的作用。然而,随着更多时代的训练或者可能更好的架构,有可能在最初几个最相似的图像之外做出更好的相似性推荐。
结束语
在本文中,我们可以看到自动编码器的另一个有益用途,这一次是作为视觉相似性推荐的工具。在这里,我们探讨了如何将 autoencoder 的编码器用作特征提取器,然后使用余弦相似性比较提取的特征,以找到相似的图像。
基本上,在这种情况下,autoencoder 所做的就是提取特征。事实上,如果你非常熟悉卷积神经网络,那么你会同意不仅自动编码器可以作为特征提取器,而且用于分类目的的网络也可以用于特征提取。因此,这意味着它们依次用于视觉相似性任务。
用于评估文本生成质量的自动化度量
原文:https://blog.paperspace.com/automated-metrics-for-evaluating-generated-text/
自动文本生成是使用机器学习系统生成自然语言文本的任务。这里的生成可以是开放式的,也可以根据一些用例进行引导。一个训练有素的文本生成系统很难区分人类和机器编写的文本。文本生成在现实世界中的一些流行用例是 Gmail 的智能回复、智能手机的自动完成等。
评估这些系统可能会变得棘手。如今,大多数可用的系统本质上都是概率性的,在生成文本时,经常会看到幻觉出事实信息。因此,执行评估的最佳方式之一是在人类 的帮助下完成,但这可能并不总是可行的,因为这是一个昂贵且耗时的过程。由于取样偏差,它也不能保证重现性。此外,手动进行评估时,不能进行大规模评估。因此,由于上述原因,在评估文本生成系统时,自动度量通常是首选的。自动度量可以是经过训练的,也可以是未经训练的。正如预期的那样,经过培训的指标在进行评估时试图考虑任务细节,但代价是要学习相关数据的功能,而未经培训的指标是通用的,不需要培训,独立于语言,并且计算速度更快。未经训练的度量标准很受欢迎,在工业和学术界广泛用于测量生成文本的质量,涵盖许多用例,如机器翻译、文本摘要、故事生成、图像字幕等。
在这篇博客中,我们将重点关注一些流行的未经训练的指标(带代码),用于评估现有的自然语言生成 (NLG)系统生成的文本质量,从经典的基于模板的生成系统到先进的模型,如【GPT】、序列模型等。
胭脂
ROUGE 代表Recall-OorientedUunder study forGistingE估价。这是在 2004 年本文中介绍的。这是一个非常流行的度量标准,你肯定会在文本摘要的相关文献中找到。该指标基于计算候选摘要和参考摘要(或任何其他文本片段)之间的语法重叠。胭脂-1、胭脂-2、胭脂-L 和胭脂-S 是一些常用的计算数字。这里, Rouge-1 计算候选文本块和参考文本块之间的单字(单个单词)的重叠。 Rouge-2 计算候选文本块和参考文本块之间的二元模型(词对)的重叠。 Rouge-L 计算候选文本片段和参考文本片段之间序列 n 元语法中最长同现的重叠。 Rouge-S 计算候选文本块和参考文本块之间Skip-bigram(任意一对单词的句子顺序)的重叠度。Rouge-1 精确召回的数学公式如下所示
ROUGE-1 Precision, Recall
在文献中,你会经常发现 R1、R2、RL 等车队的 F1 成绩都被提及。胭脂的实现在下面完成-
from rouge import Rouge
rouge = Rouge()
def calculate_rouge(candidate, reference):
'''
candidate, reference: generated and ground-truth sentences
'''
scores = rouge.get_scores([candidate], reference)
return scores
ROUGE Calculation in Python
随意查看 python 包 在这里 也可以查看这个 在线演示 。
蓝色
BLEU 代表BIlin gualE估价Uunder research。这是一个非常流行的度量标准,你肯定会在机器翻译的文献中找到,并且是在 2002 年的这篇论文中提出的。将候选翻译与一个或多个参考翻译进行比较时使用的度量。并且输出位于 0-1 的范围内,其中接近 1 的分数指示良好质量的翻译。
BLEU-1、BLEU-2、BLEU-3 和 BLEU-4 是一些常见的计算数字,您会经常在文献中发现它们。这里,1、2、3 和 4 中的每一个表示在计算 BLEU 分数时考虑的 n 元语法序列的精度。BLEU 的数学公式如下所示-
BLEU Formulation | Image from Source
该指标还引入了“简洁惩罚”的概念(上述公式中的第一项)。其中,如果输出比参考句子短,我们用那个因子惩罚 BLEU 分数,因此解决了子集生成的问题。
BLEU 的实现在下面完成-
from nltk.translate.bleu_score import sentence_bleu
from nltk import word_tokenize
def calculate_bleu(candidate, reference):
'''
candidate, reference: generated and ground-truth sentences
'''
reference = word_tokenize(reference)
candidate = word_tokenize(candidate)
score = sentence_bleu(reference, candidate)
return score
BLEU Calculation in Python
请随意查看这里的 python 包 ,并关注这里的中的更多示例。知道什么时候不使用度量标准也很好,为此我建议你阅读评估 NLP: BLEU 中的文本输出,风险自担。
贝特斯科尔
顾名思义,BERTScore 肯定和 BERT 有关系吧?所以,是的,BERTScore 使用 BERT 来计算 NLG 系统生成的文本的质量。这是最近在 2020 年的这篇论文中提出的。与大多数主要利用假设和参考文本片段之间的标记或短语级句法重叠的方法不同, BERTScore 另一方面通过使用由 BERT 模型生成的语境化嵌入来捕捉语义方面。
BERTScore Pipeline | Modified Image from Source
从上图中可以看出,它首先为参考(基本事实)和候选(由 NLG 系统生成的)文本片段获取语境化的单词嵌入。然后,它从参考和候选中计算每个单词之间的余弦相似度,得到语义上最相似的单词。然后使用上图中描述的公式计算精度、召回率和 F 值。
Recall Calculation of BERTScore with Importance weights
该论文的作者还谈到了、“重要性权重”的概念,与普通单词不同,这种概念赋予那些被认为对文本更重要和更专有的单词更多的权重。为此,他们使用逆文档频率 (IDF)权重。直觉告诉我们,一个单词在文档中越常见,它对相关文档的重要性就越低。因此,这些单词分数的重要性并不重要。在这种情况下,我们的 IDF 值会很低。另一方面,如果该单词对于相关文档是唯一的,则它将具有高 IDF 值。在这里,权重的选择是可以调整的,也是可以探索的。
使用 ti iger Bert _ score GitHub repo 可以轻松实现该代码。按照说明安装库,然后就可以像这样通过终端运行 BERTscore 进行英语语言评测了:
bert-score -r original_text.txt -c generated_text.txt --lang en
出于实现的目的,请随意使用 python 库。同样的还有一个非常好的视频解释。你可以在这里查看。
流星
流星代表米米 E 用 E xplicit 或表示 T 的估价。这是在本文中介绍的。它可用于评估各种任务的输出,如机器翻译、文本摘要、图像字幕等。但是你会经常发现它被用在机器翻译的文献中。它还声称与人类的判断有很高的相关性。
与 BLEU 度量标准(在评估来自机器翻译系统的 o/p 时很流行的一种度量标准)相比,它有一些明显的优点,BLEU 度量标准主要关注的是根据基本事实捕捉生成的精度方面。另一方面,METEOR 基于计算单字精度和召回率的调和平均值的思想,其中召回率比精度更重要。给予回忆更大的权重,可以让我们更好地理解,在生成过程中,作为输出的一部分,我们产生了多少基础事实。确切的数学公式如下图—
Meteor F-score Calculation
METEOR 也有一个关于“块惩罚”的概念,它不仅考虑了单字的重叠,还考虑了块(连续单词)的重叠,以强加某种程度的排序。用“p”表示,计算如下-
Chunk Penalty Calculation
这里,c 和 um 分别是在候选句子中的参考和单字中出现的候选语块的数量。最后,流星分数(M)的计算是通过将因子“p”乘以 F 分数来完成的,如下所示
METEOR Score Calculation
流星的实现在下面完成-
from nltk.translate import meteor
def calculate_meteor(candidate, reference):
'''
candidate, reference: tokenized list of words in the sentence
'''
reference = word_tokenize(reference)
candidate = word_tokenize(candidate)
meteor_score = round(meteor([candidate],reference), 4)
return meteor_score
Meteor Implementation in Python
请随意查看同一网站上的 nltk 文档。
自我蓝色
Self-BLEU 是对传统 BLEU 指标的巧妙使用,用于捕获和量化生成文本中的多样性。它是由上海交通大学和伦敦大学学院的研究人员在 2018 年的这篇论文中提出的。
self-bleu 分值越低,生成的文本的多样性越高。像故事生成、新闻生成等长文本生成任务非常适合关注这些指标,有助于评估模型中的冗余性和单调性。这个度量可以用其他文本生成评估度量来补充,这些度量说明了所生成文本的良好性和相关性。相关性和多样性的最佳结合点将产生完美的混合输出。
算法非常简单,如下:
- 对于给定的输入,从一组生成的句子中挑选一个句子。计算这个句子和其他句子之间的 BLEU 分数。
- 迭代所有独特的句子,为所有句子生成 BLEU 分数,并将它们存储在一个列表中。
- 最后,取作为步骤 2 的一部分计算的列表的平均值。
import numpy as np
import copy
def get_bleu_score(sentence, remaining_sentences):
lst = []
for i in remaining_sentences:
bleu = sentence_bleu(sentence, i)
lst.append(bleu)
return lst
def calculate_selfBleu(sentences):
'''
sentences - list of sentences generated by NLG system
'''
bleu_scores = []
for i in sentences:
sentences_copy = copy.deepcopy(sentences)
remaining_sentences = sentences_copy.remove(i)
print(sentences_copy)
bleu = get_bleu_score(i,sentences_copy)
bleu_scores.append(bleu)
return np.mean(bleu_scores)
calculate_selfBleu(sentences)
Self-BLEU Implementation in Python
字移动器的距离
顾名思义,它是关于在一些常见的超空间表示中计算一个单词移动到另一个单词所需要的距离。该方法可用于通过计算将一个句子转换成另一个句子所需的最小单词-单词距离来确定生成的文本片段和参考文本片段之间的语义接近度。然而,虽然 WMD 对于短文本来说工作得很好,但是计算时间显著增加,并且随着文档长度的增加,整体语义本质也有所丢失。有人试图用一些近似值来固定整个大规模杀伤性武器的计算,请随意查看这个和这个。
下图显示了该算法的直观解释-
WMD Visualization | Image from Source
从上图中可以看出,我们有两个干净的句子 d 和d’分别为“奥巴马向媒体伊利诺伊州发言”和“总统向新闻界致意。接下来我们要做的就是把每一句话的每一个记号投射到某个 d 维的公共超空间中。人们可以使用现成的现有方法来实现这一目的,如 word2vec、gloVe、fasttext、BERT 等,或者根据他们的数据训练他们的版本。表示方法越好,整体距离计算就越好。一旦完成,下一步就是计算 WMD,方法是对来自 d 的每个单词到它最近的来自文档 d '的(在语义上)单词的距离求和。比如——“speaks”最接近“greets”,“media”最接近“press”,“Obama”最接近“press”,“Illinois”最接近“Chicago”。最初的方法计算 L2(欧几里德)距离作为距离的度量。但是在处理非常高的维度时,可以使用其他距离,比如余弦。
让我们再举一个例子,看看距离的计算——从下图可以看出,文档 D1 和 D2 中的每个单词都与文档 D0 中最近的单词对齐。从 D1 到 D0 的重要令牌所覆盖的总最小距离是 1.07,而从 D2 到 D0 的重要令牌所覆盖的总最小距离是 1.63。距离越短,相似性越大。
WMD Illustration | Modified Image from Source
它的实现非常方便,可以通过 gensim 库来完成,如下所示-
import gensim.downloader as api
model = api.load('word2vec-google-news-300')
def calculate_wmd(model, hypothesis, reference):
'''
model - word vector model
hypothesis - generated sentence
reference - groud-truth sentence
'''
distance = model.wmdistance(
hypothesis,
reference
)
return distance
WMD implementation in Python Gensim
请随意查看 gensim 的文档。
总结想法
我们今天讨论的每一个指标都试图根据可用的基本事实来捕捉生成文本的某些方面。因此,对于给定的任务,测量和报告其中一个以上是可行的。对 ROUGE、BLEU 和 METEOR 在捕获同义词方面也做了一些扩展,使它们对重叠模式更加健壮。此外,对于依赖于语法重叠的度量标准,它建议在进行计算之前,对生成的文本和参考文本进行规范化(区分大小写、词根、形态变化等)。与我们今天讨论的非参数化不同,在“习得指标”领域也有很多活跃的研究。例如,你可能想要查看布莱特 ( 论文,视频),以及感知得分。我们将在另一个博客上继续讨论。
我希望你喜欢读它。谢谢大家!
参考
自动化快照简介
Paperspace 必须提供的最强大的功能之一是为您的机器创建即时备份的能力。快照是一种非常简单且无风险的测试软件或机器上其他更改的方法,只需点击一个按钮,您就可以及时回滚。我们收到了无数的电子邮件,解释快照如何帮助救援损坏的机器,找回丢失的文件,抵御恶意软件,并且通常只是提供一种安全感和内心的平静。
到目前为止,拍摄快照是一个手动过程。它们很快——只需要一秒钟左右就能完成——但这是你必须记住要做的事情。
从今天开始,您可以设置自动快照周期,该周期将按照设定的计划自动拍摄快照。该计划包括每小时、每天、每周或每月的选项。您还可以选择想要保存的快照数量。
自动快照功能在创建计算机时可用,也可以通过单击控制台中的快照按钮添加到任何现有计算机中。当然,手动拍摄快照仍然是可能的。另一方面,自动快照意味着你再也不用考虑备份你的机器了。
AMPT 遗传算法:面向 GPU 应用的自动混合精度浮点调优
原文:https://blog.paperspace.com/automatic-mixed-precision-ampt-ga/
介绍
高性能计算(HPC)科学应用在很大程度上依赖于浮点运算来获得高精度输出。在这些程序中,某些变量的精度可能会降低,以加快执行速度或最小化计算能量。
最新的 NVIDIA GPUs 执行单精度浮点运算的吞吐量是双精度的两倍。当我们努力运行大规模的应用程序时,这种权衡变得更加重要,因为问题的规模越来越大。应用结果的准确性至关重要,必须满足用户定义的准确性或误差限制。混合精度计算是将各种浮点变量的各种精度结合起来的实践。如果我们遵循确定准确性的规则,混合精度训练可以大大加快任何深度学习任务的速度。
自动混合精度之路
GPU 领域的最新发展为提升性能开辟了新的途径。除了 FP32(单精度)之外,GPU 中还添加了 FP64(双精度)和 FP16(半精度)算术单元。结果是,使用 FP64/FP32/FP16 指令可以同时实现不同的性能水平。
在这些处理器发布之前,混合 FP64 和 FP32 指令对性能的影响有限,因为混合精度数学单元很少可用。选择混合精度时需要转换。
作为使用混合精度训练的效用的一个例子,百度研究人员发现,在用于语音识别和机器翻译的 LSTMs 领域,在 GPU 上使用 FP32 和 FP16 变量的组合,而精度没有变化。
先前工作的背景
我们这篇博文所基于的论文可以在这里找到:https://engineering . purdue . edu/dcsl/publications/papers/2019/GPU-FP-tuning _ ics 19 _ submitted . pdf
以下是混合精度优化的一些相关背景和前期工作:
- 首先,过去的工作不支持 GPU 编程模型中存在的并行代码,因为它们依赖于串行工具或不跨越 GPU 编程和执行范例的分析辅助,例如 CPU 到 GPU 的调用。
- 第二个问题是,以精度为中心的技术没有一个性能模型,并且假定最快的运行时间是以最少的精度实现的。如果混合足够精确到可以进行造型,则数学运算只能满足于最精确的操作数。另一方面,造型是一种昂贵的操作,因此降低精度实际上可能会延长完成的时间。在许多 GPU 上存在 FP64 和 FP32 的并行资源池,组合精度提供了早期工作所忽略的额外并行机会。为了达到总体精度标准,已经建立了精度级别,但这些级别通常不能提高应用程序的执行时间。
- 最后,依赖实时信息的方法会遇到与搜索空间相关的问题。如果架构支持可以调整的 n 个浮点变量的三个精度级别,则搜索空间是(3 的 n 次方)。在 n 可能达到几十万的大型生产级科学应用中使用这种策略是不可能的。
AMPT 遗传算法:一种混合精度优化系统
为了解决 GPU 应用的性能最大化挑战,开发了 AMPT 遗传算法。这是一个混合精度优化系统。正如程序的最终用户所确定的,AMPT 遗传算法的目标是在应用程序级别选择浮点变量的精度级别,以提高性能,同时将引入的误差保持在可接受的阈值以下。精度向量(PV)是 AMPT 遗传算法的最终输出,它将每个浮点变量分配到一个精度级别。
AMPT 遗传算法的主要思想是,静态分析通过可行精度向量的广阔空间来辅助动态搜索方法。在该论文中,使用静态分析来识别其精度应该被集体修改的变量分组,以减少通过转换对任何一个变量的精度修改的性能的影响。这种信息可以加快大范围搜索的速度。研究人员采用遗传算法进行搜索,这有助于避免早期技术容易陷入的局部最小值。
该报声称的创新
- 在遗传搜索方法中使用 GPU 兼容的静态分析提高了识别可行的混合精度程序的效率。基于静态构建性能模型的执行过滤器排除了无利可图的精确向量,从而降低了 AMPT 遗传算法在搜索过程中所需的执行次数。
- 应用级精确制导是由 AMPT 遗传算法提供的,而不是过去研究中观察到的本地化内核、函数或指令。
- 实验表明,我们的搜索技术可以比当前的最佳实践更有效地避免局部最小值,从而节省大量时间。
我们发现,AMPT-遗传算法能够优于最先进的 pre doctory 方法,在 LULESH 的混合精度计算中,在所有双变量的基线上发现了 77.1%的额外加速,而 pre doctory 使用了类似数量的程序执行。我们的铸造感知静态性能
模型允许 AMPT 遗传算法找到 precision 由于局部最小值问题而无法识别的精确组合。通过三个具有代表性的 Rodinia 基准测试,LavaMD、Backprop 和 CFD,当容许误差阈值宽松时,我们实现了 11.8%-32.9%的额外加速,当误差阈值最严格时,实现了-5.9%-39.8%的额外加速。我们选择这三个
,因为它们涵盖了内核的数量和大小范围,以及最新一代 GPU 机器上可用的精度范围。我们还评估了三种解决方案的效率,即搜索最优 PV 所需的执行次数的性能增益,AMPT-遗传算法在这一指标上优于其他算法。
值得注意的是,美国能源部(DOE)使用 Lulesh 作为代理应用程序来测试大型集群,因为它代表了许多大型代码。
它模拟流体动力学方程,解释物质在受力时的相互运动。研究人员使用 CUDA 版本的 LULESH,输入大小为 s 50,并在 NVIDIA Tesla P100 GPU 机器上运行所有实验。
通过改变 FP32 或 FP64 空间中 LULESH 的变量级别的精度来进行研究。FOM(品质因数)被定义为模拟中部件被处理的速度。
通常的做法是在每个 LULESH 码的末尾输出 FOM 度量来测量整体性能。尽管挂钟和 FOM 的增益是成正比的,但由于更深奥的缩放原因,FOM 比普通执行时间更受青睐。评估的真实统计数据是精度级别最低的 FOM 占 FOM 的百分比。
我们发现,与之前的评估相比,LULESH 是一个相当复杂的代码,在 GPU 端包含 7,000 行 C++代码,在 CPU 端包含 600 行 LOC 代码。测试总共使用了 76 个浮点变量。与比较相关的另一个重要事实:precidont 是可以获得混合精度解的最广泛的程序,包含 32 个变量。
本研究共进行了五个实验:
- 优化的精度:在这个实验中,研究人员试图评估 AMPT 遗传算法相对于最先进的 pre doctory 和天真的遗传算法的相对执行效益。我们可以在图 a、图 b、图 c 和图 d 中看到结果。
- 误差阈值符合性:该实验证实了之前实验中挑选的肺静脉满足用户定义的误差约束(见图 e)。
- 组件测试:本实验的目的是研究各种组件对 FOM 度量的影响(见图 f)。
- 局部与全局模型:本实验的目标是量化应用程序级调优相对于内核或函数级调优的优势(见下表)。
- 精确矢量概括:在这个实验中,测试了在用户提供的输入值的狭窄范围内概括输入的能力(见图 g)。
研究人员使用 FOM(品质因数)比较精确调整后的应用程序性能,该指标与吞吐量同义,并标准化为所有浮点的 FOM。AMPT 遗传算法超过了三个阈值中的两个,并且在最高阈值上优于朴素的解决方案。见下图。
figure a
下图显示了每个协议的执行次数。不出所料,nave 的执行次数最多,紧随其后的是没有执行过滤器的 AMPT GA。AMPT GA 的执行数量少于或相当于 pre doctory 执行的数量。
figure b
对于所有三个误差阈值,AMPT 遗传算法是最有效的方法,如下所示。
figure c
下图描述了每个算法在单个程序运行期间的效率增益。结果表明,与其他两种技术相比,AMPT 遗传算法能快速收敛到更高的 FOM 解。这是由于 GA 能够做出比 delta 调试更显著的跳跃(delta 调试更侧重于局部搜索)。
figure d
每种技术的性能和利用现有错误的能力如下图所示。这些结果表明,所有的技术,无论是严格的还是灵活的,都满足误差约束。另一方面,Precimonious 无法利用 2.5 阈值的可用误差裕量。
figure e
T=2.5 情况下的 FOM 用激活的 AMPT 遗传算法的各种模块来说明。最左边的场景用实际的程序执行替换了性能模型,在那里它的性能最好,但代价是额外执行 10 倍。分组和超维空间突变的影响最大,而执行过滤器和预填充对这一特定错误阈值的影响可以忽略不计。见下图。
figure f
下表展示了 LULESH、K1、K2 和 K3 三个内核对于特定局部优化精度矢量的性能增益和误差。
table
- 由于影响 K1 的 PV 项是全浮点型,而影响 K2 和 K3 的 PV 项是全双精度型,因此 FOM 和误差存在应用级偏移。K2 和 K3 的处理方式相似。
- 如果内核级的调整足够充分,整个应用程序的 FOM 应该等于各种内核级增益的总和,但是应用程序级的度量要低 15.1 %。
- 与所有单个内核错误的总和相比,总的系统错误要低得多(52.6 %)。
误差 CDF 针对 PV 给出,对于接近测试输入的输入,目标误差为 2.5。这些发现表明,大多数邻居位于误差极限之下,因此结果可以以 80 %的置信度概括。见下图。
figure g
使用 Rodinia 基准程序进行评估
如上所述,这种方法是使用 Rodinia GPU 基准测试套件中的三个程序进行评估的:LavaMD、Backprop 和 CFD。LavaMD 是一个分子动力学程序,有一个内核和 15 个浮点变量。Backprop 是一个具有 21 个浮点变量的双内核基准程序,用于实现模式识别中的一种算法。CFD 是一个四核程序,有 26 个浮点变量,是计算流体动力学的标准基准。获得的结果如下:
来自 LavaMD 的结果:下图显示了朴素遗传算法在所有情况下都能产生最大的 FOM 值,尽管它比其他方法需要更大比例的程序执行。AMPT GA 获得了比 precid only 更高的 FOM。
反向预测的结果 : AMPT 遗传算法获得了与原始遗传算法相同的 FOM 值,而消耗的执行次数不到原始遗传算法的三分之一。对于所有三种误差水平,AMPT 遗传算法是最有效的方法;可以看到下图。
计算流体动力学的结果:下图显示,在所有场景中,朴素遗传算法和 AMPT 遗传算法的 FOM 值最大;然而,naive 使用的程序执行数量远远高于 AMPT 遗传算法。
AMPT 遗传算法的广泛概述
要使用 AMPT 遗传算法,您需要一个测试工具、一个来自目标应用程序或内核的输入、一个可以调整的变量列表,以及一个带有目标错误阈值(T)的指定错误度量。如果没有提供变量列表,我们将使用应用程序的浮点变量或目标 GPU 设备的内核来进行分析。本文假设该体系结构仅处理具有相同精度输入的指令。
- 必须建立性能模型来预测降低所考虑的浮点变量精度的影响。
- 使用一个 LLVM 通道的静态分析被用来建立模型,它在程序的变量之间创建一个依赖图。图节点是指令和变量定义,边将定义与它们在指令中的应用联系起来。
- 当给出每个变量的精度水平时,该图测量每个精度的操作数和发生的铸件数。该信息被转换成性能分数,该性能分数表示由于潜在 PV 中更快更低精度的指令而导致的相对性能。
- 动态执行参数(如循环运行的次数)不会被静态传递捕获,因为它们不会改变。虽然不能保证被赋予相同分数的函数的执行时间是相同的,但是该分数确实提供了不同 PV 之间加速潜力的相对排名。
- 为了找到具有最高性能的 PV,同时将误差水平保持在某个阈值以下,我们使用了调整的遗传算法(GA)。这是因为 PV 空间是二进制受限的,PV 之间的梯度不平滑,并且目标在误差和性能方面是非线性的。
- 只有当预测的性能比获得的最佳性能更好时,才执行搜索空间中的一个点。GA 在考虑 PV 的同时运行程序以找到某个点,然后评估性能和误差。如果该搜索点的性能比迄今为止的最佳性能差(这将由于我们的性能模型中的错误而发生),或者如果该错误大于阈值,则该搜索点被拒绝。否则,将考虑在未来的 GA 代中选择它。
- 为了避免与从属参数(相关变量组)相关的惩罚,变异函数已被修改,以改变超维组空间中的某些变量。使用遗传算法,我们可以评估最好的可能使用的 PV。下图说明了这些不同的阶段是如何交织在一起的。
Source: Paper
- 为了重申上面的重要观点,我们必须要求 GA 在考虑当前 PV 的情况下运行应用程序,因为这是一个昂贵的提议(与查询我们的性能模型相比),所以只有当性能模型预测它将是一个潜在有价值的配置时,我们才会这样做。
最终用户工作流程:对于 AMPT-GA 的部署,最终用户必须提供应用程序源,定义测试输入,并设置错误的目标阈值。AMPT 遗传算法将确定所有浮点变量的精度向量,以优化效率,同时达到阈值。允许最终用户选择应该修改哪些浮点变量是可能的。
所有这一切都可以从一台 Paperspace Core GPU 驱动的机器上运行,直接进行测试。
AMPT 遗传算法的操作概念
- AMPT-遗传算法的操作:AMPT-遗传算法是一种优化技术,用于选择最佳精度向量(PV)以最大化性能,同时将误差保持在用户指定的限制范围内。我们将约束实现为目标函数惩罚,从而拒绝错误的解决方案,而关于错误程度的信息保留在分数中,以帮助指导搜索。
Source: Paper
- perf (PV)是性能得分,值越低表示性能越好
- 误差(PV)指由指定 PV 引起的误差度量值,而 T 是该误差度量的阈值
- k 是一个乘法因子,其定义如下:
1,如果误差(PV) ≤T 和 P,一个大于最大 perf
值的大值,如果误差(PV) > T
2 。用户定义的误差指标。我们使用(默认情况下)一个应用通用的误差指标,即与最精确结果相比的精度位数。
3 。感兴趣的变量:用户可以在应用程序中加入任何浮点变量,包括数组、结构和单个变量。在 AMPT 遗传算法中,选择那些由通用分析工具确定的运行时间长的 GPU 内核中使用的变量。我们从运行时间最长的内核开始,包括它,然后根据运行时间在列表中向下移动。当我们包含了足够多的内核来捕获整个程序执行时间的一定比例时,我们就终止了。在捕获了整个程序运行时间的一定比例后,我们终止测试。
4 。黑盒搜索:“黑盒”优化程序被用作我们搜索的起点,它采用遗传算法(GA)而无需任何程序分析来指导搜索。
5 。依赖图和静态性能
模型:可以使用静态性能模型和中间依赖图来估计特定 PV 的性能增益。当提供 PV 和应用程序源代码时,该方法估计代码中静态发生的浮点和双精度运算以及强制转换的数量。当在程序的编译的混合精度版本中估计强制转换和操作类型的数量时,依赖图基于全双精度方案的程序结构。
6 。精确矢量优化: AMPT 遗传算法使用一种优化方法来确定指定误差阈值的最佳 PV。
7 。执行过滤:AMPT 遗传算法使用实际的程序执行来测量和验证错误度量是否低于阈值。这意味着必须首先执行应用程序来评估尚未看到的 PV 的目标函数。
8 。程序转换:在 AMPT 遗传算法中,我们必须最终将 PV 应用到应用程序中,以使程序的实际操作以修改后的精度进行。
结论
AMPT 遗传算法是一种解决方案,用于提高使用计算密集型 GPU 内核的应用程序的可变精度。因此,我们有两项创新:
- 确定需要同时改变的变量的静态分析。
- 一种搜索技术,可以快速移除无利可图的 PVs,基于适用的惩罚,如类型转换。
使用 PyTorch 的自动混合精度
原文:https://blog.paperspace.com/automatic-mixed-precision-using-pytorch/
介绍
更大的深度学习模型需要更多的计算能力和内存资源。通过新技术的发展,已经实现了深度神经网络的更快训练。代替 FP32(全精度浮点数格式),你可以使用 FP16(半精度浮点数格式),研究人员发现串联使用它们是更好的选择。最新的 GPU,如 Paperspace 提供的 Ampere GPUs,甚至可以利用较低的精度,如 INT8。
混合精度允许半精度训练,同时仍保留大部分单精度网络精度。术语“混合精度技术”是指这种方法同时使用单精度和半精度表示。
在使用 PyTorch 进行自动混合精度(Amp)训练的概述中,我们演示了该技术的工作原理,一步一步地介绍了使用 Amp 的过程,并讨论了 Amp 技术的更高级应用,包括代码支架,供用户以后与自己的代码集成。
混合精度概述
像大多数深度学习框架一样,PyTorch 通常在 32 位浮点数据(FP32)上进行训练。另一方面,FP32 并不总是成功的必要条件。一些操作可以使用 16 位浮点,其中 FP32 消耗更多的时间和内存。
因此,NVIDIA 工程师开发了一种技术,允许在 FP32 中执行少量操作的混合精度训练,而大部分网络在 FP16 中运行。
混合精度理论的详细解释可以在这里找到。实施混合精度训练需要三个阶段:
- 只要可行,就转换模型以利用 float16 数据类型。
- 保持浮动 32 个主权重,以累积每次迭代的权重更新。
- 使用损失缩放来保持微小的梯度值。
PyTorch 中的混合精度
对于混合精度训练,PyTorch 提供了丰富的内置特性。
当你调用.half()
方法时,一个模块的参数被转换成 FP16,当你调用.half()
时,一个张量的数据被转换成 FP16。快速 FP16 算法将用于在这些模块或张量上执行任何操作。PyTorch 很好地支持了 NVIDIA 数学库(cuBLAS 和 cuDNN )。来自 FP16 管道的数据使用张量核进行 GEMMs 和卷积处理。为了在 cuBLAS 中使用张量核,GEMM 的维数([M,K] x [K,N] - > [M,N])必须是 8 的倍数。
引入 Apex
Apex 的混合精度实用程序旨在提高训练速度,同时保持单精度训练的准确性和稳定性。Apex 可以在 FP16 或 FP32 中执行操作,自动处理主参数转换,并自动缩放损失。
Apex 的创建是为了让研究人员更容易在他们的模型中包含混合精度训练。Amp 是自动混合精度的缩写,是 Apex 的特性之一,Apex 是一个轻量级 PyTorch 扩展。用户只需在他们的网络上增加几条线路,就能从 Amp 的混合精确训练中受益。Apex 于 CVPR 2018 推出,值得注意的是 PyTorch 社区自发布以来一直对 Apex 表示大力支持。
通过对运行模型进行微小的修改,Amp 使您在创建或运行脚本时不必担心混合类型。Amp 的假设可能不太适合以不太常见的方式使用 PyTorch 的模型,但是可以根据需要调整这些假设。
Amp 提供了混合精度训练的所有优势,而不需要显式管理损失缩放或类型转换。 Apex 的 GitHub 网站包含安装过程的说明,其官方 API 文档可以在这里找到。
放大器如何工作
Amp 在逻辑级别使用白名单/黑名单范例。PyTorch 中的张量运算有torch . nn . functional . conv2d等神经网络函数、 torch.log 等简单数学函数和 torch.Tensor. add__ 等张量方法。这个宇宙中有三大类函数:
- t:可以从 FP16 math 的速度提升中受益的函数。典型的应用包括矩阵乘法和卷积。
- 黑名单:对于 16 位精度可能不够的功能,输入应该在 FP32 中。
- 其他一切(剩下的函数):可以在 FP16 上运行的函数,但是 FP32 的开销- > FP16 在 FP16 上执行这些函数是不值得的,因为加速并不显著。
Amp 的任务很简单,至少在理论上是如此。Amp 在调用 PyTorch 函数之前确定它是在白名单中还是在黑名单中,或者两者都不是。如果在白名单中,所有参数都应该转换为 FP16,如果在黑名单中,则转换为 FP32。如果都不是,只要确保所有的参数都是相同的类型。这项政策在现实中并不像看起来那么简单。
将 Amp 与 PyTorch 模型结合使用
要将 Amp 包含到当前 PyTorch 脚本中,请按照下列步骤操作:
- 使用 Apex 库导入 Amp。
- 初始化 Amp,以便它可以对模型、优化器和 PyTorch 内部函数进行所需的更改。
- 请注意反向传播(。backward())发生,以便 Amp 可以同时缩放损失和清除每次迭代状态。
第一步
第一步只有一行代码:
from apex import amp
第二步
必须已经指定用于训练的神经网络模型和优化器来完成这一步,这一步只有一行。
model, optimizer = amp.initialize(model, optimizer, opt_level="O1")
附加设置允许您微调放大器的张量和操作类型调整。函数 amp.initialize() 接受许多参数,我们将只指定其中的三个:
- 车型(torch . nn . module或 torch.nn.Modules 列表)–要修改/铸造的车型。
- 优化器 ( 可选, torch.optim.Optimizer 或 torch.optim.Optimizer 列表)——要修改/施放的优化器。训练必选,推理可选。
- opt _ level(str),可选,default = " O1 ")–纯精度或混合精度优化级别。可接受的值是“O0”、“O1”、“O2”和“O3”,在上面有详细解释。有四个优化级别:
FP32 培训的 O0:这是一个不可行的方案。不需要担心这个,因为你的新型号应该已经是 FP32 了,O0 可能有助于建立一个准确性的基线。
O1 用于混合精度(推荐典型使用):修改所有张量和 Torch 方法,使用白名单-黑名单输入转换方案。在 FP16 中,执行白名单操作,例如像 GEMMs 和卷积这样的张量核友好操作。例如,Softmax 是一个需要 FP32 精度的黑名单 op。除非另有明确说明,否则 O1 也采用动态损耗缩放。
O2 用于“几乎 FP16”混合精度: O2 将模型权重转换为 FP16,修补模型的 forward 方法以将输入数据转换为 FP16,在 FP32 中保留 batchnorms,维护 FP32 主权重,更新优化器的 param_groups 以便 optimizer.step()直接作用于 FP32 权重,并实现动态损失缩放(除非被覆盖)。与 O1 不同,O2 不修补 Torch 函数或张量方法。
用于 FP16 训练的 O3:就真实混合精度而言,O3 可能不如 O1 和 O2 稳定。因此,为您的模型设置一个基线速度可能是有利的,根据它可以评估 O1 和 O2 的效率。
O3 中的额外属性覆盖 keep_batchnorm_fp32=True 可能会帮助您确定“光速”,如果您的模型采用批处理规范化,启用 cudnn batchnorm。
O0 和 O3 不是真正的混合精度,但它们分别有助于设置精度和速度基线。混合精度实现被定义为 O1 和 O2。您可以尝试这两种方法,看看哪种方法最能提高您的特定型号的性能和准确性。
第三步
确保您确定了代码中向后传递发生的位置。将会出现如下几行代码:
loss = criterion(…)
loss.backward()
optimizer.step()
第四步
使用 Amp 上下文管理器,您可以通过简单地包装向后传递来启用损耗缩放:
loss = criterion(…)
with amp.scale_loss(loss, optimizer) as scaled_loss:
scaled_loss.backward()
optimizer.step()
仅此而已。现在,您可以在混合精度训练打开的情况下重新运行脚本。
捕获函数调用
PyTorch 缺少静态模型对象或图形来锁定和插入上面提到的类型转换,因为它非常灵活和动态。通过“猴子打补丁所需函数,Amp 可以动态拦截和强制转换参数。
例如,您可以使用下面的代码来确保方法torch . nn . functional . linear的参数总是被转换为 fp16:
orig_linear = torch.nn.functional.linear
def wrapped_linear(*args):
casted_args = []
for arg in args:
if torch.is_tensor(arg) and torch.is_floating_point(arg):
casted_args.append(torch.cast(arg, torch.float16))
else:
casted_args.append(arg)
return orig_linear(*casted_args)
torch.nn.functional.linear = wrapped_linear
尽管 Amp 可能会添加一些改进以使代码更具弹性,但调用 Amp.init() 会有效地将猴子补丁插入到所有相关的 PyTorch 函数中,以便在运行时正确地转换参数。
最小化强制转换
每个权重在每次迭代中仅转换 FP32 -> FP16 一次,因为 Amp 保留了所有参数转换的内部缓存,并在需要时重用它们。在每次迭代中,向后传递的上下文管理器指示 Amp 何时清除缓存。
使用 PyTorch 自动绘制和渐变缩放
“自动化混合精度训练”是指 torch.cuda.amp.autocast 和torch . cuda . amp . gradscaler的组合。使用 torch.cuda.amp.autocast ,你可以设置特定区域的自动施法。Autocasting 会自动选择 GPU 运算的精度,以在保持准确性的同时优化效率。
torch . cuda . amp . gradscaler实例使得执行渐变缩放步骤变得更加容易。梯度缩放减少了梯度下溢,这有助于具有 float16 梯度的网络实现更好的收敛。
下面的代码演示了如何使用 autocast()在 PyTorch 中获得自动混合精度:
# Creates model and optimizer in default precision
model = Net().cuda()
optimizer = optim.SGD(model.parameters(), ...)
# Creates a GradScaler once at the beginning of training.
scaler = GradScaler()
for epoch in epochs:
for input, target in data:
optimizer.zero_grad()
# Runs the forward pass with autocasting.
with autocast(device_type='cuda', dtype=torch.float16):
output = model(input)
loss = loss_fn(output, target)
# Backward ops run in the same dtype autocast chose for corresponding forward ops.
scaler.scale(loss).backward()
# scaler.step() first unscales the gradients of the optimizer's assigned params.
scaler.step(optimizer)
# Updates the scale for next iteration.
scaler.update()
如果某个特定操作的前向传递有 float16 输入,则该操作的后向传递会产生 float16 梯度,而 float16 可能无法表达小幅度的梯度。
如果这些值被刷新为零(“下溢”),则相关参数的更新将会丢失。
梯度缩放是一种使用比例因子来乘以网络损耗,然后对缩放后的损耗执行反向传递以避免下溢的技术。也有必要通过这个相同的因子来缩放通过网络的反向流动梯度。因此,梯度值具有较大的幅度,这防止它们冲刷到零。
在更新参数之前,每个参数的梯度(。grad 属性)应该是未缩放的,以便缩放因子不会干扰学习速率。由于是模块化的,因此 autocast 和 GradScaler 都可以独立使用。
使用未缩放的渐变
渐变剪辑
我们可以通过使用Scaler.scale(Loss).backward()
方法来缩放所有的梯度。在您更改或检查backward()
和scaler.step(optimizer)
之间的参数的.grad
属性之前,必须对其进行缩放。如果您想要将渐变集的全局范数(请参见torch . nn . utils . clip _ grad _ norm _())或最大幅度(请参见torch . nn . utils . clip _ grad _ value _())限制为小于或等于某个值(某个用户设定的阈值),您可以使用一种称为“渐变裁剪”的技术
没有按比例缩放的裁剪将导致渐变的范数/最大值被缩放,使您请求的阈值(它应该是未按比例缩放的渐变的阈值)无效。优化器给定参数所包含的梯度由scaler . un scale(optimizer)进行缩放。
您可以通过使用scaler . un scale(optimizer 1)来取消之前提供给另一个优化器(如优化器 1)的其他参数的梯度。我们可以通过添加两行代码来说明这个概念:
# Unscales the gradients of optimizer's assigned params in-place
scaler.unscale_(optimizer)
# Since the gradients of optimizer's assigned params are unscaled, clips as usual:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)
使用缩放渐变
梯度累积
梯度累积是基于一个荒谬的基本概念。它不是更新模型参数,而是等待并累积连续批次的梯度,以计算损失和梯度。
在一定数量的批次之后,根据累积梯度更新参数。下面是一段关于如何使用 pytorch 使用渐变累积的代码:
scaler = GradScaler()
for epoch in epochs:
for i, (input, target) in enumerate(data):
with autocast():
output = model(input)
loss = loss_fn(output, target)
# normalize the loss
loss = loss / iters_to_accumulate
# Accumulates scaled gradients.
scaler.scale(loss).backward()
# weights update
if (i + 1) % iters_to_accumulate == 0:
# may unscale_ here if desired
scaler.step(optimizer)
scaler.update()
optimizer.zero_grad()
- 梯度累积在足够批量的batch _ per _ ITER * iters _ to _ accumulate范围内添加梯度。
该秤应针对有效批次进行校准;这需要检查 inf/NaN 等级,如果检测到任何 inf/NaN,则跳过步骤,并将秤更新为有效批次的粒度。
当累加特定有效批次的梯度时,将梯度保持在一个比例和一致的比例因子中也是至关重要的。
如果在累积完成之前梯度未被缩放(或比例因子改变),下一次反向传递将把缩放的梯度添加到未缩放的梯度(或由不同因子缩放的梯度),之后不可能恢复累积的未缩放的梯度,必须应用步骤。
- 在累积了下一步的所有缩放后的梯度后,您可以在步骤前不久使用取消缩放来取消缩放梯度。
为了确保一个完整有效的批处理,只需在每次迭代结束时调用 update 就可以了,在那里您先前调用了步骤 - enumerate(data) 函数允许我们在迭代数据时跟踪批处理索引。
- 将运行损耗除以iters _ to _ accumulate(loss/iters _ to _ accumulate)。通过将损耗标准化,这减少了我们正在处理的每个小批量的贡献。如果你平均每一批的损失,这种划分已经是正确的,不需要进一步的标准化。根据您计算损失的方式,这一步可能没有必要。
- 当我们使用
scaler.scale(loss).backward()
时,PyTorch 累积缩放后的渐变并存储它们,直到我们调用optimizer.zero grad()
。
梯度惩罚
在实现梯度惩罚时, torch.autograd.grad() 用于构建梯度,这些梯度组合起来形成惩罚值,然后添加到损失中。没有缩放或自动转换的 L2 罚分如下例所示。
for epoch in epochs:
for input, target in data:
optimizer.zero_grad()
output = model(input)
loss = loss_fn(output, target)
# Creates gradients
grad_prams = torch.autograd.grad(outputs=loss,
inputs=model.parameters(),
create_graph=True)
# Computes the penalty term and adds it to the loss
grad_norm = 0
for grad in grad_prams:
grad_norm += grad.pow(2).sum()
grad_norm = grad_norm.sqrt()
loss = loss + grad_norm
loss.backward()
# You can clip gradients here
optimizer.step()
提供给torch . autograded . grad()的张量应该被缩放以实现梯度惩罚。在组合梯度以获得罚值之前,有必要对梯度进行非比例缩放。因为惩罚项计算是向前传递的一部分,所以它应该发生在 autocast 上下文中。
对于同一个 L2 点球,情况是这样的:
scaler = GradScaler()
for epoch in epochs:
for input, target in data:
optimizer.zero_grad()
with autocast():
output = model(input)
loss = loss_fn(output, target)
# Perform loss scaling for autograd.grad's backward pass, resulting #scaled_grad_prams
scaled_grad_prams = torch.autograd.grad(outputs=scaler.scale(loss),
inputs=model.parameters(),
create_graph=True)
# Creates grad_prams before computing the penalty(grad_prams must be #unscaled).
# Because no optimizer owns scaled_grad_prams, conventional division #is used instead of scaler.unscale_:
inv_scaled = 1./scaler.get_scale()
grad_prams = [p * inv_scaled for p in scaled_grad_prams]
# The penalty term is computed and added to the loss.
with autocast():
grad_norm = 0
for grad in grad_prams:
grad_norm += grad.pow(2).sum()
grad_norm = grad_norm.sqrt()
loss = loss + grad_norm
# Applies scaling to the backward call.
# Accumulates properly scaled leaf gradients.
scaler.scale(loss).backward()
# You can unscale_ here
# step() and update() proceed as usual.
scaler.step(optimizer)
scaler.update()
使用多个模型、损耗和优化器
如果您的网络中有许多损失,则必须对每个损失调用 Scaler.scale 。
如果你的网络中有很多优化器,你可以在其中任何一个上执行 scaler.unscale ,并且你必须在每个上调用 scaler.step 。然而, scaler.update 应该只使用一次,在这个迭代中使用的所有优化器的步进之后:
scaler = torch.cuda.amp.GradScaler()
for epoch in epochs:
for input, target in data:
optimizer1.zero_grad()
optimizer2.zero_grad()
with autocast():
output1 = model1(input)
output2 = model2(input)
loss1 = loss_fn(2 * output1 + 3 * output2, target)
loss2 = loss_fn(3 * output1 - 5 * output2, target)
#Although retain graph is unrelated to amp, it is present in this #example since both backward() calls share certain regions of graph.
scaler.scale(loss1).backward(retain_graph=True)
scaler.scale(loss2).backward()
# If you wish to view or adjust the gradients of the params they #possess, you may specify which optimizers get explicit unscaling. .
scaler.unscale_(optimizer1)
scaler.step(optimizer1)
scaler.step(optimizer2)
scaler.update()
每个优化器检查其 INF/nan 的梯度,并单独判断是否跳过这一步。一些优化器可能会跳过这一步,而其他优化器可能不会这样做。每几百次迭代只发生一次跳步;因此,它不应该影响收敛。对于多优化器模型,如果在添加梯度缩放后发现收敛不良,可以报告该问题。
使用多个 GPU
深度学习模型最重要的问题之一是,它们变得太大,无法在单个 GPU 上训练。在单个 GPU 上训练一个模型可能需要太长时间,需要多 GPU 训练才能尽快准备好模型。一位知名研究员能够将 ImageNet 的训练周期从两周缩短到 18 分钟,或者在两周内训练出最广泛、最先进的 Transformer-XL,而不是四年。
查看云 GPU 对比,看看哪里可以找到多 GPU 云机器的最佳交易。
数据并行和分布式数据并行
PyTorch 在不影响质量的前提下,提供了易用性和控制性的最佳结合。 nn。DataParallel 和nn . parallel . distributed data parallel是 PyTorch 的两个特性,用于在多个 GPU 之间分配训练。您可以使用这些易于使用的包装器和更改在多个 GPU 上训练网络。
单个流程中的数据并行
在一台机器中,DataParallel 有助于将训练分散到许多 GPU 上。让我们仔细看看 DataParallel 实际上是如何工作的。
当利用数据并行来训练神经网络时,会发生以下阶段:
- 小批量在 GPU 上划分:0。
- 将最小批量分割并分配给所有可用的 GPU。
- 将模型复制到 GPU。
- 向前传递发生在所有 GPU 上。
- 计算与 GPU:0 上的网络输出相关的损耗,以及各种 GPU 的回波损耗。应该在每个 GPU 上计算梯度。
- GPU 上的梯度求和:0 并应用优化器来更新模型。
值得注意的是,这里讨论的问题仅适用于 autocast 。 GradScaler 的行为保持不变。 torch.nn.DataParallel 是否为每个设备创建线程来执行向前传递并不重要。自动发布状态在每一个中传达,并且以下将起作用:
model = Model_m()
p_model = nn.DataParallel(model)
# Sets autocast in the main thread
with autocast():
# There will be autocasting in p_model.
output = p_model(input)
# loss_fn also autocast
loss = loss_fn(output)
分布式数据并行,每个进程一个 GPU
torch . nn . parallel . distributed data parallel的文档建议每个进程使用一个 GPU 以获得最佳性能。在这种情况下,DistributedDataParallel 不会在内部启动线程;因此,autocast 和 GradScaler 的使用不受影响。
分布式数据并行,每个进程多个 GPU
在这里,torch . nn . parallel . distributed data parallel 可能会产生一个副线程来在每个设备上运行正向传递,就像 torch.nn.DataParallel 一样。
结论
在本文中,我们有:
- 引入 Apex。
- 了解放大器的工作原理。
- 看到了如何执行梯度缩放,梯度裁剪,梯度积累和梯度惩罚。
- 看到我们如何能与多个模型,损失和优化。
- 看到了当使用多个 GPU 时,我们如何在单个进程中执行数据并行。
请务必使用页面顶部的链接查看此代码的渐变笔记本版本,并查看来自 Torch 的 Michael Carrili 的工作示例。
参考
https://developer . NVIDIA . com/blog/apex-py torch-easy-mixed-precision-training/
https://pytorch.org/docs/stable/notes/amp_examples.html
https://analyticsindiamag . com/py torch-mixed-precision-training/
https://nvidia.github.io/apex/amp.html
https://kozodoi.me/python/deep 学习/py torch/tutorch/tutorial/2021/02/19/gradient-accumulation . html
https://towa
使用 Whisper AutoCaption 从任何语言生成自动视频字幕
原文:https://blog.paperspace.com/automatic-video-subtitles-with-whisper-autocaption/
我们最近报道了 Whisper 的发布,这是一个来自 Open AI 的全新语音到文本转录模型。这个令人难以置信的模型带有许多有用的功能,包括多语言语音识别、转录和翻译。这扩展到包括在训练数据集中的 97 种语言,取得了不同程度的成功。
语音集成 NLP 的圣杯是能够以高精度实时将音频直接转录成单词。耳语代表了技术进化中的一个证据性步骤,证明了以相对较低的成本快速生成转录物是可能的。
在我们的上一篇文章中,我们展示了如何在渐变笔记本中使用这个强大的工具,或者在一个简单的 Flask 界面中使用渐变部署来部署它。在本教程中,我们将扩展这项工作,将 Whisper 与 Flask 和 MoviePy 集成在一起,为任何视频自动生成英文字幕。
遵循本教程,深入了解如何使用 Whisper 进行开发,如何将 ML/DL 与 Flask 应用程序环境集成,以及如何在 Gradient Deployment 强大的 GPU 上将 ML/DL 应用程序部署到云中。
此代码可以在渐变笔记本和部署中运行。请务必尝试这两种方法!
耳语评论
在各种语音处理任务上训练耳语变压器序列到序列模型。这些包括多语言语音识别、语音翻译、口语识别和语音活动检测。这些任务被表示为一系列标记,然后由解码器进行预测。
在功能上,这允许单个模型代替典型语音处理流水线的多个不同阶段。这种多任务训练形式使用一组特殊的标记作为任务说明符或分类目标,这赋予了游戏更多的多功能性。上图详细解释了这个过程。
为了实现这一目标,他们对对应于 680,000 小时音频的音频-语音对数据进行了训练。其中 117,000 小时的数据涵盖了除英语之外的 96 种语言。该数据集还包括 125,000 小时的各种语言到英语的翻译数据。这个庞大的训练数据集的结果使得 Whisper 在处理特殊的语言特征(如口音、行话或嘈杂的样本)时通常很健壮。
这些型号有 9 种。这些参数的大小范围从 1550 万到 3900 万个参数,微型、基本型、小型和中型型号都有一个版本用于多语言和仅英语任务。纯英语任务在纯英语语言任务上有很大的改进,但随着模型规模的增加,这种差异变得更小。此外,就精度而言,大模型的性能最好,而小模型则对应着效率的大幅提高。
综上所述,这些特性使得 Whisper 可以说是转录、翻译和语音识别/语言检测任务的最佳公开可用模型。这既适用于定量的机器分析,也适用于定性的人工任务。事实上,根据单词错误率来衡量,Whisper 在听觉理解方面的研究甚至超过了一些人类代理。
要更深入地了解 Whisper,请务必查看我们之前对该模型的了解,在该模型中,我们更深入地研究了该模型的架构和功能。接下来让我们看看如何在笔记本和 Flask 应用程序部署格式中使用 Whisper。
耳语自动字幕
Whisper AutoCaption 使用 MoviePy 和 Whisper 为任何视频文件自动生成翻译的英文字幕。该应用程序从输入视频中提取音频,使用 Whisper 生成带时间戳的字幕,然后 MoviePy 将这些字幕叠加到视频中。这些字幕的大小与原始视频输入成比例。你可以在 Github repo 中找到笔记本和 Flask 应用程序部署的源代码。
用《辛普森一家》中著名的蒸火腿场景来看看这个例子。
https://blog.paperspace.com/content/media/2022/10/special.mp4
让我们首先在渐变笔记本中浏览代码,看看这是如何一起工作的。之后,我们将展示如何在应用程序中进行部署。
笔记本演示演练
单击下面的链接在免费的 GPU 上打开这个渐变笔记本。
设置
为了开始,我们首先需要安装这个应用程序所需的包。此外,我们将从 ImageMagick 策略中删除阻止该代码运行的一行代码,创建我们的实验目录,并重新启动内核。我们需要重新启动内核,因为 MoviePy 的一个奇怪行为阻止了它在安装的同一个会话中工作。重启内核解决了这个问题。如果我们打算“运行所有”细胞,我们应该特别警惕这一点,因为它会在这里被捕捉到。
import os
!pip install -r requirements.txt
!pip install git+https://github.com/openai/whisper.git
!pip install yt-dlp
!pip install moviepy --upgrade
!apt-get update
!apt install imagemagick -y
# remove line 88 of vim ~/../etc/ImageMagick-6/policy.xml to run MoviePy
!sed -i '88d' ~/../etc/ImageMagick-6/policy.xml
!mkdir experiments
os._exit(00)
该功能
subtitle_video
函数为我们做了所有的工作,在正确的时间戳自动为提供的视频加上 Whisper 生成的文本字幕。
这适用于 Youtube 链接和直接上传到笔记本工作区的视频。此外,它会自动缩放字幕的大小,以适应视频的大小。
## Imports
from __future__ import unicode_literals
from yt_dlp import YoutubeDL
import yt_dlp
from IPython.display import Video
import whisper
import cv2
import pandas as pd
from moviepy.editor import VideoFileClip
import moviepy.editor as mp
from IPython.display import display, Markdown
from moviepy.editor import *
from moviepy.video.tools.subtitles import SubtitlesClip
import os
import cv2
def subtitle_video(download, url, aud_opts, vid_opts, model_type, name, audio_file, input_file, output, uploaded_vid = None):
## First, this checks if your expermiment name is taken. If not, it will create the directory.
## Otherwise, we will be prompted to retry with a new name
try:
os.mkdir(f'experiments/{name}')
print('Starting AutoCaptioning...')
print(f'Results will be stored in experiments/{name}')
except:
return print('Choose another folder name! This one already has files in it.')
## Use audio and video options for youtube-dl if downloading from youtube
vid_opts['outtmpl'] = f'experiments/{name}/{input_file}'
aud_opts['outtmpl'] = f'experiments/{name}/{audio_file}'
URLS = [url]
if download:
with YoutubeDL(aud_opts) as ydl:
ydl.download(url)
with YoutubeDL(vid_opts) as ydl:
ydl.download(URLS)
else:
# Use local clip if not downloading from youtube
my_clip = mp.VideoFileClip(uploaded_vid)
my_clip.audio.write_audiofile(f'experiments/{name}/{audio_file}')
# Instantiate whisper model using model_type variable
model = whisper.load_model(model_type)
# Get text from speech for subtitles from audio file
result = model.transcribe(f'experiments/{name}/{audio_file}', task = 'translate')
# create Subtitle dataframe, and save it
dict1 = {'start':[], 'end':[], 'text':[]}
for i in result['segments']:
dict1['start'].append(int(i['start']))
dict1['end'].append(int(i['end']))
dict1['text'].append(i['text'])
df = pd.DataFrame.from_dict(dict1)
df.to_csv(f'experiments/{name}/subs.csv')
vidcap = cv2.VideoCapture(f'experiments/{name}/{input_file}')
success,image = vidcap.read()
height = image.shape[0]
width =image.shape[1]
# Instantiate MoviePy subtitle generator with TextClip, subtitles, and SubtitlesClip
generator = lambda txt: TextClip(txt, font='P052-Bold', fontsize=width/50, stroke_width=.7, color='white', stroke_color = 'black', size = (width, height*.25), method='caption')
# generator = lambda txt: TextClip(txt, color='white', fontsize=20, font='Georgia-Regular',stroke_width=3, method='caption', align='south', size=video.size)
subs = tuple(zip(tuple(zip(df['start'].values, df['end'].values)), df['text'].values))
subtitles = SubtitlesClip(subs, generator)
# Ff the file was on youtube, add the captions to the downloaded video
if download:
video = VideoFileClip(f'experiments/{name}/{input_file}')
final = CompositeVideoClip([video, subtitles.set_pos(('center','bottom'))])
final.write_videofile(f'experiments/{name}/{output}', fps=video.fps, remove_temp=True, codec="libx264", audio_codec="aac")
else:
# If the file was a local upload:
video = VideoFileClip(uploaded_vid)
final = CompositeVideoClip([video, subtitles.set_pos(('center','bottom'))])
final.write_videofile(f'experiments/{name}/{output}', fps=video.fps, remove_temp=True, codec="libx264", audio_codec="aac")
首先,我们检查存储输出的目录是否已经创建,以及它是否为空。如果没有,它会创建它供我们使用。然后,它将尝试从提交的 Youtube URL 下载视频(如果已提交)及其相应的音频。我们用下载参数来控制它。如果没有提交 URL,它会检查本地文件作为输入,然后直接从本地文件中提取音频输入到 Whisper。
接下来,我们实例化 Whisper 模型。这可以是我们在本文前面提到的任何模型类型。然后,这被用于将音频输入翻译和转录成带有时间戳的字幕。然后,我们将这些数据放入 Dataframe,其中每一行都包含字幕片段的开始时间、结束时间和文本。
然后,我们使用这些字幕作为使用 TextClip 的 MoviePy 的字幕生成器的输入。然后,MoviePy 遍历输入视频的每一帧,并将这些区域的正确字幕叠加在原始视频上。然后我们保存视频。
subtitle_video(
download=True,
uploaded_vid=None, # path to local file
url = URL,
name = 'run1',
aud_opts = opts_aud,
vid_opts = opts_vid, # Video download settings
model_type = 'medium', # change to 'large' if you want more accurate results,
#change to 'medium.en' or 'large.en' for all english language tasks,
#and change to 'small' or 'base' for faster inference
audio_file = "audio.mp3",
input_file = 'video.mp4',
output = 'output.mp4')
为了调用模型进行生成,我们可以根据需要输入自己的参数来更改路径、模型类型和 Youtube 下载选项。可以确定的一件事是改变模型类型。微型模型可以以大约 32 倍于大型模型的速度生成质量不错的字幕。如果我们的机器上有超过 10 GB 的 RAM,我们也应该考虑升级到大型号。
# Display your video output in markdown
<experiments/run1/output.mp4>
最后,我们可以在 Markdown 中显示现在带字幕的视频,看看字幕如何与原始音频保持一致。在下面的例子中,我们可以看到 Whisper 如何处理《辛普森一家》中不同语言的经典“单轨场景”中使用的各种语言。
https://blog.paperspace.com/content/media/2022/10/modelasfas-1.mp4
部署和使用应用程序的指南
既然我们已经看到了它们在引擎盖下运行的代码,让我们来看看应用程序版本,看看我们如何在渐变部署中将 ML/DL 代码与 Flask 集成,以创建交互式应用程序。
设置
要开始,我们需要一个付费帐户与纸空间梯度。登录到控制台,导航到选择的项目,然后打开“部署”选项卡。单击“创建”进行新部署。
在部署创建页面中,用spec.yaml
中相应保存的值填充网页中的字段
image: paperspace/whisper-autocaption:v1.0
port: 5000
resources:
replicas: 1
instanceType: A6000
我们也可以使用 Gradient CLI 从终端运行这个部署。登录后,在终端中导航到 Github repo 的目录,并运行以下代码。
gradient deployments create --projectId <your id> --name <name for deployment> --spec spec.yaml
让我们来看看引擎盖下运行的是什么。
应用代码
from __future__ import unicode_literals
from cgitb import text
from yt_dlp import YoutubeDL
import yt_dlp
import whisper
import pandas as pd
from moviepy.editor import VideoFileClip
import moviepy.editor as mp
from moviepy.editor import *
from moviepy.video.tools.subtitles import SubtitlesClip
import os
import cv2
from os import listdir
from os.path import isfile, join
from werkzeug.utils import secure_filename
import shutil
import argparse
import torch
import torchvision.transforms as transforms
from PIL import Image
from flask import Flask, jsonify, request, render_template, redirect, url_for, send_from_directory
import sys
我们从导入所有必需的包开始。值得注意的是,我们将使用yt_dlp
下载 Youtube 视频,使用 Whisper 将音频文件翻译并转录为文本,使用 MoviePy 对视频文件进行修改并生成字幕。
UPLOAD_FOLDER = 'inputs/vids'
OUTPUT_FOLDER = 'results/subbed_vids'
ALLOWED_EXTENSIONS = {'mp4', 'mov', 'webm', 'ts', 'avi', 'y4m', 'mkv'}
app = Flask(__name__,static_folder='results')
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['OUTPUT_FOLDER'] = OUTPUT_FOLDER
接下来,我们将设置应用程序运行过程中需要的一些变量。然后,我们配置我们的 flask 应用程序,根据需要使用它们。
# Helpers Flask
@app.route("/", methods = ['GET', 'POST'])
def index():
return redirect(url_for('upload_file'))
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
# Helpers display video
@app.route("/playvideourl/<filename>")
def playvideourl(filename):
return render_template('index.html',
movie_name='video.mp4',
movie_ext='mp4')
@app.route("/media_video/<filename>")
def media_video(filename):
# config_any_dir
return send_from_directory(app.config['OUTPUT_FOLDER'],
filename, as_attachment=True)
在变量赋值和配置之后,我们定义助手函数来指导应用程序在 web 页面之间的流动,如果文件尚未提交,就将用户重定向到主页,并解析输入文件以确保它们与 MoviePy 兼容。接下来,我们有两个助手函数,使我们能够在 Flask 界面中显示最终的视频输出。
@app.route("/upload", methods = ['GET', 'POST'])
def upload_file():
# set variables
source = 'inputs/vids'
audio = 'inputs/audio'
out = 'results/subbed_vids/'
# set options for youtube download
opts_aud = {'format': 'mp3/bestaudio/best','keep-video':True, 'outtmpl': f'inputs/audio/audio.mp3', 'postprocessors': [{'key': 'FFmpegExtractAudio','preferredcodec': 'mp3'}]}
vid_opts = {'format': 'mp4/bestvideo/best','outtmpl': f'{source}/video.mp4'}
# Empty out input and output folders to prevent conflict
for f in os.listdir(source):
os.remove(os.path.join(source, f))
for f in os.listdir(audio):
os.remove(os.path.join(audio, f))
for f in os.listdir(out):
os.remove(os.path.join(out, f))
# Try to download the Youtube URL, if one exists
try:
text1 = request.form.values()
text1 = list(text1)
with YoutubeDL(vid_opts) as ydl:
ydl.download(text1)
with YoutubeDL(opts_aud) as ydl:
ydl.download(text1)
except:
None
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
if 'video.mp4' in os.listdir('inputs/vids/'):
return redirect(url_for('main', name='inputs/vids/video.mp4'))
print('No file part')
return redirect(request.url)
file = request.files['file']
# If the user does not select a file, the browser submits an
# empty file without a filename.
if file.filename == '':
print('No selected file')
return redirect(request.url)
# pass the file onto main
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], 'video.mp4'))
return redirect(url_for('main', name='video.mp4'))
return render_template('index2.html')
upload_file
函数检测本地文件或 Youtube URL 是否被提交,然后执行相应的工作,从 Youtube 下载文件或从本地机器上传文件。每次我们运行这个,它将清除输入和输出文件夹,以防止不匹配的字幕从剩余的文件。
之后,该函数检查上传/下载是否成功。如果一切都在正确的位置,那么它将把我们的文件传递给 main 函数。这将是字幕生成和叠加的地方。
@app.route('/main', methods=['POST','GET'])
def main():
my_clip = mp.VideoFileClip('inputs/vids/video.mp4')
if len(os.listdir('inputs/audio')) == 0:
my_clip.audio.write_audiofile('inputs/audio/audio.mp3', codec="libmp3lame")
# Instantiate whisper model using model_type variable
model = whisper.load_model('medium')
# Get text from speech for subtitles from audio file
result = model.transcribe(f'inputs/audio/audio.mp3', task = 'translate')
# create Subtitle dataframe, and save it
dict1 = {'start':[], 'end':[], 'text':[]}
for i in result['segments']:
dict1['start'].append(int(i['start']))
dict1['end'].append(int(i['end']))
dict1['text'].append(i['text'])
df = pd.DataFrame.from_dict(dict1)
# df.to_csv(f'experiments/{name}/subs.csv')
vidcap = cv2.VideoCapture('inputs/vids/video.mp4')
success,image = vidcap.read()
height = image.shape[0]
width =image.shape[1]
# Instantiate MoviePy subtitle generator with TextClip, subtitles, and SubtitlesClip
generator = lambda txt: TextClip(txt, font='P052-Bold', fontsize=width/20, stroke_width=1, color='white', stroke_color = 'black', size = (width, height*.25), method='caption')
# generator = lambda txt: TextClip(txt, color='white', fontsize=20, font='Georgia-Regular',stroke_width=3, method='caption', align='south', size=video.size)
subs = tuple(zip(tuple(zip(df['start'].values, df['end'].values)), df['text'].values))
subtitles = SubtitlesClip(subs, generator)
# Ff the file was on youtube, add the captions to the downloaded video
video = VideoFileClip('inputs/vids/video.mp4')
final = CompositeVideoClip([video, subtitles.set_pos(('center','bottom'))])
final.write_videofile(f'results/subbed_vids/video.mp4', fps=video.fps, remove_temp=True, codec="libx264", audio_codec="aac")
onlyfiles = [f for f in listdir('results/subbed_vids') if isfile(join('results/subbed_vids', f))]
try:
# onlyfiles.remove('.DS_Store')
return playvideourl('results/subbed_vids/video.mp4')
# return render_template("index.html", variable = onlyfiles[0])
except:
return playvideourl('results/subbed_vids/video.mp4')
# return render_template("index.html", variable = onlyfiles[0])
主要功能是应用程序完成大部分工作的地方。首先,它将视频输入加载到 MoviePy 变量中。如果没有检测到音频文件,例如上传的视频,那么它将使用write_audiofile
创建一个。
接下来,我们实例化我们的 Whisper 模型。由于所有 Gradient GPU 驱动的机器都有 8GB 以上的 VRAM,我们将使用“中型”模型来完成所有任务。然后,我们将使用它来生成输入音频的翻译转录。然后,我们将这些数据放入 Dataframe,其中每一行都包含字幕片段的开始时间、结束时间和文本。
为了确保我们的字幕美观,我们可以使用 CV2 的VideoCap
方法来检测原始输入视频的尺寸。从那里,这些用于实例化 MoviePy 的 TextClip 生成器。这确保我们的字体大小和间距适合输入视频的大小,因为它们将缩放到任何输入视频的大小。
最后,我们使用write_videofile
方法将字幕逐帧添加到原始输入视频的底部中央。Flask 应用程序会将我们重定向到一个页面,上面有 HTML 格式的视频。这个播放器允许右击下载。如果我们想给一个新视频加字幕,那么我们只需点击“返回主页”链接就可以重新开始。
这里有一个例子,使用了《辛普森一家》电影中的同一句台词,用不同的语言重复:
https://blog.paperspace.com/content/media/2022/10/seved.mp4
结束语
在本教程中,我们展示了如何将 Whisper 与 MoviePy 配合使用,以自动生成并覆盖任何视频样本的翻译字幕。这个过程是多用途的,可以处理大量不同的视频文件类型。我们演示了如何设置该流程,使其既可以在笔记本环境中运行,也可以从支持梯度部署的应用程序中运行。一定要试着两种都用!
有关该项目的更多信息,请访问这里的 Github repo。
原纸和 Github 回购也值得一看。
自动缩放现在可用于模型服务和部署
原文:https://blog.paperspace.com/autoscaling-paperspace-gradient/
我们很高兴地宣布发布用于模型服务和图纸空间梯度部署的自动缩放。
自动缩放是一种重要的 MLOps 工具,用于动态调整专用于机器学习工作负载的计算资源或实例。
自动伸缩的好处有很多:
- 自动而不是手动分配实例
- 由于实例会自动供应/取消供应,因此可严格控制成本
- 自动实例故障转移带来的高可用性
要自动缩放梯度上的部署,请执行以下步骤:
步骤 0 -创建部署
步骤 1 -选择要部署的型号
步骤 2 -选择推荐容器或定制容器
步骤 3 -选择一个集群和一台机器
步骤 4 -为部署命名
步骤 5 -启用自动缩放并提供目标自动缩放参数
步骤 6——提供容器的入口点(可选),然后创建您的部署!
此时,您应该已经在部署控制台中构建了一个全新的自动伸缩部署!恭喜你!
有关自动扩展部署和模型服务的更多信息,请阅读文档。
如果您有任何问题,欢迎给我们留言!
对比:Azure ML 笔记本和渐变笔记本
原文:https://blog.paperspace.com/azure-machine-learning-jupyter-notebooks-comparison-alternative/
Azure Machine Learning 是一套机器学习工具,帮助数据科学家和 ML 工程师完成机器学习任务。
Enterprise Jupyter notebooks are referred to as "Azure Machine Learning Studio Notebooks" within Microsoft Azure
今天我们来看一下 Azure 笔记本,这是 Azure 机器学习工具集中的一款产品,它可以让你在 Azure 虚拟机或共享 Azure 集群上运行企业级 Jupyter 笔记本。
在这篇博文中,我们将回顾 Azure 笔记本的优缺点,并将 Azure 笔记本与 Paperspace 的渐变笔记本进行比较。
我们开始吧!
Azure 笔记本简介
微软提供全栈机器学习平台的努力被称为 Azure Machine Learning。像谷歌云的人工智能平台和 AWS 的 SageMaker 一样,Azure 机器学习是公共云提供商之一为企业机器学习团队组装一套工具的努力。
笔记本是 Azure 作为其机器学习产品的一部分提供的原语之一,还有拖放式设计器和自动化机器学习 UI。
此外,Azure 还提供 MLOps 软件来生产 ML 实验和部署。这类似于梯度产品的其余部分,稍后将进行比较。
Azure Machine Learning Studio is divided between notebooks (covered in this article) and products called Automated ML and Designer which will be covered at a later date.
像其他公共云提供商的 ML 工具一样,Azure Machine Learning 面向企业用户,并为一个通常难以团队管理的领域(Jupyter 笔记本)带来了速度和协作的信息。
或者如 Azure 机器学习营销页面所说:
企业级机器学习服务,用于更快地构建和部署模型
TL;博士;医生
对 Azure 笔记本产品的最大批评是,尽管它确实是托管 JupyterLab 笔记本的全功能实现,但快速启动并运行、预先计算将向您收取的费用、添加合作者以及在需要时通过文档或客户支持获得帮助都非常困难。
因此,Azure 机器学习笔记本电脑与其他公共云提供商提供的笔记本电脑处于类似的利基市场——如果你已经在生态系统中,并且需要一系列企业功能(例如基于角色的访问控制),这很好,但如果你使用笔记本电脑来探索假设,并且需要一个地方立即开始,这就不太好了。
总的来说,Azure 笔记本最适合那些想要利用微软 200 美元入门积分的人,或者那些已经扎根于 Azure 计算生态系统并需要关于合规性、SLA 的企业功能的人,或者那些在资源分配和供应方面发号施令的 IT 部门。
同时,Paperspace Gradient 笔记本最适合那些希望运行免费 CPU 和 GPU 实例而无需大量启动时间或麻烦的人,那些希望直接从预建容器启动笔记本的人,以及那些希望在模型研发的探索阶段获得更多自由的人。
特征比较
从 Azure ML 笔记本上运行 Jupyter 和 JupyterLab 都是可能的。还有一个基本的只读 IDE,允许您查看但不能写入笔记本。
Azure ML Notebooks IDE
微软 Azure 笔记本电脑 | 图纸空间渐变笔记本 | |
---|---|---|
成本 | 面向新用户的 200 美元免费 Azure 信用点数 | 免费的 CPU 和 GPU 笔记本电脑 |
资源 | 任何 Azure 实例 | 任何图纸空间实例 |
从零要求开始 | 信用卡,GPU 批准 | 免费的 CPU 和 GPU,无需信用卡或批准 |
启动时间 | 计算需要几分钟来初始化 | 计算需要几秒钟来初始化 |
自动关机 | 否(截至 2020 年 2 月正在开发) | 是 |
Jupyter 笔记本选项 | 是 | 是 |
JupyterLab Option | 是 | 是 |
从容器构建 | 是 | 是 |
成本比较
Azure 新客户目前在创建账户并输入信用卡信息后,可以获得 200 美元的 Azure 机器学习信用。信用可用于创建计算实例。
计算实例的现货价格概述如下:
实例类型 | 图纸空间渐变笔记本 | 实例类型 | 微软 Azure 笔记本电脑 |
---|---|---|---|
免费(M4000) | 每小时 0.00 美元 | M60 | 每小时 1.14 美元 |
免费(P5000) | 每小时 0.00 美元 | M60 x2 | 每小时 2.40 美元 |
P4000* | 每小时 0.51 美元 | M60 x4 | 每小时 4.81 美元 |
P5000* | 每小时 0.78 美元 | K80 | 每小时 0.90 美元 |
P6000* | 每小时 1.10 美元 | K80 x2 | 每小时 1.80 美元 |
V100* | 每小时 2.30 美元 | K80 x 4 | 每小时 3.60 美元 |
P5000 x4* | 每小时 3.12 美元 | V100 | 每小时 3.06 美元 |
P6000 x4* | 每小时 4.40 美元 | V100 x2 | 每小时 6.12 美元 |
- | - | V100 x4 | 每小时 13.46 美元 |
*虽然 Paperspace 提供免费 GPU,无需订阅,但 Paperspace 的付费实例需要一个计划。梯度订购层级如下:
梯度订阅类型 | 费用 | 细节 |
---|---|---|
自由的 | 0 美元/月 | -仅免费实例 |
-笔记本是公共的 | ||
-限制 1 台并发笔记本 | ||
-每次会话最多限制 12 小时 |
- 5GB 永久存储 |
| G1(个人) | 8 美元/月 | -免费和付费实例
-私人笔记本
-限制 5 个并发笔记本
-无限会话长度 - 200GB 永久存储 |
| G2(个人) | 24 美元/月 | -免费和付费实例
-私人笔记本
-限制 10 个并发笔记本
-无限会话长度 - 1TB 永久存储 |
| T1(团队) | 12 美元/用户/月 | -免费和付费实例
-私有笔记本
-限制 10 个并发笔记本
-无限会话长度 - 500GB 持久存储
-私有团队协作
-私有托管集群 |
| T2(团队) | 49 美元/用户/月 | -免费和付费实例
-私有笔记本
-限制 50 个并发笔记本
-无限会话长度 - 1TB 持久存储
-私有团队协作
-私有托管集群 |
入门指南
在 Azure 中设置 Jupyter 笔记本
Azure 笔记本入门需要很多步骤:
- 创建一个 Azure 帐户(链接)
- 创建帐户后,请访问 Azure 门户(链接)
- 导航到机器学习服务(链接)
- 创建一个新的机器学习工作区,并指定订阅层、资源组和您可能需要的任何其他值,如容器注册表
- 一旦部署了新的工作空间,请访问资源并选择 Launch Studio
- 在 studio 视图中,从侧栏作者>笔记本>创建中选择
- 创建文件后,您需要选择“计算”>“新计算”,以便创建运行笔记本的实例。GPU 实例要求您从 Azure 请求额外配额。
- 注意:Azure 目前为新账户提供 200 美元的信用点数
在图纸空间渐变中设置 Jupyter 笔记本
若要开始使用渐变笔记本:
- 创建一个 Paperspace 帐户(链接)
- 导航到渐变>笔记本,并选择创建笔记本
- 输入笔记本的名称、运行时(可选),并选择一个实例
- 如果你选择了一个自由的 CPU 或自由的 GPU 实例,选择开始笔记本,就是这样!(付费实例需要信用卡。)
- 注意:Paperspace 提供对自由层 CPU 和 GPU 支持的笔记本电脑的无限制使用
启动时间
You can create a new notebook with a GPU-backed instance in well under a minute on Gradient.
任何云提供商都需要一些时间来启动 CPU 或 GPU 实例。Azure Machine Learning 大约需要 3 分钟来创建您的第一个资源,而 Paperspace 大约需要 30 秒。
如果你想使用 GPU 支持的笔记本,Azure 要求你提交额外资源类型的资源请求。Gradient 对免费 CPU 或 GPU 没有此要求,但对付费层资源有此要求。
认知开销
由于企业焦点,在 Azure 中获得“地形”自然比在 Gradient 中更困难。如果您需要严格的 RBAC 或合规措施,这很好,但如果您试图立即在笔记本上开始探索,这就不太好了。
自动关机
当您创建渐变记事本时,您总是指定自动关闭时间间隔。这可以防止成本超支,并让您放心,因为您不会为您不经常使用的笔记本电脑付费。
Auto-shutdown interval is mandatory in Gradient when creating a notebook – and saves you money!
JupyterLab
Azure 和 Gradient 都会给你一个完整版的 JupyterLab 来运行你的笔记本。这对这两种产品都是有利的,因为一些云笔记本提供商(如 Google Colab)给你的功能集要有限得多。
You can always initiate a full instance of JupyterLab from any Gradient Notebook by selecting the JupyterLab button in the left sidebar.
添加卡片
Azure 机器学习需要信用卡才能使用该产品。梯度只需要一个付费实例的信用卡。自由渐变实例始终包括至少一个 GPU 选项(通常是 NVIDIA M4000 或 P5000)。
排队单元
在我们的测试中,Azure notebook cells 在评估之前经常要排队很长一段时间。同时,渐变笔记本实例是不可抢占的。
结论
总的来说,Azure Machine Learning 和 Paperspace Gradient 都以原生 IDE 和 JupyterLab 格式提供 CPU 和 GPU 支持的云笔记本。
对于那些已经在 Azure 生态系统中的人来说,将计算和计算积分应用于驱动基于 Azure 的笔记本可能是有意义的。
对于其他人来说,Paperspace Gradient 提供了一个合适的替代方案,具有较低的复杂性和启动开销。
Bagging 和系综方法介绍
偏差-方差权衡是我们在训练机器学习算法时都面临的挑战。Bagging 是一种强大的集成方法,有助于减少方差,并通过扩展,防止过度拟合。集成方法通过使用一组(或“集成”)模型来提高模型精度,当这些模型组合在一起时,其性能优于单独使用的单个模型。
在本文中,我们将了解 bagging 的内部工作原理及其应用,并使用 scikit-learn 库实现 bagging 算法。
特别是,我们将涵盖:
- 集成学习综述
- 为什么要用集成学习?
- 集成学习的优势
- 什么是装袋?
- 拔靴带
- 基本概念
- 套袋的应用
- Bagging 算法
- 优点和缺点
- 解码超参数
- 实现算法
- 总结和结论
让我们开始吧。
集成学习综述
顾名思义,集成方法是指一组模型一起工作来解决一个共同的问题。集成学习利用几种不同方法的优点来抵消每个模型各自的缺点,而不是依赖单个模型来获得最佳解决方案。得到的集合应该比任何单独的模型更不容易出错。
为什么要用集成学习?
将几个弱模型结合起来可以产生我们所说的强学习者。
我们可以使用集成方法以两种方式组合不同的模型:或者使用在所有模型中保持相同的单个基础学习算法(一个同质集成模型),或者使用针对每个模型不同的多个基础学习算法(一个异质集成模型)。
一般来说,集成学习与决策树一起使用,因为它们是实现正则化的可靠方法。通常,随着决策树中级别数量的增加,模型变得容易受到高方差的影响,并且可能会过度拟合(导致测试数据的高误差)。我们使用具有一般规则(相对于高度特定的规则)的集成技术来实现正则化并防止过度拟合。
集成学习的优势
让我们用一个真实的场景来理解集成学习的优势。考虑这样一种情况,您想要预测收到的电子邮件是真实的还是垃圾邮件。你想通过单独看几个属性来预测类别(正版/垃圾):发件人是否在你的联系人列表中,消息内容是否与金钱勒索有关联,使用的语言是否整洁易懂等等。另外,另一方面,您可能希望使用所有这些属性来预测电子邮件的类别。显而易见,后一种选择效果很好,因为我们综合考虑所有属性,而在前一种选择中,我们单独考虑每个属性。集体性确保了结果的稳健性和可靠性,因为它是在彻底研究后生成的。****
这展示了集成学习的优势:
- 确保预测的可靠性。
- 确保模型的稳定性/稳健性。
为了利用这些好处,集成学习通常是大多数用例中最优选的选项。在下一节中,我们来了解一种流行的集成学习技术, Bagging 。
什么是装袋?
Bagging 又称 bootstrap aggregating ,是一个预测模型的多个版本的聚合。每个模型都是单独训练的,并使用平均过程进行组合。bagging 的主要目的是实现比任何模型更小的差异。要理解装袋,我们先来理解术语自举。
拔靴带
引导是从给定数据集生成引导样本的过程。通过随机抽取替换的数据点来配制样本。
**
Bootstrapping (Source: https://towardsdatascience.com/ensemble-methods-bagging-boosting-and-stacking-c9214a10a205)**
重新采样的数据包含原始数据中作为一个整体的不同特征。它绘制了数据点中存在的分布,并且也倾向于保持彼此不同,即数据分布必须保持完整,同时保持自举样本之间的不相似性。这反过来有助于开发稳健的模型。
自举还有助于避免过度拟合的问题。当在模型中使用不同的训练数据集时,该模型变得对生成错误有弹性,因此,对测试数据表现良好,并因此通过保持其在测试数据中的牢固立足点来减少方差。用不同的变量进行测试不会使模型偏向错误的解决方案。
现在,当我们试图建立完全独立的模型时,它需要大量的数据。因此,通过利用所考虑的数据集的近似属性,自举是我们选择的选项。
装袋背后的基本概念
这一切都始于 1994 年,当时 Leo Breiman 提出了这种算法,当时被称为“ Bagging 预测器”。在装袋中,首先创建引导样本。然后,对每个样本应用回归或分类算法。最后,在回归的情况下,对单个学习者预测的所有输出取平均值。对于分类,要么接受投票最多的类别(硬投票,要么将所有类别概率的最高平均值作为输出(软投票)。这就是聚合发挥作用的地方。
数学上,装袋由以下公式表示:
左边的术语是袋装预测,右边的术语是个体学习者。
当学习者不稳定并且倾向于过度适应时,即训练数据的小变化导致预测输出的大变化时,Bagging 工作得特别好。它通过聚合由不同统计属性(如不同的标准差、均值等)组成的个体学习者来有效地减少方差。它适用于高方差模型,如决策树。当与线性回归等低方差模型一起使用时,它不会真正影响学习过程。要选择的基础学习器(树)的数量取决于数据集的特征。使用太多的树不会导致过度拟合,但会消耗大量的计算能力。
打包可以并行进行,以检查过多的计算资源。这是它带来的一个优点,并且经常是增加算法在各种领域中的使用的助推器。
**
Bagging (Source: https://towardsdatascience.com/ensemble-methods-bagging-boosting-and-stacking-c9214a10a205)**
套袋的应用
装袋技术用于各种应用中。一个主要优点是,它通过在训练数据中应用不同的组合和重复(在自举样本中的替换)来生成附加数据,从而减少了预测中的方差。以下是广泛使用 bagging 算法的一些应用:
- 银行业
- 医疗数据预测
- 高维数据
- 土地覆盖制图
- 欺诈检测
- 网络入侵检测系统
- 像神经科学、修复术等医学领域。
Bagging 算法
让我们来看看实现 Bagging 算法的一步一步的过程。
- 引导包括装袋过程流程中的第一步,其中数据被分成随机样本。
- 然后将另一种算法(例如决策树)应用于这些样本中的每一个。培训同时进行。
- 取所有输出的平均值,或者一般来说,计算总输出。
就这么简单!现在来说说装袋的利弊。
优点和缺点
先说优点。Bagging 是一种完全特定于数据的算法。装袋技术减少了模型过度拟合。在高维数据上也表现不错。此外,数据集中缺失的值不会影响算法的性能。
也就是说,它的一个限制是基于子集树的平均预测给出最终预测,而不是输出分类或回归模型的精确值。
解码超参数
Scikit-learn 有两个用于打包的类,一个用于回归(sklearn.ensemble.BaggingRegressor
),另一个用于分类(sklearn.ensemble.BaggingClassifier
)。两者都接受各种参数,这些参数可以根据给定的数据提高模型的速度和精度。其中包括:
base_estimator: 对数据集的所有随机子集使用的算法。默认值是决策树。
****n _ 估计量:总体中基本估计量的数量。默认值为 10。
random_state: 随机状态生成器使用的种子。默认值为无。
n_jobs: 对于fit
和predict
方法,并行运行的作业数量。默认值为无。
在下面的代码中,我们还使用了 K-Folds 交叉验证。它输出训练/测试指数以生成训练和测试数据。 n_splits 参数决定折叠次数(默认值为 3)。
为了估计准确性,我们使用 K_Folds 和 cross_val_score ,而不是将数据分成训练集和测试集。此方法通过交叉验证来评估分数。下面来看一下cross_val_score
中定义的参数:
****估计量:拟合数据的模型。
X: 输入要拟合的数据。
y: 预测精度的目标值。
cv: 决定交叉验证拆分策略。默认值为 3。
实现 Bagging 算法
第一步:导入模块
当然,我们从导入构建我们的打包模型所需的必要包开始。我们使用 Pandas 数据分析库来加载我们的数据集。要使用的数据集被称为皮马印第安人糖尿病数据库,用于根据各种诊断措施预测糖尿病的发病。
接下来,我们从sklearn.ensemble
包中导入BaggingClassifier
,从sklearn.tree
包中导入DecisionTreeClassifier
。
`import pandas
from sklearn import model_selection
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier`
步骤二:加载数据集
我们将从下面提供的 GitHub 链接下载数据集。然后我们将链接存储在一个名为url
的变量中。我们在一个名为names
的列表中命名数据集的所有类。我们使用 Pandas 的read_csv
方法来加载数据集,然后使用参数“names”将类名设置为我们的变量,也称为names
。接下来,我们使用切片操作加载特征和目标变量,并将它们分别分配给变量 X 和 Y 。
`url="https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv"
names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class']
dataframe = pandas.read_csv(url, names=names)
array = dataframe.values
X = array[:,0:8]
Y = array[:,8]`
第三步:加载分类器
在此步骤中,我们为所有随机状态设置种子值,以便生成的随机值将保持不变,直到种子值用尽。我们现在声明 KFold 模型,其中我们将 n_splits 参数设置为 10,并将 random_state 参数设置为种子值。这里,我们将使用分类和回归树算法构建 bagging 算法。为此,我们导入 DecisionTreeClassifier ,将其存储在 cart 变量中,并将树数量变量 num_trees 初始化为 100。
`seed = 7
kfold = model_selection.KFold(n_splits=10, random_state=seed)
cart = DecisionTreeClassifier()
num_trees = 100`
第四步:训练和结果
在最后一步中,我们使用 BaggingClassifier 通过将参数声明为上一步中定义的值来训练模型。
然后我们通过使用参数来检查模型的性能,Bagging 模型, X , Y ,以及 kfold 模型。打印的输出将是模型的准确性。在这个特殊的例子中,大约是 77%。
`model = BaggingClassifier(base_estimator=cart,n_estimators=num_trees, random_state=seed)
results = model_selection.cross_val_score(model, X, Y, cv=kfold)
print(results.mean())
Output:
0.770745044429255`
当单独使用决策树分类器时,注意到的精确度大约是 66%。因此,Bagging 无疑是对决策树算法的改进。
总结和结论
Bagging 通过减少方差来提高模型的精度和准确性,但代价是计算量大。然而,它仍然是建模健壮架构的一盏明灯。一个模型的完美不仅仅是将它的成功归功于所使用的算法,还包括所使用的算法是否正确!
这里有一个简短的总结,提到了我们所了解到的值得注意的要点。我们首先了解什么是集成学习,然后介绍 bagging,接着介绍它的应用、工作过程、优缺点和超参数,最后用 Python 对它进行编码。的确,这是提高机器学习技能的好方法!
参考
https://towards data science . com/ensemble-methods-bagging-boosting-and-stacking-c 9214 a10a 205
https://bradleyboehmke . github . io/HOML/bagging . html # bagging-VIP
https://en.wikipedia.org/wiki/Bootstrap_aggregating
https://sci kit-learn . org/stable/modules/generated/sk learn . ensemble . bagging classifier . html
Windows 10 上的狂欢
敲打窗户?
自从塞特亚·纳德拉接管后,微软对开发者变得友好多了。这种转变从来没有比 Windows 10 增加 Bash shell 更明显的了。这不是一个半生不熟、交叉编译的端口,甚至不是一个虚拟机。它是在 Windows 中运行的完全原生的 Bash。这意味着您不再局限于基本命令,如 PowerShell 中内置的 SSH,或者像 Cygwin 这样的第三方工具的大杂烩。第一次,在 Windows 上开发软件是一流的体验,在许多方面与 Mac 和 Linux 不相上下。
如何在 Windows 10 上安装 Bash
在不到五分钟的时间里,我们将运行 Bash shell。
激活开发者模式
第一步是导航到设置并激活开发者模式。
添加 Linux 的 Windows 子系统
接下来,在开始菜单中输入功能,点击“打开或关闭 Windows 功能”。选中“Windows Subsystem for Linux (Beta)”选项,然后单击“确定”。出现提示时,重新启动计算机以完成安装。
这就是安装过程的全部内容。
试驾终端
重启后,在开始菜单中输入 Bash,你会看到一个选项,这个选项有点笨拙地命名为 Bash on Ubuntu on Windows 。
就是这样!
第一次打开终端时,会提示您为 Linux 环境创建用户名和密码。仅此而已。现在我们已经有了所有伟大的命令,像 vi 、 grep 、 wget 和 ssh 在我们的指尖。
运行一些命令
我将从使用 SSH 连接到我的一台 Linux 机器开始。
这是我的机器学习平台(GPU+ machine),所以我将通过运行nvidia-smi
来检查 GPU。因为我们在一个真正的 Ubuntu shell 中,Python 是默认安装的。
文件系统
最后,Ubuntu 用户与默认的 Windows 用户共享文件系统。它位于这里:
C:\Users\Paperspace\AppData\Local\lxss\rootfs
眼熟吗?
暂时就这样了。看看吧,让我们知道你的想法!
请注意:该功能仍处于测试阶段,因此目前仅在 Windows 10 上可用,而不是 Server 2016。
向三维模型添加背景图像和纹理的基础知识
原文:https://blog.paperspace.com/basics-of-adding-background-images-and-textures-to-your-3-d-models/
Photo by Syed Hussaini / Unsplash
Creating 3-D 模型和为真实世界的应用部署他们是发展他们的一个主要方面。这些创建的模型用于许多目的,例如卡通设计、电影动画、游戏设计、建筑结构等等。3d 建模的这些成就是杰出的,并且随着更新的方法和技术的引入,在下面的领域中有持续的进展。
在 Blender 中使用 Python 脚本进行三维建模系列的第一部分中,我们介绍了大部分重要的基本需求。然而,人们可能会注意到,我们实施的项目在颜色方面缺乏光泽。这个概念正是我们将在这个三维建模系列的第二部分中理解和讨论的。在这篇文章中,我们将添加一些非常需要的背景和纹理到猴子的网格中。快速浏览一下目录,对即将讨论的话题有一个直觉。
目录:
- 介绍
- 用 Blender 给你的三维模型添加背景
- 用 Python
1 在 Blender 中添加背景。导入基本库
2。进口猴网
3。选择摄像机
4。在相机视图中显示背景
5。合成背景
6。将渲染图像保存在相应的路径中 - 用 Blender 给你的三维模型添加纹理
- 用 Python
1 在 Blender 中添加纹理。导入基本库
2。添加猴子网
3。定位摄像机
4。添加纹理
5。将其分配给对象
6。保存渲染的图像 - 背景和纹理的最终组合
- 结论
简介:
将背景图像和纹理添加到三维对象的过程在三维建模中具有重要意义。除了使整个结构和外观更加美观,它还增加了质量和我们的模型的吸引力。在上一篇文章中,我们设计了具有多个摄像机视图的多个模型(猴子网格)。然而,整体结构是非常基本的,不包括任何其他核心细节。因此,生成的整体渲染图像并不吸引人,也不太真实。
在本文中,我们将改变这一因素的应用背景和纹理到我们的模型。我们将首先借助唯一的 Blender 和 Blender 中提供给用户的众多工具来实现后台。一旦我们在 Blender 中成功实现了下面的任务,我们将继续分析如何在 Python 编程的帮助下执行完全相同的操作。
在一些 Python 代码块的帮助下,我们可以理解这些操作是如何工作的,以及我们如何轻松地复制相同的任务。类似于背景生成,我们也将为使用 Blender 和 Python 编程添加的纹理执行类似的实现。一旦我们能够独立地实现添加背景和相应地添加纹理的任务,我们就可以继续将它们结合在一起并适应对象网格。搅拌机文件和代码的背景和猴子网格纹理的成功实施将提供。
使用 Blender 为您的三维模型添加背景:
Photo by Federico Beccari on Unsplash
上面的图片是我们将在整篇文章中使用的背景图片之一。我建议下载上面的图像,并将其保存在一个可访问的目录中,命名为“Universe.jpg ”,以便在整个项目中多次重用该图像。确保您记住了存储此图片的路径,以便您可以使用 Blender 打开它,或者指定 Python 路径来访问图像。重申一下,本文假设开发人员已经掌握了一些使用 Blender 的基本知识,或者已经阅读了这个 3d 建模系列的第一部分。让我们从进行必要的导入和给对象添加背景开始。
第一步是确保删除默认情况下放置在 Blender 屏幕中心位置的初始立方体。您可以用鼠标选择对象,然后单击键盘上的“删除”按钮。执行此操作的基本步骤在第一篇文章中有详细介绍。如果你有任何疑问,请再次访问这个三维建模系列的一部分。一旦猴子网格被放置在搅拌机屏幕上,我们的下一步是相应地调整相机或对象。
无论您选择改变对象的位置还是相机的位置,都没有关系。作为参考,我将改变摄像机的位置。与第一篇文章类似,在 Blender 中点击相机模块,按下键盘上的‘G’将其拖动到合适的位置。一旦你把它拖到需要的位置,你也可以点击键盘上的“R ”,选择相应的轴来旋转相机。您可以选择“X”、“Y”或“Z”轴来旋转和放置摄像机到所需的位置。一旦这一步完成,你应该有一对夫妇的数字,看起来像下图所示。
选中相机,点击数字小键盘 0 按钮查看视图。一旦确认您拥有完美的视图,下一步就是确保相应地调整设置和属性。在 Blender 平台的右侧,你会找到属性窗口,在这里你可以点击相机图标并检查背景图片选项。勾选小方框后,可以选择添加自己选择的背景图片。记住之前保存“Universe.jpg”的路径,然后继续将图像添加到 Blender。另一个确保你完成的步骤是点击属性窗口中的渲染属性图标,向下滚动到电影部分。检查透明图标,你最终可以继续重新点击数字小键盘 0 按钮来查看相应背景中的对象。
如果所有的步骤都准确地执行到这一步,那么用户就可以用相机视图查看背景。但是,当您尝试通过单击键盘上的 F12 按钮或转到菜单选项卡中的渲染选项并单击渲染图像来渲染图像时,您会注意到背景尚未应用于整个图像。尽管背景在相机视图中是可见的,但还没有应用到我们的渲染设置中。虽然你可以截图,并保存在桌面上的图像,我们将需要许多不同类型的应用程序的实际渲染图像。因此,我们的下一步是添加所需的合成,以便我们查看背景图像,包括渲染。下图显示了放置在相机视图中相应宇宙背景中的猴子网格的表示。
现在我们已经在相机视图中有了所需的网格和适当的背景,我们的下一个任务是执行合成动作,以便我们的图像将在渲染设置中包括对象网格和宇宙背景。点击主菜单栏中的合成选项。进入新布局后,选择(选中)屏幕上可用的使用节点选项。选中使用节点选项将显示渲染层和合成框,我们可以利用它们来构建我们的背景。我们将需要添加三个额外的盒子,并为每个盒子设置一些属性,以便我们可以在渲染视图中访问背景。按照下面提到的剩余步骤操作,并确保您的属性设置如示意图所示。
- 首先,我们将借助图像节点添加我们需要通过的输入图像。为了创建这个节点,在合成菜单栏中找到添加图标,并遵循这些步骤-
$ Add->Input->Image $
一旦添加了输入图像节点,我们的下一个任务是添加宇宙的 jpg 图像(或您之前使用的任何其他背景图像)。这一步可以通过选择浏览图像字段直接选择宇宙背景图像或从保存的目录中打开它来完成。这两种方法都可以,但最好遵循前者而不是后者,因为它直接为您提供了各自图像的选项。 - 我们将添加的下一个块是缩放块,这样我们就可以使我们的模型适合渲染尺寸。为了执行下面的动作,这些是必要的步骤-
$ Add->->Distort-【Scale $
一旦我们添加了缩放节点,请确保您将坐标空间从相对大小更改为渲染大小。下面的更改将允许我们根据需要在渲染空间中查看图像。 - 我们将在合成部分添加的第三个也是最后一个节点是 Alpha Over 节点。如果前景图像像素的 Alpha 值大于零,该节点通过将图像层叠在彼此之上来帮助协调整个过程。如果条件满足,那么背景图像将被覆盖。执行这个动作的步骤如下-
$ Add->Color->Alpha Over $
一旦你添加了最后一个 Alpha Over 节点,我们就可以继续连接所有的图层了,如下图所示。将使用 Python 代码进一步讨论详细的连接。现在,您可以进行适当的连接,如下图所示。
最后,通过单击键盘上的 F12 按钮或从菜单栏中选择渲染和渲染图像选项来选择渲染图像选项。带有对象网格的背景的实现成功完成。现在我们知道了如何用 Blender 及其工具添加各自的背景,对于用户来说,理解如何用 Python 编写以下代码也变得很重要。对于像 SMPL 模型这样的编码模型,可以帮助开发人员用 Python 自动完成这个过程,在这种情况下,您需要生成多个副本,并且只有很少或没有变化。因此,理解如何利用 Blender 中可用的 Python 开发环境来产生更有效的结果是至关重要的。让我们从学习如何用 Python 给 Blender 添加背景开始。
使用 Python 在 Blender 中添加背景:
在 Blender 中众多可用工具的帮助下,我们已经成功地完成了 Blender 中背景的添加。但是,需要确保我们可以在 Blender 中自动化我们的创作,我们可以在 Python 的帮助下实现这个任务。我们将导入一些基本的库,导入猴子网格,选择我们的相机,并在相机视图中显示背景。最后,我们将执行合成过程,并将渲染图像保存在适当的目录中。所有代码块都将在 Blender 的文本编辑器环境中编写。
导入基本库:
第一步是导入所有必要的库,正如我在上一篇文章中提到的。这些导入是访问 Blender 中的环境变量所必需的,而 Python 中预建的 math 函数将用于特定的数学运算。
import bpy
import math
导入猴子网格:
我们的下一步是删除搅拌机屏幕中的默认立方体,并用产卵位置的猴子网格替换它。关于与本节相关的代码的更多细节,请访问使用 Blender 的 Python 脚本简介,因为我已经解释了下面的步骤。
# Remove The Default Cude Object
# bpy.ops.object.delete(use_global=False)
for o in bpy.context.scene.objects:
if o.name == "Cube":
bpy.ops.object.delete(use_global=False)
# Import the monkey mesh
bpy.ops.mesh.primitive_monkey_add(location = (0, 0, 0))
选择摄像机:
我们的下一步是选择 Blender 中可用的默认相机。一旦我们选择了相机,我们将把图像路径设置为我们在。jpg 格式。对于下一步,我们可以改变猴子网格的位置,或者改变相机的位置,使得对象的位置以合适的方式与相机对齐。在下面的代码块中,我选择改变相机的位置,这样对象将直接放置在相机的线性位置。对于这个特定的任务,位置和旋转角度是近似选择的。您可以随意更改和试验相机放置的位置和旋转角度。
# Selecting the camera and adding the background
cam = bpy.context.scene.camera
filepath = "D:\\Cool Projects\\Paperspace\\3-D Models\\Universe.jpg"
# Locations
cam .location.x = -0.71
cam .location.y = -12
cam .location.z = 5.5
# Rotations
cam .rotation_euler[0] = math.radians(64)
cam .rotation_euler[1] = math.radians(-0)
cam .rotation_euler[2] = math.radians(-3)
在摄像机视图中显示背景:
我们的下一步是在相机视图中显示背景图像。为了执行此操作,我们将访问存储在文件路径变量中的图像路径。一旦我们加载了背景图像,我们将确保它在相机视图中是可访问的。在我们将背景图片参数设置为真之后,我们将把背景图片添加到我们的相机视图中。最后一步是确保我们根据我们的需求设置了所选相机的渲染属性。唯一需要的是在渲染属性中启用电影部分的透明值为 True。所有提到的活动都可以用下面显示的代码块来执行。
# Displaying the Background in the camera view
img = bpy.data.images.load(filepath)
cam.data.show_background_images = True
bg = cam.data.background_images.new()
bg.image = img
bpy.context.scene.render.film_transparent = True
如果到目前为止您已经正确地遵循了所有步骤,那么在选择相机并单击键盘上的 Numpad 0 按钮时,您将会看到下面的图像。
此时,您可以放大各自图像的视图,并截图保存在桌面上。但是,如果您想要按照更复杂的过程将整个渲染图像保存在您的桌面上,而不必查看相机模式并拍摄截图,那么我们必须遵循与上一节类似的过程。背景合成技术将利用与先前生成的背景任务类似的方法,但是在 Python 代码块的帮助下,相应地执行以下操作。让我们看看新的合成部分,以了解逐步的过程。
合成背景:
为了合成背景,我们首先将上下文区域从默认布局(或文本编辑器)转换到合成部分,并部署所有可用的节点。我们将确保该程序可重新运行,不会导致任何错误。因此,for 循环将在初始运行期间删除所有节点。然后,我们开始确定所有节点的适当位置,并将它们添加到各自的位置。我们还将使用与之前在我们的 Blender 构建中提到的节点完全相同的节点,并将所有参数设置为与实际的构造架构相似。使用 Python 代码,我们可以更容易地理解所有的链接,如连接图所示。我们将第一个图像链接(索引为 0 的链接)连接到第一个缩放节点。缩放节点的第一个链接和渲染层节点的第一个链接分别指向 Alpha Over 节点的第一个和第二个链接。最终的连接是 Alpha Over 节点链接到复合节点的输入链接的输出。完成所有步骤后,我们将恢复文本编辑器的默认布局。
### Compositing
bpy.context.area.ui_type = 'CompositorNodeTree'
#scene = bpy.context.scene
#nodetree = scene.node_tree
bpy.context.scene.use_nodes = True
tree = bpy.context.scene.node_tree
for every_node in tree.nodes:
tree.nodes.remove(every_node)
RenderLayers_node = tree.nodes.new('CompositorNodeRLayers')
RenderLayers_node.location = -300,300
comp_node = tree.nodes.new('CompositorNodeComposite')
comp_node.location = 400,300
AplhaOver_node = tree.nodes.new(type="CompositorNodeAlphaOver")
AplhaOver_node.location = 150,450
Scale_node = tree.nodes.new(type="CompositorNodeScale")
bpy.data.scenes["Scene"].node_tree.nodes["Scale"].space = 'RENDER_SIZE'
Scale_node.location = -150,500
Image_node = tree.nodes.new(type="CompositorNodeImage")
Image_node.image = img
Image_node.location = -550,500
links = tree.links
link1 = links.new(RenderLayers_node.outputs[0], AplhaOver_node.inputs[2])
link2 = links.new(AplhaOver_node.outputs[0], comp_node.inputs[0])
link3 = links.new(Scale_node.outputs[0], AplhaOver_node.inputs[1])
link4 = links.new(Image_node.outputs[0], Scale_node.inputs[0])
bpy.context.area.ui_type = 'TEXT_EDITOR'
将渲染图像保存在各自的路径中:
最后,我们将按照各自的比例渲染所有的图像,并创建一个包含存储图像的目录的图像路径。最后,我们将把图像写入各自的路径,在那里我们将存储渲染的图像。下面的代码块显示了在各自的路径中适当保存渲染图像的整个过程。
### Rendering Procedure
render = bpy.context.scene.render
scale = render.resolution_percentage / 100
FILE_NAME = "Space Monkey.png"
FILE_PATH = "D:\\Cool Projects\\Paperspace\\3-D Models\\Space Monkey.png"
# Save Previous Path
previous_path = bpy.context.scene.render.filepath
# Render Image
bpy.context.scene.render.filepath = FILE_PATH
bpy.ops.render.render(write_still=True)
# Restore Previous Path
bpy.context.scene.render.filepath = previous_path
随着猴子网格的渲染图像与宇宙背景的保存过程的完成,我们可以继续本文的下一部分,在这里我们将了解如何将纹理部署到我们的 3-D 模型。与添加所需的背景相比,这个过程和步骤更简单,使用 Blender 只需更少的步骤,或者使用 Python 只需更少的代码行就可以完成。
使用 Blender 向您的三维模型添加纹理:
Photo by Ashkan Forouzani on Unsplash
在了解了如何使用 Blender 中的工具包和 Blender 中的 Python 脚本来实现任何类型的网格的背景之后,我们现在将看看如何向相应的网格(或对象)添加一些纹理。下载上面的图片,标注为“background.jpg”(或者 texture.jpg)。第一步是删除搅拌机屏幕中的默认立方体,并添加猴子网格。一旦这一步完成,选择猴子对象,并转到搅拌机屏幕右侧的材质属性。如果你感到困惑,请查看下图。
点击新建按钮图标创建一个新的物料。一旦这种新材料被添加,是时候设置表面。我们将坚持默认的原则性 BSDF 着色器选择。对于这个特定的任务,有几个选项可用,但我们选择有原则的 BSDF 着色器,因为它将多个层合并到一个易于使用的节点中。虽然有许多其他选择和参数,你可以设置执行添加纹理的行动,我们将只侧重于添加我们想要的背景,而不改变任何其他属性。
一旦你点击图像纹理选项,你会发现在基色下有三个新的选项。这三个可用选项是浏览要链接的图像、创建新图像或打开图像。因为我们已经下载了想要添加到猴子网格的纹理,我们将选择打开选项。一旦你点击了打开图标按钮,你可以浏览你的文件夹,直到你找到合适的目录,在那里你已经存储了你的纹理图像。一旦你选择了纹理图像,我们几乎所有的步骤都完成了。将实体对象的视口着色更改为渲染状态,您可以在对象上看到导入的纹理。让我们也了解如何在 Python 脚本的帮助下执行这些操作。下面是纹理物体的最终视图。
使用 Python 在 Blender 中添加纹理:
既然我们已经看完了用 Blender 给你想要的网格或者物体添加纹理是多么简单,让我们也来分析一下如何用 Python 编程来执行同样的任务。一些初始步骤将保持类似于添加背景,例如导入库,添加猴子网格,相应地调整相机,并保存渲染图像。添加所需纹理并将其分配给对象的概念是我们将更多关注的主要目标。因此,这两个概念将优先于其他概念。让我们开始编码添加纹理到各自的对象。
导入基本库:
我们的主要进口商品将和以前讨论的一样。有关更多信息,请参考前面的添加背景部分或前面的介绍文章。
import bpy
from bpy import context, data, ops
import math
添加猴子网格:
接下来,我们将确保删除 Blender 中提供的默认立方体,并用猴子网格替换它,猴子网格将放置在 Blender 文件中心的(0,0,0)位置。
# Remove The Default Cude Object
# bpy.ops.object.delete(use_global=False)
for o in bpy.context.scene.objects:
if o.name == "Cube":
bpy.ops.object.delete(use_global=False)
# Import the monkey mesh
bpy.ops.mesh.primitive_monkey_add(location = (0, 0, 0))
定位摄像机:
然后,我们将继续放置相机,使其跟踪猴子网格的中心空间。我们将改变位置和旋转轴,如下面的代码块所示。这些值是近似确定的,用户可以随意试验。此外,另一个需要注意的要点是,您也可以更改猴子网格的位置。或者你可以选择不做任何改变。
# Adjust Camera Locations
cam = bpy.context.scene.camera
# Locations
cam .location.x = -0.71
cam .location.y = -12
cam .location.z = 5.5
# Rotations
cam .rotation_euler[0] = math.radians(64)
cam .rotation_euler[1] = math.radians(-0)
cam .rotation_euler[2] = math.radians(-3)
添加纹理:
随着我们的主要步骤完成,我们终于可以继续添加所需的纹理到选定的网格。请注意,您也可以根据自己的需要添加任意颜色。然而,我们将集中在添加下载的纹理到我们的网格对象。第一步是创建一个新的材质,为下面的材质提供一个名称,并将其存储在一个变量中。对于这个变量,我们将把节点的使用指定为 True。现在,我们可以访问这个变量的众多节点,我们将设置我们的树节点与原则性的 BSDF 着色器选择。对于这个特定的任务,有几个选项可用,但我们选择有原则的 BSDF 着色器,因为它将多个层合并到一个易于使用的节点中。然后,我们将包含位置的路径添加到存储的纹理图像中。最后一步是将输入节点与存储纹理图像的最终输出链接相链接,并确保我们的上下文对象被设置为活动的。
# Adding the texture
mat = bpy.data.materials.new(name="New_Mat")
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
texImage = mat.node_tree.nodes.new('ShaderNodeTexImage')
texImage.image = bpy.data.images.load("D:\\Cool Projects\\Paperspace\\3-D Models\\Background.jpg")
mat.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color'])
ob = context.view_layer.objects.active
将其分配给对象:
选择必须应用以下纹理的对象,并将其设置为包含所有节点和链接的声明变量。最后一步是将我们的维度查看空间从默认的实体视口着色更改为渲染视口着色。我们可以注意到,选定的对象已经获得了我们最初选择的所需纹理图像。这个完整的步骤可以由下面显示的代码块来执行。
# Assign it to object
if ob.data.materials:
ob.data.materials[0] = mat
else:
ob.data.materials.append(mat)
# Change the ViewPort Shading to RENDERED
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'RENDERED'
保存渲染图像:
最后一步是将纹理网格保存到用户选择的所需路径位置。这个步骤类似于前面讨论的图像渲染技术。确保您设置了所需的路径,并相应地按照前面的方法进行操作,或者查看下面显示的代码块,以将渲染的图像保存在所需的位置。一旦这一步完成,我们可以相应地结合背景和纹理。
### Rendering Procedure
render = bpy.context.scene.render
scale = render.resolution_percentage / 100
FILE_NAME = "Textured Monkey.png"
FILE_PATH = "D:\\Cool Projects\\Paperspace\\3-D Models\\Textured Monkey.png"
# Save Previous Path
previous_path = bpy.context.scene.render.filepath
# Render Image
bpy.context.scene.render.filepath = FILE_PATH
bpy.ops.render.render(write_still=True)
# Restore Previous Path
bpy.context.scene.render.filepath = previous_path
背景和纹理的最终组合:
如果到目前为止你已经成功地完成了教程,我们已经实现了添加背景和添加期望的纹理到我们的网格对象的任务。虽然我们已经单独完成了这两项任务,但是当我们使用我们获得的知识将这两个元素结合在一起并将其包装成一个实体时,它会变得更好。上面的图像是当你尝试对物体进行背景和纹理的组合时,你将会得到的最终结果。
为了在网格上执行背景和纹理的组合,您可以遵循本文的 Blender only 实现,或者遵循教程的 Python 编码实现,或者使用从这两个单独方面获得的知识的混合,从而产生可能的最佳结果。Blender 文件和 Python 文件都将被提供。如果用户被困在某个特定的点上,他们可以自由地访问这些实用程序来消除他们的困惑。如果您确实遇到了任何问题,请调试它们,因为这将帮助您了解更多信息。
结论:
Photo by Micheile Henderson / Unsplash
在本文中,我们学习了如何在 Blender 中添加背景和纹理。对于这两个任务,我们知道如何在 Blender 中用它提供给开发者的众多工具来实现它们。除了使用 Blender 提供的开发工具之外,我们还了解了如何在众多不同 Blender 布局之一提供的文本编辑器中借助 Python 脚本来实现和执行添加相应背景和纹理的任务的完整过程。一旦我们单独实现了这些元素,我们最终会将它们结合起来,创建一个改进的、更美观的渲染图像。
在这个 3d 建模系列的第一部分中,我们了解了如何实现多个猴子网格并在多摄像机视图中查看它们。虽然我们在第一节中介绍了大部分基本概念,但我们在本文中的主要焦点是更深入地探讨 Blender 的视觉方面。我们添加了背景和纹理,以提供更好的外观,使其更具美感。在这个三维建模系列的下一部分,我们将分别研究如何执行动画和如何使用 SMPL 模型。在那之前,继续尝试,享受学习吧!
卷积神经网络中的批量归一化
原文:https://blog.paperspace.com/batch-normalization-in-convolutional-neural-networks/
批量标准化是在卷积神经网络的上下文中经常提到的术语。在本文中,我们将探讨它实际需要什么,以及它对卷积神经网络的性能或整体行为的影响(如果有的话)。
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
import torchvision.datasets as Datasets
from torch.utils.data import Dataset, DataLoader
import numpy as np
import matplotlib.pyplot as plt
import cv2
from tqdm.notebook import tqdm
import seaborn as sns
from torchvision.utils import make_grid
if torch.cuda.is_available():
device = torch.device('cuda:0')
print('Running on the GPU')
else:
device = torch.device('cpu')
print('Running on the CPU')
术语标准化
统计学中的规范化是指将数据或一组值限制在 0 和 1 之间的过程。更不方便的是,在某些方面,标准化也指的是将数据分布的平均值设置为零,将其标准偏差设置为 1 的过程。
在实际意义上,将分布的平均值设置为 0,将其标准差设置为 1 的过程称为标准化。然而,由于某些自由,它也被称为标准化或 z 分数标准化。了解这种区别并牢记在心是很重要的。
数据预处理
数据预处理是指在将数据输入机器学习或深度学习算法之前,准备数据所采取的步骤。前面提到的两个过程(规范化和标准化)是数据预处理步骤。
最小-最大归一化
最小-最大归一化是归一化数据最常用的方法之一。顾名思义,它将数据集中的最小值设置为 0,最大值设置为 1,从而将数据点限制在 0 和 1 的范围内,并相应地缩放两者之间的所有值。下面的等式提供了最小-最大归一化过程的数学描述。本质上,它包括从每个数据点中减去数据集中的最小值,然后除以范围(最大值-最小值)。
使用下面的函数,我们可以复制最小-最大归一化的过程。利用这个功能,我们可以对幕后实际发生的事情有一种直觉。
def min_max_normalize(data_points: np.array):
"""
This function normalizes data by constraining
data points between the range of 0 & 1
"""
# convert list to numpy array
if type(data_points) == list:
data_points = np.array(data_points)
else:
pass
# create a list to hold normalized data
normalized = []
# derive minimum and maximum values
minimum = data_points.min()
maximum = data_points.max()
# convert to list for iteration
data_points = list(data_points)
# normalizing data
for value in data_points:
normalize = (value-minimum)/(maximum-minimum)
normalized.append(round(normalize, 2))
return np.array(normalized)
让我们使用 NumPy 创建一个随机值数组,然后尝试使用上面定义的最小-最大归一化函数来归一化它们。
# creating a random set of data points
data = np.random.rand(50)*20
# normalizing data points
normalized = min_max_normalize(data)
从下面的图中可以看出,在标准化之前,值的范围是从 0 到 20,绝大多数数据点的值在 5 到 10 之间。然而,在归一化之后,可以看出,现在值的范围在 0 和 1 之间,绝大多数数据点的值在 0.25 和 0.5 之间。注意:如果/当您运行这段代码时,数据分布将与本文中使用的不同,因为它是随机生成的。
# visualising distribution
figure, axes = plt.subplots(1, 2, sharey=True, dpi=100)
sns.histplot(data, ax=axes[0])
axes[0].set_title('unnormalized')
sns.histplot(normalized, ax=axes[1])
axes[1].set_title('min-max normalized')
z 分数标准化
z 得分标准化也称为标准化,是将数据分布的平均值和标准差分别设置为 0 和 1 的过程。下面的等式是控制 z 得分归一化的数学等式,它涉及在除以分布的标准差之前,从要归一化的值中减去分布的平均值。
下面定义的函数复制了 z 分数归一化过程,使用该函数我们可以更仔细地了解它实际需要什么。
def z_score_normalize(data_points: np.array):
"""
This function normalizes data by computing
their z-scores
"""
# convert list to numpy array
if type(data_points) == list:
data_points = np.array(data_points)
else:
pass
# create a list to hold normalized data
normalized = []
# derive mean and and standard deviation
mean = data_points.mean()
std = data_points.std()
# convert to list for iteration
data_points = list(data_points)
# normalizing data
for value in data_points:
normalize = (value-mean)/std
normalized.append(round(normalize, 2))
return np.array(normalized)
使用上一节中生成的数据分布,让我们尝试使用 z 得分函数来归一化数据点。
# normalizing data points
z_normalized = z_score_normalize(data)
# check the mean value
z_normalized.mean()
>>>> -0.0006
# check the standard deviation
z_normalized.std()
>>>> 1.0000
同样,从可视化结果中,我们可以看到原始分布的值范围为 0 到 20,而 z 分数归一化值现在集中在 0(零的平均值)周围,范围大约为-1.5 到 1.5,这是一个更易于管理的范围。
# visualizing distributions
figure, axes = plt.subplots(1, 2, sharey=True, dpi=100)
sns.histplot(data, ax=axes[0])
axes[0].set_title('unnormalized')
sns.histplot(z_normalized, ax=axes[1])
axes[1].set_title('z-score normalized')
预处理的原因
当考虑机器学习中的数据时,我们将单个数据点视为特征。所有这些特征通常不在相同的比例尺度上。例如,考虑一个有三个卧室和一个 400 平方英尺的客厅的房子。这两个特征在尺度上相距如此之远,以至于如果它们被输入到预定通过梯度下降来优化的机器学习算法中。优化将是相当乏味的,因为具有较大规模的特征将优先于所有其他特征。为了简化优化过程,最好让所有数据点都在相同的范围内。
卷积层中的标准化
图像中的数据点是它的像素。像素值的范围通常从 0 到 255;这就是为什么在将图像输入卷积神经网络之前,以某种方式将它们标准化是一个好主意,以便将所有像素置于一个可管理的范围内。
即使做到了这一点,在训练 convnet 时,权重(其过滤器中的元素)可能会变得太大,从而产生像素分布范围很广的特征地图。这实质上使得在预处理步骤中进行的标准化有些无效。此外,这可能会妨碍优化过程,使其变得缓慢,或者在极端情况下,可能会导致称为不稳定梯度的问题,这可能会从根本上阻止 convnet 进一步优化其权重。
为了防止这个问题,在修道院的每一层都引入了规范化。这种归一化称为 批量归一化 。
批量标准化的过程
批量归一化实质上是将卷积图层中所有要素地图的像素设置为新的平均值和新的标准差。通常,它从标准化所有像素的 z 分数开始,然后在添加另一个任意参数β(偏移)之前,继续将标准化的值乘以任意参数α(缩放)。
这两个参数α和β是可学习的参数,convnet 将使用它们来确保特征图中的像素值在可管理的范围内,从而改善不稳定梯度的问题。
批量标准化正在进行中
为了真正评估卷积层中批量归一化的效果,我们需要对两个卷积进行基准测试,一个不进行批量归一化,另一个进行批量归一化。为此,我们将使用 LeNet-5 架构和 MNIST 数据集。
数据集和卷积神经网络类
在本文中,如前所述,MNIST 数据集将用于基准测试。该数据集由 28×28 像素的手写数字图像组成,其范围从数字 0 到数字 9,并被相应地标记。
Sample images from the MNIST dataset.
可以使用下面的代码块将它加载到 PyTorch 中。训练集由 60,000 幅图像组成,而验证集由 10,000 幅图像组成。由于我们将在 LeNet-5 中使用该数据集,因此需要将图像的大小调整为 32 x 32 像素,如 transforms 参数中所定义的。
# loading training data
training_set = Datasets.MNIST(root='./', download=True,
transform=transforms.Compose([transforms.ToTensor(),
transforms.Resize((32, 32))]))
# loading validation data
validation_set = Datasets.MNIST(root='./', download=True, train=False,
transform=transforms.Compose([transforms.ToTensor(),
transforms.Resize((32, 32))]))
为了训练和利用我们的 convnets,我们将使用下面的类,恰当地命名为“ConvolutionalNeuralNet()”。此类包含的方法将有助于使用经过训练的 convnet 对实例进行训练和分类。train()方法还包含内部辅助函数,如 init_weights()和 accuracy。
class ConvolutionalNeuralNet():
def __init__(self, network):
self.network = network.to(device)
self.optimizer = torch.optim.Adam(self.network.parameters(), lr=1e-3)
def train(self, loss_function, epochs, batch_size,
training_set, validation_set):
# creating log
log_dict = {
'training_loss_per_batch': [],
'validation_loss_per_batch': [],
'training_accuracy_per_epoch': [],
'validation_accuracy_per_epoch': []
}
# defining weight initialization function
def init_weights(module):
if isinstance(module, nn.Conv2d):
torch.nn.init.xavier_uniform_(module.weight)
module.bias.data.fill_(0.01)
elif isinstance(module, nn.Linear):
torch.nn.init.xavier_uniform_(module.weight)
module.bias.data.fill_(0.01)
# defining accuracy function
def accuracy(network, dataloader):
network.eval()
total_correct = 0
total_instances = 0
for images, labels in tqdm(dataloader):
images, labels = images.to(device), labels.to(device)
predictions = torch.argmax(network(images), dim=1)
correct_predictions = sum(predictions==labels).item()
total_correct+=correct_predictions
total_instances+=len(images)
return round(total_correct/total_instances, 3)
# initializing network weights
self.network.apply(init_weights)
# creating dataloaders
train_loader = DataLoader(training_set, batch_size)
val_loader = DataLoader(validation_set, batch_size)
# setting convnet to training mode
self.network.train()
for epoch in range(epochs):
print(f'Epoch {epoch+1}/{epochs}')
train_losses = []
# training
print('training...')
for images, labels in tqdm(train_loader):
# sending data to device
images, labels = images.to(device), labels.to(device)
# resetting gradients
self.optimizer.zero_grad()
# making predictions
predictions = self.network(images)
# computing loss
loss = loss_function(predictions, labels)
log_dict['training_loss_per_batch'].append(loss.item())
train_losses.append(loss.item())
# computing gradients
loss.backward()
# updating weights
self.optimizer.step()
with torch.no_grad():
print('deriving training accuracy...')
# computing training accuracy
train_accuracy = accuracy(self.network, train_loader)
log_dict['training_accuracy_per_epoch'].append(train_accuracy)
# validation
print('validating...')
val_losses = []
# setting convnet to evaluation mode
self.network.eval()
with torch.no_grad():
for images, labels in tqdm(val_loader):
# sending data to device
images, labels = images.to(device), labels.to(device)
# making predictions
predictions = self.network(images)
# computing loss
val_loss = loss_function(predictions, labels)
log_dict['validation_loss_per_batch'].append(val_loss.item())
val_losses.append(val_loss.item())
# computing accuracy
print('deriving validation accuracy...')
val_accuracy = accuracy(self.network, val_loader)
log_dict['validation_accuracy_per_epoch'].append(val_accuracy)
train_losses = np.array(train_losses).mean()
val_losses = np.array(val_losses).mean()
print(f'training_loss: {round(train_losses, 4)} training_accuracy: '+
f'{train_accuracy} validation_loss: {round(val_losses, 4)} '+
f'validation_accuracy: {val_accuracy}\n')
return log_dict
def predict(self, x):
return self.network(x)
Lenet-5
LeNet-5 ( Y. Lecun 等人 )是最早的卷积神经网络之一,专门设计用于识别/分类手写数字的图像。上图描述了它的架构,下面的代码块提供了它在 PyTorch 中的实现。
class LeNet5(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 6, 5)
self.pool1 = nn.AvgPool2d(2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.pool2 = nn.AvgPool2d(2)
self.linear1 = nn.Linear(5*5*16, 120)
self.linear2 = nn.Linear(120, 84)
self.linear3 = nn. Linear(84, 10)
def forward(self, x):
x = x.view(-1, 1, 32, 32)
#----------
# LAYER 1
#----------
output_1 = self.conv1(x)
output_1 = torch.tanh(output_1)
output_1 = self.pool1(output_1)
#----------
# LAYER 2
#----------
output_2 = self.conv2(output_1)
output_2 = torch.tanh(output_2)
output_2 = self.pool2(output_2)
#----------
# FLATTEN
#----------
output_2 = output_2.view(-1, 5*5*16)
#----------
# LAYER 3
#----------
output_3 = self.linear1(output_2)
output_3 = torch.tanh(output_3)
#----------
# LAYER 4
#----------
output_4 = self.linear2(output_3)
output_4 = torch.tanh(output_4)
#-------------
# OUTPUT LAYER
#-------------
output_5 = self.linear3(output_4)
return(F.softmax(output_5, dim=1))
使用上面定义的 LeNet-5 架构,我们将用代码块中看到的参数实例化 ConvolutionalNeuralNet 类的成员model_1
。该模型将作为我们进行基准测试的基线。
# training model 1
model_1 = ConvolutionalNeuralNet(LeNet5())
log_dict_1 = model_1.train(nn.CrossEntropyLoss(), epochs=10, batch_size=64,
training_set=training_set, validation_set=validation_set)
经过 10 个时期的训练,并从我们收到的度量日志中观察准确性,我们可以看到,在训练过程中,训练和验证准确性都有所提高。在我们的实验中,验证准确性在第一个时期后开始时约为 93%,然后在接下来的 9 次迭代中稳步增加,最终在第 10 个时期结束时略高于 98%。
sns.lineplot(y=log_dict_1['training_accuracy_per_epoch'], x=range(len(log_dict_1['training_accuracy_per_epoch'])), label='training')
sns.lineplot(y=log_dict_1['validation_accuracy_per_epoch'], x=range(len(log_dict_1['validation_accuracy_per_epoch'])), label='validation')
plt.xlabel('epoch')
plt.ylabel('accuracy')
批量标准化 LeNet-5
由于本文的主题是围绕卷积层中的批处理规范化,批处理规范仅应用于该架构中的两个卷积层,如上图所示。
class LeNet5_BatchNorm(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 6, 5)
self.batchnorm1 = nn.BatchNorm2d(6)
self.pool1 = nn.AvgPool2d(2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.batchnorm2 = nn.BatchNorm2d(16)
self.pool2 = nn.AvgPool2d(2)
self.linear1 = nn.Linear(5*5*16, 120)
self.linear2 = nn.Linear(120, 84)
self.linear3 = nn. Linear(84, 10)
def forward(self, x):
x = x.view(-1, 1, 32, 32)
#----------
# LAYER 1
#----------
output_1 = self.conv1(x)
output_1 = torch.tanh(output_1)
output_1 = self.batchnorm1(output_1)
output_1 = self.pool1(output_1)
#----------
# LAYER 2
#----------
output_2 = self.conv2(output_1)
output_2 = torch.tanh(output_2)
output_2 = self.batchnorm2(output_2)
output_2 = self.pool2(output_2)
#----------
# FLATTEN
#----------
output_2 = output_2.view(-1, 5*5*16)
#----------
# LAYER 3
#----------
output_3 = self.linear1(output_2)
output_3 = torch.tanh(output_3)
#----------
# LAYER 4
#----------
output_4 = self.linear2(output_3)
output_4 = torch.tanh(output_4)
#-------------
# OUTPUT LAYER
#-------------
output_5 = self.linear3(output_4)
return(F.softmax(output_5, dim=1))
使用下面的代码段,我们可以实例化model_2
,包括批处理规范化,并使用与 model_1 相同的参数开始训练。然后,我们得出准确度分数..
# training model 2
model_2 = ConvolutionalNeuralNet(LeNet5_BatchNorm())
log_dict_2 = model_2.train(nn.CrossEntropyLoss(), epochs=10, batch_size=64,
training_set=training_set, validation_set=validation_set)
从图中可以清楚地看出,在训练过程中,训练和验证的准确性都有所提高,这与没有批量标准化的模型相似。第一个时期后的验证准确度略高于 95%,比同一时间点的model_1 at
高 3 个百分点,然后逐渐增加,最终达到约 98.5%,比model_1
高 0.5%。
sns.lineplot(y=log_dict_2['training_accuracy_per_epoch'], x=range(len(log_dict_2['training_accuracy_per_epoch'])), label='training')
sns.lineplot(y=log_dict_2['validation_accuracy_per_epoch'], x=range(len(log_dict_2['validation_accuracy_per_epoch'])), label='validation')
plt.xlabel('epoch')
plt.ylabel('accuracy')
比较模型
比较这两个模型,很明显,具有批量归一化卷积层的 LeNet-5 模型优于没有批量归一化卷积层的常规模型。因此,可以肯定地说,在这种情况下,批处理规范化有助于提高性能。
比较常规和批量标准化 LeNet-5 模型之间的训练和验证损失还表明,批量标准化模型比常规模型更快地获得更低的损失值。这是一个指向批处理规范化的指针,它提高了模型在正确方向上优化其权重的速率,换句话说,批处理规范化提高了 convnet 学习的速率。
Training & validation losses.
结束语
在这篇文章中,我们探索了在机器学习/深度学习环境中规范化意味着什么。我们还探讨了作为数据预处理步骤的归一化过程,以及如何在预处理之外进行归一化,并通过批量归一化过程进入卷积层。
随后,我们在评估其效果之前检查了批处理规范化本身的过程,方法是在 MNIST 数据集上对 LeNet-5 convnets 的两个变体(一个没有批处理规范,另一个有批处理规范)进行基准测试。从结果中,我们推断批处理规范化有助于提高性能和权重优化速度。也有一些建议认为,它可以防止内部协变量的变化,但在这一点上还没有达成共识。
如何在机器学习中应用贝叶斯决策理论
原文:https://blog.paperspace.com/bayesian-decision-theory-machine-learning/
在之前的一篇文章中,我们详细讨论了贝叶斯决策理论背后的理论。在本文中,我们将看到如何将贝叶斯决策理论应用于不同的分类问题。我们将讨论像做分类问题的损失和风险这样的概念,以及它们是如何一步一步地完成的。为了处理分类中的不确定性,还讨论了“拒绝类”。任何具有弱后验概率的样本被分配到拒绝类。我们将通过分析样品被归类为不合格品的条件来得出结论。
这篇文章的大纲如下:
- 贝叶斯决策理论快速回顾
- 二元类贝叶斯决策理论
- 多类贝叶斯决策理论
- 损失和风险
- 0/1 损失
- 计算风险的示例
- 拒绝类别
- 何时将样品归类为不合格品?
- 结论
让我们开始吧。
贝叶斯决策理论快速回顾
在之前的一篇文章中,我们详细讨论了贝叶斯决策理论,包括先验概率、似然概率和后验概率。后验概率根据以下等式计算:
$ $ P(C _ I | X)= \ frac { P(C _ I)P(X | C _ I)} { P(X)} $ $
分母中的证据\(P(X)\)可以计算如下:
$ $ p(x)=\sum_{i=1}^{k}p(x|c_i)p(c_i)$ $
因此,第一个等式可以写成如下形式:
$ $ p(c_i|x)=\frac{p(c_i)p(x|c_i)}{\sum_{k=1}^{k}p(x|c_k)p(c_k)} $ $
我们还证明了先验概率的总和必须是 1:
后验概率也是如此:
我们通过将贝叶斯决策理论的概念映射到机器学习的上下文来结束第一篇文章。根据这一讨论,已经表明先验、可能性和证据在做出考虑以下方面的良好预测中起着重要作用:
- 先验概率\(P(C_i)\)反映了如何通过来自类\(C_i\)的样本来训练模型。
- 似然概率\(P(X|C_i)\)是指模型在将样本\(X\)分类为类\(C_i\)时的知识。
- 证据项\(P(X)\)显示了模型对样本\(X\)的了解程度。
现在我们讨论一下如何用贝叶斯决策理论做分类问题。
二元类贝叶斯决策理论
对于二元分类问题,两个类别的后验概率计算如下:
$ $ p(c_1|x)=\frac{p(c_1)p(x|c_1)}{\sum_{k=1}^{k}p(x|c_k)p(c_k)}
\
p(c_2|x)=\frac{p(c_2)p(x|c_2)}{\sum_{k=1}^{k}p(x|c_k)p(c_k)} $ $
根据具有最高后验概率的类别对样本进行分类。
对于二分类问题,只有一类的后验概率就足够了。这是因为所有后验概率的总和是 1:
$ $ P(C1 | X)+P(C2 | X)= 1
\
P(C1 | X)= 1-P(C2 | X)
\
P(C2 | X)= 1-P(C1 | X)$ $
给定一个类别的后验概率,那么第二个类别的后验概率也是已知的。例如,如果类\(C_1\)的后验概率为 0.4,则另一类\(C_2\)的后验概率为:
也可以计算所有类别的后验概率,并比较它们以确定输出类别。
$ $ C1 \ space if \ space P(C1 | X)> P(C2 | X)
\
C2 \ space if \ space P(C2 | X)>P(C1 | X)$ $
多类贝叶斯决策理论
对于多类分类问题,计算每个类的后验概率。根据概率最高的类别对样本进行分类。
假设有 3 个类别,下面是每个类别的后验概率:
$ $ p(c_1|x)=\frac{p(c_1)p(x|c_1)}{\sum_{k=1}^{k}p(x|c_k)p(c_k)}
\
p(c_2|x)=\frac{p(c_2)p(x|c_2)}{\sum_{k=1}^{k}p(x|c_k)p(c_k)}
\
p(c_3|x)=\frac{p(c_3)p(x|c_3)}{\sum_{k=1}^{k}p(x|c_k)p(c_k)} $ $
基于这 3 个类别的后验概率,输入样本\(X\)被分配给具有最高后验概率的类别。
$ $ max \ space[P(C1 | X),P(C2 | X),P(C3 | X)]$ $
如果 3 个类别的概率分别为 0.2、0.3 和 0.5,则输入样本被分配给第三个类别\(C_3\)。
$ $ Choose \ space C _ I \ space \ if \ space P(C _ I | X)= max _ K \ space P(C _ K | X),\space k=1:K $$
损失和风险
对于属于类别\(C_k\)的给定样本,贝叶斯决策理论可以采取动作,该动作将样本分类为属于具有最高后验概率的类别\(\alpha_i\)的样本。
当\(i=k\)时,动作(即分类)是正确的。换句话说:
预测的类\(\alpha_i\)可能与正确的类\(C_k\)不同。这种错误的行动(即分类)的结果,是有损失的。损失表示为λ$ \λ$:
\(松动\)
其中\(i\)是预测类的索引,而\(k\)是正确类的索引。
由于采取错误行动(即分类)的可能性,存在采取错误行动的风险(即成本)。风险\(R\)的计算方法如下:
$ r($ a _ { I } \ x)= = = sum _ { k } \ lambda _ { I } p(c _ k \ x)中的一个值
目标是选择降低风险的类别。这是制定如下:
$ $ Choose \ space \ alpha _ I \ space \ so \ space that \ space R(\ alpha _ I | X)= min _ K \ space R(\ alpha _ K | X),\space k=1:K $$
请注意,损失是显示单个预测是正确还是不正确的指标。根据损失,计算风险(即成本),这是一个显示整体模型预测正确与否的指标。风险越低,模型就越擅长预测。
0/1 损失
有不同的方法来计算损失。一种方法是使用“0/1 损失”。当预测正确时,则损失为 0 美元(即没有损失)。当有一个错误的预测时,那么损失总是 1 美元。
0/1 损失建模如下,其中\(i\)是错误类别的索引,\(k\)是正确类别的索引。
请记住,风险的计算方法如下:
$ r($ a _ { I } \ x)= = = sum _ { k } \ lambda _ { I } p(c _ k \ x)中的一个值
基于 0/1 损失,损失仅为 0 美元或 1 美元。因此,损失项\(\lambda\)可以从风险等式中删除。当\(k=i\)时,通过从总和中排除后验概率,风险计算如下:
每当i not equal k
,那么就有一个错误的分类(动作),损失为 1。因此,错误类别的后验概率会增加风险。
如果我们不删除\(\lambda\),那么它将当前类\(i\)的后验概率乘以 0。因此我们将其排除在求和之外。去除\(\lambda\)后,重要的是手动从求和中排除当前类\(i\)的后验概率。这就是\(k=i\)时求和不包括后验概率的原因。
前面的等式计算除了类\(i\)的后验概率之外的所有后验概率之和。因为所有后验概率的总和是 1,所以下面的等式成立:
根据这个等式,当选择具有最高后验概率的类别时风险最小。因此,我们应该致力于最大化后验概率。
作为总结,假设使用 0/1 损失,以下是计算采取行动的风险的不同方法:
$ $ r(\ alpha _ I | x)= \sum_{k=1}^{k}\lambda_{ik} p(c _ k | x)
\ \ r(\ alpha _ I | x)= \sum_{k=1,k\not=i}^{k} p(c _ k | x)
\ \ r(\ alpha _ I | x)= 1-p(c _ I | x)$ $
以下是关于 0/1 损失的一些说明:
- 基于 0/1 损失,所有不正确的动作(即分类)被给予\(1\)的相等损失,并且所有正确的分类没有损失(例如\(0\)损失)。例如,如果有 4 个错误分类,则总损失为 4 美元(1)=4 美元。
- 给所有不正确的预测分配相等的损失是不完美的。损失应该与错误类别的后验概率成比例地增加。例如,后验概率为 0.8 的错误预测情况下的损失应该高于后验概率仅为 0.2 时的损失。
- 0/1 损失将 0 损失分配给正确的预测。然而,当后验概率较低时,可能会给正确的预测增加损失。例如,假设正确的类别是以 0.3 的后验概率预测的,这是很小的。损失函数可以添加损失来惩罚低置信度的预测,即使它们是正确的。
计算风险的示例
假设只有两类,$ C1 \(和\) C2 $,它们的后验概率是:
$ $ P(C1 | X)= 0.1
\
P(C2 | X)= 0.9 $ $
我们如何选择预测类?请记住,目标是选择风险最小的类别。其表述如下:
$ $ Choose \ space \ alpha _ I \ space \ so \ space that \ space R(\ alpha _ I | X)= min _ K \ space R(\ alpha _ K | X),\space k=1:K $$
因此,为了决定选择哪一类,我们可以很容易地计算预测$ C1 \(和\) C2 $时的风险。请记住,风险的计算方法如下:
对于\(C_1\)而言,其后验概率为0.1
,因此将样本\(X\)归类为类\(C_1\)的风险为0.9
。
$ $ R(\ alpha _ 1 | X)= 1-P(C _ 1 | X)= 1-0.1 = 0.9 $ $
对于\(C_2\)来说,它的后验概率是0.9
,因此将样本\(X\)归类为类\(C_2\)的风险是0.1
。
$ $ R(\ alpha _ 2 | X)= 1-P(C2 | X)= 1-0.9 = 0.1 $ $
将输入样本分类为\(C_1\)和\(C_2\)的风险分别为 0.9 和 0.1。因为目标是选择最小化风险的类,所以选择的类是\(C_2\)。换句话说,输入样本\(X\)被归类为类\(C_2\)。
拒绝类别
以前有错误动作(即分类)时,其后验概率并入风险。但是,还是采取了错误的行动。有时候,我们根本不需要采取错误的行动。
例如,假设一名员工根据邮政编码发送信件。有时,员工可能会采取错误的行动,将信封送到错误的家庭。我们需要避免这种行为。这可以通过将新的拒绝类添加到可用类集合中来实现。
现在有\(K+1\)个类,而不是只有\(K\)个类。\(K+1\)类是拒绝类,从\(k=1\)到\(k=K\)的类是正常类。拒绝类的损失等于\(\lambda\),介于 0 和 1 之间(不含 1)。
拒绝操作的索引为\(k+1\)如下所示:
当拒绝类存在时,以下是正确类为\(k\)时选择类\(i\)的 0/1 损失函数:
将\(X\)归类为剔除类别的风险是:
$ $ r(\ alpha _ { k+1 } | x)= \sum_{k=1}^{k}\lambda p(c _ k | x)$ $
求和运算符可以分解如下:
$ $ R(\ alpha _ { k+1 } | X)= \ lambda P(C _ 1 | X)+\ lambda P(C _ 2 | X)+...+ \lambda P(C_K|X) $$
通过去掉\(\lambda\)作为公因数,结果是:
$ $ R(\ alpha _ { K+1 } | X)= \ lambda[P(C _ 1 | X)+P(C _ 2 | X)+...+ P(C_K|X)] $$
因为所有(从 1 到 K)后验概率之和是 1,所以下面成立:
$ $ P(C1 | X)+P(C2 | X)+...+ P(C_K|X)=1 $$
因此,根据拒绝类别对\(X\)进行分类的风险是\(\lambda\):
请记住,选择\(i\)类而不是剔除类的风险是:
$ $ r(\ alpha _ { I } | x)= \sum_{k=1}^{k}\lambda_{ik} p(c _ k | x)= \ sum _ { k = 1,\ for all k \ not = i}^{k} p(c _ k | x)= 1-p(c _ I | x)$ $
何时将样品归类为不合格品?
这是一个重要的问题。什么时候我们应该将样品归类为\(i\)类,或剔除类?
当以下两个条件成立时,选择类别\(C_i\):
- 将样本分类为类别\(C_i\)的后验概率高于所有其他类别的后验概率。
- 类别\(C_i\)的后验概率高于阈值\(1-\lambda\)。
这两种情况建模如下:
$ $ Choose \ space C _ I \ space \ if \ space[P(C _ I | X)> P(C _ { k,k\not=i}|X)] \space 和\ space[P(C _ I | X)> P(C _ { Reject } | X)]
\
否则,\ space Choose \ space Reject \ space \ class $ $
由于以下两个原因,条件\(0 < \lambda < 1\)必须得到满足:
- 如果\(\lambda=0\),则阈值为\(1-\lambda = 1-0 = 1\),这意味着只有当后验概率高于 1 时,样本才被分类为类\(C_i\)类。结果,所有样本被分类为拒绝类,因为后验概率不能高于 1。
- 如果\(\lambda=1\),则阈值为\(1-\lambda = 1-1 = 0\),这意味着只有当后验概率高于 0 时,样本才被分类为类\(C_i\)类。结果,大多数样本将被分类为属于类别\(C_i\)因为大多数后验概率高于 0。只有后验概率为 0 的样本被分配到剔除类别。
结论
在这两篇文章中,以一步一步的方式详细介绍了贝叶斯决策理论。第一篇文章介绍了如何使用先验概率和似然概率来形成理论。在本文中,我们看到了如何将贝叶斯决策理论应用于二分类和多分类问题。我们描述了如何计算二元和多元损失,如何根据后验概率得出预测类别,以及如何计算风险/成本。最后,引入拒绝类来处理不确定性。
贝叶斯决策理论解释
贝叶斯决策理论是模式分类的统计方法。它利用概率进行分类,并测量将输入分配给给定类别的风险(即成本)。
在这篇文章中,我们首先来看看先验概率,以及它为什么不是一种有效的预测方式。贝叶斯决策理论通过使用先验概率、似然概率和证据来计算后验概率,从而做出更好的预测。我们将详细讨论所有这些概念。最后,我们将把贝叶斯决策理论中的这些概念映射到它们在机器学习中的上下文中。
这篇文章的大纲如下:
- 先验概率
- 似然概率
- 先验和似然概率
- 贝叶斯决策理论
- 所有先验概率的总和必须为 1
- 所有后验概率之和必须为 1
- 证据
- 机器学习和贝叶斯决策理论
- 结论
完成这篇文章后,请继续关注第 2 部分,我们将把贝叶斯决策理论应用于二分类和多分类问题。为了评估分类器的性能,讨论了进行预测的损失和风险。如果分类器做出弱预测,则使用名为“拒绝”的新类别来接受具有高不确定性的样本。第 2 部分讨论何时以及为何将样品分配到剔除类别。
让我们开始吧。
先验概率
讨论概率,要从如何计算一个动作发生的概率开始。概率是根据结果(即事件)的过去发生率计算的。这就是所谓的先验概率(“先验”的意思是“之前”)。换句话说,先验概率指的是过去的概率。
假设有人问未来两队比赛谁会赢。让\(A\)和\(B\)分别表示第一或第二队获胜。
在过去的 10 场杯赛中,\(A\)出现了 4 次,\(B\)出现了其余 6 次。那么,\(A\)在下一场比赛中出现的概率是多少?根据经验(即过去发生的事件),第一队(\(A\))在下一场比赛中获胜的先验概率为:
但是过去的事件不一定总是成立的,因为情况或背景可能会改变。例如,A$队可能只赢了 4 场比赛,因为有一些受伤的球员。当下一场比赛到来时,所有这些受伤的球员都将康复。根据目前的情况,第一队赢得下一场比赛的概率可能高于仅根据过去事件计算的概率。
先验概率测量下一个动作的概率,而不考虑当前观察(即当前情况)。这就像仅仅根据过去的医生出诊来预测一个病人患有某种疾病。
换句话说,因为先验概率仅仅是基于过去的事件(没有现在的信息)计算的,这可能降低预测值的质量。过去对两个结果\(A\)和\(B\)的预测可能在满足某些条件时发生,但是在当前时刻,这些条件可能不成立。
这个问题是用似然法解决的。
似然概率
可能性有助于回答这个问题:给定一些条件,某个结果发生的概率是多少?它表示如下:
其中\(X\)表示条件,\(C_i\)表示结果。因为可能有多种结果,所以变量\(C\)被赋予下标\(i\)。
可能性如下所示:
在一组条件\(X\)下,结果为\(C_i\)的概率是多少?
根据我们预测获胜队的例子,结果\(A\)发生的概率不仅取决于过去的事件,还取决于当前的条件。可能性将结果的发生与做出预测时的当前条件联系起来。
假设情况发生变化,第一队没有受伤球员,而第二队有许多受伤球员。因此,\(A\)比\(B\)更有可能出现。不考虑当前情况,仅使用先验信息,结果将是\(B\),这在当前情况下是不准确的。
对于诊断患者的例子,这可能是可以理解的更好的预测,因为诊断将考虑他们当前的症状而不是他们先前的状况。
仅使用可能性的缺点是它忽略了经验(先验概率),而经验在许多情况下是有用的。因此,更好的预测方法是将两者结合起来。
先验概率和似然概率
仅使用先验概率,根据过去的经验进行预测。仅使用可能性,预测仅取决于当前情况。当单独使用这两个概率中的任何一个时,结果都不够准确。在预测下一个结果时,最好同时使用经验和当前情况。
新的概率计算如下:
对于诊断患者的例子,结果将基于他们的病史以及他们当前的症状来选择。
同时使用先验概率和似然概率是理解贝叶斯决策理论的重要一步。
贝叶斯决策理论
贝叶斯决策理论(即贝叶斯决策规则)不仅基于先前的观察,而且还通过考虑当前的情况来预测结果。规则描述了根据观察结果采取的最合理的行动。
贝叶斯(Bayes)决策理论的公式如下所示:
$ $ P(C _ I | X)= \ frac { P(C _ I)P(X | C _ I)} { P(X)} $ $
该理论的要素是:
- \(P(Ci)\):先验概率。这说明了类\(C_i\)独立于任何条件出现的次数(即,不考虑输入\(X\))。
- \(P(X|Ci)\):可能性。在某些条件\(X\)下,这是结果\(C_i\)出现的次数。
- \(P(X)\):证据。条件\(X\)出现的次数。
- \(P(Ci|X)\):后验。给定某些条件\(X\)时,结果\(Ci\)发生的概率。
贝叶斯决策理论给出了平衡的预测,因为它考虑了以下因素:
- \(P(X)\):条件\(X\)出现了多少次?
- \(P(C_i)\):结果\(C_i\)出现了多少次?
- \(P(X|C_i)\):条件\(X\)和结果\(C_i\)一起出现了多少次?
如果不使用前面的任何因素,预测就会受阻。让我们解释排除任何这些因素的影响,并提到一个使用每个因素可能有所帮助的案例。
- \(P(C_i)\)假设不使用先验概率\(P(C_i)\)的情况;那么我们就无法知道结果\(C_i\)是否经常出现。如果先验概率很高,那么结果\(C_i\)频繁出现,并且它是它可能再次出现的指示。
- \(P(X|C_i)\)如果不使用似然概率\(P(X|C_i)\)则没有信息将当前输入\(X\)与结果\(C_i\)相关联。例如,结果\(C_i\)可能经常出现,但是对于当前输入\(X\)很少出现。
- \(P(X)\)如果排除证据概率\(P(X)\)则没有反映\(X\)出现频率的信息。假设结果\(C_i\)和输入\(X\)都频繁出现,那么当输入为\(X\)时,结果很可能是\(C_i\)。
当有关于\(C_i\)单独出现、\(X\)单独出现以及\(C_i\)和\(X\)一起出现的频率的信息时,可以做出更好的预测。
关于理论/规则,有一些事情需要注意:
- 所有先验概率的总和必须为 1。
- 所有后验概率之和必须为 1。
- 证据是所有结果的先验概率和似然概率的乘积之和。
接下来的三个部分将逐一讨论这些要点。
所有先验概率之和必须为 1
假设有两种可能的结果,那么必须满足以下条件:
$ $ P(C1)+P(C2)= 1 $ $
原因是对于一个给定的输入,其输出必须是这两者之一。没有未发现的结果。
如果有\(K\)个结果,则必须满足以下条件:
$ $ P(C1)+P(C2)+P(C3)+...+P(C_K)=1 $$
下面是使用求和运算符的写法,其中\(i\)是结果指数,\(K\)是结果总数:
请注意,以下条件必须适用于所有先验概率:
所有后验概率之和必须为 1
类似于先验概率,根据下面的等式,所有后验概率的总和必须是 1。
$ $ P(C1 | X)+P(C2 | X)= 1 $ $
如果结果的总数是\(K\),下面是使用求和运算符得出的总和:
$ $ P(C1 | X)+P(C2 | X)+P(C3 | X)+...+P(C_K|X)=1 $$
以下是如何使用求和运算符对\(K\)结果的所有后验概率求和:
证据
以下是当只出现两种结果时,如何计算证据:
$ $ P(X)= P(X | C _ 1)P(C _ 1)+P(X | C _ 2)P(C _ 2)$
对于\(K\) outcomes,以下是证据的计算方式:
$ $ P(X)= P(X | C _ 1)P(C _ 1)+P(X | C _ 2)P(C _ 2)+P(X | C _ 2)P(C _ 2)+...+P(X|C_K)P(C_K) $$
下面是使用求和运算符的写法:
$ $ p(x)=\sum_{i=1}^{k}p(x|c_i)p(c_i)$ $
根据最新的证据方程,贝叶斯决策理论(即后验)可以写成如下:
$ $ p(c_i|x)=\frac{p(c_i)p(x|c_i)}{\sum_{k=1}^{k}p(x|c_k)p(c_k)} $ $
机器学习&贝叶斯决策理论
本节将机器学习中的概念与贝叶斯决策理论相匹配。
首先,结局这个词应该换成类。与其说结果是\(C_i\),不如说类是\(C_i\),这样对机器学习更友好。
以下是将贝叶斯决策理论中的因素与机器学习概念相关联的列表:
- \(X\)是特征向量。
- \(P(X)\)是特征向量\(X\)和用于训练模型的特征向量之间的相似度。
- \(C_i\)是类别标签。
- \(P(C_i)\)是模型将输入特征向量分类为类\(C_i\)的次数。该决定与特征向量\(X\)无关。
- \(P(X|C_i)\)是之前的机器学习模型将类似于\(X\)的特征向量分类为类\(C_i\)的经验。这将类\(C_i\)与当前输入\(X\)联系起来。
当下列条件适用时,特征向量\(X\)可能被分配给类\(C_i\):
- 在接近当前输入向量\(X\)的特征向量上训练该模型。这增加了\(P(X)\)的价值。
- 该模型在属于类\(C_i\)的一些样本(即特征向量)上被训练。这增加了\(P(C_i)\)。
- 该模型被训练以将接近\(X\)的样本分类为属于类\(C_i\)的。这增加了\(P(X|Ci)\)的价值。
当训练分类模型时,它知道类别\(C_i\)出现的频率,并且该信息被表示为先验概率\(P(C_i)\)。如果没有先验概率\(P(C_i)\)的话,分类模型会丢失一些学习到的知识。
假设先验概率\(P(C_i)\)是唯一要使用的概率,分类模型基于过去的观察对输入\(X\)进行分类,甚至没有看到新的输入\(X\)。换句话说,甚至不需要将样本(特征向量)提供给模型,模型就可以做出决定并将其分配给一个类别。
训练数据帮助分类模型将每个输入\(X\)映射到其类别标签\(C_i\)上。这种学习到的信息被表示为似然概率\(P(X|C_i)\)。在没有似然概率\(P(X|C_i)\)的情况下,分类模型无法知道输入样本\(X\)是否与类\(C_i\)相关。
结论
本文介绍了机器学习背景下的贝叶斯决策理论。它描述了理论的所有要素,从先验概率\(P(C)\)开始,似然概率\(P(X|C)\)开始,证据\(P(X)\)开始,最后是后验概率\(p(C|X)\)结束。
然后,我们讨论了这些概念如何构建贝叶斯决策理论,以及它们如何在机器学习的环境中工作。
在下一篇文章中,我们将讨论如何将贝叶斯决策理论应用于二元和多类分类问题,看看损失和风险是如何计算的,最后,涵盖“拒绝”类的概念。
PyTorch 中玻尔兹曼机器的初学者指南
原文:https://blog.paperspace.com/beginners-guide-to-boltzmann-machines-pytorch/
随着研究的进展,研究人员可以带来更多关于人类大脑架构的证据,连接主义者机器学习模型成为人们关注的焦点。连接主义模型,也称为并行分布式处理(PDP)模型,由高度互连的处理单元组成。这些模型通常用于复杂的模式,如人类行为和感知。类似地,像建立视觉、感知或任何约束满足问题的模型这样的任务需要强大的计算能力。这种模型所需的硬件支持以前是不存在的——也就是说,直到 VLSI 技术和 GPU 的出现。这是当玻尔兹曼机器被开发出来的时候。在本文中,我们将讨论玻尔兹曼机器的工作原理,并在 PyTorch 中实现它们。
将涵盖以下主题:
- 波尔兹曼机器背后的故事
- 玻尔兹曼机器的工作
- 波尔兹曼机器背后的直觉
- CSP 的玻尔兹曼机器
- 超参数
- 不同类型的玻尔兹曼机器
- PyTorch 中的实现
波尔兹曼机器背后的故事
有时被称为“深度学习之父”的杰弗里·辛顿(Geoffrey Hinton)与约翰·霍普金斯大学教授特里·塞伊诺夫斯基(Terry Sejnowski)一起制定了玻尔兹曼机器。人们常说,玻尔兹曼机器位于深度学习和物理学的交界处。这些模型基于并行处理方法,该方法广泛用于降维、分类、回归、协作过滤、特征学习和主题建模。要注意的是,在玻尔兹曼机器的构建神经网络的词汇中,并行性归因于隐藏层的权重的并行更新。
玻尔兹曼机器的工作
玻尔兹曼机器的工作主要受玻尔兹曼分布的启发,玻尔兹曼分布认为系统的当前状态取决于系统的能量和当前运行的温度。因此,为了将它们实现为神经网络,我们使用能量模型。能量项相当于实际答案的偏差。能量越高,偏差越大。因此,训练模型直到它达到低能点是很重要的。很明显,这种理论模型会遇到局部极小值问题,导致结果不太准确。
Intuition of Boltzmann Machines
通过允许模型周期性地跳跃到更高的能量状态,然后收敛到最小值,最终导致全局最小值,解决了这个问题。我们将在后面的章节中更详细地讨论能量模型。
现在让我们看看玻尔兹曼机器如何应用于两类问题,即学习和搜索。
- 学习:当波尔兹曼机器被用于学习时,它们试图从输入中获得重要的特征,重建这个输入,并通过并行更新权重将其作为输出。让我们以 MNIST 数据集为例。下图显示了应用于 MNIST 数据集时受限玻尔兹曼机器的重构输出。
需要注意的是,在这个学习和重建过程中,玻尔兹曼机器也可能会学习预测或插值缺失的数据。考虑使用电影评论数据集。使用波尔兹曼机器,我们可以预测用户是否会喜欢或不喜欢一部新电影。
- 搜索:玻尔兹曼机器的架构和工作非常适合解决约束满足问题(搜索可以满足所有约束的解决方案),即使它具有弱约束。具有弱约束的问题试图获得与完全满足所有约束的答案足够接近的答案,即答案不需要完全满足所有约束。
在下一节中,让我们详细研究玻尔兹曼机器的体系结构。
波尔兹曼机器背后的直觉
与我们迄今为止看到的其他神经网络模型不同,玻尔兹曼机器的架构非常不同。输入层和输出层之间没有明确的界限。实际上没有输出层。玻尔兹曼机器中的节点简单地分为可见节点和隐藏节点。可见节点接受输入。接收输入的相同节点将返回重构的输入作为输出。这是通过双向权重实现的,双向权重将向后传播并在可见节点上呈现输出。
该体系结构的一个主要改进是每个节点都连接到所有其他节点,甚至在同一层中(例如,每个可见节点都连接到所有其他可见节点以及隐藏节点)。所有链路都是双向的,权重是对称的。下面是一个解释相同的图像。
此外,每个节点只有两种可能的状态,即开和关。节点的状态由与其相关的权重和偏差决定。提供给模型的输入,即与该特定输入直接或间接相关的节点(假设)将打开。
CSP 的玻尔兹曼机器
如前所述,波尔兹曼机器在处理学习问题和搜索问题时所遵循的方法是不同的。
约束满足问题简称为 CSP
在搜索问题的情况下,连接上的权重是固定的,并且它们用于表示优化问题的成本函数。通过利用随机方法,玻尔兹曼机器对二进制向量进行建模,并找到可以作为优化问题的良好解决方案的最佳模式。在学习问题的情况下,模型试图学习权重,以提出状态向量作为手头问题的良好解决方案。让我们通过研究架构如何塑造自己来解决约束满足问题(CSP)来把事情弄清楚。
体系结构中的每个节点都是一个假设,任何两个节点之间的连接都是约束。如果假设 h1 支持假设 h2,那么这种联系是肯定的。由于波尔兹曼机器可以解决弱约束的约束满足问题,每个约束都有一个与之相关的重要性值。连接权重决定了这个约束的重要性。如果权重很大,则约束更重要,反之亦然。在没有证据支持假设的情况下,施加在每个节点上的偏置决定了节点“开启”的可能性。如果偏置为正,则该节点保持“开”,否则保持“关”。
使用这样的设置,随着越来越多的例子被输入到模型中,权重和状态被改变;直到并且除非它能够产生满足大多数优先约束的输出。如果生成了足够好的输出,则训练过程可以停止。这里的问题是,如果让模型处于低能量状态,输出据说是好的。最低的能量输出将被选为最终输出。
玻尔兹曼机器的超参数
与其他神经网络结构相对应,超参数在训练玻尔兹曼机器中起着至关重要的作用。除了典型的激活率、丢失率和学习率之外,以下是几个需要优先考虑的重要超参数。
- 重量初始化:重量初始化是训练过程中的一个重要步骤。权重的正确初始化可以节省大量时间,因为它可以优化学习这些权重所需的时间,这是训练网络的全部思想。随着权重变得更好,网络可以发送更好的信号,使其分类结果更准确。
- 可见和隐藏单位: 输入的个数是明确赋予网络的特征。必须最优地选择隐藏特征的数量,以使网络掌握大多数特征。这些层中的每一层都有自己的变换函数来处理输入并将它们传递到下一层。
- 正则化: 通过正则化,一个网络的过拟合几率被拉走。每当模型过拟合或学习到较大的权重时,它就会受到惩罚,因为它有助于将权重降低到可接受的水平。
在下一节,让我们回顾不同类型的玻尔兹曼机器。
玻尔兹曼机器的类型
正如所讨论的,Boltzmann 机被开发来模拟具有弱约束的约束满足问题。但是它的范围已经扩展到解决其他各种问题。玻尔兹曼机器有一些变化,这些变化随着时间的推移而发展,以根据它们所处的用例来解决这些问题。让我们在下面的章节中简单回顾一下。
有记忆的玻尔兹曼机器
在传统的波尔兹曼机器中,一个节点知道所有那些此刻触发当前节点的节点。在具有记忆的玻尔兹曼机器的情况下,连同负责当前节点被触发的节点,每个节点将知道这发生的时间步长。这种机制使得这种模型能够预测序列。例如,它们可以用来预测单词以自动填充不完整的单词。比方说,当 SCI 作为输入被给出时,玻尔兹曼机器有可能预测作为科学的输出。
一般来说,每个单元增加一个存储单元。这改变了节点在任何时刻被激活的概率,取决于其他节点的先前值和它自己的相关权重。这是通过节点状态到下一个节点的传导延迟来实现的。总的来说,这种架构有能力跨序列重建训练数据。
受限玻尔兹曼机器
传统玻尔兹曼机器的一个主要复杂之处在于,尽管节点数量较少,但计算量巨大。在这种情况下,由于依赖连接,更新权重很费时间。为了减少这种依赖性,对这些连接进行了限制,以限制模型具有层内连接。
对连接的这种限制使得输入和隐藏节点在层内是独立的。所以现在,权重可以并行更新。
深层玻尔兹曼机器
深度玻尔兹曼机器可以被假设为一堆 RBM,与深度信念网络略有不同。传统的玻尔兹曼机器使用随机生成的马尔可夫链(给出可能事件发生的顺序)进行初始化,随后随着训练的进行对其进行微调。这个过程太慢,不实用。为了解决这个问题,深度玻尔兹曼机器采用了不同的方法。它们的架构类似于包含许多层的受限玻尔兹曼机器。每一层都被贪婪地预训练,然后通过反向传播对整个模型进行微调。
深度玻尔兹曼机器经常与深度信念网络混淆,因为它们以类似的方式工作。区别出现在连接上。dbn 中的连接在后面的层中是定向的,而在 DBMs 中是不定向的。
PyTorch 中 RBMs 的实现
在这一节中,我们将在 PyTorch 中实现受限玻尔兹曼机。我们将建立一个使用 MNIST 数据集的分类器。在已经介绍的各种波尔兹曼机器中,我们将在这里使用受限的波尔兹曼机器体系结构。下面是从头开始构建 RBM 的步骤。
步骤 1:导入所需的库
在这一步,我们导入所有必要的库。此外,为了可视化结果,我们将使用 torchvision.utils
。
import numpy as np
import torch
import torch.utils.data
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torchvision import datasets, transforms
from torchvision.utils import make_grid , save_image
%matplotlib inline
import matplotlib.pyplot as plt
步骤 2:加载 MNIST 数据集
在这一步中,我们将通过 torch.utils.data 库的 DataLoader 类使用 MNIST 数据集来加载我们的训练和测试数据集。我们将批量大小设置为64
并应用transformations
。
batch_size = 64
train_loader = torch.utils.data.DataLoader(
datasets.MNIST('./data',
train=True,
download = True,
transform = transforms.Compose(
[transforms.ToTensor()])
),
batch_size=batch_size
)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('./data',
train=False,
transform=transforms.Compose(
[transforms.ToTensor()])
),
batch_size=batch_size)
步骤 3:定义模型
在这一步,我们将开始构建我们的模型。我们将定义与可见和隐藏神经元相关的转换。此外,由于玻尔兹曼机是一个能量模型,我们还定义了一个能量函数来计算能量差。在初始化函数中,我们还初始化隐藏和可见神经元的权重和偏差。
class RBM(nn.Module):
def __init__(self,
n_vis=784,
n_hin=500,
k=5):
super(RBM, self).__init__()
self.W = nn.Parameter(torch.randn(n_hin,n_vis)*1e-2)
self.v_bias = nn.Parameter(torch.zeros(n_vis))
self.h_bias = nn.Parameter(torch.zeros(n_hin))
self.k = k
def sample_from_p(self,p):
return F.relu(torch.sign(p - Variable(torch.rand(p.size()))))
def v_to_h(self,v):
p_h = F.sigmoid(F.linear(v,self.W,self.h_bias))
sample_h = self.sample_from_p(p_h)
return p_h,sample_h
def h_to_v(self,h):
p_v = F.sigmoid(F.linear(h,self.W.t(),self.v_bias))
sample_v = self.sample_from_p(p_v)
return p_v,sample_v
def forward(self,v):
pre_h1,h1 = self.v_to_h(v)
h_ = h1
for _ in range(self.k):
pre_v_,v_ = self.h_to_v(h_)
pre_h_,h_ = self.v_to_h(v_)
return v,v_
def free_energy(self,v):
vbias_term = v.mv(self.v_bias)
wx_b = F.linear(v,self.W,self.h_bias)
hidden_term = wx_b.exp().add(1).log().sum(1)
return (-hidden_term - vbias_term).mean()
如前所述,最后,我们总是定义前向方法,神经网络使用该方法通过网络向前传播权重和偏差并执行计算。该过程重复 k 次,这定义了计算对比散度的次数。由于玻尔兹曼机器是基于能量的机器,我们现在定义计算模型能量状态的方法。
步骤 4:初始化和训练模型
RBM 类用 k 初始化为 1。在本例中,我们将使用 SGD 优化器。在这个过程的最后,我们将在一个 1D 数组中累加所有的损失,我们首先初始化这个数组。我们使用data.bernoulli()
方法提取伯努利分布。这是我们将要开始研究的输入模式。
接下来,生成的模式被提供给 rbm 模型对象。模型返回输入的模式和计算出的模式作为输出。损耗计算为这两种模式的能量之差,并将其添加到列表中。如前所述,由于优化器执行加法操作,我们最初将累加器初始化为零。使用backward()
方法反向传播损失。optimizer.step()
根据当前梯度(在backward()
调用期间累积并存储在参数的.grad
属性中)和更新规则执行参数更新。
rbm = RBM(k=1)
train_op = optim.SGD(rbm.parameters(),0.1)
for epoch in range(10):
loss_ = []
for _, (data,target) in enumerate(train_loader):
data = Variable(data.view(-1,784))
sample_data = data.bernoulli()
v,v1 = rbm(sample_data)
loss = rbm.free_energy(v) - rbm.free_energy(v1)
loss_.append(loss.data)
train_op.zero_grad()
loss.backward()
train_op.step()
print("Training loss for {} epoch: {}".format(epoch, np.mean(loss_)))
在下面的代码片段中,我们定义了一个 helper 函数,在该函数中,我们将 numpy 图像转置到合适的维度,并将其存储在本地存储中,其名称作为输入传递给该函数。
def show_adn_save(file_name,img):
npimg = np.transpose(img.numpy(),(1,2,0))
f = "./%s.png" % file_name
plt.imshow(npimg)
plt.imsave(f,npimg)
步骤 5:可视化输出
在这一步中,我们将可视化输出!
show_adn_save("real",make_grid(v.view(32,1,28,28).data))
show_adn_save("generate",make_grid(v1.view(32,1,28,28).data))
正如我们所见,上面是来自 MNIST 数据集的真实图像,下面是波尔兹曼机器生成的图像。
我希望你喜欢阅读这篇文章!
量子机器学习入门指南
原文:https://blog.paperspace.com/beginners-guide-to-quantum-machine-learning/
作为一名数据科学家和研究人员,我总是试图找到每天遇到的问题的答案。在处理现实世界的问题时,我遇到了时间和计算上的许多复杂性。已经有很多经典的机器学习和深度学习算法不起作用的情况,我的电脑最终崩溃了。
在封锁期间,我偶然发现了一个很酷的新科幻系列,叫做 Hulu 上的 Devs 。 Devs 探索世界各地正在发生的量子计算和科学研究。这让我想到了量子理论,量子计算是如何产生的,以及量子计算机如何用于预测未来。
经过进一步研究,我发现了量子机器学习(QML),这在当时对我来说是一个非常新的概念。这个领域既令人兴奋又有用;它可以帮助解决计算和时间复杂性的问题,就像我面临的那些问题。因此,我选择 QML 作为进一步研究的主题,并决定与大家分享我的发现。
量子机器学习是一个刚刚开始发展的理论领域。它位于量子计算和机器学习的交叉点。
量子机器学习的主要目标是通过将我们从量子计算中了解的知识应用到机器学习中来加快速度。量子机器学习理论从经典机器学习理论中汲取元素,并从这个角度看待量子计算。
内容
这篇文章将涵盖以下主要话题:
- 经典编程与经典机器学习和量子机器学习的比较
- 量子计算的所有基本概念
- 量子计算如何改进经典的机器学习算法
经典编程 vs .经典机器学习 vs .量子机器学习
为了比较经典编程、经典机器学习和量子机器学习,让我们考虑确定一个数是偶数还是奇数的简单问题。
解决方案很简单:首先你需要从用户那里得到一个数字,然后你把这个数字除以 2。如果你得到一个余数,那么这个数是奇数。如果你得不到余数,那么这个数就是偶数。
如果您想使用传统的编程方法编写这个特定的程序,您将遵循三个步骤:
- 获取输入
- 处理输入
- 产生输出
这是经典编程范例的工作流程。
The workflow of classical programming using the above example
处理是通过我们为数字分类(偶数或奇数)定义的规则来完成的。
同样,让我们看看如何使用机器学习方法来解决这个特殊的问题。在这种情况下,事情有点不同。首先,我们创建一组输入和输出值。在这里,方法是将输入和预期输出一起输入到机器学习模型,该模型应该学习规则。有了机器学习,我们不会告诉计算机如何解决问题;我们设置了一个场景,在这个场景中,程序会自己学习这样做。
从数学上来说,我们的目标是找到 f ,给定 x 和 y ,这样:
y = f(x)
The workflow of Classical Machine Learning using the above example
让我们转向量子计算。每当你想到“量子”这个词,它可能会引发原子或分子的想法。量子计算机也是由类似的想法组成的。在传统的计算机中,处理发生在比特级。在量子计算机的例子中,有一种特定的行为控制着系统;即量子物理学。在量子物理学中,我们有各种各样的工具用来描述不同原子之间的相互作用。在量子计算机的情况下,这些原子被称为“量子比特”(我们将在后面详细讨论)。一个量子位同时扮演着粒子和波的角色。与粒子(或比特)相比,波分布存储了大量数据。
损失函数用于检查机器学习解决方案的准确性。在训练机器学习模型并获得其预测时,我们经常观察到所有的预测都不正确。损失函数由一些数学表达式表示,其结果显示了算法错过目标的程度。
量子计算机也旨在减少损失函数。它有一种称为量子隧道的特性,可以搜索整个损失函数空间,找到损失最低的值,从而使算法以最快的速度发挥最佳性能。
量子计算的基础
在深入研究量子机器学习之前,读者应该熟悉基本的量子计算术语,这里将讨论这些术语。
布雷克记号
在量子力学和量子物理学中,“Bra-ket”符号或“Dirac”符号用于写方程。读者(尤其是初学者)一定知道这个,因为他们在阅读涉及量子计算的研究论文时会碰到。
该符号使用尖括号和竖线来构造“bras”和“kets”。
一首《偈》大概是这样的:| v \8592;。数学上,它表示复向量空间 V 中的向量 v 。在物理上,它代表了一个量子系统的状态。
一个“胸罩”长这样: 〈f| 。数学上,它表示一个线性函数 f: V → C ,即把 V 中的每个向量映射到复平面 C 中的一个数的线性映射。
让一个线性函数 〈f| 作用于一个矢量| v÷T3,写作:
□c
波函数和其他量子态可以用 Bra-ket 符号表示为复态中的矢量。量子叠加也可以用这个符号来表示。其他应用包括波函数归一化,以及与线性算子相关的测量。
“量子位”的概念和叠加态
量子计算使用的是“量子位”,而不是经典计算机使用的“位”。一个比特指的是一个二进制数字,它构成了经典计算的基础。术语“量子位”代表量子二进制数字。虽然比特只有两种状态——0 和 1——但量子比特可以同时有多种状态。该值介于 0 和 1 之间。
为了更好地理解这个概念,举一个抛硬币的例子。一枚硬币有两面,正面(1)或反面(0)。在扔硬币的时候,我们不知道它是哪一边的,直到我们停下来或者它掉在地上。看看下面的抛硬币。你能说出它有哪一面吗?它根据你的视角显示 0 和 1;只有当你停下来看的时候,它才显示出一面。量子位的情况类似。
这就是所谓的叠加 的两种状态。这意味着测量 0 或 1 的概率通常既不是 0.0 也不是 1.0。换句话说,quibit 有可能同时处于不同的状态。在抛硬币的情况下,当我们得到结果(正面或反面)时,叠加态就崩溃了。
布洛赫球
布洛赫球是量子位的数学表示。它用一个正常长度为 1 的二维向量来表示一个量子位的状态。这个向量有两个元素:实数α和复数β。
The Bloch Sphere (source)
一个量子位可以被认为是两个状态的叠加,可以用下面的语句来表示,如上图所示:
|ψ〉 = α |0〉 + β |1〉
由于光学的历史原因,布洛赫球也称为庞加莱球,专门代表不同类型的偏振。存在六种常见的极化类型,称为琼斯矢量。的确,亨利·庞加莱在 19 世纪末第一个提出使用这种几何表示法,作为斯托克斯参数的三维表示法。
量子退相干
量子位的叠加会导致像量子退相干这样的问题。这些是由于系统中的噪声而随机自然发生的不必要的崩溃。最终,这会导致计算错误。如果你认为一个量子位处于叠加态,而它并不处于叠加态,我们对它进行操作,它会给你一个不同于你预期的答案。这就是为什么我们会多次反复运行同一个程序,类似于训练一个机器学习模型。
是什么导致了量子退相干?
量子系统需要与环境隔离,因为与环境接触是导致量子退相干的原因。
量子位被冷却到接近绝对零度。当量子位与环境相互作用时,来自环境的信息漏入其中,而来自量子位内部的信息则泄漏出去。泄漏出来的信息很可能是未来或当前计算所需要的,而泄漏进来的信息是随机噪声。
这个概念与热力学第二定律非常相似,该定律指出:
“一个孤立系统的总熵永远不会随时间而减少,当且仅当所有过程都是可逆的时,它才是常数。孤立系统自发地向热力学平衡演化,即熵最大的状态。”
因此,量子系统需要处于相干状态。与较大的物体,如一本书或一张桌子相比,量子退相干在微小的粒子中更明显。事实上,所有的材料都有一个特定的波长,但是物体越大,它的波长就越小。
量子纠缠
量子纠缠的概念是指如果我们取两个量子比特,它们总是处于两个态的叠加状态。这里有一个例子。假设有一个盒子,里面有一副手套。随机从盒子里拿出一只手套。然后盒子被带到另一个房间。取出的手套被发现是右撇子,所以我们自动知道仍在盒子里的手套是左撇子。
量子位也是如此。如果一个处于向上旋转位置,则另一个自动处于向下旋转位置。不存在两个量子位处于相同状态的情况。换句话说,他们总是纠缠不清。这就是所谓的量子纠缠。
双重原则
量子位同时展现了波和粒子的特性。事实上,所有的物体都是如此,但在原子大小的物体中,如量子位,它们可以被观察得更清楚。波粒二象性使量子位能够通过干涉相互作用。
量子加速
量子相干性有助于量子计算机以经典计算机无法做到的方式处理信息。量子算法执行逐步过程来解决问题,例如搜索数据库。它可以胜过最著名的经典算法。这种现象被称为量子加速。
量子计算如何改进经典机器学习
现在你已经了解了量子计算的一些基本概念,让我们讨论一下量子计算机用来解决机器学习问题的一些方法。下面列出了我们将要研究的技术:
- 量子机器学习解决线性代数问题
- 量子主成分分析
- 量子支持向量机和核方法
- 量子优化
- 深度量子学习
1)量子机器学习解决线性代数问题
通过对高维向量空间中的向量执行矩阵运算来解决各种各样的数据分析和机器学习问题。
在量子计算中,量子位的量子状态是二维复向量空间中的一个向量。在这个空间中会发生很多矩阵变换。量子计算机可以解决常见的线性代数问题,例如傅立叶变换,寻找特征向量和特征值,以及在 a 中为多项式的时间内求解二维向量空间上的线性方程组(并且由于量子加速比经典计算机快很多)。其中一个例子是哈罗、哈西迪姆和劳埃德(HHL)算法。
The circuit diagram of the HHL Algorithm
2)量子主成分分析
主成分分析是一种降维技术,用于降低大型数据集的维数。降维是以准确性为代价的,因为我们需要决定在不丢失重要信息的情况下消除哪些变量。如果做得正确,它会使机器学习任务变得更加舒适,因为它更方便处理更小的数据集。
例如,如果我们有一个有十个输入属性的数据集,那么主成分分析可以由经典计算机有效地执行。但是,如果输入数据集有一百万个特征,传统的主成分分析方法将会失败,因为我们很难可视化每个变量的重要性。
经典计算机的另一个问题是特征向量和特征值的计算。输入的维数越高,相应的特征向量和特征值的集合就越大。量子计算机通过使用量子随机存取存储器(QRAM)随机选择一个数据向量,可以非常高效、非常高速地解决这个问题。它使用量子位将向量映射成量子态。
量子主成分分析后得到的概括向量具有对数量子位。选择的随机向量形成密集矩阵。这个矩阵实际上是协方差矩阵。
通过对数据进行重复采样,并使用一种称为密度矩阵求幂的技巧,结合量子相位估计算法(计算矩阵的特征向量和特征值),我们可以获得任何数据向量的量子版本,并将其分解为其主分量。因此,计算复杂度和时间复杂度都以指数方式降低。
Quantum Circuit to perform Principal Component Analysis
3)量子支持向量机
支持向量机是一种经典的机器学习算法,用于分类和回归。对于分类任务,它用于将可线性分离的数据集分类到它们各自的类中。假设,如果数据不是线性可分的,那么它的维数增加,直到它是线性可分的。
在经典计算机中,SVM 只能在特定的维数下执行。过了一个特定的极限,就会很难,因为这样的计算机没有足够的处理能力。
然而,量子计算机可以以指数级更快的速度执行支持向量算法。叠加纠缠的原理让它高效工作,更快出结果。
Quantum Circuit to perform SVM
4)量子优化
如果你试图用最少的资源产生最好的产出,这就是优化。在机器学习模型中使用优化来改进学习过程,以便它可以提供最充分和最准确的估计。
优化的主要目标是最小化损失函数。更大的损失函数意味着将有更不可靠和更不精确的输出,这可能是昂贵的并导致错误的估计。
机器学习中的大多数方法需要迭代优化它们的性能。量子优化算法在解决机器学习中的优化问题方面提出了改进。量子纠缠的性质使得能够产生以量子状态编码的本溶液的多个副本。它们用于在机器学习算法的每一步改进该解决方案。
5)深度量子学习
量子计算可以与深度学习相结合,以减少训练神经网络所需的时间。通过这种方法,我们可以为深度学习和执行底层优化引入新的框架。我们可以在实际的、真实世界的量子计算机上模拟经典的深度学习算法。
当实现多层感知器架构时,计算复杂度随着神经元数量的增加而增加。专用 GPU 集群可用于提高性能,显著减少训练时间。然而,与量子计算机相比,即使这样也会增加。
量子计算机本身的设计方式是,硬件可以模仿神经网络,而不是经典计算机中使用的软件。在这里,量子位充当神经元,构成神经网络的基本单元。因此,包含量子位的量子系统可以充当神经网络,并可以以超过任何经典机器学习算法的速度用于深度学习应用。
量子机器学习的模拟
PennyLane 是一款来自 Xanadu 的开源软件,用于执行量子机器学习的模拟。它将经典的机器学习包与量子模拟器和硬件相结合。
PennyLane works with all the basic python packages
PennyLane 支持不断增长的生态系统,包括各种各样的量子硬件和机器学习库。
“虽然社区仍在致力于容错量子计算,但 PennyLane 和硬件产品允许企业客户今天就开始利用量子计算”
——内森·基洛兰,Xanadu 软件和算法主管。
结论
在这篇文章中,我们看了量子计算的基础知识,它可以用来实现机器学习。
量子机器学习是一个不断发展的领域,研究人员表示,到 21 世纪 30 年代中期,量子计算机将变得流行,人们将开始使用它们。
在这里,我们首先将经典编程与经典机器学习和量子机器学习进行了对比。我们发现量子机器学习算法是其中最好的。
然后,我们深入研究了量子计算的基础知识。我们讨论了:
- bra-ket 符号,用于写量子物理中的方程。
- 量子位的概念和控制量子位状态的叠加定理。
- 布洛赫球,在数学上用来表示量子位的状态
- 量子退相干及其原因
- 量子纠缠的一个例子
- 对象的双重原则
- 量子加速
现在,读者已经熟悉了基本的量子计算术语,我们通过使用以下方法来研究量子计算如何增强经典机器学习:
- 量子机器学习解决线性代数问题,
- 量子主成分分析,
- 量子支持向量机和核方法,
- 量子优化,以及
- 深度量子学习。
在所有这些技术中,我们研究了量子系统如何比经典系统工作得更好。
我们还看到并简要了解了 PennyLane,这是一种用于模拟量子机器学习算法的开源软件。
简而言之,量子计算的未来将见证我们解决当今世界面临的一些最复杂的问题。
量子机器学习有巨大的机会颠覆许多行业。金融、制药和安全行业将在最短的时间内看到最大的变化。
虽然一些专家警告说这种能力可能会被用于危险的目的,但电气和电子工程师协会(IEEE )量子计算标准工作组主席威廉·赫尔利认为好处会超过坏处。据他说:
“对于任何新的技术进步,我们总是有理由降低乐观情绪。尽管如此,我对量子计算的潜在积极成果感到非常兴奋。从发现疾病的新疗法到帮助发现新粒子,我认为这是我整个职业生涯中最兴奋的一次。我们应该像关注负面结果一样关注正面结果。”
换句话说,量子机器可以让我们过上更好的生活,如果有效使用,可以消除我们增强机器学习算法的道路上的许多障碍。
参考
本文是对各种来源进行研究的结果,列举如下:
借助 Roboflow 数据集对 YOLOv6 和 YOLOv7 进行纸张空间梯度基准测试
原文:https://blog.paperspace.com/benchmarking-yolov6-yolov7-paperspace-roboflow-datasets/
介绍
在这篇博文中,我们将使用通过 Roboflow 生成的数据集,在 Paperspace 提供的三种流行的 GPU 机器上测试 YOLOv6 和 YOLOv7 的性能。
Roboflow 是一个计算机视觉平台,具有大量关于数据注释、模型训练和数据兼容性的有用功能。YOLOv6 和 YOLOv7 是计算机视觉领域最先进的实时物体检测库。
这篇文章并不是要深入研究 YOLO 模型架构,而是要强调将 Roboflow 数据集与不同的模型类型一起开箱即用,然后使用来自 Paperspace 的各种 GPU 在这些数据集上进行训练是多么容易。
我们开始吧!
设置
在本教程中,我们将使用两个数据集——一组绵羊的单类航拍图像和一组部落冲突基地的多类图像。每个数据集的详细信息如下。
本教程的内容是在三种不同类型的 GPU 上使用不同的数据集训练两种不同的对象检测模型(YOLOv6、YOLOv7 ),以展示如何确定哪种 GPU 最适合给定的进程。
首先,我们将使用 5 个时期进行基准测试。然后,我们将这些结果外推到 100 个时期,以估计我们需要考虑的训练时间和成本——然后,我们将继续为全部 100 个时期训练最有希望的组合,并详述结果。
基准测试流程和代码可从这里获得。
资料组
我们将使用 Roboflow 制作的两个数据集。这些数据集如下:
资料组 | 类型 | 图像尺寸 | 训练图像 | 验证图像 | 测试图像 | 环 |
---|---|---|---|---|---|---|
空中绵羊 | 单级 | 3840 x 2160 | One thousand two hundred and three | Three hundred and fifty | One hundred and seventy-four | https://universe.roboflow.com/riis/aerial-sheep/dataset/1 |
部族冲突 | 多类 | 640 x 640 | Eighty-eight | Twenty-four | Thirteen | https://universe . robo flow . com/find-this-base/clash-of-clans-vop4y/dataset/5 |
(Left) Example image from Aerial Sheep dataset. (Right) Example image from Clash of Clans dataset.
现在表已经设置好了,让我们来看看如何从 Roboflow 生成和下载这样的数据集。
从 Roboflow 下载数据
Roboflow 最棒的事情之一是 Roboflow Universe ,它提供了大量不同的项目和数据集,可以用于各种模型。
The Roboflow Universe is filled with lots of handy datasets and pre-trained models
在本教程中,我们将培训 YOLOv6 和 YOLOv7 模型。在 Roboflow 上有一些快速简单的步骤来下载他们所需的数据(以正确的格式)。我们现在将向您展示如何准备部落冲突数据集。
首先我们将前往部落冲突项目页面。接下来,我们将选择Download
。
Downloading the Clash of Clans dataset from Roboflow
从那里,我们将选择meituan/PyTorchv6
作为我们的导出格式,然后我们将确保show download code
被启用。
We'll select the meituan/YOLOv6
format with show download code
enabled
我们现在应该看到 Roboflow 已经生成了一个片段,我们可以使用它将数据集导入到我们的项目中。
Copy the snippet generated by Roboflow
这就是全部了!我们现在有了一个可以插入笔记本的片段,它将把 Roboflow 数据集下载到我们的项目中。太棒了。
在图纸空间中设置测试环境
我们将使用梯度笔记本在 Paperspace 上运行一个简单的基准测试环境。
笔记本文件位于这个 GitHub repo 中。我们可以在创建新笔记本时将回购直接放入笔记本。
在 Paperspace 控制台中,我们将首先导航到 Gradient,这是 Paperspace 的机器学习平台,由强大的 GPU 支持,然后在一个项目中创建一个新的笔记本。
然后我们将选择PyTorch 1.12
运行时。
Select the PyTorch
runtime
接下来,我们将选择机器。在这种情况下,我们将从 P6000 GPU 开始,知道我们可以随时停止笔记本电脑并在不同的机器上重新启动。
Select a machine such as the P6000 GPU
接下来,我们将切换Advanced options
并将测试报告的 URL 粘贴到工作区 URL 字段中。
Add the workspace URL to automatically pull the benchmarking repo into the notebook
现在我们可以启动笔记本了,我们应该看到我们的笔记本已经进入了Running
状态。不错!
Our notebook is now running on a P6000 GPU
现在,我们剩下要做的就是将从 Roboflow 获取的代码片段注入到适当的代码单元中。我们将确保做到这一点,然后我们应该找到自己的工作基准笔记本。
培训和基准
对于这个目标检测任务,我们运行了两个不同的 YOLO 模型, MT-YOLOv6 和 YOLOv7 PyTorch 。
让我们来看看我们将要运行的两个模型的一些细节。让我们一定要注意它们之间的尺寸差异,因为这将影响我们开始训练时可能会看到的训练时间。
YOLOv6 基本网络详细信息
模型 | YOLOv6 |
层 | Two hundred and ninety-five |
因素 | 17.2 米 |
GFLOPS | Forty-four point two |
YOLOv7 基本网络详细信息
模型 | YOLOv7 |
层 | Four hundred and fifteen |
因素 | 37.2 米 |
GFLOPS | One hundred and five point four |
在我们的测试中,我们在三个不同的 GPU 机器支持的两个不同的数据集上使用了两个不同的模型,总共有十二种不同的组合。
比较每次运行的平均历元时间,平均历元时间是 5 个历元的样本。然后,为了下面的比较,我们将这个训练样本外推至 100 个时期。一旦我们建立了这些基线,我们将为全部 100 个时期训练最有希望的组合。
为了外推至 100 个时期,训练时间乘以每台 GPU 机器的按需价格,以确定估计的训练成本。这样,我们可以比较每个基准测试的培训时间和实际成本。
比较了以下 Paperspace GPU 计算机:
GPU 类型 | GPU 内存 | TFLOPS (SP) | 张量核 | 中央处理器(central processing units 的缩写) | 随机存取存储 | 每小时美元 |
---|---|---|---|---|---|---|
V100 | 16 GB | Fourteen | Six hundred and forty | eight | 30 GB | $2.30 |
Quadro P5000 | 16 GB | Eight point nine | Zero | eight | 30 GB | $0.78 |
RTX A6000 | 48 GB | Thirty-eight point seven | Three hundred and thirty-six | eight | 45 GB | $1.89 |
结果
十二次训练运行产生了以下基准。
详细的性能指标详述如下。请注意,在以下四个表中,我们从 5 个时期外推至 100 个时期,以获得完整培训时间和成本的估计值。
YOLOv6 -绵羊数据集
机器类型 | 模型 | 资料组 | 平均历元时间 | 按需每小时定价 | 培训成本(100 个时代) |
---|---|---|---|---|---|
P5000 | YOLOv6 | 羊 | 3 分 53 秒 | $0.78 | $5.05 | |
A6000 | YOLOv6 | 羊 | 1 分 28 秒 | $1.89 | $4.62 | |
V100 | YOLOv6 | 羊 | 1 分 25 秒 | $2.30 | $5.43 |
YOLOv6 -部落冲突数据集
机器类型 | 模型 | 资料组 | 平均历元时间 | 按需每小时定价 | 培训成本(100 个时代) |
---|---|---|---|---|---|
P5000 | YOLOv6 | 部族冲突 | 9 秒 | $0.78 | $0.20 | |
A6000 | YOLOv6 | 部族冲突 | 5 秒钟 | $1.89 | $0.26 | |
V100 | YOLOv6 | 部族冲突 | 8 秒钟 | $2.30 | $0.51 |
YOLOv7 -绵羊数据集
机器类型 | 模型 | 资料组 | 平均历元时间 | 按需每小时定价 | 培训成本(100 个时代) |
---|---|---|---|---|---|
P5000 | YOLOv7 | 羊 | 5 分 24 秒 | $0.78 | $7.02 | |
A6000 | YOLOv7 | 羊 | 1 分 24 秒 | $1.89 | $4.41 | |
V100 | YOLOv7 | 羊 | 1 分 51 秒 | $2.30 | $7.09 |
YOLOv7 -部落冲突数据集
机器类型 | 模型 | 资料组 | 平均历元时间 | 按需每小时定价 | 培训成本(100 个时代) |
---|---|---|---|---|---|
P5000 | YOLOv7 | 部族冲突 | 34 秒 | $0.78 | $0.74 | |
A6000 | YOLOv7 | 部族冲突 | 12 秒 | $1.89 | $0.63 | |
V100 | YOLOv7 | 部族冲突 | 30 秒 | $2.30 | $1.92 |
训练 YOLOv6 和 YOLOv7 个时期的结果
在 3/4 训练环境中,具有 48 GB GPU 内存的较新的 A6000 GPU 机器执行了最具成本效益的训练。
在航空绵羊数据集上进行训练需要更多的计算时间和能力,这不足为奇。除了拥有更多影像外,航空绵羊数据集还具有分辨率更大的影像。从数据中可以明显看出,更大的数据集图像受益于具有更高 TFLOPS 性能的机器。
当开始扩大图像大小和数据集大小时,我们开始看到 A6000 和 V100 等高端 GPU 在处理时间方面的一些优势。
在这种情况下,A6000 显然可以最快地处理 YOLOv6 和 YOLOv7 的航空绵羊。因此,尽管它不是最便宜的 GPU,但在较长的培训周期内,它仍将是最便宜的。当准备更长的训练跑时,这些权衡是有用的。
更进一步
既然我们已经查看了临时成本,让我们实际运行我们选择的组合的 100 个时期的模型,看看我们得到了什么性能。
我们将在 P5000 和 A6000 机器的帮助下执行完整的 100 个历元训练间隔,并将使用 mAP@0.5 测量模型的性能。
如果您想了解更多关于地图作为一种度量的信息,请查看 Paperspace 博客上的使用平均精度(mAP) 评估物体检测模型。
请注意,在下面的两个表格中,我们已经训练了 100 个时期的完整持续时间,而不是外推。
YOLOv6 和 YOLOv7 -部落冲突数据集
机器类型 | 模型 | 资料组 | 训练时间(100 个时期) | 按需每小时定价 | 培训成本(100 个时代) | 地图@0.5 |
---|---|---|---|---|---|---|
P5000 | YOLOv6 | 部族冲突 | 12 分 32 秒 | $0.78 | $0.15 | Zero point zero eight two | |
P5000 | YOLOv7 | 部族冲突 | 22 分 1 秒 | $0.78 | $0.27 | Zero point zero six eight |
YOLOv6 和 YOLOv7 -绵羊数据集
机器类型 | 模型 | 资料组 | 训练时间(100 个时期) | 按需每小时定价 | 培训成本(100 个时代) | 地图@0.5 |
---|---|---|---|---|---|---|
A6000 | YOLOv6 | 羊 | 1 小时 32 分 53 秒 | $1.89 | $2.93 | Zero point nine three three | |
A6000 | YOLOv7 | 羊 | 2 小时 29 分 56 秒 | $1.89 | $4.72 | Zero point nine one eight |
训练 YOLOv6 和 yolov 7 100 个时期的结果
从更长的训练运行中最明显的一点是,对于我们正在处理的数据集,YOLOv6 和 YOLOv7 都没有比另一个给出明显更好的 mAP@0.5 结果。
其次,我们应该注意到,正如预期的那样,YOLOv6 的运行时间是 YOLOv7 的一半多一点。
最后,就数据集本身而言,我们可以看到,Clash of Clans 数据集虽然使用起来很有趣,但没有提供足够的数据来获得任何模型的准确结果。这并不完全令人惊讶,因为数据集有超过 10 个包含少量图像的类。
与此同时,空中绵羊数据集要大得多,并且只有一个预测类,我们看到 mAP @ 0.5 > 0.9,这为我们的训练运行带来了更好的结果。
下一个
我们希望这篇博文能帮助你理解使用来自 Paperspace 的 GPU 和来自 Roboflow 的数据集的可能性。请务必关注@ hello perspace和 @roboflow 以跟上所有最新最棒的计算机视觉项目。
如果你真的喜欢这个基准测试,并且想使用 YOLOv6 或 YOLOv7 开发你自己的物体检测项目,在 Paperspace 上创建一个免费帐户,并且一定要查看 Roboflow Universe 。
用于自然语言处理的 BERT 变换器
原文:https://blog.paperspace.com/bert-natural-language-processing/
Photo by Ugur Akdemir / Unsplash
人工智能的辉煌,尤其是在深度学习的子领域,一直在不断前进,随着进步的加速,每个月都有无数的成就。我的许多文章主要关注深度学习的两个重要子领域:即计算机视觉和自然语言处理(NLP)。NLP 侧重于语言任务、语法和对语言的语义理解。我们利用深度学习模型来确保我们可以提出一种模式,让人工智能解码复杂的细节和特定文本或句子上下文的特定模式。为此,自该领域开始以来,已经开发了许多方法,旨在解决一些主要的 NLP 问题,例如文本分类、语言翻译(机器翻译)、聊天机器人以及其他类似的任务。
解决这类任务的一些流行方法包括序列对序列模型、注意力机制的应用、变形器和其他类似的流行技术。在本文中,我们的主要焦点是变压器的一个更成功的变体,来自变压器的双向编码器表示(BERT)。我们将理解入门所需的大多数基本概念,并利用这个 BERT 体系结构来计算自然语言处理问题的解决方案。下面的目录显示了我们将在这篇博文中讨论的特性列表。对于并行运行代码,我建议使用 Paperspace 上的渐变平台。
简介:
大多数与自然语言处理相关的任务最初都是在简单的 LSTMs(长短期记忆)网络的帮助下解决的。这些层可以相互叠加,形成矢量单词的学习过程。然而,这些网络本身不足以产生高质量的结果,并且在需要更高精度的任务和项目中失败,例如神经机器翻译和文本分类任务。简单 LSTMs 失败的主要原因是它们是缓慢而笨重的网络。此外,LSTM 的任何变体都不是完全双向的。因此,当我们在学习过程中前进时,单词向量的上下文和真正含义就丢失了。
这些任务中的一项突破性变革发生在 2017 年,推出了变压器。这些变压器网络发表在“ Attention is all you need ”研究论文中,以其独特的方法来解决简单 LSTM 架构中先前存在的问题,从而彻底改变了自然语言处理的世界。在我以前的一篇文章中,我们已经非常详细地介绍了变形金刚的概念。我强烈推荐通过下面的链接来看看这篇博客。在下一节中,我们将进一步了解变压器网络和 BERT 架构之间的密切关系。
然而,现在值得注意的是,当我们堆叠变压器网络的一堆解码器层而忽略变压器的编码器网络时,我们将获得生成预训练变压器(GPT) 模型。这种流行的架构在 GPT-2 和 GPT-3 网络中有了更多的变化,并在以前的版本中不断改进。然而,如果我们忽略变压器的解码器网络并堆叠变压器模型的编码器部分,我们将从变压器(BERT) 获得双向编码器表示。在接下来的部分,我们将更详细地了解 BERT 变压器的工作机制,通过它我们可以轻松解决许多问题。
了解 BERT 变压器:
与简单的 LSTM 网络不同,伯特变换器能够以更加复杂的方式理解语言。在变压器架构中,单独的编码器网络和解码器模块都具有理解语言的能力。因此,这些层中的任何一层的叠加都会产生这样一种场景,其中所产生的网络能够理解和学习语言。但是,问题是这些 BERT 模型究竟是如何被训练和利用来执行各种自然语言处理任务的。
BERT 的训练过程主要包括两个阶段,即 BERT 模型的预训练和 BERT 模型的微调。在预训练步骤中,我们的主要目标是教会我们的 BERT 模型对语言的理解。这个过程是通过使用半监督学习方法来完成的:在从网络或其他资源收集的大量信息上训练数据。在 BERT 模型的预训练步骤中,同时运行两个任务,包括掩蔽语言模型(MLM)和下一句预测(NSP)以理解上下文(或语言)。
在掩蔽语言模型(MLM)中,我们利用掩蔽来隐藏句子中的一些数据(大约 15%),以使模型更好地理解句子的上下文。目标是输出这些屏蔽的标记,以通过利用双向性来实现期望的语言理解。在下一个句子预测(NSP)的情况下,BERT 模型考虑两个句子,并确定第二个句子是否是第一个句子的适当后续。
在预训练的帮助下,BERT 模型能够实现对上下文和语言的理解。然而,为了将它用于我们试图执行的特定任务,我们需要进行第二步微调。在微调 BERT 模型时,我们用新的层网络替换了完全连接的输出层。BERT 模型的微调步骤是一个监督学习任务,我们训练模型来完成特定的任务。
由于网络已经被预先训练,微调步骤将相对更快,因为只有新的网络必须被训练,而其他权重被相应地稍微修改(微调)用于特定项目。我们将在本文的应用部分讨论这些具体任务。现在,让我们继续与 BERT 一起开发一个评审分数分类项目。
使用 TensorFlow-Hub 与 BERT 一起开发项目:
在本文的这一部分,我们将详细探讨如何加载 TensorFlow Hub 提供的预训练 BERT 模型,并向该 BERT 模型添加额外的迁移学习层,以构建文本分类项目。在本文中,我们将利用 TensorFlow 和 Keras 深度学习框架。如果你不熟悉这两个库,我强烈推荐你去看看我以前的博客中的这两个库,或者快速温习一下这些主题。你可以查看下面的文章来了解更多关于 TensorFlow 和 Keras 文章这里。让我们通过导入必要的库来开始创建 BERT 工作管道。
导入所需的库:
TensorFlow 深度学习框架以及 TensorFlow Hub 导入是这个项目的精髓。TensorFlow Hub 库使开发人员能够加载预训练的模型,以便进一步微调和部署众多项目。我们还将导入一些额外的层,这些层将添加到 BERT 模型的合并输出层,并使用这些更改来创建迁移学习模型,以执行文本分类任务。
我们还将使用一些额外的回调来保存训练好的模型,并使用张量板可视化性能。此外,还使用了一些其他的基本导入,如 NumPy 库、用于访问系统组件的 os 库、用于调节正则表达式操作的 re,以及用于过滤掉不必要注释的警告。
#Importing the essential libraries
import tensorflow as tf
import tensorflow_hub as hub
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dense, Activation, Dropout, Flatten
from tensorflow.keras.layers import BatchNormalization, Conv2D, MaxPool2D
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau
from tensorflow.keras.callbacks import LearningRateScheduler, TensorBoard
from datetime import datetime
from sklearn.metrics import roc_auc_score
import numpy as np
import pandas as pd
from tqdm import tqdm
import os
import re
import warnings
warnings.filterwarnings("ignore")
准备数据集:
我们将利用从以下 Kaggle 链接获得的亚马逊美食评论数据集。下面是对数据集及其相应代码片段的简要描述,以便阅读和理解数据。确保该文件位于构建项目的工作目录中。
这个数据集由亚马逊的美食评论组成。这些数据跨越了 10 多年的时间,包括截至 2012 年 10 月的所有约 500,000 篇评论。评论包括产品和用户信息、评级和纯文本评论。它还包括所有其他亚马逊类别的评论。
#Read the dataset - Amazon fine food reviews
reviews = pd.read_csv("Reviews.csv")
#check the info of the dataset
reviews.info()
在下一步中,我们将过滤掉任何不必要的“非数字”(NAN)值,同时只检索文本信息及其各自的分数。
# Retrieving only the text and score columns while drop NAN values
reviews = reviews.loc[:, ['Text', 'Score']]
reviews = reviews.dropna(how='any')
reviews.head(1)
在下一步中,我们将通过省略中性分数,将文本数据的分数转换为只有负面或正面评论的二元分类任务。中性分数,即值为 3 的分数被移除。由于这是一个关于自然语言处理的二元分类任务,我们尽量避免中性评论。所有小于 2 的分数都被认为是负面评价,所有大于 3 的分数都被认为是正面评价。
reviews.loc[reviews['Score'] <= 2, 'Score'] = 0
reviews.loc[reviews['Score'] > 3, 'Score'] = 1
reviews.drop(reviews[reviews['Score']==3].index, inplace=True)
reviews.shape
(525814, 2)
在下一步中,我们将对数据集执行一些必要的预处理。我们将创建两个函数。第一个函数将使我们能够只检索前 50 个单词,第二个函数将允许我们删除训练过程中不需要的一些不必要的字符。我们可以调用函数并获得新的数据集。下面是预处理后的代码块和样本数据集。
def get_wordlen(x):
return len(x.split())
reviews['len'] = reviews.Text.apply(get_wordlen)
reviews = reviews[reviews.len<50]
reviews = reviews.sample(n=100000, random_state=30)
def remove_html(text):
html_pattern = re.compile('<.*?>')
return html_pattern.sub(r'', text)
text=reviews['Text']
preprocesstext=text.map(remove_html)
reviews['Text']=preprocesstext
#print head 5
reviews.head(5)
下面是用分层抽样和特定随机状态分割数据的代码块。分层抽样分裂允许我们在训练和测试数据中保持 0 和 1 的平衡计数,即保持适当的二进制组成。随机状态规范允许我们跨各种系统和平台以不变的方式更容易地跟踪结果。下面是分割数据集及其各自的训练和测试图的代码片段。
# Split the data into train and test data(20%) with Stratify sampling and random state 33
from sklearn.model_selection import train_test_split
X = reviews['Text']
y = reviews["Score"].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, stratify=y, random_state = 33)
Image by Author
最后,在执行完本节中的所有步骤后,您可以将数据以预处理的 CSV 文件的形式保存到工作目录中,以便在需要时进行任何进一步的计算。
# Saving to disk. if we need, we can load preprocessed data directly.
reviews.to_csv('preprocessed.csv', index=False)
开发 BERT 模型:
对于这个项目,我们将使用 BERT 无案例模型,您可以从以下 TensorFlow Hub 网站获得该模型。它使用 L=12 个隐藏层(即变压器块),隐藏大小 H=768,A=12 个注意头。如前一节所述,在使用预训练的 BERT 模型时,我们还将传递屏蔽输入。我们可以从 BERT 模型中检索所需的输出,并继续对其执行进一步的迁移学习操作。
## Loading the Pretrained Model from tensorflow HUB
tf.keras.backend.clear_session()
max_seq_length = 55
# Creating the necessary requirements
input_word_ids = tf.keras.layers.Input(shape=(max_seq_length,), dtype=tf.int32, name="input_word_ids")
input_mask = tf.keras.layers.Input(shape=(max_seq_length,), dtype=tf.int32, name="input_mask")
segment_ids = tf.keras.layers.Input(shape=(max_seq_length,), dtype=tf.int32, name="segment_ids")
#bert layer
bert_layer = hub.KerasLayer("https://tfhub.dev/tensorflow/bert_en_uncased_L-12_H-768_A-12/1", trainable=False)
pooled_output, sequence_output = bert_layer([input_word_ids, input_mask, segment_ids])
bert_model = Model(inputs=[input_word_ids, input_mask, segment_ids], outputs=pooled_output)
bert_model.summary()
Model: "model"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_word_ids (InputLayer) [(None, 55)] 0 []
input_mask (InputLayer) [(None, 55)] 0 []
segment_ids (InputLayer) [(None, 55)] 0 []
keras_layer (KerasLayer) [(None, 768), 109482241 ['input_word_ids[0][0]',
(None, 55, 768)] 'input_mask[0][0]',
'segment_ids[0][0]']
==================================================================================================
Total params: 109,482,241
Trainable params: 0
Non-trainable params: 109,482,241
__________________________________________________________________________________________________
一旦我们获得了预训练的 BERT 模型,我们就可以继续对可用数据进行一些最终的符号化,并开发最终的模型,该模型将被微调以有助于我们旨在实现的特定任务。
符号化:
在下一步中,我们将对数据集执行标记化操作。为了计算这个特定的动作,有必要从下面的官方 GitHub 链接下载一个名为 tokenization.py 的预处理文件(在 Gradient 中使用终端对原始文件使用wget
)。一旦我们下载了下面的文件并把它放在工作目录中,我们就可以把它导入到我们的主项目文件中。在下面的代码块中,我们将较大的数据转换成对 BERT 模型有用且可理解的较小信息块。
我们在标记的开始和结束分别标记评论和CLS和SEP操作。这样做将有助于 BERT 模型区分每个句子的开头和结尾,这通常在二进制分类或下一句预测项目中很有用。我们还可以添加一些额外的填充以避免形状不匹配,最后,继续创建训练和测试数据,如下面的代码块所示。
#getting Vocab file
vocab_file = bert_layer.resolved_object.vocab_file.asset_path.numpy()
do_lower_case = bert_layer.resolved_object.do_lower_case.numpy()
#import tokenization from the GitHub link provided
import tokenization
tokenizer = tokenization.FullTokenizer(vocab_file, do_lower_case)
X_train=np.array(X_train)
# print(X_train[0])
X_train_tokens = []
X_train_mask = []
X_train_segment = []
X_test_tokens = []
X_test_mask = []
X_test_segment = []
def TokenizeAndConvertToIds(text):
tokens= tokenizer.tokenize(reviews) # tokenize the reviews
tokens=tokens[0:(max_seq_length-2)]
tokens=['[CLS]',*tokens,'[SEP]'] # adding cls and sep at the end
masked_array=np.array([1]*len(tokens) + [0]* (max_seq_length-len(tokens))) # masking
segment_array=np.array([0]*max_seq_length)
if(len(tokens)<max_seq_length):
padding=['[PAD]']*(max_seq_length-len(tokens)) # padding
tokens=[*tokens,*padding]
tokentoid=np.array(tokenizer.convert_tokens_to_ids(tokens)) # converting the tokens to id
return tokentoid,masked_array,segment_array
for reviews in tqdm(X_train):
tokentoid,masked_array,segment_array=TokenizeAndConvertToIds(reviews)
X_train_tokens.append(tokentoid)
X_train_mask.append(masked_array)
X_train_segment.append(segment_array)
for reviews in tqdm(X_test):
tokentoid,masked_array,segment_array=TokenizeAndConvertToIds(reviews)
X_test_tokens.append(tokentoid)
X_test_mask.append(masked_array)
X_test_segment.append(segment_array)
X_train_tokens = np.array(X_train_tokens)
X_train_mask = np.array(X_train_mask)
X_train_segment = np.array(X_train_segment)
X_test_tokens = np.array(X_test_tokens)
X_test_mask = np.array(X_test_mask)
X_test_segment = np.array(X_test_segment)
一旦您成功地创建了所有的令牌,将它们保存在 pickle 文件中是一个好主意,以便为任何需要的未来计算重新加载它们。您可以从下面的代码片段中做到这一点,其中显示了转储和加载功能。
import pickle
# save all your results to disk so that, no need to run all again.
pickle.dump((X_train, X_train_tokens, X_train_mask, X_train_segment, y_train),open('train_data.pkl','wb'))
pickle.dump((X_test, X_test_tokens, X_test_mask, X_test_segment, y_test),open('test_data.pkl','wb'))
# you can load from disk
X_train, X_train_tokens, X_train_mask, X_train_segment, y_train = pickle.load(open("train_data.pkl", 'rb'))
X_test, X_test_tokens, X_test_mask, X_test_segment, y_test = pickle.load(open("test_data.pkl", 'rb'))
在 BERT 上训练迁移学习模型;
一旦我们完成了数据的预处理、BERT 模型的预训练和数据的符号化,我们就可以通过开发我们的微调架构模型来创建迁移学习模型。首先,让我们通过传递标记、掩码和它们各自的段来获得训练数据的输出。
# get the train output with the BERT model
X_train_pooled_output=bert_model.predict([X_train_tokens,X_train_mask,X_train_segment])
类似地,我们也将使用 BERT 模型对测试数据进行预测,如下面的代码片段所示。
# get the test output with the BERT model
X_test_pooled_output=bert_model.predict([X_test_tokens,X_test_mask,X_test_segment])
下一步,我们可以使用 pickle 库来存储预测。请注意,前两个预测步骤可能需要一点时间来运行,这取决于您的系统硬件。一旦您成功地保存了预测输出并将它们转储到 pickle 文件中,您就可以注释掉该语句。然后,您可以再次使用 pickle 库为任何未来的计算加载数据。
# save all the results to the respective folder to avoid running the previous predictions again.
pickle.dump((X_train_pooled_output, X_test_pooled_output),open('final_output.pkl','wb'))
# load the data for second utility
X_train_pooled_output, X_test_pooled_output= pickle.load(open('final_output.pkl', 'rb'))
我们将创建一个自定义架构,作为微调层添加到预训练的 BERT 模型中。我们将添加一个具有 768 个输入要素的输入图层、一个指定了参数的密集图层、一个批量归一化图层和一个展平图层。我们将最终使用三个背靠背完全连接的层来完成网络。
最终的输出层将利用具有一个输出节点的 sigmoid 激活函数,因为这是一个二元分类任务,我们需要预测结果输出是正还是负。我使用了函数式 API 类型建模来允许对层和架构构建进行更多的控制。在这个步骤中也可以使用序列建模结构。
# input layer
input_layer=Input(shape=(768,), name='input_layer')
# Dense layer
layer1 = Dense(50,activation='relu',kernel_initializer=tf.keras.initializers.RandomUniform(0,1), name='layer1')(input_layer)
# MaxPool Layer
Normal1 = BatchNormalization()(layer1)
# Flatten
flatten = Flatten(data_format='channels_last',name='Flatten')(Normal1)
# FC layer
FC1 = Dense(units=30,activation='relu',kernel_initializer=tf.keras.initializers.glorot_normal(seed=32),name='FC1')(flatten)
# FC layer
FC2 = Dense(units=30,activation='relu',kernel_initializer=tf.keras.initializers.glorot_normal(seed=33),name='FC2')(FC1)
# output layer
Out = Dense(units=1,activation= "sigmoid", kernel_initializer=tf.keras.initializers.glorot_normal(seed=3),name='Output')(FC2)
model = Model(inputs=input_layer,outputs=Out)
一旦迁移学习微调模型被创建,我们可以调用一些必要的回调来保存我们的模型并相应地监控结果。然而,由于培训过程非常快,一些这样的回访可能是不必要的。我们将监控该模型的 AUROC 分数,因为它更准确地描述了所获得的结果。
checkpoint = ModelCheckpoint("best_model1.hdf5", monitor='accuracy', verbose=1,
save_best_only=True, mode='auto', save_freq=1)
reduce = ReduceLROnPlateau(monitor='accuracy', factor=0.2, patience=2, min_lr=0.0001, verbose = 1)
def auroc(y_true, y_pred):
return tf.py_function(roc_auc_score, (y_true, y_pred), tf.double)
logdir = os.path.join("logs", datetime.now().strftime("%Y%m%d-%H%M%S"))
tensorboard_Visualization = TensorBoard(log_dir=logdir, histogram_freq=1)
最后,我们可以编译和训练模型。对于编译,我们将利用 Adam 优化器、二进制交叉熵损失以及准确性和 AUROC 指标。我们将用 300 的批处理大小和下面代码块中提到的必要回调来训练 10 个时期的模型。
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy', auroc])
model.fit(X_train_pooled_output, y_train,
validation_split=0.2,
epochs=10,
batch_size = 300,
callbacks=[checkpoint, reduce, tensorboard_Visualization])
下面是我们为了更好的概念理解而构建的模型的图。该网络构建在 BERT 架构之上,作为执行文本分类特定任务的微调步骤。
tf.keras.utils.plot_model(
model, to_file='model1.png', show_shapes=False, show_layer_names=True,
rankdir='TB', expand_nested=False, dpi=96
)
Image by Author
在下一步中,我们将可视化模型的训练过程的性能。我们将使用张量板的神奇功能,并指定存储在当前工作目录中的日志文件夹的路径。运行外部张量板扩展时,我们可以查看各种图表。然而,我们关心的是 AUROC 分数,因为它代表了我们模型性能的最准确描述。下面是以下任务的图表。
%load_ext tensorboard
%tensorboard --logdir logs
Image by Author
我建议尝试 BERT 模型的许多迭代和变化,看看哪种类型的模型最适合您的特定用例。在本节中,我们讨论了如何使用 BERT 模型来解决二进制文本分类问题。但是,BERT 的应用并不局限于这个任务。在接下来的部分中,我们将进一步探索 BERT 的更多应用。
BERT 的应用:
伯特变压器有许多应用。总结一下这些网络是如何工作的,我们有一个预先训练好的 BERT 模型,可以用来解决各种各样的任务。在第二步中,我们添加微调层,这些层将确定我们试图在 BERT 模型的帮助下执行的任务或应用程序的类型。让我们看看 BERT 的一些更受欢迎的应用:
- 文本分类——在本文中,我们讨论如何使用 BERT transformer 进行文本分类。最终的 BERT 模型相应地用最终的 sigmoid 激活函数进行微调,以产生任何给定句子的二元分类输出。
- 问答任务 -我们还可以使用 BERT 模型执行问答搜索。[CLS]和[SEP]标记可以用在问题的开头和结尾,而其他段落则代表答案。我们可以相应地训练模型来执行这样的任务。
- 神经机器翻译(Neural Machine Translation)-BERT 模型对于机器翻译任务也很有用,该架构可以用来将一种语言输入的信息翻译成另一种语言。我们可以用特定的语言输入来训练模型,使其得到适当的翻译输出。
- 情感分析——类似于文本分类,我们可以使用 BERT 模型对多种情感进行分类。我们可以对情绪进行分类,如积极、消极或中性,以及通过正确的数据环境扩展到人类表现出的其他一些情绪,以执行这样的项目。
除了这四个主要应用之外,还有许多更自然的语言处理应用,其中可以利用 BERT 模型来实现期望的结果。这个领域非常广阔,有各种各样的任务和项目要做。我建议观众查看更多的应用程序,并尝试本文中已经提到的应用程序。
结论:
Photo by Alfons Morales / Unsplash
随着社交媒体的急剧崛起,人工智能的力量,特别是在深度学习的分支中,正处于历史最高水平。这些深度学习模型产生和计算解决 NLP 问题的有效解决方案的要求,对于公司改进其产品至关重要。虽然在深度学习的早期,使用简单的 LSTM 架构很难对抗和处理与 NLP 相关的任务,但随着 transformer 网络的引入,在 2017 年和 2018 年期间出现了一个革命性的时期。在这个时代,建立了多种语言模型来产生高效的结果,包括 BERT 模型。
在本文中,在继续解释来自变压器(BERT) 的双向编码器表示的工作背后的工作概念之前,我们简要介绍了具有深度学习的 NLP 的历史。我们检查了它们功能的详细机制,然后继续查看这些模型可以执行得最好的自然语言处理任务的类型。在对 BERT 架构有了基本了解之后,我们在 TensorFlow-Hub 的帮助下构建了一个 BERT 模型来执行文本分类的任务,产生了非常好的结果。最后,我们分析了现代工业用来实现高质量结果的 BERT 的一些最重要的应用。
在以后的文章中,我们将看到一些更有效的方法,用于解决与深度学习的自然语言处理相关的问题和项目。我们还将着眼于其他基于循环 gan 的生成性对抗网络的项目,并在第 2 部分从头开始更深入地研究神经网络的构造。在那之前,享受学习和编程 AI 项目吧!
你可以用这篇博文中的所有代码创建一个渐变笔记本,方法是将这个 URL 粘贴到渐变笔记本创建页面的高级设置部分的“Workplace URL”字段中。
BERT:用于语言理解的深度双向转换器的预训练
介绍
语言模型预训练已被证明在增强若干自然语言处理任务中是有效的。自然语言推理和解释是句子级任务的例子,而标记级任务,如命名实体识别和问题回答,需要模型来提供标记级的细粒度输出。基于特征和微调是将预先训练的语言表示应用于下游任务的两种方法。
像生成式预训练转换器这样的微调方法引入了最少的特定于任务的参数,并且通过简单地微调所有预训练参数来对下游任务进行训练。这两种技术都在预训练期间使用单向语言模型来学习一般的语言表示。
通往伯特的路
我们认为,当前的策略限制了微调方法的先前训练表示的效力。标准语言模型是单向的,这限制了可以在预训练中使用的体系结构,这是一个很大的限制。由于在 OpenAI GPT 中使用了从左到右的设计,每个令牌只能关注在转换器的自我关注层中位于它之前的令牌。
这些限制对于句子级别的任务来说并不理想,当用于令牌级别的任务时,例如回答问题时,它们可能特别有害,在这种情况下,在两个方向上整合上下文是至关重要的。这项研究提出通过引入 BERT 来改进基于微调的技术:来自变压器的双向编码器表示。
受完形填空任务启发的 MLM 预训练目标缓解了上述单向性限制。屏蔽语言模型的目标是在屏蔽了输入中的特定标记之后,仅从其上下文来估计屏蔽单词的原始词汇 id。可以使用 MLM 目标而不是传统的从左到右语言模型预训练方法来训练深度双向变换器。除了屏蔽语言模型之外,我们还利用一个被称为“下一句预测”的任务来预训练文本对表示。
论文的贡献
- 根据这项研究,双向预训练对于发展准确的语言表达至关重要。BERT 采用屏蔽语言模型来允许预先训练的深度双向表示。它对比了预训练的单向语言模型和独立训练的从左到右和从右到左 LMs 的浅层连接。
- 作者证明了预先训练的表示减少了对大量大量设计的特定于任务的架构的需求。有多种特定于任务的体系结构是 BERT 胜过的,因为它是第一个使用微调来在广泛的句子和标记级任务上获得当前最佳性能的表示模型。BERT 改进了 11 个 NLP 任务。代码和预训练模型可在 https://github.com/google-research/bert.获得
历史:最广泛使用的预训练一般语言表征的方法
无监督的基于特征的方法
几十年来,学习广泛适用的单词表示一直是研究的焦点,并且该研究领域包括非神经和神经方法。与从零开始学习的嵌入相比,预训练的单词嵌入提供了相当大的收益,因此是当今自然语言处理(NLP)系统的重要组成部分。为了预训练单词嵌入向量,已经使用了从左到右语言建模目标和目的,其在左和右上下文中区分正确的和错误的单词。这些方法已经扩展到更粗的粒度,例如句子嵌入或段落嵌入。
无监督微调方法
以这种方式工作的第一种方法仅使用从未标记的文本中获得的预训练的单词嵌入参数,并且这种方法的操作类似于基于特征的技术。生成上下文标记表示的句子或文档编码器已经使用未标记的文本进行了预训练,并针对监督的下游任务进行了微调。
从监督数据中转移学习
该研究证明了使用大数据集的监督任务的成功迁移,如自然语言推理和机器翻译。计算机视觉的研究也证明了从大型预训练模型进行迁移学习的价值。一个被证明是有益的策略是微调已经使用 ImageNet 预先训练的模型。
伯特
在我们的框架中,有两个步骤:预训练步骤和微调步骤。在完成各种预训练任务的同时,使用跨各种预训练任务的未标记数据来训练该模型。在开始微调过程之前,用预先训练的参数初始化 BERT 模型。接下来,使用来自下游任务的标记数据对每个参数进行微调。
尽管它们都是用相同的预训练参数启动的,但每个下游任务的微调模型都是互不相同的。
模型架构
- BERT 的模型架构基于原始实现,是一个多层双向变换器编码器。
- 由于变压器的使用已经很普遍,并且实现几乎与最初的相似,所以本文没有提供模型架构的完整背景解释。相反,作者将读者引向优秀的指南,如“带注释的变压器”
- 为了这项研究,作者将把层数(也称为变形块)称为 L,把隐藏大小称为 H,把自我注意头数称为 a。
- 在他们的大部分报告中,他们关注的是两种不同模型尺寸的结果:伯特基地 (L=12,H=768,A=12,总参数=110M)和伯特大 (L=24,H=1024,A=16,总参数=340M)。
- 为了进行这种比较,选择了 BERTBASE ,因为它的模型大小相当于 GPT 的 OpenAI。值得注意的是,伯特变换器使用双向自我关注,而 GPT 变换器依赖于受约束的自我关注,其中每个令牌可能只关注其左侧的上下文。
输入/输出表示
我们的输入表示可以在一个令牌序列中清楚地表达单个句子和一对句子(例如,问题、答案),以支持广泛的下游任务。在整篇文章中,“句子”可能是指任意一段连续的文本,而不是语言学上的句子。
术语“序列”是指发送给 BERT 的输入令牌序列,它可能由一个句子或两个打包在一起的句子组成。
- 如论文中所述,使用了 30,000 个令牌的单词块嵌入。
- 在每个序列中,初始标记总是一个特殊的分类标记(【CLS】)。令牌的最终隐藏状态充当分类任务的聚合序列表示。
- 区分句子有两种方法。使用一个特定的标记来分隔它们( SEP )。第二步是给每个标记添加一个学习嵌入,指示它是属于句子 A 还是句子 b。
- 作者将输入嵌入表示为 E,将特殊的 CLS 令牌的最终隐藏向量表示为 C,将第Ith输入令牌的最终隐藏向量表示为 T i 。
- 对于给定的标记,其输入表示是通过将相应的标记、段和位置嵌入相加而构建的(见下图)。
BERT input representation. The input embeddings are the sum of the token embeddings, the segmenta-tion embeddings and the position embeddings
预训练伯特
使用两个无人监督任务对 BERT 进行预训练:
屏蔽 LM
通过屏蔽输入符号的一部分来训练深度双向表示非常简单。掩蔽 LM (MLM)是用来描述这一过程的术语,尽管“完形填空”这一术语在文献中更常见(泰勒,1953)。如同在典型的 LM 中一样,输出 softmax 被提供有对应于掩码标记的最终隐藏向量。研究人员在每个序列中随机屏蔽 15%的单词块标记。
缺点和解决方案
即使我们可以获得双向预训练模型,在微调期间也不会出现掩码标记,这在预训练和微调之间产生了差异。为了避免这一点,我们并不总是使用实际的掩码令牌来替换“被屏蔽”的单词。训练数据生成器随机选择 15%的标记位置进行预测。无论何时选择了第 I 个令牌,我们都会用三个选项中的一个来替换它:
- 掩码令牌(80%的时间)
- 随机令牌(10%)
- 未改变的第个令牌(10%)
为了预测原始令牌, Ti 将与交叉熵损失一起使用。
下一句预测(NSP)
问答(QA)和 NLI(自然语言推理)是基本的下游任务,依赖于理解两个句子之间的关系,这不能通过语言建模立即表示。
在本文中,对二元下一句预测挑战进行了预训练,该挑战可以从任何单语语料库中容易地产生,以建立理解句子关系的模型。特别是预训练示例,从语料库中选择 50%的时间跟随 A 的下一个句子(标记为 IsNext),从语料库中随机选择 50%的时间(标记为 NotNext)。上图展示了 C 语言在下一句预测(NSP)中的用法。
预训练数据
在很大程度上,预训练技术遵循已经发表的关于语言模型预训练的研究。研究人员利用图书语料库(8 亿字)和维基百科(英文)作为预训练语料库(2500 万字)。为维基百科提取文本段落,它们不包括列表、表格和标题。
微调伯特
BERT 在 Transformer 中的自我关注机制使微调变得简单,因为它可以对广泛的下游任务进行建模,无论这些任务需要单个文本还是一对文本。在对文本配对应用双向交叉注意之前,通常的做法是独立地对文本对进行编码。相反,BERT 采用自我注意机制来组合这两个步骤,因为编码具有自我注意的连接文本对有效地合并了两个句子之间的双向交叉注意。
对于每个任务,我们只需将特定于任务的输入和输出插入到 BERT 中,并端到端地微调所有参数。
预训练句子 A 和 B 类似于:
- 意译中的句子对,
- 蕴涵中的假设前提对,
- 回答问题中的问题-段落对,以及
- 文本分类或序列标记中的退化文本对。
在输出端,令牌表示被馈送到输出层用于令牌级任务,例如序列标记或问题回答,而 CLS 表示被馈送到输出层用于分类,例如蕴涵或情感分析。
与预训练相比,微调是非常经济的。使用单个云 TPU 复制论文的结果可以在不到一个小时内完成(或者在 GPU 上几个小时)。
实验
这里,我们展示了 BERT 在 11 个 NLP 任务上的微调结果。
胶
被称为通用语言理解评估 (GLUE)的基准是各种自然语言理解任务的汇编。
- 为了对 GLUE 进行微调,研究人员首先表示输入序列;然后,他们使用对应于第一个输入标记( CLS )的最终隐藏向量 C 作为集合表示。
微调过程中引入的唯一新参数是分类图层权重 R^K×H,其中 k 是标注的数量。我们用 c 和 w 计算标准分类损失,即 log(softmax(CW^T)).
- 对于所有粘合任务,他们采用 32 的批量大小,并对数据的三个时期进行微调。为开发集中的每个任务挑选最佳微调学习率(在 5e-5、4e-5、3e-5 和 2e-5 之间)。
- BERTLARGE 微调在小数据集上不稳定,所以他们进行了多次随机重启,并在开发集上选择了最佳模型。随机重启采用相同的预训练检查点,但执行各种微调数据洗牌和分类器层初始化。结果如下所示。
- 与之前的最先进水平相比,伯特 BASE 和伯特大型实现的精度增益之间存在显著差距(分别为 4.5%和 7.0%)。
- 伯特 BASE 和 OpenAI GPT 的一个显著区别是注意力掩蔽;他们的模型架构的其余部分本质上是相似的。对于最重要和最常见的胶合任务 MNLI,BERT 将绝对精度提高了 4.6%。伯特拉杰在 GLUE 官方排行榜 10 上排名高于开放联盟 GPT ,得分 80.5。
- 伯特大的表现明显优于伯特基地,尤其是在需要少量训练数据的任务中。
GLUE Test results, scored by the evaluation server (https://gluebenchmark.com/leaderboard).
班 v1.1
斯坦福问答数据集(SQuAD v1.1)是 100k 众包问答对的集合。在回答问题任务中,研究人员使用 A 和 B 嵌入将输入的问题和段落表示为单个压缩序列。
在微调过程中,他们添加了一个起始向量 S 和一个结束向量 E 。使用 Ti 和 S 之间的点积以及段落中所有单词的软最大值来确定单词 i 成为答案范围开始的概率:
从位置 I 移动到位置 j 的候选跨度的分数计算如下:
我们表现最佳的系统在组合方面比排行榜系统高出 1.5 F1,在单一系统方面高出 1.3 F1。事实上,我们的单个 BERT 模型在 F1 分数方面优于顶级集成系统。
SQuAD 1.1 results.The BERT ensembleis 7x systems which use different pre-training check-points and fine-tuning seeds.
小队 2.0 版
小队 2.0 任务扩展了小队 1.1 问题的规范,允许给定段落不包括任何简短答案的可能性,使问题更加现实。为了实现这一目标,研究人员使用一种简单的策略来扩展 SQuAD v1.1 BERT 模型。他们认为没有答案的查询的答案跨度在 CLS 标记处开始和结束。
开始和结束回答跨度位置的概率空间已经扩展到包括 CLS 记号的位置。以下是与之前排行榜条目和不包括使用 BERT 的系统的顶级已发布作品的结果比较。可以观察到比之前最好的系统提高了+5.1 F1。
SQuAD 2.0 results. We exclude entries thatuse BERT as one of their components.
赃物
在评估基础常识的对立世代(SWAG)数据集的场景中有 113,000 个句子对完成示例。目标是从一组四个选项中选择最合理的延续。
通过创建四个输入序列来微调 SWAG 数据集是可行的,每个输入序列包含所提供的句子(句子 A)的潜在延续(句子 B)。
唯一引入的特定于任务的参数是向量的形式。这个向量与 CLS 符号表示 C 的点积表示每个可能选项的分数,然后使用 softmax 层将这个分数归一化。
我们用 2e-5 的学习速率和 16 的批量大小对模型进行了三个时期的微调。结果如下图所示。伯特拉格比作者的基准 ELMo 埃尔莫系统高出 27.1%,比开放的 GPT 高出 8.3%。"
消融研究
在本节中,作者对 BERT 的各种不同方面进行了消融实验,以便我们能够更好地理解这些方面的相对重要性。
培训前任务的效果
作者通过使用与 BERTBASE 相同的预训练数据、微调技术和超参数来评估两个预训练目标,强调了 BERT 深度双向性的重要性:
- 无 NSP: 双向模型,使用“掩码 LM”(MLM)进行训练,但不包括“下一句话预测”(NSP)任务。这种模式被称为“没有 NSP”
- LTR &没有 NSP: 不是使用 MLM,而是使用典型的从左到右(LTR) LM 来训练只向左模型。微调也使用仅左侧限制,因为消除它会导致预训练/微调不匹配,从而降低性能。同样值得注意的是,模型的预训练中忽略了 NSP 任务。
- LTR 模型在每项任务上的表现都比 MLM 模型差,MRPC 和 SQuAD 的下降尤为明显。
- 因为 SQuAD 中的令牌级隐藏状态不包括任何右侧上下文,所以很明显,在进行令牌预测时, LTR 模型的表现不会很好。在 LTR 系统之上,我们添加了一个随机初始化的 BiLSTM,这样我们就可以诚实地努力提高它的强度。尽管这极大地增强了 SQuAD 上的结果,但结果的质量仍然比预训练双向模型产生的结果低得多。BiLSTM 损害了粘合任务的性能。
Ablation over the pre-training tasks using theBERTBASEarchitecture. “No NSP” is trained withoutthe next sentence prediction task. “LTR & No NSP” istrained as a left-to-right LM without the next sentenceprediction, like OpenAI GPT. “+ BiLSTM” adds a ran-domly initialized BiLSTM on top of the “LTR + NoNSP” model during fine-tuning
模型尺寸的影响
研究人员训练了一些具有不同层数、隐藏单元和注意头的 BERT 模型,同时使用相同的超参数和训练程序。
- 下图显示了一些不同粘合任务的结果。在此表中,我们提供了 5 次随机重新启动微调后的平均偏差设置精度。所有四个数据集都显示,较大的模型导致了准确性的一致提高,即使对于 MRPC,其仅包括 3600 个标记的训练样本,并且与预训练任务显著不同。
- 人们早就认识到,扩大模型规模将导致机器翻译和语言建模等大规模任务的不断进步,如下图中保留的训练数据的 LM 困惑所示。
- 然而,作者认为这是第一次令人信服地表明,给定一个适当的预训练模型,扩展到极端模型大小也导致小规模任务的可观收益。
Ablation over BERT model size.#L = thenumber of layers; #H = hidden size; #A = number of at-tention heads. “LM (ppl)” is the masked LM perplexityof held-out training data.
基于特征的 BERT 方法
到目前为止提供的所有 BERT 结果都使用了微调方法。在该方法中,将基本分类层添加到预训练的模型,然后在下游任务上联合微调所有参数。在基于特征的技术中,固定特征取自预先训练的模型,这种技术提供了一些好处:
- 因为不是每个任务都可以用 Transformer 编码器架构来表示,所以必须引入一个特定于任务的模型。
- 其次,预先计算一次训练数据的高成本表示,然后在该表示的基础上用较便宜的模型运行许多测试,这在计算上有显著的优势。
- 为了避免微调方法,研究人员通过从一个或多个层提取激活来使用基于特征的技术。在分类层之前,这些上下文嵌入被馈入一个 768 维的具有随机初始化的两层结构的 BiLSTM。
- 结果如下图所示。 BERTLARGE 利用尖端技术提供竞争结果。连接预训练的 Transformer 的令牌表示的顶部四个隐藏层是性能最好的技术,它仅比微调整个模型落后 0.3 F1。关于微调和基于特性的过程,BERT 是一个极好的选择。
CoNLL-2003 Named Entity Recognition re-sults.Hyperparameters were selected using the Devset. The reported Dev and Test scores are averaged over 5 random restarts using those hyperparameters.
基于 BERT 的情感文本分类
在本教程的上下文中,我们将深入讨论如何实现与文本分类相关的 BERT 基本模型。我们将看到这种先进的 Transformer 模型如何针对大型数据集实现令人难以置信的高性能指标。
运行时检查可用 GPU 和 RAM 分配的命令
gpu_info = !nvidia-smi
gpu_info = '\n'.join(gpu_info)
if gpu_info.find('failed') >= 0:
print('Select the Runtime > "Change runtime type" menu to enable a GPU accelerator, ')
print('and then re-execute this cell.')
else:
print(gpu_info)
安装所需的库
- 拥抱脸库(Transformers)的 transformer 包包含预先训练的语言模型。
- ktrain 是深度学习库 TensorFlow Keras(和其他库)的轻量级包装器,它旨在帮助构建、训练和部署神经网络和其他机器学习模型。
!pip install ktrain
!pip install transformers
!pip install datasets
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import ktrain
from ktrain import text
import tensorflow as tf
from sklearn.model_selection import train_test_split
from datasets import list_datasets
from datasets import load_dataset
from sklearn.metrics import classification_report, confusion_matrix
import timeit
import warnings
pd.set_option('display.max_columns', None)
warnings.simplefilter(action="ignore")
数据集加载
## Train and validation data
emotion_t = load_dataset('emotion', split='train')
emotion_v = load_dataset('emotion', split='validation')
print("\nTrain Dataset Features for Emotion: \n", emotion_t.features)
print("\nValidation Dataset Features for Emotion: \n", emotion_v.features)
## dataframe
emotion_t_df = pd.DataFrame(data=emotion_t)
emotion_v_df = pd.DataFrame(data=emotion_v)
label_names = ['sadness', 'joy', 'love', 'anger', 'fear', 'surprise']
训练和验证数据分割
X_train = emotion_t_df[:]["text"]
y_train = emotion_t_df[:]["label"]
X_test = emotion_v_df[:]["text"]
y_test = emotion_v_df[:]["label"]
print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)
实例化一个 BERT 实例
使用模型名称、最大令牌长度、将用于每个类别的标签以及批量大小来构建 BERT 的实例。
bert_transformer = text.Transformer('bert-base-uncased', maxlen=512, classes=label_names, batch_size=6)
执行数据预处理
在预处理之前,我们必须将拆分后的数据转换成列表。
bert_train = bert_transformer.preprocess_train(X_train.to_list(), y_train.to_list())
bert_val = bert_transformer.preprocess_test(X_test.to_list(), y_test.to_list())
在 Ktrain 学习者对象中编译 BERT
因为我们使用 ktrain 作为高级抽象包,所以我们需要将我们的模型包装在 k-train 学习器对象中,以便进一步计算。要使用 ktrain,我们需要做的就是将我们的模型和数据放在一个 ktrain 中。通过使用 get_learner 函数获得的学习者对象。
bert_model = bert_transformer.get_classifier()
bert_learner_ins = ktrain.get_learner(model=bert_model,
train_data=bert_train,
val_data=bert_val,
batch_size=6)
最佳学习率
这一步是可选的,仅用于演示如何为任何变压器模型确定学习率。变压器模型的最佳学习率已在研究论文中确定和定义(在 5e-5、4e-5、3e-5 和 2e-5 之间)。方法 lr_find() 模拟训练寻找最优学习率。
rate_bert_start_time = timeit.default_timer() ## we mesure the execution time ##using the function timeit()
bert_learner_ins.lr_find(show_plot=True, max_epochs=3)
rate_bert_stop_time = timeit.default_timer()
print("\nTotal time in minutes on estimating optimal learning rate: \n", (rate_bert_stop_time - rate_bert_start_time)/60)
输出:
在情感数据集上微调 BERT
我们通过采用我们的情感数据集和我们建立的 BERT 模型并指定学习速率和要利用的时期的数量来开始微调过程。 fit_onecycle() 方法在训练周期的前半段将学习速率从基本速率提升至最大值,然后在后半段逐渐降低至接近零的值。 fit_onecycle() 采用1 周期策略
## Fine tuning BERT on Emotion Dataset
## we mesure the execution time using the function timeit()
bert_fine_tuning_start= timeit.default_timer()
bert_learner_ins.fit_onecycle(lr=2e-5, epochs=3) ##1cycle learning rate ##schedule for 3 epochs
bert_fine_tuning_stop = timeit.default_timer()
print("\nFine-Tuning time for BERT on Emotion dataset: \n", (bert_fine_tuning_stop - bert_fine_tuning_start)/60, " min")
注:读者可以自己运行那些操作并欣赏结果。
检查 BERT 性能指标
## Performation metrics of Bert model on emotion dataset
## we mesure the execution time using the function timeit
bert_validation_start= timeit.default_timer()
bert_learner_ins.validate()
bert_validation_stop= timeit.default_timer()
print("\nInference time for BERT on Emotion dataset: \n", (bert_validation_stop - bert_validation_start), " sec")
输出:
上述结果表明,我们已经获得了 94%的准确率。
检查每个标签的性能指标
## Here we can get the performance metric for each label
bert_learner_ins.validate(class_names=label_names)
输出:
保存伯特模型
bert_predictor = ktrain.get_predictor(bert_learner_ins.model, preproc=bert_transformer)
bert_predictor.get_classes()
使用 save() 方法将 Bert 模型保存到当前目录中
bert_predictor.save('/path/bert-pred-emotion')
伯特的一些变体
RoBERTa:一种鲁棒优化的 BERT 预训练方法
在这篇论文中,作者报告了一项关于 BERT 预训练的重复研究,其中包括对超参数调整和训练集大小的影响的全面分析。该论文表明 BERT 训练严重不足,并提供了 RoBERTa,一种更有效的训练 BERT 模型的方法,可以达到与任何后 BERT 技术相当或更好的性能。我们的调整非常简单,包括以下内容:
- 使用更多数据的更大批量对模型进行更长时间的定型。
- 消除了下一句预测目标。
- 接受更长序列的训练。
- 动态更改了用于训练数据的掩码模式。
静态屏蔽与动态屏蔽
BERT 使用随机令牌屏蔽和预测系统。
在第一个 BERT 实现中,在数据预处理期间仅执行一次屏蔽,从而产生静态屏蔽。作者针对动态掩蔽来评估这种方法,在动态掩蔽中,每次序列被馈送到模型中,都会生成一个新的掩蔽模式。这对于较大的数据集或在预训练过程中增加更多步骤是至关重要的。
模型输入格式和下一句预测
在原始的 BERT 预训练过程中,该模型从两个连接的文档段中获取信息,这两个文档段或者是从同一文档(p = 0.5)中连续采样的,或者是从不同的文档中采样的。
除了屏蔽的语言建模目标之外,辅助的下一个语义预测(NSP)损失用于训练模型,以确定观察到的文档片段是来自相同的还是不同的文档。
接下来,我们比较没有 NSP 损失的训练和具有来自单个文档的文本块(文档句子)的训练。我们发现这个设置优于最初发表的 BERTBASE 结果,并且消除 NSP 损失匹配或稍微改善下游任务性能
大批量培训
神经机器翻译领域的研究表明,随着学习率的适当增加,用非常大的小批量进行训练可以提高优化速度和最终任务性能。大批量训练被证明增加了掩蔽语言建模目标的复杂性和最终任务的准确性。
文本编码
字节对编码(Byte-Pair Encoding,BPE)能够容纳自然语言语料库中的大量词汇,是一种结合了字符级和单词级编码的代表性方法。BPE 使用子词单元而不是整个单词;这些单元通过对训练语料库的统计分析来检索。BPE 词汇表的大小通常在 10K-100K 子词单位之间。
基于 RoBERTa 的情感文本分类
我们必须遵循与 BERT 相同的过程,只是在这里,要实例化 RoBERTa,只需编写:
roberta_transformer = text.Transformer('roberta-base', maxlen=512, classes=class_label_names, batch_size=6)
蒸馏伯特,伯特的蒸馏版本:更小,更快,更便宜,更轻
通过这篇论文,研究人员提供了一种预训练紧凑的通用语言表示模型 DistilBERT 的方法,该模型可以进行微调,以在各种应用程序上实现出色的性能。他们在预训练阶段使用知识提炼,将 BERT 模型的规模缩小了 40%,同时保持了 97%的语言理解能力,并且速度提高了 60%。作者提供了一个三重损失,它集成了语言建模、蒸馏和余弦距离损失,以利用预训练期间大型模型学习到的归纳偏差。
知识蒸馏
知识蒸馏是蒸馏过程中采用的一种压缩方法。这种方法包括训练一个较小的模型(称为学生)来模仿一个较大的模型(称为教师)或一组模型的行为。学生被训练有超过教师软目标概率的蒸馏损失:
其中 ti (resp。 si) 是老师估计的概率(resp。学生)。这个目标通过充分利用教师
分布产生了丰富的培训信号。Softmax-temperature 用于此目的:
其中 T 确定输出分布有多平滑,并且 zi 是类 i 的模型分数。在训练期间,学生和老师的温度保持不变;然而,在推断期间, T 被设置为 1 以恢复典型的 softmax。
为了达到最终的训练目标,研究人员将提取损失与监督训练损失线性结合,这就是掩蔽语言建模损失。为了对齐学生和教师隐藏状态向量的方向,他们发现添加余弦嵌入损失( Lcos )是有益的。
蒸馏伯特:伯特的蒸馏版本
distill BERT 是 BERT 的学生,有类似的架构。去除了令牌类型嵌入和池化器,并且层的总数减少了 1/2。
研究表明,与层数等其他因素的变化相比,张量最后一个维度(隐藏大小维度)的变化对计算效率的影响较小(对于固定的参数预算),并且 Transformer 架构中使用的大多数操作(线性层和层归一化)在现代线性代数框架中得到高度优化。因此,我们集中精力减少 T4 的层数。
基于蒸馏词的情感文本分类
我们必须遵循与 BERT 相同的过程,只是在这里,要实例化 DistilBERT,只需编写:
distilbert_transformer = text.Transformer('distilbert-base-uncased', maxlen=512, classes=class_label_names, batch_size=6)
ALBERT:一个用于语言表达自我监督学习的 LITE BERT
语言表征学习领域的成功可以追溯到整个网络预训练的引入。这些预先训练好的模型对于各种非平凡的 NLP 应用都是有益的,包括那些训练数据有限的应用。为中国初中和高中英语考试(RACE test)设计的阅读理解任务的机器改进是这些进步最显著的标志之一。这些进步表明,拥有规模可观的网络对于实现最佳性能至关重要。预先训练较大的模型,然后将其提炼为较小的模型以供实际使用,这是现在的标准程序。由于模型大小的重要性,研究人员询问:拥有更好的 NLP 模型和拥有更大的模型一样容易吗?
当前的硬件无法存储足够数量的数据,这限制了对这个问题的满意回答。我们今天使用的最先进的模型通常包括数亿甚至数十亿个参数,使得当研究人员试图缩放我们的模型时,很容易跨越这些限制。因为通信开销与模式中的参数数量成比例,所以分布式训练也可能大大降低训练速度。
模型并行化和智能内存管理是当前解决上述问题的方法。内存限制得到了解决,但通信开销却没有。为了解决这些问题,本文的作者设计了一种 Lite BERT(阿尔伯特)架构,它使用的参数比传统的 BERT 设计少得多。
还原技术
ALBERT 设计的核心是一个具有 GELU 非线性的变压器编码器,与 BERT 中的一样。遵循 BERT 符号建立的准则,作者使用符号“E”表示词汇嵌入的大小,“L”表示编码器层数,“H”表示隐藏大小。他们将前馈/滤波器大小设置为 4H,注意头的数量设置为 H/64。
ALBERT 使用的参数缩减技术消除了扩展预训练模型的两个主要障碍。
分解嵌入参数化
首先我们有一个因式分解的嵌入参数化:这里隐藏层的大小与词汇嵌入的大小无关,通过将大词汇嵌入矩阵分解成两个微小的矩阵来实现。这种分离使得在不显著增加词汇嵌入的参数大小的情况下增加隐藏大小变得更加容易。
单词块嵌入尺寸 E 与隐藏层尺寸 H 联系在一起,即 E ≡ H。这个决定看起来不是最佳的。从建模的角度来看,单词块嵌入意味着学习与上下文无关的表示,而隐藏层嵌入意味着学习与上下文相关的表示。
使用上下文作为学习上下文相关表示的信号是类 BERT 表示的亮点。因此,作者通过将单词块嵌入尺寸 e 与隐藏层尺寸 h 解耦,更有效地使用整体模型参数,如建模要求(H ≫ E)所规定的
在自然语言处理中,出于实用目的,大词汇量 V 通常是必要的。如果 E≡H,则嵌入矩阵的大小 V×E 与 H 成比例增长。因此,得到的模型可能有数十亿个参数。
首先,将独热向量投影到大小为 E 的嵌入空间,然后投影到大小为 H 的隐藏空间。使用这种分解,它们将嵌入参数从 O(V×H)减少到 O(V×E+E×H)。当 H≫E 出现以下情况时,该参数的减少是显著的
跨层参数共享
第二种技术是跨层参数共享。使用这种方法,参数的大小不会随着网络变深而增加。这两种方法都显著减少了 BERT 参数的数量,而不会严重影响性能,从而提高了参数效率。具有与 BERT-large 相同功能的 ALBERT 配置可以用少于 18 倍的参数训练快约 1.7 倍。
有多种方式来共享参数,例如,仅跨层共享前馈网络(FFN)参数,或者仅共享注意力参数。ALBERT 的默认决定是跨层共享所有参数。
参数缩减技术也用作一种正则化,这有助于稳定训练并有助于泛化。
句际连贯缺失
伯特使用了一种称为下一句话预测(NSP)的损失。NSP 损失基于二元分类预测两个片段是否在原始文本中连续出现。为了生成正例,我们从训练语料库中选择连续的片段;为了生成反例,我们耦合来自不同文档的片段;我们以相等的概率随机抽取正面和负面的例子。创建 NSP 目标是为了提高下游任务的性能,如自然语言推理,这需要对句子之间的关系进行推理。
然而,进一步的研究得出结论,NSP 的影响是不一致的,并选择取消它;这是由各种任务的下游任务性能的提高所支持的。通过为句序预测(SOP) 引入自我监督损失,ALBERT 的性能得到进一步增强。因为最初的 BERT 中提出的下一句预测(NSP)损失是无效的,所以 SOP 现在专注于增强句子之间的连贯性。
模型设置
与 BERT-large 相比,ALBERT-large 使用的参数少了约 1800 万个。在 H = 2048 的 ALBERT-xlarge 配置中只有 60M 个参数,在 H = 4096 的 ALBERT-xxlarge 配置中有 233M 个参数;这大约是 BERT-large 中 70%的参数。请注意,对于 ALBERT-xxlarge,我们主要报告 12 层网络
上的结果,因为 24 层网络(具有相同的配置)获得类似的结果,但计算成本更高。
The configurations of the main BERT and ALBERT models analyzed in the paper.
ALBERT-xxlarge 在计算上更昂贵,比 BERT-large 具有更少的参数和更好的结果。因此,下一步的关键是用像分散注意力这样的技术来提高艾伯特的训练和推理速度。
基于 ALBERT 的情感文本分类
我们必须遵循与 BERT 相同的过程,只是在这里,要实例化 ALBERT,只需编写:
albert_transformer = text.Transformer('albert-base-v1', maxlen=512, classes=class_label_names, batch_size=6)
结论
使用语言模型的迁移学习,研究人员表明预训练是许多语言理解系统的重要组成部分。由于这些发现,深度单向架构现在甚至可以用于低资源任务。研究人员已经将这些发现扩展到深度双向架构,这样一个单独的预训练模型就可以处理广泛的 NLP 任务。
虽然 BERT、Albert 和 Roberta 是三个最著名的变压器,但许多其他著名的变压器也达到了可比拟的最先进的性能。我们可以提到 XLNet ,BART,Mobile-BERT,还有很多其他的。
基于 Transformer-XL 模型,XLNet 是一个自回归语言模型,它利用置换语言建模来提供可与 Roberta 相媲美的最先进的结果。关于自然语言理解(NLU)任务,BART 是另一个与 Roberta 竞争良好的预训练模型。此外,BART 的与众不同之处在于它在自然语言生成(NLG)任务(如抽象摘要)方面表现出色。
参考
伯特:https://arxiv.org/pdf/1810.04805.pdf
艾伯特:https://arxiv.org/pdf/1909.11942.pdf
迪翁伯特:https://arxiv.org/pdf/1910.01108.pdf
罗伯塔:https://arxiv.org/pdf/1907.11692.pdf
https://towardsdatascience . com/everything-you-need-to-know-about-ALBERT-RoBERTa-and-DistilBERT-11a 74334 B2 da
https://sh-tsang . medium . com/review-BERT-pre
2021 年最佳 Google Colab 替代品
原文:https://blog.paperspace.com/best-google-colab-alternatives/
Jupyter 笔记本已经成为探索机器学习库和算法的首选标准。谈到云托管的笔记本电脑服务,现在有大量选项可供选择,因此我们决定整理一份当前最佳可用选项的列表。
除了可能难以在本地获得的强大计算资源(或者如果您尝试的话,可能会倾家荡产),云托管的 Jupyter 环境还具有云存储、模型培训和部署功能、版本控制等功能。通过照顾所有的硬件和后端配置,云托管环境还使用户能够专注于他们的工作,而没有任何混乱的安装、配置或硬件购买。
近年来,谷歌 Colab 已经成为云支持笔记本的热门选择。随着免费的 GPU 和存储链接到 Google Drive,ML 和数据科学社区的许多用户发现它是他们以 Google 为中心的网络存在的自然延伸。
这就引出了一个问题:
为什么我不应该使用 Google Colab?
尽管是一个受欢迎的选择,Colab 面临着几个问题,这是许多用户的交易破坏者。Google Colab 的一些缺点包括:
- 服务中断
- 低速存储器
- 未配置的环境
- 功能贫乏
Colab 用户最大的抱怨可能是实例可能会在会话过程中被关闭(“抢占”),如果您没有主动连接到您的笔记本,就会断开连接。这意味着您可能会丢失您的工作和任何培训进度——如果您碰巧关闭了您的选项卡,或者意外注销,也是如此。想象一下,等待您的模型训练几个小时,只是为了回来看到您的实例被关闭;或者想象一下,你必须让你的笔记本电脑开着 12 个小时,害怕它会进入睡眠模式并断开你的连接。另一方面,其他提供商将保证整个会话,并允许您从中断的地方重新开始,即使您在整个时间都没有连接。
Colab 的另一个缺点是存储速度极慢。当它需要摄取大量数据时,Colab 就会开始爬行。用户报告 Colab 反复超时,如果他们在一个目录中有太多的文件,或者无法读取带有模糊和难以描述的错误的文件。不幸的是,处理大数据集是大多数 ML 管道的一个非常标准的部分,因此 Colab 的存储速度慢足以让许多用户寻找替代的 Jupyter 主机。
尽管 Colab 可能满足一些爱好者的需求,但与其他提供商相比,Colab 没有为全面的数据科学/ML 工作流提供许多额外的功能。Colab 的特性本质上仅限于 Python 支持和在 Google Drive 上共享笔记本的能力,这两项都是相当标准的。例如,其他云托管笔记本提供商将支持版本控制,并与完整的 MLOps 管道轻松集成。
谷歌 Colab 替代品
选择托管 Jupyter 笔记本电脑服务时,您可能会考虑以下特性:
- 不间断服务
- 持久环境
- 储存;储备
- 附加功能
许多其他托管的 Jupyter 环境将在一个或所有这些方面胜过 Google Colab。这里列出了一些。
1.图纸空间梯度
Gradient 是一个端到端的 MLOps 平台,包括一个免费托管的 Jupyter 笔记本服务,有许多针对预配置环境和免费 GPU 和 CPU 的选项。
Gradient 简化了深度学习模型的开发、训练和部署。它由 web UI、CLI 和 SDK 组成。Gradient 的一大优点是,它为初学者和专业人士提供了有价值的功能,具有直观的 web UI 和极低的入门门槛。
与 Google Colab 相比,Gradient 的一些优势包括:
- 更快、更持久的存储(每次启动笔记本电脑时,无需重新安装库和重新上传文件!)
- 会话是有保证的,所以不会有实例在工作过程中关闭的风险。你也不需要一直保持联系;开始你的训练,退出,稍后回来,你的训练将会在你离开的地方继续。
- 预配置的容器和模板。您可以在预装了所有依赖项的不同流行环境之间进行选择(例如 PyTorch、TensorFlow 或 Data Science Stack),或者使用您自己的定制容器。还有一个 ML Showcase ,其中包括你可以(免费)派生并在你自己的账户上运行的示例项目
- 一个公共数据集存储库,包括安装在每个笔记本上的大量流行数据集,可供免费使用
- 能够根据需要轻松扩展,为相同环境添加更多存储和更高端的专用 GPU
- 完整 ML 管道的集成特性,例如一键式部署和版本控制
- 反应迅速、乐于助人的支持团队
2.卡格尔
Kaggle 是谷歌的另一款产品,功能与 Colab 相似。像 Colab 一样,Kaggle 提供免费的基于浏览器的 Jupyter 笔记本和 GPU。Kaggle 还预装了许多 Python 包,降低了一些用户的入门门槛。
另一方面,许多用户注意到 Kaggle 内核往往有点慢(尽管仍然比 Colab 快)。对于不喜欢与谷歌分享数据的用户来说,Kaggle 仍然是一个禁忌。
3.亚马逊 SageMaker
亚马逊 SageMaker 是另一个流行的端到端机器学习平台。从数据标签到进一步的培训和部署能力,SageMaker 有许多附加功能,一些用户发现它的高级功能是一个很大的优势。
也就是说,SageMaker 确实有一个不好的名声,那就是不直观,完全令人困惑,并且实现了格言“万事通,无所不能。”
4.FloydHub
FloyHub 有一个初级层,包括免费的 GPU 访问和基于云的深度学习项目 IDE。它们还提供持久存储。
用户对 FloydHub 的一个抱怨是,他们有一个独特的结构,需要花时间去适应,还有一个不直观的工作流程。
我应该使用哪个 Jupyter 笔记本服务?
我们建议从 Gradient 的免费社区笔记本功能开始。有了免费的 GPU 和 CPU、存储、不间断的服务、直观的 UI、 ML 项目模板等等,很难想象 Gradient 不适合的用例。
带着一个免费的 GPU 支持的 Jupyter 笔记本(免费账户,免费一切),从许多 ML 项目模板(并从你自己的账户免费运行它),或者查看常见问题。
Paperspace 上可用 GPU 机器的可解释基准测试
作为数据科学家、机器学习工程师和 ML/深度学习爱好者,我们许多人都有过尝试选择最佳平台来进行云计算的经历。获得更多的计算往往是必要的,尤其是现在,航运业当前的问题正在大幅提高已经很高的 GPU 价格。此外,即使可用性不是问题,购买 GPU 也不总是划算的。通常,使用 ML ops 平台来最经济高效地满足您的计算需求是最佳选择。
每个平台都有各种不同的 GPU 和 CPU 类型用于它们的远程机器,竞争对手之间也有一定程度的差异。这些图形处理器的范围可以从较老、较弱的 Maxwell 一代图形处理器到尖端的 Ampere 系列图形处理器。这些在功能方面非常大,每个网站上都附有一些有用的规格,如 GPU 内存。对于我们很多人来说,这就足够选机了。对许多其他人来说,这可能是开始遇到问题的地方。我们如何选择最适合我们需求的 GPU?
从 Paperspace 中选择 GPU 时,无论是核心还是渐变,对于没有领域知识的人来说,知道选择哪种 GPU 都是一个挑战。虽然很明显,更多的 GPU 内存和更高的价格可能意味着更好的机器,但选择不是最便宜或最贵的 GPU 是许多人会避免考虑的事情。这不符合成本效益,但深入硬件世界并真正理解选项之间的差异可能具有挑战性。
为了帮助这一过程,这篇博客文章将在三个计算机视觉相关的深度学习基准系列的背景下,分析可用 GPU 机器之间的功耗和成本差异。我们将从两个方面考虑这些基准:完成时间和最大批量。然后,我们将根据速度、功耗和成本提出一系列建议。
要知道的术语
- 吞吐量/带宽:衡量一项任务在一段时间内可以完成的次数。以每秒千兆字节为单位。
- GPU 内存:GPU 可用于处理数据的可用内存,以千兆字节为单位。
- 解决问题的时间:完成任务所需的时间。这可以是训练时间、生成时间等,以秒为单位。
- 最大批量输入大小:NN/model 在面临内存不足风险之前可以处理的每个批量的最大大小。
纸张空间机器事实:
| 国家政治保卫局。参见 OGPU | M4000 | P4000 | P5000 | P6000 | v100 至 16GB | v100 至 32GB | RTX4000 | RTX5000 | A4000 | A5000 | A6000 | A100 |
| 产生 | 麦克斯韦 | 帕 | 帕 | 帕 | 沃尔特河 | 沃尔特河 | 图灵 | 图灵 | 安培 | 安培 | 安培 | 安培 |
| | | | | | | | | | | | | |
| CUDA 核心 | One thousand six hundred and sixty-four | One thousand seven hundred and ninety-two | Two thousand five hundred and sixty | Three thousand eight hundred and forty | Five thousand one hundred and twenty | Five thousand one hundred and twenty | Two thousand three hundred and four | Three thousand and seventy-two | Six thousand one hundred and forty-four | Eight thousand one hundred and ninety-two | Ten thousand seven hundred and fifty-two | Six thousand nine hundred and twelve |
| GPU 内存(GB) | eight | eight | Sixteen | Twenty-four | Sixteen | Thirty-two | eight | Sixteen | Sixteen | Twenty-four | Forty-eight | Forty |
| 万亿次浮点运算中的单精度性能(SP FP32) | Two point six | Five point three | Nine | Twelve | Fourteen | Fourteen | Seven point one | Eleven point two | Nineteen point two | Twenty-seven point eight | Thirty-eight point seven | Nineteen point five |
| 内存带宽(GB/s) | One hundred and ninety-two | Two hundred and forty-three | Two hundred and eighty-eight | Four hundred and thirty-two | Nine hundred | Nine hundred | Four hundred and sixteen | Four hundred and forty-eight | Four hundred and forty-eight | Seven hundred and sixty-eight | Seven hundred and sixty-eight | One thousand five hundred and fifty-five |
| | | | | | | | | | | | | |
| vCPU | eight | eight | eight | eight | eight | eight | eight | eight | eight | eight | eight | Twelve |
| 记忆 | Thirty-two | Thirty-two | Thirty-two | Thirty-two | Thirty-two | Thirty-two | Thirty-two | Thirty-two | Forty-eight | Forty-eight | Forty-eight | Ninety-seven |
| 存储包括(GB) | Fifty | Fifty | Fifty | Fifty | Fifty | Fifty | Fifty | Fifty | Fifty | Fifty | Fifty | Fifty |
| 每小时价格 | $0.45 | $0.51 | $0.78 | $1.10 | $2.30 | $2.30 | $0.56 | $0.82 | $0.76 | $1.38 | $1.89 | $3.09 |
| 每月价格(仅限使用,无订阅) | $0 | $0 | $0 | $0 | $0 | $0 | $0 | $0 | $0 | $0 | $0 | $0 |
| | | | | | | | | | | | | |
| 此实例所需的订阅价格 | $0.00 | $8.00 | $8.00 | $8.00 | $39.00 | $39.00 | $8.00 | $8.00 | $8.00 | $39.00 | $39.00 | $39.00 |
| 每月价格使用和订阅 | $0 | $8 | $8 | $8 | $39 | $39 | $8 | $8 | $8 | $39 | $39 | $39 |
| | | | | | | | | | | | | |
| 可供选择 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
| 每 GB/分钟的费用(吞吐量) | $0.14 | $0.13 | $0.16 | $0.15 | $0.15 | $0.15 | $0.08 | $0.11 | $0.10 | $0.11 | $0.15 | $0.12 |
| 每 100 个 CUDA 核心的费用(小时) | $0.03 | $0.03 | $0.03 | $0.03 | $0.04 | $0.04 | $0.02 | $0.03 | $0.01 | $0.02 | $0.02 | $0.04 |
| 每内存美元(GB) | $0.06 | $0.06 | $0.05 | $0.05 | $0.14 | $0.14 | $0.07 | $0.05 | $0.05 | $0.06 | $0.04 | $0.08 |
| 每万亿次浮点运算美元 | $0.17 | $0.10 | $0.09 | $0.09 | $0.16 | $0.16 | $0.08 | $0.07 | $0.04 | $0.05 | $0.05 | $0.16 |
*注意:M4000 实际上在坡度上总是自由的。我们使用核心定价。这是为了比较。P5000 和 RTX5000 还可以免费选择 Pro 计划。
让我们来看看我们已经知道的关于 Paperspace GPUs 的相关数据。上表中特别包括 GPU 内存、内存带宽和每月价格值。这些将是我们用来帮助计算我们在成本方面的基准比较。您应该始终根据这些因素来选择您的 GPU,因为它们间接转化为运行模型一整月、每天 24 小时或总共 720 小时的功耗、速度和成本效益。
我们包含了底部 4 行的热图。绿色单元格表示基于这些指标的更具成本效益的选项,红色单元格表示更昂贵的选项。让我们先来看看我们在这种背景下的 GPU 选项。
虽然 A100 在效率方面比大约一半的替代品都贵,但在 GPU 内存方面,它总是很有吸引力。就计算能力而言,这可能是过度的,成本也很高(尽管比其他选项如 AWS 便宜),但由于其巨大的吞吐量,它将永远是最快的。其他人可能会默认使用免费的 M4000,利用 Gradient 上所有用户都可以访问的免费笔记本类型。但是,如果您运行的是生产 ML,那么选择使用 M4000 将永远失去潜在的效率。其他付费选项可以更快地完成同样的任务,这通常是值得的。基于对这些极端选项的观察,选择最贵或最便宜的选项总是很诱人的,但它不是有效的。
相反,本基准和 GPU 规格指南旨在帮助您为渐变任务选择最佳选项。例如,考虑使用 RTX4000 机器,而不是 M4000。升级到增长计划将使 RTX4000 完全免费使用,每月只需 8 美元,实际上是以相对较低的成本对所有任务进行一对一升级。如果您需要更多的功率,安培系列 GPU,如 A4000,A5000 和 A6000 可能是最具成本效益的选择。
我们希望任何阅读本文的人能够更好地了解他们的需求,然后引导他们选择适合其任务的机器类型。带着这些想法,让我们看看基准测试,看看每种机器类型在实践中的表现。
基准测试的假设和注释:
下面是我们在进行基准测试时所做的假设和注释列表,以及一些您需要记住的内容。考虑每一个问题,以及这可能如何影响我们对结果的解释,以便全面理解基准推理。
- 模型基准将在完成任务时达到足够令人满意的质量,不值得报告任何类型的评估度量。模型功效与测量训练时间无关,因为每个 GPU 仍将经历相同的任务,它与推理时间无关,因为我们将使用的预训练 YOLOR 模型已经被评估为有效。此外,其中一个基准有目的地减少了纪元的数量,以至于在任何其他实践中都没有用。
- 所有其他任务(其他笔记本内核)将从控制台中清除,因此其他进程不会占用额外的内存。
- 我们使用的数据是为这项任务优化的。不太优化的数据集可能会给深度学习模型的训练和推理的时间方面带来不同的影响。
- M4000 只能在坡道上免费通行。我们使用基于 Paperspace 核心定价的近似样本价格。
- 所有 A4000、A5000 和 A6000 实例都有 48.3 GB 的 CPU 内存,所有 A100 实例都有 96.6 GB 的 CPU 内存。所有其他实例都有 32.2 GB 的 CPU 内存。
- 在 GPU 名称中,前面的字母 out 代表 GPU 一代。这些就是麦克斯韦,帕斯卡,伏打,图灵和安培,按照他们最近被介绍的顺序排列。一般来说,与旧架构具有相似规格的新架构将优于旧架构。
- 所有基准都是在图纸空间梯度上完成的。
基准任务:
https://blog.paperspace.com/content/media/2022/04/movie.mp4
Before and after using YOLOR on the input clip
视频上的对象识别检测
YOLOR 是一种新的物体检测和识别算法。我们今天用它来预测一个 youtube 视频中的物体。由于对象识别和检测是计算量很大的任务,这些将直接向我们显示每个 GPU 能够多快地使用该模型进行预测,并从中我们可以对它们的性能进行比较推断。
推理参数:
cfg cfg/yolor_csp_x.cfg #config file
weights yolor_csp_x.pt
conf-thres 0.25 # object confidence threshold
img-size 1280 # img size to load in frames as
device 0 # our GPU
图像分类-高效网和微型图像网 200
Example photos and classifications from Tiny ImageNet 200 - Source
对于我们的下一个例子,我们将采用深度学习最常见的用例之一:图像分类。我们将在 tiny-imagenet-200 数据集上实现 efficient net(,它可以从公共数据集自由挂载到任何笔记本电脑上),并使用训练分类器所需的不同时间作为我们对效率以及它可以接收的最大批量的度量。这将作为我们深度学习模型的更传统的例子。
本测试基于本图像分类教程。
培训参数:
batch_size = 256
lr = 0.001 # Learning rate
num_epochs = 1 # Number of epochs
log_interval = 300 # Number of iterations before logging
loss_func = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)
图像生成- StyleGAN_XL 图像生成
HQ Sample generated images from the authors - Source
最后,我们将使用 StyleGAN_XL ,这是最近发布的 StyleGAN 框架的实现,旨在生成 1024 x 1024 分辨率的图像,以基准测试图像合成模型训练时间。由于图像合成是如此昂贵,我们把它作为一个很好的例子来说明不同的 GPU 如何执行计算量大的任务。此外,该任务与图像分类任务的不同之处还在于使用昂贵的高质量图像进行训练。
培训参数:
cfg=stylegan3-t #StyleGAN config
data=stylegan_xl/data/pokemon256.zip
gpus=1 #number of GPUs
batch=8 #batch size
kimg 5 # size of subset to process in each epoch
metrics none
基准测试结果:
吞吐量:
| 国家政治保卫局。参见 OGPU | M4000 | P4000 | P5000 | P6000 | RTX4000 | RTX5000 | V100 至 16GB | v 1100 至 32GB | A4000 | A5000 | A6000 | A100 |
| 内存带宽(GB/s) | One hundred and ninety-two | Two hundred and forty-three | Two hundred and eighty-eight | Four hundred and thirty-two | Four hundred and sixteen | Four hundred and forty-eight | Nine hundred | Nine hundred | Four hundred and forty-eight | Seven hundred and sixty-eight | Seven hundred and sixty-eight | One thousand five hundred and fifty-five |
| 每 GB/分钟的费用(吞吐量) | $0.14 | $0.13 | $0.16 | $0.15 | $0.08 | $0.11 | $0.15 | $0.15 | $0.10 | $0.11 | $0.15 | $0.12 |
最佳 GPU:吞吐量
吞吐量最好的 GPU:A100。当您需要快速处理大量数据时,请使用这款 GPU。
每 GB/分钟最佳 GPU 定价:RTX4000。当您需要在预算内快速处理大数据时,请使用这款 GPU。
预算最佳 GPU:RTX 4000。当节约成本是主要目标时,请使用这款 GPU
理由:
在进入基准测试任务之前,让我们先关注一下上面列出的一个度量:吞吐量。吞吐量或带宽对于了解我们的 GPU 执行任务的速度非常有用。从字面上看,它是对每秒可处理的 GB 数的度量,实际上也是对其处理速度的度量。这一指标不同于机器的总内存容量,因为某些架构比其他架构更能利用其增加的容量。
这有效地降低了这些选项的成本,例如,在吞吐量方面,与类似的 A4000 相比,使用 RTX5000 的功能更便宜。您可以使用吞吐量来估计不同 GPU 的成本效率,作为时间和机器租赁成本之间关系的函数。使用这些信息,我们可以更好地考虑来自同一代的较弱的 GPU 选项,这些选项在吞吐量上获得了相当的性能。当长期成本比速度更重要时,这一点尤其值得考虑,例如 RTX4000 和 A5000。也就是说,A100 实际上在吞吐量的成本效益方面比较好。
查看吞吐量让我们对如何比较不同的 GPU 产品有了一个抽象的想法,现在让我们来看看计算机视觉基准,看看上表中列出的不同值如何实际影响模型的完成时间,例如在训练和推理中。
完成时间:
| | M4000 | P4000 | P5000 | P6000 | RTX4000 | RTX5000 | V100 至 16GB | v 1100 至 32GB | A4000 | A5000 | A6000 | A100 |
| 黄色 | One hundred and twenty-eight point one eight | Sixty-eight point five seven | Sixty point nine eight | Thirty-five point two | Twenty-three point seven seven | Twenty point zero two | Nineteen point three four | Seventeen point zero four | Eighteen point six six | Fourteen point one one | Thirteen point eight eight | Twelve point four two |
| StyleGAN_XL (s) | Seven hundred and seventy-seven | Four hundred and nineteen | Two hundred and ninety-five | Two hundred and forty-nine | One hundred and twenty-eight | One hundred and seven | Eighty-five point six seven | One hundred and five point three three | One hundred and six point six seven | Eighty-three | Eighty-seven point three three | Eighty-eight point three three |
| 高效网络 | OOM* | OOM* | OOM* | One thousand one hundred | OOM* | Eight hundred and sixty-seven | Eight hundred and one | Six hundred and ninety | Nine hundred and twenty-five | Seven hundred and sixty-nine | Six hundred and twenty-seven | Five hundred and twenty-eight |
| 成本(每小时美元) | $0.45 | $0.51 | $0.78 | $1.10 | $0.56 | $0.82 | $2.30 | $2.30 | $0.76 | $1.38 | $1.89 | $3.09 |
| 单次运行成本** | $0.02 | $0.01 | $0.01 | $0.01 | $0.00 | $0.00 | $0.01 | $0.01 | $0.00 | $0.01 | $0.01 | $0.01 |
| StyleGAN_XL 单次运行成本** | $0.10 | $0.06 | $0.06 | $0.08 | $0.02 | $0.02 | $0.05 | $0.07 | $0.02 | $0.03 | $0.05 | $0.08 |
| 单次运行净成本** | 失败! | 失败! | 失败! | $0.34 | 失败! | $0.20 | $0.51 | $0.44 | $0.20 | $0.29 | $0.33 | $0.45 |
*OOM:内存不足。这表明由于内核中缺少内存资源,训练失败。
**单次运行成本:以秒为单位的时间转换为小时,然后乘以每小时的成本。反映任务在 GPU 上运行一次的成本。
完成时间是以秒为单位来衡量模型训练或(在 YOLOR 的情况下)做出推断所需的时间。我们选择这个指标作为我们的基准,因为它直观地有助于理解不同 GPU 产品之间的能力差异。例如,它可以让我们了解不同的值(如 GPU 内存和吞吐量)如何影响不同模型的训练时间。我们对每个任务使用 3 - 5 个记录的墙壁时间,以获得上面报告的平均墙壁时间。第一个记录的值不包括在内,因为需要运行的各种下载任务或设置任务导致这些条目明显慢于后续运行。
最佳 GPU:完成时间
从完成时间来看,最佳 GPU:A100。如果您想尽快完成任务,请使用 A1000。
作为运行成本函数的最佳 GPU:a 4000。使用 A4000 快速完成任务,同时节省成本。
预算最佳 GPU:RTX 5000。当成本是最重要的因素时,使用 RTX5000。
理由:
尤洛
我们最快的任务是,YOLOR 比较在 YouTube 短视频中识别物体的速度。正如您所看到的,这项任务揭示了不同 GPU 代的完成时间之间存在一些明显的差异。Maxwell 和 Pascal GPU 架构更老,效率更低。这些的低吞吐量值反映在它们较低的完成时间值上。例如,虽然 Turing RTX4000 与 Pascal P5000 具有相同数量的 GPU 内存,但由于其更高的吞吐量和更现代的架构,它的速度要快得多。另一方面,A4000 也有类似的 GPU RAM,但比 RTX4000 更快,因为它的新架构支持更高的吞吐量。然而,更高的 GPU 内存可以抵消这种影响,Volta V100 的表现优于更新但功能更弱的 RTX5000。最后,拥有最新架构、最高吞吐量值和 RAM 的 Ampere 系列 GPU 执行推理的速度最快。A100 总是最快的,尽管内存比 A6000 少。这可能是因为 A100 的吞吐量几乎是 A6000 的两倍。
StyleGAN_XL
StyleGAN_XL 涉及在 Pokemon 的图像集合上训练这个流行的 StyleGAN 框架的大型新实现。看上面的表格,我们可以看到一个类似的模式,与我们在 YOLOR 上完成的推理任务的结果相同。麦克斯韦和帕斯卡再次是慢得多的选项,然后是图灵 RTX GPU,V100s,最快的是安培系列。
这里有一些异常值,似乎没有反映在 GPU 规格中。在多个笔记本电脑中,A5000 优于 A6000 和 A100,V100 16GB 优于 V100 32GB。这可能只是这一特定培训任务的一个怪癖,所以我们不要太看重 V100 16GB 和 A5000 在这里的令人难以置信的性能。您可能仍然会发现 V100 32GB 和 A100 分别更有价值。尽管有这些异常值,这些发现进一步表明 A100 和其他安培系列 GPU 速度极快。Volta GPUs 的次佳选项要贵得多,因此在这项任务中选择 A5000 或 A6000 似乎是最具成本效益的。
效率网
高效网络分类任务是我们希望在输入大小方面推动 GPU 的地方。我们选择在这里的所有测试中使用相对较大的批量 64,以便了解每个 GPU 可以多快地处理这些相对较大的输入批量。虽然 tiny-imagenet-200 图像的分辨率相对较低,为 64 x 64,但它们仍然是 RGB 图像,处理起来很昂贵。正如您所看到的,这比其他两个任务花费的时间长得多,一些 GPU 在完成之前就耗尽了内存(M4000、P4000、P5000 和 RTX4000)。这是意料之中的,因为我们想提供一个例子,让我们将这些 GPU 的能力发挥到极限,并展示一些 Pascal 和 Maxwell 实例可能无法满足您的任务。
这个任务显示了与 YOLOR 任务相同的训练时间模式。Pascal 和 Maxwell 一代机器很难完成这个任务,P6000 虽然运行,但相对来说是最慢的。另一方面,RTX5000 继续保持其高性价比的趋势。虽然比在其他机器上运行的测试要慢,但是当时间不是主要的开销因素时,成本使它成为一个非常有吸引力的选择。到目前为止,Volta 和 Ampere 系列 GPU 的性能最快,而之前基准测试中的异常值 V100s 和 A5000 在这项任务中恢复了预期行为。
最大批处理输入大小(批处理大小:int):
| 国家政治保卫局。参见 OGPU | M4000 | P4000 | P5000 | P6000 | RTX4000 | RTX5000 | V100 至 16GB | v 1100 至 32GB | A4000 | A5000 | A6000 | A100 |
| 成本(每小时美元) | $0.45 | $0.51 | $0.78 | $1.10 | $0.56 | $0.82 | $2.30 | $2.30 | $0.76 | $1.38 | $1.89 | $3.09 |
| StyleGAN_XL(最大批量) | four | four | eight | eight | four | eight | eight | Sixteen | Sixteen | Sixteen | Thirty-two | Thirty-two |
| StyleGAN_XL(用于批处理项) | Zero point one one three | Zero point one two eight | Zero point zero nine eight | Zero point one three eight | Zero point one four | Zero point one zero three | Zero point two eight eight | Zero point one four four | Zero point zero four eight | Zero point zero eight six | Zero point zero five nine | Zero point zero nine seven |
| 效率网(最大批量) | Thirty-two | Thirty-two | Sixty-four | One hundred and twenty-eight | Thirty-two | Sixty-four | Sixty-four | One hundred and twenty-eight | Sixty-four | One hundred and twenty-eight | Two hundred and fifty-six | One hundred and twenty-eight |
| 效率净值(每批项目美元) | Zero point zero one four | Zero point zero one six | Zero point zero one two | Zero point zero zero nine | Zero point zero one eight | Zero point zero one three | Zero point zero three six | Zero point zero one eight | Zero point zero one two | Zero point zero one one | Zero point zero zero seven | Zero point zero two four |
测试输入的最大批量大小为我们提供了一个概念,即模型在每个 GPU 的每个更新阶段能够处理多少数据。在我们的例子中,最大批处理大小告诉我们在耗尽内存之前,模型每次迭代可以处理多少图像。因此,最大批量输入大小是在 GPU 无法再处理任务之前,该任务的代价有多高的间接衡量标准。
最佳 GPU:最大批量
处理大批量数据的最佳 GPU:a 6000。这款 GPU 实际上比 A100 拥有更多内存和 CUDA 内核,但吞吐量较低。如果我们需要处理更昂贵的任务,那么 A6000 是最好的选择。
批量项目成本最佳 GPU:a 6000。a 6000 也是两种训练任务中最具成本效益的。就每个项目的处理而言,A6000 具有最佳的成本/处理项目比率。P6000 也是一个不错的选择。
预算最佳 GPU:a 4000。对于 A4000 上的两项任务,每小时的总成本和每批项目的成本相对较低。A4000 的性能几乎与 A6000 一样好,而价格却不到 A6000 的一半,StyleGAN_XL 的性能表明它在成本效益方面可能比 a 6000 更好。
理由:
StyleGAN_XL:
StyleGAN_XL Max batch sizes
StyleGAN_XL 任务专门用于训练模型以生成高质量、1024 x 1024 分辨率的图像。因此,我们输入的图像质量(1280x1280)比 tiny-imagenet-200 (64 x 64)高得多,并且需要更多的 GPU 处理能力。这就是为什么批量比 EfficientNet 测试中的小得多。
不出所料,我们在上面的完成时间测试中看到了类似的模式。随着一代和 GPU 内存的增加,这些值也随之增加。由于 RAM 只有 8 GB,这就是 M4000、P4000 和 RTX4000 只能管理 4 个批处理的原因。内存较大的 Pascal 和 RTX GPU 性能稍好,但相比之下,Volta 和 Ampere GPUs 的大内存往往使它们的性能较低。另一方面,A6000 和 A100 的内存分别为 48gb 和 40 GB。这使他们能够处理我们可以为此任务收集的最大批量,32。
就成本而言,Ampere 系列 GPU 在处理每批项目的成本方面表现突出。这可能是每种 GPU 类型中高 GPU 内存、吞吐量和 CUDA 核心的交叉的直接结果。A6000 和 A4000 似乎是最划算的,每件 5 - 6 美分。根据这一指标,V100 16 GB 最终被显示为成本效益最低的产品,这可能是因为较旧的 Volta 架构在处理大批量数据方面不如 RTX 或安培 GPU,同时运行成本第二高,为每小时 2.30 美元。
效率网
Max batch sizes for EfficientNet
使用 tiny-imagenet-200 进行的 EfficientNet 测试非常适合显示批量大小,因为即使是较弱的 GPU 也可以毫无问题地处理相当数量的 64 x 64 图像。我们可以使用该测试更直观地了解不同测试条件下批次大小之间的差异程度。
在 EfficientNet 中,我们看到的与 StyleGAN XL 斗争的 3 个实例再次反映了它们的相对弱点。M4000、P4000 和 RTX4000 的内存都只有 8 GB,很难在这种低得多的分辨率下处理超过 32 幅图像。在它们之后,出现了一种基于 GPU 内存的新模式。更强大的 Pascal P5000 和 P6000 分别获得 64 和 128,显示了 RAM 的每次提升都有明显的跳跃。同样,RTX5000 GPU 最多可以处理 64 个批量。A6000 以 256 的成绩表现最好,P6000、32 GB V100、A5000 和 A100 以 128 的成绩并列第二。向上滚动到我们的事实表,我们可以看到这些 GPU 分别有 24、32、24 和 40 GB 的内存。这可能意味着使用 40 到 48 GB 的内存处理 256 个批处理成为可能。从所有这些,我们可以清楚地推断出最大批量大小和 GPU 内存之间的直接关系。
至于成本,V100 16 GB 的效率也很低。与 StyleGAN_XL 一样,它具有第二低的批量大小,同时每小时的成本较高,与其他选项相比,其费率不具吸引力。
第二差的选择是 A100,每小时的费用最高。我们不想删除这些数据,但这可能会产生误导。在这项任务中,A100 在处理大批量方面仍然是第二高效的。在此之后,RTX5000、V100 32GB、M4000、P4000 和 P5000 似乎代表了每项成本的平均水平。
根据成本与批量的比率,运行 EfficientNet 的最佳选择是剩下的三个安培 GPU 和 P6000。A4000 和 A5000 的性能很好,每处理一个项目的成本仅略高于 1 美分,但 P6000 和 A6000 的价格低于 1 美分。这直接反映了每个实例中相对较高的内存量(分别为 24 GB 和 48 GB ),以及运行它们相对较低的成本。P6000 的内存大小与 A5000 相同,但运行成本却高出约 80%。与此同时,A6000 的价格比 A100 贵 60%左右,而且可以在几乎相同的速度下处理更大的批量。基于此信息,如果您需要使用大批量,就项目文件大小或批量本身而言,请考虑使用 A6000 或 A5000。
结论性论点
根据我们上面检查的基准和规格,我们现在可以尝试形成一个完整的结论,即对于不同的任务,哪些 GPU 在纸张空间梯度上表现最好。这是通过使用 YOLOR、StyleGAN_XL 和 EfficientNet 的一系列推理和训练任务完成的。通过测量时间和批量大小,以及关于成本和 GPU 规格的现有信息,我们能够根据功耗、成本、速度和效率给出选择 GPU 的建议。
我们现在将为 4 种不同的情况推荐最佳的整体 GPU:当代码需要尽可能快地运行时,当需要处理尽可能大的输入时,以及当我们希望尽可能经济地运行任务时。这些不同的情况应该涵盖我们的用户可能遇到的各种各样的潜在情况。虽然这些结论都是基于我们的基准测试,但它们也反映在成本和 GPU 规格数据中。
最佳 GPU:总体推荐
当我们想尽可能快地运行一个模型,而不考虑成本时,那么 A100 就是我们的推荐。A100 的最高吞吐量为 1555 GB/s,是普通消费者可以使用的 GPU 技术的巅峰,并且总是能够足够快地完成工作,因此它的高价格不会成为问题。它还拥有第二高的可用 GPU 内存,40 GB,因此它可以承担几乎任何为单个 GPU 设计的任务。就整体性能而言,这无疑是 Paperspace 上可用的最佳 GPU。
当我们想要为模型训练的最大可能输入量做准备时,那么 A6000 是我们的推荐。凭借 48 GB 的 GPU RAM,A6000 可以处理大批量数据。这反映了其大容量的 GPU 内存和稳定的吞吐能力,当我们需要使用大批量进行训练时,当我们需要使用 GPU 内存访问大量其他变量、数据或模型时,或者手头有特别复杂的任务(如图像生成)时,我们应该始终选择 A6000。A6000 甚至比 A100 更适合使用,不考虑成本差异!
当我们希望尽可能便宜地运行我们的机器或深度学习代码时,那么我们的推荐是 A4000 。就成本效益而言,A4000 是 GPU 的最有效选择。它拥有最佳的吞吐量价格比、完成时间价格比和每批项目成本比,并且由于其 16 GB 的内存,它在处理大量数据时仍然非常强大。
注意:设置基准有一些奇怪的地方,这使得共享包含示例的笔记本变得困难。作者正在制作一个像样的版本,但你可以在这里访问渐变笔记本。
Paperspace 博客的 2021 年最佳
对我们大多数读者来说,2021 年无疑是有趣的一年。在疫情对日常生活的巨大影响和行业定义新论文和项目的持续推出之间,机器学习科学家和爱好者肯定发生了一些事情。为了帮助您记住所有这些令人兴奋的行业新事件,我们整理了 2021 年 Paperspace 博客中最受欢迎和最有用的博客帖子!
具有 Keras 的递归神经网络中的注意机制
本系列为不同的递归神经网络(rnn)提供了高级指南。它将让读者了解网络本身、它们的架构、它们的应用,以及如何使用 Keras 将模型变为现实。
如何在自定义数据集上训练 YOLO V5
本指南将向您展示如何使用 YOLO v5 为设置代码、为 YOLO 设置带有注记转换的数据集、为对象设置训练和推理
Adoro:一个 deepfake 应用程序,让你只用一张自拍就能表演意大利歌剧经典
来自 Gradient 的令人敬畏的 starter 工作流帮助您构建一个 web 应用程序,该应用程序可以将自拍转换为演唱意大利歌剧歌曲的深度伪造视频。本教程介绍了新的初学者工作流程,并允许任何专业用户启动一个迷人的一阶运动模型。
open ai 健身房入门:基本构建模块
在这篇文章中,Ayoosh 介绍了开放式人工智能健身房的基本构建模块。这包括环境、空间、包装器和矢量化环境。阅读这篇文章及其后续内容(见下文),通过 OpenAI Gym 快速提高你的工作效率。
open ai 健身房入门:创建定制健身房环境
这篇文章讲述了如何在 OpenAI Gym 中实现自定义环境。作为一个例子,您将看到如何实现一个自定义环境,包括驾驶一架直升机,同时避开半空中的障碍物。OpenAI 健身房入门第 2 部分。
使用带 TensorFlow 1.14 和 Keras 的 Mask R-CNN 进行目标检测
本教程包含对 Mask_RCNN 项目的概述。按照本指南学习使用 TensorFlow 1.14 进行对象检测,构建 Mask R-CNN 模型架构,在 TensorFlow 1.14 中训练 Mask R-CNN,并将其用于检测对象。
PyTorch 终极指南
对深度学习入门感兴趣?本指南向您展示了如何开始使用 PyTorch、张量以及使用 PyTorch 构建神经网络。
如何用 GFP-GAN 修复受损照片
按照这个教程,看看如何使用令人难以置信的 GFP-GAN 算法来升级和恢复受损照片的质量。
部署 Flask App 进行梯度深度学习
在这篇介绍使用 GFP-GAN 恢复照片的后续文章中,我们将查看如何使用渐变部署来创建一个 Flask API,从而充分利用该模型。
如何训练缩放的 YOLOv4 对象检测模型
在这篇博客中,我们展示了如何在您的自定义数据集上训练和概括 Scaled-YOLOv4 以检测自定义对象的示例。
使用渐变工作流和 GitHub 生成 NLP 文本
在本文中,我们展示了如何使用 GPT-2 运行渐变工作流来生成新颖的文本。
完整直观的迁移学习指南( Part 1 & Part 2 )
在这个系列中,我们涵盖了与迁移学习相关的大多数基本理论和概念。我们学习了卷积神经网络,它们如何用于迁移学习,并了解了如何微调这些模型。
拥抱脸的自然语言处理
在本文中,我们讨论了如何使用拥抱脸包进行情感分析、问答、命名实体识别、摘要、翻译和标记化。
我如何用 VQGAN-CLIP 制作这篇文章的封面照片
使用 PixRay 库套件使用渐变笔记本生成华丽像素作品的指南。
高级递归神经网络:双向递归神经网络
在本教程中,我们将涵盖双向 rnn:它们如何工作,网络架构,它们的应用,以及如何使用 Keras 实现双向 rnn。
具体来说,我们将涵盖:
- RNNs 概述
- LSTM 和 GRU 街区
- 双向遍历的需要
- 双向 RNNs
- 使用双向 RNN 的情感分析
- 结论
我们开始吧!
RNNs 概述
递归神经网络(RNNs)是一种专门用于处理顺序数据的神经网络。顺序数据可以被认为是一系列的数据点。例如,视频是连续的,因为它由一系列视频帧组成;音乐是连续的,因为它是一系列声音元素的组合;文本是连续的,因为它是由字母组合而成的。对顺序数据建模需要持久保存从以前的实例中学习到的数据。例如,如果你要预测辩论中的下一个论点,你必须考虑参与辩论的成员提出的前一个论点。你形成你的论点,使它符合辩论的流程。同样,RNN 学习和记忆数据,以便制定决策,这取决于以前的学习。
与典型的神经网络不同,RNN 不会将输入或输出作为一组固定大小的向量。它也没有固定训练一个模型所需的计算步骤的数量。相反,它允许我们用一系列向量(序列数据)来训练模型。
有趣的是,RNN 在整个网络中保持模型参数的持久性。它实现了参数共享,以适应不同长度的顺序数据。如果我们要为不同的数据块考虑单独的参数,那么既不可能概括整个系列的数据值,也不可能在计算上可行。概括是关于一系列值的重复。歌曲中的音符可以出现在其他地方;这需要由 RNN 捕获,以便了解数据中持久存在的依赖性。因此,RNN 不是在每个学习点都从头开始,而是将学到的信息传递给下面的层次。
为了实现参数共享和信息持久化,RNN 使用了循环。
Unfolding An RNN (Source)
一个神经网络\(A\)被重复多次,其中每个组块接受一个输入\(x_i\)并给出一个输出\(h_t\)。这里的循环将信息从一个步骤传递到另一个步骤。
事实上,大量的应用,如文本生成、图像字幕、语音识别等,都在使用 RNNs 及其变体网络。
LSTM 和 GRU 街区
并不是所有的场景都包括从序列中紧接在前面的数据中学习。考虑这样一种情况,你试图从一本书或一篇文章中介绍的另一个句子中预测一个句子。这不仅需要记住之前的数据,还需要记住更早的数据。由于参数共享机制,RNN 在每个时间步长使用相同的权重。因此,在反向传播过程中,梯度要么爆炸,要么消失;网络从远离当前位置的数据中学习不到太多。
为了解决这个问题,我们使用长短期记忆网络,简称 LSTMs。LSTM 能够学习长期依赖关系。
不像在 RNN 中,网络块中有一个简单的层,LSTM 块做一些额外的操作。通过使用输入、输出和遗忘门,它记住了重要的信息,而忘记了通过网络学习到的不必要的信息。
LSTM 的一个流行变体是门控循环单元,或 GRU,它有两个门——更新门和复位门。LSTM 和 GRU 都致力于消除长期依赖问题;区别在于操作的次数和消耗的时间。GRU 是新的,速度更快,计算成本低。然而,LSTMs 在解决许多应用时已经输出了最先进的结果。
LSTM and GRU (Source: Illustrated Guide)
要了解 LSTMs 与 GRUs 有何不同,您可以参考这篇文章。
双向遍历的需要
RNN 的典型国家(简单的 RNN、GRU 或 LSTM)依赖于过去和现在的事件。时间\(t\)处的状态取决于状态\(x_1、x_2、…、x_{t-1}\)和\(x_t\)。然而,在有些情况下,预测依赖于过去、现在和未来的事件。
例如,预测一个单词将被包含在一个句子中可能需要我们展望未来,即,一个句子中的一个单词可能依赖于一个未来事件。这种语言依赖性在一些文本预测任务中是常见的。
以语音识别为例。使用语音助理时,首先要说几句话,然后由助理进行解释并做出响应。这种解释可能不完全取决于前面的话;只有分析了后面的单词,整个单词序列才能有意义。
因此,捕获和分析过去和未来的事件在上述场景中是有帮助的。
双向 RNNs
为了实现输入的直接(过去)和反向(未来)遍历,使用双向 rnn 或 BRNNs。BRNN 是两个 RNN 的组合,一个 RNN 从数据序列的开始向前移动,另一个从数据序列的结尾向后移动。BRNN 中的网络块可以是简单的 RNN、gru 或 LSTMs。
Bidirectional RNN (Source: Colah)
BRNN 有一个额外的隐藏层,以适应落后的训练过程。在任何给定时间\(t\),向前和向后隐藏状态更新如下:
其中\(\phi\)是激活函数,\(W\)是权重矩阵,\(b\)是偏差。
时间\(t\)处的隐藏状态由\(A_t(向前)\)和\(A_t(向后)\)的组合给出。任何给定隐藏状态下的输出为:
BRNN 的训练类似于时间反向传播(BPTT)算法。BPTT 是训练 rnn 时使用的反向传播算法。典型的 BPTT 算法工作如下:
- 展开网络并计算每个时间步的误差。
- 卷起网络并更新权重。
然而,在 BRNN 中,因为向前和向后传递同时发生,所以更新两个过程的权重可以在同一时间点发生。这会导致错误的结果。因此,为了分别适应向前和向后传球,下面的算法用于训练 BRNN:
前进传球
- 向前状态(从\(t\) = 1 到\(N\))和向后状态(从\(t\) = \(N\)到 1)被传递。
- 传递输出神经元值(从\(t\) = 1 到\(N\))。
偶数道次
- 输出神经元值被传递(\(t\) = \(N\) to 1)。
- 向前状态(从\(t\)= \(N\)到 1)和向后状态(从\(t\) = 1 到\(N\))被传递。
向前传球和向后传球一起训练了一个 BRNN。
应用程序
BRNN 适用于以下应用:
- 手写识别
- 语音识别
- 依存句法分析
- 自然语言处理
双向遍历的思想也可以扩展到 2D 输入,如图像。我们可以有四个 rnn,每个表示一个方向。与卷积神经网络(CNN)不同,BRNN 可以确保图像特征图之间的长期相关性。
使用双向 RNN 的情感分析
情感分析是确定一段文本是正面的、负面的还是中性的过程。它广泛用于社交媒体监控、客户反馈和支持、识别贬损推文、产品分析等。在这里,我们将构建一个双向 RNN 网络,使用 s entiment-140 数据集将一个句子分为肯定句或否定句。
您可以在此处访问情感-140 数据集的清理子集。
步骤 1 -导入数据集
首先,导入情感-140 数据集。由于情绪-140 包含大约 160 万个数据样本,因此我们只导入其中的一个子集。当前的数据集有 50 万条推文。
! pip3 install wget
import wget
wget.download("https://nyc3.digitaloceanspaces.com/ml-files-distro/v1/sentiment-analysis-is-bad/data/sentiment140-subset.csv.zip")
!unzip -n sentiment140-subset.csv.zip
现在,您已经在当前存储库中获得了解压缩的 CSV 数据集。
步骤 2 -加载数据集
使用 pip 命令安装 pandas 库。稍后,导入并读取 csv 文件。
! pip3 install pandas
import pandas as pd
data = pd.read_csv('sentiment140-subset.csv', nrows=50000)
步骤 3 -读取数据集
打印数据列。
data.columns
# Output
Index(['polarity', 'text'], dtype='object')
正文表示句子,极性,附加在句子上的感情。极性不是 0 就是 1。0 表示消极,1 表示积极。
找出数据集中的总行数,并打印前 5 行。
print(len(data))
data.head()
# Output
50000
The first 5 data values
步骤 4 -处理数据集
由于神经网络难以处理原始文本,我们必须将其转换为相应的数字表示。
为此,通过设置您希望对句子进行标记的最大字数(特征/标记)来初始化您的标记器,
import re
import tensorflow as tf
max_features = 4000
将标记器安装到文本上,
tokenizer = tf.keras.preprocessing.text.Tokenizer(num_words=max_features, split=' ')
tokenizer.fit_on_texts(data['text'].values)
使用结果标记器来标记文本,
X = tokenizer.texts_to_sequences(data['text'].values)
最后,填充标记化的序列,以在所有输入序列中保持相同的长度。
X = tf.keras.preprocessing.sequence.pad_sequences(X)
最后,打印输入向量的形状。
X.shape
# Output
(50000, 35)
因此,我们创建了 50000 个长度为 35 的输入向量。
步骤 4 -创建模型
现在,让我们创建一个双向 RNN 模型。使用 tf.keras.Sequential() 来定义模型。添加嵌入、空间缺失、双向和密集图层。
- 嵌入层是将单词/标记符映射到具有 embed_dim 维度的向量的输入层。
- 空间删除层删除节点,以防止过拟合。0.4 表示节点必须被丢弃的概率。
- 双向层是大小为 lstm_out 的 RNN-LSTM 层。
- dense 是具有 2 个节点(表示正负)和 softmax 激活函数的输出层。Softmax 有助于确定文本倾向于正面还是负面的概率。
最后,将分类交叉熵损失和 Adam 优化函数附加到模型中。
embed_dim = 256
lstm_out = 196
model = tf.keras.Sequential()
model.add(tf.keras.layers.Embedding(max_features, embed_dim, input_length = X.shape[1]))
model.add(tf.keras.layers.SpatialDropout1D(0.4))
model.add(tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(lstm_out, dropout=0.05, recurrent_dropout=0.2)))
model.add(tf.keras.layers.Dense(2, activation='softmax'))
model.compile(loss = 'categorical_crossentropy', optimizer='adam', metrics = ['accuracy'])
打印模型摘要以了解其层堆栈。
model.summary()
步骤 5 -初始化训练和测试数据
安装并导入所需的库。
import numpy as np
! pip3 install sklearn
from sklearn.model_selection import train_test_split
使用 get_dummies() 方法创建输出标签的一键编码表示。
Y = pd.get_dummies(data['polarity'])
分别用“正”和“负映射结果 0 和 1 值。
result_dict = {0: 'Negative', 1: 'Positive'}
y_arr = np.vectorize(result_dict.get)(Y.columns)
y_arr 变量将在模型预测期间使用。
现在,获取输出标签。
Y = Y.values
使用 train_test_split() 方法分割训练和测试数据。
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.33, random_state = 42)
打印训练和测试数据的形状。
print(X_train.shape, Y_train.shape)
print(X_test.shape, Y_test.shape)
# Output
(33500, 35) (33500, 2)
(16500, 35) (16500, 2)
步骤 6 -训练模型
调用模型的 fit() 方法,在训练数据上对模型进行约 20 个历元的训练,批量为 128。
model.fit(X_train, Y_train, epochs=20, batch_size=128, verbose=2)
# Output
Train on 33500 samples
Epoch 1/20
33500/33500 - 22s - loss: 0.5422 - accuracy: 0.7204
Epoch 2/20
33500/33500 - 18s - loss: 0.4491 - accuracy: 0.7934
Epoch 3/20
33500/33500 - 18s - loss: 0.4160 - accuracy: 0.8109
Epoch 4/20
33500/33500 - 19s - loss: 0.3860 - accuracy: 0.8240
Epoch 5/20
33500/33500 - 19s - loss: 0.3579 - accuracy: 0.8387
Epoch 6/20
33500/33500 - 19s - loss: 0.3312 - accuracy: 0.8501
Epoch 7/20
33500/33500 - 18s - loss: 0.3103 - accuracy: 0.8624
Epoch 8/20
33500/33500 - 19s - loss: 0.2884 - accuracy: 0.8714
Epoch 9/20
33500/33500 - 19s - loss: 0.2678 - accuracy: 0.8813
Epoch 10/20
33500/33500 - 19s - loss: 0.2477 - accuracy: 0.8899
Epoch 11/20
33500/33500 - 19s - loss: 0.2310 - accuracy: 0.8997
Epoch 12/20
33500/33500 - 18s - loss: 0.2137 - accuracy: 0.9051
Epoch 13/20
33500/33500 - 19s - loss: 0.1937 - accuracy: 0.9169
Epoch 14/20
33500/33500 - 19s - loss: 0.1826 - accuracy: 0.9220
Epoch 15/20
33500/33500 - 19s - loss: 0.1711 - accuracy: 0.9273
Epoch 16/20
33500/33500 - 19s - loss: 0.1572 - accuracy: 0.9339
Epoch 17/20
33500/33500 - 19s - loss: 0.1448 - accuracy: 0.9400
Epoch 18/20
33500/33500 - 19s - loss: 0.1371 - accuracy: 0.9436
Epoch 19/20
33500/33500 - 18s - loss: 0.1295 - accuracy: 0.9475
Epoch 20/20
33500/33500 - 19s - loss: 0.1213 - accuracy: 0.9511
在训练过程中捕获的绘图精度和损失图。
import matplotlib.pyplot as plt
plt.plot(history.history['accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train'], loc='upper left')
plt.show()
plt.plot(history.history['loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train'], loc='upper left')
plt.show()
Accuracy captured during the training phase
Loss captured during the training phase
步骤 7 -计算精确度
打印测试数据的预测分数和准确度。
score, acc = model.evaluate(X_test, Y_test, verbose=2, batch_size=64)
print("score: %.2f" % (score))
print("acc: %.2f" % (acc))
# Output:
16500/1 - 7s - loss: 2.0045 - accuracy: 0.7444
score: 1.70
acc: 0.74
步骤 8 -进行情感分析
现在是时候预测用户给定句子的情绪(积极/消极)了。首先,初始化它。
twt = ['I do not recommend this product']
接下来,将其标记化。
twt = tokenizer.texts_to_sequences(twt)
垫它。
twt = tf.keras.preprocessing.sequence.pad_sequences(twt, maxlen=X.shape[1], dtype='int32', value=0)
通过将句子传递给我们建立的模型来预测情绪。
sentiment = model.predict(twt, batch_size=1)[0]
print(sentiment)
if(np.argmax(sentiment) == 0):
print(y_arr[0])
elif (np.argmax(sentiment) == 1):
print(y_arr[1])
# Output:
[9.9999976e-01 2.4887424e-07]
Negative
这个模型告诉我们给定的句子是否定句。
结论
双向 RNN 是在相反方向训练网络的两个 rnn 的组合,一个从序列的开始到结束,另一个从序列的结束到开始。它通过不将模型的学习局限于过去和现在来帮助分析未来事件。
最后,我们使用双向 RNN 对 140 个情感数据集的子集进行了情感分析。
在本系列的下一部分,您将学习深度递归神经网络。
参考
用 Cython 提升 Python 脚本
原文:https://blog.paperspace.com/boosting-python-scripts-cython/
Python 可能是当今最流行的编程语言之一,但它肯定不是最高效的。特别是在机器学习领域,从业者为了 Python 提供的易用性牺牲了效率。
这并不意味着你不能用其他方法加快速度。Cython 是一种显著减少 Python 脚本计算时间的简单方法,而不会牺牲使用 Python 轻松实现的功能。
本教程将向您介绍如何使用 Cython 来加速 Python 脚本。我们将看到一个简单但计算量很大的任务:为创建一个循环,该循环遍历包含 10 亿个数字的 Python 列表,并对它们求和。由于在资源有限的设备上运行代码时,时间尤其重要,我们将通过考虑如何在 Raspberry Pi (RPi)上的 Cython 中实现 Python 代码来考虑这个问题。Cython 显著提高了计算速度。把它想象成树懒和猎豹的比较。
Sloth image from IndiaToday | Cheetah image from Pixabay
本教程涵盖的部分如下:
- Python、CPython 和 Cython
- 简化 Python 代码
- 为循环发出一个
- 将 C 数据类型赋给变量
- Cython 在树莓派中的应用
让我们开始吧。
Python 和 CPython
许多人没有意识到这样一个事实,即像 Python 这样的语言实际上是用其他语言实现的。比如 Python 的 C 实现叫做 CPython 。注意不是 Cython 。关于 Python 不同实现的更多信息,你可以阅读这篇文章。
Python 的默认和最流行的实现是 CPython。使用它有一个重要的优点。c 是一种编译语言,它的代码被转换成机器代码,由中央处理器(CPU)直接执行。现在你可能会想,如果 C 是一种编译语言,那是不是意味着 Python 也是?
C (CPython)中的 Python 实现不是 100%编译的,也不是 100%解释的。在运行 Python 脚本的过程中,既有编译也有解释。为了说明这一点,让我们来看看运行 Python 脚本的步骤:
- 使用 CPython 编译源代码以生成字节码
- 在 CPython 解释器中解释字节码
- 在 CPython 虚拟机中运行 CPython 解释器的输出
当 CPython 编译源代码(.py 文件)来生成 CPython 字节码(.pyc 文件)。CPython 字节码(。pyc 文件)然后使用 CPython 解释器进行解释,输出在 CPython 虚拟机上运行。根据上面的步骤,运行 Python 脚本的过程包括编译和解释。
CPython 编译器只生成一次字节码,但是每次代码运行时都会调用解释器。通常字节码的解释要花很多时间。如果使用解释器会降低执行速度,为什么还要使用它呢?最大的原因是它有助于 Python 跨平台。因为字节码运行在 CPU 之上的 CPython 虚拟机中,所以它独立于运行它的机器。因此,字节码可以在不同的机器上运行而不发生变化。
如果没有使用解释器,那么 CPython 编译器将生成直接在 CPU 中运行的机器码。因为不同的平台有不同的指令,所以代码不会跨平台。
总之,使用编译器加快了这个过程,但是解释器使代码跨平台。所以,Python 比 C 慢的一个原因是使用了解释器。记住编译器只运行一次,但是每次代码执行时解释器都会运行。
Python 比 C 慢得多,但许多程序员仍然喜欢它,因为它更容易使用。Python 对程序员隐藏了许多细节,这有助于防止令人沮丧的调试。例如,因为 Python 是一种动态类型的语言,你不必在你的代码中显式地指定每个变量的类型——Python 会自动推导出它。相反,对于静态类型的语言(如 C、C++或 Java ),您必须指定变量的类型,如下所示。
*`int x = 10
string s = "Hello"`*
将它与下面的 Python 实现进行比较。动态类型化使编码更容易,但是增加了机器寻找合适的数据类型的负担。这使得该过程更慢。
*`x = 10
s = "Hello"`*
一般来说,像 Python 这样的“高级”语言对开发者来说更容易使用。然而,当代码运行时,它需要被转换成低级指令。这种转换需要更多的时间,这是为了易于使用而牺牲的。
如果时间是一个重要的因素,那么你需要使用低级指令。因此,您可以使用 CPython 编写代码,而不是使用 Python(这是一种接口)来键入代码,CPython 是用 C 实现的 Python 的后端。但是,如果您这样做,您会觉得您是在用 C 而不是 Python 编程。
CPython 要复杂得多。在 CPython 中,一切都是用 C 实现的。在编码中,没有办法逃避 C 的复杂性。这就是为什么许多开发者选择 Cython 而不是 T1 的原因。但是 Cython 和 CPython 有什么不同呢?
Cython 与众不同
根据 Cython 文档,Cython 是 C 数据类型的 Python。来自 Cython tutorial 2009 论文的另一个定义阐明:
Cython 是一种基于 Python 的编程语言,具有额外的语法来提供静态类型声明。这既利用了 Python 的优势,又能达到 c 语言的速度。
根据上面的定义,Cython 是一种让你拥有两个世界的最好的语言——速度和易用性。您仍然可以用 Python 编写常规代码,但是为了在运行时加快速度,Cython 允许您用 c 语言替换 Python 代码的某些部分。因此,您最终会将两种语言混合在一个文件中。请注意,您可以想象 Python 中的一切在 Cython 中都是有效的,但有一些限制。有关限制的更多信息,您可以访问本页。
常规 Python 文件的扩展名为. py,但 Cython 文件的扩展名为。改为 pyx 扩展名。相同的 Python 代码可以编写在。pyx 文件,但是这些文件也允许您使用 Cython 代码。请注意,与直接运行 Python 代码相比,仅将 Python 代码放入. pyx 文件中可能会加快速度,但不如声明变量类型时快。因此,本教程的重点不仅仅是在。pyx 文件,但也进行编辑,这将使它运行得更快。通过这样做,我们给编程增加了一点难度,但是这样做节省了很多时间。如果你有 C 语言编程的经验,那么对你来说会更容易。
简化 Python 代码
要将 Python 转换成 Cython,首先需要用创建一个文件。pyx 分机而不是。py 分机。在这个文件中,你可以从编写常规的 Python 代码开始(注意,Cython 接受的 Python 代码有一些限制,在 Cython 文档中已经阐明)。
在继续之前,请确保安装了 Cython。您可以使用以下命令来完成此操作。
*`pip install cython`*
来生成。pyd/。所以我们需要首先构建 Cython 文件。的。pyd/。所以 file 表示稍后要导入的模块。为了构建 Cython 文件,将使用 setup.py 文件。创建这个文件,并将下面的代码放入其中。我们将使用 distutils.core.setup()函数来调用 Cython。Build.cythonize()函数,该函数将。pyx 文件。这个函数接受你想要同步的文件的路径。这里我假设 setup.py 文件与 test_cython.pyx 文件放在同一个位置。
*`import distutils.core
import Cython.Build
distutils.core.setup(
ext_modules = Cython.Build.cythonize("test_cython.pyx"))`*
为了构建 Cython 文件,在命令提示符下发出下面的命令。命令提示符的当前目录应该与 setup.py 文件的目录相同。
*`python setup.py build_ext --inplace`*
该命令完成后,两个文件将被放在。pyx 文件。第一个有。c 扩展名,而另一个文件将具有该扩展名。pyd(或类似的,基于使用的操作系统)。为了使用生成的文件,只需导入 test_cython 模块,就会直接出现“Hello Cython”消息,如下所示。
我们现在已经成功地将 Python 代码细胞化了。下一节将讨论如何创建一个. pyx 文件,并在其中创建循环。
将“for”循环变成 Cythonizing
现在让我们优化我们前面提到的任务:一个 for 循环,它遍历一百万个数字并对它们求和。让我们从查看循环迭代的效率开始。导入时间模块是为了估计执行需要多长时间。
*`import time
t1 = time.time()
for k in range(1000000):
pass
t2 = time.time()
t = t2-t1
print("%.20f" % t)`*
在. pyx 文件中,3 次运行的平均时间是 0.0281 秒。代码运行在配备酷睿 i7-6500U CPU @ 2.5 GHz 和 16 GB DDR3 RAM 的机器上。
将它与运行一个普通 Python 文件所需的时间进行比较,后者的平均值为 0.0411 秒。这意味着仅在迭代方面,Cython 就比 Python 快 1.46 倍,尽管我们不需要修改循环的来让它以 C 速度执行。**
现在让我们添加求和任务。为此,我们将使用 range()函数。
*`import time
t1 = time.time()
total = 0
for k in range(1000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.100f" % t)`*
请注意,这两个脚本返回相同的值,即 499999500000。在 Python 中,平均运行时间为 0.1183 秒(三次试验之间)。在 Cython 中,它的速度快了 1.35 倍,平均为 0.0875 秒。
让我们看另一个例子,循环从 0 开始遍历 10 亿个数字。
*`import time
t1 = time.time()
total = 0
for k in range(1000000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.20f" % t)`*
Cython 脚本完成了将近 85 秒(1.4 分钟),而 Python 脚本完成了将近 115 秒(1.9 分钟)。在这两种情况下,时间都太长了。如果在如此琐碎的任务上持续超过一分钟,那么使用 Cython 的好处是什么?注意这是我们的错,不是 Cython 的。
如前所述,在 Cython 中编写 Python 代码。pyx 脚本是一个改进,但是它并没有大幅减少执行时间。我们必须在 Cython 脚本中编辑 Python 代码。首先要关注的是明确定义所用变量的数据类型。
将 C 数据类型赋给变量
根据前面的代码,使用了 5 个变量: total,k,t1,t2 ,T3, t 。所有这些变量的数据类型都是由代码隐式推导出来的,因此需要更多的时间。为了节省推断它们的数据类型的时间,让我们从 C 语言中指定它们的数据类型。**
total 变量的类型是无符号长整型。它是一个整数,因为所有数字的总和是一个整数,它是无符号的,因为总和将是正数。但是为什么是龙*龙呢?因为所有数字的和非常大,所以添加 long long 来将变量大小增加到最大可能大小。*
为变量 k 定义的类型是 int ,为剩余的三个变量 t1 、 t2 和 t 赋值 float 类型。
*`import time
cdef unsigned long long int total
cdef int k
cdef float t1, t2, t
t1 = time.time()
for k in range(1000000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.100f" % t)`*
注意,最后一个 print 语句中定义的精度被设置为 100,所有这些数字都是零(见下图)。这就是我们使用 Cython 所能期待的。Python 需要 1.9 分钟以上,而 Cython 根本不需要时间。我甚至不能说速度比 Python 快 1000 或 100000;我尝试了不同精度的打印时间,仍然没有数字出现。
请注意,您还可以创建一个整数变量来保存传递给 range()函数的值。这将进一步提高性能。下面列出了新代码,其中的值存储在整数变量 maxval 中。
*`import time
cdef unsigned long long int maxval
cdef unsigned long long int total
cdef int k
cdef float t1, t2, t
maxval=1000000000
t1=time.time()
for k in range(maxval):
total = total + k
print "Total =", total
t2=time.time()
t = t2-t1
print("%.100f" % t)`*
既然我们已经看到了如何通过使用 Cython 来提高 Python 脚本的性能,那么让我们将其应用到 Raspberry Pi (RPi)中。
从 PC 访问 Raspberry Pi
如果这是你第一次使用你的 Raspberry Pi,那么你的 PC 和 RPi 都需要通过网络连接。为此,您可以将它们连接到一台交换机,在该交换机中,DHCP(动态主机配置协议)处于活动状态,以便自动为它们分配 IP 地址。成功创建网络后,您可以根据分配给它的 IPv4 地址访问 RPi。您如何知道分配给您的 RPi 的 IPv4 地址是什么?别担心,你可以简单地使用一个 IP 扫描工具。在本教程中,我将使用一个名为高级 IP 扫描器的免费应用程序。
应用程序的界面如下所示。它接受要搜索的 IPv4 地址范围,并返回活动设备的信息。
您需要输入本地网络中的 IPv4 地址范围。如果您不知道范围,只需在 Windows 中发出 ipconfig 命令(或在 Linux 中发出 ifconfig)即可知道您的 PC IPv4 地址(如下图所示)。在我的例子中,分配给我的 PC 的 Wi-Fi 适配器的 IPv4 地址是 192.168.43.177,子网掩码是 255.255.255.0。这意味着网络中的 IPv4 地址范围是从 192.168.43.1 到 192.168.43.255。如图所示,IPv4 地址 192.168.43.1 被分配给网关。请注意,范围内的最后一个 IPv4 地址 192.168.43.255 是为广播消息保留的。因此,搜索范围应该从 192.168.43.2 开始,到 192.168.43.254 结束。
根据下图所示的扫描结果,分配给 RPi 的 IPv4 地址是 192.168.43.63。此 IPv4 地址可用于创建安全外壳(SSH)会话。
为了建立 SSH 会话,我将使用一个名为 MobaXterm 的免费软件。应用程序的界面如下。
为了创建一个 SSH 会话,只需点击左上角的会话按钮。将出现一个新窗口,如下所示。
在该窗口中,单击左上角的 SSH 按钮,打开如下所示的窗口。只需输入 RPi 的 IPv4 地址和用户名(默认为 Pi),然后单击 OK 启动会话。
单击 OK 按钮后,会出现一个新窗口,要求输入密码。默认密码是 raspberrypi。登录后,将出现下一个窗口。左侧的窗格有助于轻松导航 RPi 的目录。还有一个用于输入命令的命令行。
将 Cython 与树莓 Pi 一起使用
创建一个新文件,并将其扩展名设置为。pyx 来编写最后一个例子的代码。左侧窗格的栏中有用于创建新文件和目录的选项。您可以使用“新建文件”图标使事情变得更加简单,如下图所示。我在 RPi 的根目录下创建了一个名为 test_cython.pyx 的文件。
只需双击文件打开它,粘贴代码,并保存它。之后,我们可以创建 setup.py 文件,这与我们之前讨论的完全相同。接下来,我们必须发出以下命令来构建 Cython 脚本。
*`python3 setup.py build_ext --inplace`*
该命令成功完成后,您可以根据下图找到左侧窗格中列出的输出文件。注意,要导入的模块的扩展现在是。因此,由于我们不再使用 Windows。
现在让我们激活 Python 并导入模块,如下所示。这里也实现了在 PC 上实现的相同结果;消耗的时间基本为零。
结论
本教程讨论了如何使用 Cython 来减少执行 Python 脚本的计算时间。我们看了使用一个 for 循环对一个包含 10 亿个数字的 Python 列表中的所有元素求和的例子,并比较了声明变量类型和不声明变量类型的执行时间。虽然这在纯 Python 中运行需要将近两分钟,但使用 Cython 声明的静态变量运行基本上不需要时间。
在下一篇教程中,我们将用 NumPy 数组替换这个 Python 列表,并看看如何使用 Cython 优化 NumPy 数组处理。然后,我们将看看如何将更高级的 Python 脚本(如遗传算法)进行 cythonize。这是轻松提高机器学习项目效率的好方法。
发现为什么甘是可怕的!
原文:https://blog.paperspace.com/build-simple-gan-from-scratch-to-generate/
为什么甘很牛逼?
当我们谈论现代深度学习时,首先想到的、最受欢迎的、通常也是最有用的概念是生成对抗网络 ( 甘斯)。这些都有很多很棒的应用,比如:
- 生成不存在的真实人脸
这些脸都是假的,都是甘做的,所以很遗憾你见不到他们😔。
Random Sample of human faces that do not exist captured from human faces that do not exist
- 修复、移植和提高照片的分辨率
如果你有一个过时的损坏图像,GANs 可以帮助你恢复它,添加一些颜色,提高它的分辨率😊。
跟随 GFPGAN 上的这个教程,学习如何升级和恢复损坏的照片。
- 跨域转换图像,风格转移
另一个很酷的应用是跨域转换图像。例如,使用样式转移,我们可以拍摄一幅马的图像并将其转换为斑马,拍摄一幅夏天的图像并将其转换为冬天,或者拍摄一幅语义图并输出该语义图的真实图像。
看看这篇教程,学习如何用 JojoGAN 对人脸进行这样的变换。
- 数据扩充
gan 还经常用于数据增强,这是一个非常明确的应用,但可能不太清楚的是这些模型在医疗应用中的效用。由于来自 GANs 的数据不是来自一个真实的人,这意味着它可以在没有道德和隐私问题和限制的情况下使用,这是一个非常酷的应用程序。
概要:
到目前为止,我们看到的只是一小部分例子,还有很多很多。每天都有新的应用被发现,因为这是一个非常热门的研究领域。现在让我们继续看看什么是真正的甘,看看他们背后的想法。
什么是 GANs,它们是如何工作的?
GANs 是一种机器学习技术,由和两个网络进行对抗游戏组成。其中一个网络称为生成器、、创建样本,另一个网络称为鉴别器,试图辨别图像与真实版本相比有多真实。
假设生成器想要打印一组假的手写数字,一个鉴别器想要区分真假。在 GANs 的训练过程中,假设生成器试图打印包含数字 8 的手写数字。最初,由于缺乏训练,每个生成的样本看起来像随机噪声,因此鉴别器将尝试比较生成的随机样本和数字 8 的真实图像的相似性。在这种情况下,它会说来自发生器的信号是假的,所以发生器不能在一开始欺骗鉴别器。
The fake eight is taken from the simple GAN that we will build (epoch 0)
但是随着训练的进行,生成器运行的时间越来越长,它现在可能会产生一些看起来接近真实手写 8 的东西。这是因为在每一步,真实和虚假图像之间的损失被反向传播到下一次迭代的开始。梯度下降的每一步分别降低真实图像和生成图像的真实分布和生成分布之间的距离。
使用人类的洞察力在上面的例子图像上,我们可以看到一些不正确的像素,所以我们可以推断鉴别器可能会看着这两个,再次说来自生成器的那个是假的。尽管如此,还是取得了进展。
The fake eight is taken from the simple GAN that we will build (epoch 50)
随着训练的深入,生成器将开始生成看起来更真实的输出。当鉴别器不能区分真假样本时,它最终会试图断言真正的手写数字就是假的。最后,生成器能够骗过鉴别器。
The fake eight is taken from the simple GAN that we will build (epoch 90)
最终,生成器生成几乎无法与真实数字区分的手写数字,而鉴别器被迫猜测(大致成功概率为 1/2)。
注:两个鉴别器和发生器实际上都是从 scratch 开始,也就是说它们都是在开始时随机初始化,然后同时训练。
损失函数是什么?
我想现在已经很清楚了,在 GANs 中,我们有一个用于发生器的网络,还有一个用于鉴频器的网络,我们稍后会看到如何实现它。
现在需要理解的最重要的部分之一是损失函数是什么样子的,以及它是如何工作的。
鉴频器损耗
当鉴别器被训练时,它对来自发生器的真实数据和虚假数据进行分类。
通过最大化下面的函数,它惩罚自己将一个真实的实例误分类为假的,或者将一个假的实例(由生成器创建)误分类为真实的。
Discriminator loss
其中:
m: 训练样本数。
D: 鉴别器。
x(i): 训练例 I,这么一个真实的手写图像。
G: 发电机。
z(i): 将要输入到发电机的随机噪声。
现在让我们来看一点细节
- 对于第一项 log(D(x(i))), (x(i)是真实的图像)所以如果鉴别器能够区分真伪,它将输出 1,并且 log(1)=0
- 在另一项 log(1 - D(z(i))) 中,生成器要取一些随机噪声 z(i),它要输出看起来接近真实图像的东西,鉴别器如果没有被生成器忽悠就输出 0,log(1-0)=0。
- 在公式的开始,我们只有所有训练样本 m 的平均值。
发电机损耗
当生成器被训练时:它对随机噪声进行采样,并从该噪声中产生一个输出。然后,输出通过鉴别器,并根据鉴别器辨别真伪的能力被分类为真或假。
然后,根据鉴别器的分类计算发电机损耗——如果成功骗过鉴别器,将获得奖励,否则将受到惩罚。
下面的等式被最小化以训练发生器:
Generator loss
合计损失
如果我们一起损失,我们会有这个表达式:
我们希望最小化关于发生器,我们希望最大化关于鉴别器,这导致了这个极小极大博弈。我们希望对某个值函数 V 执行此操作,该函数将鉴别器 D 和发生器 G 作为输入,我们希望计算 x 的期望值,其中 x 来自某个实际数据分布 Ex~p(data(x)) 。我们希望对 log(D(x)) 也这样做。然后,我们要添加 z 的期望值,其中 z 来自某个随机分布 Ex~p(z(z)) ,我们要对 log(1-D(G(z))) 这样做。
注意:
从发生器的角度来看,损失函数具有非常弱的梯度,因此在实践中,发生器被训练为最大化该表达式作为损失函数:
发生器的这个新的损失函数导致非饱和梯度,这使得训练容易得多
从零开始构建我们的第一个简单 GAN 以生成 MNIST
为了学习新概念或新软件,最好首先使用没有问题的样本数据集,对我来说,MNIST 数据集是一个完美的选择,因为在我们看来,它是最容易使用的图像数据。这就像计算机视觉中的“hello world ”,他们说如果某样东西在 MNIST 数据集中不起作用,它可能永远也不会起作用。在接下来的教程中,我们将使用更高级的 GANs 处理更复杂的数据。
因此,在本节中,我们将重点从零开始构建一个简单的 GAN 来生成从 0 到 9 的手写数字。
现在让我们开始库导入。
加载我们需要的所有依赖项
第一步,我们将导入从头构建一个简单的 GAN 所需的所有依赖项。
我们首先将为线性代数导入 Numpy。然后,我们想使用 PyTorch,所以让我们导入 Torch,并从 torch 导入 nn。这将有助于我们创建和训练网络(鉴别器和生成器),同时让我们导入 optim,这是一个实现各种优化算法的包(例如 sgd、adam、..).从 torch.utils.data 导入数据加载器来创建小批量。
注:很多惊人的数据科学家分别是这样导入 nn 和 optim 的:导入 torch.nn 为 nn,导入 torch.optim 为 optim。
我们还将导入火炬视觉,因为我们显然是在处理图像。从 torchvision,我们将导入转换和数据集来下载我们的数据并应用一些转换。最后,我们将导入 matplotlib.pyplot 作为 plt 来绘制结果和一些真实样本。
import numpy as np
import torch
from torch import nn, optim
from torch.utils.data import DataLoader
import torchvision
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import matplotlib.pyplot as plt
鉴别器
现在让我们来构建鉴别器,从我之前展示的示例来看,它将充当我们的判断器,因此鉴别器将判断图像,并判断它是真图像还是假图像。
鉴别器类将从 nn 继承。模块,这将是一个非常简单的模型,它将来自 MNIST 数据集的输入 in_features ,因此它将等于 28281= 784,我们将使用以下层来构建我们的模型:
- nn。线性:这基本上是一个完全连接的层
- nn。LeakyReLU :这是应用于网络中各种输出的激活,可以使用 nn。ReLU ,但是在 GANs LeakyReLU 往往是更好的选择,或者至少是更好的默认。
- nn。Sigmoid(): 在最后一层使用它,确保结果在 0 和 1 之间
class Discriminator(nn.Module):
def __init__(self, in_features):
super().__init__()
self.disc = nn.Sequential(
nn.Linear(in_features, 256),
nn.LeakyReLU(0.1),
nn.Linear(256, 128),
nn.LeakyReLU(0.1),
nn.Linear(128, 1),
nn.Sigmoid()
)
def forward(self, x):
return self.disc(x)
发电机
让我们现在构建生成器,它将生成看起来像真的手写数字的假数字,并试图欺骗鉴别器。
生成器类也将从 nn 继承。模块,这将是一个非常简单的模型,将噪声 z_dim 作为输入,并从中生成假图像。我们将使用相同的层来建立这个模型,除了在最后的激活函数中我们将使用神经网络。Tanh 确保输出的像素值在-1 和 1 之间。我们这样做是因为我们要对来自 MNIST 数据集的输入进行归一化,以确保它在-1 和 1 之间。
class Generator(nn.Module):
def __init__(self, z_dim, img_dim):
super().__init__()
self.gen = nn.Sequential(
nn.Linear(z_dim, 256),
nn.LeakyReLU(0.1),
nn.Linear(256, 512),
nn.LeakyReLU(0.1),
nn.Linear(512, img_dim),
nn.Tanh()
)
def forward(self, x):
return self.gen(x)
超参数
GAN 对超参数非常敏感,尤其是在这个简单的 GAN 中。我们在某种程度上复制了最初的 GAN 论文。在新的论文中,他们提出了更好的方法来稳定 GANs,但我们将把它留到以后的文章中。
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
LR = 3e-4
Z_DIM = 64
IMG_DIM = 28*28*1
BS = 64
EPOCHS = 101
初始化和预处理
- 初始化鉴别器和发生器,并将其放入设备中
- 设置一些固定的噪声来观察相同的分布图像在不同时期是如何变化的
- 初始化用于数据扩充的转换(RandomHorizontalFlip ),并将我们的图像归一化到-1 和 1 之间
- 下载 MNIST 数据集并对其应用变换
- 使用数据加载器创建小批量
- 初始化鉴别器和生成器的优化器,我们将对两者都使用 Adam
- 用 BCELoss 初始化损失,因为它与我们之前看到的 GANs 损失非常接近
disc = Discriminator(IMG_DIM).to(DEVICE)
gen = Generator(Z_DIM, IMG_DIM).to(DEVICE)
fixed_noice = torch.randn((BS, Z_DIM)).to(DEVICE)
transforms = transforms.Compose([transforms.RandomHorizontalFlip(), transforms.ToTensor(),transforms.Normalize((0.5,),(0.5,))])
dataset = datasets.MNIST(root = 'dataset/', transform = transforms, download = True)
loader = DataLoader(dataset, batch_size=BS, shuffle=True)
opt_disc = optim.Adam(disc.parameters(), lr=LR)
opt_gen = optim.Adam(gen.parameters(), lr=LR)
criterion = nn.BCELoss()
绘制一些真实的样本
让我们画一些真实的样品,看看他们看起来如何。
real, y = next(iter(loader))
_, ax = plt.subplots(5,5, figsize=(10,10))
plt.suptitle('Some real samples', fontsize=19, fontweight='bold')
ind = 0
for k in range(5):
for kk in range(5):
ind += 1
ax[k][kk].imshow(real[ind][0])
培养
现在有趣的部分,魔术将会发生,在这一部分中,我们将训练我们的 GAN,并为 0、50 和 100 个时期绘制一些假样本。
为了训练 GAN,我们循环使用 DataLoader 创建的所有小批量,并且我们只获取图像,因为我们不需要标签。这使得它成为无监督学习。我们将图像命名为 real,因为它们是真实的,我们使用real.view(-1, 784)
对它们进行整形,以保持我们批次中的示例数量,然后我们展平其他所有内容,并将我们的批次大小带到设备中。
现在,让我们为鉴别器设置培训。记住,它要最大化 log(D(实))+ log(1-D(G(z))) 。
为了获得第一项 log(D(real)) 我们用 D(real) 代替 BCELoss 中的 x,D(real) 是鉴别器拍摄真实图像时的输出,我们用 1 代替 y。我们的新方程将具有以下形式:
你可以注意到,我们把 wn 从等式中去掉了,因为它只有一个,所以我们可以忽略它。但这里重要的部分是,我们前面有一个负号,这意味着如果我们想要最大化 log(D(real)) ,那么我们需要最小化该表达式的负值,如果你习惯于训练神经网络,你通常会想要最小化损失函数。
为了获得第二项 log(1-D(G(z))) 我们用 D(G(z)) 代替 BCELoss 中的 x,D(G(z)) 是鉴别器在获取生成器生成的假图像时的输出。然后,我们用零代替 y,因此我们有以下等式:
注意:为了训练生成器,我们也将使用 G(z ),没有必要再做一遍。当计算鉴频器的反向损耗时,正向传递中用于计算这些梯度的所有内容都已从缓存中清除。如果我们想防止这种清除,我们添加这个参数 retain_graph=True。
现在,让我们为想要最小化 log(1-D(G(z))) 的生成器设置训练,但是在实践中,正如我之前所说的,我们改为最大化log(D(G(z))】。为此,我们也使用 BCELoss,用 D(G(z))代替 x,用 1 代替 y,以获得该等式:
最后,我们准备写一些代码来绘制一些假样本,并尽可能多的重复所有这些。
for epoch in range(EPOCHS):
for real,_ in loader:
real = real.view(-1, 784).to(DEVICE)
batch_size = real.shape[0]
######## train Discriminator: max log(D(real)) + log(1-D(G(z)))
noise = torch.randn(batch_size, Z_DIM).to(DEVICE)
fake = gen(noise)
disc_real = disc(real).view(-1) #shape [64,1] -> [64]
lossD_real= criterion(disc_real, torch.ones_like(disc_real))
disc_fake = disc(fake).view(-1)
lossD_fake= criterion(disc_fake, torch.zeros_like(disc_fake))
lossD = (lossD_real + lossD_fake)/2
disc.zero_grad()
lossD.backward(retain_graph=True) # Add retain_graph=True, To save fake in memory
opt_disc.step()
######## train Generator: min log(1-D(G(z))) <==> max log(D(G(z)))
output = disc(fake).view(-1)
lossG = criterion(output, torch.ones_like(output))
gen.zero_grad()
lossG.backward()
opt_gen.step()
print(
f"Epoch [{epoch}/{EPOCHS}] \
Loss D: {lossD:.4f}, loss G: {lossG:.4f}"
)
if epoch %50 == 0:
with torch.no_grad():
fake = gen(fixed_noice).reshape(-1, 1, 28, 28).cpu()
_, ax = plt.subplots(5,5, figsize=(10,10))
plt.suptitle('Results of epoch '+str(epoch), fontsize=19, fontweight='bold')
ind = 0
for k in range(5):
for kk in range(5):
ind += 1
ax[k][kk].imshow(fake[ind][0])
这些图像并不完美,但看起来还不错!如果你训练的时间更长,你可以期待这些会变得更好。希望您能够遵循所有步骤,并理解如何实现简单的图像生成 GAN。
结论
在这篇文章中,我们通过谈论一些很酷的应用程序,如生成不存在的真实人脸、恢复、殖民、提高照片分辨率和跨域转换图像,发现了为什么 gan 很棒。然后,我们分析了 GANs 到底是什么,并对它们的工作方式有了直观的了解。我们深入研究了鉴频器和发生器使用的损耗函数,最后,我们从头开始实现了一个简单的 GAN 来产生 MNIST。
在接下来的文章中,我们将解释并从头实现更高级的 GANs 来生成更酷更复杂的数据。
使用深度 Q 学习构建跳棋游戏代理
Photo by Tai's Captures / Unsplash
机器学习文献中最有趣的学习方法之一是强化学习。通过奖励和惩罚,一个主体学习通过在已知或未知的环境中从一组动作中选择一个动作来达到给定的目标。强化学习与监督和非监督学习技术不同,不需要任何初始数据。
在本文中,我们将演示如何实现一个版本的强化学习技术 深度 Q 学习 ,以创建一个能够在体面的水平上玩跳棋的 AI 代理。
深度 Q 学习简介
深度强化学习是机器学习的一个分支,结合了深度学习和强化学习(RL)。RL 考虑了让计算代理通过犯错来学习的挑战。由于深度学习融入了解决方案,代理可以根据来自非结构化数据的输入做出决策,而不必手动设计状态空间。深度 RL 算法能够处理大量数据,并确定采取最佳行动以实现目标:例如赢得跳棋比赛。
首先,我们来讨论一下强化学习与有监督或无监督学习的区别。它基本上是两个要素:环境和 T2 代理。
在深度强化学习任务中,一个与其环境交互的代理正在接受训练。代理执行动作到达各种场景,或者状态。对行动的奖励可以是正面的,也可以是负面的。
在这种情况下,代理人的唯一目标是最大化整集的整体回报。这一集(也称为生成)包括在初始状态和最终状态之间的环境中发生的一切。
现在有一组必须完成的动作,以便赢得比赛并获得积极的奖励。延迟奖励的概念在这里发挥了作用。RL 的关键是学习执行这些序列,同时最大化每一代的回报,最终达到胜利的状态。这被称为马尔可夫决策过程。
Diagram of the Markov decision process (source)
让我们以跳棋游戏为例:
- 玩家是与环境互动的代理人
- 状态可以表示为具有每个棋子和国王位置的棋盘矩阵
- 一个集/代可以代表多个玩过的游戏。
- 动作是可以移动的移动棋子和可以捕获的捕获对手棋子。
- 这些行动的结果用于定义奖励。如果行动使玩家更接近胜利,应该给予积极的奖励;否则,应该分配一个负奖励。
深度 Q 学习的独特之处在于,我们使用这种强化学习技术来更新神经网络的权重。这是通过给每组(动作、状态)分配一个可量化的值来完成的,这就是所谓的 Q 值。然后,对于每一代,我们更新这个 Q 值以最大化回报。
使用旧值和新信息的加权平均值,使用贝尔曼方程完成 Q 值更新过程:
Bellman equation applied to Q-Learning (source)
作为深度 Q 学习的核心,这个等式引入了一些需要注意的重要量:
- 学习率:学习率影响新学到的知识取代以前学到的信息的程度。一个 0 的因子导致代理什么都不学,但是一个 1 的因子导致代理只考虑最近的信息,而忽略代理的先验知识。实际上,经常使用低的固定学习率,如 0.2 。
- 折扣因子:未来奖励的意义由折扣因子决定。一个接近于 1 的数值会导致代理人寻求长期的高回报,而一个接近于 0 的数值会导致其只考虑眼前回报的短视行为。对于我们的情况,应该实现一个高值的折扣因子,因为赢得游戏比拥有一个中间的“好”位置重要得多。
- 初始 Q 值 : Q 学习在第一次更新之前假设一个初始状态,因为它是一个迭代方法。高起始值可以促进探索,因为更新规则将总是使所选择的动作具有比备选方案更低的值,增加了他们选择它的可能性。
为了避免发散,最好总是根据一些逻辑选择初始值,避免随机性。这可以通过先前的经验(例如专家的跳棋或象棋游戏)或通过使用给定的逻辑产生一些“假”经验来完成(例如,根据预定义的规则集对跳棋位置的评估)。
跳棋游戏
作为最著名的棋盘游戏之一,跳棋有不同的变种。我们在官方维基百科文章中统计了至少 25 个变体。在本教程中,我们将使用 12 个初始棋子实现标准的 8×8 美国跳棋。
Starting position of the American checkers (source)
这个版本的跳棋,也叫英文跳棋,可以认为是最基础的。它使用了没有飞王的规则,棋子不能向后捕捉。我们建议将此文章作为完整的规则列表。
数学上,游戏可以用一个 8 乘 8 矩阵来建模,用一个值表示每个单元格的内容:被玩家 1 占据,被玩家 2 占据,空单元格或者不可到达的单元格。然而,也可以使用更压缩的版本:我们可以通过忽略不可到达的单元来实现一个 8 乘 4 矩阵(或 32 乘 1 阵列),因为它们在游戏过程中从不改变值。
为此,我们将修改一个公共可用的库。这个库包含了玩游戏所需的所有函数:可能的移动,可能的捕捉,生成下一个位置,检查游戏赢家,以及许多其他有用的函数。
!wget "https://raw.githubusercontent.com/adilmrk/checkers/main/checkers.py"
本库引入了一个名为 get_metrics 的关键函数,应该彻底检查一下。
def get_metrics(board): # returns [label, 10 labeling metrics]
b = expand(board)
capped = num_captured(b)
potential = possible_moves(b) - possible_moves(reverse(b))
men = num_men(b) - num_men(-b)
kings = num_kings(b) - num_kings(-b)
caps = capturables(b) - capturables(reverse(b))
semicaps = semicapturables(b)
uncaps = uncapturables(b) - uncapturables(reverse(b))
mid = at_middle(b) - at_middle(-b)
far = at_enemy(b) - at_enemy(reverse(b))
won = game_winner(b)
score = 4*capped + potential + men + 3*kings + caps + 2*semicaps + 3*uncaps + 2*mid + 3*far + 100*won
if (score < 0):
return np.array([-1, capped, potential, men, kings, caps, semicaps, uncaps, mid, far, won])
else:
return np.array([1, capped, potential, men, kings, caps, semicaps, uncaps, mid, far, won])
这个函数根据许多参数评估给定的棋盘状态,这些参数可以让我们大致了解哪个玩家的位置更好。这对于创建初始模型非常有用,我们稍后将通过深度 Q 学习来加强它。
度量模型
生成模型是产生新数据实例的统计模型的子集。无监督学习使用这些模型来执行任务,包括估计概率和可能性、建模数据点以及使用这些概率对实体进行分类。
在我们的例子中,我们将实现一个生成模型,它将一个度量数组(由函数 get_metrics 测量)作为输入,然后预测获胜概率。所以,这个模型将只着眼于 10 启发式评分指标为贴标签。在稍后的主板模型中,我们将使用指标模型创建一个不同的模型,该模型直接将主板作为一个 32 整数数组进行输入,并对主板进行评估。
为此,我们将实现一个基本的顺序 Keras 模型,它具有三个密集层、一个作为激活函数的校正线性单元和一个大小为 10 的输入,表示测量的得分指标。
# Metrics model, which only looks at heuristic scoring metrics used for labeling
metrics_model = Sequential()
metrics_model.add(Dense(32, activation='relu', input_dim=10))
metrics_model.add(Dense(16, activation='relu', kernel_regularizer=regularizers.l2(0.1)))
# output is passed to relu() because labels are binary
metrics_model.add(Dense(1, activation='relu', kernel_regularizer=regularizers.l2(0.1)))
metrics_model.compile(optimizer='nadam', loss='binary_crossentropy', metrics=["acc"])
start_board = checkers.expand(checkers.np_board())
boards_list = checkers.generate_next(start_board)
branching_position = 0
nmbr_generated_game = 10000
while len(boards_list) < nmbr_generated_game:
temp = len(boards_list) - 1
for i in range(branching_position, len(boards_list)):
if (checkers.possible_moves(checkers.reverse(checkers.expand(boards_list[i]))) > 0):
boards_list = np.vstack((boards_list, checkers.generate_next(checkers.reverse(checkers.expand(boards_list[i])))))
branching_position = temp
# calculate/save heuristic metrics for each game state
metrics = np.zeros((0, 10))
winning = np.zeros((0, 1))
for board in boards_list[:nmbr_generated_game]:
temp = checkers.get_metrics(board)
metrics = np.vstack((metrics, temp[1:]))
winning = np.vstack((winning, temp[0]))
# fit the metrics model
history = metrics_model.fit(metrics , winning, epochs=32, batch_size=64, verbose=0)
在建立了模型之后,我们从一个特定的棋盘开始使用一个树形探索制作了 10000 个游戏。然后计算每场比赛的启发性指标。最后,我们训练算法根据这些数据预测游戏的结果。
然后使用 Matplotlib 绘制训练处理历史
# History for accuracy
plot.plot(history.history['acc'])
plot.plot(history.history['val_acc'])
plot.title('model accuracy')
plot.ylabel('accuracy')
plot.xlabel('epoch')
plot.legend(['train', 'validation'], loc='upper left')
plot.show()
# History for loss
plot.plot(history.history['loss'])
plot.plot(history.history['val_loss'])
plot.title('model loss')
plot.ylabel('loss')
plot.xlabel('epoch')
plot.legend(['train', 'validation'], loc='upper left')
plot.show()
Accuracy & Loss diagram for the Metrics Model
董事会模式
接下来,我们使用度量模型来创建一个新模型,它接受棋盘的压缩版本(大小为 32)的输入,并预测从给定的棋盘中赢得的概率。换句话说,它评估位置:负面的评估可能会导致一场失败的游戏,而正面的评估可能会导致一场胜利的游戏。
这种位置评估概念与应用于玩主要棋盘游戏的 AI 引擎的概念相同:国际象棋、围棋和跳棋的其他变体。
# Board model
board_model = Sequential()
# input dimensions is 32 board position values
board_model.add(Dense(64 , activation='relu', input_dim=32))
# use regularizers, to prevent fitting noisy labels
board_model.add(Dense(32 , activation='relu', kernel_regularizer=regularizers.l2(0.01)))
board_model.add(Dense(16 , activation='relu', kernel_regularizer=regularizers.l2(0.01))) # 16
board_model.add(Dense(8 , activation='relu', kernel_regularizer=regularizers.l2(0.01))) # 8
# output isn't squashed, because it might lose information
board_model.add(Dense(1 , activation='linear', kernel_regularizer=regularizers.l2(0.01)))
board_model.compile(optimizer='nadam', loss='binary_crossentropy')
# calculate heuristic metric for data
metrics = np.zeros((0, 10))
winning = np.zeros((0, 1))
data = boards_list
编译完模型后,我们将遍历所有生成的棋盘位置。并且,对于每块板,我们提取指标,然后使用指标模型来预测该板的获胜概率。
我们还计算了每个董事会评估的置信水平:
置信度值用作 Keras 模型的样本权重,以强调概率较高的评估。
for board in data:
temp = checkers.get_metrics(board)
metrics = np.vstack((metrics, temp[1:]))
winning = np.zeros((0, 1))
# calculate probilistic (noisy) labels
probabilistic = metrics_model.predict_on_batch(metrics)
# fit labels to {-1, 1}
probabilistic = np.sign(probabilistic)
# calculate confidence score for each probabilistic label using error between probabilistic and weak label
confidence = 1/(1 + np.absolute(winning - probabilistic[:, 0]))
# pass to the Board model
board_model.fit(data, probabilistic, epochs=32, batch_size=64, sample_weight=confidence, verbose=0)
board_json = board_model.to_json()
with open('board_model.json', 'w') as json_file:
json_file.write(board_json)
board_model.save_weights('board_model.h5')
print('Checkers Board Model saved to: board_model.json/h5')
拟合模型后,我们将权重和模型配置保存到 json 和 h5 文件中,稍后我们将使用它们进行强化学习。
我们现在有了一个代表初级水平的 checker 专业知识的模型;这个模型的输出代表我们的初始 Q 值。使用这种方法来生成初始 Q 值将使强化学习更加有效,因为经过训练的棋盘模型在评估棋盘位置方面比随机选择评估的替代方案好得多。
强化模型
现在,初始模型已经训练好了,我们将使用深度 Q 学习来指导它产生更多的胜利和平局以及更少的损失。
在我们的例子中,我们将使用延迟奖励的概念。在我们的模型强化期间,我们将经历 500 代,其中每一代将代表模型的改装。在每一代中,代理(玩跳棋的模型)将玩 200 局。并且,对于每场游戏,所有导致输的棋都将有一个负奖励,而所有导致赢或和棋的棋都将得到一个正奖励。
在每一步,为了选择一个移动,我们将使用模型评估所有可能的移动,并选择具有最高评估的移动。
在每个游戏结束时设置奖励值后,我们将使用贝尔曼方程更新 Q 值,然后保存更新后的版本,以及导致最终结果的所有位置,直到生成结束。最后,在每一代结束时,用更新的 Q 值(评估)在这些游戏的所有位置上重新装配模型。
json_file = open('board_model.json', 'r')
board_json = json_file.read()
json_file.close()
reinforced_model = model_from_json(board_json)
reinforced_model.load_weights('board_model.h5')
reinforced_model.compile(optimizer='adadelta', loss='mean_squared_error')
data = np.zeros((1, 32))
labels = np.zeros(1)
win = lose = draw = 0
winrates = []
learning_rate = 0.5
discount_factor = 0.95
for gen in range(0, 500):
for game in range(0, 200):
temp_data = np.zeros((1, 32))
board = checkers.expand(checkers.np_board())
player = np.sign(np.random.random() - 0.5)
turn = 0
while (True):
moved = False
boards = np.zeros((0, 32))
if (player == 1):
boards = checkers.generate_next(board)
else:
boards = checkers.generate_next(checkers.reverse(board))
scores = reinforced_model.predict_on_batch(boards)
max_index = np.argmax(scores)
best = boards[max_index]
if (player == 1):
board = checkers.expand(best)
temp_data = np.vstack((temp_data, checkers.compress(board)))
else:
board = checkers.reverse(checkers.expand(best))
player = -player
# punish losing games, reward winners & drawish games reaching more than 200 turns
winner = checkers.game_winner(board)
if (winner == 1 or (winner == 0 and turn >= 200) ):
if winner == 1:
win = win + 1
else:
draw = draw + 1
reward = 10
old_prediction = reinforced_model.predict_on_batch(temp_data[1:])
optimal_futur_value = np.ones(old_prediction.shape)
temp_labels = old_prediction + learning_rate * (reward + discount_factor * optimal_futur_value - old_prediction )
data = np.vstack((data, temp_data[1:]))
labels = np.vstack((labels, temp_labels))
break
elif (winner == -1):
lose = lose + 1
reward = -10
old_prediction = reinforced_model.predict_on_batch(temp_data[1:])
optimal_futur_value = -1*np.ones(old_prediction.shape)
temp_labels = old_prediction + learning_rate * (reward + discount_factor * optimal_futur_value - old_prediction )
data = np.vstack((data, temp_data[1:]))
labels = np.vstack((labels, temp_labels))
break
turn = turn + 1
if ((game+1) % 200 == 0):
reinforced_model.fit(data[1:], labels[1:], epochs=16, batch_size=256, verbose=0)
data = np.zeros((1, 32))
labels = np.zeros(1)
winrate = int((win+draw)/(win+draw+lose)*100)
winrates.append(winrate)
reinforced_model.save_weights('reinforced_model.h5')
print('Checkers Board Model updated by reinforcement learning & saved to: reinforced_model.json/h5')
在运行可能需要几个小时的强化之后,云人工智能派上了用场,我们使用 matplotlib 绘制了各代的赢/平率值。
generations = range(0, 500)
print("Final win/draw rate : " + str(winrates[499])+"%" )
plot.plot(generations,winrates)
plot.show()
我们可以注意到,在第 500 代结束时,赢/平率达到最大值 85%。这被认为是一个专家水平的跳棋。
Win/draw rate through generations
然而,类似的训练技术可以被应用于一个更熟练的游戏代理,通过分配负奖励来惩罚平局。
这可能需要更多的代数和更高的计算能力来加强模型,以便在可接受的时间内达到体面的胜率。
使用模型
现在我们有了一个棋盘评估模型,问题是如何从给定的位置选择最好的棋步。基本的方法是引入一个搜索函数,搜索所有可能的走法,找到一个导致最高评价的走法。
也可以使用其他策略,例如在搜索树中更深入,并在多个回合中探索可能的位置。为了达到较大的深度(通常提前 30 步),高性能象棋和跳棋引擎使用诸如字母表修剪的方法,其中搜索算法优化了玩家可能的未来评估,同时最小化了对手可能的未来评估。
对于我们的例子,我们实现了一个简单的查找函数,在下一轮中最大化可能的评估。我们还引入了一个简单的打印功能,以可读的格式显示电路板。
def best_move(board):
compressed_board = checkers.compress(board)
boards = np.zeros((0, 32))
boards = checkers.generate_next(board)
scores = reinforced_model.predict_on_batch(boards)
max_index = np.argmax(scores)
best = boards[max_index]
return best
def print_board(board):
for row in board:
for square in row:
if square == 1:
caracter = "|O"
elif square == -1:
caracter = "|X"
else:
caracter = "| "
print(str(caracter), end='')
print('|')
让我们通过预测给定位置的变动来测试这个模型
start_board = [1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
start_board = checkers.expand(start_board)
next_board = checkers.expand(best_move(start_board))
print("Starting position : ")
print_board(start_board)
print("\nBest next move : ")
print_board(next_board)
在这个例子中,我们从游戏中间的一个位置开始,这个位置被认为是游戏中最困难的阶段。
Representation of the starting position
然后,使用前面介绍的函数 best_move ,我们找到了标记为 O 的玩家的最佳下一步棋,然后打印出结果棋盘。
Representation of the expected best move
检测到的移动遵循专家规定的跳棋基本原则,即不要让棋子滞留在棋盘中央,并尽可能用后面的棋子支持它们。这个模型和这个规则是一致的。
更进一步
本教程的跳棋引擎能够击败普通的休闲玩家,甚至体面的玩家。然而,为了生产能够与世界上最好的发动机竞争的发动机,必须采用更严格的实践:
- 必须增加世代数。
- 每一代人生产的游戏数量必须增加。
- 为了发现合适的学习率和折扣因子,必须使用一些调整策略。
- 为了发现最佳移动,必须使用 Alpha Beta 修剪深度搜索技术。
- 我们还可以考虑将专家游戏作为训练棋盘模型的初始数据集,而不是使用启发式度量。
结论
在本文中,我们展示了如何使用深度 Q 学习,一种强化学习,来开发一个能够以 85%的合理赢/平率玩跳棋的 AI 代理。
首先,我们创建了生成模型,它基于启发式跳棋度量来估计获胜概率。然后,使用它创建一个板评估模型。接下来,使用深度 Q-Learning,我们继续强化棋盘模型,让更多游戏以赢和平而不是输结束。
最后,使用我们介绍的模型,我们展示了如何从一个给定的位置选择下一步行动的最佳选择。
利用纸空间梯度和 Fast.ai 构建一流的细菌分类器
深度学习的一大承诺是它在各种复杂任务中的适用性。近年来,深度学习成功应用的领域数量激增。特别是在生物、化学、医疗保健和物理领域,已经有了巨大的突破。
在 Paperspace,我们的部分使命是为任何对 ML 研究感兴趣的人提供工具,无论他们是经验丰富的从业者还是相对较新的从业者,这些工具可以极大地提高和加快他们的生产力。吴恩达和杰瑞米·霍华德都评论说,深度学习将如何真正让领域专家在各自领域获得令人难以置信的突破,像 DeepMind 这样的组织通过将深度学习应用于蛋白质折叠等非常特定的领域,已经取得了令人难以置信的成就。
在这篇文章中,我们将展示如何使用 Fast.ai 机器学习库在梯度上建立一个最先进的细菌分类模型。我们将从理解任务和检查数据集开始。在此之后,我们将对我们的架构和培训过程做出一些决定,并评估我们的结果与当前的艺术状态相比较!
了解细菌分类
虽然看起来很模糊,但是对细菌种类进行分类的任务实际上非常有用,因为它们在我们的环境中非常普遍,并且关于它们的信息在许多领域都非常重要,包括农业和医学。建立一个可以自动识别和分类这些微生物的系统在这些领域将会非常有用,并且是今天一个开放的研究问题。这是一项异常复杂的任务。单个细菌细胞的形状可以有很大的不同,但它们在场景中的出现频率也是如此。当检查细菌菌落时,像菌落大小、质地和成分这样的因素开始起作用。
我们今天将使用的数据来自细菌物种数据集的数字图像(DIBaS),该数据是作为细菌菌落分类的深度学习方法( Zieliński 等人,2017 年)的研究的一部分汇编的。它包含了 33 种不同细菌的 660 张图片。我们将更仔细地检查他们的结果,并在稍后的帖子中比较我们自己的结果!
预处理我们的数据
这里的工作是使用 Paperspace 的渐变笔记本功能和 Fast.ai 模板完成的。使用的所有包都已经安装在这个容器中,并且可以在这个容器中访问,这有助于快速启动。DIBaS 实际上有点难以自动访问,因为它被孤立在网站的独立链接中。因此,为了自动化和节省时间,我们将利用一个抓取库来收集和解析我们的数据!让我们导入一些有用的包。
import requests
import urllib.request
import time
from bs4 import BeautifulSoup
import os
需要关注的包是BeautifulSoup
,它允许我们在抓取 HTML 页面后解析它,以搜索有用的 URL(比如保存我们下载链接的 URL)。
让我们从 DIBaS 站点获取网页并解析它!
url = 'http://misztal.edu.pl/software/databases/dibas/'
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
os.mkdir('./bacteria-dataset/full_images_alt')
既然我们已经为细菌物种数据集中的每个子文件夹解析了 URL,我们可以使用urllib
库来下载 zip 文件并解压它们!
for i in range(19,52): #'a' tags are for links
one_a_tag = soup.findAll('a')[i]
link = one_a_tag['href']
urllib.request.urlretrieve(link,'./bacteria dataset/zip_files/'+link[49:len(link)])
time.sleep(1)
import zipfilefor i in range(20,52):
one_a_tag = soup.findAll('a')[i]
link = one_a_tag['href']
zip_ref = zipfile.ZipFile('./bacteria-dataset/zip_files/'+link[49:len(link)], 'r')
zip_ref.extractall('./bacteria-dataset/full_images_alt/')
zip_ref.close()
训练我们的模型
现在我们的数据已经准备好了,我们可以继续训练我们的模型。我们将利用 Fast.ai 库的易用性、高级抽象和强大的 API。如果你已经从 Fast.ai(也称为 Fast.ai 第一部分)的《程序员实用深度学习》课程中学习了第一课,你就已经准备好理解我们在这里做的一切了!
首先,让我们从库中导入正确的模块。
from fastai.vision import *
from fastai.metrics import error_rate
我们可以设置一些配置,并使用 Fast.ai 的 get_image_files 实用程序来获取我们的文件。
bs = 64
fnames = get_image_files('bacteria-dataset/full_images_alt')
fnames[:5]
# Outputs are filepaths!
# [PosixPath('bacteria-dataset/full_images_alt/Actinomyces.israeli_0001.tif'),
# PosixPath('bacteria-dataset/full_images_alt/Actinomyces.israeli_0002.tif'),
# PosixPath('bacteria-dataset/full_images_alt/Actinomyces.israeli_0003.tif'),
# PosixPath('bacteria-dataset/full_images_alt/Actinomyces.israeli_0004.tif'),
# PosixPath('bacteria-dataset/full_images_alt/Actinomyces.israeli_0005.tif')]
现在,我们将利用 Fast.ai 中的ImageDataBunch
类,它基本上创建了一个数据结构,根据数据集文件夹的组织方式自动保存数据集和标签。在这种情况下,我们设置数据集以方便使用这个类,它开箱即用!
np.random.seed(42)
pat = r'/([^/]+)_\d+.tif$'
data = ImageDataBunch.from_name_re('bacteria-dataset/full_images_alt', fnames, pat, ds_tfms=get_transforms(), size=224, bs=bs).normalize(imagenet_stats)
现在,我们可以创建一个 CNN 架构来从我们的数据集学习。CNN 在这里被证明是非常有用的,因为我们正试图学习独立于位置的视觉特征和结构。我们将使用 ResNet34,它可能工作得非常好。ResNets 尚未在此任务中使用,是一个很好的探索领域。你可以在这里找到关于 ResNets 的有用概述,我也在这篇文章的参考资料部分包含了原始论文!
learn = create_cnn(data, models.resnet34, metrics=error_rate)
现在,为了训练我们的模型,我们将使用fit_one_cycle method
。这种方法利用了莱斯利·史密斯激动人心的论文中的策略。它使用不同的超参数配置和某些发现的规则来显著减少训练时间和提高性能。我们可以在下面看到培训过程的输出。
learn.fit_one_cycle(4)
# Outputs:
# epoch train_loss valid_loss error_rate
# 1 3.817713 2.944878 0.759124
# 2 2.632171 1.093049 0.248175
# 3 1.929509 0.544141 0.167883
# 4 1.509456 0.457186 0.145985
哇!我们的模型实际上做得很好,达到了大约 14.5%的错误率!真正的问题是这和最先进的相比如何?这个领域的开创性工作是首先包含了 DIBaS 数据集的论文。他们测试了几种不同的方法,从 CNN 到更传统的方法,如使用各种随机森林的支持向量机。他们最好的结果是大约 97%的准确率,比我们的好得多。
那么,我们如何改进我们的方法呢?嗯,ResNet 非常强大,所以我们可能希望使用像 ResNet50 这样更重的架构。我们可以使用lr_find()
方法找到最佳学习率,并使用它来尝试和改进我们的模型。
learn = create_cnn(data, models.resnet50, metrics=error_rate)
learn.lr_find()
learn.recorder.plot()
这个图表告诉我们哪里的学习率对 los 影响最大——非常酷!为了便于参考,让我们在不使用学习率值范围知识的情况下进行训练。同样的训练方法我们可以用 8 个周期。
learn.fit_one_cycle(8)
# epoch train_loss valid_loss error_rate
# 1 2.853813 1.561166 0.306569
# 2 1.639013 0.248170 0.058394
# 3 1.101536 0.230741 0.080292
# 4 0.781610 0.159655 0.043796
# 5 0.587977 0.132877 0.036496
# 6 0.455316 0.115520 0.036496
# 7 0.356362 0.108675 0.029197
# 8 0.293171 0.109001 0.029197
有意思!我们可以看到,这个模型比我们之前的模型好了很多,并且基本上与论文中概述的性能相同!97.1%的准确率没什么好嘲笑的!
但是如果我们使用我们之前学到的学习率的知识呢?让我们将一个周期的培训过程限制在学习率对损失影响最大的范围内。
learn.save('stage-1-50')
learn.unfreeze()
learn.fit_one_cycle(3, max_lr=slice(1e-6,1e-4))
# Outputs
# epoch train_loss valid_loss error_rate
# 1 0.178638 0.100145 0.021898
# 2 0.176825 0.093956 0.014599
# 3 0.159130 0.092905 0.014599
哇!我们的新模型达到了 98.5%的准确率,这绝对超过了原来的论文。当然,原始论文来自 2017 年,应用像 ResNet 这样非常强大的模型会产生很大的结果,这是有道理的。
结论
我们已经设法从一个许多人可能不熟悉的领域的一项任务中获得了一些非常惊人的结果,而且由于 Gradient 和 Fast.ai,我们完成得相当快。当然,这是有用的。自 2017 年以来,这个领域没有太多进展,所以可能有更好、更微妙的方法,而不是在这个问题上扔 ResNets。展望未来,我们可能会尝试不同的方法来完成这一细菌分类任务,或者甚至尝试解决一些其他新的数据集!你认为还有哪些深度学习架构可能有用?
如果你有一个 ML 相关的项目或想法,你一直想尝试,考虑使用 Paperspace 的渐变平台!这是一个神奇的工具,允许您做许多有用的事情,包括用笔记本探索和可视化,运行严肃的训练周期和 ML 管道,还可以将您训练的模型部署为端点!点击了解更多关于我们支持 GPU 的免费 Jupyter 笔记本电脑的信息!
参考文献
贺,张,谢,任,孙,(2016)。用于图像识别的深度残差学习。在IEEE 计算机视觉和模式识别会议论文集(第 770-778 页)。
史密斯律师事务所(2018 年)。神经网络超参数的训练方法:第 1 部分:学习速率、批量大小、动量和权重衰减。 arXiv 预印本 arXiv:1803.09820 。
Zieliński,b .,Plichta,a .,Misztal,k .,Spurek,p .,Brzychczy-woch,m .,和 Ochońska,D. (2017)。细菌菌落分类的深度学习方法。 PloS one , 12 (9),e0184554。
用遗传算法构建 CoinTex 游戏代理
原文:https://blog.paperspace.com/building-agent-for-cointex-using-genetic-algorithm/
游戏可以有复杂的环境,其中许多不同的因素有助于作出决定。人工智能的整个领域都致力于构建能够做出正确决策的游戏代理,就像专业人员一样玩游戏。
在本教程中,我们将看到如何仅使用遗传算法构建一个游戏代理来玩一个名为 CoinTex 的游戏,该游戏是在 Kivy Python 框架中开发的。CoinTex 的目标是收集随机分布的硬币,同时避免与火和怪物(随机移动)发生冲突。CoinTex 的源代码可以在 GitHub 上找到。
遗传算法是这里使用的唯一人工智能;没有其他机器/深度学习模型与之一起使用。我们将使用 PyGad 实现遗传算法。本教程首先快速概述 CoinTex,然后简要解释遗传算法,以及如何使用它来创建播放代理。最后,我们将看到如何在 Python 中实现这些思想。
遗传算法代理的源代码可以从这里获得,你可以从这里下载本教程中使用的代码。
本教程的大纲如下:
- CoinTex 概述
- 游戏是如何运作的?
- 获取屏幕引用
- 获取硬币位置
- 获得怪物位置
- 获得射击位置
- 安装 PyGAD
- 代理是如何工作的?
- 构建生成回调函数
- 使用 PyGAD 创建遗传算法的实例
- 进一步阅读
要直接跳到涉及遗传算法的部分,点击这里。
CoinTex 概述
CoinTex 是一款开源的跨平台 Python 3 游戏,使用 Kivy 框架创建。游戏的源代码可以在 GitHub 上获得——也可以随意下载和运行游戏。
CoinTex 是作为《使用 Kivy 和 Android Studio 用 Python 构建 Android 应用程序一书的一部分开发的。CoinTex 在第 5 章和第 6 章中从头到尾都有记录,所以本教程不会涵盖关于开发游戏的所有内容;只是可能有助于使用遗传算法构建代理的细节。
游戏中的单个 Python 脚本叫做main.py
。这个游戏有一个名为CointexApp
的类,它扩展了 Kivy 框架的kivy.app.App
类。游戏的 GUI (widget tree)内置在一个名为cointex.kv
的 KV 文件中。
要运行游戏,只需运行main.py
Python 脚本。要从 Mac/Linux 终端运行游戏,请使用python3
,而不是python
,因为 CoinTex 是用 Python 3 开发的。
运行一个 Kivy 应用程序就是创建一个扩展基类kivy.app.App
的类的新实例,并调用它的run()
方法。下面是在main.py
文件末尾运行游戏的代码行。变量app
是CointexApp
类的一个实例。
app = CointexApp()
app.run()
运行游戏后,会出现如下所示的窗口。游戏目前有 24 个关卡。击败前一关后,一个关卡被激活。在下面的例子中,我只通过了 10 级,因此这 10 级加上下一级,11 级被激活。
一旦选择了一个级别,就会出现一个新的屏幕。接下来的窗口显示第三层的屏幕截图。玩家通过触摸屏幕来移动,这使得角色移动到被触摸的位置。
在屏幕的顶部有一个标签,除了显示关卡中的硬币总数外,还显示收集到的硬币数量。你可以看到我刚刚从总共 12 枚硬币中收集了 4 枚。在它旁边,你可以看到当前的等级数,以及红色条显示的玩家的健康状况。一旦不再有生命值,玩家就死了。
要通过关卡,必须收集所有的硬币。一旦通过一关,下一关就被激活。
怪物的移动是随机的,屏幕上硬币的分布也是随机的。这意味着每次游戏中硬币的位置会有所不同。玩家在收集硬币时面临两个障碍:怪物和火,它们以预定的路径被扔过屏幕。下图显示了第 20 层的一些火灾。
下一节给出了一些关于游戏动作回调函数的简单细节,从开始游戏到关卡完成或者玩家死亡。
这个游戏是如何运作的?
游戏运行后,第一个要调用的方法是on_start()
。该方法基于传递的最后一个级别激活级别,之后激活级别的网格打开。
一旦用户选择了一个级别,在输入之前,回调方法screen_on_pre_enter()
被调用。在这种方法中,硬币在屏幕上的位置是随机选择的。
进入一个级别后,调用方法screen_on_enter()
。这个方法启动怪物和火的运动。负责启动怪物移动的方法叫做start_monst_animation()
,负责开火的方法叫做start_fire_animation()
。怪物/火运动一旦开始,它们就不会停止,除非角色死亡或者关卡完成。
一旦怪物/火焰移动,那么一个新的位置被分配给它的pos_hint
属性。在名为cointex.kv
的 KV 文件中,有一个分配给每个怪物/火焰的回调方法,当pos_hint
属性发生变化时调用该方法。怪物的回调方法被命名为monst_pos_hint()
。对于火,这种方法被命名为fire_pos_hint()
。
在每个回调方法中,怪物和火的位置与玩家的位置进行比较。如果玩家离怪物/火太近,那么就会发生碰撞,玩家的生命值会降低。如果生命值变成 0,玩家就会死亡。玩家的任务是收集所有的硬币,同时保持其生命值在 0 以上。
pos_hint
属性将一个字典作为一个值,该字典包含下面列出的两个条目。这两个项目的按键是'x'
和'y'
。它们的值指定小部件的 x 轴和 y 轴位置。它们的值在 0.0 之间。还有 1.0。
pos_hint = {'x': 0.2, 'y': 0.4}
对于 x 轴,值为 0.0。表示在屏幕的最左侧,而 1.0 表示在屏幕的最右侧。对于 y 轴,为 0.0。表示屏幕底部,而 1.0 表示屏幕顶部。当小部件位于屏幕中央时,pos_hint
属性的值就是{'x': 0.5, 'y': 0.5}
。
类似于怪物/火焰,玩家也有一个pos_hint
属性。更改时,调用名为char_postion_hint()
的回调方法。这种方法通过比较玩家的位置和所有未收集的硬币来检查是否发生碰撞。一旦玩家碰撞到硬币,硬币就会被收集。
根据上面的讨论,玩家应该收集所有的硬币,同时避免与怪物和火碰撞。
考虑构建一个自己玩游戏的代理,代理应该完成两个任务:
- 代理人应该将玩家的位置改变到更靠近硬币的位置来收集硬币。
- 代理人要把玩家的位置换到远离怪物和火焰的地方,以免被杀死。
因此,有助于代理做出决策的输入是:
- 硬币位置。
- 怪物的位置[如果有的话]。
- 火场位置[如果有的话]。
代理的输出是新玩家的位置。
请注意,有些级别可能有怪物,火,两者兼而有之,或者没有。
下图总结了代理的输入和输出。
在讨论如何获得硬币、怪物和火焰的位置之前,下一节讨论如何获得对关卡屏幕的引用。
获取屏幕参考
每一关都有对应的 Kivy 画面。该屏幕包含所有关于关卡的信息。这包括关于玩家、硬币、怪物、火等等的信息。因此,要获得关于某个级别的一些信息,首先要做的是获得对该级别的引用。
通常,对级别的引用根据下一行返回,其中app
是使用此行app = CointexApp()
创建的CointexApp
类的实例。lvl_num
是指从 1 开始的级数。
curr_screen = app.root.screens[lvl_num]
一旦屏幕引用可用,就可以访问任何类型的信息。
接下来的 3 个部分讨论了如何获得硬币、怪物和火焰的位置。
获取硬币位置
在屏幕内,有一个名为coins_ids
的字典,它保存着未收集的硬币的位置。被收集的硬币从字典中删除。
字典根据下一行从当前屏幕返回。
coins = curr_screen.coins_ids
字典中的每一项都包含了硬币的位置。下一个代码返回字典中第一个硬币的位置。
curr_coin = coins[list(coins.keys())[0]]
curr_coin_center = [curr_coin.pos_hint['x'], curr_coin.pos_hint['y']]
这是需要知道的硬币进行。下一节讨论如何获得怪物的位置。
获得怪物位置
对于每个屏幕,都有一个名为num_monsters
的属性来保存屏幕上怪物的数量。下一行打印该属性的值。
print(curr_level.num_monsters)
怪物的部件被赋予一个这样的 ID,其中monst_num
是屏幕中怪物的编号,lvl_num
是等级编号。
monster{monst_num}_image_lvl{lvl_num}
比如 5 屏/关卡第一个怪物的名字是 monster1_image_lvl5 。
下一行返回给定怪物编号和等级编号的怪物的引用。
monster_image = curr_screen.ids['monster'+str(monst_num)+'_image_lvl'+str(lvl_num)]
下一段代码创建了一个名为monsters_pos
的列表,它保存了屏幕中所有编号为lvl_num
的怪物的位置。
monsters_pos = []
for i in range(curr_screen.num_monsters):
monster_image = curr_screen.ids['monster'+str(i+1)+'_image_lvl'+str(lvl_num)]
monsters_pos.append([monster_image.pos_hint['x'], monster_image.pos_hint['y']])
通过获得怪物的位置,代理将能够建议一个远离它们的位置,以避免杀死玩家。
下一节讨论获取火灾的位置。
获取射击位置
火的待遇和怪物差不多。唯一的变化是使用了火这个词,而不是怪物。下一个代码创建了一个名为fires_pos
的列表,它保存了编号为lvl_num
的屏幕中所有火的位置。
fires_pos = []
for i in range(curr_screen.num_fires):
fire_image = curr_screen.ids['fire'+str(i+1)+'_lvl'+str(lvl_num)]
fires_pos.append([fire_image.pos_hint['x'], fire_image.pos_hint['y']])
基于硬币、怪物和火的位置,代理将能够选择玩家的新位置,该位置考虑收集硬币而不与怪物或火碰撞。
通过为代理准备所有需要的输入,接下来是讨论使用遗传算法为 CoinTex 构建一个游戏代理的想法。
建议先对遗传算法有个概念再看下一节。阅读教程遗传算法优化简介开始学习。
代理是如何工作的?
玩游戏的代理只使用遗传算法来构建。遗传算法本身被用来做出决定,告诉球员移动到哪里。没有使用机器/深度学习模型。
游戏代理的任务是收集所有的硬币,同时避免与怪物和火的碰撞。这一节讨论代理人如何决定玩家移动到的下一个位置,考虑硬币、怪物和火的位置。
让我们首先讨论代理如何使用硬币的位置来决定玩家的下一个位置。
如下面的 GIF 图片所示,屏幕上有不止一枚硬币。代理不会一次考虑所有硬币。相反,代理人只设置 1 个硬币作为目标。在收集到这个硬币后,一个新的硬币被设置为目标。这个过程一直持续到收集到所有的硬币。
通常,代理从左到右收集硬币,但这可能会根据怪物和火的位置而变化。
下图显示了收集硬币的顺序,其中第一个目标硬币用橙色圆圈突出显示,最后一个硬币用红色圆圈突出显示。
在收集当前的目标硬币后,代理设置一个新硬币作为目标。该过程通过收集当前的目标硬币并将新硬币设置为目标来继续,直到收集到所有硬币。
假设第一枚硬币的位置是(x, y)=(0.1, 0.15)
,那么基于遗传算法的当前群体中可用的解决方案,代理选择将玩家放置在尽可能接近目标硬币的位置的解决方案。这是通过计算群体中每个解/位置和目标硬币位置之间的距离来实现的。
假设变量solution
是保存来自群体的位置的列表,并且变量curr_coin_center
是目标硬币的位置的列表,则距离计算如下:
output = abs(solution[0] - curr_coin_center[0]) + abs(solution[1] - curr_coin_center[1])
假设人口中有 4 个解决方案,如下所列。
Solution 1 : 0.2, 0.3
Solution 2 : 0.1, 0.9
Solution 3 : 0.7, 0.4
Solution 4 : 0.8, 0.7
如下计算,这 4 个解与目标硬币位置(0.1, 0.15)
之间的距离分别为 0.25、0.95、0.85 和 1.25。基于这些距离,首选解决方案是第一个解决方案(0.6, 0.3)
,因为它具有最小的距离(0.25)。因此,玩家的下一个位置将是(0.6, 0.3)
。
Solution 1 : abs(0.2 - 0.1) + abs(0.3 - 0.15) = 0.1 + 0.15 = 0.25
Solution 2 : abs(0.1 - 0.1) + abs(0.9 - 0.15) = 0.2 + 0.75 = 0.95
Solution 3 : abs(0.7 - 0.1) + abs(0.4 - 0.15) = 0.6 + 0.25 = 0.85
Solution 4 : abs(0.8 - 0.1) + abs(0.7 - 0.15) = 0.7 + 0.55 = 1.25
记住,遗传算法的适应度函数应该是最大化函数,以便具有更高适应度值的解决方案是优选的。由于这个原因,计算的距离是颠倒的。下一个代码显示了如何计算适应值(反向距离)。适应性值保存在变量output
中。
output = abs(solution[0] - curr_coin_center[0]) + abs(solution[1] - curr_coin_center[1])
output = 1.0 / output
下面列出了 4 种解决方案的适合度值。根据反向距离,群体的最佳解仍然是值为 4.0 的第一个解。这意味着玩家的新位置是(0.6, 0.3)
。
Solution 1 : 1 / 0.25 = 4.0
Solution 2 : 1 / 0.95 = 1.05
Solution 3 : 1 / 0.85 = 1.18
Solution 4 : 1 / 1.25 = 0.8
根据上面的讨论,很清楚硬币位置是如何用来决定玩家的下一个位置的。但是硬币的位置不是唯一的因素,还有怪物和火的位置。
代理的目标不仅是收集硬币,而且要避免与怪物和火的碰撞。下图中的箭头标记了代理要收集的目标硬币。如果代理人选择[从人群中]最接近硬币的解决方案,那么它可能会与怪物相撞,导致玩家的生命值下降,并可能杀死它。因此,代理人在决定玩家的下一个位置时应该考虑怪物的位置。
为了避免到达可能导致玩家健康减少的位置,接近至少一个怪物的解的健康值通过减少它们的值来惩罚。这就是怪物的位置如何帮助决定玩家的下一个位置。
假设monsters_pos
是一个包含所有怪物位置的嵌套列表,下面是计算解决方案和怪物位置之间的距离并更新output
变量中计算的适应值的代码。
如果解在怪物周围 0.3 宽 0.3 高的一个方框内,那么适应度值减少 300,将解标记为危险。否则,适应值增加 100,以将该解决方案标记为安全。用户可以自由选择其他值,并查看它们如何影响代理。
for monst_pos in monsters_pos:
char_monst_h_distance = abs(solution[0] - monst_pos[0])
char_monst_v_distance = abs(solution[1] - monst_pos[1])
if char_monst_h_distance <= 0.3 and char_monst_v_distance <= 0.3:
output -= 300
else:
output += 100
火的待遇和怪物差不多。假设嵌套列表fires_pos
保存所有火的位置,那么下一个代码基于火的位置更新适合度值。如果遗传算法建议的解在火堆周围 0.3 宽 0.3 高的一个方框内,那么适应度值减少 300。否则增加 100。
for fire_pos in fires_pos:
char_fire_h_distance = abs(solution[0] - fire_pos[0])
char_fire_v_distance = abs(solution[1] - fire_pos[1])
if char_fire_h_distance <= 0.3 and char_fire_v_distance <= 0.3:
output -= 300
else:
output += 100
在讨论了代理如何使用硬币、怪物和火的位置来计算解决方案的适应值之后,下面是将本节中讨论的代码放在单个块中的代码。
# solution: A potential position to the player suggested by the genetic algorithm.
# curr_coin_center: The position of the targeted coin.
# monsters_pos: A nested list of the monsters' positions.
# fires_pos: A nested list of the fires' positions.
output = abs(solution[0] - curr_coin_center[0]) + abs(solution[1] - curr_coin_center[1])
output = 1.0 / output
for monst_pos in monsters_pos:
char_monst_h_distance = abs(solution[0] - monst_pos[0])
char_monst_v_distance = abs(solution[1] - monst_pos[1])
if char_monst_h_distance <= 0.3 and char_monst_v_distance <= 0.3:
output -= 300
else:
output += 100
for fire_pos in fires_pos:
char_fire_h_distance = abs(solution[0] - fire_pos[0])
char_fire_v_distance = abs(solution[1] - fire_pos[1])
if char_fire_h_distance <= 0.3 and char_fire_v_distance <= 0.3:
output -= 300
else:
output += 100
现在,代理如何工作的一切都很清楚了。下一步是使用遗传算法构建代理。
在这个项目中,遗传算法是使用名为 PyGAD 的库构建的。下一节讨论安装 PyGAD 。
安装 PyGAD
必须安装 PyGAD 才能继续这个项目。你可以从 PyPI 下载它的轮子文件,或者使用pip
安装程序,如下所示。
pip install pygad>=2.4.0
对于 Linux/Mac,使用pip3
,因为 PyGAD 是用 Python 3 开发的。查看文档了解关于 PyGAD 中参数、类和子模块的更多信息。你也可以查看我的教程,使用 PyGAD 的 5 个遗传算法应用,它更深入地介绍了这个库,并展示了如何在 5 个不同的用例中使用它。
使用遗传算法解决的每个问题都有一个适应度函数。这是一个必须仔细设计的关键参数,因为它用于判断解决方案是好是坏。
建立适应度函数
遗传算法中的适应度函数是接受由算法产生的解作为输入并返回适应度值作为输出的函数。适应值越高,解决方案越好。
在 PyGAD 中,fitness 函数是一个常规的 Python 函数,它接受两个参数作为输入:
- 解决方法。
- 总体解的指数。
该函数应该返回一个代表解的适合度值的数值。用户的工作是适当地构建适应度函数,以便它表示问题被很好地解决了。
以下是为 PyGAD 构建健身函数的模板:
def fitness_function(solution, solution_idx):
...
fitness = ...
return fitness
如前所述,代理人根据硬币、怪物和火的当前位置决定玩家的下一个位置。适应度函数使用这些位置来计算适应度值。
下面列出了适应度函数的完整实现。请注意,其 90%的代码前面已经讨论过了。
第一行返回对当前级别屏幕的引用。变量lvl_num
的值在screen_on_enter()
回调方法中设置,这将在后面讨论。
返回保存所有未收硬币位置的字典coins_ids
。如果至少有一枚未回收的硬币,则该函数会继续计算适合度值。目标硬币的字典项目被返回到curr_coin
变量中,然后该变量用于计算进入curr_coin_center
变量的硬币中心。
该函数的剩余代码根据目标硬币、怪物和火的位置计算适应值。在函数结束时,返回计算出的适应值。
def fitness_func(solution, solution_idx):
curr_screen = app.root.screens[lvl_num]
coins = curr_screen.coins_ids
if len(coins.items()) == 0:
return 0
curr_coin = coins[list(coins.keys())[0]]
curr_coin_center = [curr_coin.pos_hint['x'], curr_coin.pos_hint['y']]
output = abs(solution[0] - curr_coin_center[0]) + abs(solution[1] - curr_coin_center[1])
output = 1.0 / output
monsters_pos = []
for i in range(curr_screen.num_monsters):
monster_image = curr_screen.ids['monster'+str(i+1)+'_image_lvl'+str(lvl_num)]
monsters_pos.append([monster_image.pos_hint['x'], monster_image.pos_hint['y']])
for monst_pos in monsters_pos:
char_monst_h_distance = abs(solution[0] - monst_pos[0])
char_monst_v_distance = abs(solution[1] - monst_pos[1])
if char_monst_h_distance <= 0.3 and char_monst_v_distance <= 0.3:
output -= 300
else:
output += 100
fires_pos = []
for i in range(curr_screen.num_fires):
fire_image = curr_screen.ids['fire'+str(i+1)+'_lvl'+str(lvl_num)]
fires_pos.append([fire_image.pos_hint['x'], fire_image.pos_hint['y']])
for fire_pos in fires_pos:
char_fire_h_distance = abs(solution[0] - fire_pos[0])
char_fire_v_distance = abs(solution[1] - fire_pos[1])
if char_fire_h_distance <= 0.3 and char_fire_v_distance <= 0.3:
output -= 300
else:
output += 100
fitness = output
return fitness
在 PyGAD 中,pygad.GA
类的构造函数中有一个名为fitness_func
的参数,它接受预先准备好的适应度函数。
在构建了适应度函数之后,在下一节中还要创建另一个函数。这个函数在每次生成完成后被调用。
构建生成回调函数
本节讨论构建一个回调函数,在完成遗传算法的一代后调用。我们来讨论一下为什么需要这样的功能。
对于每一代,PyGAD 使用适应度函数来计算群体中所有解的适应度值。一代完成后,一定有办法在上一代进化的种群中找到最佳解。这个最佳方案是用来移动玩家的。
在 PyGAD 的pygad.GA
类中,有一个名为callback_generation
的参数,它接受在每一代之后调用的函数。该函数必须接受一个引用pygad.GA
类实例的参数。
在这个项目中,这个函数将用于寻找遗传算法创建的最佳解决方案,并根据解决方案的位置移动玩家。下面列出了该函数的实现。
简单地说,如果还有一些硬币需要收集,并且适应值与上一代中的最佳值不同,该函数会将玩家移动到当前代中最佳解决方案的位置。
调用pygad.GA
类中的best_solution()
方法来获取当前代中最佳解决方案的信息。best_solution()[0]
返回最佳解,best_solution()[1]
返回最佳解的适应度值。
在函数外部,定义了一个名为last_fitness
的全局变量。它被赋予上一代中的最佳解的适应度。
基于当前和前几代中最佳解决方案的适应值,在fitness_change
变量中计算适应值的变化。如果fitness_change
的值为 0,那么就没有必要移动玩家。
一旦没有更多的硬币要收集,或者当角色被杀死时,该方法返回字符串stop
,这反过来停止遗传算法。
调用CointexApp
类中定义的方法start_char_animation()
将玩家移动到最佳解决方案的位置。它接受当前的屏幕号和位置。
last_fitness = 0
def callback_generation(ga_instance):
global last_fitness
best_sol_fitness = ga_instance.best_solution()[1]
fitness_change = best_sol_fitness - last_fitness
curr_screen = app.root.screens[lvl_num]
last_fitness = best_sol_fitness
coins = curr_screen.coins_ids
if len(coins.items()) == 0 or curr_screen.character_killed:
# After either the level is completed or the character is killed, then stop the GA by returning the string "stop".
return "stop"
elif len(coins.items()) != 0 and fitness_change != 0:
best_sol = ga_instance.best_solution()[0]
app.start_char_animation(lvl_num, [float(best_sol[0]), float(best_sol[1])])
一旦适应度和回调函数准备好了,就该使用 PyGAD 创建一个遗传算法的实例并运行它了。这将在下一节讨论。
使用 PyGAD 创建遗传算法的实例
PyGAD 有一个名为GA
的类,用于创建遗传算法的实例。在 PyGAD 2.4.0 中,pygad.GA
类的构造函数接受 20 个参数。在这些参数中,下一段代码使用这个项目所需的参数来创建一个实例。
请注意,fitness_func
参数被赋予之前创建的适应度函数。同样,callback_generation
参数接受生成回调函数。
num_genes
参数被赋予值 2,因为每个解只有 2 个值(玩家的 x 和 y 位置)。
random_mutation_min_val
和random_mutation_max_val
参数被赋予值 0.0 和 1.0,因为 x 和 y 位置被限制在该范围内。将mutation_by_replacement
参数设置为True
以保持 x 和 y 位置在 0.0 到 1.0 的范围内是很重要的。
delay_after_gen
参数用于指定玩家每两次后续移动之间的延迟秒数。如果玩家在时间 0.0 移动并且delay_after_gen
参数设置为 0.3 秒,那么玩家的下一个位置将在 0.3 秒后指定。
请注意,这 0.3 秒并没有浪费,而是用来启动动画,将玩家移动到选定的位置。为了获得最佳体验,分配给delay_after_gen
参数的时间应该等于动画角色所经过的时间。动画时间在char_anim_duration
属性中的每个级别都可用。
有关pygad.GA
类构造函数中参数的详细信息,请查阅 PyGAD 文档。
import pygad
ga_instance = pygad.GA(num_generations=9999,
num_parents_mating=300,
fitness_func=fitness_func,
sol_per_pop=1000,
num_genes=2,
init_range_low=0.0,
init_range_high=1.0,
random_mutation_min_val=0.0,
random_mutation_max_val=1.0,
mutation_by_replacement=True,
callback_generation=callback_generation,
delay_after_gen=app.root.screens[lvl_num].char_anim_duration)
运行创建的pgad.GA
类的实例,然后调用run()
方法。
ga_instance.run()
在应用程序的主线程中运行遗传算法以避免处理它并不是一个好主意。相反,在新线程中运行该算法。下一段代码创建一个名为CollectCoinThread
的新线程,它创建并运行pygad.GA
类的实例。
这个调用的构造函数接受一个名为screen
的参数,该参数指向当前屏幕/级别。
import threading
class CollectCoinThread(threading.Thread):
def __init__(self, screen):
super().__init__()
self.screen = screen
def run(self):
ga_instance = pygad.GA(num_generations=9999,
num_parents_mating=300,
fitness_func=fitness_func,
sol_per_pop=1000,
num_genes=2,
init_range_low=0.0,
init_range_high=1.0,
random_mutation_min_val=0.0,
random_mutation_max_val=1.0,
mutation_by_replacement=True,
callback_generation=callback_generation,
delay_after_gen=self.screen.char_anim_duration)
ga_instance.run()
下一节将讨论当关卡/屏幕启动时,如何启动遗传算法。
启动遗传算法
要回答的一个重要问题是从哪里开始CollectCoinThread
线程?答案在CointexApp
类的screen_on_enter()
回调方法中。当一个屏幕(即关卡)打开时,调用screen_on_enter()
方法。
在这个方法的末尾,只需放置下一个代码。它创建一个名为lvl_num
的全局变量来保存当前级别/屏幕的编号。然后,创建了一个新的CollectCoinThread
线程实例。最后,线程通过调用start()
方法启动。
global lvl_num
lvl_num = screen_num
collectCoinThread = CollectCoinThread(screen=curr_screen)
collectCoinThread.start()
此时,项目已经完成。下一节将提到 GitHub 项目,该项目包含 CoinTex 和代理的代码。
GitHub 上的项目
CoinTex 的源代码可以在 ahmedfgad/CoinTex GitHub 项目中获得。
遗传算法代理的源代码可以在Ahmed gad/CoinTex/player ga目录下获得。此目录可能会在发布教程后更新。教程中讨论的确切代码可以在ahmedfgad/CoinTex/player ga/tutorial project目录下找到。
延伸阅读
- 文章:遗传算法优化简介
- 教程: 5 使用 PyGAD 的遗传算法应用
- 教程:遗传算法在 Python 中的实现
- 书:使用深度学习的实用计算机视觉应用与 CNN
- 教程: Python for Android:开始构建 Kivy 跨平台应用
- Book: 在 Android Studio 中使用 Kivy 用 Python 构建 Android 应用程序
结论
该教程讨论了如何使用遗传算法为一个名为 CoinTex 的游戏创建一个游戏代理。这款游戏是开源的,跨平台的,使用 Kivy 用 Python 开发,可用于 Android 。
PyGAD ,一个开源的 Python 库,用于实现遗传算法。
我们看到,我们的代理可以有效地工作,即使在困难的水平,有许多硬币,怪物,和火。
为计算机视觉算法建立图像数据集
原文:https://blog.paperspace.com/building-computer-vision-datasets/
根据我的经验,数据是我们在计算机视觉领域开发任何 ML/AI 管道时面临的最具挑战性的问题之一。这个问题有两个方面:
- 数据收集
- 数据标记
在本帖中,我们将深入探讨这些挑战,并使用最有效的方法和工具来应对它们。
数据收集
为了构建您的数据集,我建议使用 web 抓取以高效的方式收集大型图像数据集。这篇文章将帮助你理解如何管理来自 web 的图像数据集,这些数据集受 creative commons 许可的保护。
在本教程中,我们将学习如何使用 Python 从名为 unsplash 的网站上抓取图像。
以下是本教程的先决条件:
- Python 的基础知识
- Python 3.7 环境
- Python 库:urllib,tqdm,concurrent.futures,requests,BeautifulSoup
我们将从学习如何查询从 unsplash 网站下载的图像开始本教程。
本教程将分为两个部分:
- 网站查询-了解我们如何获得抓取的 URL
- Python 编码——自动化 web 抓取
网站查询
- 使用谷歌浏览器前往 https://unsplash.com/。
- 右键单击网页并选择检查选项。
- 选择网络- > XHR 选项。XMLHttpRequest (XHR)是一个对象形式的 API,其方法在 web 浏览器和 web 服务器之间传输数据。
- 这将为我们提供网站 URL,我们需要向其发送请求,以查询用于下载的搜索图像 URL。
- 选择如下所示的“Response”选项,查看 ping 请求 URL 时得到的响应。正如你所看到的,它是 JSON 数据的形式,我们可以使用内置的 JSON 解码器作为
request
库的一部分来读取它。查询要抓取的总页数、图片的 URL 和 id 所需的关键字可以在这里找到。 - Requests 是一个漂亮而简单的 Python HTTP 库,是为高级用途而构建的。通过利用这个易于使用的 API,它消除了发出 HTTP 请求的复杂性,您可以专注于与网站的交互。
Python 编码
现在,让我们深入了解如何编写代码来实现 web 抓取的自动化。
我们将从导入 python 中所有需要的库开始。
import argparse, urllib3, os, requests
from tqdm import trange,tqdm
from concurrent.futures import ProcessPoolExecutor,as_completed
如果我们没有这些库中的任何一个,你可以使用“pip3”来下载它们,它是 python 的一个包安装程序。
在我们开始创建端到端优化脚本之前,让我们先测试一下是否可以查询一个图像并下载它。
这里我们从定义网站的base_url
开始,接下来我们使用requests
库创建一个session
。
根据您尝试执行的操作,有许多 HTTP 方法可供您使用。最常见的是 GET 和 POST(https://en . Wikipedia . org/wiki/Hypertext _ Transfer _ Protocol # Request _ methods)。对于我们的场景,我们需要使用 GET 方法从指定的网站获取或检索数据/内容。要发出GET
请求,调用requests.get()
或requests.Session().get()
。
如果您向同一个端点发出多个请求,那么最好通过调用requests.Session()
来使用会话,因为它将保持连接之间的 TCP 会话打开,保留一个 cookie jar 并记住每个请求的任何参数,这可以显著提高性能。
website = "https://www.unsplash.com"
session = requests.Session()
search = 'face'
base_url = website+"/napi/search/photos?query={0}&xp=&per_page=20&".format(search)
response = session.get(base_url)
urls=[]
if response.status_code == 200 :
results = response.json()['results']
urls = urls+[(url['id'],url['urls']['raw']) for url in results]
urllib.request.urlretrieve(urls[0][1],'./'+urls[0][0]+'.jpg')
当您调用response.status_code
时,200 OK
状态表示您的请求成功。还有其他代码(【https://en.wikipedia.org/wiki/List_of_HTTP_status_codes】)可以让你知道你的请求的状态。
一旦response.status_code == 200
我们就可以使用内置的 JSON 解码器来获得结果字典。字典的关键字可以在上面的网站查询部分找到。
现在我们有了一个示例图像的 URL,我们将使用urllib
包来下载这个图像。
要获得抓取所需的 URL,您也可以使用漂亮的 soup library 来代替请求。它是一个工具,你可以用它来剖析一个文档,并从 HTML 页面中提取你所需要的内容。然而,与 unsplash 不同,在网站没有后端 api 的情况下,它更有优势。它非常易于使用,并且从易于导航的网页中提取内容。下面是一个关于如何在本教程中使用漂亮的 soup 代替 URL 请求的例子:
from bs4 import BeautifulSoup
import json
urls=[]
if response.status_code == 200 :
results = BeautifulSoup(response.content,'html.parser')
results = json.loads(str(results))['results']
urls = urls+[(url['id'],url['urls']['raw']) for url in results]
urllib.request.urlretrieve(urls[0][1],'./'+urls[0][0]+'.jpg')
让我们使用concurrent.futures
库来调用ProcessPoolExecutor
以支持多处理,从而为 web 抓取编写一个端到端的管道来构建数据集。
import argparse, urllib, os, requests
from tqdm import trange,tqdm
from concurrent.futures import ProcessPoolExecutor,as_completed
class _unsplash(object):
def __init__(self):
self.website = "https://www.unsplash.com"
self.session = requests.Session()
def __call__(self,search,pages=None):
base_url = self.website+"/napi/search/photos?query {0}&xp=&per_page=20&".format(search)
if not pages:
pages = self.session.get(base_url).json()['total_pages']
urls=[]
for page in trange(1,pages+5,desc = "Downloading image URLs"):
search_url = self.website+"/napi/search/photos?query={0}&xp=&per_page=20&page={1}".format(search,page)
response = self.session.get(search_url)
if response.status_code == 200 :
results = response.json()['results']
urls = urls+[(url['id'],url['urls']['raw']) for url in results]
return list(set(urls))
unsplash = _unsplash()
class _download_imgs(object):
def __init__(self,output_dir,query):
self.query = query
self.directory = output_dir+'/'+query
if not os.path.isdir(self.directory) : os.makedirs(self.directory)
def __call__(self,urls):
with ProcessPoolExecutor() as pool:
downloads = [pool.submit(urllib.request.urlretrieve,url[1],self.directory+'/'+url[0]+'.jpg') for url in urls]
for download in tqdm(as_completed(downloads),total=len(downloads),desc='Downloading '+self.query+" images...."):
pass
class _scrape(object):
def __call__(self,args):
if args.w.lower() == 'unsplash' : urls = unsplash(args.q.lower(), args.p)
download_imgs = _download_imgs(args.o,args.q.lower())
download_imgs(urls)
scrape=_scrape()
if __name__=='__main__':
parser = argparse.ArgumentParser(description='Web Scraping')
parser.add_argument('-w', default='unsplash',choices = ['unsplash'], metavar = 'website', required = False, type = str,
help = 'name of the website that you want to scrape data from, example: unsplash')
parser.add_argument('-q', metavar = 'query', required = True, type = str,
help = 'search term for query, example: mushroom')
parser.add_argument('-o', metavar = 'output directory', required = True, type = str,
help = 'Path to the folder where you want the data to be stored, the directory will be created if not present')
parser.add_argument('-p', metavar = 'no of pages', type = int, help = 'Number of pages to scrape')
args = parser.parse_args()
scrape(args)
将上面的脚本保存在 webscrape.py 下,并从命令行运行它。
python webscrape.py -q mushroom -o /Users/spandana/WebScraping/WebScraping/data -p 1
现在,您已经准备好使用 web scraping 构建您自己的图像数据集(在 creative commons 许可下)。
数据标记
在监督学习中,拥有训练数据的标签以及确保标签中没有任何噪声以构建健壮的计算机视觉算法是很重要的。因此,数据标记有助于我们处理这两种情况:
- 清理数据集以移除标注噪声
- 用于生成监督学习标签的图像注释
根据计算机视觉算法的目的,图像注释可以划分如下。
图像注释类型 | 描述/示例注释 | 使用案例 |
---|---|---|
2D 包围盒 | 4 点{点:(x1,y1),(x2,y2),(x3,y3),(x4,y4),标签:'狗' }封装对象 | 目标检测 |
3D 边界框 | 4 点{点:(x1,y1,z1),(x2,y2,z2),(x3,y3,z3),(x4,y4,z4),标签:'汽车' }封装对象 | 深度和距离计算、3D 体积也用于放射学成像的医学图像注释,以区分图像中的各种结构 |
线 | 线注释用于绘制车道,以训练用于车道检测的车辆感知模型。与边界框不同,它避免了许多空白和额外的噪点。 | 车道检测作为自主车辆的一部分 |
多边形 | 主要用于标注形状不规则的对象。贴标机必须以高精度在帧中生成对象的边界,这给出了关于对象的形状和大小的清晰概念。 | 时尚和服装分类 |
关键点 | 多个点和标签,例如:[{点:(x1,y1),标签:'鼻子' },{点:(x2,y2),标签:'外左眼' }] | 人脸标志估计 |
语义分割 | 像素级标记,将图像分为多个部分。每个片段通常由唯一的颜色代码表示 | 地理传感(识别土地覆盖类型) |
图像分类 | 整个图像只有一个标签,例如:狗 | 动物识别 |
这可以通过三种方式实现:
众包
亚马逊(Mechanical Turk)、Figure Eight Inc、谷歌(数据标签服务)、Hive 等公司已经启动了以人为中心的数据标签服务。这使得大量的人能够基于所提供的一组规则/指令来注释数据集。这种方法的处理量更快,但取决于使用它的人数,该过程可能非常昂贵。
预训练对象检测算法
这种方法完全消除了循环中的人为因素,并且是数据注释的唯一自动化方式。然而,该过程的主要缺点是数据集的质量可能较低,这取决于训练模型的概化能力。此外,在应用标签不同于训练模型的情况下,该过程将不起作用。
开源图像注释工具
有时由于数据的隐私性和敏感性,我们不能将数据集发布到在线平台进行众包源数据注释。在这种情况下,开源图像注释工具非常有用,因为数据只能在办公室网络内本地访问和注释。一些常用的工具有 RectLabel、LabelImg 等。这是一个极其缓慢的过程,因为根据应用程序的不同,很少有人会从事手动注释工作。然而,这产生了具有最小人为错误的高质量数据集。
根据需要标记的数据量及其敏感性,您可以选择上述方法之一进行数据标注。下表总结了三种不同的方法及其优缺点。此时,您已经整理了数据,并找到了标记数据的方法。
方法 | 赞成的意见 | 骗局 |
---|---|---|
众包 | 快速过程 | 代价高昂的人工错误 |
预训练对象检测算法 | 自动化、成本节约(免费软件)和快速流程 | 低质量标注数据集可能没有所需的类标注作为输出。 |
开源图像注释工具 | 节约成本(免费软件),高质量的标签数据集 | 缓慢的过程 |
根据您拥有和需要标注的数据量,您可以选择上述方法之一进行数据标注。在这一点上,您已经整理了您的数据,并找到了一种方法来标记您的数据,以开始训练您的计算机视觉算法。
参考资料:
[1]https://en.wikipedia.org/wiki/Creative_Commons_license
[2]https://en . Wikipedia . org/wiki/Requests _(软件)
[3]https://en . Wikipedia . org/wiki/Beautiful _ Soup _(HTML _ parser)
[3]https://en . Wikipedia . org/wiki/List _ of _ manual _ image _ annotation _ tools
[4]a .科瓦什卡、o .鲁萨科夫斯基、l .飞飞和 k .格劳曼(2016 年)。计算机视觉中的众包。计算机图形与视觉的基础与趋势, 10 (3),177-243。
玩超级马里奥兄弟双深 Q 网络
原文:https://blog.paperspace.com/building-double-deep-q-network-super-mario-bros/
尽管神经网络很酷,但我第一次感觉自己在构建真正的人工智能并不是在处理图像分类或回归问题时,而是在我开始进行深度强化学习时。在这篇文章中,我想与你分享我的经历。在本教程结束时,您将拥有一个能够通过超级马里奥兄弟(NES)第一关的 PyTorch 强化学习代理。
本教程分为 3 个部分:
- 强化学习简介
- 超级马里奥兄弟(NES)环境
- 建立一个可以通过这种环境的代理
你可以在 ML Showcase 上免费运行代码。
在本教程结束时,您将已经构建了一个可以完成此任务的代理:
Look at Mario go!
先决条件
- 具备深度学习和卷积神经网络的工作知识
- 有 Python 3+和一个 Jupyter 笔记本
- 可选:舒适地使用 PyTorch
什么是强化学习?
强化学习是一系列学习算法,其中代理通过与环境交互来从环境中学习。它学什么?非正式地说,代理学习采取行动,使其从当前状态达到最佳状态。
我发现例子总是有帮助的。检查以下 3×3 网格:
这个网格是我们代理的环境。这个环境中的每个方块称为一个状态,一个环境总是有一个 开始 和 结束 状态,你可以看到它们分别以绿色和红色突出显示。很像人类,我们的代理人将在一个叫做插曲的过程中从重复中学习。在一集的开始,一个代理将从开始状态开始,并且它将一直进行动作,直到到达结束状态。一旦代理到达结束状态,该片段将终止,并且新的片段将从代理再次从开始状态开始。
这里我只是给了你一个网格,但你可以想象更现实的例子。想象你在一家杂货店,你看着你的购物清单:你需要买干迷迭香。当你进入商店时,你的开始状态将是你的位置。第一次尝试寻找迷迭香时,你可能会有点迷路,你可能不会直接穿过商店找到“香草和香料”通道。但是在接下来的每一次拜访中,你会越来越好地找到它,直到当你走进去的时候,你可以直接移动到正确的通道。
当一个代理降落在一个州时,它会累积与该州相关的奖励,一个好的代理希望沿着一集最大化 累积折扣奖励(我稍后会解释折扣是什么意思)。假设我们的代理可以垂直、水平和对角移动。基于这些信息,您可以看到代理到达最终状态的最佳方式是斜向移动(直接向它移动),因为它会累积奖励 -1 + 0 = -1 。如果代理人以任何其他方式向最终状态移动,它将累积小于-1 的奖励。例如,如果代理向右、向右、向上移动,然后再向上移动一次,它将获得-1 + (-1) + (-1) + 0 = -3 的回报,小于-1。因此,对角移动被称为最优策略π[*] ,其中 π 是一个函数,它接受一个状态并输出代理将从该给定状态采取的动作。您可以从逻辑上推导出这个简单网格的最佳策略,但是我们如何使用强化学习来解决这个问题呢?由于这篇文章是关于深度 q 学习的,我们首先需要理解状态-动作值。
Q-学习
我们提到,对于上述网格问题中的每个状态,代理可以移动到任何触及当前状态的状态;所以我们对每个状态的动作集是垂直的、水平的和对角线的。一个状态-动作值是处于一个特定状态并从该状态采取一个特定动作的质量。除了结束状态,每个单独的状态和动作对都应该有一个值。我们将这些状态-动作值表示为 Q(s,a) (状态-动作对的质量),所有的状态-动作值一起形成一个叫做Q-表的东西。一旦 Q 表被学习,如果代理处于特定状态 s ,它将从 s 采取动作 a ,使得 Q(s,a) 具有最高值。数学上,如果一个代理处于状态 s,它将花费 argmax[a] Q(s,a) 。但是这些价值观是如何习得的呢?Q-learning 使用了 Bellman-update 方程的变体,更具体地说,是一种时间差异学习。
Q-学习更新方程为:
Q(s[t] 、a[t] )←Q(s[t] 、a[t])+α(r[t+1]+
本质上,这个等式表明,处于状态s[t]并采取行动a[t]的质量不仅仅由你从采取该行动中获得的直接回报来定义,还由你在到达状态 s[t+1] 后所能采取的最佳行动来定义γ参数被称为折现因子,是一个介于 0 和 1 之间的值,它定义了未来状态的重要性。α值被称为学习率,它告诉我们 Q 值更新到多大。这应该让你回想起我提到过强化学习代理的目标是最大化累积的折扣奖励。****
如果我们把这个等式改写成:
Q(s[t] 、a[t] )←Q(s[t] 、a[t])+s
你会注意到,当δ≈0 时,算法收敛,因为我们不再更新 Q(s[t] ,a[t] )。这个值δ被称为时间差误差,Q-learning 的工作是使该值变为 0。
现在,让我们用 Q-learning 来解决 Python 中的网格问题。解决网格问题的主要函数是:
def train_agent():
num_episodes = 2000
agent = Agent()
env = Grid()
rewards = []
for _ in range(num_episodes):
state = env.reset()
episode_reward = 0
while True:
action_id, action = agent.act(state)
next_state, reward, terminal = env.step(action)
episode_reward += reward
agent.q_update(state, action_id, reward, next_state, terminal)
state = next_state
if terminal:
break
rewards.append(episode_reward)
plt.plot(rewards)
plt.show()
return agent.best_policy()
print(train_agent())
这个想法很简单。给定一个状态,代理采取具有最高值的动作,并且在采取该动作之后,使用上面的贝尔曼方程更新 Q 表。下一个状态成为当前状态,代理继续使用这种模式。如果代理降落在终端状态,那么新的一集开始。 Q 表更新方法简单来说就是:
class Agent():
...
def q_update(self, state, action_id, reward, next_state, terminal):
...
if terminal:
target = reward
else:
target = reward + self.gamma*max(self.q_table[next_state])
td_error = target - self.q_table[state, action_id]
self.q_table[state, action_id] = self.q_table[state, action_id] + self.alpha*td_error
...
跑了 1000 集,我得到的最终方针是:
Normalized rolling average reward plot over 1000 episodes, and policy table for Q-learning
您可能会注意到有些箭头没有意义。例如,如果你在网格的左上方,代理不是应该向右移动而不是向下吗?只要记住,Q-learning 是一个贪婪的算法;代理没有足够的时间到达左上方,以从该位置找出最佳策略。重要的是,从一开始,它就发现最好的策略是对角移动。
双 Q 学习
Q-learning 有一个我们需要处理的主要问题:高估偏差,这意味着学习到的 Q 值实际上比它们应该的要高。数学上,max[a] Q(s[t+1] ,a)收敛于 E(max[a] Q(s[t+1] ,a)),高于 max[a](E(Q(s[t+1],a))的真实 Q 值(此处不做证明)。为了获得更精确的 Q 值,我们使用了一种叫做双 Q 学习的方法。在双 Q 学习中,我们有两个 Q 表:一个用于采取行动,另一个专门用于 Q 更新方程。双 Q 学习更新方程为:
Q(s[t] ,a[t] )←Q(s[t] ,a[t])+α(r[t+1]+
其中 Q* 是被更新的 Q 表,Q^T 是目标表。Q^T 每隔 n 步复制 Q* 的值。
下面是演示这些变化的一些代码片段:
class AgentDoubleQ():
...
def q_update(self, state, action_id, reward, next_state, terminal):
state = state[0]*3 + state[1]
next_state = next_state[0]*3 + next_state[1]
if terminal:
target = reward
else:
target = reward + self.gamma*max(self.q_target[next_state])
td_error = target - self.q_table[state, action_id]
self.q_table[state, action_id] = self.q_table[state, action_id] + self.alpha*td_error
def copy(self):
self.q_target = copy.deepcopy(self.q_table)
...
def train_agent_doubleq():
...
while True:
action_id, action = agent.act(state)
next_state, reward, terminal = env.step(action)
num_steps += 1
if num_steps % agent.copy_steps == 0:
agent.copy()
episode_reward += reward
agent.q_update(state, action_id, reward, next_state, terminal)
state = next_state
if terminal:
break
...
下面是 1000 集以上的归一化滚动平均奖励图。
Normalized rolling average reward plot over 1000 episodes, and policy table for double Q-learning
从这个图中,可能很难看出双 Q 学习比 Q 学习有什么优势,但那是因为我们的状态空间真的很小(只有 9 个状态)。当我们得到更大的状态空间时,双 Q 学习真的有助于加速收敛。
超级马里奥兄弟(NES)
现在你已经对强化学习有了一个简要的概述,让我们来构建我们的代理,它可以通过超级马里奥兄弟(NES)的第一关。我们将使用建在开放体育馆顶部的gym-super-mario-bros
图书馆。对于那些不熟悉 gym 的人来说,它是一个极其流行的 Python 库,为 ML 爱好者提供了一套强化学习的环境。下面是实例化我们的环境并查看每个状态的大小以及动作空间的代码片段:
import gym_super_mario_bros
env = gym_super_mario_bros.make('SuperMarioBros-1-1-v0')
print(env.observation_space.shape) # Dimensions of a frame
print(env.action_space.n) # Number of actions our agent can take
你会看到观察空间形状是 240 × 256 × 3 (240 和 256 分别代表高度和宽度,3 代表 3 个颜色通道)。代理可以采取 256 种不同的可能操作。在双深度 Q 学习中,减少状态和动作空间的大小加快了我们模型的收敛。gym 的一个很好的部分是我们可以使用 gym 的Wrapper
类来改变最初给我们的默认设置。下面我定义了几个类来帮助我们的代理学习得更快。
def make_env(env):
env = MaxAndSkipEnv(env)
env = ProcessFrame84(env)
env = ImageToPyTorch(env)
env = BufferWrapper(env, 4)
env = ScaledFloatFrame(env)
return JoypadSpace(env, RIGHT_ONLY)
该功能对我们的环境应用 6 种不同的转换:
- 代理所做的每一个动作都在 4 帧中重复
- 每一帧的尺寸减小到 84×84
- 框架被转换为 PyTorch 张量
- 缓冲器仅收集每四个帧
- 帧被归一化,使得像素值在 0 和 1 之间
- 动作的数量减少到 5(这样代理只能向右移动)
为超级马里奥兄弟(NES)建立代理
让我们最后来看看是什么让深度 Q-learning 变得“深”。从我们设置环境的方式来看,一个状态是一系列 4 连续的 84×84 像素帧,我们有 5 个可能的动作。如果我们要为这个环境制作一个 Q 表,该表将具有 5×256^(84×84×4) 值,因为每个状态有 5 个可能的动作,每个像素具有 0 到 255 之间的强度,并且在一个状态中有 84×84×4 个像素。显然,存储这么大的 Q 表是不可能的,所以我们不得不求助于函数逼近,其中我们使用神经网络来逼近 Q 表;也就是说,我们将使用神经网络将一个状态映射到它的状态-动作值。
在表格(基于表格的)双 Q 学习中,回想一下更新等式是:
q*(s, a); q(s, a)
r[t+1]+[γ_max_a]Qθ被认为是我们的目标,Q* (s[t] ,a[t] 是我们的网络预测的值。使用某种基于距离的损失函数(均方误差、Huber 损失等)。),我们可以使用梯度下降来优化深度 Q 网络的权重。
在开始我们如何训练我们的代理的细节之前,让我们首先建立我们将用作函数逼近器的 DQN 架构。
class DQNSolver(nn.Module):
def __init__(self, input_shape, n_actions):
super(DQNSolver, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(input_shape[0], 32, kernel_size=8, stride=4),
nn.ReLU(),
nn.Conv2d(32, 64, kernel_size=4, stride=2),
nn.ReLU(),
nn.Conv2d(64, 64, kernel_size=3, stride=1),
nn.ReLU()
)
conv_out_size = self._get_conv_out(input_shape)
self.fc = nn.Sequential(
nn.Linear(conv_out_size, 512),
nn.ReLU(),
nn.Linear(512, n_actions)
)
def _get_conv_out(self, shape):
o = self.conv(torch.zeros(1, *shape))
return int(np.prod(o.size()))
def forward(self, x):
conv_out = self.conv(x).view(x.size()[0], -1)
return self.fc(conv_out)
我们的 DQN 是一个具有 3 个卷积层和两个线性层的卷积神经网络。它需要两个参数:input_shape
和n_actions
。当然我们会提供的输入形状是 4×84×84,还有 5 个动作。我们选择使用卷积神经网络,因为它们非常适合基于图像的回归。
既然我们已经建立了我们的神经网络架构,让我们来看看代码的“主要功能”。
def run():
env = gym_super_mario_bros.make('SuperMarioBros-1-1-v0')
env = make_env(env) # Wraps the environment so that frames are grayscale
observation_space = env.observation_space.shape
action_space = env.action_space.n
agent = DQNAgent(state_space=observation_space,
action_space=action_space,
max_memory_size=30000,
batch_size=32,
gamma=0.90,
lr=0.00025,
exploration_max=0.02,
exploration_min=0.02,
exploration_decay=0.99)
num_episodes = 10000
env.reset()
total_rewards = []
for ep_num in tqdm(range(num_episodes)):
state = env.reset()
state = torch.Tensor([state])
total_reward = 0
while True:
action = agent.act(state)
state_next, reward, terminal, info = env.step(int(action[0]))
total_reward += reward
state_next = torch.Tensor([state_next])
reward = torch.tensor([reward]).unsqueeze(0)
terminal = torch.tensor([int(terminal)]).unsqueeze(0)
agent.remember(state, action, reward, state_next, terminal)
agent.experience_replay()
state = state_next
if terminal:
break
total_rewards.append(total_reward)
print("Total reward after episode {} is {}".format(ep_num + 1, total_rewards[-1]))
num_episodes += 1
看起来和网格问题的主函数几乎一模一样,对吧?您可能看到的唯一区别是remember
和experience_replay
方法。在典型的监督学习中,神经网络使用批量数据来更新其权重。在深度 Q-learning 中,想法是相同的,除了这些批量数据被称为批量体验,其中体验是一个(状态、动作、奖励、下一状态、终端)元组。不要像我们在网格问题中那样丢弃经验,我们可以将它们存储在一个缓冲区中,以便稍后使用remember
方法。在experience_replay
方法中,代理只需对一批经验进行采样,并使用双 Q 更新方程来更新网络权重。
现在,让我们回顾一下我们的代理的最重要的方法:remember
、recall
和experience_replay
。
class DQNAgent:
...
def remember(self, state, action, reward, state2, done):
self.STATE_MEM[self.ending_position] = state.float()
self.ACTION_MEM[self.ending_position] = action.float()
self.REWARD_MEM[self.ending_position] = reward.float()
self.STATE2_MEM[self.ending_position] = state2.float()
self.DONE_MEM[self.ending_position] = done.float()
self.ending_position = (self.ending_position + 1) % self.max_memory_size # FIFO tensor
self.num_in_queue = min(self.num_in_queue + 1, self.max_memory_size)
def recall(self):
# Randomly sample 'batch size' experiences
idx = random.choices(range(self.num_in_queue), k=self.memory_sample_size)
STATE = self.STATE_MEM[idx].to(self.device)
ACTION = self.ACTION_MEM[idx].to(self.device)
REWARD = self.REWARD_MEM[idx].to(self.device)
STATE2 = self.STATE2_MEM[idx].to(self.device)
DONE = self.DONE_MEM[idx].to(self.device)
return STATE, ACTION, REWARD, STATE2, DONE
def experience_replay(self):
if self.step % self.copy == 0:
self.copy_model()
if self.memory_sample_size > self.num_in_queue:
return
STATE, ACTION, REWARD, STATE2, DONE = self.recall()
self.optimizer.zero_grad()
# Double Q-Learning target is Q*(S, A) <- r + γ max_a Q_target(S', a)
target = REWARD + torch.mul((self.gamma *
self.target_net(STATE2).max(1).values.unsqueeze(1)),
1 - DONE)
current = self.local_net(STATE).gather(1, ACTION.long()) # Local net approximation of Q-value
loss = self.l1(current, target)
loss.backward() # Compute gradients
self.optimizer.step() # Backpropagate error
...
...
这是怎么回事?在remember
方法中,我们只是将一个体验推送到缓冲区,这样我们就可以在以后使用该数据。缓冲区的大小是固定的,因此它有一个 deque 数据结构。recall
方法只是从记忆中取样一批经验。在experience_replay
方法中,你会注意到我们有两个 Q 网络:目标网络和本地网络。这类似于网格问题的目标和本地 Q 表。我们将本地权重复制为目标权重,从我们的内存缓冲区中采样,并应用双 Q 学习更新方程。这种方法可以让我们的代理学习。
运行代码
你可以在 ML Showcase 上免费运行全部代码。
代码提供了允许用户运行深度 Q 学习或双重深度 Q 学习的选项,但为了便于比较,这里有一些比较 DQN 性能和 DDQN 性能的图表:
你会注意到 10000 集的 DQN 和仅仅 1000 集的 DDQN 的表现是一样的(看左边的平均奖励剧情)。单一 DQN 的代码仅用于教育目的;我强烈建议你坚持训练 DDQN。
结论
恭喜你!如果你和我一样,看到马里奥持续通过这一关会让你有一种冲动,你会想跳到新的深度强化学习项目。有些话题我在这篇文章中没有涉及,比如价值迭代、策略上与策略外学习、马尔可夫决策过程等等。然而,这篇文章旨在让人们对深度强化学习提供的可怕机会感到兴奋。保重,在我的下一篇文章中再见!
为图像数据收集构建简单的 Web 抓取器
原文:https://blog.paperspace.com/building-simple-web-scrapers-for-image-data-collection/
数据收集是机器学习/深度学习领域中很少谈论的话题。虽然在 PyTorch 和 Scikit-Learn 等库上有许多预加载的数据集,但人们可能需要为特定项目收集和整理自定义数据集。
有多种方法可以进行数据收集,例如从数据收集仪器中读取数据,或者在适当的时候手动记录观察结果。在计算机视觉的背景下,数据收集最容易的方法就是从网页上抓取预先存在的图像。
在本文中,我们将探索如何使用 BeautifulSoup 库构建一个简单的 web scraper。使用这个刮刀,我们将尝试为一个计算机视觉项目收集和管理一个定制的图像数据集。
网络抓取的概念
网页抓取是从网页中提取数据的过程。在网页抓取过程中使用的工具被称为网页刮刀(或简称刮刀)。从技术上来说,抓取器查看网页的源代码,并根据 html 标签或其他方式抓取数据。在 Python 生态系统中,有许多库可以用于 web 抓取,如 BeautifulSoup、Selenium 和 Scrapy。然而,在这篇文章中,我们将重点放在使用 BeautifulSoup 构建刮刀上。
网络抓取伦理
关于网络抓取的整个过程及其整体合法性,已经做了很多。事实上,它是如此的有争议,以至于有围绕这个主题的真实的法庭案例。
在美国第九巡回上诉法院的一项裁决中,重申了在互联网上搜集公众可访问的数据并不违反 CFAA(计算机欺诈和滥用法案)。从本质上讲,这意味着搜集向每个访问网页的人公开显示的信息是合法的,然而搜集私人数据将被视为非法。
也就是说,出于个人原因,网站也可以完全阻止网络抓取。检查特定网页是否允许抓取的一个简单方法是检查它的 robots.txt 文件。robots.txt 文件向网络爬虫(如 Google、Bing)提供信息,允许它们访问哪些文件夹,并在搜索结果中显示,爬虫基本上以此作为一种许可文件。
考虑上面的网页,要访问它的 robot.txt 文件,需要做的就是将“/robots.txt”添加到网页的地址栏,如下所示。
完成后,剩下的就是按回车键,页面的 robots.txt 文件将如下图所示显示。从文件来看,很明显,这个特定的网站并不反对网络抓取,尽管有一些限制。记住,并不是所有的 robots.txt 文件都如此详细地描述了网站对抓取的态度。要注意的一点是文件中的“不允许”参数,列出多个“不允许”参数的网站只允许在选定的文件夹(列出的文件夹)中进行抓取。一个完全限制抓取的网站会有一个参数,如‘不允许:/’,如果有完全抓取权限,那么它只会是‘不允许:’。
毕竟,网站仍然可以阻止僵尸工具(抓取工具)访问他们的网页。然而,如果你决定继续从这类页面中抓取数据(有反抓取技术的方法),请确保你保持道德,不要让他们的服务器超载。
BeautifulSoup & HTML 解析
解析是将字符串分离成其组成部分以便于分析的过程。这基本上就是 BeautifulSoup 库所做的,因为它提取网页的 html,并根据它们的标签“分离”它们,以便可以分别访问和分析它们。
考虑下面的示例 html,它由包含在 html、title、body、h1 和 p 等标签中的字符串组成,如果这个 html 作为 beautifulsoup 中的参数提供,我们将能够单独访问每个标签,并对它们包含的字符串做我们想做的任何事情。
<html>
<title>Mock Webpage</title>
<body>
<h1>Web Scraping</h1>
<p>This article is all about web scraping</p>
<p>We will be using BeautifulSoup</p>
</body>
</html>
Sample html
尝试从上面的 html 创建一个 beautifulsoup 元素,为了简单起见,复制 html,将它们格式化为 Python 字符串(通过将它们放在引号中)并创建一个如下所示的对象。接下来,使用“html.parser”参数创建一个 beautifulsoup 元素。
# article dependacies
from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.request import Request
from tqdm import tqdm
import requests
import os
import time
from bs4 import BeautifulSoup
# create html object
html = """
<html>
<title>Mock Webpage</title>
<body>
<h1>Web Scraping</h1>
<p>This article is all about web scraping</p>
<p>We will be using BeautifulSoup</p>
</body>
</html>
"""
# create beautifulsoup element
bs = BeautifulSoup(html, 'html.parser')
既然已经生成了一个 beautifulsoup 元素,我们可以简单地调用标签作为元素的属性来访问它们。下面进行了几个属性调用。
# extract the title tag
bs.title
>>>> <title>Mock Webpage</title>
# extract the h1 tag
bs.h1
>>>> <h1>Web Scraping</h1>
# extract the p tag
bs.p
# notice that just the first tag is returned
>>>> <p>This article is all about web scraping</p>
# extract all p tags
bs.find_all(p)
>>>> [<p>This article is all about web scraping</p>,
<p>We will be using BeautifulSoup</p>]
# extract only the string in the title tag
bs.title.get_text()
>>>> Mock Webpage
美丽的声音和网页抓取
从上一节我们知道,如果我们将 html 以合适的格式放入 beautifulsoup,我们就可以开始从中提取信息。当涉及到托管在服务器上的实际网页时,我们需要找到一种方法在 Python 环境中实际访问它们的 html。为此,我们需要将 urllib 库与 beautifulsoup 结合使用。
from urllib.request import urlopen
from urllib.request import Request
url = 'actual link'
# header to mimick web browser
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
'Accept-Encoding': 'none',
'Accept-Language': 'en-US,en;q=0.8',
'Connection': 'keep-alive'}
# make request to server
request = Request(url, headers=headers)
# open request and create beautifulsoup element
html = urlopen(request)
bs = BeautifulSoup(html.read(), 'html.parser')
从上面的代码块中,我们已经从 urllib 导入了 urlopen 函数和 Request 方法。首先,我们使用 request 方法向托管网页的服务器发出请求。请注意,我们已经为请求指定了新的头,这样做是为了提供一个实际的 web 浏览器正在向服务器发出请求的假象(这只是避免反刮刀的基本方法之一)。
最后,我们只需使用 urlopen 函数打开从服务器(位于请求对象中)得到的响应,然后创建一个漂亮的 output 元素,正如我们在上一节中所做的那样。html 对象上使用了“read()”属性,以允许美化组访问其内容,因为它不是字符串格式。
从网页上抓取图像
抓取图像
利用到目前为止我们积累的所有知识,我们现在可以尝试从网页上抓取图像。图像通常位于“src”链接中,或者它们的一些变体,位于 html 的“img”标签中。网页上的每一张图片都将以同样的方式标记,为了只提取感兴趣的图片,我们需要以某种方式区分它们。出于演示目的,让我们使用电子商务网站 Jumia 。
Home page.
假设我们想要构建一个二进制分类模型,能够区分男士的运动鞋和靴子。导航到任何一个链接的页面,右键点击然后点击 inspect 显示页面的 html (个人使用的谷歌浏览器)在这个例子中。
Displaying the page's html.
标签和属性
右键单击鞋子目录列表中的图片,会发现它们都包含在“img”标签中,图片本身可以在 data-src 链接中找到。还有一点需要注意的是,它们都有相同的 class 属性(class = "img ")。检查列表中没有的其他图像会发现它们具有不同的类属性。
All images in the listing have the class "img".
这大大简化了我们的任务。我们需要做的就是使用 beautifulsoup 解析这个页面,然后提取所有包含“img”类的 img 标签。为此,我们可以简单地复制上一节抓取网页的代码,但这次我们将使用一个真实的 url。为了抓取多个页面,导航到第 2 页并复制 url,您会注意到指定了页码,我们需要做的只是以某种方式修改它,以便我们可以迭代多个页面。
# copy link from page 2 and edit 2 to 1 to access the first page
url = 'https://www.jumia.com.ng/mlp-fashion-deals/mens-athletic-shoes/?page=1#catalog-listing'
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
'Accept-Encoding': 'none',
'Accept-Language': 'en-US,en;q=0.8',
'Connection': 'keep-alive'}
request = Request(url, headers=headers)
html = urlopen(request)
bs = BeautifulSoup(html.read(), 'html.parser')
# extract all img tags with class img
interest = bs.find_all('img', attrs={'class':'img'})
我们现在拥有了对象“interest”中的所有目录图像。为了提取 data-src 链接,我们需要做的就是调用列表中每一项的 data-src 属性。
# extracting links using list comprehension
links = [listing['data-src'] for listing in interest]
从 src 链接下载
使用提取的链接,我们现在可以尝试使用请求库提取其中的图像。
import requests
# instantiating counter
count = 0
# downloading images
for link in tqdm(links):
with open(f'athletic_{count}.jpg', 'wb') as f:
response = requests.get(link)
image = response.content
f.write(image)
count+=1
构建 Web 刮刀
在这一点上,把我们所知道的关于网络抓取的所有东西放在一起,我们现在可以构建一个自动完成上述所有步骤的抓取器。对于我们的 scraper,我们将创建一个 Python 类,其中包含一些方法来复制前面讨论过的抓取过程。
class WebScraper():
def __init__(self, headers, tag: str, attribute: dict,
src_attribute: str, filepath: str, count=0):
self.headers = headers
self.tag = tag
self.attribute = attribute
self.src_attribute = src_attribute
self.filepath = filepath
self.count = count
self.bs = []
self.interest = []
def __str__(self):
display = f""" CLASS ATTRIBUTES
headers: headers used so as to mimic requests coming from web browsers.
tag: html tags intended for scraping.
attribute: attributes of the html tags of interest.
filepath: path ending with filenames to use when scraping images.
count: numerical suffix to differentiate files in the same folder.
bs: a list of each page's beautifulsoup elements.
interest: a list of each page's image links."""
return display
def __repr__(self):
display = f""" CLASS ATTRIBUTES
headers: {self.headers}
tag: {self.tag}
attribute: {self.attribute}
filepath: {self.filepath}
count: {self.count}
bs: {self.bs}
interest: {self.interest}"""
return display
def parse_html(self, url):
"""
This method requests the webpage from the server and
returns a beautifulsoup element
"""
try:
request = Request(url, headers=self.headers)
html = urlopen(request)
bs = BeautifulSoup(html.read(), 'html.parser')
self.bs.append(bs)
except Exception as e:
print(f'problem with webpage\n{e}')
pass
def extract_src(self):
"""
This method extracts tags of interest from the webpage's
html
"""
# extracting tag of interest
interest = self.bs[-1].find_all(self.tag, attrs=self.attribute)
interest = [listing[self.src_attribute] for listing in interest]
self.interest.append(interest)
pass
def scrape_images(self):
"""
This method grabs images located in the src links and
saves them as required
"""
for link in tqdm(self.interest[-1]):
try:
with open(f'{self.filepath}_{self.count}.jpg', 'wb') as f:
response = requests.get(link)
image = response.content
f.write(image)
self.count+=1
# pausing scraping for 0.4secs so as to not exceed 200 requests per minute as stipulated in the web page's robots.txt file
time.sleep(0.4)
except Exception as e:
print(f'problem with image\n{e}')
time.sleep(0.4)
pass
Scraper.
上述 web scraper 类中的方法使用 try/except 块处理异常。这样做是为了防止由于网络上普遍存在的链接断开或文件丢失而引发的异常。现在已经定义了 web scraper,让我们实例化一个名为“scraper”的对象,它将是该类的成员。
# instantiating web scraper class
scraper = WebScraper(headers=headers, tag='img', attribute = {'class':'img'},
src_attribute='data-src', filepath='shoes/athletic/atl', count=0)
Instantiating scraper.
实际上,上面定义的 scraper 对象一次只能抓取一个页面/url。为了使它更健壮,并允许它抓取多个页面,我们需要将它包装在一个函数中,该函数将允许它遍历多个页面。为了让函数工作,我们需要从除了页面 1 之外的任何页面复制 url,然后将其格式化为 f 字符串,如下面的代码块所示。这样做的原因是,我们可以访问嵌入在 url 中的页面引用。
def my_scraper(scraper, page_range: list):
"""
This function wraps around the web scraper class allowing it to scrape
multiple pages. The argument page_range takes both a list of two elements
to define a range of pages or a list of one element to define a single page.
"""
if len(page_range) > 1:
for i in range(page_range[0], page_range[1] + 1):
scraper.parse_html(url=f'https://www.jumia.com.ng/mlp-fashion-deals/mens-athletic-shoes/?page={i}#catalog-listing')
scraper.extract_src()
scraper.scrape_images()
print(f'\npage {i} done.')
print('All Done!')
else:
scraper.parse_html(url=f'https://www.jumia.com.ng/mlp-fashion-deals/mens-athletic-shoes/?page={page_range[0]}#catalog-listing')
scraper.extract_src()
scraper.scrape_images()
print('\nAll Done!')
pass
Wrapping scraper in a function so as to be able to scrape multiple pages.
设置好一切后,我们现在可以使用下面的代码来抓取前五页的图片。我们设置 scraper 的方式很酷的一点是,只需调用“scraper.interest ”,就可以很容易地逐页访问 src 链接,这将返回一个嵌套列表,每个内部列表包含每个页面的 src 链接。你也可以尝试从 boots 列表中抓取前五页。
import os
# creating directory to hold images
os.mkdir('shoes')
os.mkdir('shoes/athletic')
# scraping the first five pages
my_scraper(scraper=scraper, page_range=[1, 5])
After the function is ran, navigate to shoes\athletic, you should find image files named 'atl_0.jpg' - 'atl_200.jpg'.
使用 BeautifulSoup 的缺点
从前面几节可以明显看出,BeautifulSoup 是一个强大的网络抓取工具。然而,它的一个缺点是它并不总是足以抓取动态网页。可以把动态网页想象成这样的页面,点击按钮会在同一页面上显示更多的内容。对于这种页面,Selenium 库是一个安全的选择。
结束语
在本文中,我们探讨了 web 抓取的一般概念。然后我们专注于使用 BeautifulSoup 库抓取图像。利用我们积累的知识,我们开始构建一个 web scraper,能够一次从几个页面中抓取图像。
本质上,如果想要从页面中抓取非图像数据,也可以应用相同的过程,只要从页面的 html 中识别出感兴趣的标签和属性,就可以构建一个抓取任何类型的所需数据的抓取器。
用 CyberLayerVR 构建虚拟世界
原文:https://blog.paperspace.com/building-virtual-worlds-with-cyberlayervr/
CyberLayerVR 的创始人兼首席执行官 Bo Layer 正在构建一个令人难以置信的东西:一个竞技场规模的虚拟现实体验,听起来像是绿洲和激光标签之间的混搭。
我们渴望向处于虚拟现实、游戏和机器学习前沿的主题专家学习,并了解他如何将多种技术结合在一起,创造创新的游戏体验。
将现实世界和数字世界结合起来是当今科技领域最激动人心的想法之一。我们迫不及待地想和 Bo 坐下来,详细了解他的想法和发展。CyberLayerVR 将开启哪些激动人心的新体验?什么样的技术可以实现这些体验?我们什么时候可以进入竞技场?
paper space:CyberLayerVR 的网站正在宣传“竞技竞技场——规模 VR”。你能确定这真的像听起来那么酷吗?
层: 确认!当然有很多东西要打开。让我们来看看每个部分,并解释它在我们的上下文中的含义:
竞争:我们将我们的竞技场视为下一代电子竞技运动员和游戏玩家的概念证明。这不是你的普通射击场,而是一个旨在推动你成为球员的整个世界——从你的精神韧性到你的身体健康。游戏世界记得一切,会随着你的进步更努力地推动你。
竞技场规模:正如许多其他团队运动一样,能够在一个充满其他运动员的大环境中自由活动是该计划的重要组成部分。愿景是让玩家随心所欲地加入竞技场,想玩多久就玩多久。有点像健身房,但是是给游戏玩家的。
VR: 虚拟现实是表现性和沉浸式娱乐的最终媒介。自从第一次无线电广播声音和第一次电视播放电影画面以来,这就是我们一直期待经历的事情。有什么更好的地方让游戏玩家聚在一起?
Paperspace: 哇。好的——所以你真的在融合数字游戏和实体游戏的范畴。游戏体验实际上会是什么样的?你能给我们介绍一下高水平的用户体验吗?
层: 就目前来说,玩法极其简单。每一次都是不同的,因为我们已经为你和你的朋友在里面战斗程序性地生成了关卡。这包括飞机库和城市街道等地图。我们目前提供三对三的用户体验,这使得我们的玩家可以一决雌雄,通过消灭对手来赢得胜利。目前是固定时间的免费游戏,根据每个小组的需要,可以是半小时或一小时。
Paperspace: 这么多技术成分我们要问——你的背景是什么?你能告诉我们一个人是如何设计出像 CyberLayerVR 这样多线程的东西的吗?
层: 我的专业背景分为三个主要领域:通用技术支持、通信和数据科学。我有近 20 年的工作经验,曾与苹果(Apple)和欧特克(Autodesk)等大公司合作,也曾与最近被收购的网络安全公司 Shape Security 等小型初创公司合作。最后,在军队和国防部工作期间,我了解到世界是如何联系在一起的(无论是字面上还是象征性的)。
我在军队和国防部的大部分时间是在伊拉克和阿富汗之间的海外度过的,在那里我体会到了与世界其他地方几乎完全隔绝的感觉。当我在中东的时候,我童年对游戏的热爱开始发扬光大,因为我开始看到游戏是如何把人们聚集在一起的。
回到美国并在硅谷工作后,我开始痴迷于这样一个想法,即沉浸式游戏可以带来真正有意义的生活体验——这些体验让你记忆犹新,并有助于你的个性和性格。
毫不奇怪,CyberLayerVR 是我的一堆兴趣和专业经验的交汇点。我从事计算机网络和数据科学工作,研究了虚拟现实、立体运动捕捉、物理传感器、云连接认知服务和计算机视觉领域的最新技术,以开发出第一代电子竞技竞技竞技游戏平台。
Paperspace: 你是在什么时候发现这可以工作的?
层: 两年前我参加了 Facebooks 一年一度的 VR 大会 Oculus Connect,去参加了新推出的 Quest 耳机的演示。当我正要跳过演示的时候,我的一个和脸书的 CTO 一起工作的朋友注意到了我,并帮我跳过了这一行。那天和脸书工程团队的谈话在 20 分钟内改变了我的一生。在未来,我希望我们在 CyberLayerVR 的工作能同样激励未来的创业者。
https://www.youtube.com/embed/EL24N6dtCOk?feature=oembed
Video provided by CyberLayerVR
paper space:是哪一组技术让 CyberLayerVR 现在成为现实?五年前 CyberLayerVR 能工作吗?你能告诉我们平台下的技术吗?****
层: 事实上,现在让我们能够构建我们的(物理)电子竞技平台的一半技术甚至不到 18 个月前——有些甚至不到 9 个月前!
这包括一体化 VR 头戴设备(不需要 PC)、云连接成像传感器、无线触觉反馈配件、眼球追踪、全身体积捕捉阶段、高分辨率计算机视觉摄像头、包括心率和神经信号在内的无线生物传感器……不胜枚举。
我们的平台使现场用户能够在他们身体的三十多个点/关节上进行全身跟踪,所有这些都不需要佩戴任何标志(如带有小型跟踪器的手腕/脚环)。这种能力是以相对较低的成本在单个物理位置跟踪多个人的巨大飞跃。
对于底层技术,我们正在使用现成的组件,如前面提到的来自脸书的 Oculus Quest、微软和英特尔的深度传感器、bHaptics 触觉反馈背心以及来自 NZXT 的功能强大的游戏装备,来为每个位置供电。
******Paperspace:您能告诉我们一些关于您为什么选择 paper space 作为虚拟机设置的信息吗?Paperspace 能让你做以前做不到的事情吗?
层: 我们需要访问强大的云连接机器,这些机器具有大量的物理视频内存,以及大量的标准内存和强大的 CPU。
在任何给定的时间,我们在单个工作流中使用的应用程序可能多达 10 到 12 个。
当寻找面向专业高级用户的支持 GPU 的虚拟机时,Paperspace 是唯一一个不断出现的名称,并且有着惊人的评论。
我们需要所有能得到的能量,不管我们在哪里。这是因为我们正在运行十到十二个以上的工作流,以及运行多个应用程序来处理游戏引擎的体积捕捉数据,以接近实时的角色创建流程。Paperspace 满足了我们所有的需求,甚至更多。
我还从 Paperspace 上下载了自己的个人游戏,并用它在 VR 中举办社交聚会(它成功地在一台服务器上同时接待了 100 名用户。
Paperspace: 随着时间的推移,机器学习会成为你工具栈的重要组成部分吗?为什么
层: 机器学习将是我们平台的一个巨大方面。我们将使用从我们的游戏中提取的匿名用户跟踪数据,以便为智能/反应式人工智能对手等项目训练 ML 模型。
最终,目标是让一个人工智能机器人根据以前比赛和与类似技能水平球员比赛的匿名用户移动数据做出决定。这对我们的游戏玩家来说将是一个令人兴奋的挑战,因为他们需要克服一个根据自己的行为训练的人工智能!我们希望你能说出真实的真人玩家和电脑控制的对手之间的区别,直到你把目光从游戏转移到玩家的心理竞技场上。
这个项目的下一个迭代是什么?你打算建立新的 VR 竞技场和故事吗?
层: 我们目前正在构建三个仅限于现场的体验:一个玩家对玩家的战斗游戏,一个逃离外星人类型的冒险,和一个僵尸波群射击游戏。最终,所有的经历都将存在于一个空间,就像绿洲一样,所有的经历都将是令人愉快的。目前,我们将在犹他州盐湖城开设第一家分店,然后扩展到周边地区。
我们目前正在测试在本地竞技场游戏中添加远程用户进行额外备份的能力。在线组件实际上在几天前就已经启动并运行了!我们的网站将有最新的更新,我们希望在几个月后推出我们的第一次安装。
你还有什么想分享的吗?
只是说 Paperspace 很牛逼,虚拟现实是未来,游戏会随着 CyberLayerVR 走向真正的下一代。当你可以生活在游戏中的时候,为什么还要玩游戏呢?
要了解更多关于 CyberLayerVR 的信息,请确保在脸书、推特、 Instagram 和 LinkedIn 上与他们联系。要获得最新信息,请在https://www.cyberlayervr.com/注册接收电子邮件通知。
深度学习中的优化介绍:打破批处理规范化的神话
原文:https://blog.paperspace.com/busting-the-myths-about-batch-normalization/
认识这些人吗?如果不是,这些人自称。见鬼,他们甚至在探索频道上有一个自己的节目,他们试图不辜负他们的名字,试图打破神话,比如你是否可以用牙线反复腐蚀监狱酒吧。(警告:在服刑期间不要尝试这样做)。
受到他们的启发,我们 Paperspace 也打算做类似的事情。我们要解决的迷思是,批量标准化是否真的解决了内部协变量转移的问题**。虽然批处理规范化已经存在了几年,并且已经成为深度架构中的主要内容,但它仍然是深度学习中最容易被误解的概念之一。
批量定额真的解决了内部协变量转移吗?如果不是,那它是做什么的?你的整个深度学习教育都是骗人的吗?让我们来了解一下!
就在我们开始之前..
我想提醒你,这篇文章是关于深度学习中优化的系列的一部分,我们已经讨论过了:
- 如何使用随机梯度下降来对抗深度学习中的局部极小值和鞍点问题。
- 像 Momentum 和 Adam 这样的自适应方法如何增强 vanilla gradient descent 来处理优化曲面中的病态曲率。
- 如何使用不同的激活函数来解决渐变消失的问题。
我们从上一篇文章中学到的一个教训是,为了让神经网络有效地学习,提供给网络各层的分布应该是:
- 零中心的
- 时间和数据中的常数
第二个条件意味着被馈送到各层的数据的分布在被馈送到网络的小批量中不应该变化太多,并且随着训练的进行,它应该保持一定程度的恒定。相反的情况是,分布在不同时期快速变化。
内部协变量移位
让我们直奔主题吧。论文 批量归一化:通过减少内部协变量移位 加速深度网络训练,其前提是解决一个叫做内部协变量移位的问题。
那么,这个内部协变量转移,或者说 ICS,从现在开始我们会这么称呼它。当你的神经网络各层的输入分布最终波动时,就是。内部部分是指这种波动发生在神经网络的中间层,可以认为是网络的内部部分。协变量部分指的是分布被彼此不同的权重参数化的事实。转变,嗯,意味着分布在变化。
所以,让我们试着捕捉这件事是如何发生的。再一次,想象一个最简单的神经网络。线性堆叠的神经元,因此您也可以通过用层来替换神经元来扩展这种类比。
让我们假设我们正在为上面给出的网络优化损失函数$ L \(。神经元\)d\(的权重\) \omega_d $的更新规则是
$ \( \ frac { \ partial { L } } { \ partial { \ omega _ d } } = \ frac { \ partial { L } } * \ frac { \ partial { z _ d } } { \ partial { \ omega _ d } } \) $
这里$ z_d = \omega_d z_c \(是神经元\)d$的激活。简化,我们得到,
$ \( \ frac { \ partial { L } } { \ partial { \ omega _ d } } = \ frac { \ partial { L } } { \ partial { z _ d } } * z _ c \) $
因此,我们看到层\(d\)的权重梯度取决于层\(c\)的输出。这同样适用于神经网络中的任何层。神经元的权重梯度取决于它的输入,或者它后面一层的输出。(咄!)
这个梯度然后被反向传播,并且权重被更新。重复这个过程。现在,让我们回到层\(d\)。
由于我们对\(d\)执行了梯度更新,我们现在期望\(\omega_d\)获得更低的损失。然而,情况可能并非如此。为什么会这样呢?让我们仔细看看。
-
我们在迭代\(i\)时执行初始更新。让我们用$ p_c^i \(来表示迭代\)i\(时\)c\(的输出分布。现在,\)d\(的更新假设 c 的输入分布为\) p_c^i $。
-
然而,在向后传递期间,\(c,\omega_c\)的权重也被更新。这导致了\(c\)产出分布的变化。
-
在下一次迭代\(i+1\)中,假设\(z_c\)的分布已经转移到$ p_c^{i + 1} \(上。**由于图层\)d\(的权重是根据\) p_c^i \(更新的,现在图层\)d\(面对的是输入分布\) p_c^{i+1} $,这种差异可能会导致图层产生的输出根本不会减少损失。**
现在,我们可以提出两个问题。
- 输入分布的变化究竟是如何使一个层更难学习的?
- 这种转变是否会剧烈到足以导致上述情况?
我们先回答第一个问题。
为什么内部协变量转移甚至是一件事?
神经网络所做的是生成一个映射$ f \(将输入\) x \(映射到输出\) y \(上。如果\) x $的分布发生变化,究竟为什么会有所不同呢?
我是说,看,这里是$ x $正态分布的时候。
这里是$ x $不是正态分布的时候。
假设我们正在尝试的映射是$ f = 2x \(。如果\) x $的分布在一个地方有很多密度,或者它是均匀分布的,这有什么关系呢?
事实证明,这很重要。这很重要,因为神经网络,准确地说是现代深度网络,是非常强大的曲线拟合器。正如本叔叔告诉蜘蛛侠的那样,“权力越大,责任越大”。
让我们假设,我们有一个层$ l \(,它面向\) x \(,其分布如下。此外,让我们假设,到训练的这一点为止,由层\)l$学习的函数由虚线表示。
在迭代$ i $期间
现在,假设在梯度更新之后,当下一个小批次馈入网络时,x 的分布会变成这样。
在迭代$ i + 1 $期间
请注意,与之前的损失相比,这次小批量的损失更大。呀!为什么会这样?
让我们回到先前的数字。你看,我们最初学习的映射\(f\)在减少前一个小批量的损失方面做得很好。许多其他函数也是如此,它们在\(x\)不密集的区域表现非常不同。
适合相同输入\(X\) $ i $的不同函数
如果我们选择红色虚线给出的函数,我们下一个小批量的损失也会很低。
另一个功能会更适合
但是现在最突出的问题是,我们如何修改我们的算法,以至于我们最终学会了对应红色虚线的映射?简单的答案是,这个没有简单的答案。在这一点上,更好的做法是,我们不要试图找到解决这种情况的方法,而是首先集中精力预防它们。
ICS 最终破坏我们学习的原因是我们的神经网络总是在输入分布更密集的区域表现更好。由于密度较大区域的数据点在平均损失(我们正试图将其最小化)中占主导地位,因此密度较大区域的点损失减少更多。
然而,如果 ICS 最终在训练期间在后续批次中改变输入分布的更密集区域,则网络在先前迭代期间学习的权重不再是最优的。可能需要非常仔细地调整超参数才能获得合理的学习。这解释了为什么集成电路会成为这样一个问题。
我们所说的是在我们的小批量生产中有很大的差异。方差确保我们的映射不会过度专注于输入分布的一个区域。我们也希望平均值在零左右。你为什么想要一个以零为中心的图层输入,这个原因已经在之前的文章这里中详细讨论过了。
标准化输入
解决此问题的一种方法是将神经网络的输入标准化,使输入分布具有零均值和单位方差。然而,只有当网络不够深时,这才起作用。当网络变得更深时,比如说 20 层或更多层,即使输入被归一化,超过 20 层以上的权重的微小波动也会在输入到更深层的分布中产生大的变化。
一个不完全正确的类比是语言的变化,但是得到了要点。语言随着我们旅行的距离而变化。然而,短距离内的语言有很多相似之处。比如西班牙语和葡萄牙语。然而,这两种语言都来源于史前印欧语。8000 公里外的印度所讲的语言印度斯坦语也是如此。然而,西班牙语和印度斯坦语之间的差别比西班牙语和葡萄牙语之间的差别要大得多。原因是小距离上的微小变化被放大了很多。深层网络也是如此。
输入批量标准化
我们现在引入批量标准化的概念,它实际上是标准化一个层的输出激活,然后做更多的事情。这里有一个精确的描述。
$ $ \ begin { gather * } y _ I = bn _ { \ gamma,\ beta }(x _ I)\ tag { 1 } \ \ \ mu _ b = \frac{1}{m}\sum_{i=1}^{m}x_i \ tag { 2 } \ \ \ \sigma^2_b = \ frac { 1 } { m } \ sum _ { I = 1}m(x-\mu_b)2 \ tag { 3 } \ \ \ hat { x _ I } = \ frac { x _ I-\mu_b}{\sqrt{\sigma_\beta^2+\ epsilon } } \ tag { 4 } \ \ \ \ \ \ y _ I = \ gamma * \ hat { x _ I }+\ beta \ tag { 5 }
上面的等式描述了批处理规范层的作用。等式\(2-4\)描述了如何计算小批量中每个激活的均值和方差,然后减去均值以使激活居中,再除以标准偏差。这是为了确定小批量单位(1)中每次激活的标准偏差。
注意,这里计算的平均值和方差是小批量的平均值和方差。
方程式\(5\)是真正神奇的地方。\(\gamma\)和\(\beta\)是所谓的批处理规范化层的超参数。公式\(5\)的输出具有\(\beta\)的平均值和\(\gamma\)的标准差。实际上,批量标准化层有助于我们的优化算法控制该层输出的均值和方差。
揭穿 ICS 的神话
向世界介绍批量标准化的论文把它的成功归功于它摆脱了内部协变量的转移。然而,这是一个错误的说法,批处理规范根本不能防止 ICS。
内部协变量偏移就是我们训练网络时输入分布的变化。Batch Norm 具有超参数\(\gamma\)和\(\beta\)用于调整激活的平均值和方差。然而,这确实意味着当这些超参数被训练时,它们也会改变,并且批次范数固有地导致激活分布的改变,或者内部协变量的改变。如果它阻止了内部协变量转换,超参数\(\gamma\)和\(\beta\)就没有意义了。
那么,批量定额为什么行得通呢?
批量定额并不能治愈内部协变量转移。那是肯定的。如果不是,那么它为什么会起作用呢?!
GANs 的创始人 Ian Goodfellow 是人工智能领域最重要的研究人员之一,他在自己的一次演讲中给出了一个可能的解释(文章末尾给出了演讲的链接)。在这一点上,我必须提醒你,除非我们有具体的证据支持,否则这仅仅是猜测,不管它可能来自现代深度学习的重量级人物之一。
Goodfellow 认为,解释在于批处理规范层的两个超参数。
让我们再来考虑一下超级简单的玩具网络。
给你。当我们对\(a\)的权重进行梯度更新时,我们仅计算$ \ frac { \ partial { L } } { \ partial { a } } \(,即损失函数相对于\)a\(的灵敏度。但是,我们没有考虑到改变\)a\(的权重也会改变\)b,c,d$等其他层的输出。
同样,这实际上归结为我们无法使用二阶或更高阶的优化方法,因为使用这些算法的计算难度很大。梯度下降及其变体只能捕捉一阶相互作用(我们已经在本系列的第 2 部分这里中深入讨论过)。
深度神经网络具有更高阶的相互作用,这意味着除了损失函数之外,改变一层的权重也可能影响其他层的统计。这些跨层的相互作用,当无法解释时,会导致内部协变量的变化。每当我们更新一层的权重时,就有可能以不利的方式影响神经网络中更深一层的统计。
在这种情况下,收敛可能需要仔细的初始化、超参数调整和更长的训练持续时间。然而,当我们在层之间添加批量归一化层时,层的统计数据仅受两个超参数\(\gamma\)和\(\beta\)的影响。
现在,我们的优化算法只需调整两个超参数来控制任何层的统计数据,而不是前一层的全部权重。这大大加快了收敛速度,并避免了仔细初始化和超参数调整的需要。因此,批处理规范更像是一种检查点机制。
请注意,任意设置层的平均值和标准偏差的能力也意味着我们可以恢复原始分布,如果这足以进行适当的训练的话。
激活前或激活后的批量定额
虽然原始论文谈到在激活函数之前应用批处理规范,但是在实践中发现在激活之后应用批处理规范会产生更好的结果。这似乎是有意义的,就好像我们在批量定额之后进行激活,那么批量定额层不能完全控制进入下一层的输入的统计,因为批量定额层的输出必须经过激活。在激活后应用批量定额的情况下,情况并非如此。
推断时的批量标准
在推断过程中使用批量规范化可能有点棘手。这是因为我们在推理时可能并不总是有一个批处理。例如,考虑对视频实时运行对象检测器。一次处理一个帧,因此没有批处理。
这是至关重要的,因为我们需要计算一个批次的均值{ x } \(和方差\)\sigma^2$,以产生批次范数层的输出。在这种情况下,我们在训练期间保持均值和方差的移动平均值,然后在推断期间插入这些均值和方差的值。这是大多数深度学习库采用的方法,这些库将批处理规范层开箱即用。
使用移动平均线的依据是大数定律。小批量的均值和方差是对真实均值和方差的非常嘈杂的估计。当批估计被称为批统计时,平均值和方差的真实值(我们不知道)被称为总体统计。大数定律表明,对于大量样本,批次统计数据将趋向于总体统计数据,这就是为什么我们在训练期间使用移动平均。由于优化算法的小批量性质,它还帮助我们消除了估计中的噪声。
如果我们可以选择在测试时使用批次,我们使用与上面相同的等式,除了在计算标准偏差的等式中有微小的变化。而不是方程式
$ \(
\sigma^2_b = \ frac { 1 } { m } \ sum _ { I = 1}^m(x-\mu_b)^2 \ tag { 3 } \ \
\) $
我们用,
$ \( \sigma^2_b = \ frac { 1 } { m-1 } \ sum _ { I = 1}^m(x-\mu_b)^2 \ tag { 3 } \ \ \) $
我们在分母中使用\(m-1\)而不是\(m\)的原因是,因为我们已经估计了平均值,所以现在我们的 minibatch 中只有\(m-1\)个独立实体。如果不是这样,平均值可能是任意的数字,但是我们有一个固定的平均值,我们用它来计算方差。这些独立实体被称为自由度,对它们的讨论超出了本文的范围。
作为正则化子的批范数
批量定额也起正则化作用。每批产品的平均值和方差估计值是真实平均值的噪声版本,这给我们的优化搜索带来了随机性。这有助于正规化。
结论
虽然 Batch Norm 现在已经被确立为深度架构的标准元素,但是直到最近才开始研究它是如何工作的。最近的一篇论文得到了很多关注,题目是批处理规范化如何帮助优化?(不,这不是关于内部协变量移位)它演示了与不使用批处理范数的网络相比,批处理范数实际上最终如何增加内部协变量移位。论文中的关键观点是,批处理规范实际上使损失表面更平滑,这就是它如此有效的原因。去年,我们还引入了 SELUs 或比例指数线性单位激活函数,它隐式地规范化了通过它们的激活,这是通过批处理规范显式完成的。SELU 的原始论文包含大约 100 页的数学内容,展示了这是如何发生的,我们鼓励有数学倾向的人去阅读。
优化是深度学习中一个令人兴奋的领域。虽然深度学习的许多应用已经被利用并投入使用,但直到现在我们才开始涉足深度学习理论的诱人领域。
最后,我们想说..流言被终结了。
进一步阅读
- 批量定额试卷
- 批量规范化对优化有什么帮助?(不,不是关于内部协变量移位)
- 比例指数线性单位
- Ian Goodfellow 关于批量标准化的讲座
- Reddit 讨论是否将批次定额放在 ReLU 之前或之后**
胶囊网络:快速入门
无论你是深度学习的新手还是认真的研究人员,你肯定都遇到过术语卷积神经网络。它们是该领域中研究最多、性能最高的架构之一。也就是说,当输入数据处于不同方向时,CNN 在识别输入数据的特征方面有一些缺点。为了解决这个问题,Geoffrey E. Hinton 和他的团队 Sara Sabour 和 Nicholas Frosst 提出了一种新型的神经网络。他们称它们为 胶囊网络 。
在本文中,我们将讨论以下主题来介绍胶囊网络:
- 卷积神经网络和方向问题
- CNN 中的池化问题
- 胶囊网络背后的直觉
- 胶囊网络体系结构
- 最终注释
卷积神经网络和方向问题
卷积神经网络是最流行的深度学习架构之一,广泛用于计算机视觉应用。从图像分类到对象检测和分割,CNN 已经定义了最先进的结果。也就是说,这些网络对于不同类型的图像有它们自己的复杂性和困难。让我们从他们的起源开始,然后看看他们目前的表现。
Yann LeCun 在 1998 年首次提出了 CNN。然后,他能够用一个简单的五层卷积神经网络检测手写数字,该网络在 MNIST 数据集上进行训练,包含 6 万多个例子。想法很简单:训练网络,识别图像中的特征,并对它们进行分类。然后在 2019 年, EfficientNet-B7 在 ImageNet 数据集上实现了最先进的图像分类性能。该网络可以从超过 120 万张图像中识别特定图片的标签,准确率为 84.4%。
看着这些结果和进展,我们可以推断卷积方法使得用简单的计算学习许多复杂的特性成为可能。通过对我们的输入执行许多矩阵乘法和求和,我们可以得到问题的可靠答案。
但是 CNN 并不完美。如果 CNN 接收到不同大小和方向的图像,它们就会失败。
让我举一个例子。比方说你把一张脸倒过来旋转,然后把它喂给一个 CNN 它将无法识别眼睛、鼻子或嘴巴等特征。同样,如果你重建面部的特定区域(例如,交换鼻子和眼睛的位置),网络仍然能够识别这张脸——即使它不再是一张脸。简而言之,CNN 可以通过统计来学习图像的模式,但不能学习不同方向上的实际图像。
Left image: Pug: 0.8. Right image: Pug: 0.2
有线电视新闻网的问题是
要理解池,你必须知道 CNN 是如何工作的。CNN 的构建模块是卷积层。这些层负责识别给定图像中的特征,如曲线、边缘、锐度和颜色。最终,网络的完全连接层将结合非常高级的特征并产生分类预测。你可以在下面看到一个基本卷积网络的样子。
作者使用最大/平均池操作,或者在整个网络中具有连续的卷积层。汇集操作减少了不必要的信息。使用这种设计,我们可以减少流经网络的数据的空间大小,从而增加更高层神经元的“视野”,使它们能够在输入图像的更宽区域中检测更高阶的特征。这就是 CNN 中的最大池操作如何帮助实现最先进的性能。话虽如此,我们不应被它的表现所迷惑;CNN 比之前的任何模型都要好,但是最大池仍然丢失了有价值的信息。
Max pooling operation extracting higher-level features
杰弗里·辛顿在他的一次演讲中指出:
“卷积神经网络中使用的池操作是一个很大的错误,它如此有效的事实是一场灾难!”
现在让我们看看胶囊网络如何克服这个问题。
胶囊网络如何工作
为了克服这个涉及图像旋转关系的问题,Hinton 和 Sabour 从神经科学中获得了灵感。他们解释说,大脑被组织成模块,这些模块可以被认为是胶囊。考虑到这一点,他们提出了胶囊网络,该网络结合了动态路由算法来估计物体的特征,如姿态(位置、大小、方向、变形、速度、反照率、色调、纹理等)。这项研究于 2017 年发表在他们题为 胶囊 间动态路由的论文中。
那么,什么是“胶囊”呢?动态路由是如何工作的?
胶囊网络中的胶囊是什么?
与正常的神经元不同,胶囊对其输入进行计算,然后将结果“封装”到一个高度信息输出的小向量中。胶囊可以被认为是普通人工神经元的替代品;人工神经元处理标量,而胶囊处理矢量。例如,我们可以将人工神经元采取的步骤总结如下:
1\. Multiply the input scalars with the weighted connections between the neurons.
2\. Compute the weighted sum of the input scalars.
3\. Apply an activation function (scalar nonlinearity) to get the output.
另一方面,为了实现输入的仿射变换(保持共线和距离比),除了上面列出的步骤之外,胶囊还要经过几个步骤。这里,过程如下:
1\. Multiply the input vectors by the weight matrices (which encode spatial relationships between low-level features and high-level features) (matrix multiplication).
2\. Multiply the result by the weights.
3\. Compute the weighted sum of the input vectors.
4\. Apply an activation function (vector non-linearity) to get the output.
Image: Capsule vs Artificial Neuron (source)
让我们更详细地看看每个步骤,以便更好地理解胶囊的工作原理。
1.将输入向量乘以权重矩阵(仿射变换)
输入向量代表初始输入,或者由网络中较早的层提供的输入。这些向量首先乘以权重矩阵。如前所述,权重矩阵捕捉空间关系。假设一个物体以另一个物体为中心,它们的大小比例相等。输入向量和权重矩阵的乘积将表示高级特征。例如,如果低级特征是鼻子、嘴、左眼和右眼,那么如果四个低级特征的预测指向人脸的相同方向和状态,则人脸将是预测的人脸(如下所示)。这就是“高级”的特点。
Prediction of a face (source)
2.将结果乘以权重
在这一步中,将前一步获得的输出乘以网络的权重。重量会是多少?在通常的人工神经网络(ANN)中,基于误差率调整权重,然后反向传播。然而,这种机制并不适用于胶囊网络。动态路由决定了网络中权重的修改。这定义了为神经元连接分配权重的策略。
胶囊网络调整权重,使得低级胶囊与其附近的高级胶囊强相关联。邻近度量由我们之前讨论的仿射变换步骤(步骤 1)确定。计算从仿射变换步骤获得的输出和低级胶囊预测的密集聚类之间的距离(如果低级胶囊做出的预测相似,从而彼此靠近,则可以形成密集聚类)。基于距离度量,在已经做出的预测的聚类和新预测的聚类之间具有最小距离的高级胶囊将具有较高的权重,而剩余的胶囊将被分配较低的权重。
Dynamic Routing in a Capsule Network
在上图中,权重将按以下顺序分配:中间>左边>右边。简而言之,动态路由算法的本质可以被看作是这样的:较低级别的封装将把它的输入发送到与它的输入“一致”的较高级别的封装。
3.计算输入向量的加权和
这汇总了上一步获得的所有输出。
4.应用激活函数(向量非线性)以获得输出
在胶囊网络中,通过“挤压”(即通过激活函数)输出向量来获得向量非线性,使其具有长度 1 和恒定方向。非线性函数由下式给出:
其中s[j]是上一步得到的输出,v[j]是应用非线性后得到的输出。等式的左侧执行额外的挤压,而等式的右侧执行输出向量的单位缩放。**
总的来说,动态路由算法总结如下:
第 1 行:这一行定义了路由过程,它将仿射变换输入(u)、路由迭代次数(r)和层数(l)作为输入。
第 2 行:b[ij] 是一个临时值,最终用来初始化 c[i] 。
第 3 行:for 循环迭代“r”次。
第 4 行:应用于 b[i] 的 softmax 函数确保输出一个非负的 c[i] ,其中所有输出总和为 1。
第 5 行:对于后续层中的每个胶囊,计算加权和。
第 6 行:对于后续层中的每个胶囊,加权和被压缩。
第 7 行:权重 b[ij] 在这里被更新。u[ji] 表示低级舱 I 对舱的输入,v[j] 表示高级舱 j 的输出
CapsNet 架构
CapsNet 架构由一个编码器和一个解码器组成,每个都有一组三层。编码器具有卷积层、PrimaryCaps 层和 DigitCaps 层;解码器有 3 个完全连接的层。
现在,让我们来看看这些网络。例如,我们将在 MNIST 数据集的上下文中考虑这一点。
编码器网络
CapsNet Encoder Architecture (source)
编码器有两个卷积层和一个全连接层。第一卷积层 Conv1 ,具有 256 个 9×9 卷积核,步长为 1,以及一个 ReLU 激活函数。该层负责将像素强度转换为局部特征检测器的活动,然后反馈给 PrimaryCaps 层。 PrimaryCaps 层是一个卷积层,它有 32 个信道的卷积 8-D 胶囊(每个胶囊有 8 个卷积单元,内核为 9×9,步长为 2)。主胶囊执行反向图形,这意味着它们对实际图像生成的过程进行反向工程。胶囊将八个 9×9×256 核应用到 20×20×256 输入体积上,这给出了 6×6×8 输出张量。由于有 32 个 8-D 胶囊,因此输出大小为 6×6×8×32。 DigitCaps 层每个类有 16 维胶囊,每个胶囊接收来自低级胶囊的输入。
8×16 W[ij] 是用于对每个 8-D 胶囊进行仿射变换的权重矩阵。前面讨论的路由机制总是存在于两个封装层之间(比如,在 PrimaryCaps 和 DigitCaps 之间)。
最后,使用重建损失对实例化参数进行编码。针对所有输出类计算每个训练示例的损失。总损耗是所有数字胶囊损耗的总和。损耗方程由下式给出:
其中:
如果存在 k 类的数字,则 T[k] = 1
m^+ = 0.9
m^- = 0.1
v[k] =从 DigitCaps 层获得的矢量
等式的第一项表示正确数字电容的损耗,第二项表示不正确数字电容的损耗。
解码器网络
CapsNet Decoder Architecture (source)
解码器取出正确的 16-D 数字胶囊并将其解码成图像。不会考虑任何不正确的数字胶囊。通过找到输入图像和重建图像之间的欧几里德距离来计算损失。
解码器有三个完全连接的层。第一层有 512 个神经元;第二个有 1024 个神经元;第三个具有 784 个神经元(为 MNIST 提供 28×28 的重建图像)。
最终注释
胶囊网络可以被认为是人脑的“真实模仿”。与不评估给定数据中的空间关系的卷积神经网络不同,胶囊网络将图像中各部分的方向视为数据分析的关键部分。他们检查这些层次关系,以更好地识别图像。我们的大脑使用的逆图形机制在这里被模仿,以建立图像的分层表示,并将其与网络学习的内容相匹配。虽然它的计算效率还不够高,但在处理现实世界的场景时,它的准确性似乎确实有所提高。胶囊的动态路由使这一切成为可能。它采用了一种不寻常的策略来更新网络中的权重,从而避免了池操作。久而久之认为,胶囊网络必将渗透到其他各个领域,使机器变得更像人。
通道注意和挤压-激发网络(SENet)
原文:https://blog.paperspace.com/channel-attention-squeeze-and-excitation-networks/
在计算机视觉的注意机制的早期,在 CVPR 2018(和 TPAMI)上发表的一篇论文,挤压和激励网络,介绍了一种新颖的通道注意机制。这种简单而高效的附加模块可以添加到任何基线架构中,以获得性能改善,而计算开销可以忽略不计。
在本文中,我们将分四部分讨论压缩-激发网络。首先,我们将通过参观现代摄影技术中的一些方面来理解为什么通道注意力是重要的背后的直觉。然后,我们将推进到在挤压和激发(SE)块中计算通道注意所涉及的方法。接下来,我们将剖析标准架构中挤压和激励(SE)模块的影响,同时评估它们在不同计算机视觉任务中的作用。最后,我们将对这篇论文提出的方法中的某些缺点进行评论。
目录
- 现代摄影中的画面选择
- 卷积神经网络中的通道注意
- 压缩和激励网络
- 密码
- 有效网中的 MBConv
- 基准
- 缺点
- 参考
现代摄影中的画面选择
Frame Shots in Pixel 2
随着现代摄影技术在捕捉最佳镜头的智能机制方面经历了几代人的改进,一种最微妙的技术已经不为人知,那就是选择静态照片的最佳帧镜头。这是某些智能手机的常见特征。
一张静止的照片中有许多可变因素。在同样的条件和环境下,同一主题相隔一秒钟拍摄的两张照片仍然会有很大差异。例如,在两张照片中的一张中,他们的眼睛可能是闭着的。为了获得最佳照片,最好在拍摄照片的瞬间捕捉多个帧,这样摄影师可以从捕捉的所有帧中选择最佳帧。如今,这是以自动化、智能化的方式完成的。像谷歌 Pixel 这样的智能手机能够在拍摄单张照片时,从所有可用的帧中挑选出最佳帧。这种智能机制受不同因素的制约,如照明、对比度、模糊、背景失真等。抽象地说,智能机制是选择包含照片的最佳代表信息的帧。
就现代卷积神经网络架构而言,您可以将帧视为由卷积层计算的张量中的通道。这个张量通常用 a ( B,C,H,W )维度表示,其中 B 表示批量, C 表示通道, H , W 表示特征图的空间维度( H 表示高度, W 表示宽度)。信道是卷积滤波器从输入中导出不同特征的结果。然而,通道可能不具有相同的代表性重要性。由于某些信道可能比其他信道更重要,因此在传播到下一层之前,根据信道的重要性对其应用权重是有意义的。
我们将利用这一点作为对渠道关注重要性的基本理解,这一点我们将在接下来的章节中讨论。
渠道关注
基于上一节中描述的直觉,让我们深入了解为什么通道注意力是提高深度卷积神经网络架构的泛化能力的关键组件。
概括地说,在卷积神经网络中,有两个主要组件:
- 用维度( B 、 C 、 H 、 W )表示的输入张量(通常是四维张量)。
- 包含该层权重的可训练卷积滤波器。
卷积滤波器负责基于这些滤波器内学习到的权重来构建特征图。一些滤波器学习边缘,另一些学习纹理,并且它们共同学习由输入张量嵌入的图像内的目标类别信息的不同特征表示。因此,信道的数量代表学习输入的不同特征映射的卷积滤波器的数量。从我们以前对摄影中选帧的理解来看,这些特征图也有不同的重要性。这意味着一些特征地图比其他的更重要。例如,与学习背景纹理过渡的另一个特征图相比,包含边缘信息的特征图对于学习可能更加重要和关键。因此,在基本层面上,人们希望提供与对应的特征地图相比具有更高重要程度的“更重要的”特征地图。
Example Feature Maps
这是渠道关注的基础。我们希望将这种“注意力”集中在更重要的渠道上,这基本上是给予特定渠道比其他渠道更高的重要性。最简单的方法是用更高的值来缩放更重要的通道。这正是压缩激发网络所提出的。
压缩和激励网络
2018 年,胡等人在 2018 上发表了题为 压缩-激发网络 的论文,论文的期刊版本在 TPAMI。这篇论文被誉为注意力机制领域最有影响力的作品之一,已经被引用了 1000 多次。让我们看看文件提出了什么。
Squeeze-Excitation Module
本文提出了一种新型、易于插入的模块,称为挤压激励模块(缩写为 SE-block),由三个组件组成(如上图所示):
- 挤压模块
- 激励模块
- 比例模块
让我们更详细地了解这些模块,并理解它们在渠道关注方面的重要性。
挤压模块
为了获得最佳的频道关注,人们会希望特征图的缩放自适应于特征图本身。概括地说,特征映射集本质上是来自卷积层的输出张量(通常是四维维度的张量( B,C,H,W ),其中首字母代表特征映射的批量大小、通道、高度和宽度)。为简单起见,我们仅将其视为形状的三维张量( C,H,W)–本质上,我们关心的是深度(张量中通道/特征图的数量)和该张量中每个特征图的空间维度。因此,为了使通道注意力适应每个通道本身,我们总共要关注 H × W 个像素(或值)。这基本上意味着,为了使注意力真正适应,你将总共使用C×H×W值进行操作。该值将变得非常大,因为在现代神经网络中,通道的数量随着网络深度的增加而变大。因此,需要使用能够将每个特征图的信息分解成奇异值的特征描述符,这将有助于降低整个操作的计算复杂度。
这就形成了挤压模块的动机。存在许多特征描述符可用于将特征图的空间维度减少到奇异值,但是用于在卷积神经网络中减少空间大小的一般方法是汇集。有两种非常流行的池化方法:平均池化和最大池化。前者计算定义窗口内的平均像素值,而后者取同一定义窗口内的最大像素值。两者都有各自的优点和缺点。虽然 max pooling 保留了最活跃的像素,但它也会非常嘈杂,并且不会考虑相邻像素。另一方面,平均池不能保存信息;然而,它构建了该窗口中所有像素的更平滑的平均值。
作者进行了一项消融研究,以调查每个描述符的性能,即全局平均池(GAP)和全局最大池(GMP),如下表所示。
描述符 | 最高错误率 | 前 5 名错误率 |
---|---|---|
良好操作规范 | Twenty-two point five seven | Six point zero nine |
缝隙 | 22.28 | 6.03 |
因此,挤压模块选择两者中更平滑的选项,并使用全局平均池(GAP)操作,该操作通过取该特征图中所有像素的平均值,基本上将整个特征图减少到单一值。因此,就维度而言,如果输入张量是( C × H × W ),那么在通过间隙算子之后,所获得的输出张量将是( C ×1×1)的形状,本质上是长度为 C 的向量,其中每个特征图现在被分解为奇异值。
为了验证挤压算子的重要性,作者进一步比较了挤压变量和非挤压变量,如下表所示。注意:无挤压变体本质上意味着包含特征图的张量没有被减少到单个像素,并且激励模块在整个张量上操作。
不同的 | 最高错误率 | 前 5 名错误率 | GFLOPs | 因素 |
---|---|---|---|---|
Vanilla ResNet-50 | Twenty-three point three | Six point five five | 3.86 | 25.6 米 |
诺斯奎泽 | Twenty-two point nine three | Six point three nine | Four point two seven | 28.1 米 |
如果 | 22.28 | 6.03 | Three point eight seven | 28.1 米 |
激励模块
Example of a Multi-Layer Perceptron (MLP) structure.
现在,随着输入张量被分解成(C×1×1)的相当小的尺寸,模块的下一部分是学习这些通道的自适应缩放权重。对于挤压和激励块中的激励模块,作者选择完全连接的多层感知器(MLP)瓶颈结构来映射缩放权重。这个 MLP 瓶颈有一个单独的隐藏层,以及形状相同的输入层和输出层。隐藏层用作缩减块,其中输入空间缩减为由缩减因子(默认设置为 16)定义的更小空间。然后将压缩空间扩展回原始维度作为输入张量。用更简洁的术语来说,MLP 每一层的维度变化可以由以下三点来定义:
- 输入的形状为( C ×1×1)。因此,在输入层有 C 个神经元。
- 隐藏层通过缩减因子 r 来减少这一点,从而导致神经元的总数为 C / r 。
- 最后,输出被投射回与输入相同的维度空间,总共回到 C 神经元。
总的来说,你传递( C ×1×1)张量作为输入,得到一个相同形状的加权张量——(C×1×1)。
作者提供了使用不同缩减率( r )对 ResNet-50 架构中的阿瑟模块的性能进行实验的结果,如下表所示。
r | 最高错误率 | 前 5 名错误率 | 因素 |
---|---|---|---|
Two | Twenty-two point two nine | Six | 45.7 米 |
four | 22.25 | Six point zero nine | 35.7 米 |
eight | Twenty-two point two six | 5.99 | 30.7 米 |
Sixteen | Twenty-two point two eight | Six point zero three | 28.1 米 |
Thirty-two | Twenty-two point seven two | Six point two | 26.9 米 |
香草 | Twenty-three point three | Six point five five | 25.6 米 |
理想情况下,为了改善信息传播和更好的跨信道交互(CCI),应将 r 设置为 1,从而使其成为一个每层宽度相同的全连通方形网络。然而,随着 r 的降低,复杂性的增加和性能的提高之间存在着一种平衡。因此,基于上表,作者使用 16 作为减少比率的默认值。这是一个超参数,可以进一步调整以提高性能。
比例模块
从激发模块获得( C ×1×1)“激发”张量后,它首先通过一个 sigmoid 激活层,该激活层将数值调整到 0-1 的范围内。随后,通过简单的广播的逐元素乘法,将输出直接应用于输入,该乘法利用来自激励模块中的 MLP 的相应学习权重来缩放输入张量中的每个通道/特征图。
作者对用作激励算子的不同非线性激活函数的效果做了进一步的消融研究,如下表所示。
激活功能 | 最高错误率 | 前 5 名错误率 |
---|---|---|
热卢 | Twenty-three point four seven | Six point nine eight |
双曲正切 | Twenty-three | Six point three eight |
乙状结肠的 | 22.28 | 6.03 |
基于这些结果,作者确定 Sigmoid 是性能最好的激活函数,因此将其用作比例模块中的默认激励算子。
概括地说,挤压激励块(SE 块)获取形状为( C × H × W 的输入张量 x ,通过全局平均池(GAP)将其简化为形状为( C ×1×1)的张量,随后将该 C 长度向量传递到多层感知器(MLP)瓶颈结构中,并输出相同形状的加权张量( C
现在的问题是:模块“插入”在哪里,例如在剩余网络中?
SE block integration designs explored in the ablation study.
如上图所示,作者为 SE 块尝试了不同的集成策略。其中包括:
- 标准 SE
- SE-PRE
- SE-POST
- 自我认同
标准 SE 块正好在架构的最后卷积层之后应用,在这种情况下是剩余网络,正好在跳跃连接的合并之前。SE-PRE 配置是通过将 SE 块放置在第一个卷积层之前的块的开始处来构建的,而 SE-POST 则相反,将其放置在块的末端(在合并跳过连接之后)。最后,SE-Identity 块将 SE-module 应用于 skip 连接分支本身,与主块平行,并作为正常残差添加到最终输出。
作者提供了他们对整合策略的广泛消融研究的结果,如下表所示:
表 1。不同 SE 整合策略对 ResNet-50 在 ImageNet 分类任务中错误率的影响。
战略 | 最高错误率 | 前 5 名错误率 |
---|---|---|
如果 | Twenty-two point two eight | Six point zero three |
SE-PRE | Twenty-two point two three | 6.00 |
SE-POST | Twenty-two point seven eight | Six point three five |
自我认同 | 22.20 | Six point one five |
表二。在残差块中的空间 3×3 卷积层之后引入 SE 块的效果。
设计 | 最高错误率 | 前 5 名错误率 | GFLOPs | 因素 |
---|---|---|---|---|
如果 | 22.28 | Six point zero three | Three point eight seven | 28.1 米 |
si-3×3 战斗机 | Twenty-two point four eight | 6.02 | 3.86 | 25.8 米 |
从表 1 中可以看出,除 SE-POST 之外的所有配置都提供了相似且一致的性能。如表 2 所示,作者进一步试验了在残差块中的空间卷积之后插入 SE 块。由于 3×3 空间卷积具有较少的通道数,因此参数和触发器开销要小得多。虽然与默认的 SE 配置相比,它能够提供类似的性能,但作者没有提供任何关于哪种配置最有利的结论性声明,而是将“SE”作为默认的集成配置。
幸运的是,作者确实回答了如何将 SE-block 集成到现有架构中的问题。
SE-ResNet Module
在残差网络中,在跳跃连接中添加残差之前的块中,在最后的卷积层之后插入挤压激励块。这背后的直觉是保持跳过连接分支尽可能干净,以使学习身份容易。
SE-Inception Module
然而,在初始网络中,由于没有跳跃连接,se 块被插入到最后一个卷积层之后的每个初始块中。
在论文的下图中,作者展示了修改后的 ResNet-50 和 ResNext-50 架构,每个模块中都有一个 SE 模块。
SE-Based Architecture Designs
作者在 ResNet-50 的 4 个不同阶段中广泛研究了 SE-block 的整合策略。结果如下表所示。
阶段 | 最高错误率 | 前 5 名错误率 | GFLOPs | 因素 |
---|---|---|---|---|
ResNet-50 | Twenty-three point three | Six point five five | 3.86 | 25.6 米 |
SE 阶段 2 | Twenty-three point zero three | Six point four eight | Three point eight six | 25.6 米 |
SE 阶段 3 | Twenty-three point zero four | Six point three two | Three point eight six | 25.7 米 |
SE 阶段 4 | Twenty-two point six eight | Six point two two | Three point eight six | 26.4 米 |
如果全部 | 22.28 | 6.03 | Three point eight seven | 28.1 米 |
密码
与论文相关的官方代码库可以在这里找到。然而,代码是在 Caffe 中构建的——一个现在不太流行的框架。让我们看看 PyTorch 和 TensorFlow 版本的模块。
PyTorch
### Import necessary packages
from torch import nn
### Squeeze and Excitation Class definition
class SE(nn.Module):
def __init__(self, channel, reduction_ratio =16):
super(SE, self).__init__()
### Global Average Pooling
self.gap = nn.AdaptiveAvgPool2d(1)
### Fully Connected Multi-Layer Perceptron (FC-MLP)
self.mlp = nn.Sequential(
nn.Linear(channel, channel // reduction_ratio, bias=False),
nn.ReLU(inplace=True),
nn.Linear(channel // reduction_ratio, channel, bias=False),
nn.Sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.gap(x).view(b, c)
y = self.mlp(y).view(b, c, 1, 1)
return x * y.expand_as(x)
TensorFlow
import tensorflow as tf
__all__ = [
'squeeze_and_excitation_block',
]
def squeeze_and_excitation_block(input_X, out_dim, reduction_ratio=16, layer_name='SE-block'):
"""Squeeze-and-Excitation (SE) Block
SE block to perform feature recalibration - a mechanism that allows
the network to perform feature recalibration, through which it can
learn to use global information to selectively emphasise informative
features and suppress less useful ones
"""
with tf.name_scope(layer_name):
# Squeeze: Global Information Embedding
squeeze = tf.nn.avg_pool(input_X, ksize=[1, *input_X.shape[1:3], 1], strides=[1, 1, 1, 1], padding='VALID', name='squeeze')
# Excitation: Adaptive Feature Recalibration
## Dense (Bottleneck) -> ReLU
with tf.variable_scope(layer_name+'-variables'):
excitation = tf.layers.dense(squeeze, units=out_dim/reduction_ratio, name='excitation-bottleneck')
excitation = tf.nn.relu(excitation, name='excitation-bottleneck-relu')
## Dense -> Sigmoid
with tf.variable_scope(layer_name+'-variables'):
excitation = tf.layers.dense(excitation, units=out_dim, name='excitation')
excitation = tf.nn.sigmoid(excitation, name='excitation-sigmoid')
# Scaling
scaler = tf.reshape(excitation, shape=[-1, 1, 1, out_dim], name='scaler')
return input_X * scaler
有效网中的 MBConv
MobileNet v2 和 Efficient Nets 采用了压缩激励模块,其中一些最具影响力的工作都使用了移动反向残差模块(MBConv)。高效网络还增加了一个挤压激励模块。
MBConv Blocks in Efficient Nets
在 MBConv 中,压缩激励模块放在最后一个卷积层之前,在该模块的空间卷积之后。这使得它更像是一个不可或缺的部分,而不是一个附件,这正是它最初的目的。SE-Net 的作者进行了消融研究,也测试了这种积分方法,但是他们选择了在最终 1×1 卷积后添加 SE-block 的默认配置。在许多任务中,从标准 ImageNet-1k 数据集上的图像分类到 MS-COCO 数据集上的对象检测,高效的网络被认为是最先进的(SOTA)。这证明了通道注意力的重要性,以及挤压兴奋块的效率。
基准
作者在 ImageNet、MS-COCO 和 Places-365 等竞争标准数据集上提供了图像分类、场景分类和对象检测等不同任务的大量结果。下表展示了在上述任务中使用 SE 模块的效率和优势:
CIFAR-10 分类任务
体系结构 | 香草 | SE-variant |
---|---|---|
ResNet-110 | Six point three seven | 5.21 |
ResNet-164 | Five point four six | 4.39 |
WRN-16-8 | Four point two seven | 3.88 |
Shake-Shake 26 2x96d +镂空 | Two point five six | 2.12 |
这里用于比较的度量是分类误差。作者还添加了一种数据增强形式,即 Shake-Shake 网络中的 Cutout,以确认使用 SE-module 时获得的性能改善是否与使用不同的性能增强技术(如数据增强技术)一致。
CIFAR-100 分类任务
体系结构 | 香草 | SE-variant |
---|---|---|
ResNet-110 | Twenty-six point eight eight | 23.85 |
ResNet-164 | Twenty-four point three three | 21.31 |
WRN-16-8 | Twenty point four three | 19.14 |
Shake-Shake 26 2x96d +镂空 | Fifteen point eight five | 15.41 |
ImageNet-1k 分类任务
ImageNet classification performance comparison for standard deep architectures
ImageNet classification performance comparison for light mobile architectures
训练动态
Training curves of different networks with and without Squeeze Excitation (SE).
如上图所示,配备挤压和激励模块的网络显示出一致的改进曲线,从而导致更好的通用性和更高的性能。
Places-365 数据集上的场景分类任务
体系结构 | 最高错误率 | 前 5 名错误率 |
---|---|---|
地点-365-CNN | Forty-one point zero seven | Eleven point four eight |
ResNet-152 | Forty-one point one five | Eleven point six one |
SE-ResNet-152 | 40.37 | 11.01 |
基于快速 RCNN 的 MS-COCO 数据集上的目标检测任务
毅力 | AP@IoU=0.5 | 美国联合通讯社(Associated Press) |
---|---|---|
ResNet-50 | Fifty-seven point nine | Thirty-eight |
SE-ResNet-50 | 61.0 | 40.4 |
ResNet-101 | Sixty point one | Thirty-nine point nine |
SE-ResNet-101 | 62.7 | 41.9 |
缺点
尽管这篇论文本身是革命性的,但它的结构和一些不确定的设计策略存在一些缺陷。
- 这种方法成本很高,并且在基线模型的基础上增加了大量的参数和触发器。尽管从整体来看,这种开销可能非常小,但已经有许多旨在以极低的成本提供频道关注的新方法,其表现优于 SENets,例如 ECANet (在 2020 年 CVPR 发布)。
- 尽管信道注意在参数和 FLOPs 开销方面似乎是有效的,但是一个主要缺陷是缩放操作,其中加权信道向量被广播并被逐元素地应用/乘以输入张量。这个中间广播张量与输入张量具有相同的维度空间,导致记忆复杂度大幅度增加。这使得训练过程更慢并且更加占用内存。
- 为了降低计算复杂度,在该块的激励模块的 MLP 中存在瓶颈结构,其中通道的数量以指定的减少比率减少。这导致信息丢失,因此不是最佳的。
- 由于 SENet 仅围绕通过使用专用的全局特征描述符(在这种情况下是全局平均池(GAP ))来提供频道关注,因此存在信息损失,并且所提供的关注是点状的。这意味着所有像素都被统一映射到特征图的空间域中,因此不会区分重要的或确定类别的像素与属于背景的一部分或不包含有用信息的像素。因此,空间注意力的重要性/需要与通道注意力相结合是合理的。同样的一个主要例子是 CBAM (在 2018 年 ECCV 发布)。
- 关于 SENet 有一些不确定的设计策略。作者指出,这超出了本文理解最佳设置的范围,包括 SE 模块的位置整合策略和 MLP 中使用的缩减率。
参考
- 挤压和激励网络,TPAMI 2018。
- CBAM:卷积块注意模块,ECCV 2018。
- ECA-Net:深度卷积神经网络的有效通道关注,CVPR 2020。
- SENet 原始存储库。
张量流中的检查点
几个月前在研究机器学习问题时,我意识到我需要一种更好的方法来保存训练期间模型的最佳版本。这使我找到了一种在满足特定条件时保存模型的方法。在本文中,我将讨论什么是检查点,以及如何使用 TensorFlow 实现它。
先决条件
- 熟悉 Python 编程
- 理解基本的机器学习术语
- 基本熟悉 TensorFlow
检查点
检查点通常包括保存系统的状态,以便以后可以恢复使用。通过这种方式,原本会丢失的信息得以保留并重新加载以备后用。是的,它非常类似于仅仅保存一个文件,但是作为一个术语,当计算系统在自然复杂的条件下被保存时,它被更频繁地使用。
在机器学习中,检查点涉及保存模型的当前状态(架构、权重、优化器等等),以便以后可以重新加载使用。
为什么要检查点?
-
中断的训练循环:当一个训练序列被有意或无意终止时,整个进度可能会丢失。就计算成本和开发时间而言,这可能是昂贵的。检查点有助于存储模型,以备以后继续训练时使用。
-
为生产保存最佳模型:在培训期间,通常情况下,最终模型在所需指标方面不是最佳的。这可能导致人们试图识别最佳时期,然后仅针对该数量的时期进行训练。然而,鉴于这些模型的随机性质,这是一个将被浪费的重要时间。当期望的条件被满足时,检查点有助于保存模型,使得模型的训练和保存成为更直接的过程。
Tensorflow
Tensorflow 是一个用于端到端机器学习的开源平台。虽然它支持多种语言,但机器学习最流行的选择是 Python,这也是我们将在本文中使用的。Tensorflow 在tf.train.checkpoint
下有一个检查点方法,但是在这里,我们将使用 Keras 模块下的ModelCheckpoint
回调方法。
模型检查点及其参数
回调是满足特定条件时执行的程序或脚本片段。在我们的例子中,条件是在一些训练时期之后保存模型。keras.callbacks.ModelCheckpoint
方法实现了检查点,但是需要我们指定一些参数。我们将简要讨论这些。
filepath
:这是唯一需要的参数,指的是存储的检查点的位置。这里的 Required 意味着,一旦指定了这一点,就可以将检查点回调与已经设置为默认值的其他参数一起使用。
monitor
:这个参数是一个字符串,它决定了在训练期间检查点回调所要监控的度量。默认情况下,它被设置为val_loss
,但是可以调整为最适合场景的值。为了监控训练指标,排除了前缀val_
。此外,如果所需指标的名称是用户定义的,则应该作为参数传入。
verbose
:该参数是一个整数,用于设置日志记录级别,可以是0
或1
,表示无或全部。它的默认值为0
。
save_only_best_model
:该参数接受一个布尔值来指定只保存最好的模型。这是基于它被设置为监控的任何指标。默认的是False
,所以如果没有改变,模型将会在每个实例中保存到相同的文件路径。如果目标是单独保存多个模型,必须小心,因为如果文件名没有被格式化以反映时期并区分文件,则模型将在文件名处用每个时期覆盖指定的文件。
mode
:模式参数是一个字符串,可以设置为{'auto', 'min', 'max'}
之一。'min'
和'max'
分别为需要最小化(如损失)和最大化(如准确性)的指标设置。当设置为默认的'auto'
时,代表准确性或 F 分数的指标最大化,所有其他指标最小化。
save_weights_only
:该参数接受一个布尔值,决定是否只保存模型的权重。如果True
,将调用model.save_weights
方法,而不是model.save
函数。
save_freq
:该参数接受字符串'epoch'
以在每个历元后保存模型,或者接受一个整数以批次数表示保存的频率。默认设置是'epoch'
,因为批量保存会降低监控指标的可靠性。
options
:这是一个可选的tf.train.CheckpointOptions
对象,当save_weights_only
为True
时使用。当它为假时,使用tf.saved_model.SaveOptions
对象。这些对象为检查点或保存在分布式设置中有用的模型提供了额外的选项。
现在我们知道了什么是检查点,为什么它是必要的,以及在 TensorFlow 中定义检查点所需的参数。但是实际上真正是怎么用的呢?为了回答这个问题,我们将建立一个模型来将 IMDB 电影评论分为正面和负面两类。
安装和导入所需的库
$pip install tensorflow tensorflow_datasets
import numpy as np
import tensorflow as tf
from tensorflow import keras
import tensorflow_datasets as tfds
tfds.disable_progress_bar()
在上面的代码片段中,我们首先通过命令行安装带有pip
的tensorflow
和tensorflow_datasets
,以防它们尚未安装在您的开发环境中。接下来,我们导入numpy
和tensorflow
。我们还从tensorflow
导入了keras
,因为我们将从那个特定的模块发出几个调用。最后,我们导入tensorflow_datasets
并禁用它的进度条。
创建可视化功能
import matplotlib.pyplot as plt
def plot_graphs(history, metric):
plt.plot(history.history[metric])
plt.plot(history.history['val_'+metric], '')
plt.xlabel("Epochs")
plt.ylabel(metric)
plt.legend([metric, 'val_'+metric])
接下来,我们导入matplotlib.pyplot
来创建一个函数,这将有助于以后的可视化。该函数接受一个历史对象和一个期望的度量,并根据历元数绘制该度量的训练和验证数据。还为 x 轴和 y 轴添加了标签。
加载 IMDB 评论数据集
dataset, info = tfds.load('imdb_reviews', with_info=True,
as_supervised=True)
train_dataset, test_dataset = dataset['train'], dataset['test']
print(info.description)
print(info.citation)
print(f"\n{train_dataset.element_spec}")
这里,我们以字符串和两个参数的形式传入数据集的名称,以确定返回信息的性质。with_info
是确定关于数据集的元数据是否被返回的布尔值,而as_supervised
是确定数据集是否被加载用于监督学习应用的布尔值。
接下来,我们通过将'train'
和'test'
作为数据集变量的键传入,将数据集分成训练和测试部分。最后,我们显示了从收集的信息中得到的描述和引用信息。我们还查看了train_dataset
变量的元素规范,以了解其组成元素的数据类型。我们可以看到它包含了两个分别具有tf.string
和tf.int64
数据类型的张量。
配置数据管道
print(info.splits)
{'test': <tfds.core.SplitInfo num_examples=25000>,
'train': <tfds.core.SplitInfo num_examples=25000>,
'unsupervised': <tfds.core.SplitInfo num_examples=50000>}
首先,我们使用 splits 属性来查看训练和测试数据的大小。这是每个 25000 英镑。
BUFFER_SIZE = 10000
BATCH_SIZE = 64
train_dataset = train_dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)
test_dataset = test_dataset.batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)
for review, label in train_dataset.take(1):
print('reviews: ', review.numpy()[:3])
print('\nlabels: ', label.numpy()[:3])
reviews: [b'This was a big disappointment for me. I think this is the worst Mastroianni-movie ever made. Cosmatos tries too hard to make this movie a masterpiece and that makes this movie a typical "art"-movie. I give 4/10 for this movie.'
b"This picture for me scores very highly as it is a hugely enjoyable ... These type of films often deserve a cult following:<br /><br />8/10."
b'Almost too well done... "John Carpenter\'s Vampires" was entertaining, ... "Vampires: Los Muertos" is almost too well done. (I give it 7 of 10)']
labels: [0 1 1]
在上面的代码片段中,我们设置了批处理大小和缓冲区大小。批次大小决定了一次处理的样本数量。它被传递到批处理方法中。另一方面,缓冲区大小作为参数传递给 shuffle 方法,它决定了该方法最初进行 shuffle 的范围。通过将其设置为 10000,该方法最初仅从 25000 个训练样本中的 10000 个样本中选择随机样本。
然后,它用剩余的 15000 个样本中的新样本替换所选样本。迭代地这样做,整个数据集被分成设定批量大小的批次。值得注意的是,shuffle 方法仅用于训练数据集,因为顺序在测试数据集中并不重要。
最后,我们通过从train_dataset
中取出一批并打印前 3 个评论和标签来显示数据集的一部分。
创建文本编码器
到目前为止,我们仍然有原始文本形式的输入数据,但它需要被编码成数字,以便模型对其进行训练。
VOCAB_SIZE = 1000
encoder = keras.layers.TextVectorization(
max_tokens=VOCAB_SIZE)
encoder.adapt(train_dataset.map(lambda text, label: text))
首先,我们声明一个词汇大小为1000
。这意味着我们只负责一千个单词的编码,而其他的都是未知单词。
然后,我们将这个整数传递给keras.layers.TextVectorization
方法的max_tokens
方法。然后,我们使编码器适应我们的训练数据。我们还使用了map
方法,通过添加一个lambda
函数来从train_dataset
中提取文本。
vocab = np.array(encoder.get_vocabulary())
vocab[:20]
array(['', '[UNK]', 'the', 'and', 'a', 'of', 'to', 'is', 'in', 'it', 'i',
'this', 'that', 'br', 'was', 'as', 'for', 'with', 'movie', 'but'],
dtype='<U14')
encoded_example = encoder(review)[:3].numpy()
print(encoded_example)
array([[ 11, 14, 4, ..., 0, 0, 0],
[ 11, 433, 16, ..., 0, 0, 0],
[210, 100, 74, ..., 1, 5, 302]])
这里,我们输出编码器词汇表中的 20 个元素。注意代表所有未知单词的'[UNK]'
字符串。我们还输出三个评论的编码。在TextVectorization
方法中保持默认设置不变的情况下,该过程不可逆。然而,由于我们正在构建一个分类器,这将不是一个问题。
构建模型
model = keras.Sequential([
encoder,
keras.layers.Embedding(
input_dim=len(encoder.get_vocabulary()),
output_dim=64,
),
keras.layers.Bidirectional(keras.layers.LSTM(64)),
keras.layers.Dense(64, activation='relu'),
keras.layers.Dense(1)
])
模型被定义为一个keras.Sequential
对象。第一层是我们刚刚构建的编码器,因此输入编码是首先发生的事情。接下来是嵌入层。这一层将单词索引序列转换成可训练向量序列。经过训练,意思相近的单词有相同的嵌入。
接下来,我们添加一个包裹在keras.layers.Bidirectional
层中的长短期记忆(LSTM)层。LSTM 通过迭代元素并将结果从一个时间步长传递到下一个时间步长来处理序列。然后,双向包装器通过 LSTM 层向前和向后传播输入,并连接输出。
当 LSTM 将序列转换为单个向量时,两个keras.Layers.Dense
层进行最终处理,将向量转换为单个 logit 作为分类输出。如果该值大于0
,则分类为正;否则,它是负的。
所有隐藏层被设置为只有 64 个神经元。如果能提高性能,这当然可以改变。
为了确保这一点,我们通过 model.predict 函数将文本解析为未经训练的模型,并打印结果,如下所示。
sample_text = ('The movie was cool. The animation and the graphics '
'were out of this world. I would recommend this movie.')
predictions = model.predict(np.array([sample_text]))
print("Positive" if predictions[0]>0 else "Negative")
Negative
正如所料,该模型错误地对评论进行了分类。
编译模型
我们已经走了很长一段路,但看到检查点在实际问题中的使用确实很重要,这样它的实用性才能得到认可。接下来,我们编译模型并定义检查点和提前停止回调。
model.compile(loss=keras.losses.BinaryCrossentropy(from_logits=True),
optimizer=keras.optimizers.Adam(1e-4),
metrics=['accuracy'])
checkpoint = keras.callbacks.ModelCheckpoint("best_model",
save_best_only=True)
stop_early = keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)
在上面的代码片段中,我们编译了一个带有BinaryCrossEntropy
损失的模型,一个学习率设置为1e-4
和'accuracy'
作为度量的 Adam 优化器。
接下来,通过实例化一个keras.callbacks.ModelCheckpoint
对象并传入一个字符串'best_model'
作为要保存的文件路径以及将save_best_only
设置为True
来创建检查点方法。所有其他参数都保留先前声明的默认值。
接下来,我们定义早期停止回调并解析参数,使其监控验证损失并具有一个耐心值5
epochs。这意味着当验证损失在5
时期后没有增加时,训练循环将停止。
训练模型
这里我们调用 fit 函数解析训练数据、验证数据、作为10
的历元数、验证步骤和回调。validation_steps
参数将我们验证的测试批次数量限制为30
。为了在整个测试集上运行确认循环,这可以被排除,但是这是一个昂贵的过程。回调参数通过将已定义的早期停止和检查点回调作为列表中的元素来实现。运行此单元会训练模型,并在每次获得新的最佳验证损失时保存它。
该训练的结果存储在历史变量中,并如下图所示可视化,重点关注准确度和损失。
history = model.fit(train_dataset,
validation_data=test_dataset,
epochs=10,
validation_steps=30,
callbacks=[stop_early, checkpoint],
)
Epoch 1/10
391/391 [==============================] - 94s 229ms/step - loss: 0.6162 - accuracy: 0.5985 - val_loss: 0.8097 - val_accuracy: 0.5661
INFO:tensorflow:Assets written to: best_model/assets
INFO:tensorflow:Assets written to: best_model/assets
Epoch 2/10
391/391 [==============================] - 88s 223ms/step - loss: 0.4062 - accuracy: 0.8184 - val_loss: 0.3691 - val_accuracy: 0.8339
INFO:tensorflow:Assets written to: best_model/assets
INFO:tensorflow:Assets written to: best_model/assets
Epoch 3/10
391/391 [==============================] - 89s 225ms/step - loss: 0.3470 - accuracy: 0.8510 - val_loss: 0.3729 - val_accuracy: 0.8547
Epoch 4/10
391/391 [==============================] - 88s 223ms/step - loss: 0.3334 - accuracy: 0.8568 - val_loss: 0.3491 - val_accuracy: 0.8380
INFO:tensorflow:Assets written to: best_model/assets
INFO:tensorflow:Assets written to: best_model/assets
Epoch 5/10
391/391 [==============================] - 89s 226ms/step - loss: 0.3245 - accuracy: 0.8619 - val_loss: 0.3371 - val_accuracy: 0.8479
Epoch 6/10
391/391 [==============================] - 90s 226ms/step - loss: 0.3180 - accuracy: 0.8645 - val_loss: 0.3372 - val_accuracy: 0.8526
Epoch 7/10
391/391 [==============================] - 90s 228ms/step - loss: 0.3174 - accuracy: 0.8658 - val_loss: 0.3275 - val_accuracy: 0.8604
INFO:tensorflow:Assets written to: best_model/assets
INFO:tensorflow:Assets written to: best_model/assets
Epoch 8/10
391/391 [==============================] - 90s 227ms/step - loss: 0.3120 - accuracy: 0.8664 - val_loss: 0.3359 - val_accuracy: 0.8609
Epoch 9/10
391/391 [==============================] - 89s 225ms/step - loss: 0.3111 - accuracy: 0.8681 - val_loss: 0.3378 - val_accuracy: 0.8552
Epoch 10/10
391/391 [==============================] - 90s 226ms/step - loss: 0.3077 - accuracy: 0.8698 - val_loss: 0.3285 - val_accuracy: 0.8562
plt.figure(figsize=(16, 6))
plt.subplot(1, 2, 1)
plot_graphs(history, 'accuracy')
plt.subplot(1, 2, 2)
plot_graphs(history, 'loss')
加载最佳模型
loaded_model = keras.models.load_model('best_model')
sample_text = ('The movie was nice. The animation and the graphics '
'were out of this world. I would recommend this movie.')
predictions = loaded_model.predict(np.array([sample_text]))
print("Positive" if predictions[0][0]>0 else "Negative")
Positive
为了加载保存的最佳模型,我们只需解析到keras.models.load_model
方法的文件路径。接下来,我们通过将一个样本文本解析到model.predict
方法来进行预测。我们得到了与我们的解释相符的肯定结果。
继续用检查站训练
为了继续用检查点训练一个加载的模型,我们简单地重新运行model.fit
函数,回调仍然被解析。但是这会覆盖当前保存的最佳模型,因此如果不希望这样,请确保更改检查点文件路径。
loaded_model = keras.models.load_model('best_model')
new_history = loaded_model.fit(train_dataset, epochs=20,
validation_data=test_dataset,
validation_steps=30,
callbacks=[stop_early, checkpoint],
)
结论
在本教程中,您已经学习了什么是检查点,为什么它很重要,情感分析形式的自然语言处理的基础,以及如何在实际场景中实现检查点。我希望你觉得这很有用。
参考
机器学习和人工智能的 CI/CD
原文:https://blog.paperspace.com/ci-cd-for-machine-learning-ai/
开发现代 web 应用程序的生态系统非常丰富。有数不清的工具可以将现代 web 应用交付到生产环境中,监控其性能,并进行实时部署。这些工具非常重要,没有它们,现代 web 应用程序开发几乎是不可能的。
相比之下,现代人工智能和人工智能还没有相同的生态系统。这是有道理的,原因有很多:最佳实践还没有出现(即,还没有 ML 的灯栈),工具正在快速变化,现代深度学习在大计划中只存在了一眨眼的时间。
德沃普斯·➜·姆罗普斯
围绕 ML 工具和构建生产管道的问题是我们在 Paperspace 试图解决的关键问题之一。我们目前的大部分工作都被构建到 Gradient 中——这是我们为 ML/AI 开发人员快速开发现代深度学习应用程序提供的工具。
我们认为普及和高效人工智能的最大障碍之一是基础设施和工具问题(假设这些智能系统的硬性要求是兼容性、确定性和可再现性)。
虽然容器编排工具(如 Kubernetes、Mesos 等)是现代 ML 的重要组成部分,但它们只是深度学习的真正 CI/CD 系统的一小部分。此外,传统的 CI/CD 和类似的 ML/AI 系统具有不同的参数、约束和目标。
我们花了很多时间思考这些系统,探索前景,并与学术界、初创公司和大型企业的开发人员合作,以确定跨越问题领域并可以通过现代机器学习平台解决的一般问题。
一般来说,这些管道看起来非常相似。他们接受数据,建立一个模型,然后将这个模型部署到全世界。然而,这些细节非常有趣,值得深入探究。
梯度是我们对机器学习和人工智能系统的现代 CI/CD 的理解。作为一个平台,它被设计成采用一组基本的构建模块(原语),这些模块可以组成更大更复杂的系统。
可再现的管道必须由确定性的和不可变的部分组成。这些包括 git 散列、docker 散列等。强大的散列是渐变核心的关键设计模式之一。作业运行,甚至机器硬件(主机、加速器等)都被分配了不可变的标签,这些标签被设计成有助于确定性和可再现过程的更大项目。
这篇文章不是关于梯度本身,而是关于这些类型的系统如何从根本上不同于开发人员工具箱中的现有工具的想法的集合。
数据管道的未来
在 Paperspace,我们都相信我们正处于机器智能的黄金时代,但也承认这个相对新兴的领域在许多方面仍在追赶。仅举一个例子,容器化(使用 Docker 或类似技术)推动了现代 web 基础设施的发展,这在 web 早期是不可想象的。相比之下,机器学习中的容器化仍然相对较新,大量的生产工作是在笔记本电脑、裸机服务器和手动配置的环境中完成的。
许多现代 ML 仍然是在功能强大的台式电脑上完成的(那种坐在你的桌下温暖你双脚的东西)。Lambda Labs 和 Boxx 等公司提供包括快速处理器和大型 GPU 的大型设备。我们认为这是当今存在的一种怪异(但可以理解)的反模式,因为并不真正存在一种负担得起的、强大的云计算选项。
拥有大量深度学习和人工智能专业知识的大公司一直在大力投资软件平台,帮助 ML 加快其余开发过程。让人工智能投入运营是许多公司的雄心,那些成功做到这一点的公司将拥有长期的竞争优势。
为了给出这些端到端的 ML 平台的几个例子,优步公开讨论了他们的版本米开朗基罗。脸书有 FBLearner,谷歌有 TFX,AirBnb 有 BigHead。认识到向更多公司开放这些类型的工具的力量,这些公司可能不像谷歌、FB 和优步那样拥有相同的内部专业知识,像 DataBricks 这样的公司已经引入了像 MLFlow 这样的平台。
存在开源替代方案,如 Polyaxon 和 KubeFlow,但在大多数情况下,公司只能将不同的系统拼凑在一起,或者(更糟糕的是)尝试将旧工具重新用于这些现代数据流。
传统 CI/CD 工作流程
持续集成/持续部署(CI/CD)描述了一组应用程序开发管道的最佳实践。它们主要由 devops 团队实现,以使软件开发人员能够快速可靠地将更新引入生产应用程序。
现代 CI/CD 管道的一些核心优势包括:
- 可靠性
- 再现性
- 速度
- 安全
- 版本控制
对传统 web 应用程序的 CI/CD 工具的快速调查产生了许多您可能使用过或听说过的工具:
在其核心,这些工具试图形式化一个众所周知的工作流程:构建 ➡ 测试 ➡ 部署。例如,我们在 Paperspace 大量使用 Node.js 和 Golang,并投入巨资构建基础设施,使我们能够快速将新功能推向生产。
CI/CD 经常被混为一谈,但实际上它们描述了两个独立(但相关)的概念。持续集成(CI)主要关注的是在代码被推送时进行测试——也就是确保使用单元测试自动测试新的应用程序特性。相比之下,持续部署(CD)描述了测试代码的实际发布/交付。例如,一个 CD 系统可以描述不同的特性/发布分支是如何部署的,或者甚至新的特性是如何有选择地向新用户推出的(例如,在 20%的客户基础上测试特性 A)。
然而,重要的是,这两个概念在很大程度上一直没有出现在围绕现代机器学习和深度学习管道的对话中。
ML/AI 的 CI/CD
看看一个用于机器学习的理想 CI/CD 系统,我们会立即看到许多关键差异。在 Paperspace,我们相信这些差异足以保证全新的工作流程、开发范例和工具。
1.数据
可以说,传统 web 应用程序和 ML 管道之间最重要的区别在于,系统的主要输入不仅仅是代码。相反,有两个(同等重要的)入站组件:代码和数据。
数据编排、清理、版本控制等是一个完整的领域,只是在最近的历史中才成为许多公司的一等公民。过去 10 年的“大数据”转变,以及数据科学家作为公司主要操作员的出现,说明了这一巨大转变。
不幸的是,对于真正的、版本化的、可复制的数据管道,我们仍处于非常早期的阶段。如今在大规模生产系统中广泛使用的强大的 ETL(提取、转换、加载)工具并不缺乏,如 Kafka、Spark、Cassandra、Hadoop 等。为了更好地理解这些数据,现代 ML 引入了额外的约束和更高的标准,毫不夸张地说,我们正处于“再现性危机”,现代 ML 被(正确地)批评为难以理解的“黑箱”。
围绕数据治理/来源有许多大的(且未回答的)问题。如果无人驾驶汽车发生事故,监管机构理所当然地想知道数据(和模型)中的哪些假设导致了错误状况。数据可以以深度学习模型特别擅长提取的方式嵌入偏见,随着这一趋势的持续,无疑将会增加对数据如何收集、准备并最终作为输入提供给预测引擎的审查。
像 Quilt 这样的工具正在引领一种前瞻性的数据版本化工具,但是它们还没有被广泛采用。一些替代方案包括 dat 和 gitLFS ,但是这个空间仍然是新的,相对来说还没有被探索过。同样值得一提的是团队正在做的工作,例如 Pachyderm (我们是大粉丝)将数据视为 datascience/ML 工作流中的第一类原语。
2.催速剂
这几乎是显而易见的,不言而喻,但现代 webapps 是几乎所有东西都运行在传统 x86 架构(即常规 CPU)上这一事实的巨大受益者。对于某些应用程序来说,围绕 ARM 有一些真正有趣的工作,但在大多数情况下,硬件级别的这种同质性已经允许了一般的可移植性,这无疑是一般云发展的一个重要部分。
现代 ML/AI(尤其是深度学习)最近的进展很大程度上归功于它在 GPU 上工作得非常好。这与其说是历史上的异常,不如说是历史上的异常,但在过去的几年里,GPU 已经从其在图形处理中更为人所知的应用转变为一种通用的计算设备。
不太可能使用相同的设备来驱动视频游戏和深度学习应用程序,硬件供应商正在竞相开发可以加速深度学习训练和推理的定制硅。像 Graphcore(拥有“IPU”)、英特尔(拥有 Nervana 平台)和谷歌(拥有 TPU(张量处理单元)这样的公司都在竞相开发专为现代机器学习而构建的定制芯片。
这对现代的 ML/AI 工程师意味着什么?硬件环境正变得越来越复杂、异构和丰富。现代 ML 管道很可能会在数据摄取阶段使用 CPU,使用 NVIDIA GPU 来训练模型,并使用定制架构来部署到边缘(即安装在 iPhone 中的神经引擎)。
3.训练步骤
ML 管道中的训练步骤是通常花费最多计算时间的区域。这是传统数据科学和现代深度学习之间的另一大区别。你可以在一台便宜的笔记本电脑上进行世界级的数据科学研究。仅仅开始真正的深度学习就需要投资昂贵的 GPU(理想情况下是多个)或复杂的云工具。
根据模型的复杂性、要处理的数据量(及其特定维度、特征等)以及所使用的加速器类型,训练任务可能需要几个小时到几周的时间。
分布式训练(涉及多个不同的计算节点)最近取得了很大的进展(优步的 Horovod、TensorFlow Distributed 和 PyTorch 内置的一套库等工具),这增加了任务的复杂性。
4.改装/在线
因此,现在机器学习管道的圣杯-实时、在线、预测引擎,不仅向前部署,而且实际上不断更新。您可能听说过术语“改装”模型,这意味着新的数据进入模型,并且模型被更新以响应这些新的观察。
传统的 web 应用程序开发在很大程度上是“单向的”,即从代码->部署开始。通常有静态的“发布”,现代软件开发经常需要不同的“开发”、“准备”和“生产”环境。JenkinsX 等新技术支持为每个开发人员/分支进行不同的部署,但这并不能改变大多数部署过程从外部看起来非常静态的事实。当然,客户反馈、错误修复等会影响开发生命周期,但这通常是组织过程的责任(即“敏捷”开发方法)。
在不久的将来,我们预测每个公司都将采用某种形式的持续、实时深度学习引擎。也就是说,今天创建这样一个系统的障碍很多。即使是静态的 ML 管道也很难被拥有大量资源的复杂团队付诸实践。
然而,未来是光明的,因为这种自我更新、自我修改的系统越来越实用,我们可以开始讨论它们的可能性和局限性。除了近年来推出的基本机器学习算法和工具之外(并且正在由活跃的学术界不断推动),自更新 ML 管道的基础设施解决方案可以说是真正普及的机器智能的核心组件。
自我更新深度学习引擎的想法带来了一系列问题。在基本层面上,当分类或预测引擎的输出模型开始以不期望的方式偏离输入时,我们如何检测、量化并最终校正所谓的模型漂移。也许更紧迫的是,这些系统的边界是什么,我们如何开始有意义地约束它们。下面的帖子特别有启发性。
简而言之,脸书可以同时测量我们的一切,并控制我们消费的信息。当你既能感知又能行动时,你就在看一个人工智能问题。你可以开始为人类行为建立一个优化循环。一个 RL 循环。
— François Chollet (@fchollet) March 21, 2018
图像数据集中的类别不平衡及其对卷积神经网络的影响
原文:https://blog.paperspace.com/class-imbalance-in-image-datasets/
在深度学习中处理图像分类任务时,训练集中每个类别的图像数量往往不一样。这种场景被称为类不平衡,是在为深度学习任务获取数据时发现的一个极其常见的问题。
在本文中,我们将研究类不平衡如何以及是否影响模型性能,以及它对评估指标选择的影响。
为什么阶级不平衡?
在大多数情况下,数据集中的阶级不平衡通常是因为这是不可避免的。考虑这样一种情况,例如,人们想要管理一个音频数据集来检测汽车引擎中的机械故障。显然,将需要两个音频等级,一个用于处于完美工作状态的发动机,另一个用于处于缺陷状态的发动机。
不言而喻,与处于故障状态的发动机相比,处于完美工作状态的发动机将有更多的音频样本,并且收集的数据很可能也不会反映这种模式。这实质上建立了一种阶级不平衡,我们试图模拟的情况最终成为少数阶级。这种情况在垃圾邮件检测、欺诈交易检测等数据集中也很明显。
模型目标
当为一个特定的任务建立模型时,经常会有一个清晰的目的/目标需要存档。因此,为了简单起见,让我们考虑一个二元分类任务,即只有两个类。二元分类任务可以被定义/描述为真/假任务,并且通常被标记为 1(真/正)或 0(假/负)。该任务的一种流行形式是用于癌症诊断的医学图像分类,其中建立模型以将图像(CT 扫描)分类为癌性(真/正/1)或非癌性(假/负/0)。
因为我们知道模型从来不是 100%准确的,所以在这种性能关键任务中,目标是建立一个假阴性分类率非常低的模型。换句话说,你想要一个将癌变图像归类为非癌变图像的几率非常低的模型。想想看,我们不会因为我们的模型不准确而将癌症患者送回家,那可能会危及生命。
这种模型总是将边缘图像分类为癌症阳性,这很可能产生许多假阳性分类。然而,在实际的医生在场的情况下,通过模型筛选所有的阳性分类,假阳性分类将最终被剔除。这代表了更安全和更有效的权衡。
评估指标
在本节中,我们将了解分类任务中的一些评估指标,这将使我们更容易评估模型与手头目标的匹配程度。
准确(性)
准确性可能是最流行的分类指标,是正确分类的数据实例占分类实例总数的比例。它通常是一个指针,指示一个模型对其接受训练的数据实例的概括程度,但在大多数情况下,它并不能说明全部情况。它在 0-100 或 0-1(标准化)的范围内测量。
回忆
召回率也是以与准确度类似的尺度来衡量的,它是真的肯定分类与真的肯定和假的否定分类之和的比率。回忆并不关心模型与数据的吻合程度;相反,它更关心的是测量一个模型给出错误的否定分类的倾向。
回忆分数越接近 100,模型产生假阴性分类的可能性就越小。关于上一节中描述的目标是最小化假阴性的医学成像任务,需要寻找具有高召回率的模型。
精确
与前面提到的两个度量标准类似,精度是用 0-100 的标度来衡量的,它被定义为真阳性分类与真阳性和假阳性分类之和的比率。与回忆相反,精确度衡量的是模型给出错误肯定分类的倾向。模型的精度分数越接近 100,产生假阳性分类的可能性就越小。
F-1 分数
F-1 分数是测量精确度和召回率之间的平衡/折衷的度量。这是两个指标的调和平均值。我们可以在精确度-召回率的权衡对模型的成功不重要的情况下使用这个。例如,癌症诊断就不是这一指标的理想位置。
设置
`# article dependencies
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
import torchvision.datasets as Datasets
from torch.utils.data import Dataset, DataLoader
import numpy as np
import matplotlib.pyplot as plt
from tqdm.notebook import tqdm
from tqdm import tqdm as tqdm_regular
import seaborn as sns
from torchvision.utils import make_grid
import random`
`# setting up device
if torch.cuda.is_available():
device = torch.device('cuda:0')
print('Running on the GPU')
else:
device = torch.device('cpu')
print('Running on the CPU')`
我们需要导入这些包来运行本教程。
评估评估指标
为了理解这些评估指标,让我们使用一个真实的数据集。为此,我们将使用下面代码单元中加载的 CIFAR-10 数据集。
`# loading training data
training_set = Datasets.CIFAR10(root='./', download=True,
transform=transforms.ToTensor())
# loading validation data
validation_set = Datasets.CIFAR10(root='./', download=True, train=False,
transform=transforms.ToTensor())`
为了设置一个二元分类任务,我们只需要提取这个数据集中包含的 10 个类中的两个。下面的代码单元包含一个提取猫和狗的图像的函数。然后在训练图像中产生有利于猫的 4:1 不平衡(80%猫,20%狗)。然而,在验证图像中不存在不平衡,因为存在相等数量的图像实例。
`def extract_images(dataset):
cats = []
dogs = []
for idx in tqdm_regular(range(len(dataset))):
if dataset.targets[idx]==3:
cats.append((dataset.data[idx], 0))
elif dataset.targets[idx]==5:
dogs.append((dataset.data[idx], 1))
else:
pass
return cats, dogs
# extracting training images
cats, dogs = extract_images(training_set)
# creating training data with a 80:20 imbalance in favour of cats
training_images = cats[:4800] + dogs[:1200]
random.shuffle(training_images)
# extracting validation images
cats, dogs = extract_images(validation_set)
# creating validation data
validation_images = cats + dogs
random.shuffle(validation_images)`
建造探测器
本节设置了一个假设场景,需要我们稍微配合一下。想象一个只有猫和狗的社区。这里的目标是创建一个深度学习模型,它将在猫收容所的无人安全系统中工作。流浪猫可以走向这个安全系统并被允许进入收容所,而流浪狗则被拒绝进入并被送回。
我们可以将这个模型称为狗检测器,因此狗被标记为 1(阳性),而猫被标记为 0(阴性),如上面的代码单元所示。请记住,在我们的数据集中,有 4:1 的不平衡有利于猫。这里的模型目标是创建一个假阴性率低的模型,因为在猫舍养狗是一个坏主意。因此,我们希望最大化召回率。
首先,我们需要定义一个 PyTorch 数据集类,这样我们就可以从上面定义的对象中创建 PyTorch 数据集。
`# defining dataset class
class CustomCatsvsDogs(Dataset):
def __init__(self, data, transforms=None):
self.data = data
self.transforms = transforms
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
image = self.data[idx][0]
label = torch.tensor(self.data[idx][1])
if self.transforms!=None:
image = self.transforms(image)
return(image, label)
# creating pytorch datasets
training_data = CustomCatsvsDogs(training_images, transforms=transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]))
validation_data = CustomCatsvsDogs(validation_images, transforms=transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]))`
训练模拟模型
准备好训练和验证集后,我们可以开始为我们的二元分类任务训练模型。如果准确性是唯一需要关注的指标,那么我们实际上根本不需要训练一个模型。我们需要做的只是简单地定义一个函数作为模拟模型,将训练集中的每个图像实例分类为 cat (0)。使用这个模拟模型,我们将在训练集上获得 80%的准确率,因为所有图像的 80%是猫图像。然而,当在验证集上使用这个模拟模型时,准确率下降到 50%,因为在验证集中只有 50%的图像是猫的。
`# creating a mock model
def mock_model(image_instance, batch_mode=False):
"""
This function serves as a mock model which classifies
all images as 0\. If batch_mode=True supply a list of image-label
pairs as parameter image_instance.
"""
if not batch_mode:
classification = 0
label = image_instance[1].item()
print(f'model classification = {classification}\ntrue label = {label}')
else:
# extracting true labels
labels = [x[1] for x in image_instance]
labels = np.array(labels)
# classifying all instances as class 0
classifications = [0]*(len(image_instance))
classifications = np.array(classifications)
# computing accuracy
accuracy = (sum(labels==classifications)/len(labels))
print('model accuracy:')
return round(accuracy, 3)
# testing model in batch mode
mock_model(training_data, batch_mode=True)
>>>> model accuracy:
0.8
# testing model in batch mode
mock_model(validation_data, batch_mode=True)
>>>> model accuracy:
0.5`
因此,我们最终得到了一个在训练集上表现出色的模型,但在未经训练的数据(验证数据)上表现平平。在相同的情况下,尽管我们的模拟模型在训练集上有 80%的准确性,但它在训练集和验证集上的召回率和精确度都为零,因为没有真正的正分类。事实上,根本没有积极的分类(标签 1)。由于我们已经将模型目标定义为构建一个能够最大化召回率的模型,因此该模型不符合我们的模型目标。
训练卷积神经网络
**如果我们决定训练一个实际的深度学习模型呢?当然,它能够学习两类图像之间的显著特征,并最终具有辨别力?让我们通过构建一个自定义的ConvNet
来做到这一点,如下面的代码单元格中所定义的。
class ConvNet(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 8, 3, padding=1)
self.batchnorm1 = nn.BatchNorm2d(8)
self.conv2 = nn.Conv2d(8, 8, 3, padding=1)
self.batchnorm2 = nn.BatchNorm2d(8)
self.pool2 = nn.MaxPool2d(2)
self.conv3 = nn.Conv2d(8, 32, 3, padding=1)
self.batchnorm3 = nn.BatchNorm2d(32)
self.conv4 = nn.Conv2d(32, 32, 3, padding=1)
self.batchnorm4 = nn.BatchNorm2d(32)
self.pool4 = nn.MaxPool2d(2)
self.conv5 = nn.Conv2d(32, 128, 3, padding=1)
self.batchnorm5 = nn.BatchNorm2d(128)
self.conv6 = nn.Conv2d(128, 128, 3, padding=1)
self.batchnorm6 = nn.BatchNorm2d(128)
self.pool6 = nn.MaxPool2d(2)
self.conv7 = nn.Conv2d(128, 2, 1)
self.pool7 = nn.AvgPool2d(3)
def forward(self, x):
#-------------
# INPUT
#-------------
x = x.view(-1, 3, 32, 32)
#-------------
# LAYER 1
#-------------
output_1 = self.conv1(x)
output_1 = F.relu(output_1)
output_1 = self.batchnorm1(output_1)
#-------------
# LAYER 2
#-------------
output_2 = self.conv2(output_1)
output_2 = F.relu(output_2)
output_2 = self.pool2(output_2)
output_2 = self.batchnorm2(output_2)
#-------------
# LAYER 3
#-------------
output_3 = self.conv3(output_2)
output_3 = F.relu(output_3)
output_3 = self.batchnorm3(output_3)
#-------------
# LAYER 4
#-------------
output_4 = self.conv4(output_3)
output_4 = F.relu(output_4)
output_4 = self.pool4(output_4)
output_4 = self.batchnorm4(output_4)
#-------------
# LAYER 5
#-------------
output_5 = self.conv5(output_4)
output_5 = F.relu(output_5)
output_5 = self.batchnorm5(output_5)
#-------------
# LAYER 6
#-------------
output_6 = self.conv6(output_5)
output_6 = F.relu(output_6)
output_6 = self.pool6(output_6)
output_6 = self.batchnorm6(output_6)
#--------------
# OUTPUT LAYER
#--------------
output_7 = self.conv7(output_6)
output_7 = self.pool7(output_7)
output_7 = output_7.view(-1, 2)
return F.softmax(output_7, dim=1)
现在,我们需要定义一个类,它将有助于在单个对象中整齐地包装训练和验证步骤。
class ConvolutionalNeuralNet():
def __init__(self, network):
self.network = network.to(device)
self.optimizer = torch.optim.Adam(self.network.parameters(), lr=1e-3)
def train(self, loss_function, epochs, batch_size,
training_set, validation_set):
# creating log
log_dict = {
'training_loss_per_batch': [],
'validation_loss_per_batch': [],
'training_accuracy_per_epoch': [],
'validation_accuracy_per_epoch': []
}
# defining weight initialization function
def init_weights(module):
if isinstance(module, nn.Conv2d):
torch.nn.init.xavier_uniform_(module.weight)
module.bias.data.fill_(0.01)
elif isinstance(module, nn.Linear):
torch.nn.init.xavier_uniform_(module.weight)
module.bias.data.fill_(0.01)
# defining accuracy function
def accuracy(network, dataloader):
network.eval()
total_correct = 0
total_instances = 0
for images, labels in tqdm(dataloader):
images, labels = images.to(device), labels.to(device)
predictions = torch.argmax(network(images), dim=1)
correct_predictions = sum(predictions==labels).item()
total_correct+=correct_predictions
total_instances+=len(images)
return round(total_correct/total_instances, 3)
# initializing network weights
self.network.apply(init_weights)
# creating dataloaders
train_loader = DataLoader(training_set, batch_size)
val_loader = DataLoader(validation_set, batch_size)
# setting convnet to training mode
self.network.train()
for epoch in range(epochs):
print(f'Epoch {epoch+1}/{epochs}')
train_losses = []
# training
print('training...')
for images, labels in tqdm(train_loader):
# sending data to device
images, labels = images.to(device), labels.to(device)
# resetting gradients
self.optimizer.zero_grad()
# making predictions
predictions = self.network(images)
# computing loss
loss = loss_function(predictions, labels)
log_dict['training_loss_per_batch'].append(loss.item())
train_losses.append(loss.item())
# computing gradients
loss.backward()
# updating weights
self.optimizer.step()
with torch.no_grad():
print('deriving training accuracy...')
# computing training accuracy
train_accuracy = accuracy(self.network, train_loader)
log_dict['training_accuracy_per_epoch'].append(train_accuracy)
# validation
print('validating...')
val_losses = []
# setting convnet to evaluation mode
self.network.eval()
with torch.no_grad():
for images, labels in tqdm(val_loader):
# sending data to device
images, labels = images.to(device), labels.to(device)
# making predictions
predictions = self.network(images)
# computing loss
val_loss = loss_function(predictions, labels)
log_dict['validation_loss_per_batch'].append(val_loss.item())
val_losses.append(val_loss.item())
# computing accuracy
print('deriving validation accuracy...')
val_accuracy = accuracy(self.network, val_loader)
log_dict['validation_accuracy_per_epoch'].append(val_accuracy)
train_losses = np.array(train_losses).mean()
val_losses = np.array(val_losses).mean()
print(f'training_loss: {round(train_losses, 4)} training_accuracy: '+
f'{train_accuracy} validation_loss: {round(val_losses, 4)} '+
f'validation_accuracy: {val_accuracy}\n')
return log_dict
def predict(self, x):
return self.network(x)
设置好一切后,我们现在可以将上面定义的ConvNet
实例化为上面定义的类的成员,然后使用其中定义的参数训练ConvNet
10 个时期。
# training model
model = ConvolutionalNeuralNet(ConvNet())
log_dict = model.train(nn.CrossEntropyLoss(), epochs=10, batch_size=64,
training_set=training_data, validation_set=validation_data)
从下面的结果来看,通过建立自己的模拟模型,卷积神经网络将做完全相同的事情,该模型将每个图像实例分类为 cat (0 ),因此训练精度为 80%,验证精度为 50%。正如我们在逻辑上寻找最容易的方法来最大限度地提高训练集的准确性/性能一样,深度学习/机器学习模型的优化过程具有完全相同的动机,因为它们倾向于遵循阻力最小的路径,在这种情况下预测所有图像为猫。
等级不平衡对模型目标的影响程度。
从上一节可以明显看出,阶级不平衡对于模型目标是否实现有着绝对巨大的影响。这可能会诱使人产生一种虚假的安全感,因为它可能会产生很高的训练准确性,这在我们的情况下是显而易见的,在我们的情况下,我们有 4:1 的不平衡。基于这一前提,当涉及到分类任务中的模型目标时,准确性并不总是能说明全部情况。
处理阶级不平衡
有许多方法可以处理图像数据集或任何数据集中的类别不平衡。在本节中,我们将了解其中的 3 种。
向下采样
下采样是指减少多数类中的实例数量以匹配少数类中的实例数量的过程。例如,在我们的训练数据中,我们有 4800 幅猫的图像和 1200 幅狗的图像。如果我们决定通过下采样来处理类不平衡,我们也需要将猫图像的数量减少到 1,200,这样我们在两个类之间有相等的分割。
这种方法可以显著减小数据集的大小,这在大多数情况下可能是不可取的。在我们的例子中,如果我们进行下采样,我们将从总共 6000 幅图像增加到 2400 幅图像。
上采样
上采样是指将少数类中的实例数量提高到与多数类中的实例数量相匹配的过程。这可以通过为少数类收集更多的数据实例来完成,或者当这不可能时,我们可以通过创建它们的修改版本来扩充现有的数据实例。
在我们的例子中,我们还需要将狗图像的数量增加到 4,800,这将使数据集的大小从最初的 6,000 个实例增加到 9,600 个实例。由于这个原因,上采样通常比下采样更受青睐,因为它具有增加数据集大小的理想效果,并且在某些情况下,它会引入在训练模型中建立冗余的变化。
利用类权重
当训练用于分类的模型时,该模型隐含地对所有类赋予相同的重要性/权重,并且在分类损失方面以相同的方式对待它们。然而,我们可以通过明确指定每个类对我们有多重要来明确地赋予一个类相对于另一个类更大的重要性。
我们指定的重要性被称为类权重,它可以在类不平衡的情况下对我们有利。我们可以将更多的重要性放在少数类上,从而迫使模型学习一种映射,该映射在一定程度上优先识别该特定类。这将是我们下一节的重点。
通过类别权重处理类别不平衡
在本节中,我们将尝试使用类权重来调整我们的模型的行为,以便它尽可能地符合我们的模型目标,即最大化召回率,即使我们有一个不平衡的数据集。为了监控我们的模型目标,我们需要修改前面一节中定义的卷积神经网络类,以便不仅计算准确性,还计算召回率和精确度。
`class ConvolutionalNeuralNet_2():
def __init__(self, network):
self.network = network.to(device)
self.optimizer = torch.optim.Adam(self.network.parameters(), lr=1e-3)
def train(self, loss_function, epochs, batch_size,
training_set, validation_set):
# creating log
log_dict = {
'training_loss_per_batch': [],
'validation_loss_per_batch': [],
'training_accuracy_per_epoch': [],
'training_recall_per_epoch': [],
'training_precision_per_epoch': [],
'validation_accuracy_per_epoch': [],
'validation_recall_per_epoch': [],
'validation_precision_per_epoch': []
}
# defining weight initialization function
def init_weights(module):
if isinstance(module, nn.Conv2d):
torch.nn.init.xavier_uniform_(module.weight)
module.bias.data.fill_(0.01)
elif isinstance(module, nn.Linear):
torch.nn.init.xavier_uniform_(module.weight)
module.bias.data.fill_(0.01)
# defining accuracy function
def accuracy(network, dataloader):
network.eval()
all_predictions = []
all_labels = []
# computing accuracy
total_correct = 0
total_instances = 0
for images, labels in tqdm(dataloader):
images, labels = images.to(device), labels.to(device)
all_labels.extend(labels)
predictions = torch.argmax(network(images), dim=1)
all_predictions.extend(predictions)
correct_predictions = sum(predictions==labels).item()
total_correct+=correct_predictions
total_instances+=len(images)
accuracy = round(total_correct/total_instances, 3)
# computing recall and precision
true_positives = 0
false_negatives = 0
false_positives = 0
for idx in range(len(all_predictions)):
if all_predictions[idx].item()==1 and all_labels[idx].item()==1:
true_positives+=1
elif all_predictions[idx].item()==0 and all_labels[idx].item()==1:
false_negatives+=1
elif all_predictions[idx].item()==1 and all_labels[idx].item()==0:
false_positives+=1
try:
recall = round(true_positives/(true_positives + false_negatives), 3)
except ZeroDivisionError:
recall = 0.0
try:
precision = round(true_positives/(true_positives + false_positives), 3)
except ZeroDivisionError:
precision = 0.0
return accuracy, recall, precision
# initializing network weights
self.network.apply(init_weights)
# creating dataloaders
train_loader = DataLoader(training_set, batch_size)
val_loader = DataLoader(validation_set, batch_size)
# setting convnet to training mode
self.network.train()
for epoch in range(epochs):
print(f'Epoch {epoch+1}/{epochs}')
train_losses = []
# training
print('training...')
for images, labels in tqdm(train_loader):
# sending data to device
images, labels = images.to(device), labels.to(device)
# resetting gradients
self.optimizer.zero_grad()
# making predictions
predictions = self.network(images)
# computing loss
loss = loss_function(predictions, labels)
log_dict['training_loss_per_batch'].append(loss.item())
train_losses.append(loss.item())
# computing gradients
loss.backward()
# updating weights
self.optimizer.step()
with torch.no_grad():
print('deriving training accuracy...')
# computing training accuracy
train_accuracy, train_recall, train_precision = accuracy(self.network, train_loader)
log_dict['training_accuracy_per_epoch'].append(train_accuracy)
log_dict['training_recall_per_epoch'].append(train_recall)
log_dict['training_precision_per_epoch'].append(train_precision)
# validation
print('validating...')
val_losses = []
# setting convnet to evaluation mode
self.network.eval()
with torch.no_grad():
for images, labels in tqdm(val_loader):
# sending data to device
images, labels = images.to(device), labels.to(device)
# making predictions
predictions = self.network(images)
# computing loss
val_loss = loss_function(predictions, labels)
log_dict['validation_loss_per_batch'].append(val_loss.item())
val_losses.append(val_loss.item())
# computing accuracy
print('deriving validation accuracy...')
val_accuracy, val_recall, val_precision = accuracy(self.network, val_loader)
log_dict['validation_accuracy_per_epoch'].append(val_accuracy)
log_dict['validation_recall_per_epoch'].append(val_recall)
log_dict['validation_precision_per_epoch'].append(val_precision)
train_losses = np.array(train_losses).mean()
val_losses = np.array(val_losses).mean()
print(f'training_loss: {round(train_losses, 4)} training_accuracy: '+
f'{train_accuracy} training_recall: {train_recall} training_precision: {train_precision} *~* validation_loss: {round(val_losses, 4)} '+
f'validation_accuracy: {val_accuracy} validation_recall: {val_recall} validation_precision: {val_precision}\n')
return log_dict
def predict(self, x):
return self.network(x)`
A modified version of the class defined previously.
类别权重进入我们决定用于模型训练的损失函数。在这种情况下,我们决定使用交叉熵损失函数,因此,类别权重将被定义为损失函数的参数,例如nn.CrossEntropyLoss(weight=torch.tensor((0.1, 0.9)))
。权重的形式为(类 0 的权重,类 1 的权重),它们的总和必须为 1.0。
另一个极端。
例如,以下面的代码块为例,类 0 的权重为 0,类 1 的权重为 1.0。这意味着我们指示模型不重视类 0,因此我们期望它只学习到类 1 的映射。因此,该模型应该具有 20%的准确度,因为它将仅预测少数类,并且具有 100%的召回率,因为将不存在假阴性分类。但是对于 50%的验证准确性,它对于我们的目标仍然表现不佳。
`# training model
model = ConvolutionalNeuralNet_2(ConvNet())
weight = torch.tensor((0, 1.0))
log_dict = model.train(nn.CrossEntropyLoss(weight=weight), epochs=10, batch_size=64,
training_set=training_data, validation_set=validation_data)`
**
A plot of validation metrics. Both validation accuracy and precision remained at 50%.**
更平衡的方法
就这样,我们从一个只将所有图像分类为猫的模型,发展到一个只将所有图像分类为狗的模型,只需要指定我们希望对每个类别赋予的重要性。按照同样的逻辑,我们现在知道,如果正类(1)仍然被给予更高的优先级,那么最好的模型位于两者之间。让我们试试不同的重量组合。
`# training model
model = ConvolutionalNeuralNet_2(ConvNet())
weight = torch.tensor((0.15, 0.85))
log_dict = model.train(nn.CrossEntropyLoss(weight=weight), epochs=10, batch_size=64,
training_set=training_data, validation_set=validation_data)`
从上面的图中可以明显看出,这种权重组合产生了一个更加平衡的模型。到第 10 个时期,观察到的验证准确度刚刚超过 65%,这意味着该模型不仅将所有图像分类为正面或负面,它现在实际上是有区别的。此外,在验证准确率为 65%的情况下,还观察到了大约 70%的召回率,这是目前为止我们观察到的最符合模型目标的度量组合。
细节存在于迭代中
就像机器学习中涉及超参数调整的任何事情一样,找到类权重的完美组合是一个需要大量迭代的复杂过程。当这样做时,就可以找到类权重的最佳组合。
但是请注意,使用类权重并不是解决所有类不平衡问题的灵丹妙药。即使我们有了它们的最佳组合,使用它们也可能得不到与模型目标相关的预期结果。这将需要使用一些其他的类不平衡处理方法。
它超越了阶级不平衡
有时,即使没有类别不平衡,默认情况下分类模型可能仍然不符合模型目标。为了说明这一点,我在猫和狗的平衡数据集(每个类中有 5000 个实例)上训练了我们的 convnet,并收集了如下所示的指标。
考虑第 5 个纪元,即使验证准确率超过 70%,验证召回率突然下降到 50%以下,这对于我们的模型目标来说是一个很大的危险信号,因为我们正在尝试最大化召回率。即使到了第 10 个纪元,该模型给出的假阳性分类也比假阴性多(它将更多的猫误认为狗的次数比它将狗误认为猫的次数多),这对于我们的模型目标来说仍然不令人印象深刻。在这种情况下,调整类权重将有助于尽可能多地挤出验证召回,即使数据集是平衡的。
最后的想法
在本文中,我们探讨了图像数据集中类别不平衡的概念。我们看了一下这个概念本身的定义,以及它为什么会在某些情况下出现。我们还探索了为分类任务定义明确目标的过程,着眼于更多的评估指标而不是准确性如何更好地为我们实现目标服务。最后,我们研究了如何解决阶级失衡,同时密切关注阶级权重。**
气候变化、中国和人工智能
在我列出的机器学习最重要的趋势中,气候变化和与中国的竞争是两件事。最近几个月,我有机会与这两个领域的关键人物交谈。关于中国,我与谷歌董事长埃里克·施米特、前美国国防部副部长 T2·罗伯特·O·沃克、中国最著名的人工智能评论员 T4·李开复和人工智能研究者兼作家佩德罗·多明戈斯进行了交谈。我与谷歌杰出科学家约翰·普拉特、宾夕法尼亚大学博士后大卫·罗尔尼克和 CMU 大学博士生普里亚·唐蒂就利用人工智能应对气候变化进行了交谈。如果你关心人类的发展方向,它们值得一听。
约翰·普拉特雄辩地解释了气候危机的巨大利害关系,以及我们作为一个物种要生存下去需要做的事情的顺序。
约翰·普拉特:
这些被寄予厚望的零碳技术突破可能会在 2040 年或 2050 年后出现,这是为世界带来繁荣的一件大事。我们真的想要一种无处不在、非常便宜的零碳能源。但我们现在有一个更紧迫的问题,就是开始减少现有化石燃料基础设施的碳排放。
世界平均气温比工业化前水平上升 2 摄氏度是被认为是危险的气候变化的一般阈值。
当大气温度达到比工业化前水平高 2 摄氏度时,会发生各种各样的事情。一个非常具体的事情是,预计所有珊瑚礁的 99%都会死亡。在 2.5 摄氏度,你开始得到粮食产量的影响,因为事情只是变得太热或太干燥或太潮湿的作物生长。
问题是,即使我们现在向右急转弯,我们仍然会上升 2 摄氏度,除非我们能在本世纪下半叶找到如何从大气中去除二氧化碳的方法。
如果我们让化石燃料发电持续到现有基础设施的寿命期,我们将向大气中再增加 6600 亿吨二氧化碳。这基本上足以让我们超过 1.5 度的上升,几乎达到 2 度。如果算上目前正在建设或计划中的项目,我们会走得更远。所以,我们现在必须停止建造化石燃料基础设施,今天,为了避免超过 2 摄氏度。
许多人希望我们能够在本世纪下半叶从空气中去除二氧化碳。但是,如果我们做不到这一点,如果这在经济上不可行,那么我们现在就必须停止。真的非常非常紧急。我们没有更多的时间闲聊或思考它。
我认为我们将避免气温上升 3 摄氏度带来的灾难性气候变化。但在 2 到 3 摄氏度之间,每 10 摄氏度就会有一场刀光剑影。
约翰是一个名为气候变化人工智能的组织的成员,该组织正在将机器学习应用于气候危机。David Rolnick 和 Priya Donti 是推动该组织的两个人。他们的网站 ClimateChange。AI 有一个协作平台,科学家和工程师可以通过这个平台进行交流。该网站还有一个链接,链接到该小组撰写的一篇论文,该论文概述了机器学习在气候问题上的应用方式。
大卫·罗尼克:
我们的目标是成为气候变化和人工智能工作的交流、合作和资源的纽带。该文件不仅回顾了所做的工作,还提出了一些建议。
PRIYA DONTI:
论文中出现了几个交叉主题。其中之一是材料发现,发现新材料,使我们能够从太阳捕获能量并将其转化为液体燃料,然后用于运输,或创造水泥的替代品,水泥是一种碳密集型材料,或创造更有效的二氧化碳吸附剂,这基本上是从空气中吸收二氧化碳的海绵。发现新的吸附剂将使我们更有效地进行碳捕获。
大卫·罗尼克:
当然,机器学习不会成为解决一切问题的灵丹妙药。然而,它是那些有影响力的技术的加速器。
我鼓励每个人在 ClimateChange 查看这个组织。AI 并参与其中。
与此同时,各国政府继续在谈论,但却没有采取什么行动来避免迫在眉睫的气候灾难。相反,他们关注的是更少的生存威胁,比如战争。弗拉基米尔·普京说过一句名言,谁在人工智能领域占据主导地位,谁就将统治世界,世界主要大国也是如此回应的。迄今为止,主要的竞争是在美国和中国之间。中国最著名的人工智能评论员李开复给了我他对比赛的评价。
KAI-FU LEE:
中国有许多独特的优势,其中最大的优势是巨大的数据量,然后是伟大的工程师、公司和企业家。
此外,政府采取了一种非常技术功利主义的方法,即让技术在进入监管之前进行尝试,而不是在技术投入使用之前解决问题。这些因素将推动中国前进。虽然美国确实有更深入的研究平台,但中国正在迅速赶上并培养一批非常聪明的人工智能工程师,他们可以说比拥有少量人工智能超级明星更重要。因为我们现在处于实施的时代,我们有一套非常不同的能力,使中国在一些领域遥遥领先,而在另一些领域却远远落后。但在可预见的未来,真正重要的是中国强大的元素。
谷歌前董事长埃里克·施密特(Eric Schmidt)和国防部副部长罗伯特·O·沃克(Eric Schmidt)分别与我谈到了那场竞争。埃里克和鲍勃是人工智能国家安全委员会的联合主席。
鲍勃工作:
很多人说中国有优势,因为它有更多的数据,但通过聚集所有民主国家并共同努力,我们觉得我们可以抵消这方面的任何问题。我们实际上已经和我们的盟友取得了良好的进展。到目前为止,我们已经与英国、欧盟、日本、加拿大和澳大利亚进行了会谈,我们已经证明,通过合作,我们可以解决任何数据差异。我们还打算与其他国家进行对话。
美国从来没有面对过国内生产总值大于自己 40%的战略竞争对手。苏联在冷战中勉强达到这个水平,即使你把二战中的日本和德国加在一起,也只有 40%左右。中国的购买力平价已经超过了美国。如果这种趋势持续下去,他们的绝对 GDP 将在 25 年左右超过美国。因此,我们从未面对过经济规模超过国内生产总值 40%的战略竞争对手。我们可能会面临一个经济规模比我们更大的竞争对手。
在两次世界大战之间的时期,每个人都知道机械化正在发生。每个人都有收音机,每个人都知道航空的进步。是竞争对手将他们放在一起,形成了一个他们称之为闪电战的作战概念,这给了他们巨大的优势。因此,国防部担心的是,中国人在开发一个作战概念时,能否在决策支持系统中采用机器学习,在通信中采用 5G 技术,能否以一种能给他们带来战场优势的方式将它们组合在一起?
这是一场我们可能会输的比赛。
埃里克·施密特:
我们并没有采取解耦对纠缠的立场。正如鲍勃喜欢说的那样,我们试图在这些选择中穿针引线。所以,我会鼓励你不要认为这是黑与白,我们对他们。在这些领域中,可能存在竞争与合作。例如,中国公民对我们大学的研究事业非常重要。
中国有一个 2030 年的国家计划。它有 2030 年的国家预算。它已经确定了不同领域的四个全国冠军。他们正在非常非常努力地在这一领域和其他一些战略领域培养国家能力。美国的回答是什么?我们没有提出一个答案,因为现在还不是时候。
畅销书《大师算法》的作者佩德罗·多明戈斯对这场竞争有着更悲观的看法。
佩德罗·多明戈斯:
冷战是美苏之间的技术优势竞赛,美国赢了。这场新的比赛将会非常不同。谁会赢一点也不清楚。首先是经济竞赛,然后是军事竞赛。DARPA 是世界上最大的人工智能资助者,至少到目前为止,因为人工智能的潜在军事影响是完全显而易见的。
但是竞争也是公司之间的竞争。
人工智能是终极两用技术。军用和民用 AI 真的没有区别。就像你可以把一个工厂工人,放一支步枪在他手里,他现在是一名士兵。我可以拿一个房子机器人,把一支步枪放在它的手里,它现在是一名士兵。
到目前为止,中国在某种程度上比美国玩得更好,因为政府和企业互相帮助。你知道,一个令人不寒而栗的想法是,也许民主比威权更好,但威权会赢,因为 AI 是独裁者的终极工具。它可以监视每个人,永远不会疲倦,永远不会怀疑。
中国对人工智能的使用非常可怕。
这篇文章改编自播客 Eye on AI 的一集。在这里找到完整的一集,或者听下面的。
云可靠性和性能改进💪
原文:https://blog.paperspace.com/cloud-reliability-performance-improvements/
作为 GPU 云,我们已经走过了漫长的道路,支持超过 10 万用户,并继续快速扩展。有时,我们的增长给我们的系统带来了负担,有时是以我们无法预料的方式。在某些情况下,这种负载会对我们产品的整体性能产生影响,我们对此非常重视。
在过去的几个版本中,我们一直在进行更改,以提高事件调度程序的成功率和速度,事件调度程序是处理在 web 界面和 CLI 中触发的所有操作的核心机制。更广泛地说,我们已经做出了一致的努力,在堆栈上上下下引入更多的弹性和稳定性。这包括更严密的监控、更智能的警报、对事件和运行状况的更好洞察,以及在出现问题时投资内部工具来解决问题。目标是增加正常运行时间,更加主动,并对出现的任何情况做出更快的响应。
这种努力已经开始得到回报:我们看到我们的调度程序性能提高了 2 倍,健康警报的数量急剧下降。我们的目标是加倍努力,并在我们继续扩展时解决任何遗留问题。最重要的是,我们相信我们现有的架构是支持我们下一阶段发展的坚实基础。
谢谢你对我们的容忍。我们非常感谢我们平台的每一位用户。
💗PS 工程团队
我们如何为脸书、捷豹和谷歌等公司构建云运动图形管道
有一天,我们在浏览我们的 Instagram feed 时,看到了 Offset 的惊人视觉效果。我们试图了解他们是否想尝试 Paperspace,结果发现他们已经在使用我们的服务了...来自新加坡!(众所周知,我们目前只有美国和欧洲的数据中心。)
向我们介绍一下你自己。
我是新加坡的创意总监 Clement Chia。五年来,我一直在经营一家名为偏移的领先运动设计工作室。最近,我们建立了 Offeo -一个基于云的视频制作工具,使全球的中小型企业能够轻松制作引人注目的营销视频。
Offset & Offeo 目前有 30 名员工,总部设在新加坡,新加坡也是脸书和谷歌等许多科技公司的总部所在地。我们是一个充满激情的创意团队,痴迷于视频营销,与我们的客户合作,制定战略,创造有吸引力的销售视频。多年来,该团队有幸与脸书、路虎捷豹、花旗银行、谷歌、麦当劳等客户合作。
https://player.vimeo.com/video/152263633
你是怎么开始的?
我从《变形金刚》这样的大片中受到 VFX 的启发,并投身于这个行业,以了解更多自己。我的背景实际上是计算机科学。虽然这是一个完全不同的行业,但它帮助我很快地分析和学习。在广告业工作了几年后,我去了洛杉矶的 Gnomon 进一步提高我的技能,同时与华纳兄弟合作各种音乐视频项目。我回到新加坡,专注于运动设计,并开始与我的联合创始人李尚义胶印。
你能告诉我们关于奥菲欧的事吗?
多年来,我们非常荣幸地与许多知名品牌合作令人兴奋的项目。但是看到我们作品的初创公司和小公司来找我们,却对追求一个好的营销视频所需的高额预算感到失望。奥菲欧是我们的答案。我们把我们最了解的东西整合成一个在线平台,企业可以选择合适的模板设计,然后根据自己的营销目标进行定制。
https://player.vimeo.com/video/226398581
How did you hear about Paperspace?
由于动画视频渲染的性质,我们总是需要具有强大 GPU 的计算机。在构建 OFFEO 时,我们有了在内部构建服务器的想法。我们翻修了办公室,设计了一个服务器机房,甚至开始扩建一些机架。但在我们走得太远之前,我们感谢一位后端开发专家朋友,他建议我们尝试一下 Paperspace。当我们将其与其他云提供商进行比较时,Paperspace 就没有那么令人生畏了。真正吸引我们注意的是入门和管理机器的简单性。就好像 Paperspace 确切地知道我们需要的基本功能,所以我们不必花半天时间去寻找它们。Paperspace 能够准确地提供我们需要的东西,最重要的是允许我们尽可能快地扩展。剩下的就是历史了。
Paperspace 如何简化您的工作流程?
作为一家新创公司的企业主,我们希望将大部分时间集中在产品开发上,而不是 IT 基础设施问题上。事实证明,Paperspace 的平台能够帮助我们做到这一点。有了 Paperspace,我们不再需要担心 IT 管理和可扩展性问题。该团队可以在一个屏幕上管理几十台计算机。所有这些都帮助我们变得更有生产力和效率。现在我们可以专注于真正最重要的事情——改进我们的在线视频制作平台。
对于您所在领域的潜在 Paperspace 用户,您有什么有用的提示或技巧吗?
看看 Paperspace 在团队管理页面的 UI 就知道了。你一定会喜欢的!使用 Paperspace 的平台已经改变了我们的生活。将 Paperspace 视为长期合作伙伴,而不仅仅是另一家服务提供商。他们会对你的需求做出积极回应,并且总是认真对待我们的客户反馈。
Python 中使用遗传算法的聚类
原文:https://blog.paperspace.com/clustering-using-the-genetic-algorithm/
在监督机器学习中,训练数据已经被标记,这意味着每个数据实例都有其相应的输出。在无监督的机器学习中,数据没有标签。聚类是一个无监督的学习问题,任务是探索数据,为每个数据实例找到最佳标签。
本教程讨论如何使用遗传算法对数据进行聚类,从随机聚类开始,一直运行到找到最佳聚类。我们将首先简要地修改 K-means 聚类算法,指出它的弱点,这些弱点稍后将由遗传算法来解决。本教程中的代码示例是使用 PyGAD 库在 Python 中实现的。
本教程的大纲如下:
- 介绍
- k 均值聚类
- 使用遗传算法的聚类
- 准备人工聚类数据
- 到聚类中心的距离
- 适应度函数
- 使用遗传算法的进化
- 两个集群的完整 Python 代码
- 3 个集群的示例
- 结论
简介
根据训练数据是否有标签,有两种类型的机器学习:
- 监督学习
- 无监督学习
在监督学习问题中,模型使用一些描述数据的信息。该信息是数据实例的输出,因此模型知道(并学习)它接收的每个输入实例的预期输出应该是什么。这有助于模型评估其性能,并学习减少误差(或提高准确性)的方法。
对于分类问题,输出是每个样本的期望类别。对于 RGB 颜色分类器,输入和输出数据可能如下所示:
Input 1 : 255, 0, 0
Output 1 : Red
Input 2 : 0, 255, 0
Output 2 : Green
假设只有两个类:红色,和绿色。当模型知道预期输出时,它会在训练阶段调整自身(即其参数)以返回正确的输出。对于一个新的测试样本,该模型测量其与之前在两个类中看到的样本的相似性。
在无监督学习问题中,模型不知道数据的正确输出(输入)。聚类是一个无监督的学习问题,任务是找到每个数据实例的结果(即标签)。
聚类算法的输入只是如下的输入:
Input 1 : 255, 0, 0
Input 2 : 0, 255, 0
聚类之后,模型应该预测每个数据实例的标签:
Output 1: Red
Output 2: Green
存在一些聚类算法,如 K-means 算法(这是最流行的);分支和绑定;和最大似然估计。
为了达到本教程使用遗传算法进行聚类的目的,下一节将回顾 K-means 算法。
K-均值聚类
K-means 算法是一种流行的聚类算法。虽然它非常简单,但本节快速回顾了它是如何工作的,因为理解它对于使用遗传算法进行聚类是必不可少的。
K 均值算法的输入是:
- 数据样本。
- 簇的数量为\(K\)。
该算法的输出是一组\(K\)个聚类,其中每个聚类由一组数据样本组成。一个样本可以在两个或多个聚类之间重叠,也可以不重叠;这取决于所解决问题的性质。
下图显示了一个包含 3 个人工聚类的示例,以说明 K-means 算法是如何工作的。请注意,“人工”意味着数据不是真实的。对于这个例子,很明显\(K\)(最佳聚类数)的最佳值是 3。对于现实世界的问题,不同的\(K\)值产生不同的结果,选择最可行的一个。每个样本都有一种反映其分类的颜色。
每个聚类可以由包括其样本的集合来表示。例如,\(C_1={\{32,21,5,2,9\}}\)意味着 id 为\(32,21,5,2,\)和\(9\)的样本属于第一个聚类。
K-means 通过为 K 个聚类中的每一个选择初始中心来工作。注意,K-means 算法对这些初始中心非常敏感,并且通过改变初始中心可能给出不同的结果。在下图中,选定的中心是那些带有黑色边框的中心。
下一步是计算每个样本和 3 个中心之间的距离,并将每个样本分配给最近中心的聚类。目标是最小化所有样本与其中心之间的总距离。该距离可以是根据下式计算的欧几里德距离:
其中:
- \(F\)是代表每个样本的特征数量。
- \(C\)和\(P\)是两个样本,计算了它们之间的欧几里德距离。
下图显示了样本是如何聚类的。分配给不正确簇的样本具有与它们被错误放入的簇相同颜色的边缘。
基于当前的聚类,K-means 算法通过根据下式取每个聚类内所有样本的平均值来更新其\(K\)聚类的中心:
其中:
- \(n\)是聚类内的样本数。
- \(x_i\)是群集中的一个样本。
如果一个样本到多个聚类的距离相等,那么它可以被分配到这些聚类中的任何一个。
在计算出新的中心之后,聚类中心从它们当前的位置移动到有希望创建新的、更好的聚类的其他位置。注意,新的聚类中心不必是来自聚类的样本,并且可以是新的人工样本。
新的图表显示了集群的一些潜在的新中心。使用这些新的中心,只有 2 个样本在错误的聚类中。同样,新的中心是通过对聚类中的所有样本进行平均来计算的。
再次更新中心后,下图显示所有样本都被正确聚类。
K-means 算法继续生成新聚类的中心,并将样本分配给距离最小的聚类,直到它发现新中心与当前中心完全相同。在这种情况下,K-means 算法停止。您还可以设置它,使算法在一定次数的迭代后停止,即使中心仍在变化。
在回顾了 K-means 算法的工作原理之后,让我们讨论一下它的一些局限性,以突出使用进化算法(如遗传算法)进行聚类的好处。
根据本文中的,使用 K-means 聚类有两个缺点:
- K-means 算法的第一个限制是它对初始中心非常敏感。改变初始中心会强烈影响结果。
- K-means 算法可能会陷入局部最优,并且可能无法找到全局最优解。
K-means 能够陷入局部最优的一个原因是,它停止在第一个稳定中心,然后没有办法改进它们以达到更好的解决方案。K-means 没有办法衡量解的质量,它只停留在第一个可用的解上。
K-means 算法的这些局限性通过使用遗传算法来解决。下一节将展示如何使用遗传算法进行聚类。
利用遗传算法进行聚类
遗传算法是一种优化算法,它使用一个以上的解来搜索给定问题的解。遗传算法不仅搜索解,而且通过在多个方向上对解进行一些随机(即盲目)改变来搜索全局最优解。
遗传算法按照以下步骤寻找最佳解决方案:
- 初始化一组解决方案。
- 计算群体中解的适应值。
- 选择最佳解(具有最高适应值)作为父代。
- 使用交叉和变异使选择的亲本交配。
- 创造一个新的群体。
- 重复步骤 2 到 5 若干代,或者直到满足某个条件。
有关遗传算法的更多信息,请查看以下资源:
对于聚类问题,遗传算法优于 K-means,因为它对初始中心不太敏感。遗传算法并不止步于第一个解,而是对其进行进化,以找到全局最优解。
为了用遗传算法解决问题,你必须考虑这三件事:
- 将问题公式化为遗传算法所期望的形式,其中每个解被表示为一个染色体。
- 编码问题(二进制或十进制)。
- 构建一个适应度函数来衡量解决方案的适应度(即质量)。
对于聚类问题,问题的解是聚类的中心坐标。所有的中心必须表示为一条染色体,这基本上是一个一维向量。
假设上一个问题中的每个样本只有两个要素,分别代表其\(X\)和\(Y\)位置。还假设 3 个集群的位置如下:
那么解应该表示为如下给出的一维向量,其中前两个元素是第一个聚类中心的\(X\)和\(Y\)位置,后两个元素表示第二个聚类的中心,依此类推。
其中:
- \(N_c\)是集群的数量。
- \(N_k\)是聚类\(k\)内的样本数。
- \(F\)是代表样本的特征数,在我们的例子中是 2。
- \(C_k\)是群集\(k\)的中心。
请注意适应度函数是如何使用距离总和的倒数来计算适应度的。原因是直接使用距离之和使得适应度函数成为最小化函数,这与遗传算法相矛盾。
在讨论了使用遗传算法进行聚类的工作原理之后,本教程的其余部分将使用 Python 来完成以下工作:
- 准备人工数据。
- 使用 Python 库 PyGAD 对数据进行聚类。
准备人工聚类数据
本节准备用于测试遗传算法聚类的人工数据。选择的数据在不同的集群之间有一个余量,以使事情在开始时更容易,并确保一切正常工作。
在整个教程中,生成了 2 组人工数据。第一组有 2 个簇,另一组有 3 个簇。数据样本是随机生成的,其中每个样本只有 2 个特征。现在让我们只关注两个集群的数据。
下一个代码块使用numpy.random
模块随机生成数据并绘制出来。这两个特征中的每一个都在一定范围内被缩放,以确保这两个聚类被适当地分开。以下是用于两个聚类中两个要素的起始值和结束值:
- 分类 1 特征 1: (0,5)
- 群组 1 功能 2: (2,6)
- 群组 2 功能 1: (10,15)
- 群组 2 功能 2: (8,12)
每个聚类内的样本数量被设置为 10。根据您的喜好,您可以调整每个特征的开始值和结束值,以及每个聚类中的样本数。
import numpy
import matplotlib.pyplot
cluster1_num_samples = 10
cluster1_x1_start = 0
cluster1_x1_end = 5
cluster1_x2_start = 2
cluster1_x2_end = 6
cluster1_x1 = numpy.random.random(size=(cluster1_num_samples))
cluster1_x1 = cluster1_x1 * (cluster1_x1_end - cluster1_x1_start) + cluster1_x1_start
cluster1_x2 = numpy.random.random(size=(cluster1_num_samples))
cluster1_x2 = cluster1_x2 * (cluster1_x2_end - cluster1_x2_start) + cluster1_x2_start
cluster2_num_samples = 10
cluster2_x1_start = 10
cluster2_x1_end = 15
cluster2_x2_start = 8
cluster2_x2_end = 12
cluster2_x1 = numpy.random.random(size=(cluster2_num_samples))
cluster2_x1 = cluster2_x1 * (cluster2_x1_end - cluster2_x1_start) + cluster2_x1_start
cluster2_x2 = numpy.random.random(size=(cluster2_num_samples))
cluster2_x2 = cluster2_x2 * (cluster2_x2_end - cluster2_x2_start) + cluster2_x2_start
matplotlib.pyplot.scatter(cluster1_x1, cluster1_x2)
matplotlib.pyplot.scatter(cluster2_x1, cluster2_x2)
matplotlib.pyplot.show()
因为数据是随机生成的,所以每次代码运行时都会返回不同的结果。如果您想保留数据,请将其另存为。npy 文件如下:
numpy.save("p1_cluster1_x1.npy", cluster1_x1)
numpy.save("p1_cluster1_x2.npy", cluster1_x2)
numpy.save("p1_cluster2_x1.npy", cluster2_x1)
numpy.save("p1_cluster2_x2.npy", cluster2_x2)
下图显示了两个聚类中每个聚类的样本,其中同一聚类中的样本具有相同的颜色。
请注意,每个集群中的单个要素位于不同的阵列中。需要根据下面的代码将所有集群中的数据样本分组到单个数组中。数组c1
和c2
分别保存第一和第二聚类的样本。数组data
将c1
和c2
连接在一起,将两个集群的所有样本保存在一起。因为每个聚类有 10 个样本,其中每个样本有 2 个特征,所以data
数组的形状是(20, 2)
。
c1 = numpy.array([cluster1_x1, cluster1_x2]).T
c2 = numpy.array([cluster2_x1, cluster2_x2]).T
data = numpy.concatenate((c1, c2), axis=0)
因为我们希望将数据分成两个簇,所以创建了一个名为num_clusters
的变量来保存簇的数量。
num_clusters = 2
下一节将展示如何计算样本对之间的欧几里德距离。
计算欧几里德距离
下一个名为euclidean_distance()
的函数接受 2 个输入X
和Y
。其中一个输入可以是具有多个样本的二维数组T4,另一个输入应该是只有一个样本的一维数组T6。该函数计算并返回二维数组中的每个样本与一维数组中的单个样本之间的欧几里德距离。****
def euclidean_distance(X, Y):
return numpy.sqrt(numpy.sum(numpy.power(X - Y, 2), axis=1))
例如,下面的代码计算了data
数组中的数据样本和该数组中的第一个样本之间的距离。请注意,二维数组是在第一个还是第二个参数中传递并不重要。
d = euclidean_distance(data, data[0])
# d = euclidean_distance(data[0], data)
print(d)
euclidean_distance()
函数的结果是一个一维数组,其元素数量等于二维数组中的元素数量,在本例中为 20。因为这个数组中的第一个元素计算第一个样本与其自身之间的距离,所以结果是0
。
[ 0\. 0.42619051 2.443811 1.87889259 3.75043 1.5822869
3.96625121 3.06553115 0.86155518 0.28939665 13.96001895 12.06666769
12.76627205 10.57271874 12.13148125 12.13964431 14.75208149 12.60948923
12.44900076 13.18736698]
下一部分使用data
数组和euclidean_distance()
函数来计算每个聚类中心和所有样本之间的欧几里德距离,以对数据进行聚类。稍后,遗传算法将使用计算的距离来进化聚类中心。
到聚类中心的距离
要使用遗传算法进行聚类,请记住适应度函数是根据下一个等式计算的。
为了计算适合度,遵循以下步骤:
- 遍历聚类中心,计算所有样本和所有聚类中心之间的欧几里德距离。
- 将每个样本分配到欧氏距离最小的聚类中。
- 另一个循环遍历聚类,计算每个聚类中所有距离的总和。如果一个分类有 0 个样本,那么它的总距离是 0。
- 对所有聚类中的距离求和。
- 计算距离总和的倒数。这就是健身值。
所有这些步骤都应用于下面给出的cluster_data()
函数。它接受以下两个参数:
solution
:群体的一种解决方案,计算其中心与数据样本之间的距离。solution_idx
:群体内部解的指数。
在函数开始时,定义了以下列表:
cluster_centers = []
:大小为(C, f)
的列表,其中C
是聚类的数量,f
是代表每个样本的特征的数量。all_clusters_dists = []
:大小为(C, N)
的列表,其中C
是聚类的数量,N
是数据样本的数量。它保存每个聚类中心和所有数据样本之间的距离。clusters = []
:具有C
个元素的列表,其中每个元素保存聚类内样本的索引。clusters_sum_dist = []
:具有C
个元素的列表,其中每个元素代表样本与聚类的距离之和。
根据我们的示例,这些数组的大小为:
cluster_centers = []
:(2, 2)
all_clusters_dists = []
:(2, 20)
clusters = []
:(2)
clusters_sum_dist = []
:(2)
第一个循环从\(0\)开始到集群的数量,并将集群的索引保存在clust_idx
变量中。对于每个聚类,当前的聚类中心从solution
参数返回,并附加到cluster_centers
列表中。
假设solution
参数中的初始聚类中心如下:
[5.5, 8, 8, 3.5]
结果,下面给出了cluster_centers
,其中第一个聚类的中心是[5.5, 8]
,第二个聚类的中心是[8, 3.5]
。
[[5.5, 8],
[8 , 3.5]]
下图显示了数据样本(蓝色和绿色)和聚类中心(橙色和红色)的位置。
import numpy
def cluster_data(solution, solution_idx):
global num_cluster, data
feature_vector_length = data.shape[1]
cluster_centers = []
all_clusters_dists = []
clusters = []
clusters_sum_dist = []
for clust_idx in range(num_clusters):
cluster_centers.append(solution[feature_vector_length*clust_idx:feature_vector_length*(clust_idx+1)])
cluster_center_dists = euclidean_distance(data, cluster_centers[clust_idx])
all_clusters_dists.append(numpy.array(cluster_center_dists))
cluster_centers = numpy.array(cluster_centers)
all_clusters_dists = numpy.array(all_clusters_dists)
cluster_indices = numpy.argmin(all_clusters_dists, axis=0)
for clust_idx in range(num_clusters):
clusters.append(numpy.where(cluster_indices == clust_idx)[0])
if len(clusters[clust_idx]) == 0:
clusters_sum_dist.append(0)
else:
clusters_sum_dist.append(numpy.sum(all_clusters_dists[clust_idx, clusters[clust_idx]]))
clusters_sum_dist = numpy.array(clusters_sum_dist)
return cluster_centers, all_clusters_dists, clusters, clusters_sum_dist
使用euclidean_distance()
函数计算聚类中心和所有数据样本之间的欧几里德距离,该函数返回一个带有距离的一维数组。返回的一维数组被附加到列表all_clusters_dists
中。
根据使用的中心,下面给出了all_clusters_dists
列表。对于第一个数据样本,它到第一个聚类中心的距离是6.10987275
,而对于第二个聚类中心的距离是7.58117358
。这意味着第一个样本比第二个样本更接近第一个聚类。因此,应该将其分配给第一个集群。
相反,第五个样本到第一组的距离为5.87318124
,大于到第二组的距离4.51325553
。结果,最后一个样本被分配给第二个聚类。
[
[6.10987275, 5.85488677, 3.92811163, 4.26972678, 5.87318124,
6.39720168, 5.28119451, 6.24298182, 6.07007001, 6.39430533,
7.90203904, 6.25946844, 7.09108705, 4.75587942, 6.02754277,
6.07567479, 8.79599995, 6.58285106, 6.35517978, 7.42091138],
[7.58117358, 7.1607106 , 5.38161037, 6.08709119, 4.51325553,
6.69279857, 3.9319092 , 5.39058713, 6.9519177 , 7.82907281,
9.11361263, 6.63760617, 6.88139784, 5.61260512, 8.54100171,
7.67377794, 9.30938389, 7.82144838, 8.17728968, 7.42907412]
]
然后将cluster_centers
和all_clusters_dists
列表转换成 NumPy 数组,以便更好地处理它们。
根据all_clusters_dists
列表中的距离,每个样本被分配到最近的聚类。如下所示,cluster_indices
数组的每个元素都有一个值,该值对应于每个样本的指定聚类。例如,前四个样本被分配给第一组,而第五个样本被分配给第二组。
array([0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0])
还有另一个循环,对每个聚类中样本的距离求和。对于每个聚类,其内样本的索引被返回并附加到clusters
列表中。下一个输出显示了哪些样本属于每个聚类。
[
array([ 0, 1, 2, 3, 5, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19]),
array([ 4, 6, 7, 12])
]
如果一个聚类没有样本,那么它的总距离被设置为\(0\)。否则,计算并添加到clusters_sum_dist
列表中。下一个输出显示了每个聚类的总距离。
[99.19972158, 20.71714969]
在cluster_data()
函数结束时,返回以下数组:
cluster_centers
:星团的中心。all_clusters_dists
:所有样本和所有聚类中心之间的距离。cluster_indices
:样本被分配到的聚类的索引。clusters
:每个聚类内样本的索引。clusters_sum_dist
:每个聚类中样本的总距离。
健身功能
名为fitness_func()
的适应度函数被创建并调用cluster_data()
函数,计算所有聚类中的距离之和。
计算总和的倒数以使适应度函数最大化。
在找到最优解且总距离为 0 的情况下,将微小值0.00000001
加到分母上。
def fitness_func(solution, solution_idx):
_, _, _, _, clusters_sum_dist = cluster_data(solution, solution_idx)
fitness = 1.0 / (numpy.sum(clusters_sum_dist) + 0.00000001)
return fitness
本节讨论了该过程如何从初始聚类中心的初始解开始,直到计算它们的适应度值。下一部分使用遗传算法来进化聚类中心,直到达到最优解。
利用遗传算法进化
使用 PyGAD 库可以很容易地构建遗传算法。您需要做的就是创建一个pygad.GA
类的实例,同时传递适当的参数。然后,调用run()
方法,开始进化若干代的聚类中心。
import pygad
num_genes = num_clusters * data.shape[1]
ga_instance = pygad.GA(num_generations=100,
sol_per_pop=10,
num_parents_mating=5,
keep_parents=2,
num_genes=num_genes,
fitness_func=fitness_func,
suppress_warnings=True)
ga_instance.run()
best_solution, best_solution_fitness, best_solution_idx = ga_instance.best_solution()
print("Best solution is {bs}".format(bs=best_solution))
print("Fitness of the best solution is {bsf}".format(bsf=best_solution_fitness))
print("Best solution found after {gen} generations".format(gen=ga_instance.best_solution_generation))
在run()
方法完成之后,关于最佳解决方案的一些信息被打印出来。下一个块显示了最佳解决方案本身、它的适合度以及找到它的代。
Best solution: array([11.630579, 10.40645359, 1.418031, 3.96230158])
Fitness of the best solution is 0.033685406034550607
Best solution found after 73 generations
下一个代码块使用遗传算法找到的最佳解决方案对数据进行聚类。下图显示了该解决方案对数据进行了充分的聚类。
import matplotlib.pyplot
cluster_centers, all_clusters_dists, cluster_indices, clusters, clusters_sum_dist = cluster_data(best_solution, best_solution_idx)
for cluster_idx in range(num_clusters):
cluster_x = data[clusters[cluster_idx], 0]
cluster_y = data[clusters[cluster_idx], 1]
matplotlib.pyplot.scatter(cluster_x, cluster_y)
matplotlib.pyplot.scatter(cluster_centers[cluster_idx, 0], cluster_centers[cluster_idx, 1], linewidths=5)
matplotlib.pyplot.title("Clustering using PyGAD")
matplotlib.pyplot.show()
下一张 GIF 展示了聚类中心是如何从初始中心进化到最优中心的。
两个集群的完整代码
import numpy
import matplotlib.pyplot
import pygad
cluster1_num_samples = 10
cluster1_x1_start = 0
cluster1_x1_end = 5
cluster1_x2_start = 2
cluster1_x2_end = 6
cluster1_x1 = numpy.random.random(size=(cluster1_num_samples))
cluster1_x1 = cluster1_x1 * (cluster1_x1_end - cluster1_x1_start) + cluster1_x1_start
cluster1_x2 = numpy.random.random(size=(cluster1_num_samples))
cluster1_x2 = cluster1_x2 * (cluster1_x2_end - cluster1_x2_start) + cluster1_x2_start
cluster2_num_samples = 10
cluster2_x1_start = 10
cluster2_x1_end = 15
cluster2_x2_start = 8
cluster2_x2_end = 12
cluster2_x1 = numpy.random.random(size=(cluster2_num_samples))
cluster2_x1 = cluster2_x1 * (cluster2_x1_end - cluster2_x1_start) + cluster2_x1_start
cluster2_x2 = numpy.random.random(size=(cluster2_num_samples))
cluster2_x2 = cluster2_x2 * (cluster2_x2_end - cluster2_x2_start) + cluster2_x2_start
c1 = numpy.array([cluster1_x1, cluster1_x2]).T
c2 = numpy.array([cluster2_x1, cluster2_x2]).T
data = numpy.concatenate((c1, c2), axis=0)
matplotlib.pyplot.scatter(cluster1_x1, cluster1_x2)
matplotlib.pyplot.scatter(cluster2_x1, cluster2_x2)
matplotlib.pyplot.title("Optimal Clustering")
matplotlib.pyplot.show()
def euclidean_distance(X, Y):
return numpy.sqrt(numpy.sum(numpy.power(X - Y, 2), axis=1))
def cluster_data(solution, solution_idx):
global num_cluster, data
feature_vector_length = data.shape[1]
cluster_centers = []
all_clusters_dists = []
clusters = []
clusters_sum_dist = []
for clust_idx in range(num_clusters):
cluster_centers.append(solution[feature_vector_length*clust_idx:feature_vector_length*(clust_idx+1)])
cluster_center_dists = euclidean_distance(data, cluster_centers[clust_idx])
all_clusters_dists.append(numpy.array(cluster_center_dists))
cluster_centers = numpy.array(cluster_centers)
all_clusters_dists = numpy.array(all_clusters_dists)
cluster_indices = numpy.argmin(all_clusters_dists, axis=0)
for clust_idx in range(num_clusters):
clusters.append(numpy.where(cluster_indices == clust_idx)[0])
if len(clusters[clust_idx]) == 0:
clusters_sum_dist.append(0)
else:
clusters_sum_dist.append(numpy.sum(all_clusters_dists[clust_idx, clusters[clust_idx]]))
clusters_sum_dist = numpy.array(clusters_sum_dist)
return cluster_centers, all_clusters_dists, cluster_indices, clusters, clusters_sum_dist
def fitness_func(solution, solution_idx):
_, _, _, _, clusters_sum_dist = cluster_data(solution, solution_idx)
fitness = 1.0 / (numpy.sum(clusters_sum_dist) + 0.00000001)
return fitness
num_clusters = 2
num_genes = num_clusters * data.shape[1]
ga_instance = pygad.GA(num_generations=100,
sol_per_pop=10,
num_parents_mating=5,
init_range_low=-6,
init_range_high=20,
keep_parents=2,
num_genes=num_genes,
fitness_func=fitness_func,
suppress_warnings=True)
ga_instance.run()
best_solution, best_solution_fitness, best_solution_idx = ga_instance.best_solution()
print("Best solution is {bs}".format(bs=best_solution))
print("Fitness of the best solution is {bsf}".format(bsf=best_solution_fitness))
print("Best solution found after {gen} generations".format(gen=ga_instance.best_solution_generation))
cluster_centers, all_clusters_dists, cluster_indices, clusters, clusters_sum_dist = cluster_data(best_solution, best_solution_idx)
for cluster_idx in range(num_clusters):
cluster_x = data[clusters[cluster_idx], 0]
cluster_y = data[clusters[cluster_idx], 1]
matplotlib.pyplot.scatter(cluster_x, cluster_y)
matplotlib.pyplot.scatter(cluster_centers[cluster_idx, 0], cluster_centers[cluster_idx, 1], linewidths=5)
matplotlib.pyplot.title("Clustering using PyGAD")
matplotlib.pyplot.show()
具有 3 个集群的示例
前面的示例仅使用了两个集群。本节构建了一个使用 3 个集群的示例。下面给出了这个例子的完整代码。
下一张 GIF 展示了 3 个星团的中心是如何演变的。
import numpy
import matplotlib.pyplot
import pygad
cluster1_num_samples = 20
cluster1_x1_start = 0
cluster1_x1_end = 5
cluster1_x2_start = 2
cluster1_x2_end = 6
cluster1_x1 = numpy.random.random(size=(cluster1_num_samples))
cluster1_x1 = cluster1_x1 * (cluster1_x1_end - cluster1_x1_start) + cluster1_x1_start
cluster1_x2 = numpy.random.random(size=(cluster1_num_samples))
cluster1_x2 = cluster1_x2 * (cluster1_x2_end - cluster1_x2_start) + cluster1_x2_start
cluster2_num_samples = 20
cluster2_x1_start = 4
cluster2_x1_end = 12
cluster2_x2_start = 14
cluster2_x2_end = 18
cluster2_x1 = numpy.random.random(size=(cluster2_num_samples))
cluster2_x1 = cluster2_x1 * (cluster2_x1_end - cluster2_x1_start) + cluster2_x1_start
cluster2_x2 = numpy.random.random(size=(cluster2_num_samples))
cluster2_x2 = cluster2_x2 * (cluster2_x2_end - cluster2_x2_start) + cluster2_x2_start
cluster3_num_samples = 20
cluster3_x1_start = 12
cluster3_x1_end = 18
cluster3_x2_start = 8
cluster3_x2_end = 11
cluster3_x1 = numpy.random.random(size=(cluster3_num_samples))
cluster3_x1 = cluster3_x1 * (cluster3_x1_end - cluster3_x1_start) + cluster3_x1_start
cluster3_x2 = numpy.random.random(size=(cluster3_num_samples))
cluster3_x2 = cluster3_x2 * (cluster3_x2_end - cluster3_x2_start) + cluster3_x2_start
c1 = numpy.array([cluster1_x1, cluster1_x2]).T
c2 = numpy.array([cluster2_x1, cluster2_x2]).T
c3 = numpy.array([cluster3_x1, cluster3_x2]).T
data = numpy.concatenate((c1, c2, c3), axis=0)
matplotlib.pyplot.scatter(cluster1_x1, cluster1_x2)
matplotlib.pyplot.scatter(cluster2_x1, cluster2_x2)
matplotlib.pyplot.scatter(cluster3_x1, cluster3_x2)
matplotlib.pyplot.title("Optimal Clustering")
matplotlib.pyplot.show()
def euclidean_distance(X, Y):
return numpy.sqrt(numpy.sum(numpy.power(X - Y, 2), axis=1))
def cluster_data(solution, solution_idx):
global num_clusters, feature_vector_length, data
cluster_centers = []
all_clusters_dists = []
clusters = []
clusters_sum_dist = []
for clust_idx in range(num_clusters):
cluster_centers.append(solution[feature_vector_length*clust_idx:feature_vector_length*(clust_idx+1)])
cluster_center_dists = euclidean_distance(data, cluster_centers[clust_idx])
all_clusters_dists.append(numpy.array(cluster_center_dists))
cluster_centers = numpy.array(cluster_centers)
all_clusters_dists = numpy.array(all_clusters_dists)
cluster_indices = numpy.argmin(all_clusters_dists, axis=0)
for clust_idx in range(num_clusters):
clusters.append(numpy.where(cluster_indices == clust_idx)[0])
if len(clusters[clust_idx]) == 0:
clusters_sum_dist.append(0)
else:
clusters_sum_dist.append(numpy.sum(all_clusters_dists[clust_idx, clusters[clust_idx]]))
clusters_sum_dist = numpy.array(clusters_sum_dist)
return cluster_centers, all_clusters_dists, cluster_indices, clusters, clusters_sum_dist
def fitness_func(solution, solution_idx):
_, _, _, _, clusters_sum_dist = cluster_data(solution, solution_idx)
fitness = 1.0 / (numpy.sum(clusters_sum_dist) + 0.00000001)
return fitness
num_clusters = 3
feature_vector_length = data.shape[1]
num_genes = num_clusters * feature_vector_length
ga_instance = pygad.GA(num_generations=100,
sol_per_pop=10,
init_range_low=0,
init_range_high=20,
num_parents_mating=5,
keep_parents=2,
num_genes=num_genes,
fitness_func=fitness_func,
suppress_warnings=True)
ga_instance.run()
best_solution, best_solution_fitness, best_solution_idx = ga_instance.best_solution()
print("Best solution is {bs}".format(bs=best_solution))
print("Fitness of the best solution is {bsf}".format(bsf=best_solution_fitness))
print("Best solution found after {gen} generations".format(gen=ga_instance.best_solution_generation))
cluster_centers, all_clusters_dists, cluster_indices, clusters, clusters_sum_dist = cluster_data(best_solution, best_solution_idx)
for cluster_idx in range(num_clusters):
cluster_x = data[clusters[cluster_idx], 0]
cluster_y = data[clusters[cluster_idx], 1]
matplotlib.pyplot.scatter(cluster_x, cluster_y)
matplotlib.pyplot.scatter(cluster_centers[cluster_idx, 0], cluster_centers[cluster_idx, 1], linewidths=5)
matplotlib.pyplot.title("Clustering using PyGAD")
matplotlib.pyplot.show()
了解更多详情
- 莫立克、乌吉瓦尔和僧伽蜜多。“基于遗传算法的聚类技术。”模式识别 33.9 (2000): 1455-1465。
结论
在本教程中,我们使用进化遗传算法构建了一个聚类算法。使用遗传算法而不是流行的 k-means 算法的主要动机是,除了对初始聚类中心不太敏感之外,还能够在不停留在第一个可用解决方案的情况下达到最优解决方案。
讨论了 k-means 算法的步骤;遗传算法遵循类似的步骤。遗传算法通过求和每个样本与其聚类中心之间的距离来计算适应值。
在本教程中,我们还解决了 2 个和 3 个集群的两个例子——使用随机的人工数据——以展示遗传算法是如何找到最优解的。