如何获得最佳球员证明NumPy的最佳性能

深度 | 为你的深度学习任务挑选最合适GPU:从性能到价格的全方位指南 - 今日头条()
选自 Tim Dettmers Blog机器之心编译作者:Tim Dettmers参与:张可、吴攀、Rick R本文作者 Tim Dettmers 是瑞士卢加诺大学信息学硕士,热衷于开发自己的 GPU 集群和算法来加速深度学习。本文的最早版本发布于 2014 年 8 月,之后随着相关技术的发展和硬件的更新,Dettmers 也在不断对本文进行修正,截至目前已经进行了 5 次较大的更新。机器之心今天呈现的这篇是其 2016 年 6 月 25 日的最新更新:「重新编辑了多 GPU 部分;因为不再有重大作用,去掉了简单的神经网络内存部分;扩展了卷积内存部分;对 AWS 部分进行了删节,因为不再有实效;增加了我对 Xeon Phi 的看法;更新了 GTX 1000 系列。」而除了 GPU 之外,深度学习还需要其它一些硬件基础,详情可参阅机器之心之前的文章《深度 | 史上最全面的深度学习硬件指南》。当你在深度学习中使用 GPU 时,你会一次又一次地为它带来的速度提升而感到惊叹:在一般问题中能获得相对于 CPU 快 5 倍的速度提升,在一些更大的问题中还能获得相对快 10 倍的速度提升。在 GPU 的帮助下,你可以更快地试验新的想法、算法和实验,并迅速得到反馈——哪些是可行的、哪些是不可行的。如果你对深度学习是认真的,那么你一定要使用 GPU。但是,你应该选择哪一种呢?在这篇博客中我将指导你选择一款最适合你的 GPU。拥有一个快速的 GPU 对深度学习的初学者来说是非常重要的,因为它可以使你迅速获得有助于构建专业知识的实践经验,这些专业知识可以帮助你将深度学习应用到新问题上。没有这种迅速的反馈,从错误中汲取经验将会花费太多时间,会使人在继续深度学习的过程中感到受挫和沮丧。在 GPU 的帮助下,我很快就学会了如何在一系列 Kaggle 竞赛中应用深度学习,并且在 Partly Sunny with a Chance of Hashtags Kaggle 竞赛上获得了第二名,竞赛内容是通过一个给定的 Twitter 预测气象等级。比赛中,我使用了一个相当大的带有修正线性单元(rectified linear units)和 dropout 的两层深度神经网络进行正则化(regularization),而且勉强才能塞进我的6GB GPU 内存里。应该使用多个 GPU 吗?在 GPU 的帮助下,深度学习可以完成很多事情,这让我感到兴奋。我投入了到多 GPU 的领域之中,用 InfiniBand 40Gbit/s 的互连组装了小型 GPU 集群。我非常激动地想了解多个 GPU 能否获得更好的结果。我很快就发现,在多个 GPU 上,不仅神经网络难以进行有效的并行化,对普通的密集神经网络加速效果也很一般。小型神经网络可以并行并且有效地利用数据并行性,但对于大一点的神经网络,例如我在 Partly Sunny with a Chance of Hashtags Kaggle比赛中使用的,就几乎没有加速效果。随后,我进一步试验,对比 32 位的方法,我开发了带有模型并行性的新型的 8 位压缩技术,该技术能更有效地并行处理密集或全连接的神经网络层。然而,并行化失败了。我天真地为一系列问题对并行算法进行了优化,但只发现即使有优化过的自定义代码,它的效果也并不好——考虑到你所付出的努力。你需要非常了解你的硬件及其与深度学习算法交互的方式,这样你一开始就能衡量你是否可以受益于并行化。我的电脑主机配置:你可以看到三个 GXT Titan 和一个 InfiniBand 卡。对于深度学习来说,这是一个好的配置吗?然而,在并行化上还有一些不那么专业的问题。例如,卷积层很容易并行化和很好地扩展。很多框架也支持这种并行性;对大多数框架来说(比如 TensorFlow、Caffe、Theano 和 Torch),如果你使用 4 个 GPU,通常会加速约 2.5-3 倍;而对于优化过的研究代码,大约会有 3.6-3.8 倍的加速。微软的 CNTK 拥有最好的并行化性能,其与研究代码相近。CNTK 有一个缺点:它目前非常难使用,因为其使用了配置文件(config files),而非库 API。目前我在微软研究院做实习生,我将致力于 CNTK,我可以告诉您,API 与并行性能都将会获得显著的提升。这些变化使得在多 GPU 和多计算机下的并行化可以达到使普通用户能很容易受益于不同类型深度学习模型的快速训练的程度,无论是卷积网络、循环网络、还是全连接网络。使用多 GPU 的另外一个好处是你可以分别在每个 GPU 上运行多个算法或实验。其速度没有变快,但你能一次性通过使用不同的算法或参数得到更多性能信息。如果你的主要目标是尽快获得深度学习经验,这是非常有用的,而且它对想同时测试新算法的不同版本的研究者也非常有用。如果你想要学习深度学习,这对你的心理是很重要的。执行任务的间隔以及得到反馈信息的时间越短,大脑越能将相关的记忆片段整合成连贯的画面。如果你在小数据集上使用独立的 GPU 训练两个卷积网络,你就能更快地知道什么对于性能优良来说是重要的;你将更容易地检测到在交叉验证误差的模式,并正确地解释它们——即对于哪些你需要调整的模式,你需要添加、移除或调整哪些参数与层。所以总的来说,一个 GPU 对所有任务来说就已经足够了,但多个 GPU 对于加速你的深度学习模型来说越来越重要。如果你想快速学习深度学习,多个廉价的 GPU 也很好。应该使用哪种加速器呢?NVIDIA、AMD、或 Xeon Phi?NVIDIA 的标准库使得在 CUDA 中建立第一个深度学习库很容易,但没有适合 AMD的 OpenCL 的那样强大的标准库。目前还没有适合 AMD 显卡的深度学习库——所以只能选择 NVIDIA 了。即使未来一些 OpenCL 库可用,我仍会坚持使用 NVIDIA:因为对于 CUDA 来说,GPU 计算或者 GPGPU 社区是很大的,而对于 OpenCL 来说则较小。因此,在 CUDA 社区,好的开源解决方案和为编程提供可靠的建议是现成的。此外,NVIDIA 公司现在对深度学习抱有很大信心。他们押注深度学习在未来十年会发展壮大,但你在 AMD 公司身上就看不到这样的信心。至于 Xeon Phi,其广告宣称你能够使用标准的 C 代码,并可将代码轻松转换成加速过的 Xeon Phi 代码。该功能听起来很有趣,因为你可能认为你可以依靠庞大的 C 代码资源。然而,事实上,其只支持非常小部分的 C 代码,因此该功能并不真正有用,大部分的 C 运行起来是很缓慢的。我曾研究过超过 500 个 Xeon Phi 的集群,遭遇了无止尽的挫折。我不能运行我的单元测试(unit test),因为 Xeon Phi的 MKL(数学核心函数库)并不兼容 NumPy;我不得不重写大部分代码,因为英特尔 Xeon Phi 编译器无法让模板做出适当约简。例如,switch 语句,我不得不改变我的 C 接口,因为英特尔编译器不支持 C++ 11的一些特性。这一切导致了在没有单元测试的情况下来执行代码的重构。它花了很长时间。这真是地狱啊。随后当我的代码被执行时,一切都运行得很慢。如果你的操作的张量(tensor)的尺寸连续变化,线程调度器(thread scheduler)中的漏洞或问题会削弱性能。例如,如果你有不同大小的全连接层或 dropout 层,Xeon Phi 会比 CPU 还慢。所以,如果你想做深度学习,远离 Xeon Phi!了解卷积神经网络的基本内存需求当你在选择合适的 GPU 时,你得知道自己使用深度学习所要解决的问题需要多大的内存。因此接下来的两段文字会探讨卷积神经网络的内存消耗,让你可以确保自己的 GPU 有足够满足需要的内存,但又不会太多,这样能节省点钱。卷积神经网络的内存需求非常不同于简单的神经网络。你可能会认为前者的参数更少因此需要更少内存。如果只是保存网络文件,这没什么不对,但要想训练它的话情况就不同了。每个卷积层的激活(activation)和误差(error)相较于简单的神经网络来说都是巨大的,而正是它们占用了内存的主要部分。将激活和误差的大小加起来,我们就可以确定出大概的内存需求。然而要确定网络中哪种状态下激活和误差的尺寸大小是很难的。一般来说,前几层网络会占用很多内存,所以主要内存需求就来源于你的输入数据大小,因此可以率先考虑你的输入数据。通常 ImageNet 使用的输入维度(input dimensions)是224×224×3,即 224×224 像素的 3 色信道图片。要在 ImageNet 上得到当下最先进的结果至少需要 12GB 内存,而在一个 112×112×3 维的类似数据集上我们可能只需 4-6 GB 内存。另一方面,对于一个输入尺寸为 25×75×75×3 的视频数据集来说,12GB 内存可能远给不了你想要的好效果。然而另一个重要方面就是数据集中的样本数量。比如你只取了 ImageNet 数据集图像中 10% 的样本,然后你的模型很快就会过拟合(只是没有足够多的样本来很好地训练),因此你那个消耗远远更少内存的小网络将足以与卷积网的性能媲美,所以 4GB 左右或更少的内存对于这项任务来说都绰绰有余。这就意味着图像越少,反过来所需内存就越少。标签的分类数量也是同样的道理。如果你从 ImageNet 数据集中只选取了 2 个分类来建立模型,那么相较于拥有 1000 个分类的模型,它将消耗更少的内存。这就是这样:如果你有更少的需要彼此区分的分类,那么过拟合就会出现得快得多,或者换句话说,相比于区分 1000 个分类,区分 2 个分类所需的参数要少得多。有关这些准则的一个实际例子是 Kaggle 的浮游生物检测比赛。起初我认为,用自己的 4 GPU 系统来参加比赛可能会有巨大优势。我以为自己或许能够在很短时间内训练出一个庞大的卷积网络——还有一点就是别人都缺少这样的硬件。然而由于数据集过小(50×50 像素,双色信道,40 万张训练图像;大约 100 个分类),我很快意识到即使对于一个大小刚好够放入一个训练很快的小 GPU 的小网络来说,过拟合也是一个问题。所以多个 GPU 并没有多少速度方面的优势,而大 GPU 内存更没有任何优势可言。因此对于这种任务,一个4-6 GB 内存的小 GPU 就足以达到很好的效果。虽然此例中我的内存是足够的,但你最终会遇到内存不足的情况。然而你并不需要为这个问题而买一个新 GPU,可能只需使用一个简单的内存减小技术(memory reduction technique)。内存减小技术及其效果一种方法是使用更大步态(strides )的卷积核(convolutional kernels),也就是不对每个像素都应用批形式的(patch-wise)卷积,而是针对每 2 个或 4 个像素(即2或4个步态),这样就能产生较少的输出数据。输入层通常使用这种方法,因为它消耗了大部分的内存。另一种减少内存占用的诀窍是引入一个能够减少维度的 1×1 卷积核层。例如 96 个 1×1 的卷积核能够使 64×64×256 大小的输入数据降为 64×64×96。还有一个显著的技术是池化(pooling)。一个 2×2 的池化层将减少四层的数据量,从而大大减少了后续层的内存占用。如果这些方法都不管用,你总是可以尝试减少 mini-batch 的大小。mini-batch 尺寸是一个非常重要的内存影响因素。大小为 64 的批(batch)会比大小为 128 的批减少一半内存消耗。然而训练时间可能也会更久,特别是在训练的最后阶段,为了得到准确的梯度,它显得更加重要。大多数卷积运算也为大小为 64 或更大的 mini-batch 进行了优化,因而从 32 大小的批开始,训练速度会大大减小。所以缩小 mini-batch 的大小到或者甚至低于 32,只应作为最后的对策。另一个经常被忽视的方法是改变卷积网络所使用的数据类型。将 32 位换为 16 位,你就可以轻松减半内存消耗且不会降低分类性能。将它运用在 P100 Tesla 卡中,这甚至能给你带来巨大的提速。那么将这些内存减小技术应用在实际数据中会怎样?如果我们采用 128 大小的批,3 色通道的 250×250 像素图片(250×250×3)做为输入,3×3 卷积核增长步长为32,64,96…,则我们将有大致如下、仅包含误差及激活的内存占用情况:92MB→1906MB→3720MB-&5444MB-&…此时内存将很快被挤爆。如果现在我们使用 16 位而非 32 位的数据类型,上面的数字就可降为一半;64 大小的批同理。同时使用 64 大小的批及 16 位的数据类型将使内存占用减为原先的四分之一。然而我们仍然会需要大量内存来训练一个更多层的深度网络。如果我们为第一层数据加 2 个步态则会带来哪些改变——后面跟着一个 2×2 的最大池化( max pooling)?92MB (输入)-&952MB (卷积)-&238MB (池化)-&240MB (卷积)-&340MB (卷积)→….这看起来更易于管理。在处理多达 20 至 30 层的网络时,我们仍然会遇到内存问题,而你只能应用另一个最大池化或其他技术。例如 32 个 1×1 的卷积核可将最后一层数据大小从 340MB 降为仅仅 113MB,因此我们能够轻松扩展更多层的网络而不会出现任何问题。一旦你广泛使用了最大池化、跨越式(striding)和 1×1 卷积核,你将在这些层处理过程中扔掉非常多的信息,网络缺少数据来工作,以至于损害到模型的预测性能。因此当这些技术能够非常有效地减小内存消耗时,你应当小心地使用它们。随着时间的推移,你在训练卷积神经网络时会学到的一件事情就是,如何最佳混合这些技术来得到一个良好的结果,且不会遇到任何内存问题。了解卷积神经网络的临时内存需求上文所解释的是卷积神经网络内存消耗的主要来源以及缓解内存问题的方法。然而还有另一层不太重要且更难理解的内存消耗问题,但它也可能给出最佳的网络解决方案,或许能帮你确定深度学习任务的实际内存需求。一般有三种类型的卷积实现方式。一种方式是采用傅里叶变换,其他两种都是先重新调整内存计算来直接做数据计算。这种调整要么发生在逐像素计算的类似批的结构中,要么发生于使用矩阵乘法计算卷积的矩阵结构中。滥用符号的连续卷积定理:输入函数表示一个图像或特征映射,减去 x0 可看做是创建宽度与 x 相关的图像批,然后再乘以核(*kernel)*。最后的结果在傅里叶域中是连乘形式;这里表示一个傅立叶变换函数。对于离散「维度」(x),我们运用累加而不是积分——不过思路都是相同的。卷积的数学运算可以用傅里叶频域里一个简单的元素方式的矩阵乘法来描述。因此可在输入以及每个卷积核上执行一个快速的傅立叶变换,并将这些元素形式的相乘以得到特征映射——卷积层的输出。在向后传播的过程中,我们做一个快速的傅立叶逆变换来接收标准域中的梯度以更新权重。理想情况下,我们将所有的傅立叶变换存储在内存中,从而节省每个过程中分配内存的时间。这可能会增加很多额外内存,而这是为卷积网络的傅立叶方法所添加的内存块——保留所有这些内存只是为了让一切顺利进行。这种方法显然是最快的卷积方法。通过 Winograd 快速傅里叶变换,这种卷积技术在流行的 3×3 卷积核上运行飞快。当然运用快速傅立叶变换方法的其他卷积核通常是最快。然而这种方法会占用相当多的内存,不过这个领域的研究仍然很活跃,使用快速傅里叶变换的核在性能和内存上都非常高效,而核还在变得更好。其他两种直接操作图像块的方法是为重叠的批重新分配内存来实现连续的内存访问。缓慢的内存访问可能是对算法性能的最大伤害。连续内存中的内存预取与校准能使卷积运算速度运行更快。连续内存意味着所有内存地址都相比邻——没有「越级」索引——并且能实现更快的内存读取。或者你可以安排矩阵内部的内存,然后使矩阵相乘以达到相同效果。既然矩阵乘法已经被高度优化,这对一个运行良好的卷积运算来说也是一个很好的策略。有关运用这种方法计算卷积的更多内容可参看 CUDA 代码,但是对输入或像素数据的预取是增加内存使用的主要原因。由于一些条目被重复,矩阵乘法的变量使用了相对更多的内存,但这种方法在以批的方式计算卷积时往往比已过时的方法快一点。我希望这部分内容给出了卷积神经网络中内存问题的相关思路。现在我们来看看一些实际的建议。给定预算下的最快 GPU处理性能最常由每秒浮点运算来测量(FLOPS)。这种测量标准往往被用在 GPU 计算的广告宣传中,它还确定了超级计算机领域的世界前 500 强名单。然而这种测量方式是带有误导性的,因为它测量的处理性能,所针对的是不会出现在现实世界中的问题。原来最重要的可实践性 GPU 性能测量是以 GB/s 为单位的内存带宽,它衡量了内存每秒可读取的数据大小。内存带宽非常重要,因为几乎所有的数学运算,比如矩阵乘法、点积、求和等等,都有带宽限制,即受限于可从内存中提取多少数字,而非给定数字量的情况下可执行多少计算。还有一些其他原因能够解释为什么 GPU 非常适合深度学习等许多计算任务;如果你想更深入地了解 GPU,可以阅读我在 Quora 上面的回答:「为什么 GPU 非常适合深度学习?」链接:/Why-are-GPUs-well-suited-to-deep-learning/answer/Tim-Dettmers-1CPU 和 GPU 随时间变化的带宽比较:带宽是 GPU 比 CPU 的计算速度更快的主要原因之一。带宽可直接在同一架构内进行比较,例如直接查看两张 Pascal 卡(如 GTX 1080 与 GTX 1070 )的内存带宽来比较其性能。然而不同架构之间不能直接比较,像是 Pascal 的 GTX 1080 与 Maxwell 的 GTX Titan X,因为各自给定的内存带宽架构是非常不同的。这使得一切都有点棘手,但总的说来带宽能给你一个有关 GPU 大致运行速度的良好概述。要确定一个给定预算下的最快 GPU 类型,可以使用这篇维基百科中的内容(http://en.wikipedia.org/wiki/List_of_Nvidia_graphics_processing_units#GeForce_700_Series),看一下它的带宽是多少 GB/s;其中列出的较新款显卡价格是相当准确的(900 和 1000 系列),但是旧款卡价格明显低于列表中的价格——尤其当你通过 eBay 网购时。比如一张正版 GTX Titan 在 eBay 上卖 $350 左右、 GTX 970 卖 $210。然而另一个需要考虑的因素是,并非所有架构都与 cuDNN 兼容。由于几乎所有的深度学习库都使用 cuDNN 做卷积运算,这就限制了对于 Kepler GPU 或更好 GPU 的选择,即 GTX 600 系列或以上版本。另外 Kepler GPU 通常会很慢。因此这就意味着你应该选择 900 或 1000 系列 GPU 来获得更好的性能。为了粗略估计出深度学习任务中的显卡性能比较,我创建了一个简单的GPU等价表。如何阅读它呢?例如 GTX 980 的速度相当于 0.45 个 GTX 1080,或是 GTX 1080 的速度是 GTX 980 的两倍以上。GTX 1080 = 0.8 GTX 1070 = 0.66 GTX Titan X = 0.66 GTX 980 Ti = 0.45 GTX 980GTX 1080 = 0.4 GTX 970 = 0.33 GTX Titan = 0.2 AWS GPU instance (g2.2 and g2.8) = 0.2 GTX 960通常我会推荐 GTX 1080 或 GTX 1070。二者都很卓越,如果你能承受 GTX 1080 的价格当然更好。GTX 1070 更便宜些且速度仍比 GTX Titan X 快。然而这两个卡的问题是它们的8GB内存。这个内存容量对于大多数任务来说是相当充足的,比如 Kaggle 竞赛、大多数的数据集、深度且自然的语言理解任务。但是对研究人员而言,尤其是 ImageNet 的研究者,在处理拥有庞大背景资料的视频数据或自然语言理解任务时,选择 12GB 内存的 GTX Titan X 会更好——这里内存就尤为重要。如果你缺钱的话就没有任何办法了。亚马逊网页服务上的 GPU 实例是相当昂贵且龟速的,没钱就买不到好东西。你可以到 eBay 上以 $210 买一个过得去的 GTX 970,但也有一些与此相关的内存问题(见下文)。另一个选择是从 eBay 购买正版 GTX Titan;不过 $350 也已经相当贵了。GTX 680 和 GTX 960 也是较为便宜的选择,它们通常在 $200 以下,但是相较于其他更成熟的 GPU 则是相当慢。廉价而困扰GTX 970 是一个你需要留意的特例,其架构较为怪异,内存使用超过 3.5GB 时性能可能会被削弱,因此对于大的卷积神经网络的训练就很麻烦。这个问题在新闻报道中已经相当戏剧化地变多了起来,但事实证明整个问题对于深度学习来说并不像原始基准表现的那样戏剧化:如果不超过 3.75GB,它仍然比 GTX 960 要快。不管怎样 GTX 970 是一款用来学习深度学习的优秀 GPU。它还适合于大多数未涉及太多数据的 Kaggle 数据集。如果你不想要所有这些麻烦,那么就去 eBay 购买一个不会造成麻烦的正版 GTX Titan 或者一个便宜的 4GB GTX 960。亚马逊网络服务(AWS)中的 GPU 实例在这篇博文的前一个版本中,我推荐了 AWS GPU 的现货实例,但现在我不会再推荐它了。目前 AWS 上的 GPU 相当慢(一个 GTX 1080 的速度是 AWS GPU 的五倍)并且其价格在过去的几个月里急剧上升。现在看起来购买自己的 GPU 又似乎更为明智了。总结运用这篇文章里的所有信息,你应该能通过平衡内存大小的需要、带宽速度GB/s以及 GPU 的价格来找到合适的 GPU 了,这些推理在未来许多年中都会是可靠的。但现在我所推荐的是 GTX 1080 或 GTX 1070,只要价格可以接受就行;而如果你是一位研究人员,或许会想要一个 GTX TITAN X。如果你缺少硬件投资资金并且可以接受一定的内存限制,那么 GTX 970 对于中等大小及以下的数据集来说就是个可靠的选择。如果想减少麻烦,一个eBay 上的正版 GTX Titan 将是极好的。GTX 680 和 GTX 960 最便宜,同时也是最慢的GPU。如果你真的很缺钱,目前尚没有其他好的选项。总结性建议总的说来最好的 GPU:GTX 1080 和 GTX Titan X有成本效益但价格高的:eBay 上的 GTX Titan X , GTX 1070有成本效益但麻烦多的:GTX 970(有内存问题)最便宜的卡片而且没有麻烦:eBay 上正规的 GTX Titan我的数据集大于 250GB:GTX Titan我钱不多:eBay上的 GTX 680 4GB 或者 GTX 960 4GB我几乎没钱:尚待解决;深度学习是为壕准备的我参加 Kaggle 比赛:GTX 1070 或者 GTX 970我是一名研究人员:至少需要一个 GTX TITAN X 和 1-4 GTX 1080 来做原型我想建立一个 GPU 集群:这真的很复杂,你可以从这里得到一些思路://how-to-build-and-use-a-multi-gpu-system-for-deep-learning/我刚开始进行深度学习,并且我是认真的:以 eBay 上的 GTX 970 开始对小数据集上的深度学习有个大致感受;如果你想参加 Kaggle 比赛或进行正规深度学习,就把 GTX 970 卖了并升级到 GTX 1080 ;如果你想做研究的话,就升级为 GTX Titan X。(C)本文由机器之心编译,转载请联系本公众号获得授权。?------------------------------------------------加入机器之心(全职记者/实习生):投稿或寻求报道:广告&商务合作:
做个广告也装逼,逼时代正在降临了吗。
机器翻译的痕迹很明显呀
没看懂。。。
来众筹吧?//@静静obm165: y在校大三表演系学生,C暑假期间求一个人包养,5真心的来。
你这个太深奥,我看不懂。
女人的底线就是没有底线,而且女人的底线就是用来被突破的。女人喜欢被男人征服,但是征服就必然有反抗,反抗就是她的底线,所以突破女人的底线就是征服女人的过程,但很多人一看到拒绝就不敢再前进一步,也就败阵了。。看我名字
专业的前沿科技媒体与产业服务平台,让未来在这里发声
36氪,关注互联网创业
人工智能领域的垂直,专业媒体,传达前沿和产业资讯。
致力成为人工智能权威媒体和前沿科技研究机构
深度报道新经济各行业领导者的科技新媒体。
(C) 2016 今日头条 违法和不良信息举报电话:010-公司名称:北京字节跳动科技有限公司1005人阅读
Numpy小记(14)
Introduction
NumPy提供了一个特殊的数据类型ndarray,其在向量计算上做了优化。这个对象是科学数值计算中大多数算法的核心。
相比于原生的Python,利用NumPy数组可以获得显著的性能加速,尤其是当你的计算遵循单指令多数据流(SIMD)范式时。
然而,利用NumPy也有可能有意无意地写出未优化的代码。下面这些技巧可以帮助你编写高效的NumPy代码。
避免不必要的数据拷贝
查看数组的内存地址
1. 查看静默数组拷贝的第一步是在内存中找到数组的地址。下边的函数就是做这个的:
&&&&returnx.__array_interface__['data'][0]
2. 有时你可能需要复制一个数组,例如你需要在操作一个数组时,内存中仍然保留其原始副本。
a =np.zeros(10); aid=id(a);
b =a.copy();id(b)==aid
Note:-列表拷贝一节
具有相同数据地址(比如id函数的返回值)的两个数组,共享底层数据缓冲区。然而,共享底层数据缓冲区的数组,只有当它们具有相同的偏移量(意味着它们的第一个元素相同)时,才具有相同的数据地址。共享数据缓冲区,但偏移量不同的两个数组,在内存地址上有细微的差别:
id(a),id(a[1:])
在这篇文章中,我们将确保函数用到的数组具有相同的偏移量。
下边是一个判断两个数组是否共享相同数据的更可靠的方案:
def get_data_base(arr):
&&&&&&&For a given Numpy array, finds the base array that &owns&
the actual data.&&&
&&&&base = arr
&&&&whileis instance(base.base, np.ndarray):
&&&&&&&&base = base.base
&&&&return base
def arrays_share_data(x, y):
&&&&return get_data_base(x)is
get_data_base(y)
print(arrays_share_data(a,a.copy()), arrays_share_data(a,a[1:]))
False True
感谢Michael Droettboom指出这种更精确的方法,提出这个替代方案。
Note:a和a[1:]id虽然不同,但是他们是共享内存的。
就地操作和隐式拷贝操作
3. 数组计算包括就地操作(下面第一个例子:数组修改)或隐式拷贝操作(第二个例子:创建一个新的数组)。
a *=2;id(a)==aid
c =a*2;id(c)==aid
一定要选择真正需要的操作类型。隐式拷贝操作很明显很慢,如下所示:
%%timeit a=np.zeros()
loops, best of 3:19.2ms per loop
%%timeit a=np.zeros()
loops, best of 3:42.6ms per loop
4. 重塑数组可能涉及到拷贝操作,也可能涉及不到。
例如,重塑一个二维矩阵不涉及拷贝操作,除非它被转置(或更一般的非连续操作):
a =np.zeros((10,10));
id(a); aid
重塑一个数组,同时保留其顺序,并不触发拷贝操作。
b =a.reshape((1,-1));id(b)==aid
转置一个数组会改变其顺序,所以这种重塑会触发拷贝操作。
c =a.T.reshape((1,-1));id(c)==aid
因此,后边的指令比前边的指令明显要慢。
5. 数组的flatten和revel方法将数组变为一个一维向量(铺平数组)。flatten方法总是返回一个拷贝后的副本,而revel方法只有当有必要时才返回一个拷贝后的副本(所以该方法要快得多,尤其是在大数组上进行操作时)。
d =a.flatten();id(d)==aid
e =a.ravel();id(e)==aid
%timeit a.flatten()
loops, best of 3:881ns per loop
%timeit a.ravel()
1000000loops, best of3:294ns
广播规则允许你在形状不同但却兼容的数组上进行计算。换句话说,你并不总是需要重塑或铺平数组,使它们的形状匹配。
广播规则描述了具有不同维度和/或形状的数组仍可以用于计算。一般的规则是:当两个维度相等,或其中一个为1时,它们是兼容的。NumPy使用这个规则,从后边的维数开始,向前推导,来比较两个元素级数组的形状。最小的维度在内部被自动延伸,从而匹配其他维度,但此操作并不涉及任何内存复制。
下面的例子说明了两个向量之间进行矢量积的两个方法:第一个方法涉及到数组的变形操作,第二个方法涉及到广播规则。显然第二个方法是要快得多。
a =np.arange(n)
ac =a[:, np.newaxis]
ar =a[np.newaxis, :]
%timeit np.tile(ac, (1, n))* np.tile(ar, (n,1))
100 loops, best of 3:10 ms per loop
%timeit ar* ac
100 loops, best of 3:2.36 ms per loop[- 广播规则]
NumPy数组进行高效的选择
NumPy提供了多种数组分片的方式。
数组视图涉及到一个数组的原始数据缓冲区,但具有不同的偏移量,形状和步长。NumPy只允许等步长选择(即线性分隔索引)。
NumPy还提供沿一个轴进行任意选择的特定功能。
最后,花式索引(fancy indexing)是最一般的选择方法,但正如我们将要在文章中看到的那样,它同时也是最慢的。
1. 创建一个具有很多行的数组。我们将沿第一维选择该数组的分片。
n, d =100000,100
a =np.random.random_sample((n, d)); aid=id(a)
数组视图和花式索引
2. 每10行选择一行,这里用到了两个不同的方法(数组视图和花式索引)。
b1 =a[::10]
b2 =a[np.arange(0, n,10)]
np.array_equal(b1, b2)
3. 数组视图指向原始数据缓冲区,而花式索引产生一个拷贝副本。
id(b1)==aid,id(b2)==aid
(True,False)
两个方法的执行效率,花式索引慢好几个数量级,因为它要复制一个大数组。
替代花式索引:索引列表
当需要沿一个维度进行非等步长选择时,数组视图就无能为力了。
然而,替代花式索引的方法在这种情况下依然存在。给定一个索引列表,NumPy的函数可以沿一个轴执行选择操作。
i =np.arange(0, n,10)
b2 =np.take(a, i, axis=0)
np.array_equal(b1, b2)
第二个方法更快一点:
%timeit a[i]
loops, best of 3:13ms per loop
%timeit np.take(a, i, axis=0)
loops, best of 3:4.87ms per loop
替代花式索引:布尔掩码
当沿一个轴进行选择的索引是通过一个布尔掩码向量指定时,compress函数可以作为花式索引的替代方案。
i =np.random.random_sample(n) & .5
可以使用花式索引或者np.compress函数进行选择。
b2 =np.compress(i, a, axis=0)
np.array_equal(b1, b2)
%timeit a[i]
loops, best of 3:59.8ms per loop
%press(i, a, axis=0)
loops, best of 3:24.1ms per loop
第二个方法同样比花式索引快得多。
花式索引是进行数组任意选择的最一般方法。然而,往往会存在更有效、更快的方法,应尽可能首选那些方法。
当进行等步长选择时应该使用数组视图,但需要注意这样一个事实:视图涉及到原始数据缓冲区。
为什么NumPy数组如此高效?
一个NumPy数组基本上是由元数据(维数、形状、数据类型等)和实际数据构成。数据存储在一个均匀连续的内存块中,该内存在系统内存(随机存取存储器,或RAM)的一个特定地址处,被称为数据缓冲区。这是和list等纯Python结构的主要区别,list的元素在系统内存中是分散存储的。这是使NumPy数组如此高效的决定性因素。
为什么这会如此重要?主要原因是:
1. 低级语言比如C,可以很高效的实现数组计算(NumPy的很大一部分实际上是用C编写)。例如,知道了内存块地址和数据类型,数组计算只是简单遍历其中所有的元素。但在Python中使用list实现,会有很大的开销。
2. 内存访问模式中的空间位置访问会产生显著地性能提高,尤其要感谢CPU缓存。事实上,缓存将字节块从RAM加载到CPU寄存器。然后相邻元素就能高效地被加载了(顺序位置,或引用位置)。
3. 数据元素连续地存储在内存中,所以NumPy可以利用现代CPU的矢量化指令,像英特尔的SSE和AVX,AMD的XOP等。例如,为了作为CPU指令实现的矢量化算术计算,可以加载在128,256或512位寄存器中的多个连续的浮点数。
4. NumPy可以通过Intel Math Kernel Library (MKL)与高度优化的线性代数库相连,比如BLAS和LAPACK。NumPy中一些特定的矩阵计算也可能是多线程,充分利用了现代多核处理器的优势。
总之,将数据存储在一个连续的内存块中,根据内存访问模式,CPU缓存和矢量化指令,可以确保以最佳方式使用现代CPU的体系结构。
就地操作和隐式拷贝操作之间的区别
让我们解释一下技巧3。类似于a *= 2这样的表达式对应一个就地操作,即数组的所有元素值被乘以2。相比之下,a = a*2意味着创建了一个包含a*2结果值的新数组,变量a此时指向这个新数组。旧数组变为了无引用的,将被垃圾回收器删除。第一种情况中没有发生内存分配,相反,第二种情况中发生了内存分配。
更一般的情况,类似于a[i:j]这样的表达式是数组某些部分的视图:它们指向包含数据的内存缓冲区。利用就地操作改变它们,会改变原始数据。因此,a[:] = a * 2的结果是一个就地操作,和a = a * 2不一样。
知道NumPy的这种细节可以帮助你解决一些错误(例如数组因为在一个视图上的一个操作,被无意中修改),并能通过减少不必要的副本数量,优化代码的速度和内存消耗。
为什么有些数组不进行拷贝操作,就不能被重塑?
一个转置的二维矩阵不依靠拷贝就无法进行铺平。一个二维矩阵包含的元素通过两个数字(行和列)进行索引,但它在内部是作为一个一维连续内存块存储的,可使用一个数字访问。
有多个在一维内存块中存储矩阵元素的方法:我们可以先放第一行的元素,然后第二行,以此类推,或者先放第一列的元素,然后第二列,以此类推。第一种方法叫做行优先排序,而后一种方法称为列优先排序。这两种方法之间的选择只是一个内部约定问题:NumPy使用行优先排序,类似于C,而不同于FORTRAN。
更一般的情况,NumPy使用步长的概念进行多维索引和元素的底层序列(一维)内存位置之间的转换。array[i1, i2]和内部数据的相关字节地址之间的具体映射关系为:
offset = array.strides[0] * i1 + array.strides[1] * i2
重塑一个数组时,NumPy会尽可能通过修改步长属性来避免拷贝。例如,当转置一个矩阵时,步长的顺序被翻转,但底层数据仍然是相同的。然而,仅简单地依靠修改步长无法完成铺平一个转置数组的操作(尝试下!),所以需要一个副本。
Recipe 4.6(NumPy中使用步长技巧)包含步长方面更广泛的讨论。同时,Recipe4.7(使用步长技巧实现一个高效的移动平均算法)展示了如何使用步伐加快特定数组计算。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:675283次
积分:10924
积分:10924
排名:第943名
原创:444篇
转载:73篇
评论:87条
阅读:7961
文章:13篇
阅读:19287
阅读:10626
文章:16篇
阅读:32220
文章:18篇
阅读:24929
(23)(4)(6)(16)(6)(11)(15)(5)(4)(5)(30)(8)(12)(9)(10)(17)(20)(19)(7)(24)(9)(15)(19)(57)(12)(28)(15)(36)(15)(19)(2)(2)
Contact me}

我要回帖

更多关于 最佳阵容如何获得球员 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信