fast.ai 机器学习笔记(二)(3)

简介: fast.ai 机器学习笔记(二)

fast.ai 机器学习笔记(二)(2)https://developer.aliyun.com/article/1482665

部分依赖 [50:46]

这是一个有趣的问题。非常重要,但在某种程度上有点难以理解。


让我们稍后再来看如何计算这个问题,但首先要意识到的是,绝大多数情况下,当有人向您展示一个图表时,它将是一个单变量图表,只会从数据库中获取数据,然后绘制 X 与 Y。然后管理人员往往希望做出决策。所以可能会是“哦,这里有一个下降,所以我们应该停止处理 1990 年至 1995 年之间制造的设备”。这是一个大问题,因为现实世界的数据中有很多这样的相互作用。也许在那些东西被出售的时候正值经济衰退,或者也许在那个时候,人们更多地购买了不同类型的设备。因此,通常我们实际上想知道的是,在其他所有条件相等的情况下,YearMade 和 SalePrice 之间的关系。因为如果您考虑到驱动器方法的杠杆思想,您真的希望一个模型说如果我改变这个杠杆,它将如何改变我的目标。通过使用部分依赖来分开它们,您可以说实际上这是 YearMade 和 SalePrice 之间的关系,在其他所有条件相等的情况下:


那么我们如何计算呢?

学生:例如,对于变量 YearMade,您保持所有其他变量不变。然后,您将传递 YearMade 的每个值,然后训练模型。因此,对于每个模型,您将有浅蓝色线条,中位数将是黄色线条。

Jeremy:那么让我们尝试绘制出来。通过“保持其他一切不变”,她的意思是将它们保持为数据集中的任何值。就像我们进行特征重要性时一样,我们将保持数据集的其余部分不变。我们将对 YearMade 进行部分依赖图。因此,我们有所有这些其他数据行,我们将保持它们不变。与其随机洗牌 YearMade,我们将用完全相同的东西——1960 来替换每个值。就像以前一样,我们现在将通过我们尚未重新训练或更改的现有随机森林来传递这些数据,以获得一组预测y1960。然后我们可以在图表上绘制出来——YearMade 与部分依赖。


现在我们可以为 1961 年、1962 年、1963 年等等做到这一点。我们可以对所有这些平均做到这一点,或者我们可以只对其中一个做到这一点。因此,当我们只对其中一个做到这一点,并且改变它的 YearMade 并将这个单个事物通过我们的模型,这给我们一个这些蓝线中的一个。因此,每一条这些蓝线都是一个单独的行,当我们将其 YearMade 从 1960 年改变到 2008 年。因此,我们可以简单地取所有这些蓝线的中位数,以便平均地说,YearMade 和价格之间的关系是什么,其他所有事物都相等。为什么它有效呢?为什么这个过程告诉我们 YearMade 和价格之间的关系,其他所有事物都相等呢?也许考虑一个非常简化的方法会有帮助。一个非常简化的方法会说什么是平均拍卖?什么是平均销售日期,我们最常见的机器类型是什么?我们主要在哪个地点销售物品?然后我们可以得出一个代表平均拍卖的单行,然后我们可以说,好的,让我们通过随机森林运行这一行,但用 1960 年替换它的 YearMade,然后再用 1961 年再做一次,我们可以在我们的小图表上绘制这些。这将给我们一个 YearMade 和销售价格之间关系的版本,其他所有事物都相等。但是如果拖拉机看起来像那样,而挖掘机看起来像一条平直的线:


然后取平均值会隐藏这些完全不同的关系的事实。因此,我们基本上说,好吧,我们的数据告诉我们我们倾向于销售什么样的东西,我们倾向于向谁销售,以及我们倾向于何时销售,所以让我们利用这一点。然后我们实际上发现,对于每一条蓝线,这里有这些关系的实际例子。因此,我们可以做的是除了绘制中位数之外,我们可以进行聚类分析,找出几种不同的形状。


在这种情况下,它们看起来基本上是同一件事的不同版本,具有不同的斜率,所以我从中得出的主要结论是销售价格与制造年份之间的关系基本上是一条直线。请记住,这是销售价格的对数,因此实际上向我们展示了一个指数。这就是我会引入领域专业知识的地方,比如“好吧,事物随着时间按照一个恒定比率贬值,因此,我会预期较旧的东西制造年份具有这种指数形状。”所以这就是我提到的,我的机器学习项目的开始,我通常尽量避免使用尽可能多的领域专业知识,让数据说话。因此,今天早上我收到的一个问题是“有一个销售 ID 和型号 ID,我应该抛弃它们,对吧?因为它们只是 ID。”不要。不要对数据做任何假设。保留它们,如果它们被证明是非常重要的预测因子,你会想要找出原因。但是,现在我已经完成了我的特征重要性,我已经从那个树状图中提取出了一些东西(即冗余特征),我正在查看部分依赖性,现在我在思考,这种形状是否符合我的预期?因此,更好的做法是,在绘制之前,首先考虑我期望这种形状是什么。因为事后向自己证明,哦,我知道它会看起来像这样,总是很容易。所以你期望什么形状,然后它是那种形状吗?在这种情况下,我会说这是我所期望的。而之前的图表则不符合我的预期。因此,部分依赖性图表真正揭示了潜在的真相。

问题:假设您有 20 个重要特征,您会为每一个特征测量偏依赖性吗?如果有 20 个重要特征,那么我将对所有这些特征进行偏依赖性分析,其中重要意味着它是一个我实际可以控制的杠杆,其大小的幅度与其他十九个特征的差异不大,您知道,基于所有这些因素,这是一个我应该关心的特征,那么我将想要了解它是如何相关的。在我的经验中,拥有这么多在操作和建模角度上都重要的特征是相当不寻常的。

问题:您如何定义重要性?重要意味着它是一个杠杆(即我可以改变的东西),它位于这个尾巴(左侧)的尖端:


或者它可能不是直接的杠杆。也许它像邮政编码一样,我实际上无法告诉我的客户在哪里居住,但我可以将我的新营销注意力集中在不同的邮政编码上。

问题:对于每一对特征组合进行成对洗牌,保持其他所有内容不变,以查看交互作用并比较分数是否有意义?你不会为了偏依赖性而这样做。我认为您的问题实际上是在询问我们是否可以为特征重要性这样做。我认为交互特征重要性是一个非常重要且有趣的问题。但是通过随机洗牌每一对列来做到这一点,如果您有一百列,这听起来计算量很大,可能不可行。所以我要做的是在我们讨论树解释器之后,我将谈论一个有趣但在很大程度上未被探索的方法,这可能会起作用。

