PaperSpace-博客中文翻译-六-
PaperSpace 博客中文翻译(六)
从梯度部署运行稳定扩散 web UI
原文:https://blog.paperspace.com/stable-diffusion-webui-deployment/
2022 年 11 月 30 日:本教程现已过时:请点击此处查看后续文章,了解 Paperspace 上 Web UI 部署的最新版本
随着越来越多的人赶上这股热潮,稳定扩散的受欢迎程度持续爆炸式增长。稳定扩散是潜在扩散模型的一个强大的预训练版本,是上个月由 CompVis 的研究人员发布的一个扩散模型。该模型使用 LAION 5B 数据集的子集进行训练,包括用于初始训练的高分辨率子集和用于后续回合的“美学”子集。
最终,他们得到了一个极其健壮的模型,它能够以视觉形式模拟和再现几乎任何可以想象的概念,除了文本提示输入之外,不需要任何指导。请务必查看我们关于稳定扩散的完整报道和技术讲座,以获得关于该模型如何产生、底层架构的更多信息,并在发布时更全面地了解其功能。
在本文中,我们将看看 Stable Diffusion web UI 的 AUTOMATIC111 fork ,并展示如何在任何 Paperspace GPU 驱动的机器上在不到一分钟的时间内旋转 web UI。这个令人敬畏的应用程序由 Gradio 提供支持,让用户在一个低代码 GUI 中立即访问稳定扩散。
稳定扩散网络用户界面
随着这项技术越来越受欢迎,使用 awesome 模型的不同方法也越来越多。虽然原始的扩散器包仍然是访问任何给定合成任务的稳定扩散检查点的最常见方法,但原始 repo 的许多替代分支、变化以及与其他技术的融合,如 CrossAttentionControl 或 Guided Diffusion ,以及 full on web 应用程序已经发展到从原始存储库服务于社区。
The web UI GUI
可以说,其中最流行和更新最频繁的是 AUTOMATIC111 的稳定扩散 web UI 分支。除了稳定扩散的基本功能,如 txt2img、img2img、修复和升级,web UI 还打包了许多改进、生活质量修复、附加和实验脚本以及许多附加升级,所有这些都可以从易于使用的 web 应用程序 GUI 访问。(有关 web UI 特性的更多信息,请参阅本文的特性部分)
用户开始使用稳定扩散 web 用户界面的挑战
虽然 web UI 为稳定的扩散图像合成提供了对标准扩散器包方法的强大升级,但是想要利用这些升级的用户面临两个挑战:
- 设置:虽然 web UI 自带内置的设置脚本来帮助新用户,但它还不是即插即用的——即使在渐变笔记本中也是如此。用户要么需要学习如何自己设置环境,要么使用 fork 来为他们设置环境
- 计算:在本地机器上找到足够的计算量来及时运行稳定的扩散过程是很困难的,所以远程实例是理想的。
有了梯度部署,我们可以消除很多令人头疼的问题,包括采购计算和完成设置以运行 web UI。我们已经创建了一个 docker 容器,它预装了在部署中执行 web UI 所需的所有必需的包和模型。这个公共容器可以通过 Docker Hub 访问,用于任何渐变笔记本、工作流或部署。
web 用户界面存在的问题
同样值得注意的是,这仍处于开发阶段,并且会遇到此类应用程序常见的成长烦恼。如果您遇到 web UI 的问题,比如图像生成后无法输出,这可能是 web UI 界面的问题。尽管有这些小问题,web UI 仍然是在低代码环境中使用稳定扩散的最佳方式之一。
稳定扩散模型本身有其局限性,如低分辨率图像生成。在使用 web 用户界面时,一定要记住这些,如果出现问题,可以考虑重新启动部署。
最后,如果部署本身突然离线,这可能有多种原因。在部署概述中检查您的规范,查看副本数量是否超过“0”。如果没有,请将其更改为重启部署。
如何在渐变部署中使用稳定扩散 web 用户界面
有两种方法可以启动梯度部署:通过 Paperspace web 控制台或梯度 CLI。让我们先来看看 web 设置方法:
2022 年 11 月 30 日:本教程现已过时:请点击此处查看后续文章,了解 Paperspace 上 Web UI 部署的最新版本
Web 设置
要在渐变部署中运行稳定扩散 web UI,首先登录到您的渐变帐户,并导航到您选择的团队和项目。然后点击部署选项卡,并点击创建。
进入部署页面后,单击“上传部署规范”链接在“运行部署”部分下。然后,只需将下面的部署 YAML 规范粘贴到弹出窗口中,并适当地命名您的部署。
image: paperspace/stable-diffusion-webui-deployment:v1
port: 7860
command:
- python
- webui.py
- '--share'
- '--autolaunch'
- '--listen'
- '--port'
- '7860'
env:
- name: ENV
value: VAR
resources:
replicas: 1
instanceType: <Machine Type> <-- We suggest the A4000
请务必将底部的<machine type>
改为您想要使用的机器。你可以在这里找到可用机器的完整列表,并且一定要考虑价格。我推荐 A4000,但从成本效益的角度来看,从长远来看,更大的图像生成项目将受益于使用更强大的 GPU,如 A100。
Click on the link that says Endpoint to access the web UI GUI
完成后,点击“创建部署”启动机器。这需要大约一分钟的时间来设置。完成后,单击部署详细信息页面中的 API 端点链接打开 web UI。
使用完部署后,您可以删除它或使用概览页面右侧的“编辑”按钮更新规范。在规范中,将副本数量从“1”更改为“0”,机器将停止运行。
梯度 CLI 设置
首先,确保您已经按照文档中列出的步骤下载并登录到 Gradient CLI。您需要获得 API 密钥才能从 Paperspace 控制台登录。
一旦你用你的 API 密匙登录,进入你的终端并创建一个新的目录来保存我们的 YAML 文件。然后在终端用touch deploy-stable-diffusion.yaml
新建一个文件“deploy-stable-diffusion.yaml”。在您选择文本编辑器中打开该文件,并粘贴以下内容
image: paperspace/stable-diffusion-webui-deployment:v1
port: 7860
command:
- python
- webui.py
- '--share'
- '--autolaunch'
- '--listen'
- '--port'
- '7860'
env:
- name: ENV
value: VAR
resources:
replicas: 1
instanceType: <Machine Type> <-- We suggest the A4000
请务必将底部的<machine type>
更改为您想要使用的任何机器。你可以在这里找到可用机器的完整列表,并且一定要考虑定价。接下来,运行 gradient projects list 并找到您想要用于此部署的项目的 ID。如果没有,用gradient projects create
创建一个。
一旦完成,您就可以用一个终端执行来启动您的部署了!只需运行以下命令来加速您的部署:
gradient deployments create --name [Deployment name] --projectId [your Project ID] --spec [path to your deployment spec file i.e. deploy-stable-diffusion.yaml]
从那里,它应该需要大约一分钟来设置。完成后,您需要单击部署详细信息页面中的 API 端点链接来打开 web UI。您可以在浏览器的渐变部署控制台中访问它,或者通过在您的终端中输入以下新部署 ID 来访问它:
gradient deployments get --id <your deployment id>
然后,当您使用完部署时,您可以通过返回到您的 spec 文件并将其更改为 0 个副本来删除它。这将停止部署运行,但不会删除它。然后使用以下终端命令更新您的部署:
gradient deployments update --id <your deployment id> --spec <path to updated spec>
稳定扩散 web 用户界面的功能
现在我们已经创建了我们的部署,我们准备在 GUI 中使用稳定扩散模型的功能。这些功能是从原始扩散器库高度变化和扩展的,包括但不限于:
samples from the txt2img input "A (((cowboy))) astronaut riding a horse on the moon"
- 原始 txt2img 和 img2img 模式:潜在扩散 img2img 和 txt2img 的原始脚本
Sample outpainting images from stable-diffusion-webui repo
- Outpainting:使用它来预测图像边界之外的视觉上下文,即从图像的原始边界之外到新的所需边界填充缺失的信息。
From an original input photo of the Eifel Tower, this series of inpainted images shows "a (((gorilla))) (((King Kong))) climbing the side of a tower"
- 修复:以前,可以上传蒙版图像进行图像修复。通过 web UI,用户可以在 GUI 内的所需区域上绘画,并修补特定的目标区域。
Prompt matrix example using the prompt "a cute breakfast nook in a modern home with a small table and an egg|illustration|cinematic lighting"
- 提示矩阵:使用
|
字符分隔多个提示,系统将为它们的每个组合生成一个图像。 - 稳定扩散放大:使用稳定扩散将图像生成参数放大到更高的分辨率,而不会损失视觉敏锐度或主题
Example of using multiple attention flags to change presentation of final output using prompt "a cute breakfast nook in a modern home with a small table and an egg|illustration|pixel art"
- 注意:使用注意来控制提示的哪些部分更重要或更不重要。在提示中使用
()
增加模型对包含单词的注意,而[]
减少它。您可以使用括号和圆括号的多个实例来增加或减少权重。
web UI 的其他有用功能包括:
- 采样方法选择:从大量不同的采样方法中进行选择,包括 ddim、欧拉和 PLMS。
- 随时中断处理:随时点击中断按钮,中断推理过程,吐出当前版本的图像。
- 提示长度验证:检测您的提示对于稳定扩散来说是否太长而无法用作输入。
- 设置页面:设置页面允许您定制用于不同建模过程的各种设置。
- 从 UI 运行定制代码:通过在部署规范中输入用户标志
--allow-code
,您可以在 web UI 中使用定制代码功能。 - 随机艺术家按钮:随机艺术家按钮会在你的提示中附加一个随机艺术家的名字(和风格)。
- 平铺支持:创建可以像纹理一样平铺的图像的 UI 复选框。这些平铺尺寸可以根据需要进行调整。
- 进度条和实时图像生成预览:在图像生成时实时查看您的进度。
- 负面提示:用户不希望赋予最终图像的特定关键字。
- 样式:保存提示和否定提示以备后用。
- 种子大小调整:在不同的输出分辨率下保持图像种子的“随机性”。
- 剪辑询问器:使用剪辑来确定相应的文字提示输入到任何图像。
- 即时编辑:即时编辑允许你开始对一张图片进行采样,但是在中间切换到其他图片。例如,[乔治·克鲁尼:布拉德·皮特:. 5]将让合成从克鲁尼的前半部分开始,然后使用皮特作为后半部分步骤的输入。
- 批处理:使用批处理一次对多个样本的相同参数进行推断。
- Img2img 备选方案:使用欧拉扩散器的反转来解构输入图像,以创建用于构建输入提示的噪声模式。
“附加项目”选项卡中还有更多型号:
- GFPGAN,修复人脸的神经网络
- 神经网络升级器
- ESRGAN,具有大量第三方模型的神经网络
有关这些功能的更多信息,请务必访问 稳定扩散 w eb UI 功能列表页面。
结束语
有了这个指南,我们现在应该准备好启动带有渐变部署的稳定扩散 web UI 了。当我们与 web UI 开发团队就此项目进行迭代时,请返回此页面查看此部署规范的更新,以便为您提供梯度部署的最佳体验。
2022 年 11 月 30 日:本教程现已过时:请点击此处查看后续文章,了解 Paperspace 上 Web UI 部署的最新版本
2 本书来加强你对 Python 机器学习的掌握
原文:https://blog.paperspace.com/strengthen-your-python-machine-learning/
这篇文章是“人工智能教育”的一部分,这是一系列评论和探索数据科学和机器学习教育内容的文章。
掌握机器学习并不容易,即使你是一个优秀的程序员。我见过许多人有着编写不同领域(游戏、网络、多媒体等)软件的坚实背景。)认为将机器学习添加到他们的技能列表中是另一件轻松的事情。不是的。他们每个人都很沮丧。
我认为机器学习的挑战被误解有两个原因。首先,顾名思义,机器学习是一种自我学习的软件,而不是由开发人员来指导每一条规则。这是一种过于简化的说法,许多对编写机器学习算法的实际挑战知之甚少或一无所知的媒体在谈到 ML 交易时经常使用这种说法。
第二个原因,在我看来,是许多书籍和课程承诺用几百页的篇幅教你机器学习的来龙去脉(以及 YouTube 上的广告承诺,如果你通过在线课程,就能获得一份机器学习工作)。现在,我不想诋毁任何这些书和课程。我已经回顾了其中的几篇(接下来几周还会再回顾一些),我认为它们是成为优秀机器学习开发者的无价资源。
但这还不够。机器学习既需要良好的编码和数学技能,也需要对各种类型的算法有深刻的理解。如果你在做 Python 机器学习,你必须对许多库有深入的了解,还要掌握这门语言的许多编程和内存管理技术。而且,与一些人所说的相反,你无法逃避数学。
而这一切都不是几百页就能概括的。完整的机器学习指南可能会像唐纳德·克努特著名的《计算机编程的艺术》系列一样,而不是一本书。
那么,这些长篇大论是为了什么?在我探索数据科学和机器学习的过程中,我总是在寻找那些深入探究更一般、包罗万象的书籍所跳过的主题的书籍。
在这篇文章中,我将看看 Python for Data Analysis 和Practical Statistics for Data Scientists,这两本书将有助于加深您对掌握 Python 机器学习和数据科学所需的编码和数学技能的掌握。
用于数据分析的 Python
Python for Data Analysis,第二版,由熊猫的创造者 Wes McKinney 编写,熊猫是 Python 机器学习中使用的关键库之一。在 Python 中进行机器学习包括在将数据输入到模型之前加载和预处理 pandas 中的数据。
大多数关于机器学习的书籍和课程都介绍了主要的 pandas 组件,如数据帧和系列,以及一些关键功能,如从 CSV 文件加载数据和清除丢失数据的行。但是熊猫的力量比你在大多数书中看到的一章代码样本要广泛和深刻得多。
Python for Data Analysis
在《用于数据分析的 Python》中,McKinney 带你了解 pandas 的全部功能,并设法做到了这一点,而没有让它读起来像一本参考手册。有很多有趣的例子,它们相互叠加,帮助你理解熊猫的不同功能是如何相互联系的。您将深入了解清洗、连接和可视化数据集等内容,这些主题在大多数机器学习书籍中通常只是简单讨论。
您还将探索一些非常重要的挑战,例如内存管理和代码优化,当您在机器学习中处理非常大的数据集时(您经常这样做),这可能会变得很重要。
我喜欢这本书的另一点是,它在选择 500 页的主题时所采用的技巧。虽然这本书的大部分内容是关于熊猫的,但 McKinney 非常注意用关于其他重要 Python 库和主题的材料来补充。您将很好地了解 numpy 的面向数组编程,numpy 是另一个重要的 Python 库,经常与 pandas 一起用于机器学习,以及一些使用 Jupyter 笔记本的重要技术,Jupyter 笔记本是许多数据科学家的首选工具。
说了这么多,不要指望 Python for Data Analysis 是一本很好玩的书。它可能会变得很无聊,因为它只是讨论如何处理数据(这恰好是机器学习中最无聊的部分)。不会有任何端到端的例子让你看到训练和使用机器学习算法或在实际应用中集成你的模型的结果。
我的建议:在浏览完一本关于数据科学或机器学习的入门或高级书籍后,你可能会选择 Python 进行数据分析。拥有使用 Python 机器学习库的介绍性背景将有助于您更好地掌握书中介绍的技术。
数据科学家实用统计学
虽然用于数据分析的Python提高了你的数据处理和操作编码技能,但我们要看的第二本书 数据科学家实用统计,第二版,将是加深你对许多关键算法和概念背后的核心数学逻辑的理解的完美资源,这些算法和概念是你在进行数据科学和机器学习时经常处理的。
这本书从简单的概念开始,如不同类型的数据,均值和中位数,标准差和百分位数。然后,它将逐步带你了解更高级的概念,如不同类型的分布、抽样策略和显著性检验。这些都是你可能在数学课上学过或者在数据科学和机器学习书籍中读到过的概念。
但是,这里的关键是专业化。
Practical Statistics for Data Scientists
一方面,为数据科学家带来的实用统计学对这些主题的深度比你在机器学习书籍中找到的要大。另一方面,每个主题都介绍了 Python 和 R 语言的编码示例,这使得它比经典的统计学教科书更适合统计学。此外,作者在消除数据科学和其他领域中不同术语的歧义方面做了大量工作。每个主题都附有一个框,提供了所有流行术语的不同同义词。
随着你对这本书的深入,你会深入到机器学习算法的数学中,如线性和逻辑回归,K-最近邻,树和森林,以及 K-均值聚类。在每种情况下,就像这本书的其余部分一样,人们更多地关注算法背后发生的事情,而不是将其用于应用程序。但作者再次确保这些章节读起来不像经典的数学教科书,公式和方程附有漂亮的编码示例。
就像用于数据分析的 Python, 用于数据科学家的实用统计学如果你从头到尾读,可能会有点无聊。没有令人兴奋的应用程序,也没有一个连续的过程让你一章一章地编写代码。但是从另一方面来说,这本书的结构是这样的,你可以独立地阅读任何一个章节,而不需要阅读前面的章节。
我的推荐:在浏览了一本关于数据科学和机器学习的入门书籍后,阅读数据科学家实用统计学。我绝对推荐读一遍整本书,尽管为了让它更有趣,在你探索其他机器学习课程的过程中逐个主题地进行。也要放在手边。你可能会不时重温一些章节。
一些结束语
我肯定会把用于数据分析的 Python和用于数据科学家的实用统计学作为任何正在学习数据科学和机器学习的人的必读书目。虽然它们可能不像一些更实用的书那样令人兴奋,但你会欣赏它们对你的编码和数学技能的深度。
风格转移第二部:换脸!
使用深度神经网络的人脸转移
介绍
在之前的文章中,我们讨论了什么是风格转移以及如何使用它来创建自己的艺术。现在,我们将重点讨论如何将风格转移的功能扩展到艺术领域之外。请跳转到本帖的资源部分,了解用于生成示例的代码。
假设你对金凯瑞在《指环王》中的角色有多适合感到好奇。此外,我们将尝试把金凯瑞的脸转移到咕鲁身上。一个有趣的方法是使用 Gatys 等人的风格转移法。但是这能正常工作吗?亲自看看结果:
Figure 1. Face swap using Gatys et al. method.
如你所见,结果是一场灾难。输出看起来一点也不像从吉姆到咕鲁的面部转换。相反,它只是吉姆的脸,有令人讨厌的白色纹理和棕色和灰色的阴影。
看到这个结果,两个问题出现了。首先,为什么会这样?第二,怎么修?
在本文中,我们将引导您回答这两个问题。我们将指出 Gatys 等人工作中的局限性,并发现新的技术来提高面部转移等任务的性能。
Gatys 等人的风格转移方法的局限性
不可否认,Gatys 等人使用深度神经网络进行风格转换的工作是一个先驱。而且,像所有的先锋作品一样,它充满了改进的空间。我们可以改进原始作品的一个方面是纹理估计器。回想一下第一篇文章,作者建议它基于 Gram 矩阵。
Figure 2. Gram matrix approach.
Gram matrix 方法对于艺术目的来说是很棒的,但是当我们想要对哪些纹理被传递到结果的每个部分进行一些控制时,它就会受到影响。背后的原因是它破坏了风格图像的语义,只保留了基本的纹理成分。因此,我们不能,例如,转移咕噜的鼻子风格凯瑞的,因为纹理估计器已经破坏了头发的信息。我们只能访问摩根图片的全局纹理。
所以让我们回到面部转移的例子,试着解释发生了什么:
- 金·凯瑞被用作内容
- 咕鲁被用作风格
- 结果的语义内容和凯瑞的形象非常接近
- 结果的全局纹理也接近咕鲁图像的全局纹理。毕竟,最终的脸比原始的吉姆照片更像咕噜的肤色。
所以我们能够准确定位纹理提取器的问题。现在我们需要知道如何改进它,才能使面部转移成为可能。我们将进行两项改进。第一篇是基于李和 Wand 结合马尔可夫随机场和卷积神经网络进行图像合成的论文。第二个改进是基于 Alex Champandard 的语义风格转移,将两位涂鸦变成精美的艺术品纸。
李与魔杖改良
李和 Wand 的主要贡献是针对原有纹理提取方法的局限性,提出了一种新的基于马尔可夫随机场的纹理提取方法。对于那些不熟悉这个术语的人来说,MRF 是一个在图像问题中非常常用的概率图形模型。事实上,它被认为是图像合成问题中的经典框架。
在这一点上,一个自然的问题是:MRF 能做什么,而格拉姆矩阵不能?事实证明,MRF 并没有破坏风格的语义信息,以转移其纹理到最终的图像。新的纹理提取器执行以下步骤:
- 获取样式图像和结果图像的更高级特征(使用 dCNN 的输出)
- 将特征分割成小的“神经”片(记住高级特征也是图像)
- 将结果图像的每个补丁与样式图像的所有补丁进行比较
- 对于生成的图像的每个补丁,选择最接近的样式补丁,并在生成的补丁上使用其纹理
在我们的面转移问题中,MRF 方法将做如下工作:
- 对生成的图像进行切片(合成面的位置)
- 选择一个贴片(例如贴片上有下巴的一部分)
- 在咕鲁的图片中找到更接近我们当前“结果补丁”的补丁(希望是咕鲁的下巴)
- 在生成的面片上使用该样式面片的纹理。
观察纹理传递正在局部发生,逐块进行,直到结果完成。我们从全局 Gram 矩阵纹理转换到局部面片 MRF 纹理转换。关于问题的数学公式,内容提取器和通过优化的合并过程保持不变。唯一的区别是样式提取器的变化和一个不太重要的术语。
在我们的面子转移问题中使用这种新方法,我们得到以下结果:
Figure 3. Results obtained using Li and Wand proposed technique.
如你所见,结果比我们之前的尝试好得多。由于新的局部纹理提取器,现在我们有了一个合适的面部转换。然而,结果还不是很好。这幅画有几个人工制品,它们中的大部分以咕鲁的图像背景的形式进入金凯瑞的头发。
这背后的主要原因是补丁不匹配,这在当前的方法中很难控制。还记得我们在寻找匹配最终图像下巴的最佳补丁吗?很可能是咕鲁的下巴。然而,由于图像视角的差异,该算法可以选择图像的另一部分作为最佳匹配块。
为了改善我们的结果,我们可以微调优化参数和补丁的大小。这绝对是一条路要走。另一个选择,对于面部转移来说更有趣,是使用 Champandard 论文中提出的方法。正如我们将在下一节中看到的,这种新方法以一种对我们的目的理想的方式改进了李和王的建议。
标准改进
我们最后的改进是基于标准纸。作者的主要贡献是增加了 Li 和 Wand 方法,使用户能够控制每种风格的转换。事实证明,这足以解决补丁不匹配的问题。
为了让用户对风格转换过程有更多的控制,Champandard 神经网络不仅接收内容和风格图像,还接收它们的语义图。语义图是突出原始图像特征的图像。看看下面的例子:
Figure 4. Manually created semantic maps for the source images.
在这个例子中,我们可以看到几个手工着色的语义图,其中十个特征用不同的颜色编码。
语义图背后的思想是缩小算法寻找补丁匹配的空间。例如,如果它正在搜索最佳的嘴部补丁,它将在样式图像的嘴部补丁中放置更多的权重。这样,就不太可能出现补丁不匹配的情况。
如果我们在我们的面部转移问题中运行 Champandard 方法,我们得到以下结果:
Figure X
毫无疑问,这是我们迄今为止看到的最好结果。输出的人脸看起来像是从金凯瑞到咕鲁的人脸转移。现在我们有了一个很好的方法来进行面部转移,让我们看看它在实践中是如何工作的。
面部转移的运行标准方法
幸运的是,尚标准在 github 上分享了他论文的代码。只需克隆 repo,遵循安装指南或使用 docker 容器,您就可以开始了。请记住上一篇文章,您必须有一个好的 GPU 才能在合理的时间内运行它。还有,确保你用的是推荐的茶餐厅和千层面。最后,我强烈建议不要使用 CuDNN v5。推荐的方法是使用 GitHub repo 中提供的虚拟环境。
你需要输入这两幅图像以及它们的语义图。如果我们使用默认配置运行,确保将语义图命名为" original_image_name" + "_sem.png "。您可以通过选择颜色数量和绘制相关区域来控制样式转换。此外,对于面部转移的特殊情况,您应该小心一些参数,以便获得良好的结果。关于这个方面,我和几个朋友做了一些关于参数如何影响结果的调查,我们发现了一些有趣的准则:
- 最重要的参数是:内容权重、风格权重和迭代次数
- 如果迭代次数很少,输出将有很多神经噪声(卷积神经网络的典型噪声)
- 为了得到一个真正好的图像,你应该有很多次迭代。图像收敛得非常快,但需要相当长的时间来消除部分神经噪声。我建议设置迭代次数= 1000。在 GTX 1080 上拍摄每幅图像大约需要 40 - 50 分钟。所以,只有当你已经知道自己想要什么,并且已经做了一些测试的时候,才使用它
- 为获得最佳转印面,请确保内容重量/样式重量比在区间[0.6,1]内。
例如,运行本文所示示例的命令是:
python3 doodle.py --style samples/gollum.png --content samples/jim_carrey.png --output face_transfer.png --device=gpu0 --phases=4 --iterations=1000 --content-weight=10 --style-weight=10
结论
神经类型转移仍然是一个新的研究领域。因此,有很大的改进和创新空间。在本文中,您接触了该方法的一个新应用(面部转移)。你学到了:
- Gatys 和 al 的传统方法。面部转移不能正常工作
- 与 Gatys 等人的方法相比,我们进行了两项改进,以获得一种适用于面部转移的方法
- 要运行您自己的 face transfer 项目,请克隆 Champandard repo GitHub,遵循安装指南或打开 docker 容器,创建您的语义映射并确保参数设置正确。
作为一个改进的建议,可以尝试在标准方法中添加一个算法,从原始图像中自动生成语义图。
资源
下面是一些进一步的资源和用于创建示例的源代码。特别感谢引用代码的作者,他们清晰的实现和非常详细的自述文件,这使得它非常容易复制。
- 用深度神经网络创造艺术。该系列的第一篇文章。
- Gatys 等人的方法,神经风格转移 GitHub repo
- 李与权杖法, CNNMRF GitHub repo
- 标准方法,神经涂鸦 GitHub repo
要开始使用您自己的 ML-in-a-box 设置,在此注册。
在渐变笔记本上运行 StyleGAN3
原文:https://blog.paperspace.com/stylegan3-gradient-notebooks/
更新于 2022 年 8 月 17 日
最近于 10 月 12 日发布的 StyleGAN3 在深度学习社区引起了很多兴奋,这是有充分理由的!多年来,StyleGAN2(及其各种分支,如 StyleGAN2 与 ADA 和 StarGAN-v2 )一直主导着深度学习的图像生成领域。
StyleGAN2 具有无与伦比的生成高分辨率(1024x1024)图像的能力。这种能力已经在无数的项目中得到应用,比如非常受欢迎的 FaceApp 或者学术项目这个 X 不存在。
StyleGAN2 还具有极大的通用性,这意味着它能够在任何符合其简单使用要求的图像数据集上表现良好。得益于这种高质量和易用性的结合,StyleGAN2 已成为需要生成新颖图像的任务的首选型号。
随着 StyleGAN3 的发布,这种情况可能会有所改变。
StyleGAN3 有什么新特性?
在后台
Nvidia 实验室的团队在发布 StyleGAN3 之前有许多有趣的问题要解决。
在人类中,信息的处理方式使得感知图像的子特征能够分层转换。正如作者在sync[1所说:
当人的头部转动时,头发和鼻子等子特征也会相应地移动。大多数 GAN 发生器的结构是相似的。低分辨率特征通过上采样层进行修改,通过卷积进行局部混合,并且通过非线性引入新的细节
尽管有这种明显的相似性,但事实并非如此。目前,使用典型 GAN 发生器架构的图像合成以非分级方式创建图像。在 StyleGAN2 和其他图像生成器 GAN 架构中,精细细节定位由“纹理粘连”决定,而不是由粗糙特征控制精细特征的存在和位置这是细节被锁定到从训练中继承的像素坐标的地方。
消除这种混叠效应是 StyleGAN3 开发的首要任务。为此,他们首先确定 StyleGAN2 中的信号处理是问题所在。
在生成器中,由于走样的影响,非预期的位置引用被绘制到中间层上。因此,StyleGAN2 中没有直接的机制来强制生成器以严格的分层方式合成图像。该网络将总是放大任何混叠的效果,并且在多个尺度上组合该效果之后,使得纹理主题固定到像素级坐标。[ 2
为了解决这个问题,他们重新设计了无别名网络。这是通过考虑连续域中这种混叠的影响并对结果进行低通滤波来抑制这种混叠而实现的。在实践中,这允许在所有层中执行子像素平移和旋转的连续等变——这意味着可以在 StyleGAN3 中以相同的效率生成细节,而不管它们的位置,并消除任何“纹理粘连”的影响
有什么改进?
StyleGAN 架构上的混叠效应的消除大大提高了模型的效率,并使其潜在的用例多样化。
消除混叠效应对模型生成的图像有明显的影响。混叠直接阻碍了分层信息上采样的形成。它还消除了任何“纹理粘连”的存在
随着更精细的细节不再受到粗糙细节的物理位置的限制,StyleGAN3 的内部图像表示现在使用坐标系来构建,该坐标系引导细节信息适当地附着到底层表面。这一增加允许进行显著的改进,特别是在用于生成视频和动画的模型中。
最后,StyleGAN3 在完成所有这些工作的同时,还会生成令人印象深刻的 FID 分数。FID 或 Frechet 起始距离是合成图像质量的度量。较低的分数表示高质量的图像,而 StyleGAN3 实现了较低的分数以及较低的计算开销。
渐变笔记本中的 StyleGAN3:
https://www.youtube.com/embed/iWxNyAB82-8?feature=oembed
Gradient Notebooks 是在免费的 GPU 实例上尝试 StyleGAN3 的好地方。如果你还不知道 Gradient,它是一个构建和扩展现实世界机器学习应用的平台,Gradient 笔记本提供了一个 JupyterLab 环境,具有强大的免费和付费 GPU。
为了在渐变笔记本中开始使用 StyleGAN3,我们将登录或注册渐变控制台并前往笔记本。
从笔记本页面,我们将滚动到最后一部分“高级选项”并单击切换按钮。
对于我们的Workspace
,我们将告诉 Gradient 提取位于这个 URL 的存储库:
https://github.com/NVlabs/stylegan3
我们将使用已经提供的默认容器名。最后,我们将向上滚动到Select a Machine
,并选择一个 GPU 实例。
免费的 GPU 实例将能够处理训练过程,尽管它会相当慢。如果可能的话,我们建议您尝试更强大的 GPU,例如付费的 A100 或 V100 实例类型——当然这并不总是可能的。
接下来,启动你的笔记本。
您可以通过点击下面的链接跳过上面的所有说明!
使用预训练模型生成图像
在继续之前,请确保在终端中运行以下命令来启用 StyleGAN3:
pip install ninja
首先,我们将使用一个预训练的模型来生成我们的图像。 StyleGAN3 在“入门”一节中提供了相关的操作说明
python gen_images.py --outdir=out --trunc=1 --seeds=2 \
--network=https://api.ngc.nvidia.com/v2/models/nvidia/research/stylegan3/versions/1/files/stylegan3-r-afhqv2-512x512.pkl
我们将复制列出的第一个命令。这使用了 AFHQ-v2 数据集上的预训练模型,该数据集包括 15,000 张高质量的猫、狗和各种野生动物的图像,以生成猫的单个图像。以下是输出结果的一个示例:
训练你自己的模型
对于这个演示,我们将使用动物面孔-总部数据集(AFHQ)。我们选择这个数据集是因为它很容易将文件下载到 Gradient,但是您可以使用任何符合 StyleGAN3 要求的图像数据集。
笔记本完成初始化后,在 IDE 中打开终端并输入以下命令:
git clone https://github.com/clovaai/stargan-v2
cd stargan-v2
bash download.sh afhq-dataset
这将把数据集下载到 stargan-v2 的预设文件夹中。
下载完成后,将目录改回笔记本以运行 dataset_tool.py 。这个脚本将图像打包成一个未压缩的 zip 文件,然后 StyleGAN3 可以将这个 zip 文件用于训练。在不使用数据集工具的情况下,仍然可以在文件夹上进行训练,但是这存在性能不佳的风险。
cd ..
python stylegan3/dataset_tool.py --source=stargan-v2/data/afhq/train/cat --dest=data/afhq-256x256.zip --resolution=256x256
现在我们的数据准备好了,我们可以开始训练了。如果您遵循了上述说明,您可以使用以下终端输入来运行培训:
python train.py --outdir=mods --gpus=1 --batch=16 --gamma=8.2 --data=data/afhq-256x2562.zip --cfg=stylegan3-t --kimg=10 --snap=10
一旦您完成了新模型的训练,它应该输出到指定的文件夹,在本例中是“mods”。
A cat image generated using the training parameters listed above and training for ~24 hours.
一旦我们完成了模型的训练,我们就可以开始生成图像了。这是一个使用模型生成的图像示例,我们使用此流程对该模型进行了训练。经过大约 24 小时的训练,我们能够使用 StyleGAN3 在渐变笔记本上生成逼真的新猫图像。
结论:
StyleGAN3 在以下方面改进了 StyleGAN2:
- 全面改进了上采样过滤器,以更积极地抑制图像的自动混叠
- 消除了锯齿的影响,这种影响会导致更精细的细节到粗糙细节像素坐标的纹理固定
- 通过强制对图像细节进行更自然的分层细化,并使用坐标系创建图像细节的内部表示,以将细节正确地关联到底层表面,从而提高视频和动画生成能力。
遵循本文顶部的指南,开始在渐变环境中设置您自己的 StyleGAN3 模型训练和图像生成过程,并在将来关注本文的第 2 部分,在那里我们将与 StyleGAN3 一起在工作流中训练一个完整的、新颖的模型,然后再部署它。
来源:
- https://synced review . com/2021/10/13/deep mind-pod racer-TPU-based-rl-frameworks-deliver-excellent-performance-at-low-cost-122/
- https://nvlabs-fi-cdn . NVIDIA . com/style gan 3/style gan 3-paper . pdf
SRGAN:超分辨率生成对抗网络
原文:https://blog.paperspace.com/super-resolution-generative-adversarial-networks/
Photo by Karim MANJRA / Unsplash
如今,高分辨率图像和高清视频是人们享受 R&R 时最流行的必需品。特定图像或视频的质量越高,观众的整体观看体验就变得越愉快和值得关注。当今世界开发的每一项现代化视觉技术都旨在满足高质量视频和音频性能的要求。随着这些图像和视频质量的快速提高,这些产品的供应和需求也在快速增长。然而,由于正当程序中面临的技术限制,可能并不总是能够获得或生成最高质量的图像或视频。
超分辨率生成对抗网络(SRGANs)提供了对由于技术约束或导致这些限制的任何其他原因而遇到的这些问题的解决方案。借助这些巨大的 GAN 架构,您可以将许多低分辨率图像或视频内容升级为高分辨率实体。
在本文中,我们的主要目标是使用这些 SRGAN 模型架构来实现从较低质量图像获得超分辨率图像的目标。我们将探索这个架构,并用 SRGANs 网络构建一个简单的项目。我建议看看我之前的两部作品,以了解这篇文章的最新内容。要开始使用 GANs,请查看以下链接-“”生成性对抗网络(GANs)完全指南;要进一步了解 DCGANs,请查看以下链接-“开始使用 DCGANs ”
在本文中,我们将涵盖与理解如何在 SRGANs 的帮助下将低分辨率图像转换为超分辨率图像相关的大多数基本内容。在简要介绍了众多的分辨率频谱并理解了 SRG an 的基本概念后,我们将开始实现 SRG an 的架构。我们将构建生成器和鉴别器模型,我们可以利用它们来构建许多与 SRGAN 相关的项目。我们将最终用这些架构开发一个项目,并进一步了解它们的实现是如何工作的。如果您的本地系统无法访问高质量的 GPU,强烈建议您利用 Paperspace 上的 Gradient Platform 来为我们开发的项目产生最有效的结果。
简介:
*
在我们进一步探讨超分辨率图像这个话题之前,让我们先了解一下现代世界中常见的各种视频质量。在线观看视频时,典型的较低质量是 144p、240p 或 360p。这些分辨率描述了您可以流化或观看特定视频或查看图像的较低质量。在如此低的分辨率下,特定视觉表示的一些更精细的细节和更适当的关注点可能无法被人眼察觉。因此,观众的整体体验可能不如预期的那样美观。
480p 分辨率是大多数观看格式的最低标准分辨率。该分辨率支持 640 X 480 像素大小的质量,并且是早期计算中的典型标准。观看视觉表示的这个标准定义(SD)具有 4:3 的纵横比,并且被认为是大多数表示的规范。从 720p 开始,更进一步的是高清晰度(HD ),通常像素大小约为 1280 x 720。
然后,我们有 1080p 短格式的全高清(FHD ),代表 1920x1080 的像素大小,还有 1440p 短格式的四高清(QHD ),代表 2560x1440 的像素大小。所有这三种比例都具有 16:9 的纵横比,并且是大多数普通计算引擎更广泛使用的一些比例。最终的缩放范围包括 2K、4K 和 8K 分辨率的更现代的可视化光谱。随着技术进步的改善,目标是进一步改善这些图像和视频质量,以便观众或观众能够获得最佳体验。
但是,我们可能会注意到,有时我们没有获得我们想要的图像质量或视频质量。这些原因可能是相机镜头类型、缩放功能、缺乏高效技术、无效编辑、模糊背景捕捉或任何其他类似因素。虽然一些软件可能有助于解决这个问题,但解决这些问题的最佳高级解决方案之一是借助深度学习神经网络,即超分辨率生成对抗网络(SRGANs)架构,将这些低分辨率图像(或视频)转换为更高质量的内容。
在上面的 GIF 表示中,我们可以注意到视频的较低图像分辨率在 135p 的观看范围内,并且它缺少图像中一些非常重要的细节。内容的整体质量,如较细的岩石颗粒的飞行和宇宙飞船的整体视图,看起来相当模糊。然而,通过使用 SRGANs,视频被转换为 540p 格式,使观众能够更好地了解电影的复杂细节。我推荐查看下面的图片来源剪辑,因为它展示了一个从低分辨率到高分辨率的星际电影场景转换的伟大工作。现在让我们对 SRGANs 有一个概念性的了解,然后根据所获得的知识相应地实现它们。
了解 SRGANs:
SRGANs 的概念是最早的技术之一,它允许模型对大多数图像视觉效果实现近 4 倍的放大系数。从低分辨率图像估计并生成高分辨率图像的想法是一项极具挑战性的任务。CNN 早期用于制作高分辨率图像,训练速度更快,精确度更高。然而,在某些情况下,它们无法恢复更精细的细节,并且经常生成模糊的图像。提出的 SRGAN 架构解决了这些问题中的大部分,用于生成高质量、最先进的图像。
大多数处理超分辨率的监督算法利用所获取的高分辨率图像和特定图像的地面真实情况之间的均方误差损失。这种方法被证明是方便的,因为最小化均方误差自动最大化峰值信噪比(PSNR)。PSNR 是用于评估超分辨率图像的最常用术语之一。然而,这些术语更倾向于寻找每个单独像素的特征,而不是更多的视觉感知属性,例如特定图片的高纹理细节。
因此,下面的关于使用生成对抗网络生成照片级真实单幅图像超分辨率的研究论文提出了一种损失,该损失被确定为借助于新引入的称为感知损失的损失来对抗更多面向感知的特征。VGG 损失是在实时风格传输的感知损失和超分辨率超分辨率和风格传输框架中引入的一种内容损失。感知损失是对抗性损失和内容损失的结合。这一损失的提法可以用以下解释来解释。**
这种损失优于均方误差损失,因为我们不关心图像的逐像素比较。我们最关心的是图像质量的提高。因此,通过在 SRGAN 模型中使用该损失函数,我们能够获得更理想的结果。
分解 SRGAN 架构:
在本文的这一部分,我们将更详细地了解 SRGAN 体系结构的构造。我们将分别探讨生成器和鉴别器架构,并理解它们是如何工作的。生成器架构基本上是用于生成高质量超分辨率图像的全卷积 SRRESNET 模型。作为图像分类器的鉴别器模型的添加被构造成确保整体架构根据图像的质量进行调整,并且所得到的图像更加优化。SRGAN 架构可生成具有高感知质量的逼真自然图像。
发电机:
SRRESNET 生成器网络的生成器架构由低分辨率输入组成,该输入通过 9×9 内核和 64 个特征映射的初始卷积层,然后是参数 ReLU 层。值得注意的是,整个发生器架构利用参数 ReLU 作为主要激活功能。选择参数 ReLU 的原因是因为它是将低分辨率图像映射到高分辨率图像这一特定任务的最佳非线性函数之一。
像 ReLU 这样的激活函数也可以执行以下任务,但是当小于零的值被直接映射到零时,由于死神经元的概念,可能会出现一些问题。另一种选择是 Leaky ReLU,其中小于零的值被映射到用户设置的数字。然而,在参数 ReLU 的情况下,我们可以让神经网络自己选择最佳值,因此在这种情况下是首选的。
前馈全卷积 SRRESNET 模型的下一层利用了一堆残余块。每个残差块包含一个 3×3 内核和 64 个特征映射的卷积层,随后是一个批量归一化层、一个参数化 ReLU 激活函数、另一个批量归一化的卷积层和一个最终的逐元素求和方法。逐元素求和方法使用前馈输出和跳过连接输出来提供最终的结果输出。
关于下面的神经网络体系结构,需要注意的一个关键方面是,每个卷积层都使用相似的填充,因此下面的输入和输出的大小不会改变。不像 U-Net 架构这样的完全卷积网络,你可以从下面的链接查看,通常利用池层来减少图像大小。但是,我们不要求我们的 SRGAN 模型满足以下条件,因为图像大小不需要减小。相反,这与我们希望实现的目标恰恰相反。
一旦构建了残差块,就构建了生成器模型的其余部分,如上图所示。在卷积层的 4x 上采样之后,我们利用该生成器模型架构中的像素混洗器来产生超分辨率图像。像素混洗器从通道维度获取值,并将它们粘贴到高度和宽度维度中。在这种情况下,高度和宽度都乘以 2,而通道除以 2。本文的下一部分将详细介绍生成器架构构建的代码。
鉴别器:
鉴频器架构以支持典型 GAN 程序的最佳方式构建。生成器和鉴别器都在相互竞争,同时都在改进。当鉴别器网络试图找到假图像时,发生器试图产生真实的图像,以便它可以逃避鉴别器的检测。在 SRGANs 的情况下工作也是类似的,其中生成模型 G 的目标是欺骗一个可微分的鉴别器 D ,该鉴别器被训练来区分超分辨率图像和真实图像。
因此,上图所示的鉴别器架构可以区分超分辨率图像和真实图像。所构造的鉴别器模型旨在解决对立的极小极大问题。这个方程的公式的一般思想可以解释如下:
构造的鉴别器体系结构非常直观,易于理解。我们利用一个初始卷积层,后面跟着一个泄漏 ReLU 激活函数。对于该结构,泄漏 ReLU 的 alpha 值被设置为 0.2。然后我们有一堆卷积层的重复块,后面是批量归一化层和泄漏 ReLU 激活函数。一旦有了五个这样的重复块,我们就有了致密层,然后是用于执行分类动作的 sigmoid 激活功能。请注意,初始起始卷积大小为 64 x 64,在两个完整的块之后乘以 2,直到我们达到 512 x 512 的 8 倍放大因子。这个鉴别器模型帮助生成器更有效地学习并产生更好的结果。
与 SRGANs 一起开发项目:
在本文的这一部分,我们将与 SRGANs 一起开发一个项目。有许多数据集可用于完成这项任务。该研究论文利用了来自 ImageNet 数据集的 35 万幅图像的随机样本。然而,ImageNet 数据集的大小约为 150 GB,训练这样一个模型将花费大量时间。因此,对于这个项目,我们将在多样化的 2k (div2k)数据中使用一个更方便、更小的数据集,大约 5GB。
对于这个项目,我们将利用 TensorFlow 和 Keras 深度学习框架来构建 SRGAN 模型,并根据需要对其进行训练。如果你对这两个库都不熟悉,我建议你看看下面的指南来了解 TensorFlow,以及这个链接来开始使用 Keras。用于构建这个项目的大部分代码来自下面的 GitHub 库,我强烈推荐查看。事实上,我建议将 datasets 和 utils 文件夹下载到您的工作目录中,因为这将简化提取数据的工作,并且我们可以专注于构建和训练 SRGANs 架构模型。
导入基本库:
开始 SRGAN 项目的第一步是实现执行以下任务所需的所有基本库。确保您的设备上启用了 TensorFlow 的 GPU 版本,并导入以下代码块中提到的所有必需的库。损耗、优化器、层、VGG16 损耗的 VGG19 架构和其他必要的库。其他重要的导入是从前面提到的 GitHub 链接的下载文件夹中直接导入。确保数据集和实用程序文件夹位于您的工作目录中。这些将用于简化数据集准备,并减少训练模型的工作量。
import tensorflow as tf
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.optimizers.schedules import PiecewiseConstantDecay
from tensorflow.keras.losses import MeanSquaredError, BinaryCrossentropy, MeanAbsoluteError
from tensorflow.keras.layers import Input, Conv2D, BatchNormalization, Add, Lambda, LeakyReLU, Flatten, Dense
from tensorflow.python.keras.layers import PReLU
from tensorflow.keras.applications.vgg19 import VGG19, preprocess_input
from tensorflow.keras.models import Model
from tensorflow.keras.metrics import Mean
from PIL import Image
import time
import os
from datasets.div2k.parameters import Div2kParameters
from datasets.div2k.loader import create_training_and_validation_datasets
from utils.normalization import normalize_m11, normalize_01, denormalize_m11
from utils.dataset_mappings import random_crop, random_flip, random_rotate, random_lr_jpeg_noise
from utils.metrics import psnr_metric
from utils.config import config
from utils.callbacks import SaveCustomCheckpoint
准备数据集:
多样化的 2K 数据集包含许多分辨率的高质量图像,这对于我们想要构建的 SRGANs 模型来说是完美的。你可以从下面的网站下载数据集。为了阅读本文的其余部分,我建议您下载下面代码片段中提到的四个单独的 zip 文件。这四个文件包含低分辨率和高分辨率图像的训练和验证文件。下载完成后,您可以相应地提取它们。
确保创建一个标记为 div2k 的新目录,并将所有提取的文件放在新创建的目录中。我们有低分辨率图像及其对应的高分辨率图像,我们的模型将利用这些图像进行训练。在研究论文中,他们使用了 96 x 96 大小的随机作物,因此我们将在训练方法中使用相同的作物。低分辨率图像的每个样本将被相应地裁剪成其对应的高分辨率面片。
# Dataset Link - https://data.vision.ee.ethz.ch/cvl/DIV2K/
# http://data.vision.ee.ethz.ch/cvl/DIV2K/DIV2K_train_LR_bicubic_X4.zip
# https://data.vision.ee.ethz.ch/cvl/DIV2K/DIV2K_valid_LR_bicubic_X4.zip
# http://data.vision.ee.ethz.ch/cvl/DIV2K/DIV2K_train_HR.zip
# http://data.vision.ee.ethz.ch/cvl/DIV2K/DIV2K_valid_HR.zip
dataset_key = "bicubic_x4"
data_path = config.get("data_path", "")
div2k_folder = os.path.abspath(os.path.join(data_path, "div2k"))
dataset_parameters = Div2kParameters(dataset_key, save_data_directory=div2k_folder)
hr_crop_size = 96
train_mappings = [
lambda lr, hr: random_crop(lr, hr, hr_crop_size=hr_crop_size, scale=dataset_parameters.scale),
random_flip,
random_rotate,
random_lr_jpeg_noise]
train_dataset, valid_dataset = create_training_and_validation_datasets(dataset_parameters, train_mappings)
valid_dataset_subset = valid_dataset.take(10)
构建 SRRESNET 生成器架构:
SRRESNET 生成器体系结构的构建与上一节中详细讨论的完全相同。该模型的体系结构被划分为几个功能,以便结构的整体大小变得更容易构建。我们将定义一个像素混合块和相应的函数,该函数将对我们的数据和像素混合层进行上采样。我们将为残差块定义另一个函数,该函数包含 3×3 内核的卷积层和 64 个特征映射的连续组合,其后是批量归一化层、参数化 ReLU 激活函数、具有批量归一化的另一个卷积层以及最终的逐元素求和方法,该方法相应地使用前馈和跳过连接。
upsamples_per_scale = {
2: 1,
4: 2,
8: 3
}
pretrained_srresnet_models = {
"srresnet_bicubic_x4": {
"url": "https://image-super-resolution-weights.s3.af-south-1.amazonaws.com/srresnet_bicubic_x4/generator.h5",
"scale": 4
}
}
def pixel_shuffle(scale):
return lambda x: tf.nn.depth_to_space(x, scale)
def upsample(x_in, num_filters):
x = Conv2D(num_filters, kernel_size=3, padding='same')(x_in)
x = Lambda(pixel_shuffle(scale=2))(x)
return PReLU(shared_axes=[1, 2])(x)
def residual_block(block_input, num_filters, momentum=0.8):
x = Conv2D(num_filters, kernel_size=3, padding='same')(block_input)
x = BatchNormalization(momentum=momentum)(x)
x = PReLU(shared_axes=[1, 2])(x)
x = Conv2D(num_filters, kernel_size=3, padding='same')(x)
x = BatchNormalization(momentum=momentum)(x)
x = Add()([block_input, x])
return x
def build_srresnet(scale=4, num_filters=64, num_res_blocks=16):
if scale not in upsamples_per_scale:
raise ValueError(f"available scales are: {upsamples_per_scale.keys()}")
num_upsamples = upsamples_per_scale[scale]
lr = Input(shape=(None, None, 3))
x = Lambda(normalize_01)(lr)
x = Conv2D(num_filters, kernel_size=9, padding='same')(x)
x = x_1 = PReLU(shared_axes=[1, 2])(x)
for _ in range(num_res_blocks):
x = residual_block(x, num_filters)
x = Conv2D(num_filters, kernel_size=3, padding='same')(x)
x = BatchNormalization()(x)
x = Add()([x_1, x])
for _ in range(num_upsamples):
x = upsample(x, num_filters * 4)
x = Conv2D(3, kernel_size=9, padding='same', activation='tanh')(x)
sr = Lambda(denormalize_m11)(x)
return Model(lr, sr)
构建鉴别器模型和 SRGAN 架构:
鉴别器架构的构建与上一节中详细讨论的完全相同。我们利用卷积层,然后是泄漏 ReLU 激活函数,它使用 0.2 的α值。我们为第一块添加一个卷积层和一个泄漏 ReLU 激活函数。鉴别器架构的其余五个模块利用卷积层,然后是批量归一化层,最后是添加的泄漏 ReLU 激活功能层。该架构的最后几层是 1024 个参数的全连接节点、一个泄漏 ReLU 层,以及具有用于分类目的的 sigmoid 激活函数的最终全连接密集节点。有关构建鉴别器架构的完整代码片段,请参考下面的代码块。
def discriminator_block(x_in, num_filters, strides=1, batchnorm=True, momentum=0.8):
x = Conv2D(num_filters, kernel_size=3, strides=strides, padding='same')(x_in)
if batchnorm:
x = BatchNormalization(momentum=momentum)(x)
return LeakyReLU(alpha=0.2)(x)
def build_discriminator(hr_crop_size):
x_in = Input(shape=(hr_crop_size, hr_crop_size, 3))
x = Lambda(normalize_m11)(x_in)
x = discriminator_block(x, 64, batchnorm=False)
x = discriminator_block(x, 64, strides=2)
x = discriminator_block(x, 128)
x = discriminator_block(x, 128, strides=2)
x = discriminator_block(x, 256)
x = discriminator_block(x, 256, strides=2)
x = discriminator_block(x, 512)
x = discriminator_block(x, 512, strides=2)
x = Flatten()(x)
x = Dense(1024)(x)
x = LeakyReLU(alpha=0.2)(x)
x = Dense(1, activation='sigmoid')(x)
return Model(x_in, x)
训练 SRGAN 模型:
既然我们已经成功地完成了 SRGAN 体系结构的构建,我们就可以开始训练模型了。将生成器模型和鉴别器模型存储在它们各自的模型中。定义 VGG 模型,以解释我们将在该模型中使用的永久损失。创建您的检查点,并为生成器和鉴别器网络定义优化器。完成以下步骤后,我们就可以开始训练 SRGAN 模型了。
generator = build_srresnet(scale=dataset_parameters.scale)
generator.load_weights(weights_file)
discriminator = build_discriminator(hr_crop_size=hr_crop_size)
layer_5_4 = 20
vgg = VGG19(input_shape=(None, None, 3), include_top=False)
perceptual_model = Model(vgg.input, vgg.layers[layer_5_4].output)
binary_cross_entropy = BinaryCrossentropy()
mean_squared_error = MeanSquaredError()
learning_rate=PiecewiseConstantDecay(boundaries=[100000], values=[1e-4, 1e-5])
generator_optimizer = Adam(learning_rate=learning_rate)
discriminator_optimizer = Adam(learning_rate=learning_rate)
srgan_checkpoint_dir=f'./ckpt/srgan_{dataset_key}'
srgan_checkpoint = tf.train.Checkpoint(step=tf.Variable(0),
psnr=tf.Variable(0.0),
generator_optimizer=Adam(learning_rate),
discriminator_optimizer=Adam(learning_rate),
generator=generator,
discriminator=discriminator)
srgan_checkpoint_manager = tf.train.CheckpointManager(checkpoint=srgan_checkpoint,
directory=srgan_checkpoint_dir,
max_to_keep=3)
if srgan_checkpoint_manager.latest_checkpoint:
srgan_checkpoint.restore(srgan_checkpoint_manager.latest_checkpoint)
print(f'Model restored from checkpoint at step {srgan_checkpoint.step.numpy()} with validation PSNR {srgan_checkpoint.psnr.numpy()}.')
使用充当装饰器的@tf.function,我们的 Python 命令被转换成张量流图的形式。我们将根据需要利用梯度带函数来编译和训练模型。我们将同时训练发生器和鉴别器网络,因为我们希望这两种模型架构同步改进。我们将利用前面讨论过的永久损失函数。如果读者已经阅读了我以前的一些 GANs 文章,我们在这些文章中更广泛地讨论了训练过程,那么训练过程的代码看起来一定非常直观。下面是开始 SRGANs 模型的训练过程的代码片段。
@tf.function
def train_step(lr, hr):
with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
lr = tf.cast(lr, tf.float32)
hr = tf.cast(hr, tf.float32)
sr = srgan_checkpoint.generator(lr, training=True)
hr_output = srgan_checkpoint.discriminator(hr, training=True)
sr_output = srgan_checkpoint.discriminator(sr, training=True)
con_loss = calculate_content_loss(hr, sr)
gen_loss = calculate_generator_loss(sr_output)
perc_loss = con_loss + 0.001 * gen_loss
disc_loss = calculate_discriminator_loss(hr_output, sr_output)
gradients_of_generator = gen_tape.gradient(perc_loss, srgan_checkpoint.generator.trainable_variables)
gradients_of_discriminator = disc_tape.gradient(disc_loss, srgan_checkpoint.discriminator.trainable_variables)
generator_optimizer.apply_gradients(zip(gradients_of_generator, srgan_checkpoint.generator.trainable_variables))
discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, srgan_checkpoint.discriminator.trainable_variables))
return perc_loss, disc_loss
@tf.function
def calculate_content_loss(hr, sr):
sr = preprocess_input(sr)
hr = preprocess_input(hr)
sr_features = perceptual_model(sr) / 12.75
hr_features = perceptual_model(hr) / 12.75
return mean_squared_error(hr_features, sr_features)
def calculate_generator_loss(sr_out):
return binary_cross_entropy(tf.ones_like(sr_out), sr_out)
def calculate_discriminator_loss(hr_out, sr_out):
hr_loss = binary_cross_entropy(tf.ones_like(hr_out), hr_out)
sr_loss = binary_cross_entropy(tf.zeros_like(sr_out), sr_out)
return hr_loss + sr_loss
一旦您成功地完成了上述代码块的运行,您就可以继续下一个代码片段,如下所示。请注意,训练过程可能需要相当长的时间,建议您根据您的系统类型让模型训练几个小时到几天,以获得最佳结果和我们开发的 SRGANs 生成的高分辨率图像。每个时期结束时生成的图像将保存在 monitor training 文件夹中,您可以相应地查看每个时期结束时生成的结果。由于我们之前创建的检查点回调,也保存了最佳权重。
perceptual_loss_metric = Mean()
discriminator_loss_metric = Mean()
step = srgan_checkpoint.step.numpy()
steps = 200000
monitor_folder = f"monitor_training/srgan_{dataset_key}"
os.makedirs(monitor_folder, exist_ok=True)
now = time.perf_counter()
for lr, hr in train_dataset.take(steps - step):
srgan_checkpoint.step.assign_add(1)
step = srgan_checkpoint.step.numpy()
perceptual_loss, discriminator_loss = train_step(lr, hr)
perceptual_loss_metric(perceptual_loss)
discriminator_loss_metric(discriminator_loss)
if step % 100 == 0:
psnr_values = []
for lr, hr in valid_dataset_subset:
sr = srgan_checkpoint.generator.predict(lr)[0]
sr = tf.clip_by_value(sr, 0, 255)
sr = tf.round(sr)
sr = tf.cast(sr, tf.uint8)
psnr_value = psnr_metric(hr, sr)[0]
psnr_values.append(psnr_value)
psnr = tf.reduce_mean(psnr_values)
image = Image.fromarray(sr.numpy())
image.save(f"{monitor_folder}/{step}.png" )
duration = time.perf_counter() - now
now = time.perf_counter()
print(f'{step}/{steps}, psnr = {psnr}, perceptual loss = {perceptual_loss_metric.result():.4f}, discriminator loss = {discriminator_loss_metric.result():.4f} ({duration:.2f}s)')
perceptual_loss_metric.reset_states()
discriminator_loss_metric.reset_states()
srgan_checkpoint.psnr.assign(psnr)
srgan_checkpoint_manager.save()
请注意,根据您在此过程中使用的系统类型,培训过程可能会很长。我建议查看 Paperspace 上的 Gradient 平台,它为大多数深度学习任务提供了一些最好的支持。将提供运行以下问题的基本要求。在从训练过的 SRGAN 模型产生大量图像结果的同时,可以自由地探索和深入到生成神经网络的世界。
结论:
Photo by Roman Melnychuk / Unsplash
特定可视化中所有基本特征和属性的整体加权组合有助于对表示的图像质量进行分类。较低的分辨率不能突出特定图片或视频内容中的一些更精细和关键的细节,这可以通过提高分辨率和指定实体的整体质量来解决。我们更喜欢以最高质量消费现代世界中的大多数可视化内容,以便我们作为观众和观众可以从特定内容中获得最佳体验。因此,超分辨率是一个在现代世界中具有重要意义的重要概念,也是我们在本文中通过生成神经网络来实现的目标。
在本文中,我们讨论了开始处理图像分辨率的大部分基本方面。我们理解分辨率的不同尺度以及获得高分辨率光谱而不是使用较低分辨率的重要性。在简要了解了图像和视频分辨率的概念后,我们进一步了解了 SRGANs 的概念。然后,我们通过查看相应的发生器和鉴别器模块,详细探讨了该网络的架构。最后,我们开发了一个项目来理解这些生成神经网络的意义以及它们在自然界中是如何工作的。
在未来的文章中,我们将尝试探索更多的 GAN 架构,并了解更多关于不同类型的生成网络的信息,这些网络在最近不断获得巨大的流行。我们还将探索神经类型转移的概念,并进一步详细讨论强化学习等主题。在那之前,继续学习和享受神经网络以及人工智能所提供的一切吧!*
用 K-Means 聚类+ SVR 构建 sharp 回归模型
原文:https://blog.paperspace.com/svr-kmeans-clustering-for-regression/
机器学习算法在架构和用例方面是多样的。我们在机器学习中处理的最常见的问题之一是回归,有时回归分析的复杂性增长到现有算法无法以足够高的精度预测值以部署到生产中的程度。本文深入探讨了回归模型,以及 K-Means 和支持向量回归的组合如何在某些情况下带来一个清晰的回归模型。
概观
- 回归
- 线性回归
- 为什么线性回归会失败
- 可供选择的事物
- 支持向量回归
- k 均值聚类
- K-Means + SVR 实现
- 结论
回归
一种统计方法,用于使用某些独立变量(X1,X2,..Xn)。简单来说,我们根据影响价值的因素来预测价值。
一个最好的例子就是出租车在线价格。如果我们研究在预测价格中起作用的因素,它们是距离、天气、日子、地区、车辆、燃料价格,等等。很难找到一个等式来提供乘坐的速度,因为这些因素在其中起着巨大的作用。回归通过识别每个特征(我们可以用机器学习术语来称呼它们)的影响程度来拯救我们,并提出一个可以预测乘坐率的模型。现在我们已经了解了回归背后的概念,让我们来看看线性回归。
线性回归
Formula for linear regression
线性回归是网关回归算法,旨在建立一个模型,试图找到自变量(X)和要预测的因变量(Y)之间的线性关系。从视觉的角度来看,我们试图创建一条线,使得与所有其他可能的线相比,点至少离它有距离。线性回归只能处理一个自变量,但是线性回归的扩展,多元回归遵循同样的思路用多个自变量贡献预测 Y. 详细实现线性回归。
Source: Linear Regression Explained A High-Level Overview of Linear Regression Analysis
为什么线性回归会失败?
尽管线性回归在计算上简单且易于解释,但它也有自己的缺点。只有当数据是线性可分的时候才是优选的。复杂的关系不能这样处理。聚集在一个区域的一组数据点会极大地影响模型,并且可能会出现偏差。现在我们已经理解了什么是回归以及基本的回归是如何工作的,让我们深入研究一些替代方案。
决策图表
决策树是这样一种树,其中每个节点代表一个特征,每个分支代表一个决策。结果(回归的数值)由每片叶子表示。决策树能够捕捉特征和目标变量之间的非线性交互。决策树很容易理解和解释。其中一个主要特点是它们在视觉上很直观。他们还能够处理数字和分类特征。
Source: scikit-learn Decision Trees
使用决策树的一些缺点是它容易过度适应。即使是数据中很小的变化也会导致不稳定,因为在这个过程中树结构可能会发生剧烈的变化。
随机森林回归
随机森林是朝着同一目标工作的多个决策树的组合。用随机选择的替换数据来训练每个树,并且每个分裂限于随机选择的用于分离数据的替换的可变 k 特征。
在大多数情况下,这种算法比单一的决策树更有效。有了多棵树,这些变化实际上有助于不带任何偏见地处理新数据。实际上,这允许从非常弱的学习者的集合中创建强的学习者。最终回归值是所有决策树结果的平均值。当试图用所有的树达到更高的性能时,巨大的树会带来某些缺点,比如执行速度慢。它还占据了巨大的内存需求。
支持向量回归
既然我们已经研究了大部分的回归算法,那么让我们来研究一下我们在本文中关注的算法。支持向量回归(SVR)是最好的回归算法之一,它专注于处理总体误差,并试图比线性回归等算法更好地避免异常值问题。在我们学习支持向量机之前,我们应该先了解一下什么是支持向量机,因为支持向量机的思想就是从这里产生的。
支持向量机
支持向量机的目标是在 N 维向量空间中提出一个超平面,其中 N 是所涉及的特征的数量。这个超平面被期望对我们提供的数据点进行分类。这是一种很好的分类算法,在许多情况下,它比逻辑回归更好,因为超平面使数据点远离,而不是仅仅用一条细线作为边界。这里的想法是将数据点保持在平面的任一侧,落在平面内的数据点是这里的异常值。
核是支持向量机性能背后的主要因素,支持向量机对维度进行降维,从而将其转换为线性方程。它还成功地相对较好地扩展到高维数据。标准特征中两点之间的内积由内核返回。SVM 中有不同类型的核函数。
高斯核用于在没有数据先验知识的情况下进行变换。当数据是线性可分时,使用线性核。特征空间中的数据训练集中的向量在核中使用的原始变量的多项式上的相似性由多项式核表示。这些是一些流行的内核,还有更多可以探索和实验的。让我们看看 SVM 的一个小实现,以及使用著名的虹膜数据集的超平面的可视化。
import seaborn as sns
from sklearn.svm import SVC
import matplotlib.pyplot as plt
import numpy as np
# Creating dataframe to work on
iris = sns.load_dataset("iris")
y = iris.species
X = iris.drop('species',axis=1)
df=iris[(iris['species']!='versicolor')]
df=df.drop(['sepal_length','sepal_width'], axis=1)
# Converting categorical vlaues to numericals
df=df.replace('setosa', 0)
df=df.replace('virginica', 1)
# Defining features and targer
X=df.iloc[:,0:2]
y=df['species']
# Building SVM Model using sklearn
model = SVC(kernel='linear', C=1E10)
model.fit(X, y)
# plotting the hyperplane created and the data points' distribution
ax = plt.gca()
plt.scatter(X.iloc[:, 0], X.iloc[:, 1], c=y, s=50, cmap='spring')
ax.set(xlabel = "Petal Length",
ylabel = "Petal Width")
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z = model.decision_function(xy).reshape(XX.shape)
ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5,
linestyles=['--', '-', '--'])
ax.scatter(model.support_vectors_[:, 0], model.support_vectors_[:, 1], s=100,
linewidth=1, facecolors='none', edgecolors='k')
plt.show()
Support Vector Machine Visualization showing the different feature spaces for two of the target categorizations for the Iris dataset.
了解 SVR
支持向量回归使用支持向量机背后的相同原理。SVR 还在 N 维向量空间中构建超平面,其中 N 是所涉及的特征的数量。我们的目标不是让数据点远离超平面,而是让数据点在超平面内进行回归。其中一个可调参数是ε(ε),这是我们在特征空间中创建的管的宽度。我们超调的下一个正则化参数是 C,它控制“松弛度”(ξ),测量到管外点的距离。在开始结合 K-Means 聚类和 SVR 之前,让我们先来看一个简单的 SVR 实现和可视化。这里我们使用来自 Kaggle 的房地产价格数据集。
import pandas as pd
import numpy as np
from sklearn.svm import SVR # for building support vector regression model
import plotly.graph_objects as go # for data visualization
import plotly.express as px # for data visualization
# Read data into a dataframe
df = pd.read_csv('Real estate.csv', encoding='utf-8')
X=df['X2 house age'].values.reshape(-1,1)
y=df['Y house price of unit area'].values
# Building SVR Model
model = SVR(kernel='rbf', C=1000, epsilon=1) # set kernel and hyperparameters
svr = model.fit(X, y)
x_range = np.linspace(X.min(), X.max(), 100)
y_svr = model.predict(x_range.reshape(-1, 1))
plt = px.scatter(df, x=df['X2 house age'], y=df['Y house price of unit area'],
opacity=0.8, color_discrete_sequence=['black'])
# Add SVR Hyperplane
plt.add_traces(go.Scatter(x=x_range,
y=y_svr, name='Support Vector Regression', line=dict(color='blue')))
plt.add_traces(go.Scatter(x=x_range,
y=y_svr+10, name='+epsilon', line=dict(color='blue', dash='dot')))
plt.add_traces(go.Scatter(x=x_range,
y=y_svr-10, name='-epsilon', line=dict(color='blue', dash='dot')))
# Set chart background color
plt.update_layout(dict(plot_bgcolor = 'white'))
# Updating axes lines
plt.update_xaxes(showgrid=True, gridwidth=1, gridcolor='lightgrey',
zeroline=True, zerolinewidth=1, zerolinecolor='lightgrey',
showline=True, linewidth=1, linecolor='black')
plt.update_yaxes(showgrid=True, gridwidth=1, gridcolor='lightgrey',
zeroline=True, zerolinewidth=1, zerolinecolor='lightgrey',
showline=True, linewidth=1, linecolor='black')
# Update marker size
plt.update_traces(marker=dict(size=3))
plt.show()
Support Vector Regression Visualization
这里我们可以看到,尽管数据点是非线性分布的,但是 SVR 用超平面巧妙地处理了它。与其他回归算法相比,当我们有不止一个特征时,这种影响甚至更加明显。现在我们已经对 SVR 有了一个很好的介绍,让我们来探索 K-Means + SVR 的惊人组合,它可以在正确的回归环境下创造奇迹。
k 均值聚类
K-Means 聚类是一种流行的用于聚类数据的无监督机器学习算法。对数据点进行聚类的算法如下:
- 首先,我们定义一些集群,这里设它为 K
- 随机选择 K 个数据点作为聚类的质心
- 根据到任一聚类的欧氏距离对数据进行分类
- 通过利用数据点来更新每个聚类中的质心
- 重复从步骤 3 开始的步骤一定次数(超参数:max_iter)
尽管这看起来很简单,但是有很多方法可以优化 K-Means 聚类。这包括正确选择初始化质心的点等等。我们将在下面讨论其中的一个,轮廓系数,它给出了从多少个簇中选择的好主意。
实现 K 均值聚类+支持向量回归
我们将使用来自 Kaggle 的相同的房地产价格数据集来实现。这里我们的独立变量是“X3 到最近的捷运站的距离”和“X2 房子的年龄”。“单位面积的房价”是我们预测的目标变量。让我们首先将数据点可视化,以定义对集群的需求。
import pandas as pd # for data manipulation
import numpy as np # for data manipulation
import matplotlib.pyplot as plt
# Read data into a dataframe
df = pd.read_csv('Real estate.csv', encoding='utf-8')
# Defining Dependent and Independant variable
X = np.array(df[['X3 distance to the nearest MRT station','X2 house age']])
Y = df['Y house price of unit area'].values
# Plotting the Clusters using matplotlib
plt.rcParams['figure.figsize'] = [14, 7]
plt.rc('font', size=14)
plt.scatter(df['X3 distance to the nearest MRT station'],df['X2 house age'],label="cluster "+ str(i+1))
plt.xlabel("X3 distance to the nearest MRT station")
plt.ylabel("X2 house age")
plt.legend()
plt.show()
Distribution of the Real Estate Prices House Ages (years) agains the Distance to the Nearest MRT station
显然,数据点的分布不适合包含它们的超平面。让我们使用 K-均值聚类对它们进行聚类。在我们开始之前,我们可以使用剪影系数来确定我们需要的能够将数据点准确保持在选定数量的聚类中的聚类数量。更好的轮廓系数或分数意味着更好的聚类。它表示集群之间的距离。最高分 1 表示聚类彼此相距很远,也可以清楚地区分,分 0 表示聚类之间的距离不明显,分-1 表示聚类分配不正确。
让我们首先评估数据点的轮廓系数。我们将把数据分成训练和测试,从这里开始向我们的最终目标前进。将对训练数据执行聚类。
from sklearn.model_selection import train_test_split
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
import statistics
from scipy import stats
X_train, X_test, Y_train, Y_test = train_test_split(
X, Y, test_size=0.30, random_state=42)
silhouette_coefficients = []
kmeans_kwargs= {
"init":"random",
"n_init":10,
"max_iter":300,
"random_state":42
}
for k in range(2, 11):
kmeans = KMeans(n_clusters=k, **kmeans_kwargs)
kmeans.fit(X_train)
score = silhouette_score(X_train, kmeans.labels_)
silhouette_coefficients.append(score)
# Plotting graph to choose the best number of clusters
# with the most Silhouette Coefficient score
import matplotlib.pyplot as plt
plt.style.use("fivethirtyeight")
plt.plot(range(2, 11), silhouette_coefficients)
plt.xticks(range(2, 11))
plt.xlabel("Number of Clusters")
plt.ylabel("Silhouette Coefficient")
plt.show()
Silhouette Coefficient Scores vs Clusters
我们可以得出结论,使用 3 个集群,我们可以实现更好的集群。让我们继续对训练集数据进行聚类。
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import pandas as pd # for data manipulation
import numpy as np # for data manipulation
# Instantiate the model: KMeans from sklearn
kmeans = KMeans(
init="random",
n_clusters=3,
n_init=10,
max_iter=300,
random_state=42
)
# Fit to the training data
kmeans.fit(X_train)
train_df = pd.DataFrame(X_train,columns=['X3 distance to the nearest MRT station','X2 house age'])
# Generate out clusters
train_cluster = kmeans.predict(X_train)
# Add the target and predicted clusters to our training DataFrame
train_df.insert(2,'Y house price of unit area',Y_train)
train_df.insert(3,'cluster',train_cluster)
n_clusters=3
train_clusters_df = []
for i in range(n_clusters):
train_clusters_df.append(train_df[train_df['cluster']==i])
colors = ['red','green','blue']
plt.rcParams['figure.figsize'] = [14, 7]
plt.rc('font', size=12)
# Plot X_train again with features labeled by cluster
for i in range(n_clusters):
subset = []
for count,row in enumerate(X_train):
if(train_cluster[count]==i):
subset.append(row)
x = [row[0] for row in subset]
y = [row[1] for row in subset]
plt.scatter(x,y,c=colors[i],label="cluster "+ str(i+1))
plt.title("Train Data Clusters", x=0.6, y=0.95)
plt.xlabel("X3 distance to the nearest MRT station")
plt.ylabel("X2 house age")
plt.legend()
plt.show()
Train Data Clusters visualization
为集群构建 SVR
现在,让我们继续为每个集群构建 SVR 模型。我们在这里不关注 SVR 的过度调节。如果需要,可以使用 gridsearch 尝试各种参数值的组合。
import pandas as pd
import numpy as np
from sklearn.svm import SVR
n_clusters=3
cluster_svr = []
model = SVR(kernel='rbf', C=1000, epsilon=1)
for i in range(n_clusters):
cluster_X = np.array((train_clusters_df[i])[['X3 distance to the nearest MRT station','X2 house age']])
cluster_Y = (train_clusters_df[i])['Y house price of unit area'].values
cluster_svr.append(model.fit(cluster_X, cluster_Y))
让我们定义一个函数来预测房价(Y ),方法是首先预测聚类,然后预测具有相应 SVR 的值。
def regression_function(arr, kmeans, cluster_svr):
result = []
clusters_pred = kmeans.predict(arr)
for i,data in enumerate(arr):
result.append(((cluster_svr[clusters_pred[i]]).predict([data]))[0])
return result,clusters
现在,我们已经构建了用于预测的端到端流,并构建了所需的模型,我们可以继续在我们的测试数据上进行尝试。让我们首先使用我们构建的 K 均值聚类来可视化测试数据的聚类,然后使用我们上面编写的函数使用相应的 SVR 来找到 Y 值。
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
# calculating Y value and cluster
Y_svr_k_means_pred, Y_clusters = regression_function(X_test,
kmeans, cluster_svr)
colors = ['red','green','blue']
plt.rcParams['figure.figsize'] = [14, 7]
plt.rc('font', size=12)
n_clusters=3
# Apply our model to clustering the remaining test set for validation
for i in range(n_clusters):
subset = []
for count,row in enumerate(X_test):
if(Y_clusters[count]==i):
subset.append(row)
x = [row[0] for row in subset]
y = [row[1] for row in subset]
plt.scatter(x,y,c=colors[i],label="cluster "+ str(i+1))
plt.title("Test Data Clusters", x=0.6, y=0.95)
plt.xlabel("X3 distance to the nearest MRT station")
plt.ylabel("X2 house age")
plt.legend()
plt.show()
Test data clusters
我们可以清楚地看到,我们已经为测试数据获得了明确的聚类,并且还获得了 Y 值,并且已经存储在 Y_svr_k_means_pred 中。让我们将结果和数据与集群一起存储到一个数据框中。
import pandas as pd
result_df = pd.DataFrame(X_test,columns=['X3 distance to the nearest MRT station','X2 house age'])
result_df['Y true value'] = Y_test
result_df['Y SVR + K means'] = Y_svr_k_means_pred
result_df['cluster'] = Y_clusters
result_df.head()
Resulting Data frame's first 5 rows
一些结果非常好,而其他的仍然比线性回归或单独的 SVR 模型所能预测的要好。这只是一个如何处理这类问题的例子,也是 K 均值和 SVR 的例子。这种方法仅在数据点分布不正确且存在集群数据处理范围时表现良好。这是一个只有两个独立变量的例子。如果数量增加,复杂度也会增加,普通算法很难处理这类情况,在这种情况下,尝试这种方法可能会有帮助。
我们这里的方法首先是可视化数据或评估是否有机会进行集群处理。然后对训练数据进行 K- Means 聚类。K-Means 可以使用剪影系数得分进行超调,这将有助于我们为我们的问题获得理想的聚类数。聚类后,我们为每个聚类构建支持向量回归,包括我们构建的所有模型的最终工作流遵循以下思想:首先预测聚类,然后使用该聚类的相应 SVR 模型预测因变量(Y)。
结论
回归是机器学习中执行的直接任务之一。尽管如此,要找到合适的算法来构建一个具有足够精度的模型以部署到生产环境中,可能会变得越来越困难。在最终确定一个算法之前,尝试一下大多数算法总是更好的。可能有这样的例子,一个简单的线性回归会创造奇迹,而不是巨大的随机森林回归。对数据及其性质的深刻理解将有助于我们轻松选择算法,可视化也有助于这一过程。本文主要探讨 K-Means 聚类和支持向量回归相结合的可能性。这种方法并不总是适合所有的回归情况,因为它强调聚类的可能性。这种问题的一个很好的例子是土壤湿度预测,因为它可能随季节和许多循环特征而变化,从而产生聚类数据点。K-Means 聚类将我们从对聚类的手动决策中解救出来,并且 SVR 非常好地处理非线性。SVR 和 K-Means 本身都有很多更深入的内容需要探索,包括内核、质心初始化等等,所以我强烈建议您阅读更多相关主题,以获得更好的模型。感谢阅读!
Swish 激活功能
在深度神经网络的数百层和数百万个参数的宏伟计划中,激活函数可能看起来是一个非常小的组件,但它们的重要性是至关重要的。激活函数不仅通过引入非线性来帮助训练,而且还有助于网络优化。自从感知器出现以来,激活函数一直是影响神经网络训练动态的关键因素。从早期的阶跃函数到目前大多数领域的默认激活,ReLU,激活函数一直是研究的重点领域。
ReLU(整流线性单元)已被广泛接受为训练深度神经网络的默认激活函数,因为它在不同任务域和网络类型中的通用性,以及在计算复杂性方面极其廉价的成本(考虑到公式本质上是\(max(0,x)\))。然而,在这篇博文中,我们看一看谷歌大脑在 2018 年提出的一篇题为“搜索激活功能的论文,这篇论文激发了对不同类型激活功能的作用的新一轮研究。该论文提出了一种称为 Swish 的新型激活函数,它是使用神经架构搜索(NAS)方法发现的,与 ReLU 或 Leaky ReLU 等标准激活函数相比,在性能上表现出显著的改善。然而,这篇博客文章不仅基于上面指定的论文,还基于 EMNLP 发表的另一篇论文,题为“是时候嗖嗖了吗?跨 NLP 任务比较深度学习激活功能”。本文本质上是对 Swish 在各种以自然语言处理为中心的任务上的经验评估。注意,在这篇博文中,我们将讨论 Swish 本身,而不是作者用来发现它的 NAS 方法。
我们将首先看看这篇论文背后的动机,然后剖析 Swish 的结构及其与 SILU (Sigmoid 加权线性单元)的相似之处。然后,我们将浏览前面提到的两篇论文的结果,最后提供一些结论性的评论和 PyTorch 代码,用 Swish 训练你自己的深度神经网络。
目录
- 动机
- 嗖嗖
- PyTorch Code
- 显著的成果
- 结论
- 参考
摘要
搜索激活功能
深度网络中激活函数的选择对训练动态和任务绩效有重要影响。目前,最成功和最广泛使用的激活函数是整流线性单元(ReLU)。尽管已经提出了各种手工设计的 ReLU 替代物,但是由于不一致的增益,没有一个能够成功地替代它。在这项工作中,我们提出利用自动搜索技术来发现新的激活功能。使用基于穷举和强化学习的搜索的组合,我们发现了多个新的激活功能。我们通过使用最佳发现的激活函数进行经验评估来验证搜索的有效性。我们的实验表明,最好的激活函数\(f(x) = x sigmoid(\beta x)\)我们称之为 Swish,在许多具有挑战性的数据集上,它往往比 ReLU 更好地工作。例如,简单地用 Swish 单元替换 ReLUs,对于 Mobile NASNet-A 和 Inception-ResNet-v2,ImageNet 上的 top-1 分类精度分别提高了 0.9%和 0.6%。Swish 的简单性和它与 ReLU 的相似性使得从业者很容易在任何神经网络中用 Swish 单元替换 ReLU。
是时候换衣服了吗?跨 NLP 任务比较深度学习激活函数
激活函数在神经网络中起着至关重要的作用,因为它们是被归因于深度学习成功故事的非线性。目前最流行的激活功能之一是 ReLU,但最近已经提出或“发现”了几个竞争对手,包括 LReLU 功能和 swish。虽然大多数工作在少数任务(通常来自图像分类)和少数竞争对手(通常是 ReLU)上比较新提出的激活函数,但我们在八个不同的 NLP 任务上对 21 个激活函数进行了第一次大规模比较。我们发现一个很大程度上未知的激活函数在所有任务中表现最稳定,即所谓的惩罚双曲正切函数。我们还表明,它可以成功地取代 LSTM 细胞中的 sigmoid 和 tanh 门,导致在具有挑战性的 NLP 任务中比标准选择提高 2 个百分点(pp)。
动机
激活函数是理解神经网络训练动力学的主要研究领域。虽然激活函数的作用是巨大的,但它们在应用前景中仅被视为一个组成部分,而在像平均场理论(MFT)这样的理论领域中,围绕激活函数的争论更加激烈。虽然 ReLU 和 Leaky ReLU 因其在公式和计算复杂性方面的简单性而占据了主导地位,但许多人已经提出了更平滑的变体来改善优化和信息传播,如 eLU 和 Softplus,但它们都只是一夜之间的奇迹,未能取代曾经如此通用的 ReLU。
在这项工作中,我们使用自动搜索技术来发现新的激活功能。我们致力于寻找新的标量激活函数,它接受一个标量作为输入,输出一个标量,因为标量激活函数可以用来代替 ReLU 函数而不改变网络结构。使用穷举和基于强化学习的搜索相结合,我们发现了一些新的激活函数,表现出良好的性能。为了进一步验证使用搜索来发现标量激活函数的有效性,我们根据经验评估了最佳发现的激活函数。最好发现的激活函数,我们称之为 Swish,是\(f(x) = x sigmoid(\beta x)\)其中\(\beta\)是一个常量或可训练参数。我们的大量实验表明,Swish 在应用于各种挑战性领域(如图像分类和机器翻译)的深度网络上始终匹配或优于 ReLU。
嗖嗖
简单来说,Swish 是 SILU 激活函数的扩展,该函数是在论文“Sigmoid-Weighted Linear Units for Neural Network Function Approximation in Reinforcement Learning”中提出的。e^{-x}}\(.的公式是$f(x)= x \ ast sigmoid(x)$,其中$sigmoid(x)= \ frac { 1 } { 1 在 Swish 公式中所做的微小修改是添加了一个可训练的\) beta \(参数,使其成为\)f(x) = x \ast sigmoid(\beta x)$参数。如上图所示,它有几个关键的独特属性,这些属性使它不同于 ReLU,并且比 ReLU 更好。首先,Swish 是一个光滑的连续函数,不像 ReLU 是一个分段线性函数。Swish 允许传播少量负权重,而 ReLU 将所有负权重的阈值设置为零。这是一个极其重要的性质,对于非单调平滑激活函数的成功至关重要,如 Swish,当用于越来越深的神经网络时。最后,可训练参数允许更好地调整激活函数以最大化信息传播并推动更平滑的梯度,这使得景观更容易优化,从而更好更快地概化。Swish 也是一种自门控激活功能,因为它通过将输入用作与自身的 sigmoid 相乘的门来调制输入,这是首次在长短期记忆(LSTMs)中引入的概念。
PyTorch Code
以下代码片段提供了 Swish 的 PyTorch 实现,其中$\ beta = 1 $,这是 SILU,因为这是使用最广泛的变体。
import torch
import torch.nn as nn
class Swish(nn.Module):
def __init__(
self,
):
"""
Init method.
"""
super(Swish, self).__init__()
def forward(self, input):
"""
Forward pass of the function.
"""
return input * torch.sigmoid(input)
要在 CIFAR 数据集上运行配备有 Swish 激活功能的 ResNet 模型(例如 ResNet-18 ),请使用以下命令。
CIFAR-10
python train_cifar_full.py --project Swish --name swish1 --version 1 --arch 1
CIFAR-100
python train_cifar100_full.py --project Swish --name swish --version 1 --arch 1
注意:您需要一个 Weights & Biases 帐户来启用 WandB Dashboard 记录。
显著的成果
结论
虽然 Swish 是一篇非常有影响力的论文,促进了对激活函数的更多研究,但激活函数 Swish 本身还不能取代 ReLU,因为与它相关的计算复杂性增加了。虽然几乎不可能推广到像 ReLU 一样具有成本效益的激活函数,但随着更平滑的激活函数变体的出现,只有时间才能证明 ReLU 是否会被淘汰。
参考
- 搜索激活功能
- 是时候换衣服了吗?跨 NLP 任务比较深度学习激活功能
解决表格数据问答的比较
本文对表格数据的自然语言问答问题进行了详细的研究。我们将介绍一些使用深度学习的方法,深入研究所用的架构和数据集,并使用合适的评估指标比较它们的性能。
回答表格数据上的问题是自然语言处理中的一个研究问题,有许多方法可以解决。有些涉及一种启发式方法来分解自然语言输入,并将其翻译为结构化查询语言可以理解的语言,而其他一些则涉及训练深度学习模型。
一个好的解决方案包括以下内容:
- 了解列和值之间的关系
- 对自然语言查询有很好的语义理解
- 保持性能、内存和跨域可解释性的效率
这就是我们在本教程中试图完成的。我们还将涵盖以下内容:
- 使用启发式方法理解表格数据
- 人工智能辅助工具与预训练模型
- TableQA:一个人工智能辅助的表格数据问答工具
- 预训练模型的流行数据集
- 塔帕斯
- 选择最佳解决方案
- 结论
使用启发式方法理解表格数据
表格数据是由行和列构成的数据。由于数据可能很大,在给定一个条件或一组条件的情况下,手动提取特定用户查询的答案并不容易。表格数据也可以在关系数据库中找到。
虽然结构化查询语言(SQL)查询通常用于从数据库中提取特定信息,但是个人对 SQL 查询的了解可能是有限的。一个更通用的解决方案是使用自然语言查询表格数据。
LN2SQL 是一种使用启发式方法将自然语言转换成 SQL 查询的方法。一组解析器用于将自然语言结构分解成各自的 SQL 组件。同义词库用于改进关键字过滤,使特定实体(如列名、SQL 关键字等)的翻译更加容易。然后将查询 SQL 输出以获取结果。
人工智能辅助工具与预训练模型
神经网络有助于以多种方式理解表格数据。例如,预先训练的模型可以预测将自然语言查询的复杂结构与表格数据相连接的模式,方法是嵌入它们并通过神经网络模型。
其他解决方案没有在包含表格信息的数据集上预先训练,而是使用人工智能和一些启发式算法来得出解决方案。这些被称为人工智能辅助工具。下面我们就来看看这样一个工具。
TableQA:一个人工智能辅助的表格数据问答工具
正如我们前面看到的,将自然语言分解成更小组件的一种方法是将它们转换成 SQL 查询。一个 SQL 查询是由几个包含条件、聚合操作等的组成语句构建而成的。
TableQA 以这种方式将自然语言查询转换成 SQL 查询。让我们来看一个如何使用这个工具查询从阿布扎比开放平台获得的[癌症死亡数据](https://github.com/abhijithneilabraham/tableQA/blob/master/tableqa/cleaned_data/Cancer Death - Data.csv)的演示。
首先,安装软件包和需求。通过 pip 安装模块:
!pip install tableqa
接下来,我们将从存储库中访问样本数据。
!git clone https://github.com/abhijithneilabraham/tableQA/
%cd tableQA/tableqa/
现在我们先来看看数据。
import pandas as pd
df = pd.read_csv("cleaned_data/Cancer Death - Data.csv")
# A first look at data
df.head()
Cancer death data
您可以看到我们试图查询的文件是一个 CSV(逗号分隔值)文件,其中包含诸如年份、国籍、性别、癌症部位、死亡人数和年龄等列。让我们试着从这个数据集中回答一些问题。
from tableqa.agent import Agent
agent = Agent(df)
#Ask a question
agent.query_db("how many deaths of age below 40 had stomach cancer?")
>>> [(24,)]
我们还可以查看与相同内容相关联的 SQL 查询。
agent.get_query("how many deaths of age below 40 had stomach cancer?")
>>> SELECT COUNT(death_count) FROM dataframe WHERE age < 40 AND cancer_site = "Stomach"
正如您在这里看到的,在列Death Count
上有一个计数操作,条件是age<40
和Cancer Site="Stomach"
。
TableQA 使用支持深度学习的实体提取器和聚合子句分类器的组合来构建 SQL 查询。实体提取器通过将列映射到它们各自的值(如果问题中有值的话)来匹配条件。它使用在班上训练的预先训练的问题回答模型来帮助容易地从输入问题中定位列的值。
aggregate clause 分类器是一个两层深度学习模型,它有助于在以下任何方面对输入问题的性质进行分类:
- 挑选
- 数数
- 最高的
- 最低限度
- 平均的
- 总和
将实体提取和子句分类与一些试探法的帮助相结合将生成 SQL 查询,然后使用该查询来查询数据。这种方法的一个可能的优势是,添加任何新功能都不需要重新培训——一些定制修复可以通过修改代码,用更精确的模型替换内部的人工智能模型等来完成。
预训练模型的流行数据集
使用启发式的解决方案有其自身的局限性。问题复杂性的增加不容易用显式编程来解决。
例如,我们需要能够回答需要多个步骤的重要问题,比如:“今年的 F1 锦标赛谁获得了第一名?”或者“明天早上出发的最便宜的交通方式是什么?”
用包含这种问题的数据集预训练的模型可以用在需要类似人类的推理的情况下。
WikiTableQuestions 是一个数据集,由复杂的问题和维基百科的表格组成。该数据集包含来自各种主题的 2108 个表格(涵盖更多领域知识)和不同复杂性的 22033 个问题。
微软提供了一个顺序问答数据集,也被称为 SQA 。回答一系列问题的任务是在 HTML 表格上完成的。SQA 是通过分解来自维基问答(WTQ)的 2022 个问题创建的。每个 WTQ 问题被分解成三个,产生一个 6,066 个序列的数据集,总共包含 17,553 个问题。每个问题还与表格中单元格位置形式的答案相关联。
这项任务的另一个流行数据集是 WikiSQL ,这是一个由 80654 对人工注释的问题和 SQL 查询组成的数据集,分布在维基百科的 24241 个表中。与上面的其他数据集不同,WikiSQL 包含从带有注释组件的问题映射而来的 SQL 查询。
TAPAS:通过预训练的弱监督表解析
2020 年,谷歌研究开源了一个流行的深度学习模型,用于查询表格数据,而不生成逻辑表单,称为 TAPAS。它将屏蔽语言建模(MLM)方法扩展到结构化数据。
像 BERT 一样,TAPAS 使用必须对表格输入进行编码的特性。然后,它初始化对端到端训练的文本序列和表格的联合预训练,并且能够成功地恢复被屏蔽的单词和表格单元。它可以通过选择表格单元格的子集从查询中进行推断。如果可用,聚合操作也在它们之上执行。使用 TAPAS 对各种数据集进行了基准测试,包括 WTQ、SQA 和 WIKISQL。
让我们看一个例子。
首先,安装要求:
!pip install tapas-table-parsing
将模型文件从云中复制到我们的机器上:
!gsutil cp gs://tapas_models/2020_04_21/tapas_sqa_base.zip . && unzip tapas_sqa_base.zip
我们将创建一个 predict()函数,以数据帧的形式输入表格:
import tensorflow.compat.v1 as tf
import os
import shutil
import csv
import pandas as pd
import IPython
tf.get_logger().setLevel('ERROR')
from tapas.utils import tf_example_utils
from tapas.protos import interaction_pb2
from tapas.utils import number_annotation_utils
from tapas.scripts import prediction_utils
os.makedirs('results/sqa/tf_examples', exist_ok=True)
os.makedirs('results/sqa/model', exist_ok=True)
with open('results/sqa/model/checkpoint', 'w') as f:
f.write('model_checkpoint_path: "model.ckpt-0"')
for suffix in ['.data-00000-of-00001', '.index', '.meta']:
shutil.copyfile(f'tapas_sqa_base/model.ckpt{suffix}', f'results/sqa/model/model.ckpt-0{suffix}')
max_seq_length = 512
vocab_file = "tapas_sqa_base/vocab.txt"
config = tf_example_utils.ClassifierConversionConfig(
vocab_file=vocab_file,
max_seq_length=max_seq_length,
max_column_id=max_seq_length,
max_row_id=max_seq_length,
strip_column_names=False,
add_aggregation_candidates=False,
)
converter = tf_example_utils.ToClassifierTensorflowExample(config)
def convert_interactions_to_examples(tables_and_queries):
"""Calls Tapas converter to convert interaction to example."""
for idx, (table, queries) in enumerate(tables_and_queries):
interaction = interaction_pb2.Interaction()
for position, query in enumerate(queries):
question = interaction.questions.add()
question.original_text = query
question.id = f"{idx}-0_{position}"
for header in table[0]:
interaction.table.columns.add().text = header
for line in table[1:]:
row = interaction.table.rows.add()
for cell in line:
row.cells.add().text = cell
number_annotation_utils.add_numeric_values(interaction)
for i in range(len(interaction.questions)):
try:
yield converter.convert(interaction, i)
except ValueError as e:
print(f"Can't convert interaction: {interaction.id} error: {e}")
def write_tf_example(filename, examples):
with tf.io.TFRecordWriter(filename) as writer:
for example in examples:
writer.write(example.SerializeToString())
def predict(table_data, queries):
table=[list(table_data.columns)]+table_data.values.tolist()
examples = convert_interactions_to_examples([(table, queries)])
write_tf_example("results/sqa/tf_examples/test.tfrecord", examples)
write_tf_example("results/sqa/tf_examples/random-split-1-dev.tfrecord", [])
! python -m tapas.run_task_main \
--task="SQA" \
--output_dir="results" \
--noloop_predict \
--test_batch_size={len(queries)} \
--tapas_verbosity="ERROR" \
--compression_type= \
--init_checkpoint="tapas_sqa_base/model.ckpt" \
--bert_config_file="tapas_sqa_base/bert_config.json" \
--mode="predict" 2> error
results_path = "results/sqa/model/test_sequence.tsv"
all_coordinates = []
df = pd.DataFrame(table[1:], columns=table[0])
display(IPython.display.HTML(df.to_html(index=False)))
print()
with open(results_path) as csvfile:
reader = csv.DictReader(csvfile, delimiter='\t')
for row in reader:
coordinates = prediction_utils.parse_coordinates(row["answer_coordinates"])
all_coordinates.append(coordinates)
answers = ', '.join([table[row + 1][col] for row, col in coordinates])
position = int(row['position'])
print(">", queries[position])
print(answers)
return all_coordinates
现在,让我们使用一个示例表,并将其作为 dataframe 加载。它由包含演员姓名和他们出演的电影数量的列组成。
data = {'Actors': ["Brad Pitt", "Leonardo Di Caprio", "George Clooney"], 'Number of movies': ["87", "53", "69"]}
queries = ["Who played less than 60 movies", "How many movies has George Clooney played in?"]
table = pd.DataFrame.from_dict(data)
result = predict(table, queries)
从表中可以看出模型推断如下:
> Who played less than 60 movies
Leonardo Di Caprio
> How many movies has George Clooney played in?
69
然而,使用 TAPAS 有一定的限制。它将单个表作为上下文来处理,这些表可以放在内存中。这使得它不适合大型数据集或多个数据库。此外,需要多个聚合操作的问题是不可能的,例如:“有多少演员的平均电影数高于 10?”
选择最佳解决方案
这个任务的好的解决方案取决于各种因素和用户偏好。
使用试探法的非深度学习解决方案是最快的,但它们的代价是语义理解能力差。
像 TAPAS 这样的深度学习模型可以帮助理解自然语言问题和表格结构的复杂语义,但是它会带来内存问题和昂贵的计算要求。
像 TableQA 这样的人工智能辅助解决方案在使用启发式方法方面具有优势,因为在大多数情况下,可以修改启发式方法以提高解决方案的性能,而不必重新训练深度学习模型。但是随着自然语言变得越来越复杂,从表中检索信息时会出现更多的错误。
结论
在本文中,我们介绍了针对表格数据问答的各种解决方案。我们还看到了不同的解决方案,它们使用启发式和深度学习从语义上解析从表格数据和自然语言问题中组合的信息。选择最佳解决方案需要考虑各种因素:速度、理解自然语言的能力以及记忆。
为了教授人工智能和机器学习,用正确的工具、课程和观点来满足学生
原文:https://blog.paperspace.com/teach-machine-learning-alex-castrounis/
Alex Castrounis 是人工智能的 Why 的创始人兼首席执行官,也是《人工智能对人和商业 的 的作者。他还是西北大学凯洛格/麦考密克 MBA 项目的兼职人员,在那里他创建并教授该项目的核心人工智能和机器学习课程。
除了担任前 INDYCAR 工程师、竞赛策略师和数据科学家之外,Alex 在过去二十年中还为各种规模的企业(从初创公司到财富 100 强企业)提供建议,告诉他们如何使用数据、分析和技术来推动业务和客户的成功。
我们很高兴能够与 Alex 坐下来谈谈他通过在线课程、现场研讨会和西北大学 MBA 项目教授人工智能的丰富经验。
让我们开始吧!
纸空间 。非常感谢您今天和我们连线。我们知道你在一段时间里一直在向处于不同学习阶段的人教授机器学习和人工智能。关于教 AI,你现在知道什么,你希望你在开始的时候就知道?
。很棒的问题!这也是我的公司被命名为AI的原因之一!比如:AI 为什么会存在?AI 为什么重要?为什么人工智能是有益的和/或潜在有害的?为什么公司需要更多地了解 AI,并负责任地应用它?为什么个人需要获得 AI 素养和流畅度?诸如此类。
正确回答这些问题对于创建一个成功和负责任的人工智能愿景和战略至关重要,不管你是谁,也不管你有什么背景。
很多人工智能和机器学习教育都是非常技术性的,面向从业者,所以结果是大多专注于什么和如何。我在教学中的一个主要目标是重点关注“为什么”,以帮助人们理解这些技术如何应用于现实世界的问题、用例以及特定行业和业务职能中的应用。
对于我通常教授的观众来说,这往往比围绕机器学习算法、建模技术和其他相关技术主题的深层技术细节更重要。
*
Why of AI is one of Castrounis' primary AI instruction avenues.*
纸空间 。你最喜欢的人工智能教材有哪些不是你自己的?你对其他人如何教授机器学习有特别的欣赏吗?
。我创作了大部分的教学材料,尽管我确实有一些自己不喜欢的。
对于那些想动手做 AI/ML 开发工作的人来说,我非常相信直接从 API 文档中学习。这可能包括语言、库、框架和平台。我认为没有比一头扎进去、亲自动手更好的学习方法了,尤其是学习如何直接从官方文档中阅读、理解和实现工作解决方案。
对于人工智能和机器学习领域最新最棒的研究和更新,arXiv 和 T2 的论文都是很好的资源。我也很喜欢内森·贝纳奇和伊恩·霍加斯的《人工智能状况报告》,以及斯坦福大学以人为中心的人工智能研究所(HAI)的《人工智能指数报告》。
有一些很棒的交互式人工智能和机器学习工具可以帮助演示这些技术实际上可以做什么。用 Transformer 编写和 T2 的 OpenAI API 都是很好的例子,尽管还有很多其他的例子。
至于对其他人如何教授机器学习的欣赏,我真的很喜欢两分钟论文和 Jay Alammar 创造的许多伟大的机器学习可视化。
**
Why of AI provides individual coursework options.**
纸空间 。你会给第一次学习机器学习的人推荐什么?什么样的建议对刚起步的人有帮助?
。这真的取决于你对机器学习的目标是什么。如果你的学习目标是成为一名数据科学家、机器学习工程师或相关角色,我建议你学习 Python 这样的编程语言,以及 Jupyter Notebooks 这样广泛使用的 ide。在此基础上,我建议您开始尝试一些流行的工具,如 Pandas、Scikit-learn、PyTorch、Hugging Face 等等。
我也强烈建议找出你最感兴趣的机器学习领域,并以此为指导。机器学习是一个广阔的领域,横跨许多领域,如预测分析、个性化、推荐系统、计算机视觉、自然语言(NLP、NLG、NLU)、对话式人工智能等。我建议选择一个最有趣的机器学习子领域,并学习如何使用该领域的相关数据、工具和任务,而不是尝试一下子学会所有东西。
对于那些对学习更多用于商业和决策目的的机器学习感兴趣的人,我建议熟悉一下我所教的人工智能和机器学习的“景观”。这意味着减少对特定学习算法、建模技术和工具的关注,而是关注 AI/ML 的所有不同领域(例如,计算机视觉、自然语言、预测分析)以及每个领域中可以完成的许多任务(例如,对象检测、情感分析、语义相似性)。
这一切都是为了将输入(通常是数据)映射到输出(例如现实世界的结果和解决方案)。关键是要学会如何在高度有影响力和负责任的真实世界用例及应用中识别使用人工智能和机器学习的机会。
纸质空间 。除了机器学习学生,你还教商业学生。这将如何改变课程?当提到 ML 时,对商科学生(以及相关的教职员工)来说,什么是最重要的?
。我喜欢这个问题,它涉及到我在工作中花费了大量时间和精力来回答的一些问题。它不仅与我的西北大学凯洛格商学院 MBA 课程和学生有关,还与为什么进行人工智能培训和课程、我的书 人工智能与人和商业 以及我的总体咨询工作有关。
我的主要关注点是帮助商业人士和决策者更好地理解和应用负责任的人工智能和机器学习,以便为他们的组织、客户和用户创造积极的影响和利益。在许多情况下,我建议或教导那些可能没有任何技术专长的人。
挑战在于,对商业人士来说最重要的东西往往与从业者大相径庭。例如,从业者通常非常关注特定的学习算法和建模技术,如变压器、神经网络、随机森林、XGboost 等等。
然而,从商业角度来看,算法本身并不像潜在的结果和影响那样相关,例如 ROI、社会或环境影响、效率提高、客户保留或成本降低。同样,风险、财务和无形成本,以及与解决方案商业化或投入生产相关的其他关键考虑因素,如公平、安全和责任,也是至关重要的。
换句话说,商业人士通常希望知道他们如何在今天的特定行业或业务功能中使用人工智能和机器学习,包括他们如何快速开发真实世界的人工智能/人工智能用例及应用程序以实现某些目标,同时最大限度地降低和减轻风险。
教授商科学生和创建相关课程都是为了弥合技术差距,同时也帮助学生获得适当程度的 ML 读写能力、理解和欣赏。
**
"图纸空间渐变...使学生能够直接投入到不受约束的开发工作中,而不是花费大量时间开发和构建定制的云环境。”
亚历克斯·卡斯特罗尼斯(Alex Castrounis),人工智能为何的首席执行官**
纸空间 。您提到您将 Paperspace 引入了您的课程,Paperspace 如何帮助您实现课程目标?
。在教授人工智能和机器学习时,我希望确保学习者不会受到缺乏数据存储或计算相关资源的限制。
因此,我认为 Paperspace Gradient platform 是一个很好的开发资源,在时间和成本方面促进了高效的学习。它使学生能够直接投入到不受约束的开发工作中,而不是花费大量时间开发和构建定制的云环境。由于此类环境的设置和管理可能非常耗时,并且需要基础架构专业知识和经验,因此拥有一个使这一切变得简单的环境会很方便。
梯度笔记本尤其是一个伟大的合作工具,学生执行快速原型和开发。拥有预安装了库和框架的基础环境来帮助加速开发工作是非常好的。学生还可以根据给定任务的需要轻松更改计算资源。
***
A sample of Why of AI courses.***
纸空间 。如果我们的读者想开始学习《为什么是人工智能》或你的其他课程,他们最好的入门方式是什么?他们有什么特别需要检查的吗?
。对于那些有兴趣开始他们的人工智能之旅的人,我们设计并构建了一个面向非从业者的指导方法,以理解并应用人工智能的原因。
请访问我们在https://learn.whyofai.com的学习平台,了解更多关于人工智能课程和认证的原因。在那里,个人可以直接注册课程和课程包,我们为五人或五人以上的团队提供折扣选项!
我们还提供现场研讨会和定制培训,读者可以在 https://www.whyofai.com/ai-training 了解更多。
对于那些 DIY 的人来说,人工智能的诞生源于我的研究和见解,这些研究和见解来自我的职业经历和我写这本书的过程, 《人工智能与商业:一个更好的人类体验和商业成功的框架 》。对于那些喜欢用书的方式学习人工智能以及如何将人工智能用于商业的人来说,这绝对是一个很好的选择。
团队!
我们的许多用户在团队(在公司、大学等)中使用 Paperspace。)在项目上合作。一个被高度要求的功能是能够在 Paperspace 中构建这些团队,现在只需几次点击就可以实现。
如果您曾经不得不管理多个 Paperspace 帐户,或者需要成为不同团队的成员,您将会喜欢这个新功能。
使用团队的一些主要原因包括:
- 立即邀请朋友和同事加入您的 Paperspace
- 共享机器、笔记本电脑、工作和存储等资源
- 整合您团队的帐单,并在一个位置管理您的订阅
几个音符
过去被称为“团队办公空间”的东西,现在变成了企业办公空间。业务团队将维护他们现有的所有基础设施(专用网络、共享驱动器、VPN 等),对团队的更改不会出现在他们或他们的用户帐户中。用户仍然可以从控制台中请求业务功能。
该功能目前在技术上仍处于测试阶段,但它是稳定的(当我们添加更多的铃铛和哨子时,我们会删除测试标志)。
您可以在这里探索我们的帮助中心的团队部分。
💗PS 工程团队
TensorFlow 2.0 in Action
TensorFlow 是用于深度学习项目的最受欢迎的框架之一,即将推出一个主要的新版本- TensorFlow 2.0。幸运的是,我们不必等待官方发布。测试版可在官方网站上进行测试,您也可以在 Paperspace Gradient 上使用预配置的模板。在本教程中,我们将介绍 TensorFlow 2.0 中的一些新的主要功能,以及如何在深度学习项目中利用它们。这些特性是急切执行、tf.function 装饰器和新的分布接口。本教程假设您熟悉 TensorFlow、Keras API 和创成式模型。
为了演示 TensorFlow 2.0 的功能,我们将实现一个 GAN 模型。我们将在这里实现的 GAN 论文是 MSG-GAN :用于稳定图像合成的多尺度梯度 GAN。这里,发生器产生多个不同分辨率的图像,鉴别器决定给予它的多个分辨率。通过让生成器生成多分辨率图像,我们确保整个网络中的潜在特征与输出图像相关。
数据集设置
训练网络的第一步是启动数据管道。这里,我们将使用时尚 MNIST 数据集,并使用已建立的数据集 API 来创建张量流数据集。
def mnist_dataset(batch_size):
#fashion MNIST is a drop in replacement for MNIST that is harder to solve
(train_images, _), (_, _) = tf.keras.datasets.fashion_mnist.load_data()
train_images = train_images.reshape([-1, 28, 28, 1]).astype('float32')
train_images = train_images/127.5 - 1
dataset = tf.data.Dataset.from_tensor_slices(train_images)
dataset = dataset.map(image_reshape)
dataset = dataset.cache()
dataset = dataset.shuffle(len(train_images))
dataset = dataset.batch(batch_size, drop_remainder=True)
dataset = dataset.prefetch(1)
return dataset
#Function for reshaping images into the multiple resolutions we will use
def image_reshape(x):
return [
tf.image.resize(x, (7, 7)),
tf.image.resize(x, (14, 14)),
x
]
TensorFlow 2.0 中实现的急切执行消除了初始化变量和创建会话的需要。有了热切的执行,我们现在可以以更 pythonic 化的方式使用 TensorFlow,并随时进行调试。这扩展到了 TensorFlow 中的数据集 api,使我们能够通过迭代与数据管道交互。
# use matplotlib to plot a given tensor sample
def plot_sample(sample):
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
num_samples = min(NUM_EXAMPLES, len(sample[0]))
grid = gridspec.GridSpec(num_res, num_samples)
grid.update(left=0, bottom=0, top=1, right=1, wspace=0.01, hspace=0.01)
fig = plt.figure(figsize=[num_samples, 3])
for x in range(3):
images = sample[x].numpy() #this converts the tensor to a numpy array
images = np.squeeze(images)
for y in range(num_samples):
ax = fig.add_subplot(grid[x, y])
ax.set_axis_off()
ax.imshow((images[y] + 1.0)/2, cmap='gray')
plt.show()
# now lets plot a sample
dataset = mnist_dataset(BATCH_SIZE)
for sample in dataset: # the dataset has to fit in memory with eager iteration
plot_sample(sample)
Random Sample of the Fashion MNIST Dataset
模型设置
既然数据集已经制作并验证完毕,我们就可以继续创建生成器和鉴别器模型了。在 2.0 中,Keras 接口是所有深度学习的接口。这意味着发生器和鉴别器是像任何其他 Keras 模型。这里我们将制作一个标准的生成器模型,它有一个噪声向量输入和三个输出图像,从最小到最大排序。
def generator_model():
outputs = []
z_in = tf.keras.Input(shape=(NOISE_DIM,))
x = layers.Dense(7*7*256)(z_in)
x = layers.BatchNormalization()(x)
x = layers.LeakyReLU()(x)
x = layers.Reshape((7, 7, 256))(x)
for i in range(3):
if i == 0:
x = layers.Conv2DTranspose(128, (5, 5), strides=(1, 1),
padding='same')(x)
x = layers.BatchNormalization()(x)
x = layers.LeakyReLU()(x)
else:
x = layers.Conv2DTranspose(128, (5, 5), strides=(2, 2),
padding='same')(x)
x = layers.BatchNormalization()(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128, (5, 5), strides=(1, 1), padding='same')(x)
x = layers.BatchNormalization()(x)
x = layers.LeakyReLU()(x)
outputs.append(layers.Conv2DTranspose(1, (5, 5), strides=(1, 1),
padding='same', activation='tanh')(x))
model = tf.keras.Model(inputs=z_in, outputs=outputs)
return model
接下来,我们制作鉴别器模型。
def discriminator_model():
# we have multiple inputs to make a real/fake decision from
inputs = [
tf.keras.Input(shape=(28, 28, 1)),
tf.keras.Input(shape=(14, 14, 1)),
tf.keras.Input(shape=(7, 7, 1))
]
x = None
for image_in in inputs:
if x is None:
# for the first input we don't have features to append to
x = layers.Conv2D(64, (5, 5), strides=(2, 2),
padding='same')(image_in)
x = layers.LeakyReLU()(x)
x = layers.Dropout(0.3)(x)
else:
# every additional input gets its own conv layer then appended
y = layers.Conv2D(64, (5, 5), strides=(2, 2),
padding='same')(image_in)
y = layers.LeakyReLU()(y)
y = layers.Dropout(0.3)(y)
x = layers.concatenate([x, y])
x = layers.Conv2D(128, (3, 3), strides=(1, 1), padding='same')(x)
x = layers.LeakyReLU()(x)
x = layers.Dropout(0.3)(x)
x = layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same')(x)
x = layers.LeakyReLU()(x)
x = layers.Dropout(0.3)(x)
x = layers.Flatten()(x)
out = layers.Dense(1)(x)
inputs = inputs[::-1] # reorder the list to be smallest resolution first
model = tf.keras.Model(inputs=inputs, outputs=out)
return model
# create the models and optimizers for later functions
generator = generator_model()
discriminator = discriminator_model()
generator_optimizer = tf.keras.optimizers.Adam(1e-3)
discriminator_optimizer = tf.keras.optimizers.Adam(1e-3)
用 tf.functions 训练
创建了生成器和鉴别器模型后,进行训练的最后一步是构建我们的训练循环。我们不会在这里使用 Keras 模型拟合方法来展示定制训练循环如何与 tf.functions 和分布式训练一起工作。tf.function 装饰器是 TensorFlow 2.0 中最有趣的工具之一。Tf.functions 获取给定的本机 python 函数,并将其签名到 TensorFlow 执行图上。与使用传统的 python 函数相比,这提供了性能提升,传统的 python 函数必须使用上下文切换,而不能利用图优化。要获得这种性能提升,有许多注意事项。性能下降最大的是将 python 对象作为参数传递,而不是 TensorFlow 类。考虑到这一点,我们可以使用函数装饰器创建自定义的训练循环和损失函数。
# this is the custom training loop
# if your dataset cant fit into memory, make a train_epoch tf.function
# this avoids the dataset being iterarted eagarly which can fill up memory
def train(dataset, epochs):
for epoch in range(epochs):
for image_batch in dataset:
gen_loss, dis_loss = train_step(image_batch)
# prediction of 0 = fake, 1 = real
@tf.function
def discriminator_loss(real_output, fake_output):
real_loss = tf.nn.sigmoid_cross_entropy_with_logits(
tf.ones_like(real_output), real_output)
fake_loss = tf.nn.sigmoid_cross_entropy_with_logits(
tf.zeros_like(fake_output), fake_output)
return tf.nn.compute_average_loss(real_loss + fake_loss)
@tf.function
def generator_loss(fake_output):
loss = tf.nn.sigmoid_cross_entropy_with_logits(
tf.ones_like(fake_output), fake_output)
return tf.nn.compute_average_loss(loss)
@tf.function
def train_step(images):
noise = tf.random.normal([BATCH_SIZE, NOISE_DIM])
#gradient tapes keep track of all calculations done in scope and create the
# gradients for these
with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
generated_images = generator(noise, training=True)
real_output = discriminator(images, training=True)
fake_output = discriminator(generated_images, training=True)
gen_loss = generator_loss(fake_output)
dis_loss = discriminator_loss(real_output, fake_output)
gradients_of_generator = gen_tape.gradient(gen_loss,
generator.trainable_variables)
gradients_of_discriminator = disc_tape.gradient(dis_loss,
discriminator.trainable_variables)
generator_optimizer.apply_gradients(zip(gradients_of_generator,
generator.trainable_variables))
discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator,
discriminator.trainable_variables))
return gen_loss, dis_loss
# lets train
train(dataset, 300)
MSG-GAN Training for 300 Epochs
2.0 中的分布
在我们的自定义训练循环建立之后,是时候将它分布在多个 GPU 上了。在我看来,新的专注于策略的分布式 API 是 2.0 中最令人兴奋的特性。这也是最具实验性的,并不是所有的发行版特性目前都被每个场景所支持。使用分布式 API 很简单,只需要对当前代码做一些修改。首先,我们必须选择我们想要用于分布式培训的策略。这里我们将使用镜像策略。这种策略将工作分配给单台机器上的可用 GPU。还有其他策略在工作中,但这是目前唯一支持自定义训练循环的策略。使用策略很简单;选择策略,然后将代码放入范围内。
strategy = tf.distribute.MirroredStrategy()
with strategy.scope():
# move all code we want to distribute in scope
# model creation, loss function, train functions
这个界面很好用,这是使用 Keras fit 方法训练时唯一需要做的改动。我们正在使用一个自定义的训练循环,所以我们仍然有一些修改要做。首先需要修复数据集。数据集创建的每个训练批次将被拆分到每个 GPU 上。分布在 4 个 GPU 上的批量大小为 256 的数据集将在每个 GPU 上放置批量大小为 64 的数据。我们需要调整数据集的批处理大小,以使用全局批处理大小,而不是我们想要的每个 GPU 的批处理大小。还需要使用一个实验函数来准备用于分发的数据集。
NUM_GPUS = strategy.num_replicas_in_sync
dataset = mnist_dataset(BATCH_SIZE * NUM_GPUS)
# we have to use this experimental method to make the dataset distributable
dataset = strategy.experimental_distribute_dataset(dataset)
最后一步是用分布式训练函数包装训练阶跃函数。这里我们必须使用另一个实验函数。这个需要我们给它一个非 tf.function 来分配。
@tf.function
def distributed_train(images):
gen_loss, dis_loss = strategy.experimental_run_v2(
# remove the tf functions decorator for train_step
train_step, args=(images,))
# this reduces the return losses onto one device
gen_loss = strategy.reduce(tf.distribute.ReduceOp.MEAN, gen_loss, axis=None)
dis_loss = strategy.reduce(tf.distribute.ReduceOp.MEAN, dis_loss, axis=None)
return gen_loss, dis_loss
# also change train() to use distributed_train instead of train_step
def train(dataset, epochs):
for epoch in range(epochs):
for image_batch in dataset:
gen_loss, dis_loss = distributed_train(image_batch)
现在,当我运行分配程序时,我得到了以下 1070 的数字。
- 1 个 GPU:平均 200.26 毫秒/图像
- 3 个 GPU:平均 39.27 毫秒/图像
我们预计性能会提高 3 倍,而不是 5 倍,但是,嘿,这只是测试版。
现在,我们已经使用了急切执行来检查数据管道,使用 tf.functions 进行训练,并使用带有自定义丢失函数的新分布式 api。
享受探索和使用 TensorFlow 2.0 的乐趣。提醒一下,您可以使用 TensorFlow 2.0 和所有必要的库、驱动程序(CUDA、cuDNN 等)启动一个支持 GPU 的实例。)只需点击几下鼠标。
Launching a TensorFlow 2.0 instance in Gradient
TensorFlow 回调指南
如果你正在建立深度学习模型,你可能需要坐几个小时(甚至几天)才能看到任何真正的结果。您可能需要停止模型训练以更改学习率,将训练日志推送到数据库以备将来使用,或者在 TensorBoard 中显示训练进度。看起来我们可能需要做很多工作来完成这些基本任务——这就是 TensorFlow 回调的作用。
在本文中,我们将介绍 TensorFlow 回调的细节、用法和示例。这篇文章的大纲如下:
- 什么是回调函数?
- 当回调被触发时
- TensorFlow 2.0 中可用的回调
- 结论
你也可以在 ML Showcase 上运行完整的代码。
什么是回调函数?
简单地说,回调是在训练过程的给定阶段训练期间执行的特殊实用程序或函数。回调可以帮助你防止过度拟合,可视化训练进度,调试你的代码,保存检查点,生成日志,创建一个 TensorBoard 等等。TensorFlow 中有许多回调函数,您可以使用多个。我们将看看不同的回调函数以及它们的使用示例。
当回调被触发时
当某个事件被触发时,回调被调用。在训练期间有几种类型的事件可以导致回调的触发,例如:
on_epoch_begin
:顾名思义,当一个新的纪元开始时,该事件被触发。
on_epoch_end
:当一个时期结束时触发。
on_batch_begin
:新一批通过训练时触发。
on_batch_end
:当一批训练结束。
on_train_begin
:训练开始时。
on_train_end
:训练结束时。
要在模型训练中使用任何回调,您只需要在model.fit
调用中传递回调对象,例如:
model.fit(x, y, callbacks=list_of_callbacks)
TensorFlow 2.0 中可用的回调
让我们看看tf.keras.callbacks
模块下可用的回调。
1.提前停止
这个回调用的很频繁。这允许我们监控我们的度量,并且当它停止改进时停止模型训练。比如,假设你想在精度没有提高 0.05 的情况下停止训练;您可以使用这个回调来实现这一点。在某种程度上,这有助于防止模型过度拟合。
tf.keras.callbacks.EarlyStopping(monitor='val_loss',
min_delta=0,
patience=0,
verbose=0,
mode='auto',
baseline=None,
restore_best_weights=False)
monitor
:我们想要监控的指标的名称。
min_delta
:我们在每个纪元中期望的最小改进量。
patience
:停止训练前等待的历元数。
verbose
:是否打印附加日志。
mode
:定义被监控的指标是增加、减少,还是从名称中推断;可能的值有'min'
、'max'
或'auto'
。
baseline
:被监控指标的值。
否则,它将获得最后一个纪元的权重。
通过用于训练的on_epoch_end
触发器执行EarlyStopping
回调。
2.模型检查点
这个回调允许我们在训练期间定期保存模型。这在训练需要长时间训练的深度学习模型时尤其有用。这个回调监视训练,并根据度量定期保存模型检查点。
tf.keras.callbacks.ModelCheckpoint(filepath,
monitor='val_loss',
verbose=0,
save_best_only=False,
save_weights_only=False,
mode='auto',
save_freq='epoch')
filepath
:保存模型的路径。你可以用格式选项传递文件路径,比如model-{epoch:02d}-{val_loss:0.2f}
;这将使用名称中提到的值保存模型。
monitor
:要监视的度量的名称。
save_best_only
:如果True
,最佳模型不会被覆盖。
mode
:定义被监控的指标是增加、减少还是从名称中推断;可能的值有'min'
、'max'
或'auto'
。
save_weights_only
:如果True
,则只保存模型的重量。否则将保存整个模型。
save_freq
:如果'epoch'
,模型将在每个历元后保存。如果传递了一个整数值,模型将在整数个批次之后保存(不要与 epochs 混淆)。
通过训练的on_epoch_end
触发器执行ModelCheckpoint
回调。
3.张量板
如果您想要可视化您的模型的培训总结,这是最好的回调之一。这个回调为 TensorBoard 生成日志,您可以稍后启动它来可视化您的训练进度。我们将在另一篇文章中详细介绍 TensorBoard。
> tf.keras.callbacks.TensorBoard(log_dir='logs',
histogram_freq=0,
write_graph=True,
write_images=False,
update_freq='epoch',
profile_batch=2,
embeddings_freq=0,
embeddings_metadata=None,
**kwargs)
现在我们将只看到一个参数log_dir
,它是您需要存储日志的文件夹的路径。要启动 TensorBoard,您需要执行以下命令:
tensorboard --logdir=path_to_your_logs
您可以在开始训练之前或之后启动 TensorBoard。
TensorBoard
TensorBoard 回调也在on_epoch_end
触发。
4.学习率计划程序
在用户希望随着训练的进行更新学习率的情况下,这个回调非常方便。例如,随着训练的进行,你可能想在一定数量的时期后降低学习率。LearningRateScheduler
会让你做到这一点。
tf.keras.callbacks.LearningRateScheduler(schedule, verbose=0)
schedule
:这是一个采用纪元索引并返回新学习率的函数。
verbose
:是否打印附加日志。
下面是一个如何在三个纪元后降低学习率的例子。
Function to pass to the 'schedule' parameter for the LearningRateScheduler callback
正如您在下面的输出中所看到的,在第四个纪元之后,学习率已经降低。verbose
已被设置为1
以跟踪学习率。
In epoch 5 learning rate drops to 0.0002 from 0.002
该回调也在on_epoch_end
触发。
5.CSVLogger
顾名思义,这个回调将训练细节记录在一个 CSV 文件中。记录的参数有epoch
、accuracy
、loss
、val_accuracy
和val_loss
。需要记住的一点是,在编译模型时,您需要将accuracy
作为一个度量传递,否则您将得到一个执行错误。
tf.keras.callbacks.CSVLogger(filename,
separator=',',
append=False)
记录器接受filename
、separator
和append
作为参数。append
定义是否追加到现有文件,或写入新文件。
通过训练的on_epoch_end
触发器执行CSVLogger
回调。因此,当一个时期结束时,日志被放入一个文件中。
6.LambdaCallback
当您需要在任何事件上调用一些自定义函数时,这个回调是必需的,而提供的回调是不够的。例如,假设您想将日志放入数据库。
tf.keras.callbacks.LambdaCallback(on_epoch_begin=None,
on_epoch_end=None,
on_batch_begin=None,
on_batch_end=None,
on_train_begin=None,
on_train_end=None,
**kwargs)
这个回调函数的所有参数都需要一个函数,它接受这里指定的参数:
on_epoch_begin
和on_epoch_end
:历元、日志
on_batch_begin
和on_batch_end
:批处理、日志
on_train_begin
和on_train_end
:日志
让我们看一个例子:
Function to put logs in a file at end of a batch
该回调将在批处理后将日志放入一个文件中。您可以在文件中看到的输出是:
Logs generated
所有事件都会调用这个回调,并根据传递的参数执行自定义函数。
7.ReduceLROnPlateau
当指标停止改善时,如果您想要更改学习率,可以使用这个回调。与LearningRateScheduler
相反,它将减少基于度量(而不是纪元)的学习。
tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss',
factor=0.1,
patience=10,
verbose=0,
mode='auto',
min_delta=0.0001,
cooldown=0,
min_lr=0,
**kwargs)
许多参数与EarlyStoppingCallback
相似,所以让我们把重点放在那些不同的参数上。
monitor
、patience
、verbose
、mode
、min_delta
:这些与EarlyStopping
类似。
factor
:学习率应该减少的因子(新学习率=旧学习率*因子)。
cooldown
:重新开始监视度量之前等待的时期数。
min_lr
:学习率的最小界限(学习率不能低于此)。
这个回调也在on_epoch_end
事件中被调用。
8.远程监控
当您想要将日志发布到 API 时,这个回调非常有用。这个回调也可以使用LambdaCallback
来模拟。
tf.keras.callbacks.RemoteMonitor(root='http://localhost:9000',
path='/publish/epoch/end/',
field='data',
headers=None,
send_as_json=False)
root
:这是网址。
path
:这是端点名称/路径。
field
:这是拥有所有日志的键的名字。
header
:需要发送的表头。
send_as_json
:如果True
,数据将以 JSON 格式发送。
例如:
Callback
要查看回调的工作情况,您需要在 localhost:8000 上托管一个端点。您可以使用 Node.js 来做到这一点。将代码保存在文件 server.js 中:
然后通过键入node server.js
启动服务器(您应该已经安装了 node)。在纪元结束时,您将在节点控制台中看到日志。如果服务器没有运行,那么您将在 epoch 结束时收到警告。
这个回调也在on_epoch_end
事件中被调用。
9.基线日志和历史
这两个回调自动应用于所有 Keras 模型。history
对象由model.fit
返回,并且包含一个字典,该字典具有各时期的平均准确度和损失。parameters
属性包含带有用于训练的参数的字典(epochs
、steps
、verbose
)。如果你有一个改变学习率的回调函数,那么它也是历史对象的一部分。
Output of model_history.history
BaseLogger
累积各个时期的平均指标。因此,您在时段末尾看到的指标是所有批次的所有指标的平均值。
10.终结之南
如果损失变成NaN
,则该回调终止训练。
tf.keras.callbacks.TerminateOnNaN()
结论
您可以根据自己的需要使用这些回调函数。使用多个回调通常是好的(甚至是必要的),比如用TensorBoard
来监控进度,用EarlyStopping
或LearningRateScheduler
来防止过度拟合,用ModelCheckpoint
来保存你的训练进度。
请记住,您可以在渐变上免费运行tensorflow.keras
中所有可用回调的代码。我希望这有助于你训练你的模型。
快乐深度学习。
如何在 Raspberry Pi 上运行 TensorFlow Lite 模型
原文:https://blog.paperspace.com/tensorflow-lite-raspberry-pi/
使用 TensorFlow 创建的深度学习模型需要高处理能力来执行推理。幸运的是,TensorFlow 有一个精简版,称为 TensorFlow Lite(简称 TFLite),它允许这种模型在功能有限的设备上运行。推理在不到一秒的时间内完成。
在本教程中,我们将准备 Raspberry Pi (RPi)来运行一个用于分类图像的 TFLite 模型。之后,MobileNet 模型的 TFLite 版本将被下载并用于在设备上进行预测。
本教程涵盖的部分如下:
- 从 PC 访问 Raspberry Pi
- 在 RPi 中准备 TFLite
- 下载 MobileNet
- 对单个图像进行分类
让我们开始吧。
从 PC 访问 Raspberry Pi
您有多种方法可以访问 Raspberry Pi。无论您使用哪种方式,本节的目标都是能够访问 RPi 的终端,以便输入命令来帮助它为 TFLite 做准备。让我们讨论其中的一些方法。
一种方法是通过 HDMI 端口将 RPi 连接到显示器,并插入鼠标和键盘,将 RPi 用作普通 PC。RPi 成功启动后,您可以访问它的 GUI 并打开它的终端来输入命令,比如安装或下载某些东西。不幸的是,当你购买新的 RPi 时,这种方法可能不起作用,因为需要更改一些设置以允许使用 HDMI 端口。
使用专用显示器和外围设备访问 RPi 成本很高,除非您有自己的 PC/笔记本电脑来执行此操作。当使用您自己的 PC 来控制 RPi 时,您首先需要将 RPi 的以太网端口连接到交换机中的端口。交换机必须支持 DHCP(动态主机配置协议),以便自动为 RPi 提供 IP 地址。将 IP 地址分配给 RPi 后,您可以使用 IP 扫描工具从连接到同一台交换机的 PC 上找到 RPi 以太网接口的 IP 地址。获得 RPi 的 IP 地址后,您可以从您的 PC 上打开一个 SSH 会话,在该会话中您可以访问 RPi 终端。你可以在本教程中找到关于这个过程的更多细节。
使用上述方法,每次需要访问 RPi 时,都必须将其连接到交换机。为了方便起见,我们可以使用无线接口。将 RPi 连接到交换机端口后,您可以通过在无线网络上配置其无线接口,使 RPi 的未来访问更加容易。例如,这个网络可以是使用智能手机创建的热点。网络配置完成后,您以后可能不需要使用交换机。您只需将您的电脑连接到同一个无线网络。IP 扫描工具可以告诉您 RPi 中无线接口的 IP 地址。之后,您可以打开一个 SSH 会话,在该会话中可以访问 RPi 终端。
无论您使用何种方式访问 RPi,您都应该能够根据下图访问终端。此时,您可以在终端中发出命令来准备 TFLite。我们将在下一节讨论这一点。
在 RPi 中准备 TFLite
本教程假设您已经将 TensorFlow 模型转换为 TensorFlow Lite 模型。如果没有,还有大量 TensorFlow Lite 模型可供下载。我们将使用精简版的 MobileNet 。
TensorFlow Lite 是 TensorFlow 的一部分。通过安装 TensorFlow 库,您也将安装 Lite 版本。在安装 TensorFlow 之前,只需考虑您的项目所需的模块。在本教程中,我们只需要运行一个 TFLite 模型来对图像进行分类,仅此而已。基于此,我们不需要在 TensorFlow 中安装所有东西;只有与我们的任务相关的部分。
为了使用 TFLite 模式进行预测,TensorFlow 中唯一需要的类是由tensorflow.lite.python.interpreter.Interpreter
访问的Interpreter
类。因此,我们可以只安装这个类,而不是在 TensorFlow 中安装所有东西。这可以节省我们的存储空间,避免保存未使用的文件。那么,我们如何从 TensorFlow 得到这个类呢?
有一个名为tflite_runtime
的包只包含了Interpreter
类。可以通过tflite_runtime.interpreter.Interpreter
访问。要安装tflite_runtime
包,只需下载适用于 RPi 上运行的 Python 版本的 Python wheel。以下是基于 Python 版本的车轮文件的下载链接(例如 Python 3.5 或 Python 3.7 )。
在我的 RPi 中,。whl 文件下载到这个路径:/home/pi/Downloads/TF lite _ runtime-1 . 14 . 0-cp35-cp35m-Linux _ arm v7l . whl。然后我使用pip3 install
,如下图所示,来安装它。请注意,您需要使用pip3
,因为只有pip
会引用 Python 2。
pip3 install /home/pi/Downloads/tflite_runtime-1.14.0-cp35-cp35m-linux_armv7l.whl
下图显示软件包安装成功。
在成功安装了tflite_runtime
之后,您可以通过根据下一行导入Interpreter
类来检查一切是否正常工作:
from tflite_runtime.interpreter import Interpreter
下图显示一切运行正常。
要知道安装tflite_runtime
并不意味着安装了 TFLite 中的所有东西,这一点很重要。只有Interpreter
类可用,它基于现有的 TFLite 模型进行预测。如果您需要 TFLite 的更多功能,那么您需要安装完整的 TensorFlow 包。
在安装了tflite_runtime
并使您的 RPi 准备好进行预测之后,下一步是使 TFLite 模型在 RPi 存储中可用(也就是说,通过下载)。下一节讨论下载 MobileNet 的 TFLite 版本。
下载 MobileNet
MobileNet 已经被转换成了 TFLite 版本,可以在这里下载。它是一个压缩文件,不仅包含 TFLite 模型,还包含模型预测的类标签。解压缩该文件后,其内容如下:
- mobile net _ v1 _ 1.0 _ 224 _ how . tft lite
- labels _ mobile net _ quat _ v1 _ 224 . txt
MobileNet 有两个版本,每个版本都支持不同形状的输入图像。这里我们将使用版本 1,它接受形状的图像(224,224)。最后,模型被量化,这是减少模型大小和减少预测延迟的一个步骤。
我在 RPi 中创建了一个名为 TFLite_MobileNet 的新文件夹来存放这两个项目,如下所示。
/home/pi/TFLite_MobileNet
mobilenet_v1_1.0_224_quant.tflite
labels_mobilenet_quant_v1_224.txt
test.jpg
我还包含了一个样本图像,test.jpg(如下所示),用于输入模型进行分类。
"test.jpg"
现在我们已经准备好了所有需要的文件,在下一节中,我们将看到如何将这个图像提供给模型来预测它的类标签。
对单个图像进行分类
下面列出了加载 TFLite 模型和对图像进行分类所需的代码。我们从加载所需的库开始。然后在model_path
和labels
变量中准备模型和类标签的路径。然后模型路径被提供给Interpreter
类构造函数来加载它。加载的模型在interpreter
变量中返回。
from tflite_runtime.interpreter import Interpreter
from PIL import Image
import numpy as np
import time
data_folder = "/home/pi/TFLite_MobileNet/"
model_path = data_folder + "mobilenet_v1_1.0_224_quant.tflite"
label_path = data_folder + "labels_mobilenet_quant_v1_224.txt"
interpreter = Interpreter(model_path)
print("Model Loaded Successfully.")
interpreter.allocate_tensors()
_, height, width, _ = interpreter.get_input_details()[0]['shape']
print("Image Shape (", width, ",", height, ")")
# Load an image to be classified.
image = Image.open(data_folder + "test.jpg").convert('RGB').resize((width, height))
# Classify the image.
time1 = time.time()
label_id, prob = classify_image(interpreter, image)
time2 = time.time()
classification_time = np.round(time2-time1, 3)
print("Classificaiton Time =", classification_time, "seconds.")
# Read class labels.
labels = load_labels(label_path)
# Return the classification label of the image.
classification_label = labels[label_id]
print("Image Label is :", classification_label, ", with Accuracy :", np.round(prob*100, 2), "%.")
模型加载后,调用allocate_tensors()
方法为输入和输出张量分配内存。内存分配后,调用get_input_details()
方法返回一些关于输入张量的信息。这包括输入图像的宽度和高度。我们为什么要返回这些信息?记住,加载的模型接受形状的图像(224,224)。如果一个不同大小的图像被输入到模型中,我们将会得到一个错误。通过了解模型接受的图像的宽度和高度,我们可以相应地调整输入的大小,这样一切都会正常工作。
返回输入张量的宽度和高度后,使用 PIL 读取测试图像,返回的图像大小设置为等于模型接受的图像大小。
现在我们已经准备好了模型和图像。接下来,我们将使用下面实现的classify_image()
函数对图像进行分类。在其中,模型的输入张量根据set_input_tensor()
函数被设置为等于测试图像。下一步是使用invoke()
函数运行模型并传播输入以获得输出。返回的输出是类索引,以及它的概率。
def classify_image(interpreter, image, top_k=1):
tensor_index = interpreter.get_input_details()[0]['index']
input_tensor = interpreter.tensor(tensor_index)()[0]
input_tensor[:, :] = image
interpreter.invoke()
output_details = interpreter.get_output_details()[0]
output = np.squeeze(interpreter.get_tensor(output_details['index']))
scale, zero_point = output_details['quantization']
output = scale * (output - zero_point)
ordered = np.argpartition(-output, top_k)
return [(i, output[i]) for i in ordered[:top_k]][0]
返回分类概率后,使用下面实现的load_labels()
函数从文本文件中加载分类标签。它接受文本文件路径并返回一个带有类标签的列表。图像被分类到的类别的索引用于返回相关的类别标签。最后,打印这个标签。
def load_labels(path): # Read the labels from the text file as a Python list.
with open(path, 'r') as f:
return [line.strip() for i, line in enumerate(f.readlines())]
下面列出了完整的代码。
from tflite_runtime.interpreter import Interpreter
from PIL import Image
import numpy as np
import time
def load_labels(path): # Read the labels from the text file as a Python list.
with open(path, 'r') as f:
return [line.strip() for i, line in enumerate(f.readlines())]
def set_input_tensor(interpreter, image):
tensor_index = interpreter.get_input_details()[0]['index']
input_tensor = interpreter.tensor(tensor_index)()[0]
input_tensor[:, :] = image
def classify_image(interpreter, image, top_k=1):
set_input_tensor(interpreter, image)
interpreter.invoke()
output_details = interpreter.get_output_details()[0]
output = np.squeeze(interpreter.get_tensor(output_details['index']))
scale, zero_point = output_details['quantization']
output = scale * (output - zero_point)
ordered = np.argpartition(-output, 1)
return [(i, output[i]) for i in ordered[:top_k]][0]
data_folder = "/home/pi/TFLite_MobileNet/"
model_path = data_folder + "mobilenet_v1_1.0_224_quant.tflite"
label_path = data_folder + "labels_mobilenet_quant_v1_224.txt"
interpreter = Interpreter(model_path)
print("Model Loaded Successfully.")
interpreter.allocate_tensors()
_, height, width, _ = interpreter.get_input_details()[0]['shape']
print("Image Shape (", width, ",", height, ")")
# Load an image to be classified.
image = Image.open(data_folder + "test.jpg").convert('RGB').resize((width, height))
# Classify the image.
time1 = time.time()
label_id, prob = classify_image(interpreter, image)
time2 = time.time()
classification_time = np.round(time2-time1, 3)
print("Classificaiton Time =", classification_time, "seconds.")
# Read class labels.
labels = load_labels(label_path)
# Return the classification label of the image.
classification_label = labels[label_id]
print("Image Label is :", classification_label, ", with Accuracy :", np.round(prob*100, 2), "%.")
打印消息的输出如下所示。
Model Loaded Successfully.
Image Shape ( 224 , 224 )
Classificaiton Time = 0.345 seconds.
Image Label is : Egyptian cat , with Accuracy : 53.12 %.
这就是在 RPi 上使用 TFLite 对图像进行分类所需要知道的一切。
结论
本教程展示了如何在 Raspberry Pi 上使用 TensorFlow Lite。我们查看了对单个图像进行分类的样例用例。没有必要安装完整的 TensorFlow 包;只是使用了tflite_runtime
,它支持Interpreter
类。MobileNet 模型是预先训练好的,并且已经转换为 TFLite 模型,用于进行预测。
机器会做梦吗?就玻尔兹曼机器和大脑采访特里·塞伊诺夫斯基
原文:https://blog.paperspace.com/terry-sejnowski-boltzmann-machines/
Terry Sejnowski 是深度学习的先驱之一,他与 Geoffrey Hinton 一起创造了 Boltzmann machines :一个与大脑中的学习有着惊人相似之处的深度学习网络。最近看了 Terry 的精彩著作深度学习革命后,和他进行了一次对话。我们谈到了深度学习和神经科学之间的融合,以及机器是否会做梦。
特里·塞伊诺夫斯基:
我被培养成一名理论物理学家,但却对大脑着迷。我们用与今天的计算机相比实在微不足道的计算机做模拟。当时机器学习还处于起步阶段。
杰夫和我进入了受大脑组织方式生物启发的神经网络。但是就连接性而言,这些要简单得多。它们只是简单的非线性函数。但是它们足够复杂,我们试图用它们来解决视觉中的复杂问题。
视觉似乎很简单,因为当我们早上睁开眼睛时,我们看到的是物体。似乎不需要任何努力,任何思考。那个机器是不透明的。我们没看懂。我们那时对复杂程度没有概念,也不知道大脑有多少计算能力。
在大脑中,大脑表面的大脑皮层代表了世界以及你所有的计划和行动。这是大脑中最高层次的处理。有一个突出的问题,那就是你如何在一个有这么多复杂层次的系统中学习。
我们的目标是尝试建立一个多层网络——一个输入层、一个输出层和中间的层——并让它学习。人们普遍认为,由于人工智能在 60 年代所做的早期工作,没有人会找到这样的学习算法,因为它在数学上太难了。
就在那时,Geoff 和我发明了玻尔兹曼机器,其架构灵感来自物理学。与当时考虑的所有其他架构不同的是,它是概率性的。
在当时可用的模型中,输入以确定的方式经历一系列阶段。但是我们没有自动得到相同的输出,我们认为如果每个单元都有一个概率,输出随着你给它的输入量而变化,也许我们可以取得进展。所以,输入越多,产生输出的概率就越高。如果投入低,产出的可能性就低。这带来了一定程度的可变性。
不仅如此,它创造了一个不同的网络阶层,这是生成性的。在传统的输入输出网络中,没有输入,就没有输出。基本上里面什么都没有。但是在玻尔兹曼机器中,即使没有输入,事物也在突突前进,因为总有一些可能性,每个单元都会有输出。这就是我们发现的在一个非常复杂的多层网络中学习的秘密,我们现在称之为深度学习。
我们给网络一个输入,然后跟踪网络中的活动模式。对于每个连接,我们跟踪输入和输出之间的相关性。然后为了能够学习——这都是我们做过的数学分析——你必须基本上摆脱输入,让它自由运行,在某种意义上让网络进入睡眠状态。但是,当然,它仍然在突突前进,你可以对每一对有连接的单元进行同样的测量。
你记录下这些相互关系。我们称之为睡眠阶段。学习算法非常简单。你从清醒学习阶段减去睡眠阶段相关性,这就是你如何改变重量的强度。要么上升,要么下降。我们表明,如果你这样做,并且你有足够大的数据集,那么你可以学习任意映射。我们训练它区分邮政编码上的手写数字。所以,有 10 个输出单元。你给它一些输入,这是一个手写的数字 2,然后在最顶端的单元,代表 2,将会在最高水平上比其他单元活跃。
这就是我们对数字的分类。但现在你可以做的是“箝位”,我们称之为“箝位”,或者固定 2 的输出,以便它是唯一有效的,其余的都关闭。因为这个网络有输入和输出,所以它向下渗透。这是一个高度循环的网络。它会开始创建看起来像 2 的输入,但它们会不断变化。顶部的环会来来去去,然后底部的环会来来去去,它们会四处游荡。所以这基本上是在做梦。这是一个关于 2-ness 的梦。这个网络创造了一个 2 的内在表现。
你阻止任何输入进来,这样网络可以表达一个在最高层次代表这个概念的输入。因此,信息不是从输入流向输出,而是从输出流向输入。这就是所谓的生殖网络。现在我们有了更强大的生成网络,生成对抗网络,这很神奇,因为你不仅可以生成 2,你还可以生成人脸的图片。你给它一堆像我们现在住的房间的例子,它会开始生成不存在的新房间,有不同种类的桌子、椅子和窗户,它们看起来都很真实,像照片一样逼真。这才是真正令人惊讶的地方,因为我们可以创建非常逼真的世界模型。
从某种意义上说,当我们睡着做梦时,大脑也是这样做的。我们看到的是根据我们的经验生成的模式。
杰夫和我完全相信我们已经弄清楚了大脑是如何工作的。为了在多层网络中学习,你不得不睡觉,这仅仅是巧合吗?人类每天晚上都要睡八个小时。我们为什么要睡觉?事实上,我帮助开拓的一个领域是试图真正理解当你睡着时你的大脑在想什么。
像我这样做计算模型的科学家在理解你白天的经历如何在晚上融入你的大脑方面取得了巨大的进步。这被称为记忆巩固,现在有大量的证据表明这就是正在发生的事情。
有一种叫做重放的东西发生在你大脑中对记忆很重要的部分,情节记忆,也就是发生在你身上的事情;事件独特的物品。在晚上,海马体会向大脑皮层回放这些经历,然后大脑皮层会将其整合到知识库中,这是你对世界的语义知识。玻尔兹曼机器的类比实际上是对睡眠过程中发生的事情的一个很好的洞察。但是现在,很明显,就神经元的数量和活动模式而言,睡眠期间真正发生的事情要复杂得多,我们已经对此进行了非常详细的研究。但是我们真的认为,从计算上来说,这就是正在发生的事情。
一方面,我们对大脑的知识,另一方面,我们现在有能力在大脑的图像中创建这些大规模的网络。不完全是,我们不是要复制大脑,而是从大脑中提取原理,尝试建立具有大脑某些功能的系统,比如视觉,比如语音识别,比如语言处理。
神经科学家正在观察深度学习发生了什么,受到启发,提出假设,并在大脑中进行测试。随着我们对大脑的了解越来越多,对大脑如何解决这些问题的了解越来越多,我们可以将其应用于深度学习。
考虑关注度。当我们环顾四周时,我们并不试图处理外面的一切。我们专注于一个特定的对象,专注于阅读一个句子。这意味着你必须集中注意力。事实证明,如果你关注这些深度学习网络,你会大大提高它们的表现。
如果你在做语言翻译,句子开头的一个单词可能和句子后面的一个单词有很强的关系。所以,当输入按顺序出现时,你希望能够抓住这些信息,注意它们。现在另一个词出现了,这两个词必须相互联系起来。
所以注意力是一种标记和表示这很重要的方式。牢记在心。然后在你把所有这些单词连接起来之后,它现在是一个有意义的表示。然后你开始用另一种语言输出单词。再次强调,尊重单词之间的关系,它们是如何排序的,它们的分句是什么样子的。而在德语中,你必须等到句子的末尾才能放动词。电视网必须明白这一点。它必须记住动词是什么,知道动词是什么,并且知道把它放在哪里。这都是我们认为理所当然的事情。这才是我们大脑真正擅长的。
因此,随着我们对大脑用于处理单词、语音、视觉等的机制了解得越来越多,这些将被整合并改善网络的性能。
现在,特别是自然语言处理,这已经达到了一个点,你可能从你的手机上知道,它真的很好。即使在嘈杂的环境中,语音识别也变得非常好。这是一个全新的时代。
我们的计算机视觉模型是基于摄像机的,而摄像机是基于帧的。所以当你拍摄视频时,它实际上是一系列包含图像的帧,然后你的大脑将它们组合成一个序列,这样你就可以看到运动并识别运动的东西。新一代的相机是基于你的视网膜的工作原理。你的视网膜实际上是大脑的一部分,它是你眼睛背面的一个小袋子,通过几层处理,它首先将图像转换为电信号,然后转换为尖峰信号。
流入大脑的脉冲编码了与颜色、运动和时间有关的信息。事物是如何随时间和相对强度变化的,例如,在一个边缘上,你有一个用尖峰编码的对比度变化。你有所有的信息。现在这串尖峰信号是异步的。与在 30 或 40 毫秒内收集信息的帧不同,您可以在任何时候发送尖峰信号。这意味着当世界上发生什么事情时,你可以在一毫秒或更短的时间内发出尖峰信号。尖峰信号的相对时间携带了很多关于事物走向的信息——比使用基于帧的相机要多得多。
如果你使用基于尖峰的表示法,我们称之为动态视觉传感器。它们的优点是功耗非常低,因为它们只发出这些尖峰信号。它们非常稀疏,因为如果没有东西在动,你实际上什么也没有得到。你必须有动作。而且非常轻便。例如,这对机器人来说是完美的事情,因为给机器人供电非常昂贵。如果你可以用尖峰信号而不是超级计算机或 GPU 来做视觉,这就是深度学习所用的东西,那么自治就更容易了。这就是我们要去的地方。
像你的手机和手表这样的边缘设备就是电脑,它们很快就会内置深度学习芯片。你必须有更好的电池,但最终如果你能用模拟超大规模集成电路取代数字电路,比如 DVS 相机,这将彻底改变你在手机上的计算量。
尖峰很有意思。大脑中的神经元发出这些脉冲,它们要么全是,要么没有,持续大约一毫秒。与数字电子设备相比,它们相对较慢。从这个意义上说,它们是模拟的。数字芯片有一个时钟,每个周期,每个晶体管都会更新,所以整个芯片必须同步。而这些模拟 VLSI 芯片是异步的。所以,每一个模型神经元都可以随时发出脉冲。
然后这些信号通过数字线路被传送到其他芯片。所以,这是一个混合芯片。它有模拟处理,真的很便宜,顺便说一下也不是很准确,不过没关系。事实证明,如果你用大量元素进行大量并行计算,然后整合这些信息,你会更好。但要在芯片之间进行通信,就像大脑那样,你必须将其转换成数字总线,并使用某种协议发送信息。
一旦我们意识到在多层网络中学习是可能的,那么几年内就发现了一堆其他的学习算法。最受欢迎的一种是误差反向传播,它要求你获取关于你做得有多好的信息,并将其与标记的输入进行比较,然后使用该误差反向更新权重。你总是在减少错误,而且你可以非常有效、非常迅速地做到这一点。因为它是如此的高效,它是现在大多数实际问题被越来越大的网络攻击的方式。
大脑的视觉皮层有 12 层。现在人们处理的网络有 200 层或更多。我们当时不知道的是,这是成功的关键,是这些学习算法可以很好地扩展。
人工智能中的一个典型算法能够解决一些小问题,在这些小问题中,你只有几个变量,你试图找到一个最优解。旅行推销员问题就是一个很好的例子。给定几个城市,如果你每个城市都去一次,最快的路线是什么?这被称为 NP 完全,这意味着随着城市数量的增加,问题变得更加困难。在某些时候,你的电脑有多快并不重要,问题会让它饱和。这就是在单处理器数字计算机中使用的许多算法的问题,这是冯诺依曼架构,内存与处理器分离。这两者之间有一个瓶颈。
现在,我们在 80 年代开创的这些神经网络的美妙之处在于它们是大规模并行的。这意味着他们使用简单的处理器,内存位于处理器上。它们在一起,所以你不必来回传递信息。在大脑中,我们有一千亿个并行工作的神经元。这意味着你可以实时进行更多的计算,而不必担心缓冲区或任何东西。随着你在网络中加入越来越多的神经元和越来越多的层,性能会越来越好。这意味着它的伸缩性非常好。
事实上,这绝对令人惊讶,如果您有并行硬件,也就是说,如果您同时模拟每个单元,并且同时通过连接权重传递信息,那么这就称为一级缩放。
这意味着所花的时间,与你拥有的单位数量无关。已经修好了。这就是大脑的工作方式。大脑工作正常。换句话说,随着大脑皮层在灵长类动物大脑中进化出越来越多的神经元,尤其是在人类大脑中,它仍然实时工作。它仍然在同样的时间内工作,以得出一个结论——仅仅识别一个物体就需要大约一百毫秒——没有比这更好的了。因此,大自然找到了一种扩大计算规模的方法。现在我们正在寻找答案。现在硬件已经成为机器学习的重要组成部分。直到最近,有了内存芯片,有了 CPU 芯片,可能还有一些数字信号处理芯片。
但是现在这些机器学习算法正在被植入硅片。谷歌已经有一个张量处理单元,TPU,它做深度学习。但是还有很多其他的机器学习算法可以放入硅片中,这将极大地提高你可以做的计算量,因为这些就像现在的超级计算机。事实上,有一个来自大脑,它有 20 厘米宽,有 4 亿个处理单元。这已经达到了真实的规模。当然,这是千瓦,所以你必须有一个发电机,但它是按比例增加的。这是一种全新的芯片,人们刚刚开始欣赏它。
首先,它是异步的,这意味着你不需要芯片上的时钟。你可以让整件事过去。第二,你不需要 64 位精度。有八位也过得去。这意味着可以节省大量内存。然后在当地有高度的连通性。这意味着彼此靠近的处理器一直在交换大量信息。大脑也是这样工作的。现在加载所有的数据,就像它通过你的感官一样。它像管道一样流过。信息在流通,决策在制定。归根结底,这是一个极其复杂的动力系统。
我们现在面临着一个有趣的问题。我们真正想知道的是网络内部发生了什么。它学什么?现在最热门的事情是用神经科学在大脑上做的相同实验来探索人工神经网络。你怎么知道大脑里发生了什么?你把一个电极放在其中一个单元上,观察它有什么反应,当它有反应时。是在决定前开火还是决定后开火?这给了你信息是如何在网络中流动的提示。我们现在正在用这些人工网络做这件事。真的真的很刺激。
大脑可能介于玻尔兹曼机器和后道具网之间。这实际上导致了一个真正令人兴奋的新的研究领域,这是所有可能的并行计算系统,具有学习能力和接收大量数据并能够分类或预测的能力。我们只是触及表面。这是对这个空间全新的数学探索的开始。我写了一篇文章,最近被美国国家科学院学报接受。题目是深度学习的不合理有效性,因为深度学习是能够做不可解释的事情的。
这篇文章改编自播客 Eye on AI 第 31 集的前半部分。在这里找到完整录制的剧集,或者在下面收听。
在采访的第一部分,Terry Sejnowski 向我们讲述了机器做梦,玻尔兹曼机器的诞生,大脑的内部运作,以及我们如何在人工智能中重现它们。
特斯拉 V100 今天上市!
Paperspace 是首家提供 NVIDIA Volta(世界上最强大的 GPU)的云提供商
今年在 GTC,当 NVIDIA 的首席执行官黄仁勋首次展示该公司有史以来最先进的芯片时,我们第一次看到了 GPU 的新 Volta 系列。
R&D 的预算超过 30 亿美元,有多达 5120 个 CUDA 核心和 640 个“张量核心”,使 V100 成为世界上第一个打破深度学习性能 100 万亿次浮点运算(TFLOPS)障碍的 GPU。
这款新一代 Volta GPUs 最让我们兴奋的是,它将有助于满足新兴人工智能和机器学习产品和服务日益复杂的要求。
我们在 Paperspace 的目标一直是以最优惠的价格为我们的客户提供最强大的 GPU 基础设施。
从今天开始,新的和现有的 Paperspace 客户可以通过 Paperspace 网站申请访问,所有提交的内容将按照先到先得的原则进行处理。
为了启动此次发布,我们还接受基于 GPU 的突破性技术的项目提案。我们一直在玩这些新卡,但也知道有一些我们甚至没有想到的应用。这种新的硬件已经超过了软件的发展,所以我们很想看看你能在这种新的架构上创造什么。
获胜者将获得 100 小时的 V100 GPU 免费时间,该项目将由 Paperspace 推广。向submissions@paperspace.com提交你的提案,并附上几段描述该项目的文字。
- PS 团队
使用 Flair 框架进行堆叠嵌入的 text 2 表情预测器
Photo by Domingo Alvarez E / Unsplash
与以前不同,表情符号现在已经成为几乎任何以文本形式表达情绪和感受的对话的一个组成部分。多年来,表情符号的使用和表达已经发展并获得了广泛的流行。表情符号还可以作为一种独立的非语言来传达大多数情绪,并通过增强其情商来补充书面文本。最近,手机的内置键盘已经更新,可以在你输入时为给定的单词/短语建议表情符号。这种功能为用户提供了表达情绪的媒介,而不必痛苦地从表情湖中搜索相关的表情符号。
最新研究表明,表情符号的吸引力远不止是年轻用户一个人——事实上,超过 80%的英国 T2 成年人现在在短信中使用表情符号,而高达 40%的人承认自己创造了一条完全由表情符号组成的信息。- 来源
快速建立原型的基本方法之一是拥有一个大型单词数据库及其各自的表情映射,并在需要时查询该数据库以获得一个单词及其可能的拼写变化。
在这篇博客中,我们将为短句而不仅仅是一个单词建立一个表情符号建议系统。我们还构建了一个界面来展示整个系统。
资料组
我们将在这里使用来自的数据集。我们的数据包含以相关表情符号作为类别标签的短句。标签标记为 0-4 之间的数字,从下表中可以看到实际表情符号的映射—
Dataset Label Mapping
鉴于我们的训练数据非常有限(200 个句子),对于我们来说,利用预训练的单词嵌入来完成这项任务变得非常重要。这些是基于单词在非常大的语料库中出现的上下文训练的单词的一些通用表示。对于这个博客,我们将使用多个单词嵌入的堆栈来表示一个单词。我们将在 方法论 一节中看到更多关于该方法及其细节。为了形式化,这是一个多类分类问题,其中对于给定的话语,我们需要预测最合适的表情符号。
方法学
如上所述,这项任务的核心思想是建立一个多类分类器。我们首先用一些预先训练的语义单词向量来表示句子中的每个单词,希望它们经过足够的训练来捕捉该单词不同的可能语义。为了说明每种预训练方法的优势,我们将一个单词表示为多个单词嵌入的堆栈。这些随后被输入到一个浅层的 LSTM 网络,该网络负责将单词按顺序关联起来,并学习它们之间的依赖关系。作为输出,我们期望得到数据中每个句子的固定长度向量表示。这个最终的句子向量稍后被馈送到跨 5 个类别的具有 Softmax 激活功能的前馈神经网络,并且最终的标签被选择为具有最大概率的类别。
下图显示了整个架构的图示视图—
Architecture
这种方法的新颖之处在于我们表达句子中每个单词的方式。我们利用 4 种不同类型的预训练单词嵌入在它们的连接版本中来表示句子中的单个单词。我们使用的 4 种类型的嵌入是— 手套 (无多义词层次)、Flair-news-FWD(语境化人物层次)、Flair-news-back(语境化人物层次)、 ELMo (多义词意识)使用 4 种不同类型的嵌入的假设是,所有的嵌入都以不同的方式训练,使它们能够自由地在各自的维度上捕捉同一单词的不同语义。这也有助于处理词汇表之外的标记,尤其是在合并字符级嵌入时。
下面的片段显示了模型架构-
Model: "TextClassifier(
(document_embeddings): DocumentRNNEmbeddings(
(embeddings): StackedEmbeddings(
(list_embedding_0): WordEmbeddings('glove')
(list_embedding_1): FlairEmbeddings(
(lm): LanguageModel(
(drop): Dropout(p=0.05, inplace=False)
(encoder): Embedding(300, 100)
(rnn): LSTM(100, 2048)
(decoder): Linear(in_features=2048, out_features=300, bias=True)
)
)
(list_embedding_2): FlairEmbeddings(
(lm): LanguageModel(
(drop): Dropout(p=0.05, inplace=False)
(encoder): Embedding(300, 100)
(rnn): LSTM(100, 2048)
(decoder): Linear(in_features=2048, out_features=300, bias=True)
)
)
(list_embedding_3): ELMoEmbeddings(model=elmo-medium)
)
(word_reprojection_map): Linear(in_features=5732, out_features=256, bias=True)
(rnn): LSTM(256, 512)
(dropout): Dropout(p=0.5, inplace=False)
)
(decoder): Linear(in_features=512, out_features=5, bias=True)
(loss_function): CrossEntropyLoss()
)"
实验
我们将使用 Flair NLP 框架 来训练模型。Flair 是在py torch之上开发的简单 NLP 库,由Zalando Research开源。
Data Transformation
下面是执行上述数据转换的代码片段—
def segment_data(data_file):
try:
import pandas as pd
except ImportError:
raise
data = pd.read_csv(data_file, encoding='latin-1').sample(frac=1).drop_duplicates()
data = data[['classes', 'title']].rename(columns={"classes":"label", "title":"text"})
data['label'] = '__label__' +data['label'].astype(str)
data['text'] = data['text'].apply(lambda k: k.lower().strip())
data.to_csv('./data/whole.csv', sep='\t', index = False, header = False)
data.iloc[0:int(len(data)*0.8)].to_csv('./data/train.csv', sep='\t', index = False, header = False)
data.iloc[int(len(data)*0.8):int(len(data)*0.9)].to_csv('./data/test.csv', sep='\t', index = False, header = False)
data.iloc[int(len(data)*0.9):].to_csv('./data/dev.csv', sep='\t', index = False, header = False)
return
Creating training, testing and validation splits
接下来,我们加载所有必要的嵌入,并返回封装它们的列表。每个单词以所有四种嵌入方案的堆叠版本作为其最终表示来嵌入。
def initialize_embeddings():
"""
Summary:
Stacks the list of pre-trained embedding vectors to be used as word representation (in concat.)
Return:
list: Returns list of pretrained embeddings vectors
"""
word_embeddings = [
WordEmbeddings('glove'),
FlairEmbeddings('news-forward'),
FlairEmbeddings('news-backward'),
ELMoEmbeddings('medium')
]
return word_embeddings
word_embeddings = initialize_embeddings()
Embedding Stacking
在最终确定我们的单词表示之后,我们继续使用documentrnembeddings来学习上下文化的句子表示,然后用必要的超参数训练模型。你可以在这里阅读其他可能的池技术。
document_embeddings = DocumentRNNEmbeddings(word_embeddings, hidden_size=512, reproject_words=True, reproject_words_dimension=256, rnn_type='LSTM', rnn_layers=1, bidirectional=False)
classifier = TextClassifier(document_embeddings, label_dictionary=corpus.make_label_dictionary(), multi_label=False)
trainer = ModelTrainer(classifier, corpus)
trainer.train('./model', max_epochs=20, patience=5, mini_batch_size=32, learning_rate=0.1)
Model Training
你可以在这个 github 访问所有的原代码。
访问 Gradient-AI fork 并点击“在 Gradient 上运行”以访问此代码的笔记本版本,或使用本文中的“将此项目带入生活”链接。
连接
最后,开发了一个基于 web 应用的 烧瓶 来展示这个用例。
Text2Emoji Demo
你可以在这个 GitHub 链接访问这个演示的所有代码。
可能的改进
- 针对这一特定任务微调预先训练的变形金刚模型,或者使用来自 BERT 的更高级的深度嵌入。
- 分析结合表情百科(表情百科是一个表情参考网站,记录了 Unicode 标准中表情字符的含义和常见用法)和其他统计特征以及现有嵌入向量的效果。
- 训练一个自动编码器,选取瓶颈层作为单词表示的元嵌入。请参考下图以供参考—
Reference 1
这就是我的博客,希望你喜欢阅读它。谢谢!
参考
喀拉斯绝对指南
在本文中,我们将深入 TensorFlow 和 Keras 的世界。如果你还没有,我强烈推荐你先看看我以前的文章,张量流绝对指南。它将帮助您更好地理解我们将在本文中涉及的各种概念。
快速回顾一下,到目前为止我们已经讨论了 TensorFlow 的几个方面,包括加速器如何帮助加速计算;张量、常数和变量;反向传播、图、层、模型等。在我们深入研究 Keras 的基础知识和众多重要方面之前,让我们快速浏览一下目录。请随意进入您认为最相关的部分。
目录
- Keras 简介
- 学习基础层
1。输入层
2。卷积和最大池层
3。批量归一化层
4。辍学层
5。致密层 - 了解各种模型架构
1。顺序型号
2。功能 API 模型
3。定制模型 - 回调
1。提前停止
2。检查站
3。降低平稳状态下的 LR
4。张量板 - 汇编和培训
- 结论
您可以跟随本文中的所有代码,并在 Gradient 社区笔记本上的免费 GPU 上运行它。
Keras 简介
TensorFlow 是一个用于开发神经网络的深度学习框架。正如我们在上一篇文章中看到的,TensorFlow 实际上是一种低级语言,实现的整体复杂度较高,尤其是对于初学者。这个问题的解决方案是引入另一个深度学习库,它将简化 TensorFlow 的大部分复杂性。
Keras 深度学习库提供了一种更高级的构建神经网络的方法。正如他们的官网上描述的那样,Keras 是一个为人类设计的 API,而不是为机器设计的。这意味着该模块使人类执行编码操作变得更加简单。
使用像 Keras 这样的深度学习库的最大好处是,您的工作显著减少,因为您可以在几分钟内构建复杂的架构,而模块会考虑实际的过程。你的重点仍然是整体的架构设计,而不是错综复杂的细节。因此,对于所有级别的用户来说,Keras 可以作为一种更简单的方法来获得最佳性能。它还提供了大量有助于入门的文档。但是,在本文中,我们将涵盖您需要了解的关于 Keras 的每个重要方面。那么,事不宜迟,让我们开始吧。
学习基础层
Keras 模块使用户能够用称为“层”的元素构建块来构建神经网络。每一层都有其特定的功能和操作。在本文中,我们将着重于更好地理解如何使用这些层,而不会过多地讨论它们是如何工作的。
让我们来看看几个基本层,即输入层、卷积和最大池层、批量标准化层、丢弃层和密集层。虽然您可以将激活层用于激活功能,但我通常更喜欢在其他层中使用它们。我们将在下一节中讨论更多关于激活函数的内容,同时构建不同的体系结构。现在,让我们看看用于构建神经网络的一些基本层。
输入层
from tensorflow.keras.layers import Input
input1 = Input(shape = (28,28,1))
输入层是最基本的层之一,通常用于定义模型的输入参数。正如我们将在下一节中观察到的,在顺序模型实现中,我们通常可以跳过输入层。但是,对于一些函数式 API 模型来说,它是必需的。
卷积层和最大池层(二维)
from tensorflow.keras.layers import Convolution2D, MaxPooling2D
Convolution2D(512, kernel_size=(3,3), activation='relu', padding="same")
MaxPooling2D((2,2), strides=(2,2))
卷积层创建卷积核,该卷积核与层输入卷积以产生输出张量。max-pooling 层用于对卷积层的空间维度进行下采样,主要用于在对图像进行操作时创建位置不变性。当我们处理计算机视觉任务时,我们将在以后的文章中更详细地讨论这些层。
批量标准化层
上图是从头开始实现批量标准化图层的示意图。该图层的作用是通过应用一种变换来对输入进行规范化,该变换将平均输出保持在 0 美元左右,输出标准偏差保持在 1 美元左右。在训练阶段,每批输入都有单独的计算,并相应地执行归一化。然而,在预测阶段(即,在测试期间),平均输出和标准偏差值是每批的平均值。
脱落层
这些层通常用于防止模型过度拟合。丢弃层将以设定的速率从隐藏节点中随机丢弃选定的部分,并在剩余节点上进行训练。测试时,我们不会丢弃任何层。
致密层
这些是常规的全连接层,可用于隐藏节点或输出层,以接收来自所建模型的最终预测。这些是神经网络最基本的构建模块。
理解各种模型架构
现在我们对 Keras 提供的层有了一个直觉,让我们也试着理解我们可以使用 Keras 构建的各种模型架构。在我们学习不同类型的模型架构之前,我们必须首先知道它们的用途。
作为一个高级库,Keras 主要提供了三种类型的模型架构,可以扩展更多的层和模块。这三种模型架构包括顺序模型、功能 API 模型和定制模型(也称为模型子类化)。这些架构中的每一个都有其特定的用例、优势和局限性。让我们从顺序模型开始,分别了解这些模型架构的更多信息。
注意:我们还将简要讨论上一节中未涉及的其他层。然后,我们将了解激活函数的概念,学习一些激活函数,然后继续理解为什么以及何时应该使用它们。
序列模型
顺序模型是 Keras 提供的最简单的模型架构之一。一旦导入了Sequential
类,就可以用它来堆叠我们在上一节中讨论过的许多层。序列模型由一堆层组成,其中每一层恰好有一个输入张量和一个输出张量。
顺序模型架构可以通过两种不同的方式构建。我们将在下面的代码中演示这两种方法,我们将使用这两种方法构建相同的模型架构。我们还将查看模型摘要,以更好地全面了解这些顺序模型是如何工作的。让我们从使用 Keras 库的Sequential
类构建模型的第一种方法开始。
方法一
from tensorflow.keras.layers import Convolution2D, Flatten, Dense
from tensorflow.keras.layers import Dropout, MaxPooling2D
from tensorflow.keras.models import Sequential
model = Sequential()
model.add(Convolution2D(512, kernel_size=(3,3), activation='relu', padding="same", input_shape=(28, 28, 1)))
model.add(MaxPooling2D((2,2), strides=(2,2)))
model.add(Flatten())
model.add(Dense(10, activation='relu'))
model.add(Dropout(0.5))
简单解释一下代码,我们可以看到我们从layers
类中导入了所有需要的层。上面的方法是我导入层的首选方法,但是您也可以从模块中导入layers
类,并通过调用您想要构建的特定函数和层来构建您的模型。这方面的一个例子如下:
from tensorflow.keras import layers
layers.Convolution2D()
# Or:
layers.Dense()
我们使用add
函数将层堆叠在彼此之上。我们首先使用指定的输入大小堆叠卷积层,然后添加一个 max-pooling 操作来对卷积层进行下采样。然后,我们添加了展平层来压缩我们所有的材料。因为我们还没有讨论展平层,让我们了解更多一点。
展平图层接受输入,并将输入的空间维度折叠为单个值。但是,它不会以任何方式影响批量大小。展平层的主要目的是在卷积层通过全连接神经网络进行计算操作之前,减少卷积层的复杂维数。
扁平化层之后,我们利用Dense
层创建一个全连接的神经网络架构。最后,我们使用了Dropout
层来防止过度拟合。我们还没有添加一个最终层来决定我们最终的输出是什么样子;这是因为我想首先介绍更多关于激活函数的内容。
术语“激活功能”来源于描述真实神经元在传递信息时的突触活动的生物学术语。在人工神经网络中,激活函数定义给定一个输入或一组输入的节点的输出。激活函数的完整概念非常庞大,因此出于本文的目的,我们将简要介绍您应该了解的特定激活函数。
整流线性单元(ReLU)激活函数是最流行的激活函数之一,通常在大多数神经网络层中默认使用。该激活功能有助于防止渐变爆炸或消失的问题。泄漏 ReLU 是在特定场景中表现更好的替代选择。虽然我们也有其他的激活函数,但是现在让我们把注意力集中在用于模型架构的最终密集层的最佳激活函数上。
如果在密集层中有一个节点,可以选择线性激活函数。当有两个节点时,可以添加一个 sigmoid 激活函数。最后,对于多类问题,我们使用 softmax 激活函数。在这种情况下,每个标签具有特定的概率,其中选择具有最高分配概率的类。有了对不同激活函数的基本理解,让我们来看看用 Keras 构建序列模型的第二种方法。
方法二
from tensorflow.keras.layers import Convolution2D, Flatten, Dense
from tensorflow.keras.layers import Dropout, MaxPooling2D
from tensorflow.keras.models import Sequential
model1 = Sequential([
Convolution2D(512, kernel_size=(3,3), activation='relu', padding="same", input_shape=(28, 28, 1)),
MaxPooling2D((2,2), strides=(2,2)),
Flatten(),
Dense(10, activation='relu'),
Dropout(0.5)
])
这是不言自明的。Sequential
构造函数用于指定的所需层。
现在让我们来看一下这两种方法的概要。下面提供的屏幕截图展示了层、它们的输出形状和参数数量。
Image by Author
让我们也简单地看一下我们刚刚构建的架构的图示。下面的代码将把一个模型保存到您的系统中,您可以在其中查看表示您的构造中的众多层的图像。注意,如果您试图在系统上本地运行下面的代码块,您可能还需要额外的安装,比如 Graphviz。
from tensorflow import keras
from keras.utils.vis_utils import plot_model
keras.utils.plot_model(model, to_file='model.png', show_layer_names=True)
Image by Author
对于两种构造方法,上述模型总结几乎完全相同。您可以在各自的系统上验证代码。打印两种架构的摘要并检查模型类型(在两种情况下都是连续的),并检查层的放置、输出形状以及参数。序列模型是创建神经网络最直接的方式,但它们可能并不总是最佳选择。让我们在考虑下一个模型架构时找出原因。
功能 API 模型
虽然序列模型易于构建,但在某些情况下,它们可能不是最佳选择。当你有一个非线性的拓扑或者需要一个分支状的架构时,那么合适的模型就不能用顺序的架构来构建。下图总结了不应使用序列模型的情况。
Image by Author
对于这种情况,我们使用函数式 API 模型,它提供了很大的灵活性,允许您构建非线性拓扑。功能 API 模型支持共享层和多输入多输出。让我们看一个简单的代码块来构建一个类似于我们在顺序模型中所做的架构。
密码
from tensorflow.keras.layers import Convolution2D, Flatten, Dense, Dropout
from tensorflow.keras.layers import Input, MaxPooling2D
from tensorflow.keras.models import Model
input1 = Input(shape = (28,28,1))
Conv1 = Convolution2D(512, kernel_size=(3,3), activation='relu', padding="same")(input1)
Maxpool1 = MaxPooling2D((2,2), strides=(2,2))(Conv1)
Flatten1 = Flatten()(Maxpool1)
Dense1 = Dense(10, activation='relu')(Flatten1)
Dropout1 = Dropout(0.5)(Dense1)
model = Model(input1, Dropout1)
模型总结如下。
Image by Author
而模型图如下。
Image by Author
通过模型总结和绘图,我们可以确定功能 API 可以用于构建类似于顺序架构以及具有独特非线性拓扑的模型。因此,函数式 API 模型具有不同的用例,并且在开发人员中被一致地用于创建复杂的神经网络架构。
定制模型
自定义模型(或模型子类化)是从零开始实现整个神经网络架构的地方。虽然函数式 API 非常灵活,并且最常被开发人员使用,但有时定制模型也用于特定的研究目的。您从头开始构建自己的架构,并完全控制其功能。下面显示了我的一个项目中的一个用例的实现代码块。
import tensorflow as tf
class Encoder(tf.keras.Model):
'''
Encoder model -- That takes a input sequence and returns output sequence
'''
def __init__(self, inp_vocab_size, embedding_size, lstm_size, input_length):
super(Encoder, self).__init__()
# Initialize Embedding layer
# Intialize Encoder LSTM layer
self.lstm_size = lstm_size
self.embedding = tf.keras.layers.Embedding(inp_vocab_size, embedding_size)
self.lstm = tf.keras.layers.LSTM(lstm_size, return_sequences=True, return_state=True)
def call(self, input_sequence, states):
'''
This function takes a sequence input and the initial states of the encoder.
Pass the input_sequence input to the Embedding layer, Pass the embedding layer ouput to encoder_lstm
returns -- All encoder_outputs, last time steps hidden and cell state
'''
embed = self.embedding(input_sequence)
output, state_h, state_c = self.lstm(embed, initial_state=states)
return output, state_h, state_c
def initialize_states(self,batch_size):
'''
Given a batch size it will return intial hidden state and intial cell state.
If batch size is 32- Hidden state shape is [32,lstm_units], cell state shape is [32,lstm_units]
'''
return (tf.zeros([batch_size, self.lstm_size]),
tf.zeros([batch_size, self.lstm_size]))
考虑到这三个主要的架构,你可以自由地构建你的深度学习模型来完成特定的项目或任务。也就是说,深度学习模型的旅程尚未完全完成。
我们将在下一节了解更多关于回调的内容。最后,我们将了解模型编译和训练阶段。
回收
回调在模型训练中起着重要的作用。回调是可以在训练的不同阶段执行操作的对象,例如在一个时期的开始、中间或结束时。它还会影响批次开始或结束时的训练。
虽然 Keras 中有很多回调可用,但我们将只关注构建深度学习模型时通常使用的几个最重要的回调。在模型的训练(或“拟合”)阶段,最常用的回调是提前停止、检查点、减少平台上的 LR 和张量板。我们将简要考虑每个回调及其各自的代码。
提前停止
from tensorflow.keras.callbacks import EarlyStopping
early_stopping_monitor = EarlyStopping(patience=2)
当早期停止回调发现与先前的训练时期相比没有显著改善时,在指定的耐心之后停止训练过程。在这种情况下,在任何两个时期之后,如果确认损失没有改善,则停止训练过程。
模型检查点
from tensorflow.keras.callbacks import ModelCheckpoint
checkpoint = ModelCheckpoint("checkpoint1.h5", monitor='accuracy', verbose=1,
save_best_only=True, mode='auto')
Keras 中的模型检查点回调保存了训练过程中获得的最佳权重。在训练时,由于过度拟合或其他因素,您的模型有可能在一些时期后开始表现不佳。无论哪种方式,您总是希望确保为任务的性能保存最佳权重。
您可以用自己选择的名称存储检查点。通常最好是监控最佳验证损失(使用val_loss
),但是您也可以选择监控您喜欢的任何指标。使用ModelCheckpoint
的主要好处是存储您的模型的最佳权重,以便您可以在以后的测试或部署中使用它们。通过保存存储的重量,您还可以从该点重新开始训练程序。
降低平稳状态下的 LR
from tensorflow.keras.callbacks import ReduceLROnPlateau
reduce = ReduceLROnPlateau(monitor='accuracy', factor=0.2, patience=10,
min_lr=0.0001, verbose = 1)
在拟合模型时,在某个点之后,模型可能会停止改进。此时,降低学习速度通常是个好主意。factor
决定了初始学习率必须降低的速率,即new_lr = lr * factor
。
在等待指定数量的时期(本例中为patience
,10)后,如果没有改进,则学习率会降低指定的系数。这种减少会持续下去,直到达到最小学习率。
回调独立工作。因此,如果您对ReduceLROnPlateau
使用 10 的patience
,增加您的EarlyStopping
回调patience
也是一个好主意。
张量板
from tensorflow.keras.callbacks import TensorBoard
logdir='logs'
tensorboard_Visualization = TensorBoard(log_dir=logdir)
Image by Author
TensorBoard 回调是用户可用的最有价值的工具之一。有了它,您可以图形化地显示您的训练过程,并轻松发现模型工作的趋势。例如,通过您的分析,您可以确定在多少个历元后模型训练可以停止,以及哪个历元获得最佳结果。除此之外,它为开发人员提供了许多选项来分析和改进他们的模型。
Keras 的另一个重要方面是它提供了为特定用例创建自定义回调的能力。当我们使用它们时,我们将在以后的文章中更多地讨论这个主题。但是,如果您有兴趣了解更多关于创建自定义回调函数的信息,请随意访问下面的网站。
汇编和培训
构建模型架构和定义回调之后的下一个阶段是实现模型编译和训练步骤。幸运的是,在 Keras 的帮助下,这两个步骤都非常简单,只涉及很少的代码。
模型编译
model.compile(loss='categorical_crossentropy',
optimizer = Adam(lr = 0.001),
metrics=['accuracy'])
Keras 中的编译过程为训练过程配置模型。在这一步中,您将定义一些最重要的参数,例如损失函数、优化器以及在训练模型时除“损失”之外要跟踪的指标。所使用的损失函数将因问题而异,而优化器通常是 Adam 优化器。也可以尝试其他选项(如 RMSprop ),看看什么最适合您的模型。一旦您完成了模型的编译,您就可以继续进行模型训练了。
拟合模型
model.fit(train_generator,
steps_per_epoch = train_samples//batch_size,
epochs = epochs,
callbacks = [checkpoint, reduce, tensorboard_Visualization],
validation_data = validation_generator,
validation_steps = validation_samples//batch_size,
shuffle=True)
一旦我们完成了模型的编译,我们就可以使模型适合训练数据集。fit
函数为固定数量的历元(数据集上的迭代)训练模型。为了更好地理解像反向传播这样的步骤是如何工作的,我强烈推荐看看这个系列的第一部分。您需要定义的基本参数是训练数据、时期数、批量大小和回调。
最后,我们使用model.evaluate()
来评估模型的整体性能。该函数对于衡量模型的性能以及是否准备好进行部署非常有用。然后,我们可以利用model.predict()
函数对新数据进行预测。
结论
Photo by Domenico Loia on Unsplash
在本文中,我们介绍了 TensorFlow 和 Keras 绝对指南的第二部分。在简介中,我们看到 Keras 是一个高级 API,它为 TensorFlow 的复杂编码需求提供了一种替代的简单方法。然后,我们获得了一种直觉,可以在 Keras 库的帮助下构建一些层。
有了 Keras 模块中一些层背后的基本直觉,理解可以构建的各种模型架构就变得更容易了。这些架构包括顺序模型、功能 API 模型和定制模型。根据特定任务的复杂性,您可以决定哪个选项最适合您的项目。我们还简要介绍了激活函数的效用及其用法。
从那里我们开始理解 Keras 提供的回调函数的重要性。除了学习一些基本的回调,我们还关注了 TensorBoard 回调,您可以使用它定期分析模型的性能。
最后,我们讲述了编译和训练过程。一旦构建了模型,您就可以保存它们,甚至可以将它们部署给广泛的受众,让他们从中受益。
这个关于 TensorFlow 和 Keras 的两部分系列应该让您对这些库的效用有一个基本的了解,以及如何使用它们来创建令人惊叹的深度学习项目。在接下来的文章中,我们将学习另一个基本的深度学习概念:迁移学习,有很多例子和代码。在那之前,享受你的编码吧!
2022 年最好的 Kaggle 替代品
原文:https://blog.paperspace.com/the-best-kaggle-alternatives-in-2022/
Jupyter 类似笔记本的环境仍然是大多数数据科学家、机器学习工作者和爱好者参与和探索数据集和深度学习包的标准起点。许多 ML ops 平台提供他们自己的打包的、类似 Jupyter 的 IDE 来帮助用户容易地直接跳转到处理他们平台上的数据。这些都有很多有用的特性,比如云存储、版本控制、模型保存,以及其他各种帮助用户充分利用平台的特性。
对于许多用户来说,谷歌的 Kaggle 是数据科学家在互联网上的首要社交和学习平台。Kaggle 的主要特点是,它为用户提供了一个强大的环境,可以上传数据集和代码,并与其他用户共享。然后,他们可以使用他们的笔记本工具操作、处理和执行机器和深度学习任务,并免费访问 P100 GPU 和谷歌 TPUs。
虽然 Kaggle 是一个非常有用的工具,可以用来查找数据和进行一些初步探索,但我们发现,GPU 硬件的可访问性导致的限制意味着 Kaggle 对于任何计算成本高昂或耗时的深度学习任务来说往往都远非理想。这启发我们进一步观察这个平台,然后观察一些竞争对手,看看我们是否能发现用户可能会在 Kaggle 的哪里找到缺点,然后利用这些发现提出替代方案。在这篇博客文章中,我们将首先提出一些用户可能不想使用 Kaggle 平台的原因,讨论用户应该在高质量平台中寻找的品质,并以一些减轻了我们发现的问题的高质量替代网站结束。
为什么会考虑 Kaggle 之外的其他平台?
GPU 限制
所有 Kaggle 笔记本都运行在P100 GPU上。 P100 是英伟达 2016 年发布的数据中心(原特斯拉)GPU ,采用 Pascal 微架构。这曾经是特斯拉的第一个 GPU,但 Pascal 架构自发布以来已经改进了几次。当与许多其他 ML ops 平台上可用的安培,RTX 和沃尔特 GPU 相比时,P100 经常被发现缺乏。
会话时间限制
对于 CPU 和 GPU 笔记本会话,Kaggle 笔记本上的每个会话的执行时间限制为 12 小时,对于 TPU 笔记本会话,执行时间限制为 9 小时。还有一个额外的限制,即每个用户每周允许的总 GPU 时间为 30 小时,总 TPU 时间为 20 小时。
缺乏部署能力
虽然 Kaggle 笔记本电脑完全能够执行深度学习模型的培训和评估,但部署则是另一回事。如果用户希望将他们的工作部署到另一个服务的端点上,那么他们将不得不直接从 Kaggle 下载他们的工作。
未配置的环境
所有 Kaggle 笔记本都从同一个通用容器开始。这个容器预装了许多流行的深度学习、机器学习和相邻的数据处理库,以便在平台内使用。用户被锁定使用这个容器,这限制了通过更轻的容器进行优化,以及定制选项。如果用户试图安装一个额外的库,与预安装的包发生冲突,这也可能会影响用户体验。
在 ML 操作平台中寻找什么?
基于上述详细信息,Kaggle 并不总是许多给定深度学习任务的理想平台。尽管由于其完全免费的服务、对 P100s 的访问以及类似协作环境的社交媒体,它对于业余爱好者、学生和初学者来说是一个非常棒的地方,但当涉及到企业级生产时,它与许多竞争对手相比受到了影响。现在,我们已经了解了使 Kaggle 不太适合专业数据科学家的问题,让我们考虑一下在选择像 Kaggle 这样的托管 Jupyter 笔记本服务时可能需要考虑的一些因素:
不间断服务
Kaggle 在这个功能上做得很好,但前提是训练时间在他们规定的时间限制之内。像大多数计算机视觉模型一样,寻求使用需要长训练时间的模型的用户会希望寻求替代方案。
在考虑其他产品时,考虑一下你的培训在新环境中能持续多久。生产级 EDA、模型训练和评估的稳健设置将考虑到 DL 模型可能需要达到适当功效的漫长期限。总的限制也是有问题的,因为很多时候模型在第一次训练后并不完善。
持久环境
Kaggle 笔记本文件确实会跨会话保存,但只有当您选择全部运行并保存选项时,数据才会保存在持久存储中。此外,如果我们想使用 Kaggle 上的一个笔记本,必须将它的数据下载到本地并重新上传到另一个笔记本的工作目录中。
我们的工作、数据和文件如何在不同的用户会话中保持不变?我们选择的环境需要能够在每次会话后重新访问而不丢失工作。这也有助于实现基本的版本控制。
储存;储备
Kaggle 最出名的可能是它的数据存储和数据集共享功能。这些在很大程度上促成了 Kaggle 经常举办的著名比赛。用户可以通过每台笔记本电脑访问大量不同的公开共享数据集,并且文件组织非常简单易用。虽然这很方便,但所有数据集(除了一些比赛)的大小都被限制在 20 GB。这使得在 Kaggle 上处理大数据变得复杂,单个大数据集需要多个 20 GB 的数据集页面,如果需要使用大数据集,用户应该考虑内置存储更多的平台,如 MS-COCO。
我们可以上传多少数据用于模型训练?考虑存储的限制,以及在一定程度上工作记忆的限制,以及它们如何影响我们实现高质量模型训练的能力
硬件选择和种类
Kaggle 和它的一些竞争对手如 Colab 的最大问题是硬件限制。虽然 P100 对于深度学习来说是一个非常有用的 GPU,但与更现代的微体系结构(如 Ampere 或 Volta)相比,支持 GPU 进程的软件已经非常过时。
在为我们的深度学习任务选择平台之前,我们需要评估我们是否可以选择一台具有正确硬件的机器来完成我们的任务。如果我们有会话时间限制,由于 RAM 或吞吐量不足而无法训练我们的模型,可能会迫使用户不得不在不方便的时候重新开始他们的工作,或者完全失去进度。
2022 年最佳 Kaggle 替代品
现在我们知道了在 ML ops 平台中应该寻找什么,Kaggle 在哪里做得好,在哪里挣扎,我们可以开始确定 2022 年互联网上可用的最佳替代方案。以下是建议试用的平台列表。以下托管 Jupyter 环境在上述一个或所有方面都优于 Kaggle 笔记本电脑:
图纸空间梯度
Gradient 是一个端到端的 MLOps 平台,包括一个免费托管的 Jupyter 笔记本服务,有许多预配置环境和免费 GPU 和 CPU 的选项,以及我们对 2022 年 Kaggle 最佳替代方案的选择。付费用户可以从这些机器类型的大量列表中进行选择,包括大量免费使用 GPU 驱动的实例,如付费订阅用户的 RTX4000、P5000 和 M4000,并且可以根据需要运行笔记本电脑。还提供完全免费的 GPU 笔记本,以及付费的实例,机器类型一直到任何云虚拟机服务的最佳可用:8 个 A100 - 80 GB 的多 GPU 实例。
Gradient 通过其三个主要的数据科学工具简化了深度学习模型的开发、培训和部署:笔记本、工作流和部署。它们各自执行不同的任务,但笔记本电脑是 Kaggle 笔记本电脑的可比产品。例如,每个笔记本实例都有 50 GB 的保证存储,不间断服务的保证,以及跨启动的持久文件存储。然后,工作流可用于版本化、评估和上传模型和数据到平台,而部署用于将训练好的模型部署到 API 端点。Gradient 还具有强大的 SDK 和 CLI。
Gradient 的一个优点是,它为从初学者到专业人员的所有级别的工作人员提供了有价值的功能,其直观的 web UI 和极低的入门门槛反过来又与高功能平台配合使用。爱好者可以利用梯度的免费 GPU 机器和易于使用的速度。人工智能课程,而专业数据科学家可以在 Gradient 平台内进行实验、训练模型、扩展能力和部署。Gradient 真正满足了双方的需求,而没有牺牲任何一组用户成功所需的功能或体验。
与 Google Colab 相比,Gradient 的一些优势包括:
- 会话得到保证: Gradient 笔记本用户永远不必担心他们的实例在工作中关闭。他们也不需要一直保持联系。可以在笔记本上开始一个培训会话,注销,转到其他地方,重新登录,然后继续使用继续运行代码的笔记本工作
- 预先配置的容器和模板:用户可以从 Gradient 预先选择的环境中进行选择,为他们的笔记本容器预装流行的深度学习框架依赖项,他们甚至可以通过笔记本的高级设置使用他们自己的自定义容器上传到 Dockerhub
- R 兼容性:和 Kaggle 一样,用户可以通过选择 R 笔记本栈运行时,在渐变笔记本上执行 R 代码。
- 公共数据集:一个流行的公共数据集存储库可安装在任何渐变笔记本上。安装过程非常快,选择的种类也不断增加
- 可扩展性: Gradient 可轻松实现纵向扩展:根据需要,在同一环境中添加更多存储或更多强大的 GPU
- 全 ML 管道: Gradient 包含全 ML 管道的集成特性。这些包括版本控制、平台上的模型保存库,以及通过单击鼠标直接部署这些保存的模型的能力
- 轻松协作:团队可以轻松地实时一起处理项目,项目可以将工作分散到每个团队中有组织的工作区
- 免费的 GPU 笔记本:虽然不是所有的笔记本都是免费的,但是所有付费计划级别的用户都可以在 Gradient 上访问免费的 M4000 GPUs。付费层可以访问更强大的 GPU,而无需任何额外的每分钟费用,包括 P5000s、RTX5000s,甚至未来的 A100s
- Github 集成: Github 与 Gradient 的集成允许用户随着工作的进展积极地版本化和更新他们的代码库
- 积极而有益的支持:paper space 响应团队致力于确保用户获得最佳体验。除了对产品非常了解之外,他们还能快速响应,并尽最大努力改善用户可能看到的任何问题
由于 Paperspace Gradient 的多功能性、强大功能和强健的构建,我们建议企业级和专业级用户使用它,他们正在寻找一个具有过多附加功能的更好版本的 Kaggle。
Google Colab
Colab 是另一个用于深度学习的谷歌产品,它是通过合作努力和与谷歌产品的集成而创建的。Colab 是一个免费使用的 IDE,用于在 cells 中执行 Python 代码。它是最受学生和爱好者欢迎的 ML 入门平台之一,还有 Kaggle。
与竞争对手相比,Colab 的特点是启动速度极快,环境一般化,缺乏定制选项,意外关机的可能性更高。在环境中开始、加载和测试代码片段以及训练玩具模型都很容易,但处理大数据和相应的大型深度学习模型却很困难。这种影响在某种程度上因其与 Google Drive 的强集成而得到缓解,但 Drive 并不总是存储深度学习数据的理想位置。实际上,这使得 Colab 成为一个玩弄各种 Python 代码的神奇沙盒,但它缺乏能力和冗长的定时会话,而这将实现更高级的模型训练。
像 Kaggle 一样,Colab 用户不能选择他们的机器类型,除了是否使用额外的机器类型,在他们的情况下是 TPUs 和 GPU。另外值得注意的是,Colab 不再发布平台上有哪些GPU 的信息,但过去他们会将 V100s、P100s、K80s 和 T4s 的访问权限提供给用户。Colab 的付费 Pro 和 Pro +版本对更好的 GPU 给予更高的优先级,但用户永远无法自行选择,许多用户报告称,即使在更高的层级付费 计划(单击每个单词/链接查看不同的用户报告)中,也从未获得更强大的 GPU。然而,与 Kaggle 不同的是,实际上有各种潜在的 GPU 选择,因此用户仍有机会获得更好的选择,如强大的 V100。
Colab 是 Kaggle 的一个很好的替代品,并且可能是在功能上最接近 Kaggle 的,这要归功于他们对简化环境的共同关注。由于没有每周 GPU 和 TPU 运行时限制,它甚至可能是更好的选择。也就是说,如果你想要一个像 V100 这样强大的 GPU,那么首先去亚马逊 Sagemaker 或 Paperspace Gradient 可能会更好,因为在那里它们会得到保证(尽管这可能因地区而异)。即便如此,Kaggle 的 P100s 通常也会超过 Colab 用户可能使用的 K80。
亚马逊 SageMaker
亚马逊 SageMaker 可能是企业数据科学领域最知名的端到端机器学习平台。SageMaker 拥有众多额外的功能,使这款强大的产品与 Kaggle 区别开来。从数据标签等预处理辅助工具到培训和部署能力,所有这些都在 Jupyter Labs 这样的环境中进行。SageMaker 还提供了第二个最强大的、第一个最多样化的 GPU 列表,这是这篇博客文章中提到的所有平台中的一个。它也可能是这个列表中最可靠的,因为亚马逊是云虚拟机世界中最大的玩家,拥有最大的支持团队。也就是说,虽然 SageMaker 与 Kaggle 相比功能强大,但它有一个当之无愧的名声,就是对用户来说不是很直观,而且价格比其他付费竞争对手高。
第一次使用 SageMaker 的用户通常很难理解它的文档、定价和使用。它对企业和技术用户的关注使得业务分析师和非技术用户更难上手。它还经常优先考虑平台的速度而不是定制选项,这对于许多用户来说表现为缺乏灵活性和可理解性。对于许多有这类平台经验的用户来说,这不是问题,但是值得注意。
有经验的用户关心的是定价。Kaggle,Colab 和 Gradient 都有免费笔记本在他们的平台上运行的选项。虽然这对许多企业级用户来说不是问题,但个人数据科学家和机器学习工程师可能会选择避开该平台,转而选择免费选项。此外,他们的 GPU 定价在许多情况下比竞争对手更贵。例如,一个 100 - 80 GB 的 Paperspace 在 Gradient 上的价格是 25.54 美元,在 SageMaker 上的价格是 32.77 美元。即使考虑到增长计划的每月定价,Paperspace 的更高 GPU 内存集群也只需要 5.4 个小时,比亚马逊的选项更便宜。
我应该使用哪个 Jupyter 笔记本服务?
基于这些观察,我们建议从 Gradient 的免费 GPU 笔记本开始,将作为 Kaggle IDE 的首选替代品。凭借免费的 GPU 和 CPU、50 GB 的存储、不间断的服务、直观的 UI、部署能力等等,很难想象 Gradient 不是任何用户(从初学者到专家)的理想平台。如果你想进一步扩大你的项目,请查看付费计划,因为免费的 GPU 机器类型(带 M4000)可能不足以满足你的需求。
欲了解更多关于纸空间梯度的信息,请访问paperspace.com或阅读我们在https://docs.paperspace.com/的文档
图像处理中的图像概念
原文:https://blog.paperspace.com/the-concept-of-images-in-image-processing/
为了正确地掌握计算机视觉或一般图像处理中的过程或概念,理解数字图像的本质是必要的。在这篇文章中,我们将看看在数字空间中究竟是什么构成了图像,以便更好地理解和处理它们。
光学和数字感知
从光学角度来看,当物体反射的光进入人眼时,信号就会被发送到大脑,让我们能够感知物体的形状和颜色。另一方面,在数字环境中,计算机不具备感知形状和颜色的能力,而是将图像感知为空间平面中的数字集合。
数字图像的属性
当谈到数字环境中的图像时,人们通常会想到三个主要属性。分别是,维度,像素,通道。在本节中,我们将逐一介绍。
尺寸
维度也称为坐标或轴,表示空间平面上的一种参考形式(因此称为空间维度)。考虑一张平面纸,将它的一边支撑起来,使它面向你。想象我们在这个平面上的任意点上,我们只能垂直(上下)、水平(左右)或两者的某种组合(成一定角度)移动。因为你在这个平面上的所有运动都可以用这两个方向来概括,为了以任何有意义的方式移动,我们需要两条参考线,一条水平的称为“x ”,另一条垂直的称为“y ”,如下图所示。更准确地说,水平参考可以称为维度 x,垂直参考维度 y。因为这两个维度足以描述这张纸上的任何运动或图形,所以我们可以说它是二维(2 维)表示。
现在想象一下,如果那张纸只是一个立方体的六个表面中的一个。我们一直在这个表面上移动,表面的水平和垂直边缘分别代表 x 和 y。例如,如果我们想进入立方体的中心,我们需要另一条垂直于(90 度)当前表面的参考线。让我们称这条参考线为维度 z。突然之间,我们可以使用所有 3 个维度在这个假想的立方体内移动到任何地方,所以这个立方体被称为 3-D(3-维)表示。
正如我们之前提到的,我们提到的所有 3 条参考线都称为尺寸。如果我们有某种数学背景,我们会发现术语 x、y 和 z 是笛卡尔坐标系中轴/维度的上下文精确描述(只是 3d 空间结构的另一个花哨名称)。在更几何学的意义上,它们被称为宽度、长度和深度,在地理学上它们是经度、纬度和高度。然而,在图像处理中(使用 Python 编程语言),它们分别被称为尺寸/轴 1、尺寸/轴 0 和尺寸/轴 2。
尺寸和位置的命名
空间平面上的任何一点都可以通过参照它在所有维度上的位置来定位。考虑 2-D 平面上位置(3,5)处的点 k,这意味着该点位于距离 x 轴上的原点 3 个单位和距离 y 轴上的原点 5 个单位处。出于命名目的,如果空间结构中的点出现在二维表示中,则命名为(x 位置,y 位置),如果出现在三维表示中,则命名为(x 位置,y 位置,z 位置)。
像素
像素是构成数字图像的数字表示。当处理浮点值时,它们的值可以从 0(无强度)到 1(最大强度),或者当处理整数值时,从 0(无强度)到 255(最大强度)。这些像素被放在一起形成一个网格(行和列),其尺寸与上一节中提到的尺寸相同,从而形成一个二维图形。
考虑我们在上一节中用于说明目的的 2-D 平面。想象这些轴被封闭起来形成一个正方形,如上图所示,那么它实际上就变成了一个 9 列 9 行的网格。当这个网格用像素填充时(为了方便起见,用绿色的星星表示),它变成 9 行 9 列的图像,这意味着有 9 列和 9 行像素。更简洁地说,它变成了一个(9,9)像素的图像。为了确定该图像中存在的总像素数量,我们简单地将二维中存在的单元数量相乘,在本例中为 9*9 = 81 个像素。
像素&位置的命名
在使用 Python 编程语言进行图像处理的情况下,将对我们一直使用的 2-D 平面进行轻微的修改。首先,平面的原点现在位于左上角,而不是左下角。其次,y 轴重命名为 dimension-0/axis-0,x 轴重命名为 dimension-1/axis-1。最后,从 0 开始计数,也就是说原点后的第一个度量是 0 而不是 1。
实现了所有这些更改后,我们现在有了一个标准的 Python 数组。该图像中像素的命名基本上是数组中元素的索引。右上角的像素位于索引[0, 8]
,因为它位于第一行(行 0)和第九列(列 8)。在同一叶片中,第五行中的像素被索引为[4]
(第 4 行)。使用[row-number, column-number]
形式的数组索引可以定位图像中的任何像素。
创建简单的图像
在数学中,有一个由行和列组成的数学公式,这个公式叫做矩阵。就像上面的示例(9,9)图像一样,矩阵由数字类型的行和列填充。在 Python 中,可以使用 NumPy 库将矩阵创建为数组,因此可以合理地说,计算机将数字图像视为像素数组。
为了证明这一点,我们不妨创建一个(9,9)零数组,并尝试将其可视化,如下所示。正如我们所看到的,由于数组中的所有元素(像素)都是 0(无强度),所以数组在可视化时显示为一个黑色的图像。暂时允许在 imshow()方法中使用“cmap”参数,稍后会变得更清楚。
# import these dependencies
import numpy as np
import matplotlib.pyplot as plt
# creating (9, 9) array of zeros
image = np.zeros((9, 9))
# attempting to visualize array
plt.imshow(image, cmap='gray')
Image produced when the array is visualized.
当数组中不是所有元素都为零时会发生什么?请记住,在前面的章节中提到,当处理整数时,像素值的范围可以从 0(无强度)到 255(最大强度)。因此,从理论上讲,如果我们创建一个填充了一系列递增的整数值的数组,我们应该可以获得像素逐渐变亮的图像。让我们看看这在实践中是否成立。
# creating a 1-D array (vector) of elements ranging from 0 to 80
image = np.arange(81)
# reshaping vector into 2-D array
image = image.reshape((9, 9))
# attempting to visualize array
plt.imshow(image, cmap='gray')
Pixels become progressively brighter.
在上面的代码块中,创建了一个元素范围从 0 到 80 的“一维数组”(实际上称为 vector)(因此总共有 81 个元素)。接下来,向量被整形为(9,9)数组,然后被可视化。显然,由于阵列中的像素(元素)从 0 增加到 80,像素强度从最暗到最亮逐渐增加,所以事实上像素强度理论证明是正确的。
image
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8],
[ 9, 10, 11, 12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23, 24, 25, 26],
[27, 28, 29, 30, 31, 32, 33, 34, 35],
[36, 37, 38, 39, 40, 41, 42, 43, 44],
[45, 46, 47, 48, 49, 50, 51, 52, 53],
[54, 55, 56, 57, 58, 59, 60, 61, 62],
[63, 64, 65, 66, 67, 68, 69, 70, 71],
[72, 73, 74, 75, 76, 77, 78, 79, 80]])
The array created using the code block above.
方差和相对像素强度
当处理图像中的像素时,每个像素的强度或亮度基于所有像素的整体性质。换句话说,像素值必须有变化,亮度才能发挥作用。例如,所有元素的值为 100 的数组将显示为涂黑的图像(类似于零数组),因为其元素的方差为 o。
此外,当处理整数像素值时,如果所有像素都在 0 和 10 的范围内,则像素亮度将在这些值之间逐渐缩放,其中 0 为最暗,10 为最大亮度,5 为最大亮度的 5/11。但是,如果值介于 1 和 100 之间,则值为 1 的像素看起来最暗,值为 100 的像素看起来最亮,值为 10 的像素的亮度是最大值的 1/10。
创造有意义的图像
很明显,通过把不同亮度的像素放在一起,可以产生一个想要的图形。仍然利用我们的(9,9)像素“画布”,让我们尝试产生如下所示的数组。
这个数组由一堆 0 和 1 组成,其中的 1 似乎突出了字母“J”的轮廓。基于我们对像素强度的了解,我们知道“零”像素没有强度(没有亮度),因此它们看起来是黑色的;我们还知道“1”像素将具有最大强度——在这种情况下是最大强度,因为 1 是最大值。不用说,这个数组在可视化时会产生一个字母“J”的图形。
# creating the array
image = np.zeros((9, 9))
image[1, 2:-2] = 1
image[1:-1, 4] = 1
image[-2, 2:4] = 1
plt.imshow(image)
所以,是的,把不同强度的像素放在一起会产生想要的图形,如果做得足够用心的话。下一个问题是,是否可以将像素放在一起形成更复杂的图像,比如人脸图像?这个问题的答案是肯定的!当然,我们需要 81 个以上的像素,因为人脸包含很多细节,但熟练地将像素放在一起形成复杂而逼真的图像基本上就是自动编码器、生成敌对网络和 DALL-E 等生成模型所要做的。
基本图像处理
随着对图像本质上是数组的认识,通过与它们的数组表示进行交互就可以简单地操作它们。适用于矩阵的任何数学运算也适用于数字图像。在同一个 vane 中,适用于 Python 数组的任何操作也可以适用于数字图像。
一个非常简单的对图像操作有重要影响的数组操作是数组索引和切片。只需对数组进行切片,就可以将图像旋转成直角。下面的代码演示了这个过程。
def rotate(image_path, angle):
"""
This function rotates images at right angles
in a clockwise manner.
"""
if angle % 90 != 0:
print('can only rotate at right angles (90, 180, 270, 360)')
pass
else:
# reading image
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# rotating image
if angle == 90:
image = np.transpose(image) # transposing array
image = image[:, ::-1] # reversing columns
plt.imshow(image, cmap='gray')
elif angle == 180:
image = image[::-1, :] # reversing rows
image = image[:, ::-1] # reversing columns
plt.imshow(image, cmap='gray')
elif angle == 270:
image = np.transpose(image) # transposing array
image = image[::-1, :] # reversing rows
plt.imshow(image, cmap='gray')
else:
image = image
plt.imshow(image, cmap='gray')
pass
使用上面的函数,可以以这样的方式操纵图像阵列,即重新排列它们的像素,以给出原始图像的旋转版本。
Results obtained when the above function is used.
人们也可以通过索引和分配新值来显式地改变图像中像素的值。例如,让我们假设我们想从图形‘J’生成一个图形‘T’。这是一个简单的消除“J”的尾部的问题,它是由第 7 行、第 2 和第 3 列的像素形成的。我们可以通过索引这些像素并赋予它们零值来做到这一点,因此它们没有亮度。
# indexing and assigning
image[7, 2:4] = 0
频道
通道是决定图像颜色的图像属性。将我们的思维拉回到维度部分中呈现的 3d 空间表示,为了正确地表示图像,我们还需要考虑 z 轴。事实证明,一个图像可能不只是由单个数组组成,它也可能由一堆数组组成。堆栈中的每个数组称为一个通道。
灰度图像只由一个阵列组成,因此它们只有一个通道,就像我们在上一节中处理的所有阵列一样。另一方面,彩色图像是由 3 个相互叠加的阵列组成的,因此它们有 3 个通道(把这些通道想象成相互叠加的纸片)。
彩色图像中的通道
彩色图像中的每个通道代表一个像素阵列,该阵列只能产生红、绿、蓝(RGB)三种颜色中的一种。在每个彩色图像中,这些通道的排列总是相同的,第一个是红色,第二个是绿色,第三个是蓝色。
基于这三个通道上相应像素的强度,可以形成任何颜色。让我们用我们的图“J”来测试一下。我们将尝试创建它的不同变体,每个变体将有不同的颜色。
黑色画布上的红色 J
如题所述,我们试图创建一个黑色背景的红色图形“J”。为了生成黑色背景,我们基本上需要背景中的所有像素都没有亮度,因此像素值为零。因为我们试图创建一个红色的图形,只有红色通道中的像素轮廓被“打开”,只允许红色通过,如上所示。
# creating a 9x9 pixel image with 3 channels
image = np.zeros((9,9,3)).astype(np.uint8)
# switching on pixels outlining the figure j in the red channel (channel 0)
image[1, 2:-2, 0] = 255
image[1:-1, 4, 0] = 255
image[-2, 2:4, 0] = 255
plt.imshow(image)
黑色画布上的绿色 J
与上面生成的红色图形类似,要生成绿色图形,我们只需打开绿色通道中的轮廓像素。
# creating a 9x9 pixel image with 3 channels
image = np.zeros((9,9,3)).astype(np.uint8)
# switching on pixels outlining the figure j in the green channel (channel 1)
image[1, 2:-2, 1] = 255
image[1:-1, 4, 1] = 255
image[-2, 2:4, 1] = 255
plt.imshow(image)
黑色画布上的蓝色 J
同样,为了在黑色背景上生成蓝色图形,只有蓝色通道中的轮廓像素被打开,如上所示。
# creating a 9x9 pixel image with 3 channels
image = np.zeros((9,9,3)).astype(np.uint8)
# switching on pixels outlining the figure j in the blue channel (channel 2)
image[1, 2:-2, 2] = 255
image[1:-1, 4, 2] = 255
image[-2, 2:4, 2] = 255
plt.imshow(image)
酒红色画布上的蓝绿色 J
现在来看看更复杂的东西。目标是生成一个带有酒红色背景的蓝绿色图形“J”。为了产生酒红色背景,酒红色 RGB 值(128,0,32)需要在构成背景的像素的相应通道上复制。
在同一个叶片中,要生成蓝绿色的图形“J”,蓝绿色 RGB 值(0,128,128)需要分配给相应通道中的轮廓像素,如上图所示,并在下面的代码块中复制。
# creating array
image = np.zeros((9,9,3)).astype(np.uint8)
# assigning background color in each channel
image[:,:,0] = 128
image[:,:,1] = 0
image[:,:,2] = 32
# outlining figure j in each channel
image[1, 2:-2, 0] = 0
image[1, 2:-2, 1] = 128
image[1, 2:-2, 2] = 128
image[1:-1, 4, 0] = 0
image[1:-1, 4, 1] = 128
image[1:-1, 4, 2] = 128
image[-2, 2:4, 0] = 0
image[-2, 2:4, 1] = 128
image[-2, 2:4, 2] = 128
plt.imshow(image)
彩色到灰度
由于在数字环境中区分彩色和灰度图像的唯一因素是通道的数量,理论上,如果我们能够找到一种方法,将彩色图像中的所有 3 个通道压缩为一个通道,我们就可以将彩色图像转换为灰度图像。这个理论行得通。
有几种技术可以做到这一点,但是,最简单的方法是简单地取通道间相应像素的平均值。
# computing the average value of pixels across channels
image = image.mean(axis=2)
Matplotlib 中的灰度图像
如前所述,灰度(黑白)图像只有一个通道,类似于前面章节中使用的单个阵列。然而,当可视化这些单个数组时,我们必须利用 Matplotlib 中的“cmap”参数。这样做的原因是因为 Matplotlib 不会隐式地将单通道数组识别为图像数据,因此它们不会显示为灰度,而是显示为彩色地图。
Color map produced as a result of visualizing a single array.
然而,为了生成上面图像的灰度版本(通过设置 cmap='gray ')我们需要创建一个 3 通道阵列,将背景像素设置为零,并将每个通道上的轮廓像素切换到全强度(255),因为这将生成一个黑色背景和一个白色(255,255,255)的图形。
# creating array
image = np.zeros((9,9,3))
# setting outline
image[1, 2:-2, :] = 255
image[1:-1, 4, :] = 255
image[-2, 2:4, :] = 255
plt.imshow(image)
结束语
在这篇文章中,我们已经能够对构成数字图像的基本元素形成一种直觉。我们从空间结构中作为参考点的维度讨论到像素,利用亮度形成图像的数字类型,最后是通道,通过组合不同强度的红色、蓝色和绿色来形成颜色。
数据隐私的未来
正如许多人所知,数据访问是强大的监督学习模型在医疗保健等关键领域应用的限制之一。如果没有确保隐私的策略,这些数据将会被锁定。但是,新的策略正在出现,可以允许机器学习模型在没有真正看到这些数据的情况下根据这些数据进行训练。
英特尔人工智能产品部门的高级主管 Casimir Wierzynski 带我参观了最新的战略,这些战略有望解开解放人工智能所需的数据。Cas 还谈到了针对量子计算机的代码破解能力强化加密。
卡西米尔·维尔津斯基:
我目前在英特尔的职位是人工智能产品组的 CTO 办公室。我来自一个人文主义者的家庭,所以我现在在一个技术岗位上有点可笑。我妈妈是律师,我爸爸是记者。事实上,我最近从事的一些隐私保护工作,在某种程度上得益于我的新闻背景。
这些人工智能系统有如此多的前景,我们希望释放所有的潜力,并在我们周围的数据中找到所有这些好东西。人工智能系统从根本上是由数据塑造的。但是这些数据越来越隐私和敏感。你如何调和这两者?
关于隐私的整个领域,激励我的一件事是,隐私是一项人权。我爸爸来自波兰。他在 1945 年离开了波兰,先是去了欧洲,然后去了美国,但是在戒严期间,我在波兰还有表亲和叔叔等等。我记得人们必须向政府登记他们的打字机。
所以,我现在领导着一个团队,专门研究这个新兴领域,叫做“保护隐私的机器学习”。在保护隐私的机器学习保护伞下,有一套技术可以用来从数据中获得洞察力,而不必明确地查看数据的细节。
你想这么做有两个原因。一个是基本的安全和隐私,确保你没有数据泄露等等。但另一个原因是,现在您可以启用这些全新的应用程序。假设你有一组医院希望以某种方式共享他们的数据,以便在 MRI 扫描中建立一个更可靠的脑肿瘤检测器。统计学就是这样,你拥有的数据越多,你就越准确。但是很明显,共享病人数据是有问题的。
因此,有些情况下,人们想要提取数据,但出于很好的理由,他们不能。现在,使用这些隐私保护技术,你可以打开整个空间。不仅仅是医疗保健,金融服务业也是如此。如果你想检测邪恶的活动,欺诈,诸如此类的事情。银行显然想这么做,他们显然想集体行动来这么做。但是他们对数据有着非常真实的隐私担忧。
这是一个新兴领域,但已经有一些核心技术,它们正处于为现实世界做准备的不同阶段。因此,假设您想要共享数据;我刚刚谈到的例子是,人们希望集体操作数据,而不必明确地共享数据。有一些技术叫做联合学习或多方计算。所以那是一桶。另一个方面是你对加密数据的计算。因此,一方可以接收来自某人的加密数据,而无需解密,对这些数据进行某种运算。然后仍然给别人一个答案,即使他们从来没有看到潜在的数据。所以在我看来,这种技术是最神奇的,它叫做同态加密。围绕隐私还有另一套重要的技术。所以,如果你观察一个数据集,然后建立某种统计模型来学习这个数据集,从某种意义上来说,你不会想学得太多,你不会想记住那个数据集中的个别细节。你实际上想从中提取更大的不规则性。所以差别隐私是一种帮助你实现这一目标的技术。
我们可以依次讨论每一个,从联合学习开始。这实际上是一种直截了当的方式,尽管它的工作方式很酷。想象一下,你有几个人持有私有数据,我们称他们为联盟。这听起来有点像星际迷航。这是你的联盟,然后从一些机器学习模型的初始空白版本开始。联邦的每个成员都会得到那张白纸。然后他们中的每一个人,在他们的私人数据上,并且他们的私人数据从不离开他们的前提,弄清楚他们需要什么调整来使模型在他们的特定数据上更好地工作。他们每个人都想出了如何改变模型,使其更好地为自己服务。他们与某个中央协调者共享所有这些更新,然后这个协调者基本上将所有这些建议的更新加在一起。现在你得到了一个新的候选模型,这个新的候选模型再次被联盟的所有成员共享,然后你再次迭代。他们计算另一组调整。随着时间的推移,它们会来回移动。你最终得到的是一个能满足所有人数据的模型。这就好像您已经处理了池数据集,但您从未直接处理过任何数据。
您来回传输的不是整个模型,而是模型的参数,因为其中一些模型有数以亿计的参数,在每一轮中,这些建议的更新可能只涉及这些参数的一小部分,您只发送增量;测试变化。大约一年前,我们和宾夕法尼亚大学的一名放射科医生一起做了一项研究。有一个标准数据集叫做 BraTS,是一堆图像。任务是尝试从 MRI 图像中分割出脑瘤。这些图像是在不同的机构收集的。因此,如果我们假装已经以联合的方式训练了这个模型,我们从一个机构中取出所有的例子,并把它们放在一个桶中,并把每个机构分成桶,我们可以明确地测试这种想法,即联合训练是否会使你获得与只拥有所有数据接近的相同的性能。在这种情况下,它工作得非常好。围绕 is 还有一些有趣的研究思路。当联邦的不同成员拥有大相径庭的数据时,挑战就出现了。然后这个问题变得更加尖锐,你想多测试一点。我们对我的团队做了一些研究,提出了一些策略,在这些策略中,你可以适应性地改变更新过程的速度,以适应机构之间可能存在很大差异的事实。
假设您有一家医院,他们有足够的数据来满足自己的需求。即使在那里,也有一个很好的理由通过进行联合学习来扩展数据集,因为你可以确保你的模型实际上概括了底层的任务,而不是一些伪造的东西。
现在,同态加密是一种加密数据的方式,遵循希腊词源。同形意味着具有相同的形状。因此,当您将数据从未加密的世界移动到这个加密的空间时,数据相对于彼此仍然具有相同的形状。您仍然保留了数据之间的一些相对结构,但是您当然模糊了实际的数据是什么,因为它是加密的。特别是,在这个加密的世界里,一个简单的数字变成了一个非常高阶的多项式。你还记得从高中开始,多项式就是 2x + 3x² - 2x^3 之类的东西,除了在这种情况下,x 的幂一直到,比如说,4096。在加密的世界里有这些非常大的多项式。
如果你在加密世界里把两个数字相加,然后把它们带回现实世界,你会得到你带来的两个东西的总和。因此,这是一种你可以在加密世界中操作的方式,以数学方式对应于现实世界中的操作,除了你从来没有真正看到底层数据。所以我们来举个具体的例子。医院进行扫描,他们使用自己的私钥加密扫描。现在是一堆多项式。他们把它发送到一些基于云的放射服务。放射科的服务完全是基于多项式的。他们不知道底层数据是什么。多项式回到医院,医院现在使用它的密钥,只有他们可以解锁这个东西,并获得他们正在寻找的诊断。
对于差分隐私,让我们来看一个例子,你的手机上有预测文本。你开始输入一个单词,然后你的手机预测下一个单词可能是什么。对于这种预测模型,你拿一堆短信数据,然后建立一个模型说,好吧,如果前一个词是“苹果”,那么下一个词可能是“馅饼”问题是,如果你的数据集中有人说,“我的信用卡号是,”然后他们说的下一件事就是他们的信用卡号,你不希望这种粒度级别出现在你的模型中。你不希望别人输入“我的信用卡号码是”,然后突然弹出你的号码。为了避免这种情况,我们使用差分隐私。有人称之为模糊数据或在训练数据中添加一点点噪声,以便迫使机器学习模型学习英语和基本语法的整体统计数据,但它不会有足够的统计能力来查看这些微小的细节,如个人信息。这是差异隐私的一个例子。
可能有二、三十多篇论文讨论了如何从某种模型中获得最大的效用,同时又能保护你所需要的隐私。在这个领域,隐私和实用性之间会有一点权衡。虽然我认为在实践中,人们发现在某种意义上有一个甜蜜点,一些模糊化可以使模型变得更好,因为机器学习的总体目标之一是泛化。概括的概念完全符合不要过度记忆数据集不同部分的想法,因为这些与你训练的任务没有密切关系。
有一种称为提取攻击的东西,实际上它与同态加密有很好的融合。机器学习模型实际上可以记忆或保存隐藏层中的数据,这些数据可以在训练后提取出来。你可以想到两种情况。一个是攻击者实际上可以访问模型,他们可以看到模型的所有参数以及它是如何构建的等等。这叫做白盒攻击。还有另一个版本,攻击者可以使用这个模型,他们可以向它发送东西并得到答案,但他们不能看到它是如何建立的。在这两种情况下,提取攻击都是可能的,但是如果您实际上能够访问模型的细节,那么提取攻击会容易得多。所以同态加密实际上可以成为保护模型细节的一种方式。我之前举的同态加密的例子是,我试图保护我提供给模型的数据的隐私。但是你也可以反过来,你可以加密模型,从而保护模型本身的机密性。
差分隐私解决了与同态加密略有不同的问题,因为差分隐私解决了将一组特定信息与特定个人联系起来的能力。同态加密和联合学习更多的是保密性。这些略有不同,但实际上是不同的问题,我们刚刚看了一个例子,在这个例子中,您可能希望两者都做,也就是说,您可能希望使用同态加密来防止有人训练数据来查看底层数据。但是,然后你会想使用差分隐私,以确保该模型没有学习训练集的细节。
我认为这有点像罗伯特·弗罗斯特的诗,“好篱笆造就好邻居。”如果我们可以做出很好的数学保证,我已经完成了这个机器学习操作,而你无法从数据中学到任何东西,你可以从个人和数据中学到的东西在数学上是有限的,我觉得这是一件正确的事情。就像你去超市看到麦片盒子上的成分表一样,这给了我信心,现在我可以出去买任何一盒麦片,并知道它会有某些特性。我认为这是人工智能成长所需要的基础工作。
我的设想,在某种程度上也是我的希望是,记得在网络早期,你在 Amazon.com 前面输入 HTTP,然后填写你的信用卡号。过了一会儿,人们说,‘嘿,你知道吗,好像我们不应该到处发信用卡号码。’然后对于某些非常敏感的事情,他们开发了这个叫做 HTTPS 的东西,这是一个安全的网络界面。然后人们逐渐被训练去寻找浏览器上的锁,这表明它是 HTTPS 的。但只有几页会被这样保护。过了一会儿,人们说,‘嘿,如果你能保护这一页,为什么不保护所有的页面呢?’?'
所以,现在几乎所有的东西都是 HTTPS 的。我觉得,对于机器学习来说,人们将对原始数据进行操作的想法会显得古怪、怪异和有点不雅。
为了达到 HTTPS 的目的,你需要一些东西。你需要使所有这些技术更加可用,这样做数据科学的人就不必担心在一些奇怪的空间里多项式有多大。您还需要某种程度的互操作性和围绕底层技术的行业共识。对于同态加密等一些事情来说,这已经开始发生了。我的团队成员正在与其他行业合作伙伴一起与正确的标准机构合作,以启动这一进程,使同态加密的各个方面标准化。围绕联合学习,其他标准团体已经做出了努力。所以我认为这将走到一起。
我们还没有谈到的一件事是,其中一些技术需要额外的计算。它们是计算密集型的。在 21 世纪初加密变得更加普遍的时候,英特尔曾经遇到过这种情况。有一种加密标准叫做 AES。我们在英特尔处理器产品线中添加了一条新指令,以加速加密方案的这一特定部分。因此,我们以前已经这样做了,我觉得对于其中一些协议,我们可能会再次这样做。我们需要提供硬件支持来加速这些非常特殊的计算。
可信执行环境是您可以在计算机上使用一定数量的内存来保持加密的方式。每当处理器需要访问内存时,它只会以加密的形式访问内存。然后,一旦它到达处理器的内部密室,只有到那时,我们才能解密它,进行某种操作,然后非常迅速地重新加密它。有一种方法可以加快这一过程,并使其更加有效。这绝对是我们可以使用的工具箱的一部分。
实际上,您可以两者都用,这是一种腰带和吊带的方法,您可以在其中一个可信区域内进行加密。假设两个不同的当事人都有他们想要保护的知识产权。一方拥有一个模型,而另一方拥有需要在该模型中操作的敏感数据。比方说,您可以使用同态加密来保护患者扫描,然后您可以使用 enclave 来加密模型。所以,现在你是在同时保护两个不同的党派。
密码社区正在密切关注量子计算机以及如何处理它。实际上,在 NIST 有一个过程,以前被称为标准局,他们正在研究什么是推荐的新加密系统,人们应该使用它来使它们成为所谓的后量子,或某种量子抵抗。
同态加密是一种称为基于格的加密方案的密码学家族,其中一些是后量子的。英特尔的一些人正在积极地向 NIST 建议下一个应该采用的标准是什么。其中一些是基于晶格的。
这些技术存在于一个受经济规律支配的世界,因此联合学习可能是一种方式,拥有私人数据仓库的人可以在不与任何人明确共享这些数据的情况下将这些数据货币化。这是一个非常有趣的可能性,因为它创造了市场,我以前是一名交易员,所以我喜欢围绕资源分配的市场机制。我认为这对这个领域来说是一个了不起的发展。
我的想法主要是围绕我们看到的直接的客户问题。所以我们还没有达到个人的水平,他们是否应该出售他们的个人数据。在医疗保健领域,已经有了数据共享协议,制药公司希望从医院获得数据,他们召集了大批律师,开出了一张大额支票。这是一个相当复杂的过程。促进这种企业对企业类型的互动将是一个非常好的起点。我知道杰伦·拉尼尔也在《纽约时报》上谈论个人出售他们的数据。这实际上是一个相当复杂的话题。有很多商业和社会上重要的 B2B 案例,我们希望尽快处理。
这篇文章改编自播客 Eye on AI 第 32 集的后半部分。在这里找到完整录制的剧集,或者在下面收听。
ML 的未来:无监督学习,强化学习,还是其他?
在过去十年的大部分时间里,监督学习一直是大多数人工智能研究的焦点,但机器学习的未来可能取决于无监督学习方法。我与约舒阿和萨米·本吉奥、扬·勒村、里奇·萨顿和谢尔盖·莱文谈论了机器学习的未来,以及什么可能让我们的机器达到人类水平的智能。
约舒阿·本吉奥:
无监督引导的学习方法是第一个允许我们训练深度网络的方法。然后在 2010-2011 年左右,我们意识到我们不需要这些无监督的学习技术。我们可以训练非常深入的直接监督模型。然后,计算机视觉、语音识别、机器翻译等工业应用开始迅速出现。但这对人类水平的人工智能来说是不够的。人类不需要那么多监督。
萨米·本吉奥:
不仅仅是监督和不监督。中间有很多东西。有自我监督,有强化学习。有很多方法可以从你已经拥有的数据中获得廉价的监管。所以,它变成了一个更加复杂的空间。连接所有这些的是你如何表示数据,所以表示学习实际上变得越来越重要。
去年与 Yoshua 和 Geoffrey Hinton 分享图灵奖的 Yann Lecun 谈到了他对自我监督学习的押注。
【yann lecun:
由于你需要大量的标记数据,所以你今天可以应用深度学习的东西是有限的。只有当您能够收集这些数据并且能够正确标记它们时,它才是经济上可行的,而且这只适用于相对较少的应用程序。
如果你有很多平行文本,监督学习对于分类对象和图像或者从一种语言翻译到另一种语言非常有用。如果你收集了足够的数据,它对语音识别非常有用。
但是动物有一些学习的过程,来获得它们所拥有的关于世界的所有知识,这是机器所没有的。我的钱花在自我监督学习上,让机器通过观察来学习,或者在不需要那么多标记样本的情况下学习,或许通过观察积累足够的背景知识,某种常识就会出现。
想象一下,你给机器一个输入,比如一个视频剪辑。你屏蔽一段视频剪辑,然后让机器从它看到的内容中预测下一步会发生什么。
为了让机器训练自己做到这一点,它必须开发数据的某种表示。它必须明白有些物体是有生命的,有些是无生命的。无生命的物体有可预测的轨迹,其他的没有。所以,你用这种自我监督的方式,用成千上万的数据训练一个系统。你可以让这台机器观看的 YouTube 视频数量没有限制。它会从中提取出一些世界的表象。当你有一个特定的任务时,比如学习开车或识别特定的物体,你使用这种表示作为分类器的输入,然后训练这个分类器。
我们能在某个时候制造出和人类一样聪明的机器吗?答案是,当然,毫无疑问。这是时间问题。
Rich Sutton 谈到了他开创的无监督学习形式:强化学习。
里奇·萨顿:
我在寻找类似强化学习的东西,因为如果你学习心理学,强化是一个显而易见的想法。有两种基本类型。巴甫洛夫条件作用和工具性或操作性条件作用。
巴甫洛夫条件反射就像,按铃,然后给狗一块牛排。过了一会儿,刚按完铃,他就垂涎三尺,表示他期待牛排的到来。所以,这是一种预测学习。
然后是控制学习,控制学习被称为工具性条件反射或操作性条件反射,至少是这两个名字,在这里你改变你的行为来引起一些事情发生。在巴甫洛夫条件反射中,你的唾液分泌不会影响发生的事情。而标准的操作条件是,老鼠按下一根棒,然后得到一个食物球。按压杆的动作有助于获得奖励。
这就是强化学习的理念。这是模仿动物和人类一直在做的一件显而易见的事情。在监督学习中,反馈指导你应该做什么。在强化过程中,反馈是一种奖励,它只是评估你做了什么。所以,评估和指导是最根本的区别。
最后,Sergey Levine,世界上最杰出的机器学习和机器人交叉领域的研究人员之一,谈到了如何通过他在伯克利人工智能实验室工作的机器人将无监督学习带入现实世界。
谢尔盖·莱文:
我们希望从长远来看,我们的工作可以成为未来的垫脚石,在未来,世界上有许多联网的机器人,当他们不忙着做更有生产力的事情时,他们只会玩他们的环境和学习。
他们基本上会说,‘好吧,如果我目前没有工作任务,如果我的人类主人不想让我做任何特别的事情,我就用我的空闲时间来练习。“我会摆弄周围环境中的物品,更多地了解这个世界是如何运转的,并利用它来建立我的知识体系,这样当我后来被置于某个新的环境中时,希望我已经从过去的许多情况中学到了足够多的东西,可以在这个新的环境中做一些合理的事情。”这就是转移。和所有的学习系统一样,这种转移来自于丰富的经验。
所以,如果你有足够的广度,你见过足够多的变化,那么你就为任何事情做好了准备。这就是梦想。现在的现实是,这只是朝着这个方向迈出的第一步。现在,机器人学习一个特定的环境。它花几个小时玩一扇门,移动它,它可以打开那扇门。我们下一步想做的事情之一是扩大规模。在楼下的实验室里,我们有六个不同的机器人,所以也许我们让他们都玩不同种类的门,也许然后我们会看到,当我们给它一个新的门时,它实际上会推广到那个新的门,因为它看到了足够多的变化。
多样性是一般化和转移的关键,但我也认为从长远来看,在机器人学中,这不应该是一个问题,因为机器人存在于现实世界中,与我们存在的现实世界相同。现实世界迫使你多样化。你不能逃避它。
我们的工作假设是,如果我们建立了足够通用的算法,那么我们真正要做的就是,一旦完成,把它们放在机器人身上,这些机器人在真实的世界里做真实的事情,各种各样的经验将会来到机器人身上,因为它们和我们一样在真实的世界里。所以,机器人基本上想象可能发生的事情,然后试图找出如何让它发生。当然,想象可能发生的事情需要了解世界上哪些是现实情况,哪些不是现实情况。
我可以给自己定一个目标。我可以说我想让这个杯子悬浮起来。对我来说,这将是一个很难达到的目标,因为在这个宇宙中,这并不现实。但是如果我给自己设定一个目标,让这个杯子向左移动 5 厘米,这是我可以学习和练习的,这将教会我一些关于这个杯子的物理知识。
这篇文章改编自播客 Eye on AI 第 30 集的后半部分。查看前半部分的气候变化,中国和艾,在这里找到完整录制的集,或者听下面的。
大脑三明治和大脑的连接组学
英特尔人工智能产品部门的高级主管 Casimir Wierzynski 向我讲述了他在大脑连接组学方面的工作,这种大脑“香肠切片器”使绘制我们大脑的神经网络成为可能。
卡西米尔·维尔津斯基:
我在大学里学的是电子工程,最后在华尔街做了一段时间衍生品交易员。在某种程度上,这本身就是人工智能的一种行为,因为交易就是在不确定的情况下做出决策,你可以把这看作是人工智能的一个很好的工作定义,让机器代替人来做这件事。做了几年之后,我有点渴望回到人工智能领域,真正地研究它。
我回到研究生院攻读博士学位。一个真正有前途的人工智能模型是人脑本身,所以我最终,有点惊讶地,研究了神经科学。如果你想弄清楚大脑到底是如何做事情的,你必须戴上一副手套,开始分解动物,戳脑细胞,看看里面发生了什么。有很多理论。我把它描述为寻找数据的理论。为了得到我在教老鼠完成任务时从它们大脑中记录的数据。他们白天学习,晚上睡觉。我试图理解学习和睡眠之间的联系,以及睡眠的计算功能。这是一个非常深刻的话题。在那之后,我被拉进了这个关于神经形态工程的非常有趣的项目。这个想法就是你可以制造模拟大脑的计算机芯片。你在芯片上有一些部分,就像神经元一样,产生尖峰信号,并相互发送动作电位。这让我回到了工程学,回到了如何建造智能的人工系统。
神经科学中流行的假设是,大脑的计算方式实际上是其连通性的一种功能。所以连接形式定义了函数。事物联系的方式告诉你事物是如何运作的。但这又引出了下一个问题。大脑中事物的联系有多具体?所以这个领域被称为连接组学。卡哈尔本人在 19 世纪晚期在显微镜下进行研究,对特定细胞进行染色,并跟踪它们的去向。很明显,人们对追踪连通性很感兴趣。但是,从电气工程师或计算机架构师的角度来看,他们实际上想要获得真正详细的布线图,比如具体连接到什么,这些数据很难获得,当然也很难以系统的方式完成。
所以,快进到 2007 年左右,哈佛大学的杰夫·李奇曼(Jeff Lichtman)等人开发了这些技术,这就像是一个自动的意大利香肠切片器,用来切脑组织,除了这些切片有 20 纳米厚,所以它们不能做一个很好的三明治。但是,这是你想要解决大脑连接的分辨率水平。杰夫有这个惊人的自动切片技术。这些小片段最终会被录在一盘塑料磁带上,就像录音棚里一卷一卷的磁带。然后他将这卷带子通过一台自动化的电子显微镜,你最终会得到这些大脑连续切片的超高分辨率图像。这是第一步。这已经像是一项英雄事业了。但是现在你只剩下这个非常有趣的计算问题了。
原则上,所有关于连通性的信息都在这些切片中,因为你可以看到脑细胞的轮廓,细胞膜,你有足够的分辨率来看到两层膜在哪里相遇,他们称之为突触按钮。你甚至有足够的分辨率来看到突触内部,突触是包含神经递质的小囊泡,神经递质将化学物质从一个脑细胞传递到另一个脑细胞。这是惊人的分辨率。如果你能想象在一张切片上描绘出所有这些东西的轮廓。然后在下一个切片上,你再次描绘出你所看到的所有轮廓。你可以将所有这些轮廓连接在一起,它会开始看起来像神经元和神经胶质以及大脑所有部分的三维骨架。我有幸,作为一名神经科学家,真正的乐趣在于,在计算方面与麻省理工学院的 Nir Shavit 教授合作。
你如何加快这个速度?因为,如果要用宇宙的年龄来做,那就不实用了。所以我们给自己设定了一个目标,让电脑跟上显微镜。这将是一个很大的门槛,因为这样你的数据就不会积压。我认为,通过围绕系统工程做一些聪明的事情,并充分利用英特尔处理器的所有优点,我们可能正在接近这个门槛。
因此,神经元的胞体,即神经元的细胞体,通常约为 20 微米。所以这相当于 20,000 纳米与 20 纳米的对比。所以你建立了一千层来观察一个神经元的三维图像。你可以创造这些美丽的视觉效果。
关于大脑,我认为有一点没有得到充分的重视,那就是所有这些线路和脑细胞的紧密程度。是什么让 Cajal 能够仅仅使用染色和显微镜来做这么多的神经解剖学追踪,是这项技术他和 Golgi 发现只有少数神经元会被染色,所以你可以看到它们之间的空白空间。你可以想象神经元的样子。
所以我们可以用连接组可视化做的很酷的事情是打开一半的脑细胞,看到它们是如何连接的整体完形。但是你也有数字形式的信息,所以你可以做各种很酷的统计,比如在这个大脑区域和这个大脑区域之间,一个细胞平均接触多少个脑细胞。你可以建立这样的统计数据,这是你做科学研究所需要的。
所以,我最近看到的项目是切片一立方毫米的大脑,这花了大约一个月的时间。这是在一台切片机上,但它几乎是全天候运行的。在人类基因组计划中,有一种叫做桑格测序的最初的测序方案。为了让它在单台机器上可靠地工作,我们付出了一些努力。然后一旦他们在单台机器上得到它,他们就把体育馆塞满了桑格测序机器。所以你可以想象一个月对于一立方毫米来说,它是线性增长的。有一天,它会变成 30 台机器等等。
我认为老鼠大脑肯定会在未来十年内出现。果蝇,我们的朋友果蝇,在连接组学水平上已经基本完成了。所以我们正在进化的阶梯上前进。
有意思的是,现在的瓶颈其实还是计算步骤,而不是切片步骤。
这篇文章改编自播客 Eye on AI 第 32 集的前半部分。在这里找到完整录制的剧集,或者在下面收听。
新的 Paperspace 社区
我们总是对用户使用 Paperspace 构建的东西感到惊讶,并认为提供一个人们可以分享想法、提问、学习新工具和技术并最终相互授权的空间非常重要。
我们最近添加了一个社区,我们希望正式与大家分享。该社区设计有几个涵盖不同使用案例的顶级类别,以及针对服务本身的问题/建议的 Paperspace 特定类别。有很多有用的功能,比如标记,开始投票,书签,将回答标记为问题的答案等等。
我们发现最有帮助的是关于让您的环境运行、新技术等的教程和内容。这里有一个在新的 NVIDIA Volta V100 GPU 上运行 Docker 的示例教程。除了提出问题或解决你的特定问题,这是我们认为对社区最有价值的帖子。
我们希望您看一看并探索其中的一些内容。最重要的是,我们鼓励每个人提出问题,分享你学到的可能对他人有帮助的东西!
❤️纸空间团队
动手操作 Google TPUv2
原文:https://blog.paperspace.com/the-tpu-is-weird-but-its-going-to-change-everything/
谷歌的张量处理单元(TPU) 已经在人工智能/人工智能社区引起了轰动,原因有很多。
目前,训练深度学习模型需要巨大的计算能力(和大量的能量)。毫无疑问,英伟达已经成为训练深度学习模型的黄金标准,大多数框架都建立在 CUDA 之上。新 TPUv2 的发布已经开始改变这种对话。
在过去一年左右的时间里,我们看到了人工智能芯片领域的许多新发展。像英特尔、 Graphcore 、脑波强化系统、 Vathys 等公司一直在竞相开发更新的硬件架构,这些架构承诺采用 GPU 的通用计算模型,并专门为训练和部署深度学习模型的任务而构建。
在过去的几周里,我们一直在努力将新的 TPUv2 集成到我们的机器学习套件和名为 Gradient 的人工智能工具中。一路走来,我们对这种新硬件了解了很多,更重要的是,机器学习硬件的未来是什么样子。让我们开始吧。
TPU 概述
TPU 是这些新芯片中第一个公开上市的,我们刚刚开始看到现实世界的性能基准。RiseML 的团队最近发布了一项基准测试,将 TPU 与英伟达 P100 和 V100 芯片组进行了比较,第一批结果至少可以说是令人兴奋的。
TPUv2 在几个方面不同于传统的 GPU。每个芯片实际上结合了 4 个 TPU 芯片,每个都有 16GB 的高性能内存。TPUv2 能够寻址高达 2,400GB/s 的内存带宽,像 GPU 一样,它旨在连接到多个 TPU 节点,以实现更快的并行计算。
也就是说,有几个关键限制值得注意:
- TPU 目前只支持 TensorFlow,尽管还有工作正在进行以支持 PyTorch 。
- TPU 很贵。节点本身为 6.50 美元/小时,您还需要一个计算节点来连接它,因此实际上您正在考虑使用 TPU 时为 7.00 美元/小时。也就是说,凭借出色的性能,在正常的性价比范围内,它实际上可能比其他 GPU 选项更具成本效益。
- 它还没有完全公开。这可能会很快改变,但就目前情况来看,你仍然需要申请成为测试计划的一部分,才能获得 TPU。
TPU 存储模型
在 GPU 和 TPU 上运行时,机器学习任务使用存储的方式有一些关键差异。对于传统的 GPU 支持的训练,GPU 必须依靠运行在 CPU 上的主机进程将数据从本地文件系统或其他存储源拉入 CPU 的内存,并将该内存传输到 GPU 以供进一步处理。
我们依靠 nvidia-docker 容器来管理硬件依赖性,并为 GPU 提供本地存储访问。此外,每个渐变 GPU 作业的基础是一个共享的持久数据存储,它会自动挂载到/storage
,也可以用作基于 GPU 的培训的主要存储源。
在深度学习任务中,IO 可能会很快成为瓶颈,我们已经对梯度存储架构进行了大量优化,以便能够通过多个任务处理单个数据源来充分饱和 GPU 培训工作。
TPU 存储访问与 GPU 完全不同。TPU 目前利用分布式 Tensorflow 框架来为培训课程拉和推数据流。这个框架支持几种不同的文件系统插件,但是 Google 选择 Google 云存储作为目前唯一支持的 TPU 存储模型。
在 TPU 上,在“计算节点”上运行任何代码都被认为是次优的——理想情况下,您可以使用 XLA(加速线性代数)架构将尽可能多的 TensorFlow 代码编译成可执行单元。这需要对大多数现有代码进行轻微的修改,以使用一些核心张量流算法的 TPU 特定版本。反过来,这些 TPU 特有的例程需要使用谷歌云存储中的数据源和接收器来运行。
对于 TPU 的梯度访问,我们会自动为您的机器学习作业自动提供一块谷歌云存储。在 TPU 张量流作业中,输入数据流位置通常称为 dataflow_dir,输出位置为 model_dir。在训练作业完成时,TPU 输出被自动加载到可通过网络访问的工件目录中,以供下载和浏览。
作为网络设备的 TPU
TPU 和 GPU 之间的另一个主要区别是,TPU 是作为网络可寻址设备提供的,而 GPU 通常是 PCI 总线上本地连接的硬件设备。TPU 监听 IP 地址和端口,以执行 XLA 指令和操作。
在谷歌目前的 TPU 部署模式中,这些网络设备驻留在谷歌定义的 TPU 项目和网络中,与你的个人或企业谷歌云资源相分离。TPU 项目和网络特定于您的帐户,但您不能通过 Google 的控制台或 API 直接访问它们。
要在这个独立的环境中使用 TPU,您必须首先在您的网络资源和谷歌管理的 TPU 项目和网络之间建立各种信任关系,包括设置具有特定服务帐户角色的虚拟机实例,并授予 TPU 访问谷歌云存储桶的权限。Gradient 的 job runner 通过自动管理所有这些关系简化了这一过程,从而避免了大量复杂的手动设置和配置。
TPU 名称解析
TPU 在谷歌的云中也有人类可读的名称,类似于 DNS 名称。Google 提供了一个基本的名称解析库,用于将 TPU 名称转换为网络地址和端口(实际上是一个 TPU gRPC URL,即grpc://${TPU_IP}:8470
),这样您的代码就不必对这些信息进行硬编码。
TensorFlow 1.6 中包含该库的一个版本,但我们已经看到它在 TensorFlow 1.7 中出现了一些突破性的变化。我们还发现,TPU 域名解析库需要更高级别的权限才能访问所需的 Google Cloud APIs 并正常运行。
由于我们一直在努力将 TPU 整合到梯度中,我们试图通过预先解析 TPU 名称并在 TensorFlow 容器中提供一个可用的TPU_GRPC_URL
作为环境变量来解决这些问题。Gradient 还提供了一些补充的 docker 映像,这些映像为想要使用这些功能的用户预装了所需的 TPU 名称解析依赖项。
在 TPU 上运行代码
TPU 很奇怪,因为它要求你对传统深度学习任务的看法略有改变。目前,TPU 仅支持 TensorFlow 代码,将现有代码移植到新架构有一些要求。
在高层次上,TPU 文档推荐使用新的TPUEstimator
类,它抽象了在 TPU 上运行的一些实现细节。对于大多数 TensorFlow 开发人员来说,这是众所周知的最佳实践。
my_tpu_estimator = tf.contrib.tpu.TPUEstimator(
model_fn=my_model_fn,
config=tf.contrib.tpu.RunConfig()
use_tpu=False)
你可以在这里查看更多:https://www.tensorflow.org/programmers_guide/using_tpu
今天就试试吧
有了 Gradient,您现在就可以轻松体验 TPU(视需求和供应情况而定)!
当你在 www.paperspace.com/account/signup 创建了一个账户并安装了我们的客户端后,你现在可以在云端的 TPU 上训练一个模型了。
首先,将位于https://github.com/Paperspace/tpu-test和cd tpu-test
的示例回购克隆到新目录中。
使用paperspace project init
初始化项目名称空间。现在,只需一行代码,您就可以将项目提交给运行在 Gradient 上的 TPU。
paperspace jobs create --machineType TPU --container gcr.io/tensorflow/tensorflow:1.6.0 --command “python main.py”
如果您转到控制台,您将看到生成的作业和日志:
就是这样!你在 TPU 执行任务。想试试更先进的型号吗?检查我们在 TPU 上培训 Resnet-50 的回购
注意:ResNet-50 示例可能是一个长时间运行的作业,可能需要 20 个小时才能完成。
并行计算架构的未来
我们正在进入云机器学习的黄金时代。作为一家专注于为所有开发人员提供生产机器学习技术的公司,我们对新硬件加速器的爆炸式增长以及它们对最终用户的意义感到无比兴奋。
虽然 GPU 不会去任何地方,TPU 是第一个真正的竞争对手,它指出了一个未来,开发者将有他们选择的硬件和软件。
最佳形象艺人:朱莉·梅赫雷斯图
用遗传算法开发井字游戏代理:遗传算法入门(第 1 部分)
原文:https://blog.paperspace.com/tic-tac-toe-genetic-algorithm-part-1/
简介
Machine 学习是计算的一个不断发展的方面,它能够让系统从提供给它的数据中学习,这些系统可以分为
- 监督式学习:监督式学习只涉及从标记数据中学习,例如回归和分类。
- 无监督:无监督学习涉及一个从无标签数据中学习的系统或算法,它能够根据传递给它的数据中发现的相似性对数据进行分类,一个例子是 K 均值聚类算法
- 强化学习:这是一种解决机器学习问题的较新方法,其中给一个代理一个环境,并要求其与环境交互,每次交互都根据某些定义的规则进行评分和奖励或惩罚。
解决机器学习问题的方法有很多,其中一些可以分为监督学习和非监督学习,所以这并不总是一个是或否的分类。
什么是遗传算法
遗传算法是一种元启发式问题解决过程,其灵感来自于自然界中的自然选择过程。它用于在问题空间中寻找高度优化问题的解决方案,并通过遵循受自然过程启发的步骤来做到这一点,其中最强壮或最适合的个体被允许生存下来并将其遗传物质传递给下一代。这样做可以使下一代更好地适应他们所处的环境,或者更好地解决问题。
自然选择的过程
在自然界中,自然选择的概念支持适者生存的思想,即更适应环境的生物生存下来,并且最有可能将它们的特征/基因传递给下一代。它还涉及突变的概念,这意味着生物能够使其基因突变,以便能够生存或将适应其当前的环境。这些突变是随机的,在自然选择的过程中起着很大的可变作用。这种情况会在几代人或几个周期内发生,在人类身上也能看到:在某些个体的情况下,他们的祖先对疾病产生了免疫力,并幸存下来,能够将这种免疫力传递给他们的后代。
A 类似的过程可以应用于计算和问题解决,我们试图模拟这一过程的所有步骤,以便能够在现实生活中重现。
让我们分别查看组件和步骤。
- 人口
- 基因
- 健康
- 基因库
- 选择
- 交叉
- 变化
人口
这些是我们为样本问题生成的个体。它们是我们特定问题的个别解决方案,而且从一开始就都是错的。随着时间的推移,它们被优化,以便能够在它们的问题空间中解决问题。每一种都有称为基因和染色体的组成部分。让我们来看一个问题,我们想解决找到一个简单的文本,让我们说,文本是一个 24 个字母的字符串“我是我的剑的骨头”,初始化一个群体来解决这将只是一个 N 群体的字符串组成的随机集的 24 个字符。
基因和染色体
在遗传算法问题中,群体中个体特征的最小单位是基因。染色体是由基因组成的;他们定义了解决问题的方法。它们可以通过与您的用例相匹配的任何方式来定义,但是,在我们上面提到的简单示例中,我们希望优化我们群体的成员,使之等于短语“我是我的剑的骨头”,我们群体的基因只是我们群体中每个个体的字母,而染色体可以是全文。
健身(健身功能)
这是一个计算个人解决问题集的能力的函数。它给群体中的每个个体一个健康分数。这被用来评估这些基因是否会传递给下一代。除了第一个种群之外的每个种群都是由先前的个体组成的,上一代的平均适应值总是大于上一代。
基因库
基因库被认为是可传递给下一代的每一组可用基因(染色体)的列表。它被分配以支持更多具有更有利基因的父母,并且具有更有利基因的个体被多次添加到池中,以增加其被选择产生后代或将其性状传递给下一代的概率。一个具有较少特征的个体不会被经常添加,但仍然会被添加,主要是因为它可能具有我们仍然想要的令人满意的特征。
选择
在自然界中,拥有令人满意的特征的个体或更适应环境的个体能够繁殖并把他们的遗传特征传递给下一代是非常普遍的。这同样适用于遗传算法。在这种情况下,我们优先选择适应性分数较高的个体,而不是适应性分数较低的个体,这是通过确保基因库中有更多合适的个体可供选择来实现的。
一对个体被选择在一起,产生一个或更多的后代(取决于你的设计)。身体健康的人更有可能被选中。
交叉
这是结合两个父母个体创造一个或多个后代的过程。这涉及到父母双方染色体的交叉。要做到这一点,我们必须选择一个交叉点,或者它可以是随机的,或者是中间的一个固定点,这取决于你如何设计它。创建的新个体被添加到下一个群体中。
变化
不幸的是,这不会是我们在科幻电影中看到的那种突变,突变只是暗示染色体中的基因改变了它们的值。的可能性。突变发生率低且随机,但可以根据算法的需要进行调整。这是必要的,因为有一种可能性,即初始群体中没有一个成员具有特定的性状来帮助它达到全局终极。
最后一档
在我们注意到当前代没有比上一代更有效地解决问题之后,我们终止了我们的算法,因此我们剩下多个个体,这些个体都是我们的问题集的解决方案。
样本遗传算法问题的代码库
您可以在本地环境中运行这个 GNN 类。为了更快地运行或处理更复杂的任务,可以考虑通过 Paperspace Core.使用高端 GPU 远程访问虚拟桌面来启动您的工作流程
class GNN {
constructor(number, goal) {
this.population = []
this.goal = goal || '';
this.genePool = []
this.numnber = number
this.mostFit = {fitness:0}
this.initializePopulation()
}
initializePopulation() {
const length = this.goal.length;
for (let i = 0; i <= this.numnber; i++) this.population.push({ chromosomes: this.makeIndividual(length), fitness: 0 })
}
solve() {
for (var i = 0; i < 10000; i++) {
this.calcFitnessAll()
this.crossParents()
this.mutate()
}
console.log( this.mostFit )
}
mutate() {
this.population.forEach((object) => {
const shouldIMutateChromosome = Math.random() < 0.1
if (shouldIMutateChromosome) {
for (var i = 0; i < object.chromosomes.length; i++) {
const shoulIMutateGene = Math.random() < 0.05
if (shoulIMutateGene) {
const splitVersion = object.chromosomes.split('')
splitVersion[i] = this.makeIndividual(1)
object.chromosomes = splitVersion.join('')
}
}
}
})
}
calcFitnessAll() {
let mostFit = {fitness:0}
this.population.forEach((object) => {
let str1 = this.goal
let str2 = object.chromosomes
let counter = 0;
for (var i = 0; i < str1.length; i++) {
if (str1.charAt(i) === str2.charAt(i)) {
counter++;
}
}
object.fitness = counter > 0 ? counter : 1
for (var i = 0; i < object.fitness; i++) {
this.genePool.push(object.chromosomes)
}
object.fitness > mostFit.fitness ? mostFit = object : mostFit = mostFit
})
if (mostFit.fitness>this.mostFit.fitness) {
this.mostFit = mostFit
}
console.log('Max fitnees so far is ' + this.mostFit.fitness)
}
crossParents() {
const newPopulation = [];
const pool = this.genePool
this.population.forEach(() => {
const parent1 = pool[Math.floor(Math.random() * pool.length)];
const parent2 = pool[Math.floor(Math.random() * pool.length)];
////select crossSection
const crossSection = Math.floor(Math.random() * this.goal.length - 1)
let newKid = `${parent1.slice(0, crossSection)}${parent2.slice(crossSection, this.goal.length)}`;
newPopulation.push({ chromosomes: newKid, fitness: 0 })
})
this.population = newPopulation
this.genePool = []
}
makeIndividual(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz .,';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() *
charactersLength));
}
return result;
}
}
const goal = `I am the bone of my sword. Steel is my body, and fire is my blood.`
const gnn = new GNN(1000, goal)
gnn.solve()
分解代码库
我们使用基于类的建模方法来管理我们的遗传算法的状态。
我们首先在 initializePopulation 函数中创建人口。
initializePopulation() {
const length = this.goal.length;
for (let i = 0; i <= this.number; i++) this.population.push({ chromosomes: this.makeIndividual(length), fitness: 0 })
}
该函数使用makeIndividual
函数创建一个个体,并将其添加到群体数组中。
下一步是用遗传算法来解决这个问题。
solve() {
for (var i = 0; i < 10000; i++) {
this.calcFitnessAll()
this.crossParents()
this.mutate()
}
console.log( this.mostFit )
}
solve 函数迭代 10000 次,一遍又一遍地执行优化步骤,每一步都创建一批对我们的问题更优化的个体。
this.calcFitnessAll()
this.crossParents()
this.mutate()
这些是我们遵循的步骤,包括检查适应度,杂交亲本,然后变异它。
额外事实
香草遗传算法是一个很好的工具,但也存在其他变种,这也是值得 noting.
精英主义
这意味着我们把一定数量的表现最好的个体原封不动地传给下一代。这将确保解决方案的质量不会随着时间的推移而降低。
并行遗传算法
这涉及到何时使用多种遗传算法来解决一个特定的问题。这些网络都是彼此并行运行的,然后在它们完成后,选择其中最好的一个。
最终想法
遗传算法是机器学习中一个有趣的概念,用于寻找问题集的最优解。它并不总是解决某些问题的最理想的工具,但它对全局优化问题很有效。像变异和交叉这样的过程有助于将解从局部移到全局最大值。
下一步
这是我们系列的第一部分。我觉得解释项目的理论方面并提供一个基础代码库是很重要的,我们接下来将致力于构建我们的环境和代理来与 environment交互
来源:
用遗传算法开发井字游戏代理:用遗传算法和 JavaScript 优化神经网络(第二部分)
原文:https://blog.paperspace.com/tic-tac-toe-genetic-algorithm-part-2/
介绍
欢迎阅读我的教程系列的第二篇文章,在这篇文章中,我试图解释遗传算法背后的概念,它们是如何工作的,并使用它们来解决有趣的问题,在我们的例子中,我们希望能够构建一个简单的代理来玩井字游戏。
概述
在我们之前的文章《遗传算法入门》中,我们解释了什么是遗传算法以及它背后的简单逻辑(可能,没那么简单)。然后,我们能够将其分解为包括以下步骤
- 创造人口
- 选择
- 交叉
- 变化
重复执行步骤 2 到 4 有助于我们为我们定义的问题集创建一个最佳答案,这只是重新创建一个传递给函数的短语。在本文中,我们将通过使用遗传算法来优化神经网络,从而稍微提高一点。
我们的测试或示例问题
我们还没有开始构建我们的井字游戏:我们需要先了解基础知识,以及它们如何解决简单的问题,然后将它们扩展到更大的问题。我希望这是一个教程系列,从初级水平的个人掌握遗传算法如何工作的理解。为此,我们将解决一个简单的问题,将两个数字相加,以展示遗传算法如何优化神经网络。
什么是神经网络
神经网络或人工神经网络是受生物大脑启发的简单基本计算系统。它们代表由称为神经元的更小单元组成的一组逻辑连接,神经元也是从生物大脑中模拟出来的。它们解决简单到复杂问题的能力是通过从输入层接收信息,在隐藏层中处理它们,并将结果作为输出传递到输出层来实现的。我们的网络可以有多层(由用户定义),每层都有权重和偏差。
砝码
权重是节点之间连接强度的数字表示,较大的数字表示较强的连接,较小的数字表示较弱的连接。
偏见
偏差也可以被视为一系列不正确,这意味着我们的答案有多可能是错误的,并被添加到我们每一层的加权和中。
我们在这里使用遗传算法的目的是优化神经网络的权重和偏差值。在多次迭代或世代中重复这样做,我们可以调整我们的 ANN 模型的值,使它更适合解决我们的问题任务。
我们称这种相互作用的数学表示为激活函数。
输出=激活功能(输入*权重+偏差)
激活功能
这是一个添加到图层末尾的功能。它将输入和权重的乘积加上偏差,并决定传递给下一层的内容。它通过发射大量的信号来通知下一层这个神经元是否值得听,或者通过发射较小的信号来告诉下一层忽略它。
优化神经网络
当我们的神经网络创建时,它是用随机权重和偏差初始化的。对他们来说,一开始工作得不好是可以的,因为我们要实现优化算法,以便能够调整我们的权重,并随着时间的推移减少我们的平均损失,从而改进模型。我们可以通过实现一些算法来做到这一点,比如
- 梯度下降
- 动力
- Adagrad 自适应梯度算法
- 均方根传播,
其中最流行的是梯度下降或随机梯度下降,这是一种寻找定义的可微函数或损失函数的全局最小值/最大值的方法。
如果您想了解更多,请查看 Paperspace 博客中的一些重要故障:
作为优化算法的遗传算法
遗传算法在几个方面不同于传统的优化算法
- 该算法几乎所有可能的值都已经在初始种群中定义了,因此与梯度下降等算法不同,我们已经有了一个有限的搜索空间(一个非常大的有限搜索空间)。
- 遗传算法不需要太多关于这个问题的信息。它只需要一个精确的适应度函数来处理和选择问题的最佳候选。这消除了获取培训数据的需要。
- 遗传算法是概率性的,而其他算法通常是随机的。
优势
- 遗传算法可以避免局部最大值并达到全局最大值
- 适用于现实世界的问题
- 能够训练模型,我们没有数据,但我们知道它们是如何工作的
- 较少数学方法
更多 recap🧢
到目前为止,我们在上一篇文章中所做的是能够构建一个遗传算法来生成一个短语
我们能够优化我们的人口,直到我们所有的成员都等于我们的目标短语。
使用我们的代码库来优化神经网络
优化神经网络几乎是相同的原理,但解决问题的方法不同。在我们到达那里之前,让我们看一些我们需要理解的东西。
精英主义
是的,即使是软件也可能倾向于对某些实例进行更好的处理。精英主义的概念是随着时间的推移从我们的人口中保存我们最好的表现实例。这个列表永远不会被清除,它的规模通常比人口规模小得多(我建议是 20%到 30%)。只有当我们发现一个元素的适应度高于数组中性能最低的元素时,它才会被更新,这样做可以确保我们在基因库中始终有最好的候选元素可供选择。让我们看一个代码实现:
amIElite(object) {
const smallest = this.elites[0]
if (this.elittSize > this.elites.length) {
this.elites.push(object)///elites not yet enough
}
else if (parseInt(object.fitness) > parseInt(smallest.fitness)) {///check if am bigger than the smallest
///bigger than this guy so am going to replace him
this.elites.shift()
this.elites.push(object)
}
this.elites = this.elites.sort((a, b) => a.fitness - b.fitness);///sort from smallest to biggest every time i add a new smallest
}
还有什么变化?
染色体
在我们之前的例子中,组成短语的字母所在的染色体。这里我们的染色体是我们权重的值,让我们看一个例子
为了实现这一点,我们首先将我们的神经网络权重(最初由 M * N * O 矩阵表示)平坦化为平坦矩阵,例如
[
[3,2],
[3,3]], ===>[3,2,3,3,,5,2]
[5,2]]
]
这让我们可以轻松地操纵我们的数据,并执行基本操作,如杂交亲本和变异。
适应度函数
我们的适应度函数是遗传算法的关键组成部分,我不能过分强调这一点,它会改变以适应我们当前的示例问题。
我们的示例问题是优化神经网络的遗传算法,以找到传递给它的两个数的和,数学表示是
\(T = x + y\)
我们的适应度函数是 x + y -T 的倒数,也就是 1 / (x + y -T),让我们看一个简单的例子
$X = 20 美元
Y = 40 美元
如果我们预测 T 为 25,
把所有的东西都放进去,我们会得到 1/((20+40)-25)= 1/35 = 0.0022857 美元
0.02 被认为是神经网络的这个实例的准确度或适合度,如果我们将这些值调整得多一点,并且预测 T 为 55,我们将有 0.2 作为我们的准确度或适合度。
让我们看看代码。
/// The code bellow calculates the fitness function of the model
/// ANN model by attempting to perform a calculation 5 times, and gets
/// the average fiteness
let fit = 0
for (let i = 0; i < 5; i++) {
const number1 = Math.floor(Math.random() * 100)
const number2 = Math.floor(Math.random() * 100)
const numberT = object.chromosomes.predict([number1, number2])[0]
////number T found
let score = (1 / (number1 + number2 - numberT)) || 1 ///if it evaluates as 0 then make it 1 do thee math u would understand y
if (score > 1) {
score = 1 //// if x + y - t eevaluates as 0.5 orr anything less than 1 theen score becomess 2 which may not bee a bad thing would ttest
}
if (score < 0) {
score = score * -1 //// acceptting negativee numbers should check
}
///multiply by 100 and parse as int ///test values between 10 and 1000
fit = fit + score
}
object.fitness = parseInt(fit/5)
我们实际上尝试预测该值 5 次,并使用总平均值作为该神经网络实例的适应度函数。这是因为它可能足够幸运地计算 2 个随机数的值,但这并不能保证它有能力计算另一组随机数的值。
杂交父母
这类似于我们之前的例子,我们在一个数组中有一个简单的元素列表,猜测一个随机位置,在该点上划分两个父元素,然后使用该位置再创建两个子元素。
我们将该层的组件解构为一个用于权重和偏差的平面矩阵,然后执行交叉操作,然后再次重建它们。
现在让我们看看我们正在使用的神经网络的代码
Matrix.js
matrix.js 文件用于为我们的神经网络执行矩阵计算,如加法和乘法。
class Matrix {
///initialize our object
constructor(data) {
const {outputLayer, inputLayer,init} = data
if(init){
this.data = init
this.shape = [init.length,Array.isArray(init[0])?init[0].length: 1 ]
}
else{
this.data = Array.from(Array(inputLayer), () => new Array(outputLayer).fill(0));
this.shape = [inputLayer, outputLayer]
}
}
/*
Function to perform multiplication of a matrix
*/
multiply(matrix) {
///simple check to see if we can multiply this
if (!matrix instanceof Matrix) {
throw new Error('This is no Matrix')
}
if (this.shape[1] !== matrix.shape[0]) {
throw new Error(`Can not multiply this two matrices. the object:${JSON.stringify(this.shape)} multipleidBy:${JSON.stringify(matrix.shape)}`)
}
const newMatrice = new Matrix({
inputLayer : this.shape[0],
outputLayer: matrix.shape[1]
})
for (let i = 0; i < newMatrice.shape[0]; i++) {
for (let j = 0; j < newMatrice.shape[1]; j++) {
let sum = 0;
for (let k = 0; k < this.shape[1]; k++) {
sum += this.data[i][k] * matrix.data[k][j];
}
newMatrice.data[i][j] = sum;
}
}
return newMatrice
}
/*
Function to perform addition of a matrix
*/
add(matrix) {
const newMatrice = new Matrix({
inputLayer : this.shape[0],
outputLayer: matrix.shape[1]
})
if (!(matrix instanceof Matrix)) {
for (let i = 0; i < this.shape[0]; i++)
for (let j = 0; j < this.shape[1]; j++) {
newMatrice.data[i][j] = this.data[i][j] + matrix;
}
}
else {
for (let i = 0; i < matrix.shape[0]; i++) {
for (let j = 0; j < this.shape[1]; j++) {
newMatrice.data[i][j] = matrix.data[i][j] + this.data[i][j];
}
}
}
this.data = newMatrice.data
this.shape = newMatrice.shape
return newMatrice
}
/*
Function to perform subtraction of a matrix
*/
subtract(matrix) {
const newMatrice = new Matrix(this.shape[0], this.shape[1])
if (!(matrix instanceof Matrix)) {
for (let i = 0; i < this.shape[0]; i++)
for (let j = 0; j < this.shape[1]; j++) {
newMatrice.data[i][j] = this.data[i][j] - matrix;
}
}
else {
for (let i = 0; i < matrix.shape[0]; i++) {
for (let j = 0; j < this.shape[1]; j++) {
newMatrice.data[i][j] = matrix.data[i][j] - this.data[i][j];
}
}
}
return newMatrice
}
map(func) {
// Applys a function to every element of matrix
for (let i = 0; i < this.shape[0]; i++) {
for (let j = 0; j < this.shape[1]; j++) {
let val = this.data[i][j];
this.data[i][j] = func(val);
}
}
}
/*
Function to generate random values in the matrix
*/
randomize(){
for(let i = 0; i < this.shape[0]; i++)
for(let j = 0; j < this.shape[1]; j++)
this.data[i][j] = (Math.random()*2) - 1; //between -1 and 1
}
};
module.exports = Matrix
神经网络
这个文件帮助我们创建带有类的 ANN 模型,这意味着我们可以创建多个 ANN 模型并添加到我们的群体中。每个模型都有自己的状态,如权重和偏差。
const Matrix = require('./matrix.js')
/*
Layerlink is our individual network layer
*/
class LayerLink {
constructor(prevNode_count, node_count) {
this.weights = new Matrix({ outputLayer: node_count, inputLayer: prevNode_count });
this.bias = new Matrix({ outputLayer: node_count, inputLayer: 1 });
this.weights.randomize()
this.bias.randomize()
this.weights.flat = this.flatenMatrix(this.weights)
this.bias.flat = this.flatenMatrix(this.bias)
}
updateWeights(weights) {
this.weights = weights;
}
getWeights() {
return this.weights;
}
getBias() {
return this.bias;
}
/*
Function to flatten matrix
*/
flatenMatrix({data}){
///flaten matrix
let flattened = []
data.forEach(dataRow => {
flattened = flattened.concat(dataRow)
});
return flattened
}
}
/*
NeuralNetwork model
*/
class NeuralNetwork {
constructor(layers, options) {
this.id = Math.random()
this.fitness = 0
this.weightsFlat = []
this.biasFlat =[]
if (layers.length < 2) {
console.error("Neural Network Needs Atleast 2 Layers To Work.");
return { layers: layers };
}
this.options = {
activation: function(x) {
return 1 / (1 + Math.exp(-x))
},
derivative: function(y) {
return (y * (1 - y));
},
relu:(x)=>{
return Math.max(0, x);
}
}
this.learning_rate = 0.1;
this.layerCount = layers.length - 1; // Ignoring Output Layer.
this.inputs = layers[0];
this.output_nodes = layers[layers.length - 1];
this.layerLink = [];
for (let i = 1, j = 0; j < (this.layerCount); i++ , j++) {
if (layers[i] <= 0) {
console.error("A Layer Needs To Have Atleast One Node (Neuron).");
return { layers: layers };
}
this.layerLink[j] = new LayerLink(layers[j], layers[i]); // Previous Layer Nodes & Current Layer Nodes
this.weightsFlat = this.weightsFlat.concat(this.layerLink[j].weights.flat)
this.biasFlat = this.biasFlat.concat(this.layerLink[j].bias.flat)
}
}
/*
Function to perform prediction with model, takes in input arrray
*/
predict(input_array) {
if (input_array.length !== this.inputs) {
throw new Error('Sorry the input can not be evaluated')
}
let result = new Matrix({ init: [input_array] })
for (let i = 0; i < this.layerLink.length; i++) {
result = result.multiply(this.layerLink[i].getWeights())
result = result.add(this.layerLink[i].getBias())
// console.log('old===> ',i,result.data[0])
const newR = result.data[0].map(this.options.relu)
// console.log('new====> ',i,newR)
result.data = [newR]
}
return result.data[0]
}
/*
Reconstructs a matrix from a flat array to a M * N arrray with the shape passed to it
*/
reconstructMatrix(data,shape){
const result = []
try {
for(let i = 0;i< shape[0];i++){
result.push(data.slice(i*shape[1], shape[1] + (i*shape[1])))
}
} catch (error) {
console.log(error,this)
throw new Error('')
}
return result
}
/*
Reconstructs the weight values from the weightsFlat matrix to match the shape of the matrix
*/
reconstructWeights(){
///reconstruct weights
let start = 0;
for (let i = 0; i < this.layerLink.length; i++) {
const layer = this.layerLink[i];
const shape = layer.weights.shape
const total = shape[0] * shape[1]
const array = this.weightsFlat.slice(start, start + total)
start = start + total
const weightMatrix = this.reconstructMatrix(array,shape)
this.layerLink[i].weights.data = weightMatrix
}
}
/*
Reconstructs the bias values from the biasFlat matrix to match the shape of the matrix
*/
reconstructBias(){
///reconstruct bias
let start = 0;
for (let i = 0; i < this.layerLink.length; i++) {
const layer = this.layerLink[i];
const shape = layer.bias.shape
const total = shape[0] * shape[1]
const array = this.biasFlat.slice(start, start + total)
start = start + total
const biasMatrix = this.reconstructMatrix(array,shape)
this.layerLink[i].bias.data = biasMatrix
}
}
}
module.exports = NeuralNetwork
现在我们已经建立了自定义的神经网络,让我们看看新版本的遗传算法。
中国地质大学
遗传算法文件包含我们在当前和以前的文章中讨论的遗传算法函数,我们的主要入口点是 solve 函数,它试图在 1000 代之后优化 ANN 模型。
const NeuralNetwork = require('./nn.js')
class GNN {
constructor(number,) {
this.population = []
this.genePool = []
this.numnber = number
this.mostFit = { fitness: 0 }
this.initializePopulation()
this.elittSize = 40
this.elites = []
this.soFar = {}
}
/*
Initialize a population of N amount of individuals into our geenetic algorithm
*/
initializePopulation() {
for (let i = 0; i < this.numnber; i++) this.population.push({ chromosomes: this.makeIndividual(), fitness: 0 })
}
/*
Entry point into our genetic algorithm goes through our 3 step process
*/
solve() {
for (var i = 0; i < 1000; i++) {
this.calcFitnessAll()
this.crossParents()
this.mutate()
}
}
/*
Mutate data in our flatttened weights then reconstruct them back
*/
mutate() {
this.population.forEach((object) => {
const shouldIMutateChromosome = Math.random() < 0.3
if (shouldIMutateChromosome) {
const layer = object.chromosomes
for (var j = 0; j < layer.weightsFlat.length; j++) {////going through all the layers
const shoulIMutateGene = Math.random() < 0.05
if (shoulIMutateGene) {
///mutate the item in the array
layer.weightsFlat[j] = (Math.random() * 2) - 1; //between -1 and 1
}
}
layer.reconstructWeights()
////recontruct the array
}
})
}
/*
Calcualate the fittness function of all the individuals in the model
*/
calcFitnessAll() {
this.population.forEach((object) => {
////task is to join 2 numbers
//// x + y = t
////fitness function is (1/x + y - t )basically how close it is to 1
let fit = 0
for (let i = 0; i < 5; i++) {
const number1 = Math.floor(Math.random() * 100)
const number2 = Math.floor(Math.random() * 100)
const numberT = object.chromosomes.predict([number1, number2])[0]
////number T found
let score = (1 / (number1 + number2 - numberT)) || 1 ///if it evaluates as 0 then make it 1 do thee math u would understand y
if (score > 1) {
score = 1 //// if x + y - t eevaluates as 0.5 orr anything less than 1 theen score becomess 2 which may not bee a bad thing would ttest
}
if (score < 0) {
score = score * -1 //// acceptting negativee numbers should check
}
///multiply by 100 and parse as int ///test values between 10 and 1000
fit = fit + score
}
object.fitness = parseInt(fit/5)
for (let i = 0; i < fit; i++) {
this.genePool.push(object.chromosomes)
}
this.amIElite([object].filter(()=>true)[0])
})
const getBeestElite = this.elites[39]
///run test
const number1 = Math.floor(Math.random() * 100)
const number2 = Math.floor(Math.random() * 100)
const numberT = getBeestElite.chromosomes.predict([number1, number2])[0]
console.log(`Beest item soo far numbers are ${number1} and ${number2} and the prediction is ${numberT} fittness is ${getBeestElite.fitness} `)
}
/*
Function to cheeck if our model is actually eligible to become an elite and maintians the top perfrooming models over a constant period of time
*/
amIElite(object) {
const smallest = this.elites[0]
if (this.elittSize > this.elites.length) {
this.elites.push(object)///elites not yet enough
}
else if (parseInt(object.fitness) > parseInt(smallest.fitness)) {///check if am bigger than the smallest
///bigger than this guy so am going to replace him
this.elites.shift()
this.elites.push(object)
}
this.elites = this.elites.sort((a, b) => a.fitness - b.fitness);///sort from smallest to biggest every time i add a new smallest
}
/*
Function to cross 2 rrandomly selected parents into 2 children, can be more but i dont see a need to
*/
crossParents() {
let newPopulation = [];
const pool = this.genePool
while (this.population.length - this.elites.length !== newPopulation.length) {
const parent1 = pool[Math.floor(Math.random() * pool.length)];
const parent2 = pool[Math.floor(Math.random() * pool.length)];
const newKid = this.makeIndividual()
const newKid2 = this.makeIndividual()
////select a crossSection
const items = ['', '']
const crossSection = Math.floor(Math.random() * parent1.weightsFlat.length)
////kid 1
const layerParent1 = parent1.weightsFlat.filter(() => true)
const layerParent2 = parent2.weightsFlat.filter(() => true)
const layerParent1bias = parent1.weightsFlat.filter(() => true)
const layerParent2bias = parent2.weightsFlat.filter(() => true)
const newKidWeights = layerParent1.slice(0, crossSection).concat(layerParent2.slice(crossSection, layerParent2.length))
const newKid2Weights = layerParent2.slice(0, crossSection).concat(layerParent2.slice(crossSection, layerParent1.length))
newKid.weightsFlat = newKidWeights
newKid.reconstructWeights()
newKid2.weightsFlat = newKid2Weights
newKid2.reconstructWeights()
const crossSectionBias = Math.floor(Math.random() * layerParent2bias.length)
const newKidBias = layerParent1bias.slice(0, crossSectionBias).concat(layerParent2bias.slice(crossSectionBias, layerParent2bias.length))
const newKidBias2 = layerParent2bias.slice(0, crossSectionBias).concat(layerParent1bias.slice(crossSectionBias, layerParent2bias.length))
newKid.biasFlat = newKidBias
newKid.reconstructBias()
newKid2.biasFlat = newKidBias2
newKid2.reconstructBias()
newPopulation.push({ chromosomes: newKid, fitness: 0 })
newPopulation.push({ chromosomes: newKid2, fitness: 0 })
}
newPopulation = newPopulation.concat(this.elites)///making sure we pass on elites
this.population = newPopulation
this.genePool = []///clear genepool
}
makeIndividual() {
return new NeuralNetwork([2, 8,8, 1]);
}
}
const gnn = new GNN(4000)
gnn.solve()
让我们看看这些训练
Beest item soo far numbers are 13 and 24 and the prediction is 25.28607491037004 fittness is 4
Beest item soo far numbers are 6 and 23 and the prediction is 26.32267394133354 fittness is 13
Beest item soo far numbers are 50 and 7 and the prediction is 62.49884171193919 fittness is 83
Beest item soo far numbers are 49 and 98 and the prediction is 146.41341907285596 fittness is 121
Beest item soo far numbers are 72 and 9 and the prediction is 83.4678176435441 fittness is 563
Beest item soo far numbers are 14 and 93 and the prediction is 106.26071461398661 fittness is 2743
Beest item soo far numbers are 86 and 18 and the prediction is 110.79668473593276 fittness is 1112
Beest item soo far numbers are 88 and 0 and the prediction is 103.38716156007081 fittness is 2739
Beest item soo far numbers are 43 and 36 and the prediction is 79.17631973457603 fittness is 1111
Beest item soo far numbers are 1 and 24 and the prediction is 24.314085857021304 fittness is 1110
Beest item soo far numbers are 34 and 20 and the prediction is 54.326947949897615 fittness is 1111
Beest item soo far numbers are 65 and 40 and the prediction is 105.26124354533928 fittness is 1112
Beest item soo far numbers are 83 and 79 and the prediction is 161.86964868039985 fittness is 1128
Beest item soo far numbers are 85 and 24 and the prediction is 112.87455742664426 fittness is 2741
Beest item soo far numbers are 95 and 22 and the prediction is 116.77612736073525 fittness is 2740
Beest item soo far numbers are 18 and 88 and the prediction is 105.35006857127684 fittness is 2747
Beest item soo far numbers are 79 and 66 and the prediction is 145.0125935541571 fittness is 1112
Beest item soo far numbers are 72 and 87 and the prediction is 158.69813186888078 fittness is 2740
Beest item soo far numbers are 25 and 23 and the prediction is 48.23237184355801 fittness is 2740
Beest item soo far numbers are 4 and 32 and the prediction is 35.98588328087144 fittness is 2743
Beest item soo far numbers are 72 and 47 and the prediction is 119.21435149343066 fittness is 2740
Beest item soo far numbers are 46 and 49 and the prediction is 95.02716823476345 fittness is 2742
Beest item soo far numbers are 28 and 55 and the prediction is 82.83801602208422 fittness is 2740
Beest item soo far numbers are 13 and 18 and the prediction is 31.22241978396226 fittness is 2741
Beest item soo far numbers are 18 and 48 and the prediction is 65.86628819582671 fittness is 2742
Beest item soo far numbers are 83 and 50 and the prediction is 133.2439079081985 fittness is 2740
Beest item soo far numbers are 5 and 17 and the prediction is 22.305395227971694 fittness is 54194
Beest item soo far numbers are 81 and 97 and the prediction is 179.5644485790281 fittness is 54237
我们可以看到我们的模型的训练进度,以及神经网络如何在第一次尝试中完全错误,但能够随着时间的推移进行优化,以解决我们的简单加法问题。
结论
我们学习了神经网络的基础知识,我们能够理解如何使用遗传算法来优化神经网络,在我们的下一篇文章中,我们将查看代码库,并了解如何将我们的概念实现到工作代码中。
用遗传算法开发井字游戏代理:把它们放在一起(第 3 部分)
原文:https://blog.paperspace.com/tic-tac-toe-genetic-algorithm-part-3/
介绍
当我们有正确的数据来训练我们的模型时,建立机器学习模型可能是一个简单的过程。例如,我们需要训练一个模型来预测某个客户是否会流失。如果我们有一个足够好的数据集,我们所要做的就是将这些数据插入到一个模型中,使用一个简单的库,如 Brains.js 或 TensorFlow,并添加一个反向传播算法来优化我们的模型。这样,我们就有了一个模型,它能够获取我们的输入数据,并返回一个优化的预测,预测客户是否会停止购买产品。
但是,如果我们没有好的数据,收集数据超出了我们的能力,怎么办?我们必须定义我们的目标,或者我们认为最佳答案是什么,这就是像遗传算法和强化学习这样的算法发挥作用的地方。在这些范例中,我们能够定义一个目标,我们的模型与它所处的环境相互作用,从它以前的经验中学习,直到它能够解决它定义的任务。
系列摘要
到目前为止,我们已经学习了遗传算法的基础知识,它的组成部分,以及它的功能背后的概念,使它能够找到简单和困难问题的最佳解决方案。我们通过比较有多少个字母是正确的来猜测一个短语,并使用那些最接近正确的实例来创建子实例或子代,这些子实例或子代将关于我们的问题空间的信息传递到下一代。你可以在这里查看教程。在的第二篇文章中,我们能够使用前一篇文章中的知识来优化深度神经网络。我们采用从上一个教程中获得的信息来解决我们的优化问题,添加 2 个传递给它的数字,我们建立了自己的定制神经网络,它能够利用我们的定制来操纵权重和偏差的值,而不是通过像 TensorFlow 这样的已有库来这样做。
本文的目标是
在今天的文章中,我们将回顾构建我们的模型将与之交互的环境,对该环境进行更改,并读取其状态。
设置我们的播放器
我们需要一个玩家对象能够观察环境并根据环境的状态做出决定。
天真的选手
以下是我们将在本教程中使用的 Player 类的完整代码:
class Player {
constructor(id, env, play, model,player) {
this.id = id;
this.env = env;
this.play = play; ///x or O
this.model = model;
this.position = '';
this.freeIndexes = 9;
this.won = false
}
getFreeIndexes() {
const free = [];
for (let i = 0; i < this.objectGrid.length; i++) {
const row = this.objectGrid[i];
for (let j = 0; j < row.length; j++) {
const column = this.objectGrid[i][j];
if (column === '-') {
free.push(`${i}${j}`);
}
}
}
return free;
}
verticalWin() {
const starts = [0, 1, 2];
for (let i = 0; i < starts.length; i++) {
let found = 0;
for (let j = 0; j < 3; j++) {
const position = this.objectGrid[j][i];
if (position === this.play) {
found++;
}
}
if (found === 3) {
return true; //// found a row
}
}
return false;
}
horizontalWin() {
const starts = [0, 1, 2];
for (let i = 0; i < starts.length; i++) {
let found = 0;
for (let j = 0; j < 3; j++) {
const position = this.objectGrid[i][j];
if (position === this.play) {
found++;
}
}
if (found === 3) {
return true; //// found a row
}
}
return false;
}
sideWays() {
let found = 0;
for (let i = 0; i < this.objectGrid[0].length; i++) {
const position = this.objectGrid[i][i];
if (position === this.play) {
found++;
}
}
if (found === 3) {
return true; //// found a row
}
found = 0;
for (let i = 0; i < this.objectGrid[0].length; i++) {
const max = this.objectGrid[0].length;
const position = this.objectGrid[0 + i][max - i];
if (position === this.play) {
found++;
}
}
if (found === 3) {
return true; //// found a row
}
return false; //// fo
}
win() {
//checks if i won
///vertical check
///horizontal check
///sideways
console.log({ vertival: this.verticalWin() });
console.log({ horizontalWin: this.horizontalWin() });
console.log({ sideways: this.sideWays() });
if (this.verticalWin() ||this.horizontalWin() || this.sideWays()) {
return true
}
return false
}
//generate random move
move() {
const freeIndexes = this.getFreeIndexes();
this.freeIndexes = freeIndexes.length;
if (freeIndexes.length === 0) {
return this.objectGrid;
}
const think = this.think(freeIndexes);
console.log(think);
this.objectGrid[think[0]][think[1]] = this.play;
this.position = `${think[0]}${think[1]}`;
console.log('changed position to ' + this.position);
if (this.win()) {
this.won = true
}
return this.objectGrid;
}
think(freeIndexes) {
console.log({ freeIndexes });
const random = Math.floor(Math.random() * freeIndexes.length);
const chosen = freeIndexes[random];
console.log(chosen);
return [parseInt(chosen[0]), parseInt(chosen[1])];
}
}
我们天真的玩家能够观察环境,查看空白空间,并随机选择其中一个空间来画 X 或 O。我们天真的玩家没有利用我们的遗传算法模型;它不能学习如何玩游戏,只是选择随机的空白空间来玩。在我们建立了我们的遗传算法之后,我们会给我们的玩家做出更明智选择的能力。
我们的 player 类能够通过调用 win()函数来查看棋盘并宣布他们在一步棋后是否赢了。胜利本质上可以是纵向的、横向的或相邻的。
为我们的模型设计
设计解决这个问题的方法可能有点棘手。我们需要弄清楚如何在我们的板上代表 3 个不同的州。这三种状态是:
- 代理占据的空间
- 对手占据的空间
- 空白的空间
如果我们有两个状态,我们可以方便地将其中一个标识为 1 ,另一个标识为 0。但是 three 留给我们两个选择:表示-1 到 1 (-1,0,1)范围内的值,或者长度为 18 的数组。在后一个选项中,索引 0 到 8 可以标记我们的代理选择的位置,索引 9 到 17 可以表示我们的对手占据的位置。让我们尝试本教程的第二个选项。你可以在上面看到这个阵列是如何形成的解释。
我们选择使用 ReLu 作为我们的隐藏层,因为像往常一样,它们的性能优于 sigmoid(这并不奇怪)。
这是我们模型的直观解释。
我们遗传算法代码的新版本
对于本文,您需要已经阅读了本系列的第 1 部分和第 2 部分,并确保您理解了基础知识。我们将使用相同的代码模板,并检查代码库中的更改。
代码
class GNN {
constructor(number,) {
this.population = []
this.genePool = []
this.numnber = number
this.mostFit = { fitness: 0 }
this.initializePopulation()
this.elittSize = parseInt(this.numnber * 0.3)
this.elites = []
this.soFar = {}
}
initializePopulation() {
for (let i = 0; i < this.numnber; i++) this.population.push({ chromosomes: this.makeIndividual(), fitness: 0 })
}
solve() {
for (var i = 0; i < 200; i++) {
this.calcFitnessAll()
this.crossParents()
this.mutate()
}
this.calcFitnessAll()
var fs = require('fs');
const sorted = this.population.sort((a, b) => a.fitness - b.fitness).reverse();///sort from smallest to biggest every time i add a new smallest
sorted.map((elit)=>{
console.log(elit.fitness)
})
const getBeestElite =sorted[0]
var data = JSON.stringify(getBeestElite);
fs.writeFile('./config.json', data, function (err) {
if (err) {
console.log('There has been an error saving your configuration data.');
console.log(err.message);
return;
}
console.log('Configuration saved successfully.')
});
}
mutate() {
this.population.forEach((object) => {
const shouldIMutateChromosome = Math.random() < 0.3
if (shouldIMutateChromosome) {
const layer = object.chromosomes
for (var j = 0; j < layer.weightsFlat.length; j++) {////going through all the layers
const shoulIMutateGene = Math.random() < 0.05
if (shoulIMutateGene) {
///mutate the item in the array
layer.weightsFlat[j] = (Math.random() * 2) - 1; //between -1 and 1
}
}
layer.reconstructWeights()
////recontruct the array
}
})
}
playAGame(player1, player2, Player1StartsFirst) {
class Env {
constructor() {
this.state = [
['-', '-', '-'],
['-', '-', '-'],
['-', '-', '-'],
];
this.id = Math.random()
}
}
///iniitialiise game environment
const env = new Env()
///iniitialise players and pass in agents
const playerX = new Player(0, env, 'X', Player1StartsFirst ? player1 : player2);
const playerO = new Player(1, env, 'O', Player1StartsFirst ? player2 : player1);
/**
*
* @returns @description this function is a reculrsive function that keeps playing until someone wins or a drraw
*/
const aSet = () => {
try {
playerX.move();
if (playerX.win()) {
return 1////player won
}
if (!playerX.getFreeIndexes().length === 0) {
///no free space means its a draw
return 1
}
playerO.move();
if (playerO.win()) {
return 0////player loss
}
if (!playerO.getFreeIndexes().length === 0) {
///no free space means its a draw
return 1
}
else {
return aSet() /////keep playing
}
} catch (error) {
return 0
}
}
return aSet() ///false means it lost the game, we consider win and draw to be acceptable criterias
}
calcFitnessAll() {
this.population.forEach((object) => {
let totalGames = 0;
let totalLoss = 0;
for (let i = 0; i < this.population.length; i++) {
///the fitness function is defined as the (totalGamesPlayed - totalGamesLoss)/totalGamesPlayed
///for each player we meet, games are started by this player and the next is started by the next player
totalGames = totalGames + 2/////we count the games in advance
totalLoss = totalLoss + (this.playAGame(object.chromosomes, this.population[i].chromosomes, false) ? 0 : 1)
totalLoss = totalLoss + (this.playAGame(object.chromosomes, this.population[i].chromosomes, true) ? 0 : 1)
}
object.fitness = (totalGames - totalLoss) / totalGames
let fit = parseInt(object.fitness * 1000)
for (let i = 0; i < fit; i++) {
this.genePool.push(object.chromosomes)
}
console.log(`total games is ${totalGames} total loss is ${totalLoss} total agents in testrun is ${this.genePool.length}`)
})
if (this.genePool.length < this.population.length) {
///sort the best
const newPopulation = this.population.sort((a, b) => a.fitness - b.fitness);///sort from smallest to biggest every time i add a new smallest
///get the best
this.genePool = this.genePool.concat(newPopulation.slice(0, newPopulation.length - this.genePool.length).map((data) => data.chromosomes))
}
const getBeestElite = this.elites[this.elites.length - 1]
///run test
console.log(`Best item so far has fittness as ${getBeestElite.fitness} id ${getBeestElite.chromosomes.id} `)
}
amIElite(object) {
const smallest = this.elites[0]
if (this.elittSize > this.elites.length) {
this.elites.push(object)///elites not yet enough
}
else if (parseInt(object.fitness) > parseInt(smallest.fitness)) {///check if am bigger than the smallest
///bigger than this guy so am going to replace him
this.elites.shift()
this.elites.push(object)
}
this.elites = this.elites.sort((a, b) => a.fitness - b.fitness);///sort from smallest to biggest every time i add a new smallest
}
crossParents() {
let newPopulation = [];
const pool = this.genePool
while (this.population.length - this.elites.length !== newPopulation.length) {
const parent1 = pool[Math.floor(Math.random() * pool.length)];
const parent2 = pool[Math.floor(Math.random() * pool.length)];
const newKid = this.makeIndividual()
const newKid2 = this.makeIndividual()
////select crossSection
const crossSection = Math.floor(Math.random() * parent1.weightsFlat.length)
////kid 1
const layerParent1 = parent1.weightsFlat.filter(() => true)
const layerParent2 = parent2.weightsFlat.filter(() => true)
const layerParent1bias = parent1.weightsFlat.filter(() => true)
const layerParent2bias = parent2.weightsFlat.filter(() => true)
const newKidWeights = layerParent1.slice(0, crossSection).concat(layerParent2.slice(crossSection, layerParent2.length))
const newKid2Weights = layerParent2.slice(0, crossSection).concat(layerParent2.slice(crossSection, layerParent1.length))
newKid.weightsFlat = newKidWeights
newKid.reconstructWeights()
newKid2.weightsFlat = newKid2Weights
newKid2.reconstructWeights()
const crossSectionBias = Math.floor(Math.random() * layerParent2bias.length)
const newKidBias = layerParent1bias.slice(0, crossSectionBias).concat(layerParent2bias.slice(crossSectionBias, layerParent2bias.length))
const newKidBias2 = layerParent2bias.slice(0, crossSectionBias).concat(layerParent1bias.slice(crossSectionBias, layerParent2bias.length))
newKid.biasFlat = newKidBias
newKid.reconstructBias()
newKid2.biasFlat = newKidBias2
newKid2.reconstructBias()
newPopulation.push({ chromosomes: newKid, fitness: 0 })
newPopulation.push({ chromosomes: newKid2, fitness: 0 })
}
const sorted = this.population.sort((a, b) => a.fitness - b.fitness).reverse();///sort from smallest to biggest every time i add a new smallest
newPopulation = newPopulation.concat(sorted.slice(0,parseInt(this.population.length * 0.3)))///making sure we pass on elites
this.population = newPopulation
this.genePool = []///clear genepool
}
makeIndividual() {
return new NeuralNetwork([18, 15, 15, 9]);
}
}
现在我们已经看了代码,让我们浏览一下函数,看看我们的代码库中有什么新内容。
适应度函数
我们让代理学习演奏的适应函数非常简单。我们的目标是减少我们失败的次数。如果这是梯度下降,我们的成本函数将是一个方程,它告诉我们我们输了多少次,或者代理人输了多少次游戏。知道了这一点,我们就可以推导出我们的适应度函数,即所玩游戏总数与未输游戏总数之比
(totalGames - totalGamesLost)/totalGames
我们认为平局和胜利现在都是同样令人满意的结果,并试图优化其中任何一个,因为这是最低损失的优化。
为了了解如何适合我们的代理,我们需要实际测试它与对手的对比,没有比我们创建的其他代理更好的对手了。每个代理与它的兄弟竞争,同时扮演第一个和第二个玩家。我们这样做是因为井字游戏玩家可以在游戏中第一个或第二个采取行动。
玩游戏功能
这个功能接受 2 个玩家,并且能够为我们的代理和它的对手模拟整个游戏。我们这样做是为了看看我们的经纪人是否比他的对手更好。我们更热衷于防止我们的代理松散,所以平局是可以的,但我们可以通过给 wins 1 和平局一半(0.5)来优化一场胜利(完成本教程后尝试一下,看看效果如何!).
我们如何让代理不在一个被占用的盒子里玩?
我们想给我们的代理的直觉是,它知道它不能玩已经被它或它的对手占据的盒子。为了训练出这种行为,当他们做出这样的举动时,我们就直接宣布游戏失败。
try {
playerX.move();
} catch (error) {
return 0
}
如果 move 函数能够检查我们选择的游戏是否被占用,它会自动返回一个零(我们基本上也不鼓励这样做)。
完成我们的玩家代理
我们需要对我们的玩家职业做一些改变。目前,它在玩之前不会思考,它只是选择一个可用的随机空间并标记该位置。这是不直观的,也没有传授取胜的策略。我们需要让我们的玩家利用我们传递给它的模型。看看下面的代码。
//generate random move
move() {
const freeIndexes = this.getFreeIndexes();
this.freeIndexes = freeIndexes.length;
if (freeIndexes.length === 0) {
return this.env.state;
}
const think = this.think();
const maxOfArrray = think.max()
const dic = [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]]
const loc = dic[maxOfArrray]
if (this.env.state[loc[0]][loc[1]] !== '-') {
///checks if this agent is trying to play a position thats already played
throw new Error('Some one home Already')
}
this.env.state[loc[0]][loc[1]] = this.play;
if (this.win()) {
this.won = true
}
}
think() {
///get state of the env
const state = this.env.state
///flatten array
const flat = state.flat();
///encoding data to enter the neuralNet
const opponnet = this.play === 'X' ? 'O' : 'X'
const firstHalf = flat.flat().join().replaceAll('-', '0').replaceAll(this.play, 1).replaceAll(opponnet, 0).split(',')
const secondHalf = flat.flat().join().replaceAll('-', '0').replaceAll(opponnet, 1).replaceAll(this.play, 0).split(',')
///now we have two separate arrays showing us what boxes we occupy on the first half and the opponent boxes on the second half
const together = firstHalf.concat(secondHalf).map((number) => {
return parseInt(number)///format data to b integer again
})
// console.log({ player: this.play, together })
// console.log(together)
///pass data to neurral net
const prrediction = this.model.predict(together)
// console.log({
// prrediction
// })
return prrediction
}
思考功能
我们的思考功能能够通过获取环境状态并创建单个[1 * 18 ]矩阵来利用我们的模型,我们将该矩阵传递给机器学习模型并生成输出(注意:我们的模型使用 softmax 激活功能;我们选择最大值作为玩家标记的位置)。
移动功能
运动是从思考功能中得到的。答案被 think 映射到游戏中的一个位置,移动功能可以将玩家的标记放在该位置,从而占据该位置。
运行我们的代码
要运行代码,我们需要做的就是调用 solve 函数,您可以按照下面的代码来完成:
const gnn = new GNN(100)
gnn.solve()
我们用 100、200 和 1000 之间的不同群体大小测试了代码库,发现群体大小为 100 时代码训练更有效。它优化得非常快,我们能够获得 84%的最高适应度函数。这并不坏,但这意味着我们最好的模型只赢得了 84%的比赛。
下面是一个输出示例:
total games is 400 total loss is 65 total agents in testrun is 96937
total games is 400 total loss is 206 total agents in testrun is 97422
total games is 400 total loss is 66 total agents in testrun is 98257
total games is 400 total loss is 65 total agents in testrun is 99094
total games is 400 total loss is 67 total agents in testrun is 99926
total games is 400 total loss is 100 total agents in testrun is 100676
total games is 400 total loss is 65 total agents in testrun is 101513
total games is 400 total loss is 230 total agents in testrun is 101938
total games is 400 total loss is 66 total agents in testrun is 102773
total games is 400 total loss is 65 total agents in testrun is 103610
total games is 400 total loss is 66 total agents in testrun is 104445
total games is 400 total loss is 66 total agents in testrun is 105280
total games is 400 total loss is 66 total agents in testrun is 106115
total games is 400 total loss is 66 total agents in testrun is 106950
total games is 400 total loss is 78 total agents in testrun is 107755
total games is 400 total loss is 234 total agents in testrun is 108170
total games is 400 total loss is 66 total agents in testrun is 109005
total games is 400 total loss is 276 total agents in testrun is 109315
total games is 400 total loss is 66 total agents in testrun is 110150
total games is 400 total loss is 97 total agents in testrun is 110907
total games is 400 total loss is 81 total agents in testrun is 111704
total games is 400 total loss is 66 total agents in testrun is 112539
total games is 400 total loss is 67 total agents in testrun is 113371
total games is 400 total loss is 66 total agents in testrun is 114206
total games is 400 total loss is 67 total agents in testrun is 115038
total games is 400 total loss is 66 total agents in testrun is 115873
total games is 400 total loss is 66 total agents in testrun is 116708
total games is 400 total loss is 65 total agents in testrun is 117545
total games is 400 total loss is 232 total agents in testrun is 117965
total games is 400 total loss is 400 total agents in testrun is 117965
total games is 400 total loss is 234 total agents in testrun is 118380
total games is 400 total loss is 245 total agents in testrun is 118767
total games is 400 total loss is 66 total agents in testrun is 119602
total games is 400 total loss is 79 total agents in testrun is 120404
total games is 400 total loss is 400 total agents in testrun is 120404
total games is 400 total loss is 66 total agents in testrun is 121239
total games is 400 total loss is 66 total agents in testrun is 122074
total games is 400 total loss is 66 total agents in testrun is 122909
total games is 400 total loss is 66 total agents in testrun is 123744
total games is 400 total loss is 66 total agents in testrun is 124579
total games is 400 total loss is 67 total agents in testrun is 125411
total games is 400 total loss is 67 total agents in testrun is 126243
total games is 400 total loss is 66 total agents in testrun is 127078
total games is 400 total loss is 67 total agents in testrun is 127910
total games is 400 total loss is 66 total agents in testrun is 128745
total games is 400 total loss is 400 total agents in testrun is 128745
total games is 400 total loss is 67 total agents in testrun is 129577
total games is 400 total loss is 67 total agents in testrun is 130409
total games is 400 total loss is 65 total agents in testrun is 131246
total games is 400 total loss is 67 total agents in testrun is 132078
total games is 400 total loss is 234 total agents in testrun is 132493
total games is 400 total loss is 72 total agents in testrun is 133313
total games is 400 total loss is 66 total agents in testrun is 134148
total games is 400 total loss is 67 total agents in testrun is 134980
total games is 400 total loss is 66 total agents in testrun is 135815
total games is 400 total loss is 91 total agents in testrun is 136587
The best item so far has fitness of 0.8425 id 0.6559877604817235
结论
按照这个教程系列,我们能够创建一个机器学习模型,用遗传算法优化其权重值。我们首先通过创造一个与已知短语相匹配的短语来建立对遗传算法如何工作的理解。通过这样做,我们能够理解遗传算法如何找到问题的答案。在第 2 部分中,我们能够更新神经网络的权重。对于本教程,我们最终应用了我们所有的知识,并使用神经网络和遗传算法建立了一个模型来解决我们的井字游戏问题。我们可以将这种方法应用于不同的问题空间,从象棋到破壁机,再到预测股票价格。
时间序列预测导论:自回归模型和平滑方法
原文:https://blog.paperspace.com/time-series-forecasting-autoregressive-models-smoothing-methods/
在本系列的第 1 部分中,我们看了时间序列分析。我们学习了时间序列的不同属性,自相关、偏自相关、平稳性、平稳性检验和季节性。
在本系列的这一部分中,我们将看到如何制作时间序列模型,并预测该序列在未来的变化。具体来说,在本教程中,我们将看看自回归模型和指数平滑方法。在系列的最后一部分,我们将看看机器学习和深度学习算法,如线性回归和 LSTMs。
你也可以从 ML Showcase 上的渐变社区笔记本中跟随本文中的代码(并免费运行)。
我们将使用在上一篇文章中使用的相同数据(即德国耶拿的天气数据)进行实验。您可以使用以下命令下载它。
wget https://storage.googleapis.com/tensorflow/tf-keras-datasets/jena_climate_2009_2016.csv.zip
解压缩文件,你会发现 CSV 数据,你可以使用熊猫阅读。数据集记录了几个不同的天气参数。在本教程中,我们将使用摄氏温度。数据在 24 小时内以 10 分钟的间隔定期记录。我们将在预测模型中使用每小时的数据。
import pandas as pd
df = pd.read_csv('jena_climate_2009_2016.csv')
time = pd.to_datetime(df.pop('Date Time'), format='%d.%m.%Y %H:%M:%S')
series = df['T (degC)'][5::6]
series.index = time[5::6]
时间序列预测有不同的方法,这取决于我们在上一篇文章中讨论的趋势。如果时间序列是平稳的,自回归模型可以派上用场。如果一个序列不是稳定的,平滑方法可能会很有效。季节性可以用自回归模型和平滑方法来处理。我们还可以使用经典的机器学习算法,如线性回归、随机森林回归等。,以及基于 LSTMs 的深度学习架构。
如果你没有读过本系列的第一篇文章,我建议你在开始阅读这篇文章之前通读一遍。
自回归模型
在多元线性回归中,我们根据其他变量的值来预测一个值。模型的表达式假设输出变量和预测变量之间存在线性关系。
在自回归模型中,我们假设一个变量在时间\(t\)的值与同一个变量在过去,即时间\(t-1,t-2,...,t-p,..., 2, 1, 0\).
$ $ y _ { t } = c+\ beta _ { 1 } y _ { t-1 }+\ beta _ { 2 } y _ { t-2 }+...+\ beta _ { p } y _ { t-p }+\ epsilon _ { t } $ $
这里 p 是自回归模型的滞后阶。
对于一个 AR(1) 模型,
- 当\(\beta_1 = 0\),表示随机数据
- 当\(\beta_1 = 1\)且\(c = 0\),时,表示随机行走
- 当\(\beta_1 = 1\)且\(c \neq 0\),时,它表示具有漂移的随机游走
我们通常限制平稳时间序列的自回归模型,这意味着对于一个 AR(1) 模型\(-1 < \beta_1 < 1\)。**
另一种表示时间序列的方式是考虑一个纯移动平均(MA) 模型,其中变量的值取决于过去序列的残差。
$ $ y _ { t } = m+\ epsilon _ { t }+\ phi _ { 1 } \ epsilon _ { t-1 }+\ phi _ { 2 } \ epsilon _ { t-2 }+...+ \phi_{q}\epsilon_{t -q} $$
正如我们在上一篇文章中了解到的,如果一个时间序列不是平稳的,有多种方法可以使它平稳。最常用的方法是差分法。
ARIMA 模型考虑了上述所有三种机制,并代表了如下所示的时间序列。
$ $ y _ { t } = \ alpha+\ beta _ { 1 } y _ { t-1 }+\ beta _ { 2 } y _ { t-2 }+...+\ beta _ { p } y _ { t-p }+\ epsilon _ { t }+\ phi _ { 1 } \ epsilon _ { t-1 }+\ phi _ { 2 } \ epsilon _ { t-2 }+...+ \phi_{q}\epsilon_{t -q}$$
(其中假设该序列是平稳的。)
在将模型拟合到系列上之前,实现了固定化机制。
差分的顺序可以通过使用不同的平稳性测试和查看 PACF 图来找到。您可以参考本系列的第一部分来理解测试及其实现。
MA 顺序基于差异系列的 ACF 图。该顺序取决于去除时间序列中任何自相关所需的差分顺序。
您可以按如下方式实现,其中\(p\)是滞后订单,\(q\)是移动平均订单,\(d\)是差异订单。
from matplotlib import pyplot as plt
from sklearn.metrics import mean_squared_error
from statsmodels.tsa.arima_model import ARIMA
def ARIMA_model(df, p=1, q=1, d=1):
model = ARIMA(df, order=(p, d, q))
results_ARIMA = model.fit(disp=-1)
rmse = np.sqrt(mean_squared_error(df[1:], results_ARIMA.fittedvalues))
return results_ARIMA, rmse
这对于我们的时间序列来说是不够的,因为除了不稳定之外,我们的时间序列还有季节性趋势。我们需要一个萨里玛模型。
SARIMA 模型的等式变为(假设季节性滞后为 12):
$ $ y _ { t } = \ gamma+\ beta _ { 1 } y _ { t-1 }+\ beta _ { 2 } y _ { t-2 }+...+\ beta _ { p } y _ { t-p }+\ \ \ epsilon _ { t }+\ phi _ { 1 } \ epsilon _ { t-1 }+\ phi _ { 2 } \ epsilon _ { t-2 }+...+\ phi _ { q } \ epsilon _ { t-q }+\ \ \ Beta _ { 1 } y _ { t-12 }+\ Beta _ { 2 } y _ { t-13 }+...+\ Beta _ { q } y _ { t-12-q }+\ \ \ epsilon _ { t-12 }+\ Phi _ { 1 } \ epsilon _ { t-13 }+\ Phi _ { 2 } \ epsilon _ { t-14 }+...+ \Phi_{q}\epsilon_{t - 12 - q}$$
这是一个线性方程,系数可以通过回归算法得到。
有时一个时间序列可能被过度或不足的差异,因为 ACF 和 PACF 图可能有点棘手的推断。幸运的是,有一个工具可以用来自动选择 ARIMA 参数的超参数以及同步性。您可以使用 pip 安装 pmdarima 。
pip install pmdarima
pmdarima
使用网格搜索来搜索 ARIMA 参数的所有值,并挑选具有最低 AIC 值的模型。它还会使用您选择的平稳性测试自动计算差值。
但是,对于我们的时间序列,频率是每 365 天 1 个周期(每 8544 个数据点 1 个周期)。这对于你的电脑来说有点太重了。即使在我将数据从每小时一次减少到每天一次之后,我发现建模脚本被扼杀了。我剩下要做的唯一一件事就是将数据转换为月度数据,然后运行模型。
import pandas as pd
import matplotlib.pyplot as plt
import pmdarima as pm
import pickle
df = pd.read_csv('jena_climate_2009_2016.csv')
series = df['T (degC)'][4391::4392]
time = pd.to_datetime(df.pop('Date Time'), format='%d.%m.%Y %H:%M:%S')[4391::4392]
series.index = time
train = series[:-int(len(series)/10)]
test = series[-int(len(series)/10):]
model = pm.auto_arima(train.values,
start_p=1, # lag order starting value
start_q=1, # moving average order starting value
test='adf', #ADF test to decide the differencing order
max_p=3, # maximum lag order
max_q=3, # maximum moving average order
m=12, # seasonal frequency
d=None, # None so that the algorithm can chose the differencing order depending on the test
seasonal=True,
start_P=0,
D=1, # enforcing the seasonal frequencing with a positive seasonal difference value
trace=True,
suppress_warnings=True,
stepwise=True)
print(model.summary())
# save the model
with open('seasonal_o.pkl', 'wb') as f:
pickle.dump(model, f)
# predictions for the entire series
all_vals = model.predict(n_periods=len(series), return_conf_int=False)
all_vals = pd.Series(all_vals, index=series.index)
plt.plot(series)
plt.plot(all_vals, color='darkgreen')
plt.title('Forecast values for the entire series')
plt.xlabel('Year')
plt.ylabel('Temp (Celcius)')
plt.legend(['True', 'Predicted'])
plt.show()
# predictions for the test set with confidence values
preds, conf_vals = model.predict(n_periods=len(test), return_conf_int=True)
preds = pd.Series(preds, index=test.index)
lower_bounds = pd.Series(conf_vals[:, 0], index=list(test.index))
upper_bounds = pd.Series(conf_vals[:, 1], index=list(test.index))
plt.plot(series)
plt.plot(preds, color='darkgreen')
plt.fill_between(lower_bounds.index,
lower_bounds,
upper_bounds,
color='k', alpha=.15)
plt.title("Forecast for test values")
plt.xlabel('Year')
plt.ylabel('Temp (Celcius)')
plt.legend(['True', 'Predicted'])
plt.show()
输出如下所示。
Performing stepwise search to minimize aic
ARIMA(1,0,1)(0,1,1)[12] intercept : AIC=470.845, Time=0.18 sec
ARIMA(0,0,0)(0,1,0)[12] intercept : AIC=495.164, Time=0.01 sec
ARIMA(1,0,0)(1,1,0)[12] intercept : AIC=476.897, Time=0.09 sec
ARIMA(0,0,1)(0,1,1)[12] intercept : AIC=469.206, Time=0.10 sec
ARIMA(0,0,0)(0,1,0)[12] : AIC=493.179, Time=0.01 sec
ARIMA(0,0,1)(0,1,0)[12] intercept : AIC=489.549, Time=0.03 sec
ARIMA(0,0,1)(1,1,1)[12] intercept : AIC=inf, Time=0.37 sec
ARIMA(0,0,1)(0,1,2)[12] intercept : AIC=inf, Time=0.64 sec
ARIMA(0,0,1)(1,1,0)[12] intercept : AIC=476.803, Time=0.07 sec
ARIMA(0,0,1)(1,1,2)[12] intercept : AIC=inf, Time=0.89 sec
ARIMA(0,0,0)(0,1,1)[12] intercept : AIC=472.626, Time=0.07 sec
ARIMA(0,0,2)(0,1,1)[12] intercept : AIC=470.997, Time=0.17 sec
ARIMA(1,0,0)(0,1,1)[12] intercept : AIC=468.858, Time=0.10 sec
ARIMA(1,0,0)(0,1,0)[12] intercept : AIC=490.050, Time=0.03 sec
ARIMA(1,0,0)(1,1,1)[12] intercept : AIC=inf, Time=0.32 sec
ARIMA(1,0,0)(0,1,2)[12] intercept : AIC=inf, Time=0.58 sec
ARIMA(1,0,0)(1,1,2)[12] intercept : AIC=inf, Time=0.81 sec
ARIMA(2,0,0)(0,1,1)[12] intercept : AIC=470.850, Time=0.17 sec
ARIMA(2,0,1)(0,1,1)[12] intercept : AIC=inf, Time=0.39 sec
ARIMA(1,0,0)(0,1,1)[12] : AIC=468.270, Time=0.07 sec
ARIMA(1,0,0)(0,1,0)[12] : AIC=488.078, Time=0.01 sec
ARIMA(1,0,0)(1,1,1)[12] : AIC=inf, Time=0.25 sec
ARIMA(1,0,0)(0,1,2)[12] : AIC=inf, Time=0.52 sec
ARIMA(1,0,0)(1,1,0)[12] : AIC=475.207, Time=0.05 sec
ARIMA(1,0,0)(1,1,2)[12] : AIC=inf, Time=0.74 sec
ARIMA(0,0,0)(0,1,1)[12] : AIC=471.431, Time=0.06 sec
ARIMA(2,0,0)(0,1,1)[12] : AIC=470.247, Time=0.10 sec
ARIMA(1,0,1)(0,1,1)[12] : AIC=470.243, Time=0.15 sec
ARIMA(0,0,1)(0,1,1)[12] : AIC=468.685, Time=0.07 sec
ARIMA(2,0,1)(0,1,1)[12] : AIC=471.782, Time=0.33 sec
Best model: ARIMA(1,0,0)(0,1,1)[12]
Total fit time: 7.426 seconds
SARIMAX Results
============================================================================================
Dep. Variable: y No. Observations: 86
Model: SARIMAX(1, 0, 0)x(0, 1, [1], 12) Log Likelihood -231.135
Date: Mon, 11 Jan 2021 AIC 468.270
Time: 12:39:38 BIC 475.182
Sample: 0 HQIC 471.027
- 86
Covariance Type: opg
==============================================================================
coef std err z P>|z| [0.025 0.975]
------------------------------------------------------------------------------
ar.L1 -0.2581 0.129 -2.004 0.045 -0.510 -0.006
ma.S.L12 -0.7548 0.231 -3.271 0.001 -1.207 -0.303
sigma2 26.4239 6.291 4.200 0.000 14.094 38.754
===================================================================================
Ljung-Box (Q): 39.35 Jarque-Bera (JB): 1.36
Prob(Q): 0.50 Prob(JB): 0.51
Heteroskedasticity (H): 0.70 Skew: -0.14
Prob(H) (two-sided): 0.38 Kurtosis: 3.61
===================================================================================
最好的模型是 ARIMA(1,0,0)(0,1,1)(12)。从结果中可以看到不同的系数值和\(p\)值,都在 0.05 以下。这表明\(p\)值很重要。
预测值相对于原始时间序列的曲线如下所示。
置信区间图看起来像这样。
这些情节还不错;预测值都落在置信范围内,季节模式也能很好地捕捉到。
也就是说,当我们试图用有限的计算让算法为我们工作时,我们已经失去了数据的所有粒度。我们需要其他方法。
你可以在本文的中了解更多关于自回归模型的知识。
平滑方法
指数平滑法常用于时间序列预测。他们利用指数窗口函数来平滑时间序列。平滑方法也有多种变化。
指数平滑的最简单形式可以这样想:
$ $ s _ { 0 } = x _ { 0 } \ \ s _ { t } = \ alpha x _ { t }+(1-\ alpha)s _ { t-1 } = s _ { t-1 }+\ alpha(x _ { t }-s _ { t-1 })$ $
其中\(x\) 表示原始值,\(s\)表示预测值,\(\alpha\) 是平滑因子,其中:
这意味着平滑的统计量\(s_{t}\)是当前实际值和前一时间步长的平滑值的加权平均值,前一时间步长值作为常数添加。
为了更好地平滑曲线,平滑因子的值(有点违反直觉)需要更低。有\(\alpha = 1\)相当于原始时间序列。平滑因子可以通过使用最小二乘法来找到,其中您可以最小化以下内容。
平滑方法称为指数平滑,因为当您递归应用公式时:
$ $ s _ { t } = \ alpha x _ { t }+(1-\ alpha)s _ { t-1 } $ $
您将获得:
$ $ s _ { t } = \ alpha \ sum _ { I = 0}{t}(1-\alpha)i x _ { t-I } $ $
这是一个几何级数,即指数函数的离散形式。
我们将实现指数平滑的三种变体:简单指数平滑、霍尔特线性平滑和霍尔特指数平滑。我们将尝试找出改变不同平滑算法的超参数如何改变我们的预测输出,并看看哪一个最适合我们。
from statsmodels.tsa.holtwinters import SimpleExpSmoothing, Holt
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_squared_error
def exponential_smoothing(df, smoothing_level=0.2):
model = SimpleExpSmoothing(np.asarray(df))
model._index = pd.to_datetime(df.index)
fit = model.fit(smoothing_level=smoothing_level)
return fit
def holts_linear_smoothing(df, smoothing_level=0.3, smoothing_slope=0.05):
model = Holt(np.asarray(df))
model._index = pd.to_datetime(df.index)
fit = model.fit(smoothing_level=smoothing_level, smoothing_slope=smoothing_slope)
return fit
def holts_exponential_smoothing(df, trend=None, damped=False, seasonal=None, seasonal_periods=None):
model = ExponentialSmoothing(np.asarray(df), trend=trend, seasonal=seasonal, damped=damped, seasonal_periods=seasonal_periods)
model._index = pd.to_datetime(df.index)
fit = model.fit()
return fit
所有三个模型都有不同的超参数,我们将使用网格搜索进行测试。我们还将返回 RMSE 值供我们比较,并获得最佳模型。
import numpy as np
def smoothing_experiments(train, test, params, method):
methods = ['simpl_exp', 'holts_lin', 'holts_exp']
models = {}
preds = {}
rmse = {}
if method == methods[0]:
for s in params['smoothing_level']:
models[s] = exponential_smoothing(train, smoothing_level=s)
preds[s] = models[s].predict(start=1,end=len(test))
preds[s] -= preds[s][0]
preds[s] += train.values[-1]
rmse[s] = np.sqrt(mean_squared_error(test, preds[s]))
elif method == methods[1]:
for sl in params['smoothing_level']:
for ss in params['smoothing_trend']:
models[(sl,ss)] = holts_linear_smoothing(train, smoothing_level=sl, smoothing_slope=ss)
preds[(sl,ss)] = models[(sl,ss)].predict(start=1,end=len(test))
preds[(sl,ss)] -= preds[(sl,ss)][0]
preds[(sl,ss)] += train.values[-1]
rmse[(sl,ss)] = np.sqrt(mean_squared_error(test, preds[(sl,ss)]))
elif method == methods[2]:
for t in params['trend']:
for d in params['damped_trend']:
models[(t,d)] = holts_exponential_smoothing(train, trend=t, damped=d)
preds[(t,d)] = models[(t,d)].predict(start=1,end=len(test))
preds[(t,d)] -= preds[(t,d)][0]
preds[(t,d)] += train.values[-1]
rmse[(t,d)] = np.sqrt(mean_squared_error(test, preds[(t,d)]))
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(train.index, train.values, color="gray", label='train original')
ax.plot(test.index, test.values, color="gray", label='test original')
for p, f, r in zip(list(preds.values()),list(models.values()),list(rmse.values())):
if method == methods[0]:
ax.plot(test.index, p, label="alpha="+str(f.params['smoothing_level'])[:3]+" RMSE: "+str(r))
ax.set_title("Simple Exponential Smoothing")
ax.legend();
elif method == methods[1]:
ax.plot(test.index, p, label="alpha="+str(f.params['smoothing_level'])[:4]+", beta="+str(f.params['smoothing_trend'])[:4]+" RMSE: "+str(r))
ax.set_title("Holts Linear Smoothing")
ax.legend();
elif method == methods[2]:
ax.plot(test.index, p,
label="alpha="+str(f.params['smoothing_level'])[:4]+", beta="+str(f.params['smoothing_trend'])[:4]+ ", damping="+str(True if f.params['damping_trend']>0 else False)+" RMSE: "+str(r),
)
ax.set_title("Holts Exponential Smoothing")
ax.legend();
plt.show()
return models, preds, rmse
现在我们用不同的超参数进行实验。
# defining the parameters for hyperparameter search
simpl_exp_params = {
'smoothing_level': [0.2, 0.3, 0.5],
}
holts_lin_params = {
'smoothing_level': [0.2, 0.3, 0.5],
'smoothing_trend': [0.05, 0.1, 0.2],
}
holts_exp_params = {
'trend': ['add'],
'damped_trend': [False, True],
}
df = pd.read_csv('jena_climate_2009_2016.csv')
series = df['T (degC)'][5::6]
time = pd.to_datetime(df.pop('Date Time'), format='%d.%m.%Y %H:%M:%S')[5::6]
series.index = time
train = series[:-int(len(series)/10)]
test = series[-int(len(series)/10):]
simpl_exp_models, simpl_exp_preds, simpl_exp_rmse = smoothing_experiments(train, test, simpl_exp_params, 'simpl_exp')
holts_lin_models, holts_lin_preds, holts_lin_rmse = smoothing_experiments(train, test, holts_lin_params, 'holts_lin')
holts_exp_models, holts_exp_preds, holts_exp_rmse = smoothing_experiments(train, test, holts_exp_params, 'holts_exp')
让我们看看我们的阴谋是如何实现的。
由于我们的数据非常密集,从头到尾看这些图会显得杂乱无章。让我们看看放大后的数据是什么样子。
我们可以找到所有三种方法的最佳模型,并对它们进行比较。
# find the best model based on the RMSE values
def get_best_model(rmse):
min_rmse = min(rmse.values())
best = [key for key in rmse if rmse[key] == min_rmse]
return best[0]
best1 = get_best_model(simpl_exp_rmse)
best2 = get_best_model(holts_lin_rmse)
best3 = get_best_model(holts_exp_rmse)
# get plots for all the exponential smoothing best models
plt.figure(figsize=(12,6))
plt.plot(train.index, train.values, color='gray')
plt.plot(test.index, test.values, color='gray')
plt.title('Simple and Holt Smoothing Forecast')
preds = simpl_exp_preds[best1]
rmse = np.sqrt(mean_squared_error(test, preds))
plt.plot(test.index[:len(preds)], preds, color='red', label='preds - simple exponential smoothing - RMSE - {}'.format(rmse))
plt.legend()
preds = holts_lin_preds[best2]
rmse = np.sqrt(mean_squared_error(test, preds))
plt.plot(test.index[:len(preds)], preds, color='green', label='preds - holts linear smoothing - RMSE - {}'.format(rmse))
plt.legend()
preds = holts_exp_preds[best3]
rmse = np.sqrt(mean_squared_error(test, preds))
plt.plot(test.index[:len(preds)], preds, color='orange', label='preds - holts exponential smoothing - RMSE - {}'.format(rmse))
plt.legend()
plt.show()
我们发现以下结果。
事实证明,简单指数模型具有最小的 RMSE 值,因此是我们拥有的最佳模型。
plt.figure(figsize=(12,6))
plt.plot(train.index, train.values, color='gray')
plt.plot(test.index, test.values, color='gray')
plt.title('Simple Exponential Smoothing (Best Model) Forecast ')
preds = simpl_exp_preds[best1]
rmse = np.sqrt(mean_squared_error(test, preds))
plt.plot(test.index[:len(preds)], preds, color='red', label='preds - simple exponential smoothing - RMSE - {}'.format(rmse))
plt.legend()
plt.show()
您可以在这里找到并运行本系列文章的代码。
结论
在本系列的这一部分中,我们主要关注自回归模型,探索了移动平均项、滞后阶数、差分、季节性因素及其实施,包括基于网格搜索的超参数选择。然后,我们转向指数平滑方法,研究简单指数平滑、霍尔特线性和指数平滑、基于网格搜索的超参数选择以及离散用户定义的搜索空间、最佳模型选择和推理。
在下一部分中,我们将了解如何为线性回归、随机森林回归等经典机器学习算法以及 LSTMs 等深度学习算法创建特征、训练模型和进行预测。
希望你喜欢阅读。
时间序列预测导论:回归和 LSTMs
原文:https://blog.paperspace.com/time-series-forecasting-regression-and-lstm/
在本系列的第一部分,时间序列分析简介,我们介绍了时间序列的不同属性,自相关、偏自相关、平稳性、平稳性测试和季节性。
第二部分我们介绍了时间序列预测。我们研究了如何建立预测模型,以获取时间序列并预测该序列在未来的变化。具体来说,我们研究了自回归模型和指数平滑模型。
在本系列的最后一部分,我们将研究用于时间序列预测的机器学习和深度学习算法,包括线性回归和各种类型的 LSTMs。
你可以在 ML Showcase 的渐变社区笔记本上找到这个系列的代码并免费运行。
介绍
在我们的实验中,我们将使用在以前的文章中使用的相同数据,即来自德国耶拿的天气数据。您可以使用以下命令下载它。
wget https://storage.googleapis.com/tensorflow/tf-keras-datasets/jena_climate_2009_2016.csv.zip
打开 zip 文件并将数据加载到 Pandas 数据框架中。
import pandas as pd
df = pd.read_csv('jena_climate_2009_2016.csv')
time = pd.to_datetime(df.pop('Date Time'), format='%d.%m.%Y %H:%M:%S')
series = df['T (degC)'][5::6]
series.index = time[5::6]
在我们能够建立我们的模型之前,我们将不得不做一些基本的特征工程。我们使用特定长度的窗口从时间序列中创建滞后值矩阵。假设窗口长度为 4。那么你的数组看起来会像这样:
$ $ \ begin { v matrix } x _ { t } & x _ { t-1 } & x _ { t-2 } & x _ { t-3 } \ \ x _ { t-1 } & x _ { t-2 } & x _ { t-3 } & x _ { t-4 } \ ..&..&..&..\ ..&..&..&..\ \ x _ { 4 } & x _ { 3 } & x _ { 2 } & x _ { 1 } \ \ x _ { 3 } & x _ { 2 } & x _ { 1 } & x _ { 0 } \ end { v matrix } $ $
在我们的预测模型中,最后一列将作为标签,其余列作为预测值。
在我们的例子中,由于每日数据的季节性,选择一天的窗口大小是有意义的。
import numpy as np
# function for generating the lagged matrix
def split_sequence(sequence, window_size):
X = []
y = []
# for all indexes
for i in range(len(sequence)):
end_idx = i + window_size
# exit condition
if end_idx > len(sequence) - 1:
break
# get X and Y values
seq_x, seq_y = sequence[i:end_idx], sequence[end_idx]
X.append(seq_x)
y.append(seq_y)
return np.array(X), np.array(y)
train = series[:-int(len(series)/10)]
test = series[-int(len(series)/10):]
X_train, y_train = split_sequence(train, window_size=24)
使用回归对时间序列建模
回归算法试图找到给定数据集的最佳拟合线。线性回归算法试图最小化观察值和预测值之间的差的平方和的值。OLS 回归有几个被称为高斯-马尔可夫假设的基本假设。横截面数据的高斯-马尔可夫假设包括以下内容:
- 参数(例如,双变量回归线中最佳拟合线的斜率值)应该是线性的。
- \(X\)和\(Y\)应该是随机变量。
- 多个独立变量之间没有完美的共线性。
- 残差项和自变量的协方差应该是\(0\),或者换句话说,残差项是内生的。
- 剩余项的同方差,即剩余项的方差不应随着自变量的变化而变化。
- 剩余项没有自相关。
对于时间序列数据,我们不能满足自变量都是随机变量的假设,因为不同的列来自同一过程。为了弥补这一点,对自相关性、同方差性和内生性的假设更加严格。
让我们看看一个简单的回归模型能给我们带来什么。
import statsmodels.api as sm
# train Ordinary Least Squares model
X_train = sm.add_constant(X_train)
model = sm.OLS(y_train, X_train)
result = model.fit()
print(result.summary())
模型摘要如下所示:
OLS Regression Results
==============================================================================
Dep. Variable: y R-squared: 0.992
Model: OLS Adj. R-squared: 0.992
Method: Least Squares F-statistic: 3.376e+05
Date: Fri, 22 Jan 2021 Prob (F-statistic): 0.00
Time: 13:53:01 Log-Likelihood: -70605.
No. Observations: 63058 AIC: 1.413e+05
Df Residuals: 63033 BIC: 1.415e+05
Df Model: 24
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 0.0317 0.005 6.986 0.000 0.023 0.041
x1 -0.1288 0.004 -32.606 0.000 -0.137 -0.121
x2 0.0669 0.007 10.283 0.000 0.054 0.080
x3 0.0571 0.007 8.707 0.000 0.044 0.070
x4 0.0381 0.007 5.793 0.000 0.025 0.051
x5 0.0081 0.007 1.239 0.216 -0.005 0.021
x6 0.0125 0.007 1.905 0.057 -0.000 0.025
x7 -0.0164 0.007 -2.489 0.013 -0.029 -0.003
x8 0.0062 0.007 0.939 0.348 -0.007 0.019
x9 -0.0069 0.007 -1.056 0.291 -0.020 0.006
x10 0.0148 0.007 2.256 0.024 0.002 0.028
x11 0.0029 0.007 0.436 0.663 -0.010 0.016
x12 0.0129 0.007 1.969 0.049 5.75e-05 0.026
x13 -0.0018 0.007 -0.272 0.786 -0.015 0.011
x14 -0.0033 0.007 -0.502 0.616 -0.016 0.010
x15 -0.0180 0.007 -2.731 0.006 -0.031 -0.005
x16 -0.0029 0.007 -0.437 0.662 -0.016 0.010
x17 0.0157 0.007 2.382 0.017 0.003 0.029
x18 -0.0016 0.007 -0.239 0.811 -0.014 0.011
x19 -0.0006 0.007 -0.092 0.927 -0.013 0.012
x20 -0.0062 0.007 -0.949 0.343 -0.019 0.007
x21 -0.0471 0.007 -7.163 0.000 -0.060 -0.034
x22 -0.0900 0.007 -13.716 0.000 -0.103 -0.077
x23 -0.2163 0.007 -33.219 0.000 -0.229 -0.204
x24 1.3010 0.004 329.389 0.000 1.293 1.309
==============================================================================
Omnibus: 11270.669 Durbin-Watson: 2.033
Prob(Omnibus): 0.000 Jarque-Bera (JB): 181803.608
Skew: -0.388 Prob(JB): 0.00
Kurtosis: 11.282 Cond. No. 197.
==============================================================================
大于 2 的 Durbin-Watson 值表明我们的序列没有自相关。
可以通过以下测试来检查正常性:
from scipy import stats
# get values of the residuals
residual = result.resid
# run tests and get the p values
print('p value of Jarque-Bera test is: ', stats.jarque_bera(residual)[1])
print('p value of Shapiro-Wilk test is: ', stats.shapiro(residual)[1])
print('p value of Kolmogorov-Smirnov test is: ', stats.kstest(residual, 'norm')[1])
我们得到以下结果:
p value of Jarque-Bera test is: 0.0
p value of Shapiro-Wilk test is: 0.0
p value of Kolmogorov-Smirnov test is: 0.0
假设显著性水平为 0.05,所有三个测试都表明我们的系列不是正态分布的。
对于异方差性,我们将使用以下测试:
import statsmodels.stats.api as sms
print('p value of Breusch–Pagan test is: ', sms.het_breuschpagan(result.resid, result.model.exog)[1])
print('p value of White test is: ', sms.het_white(result.resid, result.model.exog)[1])
我们得到以下结果:
p value of Breusch–Pagan test is: 2.996182722643564e-246
p value of White test is: 0.0
假设显著性水平为 0.05,两个测试表明我们的系列是异方差的。
我们的级数既不是齐次的,也不是正态分布的。幸运的是,与 OLS 不同,广义最小二乘法解释了这些残余误差。
import statsmodels.api as sm
# train Ordinary Least Squares model
X_train = sm.add_constant(X_train)
model = sm.GLS(y_train, X_train)
result = model.fit()
print(result.summary())
结果摘要如下所示。
GLS Regression Results
==============================================================================
Dep. Variable: y R-squared: 0.992
Model: GLS Adj. R-squared: 0.992
Method: Least Squares F-statistic: 3.376e+05
Date: Fri, 22 Jan 2021 Prob (F-statistic): 0.00
Time: 13:25:24 Log-Likelihood: -70605.
No. Observations: 63058 AIC: 1.413e+05
Df Residuals: 63033 BIC: 1.415e+05
Df Model: 24
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 0.0317 0.005 6.986 0.000 0.023 0.041
x1 -0.1288 0.004 -32.606 0.000 -0.137 -0.121
x2 0.0669 0.007 10.283 0.000 0.054 0.080
x3 0.0571 0.007 8.707 0.000 0.044 0.070
x4 0.0381 0.007 5.793 0.000 0.025 0.051
x5 0.0081 0.007 1.239 0.216 -0.005 0.021
x6 0.0125 0.007 1.905 0.057 -0.000 0.025
x7 -0.0164 0.007 -2.489 0.013 -0.029 -0.003
x8 0.0062 0.007 0.939 0.348 -0.007 0.019
x9 -0.0069 0.007 -1.056 0.291 -0.020 0.006
x10 0.0148 0.007 2.256 0.024 0.002 0.028
x11 0.0029 0.007 0.436 0.663 -0.010 0.016
x12 0.0129 0.007 1.969 0.049 5.75e-05 0.026
x13 -0.0018 0.007 -0.272 0.786 -0.015 0.011
x14 -0.0033 0.007 -0.502 0.616 -0.016 0.010
x15 -0.0180 0.007 -2.731 0.006 -0.031 -0.005
x16 -0.0029 0.007 -0.437 0.662 -0.016 0.010
x17 0.0157 0.007 2.382 0.017 0.003 0.029
x18 -0.0016 0.007 -0.239 0.811 -0.014 0.011
x19 -0.0006 0.007 -0.092 0.927 -0.013 0.012
x20 -0.0062 0.007 -0.949 0.343 -0.019 0.007
x21 -0.0471 0.007 -7.163 0.000 -0.060 -0.034
x22 -0.0900 0.007 -13.716 0.000 -0.103 -0.077
x23 -0.2163 0.007 -33.219 0.000 -0.229 -0.204
x24 1.3010 0.004 329.389 0.000 1.293 1.309
==============================================================================
Omnibus: 11270.669 Durbin-Watson: 2.033
Prob(Omnibus): 0.000 Jarque-Bera (JB): 181803.608
Skew: -0.388 Prob(JB): 0.00
Kurtosis: 11.282 Cond. No. 197.
==============================================================================
我们可以得到我们的预测如下:
X_test = sm.add_constant(X_test)
y_train_preds = result.predict(X_train)
y_test_preds = result.predict(X_test)
from matplotlib import pyplot as plt
# indexes start from 24 due to the window size we chose
plt.plot(pd.Series(y_train, index=train[24:].index), label='train values')
plt.plot(pd.Series(y_test, index=test[24:].index), label='test values')
plt.plot(pd.Series(y_train_preds, index=train[24:].index), label='train predictions')
plt.plot(pd.Series(y_test_preds, index=test[24:].index), label='test predictions')
plt.xlabel('Date time')
plt.ylabel('Temp (Celcius)')
plt.title('Forecasts')
plt.legend()
plt.show()
我们的预测是这样的:
放大后,测试集上的预测看起来像这样。
预测看起来非常准确!
现在,我们将看看时间序列的一些其他方法,看看它们的表现是否和回归一样好。
用递归网络模拟时间序列
RNNs,或递归神经网络,有一个隐藏层,作为一个记忆功能,在计算序列中的下一个值(当前时间步)时,考虑到先前的时间步。
递归网络会遭遇消失和爆炸梯度等问题。为了解决这些问题,LSTMs 应运而生。他们增加了多个门,如输入门和遗忘门,以避免渐变爆炸或消失的问题。
处理该问题的其他方法包括梯度裁剪和身份初始化。
我们将研究不同的基于 LSTM 的时间序列预测架构。我们将使用 PyTorch 来实现。
我们将测试普通 lstm、堆叠 lstm、双向 lstm 和 lstm,然后是全连接层。
在此之前,让我们准备好张量数据集和 dataloaders
。
首先我们加载数据。
df = pd.read_csv('jena_climate_2009_2016.csv')
time_vals = pd.to_datetime(df.pop('Date Time'), format='%d.%m.%Y %H:%M:%S')
series = df['T (degC)'][5::6]
series.index = time_vals[5::6]
然后,我们创建测试和训练分割。
# train test split
train = series[:-int(len(series)/10)]
train_idx = train.index
test = series[-int(len(series)/10):]
test_idx = test.index
我们使用 scikit-learn 中的StandardScaler
来扩展我们的数据。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
train = pd.Series(scaler.fit_transform(train.values.reshape(-1, 1))[:, 0], index=train_idx)
test = pd.Series(scaler.transform(test.values.reshape(-1, 1))[:, 0], index=test_idx)
我们使用之前定义的滞后矩阵生成函数来创建测试和训练数据集。
window_size = 24
X_train, y_train = split_sequence(train, window_size=window_size)
X_test, y_test = split_sequence(test, window_size=window_size)
然后,我们将所有这些数据转换成张量,随后是火炬TensorDataset
和DataLoaders
。
import torch
from torch.utils.data import TensorDataset, DataLoader
# convert train and test data to tensors
X_train = torch.tensor(X_train, dtype=torch.float)
y_train = torch.tensor(y_train, dtype=torch.float)
X_test = torch.tensor(X_test, dtype=torch.float)
y_test = torch.tensor(y_test, dtype=torch.float)
# use torch tensor datasets
train_data = TensorDataset(X_train, y_train)
test_data = TensorDataset(X_test, y_test)
# get data loaders
batch_size = 32
train_dataloader = DataLoader(train_data, shuffle=True, batch_size=batch_size)
test_dataloader = DataLoader(test_data, shuffle=False, batch_size=batch_size)
让我们定义我们将用来定义我们的神经网络架构的模块。我们将提供:
- 香草 LSTM
- Stacked LSTM
- 双向 LSTM
- LSTM 之后的一个全连接层
from torch import nn
class DenseLSTM(nn.Module):
def __init__(self, input_dim, hidden_dim, lstm_layers=1, bidirectional=False, dense=False):
super(DenseLSTM, self).__init__()
self.input_dim = input_dim
self.hidden_dim = hidden_dim
self.layers = lstm_layers
self.bidirectional = bidirectional
self.dense = dense
# define the LSTM layer
self.lstm = nn.LSTM(input_size=self.input_dim,
hidden_size=self.hidden_dim,
num_layers=self.layers,
bidirectional=self.bidirectional)
self.act1 = nn.ReLU()
# change linear layer inputs depending on if lstm is bidrectional
if not bidirectional:
self.linear = nn.Linear(self.hidden_dim, self.hidden_dim)
else:
self.linear = nn.Linear(self.hidden_dim * 2, self.hidden_dim)
self.act2 = nn.ReLU()
# change linear layer inputs depending on if lstm is bidrectional and extra dense layer isn't added
if bidirectional and not dense:
self.final = nn.Linear(self.hidden_dim * 2, 1)
else:
self.final = nn.Linear(self.hidden_dim, 1)
def forward(self, inputs, labels=None):
out = inputs.unsqueeze(1)
out, h = self.lstm(out)
out = self.act1(out)
if self.dense:
out = self.linear(out)
out = self.act2(out)
out = self.final(out)
return out
现在是训练功能。
import time
def fit(model, optimizer, criterion):
print("{:<8} {:<25} {:<25} {:<25}".format('Epoch',
'Train Loss',
'Test Loss',
'Time (seconds)'))
for epoch in range(epochs):
model.train()
start = time.time()
epoch_loss = []
# for batch in train data
for step, batch in enumerate(train_dataloader):
# make gradient zero to avoid accumulation
model.zero_grad()
batch = tuple(t.to(device) for t in batch)
inputs, labels = batch
# get predictions
out = model(inputs)
out.to(device)
# get loss
loss = criterion(out, labels)
epoch_loss.append(loss.float().detach().cpu().numpy().mean())
# backpropagate
loss.backward()
optimizer.step()
test_epoch_loss = []
end = time.time()
model.eval()
# for batch in validation data
for step, batch in enumerate(test_dataloader):
batch = tuple(t.to(device) for t in batch)
inputs, labels = batch
# get predictions
out = model(inputs)
# get loss
loss = criterion(out, labels)
test_epoch_loss.append(loss.float().detach().cpu().numpy().mean())
print("{:<8} {:<25} {:<25} {:<25}".format(epoch+1,
np.mean(epoch_loss),
np.mean(test_epoch_loss),
end-start))
现在我们可以开始训练了。我们定义隐藏层的大小,时期,损失函数,以及我们将使用的优化器。然后我们训练模型并绘制预测图。
device = torch.device(type='cuda')
hidden_dim = 32
epochs = 5
# vanilla LSTM
model = DenseLSTM(window_size, hidden_dim, lstm_layers=1, bidirectional=False, dense=False)
model.to(device)
# define optimizer and loss function
optimizer = torch.optim.Adam(model.parameters())
criterion = nn.MSELoss()
# initate training
fit(model, optimizer, criterion)
# get predictions on validation set
model.eval()
preds = []
for step, batch in enumerate(test_dataloader):
batch = tuple(t.to(device) for t in batch)
inputs, labels = batch
out = model(inputs)
preds.append(out)
preds = [x.float().detach().cpu().numpy() for x in preds]
preds = np.array([y for x in preds for y in x])
# plot data and predictions and applying inverse scaling on the data
plt.plot(pd.Series(scaler.inverse_transform(y_train.float().detach().cpu().numpy().reshape(-1, 1))[:, 0], index=train[window_size:].index), label='train values')
plt.plot(pd.Series(scaler.inverse_transform(y_test.float().detach().cpu().numpy().reshape(-1, 1))[:, 0], index=test[:-window_size].index), label='test values')
plt.plot(pd.Series(scaler.inverse_transform(preds.reshape(-1, 1))[:, 0], index=test[:-window_size].index), label='test predictions')
plt.xlabel('Date time')
plt.ylabel('Temp (Celcius)')
plt.title('Vanilla LSTM Forecasts')
plt.legend()
plt.show()
我们得到以下结果。
Epoch Train Loss Test Loss Time (seconds)
1 0.9967056512832642 0.7274604439735413 4.000955820083618
2 0.990510106086731 0.566585123538971 4.1188554763793945
3 0.9876610636711121 0.653666079044342 4.1249611377716064
4 0.9871518015861511 0.6488878726959229 4.147826910018921
5 0.9848467707633972 0.5858491659164429 4.120056867599487
为了测试其他 LSTM 架构,您只需要修改一行代码(除了图的标题之外)。在带有免费 GPU 的渐变社区笔记本上进行测试。但是一定要确保重用整个代码片段,因为每次训练一个新的模型时,您都需要创建一个新的优化器和损失函数实例。
对于堆叠 LSTMs:
model = DenseLSTM(window_size, hidden_dim, lstm_layers=2, bidirectional=False, dense=False)
结果是:
Epoch Train Loss Test Loss Time (seconds)
1 0.9966062903404236 0.7322613000869751 5.929869890213013
2 0.9892004728317261 0.6665323376655579 5.805717945098877
3 0.9877941608428955 0.650715172290802 5.973452806472778
4 0.9876391291618347 0.6218239665031433 6.097705602645874
5 0.9873654842376709 0.6142457127571106 6.03947639465332
对于堆叠双向 LSTMs:
model = DenseLSTM(window_size, hidden_dim, lstm_layers=2, bidirectional=True, dense=False)
结果是:
Epoch Train Loss Test Loss Time (seconds)
1 0.9923199415206909 0.5472036004066467 9.600639343261719
2 0.9756425023078918 0.37495505809783936 9.447819471359253
3 0.9717859625816345 0.3349926471710205 9.493505001068115
4 0.9706487655639648 0.3027298152446747 9.554434776306152
5 0.9707987308502197 0.3214375674724579 9.352233409881592
对于具有全连接层的堆叠双向 LSTMs:
model = DenseLSTM(window_size, hidden_dim, lstm_layers=2, bidirectional=True, dense=True)
结果是:
Epoch Train Loss Test Loss Time (seconds)
1 0.9940553903579712 0.5763574838638306 10.142680883407593
2 0.9798027276992798 0.44453883171081543 10.641068458557129
3 0.9713742733001709 0.39177998900413513 10.410093069076538
4 0.9679638147354126 0.35211560130119324 10.61124324798584
5 0.9688050746917725 0.3617672920227051 10.218038082122803
从验证损失来看,我们发现我们尝试的第三个模型(两层堆叠双向 LSTM)具有最低的损失,因此证明是我们测试的模型中最好的。
放大后,预测看起来像这样:
除了这些,你还可以做很多其他的实验:
- 为更多的时代而训练。
- 测试不同的优化器。
- 改变我们 LSTM 建筑的超参数。
- 添加更密集的层。
- 在 LSTM 之前添加一个一维卷积层。
- 在层之间使用批量标准化。
- 测试除 MSE 和 MAE 之外的损失函数。
在这些实验之后,我们仍然发现我们的回归模型比我们尝试的任何其他方法都要好得多。
结论
这是我们时间序列分析和预测系列的最后一部分。首先我们看了分析:平稳性测试,使时间序列平稳,自相关和偏自相关,频率分析等。在第 2 部分中,我们研究了 AR、MA、ARIMA、SARIMA 等预测方法,以及简单平滑和霍尔特指数平滑等平滑方法。在本文中,我们介绍了使用回归和递归网络的预测方法,如 LSTMs。
在这个系列的过程中,我们发现对于我们使用的数据,回归模型表现最好。
我希望你喜欢这些文章!
深度学习的十大云 GPU 平台
原文:https://blog.paperspace.com/top-ten-cloud-gpu-platforms-for-deep-learning/
Photo by Macrovector / Freepik
您是否需要额外的计算资源来加速密集计算,并考虑如何利用云 GPU?
您是否不确定要使用的正确平台,或者您是否正在权衡选择更好的云 GPU 平台,这些平台完全符合您的预算,并且与您的业务目标和预算兼容?
那么这篇文章正适合你。在本文中,我们将分析使用每个平台的优点和缺点,以便您可以为您的用例选择最佳平台。
什么是 GPU?
这些年来,深度学习、图形渲染和其他计算量大的领域的技术有了很大的改进,对应用程序的速度、准确性和分辨率的要求也有了显著的提高。这些改进依赖于计算资源的可用性,这些资源能够运行支持大规模和长时间应用的进程。
例如,现代游戏需要更大的存储容量来容纳额外的视觉元素。还需要更高的处理速度来支持日益高清的视觉效果和后台操作,以获得更好的游戏体验。
简而言之,我们需要更高的计算资源来运行支持现代计算密集型应用所需的大量操作。
就计算速度而言,CPU 的出现和处理器体系结构的进一步发展产生了更快的 CPU,使得运行大多数计算机操作所需的速度成为可能。但是,由于需要更快地处理更密集的操作,因此需要一种技术来为这种密集计算提供更快、更高效的可能性。这导致了 GPU 的发展。
图形处理单元GPU 是一种微处理器,它利用并行处理能力和更高的内存带宽来执行专门的任务,例如加速图形创建和同时计算。它们已经成为游戏、3D 成像、视频编辑、加密挖掘和机器学习等应用中所需的密集计算的基本要素。众所周知,在运行 CPU 速度极慢的密集计算时,GPU 速度更快、效率更高。
对于深度学习操作,GPU 比 CPU 快得多,因为训练阶段是相当资源密集型的。由于大量的卷积和密集运算,这种运算需要大量的数据点处理。这些涉及张量、权重和层之间的若干矩阵运算,用于大规模输入数据和表征深度学习项目的深度网络。
GPU 由于其众多的内核而能够更快地运行这些多重张量运算,并且由于其更高的内存带宽而能够容纳更多的数据,这使得它在运行深度学习过程中比 CPU 更高效。在 CPU 上需要 50 分钟的密集操作在低端 GPU 上可能只需要一分钟。
为什么要用云 GPU?
为什么不呢?
虽然一些用户选择拥有本地 GPU,但云 GPU 在数据科学社区中的受欢迎程度仍在持续增长。拥有内部部署的 GPU 通常需要在定制安装、管理、维护和最终升级方面投入前期费用和时间。相比之下,云平台提供的 GPU 实例只需要用户使用服务,而不需要任何技术操作,并且服务费用可以承受。
这些平台提供利用 GPU 进行计算所需的所有服务,并负责 GPU 基础设施的整体管理。
去除自我管理内部部署 GPU 所需的技术流程后,用户可以专注于自己的业务专长。从而简化业务操作并提高生产率。
除了消除管理内部 GPU 的复杂性,利用云 GPU 还可以节省时间,并且比投资和维护现场基础架构更具成本效益。这有利于较小的企业,因为它将安装和管理此类计算资源所需的资本支出转化为使用云 GPU 服务的运营成本,从而降低了他们构建深度学习基础设施的障碍。
云平台还提供其他好处,如数据迁移、可访问性、集成、存储、安全性、升级、可扩展性、协作、控制以及对无压力和高效计算的支持。
就像厨师和他们的助手一样,让其他人提供必要的配料是非常合理的,这样你就可以专注于准备食物。
云 GPU 怎么入门?
随着云平台为客户设计更加用户友好的界面,云 GPU 的入门变得越来越容易。
使用云 GPU 的第一步是选择云平台。根据各自的服务对平台进行比较,对于做出符合您需求的明智选择非常重要。虽然我在本文中对深度学习工作负载的最佳可用云 GPU 平台和实例提出了一些建议,但您可以自行探索其他选项,找到最适合您需求的选项。
选择平台后的下一步是熟悉它的界面和基础设施。在这种情况下,熟能生巧。有大量的文档、教程视频和博客可以学习如何使用大多数云平台。这些为用户提供了指南。
一些其他平台(如 Google、Amazon、IBM 和 Azure)为其服务提供学习路径和认证,以获得更好的学习体验和利用率。
如果你是云计算数据科学的绝对初学者,我建议你从 Gradient 笔记本上提供的免费、无限制的 GPU 访问开始。这将有助于您在转向更多企业平台之前获得一些实践经验。
我该如何选择合适的平台和方案?
是的,选择合适的云 GPU 平台用于多样化的个人和商业计算是一个悖论。做出选择可能会令人生畏,尤其是在可用的云平台和计划越来越多的情况下。
对于深度学习操作,云 GPU 平台的选择应该取决于其 GPU 实例的规格、基础设施、设计、定价、可用性和客户支持。特定计划的选择取决于特定的用例、数据大小、预算和工作负载。
以下是您可以利用来满足个人或商业需求的最佳云 GPU 平台列表。
10.腾讯云:
腾讯云通过各种渲染实例提供快速、稳定和弹性的云 GPU 计算,这些渲染实例利用了 NVIDIA A10、特斯拉 T4、特斯拉 P4、特斯拉 T40、特斯拉 V100 和英特尔 SG1 等 GPU。他们的服务遍及亚洲的广州、上海、北京和新加坡地区。
腾讯云平台上的 GN6s、GN7、GN8、GN10X 和 GN10XP GPU 实例支持深度学习训练和推理。他们提供按需付费的实例,可以在他们的虚拟私有云上启动,并允许连接到其他服务,无需额外费用。
该平台只允许高达 256GB 的内存大小,根据所需的资源,支持 GPU 的实例的价格在 1.72 美元/小时到 13.78 美元/小时之间。
规格及定价为 NVIDIA 特斯拉 V100 GPU 实例上腾讯云。
| ****实例 | | GPU 内存 | | 记忆 | 按需定价 |
| 特斯拉 V100T3 | 13 | 32 GB | 十核 | 40 GB | 1.72 美元/小时 |
| 特斯拉 V100T3 | 2 | 64 GB | 20 核 | 80 GB | 3.44 美元/小时 |
| 特斯拉 V100T3 | 4 | 128 GB | 40 核 | 160 GBT2 | 6.89 美元/小时 |
| 特斯拉 V100T3 | 8 | 256 GB | 80 核 | 320 GBT2 | 13.78 美元/小时 |
9.创世纪云:
Genesis cloud 采用最新技术,以合理的价格为机器学习、视觉处理和其他高性能计算工作负载提供高性能云 GPU。
他们的云 GPU 实例利用英伟达 GeForce RTX 3090、RTX 3080、RTX 3060 Ti 和 GTX 1080 Ti 等技术来加速计算。
它的计算仪表板界面简单,价格比大多数类似资源的平台都要便宜。他们还提供免费注册积分、长期计划折扣、公共 API 以及对 PyTorch 和 TensorFlow 框架的支持。
它们允许以按需和长期价格提供高达 192Gb 的内存和 80Gb 的磁盘存储。
8.拉姆达实验室云:
Lambda Labs 提供云 GPU 实例,用于从单台机器到众多虚拟机的训练和扩展深度学习模型。
他们的虚拟机预装了主要的深度学习框架、CUDA 驱动程序,并可以访问专用的 Jupyter 笔记本。通过云仪表板中的 web 终端或直接通过提供的 SSH 密钥连接到实例。
这些实例支持高达 10Gbps 的节点间带宽,用于跨众多 GPU 的分布式培训和可扩展性,从而减少了模型优化的时间。他们提供按需定价和长达 3 年的保留定价实例。
该平台上的 GPU 实例包括英伟达 RTX 6000、夸德罗 RTX 6000 和特斯拉 V100s。
在 Lambda-labs 云上 NVIDIA GPU 实例的规格和定价。
| ****GPU 实例 | | GPU 内存 | | 记忆 | 按需定价 |
| RTX a 6000T2 | 13 | 48 GB | 十四核 | 200 GBT2 | 1.45 美元/小时 |
| RTX a 6000T2 | 2 | 96 GB | 28 核 | 1tbT2 | 2.90 美元/小时 |
| RTX a 6000T2 | 4 | 192 GB | 56 核 | 1tbT2 | 5.80 美元/小时 |
| 当 RTX 6000 方块时 | 13 | 24 GB | 6 核 | 685 GB | 1.25 美元/小时 |
| 当 RTX 6000 方块时 | 2 | 48 GB | 12 内核T5 | 1.38 TB | 2.50 美元/小时 |
| 当 RTX 6000 方块时 | 4 | 96 GB | 24 内核T5 | 2.78 TB | 5.00 美元/小时 |
| 特斯拉 V100T3 | 8 | 128 GB | 92 内核T5 | 5.9 TB | 6.80 美元/小时 |
7。 IBM 云 GPU :
IBM CloudGPU 通过全球分布式数据中心网络,提供灵活的服务器选择流程以及与 IBM Cloud 架构、API 和应用的无缝集成。
其产品包括裸机服务器 GPU 选项和英特尔至强 4210、至强 5218 和至强 6248 GPU 实例。裸机实例允许客户直接在服务器硬件上运行高性能、延迟敏感、专业和传统的工作负载,就像使用本地 GPU 一样。
他们还为其裸机服务器选项提供了高达 40 核的英伟达 T4 GPU 和英特尔至强的实例,并为其虚拟服务器选项提供了英伟达 V100 和 P100 型号的实例。
虚拟服务器选项的价格从 1.95 美元/小时起,裸机服务器 GPU 选项的价格至少为 819 美元/月。
在 IBM 云上 NVIDIA GPU 实例的规格和定价。
| GPU 实例 | GPU 分配 | | 记忆 | 按需定价 |
| 特斯拉 | 13 | 8 核 | 60 GB | 1.95/小时 |
| 特斯拉 V100T3 | 13 | 8 核 | 20 GB | 3.06/小时 |
| 特斯拉 V100T3 | 13 | 8 核 | 64 GB | 2.49/小时 |
| 特斯拉 V100T3 | 2 | 16 内核T5 | 128 GB | 4.99/小时 |
| 特斯拉 V100T3 | 2 | 32 内核T5 | 256 GB | 5.98/小时 |
| 特斯拉 V100T3 | 13 | 8 核 | 60 GB | $2233/月 |
6。 甲骨文云基础设施 :
Oracle 提供裸机和虚拟机 GPU 实例,实现快速、廉价和高性能的计算。他们的 GPU 实例包括 NVIDIA Tesla V100、P100 和 A100,它们利用了低延迟网络。这允许用户按需大规模托管 500 多个 GPU 集群。
像 IBM cloud 一样,Oracle 的裸机实例允许客户运行需要在非虚拟化环境中运行的工作负载。这些实例可在美国、德国和英国地区使用,并可通过按需和可抢占的定价选项获得。
NVIDIA GPU实例在 Oracle 云基础设施上的规格和定价。
| GPU 实例 | | GPU 内存 | | 记忆 | 按需定价 |
| 特斯拉 | 13 | 16 GB | 十二核 | 72 GB | 1.275/小时 |
| 特斯拉 | 2 | 32 GB | 28 核 | 192 GB | 1.275/小时 |
| 特斯拉 V100T3 | 13 | 16 GB | 6 核 | 90 GB | 2.95/小时 |
| 特斯拉 V100T3 | 2 | 32 GB | 十二核 | 180 GBT2 | 2.95/小时 |
| 特斯拉 V100T3 | 4 | 64 GB | 24 核 | 360 GBT2 | 2.95/小时 |
| 特斯拉 V100T3 | 8 | 128 GB | 52 核 | 768 GB | 2.95/小时 |
5。 蔚蓝 N 系列 :
Azure N 系列是一系列支持 NVIDIA GPU 的虚拟机,旨在用于模拟、深度学习、图形渲染、视频编辑、游戏和远程可视化。
N 系列有三(3)个子部分,设计用于不同的工作负载。
NC 系列使用 NVIDIA Tesla V100 处理一般高性能计算和机器学习工作负载。ND 系列使用英伟达特斯拉 P40 GPU,专门用于深度学习训练和推理工作负载。NV 系列使用 NVIDIA Tesla M60 GPU,更适合图形密集型应用。NC 和 nd 虚拟机还提供可选的 InfiniBand 互连,以支持纵向扩展性能。
价格从每月 657 美元起,1 至 3 年的预订付款计划有折扣。
Azure ND 系列实例的规格和定价
| GPU 实例 | | | 记忆 | 按需定价 |
| 特斯拉 P40 | 13 | 6 核 | 112 GB | $ 1511.10/月 |
| 特斯拉 P40 | 2 | 十二核 | 224 GB | $ 3022.20/月 |
| 特斯拉 P40 | 4 | 24 颗 | 448 GB | $ 6648.84/月 |
| 特斯拉 P40 | 4 | 24 核 | 448 GB | $ 6044.40/月 |
| 特斯拉 V100T3 | 2 | 12 内核T5 | 224 GB | $ 4467.60/月 |
| 特斯拉 A100T3 | 8 | 96 核 | 900 GBT2 | $ 19853.81/月 |
| 特斯拉 A100T3 | 8 | 96 核 | 1900 国标 | $ 23922.10/月 |
4。 浩瀚艾 :
Vast AI 是一个租用低成本 GPU 进行高性能计算的全球市场。
它们通过允许主机出租其 GPU 硬件来降低计算密集型工作负载的价格,从而允许客户使用其 web 搜索界面根据需求找到最佳计算交易,并运行命令或启动 SSH 会话。
它们有一个简单的界面,并提供 SSH 实例、带有 Jupyter GUI 的 Jupyter 实例或纯命令实例。它们还提供深度学习性能函数(DLPerf ),该函数预测深度学习任务的近似性能。
Vast AI 不提供远程桌面,其系统基于 Ubuntu。它们还运行按需实例,由主机设定固定价格。只要客户端需要,这些实例就会运行。它们还提供了可中断的实例,在这些实例中,客户端为它们的实例设置投标价格,当前最高的投标运行,而其他投标暂停。
3。 谷歌计算引擎 (GCE):
谷歌计算引擎 (GCE)为计算密集型工作负载提供高性能 GPU 服务器。
GCE 使用户能够将 GPU 实例附加到新的和现有的虚拟机,并提供 TensorFlow 处理(TPU)以实现更快的经济高效的计算。
其主要产品包括广泛的 GPU 类型,如英伟达的 V100、特斯拉 K80、特斯拉 P100、特斯拉 T4、特斯拉 P4 和 A100,以满足不同的成本和性能需求,每秒计费,简单的界面,更容易与其他相关技术集成。
GCE 的定价因地区和所需的计算资源而异。
2。 【亚马逊弹性计算(EC2) :
Amazon EC2 为虚拟机提供预配置的模板,带有支持 GPU 的实例,用于加速深度学习计算。
支持 EC2 GPU 的实例被称为 P3、P4、G3、G4、G5 和 G5g。它们允许多达 4 或 8 个实例大小。亚马逊 EC2 上可用的 GPU 有英伟达特斯拉 V100、特斯拉 A100、特斯拉 M60、T4 和 A10 G 型号。
亚马逊 EC2 实例还允许轻松访问其他亚马逊网络服务,如用于将低成本 GPU 选项附加到实例的弹性图形,用于构建、培训、部署和企业扩展 ML 模型的 SageMaker,用于培训和托管工作流的虚拟私有云(VPC)以及用于存储培训数据的简单存储服务(亚马逊 S3)。
Amazon EC2 实例的定价可按需提供,并有预订计划。
亚马逊 EC2 P3 实例的规格和定价。
| GPU | | GPU 内存 | vcpu | 按需定价 |
| 特斯拉 V100T3 | 13 | 16 国标 | 8 核 | /HR |
| 特斯拉 V100T3 | 4 | 64GBT2 | 32 核 | /HR |
| 特斯拉 V100T3 | 8 | 128 GBT2 | 64 核 | /HR |
| 特斯拉 V100T3 | 8 | 256 GBT2 | 96 核 | /HR |
1。:
CORE 是由 Paperspace 构建的完全托管的云 GPU 平台,可为一系列应用提供简单、经济且加速的计算。
它的独特之处在于其简单易用的管理控制台、强大的 API 以及针对 Windows 和 Linux 系统的桌面访问。它还提供了出色的协作工具和无限的计算能力,用于运行要求最苛刻的深度学习工作负载。
它提供了最广泛的价格合理的高性能 NVIDIA GPU,这些 GPU 连接到虚拟机并预装了机器学习框架,可轻松快速地进行计算。
GPU 实例按秒计费,每小时和每月的价格较低,确保用户只使用他们使用的资源。它还提供折扣和各种实例来满足所有计算需求。
该平台旨在为用户提供最佳的简单性、性能和经济性。这使得它非常适合构建个人项目或企业应用程序。
他们的 ML Ops 平台 Gradient 也内置了许多这些功能,可以增强您构建端到端深度学习应用的体验。
paper space 核心 GPU 实例的规格和定价
| GPU 实例 | vcpu | 记忆 | 按需定价 |
| m 4000T2 | 8 核 | 30GBT2 | /HR |
| p 4000T2 | 8 核 | 30GBT2 | $0.51/HR |
| p 5000T3 | 8 核 | 30GBT2 | /HR |
| P6000 | 8 核 | 30GBT2 | /HR |
| 特斯拉 V100T3 | 8 核 | 30GBT2 | /HR |
| RTX 4000T2 | 8 核 | 30GBT2 | /HR |
| RTX 5000T2 | 8 核 | 30GBT2 | $ 0.82/小时 |
| a 4000T3 | 8 核 | 45GBT2 | $ 0.76/小时 |
| A5000 | 8 核 | 45GBT2 | $ 1.38/小时 |
| a 6000T3 | 8 核 | 45GBT2 | $ 1.89/小时 |
| 特斯拉 A100T3 | 十二核 | 90GBT2 | $ 3.09/小时 |
结论
在这篇博文中,我们考虑了使用云 GPU 来运行密集计算,并且我提出了用于深度学习操作的最佳云 GPU 平台的论点。我展示了 GPU 对于提高机器学习工作负载的性能和速度是必不可少的,以及如何利用云 GPU 比本地 GPU 更容易、更具成本效益、更省时,特别是对于小型企业和个人。
特定云 GPU 平台的选择主要取决于您的特定需求和预算。您还应该考虑这样一个平台的基础设施、定价、性能、设计和支持以及可用性。
NVIDIA Tesla A100、Tesla V100 和 Tesla P100 适用于大多数大规模深度学习工作负载,而 Tesla A4000、Tesla A5000 和 A6000 适用于几乎所有其他深度学习任务。提供这些 GPU 的平台应该优先覆盖您的所有工作负载。考虑这些平台的位置和可用性也很重要,以避免位置限制和高成本,这样您就可以以可承受的成本运行几次长时间的迭代。
基于这些因素和更多因素,Paperspace Core 在我的最佳云 GPU 平台列表中名列榜首。亚马逊 EC2 实例和谷歌计算引擎也是可靠计算的可行选择,而在广阔的人工智能市场上租用的 GPU 也可以为用户的个人项目服务。也欢迎读者探索其他选择。查看此链接,了解不同平台在纯数字方面的表现。
如何用 PyGAD 遗传算法训练 Keras 模型
原文:https://blog.paperspace.com/train-keras-models-using-genetic-algorithm-with-pygad/
PyGAD 是一个开源 Python 库,用于构建遗传算法和训练机器学习算法。它提供了广泛的参数来定制遗传算法,以处理不同类型的问题。
PyGAD 有自己的模块,支持构建和训练神经网络(NNs)和卷积神经网络(CNN)。尽管这些模块运行良好,但它们是在 Python 中实现的,没有任何额外的优化措施。这导致即使是简单的问题也需要相对较长的计算时间。
最新的 PyGAD 版本 2 . 8 . 0(2020 年 9 月 20 日发布)支持训练 Keras 模型的新模块。尽管 Keras 是用 Python 构建的,但速度很快。原因是 Keras 使用 TensorFlow 作为后端,TensorFlow 高度优化。
本教程讨论了如何使用 PyGAD 训练 Keras 模型。讨论内容包括使用顺序模型或函数式 API 构建 Keras 模型、构建 Keras 模型参数的初始群体、创建合适的适应度函数等等。
您也可以跟随本教程中的代码,并在来自 ML Showcase 的渐变社区笔记本上免费运行它。完整的教程大纲如下:
- PyGAD 入门
pygad.kerasga
模块- 使用 PyGAD 训练 Keras 模型的步骤
- 确定问题类型
- 创建 Keras 模型
- 实例化
pygad.kerasga.KerasGA
类 - 准备培训数据
- 损失函数
- 适应度函数
- 生成回调函数(可选)
- 创建一个
pygad.GA
类的实例 - 运行遗传算法
- 健身与世代图
- 有关已定型模型的统计信息
- 回归的完整代码
- 使用 CNN 分类的完整代码
让我们开始吧。
【PyGAD 入门
要开始本教程,安装 PyGAD 是必不可少的。如果您已经安装了 PyGAD,检查__version__
属性以确保至少安装了 PyGAD 2.8.0。
import pygad
print(pygad.__version__)
PyGAD 可以在 PyPI (Python 包索引)上获得,并且可以使用pip
安装程序进行安装。如果您还没有安装 PyGAD,请确保安装 PyGAD 2.8.0 或更高版本。
pip install pygad>=2.8.0
你可以在阅读文档上找到 PyGAD 文档,包括一些示例问题。下面是一个如何解决简单问题的示例,即优化线性模型的参数。
import pygad
import numpy
function_inputs = [4,-2,3.5,5,-11,-4.7]
desired_output = 44
def fitness_func(solution, solution_idx):
output = numpy.sum(solution*function_inputs)
fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001)
return fitness
num_generations = 100
num_parents_mating = 10
sol_per_pop = 20
num_genes = len(function_inputs)
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
fitness_func=fitness_func,
sol_per_pop=sol_per_pop,
num_genes=num_genes)
ga_instance.run()
ga_instance.plot_result()
pygad.kerasga
模块
从 PyGAD 2.8.0 开始,引入了一个名为kerasga
的新模块。它的名字是KerasG 遗传 A 算法的简称。该模块提供以下功能:
- 使用
KerasGA
类构建解决方案的初始群体。每个解决方案都包含 Keras 模型的所有参数。 - 使用
model_weights_as_vector()
函数将 Keras 模型的参数表示为染色体(即 1-D 向量)。 - 使用
model_weights_as_matrix()
功能从染色体中恢复 Keras 模型的参数。
pygad.kerasga
模块有一个名为KerasGA
的类。该类的构造函数接受两个参数:
- Keras 模型。
num_solutions
:群体中解的数量。
基于这两个参数,pygad.kerasga.KerasGA
类创建了 3 个实例属性:
model
:对 Keras 模型的引用。num_solutions
:群体中解的数量。population_weights
:保存模型参数的嵌套列表。该列表在每一代之后更新。
假设 Keras 模型保存在model
变量中,下面的代码创建了一个KerasGA
类的实例,并将其保存在keras_ga
变量中。num_solutions
参数被赋值为 10,这意味着群体有 10 个解。
构造函数创建一个长度等于参数num_solutions
值的列表。列表中的每个元素在使用model_weights_as_vector()
函数转换成一维向量后,为模型的参数保存不同的值。
基于KerasGA
类的实例,初始群体可以从population_weights
属性返回。假设模型有 60 个参数,有 10 个解,那么初始种群的形状就是10x60
。
import pygad.kerasga
keras_ga = pygad.kerasga.KerasGA(model=model,
num_solutions=10)
initial_population = keras_ga.population_weights
下一节总结了使用 PyGAD 训练 Keras 模型的步骤。这些步骤中的每一个都将在后面详细讨论。
使用 PyGAD 训练 Keras 模型的步骤
使用 PyGAD 训练 Keras 模型的步骤总结如下:
- 确定问题类型
- 创建 Keras 模型
- 实例化
pygad.kerasga.KerasGA
类 - 准备培训数据
- 损失函数
- 适应度函数
- 生成回调函数(可选)
- 创建一个
pygad.GA
类的实例 - 运行遗传算法
接下来的部分将讨论这些步骤。
确定问题类型
问题类型(分类或回归)有助于确定以下内容:
- 损失函数(用于构建适应度函数)
- Keras 模型中的输出图层
- 培训用数据
对于回归问题,损失函数可以是平均绝对误差、均方误差或这里列出的另一个函数。
对于一个分类问题,损失函数可以是二进制交叉熵(对于二进制分类)、分类交叉熵(对于多类问题),或者本页中所列的另一个函数。
输出层中的激活函数根据问题是分类还是回归而不同。对于分类问题,它可能是 softmax ,而对于回归问题,它可能是线性。
至于输出,与分类问题的类别标签相比,回归问题的输出将是连续函数。
总之,预先确定问题的类型至关重要,以便正确选择训练数据和损失函数。
创建 Keras 模型
构建 Keras 模型有三种方式:
PyGAD 支持使用顺序模型和函数式 API 构建 Keras 模型。
时序模型
要使用 Keras 创建顺序模型,只需使用tensorflow.keras.layers
模块创建每个层。然后创建一个tensorflow.keras.Sequential
类的实例。最后,使用add()
方法给模型添加图层。
import tensorflow.keras
input_layer = tensorflow.keras.layers.Input(3)
dense_layer1 = tensorflow.keras.layers.Dense(5, activation="relu")
output_layer = tensorflow.keras.layers.Dense(1, activation="linear")
model = tensorflow.keras.Sequential()
model.add(input_layer)
model.add(dense_layer1)
model.add(output_layer)
注意,输出层的激活函数是linear
,这意味着这是针对一个回归问题。对于分类问题,激活函数可以是softmax
。在下一行中,输出层有 2 个神经元(每个类 1 个),并使用softmax
激活函数。
output_layer = tensorflow.keras.layers.Dense(2, activation="linear")
功能 API
对于函数式 API 的情况,每一层都是正常创建的(与我们在上面看到的创建顺序模型的方式相同)。除了输入层,每个后续层都用作接受前一层作为参数的函数。最后,创建了一个tensorflow.keras.Model
类的实例,它接受输入和输出层作为参数。
input_layer = tensorflow.keras.layers.Input(3)
dense_layer1 = tensorflow.keras.layers.Dense(5, activation="relu")(input_layer)
output_layer = tensorflow.keras.layers.Dense(1, activation="linear")(dense_layer1)
model = tensorflow.keras.Model(inputs=input_layer, outputs=output_layer)
创建 Keras 模型后,下一步是使用KerasGA
类创建 Keras 模型参数的初始群体。
实例化pygad.kerasga.KerasGA
类
通过创建pygad.kerasga.KerasGA
类的实例,创建了 Keras 模型参数的初始群体。下一段代码将前一节创建的 Keras 模型传递给KerasGA
类构造函数的model
参数。
import pygad.kerasga
keras_ga = pygad.kerasga.KerasGA(model=model,
num_solutions=10)
下一节将创建用于培训 Keras 模型的培训数据。
准备培训数据
基于问题的类型(分类或回归),准备训练数据。
对于有 1 个输出的回归问题,这里有一些随机生成的训练数据,其中每个样本有 3 个输入。
# Data inputs
data_inputs = numpy.array([[0.02, 0.1, 0.15],
[0.7, 0.6, 0.8],
[1.5, 1.2, 1.7],
[3.2, 2.9, 3.1]])
# Data outputs
data_outputs = numpy.array([[0.1],
[0.6],
[1.3],
[2.5]])
下面是像 XOR 这样的二进制分类问题的具有 2 个输入的样本训练数据。准备输出,使得输出层具有 2 个神经元;每节课 1 个。
# XOR problem inputs
data_inputs = numpy.array([[0, 0],
[0, 1],
[1, 0],
[1, 1]])
# XOR problem outputs
data_outputs = numpy.array([[1, 0],
[0, 1],
[0, 1],
[1, 0]])
下一节讨论回归和分类问题的损失函数。
损失函数
损失函数因问题类型而异。本节讨论 Keras 的tensorflow.keras.losses
模块中用于回归和分类问题的一些损失函数。
回归
对于回归问题,损失函数包括:
tensorflow.keras.losses.MeanAbsoluteError()
tensorflow.keras.losses.MeanSquaredError()
查看本页了解更多信息。
下面是一个计算平均绝对误差的示例,其中y_true
和y_pred
代表真实输出和预测输出。
mae = tensorflow.keras.losses.MeanAbsoluteError()
loss = mae(y_true, y_pred).numpy()
分类
对于分类问题,损失函数包括:
tensorflow.keras.losses.BinaryCrossentropy()
为二进制分类。tensorflow.keras.losses.CategoricalCrossentropy()
用于多类分类。
查看本页了解更多信息。
下面是一个计算二元类熵的示例:
bce = tensorflow.keras.losses.BinaryCrossentropy()
loss = bce(y_true, y_pred).numpy()
基于损失函数,根据下一部分准备适应度函数。
健身功能
分类或回归问题的损失函数是最小化函数,而遗传算法的适应函数是最大化函数。因此,适应值是作为损失值的倒数来计算的。
fitness_value = 1.0 / loss
用于计算模型的适应值的步骤如下:
- 从一维向量恢复模型参数。
- 设置模型参数。
- 做预测。
- 计算损失值。
- 计算适应值。
- 返回适应值。
回归适合度
下面的代码构建了完整的适应度函数,它与 PyGAD 一起处理回归问题。PyGAD 中的 fitness 函数是一个常规的 Python 函数,它有两个参数。第一个表示要计算适应值的解。第二个参数是群体内解的指数,这在某些情况下可能是有用的。
传递给适应度函数的解是一维向量。为了从这个向量恢复 Keras 模型的参数,使用了pygad.kerasga.model_weights_as_matrix()
。
model_weights_matrix = pygad.kerasga.model_weights_as_matrix(model=model, weights_vector=solution)
一旦参数被恢复,它们就被set_weights()
方法用作模型的当前参数。
model.set_weights(weights=model_weights_matrix)
基于当前参数,模型使用predict()
方法预测输出。
predictions = model.predict(data_inputs)
预测输出用于计算损失值。平均绝对误差被用作损失函数。
mae = tensorflow.keras.losses.MeanAbsoluteError()
因为损失值可能是 0.0,所以最好像0.00000001
一样给它加上一个小值,以避免在计算适应值时被零除。
solution_fitness = 1.0 / (mae(data_outputs, predictions).numpy() + 0.00000001)
最后,返回适应值。
def fitness_func(solution, sol_idx):
global data_inputs, data_outputs, keras_ga, model
model_weights_matrix = pygad.kerasga.model_weights_as_matrix(model=model,
weights_vector=solution)
model.set_weights(weights=model_weights_matrix)
predictions = model.predict(data_inputs)
mae = tensorflow.keras.losses.MeanAbsoluteError()
solution_fitness = 1.0 / (mae(data_outputs, predictions).numpy() + 0.00000001)
return solution_fitness
二元分类的适合度
对于二元分类问题,下面是一个适用于 PyGAD 的适应度函数。它计算二元交叉熵损失,假设分类问题是二元的。
def fitness_func(solution, sol_idx):
global data_inputs, data_outputs, keras_ga, model
model_weights_matrix = pygad.kerasga.model_weights_as_matrix(model=model,
weights_vector=solution)
model.set_weights(weights=model_weights_matrix)
predictions = model.predict(data_inputs)
bce = tensorflow.keras.losses.BinaryCrossentropy()
solution_fitness = 1.0 / (bce(data_outputs, predictions).numpy() + 0.00000001)
return solution_fitness
下一节将构建一个在每代结束时执行的回调函数。
生成回调函数(可选)
在每一次生成完成后,可以调用一个回调函数来计算一些关于最新到达的参数的统计数据。这一步是可选的,仅用于调试目的。
生成回调函数实现如下。在 PyGAD 中,这个回调函数必须接受一个引用遗传算法实例的参数,通过这个参数可以使用population
属性获取当前人口。
该函数打印最佳解决方案的当前世代号和适应值。这种信息使用户不断更新遗传算法的进展。
def callback_generation(ga_instance):
print("Generation = {generation}".format(generation=ga_instance.generations_completed))
print("Fitness = {fitness}".format(fitness=ga_instance.best_solution()[1]))
创建一个pygad.GA
类的实例
使用 PyGAD 训练 Keras 模型的下一步是创建一个pygad.GA
类的实例。这个类的构造函数接受许多参数,这些参数可以在文档中找到。
下一个代码块通过为这个应用程序传递最少数量的参数来实例化pygad.GA
类,这些参数是:
num_generations
:世代数。num_parents_mating
:要交配的父母数量。initial_population
:Keras 模型参数的初始群体。fitness_func
:健身功能。on_generation
:生成回调函数。
请注意,在KerasGA
类的构造函数中,群体中的解的数量先前被设置为 10。因此,要交配的亲本数量必须少于 10 个。
num_generations = 250
num_parents_mating = 5
initial_population = keras_ga.population_weights
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
initial_population=initial_population,
fitness_func=fitness_func,
on_generation=callback_generation)
下一部分运行遗传算法来开始训练 Keras 模型。
运行遗传算法
pygad.GA
类的实例通过调用run()
方法来运行。
ga_instance.run()
通过执行这个方法,PyGAD 的生命周期按照下图开始。
下一节讨论如何对训练好的模型得出一些结论。
适应度对比世代图
使用pygad.GA
类中的plot_result()
方法,PyGAD 创建了一个图形,显示每一代的适应值是如何变化的。
ga_instance.plot_result(title="PyGAD & Keras - Iteration vs. Fitness", linewidth=4)
关于训练模型的统计
pygad.GA
类有一个名为best_solution()
的方法,它返回 3 个输出:
- 找到的最佳解决方案。
- 最佳解的适应值。
- 群体中最佳解决方案的索引。
下面的代码调用了best_solution()
方法,并打印出关于找到的最佳解决方案的信息。
solution, solution_fitness, solution_idx = ga_instance.best_solution()
print("Fitness value of the best solution = {solution_fitness}".format(solution_fitness=solution_fitness))
print("Index of the best solution : {solution_idx}".format(solution_idx=solution_idx))
接下来,我们将从最佳解决方案中恢复 Keras 模型的权重。基于恢复的权重,该模型预测训练样本的输出。您还可以预测新样本的输出。
# Fetch the parameters of the best solution.
best_solution_weights = pygad.kerasga.model_weights_as_matrix(model=model,
weights_vector=solution)
model.set_weights(best_solution_weights)
predictions = model.predict(data_inputs)
print("Predictions : \n", predictions)
下面的代码计算损失,即平均绝对误差。
mae = tensorflow.keras.losses.MeanAbsoluteError()
abs_error = mae(data_outputs, predictions).numpy()
print("Absolute Error : ", abs_error)
回归的完整代码
对于使用平均绝对误差作为损失函数的回归问题,这里是完整的代码。
import tensorflow.keras
import pygad.kerasga
import numpy
import pygad
def fitness_func(solution, sol_idx):
global data_inputs, data_outputs, keras_ga, model
model_weights_matrix = pygad.kerasga.model_weights_as_matrix(model=model,
weights_vector=solution)
model.set_weights(weights=model_weights_matrix)
predictions = model.predict(data_inputs)
mae = tensorflow.keras.losses.MeanAbsoluteError()
abs_error = mae(data_outputs, predictions).numpy() + 0.00000001
solution_fitness = 1.0 / abs_error
return solution_fitness
def callback_generation(ga_instance):
print("Generation = {generation}".format(generation=ga_instance.generations_completed))
print("Fitness = {fitness}".format(fitness=ga_instance.best_solution()[1]))
input_layer = tensorflow.keras.layers.Input(3)
dense_layer1 = tensorflow.keras.layers.Dense(5, activation="relu")(input_layer)
output_layer = tensorflow.keras.layers.Dense(1, activation="linear")(dense_layer1)
model = tensorflow.keras.Model(inputs=input_layer, outputs=output_layer)
weights_vector = pygad.kerasga.model_weights_as_vector(model=model)
keras_ga = pygad.kerasga.KerasGA(model=model,
num_solutions=10)
# Data inputs
data_inputs = numpy.array([[0.02, 0.1, 0.15],
[0.7, 0.6, 0.8],
[1.5, 1.2, 1.7],
[3.2, 2.9, 3.1]])
# Data outputs
data_outputs = numpy.array([[0.1],
[0.6],
[1.3],
[2.5]])
num_generations = 250
num_parents_mating = 5
initial_population = keras_ga.population_weights
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
initial_population=initial_population,
fitness_func=fitness_func,
on_generation=callback_generation)
ga_instance.run()
# After the generations complete, some plots are showed that summarize how the outputs/fitness values evolve over generations.
ga_instance.plot_result(title="PyGAD & Keras - Iteration vs. Fitness", linewidth=4)
# Returning the details of the best solution.
solution, solution_fitness, solution_idx = ga_instance.best_solution()
print("Fitness value of the best solution = {solution_fitness}".format(solution_fitness=solution_fitness))
print("Index of the best solution : {solution_idx}".format(solution_idx=solution_idx))
# Fetch the parameters of the best solution.
best_solution_weights = pygad.kerasga.model_weights_as_matrix(model=model,
weights_vector=solution)
model.set_weights(best_solution_weights)
predictions = model.predict(data_inputs)
print("Predictions : \n", predictions)
mae = tensorflow.keras.losses.MeanAbsoluteError()
abs_error = mae(data_outputs, predictions).numpy()
print("Absolute Error : ", abs_error)
代码完成后,下图显示了适应值是如何增加的。这说明 Keras 模型学习正常。
以下是关于训练模型的更多细节。请注意,预测值接近真实值。平均相对误差为 0.018。
Fitness value of the best solution = 54.79189095217631
Index of the best solution : 0
Predictions :
[[0.11471477]
[0.6034051 ]
[1.3416876 ]
[2.486804 ]]
Absolute Error : 0.018250866
使用 CNN 完成分类代码
以下代码使用 Keras 构建了一个卷积神经网络,用于对 80 幅图像的数据集进行分类,其中每幅图像的大小为100x100x3
。请注意,使用分类交叉熵损失是因为数据集有 4 个类。
import tensorflow.keras
import pygad.kerasga
import numpy
import pygad
def fitness_func(solution, sol_idx):
global data_inputs, data_outputs, keras_ga, model
model_weights_matrix = pygad.kerasga.model_weights_as_matrix(model=model,
weights_vector=solution)
model.set_weights(weights=model_weights_matrix)
predictions = model.predict(data_inputs)
cce = tensorflow.keras.losses.CategoricalCrossentropy()
solution_fitness = 1.0 / (cce(data_outputs, predictions).numpy() + 0.00000001)
return solution_fitness
def callback_generation(ga_instance):
print("Generation = {generation}".format(generation=ga_instance.generations_completed))
print("Fitness = {fitness}".format(fitness=ga_instance.best_solution()[1]))
# Build the keras model using the functional API.
input_layer = tensorflow.keras.layers.Input(shape=(100, 100, 3))
conv_layer1 = tensorflow.keras.layers.Conv2D(filters=5,
kernel_size=7,
activation="relu")(input_layer)
max_pool1 = tensorflow.keras.layers.MaxPooling2D(pool_size=(5,5),
strides=5)(conv_layer1)
conv_layer2 = tensorflow.keras.layers.Conv2D(filters=3,
kernel_size=3,
activation="relu")(max_pool1)
flatten_layer = tensorflow.keras.layers.Flatten()(conv_layer2)
dense_layer = tensorflow.keras.layers.Dense(15, activation="relu")(flatten_layer)
output_layer = tensorflow.keras.layers.Dense(4, activation="softmax")(dense_layer)
model = tensorflow.keras.Model(inputs=input_layer, outputs=output_layer)
keras_ga = pygad.kerasga.KerasGA(model=model,
num_solutions=10)
# Data inputs
data_inputs = numpy.load("dataset_inputs.npy")
# Data outputs
data_outputs = numpy.load("dataset_outputs.npy")
data_outputs = tensorflow.keras.utils.to_categorical(data_outputs)
num_generations = 200
num_parents_mating = 5
initial_population = keras_ga.population_weights
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
initial_population=initial_population,
fitness_func=fitness_func,
on_generation=callback_generation)
ga_instance.run()
ga_instance.plot_result(title="PyGAD & Keras - Iteration vs. Fitness", linewidth=4)
# Returning the details of the best solution.
solution, solution_fitness, solution_idx = ga_instance.best_solution()
print("Fitness value of the best solution = {solution_fitness}".format(solution_fitness=solution_fitness))
print("Index of the best solution : {solution_idx}".format(solution_idx=solution_idx))
# Fetch the parameters of the best solution.
best_solution_weights = pygad.kerasga.model_weights_as_matrix(model=model,
weights_vector=solution)
model.set_weights(best_solution_weights)
predictions = model.predict(data_inputs)
# print("Predictions : \n", predictions)
# Calculate the categorical crossentropy for the trained model.
cce = tensorflow.keras.losses.CategoricalCrossentropy()
print("Categorical Crossentropy : ", cce(data_outputs, predictions).numpy())
# Calculate the classification accuracy for the trained model.
ca = tensorflow.keras.metrics.CategoricalAccuracy()
ca.update_state(data_outputs, predictions)
accuracy = ca.result().numpy()
print("Accuracy : ", accuracy)
下图显示了适应值是如何逐代演变的。只要适应度值增加,就可以增加代数来达到更好的精度。
以下是有关已训练模型的一些信息:
Fitness value of the best solution = 2.7462310258668805
Categorical Crossentropy : 0.3641354
Accuracy : 0.75
结论
在本教程中,我们看到了如何使用遗传算法和开源的 PyGAD 库来训练 Keras 模型。Keras 模型可以使用顺序模型或函数式 API 来创建。
使用pygad.kerasga
模块创建 Keras 模型权重的初始群体,其中每个解决方案包含模型的不同权重集。这个种群随后按照 PyGAD 的生命周期进化,直到所有的世代都完成。
由于 Keras 后端 TensorFlow 的高速特性,PyGAD 可以在可接受的时间内训练复杂的架构。
如何在自定义数据集上训练 YOLO v5
2022 年 10 月 20 日更新——本教程现在提供了一些不推荐使用的数据集来源代码。请参阅我们在 YOLOv7 上更新的教程,了解关于在渐变笔记本中获取数据集以进行演示的更多说明。
YOLO,或YouOonlyLookOnce,是目前使用最广泛的基于深度学习的物体检测算法之一。在本教程中,我们将介绍如何在自定义数据集上训练它的最新变种 YOLOv5。更准确地说,我们将在路标数据集上训练 YOLO v5 检测器。在这篇文章结束时,你将拥有一个可以定位和分类路标的物体探测器。
你也可以使用本文的渐变笔记本在免费的 GPU 上运行这段代码。
在我们开始之前,让我承认 YOLOv5 在发布时引起了相当多的争议,争论的焦点是称它为 v5 是否正确。我在这篇文章的结尾已经提到了这个问题。现在,我只想说我把这个算法称为 YOLOv5,因为它是代码库的名字。
我决定选择 YOLOv5 而不是其他版本,是因为它是 YOLO 维护最活跃的 Python 端口。像 YOLO v4 这样的其他变体是用 C 编写的,对于典型的深度学习实践者来说,可能不像 Python 那样容易理解。
说完这些,我们开始吧。
这个职位的结构如下。
- 设置代码
- 下载数据
- 将注释转换为 YOLO 版本 5 的格式
- YOLO v5 注释格式
- 测试注释
- 对数据集进行分区
- 培训选项
- 数据配置文件
- 超参数配置文件
- 定制网络架构
- 训练模型
- 推理
- 在测试数据集上计算地图
- 结论...以及一些关于命名的传奇故事
设置代码
我们首先克隆 YOLO 版本 5 的存储库,并设置运行 YOLO 版本 5 所需的依赖关系。你可能需要sudo
权限来安装一些软件包。
在终端中,键入:
git clone https://github.com/ultralytics/yolov5
我建议您创建一个新的conda
或virtualenv
环境来运行您的 YOLO v5 实验,以免弄乱任何现有项目的依赖关系。
激活新环境后,使用 pip 安装依赖项。确保您使用的 pip 是新环境的 pip。您可以通过在终端中键入。
which pip
对我来说,它显示了这样的东西。
/home/ayoosh/miniconda3/envs/yolov5/bin/pip
它告诉我,我正在使用的 pip 是我刚刚创建的名为yolov5
的新环境。如果您正在使用属于不同环境的 pip,您的 python 将被安装到那个不同的库,而不是您创建的库。
解决这个问题后,让我们继续安装。
pip install -r yolov5/requirements.txt
安装完依赖项后,现在让我们导入所需的模块,以结束代码的设置。
import torch
from IPython.display import Image # for displaying images
import os
import random
import shutil
from sklearn.model_selection import train_test_split
import xml.etree.ElementTree as ET
from xml.dom import minidom
from tqdm import tqdm
from PIL import Image, ImageDraw
import numpy as np
import matplotlib.pyplot as plt
random.seed(108)
下载数据
对于本教程,我们将使用来自 MakeML 的路标对象检测数据集。
该数据集包含属于 4 类的路标:
- 交通灯
- 停止
- 速度限制
- 人行横道
Road Sign Dataset
数据集很小,总共只包含 877 幅图像。虽然您可能希望使用更大的数据集(如 LISA 数据集)进行训练,以充分实现 YOLO 的功能,但我们在本教程中使用一个小数据集来促进快速原型制作。典型的训练需要不到半个小时,这将允许您快速迭代涉及不同超参数的实验。
我们现在创建一个名为Road_Sign_Dataset
的目录来保存我们的数据集。这个目录需要与我们刚刚克隆的yolov5
存储库文件夹在同一个文件夹中。
mkdir Road_Sign_Dataset
cd Road_Sign_Dataset
下载数据集。
wget -O RoadSignDetectionDataset.zip https://arcraftimages.s3-accelerate.amazonaws.com/Datasets/RoadSigns/RoadSignsPascalVOC.zip?region=us-east-2
解压缩数据集。
unzip RoadSignDetectionDataset.zip
删除不需要的文件。
rm -r __MACOSX RoadSignDetectionDataset.zip
将注释转换为 YOLO 版本 5 的格式
在这一部分中,我们将注释转换成 YOLO 版本 5 所期望的格式。对于对象检测数据集的注释,有多种格式。
我们下载的数据集的注释遵循 PASCAL VOC XML 格式,这是一种非常流行的格式。由于这是一种流行的格式,你可以找到在线转换工具。然而,我们将为它编写代码,以便让您了解如何转换不太流行的格式(您可能找不到流行的工具)。
PASCAL VOC 格式将其注释存储在 XML 文件中,其中各种属性由标签描述。让我们看一个这样的注释文件。
# Assuming you're in the data folder
cat annotations/road4.xml
输出如下所示。
<annotation>
<folder>images</folder>
<filename>road4.png</filename>
<size>
<width>267</width>
<height>400</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>trafficlight</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<occluded>0</occluded>
<difficult>0</difficult>
<bndbox>
<xmin>20</xmin>
<ymin>109</ymin>
<xmax>81</xmax>
<ymax>237</ymax>
</bndbox>
</object>
<object>
<name>trafficlight</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<occluded>0</occluded>
<difficult>0</difficult>
<bndbox>
<xmin>116</xmin>
<ymin>162</ymin>
<xmax>163</xmax>
<ymax>272</ymax>
</bndbox>
</object>
<object>
<name>trafficlight</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<occluded>0</occluded>
<difficult>0</difficult>
<bndbox>
<xmin>189</xmin>
<ymin>189</ymin>
<xmax>233</xmax>
<ymax>295</ymax>
</bndbox>
</object>
</annotation>
上面的注释文件描述了一个名为road4.jpg
的文件,其尺寸为267 x 400 x 3
。它有 3 个object
标签,代表 3 个边界框。该类由name
标签指定,而边界框的细节由bndbox
标签表示。边界框由其左上角(x_min
、y_min
)和右下角(xmax
、ymax
)的坐标来描述。
YOLO v5 注释格式
YOLO v5 希望每个图像都有一个.txt
文件形式的注释,其中文本文件的每一行都描述了一个边界框。考虑下面的图像。
上图的注释文件如下所示:
总共有 3 个对象(2 个person
和一个tie
)。每条线代表这些对象中的一个。每条线的规格如下。
- 每个对象一行
- 每一行都是
class
x_center
y_center
width
height
格式。 - 框坐标必须根据图像的尺寸进行标准化(即值在 0 和 1 之间)
- 类别号是零索引的(从 0 开始)。
我们现在编写一个函数,它将采用 VOC 格式的注释,并将它们转换为一种格式,在这种格式中,关于边界框的信息存储在字典中。
# Function to get the data from XML Annotation
def extract_info_from_xml(xml_file):
root = ET.parse(xml_file).getroot()
# Initialise the info dict
info_dict = {}
info_dict['bboxes'] = []
# Parse the XML Tree
for elem in root:
# Get the file name
if elem.tag == "filename":
info_dict['filename'] = elem.text
# Get the image size
elif elem.tag == "size":
image_size = []
for subelem in elem:
image_size.append(int(subelem.text))
info_dict['image_size'] = tuple(image_size)
# Get details of the bounding box
elif elem.tag == "object":
bbox = {}
for subelem in elem:
if subelem.tag == "name":
bbox["class"] = subelem.text
elif subelem.tag == "bndbox":
for subsubelem in subelem:
bbox[subsubelem.tag] = int(subsubelem.text)
info_dict['bboxes'].append(bbox)
return info_dict
让我们在一个注释文件上试试这个函数。
print(extract_info_from_xml('annotations/road4.xml'))
这将输出:
{'bboxes': [{'class': 'trafficlight', 'xmin': 20, 'ymin': 109, 'xmax': 81, 'ymax': 237}, {'class': 'trafficlight', 'xmin': 116, 'ymin': 162, 'xmax': 163, 'ymax': 272}, {'class': 'trafficlight', 'xmin': 189, 'ymin': 189, 'xmax': 233, 'ymax': 295}], 'filename': 'road4.png', 'image_size': (267, 400, 3)}
我们现在编写一个函数,将包含在info_dict
中的信息转换成 YOLO v5 样式的注释,并将它们写入一个txt
文件。如果你的注释不同于 PASCAL VOC 注释,你可以写一个函数把它们转换成info_dict
格式,并使用下面的函数把它们转换成 YOLO v5 风格的注释。
# Dictionary that maps class names to IDs
class_name_to_id_mapping = {"trafficlight": 0,
"stop": 1,
"speedlimit": 2,
"crosswalk": 3}
# Convert the info dict to the required yolo format and write it to disk
def convert_to_yolov5(info_dict):
print_buffer = []
# For each bounding box
for b in info_dict["bboxes"]:
try:
class_id = class_name_to_id_mapping[b["class"]]
except KeyError:
print("Invalid Class. Must be one from ", class_name_to_id_mapping.keys())
# Transform the bbox co-ordinates as per the format required by YOLO v5
b_center_x = (b["xmin"] + b["xmax"]) / 2
b_center_y = (b["ymin"] + b["ymax"]) / 2
b_width = (b["xmax"] - b["xmin"])
b_height = (b["ymax"] - b["ymin"])
# Normalise the co-ordinates by the dimensions of the image
image_w, image_h, image_c = info_dict["image_size"]
b_center_x /= image_w
b_center_y /= image_h
b_width /= image_w
b_height /= image_h
#Write the bbox details to the file
print_buffer.append("{} {:.3f} {:.3f} {:.3f} {:.3f}".format(class_id, b_center_x, b_center_y, b_width, b_height))
# Name of the file which we have to save
save_file_name = os.path.join("annotations", info_dict["filename"].replace("png", "txt"))
# Save the annotation to disk
print("\n".join(print_buffer), file= open(save_file_name, "w"))
现在我们将所有的xml
注释转换成 YOLO 风格的txt
注释。
# Get the annotations
annotations = [os.path.join('annotations', x) for x in os.listdir('annotations') if x[-3:] == "xml"]
annotations.sort()
# Convert and save the annotations
for ann in tqdm(annotations):
info_dict = extract_info_from_xml(ann)
convert_to_yolov5(info_dict)
annotations = [os.path.join('annotations', x) for x in os.listdir('annotations') if x[-3:] == "txt"]
测试注释
只是为了检查一下,现在让我们测试一些转换后的注释。我们使用转换后的注释随机加载其中一个注释和绘图框,并直观地检查它,看看我们的代码是否按预期工作。
多次运行下一个单元格。每次都会随机抽取一个注释样本。
random.seed(0)
class_id_to_name_mapping = dict(zip(class_name_to_id_mapping.values(), class_name_to_id_mapping.keys()))
def plot_bounding_box(image, annotation_list):
annotations = np.array(annotation_list)
w, h = image.size
plotted_image = ImageDraw.Draw(image)
transformed_annotations = np.copy(annotations)
transformed_annotations[:,[1,3]] = annotations[:,[1,3]] * w
transformed_annotations[:,[2,4]] = annotations[:,[2,4]] * h
transformed_annotations[:,1] = transformed_annotations[:,1] - (transformed_annotations[:,3] / 2)
transformed_annotations[:,2] = transformed_annotations[:,2] - (transformed_annotations[:,4] / 2)
transformed_annotations[:,3] = transformed_annotations[:,1] + transformed_annotations[:,3]
transformed_annotations[:,4] = transformed_annotations[:,2] + transformed_annotations[:,4]
for ann in transformed_annotations:
obj_cls, x0, y0, x1, y1 = ann
plotted_image.rectangle(((x0,y0), (x1,y1)))
plotted_image.text((x0, y0 - 10), class_id_to_name_mapping[(int(obj_cls))])
plt.imshow(np.array(image))
plt.show()
# Get any random annotation file
annotation_file = random.choice(annotations)
with open(annotation_file, "r") as file:
annotation_list = file.read().split("\n")[:-1]
annotation_list = [x.split(" ") for x in annotation_list]
annotation_list = [[float(y) for y in x ] for x in annotation_list]
#Get the corresponding image file
image_file = annotation_file.replace("annotations", "images").replace("txt", "png")
assert os.path.exists(image_file)
#Load the image
image = Image.open(image_file)
#Plot the Bounding Box
plot_bounding_box(image, annotation_list)
OUTPUT
太好了,我们能够从 YOLO v5 格式中恢复正确的注释。这意味着我们已经正确地实现了转换函数。
对数据集进行分区
接下来,我们将数据集划分为分别包含 80%、10%和 10%数据的训练集、验证集和测试集。您可以根据自己的方便更改拆分值。
# Read images and annotations
images = [os.path.join('images', x) for x in os.listdir('images')]
annotations = [os.path.join('annotations', x) for x in os.listdir('annotations') if x[-3:] == "txt"]
images.sort()
annotations.sort()
# Split the dataset into train-valid-test splits
train_images, val_images, train_annotations, val_annotations = train_test_split(images, annotations, test_size = 0.2, random_state = 1)
val_images, test_images, val_annotations, test_annotations = train_test_split(val_images, val_annotations, test_size = 0.5, random_state = 1)
创建文件夹来保存拆分。
!mkdir images/train images/val images/test annotations/train annotations/val annotations/test
将文件移动到各自的文件夹中。
#Utility function to move images
def move_files_to_folder(list_of_files, destination_folder):
for f in list_of_files:
try:
shutil.move(f, destination_folder)
except:
print(f)
assert False
# Move the splits into their folders
move_files_to_folder(train_images, 'images/train')
move_files_to_folder(val_images, 'images/val/')
move_files_to_folder(test_images, 'images/test/')
move_files_to_folder(train_annotations, 'annotations/train/')
move_files_to_folder(val_annotations, 'annotations/val/')
move_files_to_folder(test_annotations, 'annotations/test/')
将annotations
文件夹重命名为labels
,因为这是 YOLO v5 期望注释所在的位置。
mv annotations labels
cd ../yolov5
培训选项
现在,我们训练网络。我们使用各种标志来设置关于训练的选项。
img
:图像尺寸。图像是正方形的。在保持长宽比的同时,调整原始图像的大小。图像的长边被调整到这个数字。短边用灰色填充。
An example of letter-boxed image
batch
:批量大小epochs
:训练的时期数data
:包含数据集信息(图像路径、标签)的数据 YAML 文件workers
:CPU 工作者的数量cfg
:模型架构。有四种选择:yolo5s.yaml
、yolov5m.yaml
、yolov5l.yaml
、yolov5x.yaml
。这些模型的大小和复杂性以升序增加,并且您可以选择适合您的对象检测任务的复杂性的模型。如果您想使用自定义架构,您必须在指定网络架构的models
文件夹中定义一个YAML
文件。weights
:您想要开始训练的预训练重量。如果你想从头开始训练,使用--weights ' '
name
:关于培训的各种事情,比如培训日志。训练权重将存储在名为runs/train/name
的文件夹中hyp
:描述超参数选择的 YAML 文件。关于如何定义超参数的示例,参见data/hyp.scratch.yaml
。如果未指定,则使用文件data/hyp.scratch.yaml
。
数据配置文件
数据配置YAML
文件定义了您想要训练模型的数据集的详细信息。必须在数据配置文件中定义以下参数:
train
、test
和val
:训练、测试和验证图像的位置。nc
:数据集中类的数量。names
:数据集中类的名称。这个列表中的类的索引将被用作代码中类名的标识符。
创建一个名为road_sign_data.yaml
的新文件,并将其放在yolov5/data
文件夹中。然后用以下内容填充它。
train: ../Road_Sign_Datasimg/train/
val: ../Road_Sign_Datasimg/val/
test: ../Road_Sign_Datasimg/test/
# number of classes
nc: 4
# class names
names: ["trafficlight","stop", "speedlimit","crosswalk"]
YOLO v5 期望在文件夹中找到图像的训练标签,该文件夹的名称可以通过将数据集图像路径中的images
替换为labels
来导出。例如,在上面的例子中,YOLO v5 将在../Road_Sign_Dataset/labels/train/
中寻找列车标签。
或者你可以简单地下载文件。
!wget -P data/ https://gist.githubusercontent.com/ayooshkathuria/bcf7e3c929cbad445439c506dba6198d/raw/f437350c0c17c4eaa1e8657a5cb836e65d8aa08a/road_sign_data.yaml
超参数配置文件
超参数配置文件帮助我们定义神经网络的超参数。我们将使用默认设置data/hyp.scratch.yaml
。这是它看起来的样子。
# Hyperparameters for COCO training from scratch
# python train.py --batch 40 --cfg yolov5m.yaml --weights '' --data coco.yaml --img 640 --epochs 300
# See tutorials for hyperparameter evolution https://github.com/ultralytics/yolov5#tutorials
lr0: 0.01 # initial learning rate (SGD=1E-2, Adam=1E-3)
lrf: 0.2 # final OneCycleLR learning rate (lr0 * lrf)
momentum: 0.937 # SGD momentum/Adam beta1
weight_decay: 0.0005 # optimizer weight decay 5e-4
warmup_epochs: 3.0 # warmup epochs (fractions ok)
warmup_momentum: 0.8 # warmup initial momentum
warmup_bias_lr: 0.1 # warmup initial bias lr
box: 0.05 # box loss gain
cls: 0.5 # cls loss gain
cls_pw: 1.0 # cls BCELoss positive_weight
obj: 1.0 # obj loss gain (scale with pixels)
obj_pw: 1.0 # obj BCELoss positive_weight
iou_t: 0.20 # IoU training threshold
anchor_t: 4.0 # anchor-multiple threshold
# anchors: 3 # anchors per output layer (0 to ignore)
fl_gamma: 0.0 # focal loss gamma (efficientDet default gamma=1.5)
hsv_h: 0.015 # image HSV-Hue augmentation (fraction)
hsv_s: 0.7 # image HSV-Saturation augmentation (fraction)
hsv_v: 0.4 # image HSV-Value augmentation (fraction)
degrees: 0.0 # image rotation (+/- deg)
translate: 0.1 # image translation (+/- fraction)
scale: 0.5 # image scale (+/- gain)
shear: 0.0 # image shear (+/- deg)
perspective: 0.0 # image perspective (+/- fraction), range 0-0.001
flipud: 0.0 # image flip up-down (probability)
fliplr: 0.5 # image flip left-right (probability)
mosaic: 1.0 # image mosaic (probability)
mixup: 0.0 # image mixup (probability)
您可以编辑这个文件,保存一个新文件,并将其指定为训练脚本的一个参数。
定制网络架构
YOLO v5 还允许你定义自己的定制架构和锚点,如果其中一个预定义的网络不适合你的话。为此,您必须定义一个自定义权重配置文件。对于这个例子,我们使用了yolov5s.yaml
。这是它看起来的样子。
# parameters
nc: 80 # number of classes
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
# anchors
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
# YOLOv5 backbone
backbone:
# [from, number, module, args]
[[-1, 1, Focus, [64, 3]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
[-1, 3, C3, [128]],
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 9, C3, [256]],
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, C3, [512]],
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 1, SPP, [1024, [5, 9, 13]]],
[-1, 3, C3, [1024, False]], # 9
]
# YOLOv5 head
head:
[[-1, 1, Conv, [512, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 6], 1, Concat, [1]], # cat backbone P4
[-1, 3, C3, [512, False]], # 13
[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 4], 1, Concat, [1]], # cat backbone P3
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
[-1, 1, Conv, [256, 3, 2]],
[[-1, 14], 1, Concat, [1]], # cat head P4
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
[-1, 1, Conv, [512, 3, 2]],
[[-1, 10], 1, Concat, [1]], # cat head P5
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
要使用自定义网络,创建一个新文件,并在运行时使用cfg
标志指定它。
训练模型
我们定义了train
、val
、test
的位置、类的数量(nc
)以及类的名称。由于数据集很小,并且每张图像没有很多对象,我们从最小的预训练模型yolo5s
开始,以保持简单并避免过度拟合。我们保持批量32
,图像尺寸640
,训练 100 个历元。如果您在将模型放入内存时遇到问题:
- 使用较小的批量
- 使用较小的网络
- 使用较小的图像尺寸
当然,以上所有情况都可能影响性能。折衷是你必须做出的设计决定。根据具体情况,您可能还想使用更大的 GPU 实例。
我们用yolo_road_det
这个名字进行训练。tensorboard 训练日志可在runs/train/yolo_road_det
找到。如果你不能访问 tensorboard 日志,你可以设置一个wandb
账户,这样日志就会被标绘在你的 wandb 账户上。
最后,运行培训:
!python train.py --img 640 --cfg yolov5s.yaml --hyp hyp.scratch.yaml --batch 32 --epochs 100 --data road_sign_data.yaml --weights yolov5s.pt --workers 24 --name yolo_road_det
这可能需要 30 分钟的训练时间,具体取决于您的硬件。
推理
使用detect.py
文件有很多方式来运行推理。
source
标志定义了我们的探测器的来源,可以是:
- 单一图像
- 一个图像文件夹
- 录像
- 网络摄像头
...和各种其他格式。我们想在我们的测试图像上运行它,所以我们将source
标志设置为../Road_Sign_Datasimg/test/
。
weights
标志定义了我们想要运行检测器的模型的路径。conf
flag 是阈值客观置信度。name
标志定义了检测的存储位置。我们将这个标志设置为yolo_road_det
;因此,检测结果将存储在runs/detect/yolo_road_det/
中。
决定了所有选项后,让我们对测试数据集进行推理。
!python detect.py --source ../Road_Sign_Datasimg/test/ --weights runs/train/yolo_road_det/weights/best.pt --conf 0.25 --name yolo_road_det
best.pt
包含训练期间保存的最佳性能砝码。
我们现在可以随机绘制其中一个检测。
detections_dir = "runs/detect/yolo_road_det/"
detection_images = [os.path.join(detections_dir, x) for x in os.listdir(detections_dir)]
random_detection_image = Image.open(random.choice(detection_images))
plt.imshow(np.array(random_detection_image))
OUTPUT
除了一个文件夹的图像,还有其他来源,我们可以使用我们的探测器。执行此操作的命令语法如下所述。
python detect.py --source 0 # webcam
file.jpg # image
file.mp4 # video
path/ # directory
path/*.jpg # glob
rtsp://170.93.143.139/rtplive/470011e600ef003a004ee33696235daa # rtsp stream
rtmp://192.168.1.105/live/test # rtmp stream
http://112.50.243.8/PLTV/88888888/224/3221225900/1.m3u8 # http stream
在测试数据集上计算地图
我们可以使用test
文件在我们的测试集上计算 mAP。为了在我们的测试集上执行评估,我们将task
标志设置为test
。我们把名字设为yolo_det
。各种曲线(F1、AP、精确曲线等)的图表可以在runs/test/yolo_road_det
文件夹中找到。该脚本为我们计算每个类的平均精度,以及平均精度。
!python test.py --weights runs/train/yolo_road_det/weights/best.pt --data road_sign_data.yaml --task test --name yolo_det
的输出如下所示:
Fusing layers...
Model Summary: 224 layers, 7062001 parameters, 0 gradients, 16.4 GFLOPS
test: Scanning '../Road_Sign_Dataset/labels/test' for images and labels... 88 fo
test: New cache created: ../Road_Sign_Dataset/labels/test.cache
test: Scanning '../Road_Sign_Dataset/labels/test.cache' for images and labels...
Class Images Targets P R mAP@.5
all 88 126 0.961 0.932 0.944 0.8
trafficlight 88 20 0.969 0.75 0.799 0.543
stop 88 7 1 0.98 0.995 0.909
speedlimit 88 76 0.989 1 0.997 0.906
crosswalk 88 23 0.885 1 0.983 0.842
Speed: 1.4/0.7/2.0 ms inference/NMS/total per 640x640 image at batch-size 32
Results saved to runs/test/yolo_det2
这就是本教程的基本内容。在本教程中,我们在自定义路标数据集上训练 YOLO v5。如果你想使用超参数,或者如果你想在不同的数据集上训练,你可以从本教程的梯度笔记本开始。
结论...以及一些关于命名的传奇故事。
如前所述,我想以关于 YOLO v5 引起的命名争议的两点意见来结束我的文章。
由于担心他的研究被用于军事目的,YOLO 的最初开发者放弃了它的开发。在那之后,多组人提出了对 YOLO 的改进。
之后,YOLO v4 于 2020 年 4 月由阿列克谢·博奇科夫斯基(Alexey Bochkovskiy)等人发行。阿列克谢可能是制作《YOLO》续集的最合适人选,因为他长期以来一直是第二个最受欢迎的《YOLO 回购》的维护者,与最初的版本不同,该版本也适用于 Windows。
YOLO v4 带来了大量的改进,这有助于它大大超过 YOLO v3。但是后来,Ultralytics YOLO v3 repo(YOLO 最流行的 python 端口)的维护者 Glenn Jocher 发布了 YOLO v5,它的命名引起了计算机视觉社区许多成员的保留。
为什么?因为在传统意义上,YOLO v5 没有带来任何新颖的架构/损失/技术。YOLO v5 的研究论文还没有发布。
然而,它在人们将 YOLO 集成到现有管道的速度方面提供了巨大的改进。YOLO v5 最重要的一点是,它是用 PyTorch / Python 编写的,不像 v1-v4 的原始版本是用 c 语言编写的。仅此一点就使它更易于在深度学习领域工作的人和公司使用。
此外,它介绍了一种使用模块化配置文件、混合精度训练、快速推理、更好的数据扩充技术等定义实验的干净方法。在某种程度上,如果我们把 YOLO v5 看作是一个软件,而不是一个算法,称它为 v5 是不错的。也许这就是 Glenn Jocher 在给它命名为 v5 时的想法。然而,来自社区的许多人,包括 Alexey,都强烈反对,并指出称它为 YOLO v5 是错误的,因为性能方面,它仍然不如 YOLO v4。
这里有一个帖子给你一个关于这场争论的更详细的描述。
你对此有什么看法?你觉得 YOLO v5 应该这么叫吗?请在评论中告诉我们,或者发微博到@ hellopaperspace。
在自定义数据集上训练 YOLOv7 的分步说明
按照本指南获取在自定义数据集的渐变笔记本中运行 YOLOv7 模型训练的分步说明。本教程基于我们流行的使用梯度运行 YOLOv5 自定义训练的指南,并对 YOLOv7 进行了更新。
我们将首先设置 Python 代码在笔记本中运行。接下来,我们将下载定制数据集,并将注释转换为 Yolov7 格式。提供了帮助器函数,以便于测试注释是否与图像匹配。然后,我们将数据集划分为训练集和验证集。对于训练,我们将建立几个训练选项,并设置我们的数据和超参数配置文件。然后,我们将介绍如何根据自定义数据的需要修改网络架构。一旦设置完成,我们将展示如何为 100 个时期训练模型,然后使用它在我们的验证集上生成预测。然后,我们将使用结果来计算测试数据的映射,以便我们可以评估最终模型的质量。
设置代码
我们首先克隆 YOLOv7 存储库,并设置运行 YOLOv7 所需的依赖项。如果您正在使用上面的梯度运行链接,这一步将为您完成。您还可以使用笔记本创建页面中的高级选项开关将 YOLOv7 repo 设置为您的工作区目录。
接下来,我们将安装所需的软件包。在终端中(或使用 cell magic),键入:
pip install -r requirements.txt
注意:如果你在一个渐变笔记本中工作,上面的步骤是不需要的-这些包已经被安装了。
安装完依赖项后,我们将导入所需的模块,以结束笔记本的设置。
import torch
from IPython.display import Image # for displaying images
import os
import random
import shutil
from sklearn.model_selection import train_test_split
import xml.etree.ElementTree as ET
from xml.dom import minidom
from tqdm import tqdm
from PIL import Image, ImageDraw
import numpy as np
import matplotlib.pyplot as plt
random.seed(108)
下载数据
对于本教程,我们将使用来自 MakeML 的路标对象检测数据集。只需几个步骤,我们就可以从 Kaggle 获取这些数据。
为此,首先我们将创建一个名为Road_Sign_Dataset
的目录来保存我们的数据集。该目录需要与我们刚刚克隆的YOLOv7
存储库文件夹在同一个文件夹中/这是我们在笔记本中的工作区目录。
mkdir Road_Sign_Dataset
cd Road_Sign_Dataset
要从 Kaggle 下载用于渐变笔记本的数据集,首先要获得一个 Kaggle 帐户。接下来,通过转到您的帐户设置创建一个 API 令牌,并将 kaggle.json 保存到您的本地机器。将 kaggle.json 上传到渐变笔记本。最后,运行下面的代码单元格下载 zip 文件。
pip install kaggle
mkdir ~/.kaggle
mv kaggle.json ~/.kaggle
kaggle datasets download andrewmvd/road-sign-detection
解压缩数据集。
unzip road-sign-detection.zip
删除不需要的文件。
rm -r road-sign-detection.zip
探索数据集
我们在这个例子中使用的数据集相对较小,总共只包含 877 幅图像。推荐的通常值是每类 3000 张图像以上。我们可以对更大的数据集(如 LISA 数据集)应用用于该数据集的所有相同技术,以充分实现 YOLO 的功能,但我们将在本教程中使用一个小数据集来促进快速原型制作。典型的训练需要不到半个小时,这将允许您快速迭代涉及不同超参数的实验。
数据集包含属于 4 类的路标:
- 红绿灯
- 停车标志
- 限速标志
- 人行横道标志
Road Sign Dataset
将注释转换为 YOLO 版本 5 的格式
现在我们有了数据集,我们需要将注释转换成 YOLOv7 期望的格式。YOLOv7 希望数据以特定的方式组织,否则它无法解析目录。重新排序我们的数据将确保我们没有问题开始训练。
路标检测数据集的注释都遵循 PASCAL VOC XML 格式。这是一种非常流行的格式,因为在一个表结构中存储多种类型的数据非常简单。因为这是一种流行的格式,所以有许多简单易用的在线转换工具来重新格式化 XML 数据。尽管如此,我们还是提供了代码来展示如何转换其他流行的格式(您可能找不到流行的工具)。
PASCAL VOC 格式将其注释存储在 XML 文件中,其中各种属性由标签描述。让我们看一个示例注释文件。
# Assuming you're in the data folder
cat annotations/road4.xml
输出如下所示。
<annotation>
<folder>images</folder>
<filename>road4.png</filename>
<size>
<width>267</width>
<height>400</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>trafficlight</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<occluded>0</occluded>
<difficult>0</difficult>
<bndbox>
<xmin>20</xmin>
<ymin>109</ymin>
<xmax>81</xmax>
<ymax>237</ymax>
</bndbox>
</object>
<object>
<name>trafficlight</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<occluded>0</occluded>
<difficult>0</difficult>
<bndbox>
<xmin>116</xmin>
<ymin>162</ymin>
<xmax>163</xmax>
<ymax>272</ymax>
</bndbox>
</object>
<object>
<name>trafficlight</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<occluded>0</occluded>
<difficult>0</difficult>
<bndbox>
<xmin>189</xmin>
<ymin>189</ymin>
<xmax>233</xmax>
<ymax>295</ymax>
</bndbox>
</object>
</annotation>
上面的注释文件描述了一个名为road4.jpg
的文件。我们可以看到它的尺寸为267 x 400 x 3
。它还有 3 个object
标签,每个标签代表 3 个相应的边界框。该类由name
标签指定,边界框坐标的定位由bndbox
标签表示。边界框由其左上角(x_min
、y_min
)和右下角(xmax
、ymax
)的坐标来描述。
YOLOv7 注释格式
让我们更仔细地看看注释文件。YOLOv7 希望每个图像都有一个.txt
文件形式的注释,其中文本文件的每一行都描述了一个边界框。考虑下面的图像。
图像的注释文件包含上面显示的每个边界框的坐标。这些标签的格式如下所示:
我们可以看到,总共有 3 个对象(2 个person
和一个tie
)。每条线代表这些对象中的一个。每条线的规格如下。
- 每个对象一行
- 栏目格式为
class
、x_center
、y_center
、width
、height
。 - 这些框坐标必须标准化为图像的尺寸(即具有 0 到 1 之间的值)
- 类别号是零索引的(从 0 开始)。
接下来,我们将编写一个函数,它接受 VOC 格式的注释,并将它们转换为一种格式,在这种格式下,关于边界框的信息存储在字典中。
# Function to get the data from XML Annotation
def extract_info_from_xml(xml_file):
root = ET.parse(xml_file).getroot()
# Initialise the info dict
info_dict = {}
info_dict['bboxes'] = []
# Parse the XML Tree
for elem in root:
# Get the file name
if elem.tag == "filename":
info_dict['filename'] = elem.text
# Get the image size
elif elem.tag == "size":
image_size = []
for subelem in elem:
image_size.append(int(subelem.text))
info_dict['image_size'] = tuple(image_size)
# Get details of the bounding box
elif elem.tag == "object":
bbox = {}
for subelem in elem:
if subelem.tag == "name":
bbox["class"] = subelem.text
elif subelem.tag == "bndbox":
for subsubelem in subelem:
bbox[subsubelem.tag] = int(subsubelem.text)
info_dict['bboxes'].append(bbox)
return info_dict
让我们在一个示例注释文件上尝试这个函数。
print(extract_info_from_xml('annotations/road4.xml'))
这将输出:
{'bboxes': [{'class': 'trafficlight', 'xmin': 20, 'ymin': 109, 'xmax': 81, 'ymax': 237}, {'class': 'trafficlight', 'xmin': 116, 'ymin': 162, 'xmax': 163, 'ymax': 272}, {'class': 'trafficlight', 'xmin': 189, 'ymin': 189, 'xmax': 233, 'ymax': 295}], 'filename': 'road4.png', 'image_size': (267, 400, 3)}
这有效地提取了使用 YOLOv7 所需的信息。
接下来,我们将创建一个函数来将这些信息(现在包含在info_dict
中)转换成 YOLOv7 样式的注释,并将它们写入txt
文件。如果我们的注释不同于 PASCAL VOC 注释,我们可以编写一个新的函数将它们转换成info_dict
格式,并使用下面相同的函数将它们转换成 YOLOv7 样式的注释。只要保存了info-dict
格式,这段代码应该可以工作。
# Dictionary that maps class names to IDs
class_name_to_id_mapping = {"trafficlight": 0,
"stop": 1,
"speedlimit": 2,
"crosswalk": 3}
# Convert the info dict to the required yolo format and write it to disk
def convert_to_yolov5(info_dict):
print_buffer = []
# For each bounding box
for b in info_dict["bboxes"]:
try:
class_id = class_name_to_id_mapping[b["class"]]
except KeyError:
print("Invalid Class. Must be one from ", class_name_to_id_mapping.keys())
# Transform the bbox co-ordinates as per the format required by YOLO v5
b_center_x = (b["xmin"] + b["xmax"]) / 2
b_center_y = (b["ymin"] + b["ymax"]) / 2
b_width = (b["xmax"] - b["xmin"])
b_height = (b["ymax"] - b["ymin"])
# Normalise the co-ordinates by the dimensions of the image
image_w, image_h, image_c = info_dict["image_size"]
b_center_x /= image_w
b_center_y /= image_h
b_width /= image_w
b_height /= image_h
#Write the bbox details to the file
print_buffer.append("{} {:.3f} {:.3f} {:.3f} {:.3f}".format(class_id, b_center_x, b_center_y, b_width, b_height))
# Name of the file which we have to save
save_file_name = os.path.join("annotations", info_dict["filename"].replace("png", "txt"))
# Save the annotation to disk
print("\n".join(print_buffer), file= open(save_file_name, "w"))
接下来,我们对每个xml
文件使用这个函数,将所有的xml
注释转换成 YOLO 风格的txt
注释。
# Get the annotations
annotations = [os.path.join('annotations', x) for x in os.listdir('annotations') if x[-3:] == "xml"]
annotations.sort()
# Convert and save the annotations
for ann in tqdm(annotations):
info_dict = extract_info_from_xml(ann)
convert_to_yolov5(info_dict)
annotations = [os.path.join('annotations', x) for x in os.listdir('annotations') if x[-3:] == "txt"]
测试注释
现在让我们测试一些转换后的注释,确保它们的格式正确。下面的代码将随机加载一个注释,并使用它来绘制带有转换后的注释的框。然后,我们可以直观地检查它,看看我们的代码是否按预期工作。
我们的建议是多次运行下一个单元。每次都会随机抽取一个注释,我们可以看到数据被正确排序。
random.seed(0)
class_id_to_name_mapping = dict(zip(class_name_to_id_mapping.values(), class_name_to_id_mapping.keys()))
def plot_bounding_box(image, annotation_list):
annotations = np.array(annotation_list)
w, h = image.size
plotted_image = ImageDraw.Draw(image)
transformed_annotations = np.copy(annotations)
transformed_annotations[:,[1,3]] = annotations[:,[1,3]] * w
transformed_annotations[:,[2,4]] = annotations[:,[2,4]] * h
transformed_annotations[:,1] = transformed_annotations[:,1] - (transformed_annotations[:,3] / 2)
transformed_annotations[:,2] = transformed_annotations[:,2] - (transformed_annotations[:,4] / 2)
transformed_annotations[:,3] = transformed_annotations[:,1] + transformed_annotations[:,3]
transformed_annotations[:,4] = transformed_annotations[:,2] + transformed_annotations[:,4]
for ann in transformed_annotations:
obj_cls, x0, y0, x1, y1 = ann
plotted_image.rectangle(((x0,y0), (x1,y1)))
plotted_image.text((x0, y0 - 10), class_id_to_name_mapping[(int(obj_cls))])
plt.imshow(np.array(image))
plt.show()
# Get any random annotation file
annotation_file = random.choice(annotations)
with open(annotation_file, "r") as file:
annotation_list = file.read().split("\n")[:-1]
annotation_list = [x.split(" ") for x in annotation_list]
annotation_list = [[float(y) for y in x ] for x in annotation_list]
#Get the corresponding image file
image_file = annotation_file.replace("annotations", "images").replace("txt", "png")
assert os.path.exists(image_file)
#Load the image
image = Image.open(image_file)
#Plot the Bounding Box
plot_bounding_box(image, annotation_list)
Labeled sample output from Road Sign Dataset
太好了,我们能够从 YOLOv7 格式中恢复正确的注释。这意味着我们已经正确地实现了转换函数。
对数据集进行分区
接下来,我们需要将数据集划分为训练集、验证集和测试集。这些将分别包含 80%、10%和 10%的数据。可以根据需要编辑这些值来更改分割百分比。
# Read images and annotations
images = [os.path.join('images', x) for x in os.listdir('images')]
annotations = [os.path.join('annotations', x) for x in os.listdir('annotations') if x[-3:] == "txt"]
images.sort()
annotations.sort()
# Split the dataset into train-valid-test splits
train_images, val_images, train_annotations, val_annotations = train_test_split(images, annotations, test_size = 0.2, random_state = 1)
val_images, test_images, val_annotations, test_annotations = train_test_split(val_images, val_annotations, test_size = 0.5, random_state = 1)
然后,我们将创建文件夹来保存新拆分的数据。
!mkdir images/train images/val images/test labels/train labels/val labels/test
最后,我们通过将文件移动到各自的文件夹来完成数据的设置。
#Utility function to move images
def move_files_to_folder(list_of_files, destination_folder):
for f in list_of_files:
try:
shutil.move(f, destination_folder)
except:
print(f)
assert False
# Move the splits into their folders
move_files_to_folder(train_images, 'images/train')
move_files_to_folder(val_images, 'images/val/')
move_files_to_folder(test_images, 'images/test/')
move_files_to_folder(train_annotations, 'annotations/train/')
move_files_to_folder(val_annotations, 'annotations/val/')
move_files_to_folder(test_annotations, 'annotations/test/')
!mv annotations labels
%cd ../
培训选项
现在,是时候设置我们训练网络的选项了。为此,我们将使用各种标志,包括:
img-size
:该参数对应图像的像素大小。对于 YOLOv7,图像必须是正方形。为了做到这一点,原始图像在保持纵横比的同时被调整大小。图像的长边被调整到这个数字。短边用灰色填充。
An example of letter-boxed image
batch
:批量大小对应于在训练期间的任何给定时间通过网络传播的图像-字幕对的数量epochs
:训练的历元数。该值对应于模型训练将在整个数据集内通过的总次数- 数据 YAML 文件包含关于数据集的信息,以帮助指导 YOLO。里面的信息包括图像的路径、类别标签的数量和类别标签的名称
workers
:表示分配给培训的 CPU 工作人员的数量cfg
:模型架构的配置文件。可能的选择有 yolov7-e6e.yaml 、 yolov7-d6.yaml 、 yolov7-e6.yaml 、yolov 7-w6 . YAML、 yolov7x.yaml 、 yolov7.yaml 和 yolov7-tiny。这些模型的大小和复杂度以降序增加,并且我们可以使用这些来最好地选择适合我们的对象检测任务的复杂度的模型。在我们想要使用定制架构的情况下,我们必须在指定网络架构的cfg
文件夹中定义一个YAML
文件weights
:我们想要重新开始训练的预训练模型重量。如果我们想从头开始训练,我们可以使用--weights ' '
name
:培训日志等各种培训输出的路径。输出的训练权重、日志和批量图像样本存储在与runs/train/<name>
对应的文件夹中。hyp
:描述模型训练的超参数选择的 YAML 文件。关于如何定义自定义超参数的示例,参见data/hyp.scratch.yaml
。如果未指定,则使用data/hyp.scratch.yaml
中的超参数。
数据配置文件
数据配置YAML
文件定义了您想要训练模型的数据集的详细信息。必须在数据配置文件中定义以下参数:
train
、test
和val
:训练、测试和验证图像的路径nc
:数据集中类的数量names
:数据集中类标签的名称。这个列表中的类的索引可以用作代码中类名的标识符。
我们可以创建一个名为road_sign_data.yaml
的新文件,并将其放在data
文件夹中。然后用以下内容填充它。
train: Road_Sign_Datasimg/train/
val: Road_Sign_Datasimg/val/
test: Road_Sign_Datasimg/test/
# number of classes
nc: 4
# class names
names: ["trafficlight","stop", "speedlimit","crosswalk"]
YOLOv7 期望为文件夹中的图像找到相应的训练标签,该文件夹的名称是通过将数据集图像路径中的images
替换为labels
而得到的。例如,在上面的例子中,YOLO v5 将在Road_Sign_Dataset/labels/train/
中寻找列车标签。
或者,我们可以跳过这一步,只需将文件下载到我们工作目录的data
文件夹中。
!wget -P data/ https://gist.githubusercontent.com/ayooshkathuria/bcf7e3c929cbad445439c506dba6198d/raw/f437350c0c17c4eaa1e8657a5cb836e65d8aa08a/road_sign_data.yaml
超参数配置文件
超参数配置文件帮助我们定义神经网络的超参数。这使我们对模型训练的行为有了更多的控制,如果训练没有按预期进行,更高级的用户应该考虑修改某些值,如学习率。它也许能改善最终的结果。
我们将使用默认的配置规范data/hyp.scratch.yaml
。看起来是这样的:
# Hyperparameters for COCO training from scratch
# python train.py --batch 40 --cfg yolov5m.yaml --weights '' --data coco.yaml --img 640 --epochs 300
# See tutorials for hyperparameter evolution https://github.com/ultralytics/yolov5#tutorials
lr0: 0.01 # initial learning rate (SGD=1E-2, Adam=1E-3)
lrf: 0.2 # final OneCycleLR learning rate (lr0 * lrf)
momentum: 0.937 # SGD momentum/Adam beta1
weight_decay: 0.0005 # optimizer weight decay 5e-4
warmup_epochs: 3.0 # warmup epochs (fractions ok)
warmup_momentum: 0.8 # warmup initial momentum
warmup_bias_lr: 0.1 # warmup initial bias lr
box: 0.05 # box loss gain
cls: 0.5 # cls loss gain
cls_pw: 1.0 # cls BCELoss positive_weight
obj: 1.0 # obj loss gain (scale with pixels)
obj_pw: 1.0 # obj BCELoss positive_weight
iou_t: 0.20 # IoU training threshold
anchor_t: 4.0 # anchor-multiple threshold
# anchors: 3 # anchors per output layer (0 to ignore)
fl_gamma: 0.0 # focal loss gamma (efficientDet default gamma=1.5)
hsv_h: 0.015 # image HSV-Hue augmentation (fraction)
hsv_s: 0.7 # image HSV-Saturation augmentation (fraction)
hsv_v: 0.4 # image HSV-Value augmentation (fraction)
degrees: 0.0 # image rotation (+/- deg)
translate: 0.1 # image translation (+/- fraction)
scale: 0.5 # image scale (+/- gain)
shear: 0.0 # image shear (+/- deg)
perspective: 0.0 # image perspective (+/- fraction), range 0-0.001
flipud: 0.0 # image flip up-down (probability)
fliplr: 0.5 # image flip left-right (probability)
mosaic: 1.0 # image mosaic (probability)
mixup: 0.0 # image mixup (probability)
您可以根据需要编辑这个文件,保存它,并使用hyp
选项将其指定为训练脚本的一个参数。
定制网络架构
如果某个预先定义的网络不适合我们的任务,YOLOv7 还允许我们定义自己的自定义体系结构和锚。为了利用这一点,我们将不得不定义一个自定义权重配置文件,或者只使用他们的预制规格之一。对于这个演示,我们不需要做任何修改,只需要使用yolov7.yaml
。看起来是这样的:
# parameters
nc: 80 # number of classes
depth_multiple: 1.0 # model depth multiple
width_multiple: 1.0 # layer channel multiple
# anchors
anchors:
- [12,16, 19,36, 40,28] # P3/8
- [36,75, 76,55, 72,146] # P4/16
- [142,110, 192,243, 459,401] # P5/32
# yolov7 backbone
backbone:
# [from, number, module, args]
[[-1, 1, Conv, [32, 3, 1]], # 0
[-1, 1, Conv, [64, 3, 2]], # 1-P1/2
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [128, 3, 2]], # 3-P2/4
[-1, 1, Conv, [64, 1, 1]],
[-2, 1, Conv, [64, 1, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[[-1, -3, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1]], # 11
[-1, 1, MP, []],
[-1, 1, Conv, [128, 1, 1]],
[-3, 1, Conv, [128, 1, 1]],
[-1, 1, Conv, [128, 3, 2]],
[[-1, -3], 1, Concat, [1]], # 16-P3/8
[-1, 1, Conv, [128, 1, 1]],
[-2, 1, Conv, [128, 1, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[[-1, -3, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [512, 1, 1]], # 24
[-1, 1, MP, []],
[-1, 1, Conv, [256, 1, 1]],
[-3, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [256, 3, 2]],
[[-1, -3], 1, Concat, [1]], # 29-P4/16
[-1, 1, Conv, [256, 1, 1]],
[-2, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[[-1, -3, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [1024, 1, 1]], # 37
[-1, 1, MP, []],
[-1, 1, Conv, [512, 1, 1]],
[-3, 1, Conv, [512, 1, 1]],
[-1, 1, Conv, [512, 3, 2]],
[[-1, -3], 1, Concat, [1]], # 42-P5/32
[-1, 1, Conv, [256, 1, 1]],
[-2, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[[-1, -3, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [1024, 1, 1]], # 50
]
# yolov7 head
head:
[[-1, 1, SPPCSPC, [512]], # 51
[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[37, 1, Conv, [256, 1, 1]], # route backbone P4
[[-1, -2], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1]],
[-2, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1]], # 63
[-1, 1, Conv, [128, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[24, 1, Conv, [128, 1, 1]], # route backbone P3
[[-1, -2], 1, Concat, [1]],
[-1, 1, Conv, [128, 1, 1]],
[-2, 1, Conv, [128, 1, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],
[[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [128, 1, 1]], # 75
[-1, 1, MP, []],
[-1, 1, Conv, [128, 1, 1]],
[-3, 1, Conv, [128, 1, 1]],
[-1, 1, Conv, [128, 3, 2]],
[[-1, -3, 63], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1]],
[-2, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[-1, 1, Conv, [128, 3, 1]],
[[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1]], # 88
[-1, 1, MP, []],
[-1, 1, Conv, [256, 1, 1]],
[-3, 1, Conv, [256, 1, 1]],
[-1, 1, Conv, [256, 3, 2]],
[[-1, -3, 51], 1, Concat, [1]],
[-1, 1, Conv, [512, 1, 1]],
[-2, 1, Conv, [512, 1, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[-1, 1, Conv, [256, 3, 1]],
[[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [512, 1, 1]], # 101
[75, 1, RepConv, [256, 3, 1]],
[88, 1, RepConv, [512, 3, 1]],
[101, 1, RepConv, [1024, 3, 1]],
[[102,103,104], 1, IDetect, [nc, anchors]], # Detect(P3, P4, P5)
]
如果我们想使用一个定制的网络,那么我们可以创建一个新文件(像yolov7_training.yaml
),并在我们训练时使用cfg
标志指定它。
训练模型
我们现在已经使用我们的数据文件定义了train
、val
和test
的位置、类的数量(nc
)和类的名称,所以我们准备开始模型训练。由于数据集相对较小,并且每个图像没有很多对象,我们可以从可用的预训练模型的基本版本yolosv7.pt
开始,以保持简单并避免过度拟合。我们将使用8
的批量大小,因此这将在 Gradient 的免费 GPU 笔记本上运行,图像大小为640
,我们将训练 100 个纪元。
如果您仍然无法将模型放入内存:
- 使用较小的批处理大小:如果 8 不能工作,尝试 2 或 4 的批处理大小,但这不应该发生在免费的 GPU 上
- 使用较小的网络:
yolov7-tiny.pt
检查点的运行成本将低于基本的yolov7.pt
- 使用较小的图像尺寸:图像的尺寸直接对应于培训期间的费用。将图像从 640 减少到 320,以损失预测准确性为代价显著降低成本
更改上述任何一项都可能会影响性能。这种折衷是一种设计决策,我们必须根据可用的计算能力做出决定。根据具体情况,我们可能还想使用更大的 GPU 实例。例如,像 A100-80G 这样的更强大的 GPU 将大大减少训练时间,但对于这个更小的数据集训练任务来说,这将是非常过分的。点击这里查看我们对 YOLOv7 基准测试的分析。
我们将使用名称yolo_road_det
进行培训。请记住,如果我们要再次运行训练而不改变它,输出将重定向到yolo_road_det1
,并在迭代时不断添加到追加的最后一个值。登船训练日志可以在runs/train/yolo_road_det/results.txt
找到。您还可以利用 YOLOv7 模型与权重&偏差的集成,并连接一个wandb
帐户,以便日志在您的帐户上绘制。
最后,我们已经完成了设置,并准备训练我们的模型。通过执行下面的代码单元格来运行培训:
!python train.py --img-size 640 --cfg cfg/training/yolov7.yaml --hyp data/hyp.scratch.custom.yaml --batch 8 --epochs 100 --data data/road_sign.yaml --weights yolov7_training.pt --workers 24 --name yolo_road_det
注意:在免费的 GPU (M4000)上进行训练可能需要 5 个小时。如果时间是一个因素,考虑升级到我们的一个更强大的机器。
推理
有许多方法可以使用detect.py
文件运行推断,并为测试集图像中的类生成预测标签。
当运行detect.py
代码时,我们再次需要设置一些选项。
source
标志定义了我们的检测器的来源,它通常是以下之一:单个图像、一个图像文件夹、一个视频、或者来自某个视频流或网络摄像头的实时反馈。我们想在我们的测试图像上运行它,所以我们将source
标志设置为Road_Sign_Datasimg/test/
。weights
选项定义了我们想要运行探测器的模型的路径。我们将使用上一次训练运行中的best.pt
模型,就验证集上的 mAP 而言,它是定量的“最佳”执行模型。conf
标志是阈值目标置信度。name
标志定义了检测的存储位置。我们将这个标志设置为yolo_road_det
;因此,检测结果将存储在runs/detect/yolo_road_det/
中。
决定了所有选项后,让我们用测试数据集进行推断,并定性地看看我们的模型训练是如何完成的。运行下面单元格中的代码:
!python detect.py --source Road_Sign_Datasimg/test/ --weights runs/train/yolo_road_det5/weights/best.pt --conf 0.25 --name yolo_road_det
一旦完成,我们现在可以随机绘制其中一个检测,并查看我们的训练效果。
detections_dir = "runs/detect/yolo_road_det/"
detection_images = [os.path.join(detections_dir, x) for x in os.listdir(detections_dir)]
random_detection_image = Image.open(random.choice(detection_images))
plt.imshow(np.array(random_detection_image))
OUTPUT
除了一个文件夹的图像,还有其他来源,我们可以使用我们的探测器。这些包括 http、rtsp、rtmp 视频流,它们可以为将这种模型部署到生产环境中增加许多实用工具。执行此操作的命令语法如下所述。
python detect.py --source 0 # webcam
file.jpg # image
file.mp4 # video
path/ # directory
path/*.jpg # glob
rtsp://170.93.143.139/rtplive/470011e600ef003a004ee33696235daa # rtsp stream
rtmp://192.168.1.105/live/test # rtmp stream
http://112.50.243.8/PLTV/88888888/224/3221225900/1.m3u8 # http stream
计算测试数据集的平均精度(mAP)
我们可以使用test.py
脚本来计算测试集上的模型预测图。该脚本为我们计算每个类的平均精度,以及平均精度(mAP)。为了对我们的测试集进行评估,我们需要将task
标志设置为test
。然后,我们将该名称设置为yolo_det
。test.py
的各种输出,如各种曲线(F1,AP,Precision curves 等)的图,可以在输出文件夹:runs/test/yolo_road_det
中找到。
!python test.py --weights runs/train/yolo_road_det/weights/best.pt --data road_sign_data.yaml --task test --name yolo_det
其输出如下所示:
Fusing layers...
Model Summary: 224 layers, 7062001 parameters, 0 gradients, 16.4 GFLOPS
test: Scanning 'Road_Sign_Dataset/labels/test' for images and labels...
test: New cache created: Road_Sign_Dataset/labels/test.cache
test: Scanning 'Road_Sign_Dataset/labels/test.cache' for images and labels...
Class Images Targets P R mAP@.5
all 88 126 0.961 0.932 0.944 0.8
trafficlight 88 20 0.969 0.75 0.799 0.543
stop 88 7 1 0.98 0.995 0.909
speedlimit 88 76 0.989 1 0.997 0.906
crosswalk 88 23 0.885 1 0.983 0.842
Speed: 1.4/0.7/2.0 ms inference/NMS/total per 640x640 image at batch-size 32
Results saved to runs/test/yolo_det
结束语
至此,我们完成了教程。本教程的读者现在应该能够在任何自定义数据集上训练 YOLOv7,就像路标使用的例子一样。虽然不是所有的数据集都将使用这种xml
格式,但该系统足够健壮,可以应用于任何对象检测数据集,以便与 YOLOv7 一起运行。
请务必在渐变笔记本中尝试该演示,并尝试将 GPU 扩展到支持更高 VRAM 的机器,以了解这如何影响训练时间。
感谢 Ayoosh Kathuria 在他的原创 YOLOv5 教程上的出色工作,我们在这里对 YOLOv7 进行了改编。请务必查看他在博客上的其他文章。
这个项目的 github repo 可以在这里找到。
训练 LSTM 网络并对 ml5.js 中的结果模型进行采样
原文:https://blog.paperspace.com/training-an-lstm-and-using-the-model-in-ml5-js/
更新于 2022 年 5 月 9 日:这篇博客文章引用了现已废弃的 Paperspace 作业。如果您对 Paperspace 上的类似功能感兴趣,请查看我们文档中的渐变工作流。
LSTMs 的简单介绍
有各种类型的神经网络结构。根据您的任务、手头的数据和想要生成的输出,您可以选择或创建不同的网络架构和设计模式。如果你的数据集包含图像或像素,那么一个卷积神经网络可能就是你所需要的。如果你试图在一系列输入上训练一个网络,那么一个循环神经网络 (RNN)可能会起作用。RNNs 是一种人工神经网络,当你的目标是识别数据序列中的模式时,它可以取得非常好的结果。当处理文本数据时,任何给定前一个字符计算下一个字符概率的模型都被称为语言模型[1]。
例如,如果您的输入是一个文本集或音乐作品,并且您试图从中预测有意义的序列,那么 rnn 非常有用。长短期记忆网络(LSTMs)只是一种特殊类型的 RNN,在学习“长期依赖”时表现更好。
例如,如果您有一个大型文本数据集,您可以训练一个 LSTM 模型,它将能够学习文本数据的统计结构。然后,您可以从该模型中进行采样,并创建看起来像原始训练数据的有意义的字符序列。换句话说,如果你想预测下面句子中的最后一个单词:
“我在法国长大……我说一口流利的[]”,
LSTMs 可以帮助解决这个问题。通过学习句子的上下文,基于训练数据,可以建议接下来的单词是“法语”2
我们将使用 LSTM 的生成能力来创建一个交互式的在线演示,您可以从一个经过训练的模型中抽取字符样本,并根据您所写的内容生成新的文本序列。
ml5.js 简介
关于 LSTMs 的好消息是,有许多好的方法可以轻松地开始使用它们,而不必太深入地钻研技术基础。其中一种方式是使用 ml5.js 。
ml5.js 是一个新的 JavaScript 库,旨在让艺术家、创意程序员和学生等广大受众能够接触到机器学习。该库在浏览器中提供对机器学习算法和模型的访问,构建在 TensorFlow.js 之上,没有其他外部依赖性。该项目目前由教师、居民和学生组成的社区在 NYU ITP 进行维护。你可以在这篇文章或者这个 Twitter 帖子中了解更多关于 ml5.js 的历史。
本教程将使用 ml5。LSTMGenerator() 方法加载预训练的 LSTM 模型,我们将在本文中使用 Python 和 GPU 加速计算开发该模型,并使用它在 Javascript 中生成新的字符序列。
好奇?这是我们将要建立的演示。这个例子使用了在欧内斯特·海明威的语料库上训练的模型。开始输入一些东西,模型会根据你的书写建议新的行:
https://paperspace.github.io/training-lstm/ml5js_example/
安装
LSTMs 需要很长时间来训练,所以我们将使用 P5000 GPU 显卡来加快速度。运行本教程的惟一要求是安装node . js和 Paperspace 账户。
本教程的训练代码基于 char-rnn-tensorflow ,而后者的灵感来自安德烈·卡帕西的 char-rnn 。
安装图纸空间节点 API
我们将使用纸空间节点 API 。您可以使用 npm 轻松安装它:
npm install -g paperspace-node
或者使用 Python:
pip install paperspace
(如果你愿意,也可以从 GitHub 发布页面安装二进制文件)。
创建 Paperspace 帐户后,您将能够从命令行使用您的凭据登录:
paperspace login
出现提示时,添加您的 Paperspace 电子邮件和密码。
培训说明
1)克隆存储库
这个项目的代码可以在这里找到。从克隆或下载存储库开始:
git clone https://github.com/Paperspace/training-lstm.git
cd training-lstm
这将是我们项目的开始。
2)收集你的数据
当您想要从大型数据集中预测序列或模式时,LSTMs 非常有用。尽可能多地收集干净的文本数据!越多越好。
一旦你准备好了数据,在/data
中创建一个新的文件夹,并随意命名。在这个新文件夹中,只需添加一个名为input.txt
的文件,其中包含您所有的训练数据。
(将许多小的不同的.txt
文件连接成一个大的训练文件的快速技巧:ls *.txt | xargs -L 1 cat >> input.txt
)
对于这个例子,我们将使用一些佐拉·尼尔·赫斯顿的书作为我们的源文本,因为它们可以在古腾堡项目上免费获得。你可以在这里找到我们将使用的input.txt
文件。
3)在 Paperspace 上运行您的代码
训练 LSTM 的代码包含在您刚刚下载的项目中。我们唯一需要修改的文件是 run.sh
。这个文件设置了我们需要的所有参数:
python train.py --data_dir=./data/zora_neale_hurston \
--rnn_size 128 \
--num_layers 2 \
--seq_length 50 \
--batch_size 50 \
--num_epochs 50 \
--save_checkpoints ./checkpoints \
--save_model /artifacts
在这里,我们设置所有的超参数:输入数据、网络的层数、批量大小、历元数以及保存检查点和最终模型的位置。我们现在将使用默认设置,但是请查看部分,了解更多关于如何最好地训练您的网络的信息。您需要修改的唯一一行是指向您自己的数据集的--data_dir=./data/bronte
(即:--data_dir=./data/MY_OWN_DATA
现在我们可以开始训练了。只需键入:
paperspace jobs create --container tensorflow/tensorflow:1.5.1-gpu-py3 --machineType P5000 --command 'bash run.sh' --project 'LSTM training'
这意味着我们希望create
一个新的paperspace job
使用一个 Docker 镜像作为基础container
,该镜像安装了 Tensorflow 1.5.1 和 Python 3(这样我们就不需要担心安装依赖项、包或管理版本)。我们还想使用一个machineType P5000
,我们想运行command
bash run.sh
来开始训练过程。这个project
将被称为LSTM training
如果您输入正确(或复制正确),培训过程应该开始,您应该看到如下内容:
Uploading training-lstm.zip [========================================] 18692221/bps 100% 0.0s
New jobId: j8k4wfq65y8b6
Cluster: PS Jobs on GCP
Job Pending
Waiting for job to run...
Job Running
Storage Region: GCP West
Awaiting logs...
Here we go! Reading text file...
{"chart": "loss", "axis": "Iteration"}
{"chart": "loss", "x": 0, "y": 4.431717}
0/4800 (epoch 0), train_loss = 4.432, time/batch = 0.447
Model saved to ./checkpoints/zora_neale_hurston/zora_neale_hurston!
{"chart": "loss", "x": 1, "y": 4.401691}
1/4800 (epoch 0), train_loss = 4.402, time/batch = 0.060
{"chart": "loss", "x": 2, "y": 4.337208}
2/4800 (epoch 0), train_loss = 4.337, time/batch = 0.059
{"chart": "loss", "x": 3, "y": 4.193798}
3/4800 (epoch 0), train_loss = 4.194, time/batch = 0.058
{"chart": "loss", "x": 4, "y": 3.894172}
4/4800 (epoch 0), train_loss = 3.894, time/batch = 0.056
这可能需要一段时间来运行,LSTMs 是众所周知的通话时间训练。一个好处是您不需要监控整个过程,但是您可以通过键入以下命令来检查它是如何进行的:
paperspace jobs logs --tail --jobId YOUR_JOB_ID
如果您登录到您的 Paperspace 帐户,您还可以在“渐变”选项卡下以更具互动性的方式遵循培训流程:
培训过程完成后,您应该会看到以下日志:
Model saved to ./checkpoints/zora_neale_hurston/zora_neale_hurston!
Converting model to ml5js: zora_neale_hurston zora_neale_hurston-18
Done! The output model is in /artifacts
Check https://ml5js.org/docs/training-lstm for more information.
4)使用 ml5.js 中的模型
现在我们可以用 ml5js 从 JavaScript 中的模型中取样。模型保存在作业的/artifacts
文件夹中。所以我们首先需要下载它。从项目变更目录的root
进入/ml5js_example/models
并运行:
paperspace jobs artifactsGet --jobId YOUR_JOB_ID
这将下载我们需要的包含在您的训练模型中的所有文件。
现在打开sketch.js
文件,在下面一行中更改模型的名称:
const lstm = ml5.LSTMGenerator('./PATH_TO_YOUR_MODEL', onModelReady);
代码的其余部分相当简单。一旦我们用 ml5js 创建了 lstm 方法,我们就可以使用下面的函数让它对模型进行采样:
const data = {
seed: 'The meaning of life is ',
temperature: 0.5,
length: 200
};
lstm.generate(data, function(results){
/* Do something with the results */
});
我们几乎准备好测试模型了。剩下的唯一一件事就是启动一个服务器来查看我们的文件。如果您使用的是 Python 2:
python -m SimpleHTTPServer
如果您使用的是 Python 3:
python -m http.server
请访问 http://localhost:8000 ,如果一切顺利,您应该会看到演示:
https://paperspace.github.io/training-lstm/ml5js_example/
这就对了。我们使用 Python 为字符级语言训练了一个多层递归神经网络(LSTM,RNN),通过 GPU 加速,将结果模型移植到 JavaScript,并在交互式演示中使用它来创建带有 ml5js 的文本序列。
5)调整模型
调整你的模型可能很难,因为涉及到很多参数和变量。一个很好的起点是遵循原文的存储库建议。但总的来说,考虑到训练数据集的规模,这里有一些好的见解可以考虑:
- 2 MB:
- rnn_size 256(或 128)
- 层 2
- 序列长度 64
- 批量 _ 大小 32
- 辍学 0.25
- 5-8 MB:
- rnn_size 512
- 第 2 层(或第 3 层)
- 序列长度 128
- 批量大小 64
- 辍学 0.25
- 10-20 MB:
- rnn_size 1024
- 第 2 层(或第 3 层)
- seq_length 128(或 256)
- 批量大小 128
- 辍学 0.25
- 25 MB 以上:
- rnn_size 2048
- 第 2 层(或第 3 层)
- seq_length 256(或 128)
- 批量大小 128
- 辍学 0.25
资源
了解有关 LSTMs 的更多信息
- Christopher Olah 著了解 LSTMs
- 递归神经网络的不合理有效性,作者 Andrej Karpathy。
JavaScript 中的机器学习
PyTorch 中的培训、验证和准确性
原文:https://blog.paperspace.com/training-validation-and-accuracy-in-pytorch/
说到深度学习,大多数框架都没有预打包的训练、验证和准确性功能或方法。因此,当许多工程师第一次处理数据科学问题时,开始使用这些功能可能是一个挑战。在大多数情况下,这些过程需要手动实现。在这一点上,它变得棘手。为了编写这些函数,需要真正理解这些过程需要什么。在这篇初学者教程文章中,我们将从理论上对上述过程进行高级研究,并在 PyTorch 中实现它们,然后将它们放在一起,为分类任务训练一个卷积神经网络。
导入和设置
下面是我们将用于该任务的一些导入的库。每个都预装在 Gradient Notebook 的深度学习运行时中,因此使用上面的链接在免费的 GPU 上快速开始本教程。
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
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')
神经网络剖析
首先,当谈到由神经网络产生的模型时,无论是多层感知器、卷积神经网络还是生成对抗网络等,这些模型都简单地由“数字”组成,这些数字是统称为 参数 的权重和偏差。一个有 2000 万个参数的神经网络就是一个有 2000 万个数字的网络,每个数字都会影响通过网络的任何数据实例(权重为乘法,偏差为加法)。按照这种逻辑,当 28×28 像素的图像通过具有 2000 万个参数的卷积神经网络时,所有 784 个像素实际上将遇到所有 2000 万个参数,并以某种方式被所有 2000 万个参数转换。
模型目标
考虑下面的自定义构建的 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)
假设我们想要训练这个 convnet 正确地区分标记为 0 的猫和标记为 1 的狗。本质上,我们试图在低层次上做的是确保每当一幅猫的图像通过网络时,它的所有像素都与这个 convnet 中的所有 197,898 个参数交互,输出向量中的第一个元素(索引 0)将大于第二个元素(索引 1){例如[0.65, 0.35]
}。否则,如果一个狗的图像被通过,那么第二个元素(index 1)预计会更大{例如[0.20, 0.80]
}。
现在,我们可以开始认识到模型目标的艰巨性,是的,在数百万种可能的排列中存在 197,898 个数字/参数的组合,这使我们能够做我们在上一段中描述的事情。寻找这种排列就是 训练 过程所需要的。
正确的组合
当一个神经网络被实例化时,它的参数都是随机的,或者如果你通过一种特定的技术初始化参数,它们将不是随机的,而是遵循特定于该特定技术的初始化。尽管如此,在初始化时,网络参数将不适合手边的模型目标,使得如果在该状态下使用模型,将获得随机分类。
现在的目标是找到 197,898 个参数的正确组合,这将使我们能够实现我们的目标。为此,我们需要将训练图像分成多个批次,然后将一个批次通过 convnet,测量我们的分类错误程度(),在最适合我们目标的方向上稍微调整所有 197,898 个参数( 反向传播 ),然后对所有其他批次重复,直到训练数据用尽。这个过程叫做 优化 。
`def train(network, training_set, batch_size, optimizer, loss_function):
"""
This function optimizes the convnet weights
"""
# creating list to hold loss per batch
loss_per_batch = []
# defining dataloader
train_loader = DataLoader(training_set, batch_size)
# iterating through batches
print('training...')
for images, labels in tqdm(train_loader):
#---------------------------
# sending images to device
#---------------------------
images, labels = images.to(device), labels.to(device)
#-----------------------------
# zeroing optimizer gradients
#-----------------------------
optimizer.zero_grad()
#-----------------------
# classifying instances
#-----------------------
classifications = network(images)
#---------------------------------------------------
# computing loss/how wrong our classifications are
#---------------------------------------------------
loss = loss_function(classifications, labels)
loss_per_batch.append(loss.item())
#------------------------------------------------------------
# computing gradients/the direction that fits our objective
#------------------------------------------------------------
loss.backward()
#---------------------------------------------------
# optimizing weights/slightly adjusting parameters
#---------------------------------------------------
optimizer.step()
print('all done!')
return loss_per_batch`
恰当的概括
为了确保优化的参数对训练集之外的数据起作用,我们需要利用它们对不同的图像集进行分类,并确保它们具有与在训练集上相当的性能,这次不会对参数进行优化。这个过程被称为 、 ,用于此目的的数据集被称为 验证集 。
`def validate(network, validation_set, batch_size, loss_function):
"""
This function validates convnet parameter optimizations
"""
# creating a list to hold loss per batch
loss_per_batch = []
# defining model state
network.eval()
# defining dataloader
val_loader = DataLoader(validation_set, batch_size)
print('validating...')
# preventing gradient calculations since we will not be optimizing
with torch.no_grad():
# iterating through batches
for images, labels in tqdm(val_loader):
#--------------------------------------
# sending images and labels to device
#--------------------------------------
images, labels = images.to(device), labels.to(device)
#--------------------------
# making classsifications
#--------------------------
classifications = network(images)
#-----------------
# computing loss
#-----------------
loss = loss_function(classifications, labels)
loss_per_batch.append(loss.item())
print('all done!')
return loss_per_batch`
衡量绩效
在平衡训练集的上下文中处理分类任务时,最好使用准确度作为选择的度量来测量模型性能。由于标签是整数,本质上是指向应该具有最高概率/值的索引的指针,为了获得精确度,我们需要将图像通过 convnet 时输出向量表示中最大值的索引与图像的标签进行比较。准确性是在训练集和验证集上测量的。****
**`def accuracy(network, dataset):
"""
This function computes accuracy
"""
# setting model state
network.eval()
# instantiating counters
total_correct = 0
total_instances = 0
# creating dataloader
dataloader = DataLoader(dataset, 64)
# iterating through batches
with torch.no_grad():
for images, labels in tqdm(dataloader):
images, labels = images.to(device), labels.to(device)
#-------------------------------------------------------------------------
# making classifications and deriving indices of maximum value via argmax
#-------------------------------------------------------------------------
classifications = torch.argmax(network(images), dim=1)
#--------------------------------------------------
# comparing indicies of maximum values and labels
#--------------------------------------------------
correct_predictions = sum(classifications==labels).item()
#------------------------
# incrementing counters
#------------------------
total_correct+=correct_predictions
total_instances+=len(images)
return round(total_correct/total_instances, 3)`**
连接零件
****#### 资料组
为了观察所有这些协同工作的过程,我们现在将它们应用于一个实际的数据集。CIFAR-10 数据集将用于此目的。这是一个数据集,包含来自 10 个不同类别的 32 x 32 像素图像,如下表所示。
数据集可以加载到 PyTorch 中,如下所示...
# 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())
标签 | 描述 |
---|---|
Zero | 飞机 |
one | 汽车 |
Two | 鸟 |
three | 猫 |
four | 鹿 |
five | 狗 |
six | 青蛙 |
seven | 马 |
eight | 船 |
nine | 卡车 |
转换架构
因为这是一个包含 10 个类的分类任务,所以我们需要修改我们的 convnet,以输出一个包含 10 个元素的向量,如下面的代码单元所示。
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, 10, 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, 10)
return F.softmax(output_7, dim=1)
连接过程
为了训练上面定义的 convnet,我们需要做的就是实例化模型,利用训练函数优化 convnet 权重,并记录我们对每批的分类错误程度(损失)。然后,我们利用验证函数来确保 convnet 在训练集之外的数据上工作,并再次记录我们的分类有多错误。然后,我们在训练和验证集上导出 convnets 精度,记录每一步的损失,以跟踪优化过程。
# instantiating model
model = ConvNet()
# defining optimizer
optimizer = torch.optim.Adam(model.parameters(), lr=3e-4)
# training/optimizing parameters
training_losses = train(network=model, training_set=training_set,
batch_size=64, optimizer=optimizer,
loss_function=nn.CrossEntropyLoss())
# validating optimizations
validation_losses = validate(network=model, validation_set=validation_set,
batch_size=64, loss_function=nn.CrossEntropyLoss())
# deriving model accuracy on the traininig set
training_accuracy = accuracy(model, training_set)
print(f'training accuracy: {training_accuracy}')
# deriving model accuracy on the validation set
validation_accuracy = accuracy(model, validation_set)
print(f'validation accuracy: {validation_accuracy}')
当您运行上面的代码块时,很有可能您的训练和验证精度会不太理想。但是,如果您取出前四行代码并将其粘贴到新的代码单元格中,重新运行训练、验证和准确性函数将会提高性能。对整个数据集进行多次循环的过程被称为 时期的训练。 本质上,如果您运行流程五次,那么您已经为 5 个时期 训练了模型。
移动前四行代码的原因是,在运行训练函数时,模型中随机初始化的权重已经优化到一个点。如果您随后重新运行代码单元,而您的模型实例化仍然在同一个单元中,那么它的权重将再次被随机化,并使之前的优化无效。为了确保所有进程都正确同步,将进程打包在函数或类中是一个好主意。我个人更喜欢使用类,因为它把所有的过程都放在一个整洁的包中。
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)
在上面的课程中,我们在train()
方法中结合了训练和验证过程。这是为了防止进行多个属性调用,因为这些进程无论如何都是串联工作的。还要注意的是,精确度是作为一个辅助函数添加到train()
方法中的,精确度是在训练和验证之后立即计算的。还定义了权重初始化函数,该函数有助于使用 Xavier 权重初始化技术来初始化权重,对如何初始化网络的权重进行一定程度的控制通常是很好的实践。还定义了一个度量日志,用于在训练 convnet 时跟踪所有损耗和精度。
# training model
model = ConvolutionalNeuralNet(ConvNet())
log_dict = model.train(nn.CrossEntropyLoss(), epochs=10, batch_size=64,
training_set=training_set, validation_set=validation_set)
用上面定义的参数训练 con vnet 10 个时期(10 个周期)产生了下面的度量。在培训过程中,培训和验证的准确性都有所提高,这表明 convnets 参数确实得到了适当的调整/优化。验证准确度开始时约为 58%,到第十个时期能够达到 72%。
然而,应该注意的是,虽然验证曲线已经开始变平,但 convnet 并没有被彻底训练,它仍然处于上升趋势,因此 convnet 在过度拟合开始之前可能需要更多的训练周期。损失图也表明了同样的情况,即每批的验证损失在第 10 个时期结束时仍呈下降趋势。
结束语
在本文中,我们探讨了神经网络训练中的三个重要过程:训练、验证和准确性。我们从较高的层面解释了所有这三个过程需要什么,以及它们如何在 PyTorch 中实现。然后,我们将所有这三个过程组合在一个类中,并将其用于训练卷积神经网络。读者应该期望能够在他们自己的 PyTorch 代码中实现这些功能。****
迁移学习完全实践指南(下)
原文:https://blog.paperspace.com/transfer-learning-explained-2/
在本系列的第一部分中,我们介绍了与迁移学习相关的大部分基本理论和概念。我们学习了卷积神经网络,它们如何用于迁移学习,并了解了如何微调这些模型。最后,我们还分析了几种常用的迁移学习模型。如果你对这个概念不熟悉,我强烈建议你在继续本文之前,先看看这个完整直观的迁移学习指南。
在这篇文章中,我们将集中在迁移学习的实践方面。我们将用三种不同的模型来涵盖整个人脸识别项目,即 Resnet50、InceptionV3 和 MobileNetV2。随意试验和尝试新的东西,以达到更好的效果。请参考目录,了解文章这一特定部分将涉及的内容。由于这个项目是从头开始构建的,因此最好遵循所有的内容。
您可以从 Gradient Community 笔记本的免费 GPU 上运行本文的完整代码。
目录
- 介绍
- 一般提示和建议
- 数据收集
- 数据准备(预处理数据)
- 数据扩充
1。导入所需的库
2。设置参数
3。训练和验证数据的扩充 - 模型和架构构建
1。分配不同的迁移学习架构
2。建立各自的模型 - 回调、模型编译和培训
1。定义各自的回调函数
2。模型的编译
3。拟合迁移学习模型 - 模型预测
1。导入库
2。做出预测 - 结论
介绍
虽然迁移学习模型在现实世界的应用中有大量的用例,但我们将特别关注人脸识别项目。在这个项目的帮助下,我们可以了解几种不同类型的模型将如何工作。
本文的主要目标是让您开始在一个相当流行的示例项目中使用迁移学习模型。由于各种参数、约束、数据和较少的训练次数,每个模型的整体性能可能不如预期的那样完美。然而,这只是一个示例,展示了如何在这些迁移学习模型的帮助下构建类似的架构并执行大量项目,从而在您自己的特定任务中获得更好的结果。
一般提示和建议
对于本文的其余部分,需要记住的一些值得注意的提示和建议是,我们总是可以通过进一步的实验来提高模型的质量。这里使用的不同模型,即 ResNet50、InceptionV3 和 MobileNetV2,是为了理解迁移学习项目背后的逻辑而实现的。然而,任何模型都可能比另一个模型更适合一个任务,并且这些模型可能不是每个场景的最佳选择。随着我们探索和构建我们的项目,我们将进一步理解这一点。
多年来,已经开发了几种高级的人脸识别模型。非常鼓励使用各种改进模型的方法,如微调、操纵不同的参数等。也被利用了。即使更改一个很小的参数,或者添加批处理规范化或删除之类的内容,都会对模型性能产生重大影响。
数据收集
任何机器学习或深度学习项目的第一步都涉及到将数据整理有序。这项特定人脸识别任务的数据收集主要有两种方式:使用在线数据集,或创建自己的自定义数据集,其中包含一些您自己、您的家人或朋友或您选择的任何其他人的图像。如果使用在线数据集,我建议查看下面的链接。所提供的网站有一个链接,链接到用于人脸识别的训练模型的十个最流行的数据集。
在下一个代码块中,我们将利用流行的名为 OpenCV 的计算机视觉库,通过网络摄像头捕捉特定的帧。我们将使用变量目录,该目录将创建一个文件夹,用于存储您选择的所需名称的特定外观。您可以随意编辑这个变量。count 变量用于表示将在目录中捕获的图像总数。标记将从零开始,直到您选择捕捉的图像总数。确保帧读取准确,并从多个不同角度拍摄所需面部的照片,以使其符合训练程序。空格键用于捕捉特定帧,按下 q 按钮退出 cv2 图形窗口。
import cv2
import os
capture = cv2.VideoCapture(0)
directory = "Your_Name/"
path = os.listdir(directory)
count = 0
while True:
ret, frame = capture.read()
cv2.imshow('Frame', frame)
"""
Reference: http://www.asciitable.com/
We will refer to the ascii table for the particular waitkeys.
1\. Click a picture when we press space button on the keyboard.
2\. Quit the program when q is pressed
"""
key = cv2.waitKey(1)
if key%256 == 32:
img_path = directory + str(count) + ".jpeg"
cv2.imwrite(img_path, frame)
count += 1
elif key%256 == 113:
break
capture.release()
cv2.destroyAllWindows()
通过替换变量目录中的“Your_Name ”,您可以随意为数据集创建多少个新文件夹。出于训练的目的,我选择只创建一对。每个图像将相应地以. jpeg 格式存储,并在各自的目录中增加计数。一旦您对收集和创建的数据量感到满意,我们就可以进入下一步,这将涉及数据集的准备。我们将对数据进行预处理,使其适合迁移学习模型。
数据集准备(预处理数据)
在收集数据之后,任何数据科学项目最重要的步骤之一是使数据集更适合特定的任务。此操作在数据集准备阶段执行,也称为数据预处理。对于这个特定的面部识别模型,您使用之前的代码片段或自己的代码片段准备的自定义数据集上的图像大小和形状可能会有所不同。然而,我们将努力确保通过迁移学习模型生成的所有图像大小相同。下面是调整相应目录中所有图像大小的代码片段。
import cv2
import os
directory = "Your_Name/"
path = os.listdir(directory)
for i in path:
img_path = directory + i
image = cv2.imread(img_path)
image = cv2.resize(image, (224, 224))
cv2.imwrite(img_path, image)
上面的代码片段不言自明。我们遍历目录变量中提到的特定目录,并确保特定路径中的每个图像都相应地调整了大小。我已经为图像的高度和宽度选择了 224 像素的大小值。这些值通常更适合迁移学习模型。ImageNet 的原始大小也是 224,224,有三个通道。这三个通道代表红色、绿色和蓝色(RGB 模式)。确保您收集的图像也是这种格式。否则,您将需要添加额外的代码行来将图像从一种格式(比如灰度)转换为另一种格式(在这种情况下是 RGB)。
请务必将必要的图像转移到各自的目录中,并将它们分成训练和测试图像。确保将图像分成训练和测试图像的比例分别满足至少 75:25 或 80:20 的比例。我选择手动执行以下步骤,因为我只处理了几个较小的数据集。然而,如果您有一个更大的数据集,那么最好用 scikit-learn 库编写一些代码来相应地拆分数据。
请注意,这些用于数据收集和数据集准备的 Python 片段最好在单独的 Python 文件或 Jupyter 笔记本中运行。不要将它们与即将到来的部分混淆,该部分将作为一个单元运行。从下一节开始直到预测阶段,我们将在一个 Jupyter 笔记本中继续整个模型构建过程。大多数文件都附在这篇文章后面。请随意使用和试验它们。
数据扩充
在本文的这一部分,我们将利用数据扩充来提高数据集的质量。数据扩充是一种技术,它使机器学习从业者能够增加用于训练模型的数据集的潜力和整体多样性。整个过程不需要额外收集新的数据元素。利用裁剪、填充和水平翻转的方法来获得放大的数据集。在接下来的几个部分中,我们将导入人脸识别项目任务所需的所有基本库和模块。让我们开始实施这些流程。
导入所需的库
在接下来的步骤中,我们将导入人脸识别项目所需的所有库。我强烈建议查看我之前的两篇关于 TensorFlow 和 Keras 介绍的文章,以了解关于这些特定主题的更详细的方法。你可以从这个链接查看 TensorFlow 教程,从这个特定的链接查看 Keras 文章。在继续下一步之前,请确保您对这些主题有足够的了解,以避免任何困惑。
import keras
import tensorflow as tf
from tensorflow.keras.applications import ResNet50
from tensorflow.keras.applications import InceptionV3
from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.layers import Input, Conv2D, MaxPool2D, Dropout, BatchNormalization, Dense, Flatten
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.regularizers import l2
from tensorflow.keras.optimizers import Adam
import os
一旦我们加载了所有需要的库,我们将相应地设置所有不同的参数。让我们继续下一节,了解一些基本参数并设置它们。
设置参数
在下面的代码块中,我们将设置几个变量,以便它们可以在整个项目中用于多种目的,包括增加数据、为各个模型定义图像形状,以及将文件夹和目录分配到特定的目录路径。让我们研究一下执行这个操作的简单代码块。
num_classes = 2
Img_Height = 224
Img_width = 224
batch_size = 128
train_dir = "Images/Train"
test_dir = "Images/Test"
我在我的自定义数据中使用了两个目录,这表明我主要有两个类。num_classes
变量将在稍后创建模型架构时使用,以调用 SoftMax 函数对不同类别进行多类分类。图像高度和宽度是定义的重要参数。最后,提供了到培训和测试目录的路径。您也可以随意包含您认为必要的任何其他参数。
增加训练和验证数据
本文这一部分的最后一步是在图像数据生成器的帮助下实现数据的扩充。我们将在这些变换技术的帮助下创建原始图像的副本,这些技术包括缩放、旋转、缩放等方法。但是,这些操作仅在培训数据生成器上执行。验证数据生成器将只包含重定比例因子。让我们探索下面提供的代码块,以便更清楚地了解和理解。
train_datagen = ImageDataGenerator(rescale=1./255,
rotation_range=30,
shear_range=0.3,
zoom_range=0.3,
width_shift_range=0.4,
height_shift_range=0.4,
horizontal_flip=True,
fill_mode='nearest')
validation_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(train_dir,
target_size=(Img_Height, Img_width),
batch_size=batch_size,
class_mode='categorical',
shuffle=True)
validation_generator = validation_datagen.flow_from_directory(test_dir,
target_size=(Img_Height, Img_width),
batch_size=batch_size,
class_mode='categorical',
shuffle=True)
我们将在以后的文章中更详细地讨论这个特定的数据扩充主题。为了这个项目的目的,让我们继续建立模型架构来解决人脸识别的任务。
模型和架构构造
现在,我们已经完成了人脸识别项目的所有初始步骤,包括导入所需的库、分配各种参数和增加数据元素,我们将继续前进,并定义我们将用于实施该项目的三个主要体系结构。因为我们已经调用了 Keras 库中的应用程序模块,所以我们可以直接利用我们在导入阶段调用的三个模型。让我们看看下一个代码块,以便更好地理解这一步。
分配不同的迁移学习架构
ResNet_MODEL = ResNet50(input_shape=(Img_width, Img_Height, 3), include_top=False, weights='imagenet')
Inception_MODEL = InceptionV3(input_shape=(Img_width, Img_Height, 3), include_top=False, weights='imagenet')
MobileNet_MODEL = MobileNetV2(input_shape=(Img_width, Img_Height, 3), include_top=False, weights='imagenet')
for layers in ResNet_MODEL.layers:
layers.trainable=False
for layers in Inception_MODEL.layers:
layers.trainable=False
for layers in MobileNet_MODEL.layers:
layers.trainable=False
for layers in MobileNet_MODEL.layers:
print(layers.trainable)
在上面的代码块中,我们将变量初始化为它们各自的模型,即 Resnet50、InceptionV3 和 MobileNetV2 模型。我们将用(224,224,3)的图像形状为以下三个模型中的每一个分配初始参数,并分配与 ImageNet 等价的预训练权重。然而,我们将排除顶层,以便我们可以微调我们的模型,并添加我们自己的自定义层来提高这些体系结构的整体性能。我们遍历三个模型的所有层,将可训练层声明为假。在这个特定的实现中,我将只使用一个展平层、几个密集层和一个最终的密集层,并使用 SoftMax 函数进行多类预测。
建立各自的模型
在接下来的几个代码块中,我们的主要目标是关注功能性 API 模型构建结构,其中我们将利用各自的迁移学习模型。特定的输入变量将具有您选择实施的任何迁移学习模型的相应输出。如前所述,我只使用了一个简单的架构来微调和训练这些模型。但是,我建议查看这个项目的人实现自己的方法,并使用各种层和激活功能以及节点数量进行实验,以便为下面的任务获得最佳结果。
1. Resnet50
上图是 Resnet 架构的代表。在下面的代码块中,我们将实现适合 Resnet 体系结构输出的示例代码。
# Input layer
input_layer = ResNet_MODEL.output
# Flatten
flatten = Flatten(data_format='channels_last',name='Flatten')(input_layer)
# Fully Connected layer-1
FC1 = Dense(units=30, activation='relu',
kernel_initializer=keras.initializers.glorot_normal(seed=32),
name='FC1')(flatten)
# Fully Connected layer-2
FC2 = Dense(units=30, activation='relu',
kernel_initializer=keras.initializers.glorot_normal(seed=33),
name='FC2')(FC1)
# Output layer
Out = Dense(units=num_classes, activation='softmax',
kernel_initializer=keras.initializers.glorot_normal(seed=3),
name='Output')(FC2)
model1 = Model(inputs=ResNet_MODEL.input,outputs=Out)
模型摘要和图
要分析 ResNet 模型的模型图和摘要,请使用下面提供的代码。借助这些模型,你可以相应地分析各种因素。我选择不在这篇文章中包括摘要和情节,因为它们太大了。请随时检查他们自己与代码提供给以下每一个。下面附上的 Jupyter 笔记本也将包含这些概念的完整代码。
model1.summary()
模型图的代码如下:
from tensorflow import keras
from keras.utils.vis_utils import plot_model
keras.utils.plot_model(model1, to_file='model1.png', show_layer_names=True)
2.InceptionV3
上图是 InceptionV3 架构的代表。在下面的代码块中,我们将实现适合 InceptionV3 架构输出的示例代码。
# Input layer
input_layer = Inception_MODEL.output
# Flatten
flatten = Flatten(data_format='channels_last',name='Flatten')(input_layer)
# Fully Connected layer-1
FC1 = Dense(units=30, activation='relu',
kernel_initializer=keras.initializers.glorot_normal(seed=32),
name='FC1')(flatten)
# Fully Connected layer-2
FC2 = Dense(units=30, activation='relu',
kernel_initializer=keras.initializers.glorot_normal(seed=33),
name='FC2')(FC1)
# Output layer
Out = Dense(units=num_classes, activation='softmax',
kernel_initializer=keras.initializers.glorot_normal(seed=3),
name='Output')(FC2)
model2 = Model(inputs=Inception_MODEL.input,outputs=Out)
模型摘要和图
要分析 InceptionV3 模型的模型图和摘要,请使用下面提供的代码。借助这些模型,你可以相应地分析各种因素。我选择不在这篇文章中包括摘要和情节,因为它们太大了。请随时检查他们自己与代码提供给以下每一个。下面附上的 Jupyter 笔记本也将包含这些概念的完整代码。
model2.summary()
模型图的代码如下:
from tensorflow import keras
from keras.utils.vis_utils import plot_model
keras.utils.plot_model(model2, to_file='model2.png', show_layer_names=True)
3.MobileNetV2
上图是 MobileNetV2 架构的代表。在下面的代码块中,我们将实现适合 MobileNetV2 架构输出的示例代码。
# Input layer
input_layer = MobileNet_MODEL.output
# Flatten
flatten = Flatten(data_format='channels_last',name='Flatten')(input_layer)
# Fully Connected layer-1
FC1 = Dense(units=30, activation='relu',
kernel_initializer=keras.initializers.glorot_normal(seed=32),
name='FC1')(flatten)
# Fully Connected layer-2
FC2 = Dense(units=30, activation='relu',
kernel_initializer=keras.initializers.glorot_normal(seed=33),
name='FC2')(FC1)
# Output layer
Out = Dense(units=num_classes, activation='softmax',
kernel_initializer=keras.initializers.glorot_normal(seed=3),
name='Output')(FC2)
model3 = Model(inputs=MobileNet_MODEL.input,outputs=Out)
模型摘要和图
要分析 MobileNetV2 模型的模型图和摘要,请使用下面提供的代码。借助这些模型,你可以相应地分析各种因素。我选择不在这篇文章中包括摘要和情节,因为它们太大了。请随时检查他们自己与代码提供给以下每一个。下面附上的 Jupyter 笔记本也将包含这些概念的完整代码。
model3.summary()
模型图的代码如下:
from tensorflow import keras
from keras.utils.vis_utils import plot_model
keras.utils.plot_model(model3, to_file='model3.png', show_layer_names=True)
回调、模型编译和培训
现在,我们已经完成了所有三个模型的构建,我们将使用这些模型来试验我们的人脸识别项目,让我们继续定义一些可能对我们的任务有用的基本回调。在我的代码片段中,我包含了模型检查点,以分别保存我们计划构建的三个模型的最佳权重。我使用了简化的学习率和张量板可视化回调,但对它们进行了评论,因为我只在三个模型上训练我的模型。因此,我不需要它们的用法。随意运行更多的纪元并相应地进行实验。请参考下面的代码块来定义回调。
定义各自的回调
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras.callbacks import ReduceLROnPlateau
from tensorflow.keras.callbacks import TensorBoard
checkpoint1 = ModelCheckpoint("face_rec1.h5", monitor='val_loss', save_best_only=True, mode='auto')
checkpoint2 = ModelCheckpoint("face_rec2.h5", monitor='val_loss', save_best_only=True, mode='auto')
checkpoint3 = ModelCheckpoint("face_rec3.h5", monitor='val_loss', save_best_only=True, mode='auto')
# reduce = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=3, min_lr=0.00001, verbose = 1)
# logdir='logsface3'
# tensorboard_Visualization = TensorBoard(log_dir=logdir)
模型的编译
现在我们已经定义了所有必要的回调,我们可以继续 Jupyter 笔记本中的最后两个步骤,即编译和拟合迁移学习模型。编译阶段是在执行拟合模型的操作时,用相应的损失函数、最佳优化器和所需的度量来配置三个模型。
model1.compile(loss='categorical_crossentropy',
optimizer=Adam(lr=0.00001),
metrics=['accuracy'])
model2.compile(loss='categorical_crossentropy',
optimizer=Adam(lr=0.00001),
metrics=['accuracy'])
model3.compile(loss='categorical_crossentropy',
optimizer=Adam(lr=0.00001),
metrics=['accuracy'])
拟合迁移学习模型
到目前为止,我们已经完成了所有必要的步骤,可以继续对我们各自的模型进行培训。我为每个模型实现了总共三个时期的训练过程。让我们观察输出,并对这些结果的出现得出结论。
1. ResNet 50
epochs = 3
model1.fit(train_generator,
validation_data = validation_generator,
epochs = epochs,
callbacks = [checkpoint1])
# callbacks = [checkpoint, reduce, tensorboard_Visualization])
结果
Train for 13 steps, validate for 4 steps
Epoch 1/3
13/13 [==============================] - 23s 2s/step - loss: 0.6726 - accuracy: 0.6498 - val_loss: 0.7583 - val_accuracy: 0.5000
Epoch 2/3
13/13 [==============================] - 16s 1s/step - loss: 0.3736 - accuracy: 0.8396 - val_loss: 0.7940 - val_accuracy: 0.5000
Epoch 3/3
13/13 [==============================] - 16s 1s/step - loss: 0.1984 - accuracy: 0.9332 - val_loss: 0.8568 - val_accuracy: 0.5000
2.InceptionV3
model2.fit(train_generator,
validation_data = validation_generator,
epochs = epochs,
callbacks = [checkpoint2])
结果
Train for 13 steps, validate for 4 steps
Epoch 1/3
13/13 [==============================] - 21s 2s/step - loss: 0.6885 - accuracy: 0.5618 - val_loss: 0.8024 - val_accuracy: 0.4979
Epoch 2/3
13/13 [==============================] - 15s 1s/step - loss: 0.5837 - accuracy: 0.7122 - val_loss: 0.4575 - val_accuracy: 0.8090
Epoch 3/3
13/13 [==============================] - 15s 1s/step - loss: 0.4522 - accuracy: 0.8140 - val_loss: 0.3634 - val_accuracy: 0.8991
3.MobileNetV2
model3.fit(train_generator,
validation_data = validation_generator,
epochs = epochs,
callbacks = [checkpoint3])
结果
Train for 13 steps, validate for 4 steps
Epoch 1/3
13/13 [==============================] - 17s 1s/step - loss: 0.7006 - accuracy: 0.5680 - val_loss: 0.7448 - val_accuracy: 0.5107
Epoch 2/3
13/13 [==============================] - 15s 1s/step - loss: 0.5536 - accuracy: 0.7347 - val_loss: 0.5324 - val_accuracy: 0.6803
Epoch 3/3
13/13 [==============================] - 14s 1s/step - loss: 0.4218 - accuracy: 0.8283 - val_loss: 0.4073 - val_accuracy: 0.7361
从这三个迁移学习模型的拟合和计算中,我们可以观察到一些结果。对于 ResNet 模型,虽然训练精度和损失似乎在不断提高,但验证精度保持不变。出现这种情况的几个原因可能是由于大型架构在较小的数据集上过度拟合。另一个原因是由于微调架构缺乏深度。建议您尝试许多参数和不同的微调架构,以获得更好的结果。与 ResNet50 体系结构相比,inceptionV3 体系结构和 MobileNetV2 获得的结果似乎更好。因此,我们将利用来自 InceptionV3 模型的保存的检查点来执行实时预测任务。其他的改进可以通过运行更多的纪元,改变各种参数和其他特性,添加更多的层,并进行试验。
由此我们可以得出的最后结论是,我们已经构建了三种不同类型的性能模型,并且用一种更实用的方法更详细地理解了它们的工作功能。
模型预测法
一旦我们构建了模型,并将最佳权重保存为. h5 格式的模型,我们就可以利用这个资源了。我们可以加载通过检查点过程保存的模型,并在网络摄像头的帮助下使用它们进行实时人脸识别。预测阶段是机器学习和深度学习的更重要的方面之一,因为你知道你构建的模型在实时场景中如何工作。让我们导入实时人脸识别项目预测所需的所有库。
导入库
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing import image
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.preprocessing.image import img_to_array
import numpy as np
import cv2
import time
import os
实时预测
对于实时预测,我们将加载保存的最佳检查点模型和用于检测面部结构的 Haar cascode _ frontal face _ default . XML 文件。然后我们将使用网络摄像头进行实时人脸识别。如果预测的准确度很高,我们将显示最合适的结果。大多数代码结构都很容易理解,尤其是注释行。如果您认为有必要,可以随意试验各种建模技术或构建更方便的预测设置。
model = load_model("face_rec2.h5")
face_classifier = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
Capture = cv2.VideoCapture(0)
while True:
# Capture frame from the Video Recordings.
ret, frame = Capture.read()
# Detect the faces using the classifier.
faces = face_classifier.detectMultiScale(frame,1.3,5)
if faces is ():
face = None
# Create a for loop to draw a face around the detected face.
for (x,y,w,h) in faces:
# Syntax: cv2.rectangle(image, start_point, end_point, color, thickness)
cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,255), 2)
face = frame[y:y+h, x:x+w]
# Check if the face embeddings is of type numpy array.
if type(face) is np.ndarray:
# Resize to (224, 224) and convert in to array to make it compatible with our model.
face = cv2.resize(face,(224,224), interpolation=cv2.INTER_AREA)
face = img_to_array(face)
face = np.expand_dims(face, axis=0)
# Make the Prediction.
pred1 = model.predict(face)
prediction = "Unknown"
pred = np.argmax(pred1)
# Check if the prediction matches a pre-exsisting model.
if pred > 0.9:
prediction = "Your_Name"
elif pred < 0.9:
prediction = "Other"
else:
prediction = "Unknown"
label_position = (x, y)
cv2.putText(frame, prediction, label_position, cv2.FONT_HERSHEY_SIMPLEX,2,(0,255,0),3)
else:
cv2.putText(frame, 'Access Denied', (20,60), cv2.FONT_HERSHEY_SIMPLEX,2,(0,255,0),3)
cv2.imshow('Face Recognition', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
Capture.release()
cv2.destroyAllWindows()
Capture.release()
cv2.destroyAllWindows()
您可以进一步探索项目,并在云环境、raspberry pi 或任何其他类似的嵌入式系统或您的本地桌面上部署该模型。随意探索和构建类似的架构来构建和开发各种任务。
结论
Photo by Aleksander Vlad / Unsplash
在本文中,我们首先介绍了将要讨论的许多内容,然后给出了一些通用的技巧和建议。在初步阶段之后,我们直接投入到项目的开发中,收集和预处理我们的数据以构建不同类型的模型。然后,我们实现了数据的扩充,以创建更多类型的独特模式。在这一步之后,我们继续构建我们各自的模型架构。最后,在编译和拟合模型之前,我们为所需的流程定义了所有必要的回调。然后,我们编写了另一个代码片段,用于实时识别各个人脸,以测试我们收到的结果。
我强烈建议试验和实施其他许多迁移学习的方法,或者构建你自己的定制网络建模系统,以测试结果如何。现在,我们已经涵盖了理解迁移学习网络工作的理论和实践方面,您可以疯狂地计算许多其他类型的任务。借助本文提供的资源,您可以轻松地实现用于执行人脸识别任务的所有三个模型。在未来的文章中,我们将涵盖与深度学习和计算机视觉相关的其他主题,如图像字幕、UNET、CANET 等等。直到那时,然后探索和享受乐趣!
迁移学习的完整直观指南(第 1 部分)
过去十年,深度学习的进步非常迅速。虽然神经网络的发现发生在大约 60 年前,心理学家 Frank Rosenblatt 于 1958 年发明了第一个人工神经网络(称为“感知机”),但该领域的发展直到大约十年前才获得真正的普及。2009 年最受欢迎的成就是 ImageNet 的创建。ImageNet 是一个庞大的视觉数据集,它导致了一些最好的现代深度学习和计算机视觉项目。
ImageNet 由超过 1400 万张图像组成,这些图像经过手工注释,以表示每张图片中的特定对象。超过 100 万张图像也包含特定的边界框。这些边界框对于一些计算机视觉应用是有用的,例如对象检测、对象跟踪等等。在这个由两部分组成的系列文章中,我们将进一步了解 ImageNet 的重要性。
这篇文章(第一部分)将涵盖一个完整的理论指南,以获得对迁移学习的深刻直觉。在本系列的下一部分中,我们将深入探讨一个实用的实践指南,并实现一些迁移学习模型,包括一个人脸识别项目。让我们快速浏览一下目录,以理解本文将涉及的概念。请随意跳到您最想探索的概念。然而,如果你是第一次接触这个话题,通读整篇文章是明智的。
目录
- 介绍
- 了解卷积神经网络
1。CNN 是如何工作的
2?CNN 的基本实现
3。潜入最大池层 - 学习迁移学习
- 微调背后的直觉
1。了解微调
2。最佳实践 - 探索几种迁移学习模式
1。AlexNet
2。VGG-16 - 结论
介绍
随着我们在深度学习领域取得的所有成就和持续进展,如果我们不利用所有这些学到的知识来帮助执行复杂的任务,那将是一种耻辱。一种常见的做法是使用迁移学习,即在新的环境中应用预先训练好权重的模型。
在本文中,我们将特别关注卷积神经网络(CNN),它在大多数计算机视觉任务中被利用。在我们对细胞神经网络有了一个基本的直觉之后,我们将继续学习更多关于迁移学习和与这个主题相关的几个重要方面,包括微调。我们最后将探讨几个典型的迁移学习模型及其用例。在下一篇文章中,我们将在人脸识别问题的背景下深入研究它们的实际实现。
理解卷积神经网络
Image from Wiki
卷积神经网络是一类人工深度神经网络。它们也被称为 ConvNets,或简称为 CNN。卷积神经网络松散地受到某些生物过程的启发,主要来自动物视觉皮层中神经元的活动。为了用简单的术语全面了解它们的工作过程,必须注意到这些 CNN 利用过滤器将复杂的模式变成更小更简单的东西,以便分析、解释和容易可视化。我们将在第 2 部分对此进行更详细的讨论。
二维卷积神经网络主要用于解决具有深度学习的计算机视觉问题。其中包括与手写识别、手势识别、情感识别等相关的任务。虽然它们的主要用途是计算机视觉应用,但并不局限于此。通过使用一维卷积层,我们甚至可以在自然语言处理任务中实现相当高的预测精度。然而,就本文而言,我们将仅限于研究二维卷积层。
在接下来的几节中,我们将详细分析卷积神经网络的完整工作原理,了解它们的基本实现,最后讨论 max-pooling 层。事不宜迟,我们开始吧。
CNN 是如何工作的
Image From Wiki
在上图中,我们有一个\((6 \乘以 6)\)矩阵,它与一个大小为\((3 \乘以 3)\)的核进行卷积。卷积层涉及使用在层输入上“卷积”的核来产生输出张量。上图中的\((3 \乘以 3)\)内核通常被称为“Sobel 边缘检测器”,或“水平边缘检测器”。通常,当\((6 \乘以 6)\)矩阵与\((3 \乘以 3)\)核卷积时,得到的输出矩阵将具有\((4 \乘以 4)\)的形状。为了达到这个结果,执行了一系列分量乘法。换句话说,卷积基本上是一种寻找合适点积的方法。
如上图所示,我们最终得到一个\((6 \乘以 6)\)矩阵,如下所示:
array([[9, 8, 6, 6, 8, 9],
[8, 2, 1, 1, 2, 8],
[6, 1, 0, 0, 1, 6],
[6, 1, 0, 0, 1, 6],
[8, 2, 1, 1, 2, 8],
[9, 8, 6, 6, 8, 9]])
我们的输出矩阵与输入矩阵大小相同的原因是由于填充的实现。通过填充,初始的\((6 \乘以 6)\)输入矩阵被用围绕整个矩阵的附加层“填充”,因此,它被转换成\((8 \乘以 8)\)矩阵。填充操作有两种主要类型:
- 补零:在整个矩阵周围加一层零。这种方法通常是更常用的方法。
- 相同值填充:试图复制最近的行或列中的值。
使用 CNN 时需要知道的另一个基本概念是步幅。步幅是每个特定评估中考虑的移动次数。我们可以注意到上图中只有一个单独的步幅。在卷积层的特定实现中,我们可以有任意数量的步长和填充值。可以用 TensorFlow 和 Keras 实现过滤器的数量、内核大小、填充类型、步数和激活函数(以及其他参数)。最终的输出大小由输入形状、内核大小、填充类型和步数决定。
CNN 的基本实现
在了解了卷积层和卷积神经网络的完整工作过程之后,现在让我们看看如何使用 TensorFlow 和 Keras 深度学习框架来实现其中的一些架构。在深入研究代码之前,强烈建议您查看我以前关于这些主题的文章,这些文章详细介绍了 TensorFlow 和 Keras 。让我们探索一个简单的示例代码块,它涵盖了卷积层和最大池层是如何实现的。
from tensorflow.keras.layers import Convolution2D, MaxPooling2D
Convolution2D(512, kernel_size=(3,3), activation='relu', padding="same")
MaxPooling2D((2,2), strides=(2,2))
实现完全相同代码的另一种方法如下:
from tensorflow.keras.layers import Conv2D, MaxPool2D
Conv2D(512, kernel_size=(3,3), activation='relu', padding="same")
MaxPool2D((2,2), strides=(2,2))
请注意,这些函数是彼此的别名。它们执行完全相同的功能,您可以根据自己的方便使用其中的一种。我建议你自己测试一下这些方法。值得注意的是,在未来的 Keras 版本中,一些别名可能会被弃用。跟上这些图书馆的发展总是一个好习惯。
keras.layers.Conv2D
keras.layers.Convolution2D
keras.layers.convolutional.Conv2D
keras.layers.convolutional.Convolution2D
上面的每一行都有相同的意思,是彼此的别名。您可以在方便的时候有效地使用这些方法。
潜入最大池层
Image from Wiki depicting RoI pooling, size 2x2. In this example the region proposal (an input parameter) has size 7x5.
最大池层用于对卷积层的空间维度进行下采样。这个过程主要是为了在对图像进行操作时创建位置不变性。在 max-pooling 层中,根据所提到的内核大小和跨度来选择特定的矩阵集。一旦选择了这些矩阵,就选择最大值作为该特定像元的输出。对其他较小的矩阵重复该操作,以接收每个单元所需的输出。上图描述了一个大小为\((2 \乘以 2)\)的 RoI 池示例。在本例中,区域建议(输入参数)的大小为\((7 \乘以 5)\)。
大多数卷积层之后是最大池层。最大池对于卷积神经网络的最佳性能至关重要。这些图层引入了位置不变性,这意味着图像中要素的位置不会影响网络检测它们的能力。它们还引入了其他类型的不变性,例如缩放不变性(图像的大小或形状不影响结果)和旋转不变性(图像的旋转角度不影响结果),以及许多其他重要的优点。
学习迁移学习
如今,你很少会看到一个完整的神经网络的实现,它具有从零开始构建的随机权重初始化。典型的数据集太小了,以至于当只针对这种特定于任务的数据进行训练时,无法实现高性能的模型。如今,深度学习中最常见的做法是进行迁移学习。
迁移学习包括使用一个已经在大型数据语料库上训练过的网络,然后在一个较小的特定于手头任务的数据集上重新训练(或“微调”)它。这些模型通常在大型数据集上训练,如 ImageNet ,其中包括数百万张图像。从该预训练中学习的权重被保存并用于模型初始化。
迁移学习有三种主要场景:作为固定特征提取器的 ConvNets、微调和预训练模型。在本文的这一部分中,我们将重点关注作为固定特征提取器和预训练模型的 ConvNets。在接下来的部分中,我们将更深入地讨论微调这个话题。
当使用 ConvNet 作为固定特征提取器时,典型的操作是将最终输出层替换为一个层,该层将包含借助 SoftMax 函数进行多类计算所需的输出节点数。另一方面,预训练模型是具有存储为检查点的最佳权重的模型,并且可以用于在数据集上直接测试性能,而不包括任何额外的训练过程。这种情况的主要用例是当网络需要很长时间训练时(即使使用强大的 GPU),您可以使用最佳保存的权重来获得测试数据的合适结果。下面的一个例子是 Caffe 模型动物园,在这里人们分享他们的网络权重,这样其他人就可以在他们自己的项目中使用它们。
微调背后的直觉
现代深度学习最重要的策略之一就是微调。在接下来的章节中,我们的主要目标是理解微调的概念和最佳实践,这将引导程序员获得最佳结果。
了解微调
微调不仅包括替换我们正在使用的预训练网络的最终密集输出层,还包括对新数据集的分类器进行重新训练。从业者最常用的策略是仅微调迁移学习架构的顶层,以避免过度拟合。因此,在大多数情况下,早期的层通常保持原样。
最佳实践
在对网络进行微调之前,有几个因素必须在实践中加以考虑:新数据集是小于还是大于最初训练网络的数据集,以及它与原始数据集的相似程度。
- 新数据集较小,与原始数据集相似- 对于与原始数据集非常相似的较小数据集,最好跳过微调方法,因为这可能会导致过拟合。
- 新数据集很大,并且与原始数据集相似- 这个场景非常适合迁移学习。当我们有一个与原始数据集非常相似的大型数据集时,我们可以相应地进行微调,以获得令人难以置信的结果。
- 新数据集很小,但与原始数据集非常不同- 与我们之前的讨论类似,因为数据集很小,所以最好的想法是使用线性分类器。然而,由于新数据集与原始数据集非常不同,为了获得更好的性能,从网络中较早的某处激活来训练 SVM 分类器可能会更好。
- 新数据集很大,并且与原始数据集非常不同- 当数据集很大时,微调从来都不是一个坏主意。即使新数据集与原始数据集有很大不同,我们通常也可以只排除网络的顶部。
我喜欢使用下面的代码对迁移学习模型进行微调。
# Convolutional Layer
Conv1 = Conv2D(filters=32, kernel_size=(3,3), strides=(1,1), padding='valid',
data_format='channels_last', activation='relu',
kernel_initializer=keras.initializers.he_normal(seed=0),
name='Conv1')(input_layer)
# MaxPool Layer
Pool1 = MaxPool2D(pool_size=(2,2),strides=(2,2),padding='valid',
data_format='channels_last',name='Pool1')(Conv1)
# Flatten
flatten = Flatten(data_format='channels_last',name='Flatten')(Pool1)
# Fully Connected layer-1
FC1 = Dense(units=30, activation='relu',
kernel_initializer=keras.initializers.glorot_normal(seed=32),
name='FC1')(flatten)
# Fully Connected layer-2
FC2 = Dense(units=30, activation='relu',
kernel_initializer=keras.initializers.glorot_normal(seed=33),
name='FC2')(FC1)
# Output layer
Out = Dense(units=num_classes, activation='softmax',
kernel_initializer=keras.initializers.glorot_normal(seed=3),
name='Output')(FC2)
无论您选择使用什么,重要的是要注意,需要进行试验和大量的实现,以确保获得最佳结果。深度学习的妙处在于随机性的因素。许多最好的结果只有通过尝试许多新方法才能获得。
既然我们已经对卷积神经网络和迁移学习的工作有了相当好的理解,让我们根据它们在历史上的重要性和那些通常用于实现快速、高质量结果的迁移学习模型来探索一些迁移学习模型。对于微调主题的进一步阅读,我强烈建议查看斯坦福大学的网站,了解视觉识别卷积神经网络课程的迁移学习,网址是下面的网站。
探索几种迁移学习模式
在文章的这一部分,我们将研究几个迁移学习架构,即 AlexNet 和 VGG-16。让我们用一些图像表示来简单地探索这些模型,以获得这些主题的概述。在下一篇关于“迁移学习的完整实践指南”的文章中,我们将探索三种不同类型的迁移学习模型以及一些实际实现,即 ResNet50、InceptionV3 和 MobileNetV2。现在,让我们来看看和理解 AlexNet 和 VGG-16 架构。
1.AlexNet
LeNet-5 取得初步成功后,AlexNet 随后增加了深度。这个架构是在 2012 年由 Alex Krizhevsky 和他的同事在题为“使用深度卷积神经网络的 ImageNet 分类”的研究论文中提出的。AlexNet 同年获得 ImageNet 大规模视觉识别挑战赛冠军。在 AlexNet 架构中,我们有一个输入图像,它经过卷积层和 max-pooling 层两次。然后,我们让它通过一系列三个卷积层,后面是一个最大池层。经过这一步,我们有三个隐藏层,然后输出。在 AlexNet 中,最终阶段的整体计算将为每个图像产生一个 4096-D 向量,该向量包含分类器之前的隐藏层的激活。虽然大多数层将利用 ReLU 激活功能,但最后一层利用 SoftMax 激活。
2.VGG-16
VGG-16 中的“VGG”代表“视觉几何群”。它与牛津大学的科学和工程系有联系。在最初阶段,这种体系结构用于研究大规模分类的准确性。现在,VGG-16 架构最常见的用途主要是解决人脸识别和图像分类等任务。从上面的图像表示中,我们可以注意到大小为\((224,224)\)的 RGB 图像通常通过它的层。一个\(2X\)的卷积层后跟一个最大池层被利用了三次,然后一个\(3X\)的卷积层后跟一个最大池层被利用了两次。最后,三个隐藏层之后是由 SoftMax 激活函数实现的具有 1000 个节点的输出层。所有其他层通常利用 ReLU 激活功能。
结论
Photo by Kelly Sikkema / Unsplash
有了迁移学习模型的新知识,我们更快地理解了一些用于解决深度学习中复杂任务的常用工具。深度学习有一个漫长而陡峭的学习曲线,有大量的发现有待发现。迁移学习使该领域的研究人员能够用更少的数据获得更好的结果。
在本文中,我们涵盖了理解迁移学习如何工作以及如何在实践中使用这些模型所需的大部分基本概念。我们从卷积神经网络(CNN)开始,这是迁移学习最常用的模型之一。然后,我们了解了迁移学习模型的众多实施方法,并重点关注了其实施的微调方面。最后,我们探讨了许多迁移学习模型中的一些,这些模型可供我们创建独特的项目。
虽然本文的重点是迁移学习的理论方面,但我们才刚刚开始!在下一篇文章中,我们将深入研究几种流行的迁移学习技术,并实现一个人脸识别项目。我们将讨论如何从零开始构建这样一个模型,并利用一些流行的迁移学习模型来比较它们如何处理实时问题。在那之前,继续学习探索吧!
文本分类转换器
原文:https://blog.paperspace.com/transformers-text-classification/
人类表达情感或感觉的一种常见方式是通过目前存在的多种语言形式的语音。从出生开始,我们就开始慢慢适应和学习久而久之语言。对人类来说,学习、交谈和用任何他们感到舒服的语言写作似乎不是一件非常复杂的任务。然而,当我们处理复杂语言翻译的机器或训练它们理解特定句子的语义时,事情可能会变得非常棘手,因为它们无法理解字符或潜台词等细节。
处理这些与机器学习语言相关的任务的大型研究领域是自然语言处理(NLP)。由于机器翻译、文本分类、语音到文本转换等技术的升级,这一研究领域的受欢迎程度在过去几年中有了极大的提高。在本文中,我们将探索变压器的关键深度学习概念及其在处理自然语言处理相关任务中的强大能力。我们主要关注文本分类。
我们将构建一个 transformer 项目,该项目将尝试以评论的形式学习输入句子的含义,并相应地将它们分类为正面或负面评论。我们将研究两种方法来构建这样一个项目。在第一种方法中,我们将从头开始构建整个架构,在第二种方法中,我们将使用 TensorFlow-Hub 开发相同的项目。Paperspace 上的渐变平台是实施以下项目的绝佳选择。查看目录,探索您感兴趣的主题。
文本分类介绍:
购买者或顾客在购买产品前经常做的一件直观的事情是查看评论,并分析特定产品的接受程度。对于人类来说,阅读某些评论并获得对所写句子的语义理解是很容易的。由于人类认识到他们已经适应特定语言的知识,他们能够大多理解隐藏的意图、讽刺或言语概念的其他关键方面,以感知句子或评论的真实含义。人类可以用他们的直觉来帮助解释,但是直觉的概念在机器中是不存在的。机器智能发现很难破译特定句子的真实意图和含义,因为在试图理解非二进制形式的东西时固有的复杂性。
这就是机器学习和深度学习算法发挥作用的地方。文本分类可以被描述为将文本类型分类到特定类别的机器学习技术。这些类别取决于他们执行的任务类型。一些例子包括情感分析、主题标记、垃圾邮件检测和意图检测。
自然界中可用的大部分数据都是非结构化的。因此,找到一种可靠地管理这些数据集的方法,同时制定一个理想的结构来分析这些问题变得至关重要。在本文中,我们将探讨变形金刚的概念来解决文本分类的任务。让我们从理解 transformer 架构开始。
了解变压器架构:
发表在题为“ 注意力是你所需要的全部 的研究论文中的变形金刚架构是深度学习和自然语言处理领域最有影响力的论文之一。上面的图像表示可能看起来很复杂,因为设计中有很多基本结构。然而,代码的解析要简单得多。通过将每个组件分解成单独的实体,理解变压器设计中涉及的所有核心实体变得非常容易。关于注意力的理论概念的更详细的解释,我推荐从下面的链接查看我以前的一篇文章,这篇文章涉及了使用序列对序列模型的机器翻译过程。
首先,让我们讨论上图中变压器架构的左侧。它由编码器架构和位置编码、输入、输入嵌入以及包含一些神经网络组件的模块组成。我们将该模块称为变压器模块,这是理解变压器工作程序的一个重要方面。
箭头表示将在变压器架构中进行的直接和跳过连接。多头注意力模块将接收三个输入,即值、键和查询。接下来的三个模块非常简单,因为我们执行加法和归一化操作,将它通过前馈网络,并重新调整加法和归一化操作。
我们将稍微详细地讨论多头注意力的概念,因为变压器网络的其他组件非常容易理解。向量、关键字和查询形式的嵌入输入通过线性层传递。假设嵌入具有 64 个维度,并且我们对每一层使用四次分裂。然后,通过的每一层将有四个 16 维数据块嵌入其中。
这些数据通过缩放的点积注意力传递,它利用了一个类似于上图中提到的模型。此外,下面的公式表示模型的清晰性能,其中点注意力等于查询、关键字转置的乘积的 Softmax 除以缩放因子(在这种情况下是嵌入大小的平方根)和向量的乘积。最后,连接结果以检索与嵌入相同的大小,然后通过线性层传递。
最后,变压器网络的右侧由解码器架构组成,它与之前讨论的编码器有一些相似之处。它们有几个转换模块,其中一个具有屏蔽的多头注意力,以确保每个连续的输出只知道前一个输入,而没有任何其他过多的信息。图中的 Nx 表示可以有几个编码器或解码器模块栈来计算特定任务的概念。对于我们的项目,我们将利用 transformer 块的一个更简单的变体来实现文本分类的预期结果。
使用 TensorFlow 和 Keras 从头开始开发变压器模型:
在本节中,我们将构建 transformer 体系结构来解决文本分类问题,并获得理想的结果。两个主要要求是深度学习框架 TensorFlow 和 Keras 的知识。如果你不熟悉这些,我推荐你查看 TensorFlow 的链接和 Keras 的链接。
我们将利用 TensorFlow 中可用的 IMDB 数据集来执行文本分类项目,其中我们将需要相应地对正面或负面评论进行分类。该结构所用代码的主要参考来源之一是官方 Keras 文档网站。Paperspace 上的渐变平台是开始此类项目的一个很好的方式。让我们开始做这个项目吧!
导入基本库:
第一步,我们将用 Transformers 导入文本分类项目所需的所有基本库。如前所述,TensorFlow 和 Keras 是这个项目的基本要求,因为它们将是我们深度学习框架的主要选择。我们还将导入构建 transformer 架构所需的所有层。参考上一节来了解这个 transformer 项目所需的所有层。我还导入了 numpy 和警告库,以忽略用户在加载 IMDB 数据集时可能遇到的任何反对警告。
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import MultiHeadAttention, LayerNormalization, Dropout, Layer
from tensorflow.keras.layers import Embedding, Input, GlobalAveragePooling1D, Dense
from tensorflow.keras.datasets import imdb
from tensorflow.keras.models import Sequential, Model
import numpy as np
import warnings
warnings.filterwarnings("ignore", category=np.VisibleDeprecationWarning)
创建变压器块和位置嵌入:
我们现在将继续构建 transformer 块,它遵循与本文前一节中讨论的类似的构建。虽然我将利用 TensorFlow/Keras 深度学习框架中可用的多头注意力层,但您可以修改代码并构建自己的自定义多头层,以进一步控制和访问所涉及的众多参数。
在 transformer 模块的第一个功能中,我们将初始化所需的参数,即注意层、批量标准化和丢弃层以及前馈网络。在 transformer 块的调用函数中,我们将相应地定义层,如在 transformer 块的架构概述部分中所讨论的。
class TransformerBlock(Layer):
def __init__(self, embed_dim, num_heads, ff_dim, rate=0.1):
super(TransformerBlock, self).__init__()
self.att = MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)
self.ffn = Sequential(
[Dense(ff_dim, activation="relu"),
Dense(embed_dim),]
)
self.layernorm1 = LayerNormalization(epsilon=1e-6)
self.layernorm2 = LayerNormalization(epsilon=1e-6)
self.dropout1 = Dropout(rate)
self.dropout2 = Dropout(rate)
def call(self, inputs, training):
attn_output = self.att(inputs, inputs)
attn_output = self.dropout1(attn_output, training=training)
out1 = self.layernorm1(inputs + attn_output)
ffn_output = self.ffn(out1)
ffn_output = self.dropout2(ffn_output, training=training)
return self.layernorm2(out1 + ffn_output)
在下一个代码块中,我们将定义另一个函数,该函数对于管理研究论文中指定的位置嵌入非常有用。我们正在创建两个嵌入层,即标记和标记索引位置。下面的代码块描述了如何创建一个具有两个函数的类。在第一个函数中,我们初始化令牌和位置嵌入,在第二个函数中,我们将调用它们并相应地对各自的嵌入进行编码。完成这一步后,我们可以继续准备数据集并开发用于文本分类的 transformer 模型。
class TokenAndPositionEmbedding(Layer):
def __init__(self, maxlen, vocab_size, embed_dim):
super(TokenAndPositionEmbedding, self).__init__()
self.token_emb = Embedding(input_dim=vocab_size, output_dim=embed_dim)
self.pos_emb = Embedding(input_dim=maxlen, output_dim=embed_dim)
def call(self, x):
maxlen = tf.shape(x)[-1]
positions = tf.range(start=0, limit=maxlen, delta=1)
positions = self.pos_emb(positions)
x = self.token_emb(x)
return x + positions
准备数据:
对于这个特定的任务,我们将参考 TensorFlow 和 Keras 提供的 IMDB 数据集。数据集总共包含 50,000 条评论,从中我们将数据分成 25000 个训练序列和 25000 个测试序列。它还包含平均分配的 50%正面评价和 50%负面评价。在预处理步骤中,我们的目标是将这些单词中的每一个都处理成一组整数,这样当我们继续构建 transformers 架构来验证所需结果时,就可以使用它们了。
vocab_size = 20000 # Only consider the top 20k words
maxlen = 200 # Only consider the first 200 words of each movie review
(x_train, y_train), (x_val, y_val) = imdb.load_data(num_words=vocab_size)
print(len(x_train), "Training sequences")
print(len(x_val), "Validation sequences")
在下一个代码片段中,我们将查看分配给前五个测试序列的标签。这些标签给我们一种直觉,告诉我们从我们正在看的数据中可以期待什么。在本节的后面部分,我们将对前五个数据元素进行预测,以检查我们的模型在这些数据集上的表现有多准确。
y_val[:5]
输出:
array([0, 1, 1, 0, 1], dtype=int64)
在查看了前五个元素的标签后,我们将继续填充训练和验证数据的序列,如下面的代码块所示。一旦这个过程完成,我们将开始开发我们的变压器模型。
x_train = tf.keras.preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
x_val = tf.keras.preprocessing.sequence.pad_sequences(x_val, maxlen=maxlen)
开发模型:
开发人员可以通过几种方式来实现文本分类任务的转换器模型。通常的做法是使用单独的编码器和解码器类来分别执行这些操作。在本节中,我们将利用一个相当简单的方法来开发我们的模型,并相应地将其用于文本分类任务。我们将声明每个令牌的嵌入维度、要使用的注意力头的数量以及转换器中前馈网络的层的大小。然后,在我们之前创建的 transformer 块和位置嵌入类的帮助下,我们将开发这个模型。
值得注意的是,我们同时使用了顺序 API 模型和功能 API 模型,这使得我们能够对模型架构进行更有效的控制。我们将给出一个包含句子向量的输入,为此我们创建一个嵌入,并通过一个 transformer 块传递它。最后,我们有一个全局平均池层、一个漏失层和一个密集层来返回句子可能性的概率。我们可以使用 numpy 中的 Argmax 函数来获得正确的结果。下面是开发模型的代码块。
embed_dim = 32 # Embedding size for each token
num_heads = 2 # Number of attention heads
ff_dim = 32 # Hidden layer size in feed forward network inside transformer
inputs = Input(shape=(maxlen,))
embedding_layer = TokenAndPositionEmbedding(maxlen, vocab_size, embed_dim)
x = embedding_layer(inputs)
transformer_block = TransformerBlock(embed_dim, num_heads, ff_dim)
x = transformer_block(x)
x = GlobalAveragePooling1D()(x)
x = Dropout(0.1)(x)
x = Dense(20, activation="relu")(x)
x = Dropout(0.1)(x)
outputs = Dense(2, activation="softmax")(x)
model = Model(inputs=inputs, outputs=outputs)
编译和拟合模型:
在接下来的步骤中,我们将继续编译我们已经构建的转换器模型。对于编译过程,我们将利用 Adam 优化器、稀疏分类交叉熵损失函数,并且还分配要相应计算的准确性度量。然后,我们将继续拟合模型,并在几个时期内对其进行训练。代码片段显示了这些操作是如何执行的。
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
history = model.fit(x_train, y_train,
batch_size=64, epochs=2,
validation_data=(x_val, y_val)
)
一旦执行了训练程序,我们就可以保存在拟合过程中计算的权重。这样做的过程如下所述。
model.save_weights("predict_class.h5")
评估模型:
虽然我们对模型在拟合过程中的训练情况有一个大概的了解,但评估模型并分析模型在测试数据上的表现仍然是必不可少的。因此,我们将评估测试值及其标签以获得结果。该模型将对测试数据进行某些预测,以预测相应的标签,并与原始标签进行比较。然后,我们将收到与模型精度相对应的最终值。
results = model.evaluate(x_val, y_val, verbose=2)
for name, value in zip(model.metrics_names, results):
print("%s: %.3f" % (name, value))
如果我们还记得,在本节的前一部分,我们已经打印了前五个测试标签的值。我们可以在 predict 函数的帮助下,通过使用我们训练好的模型来进行相应的预测。下面是一个截图,代表了我能够从我训练的模型中预测的结果。
Screenshot by author
从上面的截图中我们可以注意到,该模型能够很好地执行,预测适当的句子序列所代表的各个类别。transformer 模型能够很好地执行文本分类任务,因为我们能够在大多数预测中获得预期的结果。然而,仍有改进的空间,观众可以尝试变压器架构的多种变化,以获得最佳效果。建议您尝试各种变化,以获得最合适的结果。
带 TensorFlow 集线器的变压器模型:
在继续本文的这一部分之前,我建议从零开始构建 transformers,在这一部分,我们将使用 TensorFlow-Hub 执行相同的任务。有了 TensorFlow-Hub,构建我们自己的架构背后的大部分复杂性都被消除了,因为我们可以利用预先训练的模型来实现预期的结果。我们可以将此作为使用定制训练模型改进我们的结果的序言。让我们从文本分类转换器的 TensorFlow-Hub 实现开始。
导入基本库:
对于使用 transformers 的文本分类项目的 TensorFlow-Hub 实施,我们的主要库需求是 TensorFlow 深度学习框架以及包含我们预训练模型的 Hub 平台。利用 IMDB 数据集的另一种方法是利用 TensorFlow 数据集模块来加载数据。然后,我们将利用顺序类型建模在导入的模型之上添加几个层。查看下面的代码片段,了解所有必需的导入。
import tensorflow as tf
import tensorflow_hub as hub
import tensorflow_datasets as tfds
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
import numpy as np
import os
准备数据:
在下一步中,我们将通过加载 IMDB reviews,使用之前导入的库来准备数据集。我们可以查看数据集,以了解用于特定任务的数据类型。我们将相应地将数据分为训练集、验证集和测试集,并通过打印其中包含的一个句子来查看提供的关键评论,从而查看数据示例。
train_data, validation_data, test_data = tfds.load(
name="imdb_reviews",
split=('train[:60%]', 'train[60%:]', 'test'),
as_supervised=True)
train_examples_batch, train_labels_batch = next(iter(train_data.batch(10)))
train_examples_batch[:1]
<tf.Tensor: shape=(1,), dtype=string, numpy=
array([b"This was an absolutely terrible movie. Don't be lured in by Christopher Walken or Michael Ironside. Both are great actors, but this must simply be their worst role in history. Even their great acting could not redeem this movie's ridiculous storyline. This movie is an early nineties US propaganda piece. The most pathetic scenes were those when the Columbian rebels were making their cases for revolutions. Maria Conchita Alonso appeared phony, and her pseudo-love affair with Walken was nothing but a pathetic emotional plug in a movie that was devoid of any real meaning. I am disappointed that there are movies like this, ruining actor's like Christopher Walken's good name. I could barely sit through it."],
dtype=object)>
构建模型:
我们将加载嵌入,这是一个预先训练好的模型,存储在下面代码块中指定的 TensorFlow-Hub 位置。然后,我们将添加额外的层,并将参数设置为可训练的。
然后,我们将实例化我们的顺序模型,并在顶部添加我们的自定义层以及几个完全连接的层。最终的密集层应该只有一个节点,因为所做的预测是文本分类,其中我们只有两个结果,即正面或负面评论,其中必须选择其中之一。我们还可以查看模型摘要以了解整体模型构建,它包含预构建的模型以及我们添加的几个密集层。
embedding = "https://tfhub.dev/google/nnlm-en-dim50/2"
hub_layer = hub.KerasLayer(embedding, input_shape=[],
dtype=tf.string, trainable=True)
print(hub_layer(train_examples_batch[:3]))
model = Sequential()
model.add(hub_layer)
model.add(Dense(16, activation='relu'))
model.add(Dense(1))
model.summary()
输出:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
keras_layer (KerasLayer) (None, 50) 48190600
_________________________________________________________________
dense (Dense) (None, 16) 816
_________________________________________________________________
dense_1 (Dense) (None, 1) 17
=================================================================
Total params: 48,191,433
Trainable params: 48,191,433
Non-trainable params: 0
_________________________________________________________________
编译和训练模型:
一旦我们构建了我们想要的模型,我们就可以对它进行编译和相应的训练。对于编译,我们可以利用二进制交叉熵损失来获得 0 或 1 结果。我们将使用 Adam 优化器,并分配准确性指标。最后,我们将开始训练总共五个时期的模型。观众可以选择改变这些参数中的一些参数,并监视训练过程更多的时期,以获得更好的结果。
model.compile(optimizer='adam',
loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
metrics=['accuracy'])
history = model.fit(train_data.shuffle(10000).batch(512),
epochs=5,
validation_data=validation_data.batch(512),
verbose=1)
Epoch 5/5
30/30 [==============================] - 9s 310ms/step - loss: 0.1990 - accuracy: 0.9357 - val_loss: 0.3150 - val_accuracy: 0.8627
运行 Transformer 模型后我得到的结果如上所示。现在,让我们进行评估,以了解 TensorFlow-Hub 预训练模型在五个时期的性能。
评估模型:
现在,我们已经使用 TensorFlow-Hub 中可用的预训练权重完成了针对文本分类的 transformer 模型的训练,总共训练了五个时期,我们可以继续评估它在测试数据上的性能,以查看我们的模型表现如何。我们可以通过下面提供的代码片段来实现。
results = model.evaluate(test_data.batch(512), verbose=2)
for name, value in zip(model.metrics_names, results):
print("%s: %.3f" % (name, value))
49/49 - 7s - loss: 0.3277 - accuracy: 0.8464
loss: 0.328
accuracy: 0.846
为了获得更好的结果,我们可以选择运行模型更多的时期,在尝试许多其他修改中,看看我们是否可以创建一个将给出最佳结果的训练运行。从零开始构建的变压器架构将允许用户以更大的灵活性和控制来开发具有各种参数变化的期望模型,以获得最佳结果。实验和测试是找出哪些更改对改进构建的模型最有意义的最佳方式。
结论:
Photo by Samule Sun / Unsplash
自然语言处理是那些至关重要的前沿发展领域之一,另外还有计算机视觉和图像处理,在这些领域有大量的研究正在进行。深度学习和神经网络模型在处理与机器翻译、文本分类、语音到文本转换等众多任务中的自然语言处理相关的问题时,显示出了更高的成功率。随着深度学习领域的不断进步,解决这样一个项目变得更加合理,有效地产生良好的进展,并取得相当不错的结果。
在本文中,我们探索了自然语言处理在文本分类中的众多应用之一。我们理解了文本分类的意义和这种操作的重要性。然后,我们开始对 transformer 神经网络及其在 NLP 领域的巨大生产力有了直观的了解,这是因为它在完成大量项目方面的多功能性。最后,在接下来的部分中,我们构建了 transformer 架构来执行文本分类任务。在第一个项目中,我们讨论了从零开始构建变压器,而在第二个项目中,我们学习了如何利用 TensorFlow-Hub 构建这些变压器来解决相同的任务。
在未来的文章中,我们将探索与自然语言处理相关的多个其他项目以及深度神经网络解决这些问题的能力,包括 BERT 中 Transformers 体系结构的扩展。我们还将从头开始研究其他有趣的神经网络项目和有条件的 GANs。在那之前,祝你在自己的项目中工作愉快!
转置卷积解释了上采样图像
当使用深度神经网络(DNNs)进行图像处理时,卷积神经网络无需介绍。CNN 提供了一种更现实的方法来从图像中提取和学习特征,同时有助于降低图像维度。但是如果我们想要相反的结果呢?
假设您想要执行一些图像到图像的映射,比如图像或实例分割,或者超分辨率。对于语义分割的情况,您将从一个输入图像开始,其中每个像素都应该有一个相应的类标签。现在,让我们假设您试图用一个 CNN 后面跟着一个全连接层(在大多数图像分类模型中使用)来解决这个问题。你会遇到几个问题:
- 大量的参数。
- 丢失图像的空间特征。
假设输入图像的大小为 128×128×3 (128 像素的高度和宽度以及 3 个通道— RGB)。然后你通过几层 CNN 输出形状是 44×64。这意味着现在我们在第一个隐藏层中有 1024 个神经元。现在尝试将其上采样 32 倍。即使您将输出层放在该层之后,输出层的大小也将是 32,768。为了解决这个问题,我们使用了一种叫做转置卷积的方法,这种方法可以被认为是与普通卷积相反的。这是通过维护连接模式来实现的。
假设我们将卷积应用于一个 5×5×1 的图像,其内核为 3×3,步长为 2×2,填充有效。
Convolution Operation
正如您在上面的图像中看到的,输出将是一个 2×2 的图像。您也可以使用下面的公式来计算卷积运算的输出大小:
卷积输出大小= 1 +(输入大小-滤波器大小+ 2 *填充)/步幅
现在,假设您想将其上采样到与输入图像相同的维度。您将使用与卷积相同的参数,并在下采样之前首先计算图像的大小。您可以通过将上面的公式从输出图像大小更改为输入图像(在高度或宽度上)来实现这一点。有效填充表示图像周围没有填充。相同的填充意味着我们在所有边上添加了过滤器大小/2 (底值)的填充。
输出维度:
转置卷积输出大小=(输入大小- 1) *步长+滤波器大小- 2 *填充+输出填充
所以现在你必须在这个大小的矩阵中填充值。现在到了棘手的部分:如何获得与输入相同的值?答案是不能。那么你现在要怎么填充这个转置的复杂矩阵呢?遵循以下步骤:
- 你需要一个(过滤器大小*过滤器大小)的过滤器,将内核的所有值与图像的第一个值相乘。
- 然后把这个过滤器放在转置的 Conv 矩阵的左上角。
- 将第二个像素与滤波器相乘,并将结果放入具有指定步长的转置 Conv 矩阵中。
- 如果有任何重叠的值,则将这些值相加。
- 重复这个过程,直到矩阵被填满,这就是结果。
为了更好地理解,我们如何将结果可视化?让我们用一种简单的方法来想象结果。假设您有下面的 4×4 图像作为输入。
Output Of Convolution
假设我们在几个卷积层之后得到这个 4×4 的图像。现在我们将应用转置卷积。为了更好地理解转置卷积,我们假设滤波器中的所有值都是透明值,这意味着当我们将其相乘时,它不会改变颜色。这将有助于我们在与颜色的比较中理解操作;在这里,我们认为向黑色添加任何颜色都会产生黑色,等量混合 RGB 颜色会产生白色。现在,让我们在上面的图像中尝试以下参数:
输入: 4x4 | 滤波器大小: 3x3 | 步长: 4x4 | 填充: 0 | 输出填充: 1
我们来看看转置卷积运算后的输出大小会是多少。
转置卷积输出大小=(4-1) * 4 + 3 - 2 * 0 + 1 = 16
Output with non-overlapping filters
这里发生的情况是,每个单像素乘以一个 3x3 滤波器,形成一个 3x3 块,然后放入输出矩阵。由于我们的步幅为 4,中间的值将为零,这在图像中可以被视为黑色条纹。我们在输出填充中添加了 1,这将有助于获得大小均匀的输出,以便与下一个输出进行比较。
输入: 4x4 | 滤波器大小: 4x4 | 步长: 4x4 | 填充: 0 | 输出填充: 0
我们来看看转置卷积运算后的输出大小会是多少。
转置卷积输出大小=(4-1) * 4 + 4 - 2 * 0 + 0 = 16
Output with overlapped filters
正如左边的结果所示,你可以清楚地看到更多的颜色。这是重叠单元格相加的结果。第一个 4×4 单元将用红色(255,0,0)填充,接下来的 4×4 单元用绿色(0,255,0)填充,得到的单元是(255,255,0),即黄色。其他细胞也是如此。你可以看到重叠的细胞有不同的颜色。虽然与白色重叠的单元格将始终是白色的(因为所有大于 255 的值都被归一化为白色的 255),但与黑色重叠的单元格将会扩展(因为黑色是 0,添加任何值都是相同的)。
要将转置卷积图层添加到 Keras 模型中,可以使用 TF . Keras . layers . conv 2d transpose 图层。作为顺序模型的一部分。
TF . keras . layers . conv 2d transpose(
filters _ depth,filter_size,stamps =(1,1),padding='valid ',output_padding=0)
转置卷积用于许多最新的 CNN。进行图像到图像转换或生成的神经网络使用转置卷积。
现在我们知道如何使用转置卷积对图像进行上采样。当你训练一个神经网络时,我们需要计算出转置卷积层的滤波器中的值,就像 CNN 一样。这就是我们的朋友反向传播来帮忙的地方。我希望你喜欢这篇文章。
三重注意解释(WACV 2021)
在计算机视觉领域,注意力机制已经成为一个家喻户晓的名字。无论是生成建模、图像分类、对象检测还是任何下游视觉任务,结合定制的或通用的注意机制对于为手边的任务制定成功的处方已经变得势在必行。
最近注意力机制的新颖设计是基于基本的挤压和激发模块。其中包括全球上下文网络、卷积块关注模块、瓶颈关注模块、高效信道关注等等。
在本文中,我们将介绍一种主要受 CBAM 启发的通道和空间注意力的新结构形式,称为三联注意力(在 WACV 2021 上接受)。在讨论三联注意力背后的基本直觉之前,我们将首先对 CBAM 的通道和空间注意力做一个快速的概述。此外,我们将查看本文中给出的结果,并提供模块的 PyTorch 代码,然后通过触及一些缺点来结束我们的讨论。
目录
- 抽象概述
- 通道和空间注意
- 很晚了
- CBAM
- 当前注意机制的缺点
- 跨维度交互作用
- 三重注意
- PyTorch Code
- 结果
- 图像分类
- MS-COCO 上的目标检测
- grabcad
- 缺点
- 参考
抽象概述
我们的三元组注意力模块旨在捕捉跨维度交互,因此与上述方法相比,能够以合理的可忽略的计算开销提供显著的性能增益,在上述方法中,它们都没有考虑到跨维度交互,同时允许某种形式的维度减少,这对于捕捉跨通道交互是不必要的。
通道和空间注意
注意:这是非常可取的,通过我的计算机视觉注意机制系列的其他职位,为这个主题有一个良好的基础。链接此处。
注意力机制是一个简单而直观的想法,来源于人类的视觉感知。表征学习背后的基本思想是从输入中发现或提取区别特征,该区别特征将特定对象与不同类型或类别的对象区分开来。在人类视觉感知水平上,这可以与人类确定唯一描述特定对象的特征相关联。例如,眼睛、鼻子、嘴唇和耳朵是描述性特征,表明聚焦的对象是人脸。
在计算机视觉中使用的深度神经网络中,注意机制通常涉及通道或空间注意(或两者兼有)。简而言之,通道注意本质上用于在张量中加权每个特征图/通道,而空间注意通过加权单个特征图中的每个像素来提供每个特征图级别的上下文。让我们来看看这种形式的注意机制的两个突出的例子。
很晚了
挤压和激励网络 (SENets)有一个通道注意机制,该机制主要由三部分组成:挤压模块、激励模块和缩放模块。
挤压块负责使用全局平均池(GAP)将输入特征映射\((C \乘以 H \乘以 W)\)减少到单个像素,同时保持通道数量不变\((C \乘以 1 \乘以 1)\)。然后,压缩的张量被传递到激励块,这是一个多层感知器(MLP)瓶颈,负责学习通道注意力权重。最后,这些奇异权重通过 sigmoid 激活,然后按元素乘以非调制输入张量中的相应通道。
CBAM
卷积块注意模块(CBAM)从 SENet 和 SCA-CNN 中获得灵感,提出了一种新颖有效的注意机制,该机制连续(按此顺序)结合了通道和空间注意的思想。CBAM 的频道关注模块与 SENet 的相同。唯一的警告是,它不仅通过 GAP 分解输入张量,还通过 GMP(全局最大池)进行同样的操作,并在通过共享 MLP 瓶颈之前聚合两者。类似地,在空间注意模块中,它将输入张量分解为两个通道,这两个通道是所有特征图的最大汇集和平均汇集特征表示。这个双通道张量通过卷积层,卷积层将其简化为单通道。因此,该单通道张量在与来自通道注意模块的输出张量的每个通道按元素相乘之前通过 sigmoid 激活。
当前注意机制的缺点
尽管这些注意力机制展示了令人难以置信的性能提升,但它们并非没有缺陷。这些包括(但不限于)以下内容:
- 降维 -大多数频道关注机制涉及一种 MLP 瓶颈状结构,用于控制该结构的参数复杂性,但反过来也会导致显著的信息丢失。
- 缺乏空间注意——许多新颖的注意机制被提出来,忽略了拥有空间注意机制的需要和优点,只配备了通道注意机制,从而降低了整个注意机制的整体效能和表达能力。
- 空间和通道注意之间不相交 -通常,采用专用空间注意模块和通道注意模块的注意机制使它们保持分离和不相交,因此不允许两个模块之间的交互,这不是最佳的。
- 缺乏跨渠道互动 -许多关注机制在计算关注权重时不强调允许渠道与其他渠道互动,从而减少了信息传播。
- 效率 -大多数注意力机制以模型参数和触发器的形式增加了大量的额外计算,导致更大和更慢的架构。
跨维度交互作用
为了解决上述缺点,三重注意力提出了一种新颖且直观的计算注意力权重的方法,称为跨维度交互。跨维度交互是一个简单的想法,允许模块计算每个维度相对于其他维度的注意力权重,即$ C \乘以 W$ 、$ C \乘以 H\(、以及\) H \乘以 W$。这基本上允许它在单个模块中计算空间和通道注意力。通过简单地置换输入张量维度来实现跨维度交互,然后进行简单的残差变换来生成注意力权重。因此,这允许注意力机制对输入张量中的每个维度形成强依赖性,这反过来对于在特征图上的注意力区域上提供更紧密的界限是至关重要的。让我们来理解三联注意力是如何实现跨维度互动的。
三重注意
顾名思义,三联注意力是一个三分支结构,其中每个分支负责计算和应用输入张量的三个维度中的两个维度的注意力权重。顶部的两个分支计算针对两个空间维度中的每一个维度的通道注意力权重,而底部的分支负责计算与 CBAM 中呈现的相同的简单空间注意力。
在上面的两个分支中,输入张量首先被旋转以改变维度,之后它经历第零个池(Z-Pool)操作符,该操作符通过连接该维度上张量的平均池化和最大池化特征将第零个维度减少到两个。该结果张量进一步通过单个空间卷积层,这将零维进一步减少到一维,之后该输出通过 sigmoid 激活。然后,最终输出按元素与置换输入相乘,然后旋转回原始维度(与输入的维度相同)。在对所有三个分支完成这一操作后,通过简单的平均将三个结果张量聚合,这形成了三重注意力的输出。
因此,三重注意结合了跨维度交互,这消除了大多数注意机制结构中普遍存在的信息瓶颈和维度减少。随后,由于三重注意机制不包含 MLP 结构,而是包含三个卷积层,因此该模块的参数复杂度非常低,如下表所示。
注意机制 | 因素 | 开销(ResNet-50) |
---|---|---|
如果 | 2C² /r | 2.514 米 |
CBAM | 2C² /r + 2k² | 2.532 米 |
嘣 | C/r(3C + 2k² C/r + 1) | 0.358 米 |
车底距地高(Ground Clearance) | 2C² /r + C | 2.548 米 |
三重注意 | 6k² | 0.0048 米 |
PyTorch Code
import torch
import math
import torch.nn as nn
import torch.nn.functional as F
class BasicConv(nn.Module):
def __init__(self, in_planes, out_planes, kernel_size, stride=1, padding=0, dilation=1, groups=1, relu=True, bn=True, bias=False):
super(BasicConv, self).__init__()
self.out_channels = out_planes
self.conv = nn.Conv2d(in_planes, out_planes, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, groups=groups, bias=bias)
self.bn = nn.BatchNorm2d(out_planes,eps=1e-5, momentum=0.01, affine=True) if bn else None
self.relu = nn.ReLU() if relu else None
def forward(self, x):
x = self.conv(x)
if self.bn is not None:
x = self.bn(x)
if self.relu is not None:
x = self.relu(x)
return x
class ZPool(nn.Module):
def forward(self, x):
return torch.cat( (torch.max(x,1)[0].unsqueeze(1), torch.mean(x,1).unsqueeze(1)), dim=1 )
class AttentionGate(nn.Module):
def __init__(self):
super(AttentionGate, self).__init__()
kernel_size = 7
self.compress = ZPool()
self.conv = BasicConv(2, 1, kernel_size, stride=1, padding=(kernel_size-1) // 2, relu=False)
def forward(self, x):
x_compress = self.compress(x)
x_out = self.conv(x_compress)
scale = torch.sigmoid_(x_out)
return x * scale
class TripletAttention(nn.Module):
def __init__(self, no_spatial=False):
super(TripletAttention, self).__init__()
self.cw = AttentionGate()
self.hc = AttentionGate()
self.no_spatial=no_spatial
if not no_spatial:
self.hw = AttentionGate()
def forward(self, x):
x_perm1 = x.permute(0,2,1,3).contiguous()
x_out1 = self.cw(x_perm1)
x_out11 = x_out1.permute(0,2,1,3).contiguous()
x_perm2 = x.permute(0,3,2,1).contiguous()
x_out2 = self.hc(x_perm2)
x_out21 = x_out2.permute(0,3,2,1).contiguous()
if not self.no_spatial:
x_out = self.hw(x)
x_out = 1/3 * (x_out + x_out11 + x_out21)
else:
x_out = 1/2 * (x_out11 + x_out21)
return x_out
结果
图像分类
MS-COCO 上的目标检测
grabcad
如上所示,三重注意机制可以在不同和困难的任务上提供非常可观的结果,如 MS-COCO 上的 ImageNet 分类和对象检测,同时在参数复杂性方面非常轻量级。更重要的是,它在 GradCAM 和 GradCAM++图上提供了更好、更紧密的 ROI 界限,这对注意力机制的可解释性极其重要。由于跨维度的相互作用,三重注意可以发现更小范围的精细对象。
缺点
- 尽管该论文介绍了一种极其轻量和高效的注意机制,但由此导致的 FLOPs 增加仍然很高。
- 通过简单平均来聚集三重注意力的三个分支中的每一个分支的注意力权重的方式可能是次优的,并且可以被显著地改进。
参考
- 旋转出席:卷积三联体注意模块
- 正式实施三联关注
- 挤压和激励网络
- GCNet:非局域网络与压缩激发网络相遇并超越
- CBAM:卷积块注意模块
- BAM:瓶颈关注模块
- ECA-Net:深度卷积神经网络的高效信道关注
Tunebat 和 Specterr 将机器学习带到了 DJ 展台和舞池
原文:https://blog.paperspace.com/tunebat-and-specterr-bring-machine-learning-to-the-dj/
我们一直有兴趣了解更多的事情之一是 ML 在音乐行业中的使用。我们最近发布了关于使用 LSTMs 生成音乐和使用深度学习进行音频分类的教程,但是那些为音频专业人员开发基于 ML 特性的应用程序的公司呢?
这就是奥利弗·雷兹尼克发挥作用的地方。Oliver 和他的团队花了几年时间为音乐家、DJ 和制作人开发了一对 ML 驱动的应用程序,名为 Tunebat 和 Specterr。我们很幸运地与奥利弗坐在一起,一起探讨其中的一些话题。
让我们一起深入 ML 辅助专业音频工具的世界吧!
Tunebat makes it easy to identify the key and beats per minute of any song.
paper space:你现在正在同时构建两个应用程序,对吗?你能告诉我们关于 Tunebat 和 Specterr 的事情吗?
雷兹尼克 :是的没错。 Tunebat 是一款网络应用,拥有许多不同的音乐相关功能,可能最出名的是让人们可以轻松找到歌曲的基调和 BPM(每分钟节拍数)。您可以浏览音乐数据库或上传音频文件,通过键& BPM finder 进行分析。此外,Tunebat 还可以帮助用户进行音乐发现、人声去除和自动化母带制作。
Specterr 是一家在线音乐视频制作商。用户上传音频文件,并使用基于网络的编辑器创建定制的视频。导出后,视频可以下载或发布到社交媒体上,以帮助宣传该艺术家。Specterr 专门从事音乐可视化器和抒情视频创作。
Paperspace :我们对你如何开始这些想法很感兴趣。你是如何以及何时知道自己发现了对音乐产业有用的东西的?你的 MVP 是什么样的?
雷兹尼克 :最初我对学习如何做 DJ 很感兴趣,我想找一些能很好地融合在一起的歌曲。DJ 通常喜欢将具有相似 BPM 和调的曲目混合,以实现平滑过渡。当我试着用谷歌搜索歌曲的基调和 BPM 时,什么都没有出现。因此,我暂时搁置了我的 DJ 梦想,创建了 Tunebat,作为一种找到这些信息的简单方法。
同样的数据也可以用来为 DJ 混音提供更好的音乐发现。MVP 是当今 Tunebat 的数据库部分的一个更加基础和丑陋的版本。在首次发布后不久,我在 DJ 留言板上发布了一个链接,流量开始涌入。用户喜欢它,并不断回来,所以我知道 Tunebat 满足了需求。
斯佩克特这个想法又一次来自我的个人经历。作为一名业余 EDM 制作人,我需要音乐视频,以便在视频平台上发布我的音乐。现有的音乐视频工具要么使用起来太单调乏味,无法实现正确的效果,要么用户体验很差。因此,我建立了一个视频渲染引擎,并开始接受艺术家的订单,我会用引擎为他们手动创建视频。
这对任何人来说都不是一个理想的过程,但是需求仍然存在。因此,Specterr 被创建为音乐视频定制和呈现的自助服务平台。
**
“Paperspace 是将配备 GPU 的机器整合到我们的云基础设施中的理想平台。Tunebat 人声去除器依靠 GPU 来处理具有复杂模型的音频文件。另一方面,Specterr 依靠 GPU 来加速视频渲染过程,因此可以及时生成视频。Paperspace 支持这两种使用情形。”
奥利弗·雷兹尼克,Tunebat 和 Specterr 的创始人**
paper space:tune bat 能够将一个音轨解析成几个组成部分,包括每分钟节拍(BPM)和调号(或者 Camelot Wheel 上的位置)。它还能够从音轨中删除人声。你能告诉我们一些这些功能是如何工作的吗?
雷兹尼克 :确定。对于音频分析仪,我们与巴塞罗那 Pompeu Fabra 大学的音乐技术团队密切合作。
我们在 Tunebat 上实现了他们的 essentia.js 库。这允许我们在客户端使用 Essentia 项目的算法和模型来确定音频特征。这里的主要 ML 用例是用于情绪分析,这在上下文中意味着确定一首曲目的感知能量、可跳舞性和快乐度。
Essentia.js 非常酷,因为它能够完全在网络浏览器中使用模型有效地分析这些音频文件。
人声去除器还使用模型来识别音轨的人声和乐器部分。然后它将这些部分分割成单独的文件。这个过程是在云中使用 PyTorch 在支持 GPU 的 Paperspace 虚拟机上完成的。
:Specterr 能够从一首歌曲中生成一些非常有趣的音乐感知视觉效果。你能告诉我们它是如何工作的吗?
使用 Specterr,重要的是视觉效果要与音频相得益彰,而不是相互竞争。可视化工具生成响应并强调所提交的音乐文件的特征的动画。
Specterr 使用 FFT(快速傅立叶变换)分析文件,FFT 收集振幅和频率数据。该数据然后被用于生成音频可视化。
我们已经花了大量的时间来弄清楚如何处理这些数据,以创建美观的视觉效果。我们的引擎使用各种内部算法来制作各种视频元素的动画。
当视频与音频同步时,您会看到随着音乐起舞的视觉效果。
***
Specterr analyzes a song file for amplitude and frequency and then generates dramatic visualizations to accompany the music***
Paperspace :前进的路线图会是什么样子?你如何决定下一步添加什么特性?在采纳过程中,你从用户那里寻找什么信号?
雷兹尼克 :我们一直致力于改进和增加新功能。在评估一个功能的影响时,我们主要看使用量和保持率。人声去除器已经成为 Tunebat 上最常用的功能之一,所以我们打算很快扩展它,增加新的去除选项,如鼓、贝斯等。
稍后在我们的路线图中,我们希望通过 Tunebat 和 Specterr 的 API 为开发人员提供更好的支持。此外,我们希望通过一个名为MusicStats.com的项目,让音乐听众了解他们的聆听习惯。
Paperspace :你使用 Paperspace 已经有一段时间了,你能告诉我们 Paperspace 为你解决了什么问题吗?你能告诉我们关于 GPU 计算在构建 Tunebat & Specterr 中的作用吗?
:paper space 是将配备 GPU 的机器整合到我们的云基础设施中的理想平台。Tunebat 人声去除器依靠 GPU 来处理具有复杂模型的音频文件。另一方面,Specterr 依靠 GPU 来加速视频渲染过程,因此可以及时生成视频。Paperspace 支持这两种用例。
除此之外,作为一名开发人员,它真的很容易使用。Paperspace 有许多功能,如预配置的模板和设计良好的控制台应用程序,可以加快我们的许多开发过程。
Paperspace :我们的读者开始使用 Tunebat 或 Specterr 的最佳方式是什么?如果他们想了解更多关于你的产品的信息,你会推荐他们做什么?
雷兹尼克 :我肯定会推荐从参观 tunebat.com 的或 specterr.com的开始。从那里开始浏览 Tunebat 数据库相当容易。你会发现一些有趣的信息,关于你最喜欢的音乐有多快乐或有活力,你也可以很容易地发现一些新的音乐来听。
在 Specterr 上,你可以直接进入视频编辑器并开始定制。如果您有一些想要使用的音乐,只需上传它,您就可以在编辑时看到实时预览。
新视频教程:创建虚拟机和使用模板
原文:https://blog.paperspace.com/tutorial-how-to-create-a-new-paperspace-machine/
https://www.youtube.com/embed/swXhAI6DF0E
https://www.youtube.com/embed/lrnhHl2j_Gg
Paperspace 首席执行官 Dillon Erb 加入 TWIML AI 播客
原文:https://blog.paperspace.com/twiml-ai-paperspace-gradient/
很少有人像来自 TWIML【本周机器学习的 Sam Charrington 一样拥有机器学习领域的专业知识。多年来,他一直在用他对机器学习创新者的独一无二的采访来激励我们。
我们很幸运地宣布,Paperspace 首席执行官 Dillon Erb 将于 2020 年 8 月 27 日周四做客山姆的视频播客我们想邀请您参加观看派对并提出问题!
Sam 和 Dillon 将讨论在建立机器学习组织时,强大的软件工程原则的重要性。他们将深入了解 ML 实际应用的成功关键,并在会议期间和会议结束后回答问题。
查看 2020 年 8 月 27 日的首播:
https://www.youtube.com/embed/4OIjxmBhXi0?feature=oembed
如果您想了解更多关于 MLOps 的纸张空间梯度方法,请联系我们的 hello@paperspace.com 或给我们发邮件。
PyTorch 终极指南
随着人工神经网络领域技术进步的兴起,已经有几个用于解决和计算现代深度学习任务的库。在我之前的文章中,我已经详细介绍了其他一些深度学习框架,如 TensorFlow 和 Keras。建议不熟悉这个主题的观众为 TensorFlow 提供以下链接,为 Keras 提供这个特定的链接。在本文中,我们将介绍 PyTorch 中另一个引人注目的深度学习框架,它也被广泛用于执行各种复杂的任务。
PyTorch 自 2016 年 9 月发布以来,由于其编码原型的 Pythonic 风格以及在某些情况下相对更简单的编码方法,一直与 TensorFlow 进行激烈的竞争。我们将在本文中讨论的概念的目录在右边提供。首先,我们将通过一个基本的介绍来习惯 PyTorch。然后我们将着手在虚拟环境中安装 PyTorch 框架,用于构建深度学习项目。我们将理解张量的概念,以及用户可以使用 PyTorch 提供的各种功能进行计算的许多可能的操作。一旦我们对张量运算有了基本的了解,我们将讨论构建 PyTorch 模型的所有基本步骤和必要性。最后简单讨论一下 TensorFlow 和 PyTorch 的区别。
PyTorch 简介:
PyTorch 是深度学习的最佳选择之一,它是一个开源的深度学习框架,由脸书的人工智能研究实验室(FAIR)首次推出和开发。torch 环境库开发的主要目的是构建高效的模型,为特定的任务提供最佳的结果和解决方案。PyTorch 库的应用从机器学习应用扩展到自然语言处理和计算机视觉任务。除了这些用例之外,它们还被用在许多软件结构中。一些例子包括优步的 Pyro,特斯拉自动驾驶仪,拥抱脸变形金刚,PyTorch 闪电和催化剂。
PyTorch 的主要功能是通过使用张量来支持基本的 numpy 运算,这些张量可以用于图形处理单元(GPU)的复杂运算。PyTorch 能够利用张量轻松执行复杂的任务和计算,这要归功于它对 GPU 的支持,这是 PyTorch 的重要特征之一。我们将在本文的后面部分更详细地讨论这些张量运算。除了能够更快地计算运算之外,它还利用了一个自动微分系统,允许其用户以极其简化的方式直接计算神经网络的反向传播值。由于一些流行的 PyTorch 函数,用户不需要进行任何手动计算,而是使用给定的函数来相应地计算所需的导数。
虽然 PyTorch 在像 C++这样的语言中受支持,但是 PyTorch 的主要目的是为用户提供一个坚实的基础和对 Python 编程语言的全面支持。用 PyTorch 编写的大部分代码都可以无缝集成到 Python 中。PyTorch 也与大多数 Python 库结合得很好,比如 numpy。由于大部分代码都是 Python 化的,对于有 Python 经验的新手来说,学习 PyTorch 变得更加容易。它还允许用户更有效地进行调试,从而提高开发人员的工作效率。如前所述,它也有图形支持,因为 CUDA 是 PyTorch 库的编写语言之一。最后,PyTorch 还受到众多云平台的支持,用于类似 TensorFlow 的模型开发和部署。有了 PyTorch 的这个基本介绍,我们就可以继续安装这个深度学习框架的主要步骤了。
PyTorch 的安装程序:
我建议第一步从下载 Anaconda 开发环境开始。Anaconda 是一个发行版,它支持大量的库、编程语言和大量对初学者和专家有用的资料。它适用于所有操作系统平台,如 Windows、macOS 和 Linux。该软件被广泛认为是数据科学爱好者必须拥有的最佳工具之一,以实现任何特定任务的最佳和最理想的结果。您可以从以下链接下载适合您桌面的最新版本。
一旦下载并安装了 Anaconda 发行版,就可以相应地选择编辑器。您的主要选择是 PyCharm、Microsoft Visual Studio Code、Sublime Text、Jupyter Notebooks 以及许多其他软件。出于本文的目的,我建议坚持使用 Jupyter 笔记本。确保您已经完成了 Anaconda 发行版的所有设置。一旦一切准备就绪,请确保使用您喜欢的名称创建一个虚拟环境。这个虚拟环境将包含我们将安装的所有未来安装。下一步是访问 PyTorch 官方网站,根据您的系统设置构建。请跟随此链接到达网站。
到达网站后,根据您的要求设置自定义设置。上图是最适合我的电脑的设置示例。还相应地提供了用于安装 PyTorch 的适当的 Anaconda 命令。激活您的虚拟环境,并在命令提示符下复制粘贴给定的代码。需要注意的是,如果您的系统上没有 GPU,那么请选择 CPU 选项在您的系统上安装 PyTorch。但是,强烈建议您使用 GPU 来加快 PyTorch 模型的计算速度。
下面是我在我的虚拟环境中的命令提示符(或 windows shell)中输入的命令,以安装 PyTorch 深度学习框架。
conda install pytorch torchvision torchaudio cudatoolkit=10.2 -c pytorch
如果您在安装过程中遇到问题,请访问 Stack Overflow 或 GitHub。其中一个对我有用的命令如下:
conda install pytorch torchvision torchaudio cudatoolkit=11.1 -c pytorch -c conda-forge
一旦您能够在您的系统上成功安装 PyTorch GPU 或 CPU 版本,请在您的命令提示符、windows shell 或您选择使用的任何其他工具中检查相同版本的可用性(这可能包括您选择的任何集成开发环境,如 Visual Studio 或 Gradient)。激活您工作的虚拟环境,并输入以下命令。首先,我们将进入 Python 控制台并尝试导入 torch 来检查安装过程中是否没有错误,最后,检查下载的 PyTorch 版本。
(torchy) C:\Users\Admin>python
Python 3.9.5 (default, May 18 2021, 14:42:02) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>> torch .__version__
'1.8.1'
一旦您能够无任何错误地运行以下命令(有时警告是好的,只需研究并尝试修复它们),我们就可以开始构建许多 PyTorch 项目了。
详细了解 PyTorch 张量:
在本节中,我们将描述和研究 PyTorch 入门所需的所有细节。张量在 PyTorch 中的表现和在 TensorFlow 中的表现差不多。在文章的这一部分,我们将学习如何初始化张量,相应地转换它们,执行数学计算,并研究一些其他的基本操作。
1.张量的初始化
学习深度学习的第一个重要步骤是理解张量的过程。张量基本上是 n 维数组,并且是描述与向量空间相关的代数对象集之间的多线性关系的对象。在本节中,我们将了解如何在 PyTorch 中初始化张量,以便进行进一步的操作。
为了开始张量的初始化和任何其他类型的操作,我们将导入 torch 模块并快速验证版本。
# Importing PyTorch and checking its version
import torch
torch.__version__
输出:
'1.8.1'
让我们看看初始化张量的几种方法。在下面的代码块中,我们可以查看一个变量' a ',它存储一个数字列表,而'a _【T3]'存储一个包含相同数字的数值数组。在第一种方法中,我们可以使用 from_array()函数将 numpy 数组转换为 PyTorch 张量。要将一个列表转换成 PyTorch 张量,过程非常简单,只需使用 tensor()函数完成以下操作。代码和结果如下所示。
import numpy as np
a = [1,2,3]
a_ = np.array(a)
print(a_)
# Method-1
b = torch.from_numpy(a_)
print(b)
# Method-2
c = torch.tensor(a)
print(c.dtype)
print(c)
输出:
[1 2 3]
tensor([1, 2, 3], dtype=torch.int32)
torch.int64
tensor([1, 2, 3])
在接下来的几个代码块中,我们将了解 PyTorch 的一些重要方面。首先,设备变量通常用于设置 PyTorch 的计算环境。如果你有 CUDA 的 GPU 支持,所有的操作都会在 GPU 上执行,否则默认为 CPU。您还可以为张量分配一些基本属性,并相应地检查它们,如下面的代码块所示。
# Some must know parameters for tensor() function
device = torch.device('cuda' if torch.cuda.is_available() else cpu)
d = torch.tensor([1,2,3], dtype = torch.float32,
device = device, requires_grad = True)
print(d.shape)
print(d.dtype)
print(d.device)
print(d.requires_grad)
输出:
torch.Size([3])
torch.float32
cuda:0
True
在初始化张量时,你最常执行的三个主要赋值操作是给它们赋值一个特定形状的 0、1 或随机数。初始化张量的应用程序对于管理和声明权重很有用。
torch.zeros(3, 4, dtype=torch.float64)
torch.ones(4, 2, dtype=torch.float64)
torch.rand(3, 3, dtype=torch.float64)
输出:
tensor([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]], dtype=torch.float64)
tensor([[1., 1.],
[1., 1.],
[1., 1.],
[1., 1.]], dtype=torch.float64)
tensor([[0.3741, 0.7208, 0.4353],
[0.7368, 0.9386, 0.9840],
[0.2983, 0.7320, 0.6277]], dtype=torch.float64)
还有许多其他方法可以用 eye、linspace、arrange 等函数初始化张量。您可以随意探索这些选项,并了解如何在 PyTorch 项目中准确地利用这些初始化技术来获得高效的结果。此外,理解某个特定函数何时比其他函数更有用也变得更加重要。随着我们开始从事更多的项目,这个主题将在以后的文章中讨论。
2.张量转换
在这一节中,我们将看一些张量转换操作,你可以在 PyTorch 张量的帮助下执行。一些基本的张量转换包括各种数据类型,如布尔、短、长、半和双精度。代码及其各自的输出如下所示。
a = torch.tensor([0, 1, 2, 3])
# boolean values
print(a.bool())
# Integer type values
print(a.short()) # int16
print(a.long()) # int64
# float type values
print(a.half()) # float16
print(a.double()) # float64
输出:
tensor([False, True, True, True])
tensor([0, 1, 2, 3], dtype=torch.int16)
tensor([0, 1, 2, 3])
tensor([0., 1., 2., 3.], dtype=torch.float16)
tensor([0., 1., 2., 3.], dtype=torch.float64)
另一个简单的转换操作,正如在前面 tensors 部分的初始化中所讨论的,是将 list 转换为 tensor,将 numpy 数组转换为 tensor,反之亦然。下面的应用程序如下面的代码块所示。
# Conversion from numpy array to tensor and vice-versa
import numpy as np
a = [1,2,3]
a_ = np.array(a)
print(a_)
# Numpy to Tensor
b = torch.from_numpy(a_)
print(b)
# Tensor to Numpy
c = b.numpy()
print(c)
输出:
[1 2 3]
tensor([1, 2, 3], dtype=torch.int32)
[1 2 3]
3.张量数学运算
因为张量基本上是 n 维数组,所以我们可以用它们进行许多有用的计算。这些操作包括类似于那些可以在 numpy 数组上执行的数学计算,例如张量的加、减、乘等等。让我们相应地研究一下这些方面,看看如何用 PyTorch 实现它们。
张量的加法:
使用 PyTorch,您可以通过三种不同的方式执行加法操作。首先,用适当的值初始化张量变量,并将数据类型设置为 float。有三种方法可以把这些张量相加。在第一种方法中,您可以借助加号“+”直接添加它们。使用第二种方法,您可以使用 torch 库中的 add 函数来执行分配的张量的添加。
您可以通过添加一个与定义的数组形状相同的空变量来扩展这个步骤,并将输出存储在下面的变量中。最后,你还可以进行加法运算,计算整个张量矩阵中所有元素的和。
a = torch.tensor([1, 2, 3], dtype=torch.float)
b = torch.tensor([7, 8, 9], dtype=torch.float)
# Method-1
print(a + b)
# Method-2
print(torch.add(a, b))
# Method-3
c = torch.zeros(3)
c = torch.add(a, b, out=c)
print(c)
# Cumulative Sum
print(torch.add(a, b).sum())
输出:
tensor([ 8., 10., 12.])
tensor([ 8., 10., 12.])
tensor([ 8., 10., 12.])
tensor(30.)
张量减法:
类似于加法运算,可以进行张量的减法运算。您可以通过元素的顺序或中间顺序找到它们的适当差异。如果你只想知道变量的绝对值,绝对函数就派上用场了。代码和输出如下所示。
a = torch.tensor([1, 2, 3], dtype=torch.float)
b = torch.tensor([7, 8, 9], dtype=torch.float)
# Method-1
print(a + b)
# Method-2
print(torch.subtract(b, a))
# Method-3 (Variation)
c = torch.zeros(3)
c = torch.subtract(a, b, out=c)
print(c)
# Cumulative Sum of differences
torch.subtract(a, b).sum()
#Absolute cumulative Sum of differences
torch.abs(torch.subtract(a, b).sum())
输出:
tensor([ 8., 10., 12.])
tensor([6., 6., 6.])
tensor([-6., -6., -6.])
tensor(-18.)
tensor(18)
张量的乘法:
张量乘法是你能执行的最重要的运算之一。使用声明变量之间的“*”符号或使用 mul()函数,该操作是可计算的。也可以用 PyTorch 张量计算点乘。这个过程可以如下进行。
a = torch.tensor([1, 2, 3], dtype=torch.float)
b = torch.tensor([7, 8, 9], dtype=torch.float)
# Method-1
print(a * b)
# Method-2
print(a.mul(b))
# Calculating the dot product
print(a.dot(b))
输出:
tensor([ 7., 16., 27.])
tensor([ 7., 16., 27.])
tensor(50.)
另一个需要记住的关键计算是 PyTorch 张量执行矩阵乘法的能力。它们可以按如下方式计算。
# Matrix multiplication
# a shape of (m * n) and (n * p) will return a shape of (m * p)
a = torch.tensor([[1, 4, 2],[1, 5, 5]], dtype=torch.float)
b = torch.tensor([[5, 7],[8, 6],[9, 11]], dtype=torch.float)
# 3 ways of performing matrix multiplication
print("Method-1: \n", torch.matmul(a, b))
print("\nMethod-2: \n", torch.mm(a, b))
print("\nMethod-3: \n", a@b)
输出:
Method-1:
tensor([[55., 53.],
[90., 92.]])
Method-2:
tensor([[55., 53.],
[90., 92.]])
Method-3:
tensor([[55., 53.],
[90., 92.]])
张量的划分:
您也可以使用“/”符号或 PyTorch 中的 true_divide 函数来执行除法运算。下面的代码和输出显示了如何相应地计算它们。
a = torch.tensor([1, 2, 3], dtype=torch.float)
b = torch.tensor([7, 8, 9], dtype=torch.float)
# Method-1
print(a / b)
# Method-2
c = torch.true_divide(a, b)
print(c)
# Variation
c = torch.true_divide(b, a)
print(c)
输出:
tensor([0.1429, 0.2500, 0.3333])
tensor([0.1429, 0.2500, 0.3333])
tensor([7., 4., 3.])
用户必须考虑的其他数学运算是就地运算、取幂、变量之间的简单比较以及其他对特定用例有用的类似数学运算。请随意探索各种可能的选择。
4.其他基本张量运算
可以在张量上执行的一些其他操作包括索引相应的标签和从给定的起点到相应的终点分割数组的操作。这些计算如下进行。
a = torch.tensor(np.arange(0,10).reshape(2,5))
print(a)
# Indexing of tensors
print(a[0])
print(a[0][0])
# Tensor slicing
print(a[:, 0:2])
输出:
tensor([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]], dtype=torch.int32)
tensor([0, 1, 2, 3, 4], dtype=torch.int32)
tensor(0, dtype=torch.int32)
tensor([[0, 1],
[5, 6]], dtype=torch.int32)
使用 PyTorch tensors,您可以完成更多工作。如果你有兴趣了解更多,我强烈建议你自己从他们的官方网站查看更多信息。
构建 PyTorch 模型的步骤:
在文章的这一部分,我们将讨论如何借助 PyTorch 深度学习框架构建一个典型的模型架构。使用 PyTorch 构建任何模型的过程中涉及的基本步骤是导入基本库、分析问题的类型、相应地构建模型以解决特定任务、针对一定数量的时期训练模型以实现高精度和低损失,以及最后评估保存的模型。让我们来看看构建这些模型过程中三个最重要步骤的代码片段。
导入库:
创建 PyTorch 模型的一个更重要的步骤是导入合适的库,在这些库的帮助下,我们可以成功地构建我们想要完成的模型。当我们查看关于如何在 PyTorch 的帮助下构建高级项目的后续文章时,我们将理解这些导入的具体细节。
# Importing all the essential libraries
import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
import numpy as np
import matplotlib.pyplot as plt
一旦导入了完成特定任务所需的基本库,下一步就是相应地定义特定问题的所有需求。一旦指定了所需任务的所有基本参数,就可以成功地完成构建模型和训练所创建的模型的过程。
模型的构建:
PyTorch 使用 Pythonic 式的编码方式。这对于学习他们的技术非常有帮助,因为借助这种深度学习框架,理解构建我们的深度学习模型的内在过程是多么简单。为了构建模型,您可以以类的形式定义您想要的模型,然后使用您定义的函数中的函数来创建要执行的所有基本操作。下面提供的代码片段是如何利用 PyTorch 编程构建简单神经网络架构的最佳示例之一。
# Constructing the model
class neural_network(nn.Module):
def __init__(self, input_size, num_classes):
super(neural_network, self).__init__()
self.fc1 = nn.Linear(in_features=input_size, out_features=50)
self.fc2 = nn.Linear(in_features=50, out_features=num_classes)
def forward(self, x):
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
return x
已定义类中的 init 块可以拥有超级继承,以允许父类的所有重要元素都可以被访问。然后我们可以继续定义几个深度学习隐藏层,用 PyTorch 的 nn 库构建。一旦您在该类的 init 部分定义了基本需求,您就可以继续前进到 forward 函数来构建主要模型。通过利用先前定义的隐藏层(或卷积层或任何其他层),您可以继续构建模型的最终结构,并相应地返回它。
训练模型:
完成模型构建的最后一步是训练模型。下面是一个示例代码,它涵盖了针对特定数量的时期运行模型的过程。涉及的主要步骤是设置主要参数,运行前向传播,最后使用最后三个内置命令来完成反向传播过程。
# Train the network
for epoch in range(epochs):
for batch, (data, target) in enumerate(train_loader):
# Obtaining the cuda parameters
data = data.to(device=device)
target = target.to(device=device)
# Reshaping to suit our model
data = data.reshape(data.shape[0], -1)
# Forward propogation
score = model(data)
loss = criterion(score, target)
# Backward propagation
optimizer.zero_grad()
loss.backward()
optimizer.step()
一旦培训过程完成,您可以评估您的模型的性能,一旦您认为它适合特定的任务,我们可以部署这些模型来执行特定的任务。在未来与 PyTorch 相关的文章中,我们将查看更多具体的示例,并涵盖构建我们的模型的详细方法,以使用这一令人惊叹的深度学习框架解决众多任务。现在让我们看一下 TensorFlow 和 PyTorch 之间的简单比较。
TensorFlow 和 PyTorch 的区别:
在我们结束这篇文章之前,对 TensorFlow 和 PyTorch 进行一个快速的比较,以了解它们的相似之处和不同之处,这将会很有意思。这两个库都是非常棒的深度学习框架,多年来已经成功地积累了巨大的人气。它们相互之间提供了激烈的竞争,并且有一些相似的工作模式,因为在这两种情况下,我们都与张量打交道来完成各种各样的任务。
然而,这两个流行的框架之间仍然有很多不同之处。PyTorch 使用更具 Pythonic 风格的编码,更适合新开发人员或希望适应学习深度学习神经网络的人。由于一些复杂和不直观的代码结构,对于旨在开发深度学习模型的新程序员来说,TensorFlow 有时会非常复杂。
PyTorch 的主要好处之一是计算的动态状态,而 TensorFlow 将状态用于计算图形。PyTorch 由于其动态和快速的训练方法而与研究项目高度兼容。然而,它主要缺乏在部门在执行可视化领域。张量板是大多数可视化过程的首选方法。要了解更多关于 PyTorch 和 TensorFlow 的分析,我建议从下面的链接查看这篇文章。
结论:
Photo by redcharlie / Unsplash
PyTorch 是现代深度学习中最好的深度学习框架之一。它广泛用于开发解决各种应用的神经网络,是 TensorFlow 的一个很好的替代方案。它有许多有益的特性,例如支持动态计算图和允许数据并行,这意味着它可以相应地在众多 CPU 或 GPU 之间分配和划分工作。它也是一个非常简单的库,允许用户访问更好的调试功能、更高的透明度以及更高效地轻松编码的能力。
在本文中,我们讨论了与 PyTorch 基础知识相关的大部分主题。我们简要讨论了 PyTorch 深度学习框架为什么对大多数深度学习计算如此有效以及它越来越受欢迎的主要方面。首先,我们研究了 PyTorch 的安装过程。然后,我们用 PyTorch 理解了张量的大部分基本运算,包括张量转换,数学运算,以及其他基本的张量运算。然后,我们讨论了构建 PyTorch 模型的步骤。最后,我们总结了 TensorFlow 和 PyTorch 之间的差异。
在以后的文章中,我们将使用 PyTorch 从头开始开发更多的项目。在那之前,继续学习和编写新的东西吧!
对具有项目结构的自动编码器的概念性理解
生成型网络正以巨大的速度接管人工智能(AI)的世界。他们能够创造出肉眼几乎无法分辨的新实体,并将它们分类为真或假。在以前的文章中,我们已经在生成对抗网络(GANs)中讨论了这些生成类型网络中的一类。我们还研究了许多不同类型的 GAN 架构,用于创建许多不同类型的任务,如深度卷积 GAN(dcgan)、超分辨率图像、人脸生成等等。如果你对这些项目感兴趣,我推荐你去 Paperspace 博客的 GANs 版块看看。然而,在本文中,我们主要关注的是另一种类型的生成型网络中的 自动编码器。
自动编码器在深度学习领域是一个蓬勃发展的前景,类似于其对手 GANs。它们在大量的应用中非常有用,我们将在本文中进一步探讨这些应用。我们将从自动编码器领域的一些基本介绍性材料开始,然后继续分解与该主题相关的一些重要的复杂内容。一旦我们对相关概念有了适当的理解,我们将构建一些自动编码器的变体来相应地分析和试验它们的性能。最后,我们将探讨一些更关键的使用案例,您应该考虑在未来的应用中使用这些网络。下面的代码可以在 Paperspace 上的 Gradient 平台上实现,强烈建议遵循。查看目录,获得关于本文后续任务的详细指南。
自动编码器简介:
自动编码器是生成神经网络的一个分支,主要设计用于将输入编码为压缩的有意义的表示,然后将其解码,以使重建的输出尽可能与原始输入相似。它们利用学习未标记数据的有效编码来执行所需的动作。除了原始图像的重建,还有许多类型的自动编码器执行各种任务。自动编码器的主要类型之一是正则化自动编码器,其目的是通过提高捕捉基本信息的能力来防止学习身份函数并鼓励学习更丰富的表示。
我们也有具体的自动编码器,主要是为离散特征选择而设计的。它们确保潜在空间仅包含用户指定的特征数量。最后,在变型自动编码器中,我们有一个更受欢迎的自动编码器变体。他们发现自己在生成任务中的效用,类似于甘斯。他们试图通过概率分布进行数据生成。我们将在另一篇文章中了解更多关于可变自动编码器的内容,因为它们是一个足够大的概念,值得单独写一篇文章。在这篇博客中,我们的主要讨论将是理解自动编码器的基本工作操作,同时构造几个深度卷积自动编码器和其他版本,以分析它们在维数约减任务中的性能。
了解自动编码器:
自动编码器是一种独特类型的生成网络。它们由一个编码器、一个潜在维度空间和一个解码器组成。编码器包括神经网络架构,该架构试图执行高维数据到低维向量空间的转换。潜在空间包含特定图像的基本提取特征,即压缩的代表性形式的数据。
让我们假设我们有一个 100 x 100 的图像,您想通过编码器来适当地减小尺寸。在 10000 像素的维度空间中,可以说只有大约 1000 个组成部分包含了最有用和决定性的信息,换句话说,是高质量的数据。自动编码器的潜在维度空间将由这个具有对重建最有用的信息的低维度空间组成。
自动编码器中解码器的任务是从现有的潜在维度空间中重建这个新数据。因此,我们可以看到,重新生成的数据是原始样本的有效重建,尽管在此过程中丢失了一些信息。与生成式对抗网络不同,生成式对抗网络生成全新的数据样本,自动编码器主要不执行相同的功能。因此,大多数初学者可能对自动编码器有一个普遍的误解,因为他们想知道它们的真正目的;尤其是当我们只是以一些微小的信息损失为代价来重构原始样本的时候。
在大多数情况下,我们主要关心的是利用编码器和潜在的维度空间进行各种应用。这些特别是图像去噪、异常检测和其他类似的任务。解码器通常是我们可视化重建输出质量的途径。自动编码器可以被认为是降维技术的“更智能”版本,例如主成分分析(PCA)。在本文的应用部分,我们将进一步分析这些自动编码器的重要性。现在,让我们通过一些代码和深度学习来关注这些自动编码器的实际实现。
深度卷积自动编码器(方法 1):
有几种构建自动编码器体系结构的方法。在前两种方法中,我们将研究两种可以用来解决项目的深度卷积方法。我将使用 TensorFlow 和 Keras 中的时尚数据和 MNIST 数据。这些将是我们建设这些项目的主要深度学习框架。我们在以前的文章中已经非常详细地介绍了这两个库。如果你不熟悉这些,我推荐你查看 TensorFlow 的链接和 Keras 的链接。现在让我们开始构建自动编码器模型。
导入基本库:
第一步是导入为特定项目构建 autoencoder 模型所需的所有基本库。在这里,我导入了时尚 MNIST 和 MNIST 的数据集。观众可以自由探索他们需要的选项。其他必要的导入是 Keras 和 TensorFlow 提供的深度学习框架,用于可视化输入和返回输出中的图像的 matplotlib,以及帮助设置数据集的 Numpy。我们还将导入构建完全卷积深度学习网络所需的一些层。我们将使用函数式 API 模型方法来完成这项任务,以便对自动编码器的模型结构进行更多的控制。跟随本文的读者也可以选择顺序或模型子类化(定制方法)来处理这个问题。下面是包含所有需要的库导入的代码片段。
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
import numpy as np
from tensorflow.keras.datasets import fashion_mnist
from tensorflow.keras.datasets import mnist
from tensorflow.keras.layers import Conv2D, MaxPooling2D, UpSampling2D, Input, ZeroPadding2D
from tensorflow.keras.layers import BatchNormalization, LeakyReLU, Activation, Cropping2D, Dense
from tensorflow.keras.models import Model
准备数据:
在文章的下一部分,我们将为这个项目准备数据。请注意,准备步骤会因您正在进行的项目类型而有所不同。如果使用自定义数据集,在将图像或数据通过自动编码器的编码器部分之前,可能需要更多的预处理。对于我们当前的数据集,加载数据并相应地将它们与各自的训练和测试实体分离是相对容易的。用于此项目的两个数据集的形状都是 28 x 28,展平后总计 784 维特征,因为它们是通道为 1 的灰度图像。当处理具有 3 个通道的 RGB 图像时,如 RGB 彩色图像,可能会出现一些变化,必须根据要求进行计算。我们还将可视化我们的数据,以查看我们将在这个项目中使用的图像类型。
Sample Fashion Dataset Image
我们可以看到,这些图像不是最高质量的,但它们肯定足以作为自动编码器的入门项目。我们还将对训练数据和测试数据进行规范化,因为当值在 0 到 1 的范围内而不是在 0 到 255 的范围内时,处理这些值会更容易。在处理这类数据时,这种做法很常见。标准化的代码片段如下所示,一旦完成,我们就可以继续构建深度卷积自动编码器。
# Normalizing the data
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
构造深度卷积自动编码器;
正如上一节所讨论的,我们知道自动编码器主要由三个主要实体组成,即编码器、潜在维度和解码器。我们现在将使用深度卷积方法构建编码器和解码器神经网络。这一部分和下一节将围绕几种方法展开,通过这些方法,您可以构建适合您的问题的编码器和解码器网络,并相应地获得最佳结果。让我们从编码器的结构开始。
编码器:
我们的架构构建利用了 TensorFlow 的功能性 API 结构,这使我们能够快速适应一些变化,并让我们对构建的细节有更多的控制。顺序或模型子类化方法也可以用来解释下面的代码。我们的编码器架构中的第一层将由输入层组成,它将采用图像的形状,在我们的情况下,时尚和 MNIST 数据的 28 x 28 灰度像素化图像。请注意,深度卷积网络需要这种形状。如果您计划为您的项目使用隐藏(或密集)图层类型结构,那么最好将图像展平或将其转换为 784 维数据以供进一步处理。
在第一种方法中,我利用零填充层将图像从 28 x 28 的形状转换为 32 x 32 的形状。这种转换使我们能够获得现在是 2 的幂的图像,并将帮助我们添加更多的最大池和卷积层。通过这一更改,我们现在可以为每个卷积添加四个卷积层和最大池层,因为达到奇数将需要更长时间。由于计算困难,我们不想跨越一个奇数。因此,编码器神经网络架构利用具有相同类型填充和 ReLU 激活函数的四个卷积层。这些卷积层中的每一层后面都有一个最大池层,步长为(2,2)以缩小图像。编码器架构代码块如下面的代码片段所示。
# Creating the input layer with the full shape of the image
input_layer = Input(shape=(28, 28, 1))
# Note: If utilizing a deep neural network without convolution, ensure that the dimensions are multiplied and converted
#accordingly before passing through further layers of the encoder architecture.
zero_pad = ZeroPadding2D((2, 2))(input_layer)
# First layer
conv1 = Conv2D(16, (3, 3), activation='relu', padding='same')(zero_pad)
pool1 = MaxPooling2D((2, 2), padding='same')(conv1)
# Second layer
conv2 = Conv2D(16, (3, 3), activation='relu', padding='same')(pool1)
pool2 = MaxPooling2D((2, 2), padding='same')(conv2)
# Third layer
conv3 = Conv2D(8, (3, 3), activation='relu', padding='same')(pool2)
pool3 = MaxPooling2D((2, 2), padding='same')(conv3)
# Final layer
conv4 = Conv2D(8, (3, 3), activation='relu', padding='same')(pool3)
# Encoder architecture
encoder = MaxPooling2D((2, 2), padding='same')(conv4)
解码器:
在解码器架构中,我们将成功地重建我们的数据,并完成整个自动编码器结构。对于这种重建,模型将需要四层,一旦数据通过下一组深度卷积层,这四层将对数据进行上采样。我们还可以利用具有 ReLU 激活功能的卷积层和类似的填充来进行重构过程。然后,我们将对卷积层进行上采样,使其大小加倍,并在四组构建块之后达到 32 x 32 的理想图像大小。最后,我们将使用 TensorFlow 中的裁剪 2D 功能,将图像的原始值从 32 x 32 恢复为 28 x 28。查看下面的代码片段,了解整个解码器布局。
# First reconstructing decoder layer
conv_1 = Conv2D(8, (3, 3), activation='relu', padding='same')(encoder)
upsample1 = UpSampling2D((2, 2))(conv_1)
# Second reconstructing decoder layer
conv_2 = Conv2D(8, (3, 3), activation='relu', padding='same')(upsample1)
upsample2 = UpSampling2D((2, 2))(conv_2)
# Third decoder layer
conv_3 = Conv2D(16, (3, 3), activation='relu', padding='same')(upsample2)
upsample3 = UpSampling2D((2, 2))(conv_3)
# First reconstructing decoder layer
conv_4 = Conv2D(1, (3, 3), activation='relu', padding='same')(upsample3)
upsample4 = UpSampling2D((2, 2))(conv_4)
# Decoder architecture
decoder = Cropping2D((2, 2))(upsample4)
汇编和培训:
我们的下一步是编译编码器的整个架构。我们将创建功能 API 模型,编码器的输入层后面是解码器的输出层。一旦自动编码器完成,我们将使用 Adam 优化器和均方误差损失编译模型。
我们还可以查看模型的概要,以了解 autoencoder 的整个结构。下面是执行以下操作的代码片段和摘要。
# Creating and compiling the model
autoencoder = Model(input_layer, decoder)
autoencoder.compile(optimizer='adam', loss='mse')
autoencoder.summary()
Model: "model_1"
Layer (type) Output Shape Param #
input_4 (InputLayer) [(None, 28, 28, 1)] 0
zero_padding2d_2 (ZeroPaddin (None, 32, 32, 1) 0
conv2d_16 (Conv2D) (None, 32, 32, 16) 160
max_pooling2d_10 (MaxPooling (None, 16, 16, 16) 0
conv2d_17 (Conv2D) (None, 16, 16, 16) 2320
max_pooling2d_11 (MaxPooling (None, 8, 8, 16) 0
conv2d_18 (Conv2D) (None, 8, 8, 8) 1160
max_pooling2d_12 (MaxPooling (None, 4, 4, 8) 0
conv2d_19 (Conv2D) (None, 4, 4, 8) 584
max_pooling2d_13 (MaxPooling (None, 2, 2, 8) 0
conv2d_20 (Conv2D) (None, 2, 2, 8) 584
up_sampling2d_6 (UpSampling2 (None, 4, 4, 8) 0
conv2d_21 (Conv2D) (None, 4, 4, 8) 584
up_sampling2d_7 (UpSampling2 (None, 8, 8, 8) 0
conv2d_22 (Conv2D) (None, 8, 8, 16) 1168
up_sampling2d_8 (UpSampling2 (None, 16, 16, 16) 0
conv2d_23 (Conv2D) (None, 16, 16, 1) 145
up_sampling2d_9 (UpSampling2 (None, 32, 32, 1) 0
cropping2d_1 (Cropping2D) (None, 28, 28, 1) 0
Total params: 6,705 | Trainable params: 6,705 | Non-trainable params: 0
因为我们将为这个特定的任务运行大约 100 个时期的 autoencoder 模型,所以最好利用一些适当的回调。我们将在训练期间使用模型检查点来保存模型的最佳权重,在连续八个时期没有改善后以八的耐心提前停止以停止训练,在四个时期没有改善后降低学习率回调以降低学习率,最后,TensorBoard 相应地可视化我们的进度。下面是我们将在 autoencoder 项目中使用的各种回调的代码片段。
# Creating Callbacks
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.callbacks import TensorBoard
from tensorflow.keras.callbacks import ReduceLROnPlateau
tensorboad_results = TensorBoard(log_dir='autoencoder_logs_fashion/')
checkpoint = ModelCheckpoint("best_model_fashion.h5", monitor="val_loss", save_best_only=True)
early_stop = EarlyStopping(monitor="val_loss", patience=8, restore_best_weights=False)
reduce_lr = ReduceLROnPlateau(monitor="val_loss", factor=0.2, patience=4, min_lr=0.000001)
在最后一步,我们将拟合自动编码器模型,并对其进行大约 100 个历元的训练,以达到最佳的可能结果。请注意,标签,即训练和测试(y-train 和 y-test)标签值不是必需的,并且在此任务中不予考虑。我们将使用批量为 128 的训练和验证样本来训练模型。我们还将利用预定义的回调,让模型运行 100 个时期。Paperspace 上的渐变平台是运行以下项目的好地方。
# Training our autoencoder model
autoencoder.fit(x_train, x_train,
epochs=100,
batch_size=128,
shuffle=True,
validation_data=(x_test, x_test),
callbacks=[checkpoint, early_stop, tensorboad_results, reduce_lr])
结果的可视化:
在成功编译和训练 autoencoder 模型之后,最后一步是可视化和分析所获得的结果。使用模型的预测函数返回测试数据的预测值。将预测存储在一个变量中,并使用 matplotlib 库来可视化所获得的结果。构建这个动作的代码片段如下所示。我为时尚和 MNIST 数据计算了两个独立的自动编码器模型。这两个数据集的结果如下所示。
decoded_imgs = autoencoder.predict(x_test)
n = 10
plt.figure(figsize=(20, 4))
for i in range(1, n + 1):
# Display original
ax = plt.subplot(2, n, i)
plt.imshow(x_test[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
# Display reconstruction
ax = plt.subplot(2, n, i + n)
plt.imshow(decoded_imgs[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
Result on Fashion Data
Result on MNIST Data
将我们的预测可视化后获得的结果相当不错,尤其是与其他方法如 PCA 重建相比。
虽然方法 1 给了我们一个好的结果,但我们将使用类似的深度卷积方法尝试另一种方法,看看它是否会产生类似、更差或更好的结果。在本文的下一节中,我们将继续讨论构造自动编码器的第二种方法。
深度卷积自动编码器(方法 2):
在构造深度卷积自动编码器的第二种方法中,我们将使用批量标准化层和泄漏 ReLU 层。我们还将通过避免使用零填充或裁剪 2D 图层来避免打乱数据集的原始形状。在本节中,仅更改自动编码器的编码器和解码器架构。大多数其他参数和代码块可以保持不变,也可以根据用户的要求进行修改。让我们分析编码器架构,以进一步理解我们将如何构造这个自动编码器。
编码器:
编码器架构包含输入层,它将接受 MNIST 图像的输入,每个图像都是形状为 28 x 28 的灰度图像。编码器结构将主要由两组卷积网络组成。这些块中的每一个都有一个具有相同填充(并且没有激活函数)的卷积层,随后是一个用于执行跨越的最大池层、一个泄漏 ReLU 激活函数,最后是一个用于泛化神经网络的批量归一化层。经过这两个网络模块后,我们将信息传递给解码器,以完成自动编码器架构。
# Creating the input layer with the full shape of the image
input_layer = Input(shape=(28, 28, 1))
# Note: If utilizing a deep neural network without convolution, ensure that the dimensions are multiplied and converted
# accordingly before passing through further layers of the encoder architecture.
# First layer
conv1 = Conv2D(16, (3, 3), padding='same')(input_layer)
pool1 = MaxPooling2D((2, 2), padding='same')(conv1)
activation1 = LeakyReLU(alpha=0.2)(pool1)
batchnorm1 = BatchNormalization()(activation1)
# Second layer
conv2 = Conv2D(8, (3, 3), padding='same')(batchnorm1)
pool2 = MaxPooling2D((2, 2), padding='same')(conv2)
activation2 = LeakyReLU(alpha=0.2)(pool2)
# Encoder architecture
encoder = BatchNormalization()(activation2)
解码器:
对于解码器模型,我们将把来自编码器的输出传递到编码器模型中使用的类似的重构神经网络中。我们将利用上采样层获得图像的原始维度并重建数据,而不是利用最大池层来降低数据的维度。下面提供了解码器架构的代码片段。一旦编码器和解码器模块构建完成,就可以按照与上一节相同的过程来编译、训练和可视化结果。
# First reconstructing decoder layer
conv_1 = Conv2D(8, (3, 3), activation='relu', padding='same')(encoder)
upsample1 = UpSampling2D((2, 2))(conv_1)
activation_1 = LeakyReLU(alpha=0.2)(upsample1)
batchnorm_1 = BatchNormalization()(activation_1)
# Second reconstructing decoder layer
conv_2 = Conv2D(1, (3, 3), activation='relu', padding='same')(batchnorm_1)
upsample2 = UpSampling2D((2, 2))(conv_2)
activation_2 = LeakyReLU(alpha=0.2)(upsample2)
# Encoder architecture
decoder = BatchNormalization()(activation_2)
在运行以下程序大约 100 个时期后,我能够获得 MNIST 数据的以下结果。也可以随意在其他数据集上测试代码,并根据需要分析神经网络的性能。
Result with approach-2 on MNIST Data
这些结果看起来比之前生成的图像更好。对此的一个可能的解释是使用批量标准化层,这有助于神经网络更快地进行归纳和更好地学习。此外,在这种情况下,最终的维度空间缩减比第一种方法小得多。因此,恢复阶段或重建看起来更好,因为它只包含两个实际的数据压缩阶段和两个数据重建阶段。随着我们进一步压缩数据,信息在重建阶段丢失的可能性会更大。因此,每个项目都应该根据具体情况下的预期要求进行研究和处理。
现在让我们继续讨论一些其他的方法,我们可以遵循这些类型的任务,包括其他版本的自动编码器。
关于其他方法的讨论:
让我们来看看我在构建这个项目时使用的另一种方法,即利用编码器和解码器的全连接层。第一步是将图像从原始的 28×28 维数据转换成 784 维的展平形状,然后继续训练自动编码器模型。下面是代码块和两个任务分别获得的图像结果。您可以找到类似的技术,看看哪种方法最适合特定的任务。
x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:])))
x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:])))
encoding_dim = 32
# Input Sample
input_img = Input(shape=(784,))
# encoder network
encoder = Dense(encoding_dim, activation='relu')(input_img)
# decoder network
decoder = Dense(784, activation='sigmoid')(encoder)
# This model maps an input to its reconstruction
autoencoder = Model(input_img, decoder)
Result with fully connected layers on Fashion Data
Result with fully connected layers on MNIST Data
在本文中,我们主要关注深度卷积自动编码器。但是,您可以实现几种不同的结构来构造自动编码器。一种这样的方法是使用完全连接的密集网络来再现和重建原始图像的输出。下面是自动编码器与 PCA 等方法在重建方面的差异的快速比较。
从上图中可以明显看出,自动编码器是降维最有效的方法之一,尤其是与其他一些可用的方法相比,如 PCA、t-分布式随机邻居嵌入(t-SNE)、随机森林和其他类似的技术。因此,自动编码器可以被视为基本的深度学习概念,以理解解决各种任务,特别是关于计算机视觉的任务。让我们在下一节讨论一些自动编码器的应用,以便更深入地理解它们的重要性。
自动编码器的应用:
由于自动编码器在不同领域的广泛应用,它越来越受欢迎。在这一节中,我们将讨论它们所拥有的一些令人惊叹的功能,以及您可以在下面的列表中利用它们的不同类型的用途。
- 降维:我们使用自动编码器的主要原因之一是以最小的信息损失来降低内容的维度,因为它被认为是相对于 PCA 等其他方法来说完成这项任务的更高级的工具之一。本文将详细介绍这个主题。
- 异常检测:异常检测是定位数据中异常值的过程。作为其训练的一部分,自动编码器试图最小化重建误差,因此我们可以检查重建损失的幅度,以推断所发现的异常数据的程度。
- 图像去噪:自动编码器在去除特定图像的噪声方面做得很好,可以更准确地描述特定数据。旧图像或模糊图像可以用自动编码器去噪,以获得更好和更真实的表现。
- 特征提取:自动编码器的编码器部分有助于学习数据更本质的特征。因此,这些生成网络在提取对特定任务至关重要且必需的重要信息方面具有惊人的效用。
- 图像生成:自动编码器也可用于重建和创建修改后的压缩图像。虽然我们之前已经讨论了自动编码器并没有真正用于创建全新的图像,因为它们是原始产品的重建,但是变化的自动编码器可以用于生成新的图像。我们将在以后的文章中更多地讨论这个话题。
结论:
Photo by Jorge Ramirez / Unsplash
自动编码器已被证明是生成型神经网络领域的另一个革命性发展。他们能够了解提供给他们的数据的大部分基本特征,并在了解提供给他们的数据的大部分关键细节的同时减少维度。大多数现代自动编码器优先考虑给定输入的有用属性,并且它们已经能够将编码器和解码器的思想从确定性函数推广到随机映射。如需进一步阅读,请查看以下链接-【1】和【2】。
在本文中,我们涵盖了理解自动编码器模型的基本工作过程所需的大部分基本方面。我们简要介绍了自动编码器,并详细介绍了它的生成本质。我们还探索了深度卷积架构的几种变体,我们可以构建它们来降低维度空间并相应地重建它。除了我们讨论的两种方法,我们还简要分析了其他方法以及其他技术(如 PCA)的性能。最后,我们研究了自动编码器的一些令人惊叹的应用以及它所拥有的广泛功能。
在未来的文章中,我们将重点关注一些使用 Transformers 的自然语言处理(NLP)任务,并学习在不利用任何深度学习框架的情况下从头构建神经网络。我们也将在未来的博客中进一步探索变型自动编码器。在那之前,继续探索!
从头开始理解 CANet 架构
原文:https://blog.paperspace.com/understanding-canet-architecture/
Image From Wiki
图像分割是任何图像识别项目中最有趣和最关键的方面之一,因为通过图像分割,我们可以确定特定图片中最基本的元素和成分。通过图像分割,开发人员可以将特定图像中的重要对象与其他实体分开。一旦该提取过程完成,我们可以将指定的片段或整个分类的分割图像用于多种目的和各种应用。隔离图像中的特定元素可以用于确定异常。因此,图像分割在生物医学任务中具有巨大的用途。然而,学习图像中各种参数的整个分割元素可以用于其他应用,例如道路和车辆分割数据。
在我们之前的文章中,我们了解了 U-Net 模型的工作和架构。在这一部分中,我们将关注另一种类型的建筑方法来执行图像分割任务。链式上下文聚合网络(CANet)是执行图像分割任务的另一种极好的方法。它采用非对称解码器来恢复预测图的精确空间细节。本文灵感来自于研究论文 注意力引导的链式上下文聚合语义切分。这篇研究论文涵盖了这种新方法的大部分基本要素,以及它如何影响许多复杂任务的性能。如果您对学习该架构的几乎每一个元素都感兴趣,那么建议您阅读整篇文章。但是,如果您想关注您更感兴趣的特定元素,请随时查看目录。
目录:
- CANet 简介
- 对 CAM 和一些损失函数的简单理解
- 架构入门
1。导入库
2。卷积块
3。身份块
4。形状的验证 - 全局流和上下文流
- 特征选择模块和适应的全球卷积网络(AGCN)
- 完成构建
- 结论
简介:
有几种方法、算法和不同类型的架构来解决深度学习中的众多问题。一种解决图像分割问题的这样的架构是 CANet 模型。该方法利用利用类似于 U-Net 架构的全卷积网络(FCN)的概念来捕获多尺度上下文,以获得精确的定位和图像分割掩模。所提出的模型引入了串-并行混合范例的概念,具有链式上下文聚合模块(CAM ),以使特征传播多样化。以下新的整合允许通过两阶段过程(即预融合和再融合)来捕捉各种空间特征和维度。
除了前面讨论过的设计特性之外,它还包括一个编码器-解码器架构,在该架构中,编码器网络对输入图像进行连续下采样。但是在解码器网络中,数据被向上采样回到其原始大小。最常见的编码器-解码器架构结构与 CANet 模型中使用的架构结构之间的一个主要区别在于,CANet 解码器在恢复预测图的精确空间细节时是不对称的。他们也使用注意力的概念来获得最好的结果。注意方法包含在 CAM 结构旁边。因此出现了链式上下文聚合网络(CANet)这个术语。
CANet 架构的主要贡献包括链式上下文聚合模块,用于在串并联混合结构的帮助下捕获多尺度特征。串行流不断扩大输出神经元的感受域,而并行流编码不同的基于区域的上下文,从而极大地提高了性能。流引导支持多尺度上下文的强大聚合。该模型在多个具有挑战性的数据集上进行了广泛测试,似乎提供了最佳结果。因此,它可以被称为最先进的表演方法之一。
简要了解 CAM 和一些损失函数:
如前所述,链式上下文聚合模块(CAM)是 CANet 架构最关键的方面之一。它由全局流和上下文流组成。这两种结构都是浅层编码器-解码器结构,包含用于获得不同感受野的下采样层、用于集成足够的上下文特征的投影层以及用于恢复定位信息的上采样层。
当全局流和上下文流被集成为串并行混合时,它们有助于多尺度上下文的高质量编码。它们具有许多优点,包括大的接收特征、用于图像分割的以不同形状编码的像素特征、多尺度上下文的高质量编码以及简化梯度的反向传播。现在让我们继续讨论一些通常用于图像分割任务的损失函数。
了解骰子损失和借据损失:
骰子损失是用于图像分割任务的流行损失函数,也称为骰子系数。它本质上是两个样本之间重叠的度量。
配方=
损失函数的范围是从 0 到 1,它可以解释如下:
对于在预测的分段掩码上评估 Dice 系数的情况,我们可以将\(|A∩B|\)近似为预测和目标掩码之间的元素式乘法,然后对结果矩阵求和。这个损失函数帮助我们确定在每个像素中遇到了多少损失,以及我们的模型在每个像素中的表现有多精确。那么可以取总和来显示在执行分割之后从目标模型到预测模型的完全损失。
IoU 分数是用于确定图像分割任务质量的另一个重要度量。总的来说,解释起来很直观。当预测边界框精确匹配地面真实边界框时,通过预测边界框获得 1 分。分数为 0 意味着预测边界框和地面真实的真实边界框完全不重叠。IoU 分数的计算如下:
架构入门:
从零开始实现 CANet 架构是相当复杂的。我们有几个需要高精度关注的编码模块,以实现最佳结果并确保我们获得最佳结果。我们将创建自定义类来执行最初的研究论文中描述的大多数操作。这种用 TensorFlow 和 Keras 深度学习框架构建架构的方法也被称为模型子类化。在我们从头开始构建架构的每个部分之前,检查一下我们将要构建的整个模型以及它包含的所有基本实体。
上图是未来事物的代表。这种架构设计的上下两部分可以认为是两个部分,即编码器和解码器。在编码器结构中,我们有卷积块和身份块以及其他元素,如全局流、上下文流和特征选择块,其中通过模型的初始输入图像被连续下采样,因为我们只考虑宽度和高度小于原始图像的图像大小。
一旦我们将其通过解码器模块,所有这些元素将再次进行上采样,以获得所需的输出,在大多数情况下,输出大小与输入相同。让我们开始构建 CANet 架构。请仔细跟随,因为有些部分可能会有点混乱,容易卡住。我们将继续导入特定任务所需的所有库,然后继续处理卷积和标识块。
import tensorflow as tf
# tf.compat.v1.enable_eager_execution()
from tensorflow import keras
from tensorflow.keras.layers import *
from tensorflow.keras.preprocessing import image
from tensorflow.keras.models import Model, load_model
from tensorflow.keras.layers import UpSampling2D
from tensorflow.keras.layers import MaxPooling2D, GlobalAveragePooling2D
from tensorflow.keras.layers import concatenate
from tensorflow.keras.layers import Multiply
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from tensorflow.keras import backend as K
from tensorflow.keras.layers import Input, Add, Dense, Activation, ZeroPadding2D, BatchNormalization, Flatten, Conv2D, AveragePooling2D, MaxPooling2D, GlobalMaxPooling2D
from tensorflow.keras.models import Model, load_model
from tensorflow.keras.utils import plot_model
from tensorflow.keras.initializers import glorot_uniform
K.set_image_data_format('channels_last')
K.set_learning_phase(1)
卷积模块:
CANet 模型的第一步是将输入图像通过卷积模块,并应用最大池层,步长为 2。然而,该码是单个卷积层,并且它不会通过架构再次重复。因此,我们将保留初始卷积层,它在最终架构中单独处理输入图像。在本节中,我们将构建迭代卷积模块。
该架构的下一步是在编码器中使用卷积模块 C1、C2、C3 和 C4 来创建通道映射。为了执行该操作,我们将利用卷积块类和相应的子类建模方法来获得可能的最佳结果。必须注意的是,通过卷积架构的图像具有递减的形状。它们如下。
- C1 的宽度和高度比原始图像小 4 倍。
- C2 的宽度和高度比原始图像小 8 倍。
- C3 的宽度和高度比原始图像小 8 倍。
- C4 的宽度和高度比原始图像小 8 倍。
下面的代码块展示了如何成功完成这个过程。
class convolutional_block(tf.keras.layers.Layer):
def __init__(self, kernel=3, filters=[4,4,8], stride=1, name="conv block"):
super().__init__(convolutional_block)
self.F1, self.F2, self.F3 = filters
self.kernel = kernel
self.stride = stride
self.conv_1 = Conv2D(self.F1,(1,1),strides=(self.stride,self.stride),padding='same')
self.conv_2 = Conv2D(self.F2,(self.kernel,self.kernel),strides=(1,1),padding='same')
self.conv_3 = Conv2D(self.F3,(1,1),strides=(1,1),padding='same')
self.conv_4 = Conv2D(self.F3,(self.kernel,self.kernel),strides=(self.stride,self.stride),padding='same')
self.bn1 = BatchNormalization(axis=3)
self.bn2 = BatchNormalization(axis=3)
self.bn3 = BatchNormalization(axis=3)
self.bn4 = BatchNormalization(axis=3)
self.activation = Activation("relu")
self.add = Add()
def call(self, X):
# write the architecutre that was mentioned above
X_input = X
# First Convolutional Block
conv1 = self.conv_1(X)
bn1 = self.bn1(conv1)
act1 = self.activation(bn1)
# Second Convolutional Block
conv2 = self.conv_2(act1)
bn2 = self.bn2(conv2)
act2 = self.activation(bn2)
# Third Convolutional Block
conv3 = self.conv_3(act2)
bn3 = self.bn3(conv3)
# Adjusting the input
X_input = self.conv_4(X_input)
X_input = self.bn4(X_input)
X_input = self.activation(X_input)
# Re-add the input
X = self.add([bn3, X_input])
X = self.activation(X)
return X
身份模块:
当我们知道必须保留输入和输出维度时,在体系结构中使用标识块。这是一种经常用来保存实体的方法,同时也是一种避免模型特性瓶颈的方法。卷积块 C1、C2、C3 和 C4 是通过应用一个卷积块后跟\(k\) number 的身份块形成的。这意味着\(C(k)\)特征图将由一个卷积块和后面的\(k\) number 个身份块组成。下面的代码展示了如何轻松地执行这个操作。
class identity_block(tf.keras.layers.Layer):
def __init__(self, kernel=3, filters=[4,4,8], name="identity block"):
super().__init__(identity_block)
self.F1, self.F2, self.F3 = filters
self.kernel = kernel
self.conv_1 = Conv2D(self.F1, (1,1), (1,1), padding="same")
self.conv_2 = Conv2D(self.F2, (self.kernel,self.kernel), (1,1), padding="same")
self.conv_3 = Conv2D(self.F3, (1,1), (1,1), padding="same")
self.bn1 = BatchNormalization(axis=3)
self.bn2 = BatchNormalization(axis=3)
self.bn3 = BatchNormalization(axis=3)
self.activation = Activation("relu")
self.add = Add()
def call(self, X):
# write the architecutre that was mentioned above
X_input = X
conv1 = self.conv_1(X)
bn1 = self.bn1(conv1)
act1 = self.activation(bn1)
conv2 = self.conv_2(act1)
bn2 = self.bn2(conv2)
act2 = self.activation(bn2)
conv3 = self.conv_3(act2)
bn3 = self.bn3(conv3)
X = self.add([bn3, X_input])
X = self.activation(X)
return X
形状的验证:
在这一部分中,我们将快速构建初始架构的起点,并在图像形状的帮助下测试构建的进度。这个步骤被执行一次,以检查您的开发模型的进度是否准确,并相应地匹配。在接下来的部分中,我们不会重复这一步,但是您可以自己尝试一下。
X_input = Input(shape=(256,256,3))
# Stage 1
X = Conv2D(64, (3, 3), name='conv1', padding="same", kernel_initializer=glorot_uniform(seed=0))(X_input)
X = BatchNormalization(axis=3, name='bn_conv1')(X)
X = Activation('relu')(X)
X = MaxPooling2D((2, 2), strides=(2, 2))(X)
print(X.shape)
# First Convolutional Block
c1 = convolutional_block(kernel=3, filters=[4,4,8], stride=2)(X)
print("C1 Shape = ", c1.shape)
I11 = identity_block()(c1)
print("I11 Shape = ", I11.shape)
# Second Convolutional Block
c2 = convolutional_block(kernel=3, filters=[8,8,16], stride=2)(I11)
print("C2 Shape = ", c2.shape)
I21 = identity_block(kernel=3, filters=[8,8,16])(c2)
print("I21 Shape = ", I21.shape)
I22 = identity_block(kernel=3, filters=[8,8,16])(I21)
print("I22 Shape = ", I22.shape)
# Third Convolutional Block
c3 = convolutional_block(kernel=3, filters=[16,16,32], stride=1)(I22)
print("C3 Shape = ", c3.shape)
I31 = identity_block(kernel=3, filters=[16,16,32])(c3)
print("I31 Shape = ", I31.shape)
I32 = identity_block(kernel=3, filters=[16,16,32])(I31)
print("I32 Shape = ", I32.shape)
I33 = identity_block(kernel=3, filters=[16,16,32])(I32)
print("I33 Shape = ", I33.shape)
# Fourth Convolutional Block
c4 = convolutional_block(kernel=3, filters=[32,32,64], stride=1)(I33)
print("C3 Shape = ", c4.shape)
I41 = identity_block(kernel=3, filters=[32,32,64])(c4)
print("I41 Shape = ", I41.shape)
I42 = identity_block(kernel=3, filters=[32,32,64])(I41)
print("I42 Shape = ", I42.shape)
I43 = identity_block(kernel=3, filters=[32,32,64])(I42)
print("I43 Shape = ", I43.shape)
I44 = identity_block(kernel=3, filters=[32,32,64])(I42)
print("I44 Shape = ", I44.shape)
输出:
(None, 128, 128, 64)
C1 Shape = (None, 64, 64, 8)
I11 Shape = (None, 64, 64, 8)
C2 Shape = (None, 32, 32, 16)
I21 Shape = (None, 32, 32, 16)
I22 Shape = (None, 32, 32, 16)
C3 Shape = (None, 32, 32, 32)
I31 Shape = (None, 32, 32, 32)
I32 Shape = (None, 32, 32, 32)
I33 Shape = (None, 32, 32, 32)
C3 Shape = (None, 32, 32, 64)
I41 Shape = (None, 32, 32, 64)
I42 Shape = (None, 32, 32, 64)
I43 Shape = (None, 32, 32, 64)
I44 Shape = (None, 32, 32, 64)
现在我们有了一个简单的想法,我们已经走上了正确的道路,因为我们所有的图像形状似乎都完美地匹配,我们可以进入本文的下一部分,实现 CANet 模型架构的进一步步骤。
全局流和上下文流:
如上图所示,我们将利用全局平均池架构来开始构建全局流。此函数计算整个空间维度数据的全局平均值。然后,我们将这些信息传递给下一个模块,即批量标准化层、ReLU 激活函数和 1x1 卷积层。我们获得的最终输出形状的格式为(无,1,1,过滤器数量)。然后,我们可以将这些值通过一个使用双线性池作为插值技术的上采样层或 Conv2D 转置层(就像我们在上一篇文章中所做的那样)。
class global_flow(tf.keras.layers.Layer):
def __init__(self, input_dim, output_dim, channels, name="global_flow"):
super().__init__(global_flow)
self.input_dim = input_dim
self.output_dim = output_dim
self.channels = channels
self.conv1 = Conv2D(64,kernel_size=(1,1),strides=(1,1),padding='same')
self.global_avg_pool = GlobalAveragePooling2D()
self.bn = BatchNormalization(axis=3)
self.activation = Activation("relu")
self.upsample = UpSampling2D(size=(self.input_dim,self.output_dim),interpolation='bilinear')
def call(self, X):
# implement the global flow operatiom
global_avg = self.global_avg_pool(X)
global_avg= tf.expand_dims(global_avg, 1)
global_avg = tf.expand_dims(global_avg, 1)
bn1 = self.bn(global_avg)
act1 = self.activation(bn1)
conv1 = self.conv1(act1)
X = self.upsample(conv1)
return X
对于上下文流,我们有上下文融合模块,其中我们从 C4 卷积块中获取输出,从全局流中获取输出,将它们连接在一起作为一个单元。然后,我们继续应用平均池和卷积层。我们可以跳过频道切换,因为这不是真正需要的。然后,我们有上下文细化模块,在向上采样(或通过 Conv2D 转置层运行)之前执行以下操作
class context_flow(tf.keras.layers.Layer):
def __init__(self, name="context_flow"):
super().__init__(context_flow)
self.conv_1 = Conv2D(64, kernel_size=(3,3), strides=(1,1), padding="same")
self.conv_2 = Conv2D(64, kernel_size=(3,3), strides=(1,1), padding="same")
self.conv_3 = Conv2D(64, kernel_size=(1,1), strides=(1,1), padding="same")
self.conv_4 = Conv2D(64, kernel_size=(1,1), strides=(1,1), padding="same")
self.concatenate = Concatenate()
self.avg_pool = AveragePooling2D(pool_size=(2,2))
self.activation_relu = Activation("relu")
self.activation_sigmoid = Activation("sigmoid")
self.add = Add()
self.multiply = Multiply()
self.upsample = UpSampling2D(size=(2,2),interpolation='bilinear')
def call(self, X):
# here X will a list of two elements
INP, FLOW = X[0], X[1]
# implement the context flow as mentioned in the above cell
# Context Fusion Module
concat = self.concatenate([INP, FLOW])
avg_pooling = self.avg_pool(concat)
conv1 = self.conv_1(avg_pooling)
conv2 = self.conv_2(conv1)
# Context Refinement Module
conv3 = self.conv_3(conv2)
act1 = self.activation_relu(conv3)
conv4 = self.conv_4(act1)
act2 = self.activation_sigmoid(conv4)
# Combining and upsampling
multi = self.multiply([conv2, act2])
add = self.add([conv2, multi])
X = self.upsample(add)
return X
您可以在此步骤之后执行形状验证,也可以进行可选的评估,以确保您当前处于正确的轨道上。一旦确保没有形状不匹配,就可以继续下一节来实现接下来的几个块。
特征选择模块和****【AGCN】【全球卷积网络】 :
我们将构建的下两个代码块相对较小,也更容易理解。这种方法类似于注意力层的工作方式,输入通过卷积层。并且所获得的卷积层的输出被分成两段。第一个通过一些包含 sigmoid 函数的层,另一个乘以 sigmoid 层的输出。
class fsm(tf.keras.layers.Layer):
def __init__(self, name="feature_selection"):
super().__init__(fsm)
self.conv_1 = Conv2D(32, (3,3), (1,1), padding="same")
self.global_avg_pool = GlobalAveragePooling2D()
self.conv_2 = Conv2D(32 ,kernel_size=(1,1),padding='same')
self.bn = BatchNormalization()
self.act_sigmoid = Activation('sigmoid')
self.multiply = Multiply()
self.upsample = UpSampling2D(size=(2,2),interpolation='bilinear')
def call(self, X):
X = self.conv_1(X)
global_avg = self.global_avg_pool(X)
global_avg= tf.expand_dims(global_avg, 1)
global_avg = tf.expand_dims(global_avg, 1)
conv1= self.conv_2(global_avg)
bn1= self.bn(conv1)
Y = self.act_sigmoid(bn1)
output = self.multiply([X, Y])
FSM_Conv_T = self.upsample(output)
return FSM_Conv_T
我们要看的下一个结构是自适应全球卷积网络(AGCN)。
AGCN 块将从 C1 卷积块的输出接收输入。在上面的所有层中,我们将使用 padding = "same "并将步幅设置为(1,1)。因此,最终获得的输入和输出矩阵具有相同的大小。让我们研究代码块来查看实现。
class agcn(tf.keras.layers.Layer):
def __init__(self, name="global_conv_net"):
super().__init__(agcn)
self.conv_1 = Conv2D(32,kernel_size=(1,7),padding='same')
self.conv_2 = Conv2D(32,kernel_size=(7,1),padding='same')
self.conv_3 = Conv2D(32,kernel_size=(1,7),padding='same')
self.conv_4 = Conv2D(32,kernel_size=(7,1),padding='same')
self.conv_5 = Conv2D(32,kernel_size=(3,3),padding='same')
self.add = Add()
def call(self, X):
# please implement the above mentioned architecture
conv1 = self.conv_1(X)
conv2= self.conv_2(conv1)
# side path
conv3 = self.conv_4(X)
conv4 = self.conv_3(conv3)
add1 = self.add([conv2,conv4])
conv5 = self.conv_5(add1)
X = self.add([conv5,add1])
return X
现在我们已经完成了整个架构的构建,模型构建的最后一步是确保我们已经完成了预期的结果。找到工作过程的最佳方法是通过架构传递一个图像大小作为输入大小,并验证以下结果。如果这些形状与您通过下采样和上采样手动计算的形状相匹配,那么最终的上采样输出就是原始图像大小。除了要素/类的总数替换了 RGB 或灰度通道之外,此图像大小将与输入完全相同。
X_input = Input(shape=(512,512,3))
# Stage 1
X = Conv2D(64, (3, 3), name='conv1', padding="same", kernel_initializer=glorot_uniform(seed=0))(X_input)
X = BatchNormalization(axis=3, name='bn_conv1')(X)
X = Activation('relu')(X)
X = MaxPooling2D((2, 2), strides=(2, 2))(X)
print(X.shape)
# First Convolutional Block
c1 = convolutional_block(kernel=3, filters=[4,4,8], stride=2)(X)
print("C1 Shape = ", c1.shape)
I11 = identity_block()(c1)
print("I11 Shape = ", I11.shape)
# Second Convolutional Block
c2 = convolutional_block(kernel=3, filters=[8,8,16], stride=2)(I11)
print("C2 Shape = ", c2.shape)
I21 = identity_block(kernel=3, filters=[8,8,16])(c2)
print("I21 Shape = ", I21.shape)
I22 = identity_block(kernel=3, filters=[8,8,16])(I21)
print("I22 Shape = ", I22.shape)
# Third Convolutional Block
c3 = convolutional_block(kernel=3, filters=[16,16,32], stride=1)(I22)
print("C3 Shape = ", c3.shape)
I31 = identity_block(kernel=3, filters=[16,16,32])(c3)
print("I31 Shape = ", I31.shape)
I32 = identity_block(kernel=3, filters=[16,16,32])(I31)
print("I32 Shape = ", I32.shape)
I33 = identity_block(kernel=3, filters=[16,16,32])(I32)
print("I33 Shape = ", I33.shape)
# Fourth Convolutional Block
c4 = convolutional_block(kernel=3, filters=[32,32,64], stride=1)(I33)
print("C3 Shape = ", c4.shape)
I41 = identity_block(kernel=3, filters=[32,32,64])(c4)
print("I41 Shape = ", I41.shape)
I42 = identity_block(kernel=3, filters=[32,32,64])(I41)
print("I42 Shape = ", I42.shape)
I43 = identity_block(kernel=3, filters=[32,32,64])(I42)
print("I43 Shape = ", I43.shape)
I44 = identity_block(kernel=3, filters=[32,32,64])(I42)
print("I44 Shape = ", I44.shape)
# Global Flow
input_dim = I44.shape[1]
output_dim = I44.shape[2]
channels = I44.shape[-1]
GF1 = global_flow(input_dim, output_dim, channels)(I44)
print("Global Flow Shape = ", GF1.shape)
# Context Flow 1
Y = [I44, GF1]
CF1 = context_flow()(Y)
print("CF1 shape = ", CF1.shape)
# Context Flow 2
Z = [I44, CF1]
CF2 = context_flow()(Y)
print("CF2 shape = ", CF2.shape)
# Context Flow 3
W = [I44, CF1]
CF3 = context_flow()(W)
print("CF3 shape = ", CF3.shape)
# FSM Module
out = Add()([GF1, CF1, CF2, CF3])
print("Sum of Everything = ", out.shape)
fsm1 = fsm()(out)
print("Shape of FSM = ", fsm1.shape)
# AGCN Module
agcn1 = agcn()(c1)
print("Shape of AGCN = ", agcn1.shape)
# Concatinating FSM and AGCN
concat = Concatenate()([fsm1, agcn1])
print("Concatinated Shape = ", concat.shape)
# Final Convolutional Block
final_conv = Conv2D(filters=21, kernel_size=(1,1), strides=(1,1), padding="same")(concat)
print("Final Convolution Shape = ", final_conv.shape)
# Upsample
up_samp = UpSampling2D((4,4), interpolation="bilinear")(final_conv)
print("Final Shape = ", up_samp.shape)
# Activation
output = Activation("softmax")(up_samp)
print("Final Shape = ", output.shape)
输出:
(None, 256, 256, 64)
C1 Shape = (None, 128, 128, 8)
I11 Shape = (None, 128, 128, 8)
C2 Shape = (None, 64, 64, 16)
I21 Shape = (None, 64, 64, 16)
I22 Shape = (None, 64, 64, 16)
C3 Shape = (None, 64, 64, 32)
I31 Shape = (None, 64, 64, 32)
I32 Shape = (None, 64, 64, 32)
I33 Shape = (None, 64, 64, 32)
C3 Shape = (None, 64, 64, 64)
I41 Shape = (None, 64, 64, 64)
I42 Shape = (None, 64, 64, 64)
I43 Shape = (None, 64, 64, 64)
I44 Shape = (None, 64, 64, 64)
Global Flow Shape = (None, 64, 64, 64)
CF1 shape = (None, 64, 64, 64)
CF2 shape = (None, 64, 64, 64)
CF3 shape = (None, 64, 64, 64)
Sum of Everything = (None, 64, 64, 64)
Shape of FSM = (None, 128, 128, 32)
Shape of AGCN = (None, 128, 128, 32)
Concatinated Shape = (None, 128, 128, 64)
Final Convolution Shape = (None, 128, 128, 21)
Final Shape = (None, 512, 512, 21)
Final Shape = (None, 512, 512, 21)
本文还附有 CANet 模型图。如果您有任何困惑,请随时查看。
完成构建:
现在,我们已经通过从零开始定义每个元素,在我们自己的定制方法的帮助下,从零开始成功地构建了 CANet 模型的完整架构,我们可以自由地在任何类型的数据集上保存、加载和利用这个保存的模型。您需要找出适当的方法来相应地加载数据。这方面的一个例子可以参考上一篇关于 U-Net 的文章。我们在 Keras 深度学习框架中的 Sequence 类的帮助下解决了一个示例项目。我们也可以使用数据生成器来加载数据。下面是一个关于如何利用构建的 CANet 架构的示例代码。您将加载模型,调用计算所需的分数,定义优化器,编译,最后,在特定任务上训练(适应)模型。
model = Model(inputs = X_input, outputs = output)
import segmentation_models as sm
from segmentation_models.metrics import iou_score
optim = tf.keras.optimizers.Adam(0.0001)
focal_loss = sm.losses.cce_dice_loss
model.compile(optim, focal_loss, metrics=[iou_score])
history = model.fit(train_dataloader,
validation_data=test_dataloader,
epochs=5,
callbacks=callbacks1)
在我的项目对数据集进行计算后,我能够获得以下 IoU 分数。模型的 IoU 和模型损耗的图形表示如下所示。重复一下,这张图来自我在道路分段数据集上解决的一个示例项目。您可以选择自己在不同类型的数据集上进行实验。
Screenshot By Author
下面显示的图像是相应输入图像的最终图像分割输出之一。正如我们可以注意到的,它在分割过程中做得相当不错。
Screenshot By Author
如果您对使用 CANet 构建令人惊叹的独特图像分割项目感兴趣,有大量选项可供您实施。举个例子,你可以通过下面的 GitHub 链接查看一个类似的项目。有乐趣探索和构建独特的项目,你自己与这个建筑的建设!
结论:
Photo by Dawid Zawiła / Unsplash
计算机视觉和深度学习现在正处于最高峰。随着这些领域的巨大进步,它们卓越的应用和能力几乎可以完成任何一度被认为机器不可能完成的任务,这是值得称赞的。一个与其他任务相比具有同等重要意义的任务是图像分割问题。虽然 2015 年设计和推出的 U-Net 架构能够实现高质量的结果并赢得众多奖项,但自那以来,我们已经取得了进一步的进步。对这个模型进行了许多再创造、修改和创新。我们在本文中讨论和理解的这种方法就是链式上下文聚合网络(CANet)模型的架构。
在本文中,我们了解了 CANet 的概念以及设计过程中的众多元素。我们总是简要研究 CANet 模型中的一些重要主题,并简要了解损失函数的各个方面,即骰子损失和 IoU 分数。在这些部分之后,我们继续构建 CANet 架构的整体复杂架构。我们探索了构成这个模型的各种实体。这些包括具有卷积网络的输入层、卷积块、身份块、全局流和上下文流、特征选择块和自适应全局卷积网络(AGCN)。最后,我们验证了整个架构的所有适当形状。然后,我们查看了一个示例项目输出,它显示了 CANet 模型在图像分割任务中可以产生的出色结果。
在这个架构和上一篇文章中讨论的 U-Net 模型的帮助下,您可以完成很多任务。强烈建议您尝试使用这两种体系结构的大量项目,看看它们的性能。在接下来的文章中,我们将讨论图像标题和 DCGANs 的主题。在那之前,继续编码,玩得开心!
用车道检测模型理解霍夫变换
原文:https://blog.paperspace.com/understanding-hough-transform-lane-detection/
霍夫变换技术是一种神奇的工具,可以用来定位图像中的形状。它通常用于检测圆、椭圆和直线,以获得图像的精确位置或几何理解。霍夫变换识别形状的能力使其成为自动驾驶汽车检测车道线的理想工具。
当试图理解自动驾驶汽车如何工作时,车道检测是基本概念之一。在本文中,我们将构建一个程序,可以识别图片或视频中的车道线,并了解霍夫变换如何在实现这项任务中发挥巨大作用。霍夫变换几乎是在感兴趣区域中检测直线的最后一步。因为知道我们如何到达那个阶段也很重要,所以在我们经历每一步时要有耐心。
目录
- 项目设置
- 加载和显示图像
- Canny 边缘检测:灰度化,降噪,Canny 方法
- 隔离感兴趣区域(ROI)
- 霍夫变换技术
- 实现霍夫变换
- 检测视频中的车道
- 最后的想法
项目设置
当人类驾驶汽车时,我们用眼睛看到车道。但由于汽车不能做到这一点,我们使用计算机视觉让它“看到”车道线。我们将使用 OpenCV 来读取和显示图像中的一系列像素。
为了开始,
- 安装这张图片并以 JPEG 文件的形式保存在一个文件夹中。
- 打开一个 IDE 并在同一文件夹中创建一个 python 文件。我们把它命名为
lanes.py
- 使用命令-
pip install opencv-contrib-python
从终端安装 openCV
加载并显示图像
openCV 库有一个名为cv2.imread()
的函数,它从我们的文件中加载图像,并将其作为多维 NumPy 数组返回。
import cv2
image = cv2.imread('lane.jpg')
NumPy 数组表示图像中每个像素的相对强度。
我们现在有了数组形式的图像数据。下一步是用一个名为imshow()
的函数渲染我们的图像。这需要两个参数——第一个是显示我们图像的窗口的名称,第二个是我们想要显示的图像。
import cv2
image = cv2.imread('lane.jpg')
cv2.imshow('result', image)
cv2.waitKey(0)
waitKey()
功能允许我们在指定的毫秒数内显示图像。这里的“0”表示无限显示图像的功能,直到我们按下键盘上的任何键。
打开终端,用python lanes.py
运行程序,你会看到屏幕上显示的图像。
Canny 边缘检测
在这一节中,我们将讨论 canny 边缘检测,这是一种我们将用来编写检测图像边缘的程序的技术。因此,我们试图找到图像中强度和颜色发生急剧变化的区域。
请记住,图像可以作为矩阵(像素阵列)来读取,这一点很重要。像素包含图像中某个位置的光强度。每个像素的亮度由从 0 到 255 排列的数值表示。0 值表示无强度(黑色),255 表示最大强度(白色)。
注意:梯度是一系列像素亮度变化的量度。强梯度表示剧烈的变化,而小梯度表示轻微的变化。
梯度的增强有助于我们识别图像中的边缘。边缘由相邻像素的亮度值的差异来定义。每当你看到强度的急剧变化或亮度的快速变化时,在渐变图像中就会有相应的亮像素。通过追踪这些像素,我们获得了边缘。
现在,我们将应用这种直觉来检测图像的边缘。这个过程包括三个步骤。
步骤 1:灰度调整
将我们的图像转换为灰度的原因是为了便于处理。与具有三个以上值的彩色图像相比,灰度图像只有一个像素强度值(0 或 1)。这将使灰度图像在单一通道中工作,这将比三通道彩色图像更容易和更快地进行处理。
为了在代码中实现这一点,我们将在 NumPy 的帮助下复制之前创建的图像数组。
image = cv2.imread('lane.jpg')
lane_image = np.copy(image) #creating copy of the image
重要的是创建一个image
变量的副本,而不是设置新变量等于图像。这样做将确保lane_image
中所做的更改不会影响image
。
现在,我们借助 openCV 库中的cvtColor
函数将图像转换成灰度。
gray = cv2.cvtColor(lane_image, cv2.COLOR_RGB2GRAY) #converting to gray-scale
标志COLOR_RGB2GRAY
作为第二个参数传递,帮助将 RGB 颜色转换为灰度图像。
为了输出这个转换,我们需要在结果窗口中传递gray
。
import cv2
import numpy as np
image = cv2.imread('lane.jpg')
lane_image = np.copy(image)
gray = cv2.cvtColor(lane_image, cv2.COLOR_RGB2GRAY)
cv2.imshow('result', gray) #to output gray-scale image
cv2.waitKey(0)
如果我们运行这个程序,得到的图像应该如下所示。
第二步:减少噪音和平滑
尽可能多地识别图像中的边缘非常重要。但是,我们还必须过滤任何图像噪声,这些噪声会产生伪边缘,并最终影响边缘检测。这种降噪和图像平滑将通过一种叫做高斯模糊的过滤器来完成。
请记住,图像存储为离散像素的集合。灰度图像中的每个像素由描述像素亮度的单个数字表示。为了平滑图像,我们需要用像素周围像素亮度的平均值来修改像素的值。
平均像素以减少噪声将通过内核来完成。这个正态分布数字的内核窗口贯穿我们的整个图像,并将每个像素值设置为等于其相邻像素的加权平均值,从而平滑我们的图像。
为了用代码表示这个卷积,我们在灰度图像上应用了cv2.GaussianBlur()
函数。
blur = cv2.GaussianBlur(gray, (5, 5), 0)
这里,我们在图像上应用了一个 5 * 5 的内核窗口。内核的大小取决于具体情况,但在大多数情况下,5*5 的窗口是理想的。我们将变量blur
传递给imshow()
以获得输出。因此,获得高斯模糊图像的最终代码如下所示:
import cv2
import numpy as np
image = cv2.imread('lane.jpg')
lane_image = np.copy(image)
gray = cv2.cvtColor(lane_image, cv2.COLOR_RGB2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
cv2.imshow('result', blur) #to output gaussian image.
cv2.waitKey(0)
结果是通过卷积具有减少的噪声的高斯值的核而获得的模糊图像。
注意:这是理解高斯模糊的可选步骤。当我们在下一步执行 canny 边缘检测时,它会自动为我们执行。
第三步:精明的方法
为了理解这个概念,你必须回忆起图像也可以用 2D 坐标空间- X & Y 来表示
x 对应于图像的宽度(列数), Y 对应于图像的高度(行数)。宽度和高度的乘积给出了一幅图像的像素总数。这告诉我们,我们不仅可以将图像表示为数组,还可以表示为 X 和 Y 的连续函数,即: f(x,y) 。
由于 f(x,y) 是一个数学函数,我们可以执行运算来确定图像中像素亮度的快速变化。canny 方法会给出函数在 x 和 y 方向的导数。我们可以用这个导数来测量相邻像素的亮度变化。
导数的小变化对应于强度的小变化,反之亦然。
通过计算所有方向的导数,我们得到图像的梯度。在我们的代码中调用cv2.Canny()
函数为我们执行所有这些动作。
cv2.Canny(image, low_threshold, high_threshold)
该函数将最强的梯度追踪为一系列白色像素。两个参数low_threshold
和high_threshold
允许我们隔离遵循最强梯度的相邻像素。如果梯度大于上限阈值,则它被识别为边缘像素。如果低于下限,就会被拒绝。阈值之间的梯度仅在连接到强边缘时才被接受。
对于我们的例子,我们将采用 1:3 的高低阈值比率。这次我们输出的是清晰的图像,而不是模糊的图像。
canny = cv2.Canny(blur, 50, 150) #to obtain edges of the image
cv2.imshow('result', Canny)
cv2.waitKey(0)
生成的图像如下所示:
隔离感兴趣区域(ROI)
在我们教我们的模型检测之前,我们必须指定感兴趣的区域来检测车道线。
在这种情况下,让我们把感兴趣的区域作为道路的右侧,如图所示。
由于我们的代码中有很多变量和函数,现在是时候通过定义一个函数来包装一切了。
import cv2
import numpy as np
def canny(image):
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
blur = cv2.GaussianBlur(gray,(5, 5), 0)
canny = cv2.Canny(blur, 50, 150)
return canny
image = cv2.imread('lane.jpg')
lane_image = np.copy(image)
canny = cv2.Canny(lane_image)
cv2.imshow('result', canny)
cv2.waitKey(0)
为了阐明我们感兴趣区域的确切位置,我们使用 matplotlib 库来定位坐标并隔离该区域。
import cv2
import numpy as np
import matplotlib.pyplot as plt
def canny(image):
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
blur = cv2.GaussianBlur(gray,(5, 5), 0)
canny = cv2.Canny(blur, 50, 150)
return canny
image = cv2.imread('lane.jpg')
lane_image = np.copy(image)
canny = cv2.Canny(lane_image)
plt.imshow(canny)
plt.show()
运行这个程序会得到一个图像,其中 X 和 Y 坐标对应于我们感兴趣的区域。
现在我们已经有了 ROI 所需的测量值,我们将生成一个图像来掩盖其他所有的东西。我们得到的图像是一个带有原始图像中指定顶点的多边形的遮罩。
def region_of_interest(image):
height = image.shape[0]
polygons = np.array([
[(200, height), (1100, height), (550, 250)]
])
mask = np.zeros_like(image)
cv2.fillPoly(mask, polygons, 255)
return mask
...
cv2.imshow('result', region_of_interest(canny)) #changing it to show ROI instead of canny image.
...
上面的代码非常简单明了。它返回我们视野的封闭区域,这个区域是三角形的。
这个图像很重要,因为它代表了我们感兴趣的区域,具有很强的梯度差异。三角形区域中的像素亮度值为 255,而图像的其余部分为零。由于此图像与我们的原始图像具有相同的测量值,因此它可以用来轻松地从我们之前生成的 canny 图像中提取线条。
我们可以通过利用 OpenCV 的cv2.bitwise_and()
函数来实现这一点,该函数计算两幅图像的逐位& ,以仅显示由遮罩的多边形轮廓跟踪的 ROI。关于使用bitwise_and
的更多信息,请参考 openCV 文档。
def region_of_interest(image):
height = image.shape[0]
polygons = np.array([
[(200, height), (1100, height), (550, 250)]
])
mask = np.zeros_like(image)
cv2.fillPoly(mask, polygons, 255)
masked_image = cv2.bitwise_and(image, mask)
return masked_image
...
cropped_image = region_of_interest(canny)
cv2.imshow('result', cropped_image)
...
您应该会看到如下所示的输出-
这个过程的最后一步是使用霍夫变换来检测我们感兴趣的孤立区域中的直线。
霍夫变换技术
我们现在拥有的图片只是一系列像素,我们无法直接找到几何表示来了解斜率和截距。
由于图像从来都不是完美的,我们不能遍历像素来寻找斜率和截距,因为这是一项非常困难的任务。这就是可以使用霍夫变换的地方。它帮助我们找出突出的线条,并连接图像中不相交的边缘点。
我们通过比较正常的 X-Y 坐标空间和霍夫空间 (M-C 空间)来理解这个概念。
XY 平面上的一个点可以有任意多条线穿过。同样的道理,如果我们在同一个 XY 平面上取一条线,许多点都通过这条线。
为了识别图片中的线条,我们必须将每个边缘像素想象为坐标空间中的一个点,我们将把该点转换为霍夫空间中的一条线。
我们需要找到两条或更多条表示它们在霍夫空间中相交的对应点(在 XY 平面上)的线来检测这些线。这样我们就知道这两点属于同一条线。
从一系列点中寻找可能的线的想法就是我们如何在我们的梯度图像中找到线。但是,模型也需要线条的参数来识别它们。
为了获得这些参数,我们首先将霍夫空间分成一个包含小方块的网格,如图所示。对应于具有最多交点的正方形的 c 和 m 的值将用于绘制最佳拟合线。
这种方法适用于有坡度的线。但是,如果我们处理的是一条直线,斜率将永远是无穷大,我们无法精确地计算这个值。所以,我们在方法上做了一点小小的改变。
我们不是将我们的线方程表示为 y = mx + c ,而是在极坐标系中表示,即;ρ= xcosθ+ysinθ。
- ρ =垂直于 原点的距离。
- θ= x 轴法线的倾斜角。
通过在极坐标中表示直线,我们在霍夫空间中得到正弦曲线而不是直线。对于通过我们点的线的 ρ 和 θ 的所有不同值,确定该曲线。如果我们有更多的点,他们在我们的霍夫空间创造更多的曲线。与前一种情况类似,将采用与相交最多的曲线相对应的值来创建最佳拟合线。
实现霍夫变换
既然我们终于有了识别图像中线条的技术,让我们在代码中实现它。幸运的是,openCV 已经有一个名为cv2.HoughLinesP()
的函数可以用来为我们完成这项任务。
lines = cv2.HoughLinesP(cropped_image, 2, np.pi/180, 100, np.array([]), minLineLength=40, maxLineGap=5)
第一个参数是之前生成的裁剪图像,它是孤立车道线的渐变图像。第二个和第三个参数指定了霍夫累加器数组的分辨率(为识别大多数交叉点而创建的网格)。第四个参数是确定检测一行所需的最小投票数所需的阈值。
在真实图像中显示这些线条之前,让我们定义几个函数来表示它们。我们将定义 3 个函数来优化和显示车道线。
- display_lines: 我们定义这个函数,用与原始图像相似的度量在黑色图像上标记线条,然后将其融合到我们的彩色图像中。
def display_lines(image, lines):
line_image = np.zeros_like(image)
if lines is not None:
for x1, y1, x2, y2 in lines:
cv2.line(line_image, (x1, y1), (x2, y2), (255, 0, 0), 10)
return line_image
我们利用cv2.addWeight()
来组合线图像和彩色图像。
line_image = display_lines(lane_image, averaged_lines)
combo_image = cv2.addWeighted(lane_image, 0.8, line_image, 1, 1)
- make_coordinates: 这将为我们指定坐标,以便标记斜率和 y 轴截距。
def make_coordinates(image, line_parameters):
slope, intercept = line_parameters
y1 = image.shape[0]
y2 = int(y1*(3/5))
x1 = int((y1 - intercept)/slope)
x2 = int((y2 - intercept)/slope)
return np.array([x1, y1, x2, y2])
- average_slope_intercept: 我们首先声明两个空列表-
left_fit
和right_fit
,它们将分别包含左边平均线的坐标和右边线的坐标。
def average_slope_intercept(image, lines):
left_fit = []
right_fit = []
for line in lines:
x1, y1, x2, y2 = line.reshape(4)
parameters = np.polyfit((x1, x2), (y1, y2), 1)
slope = parameters[0]
intercept = parameters[1]
if slope < 0:
left_fit.append((slope, intercept))
else:
right_fit.append((slope, intercept))
left_fit_average = np.average(left_fit, axis=0)
right_fit_average = np.average(right_fit, axis=0)
left_line = make_coordinates(image, left_fit_average)
right_line = make_coordinates(image, right_fit_average)
return np.array([left_line, right_line])
np.polyfit()
将为我们提供拟合我们的点的直线参数,并返回一个描述斜率和 y 截距的系数向量。
最后,我们展示了完全检测到车道线的combo_image()
。
cv2.imshow('result', combo_image)
检测视频中的车道
遵循相同的过程来检测视频中的车道。OpenCV 的 VideoCapture 对象使这变得很容易。该对象允许您逐帧读取并执行所需的操作。
一旦你下载了这个视频,将它移动到你当前工作的项目文件夹中。
我们为视频设置了一个视频捕获对象,并应用我们已经实现的算法来检测视频中的线条,而不是静态图像。
cap = cv2.VideoCapture('test2.mp4')
while(cap.isOpened()):
_, frame = cap.read()
canny_image = canny(frame)
cropped_image = region_of_interest(canny_image)
lines = cv2.HoughLinesP(cropped_image, 2, np.pi/180, 100, np.array([]), minLineLength=40, maxLineGap=5)
averaged_lines = average_slope_intercept(frame, lines)
line_image = display_lines(frame, averaged_lines)
combo_image = cv2.addWeighted(frame, 0.8, line_image, 1, 1)
cv2.imshow('result',combo_image)
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
注意:在尝试实现上述功能时,最好注释掉与静态图像相关的代码,稍后再使用 。
#image = cv2.imread('lane.jpg')
#lane_image = np.copy(image)
#canny_image = canny(lane_image)
#cropped_image = region_of_interest(canny_image)
#lines = cv2.HoughLinesP(cropped_image, 2, np.pi/180, 100, np.array([]), minLineLength=40, maxLineGap=5)
#averaged_lines = average_slope_intercept(lane_image, lines)
#line_image = display_lines(lane_image, averaged_lines)
#combo_image = cv2.addWeighted(lane_image, 0.8, line_image, 1, 1)
#cv2.imshow('result',combo_image)
#cv2.waitKey(0)
如果一切顺利,您将在视频中看到相同的车道线。
这有很多解释,在某些时候混淆是很正常的。所以,这就是你完成后在lanes.py
上的整个代码应该看起来的样子。
import cv2
import numpy as np
def make_coordinates(image, line_parameters):
slope, intercept = line_parameters
y1 = image.shape[0]
y2 = int(y1*(3/5))
x1 = int((y1 - intercept)/slope)
x2 = int((y2 - intercept)/slope)
return np.array([x1, y1, x2, y2])
def average_slope_intercept(image, lines):
left_fit = []
right_fit = []
for line in lines:
x1, y1, x2, y2 = line.reshape(4)
parameters = np.polyfit((x1, x2), (y1, y2), 1)
slope = parameters[0]
intercept = parameters[1]
if slope < 0:
left_fit.append((slope, intercept))
else:
right_fit.append((slope, intercept))
left_fit_average = np.average(left_fit, axis=0)
right_fit_average = np.average(right_fit, axis=0)
left_line = make_coordinates(image, left_fit_average)
right_line = make_coordinates(image, right_fit_average)
return np.array([left_line, right_line])
def canny(image):
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
blur = cv2.GaussianBlur(gray,(5, 5), 0)
canny = cv2.Canny(blur, 50, 150)
return canny
def display_lines(image, lines):
line_image = np.zeros_like(image)
if lines is not None:
for x1, y1, x2, y2 in lines:
cv2.line(line_image, (x1, y1), (x2, y2), (255, 0, 0), 10)
return line_image
def region_of_interest(image):
height = image.shape[0]
polygons = np.array([
[(200, height), (1100, height), (550, 250)]
])
mask = np.zeros_like(image)
cv2.fillPoly(mask, polygons, 255)
masked_image = cv2.bitwise_and(image, mask)
return masked_image
#image = cv2.imread('lane.jpg')
#lane_image = np.copy(image)
#canny_image = canny(lane_image)
#cropped_image = region_of_interest(canny_image)
#lines = cv2.HoughLinesP(cropped_image, 2, np.pi/180, 100, np.array([]), minLineLength=40, maxLineGap=5)
#averaged_lines = average_slope_intercept(lane_image, lines)
#line_image = display_lines(lane_image, averaged_lines)
#combo_image = cv2.addWeighted(lane_image, 0.8, line_image, 1, 1)
#cv2.imshow('result',combo_image)
#cv2.waitKey(0)
cap = cv2.VideoCapture('test2.mp4')
while(cap.isOpened()):
_, frame = cap.read()
canny_image = canny(frame)
cropped_image = region_of_interest(canny_image)
lines = cv2.HoughLinesP(cropped_image, 2, np.pi/180, 100, np.array([]), minLineLength=40, maxLineGap=5)
averaged_lines = average_slope_intercept(frame, lines)
line_image = display_lines(frame, averaged_lines)
combo_image = cv2.addWeighted(frame, 0.8, line_image, 1, 1)
cv2.imshow('result',combo_image)
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
最后的想法
这是车道检测模型的一个简单例子。你可以进一步推进这个项目,使算法适应实时视频数据,并在汽车移动时检测车道线。
恭喜你走到这一步!希望本教程对您有所帮助,并探索与此概念相关的更多领域。
理解和解释卷积神经网络架构
原文:https://blog.paperspace.com/understanding-interpreting-convolutional-neural-network-architectures/
在对卷积、填充和汇集等卷积神经网络过程形成直觉后,自然的下一步是将它们以协同工作的方式组合在一起,以实现特定的目标。当所有这些过程放在一起,一个架构就产生了。
体系结构的配置在很大程度上决定了某个网络的性能。在本文中,我们将研究 CNN 架构,以了解图像从一层传递到另一层时,在空间维度上直到输出层会发生什么。
本文中使用的符号
指定矩阵/阵列尺寸的默认符号是从行数(高度)开始,到列数(宽度),然后是通道数,最后是批量。按照这个符号,将指定 15 个大小为 250×320 像素的矩阵和 3 个通道(250, 320, 3, 15)
。
然而,在本文中,我们将使用 PyTorch 表示法,从批量开始,到通道数,然后是行数,最后是列数。使用这种符号,上一段中的矩阵将被指定为(15, 3, 250, 320)
。请记住这一点。
通道上的卷积
在以前的一篇文章中,我提到了如何使用卷积过程从图像中提取特征。为了便于说明,我使用了单通道图像(灰度),即由单个像素阵列组成的图像。在大多数情况下,事实上在关于卷积神经网络的所有情况下,卷积运算被应用于具有多于一个通道的图像或一组特征图。
考虑打算对彩色图像执行卷积的情况。众所周知,彩色图像具有 3 个 RGB 通道(3 个阵列),为了从该图像产生单个特征图(例如检测边缘),需要具有相应数量通道的滤波器。
实质上,这意味着图像中的每个通道都有一个卷积滤波器,对应的卷积实例的结果在通道间相加,从而在特征图中产生单个像素。用更专业的术语来说,为了产生单个特征图,使用 *(3, 3, 3)*
(3 通道,3 行,3 列)滤波器对 *(3, 6, 6)*
(3 通道,6 行,6 列)图像进行卷积。按照相同的逻辑,如果想要从该图像产生 10 个特征图,那么需要 10 个(3, 3, 3)
滤波器,即(10, 3, 3, 3)
。
滑动窗口操作和特征图尺寸
卷积和池化等滑动窗口操作构成了卷积神经网络中的绝大多数操作。值得记住的是,这些滑动窗口操作经常会修改图像/特征图的尺寸。其中一些修改是有意的,而另一些是使用填充来控制生成的表示发生什么的副产品。
正如我在这篇文章中提到的,我来解释一下,如果大小为(m, n)
的过滤器/内核滑过大小为(x, y)
的图像,那么大小为(x-m+1, y-n+1)
的图像就产生了。现在,当这些运算中涉及大于 1 的步长和填充时,该公式可以重写为:
因此,如果使用步幅 2 在(55, 55)
图像上滑动(3, 3)
滤镜,则产生大小为(27, 27)
(({55-3}/2 + 1,{55-3}/2 + 1))的表示。
激活功能
激活函数是神经网络结构中的常见现象,因为它们在网络性能中起着至关重要的作用。它们的目的是增加网络本身的非线性,因为它们迫使网络学习输入和输出之间更复杂的映射/关系(在下一篇文章中有更多介绍)。
在卷积神经网络架构的上下文中,最常用的是校正线性单元激活函数 ReLU 或它的一些其他变体(参数 ReLU,Leaky-ReLU)。此激活功能实质上是检查要素地图中的像素,如果像素值大于零,则保持不变;如果像素值小于零(负值),则将其设置为零。更简单地说,ReLU 激活功能关闭特征图中不太重要的像素。
卷积神经网络中的正则化
正规化指的是机器学习中使用的技术,以防止模型过度拟合。在深度学习中,两种主要的正则化技术被称为批归一化和丢失。
批量标准化通过标准化使用它的层中的所有输入来工作,它具有防止神经网络中的内部协变量变化和加速模型训练的独特优势。一些研究认为它最适合卷积层。
另一方面,dropout 通过在应用它的层中随机置零某些神经元来工作,通过这样做,它在训练期间为每批数据模拟稍微不同的网络架构,从而防止本地网络过度拟合数据。一些文献表明它在线性图层上效果最好。
神经网络体系结构中的章节
神经网络由几层组成。这些层可以分为三个部分,如输入层,隐藏层和输出层。
顾名思义,输入层是神经网络的一部分,数据通过它输入。另一方面,隐藏层是网络中位于输入层和输出层之间的层。网络的这一部分(隐藏层)负责数据的计算和数值转换,而输出层是网络的最终层,在这里获得结果/输出。
AlexNet 架构
AlexNet 是 Alex Krizhevsky、Ilya Sutskever 和 Geoffery Hinton 于 2012 年设计的卷积神经网络架构( Krizhevsky 等 ,2012 )。虽然当时有点过时,但这种架构在当时是相当具有突破性的,因为它有助于形成常见卷积神经网络最佳实践的基础,例如 ReLU 非线性的主要使用,以及网络性能随着深度的增加而提高的断言。它也非常简单,易于理解和解释。
使用上面看到的 AlexNet 架构的图形表示,让我们尝试使用 PyTorch 复制这个架构。此后,我们将对每个图层进行遍历,以了解图像如何从输入图层中的原始像素到隐藏图层中的特征地图,最后到输出图层中的分类向量。
class AlexNet(nn.Module):
def __init__(self):
super().__init__()
# instantiating network classes
self.conv1 = nn.Conv2d(3, 96, (11, 11), stride=4)
self.pool1 = nn.MaxPool2d((3, 3), stride=2)
self.conv2 = nn.Conv2d(96, 256, (5, 5), padding=2)
self.pool2 = nn.MaxPool2d((3, 3), stride=2)
self.conv3 = nn.Conv2d(256, 384, (3, 3), padding=1)
self.conv4 = nn.Conv2d(384, 384, (3, 3), padding=1)
self.conv5 = nn.Conv2d(384, 256, (3, 3), padding=1)
self.pool5 = nn.MaxPool2d((3, 3), stride=2)
self.dense1 = nn.Linear(9216, 4096)
self.dense2 = nn.Linear(4096, 4096)
self.dense3 = nn.Linear(4096, 1000)
self.dropout = nn.Dropout(p=0.5)
def forward(self, x):
#-------------------
# INPUT IMAGE(S)
#-------------------
input = x.view(-1, 3, 227, 227) # -> (3, 227, 227)
#-------------------
# INPUT LAYER
#-------------------
# convolution -> activation -> pooling
layer_1 = self.conv1(x) # -> (96, 55, 55)
output_1 = F.relu(layer_1)
output_1 = self.pool1(output_1) # -> (96, 27, 27)
#-------------------
# HIDDEN LAYERS
#-------------------
# convolution -> activation -> pooling
layer_2 = self.conv2(output_1) # -> (256, 27, 27)
output_2 = F.relu(layer_2)
output_2 = self.pool2(output_2) # -> (256, 13, 13)
# convolution -> activation
layer_3 = self.conv3(output_2) # -> (384, 13, 13)
output_3 = F.relu(layer_3)
# convolution -> activation
layer_4 = self.conv4(output_3) # -> (384, 13, 13)
output_4 = F.relu(layer_4)
# convolution -> activation -> pooling
layer_5 = self.conv5(output_4) # -> (256, 13, 13)
output_5 = F.relu(layer_5)
output_5 = self.pool5(output_5) # -> (256, 6, 6)
# flattening feature map
flattened = output_5.view(-1, 9216) # (256*6*6 = 9216)
# full connection -> activation -> dropout
layer_6 = self.dense1(flattened) # -> (1, 4096)
output_6 = F.relu(layer_6)
output_6 = self.dropout(output_6)
# full connection -> activation -> dropout
layer_7 = self.dense2(layer_6) # -> (1, 4096)
output_7 = F.relu(layer_7)
output_7 = self.dropout(output_7)
#--------------------
# OUTPUT LAYER
#--------------------
layer_8 = self.dense3(layer_7) # -> (1, 1000)
output_8 = torch.sigmoid(layer_8)
return output_8
该架构通过首先在 init 方法中实例化所需的卷积、池化、线性和丢弃方法来实现。此后,网络按正向方法组合在一起。这个实现也可以使用 PyTorch 顺序方法来完成,但是我决定不这样做,因为解释起来会很麻烦。您可能会发现上面的代码注释有点多,这是为了便于理解和解释,下面提供了 forward 方法中所有层的遍历。
使用的神经网络方法
在这一节中,我们将仔细研究在 init 方法中实例化的神经网络方法。每个方法都被恰当地命名,以使它与 forward 方法中使用它的层相匹配。
自我,conv1
这是一种二维卷积方法,它接收 3 个通道的图像或特征图,并使用(11, 11)
过滤器从中产生 96 个通道,过滤器在每个实例后取 4 个像素的步长。
自助池 1
这是一种二维最大池方法,它利用一个(3, 3)
过滤器/内核,有效地将特征图的尺寸向下采样一半,因为它利用了 2 的步幅。这是一个重叠池的例子,因为内核的大小不等于它的跨度(3!= 2).
自我,conv2
这是一种二维卷积方法,它接收 96 个通道的特征图,并使用默认步长为 1 的(5, 5)
滤波器从这些特征图中产生 256 个通道,两层填充意味着所产生的通道不会缩减尺寸。
自助池 2
就像 self.pool1 一样,这是一种二维最大池化方法,它利用一个(3, 3)
过滤器/内核来有效地将特征地图下采样一半大小,因为它利用了 2 的跨距。
自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编
这是一种二维卷积方法,它接收 256 个通道的特征图,并使用默认步幅为 1 的(3, 3)
滤波器从这些特征图中产生 384 个通道,以及单个填充层,这意味着所产生的通道不会缩减尺寸。
自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编自编
这是一种二维卷积方法,它接收 384 个通道的特征图,并再次使用默认步幅为 1 的(3, 3)
滤波器从这些特征图中产生 384 个通道,并且具有单个填充层,这意味着所产生的表示不会减少维度。
自我,conv5
self.conv5 似乎与 self.conv3 相反,它是一种二维卷积方法,可接收 384 个通道的特征图,并使用默认步幅为 1 的(3, 3)
滤波器从中产生 256 个通道,以及单个填充层,这意味着所产生的通道不会缩减尺寸。
自助游泳池 5
就像 self.pool1 和 2 一样,这是一种二维最大池化方法,它利用一个(3, 3)
过滤器/内核有效地将特征图的大小向下采样一半,因为它利用了步长 2。
自密实 1
这是一个线性方法,它接收 9216 个元素的向量(大小为(1, 9216)
)并将它们连接到另一个 4096 个元素的向量(大小为(1, 4096)
)。
自我浓缩 2
这是一个线性方法,它接收 4096 个元素的向量(大小为(1, 4096)
)并将它们连接到另一个 4096 个元素的向量(大小为(1, 4096)
)。
自我浓缩 3
这是一个线性方法,它接收 4096 个元素的向量(大小为(1, 4096)
)并将它们连接到另一个 1000 个元素的向量(大小为(1, 1000)
)。
自我辍学
这是一种用于控制过拟合的辍学正则化方法,通过选择以 50%的概率忽略随机选择的神经元的输出。
输入图像
正演方法中的参数“x”称为输入图像。这是为了分类目的而传递到网络的图像或一批图像。由于 AlexNet 架构指定了大小为 227 像素 x 227 像素的彩色输入图像(3, 227, 227)
,输入被视为大小为(-1, 3, 227, 227)
的张量。第一维考虑了批量大小,并被指定为-1
,以允许网络接受任何批量大小(通常在训练期间是 8 的倍数,在生产中低至 1)。
输入层
在层 1 中,由于我们试图使用(11, 11)
滤波器产生 96 个特征图,大小为(3, 227, 227)
的单个输入图像被大小为(3, 11, 11)
的 96 个步长=4 的滤波器卷积,从而产生大小为(96, 55, 55)
的特征图。如公式*({x + 2p - m}/s + 1, {y + 2p - n}/s + 1)*
所示,图像的尺寸从(227, 227)
减小到(55, 55)
。请注意,填充保留为默认值 0,因此本例中 p=0。
接下来,使用 ReLU 非线性来激活所产生的特征图,并使用(3, 3)
内核和步幅=2 来将它们下采样到(96, 27, 27)
中。特征图变成了 27 x 27 像素,因为使用了带有(3, 3)
内核的重叠池,步长为 2。再次,记住 ({x + 2p - m}/s + 1, {y + 2p - n}/s + 1)
。
第二层
在层 2 中,将使用(5, 5)
过滤器从来自层 1(大小(96, 27, 27)
)的特征图中产生 256 个特征图。为此,使用了 256 个(96, 5, 5)
大小的过滤器。由于填充设置为 2,生成的特征图不会缩小,并作为(256, 27, 27)
返回。此后,激活被完成,随后用(3, 3)
滤波器和步幅=2 合并,产生大小为(256, 13, 13)
的特征图。
第三层
在层 3 中,将使用(3, 3)
过滤器从层 2 的输出(大小(256, 13, 13)
)中产生 386 个特征地图。为此,使用了 386 个(256, 5, 5)
大小的过滤器。由于填充设置为 1,生成的特征地图保持相同的大小。激活后没有池。
第 4 层
关于层 4,将使用(3, 3)
滤波器从层 3 的输出(大小(386, 13, 13)
)产生 386 个特征图。为此,使用了 386 个(386, 5, 5)
大小的过滤器。由于填充设置为 1,生成的特征地图保持相同的大小。同样,池化不是在激活后完成的,如网络体系结构中所规定的。
第五层
第 5 层看起来与第 3 层相反,因为将使用(3, 3)
过滤器从第 4 层的输出(大小(386, 13, 13)
)产生 256 个特征地图。为此,使用了 256 个(386, 5, 5)
大小的过滤器。由于填充设置为 1,生成的特征地图保持相同的大小。在激活后进行池化,以将特征映射下采样到(256, 6, 6)
。
第六层
就在第 6 层之前,特征地图被展平,因为它们将被送入仅接受矢量的密集层。为了展平特征图,从第 5 层作为输出返回的特征图的所有维度被相乘(25666 = 9216)并被整形为向量(大小(1, 9216)
)。这个向量然后被馈送到第 6 层,以产生另一个 4096 个元素的向量(大小(1, 4096)
)。然后,生成的矢量被激活,并在其后应用漏失。
第 7 层
从层 6 作为输出返回的 4096 个元素向量被馈送到该层,该层再次产生相同数量元素的另一个向量。在再次应用 dropout 之前,会激活 ReLU。
输出层
第 8 层,也称为输出层,从第 7 层接收 4096 个元素向量,并产生 1000 个元素向量(大小(1, 1000)
)作为最终输出。由于在包含 1000 个类的 ImageNet 数据集上使用了 AlexNet 架构,因此产生了 1000 个元素的向量作为最终输出。然后,将 sigmoid 激活应用于最终输出,以返回概率。
卷积神经网络中的参数
神经网络中的参数是将神经元从一层连接到另一层的偏差和权重。这个定义更适合于线性层中的参数,对于卷积层中的参数,参数可以说是卷积层中包含的滤波器中的元素总数(这些是权重)及其偏差。
确定某个神经网络架构中存在的参数数量是一件常见的事情。例如,具有 3 个滤波器的单个卷积层,每个滤波器具有 3 个信道和大小(3, 3)
,总共具有 3 个偏差(每个滤波器一个)和 81 个权重(333*3),导致总共 84 个参数。
利用这些知识,让我们试着确定上面 AlexNet 架构中的参数数量。
层 | 砝码 | 偏见 | 因素 |
---|---|---|---|
第一层 | 11x11x3x96 = 34,848 | Ninety-six | Thirty-four thousand nine hundred and forty-four |
第二层 | 5x5x96x256 = 614,400 | Two hundred and fifty-six | Six hundred and fourteen thousand six hundred and fifty-six |
第三层 | 3x3x256x384 = 884,736 | Three hundred and eighty-four | Eight hundred and eighty-five thousand one hundred and twenty |
第 4 层 | 3x3x384x384 = 1,327,104 | Three hundred and eighty-four | One million three hundred and twenty-seven thousand four hundred and eighty-eight |
第五层 | 3x3x384x256 = 884,736 | Two hundred and fifty-six | Eight hundred and eighty-four thousand nine hundred and ninety-two |
第六层 | 9216x4096 = 37,748,736 | Four thousand and ninety-six | Thirty-seven million seven hundred and fifty-two thousand eight hundred and thirty-two |
第 7 层 | 4096x4096 = 16,777,216 | Four thousand and ninety-six | Sixteen million seven hundred and eighty-one thousand three hundred and twelve |
第八层 | 4096x1000 = 4,096,000 | One thousand | Four million and ninety-seven thousand |
总计 | 62378344 |
从上表可以看出,AlexNet 架构总共有大约 6200 万个参数。使用下面的函数可以得到相同的结果。
def number_of_parameters(network):
"""
This model derives the number of parameters
in a PyTorch neural network architecture
"""
params = []
# deriving parameters in the network
parameters = list(network.parameters())
for parameter in parameters:
# deriving total parameters per layer
total = parameter.flatten().shape[0]
params.append(total)
return sum(params)
# passing the AlexNet class to the function
number_of_parameters(AlexNet())
输出:
62378344
结束语
在本文中,我们可以看看卷积神经网络中的大多数常见过程,以及如何使用 AlexNet 架构将它们拼凑在一起以形成一个架构,用于演示目的。此后,我们检查了在原始论文中实现的 AlexNet 中图像从一层到另一层发生了什么。
还有更复杂的体系结构,但是其中的基本思想是一样的。希望这是理解和解释卷积神经网络架构的一个很好的起点。
利用深度学习基准计算 GPU 内存带宽
原文:https://blog.paperspace.com/understanding-memory-bandwidth-benchmarks/
在这篇文章中,我们将首先讨论 GPU 和 GPU 内存带宽的含义。然后,我们将讨论如何确定 GPU 内存带宽的引擎盖下。我们还将讨论 GPU 计算的原理、各代 GPU 之间的差异以及当前高端深度学习 GPU 基准的概述。
这篇博文概括介绍了 GPU的功能和能力。如果你想阅读关于深度学习的 Paperspace GPUs 的基准测试,请参考我们的最新一轮基准测试。
介绍
这里的想法是探索 GPU 的历史,它们是如何开发的,由谁开发的,GPU 内存带宽是如何确定的,相关的商业模式,以及 GPU 在各代之间发生的实质性变化。这将为我们提供足够的信息,以便深入研究专门关注 GPU 模型和微体系结构的深度学习基准。
在开发 GPU 加速的应用程序时,有多种技术和编程语言可供使用,但在本文中,我们将使用 CUDA(一种框架)和 Python 作为示例。
注意:这篇文章要求你具备 Linux 操作系统和 Python 编程语言的基础知识,因为我们将使用它,因为它在研究、工程、数据分析和深度学习领域非常普遍,这些领域都非常依赖于并行计算。
什么是 GPU?
缩写 GPU 代表图形处理单元,这是一种定制的处理器,旨在加快图形性能。您可以将 GPU 视为开发人员用来解决计算世界中一些最困难的任务的设备。GPU 可以实时处理大量数据,这使得它们适用于深度学习、视频制作和视频游戏开发。
今天,GPU 可以说是最受认可的,因为它们用于产生现代视频和视频游戏观众所期望的流畅图像。
虽然 GPU 和视频卡(或图形卡)这两个词有时使用相同,但两者之间有一点点不同。视频卡和 GPU 不共享内存,因此将数据写入视频卡和读取输出是两个独立的操作,可能会导致系统出现“瓶颈”。显卡只是 GPU 和计算机系统之间的主要链接。
大多数计算机系统不是依赖集成到主板上的 GPU,而是利用带有 GPU 的专用显卡来增强性能。
GPU 的一点历史
1999 年,Nvidia 首次向市场推出 GPU。Nvidia 的第一个 GPU 被称为 Geforce 256,它被设计为每秒至少容纳 1000 万个多边形。缩写“GPU”在 1999 年 GeForce 256 发布之前并不存在。这使得 Nvidia 可以铸造并声称拥有“世界上第一个 GPU”在 GeForce 256 之前,有不同的其他加速器,如图形倍增器。
Figure 1: GeForce 256, Image credit, Konstantine Lanzet, Wikipedia
与较新的 GPU 相比,GeForce 256 已经过时,而且动力不足,但它仍然是 3D 图形发展中的一张重要卡片。它是基于 220 纳米技术设计的,能够处理 50 千兆次浮点运算
GPU 内存带宽
GPU 内存带宽指的是总线在任何给定时间可以处理的潜在最大数据量,并在决定 GPU 检索和使用其帧缓冲区的速度方面发挥作用。内存带宽是每个新 GPU 最广泛宣传的指标之一,有许多型号每秒能够传输数百千兆字节。
GPU 内存带宽重要吗?
你的内存带宽越高越好。这是一个硬性规定。例如,内存带宽更大的显卡可以更快更准确地绘制图形。
带宽计数器可以用来判断程序是否访问了足够的内存。当您希望尽可能快地复制数据时,您可能希望可访问的系统内存范围很大。类似地,如果你的程序执行的算术运算少于所请求的系统内存量,你可能希望使用大量的带宽。
GPU 内存带宽的计算
要确定 GPU 内存带宽,首先必须了解一些基本概念(这些概念将在后面的计算中用到):
- 咬和咬是两回事。
- 我们有不同的内存数据速率
位和字节之间的差异|内存的数据速率
比特与字节
比特与字节的主要区别在于,一个比特是最基本的计算机内存类型,只能存储两种不同的值,但一个由八个比特组成的字节可以存储多达 256 种不同的值。
在计算中,位用小写字母“b”表示,即 Gbps
字节用大写字母“B”表示,即 GB/s
存储器的数据速率
数据速率是模块在给定时间内可以发送的位数。我们有三种数据速率的随机存取存储器:
- 单数据速率(SDR)
- 双倍数据速率(DDR)
- 四倍数据速率(QDR)
最常见的类型是 DDR,比如 DDR4 RAM。DDR 每个时钟周期发送两个信号。每个时钟周期都有一个上升和下降。
因此,QDR 每个时钟周期发送四个信号。
所获得的持续存储器带宽可以被计算为传输的字节与内核执行时间的比率。
一个典型的当代 GPU 可以提供等于或大于其峰值存储器带宽 80%的流结果。
GPU- STREAM 作为一种工具,可以告知应用程序开发人员内存带宽受限内核的性能。GPU-STREAM 是开源的,可以在 GitHub 上的这个资源库中找到。
例子:让我们计算一下 GTX 1660 - 2019 的 GDDR5 Nvidia GeForce 的 GPU 内存带宽。(内存时钟= 2001 MHz,有效内存。时钟= 8 Gbps,存储器总线宽度= 192 位)
解决方案:
第一步
计算有效内存时钟
有效内存时钟由下式给出,内存时钟* 2(上升和下降)*(数据速率式)
数学表达式:
有效内存时钟=内存时钟 2(上升和下降)(数据速率类型)
因此:
2001MHz * 2(上升和下降)* 2(DDR) = 8004 位
8004 位= 8Gbps
第二步
计算内存带宽
内存带宽=有效内存布*内存总线宽度/ 8
内存带宽:
8004bits * 192/8 = 192 096 字节= 192 GB/s
最终输出
GPU 内存带宽为 192 GB/秒
寻找各代 GPU 的内存带宽?
了解何时以及如何使用每种类型的内存对于最大化应用程序的速度有很大的影响。通常最好使用共享内存,因为使用共享内存的同一个框架内的线程可以交互。如果使用得当,共享内存再加上它的最高性能,是一个极好的“全能”选择。然而,在某些情况下,最好采用其他类型的可访问存储器。
根据内存带宽,GPU 有四种主要形式,即:
- 专用显卡
- 集成图形处理单元
- 混合图形处理
- 流处理和通用图形处理器
在低端台式机和笔记本电脑领域,最新的 GPU(混合图形处理)与集成显卡竞争。ATI 的 HyperMemory 和 Nvidia 的 TurboCache 就是这方面最频繁的改编。混合显卡的成本略高于集成显卡,但远低于独立显卡。两者都与主机交换内存,并具有适度的专用内存高速缓存,以补偿计算机 RAM 的过度响应。
大多数 GPU 都是基于内存带宽和其他大规模计算为某种目的而构建的:
- 深度学习与人工智能: 英伟达特斯拉/数据中心, AMD 镭龙本能。
- 视频制作和游戏: GeForce GTX RTX , Nvidia 泰坦,镭龙 VII ,镭龙和 Navi 系列
- 小型工作站:英伟达 Quadro ,英伟达 RTX , AMD FirePro , AMD 镭龙 Pro
- 云工作站: 英伟达特斯拉/数据中心, AMD Firestream
- 机器人: Nvidia Drive PX
嗯,一旦你对 GPU 应用中可访问的各种类型的内存有了一点了解,你就准备好学习如何以及何时有效地使用它们。
GPU 编程的语言解决方案 Python 的 CUDA
GPU 编程是一种使用 GPU 加速器执行高度并行的通用计算的技术。虽然 GPU 最初是为 3d 建模而创建的,但它们现在广泛用于广泛的计算。
除了图形渲染之外,GPU 支持的并行计算正被用于深度学习和其他并行化密集型任务。
在开发 GPU 加速的应用程序时,有多种技术和编程语言可供使用,但在本文中,我们将使用 CUDA 和 Python 作为示例。
让我们看看如何在 Python 中使用 CUDA,从在机器上安装 CUDA 开始。
安装 CUDA 系统
首先,您需要确保您的机器具备以下条件:
- 支持 CUDA 的 GPU
- 支持的 Linux 版本- Ubuntu 20.04
- GCC 安装在您的系统上
- 安装正确的内核管理器和开发包
然后安装 CUDA 。按照下面的说明在本地安装程序上安装 CUDA:
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda-repo-ubuntu2004-11-4-local_11.4.2-470.57.02-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu2004-11-4-local_11.2-470.57.02-1_amd64.deb
sudo apt-key add /var/cuda-repo-ubuntu2004-11-4-local/7fa2af80.pub
sudo apt-get update
sudo apt-get -y install cuda
你可以抛弃你的本地设备,换一个更强大的云机器,比如渐变笔记本或者核心机,来消除这个麻烦。除非另有说明,否则每台 Paperspace 机器都预装了 CUDA 和 CuPy,以方便用户的任何深度学习需求。
安装 CuPy 库
其次,因为 NumPy 是 Python 数据科学环境的基础库,我们将在本次会议中使用它。
利用 NumPy 的最简单方法是使用 CuPy,这是一个在 GPU 上复制 NumPy 功能的替代库。
Pip 可用于安装 CuPy 源包的稳定发布版本:
pip install cupy
确认 CuPy 安装正确
这一步是必要的,以确认 CuPy 可以增强您的系统到什么水平。为此,我们将通过在. py 文件中编写一个简单的 Python 脚本来实现。
注意:这一步需要你知道一个 Python 程序的基本文件结构。
下面的脚本将导入 NumPy 和 CuPy 库,以及时间库,它们将用于对处理单元进行基准测试。
import numpy as np
import cupy as cp
from time import time
标杆管理
现在让我们定义一个函数,用来比较 GPU 和 CPU。
def benchmark_speed(arr, func, argument):
start_time = time()
func(arr, argument) #your argument will be broadcasted into a matrix
finish_time = finish_time - start_time
return elapsed_time
然后,您必须创建两个矩阵:一个用于 CPU,一个用于 GPU。对于我们的矩阵,我们将选择 9999 乘 9999 的形式。
# store a matrix into global memory
array_cpu = np.random.randint(0, 255, size=(9999, 9999))
# store the same matrix to GPU memory
array_gpu = cp.asarray(array_cpu)
最后,我们将使用一个基本的加法函数来测试 CPU 和 GPU 处理器的效率。
# benchmark matrix addition on CPU by using a NumPy addition function
cpu_time = benchmark_speed(array_cpu, np.add, 999)
# you need to run a pilot iteration on a GPU first to compile and cache the function kernel on a GPU
benchmark_speed(array_gpu, cp.add, 1)
# benchmark matrix addition on GPU by using CuPy addition function
gpu_time = benchmark_speed(array_gpu, cp.add, 999)
# Compare GPU and CPU speed
faster_speed = (gpu_time - cpu_time) / gpu_time * 100
让我们将结果打印到控制台上
print(f"CPU time: {cpu_time} seconds\nGPU time: {gpu_time} seconds.\nGPU was {faster_speed} percent faster")
我们已经证实,整数加法在 GPU 上要快得多。如果您处理大量可以并行处理的数据,学习更多关于 GPU 编程的知识通常是值得的。正如您所见,对大矩阵采用 GPU 计算显著提高了性能。
如果您没有 GPU,您可以在 Gradient 笔记本中运行这段代码,亲自看看他们的 GPU 在这个基准测试任务中的表现。你可以在你已经拥有的渐变笔记本的任何单元格中运行这段代码,或者你可以用任何 GPU 作为机器类型来启动一台新机器。
深度学习和 GPU 之间的联系
人工智能(AI)正在快速变化,新的神经网络模型、方法和应用案例定期出现。因为没有一种技术对所有机器学习和深度学习应用都是最优的,所以在特定的使用场景下,GPU 可以提供优于其他不同硬件平台的独特优势。
今天的许多深度学习解决方案都依赖于 GPU 与 CPU 的协作。由于 GPU 具有巨大的处理能力,它们可以极大地加速受益于 GPU 并行计算设计的工作负载,例如图像识别。人工智能和深度学习是 GPU 技术最有趣的两个用途。
基准测试 GPU 的摘要
NVIDIA 的最新产品都包括在内,包括安培 GPU 一代。多 GPU 配置的功能,如四 RTX 3090 安排,也进行了评估。
本节涵盖了本地 GPU 的一些选项,由于其计算和内存性能以及与当前深度学习框架的连接性,这些本地 GPU 目前最适合深度学习培训和开发。
GPU 名称 | 描述 |
---|---|
GTX 1080TI | NVIDIA 用于深度学习的传统 GPU 于 2017 年推出,专为计算任务而设计,具有 11 GB DDR5 内存和 3584 个 CUDA 内核。它已经停产一段时间了,只是作为参考点添加的。 |
RTX 2080TI | RTX 2080 TI 于 2018 年第四季度推出。它拥有 5342 个 CUDA 内核,结构为 544 个 NVIDIA Turing 混合精度张量内核,具有 107 个张量 TFLOPS 的人工智能功能和 11 GB 的超快速 GDDR6 内存。这款 GPU 已于 2020 年 9 月停产,不再提供。 |
泰坦 RTX | 泰坦 RTX 由最强大的 TuringTM 架构驱动。泰坦 RTX 拥有 576 个张量内核和 24 GB 超快 GDDR6 内存,可提供 130 张量 TFLOPs 的加速度。 |
RTX 6000 帧 | Quadro RTX 6000 是著名的泰坦 RTX 的服务器变体,具有增强的多 GPU 鼓风机通风、扩展的虚拟化功能和 ECC 内存。它使用与泰坦 RTX 相同的 TuringTM 内核,拥有 576 个张量内核,可提供 130 张量 TFLOPs 的生产力以及 24 GB 的超快速 GDDR6 ECC 内存。 |
RTX 8000 帧 | 夸德罗 RTX 8000 是 RTX 6000 的兄弟姐妹。采用相同的 GPU 处理单元,但 GPU 内存翻倍(48 GB GDDR6 ECC)。事实上,它是目前拥有最高可访问 GPU 内存的 GPU,是内存密集型活动的理想选择。 |
RTX 3080 | 首批使用 NVIDIA AmpereTM 架构的 GPU 型号之一,具有改进的 RT 和张量内核以及新的实时多处理器。RTX 3080 拥有 10 GB 的超快 GDDR6X 内存和 8704 个 CUDA 内核。 |
RTX 3080 Ti | RTX 3080 的大兄弟,配备 12 GB 超快 GDDR6X 内存和 10240 个 CUDA 内核。 |
RTX 3090 | GeForce RTX 3090 属于 NVIDIA 的 AmpereTM GPU 一代的泰坦级。它由 10496 个 CUDA 内核、328 个第三代张量内核和创新的流多处理器提供支持。它和泰坦 RTX 一样,拥有 24 GB 的 GDDR6X 内存。 |
英伟达 RTX A6000 | 英伟达 RTX A6000 是夸德罗 RTX 6000 的基于安培的更新。它拥有与 RTX 3090 相同的 GPU 处理器(GA-102),但是它支持所有的 CPU 内核。因此,有 10752 个 CUDA 核心和 336 个第三代张量核心。此外,它的 GPU 内存是 RTX 3090 的两倍:48GB GDDR6 ECC。 |
然而,使用本地 GPU 有许多限制。首先,你受限于你的购买能力。对于大多数 GPU 拥有者来说,切换到不同的 GPU 要么是不可能的,要么是极其昂贵的。其次,这些主要是英伟达的 GeForce 30 和 workstation 系列 GPU。这些不是像 Tesla/数据中心 GPU 那样为处理大数据而设计的。
你可以在这里访问他们可用的 GPU 的完整列表:https://docs.paperspace.com/gradient/machines/
您还可以在这里访问他们的 GPU 的完整基准分析:https://blog.paperspace.com/best-gpu-paperspace-2022/
总结
您项目的最佳 GPU 将由您的人工智能操作的成熟度、您工作的规模以及您使用的特定算法和模型来决定。在前面的章节中已经提供了许多因素来帮助您根据自己的目的选择最佳的 GPU 或 GPU 组。
Paperspace 可以让你根据任何和所有深度学习任务的需要在不同的 GPU 之间切换,所以在购买昂贵的 GPU 之前,请尝试他们的服务。
理解程序
Image Source with adding the quote
这篇文章是关于一种革命性的 GANs,来自论文为了提高质量、稳定性和变化性而进行的 GANs 渐进生长。我们将仔细检查它,查看它的目标、损失函数、结果、实现细节,并分解它的组件来理解每一个组件。如果我们想从头看到它的实现,看看这个博客,在那里我们尽可能地复制了原始论文,并使用 PyTorch 使实现变得干净、简单和可读。
如果我们已经熟悉了 GANs,并且知道它们是如何工作的,请继续阅读本文,但如果不是,建议先看看这篇博文。
Gan 改进
在本节中,我们将了解 GAN 的改进。我们将看到 GANs 是如何随着时间的推移而发展的。在上图中,我们可以看到过去几年中 GANs 的改进速度。
- 2014 年伊恩·古德菲勒从论文生成对抗网络中创造了强大的人工智能概念 甘斯 ,赋予了机器想象力,但它们对超参数极其敏感,生成的图像看起来质量很低。我们可以看到第一张脸,它是黑白的,看起来几乎不像一张脸。我们可以在这些博客中了解原始的 gan:发现为什么 gan 很棒!、生成对抗网络(GANs)完全指南和使用 TensorFlow 构建简单的生成对抗网络(GAN)。
- 此后研究人员开始改进 GANs,并在 2015 年引入了一种新方法,来自论文的 DCGANs 利用深度卷积生成对抗网络进行无监督表示学习。我们可以在第二张图片中看到,这张脸看起来更好,但它离完美还很远。我们可以在这个博客中读到:开始使用 DCGANs 。
- 接下来在 2016 年,论文耦合生成对抗网络中的 CoGAN 被引入,它进一步改进了人脸生成。
- 2017 年底, NVIDIA AI 的研究人员发布了 ProGAN 以及论文GANs 的渐进式增长以提高质量、稳定性和变化,这是本文的主要主题。我们可以看到第四张图看起来比前几张更真实。
- 2018 年,同样的研究人员从论文中提出了 StyleGAN,这是一种基于风格的生成性对抗网络生成器架构,它基于 ProGAN。我们将在下一篇文章中讨论它。我们可以看到 StyleGAN 可以生成的高质量人脸,以及它们看起来有多逼真。
- 2019 年,同样的研究人员再次从论文中提出 StyleGAN2,分析并改善 StyleGAN 的图像质量,这是对 StyleGAN 的改进。2021 年,他们再次从论文中提出了 StyleGAN3,这是对 StyleGAN2 的改进。我们将在接下来的文章中分别介绍这两者,分解它们的组件并理解它们,然后使用 PyTorch 从头开始实现它们。
StyleGan3 是图像生成中的王者,它在定量和定性评估指标上击败了其他 Gan,无论是在保真度还是多样性方面。
因为这些论文和其他论文,GANs 已经从改进的训练、稳定性、能力和多样性有了很大的进步。
程序概述
在本节中,我们将了解 ProGAN 相对较新的架构,该架构被认为是 GAN 的转折点改进。我们将回顾 ProGAN 的主要目标,并在各个组件中介绍它的架构。
计划目标
- 生成高质量、高分辨率的图像。
- 输出图像的更大多样性。
- 提高 GANs 的稳定性。
- 增加生成图像的变化。
ProGAN 的主要组件
传统的生成对抗网络有两个组成部分;发生器和鉴别器。生成器网络采用一个随机的潜在向量( z ∈Z)并试图生成一个真实的图像。鉴别器网络试图区分真实图像和生成的图像。当我们一起训练两个网络时,生成器开始生成与真实图像无法区分的图像。
在 ProGAN 中,关键思想是逐步增加生成器和鉴别器,生成器从学习生成 4x4 的非常小的输入图像开始,然后当它完成该任务时,目标图像看起来非常接近生成的图像,因此鉴别器无法在这个特定分辨率下区分它们,然后我们更新它,生成器生成 8x8 图像。当挑战结束后,我们再次将其升级到 16x16,我们可以想象这种情况会继续下去,直到最终达到 1024 x 1024 像素的图像。
这个想法很有意义,因为它类似于我们学习的方式。如果我们以数学为例,我们不要求在第一天计算梯度;我们从做简单加法的基础开始,然后逐步成长到做更具挑战性的任务。这是 ProGAN 中的关键思想,但此外,作者还描述了几个重要的实施细节,它们是 Minibatch 标准差、新层中的淡入淡出和归一化(PixelNorm & Eq。LR)。现在,让我们更深入地了解这些组件及其工作原理。
渐进增长
在这一节中,我们将学习渐进式增长,这是 ProGAN 的核心思想。我们将回顾它背后的直觉和动机,然后我们将更深入地探讨如何实现它。
首先,渐进式增长试图通过逐渐从低分辨率图像训练到高分辨率图像,使生成器更容易生成高分辨率图像。从一个更简单的任务开始,一个非常模糊的图像,它可以生成一个只有 16 像素的 4x4 图像,然后随着时间的推移,图像的分辨率会高得多。
Image from the research paper
首先,生成器只需要生成一个 4×4 的图像,鉴别器需要评估它是真的还是假的。当然,为了使真假不那么明显,真实的图像也将被下采样为 4x 4 的图像。在渐进生长的下一步中,所有的东西都加倍了,所以现在生成的图像是一个 8 乘 8 的图像。这是一个比以前分辨率高得多的图像,但仍然比超高分辨率图像更容易,当然,真实的图像也被下采样到一个 8 乘 8 的图像,以使哪个是真的哪个是假的不那么明显。沿着这条链,生成器最终能够生成超高分辨率的图像,鉴别器会将更高分辨率的图像与同样具有这种高分辨率的真实图像进行对比,因此不再需要降采样,并且能够检测出它是真的还是假的。
在下图中,我们可以看到从真正的像素化 4x 4 像素到超高分辨率图像的渐进发展。
小批量标准偏差
GAN 倾向于不像训练数据中那样显示多样的图像,因此 ProGAN 的作者用一种简单的方法解决了这个问题。他们首先计算所有通道和所有像素值的每个示例的标准偏差,然后取整批的平均值。然后,他们在所有示例中复制该值(只是一个标量值),然后复制所有像素值以获得一个通道,并将其连接到输入。
在新图层中淡入
现在,我们应该明白在 ProGANs 中,我们是如何从 4x4 开始,然后是 8x8 等等进行渐进式增长的。但是这种渐进的增长并不像在这些预定的时间间隔内规模立即翻倍那样简单,实际上比那要缓慢一些。
对于发电机
当我们想要生成双倍大小的图像时,首先我们对图像进行上采样(上采样可以使用像最近邻过滤这样的技术)。在不使用任何已知参数的情况下,这只是非常基本的上采样,然后在下一步中,我们可以进行 99%的上采样,并将 1%的上采样图像放入卷积层,产生两倍大小的分辨率图像,因此我们有一些已知参数。
随着时间的推移,我们开始减少上采样的百分比,并增加学习参数的百分比,因此图像开始看起来可能更像目标(即面部,如果我们想要生成面部的话),而不像只是从最近的邻居上采样进行上采样。随着时间的推移,该模型将开始不依赖于上采样,而是仅依赖于学习到的参数进行推断。
Created by https://app.diagrams.net
更一般地说,我们可以把这看作是一个随时间增长的 α 参数,其中α从 0 开始,然后一直增长到 1。我们可以把最终的公式写成:\([(1*α*)×upsampled layer+(*α*)×conv layer]\)
对于鉴别器
对于鉴别器来说,有一些非常相似的东西,但是方向相反。
我们有一个高分辨率的图像(例如:上图中的 8x8),随着时间的推移,我们慢慢地通过下采样层,然后处理低分辨率的图像(例如:上图中的 4x4)。在最后,我们输出一个与预测相对应的介于零和一(真实或虚假)之间的概率。
我们在鉴别器中使用的α与我们在发生器中看到的相同。
正常化
大多数(如果不是全部的话)早期的高级 GANs 在生成器和鉴别器中使用批量归一化来消除协变量移位。但是 ProGAN 的作者观察到,在 GANs 中这不是一个问题,他们使用了一种由两步过程组成的不同方法。
均衡学习率
由于优化器的问题,即 Adam 和 RMSProp 中的梯度更新步骤取决于参数的动态范围,ProGAN 的作者引入了他们的解决方案,即均衡学习率,以更好地解决他们的特定问题。
在每次向前传递之前,可以通过调整权重来均衡各层的学习速率。例如,在对大小为(k,k,c)的 f 个过滤器执行卷积之前,它们会缩放这些过滤器的权重,如下所示。通过这种方式,他们可以确保每个权重都在相同的动态范围内,然后所有权重的学习速度都相同。
像素标准化
为了摆脱批量归一化,作者在生成器的卷积层之后应用了像素归一化,以防止信号幅度在训练期间失控。
Image from the research paper
数学上,像素(x,y)中的新特征向量将是旧特征向量除以该特定位置的所有像素值平方的平均值的平方根加上等于 10^-8.的ε这一点的实现将会非常清晰(你可以在这个博客中从头看到 ProGAN 的整个实现)。
损失函数
对于损失函数,作者使用了 GANs 中常见的损失函数之一,Wasserstein 损失函数,也称为 WGAN-GP,来自论文改进 Wasserstein GANs 的训练。但是,他们也说损失函数的选择与他们的贡献是正交的,这意味着 ProGAN (Minibatch 标准偏差,新图层中的淡入淡出和归一化(PixelNorm & Eq。依赖于特定损失函数。因此,使用我们想要的任何 GAN 损耗函数都是合理的,他们通过使用 LSGAN 损耗而不是 WGAN-GP 损耗来训练相同的网络来证明这一点。下图显示了使用 LSGAN 的方法生成的 10242 幅图像的六个示例。
Image from the research paper
然而,我们正试图完全遵循这篇论文,所以让我们稍微解释一下 WGAN-GP。在下图中,我们可以看到损失方程,其中:
- “x”是生成的图像。
- x 是来自训练集的图像。
- d 是鉴别器。
- GP 是一个梯度惩罚,有助于稳定训练。
- 梯度罚函数中的 a 项指的是 0 和 1 之间的随机数张量,均匀随机选择。
- 参数λ通常设置为 10。
The WGAN-GP loss equations
结果
Image from the research paper
我认为结果对大多数人来说是令人惊讶的,它们看起来非常好,它们是 1024 乘 1024 的图像,并且它们比以前的图像好得多。所以这是第一份在 GANs 产生真正高质量图像的革命性论文之一。
实施细节
作者在八个特斯拉 v100 GPUs 上训练网络,直到他们没有观察到任何类型的改进。这花了大约四天时间。在他们的实施中,他们根据输出分辨率使用自适应的微型批次大小,以便最佳利用可用的存储器预算(当他们无法将批次大小保存到存储器中时,他们会减小批次大小)。
发电机
在传统的 GANs 中,我们要求生成器立即生成一个固定的分辨率,比如 128 乘 128。一般来说,更高分辨率的图像更难生成,并且直接输出高质量的图像是一种具有挑战性的任务。在 ProGAN 中,我们要求生成器:
- 首先,通过将等于 512 的潜在向量作为输入来生成 4×4 的图像。他们也可以将其命名为噪声向量或 z-dim,然后将其映射到 512(在 1 个通道中)。然后,他们遵循一个很好的趋势,在开始时,他们使用一个转置卷积,从一个映射到四个,然后是具有三个三个滤波器的相同卷积,在两个卷积中使用泄漏 ReLU 作为激活函数。最后,他们添加一个逐个卷积,将通道数 512 映射到 RGB (3 个通道)。
Image from the research paper with some modification
- 接下来,他们使用相同的架构生成 8 乘 8 的图像,但没有将通道数映射到 RGB 的最终卷积层。然后,他们添加以下图层:上采样以将之前的分辨率翻倍,两个 Conv 图层使用 leaky ReLU 作为激活函数的三乘三滤镜,另一个 Conv 图层使用逐个滤镜输出 RGB 图像。
Image from the research paper with some modification
- 然后,他们通过使用相同的架构而没有最后的卷积层并添加相同的层(上采样以使先前的分辨率加倍,两个具有使用 leaky ReLU 作为激活函数的 3×3 滤波器的 Conv 层,以及另一个具有逐个滤波器的 Conv 层以输出 RGB 图像),再次生成两倍于先前的尺寸。)直到达到期望的分辨率:1024×1024。在下图中,我们可以看到生成器的最终架构。
Generator architecture from the research paper
鉴别器
对于鉴别者,他们做了相反的方法。这是发电机的镜像。当我们试图生成一个当前分辨率时,我们将真实图像下采样到相同的分辨率,以使真假不那么明显。
- 他们从生成 4x 4 图像开始,这意味着对真实图像进行下采样,直到达到相同的分辨率。鉴别器的输入是 RGB 图像,我们做了三个 Conv 层,第一个用一个接一个的滤波器,其他的用三个接三个的滤波器,使用 leaky ReLU 作为激活函数。然后,我们将图像下采样到之前分辨率的一半,并添加两个带有 3 乘 3 过滤器和泄漏 Relu 的 Conv 层。然后,我们再次向下采样,以此类推,直到达到我们想要的分辨率。然后,我们注入 Minibatch 标准差作为特征图,所以它从通道数到通道数+ 1(本例中为 512 到 513)。然后,他们可以分别用 3×3 和 4×4 的过滤器通过最后两个 Conv 层。最后,它们有一个完全连接的层来将多个通道(512)映射到一个通道。在下图中,我们可以看到 4x 4 图像的鉴别器架构。
Discriminator architecture from the research paper
- 如果我们想要生成 X 乘 X 的图像(其中 X 是图像的分辨率),我们只需使用与前面相同的步骤,直到达到我们想要的分辨率,然后我们添加最后四个层(迷你批处理标准开发、Conv 3x3x、Conv 4x4 和完全连接)。在下图中,我们可以看到 32x32 图像的鉴别器架构。
Image from the research paper with some modification
结论
在本文中,我们研究了 GANs 发展史上的一些重要里程碑,并浏览了第一份生成真正高质量图像的革命性 ProGAN 论文。然后,我们探索了原始模型的目标、损失函数、结果、实现细节及其组件,以帮助深入理解这些网络。
希望读者能够遵循所有的步骤,很好地理解它,并准备好处理实现。我们可以在这篇文章中找到它,在这篇文章中,我们使用来自 Kaggle 的这个数据集进行训练,对模型进行了一个干净、简单、可读的实现,以生成一些时装而不是人脸。在下图中,您可以看到分辨率为 128x128 的结果。
在接下来的文章中,我们将解释并使用 PyTorch 从头实现 style gan(style gan 1 基于 ProGAN, StyleGAN2 是对 SyleGAN1 的改进,StyleGAN3 是对 SyleGAN2 的改进)来生成一些很酷的时尚。
理解张量核
英伟达最新一代 GPU 微架构发布的关键技术之一是张量核心。这些专门的处理子单元自引入 Volta 以来每一代都在进步,在自动混合精度训练的帮助下加快了 GPU 的性能。
在这篇博文中,我们将总结 NVIDIA 的 Volta、Turing 和 Ampere 系列 GPU 中张量核的功能。读者应该希望在阅读完本文后,能够理解不同类型的 NVIDIA GPU 核心的作用,张量核心如何在实践中支持深度学习的混合精度训练,如何区分每个微体系结构的张量核心的性能,以及在 Paperspace 上识别张量核心驱动的 GPU 的知识。
什么是 CUDA 核心?
Processing flow on CUDA for a GeForce 8800. Source
当讨论张量核的架构和效用时,我们首先需要讨论 CUDA 核的话题。 CUDA (计算统一设备架构)是 NVIDIA 专有的并行处理平台和 GPU 的 API,而 CUDA 内核是 NVIDIA 显卡中的标准浮点单元。这些已经作为 NVIDIA GPU 微体系结构的定义特性出现在过去十年发布的每一款 NVIDIA GPU 中。
每个 CUDA 内核能够执行计算,并且每个 CUDA 内核每个时钟周期可以执行一个操作。虽然能力不如 CPU 核心,但当一起用于深度学习时,许多 CUDA 核心可以通过并行执行进程来加速计算。
在张量核心发布之前,CUDA 核心是加速深度学习的定义硬件。由于每个时钟周期只能进行一次计算,因此受限于 CUDA 内核性能的 GPU 也受到可用 CUDA 内核数量和每个内核时钟速度的限制。为了克服这一限制,NVIDIA 开发了张量核心。
什么是张量核?
https://www.youtube.com/embed/yyR0ZoCeBO8?feature=oembed
A breakdown on Tensor Cores from Nvidia - Michael Houston, Nvidia
张量核心是支持混合精度训练的专门核心。这些专用内核的第一代是通过融合乘加计算实现的。这允许两个 4 x 4 FP16 矩阵相乘并相加为 4 x 4 FP16 或 FP32 矩阵。
混合精度计算之所以如此命名,是因为虽然输入矩阵可以是低精度 FP16,但最终输出将是 FP32,输出精度损失最小。实际上,这快速地加速了计算,而对模型的最终功效的负面影响最小。随后的微体系结构将这种能力扩展到更不精确的计算机数字格式!
A table comparison of the different supported precisions for each generation of data center GPU - Source
从 V100 开始,Volta 微体系结构推出了第一代张量内核。(来源)随着每一代产品的推出,新的 GPU 微体系结构支持更多的计算机数字精度格式用于计算。在下一节中,我们将讨论每一代微体系结构如何改变和改进张量核心的能力和功能。
有关混合信息训练的更多信息,请查看我们的细目分类此处了解如何在 Paperspace 上使用混合精确训练和深度学习。
张量核是如何工作的?
每一代 GPU 微体系结构都引入了一种新的方法来提高张量核运算的性能。这些变化扩展了张量核在不同计算机数字格式上操作的能力。实际上,这极大地提高了每一代 GPU 的吞吐量。
第一代
https://blog.paperspace.com/content/media/2022/05/ezgif.com-gif-maker.mp4
Visualization of Pascal and Volta computation, with and without Tensor Cores respectively - Source
第一代张量内核来自 Volta GPU 微体系结构。这些核心支持 FP16 数字格式的混合精度训练。就万亿次浮点运算而言,这将这些 GPU 的潜在吞吐量提高了 12 倍。与上一代 Pascal GPUs 相比,旗舰版 V100 的 640 个内核将性能速度提高了 5 倍。(来源)
第二代
https://blog.paperspace.com/content/media/2022/05/ezgif.com-gif-maker--1--1.mp4
Visualization of Pascal and Turing computation, comparing speeds of different precision formats - Source
随着图灵 GPU 的发布,出现了第二代张量核。支持的张量核心精度从 FP16 扩展到还包括 Int8、Int4 和 Int1。这允许混合精度训练操作将 GPU 的性能吞吐量提高到 Pascal GPUs 的 32 倍!
除了第二代 GPU,图灵 GPU 还包含光线跟踪核心,用于计算图形可视化属性,如 3d 环境中的光线和声音。借助基于 Paperspace Core 的 RTX Quadro GPU,您可以利用这些专业内核将您的游戏和视频创作流程提升到新的水平。
第三代
Comparison of DLRM training times in relative terms on FP16 precision - Source
GPU 的安培线推出了第三代张量核,也是迄今为止最强大的。
在 Ampere GPU 中,该架构基于 Volta 和 Turing 微架构的先前创新,将计算能力扩展到 FP64、TF32 和 bfloat16 精度。这些额外的精度格式有助于进一步加速深度学习训练和推理任务。例如,TF32 格式的工作方式类似于 FP32,同时在不改变任何代码的情况下确保高达 20 倍的加速。从这里开始,实现自动混合精度将进一步加快训练速度,只需几行代码就可以实现 2 倍的额外速度。此外,Ampere 微体系结构还具有其他特性,如稀疏矩阵数学的专业化、支持多 GPU 快速交互的第三代 NVLink 以及第三代光线跟踪内核。
凭借这些进步,Ampere GPUs 特别是数据中心 A100——是目前市场上最强大的 GPU。当处理更多预算时,工作站 GPU 系列,如 A4000、A5000 和 A6000,也提供了一个以较低价格利用强大的安培微体系结构及其第三代张量内核的绝佳途径。
第四代
Plot showing comparative times to train a large language model as a function of the number of H100 and A100 GPUs parallelized in a system - Source
第四代张量内核将在未来的某个时候随 Hopper 微体系结构一起发布。即将于 2022 年 3 月发布的 H100 将采用第四代张量内核,该内核将具有处理 FP8 精确格式的扩展能力,NVIDIA 声称该内核将使大型语言模型的速度“比上一代产品快 30 倍”(来源)。
除此之外,英伟达声称他们新的 NVLink 技术将允许多达 256 个 H100 GPUs 连接。这对进一步提高数据工作者的运算规模将是巨大的好处。
哪些 Paperspace GPUs 有张量核?
| | M4000 | P4000 | P5000 | P6000 | V100 | RTX4000 | RTX5000 | A4000 | A5000 | A6000 | A100 |
| 有张量核。 | 不 | 不 | 不 | 不 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
| 有 RT 内核? | 不 | 不 | 不 | 不 | 不 | 是 | 是 | 是 | 是 | 是 | 是 |
Paperspace GPU cloud 提供了过去五代的各种 GPU,包括来自 Maxwell、Pascal、Volta、Turing 和 Ampere 微体系结构的 GPU。
Maxwell 和 Pascal 微体系结构早于张量核和光线跟踪核的发展。当查看这些机器的深度学习基准数据时,这种组成差异的影响非常明显,因为它清楚地表明,当它们具有类似的规格(如内存)时,更新的微体系结构将优于旧的微体系结构。
V100 是 Paperspace 上唯一一款具有张量内核但没有光线跟踪内核的 GPU。虽然它总体上仍然是一台优秀的深度学习机器,但 V100 是第一款采用张量核心的数据中心 GPU。其较旧的设计意味着,在深度学习任务的性能方面,它已经落后于 A6000 等工作站 GPU。
工作站 GPUs RTX4000 和 RTX5000 在 Paperspace 平台上为深度学习提供了出色的预算选项。例如,与 V100 相比,第二代 Tensor Cores 在功能上的提升使 RTX5000 在批量和完成基准测试任务的时间方面实现了几乎相当的性能。
Ampere GPU 系列具有第三代张量内核和第二代光线跟踪内核,比前几代产品的吞吐量提高到了前所未有的水平。这项技术使 A100 的吞吐量从 V100 的 900 GB/s 提高到了 1555 GB/s。
除了 A100,Paperspace 上的 Ampere GPUs 工作站系列还包括 A4000、A5000 和 A6000。这些产品以低得多的价格提供出色的吞吐量和强大的安培微体系结构。
当 Hopper 微体系结构发货时,H100 将再次将 GPU 性能提高到 A100 当前峰值的 6 倍。根据英伟达首席执行官黄仁勋的 GTC 2022 主题演讲,H100 至少要到 2022 年第三季度才能上市。
总结想法
GPU 一代又一代的技术进步,部分可以用张量核心技术的进步来表征。
正如我们在这篇博客文章中详细介绍的那样,这些核心实现了高性能混合精度训练范式,使 Volta、Turing 和 Ampere GPUs 成为人工智能开发的主导机器。
通过了解这些张量核心及其能力之间的差异,我们可以更清楚地了解每一代后续产品如何导致原始数据量的大规模增加,这些数据可以在任何给定的时刻进行深度学习任务的处理。
如果您对在 Paperspace 上运行什么 GPU 有任何疑问,无论您是在 Core 上运行 Ubuntu 机器还是在 Gradient 上运行笔记本,请不要犹豫联系!
我们始终致力于帮助您优化 GPU 云支出和效率。
资源
- Paperspace 文档和 GPU 阵容
- 英伟达-张量核心
- 英伟达-张量核心(2)
- Nvidia - Hopper 架构
- Nvidia - Ampere 架构
- 英伟达-图灵架构
- Nvidia - Volta 架构
- H100 - Nvidia 产品 p 年龄
- A100 - Nvidia 产品页面
- V100 - Nvidia 产品页面
- 来自 TechSpot 的张量核的全面分解
- 【TechCenturion 提供的张量核心简明分类
- A100 GPU 中的 TensorFloat-32 加速 AI 训练,HPC 高达 20x
用于图像分割的 U-Net 体系结构
原文:https://blog.paperspace.com/unet-architecture-image-segmentation/
深度学习模型和计算机视觉在现代的应用正在突飞猛进。计算机视觉就是这样一个人工智能领域,我们训练我们的模型来解释现实生活中的视觉图像。借助 U-Net 和 CANet 等深度学习架构,我们可以在计算机视觉数据集上实现高质量的结果,以执行复杂的任务。虽然计算机视觉是一个巨大的领域,有如此多的东西要提供,有如此多不同的、独特类型的问题要解决,但我们接下来两篇文章的重点将放在两个架构上,即 U-Net 和 CANet,它们旨在解决图像分割的任务。
图像分割的任务是获取一幅图像并将其分成几个更小的片段。产生的这些片段或多个片段将有助于图像分割任务的计算。对于图像分割任务,另一个基本要求是使用遮罩。在基本上是由零值或非零值组成的二值图像的掩蔽的帮助下,我们可以获得分割任务所需的期望结果。一旦我们借助于图像和它们各自的掩模描述了在图像分割期间获得的图像的最基本的组成部分,我们就可以用它们来实现许多未来的任务。
图像分割的一些最重要的应用包括机器视觉、对象检测、医学图像分割、机器视觉、人脸识别等等。在您深入研究本文之前,我建议您查看一些可选的先决条件,以便了解本文。我建议查看 TensorFlow 和 Keras 指南,以熟悉这些深度学习框架,因为我们将利用它们来构建 U-Net 架构。下面是目录列表,用于理解我们将在本文中涉及的概念列表。建议通读整篇文章,但是如果您已经了解一些概念,您可以随意查看具体的部分。
目录:
- U-Net 简介
- 了解 U-Net 架构
- U-Net 的 TensorFlow 实现
1。已实施模型的修改
2。导入所需的库
3。构建卷积模块
4。构造编码器和解码器模块
5。构建 U-Net 架构
6。最终确定模型 - 查看 U-Net 性能的快速示例项目
1。数据集准备
2。数据可视化
3。配置数据发生器
4。U-Net 型号
5。训练模型
6。查看结果 - 结论
U-Net 简介:
U-Net 架构于 2015 年首次发布,是深度学习领域的一场革命。该架构在众多类别中以较大优势赢得了 2015 年国际生物医学成像研讨会(ISBI)细胞追踪挑战赛。他们的一些工作包括在电子显微镜堆栈和透射光显微镜图像中分割神经元结构。
有了这个 U-Net 架构,512X512 大小的图像的分割可以用现代 GPU 在很短的时间内计算出来。由于这种架构的巨大成功,已经有了许多变体和修改。其中一些包括 LadderNet、U-Net with attention、递归和剩余卷积 U-Net (R2-UNet)以及具有剩余块或具有密集连接的块的 U-Net。
虽然 U-Net 是深度学习领域的一项重大成就,但了解以前用于解决此类类似任务的方法也同样重要。即将结束的一个主要例子是滑动窗口方法,它在 2012 年 ISBI 的 EM 分段挑战赛中以较大优势胜出。滑动窗口方法能够在原始训练数据集之外生成大量样本面片。
这一结果是因为它使用了通过在像素周围提供局部区域(小块)来将每个像素的类别标签作为单独单元来建立滑动窗口架构的网络的方法。这种体系结构的另一个成就是,它可以很容易地在任何给定的训练数据集上定位相应的任务。
然而,滑动窗口方法有两个主要缺点,U-Net 体系结构克服了这两个缺点。因为每个像素都是单独考虑的,所以产生的补丁会有很多重叠。因此,产生了大量的总体冗余。另一个限制是整个培训过程非常缓慢,耗费了大量的时间和资源。由于以下原因,网络运行的可行性值得怀疑。
U-Net 是一个优雅的架构,它解决了大多数正在发生的问题。这种方法使用了全卷积网络的概念。U-Net 的目的是捕捉上下文的特征以及本地化。这一过程由构建的架构类型成功完成。实现的主要思想是利用连续的收缩层,紧接着是上采样算子,用于在输入图像上实现更高分辨率的输出。
了解 U-Net 架构:
U-Net architecture from the following research paper
通过简单看一下图中所示的架构,我们可以注意到为什么它可能被称为 U-Net 架构。这样形成的建筑的形状是一个“U”的形式,并因此得名。只要看一下这个架构的构造过程中所涉及的结构和众多元素,就可以明白所构建的网络是一个完全卷积的网络。他们没有使用任何其他层,如密集或扁平或其他类似的层。视觉表示示出了初始收缩路径,随后是扩展路径。
该架构显示,输入图像通过模型,然后是几个具有 ReLU 激活功能的卷积层。我们可以注意到,图像大小从 572X572 减小到 570X570,最后减小到 568X568。这种减少的原因是因为他们利用了无填充的卷积(将卷积定义为“有效的”),这导致了总体维度的减少。除卷积模块外,我们还注意到,左侧是编码器模块,右侧是解码器模块。
在跨距 2 的最大池层的帮助下,编码器块具有图像尺寸的持续减小。我们还在编码器架构中增加了重复卷积层的滤波器数量。一旦我们到达解码器方面,我们注意到卷积层中的滤波器数量开始减少,随后的层中的采样逐渐增加,一直到顶部。我们还注意到,使用跳过连接将先前的输出与解码器模块中的层连接起来。
这种跳过连接是一个至关重要的概念,可以保留先前图层的损失,以便它们更好地反映整体值。它们也被科学证明可以产生更好的结果,并导致更快的模型收敛。在最后一个卷积模块中,我们有几个卷积层,后面是最后一个卷积层。这一层有一个 2 的过滤器,具有显示结果输出的适当功能。这最后一层可以根据您尝试执行的项目的预期目的进行更改。
U-Net 的张量流实现
在本文的这一部分,我们将研究 U-Net 架构的 TensorFlow 实现。虽然我使用 TensorFlow 来计算模型,但您可以选择任何深度学习框架(如 PyTorch)来实现类似的实现。在以后的文章中,我们将会看到 U-Net 架构以及其他一些 PyTorch 模型结构的工作情况。然而,对于本文,我们将坚持使用 TensorFlow 库。我们将导入所有需要的库,并从头开始构建我们的 U-Net 架构。但是,我们将进行一些必要的更改,以提高模型的整体性能,并使其稍微不那么复杂。
- 已实现模型的修改
值得注意的是,U-Net 模型早在 2015 年就已推出。虽然它在那个时间点上的表现令人难以置信,但深度学习的突出方法和功能也在同步发展。因此,自最初创建以来,U-Net 架构有许多成功的变体和版本,以在复制时保持某些图像质量,并且在某些情况下,性能优于原始架构。
我将尽量保留 U-Net 架构最初实现的大部分基本参数和架构元素。然而,将有轻微的变化,从原来的内容,这将提高现代的效率,提高速度以及模型的简单性。这种结构中包含的变化之一是使用卷积的值作为“相同”,因为未来的许多研究表明,这种特定的变化不会以任何方式对体系结构的构建产生负面影响。还有,由于 2016 年引入了批量规格化的概念,原来的架构并没有用到这方面。但是,我们的模型实现将包括批处理规范化,因为它在大多数情况下会产生最佳结果。
- 导入所需的库
为了构建 U-Net 架构,我们将利用 TensorFlow 深度学习框架,如前所述。因此,我们将为此导入 TensorFlow 库以及 Keras 框架,它现在是 TensorFlow 模型结构的一个组成部分。从我们以前对 U-Net 架构的理解中,我们知道一些基本的导入包括卷积层、最大池层、输入层和基本建模结构的激活函数 ReLU。然后,我们将使用一些额外的层,如 Conv2DTranspose 层,它将为我们所需的解码器模块执行上采样。我们还将利用批量标准化层来稳定训练过程,并使用连接层来组合必要的跳过连接。
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Activation, ReLU
from tensorflow.keras.layers import BatchNormalization, Conv2DTranspose, Concatenate
from tensorflow.keras.models import Model, Sequential
- 构建卷积块
导入所需的库后,我们可以继续构建 U-Net 架构。你可以在一个完整的类中这样做,依次定义所有的参数和值,继续这个过程,直到你到达最后,或者你有几个迭代块。我将使用后一种方法,因为对于大多数用户来说,借助几个模块来理解 U-Net 的模型架构更方便。我们将利用架构表示中所示的三个迭代模块,即卷积运算模块、编码器模块和解码器模块。在这三个模块的帮助下,我们可以轻松地构建 U-Net 架构。现在让我们逐一处理和理解这些功能代码块。
卷积运算块用于执行获取输入的输入参数和处理双层卷积运算的主要操作。在这个函数中,我们有两个参数,即卷积层的输入和过滤器的数量,默认情况下是 64。我们将使用前面讨论的填充值来保持相同的形状,而不是未填充或有效的卷积。这些卷积层之后是批量标准化层。这些对原始模型的改变是为了获得可能的最佳结果。最后,按照研究论文中的定义,将 ReLU 激活层添加到混合物中。让我们研究一下用于构建卷积模块的代码模块。
def convolution_operation(entered_input, filters=64):
# Taking first input and implementing the first conv block
conv1 = Conv2D(filters, kernel_size = (3,3), padding = "same")(entered_input)
batch_norm1 = BatchNormalization()(conv1)
act1 = ReLU()(batch_norm1)
# Taking first input and implementing the second conv block
conv2 = Conv2D(filters, kernel_size = (3,3), padding = "same")(act1)
batch_norm2 = BatchNormalization()(conv2)
act2 = ReLU()(batch_norm2)
return act2
- 构建编码器和解码器模块
我们的下一步将是建立编码器和解码器模块。这两个函数构造起来非常简单。编码器架构将使用从第一层一直到底部的连续输入。我们定义的编码器函数将具有卷积块,即两个卷积层,后面是它们各自的批量归一化和 ReLU 层。一旦我们通过卷积模块,我们将快速对这些元素进行下采样,如研究论文中所述。我们将使用 max-pooling 层,并坚持本文中提到的参数,即步幅= 2。然后,我们将返回初始输出和最大池化输出,因为我们需要前者来执行跳过连接。
解码器模块将包括三个自变量,即接收输入、跳过连接的输入以及特定构建模块中的滤波器数量。我们将借助模型中的 Conv2DTranspose 层对输入的输入进行上采样。然后,我们将连接接收输入和新上采样的层,以接收跳过连接的最终值。然后,我们将使用这个组合函数并执行卷积块操作,以进入下一层并返回这个输出值。
def encoder(entered_input, filters=64):
# Collect the start and end of each sub-block for normal pass and skip connections
enc1 = convolution_operation(entered_input, filters)
MaxPool1 = MaxPooling2D(strides = (2,2))(enc1)
return enc1, MaxPool1
def decoder(entered_input, skip, filters=64):
# Upsampling and concatenating the essential features
Upsample = Conv2DTranspose(filters, (2, 2), strides=2, padding="same")(entered_input)
Connect_Skip = Concatenate()([Upsample, skip])
out = convolution_operation(Connect_Skip, filters)
return out
- 构建 U-Net 架构:
如果您试图在一个层中从零开始构建整个 U-Net 架构,您可能会发现整个结构非常庞大,因为它由许多不同的要处理的块组成。通过将我们各自的功能分为卷积运算、编码器结构和解码器结构三个独立的代码块,我们可以用几行代码轻松构建 U-Net 架构。我们将使用输入层,它将包含我们的输入图像的各个形状。
在这一步之后,我们将收集所有的主要输出和跳过输出,以便将它们传递给下一个模块。我们将创建下一个模块,构建整个解码器架构,直到输出。根据我们期望的输出,输出将具有所需的尺寸。在这种情况下,我有一个带 sigmoid 激活功能的输出节点。我们将调用函数式 API 建模系统来创建我们的最终模型,并将该模型返回给用户,以便用 U-Net 架构执行任何任务。
def U_Net(Image_Size):
# Take the image size and shape
input1 = Input(Image_Size)
# Construct the encoder blocks
skip1, encoder_1 = encoder(input1, 64)
skip2, encoder_2 = encoder(encoder_1, 64*2)
skip3, encoder_3 = encoder(encoder_2, 64*4)
skip4, encoder_4 = encoder(encoder_3, 64*8)
# Preparing the next block
conv_block = convolution_operation(encoder_4, 64*16)
# Construct the decoder blocks
decoder_1 = decoder(conv_block, skip4, 64*8)
decoder_2 = decoder(decoder_1, skip3, 64*4)
decoder_3 = decoder(decoder_2, skip2, 64*2)
decoder_4 = decoder(decoder_3, skip1, 64)
out = Conv2D(1, 1, padding="same", activation="sigmoid")(decoder_4)
model = Model(input1, out)
return model
- 最终确定模型:
确保你的图像形状至少能被 16 或 16 的倍数整除。由于我们在下采样过程中使用了四个最大池层,我们不想遇到任何奇数形状的整除。因此,最好确保您的架构的大小等同于(48,48)、(80,80)、(160,160)、(256,256)、(512,512)以及其他类似的形状。让我们针对(160,160,3)的输入形状尝试我们的模型结构,并测试结果。获得模型及其相应图的概要。你可以从随附的 Jupyter 笔记本中看到这两种结构。我还将包括 model.png,以显示整个建筑的特殊情节。
input_shape = (160, 160, 3)
model = U_Net(input_shape)
model.summary()
tf.keras.utils.plot_model(model, "model.png", show_shapes=False, show_dtype=False, show_layer_names=True, rankdir='TB', expand_nested=False, dpi=96)
您可以使用上述代码块分别查看摘要和图表。现在让我们探索一个有趣的 U-Net 架构项目。
查看 U-Net 性能的快速示例项目:
对于这个项目,我们将使用来自 Keras 的参考图像分割项目。下面的链接将引导您找到参考。对于这个项目,我们将提取数据集并可视化基本元素,以获得结构的概述。然后,我们可以继续构建数据生成器,以便相应地从数据集中加载数据。然后,我们将利用我们在上一节中构建的 U-Net 模型,并训练该模型,直到我们达到满意的结果。一旦获得我们想要的结果,我们将保存这个模型,并在一个验证样本上进行测试。让我们开始实施这个项目吧!
- 数据集准备
我们将使用牛津 pet 数据集来完成这一特殊的图像分割任务。牛津 pet 数据集由 37 类 pet 数据集组成,每类约有 200 张图像。这些图像在比例、姿态和光照方面有很大的差异。要在您的系统上本地安装数据集,您可以从这个链接下载图像数据集,并从下面的链接下载注释数据集。一旦您成功下载了 zip 文件,您就可以使用 7-zip 或您的操作系统上使用的任何其他类似的工具包解压缩它们两次。
在第一个代码块中,我们将定义图像和注释目录各自的路径。我们还将定义一些基本参数,如图像大小、批处理大小和类的数量。然后,我们将确保数据集中的所有元素以正确的顺序排列,以执行并获得满意的图像分割任务。您可以通过打印两个文件路径来验证图像及其各自的注释,以检查它们是否产生了所需的结果。
import os
input_dir = "images/"
target_dir = "annotations/trimaps/"
img_size = (160, 160)
num_classes = 3
batch_size = 8
input_img_paths = sorted(
[
os.path.join(input_dir, fname)
for fname in os.listdir(input_dir)
if fname.endswith(".jpg")
]
)
target_img_paths = sorted(
[
os.path.join(target_dir, fname)
for fname in os.listdir(target_dir)
if fname.endswith(".png") and not fname.startswith(".")
]
)
print("Number of samples:", len(input_img_paths))
for input_path, target_path in zip(input_img_paths[:10], target_img_paths[:10]):
print(input_path, "|", target_path)
- 数据可视化
既然我们已经为项目收集并预处理了数据,我们的下一步将是简要查看数据集。让我们通过显示图像及其各自的分割输出来分析数据集。这种带有屏蔽的分段输出经常被称为基本事实注释。我们将利用 I-Python 显示选项和 pillow 库来随机显示选定的图像。这个简单的代码块编写如下:
from IPython.display import Image, display
from tensorflow.keras.preprocessing.image import load_img
import PIL
from PIL import ImageOps
# Display input image #7
display(Image(filename=input_img_paths[9]))
# Display auto-contrast version of corresponding target (per-pixel categories)
img = PIL.ImageOps.autocontrast(load_img(target_img_paths[9]))
display(img)
- 配置数据发生器
在这个特定的代码块中,我们将利用 Keras 深度学习框架中的序列操作,而不是使用数据生成器来准备数据。与使用生成器相比,这种方法对于多重处理来说要安全得多,因为它们每个时期只训练每个样本一次,这将避免我们需要进行的任何不必要的调整。然后,我们将在 Keras 的 utils 模块中准备 Sequence 类来计算、加载和矢量化批量数据。然后我们将构造初始化函数,一个计算长度的附加函数和一个生成批量数据的最终函数。
from tensorflow import keras
import numpy as np
from tensorflow.keras.preprocessing.image import load_img
class OxfordPets(keras.utils.Sequence):
"""Helper to iterate over the data (as Numpy arrays)."""
def __init__(self, batch_size, img_size, input_img_paths, target_img_paths):
self.batch_size = batch_size
self.img_size = img_size
self.input_img_paths = input_img_paths
self.target_img_paths = target_img_paths
def __len__(self):
return len(self.target_img_paths) // self.batch_size
def __getitem__(self, idx):
"""Returns tuple (input, target) correspond to batch #idx."""
i = idx * self.batch_size
batch_input_img_paths = self.input_img_paths[i : i + self.batch_size]
batch_target_img_paths = self.target_img_paths[i : i + self.batch_size]
x = np.zeros((self.batch_size,) + self.img_size + (3,), dtype="float32")
for j, path in enumerate(batch_input_img_paths):
img = load_img(path, target_size=self.img_size)
x[j] = img
y = np.zeros((self.batch_size,) + self.img_size + (1,), dtype="uint8")
for j, path in enumerate(batch_target_img_paths):
img = load_img(path, target_size=self.img_size, color_mode="grayscale")
y[j] = np.expand_dims(img, 2)
# Ground truth labels are 1, 2, 3\. Subtract one to make them 0, 1, 2:
y[j] -= 1
return x, y
在下一步中,我们将分别定义定型数据和验证数据之间的拆分。我们这样做是为了确保训练集和测试集中的元素的完整性没有被破坏。这两个数据实体必须分开查看,这样模型就不会看到测试数据。在验证集中,我们还将执行一个可选的混洗操作,该操作将混合数据集中的所有图像,并且我们可以为训练和验证图像获得随机样本。然后,我们将分别调用训练值和验证值,并将它们存储在各自的变量中。
import random
# Split our img paths into a training and a validation set
val_samples = 1000
random.Random(1337).shuffle(input_img_paths)
random.Random(1337).shuffle(target_img_paths)
train_input_img_paths = input_img_paths[:-val_samples]
train_target_img_paths = target_img_paths[:-val_samples]
val_input_img_paths = input_img_paths[-val_samples:]
val_target_img_paths = target_img_paths[-val_samples:]
# Instantiate data Sequences for each split
train_gen = OxfordPets(
batch_size, img_size, train_input_img_paths, train_target_img_paths
)
val_gen = OxfordPets(batch_size, img_size, val_input_img_paths, val_target_img_paths)
一旦我们完成了以下步骤,我们就可以开始构建我们的 U-Net 架构了。
- U-Net 模型
我们将在本节中构建的 U-Net 模型与前几节中定义的模型是完全相同的架构,除了一些小的修改之外,我们将很快讨论这些修改。在准备好数据集之后,我们可以相应地构建我们的模型。该模型灌输图像大小,并开始构建我们的图像将通过的整体架构。在前面讨论的体系结构中,您需要做的唯一更改如下:
out = Conv2D(3, 1, padding="same", activation="sigmoid")(decoder_4)
或者
out = Conv2D(num_classes, 1, padding="same", activation="sigmoid")(decoder_4)
我们正在更改 U-Net 架构的最后一层,以表示在最后一步将生成的输出总数。请注意,您还可以利用 SoftMax 函数来生成多类分类的最终输出,这样可能会更准确。然而,您可以从训练结果中看到,这个激活功能也工作得很好。
训练模型
在下一步中,我们将编译和训练模型,以查看其在数据上的性能。我们还使用一个检查点来保存模型,以便我们可以在未来进行预测。在 11 个周期后,我中断了训练程序,因为我对获得的结果非常满意。如果你愿意,你可以选择运行更多的时期。
model.compile(optimizer="rmsprop", loss="sparse_categorical_crossentropy")
callbacks = [
keras.callbacks.ModelCheckpoint("oxford_segmentation.h5", save_best_only=True)
]
# Train the model, doing validation at the end of each epoch.
epochs = 15
model.fit(train_gen, epochs=epochs, validation_data=val_gen, callbacks=callbacks)
Epoch 1/15
798/798 [==============================] - 874s 1s/step - loss: 0.7482 - val_loss: 0.7945
Epoch 2/15
798/798 [==============================] - 771s 963ms/step - loss: 0.4964 - val_loss: 0.5646
Epoch 3/15
798/798 [==============================] - 776s 969ms/step - loss: 0.4039 - val_loss: 0.3900
Epoch 4/15
798/798 [==============================] - 776s 969ms/step - loss: 0.3582 - val_loss: 0.3574
Epoch 5/15
798/798 [==============================] - 788s 985ms/step - loss: 0.3335 - val_loss: 0.3607
Epoch 6/15
798/798 [==============================] - 778s 972ms/step - loss: 0.3078 - val_loss: 0.3916
Epoch 7/15
798/798 [==============================] - 780s 974ms/step - loss: 0.2772 - val_loss: 0.3226
Epoch 8/15
798/798 [==============================] - 796s 994ms/step - loss: 0.2651 - val_loss: 0.3046
Epoch 9/15
798/798 [==============================] - 802s 1s/step - loss: 0.2487 - val_loss: 0.2996
Epoch 10/15
798/798 [==============================] - 807s 1s/step - loss: 0.2335 - val_loss: 0.3020
Epoch 11/15
798/798 [==============================] - 797s 995ms/step - loss: 0.2220 - val_loss: 0.2801
- 查看结果
最后,让我们想象一下所获得的结果。
# Generate predictions for all images in the validation set
val_gen = OxfordPets(batch_size, img_size, val_input_img_paths, val_target_img_paths)
val_preds = model.predict(val_gen)
def display_mask(i):
"""Quick utility to display a model's prediction."""
mask = np.argmax(val_preds[i], axis=-1)
mask = np.expand_dims(mask, axis=-1)
img = PIL.ImageOps.autocontrast(keras.preprocessing.image.array_to_img(mask))
display(img)
# Display results for validation image #10
i = 10
# Display input image
display(Image(filename=val_input_img_paths[i]))
# Display ground-truth target mask
img = PIL.ImageOps.autocontrast(load_img(val_target_img_paths[i]))
display(img)
# Display mask predicted by our model
display_mask(i) # Note that the model only sees inputs at 150x150.
结论:
Photo by Safar Safarov / Unsplash
U-Net 架构是深度学习领域最重要和最具革命性的里程碑之一。虽然介绍 U-Net 架构的最初研究论文是为了解决生物医学图像分割的任务,但它并不局限于这一单一应用。该模型能够并且仍然能够解决深度学习中最复杂的问题。尽管原始体系结构中的一些元素已经过时,但是该体系结构还有几个变体。这些网络包括 LadderNet、U-Net with attention、递归和剩余卷积 U-Net (R2-UNet)以及其他从原始 U-Net 模型成功导出的类似网络。
在本文中,我们简要介绍了 U-Net 建模技术,该技术在大多数与图像分割相关的现代任务中有着惊人的效用。然后,我们开始了解 U-Net 架构的构建和主要方法。我们了解用于在所提供的数据集上实现最佳结果的众多方法和技术。在本节之后,我们学习了如何使用各种模块从头开始构建 U-Net 架构,以简化整体结构。最后,我们用一个简单的图像分割问题分析了我们构建的 U-Net 体系结构。
在接下来的文章中,我们将研究用于图像分割的 CANet 架构,并理解它的一些核心概念。然后,我们将从头开始构建整个架构。在此之前,祝您愉快地学习和探索深度学习的世界!
使用 CycleGAN 进行不成对的图像到图像翻译
原文:https://blog.paperspace.com/unpaired-image-to-image-translation-with-cyclegan/
脸书大学人工智能研究主任和 NYU 大学教授 Yann LeCun 将生成对抗网络描述为过去 10 年机器学习中最有趣的想法。自从 Ian Goodfellow 在 2014 年发明 GANs 以来,我们已经看到了来自 NVIDIA 和脸书等几个研究小组的这些有趣的神经网络的大量变体,但我们将研究加州大学伯克利分校一个研究小组的一个叫做循环一致对抗网络的网络。在我们深入探讨循环一致对抗网络(简称 CycleGAN)之前,我们要看看什么是生成性对抗网络。本文旨在揭示生成性对抗网络及其流行变体之一——循环一致对抗网络的工作机制。这里使用的大部分代码摘自 TensorFlow 官方文档页面。本文完整代码可从:https://www . tensor flow . org/beta/tutorials/generative/cycle gan获得
生成对抗网络
生成对抗网络是一种神经网络,通常由两个以对抗方式建立的神经网络组成。我所说的对抗方式是指他们为了做得更好而互相对抗。这两个网络被称为发生器和鉴别器。第一个 GAN 是由 Ian Goodfellow 在 2014 年提出的,在他的工作之后,我们已经看到了几个 GAN,一些具有新颖的架构,另一些具有改进的性能和稳定性。那么到底什么是生成性对抗网络呢?通俗地说,生成对抗网络是一种由两个模型组成的生成模型,其中一个模型试图生成非常接近原始真实图像或数据的图像或一些其他真实生活数据来愚弄另一个模型,而另一个模型通过查看生成的图像和真实图像来优化自身,以便不被生成模型所愚弄。在 GANs 的文献中,生成图像的模型被称为生成器,确保生成器生成看起来真实的图像的模型被称为鉴别器。让我们试着用侦探-强盗的场景来理解甘斯。在这个场景中,扮演发电机的劫匪不断向扮演鉴别者的侦探出示假钞。在这一过程的每一点上,侦探都会发现钞票是假的,拒绝接受钞票,并告诉劫匪是什么让钞票变假的。强盗也在每个阶段从侦探那里得到笔记,使用来自侦探的信息生成新的笔记,然后再次向侦探展示它。这种情况一直持续到强盗成功地创造出一张看起来足够真实的钞票来愚弄侦探。这正是生成性对抗网络的工作方式——生成器连续产生合成图像,并通过接收来自鉴别器的信号进行优化,直到合成图像的分布几乎与原始图像的分布相匹配。
GAN 的单个训练迭代步骤包括三个步骤:
- 首先,向鉴别器显示一批真实图像,并且优化其权重以将这些图像分类为真实图像(真实图像标记为 1)
- 然后,我们使用生成器生成一批伪图像,将这些伪图像显示给鉴别器,然后优化鉴别器的权重,以将这些图像分类为伪图像(伪图像标记为 0)
- 第三步是训练生成器。我们生成一批伪图像,将这些伪图像展示给鉴别器,但是我们不是优化鉴别器来将这些图像分类为伪图像,而是优化生成器来迫使鉴别器将这些伪图像分类为真实图像。
迷茫?让我们把它们分解开来,你会发现这是多么简单。如前所述,首先我们向鉴别器展示一批真实图像,并对其进行优化,以将这些真实图像分类为真实图像。假设真实图像具有 1 作为它们的标签,并且简单的绝对平均误差被用作损失函数。让我们也用公式表示鉴别器的数学表达式。我们将使用 f(x)其中 f(。)表示鉴别器是前馈神经网络或卷积网络,x 是真实图像或一批真实图像。有了上面的参数,我们的损失函数应该是这样的: | f(x) - 1 | (为了简单起见,省略了均值)。输入一批真实图像,并通过鉴别器反向传播这一损失信号以进行优化,这仅仅意味着每当我们的鉴别器看到真实图像时,我们希望它预测一个真正接近 1 的值。同样的过程用于第二步,但是我们将生成器生成的假图像标记为 0,因此损失函数如下: | f(x)-0 | = | f(x) |。通过鉴别器反向传播这种损失信号并优化其权重,这意味着每当鉴别器被显示假图像时,我们希望它预测一个非常接近 0 的值,这是假图像的标签。不像第一步和第二步,我们只训练鉴别器,第三步尝试训练生成器。我们展示了鉴别器伪造由生成器生成的图像,但是这次我们使用步骤: | f(x) - 1 |的损失签名。然后,我们将损耗信号从鉴别器一路反向传播到发生器,并利用该损耗信号优化发生器的权重。这相当于鉴别器通知发生器它需要进行的改变,以生成假图像,这将导致鉴别器将其分类为真实图像。
您可能想知道生成器是如何生成图像的。最初提出的 GAN 通过将来自均匀分布的固定大小的向量作为输入,并逐渐增加该向量的空间维度以形成图像,来生成图像。一些最近发明的 GAN,如 CycleGAN,似乎已经偏离了这种发电机架构。
图像到图像翻译的任务
在 CycleGANs 发明之前,图像到图像的转换已经存在了一段时间。一个真正有趣的例子是 Phillip Isola 等人在论文中的工作,使用条件对抗网络进行图像到图像的翻译,其中来自一个域的图像被翻译成另一个域的图像。这项工作的数据集由来自每个域的对齐图像对组成。这个模型被命名为 Pix2Pix GAN。
CycleGAN 用于执行图像到图像转换的方法非常类似于 Pix2Pix GAN,除了不成对的图像用于训练 cycle GAN,并且 cycle GAN 的目标函数具有额外的标准,周期一致性损失。事实上,这两篇论文几乎是由同一作者撰写的。
正如我前面提到的,最近的一些 gan 有不同的生成器架构设计。Pix2Pix GANs 和 CycleGANs 是具有这种不同架构的 gan 的主要例子。它不是将固定大小的向量作为输入,而是将一个域中的图像作为输入,并在另一个域中输出相应的图像。这种架构还利用跳过连接来确保在正向传播期间更多的特征从输入流向输出,以及在反向传播期间从损耗到参数的梯度。鉴别器架构几乎相同。与最初提出的将整个图像分类为真实或伪造的架构不同,这些 GANs 中使用的架构通过输出值的矩阵而不是单个值来将图像的小块分类为真实或伪造。这样做的原因是为了鼓励清晰的高频细节,也是为了减少参数的数量。
此外,Pix2Pix GAN 和 CycleGAN 之间的一个主要区别是,与仅包含两个网络(鉴别器和发生器)的 Pix2Pix GAN 不同,CycleGAN 包含四个网络(两个鉴别器和两个发生器)。让我们看看一个周期的目标函数,以及如何训练一个。
目标函数
之前,我提到训练 GAN 有三个步骤,前两个步骤训练鉴别器。让我们看看如何。我们将结合鉴别器目标损失,并在一个 python 函数中实现它。
loss_obj = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def discriminator_loss(real, generated):
real_loss = loss_obj(tf.ones_like(real), real)
generated_loss = loss_obj(tf.zeros_like(generated), generated)
total_disc_loss = real_loss + generated_loss
return total_disc_loss
注意,我们没有使用平均绝对误差,而是使用二进制交叉熵损失函数。当一个真实图像被输入到鉴别器和一个矩阵时,真实损失物镜将鉴别器输出作为输入。回想一下,当我们将真实图像输入鉴别器时,我们希望它预测一个接近 1 的值,因此我们在这个目标函数中增加了这个概率。同样的规则也适用于 generated _ loss——当我们将生成器生成的假图像输入鉴别器时,我们增加了鉴别器预测值接近于零的概率。我们将两个损失相加,以反向传播和训练鉴别器。接下来我们训练发电机。
def generator_loss(generated):
return loss_obj(tf.ones_like(generated), generated)
我们将假图像从生成器输入鉴别器,而不是增加鉴别器预测接近 1 的值的概率,我们调整生成器以迫使鉴别器预测接近 1 的值。这相当于将梯度反向传播到生成器,并用梯度更新其权重。让我们假设生成器 G 将图像从域 X 映射到 Y,而 F 将图像从域 Y 映射到 X。根据该论文,这些对抗性损失仅确保学习到的映射 G 和 F 产生分别与 Y 和 X 的分布相匹配的输出,但在视觉上与相应域中的图像不相同。例如,假设我们训练 G 将图像从包含夏季场景图像的域映射到包含冬季场景图像的域。仅使用对抗损失来学习映射,当我们使用 G 来映射来自 X 域的图像 X 时,产生的图像 Y 仅匹配 Y 的分布,因此可以是 Y 域中可能与输入图像 X 不相同的图像的任意随机排列。映射 G 和 F 是欠约束映射,并且为了减少可能映射的空间,作者引入了循环一致性损失来增加对抗损失。他们认为,为了进一步约束映射,映射应该是周期一致的。这意味着对于来自域 X 的每个图像 X,到域 Y 并回到域 X 的图像平移应该将 X 带回原始图像。即 x → G(x) → y → F(y) ≈ x .这相当于 x→G(x)→F(G(x)) ≈ x .他们把这称为正向循环一致性。类似地,对于来自域 Y 的每个图像 Y,G 和 F 应该满足映射 y → F(y) → G(F(y)) ≈ y(向后循环一致性)。
def calc_cycle_loss(real_image, cycled_image):
loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))
return LAMBDA * loss1
#lambda is to provide weight to this objective function
我们引入了最后一个损失函数,即身份损失,它进一步确保映射的输出在视觉上与它们映射到的域中的图像相匹配。
def identity_loss(real_image, same_image):
loss = tf.reduce_mean(tf.abs(real_image - same_image))
return LAMBDA * 0.5 * loss
#LAMBDA is to provide weight to this objective function
模型
在这项工作的论文中,作者为他们的发电机网络使用了更精确的架构。它由类似于剩余网络中的跳跃连接组成,但我们将使用 TensorFlow 示例模块中实现的 Unet 模型。我们可以从https://github.com/tensorflow/examples下载并安装模块,或者使用:
!pip install git+https://github.com/tensorflow/examples.git
要从 TensorFlow 示例包中访问模型,请使用以下代码片段:
from tensorflow_examples.models.pix2pix import pix2pix
generator_g = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')
generator_f = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')
discriminator_x = pix2pix.discriminator(norm_type='instancenorm', target=False)
discriminator_y = pix2pix.discriminator(norm_type='instancenorm', target=False)
生成器由下采样层和上采样层组成。输入图像首先通过连续的下采样层,这减小了图像或一批图像的空间维度。通过转置卷积层来实现下采样。在对输入图像进行充分的下采样之后,我们对其进行上采样以增加其空间维度来形成图像。通过卷积层实现上采样。
image of what our generator model should look like.
正如已经讨论过的,鉴别器网络是一个前馈网络,更具体地说是一个卷积神经网络,它输出一个值矩阵,每个值代表鉴别器对输入图像上一个小块或小区域的判定。因此,它不是将整个图像分类为假或真,而是对图像上的小块做出决定。
我们已经讨论了这种架构的原因。
数据
为生成性对抗网络获取数据可能是一个相当大的挑战。幸运的是,TensorFlow 数据集模块由几个具有不成对图像对齐的数据集组成。您可以使用以下简单命令安装模块:
pip install tensorflow-datasets
安装模块后,使用以下代码访问数据集:
dataset, metadata = tfds.load('cycle_gan/horse2zebra',
with_info=True, as_supervised=True)
train_X, train_Y = dataset['trainA'], dataset['trainB']
test_X, test_Y = dataset['testA'], dataset['testB']
请注意,我们使用的数据集包含“马”和“斑马”域。这里还有很多其他不成对的数据集:https://www.tensorflow.org/datasets/catalog/cycle_gan——只需用你选择的数据集替换加载函数中的“horse2zebra”即可。现在我们有了数据集,我们需要建立一个有效的管道将数据集输入神经网络。 tf.data API 为我们提供了创建这个管道的所有工具。
def random_crop(image):
cropped_image = tf.image.random_crop(
image, size=[IMG_HEIGHT, IMG_WIDTH, 3])
return cropped_image
# normalizing the images to [-1, 1]
def normalize(image):
image = tf.cast(image, tf.float32)
image = (image / 127.5) - 1
return image
def random_jitter(image):
# resizing to 286 x 286 x 3
image = tf.image.resize(image, [286, 286],
method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
# randomly cropping to 256 x 256 x 3
image = random_crop(image)
# random mirroring
image = tf.image.random_flip_left_right(image)
return image
def preprocess_image_train(image, label):
image = random_jitter(image)
image = normalize(image)
return image
def preprocess_image_test(image, label):
image = normalize(image)
return image
train_X = train_X .map(
preprocess_image_train, num_parallel_calls=AUTOTUNE).cache().shuffle(
BUFFER_SIZE).batch(1)
train_Y = train_Y.map(
preprocess_image_train, num_parallel_calls=AUTOTUNE).cache().shuffle(
BUFFER_SIZE).batch(1)
test_X = test_X.map(
preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
BUFFER_SIZE).batch(1)
test_Y = test_zebras.map(
preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
BUFFER_SIZE).batch(1)
基本上,上面的代码所做的就是定义一组函数来操作流经管道的图像(数据扩充)。我们还对数据集进行批处理,并在数据集的每次完整迭代后对图像进行洗牌。在整篇文章中,我们讨论了使用对抗性和周期一致损失来训练我们的模型。现在我们来看看如何用代码实现这个训练算法。
@tf.function
def train_step(real_x, real_y):
# persistent is set to True because the tape is used more than
# once to calculate the gradients.
with tf.GradientTape(persistent=True) as tape:
# Generator G translates X -> Y
# Generator F translates Y -> X.
fake_y = generator_g(real_x, training=True)
cycled_x = generator_f(fake_y, training=True)
fake_x = generator_f(real_y, training=True)
cycled_y = generator_g(fake_x, training=True)
# same_x and same_y are used for identity loss.
same_x = generator_f(real_x, training=True)
same_y = generator_g(real_y, training=True)
disc_real_x = discriminator_x(real_x, training=True)
disc_real_y = discriminator_y(real_y, training=True)
disc_fake_x = discriminator_x(fake_x, training=True)
disc_fake_y = discriminator_y(fake_y, training=True)
disc_x_loss = discriminator_loss(disc_real_x, disc_fake_x)
disc_y_loss = discriminator_loss(disc_real_y, disc_fake_y)
# calculate the loss
gen_g_loss = generator_loss(disc_fake_y)
gen_f_loss = generator_loss(disc_fake_x)
total_cycle_loss = calc_cycle_loss(real_x, cycled_x) + calc_cycle_loss(real_y, cycled_y)
# Total generator loss = adversarial loss + cycle loss
total_gen_g_loss = gen_g_loss + total_cycle_loss + identity_loss(real_y, same_y)
total_gen_f_loss = gen_f_loss + total_cycle_loss + identity_loss(real_x, same_x)
# Calculate the gradients for generator and discriminator
generator_g_gradients = tape.gradient(total_gen_g_loss,
generator_g.trainable_variables)
generator_f_gradients = tape.gradient(total_gen_f_loss,
generator_f.trainable_variables)
discriminator_x_gradients = tape.gradient(disc_x_loss,
discriminator_x.trainable_variables)
discriminator_y_gradients = tape.gradient(disc_y_loss,
discriminator_y.trainable_variables)
# Apply the gradients to the optimizer
generator_g_optimizer.apply_gradients(zip(generator_g_gradients,
generator_g.trainable_variables))
generator_f_optimizer.apply_gradients(zip(generator_f_gradients,
generator_f.trainable_variables))
discriminator_x_optimizer.apply_gradients(zip(discriminator_x_gradients,
discriminator_x.trainable_variables))
discriminator_y_optimizer.apply_gradients(zip(discriminator_y_gradients,
discriminator_y.trainable_variables))
train 函数顶部的 tf.function 装饰器将整个 train_step 函数编译成张量流图,以提高 TPU 和 GPU 等硬件的性能。训练步骤功能可能很复杂,但它遵循我们之前在训练 GAN 中描述的步骤。我们首先使用生成器来生成假像和循环图像。伪图像用于训练发生器和鉴别器,而周期图像用于周期一致性丢失,以确保周期一致性,如前所述。在训练鉴别器时,我们首先将真实图像输入鉴别器,以获得真实图像的鉴别器输出。我们对生成器产生的假图像进行同样的处理,以获得假图像的鉴别器输出。然后,我们将这些输出馈入鉴频器损耗函数,然后使用获得的每个鉴频器的损耗来训练每个鉴频器。现在我们继续训练发电机。我们将鉴别器对假图像的判定传递给 generator_loss 函数,该函数描述了生成器在欺骗鉴别器使其认为生成的图像是真实图像方面有多好。我们将这种损失与循环一致性损失和同一性损失相结合,这两种损失都负责进一步约束可能的映射,获得梯度并优化生成器的权重。训练步骤功能在一批图像上执行所有这些操作,因此我们需要定义一个训练循环,以便在一些时期的所有数据集批次上迭代执行训练步骤。训练循环由三个阶段组成。
- 首先,我们迭代通过一些历元(整数值)
- 然后在每个时期,我们迭代通过由成批图像(train_X 和 train_Y)组成的整个数据集。
- 然后,对于每批图像,我们简单地调用 train_step 函数来训练每批数据集上的鉴别器和生成器。
训练循环还包含一些 if 语句,用于在屏幕上记录我们训练的统计数据。
for epoch in range(EPOCHS):
start = time.time()
n = 0
for image_x, image_y in tf.data.Dataset.zip((train_horses, train_zebras)):
train_step(image_x, image_y)
if n % 10 == 0:
print ('.', end='')
n+=1
clear_output(wait=True)
# Using a consistent image (sample_horse) so that the progress of the model
# is clearly visible.
generate_images(generator_g, sample_horse)
if (epoch + 1) % 5 == 0:
ckpt_save_path = ckpt_manager.save()
print ('Saving checkpoint for epoch {} at {}'.format(epoch+1,
ckpt_save_path))
print ('Time taken for epoch {} is {} sec\n'.format(epoch + 1,
time.time()-start))
generate_images 函数显示生成器生成的图像。这是为了跟踪我们的模型执行得有多好。它的实现如下所示。
def generate_images(model, test_input):
prediction = model(test_input)
plt.figure(figsize=(12, 12))
display_list = [test_input[0], prediction[0]]
title = ['Input Image', 'Predicted Image']
for i in range(2):
plt.subplot(1, 2, i+1)
plt.title(title[i])
# getting the pixel values between [0, 1] to plot it.
plt.imshow(display_list[i] * 0.5 + 0.5)
plt.axis('off')
plt.show()
培训需要一些时间,因此我建议您在支持 GPU 的环境中在 Paperspace gradient 笔记本上进行培训,以加快培训过程:Paperspace.com。在长时间训练模型后,生成器应该能够生成相当真实的翻译图像,如下所示:
source: TensorFlow.org
下一步
- TensorFlow 数据集包中有几个数据集与图像到图像的转换任务相关。我恳请读者尝试不同的数据集,并在 twitter 上发布他们的结果。可以加我标签@henryansah083
- 我们使用 unet 模型实现了生成器。您还应该尝试其他架构,如本文中提出的剩余网络生成器架构。
关于我
我是一名本科生,目前在读电气电子工程。我也是一个深度学习爱好者和作家。我的工作主要集中在计算机视觉和自然语言处理上。我希望有一天能打入自动驾驶汽车领域。你可以在推特(@henryansah083)上关注:https://twitter.com/henryansah083?s=09LinkedIn:https://www.linkedin.com/in/henry-ansah-6a8b84167/。
具有循环 gan 的不成对的图像到图像转换
原文:https://blog.paperspace.com/unpaired-image-to-image-translations-with-cycle-gans/
深度学习在几十年前被引入时没有腾飞的主要原因之一是缺乏数据。神经网络本质上需要巨大的处理能力和大数据才能在实践中真正利用。在当今时代,随着设备越来越现代化,存储大量数据不再是问题。还可以使用计算机视觉或自然语言处理的现代工具来创建和提炼可用数据。大多数深度学习模型利用成对的例子,其中源图像直接链接到其目标图像。然而,一个重要的问题出现了。什么类型的深度学习最适合解决彼此之间很少或没有包含数据的链接的任务?
在我们之前的文章中,我们已经探讨了条件性 gan 及其在成功训练后相对容易地执行复杂的图像到图像翻译任务的能力。我们已经介绍了条件 gan(cgan)和这些条件 gan 在pix 2 pix gan中的变体。为了快速回顾我们以前关于 pix2pix 的 GAN 文章,我们重点关注执行卫星图像到地图的图像到图像翻译。在本文中,我们将关注循环一致对抗网络(循环 gan)中条件 gan 的另一种变体,并开发一个不成对的图像到图像翻译项目。
通过使用后续链接报告作为“工作区 URL”来创建 Paperspace Gradient 笔记本,可以利用 Paperspace Gradient 平台来运行以下项目通过切换笔记本创建页面上的高级选项按钮,可以找到该字段。
简介:
Image Source
当我们有了特定的条件和将一个图像链接到其相应对应物的数据元素的适当配对时,就更容易训练这样的条件 gan。Pix2Pix GANs 就是我们处理成对图像的一个例子。成对图像是源图像与其对应的目标图像相链接的图像。在成对的图像数据集中,我们有源图像和目标图像的清晰映射,这是相应提供的。然而,这样的数据在现实世界中很难获得。
另一方面,不成对的数据相对来说更容易获得,而且大量存在。在不成对的数据集中,不直接提供源图像和它们对应的目标图像。解决与不成对数据集相关的项目或任务的主要方法之一是利用循环一致的对抗网络(循环 gan)。在大多数情况下,Cycle GANs 为用户提供了实现令人信服的结果的有效方法。在这篇研究论文中,详细描述了该网络的大部分主要目标和成就。
循环 GAN 网络能够从不成对的例子中学习从图像源\(X\)到\(Y\)的映射,使得所产生的图像具有高质量。本文还引入了一个额外的循环损失,以证实生成图像的逆映射再次再现了源图像。让我们在本文的下一节中进一步理解工作过程和实现细节。
了解循环甘纳:
在本节中,我们将了解 Cycle GANs 的详细工作说明。在引言中,我们委婉地承认循环 gan 的应用主要用于不成对的图像到图像的翻译,这与其他条件 gan 相反,例如 pix2pix GANs。让我们试着理解这些网络的工作过程和基本概念,并清楚地了解发生器和鉴别器的结构。
在上图中,让我们考虑带有生成器\(G\)和\(F\)以及鉴别器\(Dx\)和\(Dy\)的第一个子部分。\(X\)表示输入图像,而\(Y\)表示生成的图像。与大多数使用单个发生器和鉴别器的 GAN 网络不同,循环 GAN 网络使用两个发生器和鉴别器。生成器\(G\)处理输入\(X\)以生成图像\(Y\)。鉴别器\(Dy\)鉴别生成的图像是真的还是假的。类似地,生成器\(F\)在考虑输入\(Y\)的情况下生成图像\(X\)。鉴别器\(Dx\)鉴别这个生成的图像是真的还是假的。
除了有一个双生成器和鉴别器设置之外,我们还有一个周期一致性损失来帮助模型获得更直观的理解。如果模型在一个源图像上被训练以产生生成的图像,则循环一致性损失确保当生成的图像被再次转换回原始域时,我们应该能够再次近似地检索原始源图像。
在上图中,我们可以注意到前向周期一致性丢失和后向周期一致性丢失在各自的子图中。以下是针对循环 GAN 网络的所有损失函数的最终等式。在这个项目中,我们还使用了额外的身份丢失,这在大多数情况下可能并不需要。
既然我们已经理解了这些循环 GAN 网络的工作过程,我们还可以简单地讨论实现发生器和鉴别器网络背后的细节。生成器架构由三个卷积层组成,具有不同的滤波器、内核大小和步长。在三个卷积层之后,我们有剩余的块。网络可以包含六个或九个剩余块。
在残差块的末尾,我们利用几个上采样层(或卷积转置层)和最终的卷积层,得到生成器所需的图像大小。在鉴频器架构中,我们有一个简单的 70 x 70 贴片 GAN 网络,它包含四个滤波器尺寸递增的卷积层。在这些网络中,实例规范化优于批量规范化以及泄漏 ReLU 激活函数。当我们从头开始构建这些模型时,我们将进一步讨论架构细节。
使用循环 GANS 开发不成对图像到图像的翻译;
在本文的这一部分,我们将在 Cycle GANs 的帮助下重点开发不成对的图像到图像翻译项目。现在我们已经对这些循环一致的对抗网络的基本工作过程有了一个简单的了解,我们可以建立一个整体架构来处理和计算所需的任务。我们将利用 TensorFlow 和 Keras 深度学习框架来构建这些网络。如果读者不熟悉这些库,我建议查看我以前的几篇文章,以便更熟悉这些主题。你可以查看下面的文章来了解更多关于 TensorFlow 和 Keras 文章这里。一旦完成,我们就可以开始导入必要的库了。
导入基本库:
如前所述,与之前的 pix2pix GAN 架构相比,主要变化之一是在批量标准化层上使用了实例标准化层。由于没有办法通过现有的 Keras API 直接调用实例规范化层,我们将继续安装一个额外的需求。我们将安装 Keras-Contrib 存储库,这将允许我们直接利用这些必要的层,而无需经历太多的麻烦。
pip install git+https://www.github.com/keras-team/keras-contrib.git
一旦我们完成了项目的先决条件要求的安装,我们就可以导入我们将用于构建 Cycle GAN 架构的所有基本库,并相应地训练模型以获得所需的结果。如前所述,我们将使用 TensorFlow 和 Keras 深度学习框架来构建网络。我们将使用功能性 API 模型类型,而不是顺序模型,以便更好地控制网络设计。我们还将使用 TensorFlow 数据集库中可用的数据集。其他必要的导入包括用于可视化的 matplotlib 和用于处理与本地操作系统相关的任务的 OS。以下是所有必需进口的清单。
import tensorflow as tf
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.initializers import RandomNormal
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Conv2D, LeakyReLU, Conv2DTranspose
from tensorflow.keras.layers import Activation, Concatenate, BatchNormalization
from keras_contrib.layers.normalization.instancenormalization import InstanceNormalization
from tensorflow.keras.utils import plot_model
import tensorflow_datasets as tfds
import matplotlib.pyplot as plt
from IPython.display import clear_output
import time
import os
AUTOTUNE = tf.data.AUTOTUNE
获取和准备数据集:
在这一步中,我们将获得数据集,我们将为其执行图像转换。我们利用 TensorFlow 数据集库,通过它我们可以检索所有相关信息。该数据集包含马和斑马的图像。为了获得 Cycle GAN 项目的类似数据集,我建议查看以下链接。下面是将数据集加载到它们各自的训练和测试变量中的代码片段。
dataset, metadata = tfds.load('cycle_gan/horse2zebra',
with_info=True, as_supervised=True)
train_horses, train_zebras = dataset['trainA'], dataset['trainB']
test_horses, test_zebras = dataset['testA'], dataset['testB']
一旦我们检索到数据集,我们就可以继续定义一些基本参数,我们将利用这些参数来准备数据集。
BUFFER_SIZE = 1000
BATCH_SIZE = 1
IMG_WIDTH = 256
IMG_HEIGHT = 256
在下一个代码块中,我们将为数据集准备定义一些基本函数。我们将执行数据集的规范化,以避免额外的内存使用,并使用相对较少的资源解决该任务。正如研究论文中所建议的,我们还将抖动和镜像应用于现有的可用数据,以避免过度拟合。这种增强技术通常非常有用。在这一步中,我们将图像的大小调整为 286 x 286,然后将其裁剪回所需的 256 x 256 大小,并从左向右水平翻转图像。下面是执行以下操作的代码块。
def random_crop(image):
cropped_image = tf.image.random_crop(
image, size=[IMG_HEIGHT, IMG_WIDTH, 3])
return cropped_image
# normalizing the images to [-1, 1]
def normalize(image):
image = tf.cast(image, tf.float32)
image = (image / 127.5) - 1
return image
def random_jitter(image):
# resizing to 286 x 286 x 3
image = tf.image.resize(image, [286, 286],
method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
# randomly cropping to 256 x 256 x 3
image = random_crop(image)
# random mirroring
image = tf.image.random_flip_left_right(image)
return image
def preprocess_image_train(image, label):
image = random_jitter(image)
image = normalize(image)
return image
def preprocess_image_test(image, label):
image = normalize(image)
return image
最后,我们将把所有这些数据元素计算成一个最终数据集。我们将使用随机洗牌和预定义的批量大小来映射数据,通过这些数据可以访问所有组件。我们将为马和斑马图像的所有训练元素和测试元素定义数据集,如下面的代码片段所示。
train_horses = train_horses.cache().map(
preprocess_image_train, num_parallel_calls=AUTOTUNE).shuffle(
BUFFER_SIZE).batch(BATCH_SIZE)
train_zebras = train_zebras.cache().map(
preprocess_image_train, num_parallel_calls=AUTOTUNE).shuffle(
BUFFER_SIZE).batch(BATCH_SIZE)
test_horses = test_horses.map(
preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
BUFFER_SIZE).batch(BATCH_SIZE)
test_zebras = test_zebras.map(
preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
BUFFER_SIZE).batch(BATCH_SIZE)
sample_horse = next(iter(train_horses))
sample_zebra = next(iter(train_zebras))
下面是一些样本图像,其数据集包含原始图像及其带有一些随机抖动的对应图像。
Sample Image for Horse with random jitter
Sample Image for Zebra with random jitter
一旦我们完成了获取和准备数据集所需的所有步骤,我们就可以开始创建鉴频器和发生器网络,以构建整体循环 GAN 架构。
定义鉴别器架构:
对于鉴别器架构,我们有四个卷积块,定义如下- \(C64-C128-C256-C512\)。使用α(斜率)值为 0.2 的泄漏 ReLU 激活函数。除了第一个卷积块之外,所有其他块都在卷积层之后立即使用实例归一化。步幅和内核大小如下面的代码片段所示。我们将最终编译具有均方误差损失函数和 Adam 优化器的模型,以完成贴片 GAN 鉴别器型网络。
# define the discriminator model
def define_discriminator(image_shape):
# weight initialization
init = RandomNormal(stddev=0.02)
# source image input
in_image = Input(shape=image_shape)
# C64
d = Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(in_image)
d = LeakyReLU(alpha=0.2)(d)
# C128
d = Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
d = InstanceNormalization(axis=-1)(d)
d = LeakyReLU(alpha=0.2)(d)
# C256
d = Conv2D(256, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
d = InstanceNormalization(axis=-1)(d)
d = LeakyReLU(alpha=0.2)(d)
# C512
d = Conv2D(512, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
d = InstanceNormalization(axis=-1)(d)
d = LeakyReLU(alpha=0.2)(d)
# second last output layer
d = Conv2D(512, (4,4), padding='same', kernel_initializer=init)(d)
d = InstanceNormalization(axis=-1)(d)
d = LeakyReLU(alpha=0.2)(d)
# patch output
patch_out = Conv2D(1, (4,4), padding='same', kernel_initializer=init)(d)
# define model
model = Model(in_image, patch_out)
# compile model
model.compile(loss='mse', optimizer=Adam(learning_rate=0.0002, beta_1=0.5), loss_weights=[0.5])
return model
# define image shape
image_shape = (256,256,3)
# create the model
model = define_discriminator(image_shape)
# summarize the model
model.summary()
定义发电机架构:
生成器架构包括一个卷积块,具有 64 个滤波器和步长为 2 的 7 x 7 内核大小,其后是两个步长为 1 的 3 x 3 内核大小的卷积层,分别具有 128 和 256 个滤波器。然后我们将定义残差块,它只不过是一堆卷积层。这个残差块之后是由卷积 2D 转置层定义的几个上采样层。最终的卷积层包含 7×7 的内核大小,步长为 1 和 3 个滤波器。
9 个残差块的生成器架构定义如下:$c7s 1-64,d128,d256,R256,R256,R256,R256,R256,R256,R256,R256,R256,R256,u128,u64,c7s 1-3$个
# generator a resnet block
def resnet_block(n_filters, input_layer):
# weight initialization
init = RandomNormal(stddev=0.02)
# first layer convolutional layer
g = Conv2D(n_filters, (3,3), padding='same', kernel_initializer=init)(input_layer)
g = InstanceNormalization(axis=-1)(g)
g = Activation('relu')(g)
# second convolutional layer
g = Conv2D(n_filters, (3,3), padding='same', kernel_initializer=init)(g)
g = InstanceNormalization(axis=-1)(g)
# concatenate merge channel-wise with input layer
g = Concatenate()([g, input_layer])
return g
# define the standalone generator model
def define_generator(image_shape=(256, 256, 3), n_resnet=9):
# weight initialization
init = RandomNormal(stddev=0.02)
# image input
in_image = Input(shape=image_shape)
# c7s1-64
g = Conv2D(64, (7,7), padding='same', kernel_initializer=init)(in_image)
g = InstanceNormalization(axis=-1)(g)
g = Activation('relu')(g)
# d128
g = Conv2D(128, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
g = InstanceNormalization(axis=-1)(g)
g = Activation('relu')(g)
# d256
g = Conv2D(256, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
g = InstanceNormalization(axis=-1)(g)
g = Activation('relu')(g)
# R256
for _ in range(n_resnet):
g = resnet_block(256, g)
# u128
g = Conv2DTranspose(128, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
g = InstanceNormalization(axis=-1)(g)
g = Activation('relu')(g)
# u64
g = Conv2DTranspose(64, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
g = InstanceNormalization(axis=-1)(g)
g = Activation('relu')(g)
# c7s1-3
g = Conv2D(3, (7,7), padding='same', kernel_initializer=init)(g)
g = InstanceNormalization(axis=-1)(g)
out_image = Activation('tanh')(g)
# define model
model = Model(in_image, out_image)
return model
# create the model
model = define_generator()
# summarize the model
model.summary()
Sample Untrained Generated Image
定义损失函数和检查点:
在下一段代码中,我们将探讨不同类型的损耗函数,这些函数将用于循环 GAN 架构。我们将定义生成器损失、鉴别器损失、周期一致性损失和同一性损失。如前所述,周期一致性丢失的原因是为了保持源图像和再现图像之间的近似关系。最后,我们将为生成器和鉴别器网络定义 Adam 优化器,如下面的代码块所示。
LAMBDA = 10
loss_obj = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def discriminator_loss(real, generated):
real_loss = loss_obj(tf.ones_like(real), real)
generated_loss = loss_obj(tf.zeros_like(generated), generated)
total_disc_loss = real_loss + generated_loss
return total_disc_loss * 0.5
def generator_loss(generated):
return loss_obj(tf.ones_like(generated), generated)
def calc_cycle_loss(real_image, cycled_image):
loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))
return LAMBDA * loss1
def identity_loss(real_image, same_image):
loss = tf.reduce_mean(tf.abs(real_image - same_image))
return LAMBDA * 0.5 * loss
generator_g_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
generator_f_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
discriminator_x_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
discriminator_y_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
一旦我们完成了损失函数和优化器的定义,我们还将定义一个检查点系统,在这里我们将存储所需的检查点以及保存的最新结果。我们可以这样做,以便在需要时重新加载和重新训练重量。
checkpoint_path = "./checkpoints/train"
ckpt = tf.train.Checkpoint(generator_g=generator_g,
generator_f=generator_f,
discriminator_x=discriminator_x,
discriminator_y=discriminator_y,
generator_g_optimizer=generator_g_optimizer,
generator_f_optimizer=generator_f_optimizer,
discriminator_x_optimizer=discriminator_x_optimizer,
discriminator_y_optimizer=discriminator_y_optimizer)
ckpt_manager = tf.train.CheckpointManager(ckpt, checkpoint_path, max_to_keep=5)
# if a checkpoint exists, restore the latest checkpoint.
if ckpt_manager.latest_checkpoint:
ckpt.restore(ckpt_manager.latest_checkpoint)
print ('Latest checkpoint restored!!')
定义最终培训功能:
在最后一步,我们将定义训练函数来训练我们的模型,并根据需要生成所需的图像。首先,让我们设置我们计划为其训练模型的时期的数量,并创建用于创建用于可视化输入和预测图像的相应图的函数。注意,对于这次训练,我在二十个时期后使用键盘中断,但是观众可以训练更长的时间以获得更好的结果。
EPOCHS = 50
def generate_images(model, test_input):
prediction = model(test_input)
plt.figure(figsize=(12, 12))
display_list = [test_input[0], prediction[0]]
title = ['Input Image', 'Predicted Image']
for i in range(2):
plt.subplot(1, 2, i+1)
plt.title(title[i])
# getting the pixel values between [0, 1] to plot it.
plt.imshow(display_list[i] * 0.5 + 0.5)
plt.axis('off')
plt.show()
在训练步骤函数中,我们将调用 tf .函数和梯度带来更快地训练反向传播权重的计算。训练方法类似于我们之前构建的 GAN 架构,只是我们将在此方法中训练两个生成器和鉴别器,并根据需要评估周期一致性损失。以下是 Cycle GAN 架构完整培训程序的代码块。
@tf.function
def train_step(real_x, real_y):
# persistent is set to True because the tape is used more than
# once to calculate the gradients.
with tf.GradientTape(persistent=True) as tape:
# Generator G translates X -> Y
# Generator F translates Y -> X.
fake_y = generator_g(real_x, training=True)
cycled_x = generator_f(fake_y, training=True)
fake_x = generator_f(real_y, training=True)
cycled_y = generator_g(fake_x, training=True)
# same_x and same_y are used for identity loss.
same_x = generator_f(real_x, training=True)
same_y = generator_g(real_y, training=True)
disc_real_x = discriminator_x(real_x, training=True)
disc_real_y = discriminator_y(real_y, training=True)
disc_fake_x = discriminator_x(fake_x, training=True)
disc_fake_y = discriminator_y(fake_y, training=True)
# calculate the loss
gen_g_loss = generator_loss(disc_fake_y)
gen_f_loss = generator_loss(disc_fake_x)
total_cycle_loss = calc_cycle_loss(real_x, cycled_x) + calc_cycle_loss(real_y, cycled_y)
# Total generator loss = adversarial loss + cycle loss
total_gen_g_loss = gen_g_loss + total_cycle_loss + identity_loss(real_y, same_y)
total_gen_f_loss = gen_f_loss + total_cycle_loss + identity_loss(real_x, same_x)
disc_x_loss = discriminator_loss(disc_real_x, disc_fake_x)
disc_y_loss = discriminator_loss(disc_real_y, disc_fake_y)
# Calculate the gradients for generator and discriminator
generator_g_gradients = tape.gradient(total_gen_g_loss,
generator_g.trainable_variables)
generator_f_gradients = tape.gradient(total_gen_f_loss,
generator_f.trainable_variables)
discriminator_x_gradients = tape.gradient(disc_x_loss,
discriminator_x.trainable_variables)
discriminator_y_gradients = tape.gradient(disc_y_loss,
discriminator_y.trainable_variables)
# Apply the gradients to the optimizer
generator_g_optimizer.apply_gradients(zip(generator_g_gradients,
generator_g.trainable_variables))
generator_f_optimizer.apply_gradients(zip(generator_f_gradients,
generator_f.trainable_variables))
discriminator_x_optimizer.apply_gradients(zip(discriminator_x_gradients,
discriminator_x.trainable_variables))
discriminator_y_optimizer.apply_gradients(zip(discriminator_y_gradients,
discriminator_y.trainable_variables))
最后,我们可以开始定义数量的时期的训练过程。我们将循环遍历我们的训练数据,并相应地训练模型。在每个时期结束时,我们可以生成结果图像来注意模型的进展。我们还可以保存检查点,如下面的代码片段所示。
for epoch in range(EPOCHS):
start = time.time()
n = 0
for image_x, image_y in tf.data.Dataset.zip((train_horses, train_zebras)):
train_step(image_x, image_y)
if n % 10 == 0:
print ('.', end='')
n += 1
clear_output(wait=True)
# Using a consistent image (sample_horse) so that the progress of the model
# is clearly visible.
generate_images(generator_g, sample_horse)
if (epoch + 1) % 5 == 0:
ckpt_save_path = ckpt_manager.save()
print ('Saving checkpoint for epoch {} at {}'.format(epoch+1,
ckpt_save_path))
print ('Time taken for epoch {} is {} sec\n'.format(epoch + 1,
time.time()-start))
Saving checkpoint for epoch 20 at ./checkpoints/train/ckpt-4
以下是我经过二十个时期的训练后获得的一些结果。训练更多的时期和改变一些参数可以产生更好的结果。
在 Cycle GANs 的帮助下,我们可以注意到使用训练过的架构获得的总体结果非常令人信服。我们能够为不成对的图像翻译任务产生明显可解释程度的期望结果。虽然这个网络有一些限制,包括背景颜色和纹理细节的丢失,但我们仍然能够检索特定任务的大约大部分必要信息。我建议尝试各种参数、变化和微小的架构变化,以获得更好的结果。
该代码的主要部分来自 TensorFlow 官方网站,可通过此链接查看。然而,他们利用 pix-2-pix 模型的变体来训练循环 GAN 网络,为了简单起见,利用类似 U-Net 的发生器网络和相应的鉴别器网络。在本文中,我们只关注从零开始重新构建研究论文,并使用 ResNet 架构作为生成器,使用稍加修改的 Patch GAN 架构作为鉴别器。我建议查看下面的参考链接以获得关于循环 GAN 发生器和鉴别器实现的更详细的指导。
结论:
Photo by Chris Stenger / Unsplash
在自然界中,对于复杂的问题,很难获得大量成对的例子。创建这样的将源图像链接到它们各自的目标图像的成对数据集通常是昂贵且耗时的。然而,因特网上有大量不成对的图像到图像翻译的例子。本文中讨论的马和斑马数据集就是这样一个例子,其中期望的输出没有明确定义。大多数现代 GANs 可以解决与图像到图像翻译的成对示例相关的任务和项目。但是循环 GAN 条件 GAN 具有在不成对数据集上实现期望输出的能力。
在这篇文章中,我们探索了循环一致对抗网络中条件 gan 的另一种变体(循环 gan)。我们了解这些 GANs 有多么强大,以及它们即使在没有成对例子的情况下也能学习图像到图像的翻译。我们简要介绍了这些 Cycle GANs 的一些基本概念,并详细分析了其网络架构的一些核心概念。最后,我们从零开始开发了“使用循环 GANS 的不成对图像到图像的翻译”项目,从而结束了我们的概念性理解。
在以后的文章中,我们将讨论更多类型的 GAN,比如 ProGAN、StyleGAN 等等。我们还将尝试一些具有深度学习的音频任务,并从零开始学习构建神经网络。在那之前,继续学习探索吧!
无监督政治:机器学习中的聚类
原文:https://blog.paperspace.com/unsupervised-politics-clustering-in-machine-learning/
当面对大量需要理解的数据时,可能很难知道从哪里开始寻找有趣的趋势。不要试图用数据做出具体的预测,您可能想从简单地探索数据开始,看看有什么。对于这个任务,我们转向无监督的机器学习。与监督学习相比,无监督模型只接受输入数据,并自行搜索感兴趣的模式,无需人工干预。
探索性数据挖掘中最常见的无监督方法之一称为聚类,它试图根据相似性将观察结果分组到子组中。这方面的例子包括:根据相似的特征对客户进行分组,根据相似的内容对文本进行分组,对脸书上的“朋友”进行分组,对癌性肿瘤进行聚类,等等。我们只需输入模型数据,模型将数据分成相似的组。
本文使用一个适时的例子:政治,引导读者完成 K-means 和层次聚类的任务。尽管巴拉克·奥巴马(Barack Obama)在 2012 年的竞选中巧妙地运用了数据科学技术,但许多分析师也因错误预测 2016 年大选结果而受到批评。本文提供了一个这样的模型,它可能对分析政治有用,并为您提供了一个自己尝试实现无监督机器学习模型的机会。
入门指南
让我们首先加载我们的数据和一些对集群任务有帮助的包。本文使用在 R Studio 中实现的统计语言 R。如果你还没有 R 的话,一定要安装它。
让我们装载三个包裹。 cluster
包承载了各种聚类函数,以及我们将使用的数据。 dendextend
和 circlize
包将用于可视化我们的集群。如果您以前没有使用过这些包,首先使用install.packages("")
命令获取这些包。然后,您可以从您的库中加载它们,如下所示。
library(cluster)
library(dendextend)
library(circlize)
加载完包后,让我们直接从cluster
包中导入数据。我们将使用的数据集被称为votes.repub
,它包含了从 1856 年到 1976 年各州在总统选举中给予共和党候选人的选票百分比。该数据可以使用cluster::
命令直接从cluster
包中导入。我们将把它命名为raw.data
。
raw.data <- cluster::votes.repub
您可以使用View()
功能查看原始数据。你会看到左边列出的州,列名为选举年份,每个单元格中共和党候选人获得的州选票百分比。
让我们对数据集中最近的一年进行分析:1976 年。这意味着我们只需要通过子集化我们的数据来提取 1976 年的数据。我们定义想要保留的变量——“x 1976”——并用方括号将它从原始数据中提取出来。
keep <- c("X1976")
votes.1976 <- raw.data[keep]
既然我们已经加载了包并对数据进行了子集化,我们就可以开始建模了。
R 中的聚类
聚类是无监督机器学习的一种方法——在这种情况下,您只有观察值,并且您希望模型搜索有趣的模式,而不需要您确切指定您在寻找什么。集群是一套广泛的技术,可以帮助您完成这项任务。
聚类将您的数据划分到不同的组中,以便每个组中的观察结果相对相似。例如,您可能有来自 Twitter 的数据,并希望根据内容的相似性对推文进行分组。然后,聚类算法会将推文分类成组。你可能会注意到一些模式,比如聚集在一起谈论政治和名人八卦的推文。或者,也许你有一个推文子集,都提到了一部新上映的电影。然后,你可以对这些推文进行聚类,看看是否会出现子群,也许是基于推文中使用的正面或负面语言。这有助于揭示数据中有趣的模式,并可能引导您进一步分析结果模式。
我们将尝试两种不同的聚类方法:K-means 和层次聚类。使用 K-means,您可以指定要对数据进行排序的聚类数(K)。该算法搜索数据的最佳排序,以使类内变化尽可能小,即尽可能最相似的分组。
但是,有时你不知道你要找的子群的数量。因此,我们也将使用层次聚类。分层聚类算法获取观测值之间的距离或差异,并根据最相似或最不相似的观测值对观测值进行分组。它以类似树的方式完成这项工作,并生成一个非常容易理解的图,称为树状图。
我们可以将这些模型应用于我们想要分组的任何类型的数据,但我们将把这两种方法都应用于我们的投票数据,以说明差异。我们的目标是找到在总统选举中投票相似或不同的州组。这对于政治顾问和分析师来说可能是有用的信息,但这类信息也用于行业的市场研究或学术界的政治学研究。
让我们从 K 均值聚类开始。为了在 R 中实现这一点,我们使用了kmeans
函数。在括号中,我们将告诉函数对我们 1976 年的投票数据执行 K-means。因为我们必须指定我们想要的组的数量,我们将使用 K=2,因为我们知道每个州的多数票肯定都流向了民主党候选人吉米·卡特或共和党候选人杰拉尔德·福特。
set.seed(1912)
votes.km <- kmeans(votes.1976, 2, nstart=50)
然后,我们可以将模型的结果可视化。圆形树状图提供了一个有用的可视化。我们还可以将州涂成红色或蓝色,以与他们投票支持的候选人的政党联系起来。
dist.km <- dist(votes.km$cluster)
colors <- c("blue3", "red3")
clust.km <- hclust(dist.km)
circle.km <- color_labels(clust.km, k=2, col = colors)
par(cex =.75)
circlize_dendrogram(circle.km)
这提供了一个很好的、整理过的状态投票模式的表示。
但是也许你想要一张更细致的图片来了解正在发生的事情。例如,你可能有两个州分别有 30%和 49%的人投票给共和党,而另外两个州分别有 51%和 65%的人投票给共和党。在 K-means 聚类中(以及在总统选举中),50%以下的观察值将被分类为投票给民主党,而 50%以上的观察值将被分类为投票给共和党。但实际上,投票给 49%和 51%的州比投票给 30%和 65%的州更相似。
为此,我们求助于层次聚类。首先,我们需要测量各州选票之间的距离(或差异)。其逻辑是,我们希望将人口投票比其他州更相似的州聚集在一起。
我们将使用一种常见的度量方法——欧几里得距离——但是在统计学和数学中有许多度量方法,并且很大程度上取决于您使用的数据类型和您试图完成的任务。关于选择距离/相异度度量的更多信息,请查看这篇 StackExchange 帖子。
我们可以使用dist()
函数测量距离,并将距离方法设置为“欧几里得”
data.dist <- dist(votes.1976, method = "euclidean")
然后,我们使用hclust
函数对我们的距离集执行层次聚类,并绘制结果。
clust.h <- hclust(data.dist)
par(cex =.8)
plot(clust.h, xlab = "States", sub="")
注意,我们的数据中有更多的变化。每个叶子(或状态)通过相似性定位在一起,并通过分支连接。左侧轴上分支的高度让您了解每个状态的相似或不同之处。我们可以在感兴趣的不同高度“切割”这棵树。看起来分层算法在高度切割为 6 时找到了大约七个分组(与 K-means 的两个集合相比)。为了更清楚,我们可以使用dendextend
包中的color_labels
函数给树叶着色,并指定我们希望看到这七组被着色。
clust.h.color <- color_labels(clust.h, k=7)
par(cex =.7)
plot(clust.h.color)
这更准确地展示了各州人口投票方式的相似性。这意味着选举地图可能不会告诉完整的故事:即使一些州在技术上投票给民主党或共和党,他们的选民人口在偏好上可能相当多样化。
结束语
在本教程中,我们讨论了聚类的无监督机器学习方法,并使用 1976 年总统选举期间各州的投票数据说明了 K-means 和层次聚类。我们能够根据各州对民主党或共和党候选人的投票情况,以及各州投票习惯的相似程度,对各州进行分类。
如果你对这个主题的更技术性的处理感兴趣,请查看第十章的统计学习介绍。或者,尝试在 r 中直接使用一些其他数据集。你可以尝试对任何东西进行聚类,从用于分类和诊断癌症的鸢尾花到微阵列基因表达。
然而,当您可以访问大量数据时,这些方法尤其有用。尝试寻找你感兴趣的数据,无论是脸书或推特这样的来源,还是购买你产品的顾客的属性。通过聚类技术,您可能会发现分组和理解数据的新方法。
文件上传到达渐变笔记本
原文:https://blog.paperspace.com/upload-files-paperspace-gradient-notebooks/
我们很高兴为渐变笔记本 IDE 发布一个新的文件上传器。
自从我们在 2 月份发布了全新的渐变笔记本 IDE 以来,我们一直专注于添加特性和功能,以改善笔记本电脑中的文件管理和资源管理用户体验。
在我们的上一个版本中,我们围绕文件和文件夹、GPU 利用率、笔记本预览和笔记本链接共享增加了许多生活质量改进。我们在最新版本中延续了这一主题。
新的文件上传程序可以轻松地将任何类型的文件上传到正在运行的笔记本电脑中。当文件上传到笔记本时,您还可以单独跟踪文件上传。
You can now track file upload progress in the file browser!
要访问文件上传程序,请在正在运行的笔记本的文件管理器侧边栏中查找Upload File
按钮。
其他改进
你在定位打开你的实例的按钮时有什么困难吗?
根据大众的要求,我们为笔记本实例增加了更突出的开始和停止控件。我们从许多用户那里听到,我们需要使这些控件更加可见,所以我们现在在控制台中突出显示它们。
Start and Stop commands are now much more visible within the notebook interface.
使用笔记本电脑时,内存不足是一件不幸的事情,但它确实会发生。我们决心帮助您了解您的实例何时消耗了太多内存,以便您更好地了解笔记本电脑内核崩溃的时间和原因。
为此,我们增加了系统级指标来衡量 RAM 使用情况以及 CPU 和 GPU 使用情况,从而提高了笔记本电脑页脚的信息密度。
RAM usage is now conveyed in the notebook footer.
找到计费面板变得太难了,所以我们在控制台的用户菜单顶层添加了一个快捷方式。现在,您可以快速轻松地访问您的套餐、限额和发票!
Billing is now available in the top level of the user menu in the console.
接下来,我们将深入了解笔记本电脑的更多可用性改进。我们也非常兴奋地将完全终端访问引入笔记本 IDE——这有望为今天需要我们使用 JupyterLab 的许多活动带来惊人的加速,我们非常兴奋。
前进!
关于此版本的更多信息,请查看 Paperspace 变更日志。
为年龄转换实现 CycleGAN
原文:https://blog.paperspace.com/use-cyclegan-age-conversion-keras-python/
如果你今年在任何社交媒体上,你可能会看到人们上传自己的近照,紧挨着另一张他们 30 年后的照片。
Source: https://www.boredpanda.com/old-age-filter-photos-faceapp/
这一切之所以成为可能,是因为一款名为face app的应用在全球范围内迅速走红。虽然在过去已经有过不同的尝试来解决面部老化问题,但是他们面临着一些限制,比如需要大量的数据,产生鬼影(看起来不自然),以及无法进行相反的操作,即从老变年轻。简单地说,结果没有达到我们的期望。
随着 GAN 架构最近的成功,我们现在可以产生高分辨率和自然外观的输出。在本教程中,我们将训练当今最有趣的架构之一 CycleGAN 从 20 秒到 50 秒进行正向老化,从 50 秒到 20 秒进行反向老化。让我们开始吧。
生成算法
如果你曾经训练过一个神经网络,比如一个简单的分类网络,你可能训练过一个辨别网络,它的唯一任务是区分类别(就像狗和猫的分类器)。流行的神经网络架构,如 VGG 和 Resnet 属于这一类。
生成算法 ,另一方面,是一组有趣的算法,它们能做的远不止简单的分类任务。他们可以生成看起来与模型被训练的领域相似的数据。例如,如果在马的图片上训练模型,生成算法可以创建看起来真实但与训练示例不同的新马。把这想象成人类如何想象世界上的任何事物,只要闭上眼睛思考。
**
If Yann Lecun says so, we must nod**
GANs 如何工作
生成对抗网络 (GANs)是最流行的生成算法之一。它们有许多有趣的应用(其中一些在这里被探索)。GAN 由两个神经网络组成:一个发生器和一个鉴别器。生成器网络的任务是创建真实的图像,而鉴别器网络必须区分真实图像和由生成器创建的假图像。
发生器和鉴别器在一场 minimax 游戏中相互竞争,直到发生器创建的图像如此逼真,以至于鉴别器无法确定哪个图像是真实的,哪个是人工生成的。在这个阶段达到平衡,训练停止。
**
Source: https://dzone.com/articles/working-principles-of-generative-adversarial-netwo**
两个网络同时被训练,直到达到平衡。由于两个网络相互竞争,并试图在每次迭代中自我完善,因此损失不会像一般分类网络那样持续减少。我们将在下面讨论如何监控网络性能。
为了实现不同的用例,许多新的架构正在不断开发中,其中比较流行的有 DCGAN、StyleGAN、CGAN、BigGAN 等。对于人脸老化的情况,我们感兴趣的是一种专门从事域名迁移的特殊架构,称为 CycleGAN。它可以从一个域(例如,一匹马)获取图像,并将其转换到另一个域(如斑马),同时保持输入域的特征(即,看起来与输入马相似)。
CycleGAN 与众不同
CycleGAN 是我们上面讨论的通用 GAN 架构的变体,不同之处在于它有两对发生器和鉴别器。它的开发是为了解决当试图从一个领域翻译到另一个领域时需要大量图像的问题。例如,如果我们想让一个将军甘修改马的图像看起来像斑马,这将需要大量的标记马的图像和相应的相似的斑马图像。这种数据收集不仅麻烦,而且几乎不可能,因为你不可能总是获得不同领域的配对图像。
**
Source: https://www.tensorflow.org/tutorials/generative/cyclegan**
CycleGAN 解决了需要来自两个域的标记图像的数据集的问题。它通过提出一个简单而聪明的技巧来做到这一点。它不是只有一个从马到斑马的网络,而是有两个网络:一个从马到斑马,另一个从斑马到马。下图展示了这一点。
考虑两对发生器-鉴别器作为 G1-D1 和 G2-D2 。 G1 将输入的马图像转换成看起来像斑马的图像。然后, D1 的任务是考虑来自 G1 的图像是真实的斑马,还是来自生成器网络的生成斑马。从 G1 生成的图像现在被传递给生成器 G2 。 G2 的任务是将生成的斑马图像转换成类似马的图像。所以我们拿一匹马,用 G1 把它转换成斑马,然后用 G2 把它转换回马。然后 D2 的任务是从 G2 中辨别出图像是一匹真正的马,还是一匹生成的马。
现在网络是用多重损失来训练的。我们使用两个发生器-鉴频器对的损耗,就像一般的 GAN 一样,但我们还添加了一个循环损耗。当图像在通过两个发生器之后循环返回时,使用该损失;最终图像应该看起来像原始输入图像(即,当从马→斑马→马时,最终的马应该看起来像原始的马)。对这种循环损失的需要来自于我们的要求,即从一个域转换到另一个域的图像应该保留与原始域的区别特征。
如果使用 CycleGAN 从一匹马生成斑马,它不仅应该看起来像斑马,还应该给人与被修改为看起来像斑马的原始马相同的感觉。
现在我们可以看到,不需要一个带标签的数据集来将每匹马映射到相应的相似外观的斑马。我们只需要提供一组马的图像和一组斑马的图像,网络会自己学习如何做翻译。由于域转移是双向的,我们也可以用第二个生成器 G2 将这些斑马图像转换回马图像。
**
Use cases of CycleGAN. Source: https://github.com/hyunbo9/yonsei**
使用 CycleGAN 改变面部
记住这个理论,让我们开始构建应用程序。通过查看上面讨论的架构,我们应该知道如何解决这个问题。我们将从 20-30 岁的人群中获取一组面部图像,从 50-60 岁的人群中获取另一组图像。然后,我们将使用 CycleGAN 进行域名迁移,将 20 岁的人转换为 50 岁的人,反之亦然。
完整笔记本请参考 GitHub 库 CycleGAN 进行年龄换算。
我们将使用 UTKFace 数据集,其中包含超过 20,000 张不同种族和性别的人的面部图像,年龄从 0 岁到 116 岁不等。由于我们只关注 20-30 岁和 50-60 岁的人,我们将过滤图像并删除其他年龄组的图像。
我们将使用 CycleGAN Keras 基本代码,并修改它以适应我们的用例。鉴别器是一个简单的网络,有 4 个卷积层,每个卷积层的步长为 2,还有一个最终的聚合卷积层。因此,如果我们提供大小为(256 x 256)的输入图像,我们将得到(16 x 16)的输出。这融合了 Pix2Pix 提出的建议之一,即 PatchGAN 鉴别器。PatchGAN 的输出映射到输入图像的一个补片,辨别输入图像的该补片是真的还是假的。在图像被确定为真实的情况下,期望的输出将是(16×16)个数字的矩阵,每个数字等于 1,如果图像被确定为人工生成,则等于 0。
这是更有利的,因为现在不是将整个图像分类为一类,而是将图像的多个小块分类为属于或不属于同一类。因此,我们在训练期间提供了更多的信号/梯度/信息,与使用 softmax 输出整个图像相比,可以产生更清晰的特征。
`def build_discriminator(self):
def d_layer(layer_input, filters, f_size=4, normalization=True):
"""Discriminator layer"""
d = Conv2D(filters, kernel_size=f_size, strides=2, padding='same')(layer_input)
d = LeakyReLU(alpha=0.2)(d)
if normalization:
d = InstanceNormalization()(d)
return d
img = Input(shape=self.img_shape)
d1 = d_layer(img, self.df, normalization=False)
d2 = d_layer(d1, self.df*2)
d3 = d_layer(d2, self.df*4)
d4 = d_layer(d3, self.df*8)
validity = Conv2D(1, kernel_size=4, strides=1, padding='same')(d4)
return Model(img, validity)`
**我们从 Keras GAN repo 获得的代码使用了 U-Net 风格的生成器,但需要进行修改。我们将使用一个 ResNet 风格的生成器,因为它在实验后为这个用例提供了更好的结果。生成器的输入是一个大小为(256 x 256)的图像,在这个场景中是一个 20 多岁的人的脸。
通过经过步长为 2 的 2 个卷积层,图像被下采样 4 倍(即 64×64),之后是 9 个保留大小的剩余块。然后,通过执行转置卷积,我们向上采样回到(256 x 256)的原始大小。我们得到的最终输出应该是同一个人的变形图像,现在看起来就像他们 50 多岁了。
# Resnet style generator
c0 = Input(shape=self.img_shape)
c1 = conv2d(c0, filters=self.gf, strides=1, name="g_e1", f_size=7)
c2 = conv2d(c1, filters=self.gf*2, strides=2, name="g_e2", f_size=3)
c3 = conv2d(c2, filters=self.gf*4, strides=2, name="g_e3", f_size=3)
r1 = residual(c3, filters=self.gf*4, name='g_r1')
r2 = residual(r1, self.gf*4, name='g_r2')
r3 = residual(r2, self.gf*4, name='g_r3')
r4 = residual(r3, self.gf*4, name='g_r4')
r5 = residual(r4, self.gf*4, name='g_r5')
r6 = residual(r5, self.gf*4, name='g_r6')
r7 = residual(r6, self.gf*4, name='g_r7')
r8 = residual(r7, self.gf*4, name='g_r8')
r9 = residual(r8, self.gf*4, name='g_r9')
d1 = conv2d_transpose(r9, filters=self.gf*2, f_size=3, strides=2, name='g_d1_dc')
d2 = conv2d_transpose(d1, filters=self.gf, f_size=3, strides=2, name='g_d2_dc')
output_img = Conv2D(self.channels, kernel_size=7, strides=1, padding='same', activation='tanh')(d2)
我们将有两对这样的生成器和鉴别器:一对用于前向老化,一对用于后向老化。
损失函数
我们终于到了损失函数。鉴别器损耗是我们上面讨论的贴片的均方误差。发电机损耗将是鉴频器损耗的负值,因为发电机试图最大化鉴频器误差。
如前所述,对于 CycleGAN,我们增加了循环损耗。我们将原始图像和回收图像之间的均方误差作为损失项。
如果 50 岁的图像被提供作为输入,进行从 20 岁到 50 岁的年龄转换的生成器不应该改变/转换图像。由于输入已经是期望的年龄,在这种情况下,网络应该充当身份。
当然,如果输入图像已经达到了期望的年龄,网络应该知道不做任何修改就返回该图像作为输出。为了确保网络以这种方式运行,一个身份损失被添加到损失函数中。这也是输出图像和输入图像之间的均方差。正向和反向发电机都有这个附加损耗项。
总之,我们有一般的发电机和鉴频器损耗,就像传统的氮化镓。此外,当从域 A 转换到 B ,然后回到域 A 时,我们有用于匹配输入的循环损耗。我们也有身份损失,以确保网络不会改变输入,如果它已经属于正确的域(在这种情况下,年龄)。
\ \ Loss = discriminant _ Loss+λ1 * cyclic _ Loss+λ2 * identity _ Loss \ \
这里λ1,λ2 是超参数
valid = np.ones((batch_size,) + self.disc_patch)
fake = np.zeros((batch_size,) + self.disc_patch)
fake_B = self.g_AB.predict(imgs_A)
fake_A = self.g_BA.predict(imgs_B)
dA_loss_real = self.d_A.train_on_batch(imgs_A, valid)
dA_loss_fake = self.d_A.train_on_batch(fake_A, fake)
dA_loss = 0.5 * np.add(dA_loss_real, dA_loss_fake)
dB_loss_real = self.d_B.train_on_batch(imgs_B, valid)
dB_loss_fake = self.d_B.train_on_batch(fake_B, fake)
dB_loss = 0.5 * np.add(dB_loss_real, dB_loss_fake)
# Total disciminator loss
d_loss = 0.5 * np.add(dA_loss, dB_loss)
g_loss = self.combined.train_on_batch([imgs_A, imgs_B],
[valid, valid,
imgs_A, imgs_B,
imgs_A, imgs_B])
在训练时,我们从 20 岁(图像 A)和 50 岁(图像 B)拍摄一批图像对。生成器 g_AB 将年龄 20 转换为年龄 50,鉴别器 d_A 将其分类为真实图像或生成图像。g_BA 和 d_B 对 50 岁到 20 岁的转换做了类似的工作。图像 A 被传递到 g_AB,并通过 g_BA 进行重建,反之亦然。
我们一起训练鉴别器和生成器的组合模型,并试图同时减少所有 3 种损失,即鉴别损失、循环损失和同一性损失。
稳定培训的方法
- 遵循杰瑞米·霍华德的建议,在训练时使用渐进式调整大小。我不能再强调这件事的重要性了。当我开始用 256 × 256 的大小训练时,我不得不使用批量大小 1,因为否则我的 GPU 会死。看到结果花了很多时间,相信我,你需要修改很多。如果你每次实验都要等几个小时,那就需要很长时间。所以从较小的尺寸开始,比如 64 × 64,然后逐渐增加输入图像的尺寸。这帮助我以 32 的批量运行(快了 32 倍)。这种技巧是可行的,因为网络的初始特征层学习相同的概念,而不管图像大小。
- 密切关注鉴别器、生成器、循环、身份丢失。如果一个损失超过另一个,试着调整系数λ1,λ2。否则,模型可能会专注于优化一个损失,而牺牲其他损失。例如,如果循环损失占主导地位,则循环图像看起来与输入图像相同,但生成的图像不会如我们所期望的那样,即年龄增长可能没有发生,因为网络将更多的注意力放在循环损失上。
排除故障
与传统的分类任务不同,我们不能通过查看损耗来判断网络的性能,也不能说如果损耗降低,模型就会改善,因为在 GAN 中,损耗不会一直降低。一种是试图降低损耗的鉴频器,另一种是工作原理相反的发电机,它试图增加鉴频器损耗,因此损耗以颠倒的方式进行。
但是我们怎么知道网络正在被训练?我们通过在训练过程中观察发电机的输出来做到这一点。在每几次迭代中,对一些图像进行采样,并通过生成器来查看产生了什么结果。如果你觉得结果看起来不吸引人,或者你觉得只有损失得到优化,你可以尝试修补几个部分,修复它,并重新开始训练。
此外,这种查看输出和检查输出的方式比在分类任务中查看一个简单的数字更有价值,也更容易上瘾。当我开发应用程序时,我不能停止等待每几个迭代完成,这样我就可以看到生成的输出,同时为生成器的胜利欢呼(抱歉歧视者)。
在使用上述技术和技巧训练了大约 50 个时期后,结果看起来如下,相当不错
Age conversion
现实世界中的用法
正如你在上面看到的,用于训练的图像是完美捕捉的头像,但在现实世界中,可能并不总是有可能获得这样的图像来使用我们的 Cyclegan 进行面部老化。我们需要能够找到一张脸在图像中的位置,并修改图像的这一部分。
为此,我们将在将图像传递给 cyclegan 之前运行人脸检测器。面部检测器给出图像中各种面部的边界框。然后,我们将编写一个脚本,将这些盒子中的作物发送到我们的网络。然后,我们将把输出放回输入图像。这样我们就可以处理来自真实世界的任何图像
为此,我们将使用来自的 opencv 人脸检测器,这里的基于 resnet-ssd 架构。
def detectFaceOpenCVDnn(net, frame, ctype):
frameOpencvDnn = frame.copy()
frameHeight = frameOpencvDnn.shape[0]
frameWidth = frameOpencvDnn.shape[1]
blob = cv2.dnn.blobFromImage(frameOpencvDnn, 1.0, (frameHeight, frameWidth), [104, 117, 123], False, False)
net.setInput(blob)
detections = net.forward()
bboxes = []
for i in range(detections.shape[2]):
confidence = detections[0, 0, i, 2]
if confidence > conf_threshold:
x1 = int(detections[0, 0, i, 3] * frameWidth)
y1 = int(detections[0, 0, i, 4] * frameHeight)
x2 = int(detections[0, 0, i, 5] * frameWidth)
y2 = int(detections[0, 0, i, 6] * frameHeight)
bboxes.append([x1, y1, x2, y2])
if not(x1<30 or y1<30 or x2>frameWidth-30 or y2>frameHeight-30):
y1, y2 = y1-20, y2+20
x1, x2 = x1-20, x2+20
else:
continue
crop_img = frameOpencvDnn[y1:y2, x1:x2]
crop_img = cv2.cvtColor(crop_img, cv2.COLOR_BGR2RGB).astype("float32")
cv2.imwrite("cropped"+str(i)+".jpg", crop_img)
inp = np.array([gan.data_loader.get_img(crop_img)])
case1 = np.ones(gan.condition_shape)
case2 = np.zeros(gan.condition_shape)
if ctype==0:
case = case1
else:
case = case2
case1stack = np.array([case]*1)
old_img = gan.g_AB.predict([inp, case1stack])
new_img = revert_img(old_img[0], (y2-y1, x2-x1))
new_img = cv2.cvtColor(new_img, cv2.COLOR_RGB2BGR).astype("float32")
frameOpencvDnn[y1:y2, x1:x2] = new_img
scipy.misc.imsave("old"+str(i)+".jpg", new_img)
return frameOpencvDnn, bboxes
conf_threshold = 0.8
modelFile = "opencv_face_detector_uint8.pb"
configFile = "opencv_face_detector.pbtxt"
net = cv2.dnn.readNetFromTensorflow(modelFile, configFile)
frame = cv2.imread("big3.jpg")
outOpencvDnn, bboxes = detectFaceOpenCVDnn(net,frame,0)
cv2.imwrite("big3_old.jpg", outOpencvDnn)
outOpencvDnn, bboxes = detectFaceOpenCVDnn(net,frame,1)
cv2.imwrite("big3_black.jpg", outOpencvDnn)
原象
Source: https://www.sportskeeda.com/tennis/top-3-tennis-players-of-the-21st-century
年龄转换
正如我们所看到的,对于我们训练的有限数据和图像大小来说,结果相当不错。此外,上面的图像看起来与模型训练的数据有很大不同,但模型仍然工作得很好,因此模型没有过度拟合。通过在更大的图像(UTKFace 为 256x256)和更真实的图像(如上)上训练网络,可以进一步改善结果,然后我们将有一个生产就绪的类似 Faceapp 的应用程序。
摘要
我们已经了解了什么是 GAN,以及如何使用变体 CycleGAN 来构建像 FaceApp 这样的应用。同样,我们讨论了一些稳定训练的方法。我们设计了一个实验,让发电机有足够的能力执行多项任务。
从这里去哪里?。我们可以在条件部分进行更多的实验,尝试看看我们是否可以同时执行多个任务,尝试看看生成器在不同条件输入下的表现。实验和改进的空间很大。
你也可以看看这个中的,通过使用变分自动编码器(另一种流行的生成算法)的变体,可以获得类似的结果。**
在生产中使用渐变工作流:更新我们的公共数据集
原文:https://blog.paperspace.com/using-gradient-workflows-in-production-updating-our-public-datasets/
Paperspace Gradient 由三个主要部分组成:笔记本、工作流和(即将)部署。虽然笔记本电脑主要是为探索性工作而设计的,但工作流是为更严格的方法而设计的,可直接用于生产。
在这里,我们通过使用工作流来更新我们的梯度公共数据集,展示了一个在生产中使用工作流的示例。
换句话说,我们足够信任我们的工作流程,可以在我们自己的产品上使用它们,你也可以!
注意:这篇博客是关于使用工作流来更新我们的数据集的。有关数据集本身和工作流的更多细节,请参见这里和这里。
什么是渐变公共数据集?
Gradient 旨在通过人工智能和机器学习(包括深度学习和 GPU)让人们快速轻松地启动和运行。虽然我们希望消除为人工智能提供硬件和软件环境的障碍,但我们也希望让访问您的数据变得更容易。
我们通过提供与在线数据源(如亚马逊 S3)的集成连接、为其他数据位置运行任意代码(如curl
或wget
)的能力以及我们的公共数据集来实现这一目标。深度学习和 GPU 通常需要大量数据才能发挥最佳性能,因此,通过方便地提供这些数据的示例,我们减少了用户的障碍,他们不想找到他们的数据或进行大量下载。
访问公共数据集的方式与访问其他渐变数据集的方式相同,方法是指向它们在代码中的位置并包含渐变命名空间,例如,我们的数据集openslr
位于gradient/openslr
。
我们策划的公共数据集集合中可用的数据集在我们的文档中有描述,并将在未来的博客条目中展示。
数据的收集在不断发展,但在撰写本文时,它们包括 StyleGAN,LSUN,Open SLR,Self Driving Demo,COCO,fast.ai,以及一些较小的数据,如 Tiny-imagenet-200,Sentiment140 和 MNIST。
在我们的文档中查看关于我们公共数据集的详细信息。
什么是工作流?
作为一个端到端的机器学习和 MLOps 平台,工作流是 Gradient 从探索性数据科学转向生产的途径。
工作流包含在项目中,是您或您的团队运行笔记本和工作流、存储模型以及管理部署的工作空间。每个工作流都由一组按顺序运行的作业组成。作业可以以类似图形的方式相互关联,并且可以依次调用其他脚本,比如 Python .py
。这使您可以创建一个 100%指定的、可移植的和可重复的处理步骤的管道。
Example Workflow in directed-acyclic-graph (DAG) representation in the Gradient graphical user interface
在这个例子中,从一个不同的项目到这个项目,cloneStyleGAN2Repo
和getPretrainedModel
任务输入到训练中,评估并生成任务。每个输入到另一个作业的作业都必须在后续作业开始之前完成,但是如果(在本例中)cloneStyleGAN2Repo
成功,则trainOurModel
和evaluatePretrainedModel
等作业可以并行运行。
我们还可以查看包含数据集的相同工作流:
Same Workflow DAG with Gradient Dataset also shown
工作流本身是使用 YAML 指定的,它给出了生产系统所需的严格程度。一些用户可能不太熟悉 YAML,所以我们提供一些帮助带你去你需要去的地方。
要运行工作流,您可以使用 Gradient CLI 运行 YAML,它将如上所示显示在 GUI 中。YAML 本身位于一个文件中,由指定在哪些计算资源上运行工作流、作业及其相互关系以及每个作业正在执行的操作种类的信息组成。
后一个步骤是通过使用渐变动作实现的。这些类似于 GitHub 动作,但是渐变动作支持并行运行作业和使用大数据。这里有几个动作,但是对于我们的目的来说,最重要的是script@v1
,它允许我们在一个任务中执行一组命令,就像在脚本中一样。
有关工作流程中渐变动作的更多详细信息,请访问文档的工作流程部分。
使用工作流更新我们的公共数据集
那么,为什么我们需要更新我们的公共数据集呢?以前,Gradient 支持笔记本和实验,而不是工作流。后者是为模型训练而设计的,现在已被否决,并已被工作流所取代。然而,之前的设置是通过指向 Gradient 托管存储上的公共/datasets
目录来访问数据集的。因此,现在我们需要能够在工作流可以看到的梯度数据集名称空间中访问它们,如上面的gradient/openslr
示例所示。
为什么不直接复制过来?嗯,原则上我们可以通过连接正确的文件系统来实现,但是使用工作流来更新它们有几个好处:
- 使用工作流可以确定我们的公共数据集的出处和可再现性
- 将来很容易重新运行和更新它们
- 可以通过添加新的工作流来添加新的公共数据集
- 工作流在项目中,项目可以链接到 GitHub 存储库,因此我们可以轻松地维护版本和辅助信息,如数据集许可
- 由于最终创建的数据集是我们产品的一部分,它展示了我们的工作流用于企业生产目的的示例
因此,为了更新公共数据集,我们运行如下所示的工作流:
defaults:
resources:
instance-type: C7
jobs:
downloadData:
outputs:
openslr:
type: dataset
with:
ref: gradient/openslr
uses: script@v1
with:
script: |-
...
curl -o /outputs/openslr/original-mp3.tar.gz \
'https://www.openslr.org/resources/12/original-mp3.tar.gz'
...
cd /outputs/openslr
...
md5sum original-mp3.tar.gz
...
tar -zxf original-mp3.tar.gz
...
image: ubuntu:18.04
工作流是从原始源下载数据,并将其放入渐变公共数据集中,在本例中为gradient/openslr
。每个工作流运行都会产生数据集的显式版本,其 ID 是可用的,但整个数据集可以通过名称来引用。
在显示的代码中可以看到关于我们工作流的几个细节:
- 要使用的计算资源由
resources
指定,这里是渐变云上的 C7 CPU 实例。GPU 实例,例如 P4000 或 V100,可以以同样的方式访问。 - 有一个作业
downloadData
,它列在工作流的主要部分jobs
下。像resources
这样的规范可以是特定于作业的,也可以是我们在这里使用的全局示例,这意味着你可以为需要它的作业指定一个 GPU。一个工作流可以包含几个作业。 - 作业输出,在本例中称为
openslr
,类型为dataset
,其位置(ref
)为gradient/openslr
。Gradient 有几种类型的输入和输出,可以通过类型或动作来指定,包括挂载卷、Git 库、机器学习模型和亚马逊 S3 桶。 - 渐变动作
script@v1
允许我们以脚本的形式发布任意命令,就像在终端上一样。它们在script: |-
行之后给出。 - 这里的数据存放在 openslr.org 的一个网站上,所以我们用 curl 下载。如果连接不可靠,可以使用循环和-C 选项来恢复部分完成的下载。这对于大文件来说很方便(示例中的文件是 82g,我们目前最大的文件是 206)。
- 下载的数据放在
/outputs/openslr
目录中。当/outputs
下的目录与outputs:
下给出的名称相同时,目录的内容被放在作业输出中,在这种情况下是公共数据集。 - 因为任何代码都是允许的,所以我们可以执行与获取大型数据集相关的其他有用步骤,比如
md5sum
和文件提取,在本例中是tar -zxf
。 image: ubuntu:18.04
行告诉我们,我们正在 Docker 容器中运行工作流,在本例中,是 Ubuntu 18.04 映像。一般来说,像笔记本电脑一样,工作流在给定的 Docker 映像上运行,由 Kubernetes 编排,提供了一个方便(不需要许多安装和设置)、安全和可复制的环境。
一旦工作流运行完成,因为数据集gradient/openslr
是公共的,它就可供我们的用户使用。
所示示例是针对openslr
数据集的。一般来说,我们对每个公共数据集都有一个工作流,每个工作流都包含手头数据所需的确切步骤。这意味着每个数据集可以独立维护,并且数据的维护和出处可见。
真实数据有许多位置,需要不同的连接方法。在这里更新我们的公共数据集时遇到的一些例子包括亚马逊 S3(使用渐变s3-download@v1
动作)、Google Drive(使用gdown
实用程序)和学术种子(使用at-get
工具)。同样,也看到了许多数据格式和压缩方法,包括 CSV 平面文件、LMDB 图像数据库、TFRecords 二进制文件以及.zip
和tar.gz
压缩。
容器与已安装软件的组合、处理大量数据和许多文件的能力,以及 Gradient 对工具和接口的灵活支持,使得为您需要做的事情构建一套合适的工具变得容易。
我们的公共数据集现已更新,可用。因此,我们在生产中使用工作流来改进我们的企业业务产品!
摘要
我们展示了用于更新渐变公共数据集的渐变工作流,展示了一个在企业生产中使用的工作流示例。
使用工作流可以确定我们的公共数据集的出处和可再现性,并使该集合在未来易于维护和添加。
用户可以根据自己的需求运行类似的工作流,从使用作业实现更严格的方法到端到端数据科学,再到完整的企业生产系统。
后续步骤
要开始使用渐变,包括笔记本、工作流、模型等等,请查看我们的教程、 ML Showcase 或 GitHub 资源库。
感谢 Tom、Ben、Erika 和 Paperspace 的所有人让这个项目成为可能。
感谢您的阅读!
深度学习中的优化介绍:消失梯度和选择正确的激活函数
原文:https://blog.paperspace.com/vanishing-gradients-activation-function/
这是优化系列的第三篇帖子,我们试图给读者一个深度学习优化的全面回顾。到目前为止,我们已经了解了:
-
小批量梯度下降用于对抗局部最小值和鞍点。
-
像 Momentum、RMSProp 和 Adam 这样的自适应方法如何增加普通梯度下降来解决病态曲率的问题。
分布,该死的分布和统计
与之前的机器学习方法不同,神经网络不依赖于对输入数据的任何概率或统计假设。然而,确保神经网络正确学习所需的最重要的元素之一(如果不是最重要的元素的话)是馈送到神经网络的层的数据表现出某些属性。
- 数据分布应该是以零为中心的,即分布的平均值应该在零附近。缺少这一点会导致消失梯度和紧张的训练。
- 优选地,该分布是正态分布。缺少这一点会导致网络过度适应一个输入空间域。
- 随着训练的进行,批量激活和层激活的分布应保持恒定。这种缺失被称为内部协变量转移,这可能会减慢训练速度。
在本文中,我们将讨论第一个和第二个问题,以及如何使用激活函数来解决它们。最后,我们给出了一些实用的建议来为你的深层网络选择激活功能。
消失渐变
消失梯度的问题是有据可查的,随着我们对神经网络的深入研究,这个问题变得更加突出。让我们理解它们为什么会发生。想象一下最简单的神经网络。一堆线性堆积的神经元。
人们可以很容易地将这种类比扩展到更深层的密集连接的架构。事实上,只要将网络中的每一个神经元都替换成完整的一层,就很容易做到这一点。每个神经元使用 Sigmoid 非线性作为它的激活函数。
sigmoid 函数的图形如下所示。
如果你观察 sigmoid 函数的斜率,你会发现它在任一条纹上都趋向于零。或者更好的是,让我们看看 sigmoid 函数的梯度图。
当我们根据 sigmoid 激活层的权重对其输出进行微分时,我们看到 sigmoid 函数的梯度是表达式中的一个因素。该梯度的值范围从 0 到 1。
$ $ \ frac { \ partial(\sigma(\omega^tx+b))} { \ partial \ omega } = \ frac { \ partial(\sigma(\omega^tx+b))} { \ partial(\omega^tx+b)} * \ frac { \ partial(\omega^tx+b)} { \ partial \ omega } $ $
第二项是 sigmoid 导数,其范围为 0 到 1。
回到我们的例子,让我们算出神经元 A 的梯度规则。应用链式法则,我们看到神经元 A 的梯度如下
$ $ \ frac { \ partial L } { \ partial a } = \ frac { \ partial L } * \ frac { \ partial d } * \ frac { \ partial c } * \ frac { \ partial b } * \ frac { \ partial b } { \ partial a }
$ $
要认识到,上述表达式中的每一项都可以进一步分解成梯度的乘积,其中之一是 sigmoid 函数的梯度。举个例子,
$ $ \ frac { \ partial d } { \ partial c } = \ frac { \ partial d } { \ partial(\sigma(\omega_d^tc+b _ d))} * \ frac { \ partial(\sigma(\omega_d^tc+b _ d))} { \ partial(\omega_d^tc+b _ d)} * \ frac { \ partial(\omega_d^tc+b _ d)} { \ partial c } $ $
现在,让我们假设 A 前面不是 3 个神经元,而是有大约 50 个神经元。这在网络可能很容易有 50 层的实际场景中是完全可能的。
那么 A 的梯度表达式中有 50 个 sigmoid 梯度的乘积,并且由于每个这样的项具有 0 和 1 之间的值,A 的梯度值可能被驱动为零。
为了了解这是如何发生的,让我们做一个简单的实验。让我们从 0 到 1 随机抽取 50 个数,然后一起相乘。
import random
from functools import reduce
li = [random.uniform(0,1) for x in range(50)
print(reduce(lambda x,y: x*y, li))
你自己去试试。尽管多次尝试,我永远也不可能得到比\(10^{-18}\).更多的订单价值如果这个值作为因子出现在神经元 A 的梯度表达式中,那么它的梯度几乎可以忽略不计。这意味着,在更深的架构中,更深的神经元不会学习,或者即使学习,其速度也比更浅更高层的学习慢得多。
这种现象被称为消失梯度问题,其中更深层神经元的梯度变为零,或者说消失。问题是网络的深层学习非常慢,或者在最坏的情况下,深层根本不学习。
饱和神经元
饱和的神经元会使梯度消失的问题恶化。假设,馈送给具有 s 形激活神经元的预激活\(\omega^Tx + b 要么非常高,要么非常低。在非常高或非常低的值处,sigmoid 的梯度几乎为 0。任何梯度更新几乎不会产生权重\)\omega\(和偏差\)b$的变化,并且神经元将需要许多步骤来修改权重,使得预激活落在梯度具有实质值的区域中。
依靠救援
在一般的深层网络环境中,抑制梯度消失问题的第一次尝试是引入 ReLU 激活功能(LSTMs 也是为了解决这个问题,但它们仅限于循环模型)。
对于\(x \gt 0\)而言,ReLU 的梯度为 1,对于\(x \lt 0\)而言,其梯度为 0。它有多重好处。ReLU 函数的梯度乘积最终不会收敛到 0,因为值不是 0 就是 1。如果值为 1,渐变将按原样反向传播。如果为 0,则没有梯度从该点向后传播。
单边饱和
我们在 sigmoid 函数中有双侧饱和。也就是说,激活函数将在正方向和负方向都饱和。相反,ReLUs 提供单边饱和。
虽然称 ReLU 的零部分为饱和并不准确。然而,它在某种程度上服务于相同的目的,即当函数的输入变得越来越负时,函数值根本不变(与适当饱和度中非常非常小的变化相反)。你可能会问,单边饱和会带来什么好处?
我们倾向于认为深层网络中的神经元像开关一样,专门检测某些特征,这通常被称为概念。虽然较高层中的神经元可能最终专门检测高级概念,如眼睛、轮胎等,但较低层中的神经元最终专门检测低级概念,如曲线、边缘等。
我们希望当输入中出现这样的概念时,神经元会被激发,并且它的大小是输入中概念范围的度量。例如,如果一个神经元检测到一个边缘,它的大小可能代表边缘的锐度。
神经元创建的激活图学习不同的概念
然而,对于一个神经元来说,拥有一个无限的负值是没有意义的。虽然解释概念存在时的信心很直观,但对概念的缺失进行编码却很奇怪。
考虑与检测边缘的神经元相关的例子,与激活 5 相比,激活 10 可能意味着更尖锐的边缘。但是与-5 相比,值-10 有什么意义呢,其中低于 0 的值表示根本没有边缘。因此,对于对应于没有概念的情况(一些其他概念可能存在或者根本没有)的所有输入,具有统一的零值将是方便的。ReLUs 用他们的片面饱和实现了这一点。
信息解纠缠和对噪声的鲁棒性
单侧饱和使神经元对噪声具有鲁棒性。为什么?让我们假设我们有一个神经元值是无界的,即在任一方向上都不饱和。包含不同程度概念的输入在神经元的正输出中产生差异。这很好,因为我们希望幅度作为信号强度的指标。
然而,关于背景噪声或神经元不擅长的概念(包含被馈送给擅长检测线的神经元的弧的区域)的信号中的变化,在神经元的负输出中产生变化。这种类型的差异会向其他神经元贡献无关的无用信息,这些神经元与我们正在讨论的特定神经元具有依赖性。这也会导致相关的单元。例如,检测直线的神经元可能与检测弧线的神经元呈负相关。
现在,让我们考虑在负区域饱和的神经元的相同场景,(对于预激活< 0). Here, ,由于噪声引起的方差,其早先显示为负幅度,被激活函数的饱和元素压制。这可以防止噪声产生外来信号。
稀少
使用 ReLu 激活函数也有计算上的好处。基于 ReLU 的网络训练更快,因为在计算 ReLU 激活的梯度时没有花费大量的计算。这与 Sigmoid 相反,在 Sigmoid 中,为了计算梯度,需要计算指数。
由于 ReLU 将负预激活箝位到零,它们隐含地在网络中引入了稀疏性,这可以被利用来获得计算上的好处。
垂死的 ReLU 问题
ReLUs 也有自己的缺点。虽然稀疏是一种计算优势,但过多的稀疏实际上会妨碍学习。通常,预激活还包含一个偏置项。如果该偏置项变得太负,使得\(\omega^Tx + b \lt 0\),则在反向通过期间 ReLU 激活的梯度为 0。* 因此,导致负预激活的权重和偏差无法更新。*
如果学习到的权重和偏差使得整个输入域的预激活为负,则神经元永远不会学习,从而导致类似 s 形的饱和。这就是所谓的死亡再路问题。
零中心激活
因为 ReLUs 只输出非负激活而不管它的输入,它们总是产生正激活。这可能是一个缺点。让我们了解如何。
对于基于 ReLU 的神经网络,对于损失函数\(L\)来说,属于具有激活\(z_n = ReLU(\omega_n^Tx_n + b_n)\)的层\(l_n\)的任何权重集的梯度
$ $ \ frac { \ partial l } { \ partial \ omega _ { n } } = \ frac { \ partial l } { \ partial(relu(\omega_ntx_n+b_n))}*i(relu(\omega_ntx_n+b _ n))* x _ n $ $
这里,\(i(relu(\omega_n^tx_n+b _ n)\ gt 0)\)是一个指示函数,当参数为真时,条件为 1,否则为 0。因为,ReLU 只输出一个非负值(\(x_{n}\))。由于\(x_n\)的每个元素不是正就是零,所以\(\omega_n\)中每个权重的梯度更新与$ \ frac { \ partial l } { \ partial(relu(\omega_n^tx_n+b _ n))} $
这有什么问题吗?问题是,由于所有神经元的梯度更新的符号是相同的,所以层\(l_n\)的所有权重在一次更新期间可以增加或减少。然而,理想的梯度权重更新可能是一些权重增加而其他权重减少。这在 ReLU 中是不可能的。
假设一些权重需要根据理想的权重更新而减少。然而,如果梯度更新是正的,则这些权重在当前迭代中变得太正。在下一次迭代中,梯度可以是负的,也可以是大的,以补偿这些增加的权重,这最终可能超过需要很少负变化或正变化的权重。
这可能会导致在寻找最小值时出现曲折模式,从而减慢训练速度。
ReLUs 泄漏的问题
泄漏 relu 和参数化 relu
为了解决死亡 ReLU 的问题,提出了泄漏 ReLU。一个泄漏 ReLU 与正常 ReLU 相同,除了不是对$ x \lt 0 $为 0,它在那个区域有一个小的负斜率。
实际上,负斜率\(\alpha\)被选择为 0.01 量级的值。
泄漏 ReLU 的好处是反向传递能够改变权重,这产生了负的预激活,因为输入的激活函数的梯度是$\αe^x$.例如,在 YOLO 目标检测算法中使用了泄漏 ReLU。
因为负的预激活产生负值而不是 0,所以我们不存在仅在与 ReLU 相关联的一个方向上更新权重的问题。
\(\alpha\)的值是人们已经试验了很多次的东西。存在一种被称为随机化泄漏 ReLU 的方法,其中负斜率是从具有平均值 0 和标准偏差 1 的均匀分布中随机选择的。
$ $ f(x)= \ left \ { \ begin { array } { ll } x :::::::when ::x :\ gt 0 \ \ \ alpha x ::::::when :x \ leq 0 \ \ \ end { array } \ right。\\ \alpha \sim U(0,1) $$
随机化 ReLU 的原始论文声称,它比 leaky ReLU 产生更好更快的结果,并通过经验方法提出,如果我们仅限于选择\(\alpha\),就像 Leaky ReLU 一样,选择\(\frac{1}{5.5}\)会比 0.01 更好
随机化的泄漏 ReLU 工作的原因是由于负斜率的随机选择,因此负预激活的梯度的随机性,这在优化算法中引入了随机性。这种随机性或噪声帮助我们避开局部最小值和鞍点。如果你需要更多的观点,我鼓励你看看这个系列的第一部分,在那里我们已经深入讨论了这个话题。
利用每个神经元不同的负斜率的优势,人们进一步采取了这种方法,不是随机采样负斜率$\ alpha $,而是将其转化为超参数,由网络在训练期间学习。这种激活被称为参数化 ReLU 。
修正饱和度
虽然神经元饱和似乎是神经网络中的一件非常糟糕的事情,但像 ReLU 这样的单边饱和并不一定那么糟糕。虽然上面讨论的 ReLU 的变体有助于零中心激活,但是它们没有上面讨论的单侧饱和的好处。
指数线性单位和偏移
根据上面的讨论,似乎理想的激活函数具有两个期望的性质:
- 产生一个以零为中心的分布,这可以使训练更快。
- 具有单侧饱和,这导致更好的收敛。
虽然 Leaky ReLUs 和 PReLU 解决了第一个条件,但它们无法满足第二个条件。另一方面,香草 ReLU 满足第二个条件,但不是第一个条件。
满足这两个条件的激活函数是指数线性单位(ELU)。
$ $ f(x)= \ left \ { \ begin { array } { ll } x ::::::::::::::::::when ::x :\ gt 0 \ \ \ alpha(e^{x}-1)::::::when :x \ leq 0 \ \ \ end { array } \ right。$$
对于$ x \gt 0 \(函数的梯度是 1,而对于\) x \lt 0 \(函数的梯度是\) \alpha * e^x \(。该函数对负值饱和,达到\) - \alpha $的值。\α是一个超参数,通常选择为 1。
因为,函数确实有一个负值区域,我们不再有非零中心激活导致不稳定训练的问题。
如何选择激活函数
-
试试 ReLU 激活的运气。尽管我们已经概述了 ReLU 的问题,但是许多人已经用 ReLU 取得了很好的结果。根据奥卡姆剃刀原理,最好先尝试简单的东西。在所有其他可行的竞争者中,ReLUs 拥有最便宜的计算预算,而且如果你的项目需要从头开始编码,它也非常容易实现。
-
如果 ReLU 没有输出有希望的结果,我的下一个选择是一个泄漏的 ReLU 或 eLU。我发现能够产生零中心激活的激活比不能产生零中心激活的要好得多。ELU 可能是一个非常容易的选择,但是基于 ELU 的网络训练很慢,推理时间也很慢,因为我们必须计算许多指数来计算负预激活的激活。* *如果计算资源对你来说不是问题,或者如果网络不是巨大的,去 ELU,否则,你可能想坚持使用 Leaky ReLUs。**.LReLU 和 eLU 都增加了另一个需要调整的超参数。
-
如果您有大量的计算预算和时间,您可以将上述激活的性能与 PReLU 和随机化 ReLU 的性能进行对比。如果您的函数显示过度拟合,随机化的 ReLU 会很有用。使用参数 ReLU,您可以将一大堆需要学习的参数添加到您的优化问题中。因此,参数化的 ReLU 应该只在你有大量训练数据的情况下使用。
结论
在这篇文章中,我们讨论了为使神经网络正确学习,需要一个持续的、行为良好的数据分布。虽然激活函数隐式地试图规范化这些分布,但一种称为批处理规范化的技术显式地做到了这一点,可以说这是近年来深度学习领域的重大突破之一。然而,这将包括在系列的下一部分,直到那时,你可以尝试你的网络尝试不同的激活!体验愉快!
进一步阅读
- 爆炸梯度问题
- 深入了解 ReLUs 的优势
- reddit 上关于 ReLUs 是否仍在使用的讨论,如果是,为什么?
- ELU paper
通用扩散:第一个统一的多流多模态扩散框架
在过去的几个月里,扩散模型的力量已经完全取代了以前的图像合成框架,如 DALL-E 2、DALL-E Mini 和 GLIDE,成为个人和商业图像生成的首选方法。特别是,稳定扩散及其许多衍生项目,如 Dreambooth 和 Textual Inversion,以及 Imagen 代表了这些模型的两个最高性能的例子,但只有稳定扩散已向公众发布。感谢 Stability AI 、 CompVis 和 RunwayML 的研究人员,这个免费模型在寻求参与这一超越基于机器学习的图像合成限制的最新步骤的研究人员和临时用户中广受欢迎。
框架阵容中的新成员是多功能扩散,这是第一个用于图像合成任务的统一多流多模式扩散框架。这使得通用扩散框架能够处理各种各样的任务,包括文本到图像合成、图像到文本合成、现有输入的图像变化、文本变化、语义风格解缠结、图像-文本双重引导生成以及潜在图像到文本到图像编辑。
遵循本教程的统一框架的分解,更彻底的分析,上面列出的每一个能力,并逐步说明从梯度笔记本运行多功能扩散。
通用扩散统一框架
在我们开始使用代码的通用扩散之前,让我们浏览一下架构和框架的功能,感受一下这个过程是如何工作的。
体系结构
Overview of Versatile Diffusion architecture [Source]
让我们从浏览模型套件的架构开始。我们从输入层开始,它能够接受图像和文本输入。输入的类型,以及所选择的任务类型,决定了接下来会发生什么。这就是多流模型的特征:使用不同类型的输入和上下文数据。该模型可以采用的四种主要流程是文本到图像(绿色)、图像到文本(蓝色)、图像变化(红色)和文本变化(黄色)。
在我们继续之前,这里有一个额外的图形解释提出的多流多模态扩散框架与 VAEs,扩散器和上下文编码器。实线表示处理单个任务(即,文本到图像)的模型的典型流程,而其他虚线表示其他可能的流程以及其他可能的任务。这个流程的多功能性允许扩展每个流程的应用程序,从而从这个灵活的框架中产生潜在的新任务。[ 来源
在输入层,图像由 AutoKL 编码器编码,文本输入由 Optimus Bert 处理。这些都是 VAE 编码器,准确地编码各自的输入类型。从那里,它们被传递到第一个数据层。这里,我们有两个块:ResBlock 和 FCResBlock。根据所遵循的流程,可以将文本或图像传递给任何一个块。
首先是通常被称为 ResBlock 的,用于图像数据。在 ResBlock 中,我们将输入表征为 x,输出表征为 H(x)。ResBlock 使用“跳过连接”,跳过神经网络中的一些层,并将一层的输出作为下一层的输入。利用这一点,我们可以看到,H(x)的输出等于 F(x) + x。该块因此具有调整后的学习目标。它不再试图学习完整的输出,而是试图最小化目标值 H(x)和 x 之间的差异,降低残差(F(x) = H(x) -x)。[ 来源
在实践中,这允许信息从初始层流到最后层,并且使得能够以逐渐减小的空间维度和增加的通道数量对图像信息进行进一步的鲁棒编码,而没有显著的进一步退化。
**
Source**
对于文本数据流,通用扩散的作者创建了一种新颖的全连接残差块(FCResBlock)架构。这能够将 768 维文本潜在向量扩展成 320×4 的隐藏特征,并且遵循与图像数据的 ResBlock 相似的通道增加范例。FCResBlock 包含两组完全连接的层(FC)、组规范化(GN)和 sigmoid 线性单元(路斯)。x
是输入文本潜在代码,t
是输入时间嵌入,hi
是中间特征。来源
编码然后被传递到上下文层。在那里,图像或文本交叉注意被应用于剪辑图像或文本编码器。它使用内容嵌入通过投影图层、点积和 sigmoids 来操纵数据的特征。对于文本和图像编码,归一化和投影嵌入显著地最小化了剪辑对比损失。
从那以后,对于文本到图像和图像到文本的流,这个过程就颠倒了。文本编码通过数据层中的 ResBlock 传递,图像编码通过 FCResBlock 传递。然后,它们被传递到另一个 CrossAttention 编码器层,再次交替多模态流的“两侧”。
该论文作者陈述的主要目标之一是“为了实现更好的性能,[使用]上下文编码器[来]联合最小化所有支持的内容类型上的跨模态统计距离(例如 KL-divergence)来源。通过在数据层中的不同类型的 ResBlocks 之间交替数据,他们能够使用 CLIP 更准确地将图像和文本连接到其编码。他们在实验中发现,关闭上下文类型之间的嵌入空间有助于模型更快地收敛和更好地执行。
从这个中间层,该过程然后被反转以将图像或文本编码解码成它们的结果图像或文本。
能力
**
The 6 potential flows identified by the original paper [source]**
通用扩散模型因其广泛多样的能力而得名。在代码演示中展示它们之前,让我们先讨论一下它们。
a)文本到图像
对于任何与图像生成相关的模型来说,这是当前最常见和最有用的流程:使用一个文本字符串来影响图像生成过程的输出。通用扩散使用与标准稳定扩散相似的过程。
使用“文本到图像”功能,根据文本提示生成精确的合成图像。
b)图像变化
**
[Source]**
虽然稳定扩散没有固有的图像变化任务,但作者使用稳定扩散 v1-4 检查点重新创建了他们的图像变化脚本的效果。VD-basic 是一个单流程的图像变化模型。其次,VD-DC 是一个支持文本到图像合成和图像变化的双流模型。其 UNet 扩散器包含一个数据流和两个上下文流。最后,VD-official 是一个四流模型,包括另外两个任务,即图像到文本和文本变化,其 UNet 扩散器有两个数据流,分别用于数据和上下文。我们可以从结果中看出,VD 模型不仅对于来自原始输入的相关特征具有更高的表观视敏度和多样性,对于主要对象和背景特征也是如此。
使用图像变化来创建同一对象的不同角度,给现有图像添加微小的变化,并创建大量的潜在选项来选择,以准确地表示原始图像的特征。
c)图像到文本
**
[Source]**
以上是 VD-官方流程与流行的 BLIP 框架的定性性能比较。它们都旨在以清晰的文本格式捕获输入图像的特征。很难比较这两个结果,但可以认为 VD 似乎为每个样本输入生成了更大量的描述性标记,这提供了更高程度的人类可读准确性。
这对于尝试确定哪个提示将生成您正在寻找的精确图像非常有用。一定要留好种子,以备推断!
d)解开缠绕
**
[Source]**
VD 的一个有趣的应用是,它可以在没有进一步监督的情况下从语义上增强或减少图像样式。在实践中,这允许研究人员探索潜在空间的一个新领域,其中风格和语义之间的解开可以发生在具有任意风格的任意内容的图像上。[ 来源
使用此功能在合成图像之间交换语义和样式,或者提取它们以应用于其他地方。
e)双重引导生成
通常,不可能同时以文本和图像数据为条件。论文作者认为这是一个模型级的混合问题。简单地将这些效果结合起来就可以创建一个可用的基线,但是在没有显著改进的情况下,成本会翻倍。
**
Comparison of SD and VD on a dual-guided generation task. S**
然而,VD 可以在更深的层次上处理跨模态条件:层层次或注意力层次。这允许模型在 VD 的可交互结构中混合属性,使得数据层可以适应所有的上下文层流。他们确定,在 VD 上使用注意力水平混合可以保持正确的对象结构,并协调提示和图像上下文,从而产生同时使用两种输入类型的高性能合成。
f)潜在的、可编辑的图像到文本到图像
**
[Source]**
这是使用图像生成提示的过程,然后修改该提示,并用于合成新图像。这允许用户在图像到图像编辑环境中对最终输出给予极高程度的控制,并进一步允许受控的、有针对性的特征编辑。
现在我们更彻底地理解了通用扩散,让我们跳到渐变笔记本代码演示,看看这个工具是如何工作的。
代码演示
**## 设置
!apt-get update && apt-get install git-lfs -y
!git-lfs clone https://huggingface.co/spaces/shi-labs/Versatile-Diffusion
!mv Versatile-Diffusion/pretrained ./
!rm -r Versatile-Diffusion
!pip install -r requirement.txt
!pip install -e git+https://github.com/CompVis/taming-transformers.git@master#egg=taming-transformers
!cp -r src/taming-transformers/taming ./
在开始之前,我们需要首先确保所有相关的包和模型文件都安装在实例上。首先,我们用 git-lfs 获得模型文件。
您可能需要将前两行拆分到它们自己的单元格中,因为 apt 在完成下载后似乎会挂起。这可以通过等待它说 100%,并重新启动内核来解决。
要知道,通用扩散的模型文件总共占用大约 15 GB 的存储空间,所以如果你是专业或免费帐户用户,就要小心这一点,以及存储空间过剩的可能性。
获得所有文件后,我们安装所需的包,并将 taming 的副本移动到我们的工作区目录中。
实例化vd_inference
类
现在我们的环境已经设置好了,我们可以开始了。我们首先需要导入相关的包,并声明相关的变量。
完成后,vd_inference
类就是我们统一网络的“容器”。调用vd_inference
将所有相关文件加载到内核中,为不同的流使用它们进行推理设置阶段。它还具有助手功能和推理功能。助手功能只是做一些小任务,比如优化图像输入。另一方面,推理功能用于执行每个不同的流程。
第一个是inference()
,用于文本到图像和图像到文本的合成。它检查正在使用的输入类型,然后执行合成过程。然后,上面描述的每个功能都有函数,直接集成到类中。
正如我们所见,vd_inference
类将整个项目保存在一个对象中,并包含大量相关的推理功能。通读下面的代码块,特别注意每个推理函数,以便更详细地了解这个过程是如何工作的。
import os
import PIL
from PIL import Image
from pathlib import Path
import numpy as np
import numpy.random as npr
from contextlib import nullcontext
import torch
import torchvision.transforms as tvtrans
from lib.cfg_helper import model_cfg_bank
from lib.model_zoo import get_model
from lib.model_zoo.ddim_vd import DDIMSampler_VD, DDIMSampler_VD_DualContext
from lib.model_zoo.ddim_dualcontext import DDIMSampler_DualContext
from lib.experiments.sd_default import color_adjust
n_sample_image = 2
n_sample_text = 4
cache_examples = True
class vd_inference(object):
def __init__(self, type='official'):
if type in ['dc', '2-flow']:
cfgm_name = 'vd_dc_noema'
sampler = DDIMSampler_DualContext
pth = 'pretrained/vd-dc.pth'
elif type in ['official', '4-flow']:
cfgm_name = 'vd_noema'
sampler = DDIMSampler_VD
pth = 'pretrained/vd-official.pth'
cfgm = model_cfg_bank()(cfgm_name)
net = get_model()(cfgm)
sd = torch.load(pth, map_location='cpu')
net.load_state_dict(sd, strict=False)
self.use_cuda = torch.cuda.is_available()
if self.use_cuda:
net.to('cuda')
self.model_name = cfgm_name
self.net = net
self.sampler = sampler(net)
def regularize_image(self, x):
BICUBIC = PIL.Image.Resampling.BICUBIC
if isinstance(x, str):
x = Image.open(x).resize([512, 512], resample=BICUBIC)
x = tvtrans.ToTensor()(x)
elif isinstance(x, PIL.Image.Image):
x = x.resize([512, 512], resample=BICUBIC)
x = tvtrans.ToTensor()(x)
elif isinstance(x, np.ndarray):
x = PIL.Image.fromarray(x).resize([512, 512], resample=BICUBIC)
x = tvtrans.ToTensor()(x)
elif isinstance(x, torch.Tensor):
pass
else:
assert False, 'Unknown image type'
assert (x.shape[1]==512) & (x.shape[2]==512), \
'Wrong image size'
if self.use_cuda:
x = x.to('cuda')
return x
def decode(self, z, xtype, ctype, color_adj='None', color_adj_to=None):
net = self.net
if xtype == 'image':
x = net.autokl_decode(z)
color_adj_flag = (color_adj!='None') and (color_adj is not None)
color_adj_simple = color_adj=='Simple'
color_adj_keep_ratio = 0.5
if color_adj_flag and (ctype=='vision'):
x_adj = []
for xi in x:
color_adj_f = color_adjust(ref_from=(xi+1)/2, ref_to=color_adj_to)
xi_adj = color_adj_f((xi+1)/2, keep=color_adj_keep_ratio, simple=color_adj_simple)
x_adj.append(xi_adj)
x = x_adj
else:
x = torch.clamp((x+1.0)/2.0, min=0.0, max=1.0)
x = [tvtrans.ToPILImage()(xi) for xi in x]
return x
elif xtype == 'text':
prompt_temperature = 1.0
prompt_merge_same_adj_word = True
x = net.optimus_decode(z, temperature=prompt_temperature)
if prompt_merge_same_adj_word:
xnew = []
for xi in x:
xi_split = xi.split()
xinew = []
for idxi, wi in enumerate(xi_split):
if idxi!=0 and wi==xi_split[idxi-1]:
continue
xinew.append(wi)
xnew.append(' '.join(xinew))
x = xnew
return x
def inference(self, xtype, cin, steps, ctype, h = 512, w = 512, scale=7.5, n_samples=None, color_adj=None):
net = self.net
sampler = self.sampler
ddim_steps = steps
ddim_eta = 0.0
if xtype == 'image':
n_samples = n_sample_image if n_samples is None else n_samples
elif xtype == 'text':
n_samples = n_sample_text if n_samples is None else n_samples
if ctype in ['prompt', 'text']:
c = net.clip_encode_text(n_samples * [cin])
u = None
if scale != 1.0:
u = net.clip_encode_text(n_samples * [""])
elif ctype in ['vision', 'image']:
cin = self.regularize_image(cin)
ctemp = cin*2 - 1
ctemp = ctemp[None].repeat(n_samples, 1, 1, 1)
c = net.clip_encode_vision(ctemp)
u = None
if scale != 1.0:
dummy = torch.zeros_like(ctemp)
u = net.clip_encode_vision(dummy)
if xtype == 'image':
shape = [n_samples, 4, h//8, w//8]
z, _ = sampler.sample(
steps=ddim_steps,
shape=shape,
conditioning=c,
unconditional_guidance_scale=scale,
unconditional_conditioning=u,
xtype=xtype, ctype=ctype,
eta=ddim_eta,
verbose=False,)
x = self.decode(z, xtype, ctype, color_adj=color_adj, color_adj_to=cin)
return x
elif xtype == 'text':
n = 768
shape = [n_samples, n]
z, _ = sampler.sample(
steps=ddim_steps,
shape=shape,
conditioning=c,
unconditional_guidance_scale=scale,
unconditional_conditioning=u,
xtype=xtype, ctype=ctype,
eta=ddim_eta,
verbose=False,)
x = self.decode(z, xtype, ctype)
return x
def application_disensemble(self, cin, h = 512, w = 512, n_samples=None, level=0, color_adj=None,steps = None, scale = None):
net = self.net
scale = scale
sampler = self.sampler
ddim_steps = steps
ddim_eta = 0.0
n_samples = n_sample_image if n_samples is None else n_samples
cin = self.regularize_image(cin)
ctemp = cin*2 - 1
ctemp = ctemp[None].repeat(n_samples, 1, 1, 1)
c = net.clip_encode_vision(ctemp)
u = None
if scale != 1.0:
dummy = torch.zeros_like(ctemp)
u = net.clip_encode_vision(dummy)
if level == 0:
pass
else:
c_glb = c[:, 0:1]
c_loc = c[:, 1: ]
u_glb = u[:, 0:1]
u_loc = u[:, 1: ]
if level == -1:
c_loc = self.remove_low_rank(c_loc, demean=True, q=50, q_remove=1)
u_loc = self.remove_low_rank(u_loc, demean=True, q=50, q_remove=1)
if level == -2:
c_loc = self.remove_low_rank(c_loc, demean=True, q=50, q_remove=2)
u_loc = self.remove_low_rank(u_loc, demean=True, q=50, q_remove=2)
if level == 1:
c_loc = self.find_low_rank(c_loc, demean=True, q=10)
u_loc = self.find_low_rank(u_loc, demean=True, q=10)
if level == 2:
c_loc = self.find_low_rank(c_loc, demean=True, q=2)
u_loc = self.find_low_rank(u_loc, demean=True, q=2)
c = torch.cat([c_glb, c_loc], dim=1)
u = torch.cat([u_glb, u_loc], dim=1)
shape = [n_samples, 4, h//8, w//8]
z, _ = sampler.sample(
steps=ddim_steps,
shape=shape,
conditioning=c,
unconditional_guidance_scale=scale,
unconditional_conditioning=u,
xtype='image', ctype='vision',
eta=ddim_eta,
verbose=False,)
x = self.decode(z, 'image', 'vision', color_adj=color_adj, color_adj_to=cin)
return x
def find_low_rank(self, x, demean=True, q=20, niter=10):
if demean:
x_mean = x.mean(-1, keepdim=True)
x_input = x - x_mean
else:
x_input = x
u, s, v = torch.pca_lowrank(x_input, q=q, center=False, niter=niter)
ss = torch.stack([torch.diag(si) for si in s])
x_lowrank = torch.bmm(torch.bmm(u, ss), torch.permute(v, [0, 2, 1]))
if demean:
x_lowrank += x_mean
return x_lowrank
def remove_low_rank(self, x, demean=True, q=20, niter=10, q_remove=10):
if demean:
x_mean = x.mean(-1, keepdim=True)
x_input = x - x_mean
else:
x_input = x
u, s, v = torch.pca_lowrank(x_input, q=q, center=False, niter=niter)
s[:, 0:q_remove] = 0
ss = torch.stack([torch.diag(si) for si in s])
x_lowrank = torch.bmm(torch.bmm(u, ss), torch.permute(v, [0, 2, 1]))
if demean:
x_lowrank += x_mean
return x_lowrank
def application_dualguided(self, cim, ctx, h = 512, w = 512, n_samples=None, mixing=0.5, color_adj=None, steps = None, scale = None):
net = self.net
scale = scale
sampler = DDIMSampler_VD_DualContext(net)
ddim_steps = steps
ddim_eta = 0.0
n_samples = n_sample_image if n_samples is None else n_samples
ctemp0 = self.regularize_image(cim)
ctemp1 = ctemp0*2 - 1
ctemp1 = ctemp1[None].repeat(n_samples, 1, 1, 1)
cim = net.clip_encode_vision(ctemp1)
uim = None
if scale != 1.0:
dummy = torch.zeros_like(ctemp1)
uim = net.clip_encode_vision(dummy)
ctx = net.clip_encode_text(n_samples * [ctx])
utx = None
if scale != 1.0:
utx = net.clip_encode_text(n_samples * [""])
shape = [n_samples, 4, h//8, w//8]
z, _ = sampler.sample_dc(
steps=ddim_steps,
shape=shape,
first_conditioning=[uim, cim],
second_conditioning=[utx, ctx],
unconditional_guidance_scale=scale,
xtype='image',
first_ctype='vision',
second_ctype='prompt',
eta=ddim_eta,
verbose=False,
mixed_ratio=(1-mixing), )
x = self.decode(z, 'image', 'vision', color_adj=color_adj, color_adj_to=ctemp0)
return x
def application_i2t2i(self, cim, ctx_n, ctx_p, steps, scale, h = 512, w = 512, n_samples=None, color_adj=None,):
net = self.net
scale = scale
sampler = DDIMSampler_VD_DualContext(net)
ddim_steps = steps
ddim_eta = 0.0
prompt_temperature = 1.0
n_samples = n_sample_image if n_samples is None else n_samples
ctemp0 = self.regularize_image(cim)
ctemp1 = ctemp0*2 - 1
ctemp1 = ctemp1[None].repeat(n_samples, 1, 1, 1)
cim = net.clip_encode_vision(ctemp1)
uim = None
if scale != 1.0:
dummy = torch.zeros_like(ctemp1)
uim = net.clip_encode_vision(dummy)
n = 768
shape = [n_samples, n]
zt, _ = sampler.sample(
steps=ddim_steps,
shape=shape,
conditioning=cim,
unconditional_guidance_scale=scale,
unconditional_conditioning=uim,
xtype='text', ctype='vision',
eta=ddim_eta,
verbose=False,)
ztn = net.optimus_encode([ctx_n])
ztp = net.optimus_encode([ctx_p])
ztn_norm = ztn / ztn.norm(dim=1)
zt_proj_mag = torch.matmul(zt, ztn_norm[0])
zt_perp = zt - zt_proj_mag[:, None] * ztn_norm
zt_newd = zt_perp + ztp
ctx_new = net.optimus_decode(zt_newd, temperature=prompt_temperature)
ctx_new = net.clip_encode_text(ctx_new)
ctx_p = net.clip_encode_text([ctx_p])
ctx_new = torch.cat([ctx_new, ctx_p.repeat(n_samples, 1, 1)], dim=1)
utx_new = net.clip_encode_text(n_samples * [""])
utx_new = torch.cat([utx_new, utx_new], dim=1)
cim_loc = cim[:, 1: ]
cim_loc_new = self.find_low_rank(cim_loc, demean=True, q=10)
cim_new = cim_loc_new
uim_new = uim[:, 1:]
shape = [n_samples, 4, h//8, w//8]
z, _ = sampler.sample_dc(
steps=ddim_steps,
shape=shape,
first_conditioning=[uim_new, cim_new],
second_conditioning=[utx_new, ctx_new],
unconditional_guidance_scale=scale,
xtype='image',
first_ctype='vision',
second_ctype='prompt',
eta=ddim_eta,
verbose=False,
mixed_ratio=0.33, )
x = self.decode(z, 'image', 'vision', color_adj=color_adj, color_adj_to=ctemp0)
return x
vd_inference = vd_inference('official')
main
功能
既然我们已经实例化了我们的模型代码,并将相关的模型文件加载到笔记本中,我们就可以开始制作我们的输入来放入到main
函数中。调用main
函数是为了使用我们在这篇博客的功能部分描述的任何不同的流程来启动推理。这是由mode
参数决定的,并将导致模型当前可用的 7 个不同可选流程中的任何一个。让我们看看下面的单元格。
def main(mode,
image=None,
prompt=None,
nprompt=None,
pprompt=None,
color_adj=None,
disentanglement_level=None,
dual_guided_mixing=None,
seed=0,
steps=50,
n_samples = 1,
scale = 7.5,
h = 512,
w = 512):
if seed<0:
seed = 0
np.random.seed(seed)
torch.manual_seed(seed+100)
if mode == 'Text-to-Image':
if (prompt is None) or (prompt == ""):
return None, None
with torch.no_grad():
rv = vd_inference.inference(n_samples = n_samples, steps = steps,
xtype = 'image',
cin = prompt,
ctype = 'prompt',
scale = scale)
return rv, None
elif mode == 'Image-Variation':
if image is None:
return None, None
with torch.no_grad():
rv = vd_inference.inference(n_samples = n_samples, steps = steps,
xtype = 'image',
cin = image,
ctype = 'vision',
color_adj = color_adj,
scale = scale)
return rv, None
elif mode == 'Image-to-Text':
if image is None:
return None, None
with torch.no_grad():
rv = vd_inference.inference(n_samples = n_samples, steps = steps,
xtype = 'text',
cin = image,
ctype = 'vision',
scale = scale)
return None, '\n'.join(rv)
elif mode == 'Text-Variation':
if prompt is None:
return None, None
with torch.no_grad():
rv = vd_inference.inference(n_samples = n_samples, steps = steps,
xtype = 'text',
cin = prompt,
ctype = 'prompt',
scale = scale)
return None, '\n'.join(rv)
elif mode == 'Disentanglement':
if image is None:
return None, None
with torch.no_grad():
rv = vd_inference.application_disensemble(
n_samples = n_samples,
cin = image,
level = disentanglement_level,
color_adj = color_adj,
steps = steps,
scale = scale)
return rv, None
elif mode == 'Dual-Guided':
if (image is None) or (prompt is None) or (prompt==""):
return None, None
with torch.no_grad():
rv = vd_inference.application_dualguided(
n_samples = n_samples,
cim = image,
ctx = prompt,
mixing = dual_guided_mixing,
color_adj = color_adj,
steps = steps,
scale = scale)
return rv, None
elif mode == 'Latent-I2T2I':
if (image is None) or (nprompt is None) or (nprompt=="") \
or (pprompt is None) or (pprompt==""):
return None, None
with torch.no_grad():
rv = vd_inference.application_i2t2i(
n_samples = n_samples,
cim = image,
ctx_n = nprompt,
ctx_p = pprompt,
color_adj = color_adj,
steps = steps,
scale = scale)
return rv, None
else:
assert False, "No such mode!"
调用main
进行推理。
现在我们已经设置好了一切,我们可以调用main
开始对任何流进行推断。除了模式参数之外,在开始合成之前,还有许多其他参数需要考虑。
也就是说,这些是:
- prompt (str)用于指导生成过程。该模型将尝试创建对应于提示的图像或文本输出
- nprompt (str)是负提示,包含我们不希望在合成输出中出现的单词
- pprompt (str)包含我们希望在最终输出中显示的特性的肯定提示(用于 Latent-I2T2I)
- 种子(int)控制合成的随机性,可用于“重新创建”由扩散创建的图像
- steps(int)ddim 采样步骤的数量
- 每批生成的图像数量
- scale (int)模型的指导比例,范围为 0-30。0 相当于没有引导,30 是最大引导
- 高度,以像素为单位
- w (int)宽度,以像素为单位
以下是一个可用于所有基于文本输入的流的示例,但当前设置为文本到图像:
x = main(mode = 'Text-to-Image',
image=None,
prompt= 'a magical fantasy castle overlooking a golden gold colored oceans and giant crashing tsunami waves',
nprompt='low contrast, blurry, out of frame, text, watermark, distortion, black and white, low resolution, low detail, closeup, out of focus, bad framing, tiling, grainy, grayscale, monotone, watermarked',
pprompt='a magical fantasy castle overlooking a golden gold colored oceans and giant crashing tsunami waves',
color_adj=None,
disentanglement_level=None,
dual_guided_mixing=None,
seed=552523,
steps = 50,
n_samples = 1,
scale = 7.5,
h = 512,
w = 512)
就这样了!如果我们按照上面的步骤,我们现在应该能够看到一些滑稽的,幻想的城堡坐落在一个海湾与崩溃的波浪。以下是我们实验的样本输出:
Samples generated using the code cell above
结束语
在这篇博文中,我们详细研究了最新发布的通用扩散统一、多模态和多流图像合成框架的架构和功能。然后,我们演示了如何在渐变笔记本中使用 Python 这个模型来生成梦幻城堡的图像。
我们鼓励您尝试 main 提供的所有不同模式/流程!
在此查看该项目的代码。**
用 PyTorch 从零开始写 VGG
继续我的关于构建经典卷积神经网络的系列,该网络在过去 10-20 年中彻底改变了计算机视觉领域,我们接下来将使用 PyTorch 从头开始构建 VGG,一个非常深入的卷积神经网络。你可以在我的个人资料上看到该系列之前的文章,主要是 LeNet5 和 AlexNet 。
和以前一样,我们将探究 VGG 背后的建筑和直觉,以及当时的结果。然后,我们将研究我们的数据集 CIFAR100,并使用内存高效的代码加载到我们的程序中。然后,我们将使用 PyTorch 从头开始实现 VGG16(数字是指层数,基本上有两个版本 VGG16 和 VGG19 ),然后在我们的数据集上训练它,并在我们的测试集上评估它,以查看它在看不见的数据上的表现
VGG
基于 AlexNet 的工作,VGG 专注于卷积神经网络(CNN)的另一个重要方面,即 T2 深度。由 Simonyan 和 Zisserman 开发。它通常由 16 个卷积层组成,但也可以扩展到 19 层(因此有两个版本,VGG-16 和 VGG-19)。所有卷积层由 3×3 滤波器组成。你可以在官方报纸这里阅读更多关于网络的信息
VGG16 architecture. Source
数据加载
资料组
在建立模型之前,任何机器学习项目中最重要的事情之一就是加载、分析和预处理数据集。在本文中,我们将使用 CIFAR-100 数据集。这个数据集就像 CIFAR-10,除了它有 100 个类,每个类包含 600 个图像。每个类有 500 个训练图像和 100 个测试图像。CIFAR-100 中的 100 个类被分成 20 个超类。每个图像都有一个“精细”标签(它所属的类)和一个“粗糙”标签(它所属的超类)。我们将在这里使用“优良”标签。以下是 CIFAR-100 中的类别列表:
Class List for the CIFAR-100 dataset
导入库
我们将主要与torch
(用于建立模型和训练)torchvision
(用于数据加载/处理,包含数据集和在计算机视觉中处理这些数据集的方法),以及numpy
(用于数学操作)。我们还将定义一个变量device
,以便程序可以使用 GPU(如果可用的话)
import numpy as np
import torch
import torch.nn as nn
from torchvision import datasets
from torchvision import transforms
from torch.utils.data.sampler import SubsetRandomSampler
# Device configuration
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
Importing the libraries
加载数据
torchvision
是一个库,提供对大量计算机视觉数据集和方法的轻松访问,以简单直观的方式预处理这些数据集
- 我们定义了一个函数
data_loader
,它根据参数返回训练/验证数据或者测试数据 - 我们首先用数据集中每个通道(红色、绿色和蓝色)的平均值和标准偏差来定义变量
normalize
。这些可以手动计算,但也可以在线获得。这在transform
变量中使用,我们调整数据的大小,将其转换成张量,然后归一化 - 如果
test
参数为真,我们简单地加载数据集的测试分割,并使用数据加载器返回它(下面解释) - 如果
test
为假(也是默认行为),我们加载数据集的训练分割,并将其随机分割为训练和验证集(0.9:0.1) - 最后,我们利用数据加载器。对于像 CIFAR100 这样的小型数据集,这可能不会影响性能,但对于大型数据集,这确实会影响性能,通常被认为是一种良好的做法。数据加载器允许我们批量迭代数据,数据是在迭代时加载的,而不是一次全部加载到内存中
def data_loader(data_dir,
batch_size,
random_seed=42,
valid_size=0.1,
shuffle=True,
test=False):
normalize = transforms.Normalize(
mean=[0.4914, 0.4822, 0.4465],
std=[0.2023, 0.1994, 0.2010],
)
# define transforms
transform = transforms.Compose([
transforms.Resize((227,227)),
transforms.ToTensor(),
normalize,
])
if test:
dataset = datasets.CIFAR100(
root=data_dir, train=False,
download=True, transform=transform,
)
data_loader = torch.utils.data.DataLoader(
dataset, batch_size=batch_size, shuffle=shuffle
)
return data_loader
# load the dataset
train_dataset = datasets.CIFAR100(
root=data_dir, train=True,
download=True, transform=transform,
)
valid_dataset = datasets.CIFAR10(
root=data_dir, train=True,
download=True, transform=transform,
)
num_train = len(train_dataset)
indices = list(range(num_train))
split = int(np.floor(valid_size * num_train))
if shuffle:
np.random.seed(random_seed)
np.random.shuffle(indices)
train_idx, valid_idx = indices[split:], indices[:split]
train_sampler = SubsetRandomSampler(train_idx)
valid_sampler = SubsetRandomSampler(valid_idx)
train_loader = torch.utils.data.DataLoader(
train_dataset, batch_size=batch_size, sampler=train_sampler)
valid_loader = torch.utils.data.DataLoader(
valid_dataset, batch_size=batch_size, sampler=valid_sampler)
return (train_loader, valid_loader)
# CIFAR100 dataset
train_loader, valid_loader = data_loader(data_dir='./data',
batch_size=64)
test_loader = data_loader(data_dir='./data',
batch_size=64,
test=True)
Loading the dataset
VGG16 从零开始
为了从头开始构建模型,我们需要首先了解模型定义如何在torch
中工作,以及我们将在这里使用的不同类型的层:
- 每个定制模型都需要从
nn.Module
类继承,因为它提供了一些帮助模型训练的基本功能。 - 其次,我们需要做两件主要的事情。首先,定义我们的模型在
__init__
函数中的不同层,以及这些层在forward
函数中的输入上执行的顺序
现在让我们定义我们在这里使用的各种类型的层:
- 这些卷积层接受输入和输出通道的数量作为参数,以及过滤器的内核大小。如果您想应用任何步幅或填充,它也可以接受
nn.BatchNorm2d
:对卷积层的输出进行批量归一化nn.ReLU
:这是应用于网络中各种输出的激活nn.MaxPool2d
:这将最大池应用于给定内核大小的输出nn.Dropout
:用于以给定的概率对输出进行丢弃- 这基本上是一个完全连接的层
- 从技术上来说,这不是一种类型的层,但它有助于将同一步骤中的不同操作结合起来
利用这些知识,我们现在可以使用本文中的架构构建 VGG16 模型:
class VGG16(nn.Module):
def __init__(self, num_classes=10):
super(VGG16, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(64),
nn.ReLU())
self.layer2 = nn.Sequential(
nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer3 = nn.Sequential(
nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(128),
nn.ReLU())
self.layer4 = nn.Sequential(
nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer5 = nn.Sequential(
nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(256),
nn.ReLU())
self.layer6 = nn.Sequential(
nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(256),
nn.ReLU())
self.layer7 = nn.Sequential(
nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer8 = nn.Sequential(
nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(512),
nn.ReLU())
self.layer9 = nn.Sequential(
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(512),
nn.ReLU())
self.layer10 = nn.Sequential(
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer11 = nn.Sequential(
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(512),
nn.ReLU())
self.layer12 = nn.Sequential(
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(512),
nn.ReLU())
self.layer13 = nn.Sequential(
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.fc = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(7*7*512, 4096),
nn.ReLU())
self.fc1 = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU())
self.fc2= nn.Sequential(
nn.Linear(4096, num_classes))
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.layer5(out)
out = self.layer6(out)
out = self.layer7(out)
out = self.layer8(out)
out = self.layer9(out)
out = self.layer10(out)
out = self.layer11(out)
out = self.layer12(out)
out = self.layer13(out)
out = out.reshape(out.size(0), -1)
out = self.fc(out)
out = self.fc1(out)
out = self.fc2(out)
return out
VGG16 from Scratch
超参数
任何机器或深度学习项目的重要部分之一是优化超参数。这里,我们不会用不同的值进行实验,但是我们必须事先定义它们。这些包括定义时期数、批量大小、学习率、损失函数以及优化器
num_classes = 100
num_epochs = 20
batch_size = 16
learning_rate = 0.005
model = VGG16(num_classes).to(device)
# Loss and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay = 0.005, momentum = 0.9)
# Train the model
total_step = len(train_loader)
Setting the hyper-parameters
培养
我们现在准备训练我们的模型。我们将首先看看如何在torch
中训练我们的模型,然后看看代码:
- 对于每个时期,我们遍历我们的
train_loader
中的图像和标签,并将这些图像和标签移动到 GPU(如果有的话)。这是自动发生的 - 我们使用我们的模型来预测标签(
model(images)
),然后使用我们的损失函数(criterion(outputs, labels)
)来计算预测和真实标签之间的损失 - 然后,我们使用该损失反向传播(
loss.backward
)并更新权重(optimizer.step()
)。但是一定要记住在每次更新之前将渐变设置为零。这是使用optimizer.zero_grad()
完成的 - 此外,在每个时期结束时,我们也使用我们的验证集来计算模型的准确性。在这种情况下,我们不需要梯度,所以我们使用
with torch.no_grad()
进行快速评估
现在,我们将所有这些合并到下面的代码中:
total_step = len(train_loader)
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
# Move tensors to the configured device
images = images.to(device)
labels = labels.to(device)
# Forward pass
outputs = model(images)
loss = criterion(outputs, labels)
# Backward and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()
print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
.format(epoch+1, num_epochs, i+1, total_step, loss.item()))
# Validation
with torch.no_grad():
correct = 0
total = 0
for images, labels in valid_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
del images, labels, outputs
print('Accuracy of the network on the {} validation images: {} %'.format(5000, 100 * correct / total))
Training
我们可以看到上面代码的输出如下,它确实显示了模型实际上正在学习,因为损耗随着每个时期而减少:
Training Losses
测试
对于测试,我们使用与验证完全相同的代码,但是使用了test_loader
:
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
del images, labels, outputs
print('Accuracy of the network on the {} test images: {} %'.format(10000, 100 * correct / total))
Testing
使用上述代码并训练 20 个时期的模型,我们能够在测试集上实现 75%的准确度。
结论
现在让我们总结一下我们在本文中所做的工作:
- 我们从理解 VGG-16 模型的架构和不同种类的层开始
- 接下来,我们使用
torchvision
加载并预处理了 CIFAR100 数据集 - 然后,我们使用
PyTorch
从头开始构建我们的 VGG-16 模型,同时理解torch
中可用的不同类型的层 - 最后,我们在 CIFAR100 数据集上训练和测试了我们的模型,该模型在测试数据集上表现良好,准确率为 75%
未来的工作
通过这篇文章,你可以得到一个很好的介绍和实践学习,但是如果你扩展这篇文章,看看你还能做些什么,你会学到更多:
- 你可以尝试使用不同的数据集。一个这样的数据集是 CIFAR10 或 ImageNet 数据集的子集。
- 您可以试验不同的超参数,并查看它们在模型中的最佳组合
- 最后,您可以尝试在数据集中添加或移除图层,以查看它们对模型功能的影响。更好的是,尝试建立这个模型的 VGG-19 版本
体验千兆带宽
原文:https://blog.paperspace.com/virtual-desktops-with-gigabit-bandwidth/
Paperspace 最强大的功能之一非常简单:几乎瞬间下载大文件。
我们是一个分散的团队,员工分布在三个国家,我们花大量时间处理大型文件。例如,我们的设计团队经常处理几千兆字节的 Photoshop 文件。最酷的事情之一是跨越数千英里协作处理这些类型的文件,几乎没有同步延迟。
看看我们的办公室带宽在云端机器旁边的速度测试对比:
Paperspace 包括所有机器类型上的千兆带宽。
视觉变形金刚解释
在这篇论文中,一幅图像相当于 16x16 个字:用于大规模图像识别的变形金刚,视觉变形金刚(ViT)是 SOTA 图像分类的热门话题。专家们认为,这只是变压器架构取代卷积架构完成上游/下游任务的冰山一角。
一幅图像抵得上 16x16 个字:大规模图像识别变形金刚https://t.co/r5a0RuWyZEv 酷。用变压器拆除通信网络的进一步措施。我喜欢 Vision/NLP 的日益融合和更高效/灵活的体系结构。pic.twitter.com/muj3cR6uGA
— Andrej Karpathy (@karpathy) October 3, 2020
我们是音乐制作人,我们是梦想的梦想家——丹·杰弗里斯的艺术与技术
我们很幸运有机会与 Dan Jeffries 坐在一起,他是一位多产的作家、博客作者、教师、顾问和未来学家,此外他还与我们在 Pachyderm 的朋友一起担任首席技术布道者。
如果你对未来主义、机器智能或密码感兴趣,你很有可能读过丹写的东西——无论是他的点击率超过 100 万的系列文章如果你数学不好就学习人工智能还是他广泛分享的关于加密货币的文章,比如为什么每个人都错过了过去 500 年最重要的发明。
但是在丹参与的所有项目中,我们真正想和他谈论的是他最近使用 Transformer 架构对生成性(即由机器智能生成)音乐的探索。
我们不仅会讨论丹的灵感和创作过程,还会讨论他为以创新的方式融合艺术和技术所做的激动人心的工作。我们还将探讨他对未来主义的看法,以及我们可以从他在这个大伞下研究众多不同主题的方法中学到什么。
我们建议你在阅读这篇采访时,把丹的《笑猴》专辑放在背景音乐中,看看这种由电脑生成的《T2》背景音乐如何适合你。
Paperspace: 当我们开始阅读生殖音乐时,我们惊讶地发现,对话似乎是从布赖恩·伊诺在 20 世纪 90 年代中期与 Koan 的合作开始的!有人认为 Eno 是生成音乐的第一个主流先驱,他根据两个标准来定义它:1 .音乐必须不断变化,绝不重复。它必须永远持续下去。Eno 是你的灵感来源吗?你认为你的现代探索满足这些标准吗?现代 ML 技术甚至能保证这些标准吗?四分之一世纪后,像《变形金刚》这样的建筑以何种方式将 Eno 的愿景变为现实?
Jeffries: 我喜欢布赖恩·伊诺的作品,他在我每天写作时听的环境音乐播放列表中经常出现。他在电子音乐方面的开创性工作为他之后成千上万的艺术家创造了灵感。
但是当我开始我的环境音乐机器学习项目时,我没有想到任何特定的艺术家,我在谷歌 Magenta 项目博客上以简短的形式发表了这个项目,并在 Hackernoon 上发表了更多细节,作为我的学习 AI 如果你数学很差系列的第八部分。
我只是从整体上爱上了环境音乐和机器学习,我想看看我是否可以创造更多我最喜欢的环境音乐风格。有很多种风格,从简单的白噪音,到重现热带雨林等自然环境的歌曲,但我喜欢流动、空灵的氛围,这让我有完美的心情来写作或专注。
https://open.spotify.com/embed/playlist/6qaujvXpcysfuyFMtp7Ljn?si=gF5bgd9bR26uEi4-YNCDlQ
paper space:我们喜欢你对环境音乐的冥想——特别是关于创造性流动作为冥想状态的那一点,可以通过正确的听觉输入来增强。
你能告诉我们更多关于你和环境音乐的个人关系吗?可以这么说,你认为流动增强有一天会成为一种环境设置吗?就像一个自动调温器,但动态调整以适应个人和情况?
杰弗里斯: 我一直很喜欢音乐可以适应一个人的科幻想法。它可以实时调整,理解用户的情绪,并基于来自那个人的持续反馈。
一旦越来越多的音乐由机器创作,创造你想一直听的音乐的潜力几乎是无限的。你可能收集了你最喜欢的摩城热门歌曲的完美播放列表,但摩城不再制作任何 60 年代的音乐,这意味着你最终会用完。
但是有了人工智能生成的音乐,你可以拥有无限的摩城!
它能与那些杰出艺术家的才华相媲美吗?大概不会。但是谁知道呢?也许是,也许不是。但即使它不是完全原创的,因为它需要在特定的时间点和特定的经验的杰出人才来制作一种新的声音,这也没关系。有时我们不需要全新的东西,我们需要我们已经喜欢的东西,甚至更多。
至于流动增强,我绝对认为你可以找到整个空间致力于帮助人们更快地进入流动。我们有专用的、共享的工作空间,为什么不共享流动空间呢?我想我们的工作和水疗中心交叉了。我曾经是圣地亚哥一个名为第三空间的奇妙艺术家合作空间的成员,我可以看到这样一个空间,它播放着优美的环境音乐,帮助人们进入那种美妙的状态,在那里时间似乎消失了,你完全专注于你正在做的事情。
Paperspace: 我们不禁认为,音乐固有的品质决定了我们对它的认知反应,这其中一定有层次——因为缺少一个更好或更少的哲学负载的术语。当你开始写代码生成音乐时,你学到了一些关于质量的东西吗?而如果你想去那里——是什么特质让音乐变得美好?你如何训练一个模型来优化这些特征?
Jeffries: 说到机器学习目前的状态,最重要的就是数据。几十年来,我整理了一份清单,列出了我每天写作时都会听的美妙音乐。这是一个完美的播放列表,没有我讨厌的歌曲,也没有感觉、气质和节奏完全不同的歌曲。它们像一个整体一样流动。
关键是要收集一份精心策划的音乐列表,而不是在网上随便找一首老歌,然后希望越多越好。更多不等于更好。如果你选择正确的歌曲来训练机器,少即是多。
至于算法,我把它们留给研究人员和数学奇才。我不能写一个尖端的算法来学习音乐,所以我从现有的东西中提取,并试图找到最佳的方法来优化它,以适合我想做的事情。在这种情况下,数据集让世界变得不同。
至于音乐本身,还有人类感知和回应的更深层次的模式,机器仍然无法理解。我不确定我们甚至能阐明那些更深层次的模式是什么,尽管许多人已经尝试过了。当艺术家们越来越擅长创作音乐时,他们会在原始的水平上挖掘一些东西。当他们创造高度原创的东西时,人们会在无意识的层面上以我们刚刚开始理解的方式做出反应。但即使它是原始的,它也遵循着一种更深层次的、超自然的模式,不管它采取什么样的外部形式,我们似乎都承认它是人类。
我第一次听到马文·盖的《正在发生什么事情》时,我知道我在听摩城音乐,但我也在听一些完全不同于摩城音乐的新东西。盖伊作为一名艺术家已经取得了巨大的飞跃,他自己在多个层面上创作了歌曲,演唱了背景音乐一和二,演奏了康茄舞曲,也演唱了主唱。是马文在马文的上面。他们在《T2》这部讲述汽车城历史的电影《希斯维尔 T3》中出色地戏剧化了这一演变。
在未来,我看到人工智能和艺术家一起创造下一个声音。他们可能会让人工智能创建一个重复片段的二十个延续,然后用它来即兴演奏,人工智能会实时调整。艺术家可能会告诉人工智能在重复片段三的基础上更进一步,创造新的变化,然后从那里接管。这将变成一个令人难以置信的人机合作的共同创造过程。
Paperspace: 你听过其他用 AI 制作的音乐吗?我们认为现在谈论最多的专辑来自前美国偶像选手 T4 塔瑞安·扫森?你有什么看法?
杰弗里斯: 我听了很多外面的东西,但我并不喜欢其中的大部分。古典音乐很好,但它可能只对我有好处,因为我不是古典音乐专家,所以对我来说听起来都有点像。
也就是说,Taryn 正在做我之前提到的事情,与 AI 共同创作。这是一项开创性的工作,必须有人去开拓一条道路,为什么不是她呢!这还处于超级早期阶段,但几乎每个艺术家都会在未来十年将人工智能共同创作融入他们的过程。
老实说,我在做这个项目时听的大部分人工智能音乐并没有让我太感动。我看过的太多文章一般都以失败告终。程序员和数据科学家说,“这些都是我们尝试过的很酷的东西,但是没有成功。”
我想成为第一批成功的人之一,我想我做到了,尽管当然没有 Magenta 的人和其他研究人员我没有机会。
Paperspace: 好吧,现在我有一个大胆的想法,作为你迄今为止作品的后续部分——创作一种音乐图灵测试怎么样?如果一个 ML 模型可以被训练生成一个专业音乐家无法从人类生成的图表中区分的乐谱...那不是很了不起吗?你能告诉我们机器创作的音乐和人类创作的音乐有什么区别吗?
杰弗里斯: 我认为你已经可以用一些研究团队正在开发的东西来愚弄人们,特别是钢琴和古典音乐发生器。
目前最大的问题是保持歌曲的长期一致性。很多发生器可以让一首歌曲持续 10 到 15 秒,但之后发生器似乎“失去了理智”,创造出一首完全不同的歌曲。我专注于 Magenta 的音乐转换器,因为它特别擅长学习音乐的长期结构。
但是还有很长的路要走。
这些系统中的大多数都不能像人类那样做,即生成一个惊人的钩子,然后每 30 秒循环一次作为合唱,然后使用歌曲的剩余部分来支持它。
算法大多只是预测机器,试图从之前的音符中猜出下一个音符。这是一台向前犁雪的雪犁,忘记了之前发生的许多事情。《变形金刚》现在可以让歌曲的“感觉”都一样,但这并不意味着它在大多数时候都产生了像伟大的音乐或热门歌曲这样的东西。
paper space:创作《笑猴》专辑最大的挑战是什么?如果这篇采访的读者想开始一个类似的项目,你会建议他们如何开始?**
杰弗里斯: 最大的挑战就是争论代码。这些算法和库大部分都是研究机构和大学出来的。它们不是被完美维护的企业软件库,所以你最终不得不重写它的一部分,或者用依赖关系修复 bug,或者花两天时间尝试编译一些东西。当事情出错时,你只能靠自己解决,因为没有支持。
在我们将歌曲转换成 MIDI 的阶段,我们花了一个月的时间,因为我们找不到一个很棒的库,而且我们有的库也不完美。把东西转换成 MIDI 是没有钱的,所以没有伟大的商业图书馆可以依靠,真正捕捉到接近原始的音乐。
Paperspace: 你对谁或什么人应该因创作一首音乐而获得荣誉持什么立场?如果有人使用相同的模型,用你的代码生成不同的音乐片段,他们会是抄袭者还是唯一的艺术家,或者介于两者之间?随着 ML 迅速变得更容易实现,这对作者来说意味着什么?
对于这个问题,我没有一个好的答案,除了说我希望律师和立法者这次比过去做得更好。
我们有大量的权利跟踪数据库,试图捕捉人们使用一些东西来起诉他们。我讨厌那样。
我记得当野兽男孩·保罗的精品店推出大量样品时,那张专辑以及那个时代的其他专辑导致了法律的改变,所以即使是小酒吧也需要许可证。如果没有价值 2000 万美元的作品和来自 30 个不同版权所有者的信件,你现在不可能制作出保罗精品店。我希望我们做得更好。
至于机器,我不认为机器或算法应该拥有任何版权。
Paperspace: 我们想换个话题,谈谈你的创作过程。在我们看来,你似乎参与了大量疯狂的事情——从宣传数据科学管道产品到教数百万读者如何开始使用人工智能,到设计新颖的密码架构,再到写科幻小说,等等——你是多线程处理所有这些事情,还是你只是坐下来,进入一种流动状态,然后连续完成事情的人之一?
杰弗里斯: 前几天我在推特上发了一个老笑话:
“你是等待灵感的出现,还是按照时间表写作?
我等待缪斯女神,当我每天写作时,她会在上午 10 点准时出现。"
对一个艺术家来说,没有什么比每天工作更重要的了。培养钢铁般的意志去努力,你就能创造奇迹。我花了十年的时间四处游荡,才真正致力于在心里写作,并专注于此。从那以后的十年里,我几乎每天都写作。
你没有得到昨天的荣誉。你必须一遍又一遍地做。而且每次都一样。前半个小时到一个小时我都在和自己交战。我想退出,做其他事情或焦虑和分心,这个世界把我夹在中间。但我坚持下去,很快奇迹就发生了。突然两三四个小时过去了,我完全忘记了时间。我只是在那里写作和专注,除了我写的东西,什么都不想。
我为心流而活。我刚刚完成了一本基于我的文章控制抑郁,过你应该过的生活的自助书,其中有一章是关于我如何利用心流给我的生活带来平衡的。我希望这本书能在今年晚些时候或明年初出版。
心流帮助你处理情绪,找到放松的时间,感觉你在为这个世界做出有意义的贡献。我无法想象没有它的生活,这是我创造如此多东西的秘密。实际上我认为我并没有创造那么多,但是当我回头看的时候,我意识到我确实创造了,这都是因为心流,我浪费了所有的时间,似乎做了比可能做的更多的事情。我坚信应该去做工作。正是这种一致性让你更容易进入心流。史蒂文·普莱斯菲尔德的《艺术的战争》对此谈得最好。每天去上班,有纪律地完成工作,剩下的事情会自己解决的。
Paperspace: 什么样的背景或训练让你准备在作品中撒下如此大的网?
杰弗里斯: 我人生中伟大的导师教会了我批判性思维是世界上最重要的技能。
如果你不接受自己的局限,并认为只要足够努力就能学到任何东西,那么你就能学到任何东西。
我们现在太专注于生活中狭隘的追求。我们不教孩子跨学科思维或文科。都是关于这个人类要为蜂巢做什么?他们是如何在一些毫无意义的标准化考试中得分的,这些考试除了你能通过考试之外毫无意义。和真正的聪明没有任何关系。
真正的智慧是自我意识。
如果你有自我意识,你就不会认同自己的局限和现在的自己。你总是可以变得更好,放下对自己的旧观念。
Paperspace: 一个适用于你的许多(面向公众的)作品的主题是未来主义。你能告诉我们未来主义对你意味着什么吗?你是否在探索与像雷·库兹韦尔或加来道雄一样的未来主义?你目前的困扰是什么,你认为没有人给予足够的关注?
杰弗里斯: 我最近痴迷于心理学,因为我正在完成我的自助书。
当我写这本书的时候,我想看看我的个性和思想有多少是硬件,多少是固件,多少是软件,意思是不可能重写,难以重写和容易重写。我发现软件比我们想象的要多得多。尤瓦尔·赫拉利谈了很多关于生物学家和人工智能研究人员将如何向人们展示他们大多只是程序,以及我们认为的许多原创思想只不过是一种精神启发。
除此之外,我目前正在密切关注 CBDC 或央行的数字货币。我认为很少有人关注 CBDC,他们应该尽快关注,因为他们可以轻松粉碎分散加密货币和自由货币的梦想。大多数人不了解比特币或者 Monero 或者 Zcash 之类的隐私保护币。他们没有意识到现金已经在保护隐私,当它一起消失时,他们会惊讶地发现权力就在他们的口袋里。中央政府已经妖魔化了分散的加密货币,同时他们正在想办法将隐私保护的梦想扭曲,将分散的钱变成一个集中控制的、全天候监控的硬币,放在你的口袋里。
如果我们不小心,你的口袋里很快就会有一个全景监狱支付系统,它会比你的智能手机更密切地跟踪你,这一点都不好。
我也认为人们对人工智能伦理关注不够。
我做过一些关于为公司建立真实世界道德项目的咨询,而不是像大多数公司那样只是一堆韦斯莱式的陈词滥调。我最近在柏林的 2b 前瞻智囊团的播客中谈到了这个问题。当人工智能做出生死决定,决定谁被雇用、晋升或解雇,或者谁能进学校、谁不能进学校,或者谁能进监狱、谁不能进监狱时,我们最好有一些好的答案来解释这些机器是如何以及为什么做出这些决定的。
现在我们没有好的答案,这比任何好莱坞幻想的机器人杀手都要可怕。
Paperspace : 在你对未来主义的看法中——所有事情都必须发生在一个连续的世界中吗?或者你的写作是否允许不同未来的可能性,即一些进步是相互排斥的?
杰弗里斯: 我认为我对未来的看法是对未来的蒙特卡洛分析。我在很多可能性中运行,寻找基于所有变量的最可能的分支。
但是没有未来学家能够预见黑天鹅事件,比如互联网的发明。一旦你有了互联网,它会使之前的所有预测失效,因为它改变了一切。一旦有人发明了印刷机,整个世界都会改变,所有的预测都会随之改变。
所以你最多可以说,如果我们继续这样下去,未来将会是这样。这仅仅取决于我们现在所知道的,以及你有多擅长跳出自我,从许多不同的角度看世界。
Paperspace: 未来主义的挑战之一是难以(或不可能)拥有认识论的确定性——也就是说,你如何知道你知道你知道什么?认识论总是令人麻木,但在未来主义的理论空间背景下(它还没有发生),似乎你需要一个关于世界如何工作的非常可塑的理论来解释每天出现的所有奇妙的新事物。
这是一种深入思考这种东西的吸引力吗——你不得不坚持不懈地重新优化?你有没有过这样的时刻-哦该死!一些大的新发展改变了你逻辑系统中的承重梁?当 GPT-3 的演示公开时,我们中的一些人几乎说不出话来——你有过类似的经历吗?
杰弗里斯: 在我的里克和莫蒂以及生命的意义一文中,我已经深入到我们真正能知道的兔子洞里。这是一个永无止境的洞,但它也主要是一个人们自己玩的游戏。好吧,除了“我存在”和其他几件事,你不可能知道太多。但是一旦你停止玩这个游戏,缩小范围,就会有一些相当一致的启发法和算法在人类、文化和社会层面上运行。如果你能走出自己的路,足够长的时间去观察它们,规则是相当清楚的。
前几天有人发微博说,你不可能有绝对的真实,如果他认为这是相对的。换句话说,一切都是相对的。我在推特上回复道:“如果你从 50 层楼的窗户跳出去,你会发现重力并不在乎你怎么想。”这个世界有一些硬性的规则,如果你睁开眼睛仔细观察,就不难发现。
什么都瞒不过我们。我们只是对自己隐瞒。
我认为无论出现什么新的证据或经历,我都能调整自己的思维。如果某些事情改变了我思考或看待事物的方式,那就更好了。固守过去的理解是通往停滞的道路。只要我们活着,我们就有机会成长,我们应该这样做。
paper space:这里有很多细微的差别和重要的想法。在你即将出版的书中会提到这个问题吗?你正在做的下一个大项目是什么?****
Jeffries: 对我来说,下一件大事将是我的自助书,这将是我的一个重大转变,但我希望它能帮助很多人。我不只是想写一些我在其他书上看到的合法的东西。我不能忍受像走火这样的室内把戏。这改变不了任何事。这只是一个魔术。我对自己做了真正的努力,改变了我的生活。
在写这本书的时候,我在自己和周围的人身上测试了所有的东西,我扔掉的比我保留的多。在大多数情况下,我不得不发展我自己的技术来对抗我的思维习惯,但最终我做到了。
我为这本书感到骄傲。这是我迄今为止写的最好的东西。我希望它能帮助人们过上精彩的生活,而不是只是做他们被展示的事情,接受他们被赋予的平庸生活。
作为一名 作者 、工程师、 专业博主 、 播客 、 公共演讲人 ,以及厚皮网 的首席技术布道者以及他在 Twitter 上的沉思,以及在 实用 AI 伦理联盟 和 AI 基础设施联盟 ,以及 机器学习规范栈 的工作。
我们塑造我们的工具,然后我们的工具塑造我们:采访 RunwayML 创始人克里斯托·巴尔·瓦伦苏拉
毫无疑问,机器学习应用正在整个媒体行业激增。
在这个博客中,我们已经涵盖了广泛的艺术为重点的 ML 用例。我们最近采访了视觉艺术家、游戏设计师和唱诗班,我们还编写了一些主题指南,如pose estimation with pose net、semantic image synthesis with GauGAN和face app-style image filters with CycleGAN。
简而言之,我们认为非常清楚的是,机器智能将改变——并且已经在改变——电影和媒体行业,从 VFX 到 ar 和 VR,到动画,资产创建,视频分析,排版设计,文本生成,以及这之间的一切。
这就是克里斯托·巴尔·巴伦苏埃拉的用武之地。克里斯是 NYU·蒂施著名的 ITP(交互式电信项目)的前研究员,也是前 Paperspace ATG 研究员,他正在建立一家名为 RunwayML 的令人兴奋的新公司,将最先进的机器学习技术和架构带给媒体创意人员。
随着我们变得痴迷于艺术和技术的交叉,我们很兴奋地看到 Runway 一次又一次地出现在关于如何为创意人员配备强大的人工智能辅助媒体创作工具的讨论中。
我们很高兴能与 Cris 交谈,了解他对媒体中机器智能的未来的看法,以及如何建立一套新的工具,让每一个创意者都能做出前所未有的事情。
Paperspace: 先来后到 RunwayML 这个名字的背后是什么?它似乎让人联想到时尚、飞机和生产线——这些东西是否抓住了你想做的事情的精神?
瓦伦苏拉: 我在 NYU 读书的时候就开始从事 t 台工作了。在研究阶段的早期,我希望有一个简短的名字,可以用来与我的顾问讨论项目,我不想要太长或复杂的东西。该项目的最初想法是创建一个平台,使艺术家可以访问机器学习模型。所以我开始围绕这个问题集思广益,然后我意识到“模特的平台”已经有了一个名字:跑道。
Paperspace: 啊!是的,当然。那么是什么让你对机器学习感兴趣呢?你能告诉我们你在 NYU 的研究吗,那是如何导致或加速你对人工智能的兴趣的?
Valenzuela: 我在智利工作的时候偶然发现了基因科岗围绕神经风格转移的工作。当时,我不知道它是如何制作的,但我对计算创造力的想法以及深层图像技术可能对艺术家产生的影响和意义非常着迷。我掉进了一个兔子洞,研究神经网络,直到我对这个主题如此着迷,最终我辞掉了工作,离开了智利,进入 NYU 的 ITP 全职学习计算创造力。
Paperspace: 您是否曾有过这样的想法:“好吧,我需要构建 Runway,因为这个工具堆栈还不存在,我想要它?”或者,这是对传统繁重的 ML 任务变得容易 100 倍的潜力的不同认识?
瓦伦苏拉: 围绕一个创意工作需要大量的实验。任何创造性的努力都需要一个搜索和实验阶段,一种快速原型的精神,以及快速尝试新想法的意愿。我想围绕神经网络创造艺术和探索想法,但每次我试图构建原型时,我都会遇到与我的目标无关的技术难题。
想象一下,如果每次画家想要绘制新的画布,她都必须手动创建颜料和颜料管——这就是我每次想使用机器学习模型的感觉。在尝试画任何东西之前,我用手磨了几个星期的颜料。我非常沮丧,最终我决定构建一些东西,使在一个创造性的环境中使用 ML 的整个过程更加容易。
paper space:你在 ml5js 上的工作是如何翻译到 Runway 上的?这两个项目有相似的使命吗——让视觉创意者可以使用先进的机器学习技术?
当我在 NYU 的时候,我有一个惊人的机会与丹·希夫曼密切合作。与丹和 ITP 大学的一群学生一起,我们有了一个想法,即创造一种方法,使机器学习技术在网络上更容易访问和友好——特别是对于创意编码社区。
我们从 p5js 和处理基金会在视觉艺术中促进软件素养的使命中受到了极大的鼓舞。Runway 和 ml5js 在可访问性、包容性和社区方面有着共同的价值观和原则。这两个项目几乎同时开始,并且对如何发展艺术技术有着相似的愿景。
paper space:你见过的创作者追求使用你的软件的最酷的用例是什么?**
瓦伦苏拉: 我们正在建造跑道,让其他人创造和表达自己。我喜欢看到来自不同背景的创作者使用 Runway 来创作艺术、视觉、视频——或者只是为了学习或实验。
随着时间的推移,我们看到了如此多令人惊叹的项目,我们开始在一个专门的网站上对它们进行分组:runwayml.com/madewith。我认为这些只是社区中最好的项目中的一些。
我们还不断采访创作者,展示他们的一些作品。比如《游艇》的 Claire Evans、Jona Bechtolt 和 Rob Kieswetter 最近为他们的格莱美提名专辑所做的。
https://www.youtube.com/embed/Exgd6AW-NKg?start=1&feature=oembed
Paperspace: 你或者你的一个团队成员呢?你的团队有没有创造出真正激励你的东西?
瓦伦苏拉: 丹·希夫曼用 Runway 创造了一长串惊艳的项目。最近,他一直在玩文本生成功能,并创建了迷人的 Twitter 机器人。这个想法是,你可以基于 OpenAI 的 GPT-2 模型训练你自己的生成文本模型。一旦模型在 Runway 中完成训练,您就可以将它部署为一个托管的 URL,它可以以各种不同的方式使用。
丹一直在根据他收集的不同数据集创建 Discord、Slack 和 Twitter 机器人。查看 YouTube 频道的编码训练,了解更多信息并创建自己的编码训练。如果你想用 Runway 创建你自己的 GPT-2 机器人,看看这个教程。
paper space:runway ml 有一个很大的元素与成为技术人员的创造性社区的一部分有关。你如何培养这种合作的环境?建立这个社区的早期回报是什么?你可以在 Runway 周围创建什么样的社区?
*****valen zuela:*我认为一个好的社区是由成员之间不断讨论共同的理想、愿景和激情组成的。t 台社区,或者说创新技术社区,也没什么不同。
我们密切倾听社区成员的心声,能够与他们一起创造至关重要。这就是我们帮助并与来自世界各地的数百名学生、艺术家和技术专家合作的原因。
Runway 还被广泛用于各种机构的教学,从麻省理工学院的建筑项目到秘鲁自行组织的独立工作室。但最重要的是,我们希望营造一种促进创造力、友善、平等和尊重的环境。
Paperspace: 你的应用程序使用收费吗?这是一款令人惊叹的产品,我们不断惊讶于我们可以如此简单快速地完成这么多工作。就像魔法一样!
Valenzuela: 当建立一个允许创作者使用强大的机器学习模型和技术的大型平台时,会有大量的技术复杂性。我们的目标是让每个人都能尽可能地使用这个平台。Runway 可以在网上免费使用,也可以免费下载。用户可以在本地运行模型,也可以付费在云中远程使用模型。还有一个订阅计划,以获得该平台中更高级的功能。
***paper space***:当我们设计软件时,我们经常会想到这句名言“我们塑造我们的工具,然后我们的工具塑造我们。”你认为 Runway 是一个具有这种创造世界潜力的工具吗?或者你认为《天桥》是一个有创造力的合著者?对于像 Runway 这样的平台来说,相对于其用户产生的输出,它的正确角色是什么?
Valenzuela : 在构建界面时,一个常见的比喻是将物理世界的对象转化为软件概念,以帮助用户更轻松地与应用程序交互。例如,的桌面隐喻暗示了一张放有文件和文件夹的实体桌子。
类似的事情也发生在媒体创作和创意软件的界面上。在传统的图像编辑软件中,我们有铅笔、橡皮擦、尺子和剪刀的概念。但问题是我们依赖这些隐喻太久了——它们影响了我们对工具局限性的思考。
我们正面临着几十年前的媒体范式和隐喻,围绕着如何创建内容和围绕它们构建复杂的数字工具。我认为现在是我们改变这些原则的时候了,因为它们限制了我们的创造性表达,并采用了一套新的隐喻来利用现代计算机图形技术。Runway 是构建这些新工具和原则的平台。
Paperspace: 你喜欢生成艺术或合成媒体这两个术语吗?你认为这两者中的任何一个都可以很好地描述人工智能辅助媒体创作领域正在发生的事情吗?你认为我们会开始看到艺术家通过作品获得财富和名声吗?因为没有更好的词,这些作品就是合成的。
瓦伦苏拉 : 我相信艺术家会尝试使用任何与他们的实践相关的媒介。r·卢克·杜波依斯说得很好:“艺术家有责任提出技术意味着什么以及它如何反映我们的文化的问题。”
生成艺术的历史并不新鲜。在最近的人工智能热潮之外,在艺术制作过程中引入自主系统的想法已经存在了几十年。不同的是,现在我们进入了一个合成时代。
使用大量数据和深度技术来操纵和编辑媒体的想法不仅会极大地改变艺术,还会以类似于 90 年代 CGI 革命的方式改变一般内容创作的可能性。很快,每个人都将能够创作出专业的好莱坞式的内容。当我们被我们能用它创造的东西所激励时,我们叫它什么并不重要。
Paperspace :你认为有哪些创意产业会从 ML 的应用中受益,但它们还没有应用这项技术?
在过去的一年里,我们与许多创意人员进行了交流和合作。我认为大多数创意产业都将受益于 ML。例如,我们已经与来自扎哈·哈迪德建筑师的技术团队 ZHA 代码进行了一些研讨会和实验。我相信建筑师现在正在快速地将 ML 技术融入到他们的工作流程中,我们将在接下来的几年里看到建筑/设计系统的重大变化。
我确实认为从 ML 中获益最大的领域之一将是娱乐业。自动化不仅将有助于加快目前需要几周或几个月才能完成的流程(从取景到剪辑的一切都将自动化)——而且合成媒体对下一代电影制作人和创意人员的影响将是巨大的。
有了 ML,创建专业级内容的门槛将大大降低。每个拥有电脑的人都可以创作出传统上只有专业的 VFX 工作室才能创作的内容。
Paperspace: 作为研究员,你在 Paperspace 工作的体验如何?那段经历对你创作《天桥》有帮助吗?
valen zuela:2018 年我有幸与 Paperspace 团队合作,学到了很多东西。有机会与 Paperspace 团队一起工作和协作,让我对如何构建一个优秀的产品、公司和社区有了深刻的认识。你所做的使团队在 ML 模型上合作的事情是很棒的,并且是工程团队所需要的。
paper space:Runway 的下一步是什么?您最想与您的社区分享的产品、功能、实现或开发是什么?在你的团队的期待下,你最兴奋的成就是什么?
Valenzuela: 还有一个几大更新很快就要来了!我们一直在围绕视频和图像模型开发一些激动人心的新功能,这些功能将会改变游戏规则。但最让我兴奋的总是《天桥》的所有精彩作品。我迫不及待地想看看创作者是怎么做的!
paper space:还有什么要补充的吗?对于这篇采访的读者来说,在《天桥》上开始创作的最好方法是什么?你对那些第一次探索 ML 如何增强他们作品的创意人员有什么建议吗?
:我们正在招聘!如果您有兴趣帮助我们想象和构建未来的创造性工具,我们将很高兴收到您的来信。****
如果你是 Runway 或者机器学习的入门者,查看一些编码训练 视频教程 ,加入我们的 Slack 频道 与更多创意者联系,或者只是在 Twitter 上 DM 我们:@ runwayml和@ c _ valenzuelab**
新的!控制台重新设计
我们非常高兴从今天开始向所有用户推出最新的控制台重新设计。
Paperspace web 控制台是成千上万的开发人员每天与 Paperspace 交互的主要方式。
新的控制台既是对我们现有信息架构(IA)的改造,也是对引擎盖下的重大升级,以支持更快、更优雅的网络体验。我们与用户交谈,分析常见模式,并升级关键组件,以构建一个我们认为您会喜欢的控制台。
亮点:
⭐ 曾经生活在侧边栏中的团队切换器现在是一个顶级组件,允许在团队之间和团队内部更快地导航。
⭐所有 URL 路由的范围都是您所在的团队工作区。这使得跨团队的链接共享和协作变得前所未有的简单。
⭐ 能够添加协作者的实体,如团队和项目,现在可以更清楚地表示出来。
下一步是什么?我们正致力于简化许多底层组件(表格、按钮、表单等)。)默认为手机友好型。
新的 ML Showcase 条目:使用 NVIDIA RAPIDS 和 Plot.ly 创建交互式 Web 应用
原文:https://blog.paperspace.com/webapp-nvidia-rapids-plot-ly/
https://www.youtube.com/embed/ppr74dY9-fE?feature=oembed
介绍
我们最近与来自拉皮兹和 Plot.ly 的人们合作,演示如何生成一个 web 应用程序来执行复杂的数据集可视化——所有这些都来自运行在 Paperspace 上的渐变笔记本。
我们很高兴发布这次合作的结果作为新的 ML 展示条目。与所有 ML Showcase 条目一样,我们邀请您将这些笔记本分发给您自己的团队并开始探索!
注意:强烈建议您在 P4000、P5000 或更高的 GPU 实例上运行笔记本电脑,因为笔记本电脑中使用的库仅与 Pascal 和更新的 NVIDIA GPU 架构兼容。
相互了解
如果你还不熟悉, RAPIDS 是 NVIDIA 的开源库集合,旨在将经典的机器学习功能直接移植到 GPU。
RAPIDS 的优势是多方面的,但总的来说,其理念是优化受益于 GPU 并行的任务,如将大数据块加载到内存中,或受益于增加处理器内核数量的任何其他任务。
在这个 ML 展示项目中有几个演示笔记本。您可以在这里亲自探索 GitHub 源代码:
所有笔记本都使用 NVIDIA 的 RAPIDS 框架,少数还整合了 Plot.ly 的 Dash
通过使用这些笔记本,您将熟悉许多令人兴奋的库,以及如何使用代理将应用部署到外部端点——所有这些都在一个笔记本中完成!
我们开始吧
我们要做的第一件事是使用 Paperspace 渐变控制台中的Create Notebook
功能创建一个新笔记本。确保为您的笔记本命名,并选择 RAPIDS tile 作为您的运行时。
Before we can do anything we will need to create a new notebook.
注意:当您选择一个运行时图块时,它等同于指定一个Workspace URL
、Container Name
和Container Command
——这些选项也可以在 Create Notebook 视图的Advanced Options
部分手动指定。
接下来,我们将选择一个机器类型。这里我们选择 NVIDIA Quadro P6000,30GB 内存,8 个 vCPUs。
Select a machine and toggle the advanced options to enter a Workspace URL manually
我们还将切换Advanced Options
并手动添加我们希望笔记本使用的 GitHub 工作区。
我们将使用这个回购:
https://github.com/gradient-ai/Building-an-Interactive-ML-Web-App-with-RAPIDS-and-Plotly
单击Start Notebook
后,我们将等待几分钟,让笔记本电脑启动。
一旦它启动,我们将能够直接从渐变 IDE 中看到存储库中不同的笔记本文件。
在这张图片中,我们正在探索纽约市出租车空间笔记本的一部分。
When the notebook starts we'll be able to enter the NYC Taxi Spatial notebook
太棒了。为了充分利用这些例子,我们建议从纽约市出租车笔记本开始,它介绍了急流生态系统的关键部分。
关于渐变笔记本 IDE 和 JupyterLab 的说明
当您使用本笔记本时,有时可能需要访问完整的 JupyterLab 实例。
例如,在编写本文时,Gradient IDE 还不支持基于 javascript 的可视化,因此当您在标签为的部分中运行单元格时,您可能会收到此警告,创建您的图表并启动仪表板:
Warning: Output format application/javascript is not supported at the moment.
不要害怕!你可以通过左边栏的图标轻松切换到 Jupyter。
Swap over to JupyterLab via the icon in the left sidebar.
总的来说,你应该能够在渐变 IDE 中执行大多数操作——并且新功能会不断添加——但是很高兴知道 JupyterLab 在你需要的时候就在那里。
纽约出租车空间笔记本
在这个由 RAPIDS 背后的团队创建的笔记本中,我们将利用许多 GPU 加速的 RAPIDS 库来探索纽约市出租车的行为。
NYC Taxi Spatial notebook created by the team at NVIDIA RAPIDS
该笔记本通过 NYC OpenData 以及以下库使用 2015 绿色出租车数据集的数据:
- cuSpatial -来自 RAPIDS 的 GPU 加速空间库
- cuDF -同样来自 RAPIDS 的 GPU 数据帧库
- cuXFilter -一个将 web 可视化连接到 GPU 加速交叉过滤的框架
该笔记本主要利用 cuSpatial 来清理和分析 borough 间的数据,并使用 cuXFilter 来可视化这些数据。
在这个过程中,笔记本通过代理服务器建立了一个端点来托管实时可视化!请务必在笔记本开头标记为 Add notebook ports 的单元格中记录您的 URL 模式。
Rapids + Plotly Dash on Paperspace 教程 1-3
如果您喜欢使用 NYC taxi spatial 笔记本,我们建议您看一看剩余的 QTY 3 教程。ipynb 文件,它具有额外的 RAPIDS 库和 Dash 功能。
RAPIDS + Plotly Dash Tutorial #1
在教程#1 中,我们将使用 Dash、cuDF 和 cuxfilter 来分析 65K+细胞及其基因表达。教程#2 和#3 展示了聚类和可视化数据的其他方法。
每个笔记本都是独立的,所以不用担心按顺序做。如果您有任何问题或意见,请务必联系我们或 NVIDIA RAPIDS 团队。
额外资源
如果您喜欢使用这些笔记本,我们邀请您探索其他的 ML Showcase 笔记本。我们还建议定期查看 Paperspace 博客的教程部分,了解定期添加的新条目。
最后,如果您想用 RAPIDS 启动一个新项目,您可以在 Paperspace 控制台中简单地创建一个新的笔记本,然后从那里开始。
Starting a new notebook with RAPIDS is as easy as selecting the RAPIDS runtime in the notebook create menu
我们迫不及待地想看看你的作品!
带梯度的权重和偏差
原文:https://blog.paperspace.com/weights-biases-with-gradient/
前言
Weights and Biases 是一个 ML Ops 平台,具有模型跟踪、超参数调整和模型训练步骤中的伪影保存等有用功能。与重量和偏差相结合,为 Gradient 用户提供世界一流的模型实验功能,同时利用 Gradient 易于使用的开发平台和加速硬件。
本教程的目标是突出权重和偏差功能,并展示如何在渐变中使用它们来扩大模型训练。在本教程中,您将学习启动 W&B 模型运行、记录指标、保存工件、调优超参数以及确定性能最佳的模型。然后,您将看到如何保存该模型,以用于使用 Gradient SDK 的工作流和部署。
本教程的内容是训练和记录 ResNet 模型变化的度量,以提高图像分类模型的准确性。我们将使用的数据集是 CIFAR-10 数据集,这是一个用于测试影像分类模型性能的常用数据集。CIFAR-10 数据集将包括属于 10 个类别之一的图像。如果您不熟悉 ResNet 模型或 CIFAR-10 数据集,可以看看这个演练。
本教程中使用的代码可以在 GitHub repo 这里找到,或者跟随下面链接中的公开示例。
基本命令
设置
首先,在这里登录或注册您的 Paperspace 帐户。登录后,创建一个名为 Gradient - W & B 的项目。在您的项目中,单击“笔记本”选项卡,然后单击“创建”按钮,弹出如下所示的创建笔记本页面。
对于这个项目,选择 PyTorch 1.10 运行时,然后在下选择任意一个 GPU 实例,选择一台机器。
切换高级选项菜单,将工作区 URL 更改为 https://github.com/gradient-ai/Gradient-WandB-Tutorial。这是 GitHub repo,它将包含运行本教程的文件。您的高级选项应该如下图所示。
最后,单击启动笔记本按钮,您的笔记本将被创建。等到笔记本运行后再进行下一步。
水电安装
下面的代码可以在 Python 笔记本 train_model_wandb.ipynb 中找到。
笔记本中的第一步是使用下面的命令将 Weights & Biases Python 库安装到您的环境中。
pip install wandb
要将刚刚安装的包导入到 Python 笔记本中,可以使用:
import wandb
如果通过 Python 笔记本运行 wandb,则必须将 WANDB_NOTEBOOK_NAME 环境变量设置为笔记本的相对路径。下面是如何做到这一点的一个例子。
import os
os.environ["WANDB_NOTEBOOK_NAME"] = "./train_model_wandb.ipynb"
注册
安装和设置后,您需要创建一个 Weights & Biases 帐户。你可以在他们位于 https://wandb.ai/site 的网站上这样做。
一旦你创建了一个 W&B 帐户,你将需要获取你的帐户的 API 密钥,以便将你的梯度笔记本与你的重量和偏差帐户相结合。API 密匙可以在 W&B 主页的 Profile → Settings → API 密匙下找到。它应该是一个 40 个字符的字符串。
现在,在您的渐变笔记本中,您可以使用上一步中找到的 W&B API 密钥登录 W&B。
wandb.login(key='your-wandb-api-key')
初始化模型运行
登录 W&B 帐户后,您需要在记录任何培训指标前初始化一次跑步。要初始化运行,请使用以下命令。
with wandb.init(project="test-project", config=config, name='ResNet18'):
# Python code below
关于 wandb.init 函数的更多细节可以在这里找到。
上面的命令将在 Weights and Biases 中创建一个名为“test-project”的项目(如果尚未创建),并初始化一个运行,该运行将存储您通过 config 对象传递的模型配置。Python 配置对象的示例可能如下所示:
config={
"epochs": 10,
"batch_size": 128,
"lr": 1e-3,
}
权重和偏差将在表格选项卡下的 W&B 项目仪表板中跟踪该运行和那些配置。下表显示了一个示例,其中型号名称用于标识运行和捕获配置值。
原木
在带有 wand.init() 缩进的中,您将添加 Python 代码,以便在训练期间将特定的模型细节记录到 W & B 中。在流程的不同步骤记录数据将会在您的 W & B 项目工作区中创建图表。根据您在模型运行期间记录的数据类型和频率,W & B 将创建一个特定的图表类型来最好地可视化这些数据。
在下面的脚本中,目标是在 10 个时期内训练 ResNet18 模型,记录每 50 个批次的训练损失以及每个时期结束时的验证损失和验证准确度。该脚本还记录每个时期的持续时间和平均时期运行时间。
该脚本分为以下三个步骤。第一步是导入所有必要的库和模块。第二步是创建一个用于计算和记录验证指标的验证模型函数。最后一步是运行贯穿模型训练和日志记录过程的训练脚本。
import time
import torch.nn as nn
import torch.optim as optim
import torch
from resnet import resnet18, resnet34
from load_data import load_data
We first import the libraries needed to run the training.
def validate_model(model, valid_dl, loss_func, device):
# Compute performance of the model on the validation dataset
model.eval()
val_loss = 0.
with torch.inference_mode():
correct = 0
for i, (images, labels) in enumerate(valid_dl, 0):
images, labels = images.to(device), labels.to(device)
# Forward pass
outputs = model(images)
val_loss += loss_func(outputs, labels)*labels.size(0)
# Compute accuracy and accumulate
_, predicted = torch.max(outputs.data, 1)
correct += (predicted == labels).sum().item()
return val_loss / len(valid_dl.dataset), correct / len(valid_dl.dataset)
Next, we instantiate the validate_model function
model_name = 'ResNet18'
# Initialize W&B run
with wandb.init(project="test-project", config=config, name=model_name):
# Create Data Loader objects
trainloader, valloader, testloader = load_data(config)
# Create ResNet18 Model with 3 channel inputs (colored image) and 10 output classes
model = resnet18(3, 10)
# Define loss and optimization functions
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=config['lr'], momentum=0.9)
# Move the model to GPU if accessible
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)
step = 0
epoch_durations = []
for epoch in range(config['epochs']):
epoch_start_time = time.time()
batch_checkpoint=50
running_loss = 0.0
model.train()
for i, data in enumerate(trainloader, 0):
# Move the data to GPU if accessible
inputs, labels = data[0].to(device), data[1].to(device)
# Zero the parameter gradients
optimizer.zero_grad()
# Forward + Backward + Optimize
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
# Log every 50 mini-batches
if i % batch_checkpoint == batch_checkpoint-1: # log every 50 mini-batches
step +=1
print(f'epoch: {epoch + ((i+1)/len(trainloader)):.2f}')
wandb.log({"train_loss": running_loss/batch_checkpoint, "epoch": epoch + ((i+1)/len(trainloader))}, step=step)
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / batch_checkpoint))
running_loss = 0.0
# Log validation metrics
val_loss, accuracy = validate_model(model, valloader, criterion, device)
wandb.log({"val_loss": val_loss, "val_accuracy": accuracy}, step=step)
print(f"Valid Loss: {val_loss:3f}, accuracy: {accuracy:.2f}")
# Log epoch duration
epoch_duration = time.time() - epoch_start_time
wandb.log({"epoch_runtime (seconds)": epoch_duration}, step=step)
epoch_durations.append(epoch_duration)
# Log average epoch duration
avg_epoch_runtime = sum(epoch_durations) / len(epoch_durations)
wandb.log({"avg epoch runtime (seconds)": avg_epoch_runtime})
print('Training Finished')
Finally, we create our training loop, and direct it to log the results of the training run in Weights and Biases
上述脚本将在 10 个时期(根据配置确定)内训练模型,并将每 50 个批次和每个时期结束时记录训练指标。在每个时期结束时,也将记录验证损失、验证准确度和时期持续时间。
所有这些细节都将在我们的权重和偏好项目中记录下来。您可以在工作区下的 Weights & bias测试项目中找到这一点。下面是一个这样的例子。
如果您运行相同的脚本,但是使用 ResNet34 模型而不是 ResNet18 模型,将会捕获另一个运行。可以在相同的图表上查看两次运行,以了解它们的比较情况。
从上面的图表中,我们可以看出每个模型的训练损失非常相似,但是 ResNet18 模型的验证精度更高,平均历元运行时间更低。epoch 运行时是有意义的,因为 ResNet34 模型有更多的层,因此具有更长的训练时间。看起来可能有一些过度拟合,以及列车损失持续下降,但验证损失在第 4 个时期后开始攀升。
保存渐变模型
为了充分利用我们的模型培训,我们希望能够做两件额外的事情。首先是将训练好的模型保存为渐变模型工件。其次,我们希望能够将 W&B 模型运行与渐变模型工件联系起来。这是很重要的,这样你就可以把我们上面看到的运行结果链接到一个渐变模型工件,然后你可以在渐变工作流和部署中使用它。有关渐变模型以及如何在工作流和部署中使用它们的信息,请参见渐变文档。
下面是一个将模型保存为渐变工件的过程,以及我们培训过程中的附加代码,用于在 W&B 运行的注释中记录模型名称。
在保存模型之前,您需要安装渐变 Python SDK。您可以使用下面的命令来完成。
pip install gradient
一旦安装了 Gradient SDK,您将需要创建一个模型客户端来与项目中的模型进行交互。为此,您需要使用渐变 API 键。要生成渐变 API 密钥,请在渐变页面的右上角,选择您的配置文件并单击团队设置。在“团队设置”下,选择“API 密钥”选项卡。要生成 API 密钥,请输入 API 密钥名称(例如 my-api-key ),然后单击添加。这将显示生成一个字母数字字符串,这是您的 API 键。请确保将此密钥保存在安全的地方,因为一旦您离开该页面,您将无法引用它。一旦您生成并复制了您的密钥,您应该能够看到它如下所示。
从上面获取 API 密钥,并使用它来创建 ModelsClient,如下所示。
from gradient import ModelsClient
models_client = ModelsClient(api_key='your-gradient-api-key')
在上传功能中,您需要您的项目 ID,它位于项目工作区的左上角。
下面是上传函数,它接收模型配置和模型客户端,并将模型保存为渐变工件,并返回模型名称。
def upload_model(config, model_client, model_dir='models'):
# Create model directory
if not os.path.exists(model_dir):
os.makedirs(model_dir)
# Save model file
params = [config['model'], 'epchs', str(config['epochs']), 'bs', str(config['batch_size']), 'lr', str(round(config['lr'], 6))]
full_model_name = '-'.join(params)
model_path = os.path.join(model_dir, full_model_name + '.pth')
torch.save(model.state_dict(), model_path)
# Upload model as a Gradient artifact
model_client.upload(path=model_path, name=full_model_name, model_type='Custom', project_id='your-project-id')
return full_model_name
在培训过程结束时,您可以调用该函数来保存您的模型,并将模型名称记录到您的 W&B 运行中。下面是该功能的一个示例。
# At the end of your training process
# Upload model artifact to Gradient and log model name to W&B
full_model_name = upload_model(config, model_client)
wandb.log({"Notes": full_model_name})
print('Training Finished')
现在我们可以在 Models 选项卡下看到保存在渐变项目中的模型。
您还可以在表格选项卡中找到的 W&B 运行的注释部分中引用渐变模型工件。
现在,您可以获得运行结果,找到最佳模型,在渐变中找到该模型,并在渐变工作流和部署中使用它!
史前古器物
Weights and Biases 的另一个有用的特性是能够创建可以保存到项目中的定制工件。作为一个例子,下面是一个创建 W&B 表的脚本,该表将作为工件存储在测试项目中。该表将包括来自测试数据集的图像图片、它们的类标签、模型的预测类以及该预测的详细分数。
首先,您需要初始化一个 W&B 运行并创建一个工件。这可以通过下面的脚本来完成。
with wandb.init(project='test-project'):
artifact = wandb.Artifact('cifar10_image_predictions', type='predictions')
在 with wand.init() 缩进中,创建一个存储上述数据的表。
# Classes of images in CIFAR-10 dataset
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# Create Data Loader objects
trainloader, valloader, testloader = load_data(config)
# Create columns for W&B table
columns=['image', 'label', 'prediction']
for digit in range(10):
columns.append("score_" + classes[digit])
# Create W&B table
pred_table = wandb.Table(columns=columns)
然后,通过训练好的模型运行第一批测试,并获取预测、分数和其他所需的数据。
with torch.no_grad():
for i, data in enumerate(testloader, 0):
# Move data to GPU if available
inputs, labels = data[0].to(device), data[1].to(device)
# Calculate model outputs and predictions
outputs = model(in# Create Data Loader objects
_, predicted = torch.max(outputs.data, 1)
# Loop through first batch of images and add data to the table
for j, image in enumerate(inputs, 0):
pred_table.add_data(wandb.Image(image), classes[labels[j].item()], classes[predicted[j]], *outputs[j])
break
最后,将表的数据保存为工件,并将其记录到您的 W&B 项目中
# Log W&B model artifact
artifact.add(pred_table, "cifar10_predictions")
wandb.log_artifact(artifact)
运行上面的脚本之后,您可以通过转到 W&B 项目的工件部分来导航到工件。在 Artifact 部分,点击 Version,Files 选项卡,然后点击保存工件的 JSON 文件。然后,您应该能够查看与下图类似的表格。
全胜
Weights & Biases 有一个名为 Sweeps 的超参数调整功能。扫描允许您指定模型配置和训练函数,然后遍历模型参数的不同组合,以确定模型的最佳超参数。我们可以将 Sweeps 与上面显示的 W&B 日志记录功能结合起来,记录所有这些运行的指标,以帮助确定性能最佳的模型。
您需要做的第一件事是创建一个扫描配置对象,它将存储模型的超参数选项。
sweep_config = {
'method': 'bayes',
'metric': {'goal': 'minimize', 'name': 'val_loss'},
'parameters': {
'batch_size': {'values': [32, 128]},
'epochs': {'value': 5},
'lr': {'distribution': 'uniform',
'max': 1e-2,
'min': 1e-4},
'model': {'values': ['ResNet18', 'ResNet34']}
}
}
从上面的配置中,您可以看到 3 个高级键被定义。第一个是定义扫描如何搜索最佳超参数的方法。在上面的配置中,我们使用贝叶斯搜索。
接下来,是公制键。这将指定扫描试图优化的内容。在这种情况下,目标是最小化验证损失。
最后,配置有参数说明。包含的参数是扫描要搜索的超参数。这些值可以是单个值、一组值或分布。在这种情况下,所有模型运行将在 5 个时期内进行训练,要么是 2 个 ResNet 模型中的一个,具有 32 或 128 的批量大小,并且具有指定分布内的学习率。
更多关于扫掠配置的文档可以在这里找到。
一旦指定了扫描配置,您将需要指定用于训练和记录模型的训练函数。下面的函数看起来应该与上面的训练脚本很相似,但是该功能被移到了一个名为 train 的函数中。
def train(config = None):
# Initialize W&B run
with wandb.init(project='test-project', config=config):
config = wandb.config
# Create Data Loader objects
trainloader, valloader, testloader = load_data(config)
# Create a ResNet model depending on the configuration parameters
if config['model']=='ResNet18':
model = resnet18(3,10)
else:
model = resnet34(3,10)
# Define loss and optimization functions
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=config['lr'], momentum=0.9)
# Move the model to GPU if accessible
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)
step = 0
batch_checkpoint=50
epoch_durations = []
for epoch in range(config['epochs']):
epoch_start_time = time.time()
running_loss = 0.0
model.train()
for i, data in enumerate(trainloader, 0):
# Move the data to GPU if accessible
inputs, labels = data[0].to(device), data[1].to(device)
# Zero the parameter gradients
optimizer.zero_grad()
# Forward + Backward + Optimize
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
# log every 50 mini-batches
if i % batch_checkpoint == batch_checkpoint-1: # log every 50 mini-batches
step +=1
print(f'epoch: {epoch + ((i+1)/len(trainloader)):.2f}')
wandb.log({"train_loss": running_loss/batch_checkpoint, "epoch": epoch + ((i+1)/len(trainloader))}, step=step)
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / batch_checkpoint))
running_loss = 0.0
# Log validation metrics
val_loss, accuracy = validate_model(model, valloader, criterion, device)
wandb.log({"val_loss": val_loss, "val_accuracy": accuracy}, step=step)
print(f"Valid Loss: {val_loss:3f}, accuracy: {accuracy:.2f}")
# Log epoch duration
epoch_duration = time.time() - epoch_start_time
wandb.log({"epoch_runtime (seconds)": epoch_duration}, step=step)
epoch_durations.append(epoch_duration)
# Log average epoch duration
avg_epoch_runtime = sum(epoch_durations) / len(epoch_durations)
wandb.log({"avg epoch runtime (seconds)": avg_epoch_runtime})
print('Training Finished')
在定义了扫描配置和训练功能之后,您可以通过调用 wandb.agent()来初始化 sweep_id 并开始扫描。
sweep_id = wandb.sweep(sweep_config, project="test-project")
wandb.agent(sweep_id, function=train, count=10)
该扫描将使用扫描配置中指定的超参数值启动 10 次不同的模型运行(使用 count 参数设置),并使用贝叶斯搜索找到超参数的最佳值。这些运行将被保存到扫描下的测试项目仪表板中。
一旦扫描完成,您就可以像下面这样比较运行的图表。
如果放大上图,可以看到 winter-sweep-7 的验证准确性最高。
让我们放大模型运行的细节,看看为这个模型运行设置了什么参数。您可以通过转到“表格”选项卡来完成此操作。
在这个例子中,winter-sweep-7 是用 ResNet18 模型运行的,批量大小为 32,学习率在指定范围的中间。
回到 Sweep 工作区,有一些更有用的图表。下图显示了超参数在验证准确性方面的重要性。学习率是最重要的负相关超参数,意味着学习率越低,验证准确率越高。
最后要看的图表是后续的模型运行是如何提高性能的。
在这个例子中,在模型运行期间,扫描调整了超参数,以改善关于验证损失的模型性能。第 5 次和第 7 次模型运行改善了之前模型运行的验证损失,第 7 次模型运行是 10 次运行中表现最好的。
结论
太好了!现在,您应该能够利用权重和偏差为实验跟踪和超参数调整提供的一些功能。然后,您可以将模型工件存储在 Gradient 中,以便能够在 Gradient 工作流和部署中引用它。渐变文档的链接,包括如何存储模型工件、使用工作流以及设置部署可以在这里找到。
同样,在 Gradient 上运行上述权重和偏差教程所需的笔记本和脚本可以在这里找到。
WGAN: Wasserstein 生成对抗网络
Photo by Sung Jin Cho / Unsplash
自 2014 年以来,由于这些架构具有巨大的潜力,生成式对抗网络(GANs)一直在接管深度学习和神经网络领域。虽然最初的 GANs 能够产生不错的结果,但当试图执行更困难的计算时,它们经常被发现失败。因此,已经提出了这些 gan 的几种变体,以确保我们能够实现可能的最佳结果。在我们之前的文章中,我们已经介绍了这种版本的 GANs 来解决不同类型的项目,在本文中,我们也将这样做。
在这篇文章中,我们将讨论 Wasserstein GAN (WGANs)中的一种生成对抗网络(GANs)。我们将了解这些 WGAN 发生器和鉴别器结构的工作原理,并详述其实现细节。我们将使用梯度惩罚方法来研究它的实现,最后,从头开始构建一个具有以下架构的项目。整个项目可以在 Paperspace 上提供的 Gradient 平台上实施。对于那些想培训这个项目的观众,我会推荐他们去看看这个网站,同时实施这个项目。
简介:
生成对手网络(GANs)是人工智能和深度学习领域的一项巨大成就。自从最初引入以来,它们一直被用于大型项目的开发中。虽然这些 gan 及其竞争的发电机和鉴别器模型能够取得巨大成功,但这些网络也有几个失败的案例。
两个最常见的原因是收敛失败或模式崩溃。在收敛失败的情况下,模型不能产生最优或良好质量的结果。在模式崩溃的情况下,模型无法产生重复类似模式或质量的独特图像。因此,为了解决其中的一些问题或解决众多类型的问题,逐渐出现了许多为 GANs 开发的变体和版本。
虽然我们已经在之前的一些文章中讨论了dcgan的概念,但在这篇博客中,我们将重点讨论 WGAN 网络如何应对这些问题。与简单的 GAN 架构相比,WGAN 为训练模型提供了更高的稳定性。WGAN 中使用的损失函数也为我们提供了评估模型的终止标准。虽然有时可能需要稍长的训练时间,但这是获得更有效结果的较好选择之一。让我们在下一节中更详细地理解这些 WGANs 的概念。
了解 WGANs:
生成对抗网络(GANs)的工作思想是利用两种主要的概率分布。其中一个主要的实体是发生器的概率分布(Pg),它指的是来自发生器模型输出的分布。另一个重要的实体是来自真实图像(Pr)的概率分布。生成性对抗网络的目标是确保这两种概率分布彼此接近,使得生成的输出是高度真实和高质量的。
为了计算这些概率分布的距离,机器学习中的数理统计提出了三种主要方法,即 kull back-lei bler 散度、Jensen-Shannon 散度和 Wasserstein 距离。Jensen-Shannon 发散(也是一种典型的 GAN 损耗)最初是简单 GAN 网络中更常用的机制。
然而,这种方法在处理梯度时存在问题,可能导致不稳定的训练。因此,我们利用 Wasserstein 距离来解决这些重复出现的问题。数学公式的表示如下所示。参考下面的研究论文以获得更多阅读和信息。
在上面的等式中,最大值表示对鉴别器的约束。在 WGAN 架构中,鉴别器被称为鉴别器。这种惯例的原因之一是没有 sigmoid 激活函数将值限制为 0 或 1,这意味着真或假。相反,WGAN 鉴别器网络返回一个范围内的值,这使得它可以不那么严格地充当批评家。
等式的第一部分代表实际数据,而第二部分代表发电机数据。上述等式中的鉴别器(或评论家)旨在最大化真实数据和生成数据之间的距离,因为它希望能够相应地成功区分数据。生成器网络旨在最小化真实数据和生成数据之间的距离,因为它希望生成的数据尽可能真实。
了解工作组的实施细节:
对于 WGAN 网络的原始实现,我建议查看下面的研究论文。它详细描述了架构构建的实现。critic 为与 GAN 相关的问题的期望计算添加了有意义的度量,并且还提高了训练稳定性。
然而,最初的研究论文的一个主要缺点是,使用了一种重量剪裁的方法,发现这种方法并不总是像预期的那样最佳地工作。当重量削减足够大时,它导致更长的训练时间,因为评论家要花很多时间来适应预期的重量。当权重裁剪较小时,它会导致渐变消失,尤其是在大量图层、无批量归一化或与 RNNs 相关的问题的情况下。
因此,有必要稍微改进 WGAN 的培训机制。下面的研究论文介绍了解决这些问题的最佳方法之一,该论文使用梯度罚函数法解决了这个问题。本文的研究有助于改进 WGAN 的训练。让我们看一下为完成所需任务而提出的算法的图像。
WGAN 使用梯度惩罚方法来有效地解决该网络的先前问题。WGAN-GP 方法提出了一种替代权重剪裁的方法,以确保平滑的训练。作者没有削减权重,而是提出了一种“梯度惩罚”,通过添加一个损失项来保持鉴别器梯度的 L2 范数接近 1 ( 源)。上面的算法定义了我们在使用这种方法时必须考虑的一些基本参数。
λ定义了梯度惩罚系数,而 n-critic 指的是每次生成器迭代的 critic 迭代次数。alpha 和 beta 值指的是 Adam 优化器的约束。该方法提出,在添加具有梯度惩罚的损失函数之前,我们利用生成图像旁边的插值图像,因为它有助于满足 Lipschitz 约束。该算法一直运行,直到我们能够在所需数据上实现令人满意的收敛。现在让我们看看这个 WGAN 的实际实现,用梯度罚函数法来构造 MNIST 项目。
用 WGANs 构建一个项目:
在本文的这一部分中,我们将根据我们对运行方法和实现细节的理解来开发 WGAN 网络。我们将确保在训练 WGAN 网络时使用梯度惩罚方法。对于本项目的构建,我们将利用来自官方 Keras 网站的以下参考链接,其中考虑了大部分代码。
如果你在 Gradient 中工作,我建议你使用 TensorFlow 运行时创建一个笔记本。这将在安装了 TensorFlow 和 Keras 的 docker 容器中设置您的环境。
导入基本库:
我们将利用 TensorFlow 和 Keras 深度学习框架来构建 WGAN 架构。如果您不太熟悉这些库,我将推荐您参考我以前广泛涉及这两个主题的文章。观众可以通过这个链接查看 TensorFlow 的文章,通过下面的链接查看 Keras 的博客。这两个库应该足以完成这个项目中的大多数任务。如果需要,我们还将为一些数组计算导入 numpy,为一些可视化导入 matplotlib。
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import matplotlib.pyplot as plt
import numpy as np
定义参数和加载数据:
在本节中,我们将定义一些基本参数,定义几个在整个项目中重复使用的神经网络模块,即 conv 模块和上采样模块,并相应地加载 MNIST 数据。让我们首先定义一些参数,例如 MNIST 数据的图像大小,它是 28 x 28 x 1,因为每个图像的高度和宽度都是 28,并且有一个通道,这意味着它是一个灰度图像。让我们也定义一个基本批量大小和一个噪声维度,生成器可以利用它来生成所需数量的“数字”图像。
IMG_SHAPE = (28, 28, 1)
BATCH_SIZE = 512
noise_dim = 128
下一步,我们将加载 MNIST 数据,该数据可从 TensorFlow 和 Keras 数据集免费示例数据集直接访问。我们将把 60000 个现有图像等效地分成它们各自的训练图像、训练标签、测试图像和测试标签。最后,我们将标准化这些图像,以便训练模型可以轻松计算特定范围内的值。下面是执行以下操作的代码块。
MNIST_DATA = keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = MNIST_DATA.load_data()
print(f"Number of examples: {len(train_images)}")
print(f"Shape of the images in the dataset: {train_images.shape[1:]}")
train_images = train_images.reshape(train_images.shape[0], *IMG_SHAPE).astype("float32")
train_images = (train_images - 127.5) / 127.5
在下一个代码片段中,我们将定义卷积块,我们将主要利用它来构建鉴别器架构,使其充当所生成图像的评论家。卷积块函数将接受卷积 2D 层的一些基本参数以及一些其他参数,即批量归一化和丢失。正如研究论文中所述,discriminator critic 模型的一些层利用了批量标准化或丢弃层。因此,如果需要,我们可以选择在卷积层之后添加两层中的任何一层。下面的代码片段代表卷积模块的功能。
def conv_block(x, filters, activation, kernel_size=(3, 3), strides=(1, 1), padding="same",
use_bias=True, use_bn=False, use_dropout=False, drop_value=0.5):
x = layers.Conv2D(filters, kernel_size, strides=strides,
padding=padding, use_bias=use_bias)(x)
if use_bn:
x = layers.BatchNormalization()(x)
x = activation(x)
if use_dropout:
x = layers.Dropout(drop_value)(x)
return x
类似地,我们还将为上采样模块构造另一个函数,我们将在 WGAN 结构的生成器架构的整个计算过程中主要使用该函数。我们将定义一些基本参数和一个选项,如果我们想包括批处理规范化层或辍学层。注意,每个上采样块之后也是传统的卷积层。如果需要,可以在这两个层之后添加批标准化或丢弃层。查看下面创建上采样块的代码。
def upsample_block(x, filters, activation, kernel_size=(3, 3), strides=(1, 1), up_size=(2, 2), padding="same",
use_bn=False, use_bias=True, use_dropout=False, drop_value=0.3):
x = layers.UpSampling2D(up_size)(x)
x = layers.Conv2D(filters, kernel_size, strides=strides,
padding=padding, use_bias=use_bias)(x)
if use_bn:
x = layers.BatchNormalization()(x)
if activation:
x = activation(x)
if use_dropout:
x = layers.Dropout(drop_value)(x)
return x
在接下来的几节中,我们将利用卷积模块和上采样模块来构建发生器和鉴频器架构。让我们来看看如何相应地构建生成器模型和鉴别器模型,以创建一个整体高效的 WGAN 架构来解决 MNIST 项目。
构建发电机架构:
在先前定义的上采样模块函数的帮助下,我们可以继续构建我们的生成器模型,以用于这个项目。我们现在将定义一些基本要求,例如我们之前指定的具有潜在维度的噪声。我们将用一个完全连接的层、一个批量标准化层和一个泄漏 ReLU 来跟踪这个噪声。在将输出传递给下一个上采样模块之前,我们需要对函数进行相应的整形。
然后,我们将整形后的噪声输出传递到一系列上采样模块中。一旦我们将输出通过三个上采样模块,我们就获得了高度和宽度为 32 x 32 的最终形状。但是我们知道 MNIST 数据集的形状是 28x28 的形式。为了获得这些数据,我们将使用裁剪 2D 层来获得所需的形状。最后,我们将通过调用模型函数来完成生成器架构的构建。
def get_generator_model():
noise = layers.Input(shape=(noise_dim,))
x = layers.Dense(4 * 4 * 256, use_bias=False)(noise)
x = layers.BatchNormalization()(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Reshape((4, 4, 256))(x)
x = upsample_block(x, 128, layers.LeakyReLU(0.2), strides=(1, 1), use_bias=False,
use_bn=True, padding="same", use_dropout=False)
x = upsample_block(x, 64, layers.LeakyReLU(0.2), strides=(1, 1), use_bias=False,
use_bn=True, padding="same", use_dropout=False)
x = upsample_block(x, 1, layers.Activation("tanh"), strides=(1, 1),
use_bias=False, use_bn=True)
x = layers.Cropping2D((2, 2))(x)
g_model = keras.models.Model(noise, x, name="generator")
return g_model
g_model = get_generator_model()
g_model.summary()
构建鉴别器架构:
现在我们已经完成了生成器架构的构建,我们可以继续创建鉴别器网络(在 WGANs 中通常称为 critic)。在执行 MNIST 数据生成项目的鉴别器模型中,我们将执行的第一步是相应地调整形状。由于 28 x 28 的尺寸在几次跨步后会导致奇数尺寸,因此最好将图像尺寸转换为 32 x 32 的尺寸,因为它在执行跨步操作后会提供偶数尺寸。
一旦我们添加了零填充层,我们就可以根据需要继续开发 critic 架构。然后,我们将继续添加一系列卷积块,如前一函数所述。请注意,这些图层可能使用也可能不使用批量归一化或缺失图层。经过四个卷积块后,我们将输出通过一个平坦层、一个下降层,最后是一个密集层。注意,与简单 GAN 网络中的其他鉴别器不同,密集层不利用 sigmoid 激活函数。最后,调用模型创建评论家网络。
def get_discriminator_model():
img_input = layers.Input(shape=IMG_SHAPE)
x = layers.ZeroPadding2D((2, 2))(img_input)
x = conv_block(x, 64, kernel_size=(5, 5), strides=(2, 2), use_bn=False, use_bias=True,
activation=layers.LeakyReLU(0.2), use_dropout=False, drop_value=0.3)
x = conv_block(x, 128, kernel_size=(5, 5), strides=(2, 2), use_bn=False, use_bias=True,
activation=layers.LeakyReLU(0.2), use_dropout=True, drop_value=0.3)
x = conv_block(x, 256, kernel_size=(5, 5), strides=(2, 2), use_bn=False, use_bias=True,
activation=layers.LeakyReLU(0.2), use_dropout=True, drop_value=0.3)
x = conv_block(x, 512, kernel_size=(5, 5), strides=(2, 2), use_bn=False, use_bias=True,
activation=layers.LeakyReLU(0.2), use_dropout=False, drop_value=0.3)
x = layers.Flatten()(x)
x = layers.Dropout(0.2)(x)
x = layers.Dense(1)(x)
d_model = keras.models.Model(img_input, x, name="discriminator")
return d_model
d_model = get_discriminator_model()
d_model.summary()
创建整体 WGAN 模型:
下一步是定义整个 Wasserstein GAN 网络。我们将把这个 WGAN 建筑结构分成三块的形式。在第一个代码块中,我们将定义在整个类的各种函数中使用的所有参数。查看下面的代码片段,了解我们将使用的不同参数。注意,所有的函数都在 WGAN 类中。
class WGAN(keras.Model):
def __init__(self, discriminator, generator, latent_dim,
discriminator_extra_steps=3, gp_weight=10.0):
super(WGAN, self).__init__()
self.discriminator = discriminator
self.generator = generator
self.latent_dim = latent_dim
self.d_steps = discriminator_extra_steps
self.gp_weight = gp_weight
def compile(self, d_optimizer, g_optimizer, d_loss_fn, g_loss_fn):
super(WGAN, self).compile()
self.d_optimizer = d_optimizer
self.g_optimizer = g_optimizer
self.d_loss_fn = d_loss_fn
self.g_loss_fn = g_loss_fn
在下一个函数中,我们将创建我们在上一节中讨论过的梯度罚方法。请注意,梯度损失是在插值图像上计算的,并添加到鉴频器损失中,如前一节的算法中所述。这种方法可以让我们在训练时实现更快的收敛和更高的稳定性。它还使我们能够实现更好的权重分配。检查下面的代码实现梯度惩罚。
def gradient_penalty(self, batch_size, real_images, fake_images):
# Get the interpolated image
alpha = tf.random.normal([batch_size, 1, 1, 1], 0.0, 1.0)
diff = fake_images - real_images
interpolated = real_images + alpha * diff
with tf.GradientTape() as gp_tape:
gp_tape.watch(interpolated)
pred = self.discriminator(interpolated, training=True)
grads = gp_tape.gradient(pred, [interpolated])[0]
norm = tf.sqrt(tf.reduce_sum(tf.square(grads), axis=[1, 2, 3]))
gp = tf.reduce_mean((norm - 1.0) ** 2)
return gp
在下一个也是最后一个功能中,我们将定义 WGAN 架构的训练步骤,类似于上一节中指定的算法。我们将首先训练发电机,并获得发电机的损耗。然后,我们将训练 critic 模型,并获得鉴别器的损耗。一旦我们知道了发生器和评论家的损失,我们将解释梯度惩罚。一旦计算出梯度罚分,我们将把它乘以一个恒定的权重因子,并将这个梯度罚分发给评论家。最后,我们将退回相应的发电机和批判损失。下面的代码片段定义了如何执行以下操作。
def train_step(self, real_images):
if isinstance(real_images, tuple):
real_images = real_images[0]
batch_size = tf.shape(real_images)[0]
for i in range(self.d_steps):
# Get the latent vector
random_latent_vectors = tf.random.normal(
shape=(batch_size, self.latent_dim)
)
with tf.GradientTape() as tape:
# Generate fake images from the latent vector
fake_images = self.generator(random_latent_vectors, training=True)
# Get the logits for the fake images
fake_logits = self.discriminator(fake_images, training=True)
# Get the logits for the real images
real_logits = self.discriminator(real_images, training=True)
# Calculate the discriminator loss using the fake and real image logits
d_cost = self.d_loss_fn(real_img=real_logits, fake_img=fake_logits)
# Calculate the gradient penalty
gp = self.gradient_penalty(batch_size, real_images, fake_images)
# Add the gradient penalty to the original discriminator loss
d_loss = d_cost + gp * self.gp_weight
# Get the gradients w.r.t the discriminator loss
d_gradient = tape.gradient(d_loss, self.discriminator.trainable_variables)
# Update the weights of the discriminator using the discriminator optimizer
self.d_optimizer.apply_gradients(zip(d_gradient, self.discriminator.trainable_variables))
# Train the generator
random_latent_vectors = tf.random.normal(shape=(batch_size, self.latent_dim))
with tf.GradientTape() as tape:
# Generate fake images using the generator
generated_images = self.generator(random_latent_vectors, training=True)
# Get the discriminator logits for fake images
gen_img_logits = self.discriminator(generated_images, training=True)
# Calculate the generator loss
g_loss = self.g_loss_fn(gen_img_logits)
# Get the gradients w.r.t the generator loss
gen_gradient = tape.gradient(g_loss, self.generator.trainable_variables)
# Update the weights of the generator using the generator optimizer
self.g_optimizer.apply_gradients(zip(gen_gradient, self.generator.trainable_variables))
return {"d_loss": d_loss, "g_loss": g_loss}
训练模型:
开发 WGAN 架构和解决我们项目的最后一步是有效地训练它并达到预期的结果。我们将把这个部分分成几个功能。在第一个函数中,我们将继续为 WGAN 模型创建自定义回调。使用我们创建的这个自定义回调,我们可以定期保存生成的图像。下面的代码片段展示了如何创建自己的定制回调来执行特定的操作。
class GANMonitor(keras.callbacks.Callback):
def __init__(self, num_img=6, latent_dim=128):
self.num_img = num_img
self.latent_dim = latent_dim
def on_epoch_end(self, epoch, logs=None):
random_latent_vectors = tf.random.normal(shape=(self.num_img, self.latent_dim))
generated_images = self.model.generator(random_latent_vectors)
generated_images = (generated_images * 127.5) + 127.5
for i in range(self.num_img):
img = generated_images[i].numpy()
img = keras.preprocessing.image.array_to_img(img)
img.save("generated_img_{i}_{epoch}.png".format(i=i, epoch=epoch))
下一步,我们将创建一些分析和解决问题所需的基本参数。我们将为生成器和鉴别器定义优化器。我们可以利用 Adam 优化器和我们在上一节中研究的研究论文算法中建议的超参数。然后,我们还将着手创建我们可以相应监控的发电机和鉴频器损耗。这些损耗有一定的意义,不同于我们在以前的文章中开发的简单 GAN 架构。
generator_optimizer = keras.optimizers.Adam(
learning_rate=0.0002, beta_1=0.5, beta_2=0.9)
discriminator_optimizer = keras.optimizers.Adam(
learning_rate=0.0002, beta_1=0.5, beta_2=0.9)
def discriminator_loss(real_img, fake_img):
real_loss = tf.reduce_mean(real_img)
fake_loss = tf.reduce_mean(fake_img)
return fake_loss - real_loss
def generator_loss(fake_img):
return -tf.reduce_mean(fake_img)
最后,我们将调用并满足模型的所有需求。我们将为总共 20 个时期训练我们的模型。如果观众愿意,他们可以选择进行更多的训练。我们将定义 WGAN 架构,创建回调,并使用所有相关参数编译模型。最后,我们将继续拟合模型,这将使我们能够训练 WGAN 网络并为 MNIST 项目生成图像。
epochs = 20
# Instantiate the custom defined Keras callback.
cbk = GANMonitor(num_img=3, latent_dim=noise_dim)
# Instantiate the WGAN model.
wgan = WGAN(discriminator=d_model,
generator=g_model,
latent_dim=noise_dim,
discriminator_extra_steps=3,)
# Compile the WGAN model.
wgan.compile(d_optimizer=discriminator_optimizer,
g_optimizer=generator_optimizer,
g_loss_fn=generator_loss,
d_loss_fn=discriminator_loss,)
# Start training the model.
wgan.fit(train_images, batch_size=BATCH_SIZE, epochs=epochs, callbacks=[cbk])
在对 WGAN 模型进行了有限次数的历元训练后,我仍然能够在 MNIST 数据集上获得不错的结果。下面是我能够通过下面的模型架构生成的一些好数据的图像表示。在训练了更多的时期之后,生成器应该能够有效地生成质量好得多的图像。如果您有时间和资源,建议多运行一会儿下面的程序,以获得高效的结果。Paperspace 提供的梯度平台是运行此类深度学习程序的最佳选择之一,以实现您培训的最佳结果。
结论:
生成对抗网络正在解决当今时代的一些高度困难的问题。Wasserstein GAN 是对简单 GAN 架构的重大改进,有助于解决收敛失败或模式崩溃等问题。虽然可以说有时可能需要稍长的时间来训练,但有了最好的资源,您将始终注意到以下模型将获得高质量的结果,并有保证。
在本文中,我们了解了 Wasserstein 生成对抗网络(wgan)的理论工作过程,以及为什么它们比简单的 GAN 网络架构更有效。在着手构建用于执行 MNIST 任务的 WGAN 网络之前,我们还了解了 WGAN 网络的实施细节。我们使用梯度惩罚的概念和 WGAN 网络来产生高效的结果。建议查看者尝试相同的程序运行更多次,并执行其他实验。查看 Paperspace 的渐变平台,了解该项目的生产性重建。
在未来的文章中,我们将揭示更多的生成性对抗网络的变体和版本,它们正在被不断开发以实现更好的结果。我们还将理解强化学习的概念,并用它们开发项目。在那之前,继续发现和探索深度学习的世界吧!
关于 ONNX,每个 ML/AI 开发者都应该知道的
原文:https://blog.paperspace.com/what-every-ml-ai-developer-should-know-about-onnx/
开放神经网络交换格式(ONNYX) 是交换深度学习模型的新标准。它承诺使深度学习模型可移植,从而防止供应商锁定。让我们看看为什么这对现代 ML/AI 开发人员很重要。
The end result of a trained deep learning algorithm is a model file that efficiently represents the relationship between input data and output predictions. A neural network is one of the most powerful ways to generate these predictive models but can be difficult to build in to production systems. Most often, these models exist in a data format such as a .pth
file or an HD5 file. Oftentimes you want these models to be portable so that you can deploy them in environments that might be different than where you initially trained the model.
ONNX 概述
在高层次上,ONNX 被设计成允许框架互操作性。有许多各种语言的优秀机器学习库——py torch、TensorFlow、MXNet 和 Caffe 只是近年来非常受欢迎的几个,但还有许多其他库。
这个想法是你可以用一个工具栈训练一个模型,然后用另一个工具栈部署它进行推理和预测。为了确保这种互操作性,您必须以model.onnx
格式导出您的模型,这是 protobuf 文件中模型的序列化表示。目前 ONNX 中有对 PyTorch、CNTK、MXNet 和 Caffe2 的原生支持,但也有针对 TensorFlow 和 CoreML 的转换器。
ONNX 在实践中
让我们假设您想要训练一个模型来预测您冰箱中的一种食品是否仍然可以食用。你决定运行一组超过保质期的不同阶段的食物照片,并将其传递给卷积神经网络(CNN),该网络查看食物的图像并训练它预测食物是否仍然可以食用。
一旦你训练好了你的模型,你就想把它部署到一个新的 iOS 应用程序上,这样任何人都可以使用你预先训练好的模型来检查他们自己的食物的安全性。你最初使用 PyTorch 训练你的模型,但是 iOS 期望使用用于应用程序内部的 CoreML。ONNX 是模型的中间表示,让您可以轻松地从一个环境转移到下一个环境。
使用 PyTorch 你通常会使用torch.save(the_model.state_dict(), PATH)
导出你的模型,导出到 ONNX 交换格式只是多了一行:
torch.onnx.export(model, dummy_input, 'SplitModel.proto', verbose=True)
使用 ONNX-CoreML 这样的工具,您现在可以轻松地将预训练的模型转换为文件,然后导入 XCode 并与您的应用程序无缝集成。作为一个工作示例,请查看 Stefano Attardi 关于从头到尾构建 ML 驱动的 iOS 应用程序的这篇精彩文章。
结论
随着越来越多的深度学习框架出现,工作流变得更加先进,对可移植性的需求比以往任何时候都更加重要。ONNX 是一个强大的开放标准,用于防止框架锁定,并确保您开发的模型在长期内是可用的。
什么是数据科学?
在您阅读本文的时间内,将会产生大约 2600 万千兆字节的数据。这相当于 iTunes 上约 23.4 亿分钟的标清视频或约 130 亿本电子书。一年内,这将填满一堆 DVD,这些 DVD 可以两次到达月球,还有一些剩余。而且,这个比率还在增长。
也许你想整理成千上万的顾客产品评论,追踪流行病在机场的传播,或者从复杂的 DNA 结构中建模并做出预测。所需的数据量通常远远超出了人类人工理解的能力。那么,我们如何理解这些数据并利用它们做有意义的事情呢?
进入:数据科学。也许你听说过机器学习或大数据这样的术语。哈佛商业评论 称“数据科学家”是“21 世纪最性感的工作”大学甚至正在设计新的学位课程,专门培养分析师,以满足数据革命的需求。但是数据科学到底是什么,它有什么新意,它只是一堆炒作和夸张吗?
无论你是希望在这个领域获得博士学位,还是仅仅成为一名更懂数据的消费者,这篇文章都旨在为你的旅程提供一个起点。
现代数据科学
“数据”是指我们周围的许多定性和定量的值。数据科学的新兴概念是指使用自动化方法和程序来分析和提取海量数据中的知识。关于什么是“海量”或“大”数据,人们几乎没有一致的看法,因此研究人员通常通过观察的数量( N )来指代数据集的大小。我们可以广泛地将数据科学视为围绕获取和管理数据,以及理解这些数据并向更广泛的受众传达研究结果。
数据科学的跨学科基础可以追溯到数学、统计学和计算机科学的悠久历史。数据科学中的许多基本概念和方法都是基于这项工作,例如统计学中的线性回归、数学中的图论以及计算机科学中的人工智能。有了这些继承的方法,过去二十年数据可用性和计算能力的爆炸式增长带来了数据科学家试图应对的新挑战和机遇。
首先,数据科学家访问、获取和管理大量数据。这可以通过开发从互联网上自动挖掘数据的方法来实现,或者通过查询语言简单地存储在现有的大型数据库中或与现有的大型数据库通信来实现。
通常,仅仅有数据是不够的,我们需要理解它。这里的困难通常与规模和复杂性有关。例如,降维的任务旨在以一种更适合现有统计模型工作的方式减少所考虑的变量的数量。或者,就人工智能而言,研究自动驾驶汽车的研究人员的任务是调整现有模型或开发新方法,以便汽车能够实时吸收周围的数百万个数据点,理解这些数据,然后据此驾驶。
这些案例也是机器学习方法在数据科学任务中派上用场的例子,有时会从根本上推动手头的任务。在降维的情况下,我们可能希望找到特征的线性组合,将数据分成不同的类,然后对新观察的类进行预测。在自动驾驶汽车的情况下,正在开发视觉对象识别软件,该软件使用模拟人脑工作方式的人工神经网络。
最后,数据科学家必须交流他们的发现。事实上,一个完整的领域正在围绕数据可视化的任务发展,这有助于以直观和可访问的方式呈现发现。这里采用的方法可能也取决于你所面对的观众。
我们需要你的帮助!
我们正在寻找专注于机器学习的内容作家、爱好者和研究人员,以帮助建立我们的社区。给 hello@paperspace.com 发电子邮件,附上写作范例和教学想法
什么是 MLOps?
[2021 年 12 月 2 日更新:本文包含关于梯度实验的信息。实验现已被弃用,渐变工作流已经取代了它的功能。请参见工作流程文档了解更多信息。]
机器学习领域可以说是世界上最先进的行业之一,然而,具有讽刺意味的是,它的运作方式让人想起了 90 年代的软件开发。
这里有几个例子:大量荒谬的工作发生在孤立的台式计算机上(甚至到今天);几乎没有自动化(例如自动化测试);协作一塌糊涂;管道与一系列自制的脆弱脚本一起被破解;当涉及到部署到开发、试运行和生产的模型时,可见性实际上是不存在的;CI/CD 是一个外来概念;许多组织甚至难以定义模型版本;对模型进行严格的健康和性能监控是极其罕见的,这样的例子不胜枚举。
听起来熟悉吗?整个行业似乎都停留在研发模式,需要一条清晰的成熟之路。
当我们开始构建 Gradient 时,我们的愿景是将过去几十年的软件工程和 DevOps 最佳实践应用于机器学习行业。我们称这个为机器学习的 CI/CD。
作为一个主要由软件工程师组成的团队,这种类比似乎是不可避免的,甚至可能是显而易见的。然而,除了这里或那里的一些例外,行业还没有成熟到 ML 在实践中像软件开发一样有效和严格的程度。这是为什么呢?我们倾向于想当然地认为围绕软件行业的生态系统已经变得多么丰富。当软件团队部署每个版本时,他们依赖于许多工具,如 GitHub、CircleCI、Docker、几个自动化测试套件、健康和性能监控工具,如 NewRelic 等。相比之下,机器学习行业的可比工具少之又少。我们认为缺乏机器学习的最佳实践不是缺乏意愿的结果,而是缺乏可用的工具。
购买 vs 构建?
不仅许多公司没有能力自己开发一个完整的机器学习平台,而且世界上每家公司从头开始构建自己的平台也是极其低效的。即使是通常有空闲周期和构建内部工具的专业知识的超大规模人员,也可能不应该重新构建 GitHub。那将是多么浪费时间。公司应该专注于在内部构建他们擅长的东西,并产生某种竞争优势(知识产权或其他)。重建 GitHub 不会选中这两个框。构建机器学习平台也是如此。这就是为什么我们一直忙于构建 Gradient:我们可以一次解决问题,并立即向成千上万的组织提供新的能力,因此他们可以将 100%的资源集中在开发模型上,而不是开发工具。
引入 MLOps 的概念
机器学习操作(MLOps)是一组实践,在模型开发和部署管道中提供确定性、可伸缩性、敏捷性和治理。这种新范式专注于模型训练、调整和部署(推理)中的四个关键领域:机器学习必须是可再现的,必须是协作的,必须是可扩展的,必须是连续的。
可再生的
对于任何现代软件团队来说,查看一年前发布的单个组件(代码+依赖项)并在生产中重新部署那个确切的版本是微不足道的。相反,在今天缺乏工具的情况下,重建一年前的机器学习模型(精确度在几个百分点以内)的可能性通常几乎是不可能的。这需要具有覆盖所有输入的可追溯性:所使用的数据集、机器学习框架的版本、代码提交、依赖性/包、驱动程序版本和低级库(如 CUDA 和 cuDNN)、容器或运行时、用于训练模型的参数、训练模型的设备以及一些特定的机器学习输入,如层权重的初始化。
The code diffing tool in a Gradient Experiment run
无论是出于监管目的,还是您的组织仅仅重视记录其开发的内容并提供给客户和内部利益相关方,再现性都是至关重要的。通往可再生机器学习的道路可以被认为是从特定方法向更确定的工作方式的哲学转变。
合作的
从事 python 项目并在孤立的工作站(甚至是 AWS 实例)上生成模型是一种反模式。如果你是一个人的 ML 团队,你可以摆脱这一点,但是当你将一个模型投入生产或者有几个贡献者试图一起工作时,这个策略很快就失败了。随着模型数量和复杂性的增加,协作环境的缺乏变得尤其成问题。
从战术上来说,协作始于拥有一个统一的中心,在这里可以跟踪所有的活动、沿袭和模型性能。这包括训练运行、Jupyter 笔记本、超参数搜索、可视化、统计指标、数据集、代码引用和模型工件的存储库(通常称为模型存储库)。对团队成员、审计日志和标签的细粒度权限进行分层也很重要。
最终,组织范围的可见性和实时协作对于现代 ML 团队来说是必不可少的,就像它们对于软件团队一样。这种方法应该跨越模型生命周期的每个阶段,从概念和研发到测试和 QA,一直到生产。
可攀登的
这一条有点罗嗦,但概念很简单:与软件工程相反,实践中的机器学习需要大量(有时是海量)的计算能力(和存储),并且通常需要像专用硅这样的深奥的基础设施(例如 GPU、TPU 和进入市场的无数其他芯片)。机器学习工程师需要一个基础设施抽象层,它可以轻松调度和扩展工作负载,而不需要多年的网络、Kubernetes、Docker、存储系统等方面的经验。这些都是主要的干扰。
基础设施自动化价值的一些示例:
- 多云:机器学习平台应该使内部训练模型变得简单,并将该模型无缝部署到公共云(反之亦然)。
- 扩展工作负载:随着计算需求的增加,培训或调整跨多个计算实例的模型变得至关重要。通过 MPI 或 gRPC 消息总线将共享存储卷连接到运行在异构硬件上的分布式容器群,这不是您希望机器学习工程师花费时间去做的事情。
最终,当 ML 团队可以完全自主地操作并拥有整个堆栈时,他们会更加高效和敏捷。数据科学家需要访问按需计算和存储资源,以便他们可以在训练、调整和推理阶段更快地迭代。借助 MLOps,整个流程不受基础架构限制,可扩展,并最大限度地降低了数据科学家的复杂性。
连续的
对于一个生活在自动化前沿的行业(例如聊天机器人到自动驾驶汽车),ML 模型的生产几乎没有自动化。
许多年前,软件开发行业围绕着一个称为 CI/CD 的过程进行整合,在这个过程中,由工程师进行的代码合并触发了一系列自动化步骤。在基本管道中,应用程序被自动编译、测试和部署。部署后,通常需要对部署的应用程序进行自动运行状况和性能监控。诸如此类的概念对于应用程序的可靠性和开发速度至关重要。不幸的是,在机器学习行业中,许多这些概念都没有对等物。这导致了巨大的效率低下,因为高薪数据科学家花费大量时间处理重复性和乏味的任务,这些任务是在容易出错的过程中手动执行的。查看我们在 CI/CD 上关于机器学习的帖子,了解更多关于这在实践中是什么样子的。
包扎
最终,从概念到生产并交付商业价值所需的时间是该行业的一个主要障碍。这就是为什么我们需要优秀的 MLOps 来标准化和简化生产中 ML 的生命周期。
DevOps 作为一种实践确保了软件开发和 IT 运营生命周期是高效的、有良好文档记录的、可伸缩的,并且易于故障排除。MLOps 整合了这些实践,以高速交付机器学习应用和服务。
渐变有什么新的?
原文:https://blog.paperspace.com/whats-new-in-gradient-june-2018/
我们一直在努力将 Gradient 开发成一个强大且可扩展的深度学习平台。以下是我们最近添加的一些内容的综述:
新工作页面
除了主作业页面上的摘要视图之外,我们还添加了一个独立页面,其中包含各种新功能。
汇总视图
要深入查看新职务详细信息页面,请单击职务名称旁边的箭头图标。Job details 页面包括关于您的作业的所有内容:参数、环境、日志、指标和代码都在一个地方。
新职务详细信息页面
JupyterLab
JupyterLab 是 Jupyter 的下一代基于网络的用户界面。这个新版本包括多个选项卡式文档,一个改进的终端,可定制的快捷方式等。这里有一个概述。我们添加了 数据科学堆栈 和 R 堆栈 容器作为基础容器选项。
公共工作
建立一个正常运行的环境可能是一项艰巨的任务。使用公共作业,您可以轻松打包您的作业并与其他人共享。只需点击右上角的按钮,将作业公开,这样任何人都可以在自己的帐户中克隆它。您可以随时将职务转换为私人职务。
这里有一个公职的例子
笔记本电脑中可访问的持久存储
Jupyter 是一个很好的数据管理环境,你现在可以用它来管理你的永久存储器。永久存储将自动安装到您帐户中的每个笔记本(和作业)上。只要旋转笔记本,你就会看到/storage
目录。在这里你可以轻松上传数据,移动文件等。
作业生成器用户界面
刚接触渐变或者不喜欢使用命令行?尝试新的图形化作业构建器,这是一个逐步构建作业的用户界面。顶部的部分包括几个示例项目,只需点击几下就可以运行。更多文档可在这里找到
工作指标
了解你正在训练的模型的性能是很重要的。准确性、损失和验证等指标通常用于此目的。我们在 Jobs 页面中添加了一个部分来绘制这些指标。由于培训可能需要数小时、数天甚至数周时间,因此实时跟踪指标非常重要。只需在您的代码中添加几行代码,我们将解析输出并将它们转换成指标。一旦模型开始训练,图表将开始绘制,并在工作完成后可用于参考/比较。
# Initializing charts
print('{"chart": "loss", "axis": "Iteration"}')
print('{"chart": "accuracy", "axis": "Iteration"}')
# Graph Loss and Accuracy
print('{"chart": "loss", "x": <value>, "y": <value>}')
print('{"chart": "accuracy", "x": <value>, "y": <value>}')
下面是一个典型输出的例子:
指南和示例代码可以在这里找到
公共职位示例
机器学习框架比较
原文:https://blog.paperspace.com/which-ml-framework-should-i-use/
我们需要你的帮助!
我们正在寻找专注于机器学习的内容作家、爱好者和研究人员,以帮助建立我们的社区。给 hello@paperspace.com 发电子邮件,附上写作范例和教学想法
When taking the deep-dive into Machine Learning (ML), choosing a framework can be daunting. You've probably heard the many names/acronyms that make-up the constellation of frameworks, toolkits, libraries, data sets, applications etc. but may be curious about how they differ, where they fall short and which ones are worth investing in. Since the field and surrounding technologies are relatively new, the most common concern amongst new users is understanding which of these frameworks has the most momentum. This article summarizes their major differences and attempts to contextualize them within a broader landscape.
TensorFlow 由谷歌大脑团队开发,用于进行机器学习和深度神经网络的研究。谷歌最近从 Torch 转移到 TensorFlow,这对其他框架是一个打击——特别是 Torch 和 Theano。许多人将 TensorFlow 描述为更现代版本的 Theano,因为多年来我们已经学到了许多关于这一新领域/技术的重要经验。
TensorFlow 的设置相对容易,并为初学者提供教程,涵盖了神经网络的理论基础和实际应用。TensorFlow 比 Theano 和 Torch 慢,但这一问题目前正由谷歌和开源社区正面解决。 TensorBoard 是 TensorFlow 的可视化模块,为您的计算管道提供直观的视图。深度学习库 Keras 最近被移植到 TensorFlow 上运行,这意味着用 Keras 编写的任何模型现在都可以在 TensorFlow 上运行。最后,值得一提的是,TensorFlow 可以在多种硬件上运行。
- GPU 加速:是
- 语言/接口:Python、Numpy、C++
- 平台:跨平台
- 维护者:谷歌
Theano 起源于 2007 年蒙特利尔大学在著名的学习算法研究所。Theano 功能强大,速度极快,非常灵活,但通常被认为是一个低级框架(例如,错误消息特别神秘/无用)。因此,raw Theano 更像是一个研究平台和生态系统,而不是一个深度学习图书馆。它经常被用作高级抽象库的底层平台,这些抽象库提供了简单的 API 包装器。一些更受欢迎的图书馆包括 Keras 、千层面和街区。ano 的一个缺点是多 GPU 支持仍然需要一个解决方案。
- GPU 加速:是
- 语言/接口:Python、Numpy
- 平台:Linux、Mac OS X 和 Windows
- 维护者:蒙特利尔大学 MILA 实验室
在所有常见的框架中,Torch 可能是最容易上手和运行的,尤其是当你使用 Ubuntu 的时候。Torch 最初于 2002 年在 NYU 开发,被脸书和 Twitter 等大型科技公司广泛使用,也得到了 NVIDIA 的支持。Torch 是用一种叫做 Lua 的脚本语言编写的,这种语言很容易阅读,但不像 Python 这样的语言那样常见。有用的错误消息、大量的示例代码/教程以及 Lua 的简单性使得 Torch 成为一个很好的起点。
- GPU 加速:是
- 语言/界面:Lua
- 平台:Linux、Android、Mac OS X、iOS 和 Windows
- 维护者:罗南、克莱门特、科雷和索史密斯
Caffe 是利用卷积神经网络(CNN)为图像分类/机器视觉开发的。Caffe 最出名的可能是模型动物园,一套预先训练好的模型,你不用写任何代码就可以使用。
Caffe 的目标是那些建筑应用,而 Torch 和 Theano 是为研究量身定制的。Caffe 不适合文本、声音或时间序列数据等非计算机视觉深度学习应用。Caffe 可以在各种硬件上运行,在 CPU 和 GPU 之间的切换是通过一个标志来设置的。Caffe 比 Theano 和 Torch 慢。
- GPU 加速:是
- 语言/界面:C、C++、Python、MATLAB、CLI
- 平台:Ubuntu,Mac OS X,实验性 Windows 支持
- 维护者: BVLC
微软认知工具包(Cognitive Toolkit),又称 CNTK,是微软开源的深度学习框架。尽管 CNTK 也可以用于图像和文本训练,但在语音社区中比在一般的深度学习社区中更为人所知。CNTK 支持多种算法,如前馈、CNN、RNN、LSTM 和序列到序列。它运行在许多不同的硬件类型上,包括多个 GPU。
- GPU 加速:是
- 语言/界面:Python、C++、C#和 CLI
- 平台:Windows、Linux
- 维护者:微软研究院
其他框架
还有其他几个深度学习框架,包括 MXnet 、 Chainer 、 BidMach 、 Brainstorm 、 Kaldi 、 MatConvNet 、 MaxDNN 、 Deeplearning4j 、 Keras 、千层面、 Leaf
GitHub 活动对比:
使用 OpenAI 和 Flask 中的 Whisper 创建您自己的语音转文本应用程序
原文:https://blog.paperspace.com/whisper-openai-flask-application-deployment/
语音转文本已经迅速成为我们日常生活中深度学习技术的一个更突出的用例。从自动呼叫系统到语音助手,再到简单的搜索引擎集成,这种功能可以大大改善用户体验。这种增加的多功能性已经成为这些技术中的一部分,并且是使用它们的体验的同义词。
多年来,这种技术很大程度上是专有的,这是有充分理由的。这是一场创造最佳 NLP 模型的竞赛,军备竞赛仍在继续。语音转文本的例子包括流行的谷歌翻译 API 和 AWS 转录。其他的内置在流行的应用程序中,比如苹果的 Siri。
为了满足这些工具,来自 Open AI 的快速普及的 Whisper 应运而生,它向用户免费提供与生产级模型相当的功效,并有大量预先训练的模型可供利用。在本教程中,我们将详细了解 Whisper 的功能和架构。然后,我们将进入一个编码演示,展示如何在渐变笔记本中运行强大的语音到文本模型,最后,我们将在一个简单的 Flask 应用程序中使用渐变部署来设置相同的设置,从而结束我们的教程。
低语
Whisper 接受了 680,000 小时的多语言和多任务监督数据训练,这些数据是从网络上收集的。他们发现,他们的数据集选择和训练为模型提供了强大的力量,以处理口音和背景噪音等可能干扰推断的特性。在这一节中,我们将详细探讨这种训练给我们的模型带来的优势,以及该模型如何根据这些数据进行训练。
能力
Whisper 的功能本质上可以归结为 3 个主要的关键功能:语音到文本的转录、语言识别和一种语言到另一种语言的翻译。
抄本
[Source]
转录是理解并记下音频片段中感知到的话语的能力。从上面的图中可以看出,Whisper 的性能与 5 家未命名公司的专有音频语音识别(ASR)系统相当。这种性能来自于观察到他们可以通过连续转录 30 秒长的整体片段来有效地执行长音频序列的缓冲转录,并通过根据模型预测的时间戳移动窗口来排序。他们发现,通过基于模型预测的重复性和对数概率的波束搜索和温度调度,他们能够准确可靠地转录长音频序列。
在实践中,该模型能够准确有效地将任何长度的文本从音频转录为字符串格式。
语言识别
研究小组通过微调 VoxLingua107 数据集上的原型模型来集成他们的模型,从而创建了一个音频语言检测器。这样做是为了确保音频样本中大声朗读的语言与输出抄本的语言相匹配。使用的 CLD2 模型的版本没有达到竞争识别模型的标准,但作者将此归因于缺乏用于相关测试的训练数据。测试中使用的 20 种语言没有训练数据,所以这是一个合乎逻辑的结论。
翻译
翻译和转录的能力可能是 Whisper 最有趣的功能之一。这种技术在现代世界的应用是无穷无尽的,尤其是在我们进一步全球化的时候。也就是说,Whisper 并不能翻译所有语言的例子。
下图显示了使用large
模型的 Fleurs 数据集语言的 WER(单词错误率)细分,该模型是其预训练样本模型中性能最好的。这衡量了在所有翻译中有多少预测的单词是不正确的,并让我们很好地了解了该模型在各种任务中的表现。与其他模型和数据集相对应的更多 WER 和 BLEU 分数可在论文的附录 D 中找到。
正如我们所看到的,Fleurs 语言数据集的能力有很大的差异。该模型在最接近英语的语言上表现最佳,包括罗曼语和日耳曼语。这是显而易见的,因为这些语系和英语有许多共同的词汇和特征。最高的 WER 来自信德、肖纳和阿姆哈拉语,代表了更杰出的印度雅利安、班图和闪米特语系。鉴于这些语言和英语在口语观察上的显著差异,这也是合乎逻辑的。
这表明,虽然 Whisper 是一个强大的工具,但它并不代表一个通用的翻译器。像任何深度学习框架一样,它需要在训练期间接触来自主题数据源的示例,以真正实现预期的功能。
体系结构
Source https://github.com/openai/whisper
Whisper 模型的核心是基于经典的编码器-解码器转换器模型。它接收音频-文本数据对来学习预测输入音频样本的文本输出。在训练之前,音频数据全部被重新采样到 16,000 Hz。在训练期间,首先,该模型使用 25 毫秒的窗口和 10 毫秒的步长,生成音频数据的 80 通道对数幅度 Mel 谱图表示。
然后,数据被全局缩放到-1 和 1 之间,整个预训练数据集的平均值约为零。编码器使用由滤波器宽度为 3 的两个卷积层和 GELU 激活函数组成的小词干处理该输入表示,其中第二个卷积层的步幅为 2。正弦位置嵌入然后被添加到 stem 的输出。最后,将变换器预激活残余块应用于变换后的数据,并将最终层归一化应用于编码器输出。然后,解码器反转该过程,以使用学习到的位置嵌入和绑定的输入-输出令牌表示来将编码的单词重新创建为文本。
这种流水线是这样设计的,以便除了转录之外的任务可以附加到这个过程,但是某种形式的任务规范是控制它所必需的。他们设计了一种简单的格式,将所有任务和条件信息指定为解码器的输入标记序列。这些专用的标记,包括一个用于检测到的语言和手边的任务的标记,指导解码器生成输出的目的。这使得该模型相当巧妙的能够处理各种各样的 NLP 任务,而几乎不改变其架构。
民众
在使用 Whisper 时,我们很幸运能够使用 9 个预训练模型来进行我们自己的推断,不需要额外的培训。
这些型号在性能上差异很大,因此请确保根据您的使用案例和 GPU 能力来选择型号。您可以预期参数的数量越多,您的模型推断就越通用和有能力。此外,带有.en
后缀的型号在所有英语任务上的表现都更好。
现在让我们看看如何在我们的两个渐变工具中使用这些不同的模型:笔记本和部署。
笔记本
由于 Open AI 提供的简洁和编写良好的管道,在渐变笔记本中运行 Whisper 很容易。遵循这个指南,你将能够在很短的时间内翻译和转录多种语言的语音样本。
首先,让我们用一些安装来设置工作区。
## Install whisper package
pip install git+https://github.com/openai/whisper.git
## Should already be up to date
apt update && apt install ffmpeg
一旦完成,运行推理代码就简单了。让我们浏览一下 Github 项目提供的示例推理代码,看看如何才能最好地将 Whisper 与 Python 结合使用。
import whisper
model = whisper.load_model("base")
首先,我们导入 whisper 包并加载到我们的模型中。使用表 1 中指定的标签,您可以更改我们在调用whisper.load_model()
时使用的模型类型。我们建议在轻量级应用中使用tiny
型号,如果精确度非常重要,则使用large
型号,如果不确定,则使用base
型号。
# load audio and pad/trim it to fit 30 seconds
audio = whisper.load_audio("audio.mp3")
audio = whisper.pad_or_trim(audio)
接下来,我们使用whisper.load_audio()
载入音频数据。然后,我们使用 pad_or_trim()方法来确保样本具有正确的推断形式。
# make log-Mel spectrogram and move to the same device as the model
mel = whisper.log_mel_spectrogram(audio).to(model.device)
然后,我们使用我们的音频数据来生成音频的 log-Mel 声谱图。这将作为我们对编码器模块的输入。一旦生成,我们就把它从 CPU 移到 GPU,所以它和模型在同一个设备上。
# detect the spoken language
_, probs = model.detect_language(mel)
print(f"Detected language: {max(probs, key=probs.get)}")
然后,我们使用内置的detect_language()
方法来检测语音的输入语言。在运行转录/翻译以及 WER 速率图之前,我们可以使用这些信息来推断我们的模型将做得有多好。这在尝试转录特别长的音频片段之前运行会很有用。
# decode the audio
options = whisper.DecodingOptions()
result = whisper.decode(model, mel, options)
# print the recognized text
print(result.text)
最后,我们使用whisper.DecodingOptions()
声明解码语音的任务和参数。在这里,我们可以声明我们希望使用模型进行翻译或转录,并另外输入任何其他数量的采样选项。然后,我们将模型、mel 和选项输入到whisper.decode()
方法,该方法将语音转录(或者翻译然后转录)成文本字符串。这些然后以 3 种格式保存为。txt,。VTT 还有。SRT 档案。
总之,这些代码可以在一个单元格中运行,如上图所示。
部署
我们已经创建了一个small
预训练模型的示例部署,供您使用,并基于它开发您自己的应用程序。这个支架本身改编自一个类似的面部修复模型 GFPGAN 的应用程序。在本节中,我们将演练使用梯度部署启动该应用程序的两种方法:梯度 CLI 和部署 GUI。我们建议使用后者,您很快就会明白为什么。
有关从头开始创建自己的渐变部署的更多信息,请务必阅读教程,了解我们如何制作这个用于启动具有渐变部署的深度学习 Flask 应用程序的脚手架。
渐变客户端
当使用 Gradient CLI 时,我们需要首先采取一些步骤来设置我们的本地环境。具体来说,我们需要安装这个包,如果还没有安装的话,并把我们个人的 API 密匙附加到安装中。
为此,请在您的浏览器上登录您的 Gradient 帐户,将鼠标导航到右上角的下拉菜单,然后选择“团队设置”。然后,打开“API 密钥”选项卡,并创建一个 API 密钥。保存此 API 键以备后用,并导航回渐变控制台。在那里,为您选择的团队中的部署选择或创建一个项目空间。将项目 ID 保存在保存 API 密钥的相同位置。
然后,我们将通过在本地计算机的终端中输入以下内容来登录我们的帐户:
pip install gradient
gradient apikey <your api key here>
完成后,我们需要将 YAML 规范加载到计算机上,并运行命令来启动部署。我们可以这样做,首先克隆gradient-ai/whisper
分支,并导航到那个目录。
git clone https://github.com/gradient-ai/whisper
cd whisper
最后,我们可以在 YAML 文件的指导下创建并运行部署。
gradient deployments create --projectId <your project id of choice> --name <name of deployment> --spec spec.yaml
这将创建一个 API 端点,您可以通过 web 浏览器访问它。
梯度部署 GUI
启动演示部署的下一种方法是推荐的方法:使用部署 GUI。
要开始,请登录您的帐户并打开您选择的项目。然后,用鼠标导航到“部署”选项卡。进入后,点击“创建”进入部署创建页面。
一旦进入创建页面,我们就可以使用与使用 Gradient CLI 时从 spec.yaml 文件输入的数据相同的数据。首先,我们指定部署的名称,在本例中为“Whisper”
接下来,向下滚动到“选择一台机器”,然后选择您喜欢的 GPU 机器。这应该很容易在我们的任何 GPU 上运行,因为它们每个都提供 8+ GB 的 VRAM,但也有按需扩展的选项。如果您打算将应用程序的当前模型更改为Large
版本,这可能是需要考虑的事情。
最后,导航到“容器”部分,在“图像”框中填入paperspace/whisper-deploy:v1.1
,在“端口”框中填入5000
。这个图像预先打包了运行模型所需的各种包,以及为small
模型本身预先训练的模型权重。
Click the URL after the 'Endpoint' section to view your application's deployment.
填写完所有内容后,您可以使用页面底部的按钮启动您的部署。大约一分钟后,部署应该完成构建,然后您可以选择 API 端点来打开您的部署。
使用部署应用程序
https://blog.paperspace.com/content/media/2022/10/upload.mp4
一旦您的模型部署正在运行,您可以通过在 GUI 的“详细信息”部分中单击“端点”字段后面的链接来访问它。或者,当部署构建完成后使用 CLI 时,这也将在终端中自动打印出来。
一旦你打开链接,你将会看到一个简单的上传音频文件的界面。当你选择文件,并点击应用程序中的“上传”,该模型将开始转录你的文本。当模型预测完成时,应用程序将在输出页面中将文本输出为 HTML。
改变部署
在这个阶段,改变这种部署会很复杂,但是这个框架的未来版本旨在使其更加灵活。尽管如此,我们还是邀请读者尽可能多地尝试这个应用程序。这个脚手架是设计用来扩充和改进的。
目前要开始,可能需要分叉 Whisper Github repo 的 Gradient-AI fork 。然后,切换到该目录并执行以下步骤:
- 将 docker 文件的第 17 行和第 20 行编辑为您的新 fork 的 URL 和您想要使用的模型类型(模型的 URL 可以在
whisper/__init__.py
中找到) - 编辑
whisper/transcribe.py
的第 313 行,以包含新模型运行的参数。如果您愿意,这将允许您添加--task translation
标签,并将型号从small
更改为您的新型号类型。 - 在“app.py”中根据需要对应用程序 HTML 进行其他更改
- 用这个新目录构建一个新的 Docker 映像,并推送到 Dockerhub
- 使用新容器启动您的部署
结束语
在本教程中,我们浏览了 Open AI 的 Whisper 的模型功能和架构,然后展示了两种方法,用户可以通过我们的渐变笔记本和部署演示在几分钟内充分利用模型。
Whisper 是一个令人难以置信的模型,并且是一个非常棒的测试应用程序。底层技术极具创新性,这使得它能够通过一个极其轻量级而又健壮的模型进行准确的转录和翻译。请务必浏览原始的论文和 Github 项目页面,以获取关于创建这个令人敬畏的模型的研究的更多信息。
为什么 Paperspace 是企业桌面的未来
原文:https://blog.paperspace.com/why-paperspace-is-the-future-of-enterprise-desktops/
在云时代,内部 VDI 就像死了一样。当然,“本地”VDI 将会存在更长一段时间,就像所有传统技术一样,但它仍处于生命支持阶段。为什么?因为云的存在有一个很好的理由,让 VDI 保持活力的唯一原因是一个简单的事实,即除了在内部运行虚拟桌面之外,没有多少其他选择。
然而,VDI 非常强大。[98%的财富 500 强企业运行虚拟桌面](财富 100 强企业大量采用虚拟桌面(http://virtual ization . info/en/news/2009/08/more-of-10-of-Fortune-500-uses . html)因为它们在许多方面都远远优于传统计算机:它们更安全、更易于管理,并且可以按需扩展和缩减。对于大多数企业来说,关键在于设置 VDI 并保持其运行。
想象一下,VDI 固有的所有复杂性都被抽象到一个简单的界面中,您只需点击一个按钮即可按需添加虚拟机。
这就是 Paperspace 要解决的问题:没有复杂性的 VDI 的强大功能。我们负责服务器、网络设备、虚拟化、灾难恢复、负载平衡、冗余、许可、防火墙等。并将其置于优雅的网络界面之后。这是第一次,任何规模的企业都可以获得通常为大公司保留的技术。事实上,它非常易于使用,我们 50%的客户无需 it 人员的参与即可设置和管理 Paperspace。
在过去的 10 年里,公司一直在远离本地模式。如今,随着公共云的成熟,你会疯了一样去安装自己的服务器。即使是最大的公司也在把所有东西都转移到云上。如果你正在考虑虚拟化,除非数据中心是你的核心竞争力,否则你不应该走这条路。
这就是今天存在的情况:
Citrix/VMware 内部模式
庞大的基础设施开支、服务器维护、复杂的网络、VPN...
This is what the future looks like: ## Welcome to Paperspace Click button, get machine
为什么 PyTorch 是未来的深度学习框架
原文:https://blog.paperspace.com/why-use-pytorch-deep-learning-framework/
你正在寻找一个高效的现代框架来创建你的深度学习模型吗?看看 PyTorch 就知道了!
在本文中,我们将介绍 PyTorch,是什么让它如此有优势,以及 PyTorch 与 TensorFlow 和 Scikit-Learn 相比如何。然后,我们将通过构建一个线性回归模型来了解如何使用 PyTorch,并使用它来进行预测。
让我们开始吧。
PyTorch 简介
PyTorch 是脸书在 2016 年 10 月制作的机器学习框架。它是开源的,基于流行的 Torch 库。PyTorch 旨在为深度神经网络实现提供良好的灵活性和高速度。
PyTorch 与其他深度学习框架的不同之处在于,它使用了动态计算图。静态计算图(如 TensorFlow 中使用的图)是在运行之前定义的,而动态图是通过正向计算“即时”定义的。换句话说,在每一次迭代中,图形都是从零开始重建的(更多信息,请查看斯坦福 CS231n 课程)。
PyTorch 的优势
虽然 PyTorch 有很多优点,但这里我们将重点介绍几个核心优点。
1.蟒蛇的本性
从下图中可以看出,Python 是过去 5-10 年中发展最快的编程语言之一。大多数机器学习和人工智能相关的工作都是使用 Python 完成的。PyTorch 是 Python 的,这意味着 Python 开发人员在用 PyTorch 编码时应该比用其他深度学习框架更舒服。也就是说,PyTorch 也有一个 C++前端。
如果需要,您还可以使用您喜欢的 Python 包(如 NumPy、SciPy 和 Cython)来扩展 PyTorch 功能。
Source: Stack Overflow
2.简单易学
与 Python 语言一样,PyTorch 被认为比其他深度学习框架相对更容易学习。主要原因是因为它简单直观的语法。
3.强大的社区
尽管 PyTorch 是一个相对较新的框架,但它已经非常迅速地发展了一个专门的开发人员社区。不仅如此,PyTorch 的文档非常有条理,对开发人员很有帮助。
4.易于调试
Photo by Kelsey Krajewski / Unsplash
PyTorch 与 Python 深度集成,因此许多 Python 调试工具可以很容易地与它一起使用。例如,Python pdb 和 ipdb 工具可用于调试 PyTorch 代码。PyCharm 的调试器也可以与 PyTorch 代码无缝协作。
PyTorch vs TensorFlow
- 动态与静态:虽然 PyTorch 和 TensorFlow 都在张量上工作,但是 PyTorch 和 Tensorflow 之间的主要区别是 PyTorch 使用动态计算图,TensorFlow 使用静态计算图。也就是说,随着 TensorFlow 2.0 的发布,已经出现了一个向急切执行的重大转变,远离了静态图形计算。TensorFlow 2.0 中的急切执行会立即计算操作,而无需构建图形。
- 数据并行: PyTorch 使用 Python 的异步执行来实现数据并行,而使用 TensorFlow 却不是这样。使用 TensorFlow,您需要手动配置每个数据并行操作。
- 可视化支持: TensorFlow 有一个非常好的可视化库,叫做 TensorBoard。这种可视化支持有助于开发人员很好地跟踪模型训练过程。PyTorch 最初有一个名为 Visdom 的可视化库,但此后也为提供了对 TensorBoard 的全面支持。PyTorch 用户可以利用 TensorBoard 在 TensorBoard UI 中记录 PyTorch 模型和指标。PyTorch 模型和张量都支持标量、图像、直方图、图形和嵌入可视化。
- 模型部署: TensorFlow 使用名为 TensorFlow serving 的框架对部署模型提供了强大的支持。这是一个使用 REST 客户端 API 的框架,用于在部署后使用模型进行预测。另一方面,PyTorch 没有提供像使用 REST Client 在 web 上部署模型那样的框架。
PyTorch vs Scikit-Learn
深度学习 vs 机器学习: Sklearn,或 scikit-learn ,是一个主要用于机器学习的 Python 库。Scikit-learn 很好地支持传统的机器学习功能,如分类、降维、聚类等。Sklearn 构建在 NumPy、SciPy 和 Matplotlib 等 Python 库之上,对于数据分析来说简单而高效。然而,虽然 Sklearn 主要用于机器学习,但 PyTorch 是为深度学习而设计的。Sklearn 对定义算法很好,但不能真正用于深度神经网络的端到端训练。
易用性:毫无疑问 Sklearn 比 PyTorch 好用。与 Sklearn 相比,在 PyTorch 中实现相同的代码,你需要编写更多的代码。
易于定制:不言而喻,如果你想为机器学习中的特定问题定制你的代码,PyTorch 在这方面会更容易使用。Sklearn 相对来说比较难定制。
PyTorch 中的张量
PyTorch 张量类似于 NumPy 阵列,具有额外的功能,因此可以在图形处理单元或 GPU 上使用,以加速计算。
标量是零维数组,例如数字 10 就是一个标量。
向量是一维数组,例如[10,20]是一个向量。
矩阵是二维数组。
张量是三维或多维数组。
然而,通常的做法是将向量和矩阵分别称为一维和二维张量。
PyTorch 张量和 numpy 数组的主要区别在于 PyTorch 张量可以在中央处理器和图形处理器上运行。如果你想在图形处理器上运行 PyTorch 张量,你只需要将张量转换成 CUDA 数据类型。
CUDA 代表计算统一设备架构。CUDA 是 Nvidia 创建的并行计算平台和应用编程接口模型。它允许开发人员使用支持 CUDA 的图形处理单元。
创建回归模型
线性回归是最流行的机器学习算法之一,因为它基于简单的数学,所以非常适合实现。线性回归基于直线的数学方程,写作 y = mx + c,其中 m 代表直线的斜率,c 代表 y 轴截距。
Linear regression residuals and data analysis
正如你在上面的图像中所看到的,我们用红点表示数据点,我们试图拟合一条线来表示所有的数据点。请注意,所有红色数据点可能不在直线上,但是我们的目标是找到最适合所有数据点的直线。找到最佳拟合直线实质上意味着找到斜率 m 和截距 c ,因为这两个参数可以定义唯一的直线。注意,这里 x 叫自变量,y 叫因变量。
线性回归的假设
线性回归有五个重要假设。
- 线性回归假设自变量和因变量之间的关系为线性。
- 数据为正态分布
- 独立变量(如果多于一个)彼此不相关。
- 数据中没有自相关。当残差相互依赖时,可以观察到自相关。例如,对于股票价格数据,价格取决于先前的价格。
- 数据是同方差的,这意味着残差在回归线上是相等的。
构建模型的先决条件
您可以使用下面的命令安装 numpy、pandas 和 PyTorch。
pip install numpy
pip install pandas
conda install pytorch torchvision cudatoolkit=10.1 -c pytorch
请注意,安装 PyTorch 后,您将能够导入 Torch,如下所示。
导入库
PyTorch Linear Regression Loading data
定义模型
让我们通过创建一个名为 MyModel 的类来开始定义我们的模型,如下所示。在 MyModel 类中,我们需要定义两个名为 forward 和 init 的方法。之后,我们将创建 MyModel 类的实例,这里的实例名是 my_lr_model。
Defining Linear regression model in PyTorch
训练模型
现在我们准备好训练模型了。在我们开始训练之前,我们需要定义损失函数(这里是ms loss)、优化器(这里是 SGD 或随机梯度下降),然后我们必须分配学习率(在这种情况下为 0.011)和动量(0.89)。
学习速率也称为步长,是一个超参数,它决定每次模型权重改变时,机器学习模型相对于计算误差的改变程度。
动量是一个超参数,它加快了模型训练和学习的速度,从而加快了模型的收敛速度。默认情况下,动量设置为零。
在 PyTorch 中,变量是一个张量的包装器。因此,Variable 几乎支持所有由张量定义的 API。
一旦定义了这些参数,我们需要使用 for 循环来启动 epochs。注意损失值如何随着每个时期而变化。
Training the model of Linear Regression using PyTorch
预言;预测;预告
训练完模型后,下一步是预测测试输入的值。这里我们考虑输入值为 4.0,我们得到的预测(输出)为 21.75。
注意,为了将输入值输入到模型中,我们需要使用 torch 将浮点值转换成张量格式。张量法。因此,我们测试了我们的模型的工作情况,并给出了输出。
Predicting the value using Linear regression model in PyTorch
概括起来
在本教程中,我们了解了 PyTorch 是什么,它的优势是什么,以及它与 TensorFlow 和 Sklearn 相比如何。我们还讨论了 PyTorch 中的张量,并研究了如何构建一个简单的线性回归模型。
感谢阅读!如果您有任何问题或讨论要点,请访问 Paperspace 社区。
为什么您不能在公共云中托管您的 VDI
原文:https://blog.paperspace.com/why-you-cant-host-your-vdi-in-the-public-cloud/
似乎每天你都会读到另一家公司关闭其数据中心并转向 AWS 。网飞的运营工程总监乔希·埃文斯最近讨论了如何“从运营的角度来看,网飞的一切都依赖于 AWS……”
公司愿意支付巨额溢价来获得 AWS 等大型公共云提供商的产品范围、全球可用性和似乎永无止境的规模。从公司的角度来看,这消除了花费宝贵的工程时间在世界各地建设和管理数据中心的需要,即使这意味着在硬件本身上支付巨大的溢价。事实上,Paperspace 的大部分网络服务都是在 AWS 上运行的——像我们这样的公司承担这样的风险、工程工作和资本支出是没有意义的。
公共云的增长:
AWS 是托管数据库和 web 服务的绝佳平台,但以下是您无法在 AWS 上运行 VDI 的原因:
在公共云中托管您的 VDI 将比购买传统计算机花费10 倍。就财务可行性而言,公共云似乎是托管服务器基础设施的明显未来...事情是这样的:对于一个公司来说,构建自己的弹性豆茎、S3、数据库服务等可能没有意义。,但支付 10 倍的溢价只是为了利用公共云的优势?不会吧。外包桌面和外包 web 服务的经济性是不一样的,这就是将您的 VDI 部署迁移到公共云的概念不成立的地方。由于这个原因,VDI 停留在“内部”世界,而几乎所有其他企业技术都在向公共云迁移。
这是我们着手解决的 VDI 的许多固有问题之一。Paperspace 提供了与公共云相同的可靠性和规模经济,但价格合理。事实上,我们是历史上第一家让 VDI 比传统计算机更经济实惠的公司,尤其是在考虑总拥有成本的情况下。
以下是我们的服务与最大的公共云提供商 EC2 的对比:
<td>$720 for 3 years</td>
<td>$6,588 for 3 years</td>
<td>$3,204 for 3 years</td>
</tr>
<tr>
<td rowspan="2"><strong>CAD/Gaming machine</td>
<td>$60 month</td>
<td>$552 month</td>
<td>N/A</td>
</tr>
<tr>
<td>$2,160 for 3 years</td>
<td>$19,880 for 3 years</td>
<td>$10,310 for 3 years</td>
</tr>
| | 纸张空间 | EC2 (每小时) | EC2 (3 年承诺) |
| 普通机 | 每月 20 美元 | 每月 183 美元 | 不适用的 |
公共云似乎是托管您的 VDI 的完美场所:可以无限制地访问全球分布的高质量基础架构,并获得世界上最好的专家来维护它。但是,如果定价模式与托管计算机根本不兼容,所有这些都是无关紧要的。我们喜欢 AWS,但它不适合运行您的 VDI。
为什么您应该将您的公司迁移到云
原文:https://blog.paperspace.com/why-you-should-move-your-company-to-the-cloud/
好吧,你对虚拟桌面很感兴趣,但你仍然不相信它的好处。以下是您应该迁移到云的五个原因:
1)远程访问
移动不是一个时髦的词。在当今的经济环境下,远程或移动工作是必须的。这通常意味着设置复杂而缓慢的 VPN 或一堆笨重的远程桌面应用。我们每天都与客户交谈,他们几乎已经放弃了远程工作,因为这是一个非常痛苦的过程。有了 Paperspace,每台设备都成了进入您桌面和整个办公网络的门户。流式传输桌面比移动越来越多的文件要简单快捷得多。对于技术游牧工人来说,这是一个新的范例。
2)硬件是一种痛苦
对于大多数企业来说,管理关键任务计算机和共享服务器是一件令人头疼的事情。有初始收购、维护和升级,所有这些步骤都需要 IT 顾问或专门的 IT 部门来管理。计算机通常在第一年就觉得过时了——因为它们确实过时了。Paperspace 机器是你需要购买的最后一台电脑。无需 36 个月的生命周期,无需维护和升级。想象一下,再也不用担心硬盘故障或咖啡溅到笔记本电脑上了。你的暑期实习生需要 10 台新机器吗?您可以通过管理控制台在两分钟内创建它们,并在夏天结束时点击一个按钮将其删除。世界正朝着自带设备(BYOD)模式发展,你也应该如此。
3)简化的软件管理
虚拟桌面在软件管理方面远远优于传统工作站。这里有几个例子:
- 轻松创建包含所有应用程序/设置的模板,并在整个公司内分发。
- 快照和回滚只需点击一下鼠标,如果不使用虚拟机,这几乎是不可能的。
- 通过集中管理控制台控制谁可以访问什么
4)成本
虚拟机消除了购买计算机的高额前期成本,只需支付可预测的月费。此外,该模型将您的计算机资本支出问题转化为运营支出机会。但是还有更多:消费者只通过最初的预付购买来衡量电脑的成本。企业还需要考虑计算机的管理,通常称为 TCO 或总拥有成本。由于 TCO 通常是硬件直接成本的 2 1/2 - 3 倍。Paperspace 的每月成本包括与 TCO 相关的所有硬件管理。最后,我们正在管理大量硬件,并将这些规模经济带来的节约直接传递给您。
5)安全性
安全性是当今使用虚拟机的主要原因。为什么?因为在这种模式下,任何数据都不会存储在员工设备上。[1]这意味着,如果笔记本电脑丢失或被盗,数据落入他人手中的风险极小。从设计角度来看,保护单个集中式系统比保护每个单独的端点要容易得多。[1]安全就是控制:有了虚拟机,您只需通过一个简单的控制面板就能决定谁有权访问以及何时访问。
云中的虚拟桌面是企业计算的未来。一些人称之为“DaaS”时代或“桌面即服务”时代。我们喜欢避免使用 DaaS 这个词,因为我们的竞争对手给了它一个如此糟糕的名字。我们称之为纸空间。
- 像我们这样的书呆子称之为“最小化攻击面”↩︎↩︎
Windows 10
原文:https://blog.paperspace.com/windows-10-launches-on-paperspace/
Windows 10 现已在 Paperspace 上发布。这里有一些你可能会喜欢这个操作系统的东西:
1.新开始菜单
Windows 8 的开始菜单是一个非常不受欢迎的项目。Windows 10 带回了传统的开始菜单,增加了一些新功能,但没有让事情变得混乱。
2.改进的任务切换器
按 Windows 键+ Tab 调出这个新设计的任务切换器,也可以通过单击开始菜单旁边的图标来访问。更大的缩略图使一切更容易阅读。
3.快照辅助
将一个应用程序拖到显示器的一侧可让您将它吸附到屏幕的一半,并提示您点按另一个应用程序(如果您想要它们并排的话)。如果您希望每个窗口占据屏幕的四分之一而不是一半,您也可以使用屏幕的所有四个角。
4.命令提示符走出了 20 世纪
复制并粘贴。可扩展窗口。说够了。
5.多个桌面
您可以创建多个桌面来组织您的工作。滑动浏览就像 Ctrl+Alt+箭头键或单击开始菜单旁边的按钮一样简单。
6.计划 Windows Update 重新启动
几乎每个人对 Windows 最大的烦恼就是突然需要微软更新你的电脑。现在,很容易安排一个更新“窗口”,这样更新就可以在你没有工作的时候发生(你甚至可以在你睡觉的时候安排它们)。
7.设置面板被完全重新设计
Windows 设置面板感觉很现代,一切都很容易找到。你可以使用关键字搜索任何东西,这是一个游戏规则的改变。
8.轻松找出哪些应用程序占用了空间
这在以前是可能的,但从未如此直观和容易找到。
在 PyTorch 中使用自定义影像数据集
原文:https://blog.paperspace.com/working-with-custom-image-datasets-in-pytorch/
许多初学者在尝试使用 PyTorch 定制、管理的数据集时可能会遇到一些困难。之前已经探讨了如何管理定制图像数据集(通过网络抓取),本文将作为如何加载和标记定制数据集以与 PyTorch 一起使用的指南。
创建自定义数据集
本节借用了关于管理数据集的文章中的代码。这里的目标是为一个模型策划一个自定义数据集,该数据集将区分男士运动鞋/运动鞋和男士靴子。
为了简洁起见,我不会详细介绍代码做了什么,而是提供一个快速的总结,因为我相信您一定已经阅读了前一篇文章。如果你没有,不要担心:它再次链接到这里的。您也可以简单地运行代码块,为下一部分做好准备。
# article dependencies
import cv2
import numpy as np
import os
import requests
from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.request import Request
import time
from torch.utils.data import Dataset
import torch
from torchvision import transforms
from tqdm import tqdm
WebScraper 类
下面的类包含的方法将帮助我们通过使用 beautifulsoup 库解析 html,使用感兴趣的标签和属性提取图像 src 链接,最后从网页下载/抓取感兴趣的图像来管理自定义数据集。这些方法被相应地命名。
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
time.sleep(0.4)
except Exception as e:
print(f'problem with image\n{e}')
time.sleep(0.4)
pass
刮擦功能
为了使用我们的 web scraper 遍历多个页面,我们需要将它封装在一个允许它这样做的函数中。下面的函数就是为了达到这个效果而编写的,因为它包含了格式化为 f 字符串的感兴趣的 url,这将允许迭代 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
创建目录
由于目标是管理男鞋数据集,我们需要为此创建目录。为了整洁,我们在根目录下创建一个名为 shoes 的父目录,这个目录包含两个名为 athletic 和 boots 的子目录,它们将保存相应的图像。
# create directories to hold images
os.mkdir('shoes')
os.mkdir('shoes/athletic')
os.mkdir('shoes/boots')
抓取图像
首先,我们需要为 web scraper 定义一个合适的标题。标题有助于屏蔽 scraper,因为它模拟了来自实际 web 浏览器的请求。然后,我们可以使用我们定义的头、我们想要从中提取图像的标签(img)、感兴趣的标签的属性(class: img)、保存图像链接的属性(data-src)、以文件名结尾的感兴趣的文件路径以及要包含在文件名中的计数前缀的起点来实例化运动鞋图像的 scraper。然后我们可以将运动刮刀传递给 my_scraper 函数,因为它已经包含了与运动鞋相关的 URL。
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'}
# scrape athletic shoe images
athletic_scraper = WebScraper(headers=headers, tag='img', attribute = {'class':'img'},
src_attribute='data-src', filepath='shoes/athletic/atl', count=0)
my_scraper(scraper=athletic_scraper, page_range=[1, 3])
为了抓取靴子的图像,复制下面评论中的两个 URL,替换 my_scraper 函数中的当前 URL。从那以后,引导刮擦器以与运动刮擦器相同的方式被实例化,并被提供给 my_scraper 函数,以便刮擦引导映像。
# replace the urls in the my scraper function with the urls below
# first url:
# f'https://www.jumia.com.ng/mlp-fashion-deals/mens-boots/?page={i}#catalog-listing'
# second url:
# f'https://www.jumia.com.ng/mlp-fashion-deals/mens-boots/?page={page_range[0]}#catalog-listing'
# rerun my_scraper function code cell
# scrape boot images
boot_scraper = WebScraper(headers=headers, tag='img', attribute = {'class':'img'},
src_attribute='data-src', filepath='shoes/boots/boot', count=0)
my_scraper(scraper=boot_scraper, page_range=[1, 3])
当所有这些代码单元按顺序运行时,应该在当前工作目录中创建一个名为“shoes”的父目录。这个父目录应该包含两个名为“运动”和“靴子”的子目录,它们将保存属于这两个类别的图像。
加载和标记图像
现在我们已经有了自定义数据集,我们需要生成其组成图像的数组表示(加载),标记数组,然后将它们转换为张量,以便在 PyTorch 中使用。存档这将需要我们定义一个类来完成所有这些过程。下面定义的类执行前两个步骤,它将图像读取为灰度,将它们的大小调整为 100 x 100 像素,然后根据需要对它们进行标记(运动鞋= [1, 0]
,靴子= [0, 1]
)。注意:从我的角度来看,我的工作目录是根目录,所以我在下面的 Python 类中相应地定义了文件路径,你应该基于你自己的工作目录定义文件路径。
# defining class to load and label data
class LoadShoeData():
"""
This class loads in data from each directory in numpy array format then saves
loaded dataset
"""
def __init__(self):
self.athletic = 'shoes/athletic'
self.boots = 'shoes/boots'
self.labels = {self.athletic: np.eye(2, 2)[0], self.boots: np.eye(2, 2)[1]}
self.img_size = 100
self.dataset = []
self.athletic_count = 0
self.boots_count = 0
def create_dataset(self):
"""
This method reads images as grayscale from directories,
resizes them and labels them as required.
"""
# reading from directory
for key in self.labels:
print(key)
# looping through all files in the directory
for img_file in tqdm(os.listdir(key)):
try:
# deriving image path
path = os.path.join(key, img_file)
# reading image
image = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
image = cv2.resize(image, (self.img_size, self.img_size))
# appending image and class label to list
self.dataset.append([image, self.labels[key]])
# incrementing counter
if key == self.athletic:
self.athletic_count+=1
elif key == self.boots:
self.boots_count+=1
except Exception as e:
pass
# shuffling array of images
np.random.shuffle(self.dataset)
# printing to screen
print(f'\nathletic shoe images: {self.athletic_count}')
print(f'boot images: {self.boots_count}')
print(f'total: {self.athletic_count + self.boots_count}')
print('All done!')
return np.array(self.dataset, dtype='object')
# load data
data = LoadShoeData()
dataset = data.create_dataset()
运行上面的代码单元格应该会返回一个包含自定义数据集中所有图像的 NumPy 数组。该数组的每个元素都是一个自己的数组,保存一个图像及其标签。
创建 PyTorch 数据集
生成了自定义数据集中所有图像和标签的数组表示之后,就该创建 PyTorch 数据集了。为此,我们需要定义一个从 PyTorch datasets 类继承的类,如下所示。
# extending Dataset class
class ShoeDataset(Dataset):
def __init__(self, custom_dataset, transforms=None):
self.custom_dataset = custom_dataset
self.transforms = transforms
def __len__(self):
return len(self.custom_dataset)
def __getitem__(self, idx):
# extracting image from index and scaling
image = self.custom_dataset[idx][0]
# extracting label from index
label = torch.tensor(self.custom_dataset[idx][1])
# applying transforms if transforms are supplied
if self.transforms:
image = self.transforms(image)
return (image, label)
基本上,定义了两个重要的方法__len__()
和__getitem__()
。__len__()
方法返回自定义数据集的长度,而__getitem__()
方法通过索引从自定义数据集中获取图像及其标签,应用转换(如果有)并返回一个元组,然后 PyTorch 可以使用该元组。
# creating an instance of the dataset class
dataset = ShoeDataset(dataset, transforms=transforms.ToTensor())
当上面的代码单元运行时,dataset 对象成为 PyTorch 数据集,现在可以用于构建深度学习模型。
结束语
在本文中,我们了解了如何在 PyTorch 中使用自定义数据集。我们通过网络抓取整理了一个自定义数据集,加载并标记了它,并从中创建了一个 PyTorch 数据集。
在本文中,Python 类的知识将被揭示出来。大多数被定义为类的过程也可以用常规函数来完成(除了 PyTorch dataset 类),但是作为一个个人偏好,我选择这样做。在您的编程之旅的这个阶段,通过做最适合您的事情,您可以随意尝试复制这些代码。
在 Python 中使用不同的遗传算法表示
原文:https://blog.paperspace.com/working-with-different-genetic-algorithm-representations-python/
根据被优化问题的性质,遗传算法(GA)支持两种不同的基因表达:二进制和十进制。二进制遗传算法的基因只有两个值,即 0 和 1。这更容易管理,因为与十进制遗传算法相比,它的基因值是有限的,对于十进制遗传算法,我们可以使用不同的格式,如浮点或整数,以及有限或无限的范围。
本教程讨论了 PyGAD 库如何支持两种 GA 表示,二进制和十进制。本教程的大纲如下:
- 开始使用 PyGAD
- 控制基因在初始群体中的范围
- 基因类型(
int
或float
) - 避免超出初始范围
- 连续和离散基因范围
- 每个基因的自定义值
- 定制一些基因,同时随机化其他基因
- 二进制遗传算法
- 用户定义的初始群体
你也可以在 Gradient 上免费运行本教程的代码。
让我们开始吧。
【PyGAD 入门
PyGAD 是一个用于实现遗传算法的 Python 库。要安装它并开始使用,请查看教程 5 使用 PyGAD 的遗传算法应用。顾名思义,我们将向您展示如何使用该库开发五个不同的应用程序。你可以在 Gradient 上免费运行代码。
在使用遗传算法为 CoinTex 构建一个玩游戏的代理中,PyGAD 被用来构建一个玩名为 CoinTex 的游戏的代理。
PyGAD 也被记录在阅读文件。
在开始本教程之前,请确保您至少安装了 PyGad 2 . 6 . 0 版。
pip install pygad>=2.6.0
下一节将讨论如何使用 PyGAD 定制基因的取值范围。
控制基因在初始种群中的范围
正如我们所讨论的,遗传算法有两种基因表达方式:
- 二进制的
- 小数
对于二元遗传算法,每个基因只有两个值:0 或 1。另一方面,十进制表示可以使用基因的任何十进制值。本节讨论如何限制基因值的范围。
在一些问题中,用户限制有效基因值的范围可能是有用的。例如,假设每个基因值必须在 5 到 15 之间,没有任何例外。我们如何做到这一点?
PyGAD 支持两个参数来处理这个场景:init_range_low
和init_range_high
。参数init_range_low
指定创建初始群体的范围的下限,而init_range_high
指定上限。注意init_range_high
是独家的。所以,如果init_range_low=5
和init_range_high=15
,那么可能的基因值是从 5 到 15,但不包括 15。下面的代码片段显示了这个例子。
除了这些参数之外,我们还必须指定我们的适应度函数fitness_func
和世代数num_generations
。
尽管下面三个参数是可选的,但是在随机创建初始群体时必须指定它们:
num_parents_mating
:要交配的父母数量。sol_per_pop
:设置为 3,表示种群有 3 个解。num_genes
:每个溶液有 4 个基因。
import pygad
def fitness_function(solution, solution_idx):
return sum(solution)
ga_instance = pygad.GA(num_generations=1,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
init_range_low=5,
init_range_high=15)
一旦实例被创建,随机初始群体就在initial_population
属性中准备好了。下面一行打印了它。因为群体有 3 个解,其中每个解有 4 个基因,所以群体的形状是(3, 4)
。注意,每个基因都有一个介于 5 和 15 之间的值。默认情况下,基因的类型是 float。
print(ga_instance.initial_population)
print(ga_instance.initial_population.shape)
[[14.02138539 10.13561641 13.77733116 5]
[13.28398269 14.13789428 12.6097329 7.51336248]
[ 9.42208693 6.97035939 14.54414418 6.54276097]]
(3, 4)
下面的代码将init_range_low
设置为 1,将init_range_high
设置为 3,看看基因的范围是如何变化的。
ga_instance = pygad.GA(num_generations=1,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
init_range_low=1,
init_range_high=3)
print(ga_instance.initial_population)
如下所示,随机创建的群体具有 1 到 3 之间的所有基因。请注意,可能有值为 1 的基因,但不可能有值为 3 的基因。
[[1.00631559 2.91140666 1.30055502 2.10605866]
[2.23160212 2.32108812 1.90731624 1]
[2.23293791 1.9496456 1.25106388 2.46866602]]
注意,init_range_low
和init_range_high
参数只是限制了初始群体中基因的范围。如果解决方案进化成几代人呢?这可能会使基因超出初始范围。
为了做一个实验,将num_generations
参数设置为 10,调用run()
方法,通过 10 代进化解决方案。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
init_range_low=1,
init_range_high=3)
ga_instance.run()
在run()
方法完成后,下一段代码打印如下内容:
- 使用
initial_population
属性的初始群体。 - 使用
population
属性的最终人口。
print(ga_instance.initial_population)
print(ga_instance.population)
[[1.08808272 1.16951518 1.30742402 1.40566555]
[2.88777068 2.49699173 2.47277427 2.36010308]
[1.94598736 2.10177613 1.57860387 1.45981019]]
[[3.7134492 1.9735615 3.39366783 2.21956642]
[3.7134492 2.49699173 2.47277427 2.36010308]
[2.94450144 1.9735615 3.39366783 2.36010308]]
对于初始种群,所有的基因都在 1 到 3 之间。对于最终群体,一些基因超出了范围,如第一个解决方案中的第一个和第三个基因。如何强制任一种群内的基因在范围内?这在避免超出范围一节中讨论。
另外需要注意的是,基因的类型是浮点型的。有些问题可能只适用于整数值。下一节讨论如何使用gene_type
参数指定基因的类型。
基因类型(int
或float
)
默认情况下,PyGAD 为初始种群分配随机浮点值。如果用户希望这些值是整数,gene_type
参数可用于此目的。PyGAD 2.6.0 及更高版本支持它。
它支持两个值:
float
:默认值。这意味着基因是浮点数。int
:基因由浮点数转换为整数。
下一段代码将gene_type
参数设置为int
,以强制随机初始种群拥有整数基因。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
init_range_low=1,
init_range_high=3,
gene_type=int)
print(ga_instance.initial_population)
随机初始群体如下所示。注意,基因的范围是从 1 到 3,不包括 1 和 3。这意味着 1 和 2 是仅有的整数。因此,总体只有值 1 和 2。
[[1 1 2 1]
[1 2 2 1]
[1 2 1 2]]
当范围从 5 变为 10 时,可能的基因值是 5、6、7、8 和 9。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
init_range_low=5,
init_range_high=10,
gene_type=int)
print(ga_instance.initial_population)
[[5 9 7 8]
[5 7 9 8]
[5 5 6 7]]
请注意,将gene_type
参数设置为int
或float
不会阻止基因超出使用init_range_low
和init_range_high
参数指定的范围。这将在下一节讨论。
避免超出初始范围
随机创建的初始群体的基因在两个参数init_range_low
和init_range_high
指定的范围内。但这并不能保证它的基因总是在这个范围内。原因是基因的值由于突变操作而改变。
默认情况下,random
类型的变异操作适用于所有基因。这导致基因的一些随机变化,从而导致它们的值超过初始范围。根据所解决问题的类型,超出范围可能是问题,也可能不是问题。
如果问题必须有它的基因在一个范围内,那么有不同的选项来强制所有代中的所有基因都在该范围内。这些选项总结如下:
- 不要使用
random
突变。 - 禁用变异操作。
- 使用
mutation_by_replacement
参数。这是最实际的选择。
让我们来讨论一下这些选项。
不使用random
突变
使用mutation_type
参数指定所用突变操作的类型。支持的类型有:
- 随机:
mutation_type=random
- 互换:
mutation_type=swap
- 反转:
mutation_type=inversion
- 争夺:
mutation_type=scramble
在这 4 种类型中,只有random
突变可以改变超出范围的基因值。因此,迫使基因在初始范围内的一种方法是使用另一种类型的突变,而不是random
突变。
下一段代码使用了swap
突变。即使在run()
方法执行之后,基因值仍然在初始范围内。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
init_range_low=5,
init_range_high=10,
mutation_type="swap",
gene_type=int)
ga_instance.run()
print(ga_instance.initial_population)
print(ga_instance.population)
[[6 9 8 7]
[5 5 8 8]
[9 8 5 6]]
[[8 9 6 9]
[8 9 6 9]
[8 9 6 9]]
这个选项在许多情况下可能不可行,因为其他类型保持原始基因值,而只是改变它们的顺序。基因没有发生变化。
禁用变异操作
PyGAD 可以通过将mutation_type
参数设置为None
来禁用变异操作。尽管它保留了初始范围内的基因值,但它禁用了进化解的主要选项之一。
下一段代码禁用变异操作。10 代后,基因仍在规定范围内。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
init_range_low=5,
init_range_high=10,
mutation_type=None,
gene_type=int)
ga_instance.run()
print(ga_instance.initial_population)
print(ga_instance.population)
[[7 9 5 9]
[5 6 6 8]
[8 5 6 6]]
[[7 9 5 9]
[7 9 6 6]
[7 9 5 6]]
使用mutation_by_replacement
参数
前两个选项要么被random
突变破坏,要么被突变本身破坏,以将基因保持在初始范围内。支持使用random
突变同时仍将基因保持在指定范围内的最可行选项是布尔参数mutation_by_replacement
。
正常情况下,随机突变会产生一个随机值。然后将该值添加到当前基因值中。假设有一个值为 2.5 的基因,指定的范围是 1 到 3,不包括 1 和 3。如果随机值为 0.7,那么将其添加到当前基因值会导致2.5+0.7=3.2
超出范围。
当mutation_by_replacement
参数为True
时,它会用随机值替换(而不是添加)基因值。所以,当随机值为 0.7 时,新的基因值将为 0.7。如果gene_type
设置为int
,则结果为1.0
。
用户可以使用分别指定下限和上限的两个参数random_mutation_min_val
和random_mutation_max_val
来控制产生随机值的范围。
为了将基因保持在该范围内,这些参数中的每一个都必须满足以下条件:
init_range_low <= param <= init_range_high
为了获得最佳体验,请设置random_mutation_min_val=init_range_low
和random_mutation_max_val=init_range_high
。
下一个代码给出了使用本小节中讨论的 3 个参数的示例(mutation_by_replacement
、random_mutation_min_val
和random_mutation_max_val
)。
ga_instance = pygad.GA(num_generations=1000,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
init_range_low=5,
init_range_high=10,
random_mutation_min_val=5,
random_mutation_max_val=10,
mutation_by_replacement=True,
gene_type=int)
ga_instance.run()
对于任何数量的世代,基因都不会超出这个范围。下一段代码打印初始和最终人口。最终群体中的基因不超出该范围。
print(ga_instance.initial_population)
print(ga_instance.population)
[[5 8 8 5]
[9 8 8 9]
[5 9 8 9]]
[[9 9 9 9]
[9 9 9 9]
[9 9 8 9]]
使用 3 个参数mutation_by_replacement
,可以使遗传算法仅适用于二元基因(即值为 0 和 1 的基因)。这是通过执行以下操作实现的:
- 设置
init_range_low=random_mutation_min_val=0
。 - 设置
init_range_high=random_mutation_max_val=2
。 - 设置
mutation_by_replacement=True
。 - 设置
gene_type=int
。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
init_range_low=0,
init_range_high=2,
random_mutation_min_val=0,
random_mutation_max_val=2,
mutation_by_replacement=True,
gene_type=int)
ga_instance.run()
这是所有基因都为 0 或 1 的初始和最终群体。
print(ga_instance.initial_population)
print(ga_instance.population)
[[1 1 0 1]
[0 1 0 0]
[0 1 1 1]]
[[0 1 0 1]
[0 1 0 0]
[0 1 1 0]]
注意,这不是支持二进制 GA 的唯一方法。使用gene_space
参数,也可以支持二进制遗传算法。下一节将介绍该参数。
连续和离散基因范围
前面的讨论假设基因取样的范围是连续的。因此,如果范围从 1 到 5 开始,则此范围内的所有值(1、2、3 和 4)都是可接受的。如果某个范围内的某些值是不允许的,或者这些值不符合连续的范围(例如-2、18、43 和 78),该怎么办?为此,PyGAD 支持一个名为gene_space
的参数来指定基因值空间。
gene_space
参数允许用户列出所有可能的基因值。它接受一个列表或元组,其中列出了所有可能的基因值。
下一段代码使用gene_space
参数列出所有基因的可能值。因此,所有基因都是从列出的 4 个值中取样的。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
gene_space=[-2, 18, 43, 78])
ga_instance.run()
print(ga_instance.initial_population)
print(ga_instance.population)
[[78 43 78 -2]
[18 -2 78 78]
[43 43 18 -2]]
[[-2 -2 18 78]
[18 -2 78 78]
[-2 -2 18 78]]
注意,所有的基因都是从相同的值中取样的。换句话说,gene_space
参数中的值对所有基因都是通用的。如果每个基因都有不同的价值呢?下一节讨论如何为每个基因指定自定义值。
每个基因的自定义值
当gene_space
参数接受一个非嵌套列表/元组时,那么这个列表/元组中的值被用来采样所有基因的值。有些基因可能有自己独特的价值。gene_space
参数分别接受每个基因的值。这是通过创建嵌套列表/元组来实现的,其中每一项都包含其对应基因的可能值。
假设有 4 个基因,每个基因都有自己的值空间。每个基因的可能值列表如下所示。请注意,没有一个基因的值遵循一个序列。每个基因可能有不同数量的值。
- 基因 1: [-4,2]
- 基因 2: [0,5,7,22,84]
- 基因 3: [-8,-3,0,4]
- 基因 4: [1,6,16,18]
所有 4 个列表都作为项目添加到下面给出的gene_space
参数中。
gene_space = [[-4, 2],
[0, 5, 7, 22, 84],
[-8, -3, 0, 4],
[1, 6, 16, 18] ]
下一段代码创建了一个使用gene_space
属性的pygad.GA
类的实例。打印的初始和最终群体显示了每个基因是如何从其自己的空间中取样的。例如,所有解决方案的第一个基因的值都是-4 和 2。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
gene_space=[[-4, 2],
[0, 5, 7, 22, 84],
[-8, -3, 0, 4],
[1, 6, 16, 18] ])
ga_instance.run()
print(ga_instance.initial_population)
print(ga_instance.population)
[[-4\. 84\. 0\. 18.]
[ 2\. 7\. 4\. 1.]
[ 2\. 0\. -8\. 6.]]
[[-4\. 7\. 4\. 1.]
[ 2\. 7\. 4\. 1.]
[-4\. 7\. 4\. 6.]]
前 4 个基因的值没有遵循一个序列。某些基因的值可能遵循一个序列。下面列出了 4 个基因的值。第一个基因的值从 0 到 5(不含)开始,第二个基因的值从 16 到 27(不含)开始。第三个和第四个基因的值与之前相同。
- 基因 1: [0,1,2,3,4]
- 基因 2: [16,17,18,19,20,21,22,23,24,25,26]
- 基因 3: [-8,-3,0,4]
- 基因 4: [1,6,16,18]
参数gene_space
的新值如下所示。
gene_space = [ [0, 1, 2, 3, 4],
[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26],
[-8, -3, 0, 4],
[1, 6, 16, 18] ]
如果一个基因有一个序列,例如,1000 个值。我们必须列出它的单个元素吗?幸运的是,PyGAD 允许使用range()
函数指定单个基因的空间。如果第一个基因的值空间从 0 开始,但不包括 5,那么可以使用range(0, 5)
对其建模。对于从 16 开始但不包括 26 的第二个基因,其值空间用range(16, 27)
表示。
使用range()
功能后,gene_space
参数的新值如下所示。
gene_space = [ range(5), range(16, 27), [-8, -3, 0, 4], [1, 6, 16, 18] ]
下面是使用更新后的gene_space
的代码。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
gene_space=[range(5),
range(16, 27),
[-8, -3, 0, 4],
[1, 6, 16, 18] ])
ga_instance.run()
print(ga_instance.initial_population)
print(ga_instance.population)
[[ 0\. 19\. -8\. 18.]
[ 2\. 26\. 4\. 6.]
[ 3\. 18\. -3\. 1.]]
[[ 3\. 25\. 0\. 6.]
[ 0\. 26\. 4\. 18.]
[ 3\. 22\. 0\. 6.]]
将一个基因固定为一个单一的值是可能的。这是通过将其在gene_space
参数中的项目分配给该单一值来实现的。下面是一个例子,其中第一个基因设置为 4,第三个基因设置为 5。这两个基因的值永远不会改变。
gene_space = [4,
range(16, 27),
5,
[1, 6, 16, 18] ]
下面是使用最后一个gene_space
值的代码。在初始和最终群体中,第一个和第三个基因从不改变。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
gene_space=[4,
range(16, 27),
5,
[1, 6, 16, 18] ])
ga_instance.run()
print(ga_instance.initial_population)
print(ga_instance.population)
[[ 4\. 21\. 5\. 16.]
[ 4\. 18\. 5\. 16.]
[ 4\. 24\. 5\. 16.]]
[[ 4\. 18\. 5\. 1.]
[ 4\. 18\. 5\. 16.]
[ 4\. 18\. 5\. 18.]]
定制一些基因,同时随机化其他基因
根据前面对gene_space
参数的讨论,每个基因都有自己的基因空间,通过硬编码单个值或使用range()
函数来指定。
在某些情况下,用户可能需要强制将某些基因限制为某些值,但其他基因可能会被随机化。例如,如果染色体中有一个基因必须是-1 或 1,但其他基因可以是任何随机值。怎么做呢?
对于要随机化的基因,将其在gene_space
参数中的项目分配给None
。这意味着该基因的值将被随机化。下一行将列表[-1, 1]
分配给第一个基因,将None
分配给剩余的 3 个基因。最后 3 个基因将具有随机值。
gene_space = [[-1, 1], None, None, None]
下一个代码使用最后一个gene_space
值。注意第一个基因是如何从列表[-1, 1]
中抽取的,而其他基因具有随机值。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
gene_space=[[-1, 1], None, None, None])
ga_instance.run()
print(ga_instance.initial_population)
print(ga_instance.population)
[[ 1\. 0.28682682 1.39230915 1.12768838]
[-1\. -1.05781089 1.71296713 2.56994039]
[ 1\. 3.78611876 -3.80634854 2.15975074]]
[[-1\. -1.05781089 1.88097581 2.56994039]
[-1\. -1.05781089 1.71296713 2.56994039]
[-1\. -1.05781089 1.3061504 2.56994039]]
注意,随机基因是从 2 个参数init_range_low
和init_range_high
指定的范围内的值随机初始化的。如果突变类型是随机的,那么加到基因上的随机值是从两个参数random_mutation_min_val
和random_mutation_max_val
指定的范围中取样的。此外,随机值的类型根据gene_type
参数来确定。最后,如果mutation_by_replacement
被设置为True
,那么随机值将不会被添加而是替换基因。请注意,这些参数仅影响其间距设置为None
的基因。
下一个代码强制基因初始值在 10 到 20 之间,不包括 10 和 20。突变随机范围从 30 到 40,不含 30 和 40。将gene_type
设置为int
。
ga_instance = pygad.GA(num_generations=1000,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
init_range_low=10,
init_range_high=20,
random_mutation_min_val=30,
random_mutation_max_val=40,
gene_space=[[-1, 1], None, None, None],
gene_type=int)
ga_instance.run()
print(ga_instance.initial_population)
print(ga_instance.population)
[[ 1\. 16\. 14\. 10.]
[-1\. 12\. 16\. 14.]
[-1\. 17\. 19\. 13.]]
[[-1\. 12\. 16\. 48.]
[-1\. 15\. 26\. 14.]
[-1\. 12\. 16\. 14.]]
二进制遗传算法
在使用突变替换参数部分,PyGAD 通过使用以下参数支持二进制遗传算法。
init_range_low=random_mutation_min_val=0
。init_range_high=random_mutation_max_val=2
。mutation_by_replacement=True
。gene_type=int
。
也可以通过使用gene_space
参数支持二进制遗传算法。这是通过将该参数设置为全局空间[0, 1]
来实现的。这意味着所有基因的值不是 0 就是 1。
下一段代码将gene_space
参数设置为[0, 1]
。这将强制所有基因的值为 0 或 1。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
sol_per_pop=3,
num_genes=4,
fitness_func=fitness_function,
gene_space=[0, 1])
ga_instance.run()
print(ga_instance.initial_population)
print(ga_instance.population)
[[1 1 1 0]
[0 1 0 0]
[1 1 1 0]]
[[0 1 1 0]
[0 1 0 1]
[0 1 0 0]]
用户自定义初始人口
有时,用户可能希望从没有任何随机化的自定义初始群体开始。PyGAD 支持一个名为initial_population
的参数,该参数允许用户指定一个定制的初始群体。
下一个代码 prepares 将一个嵌套列表分配给initial_population
参数,其中有 3 个解,每个解有 4 个基因。在这种情况下,不需要num_genes
和sol_per_pop
参数,因为它们将从分配给initial_population
参数的值中推导出来。
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
fitness_func=fitness_function,
initial_population=[[34, 32, 24, -2],
[3, 7, 2, 7],
[-2, -4, -6, 1]])
ga_instance.run()
print(ga_instance.initial_population)
print(ga_instance.population)
[[34 32 24 -2]
[ 3 7 2 7]
[-2 -4 -6 1]]
[[3 7 2 6]
[3 7 2 7]
[3 7 2 7]]
结论
本教程使用了 PyGAD 库来处理遗传算法的二进制和十进制表示。本教程讨论了 PyGAD 中的不同参数,以允许用户除了控制变异操作之外,还可以控制如何创建初始种群。
使用gene_type
参数,基因值可以是浮点数或整数。mutation_by_replacement
参数用于将基因保持在其初始范围内。initial_population
参数接受用户定义的初始群体。
在基因值不遵循序列的情况下,gene_space
参数会有所帮助。在这种情况下,离散基因值以列表形式提供。该参数接受每个基因的自定义值空间。此外,它允许从一个确定的空间取样一些基因,而随机选择其他基因。