TowardsDataScience 博客中文翻译 2016~2018(二百三十八)
Python 与 Scala:基本命令的比较(第一部分)
“MacBook Pro on brown wooden table” by Max Nelson on Unsplash
我最近开始玩一点 Scala,我不得不说这是一种创伤。我喜欢学习新事物,但在用 Python 编程数月后,在解决数据科学问题时将它放在一边并切换模式是不自然的。在学习一门新语言的时候,不管是编码还是口语,出现这种情况是很正常的。我们倾向于用我们知道的东西来填补我们不知道的东西的空白,即使它们不属于我们试图写/说的语言!当试图学习一门新语言时,完全被你想学的语言所包围是很重要的,但首先,在已知语言和新语言之间建立良好的平行关系是很重要的,至少在开始时是如此。这对我很有用,我是一个双语的人,在成年后很快就学会了第二语言。一开始,我需要意大利语(我所知道的语言)和英语(我正在学习的语言)之间的联系,但是随着我的英语越来越流利,我开始忘记平行关系,因为这变得很自然,我不再需要先在脑子里翻译它了。事实上,我决定写这篇文章的原因是为了在 Python 和 Scala 之间建立并行性,对于像我一样精通其中一种,并开始学习另一种的人来说。
我最初是想把重点放在熊猫/Sklearn 和 Spark 上,但我意识到,不先把基础打好,意义不大。这就是为什么在这篇文章中,我们将看看 Python 和 Scala 的基础知识:如何处理字符串、列表、字典等等。我打算在不久的将来发表第二部分,其中我将介绍如何用两种语言处理数据帧和构建预测模型。
1.重要的事情先来
第一个区别是当编码是这两种语言时使用的约定:如果你不遵循它,这不会抛出一个错误或任何类似的东西,但它只是一个编码者遵循的非书面规则。
当定义一个新的变量、函数或其他东西时,我们总是选择一个对我们有意义的名字,这个名字很可能由两个或更多的单词组成。如果是这种情况,在 Python 中我们将使用snake_case,而在 Scala 中camelCase:差别是显而易见的。在蛇的情况下,所有的单词都是小写的,我们使用_来分隔它们,在骆驼的情况下没有分隔,除了第一个单词,所有的单词都是大写的。
另一个显著的区别是我们如何定义两种语言中的变量。在 Python 中,我们只需创建一个名称,并将其赋给我们需要的值,而在 Scala 中,我们需要指定我们是在定义一个变量还是一个值,我们通过在名称前分别放置var或val来做到这一点(注意,无论我们是在赋数值还是字符串,这都是有效的)。
Initializing values and variables in Scala.
var和val的区别很简单:变量可以修改,而值不能。在图中所示的例子中,我实例化了一个var字符串,然后将其更改为:all good。然后,我将同一个字符串赋给了一个val,并试图再次改变它:不可行。
在 Python 中,不需要指定:如果你想改变你之前分配的东西,这取决于你。在 Python 的情况下,我只需要做string = 'my_string'。
另一个普遍的区别是关于注释。在 Python 中,只有一种方法可以做到这一点,不管是单行还是多行,那就是在每一行的注释前加一个#:
# this is a commented line in Python
Scala 提供了两种注释方式,要么将//放在每一行,要么将注释放在/*和*/之间:
// this is a commented line in Scala
/* and this is a multiline comment, still in Scala...
...just choose! */
现在,非常基础的解释,让我们看看潜水更深。
2.列表和数组
list(Python 中的)或 Array(Scala 中的)是最重要的对象:它们可以包含字符串和/或数字,我们可以操作它们,遍历它们,添加或减去元素等等。它们基本上可以服务于任何目的,我不认为我曾经不使用它们编码过任何东西,所以让我们看看我们可以用它们做什么,以及如何做。
2.1.规定
让我们创建一个包含数字和字符串混合的列表。
my_list = [2, 5, 'apple', 78] **# Python**
var myArray = Array(2, 5, "apple", 78) **// Scala**
**/* notice that in Scala I wrapped the string between "", and that is the only way to do it! In python you can use both "" and '' indifferently */**
2.2.索引
列表和数组都是零索引的,这意味着第一个元素放在索引 0 处。所以,如果我们想提取第二个元素:
my_list[1] **# Python** uses [] to index
myArray(1) **// Scala** uses () to index
2.3.限幅
在这两种语言中,切片时不会计算第二个索引。所以,如果我们想提取前三个元素:
my_list[0:3] **# Python** slicing works like indexing
myArray.slice(0,3) **// Scala** needs the .slice()
2.4.检查第一个、最后一个、最大和最小元素
**# Python**my_list[0] # first element
my_list[-1] # last element
max(my_list) # maximum element
min(my_list) # minimum element# NOTE: min() and max() will work exclusively if the list contains
# numbers only!**// Scala**myArray.head // first element
myArray(0) // other way to check the first element
myArray.last // last element
myArray.max // maximum element
myArray.min // minimum element/* NOTE: .min and .max will work exclusively if the array contains numbers only!*/
2.5.总和与乘积
对于 min 和 max,只有当列表/数组只包含数字时,才支持这些操作。此外,为了将 Python 列表中的所有元素相乘,我们需要建立一个for循环,这将在本文中进一步讨论。与 Scala 不同,它没有预加载的功能。
sum(my_list) # summing elements in **Python**'s list
// **Scala**
myArray.sum // summing elements in array
myArray.product // multiplying elements in array
2.6.添加元素
列表和数组是没有顺序的,所以通常的做法是在末尾添加元素。假设我们想要添加字符串"last words":
my_list.append('last words') # adding at the end of **Python**'s list
myArray :+= "last words" // adding at the end of **Scala**'s array
如果出于某种原因,我们想在最开始添加一些东西,就说数字99:
my_list.insert(0, 99) # this is a generic method in **Python**. The
# first number you specify in the parenthesis is the index of the
# position where you want to add the element.
# 0 means that you want the element to be added at the very
# beginningmyArray +:= 99 /* adding an element at the beginning of **Scala**'s array */
3.打印
这也是我们在编码时一直使用的东西,幸运的是这两种语言之间只有一点点差别。
print("whatever you want") # printing in **Python**
println("whatever you want") // printing in **Scala**
4.For 循环
这里有一些不同:Python 需要缩进来创建语句后的块和冒号,而 Scala 需要括号中的 for 条件,以及不需要缩进的花括号中的块。尽管如此,我还是喜欢使用缩进,它让代码看起来更整洁。
# for loop in **Python**
for i in my_list:
print(i)// for loop in **Scala**
for (i <- myArray){
println(i)
}
5.映射和/或过滤
在 Python 中,所有的事情都可以通过使用列表理解来完成。在 Scala 中,我们将不得不使用函数。
5.1.绘图
假设我们有一个只有数值的列表/数组,我们想把它们都增加三倍。
[i*3 for i in my_list] # mapping in **Python**
myArray.map(i => i*3) // mapping in **Scala**
5.2.过滤
假设我们有一个只有数值的列表/数组,我们想只过滤那些能被 3 整除的数值。
[i for i in my_list if i%3 == 0] # filtering in **Python**
myArray.filter(i => i%3 == 0) // filtering in **Scala**
5.3.过滤和映射
如果我们想找到偶数并且只将它们乘以 3 呢?
[i*3 for i in my_list if i%2 == 0] # **Python**
myArray.filter(i => i%2 == 0).map(i => i*3) // **Scala**
6.字典/地图
虽然它们在两种语言中的名称不同,但它们完全是一回事。它们都有keys,我们给它们赋值values。
6.1.创建字典/地图
让我们创建一个存储我的名、姓和年龄的数据库,并假设我 18 岁。
# **Python**
my_dict = {
'first_name': 'Emma',
'last_name': 'Grimaldi',
'age': 18
}
在 Scala 中,我们可以用两种不同的方式做到这一点。
// **Scala** mode 1
var myMap = (
"firstName" -> "Emma",
"lastName" -> "Grimaldi",
"age" -> 18
)// Scala mode 2
var myMap = (
("firstName", "Emma"),
("lastName", "Grimaldi"),
("age", 18)
)
6.2.添加到字典/地图
让我们把我的原籍国加入我的字典/地图。
my_dict['country_of_origin'] = 'Italy' # creating new key in **Python**
myMap += ("countryOfOrigin" -> "Italy") /* creating new key in **Scala** */
6.3.索引
这与索引列表/数组的工作方式相同,但是我们使用的是键,而不是位置。如果我想看我的名字:
# **Python**
my_dict['first_name']// **Scala**
myMap("firstName")
6.4.环
如果我们想打印字典/映射,在这两种情况下,我们都必须通过键和值进行 for 循环。
# Python
for key, value in my_dict.items():
print(key)
print(value)// Scala
for ((key, value) <- myMap){
println(key)
println(value)
}
7.元组
是的,它们在两种语言中的叫法是一样的!但是,虽然它们在 Python 中是零索引,但在 Scala 中却不是。让我们创建一个元组(1, 2, 3),然后调用第一个值。
# Python
my_tup = (1, 2, 3)
my_tup[0]
# the indexing is the same as lists// Scala
myTup = (1, 2, 3)
myTup._1
// the indexing is way different than arrays!
8.设置
是的,另一个共同的名字!在下面的两个例子中,集合将只包含1, 3, 5,因为集合不接受副本。
my_set = {1, 3, 5, 1} # in **Python**, sets are defined by curly braces
mySet = Set(1, 3, 5, 1) // **Scala**
9.功能
到目前为止,我们已经做了很多,如果你成功了,做得很好!这是这篇文章的最后一段,幸运的是在 Python 和 Scala 之间定义函数并没有太大的不同。它们都以def开头,而前者需要一个return语句,后者不需要。另一方面,Scala 想知道我们将要输入和输出什么类型的变量,而 Python 并不关心。让我们编写一个非常简单的函数,它接受一个字符串作为输入,并返回前 5 个字符。
# **Python**
def chop_string(input_string):
return input_string[0:5]
缩进在 Python 中也很重要,否则函数将不起作用。Scala 只是喜欢它的花括号。
// **Scala**
def chopString(inputString: String): String = {
inputString.slice(0, 5)
}
就是这样!我希望这对那些刚刚开始熟悉 Python 或 Scala 的人有所帮助。下一步将构建类似的指南,探索熊猫/sklearn 和 sparks 的不同之处,期待!我希望你也一样!
如果你想知道为什么应该使用 Python 而不是 Scala,或者反之亦然,我发现下面的图片非常有助于澄清两者之间的直接差异。
请随意查看:
我的其他媒体帖子。
感谢您的阅读!
Python Web 框架-Python 中 Web 框架的详细列表
Python Web Framework — A Detailed List of Web Frameworks in Python
什么是 Python Web 框架?
Python Web framework 是允许开发人员编写 Web 应用程序或服务的包或模块的集合。有了它,开发人员不需要处理像协议、套接字或进程/线程管理这样的底层细节。
Python web 框架将帮助您:
- 解释请求(获取表单参数,处理 cookies 和会话,..)
- 产生响应(以 HTML 或其他格式呈现数据,..)
- 持久存储数据(和其他东西)
现在,让我们看看最有用和最著名的 Python web 框架,来帮助您进行 web 开发。
Python 全栈框架
Python 中的全栈框架试图为应用程序提供完整的解决方案。它试图为堆栈中的每一层提供组件。
a.姜戈
Django Python 是为有期限的完美主义者设计的框架。有了它,你可以用更少的时间和代码构建更好的 Web 应用。Django 以专注于自动化而闻名。它还信奉干(不要重复自己)原则。
Django 最初是为内容管理系统开发的,但是现在被用于多种 web 应用程序。这是因为它的模板、自动数据库生成、DB 访问层和自动管理界面生成。它还提供了一个用于开发的 web 服务器。
使用 Django Python 的大公司有- Instagram、Pinterest、Disqus、Mozilla、华盛顿时报和 Bitbucket。事实上,当我们想到“框架”和“Python”这两个术语时,首先想到的是 Django。 我们将在下一课看到更多关于姜戈的内容。
b.涡轮齿轮
Python TurboGears — Python Web 框架
使用 TurboGears,您可以在几分钟内创建一个数据库驱动的、可扩展的应用程序。
它是一个带有 ORM 的 MVC web 框架,具有真正的多数据库支持和对水平数据分区的支持。它还有一个小部件系统来简化 AJAX 应用程序的开发。你可以另外安装它的模板引擎 Kajiki。
TurboGears 是一个微框架和全栈解决方案。它的 PyPI 包叫做 tg.devtools。
c.web2py
Python Web 框架— Python web2py
使用 web2py,您可以通过提供的 web 界面开发、部署、调试、测试、管理数据库和维护应用程序。它没有配置文件,你甚至可以从 USB 驱动器上运行它。
web2py 使用 MVC 内置的票务系统来管理错误。
d.立方体网络
CubicWeb 是一个语义 Web 应用框架,它以查询语言和选择+查看机制为特色。它还具有多个数据库、安全性、工作流和可重用组件。
e.姜戈-霍索奇
Django-hotsauce 是一个通用的 web 工具包,位于 Django 和其他框架之上。它是一个交互式 Pythonic API,允许您使用 WSGI 1.0 规范创建可伸缩的 web 应用程序。它还为 Schevo DBMS、Durus、ZODB 和 Authkit 项目提供了本机绑定。
f.乔托
一个严格的 MVC 框架,严格分离模型、视图和控制器元素,Giotto 确保设计者、Web 开发者和系统管理员可以独立工作。它还包括控制器模块,允许您在 web、irc 或命令行上构建应用程序。这些都是最流行的 Python web 框架。
g.神交
Grok 构建在现有的 Zope 3 库之上。它旨在通过强调约定胜于配置和 DRY(不要重复自己)来提供更容易的学习曲线和更敏捷的开发体验。
h.塔架
Python Web Framework — Python Pylons
Pylons 是一个轻量级 Web 框架,旨在实现灵活性和快速开发。它结合了 Ruby、Python 和 Perl 的最佳思想,形成了一个结构化但极其灵活的 Python Web 框架。有了 Pylons,Web 开发变得快速、灵活和容易。塔是建立在粘贴的顶部。但在与金字塔合并形成塔项目后,它处于仅维护状态。
一.里尔
可以使用 Reahl 用纯 Python 开发 web 应用。但是,您可以使用常规 Python 代码来使用、定制或编写小部件。这些小部件描述了特定的服务器端和客户端行为。
j.喘息网
Wheezy 是一个轻量级、高性能和高并发性的 WSGI web 框架。它的主要特性包括路由、模型更新/验证、身份验证/授权、具有依赖性的内容缓存、中间件等等。有了这些,我们就能建立现代、高效的网络。
k.Zope2
Python Web Framework — Python Zope
Zope2 是 Python web 框架的鼻祖,它是一个网络家族。它是一个 web 框架和通用应用服务器。今天,它主要用于 CMS。我们还有 Zope3,它是一个独立的框架和相关库的集合。
长度龙卷风
Python Web Framework — Tornado
虽然 Tornado 并不出名,但它在非阻塞 I/O 方面非常出色。您可以扩展它来处理数万个开放连接。它为长轮询、WebSockets 和其他需要持续连接的应用提供了一个完美的框架。官方上,Tornado 只支持 Linux 和 BSD OS (Windows 和 Mac OS X-仅用于开发)。Tornado 起源于 FriendFeed 项目,现在属于脸书。
Python 中的非全栈框架
Python 非全栈框架将提供基础应用服务器。这要么作为它自己的独立进程在 Apache 上运行,要么在其他环境中运行。我们来看看最受欢迎的。
a.蟒蛇瓶
Bottle 是一个简单快速的微框架,可以用来创建小型 Web 应用程序。它提供带有 URL 参数支持、模板、键/值数据库和内置 HTTP 服务器的请求调度路由。它还为第三方 WSGI/HTTP-server 和模板引擎提供了适配器。这些都在一个文件中;除了 Python 标准库之外,没有任何依赖关系。
b.樱桃派
Python Web Framework — Python CherryPy
这是一个 pythonic 化的、面向对象的 HTTP 框架。CherryPy 支持的 web 应用程序是一个独立的 Python 应用程序,它嵌入了自己的多线程 web 服务器。在某种程度上,CherryPy 是程序员和问题之间的一种方式。它还支持各种 web 服务器,如 Apache、IIS 等。CherryPy 将允许您一次启动多个 HTTP 服务器。
c.蟒蛇皮烧瓶
Python Web 框架— Python Flask
就像我们之前说过的,Flask 是 Python 的一个微框架。它包括一个内置的开发服务器和单元测试支持。它还完全支持 Unicode,支持 RESTful 请求调度和 WSGI 遵从性。
学习: Python 正则表达式
当你想开发小而简单的应用程序时,Flask 会很有用。有了它,您可以随心所欲地操作您的数据库——使用 SQLAlchemy 或其他任何东西。LinkedIn 和 Pinterest 使用 goof Flask 就是一个例子。
d.紧抱
Python Web Framework — Python Hug
Hug 是 Python 最快的 web 框架之一。有了它,您可以构建 API。它支持几个 API 版本、自动 API 文档和注释驱动的验证。它构建在另一个 JSON 框架 Falcon 之上。
e.金字塔
Python Web Framework — pyramid-positive
与我们目前讨论的一些不同,Pyramid 是大型应用程序的框架。它是灵活的;金字塔 web 应用程序从单个文件模块开始,并演变成一个雄心勃勃的项目。你可以说它使现实世界的 Web 应用程序开发和部署变得更加有趣、可预测和高效。实际上,金字塔是一个塔架项目。
f.信天翁
它是一个小型、灵活的 Python 工具包,允许您开发高度有状态的 Web 应用程序。Albatross 部署到 CGI、FastCGI 和 ModPython 服务器。
g.电路
Circuits 很像 CherryPy,但它是开发独立多进程应用程序的高效 web 框架。它支持并发、异步 I/O 组件,并且是事件驱动的。
h.猎鹰
Python Web Framework — Python Falcon
Falcon 是一个面向小型应用程序、应用程序后端和高级框架的微框架,它鼓励遵循 REST 的概念。它是 Python 最快的 web 框架之一,被 EMC、Hurricane Electric、OpenStack、Opera Software、Wargaming 和其他公司使用。
一.咆哮者
Python Web Framework — Python Growler
Growler 构建于 asyncio 之上,灵感来源于 Node.js 的 Connect 和 Express 框架,如果想要 ORM 或者模板化,必须手动安装。它通过中间件链来处理请求。
j.更多路径
MorePath 是一个灵活的、模型驱动的 web 框架。它支持 REST,关注可重用性和可扩展性。
k.比重瓶
Pycnic 是 Python 开发 JSON APIs 最快的 web 框架之一。该框架是面向对象的,并针对 JSON APIs 进行了优化。它只包含创建 Web APIs 的工具,这些工具占用的内存更少。
长度萨尼奇
Python Web Framework — Python Sanic
Sanic 是一个类似烧瓶的框架,但是速度很快。它支持异步请求处理程序,并使代码非阻塞和快速。
这就是 Python Web 框架教程。希望你喜欢我们的解释。
结论
这些是 Python web 开发中最著名的 Python Web 框架。你喜欢 python web 开发框架吗?告诉我们,你最喜欢哪一个?或者,如果您对 Python web 框架有任何疑问,请发表评论。
带有 Flask 和 Raspberry Pi 的 Python WebServer
让我们创建一个简单的网络服务器来控制你家里的事情。有很多方法可以做到这一点。例如,在我的教程:IoT——仅使用 HTML 和 Shell 脚本通过互联网控制 Raspberry Pi 机器人中,我们已经探索了如何使用light tpd web 服务器通过本地网络控制机器人。对于这里的这个项目,我们将使用 FLASK ,一个非常简单和免费的 Python 微框架。有了 Flask,通过互联网控制 Raspberry GPIOs 将变得非常简单。
看完这篇教程,请访问它的续篇: 从数据到图形:一个带 Flask 和 SQLite 的 Web Jorney
1.介绍
Flask 被称为微框架,因为它不需要特殊的工具或库。它没有数据库抽象层、表单验证或任何其他组件,而现有的第三方库提供了通用功能。然而,Flask 支持可以添加应用程序特性的扩展,就好像它们是在 Flask 本身中实现的一样。
在本教程中,我们将使用 Raspberry Pi 作为本地 Web 服务器,我们将通过一个简单的网页来控制其 3 个编程为输出的 gpio(充当致动器)和监视器 2 个编程为输入的 gpio(传感器)。
上面的框图显示了我们想要实现的目标
下面的视频可以给出一些提示:
请注意,您将在这里学习的使用 Flask 的 Python WebServer 可以直接应用于任何 Linux/OS 机器,并对 Windows PCs 进行一些调整(不幸的是,这不是我的专业领域)。
2.安装 FLASK 并设置您的 RPi 服务器
砂箱安装
首先要做的是在你的树莓派上安装 Flask。转到终端并输入:
sudo apt-get install python3-flask
当你开始一个新项目时,最好是创建一个文件夹来组织你的文件。例如:
mkdir rpiWebServer
上面的命令将创建一个名为“Server”的文件夹。我们将在那里保存我们的 python 文件(应用程序):
/home/pi/Documents/Server
在这个文件夹中,让我们创建另外两个子文件夹:静态用于 CSS 和最终的 JavaScript 文件,以及模板用于 HTML 文件(或者更准确地说,Jinja2 模板。但是不要担心。转到您新创建的文件夹:
cd rpiWebServer
并创建两个新的子文件夹:
mkdir static
和
mkdir templates
最终的文件夹“树”,看起来会像:
/rpiWebServer
/static
/templates
Python 服务器应用程序
现在,让我们用 Flask 创建我们的第一个 python 服务器:
- 打开你的 Python3 IDE,Thonny 或者 Geany。
- 在您的 IDE 上复制下面的“Hello Word”代码,并将其保存为例如 helloWorld.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello world'
if __name__ == '__main__':
app.run(debug=True, port=80, host='0.0.0.0')
上述代码的作用是:
1.将 Flask 模块加载到 Python 脚本中:
from flask import Flask
2.创建名为 app 的 Flask 对象:
app = Flask(__name__)
3.当有人访问服务器的根 URL ('/')时,运行 index()函数。在这种情况下,只发送文本“Hello World!”通过“返回”到客户的网络浏览器
def index():
return "Hello Word"
4.一旦这个脚本从命令行在终端上运行,服务器就开始“监听”端口 80,报告任何错误:
if __name__ == '__main__':
app.run(debug=True, port=80, host='0.0.0.0')
Raspberry PI IP 地址:
如果您不确定您的 RPi IP 地址,请在您的终端上运行:
ifconfig
在 wlan0: section 你会找到。就我而言:
10.0.1.27
运行应用程序
现在,运行上面的应用程序:
sudo python3 helloWorld.py
在上面的第一张图片上,你可以看到终端上会出现什么。除非您键入[CTRL] + [C],否则应用程序将会运行。
现在,你必须打开与你的树莓连接到同一个 wifi 网络的任何网络浏览器,并键入它的 IP 地址,如上面第二张图片所示。如上图所示,“Hello World”应该会出现在您的浏览器中。
注意上图底部有 2 条线。这几行显示两个不同的 web 浏览器请求了根 URL,我们的服务器返回了 HTTP 状态代码 200 表示“OK”。我在 RPI 本身(10.1.0.27)和我的 Mac (10.1.0.10)上输入了我们的 RPi 服务器地址。对于每个新的请求,只要应用程序正在运行,终端就会出现一个新的行。
3.创建合适的服务器网页
让我们完善我们的“Hello World”应用程序,创建一个 HTML 模板和一个 CSS 文件来设计我们的页面。事实上,这是很重要的,否则,你将会使 Python 脚本变得复杂。
模板
创建一个位于“模板”子文件夹中的 HTML 文件,我们可以使用单独的文件,在需要插入动态数据的地方放置占位符。
因此,我们将创建一个名为index.html的文件,保存在 /templates 中。您可以使用任何文本编辑器来创建您的 HTML 文件。可以是 Geany、终端的“nano”或位于“附件”主菜单下的 RPi 文本编辑器(LeafPad)。
GEANY 可用于同时处理所有项目文件。py;。html 和。css)对于更大更复杂的项目来说,这是一个很好的选择
好了,让我们创建 /templates/index.html :
<!DOCTYPE html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>Hello, World!</h1>
<h2>The date and time on the server is: {{ time }}</h2>
</body>
</html>
注意,HTML 模板中的 双花括号 中的任何内容都被解释为一个变量,该变量将通过 render_template 函数从 Python 脚本传递给它。
现在,让我们创建一个新的 Python 脚本。我们将把它命名为 helloWorldTemplate.py:
'''
Code created by Matt Richardson
for details, visit: http://mattrichardson.com/Raspberry-Pi-Flask/inde...
'''
from flask import Flask, render_template
import datetime
app = Flask(__name__)
@app.route("/")
def hello():
now = datetime.datetime.now()
timeString = now.strftime("%Y-%m-%d %H:%M")
templateData = {
'title' : 'HELLO!',
'time': timeString
}
return render_template('index.html', **templateData)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80, debug=True)
注意,我们使用“now”对象中的日期和时间创建了一个格式化的string("timeString"),其中存储了当前时间。
在上面的代码中,下一件重要的事情是我们创建了一个变量字典(一组键,比如与值相关联的标题,比如 HELLO!)传递到模板中。在“返回”上,我们将使用模板数据字典中的变量将index.html模板返回到网络浏览器。
执行 Python 脚本:
sudo python3 helloWorldTemplate.py
打开任何网络浏览器,输入您的 RPi IP 地址。上图是结果。
请注意,每当您使用 Python 脚本传递的实际变量数据刷新页面时,页面的内容都会发生动态变化。在我们的例子中,“标题”是一个固定值,但是“时间”每秒都在改变它。
现在,让我们在页面上添加一些样式,创建一个 CSS 文件并将其存储在/static/style.css 上:
body {
background: blue;
color: yellow;
}
您还必须修改 index.html 文件,通知它查找 style.css 文件。您可以在“head”处插入“link”:
<!DOCTYPE html>
<head>
<title>{{ title }}</title>
<link rel="stylesheet" href="../static/style.css/">
</head>
<body>
<h1>Hello, World!</h1>
<h2>The date and time on the server is: {{ time }}</h2>
</body>
</html>
记住,index.html 在“下”/template,style.css 在“下”/static,所以,注意,你必须告诉 HTML 再次“向上”和“向下”去寻找 static 子文件夹:../static/style . CSS .
下图是新风格的网页!
有关 Raspberry Pi 的更详细的 Flask 概述,请访问 Raspberry Pi 组织项目:python-we b-server-with-Flask。
4.硬件
硬件很简单。仅遵循上述电气连接。
5.读取 GPIO 状态
现在让我们通过监控它们的 GPIOs 来读取我们的“传感器”的状态。
Python 脚本
让我们创建一个新的 Python 脚本,并将其命名为 app.py:
'''
Raspberry Pi GPIO Status and Control
'''
import RPi.GPIO as GPIO
from flask import Flask, render_template
app = Flask(__name__)
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
button = 20
senPIR = 16
buttonSts = GPIO.LOW
senPIRSts = GPIO.LOW
# Set button and PIR sensor pins as an input
GPIO.setup(button, GPIO.IN)
GPIO.setup(senPIR, GPIO.IN)
@app.route("/")
def index():
# Read Sensors Status
buttonSts = GPIO.input(button)
senPIRSts = GPIO.input(senPIR)
templateData = {
'title' : 'GPIO input Status!',
'button' : buttonSts,
'senPIR' : senPIRSts
}
return render_template('index.html', **templateData)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80, debug=True)
注意,我们所做的只是将 GIPOs 20 和 16 定义为输入,读取其值并将其存储在两个变量上:buttonSts 和 *senPIRSts。*在函数 index()中,我们将通过变量字典中的“button”和“senPIR”变量将这些值传递给我们的网页: templateData。
创建 app.py 后,在终端上运行它:
python3 app.py
模板
我们还创建一个新的 index.html 来显示两个传感器的 GPIO 状态:
<!DOCTYPE html>
<head>
<title>{{ title }}</title>
<link rel="stylesheet" href='../static/style.css'/>
</head>
<body>
<h1>{{ title }}</h1>
<h2>Button pressed: {{ button }}</h1>
<h2>Motion detected: {{ senPIR }}</h2>
</body>
</html>
不要忘记刷新页面以查看结果。
按下按钮或在 PIR 传感器前移动并刷新页面。
图为网页。
6.控制 GPIOs
现在我们知道了如何“读取”GPIO 状态,让我们来改变它们。我们要做的是通过网页“命令”这些“致动器”。我们有 3 个 led 连接到树莓 GPIOs。远程命令他们,我们将改变他们的地位从低到高,反之亦然。例如,我们可以用继电器代替发光二极管来控制你房间的灯和/或风扇。
python 脚本
让我们创建一个新的 Python 脚本,并将其命名为 app.py:
'''
Raspberry Pi GPIO Status and Control
'''
import RPi.GPIO as GPIO
from flask import Flask, render_template, request
app = Flask(__name__)
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
#define actuators GPIOs
ledRed = 13
ledYlw = 19
ledGrn = 26
#initialize GPIO status variables
ledRedSts = 0
ledYlwSts = 0
ledGrnSts = 0
# Define led pins as output
GPIO.setup(ledRed, GPIO.OUT)
GPIO.setup(ledYlw, GPIO.OUT)
GPIO.setup(ledGrn, GPIO.OUT)
# turn leds OFF
GPIO.output(ledRed, GPIO.LOW)
GPIO.output(ledYlw, GPIO.LOW)
GPIO.output(ledGrn, GPIO.LOW)
@app.route("/")
def index():
# Read Sensors Status
ledRedSts = GPIO.input(ledRed)
ledYlwSts = GPIO.input(ledYlw)
ledGrnSts = GPIO.input(ledGrn)
templateData = {
'title' : 'GPIO output Status!',
'ledRed' : ledRedSts,
'ledYlw' : ledYlwSts,
'ledGrn' : ledGrnSts,
}
return render_template('index.html', **templateData)
@app.route("/<deviceName>/<action>")
def action(deviceName, action):
if deviceName == 'ledRed':
actuator = ledRed
if deviceName == 'ledYlw':
actuator = ledYlw
if deviceName == 'ledGrn':
actuator = ledGrn
if action == "on":
GPIO.output(actuator, GPIO.HIGH)
if action == "off":
GPIO.output(actuator, GPIO.LOW)
ledRedSts = GPIO.input(ledRed)
ledYlwSts = GPIO.input(ledYlw)
ledGrnSts = GPIO.input(ledGrn)
templateData = {
'ledRed' : ledRedSts,
'ledYlw' : ledYlwSts,
'ledGrn' : ledGrnSts,
}
return render_template('index.html', **templateData)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80, debug=True)
我们在上述代码中的新内容是新的“路线”:
@app.route("/<deviceName>/<action>")
在网页上,呼叫将按照以下格式生成:
[http://10.0.1.27/ledRed/on](http://10.0.1.27/ledRed/on)
或者
[http://10.0.1.27/ledRed/off](http://10.0.1.27/ledRed/off)
对于上面的例子, ledRed 是“设备名称”,而 on 或 off 是可能的“动作”的例子。
这些路线将被识别并适当地“工作”。主要步骤是:
- 例如,在其等效的 GPIO 引脚上转换字符串“ledRED”。整数变量 ledRed 相当于 GPIO13。我们将把这个值存储在变量“执行器”上
- 对于每个致动器,我们将分析“动作”,或“命令”并正确地动作。例如,如果“action = on ”,我们必须使用命令:GPIO.output(actuator,GPIO。高)
- 更新每个执行器的状态
- 更新变量库
- 把数据还给 index.html
模板
现在让我们创建一个 index.html 来显示每个执行器的 GPIO 状态,更重要的是,创建“按钮”来发送命令:
<!DOCTYPE html>
<head>
<title>GPIO Control</title>
<link rel="stylesheet" href='../static/style.css'/>
</head>
<body>
<h1>Actuators</h1>
<h2> Status </h2>
<h3> RED LED ==> {{ ledRed }}</h3>
<h3> YLW LED ==> {{ ledYlw }}</h3>
<h3> GRN LED ==> {{ ledGrn }}</h3>
<br>
<h2> Commands </h2>
<h3>
RED LED Ctrl ==>
<a href="/ledRed/on" class="button">TURN ON</a>
<a href="/ledRed/off"class="button">TURN OFF</a>
</h3>
<h3>
YLW LED Ctrl ==>
<a href="/ledYlw/on" class="button">TURN ON</a>
<a href="/ledYlw/off"class="button">TURN OFF</a>
</h3>
<h3>
GRN LED Ctrl ==>
<a href="/ledGrn/on" class="button">TURN ON</a>
<a href="/ledGrn/off"class="button">TURN OFF</a>
</h3>
</body>
</html>
只为了给一个更好的“伏”,我创建了一个类“按钮”。如果你愿意,你只能保留正常链接。
在 style.css 文件下:
body {
background: blue;
color: yellow;
}
.button {
font: bold 15px Arial;
text-decoration: none;
background-color: #EEEEEE;
color: #333333;
padding: 2px 6px 2px 6px;
border-top: 1px solid #CCCCCC;
border-right: 1px solid #333333;
border-bottom: 1px solid #333333;
border-left: 1px solid #CCCCCC;
}
图为控制我们的执行器的网站。
7.集成传感器和执行器
现在,我们必须将之前开发的两个部分放在一起。最终的 Python 脚本如下所示:
'''
Raspberry Pi GPIO Status and Control
'''
import RPi.GPIO as GPIO
from flask import Flask, render_template, request
app = Flask(__name__)
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
#define sensors GPIOs
button = 20
senPIR = 16
#define actuators GPIOs
ledRed = 13
ledYlw = 19
ledGrn = 26
#initialize GPIO status variables
buttonSts = 0
senPIRSts = 0
ledRedSts = 0
ledYlwSts = 0
ledGrnSts = 0
# Define button and PIR sensor pins as an input
GPIO.setup(button, GPIO.IN)
GPIO.setup(senPIR, GPIO.IN)
# Define led pins as output
GPIO.setup(ledRed, GPIO.OUT)
GPIO.setup(ledYlw, GPIO.OUT)
GPIO.setup(ledGrn, GPIO.OUT)
# turn leds OFF
GPIO.output(ledRed, GPIO.LOW)
GPIO.output(ledYlw, GPIO.LOW)
GPIO.output(ledGrn, GPIO.LOW)
@app.route("/")
def index():
# Read GPIO Status
buttonSts = GPIO.input(button)
senPIRSts = GPIO.input(senPIR)
ledRedSts = GPIO.input(ledRed)
ledYlwSts = GPIO.input(ledYlw)
ledGrnSts = GPIO.input(ledGrn)
templateData = {
'button' : buttonSts,
'senPIR' : senPIRSts,
'ledRed' : ledRedSts,
'ledYlw' : ledYlwSts,
'ledGrn' : ledGrnSts,
}
return render_template('index.html', **templateData)
@app.route("/<deviceName>/<action>")
def action(deviceName, action):
if deviceName == 'ledRed':
actuator = ledRed
if deviceName == 'ledYlw':
actuator = ledYlw
if deviceName == 'ledGrn':
actuator = ledGrn
if action == "on":
GPIO.output(actuator, GPIO.HIGH)
if action == "off":
GPIO.output(actuator, GPIO.LOW)
buttonSts = GPIO.input(button)
senPIRSts = GPIO.input(senPIR)
ledRedSts = GPIO.input(ledRed)
ledYlwSts = GPIO.input(ledYlw)
ledGrnSts = GPIO.input(ledGrn)
templateData = {
'button' : buttonSts,
'senPIR' : senPIRSts,
'ledRed' : ledRedSts,
'ledYlw' : ledYlwSts,
'ledGrn' : ledGrnSts,
}
return render_template('index.html', **templateData)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80, debug=True)
最后的 index.html:
<!DOCTYPE html>
<head>
<title>GPIO Control</title>
<link rel="stylesheet" href='../static/master.css'/>
</head>
<body>
<h1>RPi GPIO Control</h1>
<h2> Sensor Status </h2>
<h3> BUTTON ==> {{ button }}</h3>
<h3> MOTION ==> {{ senPIR }}</h3>
<br>
<h2> Actuator Status </h2>
<h3> RED LED ==> {{ ledRed }}</h3>
<h3> YLW LED ==> {{ ledYlw }}</h3>
<h3> GRN LED ==> {{ ledGrn }}</h3>
<br>
<h2> Commands </h2>
<h3>
RED LED Ctrl ==>
<a href="/ledRed/on" class="button">TURN ON</a>
<a href="/ledRed/off"class="button">TURN OFF</a>
</h3>
<h3>
YLW LED Ctrl ==>
<a href="/ledYlw/on" class="button">TURN ON</a>
<a href="/ledYlw/off"class="button">TURN OFF</a>
</h3>
<h3>
GRN LED Ctrl ==>
<a href="/ledGrn/on" class="button">TURN ON</a>
<a href="/ledGrn/off"class="button">TURN OFF</a>
</h3>
</body>
</html>
图为最终的网页。
8.使用模板更进一步
正如我们之前简单讨论过的, render_template()函数调用与 Flask 框架捆绑在一起的 Jinja2 模板引擎。Jinja2 用 render_template()调用中提供的参数给出的相应值替换{{ … }}块。
但这并不仅仅是 Jinja2 能做的。例如,模板也支持在{% … %}块中给出的控制语句。我们可以改变我们的 index.html 模板,以便添加条件语句,根据执行器的实际值来部署特定的按钮。换句话说,“切换”致动器状态。让我们用条件语句重写 index.html 的状态和命令部分:
<!DOCTYPE html>
<head>
<title>GPIO Control</title>
<link rel="stylesheet" href='../static/master.css'/>
</head>
<body>
<h1>RPi GPIO Control</h1>
<h2> Sensor Status </h2>
<h3> BUTTON ==> {{ button }}</h3>
<h3> MOTION ==> {{ senPIR }}</h3>
<br>
<h2> Actuator Status & Control </h2>
<h3> RED LED ==> {{ ledRed }} ==>
{% if ledRed == 1 %}
<a href="/ledRed/off"class="button">TURN OFF</a>
{% else %}
<a href="/ledRed/on" class="button">TURN ON</a>
{% endif %}
</h3>
<h3> YLW LED ==> {{ ledYlw }} ==>
{% if ledYlw == 1 %}
<a href="/ledYlw/off"class="button">TURN OFF</a>
{% else %}
<a href="/ledYlw/on" class="button">TURN ON</a>
{% endif %}
</h3>
<h3> GRN LED ==> {{ ledGrn }} ==>
{% if ledGrn == 1 %}
<a href="/ledGrn/off"class="button">TURN OFF</a>
{% else %}
<a href="/ledGrn/on" class="button">TURN ON</a>
{% endif %}
</h3>
</body>
</html>
下图显示了结果:
这只是 Python 和 Flask 所能做的一小部分。如果你真的想深入了解 Flask,你可以跟随 Miguel Grimberg 的伟大教程:Flask Mega 教程,
9.结论
一如既往,我希望这个项目可以帮助其他人找到进入令人兴奋的电子世界的方法!
详情和最终代码,请访问我的 GitHub 仓库: RPi-Flask-WebServer
而要学习如何处理数据、图形、数据库,也可以看我的教程:从数据到图形。带烧瓶和 SQLite 的 Web Jorney
更多项目,请访问我的博客:【MJRoBot.org
来自世界南部的 Saludos!
下节课再见!
谢谢你,
马塞洛
Python 的生成器表达式:将大型数据集放入内存
Don’t forget to stay hydrated while you code. Source: Pixabay
生成器表达式是 Python 中一个有趣的特性,它允许我们创建延迟生成的可迭代对象。如果您的数据不适合内存,它们可能是解决方案。
这篇文章是我写的介绍列表理解表达的那篇文章的后续,如果你以前从未接触过这个主题,我建议你在这篇文章之前读一读。
什么是生成器表达式?
为了用生成器创建一个 Iterable,你所要做的就是写一个 List Comprehension,但是用圆括号代替方括号。所有关于列表理解的语法规则在这里都适用:你可以用一个结尾的 if 子句过滤一个生成器,并用两个嵌套的 for 循环从一个矩阵中生成一个生成器。
不过生成器有一个有趣的特性,那就是它们以一种懒惰的方式生成它们的 Iterable 对象:Iterable 中的第 i 个元素直到必要时才会被创建(因此不会占用宝贵的虚拟内存)。作为一个 catch,您不能像对列表那样对生成器进行索引或切片——而不是从 Iterable 中检索任意元素,您只能按顺序迭代它。这也是为什么不能在生成器上调用 len 函数的原因。
使用发电机的优势:一个简单的实验
为了证明发电机为什么有用,我进行了以下实验:
如您所见,生成器存储“相同”的信息,仅使用 80 字节,而列表占用了 80Mb。发电机的加载速度也快了很多,虽然我们在这里谈论的是几秒钟。很明显,在任何内存不足的问题中,用生成器替换列表可能是一个明智的选择,只要我们记住前面提到的注意事项(不要任意检索,不要进行 len 检查)。
作为迭代器的生成器
对于那些有 Java/C++背景的人来说,知道生成器可以和类似迭代器的接口一起使用可能会很有趣。这是通过使用 Python 2 中的 next 方法和 Python 3+中的 next 函数来完成的。这里有一个关于我们如何在 Python 2.7 中迭代生成器的例子:
我们通常会像其他可迭代对象一样迭代它:使用一个 for 循环。然而,给定循环结束或继续的非平凡条件,我们可能会在想要手动迭代的情况下结束。为此,我们将调用的下一个方法(Python 2)或函数(Python 3),直到它抛出一个 StopIteration 异常。请注意,在检索时单独生成每个元素所花费的时间加起来将与以非懒惰方式初始化整个列表所花费的时间一样多。最后,给定一个生成器,我们总是可以通过调用 list(our_generator),支付全部初始化成本,将它转换成一个普通的旧非懒惰列表。
发电机的一个常见用法你可能错过了
我的一位令人敬畏的读者提交了发电机的另一种使用方式。您可能熟悉我们在 Python 中打开文件并迭代其行的方式:
该代码片段实际上使用生成器一行一行地缓慢加载文件。你看,我们一直在使用发电机*!下一部夏马兰的电影怎么样?*
总而言之,我们可以在任何情况下使用生成器,只要我们只需要迭代它们的结果,而不需要关心切片、索引或返回。在这些情况下使用它们通常是好的,因为我们将能够在内存中容纳非常大的数据集,而不会损失表达能力或计算时间——只要我们只需要迭代它们,一次一个对象或一行。
这是我对生成器表达式的介绍,我希望你会觉得有用。如果您认为我应该介绍任何用例,或者您认为我应该提到的任何重要特性,以及您发现的任何明显错误,请告诉我!我也很高兴知道在阅读本文后,您是否在代码中的任何地方应用了生成器。
最后,有一本我喜欢的 O'Reilly 的书,当我开始我的数据科学之旅时,我发现它非常有用。用 Python 叫做从零开始的数据科学,大概也是我得到这份工作的一半原因。如果你读到这里,你可能会喜欢它!
你可以在我的 个人网站 看到我正在做的事情以及我最近的文章和笔记。
一如既往,继续编码!
PyTorch:第一个程序并遍历
我看到 Fast.ai 正在 PyTorch 上转移,我看到 PyTorch 最适合研究原型。因此,我决定在 PyTorch 中实现一些研究论文。我已经在平行点研究过 C-DSSM 模型。但是我的实现是在喀拉斯。我将强调黑客的观点,将代码从 Keras 移植到 PyTorch,而不是博客中的研究观点。 我的实现在nish Nik/Deep-Semantic-Similarity-Model-py torch,我也记录了代码。 更多关于 C-DSSM 车型在这里。
- C-DSSM 模型接受多个输入。一个是查询,正面单据和负面单据。在 Keras 中,您可以传递一个列表:
Model(inputs = [query, pos_doc] + neg_docs, outputs = prob)
# where query and pos_doc is the numpy array and neg_docs is a list of numpy array
在 PyTorch 中,您可以:
def forward(self, q, pos, negs):
# this is in the class definition itself, you can easily access negs[0] for the 0th element of list. I was surprised to find that it works with list
# Another way would have been:
# def forward(self, x):
# q = x[0]
# pos = x[1]
# negs = x[2:]
2.C-DSSM 模型中的第一层是 Conv1d 层。所以,我对比了 Keras 和 PyTorch 的 conv1d 文档。在喀拉斯:
keras.layers.convolutional.Conv1D(filters, kernel_size, strides=1, padding='valid', dilation_rate=1, activation=**None**, use_bias=**True**, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=**None**, bias_regularizer=**None**, activity_regularizer=**None**, kernel_constraint=**None**, bias_constraint=**None**)
在 PyTorch:
*class* torch.nn.Conv1d(*in_channels*, *out_channels*, *kernel_size*, *stride=1*, *padding=0*, *dilation=1*, *groups=1*, *bias=True*)
我必须说明:
- 内核大小:与 Keras 内核大小相同
- out_channels :同 Keras 滤镜
- in_channels :输入的通道数
我对 in_channels 感到困惑,我曾设想一个 590000 的输入,其中内核将排成一行并给出 5300 的输出。但是我错了
PyTorch 拥有动态图形,这也是为什么它对程序员来说很棒的原因,你可以看到图形创建时发生了什么。
所以我输入了:
lq
( 0 ,.,.) =
1.8577e-01 8.3356e-01 8.5541e-01 … 1.1579e-01 6.4221e-01 6.4712e-01
8.3658e-01 1.4647e-01 2.0220e-01 … 2.2165e-01 2.1841e-01 3.0833e-01
7.1619e-01 1.8811e-01 1.6903e-01 … 9.2867e-01 5.6902e-01 9.1074e-01
9.4578e-01 9.4889e-01 8.4584e-01 … 3.7839e-01 3.6997e-01 2.7487e-01
6.4675e-01 2.5806e-01 3.1640e-01 … 9.8110e-01 8.6193e-01 1.0357e-02
[torch.FloatTensor of size 3x5x90000]
我对它进行了卷积核运算,权重为:
( 0 ,.,.) =
3.0515e-01
-3.3296e-01
-4.1675e-01
1.3134e-01
4.1769e-01
和偏差:
0.1725
得到的结果是:
qc
4.5800e-02 5.3139e-01 5.3828e-01 … 2.0578e-01 4.6649e-01 -7.2540e-02
-7.0118e-02 -5.8567e-01 -5.5335e-01 … 3.0976e-01 -3.3483e-02 -1.3638e-01
-8.8448e-02 -4.1210e-02 -8.5710e-02 … -6.0755e-01 -5.5320e-01 -4.0962e-01
… ⋱ …
-2.8095e-01 1.1081e-01 7.9184e-02 … -3.7869e-01 -1.7801e-01 -4.1227e-01
-8.7498e-01 -8.1998e-01 -8.2176e-01 … -7.7750e-01 -8.6171e-01 -5.1366e-01
-5.7234e-01 -2.5415e-01 -2.5126e-01 … -5.8857e-01 -4.8405e-01 -2.4303e-01
[torch.FloatTensor of size 3x300x90000]
让我们手动计算:(内核权重*输入)+偏差
0.30515 * 0.18577 + -0.33296 * 0.83658 + -0.41675 * 0.71619 + 0.13134 * 0.94578 + 0.41769 * 0.645 + 0.1725
结果是:
0.045796651399999944
哪个接近 qc[0][0][0] 注意:我刚刚展示了第一维度的第 0 个元素。(仅与数学相关的内容) 因此,这个动态图让我直观地了解了 Conv1d 在 PyTorch 中是如何按列操作的。 上面的代码很简单:
query_len = 5
lq = np.random.rand(3, query_len, WORD_DEPTH)
lq = Variable(torch.from_numpy(lq).float())
query_conv = nn.Conv1d(WORD_DEPTH, K, FILTER_LENGTH)
# print(lq)
# print (query_conv.weight)
# print (query_conv.bias)
qc = query_conv(lq)
# print (qc)
3.在获得查询、pos 和否定文档的所有向量之后。我们必须计算余弦相似度,在 Keras 中你必须为它创建一个新层。在 PyTorch:
dots = [q_s.dot(pos_s)]
dots = dots + [q_s.dot(neg_s) for neg_s in neg_ss]
变得如此简单:)
4.现在dots是一个列表,要把它转换成 PyTorch 变量,我们只需做:
dots = torch.stack(dots)
PS:这是我在 PyTorch 中的第一个实现,如果代码中有任何问题或者你不明白的地方,请联系我。 我的 twitter 句柄是nishan tiamgithub 句柄是 nishnik 。
Pytorch:如何以及何时使用模块、顺序、模块列表和模块指令
Photo by Markus Spiske on Unsplash
嘿,我在LinkedIn过来打个招呼👋
在 Pytorch 1.7 更新
你可以在这里找到代码
Pytorch 是一个开源的深度学习框架,提供了一种创建 ML 模型的智能方式。即使文档做得很好,我仍然发现大多数人仍然能够写出糟糕的、没有条理的 PyTorch 代码。
今天我们来看看 PyTorch 的三个主要构建模块是如何使用的:Module, Sequential and ModuleList。我们将从一个例子开始,并不断改进。
这四个类都包含在torch.nn中
模块:主要构建模块
模块是主要的构建模块,它定义了所有神经网络的基类,您必须对其进行子类化。
让我们创建一个经典的 CNN 分类器作为示例:
MyCNNClassifier( (conv1): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (bn1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (bn2): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (fc1): Linear(in_features=25088, out_features=1024, bias=True) (fc2): Linear(in_features=1024, out_features=10, bias=True) )
这是一个非常简单的分类器,编码部分使用两层 3x3 convs + batchnorm + relu,解码部分使用两个线性层。如果您不熟悉 PyTorch,您可能以前见过这种类型的编码,但是有两个问题。
如果我们想增加一层,我们必须再次在__init__和forward函数中写很多代码。此外,如果我们有一些想要在另一个模型中使用的公共块,例如 3x3 conv + batchnorm + relu,我们必须重新编写它。
连续:堆叠和合并层
顺序是模块的容器,可以堆叠在一起,同时运行。
你可以注意到我们已经把所有东西都储存到self里了。我们可以使用Sequential来改进我们的代码。
MyCNNClassifier( (conv_block1): Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) (conv_block2): Sequential( (0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) (decoder): Sequential( (0): Linear(in_features=25088, out_features=1024, bias=True) (1): Sigmoid() (2): Linear(in_features=1024, out_features=10, bias=True) ) )
好多了 uhu?
你有没有注意到conv_block1和conv_block2看起来几乎一样?我们可以创建一个重述nn.Sequential的函数来简化代码!
然后我们可以在模块中调用这个函数
MyCNNClassifier( (conv_block1): Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) (conv_block2): Sequential( (0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) (decoder): Sequential( (0): Linear(in_features=25088, out_features=1024, bias=True) (1): Sigmoid() (2): Linear(in_features=1024, out_features=10, bias=True) ) )
更干净!还是conv_block1和conv_block2差不多!我们可以用nn.Sequential合并它们
MyCNNClassifier( (encoder): Sequential( (0): Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) (1): Sequential( (0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) (decoder): Sequential( (0): Linear(in_features=25088, out_features=1024, bias=True) (1): Sigmoid() (2): Linear(in_features=1024, out_features=10, bias=True) ) )
self.encoder现占据展位conv_block。我们已经为我们的模型解耦了逻辑,使它更容易阅读和重用。我们的conv_block功能可以导入到另一个模型中使用。
动态连续:一次创建多个层
如果我们可以在self.encoder中添加一个新的层,硬编码会不方便:
如果我们可以将大小定义为一个数组并自动创建所有的层,而不需要写入每一层,这是不是很好?幸运的是,我们可以创建一个数组并将其传递给Sequential
MyCNNClassifier( (encoder): Sequential( (0): Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) (1): Sequential( (0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) (decoder): Sequential( (0): Linear(in_features=25088, out_features=1024, bias=True) (1): Sigmoid() (2): Linear(in_features=1024, out_features=10, bias=True) ) )
我们来分解一下。我们创建了一个数组self.enc_sizes来保存编码器的大小。然后我们通过迭代大小创建一个数组conv_blocks。因为我们必须给 booth 一个尺寸,并给每一层一个特大号,所以我们通过将数组移动一位来实现数组本身。
为了清楚起见,看一下下面的例子:
1 32 32 64
然后,由于Sequential不接受列表,我们通过使用*操作符来分解它。
Tada!现在,如果我们只想添加一个尺寸,我们可以很容易地在列表中添加一个新的数字。将大小作为参数是一种常见的做法。
MyCNNClassifier( (encoder): Sequential( (0): Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) (1): Sequential( (0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) (2): Sequential( (0): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) (decoder): Sequential( (0): Linear(in_features=25088, out_features=1024, bias=True) (1): Sigmoid() (2): Linear(in_features=1024, out_features=10, bias=True) ) )
我们可以对解码器部分做同样的事情
MyCNNClassifier( (encoder): Sequential( (0): Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) (1): Sequential( (0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) (decoder): Sequential( (0): Sequential( (0): Linear(in_features=25088, out_features=1024, bias=True) (1): Sigmoid() ) (1): Sequential( (0): Linear(in_features=1024, out_features=512, bias=True) (1): Sigmoid() ) ) (last): Linear(in_features=512, out_features=10, bias=True) )
我们遵循相同的模式,我们为解码部分创建一个新的块,linear + sigmoid,并传递一个包含大小的数组。我们必须添加一个self.last,因为我们不想激活输出
现在,我们甚至可以将我们的模型一分为二!编码器+解码器
MyCNNClassifier( (encoder): MyEncoder( (conv_blokcs): Sequential( (0): Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) (1): Sequential( (0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() ) ) ) (decoder): MyDecoder( (dec_blocks): Sequential( (0): Sequential( (0): Linear(in_features=1024, out_features=512, bias=True) (1): Sigmoid() ) ) (last): Linear(in_features=512, out_features=10, bias=True) ) )
注意MyEncoder和MyDecoder也可以是返回nn.Sequential的函数。我更喜欢对模型使用第一种模式,对构建模块使用第二种模式。
通过将我们的模块分成子模块,共享代码、调试代码、T23 测试代码变得更加容易。
ModuleList:当我们需要迭代时
ModuleList允许您将Module存储为列表。当你需要遍历层并存储/使用一些信息时,比如在 U-net 中,它会很有用。
Sequential之间的主要区别是ModuleList没有forward方法,所以内层没有连接。假设我们需要解码器中每个层的每个输出,我们可以通过以下方式存储它:
torch.Size([4, 16]) torch.Size([4, 32]) [None, None]
ModuleDict:当我们需要选择时
如果我们想在我们的conv_block中切换到LearkyRelu呢?我们可以使用ModuleDict创建一个Module的字典,并在需要时动态切换Module
Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): LeakyReLU(negative_slope=0.01) ) Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() )
最终实施
让我们把一切都结束吧!
MyCNNClassifier( (encoder): MyEncoder( (conv_blokcs): Sequential( (0): Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): LeakyReLU(negative_slope=0.01) ) (1): Sequential( (0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): LeakyReLU(negative_slope=0.01) ) ) ) (decoder): MyDecoder( (dec_blocks): Sequential( (0): Sequential( (0): Linear(in_features=1024, out_features=512, bias=True) (1): Sigmoid() ) ) (last): Linear(in_features=512, out_features=10, bias=True) ) )
结论
你可以在这里找到代码
综上所述。
- 当你有一个由多个小块组成大块时,使用
Module - 当你想从层中创建一个小块时,使用
Sequential - 当你需要迭代一些层或构建块并做一些事情时,使用
ModuleList - 当需要参数化模型的某些模块时,例如激活功能,使用
ModuleDict
那都是乡亲们!
感谢您的阅读
原载于gist.github.com。
Pytorch 实现实时风格转换的感知损失
在这篇文章中,我将简要回顾我在 Pytorch 中编写和训练实时风格转换模型的经历。这项工作在很大程度上基于 Abhishek Kadian 的实现,,它工作得非常好。我做了一些修改,既是为了好玩,也是为了更熟悉 Pytorch。
该模型使用中描述的方法进行实时风格转换和超分辨率以及实例归一化。(未实现超分辨率)
我在实现中添加了三个主要部分:
- 使用官方预培训的 VGG 模型
- 在培训期间输出中间结果
- 添加总变差正则化如文中所述
使用官方预先训练的 VGG 模型
Model structure from the paper
首先,我们需要快速浏览一下模型结构。本文的主要贡献是提出将生成的图像前馈到预训练的图像分类模型,并从一些中间层提取输出来计算损失,这将产生类似于 Gatys 等人的的结果,但具有明显更少的计算资源。因此,该结构的第一部分是一个“图像变换网络”,它从输入图像生成新的图像。而第二部分简单来说就是一个“损耗网络”,也就是前馈部分。损失网络的权重是固定的,在训练过程中不会更新。
Abhishek 的实现使用传统的 VGG 模型,具有 BGR 信道顺序和[-103.939,-116.779,-123.680]到中心信道的偏移(这似乎也是该论文所使用的)。【pytorch 官方预训车型使用统一格式:
所有预训练模型都期望输入图像以相同的方式归一化,即形状为(3 x H x W)的 3 通道 RGB 图像的小批量,其中 H 和 W 预计至少为 224。必须将图像加载到[0,1]的范围内,然后使用
mean = [0.485, 0.456, 0.406]和std = [0.229, 0.224, 0.225]进行归一化
下面是从官方预训练模型中提取输出的代码:
**LossOutput = namedtuple("LossOutput", ["relu1_2", "relu2_2", "relu3_3", "relu4_3"])
# [https://discuss.pytorch.org/t/how-to-extract-features-of-an-image-from-a-trained-model/119/3](https://discuss.pytorch.org/t/how-to-extract-features-of-an-image-from-a-trained-model/119/3)
class LossNetwork(torch.nn.Module):
def __init__(self, vgg_model):
super(LossNetwork, self).__init__()
self.vgg_layers = vgg_model.features
self.layer_name_mapping = {
'3': "relu1_2",
'8': "relu2_2",
'15': "relu3_3",
'22': "relu4_3"
}
def forward(self, x):
output = {}
for name, module in self.vgg_layers._modules.items():
x = module(x)
if name in self.layer_name_mapping:
output[self.layer_name_mapping[name]] = x
return LossOutput(**output)**
启动:
**vgg_model = vgg.vgg16(pretrained=True)
if torch.cuda.is_available():
vgg_model.cuda()
loss_network = LossNetwork(vgg_model)
loss_network.eval()**
除非明确指定,否则在 VGG 模型中没有批处理规范化。因此,激活值与之前的实现相比有很大不同。一般来说,你需要按比例增加样式损失(gram 矩阵),因为大多数激活小于 1,采用点积会使它更小。
在培训期间输出中间结果
Epoch 2, 75200th training sample
这在根据风格权重比例调整内容权重时很有帮助。您可以在训练过程中停止训练并重新调整参数,而不必等待 4 个小时才能完成训练。
添加总变差正则化如文中所述
论文在实验部分提到了这一点,但似乎 Abhishek 并没有实现:
输出图像用总变分正则化进行正则化,强度在 1 × 10e-6 和 1 ×10e-4 之间,通过每个样式目标的交叉验证进行选择。
The total variation norm formula for 2D signal images from Wikipedia
这很容易实现:
**reg_loss = REGULARIZATION * (
torch.sum(torch.abs(y[:, :, :, :-1] - y[:, :, :, 1:])) +
torch.sum(torch.abs(y[:, :, :-1, :] - y[:, :, 1:, :]))
)**
Pytorch 亲笔签名将为您处理反向传播。在实践中,我还没有发现如何调整正则化权重。到目前为止,我使用的权重似乎对输出图像没有太大影响。
培训结果
该模型使用 Microsoft COCO 数据集进行训练。图像的大小被调整为 256×256,网络被训练大约 2 个时期,批次大小为 4(与纸张相同)。使用 GTX1070 的训练时间约为 4 到 4.5 小时,与论文报道的时间相当。根据我粗略的实验,大量的计算时间被用于标准化输入图像。如果我们使用原始的 VGG 模型(未测试),训练可能会更快。在一些手动调整之后,内容权重与样式的比率通常被设置为 1 : 10e3 ~ 10e5。
An example model (waterfall) (pardon my poor GIMP skill…)
因为网络是完全卷积的,你可以在测试时间向网络提供比 256 x 256 更大或更小的图像。我写了一些脚本来转换动画 gif 和视频,使用 scikit-video 和 ffmpeg 来取乐:
Some additional style images used
Mosaic-styled cat typing
原视频鸣谢:背包环游新西兰。处理速度在每秒 4 帧左右。我没有调准帧率,所以下面几个视频的时间都比原来慢。
Mosaic style
Candy Style
经验教训
- 记住将输出 numpy 数组裁剪到[0,255]范围,并将其转换为 uint8。否则 matplot.pyplot.imshow 会显示奇怪的结果。花了相当多的时间在错误的方向上调试,认为模型训练代码有一些严重的错误。
- 记得使用 model.train() 和 model.eval()。它仅对包含丢弃或批量规范化图层的模型有效,但这是一个需要保持的好习惯。如果你来自喀拉斯,尤其容易忘记。
可能的改进和未来的工作
- 有时网络会在开放区域生成一些奇怪的补丁。还不知道是哪里来的,怎么修。
- 对于不同风格的图像,为 relu1_2、relu2_2、relu3_3、relu4_3 输出分配不同的权重可能会产生更好的结果。
- 尝试使用不同的预训练网络作为损耗网络。
- 也尝试实现超分辨率
- 比较来自循环的结果。我试过 CycleGAN,但训练时间太长,我当时失去了耐心。应该稍后再试。
- 生成视频时,调整连续帧之间的变化。这应该有助于减少播放过程中的闪烁。我听说有人使用这种技术,但还不知道如何实际操作。
A sample of the weird patches
感谢阅读!
代码位于本 Github repo 的。由于这基本上是一个个人有趣的项目,文档是不存在的,代码的主要部分驻留在两个 Jupyter 笔记本中: style-transfer.ipynb 和 Video.ipynb 。很抱歉。
(2011/11/30 更新:修复了上述笔记本的链接。与此同时,这里有更新版本的笔记本:01-image-style-transfer . ipynb和 03-Video-Creation.ipynb 。)
PyTorch 教程精华
从 TensorFlow 迁移到 PyTorch
当我刚开始研究 PyTorch 时,几天后我就放弃了。与 TensorFlow 相比,我很难理解这个框架的核心概念。这就是为什么我把它放在我的“知识书架”上,然后忘记了它。但是不久前,PyTorch 的新版本发布了。所以我决定再给它一次机会。过了一段时间,我明白了这个框架真的很容易使用,它让我很高兴用 PyTorch 编码。在这篇文章中,我会试着解释清楚它的核心概念,这样你会有动力至少现在就试一试,而不是在几年或更久之后。我们将涵盖一些基本原则和一些先进的东西,如学习率调度,自定义层等。
资源
首先,你应该知道 PyTorch 的文档和教程是分开存放的。有时,由于快速的开发和版本变化,他们可能不会见面。所以填免费调查源代码。它非常清晰明了。最好提一下,有很棒的 PyTorch 论坛,在那里你可以问任何合适的问题,你会很快得到答案。对于 PyTorch 用户来说,这个地方似乎比 StackOverflow 更受欢迎。
PyTorch as NumPy
所以让我们深入 PyTorch 本身。PyTorch 的主要构件是张量。真的,它们和 NumPy 的很像。Tensors 支持很多相同的 API,所以有时你可以使用 PyTorch 作为 NumPy 的替代。你可能会问这是什么原因。主要目标是 PyTorch 可以利用 GPU,以便您可以将数据预处理或任何其他计算密集型工作转移到机器学习领域。很容易将张量从 NumPy 转换成 PyTorch,反之亦然。让我们检查一些代码示例:
从张量到变量
张量是 PyTorch 中令人敬畏的一部分。但主要我们想要的是建立一些神经网络。什么是反向传播?当然我们可以手动实现,但是这是什么原因呢?谢天谢地,自动微分存在。为了支持它,PyTorch 为你提供了变量。变量是张量之上的包装器。有了它们,我们可以建立我们的计算图,并在以后自动计算梯度。每个变量实例都有两个属性:.data包含初始张量本身,.grad包含相应张量的梯度。
您可能会注意到,我们已经手动计算并应用了我们的梯度。太乏味了。我们有优化器吗?当然啦!
现在我们所有的变量都会自动更新。但是你应该从上一个片段中得到的要点是:在计算新的梯度之前,我们仍然应该手动置零梯度。这是 PyTorch 的核心概念之一。有时,我们为什么要这样做可能不是很明显,但另一方面,我们可以完全控制我们的渐变,何时以及如何应用它们。
静态与动态计算图
PyTorch 和 TensorFlow 的下一个主要区别是它们的图形表示方法。Tensorflow 使用静态图,这意味着我们定义它一次,然后一次又一次地执行那个图。在 PyTorch 中,每次向前传递都会定义一个新的计算图。一开始,这些方法之间的区别并不是很大。但是当你想调试你的代码或者定义一些条件语句时,动态图就变得很少了。您可以直接使用您喜欢的调试器!比较 while 循环语句的下两个定义 TensorFlow 中的第一个和 PyTorch 中的第二个:
在我看来,第二个解决办法比第一个容易得多。你对此有什么看法?
模型定义
好了,现在我们看到在 PyTorch 中构建一些 if/else/while 复杂语句很容易。但是让我们回到通常的模型。该框架提供了非常类似于 Keras 的现成层构造器:
nn包定义了一组模块,大致相当于神经网络层。模块接收输入变量并计算输出变量,但也可以保存内部状态,例如包含可学习参数的变量。nn包还定义了一组在训练神经网络时常用的有用的损失函数。
此外,如果我们想构建更复杂的模型,我们可以子类化提供的nn.Module类。当然,这两种方法可以混合使用。
在__init__方法中,我们应该定义所有将在以后使用的层。在forward方法中,我们应该提出如何使用已经定义的层的步骤。像往常一样,向后传球将被自动计算。
自定义层
但是如果我们想定义一些带有非标准 backprop 的定制模型呢?这里有一个例子——XNOR 网络公司:
我不会深究细节,更多关于这种类型的网络,你可以在初始论文中读到。与我们的问题相关的是,反向传播应该只应用于小于 1 和大于-1 的权重。在 PyTorch 中,它可以很容易地实现:
正如你所看到的,我们应该只定义两个方法:一个用于向前传递,一个用于向后传递。如果我们需要从正向传递中访问一些变量,我们可以将它们存储在ctx变量中。注意:在以前的 API 中,向前/向后方法不是静态的,我们将所需的变量存储为self.save_for_backward(input),并以input, _ = self.saved_tensors的形式访问它们。
使用 CUDA 训练模型
我们之前讨论过如何将一个张量传递给 CUDA。但是如果要传递整个模型,从模型本身调用.cuda()方法就可以了,把每个输入变量包装到.cuda()就够了。在所有的计算之后,我们应该用.cpu()方法得到结果。
此外,PyTorch 在源代码中支持直接设备分配:
因为有时我们希望在 CPU 和 GPU 上运行相同的模型,而不修改代码,所以我提出了某种包装器:
重量初始化
在张量流中,权重初始化主要在张量声明期间进行。PyTorch 提供了另一种方法——首先,应该声明张量,下一步应该改变张量的权重。权重可以初始化为对张量属性的直接访问,调用torch.nn.init包中的一堆方法。这个决定可能不是很直接,但当您想要用相同的初始化来初始化某个类型的所有层时,它会变得很有用。
从反向中排除子图
有时,当您想要重新训练模型的某些层或为生产模式做准备时,您可以禁用某些层的亲笔签名机制,这非常有用。为此, PyTorch 提供了两个标志 : requires_grad和volatile。第一个将禁用当前层的渐变,但子节点仍然可以计算一些。第二个将禁用当前层和所有子节点的自动签名。
培训过程
PyTorch 中还存在其他一些附加功能。例如,你可以使用学习率调度器,它将根据一些规则调整你的学习率。或者,您可以启用/禁用具有单个训练标志的批量定额层和辍学。如果你想很容易改变随机种子分别为 CPU 和 GPU。
此外,您可以打印关于您的模型的信息,或者用几行代码保存/加载它。如果你的模型是用 OrderedDict 或基于类的模型来初始化的,那么字符串表示将包含层的名称。
根据 PyTorch 文档,使用state_dict()方法的保存模型更可取。
记录
记录训练过程是非常重要的一部分。不幸的是,PyTorch 没有任何类似 tensorboard 的工具。因此,您可以使用带有 Python 日志模块的普通文本日志,或者尝试一些第三方库:
数据处理
您可能还记得 TensorFlow 中提出的数据加载器,甚至试图实现其中的一些。对我来说,花了大约 4 个小时或更多的时间来了解所有管道应该如何工作。
Image source: TensorFlow docs
最初,我想在这里添加一些代码,但我认为这样的 gif 将足以解释所有事情如何发生的基本想法。
PyTorch 开发者决定不重新发明轮子。他们只是使用多重处理。要创建您自己的定制数据加载器,从torch.utils.data.Dataset继承您的类并更改一些方法就足够了:
你应该知道的两件事。首先,图像尺寸不同于张量流。它们是[batch _ size x channels x height x width]。但是这种转换可以通过预处理步骤torchvision.transforms.ToTensor()在没有交互的情况下完成。在转换包中也有很多有用的实用程序。
第二件重要的事情是你可以在 GPU 上使用固定内存。为此,您只需要为一个cuda()调用添加一个额外的标志async=True,并从带有标志pin_memory=True的数据加载器中获取固定批次。关于此功能的更多信息在这里讨论。
最终架构概述
现在你知道了模型、优化器和许多其他东西。什么才是把它们全部合并的正确方法?我建议将您的模型和所有包装器拆分成这样的构件:
为了清楚起见,这里有一些伪代码:
结论
我希望这篇文章能让你理解 PyTorch 的要点:
- 它可以作为 Numpy 的替代产品
- 这对于原型制作来说真的很快
- 调试和使用条件流很容易
- 有许多现成的好工具
PyTorch 是一个快速发展的框架,有一个很棒的社区。我认为今天是尝试的最佳时机!
PyTorch 与 TensorFlow: 1 个月总结
py torch 与 TensorFlow 合作一个月后的对比。
在我深度学习工作的大部分时间里,我一直是 TensorFlow 的用户。然而,当我加入英伟达时,我们决定改用 py torch——只是作为一种测试。以上是我对它的体验。
装置
安装非常简单明了。PyTorch 可以通过 PIP 安装,也可以从源代码构建。PyTorch 还提供 Docker 图像,可以用作您自己项目的基础图像。
PyTorch 不像 TensorFlow 那样有指定的 CPU 和 GPU 版本。虽然这使安装更容易,但如果您想同时支持 CPU 和 GPU 的使用,它会生成更多的代码。
值得注意的是 PyTorch 还没有提供正式的 windows 发行版。有到 windows 的非官方端口,但是没有 PyTorch 的支持。
使用
PyTorch 提供了一个非常 Pythonic 化的 API。这与 TensorFlow 有很大不同,在 tensor flow 中,您应该定义所有的张量和图形,然后在一个会话中运行它。
在我看来,这会产生更多但更干净的代码。PyTorch 图必须在从 PyTorch nn.Module类继承的类中定义。当图形运行时,调用一个forward()函数。使用这种“约定优于配置”的方法,图的位置总是已知的,并且变量不会在代码的其余部分全部定义。
这种“新”的方法需要一些时间来适应,但我认为如果你以前在深度学习之外使用过 Python,这是非常直观的。
根据一些评论,PyTorch 还在许多模型上显示出比 TensorFlow 更好的性能。
证明文件
文件大部分是完整的。我总能找到函数或模块的定义。与 TensorFlow 相反,在 tensor flow 中,所有函数都只有一个页面,PyTorch 每个模块只使用一个页面。如果你来自谷歌,寻找一个功能,这就有点困难了。
社区
显然 PyTorch 的社区没有 TensorFlow 的社区大。然而,许多人喜欢在空闲时间使用 PyTorch,即使他们在工作中使用 TensorFlow。我认为 PyTorch 一退出 Beta,这种情况就会改变。
目前来看,在 PyTorch 中找到精通的人还是有点困难。
这个社区非常大,官方论坛上的问题通常都能很快得到答案,因此很多伟大的神经网络实现的例子都被翻译成 PyTorch。
工具和助手
尽管 PyTorch 提供了相当多的工具,但是缺少一些非常有用的工具。缺少的最有用的工具之一是 TensorFlow 的 TensorBoard。这使得虚拟化有点困难。
还有一些很常用的帮手不见了。这比 TensorFlow 需要更多的自写代码。
结论
PyTorch 是 TensorFlow 的绝佳替代品。由于 PyTorch 仍处于测试阶段,我希望在可用性、文档和性能方面有更多的变化和改进。
PyTorch 非常 pythonic 化,使用起来感觉很舒服。它有一个很好的社区和文档。据说比 TensorFlow 还快一点。
然而,与 TensorFlow 相比,这个社区仍然很小,并且缺少一些有用的工具,如 TensorBoard。
PyTorch 与 TensorFlow —发现差异
在这篇文章中,我想探讨两个流行的深度学习框架:PyTorch 和 TensorFlow 之间的一些关键异同。为什么是这两个而不是其他的?有许多深度学习框架,其中许多是可行的工具,我选择这两个只是因为我对具体比较它们感兴趣。
起源
TensorFlow 由 Google Brain 开发,并在 Google 积极用于研究和生产需求。它的闭源前身叫做 DistBelief。
PyTorch 是在脸书开发和使用的基于 lua 的 Torch 框架的表亲。然而,PyTorch 并不是一套简单的支持流行语言的包装器,它被重新编写和修改以使速度更快,感觉更自然。
比较两个框架的最好方法是在两个框架中都编写一些代码。我已经为这个帖子写了一个配套的 jupyter 笔记本,你可以在这里得到它。所有代码都将在帖子中提供。
首先,让我们为两个框架中的以下函数编写一个简单的近似器:
我们将尝试找到未知参数φ给定数据 x 和函数值 f(x) 。是的,使用随机梯度下降是一种矫枉过正,解析解可能很容易找到,但这个问题将作为一个简单的例子很好地服务于我们的目的。
我们将首先用 PyTorch 解决这个问题:
如果你在深度学习框架方面有一些经验,你可能已经注意到我们正在手工实现梯度下降。不太方便吧?令人高兴的是,PyTorch 拥有optimize模块,其中包含了诸如 RMSProp 或 Adam 等流行优化算法的实现。我们将带着动力使用新加坡元
Loss function and exponent plots for PyTorch
正如你所看到的,我们很快从训练数据中推断出真实指数。现在让我们继续张量流:
Loss function and exponent plots for TensorFlow
如您所见,TensorFlow 中的实现也可以工作(令人惊讶的是🙃).它花费了更多的迭代来恢复指数,但我确信原因是我没有充分利用 optimiser 的参数来达到可比的结果。
现在我们准备探索一些不同之处。
差异#0 —采用
目前,TensorFlow 被许多研究人员和行业专业人士视为随身工具。这个框架有很好的文档记录,如果文档还不够,互联网上有很多写得非常好的教程。你可以在 github 上找到数百个已经实现和训练过的模型,从这里开始。
PyTorch 与其竞争对手相比相对较新(仍处于测试阶段),但它的发展势头很快。文档和官方教程也不错。PyTorch 还包括几个超级易用的流行计算机视觉架构的实现。
区别#1 —动态与静态图形定义
这两个框架都在张量上操作,并将任何模型视为有向无环图(DAG ),但是它们在如何定义它们上有很大的不同。
TensorFlow 遵循“数据即代码,代码即数据”习语。在 TensorFlow 中,您可以在模型运行之前静态定义图形。与外部世界的所有通信都是通过tf.Session对象和tf.Placeholder进行的,它们是在运行时将被外部数据替代的张量。
在 PyTorch 中,事情更加命令化和动态化:您可以随时定义、更改和执行节点,没有特殊的会话接口或占位符。总的来说,该框架与 Python 语言集成得更紧密,大多数时候感觉更原生。当你在 TensorFlow 中写作时,有时你会觉得你的模型在一堵砖墙后面,有几个小孔可以沟通。无论如何,这听起来或多或少还是一个品味问题。
然而,这些方法不仅在软件工程的角度上有所不同:有几种动态神经网络体系结构可以从动态方法中受益。回想一下 RNNs:对于静态图,输入序列长度将保持不变。这意味着,如果你为英语句子开发一个情感分析模型,你必须将句子长度固定到某个最大值,并用零填充所有较小的序列。不太方便吧。在递归 RNNs 和树形 RNNs 领域,你会遇到更多的问题。目前 Tensorflow 通过 Tensorflow Fold 对动态输入的支持有限。PyTorch 默认拥有它。
区别#2 —调试
由于 PyTorch 中的计算图是在运行时定义的,所以您可以使用我们最喜欢的 Python 调试工具,如 pdb、ipdb、PyCharm 调试器或旧的可信打印语句。
TensorFlow 就不是这样。您可以选择使用名为 tfdbg 的特殊工具,该工具允许在运行时计算张量流表达式,并浏览会话范围内的所有张量和操作。当然,你不能用它来调试任何 python 代码,所以有必要单独使用 pdb。
差异#3 —可视化
Tensorboard 在可视化方面非常棒😎。这个工具是 TensorFlow 自带的,对于调试和比较不同的训练运行非常有用。例如,假设您训练了一个模型,然后调整了一些超参数并再次训练它。两次运行可以同时显示在 Tensorboard 上,以指示可能的差异。Tensorboard 可以:
- 显示模型图
- 绘制标量变量
- 可视化分布和直方图
- 可视化图像
- 可视化嵌入
- 播放音频
Tensorboard 可以显示各种摘要,这些摘要可以通过tf.summary模块收集。我们将为我们的玩具指数示例定义汇总操作,并使用tf.summary.FileWriter将它们保存到磁盘。
启动 Tensorboard 执行tensorboard --logdir=./tensorboard。这个工具在云实例上使用非常方便,因为它是一个 webapp。
PyTorch 这边的 Tensorboard 竞争对手是 visdom 。它功能不全,但使用起来更方便。另外,与 Tensorboard 的集成确实存在。此外,您可以自由使用标准绘图工具— matplotlib 和 seaborn 。
差异#4 —部署
如果我们开始谈论部署,TensorFlow 现在显然是赢家:它有 TensorFlow 服务,这是一个在专用 gRPC 服务器上部署您的模型的框架。也支持移动。
当我们切换回 PyTorch 时,我们可能会使用 Flask 或另一种替代方式在模型之上编写一个 REST API。如果 gRPC 不适合您的用例,这也可以用张量流模型来完成。然而,如果性能是一个问题,TensorFlow 服务可能是一个更好的选择。
Tensorflow 还支持 PyTorch 目前缺乏的分布式训练。
差异#5 —数据并行
PyTorch 区别于 TensorFlow 的最大特征之一是声明式数据并行性:您可以使用torch.nn.DataParallel包装任何模块,它将(几乎神奇地)在批处理维度上并行化。通过这种方式,您几乎可以毫不费力地利用多个 GPU。
另一方面,TensorFlow 允许您微调要在特定设备上运行的每个操作。尽管如此,定义并行性需要更多的手动操作,并且需要仔细思考。考虑在 TensorFlow 中实现类似于DataParallel的代码:
def make_parallel(fn, num_gpus, **kwargs):
in_splits = {}
for k, v in kwargs.items():
in_splits[k] = tf.split(v, num_gpus)out_split = []
for i in range(num_gpus):
with tf.device(tf.DeviceSpec(device_type="GPU", device_index=i)):
with tf.variable_scope(tf.get_variable_scope(), reuse=tf.AUTO_REUSE):
out_split.append(fn(**{k : v[i] for k, v in in_splits.items()}))return tf.concat(out_split, axis=0)def model(a, b):
return a + bc = make_parallel(model, 2, a=a, b=b)
也就是说,当使用 TensorFlow 时,你可以实现你在 PyTorch 中可以做的一切,但需要更多的努力(你有更多的控制权作为奖励)。
同样值得注意的是,这两个框架都支持分布式执行,并为定义集群提供了高级接口。
区别# 6——框架还是库
让我们为手写数字建立一个 CNN 分类器。现在 PyTorch 将真正开始看起来像一个框架。回想一下,编程框架为我们提供了某些领域中有用的抽象,以及使用它们来解决具体问题的便捷方式。这就是框架和库的本质区别。
这里我们介绍datasets模块,它包含用于测试深度学习架构的流行数据集的包装器。另外nn.Module用于构建一个定制的卷积神经网络分类器。nn.Module是 PyTorch 给我们创建复杂深度学习架构的一个积木。在torch.nn包中有大量现成的模块,我们可以将其用作我们模型的基础。请注意 PyTorch 如何使用面向对象的方法来定义基本的构建模块,并在提供通过子类化来扩展功能的能力的同时,给我们一些“轨道”来继续前进。
下面是对稍加修改的版本 https://github . com/py torch/examples/blob/master/mnist/main . py:
普通 TensorFlow 感觉更像一个库,而不是一个框架:所有的操作都是非常低级的,即使你可能不想写,也需要编写大量的样板代码(让我们一次又一次地定义这些偏差和权重……)。
随着时间的推移,围绕 TensorFlow 出现了一整套高级包装器的生态系统。这些都旨在简化您使用库的方式。他们中的许多人目前位于tensorflow.contrib模块(这不被认为是一个稳定的 API),一些人开始迁移到主存储库(见tf.layers)。
所以,在如何使用 TensorFlow 以及什么样的框架最适合这个任务上,你有很大的自由度: TFLearn , tf.contrib.learn , Sonnet , Keras ,plain tf.layers等。老实说,Keras 值得另一篇文章,但目前不在这个比较的范围之内。
这里我们将使用tf.layers和tf.contrib.learn来构建我们的 CNN 分类器。代码遵循TF . layers 官方教程:
因此,TensorFlow 和 PyTorch 都提供了有用的抽象来减少样板代码的数量并加速模型开发。它们之间的主要区别是 PyTorch 可能感觉更“pythonic 化”,并且具有面向对象的方法,而 TensorFlow 有几个选项供您选择。
我个人认为 PyTorch 更清晰,对开发者更友好。它的torch.nn.Module给了你以 OOP 方式定义可重用模块的能力,我发现这种方法非常灵活和强大。稍后你可以通过torch.nn.Sequential组成各种模块(你好,✋🏻).此外,所有内置模块都在一个功能表中,这非常方便。总的来说,API 的所有部分都配合得很好。
当然,你可以用普通的 TensorFlow 编写非常简洁的代码,但是在你得到它之前需要更多的技巧和反复试验。当使用 Keras 或 TFLearn 这样的高级框架时,至少要准备好失去 TensorFlow 提供的一些灵活性。
结论
TensorFlow 是一个非常强大和成熟的深度学习库,具有强大的可视化功能和几个用于高级模型开发的选项。它具有生产就绪型部署选项和对移动平台的支持。如果您满足以下条件,TensorFlow 是一个不错的选择:
- 为生产开发模型
- 开发需要在移动平台上部署的模型
- 想要良好的社区支持和全面的文档
- 想要各种形式的丰富学习资源(TensorFlow 有一整个 MOOC
- 想要或需要使用 Tensorboard
- 需要使用大规模分布式模型训练
PyTorch 仍然是一个发展势头迅猛的年轻框架。如果您符合以下条件,您可能会发现它非常适合:
- 做研究或者你的生产非功能需求不是很苛刻
- 想要更好的开发和调试体验
- 爱万物蟒
如果你有时间,最好的建议是两者都试试,看看哪一个最适合你的需求。
如果你喜欢这篇文章,请留下一些👏。它让我知道我在帮忙。
PyTorch 1.0 笔记
PyTorch 1.0 即将发布。它引入了许多令人惊叹的特性,包括原生 C++ API、JIT 编译和 ONNX 集成。这意味着您将能够编写生产就绪的服务,并做 TensorFlow 服务所做的事情。这是 PyTorch 迈出的一大步,肯定会增强它作为研究和生产用途的全功能框架的地位。
联系我们
需要 TensorFlow 或 PyTorch 的帮助?请通过 datalab@cinimex.ru 联系我们
想要定期获得关于数据科学和创业的有趣资源吗?
Q-Bay:用模拟拍卖解释 Q-Learning
强化学习是一个热门话题,近年来,DeepMind 的 AlphaGo 等复杂系统利用一种叫做深度 Q 学习的方法取得了引人注目的成功,但它们是如何工作的?本文解释了 Q-Learning(深度 Q-Learning 背后的强化学习方法)是如何工作的,首先看一个应用于一个简单得多的问题的例子,即地铁网络的一个小部分的导航,然后看一个稍微复杂一点的例子,即拍卖。
Our q-Bay agent
Q 学习背后的理念
在一个 Q-Learning 问题中,一个智能体试图学习一种导航其环境的最佳方式,通过估计在任何一点它可用的哪个即时动作最有可能导致最佳的长期结果,该结果由达到某个目标状态(或多个状态)所获得的奖励来定义。这可能是选择哪一步棋最有可能赢得围棋比赛,哪一次出价最有可能以好价钱赢得拍卖,或者在地铁系统中选择哪条路线,我将在这里更详细地解释 Q-learning 背后的直觉。
Q 学习代理的世界围绕着两个矩阵——R 矩阵和 Q 矩阵。R 矩阵表示代理将在其中操作的环境,从代理可能处于的状态、代理从每个状态可用的动作(通常被视为移动到其他状态)以及到达一个状态所收到的奖励来看。代理不知道整个 R 矩阵;它所能看到的是它能立即采取的行动。
R-matrix for navigation of a small section of the London Underground
上表是一个 R 矩阵,用于导航图中所示的一小部分伦敦地铁,目标是到达托特纳姆黑尔站。表中的每一行描述一个状态,每一列中的值代表代理移动到另一个状态所获得的奖励。例如,当在斯坦福德山时,经纪人可以留在斯坦福德山,或者搬到七姐妹。但是如果这是代理人能看到的全部,并且两个移动有相同的回报,代理人如何能算出哪个移动是最好的?这就是 Q 矩阵的用武之地。
Q 矩阵表示代理积累的关于其环境的知识。它与 R-矩阵的结构相同,但在开始时,它的所有值都是 0(因为它还没有学到任何东西)。它只知道,它希望获得最大回报。因此,代理开始探索它的世界,每次移动后,它都用它所学到的东西更新它的 Q 矩阵。这可以通过以下等式来实现:
简而言之,这意味着在每一步棋后,代理人都会更新它对刚才所做选择的价值的估计,基于它得到的回报,它对这一步棋的价值的现有估计,以及它对现在可用的这一步棋的价值的现有估计。
让我们看一下这个例子。由于代理正在探索,假设它随机选择了一个选项,这个随机选择是从斯坦福德山移动到七姐妹。现在,因为这一步的回报是 0,所有当前的 Q 值都是 0,没有新的知识来更新我们的 Q 矩阵,所以什么都没有改变。
现在经纪人面临四个选择:留在七姐妹,或者搬到托特纳姆黑尔,芬斯伯里公园,或者回到斯坦福山。同样,经纪人无法在这些选项中做出选择,所以只能随机选择,为了简单起见,我们会说是去托特纳姆黑尔。这一次,它确实得到了回报,所以这一步的 Q 值更新了 100 倍的学习率(我们假设α和γ都是 0.1)。
Q 矩阵现在看起来像这样:
因为代理已经达到目标,所以该集结束,并且代理被放置在随机站中以再次开始该过程。然而这一次,它有了一些知识。下一次它从斯坦福德山移动到七姐妹时,它将看到它的一个可用的下一个动作具有值,这将意味着状态/动作对值也得到更新,即使它没有得到任何奖励:
就最终目标而言,这一步是有价值的,因为它导致了另一步,而这一步会带来回报。
从这个简单的例子中,您可以看到,经过多次重复,达到目标所获得的奖励值是如何通过前面所有可能的选择进行反馈的,直到代理最终制定出从任何站点采取的最佳移动,以便尽快达到其目标。您可能还会看到,这将需要大量的重复,并且将问题扩大,即使是相对简单的任务,也会使涉及的矩阵非常大(伦敦网络上有 427 个车站,我们只研究了 6 个!).
回到本文开头提到的标题抓取 Deep-Q 系统,这些系统运行在所需矩阵的复杂性和大小使这种方法不切实际的环境中(例如,围棋有 10 种⁷⁰可能状态)。他们不是试图学习和记住每个可能的状态/动作对的值,而是使用神经网络来学习近似这些值(如果你想一想,这很像我们在遇到以前从未遇到过的情况时所做的)。然而,他们仍然致力于相同的基本原则,即从经验中学习,根据预期的长期回报来评估给定状态和行为的价值。
q 湾
我的理学硕士同学莎拉·斯科特和我在一个简单的拍卖游戏中实现了 Q-learning,首先是一个代理,然后是两个代理。拍卖和上述场景的第一个主要区别是时间依赖性。在地铁的例子中,没有时间限制——代理可以花它需要的时间来达到它的目标,直到它这样做,情节才结束。它也总是因为达到目标而获得相同的奖励,不管它在哪个时间段达到目标。在拍卖中,这显然是不同的——有预先确定的移动次数,只有在最后一段时间才会收到奖励。这将我们的两个矩阵的形状从二维变为三维,尽管在新的维度上的运动是预先确定的(因为时间总是增加 1)。
在这个游戏中,状态是代理的当前出价,动作是向新出价的移动。代理人开始时没有出价,每次都可以提高出价,保持不变,或者退出拍卖。代理在拍卖中具有该物品的预设价值,并且奖励是该价值与赢得拍卖时支付的价格之间的差(因此,无论出价如何,代理未赢得拍卖都获得 0,但是超额支付可以获得负奖励)。
代理人行为的一个关键因素,我上面没有提到,是探索政策。在每一步行动中,代理人都有一个选择,是应该遵循当前的估计以获得最大的回报(exploit),还是在不同的路线上冒险,这可能会导致一个未发现的路径,以获得更好的解决方案(explore)。如果代理人太热衷于利用,那么它很可能会选择它找到的第一条路径来获得积极的结果,这可能是好的,但不太可能是最佳的。如果它过于热衷于探索,它将无法实现其回报最大化的总体目标。
在探索和利用之间找到平衡对代理识别最优路径的速度有很大的影响,在我们的实验中,这可能比学习率或折扣因子更重要。实现这种行为的常用方法是使用衰减参数ε,它决定了代理在每次移动时探索的概率。在学习之初,代理什么都不知道,所以我们希望它多多探索。渐渐地,随着它的知识增加,我们希望它走向更贪婪的方法,在那里它经常利用,探索更少,所以我们需要 epsilon 随时间减少。我们使用两个衰减因子来控制这种变化。每次学习后,epsilon 更新如下:
其中,df1 是接近 1 的数字(例如,0.9999),这导致ε在每次转动时仅减小很小的量,df2 是更小的数字(例如,0.99),这导致ε减小得更快。
下面的两个图表显示了使用这些参数的两种不同设置训练单个代理的结果。
在这里,我们可以看到ε衰变得太快了。代理人找到了一种从每集获得 3 英镑奖励的方法,并很快停止了探索,尽管这不是最优策略,甚至不是代理人经历过的最佳奖励。
这一次,我们可以看到代理人花费了更长的时间来探索,并逐渐走向剥削(随着紫色线移动得更高),在 3000 多集后越过阈值( d )之前,并切换到完全剥削行为,这一次在每一集中都实现了最大奖励。
多智能体 Q-学习
虽然单个代理人的场景展示了一些有趣的行为,但最优解对大多数人来说可能是直观上显而易见的——在任何时候出价 1,然后坚持这个出价。在有多个代理的情况下,最佳方法并不那么明显,并且根据所遵循的策略而变化(例如,我是将其他代理视为竞争对手还是合作伙伴?).
为了让自己更进一步,并开始探索这些博弈论的思想,我们用两个代理实现了我们的拍卖,使用了三种不同的策略,这是由 Q 更新规则的变化引入的。为了简单起见(ish ),拍卖被限制在一个时间段内,每个代理遵循相同的策略,并对拍卖项目赋予相同的价值,因此两者的 R 矩阵是相同的。允许的最高出价是 4,项目的价值是 3,出价-1 表示从拍卖中退出。
增加玩家的数量会增加状态的数量(因此也会增加动作的数量),因为状态需要代表每个单独玩家的出价。这意味着,如果有 k 个可能的出价和 n 个参与者,那么现在就有个 k^n 个状态,这再次说明了场景中看似微小的变化会多么迅速地导致环境复杂性的大幅增加。
我们尝试在确定性的回合顺序和随机性的回合顺序之间进行切换,在确定性的回合顺序中,一个代理总是先移动,而在随机性的回合顺序中,代理仍然依次移动,但先移动的玩家在每集开始时确定。如果出现平局,任何一方都没有赢得奖励。
标准 Q 学习
在两个玩家都遵循与单人游戏相同的策略的情况下,双方都知道,无论谁先移动,都无法获得积极的奖励,因为第二个玩家总是可以出价高于他们或迫使平手,因此满足于不出价(这仍然比通过支付赔率赢得拍卖要好)。然后第二个玩家叫牌并得到奖励。
Bidding behaviour in the final 100 episodes
朋友-Q
Friend-Q 是对前面描述的 Q-Learning 规则的改编:
这意味着每个代理寻找具有最高估计值的一对动作(每个玩家一个)。实际上,它是假设另一个代理人会对它采取利他行为。与前一种情况一样,先行动的玩家学会出价 0,允许第二个玩家出价 1 并赢得最大可能的奖励。
Foe-Q
Foe-Q 是一个极小极大策略;假设其他代理人都在努力使其报酬最小化,代理人正在努力使其报酬最大化,如下式所示(从 0 号玩家的角度):
在这个场景中,我们的实现产生了一些有问题的结果。人们可能会认为,第一个行动的代理人将学会出价低于物品价值的最高价格,让对手选择要么出价高于物品,要么与物品的出价相当,从而导致任何一方都没有任何回报。虽然这种情况确实会发生,但我们的代理无法确定一种恒定的方法,即使在 Q 矩阵已经收敛之后,这表明我们的代码可能有问题,我们甚至可能已经处于这样一个点,即用某种形式的值近似函数(可以是神经网络,但也可以是更简单的东西,如线性回归)来替换我们的值矩阵。
包扎
除了我们在这里尝试的以外,还有许多可供研究的选项,例如:
- 当代理对该项目有不同的价值时会发生什么?
- 如果代理遵循不同的策略会发生什么?
- 如果两个代理同时出价会发生什么?
- 在多代理、多时间段场景中会发生什么?
…等等,但希望这篇文章已经让你很好地理解了这种流行的强化学习方法背后的思想,以及我们可以开始扩展它的一些方法,而不仅仅是最简单的例子。
所有的代码(大部分是莎拉写的!)可以在 GitHub 上获得(如果你能解决 Foe-Q 问题,欢迎拉请求!).它是用 python 写的,主要使用了 Numpy、Pandas 和 Matplotlib。感谢阅读,如果你喜欢,请留下一些掌声!
Q 学习和深度 Q 网络
强化学习的旅程还在继续……是时候分析一下臭名昭著的 Q-learning 了,看看它是如何成为 AI 领域的新标准的(在神经网络的一点帮助下)。
重要的事情先来。在的上一篇帖子中,我们看到了强化学习背后的基本概念,我们使用代理、环境、状态(S)、动作(A)和奖励(R)来构建问题。我们讨论了如何将整个过程描述为马尔可夫决策过程,并引入了术语策略和值。最后,我们对基本方法进行了一个快速的高层次概述。
请记住,目标是找到最佳策略,而策略是状态和动作之间的映射。所以,我们需要找到当我们站在一个特定的状态下采取什么行动来最大化我们的预期回报。找到最优策略的一种方法是利用价值函数(一种无模型技术)。
在这里,我们将得到新的东西。事实上,现在有两种价值函数在使用。状态值函数 V(s)和动作值函数 Q(s,a)。
- 状态价值函数:是根据政策从一个状态行动时达到的预期收益。
- 动作价值函数:给定状态和动作的期望收益。
你可能会问有什么区别?第一个值是特定状态的值。第二个是该状态的值加上来自该状态的所有可能动作的值。
当我们有了动作值函数,即 Q 值,我们可以简单地从一个状态中选择执行具有最高值的动作。但是我们如何找到 Q 值呢?
什么是 Q 学习?
所以,我们将从试错中学习 Q 值?没错。我们初始化 Q,我们选择一个动作并执行它,我们通过测量回报来评估它,并相应地更新 Q。首先,随机性将是一个关键因素,但随着智能体探索环境,算法将为每个状态和动作找到最佳 Q 值。我们可以用数学来描述这个吗?
谢谢理查德 E. 贝尔曼。上述方程被称为贝尔曼方程,并在今天的研究中发挥了巨大作用。但是它说明了什么?
Q 值,也就是一个状态和动作的最大未来回报,是当前回报加上下一个状态的最大未来回报。如果你仔细想想,这很有道理。Gamma (γ)是一个介于[0,1]之间的数字,它用于随着时间的推移对奖励进行折现,假设开始时的行为比结束时的行为更重要(这一假设被许多现实生活中的用例所证实)。结果,我们可以迭代更新 Q 值。
这里要理解的基本概念是,贝尔曼方程将状态彼此联系起来,因此,它将作用值函数联系起来。这有助于我们迭代环境并计算最佳值,从而为我们提供最佳策略。
最简单的形式是,Q 值是一个矩阵,状态是行,动作是列。我们随机初始化 Q 矩阵,智能体开始与环境互动,并测量每个动作的回报。然后,它计算观察到的 Q 值并更新矩阵。
探索与开发
如上所述,该算法是一种贪婪算法,因为它总是选择具有最佳值的动作。但如果某个行为产生非常大回报的概率非常小呢?代理永远不会到达那里。这是通过添加随机探索修复的。每隔一段时间,代理会执行一次随机移动,不考虑最优策略。但是因为我们希望算法在某个点上收敛,所以随着游戏的进行,我们降低了采取随机行动的概率。
为什么要深入?
q 学习好。没有人能否认这一点。但是它在大的状态空间中无效的事实仍然存在。想象一个有 1000 个状态,每个状态有 1000 个动作的游戏。我们需要一张包含一百万个单元格的表格。与国际象棋或围棋相比,这是一个非常小的状态空间。还有,Q 学习不能用于未知状态,因为它不能从以前的状态推断出新状态的 Q 值。
如果我们用机器学习模型来近似 Q 值呢。如果我们用神经网络来逼近它们呢?这个简单的想法(当然还有执行)是 DeepMind 以 5 亿美元从谷歌收购的原因。DeepMind 提出了一个名为 Deep Q Learner 的算法,并用它来玩 Atari 游戏,掌握得无懈可击。
在深度 Q 学习中,我们利用神经网络来逼近 Q 值函数。网络接收状态作为输入(无论是当前状态的帧还是单个值),并输出所有可能动作的 Q 值。最大的产出是我们下一步的行动。我们可以看到,我们并不局限于完全连接的神经网络,但我们可以使用卷积、递归和任何其他类型的模型来满足我们的需求。
我认为是时候在实践中使用所有这些东西,并教代理人玩山地车了。目标是让一辆汽车开上一座小山。这辆汽车的发动机不够强劲,不能一次爬完这座山。所以,成功的唯一方法就是来回开车造势。
我将解释更多关于深度 Q 网络和代码。首先,我们应该建立一个具有 3 个密集层的神经网络,我们将使用 Adam 优化来训练它。
关键点:
- 代理拥有一个包含所有过去经历的记忆缓冲区。
- 他的下一步行动由网络的最大输出(Q 值)决定。
- 损失函数是预测 Q 值和目标 Q 值的均方误差。
- 根据贝尔曼方程,我们知道目标是 R + g*max(Q)。
- 目标值和预测值之间的差异称为时间差异误差(TD 误差)
在我们训练我们的 DQN 之前,我们需要解决一个对代理如何学习估计 Q 值起着至关重要作用的问题,这就是:
体验回放
体验重放是一个概念,我们通过重放帮助代理记住并且不忘记它以前的动作。每隔一段时间,我们会对一批以前的经历进行采样(存储在一个缓冲区中),然后反馈给网络。这样,代理重温了它的过去并改善了它的记忆。这项任务的另一个原因是迫使代理人从振荡中释放自己,振荡是由于一些状态之间的高度相关性而发生的,并导致重复相同的动作。
最后,我们让我们的代理与环境互动,并训练他预测每个下一步行动的 Q 值
如你所见,这与 Q 表示例的过程完全相同,不同之处在于,下一个动作来自 DQN 预测,而不是 Q 表。因此,它可以应用于未知的状态。这就是神经网络的神奇之处。
你刚刚创造了一个学习开车上山的代理。太棒了。而且更牛逼的是,完全相同的代码(我是说复制粘贴)可以用在更多的游戏中,从 Atari、超级马里奥到 Doom(!!!)
厉害!
我保证,再坚持一会儿。
厉害!
在下一集,我们将继续深入 Q 学习领域,讨论一些更高级的技术,如双 DQN 网络、决斗 DQN 和优先体验重放。
一会儿见…
如果你有任何想法、评论、问题或者你只是想了解我的最新内容,请随时与我联系LinkedinTwitterinsta gramGithub或者****
要阅读整个深度强化学习课程,学习所有你需要了解的人工智能知识,去 这里 。
最初发布于 2018 年 10 月 1 日sergioskar . github . io。**
q-局部搜索
Photo by Franki Chamaki on Unsplash
一种基于 Q 学习的特征选择算法
“这次我不会再开什么无聊的玩笑,也不会做什么参考”,她就是这么说的!
在今天的文章中,我将尝试向您解释我上周一直在做什么,以及我在我的上一篇文章中谈论的内容,任何评论或建议都将是很好的,即使您觉得您并不关心,也许您正在从一个可能有所帮助的角度看待它,您可以在此链接中找到完整的代码(确保选择分支:“local-search-rl”)。
总之,在这部分代码中,你可以看到有初始局部搜索函数和 q-局部搜索函数,保持代码的可扩展性很好,所以当我需要实现一个新算法时,我所要做的就是在它们之间进行选择,因为它们不会影响其他部分。
BSO algorithm
如果我们在蜜蜂的水平上考虑局部搜索的初始算法,它进行穷举搜索,换句话说,它采用解决方案,一个接一个地翻转它的所有位,并每次进行评估,这使得复杂性,如果我们将其作为训练模型的成本,相当于模型的属性数量(例如声纳数据集,有 60 个属性, 这意味着我们将训练 60 个模型)并且这仅针对单个蜜蜂,并且针对单次迭代,这使得复杂度: o(最大 Iter x Nbr 蜜蜂 x Nbr 本地 Iter x Nbr 属性)
这是初始的本地搜索功能:
The original local search algorithm
Q-LocalSearch 背后的想法是提供“过滤”数据的子集(仍然是可选的),智能地翻转属性**,或者换句话说,根据策略,以避免穷举搜索。**
我们首先解释什么是 Q-Learning:
Q-learning 是由(Watkins & Dayan,1992)提出的一种无模型、非策略的强化学习算法。它包括评估状态-动作对的良好性。状态 s 和行动 a 的 q 值定义为在状态 s 采取行动 a,然后遵循政策的预期累积回报。
Reinforcement learning basic classification, Source
蒙特卡罗树搜索 (MCTS)、状态-动作-奖励-状态-动作 (SARSA)和 Q-Learning 是这种分类的一些示例算法。
q 表
Q-Table 只是简单的跟踪表的一个花哨的名字,就像 T2 ADL T3 在他的文章 T5 中所说的。它通常由 2D 数组组成,其中行代表状态,列代表可能的动作,因为当您处理特性选择时,使用静态数据结构是不明智的,所以我使用字典列表[索引是 nbrOnes(state)]字典列表[关键字是“state”][关键字是“action”](python 中的字典或 dict,就像 JAVA 中的 HashMap)
q_table[nbrOnes(state)][“state”][“action”]
q 函数
Q 学习算法基于下面给出的贝尔曼最优方程:
Q(状态,动作)= R(状态,动作)+ Gamma * Max[Q(下一个状态,所有动作)]
The Q-function explained
这就是我们每次更新表的方式,我们使用 Q 值来选择下一个状态,在这种情况下,要翻转哪些属性,这是 Q-LocalSearch 函数:
The Q-localSearch algorithm
概述
为了概括我们今天在这里所做的事情,我们谈到了 Q-learning,以及我们如何使用它来制作所谓的 Q-LocalSearch 函数,这是一种使用 Q-learning 算法进行特征选择的方法。
对于这个模型,我们有:
- 状态:是特征的组合
- 动作:翻转特征
- 奖励:目前,我们认为状态动作的准确性(适合度)是一种奖励,但是在下一篇文章中,我们可能会更深入,看看为什么它不是定义它的最佳方式。
我真的希望你喜欢它,我试图把我认为重要的一切,如果你需要更多的细节,不要犹豫留下评论,或 PM 我。
是的,你可以随时查看我的上一篇文章。
原载于 2018 年 12 月 26 日amineremache.blogspot.com。
基于深度学习的计算机视觉在制造业中的质量检测
通过图像识别去除劣质材料来提高产量
作者:帕萨·德卡和罗希特·米塔尔
工业制造中的自动化:
当今制造业自动化水平的提高也要求在很少人工干预的情况下实现材料质量检测的自动化。自动化质量检测的趋势是达到人类水平或更高的精度。为了保持竞争力,现代工业企业努力实现自动化的数量和质量,而不牺牲其中一个。这篇文章向用户展示了深度学习的一个用例,并展示了优化整个堆栈(算法、推理框架和硬件加速器)以获得最佳性能的需求。
质量检测深度学习:
为了达到行业标准,制造企业的质量检查员通常在产品制造完成后检查产品质量,这是一项耗时的人工工作,不合格产品会导致上游工厂产能、耗材、劳动力和成本的浪费。随着人工智能的现代趋势,工业企业正在寻求在生产周期中使用基于深度学习的计算机视觉技术来自动化材料质量检测。目标是最大限度地减少人工干预,同时达到人类水平或更高的精度,并优化工厂产能、劳动力成本等。深度学习的用途是多种多样的,从自动驾驶汽车中的物体检测到医学成像中的疾病检测,深度学习已经被证明可以达到人类水平的准确性,甚至更好。
什么是深度学习?
深度学习是学习数据的深度结构化和非结构化表示的领域。当数据庞大而复杂时,深度学习是人工智能的发展趋势,可以抽象出更好的结果。深度学习架构由神经网络的深层组成,如输入层、隐藏层和输出层。隐藏层用于理解数据的复杂结构。神经网络不需要被编程来执行复杂的任务。千兆字节到兆兆字节的数据被馈送到神经网络架构,以便自己学习。下面是深度神经网络的示例:
卷积神经网络:
卷积神经网络是一类常用于图像分析的深度神经网络。卷积层对输入应用卷积运算,并将结果传递给下一层。例如,1000 乘 1000 像素的图像有一百万个特征。如果第一个隐藏层有 1000 个神经元,那么在第一个隐藏层之后,它最终会有 10 亿个特征。有这么多特征,很难防止神经网络用较少的数据过度拟合。训练一个具有十亿个特征的神经网络的计算和存储要求是非常高的。卷积运算为这一问题带来了解决方案,因为它减少了自由要素的数量,从而允许网络更深且要素更少。与完全连接的图层相比,使用卷积图层有两个主要优势-参数共享和连接稀疏。
卷积神经网络在图像中寻找模式。图像与较小的矩阵进行卷积,并且该卷积寻找图像中的模式。前几层可以识别线/角/边等,这些模式被传递到更深的神经网络层,以识别更复杂的特征。CNN 的这个属性确实很擅长识别图像中的物体。
卷积神经网络(又名 ConvNet)只不过是一系列层。三种主要类型的层用于构建 ConvNet 架构:卷积层、池层和全连接层。这些层是堆叠的层,以形成完整的 ConvNet 架构:
图片来源:cs231n.github.io/convolution…
下图阐明了卷积层的概念:
下图阐明了**池层(平均或最大池)**的概念:
以下是 CNN 最初的架构之一:
可视化 CNN :
以下是平面上裂纹的图像:
类似于 LENET-5 架构的两层 Conv(一个 3X3 过滤器)、ReLU 和 Max Pooling (2X2)应用于上面的裂缝图像。从下面可以看出,CNN 架构关注的是裂缝区域的区块及其在整个表面的扩散:
案例分析:
为了保持我们工作的机密性,我们在下面呈现一个抽象用例:
问题陈述:
检测硬件制造中的劣质材料是一个容易出错且耗时的手动过程,会导致误报(将劣质材料检测为优质材料)。如果在生产线末端检测到有缺陷的组件/零件,就会造成上游劳动力、耗材、工厂产能和收入的损失。另一方面,如果未检测到的不良零件进入最终产品,将会影响客户和市场反应。这可能对本组织的声誉造成不可挽回的损害。
总结:
我们使用深度学习对硬件产品进行自动化缺陷检测。在我们的硬件制造过程中,可能会出现划痕/裂纹等损坏,使我们的产品无法用于生产线的后续工序。我们的深度学习应用程序以人类水平的精度在毫秒内检测到裂纹/划痕等缺陷,并通过热图解释图像中的缺陷区域。
我们深度学习架构的细节:
为了更好地描述,我们在下面使用一个带有集成芯片的电路板示例图像:
我们的第一次进场:
我们采用了纯计算机视觉方法(非机器学习方法)的组合来从原始图像中提取感兴趣区域(ROI ),并采用纯深度学习方法来检测 ROI 中的缺陷。
为什么在 DL 之前提取 ROI?
当捕捉图像时,相机组件、照明等。专注于赛道的整个区域(下图)。我们只检查芯片区域的缺陷,不检查电路中的其他区域。我们通过几个实验发现,当神经网络仅关注感兴趣的区域而不是整个区域时,DL 准确性显著增加。
- 首先用计算机视觉(非机器学习方法)提取“感兴趣区域(ROI)”。这里,我们对图像进行多种处理,如灰度缩放、腐蚀、膨胀、关闭图像等变换。并最终根据用例类型/产品类型等从图像中绘制出 ROI。侵蚀的基本概念就像土壤侵蚀一样——它侵蚀掉前景对象的边界。扩张与侵蚀正好相反,它增加了前景对象的大小。通常,在像噪声去除这样的情况下,侵蚀之后是膨胀。开放只是侵蚀和扩张的另一个名称。它在消除噪音方面很有用。关闭与打开相反,先膨胀后侵蚀。它在关闭前景对象内的小孔或对象上的小黑点时很有用。梯度变换是图像的膨胀和腐蚀之间的差异。总的来说,这些步骤有助于打开原始图像中几乎看不见的裂缝/划痕。参考下图:
- 第二,使用深度神经网络(基于深度神经网络(CNN)的模型)来检测缺陷,使用已证实的 CNN 拓扑,例如 Inception Net(又名 Google Net)、Res Net、Dense Net:
为了找到最佳架构,需要进行实验的其他一些领域
- 数据扩充:我们有几千张被标记为缺陷的独特图像,还有几千张被标记为良好图像。扩充对于避免过度适应训练集至关重要。我们做了 X 个随机裁剪和 Y 个旋转(1 个原始图像产生 XY 个增强图像)。在增强之后,我们有 XY 千个有缺陷的图像和 X*Y 千个好的图像。参考 CNN 在这方面的一篇原始论文https://papers . nips . cc/paper/4824-imagenet-class ification-with-deep-convolutionary-neural-networks . pdf
- 初始化策略用于 CNN 拓扑结构:
我们用自己的 FC 层和 sigmoid 层(二进制分类)替换了最终连接的层,如下图所示:
对于每个 CNN 拓扑,我们考虑 ImageNet 初始化,而不是随机初始化每一层中的权重,当我们使用 ImageNet 初始化时,我们的 DL 准确性比随机大大增加。
- 损失函数和优化器:
交叉熵损失:交叉熵损失,或对数损失,测量分类模型的性能,其输出是 0 和 1 之间的概率值。交叉熵损失随着预测概率偏离实际标签而增加。因此,当实际观察值为 1 时,预测概率为 0.01 将是糟糕的,并导致高损失值。完美的模型的对数损失为 0
SGD 和内斯特罗夫动量:SGD 或随机梯度下降是一种迭代方法用于优化一个可微 目标函数(损失函数),它是随机的,因为它从数据中抽取随机样本来做梯度下降更新。动量是梯度的移动平均值,它用于更新网络的权重,并有助于在正确的方向上加速梯度。内斯特罗夫是最近开始流行的一个版本的动量。
我们的第二种方法:
对第一种方法的评论:在提取感兴趣区域时,每当产品类型、电路板类型/芯片类型(在我们的抽象示例中)、摄像机设置/方向等发生变化时,都需要重写代码。这是不可伸缩的。
**解决方案:**我们构建了一个端到端的两步 DL 架构。在第一步中,我们没有使用 CV 方法,而是使用 DL 方法来预测 ROI 本身。我们用边界框工具&手动创建了一个带标签的数据集,我们让训练一个 DL 架构来预测 ROI。这种技术的一个缺点是标签数据集必须足够明确和广泛,以包括所有产品类型等。(在我们的抽象示例的情况下,为电路板类型/芯片类型)用于深度神经网络在看不见的图像上很好地概括。参考下图:
- CNN ROI 生成器损失函数:
我们最初使用基于平方距离的损失函数如下:
在验证集上对 Resnet50 模型进行 20 个时期的训练后,我们在平均遗漏面积和 IOU 上实现了以下验证指标:
平均漏测面积= 8.52 * 10–3
Ave. IOU(交集/并集)= 0.7817
我们希望至少在欠条上有所改进
我们想出了一个基于面积的损失,请参考下图,以了解我们如何使用基本数学来计算地面真实值和预测标签之间的相交面积。在损失函数中,我们希望惩罚错过的区域和超出的区域。理想情况下,我们希望对遗漏区域的惩罚多于对超出区域的惩罚:
上面的损失函数是可微的,因此我们可以对损失函数进行梯度下降优化
- CNN ROI 生成器增强:我们只是在训练时间和测试时间在我们预测的 ROI 上增加了 5%(左右)的余量
- CNN ROI 生成器结果:我们使用 resnet 50(ImageNet initialization)topology 和 SGD +内斯特罗夫动量优化器,其中=2,=1 在区域基于损失如上所述。为多个时期训练 Resnet50 模型,我们希望最小化我们的平均值。错过的区域和最大化我们的平均值。IOU(最佳 IOU 为‘1’)。经过 20 个时期的训练后,我们在验证集上实现了以下内容,通过基于面积的损失和增加,我们改进了(如上所述)我们关于遗漏面积和 IOU 的验证度量:
平均缺失面积= 3.65 * 10–3
平均 IOU(并集上的交集)= 0.8577
实验&基准:
图像总数:几千张图像
数据分割:80:10:10 分割,仅使用独特的图像
使用的框架:PyTorch & Tensorflow / Keras
重量初始化:在 ImageNet 上预先训练
优化器:学习率= 0.001 的 SGD,使用动量= 0.9 的内斯特罗夫
损失:交叉熵
批量:12 个
纪元总数:24
图像形状:224 x224 x3(Inception V3 除外,它需要 299x299x3)
标准:最低验证损失
我们对这两种方法的基准测试相当接近,CV+DL(第一种)方法的结果比 DL+DL(第二种)方法好不了多少。我们相信,如果我们能创建一个广泛和明确的带标签的包围盒数据集,我们的 DL+DL 会更好。
成功完成训练后,必须找到推理解决方案来完成整个端到端解决方案。我们使用英特尔 OpenVino 软件来优化除 CPU 之外的不同类型硬件中的推理,如 FPGA、英特尔 Movidius 等。
推论:
英特尔开放 Vino :基于卷积神经网络(CNN),英特尔开放 Vino 工具包将工作负载扩展到整个英特尔硬件,并最大限度地提高性能:
-在边缘实现基于 CNN 的深度学习推理
-使用通用 API 支持跨计算机视觉加速器(CPU、GPU、英特尔 Movidius 神经计算棒和 FPGA)的异构执行
-通过函数库和预先优化的内核加快上市时间
-包括针对 OpenCV 和 OpenVX*的优化调用
请参考以下开放式葡萄酒架构图:
两步部署:
-第一步是使用模型优化器将预训练模型转换成 IRs:
产生一个有效的中间表示:如果这个主转换工件无效,那么推理机就不能运行。模型优化器的主要职责是生成两个文件来形成中间表示。
**产生优化的中间表示:**预训练的模型包含对训练很重要的层,例如丢弃层。这些层在推理过程中没有用,可能会增加推理时间。在许多情况下,这些层可以从生成的中间表示中自动移除。但是,如果一组层可以表示为一个数学运算,因此可以表示为一个层,则模型优化器会识别这种模式并用一个层替换这些层。结果是一个比原始模型层数更少的中间表示。这减少了推断时间。
IR 是描述整个模型的一对文件:
。xml :描述网络拓扑
。bin :包含权重和偏差二进制数据
-第二步是使用推理引擎来读取、加载和推理 IR 文件,使用跨 CPU、GPU 或 VPU 硬件的通用 API
样本图像上的推理基准:
很明显,使用软件堆栈进行优化对于减少推理时间至关重要。使用 OpenVino 软件优化,延迟时间提高了 30 到 100 倍。此外,英特尔 Movidius 和 FPGA 等其他英特尔硬件加速器也进行了相同的推理测试。目的是看看加速器相对于传统 CPU 能有多少改进。下面是一些关于示例图像的推理基准:
使用英特尔 Movidius Myriad1,使用 NCS SDK 将我们的 Resnet-50 Tensorflow/Keras 模型转换为 NCS 图形,Raspberry Pi 托管图像,推理由 movidius stick 中的视觉处理单元执行。movidius stick 的计算能力较低,因此该加速器没有提供大的性能提升。此外,使用的软件框架是一个 NCS 图,它可能不包含 OpenVino 等框架的所有性能提升(稀疏性、量化等)。
*使用为我们的 Resnet-50 型号提供的位流,在 linux 机器上使用 Open Vino 配置和编程 FPGA 板。FPGA 就像一个真正的加速器,在相同的软件框架(OpenVino)下,比 CPU 进一步提高了约 10 倍。
上述性能数字清楚地表明,需要一个整体视图来提高深度学习性能。优化的软件堆栈和硬件加速器都是实现最佳性能所必需的。
用热图可视化我们的 CNN:
通常,深度神经网络因可解释性低而受到批评,大多数深度学习解决方案在标签分类完成时就停止了。我们想解释我们的结果,为什么 CNN 架构将一个图像标记为好或坏(我们案例研究的二进制分类),CNN 最关注图像中的哪个区域。
基于麻省理工学院arxiv.org/pdf/1512.04…的这项研究,已经提出了结合全局最大池层的类激活图来定位特定于类的图像区域。
全球平均池通常作为一个正则化,防止在训练期间过度拟合。本研究证实,全局平均池层的优势不仅仅是作为一个正则化层,只要稍加调整,网络就能保持其卓越的定位能力,直到最后一层。这种调整允许在单次向前传递中容易地识别区别性图像区域,用于各种各样的任务,甚至那些网络最初没有被训练的任务。
以下是使用 ImageNet 上训练的 Resnet-50 架构,在“平面上的裂纹”图像上使用该技术的热图解释。正如我们所看到的,热图聚焦于下面的裂缝区域,尽管建筑并没有在这样的图像上训练——
总结&结论:
通过基于深度学习的计算机视觉,我们实现了人类水平的准确性,并且我们的两种方法——c v+ DL 和 DL+DL(在本博客的前面讨论过)都更好。我们的解决方案是独特的——我们不仅使用深度学习进行分类,还使用图像本身的热图来解释缺陷区域。
人的因素不能完全分离,但我们可以大大减少人为干预。最佳模型总是在 FPR(假阳性率)和 FNR(假阴性率)或精确度和召回率之间微调。对于我们的用例,我们使用一个针对低 FNR(高召回率)优化的模型成功地自动化了缺陷检测。我们大幅降低了人工审查率。通过我们的案例研究,我们证明了我们可以通过深度学习实现材料检测自动化,并降低人工审查率。
参考资料:
https://www . coursera . org/learn/卷积神经网络