树解释器

Prince:我认为这更像是特征重要性,但特征重要性是针对完整的随机森林模型,而这个树解释器是针对特定观察的特征重要性。所以让我们说这是关于医院再入院的。如果患者 A 将要再次入院,那么对于该特定患者,哪个特征会产生影响,我们如何改变这种情况。它是从平均预测开始计算,然后看每个特征如何改变该特定患者的行为。

Jeremy:我在微笑,因为这是我很长时间以来听到的最好的技术沟通示例之一,所以思考为什么这么有效是非常有意义的。Prince 所做的是,他尽可能具体地举例说明。人类在理解抽象概念方面要差得多。因此,如果您说“它需要某种特征,然后在该特征的观察中”,而不是医院再入院。因此,我们举了一个具体的例子。他做的另一件非常有效的事情是将类比与我们已经理解的东西联系起来。因此,我们已经理解了数据集中所有行的特征重要性的概念。因此,现在我们将为单个行执行此操作。因此,我真的希望我们从这次经验中学到如何成为有效的技术沟通者。因此,Prince 在使用我们可以利用的所有技巧进行有效的技术沟通方面是一个非常好的榜样。希望您觉得这个解释有用。除了向您展示它是什么样子之外,我没有太多要补充。

使用树解释器,我们挑选出一行:


还记得我们在一开始谈到的置信区间吗(即基于树方差的置信度)?我们说你主要用于一行。所以这也是为一行。就像“为什么这个患者可能会再次入院?”这是我们关于该患者或在这种情况下该拍卖会的所有信息。为什么这个拍卖会这么贵?然后我们调用ti.predict,我们得到价格的预测,偏差(即树的根 - 这只是每个人的平均价格,所以这总是一样的),然后是贡献,即这些事情有多重要:


我们计算的方法是说一开始,平均价格是 10。然后我们根据围栏进行分割。对于那些有这个围栏的人,平均价格是 9.5。然后我们根据制造年份小于 1990 进行分割,对于那些有这个制造年份的人,平均价格是 9.7。然后我们根据米数进行分割,对于这个分支,我们得到了 9.4。


然后我们有一个特定的拍卖会,我们通过树传递它。碰巧它走了最顶层的路径。一行只能通过树有一条路径。所以我们最终到了 9.4。然后我们可以创建一个小表格。当我们逐步进行时,我们从顶部开始,我们从 10 开始 - 这是我们的偏差。我们说围栏导致了从 10 变为 9.5(即-0.5)。制造年份将其从 9.5 变为 9.7(即+0.2),然后米数将其从 9.7 降至 9.4(-0.3)。然后如果我们把所有这些加在一起(10-0.5+0.2-0.3),哎呀,那就是预测。


这让我们来到我们的 Excel 电子表格:


上周,我们使用 Excel 进行了这项工作,因为没有一个很好的 Python 库可以制作瀑布图。所以我们看到我们得到了我们的起点这是偏差,然后我们有我们每个贡献,最后我们得到了我们的总数。现在世界变得更美好了,因为 Chris 为我们创建了一个 Python 瀑布图模块,并将其放在 pip 上。所以我们再也不必使用 Excel 了。我想指出,瀑布图至少在我从事业务以来一直在商业沟通中非常重要 - 大约 25 年了。Python 可能已经有几十年的历史了。但尽管如此,Python 世界中没有人真正想到“你知道,我要制作一个瀑布图”,所以直到两天前它们才存在,也就是说这个世界充满了应该存在但不存在的东西。而且并不一定需要花费很多时间来构建。Chris 花了大约 8 个小时,所以数量相当可观但不过分。现在以后,当人们想要 Python 瀑布图时,他们将最终到达 Chris 的 Github 存储库,并希望找到许多其他美国大学的贡献者,他们使其变得更好。

为了帮助改进 Chris 的 Python 瀑布,您需要知道如何做到这一点。因此,您需要提交一个拉取请求。如果您使用一个叫做hub的东西,那么提交拉取请求将变得非常容易。他们建议您将git别名为hub,因为事实证明,hub 实际上是 git 的一个严格的超集。它让您可以执行git forkgit pushgit pull-request,然后您现在已经向 Chris 发送了一个拉取请求。没有 hub,这实际上是一种痛苦,需要像去网站并填写表格之类的事情。因此,这给您没有理由不提交拉取请求。我提到这一点是因为当您面试工作时,我可以向您保证,您正在与之交谈的人将检查您的 github,如果他们看到您有提交经过深思熟虑的拉取请求并被接受到有趣的库,那看起来很棒。这看起来很棒,因为它表明您是一个实际做出贡献的人。它还表明,如果它们被接受,那么您知道如何创建符合人们编码标准、具有适当文档、通过测试和覆盖率等的代码。因此,当人们看着您并说哦,这里是一个有着成功贡献历史的人,接受了开源库的拉取请求,这是您作品集的一个很好的部分。您可以具体引用它。因此,无论是我是构建 Python 瀑布的人,这是我的存储库,还是我是为 Python 瀑布贡献货币数字格式化的人,这是我的拉取请求。每当您在使用任何开源软件时看到某些不正常的东西,这不是问题,而是一个很好的机会,因为您可以修复它并发送拉取请求。所以试一试。第一次有拉取请求被接受时,感觉真的很棒。当然,一个很大的机会是 fastai 库。由于我们的一位学生,我们现在对fastai.structured库的大部分文档字符串都有了,这也是通过拉取请求完成的。

有人对如何计算这些随机森林解释方法或为什么我们可能想要使用它们有任何问题吗?在本周末,您将需要能够从头开始构建所有这些。

问题:只是看着树解释器,我注意到一些值是nan。我明白为什么要保留它们在树中,但nan如何有特征重要性呢?让我把问题传递给你。为什么不呢?换句话说,Pandas 中如何处理nan,因此在树中呢?有人记得,注意这些都是分类变量,Pandas 如何处理分类变量中的nan,fastai 又是如何处理的?Pandas 将它们设置为-1 类别代码,而 fastai 将所有类别代码加一,因此最终变为零。换句话说,记住,当它到达随机森林时,它只是一个数字,只是零。然后我们将其映射回这里的描述。所以问题实际上是为什么随机森林不能在零上分裂?它只是另一个数字。所以它可以是nan= 0,1,2,3。因此,缺失值是通常教得很糟糕的事情之一。通常人们被教导要删除具有缺失值的列或删除具有缺失值的行,或者替换缺失值。这绝不是我们想要的,因为缺失通常是非常非常有趣的。因此,我们实际上从我们的特征重要性中学到,耦合器系统nan是最重要的特征之一。出于某种原因,嗯,我可以猜测,对吧?耦合器系统nan可能意味着这是一种没有耦合器系统的工业设备。现在我不知道是什么类型,但显然是更昂贵的类型。

