TowardsDataScience 博客中文翻译 2022(二百六十九)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

马尔可夫链:多步转移

原文:https://towardsdatascience.com/markov-chains-multi-step-transitions-6772114bcc1d

使用 Chapman-Kolmogorov 方程理解马尔可夫链中的多步转移

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

沃洛季米尔·赫里先科在 Unsplash 上的照片

介绍

在我以前的文章中,我们介绍了 马尔可夫属性 的概念,并将其发展为解释一个 马尔可夫链 **。**我建议现在的读者在阅读这篇文章之前先浏览一下那篇文章:

然而,在坚果壳中,马尔可夫性质是当转移到下一个状态的概率仅取决于当前状态时。系统是 无记忆 马尔可夫链是有限状态空间下马尔可夫性质下的时间离散变迁的序列。

在本文中,我们将讨论 Chapman-Kolmogorov 方程以及如何使用它们来计算给定马尔可夫链的多步转移概率。

马尔可夫链和转移矩阵

考虑以下状态空间为 {A,B,C} 的马尔可夫链:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者制作的图像。

这个马尔可夫链有如下关联的 转移矩阵 :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由作者在 LaTeX 中生成的矩阵。

这些值告知我们从状态 i (行)移动到状态 j (列)的概率。然而,这些概率仅针对单步过渡。比如说,分两步从状态 **B 到状态 A 的概率是多少?**我们可以通过参考马尔可夫链图来解决这个问题:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LaTeX 中生成的方程。

然而,当状态空间变大,我们需要计算两个以上的转换时,这种方法变得越来越困难。有一种更简单、更通用的方法来表达多步跃迁,使用 T 和 Chapman-Kolmogorov 方程,我们接下来将深入探讨。

查普曼-柯尔莫哥洛夫方程

我们可以使用以下公式概括多步转换:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LaTeX 中生成的方程。

也就是我们刚刚在时间 m 处于状态 i 时,在时间 n 进入状态 j 的概率。这个方程可以用查普曼-科尔莫戈罗夫方程求解:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LaTeX 中生成的方程。

其中 l 为介于 mn 之间的整数值,P 为状态间转移的概率, k 为只能占用状态空间中的值的索引变量。

让我们通过一个例子来实现上面马尔可夫链的Chapman-Kolmogorov 方程。我们希望分两步从状态 B 到状态 A:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LaTeX 中生成的方程。

我们得到了和上面一样的概率!

我将当前时间设置为零。这是允许的,因为状态的历史与马尔可夫链无关,所以将当前时间设置为零不会失去表达式的通用性。

直觉上,我们只是两步转换分解为一组一步转换,因为我们知道它们的概率。然后,我们将它们结合起来计算两步跃迁概率。

如果我们想分三步计算转换,那么 l 的值可以是 1 或 2 。因此,我们必须应用两次查普曼-科尔莫戈罗夫方程来表达一步转换公式。这个过程被称为递归**,因为我们不断地向后计算概率。**

多步转移矩阵

转移矩阵是一个正方形矩阵,其中每个单元给出了在一步中从状态*到 j 的概率。*解读上面的Chapman-Kolmogorov 方程,可以看到,从状态 ij 中的两步等于转移矩阵的平方:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LaTeX 中生成的方程。

请注意,对于从 B 到 A 的条目(1,0)* (我使用的是从零开始的索引),我们得到的概率是 0.25,正是我们上面得到的相同结果!***

因此,要得到多步转移概率,你所要做的就是将一步转移矩阵本身乘以你需要的转移次数!****

结论

在本文中,我们展示了如何使用 Chapman-Kolmogorov 方程推导出马尔可夫链多步转移概率*。在我的下一篇文章中,我们将看到当转换的数量变大并趋于无穷大时会发生什么!***

和我联系!

