FastAI 之书(面向程序员的 FastAI)(四)(1)https://developer.aliyun.com/article/1483407
通过打印模型可以看到层的名称:
learn.model
EmbeddingDotBias( (u_weight): Embedding(944, 50) (i_weight): Embedding(1635, 50) (u_bias): Embedding(944, 1) (i_bias): Embedding(1635, 1) )
我们可以使用这些来复制我们在上一节中所做的任何分析,例如:
movie_bias = learn.model.i_bias.weight.squeeze() idxs = movie_bias.argsort(descending=True)[:5] [dls.classes['title'][i] for i in idxs]
['Titanic (1997)', "Schindler's List (1993)", 'Shawshank Redemption, The (1994)', 'L.A. Confidential (1997)', 'Silence of the Lambs, The (1991)']
我们可以使用这些学到的嵌入来查看距离。
嵌入距离
在二维地图上,我们可以通过使用毕达哥拉斯定理的公式来计算两个坐标之间的距离:x 2 + y 2(假设x和y是每个轴上坐标之间的距离)。对于一个 50 维的嵌入,我们可以做完全相同的事情,只是将所有 50 个坐标距离的平方相加。
如果有两部几乎相同的电影,它们的嵌入向量也必须几乎相同,因为喜欢它们的用户几乎完全相同。这里有一个更一般的想法:电影的相似性可以由喜欢这些电影的用户的相似性来定义。这直接意味着两部电影的嵌入向量之间的距离可以定义这种相似性。我们可以利用这一点找到与“沉默的羔羊”最相似的电影:
movie_factors = learn.model.i_weight.weight idx = dls.classes['title'].o2i['Silence of the Lambs, The (1991)'] distances = nn.CosineSimilarity(dim=1)(movie_factors, movie_factors[idx][None]) idx = distances.argsort(descending=True)[1] dls.classes['title'][idx]
'Dial M for Murder (1954)'
现在我们已经成功训练了一个模型,让我们看看如何处理没有用户数据的情况。我们如何向新用户推荐?
引导协同过滤模型
在实践中使用协同过滤模型的最大挑战是“引导问题”。这个问题的最极端版本是没有用户,因此没有历史可供学习。您向您的第一个用户推荐什么产品?
但即使您是一家历史悠久的公司,拥有长期的用户交易记录,您仍然会面临一个问题:当新用户注册时,您该怎么办?实际上,当您向您的产品组合添加新产品时,您该怎么办?这个问题没有魔法解决方案,而我们建议的解决方案实际上只是“运用常识”的变体。您可以将新用户分配为其他用户所有嵌入向量的平均值,但这会带来一个问题,即该潜在因素的特定组合可能并不常见(例如,科幻因素的平均值可能很高,而动作因素的平均值可能很低,但很少有人喜欢科幻而不喜欢动作)。最好选择一个特定用户来代表“平均品味”。
更好的方法是使用基于用户元数据的表格模型来构建您的初始嵌入向量。当用户注册时,考虑一下您可以询问哪些问题来帮助您了解他们的口味。然后,您可以创建一个模型,其中因变量是用户的嵌入向量,而自变量是您问他们的问题的结果,以及他们的注册元数据。我们将在下一节中看到如何创建这些类型的表格模型。(您可能已经注意到,当您注册 Pandora 和 Netflix 等服务时,它们往往会问您一些关于您喜欢的电影或音乐类型的问题;这就是它们如何提出您的初始协同过滤推荐的方式。)
需要注意的一点是,一小部分非常热情的用户可能最终会有效地为整个用户群设置推荐。这是一个非常常见的问题,例如,在电影推荐系统中。看动漫的人往往会看很多动漫,而且不怎么看其他东西,花很多时间在网站上评分。因此,动漫往往在许多“有史以来最佳电影”列表中被过度代表。在这种特殊情况下,很明显您有一个代表性偏见的问题,但如果偏见发生在潜在因素中,可能一点也不明显。
这样的问题可能会改变您的用户群体的整体构成,以及您系统的行为。这特别是由于正反馈循环。如果您的一小部分用户倾向于设定您的推荐系统的方向,他们自然会吸引更多类似他们的人来到您的系统。这当然会放大原始的表征偏见。这种偏见是一种被指数级放大的自然倾向。您可能已经看到一些公司高管对他们的在线平台如何迅速恶化表示惊讶,以至于表达了与创始人价值观不符的价值观。在存在这种类型的反馈循环的情况下,很容易看到这种分歧如何迅速发生,以及以一种隐藏的方式,直到为时已晚。
在这样一个自我强化的系统中,我们可能应该预期这些反馈循环是常态,而不是例外。因此,您应该假设您会看到它们,为此做好计划,并提前确定如何处理这些问题。尝试考虑反馈循环可能在您的系统中表示的所有方式,以及您如何能够在数据中识别它们。最终,这又回到了我们关于如何在推出任何类型的机器学习系统时避免灾难的最初建议。这一切都是为了确保有人参与其中;有仔细的监控,以及一个渐进和周到的推出。
我们的点积模型效果相当不错,并且是许多成功的现实世界推荐系统的基础。这种协同过滤方法被称为概率矩阵分解(PMF)。另一种方法,通常在给定相同数据时效果类似,是深度学习。
协同过滤的深度学习
将我们的架构转换为深度学习模型的第一步是获取嵌入查找的结果并将这些激活连接在一起。这给我们一个矩阵,然后我们可以按照通常的方式通过线性层和非线性传递它们。
由于我们将连接嵌入矩阵,而不是取它们的点积,所以两个嵌入矩阵可以具有不同的大小(不同数量的潜在因素)。fastai 有一个函数get_emb_sz
,根据 fast.ai 发现在实践中往往效果良好的启发式方法,返回推荐的嵌入矩阵大小:
embs = get_emb_sz(dls) embs
[(944, 74), (1635, 101)]
让我们实现这个类:
class CollabNN(Module): def __init__(self, user_sz, item_sz, y_range=(0,5.5), n_act=100): self.user_factors = Embedding(*user_sz) self.item_factors = Embedding(*item_sz) self.layers = nn.Sequential( nn.Linear(user_sz[1]+item_sz[1], n_act), nn.ReLU(), nn.Linear(n_act, 1)) self.y_range = y_range def forward(self, x): embs = self.user_factors(x[:,0]),self.item_factors(x[:,1]) x = self.layers(torch.cat(embs, dim=1)) return sigmoid_range(x, *self.y_range)
并使用它创建一个模型:
model = CollabNN(*embs)
CollabNN
以与本章中先前类似的方式创建我们的Embedding
层,只是现在我们使用embs
大小。self.layers
与我们在第四章为 MNIST 创建的迷你神经网络是相同的。然后,在forward
中,我们应用嵌入,连接结果,并通过迷你神经网络传递。最后,我们像以前的模型一样应用sigmoid_range
。
让我们看看它是否训练:
learn = Learner(dls, model, loss_func=MSELossFlat()) learn.fit_one_cycle(5, 5e-3, wd=0.01)
epoch | train_loss | valid_loss | time |
0 | 0.940104 | 0.959786 | 00:15 |
1 | 0.893943 | 0.905222 | 00:14 |
2 | 0.865591 | 0.875238 | 00:14 |
3 | 0.800177 | 0.867468 | 00:14 |
4 | 0.760255 | 0.867455 | 00:14 |
如果您在调用collab_learner
时传递use_nn=True
(包括为您调用get_emb_sz
),fastai 在fastai.collab
中提供了这个模型,并且让您轻松创建更多层。例如,在这里我们创建了两个隐藏层,分别为大小 100 和 50:
learn = collab_learner(dls, use_nn=True, y_range=(0, 5.5), layers=[100,50]) learn.fit_one_cycle(5, 5e-3, wd=0.1)
epoch | train_loss | valid_loss | time |
0 | 1.002747 | 0.972392 | 00:16 |
1 | 0.926903 | 0.922348 | 00:16 |
2 | 0.877160 | 0.893401 | 00:16 |
3 | 0.838334 | 0.865040 | 00:16 |
4 | 0.781666 | 0.864936 | 00:16 |
learn.model
是EmbeddingNN
类型的对象。让我们看一下 fastai 对这个类的代码:
@delegates(TabularModel) class EmbeddingNN(TabularModel): def __init__(self, emb_szs, layers, **kwargs): super().__init__(emb_szs, layers=layers, n_cont=0, out_sz=1, **kwargs)
哇,这不是很多代码!这个类继承自TabularModel
,这是它获取所有功能的地方。在__init__
中,它调用TabularModel
中的相同方法,传递n_cont=0
和out_sz=1
;除此之外,它只传递它收到的任何参数。
尽管EmbeddingNN
的结果比点积方法稍差一些(这显示了为领域精心构建架构的力量),但它确实允许我们做一件非常重要的事情:我们现在可以直接将其他用户和电影信息、日期和时间信息或任何可能与推荐相关的信息纳入考虑。这正是TabularModel
所做的。事实上,我们现在已经看到,EmbeddingNN
只是一个TabularModel
,其中n_cont=0
和out_sz=1
。因此,我们最好花一些时间了解TabularModel
,以及如何使用它获得出色的结果!我们将在下一章中做到这一点。
结论
对于我们的第一个非计算机视觉应用,我们研究了推荐系统,并看到梯度下降如何从评分历史中学习有关项目的内在因素或偏差。然后,这些因素可以为我们提供有关数据的信息。
我们还在 PyTorch 中构建了我们的第一个模型。在书的下一部分中,我们将做更多这样的工作,但首先,让我们完成对深度学习的其他一般应用的探讨,继续处理表格数据。
问卷
- 协同过滤解决了什么问题?
- 它是如何解决的?
- 为什么协同过滤预测模型可能无法成为非常有用的推荐系统?
- 协同过滤数据的交叉表表示是什么样的?
- 编写代码创建 MovieLens 数据的交叉表表示(您可能需要进行一些网络搜索!)。
- 什么是潜在因素?为什么它是“潜在”的?
- 什么是点积?使用纯 Python 和列表手动计算点积。
pandas.DataFrame.merge
是做什么的?- 什么是嵌入矩阵?
- 嵌入和一个独热编码向量矩阵之间的关系是什么?
- 如果我们可以使用独热编码向量来做同样的事情,为什么我们需要
Embedding
? - 在我们开始训练之前,嵌入包含什么内容(假设我们没有使用预训练模型)?
- 创建一个类(尽量不要偷看!)并使用它。
x[:,0]
返回什么?- 重写
DotProduct
类(尽量不要偷看!)并用它训练模型。 - 在 MovieLens 中使用什么样的损失函数是好的?为什么?
- 如果我们在 MovieLens 中使用交叉熵损失会发生什么?我们需要如何更改模型?
- 点积模型中偏差的用途是什么?
- 权重衰减的另一个名称是什么?
- 写出权重衰减的方程(不要偷看!)。
- 写出权重衰减的梯度方程。为什么它有助于减少权重?
- 为什么减少权重会导致更好的泛化?
- PyTorch 中的
argsort
是做什么的? - 对电影偏差进行排序是否会得到与按电影平均评分相同的结果?为什么/为什么不?
- 如何打印模型中层的名称和详细信息?
- 协同过滤中的“自举问题”是什么?
- 如何处理新用户的自举问题?对于新电影呢?
- 反馈循环如何影响协同过滤系统?
- 在协同过滤中使用神经网络时,为什么我们可以为电影和用户使用不同数量的因素?
- 为什么在
CollabNN
模型中有一个nn.Sequential
? - 如果我们想要向协同过滤模型添加有关用户和项目的元数据,或者有关日期和时间等信息,应该使用什么样的模型?
进一步研究
- 看看
Embedding
版本的DotProductBias
和create_params
版本之间的所有差异,并尝试理解为什么需要进行每一项更改。如果不确定,尝试撤销每个更改以查看发生了什么。(注意:甚至在forward
中使用的括号类型也已更改!) - 找到另外三个协同过滤正在使用的领域,并在这些领域中确定这种方法的优缺点。
- 使用完整的 MovieLens 数据集完成这个笔记本,并将结果与在线基准进行比较。看看你能否提高准确性。在书的网站和 fast.ai 论坛上寻找想法。请注意,完整数据集中有更多列,看看你是否也可以使用这些列(下一章可能会给你一些想法)。
- 为 MovieLens 创建一个使用交叉熵损失的模型,并将其与本章中的模型进行比较。
第九章:表格建模深入探讨
原文:
www.bookstack.cn/read/th-fastai-book/863394bdf5dc8421.md
译者:飞龙
表格建模将数据以表格形式(如电子表格或 CSV 文件)呈现。目标是基于其他列中的值来预测一列中的值。在本章中,我们将不仅看深度学习,还将看更一般的机器学习技术,如随机森林,因为根据您的问题,它们可能会给出更好的结果。
我们将看看我们应该如何预处理和清理数据,以及如何在训练后解释我们模型的结果,但首先我们将看看如何通过使用嵌入将包含类别的列馈送到期望数字的模型中。
分类嵌入
在表格数据中,某些列可能包含数值数据,如“年龄”,而其他列包含字符串值,如“性别”。数值数据可以直接输入模型(经过一些可选的预处理),但其他列需要转换为数字。由于这些值对应不同的类别,我们通常将这种类型的变量称为分类变量。第一种类型被称为连续 变量。
术语:连续和分类变量
连续变量是数值数据,如“年龄”,可以直接输入模型,因为可以直接进行加法和乘法。分类变量包含多个离散级别,如“电影 ID”,对于这些级别,加法和乘法没有意义(即使它们以数字形式存储)。
2015 年底,Rossmann 销售竞赛在 Kaggle 上举行。参赛者获得了有关德国各个商店的各种信息,并被要求尝试预测若干天的销售额。目标是帮助公司适当管理库存,并能够满足需求而不必持有不必要的库存。官方训练集提供了大量有关商店的信息。允许参赛者使用额外的数据,只要这些数据是公开的并对所有参与者可用。
其中一位金牌得主使用了深度学习,在已知的最先进深度学习表格模型的早期示例中。他们的方法涉及远少于其他金牌得主的基于领域知识的特征工程。论文“分类变量的实体嵌入”描述了他们的方法。在书籍网站的在线专章中,我们展示了如何从头开始复制它,并获得论文中显示的相同准确性。在论文的摘要中,作者(Cheng Guo 和 Felix Bekhahn)说:
实体嵌入不仅可以减少内存使用量并加快神经网络的速度,与独热编码相比,更重要的是通过将相似值映射到嵌入空间中的相邻位置,揭示了分类变量的固有属性…[它]在具有大量高基数特征的数据集中特别有用,其他方法往往容易过拟合…由于实体嵌入为分类变量定义了距离度量,因此可以用于可视化分类数据和数据聚类。
当我们构建协同过滤模型时,我们已经注意到了所有这些要点。然而,我们可以清楚地看到这些见解远不止于协同过滤。
该论文还指出(正如我们在前一章中讨论的),嵌入层与在每个独热编码输入层之后放置普通线性层完全等效。作者使用图 9-1 中的图表来展示这种等效性。请注意,“密集层”是与“线性层”相同含义的术语,而独热编码层代表输入。
这一见解很重要,因为我们已经知道如何训练线性层,所以从架构和训练算法的角度来看,嵌入层只是另一层。我们在前一章中实践中也看到了这一点,当我们构建了一个与这个图表完全相同的协同过滤神经网络时。
就像我们分析了电影评论的嵌入权重一样,实体嵌入论文的作者分析了他们的销售预测模型的嵌入权重。他们发现的结果非常惊人,并展示了他们的第二个关键见解:嵌入将分类变量转换为连续且有意义的输入。
图 9-1。神经网络中的实体嵌入(由 Cheng Guo 和 Felix Berkhahn 提供)
图 9-2 中的图像说明了这些想法。它们基于论文中使用的方法,以及我们添加的一些分析。
图 9-2。状态嵌入和地图(由 Cheng Guo 和 Felix Berkhahn 提供)
左侧是State
类别可能值的嵌入矩阵图。对于分类变量,我们称变量的可能值为其“级别”(或“类别”或“类别”),因此这里一个级别是“柏林”,另一个是“汉堡”等。右侧是德国地图。德国各州的实际物理位置不是提供的数据的一部分,但模型本身学会了它们必须在哪里,仅基于商店销售的行为!
您还记得我们谈论过嵌入之间的距离吗?论文的作者绘制了商店嵌入之间的距离与商店之间的实际地理距离之间的关系(参见图 9-3)。他们发现它们非常接近!
图 9-3。存储距离(由 Cheng Guo 和 Felix Berkhahn 提供)
我们甚至尝试绘制一周中的日期和一年中的月份的嵌入,发现在日历上彼此相邻的日期和月份也在嵌入中靠近,如图 9-4 所示。
这两个示例中突出的是,我们向模型提供了基本关于离散实体的分类数据(例如德国各州或一周中的日期),然后模型学习了这些实体的嵌入,定义了它们之间的连续距离概念。由于嵌入距离是基于数据中的真实模式学习的,因此该距离往往与我们的直觉相匹配。
图 9-4。日期嵌入(由 Cheng Guo 和 Felix Berkhahn 提供)
此外,嵌入本身是有价值的,因为模型更擅长理解连续变量。这并不奇怪,因为模型由许多连续参数权重和连续激活值构成,这些值通过梯度下降(一种用于找到连续函数最小值的学习算法)进行更新。
另一个好处是,我们可以将连续嵌入值与真正连续的输入数据简单地结合在一起:我们只需连接变量并将连接输入到我们的第一个密集层中。换句话说,在与原始连续输入数据交互之前,原始分类数据通过嵌入层进行转换。这就是 fastai 和 Guo 和 Berkhahn 处理包含连续和分类变量的表格模型的方式。
使用这种连接方法的一个示例是谷歌在 Google Play 上进行推荐的方式,正如在论文“广泛和深度学习用于推荐系统”中所解释的那样。图 9-5 说明了这一点。
有趣的是,谷歌团队结合了我们在上一章看到的两种方法:点积(他们称之为交叉乘积)和神经网络方法。
图 9-5. 谷歌 Play 推荐系统
让我们暂停一下。到目前为止,解决我们所有建模问题的方法都是训练一个深度学习模型。确实,对于像图像、声音、自然语言文本等复杂的非结构化数据,这是一个相当好的经验法则。深度学习在协同过滤方面也表现得非常出色。但对于分析表格数据来说,它并不总是最佳的起点。
超越深度学习
大多数机器学习课程会向你介绍几十种算法,简要介绍它们背后的数学原理,可能还会有一个玩具示例。你会被展示的各种技术茫然不解,对如何应用它们几乎没有实际的理解。
好消息是,现代机器学习可以归结为几种广泛适用的关键技术。最近的研究表明,绝大多数数据集最适合用两种方法建模:
- 决策树集成(即随机森林和梯度提升机),主要用于结构化数据(比如大多数公司数据库表中可能找到的数据)
- 使用 SGD 学习的多层神经网络(即浅层和/或深度学习),主要用于非结构化数据(比如音频、图像和自然语言)
尽管深度学习几乎总是在非结构化数据方面明显优越,但对于许多种结构化数据,这两种方法往往给出相似的结果。但决策树集成往往训练更快,通常更容易解释,不需要特殊的 GPU 硬件进行规模推断,并且通常需要更少的超参数调整。它们也比深度学习流行得早得多,因此在它们周围有更成熟的工具和文档生态系统。
最重要的是,解释表格数据模型的关键步骤对于决策树集成来说要容易得多。有工具和方法可以回答相关问题,比如:数据集中哪些列对你的预测最重要?它们与因变量有什么关系?它们如何相互作用?哪些特定特征对某个特定观察最重要?
因此,决策树集成是我们分析新表格数据集的第一步方法。
这一准则的例外情况是当数据集符合以下条件之一时:
- 有一些高基数分类变量非常重要(“基数”指代表示类别的离散级别的数量,因此高基数分类变量是指像邮政编码这样可能有数千个可能级别的变量)。
- 有一些包含最好用神经网络理解的数据的列,比如纯文本数据。
在实践中,当我们处理符合这些特殊条件的数据集时,我们总是尝试决策树集成和深度学习,看哪个效果更好。在我们的协同过滤示例中,深度学习可能是一个有用的方法,因为我们至少有两个高基数分类变量:用户和电影。但在实践中,事情往往没有那么明确,通常会有高基数和低基数分类变量以及连续变量的混合。
无论如何,很明显我们需要将决策树集成添加到我们的建模工具箱中!
到目前为止,我们几乎所有的繁重工作都是使用 PyTorch 和 fastai 完成的。但是这些库主要设计用于进行大量矩阵乘法和导数计算(即,类似深度学习的操作!)。决策树根本不依赖于这些操作,因此 PyTorch 没有太多用处。
相反,我们将主要依赖一个名为scikit-learn(也称为sklearn)的库。Scikit-learn 是一个流行的库,用于创建机器学习模型,使用的方法不包括深度学习。此外,我们需要进行一些表格数据处理和查询,因此我们将使用 Pandas 库。最后,我们还需要 NumPy,因为这是 sklearn 和 Pandas 都依赖的主要数值编程库。
我们没有时间在本书中深入研究所有这些库,因此我们只会涉及每个库的一些主要部分。对于更深入的讨论,我们强烈建议阅读 Wes McKinney 的Python 数据分析(O’Reilly)。McKinney 是 Pandas 的创始人,因此您可以确信信息是准确的!
首先,让我们收集我们将使用的数据。
数据集
本章中使用的数据集来自于蓝皮书对推土机的 Kaggle 竞赛,该竞赛的描述如下:“比赛的目标是根据其使用情况、设备类型和配置来预测拍卖中特定重型设备的销售价格。数据来源于拍卖结果发布,并包括有关使用情况和设备配置的信息。”
这是一种非常常见的数据集类型和预测问题,类似于您在项目或工作场所中可能遇到的情况。该数据集可以在 Kaggle 上下载,Kaggle 是一个举办数据科学竞赛的网站。
Kaggle 竞赛
Kaggle 是一个非常棒的资源,适合有志成为数据科学家或任何希望提高机器学习技能的人。没有什么比亲自动手实践并获得实时反馈来帮助您提高技能。
Kaggle 提供了以下内容:
- 有趣的数据集
- 关于您的表现的反馈
- 排行榜可以看到什么是好的,什么是可能的,以及什么是最先进的
- 获奖选手分享有用的技巧和技术的博客文章
到目前为止,我们所有的数据集都可以通过 fastai 的集成数据集系统下载。然而,在本章中我们将使用的数据集只能从 Kaggle 获取。因此,您需要在该网站上注册,然后转到比赛页面。在该页面上点击规则,然后点击我理解并接受。(尽管比赛已经结束,您不会参加,但您仍然需要同意规则才能下载数据。)
下载 Kaggle 数据集的最简单方法是使用 Kaggle API。您可以通过使用pip
安装它,并在笔记本单元格中运行以下命令:
!pip install kaggle
使用 Kaggle API 需要一个 API 密钥;要获取一个,点击 Kaggle 网站上的个人资料图片,选择我的账户;然后点击创建新的 API 令牌。这将在您的 PC 上保存一个名为kaggle.json的文件。您需要将此密钥复制到您的 GPU 服务器上。为此,请打开您下载的文件,复制内容,并将其粘贴到与本章相关的笔记本中的以下单引号内(例如,creds =
‘{"username":"*xxx*","key":"*xxx*"}
’``):
creds = ''
然后执行此单元格(这只需要运行一次):
cred_path = Path('~/.kaggle/kaggle.json').expanduser() if not cred_path.exists(): cred_path.parent.mkdir(exist_ok=True) cred_path.write(creds) cred_path.chmod(0o600)
现在您可以从 Kaggle 下载数据集!选择一个路径来下载数据集:
path = URLs.path('bluebook') path
Path('/home/sgugger/.fastai/archive/bluebook')
然后使用 Kaggle API 将数据集下载到该路径并解压缩:
if not path.exists(): path.mkdir() api.competition_download_cli('bluebook-for-bulldozers', path=path) file_extract(path/'bluebook-for-bulldozers.zip') path.ls(file_type='text')
(#7) [Path('Valid.csv'),Path('Machine_Appendix.csv'),Path('ValidSolution.csv'),P > ath('TrainAndValid.csv'),Path('random_forest_benchmark_test.csv'),Path('Test. > csv'),Path('median_benchmark.csv')]
现在我们已经下载了数据集,让我们来看一下!
查看数据
Kaggle 提供了有关我们数据集中某些字段的信息。数据页面解释了train.csv中的关键字段如下:
SalesID
销售的唯一标识符。
MachineID
机器的唯一标识符。一台机器可以被多次出售。
销售价格
机器在拍卖中的售价(仅在train.csv中提供)。
销售日期
销售日期。
在任何数据科学工作中,直接查看数据是很重要的,以确保您了解格式、存储方式、包含的值类型等。即使您已经阅读了数据的描述,实际数据可能并非您所期望的。我们将从将训练集读入 Pandas DataFrame 开始。通常,除非 Pandas 实际耗尽内存并返回错误,否则最好也指定low_memory=False
。low_memory
参数默认为True
,告诉 Pandas 一次只查看几行数据,以确定每列中包含的数据类型。这意味着 Pandas 最终可能会为不同的行使用不同的数据类型,这通常会导致数据处理错误或模型训练问题。
让我们加载数据并查看列:
df = pd.read_csv(path/'TrainAndValid.csv', low_memory=False)
df.columns
Index(['SalesID', 'SalePrice', 'MachineID', 'ModelID', 'datasource', 'auctioneerID', 'YearMade', 'MachineHoursCurrentMeter', 'UsageBand', 'saledate', 'fiModelDesc', 'fiBaseModel', 'fiSecondaryDesc', 'fiModelSeries', 'fiModelDescriptor', 'ProductSize', 'fiProductClassDesc', 'state', 'ProductGroup', 'ProductGroupDesc', 'Drive_System', 'Enclosure', 'Forks', 'Pad_Type', 'Ride_Control', 'Stick', 'Transmission', 'Turbocharged', 'Blade_Extension', 'Blade_Width', 'Enclosure_Type', 'Engine_Horsepower', 'Hydraulics', 'Pushblock', 'Ripper', 'Scarifier', 'Tip_Control', 'Tire_Size', 'Coupler', 'Coupler_System', 'Grouser_Tracks', 'Hydraulics_Flow', 'Track_Type', 'Undercarriage_Pad_Width', 'Stick_Length', 'Thumb', 'Pattern_Changer', 'Grouser_Type', 'Backhoe_Mounting', 'Blade_Type', 'Travel_Controls', 'Differential_Type', 'Steering_Controls'], dtype='object')
这是我们要查看的许多列!尝试浏览数据集,了解每个列中包含的信息类型。我们很快将看到如何“聚焦”于最有趣的部分。
在这一点上,一个很好的下一步是处理有序列的列。这指的是包含字符串或类似内容的列,但其中这些字符串具有自然排序。例如,这里是ProductSize
的级别:
df['ProductSize'].unique()
array([nan, 'Medium', 'Small', 'Large / Medium', 'Mini', 'Large', 'Compact'], > dtype=object)
我们可以告诉 Pandas 这些级别的适当排序方式如下:
sizes = 'Large','Large / Medium','Medium','Small','Mini','Compact'
df['ProductSize'] = df['ProductSize'].astype('category') df['ProductSize'].cat.set_categories(sizes, ordered=True, inplace=True)
最重要的数据列是因变量——我们想要预测的变量。请记住,模型的度量是反映预测有多好的函数。重要的是要注意项目使用的度量标准。通常,选择度量标准是项目设置的重要部分。在许多情况下,选择一个好的度量标准将需要不仅仅是选择一个已经存在的变量。这更像是一个设计过程。您应该仔细考虑哪种度量标准,或一组度量标准,实际上衡量了对您重要的模型质量概念。如果没有变量代表该度量标准,您应该看看是否可以从可用的变量构建度量标准。
然而,在这种情况下,Kaggle 告诉我们要使用的度量标准是实际和预测拍卖价格之间的平方对数误差(RMLSE)。我们只需要进行少量处理即可使用这个度量标准:我们取价格的对数,这样该值的m_rmse
将给出我们最终需要的值:
dep_var = 'SalePrice'
df[dep_var] = np.log(df[dep_var])
我们现在准备探索我们的第一个用于表格数据的机器学习算法:决策树。
决策树
决策树集成,顾名思义,依赖于决策树。所以让我们从那里开始!决策树对数据提出一系列关于数据的二元(是或否)问题。在每个问题之后,树的那部分数据在“是”和“否”分支之间分割,如图 9-6 所示。经过一个或多个问题后,可以基于所有先前答案做出预测,或者需要另一个问题。
现在,这一系列问题是一个过程,用于获取任何数据项,无论是来自训练集还是新数据项,并将该项分配到一个组中。换句话说,在提出问题并回答问题之后,我们可以说该项属于与所有其他训练数据项相同的组,这些数据项对问题的答案相同。但这有什么好处呢?我们模型的目标是预测项目的值,而不是将它们分配到训练数据集中的组中。好处在于我们现在可以为这些组中的每个项目分配一个预测值——对于回归,我们取该组中项目的目标均值。
图 9-6. 决策树示例
让我们考虑如何找到正确的问题。当然,我们不希望自己创建所有这些问题-这就是计算机的作用!训练决策树的基本步骤可以很容易地写下来:
- 依次循环数据集的每一列。
- 对于每一列,依次循环该列的每个可能级别。
- 尝试将数据分成两组,基于它们是否大于或小于该值(或者如果它是一个分类变量,则基于它们是否等于或不等于该分类变量的水平)。
- 找到这两组中每组的平均销售价格,并查看这与该组中每个设备的实际销售价格有多接近。将这视为一个非常简单的“模型”,其中我们的预测只是该项组的平均销售价格。
- 在循环遍历所有列和每个可能的级别后,选择使用该简单模型给出最佳预测的分割点。
- 现在我们的数据有两组,基于这个选定的分割。将每个组视为一个单独的数据集,并通过返回到步骤 1 为每个组找到最佳分割。
- 递归地继续这个过程,直到每个组达到某个停止标准-例如,当组中只有 20 个项目时停止进一步分割。
尽管这是一个很容易自己实现的算法(这是一个很好的练习),但我们可以节省一些时间,使用内置在 sklearn 中的实现。
然而,首先,我们需要做一些数据准备。
Alexis 说
这是一个值得思考的有益问题。如果您考虑到定义决策树的过程本质上选择了一个关于变量的分割问题序列,您可能会问自己,我们如何知道这个过程选择了正确的序列?规则是选择产生最佳分割(即最准确地将项目分为两个不同类别)的分割问题,然后将同样的规则应用于该分割产生的组,依此类推。这在计算机科学中被称为“贪婪”方法。您能想象出一个情景,其中提出一个“不那么强大”的分割问题会使未来的分割更好(或者我应该说更好地导致更好的结果)吗?
处理日期
我们需要做的第一件数据准备工作是丰富我们对日期的表示。我们刚刚描述的决策树的基本基础是二分 - 将一组分成两组。我们查看序数变量,并根据变量的值是大于(或小于)阈值来划分数据集,我们查看分类变量,并根据变量的级别是否是特定级别来划分数据集。因此,这个算法有一种根据序数和分类数据划分数据集的方法。
但是这如何适用于常见的数据类型,日期呢?您可能希望将日期视为序数值,因为说一个日期比另一个日期更大是有意义的。然而,日期与大多数序数值有所不同,因为一些日期在某种方面与其他日期有质的不同,这通常与我们建模的系统相关。
为了帮助我们的算法智能处理日期,我们希望我们的模型不仅知道一个日期是否比另一个日期更近或更早。我们可能希望我们的模型根据日期的星期几、某一天是否是假期、所在月份等来做决策。为此,我们用一组日期元数据列替换每个日期列,例如假期、星期几和月份。这些列提供了我们认为会有用的分类数据。
fastai 带有一个函数,可以为我们执行此操作-我们只需传递包含日期的列名:
df = add_datepart(df, 'saledate')
在那里的同时,让我们为测试集做同样的事情:
df_test = pd.read_csv(path/'Test.csv', low_memory=False) df_test = add_datepart(df_test, 'saledate')
我们可以看到我们的 DataFrame 中现在有很多新的列:
' '.join(o for o in df.columns if o.startswith('sale'))
'saleYear saleMonth saleWeek saleDay saleDayofweek saleDayofyear > saleIs_month_end saleIs_month_start saleIs_quarter_end saleIs_quarter_start > saleIs_year_end saleIs_year_start saleElapsed'
这是一个很好的第一步,但我们需要做更多的清理。为此,我们将使用 fastai 对象TabularPandas
和TabularProc
。
使用 TabularPandas 和 TabularProc
第二个预处理步骤是确保我们可以处理字符串和缺失数据。默认情况下,sklearn 都不能处理。相反,我们将使用 fastai 的TabularPandas
类,它包装了一个 Pandas DataFrame 并提供了一些便利。为了填充一个TabularPandas
,我们将使用两个TabularProc
,Categorify
和FillMissing
。TabularProc
类似于常规的Transform
,但有以下不同:
- 它返回传递给它的完全相同的对象,在原地修改对象后返回。
- 它在数据首次传入时运行变换,而不是在访问数据时懒惰地运行。
Categorify
是一个TabularProc
,用数字分类列替换列。FillMissing
是一个TabularProc
,用列的中位数替换缺失值,并创建一个新的布尔列,对于任何值缺失的行,该列设置为True
。这两个变换几乎适用于您将使用的每个表格数据集,因此这是您数据处理的一个很好的起点:
procs = [Categorify, FillMissing]
TabularPandas
还将为我们处理数据集的拆分为训练集和验证集。但是,我们需要非常小心处理我们的验证集。我们希望设计它,使其类似于 Kaggle 将用来评判比赛的测试集。
回想一下验证集和测试集之间的区别,如第一章中所讨论的。验证集是我们从训练中保留的数据,以确保训练过程不会在训练数据上过拟合。测试集是更深层次地被我们自己保留的数据,以确保我们在探索各种模型架构和超参数时不会在验证数据上过拟合。
我们看不到测试集。但我们确实希望定义我们的验证数据,使其与训练数据具有与测试集相同类型的关系。
在某些情况下,随机选择数据点的子集就足够了。但这不是这种情况,因为这是一个时间序列。
如果您查看测试集中表示的日期范围,您会发现它覆盖了 2012 年 5 月的六个月期间,这比训练集中的任何日期都要晚。这是一个很好的设计,因为竞赛赞助商希望确保模型能够预测未来。但这意味着如果我们要有一个有用的验证集,我们也希望验证集比训练集更晚。Kaggle 的训练数据在 2012 年 4 月结束,因此我们将定义一个更窄的训练数据集,其中只包括 2011 年 11 月之前的 Kaggle 训练数据,并且我们将定义一个验证集,其中包括 2011 年 11 月之后的数据。
为了做到这一点,我们使用np.where
,这是一个有用的函数,返回(作为元组的第一个元素)所有True
值的索引:
cond = (df.saleYear<2011) | (df.saleMonth<10) train_idx = np.where( cond)[0] valid_idx = np.where(~cond)[0] splits = (list(train_idx),list(valid_idx))
TabularPandas
需要告诉哪些列是连续的,哪些是分类的。我们可以使用辅助函数cont_cat_split
自动处理:
cont,cat = cont_cat_split(df, 1, dep_var=dep_var)
to = TabularPandas(df, procs, cat, cont, y_names=dep_var, splits=splits)
TabularPandas
的行为很像一个 fastai 的Datasets
对象,包括提供train
和valid
属性:
len(to.train),len(to.valid)
(404710, 7988)
我们可以看到数据仍然显示为类别的字符串(这里我们只显示了一些列,因为完整的表太大了,无法放在一页上)。
to.show(3)
state | ProductGroup | Drive_System | Enclosure | SalePrice | |
0 | Alabama | WL | #na# | EROPS w AC | 11.097410 |
1 | North Carolina | WL | #na# | EROPS w AC | 10.950807 |
2 | New York | SSL | #na# | OROPS | 9.210340 |
FastAI 之书(面向程序员的 FastAI)(四)(3)https://developer.aliyun.com/article/1483409