清楚地解释:什么是偏差-方差权衡,过度拟合和欠拟合
对于数据科学领域的任何人来说,这些概念都是非常重要的!
用例子解释,它将值得你花时间,继续读下去:)
约翰·莫塞斯·鲍恩在 Unsplash 上拍摄的照片
在这篇文章中,我们将以一种即使新手也能毫不费力地掌握的方式来理解这个建模和统计世界中由来已久的重要敲门砖。
每当我们建立一个预测模型时,即使在所有的调整和处理之后,我们的预测通常也是不完美的:在预测值和实际值之间会有一些非零的差异。这个差值被称为预测误差。我们可以把预测误差分解成两个分量:可约和不可约误差。
让我们用现实生活中的类比来理解它。火车司机是否能够成功地将火车引导到期望的目的地,这取决于两个因素,他自己(可减少的错误,如果他是警觉的和意识到的,他将不会犯任何无意的错误)和外部因素(不可减少的错误,如果一只迷路的动物冒险在铁轨上,如果另一个来自相反方向的火车司机失去控制,如果一个自杀的人扑到他的火车前面,等等)。)
看看下面的等式来总结这一切:
现在,我们将了解这个等式的两个组成部分(偏差误差和方差误差),然后再讨论困扰每个模型的两者之间的权衡情况。
考虑一个人 X 先生,他是一名大一新生,是一名有抱负的数据科学家,目前正在与各公司面试,以获得他在这个行业的第一个突破。有一天,他去参加一个面试,回家后感到非常紧张和沮丧,因为面试并不顺利。他的母亲问了他所有的事情,他说面试官问了他太高级的问题,对他的每一点都进行了批判性的判断,没有任何削弱他信心的言辞。然后,他的母亲安慰他说:“今天不是你的好日子,明天会更好的”。
现在,从这个故事中我们可以看出,母亲并没有要求儿子比以前更积极地准备,以增加他成功的机会。原因是她对儿子总是有情感上的偏见,并且总是试图淡化他的失败。如果你试图预测她对一个来找她寻求安慰的人的反应,而不考虑这个人是不是她的儿子(直系亲属),你的预测就会有偏差。现在,以下几点将帮助您理解偏差如何影响统计模型以及可以依赖的技术定义:
- 偏差的真实例子 -我将在这里引用一个股票市场的例子,假设我建立了一个相当简单的模型,该模型仅基于 3-4 个预测因素来预测股票的股价。该模型预测,2019 年 2 月 3 日,每股股价将为 4 美元,但当天的实际价格为 3.4 美元。因此,总误差将是两个数字之间的差值,即 0.6 美元,偏差分量在这里起主要作用,因为我们只取了几个预测值。
偏差误差是预测数据点和实际数据点之间的差异,这是由于我们的模型过于简化造成的。
- 具有高偏差的模型过于简单,并且具有低数量的预测器。它缺少一些其他重要的预测因素,因此无法捕捉数据的潜在模式。它忽略了训练数据集中的特征与预期输出之间的关系。它很少关注训练数据,并且过于简化模型。这导致训练和测试数据中的高误差。
现在,让我们再一次回到这个故事。我们看到面试官问了一个大一新生的高难度问题,也就是说,他让看似简单的情况变得复杂,因为不必要的复杂问题让 x 先生目瞪口呆。让我们再来看两个事实-
- X 先生只准备了初级问题,因此他不能回答高级问题。
- 面试官只面试过需要 5-6 年工作经验的职位,因此,他从来没有面试过一个新人。
在这种情况下,面试官在自己的舒适区之外会感到不舒服。X 先生对他来说是一个看不见的输入,他不知道如何处理,所以他问了太复杂的问题。这是理解方差的最佳案例。当暴露在他的舒适区(训练数据)的情况下,面试官表现出色,但一个新的经验(测试数据)让他偏离了轨道。现在,您已经准备好理解方差的技术定义及其对模型的影响,如下所述:
- 任何具有大量预测值的模型最终都将是一个非常复杂的模型,它将为已经看到的训练数据提供非常准确的预测,但是这种复杂性使得将该模型推广到看不见的数据非常困难,即高方差模型。因此,这个模型在测试数据上表现很差。
模型的高方差误差意味着它对小波动高度敏感。这个模型在它的舒适区之外挣扎(训练数据)。方差也有助于我们理解数据的分布。
现在我们必须了解与偏差和方差相关的两个更重要的术语- 过拟合和欠拟合 。我将再次使用现实生活中的类比。对于这个例子,我已经参考了机器学习@Berkeley 的博客。
当机器学习算法试图预测事物时,有一个非常微妙的平衡动作。
一方面,我们希望我们的算法能够非常紧密地模拟训练数据,否则,我们会错过相关的特征和有趣的趋势。
然而,另一方面,我们不希望我们的模型过于接近,并冒着过度解读每个异常值和不规则性的风险。
福岛核电站灾难
福岛核电站灾难是过度适应的一个毁灭性例子。设计发电厂时,工程师们必须确定地震发生的频率。他们使用了一个众所周知的古腾堡-里克特定律,该定律给出了一种从非常弱的地震发生的频率预测非常强的地震的概率的方法。这很有用,因为弱地震——弱到你甚至感觉不到的地震——几乎随时都在发生,并由地质学家记录下来,所以工程师们有相当大的数据集要处理。也许这条定律最重要的结果是,地震的震级和发生概率的对数之间的关系是线性的 T21。
核电站的工程师使用过去 400 年的地震数据来训练回归模型。他们的预测大概是这样的:
菱形代表实际数据,而细线代表工程师的回归。注意他们的模型是如何非常紧密地拥抱数据点的。事实上,他们的模型在 7.3 级附近出现了一个拐点——显然不是线性的。
在机器学习的行话中,我们把这种过拟合 。顾名思义,过度拟合是指我们训练的预测模型过于紧密地“拥抱”训练数据。在这种情况下,工程师们知道这种关系应该是一条直线,但他们使用了一个比他们需要的更复杂的模型。
如果工程师使用了正确的线性模型,他们的结果应该是这样的:
注意这次没有扭结,所以右边的线没有那么陡。
这两款的区别?过度拟合的模型预测大约每 13,000 年发生一次至少 9 级的地震,而正确的模型预测大约每 300 年发生一次至少 9 级的地震。正因为如此,福岛核电站的建造只能抵御 8.6 级的地震。2011 年摧毁该工厂的地震震级为 9 级(约为 8.6 级地震的 2.5 倍)。
过拟合实际上有一个双重问题,叫做欠拟合。在我们减少过度拟合的尝试中,我们实际上可能开始走向另一个极端,我们的模型可能开始忽略数据集的重要特征。当我们选择一个不够复杂的模型来捕捉这些重要特征时,就会发生这种情况,例如在需要二次模型时使用线性模型。
现在,从这个例子中,我们必须记住的重要信息如下:
- 不合身
- 当一个模型由于参数数量少而不能恰当地捕捉训练数据的本质时,这种现象称为欠拟合。
- 高偏置误差、低方差误差
- 当我们只有很少的数据来建立一个精确的模型时,这种情况也会发生。
- 如果我们试图用非线性数据建立线性模型,也会出现这种情况。
- 示例-线性回归和逻辑回归模型可能会面临这种情况
- 过拟合
- 当使用如此多的预测器来构建模型时,它会捕捉噪声以及潜在的模式,然后它会试图将模型拟合得过于接近训练数据,从而留下非常小的概化范围。这种现象被称为过度拟合。
- 低偏置误差、高方差误差
- 这是一个简单现实的复杂表现
- 示例-决策树容易过度拟合
偏差-方差权衡
我们必须避免过度拟合,因为它给了我们训练数据中的噪声元素太多的预测能力。但是,在我们试图减少过度拟合时,我们也可能开始拟合不足,忽略训练数据中的重要特征。那么我们如何平衡这两者呢?
在机器学习领域,这个极其重要的问题被称为偏差-方差困境。拥有最先进的算法、最快的计算机和最新的 GPU 是完全可能的,但如果你的模型对训练数据过度拟合或拟合不足,无论你投入多少资金或技术,它的预测能力都将是可怕的。
偏差-方差困境这个名称来自统计学中的两个术语:偏差,对应于欠拟合,以及方差,对应于过拟合,如果你一直关注到这里,你一定已经完全理解了:)
解释困境
那么,为什么在偏差和方差之间会有一个权衡呢?为什么我们不能两全其美,拥有一个既有低偏差又有低方差的模型?事实证明,偏差和方差实际上是一个因素的副作用:我们模型的复杂性。
举例- 对于高偏置的情况,我们有一个非常简单的模型。在我们下面的例子中,使用了一个线性模型,这可能是最简单的模型。对于高方差的情况,我们使用的模型非常复杂(想想弯弯曲曲)。
来源:链接:高偏置-欠匹配模型
来源:链接:高方差-过拟合模型
这种权衡有点像原子中的电子和质子平衡,两者同等重要,它们的和谐对整个宇宙都很重要。类似地,偏差和方差是在模型建立期间要最小化的两种误差。但是,要同时最小化这两者是一个挑战,因为如下图所示:
- 任何低复杂度的模型——由于高偏差和低方差,容易出现欠拟合
- 任何高度复杂的模型(决策树)-由于低偏差和高方差,将易于过度拟合
我们能做的最好的事情就是试着在光谱中间的某个地方,紫色指针的位置。
来源:链接
最后,看看下面的图表,你会发现它在偏差-方差权衡的上下文中到处都在使用,我们已经详细介绍了所有概念,现在你可以轻松理解了。
来源:链接
感谢您的阅读:)我很乐意听到您的反馈/建议!
【参考:】【https://ml.berkeley.edu/blog/2017/07/13/tutorial-4/】
你可以看看我下面的其他帖子:
定义、目的、流行的算法和用例——都有解释
towardsdatascience.com](/clearly-explained-4-types-of-machine-learning-algorithms-71304380c59a) [## 清楚地解释:机器学习如何不同于统计建模
它们彼此非常不同,所有的数据科学家都必须理解为什么和如何!
towardsdatascience.com](/clearly-explained-how-machine-learning-differs-from-statistical-modeling-967f2c5a9cfd) [## 清楚地解释:机器学习与数据挖掘有何不同
定义、混淆、区别——都有解释
towardsdatascience.com](/clearly-explained-how-machine-learning-is-different-from-data-mining-4ee0e0c91bd4)
请关注这个空间,了解更多关于数据科学、机器学习和统计学的内容!
快乐学习:)
清楚地解释:什么,为什么和如何特征缩放-规范化和标准化
特征缩放的重要性以及如何应用它。我的机器学习模型会从规范化中受益吗?
为什么要正常化?
你可能会对这篇文章封面图片的选择感到惊讶,但这就是我们理解正常化的方式!当我们的数据具有各种不同测量尺度的特征时,这个强大的概念会帮助我们,因此当我们试图从这些数据中获得洞察力或试图根据这些数据拟合模型时,我们会陷入困境。
就像我们不能在一个共同的尺度上比较上图所示的不同水果一样,我们也不能有效地处理太多尺度的数据。
例如:见下图,观察工资、工作经验和级别。由于属性 Salary 的比例范围较大,因此在定型模型时,它可以优先于其他两个属性,而不管它在预测因变量时实际上是否具有更大的权重。
因此,在数据挖掘和模型开发(统计或机器学习)的数据预处理阶段,对所有变量进行标准化是一个很好的做法,如果它们处于不同的范围,那么将它们降低到一个相似的尺度**。**
并非每个数据集都需要归一化,您必须筛选它,并确保您的数据需要它,然后才继续将这一步骤纳入您的程序。此外,如果您不确定数据分布实际上是否是高斯/正态/钟形曲线,您应该应用归一化。归一化将有助于减少非高斯属性对模型的影响。
什么是正常化?
我们将在这里讨论两种情况:
1.您的数据不符合正态/高斯分布*(在有疑问的情况下也喜欢这个)*
在这种情况下,数据规范化是将一个或多个属性的范围重新调整为 0 到 1 的过程。这意味着每个属性的最大值是 1,最小值是 0。
这也称为最小-最大缩放。
最小-最大缩放公式—来源:维基百科
来源:维基百科
2.您的数据遵循高斯分布
在这种情况下,可以通过下面描述的公式进行归一化,其中μ是平均值,σ是样本/总体的标准偏差。
当我们使用下面给出的标准分数进行标准化时,它通常也被称为标准化或 Z 分数。
标准化公式/Z 分数—来源:维基百科
来源:维基百科
关于 Z-Score 的更多信息
z 分数告诉我们你的分数离平均值有多少标准差。
比如说—
- z 得分为 1.5,则意味着它比平均值高 1.5 个标准差*。*
- z 分数为-0.8 表示我们的值比平均值低 0.8 个标准偏差。**
如上所述,z 分数告诉我们分数在正态分布曲线上的位置。z 值为零表示该值正好是平均值/平均值,而+3 表示该值远高于平均值(可能是异常值)
如果你参考我关于正态分布的文章,你会很快明白 Z-score 是把我们的分布转换成均值为 0、标准差为 1 的标准正态分布。
Z 分数的解释
让我们快速了解如何根据 AUC(曲线下面积)来解释 Z 得分的值。
根据经验法则,在上面链接的关于正态分布的文章中有详细的讨论,在这篇文章的结尾也有陈述,陈述如下:
- 68%的数据位于+1SD 和-1SD 之间
- 99.5%的数据位于+2SD 和-2SD 之间
- 99.7%的数据位于+3SD 和-3SD 之间
现在,如果我们想查看一个定制的范围,并计算该细分涵盖了多少数据,那么 Z 分数就可以帮我们解决问题。让我们看看怎么做。
例如,我们想知道左侧负极端和-1SD 之间覆盖了多少百分比的数据(数据点出现的概率),我们必须参考下面链接的 Z 得分表:
现在,我们必须寻找值-1.00,我们可以从下面的快照中看到,状态 15.8%是我们问题的答案。
类似地,如果我们一直在寻找-1.25,我们将得到值 10.56%(Z 列中的-1.2 和跨列匹配 0.05 得到-1.25)**
来源:链接
常见的 Z 得分值及其来自 Z 得分表的结果,表明在负极端和 Z 得分点之间覆盖了多少,即 Z 得分点左侧的区域:
我们也可以使用这些值来计算自定义范围之间的值,例如:如果我们希望 AUC 在-3 和-2.5 Z 值之间,它将是(0.62–1.13)% = 0.49% ~ 0.5%。因此,当涉及到没有直接的 Z 得分值要解释的问题时,这非常方便。
现实生活解释示例
假设我们有一个样本的智商得分数据,我们已经使用 Z 得分进行了标准化。现在来看一些事情,如果一个人的智商 Z 值是 2,我们看到+2 对应于 Z 值表上的 97.72%,这意味着他/她的智商比 97.72%的人高,或者他/她的智商只比 2.28%的人低,这意味着你挑选的人真的很聪明!!
这可以适用于几乎每一个用例(体重,身高,工资,免疫水平,等等!)
如果规范化和标准化之间出现混淆
如果您有一个用例,在这个用例中,您不容易决定哪一个适合您的模型,那么您应该运行两次迭代,一次使用归一化(最小-最大缩放),另一次使用标准化(Z 分数),然后绘制曲线,或者使用箱线图可视化来比较哪种技术对您来说性能更好,或者更好,使您的模型适合这两个版本,并使用模型验证指标进行判断。
我们应该在使用机器学习算法的同时应用归一化吗?
与普遍认为 ML 算法不需要规范化相反,您应该首先仔细看看您的算法使用的技术,以做出有利于您正在开发的模型的合理决策。
如果您正在使用决策树,或者任何基于树的算法,那么您可以不进行归一化,因为树的基本概念是围绕着每次基于单个特征在节点上做出决策,因此不同特征的尺度差异不会影响基于树的算法。
然而,如果您使用线性回归、逻辑回归、神经网络、SVM、K-NN、K-Means 或任何其他基于距离的算法或基于梯度下降的算法,那么所有这些算法都对您的要素的比例范围敏感,应用归一化将提高这些 ML 算法的准确性。
这就是关于功能扩展的全部内容:)
快乐学习,快乐成长:)
我在这篇文章中提到的关于正态分布的文章:
这些概念有什么正常或不正常的地方?
towardsdatascience.com](/clearly-explained-normal-distributions-and-the-central-limit-theorem-8d7cc5a6052f)
请关注这个空间,了解更多关于机器学习、数据分析和统计的信息!*
气候变化统计分析的基础
量化全球变暖的环境和经济影响的基础
问问题:当讨论气候变化时,你从哪里开始呢?当海水上升时,你如何量化密西西比三角洲的土壤侵蚀?由于全球变暖和滥伐森林的综合影响,亚马逊雨林的生物多样性丧失怎么办?
答:从最底层开始,一步步往上爬。收集和探索气候数据。揭示趋势。预测未来的变化。地球上的居民越有见识,我们就越有可能采取相应的行动来拯救这个星球,一次拯救一度。
统计学家和数据科学家都使用基本的分析概念从他们的数据中做出可信的推断。本文将使用来自国家气候数据中心的 Berkely Earth 全球陆地和海洋温度数据,以及 Python 编程语言来探索将在未来文章中构建的基础元素。
在我们开始之前,在查看全球气候数据时,有一个重要的概念需要理解。通常情况下,全球温度是通过异常而不是绝对温度来确定的。由于一些地区的气象站可能很少,而另一些地区的气象站可能海拔不同,因此将温度与从更小的、更局部化的尺度上的参考值发展的基线进行比较,并且可以计算异常。这种异常将会解释气候的变化。要了解更多关于为什么使用异常的信息,请阅读这里。
船尾鉴于国家海洋和大气管理局船舶雷尼尔与调查发射旁边。 **鸣谢:**美国国家海洋与大气管理局雷尼尔船的人员。
为了快速方便地浏览数据,我将它缩减为两列:日期(年和月)和以摄氏度为单位的月异常。数据点范围从 1850 年 1 月到 2014 年 12 月,包含 Berkely 的地表气温异常(相对于 1951-1980 年的平均值)和 HadSST 数据集中的海洋温度数据。
首先,我们来看看四个变量:均值、标准差、分位数和方差。
- 平均值 —样本的平均值,通过将所有数据的总和除以数据点的总数来计算。
- 方差 —离差的度量,描述数据如何围绕其平均值聚集。方差的计算方法是将每个数据点与平均值的距离平方。
- 标准差——另一种离散度的度量,作为方差的平方根。68%的值落在均值的右侧或左侧一个标准差,95%落在均值的两个标准差,99.7%落在均值的三个标准差。
- 分位数—有序分布中的点,受最小值和最大值限制。数据按从小到大排序,样本的中间值为中值。中间的每个点都可以描述为一个百分位数,从 1 到 100。
与我们的最小和最大异常相比,方差表明数据非常紧密地聚集在平均值周围。我们可以预计 68%的异常会落在平均值左右 0.345 摄氏度之内。然而,平均值并不总是提供我们数据的最佳描述,因为它受到异常值的严重影响。例如,当输入数据时,气候科学家可能放错了小数点。她没有打 1.3,而是打了 13。如果大部分数据都聚集在-0.2 附近,那么 13 将使平均值更加向右倾斜。
Python 的 Seaborn 库提供了一些图表,使得研究分位数变得非常容易。
盒图(或盒须图)是一个五位数的插图。蓝框从左到右分别表示第 25 个百分点、中间值(第 50 个百分点)和第 75 个百分点。这可以用经验法则来描述,如上面标准偏差的定义中所述。每一端显示最小值和最大值,菱形表示异常值,或距离中值超过三个四分位数的点。
Seaborn 图书馆的另一个伟大的情节是小提琴情节。与箱线图类似,小提琴图显示数据点的分布,但作为潜在分布的核密度估计。
简单来说,核密度估计(KDE)可视化了数据集中变量的分布。在以后的文章中,我们将更深入地研究这个和其他概率函数。
当我们解释这两个图时,我们现在可以开始进一步考虑数据集的分布。正态或高斯分布以数据集的平均值为中心。
这是一个随机正态分布的例子。根据 Investopedia 的说法,“正态分布是一种关于平均值对称的概率分布,表明接近平均值的数据比远离平均值的数据出现得更频繁。”
直觉上,随机值在任一极端的概率远小于随机值接近平均值的概率。
Seaborn 的 distplot()函数通过绘制直方图、数据集中值在区间或条柱中出现的频率以及核密度估计来可视化数据的分布。
分布图和 kde 也有助于可视化偏度和峰度。
- 偏斜度 —偏斜度衡量值的对称性或对称性缺失。正偏斜度表示向右偏斜,或者较大值的集中度较高。负偏斜度表示向左偏斜,或者较小值更集中。完全对称的分布的偏斜度为零。
- 峰度 —峰度衡量分布的“峰值”程度。较高的峰值导致峰度值> 3,较低的峰值导致峰度值< 3,并且正态分布具有等于 3 的峰度。
对于上面的偏度图,蓝色表示负偏度,黄色表示正常,红色表示正偏度。
峰度有三种类型:
- 中层大气——遵循正态分布,用绿色表示
- Platykurtic —分布较短,异常值较少,用红色表示
- Leptokurtic —分布很长,有许多异常值,用蓝色表示
至于我们的气候数据:
当偏斜度为 0.461 时,我们可以有把握地假设存在具有更高值的更高浓度的异常。这表明气温更有可能高于以前的全球平均水平,导致地球变暖。
峰度为-0.191 时,我们可以假设异常值比正态分布少。更少的异常值意味着更少的极端值。
这些基本概念是理解数据的基础,可以应用于许多行业,而不仅仅是气候学。在本系列的其余部分,我们将使用这些数据和更多的数据来揭示我们数据的真相,以保持可信度,并告知我们自己和他人这个星球所面临的问题。
气候变化和世界上种植最广泛的主要作物
一种预测农业生产和调查农业保险损失率的精算方法
图片由卡斯滕在的 Unsplash 上拍摄
小麦是世界上种植最广泛的主要作物,占全球消耗热量的五分之一,而且需求还在继续增长。它存在于我们每天消费的许多食物中——从面包和谷类到酱油和罐装汤。
因为小麦是我们饮食中不可或缺的一部分,所以了解它的产量在不久的将来会受到怎样的影响是很重要的。所展示的实验提供了与小麦种植区相关的农业保险公司所遭受的损失和收益的差异,我们分析了损失原因*、*气候变化和小麦产量之间的关系,以便为农民、保险公司和消费者提供更多信息。
我们在调查什么?
在保险行业, 损失率 代表亏损与赚取的保费(收益)的比率。农作物保险政策倾向于在地区和州的水平上解决生产问题,也许是基于相似地理位置的农场产出相似数量的产量的假设。我们建议,在县一级的详细分析可以为农民和保险公司提供更好的收益和更具体的信息。为了评估在保险单开发中鼓励更大的粒度是否有益,我们问:
地理位置相近的县的损失率分布会有显著差异吗?
华盛顿州是美国小麦的主要产地。在这里,我们评估华盛顿各县是否表现出不同的损失率分布。为此,我们随机选择了地理位置相近的两个县——斯卡吉特和惠特曼。在这些县,我们将使用来自美国农业部农业报告生成器和快速统计门户的作物和财务数据。
它们的损失率分布非常不同!
结果相当令人惊讶。从上面的方框图中可以看出,惠特曼和斯卡吉特的分布差别很大。总的来说,斯卡吉特比惠特曼分布更广,方差更大。这些县有明显不同的四分位值、中位数和平均数;他们没有遵循类似的趋势。
为了分析上面的观察和假设是否有效,我们可以进行显著性检验,确定 Skagit 和 Whitman 数据集的显著差异。虽然数据不是正态分布的,但所使用的数据集包含足够的条目来有效地进行 t 检验。
在设置置信区间 α 等于 0.05 后,我们对各县进行双样本、双尾 t 检验。
寻找 t 统计量的过程,然后是 p 值
遵循传统的双样本 t 检验零假设(𝜇₁ = 𝜇₂)和替代假设(𝜇₁ ≠ 𝜇₂),我们看到斯卡吉特和惠特曼之间的 t 检验产生了 0.0434 的 p- 值,这意味着我们拒绝零假设,并确定他们的损失率值在统计上是不同的。
那么,这告诉我们什么呢?
结果和统计测试(在本例中为 t-测试)旨在检查各对相同州县的赔付率是否不同,这表明它们几乎没有一致性,并且它们证实了在县一级制定更精细的保险政策的必要性,因为更广泛的级别(例如,区域或全州)可能是不够的。
气候变化是如何发挥作用的?
气候以多种方式影响作物生长和产量。任何天气变化都可能威胁到农作物并给农民带来损失,因此探索这些问题中的哪一种影响最大,以及它们如何改变农民和保险公司基于货币的衡量标准非常重要。
在下面的联合图中,显示了 赔偿 (指定事件的作物损失总额)和 总保费 (农民向保险公司支付的承保作物的总额)之间的关系,很明显,这两个变量分布都是右偏的,这意味着在低端有大量聚集,在高端有较长的尾部。我们还看到,对于几个总保费值,赔偿看起来变化很小或没有变化,表明它们的比率(损失率)波动,有时剧烈,有时不剧烈。该图表明,从历史上看,总保费比赔款更有可能发生变化。
绘制关系赔偿和总保费
我们可以按县来分析损失的原因。
该嵌套饼图揭示了县级损失原因的相对构成。即使对华盛顿的更多县进行分析,我们也会发现它们对每一种损失原因的影响程度并不相同,这或许表明,在地区层面上对气候如何影响小麦产量所做的预测与在县一级所做的预测并不相似。
饼状图部门是不均衡的,表明不同县之间很少或没有可预测性
在下面的箱线图中,我们探索了与气候相关的损失原因是如何依赖于平均温度(F)的。中位数较高的损失原因表明与较高的平均温度有关。扩散越大,这种原因对温度的依赖性就越小。例如,热风和火灾表现出相对较小的传播范围,因此它们与温度高度相关,但干旱和过多的雨水传播范围较大,与温度的关系不大,这意味着可能有其他因素与它们相关。
显示温度和损耗原因之间的关系
现在,我们进行时间分析。
我们可以观察惠特曼的平均气温是如何随时间变化的。每年一次的温度分析有助于降低更精细层次的噪声。数据集中有一个明显的上升趋势,表明由于全球变暖和气候变化,温度可能正在上升。这条线正以每年 0.019 华氏度的速度增长。鉴于此,在下面的预测实验中,我们将温度作为影响小麦产量的一个关键因素,它直接影响保险公司设定的保险费率。
平均温度随着时间逐渐升高
在下面的多元回归中,生产图预测趋势是虚线,而地面真实数据是实线。平均温度是模型中六个系数之一;其他五个是县、年份、补贴金额、获得保险费的单位数量(进行保险索赔的单个地块)和获得赔偿的单位数量。该图显示了华盛顿各县在预测产量方面的差异,这进一步强调了州一级制定的政策不足以让保险公司获得最佳收益。
华盛顿各县小麦产量的预测趋势与实际趋势
结论
很明显,华盛顿县,以及其他州的县,通常需要不同的保险计划、建议和保险单。通过创建一种基于位置指定赔付率的方法,保险公司可以更好地根据不同的位置定制保单,并调整决策、保费定价率等。同样,农民可以利用这种预测来预测他们的作物产量,做出改进,并相应地提前计划。
以上实验的数据和代码在 这里 提供。
参考
A.库马尔,了解农业保险赔付率特征 (2020),精算基础
气候是你所期待的
气候数据科学
研究气候系统的规律
气候系统是其组成部分——大气圈、生物圈、冰冻圈、水圈和岩石圈——之间复杂相互作用的产物,由相当多的强迫机制驱动,如太阳辐射和温室气体浓度。尽管混沌是这个系统的固有特征,但在气候变量的行为中有几个规律和组织层次。
亚马逊的典型景观。 Rodrigo Kugnharski 在 Unsplash 上的照片
以一年中的季节为例。如果你住在北半球,你肯定会希望看到冬天下雪,夏天阳光明媚。在热带地区,气温波动不那么剧烈,夏季多雨寒冷,冬季干燥炎热。所有这些都可以用马克·吐温说过的一句话来概括:
气候是你所期待的,天气是你所得到的。
一个简单的短语,但足以让你深刻理解气候系统中的事情是如何运作的。你认为亚马逊雨林中部会有大雪吗?在这个世界上没有。描述气候的一种分析方法是表达其变量的平均条件,如降雨量和温度,至少在 30 年内。这些是著名的气候平均值,,它们告诉你许多关于你应该期待的气候的重要事情。
一年三月的降水
本教程着重于你可以采取的步骤,做你自己的任何感兴趣的特定地区的气候正常。为此,您将使用来自全球降水气候学中心 (GPCC)的网格降水数据集,该数据集由气候学研究中非常常见的全球数据集组成,因为其质量高且时间跨度长,从 1901 年 1 月到 2013 年 12 月。
首先,像往常一样,您需要导入包:
import xarray as xr
import proplot as plot
import matplotlib.pyplot as pltfrom esmtools.stats import*
你已经知道Matplotlib
是每一个 Python 程序员的可视化软件包的黄金标准,自从cmip 6快速介绍以来,你已经接触了用于 n 维网格数据的Xarray
和数据可视化的下一件大事Proplot
。新成员是Esmtools
,这是一个软件包,主要用于复杂气候模型的统计分析。最简单的安装方法是使用Pip
:
*pip install esmtools*
Xarray
的许多优秀功能之一是可以直接加载大量数据集,而无需通过 OPeNDAP 下载。由于 NOAA/PSD 目录中的所有数据都可以通过 OpenDAP 访问,因此您可以轻松获得本教程的 GPCC 产品。
*# OPEenDAP url
url = 'http://www.esrl.noaa.gov/psd/thredds/dodsC/Datasets/gpcc/full_v7/precip.mon.total.2.5x2.5.v7.nc'
# load dataset
dset = xr.open_dataset(url)
>>> dset*
GPCC 月降水网格数据集元数据。
这种降水在地图上看起来怎么样?
*fig, ax = plot.subplots(axwidth=4.5, tight=True,
proj='robin', proj_kw={'lon_0': 180},)
# format options
ax.format(land=False, coast=True, innerborders=True, borders=True,
labels=True, geogridlinewidth=0,)map1 = ax.contourf(dset['lon'], dset['lat'],
dset['precip'][0, :, :],
cmap='Dusk',
levels=plot.arange(0, 400, 50),
extend='both')ax.colorbar(map1, loc='b', shrink=0.5, extendrect=True)plt.show()*
GPCC 数据集的第一个全球降水场。
设置感兴趣的区域(和时间)
在本教程中,您将调查亚马逊流域的年降雨量。这一循环受南美季风系统(SAMS)的起伏影响,表现为两个截然不同的雨季和旱季。为了关注这个特定的区域,Xarray
允许使用灵活的.sel()
方法。你可以在盆地周围设置一个方框区域,选择 1981 年到 2010 年这一特定时期的纬度和经度,根据世界气象组织(WMO)的说法,这是最近的气候时期。
*amazon_area = dset.sel(lat=slice(5, -22), lon=slice(285, 315),
time=slice('1981-01-01', '2010-12-01'))*
对于您在路上找到的每一个数据集,您必须格外小心地知道维度在其中是如何描述的。例如,如果 GPCC 数据集将经度维度命名为longitude
而不是lon
,那么您必须将维度命名为sel
而不是lon
。所有这些小细节都与您的代码密切相关,所以如果您不熟悉特定的数据集,请务必检查元数据。
气候学研究中的一个常见做法是创建一个指数*,这是一个时间序列,用于描述特定地区特定现象或变量的行为。由于您已经选择了亚马逊盆地周围的区域,Esmtools
允许您通过对规则网格进行余弦加权,以最严格的方式从网格数据中创建一个索引。抛开气候学的措辞,用以下公式计算降水指数:*
*amazon_index = cos_weight(amazon_area['precip'])
>>> amazon_index.dims
('time',)*
亚马逊的年降雨周期是怎样的?
*fig, ax = plot.subplots(figsize=(5, 5), tight=True)
ax.plot(amazon_index['time'], amazon_index, color='Blue')# format options
ax.format(xlabel='Time', ylabel='Precipitation (mm/month)')plt.show()*
亚马逊地区降水周期的起伏。
虽然这很好地反映了年周期应该是什么样子,但它没有反映亚马逊地区降雨行为的一些重要细节。改善它的一个好方法是做一个简单的月图或者更精细的东西,比如箱线图。好消息是,Proplot
环绕着Matplotlib
,让你可以很容易地做出一个箱线图:
*# numpy trick: transform from a row vector to a matrix
amazon_index = np.reshape(np.array(amazon_index), (30, 12),
order='C')fig, ax = plot.subplots(figsize=(5, 5), tight=True, sharex=False)
# format options
ax.format(ylim=(0, 300))ax[0].boxplot(amazon_index, marker='x', fillcolor='azure',
labels= months)plt.show()*
一年一度的三月降雨,清晰可见。
箱线图可以让你看到亚马逊盆地降雨的真实情况。从 1 月到 5 月中旬,整个盆地有一个大范围的雨季,主要受 SAMS 活跃期和热带辐合带(ITCZ)向南迁移的驱动。然而,令大多数人惊讶的是,雨林经历了正常的降雨缺乏期,因为它有非常明显的旱季,从六月到九月中旬。在这个明显的旱季,火灾季节也开始了,随着时间的推移,农业边界向亚马逊河推进了一点。
最后的话
在大多数情况下,任何感兴趣的气候变量的年周期都是更详细和更深入研究的第一步。然而,这个有点简单的行为已经足以让你很好地了解温度、降水和其他因素的自然行为。在不断变化的气候中,了解特定地区甚至全球的气候如何波动变得格外重要。
一件好事是大量可靠的数据集可供快速下载,而且Xarray
甚至允许基于云的访问。其他优秀的软件包,如Proplot
和新的Esmtools
,促进了这些通常复杂的网格数据集的可视化和统计分析。通常,本教程的 Jupyter 笔记本可以在我的 气候数据科学 资源库中免费获得。
攀登信息阶梯
将您的数据转化为有用的信息
如何将数据转化为信息
许多组织拥有大量数据,却不知道如何处理这些数据。数据科学(泛指商业智能、数据可视化、机器学习等)是一个相对较新的行业,旨在找出如何从这些数据中提取价值。
这篇文章展示了从众多数据源中提取越来越多信息的渐进学习过程。
将这些数据转换成有用的信息是一个旅程。
信息阶梯显示了这个旅程,从简单开始,一直到复杂的机器学习。
数据和信息的区别
在工作场所,我经常听到“数据”和“信息”这两个词被错误地使用。这是两件截然不同的事情,经常会被混淆为同一件事情。
在一个许多组织都在努力变得更加数据驱动的世界里,理解其中的区别很重要。
数据是用来产生信息的原材料。
信息是数据的产物;没有数据,就不会有信息。
数据有多种形式和大小。有些数据经常变化,有些是静态的。
频繁变化的数据包括网站数据;有些网站每秒钟有很多点击量。货币汇率是另一个很好的例子,它们整天都在波动。
静态数据包括一个国家的首都或债券的期限。
数据示例包括:
- 单击网站上的数据
- 你的电子邮件的书面内容
- 一本书的静态属性
- 带有国家代码的国家列表
- 事件参与者的姓名
- 电子邮件收件人是否参与了电子邮件
- 投资账户的持有量
- 固定收益产品的静态属性
互联网的爆炸式增长导致了数据的大幅增长。我甚至不会试图量化有多少数据,(别人有),但网上发生的一切都是另一个数据点。每封电子邮件、网络点击、点赞、视频浏览、推文等都存储在某个地方。
从数据中获取价值是一项重大挑战。组织成为数据驱动型组织需要时间。理解数据需要时间,解决如何处理数据也需要时间。
因此,当开始让一个组织更加数据驱动的旅程时,管理期望可能会很困难。
信息阶梯代表了数据到有用信息的转换。
攀登信息阶梯是一个旅程。
在每一个阶段学到的经验教训能够帮助你进入下一个阶段。
每一级都导致对数据更深入的理解。在阶梯的底部,数据工作通常更简单。向上移动通常会导致更高级的数据工作和更大量的数据源。在高层,数据工作变得更加复杂。
从数据到信息的阶梯是什么?
静态报告
图片来自焦油溶液
这些通常是任何商业信息的起点。通常是表格形式的报告,经常有人获取数据,将其放入 Excel/Powerpoint 中并四处发送。通常,这将是一个或两个数据源的组合,保持在基本 Excel 的限制范围内。有时,数据分布在大的 Excel 文件中,查找信息可能需要接收者做一些工作,要么扫描整个表,要么构建数据透视表,等等。
临时报告
图片来自焦油解决方案
如上所述,只是这一次用户可以拖放有限的数据源来自己查找信息。或者,他们可能要求他们的“数据团队”在特定的基础上提供信息。这个一般是 Excel 的形式。可用的数据往往是有限的。这也很容易出错,因为并非所有用户都知道要应用的正确过滤器/数据的特点/等等,以便有效地使用。对用户来说,积极的一面是,只要数据可用,就能很快得到答案。
交互式仪表盘
图片来自焦油解决方案
一个设计良好的仪表板应该能够回答许多业务问题。仪表板应该为用户提供回答他们最初的标准问题的方法(例如,销售和预算如何?)并回答后续问题(例如,哪些产品最畅销?哪些销售人员表现好/差?).通常,这些都是自动化的和基于网络的,把信息放在那些需要它的人的指尖。
仪表板应该回答成熟的商业智能环境中的大多数标准业务问题。
警报
图片来自焦油溶液
如果有什么事情需要用户注意,用户会在这里得到主动通知。业务规则驱动警报。有些警报需要多个数据源,有些警报只需要一个数据源。它通知某人他们需要了解一些信息,并可能采取一些行动。示例包括:
- CRM 系统中的一些可疑数据需要清理
- 交易者违反了交易限额;交易者和他们的管理层需要知道
- 基金投资组合中的一个项目变动超过了正常水平,基金/投资组合经理应该知道
统计分析
图片来自焦油溶液
这就是数据复杂性开始增加的地方。为了使统计分析有意义,基础数据必须是好的。阶梯的前几级应该确保数据的高质量。如果人们正在使用来自以前的梯级的信息,任何现有的数据问题应该已经被纠正。
这是数据被用来尝试和更好地理解一些东西的地方。例如,在订阅业务中,哪些因素会导致订阅续订?对于 PPI 索赔,贷款的哪些特征会导致赔偿?在资产管理领域,是什么导致了资金外流?
预测
图片来自焦油解决方案
根据我们在统计分析中了解到的情况,什么是可能的总结果?例如,我们认为订阅续订率会是多少?有多少 PPI 索赔需要人工调查?在接下来的 12 个月里,AUM 可能会损失多少?
预测分析
图片来自焦油溶液
这就是我们试图在个人层面上预测未来会发生什么的地方。
预测分析在金融服务领域已经广泛使用了很长时间。例如:
- 您的信用评分用于计算贷款违约风险,然后用于决定 a)是否提供贷款和 b)提供贷款的价格(利率)
- 汽车保险——你过去的索赔历史、年龄、汽车类型等。分担你的保险费用;他们根据同类人群的事故历史分析事故发生的概率,然后根据索赔的可能性给你的保险定价
- 人寿保险——在为您的人寿保险定价之前,会收集有关您的年龄、健康和生活方式的数据;利用过去的数据,保险公司正在计算你在投保时死亡的概率。因此,一个 50 多岁的超重吸烟者将比一个 20 多岁的健康运动者付出更多
机器学习
图片来自焦油溶液
这很容易成为一个单独的话题。机器学习是人工智能的一个子集,这篇文章很好地解释了。它应该有助于更好地理解客户,从而使他们的体验更适合他们的需求。它可以进一步细分为:
- 情景建模——在假设情景下的预测
- 决策支持—扩展场景建模,目标是优化决策
- 机器人和推荐系统——使用机器学习来影响客户行为。一些领先的组织在这方面表现出色,比如亚马逊的推荐引擎。例如,这个顾客/客户想要什么?基于相似客户的行为,我们能影响决策过程吗?我们能赢得更多业务/维持他们的业务吗?
信息阶梯的现实
在现实世界中,梯级之间可能存在明显的交叉。此外,没有必要踩在梯子的每个梯级上。例如,许多数据项目从梯级 3“交互式”开始。梯级 4,“警报”,通常是“互动”的副产品。
同样,“预测分析”和“机器学习”也可以非常相似。
然而,真实的情况是,随着一个人在阶梯上的进步,对数据的理解和数据源的数量确实增加了。
不爬下面的梯级,从梯子的顶端开始根本不可能。
攀登信息阶梯
数据到信息项目通常从简单的数据集开始。所有的组织都是不同的,有些是数据驱动的,有些则不然。
然而,一旦这些项目开始获得牵引力,并开始证明其价值,它们可以导致重大的文化变革。
对数据的兴趣往往会在各种规模的组织中迅速蔓延。
扩展到更多数据并以不同方式使用现有数据是一个自然的过程。
仅仅回答一个问题很可能会引出更多的问题。
例如:
- 哪些交易者最赚钱?
- 他们和谁交易?
- 他们在交易哪些产品?
- 是否有外部事件导致这些交易——例如,通货膨胀率上升?
- 我们能够预测哪些事件会增加某些产品的活动吗?
- 哪些客户会从这些产品中受益?
- 等等
一旦一个组织转向更加数据驱动的道路,业务问题就会变得没完没了。
在 Postgres 中攀爬 B 树索引
理解并应用 Postgres 中的定位索引进行排序和匹配
您的查询很慢,所以您决定通过添加索引来加快速度。它是什么类型的指数?可能是 B 树。
Postgres 有几种索引类型,但是 B 树是最常见的。它们有利于分类和匹配;一旦你理解了它们在引擎盖下的样子,原因就显而易见了。
我们将深入探究 Postgres 中 B 树实现的内部机制,然后做一些 SQL 示例来展示它们的效果。我已经提供了查询,所以你可以自己运行相同的实验。
这篇文章假设你已经对索引是什么和做什么有了一个大致的概念。如果不是,通常提供的抽象是教科书中的术语表。你可以在词汇表中按字母顺序查找,然后跳到它所在的页面,而不是阅读书中的每一行来找到一个单词/主题。我们很快就会看到现实稍微复杂一些。
什么是 B 树?
b 树代表平衡树。
它不是一个二叉树,它允许每个父节点最多 2 个子节点,并且是为内存搜索而设计的。
来自维基百科,
在计算机科学中, B 树是一种自平衡树数据结构,它维护分类数据并允许在对数时间内进行搜索、顺序访问、插入和删除。B 树概括了二叉查找树,允许节点有两个以上的子节点。【2】与其他自平衡二分搜索法树不同,B 树非常适合读写相对较大数据块的存储系统,比如磁盘。常用于数据库和文件系统。
这是一个非常好的非技术性介绍。要获得真正的技术解释,请参见由 Lehman 和 Yao 撰写的论文,Postgres 的实现就是基于该论文。
B 树的结构
b 树是“平衡的”,因为从根到任何叶节点的距离都是相同的。叶节点是没有子节点的节点。根节点是位于顶部的节点。
一个节点有键。在我们下面的根节点中,[10,15,20]是键。键映射到数据库中的值,但也映射到子节点中的绑定键。
第一个子节点[2,9]的值小于 10,因此指针位于 10 的左侧。
第二个子节点[12]的值介于 10 和 15 之间,因此指针从那里开始。
第三个子节点[22]大于 20,因此指针位于 20 的右侧。
现在,如果我们想查找键 12,我们将 12 与根节点中的值进行比较,看到它在 10 和 15 之间。所以我们使用 10 和 15 之间的指针来查找包含 12 的节点。
这是对 Postgres 实现的抽象,但是你可以想象为什么这比遍历表中的每个数字并检查它是否等于 12 要快。
这就是为什么 B 树可以在 O(logN)时间内进行搜索、插入和删除。
B 树每个节点也有最小和最大数量的键。在插入和删除时连接和拆分节点,以保持节点在范围内。
数字、文本和日期的 b 树
我们将通过三个例子来研究 B 树的巨大影响力:数字、文本和日期。
设置
为每种数据类型创建一个表。每个表有两列,但我们只索引其中一列。
create table numbers(
val integer,
val_indexed integer
);
CREATE INDEX numbers_indexed ON numbers using btree (val_indexed); create table strings(
val varchar,
val_indexed varchar
);
CREATE INDEX strings_indexed ON strings using btree (val_indexed); create table dates(
val varchar,
val_indexed varchar
);
CREATE INDEX dates_indexed ON dates using btree (val_indexed);
数字
我们来生成 1 到~1M 之间的 1M 个随机整数。
**insert** **into** numbers
**select** (**random**()*1000000 +1)::**int**,
(**random**()*1000000 +1)::**int
from** (**SELECT** * **FROM** **generate_series**(1,1000000)) ser;
我们将重复运行下面的查询,记录查询时间,然后再添加额外的 100 万条记录。
**select** * **from** numbers **order** **by** val **desc**;
**select** * **from** numbers **order** **by** val_indexed **desc**;
我们看到的是,在对索引列和非索引列进行排序时,查询时间存在巨大差异。
500 万条记录对于 Postgres 来说微不足道,但我们已经可以看到效率上的巨大差异。
为什么不同?让我们使用 Postgres 中的explain
函数来深入研究一下。
explain select * from numbers order by val desc;
explain select * from numbers order by val_indexed desc;
无索引的
编入索引的
我们看到非索引搜索使用顺序扫描,而索引搜索使用索引扫描。给定上面讨论的 B 树结构,很容易理解为什么搜索索引会比搜索表快得多。
文本
让我们用字符串做同样的比较。不过这次我们将一次增加 100k 行。
**insert** **into** strings
**SELECT
md5**(**random**()::**text**),
**md5**(**random**()::**text**)
**from** (
**SELECT** * **FROM** **generate_series**(1,100000) **AS** id
) **AS** ser;
现在对这些行进行排序。
**select** * **from** strings **order** **by** val **desc**;
**select** * **from** strings **order** **by** val_indexed **desc**;
我们看到了什么?与索引搜索击败非索引搜索的模式完全相同。
偷看explain
,我们看到的是同样的原因。
explain select * from strings order by val desc;
explain select * from strings order by val_indexed desc;
日期
让我们用日期再做一次。同样,我们一次只生成 100k 行。
with cte as (
select
timestamp '1900-01-10 20:00:00' +
random() * (
timestamp '2000-01-20 20:00:00'-timestamp '1900-01-10 10:00:00'
) rdate
from (SELECT * FROM generate_series(1,100000) AS id) ser
)
insert into dates
select
rdate,
rdate
from cte;
查询日期并记录查询时间。
select * from dates order by val desc;
select * from dates order by val_indexed desc;
比较一下区别。
我们已经可以猜到explain
将会展示什么,但还是让我们来看看吧。
explain select * from dates order by val desc;
explain select * from dates order by val_indexed desc;
同样,通过索引扫描对行进行排序比顺序扫描快几个数量级。
结论
我的目的是让您对索引的结构有一个大致的了解,这样您就可以直观地理解为什么它们会缩短查询时间。
然后我们讨论了一些例子,在这些例子中,添加一个索引会产生昼夜差异。
我们只讨论了对行进行排序,但是调查查询时间并查看其他类型的查询(如where
或exists
)会很有趣。
Python 中的闭包和装饰器
你将最终理解什么是闭包和装饰器
闭包是函数式编程中的一个重要工具,一些重要的概念如curring和 partial application 可以使用它们来实现。装饰器也是 Python 中一个强大的工具,它使用闭包来实现,允许程序员修改函数的行为,而不用永久地修改它。在本文中,我将首先解释闭包和它们的一些应用程序,然后介绍装饰器。
变量范围
为了更好地理解闭包,我们首先需要了解 Python 中变量的作用域。变量的作用域是指可以看到或访问变量的区域。变量不能在其作用域之外被访问。变量的范围由它在源代码中的赋值位置决定。通常,变量可以在三个不同的地方赋值,对应三个不同的作用域:
全局作用域:当一个变量在所有函数之外被定义时。文件中的所有函数都可以访问全局变量。
局部作用域:当一个变量被定义在一个函数内部时,它是这个函数的局部变量。局部变量只能在定义它的函数内部访问。
非局部作用域:当一个变量在封闭函数中赋值时,它对其嵌套函数来说是非局部的。定义非局部变量的函数及其所有嵌套函数都可以访问非局部变量。
如果你在一个函数内部重新分配一个全局变量,一个新的同名局部变量将被创建,这个全局变量被称为被这个局部变量遮蔽。在函数内部对这个局部变量的任何改变都不会影响全局变量。所以如果你想在函数中改变一个全局值,你必须在 Python 中使用global
关键字。
例如在清单 1 中,最初,我们定义了两个全局变量x
和y
。当我们在函数f()
内部重新赋值x
时,定义了一个新的同名局部变量,它并不影响全局变量x
。然而,通过使用global
关键字,我们可以访问f()
中的全局变量y
。z
是f()
的局部变量,不能在外部访问。退出f()
后,该变量不在内存中,因此不能再访问。
# Listing 1x = 1 # x is a global variable
y = 5 # y is a global variable
def f():
global y
x = 2 # x is a local variable
y += 1 # Reassigning the global variable y
z = 10 # z is a local variable
print("Local variable x =", x)
print("Global variable y =", y)
print("Local variable z =", z)
f()
print("Global variable x =", x)
print("Global variable y =", y)
输出:
Local variable x = 2
Global variable y = 6
Local variable z = 10
Global variable x = 1
Global variable y = 6
在 Python 中,一切都是对象,变量是对这些对象的引用。当你将一个变量传递给一个函数时,Python 会将一个引用的副本传递给该变量所引用的对象。它不发送对象或对函数的原始引用。因此,函数作为参数接收的原始引用和复制引用都引用同一个对象。现在,如果我们传递一个不可变的全局对象(比如一个整数或一个字符串),函数就不能使用它的参数来修改它。然而,如果对象是可变的(比如一个列表),函数可以修改它。这里有一个例子:
Listing 2a = [1, 2, 3]
b = 5
def func(x, y):
x.append(4)
y = y + 1func(a, b)
print("a=", a) # Output is a=[1, 2, 3, 4]
print("b=", b) # Output is b=5
我们将两个变量a
和b
传递给func
。a
是对可变对象列表的引用,而b
是对不可变整数的引用。func
接收作为x
的a
的副本和作为y
的b
的副本。向x
添加新元素将改变a
引用的原始对象。但是在func
里面给y
加 1 并不影响b
所引用的对象。它只创建了一个包含 6 的新整数对象,现在y
将引用它(图 1)。
图 1
内部函数
内部函数(或嵌套函数)是定义在另一个函数(外部函数)内部的函数。外部函数的局部变量对其内部函数来说是非局部的。内部函数可以访问非局部变量,但不能改变它们。重新分配它们只是在内部函数中创建一个同名的新局部变量,而不会影响非局部变量。所以如果你想在一个嵌套函数中改变一个非局部变量,你必须使用nonlocal
关键字。
在本文中,我们可以简单地称外部函数的变量为非局部变量,但这意味着它相对于内部函数是非局部的。如果删除内部函数,它将只是外部函数的一个局部变量。此外,内部函数应该以某种方式访问这个变量或者声明它为nonlocal
来调用它作为一个非局部变量。如果内部函数不接触外部函数的局部变量,它就不是内部函数的非局部变量。
在本文中,我们从不试图改变函数参数。因此,只要知道外部函数参数的行为类似于非局部变量,并且也可以被内部函数访问(或读取)就足够了。
在清单 3 中,函数参数x
,变量y
、z
和t
是f()
的局部变量。
# Listing 3def f(x):
y = 5
z = 10
t = 10
def g():
nonlocal y
y += 1
z = 20
print("Nonlocal variable x =", x)
print("Local variable z =", z)
print("Local variable t =", t)
g()
print("Nonlocal variable x =", x)
print("Nonlocal variable y =", y)
print("Local variable z =", z)
f(5)
# This does not work:
# g()
输出:
Local variable t = 10
Nonlocal variable x = 5
Local variable z = 20
Nonlocal variable x = 5
Nonlocal variable y = 6
Local variable z = 10
x
,和y
也是其内部函数g()
的非局部变量。它们在g()
内部被访问。当你重新赋值z
时,在g()
内部会创建一个新的同名局部变量,它不会影响f()
中的局部变量z
,所以z
不是一个非局部变量(到g
)。然而,通过使用关键字nonlocal
你可以改变y
,它仍然是一个非局部变量。变量t
根本不被g
访问,所以它不是一个非局部变量。
变量y
和z
属于f()
的局部范围,不能在f()
之外访问。内部函数g()
也被定义在f()
内部,只能在那里被调用。事实上,如果您试图运行清单 3 并在f()
之外调用g()
,Python 会给出一个错误。
但是如果我们能找到一种方法在g()
之外调用它呢?那么我们会有第二个问题。退出外部函数后,其局部变量(不属于g()
的局部变量)不再存在于内存中。在这种情况下,内部函数不能再访问它们。闭包使得在外部函数之外调用内部函数并访问其非局部变量成为可能。
关闭
首先,我们需要找到一种在外部函数之外调用内部函数的方法。记住函数总是可以返回值的。在清单 4 中,x
和y
是f()
的局部变量:
# Listing 4def f():
x = 5
y = 10
return x
h=f()
所以在运行h=f()
之后,f()
的所有局部变量都消失了,你再也不能访问x
和y
了。但是我们仍然有x
的值,它被返回并存储在h
中。所以也许我们可以让外层函数返回内层函数。这在 Python 中是可能的,因为 Python 函数是第一类。这意味着 Python 将函数视为值,因此您可以将函数赋给变量,将其作为函数参数传递或由另一个函数返回。在清单 5 中,外部函数f(x)
返回其内部函数g
。
# Listing 5def f(x):
def g(y):
return y
return g
a = 5
b = 1
h=f(a)
h(b) # Output is 1
现在f(x)
返回函数g
。所以当我们写h=f(a)
时,我们将g
赋给h
,现在h
可以像g
一样被对待,并接受g
的参数。结果h(b)
就像调用g(b)
。
函数的__name__
属性存储定义函数的名称。如果我们写:
h.__name__
它回来了
'g'
也就是说h
现在指的是函数g
。
你应该注意到f(x)
返回的是函数g
而不是一个特定的值。比如说。如果我们写下如下内容:
# Listing 6def f(x):
def g(y):
return y
return g(y)
a = 5
b = 1
h=f(a)
# This does not work:
# h(b)
然后我们得到一个错误。原因是我们是由f(x)
(也就是 1)返回g(y)
的值,所以h=1
,不能用它来调用函数。
我们不需要将f(a)
存储在h
中。而是可以直接调用f(a)(b)
。
# Listing 7def f(x):
def g(y):
return y
return g
a = 5
b = 1
f(a)(b) # Output is 1
需要注意的是,要区分f(a, b)
和f(a)(b)
。函数f(a, b)
有两个参数。然而,f(a)(b)
是一系列嵌套函数,每个函数都有一个参数。只有一个参数的函数叫做一元函数。所以f(a)(b)
是两个嵌套的一元函数的序列。如图 2 所示,Python 从左到右计算这些嵌套函数。所以首先对f(a)
进行评估。为了能够链接这些函数,f(a)
应该返回另一个函数。这里它返回内部函数g
。所以f(a)(b)=g(b)
图 2
我们可以很容易地扩展这个方法,得到一个更长的一元函数序列。清单 8 展示了我们如何拥有三个嵌套的一元函数。第一个函数f(x)
具有内部函数g(y)
并且 g(y)
具有内部函数h(z)
。每个外部函数返回其内部函数。
# Listing 8def f(x):
def g(y):
def h(z):
return z
return h
return g
a = 5
b = 2
c = 1
f(a)(b)(c) # Output is 1
但是如果外层函数有一些非局部变量会怎么样呢?这里有一个例子:
# Listing 9def f(x):
z = 2
def g(y):
return z*x + y
return g
a = 5
b = 1
h = f(a)
h(b) # Output is 11
如果我们运行它,我们可以看到它工作正常,并且g(y)
可以访问变量x
和z
。但这怎么可能呢?运行f(x)
后,我们不再在f(x)
的范围内,变量x
和z
应该是不可访问的。为什么g(y)
仍然可以访问它们?那是因为内部函数g(y)
现在是一个闭包。
闭包是具有扩展范围的内部函数,它包含外部函数的非局部变量。所以它记住了封闭范围中的非局部变量,即使它们不在内存中。像y
这样在内部函数g(y)
局部范围内的变量称为绑定变量。只有有界变量的函数称为闭项。另一方面,像z
这样的非局部变量被称为自由变量,因为它可以在g(y)
之外自由定义,而包含自由变量的函数被称为开项。
图 3
“闭包”这个名字来源于这样一个事实,即它捕获其自由(非局部)变量的绑定,并且是关闭一个开放项的结果。
所以闭包是一个开放项,它通过捕获自由(非局部)变量的绑定而被封闭。在清单 9 中,只要内部函数g(y)
有一个尚未绑定的自由变量(x
和z
),它就不是闭包。一旦我们对h=f(a)
求值,封闭函数f(x)
也被求值,自由变量x
和z
分别被绑定到 5 和 2。所以由f(a)
返回的g(y)
变成了一个闭包,并且h
现在引用了一个闭包(图 4)。
图 4
Python 可以跟踪每个函数的自由变量。我们可以简单地使用__code__.co_freevars
属性来查看内部函数捕获了哪些自由变量。例如,对于清单 9 中定义的闭包,我们可以写:
h.__code__.co_freevars
输出将是:
('x', 'z')
您还可以使用closure
属性获得这些自由变量的值:
print(h.__code__.co_freevars[0], "=",
h.__closure__[0].cell_contents)
print(h.__code__.co_freevars[1], "=",
h.__closure__[1].cell_contents)
输出将是
x = 5
z = 2
重要的是要注意,为了有一个闭包,内部函数应该访问外部函数的非局部变量。当内部函数中没有自由变量被访问时,它不会捕获它们,因为它已经是一个封闭项,不需要被封闭。例如,如果我们将清单 9 中的内部函数改为:
# Listing 10def f(x):
z = 2
def g(y):
return y
return g
a = 5
b = 1
h = f(a)
h(b) # Output is 1
那就不能再认为这是一个终结了。原因是非局部变量x
和z
在g(y)
内部没有被访问,不需要g(y)
去捕获它们。我们可以很容易地通过写下:
h.__code__.co_freevars
输出将是:
()
此外,h.__closure
返回None
,这意味着h
不再是闭包。如果您没有访问一个非局部变量,而是在内部函数中将它定义为nonlocal
,它仍然会被闭包捕获。所以在清单 11 中,g(y)
是一个闭包,因为它捕获了t
。
# Listing 11def f(x):
z = 2
t = 3
def g(y):
nonlocal t
return y
return g
a = 5
b = 1
h = f(a)
h(b)
h.__code__.co_freevars # Output is ('t',)
另一个例子有点复杂:
# Listing 12def f(x):
def g(y = x):
return y
return g
a = 5
b = 1
h = f(a)
h() # Output is 5
这里g(y)
也不是一个闭包,因为x
的值只是用来初始化y
,而g
不需要捕获x
。
当您有多个嵌套函数时,每个闭包都能够捕获更高层的所有非局部变量。例如,如果我们有三个嵌套函数:
# Listing 13def f(x):
def g(y):
def h(z):
return x * y * z
return h
return g
a = 5
b = 2
c = 1
f(a)(b)(c) # Output is 10
然后h(z)
正在访问f
和g
的非局部变量,所以它将捕获这两个变量,所以
# f(a)(b) refers to h
f(a)(b).__code__.co_freevars
将会是:
('x', 'y')
此外,g(y)
也是一个闭包,它捕获x
作为一个非局部变量。我们可以很容易的检查出来(记住f(a)
指的是g(y)
):
f(a).__code__.co_freevars # Output is ('x',)
但是g(y)
不访问x
。为什么g(y)
要变成关闭并捕获x
?原因是它的内在功能是访问它。h(z)
需要捕获x
,但是一个闭包只能捕获其外部函数的自由变量,在本例中是g(y)
。所以首先g(y)
应该扩展其范围并捕获x
,然后h(z)
可以扩展其范围并捕获x
作为g(y)
的自由变量。如果我们将清单 13 替换为:
# Listing 14def f(x):
def g(y):
def h(z):
return y * z
return h
return g
a = 5
b = 2
c = 1
f(a).__code__.co_freevars # Output is ()
我们可以看到g(y)
不再是一个闭包。那是因为h(z)
不需要捕捉x
。结果,g(y)
也没有捕捉到,没有成为闭包。
您可能已经注意到,为了调用闭包,我们不使用内部函数的名称。我们只使用外部函数的名称。所以如果我们只有一个表达式,我们也可以用 lambda 函数代替内部函数。例如,我们可以用一个匿名函数替换清单 10 中的内部函数g
:
# Listing 15def f(x):
z = 2
return lambda y: z*x+y
a = 5
b = 1
f(a)(b) # Output is 11
至此,我想总结一下到目前为止我们学到的关于闭包的知识。为了定义闭包,我们需要一个内部函数:
1-它应该由外部函数返回。
2-它应该捕获外部函数的一些非局部变量。这可以通过访问这些变量,或者将它们定义为非局部变量,或者使用需要捕获它们的嵌套闭包来实现。
在定义闭包之后,为了初始化它,您必须调用外部函数来返回闭包。
在函数式编程中,闭包可以将数据绑定到函数,而不需要将它们作为参数传递。这类似于面向对象编程中的类。在清单 16 中,我们比较了这些范例。我们首先创建一个类来计算一个数的 n 次方根。
# Listing 16class NthRoot:
def __init__(self, n=2):
self.n = n
def set_root(n):
self.n = n
def calc(self, x):
return x ** (1/self.n)
thirdRoot = NthRoot(3)
print(thirdRoot.calc(27)) # Output is 3def nth_root(n=2):
def calc(x):
return x ** (1/n)
return calcthird_root = nth_root(3)
print(third_root(27)) # Output is 3
如你所见,外部函数在这里可以为我们扮演一个构造函数的角色。它初始化内部函数将使用的非局部变量。然而,也有一些不同之处。NthRoot
类可以有更多可以被对象thirdRoot
调用的方法。然而,nth_root
返回的是一个函数本身。所以这个方法比类能做的更有限。既然我们已经熟悉了闭包,我们可以看看它们的一些应用。
构图
如果我们有两个函数 f 和 g ,我们可以以这样的方式组合它们,使得 f 的输出成为 g 的输入。在数学上,这种运算叫做合成。因此合成操作采用两个函数 f 和 g 并产生一个函数 h ,使得h(x)=g(f(x)。我们可以使用闭包轻松实现它:
# Listing 17def compose(g, f):
def h(*args, **kwargs):
return g(f(*args, **kwargs))
return h
这里的h
是一个闭包,因为它捕获了非局部变量f
和g
。这些非局部变量本身就是函数。这个闭包返回的是f
和g
的组合,也就是g(f(*args, **kwargs))
。我们使用了*args
和**kwargs
来传递多个参数或关键字参数给h
,
这是一个简单的应用程序。假设我们有一些转换单位的函数。例如,假设我们有两个函数,分别将英寸转换为英尺和英尺转换为米。现在我们可以使用我们的compose()
函数将它们合并成一个函数,将英寸转换成米:
# Listing 18inch_to_foot= lambda x: x/12
foot_meter= lambda x: x * 0.3048inch_to_meter = compose(foot_meter, inch_to_foot)
inch_to_meter(12) # Output 0.3048
局部应用
在数学中,一个函数接受的参数数量被称为该函数的 arity。部分应用是减少函数的 arity 的操作。这意味着它允许你固定一些参数的值,并冻结它们来得到一个参数更少的函数。所以它在某种程度上简化了功能。
比如 f ( x,y,z) 的 arity 是 3。我们可以将自变量 x 的值固定在 a 处,得到 f(x=a,y,z) = g(y,z) 。现在 g(y,z) 的 arity 为 2,是 f(x,y,z) 部分应用的结果。所以偏(f)=>g。我们可以使用闭包来实现部分应用程序:
# Listing 19def partial(f, *f_args, **f_keywords):
def g(*args, **keywords):
new_keywords = f_keywords.copy()
new_keywords.update(keywords)
return f(*(f_args + args), **new_keywords)
return g
这里,外部函数接收需要修复的f
和f
的位置和关键字参数。内部函数g
将这些固定参数添加到f
的剩余参数中,稍后它将接收这些参数作为部分函数。最后,它用所有收集到的参数调用f
。清单 20 给出了一个使用部分函数的例子:
# Listing 20func = lambda x,y,z: x**2 + 2*y + zpfunc = partial(func, 1)
pfunc(2, 3) # Output is 8
这里我们将x
的值固定为 1,并将func
转换为pfunc
,现在它有两个参数y
和z
。
阿谀奉承
在数学中,currying 意味着将一个具有多个参数的函数转换成一系列嵌套的一元函数。如前所述,一元函数是一个只有一个参数的函数。例如,如果我们有一个函数 f(x,y,z) 。Currying 将其转换为g(x)(y)(z)=((g(x))(y))(z)。清单 21 展示了我们如何用 Python 实现它:
# Listing 21def curry(f):
argc = f.__code__.co_argcount
f_args = []
f_kwargs = {}
def g(*args, **kwargs):
nonlocal f_args, f_kwargs
f_args += args
f_kwargs.update(kwargs)
if len(f_args)+len(f_kwargs) == argc:
return f(*f_args, **f_kwargs)
else:
return g
return g
现在我们可以将它应用于清单 20 中定义的函数:
cfunc = curry(func)
cfunc(1)(2)# Output:
# <function __main__.curry.<locals>.g(*args, **kwargs)>
和
cfunc(3) # Output is 8
我们再次使用了闭包。但是这个闭包g
是一个递归函数。图 5 显示了这个函数是如何工作的。首先,外部函数curry
接收f
作为参数。f
是应该进行 currying 的函数。我们使用co_argcount
属性获得f
接受的参数数量(即f
的 arity)并将其存储在argc
中。在本例中args
=3。我们有两个非局部变量f_args
和f_kwargs
,它们用于存储g
接受的参数。curry
返回闭包g
并将其分配给cfunc
。所以当我们调用cfunc(1)(2)
时,我们是在调用g(1)(2)
。首先将对g(1)
进行评估。
图 5
g
将其参数添加到f_args
列表中,由于它没有捕获func
的所有参数,因此它递归地返回自身。所以g(1)
的结果是g
,g(1)(2)
变成了g(2)
。现在g(2)
评估。再次g
将其参数添加到f_args
列表中,并且f_args
=[1,2]。g
再次返回自身,但此时它没有要计算的参数。因此,cfunc(1)(2)
的最终输出是g
,在 Python 中显示为<function __main__.curry.<locals>.g(*args, **kwargs)>
。然后我们运行cfunc(3)
。这次对g(3)
求值,给f_args
加 3,就等于[1,2,3]。现在len(f_args)
等于argc
,已经捕获了func
的所有原始参数,所以最后func(1,2,3)
会被g
求值并返回。
您应该知道清单 21 中的curry
函数不仅仅可以做数学运算。这个函数的输出并不局限于嵌套的一元函数,我们可以有更高 arity 的嵌套函数。例如,我们也可以写:
cfunc = curry(func)
cfunc(1, 2)
cfunc(3) # Output is 8
得到同样的结果。这就好比把 f(x,y,z) 转换成 g(x,y)(z) 。
装修和装饰工
在谈论 decorators 之前,我需要提一下 Python 中的函数。当您在 Python 中定义函数时,该函数的名称只是对函数体(函数定义)的引用。所以通过给它赋一个新值,你可以强制它引用另一个函数定义。清单 22 给出了一个简单的例子:
# Listing 22def f():
return("f definition")
def g():
return("g definition")print("f is referring to ", f())
print("g is referring to ", g())print("Swapping f and g")
temp = f
f = g
g = tempprint("f is referring to ", f())
print("g is referring to ", g())
输出:
f is referring to f definition
g is referring to g definition
Swapping f and g
f is referring to g definition
g is referring to f definition
到目前为止,我们已经通过将外部函数的结果赋给一个新变量创建了一个闭包:
h=f(a)
现在假设我们有一个名为deco(f)
的外部函数,它在清单 23 中定义:
# Listing 23def deco(f):
def g(*args, **kwargs):
return f(*args, **kwargs)
return gdef func(x):
return 2*xfunc = deco(func)
func(2) # Output is 4
这里,外部函数是deco(f)
,它将函数f
作为参数,内部函数g
被定义为闭包。我们定义另一个函数func
并对其应用deco
。但是为了初始化闭包,我们将deco
的结果赋给func
。所以deco
将func
作为参数,并再次将其闭包赋给func
。在这种情况下,我们说func
是由deco
修饰,deco
是的修饰者。
图 6
需要注意的是,在将装饰器的结果赋给func
之后,func
指的是闭包g
。所以叫func(a)
就像叫g(a)
。如果我们写:
func.__name__
输出将是'g'
。实际上,变量func
只是对函数定义的引用。最初指func(x)
定义,装修后指g(*args, **kwargs)
。
但是对于原始函数func(x)
会发生什么呢?我们会失去它吗?记住装饰器接收func
作为它的参数。所以它有一个引用func
的副本作为它的局部变量f
。如果原引用发生变化,并不影响这个局部变量,所以在g
内部,f
仍然引用func(x)
定义(我说的函数定义,是指如图 7 所示的函数func(x)
的实际体)。
图 7
所以总结一下,修饰后变量func
指闭包g
,内部g
,f
指func(x)
定义。事实上,g
现在充当了被修饰的原始函数func(x)
的接口。我们不能在g
之外直接调用func(x)
。而是先调用func
调用g
,然后在g
内部可以调用f
调用原函数func(x)
。所以我们使用闭包g
调用原始函数func(x)
。
现在使用这个闭包,我们可以添加更多的代码在调用func(x)
之前或之后运行。我举个简单的例子。假设我们想调试清单 23 中的代码。我们想知道func(x)
是什么时候被调用的。我们可以简单地在func(x)
中放置一个打印语句,让我们知道它何时运行:
def func(x):
print("func is called")
return 2*x
但是它改变了功能,我们必须记得稍后删除它。更好的解决方案是定义一个装饰器来包装函数,并将 print 语句添加到闭包中。
# Listing 24def deco(f):
def g(*args, **kwargs):
print("Calling ", f.__name__)
return f(*args, **kwargs)
return gdef func(x):
return 2*xfunc = deco(func)
func(2)
输出是:
Calling func
4
这个例子显示了装饰器有多有用。现在我们可以看到亵渎的更多应用:
记忆
内存优化是一种用来加速程序的编程技术。它源自拉丁语备忘录,意思是“被记住”。顾名思义,它是基于记忆或缓存昂贵的函数调用的结果。如果使用相同的输入或具有相同参数的函数调用,则之前缓存的结果将用于避免不必要的计算。在 Python 中,我们可以使用闭包和装饰器自动记忆函数。
清单 25 展示了一个计算斐波那契数列的函数。斐波那契数列是递归定义的。每个数字都是前面两个数字的和,从 0 和 1 开始:
# Listing 25def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
for i in range(6):
print(fib(i), end=" ")# Output
# 0 1 1 2 3 5
现在我们定义一个新函数,它可以记忆另一个函数:
# Listing 26def memoize(f):
memo = {}
def memoized_func(n):
if n not in memo:
memo[n] = f(n)
return memo[n]
return memoized_func
要使用memoize
函数,我们可以将其用作fib
的装饰器:
fib = memoize(fib)
fib(30) # Output is 832040
所以功能fib
现在由memoize()
修饰。现在fib
指的是关闭memoized_func
,所以当我们叫fib(30)
的时候,就像叫memoized_func(30)
。装饰器接收原始的fib
作为它的参数f
,所以f
引用memoized_func
中的fib(n)
定义。闭包memoized_func
首先检查n
是否在备忘录字典中。如果在里面,就简单的返回memo[n]
,不调用原来的fib(n)
。如果n
不在 memo 字典中,它首先调用引用原始fib(n).
的f(n)
,然后将结果存储在memo
字典中,最后将其作为最终结果返回。
追踪递归函数
假设我们想调试清单 25 中定义的递归 Fibonacci 函数。我们想看看这个函数是如何调用自己来计算最终结果的。我们可以定义一个装饰器来跟踪函数调用:
# Listing 27def trace(f):
level = 1
def helper(*arg):
nonlocal level
print((level-1)*" │", " ┌", f.__name__,
"(", ",".join(map(str, arg)), ")", sep="")
level += 1
result = f(*arg)
level -= 1
print((level-1)*" │", " └", result, sep="")
return result
return helper
现在我们可以写:
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
fib = trace(fib)
fib(4)
输出是:
┌fib(4)
│ ┌fib(3)
│ │ ┌fib(2)
│ │ │ ┌fib(1)
│ │ │ └1
│ │ │ ┌fib(0)
│ │ │ └0
│ │ └1
│ │ ┌fib(1)
│ │ └1
│ └2
│ ┌fib(2)
│ │ ┌fib(1)
│ │ └1
│ │ ┌fib(0)
│ │ └0
│ └1
└33
装饰函数trace
接收fib
作为它的参数。在调用装饰者之后,fib
指的是关闭helper
。trace
有一个名为level
的非局部变量,显示递归深度,初始设置为 1。helper
首先打印一些填充字符,包括│
、┌
,并显示递归树结构。│
字符的数量与level
的数值成正比。
随着递归深度的增加,更多的│
被打印。对于第一次调用,因为level-1
等于零,所以|
不打印。然后打印函数名及其参数的当前值。之后level
加 1 以显示下一级递归。然后评估函数f
(指传递给装饰器的原始函数)。因此对fib(n)
定义进行评估。由于fib
是递归定义的,它会在某个时候调用自己。记住装修后 fib 指的是封闭helper
。所以递归调用会再次调用helper
,下一级递归会被打印出来。
递归继续,直到我们到达递归基本情况,并且函数定义fib(n)
返回 1。然后level
减一,将返回的结果与返回它的函数名联系起来。返回值被一个接一个地打印出来,直到我们回到第一个调用。
句法糖
语法糖是编程语言中的语法,它使事情更容易阅读或表达。使用语法糖,你可以更清楚或更简洁地编写代码。例如,在 Python 中,+=
操作符是语法糖。所以不用写a=a+1
,你可以简单地写a+=1
。
在清单 24 中,我们编写了func = deco(func)
来修饰函数func
。实际上,我们可以用更简单的方法来做,最终结果是一样的:
# Listing 28def deco(f):
def g(*args, **kwargs):
print("Calling ", f.__name__)
return f(*args, **kwargs)
return g@deco
def func(x):
return 2*xfunc(2)
这里我们简单地在应该由deco(f)
修饰的函数上写@deco
,而不是写func = deco(func)
。这是修饰函数的语法糖,有时被称为派派语法。
图 8
关于这个语法的一些注意事项。@deco
不能在其他地方,并且在@deco
和func(x)
的定义之间不允许有更多的语句。你不应该在@deco
后面写deco
的参数。语法假设deco
只接受一个参数,这个参数是应该被修饰的函数。稍后我会解释如果装饰函数有更多的参数该怎么做。
在清单 24 中,一旦编写了func = deco(func)
,装饰就开始了。但是在清单 28 中什么时候会发生呢?用 pie 语法定义的 decorators 的一个关键特性是它们在被修饰的函数被定义后立即运行。这通常是在 Python 加载模块时的导入时间。
堆叠装饰者
在清单 29 中,我们定义了两个装饰器:
# Listing 29def deco1(f):
def g1(*args, **kwargs):
print("Calling ", f.__name__, "using deco1")
return f(*args, **kwargs)
return g1def deco2(f):
def g2(*args, **kwargs):
print("Calling ", f.__name__, "using deco2")
return f(*args, **kwargs)
return g2def func(x):
return 2*xfunc = deco2(deco1(func))
func(2)
这里我们首先将deco1
应用于func
,然后将deco2
应用于deco1
返回的闭包。所以我们有两个堆叠的装饰者。我们先用deco1
装饰func
,再用deco2
装饰。输出是:
Calling g1 using deco2
Calling func using deco1
4
事实上g2
和g1
现在充当被修饰的原始函数func(x)
的接口。为了达到最初的功能func(x)
,我们首先调用func
,它指的是g2
。在g2
内部,参数f
指的是g1
。所以通过调用f
,我们在调用g1
,然后在g1
内部我们可以调用f
来调用原来的函数func(x)
。
图 9
我们还可以使用 pie 语法将堆栈装饰器应用到函数中。为此,我们可以写:
# Listing 30def deco1(f):
def g1(*args, **kwargs):
print("Calling ", f.__name__, "using deco1")
return f(*args, **kwargs)
return g1def deco2(f):
def g2(*args, **kwargs):
print("Calling ", f.__name__, "using deco2")
return f(*args, **kwargs)
return g2@deco2
@deco1
def func(x):
return 2*xfunc(2)
在这里,装饰者按照包装函数的顺序堆叠在函数定义的顶部。
图 10
我们还可以使用清单 17 中引入的函数compose
来组合这两个装饰器,然后将它们应用到目标函数。合成操作可以采用两个函数deco1(f)
和deco2(f)
,并产生一个函数deco
,使得deco(f) = deco2(deco1(f))
。这如清单 31 所示。
# Listing 31def deco1(f):
def g1(*args, **kwargs):
print("Calling ", f.__name__, "using deco1")
return f(*args, **kwargs)
return g1
def deco2(f):
def g2(*args, **kwargs):
print("Calling ", f.__name__, "using deco2")
return f(*args, **kwargs)
return g2deco = compose(deco2, deco1)
@deco
def func(x):
return 2*x
func(2)
现在我们可以看到一个堆叠装饰器的例子。记得我们为记忆定义了一个装饰器。我们还定义了一个装饰器来跟踪递归函数。现在我们可以把它们结合起来,用记忆来追踪一个递归函数。我们首先用清单 26 中给出的memoize
装饰器装饰 Fibonacci 函数,然后用清单 27 中给出的trace
装饰器。我们先试着算一下fib(5)
。
# Listing 32@trace
@memoize
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)fib(5)
输出是:
┌memoized_func(5)
│ ┌memoized_func(4)
│ │ ┌memoized_func(3)
│ │ │ ┌memoized_func(2)
│ │ │ │ ┌memoized_func(1)
│ │ │ │ └1
│ │ │ │ ┌memoized_func(0)
│ │ │ │ └0
│ │ │ └1
│ │ │ ┌memoized_func(1)
│ │ │ └1
│ │ └2
│ │ ┌memoized_func(2)
│ │ └1
│ └3
│ ┌memoized_func(3)
│ └2
└55
由于 memo 字典最初是空的,它将执行所有的递归调用来计算结果。现在,如果我们尝试fib(6)
,我们会得到:
┌memoized_func(6)
│ ┌memoized_func(5)
│ └5
│ ┌memoized_func(4)
│ └3
└8
8
这次fib(5)
和fib(4)
的结果已经存储在memo
字典中。所以它只需要检索它们来计算结果,不需要更多的递归。你可能已经注意到了,函数fib
的名字没有打印在递归树中。相反,我们看到了闭包的名字memoized_func
。原因是我们有两个堆叠的装饰器,并且trace
装饰器接收memoized_func
作为它的参数,而不是函数fib
。所以在trace
里面现在f
是指memoized_func
,f.__name__
返回memoized_func
。在下一节中,我将向您展示我们如何解决这个问题。
装饰者附加参数
到目前为止,我们讨论的装饰器只接受一个参数,即被装饰的函数,然而,我们可以有额外的参数。还记得我们在清单 24 中看到的装饰器吗?它在调用之前打印一条消息,加上被修饰函数的名称。现在我们想创建一个装饰器,它将消息作为一个附加参数。事实上,我们希望向装饰器传递两个额外的参数。一个是在调用修饰函数之前将被打印的消息,另一个是在调用它之后将被打印的消息。
# Listing 33def deco(msg_before, msg_after):
def original_deco(f):
def g(*args, **kwargs):
print(msg_before + " " + f.__name__)
result = f(*args, **kwargs)
print(msg_after + " " + f.__name__)
return result
return g
return original_deco
def func(x):
return 2*x
func = deco("Starting", "Finished")(func)
func(2)
输出是:
Starting func
Finished func
4
正如您所看到的,要将附加参数传递给装饰器,我们需要用另一个接受这些附加参数的函数来包装原来的装饰器函数。现在,这个函数将是装饰器,而最初的装饰器将是它内部的一个嵌套闭包。为了调用装饰器,我们需要首先将附加参数传递给deco
,然后被装饰的函数f
将被传递给它返回的闭包(这是最初的装饰器)。我们还可以使用清单 34 和图 11 所示的饼图语法。
# Listing 34def deco(msg_before, msg_after):
def original_deco(f):
def g(*args, **kwargs):
print(msg_before + " " + f.__name__)
result = f(*args, **kwargs)
print(msg_after + " " + f.__name__)
return result
return g
return original_deco@deco("Starting", "Finished")
def func(x):
return 2*xfunc(2)
然后我们只需要将附加参数传递给@deco
。
图 11
你可能会问为什么我们不能把额外的参数传递给最初的装饰者?实际上我们可以:
# Listing 35def deco(msg_before, msg_after, f):
def g(*args, **kwargs):
print(msg_before + " " + f.__name__)
result = f(*args, **kwargs)
print(msg_after + " " + f.__name__)
return result
return g
def func(x):
return 2*x
func = deco("Starting", "Finished", func)
func(2)
sds
这里我们简单地传递附加参数和函数名。问题是如果你这样定义,你就不能使用 pie 语法。所以如果你写:
# This does not work:
@deco("Starting", "Finished")
def func(x):
return 2*x
#func = deco("Starting", "Finished")
func(2)
Python 给出一个错误。也可以使用我们在清单 21 中定义的函数curry
来简化装饰函数。curry
函数将另一个函数作为它的参数,并返回一个闭包,因此它可以用来装饰我们的装饰器。所以我们可以用下面的代码替换清单 34:
# Listing 36@curry
def deco(msg_before, msg_after, f):
def g(*args, **kwargs):
print(msg_before + " " + f.__name__)
result = f(*args, **kwargs)
print(msg_after + " " + f.__name__)
return result
return g
@deco("Starting", "Finished")
def func(x):
return 2*xfunc(2)
运行函数deco
后,可以单独获取其参数。因此,通过编写@deco(“Starting”, “Finished”)
,我们提供了deco
的前两个参数,现在它只需要一个参数,即修饰函数。所以我们可以使用 pie 语法,我们不需要定义另一个函数来获取额外的参数。
我们现在可以回到上一节中的问题。我们希望在修饰后保留被修饰函数的签名。我们可以为此定义一个新的装饰器:
# Listing 37def wraps(f):
def decorator(g):
def helper(*args, **kwargs):
return g(*args, **kwargs)
attributes = ('__module__', '__name__', '__qualname__',
'__doc__', '__annotations__')
for attr in attributes:
try:
value = getattr(f, attr)
except AttributeError:
pass
else:
setattr(helper, attr, value)
return helper
return decorator
为了理解这个装饰器,假设我们有一个名为f
的函数,我们想用另一个函数g
来装饰它。但是我们也希望g
(也就是helper
)返回的闭包有f
的相同签名,这样我们可以在装修后保留f
的签名。wraps
是一个装饰器,它接收f
作为附加参数,并装饰传递给其嵌套闭包decorate
的函数g
。然后,它使用getattr()
获取f
的所有属性,并将它们分配给闭包helper
,闭包最终作为修饰的结果返回。现在helper
有了f
的签名。
我们将这个装饰器添加到清单 26 中定义的 memoize 函数中。这里wraps
保存了应该被记忆的函数f
的签名。
# Listing 38def memoize(f):
memo = {}
@wraps(f)
def memoized_func(n):
if n not in memo:
memo[n] = f(n)
return memo[n]
return memoized_func
功能trace
保持不变,我们可以和memoize
一起使用来正确跟踪fib
:
# Listing 39@trace
@memoize
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)fib(5)
现在输出显示了正确的函数名:
┌fib(5)
│ ┌fib(4)
│ │ ┌fib(3)
│ │ │ ┌fib(2)
│ │ │ │ ┌fib(1)
│ │ │ │ └1
│ │ │ │ ┌fib(0)
│ │ │ │ └0
│ │ │ └1
│ │ │ ┌fib(1)
│ │ │ └1
│ │ └2
│ │ ┌fib(2)
│ │ └1
│ └3
│ ┌fib(3)
│ └2
└55
我们可以通过评估fib(6)
来检查记忆。输出是:
┌fib(6)
│ ┌fib(5)
│ └5
│ ┌fib(4)
│ └3
└8
8
这个例子展示了不同类型的装饰器如何组合在一起产生期望的输出。
装饰器是 Python 中元编程的有用工具。元编程是关于创建以操作代码为主要目标的函数。本文中的例子展示了如何通过用不同的装饰器包装它们来改变函数的行为。我希望你喜欢阅读这篇文章。如果您有任何问题或建议,请告诉我。本文中的所有代码清单都可以作为 Jupyter 笔记本从 GitHub 下载,网址是:【https://github.com/reza-bagheri/Closures-and-decorators
用自然语言处理分析服装评论—第二部分
https://www.klaviyo.com/blog/fashion-apparel-best-practices
从文本评论分析预测项目评分
在我之前的文章中,我一直在分析网上购买女装的文字评论,以推断顾客的情绪。这个想法是调查情绪是否与购买建议一致。
在本文中,我将继续分析文本评论,但现在将重点放在评级列上,该列显示该商品的得分从 1(最差)到 5(最好)。
让我们刷新一下我们正在谈论的数据集(你可以在 Kaggle 上找到它):
import pandas as pd
df = pd.read_csv('Womens Clothing E-Commerce Reviews.csv')#data cleaningdf.dropna(inplace=True)
df.reset_index(drop=True, inplace=True)
import re
for i in range(len(df)):
#print(i)
df['Review Text'][i] = df['Review Text'][i].replace("'s", " is").replace("'ll", " will").replace("'ve", " have").replace("'m", " am").replace("\'", "'")
df['Review Text'][1]
df = df.drop('Unnamed: 0', 1)#data preprocessingimport nltk
import en_core_web_sm
import spacy
nlp = spacy.load("en_core_web_sm")
from nltk.corpus import stopwords
def lemmatization(df):
df["Lemmas"] = [" ".join([token.lemma_ if token.lemma_ != "-
PRON-" else token.text.lower()
for sentence in nlp(speech).sents for token in sentence if
token.pos_ in {"NOUN", "VERB", "ADJ", "ADV", "X"} and
token.is_stop == False]) for speech in df.text]
df["Lemmas"] = [" ".join([token.lemma_ if token.lemma_ != "-PRON-"
else token.text.lower() for sentence in
nlp(speech).sents for token in sentence if
token.pos_ in {"NOUN", "VERB", "ADJ", "ADV", "X"}
and token.is_stop == False]) for speech in
df['Review Text']]df.head()df.head()
(如果您不熟悉代码,请务必查看本系列的第 1 部分,其中解释了代码的每个块!)
为此,我们将使用 Keras (内嵌 Tensorflow)构建一个神经网络。在开始之前,我们需要对数据集做一些进一步的预处理。事实上,由于它是一个多类分类(5 个类组成评级列),我们首先需要对该列进行一次性编码(一旦将其从整数转换为字符串):
from sklearn.preprocessing import MultiLabelBinarizermlb = MultiLabelBinarizer()df_factor = df.join(pd.DataFrame(mlb.fit_transform(df['Rating']),
columns=mlb.classes_,
index=df.index))
df_factor.head()
很好,现在让我们导入必要的库,并将数据集分成训练(80%)和测试(20%)集。这对于我们模型的稳健性至关重要:事实上,由于任何机器学习模型的主要目标都是对新的、前所未见的数据进行准确预测,因此根据算法在测试集而不是训练集上的性能来调整算法的参数是至关重要的,以避免过度拟合。
import tensorflow as tf
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import InputLayer, Input
from tensorflow.python.keras.layers import Reshape, MaxPooling2D
from tensorflow.python.keras.layers import Conv2D, Dense, Flatten, Activation, Dropout
from tensorflow.python.keras.optimizers import SGD#diseabling eager execution
from tensorflow.python.framework.ops import disable_eager_execution
disable_eager_execution()import pandas as pd
from sklearn.model_selection import train_test_splittrain, test = train_test_split(df_factor, test_size=0.2)vectorizer_nn = TfidfVectorizer(ngram_range = (1, 2), min_df = 0.001, max_df = 0.25, stop_words = 'english')X_train_nn = vectorizer_nn.fit_transform(train.Lemmas)
X_test_nn = vectorizer_nn.transform(test.Lemmas)y_train = train.drop(["Clothing ID", "Age", "Title", "Review Text", "Rating", "Recommended IND", "Lemmas", "Positive Feedback Count", "Division Name", "Department Name", "Class Name"], axis = 1)
y_test = test.drop(["Clothing ID", "Age", "Title", "Review Text", "Rating", "Recommended IND", "Lemmas", "Positive Feedback Count", "Division Name", "Department Name", "Class Name"], axis = 1)
注意:在导入必要的模块时,我编写了“tensorflow.python.keras.models”代码,因为这是存储模块的路径。在输入之前,请确保检索 TensorFlow 模块的正确路径。
现在让我们建立我们的模型。这个想法是初始化一个空模型,用 Sequential(),然后添加许多隐藏层,用 dropout 选项来避免过度拟合,最后,用 softmax 函数激活层。
(注:如果你有兴趣了解更多关于神经网络的参数和超参数,可以在这里阅读我以前的文章。)
model = Sequential()
model.add(Dense(5000, activation='relu', input_dim = X_train_nn.shape[1]))
model.add(Dropout(0.1))
model.add(Dense(600, activation='relu'))
model.add(Dropout(0.1))
model.add(Dense(200, activation='relu'))
model.add(Dropout(0.1))
model.add(Dense(y_train.shape[1], activation='softmax'))sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='categorical_crossentropy',
optimizer=sgd,
metrics=['accuracy',])score = model.evaluate(X_test_nn, y_test, batch_size = 7000)
score
正如你所看到的,我们最终得到了一个准确率为 54.36%的模型,这并不是那么糟糕,但仍然太低,不能作为一个可靠的预测指标。我们能做得比这更好吗?好吧,如果我们让病人用我们的神经网络玩一点甜菜,我们可以尝试不同的组合,看看结果。
令人欣慰的是,Keras 是一个强大的工具,因为它提供了一个简单的界面。我们还可以绘制培训和测试过程的历史,如下所示:
import matplotlib.pyplot as plthistory = model.fit(X_train_nn, y_train, validation_split=0.25, epochs = 10, batch_size = 7000, verbose=1)# Plot training & validation accuracy values
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.show()# Plot training & validation loss values
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.show()
本文并不是关于 Keras 的详尽指南,但它是一个例子,说明了如何轻松实现 Keras 模块来完成这种任务。
如果你有兴趣了解更多关于 Keras 和神经网络的知识,我推荐以下读物:
- https://keras.io/getting-started/sequential-model-guide/
- https://www.tensorflow.org/api_docs/python/tf
- 【https://pathmind.com/wiki/neural-network
- https://medium . com/data series/understanding-the-maths-behind-neural-networks-108 a4 ad 4d db
用 NLP 分析服装评论—第 1 部分
https://www.klaviyo.com/blog/fashion-apparel-best-practices
如何推断顾客评论的情绪
自然语言处理(NPL)是人工智能的一个领域,其目的是找到计算方法来解释人类语言的口语或书面语。NLP 的思想超越了可以由 ML 算法或深度学习神经网络进行的简单分类任务。的确,NLP 是关于解释的:你想训练你的模型不仅要检测频繁出现的单词,还要统计它们或者消除一些嘈杂的标点符号;你希望它告诉你谈话的情绪是积极的还是消极的,电子邮件的内容是纯粹的宣传还是重要的事情,过去几年关于惊悚小说的评论是好是坏。
好消息是,对于 NLP,我们提供了有趣的 Python 库,它提供了一个预先训练好的模型,能够查询书面文本。其中,我将使用空间和 NLTK 。
在本文中,我将分析女装电子商务数据集,其中包含客户撰写的文本评论(此处可用)。
以下是数据集的描述:
This dataset includes 23486 rows and 10 feature variables. Each row corresponds to a customer review, and includes the variables:Clothing ID: Integer Categorical variable that refers to the specific piece being reviewed.Age: Positive Integer variable of the reviewers age.Title: String variable for the title of the review.Review Text: String variable for the review body.Rating: Positive Ordinal Integer variable for the product score granted by the customer from 1 Worst, to 5 Best.Recommended IND: Binary variable stating where the customer recommends the product where 1 is recommended, 0 is not recommended.Positive Feedback Count: Positive Integer documenting the number of other customers who found this review positive.Division Name: Categorical name of the product high level division.Department Name: Categorical name of the product department name.Class Name: Categorical name of the product class name.
因此,让我们导入并检查我们的数据集:
import pandas as pd
df = pd.read_csv('Womens Clothing E-Commerce Reviews.csv')
df.head()
现在让我们清理它,删除 NaN 值和无用的列,以及清理文本。
df.dropna(inplace=True)
df.reset_index(drop=True, inplace=True)
import re
for i in range(len(df)):
#print(i)
df['Review Text'][i] = df['Review Text'][i].replace("'s", " is").replace("'ll", " will").replace("'ve", " have").replace("'m", " am").replace("\'", "'")
df['Review Text'][1]
df = df.drop('Unnamed: 0', 1)
df.head()
这个想法是提取每个评论的情感,看它是否与推荐指标一致(如果评论推荐该项目,则为 1,否则为 0)。
为此,我们将使用 NLTK 包中可用的 Vader 模块(您可以在这里找到官方文档)。但是首先,让我们预处理我们的文本并提取词条:
#importing necessary librariesimport nltk
import en_core_web_sm
import spacy
nlp = spacy.load("en_core_web_sm")
from nltk.corpus import stopwords def lemmatization(df):
df["Lemmas"] = [" ".join([token.lemma_ if token.lemma_ != "-
PRON-" else token.text.lower()
for sentence in nlp(speech).sents for token in sentence if
token.pos_ in {"NOUN", "VERB", "ADJ", "ADV", "X"} and
token.is_stop == False]) for speech in df.text] df["Lemmas"] = [" ".join([token.lemma_ if token.lemma_ != "-PRON-"
else token.text.lower() for sentence in
nlp(speech).sents for token in sentence if
token.pos_ in {"NOUN", "VERB", "ADJ", "ADV", "X"}
and token.is_stop == False]) for speech in
df['Review Text']]df.head()
现在让我们对 Lemmas 列进行情感分析:
from nltk.sentiment.vader import SentimentIntensityAnalyzersid = SentimentIntensityAnalyzer()
sentiment=[]
for i in range(len(df)):
ss = sid.polarity_scores(df.Lemmas[i])
sentiment.append(ss)
compound=[sentiment[i]['compound'] for i in range(len(sentiment))]
df['compound']=compound
df.head()
复合列包含范围从-1(非常负)到 1(非常正)的分数。一般来说,如果一篇文章的分数大于 0.05,我们就给它贴上“正面”标签;如果分数小于-0.05,我们就贴上“负面”标签;如果分数在-0.05 到 0.05 之间,我们就贴上“中性”标签。但是,为了这个研究问题的目的,我们将只区分积极和消极,使用分数=0 作为阈值。
sent=[]
for i in compound:
if i<0:
sent.append('negative')
else:
sent.append('positive')df['sentiment']=sent
df.head()
第一眼看到情感专栏,似乎大多数评论都是积极的,但它与推荐项目的数量一致:在 19661 篇评论中,有 16087 篇最终得到了推荐。
现在我们来看看有多少推荐商品符合点评的情绪。让我借用假阳性/阴性和真阳性/阴性的统计学术语来完成这个任务:当商品被推荐购买时,我谈论假阳性,然而文本评论却带有负面情绪。
#initializing a 2x2 matrix populated with zerosmat = np.zeros(4).reshape(2,2)
true_neg=0
true_pos=0
false_neg=0
false_pos=0for i in range(len(df)):
if df['sentiment'][i]=='negative' and df['Recommended IND'][i]==0:
true_neg+=1
elif df['sentiment'][i]=='positive' and df['Recommended IND'][i]==1:
true_pos+=1
elif df['sentiment'][i]=='positive' and df['Recommended IND'][i]==0:
false_neg+=1
elif df['sentiment'][i]=='negative' and df['Recommended IND'][i]==1:
false_pos+=1
mat[0][0]=true_neg
mat[0][1]=false_neg
mat[1][0]=false_pos
mat[1][1]=true_pos
mat
让我们把它变得漂亮一点:
from pandas import DataFrameIndex= ['Not Recommended', 'Recommended']
Cols = ['Negative', 'Positive']
d = DataFrame(mat, index=Index, columns=Cols)
所以在 16087 个推荐条目中,只有 227 个不符合文本的情感。另一方面,我们并不推荐 3154 个实际上已经获得正面评价的项目。这种结果可能暗示了推荐系统的不同方法,因为如果我们观察评论者的情绪,可能会购买更多的项目。
情感分析在市场研究和类似领域中起着举足轻重的作用。事实上,跟踪顾客的满意度,最重要的是,隔离那些决定他们对商品的情绪的驱动因素,可能会导致赢得营销活动和销售策略。
在这篇文章中,我将我的研究局限于情感分析,但是这个数据集提供了多个研究问题,这将在我的下一篇文章中涉及,所以请继续关注!
参考资料:
- https://www.nltk.org/
- https://spacy.io/
- 【https://medium.com/r/? URL = https % 3A % 2F %2Fwww . ka ggle . com % 2Fnicapotato % 2f 预兆-电子商务-服装-评论% 2f 版本%2F1%23
云采用—企业有选择吗?
来源: Freepik 网站
在我们讨论为什么要云这个更大的问题之前,让我们先了解云计算的一些基础知识:
什么是云计算?
简而言之,云计算是指使用通过互联网提供的按需 IT 资源,采用按需付费的定价模式。与购买基础设施的传统模式相比,关键区别在于,提供商(比如谷歌、亚马逊、微软、IBM、阿里巴巴或任何其他提供商)为您维护相同的硬件,而不是购买您自己的硬件。
云提供商提供核心 IT 基础设施服务(例如计算、存储、网络、数据库)以及基于核心基础设施的解决方案(大数据和分析、IOT、SaaS 应用等)。).
对于企业来说,采用云是一种选择还是一种必需?
在回答这个问题之前,我们先来看看采用云技术相对于购买和维护传统 IT 基础设施有什么好处
- 收益提升导向效益
**a)灵活快速地管理不断增长的客户群:**随着业务的增长,您需要更多的 IT 基础设施来管理不断增长的客户需求(例如,更高的计算能力、更多的数据存储、更快的响应时间)。你是怎么做到的?
- 选择 1(传统):每次现有硬件无法管理工作负载时,购买更多硬件。这更像是一个阶跃函数,您要么购买刚好满足新工作负载的量(并随着需求的增加继续购买),要么购买足以满足未来几个月/几年的峰值需求的量(这意味着浪费了大量容量)。如果您对此管理不当,将会对收入产生影响(例如,由于响应时间短、网站无法加载等原因导致客户不满意。)
- 选择 2(基于云):借助云的力量,您无需在需求发生变化时购买/销售物理硬件—扩展/缩减可以动态自动发生
**b)有效利用数据获得/保留客户/交叉销售/追加销售:**利用您周围不同来源的大量数据(企业应用程序、社交媒体、交易数据、IOT 数据等)。),作为一个组织,您有两种选择:
- 选择 1(传统):使用固定容量的 IT 基础架构,仅处理基础架构允许的数据量
- 选择 2(基于云):挖掘所有可用的数据,使用云弹性容量将它们存储(或有意识地丢弃)在基于云的数据湖/数据仓库中,并创建真正的 360 度客户视图,以推动客户增长相关的分析用例
c) **以敏捷的方式应对变化:**面对如此众多的市场竞争对手和不断变化的客户环境,您需要不断发展以保持相关性。环境要求推出新产品/新功能,并前所未有地挑战客户/员工体验的极限。你如何解决这个问题?
- 选择 1(传统):在现有硬件和软件解决方案的限制下开发和部署新产品(或者等待很长时间来购买和集成任何新的外部解决方案)。
- 选择 2(云原生):利用云上可用的大量解决方案来快速开发/部署新产品/功能;利用数据和分析平台,无需从头构建;使用现有的客户服务解决方案(例如支持人工智能的聊天机器人)
**d)支持地域增长:**您需要扩展到一个新的地域,您会怎么做?
- 选择 1(传统):在新的地理位置设置整个技术基础设施,并在那里部署您的应用程序
- 选择 2(基于云):立即在云中为新的地理位置配置基础架构,在新的地理位置部署您的应用程序并投入使用!
2。成本降低导向效益**
a)将资本支出转变为运营支出,降低单位运营成本(按使用付费;不需要为未使用的部分付费,这是在传统模式中容量固定的情况下)。
**b)避免成本:**消除硬件更新、维护、升级等成本。
**c)提高生产率:**降低停电管理成本;使用云上可用的自动化解决方案提高工作效率
**d)提高弹性:**减少硬件故障导致的停机时间
考虑到云影响核心业务和关键 KPI(业务收入和利润)的多种方式,说采用云只是众多可用 it 选择之一是不明智的。如果处理得当,它可以为企业带来一些真正的核心业务利益。
一个有趣的例子是网飞,以及它如何使用云来推动其核心业务。详细信息可在 AWS 网站上找到:
https://AWS . Amazon . com/solutions/case-studies/网飞案例研究/
有哪些不同的云迁移策略?
一旦您决定要开始向云迁移,那么在将您的技术环境向云迁移时,您有哪些不同的选择?(请注意,以下策略是从应用程序组合的角度来看的,它最终会影响对运行应用程序的底层基础架构的选择)
a) **保留:**一些应用程序可能过于复杂,无法迁移到云(esp。在短期到中期内),所以最好不要碰它。在大型遗留应用程序的情况下,这是最真实的
**b)丢弃/整合:**一些应用程序可能不再需要(例如,另一个应用程序已经提供的功能)。这些应该从应用程序组合中丢弃
**c)主机转移(提升和转移)😗*在大型传统迁移场景中,组织希望快速扩展其迁移以满足业务需求,大多数应用程序都需要进行主机转移。借助工具,可以极大地自动化重新托管,并带来一些立竿见影的效果(但是,不具备云原生应用所具备的长期优势)
**d)重新平台:**这种方法进行了一些云(或其他)优化,以获得一些切实的好处,但您不会改变应用程序的核心架构。
**e)重新架构(云原生)😗*这是由增加功能、规模或性能的强烈业务需求所驱动的,否则在应用的现有环境中很难实现这些功能、规模或性能
f) 回购:这意味着完全放弃你内部应用程序,从其他提供商那里转移到 SaaS 模式
任何云迁移策略都不必只是其中之一。根据产品组合中不同应用的不同需求,可以采用这些策略的组合。
典型云迁移计划的执行阶段有哪些?
如果您决定迁移,您如何计划迁移,涉及哪些不同的阶段?
a)根据您试图通过云迁移计划实现的目标,评估上一节所述的各种迁移策略。
**b)评估迁移准备情况:**此包括查看您的投资组合中的所有应用程序,并针对不同的策略对它们进行评估(例如,重组评估包括查看不同应用程序的代码级别细节,以了解与其他应用程序的依赖性、接口数量等。).
**c)确定机会:**任何迁移都应该包括在进行实际迁移之前创建业务案例,以确定潜在收益(和所需投资)。
**d)迁移:**一旦业务案例建立,根据最终确定的策略进行实际的迁移。
**e)验证:**进行迁移后测试/检查,以验证迁移已按计划完成,没有中断用户旅程,达到了预期的性能水平等。
**f)优化:**确保云成本得到优化,以最大限度地从您的迁移计划中获益,这一点非常重要。通常对于新的云采纳者来说,这可能是一个很大的挑战,因为他们关注的是保持一段时间的正常运行,并确保云迁移后业务正常运行。然后,他们开始考虑成本合理化(例如,评估计算实例、存储实例等的数量。)用于现有的云设置。
组织面临的典型挑战是什么?他们如何克服这些挑战?
描述了采用云的所有优势、不同的战略和阶段后,同样重要的是要认识到这不是一项简单的任务(尤其是对于那些多年来一直在使用大量传统设置的大型企业)。企业在采用云的过程中面临一些常见的挑战,例如
- **正确的云知识:**理解云的细微差别是最大的障碍之一。通常我们会尽量避免做我们不理解的事情——这是基本的人性。因此,不管云能给企业带来多大的上升潜力,都有一种继续照常经营的趋势。
- 对变化的恐惧:人们通常害怕变化——“如果事情停止运转了怎么办?”、“实现的收益不如预期怎么办?”。
- **理解执行之路:**像“我从哪里开始?”这样的问题,“路线图是什么?”“时间框架是什么?”、“我以什么顺序移动应用程序”、“我从哪个提供商选择?”等等。在决策者的头脑中是很常见的。
好消息是,在生态系统中可用人才的正确组合的帮助下,可以解决大多数这些挑战,以支持组织的转型之旅。人才组合通常是以下因素的组合
- **内部人才:**您组织中了解云并帮助定义云采用战略的人员。
- 云提供商:比如亚马逊(Amazon)、谷歌(Google)、微软(Microsoft)。
- IT 服务提供商他们带来急需的公正观点和客观性(例如选择合适的云提供商),并帮助组织规划/执行云采用计划的不同阶段。
那么,您应该何时开始您的云采用之旅呢?
用马克·吐温的话来说——“二十年后,你会因为你没有做的事情而比因为你做了的事情更失望,所以扔掉缆绳,远离安全的港湾,在你的帆上抓住信风。探索,梦想,发现。”
我也写关于财务规划和投资的话题。在我的网站上查找更多信息 Getmoneyinsights
未雨绸缪的云 API
使用 Wolfram 语言编写、部署和共享计算从未如此简单
尼克·舍尔巴特在 Unsplash 上的照片
很久以前,我有幸徒步旅行到爱尔兰西部,靠近戈尔韦,穿过康尼马拉国家公园。景色非常壮观,但真正吸引我注意的是不断变化的天气模式。在一天的徒步旅行中,出现多次晴雨循环并不罕见,很多天还会出现午后彩虹。
位于大西洋附近,一股来自墨西哥湾和加勒比海的暖流。这使得爱尔兰的气候比基于纬度的预期要温和,也限制了全年的极端温度波动。但是爱尔兰经常下雨。
为了了解爱尔兰的降雨量,我访问了爱尔兰气象局的网站。他们提供当前的天气信息和历史数据。从历史数据页面,我下载了一个包含 1850 年至 2010 年时间序列数据的数据集。该数据集包括爱尔兰全国 25 个气象站的降雨量。
在这个故事中,我将讨论如何使用这些数据并创建一个可调用的 API 来可视化这些数据。
每个工作站都有自己的 CSV 文件。这是戈尔韦大学学院电视台的一个样本:
(图片由作者提供)
这个原始数据最好用 Wolfram 语言中的一个 TimeSeries 对象来表示。我们从 cvs 表达式中获取数据字段,用 DateRange 生成每月日期,并构造时间序列(快速注意:在本文结尾提供了一个包含完整代码的笔记本):
rain = Flatten[csv[[4 ;;, 2 ;;]]];dates = DateRange[
DateObject[{1850, 1}, "Month"],
DateObject[{2010, 12}, "Month"]];ts = TimeSeries[Thread[{dates, rain}]]
使用这个时间序列对象,我们可以创建一个可视化函数:
(图片由作者提供)
这使得我们可以绘制任何时间范围的图,例如,20 世纪 50 年代:
(图片由作者提供)
此时,将该功能部署为 API 是非常容易的。我们定义了一个 APIFunction ,它定义了 URL 参数和函数参数之间的联系:
api = APIFunction[{
"y1" -> "Integer", "m1" -> "Integer",
"y2" -> "Integer", "m2" -> "Integer"},
ExportForm[GalwayRainPlot[{#y1, #m1}, {#y2, #m2}], "PNG"] &
];
接下来,我们使用 CloudDeploy 将这个 APIFunction 部署到一个 CloudObject:
CloudDeploy[api, CloudObject["GalwayRainPlot.api"], Permissions -> "Public"]
此时,API 是公开可用的。您可以更改权限设置,使您的 API 更加私密。这里有一个的例子,称之为 20 世纪 60 年代高威降雨量的 API。
(图片由作者提供)
当然,你可以按照这个工作流程,与云中的其他人共享任何计算。只用几行代码就可以将任何计算部署到 Wolfram 云的概念非常强大。有关更多信息,请查看以下指南页面:
Wolfram 语言具有在 web 和其他地方创建和部署 API 的内置功能…
reference.wolfram.com](https://reference.wolfram.com/language/guide/CreatingAnInstantAPI.html)
至于在爱尔兰徒步旅行,我希望有一天能再去一次。但是如果你打算去,一定要带一件雨衣和多一双干袜子!
由康纳无法无天——【https://www.flickr.com/photos/conchur/28657824895/】T4,CC 由 2.0,https://commons.wikimedia.org/w/index.php?curid=86922866
这款笔记本的完整代码可从以下网址获得:
用 Wolfram 语言进行计算思维
www.wolframcloud.com](https://www.wolframcloud.com/obj/arnoudb/Published/GalwayRainPlot-01.nb)