我参加了一个为大学拨款研究成功而举办的比赛,迄今为止最重要的预测因素是某些字段是否为空[1:15:41]。结果表明,这是数据泄漏,这些字段大多数情况下只在研究拨款被接受后填写。所以这让我赢得了那个 Kaggle 比赛,但实际上并没有对大学有太大帮助。

外推[1:16:16]

我要做一些冒险和危险的事情,我们将进行一些现场编码。我们要进行一些现场编码的原因是我想和你一起探索外推,我也想让你感受一下在这个笔记本环境中如何快速编写代码。这是你在现实世界和考试中需要做的事情,快速创建我们将要讨论的代码。

每当我尝试调查某事的行为时,我都非常喜欢创建合成数据集,因为如果我有一个合成数据集,我知道它应该如何表现。

提醒我,我们在做这个之前,我承诺我们会谈论交互重要性,我差点忘了。

交互重要性[1:17:24]

树解释器告诉我们基于树中差异的特定行的贡献。我们可以为数据集中的每一行计算这个值并将它们相加。这将告诉我们特征的重要性。它将以不同的方式告诉我们特征的重要性。评估特征重要性的一种方法是逐个对列进行洗牌。另一种方法是为每一行进行树解释并将它们相加。两种方法都没有更正确的一种。它们实际上都被广泛使用,所以这是一种类型 1 和类型 2 的特征重要性。所以我们可以尝试扩展一下。不仅仅是单变量特征重要性,还有交互特征重要性。现在这里有一点。我要描述的东西很容易描述。当随机森林首次被发明时,Breiman 就描述过这个方法,它也是 Salford 系统商业软件产品的一部分,他们拥有随机森林的商标。但我不知道它是任何开源库的一部分,我从来没有看到过一篇真正研究它的学术论文。所以我要在这里描述的是一个巨大的机会,但也像有很多细节需要完善。但这里是基本思想。

这里的这个特定差异(红色)不仅仅是因为 year made,而是因为 year made 和 enclosure 的组合[1:19:15]:


这是 9.7 的原因是因为 enclosure 在这个分支中,year made 在这个分支中。换句话说,我们可以说 enclosure 与 year made 的交互作用是-0.3。

那么 9.5 和 9.4 之间的差异呢?那是 year made 和表计上的小时数的交互作用。我在这里使用星号不是表示“乘”,而是表示“与…交互”。这是一种常见的做法,就像 R 的公式也是这样做的。所以 year made 与表计的交互作用贡献了-0.1。


也许我们还可以说从 10 到 9.4,这也显示了仪表和围栏之间的相互作用,中间有一件事。所以我们可以说仪表与围栏的相互作用等于…应该是多少呢?应该是-0.6 吗?有些方式似乎不公平,因为我们还包括了年份的影响。所以也许应该是-0.6,也许我们应该加回这个 0.2(9.5 → 9.7)。这些都是我实际上不知道答案的细节。我们应该如何最好地为这条路径中的每对变量分配贡献?但从概念上来说,我们可以。该路径中的变量对都代表相互作用。

问题:为什么你不强制它们在树中相邻[1:21:47]?我不会说这是错误的方法。但我不认为这是正确的方法。因为在这条路径中,仪表和围栏是相互作用的。所以似乎不承认这种贡献是在丢弃信息。但我不确定。几年前,我在 Kaggle 的员工中有一个实际上对此进行了一些研发,他们确实发现了(我不够接近知道他们如何处理这些细节),但他们做得相当不错。但不幸的是,它从未成为软件产品问世。但也许你们中的一群人可以聚在一起并构建。做一些搜索来检查,但我真的不认为任何开源库中有任何相互作用特征重要性部分。

问题:但这样会排除那些在相互作用之前并不重要的变量之间的相互作用吗?所以说,如果你的行永远不选择沿着那条路径分裂,但是那个变量与另一个变量的相互作用成为你最重要的分裂。我不认为会发生这种情况。因为如果有一个相互作用是重要的,只是因为它是一个相互作用(而不是在单变量基础上),它有时会出现,假设你将最大特征设置为小于一,因此它会出现在某些路径中。

问题:相互作用是什么意思?是乘法、比率、加法吗?相互作用意味着出现在树的同一路径上。在上面的例子中,由于我们在围栏上分支,然后在年份上分支,所以围栏和年份之间存在相互作用。因此,要达到 9.7,我们必须有某个特定的围栏值和某个特定的年份值。

问题:如果你走在你试图观察的两件事之间的中间叶子上,你也会考虑最终的度量是什么吗?我的意思是,如果我们向下延伸树,你会有很多度量,既包括你试图观察的两件事,也包括中间步骤。似乎有一种方法可以在它们之间平均信息吗?也许有。我认为我们应该在论坛上讨论这个。我觉得这很有趣,希望我们能构建出一些伟大的东西,但我需要进行现场编码。这是一个很好的讨论。继续思考并进行一些实验。

回到现场编码。

因此,为了尝试这个,你几乎肯定想先创建一个合成数据集。就像 y = x1 + x2 + x1*x2 或者其他什么。有一些你知道存在交互效应,有一些你知道不存在交互效应,你想确保最终得到的特征重要性是你期望的。

所以可能第一步是使用树解释器风格的单变量特征重要性。这种方法的一个好处是,你拥有多少数据并不重要。你只需要遍历树来计算特征重要性。所以你应该能够以一种相当快速的方式编写代码,所以即使只用纯 Python 编写,也可能足够快,这取决于你的树的大小。

我们将讨论外推和我想要做的第一件事是创建一个具有简单线性关系的合成数据集。我们将假装它就像一个时间序列。所以我们需要创建一些 x 值。创建这种类型的合成数据的最简单方法是使用linspace,它默认创建 50 个观测值在开始和结束之间均匀分布的数据。


然后我们将创建一个因变量,所以让我们假设 x 和 y 之间存在线性关系,并且让我们添加一点随机性。在低和高之间使用random.uniform,所以我们可以添加-0.2 到 0.2 之间的某个值,例如。


接下来我们需要一个形状,基本上就是你想要这些随机数的维度是什么,显然我们希望它们与x的形状相同。所以我们可以直接说x.shape


