PandasGUI:用图形用户界面分析 Pandas 数据帧
只需点击鼠标即可访问熊猫数据框
图片格式由作者| 剪贴画来源
今天 熊猫 库已经成为用 Python 进行任何探索性数据分析的事实上的工具。它的多功能性、灵活性和易用性使其成为当今许多数据科学家的首选库。熊猫图书馆也享有很好的社区支持,因此一直在积极发展和改进。由于熊猫的这种不可或缺的性质,各种各样的工具被不时地创造出来以增强它的有效性或对它进行改进。谈到熊猫,我遇到过两种特定的工具:
- 可以用两三行代码执行基本 EDA 的工具。这些库本质上使用了熊猫的功能。例如 SweetViz 和 Pandas profiling 库。
- 熊猫的基于 GUI 的替代品,例如 Bamboolib 。
最近,我遇到了另一个基于 GUI 的熊猫替代品,叫做 PandasGUI。令人印象深刻的一点是,它提供了绘图和重构数据框架的能力。此外,用户也可以自由地执行自定义操作。本文将尝试解释它的各种特性和功能,以及如何将它用于您的数据。
潘达斯吉
PandasGUI 顾名思义,是一个用于分析熊猫数据帧的图形用户界面。该项目仍在积极开发中,因此有时可能会发生重大变化。PandasGUI 提供了许多有用的特性,我们将在本文后面详细介绍这些特性。在此之前,让我们看看如何安装这个库并让它运行起来。
装置
有几种方法可以安装 PandasGUI:
# from PyPi
pip install pandasguior# from Github
pip install git+https://github.com/adamerose/pandasgui.git
特征
现在让我们通过一个例子来看看 PandasGUI 库的各种功能。PandasGUI 已经提供了一些示例数据集。所以我们将使用库附带的Titanic
数据集。泰坦尼克号是机器学习中一个相当著名的“Hello World”数据集,其任务是创建一个模型,预测哪些乘客在泰坦尼克号沉船中幸存。
import pandas as pd
from pandasgui import show
from pandasgui.datasets import titanic
gui = show(titanic)
如果要导入数据集,可以按如下方式进行:
titanic = pd.read_csv('[https://github.com/adamerose/datasets/blob/master/titanic.csv](https://github.com/adamerose/datasets/blob/master/titanic.csv)')
gui = show(titanic)
运行以上命令后,会打开一个单独的窗口,显示上传的数据帧:
PandasGUI 中显示的泰坦尼克号数据帧|图片由作者提供
现在让我们一个接一个地看看这个工具的各种产品。
1.查看和排序数据帧和系列
您可以查看整个导入的数据帧,然后按升序或降序快速排序。注意,PandasGUI 也可以处理多类 datarfames。
按作者查看和排序数据帧和系列| Gif
2.借助查询表达式过滤数据帧
一旦浏览了数据集,您甚至可以基于一些查询表达式来过滤数据集。[**Dataframe.query()**](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.query.html)
是最初由熊猫提供的用于执行过滤操作的方法。它采用字符串形式的表达式来过滤数据,对原始数据帧进行更改,然后返回过滤后的数据帧。
对于我们的数据集,假设我们要过滤以下乘客的全部数据:
- 男性的
- 属于 Pclass 3,并且
- 在海难中幸存。
按作者过滤 Dataframe | Gif
3.数据编辑和复制/粘贴
这个工具的另一个很大的特点是,你可以直接编辑任何条目,甚至可以将选中的数据复制粘贴到另一个环境中,如 excel 或记事本。
作者的数据编辑和复制/粘贴| Gif
4.统计摘要
PandasGUI 还提供了整个数据集的压缩统计概览。
统计摘要|作者图片
5.交互式绘图
数据可视化是任何数据分析过程的重要组成部分,PandasGUI 提供了几个选项来快速创建一些很酷的交互式图表,如:
熊猫图形用户界面中可用的图表|作者图片
下面我们创建了一个饼图,一个条形图,甚至一个单词云。由于图表是在 plotly 中创建的,因此它们具有响应性和交互性。
互动绘图|作者 Gif
6.用旋转和熔化功能重塑数据帧
有时,需要重塑数据以获得更清晰的见解。PandasGUI 提供了两个函数— pivot 和 melts 来实现相同的功能。让我们看看如何通过这个工具透视数据。
通过透视方法重塑数据|作者 Gif
7.通过拖放导入 CSV 文件
该工具的另一个很酷的特性是,可以通过将数据帧拖动到 GUI 界面上来导入数据帧,这有时很方便。
通过拖放| Gif 按作者导入 CSV 文件
8.从 Jupyter 笔记本访问 GUI 数据框
如果您已经将数据帧直接导入 PandasGUI 界面,那么您也可以在熟悉的 juptyer 笔记本中访问该数据帧,只需几行代码。从那里,如果你愿意,你可以在笔记本上进行数据分析过程。
从 Jupyter Notebook | Gif 按作者访问 GUI 数据帧
结论
在本文中,我们看了一个基于 GUI 的工具,用于分析熊猫数据帧。这个工具有许多有趣的特性,比如过滤、排序、可视化,甚至聚合,我们在一个数据集示例中详细看到了这些特性。由于 PandasGUI 正在积极开发中,我们可能会在未来几天看到更多的功能。这样的库对于那些不太擅长编码或者正在寻找低代码环境的人来说是一个福音。无论最终目标是什么,了解另一个开源工具来添加到我们的数据科学工具包中总是有好处的。
流行病如何影响金融市场
从过去的大流行中获得线索,定量分析对金融市场的影响。在 Python 中可视化和分析数据
我们正处于一个不确定的环境中,这也反映在金融市场上。你会有许多问题,例如 COVID19 将如何影响金融市场,它还会下跌多少,它将何时结束以及如何结束。在这篇文章中,我们将分析并从过去的大流行中寻找线索来回答这些问题以及我们对未来的预期。本文是根据 QuantInsti 于 2020 年 4 月 2 日就此主题举办的网络研讨会撰写的。录音和代码可从下面访问。
新冠肺炎疫情已经影响了全球股市。在本次网络研讨会中,我们将从……
blog.quantinsti.com](https://blog.quantinsti.com/pandemics-impact-financial-markets-webinar-2-april-2020/)
在我们开始深入研究对市场的影响之前,下面是我们在周围看到的或预期的新闻。
- 企业倒闭和制造工厂关闭
- 失业和失业率上升
- 供应链中断
- 日常必需品的短缺
- 医疗系统的压力
- 呈指数上升的感染和死亡人数
- 还有更多……
你已经看到商店关门,制造厂关闭。没有人生产任何东西,他们没有工资支付,所以他们解雇人,因此,失业率上升。供应链出现中断,人们开始囤积包括卫生纸在内的日常必需品。这将导致短缺。与感染相关的病例数量正在上升,这给医疗保健系统造成了巨大压力。发达市场正在某种程度上应对这种前所未有的局面,但印度等发展中市场将发现很难管理医疗体系的压力。
与此同时,金融市场大幅下跌。在这种情况下,你可能会问,我们什么时候能期待一些好消息,市场什么时候会复苏,以及许多其他问题。
COVID19 对金融市场的影响
与 10 年前相比,当今市场的风险速度提高了。社交媒体驱动的新闻周期、全球供应链的相互关联性和昂贵的股票市场,使华尔街更容易受到黑天鹅的影响。
- Seema Shah,Principal Global Investors 首席策略师
让我们从评估 COVID19 对金融市场的当前影响开始。我使用 Python 免费提供的 API 和代码创建了本文中的所有图表。所有这些代码都可以在 GitHub 上找到。
如果你是 Python 的新手,还没有使用过像 NumPy 和 Pandas 这样的金融库,那么我建议你在下面关于 Python 交易的免费互动课程中复习一下这些概念。
使用不同的数据结构,如列表、元组和字典,使用循环、条件语句、函数…
quantra.quantinsti.com](https://quantra.quantinsti.com/course/python-trading-basic)
让我带您看一下这段代码,它用于绘制标准普尔 500 的每日百分比变化。
代码如下所示:
SP500 每日百分比变化图
我使用了 pandas_datareader 的 get_data_yahoo 方法来获取 S&P500 的价格。
get_data_yahoo 方法有两个参数,第一个参数是股票代码,即 Yahoo Finance 中标准普尔 500 指数的“^GPSC ”,第二个参数是我想要的数据的起始日期。如果你查过,第一例冠状病毒是在 11 月 17 日确诊的。因此,我使用了相同的日期。
当我运行这段代码时,我将得到包含六列的数据集,它们是开盘价、最高价、最低价、收盘价、成交量和调整收盘价。由于我们对每日百分比变化感兴趣,我将使用 Python 的 pct_change()函数来计算它,并在 close 列上调用它。例如,如果每日百分比变化从 100 变为 102,那么我在该数据框中的值为 0.02。
如果您想用 Python 绘制这个系列,您只需传递系列的名称(在本例中为“data_pc ”)并调用函数 plot,它将绘制数据系列。这就是每日回报的样子。由于它看起来更小,我添加了一个网格,并做了一些改进,以提高其可读性。
每日百分比变化
除了 S&P500,我还绘制了原油,黄金和 TLT 的图表,这是美国市场的 20 年期国债。
我画出了它们的每日百分比变化。为了更详细地描述这些图表,蓝线是发现首例病例的日期,红线是世卫组织宣布进入公共卫生紧急状态的日期。
因此,我们可以看到 S&P500 每日价格的波动性或变化非常接近于零,我们可以看到,在世卫组织宣布紧急状态几天后,其波动性发生了急剧变化。在日常生活中,这一比例在 5%到 10%之间。此外,不仅仅是 S&P500 是美国市场的晴雨表,它也可以在原油,黄金和我们的 TLT 中看到,这是美国市场的 20 年期国债。因此,我们很容易得出结论,电晕正在使金融市场紧张不安。
观察这些图表的另一种方式是,有很多向下的运动,但也有很多向上的运动。因此,我该如何评估市场的走势呢?
累积回报
为此,我绘制了四个案例的累积收益。开始日期是报告第一例病例的前几天,最后一天是 3 月 30 日。
如你所见,红线代表原油,它似乎受到的影响最大。从顶部开始下降了将近 60%。表示标准普尔 500 的蓝线也出现了大幅下降。但如果你看看黄金,你会发现其影响不如 S&P500 和原油那么显著。
这是否意味着在这个不确定的时期,我们应该将黄金纳入我们的投资组合,作为一种合理的对冲手段?
似乎是这样,但我们不要这么快下结论。我们稍后将回到这个问题。最后但并非最不重要的是美国国债,它经历了急剧下降,但很快恢复。
水位降低
另一种方式来看这个图表是下降图。简而言之,下降衡量的是在任何特定时间段从峰值的下降。现在让我们看看图表。
例如,在 S&P500 图表中,在二月左右有一个峰值,然后下降到接近 33%。每种情况的下降值如下所示:
最大提现
原油 67.07%
SP500 33.92%
黄金 11.80%
TLT 15.72%
SP500 与原油、黄金和 TLT 的关系
提款帮助我们理解资产类别可以下降多少,但它们仍然是不同的资产类别,对吗?我们如何比较它们,或者说,它们之间的关系是什么?
为了回答这个问题,我们将创建一个散点图。散点图有助于我们理解现有工具之间的关系。
首先,我们将绘制不同仪器相对于 S&P500 的散点图。让我们看看他们看起来怎么样。
红色散点图是相对于 TLT 的 S&P500,显示了负相关关系。我们都知道,在危机时刻,会出现经济放缓,美联储和其他政府。代理机构介入后,利率就会降低。由于费率和价格之间存在反比关系,费率的降低会导致价格的上涨。因此,我们看到了 S&P500 和 TLT 之间的这种消极关系。然而,我们有一些数据点,因此在这里下结论是不明智的。
对于黄金,我们认为与 S&P500 没有关系,因为数据点分布在该图中。当谈到原油和 S&P500 时,似乎远点是负相关的,但近点是正相关的。因此,我们在这一点上不能真正下结论。
这是在不同文书之间建立联系的一种方式。基于这些关系,我们可以创建一个在流行病时期对我们有用的投资组合。
部门绩效
到目前为止,我们谈论的是整个 S&P500。但是构成指数的单个板块呢?
看待这个问题的一种方式是行业表现。我绘制了一张图表,其中考虑了市场的主要部门。
**从这张图中可以看出,制药和科技是受影响相对较小的两个行业。**在上图中,技术是这条橙色的线,你可以在最上面看到,后面是蓝色的制药线。总的来说,你可以看到所有的部分都在同一个方向,但是相对幅度,或者说下降幅度,是不同的。正如我之前所说的,制药和技术受影响相对较小。
**你可以看到粉红色的线代表能源受到的影响最大。**这是合乎逻辑的,因为我们之前已经看到原油受到的影响最大,而这又反过来影响了能源行业,因为它们两者直接相关。另一个观察是绿线,这表明金融,也下降了。其原因是,每当经济放缓时,就会有大量贷款违约,导致银行不良资产增加,最终它们的损益将受到打击。不良资产的增加使银行很难生存。印度市场的一个经典例子是 Yes bank。因此,在这场经济危机中,金融部门首当其冲。
这是否意味着我们应该忽略金融类股,转而投资科技和制药类股,我的投资组合应该是什么样的?
除此之外,我们都有如下所示的问题:
- 什么时候会结束?
- 会如何收场?
- 市场会从这里进一步下跌吗?它将如何发展?
- 我应该买黄金作为避风港吗?我的作品集应该是什么样的?
- 它如此具有传染性,每个人都会受到它的影响吗?
- 收益会受到怎样的影响,哪个部门会受到最大的影响?
让我们通过回顾过去的大流行和世界的发展来回答这些问题。我想在这里补充一个小小的免责声明,我没有任何水晶球可以给我一个 100%正确的答案,但我相信数据永远是得出合理结论的最佳方式。让我们现在开始吧。
过去的大流行
“那些不记得过去的人注定要重蹈覆辙。”
~乔治·桑塔亚纳
如果你认为这是一个特殊的情况,那么,你会惊讶地知道,自过去几个世纪以来,流行病一直在影响着我们。我在一个简单的表格中收集了一些数据,如下所示
来源:世界经济论坛
我用红色标出了死亡人数最多的三种流行病,绿色标出了死亡人数最少的四种。
这里出现了一个重要趋势。你能搞清楚吗?
这里实际上出现了两种趋势。一个是这些年来死亡人数显著下降。另一个趋势是期限也大幅缩短。这是因为我们有了一个更好的医疗保健系统,同时我们也付出了很大的努力来为大众寻找和部署疫苗。
但这是对过去的回顾。**冠状病毒是如何影响感染和死亡人数的?**让我们快速了解一下冠状病毒的传染性有多大
来源:世界经济论坛
这张信息图告诉了你什么?
它向我们展示了感染传播的速度。麻疹是这里 16 岁的头号杀手。这意味着一个患麻疹的人可以感染 16 个未接种疫苗的人。相比之下,冠状病毒为 2.5,这意味着一个感染冠状病毒的人平均可以影响 2.5 人。
但是为什么我们会看到病例数量呈指数增长呢?
我想用我们的投资世界做一个类比,并引起你对复利的注意。一个人在第一天就会受到影响。所以第二天 2.5 将受到影响。第三天,我们将会看到大约六个人被感染。这六个人会在 15 左右再次冲击(6*2.5)。以这种方式,受感染的人数呈指数上升。
遏制这种情况的最好方法是社会距离,这是世界上大多数政府所遵循的。
这件事会如何收场?
过去的表现是成功的最佳预测
~吉姆·西蒙斯
实际上,这里有两种情况我想谈谈。先说第一个。
场景 1:采取严格措施
严重急性呼吸综合征
在我们的历史上有一个类比,它是关于非典疫情。SARS 具有类似的性质,它于 2002 年在中国南方爆发。通过我们在周围看到的隔离、检疫和接触者追踪措施,疫情得到了控制。
在这个疫情,大约有 8500 人被感染。目前,还没有疫苗,但他们能够通过社会距离来控制这种情况,新感染的数量持平,因此,死亡人数最终减少。但是,尽管识别感染 SARS 的人很容易,但对于冠状病毒携带者来说就有点棘手了。尽管如此,我们还是可以以此为参考来理解冠状病毒对行业的影响。
来源:英国《金融时报》
在这里,我们累计计数,以及自第 100 例以来的天数。您还可以看到案例每天翻倍、第二天翻倍等等的轨迹。
例如,美国用了 24 天达到 100,000 次计数,其他国家也是如此。这里的目标是使这个指数上升曲线尽快变平。在这里,让我把你的注意力放在韩国的泥土上,他们把它弄得很平,在非典时期也做了类似的事情。现在让我们看看图表。
来源:维基百科
这是从维基百科上截取的,在这里你可以看到累积的病例以橙色线的形式,代表 8437。最初,从 4 月到 5 月,病例数呈指数增长,但由于社会距离和其他措施,增量病例数开始减少。这创造了一个“S”或我们在上面看到的逻辑曲线。死亡率遵循橙色曲线并趋于平缓。
如果我们认为 SARS 是冠状病毒的一个很好的类比,那么下面的时间表将有助于我们理解当前的情况会如何发展。
来源:疾病预防控制中心
让我们看看金融市场在 SARS 爆发期间的表现。
来源:雅虎财经和 GitHub 代码
这是我们正在使用的四种工具的相同的每日百分比变化。回想一下,原油、标准普尔 500、黄金和 20 年期美国国债。因为这是一种在中国流行的疾病,所以我们认为对金融市场的影响较小,但红线表明何时宣布这是一种可能的卫生紧急情况。你可以看到两个方向都有一些剧烈的波动,表明波动性增加了。下面的图表将使它更加清晰。
来源:雅虎财经和 GitHub 代码
你可以看到原油在此期间大幅下跌,但与冠状病毒相比,S&P500 受到的影响相对较小。黄金和 TLT 的情况也是如此。
让我们看看这一时期的提款情况。
来源:雅虎财经和 GitHub 代码
你可以看到下降相当严重。在此期间,S&P500 约为 15%,原油约为 30%。但金价又回到了 15%左右,美国国债下跌了 5%左右。我应该指出的是,由于疫情得到了很好的控制,下降幅度没有我们现在看到的那么大。
我们将尝试相关性分析,我们看到一切都与 S&P500 负相关。
来源:雅虎财经和 GitHub 代码
这不是正确的数据,因为这种流行病发生在中国。一个更好的衡量标准是将其与上证综指进行比较。但尽管如此,我们看到这段时间 S&P500 和美国国债、黄金和原油的负相关性非常高。
让我们看看危机结束后事情会如何发展。
来源:雅虎财经和 GitHub 代码
蓝色阴影区域是世卫组织宣布危机已经避免的时候。你可以看到,虽然在世卫组织宣布进入紧急状态后有相当大的跌幅,但指数实际上恢复得相当快,而且自这种情况发生以来的天数也相当短。
为简单起见,我仅采用了四种工具,您可以使用自己的本地指数进行任何您可能想要进行的相关性分析。请在评论中与我分享你的见解。
控制 SARS 的措施与控制冠状病毒的措施相似。事实上,它处理得非常好,因此它没有从中国传播出去。
我们将看到 3 月 26 日世卫组织宣布紧急状态后的结果。
来源:雅虎财经和 GitHub 代码
一定要明白,这是一个月、三个月、半年和一年的回报。你可以看到,标准普尔 500 的回报率非常强劲,在各个时间段分别为 5%、10%、15%和 25%。对其他人来说也是类似的情况。这里,CL=F 代表原油期货,GC=F 代表黄金期货,TLT 代表国债。
如果你要问哪些行业受影响最大,以及危机过后该行业的表现如何,我试图用这张图表来回答这个问题。
来源:英国《金融时报》和摩根大通
您可以看到,受影响最大的行业位于顶部,回报为正或受影响最小的行业位于底部。这张图是摩根士丹利资本国际中国板块。如你所见,受影响最大的是耐用消费品、软件、酒店、餐厅和休闲以及航空公司。受影响最小的是医疗保健、食品、饮料和烟草。深蓝色表示该行业在危机期间的表现,浅蓝色表示该行业在危机后的表现。你可以看到,食品和饮料不仅在危机期间有正回报,而且在疫情结束后也延续了这种积极的表现。这给了我们一些线索,比如快速消费品或日常必需品中的产品是我们可以寻找的,类似的医疗保健和制药公司也显示出类似的趋势。有趣的是,在疫情结束期间和之后,零售业呈现出负面趋势。
因此,如果我们认为这种情况很有可能发生,那么你就会知道哪些行业将出现健康复苏,哪些行业将需要更多时间才能复苏。
在这种情况下,我们能够通过强有力的措施控制疫情。我们现在将进入下一个场景。
场景 2:疾病广泛传播
来源:维基百科
类似的事情在过去也发生过。我们正在谈论始于第一次世界大战期间的西班牙流感,它影响了近 56%的世界人口。
另外两个是亚洲流感和香港流感,影响了大约 50 万人。为了比较,我给出了冠状病毒的数字。这些数字可能不是最近的,但它可以帮助比较现在和过去的情况。
猪流感
对于第二种情况,我们将与始于 2009 年、影响了大约 20%人口的猪流感进行类比。让我们看看这一时期的财务影响。
这是猪流感的时间表。
来源:疾病预防控制中心
现在让我们看看金融市场。
来源:雅虎财经和 GitHub 代码
蓝线是第一个病例报告的时间,红线是世卫组织宣布紧急状态的时间。
因此,你会看到与目前市场上看到的截然不同的情况,因为市场正在从 2008 年金融危机中复苏,而股票在那个特定时期被大幅低估。因此,这些运动不像我们现在看到的那样剧烈。
来源:雅虎财经和 GitHub 代码
但是尽管如此,当疫情宣布的时候,在红线之后有原油的下降。黄金和标准普尔 500 也是如此。只有 TLT 在上升,原因是美联储降低了利率以支持经济。
让我们现在检查一下提款。
来源:雅虎财经和 GitHub 代码
最大下降
原油-21.68%
SP500 -16.00%
黄金-13.56%
TLT -14.37%
所以标准普尔 500 在疫情宣布后下降了 6%。原油下跌了 18%左右,黄金下跌了 7%左右,TLT 下跌了 13%左右。然而,出现了多次波动,虽然最初并不认为它很严重,但它开始蔓延到美国的许多人。因此,我们看到许多人不是在疫情宣布时而是在后来的某个时候跌倒的。
现在让我们看看散点图告诉我们什么。
来源:雅虎财经和 GitHub 代码
在这里,你可以看到一个清晰的关系出现在资产类别中。例如,标准普尔 500 和 TLT 是负数,这意味着如果 TLT 上升,S&P 500 将下降。与黄金的关系并不明确,因此我们不能确定黄金是否是一个安全的避风港。
对于原油,我们看到两者之间的正相关关系,这意味着如果 S&P500 下跌,原油价格也会下跌。
总而言之,S&P500 与原油正相关,与 TLT 负相关,与黄金无关。
让我们看看在世卫组织说一切都好之后事情会变成怎样。
来源:雅虎财经和 GitHub 代码
正如我们之前看到的,蓝色阴影区域是在世卫组织宣布疫情结束之后。你可以看到回报相当可观。数据是申报后一年的,这些是回报。请记住,回报是从报告第一个案例的开始日期开始计算的。
从第一次约会返回
原油 73.62%
SP500 42.20%
黄金 29.89%
TLT -10.46%
因此,当一切都理清了,市场试图赶上,并进入牛市模式,一切看起来都非常正常。让我们以条形格式查看回报,以示强调。
来源:雅虎财经和 GitHub 代码
在这里,您可以看到所有仪器在时间范围内的性能。
我们来看看板块表现。
来源:雅虎财经和 GitHub 代码
有趣的是,与前一种情况不同,这里的金融部门表现相对较好,实际上处于领先地位。原因基本上是,自猪流感疫情爆发一年后,世界陷入了经济衰退,导致大量抛售,并导致金融市场被低估。科技股也是类似的情况。
能源行业在疫情期间受到了严重影响,但在形势好转后大幅反弹。这类似于我们之前看到的原油反弹。
亚洲型流感
来源:雅虎财经和 GitHub 代码
来源:雅虎财经和 GitHub 代码
我希望这能让人们对目前的形势有所了解。事实上,再做一个练习,让我们看看亚洲的流感疫情。
以下是 S&P500 的表演。
在这里,红线表示它开始在美国传播的时间。蓝色区域是在世卫组织宣布它结束之后。
同样的故事也发生在提款上,在世卫组织宣布公共紧急状态后,我们看到最高提款率为 20%。
香港流感
现在来看看香港流感告诉我们什么。
来源:雅虎财经和 GitHub 代码
红线表示世卫组织宣布紧急状态的时间,而蓝色阴影区域显示世卫组织宣布紧急状态结束后的时间。
在提款方面,我们看到最大提款率为 35%。
来源:雅虎财经和 GitHub 代码
这里的一个重要观察是,市场不喜欢不确定性。因此,当宣布紧急状态时,市场急剧下跌。但一旦我们采取了必要的措施来控制局势,一旦世卫组织宣布它是安全的,市场就会立即做出反应,反弹到危机前的水平,在某些情况下甚至超过危机前的水平。
西班牙流感
让我们看看西班牙流感给我们的启示。西班牙流感可以说发生了三波。1918 年 6 月和 7 月,然后是 1918 年 10 月至 12 月,最后是 1919 年 1 月至 3 月。我用的是道琼斯指数,S&P500 是 1927 年创建的。你可以看下面的表现。
来源:宏观趋势
你可以看到,即使在西班牙流感最严重的时期,市场也在上涨。原因是,类似于猪流感,有一个更大的事件影响了市场,这就是第一次世界大战。1918 年西班牙流感来袭时,世界正从结束的第一次世界大战中恢复过来。这就是市场上升趋势的原因。
第二次世界大战
作为题外话,下面是第二次世界大战对金融市场的影响。
来源:雅虎财经和 GitHub 代码
V 形类似于我们过去看到的图表,只是恢复的时间不同。
来源:雅虎财经和 GitHub 代码
我们知道第二次世界大战结束于 1945 年,但市场从 1942 年开始上涨。正如我之前所说,市场不喜欢不确定性。这就是为什么市场跌破,但在 1942 年,盟军已经走到一起,并开始规划一个协调的计划,以击败敌人。因此,市场考虑了这一信息,因此,一旦我们确定了计划,市场就开始复苏。这里可以看到的最大下降幅度约为 43%。
要素投资
在当前形势下,你会如何投资?一种新兴的方法是要素投资。因素的一个例子是动量,它表示如果任何东西在一年内上涨,它将继续上涨。
让我们看看 COVID19 期间一些因素的表现。
来源:范弗利特,皮姆和巴尔图森,圭多,股票风格和西班牙流感(2020 年 3 月 30 日)
从 1 月到 3 月 20 日,各种因素的月度表现如上所示。
在这里你可以看到,当其他投资者普遍持消极态度时,基于动量的因素投资却跑赢了市场。在某种程度上,即使是低波动性也跑赢了市场。但这种情况会持续下去吗?
基于目前的表格回答这个问题其实很难。让我们试着看看过去事情是如何发展的。
来源:范弗利特,皮姆和巴尔图森,圭多,股票风格和西班牙流感(2020 年 3 月 30 日)
我们可以看到,虽然动量接近市场(黑色),小盘股表现不佳。但在复苏阶段,小盘股的表现优于所有其他形式的投资。你还可以看到,在复苏阶段,动量和低波动股票的表现不如市场。
但到目前为止,我们已经看到了与流行病相关的数据。我将尝试展示 1926 年之前,或者更具体地说是 1872 年至 1918 年之间发生的市场调整数据。这些调整并不完全与大流行有关,但它们是一个非常不确定的时期,让我们看看这将如何发展。
来源:范弗利特,皮姆和巴尔图森,圭多,股票风格和西班牙流感(2020 年 3 月 30 日)
正如我们之前看到的,低波动性和基于动量的投资不会像市场那样下跌,但在复苏阶段也不会跑赢市场。相比之下,小盘股在调整阶段跌幅大于大盘,但在复苏阶段的表现优于大盘。
这什么时候会结束?
哈克特金融公司对流行病的研究表明,大多数重大病毒爆发,如 1665 年的大瘟疫和一个多世纪前的西班牙流感,都在 3 个月内发生。换句话说,那些糟糕的事件主要在 12 周内来来去去。
考虑到过去的数据显示,世卫组织宣布安全的天数已经减少,我们可能需要三到六周的时间。
猪流感:116 天
非典:101 天
然而,我怎么强调社交距离的重要性都不为过。世界各地的政府应该积极主动地施加社会距离。事实上,以下是西班牙流感在美国蔓延时美国各城市的图表。
来源:国家地理
阴影区域是实施封锁的时间,你可以看到死亡人数迅速下降。因此,社交距离对于克服这种危机是非常有效的。
如何保护你的股票重投资组合?
如果你在考虑你的投资组合,那么 TLT 是有意义的,因为它与 S&P500 负相关。你应该避开原油,因为由于当前的危机,对原油的需求可能会减弱。
注意:在采纳任何建议之前,您应该咨询财务顾问。
摘要
唷!我们经历了相当多的场景。让我们试着用表格总结一下。
来源:雅虎财经和 GitHub 代码
您可以看到,在缩减方面,COVID 排名第三,为 33%。但如果你在六个月后检查回报,它们看起来真的很有希望。这将让您对 COVID 19 后 6 个月和一年后的回报有所了解。
来自《走向数据科学》编辑的提示: 虽然我们允许独立作者根据我们的 规则和指南 发表文章,但我们并不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语 。
来源和参考
- 大英百科全书
- 疾控中心
- 世界卫生组织:世卫组织
- 雅虎财经
- 金融时报
- 世界经济论坛
- 医药网
- 源代码
- 维基百科(一个基于 wiki 技术的多语言的百科全书协作计划ˌ也是一部用不同语言写成的网络百科全书ˌ 其目标及宗旨是为全人类提供自由的百科全书)ˌ开放性的百科全书
- 摩根大通
- 霍华德·马克斯备忘录
- NCBI
- 世界经济论坛
- 摩根大通
- van Vliet,Pim 和 Baltussen,Guido,股票风格和西班牙流感(2020 年 3 月 30 日)
- 国家地理
- 熊猫数据阅读器
面板数据回归:一种强有力的时间序列建模技术
在数据科学和机器学习问题中应用计量经济学
弗兰基·查马基在 Unsplash 上拍摄的照片
在之前的一篇文章中,我简要提到了面板数据模型;在这篇文章中,我将通过一些技术细节对此进行更深入的探讨。正如我在那篇文章中所说的,计量经济学拥有数据科学家工具箱中一些最重要的工具。它有许多用例——从测量温度变化对农业的影响到时间序列数据建模和预测。
什么是面板数据?
那么到底什么是面板数据呢?首先,让我们来看看下面的定义:
面板数据是随时间重复测量的观察的多维数据。
这是一个单行的定义,但是有很多内容需要解释。该定义隐含地描述了面板数据集的三个关键属性:
- 特性 1:重复观察相同的对象/个人
- 特性 2:对那些相同的个人/对象测量多个变量
- 特性 3:观察发生在多个时间点
为了说明这些属性,下面是一个假设的(我在运行中创建的)数据集,有 4 列 4 行。
- 数据集包含两个(假想的)美国县的 4 个观测值-克劳县和布尔县(属性 1)。
- 对于每个县,测量了 2 个变量—安装的速度摄像头的数量和交通违规的数量(属性 2)。
- 最后,测量发生在 2 个不同的离散时间点——2018 年和 2019 年(属性 3)。
多酷啊!
面板数据回归技术
我现在将继续描述面板数据建模技术如何回答特定的问题。假设使用上述 4 x 4 数据集,我们想要回答以下问题:
安装的测速摄像头数量对交通违章案件数量有影响吗?
如果我们运行简单的线性 OLS 回归,我们应该能够快速检查两个变量之间的关联(如果有的话):
traffic _ violation = f(speed _ camera)
然而,请记住,这不是普通的数据集,这是一个面板数据。这意味着我们可以比运行简单的 OLS 回归更有效地使用它。
怎么会?
首先,我们不应该忘记自变量还有另外两个属性——county 和 year。这意味着个体和时间维度存在差异,我们可以在更高级的模型中捕捉到这种差异,我们称之为 面板数据回归 。
面板数据模型(即估计量)有三种主要类型,下面简要介绍它们的公式。
a)汇集 OLS 模型
混合 OLS(普通最小二乘)模型将数据集视为任何其他横截面数据,并忽略数据具有时间和个体维度。这就是假设与普通线性回归相似的原因。
b)固定效果模型
虽然安装速度摄像头可能会对交通违规产生影响,但由于速度摄像头以外的原因(例如,高速公路巡逻率较高),每个县的交通违规情况也可能有所不同。).然而,这并没有反映在上述 OLS 模型中。固定效应模型更进了一步,它考虑了个体实体(在我们的例子中是县)之间的差异:
c)随机效应模型
在固定效应模型中,我们控制了各个县之间的差异。但是那些在个体中不变但随时间变化的变量呢?随机效应模型考虑了这些个体变化以及时间相关变化。该模型消除了变量的偏差,这些偏差是无法观察到的,并且会随着时间的推移而变化。
履行
真的只是几行代码(假设你已经完成了数据角力的另外 80%的工作!).是镇上最好的 R 库,只需三个简单的步骤就能实现你的模型:(1)输入数据;(2)将数据转换成面板数据帧;(3)实现您指定的模型。
# import package
library(plm)
# import data
df = read.table("../data.csv")
# convert the data frame to a data format recognizable by `plm`
pdf = pdata.frame(df, index = c("county", "year"))
# specify and run the model
model = plm(traffic_violation~speed_camera, data = pdf, model = "random")summary(model)
最终注释
数据科学是一个不断发展的学科,来自许多学科的各种工具和技术丰富了它。计量经济学提供了传统上用于社会和经济研究的工具,但也增加了数据科学的价值。对于那些感兴趣的人来说,两本开放存取的书可以让你开始计量经济学和高级社会科学研究:
这两本书以通俗易懂的方式描述了复杂的技术,以及 R 编程语言的应用和实现。
PAPEM-DM:迈向数据科学胜利的 7 个步骤
数据科学项目生命周期
数据科学管道的高级概述。
介绍
在这篇文章中,我将尝试分解成功的数据科学项目所涉及的步骤:从理解业务需求到维护您的数据科学团队最终产生的任何数据产品。在这个过程中,我们将讨论每个步骤的要求以及每个步骤所必需的技能和工具。
PAPEM-DM:数据科学框架
我第一次了解数据科学管道是在参加 Codeup (完全沉浸式、基于项目的 20 周数据科学和 Web 开发职业加速器)时。从那以后,我阅读了无数的文章,列举了数据科学项目中涉及的步骤,但我还没有遇到一篇包括所有这些步骤的文章。因此,这篇文章。
PAPEM-DM 是一个方便的首字母缩写词,是我自己编的,用来提醒我的步骤:计划、获取、准备、探索、建模、交付和维护。这应该从头到尾涵盖数据科学流程的所有主要步骤。如果我错过了什么重要的东西,请告诉我!哦,等等,他们都很重要…所以评论吧!LOL。
规划
在这第一步,我们必须确定需要解决的问题。大多数利益相关者和最终用户讲的是业务,而不是数据科学;通常情况下,他们问的问题要么太笼统,要么太具体,无法解决需要解决的实际问题。当开始一个新的项目时,重要的是在迷失在杂草中之前先了解事情的商业方面。
在《用数据思考的一书中,马克斯·史隆主张先回答“为什么”,再弄清楚“如何”他建议使用由背景、需求、愿景和结果组成的 CoNVO 框架。有哪些情况或术语可以更好地理解或解释问题?利用数据可以满足哪些特定需求?一旦项目达到目标,它会是什么样子?最后但同样重要的是,项目的可交付成果将如何在组织内使用,谁将拥有或维护它?
这一步不需要特别的工具,但这并不意味着它不重要。事实上,这一步非常关键,因为它可能意味着您的数据科学项目的成败。
这一步的可交付成果是对您想要完成的事情和成功的衡量标准的清晰描述。
获得物ˌ获得
这一步包括获取原始数据,您需要这些数据来洞察您所面临的问题。它要求您考虑从哪里以及如何获取数据,以及这是一个手动还是自动的过程。
您需要的工具将取决于数据源(数据将来自哪里)和您的工作环境。例如,如果数据已经存在于数据仓库中,那么获取数据可能仅仅意味着使用 SQL Alchemy 连接到您的数据仓库(如 Redshift ),并通过 Pandas dataframe 将其加载到您的 Python 环境中。另一方面,如果数据是通过电子邮件定期发送的,您可能希望使用 Fivetran 等连接器服务首先将文件加载到 Redshift 中,然后使用 Microsoft Power BI 等业务分析工具连接到 Redshift 集群。如果一个项目是一次性的特别请求,那么这个过程可能仅仅意味着检索一个平面文件(。csv,。tsv,甚至。xlsx ),然后将其直接加载到 Pandas 数据帧中,以便在 Jupyter 笔记本中进行进一步处理。另一种直接的方法是从 Salesforce 或 Zendesk 等 CRM 平台中手动提取数据。
无论是哪种情况,这一步的结果都是一个数据集,可以被您选择的武器(或工具)处理。
准备
真实世界的数据通常是杂乱的,在进行分析之前需要清理和处理。数据准备就是将我们从上一步获得的原始数据转换成一种格式,使我们能够收集见解。该步骤中涉及的一些过程通常包括处理缺失值、null 值、NaN 值和重复值。根据项目的性质,这些可能需要估算或完全放弃。还需要处理数据类型以及日期时间冲突。此外,您可能还需要将几个数据集合并到一个大规模的表或数据框架中,或者甚至将大量的列精简为几列。
同样,所需的工具将根据您的工作环境而有所不同。对于 Python,您可能使用的库是 Pandas、matplotlib 和 scikit-learn。如果您正在使用 Power BI,您很可能会使用它的 Power 查询编辑器和 DAX 公式,请记住,在连接和合并工作之前,表之间的关系也必须正确定义。如果您的组织使用 Fivetran,您可以在分析之前使用它的“转换”特性来执行一些 SQL 查询。
一旦您完成了清理和准备工作,您就可以进入下一步:探索性数据分析。
探测
根据 工程统计手册 ,探索性数据分析,简称 EDA,是一种采用多种技术进行数据分析的方法,目的是:
- 最大限度地洞察数据集;
- 揭开底层结构;
- 提取重要变量;
- 检测异常值和异常值;
- 测试基本假设;
- 开发简约的模型;和
- 确定最佳因子设置。
换句话说,这一步是你开始处理数据以发现有趣的模式、异常,并发现哪些特征或变量是你最大的驱动因素。
特征工程和预处理也是这一步骤的主要组成部分。对于 Python,可以使用 Pandas、scipy、numpy、statsmodels 和可视化库,如 matplotlib、seaborn、plotly 和 bokeh。
在这一步结束时,您不仅应该有一个可以在机器学习模型中使用的格式的数据集,而且还应该回答在规划步骤中解决的一些问题。
建模
这是数据科学项目生命周期中最受欢迎的部分。在建模过程中,我们将经过清理和处理的数据用于训练和测试一个或几个机器学习算法,以便进行预测。
机器学习算法的类型包括回归,分类,聚类,异常检测,时间序列预测,以及我最喜欢的:自然语言处理或 NLP。
这一步涉及的任务有:
- 将数据分成训练集和测试集
- 确定最适合项目特定用例的机器学习模型
- 训练模特
- 基于训练集进行预测
- 评估训练集的结果
- 调整超参数
- 冲洗并重复
- 选择性能最佳的型号
- 基于测试集进行预测
- 评估测试集的结果
有太多的机器学习算法,由数据科学家根据特征(变量)和目标(我们试图预测的东西)的性质来选择使用哪一种。
交付
训练和评估一个机器学习模型很好,但迟早,你需要让其他人使用你发现或开发的东西。数据科学项目的交付成果可以像幻灯片一样简单,报告您的探索性数据分析的结果,并对后续行动提出建议。另一个是自助仪表板,其他人可以使用它来促进基于数据的管理。您可以在数据库中生成另一个可用于实时报告的表。最后但同样重要的是,您可以开发一个应用程序,使用您训练好的模型根据新的观察结果进行预测。例如,处理与客户的聊天记录以预测他们的满意度或当前情绪的机制。
这一步所需的工具将取决于项目可交付成果的类型。所需的技能可能很简单,比如在使用 AWS 或 Azure 等服务的无服务器环境中采用讲述故事的技术或成熟的管道部署。技术能力也是决定您可以交付何种类型的数据产品的一个因素。这也是为什么与数据工程团队保持良好的工作关系至关重要。事实上,在一个完美的世界里,数据科学家和数据工程师应该步调一致。
在我们今天生活的这个敏捷世界中,今天发布一个最低可行产品(MVP)比明天发布一些完美的产品更有益。
“现在发货,以后迭代。”
–查纳
维护
维护数据科学项目需要对管道中的每个组件保持警惕。它有助于从头到尾回顾和检查任何会影响项目的变更。例如,数据的形状和结构可能会改变,这将影响您的预处理脚本。确保您知道您使用的包和库的安全漏洞,并相应地更新每一个包和库,同时确保不破坏项目依赖性。此外,随着新技术的出现,寻找改进管道的机会(如自动化)。
结论
这篇文章已经很长了。概括地说,在规划和执行数据科学项目时,使用 PAPEM-DM 框架。请注意,框架中的步骤并不是相互隔离的。通常情况下,你会发现自己在以一种迭代循环的方式而不是顺序地做这些步骤。例如,您可能希望在建模后进行更多的探索,因为您在结果中发现了一些有趣的东西,并且希望深入研究。只是不要在杂草中迷失太多!
今天到此为止!
您对数据科学项目生命周期有什么样的体验?当从开发切换到生产时,这个框架会如何变化或保持不变?请留言,多多益善!
论文剖析:完形填空驱动的自我注意网络预训练:艾
一种用转换器预训练语言模型的新方法。
完形填空驱动的自我注意网络预训练是脸书人工智能研究所发布的一种新型语言预训练,旨在使用 transformer 模块获得改进的单词嵌入进行微调。由于这是一次论文剖析会议,如果读者需要更好地理解,我将尝试从直觉和数学两方面解释论文的主要思想。如果读者能同时打开这篇文章和报纸,并开始一起阅读,那就太好了。我将跳过摘要、介绍和相关工作部分,从第 3 部分开始。
先决条件
关于变形金刚的知识。杰伊·阿拉姆马的这篇博客和原始论文一起,对理解《变形金刚》非常有帮助。
3。双塔型号:
在这一部分,本文揭示了他们的新颖的结构,以预训练单词嵌入。在直接跳到架构之前,让我们先建立这个预训练的直觉。
现在,什么是完形填空阅读?完形填空阅读是一种教学策略,要求用户从单词库中用正确的单词填空。例如,给定一个句子,这是我的第一篇中文章,预训练的思想是预测我的,给定这是和**第一篇中文章。**有道理?如果没有,不要担心。一会儿我会展示一个示意图,让你超级清楚。
让我们来看一下双塔的类比。现在,假设这两座塔是两个黑匣子。由于单词/令牌 my 在短语和之间,这是和,这是将前往左塔,而第一中间文章将前往右塔作为输入,以最终预测 **my。左塔或前塔从左到右工作,这意味着给定这是,它试图预测我的,右塔或后塔从右到左工作,这意味着给定文章介质第一,它试图预测我的。**句子的开头和结尾都附有 < s > 标记。由于两座塔的输入句子长度不同,需要进行屏蔽。
3.1 块状结构
图 1:完形填空预训练的模型结构。来源:原始论文
在这一节中,我将详细介绍双塔。这些塔是堆叠在一起的变压器解码器模块,如图 1 所示。绿色方块是前塔的一部分,蓝色方块是后塔的一部分。从给定的图中可以看出,给定前塔的 < s >、a 和后塔的 c、****b需要最终预测。
本文使用了一种不同的使用 CNN 编码的单词嵌入。编码的细节可以在这里找到。简而言之,给定的单词输入被分解成字符并生成字符嵌入。除此之外,还使用不同滤波器尺寸的 Conv1D 层,从而获得不同尺寸的输出。在此之后,应用最大时间池操作来获得单词的固定维度表示,将其提供给高速公路网络以获得最终的单词嵌入。这个过程如图 2 所示。
图 CNN 编码的细节。从文字输入到高速公路网是本文的相关内容。来源原文论文。
完形填空预训练使用这种 CNN 编码来生成单词嵌入。固定正弦位置编码(如 transformer 论文中所述)与 CNN 编码的单词嵌入相加,以提供单词在句子中的相对位置。为了让读者更好地理解,我准备了模型架构的扩展版本。这如图 3 所示。CNN 编码+位置编码生成单词/令牌嵌入,用作转换器模块的输入。输入嵌入在两个塔之间共享。每层前后塔的块数相同。明确一下,块 11 表示第一层的第一块,块 N3 表示第 N 层的第三块。在预训练时,如果我们想要预测第 i 个令牌/单词(例如 my ,在转发塔中,他们屏蔽了所有在 i 之后的令牌,包括第 i 个令牌。类似地,在向后的塔中,它们屏蔽了在 i 之前的所有记号,包括第 i 个记号,使得模型在预测第 i 个记号时不会得到该记号的信息。
图 3:完形填空预训练的扩展模型结构。
这里需要注意的一点是,在左边(绿色)侧,模块 11 仅从s>T21 获取输入,模块 12 同时从st和获取输入,这个和模块 13** 从s>获取输入,这个和**就是****
3.2 表示的组合
在本节中,我将讨论图 3 所示的联合收割机块。一旦两座塔建成,最后一层的输出将与自我关注层结合。在此之后,使用一个前馈模块,通过大小为 V (其中 V 是词汇大小)的 softmax 激活来预测正确的单词/令牌。
他们没有建议简单地馈送前向和后向塔的最后层的所有输出,而是在这样做之前使用掩蔽。屏蔽几乎与前面类似,即对于前向塔,屏蔽第 i 块之后的所有输出,包括第 i 块;对于后向塔,在预测第 i 令牌时,屏蔽第 i 块之前的所有输出,包括第 i 块。如果, FL1 ( 同图 3 中的绿色区块 N1**)和 BL1 ( 同图 3 中的蓝色区块 N1)**是最后一层区块 1 的输出(符号中的 L 和图 3 中的 N),则 FL1、FL2、…、FL(I-1)【T24 最终层的剩余块被屏蔽以预测第 i 个令牌,如图 3 所示。在自我关注层中, FL(i-1) 和 BL(i+1) 被求和并串接,分别作为基模型和大模型的查询向量。基于前向和后向块的其他未屏蔽的最终层输出来创建关键字和值向量。
第 i 个模块的输出屏蔽在预训练期间完成,但在微调时屏蔽被移除,这表明测试结果有所改善。
4 微调
不同的过程用于不同的微调任务。
分类和回归任务
图 4 显示了单句任务的微调过程。变化是:(a)输入句子的所有记号都到每个塔。(b)移除具有 softmax 激活的前馈层。在这种情况下,语言模型的输出将是自我关注层的输出,即:**【批量大小 X 时间步长 X 关注度】。由于所有输入的句子在开头和结尾都附加了记号 < s > ,所以很容易得到这两个记号的自我注意输出向量,并且将它们连接起来产生一个向量2 * attention-dim(attention-dim = 1024****)size。**基本上,这两个令牌用于计算分类和回归任务的最终输出。如果这是一个分类问题,在级联向量的顶部,一个前馈层与 softmax 激活一起使用,以输出期望数量的类。对于回归,输出维度是 1,激活是线性的。
图 4:对单句任务进行微调的图示,其中第一个和最后一个标记的输出被提供给特定于任务的分类器(W)。最终组合层(梳状)的掩膜被移除,这导致基于所有前向和后向状态的表示
当输入是两个句子或问答对时,则在两个输入句子之间使用一个分隔符( < sep > )。所以,在自我关注层的输出连同 < s > token 向量,我们也得到了 < sep > token 的向量输出。在句子对问题中,不是仅连接 < s > 记号向量,而是连同 < s > 记号向量 < sep > 记号向量也被连接以产生 3*attention-dim 向量。现在在回归或分类问题的基础上,前馈层被用于描述单句任务。
结构化预测任务
对于命名实体识别,输入将是预先训练的语言模型的输出,但是没有仅在自我注意层的输入处的掩蔽。
无屏蔽
可以看出,在自关注层的输入处禁用掩蔽改善了测试结果。但它不建议在塔的最后一层之前禁用掩蔽。
结论
我跳过了其余部分,因为它们以非常简单易懂的方式描述了模型参数、数据集和结果。请在评论中告诉我,我是否应该将这篇文章扩展到实验设置和结果部分。
论文摘要:发现强化学习代理
通过元学习学习更新规则
Jelleke Vanooteghem 在 Unsplash 上拍摄的照片
介绍
虽然深度学习领域发展速度极快,但有可能让我们更接近人工通用智能(AGI)的独特研究很少,也很难找到。这个规则的一个例外可以在元学习领域找到。最近,元学习也被成功地应用于强化学习。Oh 等人的论文“发现强化学习代理”从 DeepMind 提供了一个新的和令人耳目一新的视角来看待元学习在强化学习中的应用。
传统上,RL 依赖手工制作的算法,如时间差异学习(TD-learning)和蒙特卡洛学习、各种政策梯度方法或其组合,如行动者-批评家模型。这些 RL 算法通常经过微调,以训练模型完成非常特定的任务,例如玩 Go 或 Dota 。一个原因是,为了稳定训练,需要调整多个超参数,例如折扣因子γ和自举参数λ。此外,非常更新的规则以及诸如值函数的预测器的选择需要努力地选择,以确保模型的良好性能。整个过程必须手动执行,并且通常是乏味和耗时的。
DeepMind 正试图通过他们的最新出版物来改变这一点。在论文中,作者提出了一种新的元学习方法,通过与一组简单的环境进行交互来发现学习目标和探索过程。他们称这种方法为习得政策梯度(LPG) 。这篇论文最吸引人的结果是,该算法能够有效地推广到更复杂的环境,这表明了纯粹通过交互来发现新的 RL 框架的潜力。
在这篇文章中,我将试着详细解释这篇论文,并在我理解有问题的地方提供额外的解释。因此,如果你想了解更多的细节,我将尽量贴近本文的结构,以便你能在原文中找到相关的部分。让我们开始吧!
元学习和早期方法
深度学习(包括深度 RL)是众所周知的极度数据饥渴。相比之下,人类可以更有效地学习新技能。例如,会骑山地车的人也可以很快学会如何骑公路车。也许他们甚至可以学习如何骑摩托车,而不需要太多额外的外部输入。元学习旨在通过“学会学习”为机器学习模型配备类似的能力,即学习训练过程,以便更快地适应新的数据分布。
在论文中,作者根据他们旨在解决的问题细分了元学习框架:
- 仅使用几个例子将在一个或多个任务上训练的模型适应新的任务(少数镜头适应):这种变体由通用算法举例说明,例如 MAML 或爬行动物,以及 RL 特别是在 RL 的上下文中。
- 单个任务在线改编的元学习 : 徐等人(同样来自 DeepMind)的元梯度 RL 就属于这一类。该算法在与环境交互的同时在线调整超参数,例如γ和自举参数λ**。也有可能以这种方式学习内在奖励或辅助任务。**
- 学习新的 RL 算法:已经有多个团队在尝试通过与多个环境交互来元学习新算法。例如,进化策略梯度方法试图使用进化方法来学习策略梯度损失函数。DeepMind 的研究人员最近也表明,探索的有用知识可以作为一种奖励功能来学习。
以上所有方法都使用了价值函数的概念,并试图将其一般化。在所描述的论文中提出的框架第一次尝试学习它自己的引导机制。现在让我们来看看这是如何做到的。
已知政策梯度(LPG)
本文的主要目标是找到最佳梯度更新规则:
让我们详细解释这个公式:由η参数化的最佳更新规则使代理生命周期结束时的期望回报最大化,
据此,我们从环境 p(ε)和初始代理参数 p(θₒ).的分布中取样这意味着在训练一个代理直到它的寿命结束之后,我们想要达到最大的期望回报。
为了实现这一点,而不具体指定我们采样的环境类型,我们要求代理生成两个单独的输出:
- 预测的策略π(a|s)通常在策略梯度算法中,
- 输出在范围[0,1]内的 m 维分类预测向量 y(s)。
策略和预测向量 y(s)都被用作元网络的输入。元网络是一个反向 LSTM,在每个更新步骤产生一个关于如何更新参数π_hat 和 y_hat 的指导(见图 1)。在这一点上,我并不完全清楚为什么选择了落后的 LSTM 模式。我的理解(虽然我可能是错的)是向后的方向(从环境生存期结束到初始代理状态)对应的是代理的梯度下降优化中的向后方向。
图 1:元学习过程。来自:Oh,Junhyuk,等《发现强化学习算法》。arXiv 预印本 arXiv:2007.08794 (2020)。
元学习 LSTM 网络的输入是
其中 r_t 是时间 t 时的奖励,d_t 表示剧集终止,γ是前述的折扣因子。由于 LSTM 不明显依赖于观察和动作空间,所以它在很大程度上对环境是不变的。相反,通过训练有素的代理π的策略,观察和动作空间仅被间接考虑。
更新代理
在内部循环中,代理使用以下公式进行更新
如果您仔细观察这个公式,您会注意到期望值中的第一项与加强更新类似,只是使用了π_hat 而不是通常的期望回报 g。由于π_hat 是由元学习者生成的,因此它允许算法灵活地指定自己的“值”函数概念。
第二项最小化预测 y 和期望 y 之间的 Kullback-Leibler 散度(分布的一种距离度量形式)。y 为 LSTM 发现有用的更新规则提供了额外的信息。元学习者可能通过 y 间接影响政策。
更新元学习者
更新元学习者 LSTM 的公式如下:
这肯定需要一些解释。理想情况下,我们希望优化环境分布上的最佳梯度更新规则的公式,如上所示。正如您可能注意到的,寿命结束时的预期回报取决于带有寿命结束参数的保单的预期,而这些参数又取决于η。这种认识使我们产生了将元梯度作为政策梯度来计算的想法。上面公式中的第一项正好对应于这个梯度。
其他项是正则项。引入这些是因为元学习可能非常不稳定,特别是在训练过程的开始,y_hat 没有任何附加的语义。前两个正则项用于最大化策略和预测向量的熵。策略熵正则化是 RL 中众所周知的技术(例如参见https://arxiv.org/abs/1602.01783)。其余的术语引入了 L2 正则化。这有助于防止策略和预测更新的快速变化。
警告
如您所料,在让这种方法发挥作用之前,还有一些其他小的实现问题需要解决。
首先,在跨不同环境培训代理时,不可能对所有代理使用固定的学习率。作者通过以下事实来解释这一点:最优策略π_hat 需要根据学习率更新来缩放,以使训练稳定。此外,由于η和π_hat 在训练期间改变,我们别无选择,只能在元学习期间动态调整学习速率(值得注意的是,这仅用于训练元学习者)。在该论文中,建议使用 bandit 来分别对每个寿命的有用超参数进行采样,并根据寿命结束时的回报来更新采样分布。
此外,在补充材料中,作者指出,每当策略的熵变成 0(策略变成确定性的)时,他们就重置生存期,以防止训练崩溃。
实验
为了训练 LPG,作者为代理人设置了两个极其简单的玩具环境。其中之一是一个简单的网格世界,在环境中的特定像素处有奖励,如下图所示。第二个是延迟马尔可夫决策过程(MDP)。这只是一种描述环境的复杂方法,在这种环境中,代理人可以在某个时间点做出决策,而该决策将在稍后的某个时间点获得积极或消极的回报。每个领域使用 5 种不同的环境,捕捉诸如“延迟奖励、嘈杂奖励和长期信用分配”等问题。有关实验设置的更多详细信息,请参考文章。
图 2:来自网格世界的预测结果。来自:Oh,Junhyuk,等《发现强化学习算法》。 arXiv 预印本 arXiv:2007.08794 (2020)。
在我看来,论文中提出的两个最重要的问题是:
- LPG 如何发现预测的有用语义?
- 液化石油气能推广到更复杂的环境吗?
预测语义学
图 2 显示了论文中的可视化预测。左上方是一个网格世界示例,其中包含积极和消极因素,左下方是一个接近最佳的策略(白色路径)及其值(黄色=高,蓝色=低)。实验的结果显示在右边。这些是左下方策略的 y 的 30 个组成部分。这些是通过使用具有固定策略的 LPG 更新 y 获得的。查看预测,我们可以看到几乎所有的预测都与真实值有很高的相关性。它们中的一些在正奖励周围具有大的值,并且这些值被传播到邻近的像素。正如作者所指出的,这个“隐含地表明 LPG 正在要求代理人预测未来的回报,并使用这样的信息进行引导”。
为了显示可视化中看到的相关性确实存在,训练了一个简单的 1 层感知器来预测各种折扣因子的 y 的真实值。虽然 y 是用 0.995 的折扣因子生成的,但训练过的感知器也可以预测低至 0.9 的折扣因子的真实值。这意味着该框架可以自动发现预测的丰富和有用的语义,即“框架几乎可以恢复各种水平上的值函数,即使这样的语义在元训练期间没有被强制执行”。 这一点很重要,因为它表明,与单值函数相比,学习到的 30 维向量确实捕获了额外的语义信息。注意,该语义信息完全由算法学习。
图 3:推广到雅达利游戏。来自:Oh,Junhyuk,等《发现强化学习算法》。arXiv 预印本 arXiv:2007.08794 (2020)。
推广到更复杂的环境
****另一个重要结果是,LPG 可以无缝地推广到更复杂的环境,如 Atari 游戏,同时仅在上述玩具环境中进行训练。如图 3 所示,LPG 可以在几乎一半的游戏中击败人类水平的性能,而无需在如此复杂的领域上进行明确的元训练。在一些游戏中,元算法甚至超过了常用的优势演员评论家(A2C)。从图中可以看出,随着 LPG 看到越来越多的训练环境,性能提升很快。可以想象,通过专门设计的训练环境,性能在未来甚至可以超过最先进的专业算法。
讨论
从实验来看,LPG 似乎能够通过与简单(或者甚至更复杂)的环境交互来在很大程度上自动发现新的 RL 算法。由于教导人类也主要依赖于创建适当的学习环境,而不是微调更新规则,这使得训练算法和训练人类更加接近。此外,该框架能够推广到比它被训练的环境更复杂的环境,潜在地开辟了一种完全基于数据的 RL 的新方法。尽管与手工制作的 RL 算法相比,新方法在性能上仍然落后,但它可以在一些 Atari 游戏以及训练环境中胜过 A2C,这表明它严格来说并不比手工制作的方法差。我们还必须考虑到这样一个事实,即这些手工制作的方法是经过多年的工作而完善的,而 LPG 只是使用数据进行训练(如果我们暂时忘记训练稳定性的问题)。
在我看来,也许最重要的一点是,这种方法可以随着计算能力和数据的增加而扩展。这意味着随着我们的计算机越来越快(这是不可避免的),液化石油气只会越来越好。使用 16 核 TPU-v2 对论文中描述的模型进行了 24 小时的训练。虽然这可能对任何无法访问谷歌庞大计算资源的人来说都是禁止的,但在几年内,任何拥有现代 PC 的人都将拥有这种计算能力。我坚信,完全基于数据的算法最终是实现更强人工智能的唯一途径。
结论
在本文中,作者首次尝试从零开始学习 RL 中的更新规则,从而避免了手动发现复杂更新规则的繁琐过程。他们的方法完全是数据驱动的,并在学习过程中引入了归纳偏差,这也是我们可能期望在人脑中发生的。该论文表明,在玩具环境中训练时,奖励预测和状态评估是自然出现的。该方法强大的泛化能力表明,有可能从与可能简单的、程序化生成的环境的交互中发现极其有效的 RL 算法。
[知识蒸馏]从神经网络中提取知识
注意——YouTube 上还有一段视频解释了这篇论文
解决的问题
作者以一个非常有趣的类比开始这篇论文,以解释训练和推理的要求可能非常不同的概念。
给出的类比是幼虫和它的成虫形式,事实上两种形式对营养物的要求是完全不同的。
我们很容易理解,在训练期间,首要任务是解决手头的问题。我们最终会采用多种技术和技巧来实现目标。即学习模型参数的目标。
例如,您可以
- 使用已被证明能解决许多不同类型问题的网络集合
- 你可以用辍学来更好地概括
- 增加网络的深度,
- 使用更大的数据集等
此外,重要的是要认识到,在寻求学习的过程中,机器学习的机制是这样的,我们将探索各种路径,这些路径虽然对学习至关重要,但在推理阶段可能并不需要。换句话说,从推理的角度来看,这些额外的信息可能被认为是多余的。
这就把我们带到了推理的需求上,除了准确性,运行时性能,也就是预测的速度也起着重要的作用。
如果您的产品因为速度慢而无法使用,那么无论它有多精确,都没有关系。在大多数情况下,可用性战胜了准确性!
本文旨在解决如何使用具有较少参数的网络架构运行精确模型而不牺牲太多精度的挑战。
现有技术及其局限性
这不是第一次讨论这个问题。Rich Caruana 等人在 2006 年发表的一篇题为“模型压缩”的论文中阐述了使用繁琐模型知识训练简单网络的概念。
繁琐模型是具有大量参数的模型或者是模型的集合,并且通常难以在具有较少计算资源的设备上设置和运行。
在本文中,Hinton 提到了模型压缩,以证明他们可以从繁琐的模型中提取信息,并将其提供给更简单的模型。
在模型压缩文件中,使用的技术是使用 RMSE 最小化 logits 空间中的距离。本文认为,他们建立在这一见解,并提出了一个更普遍的解决方案;换句话说,Caruana 等人的模型压缩技术是 Hinton 等人提出的一个特例。
理解关键见解所需的背景知识
要欣赏这篇论文的关键见解,你应该有良好的直觉以及对 softmax 激活功能的数学理解!
这里我展示了一个典型的分类网络,在最后一个输出层有 3 个神经元。这意味着我们有 3 个类。典型分类问题中使用的激活函数是 softmax 函数(在最后一层)。对于我们的讨论,在隐藏层中使用什么激活函数并不重要。
来源—文章的作者
以上动画展示了 softmax 激活公式,即
q_i = exp(z_i) / sum(exp(z_j)where j = 1 to 3
where q_i corresponds to the value of neuron i in the last layerThus the numerator corresponds to the exponentiated value of logit provided by a neuron whereas the denominator is the sum of all the logits in the exponential space.
但是我们为什么要找 softmax?这可以通过把这个问题分成两个子问题来回答
- 为什么我们要取幂(分子部分)?
- 我们为什么要归一化(分母部分)?
这两个问题的答案是,我们需要一个概率分布作为网络的输出。
现在任何概率分布都必须考虑两个重要的性质:
- 分布中的所有条目都应该是正数
- 所有条目的总和应为 1
指数函数具有将负实数转化为正实数的魔力,因此这满足了我们的第一个要求。接下来,归一化(即每个条目除以所有条目的总和)使其成为分布。
但是现在你可能想知道为什么我们需要概率分布作为输出?
在分类问题中,你使用基本事实标签作为独热编码向量。一个独热编码向量只不过是一个概率分布,其中只有一个条目得到所有的概率。因此,任务(目标)是将该基础真实概率分布与预测的概率分布进行比较。这就是为什么我们希望我们的输出是全能的 softmax 激活函数提供给我们的概率分布。这两种概率分布之间的比较是使用交叉熵损失函数来完成的。
关键见解
Softmax 适用于交叉熵损失,但它有一个问题,即在赋予最可能的类重要性的过程中,它会将其余的类推向非常小的值。
该论文从手数字分类中选取了一个例子,其中它强调了这样一种情况,即 2 的例子图像可能比 7 的例子图像更接近或相似于 3 的例子图像。
这些例子有多接近或相似对于理解网络实际学到了什么非常重要!
这里的关键见解是,softmax 函数倾向于隐藏其他类之间的这种相对相似性,并且这种信息(如果可用的话)可以在训练提取的网络中发挥至关重要的作用。
第二个关键的见解是关于如何突出类的例子之间的相对相似性,同时保持在 softmax 的领域中。
作者认为,如果在将 logitss 的值传递给指数函数之前,我们使 logit 的值(即最后一层神经元的输出)更低,那么我们将获得更平滑的分布。
这里的“更平滑”意味着与常规的 softmax 不同,没有对应于一个条目的大尖峰。
为了使逻辑的输出更低,你现在需要一个数来除它们。这个数字有一个叫做 T 的符号,作者称之为温度。温度越高,分布越平稳。
他们修改了 softmax 函数,如下所示
q_i = exp(z_i/T) / sum(exp(z_j/T)
这里的类比是蒸馏,你用温度蒸馏杂质。然而,T 的值非常重要,你必须通过实验才能找到。这就是为什么它是一个超参数。
下面的代码片段展示了不同的 T 值对 softmax 函数输出的影响
如您所见,温度(T)越高,获得的分布越平滑。T=1 的值对应于常规的 softmax 行为。
它是如何工作的?
首先,让我在这里介绍一些新的术语。
教师模型。 原始的(繁琐的)模型被称为教师模型,因为我们正在从中提取知识。
学生模型。参数较少的新模型被称为学生模型,因为我们将信息提取到其中。
软标签。 使用温度大于 1 (T > 1)的 softmax 的教师模型的输出。
软预测。 使用温度大于 1 (T > 1)的 softmax 的学生模型的输出。
艰难的预言。 当常规 softmax 用于学生模式时
硬标签。
下图说明了学生培训流程的设置。
来源—https://nerv anasystems . github . io/distiller/knowledge _ distillation . html
你最终会有两个损失项。第一个损失项使用软标签(来自教师)&软预测(来自学生),第二个损失项使用**硬预测(来自学生)和硬标签。**你可以随时配置这两项的贡献。
作者对 MNIST 和语音识别问题进行了实验,取得了很好的结果。
Android 语音识别论文的结果
各种链接和详细信息
论文有开源实现吗?
Github 上有多种实现,实现起来非常简单。这里有一个链接,链接到一个实施多个 KD 损失的存储库
【https://github.com/karanchahal/distiller
这篇论文是在一次会议上发表的吗?
是的。这篇论文在 NIPS 2014 上被接受,有超过 3000 次引用
论文链接—https://arxiv.org/abs/1412.6550
有解释论文的视频吗?
是的。我为这篇论文制作了 youtube 视频。
我的观点和建议
- 这是启动知识蒸馏研究领域的基础论文。
- 这篇论文写得很好,如果你对 softmax 函数的数学性质有很好的直觉和理解,那么温度的概念将是有意义的。这就是为什么我在这篇文章中花了很大篇幅解释 softmax 在分类网络中的重要性
- 该技术(即更平滑的 softmax)仍在使用,并且通常根据不同问题和架构的要求由其他方法补充。
作为这篇文章的后续,我会写更多关于知识蒸馏的论文摘要。
希望你喜欢这个摘要,我可能误解/曲解了论文的某些部分,因此,如果有的话,错误是我的,而不是原论文作者的。
论文摘要与实施:通用音频表征的对比学习。
PyTorch 实施的 COLA 培训计划。
赫拉尔·莫夫西斯扬在 Unsplash 上拍摄的照片
这篇文章是一个简短的总结和实施以下文章的步骤:
本文的目的是使用区分性预训练来学习自我监督的通用音频表示。作者训练了一个 2D CNN EfficientNet-B0 来将梅尔频谱图转换成 1D-512 向量。这些表征然后被转移到其他任务,如说话人识别或鸟鸣检测。
DPT 背后的基本思想是定义一个锚元素、一个积极元素和一个或多个干扰元素。然后训练一个模型来匹配锚和正面的例子。
DPT —作者提供的图像
使用 DPT 的一种这样的方式是使用三元组损失以及余弦相似性度量来训练模型,例如余弦(F§,F(A))比余弦(F(D),F(A))高得多。这将使锚的潜在空间中的表征更接近于积极的例子,而不是干扰物。上面链接的论文的作者使用这种方法作为基线来表明他们的方法 COLA 效果更好。
可乐
这种方法适用于音频领域。对于每个音频剪辑,作者挑选一个片段作为锚,另一个片段作为正例,对于这些样本中的每一个(锚,正),他们挑选训练批次中的其他样本作为干扰物。这是一个好主意,原因有二:
- 有多个干扰物,这使得训练任务更加困难,迫使模型学习更有意义的表示来解决它。
- 干扰项从批次中的其他样本中重用,这降低了生成它们的 IO、计算和内存成本。
COLA 还使用双线性相似度,这是直接从数据中学习的。作者表明双线性相似性比余弦好得多,相比之下,在下游任务上给出了额外的 7% 平均准确度。
在计算锚和其他例子之间的相似性之后,相似性值被用于交叉熵损失,该损失测量模型在干扰物中识别正面例子的能力(论文中的等式 2)。
可乐评价
线性模型评估
COLA 用于在 AudioSet 上训练 EfficientNet-B0,AudioSet 是一个数据集,包含来自 YouTube 的大约 100 万个音频剪辑。由该模型生成的特征向量然后被用于在广泛的下游任务上训练线性分类器。模型学习的表示越好,当用作执行监督任务的线性模型的输入时,其性能就越好。作者发现,在下游任务中,COLA 比其他方法(如三重损失)多了 20%的平均准确率(论文的表 2)
微调评估
测试这种方法的另一种方法是在下游任务中微调模型。这使得作者可以将使用 COLA 预先训练的模型与从头开始训练的模型进行比较。他们的结果显示,预训练的模型比从零开始训练的模型平均高出大约 1.2% 。
PyTorch 实现
这种方法很容易在 PyTorch Lightning 中实现。
编码器可以定义为:
**class** Encoder(torch.nn.Module):
**def** __init__(self, drop_connect_rate=0.1):
super(Encoder, self).__init__()
self.cnn1 = torch.nn.Conv2d(1, 3, kernel_size=3)
self.efficientnet = EfficientNet.from_name(
**"efficientnet-b0"**, include_top=**False**, drop_connect_rate=drop_connect_rate
)
**def** forward(self, x):
x = x.unsqueeze(1)
x = self.cnn1(x)
x = self.efficientnet(x)
y = x.squeeze(3).squeeze(2)
**return** y
然后可乐训练可以定义为:
**class** Cola(pl.LightningModule):
**def** __init__(self, p=0.1):
super().__init__()
self.save_hyperparameters()
self.p = p
self.do = torch.nn.Dropout(p=self.p)
self.encoder = Encoder(drop_connect_rate=p)
self.g = torch.nn.Linear(1280, 512)
self.layer_norm = torch.nn.LayerNorm(normalized_shape=512)
self.linear = torch.nn.Linear(512, 512, bias=**False**)
**def** forward(self, x):
x1, x2 = x
x1 = self.do(self.encoder(x1))
x1 = self.do(self.g(x1))
x1 = self.do(torch.tanh(self.layer_norm(x1)))
x2 = self.do(self.encoder(x2))
x2 = self.do(self.g(x2))
x2 = self.do(torch.tanh(self.layer_norm(x2)))
x1 = self.linear(x1)
**return** x1, x2
**def** training_step(self, x, batch_idx):
x1, x2 = self(x)
y = torch.arange(x1.size(0), device=x1.device)
y_hat = torch.mm(x1, x2.t())
loss = F.cross_entropy(y_hat, y)
_, predicted = torch.max(y_hat, 1)
acc = (predicted == y).double().mean()
self.log(**"train_loss"**, loss)
self.log(**"train_acc"**, acc)
**return** loss
我没有计算资源来复制论文中的实验,所以我试着在小得多的规模上做类似的事情。我在 FMA 大号(没有标签)上使用可乐预训练了一个模型几个时代,然后微调了应用于 FMA 小号的音乐流派检测。
FMA·斯莫尔的结果如下:
- 随机猜测:12.5%
- 从零开始培训:51.1%
- 使用可乐预先训练: 54.3%
结论
论文通用音频表示的学习介绍了 COLA 预训练方法,该方法实现了一些伟大的想法,使自我监督训练更加有效,如使用批量样本作为干扰物和双线性相似性度量。这种方法可用于提高下游监督音频任务的性能。
代码:https://github.com/CVxTz/COLA_pytorch
纸周刊:没有数据的图像重建
每周二,我都会强调我在研究或工作中遇到的一篇有趣的论文。希望我的评论能帮助你在 2 分钟内获得论文中最多汁的部分!
基本思想
图像重建是一项具有挑战性的学习任务,因为没有人知道原始图像是什么样子的。因此,似乎唯一实用且合乎逻辑的方法是开发关于图像的一些先验知识,并挑选具有最大概率(最大先验估计)的重建。例如,我们希望在 MNIST 数据集上训练的模型能够开发一些关于手写数字的先验知识,可以用来对模糊的数字进行降噪。
我偶然看到了这篇名为 Deep Image Prior 的论文,该论文由 Ulynov、Veldadi 和 Lempitsky 于 2017 年发表。下面是链接:https://sites . skol tech . ru/app/data/uploads/sites/25/2018/04/deep _ image _ prior . pdf
研究人员发现了深度 CNN 的一个有趣特性——随机初始化的网络比纯噪声更快地拟合自然图像。换句话说,CNN 对自然图像有天然的“优先”偏好,可以利用这种偏好在没有任何数据的情况下去除图像中的伪影!
来自的论文
为了对图像去噪(去除水印、去除修补等),随机初始化 CNN 并将图像馈送到模型中(输入=图像,输出=图像,就像自动编码器一样)。不出所料,模型逐渐实现零训练损失(参数> >图像中的像素)。然而,当适当地提前停止训练时,网络产生去噪图像。
培训目标,来自论文
结果
图像重建可以采取多种形式——去噪、修复恢复、超分辨率等。研究人员证明,神经网络架构在免训练图像重建中起着重要作用
摘自论文
当选择合适的网络架构时,DIP 会产生惊人的重建性能,与监督模型相当,甚至超过监督模型:
来自的论文
来自的论文
一些想法
DIP 是过度参数化(权重> >数据)的力量被完全低估的另一个证明。在我的 ML 课上,我的教授警告我们不要在“小”数据集上使用巨型模型,因为可能会过度拟合。然而,超参数化网络有许多有趣的特性可以利用!如果我们可以用一个未训练的模型对图像去噪,谁知道一个有无数参数的训练有素的模型能做什么?
带代码的论文+ arXiv =可重复、有组织的研究
通过联合合作,《论文与代码》现在为 arXiv 数据库中的文章提供分类和代码参考
在有代码的纸上轻松浏览最先进的机器学习代码
摘要
数百万篇科学文章通过 arXiv 公开分享,这是一个康乃尔支持的网站,专注于开放研究。代码为的【T4 论文】网站上有学术论文,这些论文也共享它们的支持软件,因此实验可以被忠实地复制。通过联合合作,《论文与代码》现在为 arXiv 数据库中的文章提供分类和代码参考。
arXiv 发布研究成果
我们都喜欢 arXiv。尽管这里或那里有些怪癖,前提是梦幻般的。该网站提供了物理学、数学、计算机科学、定量生物学、定量金融学、统计学、电子工程和系统科学以及经济学的开放式档案。截至 2020 年 10 月,该网站已发布超过 170 万篇文章。任何人都可以在任何时候免费获得这些文章。这使得知识能够以前几代人闻所未闻的速度被共享,同时仍然保持作者归属以用于学分分配目的。
以前,新的研究进展主要通过学术期刊传播,这些期刊是由人准备的,很可能不是免费的。虽然这个过程实现了信息的组织和共享,但是它是有偏见的,并且是排他的。由于准备期刊的工作人员的集体偏见,这个过程是有偏见的,接受一些,拒绝另一些。当然,这可能比它不工作更经常;然而,我相信它远非完美。此外,该过程具有排他性,因为它为获取信息贴上了价格标签。是的,世界在交换有价值的物品。这并不意味着价值必须是货币,也不意味着货币必须直接来自最终消费者。
arXiv 的发展有助于世界上更自由的信息流动。有了这个网站,突破性的研究现在可以以互联网电缆传输的速度向全世界传播。此外,由于网站的开放性,任何人都可以使用 arXiv API 以编程方式阅读文章。一旦你有了一个 API。有一个数据集。一旦你有了一个数据集,你就有可能得到漂亮的可视化效果,比如这个。
Paperscape 的 arXiv 文章交互可视化的高级视图。
论文与代码分发研究成果的复制
arXiv 单独支持开放研究共享这一事实对全球研究社区来说是件好事。然而,现在科学领域的绝大多数新研究都有软件支持研究结果。未能提供该软件相当于未能提供验证研究所需的所有资产。在一个以信息为中心的世界里,大多数可用的数据都是在过去几年里创建的,使用软件独立证明研究结果有效的能力变得越来越重要。你可以读一篇论文并立即知道它的有效性的日子已经一去不复返了。随机优化算法和不同的数据源需要提供软件和数据,以便充分验证现代研究。
一些排名最高的知识库都是关于论文和代码的。
PwC(papers with Code)是一个组织访问技术论文的网站,该网站还提供用于创建论文结果的软件,在过去几年中发展迅速。随着可公开获得的数据集的增加,现代研究已经开始向完全透明和可信的方向靠拢。普华永道也一直在改进自己的网站。通过按照任务(例如,对象检测、情感分析等)或者按照方法(例如,注意力、卷积)进行浏览,可以容易地浏览现有技术。此外,如下所示,普华永道还使用从论文中收集的数据来跟踪软件框架和代码可用性如何随时间变化的趋势。
这项服务使普通用户能够享受前所未有的机器学习艺术。想看看与新冠肺炎有关的一些最好的模型吗?文字嵌入、图像生成或语音识别怎么样?一切触手可及。
基于时间的框架使用和论文代码可用性趋势,由代码为的论文管理。
普华永道 arXiv 使现代科学民主化
最大的成果是通过协作产生的,这两个网站也不例外。普华永道与 arXiv 合作,直接连接到他们的网站,为读者提供所有共享软件实现的链接。不再需要点击进入论文并搜索脚注和参考文献,希望神奇的“GitHub”关键字以其所有的威严出现。虽然看起来是一个微小的变化,但这种整合越来越鼓励作者在提交材料时提供这些额外的实验细节,以便读者可以更好地验证这些发现。我想任何被训练来预测论文价值的模型都会对 has_code 特性赋予很高的权重!
带代码论文链接的 EfficientNet arXiv 页面预览。
PwC 和 arXiv 之间的集成还提供了基于论文标题和摘要的论文自动分类。现在,作者将可以选择采用自动建议的论文类别,以便更好地组织内容和增加读者范围。延续开放存取的精神,他们已经公开了模型库就在这里。多棒啊。这个模型的前一个版本不可用,这导致了对其内部工作方式的许多困惑。这个问题现在已经解决了。
结论
由于现代技术研究的统计性质,研究结果现在需要软件和数据实验细节以及白皮书文档进行验证。信息生产的惊人速度也需要开放存取和自动化组织。arXiv 和有 Code 网站的报纸已经在这些领域取得了很大进展,从混乱中带来了秩序。在最近的一次合作中,我们现在可以享受这些服务之间的集成,以及它们的数据组合所带来的好处。整体绝对大于部分之和。
链接
不断学习
[## Hello Danfo:用于 Javascript 的熊猫,来自 Tensorflow
Tensorflow.js 刚刚获得了更多端到端
towardsdatascience.com](/hello-danfo-pandas-for-javascript-from-tensorflow-3d1d0ea3f3be) [## 亚马逊想让你免费成为一名人工智能从业者
这家科技巨头计划通过公开其长期的内部材料来提高 ML 的熟练度
towardsdatascience.com](/amazon-wants-to-make-you-an-ml-practitioner-for-free-552c46cea9ba) [## Deepnote 将成为 Jupyter 黑仔
旧金山的一个小团队 DeepNote 希望在你的数据科学工作流程中取代 Jupyter。
towardsdatascience.com](/deepnote-sets-course-to-become-the-jupyter-killer-d0cb6e3ca011)
保持最新状态
这一个就到此为止。然而,在学术界和工业界,事情发生得很快!用 LifeWithData 博客、Medium上的文章和我的 Twitter 让自己保持最新状态。
原载于 2020 年 10 月 13 日 https://www.lifewithdata.org**的 。
并行计算—利用 GPU 计算升级您的数据科学
GPU 如何加速数据科学工作流的研究
GPU 比 CPU 快吗?这是一个非常复杂的问题,但简单的回答是不,不总是这样。事实上,对于大多数通用计算,CPU 的性能要比 GPU 好得多。这是因为 CPU 设计的处理器内核数量比 GPU 上的处理器内核数量少,但时钟速度比 GPU 上的处理器内核速度高,这使得它们能够非常快速地完成一系列任务。另一方面,GPU 有更多的内核,并被设计用于不同的目的。最初,GPU 的设计初衷是加速图形渲染的性能。这是通过允许 CPU 卸载繁重的计算并释放处理能力来实现的。GPU 比 CPU 更快地渲染图像,因为它的并行处理架构,这允许它同时跨数据流执行多个计算。CPU 是操作的大脑,负责向系统的其他部分发出指令,包括 GPU。如今,在额外软件的帮助下,GPU 的能力已经扩展到显著减少完成数据科学不同阶段所需的某些类型的计算所需的时间。需要强调的是,GPU 不会取代 CPU,而是作为一个协处理器来加速科学和工程计算。
CPU 和 GPU 处理的比较:
GPU 与 CPU
如果你想看有趣的插图,这里有一个来自 2009 年的 视频
面向数据科学的 GPU 并行处理
在过去几年 CPU 性能增长放缓的时候,GPU 提供了显著的速度提升(可悲的是打破了摩尔定律**)。因此,预计未来几年对 GPU 计算的采用将会增加。这个特性在数据科学领域非常重要,它涉及到高效处理非常大的数据集(以矩阵/向量的形式)。 SIMD 设计,或单指令/多数据,意味着 GPU 计算可以用一条指令处理多个数据,就像矩阵乘法一样。例如,深度学习可以利用并行计算来减少训练周期中花费的时间,因为许多卷积运算都是重复的。事实上,数据科学工作流程中的许多步骤都可以通过 GPU 来处理(例如,数据预处理、特征工程和可视化),但它们确实需要额外的功能来实现。由于传统的计算机编程不是为并行处理而写的,它们需要被转换成支持 GPU 的**。GPU 制造商非常热衷于为开发者提供软件支持以加速采用。****
量化收益
为了说明 GPU 计算对深度学习的影响,我在有和没有 GPU 的情况下重新训练了 CIFAR-10 对象识别模型(来自 TensorFlow)。CIFAR-10 CNN 模型通常用于基准测试。它通过 14 层 CNN 模型用 60,000 张 32x32 的彩色图像进行训练,以给出计算强度的一些观点。因为我主要关注的是计算速度,所以我的比较指标是每秒个示例和个总训练时间**。结果显示,使用 GPU 进行训练时,性能提高了 27 倍。**
使用英伟达 RTX 2080 Ti GPU (CUDA 10.0)训练的 CIFAR-10
GPU 实现了非常高的吞吐量,大大减少了训练时间。这直接转化为在时间和精力方面更低的成本。节省的时间意味着在模型开发周期中有更多的优化迭代(即模型选择和特征调整**)。**
但是还有更多——云上的 GPU
GPU 云计算已经可以在微软 Azure、AWS、Google Cloud on-demand 等主流云平台上使用。尽管它并不总是和最先进的 GPU 工作站一样快。它提供了可访问性和便利性,无需投资昂贵的硬件和维护。
摘要
GPU 在数据科学领域发挥着至关重要的作用。由于其并行架构的性质,他们可以同时对数据流快速执行计算,解决了人工智能和机器学习最棘手的挑战之一。现在是时候熟悉 GPU 计算了——通过云或者在你的本地机器上。
平行坐标图
为什么&如何,用类比讲故事
图片来自 Unsplash 的 Mitchell Luo
又名:平行坐标、平行坐标图、平行图、剖面图
为什么:平行坐标图(PCP)是一种可视化技术,用于分析多元数值数据。它允许数据分析师一起比较许多量化变量,寻找它们之间的模式和关系。当这些变量具有不同的量值(不同的标度)和不同的测量单位时,它们适用于同时比较多个数值变量。其思想是在多维数据集中发现模式、相似性、聚类以及积极、消极或无特定关系。
第一个 PCP 出版于 1885 年(#1),但它的流行是在一个世纪后通过 Inselberg (#2)的工作。它们被广泛使用,特别是在学术论文中,以克服与高维数据可视化相关的挑战。PCP 的 n 维能力使复杂的关系能够简单地绘制出来(#3)。
如何:这种表示法并没有显示经典的笛卡尔坐标平面,而是给每个数值变量赋予了自己的轴。如下图所示,所有轴平行,垂直,等距放置。数据集的每个数据元素通过连接的线段表示,从一组连接的点中导出,每个轴上一个点。我们最终得到一组线条,每一行都是每个数据记录的多轴表示。一般来说,许多平行线表示正相关,而许多交叉线(X 形状)表示负相关。
图 1:平行坐标图的示意图。该图形是用 Matplotlib 开发的。
我们的示例数据集有四个数值变量和与三个不同类相关的六条记录。所以我们得到四个平行的轴和六条线(图 1)。每条线都是从四个相连的点(每个轴上一个点)派生出来的。每一类都用一种特定的颜色来代表。我们正在寻找四个数字变量和不同类别之间的模式或关系。显然,我们有一个严重的问题:变量 4 与变量 1 和变量 3 之间的不同数量级不允许我们发现这些可能的模式。
为了解决这个问题,在绘制数据之前,我们必须执行数据标准化任务。请记住,原始数据不仅可能有不同的数量级,而且可能有不同的测量单位。标准化(缩放)将原始数据转换为新的尺度,使我们能够比较最初非常不同的量值。
有几种缩放技术。通常的做法,虽然不是最稳健的,是在其最小值和最大值之间缩放每个轴。在此过程中,最小值设置为 0,最大值设置为 1(或 100%),中间的所有其他值也相应地进行转换。例如,使用此程序进行缩放后,您可以用一个 PCP 比较两个数值变量,其中一个变量最初在 0-10 范围内,另一个在 100 和 10000 之间。其他缩放技术使用平均值和标准偏差、中值或其他统计数据将原始数值转换为通用的比例。
图 2:缩放和重新排序的平行坐标图。该图形是用 Matplotlib 开发的。
图 2 使用与图 1 相同的数据,但是每个轴在 0 和 1 之间缩放。我们进行了一些重新排序(如下所述),直到我们注意到标签 1 和标签 2 共享某种积极的关系,而标签 3 显示了相反的模式。
除了缩放之外,为了发现变量之间的模式或关系,通常在 PCP 中使用三种其他技术:着色;重新排序;刷牙。
着色意味着用特定的颜色突出显示一条或一组线条,以便在视觉上将它们与其他线条区分开来。例如,当您必须将一种特定产品与列表中的其他产品进行比较时。
重新排序是改变垂直轴的顺序。其背后的原因是相邻变量之间的关系比不相邻变量之间的关系更容易可视化。然后,你可能不会发现某种模式或关系,仅仅因为变量彼此不相邻。此外,您可以通过改变一些轴的顺序并最小化它们之间的交叉数量来减少 PCP 的混乱。现代可视化工具允许我们沿着图拖动轴,以便于重新排序。一个聪明的分析师总是尝试重新排序,直到他增强了情节的可读性,并从显示中获得尽可能多的信息。
刷是一种通过应用刷子选择单个数据点的技术,用于高亮显示数据子集。选定的线条会被强调,而其他线条会被淡化。画笔就像一个过滤器,可以减少线条的数量,最大限度地减少混乱,并显示数据集中的模式。在 PCP 上刷是强制性的,以避免在分析大型数据集时过度绘制和遮挡。
下图清楚地显示了刷涂技术的效用:上图(a)显示了 PCP 过度绘制的数据;下图(b)显示了应用了画笔的同一个 PCP。原始数据集对应于由专门从事呼叫中心数据收集和分析的初创公司记录的电信行业(#3)。该数据集包含 500 万条与呼叫中心的客户交互(电话)相关的记录。有九个数值变量,每个变量在 PCP 中都有相应的轴。这种梳理让该公司能够识别那些无法长时间通话来解决任何问题的呼叫者,并为电信专家提供以前未观察到的模式。很明显,在最初过度绘制的五氯苯酚中无法观察到该模式。
图 3: a)刷牙前的五氯苯酚;b)刷牙后的 PCP,来源(#3)。
警告
对于非技术人员来说,平行坐标图通常难以理解。无法避免典型的线路混乱;
尽量不要同时显示多个数值变量。屏幕中超过十二个轴及其相应的线条(即使有刷)可能会使观众感到困惑;
这种显示的一个缺点是缩放后,我们会丢失每个变量的原始值。
PCP 不适用于分类数据。请记住,分类变量,也称为定性变量,通常采用有限数量的互斥类别或组的值。这些值可以是数字,但不代表数量,而是相互排斥的组(即婚姻状况:1-从未结婚;2-已婚;3-离异;4-丧偶)。您必须使用带有分类变量的平行集图;
雷达图(蜘蛛图、网络图)是显示三个或更多数量变量的多元 数据的另一种方法。雷达图的问题是它们基于非公共轴和圆周网格线,这使得比较任务非常困难。
用线条剧情很容易误导 PCP,但是他们讲的故事不一样。刷和重排序背后的思想是 PCP 特有的。此外,时间序列分析不适用于五氯苯酚,因为不可能重新排序。
总之:相邻轴之间的关系很容易看出来,但不相邻的轴之间就看不出来了。因此,揭示数据模式通常需要重新排序轴,并仔细选择彩色地图或画笔过滤器。当两个平行轴之间的线相互平行(X 形)时,两个变量之间存在正(负)关系。
平行坐标讲故事:化学性质影响葡萄酒质量
在之前发表在《走向数据科学》的一篇文章中(“气泡图,为什么&如何,用气泡讲故事”,https://Towards Data Science . com/Bubble-Charts-Why-How-f 96 D2 c 86d 167),我们分析了一场 Kaggle 比赛的数据,该比赛与葡萄酒质量探索和葡萄牙“Vinho Verde”葡萄酒的红色和白色变种分析有关。
数据可在 https://archive.ics.uci.edu/ml/datasets/Wine+Quality获得,包括 4898 项记录,包括白葡萄酒和红葡萄酒的十种化学特性和一种物理特性。该项目的目的是评估以下哪些化学或物理特性影响葡萄酒的质量:固定酸度、挥发性酸度、柠檬酸、残糖、氯化物、游离二氧化硫、总二氧化硫、密度、pH 值、硫酸盐和酒精等级。这组葡萄酒由三位专家评估,他们为每种葡萄酒提供了 0(差)到 10(优)之间的质量分数。表 1 显示了数据集的前五条记录。
表 1:葡萄酒数据集的前五条记录
在文章的最后,我们声称酒精等级和残糖水平是区分低质量葡萄酒和高质量葡萄酒的两个最重要的参数。现在还有待观察,是否有任何其他化学参数在决定最终产品的质量中起主要作用。
为了回答这个问题,我们决定使用五氯酚,因为剩余的八种化学性质被记录为定量变量,我们希望同时分析它们。
我们开发了以下程序:
**首先安装 Anaconda,这是一个用于 Python 计算和数据分析等任务的跨平台 Python 发行版。然后导入以下库:Pandas、Numpy、Matplotlib、Scypy 和 Scikit-learn。使用 read_csv() 函数读取并解析文件。检查缺失的记录并寻找数据集的形状:有 4898 条记录和 13 列。十列对应化学性质(固定酸度、挥发性酸度、柠檬酸、残糖、氯化物、游离二氧化硫、总二氧化硫、pH、硫酸盐和酒精等级)。一栏对应物理属性(密度),另一栏与质量分数相关,最后一栏表示红葡萄酒或白葡萄酒;
**接下来消除异常值(异常值),因为它们可能会在标准化过程中产生重大影响。使用 scipy.stats.zscore() ,用不同的阈值进行实验;
**将数据集分成高质量葡萄酒(质量分数为 7、8 和 9)和低质量葡萄酒(质量分数为 3、4 和 5);
**利用 scikit-learn 方法 MinMaxScaler 进行缩放阶段。记住最小最大缩放器从原始值重新缩放数据,因此所有新值都在范围 0–1(# 4)内;
**使用 parallel_coordinates 绘图功能,Pandas 内置绘图功能,使用 Matplotlib 创建平行坐标图。必需的参数是帧、class_column 和颜色图。框架包括以下九列(质量分数+八个化学性质):质量、固定酸度、挥发性酸度、柠檬酸、氯化物、游离二氧化硫、总二氧化硫、pH 和硫酸盐。Quality 是 class_column(包含类名的列名)。选择两个不同的颜色图,一个用于高品质葡萄酒,另一个用于低品质葡萄酒地块;
**尝试重新排序。
我们得到了高品质葡萄酒的图 4 和低品质葡萄酒的图 5。高质量的葡萄酒显示出较低的氯化物和游离二氧化硫含量,较高的固定酸度和总二氧化硫含量。低质量的葡萄酒显示出较低的游离二氧化硫含量,较高的氯化物和挥发性酸度。
图 4:高品质葡萄酒的五氯苯酚。该图形是用 Matplotlib 开发的。
图 5:低品质葡萄酒的五氯苯酚。该图形是用 Matplotlib 开发的。
葡萄酒行业利用二氧化硫的抗氧化和抗菌特性,防止颜色变化,尤其是在白葡萄酒中。二氧化硫总量是衡量游离态和结合态二氧化硫的指标。二氧化硫还能改善口感,保留葡萄酒的果味和新鲜香气(#5)。由于消费者中不良反应的逐渐记录,二氧化硫的使用已成为一个有争议的问题,消费者可能对其存在轻度过敏(因此,在标签上注明其用途已成为一些葡萄酒法规的一部分)。
另一方面,中到高浓度的氯化物可能会给葡萄酒带来咸味,这可能会赶走潜在的消费者。挥发性酸度是由葡萄酒中发现的酸类化合物制成,呈现出一种香气,而不是在口感上发现的。过量的挥发性酸性物质会导致不良的感官效果(#5)。
几个化学特性决定了葡萄酒的感官特性。我们发现,二氧化硫、氯化物和挥发性酸度在决定最终产品质量方面起着重要作用。由于这些属性或多或少可以量化,五氯苯酚可以帮助生产商为更多的消费者开发高质量的葡萄酒。
如果你对这篇文章感兴趣,请阅读我以前的:
簇状和重叠条形图,为什么和如何
https://towards data science . com/clustered-overlapped-bar-charts-94 f1 db 93778 e
堆积条形图,为什么和如何,讲故事和警告
https://towards data science . com/stacked-bar-graphs-why-how-f1 b 68 a 7454 b 7
参考
1:M·德奥卡尼,“平行坐标和轴坐标:考虑平行坐标的几何变换方法和新的图形计算方法”。高迪尔-维拉尔斯,1885 年。
#2: A. Inselberg,“N 维坐标”,图片数据描述与管理,第 136 页,1980 年。
#3: R. Roberts 等,“平行坐标的智能刷图”,Latex 类文件学报,第 14 卷,第 8 期,2015 年 8 月。
#4: J. Brownlee,“如何在 Python 中使用 StandardScaler 和 MinMaxScaler 变换”,https://machinelementmastery . com/standard scaler-and-minmax scaler-Transforms-in-Python/
并行神经网络与迁移学习
近距离观察迁移学习和一个有趣的用例
介绍
你好。这是我第一篇关于媒介的文章。我已经计划写这篇文章有一段时间了。我的主要动机是帮助简化,甚至可能提供一个模板,用于下一步构建复杂的神经网络,除了您可能已经构建的特定架构中的神经元之外,还涉及并行神经元。这是因为尽管在理论上看起来很简单,但构建、保存、加载和精炼(以后再训练)神经网络确实会在我们实际开始编码它们时形成绊脚石。希望这篇文章能帮助你更好地理解与这些相关的问题,并帮助你解决在开始深度学习时不时遇到的小问题。就这样,让我们开始我们的旅程。
对于那些刚开始学习或正在练习神经网络的人来说,你可能已经观察到,我们使用的大多数神经网络架构通常是前馈的,即张量从一层按顺序流向下一层。假设我们想要探索,在我们构建并训练我们的架构达到一定程度的满意后,如果我们在一个层中增加神经元的数量,会发生什么。这通常意味着从头再来,可能会浪费几个小时的训练时间。仅仅为了看到在特定层中添加几个神经元的效果,从头开始似乎是不合理的。这就是迁移学习的用武之地。简单地说,它的意思是,你想把你在学习一个特定任务时学到的东西转移到另一个任务中去。在神经网络的范式中,我们学习的东西是用训练后得到的权重值来表示的。
当我们开始更多地了解如何利用迁移学习时,大多数内置函数都有固定的神经架构,并包含用于重新加载权重并在新的上下文中更新它们的代码。要想弄清楚将它应用到您的定制模型的细节,很可能需要几个小时或几天的时间,仔细琢磨网上的多个例子。我自己已经这样做了,我相信将我所学的东西都集中在一个地方,肯定会帮助任何想要更多地实践迁移学习以及探索增强神经网络的另一种替代方法的人。就这样,让我们开始我们的例子。
我假设您熟悉 PyTorch 中关于构建神经网络的教程,可从以下链接获得:https://py torch . org/tutorials/beginner/blitz/cifar 10 _ tutorial . html。要了解现有流行架构的迁移学习基础,请参考链接:https://py torch . org/tutorials/初学者/transfer _ learning _ tutorial . html。我强烈建议您访问这些链接和其他可用的链接进行实践,并形成一个训练神经网络架构的全面的想法。
我们的场景
假设我们现在想要创建 CIFAR 10 教程中描述的网络的副本,并将其与我们的训练版本并行放置,为图像分类的相同问题创建一个更大的网络。我们当前的模型在概念上是这样的:
教程中简单的前馈神经网络。来源:这是我自己用 MS Paint 画的概念图。
该图的张量板描述揭示了以下内容:
我电脑上模型的张量板表示。
我们现在的目标是构建一个如下所示的神经网络架构:
并行前馈神经网络——本质上是并排放置的模型的核心。来源:这是我自己用 MS Paint 画的概念图。
我们还希望这个新结构的上部子部分包含与通过执行教程获得的权重相同的权重。
把它编码
让我们看看帮助我们实现这种结构的代码:
import torch
import torchvision
import torchvision.transforms as transformstransform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=0)testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)testloader = torch.utils.data.DataLoader(testset, batch_size=4,
shuffle=False, num_workers=0)classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')import matplotlib.pyplot as plt
import numpy as np# functions to show an image
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()# get some random training images
dataiter = iter(trainloader)
images, labels = dataiter.next()# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))import torch.nn as nn
import torch.nn.functional as Fclass Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return xnet = Net()
PATH = './cifar_net.pth'
net.load_state_dict(torch.load(PATH))
到目前为止,我们已经重新创建了从教程中学习到的模型,并为我们训练和加载了权重,以复制到我们的新网络中。
下面的代码构建了我们需要的架构。
class SideNet(nn.Module):
def __init__(self):
super(SideNet, self).__init__()
self.pool = nn.MaxPool2d(2, 2) self.conv11 = nn.Conv2d(3, 6, 5)
self.conv12 = nn.Conv2d(6, 16, 5)
self.conv11.weight.data.copy_( net.conv1.weight.data)
self.conv12.weight.data.copy_(net.conv2.weight.data)
self.conv21 = nn.Conv2d(3, 6, 5)
self.conv22 = nn.Conv2d(6, 16, 5)
self.fc11 = nn.Linear(16 * 5 * 5, 120)
self.fc12 = nn.Linear(120, 84)
self.fc11.weight.data.copy_(net.fc1.weight.data)
self.fc12.weight.data.copy_(net.fc2.weight.data)
self.fc21 = nn.Linear(16 * 5 * 5, 120)
self.fc22 = nn.Linear(120, 84)
self.fc3 = nn.Linear(168,10)
def forward(self, x):
y = self.pool(F.relu(self.conv11(x)))
y = self.pool(F.relu(self.conv12(y)))
y = y.view(-1, 16 * 5 * 5)
y = F.relu(self.fc11(y))
y = F.relu(self.fc12(y))
x = self.pool(F.relu(self.conv21(x)))
x = self.pool(F.relu(self.conv22(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc21(x))
x = F.relu(self.fc22(x))
out = self.fc3(torch.cat((x,y),dim=1))
return out# create a new model
net1 = SideNet()
我们现在已经创建了我们的网络架构,并在类 SideNet()的 forward 函数中定义了网络中的张量流。
self.conv11.weight.data.copy_( net.conv1.weight.data)self.conv12.weight.data.copy_(net.conv2.weight.data)self.fc11.weight.data.copy_(net.fc1.weight.data)self.fc12.weight.data.copy_(net.fc2.weight.data)
这段代码片段在很多方面都很关键。你可能会立刻意识到这里发生了什么,因为这是迁移学习中基本发生的事情。我们刚刚将训练好的网络的权重复制到新结构的上部子部分。瞧啊。
我们现在可以选择保持这些权重不变,通过设置 requires_grad = False(实质上是冻结那些层的权重,这些层的权重是从我们训练的网络中复制的)或者在训练我们的新架构时更新它们。因为我只训练了几个纪元的网络,所以我将训练我们架构的两个子部分的权重。最初只训练一个时期的原因将在以后变得清楚。
#check weights
print(net.fc2.weight.data)
print(net1.fc12.weight.data)
print(net1.fc22.weight.data)#for param in net.parameters():
# param.requires_grad = False
from torch.utils.tensorboard import SummaryWriter# default `log_dir` is "runs" - we'll be more specific here
writer = SummaryWriter('runs/temp')# write model to tensorboard
writer.add_graph(net1, images)writer.close()# train the new model
import torch.optim as optimcriterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net1.parameters(), lr=0.01)for epoch in range(1): # loop over the dataset multiple timesrunning_loss = 0.0
for i, data in enumerate(trainloader, 0):
# get the inputs; data is a list of [inputs, labels]
inputs, labels = data# zero the parameter gradients
optimizer.zero_grad()# forward + backward + optimize
outputs = net1(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()# print statistics
running_loss += loss.item()
if i % 2000 == 1999: # print every 2000 mini-batches
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0print('Finished Training')correct = 0
total = 0
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net1(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))
#check weights
print(net.fc2.weight.data)
print(net1.fc12.weight.data)
print(net1.fc22.weight.data)
这里,优化器的参数设置如下:学习率= 0.01,没有使用动量。我使用相同的参数来训练初始网络。这是因为当我们并排查看新网络和初始网络的权重时,我们需要检查旧网络的权重是否被复制,新网络的权重是否已被适当初始化,以及在训练新网络的权重时,初始网络的权重是否没有改变。最后一个短语是关键,有许多方法可以实现前一句中的前两个短语,但它们通常也会导致修改初始网络的权重!例如,如果使用以下代码片段:
self.conv11.weight.data = net.conv1.weight.dataself.conv12.weight.data = net.conv2.weight.dataself.fc11.weight.data = net.fc1.weight.dataself.fc12.weight.data = net.fc2.weight.data
让我们看看 TensorBoard 推断出的结构:
我电脑上模型的张量板表示。
它确实符合我们试图建立的东西。太好了!
验证我们对实际情况的理解
作为验证步骤,让我们打印两个网络的 FC2 层(全连接 2)的权重值:
print(net.fc2.weight.data) # before train
tensor([[-2.5432e-03, -6.9285e-02, 7.7019e-02, ..., 2.8243e-02,
4.9399e-02, -8.7909e-05],
[-7.2035e-02, -1.2313e-03, -8.9993e-02, ..., 1.8121e-02,
-6.1479e-02, -3.8699e-02],
[-6.3147e-02, 5.5815e-02, -6.0806e-02, ..., 3.3566e-02,
7.6486e-02, 7.3699e-02],
...,
[ 1.9772e-03, -1.8449e-02, 6.8946e-02, ..., -2.1011e-02,
7.5202e-02, 4.1823e-02],
[ 2.9912e-02, -7.9396e-02, -8.7561e-02, ..., 4.6011e-02,
-9.0685e-02, 4.1302e-02],
[-1.8297e-02, -7.3356e-02, 4.7250e-02, ..., -7.5147e-02,
-6.4722e-02, 6.0243e-02]])print(net.fc2.weight.data) # after train
tensor([[-0.0151, -0.0470, 0.1057, ..., 0.0288, 0.0280, 0.0171],
[-0.0720, -0.0029, -0.0907, ..., 0.0181, -0.0630, -0.0408],
[-0.0417, 0.0548, -0.1226, ..., 0.0335, 0.0679, 0.0900],
...,
[ 0.0074, -0.0028, 0.0292, ..., -0.0218, 0.0754, 0.0473],
[ 0.0307, -0.0784, -0.0875, ..., 0.0460, -0.0903, 0.0510],
[-0.0252, -0.0824, 0.0380, ..., -0.0744, -0.0741, 0.1009]])In our new model code, before train,
print(net.fc2.weight.data)
print(net1.fc12.weight.data)
print(net1.fc22.weight.data) tensor([[-0.0151, -0.0470, 0.1057, ..., 0.0288, 0.0280, 0.0171],
[-0.0720, -0.0029, -0.0907, ..., 0.0181, -0.0630, -0.0408],
[-0.0417, 0.0548, -0.1226, ..., 0.0335, 0.0679, 0.0900],
...,
[ 0.0074, -0.0028, 0.0292, ..., -0.0218, 0.0754, 0.0473],
[ 0.0307, -0.0784, -0.0875, ..., 0.0460, -0.0903, 0.0510],
[-0.0252, -0.0824, 0.0380, ..., -0.0744, -0.0741, 0.1009]])
tensor([[-0.0151, -0.0470, 0.1057, ..., 0.0288, 0.0280, 0.0171],
[-0.0720, -0.0029, -0.0907, ..., 0.0181, -0.0630, -0.0408],
[-0.0417, 0.0548, -0.1226, ..., 0.0335, 0.0679, 0.0900],
...,
[ 0.0074, -0.0028, 0.0292, ..., -0.0218, 0.0754, 0.0473],
[ 0.0307, -0.0784, -0.0875, ..., 0.0460, -0.0903, 0.0510],
[-0.0252, -0.0824, 0.0380, ..., -0.0744, -0.0741, 0.1009]]) tensor([[ 0.0864, 0.0843, 0.0060, ..., 0.0325, -0.0519, -0.0048],
[ 0.0394, -0.0486, -0.0258, ..., 0.0515, 0.0077, -0.0702],
[ 0.0570, -0.0178, 0.0411, ..., -0.0026, -0.0385, 0.0893],
...,
[-0.0760, 0.0237, 0.0782, ..., 0.0338, 0.0055, -0.0830],
[-0.0755, -0.0767, 0.0308, ..., -0.0234, -0.0403, 0.0812],
[ 0.0057, -0.0511, -0.0834, ..., 0.0028, 0.0834, -0.0340]])After training,
print(net.fc2.weight.data)
print(net1.fc12.weight.data)
print(net1.fc22.weight.data)tensor([[-0.0151, -0.0470, 0.1057, ..., 0.0288, 0.0280, 0.0171],
[-0.0720, -0.0029, -0.0907, ..., 0.0181, -0.0630, -0.0408],
[-0.0417, 0.0548, -0.1226, ..., 0.0335, 0.0679, 0.0900],
...,
[ 0.0074, -0.0028, 0.0292, ..., -0.0218, 0.0754, 0.0473],
[ 0.0307, -0.0784, -0.0875, ..., 0.0460, -0.0903, 0.0510],
[-0.0252, -0.0824, 0.0380, ..., -0.0744, -0.0741, 0.1009]])tensor([[-0.0322, -0.0377, 0.0366, ..., 0.0290, 0.0322, 0.0069],
[-0.0749, -0.0033, -0.0902, ..., 0.0179, -0.0650, -0.0402],
[-0.0362, 0.0748, -0.1354, ..., 0.0352, 0.0715, 0.1009],
...,
[ 0.0244, -0.0192, -0.0326, ..., -0.0220, 0.0661, 0.0834],
[ 0.0304, -0.0785, -0.0976, ..., 0.0461, -0.0911, 0.0529],
[-0.0225, -0.0737, 0.0275, ..., -0.0747, -0.0805, 0.1130]])tensor([[ 0.0864, 0.0843, 0.0060, ..., 0.0325, -0.0519, -0.0048],
[ 0.0390, -0.0469, -0.0283, ..., 0.0506, 0.0030, -0.0723],
[ 0.0571, -0.0178, 0.0411, ..., -0.0027, -0.0389, 0.0893],
...,
[-0.0763, 0.0230, 0.0792, ..., 0.0337, 0.0065, -0.0802],
[-0.0756, -0.0769, 0.0306, ..., -0.0235, -0.0413, 0.0810],
[ 0.0048, -0.0525, -0.0822, ..., 0.0019, 0.0785, -0.0313]])
因此,我们可以观察到,在训练新模型时,我们的初始模型的参数权重被复制,但没有改变。此外,复制了权重的层在训练后改变了权重,从而验证了我们的健全性检查。
我们现在已经建立了一个更复杂的模型,并且能够并行地重用我们的权重。当然,如果想要中间并行,只需要改变类 SideNet()的 forward 函数中张量的流向即可。
另一个例子
例如,假设我们希望保留卷积层,但在此之后引入两条平行路线。我们想要:
在神经网络之间引入并行性。来源:这是我自己用 MS Paint 画的概念图。
类 SideNet()现在看起来如下:
class SideNet(nn.Module):
def __init__(self):
super(SideNet, self).__init__()
self.pool = nn.MaxPool2d(2, 2)self.conv11 = nn.Conv2d(3, 6, 5)
self.conv12 = nn.Conv2d(6, 16, 5)
self.conv11.weight.data.copy_(net.conv1.weight.data)
self.conv12.weight.data.copy_(net.conv2.weight.data)
self.fc11 = nn.Linear(16 * 5 * 5, 120)
self.fc12 = nn.Linear(120, 84)
self.fc11.weight.data.copy_(net.fc1.weight.data)
self.fc12.weight.data.copy_(net.fc2.weight.data)
self.fc21 = nn.Linear(16 * 5 * 5, 120)
self.fc22 = nn.Linear(120, 84)
self.fc3 = nn.Linear(168,10)def forward(self, x):
y = self.pool(F.relu(self.conv11(x)))
y = self.pool(F.relu(self.conv12(y)))
z = y.view(-1, 16 * 5 * 5)
y = F.relu(self.fc11(z))
y = F.relu(self.fc12(y))
x = F.relu(self.fc21(z))
x = F.relu(self.fc22(x))
out = self.fc3(torch.cat((x,y),dim=1))
return out
# create a new model
net1 = SideNet()
TensorBoard 的描绘证实了我们想要建造的东西:
在我的电脑上对我们改进的并行神经网络的张量板描绘。
我希望所有这些例子能让你对编写复杂的神经网络充满信心,并成为你机器学习之旅中的关键垫脚石。感谢阅读:)
使用消息传递接口(mpi4py)的 Python 并行编程
为超级计算机准备好您的代码。没那么难。
照片由 Carol Jeng 在 Unsplash 上拍摄
您知道您可以编写并行 Python 代码,在您的笔记本电脑和超级计算机上运行吗?你可以,而且没有你想象的那么难。如果您已经为异步并行化编写了代码,那么您甚至不需要进行太多的重构。
高性能计算(HPC)将任务分配到数千个 CPU 上(与笔记本电脑上的 4–8 个 CPU 形成对比),以实现显著的性能提升。CPU 使用消息传递接口(MPI)进行通信和传递数据。当您编写代码将任务分配给多个内核同时运行时,在您的笔记本电脑上也使用了相同的原则。本文将演示如何使用 MPI 和 Python 来编写可以在您的笔记本电脑或超级计算机上并行运行的代码。
安装 MPI
您需要为您的操作系统安装一个 MPI 应用程序。对于 Windows 用户,我建议直接从微软安装 MPI。对于 Mac 和 Linux 用户,我建议安装 OpenMPI 。Windows 用户必须将 MPI 安装目录添加到路径变量。
要测试您的安装,请在终端窗口中键入mpiexec
(Windows)或mpirun
(Mac/Linux,但请检查安装文档),然后按“Enter”键。如果您已正确安装,这将生成一条包含使用信息的消息。当你在终端时,也输入python
并按下‘回车’。这应该会启动一个交互式 Python 会话。如果没有,您需要安装或配置 Python。
安装 mpi4py
mpi4py
是一个 Python 模块,允许您与您的 MPI 应用程序进行交互(mpiexec
或mpirun
)。和任何 Python 模块(pip install mpi4py
等)一样安装。).
一旦你安装了 MPI 和mpi4py
,你就可以开始了!
一个基本例子
使用 MPI 运行 Python 脚本与您可能习惯的略有不同。使用mpiexec
和mpirun
,每一行代码将由每个处理器运行,除非另有说明。让我们制作一个“hello world”示例来演示 MPI 基础知识。
创建一个新的 python 脚本(.py
文件)。导入mpi4py
并使用MPI.COMM_WORLD
来获取关于所有可用于运行您的脚本的处理器的信息(当调用脚本时,这个数字被传递给 MPI 应用程序)。COMM_WORLD
访问可用于分配工作的进程数量(等级/处理器),以及关于每个处理器的信息。给出了为运行我们的脚本而分配的队列或处理器的总数。rank
给出当前执行代码的处理器的标识符。下面的print
语句将为作业中使用的每个处理器打印一次。
通过打开终端,导航到包含该脚本的目录,并执行以下命令来执行该脚本:
mpiexec -n 4 python mpi_hello_world.py
n -4
指定要使用的处理器数量。在这个实例中,我使用了 4 个处理器,这意味着 print 语句将执行 4 次。注意,等级不是按数字顺序打印出来的,所以您需要确保您的代码可以异步运行。换句话说,不可能知道哪个处理器将首先启动或完成,因此您的代码需要以这样一种方式进行组织,即结果不依赖于可能在不同处理器上计算的值。
Hello world from rank 2 of 4
Hello world from rank 3 of 4
Hello world from rank 0 of 4
Hello world from rank 1 of 4
现在,更新脚本,以便它为不同的等级输出不同的消息。这是使用逻辑语句(if
、elif
、else
)完成的。
我们现在得到 0 级和 1 级的不同消息。
First rank
Hello world from rank 0 of 4
Not first or second rank
Hello world from rank 2 of 4
Not first or second rank
Hello world from rank 3 of 4
Second rank
Hello world from rank 1 of 4
发送和接收数组
send
和recv
功能分别将数据从一个处理器发送到另一个处理器,并从一个处理器接收数据。许多数据类型可以通过这些函数传递。这个例子将特别关注发送和接收numpy
数组。Send
和Recv
函数(注意大写字母“S”和“R”)是专用于numpy
数组的。有关基本send
和recv
的示例,请参见[mpi4py](https://mpi4py.readthedocs.io/en/stable/tutorial.html)
文档。
在上一篇文章中,我用multiprocessing
模块演示了并行处理。我们将在这个例子中使用相同的函数。
一种在个人计算机上加速代码的灵活方法
towardsdatascience.com](/asynchronous-parallel-programming-in-python-with-multiprocessing-a3fc882b4023)
在同一个目录中创建两个新的 Python 脚本。说出一个my_function.py
和另一个mpi_my_function.py
的名字。在my_function.py
中,实现上面链接的文章中的功能。你的脚本应该是这样的。这是一个简单的函数,用暂停来模拟长时间的运行。
这些段落解释了my_function
的并行化程序。代码在下面的要点中给出(带注释)。在mpi_my_function.py
中导入my_function
、mpi4py
和numpy
。然后从MPI.COMM_WORLD
得到大小和等级。使用numpy
为my_function
创建随机参数值。所有处理器上都有params
变量。现在划分参数列表,将数组的一部分分配给每个进程(或等级)。我特意将params
(15)中的行数奇怪地被处理器(4)的数量整除,因此我们必须做一些额外的数学运算来分解params
。现在每个处理器都有一个变量来索引它在params
数组中的块的start
和stop
位置。
我们希望最终结果是一个数组,其中包含每个参数集的参数值和函数结果。创建一个空数组,local_results
,其行数与参数数组相同,并增加一列来存储结果。然后对每个参数集运行my_function
,并将结果保存在结果数组中(local_results
)。现在每个处理器都有了它的params
数组块的结果。
必须收集结果以创建最终数组,其中包含每个原始参数组合的结果。将每个等级的local_results
数组发送到等级‘0’,在那里它们被组合成一个数组。当使用Send
时,指定要发送到的秩,dest
,并指定一个tag
(唯一的整数),以便接收秩知道要检索哪个值(如果您最终执行多个Send
,这一点很重要)。
对于接收秩(0),遍历所有其他秩,创建一个大小为要接收的数组的空数组,并使用Recv
从每个秩中检索发送的值,指定要接收的秩和tag
。检索到数组后,将其添加到现有值中。打印出最终的数组,确保它看起来是正确的。我们完事了。
使用以下命令运行上面的脚本:
mpiexec -n 4 python mpi_my_function.py
结果应该类似于:
results
[[7.58886620e+00 5.62618310e+01 9.09064771e+01 3.33107541e+03]
[2.76707037e+01 4.03218572e+01 2.20310537e+01 3.08951805e+04]
[7.82729169e+01 9.40939134e+01 7.24046134e+01 5.76552834e+05]
[9.88496826e+01 6.91320832e+00 1.59490375e+01 6.75667032e+04]
[8.94286742e+01 8.88605014e+01 5.31814181e+01 7.10713954e+05]
[3.83757552e+01 4.64666288e+01 3.72791712e+01 6.84686177e+04]
[9.33796247e+01 1.71058163e+01 2.94036272e+00 1.49161456e+05]
[1.49763382e+01 6.77803268e+01 7.62249839e+01 1.52787224e+04]
[7.42368720e+01 8.45623531e+01 6.27481273e+01 4.66095445e+05]
[6.76429554e+01 5.95075836e+01 9.82287031e+00 2.72290902e+05]
[4.94157194e+00 7.38840592e+01 3.70077813e+00 1.80788546e+03]
[2.71179540e+01 2.94973140e+00 2.86632603e+01 2.19784685e+03]
[2.92793532e+01 9.90621647e+01 9.45343344e+01 8.50185987e+04]
[1.20975353e+01 8.89643839e+01 7.13313160e+01 1.30913009e+04]
[8.45193908e+01 4.89884544e+01 5.67737042e+01 3.50007141e+05]]
结论
您可能已经注意到,并行运行一个 4 行函数需要 43 行代码。似乎有点矫枉过正。我们并行化了一个简单的函数,但这实际上是一个更复杂的例子,因为需要进行操作来获得所需的输出格式。这个例子作为更复杂函数的并行化模板。一般来说,我将编写一个 Python 模块来运行我的模型/分析,然后在并行化脚本(上面的 43 行要点)中设置并调用模块中的一个函数(完成所有工作)。您可以运行非常复杂的分析,而无需向我们在此创建的脚本添加太多代码。一旦你让这个代码在你的个人机器上运行,它就可以在大多数超级计算机上运行,而不需要太多额外的工作。
并行快乐!
并行编程:Python 中的多重处理
在这个计算能力贪婪的时代,我们往往会忘记在自己的电脑上使用我们可以利用的能力
一般来说,程序员、游戏玩家、科学家、软件开发人员和大多数知道如何使用计算机的人对计算能力的渴望是巨大的。我们一直在寻找计算强度更低、效率更高的应用。这使我们能够更有效地利用我们的计算机设置。
然而,我们中的许多人并没有充分利用计算机上已经可用的计算能力。在需要的时候利用这种能力可以带来指数级的更好的性能,通常,通过一些代码的改变,你可以将进程运行速度提高 2-3 倍。你会问我们如何做到这一点?好吧,我们开始吧。
这个博客主要关注并行编程。即同时在多个处理器上运行程序。当你运行你的程序时,它通常使用你计算机中的一个内核。但是,大多数计算机都有多个内核。根据您的处理器,它可能是双核、四核、八核,或者可能包含更多内核。如果(比方说)你有一个四核处理器,只在一个内核上运行程序,你实际上放弃了其他三个内核,因此计算能力是你正在使用的三倍。理论上,使用所有这些内核可以将您的任务速度提高四倍。
然而,这并不简单,否则软件公司将一直使用所有的内核来获得更好的性能。如果你想提高程序的性能,你需要确保它可以并行化。也就是说,它可以同时在不同的内核上运行。
让我们举一个简单的例子来理解这一点。假设你要生产一个产品,你把它的生产分成四个阶段。如果只有在阶段 1 已经完成的情况下才能从阶段 1 进入阶段 2,然后只有在阶段 2 完成的情况下才能从阶段 2 进入阶段 3,以此类推。那么这个过程就是一个连续的过程,因为你必须遵循一个序列来执行你的指令。然而,如果你能把生产分成四个部分并分配给不同的工人,那么这个过程就可以并行化。例如,为一个玩具构建四个组件,这些组件一旦完成就可以连接起来。
一旦您确定您的程序可以并行化,下一步就是为它编写代码。我不会在这个博客中写代码,但是,这个博客的代码可以在这个 Github repo 中找到。我选择 python 来编写代码,我使用了多处理模块来在多个处理器上运行程序。
[## Khan saadbinhasan/Python 中的并行编程多处理
程序乘以 mXn 矩阵与单核以及多核。该计划产生两个随机…
github.com](https://github.com/khansaadbinhasan/Parallel-Programming-MultiProcessing-in-Python/blob/master/README.md)
在本期节目中,我们将看到并行编程的两个应用。首先是矩阵乘法,它可以很容易地并行化,接下来我们将看到前缀和扫描,乍一看似乎是一个顺序问题,但可以并行化,以在多个处理器上运行。
矩阵乘法
将两个矩阵相乘相当简单,是大多数编程入门课程的一部分——从第一个矩阵中选择一行,从第二个矩阵中选择一列,将相应的元素相乘并相加,得到第一个元素,然后移到下一列,以此类推,得到下一个元素。
这里,人们可能会注意到,给定两个矩阵 A(mXn)和 B(nXr)以及它们的合成和矩阵 C(mXr),为了得到元素 C(i,j ),人们只需要考虑矩阵 A 的第 I 行和矩阵 B 的第 j 列就可以得到所需的矩阵 C 的元素。因此,我们是否已经计算了元素 C(1,1)来计算元素 C(1,2)并不重要。
因此,可以将程序分成不同的进程,并在不同的处理器上运行。这可以导致程序运行速度的显著提高,因为矩阵乘法在许多应用中是如此重要的操作,例如机器学习、科学模拟、游戏等。回报是巨大的。
现在,尽管这听起来很简单,但在代码中实现时,我们仍然需要小心一些事情。首先要做的事情之一是决定哪些计算将在哪个内核上运行。您可以决定按行或按列划分矩阵。我们应该按顺序做。也就是说,首先,我们取第一个矩阵,取其一定数量的行,并将其与第二个矩阵中的所有列相乘,同时对不同内核上的不同行执行此操作。
例如,假设我们有 A(4X3)和 B(3X5)两个矩阵,我们将 A 的第一行的计算负载分布在一个处理器上,第二行分布在第二个处理器上,依此类推。但是,如果我们的行数超过了处理器的数量,那该怎么办呢?例如,A(7×4)和 B(4×5),这里,我们为我们做一个除矩阵的函数。也就是说,它决定了矩阵如何被划分以在不同的处理器上运行。
实现该函数的一种方法是从 0 到 r 开始第一次除法,其中 r 可以通过整数除法(总行数/处理器数)来计算,然后是从 r 到 r +整数除法(总行数/处理器数),其中现在的总行数是之前的总行数减去已经要执行的行数,我们少了一个处理器,以此类推。
让我们举个例子来理解这一点
然后,第一次除法将从 0 到(7/4)即 0 到 1现在 R = R-1 = 6,N = N-1 = 3因此,第二次除法将从 1 到 1+(6/3)即 1 到 3现在 R = R-2 = 4,N = N-1 = 2因此,第三次除法将从 3 到 3+ (4/2) i 3 到 5现在 R = R — 2 = 2,N = N — 1 = 1因此,第三次除法将是从 5 到 5+(2/1)即 5 到 7因此,除法将是从:(0 到 1)、(1 到 3)、(3 到 5)和(5 到 7)
注:此处第一项是非包容性的,矩阵从索引 1 开始。因此,第一行在第一处理器上执行,而不是在第二处理器上执行。
现在,我们可以将矩阵分成所需的行,并在不同的内核上执行。我在随机生成的 100X100 矩阵上运行上述程序,发现加速几乎是 2.75 倍。那么,加速到底是什么?
这是一种方法来衡量你的算法有多快,相比之下,一个类似的算法顺序运行。我们怎么做呢?我们首先计算执行顺序程序的时间和执行并行程序的时间。然后我们将第一次得到的时间除以第二次得到的时间,这就是我们的加速比,也就是说,我们的程序比顺序程序快多少。
从理论上讲,给定四个内核应该会将算法的性能提高四倍,但是,当我们试图并行运行这些进程时,会产生额外的开销。例如,我们必须生成多个进程并进行额外的处理,因为在这种情况下,我们将矩阵行划分为在不同的处理器上运行。此外,由于我们将写入一个公共内存阵列,因此在写入结果时可能会有冲突,这也可能会导致速度变慢。另一个原因可能是这些过程需要不同的时间来执行。例如,在上面的例子中,不是所有的行都有相同的大小,因此需要不同的时间来执行。
我建议你去我的 GitHub repo 下载文件夹,按照说明运行程序。当你使用它的时候,在 ubuntu 中打开名为“系统监视器”的软件,或者在 windows 中打开名为“任务管理器”的软件。转到显示不同处理器使用情况的选项卡。当顺序程序运行时,它应该是这样的。
在顺序算法中仅使用一个内核
这表明当顺序乘法运行时,只有一个内核在使用。而运行并行计算会导致使用所有四个内核。你可以在下面的截图中看到。
并行算法中使用的所有内核
现在,我们已经执行了矩阵相乘的程序,并确保它可以在多个处理器上执行。我们现在将关注一个更反直觉的问题,即前缀求和。
前缀总和扫描
扫描可能是并行编程中最重要的主题之一。理解什么是扫描很简单,但是,很难找到一种方法来并行化它,因为它看起来本质上是顺序的。
给定 n 个元素的列表,第 I 个元素的扫描被定义为在它之前的所有 i-1 个元素的总和,根据扫描的类型包括或不包括第 I 个元素。在这篇博客中,我们将看到一个包含第 I 个元素的全面扫描。例如,列表[1,2,3,4,5,6,7,8]的扫描将是[1,3,6,10,15,21,28,36]。这个操作可能看起来非常简单,但它是相当常见的,它不限于加法,你可以应用大量的运算,因此你正在做的是找到一种方法来应用一些运算到一个数字列表,但不是顺序地做,而是并行地做。
计算扫描顺序是足够容易的,我们必须维护一个变量运行总和,并添加每个元素一个接一个,并取代它。然而,当试图并行化这个问题时,没有想到立即的解决方案。我们要看一个算法,对思考这个问题有帮助。这种算法以最早提出它的人的名字命名为希利斯-斯蒂尔斯算法。
从这个算法开始,首先,把第 I 个位置的每个元素加到第 i + 1 个位置的元素上,更新第(i+1)个元素的结果。如果没有元素替换该元素,则按原样复制它。在下一阶段,将第 I 个元素添加到第(i+2)个位置的元素,并更新第(i+2)个位置的元素。如果元素没有更新,请再次复制它。对第 I 个和第(i + 4)个元素再做一次,依此类推。
这里,我们在每个阶段取第 2^r 元素,其中 r 是从 r = 0 开始的阶段号。停止条件将是当我们不能再把元素加在一起,因为 2^r 大于或等于元素的数目。因此,总共有 log(N)个阶段,其中 N 是元素的总数。r 从 0 到 log(N)-1 不等,包括 0 和 log(N)-1。
这个算法为什么会起作用?好吧,让我们来看看下面这个来自 Udacity 并行编程课程的精彩视频的例子。让我们以第 5 个(1 索引)元素为例,即 5。在包含和扫描中,其最终值将为 15(即 1 + 2 + 3 + 4 + 5)。
第一步,我们用 4 加 5,得到 5+4 = 9。在第二步中,由于我们已经将前面的元素添加到我们的元素中,现在我们需要在它之前添加元素。即 3、2、1 等。但是在上一步中,我们已经将 3 加到了 2,即 3+2 = 5。这个元素现在位于原始元素之前的两个位置。因此,如果我们把它加到 9,我们得到 9 + 5 = 14 或 5 + 4 + 3 + 2 = 14。现在,我们已经添加了 5,4,3,2。因此,我们需要更进一步,也就是说,在 1 之前增加元素 4 步,因此我们得到总和 15。
但是如果我们在 1 之前有更多的元素,在这种情况下,1 所在位置的元素将包含它之前 3 个元素的总和。因此,很容易看出这将如何导致先前没有添加的那些元素的添加,而在每个阶段,任何元素都包含在其之前的 2^r 元素的总和(包括其自身)。
希利斯-斯蒂尔斯扫描
这个算法对并行化有什么帮助?在每个阶段,我们只关心将一个元素添加到当前元素,而不关心它之前的所有元素,因为在每个阶段,我们添加到当前元素的元素包含关于它之前的一些元素的信息,而在下一个阶段,我们添加的元素将包含关于更多元素的信息,最终我们将获得关于当前元素之前的所有元素的信息。因此,在每个阶段,我们只能处理一些元素,而忽略其他元素。这可以在不同的内核上完成。
为了并行化该算法,我们将遵循与矩阵乘法类似的方法,首先,我们创建一个函数来划分列表,我们需要将它分布在不同的内核上。我们可以使用与矩阵乘法几乎相同的算法。然后,我们可以将第 I 个元素添加到(i — 2^j)th 元素,其中 j 是我们从 0 迭代到 lg(N)的迭代变量。然而,这个算法将只对偶数个元素起作用,因为您可以通过取奇数个元素来验证。处理这个问题的方法是在元素个数为奇数的末尾再填充一个元素,一旦计算完成,就删除最后一个元素。
要查看实际的实现细节,请访问包含代码的 Github repo ,并按照 README.md 文件运行程序
为了更直观地理解这个算法,请看下面的视频,这个算法可以在这里找到。
【https://khansaadbinhasan.blogspot.com】最初发表于。
R 中的并行 API 连接
节省大量时间的方法。
创建新数据集是数据科学家和分析师目前面临的挑战之一。创建数据集的一种流行方式是网上冲浪,并根据需要从不同的网站收集信息。但是您可能知道,这非常耗时,因此在不使用多个虚拟机或集群的情况下加速这一过程是您的数据科学家工具集的一部分。在这里,我们将深入探讨如何使用 r 进行并行 API 连接。
这是一个关于如何使用并行计算的简单指南。这个想法是,你的计算机的每个线程/工作者/核心使用不同的连接来访问网络,因此,你不需要等待 API 响应或动态网站的加载来开始抓取另一个。只需将简单的命令添加到代码中,并以巧妙的方式包装它,就可以加快速度,节省大量时间。
并行 API 连接
我们将从并行使用 R 和 API 连接开始。这将允许我们更有效地下载信息,就像在你的网络浏览器中打开多个标签一样。对于我们的例子,我们将使用 OMDB API 。这个 API 让您可以访问许多关于电影的信息,对于我们的测试,我们使用了 Poster API 来下载电影海报并将其保存在本地,以便我们可以在以后处理它们。
1.-加载库
第一步是加载所有需要的库。对于并行设置我们的代码,我们将使用, *future。*这个库设置了我们将要使用的并行方法。对于并行功能,我们选择, furr 包。这个包是 purrr 包的未来版本。
library(RCurl)
library(curl)
library(future)
library(furrr)
2.-创建一个函数
其次,我们需要创建一个新功能。这个函数将是我们进行 API 连接和下载的地方。在这里,我们可以将 API 连接代码更改为 web 抓取代码。
为了演示如何做,该函数将打开一个与 Poster API 的 curl 连接,并将一个图像下载到一个名为 images 的新目录中。
imgAPI <- '[http://img.omdbapi.com/?apikey=*[your-own-api-key]*'](http://img.omdbapi.com/?apikey=9c9e16a&i=tt')
rootDir <- 'images/'
get_img <- function(id){
url <- paste0(imgAPI,str_pad(id, 7, pad = "0"))
if(url.exists(url)){
curl_download(url = url ,destfile = paste0(rootDir,'tt',id,'.png'))
}
}
使用并行连接和传统的并行处理之间的一个区别是,您几乎可以肯定您的代码能够并行运行,因为通常连接本质上是独立的。
3.-在所有连接上运行 future_map
最后,我们需要在我们的链接列表上运行我们之前创建的函数。为了实现这一点,我们使用了 future_map ,一个并行版本的 map 函数。这就是并行连接发生的地方。
#load file with list of movie ids to create API links
links <- read_csv('links.csv')future_map(links,get_img)
重要:为了让我们的代码知道它需要并行运行,我们需要告诉它如何去做,以及它应该使用多少个不同的内核。未来库中有两个函数允许我们这样做:【可用资源() & 【计划()。第一个读取关于我们的计算机有多少内核的信息,第二个告诉代码有多少内核以及如何并行化未来的功能。
n_cores <- availableCores() - 1
plan(multiprocess, workers = cores)
为不属于您代码的其他计算机进程留出一个空闲的内核总是一个好的做法。
4.-测试和结果
使用之前定义的代码,我们将运行一个小测试,看看这些并行连接如何提高我们的下载速度。对于该测试,我们使用 4 种不同的配置:
- 完全没有平行,使用正常的映射功能。
- 多重处理计划和设置 3 个核心。
- 多重处理计划和设置 5 个核心。
- 多重处理计划和设置 7 个核心。
内核数量是根据 8 个内核的机器选择的,至少有 1 个内核空闲。
为了进行测试,我们在 3 个不同的场景中运行每个配置,以检查卷如何影响性能。这些场景基于我们将要下载的电影海报数量(500,1000,2500)。在每个场景中,我们运行 5 次,取下载所有电影的平均时间。
结果如下图所示。
作者图片
结论
该测试向我们展示了使用并行计算从 web 访问信息可以极大地丰富我们的工具包。作为数据科学家,我们有很多关于访问 API 以访问信息的项目,这是一种加速这一过程的方法。即使这个过程不是那么庞大,改进也是巨大的。
您可以在这个 GitHub 链接中访问该实验的代码。
R #1 中的并行化警告:性能问题
R 中的多处理的介绍,以及一些很难注意到但可能严重影响性能的缺陷。
关于这部作品。我以一篇独立的文章开始这篇文章,总结我在研究项目中遇到的并行处理问题。在看到这篇文章变得相当长之后,我决定将它分成多个部分,每个部分涵盖一种特定的陷阱,在最好的情况下,这些陷阱会导致错误,或者在最坏的情况下,会导致明显的糟糕性能或看不见的后果。因此,您将[有希望]看到多篇文章,这些文章涵盖了这些陷阱以及我已经成功应用于解决我的问题的解决方案。本文讨论基本的设置和可能的性能问题。
我们大多数人在工作中都遇到过代码速度不够快的情况,因为它只使用了一个 CPU 内核,需要几个小时才能完成。为了解决这个问题,我们求助于传统的多重处理技术,希望它能弥补我们计算的缓慢。然后,在快得多的 30 分钟的运行结束时,我们看到一个错误,它根本没有告诉我们哪里出错了,可能类似于Error reading from connection 7
(如果您来自搜索引擎,这不是涵盖这个特定错误消息的文章)。在本系列中,我将总结我在 R 中的并行性经验,并尝试指出在并行计算中可能会出错的地方。我不会深入讨论并行处理是如何工作的——尽管我将尝试至少定义我所使用的概念——本系列旨在收集在决定切换到并行模式时需要考虑和注意的事情。
在第一部分中,我将谈论为什么我们会转向并行工作流(只是为了给我们一点动力),然后我将展示一些在 R 中实现它的技术,最后我们将开始组合它们以在我们的计算机上释放完全的混乱,我们将看到为什么这个或那个不应该一起使用,或者为什么以某种方式做某事会减慢您的计算。好吧,也许我有点太戏剧化了,但是我最近设法冻结了一个 32 核服务器,给了它太多的事情去做——当然是不情愿的。
最终,我决定写这些文章,因为我自己在编写代码时遇到了这些问题,而且没有现成的食谱(至少我还没有找到一本)可以指出我犯的错误——有些可能是语言或特定于包的错误,但大多数错误很难找出——并给我一些如何解决它们的建议。虽然本系列的大部分内容包含特定于 R 的建议,但我将要讨论的一些问题是语言不可知的,这意味着如果您使用 MATLAB 或 Python 或 Assembly (hah)来做类似的工作,您可能会面临与我相同的问题。还要注意,我不是并行化大师(既不是也不是的 R 大师),所以如果您发现什么不正确的地方或者需要更好的解释,请不要犹豫,告诉我!
四个任务的顺序执行和并行执行的简化(理想)图(图片由作者提供)
简介:“为什么还要并行?”
你是否尝试过执行一个包含一些复杂数学运算的长循环?就像一系列的矩阵求逆,其结果被相乘,多个函数被调用,甚至可能解决一些二次优化问题,等等?如果你在代码运行的时候打开了任务管理器(或者在你喜欢的操作系统上打开了它),你可能会发现 R 进程并没有完全使用你的全部处理能力,而只是使用了一小部分。CPU 使用百分比可以从大约 50%(在 2 核 CPU 上)到 4–5%(在 24 个[逻辑]核上)不等。你可能会问一个关键的问题:
如果 R 使用了我 100%的 CPU,我的计算不会运行得更快吗?
答案很不幸只是:大概是。看,在我们开始让我们的代码使用更多的 CPU 之前,我们必须了解一些关于我们如何计算的事情,更重要的是,我们的计算结果是否相互依赖。一般来说,如果你有两个相互依赖的任务,那些不能并行执行(如果没有第一个的结果,第二个会怎么做?).否则,如果你的任务不相互依赖,它们可能并行运行。你也可以把你的任务想象成一个for
循环的主体:如果你需要来自前一次迭代的数据,那就不能并行运行。
为了创建一个更实际的例子,我们将计算矩阵行的总和。是的,有一个叫做rowSums
的函数可以更快地完成这项工作(你应该使用它来代替下面的实现),但是这个例子应该足够简单来帮助我们理解基础知识。有一种方法可以做到这一点:
这段代码将计算矩阵中每一行的总和,并将其存储在results
向量中(请尽量避免像这样分配向量,因为它们将不得不动态增长,这是很昂贵的,一个额外的提示是使用results = numeric(nrow(bigMatrixWithLotsOfData))
来代替——假设您知道结果将是数字)。因此,我们将依次计算每一行的总和,一个接一个。但是事情是,对于任何一个 **i**
我们不需要任何其他迭代的结果!因此,如果我们的计算不相互依赖,我们可以理论上并行运行它们,将一些工作交给我们的其他 CPU 内核!
顺便说一下,我会经常提到
for
循环,因为 R 中一个流行的并行解决方案也使用它们——不过,我们不要太超前了。现在,关于什么有助于无错并行(线程安全等等),将有更多的讨论,但这不是一个一般的并行计算讲座,而是关于 当进行并行处理时,什么可能出错 。
好的,那么并行计算行和需要什么呢?有几种方法可以实现它,但是所有的解决方案都涉及到立刻开始多重、 独立 计算(就像为每个i
创建一个 R 实例)当它们都完成时,就把它们的结果粘在一起。现在,我将向您展示如何做到这一点。
R 中的并行处理:使用“foreach”的多处理
在 R 中,进行并行计算的一种典型方式是借助于foreach
包(这里有更多关于如何使用它的信息)。它提供了一种便捷的方式将您的任务( jobs )分配给多个进程,而不需要考虑如何以及何时将某个任务安排给某个进程。当这些过程完成时,这个包还会帮助您将结果连接成一个列表或向量(或者实际上是任何东西)。就其本身而言,这可能还不够,因为您需要提供所谓的并行后端(处理进程的创建和销毁,以及它们之间的通信)。包doParallel
提供了一个这样的后端。无需深入了解它们是如何工作的,这里有一段代码为我们做了并行行求和:
我插入了一些更长的注释来说明这些指令的用途。该代码片段将让您快速了解如何使用该包来运行并行计算。通常,您会希望以item = theListToProcess
的形式向foreach
结构提供一个列表,然后您可以在您的子流程中用item
变量引用列表的当前元素——您可以为它选择(几乎)任何名称。
不幸的是,我们现实生活中的计算很少这么容易实现。也许我们正在运行复杂的模拟(计算多个矩阵的协方差,解决线性规划问题等等),我们的结果只有在执行了一些函数后才会出现。也许您已经创建了顺序运行的代码,现在您想让它并行运行。也许你让你的代码并行运行,但实际上比顺序运行要慢(是的,这可能发生,我们会看到)。很多事情都可能出错,但是如果你注意其中的几个,你就可以编写出引起麻烦的几率更低的代码。
首先,我可以并行化什么?
如果您已经有了一些代码,并且正在考虑通过并行执行来使它更快,那么停下来想想它计算最终结果的步骤是一个好主意。创建一个简单的流程图也很有帮助——看看下面的速写:
处理每日股票市场价格的程序的简化流程图(图片由作者提供)。
该图显示了从数据集的某些部分组装投资组合的过程(例如,基于 2020 年 6 月 1 日和 2020 年 6 月 5 日之间的数据创建投资组合,然后基于 2020 年 6 月 8 日和 2020 年 8 月 12 日之间的数据创建投资组合,以此类推),并汇总这些操作的结果。在绿色区域,我们可以看到组装投资组合的过程不而相互依赖,只依赖于预先获得的数据,因此我们可以重新安排它们并行运行。一般来说,for
循环是开始观察的好地方:如果您没有将数据从一个迭代传递到另一个迭代(并且您也没有在循环之外修改数据*,您应该可以让那个循环并行运行(尽管尝试并行化一切很容易,请参见性能部分)。*
如果您已经嵌套了 for 循环,并希望将其中一个循环转换为并行任务,请确保您执行的是仍然可以独立运行的最外层循环(如果它不止循环几次)。
这可能是一项令人生畏的任务,而且真的没有通用的方法来做到这一点:你必须做出决定,将一些事情变成平行的,或者保持现状。或者,您可能需要重构代码,使其更易于并行化。现在——在这个冗长的介绍之后——让我们直入主题:
什么可能出错(第 1 部分)?
无意中问了太多你的 CPU 也就是性能问题
在本文的剩余部分,我们将看看一些看似微妙的问题,这些问题不一定会使 R 会话崩溃或抛出错误,而是会悄悄地削弱您的计算性能,甚至可能使您的计算机进入无响应状态。首先,我将讨论并行化的开销,这种开销随着并行执行的性能增长而慢慢减少。然后我们将了解为什么混合多种并行化技术是一个坏主意,最后我将向您展示两个边缘案例,如果与其他一些问题结合起来,可能会导致更多问题。
并行化不是免费的:开销
很容易将并行化视为解决所有性能问题的金锤。事实上,
为什么我不应该并行运行所有的事情呢?
问得好,但原因如下。很容易从上面的例子中得出结论,说“嗯,我有一个长度大于 100 的向量,让我们把它扔到 32 个进程上来计算一些东西”。问题是创建进程不自由,它们之间的通信也不自由。它们中的每一个都需要启动(至少在不分叉的时候),并且它们有自己的内存空间(Windows 上的一个基本 R 实例可以占用 30-50 MiB RAM,乘以 12 个进程就已经是 480-600 MiB 了——我们甚至还没有在它们上面加载包和数据!).如果您需要访问所有进程上的整个大型数据框架,那么需要将数据加载到所有进程上——不过,只需将真正需要的变量移动到您的进程上。如果您使用具有大量依赖项的包,它们将使用大约 12 倍多的 RAM——在我们的 12 进程示例中。您很快就会明白为什么我们需要将数据加载到我们所有的流程中。
还记得文章开头的这张图吗?这离现实又近了一步(图片由作者提供)。
因为我在 32 个内核的 Linux 服务器上使用了一个“SOCK”集群(即我没有分叉我的进程),我必须等到所有 32 个进程都正确初始化,这导致了大约 45 到 90 秒的等待时间:每个进程都必须加载多个包并调用一些函数来准备处理。在这种情况下,最好重新考虑启动时间(和增加的内存使用)是否真的值得多处理带来的性能提升。对我来说,这绝对是值得的,因为我解决了成千上万的二次规划问题:这里开销和并行执行时间的总和仍然比顺序运行时低得多。然而,对于小任务,选择更少的进程或者根本不并行会更有效率。
在下一节中,我们将看到另一种提高速度的方法,而不必求助于多重处理,以及它是如何引起更多麻烦的。
多线程×多处理=不好
如果你想让数学运算更快,从 base R 转换到微软 R Open (MRO) 可能是个不错的选择。它是 R 的一个略微修改的版本,依赖于一个不同的库(英特尔数学内核库(MKL) )来进行数学计算。了解微软 R Open 很重要的一点是:( 1)它与 base R 兼容,更重要的是,( 2)由于采用了英特尔 MKL,它通过以多线程的方式执行矩阵乘法和类似的矢量化任务,实现了更快的速度——可以在这里看到微软进行的性能比较。坚持住。什么,现在?
从概念上讲,所做的事情与我们在上面的行求和示例中所做的非常相似。多线程工作流,然而,创建单独的线程,而不是单独的进程(如同多重处理)。如果你不介意的话,我不会深入研究具体的技术差异,但我们也不要在这里留下一个黑洞。在多线程环境中,内存通常在计算(线程)之间共享,而在多处理环境中,每个进程都有自己的内存。如果您还记得上一节,这就是为什么我们必须初始化集群中的每个 R 进程:因为它们是独立的进程(实例),有自己的内存,所以每个进程都作为一个空的 R 实例启动,没有加载额外的包。一个进程可以有多个线程,所以这里也有一个层次结构,但就像我说的,这不是一个并行计算或操作系统大师级。
在高层次上,创建线程通常比创建进程便宜(CPU 和内存方面也是如此),但是对于线程,您必须非常小心内存,因为您可能会覆盖另一个线程正在处理的内容。但是在进程的情况下,由于独立的内存空间,这不是问题,但是您必须处理进程之间的通信(这是由并行后端和foreach
完成的)。每一种都有它们的优点和缺点,但是知道你可能会在不知情的情况下使用它们和可能会导致速度大幅下降就足够了。我们能把这叫做过度平行化吗?
我们来数一数。您正在使用 MRO (Microsoft R Open ),因为它在顺序工作流中更快,但是您注意到您的代码的一部分可以并行执行,所以您将并行foreach
放入其中。您有一个 4 核 CPU (4 个逻辑核,没有超线程),因此您会产生 4 R 个进程。但是,您没有意识到的是,您正在创建 4 个 MRO 进程,这些进程可能没有被告知并行情况,因此它们每个都决定使用 4 个线程来进行计算。你开始进行大量矩阵求逆或乘法的并行计算,却突然意识到。只是。慢。这怎么可能?
事实是,你实际上使用了 4*4 = 16 个线程!CPU 能够同时运行的线程数量的四倍。结果,你的 CPU 在这些线程之间来回切换,大大降低了矩阵运算和并行执行的速度。它可能会减慢到顺序执行会更快完成的程度。
为了避免这个问题,您应该使用 MRO 的命令(setMKLthreads
)将每个子进程上使用的线程数限制为 1。我将给你一个使用doParallel
包实现这一点的完整例子。确保在实际的foreach
计算之前运行下面的代码片段**!**
很好。试着再次运行你的计算,你可能会注意到它们变得更快了,就像我一样。哦,顺便说一下,clusterEvalQ
只是在你所有的子进程上执行相同的代码(文档在这里,我稍后会谈到)。它可用于在您的流程上进行一些设置工作,如加载包和调整设置。
错过这一点的有趣之处在于你可能甚至不会注意到背景中发生的事情。您不会得到任何错误,但在某些时候,您的计算可能会大大减慢。只要您的进程没有同时碰到一些繁重的多线程部分,您可能会没事,但是一旦它们碰到了,您会发现您的进度条(如果您正在使用的话)停止了,只是盯着监视器,心想“嗯,它似乎不能再快了”。我的模拟似乎也停止了,我想这是因为我使用了 500×500 的矩阵——它们在 50×50 的矩阵下运行良好!一旦我告诉 MRO 停止在它的每个进程上使用多线程,我的并行执行甚至可以在大(500×500)矩阵上运行!我在 32 核服务器上运行这些,所以我实际上产生了 3232 个线程,太棒了。*
即使你不使用 MRO,你仍然可能遇到这个问题。看,例如data.table
包也是多线程的,但是当文档说它可以检测它是否运行在一个分叉进程上并切换回单线程操作时,我有点担心明确提到的分叉。分叉是创建集群时的另一种方法,它克隆进程(在 Linux 上传递type = "FORK"
到makeCluster
),但它在 Windows 上不起作用,所以我使用“SOCK”集群——我在 Windows 上开发我的代码,并在 Linux 服务器上运行它,这是最好的办法。为了扩展前面的代码,我们可以改为执行以下代码:
这应该足以让你开始多线程和多处理。请记住,您需要确保您在子进程上使用的每个包都以单线程模式运行。软件包通常会告诉你它们是否像data.table
一样使用多线程。
顺便说一下,如果您想知道 MRO 在单线程上的性能,当切换到单线程操作时,它不会变得无用。它仍然使用英特尔的 MKL,即使在一个线程上,它也比 base R 的 BLAS/LAPACK 库快得多(参见我之前提到的基准测试,这里)。
**TLDR。**你真的不想同时使用这两种类型的并行性,小心多处理环境中的多线程部分!
“嘿,我们还在运行”——不是停止集群,或者只是认为已经停止了集群
关于性能还有一件事要提。看,我遇到过这样的问题,我几乎冻结了一台 32 核服务器。原来错误不仅仅是我生成了太多的线程,而是当我强行告诉主进程停止时,子进程没有退出(使用每个人都喜欢的组合键,CTRL + C
)。由于我在 Windows 机器上工作,我天真地认为事情在 Linux 机器上应该以大致相同的方式运行我的子进程:用CTRL + C
从控制台关闭主进程也应该停止子进程——只是看起来不是在 Linux 机器上。
这些子进程处于过度并行化的情况中:每个线程都在争夺 CPU (1024 个线程争夺 32 个内核!),所以他们的预计完成时间基本上是从来没有。现在,由于我没有意识到这一点,我实际上重新启动了几次计算(在不断更新代码之后),所以运行的子进程和线程比我想象的要多——这就是为什么您至少应该运行top
,如果不是htop
来从命令行检查您的服务器的性能。
为了确保你的过程确实停止,在你开始计算之前,你可以做一些事情。r 提供了一个名为on.exit
(此处为文档)的函数,这是一个方便的小工具,当函数退出时,它会执行代码,即使是在发出CTRL + C
的时候。我们可以使用它来尝试关闭我们的集群,而不管我们是否已经成功执行完当前函数:
但那是两个停止命令,不是吗?是的,但是我真的想确保我的集群被正确关闭——它最终关闭了。我还将代码包装在一个try
块中,以防其中一个出错(例如,因为集群已经停止)。这两个命令是否都是必要的,我无法验证,但是尽管发出多个 stop 命令看起来很难看,但我从未遇到过这方面的问题。就像我说的,在 Windows 机器上你可能也不会有(如果你的主进程退出,子进程也会被关闭),但是一旦你完成了对集群的处理并处理了异常关闭,显式地停止集群总是一个好主意。
“多几百兆字节的内存怎么样?”—观看集群呼叫
虽然使用clusterCall
代替我们之前使用的clusterEvalQ
可能同样容易——以至于它们看起来是可交换的——但是这个函数有一个有趣的副作用,你可能没有意识到。为了进行模拟,我生成了一个巨大的表达式列表(占用了几百 MiB 的内存),然后将它馈送给并行的foreach
循环——集群的进程然后评估这些表达式,给我最终的结果。表达式列表是在我设置集群之前生成的,所以我在确定任务中没有错误之前不会启动集群。这个生成过程完成时没有出现错误(从 base R 进程的内存使用量的大幅增加可以看出)。然后为了设置我的集群,我从使用名为.packages
的foreach
参数切换到使用clusterCall()
来加载我需要的包,并为 MKL 和data.table
设置子进程的线程数。
这一切都很好,直到我突然注意到,每个子进程的内存使用量开始从 40–50 MiB 上升到大约 6–900 MiB(准确地说,每个子进程的大小大致相同),并且这种情况一个接一个地发生:进程产生,进程#1 在几秒钟内从 40 MiB 上升到大约 670 MiB,然后进程#2 也这样做,以此类推。我花了相当多的调试时间来弄清楚到底发生了什么——我甚至没有在子进程上执行任何东西*,只有一个空的clusterCall
,内存仍然增加到这个荒谬的高数量。如果您想知道,这是导致问题的一行代码:*
clusterCall(cl = parallelCluster, function () { }) # do nothing
很奇怪吧。这怎么可能呢?此外,更好的是,当我从控制台执行同样的代码行时,这个内存使用问题没有发生:内存使用保持在 40 MiB 左右。当我从控制台而不是在函数中发出命令时,我可以在集群上加载包,而不会增加几乎 1gb 的内存使用。
鉴于本节的上下文,您可能会怀疑这个问题,但是我从未看到任何迹象表明clusterCall
首先将当前函数的所有局部变量复制到子进程上。这可能是一个方便的特性,可以在其上计算函数,而不必先显式地复制变量(在clusterExport
的帮助下),但这仍然很令人头疼。
所以,确保不要把太多(大)变量放入你从调用 clusterCall
的同一个函数中!相反,您可以尝试创建一个单独的函数,调用clusterCall
来设置您的集群,或者确保您在函数开始时调用这个函数,但是在发出makeCluster
之后(我相信所有的局部变量都会复制过来,所以如果您将一个巨大的数据帧作为参数传递给函数,它也可能会被复制到子进程)。有一个更简单的方法,尽管是,因为你可能不想用clusterCall
来做设置工作(比如加载包)。
我推荐的另一个选择是用clusterEvalQ
代替,特别是在初始化的时候。后者不接受函数作为第二个参数,而是接受一个表达式作为第二个参数(语法上类似),并且不会导致将局部变量复制到集群中。如果您愿意,您仍然可以使用clusterExport
手动(显式)将变量移动到您的子流程中。所以用这个代替:****
尽管如此,还是有它的用处!如果您想使用当前函数中的多个局部变量,并且不想在一个clusterExport
调用中显式地列出它们,这将非常方便。
有趣的事实。根据文档(以及源代码)clusterEvalQ
在后台调用clusterCall
。这乍一看似乎很奇怪,因为clusterEvalQ
不会导致复制变量,但是我有一个答案,如果你还在阅读的话!由于clusterCall
被包装在clusterEvalQ
中,只有clusterEvalQ
的局部变量会被复制(除了表达式本身之外,没有局部变量),所以没有额外的变量被复制到集群中。下面是clusterEvalQ
的源代码(代码取自包的源代码,我只是重新格式化了它——点击侧边栏上的ClusterApply.R
查看相关文件):
clusterEvalQ <- function(cl = NULL, expr) {
clusterCall(cl, eval, substitute(expr), env=.GlobalEnv)
}
至此,我们已经结束了这个话题。虽然本文没有触及所有的并行化主题(比如多线程的 OpenMP 和Rcpp
),但我希望它提供的信息足以帮助您避免一些狡猾的陷阱。我也希望 20 分钟的阅读时间没有把你吓跑——我很容易过度解释自己。下一次,我将尝试涵盖一些确实会带来错误消息的问题(这样它们更容易被检测到),但是这些消息可能不是很有帮助,并且可能很难调试和找到它们的根本原因——就像偶尔(即。随机得到一个Error reading from connection
错误。
如果你有任何建议或更正,请在评论中添加!
以下是我在本文中涉及或触及的主题(供参考)
- 如何用
foreach
在 R 中进行多重处理,为什么你会想使用它 - 对于较小的任务,多处理的成本(称为开销)可能会超过并行处理的优势
- 将多处理与多线程工作结合起来会意外地导致速度大幅下降
- Microsoft R Open 是使用多线程加速计算的一个很好的选择,尤其是在不使用多重处理的时候。它可能是 base R 版本的一个很好的替代品
- 按下
CTRL + C
不一定会终止集群的进程,只会终止您的主进程:确保您也能处理意外退出 clusterCall
可以用你不想加载的数据填充你的子进程的内存,而你可能没有意识到这一点。如果您不需要您的变量在您的流程中神奇地变得可用,请使用clusterEvalQ
。
使用 Kompute 和 Vulkan 通过多队列操作并行处理 GPU 密集型工作负载
通过使用 Kompute 和 Vulkan SDK 利用多队列操作并行性,在 GPU 密集型工作负载上实现 2 倍以上的性能提升
博文视频版(Kompute 部分 13:33 开始)
GPU 已经证明对于高度并行的数据处理用例非常有用。例如,在机器学习&深度学习中发现的计算范式非常适合图形卡提供的处理架构。
然而,当涉及到多个 GPU 工作负载时,人们会认为这些工作负载会被并发处理,但事实并非如此。虽然单个 GPU 计算工作负载在众多 GPU 核心上实现了并行化,但多个工作负载是按顺序逐一运行的。那当然是直到最近显卡架构的改进,现在支持跨多个工作负载的硬件并行化。这可以通过将工作负载提交给支持并发的不同底层物理 GPU“队列系列”来实现。受益于此的机器学习实用技术包括模型并行和数据并行。
在本例中,我们将展示如何通过简单地跨两个队列系列提交多个工作负载,使这些工作负载并行运行,从而在同步示例上实现 2 倍的性能提升。
这是一项重要的优化技术,因为最近在本文档第 19 页的 NVIDIA Ampere GA10x 架构规范中概述的公告将实现3 倍的性能提升(即跨一个图形队列和两个计算队列的并发性)**,**表明这一趋势只会继续带来该领域的进一步优化改进。
我们将使用 Vulkan 和 Kompute 框架来实现这一点。更具体地说,我们将涵盖:
- GPU 处理中“异步”和“并行”的歧义消解
- 我们将在此基础上构建一个基本的同步示例
- 扩展异步工作负载提交示例的步骤
- 扩展并行多队列 GPU 处理示例的步骤
你可以在这个文件 中找到 的完整代码——关于如何使用 CMAKE 运行完整套件的说明可以在主 Kompute 库构建部分中找到。
关于 Vulkan 和 Kompute
Khronos 成员(图片由 Vincent Hindriksen 通过 StreamHPC 提供)
Vulkan SDK是由 Khronos Group 领导的一个开源项目,能够实现高度优化的跨厂商/跨平台 GPU 处理。
Kompute 是一个构建在 Vulkan SDK 之上的框架,它抽象了所需的数千行样板代码,介绍了展示 Vulkan 核心计算能力的最佳实践。Kompute 是 GPGPU 计算框架,我们将在本教程中使用它来构建核心异步和并行代码实现。
来自 Kompute Repo 的“电脑”(图片由作者提供)
异步与并行处理
在深入研究代码之前,有必要澄清两个概念,即异步工作负载提交和并行工作负载处理**。**
简化的 Vulkan 建筑(图片由作者提供)
使用 Vulkan SDK 到 GPU 队列提交并行工作负载进行处理的方式。这可以在简化的 Vulkan 架构图中看到(为简单起见,省略了管道和描述符组件)。
异步工作负载提交
异步处理包括 CPU 主机端能够在 GPU 处理工作负载的同时执行其他工作的能力。“其他工作”可以包括调用其他 C++函数,甚至向相同或其他 GPU 队列提交进一步的工作负载。当 CPU 想要检查 GPU 工作负载是否完成时,它可以使用一个 Vulkan“栅栏”,这基本上是一个信号量资源,允许 CPU 在 GPU 工作负载完成时得到通知。
值得注意的是,当多个工作负载被提交到同一个队列时,即使这些是从多个 C++线程中完成的,预期的执行顺序仍然是连续的——至少在今天的 GPU 架构中是这样。
并行工作量处理
并行工作负载处理由 GPU 同时执行两个或更多工作负载组成。更具体地说,如果您有两个 GPU 任务,每个任务需要 10 秒钟来处理,理论上两个任务的并行执行仍然需要 10 秒钟,因为它们将同时执行。
为了实现并行工作负载处理,底层 GPU 必须首先支持这一点。这一点之所以重要,是因为即使您要跨不同的 GPU 队列提交工作负载,处理仍可能由底层硬件基于其限制按顺序完成。
基本顺序处理示例
我们现在来看一下将在本文中使用的代码。代码的第一个版本将是顺序流——我们将能够将其转换成异步代码,并最终转换成并行代码。我们将基本上运行一个工作负载,在该工作负载中,我们将执行以下操作:
- 创建一个 Kompute 管理器来协调所有的 GPU 工作
- 在将用于处理数据的 CPU 主机中创建 Kompute 张量
- 将 Kompute 张量映射到 GPU 设备内存中
- 定义计算着色器,让 GPU 忙碌几个 100 毫秒
- 使用张量在 GPU 中运行计算着色器进行数据处理
- 将 Kompute 张量的结果映射回 CPU 主机内存
- 验证操作是否成功
为了测量时间,我们将使用标准库中的<chrono>
。我们将主要使用它来计算使用std::chrono::high_resolution_clock::now()
检索的开始和结束时间之间的差异,如下所示:
您可以在这个文件中找到的可运行代码,它是 Kompute 测试套件的一部分。
1.创建一个 Kompute 管理器来协调所有的 GPU 工作
首先我们必须创建 Kompute 管理器,它执行所有需要的内存管理并创建所有需要的 Vulkan 资源。默认情况下,Kompute 管理器将选择 GPU 设备 0,但是您可以传递您想要初始化的特定设备索引,如果您已经有 Vulkan 应用程序,您可以传递您的 Vulkan 资源。
2.在 CPU 主机中创建将用于处理数据的 Kompute 张量
我们现在将能够创建一组 Kompute 张量。我们首先在 CPU 主机中初始化数据,该数据由长度为 10 的零数组组成。我们将使用两个张量,因为我们将运行两个算法执行。我们将能够在最后检查这些 Kompute 张量,以确认执行已经成功。
3.将 Kompute 张量映射到 GPU 设备内存中
斯坦福 CS149 课程 2019 幻灯片
我们现在能够将 Kompute 张量的主机数据复制到 GPU 设备内存中。
这是一个重要的步骤,因为默认情况下,Kompute 张量使用仅设备可见的内存,这意味着 GPU 操作需要用 staging tensor 复制它。
Kompute 允许我们创建缓冲区和 GPU 内存块,以及通过kp::OpTensorCreate
操作使用分段缓冲区执行复制。
4.定义计算着色器,让 GPU 忙碌几个 100 毫秒
我们创建的计算着色器有一个相对较大的循环来模拟“昂贵的计算”。它基本上为100000000
次迭代执行一个单位加法,并将结果加到输入张量上。
5.使用张量在 GPU 中运行计算着色器进行数据处理
现在我们能够通过kp::OpAlgoBase
操作提交计算着色器来执行。这基本上允许我们用各自的张量执行着色器的提交。这个初始实现同步运行执行,所以它将首先用tensorA
运行着色器的执行,然后用tensorB
运行同一个着色器的执行。
6.将 Kompute 张量的结果映射回 CPU 主机内存
最后,我们希望将 GPU 设备内存中的结果检索到 CPU 主机内存中,这样我们就可以从 C++中访问它。为此,我们可以使用kp::OpTensorSync
操作。
7.验证操作是否成功
最后,我们可以检查两个结果kp::Tensor
都包含了100000000
的预期值。
扩展异步工作负载提交
在这种情况下,我们需要为异步提交扩展的步骤非常少。我们唯一需要做的就是用evalOpDefault
函数代替evalOpAsyncDefault
函数,然后使用evalOpAwaitDefault(<timeInNanoSecs>)
等待任务完成。这基本上如下所示:
正如您所看到的,我们能够异步提交两个任务进行处理,然后使用 Await 函数等待它们完成。
值得指出的是,每次我们调用evalOpAsyncDefault
时,它都会创建一个新的托管序列,而evalOpAwaitDefault
只等待最近的默认序列。这意味着在上面的代码片段中,我们只等待第二个异步操作。对于我们的例子来说,这不是问题,但是如果我们现在知道的话,这可能会引入错误。正确的方法是使用显式创建的“命名序列”——我们将在下一节中这样做。
扩展并行工作负载处理
现在我们知道我们能够异步执行多个工作负载,我们能够扩展这一点,以利用 GPU 中的多个队列来实现工作负载的并行执行。
在 NVIDIA 1650 显卡上运行
为了展示一个有用的例子,我们将深入探讨如何在 NVIDIA 1650 显卡中实现这一点。您可以通过检查视频卡的设备报告(即可用的队列系列和并行处理功能)来亲自尝试。
NVIDIA 1650 中队列的概念性概述(图片由作者提供)
NVIDIA 1650 GPU 有 3 个队列系列。NVIDIA 1650 使用G
进行图形处理,使用T
进行传输,使用C
进行计算,其familyIndex 0
中的G+T+C
系列有 16 个队列,familyIndex 1
上的T
系列有 2 个队列,familyIndex 2
上的T+C
系列有 8 个队列。
截至今天(2020 年 10 月),当工作跨同一系列内的多个队列提交时,NVIDIA 不支持并行处理工作负载。但是,当跨队列系列提交工作负载时,它支持并行化。这意味着图形和计算系列队列之间的工作负载可以并行化,我们将在实施中使用这一知识。
并行工作流执行的实现
到目前为止,我们已经将所有 GPU 工作负载提交到一个队列,即使用底层队列索引 0 的图形familyIndex 0
。在我们使用 GPU 1650 的情况下,如果我们跨图形系列和计算系列提交工作负载,我们将能够实现并行处理。下面的图表应该为我们将要做的事情提供一个直觉。
通过多个系列队列并行执行操作(图片由作者提供)
为了做到这一点,我们需要修改三个关键的东西:
- 我们用相应的可用队列初始化 Kompute 管理器
- 我们创建了两个 Kompute 序列,每个序列都分配了相应的队列
- 我们在每个队列上运行操作
我们将深入研究这三点。
1.我们用相应的可用队列初始化 Kompute 管理器
当初始化一个管理器时,我们能够传递一个包含我们想要获取的队列的数组。在这种情况下,我们只获取一个图形队列和一个计算队列,但是,根据 NVIDIA 1650 的硬件规格,我们将能够请求多达 16 个图形队列(familyIndex 0)、2 个传输队列(familyIndex 1)和 8 个计算队列(familyIndex 2)。
2.我们创建了两个 Kompute 序列,每个序列都分配了相应的队列
现在,我们能够显式初始化两个托管序列,每个序列都分配到不同的队列,引用我们在上一步中传递的数组的索引。
3.我们在每个队列上运行操作
现在,我们能够运行提交到各个队列的操作。在这种情况下,并行提交两个 GPU 工作负载。
并行工作负载执行结果
运行上面提供的代码时,我们可以看到由于并行系列队列提交工作负载,执行时间提高了 2 倍。您还可以看到,如果我们从图形或计算队列提交到额外的队列,我们将不会看到任何进一步的速度提升,因为该 NVIDIA 1650 卡不支持队列内并行化。
你可以在这个文件中找到完整的代码并运行它——关于如何使用 CMAKE 运行完整套件的说明可以在主 Kompute 库中找到。
这是一个特别重要的结果,因为根据 NVIDIA 最近发布的 300x 显卡,Ampere GA10x 架构有所改进,允许同时处理两个计算工作负载。相对于上面的示例,这意味着如果我们使用一个图形队列和两个计算队列,我们可以看到 3 倍的性能提升(以及使用传输队列进行传输操作的额外性能)。
后续步骤
恭喜你,你一路走到了最后!虽然这篇文章涵盖了广泛的主题,但是也有大量的概念被浏览过。其中包括底层的 Vulkan 概念、GPU 计算基础和更高级的 Kompute 概念。幸运的是,网上有资源可以扩展你在这些方面的知识。以下是我推荐的一些进一步阅读的链接:
- "利用 Kompute 简化移动设备中的机器学习&跨供应商 GPU&Vulkan文章,深入探讨理论和概念
- Kompute 文档了解更多细节和更多示例
- 移动设备中的机器学习&利用 Kompute 简化跨厂商 GPU&Vulkan
- 用 GPU 加速你的移动应用程序使用安卓 NDK & Kompute 加速机器学习
- GPU 使用 Godot 引擎和 Kompute 加速 ML
- Vulkan SDK 教程深入了解底层 Vulkan 组件