卷积神经网络:生物启发模型
你能认出上图中的人吗?如果你是一个科幻迷,你会立刻认出他们是 J.K .罗琳的全球现象哈利波特系列丛书中的哈利、罗恩和赫敏。这张照片是《哈利·波特与死亡圣器》第一部的一个场景——哈利、罗恩和赫敏正在审问小偷蒙顿格斯·弗莱奇,他们不久前被小精灵多比和克利切抓住了。嗯,我知道这一点,因为我已经看过这部电影和这本书很多次了!但是,怎样才能让计算机像你我一样理解这幅图像呢?让我们明确地思考一下,要使它有意义,所有的知识都必须到位:
- 你认出这是一群人的图像,并且明白他们在一个房间里。
- 你认出那里有成堆的报纸和一张带椅子的长木桌,所以位置很可能是厨房或普通客厅。
- 你从构成哈利·波特脸上眼镜的几个像素认出了他。他也有一头黑发,这很有帮助。
- 同样,你认出罗恩·韦斯莱是因为他的红头发,赫敏·格兰杰是因为她的长发。
- 你认出了另一个秃顶、穿着老式服装的男人,这表明他要老得多。
- 你认出了另外两个不是人类的生物(多比和克利切),即使你不熟悉它们。除了你对正常人长相的了解之外,你还利用了他们的身高、面部结构、身体尺寸。
- 哈利、罗恩和赫敏正在审问那个光头男人。你得出这个结论是因为你知道他们的身体姿势正对着他,你感觉到他们的视力出现了疑问,你还看到赫敏手里拿着一根魔杖(魔杖是哈利波特魔法世界中的魔法武器)。
- 你知道秃头男人感到害怕。鉴于他双手捂胸,你明白他在隐瞒什么。你开始思考这个场景后几秒钟即将展开的事件的含义,并对将会揭露什么秘密感到好奇。
- 这两个生物正看着哈利和罗恩。看起来,他们想说些什么。换句话说,你是在推理那些生物的精神状态。哇,你会读心术!
我可以继续,但这里的要点是,当你看照片时,你在那一秒钟使用了大量的信息。关于场景的 2D 和 3D 结构的信息,视觉元素,如人的身份、他们的行为,甚至他们的想法。你思考场景的动态,并猜测接下来会发生什么。所有这些因素加在一起,你就能理解这个场景。
令人难以置信的是,人类的大脑是如何展开一幅仅由 R、G、B 值组成的图像的。电脑怎么样?我们如何开始编写一个算法,像我上面做的那样对场景进行推理?我们怎样才能得到正确的数据来支持我们的推论呢?
(Source: https://medium.com/@alonbonder/ces-2018-computer-vision-takes-center-stage-9abca8a2546d)
随着时间的推移,机器学习研究人员广泛关注对象检测问题,计算机视觉领域解决了这个问题。有各种各样的东西使识别物体变得困难:图像分割/变形、照明、启示、视点、巨大的尺寸等等。特别是,计算机视觉研究人员通过将许多简单的神经元链接在一起,使用神经网络来解决复杂的对象识别问题。在传统的前馈神经网络中,图像被输入到网络中,神经元处理图像并将它们分类为真和假可能性的输出。听起来很简单,不是吗?
(Source: https://www.simplicity.be/article/recognizing-handwritten-digits/)
但是如果图像变形了,就像上面的数字一样,怎么办?前馈神经网络仅在数字位于图像的正中间时工作良好,但是当数字稍微偏离位置时会严重失败。换句话说,网络只知道一种模式。这在现实世界中显然是没有用的,因为真实的数据集总是脏的和未经处理的。因此,在输入图像不完美的情况下,我们需要改进我们的神经网络。
谢天谢地,卷积神经网络来了!
卷积过程
那么卷积神经网络到底是什么?据谷歌大脑的研究科学家克里斯·奥拉赫称:
最基本的,卷积神经网络可以被认为是一种使用相同神经元的许多相同副本的神经网络。这使得网络可以拥有大量神经元,并表达计算量大的模型,同时保持需要学习的实际参数(描述神经元行为的值)数量相当少。”
(Source: A 2D CNN — http://colah.github.io/posts/2014-07-Conv-Nets-Modular/)
注意这里使用的术语:**同一个神经元的完全相同的副本。**这大致基于人类大脑的工作过程。通过使用相同的大脑记忆点,人类可以发现和识别模式,而不必重新学习概念。例如,无论我们从哪个角度看,我们都能认出上面的数字。前馈神经网络做不到这一点。但是卷积神经网络可以,因为它理解平移不变性——它将一个对象识别为一个对象,即使它的外观以某种方式变化。
简单来说,卷积过程是这样工作的:
- 首先,CNN 使用滑动窗口搜索将图像分成重叠的图像块。
- 然后,CNN 将每个图像块输入一个小的神经网络,对每个图像块使用相同的权重。
- 然后,CNN 将每个图块的结果保存到一个新的输出数组中。
- 之后,CNN 对输出数组进行降采样以减小其大小。
- 最后但同样重要的是,在将一幅大图像缩小成一个小数组后,CNN 预测该图像是否匹配。
有一篇由 Adam Geitgey 撰写的精彩教程,详细介绍了卷积过程的工作原理。我绝对建议你去看看。
历史背景
CNN 的普及主要归功于 Yann LeCun 的努力,他现在是脸书人工智能研究的主任。20 世纪 90 年代初,LeCun 在当时世界上最负盛名的研究实验室之一贝尔实验室工作,并建立了一个用于读取手写数字的支票识别系统。在 1993 年有一个非常酷的视频,LeCun 展示了这个系统是如何工作的。这个系统实际上是一个完整的端到端的图像识别过程。他在 1998 年与 Leon Bottou、Patrick Haffner 和 Yoshua Bengio 合著的论文介绍了卷积网络以及他们构建的完整的端到端系统。这是一篇相当长的论文,所以我在这里简单总结一下。前半部分描述了卷积网络,展示了它的实现,并提到了与该技术相关的所有内容(我将在下面的 CNN 架构一节中介绍)。第二部分展示了如何将卷积网络与语言模型集成在一起。例如,当你阅读一段英语文本时,你可以在英语语法的基础上建立一个系统来提取该语言中最可能的解释。最重要的是,你可以建立一个 CNN 系统,并训练它同时进行识别和分割,并为语言模型提供正确的输入。
(Source: https://www.wired.com/2014/08/deep-learning-yann-lecun/)
CNN 架构
让我们讨论卷积神经网络的架构。我们正在处理一个输入图像。我们执行一系列卷积+池操作,然后是一些完全连接的层。如果我们正在执行多类分类,输出是 softmax。在每个 CNN 中有 4 个基本构件:卷积层、非线性(CNN 层中使用的 ReLU 激活)、池层和全连接层。
1 —卷积层
这里我们从输入图像中提取特征:
- 我们通过使用输入数据的小方块学习图像特征来保持像素之间的空间关系。这些输入数据的方块也被称为滤波器或内核。
- 通过在图像上滑动过滤器并计算点积形成的矩阵被称为特征图。过滤器数量越多,提取的图像特征就越多,我们的网络就能更好地识别看不见的图像中的模式。
- 我们的特征图的大小由深度(使用的过滤器数量)、步幅(滑过输入矩阵的像素数量)和零填充(在边界周围用 0 填充输入矩阵)控制。
(Source: https://www.jessicayung.com/explaining-tensorflow-code-for-a-convolutional-neural-network/)
2 —非线性:
对于任何一种强大的神经网络,它都需要包含非线性。LeNet 使用 *Sigmoid 非线性,*采用一个实数值,并将其压缩到 0 到 1 之间的范围内。特别是大负数变成 0,大正数变成 1。然而,sigmoid 非线性有几个主要缺点:(i) Sigmoid 饱和并消除梯度,(ii) Sigmoid 收敛慢,以及(iii) Sigmoid 输出不是以零为中心的。
更强大的非线性操作是 *ReLU,*代表整流线性单元。这是一个元素式操作,用 0 替换特征图中的所有负像素值。我们通过 ReLU 激活函数传递卷积层的结果。几乎所有后来开发的基于 CNN 的架构都使用 ReLU,就像我下面讨论的 AlexNet 一样。
3 —池层
在这之后,我们执行一个池操作来减少每个特征图的维度。这使我们能够减少网络中参数和计算的数量,从而控制过拟合。
CNN 使用 max-pooling ,其中它定义了一个空间邻域,并从该窗口内的矫正特征地图中获取最大元素。在池层之后,我们的网络对于输入图像中的小变换、扭曲和平移变得不变。
(Source: https://ujjwalkarn.me/2016/08/11/intuitive-explanation-convnets/)
4 —全连接层
在卷积层和池层之后,我们添加几个全连接层来包装 CNN 架构。卷积和池图层的输出表示输入影像的高级特征。FC 层使用这些特征来基于训练数据集将输入图像分类成各种类别。除了分类之外,添加 FC 层也有助于学习这些特征的非线性组合。
从更大的画面来看,CNN 架构完成 2 个主要任务:特征提取(卷积+池层)和分类(全连接层)。一般来说,我们的卷积步骤越多,我们的网络能够学习识别的复杂特征就越多。
进一步发展
从那时起,CNN 已经被改造成各种形式,用于自然语言处理、计算机视觉和语音识别的不同环境。我将在这篇文章的后面介绍一些著名的行业应用,但首先让我们讨论一下 CNN 在计算机视觉中的应用。从网上下载的彩色照片中识别真实物体比识别手写数字要复杂得多。存在数百倍的类别、数百倍的像素、三维场景的二维图像、需要分割的杂乱场景以及每个图像中的多个对象。CNN 将如何应对这些挑战?
2012 年,斯坦福大学计算机视觉小组组织了 ILSVRC-2012 竞赛 (ImageNet 大规模视觉识别挑战赛)——计算机视觉领域最大的挑战之一。它基于 ImageNet ,一个拥有大约 120 万张高分辨率训练图像的数据集。呈现的测试图像没有初始注释,并且算法将必须产生指定图像中存在什么对象的标记。从那以后,每年都有来自顶尖大学、创业公司和大公司的团队竞相在数据集上宣称最先进的性能。
第一次比赛的获胜者, Alex Krizhevsky (NIPS 2012) ,构建了一个由 Yann LeCun 首创的非常深度的卷积神经网络(称为 AlexNet) 。与 LeNet 相比,AlexNet 更深入,每层有更多的过滤器,并且还配备了堆叠卷积层。查看下面的 AlexNet 架构,您可以确定它与 LeNet 的主要区别:
- 处理和可训练层数: AlexNet 包括 5 个卷积层、3 个最大池层和 3 个全连接层。LeNet 只有 2 个卷积层、2 个最大池层和 3 个全连接层。
- ReLU 非线性: AlexNet 使用 ReLU,而 LeNet 使用 logistic sigmoid。ReLU 有助于减少 AlexNet 的训练时间,因为它比传统的逻辑 sigmoid 函数快几倍。
- dropout 的使用: AlexNet 使用 dropout 层来解决过度拟合训练数据的问题。LeNet 没有使用这样的概念。
- 多样化的数据集:【LeNet 只被训练识别手写数字,而 AlexNet 被训练处理 ImageNet 数据,这些数据在尺寸、颜色、角度、语义等方面丰富得多。
AlexNet 成为开创性的“深度”CNN,以 84.6%的准确率赢得了比赛,而第二名的模型(仍然使用 LeNet 中的传统技术,而不是深度架构),仅实现了 73.8%的准确率。
(Source: https://indoml.com/2018/03/07/student-notes-convolutional-neural-networks-cnn-introduction/)
从那时起,这个比赛已经成为引入最先进的计算机视觉模型的基准舞台。特别是,已经有许多使用深度卷积神经网络作为其主干架构的竞争模型。在 ImageNet 比赛中取得优异成绩的最热门的有:ZFNet(2013)Google net(2014)VGGNet(2014)ResNet(2015)dense net(2016)等。这些建筑一年比一年深。
应用程序
CNN 架构继续在计算机视觉中占据显著地位,架构进步为下面提到的许多应用和任务提供了速度、精度和训练方面的改进:
- 在目标检测中, CNN 是最流行的模型背后的主要架构,例如R-CNN 、快速 R-CNN 、更快 R-CNN 。在这些模型中,网络假设目标区域,然后使用 CNN 对这些区域进行分类。现在,这是许多对象检测模型的主要渠道,部署在自动驾驶汽车、智能视频监控、面部检测等领域。
- 在**目标跟踪中,**细胞神经网络已经广泛应用于视觉跟踪应用中。例如,给定一个在离线的大规模图像库中预先训练的 CNN,韩国浦项研究所团队开发的这种在线视觉跟踪算法可以学习判别显著图,以在空间和局部上可视化目标。另一个例子是 DeepTrack ,这是一种在跟踪过程中自动重新学习最有用的特征表示的解决方案,以便准确地适应外观变化、姿势和比例变化,同时防止漂移和跟踪失败。
- 在**物体识别中,**来自法国 INRIA 和 MSR 的团队开发了一个弱监督 CNN 用于物体分类,它仅依赖于图像 lvil 标签,但可以从包含多个物体的混乱场景中学习。另一个实例是 FV-CNN ,这是牛津的人为了解决纹理识别中的杂乱问题而开发的纹理描述符。
- 在语义分割中, 深度解析网络是一组来自香港的研究人员开发的基于 CNN 的网络,用于将丰富的信息融入图像分割过程。另一方面,加州大学伯克利分校的研究人员建立了全卷积网络,并在语义分割方面超过了最先进水平。最近, SegNet 是一个深度全卷积神经网络,在语义像素分割的内存和计算时间方面极其高效。
- 在**视频和图像字幕方面,**最重要的发明是加州大学伯克利分校的长期递归卷积网络,它结合了 CNN 和 RNNs(递归神经网络)来处理大规模视觉理解任务,包括活动识别、图像字幕和视频描述。YouTube 的数据科学团队已经大量部署了该工具,以理解每天上传到该平台的大量视频。
CNN 还发现了视觉之外的许多新应用,特别是自然语言处理和语音识别:
- 自然语言处理:在机器翻译领域,脸书的人工智能研究团队使用 CNN 以 9 倍于递归神经系统的速度实现了最先进的准确性。在句子分类领域,NYU 的 Yoon Kim 对基于预训练单词向量训练的 CNN 进行了实验,用于句子级分类任务,并在 7 项任务中的 4 项上进行了改进。在问题回答领域,来自滑铁卢和马里兰的一些研究人员探索了 CNN 在端到端问题回答中答案选择的有效性。他们发现 CNN 的答案明显优于以前的算法。
- 语音识别:对于自动语音识别来说,CNN 是减少频谱变化和对声学特征中的频谱相关性建模的非常有效的模型。将 CNN 与隐马尔可夫模型/高斯混合模型相结合的混合语音识别系统已经在各种基准中取得了最先进的结果。蒙特利尔大学的研究人员提出了一个用于序列标记的端到端语音框架,通过将分层 CNN 与 CTC(连接主义时间分类)相结合,与现有的基线系统相竞争。微软的团队使用 CNN 来降低语音识别性能的错误率,特别是通过建立一个具有本地连接、权重共享和池化的 CNN 架构。他们的模型能够不受说话者和环境变化的影响。
(Source: http://www.wildml.com/2015/11/understanding-convolutional-neural-networks-for-nlp/)
结论
让我们再次回顾哈利·波特图像的例子,看看我如何使用 CNN 来识别它的特征:
- 首先,我在整个原始图像上传递一个滑动窗口,并将每个结果保存为一个单独的小图片块。通过这样做,我将原始图像转换成多个大小相等的小图像块。
- 然后,我将每个图像块输入卷积层,并为同一原始图像中的每个图像块保持相同的神经网络权重。
- 接下来,我将每个图块的结果保存到一个新数组中,其排列方式与原始图像相同。
- 然后,我使用 max-pooling 来减小数组的大小。例如,我可以查看数组的每个 2 x 2 的正方形,并保留最大的数字。
- 经过下采样后,这个小数组被送入全卷积层进行预测,比如它是哈利、罗恩、赫敏、精灵、报纸、椅子等的图像。
- 经过训练,我现在有信心对自己的形象做出预测!
从这篇文章中可以看出,卷积神经网络在塑造深度学习的历史中发挥了重要作用。与大多数其他神经网络相比,受到大脑研究的极大启发,CNN 在深度学习(视觉、语言、语音)的商业应用中表现得非常好。它们已经被许多机器学习从业者用来赢得学术和行业竞赛。对 CNN 架构的研究进展如此之快:使用更少的权重/参数,自动学习和概括输入对象的特征,对图像/文本/语音中的对象位置和失真不变…毫无疑问,CNN 是最受欢迎的神经网络技术,是任何想进入深度学习领域的人必须知道的。
— —
如果你喜欢这首曲子,我希望你能按下鼓掌按钮👏这样别人可能会偶然发现它。你可以在 GitHub 上找到我自己的代码,在【https://jameskle.com/】上找到更多我的写作和项目。也可以在 推特 , 上关注我直接发邮件给我 或者 在 LinkedIn 上找我。
康威在搅拌机中的生活游戏
《生命的游戏》( GOL)可能是细胞自动机最臭名昭著的例子之一。
由数学家约翰·何顿·康威定义,它在一个二维网格上展开,每个细胞可以处于两种可能状态中的一种。从初始网格配置开始,系统在每个单元步骤中仅考虑前一个配置。如果对于每个小区,我们将周围的八个小区视为邻居,则系统转换由四个简单规则定义。
A basic plain-2D example
我对用 Blender 探索这种现象的可视化很感兴趣。以下是一些实验结果。
25x25 Cubes Grid with shrinking update function
20x20 Ico-Spheres Grid with shrinking update function
40x40 Cubes Grid with hiding update function
这里的代码如果有人感兴趣。这是一个可重用的脚本,你可以直接在 Blender 脚本界面中导入和运行。它定义了 GOL 逻辑,并将 GOL 网格到 Blender 的移植分解为两个可定制的组件:
- 生成器—负责生成将被映射到原始 GOL 网格中的单元格的 Blender 对象。一个生成器被精确地用来建立初始的混合网格,并带有首选的网格(例子:立方体,球体,猴子)。
- 更新器 —基于 GOL 网格值定义 Blender 对象的更新行为。应该根据相应的网格值是 0 还是 1 来指定对象的变化(例如:缩放、隐藏)。
剩下的只是注册更新处理程序的帮助器代码,这样一个帧的改变会导致 GOL 网格的更新(可能包括关键帧)。
我建议一旦你得到你喜欢的结果就删除处理程序,这样额外的帧变化就不会再次触发更新并破坏你的结果。
3D 版本
我还试验了一个三维网格,它使用相同的规则集,同时将邻居的数量扩展到新的维度。
我计划更深入地研究 3D 概念,以找到更稳定的配置。另一个有趣的改进是拥有不受约束的网格,这意味着从初始配置开始的自动机可以在空间中无限增长。对于这种方法,我不得不重新制定我目前的代码逻辑,可能首先用一些替代 Blender 的方法进行实验,因为这里的家伙消耗了大量的资源,即使是这些简单的渲染,所以这方面的任何建议都是非常受欢迎的!
离题
玩这些细胞自动机的可视化在我的脑海中引发了像因果关系/目的论和尘埃理论这样的概念。所有系统状态都是确定性定义的,但只能根据前一时间步的状态进行计算。有了缩放/收缩功能,系统的行为方式与其他类型的更新功能完全相同,改变的是单元相对于系统状态的行为。一个细胞在外力的作用下会立即收缩,以反映一个等于零的状态,但随后会随着自由意志的幻觉而进化到一个完整大小的新状态。实际上,这种增长只不过是关键帧填充。不是一个细胞的故事决定了它的未来,而是系统的未来决定了所有细胞生命的故事。
用机器学习烹饪:降维
最近,我在 Kaggle 上偶然发现了这个烹饪食谱数据集,它启发我将生活中的两个主要兴趣结合起来。食物和机器学习。
这个数据集的特别之处在于,它包含了 20 种不同菜系的食谱,6714 种不同的食材,但只有 26648 个样本。有些菜系的食谱比其他菜系少得多。
对于这么多的数据来说,这是太多的功能了。在处理这个数据集之前,第一步应该是减少它的维数。在这篇文章中,我将展示如何使用主成分分析将这 6714 种成分缩减到一个只有 700 维的潜在空间中。作为奖励,我们将使用这个模型作为异常探测器。
数据如下所示:
{
"id": 24717,
"cuisine": "indian",
"ingredients": [
"tumeric",
"vegetable stock",
"tomatoes",
"garam masala",
"naan",
"red lentils",
"red chili peppers",
"onions",
"spinach",
"sweet potatoes"
]
},
最具挑战性的方面是它非常稀少,这里是每个食谱的成分分类:
mean 10.76771257605471
min 1 #rice!
max 65 #complicated fettuccine recipe
std 4.428921893064523
另一种方法是查看这个方差直方图:
如果我们放大一点:
这意味着平均起来,每行 6714 个特征只有 10 个特征是活动的。少于 1%。这使得将数据分成训练集和测试集变得困难。正因为如此,你很有可能最终得到两套完全不同的食谱。解决这个问题的一个好方法是对数据进行 k 倍折叠,但不是在这种情况下。数据太稀疏,不会有太大改善。
对于具有如此多特征的稀疏数据集,第一步通常是减少维数。你没听说过维度诅咒吗?
在这篇文章中,我将使用一种非常流行的方法来降低维度:PCA
转换数据
该忙起来了!让我们对数据做一些基本的转换。这里的主要思想是,因为我们有定性的数据,我们需要做一些被称为一次性编码的事情。长话短说:6714 食材-> 6714 栏目。当配方中有一种配料时,它的列将变为 1。其余的都是 0。平均而言,每行中只有 10 列是“活动的”。这段代码将创建“transformer ”,它将获得一种配料并输出其矢量表示
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
from numpy import array
import jsonf = open('recipes_train.json', 'r')
recipes_train_txt = f.read()
recipes_train_json = json.loads(recipes_train_txt)#get list of ingredients
ingredients = set()
ingredients_matrix = []for recipe in recipes_train_json: ingredients_matrix.append(recipe["ingredients"])
for ingred in recipe["ingredients"]: ingredients.add(ingred)ingredients = list(ingredients)ingredients.sort() #it made my life easier to have it sorted when i needed to check what is what in the encoded vectorvalues = array(ingredients) label_encoder = LabelEncoder()#gives a unique int value for each string ingredient, and saves the #mapping. you need that for the encoder. something like:
#['banana'] -> [1]
integer_encoded = label_encoder.fit_transform(values)
onehot_encoder = OneHotEncoder(sparse=False)
integer_encoded = integer_encoded.reshape(len(integer_encoded), 1)#here you encode something like : [2] -> [0,1,0,0,...]
onehot_encoded = onehot_encoder.fit_transform(integer_encoded)def transform_value(s):
l = array([s])
integer_encoded = label_encoder.transform(l)
integer_encoded = integer_encoded.reshape(len(integer_encoded), 1)
onehot_encoded = onehot_encoder.transform(integer_encoded)
return onehot_encoded[0]
这段代码为我们提供了一个编码器,它将获得一个成分(字符串)作为输入,并输出其向量表示。包含所有配方成分的最终向量将是对这些成分向量中的每一个进行“逻辑或”运算的结果
主成分分析
PCA 是一个非常受欢迎的选择。它在输入新的简化数据集以使用 t-sne 可视化之前用作预处理工具,但也是您可能希望在输入机器学习算法之前用来简化您的特征的工具。
这是可取的,因为您拥有的功能越多,需要的数据就越多,学习过程就越慢。所以,把事情做小将会提升你的表现。
但是在最小化数据之前,你需要打一个电话:你想要多小?这里有一个权衡,你去的越小,你丢失的信息就越多。您可以通过使用与最小化数据相同的训练模型来测量,然后最大化回原始大小。之后,您可以比较这两个样本,并测量它们之间的差异(记住,当您下降时,您会丢失信息)。
所以,让我们只是训练一堆不同的模型,并选择一个具有很少特征但重建误差低的模型。
X 轴是分量向量的数量,而 Y 轴是整个样本的重建误差(使用 L2)。那些结果看起来不错,对吗?但是让我们在这里深入挖掘一下。700 似乎是一个安全的数字,在这个区域周围没有太大的改善。这已经是对 6714 功能的巨大改进。我们来对比一些看不见的数据,测试集。
这里我们可以看到,主成分分析在概括数据结构方面做得不错。训练均方误差~ = 0.000171%测试均方误差~= 0.0002431% 。还不错。
我们之前知道,数据平均有 10 种成分,标准偏差为 4.42。如果我们用这个分布创造一些‘随机食谱’(随机挑选配料)会怎么样?这可以使用高斯发生器来实现。如果 PCA 学到了什么,我们应该看到一些主要的重建错误。
for n_candidates in range(N_CANDIDATES):
dna = [0 for i in range(N_INGREDIENTS)]
n_flips = int(round(random.gauss(mu=MEAN, sigma=STD)))
indexes_to_flip = [ random.randint(0, N_INGREDIENTS-1 ) for i in range(n_flips) ] for i in range(n_flips):
dna[ indexes_to_flip[i] ] = 1 BAD_DATA.append(dna)
让我们看看模型如何处理这些假数据。
这不是最初的目标,但看起来我们有一个很好的模型来检测异常配方。如果我们将阈值设置为 0.0004,并且将重构误差大于该阈值的任何事物视为异常,则我们得到以下矩阵:
结论
我们将这个数据集从 6714 个特征减少到只有 700 个。
我们现在可以得出结论,模型确实从训练集中学到了一些东西。我们试图欺骗 PCA 模型,我们了解到一些成分通常会在一起,而一些不会混合。您也可以使用这个模型作为异常检测,其中糟糕的食谱是异常的(您不应该吃那些!).也许作为一个后续项目,我可以尝试利用这种“学习”。
正如你所看到的,不同的菜系之间有一种模式。我们已经有了一个模型来检测不属于任何一种模式的异常配方,生成新配方会有多难?有可能创造新的法国菜吗?
使用 Kaggle 数据集和内核设计一个数据科学项目
在《人工智能历险记》的这一集里,我邀请了 Kaggle 数据集的产品负责人 Megan Risdal 为我们介绍一下 Kaggle 内核和 Kaggle 数据集的一些最新功能,并展示一些在 Kaggle 内核上合作的方式。你可以在下面观看我们在卡格尔探险的视频。
在《人工智能历险记》的这一集里,你会发现什么是 Kaggle 内核,以及如何开始使用它们。虽然没有…
towardsdatascience.com](/introduction-to-kaggle-kernels-2ad754ebf77)
我们将一起工作,使用最新鲜的成分(数据),使用不同的工具准备它们,并一起工作,拿出一个美味的结果——一个发布的数据集和一些很酷的分析,我们将与世界分享。
使用数据集和内核
我们将从洛杉矶市的开放数据门户下载公共数据,包括洛杉矶餐馆违反环境卫生的情况。然后,我们将使用这些数据创建一个新的数据集,并在向全世界发布之前,共同开发一个内核。
在这一集里,你将学到:
- 如何从原始数据中创建一个新的、私有的Kaggle 数据集
- 如何在公开数据集之前与合作者共享数据集
- 如何在 Kaggle 内核上使用 collaborate 通过向私有内核添加协作者
- 如何正确地注释和配置您的 Kaggle 数据集,以便其他人可以轻松地发现和贡献它
当数据与可再生代码以及专家和学习者社区共享时,它是最强大的。通过将数据和代码放在一个共享的、一致的平台上,您可以与最好的高效能笔记本电脑进行最佳协作。
资源
既然你已经看到了如何制作一个数据集和内核,现在是你自己制作的时候了!前往 Kaggle 立即开始制作新数据集。
数据集:https://www . ka ggle . com/Megan risdal/exploring-la-county-health-code-violations-by-date
我们的内核:https://www . ka ggle . com/Megan risdal/exploring-la-county-health-code-violations-by-date
Megan Risdal 是 Kaggle 数据集的产品负责人,这意味着她与工程师、设计师和拥有 170 万数据科学家的 Kaggle 社区合作,构建用于查找、共享和分析数据的工具。她希望 Kaggle 成为人们分享和合作数据科学项目的最佳场所。
感谢阅读这一集的云人工智能冒险。如果你喜欢这个系列,请为这篇文章鼓掌让我知道。如果你想要更多的机器学习动作,一定要关注媒体上的我或订阅 YouTube 频道以观看未来的剧集。更多剧集即将推出!
用计算机视觉烹饪
为什么机器人给我们做饭要花这么长时间?
机器人和人工智能最有趣的应用之一,(即计算机视觉),是在厨房里!有许多初创公司正在研究复杂的机器人厨师,并使用机器人来自动化烹饪过程,例如制作汉堡:
机器人烹饪的潜力远远超出了汉堡,在这篇文章中,我将讨论如何使用计算机视觉来自动化烹饪牛排、鸡肉和鱼肉等日常肉类的过程。本文中介绍的算法可以很容易地在用刮刀捆绑的 raspberry pi 相机中实现。
烹饪牛排是一项相当简单的任务,而且大多需要远见才能完成。只要我们的机器人厨师有一个摄像头和一个翻转和取出牛排的机械装置,(就像一把锅铲),就可以走了。这里有一个简单的算法来说明机器人厨师应该如何烹饪牛排。
该算法的主要概念是,它将使用图像识别模型来处理来自摄像机的视频馈送中的图像。随着时间的推移,我们将利用卷积神经网络的惊人能力来处理牛排。有人可能会反驳说,你可以简单地用牛排的颜色直方图,随着时间的推移,从生的(红色),到熟的(棕色),进行比较。但是,在牛排上放调料、光线不好等情况下,CNN 会概括得更好。
这些图像不需要实时处理(或类似 1/30 帧/秒),因为这不是一个真正的时间关键任务。为了节省计算资源和加速算法,我们将每隔 6 帧处理一次,以检查牛排的状态。此外,我们将在环路中支持更多烹饪牛排所需的基础设施。
初始配置 :建立一个图像分类器,将牛排从‘生的’,映射到‘生的’,‘三分熟的’,‘中等的’,‘五分熟的’,‘全熟的’。由于这些类遵循逻辑顺序,我们将只从 0 到 5 映射牛排。
**类别不平衡:**在具有罕见实例的数据集上训练分类器所涉及的一个常见问题是类别不平衡。例如,在为检测海洋中的鲸鱼鳍之类的事情构建图像分类器时,经常会讨论到这一点。由于大部分时间我们的牛排被分类为生的,我们将需要确保我们相应地评估我们的分类器的性能指标。
一旦分类器达到约 95%的准确率,将牛排分类为生的或熟的,以 Pythonic 风格编写伪代码
def cookSteaks(target):
timer = 0 # used to flip the steaks arbitrarily
side = True # flag used to label sides of the steak as cooked
side0Finished = False
side1Finished = False
Finished = False
seq = 0 # require 5 consecutive classifications to finishProcess every 6 frames
Classify frame
if (frame == target): # target indicates ['rare', ..' well-done']
seq += 1
if (seq == 5):
if (side):
side0Finished = True
if (side1Finished):
Finished = True
exit;
else:
seq = 0
flip()
else:
....
...
timer += 1
if (timer == 120):
flip()
side = !side
timer = 0
在没有完成伪代码的情况下,我认为算法背后的逻辑已经很清楚了。
我们在这里运行 4 个主要部分:
- 使用图像识别模型来处理来自视频流的输入帧(不一定需要具有该任务的实时能力)。
- 由于分类模型中的一些错误,需要连续的分类来触发响应,(如果没有大量的训练数据,95%的准确率将被认为是二进制分类任务的一个相当好的模型),我们不能让机器人每次都用分类器来翻动牛排。在这个例子中,我们尝试使用 5 作为翻转的阈值,但是这可能太低了。
- 标志框住任务,使用布尔标志引导循环,确保两面都被烹饪,并在烹饪过程中保持翻转。
- **任意翻转的计时器,**我们希望每隔 2 分钟左右任意翻转一次牛排,以确保我们不会只煎一面,然后翻转另一面,然后从头到尾煎完。我相信专业厨师有比这更好的策略,但这足以解决我们的问题
收集用于训练分类器的数据
我们将输入一个由这样的图像组成的数据集。基于观察平底锅的视角标记图像。
总之,我们看到烹饪肉类是一个相当简单的任务,不需要革命性的算法来完成。建立在卷积神经网络上的图像识别模型非常强大,可以在视频上使用,以实现令人惊叹的事情。我对机器人烹饪的未来感到非常兴奋,并看到这将如何改变食品市场,感谢阅读!
CShorten
Connor Shorten 是佛罗里达大西洋大学计算机科学专业的学生。对数据科学、深度学习和软件工程感兴趣。主要用 Python,JavaScript,C++编码。请关注更多关于这些主题的文章。
用机器学习烹饪:自动编码器
这是关于在烹饪食谱上使用机器学习的机器学习系列文章的一部分。如果你错过了第一篇文章,请点击 这里
前情提要关于 PCA
在上一篇文章中,我们探讨了如何使用 PCA 来降低数据集的维数。此外,由于 PCA 通过探索变量之间的“线性”协方差来转换数据,因此它还可以用作异常检测器。因为任何不遵循初始数据集“结构”的配方都不会很好地转化。在我们的情况下,这意味着这是一个糟糕的食谱!更多信息请点击此处
如果 PCA 工作正常,为什么要使用 autoencoder? 在本文中,我们将通过使用自动编码器来击败 PCA。我们将压缩和解压缩数据,就像我们对 PCA 所做的那样,但我们的自动编码器不会受到“线性”变换的限制,这使得它更加强大。
什么是自动编码器?
简而言之,自动编码器是一种神经网络架构。主元件是中间的瓶颈,基于输入计算误差。所以你通过一个瓶颈挤压数据,并试图在另一端重建相同的输入。没错,没有标签或值被预测。你在努力找回你的输入。如果你成功了,你可以把它作为一个变压器来降低你的维度。您只需要捕获瓶颈层中的特性。
普通自动编码器 这是我们自动编码器的第一次迭代:
num_input = len(X_Total[0])
num_hidden_l = 700X = tf.placeholder(“float”, [None, num_input])w_encoder_h1 = tf.Variable(tf.random_normal([num_input, num_hidden_l])w_decoder_h2 = tf.Variable(tf.random_normal([num_hidden_l, num_input])) encoder_b1 = tf.Variable(tf.random_normal([num_hidden_l]))
decoder_b2 = tf.Variable(tf.random_normal([num_input])) layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(X, w_encoder_h1),
encoder_b1))
layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, w_decoder_h2),
decoder_b2))# Prediction
y_pred = layer_2
# Targets (Labels) are the input data.
y_true = X# Define loss and optimizer, minimize the squared error
loss = tf.reduce_mean(tf.pow(y_true - y_pred, 2))
optimizer = tf.train.RMSPropOptimizer(learning_rate).minimize(loss)
一切都很标准。如果你花一些时间谷歌一下 autoencoder 的例子,你应该会遇到一些非常相似的东西。我到处都在用 sigmoid 作为激活函数,损失函数就是均方差。最后,RMSPropOptimizer 只是一个花哨的梯度下降。最终结果应该类似于下图:
Input = 6714 , Hidden layer = 700, Output = 6714
为什么这不起作用?
如果你尝试运行那个代码,你会发现你打不过 PCA。这是因为我们的数据集和模型的性质。你会卡在~= 0.024113%的重建误差,听起来不错,其实不然。主成分分析在测试集中达到了 ~= 0.0002431%。让我们来解决这个问题。
问题 1:消失渐变 首先,观察 sigmoid 曲线是什么样子的:
请记住,我们的数据非常少,有 6714 列(成分),大多数时候每个配方只有 10 种成分。这意味着我们的网络正在学习没有一种成分是真正“重要”的,它将所有成分的权重值降低到非常接近 0。想想看,你给我 6714 种成分,我完全忽略你给我的一切,我还给你另外 10 种随机成分。我错过了所有的 1,但与此同时,我答对了所有成千上万的 0。
6714 - 20 misses -> 0.0029% error.
当使用 sigmoid 作为激活函数时,这就产生了问题,因为它在零附近开始变得非常平坦。这意味着学习非常缓慢,甚至没有学习。这也是这个激活功能正在失宠的原因之一,但它仍然有一些非常好的用例。因为它的输出总是落在 0 和 1 之间,所以如果你想输出概率,它是很棒的。
为了解决卡在零附近的问题,我们将把激活函数改为“泄漏 ReLU”。
这里的主要区别是,在零点以下和零点之后,你有一个恒定的斜率。这将会给我们一些腿来左右移动,不管重量有多小。现在发表的大多数现代机器学习论文都在使用 ReLU 激活函数的变体,比如 leaky ReLU。
问题 2:这是正确的误差函数吗?
使用中值平方误差的问题是,无论你做了什么错误的预测,所有这些 0 都会冲淡。因为你总会有一堆 0 的对。另一个问题是 sigmoid 返回十进制数,我们需要 0 或 1。并且,最终误差是根据成分向量的“总和”计算的。因此,任何两个加起来为 10 的食谱看起来都是一样的,即使一个食谱有 20 种成分,每种成分的价值为 0.5,看起来也是一样的。这个误差函数更适合于回归问题,这里我们有一个多标签分类问题。
解决办法就是改成‘交叉熵’。这将集中在“预测的标签”(1),而不是整个事情。成千上万的 0 不会再淡化错误。更具体地说,我们将使用“sigmoid _ cross _ entropy _ with _ logits”。以下是 tensorflow 文档中的描述:
测量离散分类任务中的概率误差,其中每个类别都是独立的且不互斥。例如,可以进行多标签分类,一张图片可以同时包含一头大象和一只狗
用我自己的话说,这是测量预测向量和标签向量之间的距离。之前,我们只是将所有值相加,然后将预测向量和与标签向量和进行比较。
问题 3 sigmoid 和 leaky ReLU 都输出十进制数。虽然 sigmoid 有界在 0 和 1 之间,但我们当前的选择是无界的。对于我们的问题来说,0.32 番茄这样的东西没有意义。你要么有西红柿,要么没有。因此,尽管我们将使用交叉熵来训练我们的模型,但是每当我们使用我们的模型时,我们将对输出应用最终变换:
correct_prediction = tf.equal(tf.round(y_pred), y_true)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
round 函数将所有数字舍入到最接近的整数,稍后我们比较两个向量的所有索引。最后,我们得到一个“重建误差”并存储在精度变量中。
更新解决方案
num_input = len(X_Total[0])
num_hidden_l = 700X = tf.placeholder(“float”, [None, num_input])w_encoder_h1 = tf.Variable(tf.random_normal([num_input, num_hidden_l])w_decoder_h2 = tf.Variable(tf.random_normal([num_hidden_l, num_input]))encoder_b1 = tf.Variable(tf.random_normal([num_hidden_l]))
decoder_b2 = tf.Variable(tf.random_normal([num_input]))layer_1 = tf.nn.leaky_relu(tf.add(tf.matmul(X, w_encoder_h1),
encoder_b1))
layer_2 = tf.nn.leaky_relu(tf.add(tf.matmul(layer_1, w_decoder_h2),
decoder_b2))# Prediction
y_pred = layer_2
# Targets (Labels) are the input data.
y_true = X# Define loss and optimizer, minimize the squared error
loss_entropy = tf.nn.sigmoid_cross_entropy_with_logits (logits=y_logit,labels= y_true)loss = tf.reduce_mean(loss_entropy)
optimizer = tf.train.RMSPropOptimizer(learning_rate).minimize(loss)
有了这个更新的模型,我们可以很容易地击败 PCA 的重建误差。大约 15 个历元之后,代码大小为 700(如果您在此处 *,*查看以前的帖子,您会发现这是我们在开始丢失太多数据之前所能得到的最小值)我们在测试集中得到 0.000003676%的重构**误差,而 PCA 模型得到 0.0002431%。**我们已经有了 100 倍的改进,但我们不必局限于 700 项功能。如果我们将代码大小更改为 200,我们的误差在测试集中上升到只有 0.000008964%。这仍然是 100 倍的改进,但是使用的功能更少了。我们使用这 200 个特征的任何模型都将比使用 700 个特征的模型训练得更快。
结论 虽然 PCA 方便多了,但使用 autoencoder 最终还是有回报的。我们不费吹灰之力就将 6714 个特性减少到了 200 个。我们简单地实现了 autoencoder,发现了一些问题,并根据我们的模型需求进行了调整。我相信这不是极限,通过增加几个隐藏层,并使用最先进的深度学习技巧,我们可能会变得更低。我们的下一步将是在另一个模型中使用这 200 个特性。
我发现的很酷的数据集
在尝试为我正在进行的编码训练营做铺垫时,我发现了一些很酷的数据集,我想我应该分享一下。
每个人都应该注册由杰里米·辛格·万撰写的《数据是复数》时事通讯。他每周三发出 5 个很酷的数据集。主页上有一个电子表格,里面有所有过去的数据集,它们太酷了。
[## 数据是复数杰里米·辛格·文
编辑描述
tinyletter.com](http://tinyletter.com/data-is-plural)
有一个 github 叫做 awesome public data sets,它有很多不同主题的资源。
awesome-public-datasets --公共领域高质量开放数据集的 awesome 列表(正在进行中)。由每个人,为…
github.com](https://github.com/caesar0301/awesome-public-datasets)
由于 kaggle 使用公共数据举办了许多比赛,他们拥有世界上所有事物的大量数据集
[## 数据集| Kaggle
编辑描述
www.kaggle.com](https://www.kaggle.com/datasets)
如果您正在处理大数据,并且需要一些帮助来开始实践,Amazon Web Services 有几个可供下载。
AWS 公共数据集允许任何人从集中的数据存储库中访问大型数据集,并使用…
aws.amazon.com](https://aws.amazon.com/public-datasets/)
KDNuggets 有一个全面的数据集和门户列表,您可以从中获取更多数据。
另见资产宏观、宏观经济指标的历史数据和市场数据。github 上令人惊叹的公共数据集…
www.kdnuggets.com](http://www.kdnuggets.com/datasets/index.html)
如果你在寻找州数据集,大多数州都有一个开放的数据门户,其中一些要比其他的好得多,但它们都很容易谷歌。以下是我找到的一些例子:
- https://opendata.cityofnewyork.us/
- http://opendata.dc.gov/
- 【https://data.virginia.gov/
- https://www.data.gov/open-gov/
CoQA:一个会话式问答挑战
我们能教聊天机器人进行推理,并与我们进行对话和信息检索吗?
对于自然语言处理来说,这是非常棒的一周,我真的很兴奋(你也应该如此)!本周,我像往常一样偶然浏览了 Arxiv Sanity Preserver 网站,但我看到的不是一大堆 GAN(这是美国有线电视新闻网)或 CNN(那是美国有线电视新闻网)的报道,而是两个让我非常高兴的数据集。他们是 CoQA 和 QuAC ,在这篇博客中我们将谈论 CoQA 为什么如此令人兴奋。
如果你对更多的问题回答感兴趣,我也在这里谈论 QuAC!
对话式问答挑战
“对话式问答挑战”到底意味着什么?这不仅需要阅读一篇文章来寻找答案,还需要就这些信息进行对话。它需要在对话中使用上下文线索来发现对方在问什么。它要求信息以一种抽象而不是抽象的方式重新表述。
哇哦。
这个数据集不同于我们在问答中见过的任何东西!
动机
那么我们为什么需要 CoQA 这样的数据集呢?
很简单,因为我们当前的数据集并没有为我们做这些。人类通过相互交流、提问、理解上下文和对信息进行推理来学习。聊天机器人…嗯,聊天机器人不会这样做。至少现在不是。结果是,虚拟代理缺乏,聊天机器人看起来很愚蠢(大多数时候)。
CoQA 是作为一个数据集创建的,以帮助我们衡量算法参与问答对话的能力。本质上,是为了衡量一台机器的对话能力。
在他们的论文中,CoQA 的创建有三个目标:
- 1.创建以依赖于对话历史记录的问答对为特征的数据集
- CoQA 是第一个大规模这样做的数据集!
- 在对话中的第一个问题之后,每个问题都依赖于所说内容的历史
- 有 127,000 个问题跨越 8,000 个对话!
- 2.创建一个寻找自然答案的数据集
- 许多问答数据集是提取的,搜索词汇相似的回答,并直接从文本中提取出来
- CoQA 希望从文章中提取基本原理,但要有一个重新措辞的、抽象的答案
- 3.为了确保问答系统在不同的领域中表现良好
- CoQA 有一个包含 5 个领域的训练集和一个包含 7 个领域的测试集(2 个从未在训练中出现过!)
虽然人类绩效的 F1 值为 88.8%,但表现最佳的模型的 F1 值仅为 65.1%。这是一个很大的差距,机器必须要做大量的追赶工作!
创建 CoQA
像这样的数据集是如何创建的?
在亚马逊土耳其机械公司(Amazon Mechanical Turk)的帮助下,这个市场为需要人类智慧的人提供工作,比如给数据集贴标签。两名 AMT 员工将被配对,一人提问,另一人回答。回答者会被要求在文章中突出给出答案的文本部分,然后用不同于文章中给出的词来回答(从而变得抽象)。
所有这些都是在两个工人的对话中完成的。似乎下面的例子在论文中得到强调:
文章的范围是什么
段落来自 7 个域,5 个在训练集中,2 个为测试集保留。它们是:
- 来自的儿童故事 MCTest
- 来自古腾堡项目的文献
- 初高中英语考试从赛
- CNN 的新闻文章
- 来自维基百科的文章
- 来自 AI2 科学问题的科学文章(仅测试集)
- 来自写作提示数据集的 Reddit 文章(仅测试集)
他们还特意收集了问题的多个答案,因为问题被重新措辞,这有助于对话代理有更多的机会获得更好的分数,并实际上找到正确的答案。
与其他数据集相比
在这之前我们有什么?
嗯…我们有班,斯坦福问答数据集。1.0 版本在维基百科上使用了 10 万多个问题,要求模型在回答问题时提取文章的正确部分。当决定这还不够时,我们得到了版本 2.0 。这给 SQuAD 增加了 50,000 多个无法回答的问题,现在要求算法不仅要找到正确的答案,还要推理答案是否存在于文章中。
这个任务还没有解决。
但是它也不能解决提取信息和理解对话中所要求的内容的问题。这就是 CoQA 成立的原因,正如它的第一个目标所概述的那样!
更多分析
CoQA 的创建者对小队和 CoQA 进行了分析,发现虽然大约一半的小队问题是“什么”问题,但 CoQA 的问题类型分布更广泛、更均衡。他们给了我们这个很酷的图片来描述:
让 CoQA 更难的是,有时它只有一个词的问题,比如“谁?”或者“哪里?”甚至“为什么?”
这些完全取决于上下文!作为人类,我们甚至不能在不知道他们被问到的背景的情况下开始尝试回答这些问题!
语言上的挑战
以防你还没拼起来,CoQA 非常非常难。它充满了被称为共指的东西,可以是像代词一样简单的东西,但通常是当两个或更多的表达指同一件事(因此共指!).
这在 NLP(共指解析、代词解析等)中仍然是一个公开的问题。),所以将这一步合并到问答数据集中肯定会增加一些难度。
CoQA 中大约一半的问题包含显式的共指(像他、它、她、那个这样的一些指示)。近五分之一的问题包含隐含的共同参照。这是当你问一个类似“在哪里?”的问题时我们要求解决一些事情,但这是隐含的。这对机器来说很难。见鬼,有时候对人们来说甚至很难!
现在是挑战!
当前的模式如何应对这一挑战?答案是不好。
自己看:
CoQA 会帮助我们回答 ImageNet 对图像识别做了什么吗?只有时间会告诉我们,但事情肯定是令人兴奋的!
所以现在对我们所有人来说是一个挑战(就像我和 QuAC 摆的姿势一样)!我们都可以努力尝试解决这一挑战,将对话代理带入下一个发展阶段。CoQA 有一个排行榜,目前是空的!我们有责任走出去,努力应对这一挑战,努力解决这一问题,推动知识向前发展,也许还能登上排行榜,获得一点名气和一点骄傲;)
我们在那里见!
如果你喜欢这篇文章或者觉得它有任何帮助,为什么不递给我一两美元来资助我的机器学习教育和研究呢!每一美元都让我离成功更近一步,我永远心存感激。
珊瑚城市:Ito 设计实验室概念
European Cities — Visualised as Corals
在过去的六个月里,我一直着迷于让城市网络看起来像活珊瑚的概念。城市形态的变化模式是由其道路网络决定的;一个复杂的,看似有机的联系,让人们穿越他们的城市。就像珊瑚的枝干一样,它们有一种模式和功能,我选择展示这种模式,并对其进行处理,使之变得更加概念化。然而,虽然它们美得令人难以置信,但它们是从行驶时间集水区的各种地理空间分析中得出的,这也使它们具有一定的信息量。
Top 42 places to live (Mercer Livability Index)
项目:
Kantar 信息很美大奖 2018。
在过去的 6 个月里,我已经制作了如此多的珊瑚,我认为现在是时候开始最大的应用了;绘制 40 个最宜居城市的地图,进行分析并制作动画。我觉得这个概念和视觉效果很强,但是城市列表需要有相关性,不要像我早期的一些“概念”项目那样随意。我决定使用与经济学人信息部类似的宜居指数,但通过一家名为 Mercer 的公司,该公司每年发布一次排名(https://mobility exchange . Mercer . com/Insights/quality-of-living-rankings)。经美世允许使用他们的名单,我们创建了以下内容提交给奖项。
是什么造就了一座伟大的城市?是政治稳定吗?低犯罪率?获得教育或医疗保健?我们采用了一个衡量标准,看看人们在城市内移动有多容易。我们计算了你在 30 分钟内可以从每个市中心(开车)走多远。由此产生的“珊瑚结构”以一种新的方式展示了运输数据,揭示了美丽的有机形式。每条线代表一个城市的动脉,代表一条从市中心出发的可能路线。
我们将这一技术应用于美世生活质量城市排名中的前 40 个城市。其结果是对我们如何在世界上最大的一些城市走动有了一个独特的视角。
Hi-resolution A0 poster
Printed, mounted and framed!
Close-up of detail
4k animated version
A0 海报印在墙上看起来棒极了!4k 动画版本缓慢地迭代通过一个不断增长的城市网格 1,12,20,最后是 40。我确实想把这个扩展到 80 度,但是 40 度之后,珊瑚的细节就消失了,除非你有一个巨大的屏幕。
英国珊瑚城市:
这是我第一次尝试用小倍数的方法来完成这个项目,并分析了英国不同城市的 30 分钟车程集水区。行驶时间分析基于对零交通流量的乐观看法,因此呈现的集水区比您预期的要宽得多。
高分辨率静态图像以清晰的模式显示了地层的复杂性,其中河流、海洋和山脉等物理特征影响着网络。我还制作了一个 4k 的动画版本,在上面的 Vimeo 链接中可以看到,动画在视频结束时逐渐变慢。
欧洲版:
此后不久,我着手制作一个地理上更大的版本,分析一些主要的欧洲城市。
这种风格从最初的蓝色网格基础略微演变成一种浅色调色板,环境遮挡和阴影更加明显。“珊瑚城市”的概念传到了德国地图学杂志“Kartographische Nachrichten”的好人们手中(http://www . Kartographische-Nachrichten . de/Kartographische-Nachrichten/aktuelles-heft . html),他们渴望在最新杂志的封面上刊登欧洲版本。浅色的调色板看起来很可爱…
经济学人智库:宜居指数
上个月,EIU 发布了 2018 年宜居指数,对所有主要城市的宜居指数进行了排名(【http://www.eiu.com/topic/liveability】T2)。我们认为这将是一个伟大的想法,制作一个“珊瑚城”版本的十大名单,当然还有一个动画版本。
它们是怎么做的?
让我们解释一下它们是如何计算的,以及它们显示了什么,为了做到这一点,让我们更仔细地看看我最喜欢的珊瑚之一——东京。
Tokyo City — 30 minute catchment
我从东京市及其周边地区的基本 OpenStreetMap 网络开始。使用各种方法,我可以计算从中心到指定行驶时间的集水区。在伊藤世界(www.itoworld.com),我们有一个内部路由系统允许我们这样做,但是,有一堆其他的方法和软件可以让你计算驾驶时间(ESRI,PgRouting,GraphHopper,Here API 等)。值得注意的是,在大多数情况下,网络集水区是以零流量计算的,因此结果非常乐观。然而,我们已经尝试从 Here API 导入基于拥塞的等时线,并将其转换为珊瑚,这相当成功(见下文)
Comparing congestion catchments in the worst/best cities using data from Here API.
汇水区分析将给出一个粗略的区域,然后我需要把它变成珊瑚,为此,线性连接上的每个顶点都给出一个距离市中心的距离值。我可以使用这个距离度量作为链接的高度值,创建一个 3d 网络,随着与中心的距离增加而增加。
我也将一个“权重”值应用于链接,这与道路分类无关,但是它经常被链接。权重与连接到分支的链接数量成比例,因此较厚的网络有更多连接的主干网络,从而创建珊瑚状外观,这也表明路由网络的较繁忙部分。
然后,每个链接都经历了一个矩形几何体的“扫描”,创建了一个物理存在,我们可以在渲染过程中应用环境遮挡。
颜色渐变纯粹是一种美学添加,只是从中心映射渐变。动画链接出现在距离的顺序导致迷人的珊瑚生长…
UK Cities — Coral Animation
定制版本
我的妻子通常对我的工作不感兴趣:)然而,她对珊瑚城的海报很感兴趣,以至于她委托我(报酬是无尽的茶和咖啡!)为房子生产一个。作为一个历史极客,主题是“历史上重要的珊瑚城市”我们仍在考虑框架选择,但下面是海报的截图。
Cinema4d/Blender 渲染:
在整个过程中,我对将我们在伊藤世界的可视化套件中的分析移植到其他 3d 软件包(如 Cinema4d)的可能性越来越感兴趣。这很棘手…我们的分析工具分析网络的每个链接,节点到节点的链接用每个节点的高度值分割。然后,该值被放入具有 z-min 和 z-max 属性的着色器中,该属性创建链接的高度。类似的方法也用于连接厚度。所以将它导入其他包并不简单,会产生大量的 CSV 文件。我确实为 Cinema4D 开发了一种方法,但是这个过程非常耗时,但是确实产生了一些可爱的渲染效果。
Cinema4d 有一个非常方便的“结构”标签,你可以通过 x,y,z 坐标导入线性路径。顺带一提,某天才创建了一个可以批量导入链接的脚本(https://www . C4 dcafe . com/ipb/forums/topic/91550-create-spline-from-CSV-file/?page=2 )这意味着只要我的每个链接都是一个单独的 CSV 文件,我就可以使用该脚本在 Cinema4d 中重建样条几何图形。不利的一面是每个珊瑚都有成千上万的链接,所以大量的 csv
一旦我所有的链接被导入,我就面临一个新的问题。每个单独的链接都是一条单独的样条线,尽管它们与下一条样条线重叠,但实际上它们并不相连。当“扫描”样条线时,这就成了问题,你最终得到的不是漂亮的 3d 动脉,而是成百上千的小动脉连接在一起,这是 a)资源猪 b)看起来很丑。治愈?“快速花键连接器”,我的救命恩人(https://cgtools.com/fast-spline-connector/)物有所值。使用该工具意味着,只要将阈值设置得较低,任何重叠的样条线都可以连接到更长的样条线。因此,这是一个相当漫长的过程,毫无疑问可以简化,但无论如何,我在最终导入的城市中获得了很多乐趣。
Early concepts for render lighting setups.
Frankfurt light test render with baseline OSM on the base.
Frankfurt dark test render with baseline OSM on the base.
Cinema4d HDRi lighting render.
我还实验了一些相当有问题的渲染…
Jade Coral City?
Inverted Coral City.
Some odd volumetric lighting tests…
Early idea of plinth renders.
Early attempt at Corals in water.
接下来是什么?
我喜欢 Kantar 条目打印出来的样子,我热衷于探索一些创作定制作品的选项,已经为朋友和家人创作了几个版本,但是我不确定这在更大范围内如何工作。我们内部也在讨论 3d 打印一些更令人印象深刻的城市。谁知道呢,到了圣诞节,你可能会在约翰·刘易斯的货架上看到“珊瑚城的小玩意”
面向 iOS 开发者的核心机器学习
在 WWDC 2017 上,苹果为开发者推出了一种简单的方法,用Core ML 为他们的 iOS 应用添加 AI 功能。这可以用于各种领域,如对象检测、情感分析、手写识别、音乐标记等。所有这些都可以集成到一个名为 Core ML Model 的文件和几行代码中。😏
什么是核心 ML 模型⁉
它是将训练好的模型转换成 Apple 格式的模型文件(。mlmodel),它可以添加到您的项目中。
什么是训练有素的模特‼︎⁉︎
它是由机器的训练过程产生的产物。训练模型的过程包括向学习算法提供训练数据以供学习。
因此,由于核心 ML 模型是关键,我选择使用苹果公司提供的模型,而不是自己转换一个经过训练的模型。目前有 4 个选项可用: Places205-GoogLeNet,ResNet50,Inception v3,VGG16。
演示时间
要运行演示应用程序或使用 CoreML,你需要获得 Xcode9 测试版(振作起来,这是真正的测试版😖).
该应用程序将允许用户从照片库中选择一张照片,然后智能模型将推断出主要对象。
打开 Xcode 并创建一个新的单个应用程序视图项目。转到你的 Main.storyboard 并为应用程序设置你想要的 UI。我选择了一个简单的 UI,一个按钮打开图库,一个图像视图显示选中的图像,一个文本视图显示预测结果。
然后将按钮、图像视图和文本视图挂接到视图控制器。设置一个UIImagePickerController aafter wards,及其委托方法diddfinishpickingmediwithinfo并从信息字典中检索图像。
此时,您的项目应该可以运行了。您可以选择一个图像并在图像视图中查看它。提示:你可以在模拟器上打开 safari 并保存一些图像,在你的图库中创建一些变化。
添加核心 ML
现在到了激动人心的部分:让我们从 苹果 下载实际的模型文件。我选择了Inceptionv3型号因为它功能强大并且文件大小合理。
一旦你得到了。mlmodel 文件,将其拖放到您的项目中。确保将其添加到您的应用目标**(如果您有测试目标,不要将其添加到测试目标,因为这不会生成模型类)。然后从项目导航器中选择文件。**
你可以看到一些与模型相关的元数据。但最重要的是,您可以看到自动生成的模型类**,它将在我们的代码中使用。我们还知道该模型将一个图像作为输入,将一个字典和字符串作为输出。**
CoreML & Vision
为了在我们的代码中使用智能,我们必须导入 CoreML 和 Vision。CoreML 被导入,所以我们可以在我们的视图控制器中使用自动生成的模型类**。由于图像输入属于 CVPixelBuffer 类型, Vision 将通过给它一个用作输入的 CGImage 来使我们的生活变得更容易。**
Data types used to interact with the mlmodel
值得注意的是,视觉与将图像作为输入的模型相关。例如,如果我们的模型采用字数统计,那么就应该使用 NLP (NSLinguisticTagger)来代替。
Vision and NLP are build on top of Core ML
履行
**现在回到我们的视图控制器。从导入 CoreML 和 Vision 开始。创建一个新函数,姑且称之为 processImage()。请注意,该实现特定于 Vision,如果您使用另一个将 String 或 Double 作为输入的模型,您就不必以这种方式使用它。
为了提交输入,我们首先使用我们的 Inceptionv3 模型初始化一个 VNCoreMLModel 。然后创建一个 VNCoreMLRequest。最后设置一个 VNImageRequestHandler 并以请求为参数调用 perform。
为了检索输出,我们必须使用 VNCoreMLRequest 闭包,它有一个包含结果属性[VNClassificationObservation]的请求对象。就是这样,这是输出。⭐️✨
现在将 processImage() 添加到imagePickerViewController的委托方法中,并更新文本视图。下面是 90 行代码下的视图控制器的完整代码:
让我们运行应用程序,看看预测结果是什么样的。😍
iOS Simulator screenshot
我期待你的反馈🤓
🤖在此下载完整项目🤖
通过机器翻译和分类纠正文本输入
“person holding eyeglasses” by David Travis on Unsplash
最近,我正在做一个光学字符识别(OCR)相关的项目。挑战之一是预训练的 OCR 模型输出不正确的文本。除了 OCR 模型的性能之外,图像质量和布局对齐也是文本错误的主要来源。
看完这篇帖子,你会明白:
- 光学字符识别错误类型
- 基于词典的
- 基于内容
- 统计机器翻译
- 基于特征的词分类
OCR 错误类型
- 字符检测:识别不正确的字符。这可能是由于图像质量差和方向不正确造成的。
- 单词检测:无法检测文本。这可能是由字符检测错误引起的。
- 分段错误:无法正确分段字符和/或单词。
这种方法是最简单的一种解决方案,它可能不需要任何机器学习技能,只需要编程语言和正则表达式技能。通过计算 Levenshtein 距离(编辑距离)并从字典中找到最短的词典来替换“不正确的”单词,可以捕捉不正确的单词。你可以通过这个故事进行基于词汇的识别。局限性在于需要大量的词汇,并且还需要定义特定领域的数据。
另外,你可以访问拼写校正器故事和符号拼写故事来对它有更多的了解。
基于上下文
第二种方法计算单词序列的可能性。在语言学中,我们注意到大多数语言中都有一定的模式(或语法)。给定模式和分布,我们了解单词序列的概率。然而,限制是高频词,如停用词可能会支配结果。此外,罕见但重要的单词可能会受到影响。
统计机器翻译
Afli 等人提出了用于光学字符识别(OCR)纠错的统计机器翻译(SMT)方法。机器翻译(MT)用于将源语言翻译成目标语言。在这个特定的上下文中,源语言是 OCR 输出,而目标语言是正确的文本。
典型的机器翻译输入是一个单词序列(OCR 输出),输出是另一个单词序列(校正文本)。而统计方法的目标是最大化后验概率。另一个不同是,作者不仅评估单词级别模型,还评估字符级别模型。
单词错误率(WER)和双语评价替角(BLEU)在评价中被选中。从实验结果来看,单词级模型略好于字符级模型。
Evaluation Result (Afli et al., 2015)
精选基础词分类
Kissos 等人提出了另一种方法来修复来自 OCR 的文本错误。他们使用字符和单词分类方法来识别不正确的单词并进行修正。
作者提出候选排序器和校正决策器来判断单词是否应该被校正的文本替换。首先,将输入传递给候选排序器,并寻找候选替换的可能性。一旦候选词被确定,它将被传递到另一个模型,即修正决策器,以分类是否应该替换原始单词。
单词令牌将首先通过候选排序器,其功能包括:
- 混淆权重:讹误纠正对的权重属性。
- 单字频率:特定单词的总计数。
- 正向二元模型频率:二元模型(由前一个单词构成)的最大字数。
- 反向二元模型频率:二元模型(由下一个单词构成)的最大字数。
Example of the Candidate Ranker feature (Kissos et al., 2016)
候选人排名,随后是修正决策者。一旦可能性候选(替换的单词)被识别。校正决策器判断原始单词是否应该被该校正单词替换。功能包括:
- 置信度:OCR 输出度量。
- 词频:OCR 文本的总字数。
- 比例字典功能:与“候选人排名器”中使用的功能相同。
Example of the Correction Decision Maker feature (Kissos et al., 2016)
参考
Afli H,Barrault L .,Schwenk H …2015.使用统计机器翻译进行 OCR 纠错
基索斯一世,德肖维茨。普通…2016.使用字符校正和基于特征的单词分类的 OCR 错误校正
通过两个距离纠正你的拼写错误
“red pencil on top of mathematical quiz paper” by Chris Liverani on Unsplash
在处理文本时,我们可能需要处理不正确的文本。尽管我们仍然可以使用字符嵌入和单词嵌入来计算类似的向量。它对看不见的数据和词汇表之外的东西很有用(OOV)。不过如果能纠正错别字就更好了。
错别字可以在几种情况下产生。如果您使用光学字符识别(OCR),OCR 输出的后处理步骤是非常关键的部分,因为 OCR 引擎会引入一些错误,这些错误可能是由图像质量差和 OCR 引擎错误引起的。另一个错别字来源于人类。当你在聊天机器人项目工作,输入来自人类,它必须包括错别字。
为了获得更好的结果,最好是尽早纠正错别字。看完这篇帖子,你会明白:
- 拼写纠正器
- 履行
- 拿走
拼写纠正器
Norvig 在 2007 年实现了一个非常简单但是惊人的库来纠正拼写错误。通过不同的方式计算可能的候选校正,并从中找到最可能的单词。有两个阶段来寻找可能的候选词。
“brown rail train” by Johannes Plenio on Unsplash
首先,当原始词和候选词之间的编辑距离为 1 时,使用 4 种不同的方法生成新词。不同于 Levenshtein 距离,它认为:
- 删除:删除一个字母
- 换位:交换两个相邻的字母
- 替换:把一个字母换成另一个
- 插入:添加一个字母
以“edward”为例,“edwar”、“edwadr”、“edwadd”、“edwward”分别是“删除”、“换位”、“替换”、“插入”的例子。显然,将会产生大量的无效单词。因此,它会按给定的词汇过滤掉(它在库中称为“已知词”)。为了扩大潜在的候选,算法再次重复这个步骤,但是编辑距离是 2。
第二部分是根据概率从可能的候选者中选择候选者。例如,“Edward”在给定字典中的出现次数是 2%,概率是 0.02。概率最高的词将从潜在候选词中选出。
履行
为了方便拼写检查,需要语料库。为了便于演示,我简单地使用了 sklearn 库中的数据集,没有进行预处理。您应该使用特定领域的数据集来为您的数据构建更好的语料库。
建立语料库
from collections import Counter
from sklearn.datasets import fetch_20newsgroups
import recorpus = []
for line in fetch_20newsgroups().data:
line = line.replace('\n', ' ').replace('\t', ' ').lower()
line = re.sub('[^a-z ]', ' ', line)
tokens = line.split(' ')
tokens = [token for token in tokens if len(token) > 0]
corpus.extend(tokens)corpus = Counter(corpus)
校正
spell_corrector = SpellCorrector(dictionary=corpus)
spell_corrector.correction('edwar')
输出为
edward
拿走
要访问所有代码,你可以访问我的 github repo。
- 拼写校正器不考虑上下文,而仅仅考虑拼写。然而,鉴于它是在 11 年前(2007 年)推出的。这是一个神奇的工具。
- 从作者编码来看,预处理结果应该只保留英文字符和小写字母。换句话说,特殊字符和数字要去掉。
- 性能(就速度而言)非常快。
关于我
我是湾区的数据科学家。专注于数据科学、人工智能,尤其是 NLP 和平台相关领域的最新发展。你可以通过媒体博客、 LinkedIn 或 Github 联系我。
参考
相关性一终端来到佐治亚理工学院
有没有编过玩电子游戏的算法?
欢迎来到航站楼。
吃披萨,穿酷酷的 t 恤,参加四个小时的黑客竞赛,你就有了佐治亚理工学院的游戏之夜。我以竞争对手的身份参加了这次活动,以下是我认为 Terminal(字面上)改变游戏规则的原因。
在计算机科学领域,发展技能是至关重要的。通过上课、个人项目和实习,像我这样的学生为了吸引顶级公司,不断地竞争建立他们的简历。
然而,我们经常忽略硬币的另一面。就像我们学生渴望被雇佣一样,雇主也在不断寻找顶尖的科技人才。招聘已经成为一个蓬勃发展的行业,而 Correlation One 是这个领域最具颠覆性的新参与者之一。
Members of the Correlation One team and Data Science @ GT helped to run the event.
在过去的一年里, Correlation One 的团队一直在开发终端,这是一款定制的塔防游戏,通过提交的算法自动进行游戏。在佐治亚理工学院的游戏之夜,这是同类活动中的第一次,超过 230 名学生注册整夜编写他们自己的算法。
游戏之夜得到了很好的宣传,并在一个学生组织的帮助下举办——T4 数据科学@ GT 。我的许多朋友都来参加比赛,都对这项活动的独特性质感兴趣。毕竟,你很少有机会将编码和视频游戏与赢得现金奖励的前景结合在一起!
The event was held at the Clough Learning Commons.
在解释完规则后,每个人都开始研究他们的算法。提供的 API 有很好的文档,视频教程也很棒。本科生、研究生和博士候选人都有同等的机会证明自己的优势。精英精神确实闪耀着光芒,这也是我最喜欢的活动特征之一。
终端是一款塔防游戏,但绝不是简单的一款。尽管提供的 starter 算法只有几百行 Python 代码,但要熟悉游戏的规则和大致范围确实需要一些时间。在活动的第一个小时,提交的竞争算法非常少。随着我和我的同伴们努力解决问题,逐渐熟悉了这个游戏,竞争的感觉开始主导这个房间。
Tech students worked through the night to improve their submissions.
当我整晚继续迭代我的算法时,我对一个团队用终端创建的相关性有了真正的理解。我可能和周围的人一样埋头于电脑中,但通过上传我的算法并观看他们播放其他提交的内容,我正以一种精神上具有挑战性的方式直接与我的同行互动。
传统的编码挑战需要大量的准备工作,通常需要掌握数百个实践问题,而 Terminal 将你置于一个进入的障碍仅仅是知道如何编程的舞台上。除此之外的一切都是开放式的,并测试现实世界的思维,这使它明显不同于我参加过的任何此类比赛。
Terminal incentivizes creativity and strategic thinking unlike most popular recruiting tools.
你可以自由地想出新的策略来打击对手,并按照自己的意愿实施适应性强的复杂解决方案。创新的自由和潜力是独一无二的,令人兴奋的,只有在时间敏感的环境中,让你的工作与竞争对手的工作相竞争,这种挑战才能支撑你的创新。
随着提交截止日期的临近,房间里的能量明显上升。在最后一轮比赛中,所有人都可以看到直播,观众欢呼着算法之间的较量。看到我的算法与我的对手面对面,真是太激动了!
The final round of the night, where my algorithm (in pink) faced off against 2nd place (in blue).
我对我们提出的各种策略和解决方案感到惊讶,终端作为招聘工具的真正价值变得非常清楚。这个游戏不仅能很好地激发学生的兴趣,还能挑战我们的战略思维和适应时间敏感的环境。我认为 Correlation One 已经击中了要害,我期待着在未来的几个月里看到 Terminal 的变化,因为来自全国各地的学生将在未来的比赛中竞争。
The award ceremony from the night! To the left of me is runner-up Neil Thistlethwaite.
在终端上随意查看 Correlation One 的官方博客帖子。更好的是,如果你有竞争精神和 Python 的工作知识,那就自己去玩吧!
相关性与因果性:一个例子
怀疑地看待现实世界的统计数据
令人惊讶的是,在我们收到的大量电子邮件中,有一些深刻的见解等待着我们去发现。当我漫不经心地浏览我的收件箱时,我浏览了一封来自学校留学办公室的邮件,里面有以下关于海外留学好处的信息:
立即引起我注意的是 90 年代的那些数字——显然,出国留学让你对研究生院和雇主具有不可抗拒的吸引力。我惊讶于在另一个国家学习所带来的学术和职业利益是如此之大。我的第二个想法是:太糟糕了,我没有选择利用这些好处,我很快存档了电子邮件,在我开始后悔任何进一步的人生决定之前。然而,关于这些信息的一些东西一直困扰着我。在这个假新闻占主导地位的时代,我一直试图花更多的时间有意识地思考各种说法和统计数据,虽然这不在同一社会退化水平上,但我得出的结论似乎有些不对劲。几天后,在听一个对数据持怀疑态度的播客时,我突然想到:我曾假设出国留学会让学生有更好的成绩和职业前景,而所有的统计数据显示这两者是相关的。
我们大多数人经常犯这样的错误,无意中混淆了相关性和因果关系,这种倾向被媒体标题所强化,如音乐课提高学生的表现,或者呆在学校是长寿的秘诀。有时候,特别是在健康方面,这些倾向于不可思议的事情,比如《卫报》的头条声称吃鱼会减少暴力。
The work of the powerful tuna lobbying industry
这些文章中的共同问题是,他们采用两种相关的趋势,并将其作为一种现象引起另一种现象。真正的解释通常不那么令人兴奋。例如,上音乐课的学生可能在学校表现更好,但他们也更有可能在一个非常重视教育和取得学术成功所需资源的环境中长大。因此,不管有没有音乐课,这些学生都会有更高的学习成绩。上音乐课和在学校表演碰巧是同时出现的,因为它们都是相似背景下的产物,但一个不一定会导致另一个。同样,在校时间越长的人通常拥有更多的资源,这也意味着他们能够负担更好的医疗保健。大多数情况下,这些错误并不是出于有意欺骗(尽管这种情况确实发生过),而是出于对因果关系概念的诚实误解。统计数据,尤其是留学邮件中的数据,显示的是一种选择偏差。在每项研究中,被观察的个体并不来自社会的代表性部分,而是来自相似的群体,这导致了一个扭曲的结果。
想想统计数据显示,在国外学习的学生按时毕业的可能性要高出 19%。虽然出国留学可能确实在某种程度上激励了落后的学生按时毕业,但更可能的解释是,选择出国的学生首先是那些在学术上处于更好地位的学生。不管他们是否去了另一个国家,他们都会以高 GPA 按时毕业。去另一个国家学习一年需要做大量的工作和准备,而有足够信心这样做的学生是那些学业有成的人。在这个现实世界中,选择偏向于更好的学生。出国留学的学生样本并不能代表学生的整体情况,相反,它只包括准备最充分的学生,因此,这一群体拥有更好的学术和职业成果也就不足为奇了。
事后看来,出国留学的经历可能看起来很棒,但如果我们只选择最优秀的学生,让他们做任何事情,那么说这种现象导致更好的成绩将是误导。比方说,我们拥有一家瓶装水公司,我们希望收集一些积极的数据来帮助销售。我们雇几个学生站在荣誉班外面,只把我们的水给优等生。然后,我们进行了一项研究,最终表明喝我们品牌饮料的学生成绩更好。因为我们选择了一组特定的受试者来参与我们的研究,我们可以让它看起来像是我们的水导致了成绩的提高。
海外留学统计数据来自所谓的观察研究。观察性研究不是构建一个实验,而是观察现实世界中无法控制自变量的一些过程,在这个案例中,是选择出国留学的学生。观察性研究无法证明因果关系,只能证明不同因素之间的联系(比如成就和在另一个国家学习)。为了证明一个过程导致了另一个过程,需要进行随机对照试验,受试者代表整个人群。在这种情况下,进行随机对照试验需要从各种学习表现中随机选择一部分学生,送一些去国外学习,留一个对照组在家。然后我们可以分析结果,以确定两组之间是否有显著差异。如果有,那么我们可能会进行更多的研究,控制更多的变量,直到最终我们确信没有隐藏的影响,我们可以建立因果关系。
我向 CWRU 留学办公室指出了这些观察结果,随后是一次体面而富有成效的谈话。
Civility and Honest Discussion. On Twitter!
我发这个帖子,并不是想给办公室打电话。尽管这封邮件确实写道:“这只是这个新年决心能带来的一些好处,”但它并没有声称确切的因果关系。然而,当一个单一的话题被大量的事实包围时,我们的自然倾向是画出一个因果关系,这是营销人员和公司经常利用的一种倾向。我相信这个案例中的所有统计数据都是有效的,但我们仍然需要避免指定因果关系。没有随机对照试验,我们不能说一种活动引起了另一种活动,我们只能说两种趋势是相关的。
这是一个小例子,但它说明了一个极其关键的问题:我们所有人,甚至每天都在使用这些概念的研究生,都可能被统计数据所愚弄。人类自然会看到不存在的模式,我们喜欢讲述一个我们认为正在发生的事情的连贯故事(叙事谬误)。然而,这个世界通常没有明确的因果关系,我们必须满足于相关性。这种世界观可能会让头条新闻变得不那么令人兴奋(事实证明巧克力并不是一种神奇的食物,但这意味着你不会因为可疑的证据而被骗去购买不符合你最佳利益的产品或采取不符合你最佳利益的行动。此外,我们可以与他人分享我们的经验,并创建一个持怀疑态度的社区,在这个社区中,我们为了自己的利益而不是公司的底线做出合理的决策。
一如既往,我欢迎建设性的批评和反馈。可以在推特上找到我,电话是 @koehrsen_will 。
亚马逊、谷歌、IBM 和微软的云情感分析成本比较
Source: Library of Congress
我们生活在一个激动人心的时代,尤其是对于负责快速分析大量文本的数据科学家来说。除了像 R Studio 和无数现成软件这样的免费 ide 包之外,大型云玩家已经通过 API 将他们的机器学习工具交给你使用。单独来看,云服务工作得很好,但将它们结合起来(就像我们最近的)会产生更好的结果:我们发现结合亚马逊理解、谷歌云、IBM 沃森和微软 Azure 的情绪分析结果可以正确预测 78%的情绪。
使用这些服务非常简单。计算它们的成本,以及这些成本之间的比较,完全是另一回事。在本文中,我们将比较四家最大的云计算公司在各种场景下的成本。
比较苹果、橘子和香蕉
亚马逊的定价是出了名的复杂,它是少数几个具有开放 API 的云服务之一,可以提供具体的定价示例,所以我们将使用它作为基准。
场景是这样的:你有 10,000 条顾客评论,每条平均 550 个字符。你才刚刚开始,想让事情变得简单,所以你只想知道每个评论的整体情绪。更复杂的分析技术在每个平台上都是可能的,包括主题分析和定制建模。但是我们现在不需要这个。
为了便于比较,我们将排除任何存储成本,因为这也因平台而异。
亚马逊理解
虽然这个简单的例子可以在亚马逊理解上免费执行,但我们将假设最低标准定价层(同样值得注意的是,免费层有 12 个月的使用期限)。亚马逊将每 100 个字符作为一个“单位”
10,000 requests X 550 characters/request = 60,000 units
60,000 X $0.0001 per unit = **$6**
Amazon Comprehend Pricing
谷歌云自然语言
谷歌云自然语言免费层最多有 5000 条记录,所以你不得不为这个简单的 10000 条记录付费。然而,每条记录有 1000 个字符的限制,所以我们示例中的每条记录只需要一个单元。
10,000 requests X 550 characters/request = 10,000 units
10,000 X $1 per 1,000 units = **$10**
Google Cloud Natural Language Understanding Pricing
IBM 沃森
IBM Watson Natural Language Understanding在其免费定价层中每月允许多达 30,000 个“自然语言单位(nlu)”,但我们将考虑最低定价层进行比较。每个 NLU 允许 10,000 个字符和两个“丰富特性”,因此我们的示例需要 10,000 个 nlu。
10,000 requests X 550 characters/request = 10,000 NLUs
10,000 X $0.003 per NLU = **$30**
IBM Watson Natural Language Understanding Pricing
微软 Azure 认知服务
像谷歌一样,微软 Azure 认知服务免费层被限制为 5000 个“交易”,所以我们将分析他们的最低定价层。每笔交易包括多达 5,000 个字符的情感分析和关键短语提取、语言检测和实体识别。微软不分解单个元素的定价,只提供基于层而不是量的定价。
10,000 requests X 550 characters/request = 10,000 transactions
10,000 transactions requires “Standard S0 pricing tier” = **$74.71**
Microsoft Azure Text Analytics Pricing
更多的场景,更多的水果来比较
从上面的比较来看,你会认为亚马逊在定价上显然是最激进的,这就是故事的结尾。不完全是。当您增加记录的数量或记录的大小时,事情会变得稍微复杂一些。
在上面的第一个例子中,我们比较了 10,000 条客户评论的情感分析。但是如果我们想分析 100 万条推文呢?还是 5000 篇学术论文每篇 10000 条记录?
Pricing based on publicly available information; account-level discounting may lead to lower pricing.
虽然亚马逊在定价上普遍领先于竞争对手,但在规模定价方面有一些奇怪之处变得很明显。例如,如果您有相对较少数量的非常大的文档,那么 IBM 看起来是运行您的分析的好地方。当做一些小的事情时,微软的最低定价层看起来很贵,但是如果这些文档更大,它看起来更有吸引力。
我们建议您使用测试数据集亲自尝试它们,看看它们的表现如何。如果它们不起作用,花多少钱就无关紧要了。
计算社会科学家会是你的下一个最佳雇员吗?
Social Scientists are experts are using data to find insights on behavior and relationships between groups of people.
我想向你们介绍一个鲜为人知的专业领域,叫做计算社会科学家。
每个企业都可以从雇佣计算社会科学家中受益,但很少有人知道这到底意味着什么。所以我想尝试照亮这个领域。
完全公开——我把自己放在计算社会科学的桶里,所以我有偏见。但是听我说完,我想你会喜欢这个领域的。
计算社会科学归结为社会科学家使用数据处理和数据科学计算工具(想想 R,Python 等)来分析关于人和关系的数据。
计算社会科学家和数据科学家的区别在于,社会科学家是研究人类行为和在关于人口群体的数据中寻找模式的专家。
社会科学家来自不同的流派,但主要群体包括经济学家、人类学家、社会学家和心理学家。
另一方面,数据科学家通常倾向于深入了解统计学和数据计算。数据科学家通常具有统计学、数学、计算机科学和数据工程背景。
这并不是说一个比另一个好。它们是互补的。
当想要跟踪大量数据、运行算法和大规模开发复杂的机器学习模型时,数据科学家将是您的最佳人选。
当你想了解人口、用户群或行为模式时,社会科学家会是你的得力助手。他们使用数据科学工具的方式与数据科学家相似,但他们希望在社会行为的背景下解释趋势。
数据科学家带来了计算和统计方面的丰富知识,而社会科学家带来了与人群相关的模式方面的丰富知识。因此,这两种技能组合的合作可能是在大量数据中释放巨大价值的关键。
例如,我最近与数据科学同事合作,展示了关于银行模式的数据。这位数据科学家专注于预测不同国家和产品组的总体趋势。我专注于机器学习如何帮助我们识别客户群,以及数据如何显示客户群是由价格或服务或奖励积分等不同价值驱动的。这两种观点共同提供了丰富的概述。
大多数企业专注于雇佣两组数据洞察专业人士:数据科学家和研究人员/消费者洞察。
我认为还有第三个重要的群体,那就是计算社会科学家——拥有处理大量数据的技术技能和帮助他们识别人群行为模式的知识集的专业人士。
现在比历史上任何时候都更加强调数字社会联系和更多可用于跟踪行为的数据。这创造了一个充满希望的环境,计算社会科学家可以帮助进步的企业找到关于行为和群体关系的宝贵数据。
所有这些都表明,计算社会科学家可能是你的下一个最佳雇员。
替代投票系统能阻止特朗普吗?
现在,唐纳德·特朗普正式成为共和党提名人,回顾一下让曾经不可想象的事件发生的背景是很重要的。在接下来的几个月和几年中,大量的博客文章、论文、同行评议文章和书籍将研究导致特朗普初选胜利的文化和经济环境,但讨论中似乎缺少了一个背景,那就是初选制度本身。情况并非总是如此。
事实上,在初选期间,【RCV】的选择性选举制度曾经风光一时。尽管实施这种投票系统的想法并不新奇,但这次选举的一些事情让人们对它进行了更仔细的审视。RCV 的工作方式是,每个选民不是投票给一个候选人,而是按照他或她的偏好排列所有候选人。如果没有候选人获得第一选择票的绝对多数,则第一选择票最少的候选人将从选票中退出,这些选票将转移给每位选民的第二选择候选人。这一过程一直持续到一名候选人获得至少 50%的选票。
举个简单的例子,想象一下萨姆、克莱尔和哈利之间的选举,40 个人投票“1:萨姆,2:克莱尔,3:哈利”,35 个人投票“1:克莱尔,2:哈利,3:萨姆”,25 个人投票“1:哈利,2:克莱尔,3:萨姆”。在目前的制度下,山姆将赢得 40%的选票,相比之下,克莱尔和哈利分别获得 35%和 25%的选票。但在 RCV 治下,由于没有候选人获得 50%的选票,第一选择票数最少的人(哈里)将被淘汰,他的选民将被重新分配给他们的第二选择。在这个例子中,Harry 的所有 25 个投票者都将 Clare 作为他们的第二选择,所以他们的票都投给了她。重新分配后,克莱尔将获得 60 票,而萨姆只有 40 票,他将以 60%的优势获胜。从本质上讲,该制度确保了当大多数选民倾向于另一位候选人时,一位候选人不会以多数票获胜。
如果这听起来很熟悉,那可能是因为在#nevertrump 运动的巅峰时期,它变得非常流行讨论它的优点(以及类似系统的优点)。当时的普遍理论似乎是特朗普以 30—40%的多数票赢得了第一轮初选,尽管 60—70%的“反特朗普”选民肯定会团结在另一位候选人周围,如果这个领域没有如此分裂的话。在这种情况下,排名选择投票是可以拯救该党的白衣骑士,提供了一种将提名保留在人民手中的方式,同时确保最终被提名者可以被大多数选民接受。
虽然这是一个令人信服的故事,而且特朗普最终在初选中只获得了 46%的选票,这一事实加强了这一故事,但仍然有必要问一个问题:RCV 真的会改变什么吗?我开始借助两项早期民意调查(公共政策民意调查和 NBC 新闻|调查猴子)来寻找答案,这两项调查要求选民给出他们的第二选择候选人。
使用这些民意调查的数据以及每个初选中每个候选人的投票数量,我能够模拟在排名选择投票系统下前 15 个共和党初选和党团会议中可能发生的情况。模拟一直进行到超级星期二初选,包括超级星期二初选,因为剩下的部分需要极端猜测早期初选结果可能如何影响后来的候选人退出和选民偏好。每个州的选举都被模拟了 1000 次,并添加了随机噪声,以考虑所使用的投票数据的不确定性(更详细的方法可以在这里找到)。
有趣的是,这些模拟的结果描绘了一幅比通常理论化的稍微低调的画面。具体来说,在超级星期二之前的几乎每个州,在当前制度下赢得多数票的候选人在 RCV 制度下最有可能赢得大多数选票。两个例外是阿肯色州和弗吉尼亚州,前者克鲁兹赢得了 77%的模拟 RCV 选举,后者卢比奥赢得了 60%的模拟 RCV 选举,尽管特朗普在当前制度下赢得了这两个州。
然而,结果确实表明,特朗普在当前体系下的结局是他在 RCV 治下所能希望的最好结果。特朗普在 RCV 治下至少有 10%胜算的州是他在当前制度下赢得的州。相比之下,非特朗普候选人(克鲁兹和卢比奥的组合)有很大的机会(> 50%)从特朗普手中拿下两个新的州(阿肯色州和弗吉尼亚州),也有合理的机会(> 20%)拿下另外两个州(新罕布什尔州和南卡罗来纳州)。
此外,通过检查州赢和输的所有可能组合,很明显特朗普会受到这一系统的至少轻微伤害。事实上,他只有 7.5%的机会赢得至少 10 个州(这是他在现行制度下赢得的数字),相比之下,他有 27%的机会赢得 9 个州,40%的机会赢得 8 个州,21%的机会赢得 7 个州。按照同样的标准,克鲁兹和卢比奥都将略有受益,他们最有可能取得+1 的胜利。
基于这些结果,一个有趣但更主观的任务是分析这样的结果会对剩下的初选产生什么影响。很容易想象 8/15 的表现不如 10/15 的表现占据主导地位,特别是在初选的早期阶段,特朗普的竞选活动非常注重始终“获胜”的理念。然而与此同时,可以想象,8 次多数胜利实际上可能比 10 次多数胜利更令人印象深刻。
因此,尽管这些结果指向反对特朗普的整合大方向,但它们并不像一些人认为的那样确定。最终,这可能简单地归结为记者和政治家不喜欢特朗普的程度与共和党选民不喜欢特朗普的程度之间的脱节。虽然被迫承认 30%至 40%投票给他的选民的意见,但在早期阶段,许多 T2 坚持认为,尽管川普是这些选民的第一选择,但他肯定是其他人的最后选择。事实上,这个天花板理论很可能是错误的。相反,特朗普似乎从一开始就是一个常见的第二或第三选择,并且和其他任何候选人一样从不断缩小的领域中受益匪浅。这一事实很可能是 RCV 对他的成功的适度影响的根源。
虽然排名选择投票可能没有彻底改变这次选举的进程,但仍然有几个原因使这个投票系统值得关注和考虑。如果不说别的,选民排名行为所提供的对选民偏好的深刻洞察可能对更早、更有效地动员反特朗普倡导者非常宝贵。因此,尽管 RCV 在全国范围内实施的可能性不大,但我们仍然可以通过写信给当地的民意调查机构,在他们的下一封邮件中询问第二和第三选择问题来尽自己的一份力量。
你最喜欢的 2020 年的希望会感激它。
无数 3D-使用 Python 和 Numpy 对标记的体积图像进行矢量化 2 倍缩减采样
A visualization of a 2x2x2 downsample of a labeled dataset. This is what COUNTLESS 3D does.
之前,我演示了一个完全矢量化的算法,neutrable,它通过寻找 2x2 面片的模式,在不计算像素值频率的情况下,对标记的图像进行下采样。在这里,我使用 2x2x2 的补丁和任意的下采样因子将无数的图像归纳为 3D 图像。无数 2D 通过寻找多数像素而不计算其总频率来工作。这是通过在一个 2×2 图像块中找到两个匹配像素的存在或不存在来确定的。无数 3D 扩展了这一逻辑,在八个体素(三维像素)中搜索匹配的四个像素,然后是三个,最后是两个。无数个 N 将这种技术推广到任何输入大小,尽管它很快变得不切实际无数个 2D 和无数个 3D 可以分别被视为无数个 4 和无数个 8 的特定应用实现。仅使用矢量化指令实现了无数变体,这使得它们对于在 Python 等编程语言中实现非常实用,Python 等编程语言具有快速的线性代数库(如 Numpy ),但具有缓慢的循环和分支语句。虽然无数的 2D 可以胜过简单的算法,但是在高吞吐量应用中使用无数的 3D 是不可取的,因为它比标准方法慢得多。然而,在 Python 中,它可以在不求助于额外编译步骤的情况下实现,并且仍然提供合理的性能。
动机
Figure 1. Volumetric images used in connectomics research, a field where we attempt to find the wiring diagrams of brains. Left: A 3D image formed from a stack of electron microscope scans of brain tissue. Right: A machine labeling of neurons in the left image by Kisuk Lee. This article concerns the type of image on the right.
虽然普通人不经常遇到,但 3D 图像(体积图像)在生物医学成像中大量使用。可以从以规则加深的间隔采集的一叠 2D 图像中构建体积图像。MRI 机器使用磁体来非侵入性地获取大脑切片的图像,细胞生物学家经常使用激光显微镜来扫描不同深度的样本。他们采集的图像按排序顺序排列在一个堆栈中,形成最终图像。
我感兴趣的组织是一个大脑,获取这些图像的方法是使用一种类似于熟食切片机的叫做超微型切片机的机器将其非常精细地切片。然后用电子显微镜对得到的切片进行成像,并组合成一堆以生成体积图像。切片很大,每边的像素在数万到数十万之间。相比之下,高端的 T4 4K 分辨率电视或电脑显示器每边只能显示 3000 到 4000 像素的图像,而另一边通常只有它的一半大。
为了使用消费类硬件显示这些图像,通常的做法是对它们进行下采样,即创建一系列更小的摘要图像,这些摘要图像公平地表示底层的高分辨率图像。例如,在谷歌地图上,世界显示为单个(或少量)拼接的图像,但当你放大时,更高分辨率的图像被提取并仅显示感兴趣的区域。对于显微镜图像,我们可以通过平均 2x2x2 小块来对其进行缩减采样,以创建一个八分之一小的图像,并重复这样做,直到图像足够小。然而,一旦我们生成标签来描述哪个体素属于哪个神经元,就不能进行平均,因为标签不是模拟信号,而是离散标识符。总结它们的最合适的方法是选择一个补丁中最频繁出现的值。
下采样的存储导致了额外的成本,尽管它随着每一个额外的 mip 级别而指数下降。对于递归 2x2 缩减,缩减采样堆栈的存储和传输成本比存储全分辨率图层的成本高 33%。
**LISTING 1: Size of an Infinite Stack of Downsamples**Let S = The size of all the downsamples
Let r = The reduction factor of a downsample (e.g. 4 for 2x2)S = 1 + 1/r + 1/r^2 + 1/r^3 + …S/r = 1/r + 1/r^2 + 1/r^3 + 1/r^4 + …S — S/r = 1S = r / (r-1)
因此,2×2 下采样堆栈的存储成本最多是原始图像本身成本的 4/3 或 133%。2x2x2 下采样堆栈的存储成本最多是全分辨率的 8/7 或 114.3%。对于某些用例来说,这种减少可能是诱人的。对于那些被如此吸引的人来说,问题变成了如何在不计数的情况下实现它?
无数个 5——2D 问题的一个小扩展
从根本上说,无数的 2D 依赖于这样一种思想,即如果一组四个标签中的两个匹配,就没有必要考虑任何附加信息来声明该补丁的模式。人们很自然地会问,对于这个问题的最小可能扩展,一组五个标签,是否可以陈述类似的原理。
Figure 2. The seven cases of COUNTLESS 5. (a) all labels match (b) all but one label matches © three labels match versus two labels (d) three match but the other two are both different (e) two pairs and one different (f) one pair and three different pixels (g) all different
在四个标签的情况下,我们发现如果任何两个匹配,它们立即形成多数或平局,在这种情况下,可以任意选择任何一个平局组的成员。在五个标签的情况下,两个标签的匹配不再足以宣布获胜者,然而三个标签是。如果三个标签匹配,它们将总是形成多数,不可能出现平局。然而,如果没有三个匹配,那么模式将由两个匹配组成。如果没有两个标签匹配,那么可以任意选择一个标签。
因此,我们必须通过某种机制寻找三个匹配,如果不存在,则寻找两个匹配,如果没有两个匹配,则举手选择一个合适的像素。搜索三个匹配意味着检查五个标签中的三个标签的每个组合,同样地,搜索两个匹配也是如此。
**LISTING 2: The Relevant Combinations of Five Labels**Let the five integer labels be denoted A, B, C, D, ECombinations of three: 5C3 = 5! / 3!2! = 10 combinations ABC, ABD, ABE,
ACD, ACE,
ADE,
BCD, BCE,
BDE,
CDECombinations of two: 5C2 = 5! / 2!3! = 10 combinations AB, AC, AD, AE
BC, BD, BE
CD, CE
DECombinations of one: 5C1 = 5 combinations A, B, C, D, E
为了评估每个组合,我们可以从无数 2D 中归纳出 PICK(A,B)运算符来接受任意数量的参数:
PICK(A,B,C, ..., N) := A if A == B == C == ... == N else 0 EQN. 1
这可以用 Python/numpy 伪代码实现(“&”是按位 AND 运算符):
PICK(A,B,C ..., M,N) := A * (A == B & B == C & ... & M == N) EQN. 2
对给定组合每次应用 PICK 将产生一个标签或零。如果我们的三个组合中的任何一个产生非零值,它就有资格作为候选模式。如果有一个以上的匹配,例如,如果实际上有四个匹配的标签,我们可以任意选择任何一个候选模式。短路逻辑 or 操作符有正确的语义来选择一个标签(如果存在的话)。
MODE(A,B,C,D,E) := PICK(A,B,C) || PICK(A,C,D) || PICK(A,D,E)
|| PICK(B,C,D) || PICK(B,D,E) || PICK(C,D,E) || PICK(A,B)
|| PICK(A,C) || PICK(A,D) || PICK(A,E) || PICK(B,C)
|| PICK(B,D) || PICK(B,E) || PICK(C,D) || PICK(C,E)
|| PICK(D,E) || E EQN. 3
如前所述,||运算符可以这样实现:
LOGICAL_OR(X,Y) := X + (X == 0) * Y EQN. 4
等式 3 有十七个元素需要一起计算和逻辑或。有什么方法可以减少所需元素的数量吗?基本上降低通用算法的时间复杂度是不可能的,但是对于少量的标签来说,还是有一些边际节省的。注意,对于两个匹配的情况,我们可以扩展在无数 2D 中使用的技巧,以避免计算最后一个元素的匹配。使用符号 PQ 表示 PICK(P,Q),如果 AB、AC、AD、BC、BD 和 CD 都不匹配,那么我们将被迫选择 AE、be、CE、DE 或 E 中的一个,所有这些都与 E 相同。因此,我们可以省略 AE、BE、CE 和 DE,留下十三个元素来计算,这是一笔可观的节省。
MODE(A,B,C,D,E) := PICK(A,B,C) || PICK(A,C,D) || PICK(A,D,E)
|| PICK(B,C,D) || PICK(B,D,E) || PICK(C,D,E)
|| PICK(A,B) || PICK(A,C) || PICK(A,D) || PICK(B,C)
|| PICK(B,D) || PICK(C,D) || E EQN. 5
无论我们考虑的标签集有多大,最后一个标签总是如此。这意味着对于 N 选 2 的情况,我们总是可以把它简化为 N-1 个选择两个组合来考虑。
将所有这些放在一起,会产生一些适用于无数 5。请注意,这只是无数 3D 的垫脚石,也许更好地称为无数 8,因为它解决了 2 x 2 x 2 体素的模式。
无数 3D(又名无数 8)
无数的 3D,这个概念的实际应用现在已经触手可及。对 2×2×2 的体积图像补片进行下采样相当于找到八个整数的模。候选人多数或平局的最低要求是四个匹配的标签。如果比分是 4 比 4,我们可以任意选择一场比赛。要计算无数个 8,我们必须考虑长度为 4、3、2 的匹配。
下面的等式 6 显示了必须进行的提货呼叫的数量。请注意,下面的 7C2 项来自我们使用无数个 5 部分中显示的方法对 8C2 的简化。
Total PICKs = 8C4 + 8C3 + 7C2
= 70 + 56 + 21
= 147 EQN. 6
现在请注意,PICK(A,B,C,D)比 PICK(A,B,C)贵 1.5 倍(六次操作对四次操作),比 PICK(A,B)贵 3 倍(六次操作对两次操作)。这个事实以后会很重要。
下面是无数个 8 的实现。请注意,如果我们不小心的话,创建 148 个精选将会增加所需的内存。使用 Python 3 生成器表达式,我们可以通过一次只创建几个额外的派生图像来大大减少这个程序的内存需求。
类似于无数的 2D,如果匹配的标签是零,那么 PICK 操作符的输出是无意义的(无论它们是否匹配,它都返回 0),所以我们将数据上移一位以适应零标签,并在最后下移。
无数 3D 动态编程
到目前为止还不错,但可以加快一点。如等式 6 所示,无数 3D 的性能大致与必须计算的拾取操作的数量成比例,而拾取操作的数量又等于必须计算的操作的数量。PICK(A,B,C,D)需要六次运算,而 PICK(A,B,C)需要四次,PICK(A,B)需要两次。我们还需要计算每个逻辑 or 的三次运算。
**LISTING 3: Number of Operations Required for COUNTLESS 3D**# of COUNTLESS_3D operations
= 8C4 PICK(A,B,C,D) + 8C3 PICK(A,B,C) + 7C2 PICK(A,B)
+ (8C4 + 8C3 + 7C2 + 1 - 1) logical ors
= 8C4 x 6 + 8C3 x 4 + 7C2 x 2
+ (8C4 + 8C3 + 7C2) x 3
= 686 + 441 // picks + logical or **= 1,127 operations**
或者更一般地说:
Eqn. 8: Establishing a lower time complexity bound for COUNTLESS N, where N ≥ 2. N represents the number of labels being evaluated, 7C2 + 1 represents the abbreviated cost of the matches of two case. The left hand term refers to the cost of PICK and the right hand term refers to the cost of logical or.
到目前为止,因为问题的解决方案是由较大的匹配先于较小的匹配来控制的,所以我们首先计算最大的匹配。然而,可以使用动态编程方法从较小的组合中构建较大的组合。以额外的内存为代价,我们可以将两个匹配视为三个匹配的子问题,将三个匹配视为四个匹配的子问题。由于每个新层都通过添加一个元素建立在旧层的基础上,这种技术将使我们能够通过消除重复工作来降低选择项中的(r-1)系数。
Eqn. 9: Dynamic programming drops the coefficient (r-1) from the summation.
通过去掉因子 2,我们可以将该等式简化为:
Eqn. 10: Simplified cost of the dynamic programming solution for COUNTLESS N.
然而,这对于计算和交流来说有点烦人,所以让我们用一些朗道符号来简化它。从 0 到 N 的所有组合之和等于2^N
。由于组合是对称的,并且我们在跳过少量计算的同时求和到 N/2,这算出了 2^(N-1).的指数时间复杂度
Eqn. 11: Exponential big-O time complexity.
动态规划保存已解决的子问题,供算法的下一次迭代使用。在这种情况下,这意味着保存匹配长度为 2 的结果,并使用它们来引导匹配长度为 3 的匹配。这非常耗费内存,但是有一个技巧可以让它变得稍微好一些。
**LISTING 4: Not All Subproblems are Used**Let A, B, C, D, E represent five labels.
Let the notation AB := PICK(A,B) and ABC := PICK(A,B,C) and so on.Combinations of Length Two: AB, AC, AD, AE
BC, BD, BE
CD, CE
DECombinations of Length Three:
ABC, ABD, ABE, ACD, ACE, ADE
BCD, BCE, BDE
CDENotice that all length three combinations are prefixed with length two problems. For example, PICK(A,B,C) can be written as PICK(PICK(A,B), C). However, certain length two subproblems are not used in length three. AE, BE, CE, and DE can never be used as a prefix because E always occurs at the end.Therefore, substantial memory can be saved by not memoizing subproblems that include the terminal element.
将所有这些放在一起,就产生了无数 3D 的动态编程版本,虽然需要更多内存,但速度要快得多。它需要存储前一次迭代的结果。这导致内存消耗峰值为:
Fig. 11: Combinatoric space complexity.
我们应该期待无数 3D 的动态版快多少?使用等式 8 和 10:
**LISTING 5: Comparing the Ratio of Eqn. 10 and Eqn. 8**Non-Dynamic = 1,127 operations ; from LISTING 3
Dynamic = 5 * ( 8C4 + 8C3 + 7C2 ) ; from Eqn 9
= 735 operationsDynamic / Non-Dynamic = 0.65 or 1.53x faster
由于理想化的动态编程解决方案只需要 65%的工作,我们应该粗略地预计动态编程解决方案大约快 1.5 倍。实际上,我发现它要快 1.3 到 2.3 倍,可能是由于操作者速度的差异。
无数 N——广义的无数
总结我们在无数 3D 公式中所学到的东西是很简单的。无数个 N 是一个非常消耗内存和计算的算法。我不期望它在一个狭窄的参数范围之外表现良好。然而,当给出 2x2x2 缩减系数时,下面的算法在性能上匹配无数 3D。
表演
无数 2D 的性能指标都集中在纯粹的执行速度上。然而,对于无数的 3D 和无数的 nd,由于组合的数量,也需要测量内存使用量。
为了确定这种算法的速度,我们开发了一个比较套件,并在双核 2.8 GHz、i7 Macbook Pro(大约 2014 年)、256 kB L2 高速缓存和 4MB 三级高速缓存上运行。最大汇集、平均和大步跑包括在速度比较中,尽管它们不适合这项任务。
下面的实验使用了 Python 3.6.2 和 numpy-1.13.3 以及 clang-900.0.39.2。
经过测试的算法:
- **大步走:**每隔一个像素拾取一次。
- **countless 3d:**countless 3d 的低内存实现
- **dynamic _ countless 3d:**countless 3d 的动态编程实现
- countless3d_generalized: 用下采样因子 2,2,2 匹配无数 3d 的无数 N 的低内存实现
- **COUNTLESS 3d _ Dynamic _ generalized:**用下采样因子 2,2,2 来匹配无数 3d 的无数 N 的动态编程版本
尽管这些算法不适合处理分割,但也对它们进行了测试,以便为其他图像处理算法提供比较点:
- **down sample _ with _ averaging:**对 2x2 窗口中的像素进行平均。
- **down sample _ with _ max _ pooling:**在 2x2 窗口中选取最高值像素。
用于测试算法的代码可以在这里找到。以下数字是在修订版 15c0077 上测试的。
吞吐量
Table 1. Python 3 Downsample Performance in Megavoxels/sec on a 512x512x512 Numpy Array by Array Data Type, 5 Trials
当使用 uint8、uint16、uint32 和 uint64 分配立方体时,下面的数字来自对 512 x 512 x 512 体素立方体运行上述算法五次。表 1 和图 3 中的结果以每秒兆体素(MVx/sec)为单位,因为人们通常希望处理特定“物理”大小的体积。然而,表 2、图 4 和图 5 以每秒兆字节(MB/sec)为单位,因为运行之间的差异主要在于存储器的大小。
无数 3D 的四个变体由基本算法的 3D 和 N 维实现以及它们的动态编程对应物组成。在基本算法和动态算法中,具体实现的性能与通用实现非常相似。所有这些比较的误差都在 5%以内。使用动态编程算法处理这个 128 MVx 的立方体大约需要 5 秒钟。
Figure 3. Python 3 Downsample Performance in Megavoxels/sec on a 512x512x512 Numpy Array by Array Data Type, 5 Trials showing only COUNTLESS 3D variants
就 MVx/sec 而言,对于 uint8 阵列,动态编程产生了大约 2.2 倍的速度提升。对于 uint16 和 uint32,它更接近 1.5 倍。对于 uint64,它大约是 1.27 倍。因此,随着内存的增加,性能有明显的下降趋势,但这在某种程度上被以 MB/秒为单位测量的性能所掩盖。
Table 2. Python 3 Downsample Performance in Megabytes/sec on a 512x512x512 Numpy Array by Array Data Type, 5 Trials
图 4 清楚地表明,在 uint8 和 uint64 之间,性能实际上略有提高,尽管在动态实现中,uint64 与 uint32 相比略有下降。uint16 的结果似乎是一个异常值。它们与 uint8 性能相当,比 uint32 高出近一倍。我怀疑,要么在库内部,要么在我的芯片上,uint16s 被当作 uint32s 处理。动态编程带来的改进是巨大的,但是在下一节我们将看到这是有代价的。
图 5 是为上下文提供的。平均算法和最大池的简单实现展示了更快的执行速度和几乎与数据类型宽度的增加相称的明显上升趋势。
Figure 4. Python 3 Downsample Performance in Megavoxels/sec on a 512x512x512 Numpy Array by Array Data Type, 5 Trials showing only COUNTLESS 3D variants
Figure 5. Python 3 Downsample Performance in Megavoxels/sec on a 512x512x512 Numpy Array by Array Data Type, 5 Trials including COUNTLESS 3D variants, averaging, and max pooling for context.
内存使用
在无数个 3D 的动态编程实现中,存储子问题所需的内存显然是巨大的。下面,我测量了五次处理 128MB 512x512x512 立方体的内存需求。图 6 示出了操作中的使用基本算法,而图 7 示出了动态编程算法。下面的图表是使用 Python 3.4 在 Ubuntu 14.04 Linux 上使用 mprof 工具生成的。
基本算法相当一致地使用大约 350MB 的 RAM 或大约 2.7 倍的存储立方体所需的内存。动态编程算法需要大约 1150MB,或者大约 9 倍于立方体的内存占用。
Figure 6. Memory profile for ordinary COUNTLESS 3D over five trials.
Figure 7. Memory profile for dynamic programming COUNTLESS 3D over five trials.
讨论
无数的 3D 是一个“慢”的算法。更快的动态编程(DP)变体具有指数级的时间复杂度O(2^(N-1))
,而基本变体稍微慢于此。DP 变体具有组合增长的空间复杂性。然而,非常幸运的是,2x2 (N=4)和 2x2x2 (N=8)的特殊情况是常用的,并且足够小,甚至在这些可怕的条件下也是实用的。在某些情况下,2x2 的情况显示出能够优于实现为 O(N)的标准计数算法,尽管它可以通过使用 hashmaps 减少到 O(N )(尽管由于常数开销增加,它对于小 N 可能会更慢)。
DP 算法的速度提高了 1.5 到 2.2 倍,但内存价格昂贵。无数 3D 的基本变体消耗的内存不到 DP 变体的三分之一。花 3 倍的内存换 2 倍的速度。幸运的是,即使在 uint64 的情况下(~ 8–10gb),对于单个进程的消费级设置来说,这仍然是一个合理的范围。
可以创建比无数 3D 运行速度快很多倍的 C 或 Cython 代码,而几乎不占用额外的空间。尽管如此,无数的 3D 可以服务于一个特殊的领域:使用普通的 Python/numpy 安装在中等大小的数据集上运行 3D 缩减采样,而不需要额外的编译。这种 3D 算法可以帮助研究人员和程序员,他们需要一个可以在 Python/numpy 所在的地方可靠部署的库。
这些研究人员可以获得最佳性能的一个用例是对感兴趣区域的二进制掩膜进行下采样。某些种类的语义分割可以利用少量的标签。在这种情况下,uint8 阵列提供了足够多的标签,并受益于约 20 MVx/秒的处理速度。在连接组学中,更典型的要求是小容量的 uint16,以及大型项目的 uint32 或 uint64。对于这些情况,我推荐编译代码。
这篇文章存在的一个重要原因是,我在研究无数 2D 的全面概括时获得了乐趣。感谢阅读。😇
未来的工作
我计划在将来展示 C 实现之间的比较。我的预期是,它的时钟频率将超过 100 MVx/秒。Github 上有一些 C 代码,但是我还不能确信它是正确的。
承认
在开发无数的 3D 和撰写本文的过程中,一些人提供了有益的建议和帮助。 Kisuk Lee 提供了图 1 和图 2 所示的 3D 数据的可视化。Chris Jordan 提供了 C 实现计数和无数的种子。乔治·纳吉博士建议概括无数的 2D。Google 的 Jeremy Maitin-Shepard 最初开发了用于 neuroglancer 的大步走和下采样平均的 Python 代码。特别感谢 Seung Lab 提供神经分割标签。
密码
用于测试该管道的代码可以在 Github 上找到。欢迎其他语言的贡献和反馈,并将记入贷方。
**2019 年 3 月 11 日:**现作为 tinybrain PyPI 包的一部分提供。
本文是“ ”无数——使用 Python 和 Numpy ”对标记图像进行高性能 2 倍下采样(2017)的后续。
无数—使用 Python 和 Numpy 对标签图像进行高性能 2 倍缩减采样
Figure 1. A partial of a slice of SNEMI3D, a machine learning contest dataset for neuroscience.
管理 terravoxel 图像上的 convnet 输出通常涉及生成可在廉价硬件上更容易下载、显示和处理的摘要图像。对普通照片或显微镜图像进行下采样的常用方法是在图像上定义一个窗口,然后应用 averaging 或 lanczos3 (sinc)等过滤器将窗口内容汇总到一个较小的像素集中。这些混合方法不适用于分段标签。一组分割标签的下采样必须包含来自输入图像的实际像素值,因为标签是分类的,混合标签是无意义的。如果标记为 1 的像素指的是汽车,标记为 3 的像素指的是鸟,那么这两个像素的平均值(2 指的是人)就不是底层图像的忠实表示。
因此,下采样分类标签包括在图像上定义窗口并从该块中选择样本。一种常见的方法是通过在块中最频繁的像素中挑选来选择样本,也称为寻找模式。实现这一点最明显的方法是计算每个标签的频率,这在 C 等高性能语言中很容易实现。然而,Python 循环非常慢,这使得这种方法在不使用 C 扩展(Cython)的情况下无法实现,这使得项目维护起来更加麻烦,并且需要专业知识。在这里,我给出了一个方法 neutrally,它计算四个无符号整数的模,同时给出了一个 Numpy 实现,用于生成标签图像的 2x 缩减采样。
无数算法
要解决的最简单的 2D 下采样问题是 4 像素 2x2 图像。2x2 图像可以通过其最常见的单个像素来概括,以在每一侧实现 2 倍的缩减。现在坚持使用均匀大小的图像,一个较大的图像可以被分成 2×2 的块,如果这个过程在每个块上独立重复,这将导致整个图像整体缩小 2 倍。
2x2 图像由图 1 中列出的五个问题组成。在 1(a)、1©和 1(e)中,所有像素都在最频繁的类别中,因此是有效的解决方案。1(b)和 1(d)需要更复杂的方法。值得注意的是,在所有五种情况下,选择随机像素更有可能是大多数,这表明为什么大步走可以是一种可接受的,尽管不是完美的方法。形成该算法基础的关键见解是,在所有情况下,如果两个像素匹配,则它们占多数。
Figure 2. The Five Cases. Capital letters A,B,C,D refer to the identity of a non-zero pixel. (a) All the same. (b) Two the same. © Two pairs the same. (d) Three the same. (e) All different.
在下文中,大写字母 A、B、C、D 指的是像素位置的非零值。我们定义了比较操作符 PICK(A,B ),它生成一个实像素值或零。
PICK(A,B) := A if A == B else 0 EQN. 1
在 Python/numpy 这样的框架中,True 和 False 分别表示为 1 和 0,这可以用数字实现为:
PICK(A,B) := A * (A == B) EQN. 2
接下来,让我们考虑 A、B 和 c 之间的各种相互作用。在下表中,符号 AB 表示 PICK(A,B ),小写字母表示特定值,因此两列中重复的字母“A”表示两个像素都是红色。
Pixel PICK(X,Y)
A B C AB BC AC
a a a => a a a
a b a => 0 0 a
a a b => a 0 0
b a a => 0 a 0
a b c => 0 0 0 <-- Only fully zeroed rowTABLE 1\. PICK(X,Y) (denoted XY) interactions between A, B, and C
表 1 显示 A、B 和 C 中仅有的多数像素或零点将作为拾取操作的结果出现。在 A、B 和 C 都不相同的情况下,所有选择都将返回零。这使得像素选择的问题服从于简单的逻辑。a、B 和 C 都不同,对应于图 1 中的情况 1(b)或 1(e ),在 1(b)的情况中 D 占大多数。如果情况是 1(b),这意味着 D 是一个可接受的解决方案。如果情况是 1(e),则不存在多数像素,D 也是可接受的解决方案。
因此,当 A、B 或 C 匹配时,选择匹配项,当它们都不匹配时,选择 d。这可以在计算机语言中用短路逻辑 OR (||)表示为:
MODE(A,B,C,D) := PICK(A,B) || PICK(B,C) || PICK(A,C) || D EQN. 3
我们可以将逻辑或数字实现为:
LOGICAL_OR(X,Y) := X + (X == 0) * Y EQN. 4
EQN。3 和 EQN。4 将正确处理除零以外的所有无符号整数值。因此,在零是有效像素的情况下,我们可以在算法开始时给图像加一,然后在返回结果之前减一。
2x2 方法可以很容易地扩展到覆盖任何偶数尺寸的图像。只需用不重叠的 2x2 块覆盖图像,并求解每个块中的模式,即可生成缩减采样图像。然而,我们仍然必须处理奇数图像,其中边缘没有被 2x2 块完全覆盖。
幸运的是,有一个简单的解决方案。对于任何奇数图像,镜像边缘以生成偶数图像。有两种情况:角(1x1)和边(2x1 或 1x2)。镜像一个角将产生情况 1(a ),这将导致绘制相同的像素。镜像一条边将导致情况 1(a )(如果像素相同)或者情况 1(c ),这两种情况都将被无数个正确处理。
履行
Figure 3. Results of applying COUNTLESS. (a) A source image of 128 x 128 pixels is reduced to (b) an image of 64 x 64 pixels.
Numpy 中无数的实现是简单明了的。首先,图像必须被分成 2×2 块的覆盖。这可以通过创建四个 2D 数组 a、b、c 和 d 来表示,每个数组在概念上表示图 1 中与其同名的像素,但在图像中的每个 2x2 块上并行执行。这是通过从左上角跨越(2,2)偏移(0,0)、(0,1)、(1,0)和(1,1)来实现的。接下来,我们开始用无数的。Numpy 不支持逻辑 OR,但它支持按位 OR。幸运的是,根据表 1,从 PICK(A,B)、PICK(A,C)和 PICK(B,C)得到的值要么是单个值,可能是重复值,要么为零。因此,在这种特殊情况下,按位“或”的行为与逻辑“或”相同,为我们节省了一些在 EQN 中需要的运算。4.清单 1 展示了实现:
Listing 1. The simplest implementation of countless that doesn’t handle black pixels.
这种实现适用于大多数情况,但是它有一个重要的故障模式。如果匹配像素为零,我们将意外地选择 D,因为结果看起来与表 1 中的最后一行相同。不幸的是,当使用有限整数表示时,这个问题无法完全消除,但是我们可以非常接近。
策略是在执行无数次之前给图像加一,之后减一。这会将 0 变成 1,并使算法正确工作,但它会导致最大值整数溢出(uint 8 为 255,uint16 为 65,535,依此类推)。但是,在添加数据类型之前转换为下一个最大的数据类型可以消除溢出效应(例如,将 uint8 数组转换为 uint16)。在目前的硬件上,这种方法在 uint64 以下是可行的。uint8、uint16、uint32 完全消除了零点问题,但 uint64 没有。这意味着,如果您的标注包含大约为 1.84 x 10 ⁹.的 2⁶⁴-1,该算法将会失败对于许多用途,这应该是可以接受的。减去一后强制转换回原始数据类型。对于将最大 uint64 视为特殊标志的编码方案,只需充分改变偏移量即可。
Listing 2. zero_corrected_countless.py: simplest_countless.py updated to handle black pixels correctly.
最后一件事,我们添加了一些操作来解决零标签问题,但是这会影响性能。我们可以通过注意到 ab 和 ac 都乘以 a 来恢复其中的一部分。
Listing 3. zero_corrected_countless.py augmented with an algebraic simplification to slightly improve performance.
表演
为了确定这种算法的速度,我们开发了一个比较套件,并在双核 2.8 GHz、i7 Macbook Pro(大约 2014 年)、256 kB L2 高速缓存和 4MB 三级高速缓存上运行。虽然该算法是为分割标签开发的,但普通照片也包括在内,以展示当数据不均匀时该算法如何执行。最大汇集、平均和大步跑包括在速度比较中,尽管它们不适合这项任务。
我用 Python 3.6.2 配合 numpy-1.13.3 和 clang-802.0.42 做了以下实验。
经过测试的算法:
- **大步走:**每隔一个像素拾取一次。
- **统计:**统计每个标签出现的频率。
- **最简单 _ 无数:**最简单 _ 无数. py
- **快速 _ 无数:**最简单 _ 无数. py +代数化简
- **零 _ 校正 _ 无数:**零 _ 校正 _ 无数. py
- **无数:**无数. py
- 无数 _if: 用 if 语句代替花里胡哨的把戏
尽管这些算法不适合处理分割,但也对它们进行了测试,以便为其他图像处理算法提供比较点:
- **down sample _ with _ averaging:**对 2x2 窗口中的像素进行平均。
- **down sample _ with _ max _ pooling:**在 2x2 窗口中选取最高值像素。
- ndzoom: 使用 scipy 函数 ndimage.interpolation.zoom 缩小到 2x2。
用于测试算法的代码可以在这里找到。
在这篇文章发表后 Aleks Zlateski 贡献了一个骑士登陆(KNL)的矢量化版本 bitwise neutrally,据报道在随机 int32 数据上运行速度为 1 GPx/sec,4 GB/sec。可以在 Github 上找到。
试验 1–神经组织的分割
Figure 4. Integer labels representing a segmentation of a neural tissue micrograph.
RGB 分割是由卷积神经网络分配的像素标签的 1024x1024 图像,有趣地看着神经组织。这是无数在设计时就考虑到的那种形象*。每个像素是一个 RGB 三元组,合起来表示一个无符号整数。比如,(R,G,B): (15,1,0)代表 271 (15 + 1 * 256)。*
在表 2 中,虽然它不符合我们选择最频繁像素的标准,但大步走显然是速度恶魔。它似乎只受到内存带宽的限制。单纯的计数运行速度仅为 38 kPx/秒,这意味着计算一张图像需要大约 27.6 秒。
无数算法的各种版本在从 986 kPx/秒到 38.59 MPx/秒的宽范围内运行,轻松击败了计数。无数 _if 实际上是使用 if 语句测试两个像素是否匹配的计数实现的变体。其他无数变体之间的主要性能差异取决于我们是否正确地处理零(在无数和快速 _ 无数之间有 37%的差异,在最简单 _ 无数和零 _ 校正 _ 无数之间有 39%的差异)以及乘法是否被简化掉(在最简单 _ 无数和快速 _ 无数之间有 13.8%的加速,以及 15.6% 【T34
将该算法最快的综合变体与其他两种常见的下采样方法进行比较,结果表明它比平均法慢 1.7 倍,比最大池法慢 3.1 倍。如果我们要处理不包含零作为有效像素的图像,相对差异将分别慢 1.3 倍和 2.3 倍。
Table 2. Python algorithm performance on a three channel 1024x1024 segmentation image. N=200 except where noted.
Figure 5. Python algorithm performance on three channel 1024 x 1024 segmentation. Striding omitted to better show the other algorithms.
试验 2 —灰色分割
Figure 6. A grayscale version of the RGB segmentation from Trial 1.
三通道存储器布局对算法性能有什么影响?在灰度(即单通道)版本的试用 1 图像上运行相同的一组测试。这个试验比试验 1 更类似于测量现实世界任务的性能,尽管我们更经常在 uint16、uint32 和 uint64 阵列上操作而不是 uint8。
灰色图像的每像素字节数比 RGB 少三倍。如果这种关系是简单的线性关系,那么人们会认为 MB/秒的数字大致保持不变,而 MPx/秒会提高三倍,但事实并非如此。对于最简单 _ 无数来说,MB/秒在灰度上快了 17.4 倍左右。这可能是由于内存中 RGB 通道的非连续布局,而灰度得益于这种内存访问效率的提高。该试验的迭代次数增加到 1000 次,以允许试验运行与试验 1 大致相似的时间长度。由于计数和无数 _if 已经被认为是缓慢的,为了方便起见,它们在五次迭代中被测量,这仍然导致大量挂钟时间。
还测试了 counting、quick _ neutrally 和 neutrally _ if 的 C 实现。正如所料,在 MPx/sec 测量上,C 代码以大约 2.9 倍(对于quick _ neutrable)到 1025 倍(对于neutrable _ if)的速度击败了 Python。虽然看到quick _ neutrally从 C 实现中获得巨大的速度提升并不令人惊讶,但neutrally _ if中的巨大增益令人印象深刻,以 3.12 GPx/sec 成为赢家。
Table 3. Python algorithm performance on a one channel 1024 x 1024 version of the image from Trial 1. N=1000 except where noted.
Figure 7. Python algorithm performance on a single uint8 channel 1024 x 1024 image. Striding is omitted from this figure because it’s so fast it makes the differences between the other algorithms hard to see.
Figure 8. C implementation performance on counting, quick_countless, and countless_if. All three far exceed Python implementations and reach into the GPx/sec range. countless_if is by far the winner.
试验 3——灰色冰淇淋人(GICM)
Figure 9. Photograph of a man eating ice cream. 5456 × 3632 pixels, uint8
灰色冰淇淋人(GICM)是一个相对较大的 DSLR 照片转换成灰度。存在显著的动态范围和模糊效果,使得图像在像素与像素之间变化显著。这意味着 CPU 管道中的分支预测将比试验 2 中更频繁地失败。图像的大尺寸使得每次测试更加稳定,同时也为 CPU 性能的均衡提供了更多的时间。
同样,在表 4 中,大步走显然是赢家。简单的计数运行速度仅为 44 kPx/秒,这意味着计算一张图像需要大约 171 秒。无数算法的各种版本在从 2.4 MPx/秒到 594.7 MPx/秒的广泛范围内运行,轻松击败了计数算法。
其他无数变体之间的主要性能差异取决于我们是否正确处理零(无数和之间的 3.2 倍差异,以及最简单 _ 无数和之间的 3.2 倍差异)。代数化简在最简单 _ 无数和快速 _ 无数之间占 14.9%,在无数和零 _ 校正 _ 无数之间占 16.2%。**
有趣的是,在这种情况下,与灰度分割相比,quick _ neutrally比down sample _ with _ averaging和down sample _ with _ max _ pooling表现得更好。
这里的 C 结果与试验 2 非常相似,但是有一些有趣的特点需要注意。无数 _ 如果跌了 617 MPx/秒(~20%)。这可能是由于在非同质映像上分支预测失败的增加。快速 _ 无数在定性而非定量测量的误差范围内保持稳定。无数 _if 要快得多,但 quick _ numbery 在不同图像上的性能更可预测,尽管无数 _if 在测试图像上的性能变化似乎始终高于quick _ numbery。
Table 4. Python/numpy algorithm performance on Gray Ice Cream Man. N = 200 except where noted.
Figure 10. Python Algorithm Performance on GICM. Striding is omitted from this figure because it’s so fast it makes the differences between the other algorithms hard to see.
Figure 11. C Implementation of three algorithms. counting is still in last place, but countless_if is now far ahead of standard countless.
讨论
neutrally 的标准 Python/numpy 实现相对于计数方法的原始实现表现出了巨大的性能增益,并且在性能上与图像处理社区中大量使用的简单方法平均值和最大池相当。在 Seung Lab 的生产图像处理管道中,我们经常处理大小为 2048x2048 的 64 个图像块进行下采样。重要的是,处理时间与下载时间相当,以确保有效的管道,无数做这项工作。
在 C 实现中有数不清的好处。一个优化的计数实现能够在 GICM 上实现 880 MPx/sec,比最快的 Python 实现quick _ neutrable快大约 1.48 倍。然而,quick _ numbered最快的基于位运算符的 C 实现达到了 1.9 GPx/秒,而基于 if 语句的实现numbered _ if达到了 2.5 GPx/秒,这意味着单个内核每秒可以处理大约 9 个 2048x2048x64 块,而使用计数大约可以处理 3 个。作为参考,一个给定的数据集可以包含数万个或更多这样的块。
然而,似乎至少在 C 语言中,与按位运算符相关的巧妙之处可能并不那么有用。毕竟,简单的 if 语句打败了它们。然而,有一个技术领域,按位运算符会胜出。我们已经证明了在 Python/numpy 中无数的方法是有用的,然而,一般的方法在其他带有矢量化运算符的解释语言中似乎也能成功。尽管 Julia 是一种编译语言,但在 MATLAB、Octave、R 和 Julia 中,按位无数可能也是值得的。按位变量似乎特别适合 GPU 实现,其中 if 语句非常昂贵。虽然要实现这一点,环境必须相当特殊,但如果使用 Z 顺序曲线重新排列输入,似乎有可能通过向量化指令大大加快 C 实现逐位无数的速度。(这篇文章发表后,Aleks Zlateski 的 KNL 矢量化实现达到了 4gb/秒的速度,最大限度地提高了内存带宽。)
无数算法允许基于最频繁的值快速生成分割的 2x 下采样。可以想象,这种能力可能在各种机器学习管道中有用,甚至可能在算法中有用(尽管这种情况需要特殊,以支持模式而不是最大池)。无数是寻找四个数的模式的一般方法,甚至可能有其他与图像处理无关的应用。
无数确实有两个缺点。第一个是,虽然它可以递归使用,但只有第一次迭代才能保证是原始图像的模式。4x4 图像的模式可能不同于四个 2x2 图像的模式。第二个缺点是 Python 中的按位无数(虽然 C 中没有)比计数需要更多的内存。原始图像、 a 、 b 、 c 、 d 以及中间运算的结果,并且在算法运行时必须保留最终结果,这导致至少四倍的存储器增加,而计数只需要比原始数据多存储少量的整数。如果在生成 a 、 b 、 c 和 d 后可以丢弃原始数据,那么只需要增加三倍。需要注意的是无数 _if 同样只需要几个整数。
在它的首次应用中,使用 Python/numpy 递归生成了从脑组织的大型电子显微照片中得到的分割的 MIP 图。虽然只有第一个 MIP 级别保证是模式,但生成的 MIP 级别比大步流星混合得更好,后者往往会随着新 MIP 级别的加载而沿对角线穿过屏幕。
是无数的新?这种图像处理算法可能以前已经被发明过,并且基本的数学几乎肯定已经被用在像纯数学这样的其他环境中。但是,我还没有找到它的参考文献。
有几个潜在的卓有成效的方向来扩展无数的算法。第一个涉及随机图像的问题,第二个涉及将算法扩展到三维以处理体积组织图像。
关于随机图像,回过头来看案例 1(e ),我们将始终选择右下角,在随机或病理数据上,它可能会导致与天真大步走相同的对角线偏移效果。这种伪像是由 1©和 1(e)中出现的 d 的按位或引起的。也许可以通过增加一项(a!来把 1©和 1(e)分开。= b!= c!= d)然而,考虑到 1e,如何在图像中的所有 2x2 块上并行化 a、b、c 或 d 的随机选择并不明显。这种改变也可能是不希望的,因为它使输出不确定。
关于体积图像,由于我的实验室处理脑组织的 3D 图像,因此提出了这样一个问题,即这种方法是否可以扩展到 2x2x2 立方体(8 的模式)。分析这个问题的最简单的方法是考虑一个更简单的情况,即我们是否可以扩展这种方法,采取五个整数的模式而不计数。在这种情况下,如果至少有三个像素匹配,那么匹配的像素保证是正确的。然而,如果没有匹配,则取决于两个是否匹配,如果没有两个匹配,则任何像素都是候选。很明显,扩展这种方法需要进行大量的组合比较。虽然可以想象这比数五个数更有效,但回报会迅速减少。
在某种程度上,基于 if 语句的 neutrally 是一种识字前的算法,如果没有人学会如何计数,它就会被使用。很明显,试图在 C 语言中对大数模式的计数上胜出是很困难的。然而,在 Python 中,quick _ neutral比countonGICM 有 5263 倍的优势,这意味着即使在 3D 情况下效率很低,仍有很大的改进空间。一个早期的演示表明,在 Python/numpy 中,3D neutrally 可能快至约 4 兆体素/秒,比 2D 计数快约 35 倍。还会有更多的实验。
***编辑 2018 年 2 月 14 日:*在即将发表的一篇关于无数 3D 的文章中,我将使用 Python3/numpy 记录高达 24.9 MVx/sec 的速度。
***编辑 2018 . 2 . 20:*无数的3D 文章现已出。
***编辑 2018 年 6 月 21 日:*如果你想在稀疏数据上使用无数的 2D 而不把上部下采样变黑,试试点画无数的 2D 。
感谢
几个人提供了有益的建议和援助,在开发无数。Chris Jordan 提供了 C 实现计数和无数的种子。阿列克斯·兹拉特斯基博士在这篇文章发表后贡献了一个骑士道 SIMD 版本。ndzoom 基准测试由 Davit Buniatyan 提供。George Nagy 博士建议测试无数 _if* 并在同质和非同质图像上测试性能差异。谷歌的 Jeremy Maitin-Shepard 最初开发的 Python 代码用于与 neuroglancer 一起使用的大步走和下采样平均。特别感谢 Seung Lab 提供神经分段标签。*
密码
用于测试该管道的代码可以在 github 上找到。欢迎其他语言的贡献和反馈,并将记入贷方。
***2019 年 3 月 11 日:*现已作为 tinybrain PyPI 包的一部分提供。
更新和勘误表
***2019 . 12 . 10:*之所以跨步这么快,是因为测试的操作只是更新 numpy 数组的内部跨步数;它实际上并没有复制数组。应该重新进行这些实验来反映这一点。
***2018 年 7 月 9 日:*找到了消除变量 bc 的方法,重用 ab_ac 进行小加速(~2%?).请查看 github 获取此更新。
***2018 年 2 月 14 日:*更新了图表和文本,具有现在使用 Python3.6.2 的 Python 代码的更新基准。表 2、3、& 4 和图 5、7、& 10 已被替换。基准现在包括由戴维·布尼亚延贡献的 scipy 函数 ndimage.interpolation.zoom 的代码。❤️
***2018 年 2 月 1 日:*我在 Python 基准测试代码中发现了一个错误,该错误导致大多数算法的速度被低估了 4 倍。我将很快更新这篇文章,带来新的结果。我已经找到了一种方法,使无数的实现更快,更有效的内存。Python3 也比 Python2 快。最新见 Github 。
***2017 年 8 月 21 日:*在这篇文章发表后 Aleks Zlateski 贡献了一个骑士登陆(KNL)矢量化版本的 bitwise neutral,据报道,该版本在随机 int32 数据上以 1 GPx/sec,4 GB/sec 的速度运行。可以在 Github 上找到。
课程 1 —算法工具箱—第 1 部分:简介
我在做全职工作,创业工作 5 年了。加入软件开发世界是一种幸运。我每天都有机会学习新东西。我构建了许多有效的应用程序(我以前主要是 iOS 开发人员),许多 RoR、React、Elixir 产品代码正在运行。一切似乎都很好。但是有一天,我深刻地意识到我错过了一些东西,让我的工作更上一层楼。所以我回顾了一下基础知识,看到术语“算法和数据结构”一直在重复。复习完知识后,我决定花时间认真学习算法和数据结构。
幸运的是,这个世界对学习者来说充满了开放的机会。Coursera 为愿意学习的人带来了世界顶尖大学的精彩讲座。毫无疑问,加州大学圣地亚哥分校高等经济学院的“数据结构和算法”6 门专业课程脱颖而出。我的朋友正在上课,并向我强烈推荐。
这一系列的博客类似于课堂笔记,包括我基于这些课程材料的想法和实践。它还包含一些我已经解决的问题的提示。如果你有任何问题或评论,请在这里或 github 留言。我愿意一起学习。我做这些笔记是为了帮助我复习。希望对你也有帮助,所以公开一下。让我们跳进游泳池吧!
Getting started with Algorithms
问题 1:最大两两乘积。
解决方案:
我们只需要将两个最大的数字相乘。
问题 2:大公约数(GCD)
两个非负整数 a 和 b(不都等于 0)的最大公约数 GCD(a,b)是除 a 和 b 的最大整数 d。
你在这个问题中的目标是实现计算最大公约数的欧几里德算法。计算最大公约数的高效算法是 RSA 等常用密码算法的重要基本原语。
解决方案:
问题 3:最小公倍数
两个正整数 a 和 b 的最小公倍数是能被 a 和 b 整除的最小正整数 m。
解决方案:
如果你知道 LCM(a,b)。GCD(a,b) = ab,复用上一个 GCD 算法,这个问题的算法会很好写。
注意: 在 python 中使用//在 Python3 中对大数进行右除运算。
问题四:斐波那契数
斐波那契数列的定义:
解决方案:
很容易实现简单的递归算法来计算斐波那契数。
但是运行时间不好。你可以试着计算一下 F40 ,这会花费相当长的时间。
我们需要一种更快的方法来计算斐波那契数列。
练习 1: 用归纳法证明 n ≥ 6 时 Fn ≥ 2^(0.5n)。
证明:
*assume
练习 2: 求一常数 c < 1 使得对于所有 n ≥ 0,Fn ≤ 2^cn。表明你的答案是正确的。
证明:
问题 5:一个大斐波那契数的最后一位数字
求第 n 个斐波那契数的最后一位数。回想一下,斐波那契数列以指数速度增长。举个例子,
f200 = 280571172992510140037611932413038677189525。
解决方案:
因为它的增长是指数级的快,所以我们不能用 fast_fibonacci(n) % 10 来给出答案。我们需要另一种方法来解决这个问题。我们只对 Fn-1 的最后一位和 Fn-2 的最后一位求和,而不是对 Fn-1 + Fn-2 求和。
高级问题 6:模 m 的巨大斐波那契数
Pisano alike
求 Fn 模 m,这里 n 可能真的很大:高达 1018。对于这样的 n 值,循环 n 次迭代的算法肯定不适合一秒钟。因此,我们需要避免这样的循环。为了了解如何解决这个问题,而不需要遍历 I 从 0 到 n 的所有 Fi,让我们看看当 m 很小时会发生什么,比如 m = 2 或 m = 3。
所以可以观察到它有周期。Fn 模式 2 具有长度为 3 的 011 周期,Fn 模式 3 具有长度为 8 的 01120221 周期。因此,要计算 F2016 mod 3,我们只需找到 2016 除以 8 的余数。自 2016 年= 251.8 + 8。我们的结论是 F2016 mod 3 = F8 mod 3 = 0。
这在一般情况下是成立的:对于任何 m >= 2 的整数,序列 Fn mod m 是周期的。周期总是从 01 开始,被称为皮萨诺周期。
解决方案:
基于这一假设,我们通过检查 Pisano 周期是否由 01 重复并保持重复 Pisano 表中的其他数字来检测 Pisano 周期。所以我们检查
pisano_table[half_size] == 0 并且
pisano_table[half_size+1] == 1 并且
pisano _ table[0:half _ size]= = pisano _ table[half _ size:size]
该算法工作正常,并为我们提供了小数字的正确答案。由于数字很大,Pisano 表的长度很长,计算和检测 Pisano 周期需要时间。为了减少计算时间,我们只需检查后面重复的 01 和 10(其他数字也可以)数字的周期。对于大型斐波那契运算,检测 Pisano 表将节省时间。
高级问题 7:斐波那契数列的和
寻找前 n 个斐波那契数列总和的最后一位数。(查找 F0 + F1 + … + Fn 的最后一位数字)
解决方案:
借助于皮萨诺周期,我们可以很容易地计算出任何 Fi 的最后一位数字。我们有 F0 + F1 + … + Fn = F(n+2) — 1。该算法将易于实现:
这里的一个技巧是加 10,以确保我们在 get_fibonacci_huge(n+2,10)的最后一位数字为 0 的情况下返回正余数。
高等问题 8:斐波那契数列的部分和。
寻找斐波那契数列部分和的最后一位数字:Fm + Fm+1 + + Fn。
解决方案:
如果我们得出 Fm + Fm+1 + … + Fn = F(n+2) — F(m+1)。实现该解决方案将会很容易
注意: 我们只取 F(n+2) + 10 的最后一位数字,减去 F(m+1)的最后一位数字,模块得到该数字。
例如:F(n+2) = abc12,F(m+1) = def37,所以要得到正确的最后一位数字,我们必须做:(2+10–7)% 10
旁注:如果你喜欢这个帖子,你会喜欢:https://www.coursera.org/learn/fibonacci/home/info。我喜欢关于迷惑斐波那契的讲座
资源:
最大公约数:[DPV08]第 1.2.3 节,[CLRS]第 31.2 节
计算斐波那契数:[DPV08]的第 0.2 节
斐波那契数列的性质:练习 0.2–0.4[dpv 08]
参考文献:
桑乔伊·达斯古普塔、克里斯托斯·帕帕迪米特里乌和乌梅什·瓦齐拉尼。算法(第一版)。麦格劳-希尔高等教育。2008.
托马斯·h·科尔曼,查尔斯·e·莱瑟森,罗纳德·L·李维斯特,克利福德·斯坦。算法导论(第三版)。麻省理工学院出版社和麦格劳-希尔。2009.
课程 1 —算法工具箱—第 2 部分:大 O 和贪婪
上一篇文章中,我们讨论了与斐波那契数列相关的小编程问题和数学。为了知道算法有多快,我们使用 Big-O 符号。看看是什么!
Big-O 的定义来自[1]:
设 f(n)和 g(n)是正整数到正整数的函数。如果有一个常数 c > 0 使得 f(n) ≤ c . g(n),我们就说 f = O(g)(意思是“f 的增长速度不比 g 快”)。
From lecture 3
当我们处理大 O 记数法时,我们通常会遇到对数。以下是使用对数的规则:
From lecture 3
让我们解决一个小问题来暖一下我们的大脑吧!
***问题:表明对于任意实数常数 a 和 b,其中 b >为 0,*为 **
证明:
加分: 麻省理工给 Big-O 的作业太好了[3]。
贪婪
贪婪算法首先做出局部最优选择。每个阶段,只是贪婪地做出选择,祈祷你会找到全局的答案。太贪心了。
要做的步骤:
-做出贪婪的选择。
-证明这是一个安全的举动。(也就是证明你确实贪婪)。
-归结为一个子问题。
-解决子问题。
玩具问题举例:
-由数字 3、9、5、9、7、1 组成的最大数字是多少?使用所有数字。
可能的解决方案:359179、537991、913579。。。
在用贪婪算法解决这个问题时,我们一步一步地做:
-
做一个贪婪的选择:列表中的最大数字(数字 9)。
-
证明这是一个安全的移动:列表只包含数字(没有大于 10 的数字),最大数字从有效的最大数字(不是零)开始。因此,选择最大数量是一个安全的举动。
-
归结为一个子问题:我们选择最大数字(9),并将其从列表中移除。左边的列表包含[ 3,5,9,7,1 ]。我们在每一步都有更小的列表。
-
解决子问题:我们又有一个列表,只要选择最大值,一直做,直到列表为空。你会得到答案的。
正确答案:997531。
分数背包
分数背包问题是计算机科学中众所周知的问题。我们可以用贪婪策略来实现背包问题(简单版):
**输入:**权重 w1,w2,…,wn 和值 v1,v2,…。n 个项目的 VN;容量 w。找出一个容量为 w 的袋子中的物品的最大总价值(简单的版本,意味着我们可以将 wi 分成许多重量单位)
例:我们有 3 种包装:价值 20 美元,重 4 公斤;价值 18 美元,重 3 公斤;价值 14 美元,重 2 公斤。我们只能负重 7 公斤。那么,我们如何选择最大化我们的价值呢?
明确的答案是选择 2 公斤 14 美元、3 公斤 18 美元和 2 公斤 20 美元,这样我们就可以得到 14 美元+18 美元+20 美元/2 = 42 美元的价值。
注:2 公斤和 3 公斤的最大值为每单位 14/2 美元和 18/3 美元。
使用贪婪策略来解决这个问题。我们一步一步来。
- **做一个贪婪的选择:**尽可能多的选择单位重量价值最大的物品。
a = 20 美元/4 = 5 美元/单位
b = 18 美元/3 = 6 美元/单位
c = 14 美元/2 = 7 美元/单位。
所以我们先选 A,B 再选 c。
- **证明它是安全之举:**我们把任何物品 I 分成 wi 个更小的物品,每个物品都有 vi/wi 的单位重量。我们有 w0 + w1 + …+ w2 数量的单位项目。因为是单位,所以我们就尽可能选择单位有最大值的。
——**将问题化简为子问题:**在我们选择了一个贪婪的选择 wi 的值为 vi 之后,我们将其从列表中移除,这样我们就有 n-1 个项目对应 n-1 个值。
- **解决子问题:**一直选择每单位最大值,直到容量 W 满为止。(您可以将最后一个分成较小的重量,以适应容量 W)
From lecture 3
背包的运行时间是 O(n)。
问题 1:换钱
该问题的目标是找到将输入值(整数)转换为面值为 1、5 和 10 的硬币所需的最少硬币数。
**输入格式:**输入由一个整数 m 组成。(1 < = m < = 10)
**输出格式:**输出改变 m 的面额为 1,5,10 的硬币的最小数量。
样品 1:
输入:3
输出:3
解释:3 = 1 + 1 + 1
样品 2:
输入:28
产出:6
解释:28 = 10 + 10 + 5 + 1 + 1 + 1。
解:
贪婪的选择是选择尽可能多的数量最大的硬币。
问题 2:最大化战利品的价值
一个小偷发现了比他的包能装得下的更多的赃物。帮助他找到最有价值的物品组合,假设战利品的任何部分都可以放进他的包里。
这个代码问题的目标是实现一个派系背包问题的算法。
**输入格式:**输入的第一行包含物品数量 n 和一个背包的容量 W。接下来的 n 行定义了项目的值和重量。第 I 行包含整数 vi 和 wi,分别是第 I 项的值和权重。
**输出格式:**输出适合背包的物品分数的最大值。你的程序的答案和最优值之差的绝对值最多应该是 10 ^ 3。为了确保这一点,输出您的答案时小数点后至少要有四位数字(否则,虽然您的答案计算正确,但可能会因为舍入问题而出错)。
样品 1:
输入:
3 50
6 20
100 50
120 30
产量: 180.0000
解释:
要达到数值 180,我们把第一个物品和第三个物品放进袋子里。
解决方案:
上面有伪代码,我们可以很容易地实现派系背包。
这里的要点是从价值/重量中选择最大指数。将该值添加到结果中,并清除原始列表中的值。
问题 3:在线广告投放收入最大化
你有 n 个广告可以放在一个流行的网页上。对于每个广告,你知道广告商愿意为这个广告的一次点击支付多少钱。您已经在页面上设置了 n 个位置,并估计了每个位置每天的预期点击数。现在,你的目标是在广告位之间分配广告,以使总收入最大化。
问题描述:
给定两个序列 a1,a2,…,an (ai 是第 I 个广告的每次点击利润)和 b1,b2,…,bn (bi 是第 I 个槽的平均每天点击次数),我们需要把它们划分成 n 对(ai,bj),使得它们的乘积之和最大化。
**输入格式:**第一行包含整数 n,第二行包含整数 a1,a2,…,an 的序列,第三行包含整数 b1,b2,…,bn 的序列。
**输出格式:**输出∑ai*ci (1 ≤ i ≤ n)的最大值其中 c1,c2,.。。,cn 是 b1,b2,…,bn 的置换。
样品 1。
输入:1 23 39
产量:897
解释:897 = 23 39。
解决方案:
贪婪的选择是为每天点击次数最多的位置选择最高的每次点击利润。为了证明这是一个安全的选择,请阅读作业细节[2]。
所以,我们只要选择最高的 ai 和 bj,compute: result += ai。bj。
问题 4:收集签名
你负责收集某栋大楼所有租户的签名。对于每个房客,你知道他或她在家的一段时间。你想通过尽可能少的拜访建筑物来收集所有的签名。
这个问题的数学模型如下。给你一组直线上的线段,你的目标是在一条直线上标记尽可能少的点,使每一段至少包含一个标记点。
问题描述
给定一组 n 个线段{[a0,b0],[a1,b1],。。。,[an-1,bn-1]}整数坐标在一条直线上,求最小 m 个数的点,使得每段至少包含一个点。也就是说,找出一组最小尺寸的整数 X,使得对于任何线段[ai,bi]都有一个点 x ∈ X 使得 ai ≤ x ≤ bi。
**输入格式:**输入的第一行包含段数 n。下面 n 行中的每一行都包含两个整数 ai 和 bi(用空格分隔),它们定义了第 I 个线段端点的坐标。
**输出格式:**第一行输出最少 m 个点,第二行输出 m 个点的整数坐标(用空格隔开)。您可以按任意顺序输出点。如果有很多这样的点集,可以输出任意一个集合。(不难看出,总是存在一组最小尺寸的点,使得所有这些点的坐标都是整数。)
样品 1:
输入:3
1 3
2 5
3 6
输出: 1 3
说明:
在这个样本中,我们有三段:[1,3],[2,5],[3,6](长度分别为 2,3,3)。都包含坐标为 3 的点:1 ≤3 ≤3,2 ≤3 ≤5,3 ≤ 3 ≤ 6。
样品 2:
输入: 4
4 7
1 3
2 5
5 6
输出: 2
3 6
解释:
第二和第三段包含坐标为 3 的点,而第一和第四段包含坐标为 6 的点。所有四个线段不能被一个点覆盖,因为线段[1,3]和[5,6]是不相交的。
解决方案:
贪婪的选择是选择最小的右端点。然后删除包含该端点的所有线段。继续选择最小右端点并移除线段。
问题 5:最大化竞赛中的奖励名额数量
你正在为孩子们组织一场有趣的比赛。作为奖励基金,你有 n 颗糖果。你想用这些糖果在一场自然限制的比赛中争夺前 k 名,位置越高,糖果越多。为了让尽可能多的孩子快乐,你要找到可能的 k 的最大值。
问题描述
这个问题的目标是将一个给定的正整数 n 表示为尽可能多的两两不同的正整数之和。也就是求 k 的最大值使得 n 可以写成 a1+a2+ +ak 其中 a1,…,ak 是正整数,ai!= aj for all 1 ≤i
**输入格式:**输入由单个整数 n 组成。
**输出格式:**在第一行,输出最大数 k,使得 n 可以表示为 k 个两两不同的正整数之和。在第二行中,输出 k 个两两不同的正整数,总和为 n(如果有许多这样的表示,则输出其中任何一个)。
样品 1:
输入:6
输出:3
1 2 3
样品 2:
输入:8
输出:3
1 2 5
解决方案:
贪婪的选择是从 1,2…中选择最小的数,将它们相加为 S,如果你的最小数大于(input_number— S)/ 2。你找到了最后一个号码。为什么?请看一下作业细节[2]。
高级问题 6:最大化你的工资
作为一次成功面试的最后一个问题,你的老板给你几张写有数字的纸,让你用这些数字组成一个最大的数字。得出的数字就是你的工资,所以你非常想最大化这个数字。你怎么能这样做?
在讲座中,我们考虑了以下算法,用于从给定的一位数中合成最大的数。
不幸的是,这种算法只在输入由一位数组成的情况下有效。例如,对于由两个整数 23 和 3 组成的输入(23 不是个位数!)它返回 233,而实际上最大的数字是 323。换句话说,使用输入中最大的数字作为第一个数字并不安全。
你在这个问题中的目标是调整上面的算法,使它不仅适用于一位数,而且适用于任意正整数。
问题描述
组成一组整数中的最大数。
**输入格式:**输入的第一行包含整数 n .第二行包含整数 a1,a2,…,an。
**输出格式:**输出 a1,a2,.。。,安。
样品 1:
输入:2
21 2
输出:
221
说明:
注意,在这种情况下,上述算法也返回不正确的答案 212。
解决方案:
这个问题有很多边缘情况。根据上面的伪代码,要点是用正确的方法检查“digit ≥ maxDigit”。因为它不再仅仅是数字,你必须确保 12 和 2 => 221,22 和 225 => 22522…为此,比较两个数的策略是:
- 把 A 变成字符串 A
- 把 B 变成字符串 B
- 比较两个字符串 AB 和 BA
资源:
背包:[BB]第 6.5 节
参考文献:
[1]:[DPV]第 0.3 节
[DPV]桑乔伊·达斯古普塔、克里斯托斯·帕帕迪米特里乌和乌姆什·瓦齐拉尼。算法(第一版)。麦格劳-希尔高等教育。2008.
吉勒·布拉萨尔和保罗·布拉特利。算法基础。普伦蒂斯-霍尔。1996.
课程 1 —算法工具箱—第 3 部分:分而治之
我们将在本文中讨论各个击破。我对这个话题很感兴趣。我希望你也能。完成作业花了我很多时间。我强烈建议你认真对待作业,并尝试解决两个高级问题。
Divider.
各个击破
分而治之的步骤:
- 分解成相同类型的不重叠的子问题。
- 解决子问题。
- 结合结果。
二分搜索法:在排序数组中搜索。
**输入:**一个排序数组 A[low…high] (A[i] < A[i+1])和一个 key k。
**输出:**一个指标,I,其中 A[i] = k
否则,最大指数 I,其中 A[i] < k.
Otherwise (k < A[low]), the result is low — 1.
样本 1:
输入:[3,5,9,20,27,52,65]和一个键 20。
输出:3。
样品 2:
输入:[3,5,9,20,27,52,65]和一个键 7。
输出:1。
我们将使用分治算法来解决这个问题。我们将一步一步地解决它。
- 分解成相同类型的不重叠的子问题。
- 输入数组已排序。我们将它分成两半数组。因此在两个子阵列之间没有重叠元素。
2.解决子问题。
- 我们有两个子问题:A 和 B。我们比较密钥 k 和 B[0]。
- 如果 B[0] == k = >我们找到了结果(结果= B[0]的索引)
- 如果 B[0] ≤ k = >我们选择数组 a。(结果= B[0]的索引)
- 如果 B[0] ≥ k = >我们选择数组 B。(结果= A[0]的索引)
3.结合结果。
- 只需返回结果。
伪代码:
Binary Search
二分搜索法的运行时间是 O(logn) 。
乘法多项式
这是非常有趣的部分。我们想要乘以大整数。我们如何做到这一点?除此之外,乘法多项式还应用于纠错码、生成函数、信号处理中的卷积等
我们有两个 n 位数:x 和 y(基数 r = 2,10)。
我们将 x 和 y 表示成两种形式:
Karatsuba’s method
主定理
计算递归算法的 Big-O。我们将公式一般化,并在一般情况下求解。
Master Theorem
这个公式的证明在[2]中。在本文中,我们将关注问题的解决。😃
合并排序
MergeSort 喜欢这个名字,分解和合并。这个概念非常简单:
- 分成两个子列表,直到只剩下一个元素。
- 按顺序合并 2 个子列表(排序)。
伪代码:
MergeSort
这里的关键点是合并策略。循环思考 2 个子数组,选择较小的元素,放入第三个数组。
Merge strategy
大 O 就是 O(nlogn)。
**注意:**下面有一个有趣的问题,需要我们调整合并策略。通过调整,你会完全理解合并排序。
快速排序
快速排序的概念是选择一个支点,重新排列数组,使左支点上的所有元素都小于支点,右支点上的所有元素都大于支点。
例:对于数组 A = [ 6 ,4,2,3,9,8,9,4,7,6,1]
如果我们选择 A[1] = 6 是一个支点,我们需要把 A 重新排列成:
[1,4,2,3,4,6, 6 ,9,7,8,9]
快速排序算法分两步实现:
- 选择支点,重新排列成 A[left] ≤A[pivot] < A[right].
- Keep choosing pivot and re-arrange A[left] and A[right].
The pseudocode for this 2 steps:
QuickSort
As you see, l is left index, r is right index.
Partition(A, l, r) function is containing choosing pivot, and rearrange A from l(left-index) to r(right-index) into A[left] ≤ A[pivot] ≤ A[right] and returning the position(index) of A[pivot].
To choose pivot, we have many strategies. If we know what types of data we have, we will have better choice of pivot. There are common choices of pivot:
- Choose A[0] as a pivot.
- Choose random from l -> r 作为支点
- 中位数的中位数算法。
以[0]为轴心重新排列数组的伪代码:
QuickSort partition
x 将是 pivot 的值。
我们想把 A[l…r]重新排列成 A[left] ≤ A[pivot] ≤ A[right]用
左=左
A[右] = A[j+1…r]
我们将使用最后一个示例来说明如何将 A = [ 6 ,4,2,3,9,8,9,4,7,6,1]转换为[1,4,2,3,4,6, 6 ,9,7,8,9]。
left = 0,r = 10,pivot A[0] = 6。
假设我们有一个[left]只有一个[l]元素,所以 j = 0。
我们从 l+1 到 r 循环 I,每一步都要保证 A[left…j] ≤ A[pivot] ≤ A[j+1…r]。有两种情况:
- A[i] ≥ A[pivot],我们什么都不做,因为它已经是 A[i]≥ A[pivot]。
- A[i] ≤ A[pivot],我们有 A[j]是小于 A[pivot]的元素的最后一个索引。因此,我们需要交换 A[j+1]和 A[i]并标记 j = j+1,以表明 j 是小于 A[pivot]的元素的最后一个索引。通过交换 A[j+1]和 A[i],我们确定 A[l…j] ≤ A[pivot] ≤ A[j+1…r]。
如果您手动运行该算法,我们将得到 A =[ 6 ,4,2,3,4,6,1,9,7,8,9]。
所以我们现在有了数组:A[l] + A[1…j] + A[j+1…r]。最后一步是交换 A[j]和 A[0],这样我们将得到数组:A[0…j-1] + A[pivot] + a[j+1…r]。
于是我们得到:A = [1,4,2,3,4,6, 6 ,9,7,8,9]。
随机化枢纽:
要实现随机化透视,很简单,只需:
- 选择随机支点 l ≤ k ≤ r。
- 交换 A[0]和 A[k]
- 保持分区就像选择一个[o]作为支点。
随机化 pivot 快速排序的 Big-O 在平均运行时间上是 O(nlogn) 。最坏的情况运行时间是 O(n ) 。这个大 O 有点棘手。你可以阅读[3]中的证明。
**注:**快速排序最差的情况是 O(n ) 但是在实践中,快速排序平均给了我们比合并排序更好的性能。
Equal Elements:带有一些 uniq 元素的快速排序。
你可以在这里看到快速排序的可视化。我们可以观察到,如果数据有许多相等的元素,快速排序需要很长时间才能完成。为了优化,我们应该有不同的分区策略:
而不是把 A 重新排列成 A[左]≤A[支点]≤A[右]。我们将把 A 变成:A[左]≤A[m1…m2]≤A[右]有 A[I]= = A[支点] (m1 ≤ i ≤ m2)。
所以在每一步,我们都少了一个[左],一个[右]项要排序。
相等元素的伪代码:
Equal elements QuickSort
在练习中,我们将了解该算法的实现细节。
完全理解这些排序算法和分而治之技术的最好方法是解决有趣的问题。大家一起解决吧。
问题 1:实现二分搜索法
在这个问题中,你将实现二分搜索法算法,该算法允许非常有效地搜索(甚至是巨大的)列表,只要列表是排序的。
**输入格式。**输入的第一行包含一个整数 n 和一个序列 a0 < a1 <。。。< a(n-1)个按升序排列的 n 个两两不同的正整数。下一行包含一个整数 k 和 k 个正整数 b0,b1,.。。,b(k-1)。
约束。 1 ≤ n,k≤10⁵;对于所有 0 ≤i < n,1≤ai≤10⁹t11;对于所有 0≤j < k,1≤bj≤10⁹;
**输出格式。**对于所有的 0 ≤ i ≤ k-1,输出一个索引 0≤j≤n-1,使得 aj = bi,如果没有这样的索引,则输出-1。
样本。
输入:
5 1 5 8 12 13
5 8 1 23 1 11
输出:
2 0 –1 0 –1
解释: 在这个例子中,给我们一个长度为五的递增序列 a0 = 1,a1 = 5,a2 = 8,a3 = 12,a4 = 13,五个键进行搜索:8,1,23,1,11。我们看到 a2 = 8,a0 = 1,但是键 23 和 11 没有出现在序列 a 中,为此,我们输出一个序列 2,0,1,0,1。
解决方案:
我们只是按照上面的二进制搜索伪代码,实现二进制搜索算法。最后,我们循环搜索关键字 b0,b1,…,bk,并运行 binary_search(a,bi) 。
BinarySeach
问题 2:寻找多数元素
多数规则是一种决策规则,它选择拥有多数票(即超过半数的选票)的方案。
给定一系列元素 a1,a2,.。。,您希望检查它是否包含出现 n/2 次以上的元素。下面是一个简单的方法。
该算法的运行时间是二次的。你的目标是使用分治技术设计一个 O(n log n)算法。
**输入格式。**第一行包含整数 n,下一行包含 n 个非负整数 a0,a1,.。。,一个 1。
**输出格式。**如果序列包含一个严格出现次数超过 n/2 次的元素,则输出 1,否则输出 0。
样品 1。
输入 : 5
2 3 9 2 2
输出:
1
解释: 2 是多数元素。
样品 2。
输入 : 4
1 2 3 1
输出:
0
解释 :
这个序列也没有多数元素(注意元素 1 出现了两次,因此不是多数元素)。
你可能已经猜到了,这个问题可以通过分治算法在时间 O(nlogn)内解决。事实上,如果一个长度为 n 的序列包含一个多数元素,那么这个元素也是它的一半的多数元素。因此,要解决这个问题,首先要将一个给定的序列分成两半,并进行两次递归调用。你看到如何组合两个递归调用的结果了吗?
有趣的是,这个问题也可以通过更高级的(非分治)算法在 O(n)时间内解决,该算法只需扫描给定序列两次。
解决方案:
这是一个超级有趣的问题,如果你用分而治之来解决它。
首先,我们需要分成子问题。在每一步,我们将序列分成两个左半序列和右半序列。主要工作是编写合并策略:
为了确定序列的多数,我们将把的左半部分或的右半部分表示成两个元素的数组: A【多数,其他】。
majorities = A[0] 包含在此序列中占多数的所有元素(相同值)。名为 A_major_elements
others = A[1] 是内部没有 A_major_elements 的所有元素。
示例:
left_half = [[2,2,2],[3,5,7,7,9]]
right_half = [[5,5,5,5],[4,8,10,34,2,10,10]]
count_merge 函数将基于左 _half 和右 _half 返回 M【多数,其他】。
为此,我们会:
- 从右半部分[1] ([4,8,10,34,2,10,10])中取出所有左半部分主元素**(例:2) 放入**左半部分[0】。****
- 从左半部分**(【3,5,7,7,9】),中取出所有右半部分主元素(ex: 5) 放入**右半部分【0】。****
所以我们得到了:
left_half = [[2,2,2, 2 ],[3,7,7,9]](加 2,减 5)
right_half = [[5,5,5,5, 5 ,[4,8,10,34,10,10]](加 5,减 2)。
我们称这个过程为 chunk_process :
在 count_merge 函数中,我们只需要根据 chunk_left 和 chunk_right 选择多数。
最终我们会有一个结果数组:【多数,其他】。我们只需检查大多数的长度超过 n/2 就可以给出答案。
问题 3:改进快速排序
这个问题的目标是重新设计随机快速排序算法的一个给定实现,使它即使在包含许多相等元素的序列上也能快速工作。
问题描述
****任务。为了强制快速排序算法的给定实现有效地处理具有很少唯一元素的序列,您的目标是用 3 路分区替换 2 路分区。也就是说,您的新分区过程应该将数组分成三部分:< x 部分、= x 部分和> x 部分。
****输入格式。输入的第一行包含一个整数 n。下一行包含 n 个整数 a0,a1,.。。,a(n-1)。
****输出格式。输出这个按非降序排序的序列。
样品 1。
输入:5
2 3 9 2 2
输出:
2 2 2 3 9
解决方案:
正如我们上面所说的,我们需要为具有许多相等元素的数据优化 QuickSort。因此,我们将阵列划分为 3 个阵列:
A[left]≤A[m1…m2]≤A[right]with A[I]= = Apivot。
我们的主要工作是实现 partition3 函数来重新排列数组 A 并返回位置 m1,m2。
我们从 l + 1 到 r 循环遍历所有元素 I。在每一步,我们确保将 a[i]重新排列到正确的位置。
例如,如果 a[i] ≤ pivot (a[l]),我们将 a[i]重定位到 a[begin…end](与 pivot 具有相同的值)。我们继续检查是否 a[i] == pivot ,我们通过什么都不做来保持那个位置,如果 a[i] < pivot ,我们需要在 a[begin]之前移动 a[i]。
最后,我们只需交换 a0和 a[begin]就可以得到分区(重新排列)数组 a 的最后一步。
这似乎很难理解,但如果你手动运行它,它会在你的手下非常清晰(我保证)。
高级问题 4:一个数据离被排序有多近?
序列 a0,a1,…,a(n-1)的逆是一对指数 0 ≤i < j < n such that ai > aj。在某种意义上,一个序列的反转次数衡量了这个序列接近被排序的程度。例如,一个排序的(非降序)序列根本不包含反转,而在一个降序排序的序列中,任何两个元素构成一个反转(总共 n(n-1)/2 个反转)。
问题描述
****任务。这个问题的目标是统计给定序列的求逆次数。
****输入格式。第一行包含一个整数 n,下一行包含一个整数序列 a0,a1,…,a(n-1)。
****输出格式。输出序列中反转的次数。
样品 1。
输入 : 5
2 3 9 2 9
输出:2
说明:
这里的两个逆是(1,3)(a1 =3 > 2=a3)和(2,3)(a2 =9 > 2=a3)。
这个问题可以通过修改合并排序算法来解决。为此,我们将合并和合并排序过程更改如下:
- Merge(B,C)返回排序后的数组和对(B,C)的数量,使得 b ∈ B,c ∈ C,b > c
- MergeSort(A)返回一个已排序的数组 A 和 A 中的求逆次数。
解决方案:
我们的目标是打印反转次数 ai > aj ( 0 ≤ i ≤ j ≤ n)。我们将数组 A 分为 left_array 和 right_array ,格式:【count _ inversions,sorted_elements】。****
在 merge 函数中,我们可以实现类似 MergeSort 的功能,我们需要为具有left _ array[1][I]>right _ array[1][I]的步骤添加 count_inversions 。
高级问题 5:组织一次抽奖
您正在组织在线抽奖。为了参与,一个人在一个整数上下赌注。然后,随机绘制几个连续整数的范围。然后,参与者的收益与包含该参与者数字的区间数减去不包含该数字的区间数成正比。你需要一个有效的算法来计算所有参与者的收益。一种简单的方法是扫描所有参与者的所有范围列表。然而,你的彩票很受欢迎:你有成千上万的参与者和成千上万的范围。出于这个原因,你不能负担一个缓慢的幼稚算法。
问题描述
任务。给你一组线上的点和一组线上的线段。目标是为每个点计算包含该点的线段数。
输入格式。第一行包含两个非负整数 s 和 p,分别定义线段数和一行上的点数。接下来的 s 行包含定义第 I 个段[ai,bi]的两个整数 ai,bi。下一行包含定义点 x1、x2、.。。,xp。
****输出格式。输出 p 个非负整数 k0,k1,.。。k(p-1),其中 ki 是包含 xi 的片段的数量。
样品 1。
输入:
2 3
0 5
7 10
1 6 11
输出:
1 0 0
解释:
这里我们有两段([0,5],[7,10])和三点([1,6,11])。第一个点仅位于第一个线段中,而其余两个点位于所有给定线段之外。
样品 2。
输入:
3 2
0 5
-3 2
7 10
1 6
输出:
2 0
您可能已经猜到,您的目标是首先以某种方式对给定的片段进行排序(因此,这是一个排序问题,而不是分治问题)。
解决方案:
这个问题很有挑战性。为了解决这个问题,我们必须通过一些策略对给定的片段进行排序。
我们将[ai,aj]和 POINT[pk]变成 3 个数组:[ai,LEFT],[aj,RIGHT],[PK,POINT]左= 1,点= 2,右= 3。
我们尝试排序[ai,LEFT] + [aj,RIGHT] + [pk,POINT]。为了比较两个元素[x,左/右/点]和[y,左/右/点],我们比较 x 和 y,如果 x == y,我们根据左= 1,点= 2,右= 3 的值比较左/右/点。
例如,我们有段:[0,5],[-3,2],[7,10]和点:[1,6]。我们把这些变成:[0,左],[5,右],[-3,左],[2,右],[1,点],[6,点]。然后,我们使用问题 3 中的 RandomizedQuickSort 对这些项目进行排序,我们将得到一个排序后的数组:
[-3,左],[0,左],[1,点],[2,右],[5,右],[6,点],[10,右]。
为了计算覆盖段,我们只需要计算在“点”项之前出现了多少(“左”-右”)项。
为了通过这个问题,我必须提交 11 次大量的变化策略。所以,不要放弃,你比你想象的要优秀!
高级问题 6:寻找最近的一对点
在这个问题中,你的目标是在给定的 n 个点中找到最近的一对点。这是计算几何中的一个基本原语,在例如图形、计算机视觉、交通控制系统中有应用。
问题描述
任务。给定平面上的 n 个点,求一对两点(不同点)之间的最小距离。
输入格式。第一行包含点数 n。下面 n 条线的每一条定义一个点(,易)。
输出格式。输出最小距离。你的程序的答案和最优值之差的绝对值最多应该是 1/10。为了确保这一点,输出您的答案时小数点后至少要有四位数字(否则,虽然您的答案计算正确,但可能会因为舍入问题而出错)。
样品 1 。
输入:
11
4 4
-2 -2
-3 -4
-1 3
2 3
-4 0
1 1
-1 -1
3 –1
-4 2
-2 4
输出:
1.414213
说明:最小距离√2。这个距离上有两对点:(1,1)和(2,2);(2,4)和(1,3)。
解决方案:
在[CLRS]中有一章讨论这个问题,我认为这个问题是这门课中最难的。我经常遇到失败案例#22/23:超过时间限制。所以,一定要先看[CLRS]教材,[33.4 节]关于这个问题。
我们需要通过 x 坐标对点进行预排序,并将点分成两个数组: left_points 和 right_points。我们的目标是计算最小距离的左 _ 点称为最小 _ 左**,最小距离的右 _ 点称为最小 _ 右。和左 _ 点和右 _ 点之间的最小距离称为**混合 _ 最小。****
对于小尺寸的数组(首先是尺寸< 3), we use compute min distance by brute-force. We try to compute 左 _ 最小和右 _ 最小**。我们称左 _min 和右 _min 的最小距离为分隔 _min 。因为 left_points 和 right_points 是按 x 排序的,并且我们确实计算了 separated_min ,所以不计算所有 left_points 的 hybrid_min 和所有 right_points 的 separated_min 半径内具有 x 坐标的点,我们只关心从中线**line _ l =(left _ points【last】。x + right_points[first]。x)/2。****
Red points are in separated radius.
我们减少了具有 x 坐标的点在分隔 _ 最小半径之间的数量。我们称这些缩减点为缩减 _ 总计点。我们可以看到,要计算一个点到 reduced_total 点中所有点的最小距离,我们只需要计算 reduced_total 点中 y 坐标在 separated_min 内的 7 个点。表示减总点 x 点的边界是一个矩形,其宽= 2 x 分隔最小**,高=分隔最小。为此,我们需要按 y 坐标对约简总数点进行排序,并遍历所有排序后的约简总数点,计算 7 点边界的最小距离。然后我们可以最终计算混合最小距离。**
很难找到实现这种算法的正确方向。一旦你实施正确,你将通过这个问题。我确实为这个问题提交了 17 次。所以保持冷静,继续提交。😄
资源:
[2]主定理:[DPV08]的第 2.2 节
[3]快速排序:[CLRS]的第 7 章
多项式乘法:[DPV08]的第 2.1 节
合并排序和基于比较排序的下限:[DPV08]的第 2.3 节
快速排序:[CLRS]第 7 章
参考文献:
Sanjoy Dasgupta、Christos Papadimitriou 和 Umesh Vazirani。算法(第一版)。麦格劳-希尔高等教育。2008.
托马斯·h·科尔曼,查尔斯·e·莱瑟森,罗纳德·L·李维斯特,克利福德·斯坦。算法导论(第三版)。麻省理工学院出版社和麦格劳-希尔。2009.