换句话说,(50,)x.shape。记住,当你看到括号里有逗号的时候,那就是一个只有一个元素的元组。所以这是形状为 50,我们添加了 50 个随机数。现在我们可以绘制这些数值。


好的,这就是我们的数据。当你作为数据科学家工作或在这门课程中做考试时,你需要能够快速地创建一个类似的数据集,将其绘制在图表中而不用考虑太多。正如你所看到的,你不必真的记住太多东西。你只需要知道如何按shift + tab来检查参数的名称,搜索一下,或者尝试找到linspace如果你忘记了它的名字。

所以让我们假设这是我们的数据。我们现在要构建一个随机森林模型,我想要构建一个随机森林模型,让它像一个时间序列一样运行。所以我将左边部分作为训练集。然后将右边部分作为我们的验证或测试集,就像我们在购物或推土机中所做的那样。


我们可以使用与我们在split_vals中使用的完全相同的代码。所以我们可以说:

x_trn, x_val = x[:40], x[40:]

这将数据分为前 40 个和后 10 个。我们可以对 y 做同样的操作。

y_trn, y_val = y[:40], y[40:]

接下来要做的是创建一个随机森林并拟合它,这需要 x 和 y。

m = RandomForestRegressor().fit(x, y)

这实际上会导致错误,原因是它期望 x 是一个矩阵,而不是一个向量,因为它期望 x 有多列数据。

重要的是要知道,只有一列的矩阵和向量不是同一回事。

所以如果我尝试运行这个代码,“预期 2D 数组,实际得到 1D 数组”:


所以我们需要将一维数组转换为二维数组。记住我说过x.shape(50,)。所以x有一个轴,x 的秩是 1。变量的秩等于它的形状的长度 - 它有多少个轴。我们可以将向量看作是秩为 1 的数组,将矩阵看作是秩为 2 的数组。我很少使用向量和矩阵这样的词,因为它们有点毫无意义 - 它们只是更一般的东西的具体例子,它们都是 N 维张量或 N 维数组。所以 N 维数组可以说是秩为 N 的张量。它们基本上意味着相同的事情。物理学家听到这个会很疯狂,因为对于物理学家来说,张量有一个非常具体的含义,但在机器学习中,我们通常以相同的方式使用它。

那么我们如何将一维数组转换为二维数组。我们可以这样做的方法有几种,但基本上我们是切片。冒号(:)表示给我在那个轴上的所有东西。:, None表示给我第一个轴上的所有东西(这是我们唯一拥有的轴),然后None是一个特殊的索引器,表示在这里添加一个单位轴。所以让我给你看看。


它的形状是(50, 1),所以它是秩为 2 的。它有两个轴。其中一个是一个非常无聊的轴 - 它是一个长度为一的轴。所以让我们将None移到左边。这是(1, 50)。然后提醒你,原始的是(50,)。


所以你可以看到我可以将None作为一个特殊的索引器放在那里引入一个新的单位轴。x[None, :]有一行和五十列。x[:, None]有五十行和一列 - 这就是我们想要的。在这门课程和深度学习课程中,这种对秩和维度的玩耍将变得越来越重要。所以花很多时间用 None 切片,用其他东西切片,尝试创建三维、四维张量等。我会向你展示两个技巧。

第一个是你永远不需要写,:,因为它总是被假定的。所以这些是完全相同的:


你会在代码中一直看到这样的写法,所以你需要认识到它。

第二个技巧是x[:, None]是在第二维度(或我猜是索引 1 维度)添加一个轴。如果我总是想把它放在最后一个维度怎么办?通常我们的张量在我们不注意的情况下改变维度,因为你从一个单通道图像变成了一个三通道图像,或者你从一个单个图像变成了一个图像的小批量。突然之间,新的维度出现了。所以为了让事情更一般化,我会说...,这意味着你需要多少维度来填充这个。所以在这种情况下(x[…, None].shape),它是完全相同的,但我总是尝试以这种方式写,因为这意味着当我得到更高维度的张量时,它将继续工作。

所以在这种情况下,我想要 50 行和一列,所以我会称之为 x1。现在让我们在这里使用它,这样就是一个二维数组,所以我可以创建我的随机森林。


然后我可以绘制出来,这就是你需要打开大脑的地方,因为今天早上的人们非常快地理解了这一点,这是非常令人印象深刻的。我将绘制y_trnm.predict(x_trn)。在我开始之前,这会是什么样子?它应该基本上是一样的。我们的预测希望与实际相同。所以这应该落在一条线上,但有一些随机性,所以不会完全相同。


那是容易的。现在让我们做困难的,有趣的那个。那会是什么样子?


想想树的作用,想想右边有一个验证集,左边有一个训练集:


所以想想森林只是一堆树。

Tim: 我猜测由于所有新数据实际上都在原始范围之外,所以它们基本上都是一样的 - 就像一个巨大的群体。

Jeremy: 是的,对。所以忘掉森林,让我们创建一棵树。所以我们可能首先在这里分裂,然后在这里分裂,… 所以我们的最终分裂是最右边的节点。当我们从验证集中取一个时,它会将其通过森林,最终预测最右边的平均值。它无法预测比那更高的任何东西,因为没有更高的东西可以平均。


因此,要意识到随机森林并不是魔法。它只是返回附近观察值的平均值,其中附近在这种“树空间”中。所以让我们运行它,看看 Tim 是不是对的。


天啊,太糟糕了。如果你不知道随机森林是如何工作的,那么这将完全搞砸你。如果你认为它实际上能够对任何它以前没有见过的数据进行外推,尤其是未来的时间段,那就错了。它只是无法做到。它只是对它已经看到的东西进行平均。这就是它能做的全部。

好的,我们将讨论如何避免这个问题。在上一课中,我们稍微谈到通过避免不必要的时间相关变量来避免这个问题。但最终,如果你真的有一个看起来像这样的时间序列,我们实际上必须处理一个问题。我们可以处理这个问题的一种方法是使用神经网络。使用一些实际上具有可以拟合这样的函数或形状的东西,这样它将可以很好地外推:


另一种方法是使用你们在早上课上学到的所有时间序列技术来拟合某种时间序列,然后去趋势化。然后你会得到去趋势化的点,然后使用随机森林来预测这些点。这特别酷,因为想象一下你的随机森林实际上试图预测两种不同状态的数据。所以蓝色的在下面,红色的在上面。