(所有表情符号都是由 OpenMoji 设计的——开源的表情符号和图标项目。许可证: CC BY-SA 4.0

马尔可夫链简单地解释了

原文:https://towardsdatascience.com/markov-chains-simply-explained-dc77836b47e3

马尔可夫性和马尔可夫链的直观而简单的解释

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由郭佳欣·阿维蒂西安Unsplash 上拍摄

介绍

马尔可夫链出现在许多领域:物理学、遗传学、金融,当然还有数据科学和机器学习。作为一名数据科学家,你可能听说过这个词**‘马尔可夫’**在你的研究或一般阅读中出现过几次。它是自然语言处理和强化学习中的典型统计技术。在本文中,我们将简单解释什么是马尔可夫链,以及它的含义。

马尔可夫性质

对于任何被认为是马尔可夫/马尔可夫的建模过程,它必须满足 马尔可夫特性 。这个性质表明下一个状态的概率只取决于当前状态,当前状态之前的一切都是无关的。换句话说,整个系统完全是无记忆

从数学上来说,这可以写成:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LaTeX 中生成的方程

其中 n时间步长参数X 是在给定的状态空间中取值的随机变量状态空间是指事件的所有可能结果。例如,一次掷硬币在其状态空间中有两个值:s = {正面,反面}从一种状态转移到另一种状态的概率*为 0.5。*

马尔可夫链

使用马尔可夫属性的过程被称为马尔可夫过程。如果状态空间是有限的并且我们使用离散时间步这个过程被称为马尔可夫链。换句话说,它是在给定的状态空间中呈现状态的随机变量序列。

在本文中,我们将考虑时间齐次离散时间马尔可夫链,因为它们最容易处理并建立背后的直觉。确实存在非时齐马尔可夫链,其中状态之间的转移概率不是固定的,而是随时间变化的

下面显示的是带有状态空间{A,B,C} 的示例马尔可夫链。箭头上的数字表示在这两种状态之间转换的概率。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片作者。

比如你想从状态 B 到状态 C,那么这个转换有 20%的几率。从数学角度来看,我们得出以下结论:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LaTeX 中生成的方程

概率转移矩阵

我们可以通过为给定的马尔可夫链构建一个概率转移矩阵来简化和概括这些转移。转移矩阵具有行 i 和列 j ,因此 i,j 索引值给出了从 ij 的转移概率:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LateX 中生成的方程

上述马尔可夫链的转移矩阵是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由作者在 LaTeX 中生成的矩阵。

所以(0,1)项告诉我们从 A 到 B 的转移概率是 0.4。这与我们在上面的马尔可夫链图中得到的结果一致。

摘要

在本文中,我们介绍了马尔可夫性质的概念,并使用这一概念来构建和理解一个基本的马尔可夫链。这种随机过程出现在数据科学和机器学习的许多方面,因此对它有所了解是很重要的。在我的下一篇文章中,我们将深入研究马尔可夫链和过程的更复杂和更特殊的部分。

希望你喜欢这篇文章!

和我联系!

(所有表情符号都是由 OpenMoji 设计的——开源的表情符号和图标项目。许可证: CC BY-SA 4.0

马尔可夫链:平稳分布

原文:https://towardsdatascience.com/markov-chains-stationary-distribution-bedd67140112

用 Python 模拟解释和推导马尔可夫链的平稳分布

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

马库斯·温克勒在 Unsplash 上的照片

介绍

在我以前的文章中,我们开始发展我们对 马尔可夫链 的直觉和知识。到目前为止,我们已经讲述了: 马尔可夫性质、 转移矩阵多步转移利用 查普-柯尔莫哥洛夫方程 。我建议在继续写这篇文章之前通读这些文章:

但是,简单总结一下上面的文章:马尔可夫链是离散时间内有限状态空间中的一系列转移,其中转移的概率只取决于当前状态。这个系统是完全无记忆的。

转移矩阵显示状态空间中状态间转移的概率。Chapman-Kolmogorov 方程告诉我们如何计算多步跃迁概率。****

在本文中,我们将讨论当我们采用大量离散时间步长时,转移矩阵会发生什么。换句话说,我们将描述马尔可夫链如何随着时间趋于无穷大而发展。

马尔可夫链和转移矩阵

首先,让我们快速回顾一下我以前文章中的内容。下面是一个状态空间为 {A,B,C}的马尔可夫链。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者制作的图像。

该马尔可夫链具有以下转移矩阵:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由作者在 LaTeX 中生成的矩阵。

其中每个单元格传达在马尔可夫属性下从状态 i (行)到状态*(列)的转移的概率。然而,该矩阵仅用于单步过渡。如果我们想分两步从 ij 呢?*

这个问题可以通过 Chapman-Kolmogorov 方程来解决,它告诉我们这就是转移矩阵的平方。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由作者在 LaTeX 中生成的矩阵。

如果我们想计算三步概率,我们就要对转移矩阵求立方。一般来说对于 n 步:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者在 LaTeX 中生成的方程。

现在,随着 n 变大会发生什么?我们接下来会回答这个问题。

稳定分布

随着时间的推移,处于某些状态的可能性比其他状态更大。从长远来看,该分布将达到一个平衡,并具有处于每个状态的相关概率。这就是所谓的固定分配

它是稳定的原因是,如果你将转移矩阵应用于这个给定的分布,结果分布是与之前相同的*😗**

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者在 LaTeX 中生成的方程。

其中 π 是某个分布,它是列数等于状态空间中的状态的行向量,而 P 是转移矩阵。

特征值分解

有些人可能会把上面的方程认作是 πP 的一个特征向量*,其特征值为 1 。这确实是真的,所以我们可以用 【特征值分解(谱定理) 来求解。***

注意:在这篇文章中,我假设读者已经理解了矩阵的特征值以及如何找到它们。如果没有,我建议阅读一些基础教程来获得一些直觉。

让我们看看上面的例子马尔可夫链,它有一个 3x3 转移矩阵。根据上面的转换矩阵,我们想要求解下面的等式:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者在 LaTeX 中生成的方程。

其中 λ 为特征向量对应的特征值。利用 三角形法则 ,这等于:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者在 LaTeX 中生成的方程。

所以我们的特征值是 0,-0.5 和 1* 。我们知道我们的解只在特征值等于 1 的情况下有效,所以我们现在将使用它来找到我们相应的特征向量,这将是我们的平稳分布:***

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者在 LaTeX 中生成的方程。

这里,下标指的是当我们有一个稳定分布时,处于相应状态的概率。上述方程可以通过高斯消去求解得到如下结果:**

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者在 LaTeX 中生成的方程。

将上述向量归一化,我们的平稳分布为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者在 LaTeX 中生成的方程。

这意味着从长远来看,我们在这三个州中的任何一个州都有同等的可能性!

注意:这不是平稳分布的密集深度推导,因为我不想把它变成教科书!不过网上有很多关于特征值分解的深入例子。

Python 模拟

我们刚刚展示了理论上的稳定分布,现在我们将使用 Python 中的模拟来根据经验计算它****

下面的代码模拟了开始分布为{1,0,0}的 50 步后处于每个状态的概率:****

***# import packages
import numpy as np
import matplotlib.pyplot as plt# set transition matrix
transition_matrix =  np.array([[0, 0.4, 0.6],
                               [0.5, 0.3, 0.2],
                               [0.5, 0.3, 0.2]])# set initial distribution
initial_dist = np.array([1,0,0])# simulate 50 time steps
for _ in range(50):
    update = initial_dist @ transition_matrix
    initial_dist = update

print(initial_dist)***

输出:

***[0.33333333 0.33333333 0.33333333]***

这与我们上面推导的平稳分布完全相同!

完整代码可以在我的 GitHub 上找到:

***https://github.com/egorhowell/Medium-Articles/blob/main/Statistics/markov_stationary.py

摘要

在这篇文章中,我们描述了什么是平稳分布,以及如何为马尔可夫链推导出平稳分布。在我的下一篇文章中,我们将讨论马尔可夫链的不同性质。

和我联系!

(所有表情符号都是由 OpenMoji 设计的——开源的表情符号和图标项目。许可证: CC BY-SA 4.0***

科技领域大规模裁员——人工智能的冬天来了吗?

原文:https://towardsdatascience.com/mass-layoffs-in-tech-is-the-ai-winter-coming-d7f9968006d

意见

面对经济逆风,亚马逊、Meta 和 Twitter 等科技巨头削减了数千个工作岗位。这对人工智能的未来意味着什么?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Unsplash 上的 Aditya Vyas 拍摄

直到最近,公司还在努力吸引和留住数据科学领域的优秀员工。在线业务在封锁时期蓬勃发展,世界突然依赖于包裹递送、云环境、在线会议空间和虚拟娱乐。科技巨头报告了创纪录的利润,将多余的现金投入到雄心勃勃的人工智能项目和创新中。

每个合格的数据科学家都是高价值的商品,公司竭尽全力阻止员工加入伟大的辞职运动。不管有没有电晕,天空似乎是科技行业的极限。

然后,几乎在一夜之间,LinkedIn 突然被寻找另一份工作的有经验的数据科学家淹没了。几天之内,Twitter 解雇了一半的员工,亚马逊和 Meta 都在大规模裁员中削减了超过 10,000 个职位,更多的公司要么冻结招聘,要么大幅缩减员工人数[2]。在全球范围内,估计已经有 200,000 名技术工人失业,这个数字在未来几个月内可能会上升。

突然之间,数据科学界似乎陷入了低谷。我们正在走向另一个人工智能冬天吗?

什么是 AI 冬天?

首先,什么是 AI 冬天?维基百科[4]将其定义为:

“人工智能研究资金和兴趣减少的时期.”

通向这样一个冬天的道路概述如下:

“这是一个连锁反应,首先是人工智能界的悲观情绪,然后是媒体的悲观情绪,接着是资金的大幅削减,然后是严肃研究的结束。”

更广泛地说,人工智能的冬天可以被归类为 Gartner 炒作周期【5】中的一个低谷,在这个周期中,当事实证明过高的预期无法满足时,人们对某项技术的兴趣急剧下降。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Gartner 炒作周期。如果过高的期望得不到满足,这项技术就会失去兴趣,资金也会被抽走,从而阻碍进一步的发展

据报道,主要的 AI 冬季发生在 1974-1980 年和 1987-1993 年,人们一直预测另一次萧条迟早会到来。

总而言之,要实现人工智能冬天,在一段较长的时间内,必须满足以下两个条件:

  • 资金减少
  • 降低利息

根据记录,炒作周期存在的经验证据充其量也是站不住脚的,但为了这篇文章,我们将配合一下。

为什么数据科学家会被解雇?

先说经费减少。科技公司创纪录的裁员自然降低了进一步开发人工智能的能力。

显然,不是所有被解雇的人都是数据科学家,也不是所有数据科学家都设计 AI。尽管如此,大多数从事技术工作的人确实在日常工作中以这样或那样的方式使用人工智能。

在更多的应用角色中,你甚至可能不会直接注意到创新。然而,从长远来看,考虑一下如果没有更有效的矩阵乘法发明、更快速的梯度计算、透明地解释自动化决策的实践,会发生什么……你使用五年前的工具包会有多有效?

当这类创新停滞时,整个行业都会停滞不前,数据科学家的影响力也会大打折扣。人工智能与数据科学的许多分支交织在一起,大规模裁员的影响将渗透到该领域的所有缝隙中。自然,那些不幸失业的人受到的影响最大,然而我们所有人都会受到人工智能创新能力丧失的影响。

从商业常识的角度来看,裁员的原因很简单:

  • 高成本削减:数据科学以高工资和丰厚奖金著称;这也是很多人试图进入这个领域的原因之一。因此,削减对公司的运营成本有着重大而直接的影响。
  • 降低 R & D 的优先级:尽管“数据科学”的概念相当宽泛,但该领域的许多人都以某种方式参与了研究开发。在危机时期,研发活动总是受到打击,人们关注的是短期生存,而不是长期愿景和投机努力。
  • 修正表现不佳:科技股最近经历了大幅下跌。corona 似乎将推动不断扩张的数字世界发生永久性变化,科技行业也随之扩张。然而,实际表现与乐观的预期并不相符。

一些具体的例子?

  • Meta 向元宇宙投入了数十亿美元——仅今年一年就在这个项目上损失了近 100 亿美元[6]——还看不到盈亏平衡点。
  • 根据马斯克的说法,Twitter 目前每天损失 400 万美元。
  • 亚马逊最近成为历史上第一家亏损一万亿(!)的市值,微软紧随其后。
  • 谷歌继续经历利润缩水,部分原因是广告市场饱和,部分原因是创新失败[9]。

在更细的层面上,特定的团队或产品无法产生利润,无论成员的素质或想法的才华如何。稍后会详细介绍。

最终,裁员决定通常只是一个团队花费多少和产生多少的问题。有办公室政治和商业愿景,但底线最终很重要。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

马克西姆·霍普曼在 Unsplash 上的照片

艾温特或’只是’另一个危机?

人工智能资金的减少是不可否认的,但从表面上看,裁员有明显的宏观经济原因。全球经济出人意料地迅速从科罗纳危机中复苏——部分原因是政府机构近乎无限的资助——但乌克兰战争引发了另一系列问题,包括供应链进一步中断和能源价格飙升。通货膨胀率飙升,消费者有了购买力,人们变得恐惧……这就是一场危机所需要的所有要素。

经济逆风和裁员齐头并进,因此仅仅削减员工成本不足以构成人工智能的冬天。然而,如果我们仔细看看被解雇的*,我们可能会认为最近的发展不仅仅是为风暴做准备。是时候考虑一些例子了:*

  • Twitter 的整个道德人工智能团队的解散引起了广泛的关注,因为该团队被认为是透明和公正人工智能的领导者[10]。裁员可能会被解读为独角戏,但类似的有针对性的裁员可能也会在其他科技公司出现。
  • Meta 的概率团队完全解散了,他们的工作主题是概率和微分编程,这可以帮助 ML 工程师。据报道,这是一个世界级的专家团队,但似乎它缺乏足够明显的影响[11]。
  • 据报道,亚马逊解雇了其**机器人和设备部门的大部分人员,**标志着重新定位于被证明能够产生现金流的服务。

在这些决策中,应该考虑到科技巨头——尽管显然不是慈善家——有大量现金可供支配。因此,停止人工智能项目对于短期生存来说并不重要,这意味着他们对长期的盈利能力或价值失去了信心。

终止项目随时都在发生,但此时一批插头正在被拔出。对许多公司来说,这是几十年来最大规模的裁员;很难夸大当前事件的重要性。

处于过程中间,缺乏关于重组努力的规模和范围的全面声明,现在看 AI 将走向何方还为时过早。然而,鉴于即使是世界级的人工智能专家也不再能保证得到一份工作,这似乎不仅仅是简单地预测经济挫折。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

卡莱斯·拉巴达Unsplash 上拍摄

下一步是什么?

未来如何发展显然取决于许多因素:战争、能源危机、反通胀措施的成功、消费者的情绪等等。尽管如此,日冕期间经历的 V 型复苏(快速内爆后同样快速的反弹)似乎不太可能。一个 U 型模式(逐渐衰退、停滞、缓慢复苏)似乎是我们所能期待的最好结果。鉴于科技劳动力的大幅减少,我们需要相当长的时间才能回到 2022 年开始时的水平。

所有这些是否意味着一个迫在眉睫的人工智能冬天?资金和人力的减少似乎是既定的,许多人工智能部门的有针对性的取消和精简肯定可以解释为对人工智能或至少该领域分支的兴趣减少。

话虽如此,AI 发展当然不会停止。甚至以前的冬天也没有完全停止人工智能的发展。此外,上一个冬天发生在 90 年代初。当今的人工智能如此庞大,在日常生活中如此根深蒂固,很难想象人工智能发展会出现真正的“突破”。

尽管大规模裁员、许多人工智能计划的终止以及公司目前的短期关注不太可能而不是损害人工智能的进展,但经济逆风似乎是比普遍丧失对人工智能的信心更强大的驱动力。因此,一个严重的人工智能冬天不太可能出现——人工智能仍然有太多的优势。

也就是说,在我们未来的日子里,多一条毯子可能没什么坏处。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

罗伯特·蒂曼在 Unsplash 上拍摄的照片

喜欢这篇文章?你可能也会喜欢下面这些:

参考

[1]https://www.bbc.com/news/business-57979268

[2]https://www . computer world . com/article/3679733/tech-裁员-in-2022-a-timeline.html

[3]https://www.trueup.io/layoffs

https://en.wikipedia.org/wiki/AI_winter

[5]https://www . Gartner . com/en/research/methodologies/Gartner-hype-cycle

[6]https://www . ign . com/articles/meta-正在元宇宙损失数十亿美元,而它并没有好转

[7]https://www . Reuters . com/markets/deals/twitters-massive-revenue-drop-adds-heavy-debt-burden-2022-11-07/

[8]https://www . Bloomberg . com/news/articles/2022-11-09/Amazon-hits-unwanted-milestone-with-one-万亿-value-lost

[9]https://www . the verge . com/2022/10/25/23423560/Google-Q3-2022-增长-收入-萎缩-利润-收益

https://www.wired.com/story/twitter-ethical-ai-team/

[11]https://venturebeat . com/ai/why-meta-and-twitters-ai-and-ml-offsets-matter-the-ai-beat/

[12]https://www . business today . in/latest/corporate/story/after-meta-Twitter-and-Microsoft-now-Amazon-starts-fireing-employees-352547-2022-11-11

[13]https://www . nytimes . com/2022/11/14/technology/Amazon-offsets . html

[14]https://TechCrunch . com/2022/11/11/面临-经济-逆风-亚马逊-整合-机器人-项目/

[15]https://edition . CNN . com/2022/05/12/investing/recession-shapes-alphabet/index . html

Python 中图像处理和深度学习准备的大量教程#1

原文:https://towardsdatascience.com/massive-tutorial-on-image-processing-and-preparation-for-deep-learning-in-python-1-e534ee42f122

随意操纵和变换图像

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由Prasad PanchakshariUnsplash

介绍

我们来这里是为了一件悲伤的事情。真的很悲伤。我们在这里学习如何拍摄美丽、令人惊叹的图像,并把它们变成一堆丑陋的小数字,以便它们对所有那些没有灵魂、没有头脑的机器来说更像样。

我们会把动物身上的颜色剥去,变成黑色和白色。抢过色彩鲜艳的花,抢走它们的美丽。我们将看到令人不安的 x 射线图像,并想办法让它们变得更加令人不安。有时候,我们甚至可以用计算机算法来画硬币。

换句话说,我们将学习如何执行图像处理。我们的荣誉图书馆将在整篇文章中以 Scikit-Image (Skimage)的形式出现。

这是一篇由两部分组成的文章。你可以在这个之后在这里阅读第二部分。最后还有一个链接。

**https://ibexorigin.medium.com/membership

获得由强大的 AI-Alpha 信号选择和总结的最佳和最新的 ML 和 AI 论文:

https://alphasignal.ai/?referrer=Bex

基础

1.什么是图像?

除了文本,图像数据可能是最常见的。那么,计算机是如何理解你在埃菲尔铁塔前的那张自拍的呢?

它使用称为像素的小正方形单元的网格。像素覆盖很小的区域,并且具有代表颜色的值。图像中的像素越多,质量就越高,需要更多的内存来存储。

就是这样。图像处理主要是操纵这些单个像素(有时是一组像素),以便计算机视觉算法可以从中提取更多信息。

2.使用数字和浏览的图像基础

图像在 Matplotlib 和 Skimage 中都作为 NumPy ndarrays 加载。

数字阵列总是给游戏带来灵活性、速度和力量。图像处理也不例外。

Ndarrays 可以轻松检索图像的一般细节,如尺寸:

我们隐藏的image高 853 像素,宽 1280 像素。第三维表示 RGB(红、绿、蓝)颜色通道的值。最常见的图像格式是 3D 格式。

您可以通过常规的 NumPy 索引来检索单个像素值。下面,我们尝试对图像进行索引,以检索三个颜色通道中的每一个:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Pixabay像素拍摄

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由像素Pixabay 拍摄

红色通道为 0,绿色通道为 1,蓝色通道为 2——非常简单。

我已经创建了两个函数,[show](https://gist.github.com/BexTuychiev/e65c222f6fa388b22d7cf80eb561a1f4)[compare](https://gist.github.com/BexTuychiev/37898e5a44fae4851f6fc6e951e560d7),它们显示一幅图像或者并排显示两幅图像进行比较。我们将在整个教程中广泛使用这两个函数,所以您可能想看看我超链接的代码。

按照惯例,ndarray 的第三维是用于颜色通道的,但是这个惯例并不总是被遵守。Skimage 通常会提供一些参数来指定这种行为。

图像不同于通常的 Matplotlib 图。它们的原点不在左下方,而是在左上方的位置(0, 0)

>>> show(image, axis=True)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Pixabay像素拍摄

当我们在 Matplotlib 中绘制图像时,轴表示像素的顺序,但我们通常会隐藏它们,因为它们不会给查看者带来太多的价值。

3.常见转换

我们将执行的最常见的图像转换是将彩色图像转换为灰度图像。许多图像处理算法需要灰度 2D 阵列,因为颜色不是图片的定义特征,并且计算机已经可以在没有它的情况下提取足够的信息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由来自 PexelsJovana Nesic 拍摄

>>> gray.shape(853, 1280)

当将图像转换为灰度时,它们会失去第三维空间——颜色通道。相反,图像数组中的每个单元格现在都表示一个uint8类型的整数。它们的范围从 0 到 255,给出了 256 种灰度。

你也可以使用像[np.flipud](https://numpy.org/doc/stable/reference/generated/numpy.flipud.html)[np.fliplr](https://numpy.org/doc/stable/reference/generated/numpy.fliplr.html#numpy.fliplr)这样的 NumPy 函数,随心所欲地操纵图像,就像操纵 NumPy 数组一样。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 HannaPexels 拍摄

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

保罗·巴塞尔从 Pexels 拍摄的照片

[color](https://scikit-image.org/docs/dev/api/skimage.color.html) 模块中,您可以找到许多其他变换函数来处理图像中的颜色。

4.颜色通道直方图

有时,查看每个颜色通道的强度有助于了解颜色分布。我们可以通过分割每个颜色通道并绘制它们的直方图来实现。以下是执行此操作的函数:

除了几个 Matplotlib 细节,你应该注意一下hist函数的调用。一旦我们提取了颜色通道及其数组,我们将它展平成一个 1D 数组,并将其传递给hist函数。面元应该是 256,每个像素值一个面元- 0 是全黑,255 是全白。

让我们为我们的彩色风景图像使用函数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由来自 PexelsPixabay 拍摄

>>> plot_with_hist_channel(colorful_scenery, "green")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由像素Pixabay 拍摄

>>> plot_with_hist_channel(colorful_scenery, "blue")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Pixabay像素拍摄

您也可以使用直方图找出图像转换为灰度后的亮度:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

大多数像素值较低,因为风景图像有点暗。

我们将在接下来的章节中探索直方图的更多应用。

过滤

1.手动阈值

现在,我们开始有趣的事情——过滤图像。我们将学习的第一个操作是阈值处理。让我们加载一个示例图像:

stag = imread("images/binary_example.jpg")

>>> show(stag)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来自像素cmonphotography 拍摄的照片

阈值处理在图像分割、对象检测、寻找边缘或轮廓等方面有许多应用。它主要用于区分图像的背景和前景。

阈值处理最适用于高对比度灰度图像,因此我们将转换 stag 图像:

# Convert to graysacle
stag_gray = rgb2gray(stag)

>>> show(stag_gray)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来自像素cmonphotography 摄影

我们将从基本的手动阈值处理开始,然后转向自动阈值处理。

首先,我们看看灰度图像中所有像素的平均值:

>>> stag_gray.mean()0.20056262759859955

请注意,上面的灰色图像的像素通过将其所有值除以 256,在 0 和 1 之间进行归一化。

我们得到一个平均值 0.2,这给了我们一个可能想要使用的阈值的初步想法。

现在,我们使用这个阈值来屏蔽图像阵列。如果像素值低于阈值,则它的值变为 0 —黑色,否则变为 1 —白色。换句话说,我们得到一张黑白二进制图片:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来自像素cmonphotography 照片

在这个版本中,我们可以更清楚地区分牡鹿的轮廓。我们可以反转蒙版,使背景变成白色:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

cmonphotography像素拍摄的照片

2.阈值处理—全局

虽然尝试不同的阈值并观察它们对图像的影响可能很有趣,但我们通常使用比我们的眼球估计更鲁棒的算法来执行阈值处理。

有许多阈值算法,所以可能很难选择一个。在这种情况下,skimagetry_all_threshold函数,它对给定的灰度图像运行七种阈值算法。让我们加载一个示例并进行转换:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由丽莎·福蒂斯派克斯拍摄

我们将看看是否可以通过使用阈值来改进郁金香的特征:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如你所看到的,在这张图片上,一些算法工作得更好,而另一些则很糟糕。otsu算法看起来更好,所以我们将继续使用它。

现在,我想把你们的注意力拉回到最初的郁金香图片上:

>>> show(flower)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Pexels丽莎·福蒂斯拍摄

这幅图像的背景不均匀,因为太多的光线来自后面的窗户。我们可以通过绘制灰色郁金香的直方图来证实这一点:

>>> plt.hist(flower_gray.ravel(), bins=256);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如预期的那样,大多数像素的值都在直方图的远端,这表明它们大部分都很亮。

为什么这很重要?根据图像的亮度,阈值算法的性能也会改变。为此,阈值算法通常有两种类型:

  1. 全局—适用于背景均匀一致的照片
  2. 局部—适用于不同图片区域中具有不同亮度级别的图像。

郁金香图像属于第二类,因为右边部分比另一半亮得多,使其背景不均匀。我们不能对其使用全局阈值算法,这也是为什么[try_all_threshold](https://scikit-image.org/docs/dev/api/skimage.filters.html#skimage.filters.try_all_threshold)中所有算法的性能如此之差的原因。

我们一会儿将回到郁金香的例子和局部阈值。现在,我们将加载另一个具有更精细亮度的实例,并尝试自动设置阈值:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Pixabay像素拍摄

我们将使用一个通用的全局阈值算法[threshold_otsu](https://scikit-image.org/docs/stable/api/skimage.filters.html#skimage.filters.threshold_otsu):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Pixabay像素拍摄

效果好多了!

3.阈值处理—局部

现在,我们将使用局部阈值算法。

局部算法不是查看整个图像,而是关注像素邻域,以解决不同区域中亮度不均匀的问题。skimage中常见的局部算法为[threshold_local](https://scikit-image.org/docs/stable/api/skimage.filters.html#skimage.filters.threshold_local)函数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来自 Pexels丽莎·福蒂斯的照片

你必须玩弄offset的论点来找到你需要的最佳图像。offset是从局部像素邻域的平均值中减去的常数。这个“像素邻域”由local_threshold中的block_size参数决定,该参数表示算法在每个方向的每个点周围寻找的像素数量。

显然,同时调整offsetblock_size是不利的,但是局部阈值处理是产生比手动或全局阈值处理更好结果的唯一选择。

让我们再举一个例子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由龟背竹派克斯拍摄

如你所见,在阈值化之后,板上的笔迹更加精细。

4.边缘检测

边缘检测在许多方面都是有用的,例如识别物体、从物体中提取特征、计数等等。我们将从基本的 Sobel 滤波器开始,它在灰度图像中找到对象的边缘。我们将加载硬币的图片,并对其使用 Sobel 滤镜:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由来自 Pexels德米特里·德米多夫拍摄

索贝尔是非常简单的;你只需要在灰色图像上调用它,就可以得到上面这样的输出。我们将在后面的章节中看到 Sobel 的更复杂的版本。

5.缓和

另一种图像过滤技术是平滑。许多图像,如下面的鸡,可能包含随机噪声,对 ML 和 DL 算法没有任何有价值的信息。

例如,鸡周围的毛发会给图像添加噪声,这可能会使 ML 模型的注意力偏离主要对象本身。在这种情况下,我们使用平滑来模糊噪声或边缘,并降低对比度。

chickens = imread("images/chickens.jpg")

>>> show(chickens)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由维克托·伯恩赛德Pexels 拍摄

最流行和最强大的平滑技术之一是[gaussian](https://scikit-image.org/docs/stable/api/skimage.filters.html#skimage.filters.gaussian)平滑:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由维克托·伯恩赛德派克斯拍摄

您可以通过调整sigma参数来控制模糊效果。如果你正在处理一个 RGB 图像,不要忘记设置multichannel为真。

如果图像分辨率太高,平滑效果可能肉眼看不到,但在遮光罩下仍然有效。

你可以在这里阅读帖子的下一部分

https://ibexorigin.medium.com/membership

或者订阅我的邮件列表:

https://ibexorigin.medium.com/subscribe

你可以在LinkedInTwitter上联系我,友好地聊一聊所有的事情数据。或者你可以读我的另一个故事。这些怎么样:

</10-minute-guide-to-julia-for-die-hard-python-lovers-a2fcf7dcb860> </6-pandas-mistakes-that-silently-tell-you-are-a-rookie-b566a252e60d> </8-booming-data-science-libraries-you-must-watch-out-in-2022-cec2dbb42437> **

Python 中图像处理和深度学习准备的大量教程#2

原文:https://towardsdatascience.com/massive-tutorial-on-image-processing-and-preparation-for-deep-learning-in-python-2-14816263b4a5

随意操纵和变换图像

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Ihsan Adityawarman 在 Pexels 上拍摄

这是我关于图像处理的第一篇文章的第二部分。请阅读第一篇了解背景和设置

我们将从对比度增强开始第二部分。

6.对比度增强

某些类型的图像(如医疗分析结果)对比度较低,难以发现细节,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Pixabay 提供

在这种情况下,我们可以使用对比度增强来使细节更加清晰。有两种类型的对比度增强算法:

  1. 对比度扩展
  2. 直方图均衡

我们将在本帖中讨论直方图均衡化,它依次有三种类型:

  1. 标准直方图均衡化
  2. 自适应直方图均衡
  3. 对比度受限的自适应直方图均衡(CLAHE)

直方图均衡将图像对比度最高的区域扩展到亮度较低的区域,对其进行均衡

哦,对了,你可以通过从最高像素值中减去最低像素值来计算图像的对比度。

>>> xray.max() - xray.min()255

现在,让我们试试来自exposure模块的标准直方图均衡化:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Pixabay 提供

我们已经可以更清楚地看到细节了。

接下来,我们将使用 CLAHE (这是一个有趣的发音单词!),它为图像中不同的像素邻域计算许多直方图,即使在最暗的区域也能获得更多细节:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Pixabay 提供

这张看起来好很多,因为它可以在背景中显示细节,在左下角显示更多丢失的肋骨。您可以调整clip_limit来获得更多或更少的细节。

https://ibexorigin.medium.com/membership

获得由强大的 AI-Alpha 信号选择和总结的最佳和最新的 ML 和 AI 论文:

https://alphasignal.ai/?referrer=Bex

7.转换

数据集中的影像可能有几个相互冲突的特征,如不同的比例、未对齐的旋转等。ML 和 DL 算法希望您的图片具有相同的形状和尺寸。因此,您需要学习如何修复它们。

旋转

要旋转图像,使用transform模块中的rotate功能。我选择了真正的时钟,所以你可能会更好地记住角度符号:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由来自 PexelsRP Singh 拍摄

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 RP SinghPexels 拍摄

重新缩放

另一个标准操作是缩放图像,它主要在图像比例不同的情况下有用。

我们使用一个类似的[rescale](https://scikit-image.org/docs/stable/api/skimage.transform.html#skimage.transform.rescale)函数来完成这个操作:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Pixabay像素拍摄

当图像分辨率较高时,过度缩小可能会导致质量下降,或者像素随意地摩擦在一起,从而产生意外的边缘或拐角。为了考虑这种影响,您可以将anti_aliasing设置为 True,这将使用高斯平滑:

https://gist.github.com/f7ae272b6eb1bce408189d8de2b71656

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由像素Pixabay 拍摄

和以前一样,平滑并不明显,但在更精细的层次上它会很明显。

调整大小

如果您希望图像具有特定的宽度和高度,而不是按某个因子缩放,您可以通过提供一个output_shape来使用[resize](https://scikit-image.org/docs/stable/api/skimage.transform.html#skimage.transform.resize)函数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由切瓦农摄影Pexels 拍摄

图像恢复和增强

在文件转换、错误下载或许多其他情况下,一些图像可能会扭曲、损坏或丢失。与其放弃这个想法,你可以使用skimage来说明损坏的原因,并使图像完好如初。

在这一节中,我们将讨论一些图像恢复技术,从修复开始。

1.修补

修复算法可以智能地填充图像中的空白。我找不到损坏的图片,所以我们将使用这张鲸鱼图片,并手动在上面放置一些空白:

whale_image = imread("images/00206a224e68de.jpg")>>> show(whale_image)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

>>> whale_image.shape(428, 1916, 3)

下面的函数创建了四个漆黑的区域来模拟图像上丢失的信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们将使用来自inpaint模块的[inpaint_biharmonic](https://scikit-image.org/docs/stable/api/skimage.restoration.html#skimage.restoration.inpaint_biharmonic)函数来填充空白,传入我们创建的mask:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如你所看到的,在看到有缺陷的图像之前,很难判断缺陷区域在哪里。

现在,让我们制造一些噪音📣!

2.噪音📣

如前所述,噪声在图像增强和恢复中起着重要作用。有时,您可能会有意将它添加到如下所示的图像中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由阮俊派克斯拍摄

我们使用[random_noise](https://scikit-image.org/docs/dev/api/skimage.util.html#skimage.util.random_noise)函数给一幅图像洒上随机的颜色斑点。因此,这种方法被称为“盐和胡椒”技术。

3.减少噪音—去噪

但是,大多数情况下,您希望从图像中去除噪声,而不是添加噪声。有几种去噪算法:

  1. 全变差(TV)滤波器
  2. 双边去噪
  3. 小波去噪
  4. 非局部均值去噪

在本文中,我们只看前两个。先来试试电视滤镜,有[denoise_tv_chambolle](https://scikit-image.org/docs/stable/api/skimage.restoration.html#denoise-tv-chambolle)的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Tuan NguyenPexels 拍摄

图像的分辨率越高,去噪的时间就越长。您可以使用weight参数控制去噪效果。现在,让我们试试[denoise_bilateral](https://scikit-image.org/docs/stable/api/skimage.restoration.html#skimage.restoration.denoise_bilateral):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Tuan NguyenPexels 拍摄

它不如电视滤镜有效,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Tuan NguyenPexels 拍摄

4.超像素和分段

图像分割是图像处理中最基本也是最常见的课题之一,广泛应用于运动和目标检测、图像分类等领域。

我们已经看到了一个分割的例子——对图像进行阈值处理,从前景中提取背景。本节将学习做更多的事情,比如将图像分割成相似的区域。

要开始分段,我们需要了解超像素的概念。

一个像素,就其本身而言,只是代表一个很小的颜色区域,一旦从图像中分离出来,单个像素就没用了。因此,分割算法使用多组对比度、颜色或亮度相似的像素,它们被称为超像素。

一种试图寻找超像素的算法是简单线性迭代聚类(SLIC ),它使用 k 均值聚类。让我们看看如何在skimage库中可用的咖啡图像上使用它:

from skimage import datacoffee = data.coffee()>>> show(coffee)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们将使用segmentation模块中的[slic](https://scikit-image.org/docs/dev/api/skimage.segmentation.html?highlight=slic#skimage.segmentation.slic)函数:

from skimage.segmentation import slicsegments = slic(coffee)>>> show(segments)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

slic默认查找 100 个段或标签。为了把它们放回图像上,我们使用了[label2rgb](https://scikit-image.org/docs/dev/api/skimage.color.html#skimage.color.label2rgb)函数:

from skimage.color import label2rgbfinal_image = label2rgb(segments, coffee, kind="avg")>>> show(final_image)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们将这个操作封装在一个函数中,并尝试使用更多的段:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分割将使计算机视觉算法更容易从图像中提取有用的特征。

5.轮廓

物体的大部分信息存在于它的形状中。如果我们能检测出一个物体的线条或轮廓,我们就能提取出有价值的数据,如它的大小、标记等。

让我们来看看在实践中使用多米诺骨牌的形象寻找轮廓。

dominoes = imread("images/dominoes.jpg")>>> show(dominoes)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Pixabay像素拍摄

我们将看看是否可以使用skimage中的[find_contours](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.find_contours)函数隔离瓷砖和圆圈。这个函数需要一个二值(黑白)图像,所以我们必须先对图像进行阈值处理。

产生的数组是表示等高线坐标的(n,2)数组列表:

我们将把操作包装在一个名为mark_contours的函数中:

为了在图像上绘制等高线,我们将创建另一个名为plot_image_contours的函数,它使用了上面的函数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由像素像素拍摄

正如我们所看到的,我们成功地检测到了大多数轮廓,但我们仍然可以看到中心的一些随机波动。在将多米诺骨牌的图像传递给我们的轮廓寻找函数之前,让我们应用去噪:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Pixabay像素拍摄

就是这样!我们消除了大部分的噪声,导致不正确的轮廓线!

高级操作

1.边缘检测

之前,我们使用 Sobel 算法来检测对象的边缘。在这里,我们将使用 Canny 算法,因为它更快、更准确,所以应用更广泛。像往常一样,函数[canny](https://scikit-image.org/docs/dev/api/skimage.feature.html#skimage.feature.canny)需要一个灰度图像。

这一次,我们将使用包含更多硬币的图像,从而检测更多边缘:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由来自 Pexels德米特里·德米多夫拍摄

为了找到边缘,我们只需将图像传递给canny函数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由来自 Pexels德米特里·德米多夫拍摄

该算法发现了几乎所有硬币的边缘,但它非常嘈杂,因为硬币上的雕刻也被检测到。我们可以通过调整sigma参数来降低canny的灵敏度:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由来自 Pexels德米特里·德米多夫拍摄

可以看到,canny现在只找到了硬币的大致轮廓。

2.角点检测

另一种重要的图像处理技术是角点检测。在图像分类中,角点可能是对象的关键特征。

为了找到角点,我们将使用哈里斯角点检测算法。让我们加载一个样本图像并将其转换为灰度:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Pixabay像素拍摄

我们将使用[corner_harris](https://scikit-image.org/docs/dev/api/skimage.feature.html#skimage.feature.corner_harris)功能来生成一个测量图像,该图像遮盖了角所在的区域。

from skimage.feature import corner_harrismeasured_image = corner_harris(windows_gray)>>> show(measured_image)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,我们将把这个屏蔽的测量图像传递给corner_peaks函数,这次它返回角坐标:

该函数找到了 79 个角,每个角之间的最小距离为 50 个像素。让我们将到目前为止的操作封装在一个函数中:

现在,我们将创建另一个函数,使用上述函数生成的坐标绘制每个角:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由像素皮克斯拜拍摄

不幸的是,算法并不像预期的那样工作。这些标记被放置在砖块的相交处,而不是寻找窗户的角落。这些路口都是噪音,让他们没用。让我们对图像进行降噪,并再次将其传递给函数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,这样好多了!它忽略了砖块的边缘,找到了大部分的窗户角落。

结论

唷!多好的帖子!你和我都值得表扬!

写这两篇文章的时候,我很开心。当然,在真正的计算机视觉问题中,你不会同时使用所有这些。你可能已经注意到了,我们今天学到的东西并不复杂,最多只需要几行代码。棘手的部分是将它们应用到实际问题中,并实际提高模型的性能。

这一点来自艰苦的工作和实践,而不是很好地包装在一篇文章中。感谢您的阅读!

在这里阅读第一部分。

您可以使用下面的链接成为高级媒体会员,并访问我的所有故事和数以千计的其他故事:

https://ibexorigin.medium.com/membership

或者订阅我的邮件列表:

https://ibexorigin.medium.com/subscribe

你可以在LinkedIn或者Twitter上联系我,友好的聊一聊万物数据。或者你可以读我的另一个故事。这些怎么样:

</10-minute-guide-to-julia-for-die-hard-python-lovers-a2fcf7dcb860> </6-pandas-mistakes-that-silently-tell-you-are-a-rookie-b566a252e60d> </8-booming-data-science-libraries-you-must-watch-out-in-2022-cec2dbb42437>

使用 Csvkit 包在终端中控制 CSV 文件

原文:https://towardsdatascience.com/master-csv-files-in-the-terminal-with-the-csvkit-package-70984d394501

使用 Excel 并不总是一个好主意。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由西格蒙德Unsplash 拍摄

您的旅程概述

  1. 为什么要在终端处理 CSV 文件?
  2. 快速设置
  3. Excel 黑仔(in2csv)
  4. 数据 Scapel (csvcut)
  5. 好看(csvlook)
  6. 牛逼统计(csvstat)
  7. 巧妙搜索(csvgrep)
  8. 包装

1 —为什么要在终端中处理 CSV 文件?

很少有事情能像在命令行终端中工作一样令人生畏。许多数据科学家没有计算机科学背景,因此不习惯这样。甚至看起来在命令行终端工作已经是过去的事情了😧

我在这里告诉你,这是假的。事实上,随着像 Azure Cloud Shell 这样的基于云的 Shell 的增加,学习命令行比以往任何时候都更有价值。事实是命令行是一个效率助推器。对文件夹使用图形用户界面(GUI)对于简单的事情来说是最容易的,但是对于较难的事情来说就变得越来越复杂了。

对于数据科学家/数据工程师来说,有许多终端工具可以快速处理终端中的数据。在这篇博文中,我将告诉你一个我最喜欢的叫做 csvkit 的东西。这是一个在终端中处理 CSV 文件的工具。如果您正在处理 CSV 文件或 Excel 文件形式的月度报告,这将非常有用。使用 csvkit,您可以轻松地:

  • 将 excel 文件转换为 CSV 文件
  • 打印格式良好的 CSV 文件
  • 剪切特定的列
  • 获取有关列的统计信息
  • 使用正则表达式在列内搜索

还有更多!您也许可以用 Excel 做同样的事情。然而,使用 csvkit 做事情的真正价值在于您可以自动化这个过程(例如使用 cronjobs)。

我将向您介绍如何使用 csvkit,以便您可以在终端中处理 CSV 文件。具体来说,我会教你我认为最重要的五个 csvkit 命令。如果你更喜欢视觉学习,那么我也在 csvkit 上制作了一个免费的 youtube 视频系列你可以看看😃

**先决条件:**您应该已经安装了运行 BASH 的命令行终端。如果你使用的是 Linux,那么这应该不成问题,而 Windows 用户可能想看看 cmder 或者用 WSL2 启用 Linux 子系统。

2—快速设置

要开始使用 csvkit 包,您需要安装它。csvkit 包是一个 Python 包,所以您需要做的就是运行(假设您已经安装了 Pyhon 的包安装程序 pip ):

pip install csvkit

如果不想全局安装 csvkit 包,那么可以考虑使用 virtualenv 来设置一个虚拟环境。要检查安装是否正常,您可以运行以下命令:

in2csv --help

如果一切安装正确,您现在应该会看到命令in2csv的帮助页面。命令in2csv是 csvkit 为我们安装的几个命令之一。

让我们打开一个名为csvkit_sandbox的新文件夹,你可以在这里玩你的新玩具:

mkdir csvkit_sandbox
cd csvkit_sandbox

最后,您需要一些实际使用的数据:让我们下载 csvkit 文档也在使用的数据:

curl -L -O https://raw.githubusercontent.com/wireservice/csvkit/master/examples/realdata/ne_1033_data.xlsx

运行上面的命令后,您应该在您的文件夹中安装了文件ne_1033_data.xlsx。如果你不信任我,你可以运行ls来检查你的csvkit_sandbox文件夹的内容😉

Excel 黑仔(in2csv)

我们要看的第一个命令是in2csv命令。这个命令将一个 excel 文件转换成一个 CSV 文件(废话!).这非常有用,因为数据科学家经常获得 excel 格式的报告,并且必须将它们转换为 CSV 文件才能有效地使用它们。

要使用in2csv命令,您只需编写:

in2csv ne_1033_data.xlsx

如果您像这样使用该命令,那么您只需将所有输出转储到终端。即使对于中等大小的文件,这也很糟糕。更好的选择是将输出重定向到一个文件,如下所示:

in2csv ne_1033_data.xlsx > new_file.csv

现在,在您的csvkit_sandbox文件夹中应该有一个名为new_file.csv的新 CSV 文件。我有时使用的一个技巧是在同一个命令中添加一个rm命令来删除原始文件,如下所示:

in2csv ne_1033_data.xlsx > new_file.csv && rm ne_1033_data

如果您运行上面的命令,那么在您的csvkit_sandbox文件夹中应该只有文件new_file.csv。如果您想签出文件new_file.csv,那么尝试命令:

head new_file.csv

该命令将文件new_file.csv中的第一行输出到终端。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Mohamed NohassiUnsplash 拍摄

4—数据手术刀(csvcut)

在上一节中,您从 excel 文件中检索了所有列,并将其转换为 CSV 文件。但是通常您不需要所有的列来进行进一步的分析。如果是这样的话,那么和我们一起拖无用的列将是一件麻烦事。不要害怕!使用命令csvcut可以选择 CSV 文件的某些列:

csvcut -c county,total_cost,ship_date new_file.csv**Output:**
county,total_cost,ship_date
ADAMS,138.0,2008-07-11
ADAMS,138.0,2008-07-11
ADAMS,138.0,2008-07-11
ADAMS,138.0,2008-07-11
ADAMS,138.0,2008-07-11
ADAMS,138.0,2008-07-11
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BURT,499.0,2013-06-20
...

上述命令仅从 CSV 文件new_file.csv中选择列countytotal_costship_date

但是如果您不记得列名了呢?然后,一个快速的技巧是运行命令:

csvcut -n new_file.csv**Output:** 1: state
2: county
3: fips
4: nsn
5: item_name
6: quantity
7: ui
8: acquisition_cost
9: total_cost
10: ship_date
11: federal_supply_category
12: federal_supply_category_name
13: federal_supply_class
14: federal_supply_class_name

可选参数-n给出了列的名称。整洁,对不对?命令csvcut对于选择想要继续的列非常有用。

5-看起来不错(csvlook)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

梅尔·埃利亚斯Unsplash 上拍摄的照片

当您将最后一部分中的 CSV 文件输出到终端时,它看起来不是很好。将原始 CSV 文件打印到终端时,这样读起来通常会很糟糕:

csvcut -c county,total_cost,ship_date new_file.csv**Output:**
county,total_cost,ship_date
ADAMS,138.0,2008-07-11
ADAMS,138.0,2008-07-11
ADAMS,138.0,2008-07-11
ADAMS,138.0,2008-07-11
ADAMS,138.0,2008-07-11
ADAMS,138.0,2008-07-11
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BUFFALO,499.0,2008-09-24
BURT,499.0,2013-06-20
...

命令csvlook前来救援。您可以使用管道操作符 |将命令csvcut的输出作为命令csvlook的输入,如下所示:

csvcut -c county,total_cost,ship_date new_file.csv | csvlook**Output:** | county     | total_cost |  ship_date |
| ---------- | ---------- | ---------- |
| ADAMS      |     138.00 | 2008-07-11 |
| ADAMS      |     138.00 | 2008-07-11 |
| ADAMS      |     138.00 | 2008-07-11 |
| ADAMS      |     138.00 | 2008-07-11 |
| ADAMS      |     138.00 | 2008-07-11 |
| ADAMS      |     138.00 | 2008-07-11 |
| BUFFALO    |     499.00 | 2008-09-24 |
| BUFFALO    |     499.00 | 2008-09-24 |
| BUFFALO    |     499.00 | 2008-09-24 |
| BUFFALO    |     499.00 | 2008-09-24 |
| BUFFALO    |     499.00 | 2008-09-24 |
| BUFFALO    |     499.00 | 2008-09-24 |
| BUFFALO    |     499.00 | 2008-09-24 |
| BUFFALO    |     499.00 | 2008-09-24 |
| BURT       |     499.00 | 2013-06-20 |
...

那看起来好多了!现在,用肉眼解析输出要容易得多。

还记得你用管道操作符|csvcut的输出传递给csvlook的输入的技巧吗?这在 csvkit 中非常常见,并且是创建更复杂管道的关键🔥

6 —令人敬畏的统计数据(csvstat)

当作为数据分析师或数据科学家处理 CSV 文件时,我们经常希望提取汇总统计数据。有数百种方法可以提取 CSV 文件中各列的中值和平均值。你应该用哪一个?将数据导入 Excel?用熊猫进行快速数据分析?

我想你知道我想说什么。当然,csvkit 对此有一个命令叫做csvstat。命令csvstat从 CSV 文件中的每一列提取基本统计数据:

csvstat new_file.csv**Output:**1\. "state"
Type of data:          Text
Contains null values:  False
Unique values:         1
Longest value:         2 characters
Most common values:    NE (1036x)2\. "county"
Type of data:          Text
Contains null values:  False
Unique values:         35
Longest value:         10 characters
Most common values:    DOUGLAS (760x)
                       DAKOTA (42x)
                       CASS (37x)
                       HALL (23x)
                       LANCASTER (18x)3\. "fips"
Type of data:          Number
Contains null values:  False
Unique values:         35
Smallest value:        31001
Largest value:         31185
Sum:                   32176888
Mean:                  31058.772
Median:                31055
StDev:                 23.881
Most common values:    31055 (760x)
                       31043 (42x)
                       31025 (37x)
                       31079 (23x)
                       31109 (18x)
...

如果您仔细阅读上面的输出,那么您会发现csvstat根据列类型给出不同的统计信息。如果你的某一列是文本格式,那么谈论那一列的标准差就没什么意义了。

命令csvstat执行类型的推理。它尝试推断列的类型,然后找到合适的汇总统计信息。根据我的经验,这非常有效。

有时,您不想要所有列的统计数据,而只想要几列。为此,使用命令csvcut过滤出必要的列,然后通过管道将其传递给csvstat以获得统计数据:

csvcut -c total_cost,ship_date new_file.csv | csvstat**Output:**1\. "total_cost"
Type of data:          Number
Contains null values:  False
Unique values:         92
Smallest value:        0
Largest value:         412000
Sum:                   5553135.17
Mean:                  5360.169
Median:                6000
StDev:                 13352.139
Most common values:    6800 (304x)
                       10747 (195x)
                       6000 (105x)
                       499 (98x)
                       0 (81x)2\. "ship_date"
Type of data:          Date
Contains null values:  False
Unique values:         84
Smallest value:        2006-03-07
Largest value:         2014-01-30
Most common values:    2013-04-25 (495x)
                       2013-04-26 (160x)
                       2008-05-20 (28x)
                       2012-04-16 (26x)
                       2006-11-17 (20x)Row count: 1036

7 —智能搜索(csvgrep)

我极力推荐的最后一个命令是csvgrep命令。命令csvgrep允许您搜索 CSV 文件以找到特定的记录。如果你习惯了终端命令grep,那么csvgrep命令也差不多。

要查看它的运行情况,让我们在 CSV 文件中搜索 county HOLT :

csvgrep -c county -m "HOLT" new_file.csv**Output:** NE,HOLT,31089.0,1005-00-073-9421,"RIFLE,5.56 MILLIMETER",1.0,Each,499.0,499.0,2008-05-19,10.0,WEAPONS,1005.0,"Guns, through 30 mm"NE,HOLT,31089.0,1005-00-589-1271,"RIFLE,7.62 MILLIMETER",1.0,Each,138.0,138.0,2006-03-08,10.0,WEAPONS,1005.0,"Guns, through 30 mm"NE,HOLT,31089.0,1005-00-589-1271,"RIFLE,7.62 MILLIMETER",1.0,Each,138.0,138.0,2006-03-08,10.0,WEAPONS,1005.0,"Guns, through 30 mm"
...

成功了!现在你只能得到县霍尔特的记录。

  • 参数-c用于选择列。
  • 参数-m用于查找与匹配的字符串*。*

Pro 提示:csvgrep命令也允许使用可选参数-r进行正则表达式搜索。查看 csvgrep 文档了解更多相关信息!

我给你的最后一个建议是,试着组合几个 csvkit 命令来获得很酷的结果。看看这个。

csvcut -c county,total_cost new_file.csv | csvgrep -c county -m "HOLT" | csvlook**Output:**
| county | total_cost |
| ------ | ---------- |
| HOLT   |     499.00 |
| HOLT   |     138.00 |
| HOLT   |     138.00 |
| HOLT   |     138.00 |
| HOLT   |     138.00 |
| HOLT   |     138.00 |
| HOLT   |     138.00 |
| HOLT   |      58.71 |

首先,我只选择了带有csvcut的两列countytotal_cost。然后我用命令csvgrep搜索县 HOLT 。最后,我用命令csvlook很好地显示了结果。现在轮到你和✌️搭档了

8 —总结

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由斯潘塞·伯根Unsplash 上拍摄

希望你开始像我一样喜欢 csvkit。同样,如果你想学习一些更酷的技巧和诀窍,那么查看 csvkit 文档或 csvkit 上我的 youtube 视频

**喜欢我写的?**查看我的其他帖子,了解更多 Python 内容:

如果你对数据科学、编程或任何介于两者之间的东西感兴趣,那么请随意在 LinkedIn 上加我,并向✋问好

主数据完整性清理您的计算机视觉数据集

原文:https://towardsdatascience.com/master-data-integrity-to-clean-your-computer-vision-datasets-df432cf9e596

辅导的

处理数据泄漏。降低贴标成本。减少计算时间和费用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

JESHOOTS.COMUnsplash 上拍照

数据完整性是最近一段时间公司和工程师最关心的问题之一。

我们必须处理和理解的数据量只会变得更加重要,手动查看数百万个样本是不可持续的。因此,我们需要能够帮助我们浏览数据集的工具。

本教程将介绍如何清理、可视化和理解计算机视觉数据集,如视频或图像。

我们将制作一部关于我家最珍贵的东西——我的猫的视频。我们的最终目标是从视频中提取关键帧,这些帧最近可以被发送到标签和训练你的模型。从视频中提取关键信息并不简单,因为视频属性不断变化。例如,在开始时,视频是高度静态的,从中间开始有很多动作。因此,我们需要一种智能的方法来理解视频的属性,以消除重复的图像,找到离群值,并对相似的照片进行聚类。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

GIF 来自我的 sassy 猫的视频【作者 GIF】。

利用 FastDup,一个用于理解和清理 CV 数据集的工具,我们将向您展示如何解决上述问题。

最终目标

我们将提供一个教程,对我的野蛮女友的视频进行解码,并从中提取所有帧作为图像。我们将使用 FastDup 来可视化图片数据集上的不同统计数据。

主要目标是从数据集中移除相似/重复的图像。在下一节中,我们将详细说明为什么清除数据集的重复项至关重要。最终,我们将查看移除所有相似照片前后的异常值。

为什么删除重复图像很重要?

当数据变大时,手动解析和理解所有的样本是非常困难的。因此,您需要更多创新的方法来查询数据集以找到重复的图像。

当使用表格数据时,可以用 Pandas、SQL 等工具绕过这个问题。但是,当建模图像是一个挑战。因此,我们需要从不同的角度来处理这个问题。使用 FastDup,我们可以快速计算数据集上的一组嵌入,用它我们可以执行数据检索、聚类、离群点检测等。最终,我们可以轻松地查询和可视化我们的数据集,以更好地理解它。

我们将消除相似图像的重要性归结为三个主要观点:

#1 数据泄露

在处理图像时,尤其是处理视频时,许多示例都是相似的。一个稳健的策略是使用嵌入来检测相似的照片,并相应地删除重复的照片。保留副本容易导致数据泄露。比如在拆分数据时,很容易不小心在列车拆分中加入图像 A,在测试拆分中加入图像 B,其中 A 和 B 是相似的。在处理大型数据集时,经常会遇到这样的数据泄漏问题,不幸的是,这会导致错误的结果,并可能导致在生产中部署模型时性能不佳。

#2 降低贴标成本

大多数表现良好的 ML 模型使用有监督的方法来学习。因此,我们需要标签来训练它们。

在数据集中保留相似的影像会使数据集变大,但没有任何积极效果。在这种情况下,更多的数据不会增加任何价值,因为它不会增加任何新的信息。而是你只是花钱花力气给相似的图像贴标签。

#3 减少计算时间&成本

正如我们在第 3 点中讨论的。,数据需要添加额外的信息来帮助建模。因此,通过在您的数据集中保留相似的样本,您的训练时间将会更长且成本更高。最终,它将导致更少和更长的实验,由于缺乏反馈而导致更差的模型。

什么是 FastDup?

FastDup 是计算机视觉中的一个 Python 工具,具有许多奇妙的特性,可以洞察大量的图像/视频集合。

它们的一些主要特征:

  • 发现异常
  • 查找重复项
  • 使聚集
  • 图像/视频之间的交互
  • 计算嵌入

它是无人监督的,可伸缩的,并且在你的 CPU 上运行很快。这太棒了,因为 GPU 很贵,是小型团队的最大障碍之一。

现在,因为我们看到了理解和清理数据集的重要性,所以让我们开始本教程,并使用 FastDup 来看看我们如何在我的 sassy 猫的视频中解决这些问题。

我们走吧👇🏻

从视频中提取图像

下面是一些反映我们资源位置的常量。我们有输入视频的路径、提取帧的目录,以及 FastDup 计算和缓存数据集信息(如嵌入、离群值和各种统计数据)的目录。FastDup 提取的信息只计算一次,可以随时访问。

我们将视频逐帧提取到给定的输出目录中。该视频的 FPS 为 30,长度为 47 秒。 skip_rate 是我们从视频中提取帧的速率。这种情况下, skip_rate = 1 。因此,我们逐帧提取视频。如果 skip_rate = 5,我们将提取第 0 帧、第 5 帧、第 10 帧等等。

frames_count = extract(input_video_path=INPUT_VIDEO_PATH, output_dir=VIDEO_EXTRACTION_1_SKIP_DIR)FPS = 30
Frame Count = 1409
Video Length = 47 seconds
Extract Frame Count = 1409

使用 FastDup 可视化数据

首先,让我们了解一些我们将在本文中继续使用的术语:

  • **成分:**相似图像的聚类。一个组件总是有多个图像。
  • **相似图像:**不完全相同但包含相同信息的图像。大多数图像不会是彼此完美的复制品。因此,必须有一种机制来查找密切相关的图像。

现在我们调用 FastDup 来分析我们的数据集,并在给定的目录中缓存所有的分析数据。

compute_fastdup_analytics(images_dir=VIDEO_EXTRACTION_1_SKIP_DIR, analytics_dir=FASTDUP_ANALYTICS_1_SKIP_DIR)

统计数字

使用 FastDup,您可以根据不同的统计数据有效地计算数据集的直方图,这些统计数据包括:平均值、标准差、最小值、最大值、大小等。

fastdup.create_stats_gallery(FASTDUP_ANALYTICS_1_SKIP_DIR, save_path="stats", metric="mean")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

视频内图像平均值的直方图【图片由作者提供】。

fastdup.create_stats_gallery(FASTDUP_ANALYTICS_1_SKIP_DIR, save_path="stats", metric="stdv")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

视频内图像标准差直方图【图片由作者提供】。

最有趣的指标是模糊,它可以快速突出模糊图像及其统计数据:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

关于视频内模糊图像的统计[图片由作者提供]。

极端值

Fastdup 利用嵌入和 K-Means 将图像聚类到嵌入空间中。因此,我们可以快速找到相似图像的集群(用他们的行话来说就是“组件”)和离群值。

让我们来计算异常值:

离群值可视化帮助我们看到令人兴奋的场景。在我们的例子中,异常图像是模糊的。只要样本中的信息是有效的,使用有噪声的样本来提高模型的稳健性是一种好的做法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

视频中的前 5 个异常值,用 FastDup 快速计算[图片由作者提供]。

复制

最后,让我们计算并找出我们从视频中提取了多少相似的图像:

FastDup 基于组件的大小创建直方图,其中组件是相似图像的集群。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

视频中的前 3 个组件逐帧提取。最重要的部分是包含最相似图像的部分。

正如所料,如果我们导出视频中的所有帧,我们最终会得到许多重复的帧。我们可以清楚地看到包含最多副本的前 3 个组件。此外,我们可以看到组件大小的分布。6 个分量有超过 40 个图像,这表明在大约 6 个场景中,视频非常静态。

此外,通过计算 num_images / FPS ,我们可以估计静态场景的秒数。

我们如何删除所有重复的内容?

一种简单的方法是增加 skip_rate 来消除相似相邻帧之间的冗余。让我们看看表现如何。

提取具有较高跳过率的帧

正如我们之前所说的,让我们使用 skip_rate = 10 来看看我们是否可以解决相似性问题。该视频的 FPS 为 30,经过几次实验,我们发现 skip_rate = 10 是一个平衡的数字。

frames_count_10_skip = extract(input_video_path=INPUT_VIDEO_PATH, output_dir=VIDEO_EXTRACTION_10_SKIP_DIR, skip_rate=SKIP_RATE)FPS = 30
Frame Count = 1409
Video Length = 47 seconds
Extract Frame Count = 140

我们必须重新计算 FastDup 分析,以可视化新组件:

现在让我们来想象一下组件:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

视频摘录中跳过率为 10 的前 3 个组件。最重要的部分是包含最相似图像的部分。

在尝试了多种跳过率之后,我们解决了大部分相似性问题。我们可以看到,在只有 9 个和 8 个样本的 2 个组件中发现了重复图像。我们用这种简单的方法做了一件体面的工作。

此外,一个巨大的好处是提取时间快了 10 倍。当有大量视频时,这是需要考虑的一个重要方面。

但遗憾的是,这种方法一点也不健壮。原因如下:

增加跳级率的危险

#1.信息的丢失

视频中的活动总是以不同的速率移动。我们可以找到 2-3 个视频的最佳 skip_rate ,在保留足够的必要样本和去除冗余数据之间找到最合适的平衡。尽管如此,因为现实世界是混乱的,这个数字可能会在新视频的某个时候打破。在不知情的情况下,你会跳过视频中的关键帧。

#2.你无法控制相似性问题

因为视频是动态的,固定的跳过率只适合你的一些视频。因此,为数据集中的每个视频寻找不同的跳过率是不可持续的。此外,在所有视频中使用一个较高的 skip_rate 值是行不通的。

即使对于我们的视频,这是非常简单的,我们只能删除一些重复的。

如上所述,我们看到,使用更高的跳过率不会做这项工作。因此,我们需要一种更加智能和动态的方法来找到相似的图像并删除重复的图像。

我们很幸运,因为 Fastdup 正在这么做。让我们看看怎么做。

使用 FastDup 排除重复项

使用 FastDup,我们可以使用更巧妙的方法。我们已经知道图像的聚类嵌入空间内的分量。因此,我们知道哪些图像在同一个聚类中,这转化为相似的照片。FastDup 为我们提供了仅保留一个映像/组件的功能。

这种方法好 10 倍,因为它独立于视频中的动态。相对于视频语义的不规则性,它是鲁棒的和灵活的。

我们必须再次提取所有帧并重新计算 FastDup 分析。

 FPS = 30
Frame Count = 1409
Video Length = 47 seconds
Extract Frame Count = 1409

-------------Deleted frames count: 1202
Deleted 85.308730% of the frames.

哇哦。85%的图像代表冗余数据,这是一个很大的数字。想象一下,不用标注 1000 张图片,只需要标注 150 张。这节省了很多时间。

清理完目录映像后,让我们重新计算组件,看看它们是什么样子。

compute_fastdup_analytics(images_dir=VIDEO_EXTRACTION_1_SKIP_DIR, analytics_dir=FASTDUP_ANALYTICS_1_SKIP_DIR)
AssertionError: No components found with more than one image/video

那是什么?我们出错了吗?

是的,我们做到了,这是完美的。这意味着 FastDup 不再找到任何组件。因此,数据集不再包含相似的图像。

仅此而已。简单对吗?是不是很酷?

最终策略。将 Fastdup 与更高的提取率相结合。

我称赞说,使用嵌入来删除相似的图像是最好的方法。但不幸的是,逐帧导出视频需要很长时间。因此,最好的方法是将这两种策略结合起来。但这一次,我们将使用 2 的低跳过率,因为现在跳过的目标不是消除表里不一,而是加快提取时间。我们仍将使用 FastDup 来处理清理过程。

frames_count_2_skip = extract(input_video_path=INPUT_VIDEO_PATH, output_dir=VIDEO_EXTRACTION_2_SKIP_DIR, skip_rate=SKIP_RATE)FPS = 30
Frame Count = 1409
Video Length = 47 seconds
Extract Frame Count = 704compute_fastdup_analytics(images_dir=VIDEO_EXTRACTION_2_SKIP_DIR, analytics_dir=FASTDUP_ANALYTICS_2_SKIP_DIR)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

视频摘录中跳过率为 2 的前 3 个组件。最重要的部分是包含最相似图像的部分。

如你所见,使用 skip_rate = 2 ,我们仍然有很多相似的图像。但是导出时间快了两倍。

delete_duplicates(FASTDUP_ANALYTICS_2_SKIP_DIR, total_frames_count=frames_count_2_skip)Deleted frames count: 346
Deleted 49.147727% of the frames.

很好。这次只有总帧数的 50%是重复的。

移除相似图像前后的异常值

观察清理前后异常值的 IoU 是很有趣的。理论上,离群值在删除重复项后基本保持不变,因为数据集的一般语义信息应该保持不变。不幸的是,异常值之前和之后之间的 IoU 只有大约 30%,这表明移除所有重复图像改变了几何空间,这可以更好。

0.31

通过向您展示离群值的例子,我想强调的是,尽管这种方法是创新的并且非常有用,但它仍然需要改进。我们刚刚开始以数据为中心的运动;因此,我们必须保持关注,看看它会给未来带来什么。

结论

谢谢你看我的教程。如果你读过它,你会发现控制你的数据集是多么容易。

我们展示了如何使用 FastDup 处理一个真实世界的视频(我的一只时髦的猫),以快速找到计算机视觉数据集中的相似图像和离群值。

我们强调了理解数据集并根据您的业务问题正确处理相似图像的重要性。否则,您可能会遇到数据泄漏、更高的标注成本以及更高的计算时间和成本。问题转化为错误的度量和更少的实验,最终导致更差的模型。

这里是 GitHub 资源库的链接。

💡我的目标是让机器学习变得简单直观。如果你喜欢我的文章,我们可以在 LinkedIn 上联系,了解关于#数据#ml#mlops 的每日见解。

https://pauliusztin.medium.com/membership

数据网格中的主数据管理

原文:https://towardsdatascience.com/master-data-management-in-data-mesh-594d21f3ee10

如果它是稳定的并且确实重要,那么考虑使用 MDM

使用数据网格架构的企业通常有大量的,每个域都有自己的系统和数据。这意味着增加了复杂性,因为数据分布很广,同一数据可能存在多个版本。集成,例如提供客户的 360 度视图,因此需要更多的努力,因为它需要您集成和协调来自不同领域的相同数据的所有不同的独立部分。另一个挑战是,不同领域之间的数据可能不一致,数据质量水平也可能存在差异。为了应对这些挑战,您应该在数据网格架构中应用主数据管理(MDM)。

面向领域的架构中的主数据管理工作方式不同,因为它是分布式的。一致性更难实现,因为您依赖于域内主数据的管理。

如果数据是快速和流动的,将它分成更小的片段,并留给域处理。如果数据是稳定的并且确实很重要,那么考虑使用 MDM。

面向领域的主数据管理

尽管许多 MDM 解决方案可以管理参考数据和主数据,但我建议明确区分这两者。参考数据是用于定义、分类、组织、分组或归类其他数据(或价值层次,如产品和地理层次之间的关系)的数据。相比之下,主数据讲的是核心概念。每种类型通常都有不同的管理方式。

参考数据

实现域间数据一致性的一种方法是,在分发数据产品时,要求您的域符合集中管理的参考数据。货币代码、国家代码或产品代码通常是可疑的。这些参考数据列表被发布在例如主数据存储或中央储存库中。当您的任何域跨其他域分发数据产品时,域使用企业参考数据中的企业参考标识符来对数据进行分类。本地数据到企业数据的映射允许其他域快速识别数据产品中的主数据。这种映射活动还意味着域需要查找或交换集中管理的参考数据,以了解哪些本地数据需要映射到中央主数据。

主数据

对于主数据,方法略有不同。主数据的一个重要方面是主标识号,它将主数据和域中的数据联系在一起。这些数据元素对于追踪哪些数据已经被掌握以及哪些属于一起是至关重要的。识别唯一数据和分配主标识符只能集中完成,而不能在系统内部本地完成。它需要将来自不同系统的主数据放在一个 MDM 解决方案中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

主数据管理领域(鸣谢:Piethein Strengholt)

当对 MDM 活动进行分组并使用主数据存储作为集中式存储库时,您还可以创建新的 MDM 域。这种 MDM 域指的是一个特定的数据主题,其中主要关注主数据的识别和控制。众所周知的例子有客户、产品、地理位置、财务和风险以及员工数据。来自这些 MDM 域的主数据有望返回到域中。这些数据的分发类似于数据产品的分发。

您可以确定主数据管理的范围,并允许不同的数据产品分发方法。例如,在某个范围边界内的一些数据产品不必符合企业主数据,而分布在该范围之外的数据产品必须符合。或者反过来应用这种模式:只有在特定的范围内才需要严格遵守。

主识别号

识别和维护这些关系不仅对于了解已经掌握了哪些数据非常重要,而且对于了解哪些数据可以快速链接到其他数据也非常重要。如果操作系统中的本地(域)键发生变化,将所有内容绑定在一起的唯一元素将是主标识符。因此,建议您也建立从 MDM 解决方案到当地管理部门的反馈回路。

在分发主标识符时,建议您不要将 MDM 主标识符外推至所有源系统,这可能会产生不一致的问题。只有受主数据管理区域约束的应用程序或系统才应该从 MDM hub 获取主标识符。不受 MDM 约束的系统应该使用自己的本地(域)完整性。

域级主数据管理

当您寻找重叠数据时,您可能会发现不同程度的重叠。有些数据是通用的,跨越许多领域;其他数据具有有限的重叠,并且仅跨越几个领域。为了区分重叠的重要性和数量,您还可以通过在特定范围内创建主数据的局部视图,将 MDM 扩展到域级 MDM。这对于在一些(但不是所有)域之间共享的数据非常有用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

域级主数据管理(鸣谢:Piethein Strengholt)

对于重叠的域来说,重要的是管理它们的数据,但是没有中心依赖性。这方面的 MDM 解决方案最好以服务的形式提供。将基础设施抽象出来,将 MDM 作为服务提供给域,可以极大地简化使用。如果您正在使用中央 MDM 基础设施,建议您对每个单独的域或范围应用隔离视图。

通过可重用组件实现 MDM 一致性

主数据协作和可重用性的另一种方式是代码共享。这里共享的不是主数据,而是底层代码(代码片段和脚本)来生成输出并促进有效的重用。这些代码存储在一个集中的开放的存储库中,包括版本控制,允许领域团队贡献和改进已经发布的内容。

这种模型的好处是,业务逻辑只在领域内应用,这允许团队在他们认为合适的时候偏离、改进或使用稍微优化的逻辑版本。此外,随着社区的改进进入中央代码库,这些输出可以重新生成。这种模型的一个缺点是一致性,因为允许团队修改他们的代码会使得在团队之间比较结果更加困难。

后续步骤

主数据管理的重要性显而易见:只有当用户使用的数据一致且正确时,他们才能做出正确的决策。MDM 确保跨域级别的一致性和质量。组织需要找到一个平衡点。引入过多的主数据或参考值区域会引入过多的跨域对齐。根本没有企业数据,无法比较任何结果。

开始在您的组织中实现 MDM 的一个实用方法是从最简单的主数据管理方式开始:实现存储库。有了存储库,您可以通过了解哪些数据需要调整或哪些数据质量差来快速交付价值,而无需调整任何域系统。

下一步将是设定更清晰的范围。不要因为选择所有数据而陷入企业数据统一的陷阱。从增加价值最多的主题开始,如客户、合同、组织单位或产品。只选择最重要的领域来掌握。属性的数量应该是十个,而不是几百个。在您与您的领域达成一致之后,调整流程和治理。让所有领域都清楚地了解您在时间表和评审方面的协议。还要处理元数据,因此主数据被编目,域知道来自什么源系统的什么数据元素是候选的,以及这些元素如何在数据管道中流动。

最后一步,也是最终目标,是实现共存:改进直接反馈到您的领域。这一步是最困难的,因为它需要对架构进行许多更改。域需要能够处理来自集中管理的 MDM 解决方案的修正和改进。

用这三种有用的技术掌握熊猫的数据转换

原文:https://towardsdatascience.com/master-data-transformation-in-pandas-with-these-three-useful-techniques-20699f03e51d

对过滤、操作和功能的深入研究

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Milad FakurianUnsplash 上拍摄的照片

回想一下您最后一次使用格式良好的数据集的情况。命名良好的列、最少的缺失值和适当的组织。拥有不需要清理和转换的数据是一种很好的感觉——几乎是自由的。

好吧,这很好,直到你从你的白日梦中抽离出来,继续在你面前破碎的行和无意义的标签的绝望混乱中修补。

没有干净数据(原始形式)这种东西。如果你是一个数据科学家,你知道这一点。如果你刚刚开始,你应该接受这一点。为了有效地使用数据,您需要转换数据。

我们来谈谈三种方法。

过滤——但解释得当

让我们来谈谈过滤——但比你可能习惯做的更深入一点。作为最常见和最有用的数据转换操作之一,有效地过滤是任何数据科学家的必备技能。如果你了解熊猫,这可能是你学会做的第一个手术。

让我们回顾一下,用我最喜欢的,奇怪的通用例子:一个学生成绩的数据框架,恰当地称为grades:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

我们将过滤掉任何低于 90 分的分数,因为在这一天,我们决定成为只迎合优等生的训练不足的教育者(请永远不要这样做)。完成此任务的标准代码行如下:

grades[grades['Score'] >= 90]

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

只剩下杰克和赫敏了。酷毙了。但是这里到底发生了什么?为什么上面这行代码行得通?让我们更深入一点,看看上面外层括号内的表达式的输出:

grades['Score'] >= 90

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

啊,好吧。有道理。这一行代码似乎返回了一个 Pandas Series 对象,该对象包含由每一行返回的内容确定的布尔值(True / False)。这是关键的中间步骤。之后,就是这一系列的布尔值被传递到外层的括号中,并相应地过滤所有的行。

为了完整起见,我还将提到使用loc关键字可以实现相同的行为:

grades.loc[grades['Score'] >= 90]

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

我们选择使用loc有很多原因(其中之一是它实际上允许我们通过一个操作来过滤行和列),但是这打开了 Pandas 操作的潘多拉魔盒,最好留给另一篇文章来处理。

目前,重要的学习目标是这样的:当我们过滤熊猫时,令人困惑的语法不是某种怪异的魔法。我们只需要把它分解成两个组成步骤:1)获得满足我们条件的行的布尔序列,2)使用该序列过滤出整个数据帧。

你可能会问,这为什么有用?嗯,一般来说,如果你只是使用操作而不了解它们实际上是如何工作的,这很可能会导致令人困惑的错误。过滤是一种非常有用且非常常见的操作,您现在知道它是如何工作的了。

我们继续吧。

λ函数的美妙之处

有时,您的数据需要转换,而这并不是 Pandas 的内置功能。尽管你可能会尝试,但是再多的搜索栈溢出或者勤奋地研究 Pandas 文档也不能揭示你的问题的解决方案。

输入 lambda 函数——这是一个有用的语言功能,与 Pandas 完美地集成在一起。

快速回顾一下,lambdas 是如何工作的:

>>> add_function = lambda x, y: x + y
>>> add_function(2, 3)
5

Lambda 函数与常规函数没有什么不同,只是它们的语法更简洁:

  • 等号左边的函数名。
  • 等号右边的lambda关键字(类似于传统 Python 函数定义中的def关键字,这让 Python 知道我们正在定义一个函数)。
  • 参数在lambda关键字之后,冒号的左边。
  • 冒号右边的返回值。

现在,让我们将 lambda 函数应用于实际情况。

数据集通常有自己的格式特点,具体到数据输入和收集的变化。因此,您正在处理的数据可能有您需要解决的奇怪的特定问题。例如,考虑下面的简单数据集,它存储了人们的姓名和收入。姑且称之为monies

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

现在,作为该公司的主数据殿下,我们得到了一些绝密信息:该公司的每个人都将获得 10%的加薪,外加 1000 美元。这种计算可能过于具体,无法找到具体的方法,但对于 lambda 函数来说就足够简单了:

update_income = lambda num: num + (num * .10) + 1000

然后,我们需要做的就是将这个函数与 Pandas apply函数一起使用,这样我们就可以将一个函数应用于所选系列的每个元素:

monies['New Income'] = monies['Income'].apply(update_income)
monies

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

我们完事了。这是一个全新的数据框架,包含了我们需要的所有信息,只有两行代码。为了使它更加简洁,我们甚至可以直接在apply中定义 lambda 函数——这是一个值得记住的很酷的技巧。

我会简单地说明这一点。

Lambdas 非常有用,因此,你应该使用它们。尽情享受吧!

系列字符串操作函数

在上一节中,我们讨论了 lambda 函数的多功能性,以及它们可以帮助您使用数据完成的所有很酷的事情。这很好,但是你应该小心不要忘乎所以。如此沉迷于一种熟悉的做事方式,以至于错过了 Python 赋予程序员的更简单的捷径,这种情况非常普遍。当然,这不仅仅适用于 lambdas,但我们暂时会坚持这一点。

例如,假设我们有以下名为names 的数据帧,其中存储了人们的名字和姓氏:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

现在,由于数据库的空间限制,我们决定不存储一个人的整个姓氏,而只存储他们的姓首字母会更有效。因此,我们需要相应地转换'Last Name'列。对于 lambdas,我们这样做的尝试可能如下所示:

names['Last Name'] = names['Last Name'].apply(lambda s: s[:1])
names

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

这显然是可行的,但是有点笨拙,因此不像 Pythonic 那样好。幸运的是,有了 Pandas 中的字符串操作功能,还有另一种更优雅的方式(为了下一行代码的目的,假设我们还没有用上面的代码修改过'Last Name'列):

names['Last Name'] = names['Last Name'].str[:1]
names

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

哒哒!熊猫系列的.str属性让我们通过指定的字符串操作拼接系列中的每个字符串,就像我们单独处理每个字符串一样。

但是等等,还有更好的。由于.str有效地让我们通过系列访问字符串的正常功能,我们还可以应用一系列字符串函数来帮助快速处理我们的数据!例如,假设我们决定将两列都转换成小写。下面的代码完成了这项工作:

names['First Name'] = names['First Name'].str.lower()
names['Last Name'] = names['Last Name'].str.lower()
names

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

这比定义自己的 lambda 函数并在其中调用字符串函数要简单得多。并不是说我不喜欢 lambdas——但是每样东西都有它的位置,在 Python 中简单应该总是优先考虑的。

这里我只介绍了几个例子,但是有大量的字符串函数供您使用。

自由地使用它们。他们很棒。

最终想法和总结

这里有一个小小的数据转换备忘单:

  1. 认真过滤。了解到底发生了什么,这样你才知道自己在做什么。
  2. 爱你的小羊羔。它们可以帮助你以惊人的方式操纵数据。
  3. 熊猫和你一样热爱弦乐。有许多内置的功能,你也可以使用它。

这里是最后一条建议:没有“正确”的方法来过滤数据集。这取决于手头的数据以及您希望解决的独特问题。然而,虽然每次都没有固定的方法可以遵循,但是有一些有用的工具值得您使用。在本文中,我讨论了其中的三种。

我鼓励你出去多找一些。

想擅长 Python? 在这里 获得独家、免费获取我简单易懂的指南。想在介质上无限阅读故事?用我下面的推荐链接注册!

https://murtaza5152-ali.medium.com/?source=entity_driven_subscription-607fa603b7ce---------------------------------------

参考

[1]https://www . about datablog . com/post/10-most-used-string-functions-in-pandas

掌握 Python 理解

原文:https://towardsdatascience.com/master-python-comprehensions-4ef1c66b452d

以简单性和可读性为目标编写代码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Unsplash 的 Ksenia Makagonova 拍摄。

Python 最显著的优势之一是它是一种高度表达的语言。没有太多的样板文件,开发人员可以想出优雅的解决方案,如果结构良好,读起来就像一个故事。

这就是语言之美,在 Python 的中得到了很好的展现。如果我必须强调其中的一句话,那就是“简单胜于复杂”。我们应该尽可能地坚持这样一条黄金法则,即代码被阅读的次数要比它被编写的次数多得多,无论是被作者、同事还是外部贡献者阅读。因此,直截了当,避免华丽的辞藻,帮助你的队友和未来享受你今天写的东西。

这篇文章将遵循这一思想,通过一系列例子介绍处理数据时最常见的任务之一:转换列表,重点关注简单性和可读性。

语句是 Python 的控制流工具之一,可以帮助我们迭代序列中的项目。在每一步中,我们都拥有原始数据中的一个元素。

第一个示例是将一系列数字中的偶数元素加 1。

虽然这是一个相对简单的问题,但我们需要执行多个操作:

  1. 实例化一个空列表。
  2. 迭代data的每个元素。
  3. 检查元素是否是偶数。
  4. 在每一步更新added对象。

它完成了工作,但是我们可以使代码更紧凑,更容易理解。我们的主要目标是通过浏览代码来传达我们的意图。

地图和过滤器

高阶函数接受函数作为参数或者返回函数作为结果。这有点拗口,但是利用 Python 允许我们像对待任何其他对象一样对待函数是很重要的。

map将帮助我们在序列的每个元素上运行一个函数,而filter添加了跳过某些项目的逻辑。为了简单起见,我们将使用lambda来编写我们的函数,但是这个例子也将使用命名定义。

从这个片段中重要的是,能够编写一行程序并不意味着我们应该这样做。

请注意理解最后几行中发生的事情是多么容易,以及编写有意义的名称的重要性。人们可能不理解filter是做什么的,但是通过使用名称even,我们帮助我们的读者继续下去,而不要跳到任何实现细节。按照这个逻辑,我应该在这里做得更好,并且在 lambda 表达式中使用num而不是x

列表理解

Python 中我最喜欢的特性之一是理解。它们在简单直接地表达转换方面非常出色,开发人员可以通过使用不同的符号将这些转换应用于列表、集合、字典和生成器。

我们可以定义集合,并通过函数和条件动态地构建它。

请注意,阅读这段代码时主要关注的是实际的逻辑。一眼就能看出代码的目的是什么。然后,我们可以包装这个逻辑,将data对象转换成不同的结构。

我发现自己在理解中反复使用的一个小技巧是从一个列表中提取一个元素,如果它存在的话。

这个要点提取理解逻辑,并把它变成一个迭代器。在内部,Python 使用next从迭代器中的一个元素跳到另一个元素。因此,我们可以应用我们的过滤逻辑,返回我们感兴趣的元素,或者获取它,或者如果它不存在,获取None。这种方法让我们可以在代码的后面优雅地处理类型提示和条件。

回到理解。这是否意味着我们应该为循环 或更高阶的函数抛弃*?一点也不。开发人员需要评估不同方法的逻辑有多复杂。每种工具都有它的位置,有了经验,人们就会知道哪种设计变得更容易维护。*

三元运算符

我们已经看到了如何使用条件来过滤掉元素。然而,另一种典型的做法是将条件直接应用于每个元素,作为转换的一种方式。三元运算符有助于我们做到这一点。

通过检查每个元素,我们已经根据项目的属性将数字列表转换为字符串列表。这种情况可能看起来过于简单,但是将这种逻辑与过滤和数据描述符(如 Pydantic )组合在一起就成了一个极好的方法。

可读性强、直截了当的永远是赢家。在这种情况下,它比其他选择更优雅。不是因为它更短,而是因为它更好地传递了代码的目的。

赋值表达式

我们将以一个来自 PEP 572 的精彩附加节目来结束这个节目。Python 3.8 中提供的赋值表达式允许开发人员为表达式结果命名(赋值变量),这在处理理解时特别有用。

让我们想象一个函数,它可能为一些输入返回None。然后,我们需要将该函数应用于给定的列表,并过滤掉缺失的值。

简单的方法意味着运行该函数两次:

  1. 首先,检查输出是否为用于滤波的None,以及
  2. 然后,存储有效的返回值。

如果函数需要很长时间来运行或者需要一些外部组件,我们应该完全避免这种方法,因为执行两次这个过程是不可能的。

然而,使用赋值表达式(又名 walrus 操作符),我们可以获得理解的所有好处,而不会牺牲函数方面的任何性能。该函数只执行一次,我们可以在本地使用res变量来构建结果列表。

结论

Python 是一种非常灵活的语言:有很多途径可以达到相同的结果。因此,了解可供我们选择的方案至关重要。否则,如果你有锤子,一切看起来都像钉子

在这篇文章中,我们看到了如何使用以下方法过滤和转换数据的不同选择:

  • 对于循环,
  • 映射和过滤,以及
  • 三元运算符和赋值表达式的理解。

难的是没有金科玉律。我们需要找到传达我们意图的最佳方式。这不是聪明的问题,而是清晰的问题。

使用助手类掌握数据科学工作流

原文:https://towardsdatascience.com/mastering-data-science-workflows-with-helper-classes-1134afbd0600

用于 EDA、特征工程和机器学习的 Python 助手类

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

像素上的像素生成的图像

在计算机编程中,类是组织数据(属性)和函数(方法)的一种有用方式。例如,您可以定义一个类,该类定义与机器学习模型相关的属性和方法。这种类型的类的实例可能具有诸如训练数据文件名、模型类型等属性。与这些属性相关的方法可以是拟合、预测和验证。

除了机器学习,一般来说,类在数据科学中有广泛的应用。您可以使用类来组织各种 EDA 任务、功能工程操作和机器学习模型培训。这是很理想的,因为如果写得好,类会使理解、修改和调试现有的属性和方法变得容易。如果类方法被定义为完成一个明确定义的任务,这一点尤其正确。定义只做一件事的函数和类使理解和维护这些方法更加直接,这通常是一种好的做法。

虽然使用类可以使维护代码变得更加简单,但是随着复杂性的增加,它也会变得更加难以理解。如果你喜欢组织基本 EDA、特征工程和模型训练的属性和方法,一个单独的类可能就足够了。但是,当您为每种类型的任务添加更多的属性和方法时,这些对象的初始化会变得非常模糊,尤其是对于阅读您代码的合作者。考虑到这一点,随着复杂性的增加,为每种类型的任务(EDA、特征工程、机器学习)设置助手类而不是单个类是理想的。当开发复杂的 ML 工作流时,应该有单独的 EDA、特征工程和机器学习课程,而不是单一的课程。

在这里,我们将考虑每一种类型的任务,并看看如何编写一个单一的类,使我们能够执行它们。对于 EDA,我们的课程将允许我们读入数据,生成直方图和散点图。对于特征工程,我们的类将有一个采用对数变换的方法。最后,对于机器学习,我们班将有适合,预测和验证方法。

从这里我们将看到当我们添加额外的属性和方法时,类实例化和方法调用变得更加难以阅读。我们将为每种任务类型添加额外的方法和属性,并说明当我们增加复杂性时可读性如何受到损害。从这里我们将看到如何将我们的类分成更容易理解和管理的助手类。

对于这项工作,我将在 Deepnote 中编写代码,这是一个协作数据科学笔记本,使运行可重复的实验变得非常容易。我们将使用医疗成本数据集。我们将使用患者属性,如年龄、体重指数和子女数量来预测医疗费用。这些数据在数据库内容许可 (DbCL: Public Domain)下公开免费使用、修改和共享。

面向对象的簿记模型类型

首先,让我们导航到 Deepnote 并创建一个新项目(如果您还没有帐户,可以免费注册)。

让我们创建一个名为“helper_classes”的项目,并在该项目中创建一个名为“helper_classes_ds”的笔记本。另外,让我们将 insurance.csv 文件拖放到页面左侧的“文件”面板中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

我们将通过定义一个类来继续,该类包含机器学习工作流中的一些基本步骤。让我们从导入我们将使用的所有包开始:

作者创建的嵌入

让我们定义一个名为“MLworkflow”的类,它包含一个初始化字典的 init 方法,我们将使用字典来存储模型预测和性能。我们还将定义一个存储医疗成本数据的类属性:

class MLworkflow(object):
    def __init__(self):
        self._performance = {}
        self._predictions = {}
        self.data = pd.read_csv("insurance.csv")

接下来,我们将定义一个名为“eda”的方法来执行一些简单的可视化。如果您为变量直方图传递一个值“True ”,它将为指定的数字特征生成一个直方图。如果您为变量 scatter_plot 传递值“True ”,它将根据目标生成数字特征的散点图:

class MLworkflow(object):
    ...
    def eda(self, feature, target, histogram, scatter_plot):
        self.corr = self.data[feature].corr(self.data[target])
        if histogram:
            self.data[feature].hist()
            plt.show()
        if scatter_plot:
            plt.scatter(self.data[feature], self.data[target])
            plt.show()

接下来,我们将定义另一个名为“data_prep”的方法,它定义了我们的输入和输出。我们还将定义一个名为 transform 的参数,我们可以用它对数字列进行对数变换:

class MLworkflow(object):
    ...
   def data_prep(self, features, target, transform):
        for feature in features:
            if transform:
                self.data[feature] = np.log(self.data[feature])
        self.X = self.data[features]
        self.y = self.data[target]

我们还将定义一个拟合方法。它将为训练和测试拆分数据,其中 test_size 可以由“split”参数指定。我们还将提供适合线性回归或随机森林模型的选项。这显然可以扩展到任意数量的模型类型:

class MLworkflow(object):
    ...
    def fit(self, model_name, split):
        X_train, X_test, y_train, y_test = train_test_split(self.X, self.y, random_state=42, test_size=split)
        self.X_test = X_test
        self.y_test = y_test
        if model_name == 'lr':
            self.model = LinearRegression()
            self.model.fit(X_train, y_train)
        elif model_name == 'rf':
            self.model = RandomForestRegressor(random_state=42)
            self.model.fit(X_train, y_train)

然后,我们将定义一个 predict 方法,在我们的测试集上生成预测。我们将把结果存储在我们的预测字典中,字典键将是模型类型:

class MLworkflow(object):
    ...
    def predict(self, model_name):
        self._predictions[model_name] = self.model.predict(self.X_test)

最后计算每种模型类型的性能。我们将使用平均绝对误差作为我们的性能指标,并使用名为 validate 的方法将值存储在我们的性能字典中:

class MLworkflow(object):
    ...
    def validate(self, model_name):
        self._performance[model_name] = mean_absolute_error(self._predictions[model_name], self.y_test)

完整的类如下:

作者创建的嵌入

我们可以定义这个类的一个实例,并生成一些可视化效果:

作者创建的嵌入

然后,我们可以定义一个实例,并构建线性回归和随机森林模型。我们首先定义类的一个实例,并用我们希望使用的输入和输出调用数据准备方法:

model = MLworkflow()
features = ['bmi', 'age']
model.data_prep(features, 'charges', True)

然后,我们可以通过调用 fit 方法来构建一个线性回归模型,使用线性回归的 model_name 参数值“lr”和 20%的 test_size。然后,我们在模型实例上调用预测和验证方法:

model.fit('lr', 0.2)
model.predict('lr')
model.validate('lr')

我们可以对随机森林模型做同样的事情:

model.fit('rf', 0.2)
model.predict('rf')
model.validate('rf')

因此,我们的模型对象将有一个名为 _performance 的属性。我们可以通过模型对象访问它,并打印字典:

作者创建的嵌入

我们看到我们有一个字典,其中的关键字“lr”和“rf”的平均绝对误差值分别为 9232 和 9161。

簿记模型类型和用单个类分类分段的训练数据

虽然用来定义这个类的代码足够简单,但是随着复杂性的增加,阅读和解释起来会变得很困难。例如,如果除了能够监控 model_types 之外,我们还希望能够在数据中的不同类别上构建模型,那会怎么样呢?例如,如果我们希望只对女性患者训练线性回归模型,或者只对男性患者训练随机森林模型,该怎么办?让我们看看如何编写这个修改后的类。类似于在我们定义 init 方法之前,我们初始化必要的字典。我们将添加一个名为 models:

class MLworkflowExtended(object):
    def __init__(self):
        self._performance = {}
        self._predictions = {}
        self._models = {}
        self.data = pd.read_csv("insurance.csv")

eda 和数据准备方法基本保持不变:

class MLworkflowExtended(object):
    ...
    def eda(self, feature, target, histogram, scatter_plot):
        self.corr = self.data[feature].corr(self.data[target])
        if histogram:
            self.data[feature].hist()
            plt.show()
        if scatter_plot:
            plt.scatter(self.data[feature], self.data[target])
            plt.show()

    def data_prep(self, features, target, transform):
        self.target = target
        for feature in features:
            if transform:
                self.data[feature] = np.log(self.data[feature])

fit 方法包含相当多的变化。它现在接受变量 model_category 和 category_values 以及我们的随机森林算法的默认值。它还检查类别值是否在初始化的字典中。如果不是,就用一个空字典初始化它们。结果是字典的字典,其中最外面的键是分类值。分类键映射到的值是包含算法类型及其性能的字典。其结构如下:

_performance = {'category1':{'algorithm1':100, 'algorithm2':200}, 'category2':{'algorithm1':300, 'algorithm2':500}

我们还将过滤指定类别的数据。对应于此逻辑的代码如下:

 def fit(self, model_name, model_category, category_value, split, n_estimators=10, max_depth=10):
        self.split = split
        self.model_category = model_category
        self.category_value = category_value
        if category_value not in self._predictions:
            self._predictions[category_value]= {}
        if category_value not in self._performance:
            self._performance[category_value] = {}
        if category_value not in self._models:
            self._models[category_value] = {}

        self.data_cat = self.data[self.data[model_category] == category_value]

剩下的逻辑和我们之前的差不多。完整的功能如下:

 def fit(self, model_name, model_category, category_value, split, n_estimators=10, max_depth=10):
        self.split = split
        self.model_category = model_category
        self.category_value = category_value
        if category_value not in self._predictions:
            self._predictions[category_value]= {}
        if category_value not in self._performance:
            self._performance[category_value] = {}
        if category_value not in self._models:
            self._models[category_value] = {}

        self.data_cat = self.data[self.data[model_category] == category_value]

        self.X = self.data_cat[features]
        self.y = self.data_cat[self.target]

        X_train, X_test, y_train, y_test = train_test_split(self.X, self.y, random_state=42, test_size=split)
        self.X_test = X_test
        self.y_test = y_test

        if model_name == 'lr':
            self.model = LinearRegression()
            self.model.fit(X_train, y_train)
        elif model_name == 'rf':
            self.model = RandomForestRegressor(n_estimators=n_estimators, max_depth = max_depth, random_state=42)
            self.model.fit(X_train, y_train)
        self._models[category_value] = self.model 

注意,这个函数要复杂得多。

预测和验证方法是相似的。不同的是,我们现在也按类别存储预测和性能:

 def predict(self, model_name):
        self._predictions[self.category_value][model_name] = self._models[self.category_value].predict(self.X_test)

    def validate(self, model_name):
        self._performance[self.category_value][model_name] = mean_absolute_error(self._predictions[self.category_value][model_name], self.y_test)

完整的类如下:

作者创建的嵌入

然后,我们可以根据模型类型和类别进行不同的实验。例如,让我们在单独的女性和男性数据集上构建一些线性回归和随机森林模型:

作者创建的嵌入

我们可以对 region 类别做同样的事情。让我们对西南和西北进行实验:

作者创建的嵌入

虽然这样做很好,但是运行某些实验的代码变得难以阅读。例如,当拟合我们的随机森林时,第一次阅读我们代码的人可能不清楚传递给 fit 方法的所有值是什么意思:

model.fit('rf','region', 'northwest', 0.2, 100, 100)

随着我们增加类的功能,这可能会变得更加复杂。

簿记模型类型和带有助手类的分类分段训练数据

为了避免这种日益增加的复杂性,求助于基于 ML 工作流的每个部分定义的助手类通常是有帮助的。

我们可以从定义一个 EDA 助手类开始:

作者创建的嵌入

然后,我们可以使用 eda 类来访问特征工程类中的数据:

作者创建的嵌入

接下来,我们将定义我们的数据准备类。在数据准备类的 init 方法中,我们将初始化字典来存储模型、预测和性能。我们还将使用特征工程类将对数变换应用于 bmi 和年龄。最后,我们将修改后的数据和目标变量存储在数据准备属性中:

class DataPrep(object):
    def __init__(self):
        self._performance = {}
        self._predictions = {}
        self._models = {}
        feature_engineering = FeatureEngineering()
        feature_engineering.engineer(['bmi', 'age'], 'charges', True, False)
        self.data = feature_engineering.data
        self.target = feature_engineering.target

    def dataprep(self, model_name, model_category, category_value, split):
        self.split = split
        self.model_category = model_category
        self.category_value = category_value
        if category_value not in self._predictions:
            self._predictions[category_value]= {}
        if category_value not in self._performance:
            self._performance[category_value] = {}
        if category_value not in self._models:
            self._models[category_value] = {}

接下来,我们将在数据准备类中定义一个数据准备方法。我们将从定义训练/测试分割、模型类别和类别值的属性开始。然后,我们将检查类别值是否存在于我们的预测、性能和模型字典中。如果不是,我们将为新类别存储一个空字典:

class DataPrep(object):
    ...
    def dataprep(self, model_name, model_category, category_value, split):
        self.split = split
        self.model_category = model_category
        self.category_value = category_value
        if category_value not in self._predictions:
            self._predictions[category_value]= {}
        if category_value not in self._performance:
            self._performance[category_value] = {}
        if category_value not in self._models:
            self._models[category_value] = {}

然后,我们将筛选我们的类别,定义输入和输出,为训练和测试拆分数据,并将结果存储在数据准备属性中:

class DataPrep(object):
    ...
    def dataprep(self, model_name, model_category, category_value, split):
    ...
      self.data_cat = self.data[self.data[model_category] == category_value]

      self.X = self.data_cat[features]
      self.y = self.data_cat[self.target]

      X_train, X_test, y_train, y_test = train_test_split(self.X, self.y, random_state=42, test_size=split)
      self.X_test = X_test
      self.y_test = y_test
      self.X_train = X_train
      self.y_train = y_train 

完整的数据准备课程如下:

作者创建的嵌入

最后,我们定义一个模型训练类,它允许我们访问准备好的数据、训练我们的模型、生成预测和计算性能:

作者创建的嵌入

我们现在可以用我们的类层次结构进行一系列实验。例如,我们可以建立一个随机森林模型,只根据对应于女性患者的数据进行训练:

作者创建的嵌入

我们还可以建立一个线性回归模型,只根据女性患者的相关数据进行训练。此模型的性能将添加到现有的性能字典中:

作者创建的嵌入

我们可以对男性患者做同样的事情。以下是线性回归的结果:

作者创建的嵌入

对于随机森林:

作者创建的嵌入

我们看到,我们有一个几个实验及其相应的模型类型、类别级别和模型性能值的字典。

这篇文章中使用的代码可以在 GitHub 上找到。

结论

在这篇文章中,我们讨论了如何使用面向对象编程来简化数据科学工作流程的各个部分。首先,我们定义了一个 ML 工作流类,它支持简单的 ed a、数据准备、模型训练和验证。然后我们看到,当我们向类中添加功能时,类实例上的方法调用变得难以阅读。为了避免阅读和解释代码的问题,我们设计了一个由一系列助手类组成的类层次结构。每个助手类对应于 ML 工作流中的一个步骤。这使得理解方法变得容易,因为它们与高级任务相关,这有助于可读性和可维护性。我鼓励你在自己的 ML 项目中尝试一下。

掌握动态编程

原文:https://towardsdatascience.com/mastering-dynamic-programming-a627dbdf0229

理解基本原理,知道何时以及如何应用这种优化技术

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

除特别注明外,所有图片均为作者所有

A 你期待去 FAANG 这样的大科技公司面试吗?如果是这样,请准备好进行一轮或多轮技术编码评估。

事实上,越来越多的公司已经开始在数据科学家、机器学习工程师或软件工程师职位的面试过程中纳入技术编码评估。

这种评估通常测试候选人的数据库和 SQL 数据操作技能,或者数据结构和算法的问题解决和编程技能。后者通常包括动态编程,这是一种优化技术,可以产生时间或空间复杂度降低的高效代码。

让我们行动起来,开始学习动态编程。

内容

1。动态编程的特点2。动态规划题型3。实现动态编程的方法 自上而下的方法 自下而上的方法4。动态编程示例 示例 1:爬楼梯 示例 2:入室抢劫犯 示例 3:樱桃皮卡什么概要

1.动态规划的特征

我们许多人都面临着识别动态编程问题的困难。我们如何知道我们能否用动态编程解决一个问题?我们可以问自己以下问题。

  • 我们能把这个问题分解成更小的子问题吗?
  • 有没有重叠的子问题?
  • 如果是,我们能否最优地解决较小的子问题,然后用它们构造一个最优解来解决主问题?
  • 在解决问题的同时,当前步骤的决策是否影响后续步骤的结果和决策?

如果你对以上问题的回答是肯定的,那么你可以应用动态规划来解决给定的问题。根据维基百科

一个问题必须具备两个关键属性,才能应用动态规划: 最优子结构重叠子问题

不要混淆动态编程与分治或贪婪算法。

分治 也是通过组合子问题的最优解来解决一个问题。但《分治法》中的子问题是不重叠

一个 贪婪算法 将试图做出贪婪的选择来为每一步提供局部最优解。这可能无法保证最终的解决方案是最优的。贪婪算法永远不会回顾并重新考虑它的选择,而动态编程可能会根据对前面步骤的回顾来修改它的决定。

2.动态规划问题的类型

动态编程题是什么样子的?

让我们来看看一些最常见的动态编程问题。

第一种类型的动态规划问题,也是经常遇到的一种,是为一个给定的问题找到一个最优解。例子包括寻找最大利润、最小成本、最短路径或最长公共子序列。

第二类动态规划问题是寻找达到某种结果的可能性,到达某一点的可能性,或者在规定的条件下完成一项任务的可能性。

第三种类型通常被称为动态规划计数问题(或组合问题),如寻找执行或完成一项任务的方法的数量。

注意,我们讨论的所有三种类型的动态规划问题也可以以矩阵的形式作为二维问题出现。更复杂的问题可能涉及多个方面。

3.实现动态规划的方法

我们可以用自底向上或自顶向下的方法解决动态规划问题。不管哪种情况,我们都需要定义并得出问题的基本情况。

一般来说,自上而下的方法是从查看全局开始的。从高层开始,它逐渐深入到更小的子问题的解决方案,最后是构建解决方案的基础案例。

与自上而下的方法相反,自下而上的方法从小处着手,首先查看基础案例,然后在整体解决整个问题之前,一步一步地构建更大的子问题的解决方案。

想象以下情况:

自上而下 :“我想做一个好吃的,看起来很华丽的生日蛋糕。如何实现这一点?嗯……我可以用甘美的香蒂伊奶油、浆果果盘、鲜花和新鲜草莓来装饰蛋糕。如何准备它们?我需要用糖搅打浓奶油,切草莓,煮浆果果盘。但是蛋糕在哪里?哎呀,我需要准备蛋糕面糊,然后烘烤它。怎么会?用糖和鸡蛋搅打黄油,然后拌入面粉和牛奶。每种成分的用量是多少?我需要根据所需的比例来衡量它们”。

自下而上 :“我先从测量和准备蛋糕配料开始。所有的材料都准备好了,我把黄油和糖、鸡蛋搅拌在一起,然后加入面粉和牛奶。接下来,我把它放进烤箱,烤 30 分钟。烤好蛋糕并让它冷却后,我煮浆果果盘,切新鲜草莓,用糖搅打浓奶油。此后,我在蛋糕上涂上尚蒂伊奶油,在上面铺上浆果果盘,最后放上并摆好切好的草莓和可食用的花”。

你能注意到自下而上的方法是如何从基础开始有序地做事的吗?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

塞巴斯蒂安·科曼摄影Unsplash 上拍摄

自上而下的方法

自顶向下方法的实现使用带记忆的递归。

记忆化是一种存储计算结果的技术,这样当程序再次需要它们时,就可以直接检索和使用它们。这对于重叠的子问题特别有用,因为它有助于避免两次执行相同的计算,从而提高效率并节省计算时间。哈希表或数组通常用于记忆。

只要当前状态不是基本情况,这种方法将根据我们定义的递归关系进行递归函数调用。

自下而上的方法

为了实现自底向上的方法,我们需要从基础案例开始,以特定的顺序迭代问题的所有状态。

通过这样做,我们从小到大一步一步地构建解决方案,这样当前步骤的答案可以很容易地使用从先前步骤计算的可用子问题解决方案来计算。这个过程会一直持续下去,直到我们构建出完整的解决方案。

自下而上的方法也被称为列表法。由于这种方法会以特定的顺序遍历每一步并执行计算,因此很容易将结果列表成数组或列表,这样就可以通过相关索引方便地检索它们,以便在后续步骤中使用。

对于某些情况,可以避免制表以节省空间和内存。我们将在后面的示例 1 和示例 2 中看到这一点。

那么使用哪种方法呢?

通常使用这两种方法都可以。毕竟,动态编程不容易理解。你可以使用对你来说更自然的方法,也可以使用你更习惯的方法。

虽然有些人可能发现使用自顶向下的方法用递归关系和代码构建解决方案更容易,但自底向上的迭代过程有时会执行得更快,因为它没有递归的堆栈开销。大多数时候,这种差异是微不足道的。不用说,如果你很有技巧,能够编写两种代码,那么就选择时间和空间复杂度较低的一种。

4.动态编程示例

在本帖中,我们将详细讨论三个解决动态编程问题的例子。

示例 1:爬楼梯

先说一个简单易懂的例子,爬楼梯

在这个问题中,我们有一个楼梯,它会带我们走n步到达顶端。每次,我们只允许爬 1 步或 2 步。

中,我们可以用多少种不同的方式爬到山顶?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们从思考基本情况开始。如果我们要迈出第一步,有多少种方法可以做到?当然只有一条路,那就是爬 1 级台阶。

进入第二步怎么样?有两种方法可以做到这一点。我们可以两次爬 1 级台阶,也可以一次爬 2 级台阶。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

爬楼梯问题的基本情况

很好!到达第一步和到达第二步构成了这个问题的基本情况。那么,第三步,第四步,等等呢?

对于第三步,只有 3 种可能的方法可以到达那里。它们是:

  • 爬 1 级台阶 3 次,或
  • 先爬 1 步,再爬 2 步,或
  • 先爬两步,再爬一步

我们怎么得到这个数字?就是把第一步和第二步的结果相加(1 + 2 = 3)。这告诉我们,从基础案例出发,我们可以构建其他步骤的解决方案。假设climb(n)是返回到达步骤n的路径数的函数,那么

climb(n) = climb(n-1) + climb(n-2)

现在我们有了,这是这个爬楼梯问题的递推关系。当前步骤采用前两步的计算结果,并将它们相加。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

基本上,这就是我们如何将一个问题分解成子问题,解决子问题将构建主问题的解决方案。

你能看出这些子问题是重叠的吗?

climb(6) = climb(5) + climb(4)
climb(5) = climb(4) + climb(3)
climb(4) = climb(3) + climb(2)
climb(3) = climb(2) + climb(1)

为了得到climb(6)的结果,我们需要计算climb(5)climb(4)。为了得到climb(5)的结果,我们需要再次计算climb(4)

从下图我们可以清楚的看到,为了得到climb(6),我们要计算climb(4)两次,计算climb(3)三次。这将导致 O(2^n).的时间复杂度

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

重叠子问题— climb(6)无记忆

有了记忆化,我们可以摆脱多余的计算,只检索先前存储的结果。时间复杂度将降低到 O(n)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

climb(6)带记忆功能

好了,我们有了基本情况和递归关系,现在是时候使用自顶向下的方法编写实现了。

这里,为了记忆,我们使用 hashmap (Python 字典)来存储计算结果。如果n的结果存在,我们跳过计算并从 hashmap 中检索结果。

该方法的时间和空间复杂度均为 O(n)。这是因为我们实际上经历了每一步,并且对于每一步,我们都将计算结果存储在 hashmap 中。

爬楼梯的自上而下解决方案

下面是使用自下而上方法的解决方案。

通常,我们可以使用一个数组来列出每一步的计算值。然而,对于这个问题,由于当前状态只依赖于前两个状态,我们可以有效地只使用两个变量,case1case2,在迭代for循环时跟踪它们。

通过这样做,我们能够实现恒定的空间复杂度 O(1)。时间复杂度将与自顶向下的方法相同,O(n)。

爬楼梯的自下而上解决方案

例子 2:入室抢劫犯

这是动态编程问题的另一个经典例子,入室抢劫

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个问题中,我们得到了一个整数数组,代表一个强盗可以抢劫的每所房子的钱数。这些房子排列在一条街上。不过有一个限制,即强盗不能抢劫邻近的房子。例如,如果房屋的索引从 0 到 4,强盗可以抢劫房屋 0、2、4 或房屋 1、3。

强盗能从这些房子里抢劫的最大金额是多少?

让我们用一个示例输入*house = [10, 60, 80, 20, 8]* 来演示一下。

同样,我们从考虑基本情况开始。如果只有一套房子,house[0],我们怎么办?我们只会抢劫,从house[0]那里拿钱。这是因为没有其他选择可以考虑。

现在,如果我们有两栋房子,house[0]house[1],我们抢哪栋?我们不能抢劫两家,因为它们相邻。我们要么抢house[0]要么抢house[1]。好吧,当然,我们会抢劫装有更多钱的房子。为了找到包含更多钱的房子,我们应用max(house[0], house[1])

注意上面是如何构成这个问题的基础的?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

入室抢劫问题的基本情况

现在,如果有很多房子呢?那我们该怎么办?我们这么想吧。如果我们在house[n],我们有两个选择,即是否抢劫房子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

抢还是不抢这房子?

  • 假设我们在house[9]。如果我们决定抢劫house[9],那么我们将得到的是来自house[9]的钱加上我们之前得到的 2 栋房子的钱。
  • 如果我们决定不抢劫house[9],那么我们所拥有的仅仅是我们在house[8]之前从抢劫房屋中获得的金额。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

入室抢劫问题的递推关系

为了找出哪个选项能产生最大金额,我们对这两个选项应用了max()函数,这就形成了我们的递归关系:

rob_house(n) = max(rob_house(n-2) + house[n], rob_house(n-1))

有了基本情况和递归关系,我们现在可以使用自顶向下的方法来编写解决方案。该方法的时间和空间复杂度为 O(n)。

自上而下的入室抢劫解决方案

对于自底向上的方法,我们再次使用两个变量case1case2来跟踪前两个状态,而不是将数组列表。

还记得只有一栋房子和有两栋房子的基本情况吗?case1用第一个房子的值初始化,case2max(house[0], house[1])的结果初始化。

下图说明了我们有 3 个房子时的训练。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于n = 2所在的第三个房子,以及后续的迭代,我们可以通过应用max(case1 + house[n], case2)来计算result。因为我们正在回收这两个变量,所以在当前计算之后,case1将被替换为case2,并且当前计算的结果将被赋给case2

仅使用两个变量允许我们实现恒定的空间复杂度 O(1)。时间复杂度将与自顶向下的方法相同,O(n)。

自下而上解决入室抢劫

示例 3:樱桃皮卡

让我们来尝试一个更有挑战性的问题,樱桃皮卡

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个问题中,有一个n x n网格,其中每个单元格中的值可以是1(单元格中有一颗樱桃)0(单元格是空的),或者-1(有一根刺,一个挡住去路的障碍物)。

从单元格(0,0)开始,我们应该移动到位于(n-1, n-1)的目的地。如果一路上有樱桃,我们就把它们捡起来。如果我们从一个细胞中拿起樱桃,这个细胞就变成了一个空细胞,0。我们只允许向下或向右移动,如果有荆棘,我们将无法通过。

(n-1, n-1)到达目的地后,我们必须返回起点(0,0)。回程时,我们只允许向上或向左移动,捡起剩余的樱桃,如果有的话。

如果我们在(n-1, n-1)找不到到达目的地的方法,那么我们就收不到任何樱桃。

我们被要求归还我们能够收集的最大数量的樱桃。

首先,我们想到的是建模一个解决方案,如问题所述,在向前的行程中拾取樱桃,然后返回。

但是,让我们用从这里采用的方法来尝试一种新的方法。

也就是说,我们将只在向前的行程中遍历,而不是先向前再返回。为什么?这是因为在这个问题中,向前的行程和向后的行程实际上是一样的。换句话说,我们可以在只能向下移动或 right️的向前行程中,以及在只能向上移动或 left️.的返回行程中,遍历相同的路径这有意义吗?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

向前行程或返回行程可以穿过相同的路径

接下来,我们可以用两个人同时移动的想法简单地做一次向前的旅行,而不是做两次旅行。因此,在任何时间点上,这两个人都会移动相同的次数。

假设人员 1 在位置(r1,c1)并且人员 2 在位置(r2,c2)移动一定次数后,我们有r1 + c1等于r2 + c2。听起来很奇怪?看看下面的插图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这意味着我们可以在我们的问题中使用r1c1r2c2作为状态变量。但是我们真的需要这四个吗?对于长度为n的网格,它将产生 O(n⁴)状态。

我们可以改进并将状态变量减少到只有三个,即r1c1c2。但是r2怎么样?好吧,我们还需要r2,我们可以从等式中导出它的值。这个状态空间约简将把这个问题的时间和空间复杂度从 O(n⁴)降低到 O(n)。

r1 + c1 = r2 + c2Derive r2 as:
  r2 = r1 + c1 - c2Where
  r1 is the row index of *person1*
  c1 is the column index of *person1*
  r2 is the row index of *person2*
  c2 is the column index of *person2*

那么,这个问题中的三个状态变量是怎么算出来的呢?因为我们有两个人同时移动,所以每次移动有四种可能性,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用三个状态变量(r1、c1、c2)来表示每次移动的四种可能性

现在,在对概念有了清晰的理解和足够的信息后,让我们构建自顶向下的解决方案。

这一次,我们将使用来自[functools](https://docs.python.org/3/library/functools.html#module-functools)模块的 Python 的[lru_cache()](https://docs.python.org/3/library/functools.html#functools.lru_cache)装饰器,而不是使用 hashmap 进行记忆。

在我们的解决方案中,我们将创建一个get_cherry()递归函数,并使用一个名为cherries的变量来累加采摘的樱桃。

假设人员 1(r1,c1)位置人员 2(r2,c2)位置,并且两个位置都没有刺,我们首先将(r1,c1)cherries的值相加。因为没有刺,所以值为01

其次,为了避免重复计算,只有当人员 2 在不同的位置时,我们才会将(r2,c2)的值加到cherries上。

到目前为止,我们已经在人员 1人员 2 的当前位置积累了樱桃。我们也需要对后续位置进行同样的累积。

还记得我们之前说过的吗,每一步都有四种可能?为了确定哪条路径可以获得后续位置的最大樱桃数量,我们可以对四个选项应用max()函数。这就形成了这个问题的递归关系。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

樱桃采摘的递归关系

递归函数调用将继续,直到到达目标单元格。当这种情况发生时,函数将返回该单元格的值。因为这是基本情况,所以不会进行进一步的递归函数调用。

樱桃采摘自顶向下解决方案

哇。这是个完美的计划,不是吗?但也有例外。

(r1,c1)(r2,c2)有刺怎么办?如果人员 1人员 2 越界了呢?

显然,我们想尽可能避开那些地方。如果发生这种情况,函数将返回一个负无穷大,表示不值得采取特定的路径来寻找最佳答案。负无穷大本质上是最小值,所以max()函数会采用另一个值最大的选项。

最后,如果人员 1人员 2 无法到达目的地怎么办?嗯,这是可能的,它可能会发生由于荆棘阻碍。在这种情况下,没有樱桃可以收集,最后的答案将是0

对于这个问题,我将把它留给好奇的读者去思考自底向上的解决方案。谁知道呢,你也许能想出一个绝妙的解决方案。

下一步是什么?

无论如何,去练习解决动态编程问题。

迈出第一步。如果你不能破解解决方案,不要烦恼。查看一些论坛,在那里您通常可以找到有用的提示和指导。

最知名的可以练习的平台是 LeetCode 。其他还有 GeeksforGeekshackere earthHackerRank

我们练习得越多,学到的就越多。我们学得越多,就越容易!

摘要

☑️:在这篇文章中,我们讨论了动态编程的特征以及适用于动态编程的关键属性。

☑️:我们揭示了经常被问到的动态编程问题的典型类型。

☑️我们探讨了自顶向下和自底向上方法之间的差异,以及以何种方式实现它们。

☑️:我们通过 3 个例子学习了如何解决动态编程问题,其中包括详细的解释和解决方案。

参考

[1] LeetCode,爬楼梯入室抢劫樱桃皮卡

[2] GeeksforGeeks,动态编程

[3] Prateek Garg 动态编程介绍 1

[4]编程,动态编程

动态编程很有趣。有些问题可能是令人生畏和难以置信的,但是学习解决它们并掌握技巧可以打开一个可能性的彩虹!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Unsplash 上的不锈钢图片拍摄

***Before You Go...****Thank you for reading this post, and I hope you’ve enjoyed learning dynamic programming as much as I do. Please leave a comment if you’d like to see more articles of this nature in the future.**If you like my post, don’t forget to hit* [***Follow***](https://peggy1502.medium.com/) *and* [***Subscribe***](https://peggy1502.medium.com/subscribe) *to get notified via email when I publish.**Optionally, you may also* [*sign up*](https://peggy1502.medium.com/membership) *for a Medium membership to get full access to every story on Medium.*📑 *Visit this* [*GitHub repo*](https://github.com/peggy1502/Data-Science-Articles/blob/main/README.md) *for all codes and notebooks that I’ve shared in my post.*© 2022 All rights reserved.

准备好深入潜水了吗?跳上 掌握动态编程 II

https://peggy1502.medium.com/mastering-dynamic-programming-ii-73149d26b16d

有兴趣阅读我的其他数据科学文章吗?查看以下内容:

https://pub.towardsai.net/building-a-product-recommendation-engine-with-aws-sagemaker-321a0e7c7f7b

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值