理解 PCA(主成分分析)
Photo by Margaret Weir on Unsplash
我们发现主成分分析如何帮助我们揭示数据中的潜在趋势
在数据科学和金融(以及几乎任何定量学科)中,我们总是在大量噪音中寻找信号。现在,如果有一个算法可以为我们做到这一点…
有!今天,我们将探索 PCA(主成分分析)如何帮助我们发现隐藏在数据中的潜在驱动因素——这是一个非常有用的功能,因为它允许我们仅使用几个主成分来总结庞大的功能集。
如果你对我用来生成下面图表的代码感兴趣,你可以在我的 GitHub 上找到它 这里 。
变化既是我们的敌人,也是我们的朋友
如果你花了一些时间阅读统计学或数据科学教科书,你会注意到,我们费尽周折建立模型的主要原因是为了解释方差。
但是这到底意味着什么呢?让我们一步一步地解开这个。首先,我们所说的方差实际上指的是什么?想象我们的数据是这样的:
A Flat Line Has Zero Variance
你在想,“托尼,你为什么给我看一条直线?这太无聊了。”这正是问题的关键。如果我们的数据只是一条平坦的线,那么预测起来会非常容易(只是一直预测五条),但也完全没意思。扁平线是零方差数据的一个例子,数据中绝对没有垂直变化。
现实生活中有什么零方差数据的例子?这听起来很荒谬,但是让我们假设你的老板让你预测一栋五层楼的楼层数。所以在 100 天的每一天,你都要测量这个建筑的层数,最后你会得到上面的图表,一条直线。当你的老板回来问你的预测时,你自信地说“我预测明天大楼仍然有五层楼高!”火箭科学对吗?
除非发生灾难,否则你有 100%的可能是正确的。但是你的所作所为毫无意义——没有方差的数据没有不确定性,所以我们没有什么可以预测或解释的,它就是这样。如果我们试图使用方差为零的变量作为模型中的 X 变量(以预测方差非零的目标变量),我们会因为特征集中信号的绝对缺乏而变得非常沮丧。
那么有方差的数据是什么样的呢?以下是苹果股票 100 天的每日价格回报(百分比):
100 Days of AAPL Daily Returns
现在我们有东西可以利用了。不出所料,我们的苹果股票回报数据中存在大量的方差— **方差基本上就是数据上下波动的幅度。它越有弹性,就越难预测或解释。**苹果的日回报率大幅反弹,在正值和负值之间来回波动,包括一些大幅飙升。
但是在这些噪音中,有弹性的数据也包含信号(也就是信息)。
与没有方差的数据相比,弹性数据作为模型的目标变量比有趣得多,作为模型内部的特征变量比有用得多。
这就是为什么我说变化既是我们的敌人也是我们的朋友:
- 它是我们的敌人,因为我们的目标变量中更多的变化会产生不确定性,使目标更难预测或解释。
- 但它也是我们的朋友,因为正如我们上面看到的,没有方差的特征是完全没有意思的,根本不包含任何信号。因此,为了有机会建立一个好的模型,我们需要具有信号的特征,或者换句话说,具有方差的特征(实际上,更具体地说,我们想要与目标具有非零协方差的特征)。
用主成分捕获信号
我们生活在一个信息太多而不是太少的世界。数据科学也是如此——几乎总是有一大堆潜在特征可供我们用来做出预测。
尽管我们很想这样做,但我们不可能把它们都用上。我们只是没有足够的观察值-当我们只有目标变量的 5000 个观察值时,使用 10,000 个特征来拟合模型将是一个可怕的想法。我们最终会得到一个严重过度拟合的模型,一旦我们试图在真实世界中运行该模型(基于真正的样本数据),该模型就会崩溃。
但是如果没有领域专业知识和建模经验(有时即使有),也很难决定哪些特性真正值得保留在我们的模型中。现在,如果有一种算法可以将我们的 10,000 个特征转换成一组理想的特征就好了。
理想的功能集
如果我们可以从头开始创建一个理想的特性集,这个集将具有什么属性?我建议它具有以下三个特性:
- **方差大:**方差大的特征包含了大量的潜在信号——信号(又称有用信息)是建立好模型的基本要求。
- **不相关:**彼此高度相关的特征用处不大,在某些情况下甚至是有害的(当相关性高到导致多重共线性)。要明白为什么会这样,假设你雇佣了一屋子有才华的股票交易者。你希望他们都以相似还是不同的方式投资?如果是我,我更希望他们彼此尽可能不同——这创造了一种多样化的效果,交易者互相保护,避免犯错,你几乎不会遇到所有人同时犯错的情况。
- **没有那么多:**相对于我们的目标变量观测值,我们希望有少量的特征。相对于观察值的太多特征将导致过度拟合模型,该模型在样本外表现不佳。
PCA(主成分分析)给了我们一套理想的特征。它创建一组按方差排序的主成分(第一个成分比第二个成分方差大,第二个成分比第三个成分方差大,依此类推)、不相关、数量少(我们可以丢弃排序较低的成分,因为它们包含的信号很少)。
PCA 是怎么做到的?
那么它是如何产生如此神奇的效果的呢?有线性代数的解释或者直觉的解释。我们在这里会选择直观的,但是如果你想检查数学,这篇博文很棒。
PCA 通过反复询问和回答以下问题来发挥其魔力:
- 在该过程的最开始,PCA 询问特征集中最强的潜在趋势是什么(我们称之为组件 1)?我们将在后面以多种方式形象化这一点,所以如果现在还不清楚,请不要担心。
- 接下来,PCA 询问特征集中第二强的潜在趋势是什么,它恰好与组件 1(我们称之为组件 2)不相关?
- 然后 PCA 询问特征集中第三强的潜在趋势是什么,它恰好与成分 1 和成分 2 都不相关(我们称之为成分 3)?
- 诸如此类…
它是如何发现这些潜在趋势的?如果你曾经谷歌过 PCA,你很可能看到过类似下图的东西:
PCA Simple Example
在图中,我们的数据是黑点。那么最强的潜在趋势是什么呢?我们可以把它当作一个线性回归问题来处理,最强的趋势是最佳拟合线(蓝线)。所以蓝线是组件 1。你可能会问为什么蓝线是分量 1 而不是红线?记住,分量 1 是具有最高方差的主分量(因为最高方差等于最高潜在信号)。
**线性回归连接很有用,因为它帮助我们认识到每个主成分都是单个特征的线性组合。**就像线性回归模型是与我们的目标变量最接近的特征的加权和一样,主成分也是我们特征的加权和。除了在这种情况下,它们是最能表达我们特征集中潜在趋势的加权和。
回到我们的例子,我们可以直观地看到,蓝线比红线捕捉到更多的差异,因为蓝色勾线之间的距离比红色勾线之间的距离长。标记线之间的距离是我们的主成分捕获的方差的近似值-黑点越多,我们的数据沿着主成分的轴变化,它捕获的方差就越多。
现在对于成分 2,我们想要找到第二强的潜在趋势,附加条件是它与成分 1 不相关。在统计学中,相互正交的趋势和数据是不相关的。
Orthogonal Data
看看左边的那块地。我绘制了两个特征,一个用蓝色,另一个用红色。如你所见,它们是相互垂直的。蓝色特征的所有变化都是水平的,而红色特征的所有变化都是垂直的。因此,当蓝色要素改变(水平)时,红色要素完全保持不变,因为它只能垂直改变。
很好,所以为了找到分量 2,我们只需要找到一个方差尽可能大的分量,它也和分量 1 正交。由于我们之前的 PCA 示例非常简单,只有两个维度,因此对于组件 2,我们只有一个选项,即红线。事实上,我们可能有大量的功能,所以当我们搜索组件时,我们需要考虑许多方面,但即使这样,过程也是一样的。
一个将这一切联系在一起的例子
让我们回到之前的股票例子。除了苹果股票,我还下载了代表多个行业的 30 只不同股票的数据。如果我们画出他们所有的日收益(100 天,同上),我们会得到下面这张混乱的图表:
100 Days of Stock Returns for 30 Different Stocks
每只股票都在做自己的事情,除了每天的股票回报是嘈杂和不稳定的之外,从这张图表中没有太多可以收集的信息。让我们使用 sci-kit learn 来计算主成分 1,然后绘制它( PCA 对你的特征的相对比例很敏感——因为我的所有特征都是每日股票回报,我没有缩放数据,但在实践中,你应该考虑使用标准缩放器或最小最大缩放器)。下图中的黑线是组件 1:
Stock Returns with PCA Component 1 (In Black)
所以黑线代表了我们股票回报中最强的潜在趋势。“这是什么?”,你问?好问题,不幸的是,没有一些领域的专业知识,我们不知道。这种解释的损失是使用类似 PCA 的东西来将我们大得多的特征集减少到一个更小的关键底层驱动集的主要缺点。除非我们运气好,或者仅仅是数据专家,否则我们不会知道每一个主成分分析的成分意味着什么。
在这种情况下,我会猜测第一部分是标准普尔 500——在我们所有的股票回报数据中,最强的潜在趋势可能是整体市场,其涨跌会影响每只股票的价格。让我们通过绘制标准普尔 500 的每日收益与第一部分的对比来验证这一点。考虑到数据的噪音,这几乎是完美的匹配!标准普尔 500 指数的日收益率和主成分 1 的相关性为 0.92。
The S&P 500 and Component 1 are Extremely Correlated
**所以就像我们猜测的那样,我们股票数据中最重要的潜在趋势是股票市场。**PCA 的 scikit-learn 实现还告诉我们每个组件解释了多少差异——组件 1 解释了我们特征集中总差异的 38%。
让我们来看看另一个主成分。下面,我绘制了组件 1(黑色)和 3(绿色)。正如所料,它们之间的相关性很低(0.08)。与组件 1 不同,组件 3 仅解释了我们特征集中 9%的差异,远低于组件 1 的 38%。不幸的是,我不知道成分 3 代表什么——这就是 PCA 缺乏解释来咬我们的地方。
Principal Components 1 and 3
结论
在这篇文章中,我们看到了 PCA 如何帮助我们揭示数据中的潜在趋势——这是当今大数据世界中一种非常有用的能力。PCA 很棒,因为:
- 它将潜在信号隔离在我们的特征集中,以便我们可以在我们的模型中使用它。
- 它将大量的特征简化为一组更小的关键潜在趋势。
然而,缺点是当我们通过 PCA 运行我们的特征时,我们失去了许多可解释性。如果没有领域专业知识和大量猜测,我们可能不知道除了前一两个组件之外的任何组件代表什么。
但一般来说,这不是一个交易破坏者。如果您确信在您的大量特征中有充足的信号,那么 PCA 仍然是一种有用的算法,它允许您提取大部分信号用于您的模型,而不必担心过度拟合。
如果你总体上喜欢这篇文章和我的写作,请考虑通过我在这里的推荐链接注册 Medium 来支持我的写作。谢谢!
理解概率。终于!
数据科学家概率概念图解实用指南
虽然您已经使用随机森林、逻辑回归、K-means 聚类、支持向量机甚至深度学习解决了日常生活中的实际问题,但在本次复习结束时,您将能够自信地谈论概率。
本文温和地介绍了数据科学和机器学习背后的概率概念。每一个概念都通过真实的例子被小心地介绍和说明,同时尽可能地避免数学和定理。
作为一名数据科学家,你将最终确定结果、事件、随机变量、概率分布、期望、均值和方差等概念。
本杰明·富兰克林曾经说过,生命中有两件事是确定的:死亡和纳税。
许多人可能会进一步扩展这一主张,断言这些是唯一确定的事情。生活的其余部分是最不可预测的。
然而,我们可以争辩说,即使是不可避免的死亡也可能不像我们想象的那样确定,因为我们并不总是知道它将在何时、何地以及如何发生。
我们如何处理随机现象?凭信念还是凭理智?
信念是对不需要任何证据且被认为无法通过任何经验或理性手段证明的事情的真实性的绝对信念。
理性是思维的能力,通过它我们可以逻辑地得出理性的结论。
虽然信念是冷静处理随机现象的好工具,但理性给了我们学习我们观察到的范围、平均值或可变性的方法。
理性允许我们从更大范围的观察中推断出结构、变化和关系。我们还可以尝试预测未来、可能性,并保证我们对结果有多确定。
推理方法带来了一些好处,比如理解潜在的观察结果,提前计划事情如何变化,以及构建提供最确定结果的东西。
打破不确定性
当我们观察周围的世界并收集数据时,我们正在进行一项实验。一个简单的例子是抛硬币,同时观察它是正面还是反面落地。一个更复杂的例子是试用一个软件,观察我们愿意为它的完整版本支付多少钱。这些实验的所有可能结果构建了所谓的样本空间,表示为ω。
在硬币的例子中,样本空间有两个元素:头部和尾部。这是一个离散样本空间,与软件示例中的连续样本空间形成对比。在后一种情况下,为软件支付的金额可以是任何正实数。在这两种情况下,我们都在进行一个概率实验,结果未知 X ,也称为随机变量。
在抛硬币实验中,如果硬币正面落地, X 将得到值 h ,否则将得到值 t 。如果实验重复多次,我们通常将随机变量 X 得到小 x 的概率定义为小 x 发生的次数的分数。
概率作为一个函数
在抛硬币的情况下,如果我们用一枚公平的硬币进行足够长时间的实验,几乎无数次,我们将期望正面或反面落地的概率是 1/2。头部或尾部着地的概率都是非负的,总和为 1。
因此,我们可以将概率视为一个函数,它获取一个结果,即样本空间中的一个元素,并将该结果映射到一个非负实数,使得所有这些数字的总和等于 1。我们也把这个叫做概率分布函数。
当我们知道了样本空间和概率分布,我们就有了实验的完整描述,我们可以对不确定性进行推理。
在均匀概率分布中,样本空间中的所有结果将具有相同的发生概率。我们的抛硬币案例就是这样一个例子。另一个例子是纸牌游戏,从一副标准的 52 张牌中挑出一张给定牌的概率是 1/52。
非均匀分布将不同的概率映射到不同的结果或一组结果。下面的饼图展示了一个很好的例子,其中每个类别代表一个结果,比例代表概率。
当出现多种结果时
一组结果也被称为概率事件。比如我们掷骰子,所有偶数的集合就是一个事件。如果包含已发生的结果,则发生事件。因此,如果我们掷出数字 4,事件就发生了。
当实验重复多次时,事件的概率将是事件发生次数的分数。例如,如果我们投掷骰子 10 次,我们得到以下数字 5,3,2,3,2,1,4,6,5,2,那么,奇数事件的概率是 5/10=1/2。
如果我们重复掷骰子无数次,则事件的概率将是我们得到偶数的次数的分数,这是 2、4 或 6 次发生的分数之和,这给出了 1/6+1/6+1/6=3/6=1/2。
替换或不替换的采样
有时我们可能想多次重复同样的经历,就像抛硬币一样。当重复实验时,一个常见的假设是一个实验的结果对其他实验的结果没有任何影响。换句话说,这些实验是独立的。
在现实生活中,我们经常从更大的人群中选择事物并进行分析。有以下两种方法:带替换的采样,不带替换的采样。
当我们用替换顺序选择时,结果可以重复,实验是独立的。
在下面的例子中,我们从一个装有两个球的瓶子中取出两个球,一个是黄色的,另一个是蓝色的。
如果我们从罐子里选择了没有替换的,结果就不会重复,实验也是相互依赖的。
在迄今为止的所有例子中,顺序确实很重要。有时候顺序并不重要,我们只关心集合。我们不是处理可能结果的元组,而是处理可能结果的集合。
例如,如果我们抛硬币两次,我们可以得到(正面,反面)或(反面,正面)的结果。当顺序无关紧要时,两种结果都会构建事件{(head,tail),(tail,head)}。
现在让我们看看另一种情况,我们从一副有 6 张替换牌的牌中选择 2 张。当顺序很重要时,我们有一个均匀分布,这意味着所有可能的元组(1,1),(1,2),…,(6,6,)都有相同的出现概率 1/36。
然而,当顺序无关紧要时,分布不再均匀,如下图所示。
让我们重复同样的实验,这次不用替换。现在,分布是均匀的,如下所示。顺序和抽样方法会对概率产生重大影响。
当结果是数字时
一个随机变量将是一个随机结果,其值是数字。研究数字(随机变量)而不是结果有几个好处。
我们可以控制头的数量、尾的数量或者两者之和,而不是把头和尾看作事件。此外,随机变量的概率可以显示在图中,也可以表示为具有特定属性的函数。
随机变量从样本空间中取值。如果样本空间是有限的,比如一组数{1,2,3},那么我们处理的是一个离散随机变量。
当样本空间是无限的,比如ℝ,那么我们就有了一个连续随机变量。下面你可以看到一个离散随机变量的例子,它描述了在一个投掷三枚公平硬币的实验中的人头数。
上面的表格和图表描述了一个离散随机变量的概率质量函数(PMF) ,它也可以用直方图表示。
在连续随机变量的情况下,显然 PMF 不可能是一致的,也不可能是增函数。然而,它可以是一个递减函数或双无限函数。
区间概率
有时我们对计算随机变量的概率感兴趣,这个随机变量的值小于或等于某个阈值。
累积分布函数(CDF) 允许我们操纵区间概率,如下例所示。
我们应该期待什么
为了研究随机变量的不确定性,我们还对可能值的范围、最小值和最大值、范围平均值、元素平均值和样本平均值感兴趣。
特别是,样本均值是我们将来可能观察到的平均值的期望值。样本均值或期望值是一个随机变量的平均值,按概率加权。
下面我们有一个温度分布,大多数时间观察到 0 度。在 10 个温度值的样本中,我们获得样本平均值 19 度,这告诉我们可能观察到的平均温度的概念。
当样本数量趋于无穷大时,我们期望看到一个称为随机变量的期望值或均值的值,如下所示。也表示为 μ 。
预期寿命实际上是期望的一个很好的例证。下面我们可以看到发达国家人口统计数字的预期在 50 年内是如何变化的。
source (2017)
与期望值的差异
在我们知道期望什么之后,我们现在可以看看随机变量将如何不同于期望值或均值。具有相同平均值的两个样本可能看起来非常不同。
这些变化由随机变量的方差捕获,该方差被定义为随机变量 X 与其均值 μ 之间的绝对差的期望值。
因为绝对值不容易分析,尤其是它的微分在零处是不可能的,所以方差是使用差的平方来计算的。
标准偏差被定义为方差的平方根。
当所有事情同时发生时
在现实生活中,样本空间是多个变量的组合。如果我们要向学生发送广告,我们可能要考虑他们的年级、学习年份和专业,以便向每个特定的受众传递正确的信息。
因此,给定 2 个或更多随机变量,我们考虑所谓的联合分布,其给出变量在样本空间中可以取的每个可能的值元组的概率。
联合分布足以计算边际概率,即单个变量取特定值的概率。
我们应该知道什么
随机变量通常属于流行的分布的族,它们被很好地研究过,并给我们的生活带来便利。
与连续分布相比,离散分布描述了具有可数结果的随机变量。
最简单的非平凡随机变量将取两个可能的值,例如 1 和 0,代表成功和失败,或头和尾。伯努利分布定义了成功概率 p ,以及失败概率 q=1-p 。当进行几个这样的独立二元实验时,给定结果序列的概率很容易计算为各个概率的乘积。
二项式分布定义了 n 次独立伯努利试验中特定次数成功的概率,其中成功概率为 p 。它可用于研究临床试验中的阳性应答者数量,或成功转化为付费客户的营销线索数量。
泊松二项式分布是二项式分布的变体,其中成功的概率是不固定的。
泊松分布近似二项式分布,表示成功的小概率 p 对于大量的试验 n. 例如,研究患有罕见疾病感染的人数、点击广告的网站访问者人数、垃圾邮件回复数、每日紧急呼叫数、全市人口中逛商店的人数、购买画作的画廊访问者人数非常有用。
当我们以成功概率 p 重复独立的伯努利试验时,直到我们观察到成功的试验次数 n 遵循几何分布。例如,侵入计算机系统之前所需的黑客攻击次数和成功开展业务之前的创业公司数量将呈几何分布。
在许多情况下,随机变量的值来自一个无限的空间,有无数个可能的值。例如,诸如飞行持续时间、寿命持续时间的时间变量,或者诸如表面和高度的空间相关变量;或者与质量相关的变量,例如重量;或者温度。
这里甚至可以考虑几乎可数的数量,如房屋成本、产品价格、利率和失业率。
在所有这些情况下,我们不再使用概率质量函数。相反,我们应该使用所谓的概率密度函数(PDF) 。
指数分布将几何分布扩展为连续值。例如,电话呼叫的持续时间、客户拨打客户服务热线的等待时间以及车辆的寿命都可以用参数为λ的指数分布来描述。
最流行的是正态或高斯分布,这通常适用于添加许多独立因素的情况。它描述了已知平均值和标准差的值的概率。例子包括人们的身高和体重,以及工资。
下表总结了连续分布的概率密度函数、均值和方差。
结论
在本文中,我们提供了大多数数据科学家想要掌握的概率概念的通俗易懂的复习。我们超越了掷骰子,直观地说明了如何使用结果、事件、样本空间、分布函数、累积函数、随机变量、期望和方差来量化和分析不确定性。
在这篇文章中,我们使用了加州大学圣地亚哥分校的 Alon Orlitsky 教授的教学材料。他和 AdaBoost 算法的发明者 Yoav Freund 教授一起提供了一个关于概率和统计的免费课程。如果您想更深入地了解本文介绍的概念,我们强烈建议您参加本课程。
条件概率不包括在内;也不是贝叶斯方法的本质特征,贝叶斯方法的本质特征是在基于统计数据分析的推理中明确使用概率来量化不确定性。
我们在下面的文章中温和地介绍贝叶斯数据分析。
通过 Python PyMC3 中的示例和代码对贝叶斯数据分析进行了温和的介绍。
towardsdatascience.com](/bayesian-nightmare-how-to-start-loving-bayes-1622741fa960)
感谢阅读。
了解 Python 虚拟环境
使用 VR 的 Python 虚拟环境介绍
A man is using three screens at once. Photo by Maxim Dužij on Unsplash
不,看这篇文章不需要 VR 眼镜。只要一袋注意力和兴奋就够了。
如果您是数据科学和 python 领域的新手,虚拟环境可能看起来是一个非常复杂的想法——但事实恰恰相反。它简单易懂,使用起来更简单!如果你以前体验过虚拟现实(VR),你也会有一个良好的开端(例如,玩虚拟现实游戏或出于任何目的尝试虚拟现实眼镜)。这篇文章涵盖了从什么是环境到设置和使用虚拟环境的所有内容!
什么是环境?
人类的环境可以指他们的周围环境——他们居住的地方。对于像 python 这样的编程语言来说,环境的概念是类似的。对于安装在计算机上的 python,计算机就是它的环境。它通常被称为“本地”环境,因为语言也可以在服务器上运行(服务器只是在数据中心运行的计算机,可以通过互联网访问)。
什么是虚拟环境?
你体验过虚拟现实或者看到有人体验过吗?如果没有,这里有一个例子:
Photo by Hammer & Tusk on Unsplash
回想一下人类对环境的定义——他们的周围环境。对于上面照片中的人来说,他们的周围就是他通过镜头看到的东西。对他来说,现实的概念(在一定程度上)已经从他用肉眼看到的变成了他现在感知到的。
python 的虚拟环境是一个类似的想法:你在一个环境(你的计算机)中给它一个单独的“环境”,并允许它在那里运行。
然而,人类的虚拟现实体验和 python 的虚拟现实体验之间有一些关键的区别。首先,你可以为 python 创建多个虚拟环境(你可以认为 python 有无限多的脸,而人类只有一张脸,所以 python 可以戴任意多的 VR 眼镜)。其次,虽然人类仍然可以触摸和感受“真实”环境中的物体,但 python 不一定能做到这一点。当你戴上 python 的 VR 眼镜时,它可能会也可能不会访问“真实”的环境(取决于你的设置,对于这篇文章来说有点太高级了)。因此,在虚拟环境中,安装在您的计算机上的所有库和包(也称为真实环境)都是不可访问的,反之亦然。
我为什么要关心?
好吧,这听起来很酷,但是虚拟环境的目的是什么?为什么要把酷炫的 VR 眼镜交给枯燥复杂的编程语言?
**方便。稳定。安心。**不是为了编程语言,而是为了作为程序员的你。注意不同的项目可能需要不同版本的库,甚至可能需要不同的语言?这就是虚拟环境有用的地方。这个想法是为每个项目创建一个单独的虚拟环境。
因为一种语言的资源(库、包等)对真实环境和其他虚拟环境是隐藏的,所以版本之间没有干扰和冲突。例如,假设您在项目 A 的虚拟环境中更新了库 X。因为您在项目 A 的虚拟环境中更新了库 X,所以您可以确保这不会导致库 X 在任何其他环境中更新。因此,您的代码及其在其他地方的依赖项不受影响。
好的,我如何制作一个虚拟环境?
对于这个故事,我将介绍 virtualenv 库,但是有许多不同的(也许更好的)方法来创建 python 虚拟环境。我还将使用 Mac,这是一个基于 UNIX 的系统。因此,如果您的计算机使用不同的系统,步骤可能会有所不同。
您的系统上可能没有安装 virtualenv。您可以使用以下命令进行检查:
virtualenv --version
如果您安装了 virtualenv,“终端”将打印您系统上安装的 virtual env 版本(如 16.4.3)。否则,它会给你一个“命令未找到”的错误。如果是这种情况,请输入以下命令:
pip install virtualenv
PS:如果你没有 pip ,可以为你的系统下载。默认情况下,它包含在 Python 3.4 或更高版本的二进制安装程序中。
接下来,导航到您想要创建虚拟环境的目录。您可以只为环境创建另一个目录,并使用以下命令切换到该目录:
mkdir Environments && cd Environments
这将创建一个名为“环境”的新文件夹,并在终端中打开该目录。您现在已经准备好为 python 创建 VR 体验了。要创建环境,请使用以下命令
virtualenv project_name
上面的命令在当前工作目录中创建了一个名为“project_name”的环境。将“项目名称”替换为您的项目名称。完成上述步骤后,您的终端窗口应该看起来像这样:
Terminal screen after creating the virtual environment.
但是,虚拟环境尚未激活。每当您想要进入环境或激活它(也就是戴上眼镜)时,您需要运行以下命令:
source project_name/bin/activate
Tada!你现在在你的虚拟环境中。你如何确认你确实在一个虚拟环境中?您的终端将在所有行中以 project_name 为前缀,如下所示:
Terminal screen after activating python virtual environment
就是这样!您已经成功学习、创建并激活了一个 python 虚拟环境。现在,您可以在这个虚拟环境中处理您的项目。您可以像在普通终端上一样安装库,只是这些库将安装在虚拟环境中,而不是“真实”环境中。您可以使用以下命令退出虚拟环境,以“注销”虚拟环境:
deactivate
进一步学习的资源:
你还没有完全发现虚拟环境的世界。您可能想探索许多有趣的事情:
这个故事比较了不同的 python 虚拟环境工具(回想一下,我只展示了 virtualenv):
设置 Python 虚拟环境的各种工具
towardsdatascience.com](/comparing-python-virtual-environment-tools-9a6543643a44)
这是一篇非常适合虚拟环境专家的文章:
2019 年 1 月补充:如果你在升级到 macOS Mojave 后回到这个博客,请查看这个 github 问题…
medium.freecodecamp.org](https://medium.freecodecamp.org/manage-multiple-python-versions-and-virtual-environments-venv-pyenv-pyvenv-a29fb00c296f)
以下文章讨论了与虚拟环境相关的最新发展:
[## 再见虚拟环境?
如果您是 Python 开发人员,您可能听说过虚拟环境—“一个自包含的目录树…
medium.com](https://medium.com/@grassfedcode/goodbye-virtual-environments-b9f8115bc2b6)
我希望这篇文章能帮助您理解什么是虚拟环境,并让您开始使用它们。如果您有任何问题,请不要犹豫回应这个故事或联系我。祝 Python 虚拟现实愉快!
通过示例了解 PyTorch:分步指南
Photo by Allen Cai on Unsplash
更新(2021 年 5 月 18 日):今天我已经完成了我的书: 深度学习用 PyTorch 循序渐进:入门指南 。
更新(2022 年 2 月 23 日):平装版现已上市(三册)。更多详情请查看pytorchstepbystep.com。
更新(2022 年 7 月 19 日):第一卷《基础》的西班牙语版现已在 Leanpub 上发布。
介绍
PyTorch 是发展最快的深度学习框架, Fast.ai 在其 MOOC、深度学习 for Coders 及其库中也使用了该框架。
PyTorch 也非常 Python 化,也就是说,如果你已经是 Python 开发者,使用它会感觉更自然。
此外,根据安德烈·卡帕西的说法,使用 PyTorch 甚至可以改善你的健康状况
动机
有很多 PyTorch 教程,它的文档非常完整和广泛。所以,为什么要一直看这个循序渐进的教程?
好吧,尽管人们可以找到关于 PyTorch 能做的几乎所有事情的信息,但我错过了从基本原理的结构化方法中得到结构化的方法。
在这篇文章中,我将引导你了解 PyTorch 使用 Python 构建深度学习模型变得更加简单和直观的主要原因,包括亲笔签名的模型、动态计算图、模型类和更多,我还将向你展示如何避免常见的陷阱
此外,由于这是一篇很长的文章,我建立了一个 T2 目录 T3,如果你把它作为 T4 的迷你课程 T5,一次一个主题地浏览内容,会使浏览更容易。
目录
简单的回归问题
大多数教程都是从一些好看的图像分类问题开始,来说明如何使用 PyTorch。这看起来很酷,但我相信它会分散你对主要目标的注意力。
为此,在本教程中,我将坚持使用一个简单的和熟悉的问题:一个线性回归 与一个单一特征 x !没有比这更简单的了…
Simple Linear Regression model
数据生成
让我们开始生成一些合成数据:我们从我们的特征 x 的 100 个点的向量开始,并使用 a = 1 、 b = 2 和一些高斯噪声来创建我们的标签。
接下来,让我们将我们的合成数据分割成训练和验证集合,洗牌指数数组并使用前 80 个洗牌点进行训练。
Generating synthetic train and validation sets for a linear regression
Figure 1: Synthetic data — Train and Validation sets
我们知道a = 1 和 b = 2,但是现在让我们看看通过使用梯度下降和训练 集合中的 80 个点,我们能多接近真实值…
梯度下降
如果你对梯度下降的内部工作方式很熟悉,可以跳过这一部分。完全解释梯度下降是如何工作的已经超出了这篇文章的范围,但是我将涵盖计算梯度下降需要经历的四个基本步骤。
第一步:计算损失
对于一个回归问题,损失由均方差(MSE) 给出,即标签 (y)和预测 (a + bx)之间所有平方差的平均值。
值得一提的是,如果我们使用训练集中的所有点(N)来计算损失,我们正在执行批次梯度下降。如果我们每次都使用一个单点,这将是一个随机梯度下降。在 1 和 N 之间的任何其他(n) 表示小批量梯度下降。
Loss: Mean Squared Error (MSE)
第二步:计算梯度
一个梯度是一个偏导数 — 为什么 偏导数?因为它是相对于(w.r.t .)一个单个 参数来计算的。我们有两个参数, a 和 b ,所以必须计算两个偏导数。
一个导数告诉你一个给定的 量变化多少当你稍微* 变化一些其他量。在我们的例子中,当我们改变两个参数中的每一个时,我们的 MSE 损耗变化了多少?*
下面等式的最右边的部分是你通常在简单线性回归的梯度下降实现中看到的。在中间步骤中,我向您展示应用链式法则弹出的所有元素,这样您就知道最终表达式是如何产生的。
Computing gradients w.r.t coefficients a and b
步骤 3:更新参数
在最后一步,我们使用渐变来更新参数。由于我们试图最小化我们的损失,我们反转梯度的符号用于更新。
还有另一个需要考虑的参数:学习率**,用希腊字母 eta (看起来像字母 n )表示,这是我们需要应用于梯度的乘法因子,用于参数更新。**
Updating coefficients a and b using computed gradients and a learning rate
如何选择一个学习率?这是一个独立的话题,也超出了本文的范围。
第四步:冲洗,重复!
现在我们使用更新的** 参数返回到步骤 1 并重新开始该过程。**
每当每个点已经用于计算损失时,一个历元完成。对于批次梯度下降,这是微不足道的,因为它使用所有点来计算损失——一个历元与一个更新相同。对于随机梯度下降,一个历元表示 N 个 更新,而对于小批量**(尺寸 N),一个历元有 N/n 个更新。**
一遍又一遍地重复这个过程,对于许多时代**,简而言之就是训练一个模型。**
数字线性回归
是时候使用 Numpy only 使用梯度下降实现我们的线性回归模型了。
等一下…我以为这个教程是关于 PyTorch 的!
是的,但这有两个目的:第一,介绍我们任务的结构,它将基本保持不变,第二,向您展示主要的难点,这样您就可以充分体会 PyTorch 让您的生活变得多么轻松:-)****
为了训练一个模型,有两个初始化步骤:
- 参数/权重的随机初始化(我们只有两个, a 和 b ) —第 3 行和第 4 行;
- 超参数的初始化(在我们的例子中,只有学习率和周期数 ) —第 9 行和第 11 行;****
确保总是初始化你的随机种子,以确保你的结果的再现性。像往常一样,随机种子是 42 ,一个人可能选择的所有随机种子中最不随机的:)**
对于每个时期,有四个训练步骤😗***
- 计算模型的预测—这是正向传递 —第 15 行;
- 使用预测和标签和手头任务的适当损失函数计算损失——第 18 和 20 行;****
- 计算每个参数的梯度——第 23 和 24 行;
- 更新参数—第 27 行和第 28 行;
只要记住,如果你不使用批量梯度下降(我们的例子使用了),你将不得不写一个内循环来执行四个训练步骤用于每个个体点 ( 随机)或 n 点 ( 小批量)。稍后我们将看到一个小批量的例子。**
Implementing gradient descent for linear regression using Numpy
只是为了确保我们没有在代码中犯任何错误,我们可以使用 Scikit-Learn 的线性回归来拟合模型并比较系数。
**# a and b after initialization
[0.49671415] [-0.1382643]
# a and b after our gradient descent
[1.02354094] [1.96896411]
# intercept and coef from Scikit-Learn
[1.02354075] [1.96896447]**
它们匹配多达 6 位小数——我们有一个使用 Numpy 的完全工作的线性回归实现。**
时间到了火炬它:-)
PyTorch
首先,我们需要涵盖一些基本概念,如果你在全力建模之前没有很好地掌握它们,这些概念可能会让你失去平衡。
在深度学习中,我们随处可见张量。嗯,Google 的框架叫 TensorFlow 是有原因的!究竟什么是张量?
张量
在 Numpy 中,你可能有一个数组有三个维度吧?也就是从技术上来说,一个张量。
一个标量(单个数)有个零维,一个向量 有 个维,一个矩阵有两个维,一个张量有三个或更多个维。就是这样!
但是,为了简单起见,通常也称向量和矩阵为张量——所以,从现在开始,所有东西要么是标量,要么是张量。
Figure 2: Tensors are just higher-dimensional matrices 😃 Source
加载数据、设备和 CUDA
你会问,我们如何从 Numpy 的数组到 PyTorch 的张量?这就是[**from_numpy**](https://pytorch.org/docs/stable/torch.html#torch.from_numpy)
的好处。不过,它返回一个 CPU 张量。
“但是我想用我的花式 GPU… ”你说。别担心,这就是[**to()**](https://pytorch.org/docs/stable/tensors.html#torch.Tensor.to)
的用处。它将你的张量发送到你指定的任何设备,包括你的 GPU (简称cuda
或cuda:0
)。
"如果没有可用的 GPU,我希望我的代码回退到 CPU,该怎么办?你可能想知道……py torch 又一次支持了你——你可以使用[**cuda.is_available()**](https://pytorch.org/docs/stable/cuda.html?highlight=is_available#torch.cuda.is_available)
来查找你是否有 GPU 供你使用,并相应地设置你的设备。
您还可以使用[float()](https://pytorch.org/docs/stable/tensors.html#torch.Tensor.float)
轻松地将转换为较低的精度(32 位浮点)。
Loading data: turning Numpy arrays into PyTorch tensors
如果您比较两个变量的类型,您将得到您所期望的:第一个变量为numpy.ndarray
,第二个变量为torch.Tensor
。
但是你的张量“住”在哪里呢?在你的 CPU 还是你的 GPU 里?你不能说……但是如果你用 PyTorch 的**type()**
,它会揭示它的位置 — torch.cuda.FloatTensor
—这里是一个 GPU 张量。
我们也可以反过来,使用[**numpy()**](https://pytorch.org/docs/stable/tensors.html?highlight=numpy#torch.Tensor.numpy)
,将张量转换回 Numpy 数组。应该和x_train_tensor.numpy()
一样简单,但是 …
**TypeError: can't convert CUDA tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first.**
不幸的是,Numpy 不能处理 GPU 张量……你需要先用[**cpu()**](https://pytorch.org/docs/stable/tensors.html#torch.Tensor.cpu)
让它们成为 CPU 张量。
创建参数
用于数据的张量与用作(可训练 ) 参数/权重的张量有何区别?****
后面的张量需要计算其梯度,因此我们可以更新它们的值(即参数值)。这就是**requires_grad=True**
论点的好处。它告诉 PyTorch 我们希望它为我们计算梯度。
你可能想为一个参数创建一个简单的张量,然后发送到你选择的设备,就像我们对数据所做的那样,对吗?没那么快…
Trying to create variables for the coefficients…
第一段代码为我们的参数、梯度和所有的东西创建了两个很好的张量。但他们是 CPU 张量。
**# FIRST
tensor([-0.5531], requires_grad=True)
tensor([-0.7314], requires_grad=True)**
在第二段代码中,我们尝试了将它们发送到 GPU 的简单方法。我们成功地将它们发送到了另一个设备上,但是我们不知何故“丢失”了梯度
**# SECOND
tensor([0.5158], device='cuda:0', grad_fn=<CopyBackwards>) tensor([0.0246], device='cuda:0', grad_fn=<CopyBackwards>)**
在第三块中,我们首先将我们的张量发送到设备和,然后**使用[**requires_grad_()**](https://pytorch.org/docs/stable/tensors.html#torch.Tensor.requires_grad_)
方法将其requires_grad
设置到True
位置。**
**# THIRD
tensor([-0.8915], device='cuda:0', requires_grad=True) tensor([0.3616], device='cuda:0', requires_grad=True)**
在 PyTorch 中,每一个以一个下划线 ( _ )结束的方法都会就地修改,也就是说,它们将修改底层变量。****
虽然最后一种方法工作得很好,但是最好是在创建时将张量分配给设备**。**
Actually creating variables for the coefficients 😃
tensor([0.6226], device='cuda:0', requires_grad=True) tensor([1.4505], device='cuda:0', requires_grad=True)
简单多了,对吧?
现在我们知道了如何创建需要梯度的张量,让我们看看 PyTorch 如何处理它们——这是……
亲笔签名
亲笔签名的是 PyTorch 的自动微分包。多亏了它,我们不需要担心关于偏导数,链式法则或者任何类似的东西。
那么,我们如何告诉 PyTorch 去做它的事情并且计算所有的梯度**?这就是[**backward()**](https://pytorch.org/docs/stable/autograd.html#torch.autograd.backward)
的好处。**
你还记得计算梯度的起点吗?当我们计算它相对于我们的参数的偏导数时,它是损失。因此,我们需要从相应的 Python 变量中调用backward()
方法,比如loss.backward().
****坡度的实际值是多少?我们可以通过查看张量的[**grad**](https://pytorch.org/docs/stable/autograd.html#torch.Tensor.grad)
属性来考察它们。
如果你检查该方法的文档,它清楚地说明了梯度是累积的**。所以,每次我们使用渐变到更新参数时,我们需要将渐变归零。这就是[**zero_()**](https://pytorch.org/docs/stable/tensors.html#torch.Tensor.zero_)
的好处。**
方法名末尾的下划线** ( _ )是什么意思?你还记得吗?如果没有,请返回到上一节并找出答案。**
因此,让我们放弃****手动** 计算梯度并使用backward()
和zero_()
方法来代替。**
就这样?嗯,差不多吧…但是,总有一个抓手**,这次和参数的更新有关…**
在第一次尝试中,如果我们使用与我们的 Numpy 代码中相同的更新结构,我们将会得到下面奇怪的错误**…但是我们可以通过查看张量本身得到一个关于正在发生什么的提示——我们再次**“丢失”梯度,同时将更新结果重新分配给我们的参数。因此,**grad**
属性变成了**None**
,并引发了错误…
# FIRST ATTEMPT
tensor([0.7518], device='cuda:0', grad_fn=<SubBackward0>)
AttributeError: 'NoneType' object has no attribute 'zero_'
然后我们稍微改变一下,在第二次尝试中使用熟悉的就地 Python 赋值**。PyTorch 再一次对此进行了抱怨,并引发了一个错误。**
# SECOND ATTEMPT
RuntimeError: a leaf Variable that requires grad has been used in an in-place operation.
为什么?!原来是“过犹不及”的情况。罪魁祸首是 PyTorch 从每一个 Python 操作中构建一个动态计算图的能力,该操作涉及任何梯度计算张量或其依赖关系**。**
我们将在下一节更深入地研究动态计算图的内部工作原理。
那么,我们如何告诉 PyTorch 去“后退”并让我们更新我们的参数而不弄乱它的花哨的动态计算图?这就是[**torch.no_grad()**](https://pytorch.org/docs/stable/autograd.html#torch.autograd.no_grad)
的好处。它允许我们独立于 PyTorch 的计算图**对张量、执行常规的 Python 操作。**
最后,我们成功地运行了我们的模型,并获得了结果参数**。毫无疑问,它们与我们在 Numpy 唯一实现中得到的相匹配。**
# THIRD ATTEMPT
tensor([1.0235], device='cuda:0', requires_grad=True) tensor([1.9690], device='cuda:0', requires_grad=True)
动态计算图
“不幸的是,没有人能被告知动态计算图是什么。你得自己去看。”摩耳甫斯
"黑客帝国"有多伟大?对吧。但是,玩笑归玩笑,我想让你到也自己看看图表**!**
PyTorchViz 包和它的
*make_dot(variable)*
方法允许我们轻松地可视化与给定 Python 变量相关的图形。
因此,让我们坚持使用最小量**:两个(梯度计算 ) 张量用于我们的参数、预测、误差和损失。**
Computing MSE in three steps
如果我们调用**make_dot(yhat)**
,我们将得到下面图 3 中最左边的****图😗***
Figure 3: Computation graph for every step in computing MSE
让我们仔细看看它的组件:
- 蓝框:这些对应于我们用作参数的张量,我们要求 py torch计算梯度的那些张量;****
- 灰框:一个 Python 操作,涉及一个梯度计算张量或它的依赖;****
- 绿框:与灰框相同,除了它是计算渐变的起点(假设
**backward()**
方法是从用于可视化图形的变量调用的)——它们是从图形中的自下而上开始计算的。****
如果我们为**error**
(中间)和**loss**
(右边)变量绘图,它们与第一个变量唯一的区别是中间步骤** ( 灰色方框)的数量。**
现在,仔细看看最左边图的绿框**:有两个 箭头指向它,既然是加上两个 变量、a
和b*x
。似乎很明显,对吧?**
然后再看同图的灰色** 方框:它正在执行一个乘法,即b*x
。但是只有一个箭头指向它!箭头来自与我们的参数 b 对应的蓝色 框。**
为什么没有一个盒子放我们的数据 x ?答案是:我们不为它计算梯度!因此,尽管计算图执行的操作中涉及到更多的张量,但它仅显示了梯度计算张量和的依赖关系。**
如果我们为我们的参数 a** 设置**requires_grad**
为**False**
,计算图会发生什么?**
Figure 4: now variable a does NOT have its gradient computed anymore. But it is STILL used in computation
不出所料,参数 a 对应的蓝框没了!很简单:没有渐变,没有图形。
关于动态计算图的最好的事情是,你可以让它如你所愿一样复杂。你甚至可以使用控制流语句(例如 if 语句)来控制渐变的流**(显然!) 😃**
下面的图 5 显示了一个例子。是的,我知道计算本身完全是无意义的
Figure 5: Complex computation graph just to make a point 😃
【计算机】优化程序
到目前为止,我们一直在使用计算出的梯度手动更新参数。这对两个参数的来说可能没问题……但是如果我们有一大堆参数呢?!我们使用 PyTorch 的优化器**之一,比如 SGD 或者 Adam 。******
一个优化器接受我们想要更新的参数,我们想要使用的学习率(可能还有许多其他超参数!)和执行通过其[**step()**](https://pytorch.org/docs/stable/optim.html#torch.optim.Optimizer.step)
方法更新。
此外,我们也不再需要一个接一个地将梯度归零。我们只需调用优化器的[**zero_grad()**](https://pytorch.org/docs/stable/optim.html#torch.optim.Optimizer.zero_grad)
方法,仅此而已!
在下面的代码中,我们创建一个随机梯度下降 (SGD)优化器来更新我们的参数 a 和 b 。
不要被优化器的名字所迷惑:如果我们一次使用所有训练数据进行更新——正如我们在代码中实际做的那样——优化器正在执行一个批处理梯度下降,尽管它的名字如此。
PyTorch’s optimizer in action — no more manual update of parameters!
让我们检查我们的两个参数,之前和之后,只是为了确保一切仍然工作正常:
# BEFORE: a, b
tensor([0.6226], device='cuda:0', requires_grad=True) tensor([1.4505], device='cuda:0', requires_grad=True)
# AFTER: a, b
tensor([1.0235], device='cuda:0', requires_grad=True) tensor([1.9690], device='cuda:0', requires_grad=True)
酷!我们已经优化了优化流程:-)还剩下什么?
失败
我们现在处理损失计算**。不出所料,PyTorch 又一次掩护了我们。根据手头的任务,有许多损失函数可供选择。由于我们的是回归,我们使用的是均方误差(MSE)损失。**
注意
*nn.MSELoss*
实际上为我们创建了一个损失函数**—它不是损失函数本身。此外,您可以指定一个要应用的归约方法,即您希望如何合计单个点的结果 —您可以将它们平均(归约= ‘均值’)或简单地将它们相加(归约= ‘总和’)。**
然后我们使用创建的损失函数,在第 20 行,计算给定我们的预测和我们的标签的损失。
我们的代码现在看起来像这样:
PyTorch’s loss in action — no more manual loss computation!
此时,只剩下一段代码需要修改:预测。然后是时候介绍 PyTorch 实现一个…****
模型
在 PyTorch 中,一个模型由一个从 模块 类继承的常规 Python 类表示。
它需要实现的最基本的方法是:
**__init__(self)**
: 它定义了组成模型的部件——在我们的例子中,是两个参数、 a 和 b 。**
您不局限于定义参数,尽管……模型也可以包含其他模型(或层)作为其属性,因此您可以轻松地嵌套它们。我们很快也会看到这样的例子。
[**forward(self, x)**](https://pytorch.org/docs/stable/nn.html#torch.nn.Module.forward)
:在给定输入 x 的情况下,执行实际计算,即输出预测。
不过,你不应该把叫做
***forward(x)***
方法。你应该调用整个模型本身,就像在***model(x)***
中执行正向传递和输出预测一样。
让我们为我们的回归任务建立一个合适的(然而简单的)模型。它应该是这样的:
Building our “Manual” model, creating parameter by parameter!
在__init__
方法中,我们定义了我们的两个参数、 a 和 b ,使用[**Parameter()**](https://pytorch.org/docs/stable/nn.html#torch.nn.Parameter)
类告诉 PyTorch 这些张量应该被认为是模型的参数,它们是的一个属性。
我们为什么要关心这个?通过这样做,我们可以使用我们模型的[**parameters()**](https://pytorch.org/docs/stable/nn.html#torch.nn.Module.parameters)
方法来检索所有模型参数、甚至嵌套模型的那些参数的迭代器,我们可以使用这些参数来馈送我们的优化器(而不是我们自己构建一个参数列表!).
此外,我们可以使用我们模型的[**state_dict()**](https://pytorch.org/docs/stable/nn.html#torch.nn.Module.state_dict)
方法获得所有参数的当前值。
重要的:我们需要将我们的模型发送到数据所在的同一个设备。如果我们的数据是由 GPU 张量构成的,那么我们的模型也必须“生活”在 GPU 中。****
我们可以使用所有这些方便的方法来更改我们的代码,代码应该是这样的:
PyTorch’s model in action — no more manual prediction/forward step!
现在,打印出来的语句看起来像这样——参数 a 和 b 的最终值仍然相同,所以一切正常:-)
**OrderedDict([('a', tensor([0.3367], device='cuda:0')), ('b', tensor([0.1288], device='cuda:0'))])
OrderedDict([('a', tensor([1.0235], device='cuda:0')), ('b', tensor([1.9690], device='cuda:0'))])**
我希望你注意到了代码中的一个特殊语句,我给它分配了一个注释“这是什么?!?"— **model.train()**
。****
在 PyTorch 中,模型有一个
[***train()***](https://pytorch.org/docs/stable/nn.html#torch.nn.Module.train)
方法,有点令人失望的是,没有执行训练步骤。其唯一目的是将模型设置为训练模式。为什么这很重要?例如,一些模型可能使用类似 退出 的机制,这些机制在培训和评估阶段具有不同的行为。
嵌套模型
在我们的模型中,我们手动创建了两个参数来执行线性回归。让我们使用 PyTorch 的 线性 模型作为我们自己的属性,从而创建一个嵌套模型。
尽管这显然是一个虚构的例子,因为我们几乎包装了底层模型,而没有添加任何有用的东西(或者根本没有!)对它来说,它很好地说明了这个概念。
在**__init__**
方法中,我们创建了一个属性,它包含了我们的嵌套 **Linear**
模型。
在**forward()**
方法中,我们调用嵌套模型本身来执行正向传递(注意,我们是 而不是 调用 *self.linear.forward(x)*
!)。
Building a model using PyTorch’s Linear layer
现在,如果我们调用这个模型的**parameters()**
方法, PyTorch 将以递归的方式计算其属性的参数。您可以自己尝试使用类似于:[*LayerLinearRegression().parameters()]
的代码来获得所有参数的列表。你也可以添加新的Linear
属性,即使你在向前传球中根本不使用它们,它们也会仍然列在parameters()
下。
序列模型
我们的模型足够简单…您可能会想:“为什么还要费心为它构建一个类呢?!"嗯,你说得有道理…**
对于使用普通层的简单模型,其中一层的输出作为输入被顺序地馈送到下一层,我们可以使用一个,呃… 顺序 模型:-)
在我们的例子中,我们将建立一个带有单个参数的序列模型,也就是我们用来训练线性回归的Linear
层。该模型将如下所示:
**# Alternatively, you can use a Sequential model
model = nn.Sequential(nn.Linear(1, 1)).to(device)**
很简单,对吧?
训练步骤
到目前为止,我们已经定义了一个优化器,一个损失函数和一个模型。向上滚动一点,快速查看循环中的代码。如果我们使用不同的优化器,或者损失,甚至模型,会不会改变?如果不是,我们如何使更通用?**
嗯,我想我们可以说所有这些代码行执行一个训练步骤,给定那些三个元素 ( 优化器、损耗和模型)特性和标签。
**那么,编写一个函数,将这三个元素和作为参数,返回另一个执行训练步骤、**、的函数,并返回相应的损失,这样如何?
然后我们可以使用这个通用函数构建一个**train_step()**
函数,在我们的训练循环中调用。现在我们的代码应该看起来像这样…看看训练循环现在有多小?
Building a function to perform one step of training!
让我们休息一下我们的训练循环,暂时关注一下我们的数据…到目前为止,我们只是简单地使用了我们的 Numpy 数组转 PyTorch 张量。但是我们可以做得更好,我们可以建立一个…
资料组
在 PyTorch 中,数据集由从 数据集 类继承的常规 Python 类表示。你可以把它想象成一种 Python 元组列表,每个元组对应一个点(特性,标签)。
它需要实现的最基本的方法是:
**__init__(self)**
:它采用构建一个元组列表所需的任何参数——它可能是将要加载和处理的 CSV 文件的名称;可能是两个张量,一个是特征,一个是标签;或者其他什么,取决于手头的任务。**
在构造函数方法 (
__init__
)中不需要加载整个数据集。如果您的数据集很大(例如,成千上万的图像文件),一次加载它将不会是内存高效的。建议按需加载(每当__get_item__
被调用时)。****
**__get_item__(self, index)**
:它允许数据集被索引**,因此它可以像列表 (dataset[i]
)一样工作**——它必须返回一个对应于所请求数据点的元组(特征,标签)。我们可以返回我们的预加载的数据集或张量的对应切片,或者如上所述,按需加载 它们(就像这个示例)。******__len__(self)**
:它应该简单地返回整个数据集的大小**,这样,无论何时对它进行采样,它的索引都被限制为实际大小。**
让我们构建一个简单的自定义数据集,它采用两个张量作为参数:一个用于要素,一个用于标注。对于任何给定的索引,我们的数据集类将返回每个张量的相应切片。它应该是这样的:
Creating datasets using train tensors
再一次,你可能会想"为什么要大费周章地在一个类中包含几个张量呢?”。而且,再一次,你确实有一个观点…如果一个数据集只是一对张量的,我们可以使用 PyTorch 的 TensorDataset 类,它将做我们在上面的自定义数据集中所做的事情。****
你是否注意到我们用 Numpy 数组构建了我们的训练张量**,但是我们没有将它们发送到设备?所以,他们现在是 CPU 张量!为什么是?**
我们不希望我们的整个训练数据被加载到 GPU 张量中,就像我们到目前为止在我们的示例中所做的那样,因为会占用我们宝贵的显卡 RAM 中的空间。****
好吧,好吧,但是话说回来,我们为什么要建立数据集呢?我们这样做是因为我们想用一种…
数据加载器
到目前为止,我们在每个训练步骤中都使用了整体训练数据**。一直是批次梯度下降。当然,这对我们小得可笑的数据集来说没问题,但是如果我们想要认真对待这一切,我们必须使用小批量梯度下降。因此,我们需要小批量。因此,我们需要相应地分割我们的数据集。要不要手动做?!我也没有!**
所以我们使用 PyTorch 的 DataLoader 类来完成这项工作。我们告诉它使用哪个数据集**(我们刚刚在上一节中构建的那个),期望的小批量以及我们是否想要洗牌。就是这样!**
我们的加载器将表现得像一个迭代器**,所以我们可以循环遍历它并且每次获取一个不同的小批量。**
Building a data loader for our training data
要检索小批量样本,只需运行下面的命令,它将返回包含两个张量的列表,一个用于要素,另一个用于标注。
next(iter(train_loader))
这如何改变我们的训练循环?我们去看看吧!
Using mini-batch gradient descent!
现在有两件事情不同了:不仅我们有一个内循环来从我们的DataLoader
加载每个小批量,更重要的是,我们现在只发送一个小批量到设备**。**
对于更大的数据集,使用数据集的
**__get_item__**
逐个样本地加载数据** (到 CPU 张量中),然后将属于同一小批量的所有样本一次发送到您的 GPU** (设备)是为了让最好地利用您的显卡 RAM 。****此外,如果您有许多 GPU 来训练您的模型,最好保持您的数据集“不可知”,并在训练期间将批处理分配给不同的 GPU。
到目前为止,我们只关注了训练 数据。我们为它构建了一个数据集和一个数据加载器。我们可以对验证数据做同样的事情,使用我们在这篇文章开始时执行的分割…或者我们可以使用random_split
来代替。
随机分裂
PyTorch 的[**random_split()**](https://pytorch.org/docs/stable/data.html#torch.utils.data.random_split)
方法是执行训练-验证分割的一种简单而熟悉的方式。请记住,在我们的示例中,我们需要将它应用到整个数据集 ( 不是我们在前面两节中构建的训练数据集)。
然后,对于每个数据子集,我们构建一个相应的DataLoader
,因此我们的代码看起来像这样:
Splitting the dataset into training and validation sets, the PyTorch way!
现在我们有了一个数据加载器用于我们的验证集,所以,用它来…
估价
这是我们旅程的最后部分——我们需要改变训练循环,以包括对我们的模型的评估,即计算验证损失**。第一步是包括另一个内部循环来处理来自验证加载器的小批量,将它们发送到与我们的模型相同的设备。接下来,我们使用我们的模型(第 23 行)进行预测,并计算相应的损失(第 24 行)。**
这就差不多了,但是还有两个小,一个大,要考虑的事情:
[**torch.no_grad()**](https://pytorch.org/docs/stable/autograd.html#torch.autograd.no_grad)
:尽管在我们的简单模型中不会有什么不同,但是用这个上下文管理器包装验证内部循环来禁用您可能无意中触发的任何梯度计算是一个良好实践,因为梯度属于训练,而不属于验证步骤;[**eval()**](https://pytorch.org/docs/stable/nn.html#torch.nn.Module.eval)
:它唯一做的事情就是将模型设置为评估模式(就像它的train()
对应的那样),这样模型就可以调整它关于一些操作的行为,比如。
现在,我们的训练循环应该是这样的:
Computing validation loss
T21,我们还有什么可以改进或改变的吗?当然,总会有其他东西添加到你的模型中——例如,使用 学习速率调度器 。但是这个帖子已经太长了,所以我就停在这里。****
包含所有附加功能的完整工作代码在哪里?“你问什么?你可以在这里 找到 。
最后的想法
虽然这篇文章比我开始写它时预期的长得多,但我不会让它有任何不同——我相信它有大多数必要的步骤,以一种结构化的方式学习 T42,如何使用 PyTorch 开发深度学习模型。
希望在完成这篇文章中的所有代码后,你能够更好地理解 PyTorch 的官方教程。
更新(2021 年 5 月 18 日):今天我已经完成了**我的书: 深度学习用 PyTorch 循序渐进:入门指南 。**
更新(2022 年 2 月 23 日):平装版现已上市(三册)。更多详情请查看pytorchstepbystep.com。
更新(2022 年 7 月 19 日):第一卷《基础》的西班牙语版现已在 Leanpub 上发布。
Deep Learning with PyTorch Step-by-Step
如果您有任何想法、意见或问题,请在下方留言或联系我 推特 。
理解量子计算机
随着我们寻求探索未知领域,并将人类的影响范围从亚原子粒子扩大到遥远的星球,对计算能力的渴望与日俱增。量子计算机可能是这些需求的答案。
尽管没有必要去研究经典计算机来理解量子计算机是如何工作的,但是计算机是如何进化的以及围绕量子计算机有如此多的误解是非常有趣的,我发现有必要去研究经典计算机是如何变成今天的样子以及它们与量子计算机有什么关系。
“电脑”。这个词现在可能看起来不那么特别了,见鬼,它变得如此流行,以至于当人们说他们不知道如何使用计算机时,这看起来很奇怪。电脑无处不在,它们几乎可以放在任何地方——从你的膝盖到你的手(或者在你的手里!!!)但情况并非总是如此。坐在你腿上的电脑在 19 世纪可能无法适应整个世界,在 20 世纪下半叶也只能勉强适应足球场。这种变化是如何发生的,这很有趣。让我们简要地看一下计算机的发展历程,但在此之前,让我们先定义一下什么是计算机。
大多数人都知道什么是计算机,但对他们来说很难定义它。从这个词最基本的意义上来说,计算机是任何可以计算的东西,也就是说,可以做简单的算术计算,下面是来自维基百科的一个更正式的定义:
计算机是一种可以通过计算机编程指令自动执行一系列算术或逻辑运算的装置。现代计算机有能力遵循被称为程序的广义操作集。这些程序使计算机能够执行非常广泛的任务。包括硬件、操作系统(主软件)和用于“完整”操作所需的外围设备的“完整”计算机可称为计算机系统。该术语也可用于连接并一起工作的一组计算机,特别是计算机网络或计算机集群。
The Difference Engine
尽管现代计算机的旅程始于 20 世纪,但早在 19 世纪人们就感受到了对这种机器的需求,伟大的发明家/科学家们也尝试着制造这种机器。机械计算机就是在那个时候设计出来的(最著名的是由查尔斯·巴贝奇,第一个程序是由包括阿达·洛芙莱斯女士在内的许多人编写的。但是一些计算机仅仅是在设计中构思出来的,因为它们远远超出了当时的技术水平,而且非常昂贵,我们最好使用人类来代替。巴贝奇的计算机是在 20 世纪 90 年代由美国加州山景城的历史博物馆建造的,这里有一个好视频展示了巴贝奇设计的实际差分机。
然后重点转移到机电计算机和电线,水银用于穿孔卡和 IBM(国际商业机器)的出现,计算机变得越来越好,现在可以执行非常复杂的计算,这进一步加速了两次世界大战,军事上需要计算机来获得战略优势。但是这些仍然不足以满足不断增长的计算需求,因此计算机变得越来越大。更大的计算机意味着更复杂的结构,管理它们变得越来越困难。(有趣的事实:正是在这个时候,bug 会飞入这些计算机,导致结果出现问题,因此在现代计算机科学中使用了单词“ bugs ”。
Alan Turing help build an electro-mechanical device, “The Bombe” to help decipher German Enigma-machine-encrypted secret messages during World War II.
随着电子晶体管的引入,这一切都改变了,现在计算机开始缩小,它们的潜力现在已经为人所知,大量的激烈竞争(加上许多科幻小说)导致了越来越小的计算机的发展,这都归功于晶体管尺寸的缩小。每个人都注意到了这一点,但戈登·摩尔浓缩了这一观察,并将其公式化——“密集集成电路上的晶体管数量每两年翻一番”。
被称为摩尔定律的定律多年来都是正确的(现在仍然如此),但摩尔定律有一个问题:晶体管不能一直缩小,它们必然会遇到量子效应,当量子物理出现时,就会有大量的不确定性(明白吗!).计算机需要对答案有绝对的把握,否则计算机就不会很有用。
摩尔定律是指在密集集成电路中,晶体管的数量大约每两年翻一番。这一观察结果是以飞兆半导体(Fairchild Semiconductor)联合创始人兼英特尔(Intel)首席执行官戈登·摩尔的名字命名的,他在 1965 年的论文中描述了每个集成电路的组件数量每年翻一番,并预测这一增长速度将至少再持续十年。1975 年,展望下一个十年,他将预测修改为每两年翻一番。由于英特尔高管大卫·豪斯(David House)的预测(更多晶体管和更快晶体管的综合效应),这一时期通常被称为 18 个月。
根据目前的估计,晶体管将在 2024 年左右停止变得更小。由于在此期间,单个晶体管将足够小,量子效应将非常显著,如果我们要制造晶体管,任何更小的量子效应都将发挥作用,因此传统(或经典)计算方法将停止其在性能改善方面的指数增长。
为了克服性能增强中的这一障碍,我们可以使用新型计算,也称为量子计算。量子计算可以帮助我们在几秒钟内完成大量的计算。这有助于解决传统计算机以前无法解决的问题。许多人以此为论据,说这是量子计算机是必要的原因之一,它们可以帮助取代经典计算机,但这是完全错误的。我们将会看到,它们存在和需要的原因要复杂得多,也更令人着迷。
以上信息(以及许多其他东西)可以在这个 YouTube 课程中以更详细、更结构化的方式找到。
进入量子世界
量子计算机不是来取代经典计算的,这不是它们的本意。它们与经典计算机非常不同,有自己的计算方式。在我们用经典计算机解决的大多数问题上,它们可能永远无法打败普通计算机。那为什么需要他们?
原因是量子计算机是为了解决一些更大的问题而不是为了在网上看猫视频(叹)。量子计算机可以用来解决经典计算机无法解决的问题。像破解 RSA 加密这样的问题,以及像量子建模这样需要量子解决方案的问题。实际上,(伟大的物理学家理查德·费曼)构想的量子计算机的第一个应用是研究量子粒子。因为,如果我们可以用数学方法模拟一个量子粒子,然后在计算机中创建它的模拟,那么我们就可以对它们进行各种实验,否则这是不可能的。
那么这些问题是什么呢?为什么我们的普通电脑不能帮上忙呢?有些问题需要极大的计算能力和空间,例如寻找超大素数的因子、模拟分子、模拟量子现象等。在计算机科学中,我们将问题分成不同的类别,这取决于当我们增加输入值的数量时,它们是如何增长的(例如,需要多少计算)。这些被称为多项式时间,不确定多项式时间和 np 难问题。对于很少的输入,经典计算机可以解决 NP 问题,但是对于大的 N 值,计算时间变得很长,因此计算变得不可行。但是有些问题是 NP 级别的,但是仍然可以在有限的时间内通过量子计算机解决。这些问题被称为 B 舍入误差 Q 量子 P 奥林匹克时间问题。但是到目前为止,这类问题是有限的,你最好在经典计算机上解决这些问题,而不是在量子计算机上。但是有些问题(比如大数的质因数分解)只能由量子计算机解决(尽管是非常强大的)。
The Problem Hierarchy
为了理解计算机,我们首先看一下它的构成材料,即硅,然后我们从二极管到门到寄存器,再到更复杂的体系结构,如可以执行更复杂计算的 alu 和 CPU,然后我们在它上面放一个抽象层,为它编写程序,指导它执行我们希望它执行的任务。我们将用类似的方法来理解量子计算机的内部工作原理。但在此之前,我们需要了解量子计算机利用的一些奇怪的物理现象。
先决物理学
量子物理学本身是一门极其庞大和有趣的学科,但要理解量子计算机我们不需要完全理解它,我们只需要看看一些量子现象(即量子纠缠和量子叠加),并对概念进行抽象理解,以理解量子计算机的工作。
量子叠加是一种非常反直觉的物理现象,它表明电子等量子粒子可以同时出现在一个以上的状态,即一个电子可以同时处于两个地方,也可以同时具有不同的动量或能量。这在我们的日常生活中是观察不到的,因为一个东西应该只在一个地方,如果它同时出现在许多地方,那就违反了经典物理定律。但是这对于量子粒子来说确实是可能的,因为它们具有双重性,即类波性和类粒子性。量子粒子据说同时是波和粒子(需要注意的是,“同时”只是为了理解,因为在量子世界中没有时间或现实的概念),因此具有双重性质。现在波有这个叠加原理,你可能知道,也就是说,两个波可以相互叠加产生另一个波。这是根据维基百科对量子叠加的定义:
它指出,就像经典物理学中的波一样,任何两个(或更多)量子态可以加在一起(“叠加”),结果将是另一个有效的量子态;相反,每个量子态都可以表示为两个或更多不同态的总和。数学上是指薛定谔方程解的一个性质;由于薛定谔方程是线性的,任何解的线性组合也将是一个解。
这里有一个来自狄拉克的书【1】的关于量子叠加的更数学的解释:
- 考虑两个状态 A 和 B 的叠加,通过观察得到结果 A 和 B。
- 根据叠加过程中 A 和 B 的相对权重的概率定律,对处于叠加状态的系统进行的观察有时是 A,有时是 B。
为了更好地理解量子叠加,你可能想看看量子叠加 wiki 页面的附带视频,我发现那个视频对理解量子叠加很有帮助。如果你仍然有困难,这里有一个很棒的视频解释了量子叠加,或者看看这个 quora 线程来进一步理清你的理解
Quantum Effects
量子纠缠是最令人着迷的量子现象之一,这也是爱因斯坦对量子力学持怀疑态度并将其称为“幽灵般的超距作用”的原因之一。
假设你有两个粒子在一个孤立的宇宙中创造出能量,现在让我们考虑它们的角动量,它们的角动量之和应该是常数(角动量守恒定律)。假设它们相距很远,那么它们的角动量之和应该是相同的(因为角动量是守恒的)。例如,如果一个粒子向上,那么另一个粒子应该在相反的方向。现在让我们考虑一下,我们测量第一个粒子的自旋,发现它是向上的,那么其他粒子的自旋一定是向下的(角动量守恒),借助于一些未知的通道相互接触(假设),或者它们正在用比光更快的东西进行通信(因为物理学这是不可能的)。
Quantum Entanglement gif
我发现上面的思想实验对于理解为什么量子纠缠如此怪异非常有用,但是这里有一个关于量子纠缠的更的正式定义:
量子纠缠是一种物理现象,当成对或成组的粒子以某种方式产生、相互作用或共享空间接近度时,即使粒子相距很远,每个粒子的量子状态也无法独立于其他粒子的状态来描述。
量子纠缠已经用光子、中微子、电子、像巴基球一样大的分子,甚至小钻石进行了实验演示。纠缠在通信和计算中的应用是一个非常活跃的研究领域。这里有一个好视频把量子纠缠和量子叠加都解释的很清楚。
尽管量子纠缠看起来很奇怪,但它仍然有效,这是量子计算机的关键所在,已经有许多实验证明量子纠缠确实存在,中国在 2011 年发射了一颗卫星(名为 Micius ),以促进使用量子纠缠进行通信,并在 2017 年进行了一次视频会议。
了解了上述信息,我们现在可以进一步了解量子计算机的内部工作原理。我们将从信息存储和检索开始,看看如何利用量子效应进行计算。然后我们看看这些工具是如何组合在一起制造量子逻辑门的。然后,这些门可以组合起来进行更复杂的计算。然后我们可以将抽象层层叠加,最终得到量子计算机的最终模型。
量子位
在传统的计算机中,信息是以比特的形式存储的,这些二进制二进制 T2 三进制 T4 可以代表 0 或 1 的状态,我们已经开发了相当复杂的系统,仅仅通过使用这种简单的存储形式,但是我们仍然可以用一个比特做得更好,不像比特只能存储 0 和 1,量子比特可以存储第三种也是非常重要的状态,即 0 和 1 的叠加。这种状态的叠加有助于量子计算机做经典计算机可能做不到的事情。当一个量子位处于其中一个叠加态时,可以说它同时处于 0 和 1 两种状态,但实际情况并非如此。例如,假设你向北移动了 3 米,然后向西移动了 4 米,现在你在西北方向上 5 米,或者你在北方和西方的叠加上。换个说法——“你同时在北和西!”听起来不再那么美味了,是吗?量子态的叠加也是如此。即使一个量子位不处于 1 或 0 状态中的任何一个,但是它处于例如 80% 1 状态和 40% 0 状态。因此,它同时处于两种状态的叠加。
这里有一个很好的答案解释了量子比特如何同时持有 0 和 1?
一个量子位通常是由硅和磷组成的,有时是由光子组成的!人们还试图利用原子核的自旋来制造量子位。但是,是什么让量子位不同于经典位呢?答案在于量子叠加原理。让我们考虑一个例子
假设你现在有两个比特,它们可以是00, 01, 10, 11
,也就是说,它们可以是四种状态中的一种。现在考虑两个量子比特,它们可以是- (↑,↑),(↓,↓),((↓,↓) + (↓,↓))或者((↓,↓)-(↓,↓),其中,+和-表示要么相干要么消相干叠加如图所示。
现在假设上述每个状态的概率由α,β,γ,δ给出。现在有趣的部分来了,量子位不在这些状态中的任何一个,而是在这些状态的叠加中。即,它的状态是
α(↑,↑) + β((↓,↑)-(↑,↓)) + γ((↓,↑)+(↑,↓)) + δ(↑,↑)
因此,如果我们现在执行计算,我们实际上将同时对所有这些状态执行计算,而不是单独对每个状态执行计算,从而显著提高某些算法的性能。
States of a two qubit system
量子位最令人印象深刻的一点是,增加计算能力并不需要太多。因此,举例来说,你想要将上述系统的计算能力加倍以表示 8 个状态,现在要做到这一点,我们只需要再增加一个量子位,从而使所表示的状态的数量加倍。现在,假设我们需要用经典比特来表示上述系统,为此我们需要四个单独的状态(由比特组成),现在要加倍它,即表示 8 个状态,我们需要加倍状态,即 4 个以上的表示。因此,为了让经典计算机的计算能力翻倍,我们需要让它的体积翻倍。但是对于已经很庞大的超级计算机来说,这是不切实际的。但是有了量子计算机,你只需增加一个量子位就可以逃脱。
即使原则上我们可以同时进行这些操作,我们也不可能测量这些状态,因为一旦我们测量了这些状态,叠加态就被破坏了。因此,实际上,我们只能得到一个答案,如果我们测量一个量子位,我们很可能会得到一个无用的随机答案。为了解决这个问题,我们设计了这样一种算法,即错误的状态相互干扰并相互抵消,而正确的答案通过干扰得到提升(正如在 shor 的算法中所做的)。最后,留给我们的答案是正确的可能性大于错误的可能性。这可以通过量子傅立叶变换来实现。这里有一个很棒的视频来理解量子算法是如何工作的。
信息存储和检索
计算机做的最重要的事情之一是读取和写入数据,因为如果可能的话,它可以做任何事情,如艾伦·图灵所示。图灵机的概念在量子计算机中得到了进一步的发展,已经提出了量子图灵机。让我们了解一下量子计算机是如何读写数据的。
States of a two qubit system
量子位嵌入在晶体管中。现在所有的量子位都有能量,假设我们有一个处于向下状态的电子,那么它会有低能量,现在用一个精确的微波(精确的意思是在正确的位置以正确的频率)我们可以给那个电子足够的能量使它向上旋转。这将导致晶体管中电压的变化,我们现在可以说,在此基础上,它确实增加了。因此,我们已经将一个量子位的状态从 0 改变为 1,并且我们现在已经成功地写入了一个量子位。
从一个量子位中读取也是类似的,但是我们必须记住,这并不容易,我们不能仅仅有一个好的微波发生器和从/向量子位中读取/写入。由于在室温下,量子粒子有太多的能量,它会产生随机的模式,但我们需要非常精确的模式,所以我们需要将物质冷却到几乎零开尔文,这样我们就可以记录量子位数据。这里有一个由 veritasium 制作的精彩视频,非常详细地说明了上述内容。
大局
已经了解了量子位的读写等基本操作,我们现在可以理解量子计算机如何执行逻辑运算了。这是借助量子门完成的。量子逻辑门是在少量量子位上运行的基本量子电路。它们是量子电路的组成部分,就像传统数字电路中的经典逻辑门一样。但与经典门不同的是,量子门有一个奇特的性质——它们是可逆的。这意味着输入可以产生输出,反之亦然。例如,知道一个非门的输出是 1,我们就知道输入必须是 0。类似地,如果我们知道输出是 0,那么输入一定是 1。因此,非门是可逆的。
量子门必须是可逆的,因为量子力学要求量子系统永远不会随时间丢失信息,并且它必须总是能够重建过去。有许多可用的量子门,完整的列表可以在这里找到。这里我们将简要介绍一些最重要的量子门:
- CNOT 门: or Controlled NOT 是量子计算机最基本的门之一。结合使用 CNOT 门和单量子位旋转,任何量子电路都可以被模拟到任意的精确度。它可以用来纠缠和解开 EPR 态。CNOT 门在一个由两个量子位组成的量子寄存器上运行。当且仅当第一个量子位(控制量子位)为 1 时,CNOT 门翻转第二个量子位(目标量子位)。
- **托夫里门:**托夫里门是一种通用的可逆逻辑门,也就是说任何可逆电路都可以由托夫里门构成。它也被称为“受控-受控-非”门。它有 3 位输入和输出;如果前两位都设为 1,则反转第三位,否则所有位保持不变。
- **哈达玛门:**哈达玛门是一种单量子位操作,它将基态∣0 >映射到(∣0 > +∣1 > )/√2,∣1 >映射到(∣0 > −∣1 > )/√2,从而创建两个基态的相等叠加。哈达玛门也可以表示为绕 Y 轴旋转 90°,然后绕 X 轴旋转 180°。
有了量子计算机内部工作的上述知识,我们可以将这些结合起来,形成计算机更大、更有用的部分(就像在经典计算机中一样)。这些组成了我们的量子计算机。现在我们需要经典计算机中所有可用的东西,从编译器、操作系统、编程语言等等开始。由于经典计算机有丰富的历史,我们可以利用这一点,不需要太多的工作,就可以构建一个工作量子计算软件。目前,大多数公司都有自己的架构,但如果我们想提高这一领域的研究速度,这需要尽快改变。我们需要标准化的架构,所以研究人员很容易开发和测试新事物。这是建议的架构之一。
States of a two qubit system
正如我们所看到的,这似乎是一个经典的计算机体系结构,量子这个术语与之相关联,的确如此。现在我们能够理解如何使用这些组件,并利用它们做一些有用的事情。已经提出了许多量子算法:
- Shor 的算法可以用于整数因式分解,它利用数论来实现这一点,它理论上可以破解 RSA 加密。
- 格罗弗算法是一种量子算法,仅使用函数的 O ( N)次评估,以高概率找到产生特定输出值的黑盒函数的唯一输入,其中 N 是函数域的大小。它是由 Lov Grover 在 1996 年发明的。
- Deutsch-Jozsa 问题将 n 位二进制值作为输入,并为每个这样的值产生 0 或 1 作为输出。我们被承诺函数要么是常数(所有输出为 0 或所有输出为 1)要么是平衡的,那么任务是通过使用 oracle(黑盒量子计算机)来确定 f 是常数还是平衡的。
这些算法非常复杂,已经有非常好的资源可以详细解释它们,因此在这个博客中解释它们没有什么意义。这里有一个斯科特·阿伦森的关于肖尔算法的博客,还有一个 T2 的关于格罗弗算法的博客
挑战和缺点
量子计算机非常容易出错。的确,我们从来没有从量子计算机中得到准确的结果。我们有时得到正确的结果,有时得到错误的结果,当我们得到正确的结果多于错误的结果时,我们说我们的量子计算机给了我们好的答案。量子纠错本身就是一个领域。降低结果的不确定性是非常必要的,因为只有在这之后,我们才能使量子计算机可用。
量子计算机的另一个问题是,一旦你观察到一个状态,它就会失去所有的信息,因为它必须坍缩到只有一个状态。现在,观察并不意味着人类的观察,它意味着任何可能导致光子交换的与量子粒子的相互作用。现在,这可以通过与几乎任何东西的相互作用来实现,这被称为量子退相干。这里有一个来自维基百科的更正式的定义
量子退相干是量子相干性的丧失。在量子力学中,电子等粒子由波函数描述,波函数是系统量子态的数学表示;波函数的概率解释被用来解释各种量子效应。只要不同状态之间存在确定的相位关系,就说系统是相干的。相干性在量子物理定律下得以保留,这对量子计算机的运行是必要的。然而,当量子系统不是完全孤立的(在这种情况下,不可能操纵或研究它),相干性与环境共享,并随着时间的推移而消失,这一过程称为量子退相干。这个过程的结果是,量子行为显然消失了,就像经典力学中能量似乎因摩擦而消失一样。
退相干导致了另一个问题——只有当多个量子比特可以相互纠缠时,它们在量子计算机中才有用。在今天的量子计算机中,当你试图纠缠 10 个量子比特时,它们已经失去了相干性。今天的量子阱的相干寿命只有几微秒,远低于执行有意义的计算任务所需的时间。因此,退相干限制了量子计算机计算能力的增长。
为了克服退相干,我们需要非常复杂且非常绝缘(甚至与空气中存在的波(信号)绝缘)的系统来保护量子位不受任何外部影响。不用说,这是极其困难的,需要大量的资源和巧妙的工程,导致巨额资金的投资。再加上安装它的难度和危险性,你就可以告别在家里拥有一台量子计算机的梦想了。
前方的路
即使量子计算机为我们提供了一个伟大的视角,但围绕它们有很多虚假的炒作,有一些阴谋论认为量子计算机是通往其他维度的通道(lol!!!),鉴于任何新技术和大量围绕量子物理的科幻小说,这种炒作是很自然的。但我们需要对量子计算机持现实态度,因为我们甚至不知道我们是否会有可用于实际目的(如模拟分子)的量子计算机。由于退相干,增加量子计算机中的量子比特数量非常困难,即使量子比特的质量在过去十年中有所提高,但在我们可以期待量子计算机的一些结果之前,仍然需要对量子计算机进行大量的研究和关注,并需要教育人们。
大多数量子计算研究都集中在破解密码术上(由于它的影响),这是由于 Shor 的算法,但是期望量子计算机在不久的将来破解密码术是不现实的,因为我们需要成千上万的量子位来破解密码术,而这些天我们几乎没有两位数。随着时间的推移,量子计算机的许多新应用已经出现,从量子互联网到量子力学系统的模拟,这可能导致新材料的形成,这些新材料可用于抗癌、制造纳米粒子、制造超导材料等。量子密码术正在成为量子效应应用的更先进的领域之一。
中国科技大学的简希望,到 2030 年,量子通信将跨越多个国家。在 10 年左右,你可以期待量子互联网。英特尔最近宣布了一台名为“Tangle Lake”的 49 位量子计算机,据报道,谷歌已经建造了一个名为“Bristlecone”的 72 位量子计算机芯片,IBM 推出了世界上第一台商用量子计算机,名为 IBM Q System One。阿里巴巴在这一领域也取得了长足的进步。
总而言之,量子计算机的前景似乎是光明的,再加上大公司之间的激烈竞争,你就有了生活在即将成为伟大历史的时代的秘诀。
[1] P.A.M .狄拉克(1947)。量子力学原理(第二版)。克拉伦登出版社。第 12 页。
原载于 2019 年 4 月 8 日https://khansaadbinhasan . github . io。
了解随机森林
Photo by Skitterphoto from Pexels
该算法如何工作以及为什么如此有效
机器学习的很大一部分是分类——我们想知道一个观察值属于哪个类(也叫组)。对观察结果进行精确分类的能力对于各种业务应用程序非常有价值,例如预测特定用户是否会购买产品,或者预测给定的贷款是否会违约。
数据科学提供了大量的分类算法,如逻辑回归、支持向量机、朴素贝叶斯分类器和决策树。但是靠近分类器层次结构顶端的是随机森林分类器(也有随机森林回归器,但那是另一天的主题)。
在这篇文章中,我们将研究基本决策树是如何工作的,单个决策树是如何组合成随机森林的,并最终发现为什么随机森林如此擅长它们的工作。
决策树
让我们快速浏览一下决策树,因为它们是随机森林模型的构建模块。幸运的是,它们非常直观。我敢打赌,大多数人在生活中的某个阶段都有意无意地使用过决策树。
Simple Decision Tree Example
通过一个例子可能更容易理解决策树是如何工作的。
假设我们的数据集由左图顶部的数字组成。我们有两个 1 和五个 0(1 和 0 是我们的类),并希望使用它们的特性来区分这些类。这些特征是颜色(红色对蓝色)以及观察值是否带下划线。那么我们如何做到这一点呢?
颜色似乎是一个非常明显的特征,因为除了一个 0 以外,所有的 0 都是蓝色的。所以我们可以用这个问题,“它是红色的吗?”分割我们的第一个节点。您可以将树中的一个节点看作是路径分成两部分的点——符合标准的观察值沿着“是”分支前进,不符合标准的观察值沿着“否”分支前进。
No 分支(蓝色)现在都是 0,所以我们已经完成了,但是我们的 Yes 分支仍然可以进一步分裂。现在我们可以使用第二个特征,并询问“它是否加了下划线?”进行第二次分裂。
带下划线的两个 1 进入 Yes 子分支,不带下划线的 0 进入右边的子分支,我们就完成了。我们的决策树能够使用这两个特征完美地分割数据。胜利!
显然,在现实生活中,我们的数据不会如此清晰,但决策树采用的逻辑是相同的。在每个节点,它会问—
什么特征允许我以一种方式分割手头的观察结果,使得结果组尽可能地彼此不同(并且每个结果子组的成员尽可能地彼此相似)?
随机森林分类器
随机森林,顾名思义,由大量个体决策树组成,作为一个集合体运行。随机森林中的每棵树都给出一个类别预测,拥有最多票数的类别成为我们模型的预测(见下图)。
Visualization of a Random Forest Model Making a Prediction
随机森林背后的基本概念简单而强大——群体的智慧。用数据科学的话说,随机森林模型如此有效的原因是:
作为一个委员会运作的大量相对不相关的模型(树)将胜过任何单个的组成模型。
模型之间的低相关性是关键。就像低相关性投资(如股票和债券)组合在一起形成的投资组合大于其各部分之和一样,不相关模型可以产生比任何单个预测都更准确的整体预测。产生这种奇妙效果的原因是这些树相互保护,避免各自的错误(只要它们不总是朝着同一个方向犯错)。虽然有些树可能是错误的,但许多其他的树将是正确的,因此作为一个群体,这些树能够朝着正确的方向移动。因此,随机森林运行良好的先决条件是:
- 我们的特征中需要有一些实际的信号,以便使用这些特征建立的模型比随机猜测做得更好。
- 由单个树做出的预测(以及因此产生的误差)需要彼此具有低相关性。
为什么不相关的结果如此巨大的一个例子
拥有许多不相关模型的奇妙效果是一个如此重要的概念,我想给你看一个例子来帮助你真正理解它。想象我们正在玩下面的游戏:
- 我使用一个均匀分布的随机数生成器来产生一个数。
- 如果我生成的数字大于或等于 40,你就赢了(所以你有 60%的胜算),我付给你一些钱。如果低于 40,我赢了,你付给我同样的钱。
- 现在我给你以下选择。我们可以:
- 游戏 1 —玩 100 次,每次下注 1 美元。
- 游戏 2 —玩 10 次,每次下注 10 美元。
- 第三场 —玩一次,下注$100。
你会选哪个?每场比赛的期望值是一样的:
期望值游戏 1 = (0.601 + 0.40-1)*100 = 20
期望值游戏 2= (0.6010 + 0.40-10)*10 = 20
期望值游戏 3= 0.60100 + 0.40-100 = 20
Outcome Distribution of 10,000 Simulations for each Game
发行版呢?让我们用蒙特卡洛模拟来可视化结果(我们将对每种游戏类型运行 10,000 次模拟;比如我们会模拟 10000 次第一场的 100 次打法。看看左边的图表,现在你会选择哪种游戏?即使期望值相同,结果分布也有很大不同,从正的、窄的(蓝色)到二进制的(粉色)。
游戏 1(我们玩 100 次)提供了赚钱的最好机会——**在我运行的 10,000 次模拟中,你在 97%的模拟中都赚钱了!**对于第二场游戏(我们玩了 10 次),你在 63%的模拟中赚钱,这是一个急剧的下降(你输钱的概率也急剧增加)。而我们只玩一次的游戏 3,你在 60%的模拟中赚钱,正如预期的那样。
Probability of Making Money for Each Game
因此,即使这些游戏有着相同的期望值,它们的结果分布却完全不同。我们越是将 100 美元的赌注分成不同的游戏,我们就越有信心赚钱。如前所述,这是可行的,因为每个剧本都是独立于其他剧本的。
随机森林也是一样——每棵树就像我们之前游戏中的一个游戏。我们只是看到我们赚钱的机会随着我们玩的次数越来越多而增加。同样,对于随机森林模型,我们做出正确预测的机会随着模型中不相关树木的数量而增加。
如果你想自己运行模拟游戏的代码,你可以在我的 GitHub 这里找到它。
确保模型相互多样化
那么随机森林如何确保每棵树的行为与模型中任何其他树的行为不太相关呢?它使用以下两种方法:
Bagging(Bootstrap Aggregation)——决策树对它们接受训练的数据非常敏感——训练集的微小变化会导致明显不同的树结构。 Random forest 利用了这一点,它允许每棵单独的树通过替换从数据集中随机采样,从而产生不同的树。这个过程被称为装袋。
注意,使用 bagging,我们不是将训练数据分成更小的块,而是在不同的块上训练每棵树。相反,如果我们有一个大小为 N 的样本,我们仍然为每棵树提供一个大小为 N 的训练集(除非另有说明)。但是代替原始的训练数据,我们用替换取一个大小为 N 的随机样本。例如,如果我们的训练数据是[1,2,3,4,5,6],那么我们可以给我们的树一个下面的列表[1,2,2,3,6,6]。请注意,这两个列表的长度都是 6,并且“2”和“6”都在我们提供给我们的树的随机选择的训练数据中重复出现(因为我们使用替换进行采样)。
Node splitting in a random forest model is based on a random subset of features for each tree.
特征随机性— 在正常的决策树中,当需要分割一个节点时,我们会考虑每一个可能的特征,并选择一个在左节点和右节点的观察值之间产生最大分离的特征。相比之下,随机森林中的每棵树只能从随机要素子集中选取。这迫使模型中的树之间有更多的变化,并最终导致树之间更低的相关性和更多的多样化。
让我们来看一个直观的例子——在上图中,传统的决策树(蓝色)在决定如何拆分节点时,可以从所有四个特征中进行选择。它决定使用特性 1(黑色加下划线),因为它将数据分成尽可能独立的组。
现在让我们来看看我们的随机森林。在本例中,我们将只检查森林中的两棵树。当我们检查随机森林树 1 时,我们发现它只能考虑特征 2 和 3(随机选择)来进行节点分裂决策。我们从传统的决策树(蓝色)中知道,特征 1 是最好的分割特征,但是树 1 看不到特征 1,所以它被迫使用特征 2(黑色并带下划线)。另一方面,树 2 只能看到特征 1 和 3,因此它能够选择特征 1。
所以在我们的随机森林中,我们最终得到的树不仅在不同的数据集上进行了训练(多亏了 bagging ),而且还使用不同的特征来做决策。
我亲爱的读者,这就创造了不相关的树,它们缓冲并保护彼此免受错误的影响。
结论
随机森林是我个人的最爱。来自金融和投资领域的圣杯总是建立一系列不相关的模型,每个模型都有正的预期回报,然后将它们放在一个投资组合中,以赚取巨大的阿尔法(阿尔法=市场回报)。说起来容易做起来难!
随机森林是数据科学的等价物。我们最后复习一遍。什么是随机森林分类器?
随机森林是一种由许多决策树组成的分类算法。它在构建每棵树时使用 bagging 和特征随机性,试图创建一个不相关的树木森林,其委员会的预测比任何单棵树都更准确。
为了让我们的随机森林做出准确的类别预测,我们需要什么?
- 我们需要至少有一些预测能力的功能。毕竟,如果我们把垃圾放进去,我们就会把垃圾弄出来。
- 森林中的树木,更重要的是它们的预测需要不相关(或者至少彼此之间的相关性很低)。虽然算法本身通过特征随机性试图为我们设计这些低相关性,但我们选择的特征和超参数也会影响最终的相关性。
感谢阅读。我希望你从阅读这篇文章中学到的和我写这篇文章时学到的一样多。干杯!
如果你总体上喜欢这篇文章和我的写作,请考虑通过我的推荐链接注册 Medium 来支持我的写作。谢谢!
通过示例了解 RNNs
训练你的 PyTorch 模型计数
成为机器学习实践者最艰难的任务之一是直观地理解模型背后的魔力。人们普遍认为你需要成为一名数学专家才能完全掌握底层的机制,但你真正需要的只是浏览几个基本的例子。掌握简单的模型会给你在复杂性增加时所需要的基础。
有很多精彩的文章从较高的层面讲述了 RNN 是如何工作的,所以我将本课的内容提供给那些对自己实现一个具体示例感兴趣的人。
这篇文章中的很多想法和插图都来源于 fast.ai 以及他们在 NLP 中的课程。
RNNs 入门:
传统的前馈网络接收固定大小的输入,并产生固定大小的输出。当您的表格数据具有恒定的列数时,这非常有用,但是当您具有可变长度的序列(如音频、时间序列或我们示例中的文本)时,这就没什么用了。
RNN 的魔力在于它将当前输入与先前或隐藏状态相结合的方式。这种隐藏状态可以简单地认为是模型的内存或上下文。如果我让你预测一个句子中的下一个单词,如果当前单词是“hot ”,那就不可能做出准确的猜测。如果你有一些比当前输入更多的记忆,比如“我吃了一个热的”,你可以更好地猜测下一个单词是“狗”。
鉴于其简单性,rnn 的能力令人惊叹。rnn 只是一个循环,将它们的内存与当前输入相加,并通过线性层发送该组合值,以计算下一步的内存。在为每一步输入计算隐藏状态后,隐藏状态通过输出层发送,以进行最终预测。下面是这个循环的伪代码:
h = hidden_state_init()
for word in words:
h += embedding(h)
h = linear(h)
out = linear_output(h)
return out
在本教程中,我们将用英语教我们的 RNN 数数。例如,如果我们的输入是:
['one', 'thousand', 'three', 'hundred', 'tweleve', ',' , 'one']
那么序列中的下一个单词将是*‘千’*。
设置数据管道:
加载数据和构建词汇表:
首先,我们需要加载 train.txt 和 valid.txt 文件,它们的数字分别从 1 到 8000 和 8000 到 9999。我们将把这些文件加载到一个字符串列表中,通过制作一组字符串来构建我们的词汇表,然后构造一个从每个字符串到一个整数的映射。
创建批处理迭代器:
当训练我们的模型时,我们希望输出比输入快一步。例如,如果当前输入是“二”,那么输出应该是“,”。
x = ['one', ',', 'two', ',', 'three', ',', 'four', ',', 'five', ',']
y = [',', 'two', ',', 'three', ',', 'four', ',', 'five', ',', 'six']
这个迭代器将首先生成形状的 2d 列表(bs,bptt ),其中 bs 代表批量大小,bptt 代表时间上的反向传播,或者基本上是序列中的字数。一旦我们生成了这些列表,我们就使用我们在上一步中构建的字典将令牌映射到它对应的整数。最后,我们将 2d 列表转换为 torch 张量,以便我们的模型可以处理它们。
训练第一个模型:
虽然 PyTorch 有一个专用的 RNN 层,但我们实际上将使用我们的循环来重新创建该层,这样我们可以更直观地掌握我们的层的顺序。我们的模型将由 4 个独特的层构成:
- **i_h(隐藏的输入)😗*将输入(表示特定令牌的数字)映射到一个嵌入,其大小是我们定义的隐藏状态的 64
- h_h (hidden to hidden): 线性层将先前的隐藏状态+当前输入作为输入,并向前传播这些组合值以生成当前隐藏状态
- **h_o(隐藏到输出)😗*线性层,正向传播当前隐藏状态,生成预测输出
- **bn(批量标准化)😗*移动和缩放激活以使优化场景明显更平滑(不要担心这个!).
一旦我们建立了模型,训练模型就相当于样板代码。然而,我将讨论一些关于我们的训练循环的细节。
- 我们使用 Adam 作为我们的优化器,它和学习率一起决定在随机梯度下降(SGD)过程中减去梯度时的步长。
- 我们使用交叉熵损失,这是任何分类任务中最常见的损失函数,并且通过对不正确答案的高概率猜测(假阳性)和正确答案的低概率猜测(假阴性)进行惩罚来工作。
- 我们迭代 10 个时期,这意味着我们将暴露所有的输入 10 次。在每个时期内,我们使用 DataLM 迭代器通过迭代整个输入来构建批处理。
- 我们记录每一步的损失和准确性。注意,损失是为每个标记计算的,而准确度只是模型正确获得第 71 个标记的能力的度量。我会让你想想,如果我们为每个记号计算,精度会有什么变化。
plt.plot(pd.Series(losses).rolling(4).mean())
plt.plot(pd.Series(accuracy).rolling(4).mean())
改进模型:
我们可以使用 PyTorch 的原生 RNN 层重构上面的模型,以获得与上面相同的结果。在实践中,您应该使用本机层,因为它更容易使用,并且由于一些 GPU 优化,运行速度更快。
虽然我们上面的演示表明,RNNs 可以有效地处理序列数据,但它们与长期依赖性作斗争。gru(门控循环单元)的功能与 rnn 非常相似,但在决定当前输入对当前状态的影响程度方面更智能。我很快会写一篇关于 GRUs 的文章,但与此同时,我会推荐这个由 fast.ai 做的讲座。
通过使用 GRU 而不是 RNN,我们可以将准确率从略高于 50%提高到 95%。
测试模型:
我们使用由数字 8001–9999 组成的验证列表,随机选择 20 个令牌。当我运行代码时,我对以下输入序列进行了采样:
seven , nine thousand three hundred eight , nine thousand three hundred nine , nine thousand three hundred ten ,
我们将此输入运行到我们的模型中,以预测下一个令牌。在预测了那个记号之后,我们把它附加到输入中来预测另一个记号。我们重复这个输入→预测→连接→输入序列 20 步。
使用与上面相同的输入字符串,我们得到以下输出:
nine thousand three hundred eight , nine thousand three hundred nine , nine thousand three hundred ten , nine thousand nine hundred eleven , nine thousand nine hundred twelve , nine thousand nine hundred thirteen , nine thousand
现在,当以 95%的准确率预测 20 个令牌时,我们只会在 36%的时间内得到正确的输出。根据经验,使用训练集,我在 80%的情况下得到正确的输出,在 45%的情况下使用验证得到正确的输出
总结:
希望现在你对 RNN 的工作原理有了更好的理解,但是我不期望你完全掌握所有的细节。说到这里,我对如何加强你的理解有很多建议:
- 如果你喜欢自上而下的结账方式
- 如果你想深入了解数学,请访问关于序列模型的 deeplearning.ai 课程。
- 如果你是动手型的,那么把 RNN 应用到另一个应用程序中。
参考文献:
J.霍华德,r .托马斯,自然语言处理的代码优先介绍 (2019), fast.ai
了解 rnn、LSTMs 和 gru
一个递归神经网络 (RNN)是一个基本神经网络的变体。rnn 适用于处理序列数据,如自然语言处理和音频识别。直到最近,他们还饱受短期记忆问题的困扰。在这篇文章中,我将尝试解释什么是(1) RNN,(2)消失梯度问题,(3)这个问题的解决方案被称为长短期记忆 (LSTM)和门控循环单元 (GRU)。
什么是 RNN?
首先,让我们介绍基本的神经网络架构,神经网络通过 3 个基本步骤进行训练:
(1)进行预测的向前传球。
(2)使用损失函数将预测与地面真实情况进行比较。损失函数输出一个误差值。
(3)使用该误差值,执行反向传播,其计算网络中每个节点的梯度。
相比之下,RNN 包含一个隐藏状态,它从先前的状态中获取信息:
隐藏状态的概念类似于为了做出更准确的预测而对顺序数据进行整合。考虑一下,如果您的数据仍然是运动中的球的照片,预测球的运动会容易得多:
没有序列信息,就不可能预测它的移动方向,相反,如果你知道以前的位置:
预测会更准确。同样的逻辑也适用于估计句子中的下一个单词,或者歌曲中的下一段音频。这个信息是隐藏状态,它是先前输入的表示。
消失梯度问题
然而,这变得有问题,为了训练一个 RNN,你使用一个叫做通过时间 (BPTT)的反向传播的应用。由于每一层的权重通过链规则进行调整,它们的梯度值将随着其在每个时间步长的传播而呈指数缩小,最终“消失”:
为了在 NLP 应用程序中说明这一现象:
Vanishing Gradient
通过输出 5,您可以看到来自“什么”和“时间”的信息几乎都消失了,如果没有这些信息,您认为您能多好地预测“是”和“它”之后的内容呢?
LSTM 和 GRU 作为解决方案
LSTMs 和 GRUs 是作为消失梯度问题的解决方案而创建的。它们内部有一种叫做门的机制,可以调节信息的流动。
对于 LSTM,有一个主单元状态,或传送带,以及几个控制新信息是否可以进入传送带的门:
在上面的问题中,假设我们要确定新句子中说话人的性别。我们将不得不选择性地忘记关于先前状态的某些事情,即,关于鲍勃是谁,他是否喜欢苹果,并记住其他事情,爱丽丝是一个女人,她喜欢桔子。
放大来看,LSTM 中的门通过三个步骤来完成这一过程:
(1)决定忘记什么(状态)
(2)决定记住什么(状态)
(3)状态的实际“遗忘”和更新
(4)输出的产生
总结一下,在 LSTM 中,遗忘门(1)决定哪些相关内容要从前面的步骤中保留。输入(2)门决定从当前步骤添加什么相关信息。输出门(4)决定下一个隐藏状态应该是什么。
对于作为新一代 rnn 的 GRU,它与 LSTM 非常相似,只是 GRUs 摆脱了小区状态,使用隐藏状态来传递信息。它也只有两个门,一个复位门和更新门:
(1)更新门的作用类似于 LSTM 的遗忘和输入门,它决定保留什么信息和丢弃什么信息,以及添加什么新信息。
候选隐藏状态的实现方式与 LSTM 相同,不同之处在于,在候选计算内部,它将重置一些先前的门(0-1 之间的向量,由线性变换定义)。
(2)重置门用于决定忘记多少过去的信息。
插图摘自
1。麻省理工 6。s094:Lex frid man
2 在 2017 年冬季讲授的自动驾驶汽车深度学习。LSTM 和 GRU 的图解指南:一步步的解释
了解 RNNs(递归神经网络)
Photo by Gemma Evans on Unsplash
能够记住过去的神经网络
第一次听说 RNN(递归神经网络)时,我有些不知所措。我读过的一篇文章声称,RNN 是一个具有记忆的神经网络——它可以记住数据的连续起伏,以便做出更明智的预测。
当时我的第一个想法是——RNN 与有许多滞后的线性回归(自回归模型)有什么不同?事实证明,RNN 不仅与众不同,而且更灵活、更强大。
但是,在我们将它添加到我们的预测工具包之前,我们应该尽最大努力对它的工作原理有一个直观的理解——从 RNN 如何能够记住过去开始。让我们找出答案。
顺序数据
RNN 是一种最适合处理序列数据的神经网络。如果你对神经网络不熟悉,那么你应该从我的理解神经网络帖子开始。在本文中,我将假设读者对什么是神经网络以及如何工作有一个基本的了解。
**什么是顺序数据——顺序很重要的数据。**序列数据的一些示例包括股票价格和利率(按时间排序)、博客文章中的文字(文字的顺序传达上下文和含义)或每天的平均温度(按时间排序)。
An example of sequential data (Apple stock price)
通常,对于序列数据,我们希望预测接下来会发生什么。例如,我们可能想要预测明天的温度,或者下个月股票的价格是高还是低。
简单线性回归预测
我能想到的最简单的预测是 AR(1)模型(一种具有单一滞后的自回归模型),在这种模型中,您只需使用前一个观察值来尝试预测下一个观察值。让我们以股票回报为例:
- 我们想预测 Ret、苹果下个月的回归。这是我们的目标变量。
- 我们唯一的特征变量是最近一个月的回报。
- 我们的数据集由 20 年的苹果月度股票回报时间序列组成(计算为从上个月最后一天到本月最后一天苹果股票价格的百分比变化)。
AR(1)模型将具有以下等式:
预测 _Ret(t) = m*Ret(t-1) + B
这应该看起来很熟悉,因为这是一条线的方程(Y = mX + B)。下图更详细地描绘了我们的 AR(1)模型。让我们一点一点来看:
- 我们使用我们的数据来估计绿色参数的最佳值——m 是我们直线的斜率,B 是截距。
- 请注意,左边的等式产生了一个带有帽子符号(^).)的输出帽子表示输出仅仅是我们的目标变量的一个估计,实际的股票回报。
- 我们的目标是最小化预测回报(^)和实际回报之间的差异。
- 请注意,第一个月返回的横向 Ret(0) 与第二个月返回的 Ret(1) 对齐, Ret(1) 与 Ret(2) 对齐,以此类推。这就是我们所说的使用前一个观察值(我们的特征)来预测下一个观察值(我们的目标)的含义——给定上个月的收益和我们的估计值 m 和 B ,我们可以预测下个月的收益。
AR(1) model
你不需要在市场上待很久就知道这不是一个好的模式。股票价格会因为各种原因波动(公司基本面、经济冲击、投资者恐惧/兴奋),有时甚至没有任何原因。所以我们不应该指望一个简单的 AR(1)模型就能做好。
相反,我们应该超越最近的观察,考虑整个序列**(实际上,我们应该考虑价格变动之外的数据,但那是另一个故事了)**。RNNs 允许我们这样做。
使用 RNNs 考虑整个序列
在我们深入研究 RNNs 之前,读者可能会问一个问题:“为什么不增加自回归模型中的滞后数?”也就是说,与其使用单一滞后,为什么不使用 AR(20)之类的东西,它使用最近 20 个月的月度回报来预测下个月的回报。答案是双重的:
- 用于我们的模型的月回报率实际上是我们需要调整的一个重要参数。选择错误的数字可能会导致糟糕的性能。此外,根据经济体制的不同,月度回报的最佳使用次数可能会有很大差异。RNN 绕过了这一点,因为它可以看到整个可用的历史回报,更重要的是,它自动决定在每个时间点给历史回报多少权重。所以我们不需要告诉 RNN 查看之前的 5、10 或 20 个回报,因为它天生就知道要这么做。
- 自回归模型是线性模型,因此假设我们的特征和目标之间存在线性关系。在存在非线性的情况下,这可能会导致性能问题。 **RNNs,尤其是当堆叠在更多 RNNs 或者密集层(一个密集层就是一层正常的神经网络神经元)上时,可以检测和捕获我们数据中的所有非线性关系。**事实上,对于 RNNs(以及一般的神经网络),我们应该更担心过拟合而不是欠拟合。
模特有记忆意味着什么?
让我们使用神经网络神经元重新创建 AR(1)模型。这里没有什么复杂的——回想一下,一个单一的神经元(如果你不熟悉神经元,请花点时间阅读我之前关于神经网络的博客)接受一个输入,将其乘以一个权重( m ,并向其添加一个偏差( B )。这些正是单变量线性回归的操作,这就是 AR(1)模型。 注意,为了简化说明,我们在这里忽略了激活功能。
AR(1) via a neural net neuron
现在让我们考虑一下如何给这个模型增加内存。在数量模型的情况下,记忆是什么?没有 100%正确的答案,但在我看来,记忆是从相关的过去经历中提取信息以帮助决策的能力。在建模方面,我们希望模型是动态的——换句话说,我们希望它能够根据它对情况的理解(基于它过去的经验)而变化。
我们的 AR(1)模型可能会用历史数据进行训练(回想一下,我们给它输入了苹果过去 20 年的月度股票回报),但它肯定没有记忆。在估计回归参数 m 和 B 时,AR(1)模型对我们给定的每个数据点进行同等加权。因此,它不能决定哪些数据点更相关,哪些数据点不相关(回归参数是静态的,一旦估计)。它是静态的,不是动态的。
记忆从何而来?
最后,是时候给我们的模型记忆了。它实际上只需要一个简单的技巧。请注意下图底部的附加内容:
Super simple RNN with memory
关键的添加是我们现在接受先前的输出,输出(t-1) ,将其与新的参数 *u,*相乘,并将其添加到先前的输出中。所以我们更新后的等式看起来像:
预测 _ Ret(t)=*u 预测 _Ret(t-1) + m*Ret(t-1) + B
或者用更一般的符号来表示:
输出(t)=*u 输出(t-1) + m*Ret(t-1) + B
那么这到底为什么构成记忆呢?要了解原因,我们需要向前跳一点,首先了解 RNN 是如何分析数据的。以下是 RNN 如何生成预测的 Python 伪代码:
# Our input data is 20 years of Apple's monthly returns
inputs = appl_monthy_returns
time_steps = range(inputs.shape[0])# List to store outputs
predictions = []# Initialize state to 0 (state is output[t-1] from above)
state = 0# Initialize u, m, and B
u = 1
m = 1
B = 1for t in time_steps:
input_t = inputs[t]
current_prediction = (u * state) + (m * input_t) + B
predictions.append(current_prediction)
state = current_prediction
# Function that updates m and B via backpropagation through time
u, m, B = BPTT(state, predictions, inputs)
好了,现在让我们浏览一下代码:
- 首先我们将状态,也就是我所说的输出(t-1) 初始化为 0,因为在 t=0 时没有先前的状态。
- 然后我们开始循环。输入多少,循环就运行多少次——在我们的例子中,我们有 20 年的月回报,所以它将运行 20*12 = 240 次。
- 在循环的每次迭代中,我们计算当前预测。然后,我们将计算出的预测附加到我们的输出列表中,预测——该列表是我们对下个月收益的预测。
- 接下来,我们设置状态等于当前预测,以便我们可以在下一个循环中使用它——也就是说,我们需要从 t=0 的预测来计算在 t=1 的预测。
- 最后,我们使用时间反向传播(超出了本文的范围)来更新 RNN 的参数 u 、 m 和 B 。
请注意几个关键事项:
- **状态是记忆的来源。**时间 t 的状态是先前的输出(从时间 t-1 开始),该先前的输出包括先前的模型参数(时间 t-1 的 u 、 m 和 B )以及时间 t-2 的输出。当 RNN 的当前时间步长(每个时间步长都是 for 循环的一次迭代)查看前一个时间步长的输出(状态)时,它实际上是在查看过去的自己。之前的输出是 RNN 对过去的自己进行快照并将其向前传递的方式。这就是为什么我在伪代码中称之为状态的原因——这只是总结(非常粗略地)模型决策过程的最新状态的一种方式。
- 我们用参数 u 乘以状态。这允许 RNN 决定使用多少内存(自身过去的快照)。
- 我们在 for 循环的每次迭代中做的最后一件事是更新 RNN 的参数( u 、 m 和 B )。这意味着在循环的每次迭代中,我们可能会看到 u 、 m 和 B 的不同值。
- for 循环的目的是让 RNN 在时间中向前移动。与一次性估计模型的线性回归不同,RNN 通过一次一个时间步长增量检查序列数据而逐渐收敛。
让我们写出这个等式(使用更简单的符号),以确保我们理解了所有内容。我将调用 t 时刻的输出, *O(t),*和输入, *X(t)。*让我们写出 O(3):
O(3) = u * O(2) + m * X (3) + B
我们也可以写出 O(2) :
O(2) = u_2 * O(1) + m_2* X (2) + B_2
代入 O(2) ,我们得到:
O(3)= u *(u _ 2 * O(1)+m _ 2 * X(2)+B _ 2)+m * X(3)+B
看看时间 t=3 时的输出如何包括先前的参数( u_2 、 m_2 和 B_2 ) — **这些参数是 RNN 如何在时间 t=2 时做出决定的,并且它们现在已经被传递到模型的当前迭代。**如果我们愿意,我们也可以展开 O(1) 来查看从时间 t=1 开始的参数也包括在内。
回想一下,我们将模型记忆定义为动态的,能够根据情况变化。RNN 现在可以做到这一点——通过以前的输出包括过去的快照,RNN 可以访问其历史参数,并可以根据情况需要决定是否将它们纳入其决策过程。
结论
写这篇文章让我学到了很多。以前我很清楚 RNNs 是如何工作的,但我一直想更好地理解人们说 RNNs 有记忆是什么意思。我希望你现在也能更好地理解它。
但是我们的工作还没有完成。理论上,rnn 应该能够熟练地利用过去的经验来做决策。但在现实中,他们遭遇了所谓的消失梯度问题。在未来的帖子中,我们将探索为什么会这样,以及一个称为 LSTM 的增强 RNN 如何帮助我们解决这个问题。在那之前,干杯!
更多数据科学与商业相关岗位由我:
理解得分倾向:评估 NBA 球员的混合模型方法
"谁是 NBA 的最佳得分手?"是我和朋友交谈时经常提到的一个问题。像勒布朗·詹姆斯、詹姆斯·哈登和斯蒂芬·库里这样的名字总是会出现。
通常很难找到一个单一的答案;当计分员之间存在差异时,这个问题变得更加微妙。当考虑到球员得分的不同情况时,我们如何区分天赋?
例如,我们如何像这样比较两个玩家:
Steph Curry
LeBron James
两位球员都是高产射手,但他们得分的方式不同。我们怎样才能让它们有共同点呢?
注意:在这个分析中,我不会使用罚球命中率和/或百分比。如果他们没有犯规,这个更好的头衔应该是“最佳射手”。
一个简单的方法:每杆得分
天真地说,我们对“最佳得分手”的第一个论点可能取决于使用每杆得分(PPS)作为一种分析手段。毕竟,谁每次尝试得分最多,理论上就应该是最佳得分手。这种方法有缺点,但是我们将在后面解决这些问题。
从 kaggle.com 收集 2014-2015 赛季的数据,我们可以开始一些初步的分析。如果我们聚集球员,取他们每次投篮的平均得分,我们可以看到谁是最成功和最不成功的投篮手。
Top 10 PPS (2014–2015)
从左边的列表中,我们可以看到最有效率的得分手是抓高球的大个子(乔丹/钱德勒)。混合在一起的,是像科弗和巴比特这样精准的三分球投手。
如果我们太天真,我们会简单地认为他们是联盟中最好的“投篮手”。然而,从我们的篮球知识来看,我们知道靠近篮筐的投篮(例如乔丹/钱德勒接到的扣篮和高球)比远一点的投篮容易得多。此外,我们也知道更多的空位投篮比高度争议的投篮更容易。让我们用数据验证来测试我们的直觉,以确保我们是在正确的轨道上。
Shot Distance vs Def Distance (yellow indicates make, purple indicates miss)
如果我们看上面的散点图,我们可以看到球员离篮筐越远,就越容易失误。当他们离篮筐更近而防守队员离得更远时,他们很少会失手。我们可以把这想象成一个过渡上篮/扣篮的机会。我们的直觉是正确的,我们可以进行更细致的分析。
稍微好一点的方法:逻辑回归
为了说明这些不同的因素(投篮距离,防守距离),我们可以建立一个简单的逻辑回归模型来预测投篮是否进了。特征包括截击项、射门距离和防守距离。逻辑回归假设每个样本的误差产生过程是相同的。在我们采用这种方法时,请记住这个假设。
Model Generation
仅仅建立一个简单的模型,我们可以看到,盲目猜测镜头转换会给我们大约 55%的准确性。该模型提高了约 5%。随着更多的超参数调整,我相信我们可以做得更好,但不是令人难以置信的。
检查残差显示,模型并不完全拟合(不以 0 为中心,并且略有偏斜)。这可能表明违反了前面提到的 i.i.d .假设。
Residuals of Fit
这种违反可能是由于模型对玩家是盲目的。也许每个玩家都有不同的出错过程。从本质上来说,基于一些隐含的潜在技能水平,每个球员对投篮是否会有不同的影响。
为了解决这个问题,我们可以做三件事之一:
- 在现有的逻辑回归模型中添加使用一次性编码表示的玩家
- 为每个玩家建立一个单独的模型
- 以某种方式结合上述策略
在第一种方法中,我们并没有真正以他们自己独特的方式对待不同球员的每一次投篮尝试。我们仍然假设每个镜头来自相同的数据生成过程,所以我们的假设仍然会被违反,尽管我们的准确性可能会提高。
在第二种方法中,我们当然会考虑不同玩家的不同错误,但是我们最终会建立很多模型!
第三种方法是我的首选方法,它将上述策略结合到一个模型中。这就是所谓的分层线性建模。
一个不完美但改进很多的模型:HLMMs
分层线性混合模型(HLMMs)是指处理具有清晰分层结构的分组数据的模型。例如,这些模型已被用于研究不同学区对学生 SAT 成绩的效应。
在我们的例子中,我们希望看到不同球员对击球结果的影响。我们的数据(投篮次数)可以按以下方式分组:
- 通过游戏
- 按团队
- 按玩家
- 由最近的防守队员防守
- 按季度
这些不同的分组都是为了解释数据中的一些差异。另一种思考方式是,上述每一组对击球结果都有不同的影响。例如,也许数据中的大多数差异(即成功或失败)是由“玩家”组决定的。这些类型的分组被称为“随机效应”。
HLMMs 由固定效果和随机效果组成(因此得名“混合”)。固定效应是被认为同等影响所有群体的变量。例如,在我们的例子中,镜头的距离可以被认为是一个固定的效果。
这些模型也解释了分组中的不平衡。例如,让我们说在 10000 次投篮中,我们只有一个板凳球员的 6 个数据点,因为这个球员通常不怎么上场。如果我们使用我们的第一种方法,这 6 个数据点在决定这个玩家的价值时会稍微不重要。如果我们使用我们的第二种方法,为每个玩家建立一个单独的模型,这个玩家的影响将会严重过度拟合。想象一下,如果这个球员 6 投全中;这位模特会对他评价很高,但可能无法解释他的运气。
HLMMs 在这两种方法之间找到了一个折中的办法。简单来说,如果一个群体的成员没有太多的数据,他的效果会更接近群体所有成员的均值。如果他这样做了,他的效果会更远。这就是所谓的“缩水”。如果你有兴趣了解更多关于混合模型的信息,这个链接会给你一个很好的起点。
将 HLMMs 应用于拍摄数据
让我们重温一下我们的每次投篮得分分析。之前,我们只显示了球员的每次投篮得分,但没有显示他们的投篮次数。包含这些信息可以显示玩家是否“不走运”。
在左边,我们可以看到球员之间投篮次数的巨大差异。以斯蒂芬·库里和希度·特克格鲁的差异为例。两者的 PPS 都一样,但是 Steph 的尝试次数多了 9 倍!直觉告诉我们,斯蒂芬的信息更可信,但我们能肯定地说,斯蒂芬是一个比特科格鲁单独使用这些信息更好的投篮手吗?
事实证明,我们可以尝试使用假设检验,但这是假设斯蒂芬和赫多拍摄了完全相同类型的照片。因为这是极不可能的,我们将忽略这条路线。
我们能做的是建立一个 HLMM 并解释模型的系数。
HLMM Specification
以上是如何指定混合模型的。贝塔项是固定效应“X”的系数,“u”是随机效应“Z”的系数。请注意,模型是线性的;系数的正值表示相关预测值的增加(当其他值保持不变时),与预测值“y”的适当增加相一致。
在篮球方面,我们可以看到一个球员的系数如何与预测值,投篮结果相关联。一个更好的射手会有更大的正系数。
构建 HLMM:使用贝叶斯方法
让我们重温一下我们的数据。
Data Frame for Shot Data
让我们在数据中寻找可能引入差异的分组。我想到了一些:
- 运动员
- 球员的防守队员
- 游戏 ID
- 玩家的团队
- 玩家的对手
- 四分之一
现在,我们可以寻找可能在人群中固定的影响:
- 主场/客场比赛
- 射击距离
- 防守距离
- 运球次数
- 触摸时间
- 季度剩余时间
- 计时器
通常,当使用贝叶斯方法时,人们希望拟合“最大模型”。这意味着,如果一个人想要在我们的情况下,确认或否认一个假设(即谁是最好的射手),我们应该指定最大随机效应结构。那是什么意思?
考虑我们上面的功能。我们可以这样设置固定效果,并为每个组指定一个随机截距。然而,直觉上我们知道射门距离、防守距离和射门前运球对每个球员的影响是不同的。因此,我们可以假设一种结构,它包括上述每个特征的随机斜率以及随机截距。此外,我们可以假设防守者的距离对每个防守者的影响不同,并包括一个随机斜率。
使用 R 的 brms 软件包,我可以拟合一个具有“最大结构”的模型,并解释结果。请注意,这将需要一段时间,取决于您运行的迭代和链的数量。
分析模型结果
我最终拟合的模型在 r 中有这个公式。
FGM ~拦截+位置+ CLOSE_DEF_DIST + (1 +射门 _DIST +运球|球员)+(1 |最近的 _ 后卫)
这意味着,响应(FGM)是固定效应(主队/客场和后卫距离)+最近的后卫和球员的截距+球员的随机坡度效应乘以射门距离和运球次数的函数。响应 FGM 是二进制 0/1 响应;这意味着系数必须以对数标度进行解释,因为影响是使用 logit 链接函数进行转换的。
为了捕捉某个玩家相对于其他玩家的影响,我们需要做一点数学计算。回想一下,如果其他预测因子保持不变,系数测量的是反应。在我们指定的模型中,我们有几个变量保持不变:
- 位置(家庭/外出)
- 防守距离
- 射击距离
- 运球
- 拥护者
因为我换算了防守距离、射门距离和运球次数的值,它们的中间值应该是 0。我们将用它作为我们的常数,使我们的计算更容易。为了简单起见,我们假设每个玩家都在家。接下来,我们必须固定一个防御者值;我们将使用中间值。随机效应用一个正态(均值=0,方差=sigma)来指定,所以中值应该是 0!这使得我们的最终计算相当容易。
计算玩家效果
logit 链接执行以下转换:
ln(p/(1-p)) = βX + Zu
回想一下,“Z”和“ β 分别是随机效应和固定效应的系数。“P”是成功结果的概率值(在我们的例子中是命中)。
在我们的例子中(因为我们使用中值 0),这分解为:
ln(p/(1-p)) =拦截+主场+球员
我称方程的 RHS 为线性预测值。现在,求解 p,我们得到:
e(linear_predictor)/(1+e(linear_predictor)
我们现在可以用概率来比较球员对射门转换的影响了!
因为我们去贝叶斯,这不是我们能做的。我们实际上可以使用生成的样本来查看每个玩家效果的概率分布。这比使用单一值要好得多,因为我们可以比较估计中的不确定性。
我将在下面用一个例子来说明,重温斯蒂芬·库里和希度·特克格鲁的例子。回想一下,斯蒂芬和海多的得分相当,但是斯蒂芬的出手次数是他的 9 倍。
Player Effects
橙色的是特科格鲁的投篮转换值分布,蓝色的是库里的。x 轴代表转换的概率,而 y 轴代表相应 x 值的非归一化频率。
一些要带走的东西:
- 特科格鲁的分布更加分散。这意味着我们对他的影响不太确定。这是有道理的,因为我们只有 100 次投篮机会。因此,库里的分布更紧密,这意味着模型对他的效果更确定。
- 特科格鲁的分布峰值向库里的左移。这表明,模型估计库里更有可能比特科格鲁对投篮命中率有更大的影响!然而,我们可以看看分布的交叉点,看看库里拥有更高效果的频率。我们简单取一下库里的“x”值比特科格鲁大多少倍的平均值。从上面的分布来看,由样本确定为 91.875%。斯蒂芬比特科格鲁有 92%的可能拥有更好的转化能力。
我们可以对球员的所有组合重复这个过程,并进行比较,但更有用的是考虑一个球员如何比其他人增加投篮的概率。我不会在发布会上发言。相反,我将只使用样本的中间值。
然后,我们可以从每个玩家的效果中减去所有玩家的转换中值,并查看每个玩家的转换高于替换的概率。
Top 20 Shot Converters
我们可以在左侧看到 2014-2015 赛季前 20 名和后 20 名击球手的名单。我们注意到了什么?有什么突出的?我们如何用我们建立的模型来解释这些结果?为什么我们会看到某些玩家凌驾于其他人之上?
Bottom 20 Shot Converters
结束语
数据有些嘈杂。最近的防守者可能只是一个帮忙的球员,而不是一个真正争球的球员。这些数据缺少球员投篮的类型(跳投、扣篮、勾手)。数据还缺乏球员投篮时周围的密度(球道是否拥挤,或者只有一个防守队员时是否相当清晰)。我们注意到大人物受到相当重的惩罚;它们经常位于替换级别图表的底部。这可能是因为这些玩家经常参与提示,而这些提示通常在第一次尝试时不会完全转化。同样,拥有更精细的数据(包括分数差异以剔除“关键”球员)将是有益的。请在下面留下您的评论。请记住,这仅适用于 2014-2015 赛季,但该流程也适用于今天的数据。
承认
我要感谢 Alex Hayes 帮助我理解贝叶斯环境下混合模型的方差-协方差结构。我还要感谢我的朋友们,他们不断地参与激烈的辩论,给了我分析性地解决辩论的灵感。
用 UNET 理解语义分割
盐鉴定案例研究
目录:
- 介绍
- 先决条件
- 什么是语义切分?
- 应用程序
- 商业问题
- 理解数据
- 了解卷积、最大汇集和转置卷积
- UNET 建筑与培训
- 推理
- 结论
- 参考
1.介绍
计算机视觉是一个跨学科的科学领域,研究如何让计算机从数字图像或视频中获得高层次的理解。从工程的角度来看,它寻求将人类视觉系统可以完成的任务自动化。(维基百科)
CV is a very interdisciplinary field
深度学习使得计算机视觉领域在过去几年中迅速发展。在这篇文章中,我想讨论一下计算机视觉中的一个特殊任务,叫做语义分割。尽管研究人员已经提出了许多方法来解决这个问题,但我将谈论一种特殊的架构,即 UNET ,它使用完全卷积的网络模型来完成任务。
我们将使用 UNET 为 Kaggle 主办的 TGS 盐鉴定 挑战赛构建第一套解决方案。
除此之外,我写这篇博客的目的还在于为图像理解提供一些关于卷积网络中常用运算和术语的直观见解。其中一些包括卷积、最大池、感受野、上采样、转置卷积、跳过连接等。
2.先决条件
我将假设读者已经熟悉机器学习和卷积网络的基本概念。此外,你必须有一些与 Python 和 Keras 库 ConvNets 的工作知识。
3.什么是语义切分?
计算机理解图像有不同的粒度级别。对于这些级别中的每一个,都有一个在计算机视觉领域中定义的问题。从粗粒度到更细粒度的理解开始,让我们在下面描述这些问题:
a .图像分类
Image Classification
计算机视觉中最基本的构建模块是图像分类问题,其中给定一幅图像,我们期望计算机输出一个离散的标签,它是图像中的主要对象。在图像分类中,我们假设图像中只有一个(而不是多个)对象。
b .本地化分类
Classification with localization
在与离散标签一起定位时,我们还期望计算机准确定位图像中对象的位置。这种定位通常使用边界框来实现,该边界框可以由相对于图像边界的一些数值参数来标识。即使在这种情况下,假设每个图像只有一个对象。
c.目标检测
Object Detection
对象检测将定位扩展到下一个级别,现在图像不再局限于只有一个对象,而是可以包含多个对象。任务是对图像中的所有对象进行分类和定位。这里同样使用边界框的概念来完成定位。
d.语义分割
Semantic Segmentation
语义图像分割的目标是给图像的每个像素标上所表示内容的相应类别。因为我们对图像中的每个像素进行预测,这项任务通常被称为密集预测。
注意,与前面的任务不同,语义分割的预期输出不仅仅是标签和边界框参数。输出本身是高分辨率图像(通常与输入图像大小相同),其中每个像素都被分类到特定的类别。因此,这是一个像素级的图像分类。
e.实例分割
Instance Segmentation
实例分割比语义分割领先一步,在语义分割中,除了像素级分类,我们还希望计算机能够分别对一个类的每个实例进行分类。例如,在上面的图像中有 3 个人,从技术上讲,是类“Person”的 3 个实例。所有这 3 个都是单独分类的(用不同的颜色)。但是语义分割并不区分特定类的实例。
如果您仍然对对象检测、语义分割和实例分割的区别感到困惑,下图将有助于澄清这一点:
Object Detection vs Semantic Segmentation vs Instance Segmentation
在这篇文章中,我们将学习使用全卷积网络(FCN)UNET 来解决语义分割问题。
4.应用程序
如果你想知道语义分割是否有用,你的疑问是合理的。然而,事实证明,视觉中的许多复杂任务都需要对图像有这种精细的理解。例如:
a.自动驾驶汽车
自动驾驶是一项复杂的机器人任务,需要在不断进化的环境中进行感知、规划和执行。这项任务也需要极其精确地完成,因为安全是最重要的。语义分割提供关于道路上自由空间的信息,以及检测车道标记和交通标志。
Source: https://www.youtube.com/watch?v=ATlcEDSPWXY
b.生物医学图像诊断
机器可以增强放射科医生进行的分析,大大减少运行诊断测试所需的时间。
Source: https://arxiv.org/abs/1701.08816
c.地理传感
语义分割问题也可以被认为是分类问题,其中每个像素被分类为一系列对象类中的一个。因此,有一个卫星图像土地利用制图的用例。土地覆盖信息对于各种应用都很重要,例如监测森林砍伐和城市化地区。
识别土地覆盖的类型(例如,城市、农业、水等区域)。)对于卫星图像上的每个像素,土地覆盖分类可以被视为多类语义分割任务。道路和建筑物检测也是交通管理、城市规划和道路监控的重要研究课题。
大规模公开可用数据集很少(如:SpaceNet),数据标注一直是分割任务的瓶颈。
Source: https://blog.playment.io/semantic-segmentation/
d.精准农业
精准农业机器人可以减少农田中需要喷洒的除草剂数量,作物和杂草的语义分割可以实时帮助它们触发除草行动。这种先进的农业图像视觉技术可以减少对农业的人工监控。
Source: https://blog.playment.io/semantic-segmentation/
我们还将考虑一个实际的真实世界案例研究,以理解语义分割的重要性。问题陈述和数据集将在以下章节中介绍。
5.商业问题
在任何机器学习任务中,总是建议花相当多的时间来恰当地理解我们旨在解决的业务问题。这不仅有助于有效地应用技术工具,还能激励开发人员使用他/她的技能来解决现实世界的问题。
TGS 是一家领先的地球科学和数据公司,该公司使用地震图像和 3D 渲染来了解地球表面下哪些区域含有大量石油和天然气。
有趣的是,含有石油和天然气的地表也含有大量的盐。因此,在 T4 地震技术的帮助下,他们试图预测地球表面的哪些区域含有大量的盐。
不幸的是,专业地震成像需要专业的人类视觉来准确识别盐体。这导致高度主观和可变的渲染。此外,如果人类预测不正确,可能会给石油和天然气公司的钻探人员造成巨大损失。
因此,TGS 举办了一场 Kaggle 比赛,利用机器视觉以更高的效率和精确度来解决这个问题。
要了解更多挑战信息,请点击 此处 。
阅读更多关于地震技术的内容,点击 此处 。
6.理解数据
从 这里 下载数据文件。
为简单起见,我们将只使用包含图像及其相应遮罩的 train.zip 文件。
在图像目录中,有 4000 张地震图像被人类专家用来预测该地区是否有盐层。
在 masks 目录中,有 4000 幅灰度图像,它们是相应图像的实际地面真实值,表示地震图像是否包含盐沉积,如果包含,在哪里。这些将用于建立监督学习模型。
让我们将给出的数据形象化,以便更好地理解:
Sample data point and corresponding label
左边的图像是地震图像。画黑色边界只是为了便于理解,表示哪个部分含盐,哪个部分不含盐。(当然这个边界不是原始图像的一部分)
右边的图像被称为遮罩,它是地面真实标签。对于给定的地震图像,这是我们的模型必须预测的。白色区域表示盐沉积,黑色区域表示没有盐。
让我们再看几张图片:
Sample data point and corresponding label
Sample data point and corresponding label
Sample data point and corresponding label
请注意,如果蒙版完全是黑色的,这意味着在给定的地震图像中没有盐沉积。
从上面的几幅图像可以清楚地推断出,对于人类专家来说,对地震图像进行准确的屏蔽预测是不容易的。
7.了解卷积、最大汇集和转置卷积
在深入研究 UNET 模型之前,了解卷积网络中常用的不同运算非常重要。请记下所用的术语。
一、卷积运算
卷积运算有两个输入
I)尺寸为(nin×nin×通道)的 3D 体积(输入图像)
ii)一组“k”个过滤器(也称为内核或特征提取器),每个过滤器的大小为(f×f×通道),其中 f 通常为 3 或 5。
卷积运算的输出也是大小为(nout x nout x k)的 3D 体(也称为输出图像或特征图)。
nin 和 nout 之间的关系如下:
Convolution Arithmetic
卷积运算可以如下所示:
Source: http://cs231n.github.io/convolutional-networks/
在上面的 GIF 中,我们有一个大小为 7x7x3 的输入体积。两个过滤器,每个尺寸为 3x3x3。填充=0,步幅= 2。因此输出体积是 3x3x2。如果你对这种算法感到不舒服,那么在继续下一步之前,你需要先修改卷积网络的概念。
经常使用的一个重要术语叫做感受野。这只是输入体积中特定特征提取器(滤波器)正在查看的区域。在上面的 GIF 中,过滤器在任何给定情况下覆盖的输入体积中的 3x3 蓝色区域是感受野。这有时也被称为语境。
简单来说,感受野(context)就是 滤波器在任意给定时间点覆盖的输入图像区域。
ii)最大汇集操作
简单来说,池化的作用就是减少特征图的大小,使我们在网络中的参数更少。
例如:
Source: https://www.quora.com/What-is-max-pooling-in-convolutional-neural-networks#
基本上,从输入特征图的每个 2×2 块中,我们选择最大像素值,从而获得汇集的特征图。请注意,过滤器的大小和跨度是最大池操作中的两个重要的超参数。
其思想是只保留每个区域的重要特征(最大值像素),并丢弃不重要的信息。所谓重要,我指的是最能描述图像上下文的信息。
这里要注意的非常重要的一点是,卷积运算,特别是合并运算,都会减小图像的大小。这被称为下采样。在上面的示例中,合并前图像的大小是 4x4,合并后是 2x2。事实上,下采样基本上意味着将高分辨率图像转换成低分辨率图像。
因此,在汇集之前,存在于 4×4 图像中的信息,在汇集之后,(几乎)相同的信息现在存在于 2×2 图像中。
现在,当我们再次应用卷积运算时,下一层中的滤波器将能够看到更大的上下文,即,随着我们深入网络,图像的尺寸减小,然而感受野增大。
例如,下面是 LeNet 5 架构:
LeNet 5
请注意,在典型的卷积网络中,图像的高度和宽度逐渐减小(由于汇集,向下采样),这有助于更深层中的滤波器聚焦于更大的感受域(上下文)。然而,通道/深度的数量(使用的过滤器数量)逐渐增加,这有助于从图像中提取更复杂的特征。
凭直觉,我们可以对联营作业作出如下结论。通过下采样,模型更好地理解了图像中存在的是什么,但是它丢失了其存在的的信息。****
iii)上采样的需要
如前所述,语义分割的输出不仅仅是一个类标签或一些边界框参数。事实上,输出是一个完整的高分辨率图像,其中所有像素都被分类。
因此,如果我们使用具有池层和密集层的常规卷积网络,我们将丢失“在哪里”的信息,而只保留“是什么”的信息,这不是我们想要的。在细分的情况下,我们既需要“什么”也需要“哪里”的信息。
因此,需要对图像进行上采样,即将低分辨率图像转换为高分辨率图像,以恢复“在哪里”的信息。
在文献中,有许多对图像进行上采样的技术。其中一些是双线性插值、三次插值、最近邻插值、解卷积、转置卷积等。然而,在大多数先进的网络中,转置卷积是对图像进行上采样的首选。
iv)转置卷积
转置卷积(有时也称为去卷积或分数步长卷积)是一种使用可学习参数对图像进行上采样的技术。
我不会描述转置卷积是如何工作的,因为 Naoki Shibuya 已经在他的博客 中用转置卷积 进行了出色的采样。我强烈建议你通读这篇博客(如果需要的话可以多次通读)以了解转置卷积的过程。
然而,在高级别上,转置卷积是与正常卷积完全相反的过程,即,输入体积是低分辨率图像,而输出体积是高分辨率图像。
在博客中,很好地解释了如何将正常卷积表示为输入图像和滤波器的矩阵乘法,以产生输出图像。通过对滤波器矩阵进行转置,我们可以反转卷积过程,因此称为转置卷积。
v)本节概述
阅读本节后,您必须熟悉以下概念:
- 感受领域或环境
- 卷积和汇集操作对图像进行下采样,即将高分辨率图像转换为低分辨率图像
- 最大池操作通过增加感受野来帮助理解图像中有“什么”。然而,它往往会丢失对象“在哪里”的信息。
- 在语义分割中,不仅重要的是知道图像中存在“什么”,同样重要的是知道它存在于“哪里”。因此,我们需要一种方法来将图像从低分辨率向上采样到高分辨率,这将帮助我们恢复“在哪里”的信息。
- 转置卷积是执行上采样的最优选选择,上采样基本上是通过反向传播来学习参数,以将低分辨率图像转换为高分辨率图像。
如果您对本节中解释的任何术语或概念感到困惑,请随意再读一遍,直到您熟悉为止。
8.UNET 建筑与培训
UNET 由 Olaf Ronneberger 等人开发,用于生物医学图像分割。该架构包含两条路径。第一条路径是收缩路径(也称为编码器),用于捕获图像中的上下文。编码器只是一个传统的卷积和最大池层堆栈。第二条路径是对称扩展路径(也称为解码器),用于使用转置卷积实现精确定位。因此,它是一个端到端的完全卷积网络(FCN),即它只包含卷积层,不包含任何密集层,因此它可以接受任何大小的图像。
在原始论文中,UNET 是这样描述的:
如果你不明白,没关系。我将尝试更直观地描述这个架构。请注意,在原始纸张中,输入图像的大小是 572x572x3,但是,我们将使用大小为 128x128x3 的输入图像。因此,不同位置的尺寸将与原始纸张中的尺寸不同,但核心组件保持不变。
下面是架构的详细说明:
Detailed UNET Architecture
注意事项:
- 2@Conv 层意味着应用两个连续的卷积层
- c1,c2,…c9 是卷积层的输出张量
- p1、p2、p3 和 p4 是最大池层的输出张量
- u6、u7、u8 和 u9 是上采样(转置卷积)层的输出张量
- 左手边是收缩路径(编码器),在这里我们应用常规卷积和最大池层。
- 在编码器中,图像的尺寸逐渐减小,而深度逐渐增加。从 128x128x3 到 8x8x256
- 这基本上意味着网络学习了图像中的“什么”信息,然而它丢失了“在哪里”的信息
- 右手边是扩展路径(解码器),在这里我们应用转置卷积和常规卷积
- 在解码器中,图像的尺寸逐渐增大,深度逐渐减小。从 8x8x256 到 128x128x1
- 直观上,解码器通过逐渐应用上采样来恢复“在哪里”的信息(精确定位)
- 为了获得更精确的位置,在解码器的每一步,我们通过将转置卷积层的输出与来自同一级别的编码器的特征图连接来使用跳过连接:
U6 = U6+C4
u7 = u7+C3
u8 = u8+C2
u9 = u9+C1
在每次连接之后,我们再次应用两个连续的常规卷积,以便模型可以学习组装更精确的输出 - 这使得该建筑呈对称的 U 形,因此得名 UNET
- 在高层次上,我们有以下关系:
输入(128x128x1) = >编码器= > (8x8x256) = >解码器= >输出(128x128x1)
下面是定义上述模型的 Keras 代码:
培养
模型用 Adam 优化器编译,我们使用二元交叉熵损失函数,因为只有两个类(盐和无盐)。
我们使用 Keras 回调来实现:
- 如果验证损失在 5 个连续时期内没有改善,则学习率衰减。
- 如果验证损失在 10 个连续时期内没有改善,则提前停止。
- 仅当验证损失有所改善时,才保存权重。
我们使用的批量大小为 32。
请注意,可能有很大的余地来调整这些超参数,并进一步提高模型性能。
该模型在 P4000 GPU 上训练,训练时间不到 20 分钟。
9.推理
注意,对于每个像素,我们得到一个 0 到 1 之间的值。
0 代表无盐,1 代表盐。
我们以 0.5 为阈值来决定一个像素是归类为 0 还是 1。
然而,决定阈值是棘手的,可以被视为另一个超参数。
让我们看看训练集和验证集的一些结果:
训练集的结果
********
验证集的结果
训练集上的结果相对好于验证集上的结果,这意味着模型遭受过拟合。一个明显的原因可能是用于训练模型的图像数量较少。
10.结论
感谢您对博客的兴趣。如果您有任何意见、反馈和建议,请留下。
完整代码在我的 GitHub 回购T3 这里。
11.参考
- https://arxiv.org/abs/1505.04597
- https://www . depends-on-the-definition . com/unet-keras-segmenting-images/
- https://towards data science . com/up-sampling-with-transposed-convolution-9 AE 4 F2 df 52d 0
从不同的角度理解随机梯度下降
gradient descent path in a contour line with 2D feature space (from Genevieve B. Orr)
随机优化 [1]是训练神经网络时常用的方法。在此基础上,还有像 *SGD with Momentum、Adagrad、*和 *RMSProp、*这样的方法可以给出不错的训练结果。上图显示了 2D 等高线中的之字形梯度下降路径。人们相信使用随机梯度下降法,因为它被认为是计算友好的,因为模型有大量的参数。也就是说,SGD 可以达到的最好结果相当于具有相同超参数设置的普通梯度下降法。然而,我们会发现,在很多情况下,SGD 在测试集上提供了更好的性能。最近在高级深度学习课程中,我被教导从不同的角度来看待 SGD 方法,它认为它类似于正则化。
香草梯度下降
假设我们的损失函数看起来像这样:
似乎 B 点是我们通过执行梯度下降想要到达的地方,因为 B 处于全局最小值。但是,如果参数有很小的偏移,损失的值会有很大的变化。我们应该注意到,这个损失函数仅仅是基于我们可以看到的训练集绘制的。通常,测试数据会有很小的变化。所以我们想要达到的理想点实际上是 D,因为它给出了我们模型的最佳概括能力。
在香草梯度下降的情况下,如果我们在点 A 初始化模型,它将一直下降到点 B。并且它没有办法从 B 周围的凹区域逃脱(具有合理的学习速率)。解决这个问题的直接方法是引入随机采样噪声。然而,与训练数据的学习分布相比,随机采样的噪声通常是微不足道的。
随机梯度下降
随机梯度下降方法是这个问题的完美解决方案。SGD 使用 MAP 估计值计算的梯度如下所示:
在每次迭代中,我们将随机对数据进行采样,并将梯度计算为整个数据集上真实梯度的近似值。
由于在一次迭代中用于训练的所有数据都是从训练集中随机采样的,因此它们将具有不同的分布,从而给我们更多的自由来探索损失函数的空间。所以更有可能达到我们想要的最小值(如上图)。迷你浴池的尺寸越小,损失功能空间越大。在我看来,随机梯度下降方法与*交叉验证、*方法具有相同的高级精神,因为两种方法都一次性使用部分训练数据,并尝试不同的可能分布,以增加模型的泛化能力。
随机梯度朗之万动力学
在[2]中,上述思想得到了进一步发展。通过随机梯度朗之万动力学(SGLD) 计算的梯度为:
SGLD 方法将高斯噪声引入到梯度中,这确保了最终探索整个支持。它被看作是随机微分方程的离散化。通过遵循马尔可夫链蒙特卡罗(MCMC)技术,SGLD 方法使模型能够捕捉参数不确定性,这是一种流行的贝叶斯方法来执行不确定性估计。
参考
[1] Robbins H,Monro S .一种随机逼近方法[J].数理统计年鉴,1951:400–407。
[2] Welling M,Y W .通过随机梯度朗之万动力学进行贝叶斯学习[C]//第 28 届国际机器学习会议(ICML-11)论文集。2011: 681–688.
理解流处理和 Apache Kafka
观看节目时被称为 Torrenting,包括下载整个 mp4 文件并在本地观看。相比之下,流式传输意味着当数据包到达时,您正在观看节目。流处理是处理连续的输入数据流的行为。
活动采购
假设我们负责一个社交媒体平台。在传统方法中,我们将所有用户数据存储在某种持久存储中,比如关系数据库。每当其中一个用户更新他们的状态时,数据库中的值就会被覆盖。Event sourcing 认为这不是存储数据的好方法,因为我们可能希望使用以前的值进行业务分析。相反,我们应该单独记录数据库发生的每个变化。回到我们的例子,每次用户改变他们的状态,我们会创建一个单独的条目,而不是覆盖以前的值。每当数据库收到读取数据的请求时,我们都返回最新的值。
原始事件的形式非常适合写入,因为您不需要搜索条目并更新它。相反,您可以将事件附加到日志的末尾。另一方面,聚合数据的形式非常适合读取。如果用户正在查看某人的状态,他们不会对导致当前状态的所有修改历史感兴趣。因此,将写入系统的方式与读取系统的方式分开是有意义的。我们会回来的。
现在,假设一个用户试图查看他们的朋友列表。为了将这些信息呈现给用户,我们必须对数据库执行读取查询。假设活跃用户的数量从 10,000 增加到 100,000。这意味着我们必须将与这 90,000 个额外用户中的每一个相对应的信息存储到我们的数据库中。一些非常聪明的人已经想出了可以达到 O(log n)量级性能的搜索算法。然而,随着条目数量的增加,搜索某个值所花费的时间仍然会增加。因此,在扩展时实现一致的低延迟要求您利用缓存。缓存将只保存全部数据的一个子集。因此,查询花费的时间更少,因为它们不必遍历那么多的元素。
双重写入
缓存和其他形式的冗余数据(如索引)通常对于获得良好的读取性能至关重要。然而,保持不同系统之间的数据同步成为一个真正的挑战。在双写方法中,更新所有适当位置的数据是应用程序代码的责任。
假设我们有一个用户可以互相发送消息的应用程序。当发送新消息时,我们要做两件事:
- 将邮件添加到用户的收件箱
- 增加用户未读邮件的数量
我们保留一个单独的计数器,因为我们一直在用户界面上显示它,如果每次需要显示未读邮件数时都要扫描邮件列表来查询未读邮件数,会太慢。该计数来自收件箱中的实际邮件。因此,每当未读消息的数量发生变化时,我们需要相应地更新计数器。
假设我们有以下场景:
客户端向另一个用户发送消息,将其插入收件人的收件箱。然后,客户端请求增加未读计数器。然而,就在那一刻,出现了问题,可能是数据库宕机,或者某个进程崩溃,或者网络中断。不管什么原因,对未读计数器的更新都会失败。
现在,我们的数据库不一致。我们不一定知道哪些计数器与哪些收件箱不一致。因此,解决这个问题的唯一方法是定期重新计算值,如果我们有很多用户,这可能会非常昂贵。
即使一切运行顺利,我们仍然会遇到像比赛条件这样的问题。
假设我们有以下场景:
第一数据存储中的 x 值由第一客户端设置为 y,然后由第二客户端设置为 z。
在第二个数据存储中,请求以不同的顺序到达,值首先被设置为 z,然后是 y。
现在,两个数据存储区再次不一致。
分布式流媒体平台
很多时候,我们希望将应用程序数据用于其他目的,例如训练机器学习模型或可视化趋势。但是,我们不能直接从数据库中读取值,因为那样会占用资源,而这些资源本来可以用来处理用户对数据的请求。因此,我们必须在其他地方复制数据,这不可避免地会导致前面讨论过的相同问题。
那么,我们如何在几个不同的系统中获得相同数据的副本,并在数据变化时保持它们一致的同步呢?有多种可能的解决方案,但在本文中,我们将介绍分布式流媒体平台 Apache Kafka。
在大多数应用程序中,日志是对用户隐藏的实现细节。例如,大多数关系数据库使用预写日志。实际上,每当数据库必须对底层数据进行更改时,它都会将这些更改附加到预写日志中。如果数据库在执行写操作时崩溃,它可以重置为已知状态。
在卡夫卡那里,木头成了一等公民。换句话说,主要的存储机制是日志本身。为了使 Kafka 具有水平可伸缩性,日志被分割成多个分区。每个分区都存储在磁盘上,并在几台机器上复制,因此它可以承受机器故障而不会丢失数据。
回到我们的社交媒体应用程序的例子,用户所做的任何更改都将被发送到后端服务器。然后,服务器可以将这些更改作为事件添加到日志中。数据库、索引、缓存和所有其他存储系统都可以通过顺序读取日志来构建。
回想一下,在事件源的上下文中,日志是写入的理想选择,因为它们只需将数据附加到末尾,而数据库是读取的理想选择,因为它们只包含当前状态。
卡夫卡之所以能够解决竞争条件的问题,是因为它实施了严格的事件排序。每个消费者从日志的开头开始,读取每个事件,更新其内容,直到它被赶上。在数据库崩溃的情况下,它能够从它停止的地方继续读取日志,直到它被赶上。
最后的想法
流处理指的是我们如何处理新的输入数据。Kafka 是一个开源的分布式流媒体平台,它提供了一种跨不同存储系统可靠获取数据的机制。
理解种族划分的研究
种族划分的研究通常是在回归分析的背景下进行的。简而言之,回归能够评估某些感兴趣的变量(比如学生的考试成绩)与定义所述学生的变量(比如种族、家庭收入、父母的职业、父母的教育程度等)之间的关系。
形象地说,用 x 表示定义学生的变量,用 y 表示学生的考试成绩,回归表明:
Y=f(x_1,x_2,x_3,…,x_n)。
这被解释为,“y 独立地与 x_1、x_2、x_3 中的每一个相关,直到 x_n。”
虽然解释为“y 独立地是 x_1、x_2、x_3 中的每一个的函数(等效地,受其影响),直到 x_n”是可接受的,但这通常是可接受的夸张,并不总是正确的。
在不接受夸大的情况下,决策的条件化,包括政府内部发生的政策决策,对回归结果的条件化是不可能的。
S uppose a 回归显示’ 学生考试成绩 ,以考试成绩为数据(y) ‘受’ 父母财富 (x_1),以收入或净资产为数据’,并认为’白人学生分数高于黑人学生, 白人= ‘是’,而黑人= ‘否’
缺少任何额外的信息,综合起来,结果,
测试分数随着财富的增加而增加,
和
白人孩子的考试分数更高
不要暗示白人父母比黑人父母有钱。只有对人口统计学数据的研究才能揭示白人父母比黑人父母更富有。
解释回归的规范性原则一:
综合起来,y 和 x_1 之间的关系以及 y 和 x_2 之间的关系本身并不意味着 x_1 和 x_2 之间的关系。
S uppose ’ 白人父母为他们的孩子提供更好的早餐 ,将不同选择的营养价值不同的早餐标识为数据’,但这并没有在回归模型的上下文中明确探讨。假设,正如已经假设的那样,白人孩子在测试中比黑人孩子得分高。
人们可以假设,鉴于白人孩子通常在考试中得分较高,部分原因可能是白人父母给孩子提供的早餐比黑人父母好。虽然这一假设事实上可能是正确的,但它不能从考试成绩和种族界限之间的关系中推导出来。回归只对显式包含为变量的数据进行陈述,不对可能与该数据相关的因素进行任何显式陈述。
为了进一步探索这一点,假设 白人父母比黑人父母 更有可能为他们的孩子雇佣家教,以‘雇佣家教=‘是’或‘否’作为数据,而 白人更有可能在家庭作业 的背景下与他们的孩子互动,以‘参与家庭作业=‘是’或‘否’作为数据。虽然这些因素可能与种族相关,但如果没有将*“父母为子女聘请家教”、“是”或“否”以及“父母在家庭作业背景下与子女互动”、“是”或“否”*作为变量纳入回归,则不能假设回归对这些因素做出任何明确的陈述。
如果研究人员试图对额外的相关因素做出明确的陈述,这些因素必须明确地包含在回归中。
解释回归的规范原则二:
假设 x_2 和 x_3 与 x_1 相关,但不包括在 y 对 x_1 的一个回归中[y=f(x_1)]。虽然可以说 x_2 和 x_3 是 y 和 x_1 相关的来源,但不能说 y 与 x_2 或 x_3 相关。
Y 你可能想知道,“为什么不把 x_2 和 x_3 包括在 Y 对 x 的回归中,这样研究者就可以得出 Y 和 x_2 之间的关系,以及 Y 和 x_3 之间的关系和 Y 和 x_1 之间的关系?”
形象地说,为什么不具体说明:
y=f(x_1,x_2,x_3),与 y=f(x_1)相对?
问题?
给定 x_2 和 x_3 中的每一个都与 y 相关,在同一个回归中包含 x_1、x_2 和 x_3 使得从回归中获得的任何结果都不太可信。在规定的条件下,结果可信度的下降是回归分析的一个典型事实。在专家中,这个问题被称为由’*x1,x2,x3 '*多重共线性引起的问题。
如果研究人员真的相信 x_2 和 x_3 是重要的,他或她将不得不分别探索 y 和 x_2 或 y 和 x_3 之间的关系,或者找到一种方法将 x_2 和 x_3 用“”、x_2 和 x_3 用“正交化”。
但是这变得很专业,对于不熟悉回归分析的普通读者来说很难理解。
事实是什么?研究人员有时对事物有先入为主的看法,结果他们做出的推论与我概述的解释回归的两个规范性原则相矛盾。读者有责任意识到这些原则,因此能够不理会任何与概述的原则相矛盾的解释。
在种族划分的背景下,回归分析的正确使用是为了更好地理解种族之间的差异,了解可以通过教育或财富调节的显著差异。
例如,提高早餐的营养价值可以提高小学生的学习成绩,这一发现导致了一些旨在帮助相对贫困家庭的孩子吃更好的早餐的倡议。鉴于营养早餐的重要性随着儿童年龄的增长而下降,营养早餐的益处在以后的生活中是无法获得的。如果一个孩子在小学没有得到营养早餐的好处,那么失去的好处在以后的生活中是无法弥补的。
一个胎儿不能在第四个三月发育成它应该在第三个三月发育成的样子。这就是为什么每当分娩的持续时间莫名其妙地超过妊娠晚期时,分娩就会被诱发。
在医疗健康方面,非裔美国人比白人更易患糖尿病的发现导致了对针对非裔美国人的治疗和预防性饮食的开发的更多关注。
正确地组织和使用,对不同种族间明显分界线的研究可以成为一种有益于社会的力量。
假定不同种族的智力分布是相同的,那么试图推断智力界限的种族界限研究违反了事实和矛盾的数据。
事实呢?
有高中辍学的白人和黑人。
有从大学辍学的白人和黑人。
有白人也有黑人毕业生。
在科学、工程、商业等领域,有来自顶尖大学的白人和黑人博士。
获得诺贝尔经济学奖的白人越来越多,但已经有一个黑人了。
诚然,财富、收入水平和父母的教育水平等因素会影响孩子在学校的表现,但这些因素只是将考试分数的分布向左或向右移动,保持相同的分布。然后,我们有了正确的解释,对种族分界的研究探索了将相同的“钟形曲线”智力分布从白人或黑人的分布中向左或向右移动的因素。
如果智力在不同种族间的分布是相同的,那么对不同种族间智力系统性差异的研究与事实证据相矛盾。与事实证据相矛盾的研究证据永远不会可靠。遗传学领域的研究一致认为,没有任何可靠的证据表明,在不同种族的天生智力方面存在系统性的遗传差异。
如果研究论文的读者和研究人员在不违反解释原则的情况下,以负责任的态度解释回归分析的结果,那么对研究结果的讨论不存在偏见的可能性就会提高,从而有利于社会。