如果你尝试使用随机森林,它将做得相当糟糕,因为时间看起来更加重要。所以它基本上仍然会像这样分裂,然后像那样分裂,最终一旦到达左下角,它会像“哦,好吧,现在我可以看到两种状态之间的差异了。”


换句话说,当你有这么大的时间序列时,直到每棵树都处理时间,你才能看到随机森林中的其他关系。因此,解决这个问题的一种方法是使用梯度提升机(GBM)。GBM 的作用是创建一棵小树,然后通过第一棵小树运行所有内容(可能是时间树),然后计算残差,下一棵小树只是预测残差。所以这有点像去趋势化,对吧?GBM 仍然无法对未来进行外推,但至少它们可以更方便地处理时间相关数据。

在接下来的几周里,我们将更多地讨论这个问题,最终的解决方案将是使用神经网络。但是现在,使用某种时间序列分析,去趋势化,然后在随机森林上使用它并不是一种坏技术。如果你在玩类似厄瓜多尔杂货竞赛之类的东西,那将是一个非常好的东西来尝试。

机器学习 1:第 7 课

原文:medium.com/@hiromi_suenaga/machine-learning-1-lesson-7-69c50bc5e9af

译者:飞龙

协议:CC BY-NC-SA 4.0

来自机器学习课程的个人笔记。随着我继续复习课程以“真正”理解它,这些笔记将继续更新和改进。非常感谢 JeremyRachel 给了我这个学习的机会。

视频

我们将完成从头开始构建我们自己的随机森林!但在此之前,我想解决一些在这一周中出现的问题。

随机森林在一般情况下的位置

我们大约花了一半的课程时间来讲解随机森林,然后在今天之后,这门课程的第二半将广义地讲解神经网络。这是因为这两者代表了覆盖几乎所有你可能需要做的技术的两个关键类别。随机森林属于决策树集成技术类别,与梯度提升机是另一种关键类型,还有一些变体,比如极端随机树。它们的好处是它们非常易解释,可扩展,灵活,适用于大多数类型的数据。它们的缺点是它们完全不会对超出你所见范围的数据进行外推,就像我们在上周课程结束时看到的那样。但它们是一个很好的起点。我认为有很多机器学习工具的目录,很多课程和书籍并没有试图筛选出来,说对于这些问题,使用这个,对于那些问题,使用那个,完了。但它们更像是这里有 100 种不同算法的描述,而你根本不需要它们。比如,我不明白为什么今天你会使用支持向量机。我想不出任何理由去做那样的事情。人们在 90 年代喜欢研究它们,因为它们在理论上非常优雅,你可以写很多关于支持向量机的数学,人们确实这样做了,但实际上我认为它们没有任何用武之地。

在详尽的列表中,有很多技术可以包括在人们采用机器学习问题的每一种方式中,但我更愿意告诉你如何实际解决机器学习问题。我们即将结束今天的第一堂课,这是一种决策树集成的一种类型,在第二部分,Yanett 将告诉你另一种关键类型,即梯度提升,我们即将启动下一课程,介绍神经网络,其中包括各种广义线性模型(GLM)、岭回归、弹性网络套索、逻辑回归等都是神经网络的变体。

有趣的是,创造随机森林的 Leo Breiman 在他的晚年才做出了这一成就,不幸的是,他在之后不久就去世了。因此,关于随机森林的学术文献很少,部分原因是因为在那个时候支持向量机(SVM)开始流行,其他人没有关注它们。另一个原因是随机森林在理论层面上相当难以理解(在理论上分析它们),很难撰写关于它们的会议论文或学术论文。因此,关于它们的研究并不多。但近年来出现了一股新的经验机器学习浪潮,关注的是什么实际上有效。Kaggle 是其中的一部分,但也有像亚马逊和谷歌这样的公司利用机器学习赚取大量利润。因此,如今很多人都在写关于决策树集成的文章,并为决策树集成创建更好的软件,如 GBM 和 xgboost,以及 R 的 ranger 和 scikit-learn 等等。但很多这方面的工作是在工业界而不是学术界进行的,但这是令人鼓舞的。当然,目前在学术界进行的深度学习工作比决策树集成要多,但两者都在取得很大进展。如果看看今天用于决策树集成的软件包,排名前五或六名的最好的软件包,我不确定其中有哪个在五年前真的存在,也许除了 sklearn 之外,甚至三年前也没有。这是一个好的现象。但我认为还有很多工作要做。比如,上周我们讨论了找出哪些交互作用最重要的问题。你们中一些人在论坛中指出,实际上已经有一个梯度提升机的项目,这很棒,但似乎还没有类似的项目用于随机森林。随机森林相比 GBM 有一个很好的优势,那就是它们更难出错,更容易扩展。因此,希望这个社区可以帮助解决这个问题。

您的验证集大小 [5:42]

另一个我在这周期间遇到的问题是关于验证集的大小。它应该有多大呢?所以要回答这个关于验证集需要多大的问题,你首先需要回答这个算法的准确度我需要知道多精确。如果你的验证集显示这是 70%的准确率,如果有人问说,那是 75%还是 65%还是 70%,答案是“我不知道,这个范围内的任何值都足够接近”,那就是一个答案。另一方面,如果是 70%还是 70.01%还是 69.99%呢?那又是另一回事了。所以你需要先问自己,我需要多准确。

例如,在深度学习课程中,我们一直在研究狗和猫的图像,我们所研究的模型在验证集上的准确率大约为 99.4%,99.5%。验证集的大小为 2000。实际上,让我们在 Excel 中做这个,这会更容易一些。因此,错误的数量大约是(1 - 准确率) * n


所以我们大约有 12 只错误。我们拥有的猫的数量是一半,所以错误的猫的数量大约是 6 只。然后我们运行了一个新模型,发现准确率提高到了 99.2%。然后就像,好吧,这个模型在找猫方面是否不太好?嗯,它多找错了 2 只猫,所以可能不是。这重要吗?99.4 和 99.2 有关系吗?如果不是关于猫和狗,而是关于发现欺诈,那么 0.6%的错误率和 0.8%的错误率之间的差异就相当于欺诈成本的 25%,这可能是巨大的。

今年早些时候,ImageNet 发布时真的很有趣,新的竞赛结果出来了,错误率从 3%降到了 2%,我看到很多人在互联网上,一些著名的机器学习研究人员都觉得,一些中国人把准确率从 97%提高到了 98% —— 这在统计上甚至不重要,谁在乎呢。但实际上我觉得哇塞,这支中国团队刚刚超越了最先进的图像识别技术,旧技术的准确率比新技术低了 50%。这才是正确的思考方式,不是吗。因为我们试图识别哪些西红柿是成熟的,哪些不是,而旧方法,有 50%的时间多让进了未成熟的西红柿,或者说有 50%的时间,我们接受了欺诈性的客户。这是一个非常大的差异。所以仅仅因为这个特定的验证集,我们看不出 6 和 8 的区别,并不意味着 0.2%的差异不重要。它可能很重要。所以我的经验法则是,你实际上看了多少观察值,我希望这个数字通常要高于 22。为什么是 22?因为 22 是 t-分布大致变成正态分布的魔法数字。所以你可能已经学过,t-分布是小数据集的正态分布。换句话说,一旦我们有了 22 个或更多的东西,它开始在两个意义上表现得有点正常,就像它更加稳定,你可以更好地理解它。所以当有人问我是否有足够的东西时,我通常会说你是否有 22 个感兴趣的事物的观察值。所以如果你在研究肺癌,你有一个数据集,其中有一千个没有肺癌的人和 20 个患有肺癌的人,我会说我非常怀疑我们会取得多少进展,因为我们甚至没有得到你想要的 20 个东西。同样适用于验证集。如果你没有你想要的 20 个东西,那很可能是没有用的,或者说不符合我们需要的准确度水平。这不是加减 20,只是我在考虑时会有点小心。

问题:所以清楚一点,你想要每组样本的数量是 22,就像在验证集、测试集和训练集中一样吗?所以我的意思是,如果任何一组中某个类别的样本少于 22 个,那么在那一点上就会变得非常不稳定。这就像是第一个经验法则。但接下来我会开始练习我们学到的关于二项分布或伯努利分布的知识。那么 n 个样本和概率 p 的二项分布的均值是多少?n*p。n 乘以 p 就是均值。所以如果你有 50%的机会抛硬币得到正面,你抛 100 次,平均得到 50 次正面。那么标准差是多少?n*p*(1-p)


所以第一个数字你不必记住——这是直观明显的。第二个数字是一个你要永远记住的数字,因为它不仅经常出现,你与之合作的人都会忘记,所以你会成为对话中唯一能立即说出“我们不必运行这个 100 次,我可以立即告诉你这是二项式,它将是n*p*(1-p)的人。

然后是标准误差。标准误差是指如果你运行一堆试验,每次得到一个平均值,那么平均值的标准偏差是多少。我不认为你们已经涵盖了这个内容。这是非常重要的,因为这意味着如果你训练了一百个模型,每次验证集的准确率就像是一个分布的平均值。因此,验证集准确率的标准偏差可以用标准误差来计算,这等于标准偏差除以 n 的平方根。


因此,确定我的验证集是否足够大的一种方法是,每次使用完全相同的超参数训练模型 5 次,然后查看每次的验证集准确率,可以计算出 5 个数字的平均值和标准差,或者可以使用最大值和最小值。但为了节省时间,您可以立即确定,我有一个 0.99 的准确率,无论我是否正确地识别了猫。因此,标准差等于 0.99 * 0.01,然后可以得到标准误差。因此,您需要的验证集大小,就像它必须足够大,以便您对准确性的洞察对于您特定的业务问题足够好。因此,简单的方法是选择一个大小为一千的验证集,训练 5 个模型,查看验证集准确率的变化情况,如果它们都足够接近您所需的水平,那么就可以了。如果不是,也许您应该使其更大,或者考虑改用交叉验证。因此,可以看到,这取决于您试图做什么,您的较不常见类别有多常见,以及您的模型有多准确。

问题:关于较少常见的类别,如果你少于 22 个,比如你有一个样本,比如是一个脸,我只有一个来自那个特定国家的代表,我是把它放入训练集并增加多样性,还是完全从数据集中删除,或者我把它放入测试集而不是验证集?所以你肯定不能把它放入测试集或验证集,因为你在问我能否识别我以前从未见过的东西。但实际上,关于我能否识别我以前从未见过的东西,实际上有一个专门用于这个目的的模型类别——它被称为一次性学习,你只能看到一次东西,然后必须再次识别它,或者零次学习,你必须识别你以前从未见过的东西。我们在本课程中不会涵盖它们,但它们对于像人脸识别这样的事情可能会有用,比如这是我以前见过的同一个人吗。所以一般来说,显然,为了使这样的事情起作用,不是你以前从未见过一个脸,而是你以前从未见过 Melissa 的脸。所以你看到 Melissa 的脸一次,然后你必须再次识别它。所以一般来说,你的验证集和测试集需要具有与你将在实际生产中看到的观察频率相同的混合或频率。你的训练集应该每个类别有相等数量,如果没有,只需复制较少常见的类别直到相等。我想我们之前提到过这篇论文,一篇最近发表的论文,他们尝试了许多不同的方法来训练不平衡的数据集,并一直发现,直到较少常见的类别与较常见的类别大小相同为止,过采样较少常见的类别总是正确的做法。所以你可以简单地复制,比如我只有十个患癌症的人的例子,而没有百个,所以我可以再复制这 10 个另外 90 次,这在一定程度上是一种内存效率低下的方式,包括我认为 sklearn 的随机森林有一个类别权重参数,每次你进行自助抽样或重新采样时,我希望你以更高的概率对较少常见的类别进行抽样。或者如果你正在进行深度学习,确保在你的小批量中,不是随机抽样,而是较少常见的类别的分层样本更频繁地被选中。

回到完成随机森林的部分 18:39

笔记本

让我们回到完成随机森林的工作。今天我们要做的是完成编写我们的随机森林,然后在今天之后,你的作业就是拿这节课并添加我们学到的所有随机森林解释算法。显然,为了能够做到这一点,你需要完全理解这节课的工作原理,所以在我们进行时,请尽可能多地提问。提醒一下,我们再次使用推土机 Kaggle 竞赛数据集。我们将其分为 12,000 个验证集(最后 12,000 条记录),为了更容易跟踪我们的工作,我们将从中挑选两列开始:YearMadeMachineHoursCurrentMeter

from fastai.imports import *
from fastai.structured import *
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
from IPython.display import display
from sklearn import metrics
PATH = "data/bulldozers/"
df_raw = pd.read_feather('tmp/bulldozers-raw')
df_trn, y_trn, nas = proc_df(df_raw, 'SalePrice')
def split_vals(a,n): 
    return a[:n], a[n:]
n_valid = 12000
n_trn = len(df_trn)-n_valid
X_train, X_valid = split_vals(df_trn, n_trn)
y_train, y_valid = split_vals(y_trn, n_trn)
raw_train, raw_valid = split_vals(df_raw, n_trn)
x_sub = X_train[['YearMade', 'MachineHoursCurrentMeter']]

上次我们做的是创建了一个树集合,这个树集合包含了一堆树,实际上是一个包含n_trees棵树的列表,每次我们只是调用create_treecreate_tree包含了一个样本大小(sample_sz)的随机索引(rnd_idxs)。这里是无重复抽样。所以记住,自助法意味着有放回抽样。通常在 scikit-learn 中,如果有 n 行数据,我们用有放回抽样抽取 n 行数据,这意味着很多行会出现多次。所以每次我们得到一个不同的样本,但它的大小总是与原始数据集相同。然后我们有一个set_rf_samples函数,我们可以使用它进行少于 n 行的有放回抽样。create_tree再次做的是无重复抽样sample_sz行。因为我们对从零到self.y-1的数字进行排列,然后抽取其中的前self.sample_sz个。实际上有一种更快的方法可以做到这一点。你可以直接使用np.random.choice(而不是np.random.permutation),这是一种稍微更直接的方法,但这种方法也可以。所以rnd_idxs是我们n_trees棵树中的一个的随机样本。然后我们将创建一个DecisionTree。我们的决策树,我们不会传递所有的x,而是传递这些特定的索引,记住 x 是一个 Pandas DataFrame,所以如果我们想用一堆整数对其进行索引,我们使用iloc(整数位置),这使得它在索引方面的行为就像 numpy 一样。现在y向量是 numpy,所以我们可以直接对其进行索引。然后我们将跟踪最小叶子大小(min_leaf)。

class TreeEnsemble():
  def __init__(self, x, y, n_trees, sample_sz, min_leaf=5):
    np.random.seed(42)
    self.x,self.y,self.sample_sz,self.min_leaf = x,y,sample_sz,min_leaf
    self.trees = [self.create_tree() for i in range(n_trees)]
  def create_tree(self):
    rnd_idxs = np.random.permutation(len(self.y))[:self.sample_sz]
    return DecisionTree(
        self.x.iloc[rnd_idxs], 
        self.y[rnd_idxs],
        min_leaf=self.min_leaf
    )

然后在集成中我们真正需要的另一件事情就是一个地方来进行预测。因此我们只需要对每棵树的预测取平均值。就是这样。

def predict(self, x):
    return np.mean([t.predict(x) for t in self.trees], axis=0)
class DecisionTree():
    def __init__(self, x, y, idxs=None, min_leaf=5):
        self.x,self.y,self.idxs,self.min_leaf = x,y,idxs,min_leaf
        m = TreeEnsemble(
            X_train, y_train, 
            n_trees=10, 
            sample_sz=1000, 
            min_leaf=3
        )

然后为了能够运行它,我们需要一个决策树类,因为它被create_tree调用。所以我们开始吧。这就是起点。接下来我们需要做的是完善我们的决策树。所要记住的重要一点是我们所有的随机性都发生在TreeEnsemble中。我们将要创建的 DecisionTree 类中没有随机性。

问题:现在我们正在构建一个随机树回归器,这就是为什么我们要取树输出的平均值。如果我们要处理分类,我们要取最大值吗?就像分类器会给你零或一[22:36]?不,我仍然会取平均值。因此,每棵树都会告诉你叶节点中包含猫的百分比和包含狗的百分比。然后我会平均所有这些百分比,并说在所有树上平均,有 19%的猫和 81%的狗。

随机树分类器几乎与随机树回归器相同,或者几乎可以相同。我们今天要使用的技术基本上完全适用于分类。对于二元分类,您可以使用完全相同的代码。对于多类分类,您只需要更改数据结构,使其像一个独热编码矩阵或一个整数列表,您将其视为一个独热编码矩阵。

所以我们的决策树,记住,我们的想法是我们要尽量避免思考,所以我们基本上会写成如果我们需要的一切已经存在的样子。我们知道当我们创建决策树时,我们将传入 x、y 和最小叶子大小。所以在这里我们需要确保在__init__中有xymin_leaf。还有一件事是,当我们将树分割成子树时,我们需要跟踪哪些行索引进入了树的左侧,哪些进入了树的右侧。所以我们还会有一个叫做idxs的东西。起初,我们根本不费心传入idxs,所以如果没有传入idxs(即if idxs is None),那么我们就会将其设置为 y 的整个长度。np.arange在 Python 中与range相同,但它返回一个 numpy 数组。所以决策树的根包含所有行。这实际上就是决策树根的定义(第 0 行,第 1 行,直到第 y-1 行)。我们将存储我们得到的所有信息。我们将跟踪有多少行,有多少列。

然后树中的每个叶子和每个节点都有一个值/预测。该预测只是等于因变量的平均值。因此,树中的每个节点,用idxs索引的y是在树的这一分支中的因变量的值,因此这里是平均值。树中的一些节点还有一个分数,这就像这里的分割有多有效。但只有在它不是叶子节点时才会成立。叶子节点没有进一步的分割。在创建树时,我们还没有进行任何分割,因此其分数开始时为无穷大。构建了树的根节点后,我们的下一个任务是找出应该在哪个变量上进行分割,以及应该在该变量的哪个水平上进行分割。因此,让我们假设有一个可以做到这一点的东西——find_varsplit。然后我们就完成了。

class DecisionTree():
    def __init__(self, x, y, idxs=None, min_leaf=5):
        if idxs is None: 
            idxs=np.arange(len(y))
        self.x,self.y,self.idxs,self.min_leaf = x,y,idxs,min_leaf
        self.n,self.c = len(idxs), x.shape[1]
        self.val = np.mean(y[idxs])
        self.score = float('inf')
        self.find_varsplit()

那么我们如何找到一个变量来分割呢?嗯,我们可以逐个检查每个潜在的变量,所以c包含我们拥有的列数,逐个检查并查看是否能在该列上找到比目前更好的分割。现在请注意,这并不是完整的随机森林定义。这是假设最大特征被设置为全部的情况。请记住,我们可以将最大特征设置为 0.5,这样我们就不会检查从零到c的所有数字,而是会随机检查从零到c的一半数字。因此,如果您想将其转换为支持最大特征的随机森林,您可以轻松添加一行代码来实现。但是在我们今天的实现中,我们不打算这样做。因此,我们只需要找到更好的分割点,由于我们目前不感兴趣,所以现在我们将其留空。

# This just does one decision; we'll make it recursive later
    def find_varsplit(self):
        for i in range(self.c): 
            self.find_better_split(i)
    # We'll write this later!
    def find_better_split(self, var_idx): 
        pass
    @property
    def split_name(self): 
        return self.x.columns[self.var_idx]
    @property
    def split_col(self): 
        return self.x.values[self.idxs,self.var_idx]
    @property
    def is_leaf(self): 
        return self.score == float('inf')
    def __repr__(self):
        s = f'n: {self.n}; val:{self.val}'
        if not self.is_leaf:
            s += f'; score:{self.score}; split:{self.split}; var: {self.split_name}'
        return s

在开始编写一个类时,我喜欢做的另一件事是,我想要有一种方法来打印出该类中的内容。如果你输入 print,后面跟着一个对象,或者在 Jupyter Notebook 中,你只需输入对象的名称。目前,它只是打印出<__main__.DecisionTree at 0x7f645ec22358>,这并不是很有帮助。所以如果我们想要用有用的东西来替换它,我们必须定义一个特殊的 Python 方法,名为__repr__,以获得这个对象的表示。所以当我们在 Jupyter Notebook 单元格中基本上只写出名称时,在幕后,它调用那个函数,而该方法的默认实现只是打印出那些无用的东西。所以我们可以替换它,而不是说让我们创建一个格式化字符串,在这里我们将打印出f'n: **{self.n}**; val:**{self.val}'**,所以这个节点中有多少行,以及因变量的平均值是多少。然后,如果它不是叶节点,也就是说如果它有一个分裂,那么我们还应该能够打印出分数,我们分裂出的值,以及我们分裂的变量。现在你会注意到这里,self.is_leaf被定义为一个方法,但我后面没有加括号。这是一种特殊类型的方法,称为属性。属性看起来像一个普通的变量,但实际上是动态计算的。所以当我调用is_leaf时,实际上调用的是**def** is_leaf(self)函数。但我有这个特殊的装饰器@property。这意味着当你调用它时,你不必包括括号。所以它会说这是一个叶子还是不是。所以叶子是我们不分裂的东西。如果我们没有对它进行分裂,那么它的分数仍然设置为无穷大,这就是我的逻辑。

这个@符号被称为装饰器。基本上是告诉 Python 关于你的方法的更多信息的一种方式。任何之前使用过像 Flask 或类似框架进行过 web 编程的人都必须声明这个方法将响应 URL 的这一部分,要么是 POST,要么是 GET,并将其放在一个特殊的装饰器中。在幕后,这告诉 Python 以一种特殊的方式处理这个方法。所以@property是另一个装饰器。如果你在 Python 中变得更加高级,你实际上可以学习如何编写自己的装饰器,就像之前提到的那样,基本上插入一些额外的代码,但现在只需要知道有一堆预定义的装饰器可以用来改变我们的方法的行为,其中之一就是@property,这基本上意味着你不再需要加括号,当然这意味着你不能再添加任何参数了,只能是self

问题:如果分数是无穷大,为什么它是叶子?无穷大不是意味着你在根节点吗?不,无穷大意味着你不在根节点。它意味着你在叶子节点。所以根节点将会有一个分裂,假设我们找到一个。一切都会分裂,直到我们到达底部(即叶子节点),所以叶子节点的分数将是无穷大,因为它们不会分裂。

m = TreeEnsemble(
    X_train, y_train, 
    n_trees=10, 
    sample_sz=1000,
    min_leaf=3
)
m.trees[0]
'''
n: 1000; val:10.079014121552744
'''

这就是我们的决策树。它并没有做太多事情,但至少我们可以创建一个集成。10 棵树,样本量为 1,000,我们可以打印出来。现在当我们输入m.trees[0]时,它不会显示<__main__.DecisionTree at 0x7f645ec22358>,而是显示我们要求它显示的内容。这是叶子节点,因为我们还没有在其上进行分割,所以我们没有更多要说的。

然后索引是,所有从零到一千的数字,因为树的基础包含了一切。这是随机样本中的所有内容,因为当我们到达决策树的时候,我们不再需要担心随机森林中的任何随机性。

fast.ai 机器学习笔记(二)(4)https://developer.aliyun.com/article/1482669

相关文章
|
1天前
|
机器学习/深度学习 人工智能 算法
【机器学习】探究Q-Learning通过学习最优策略来解决AI序列决策问题
【机器学习】探究Q-Learning通过学习最优策略来解决AI序列决策问题
|
1天前
|
机器学习/深度学习 人工智能 测试技术
自动化测试中AI与机器学习的融合应用
【4月更文挑战第29天】 随着技术的不断进步,人工智能(AI)和机器学习(ML)在软件测试中的应用越来越广泛。本文将探讨AI和ML如何改变自动化测试领域,提高测试效率和质量。我们将讨论AI和ML的基本概念,以及它们如何应用于自动化测试,包括智能测试用例生成,缺陷预测,测试执行优化等方面。最后,我们还将讨论AI和ML在自动化测试中的挑战和未来发展趋势。
|
14天前
|
机器学习/深度学习 自然语言处理 PyTorch
fast.ai 深度学习笔记(三)(4)
fast.ai 深度学习笔记(三)(4)
22 0
|
14天前
|
机器学习/深度学习 算法 PyTorch
fast.ai 深度学习笔记(三)(3)
fast.ai 深度学习笔记(三)(3)
30 0
|
14天前
|
机器学习/深度学习 编解码 自然语言处理
fast.ai 深度学习笔记(三)(2)
fast.ai 深度学习笔记(三)(2)
31 0
|
14天前
|
机器学习/深度学习 PyTorch 算法框架/工具
fast.ai 深度学习笔记(三)(1)
fast.ai 深度学习笔记(三)(1)
34 0
|
索引 机器学习/深度学习 计算机视觉
fast.ai 深度学习笔记(四)(3)
fast.ai 深度学习笔记(四)
40 0
|
15天前
|
机器学习/深度学习 固态存储 Python
fast.ai 深度学习笔记(四)(2)
fast.ai 深度学习笔记(四)
48 3
fast.ai 深度学习笔记(四)(2)
|
15天前
|
API 机器学习/深度学习 Python
fast.ai 深度学习笔记(四)(1)
fast.ai 深度学习笔记(四)
62 3
fast.ai 深度学习笔记(四)(1)
|
15天前
|
机器学习/深度学习 算法框架/工具 PyTorch
fast.ai 深度学习笔记(五)(4)
fast.ai 深度学习笔记(五)
71 3
fast.ai 深度学习笔记(五)(4)