fast.ai 机器学习笔记(一)(2)https://developer.aliyun.com/article/1482637
为什么随机森林效果如此好[1:30:21]
让我们看看小单树中的一个分割点。
fiProductClassDesc ≤ 7.5
将分割:
为什么这样做有效呢?想象一下,唯一重要的是液压挖掘机,履带−0.0 到 2.0 公吨
,其他都不重要。它可以通过首先分割fiProductClassDesc ≤ 5.5
然后fiProductClassDesc > 4.5
来选择单个元素。只需两次分割,我们就可以提取出一个单一类别。即使是具有分类变量的树也是无限灵活的。如果有一个具有不同价格水平的特定类别,它可以通过多次分割逐渐缩小到这些组。随机森林非常易于使用,而且非常弹性。
下一课,我们将学习如何分析模型,了解更多关于数据的信息,使其变得更好。
机器学习 1:第 3 课
原文:
medium.com/@hiromi_suenaga/machine-learning-1-lesson-3-fa4065d8cb1e
译者:飞龙
来自机器学习课程的个人笔记。随着我继续复习课程以“真正”理解它,这些笔记将继续更新和改进。非常感谢 Jeremy 和 Rachel 给了我这个学习的机会。
今天的课程内容:
通过使用机器学习更好地理解数据
- 这个想法与常见的说法相反,即随机森林等东西是隐藏我们意义的黑匣子。事实恰恰相反。随机森林让我们比传统方法更深入更快地理解我们的数据。
如何查看更大的数据集
- 拥有超过 1 亿行的数据集 - 杂货销售预测
问题:何时使用随机森林[2:41]?我无法想到任何绝对不会至少有些用处的情况。因此,值得一试。真正的问题可能是在什么情况下我们应该尝试其他方法,简短的答案是对于非结构化数据(图像,声音等),您几乎肯定要尝试深度学习。对于协同过滤模型(杂货竞赛属于这种类型),随机森林和深度学习方法都不是您想要的,您需要做一些调整。
上周回顾[4:42]
读取 CSV 花了一两分钟,我们将其保存为羽毛格式文件。羽毛格式几乎与 RAM 中的格式相同,因此读写速度非常快。我们在第 2 课笔记本中做的第一件事是读取羽毛格式文件。
proc_df 问题[5:28]
在这一周期间提出的一个有趣的小问题是在proc_df
函数中。proc_df
函数执行以下操作:
- 查找具有缺失值的数值列,并创建一个额外的布尔列,同时用中位数替换缺失值。
- 将分类对象转换为整数代码。
问题#1:您的测试集中可能有一些列中的缺失值,这些列在训练集中不存在,反之亦然。如果发生这种情况,当您尝试进行随机森林时,您将会出现错误,因为“缺失”布尔列出现在训练集中,但不在测试集中。
问题#2:测试集中数值的中位数可能与训练集不同。因此,它可能将其处理为具有不同语义的内容。
解决方案:现在有一个额外的返回变量nas
从proc_df
,它是一个字典,其键是具有缺失值的列的名称,字典的值是中位数。可选地,您可以将nas
作为参数传递给proc_df
,以确保它添加这些特定列并使用这些特定中位数:
df, y, nas = proc_df(df_raw, 'SalePrice', nas)
Corporación Favorita 杂货销售预测[9:25]
让我们走一遍当您处理一个真正大的数据集时的相同过程。几乎相同,但有一些情况下我们不能使用默认值,因为默认值运行速度有点慢。
能够解释您正在处理的问题是很重要的。在机器学习问题中理解的关键事项是:
- 独立变量是什么?
- 什么是因变量(您试图预测的东西)?
在这个比赛中
- 因变量 — 在两周期间每天每个商店销售了多少种产品。
- 自变量 — 过去几年每个产品在每个商店每天销售了多少单位。对于每个商店,它的位置在哪里以及它是什么类型的商店(元数据)。对于每种产品,它是什么类别的产品等。对于每个日期,我们有元数据,比如油价是多少。
这就是我们所说的关系数据集。关系数据集是指我们可以将许多不同信息连接在一起的数据集。具体来说,这种关系数据集是我们所说的“星型模式”,其中有一张中心交易表。在这个比赛中,中心交易表是train.csv
,其中包含了按日期
、store_nbr
和item_nbr
销售的数量。通过这个表,我们可以连接各种元数据(因此称为“星型”模式 — 还有一种叫做“雪花”模式)。
读取数据[15:12]
types = { 'id': 'int64', 'item_nbr': 'int32', 'store_nbr': 'int8', 'unit_sales': 'float32', 'onpromotion': 'object' } %%time df_all = pd.read_csv( f'{PATH}train.csv', parse_dates=['date'], dtype=types, infer_datetime_format=True ) ''' CPU times: user 1min 41s, sys: 5.08s, total: 1min 46s Wall time: 1min 48s '''
- 如果设置
low_memory=False
,无论您有多少内存,它都会耗尽内存。 - 为了在读取时限制占用的空间量,我们为每个列名创建一个字典,指定该列的数据类型。您可以通过运行或在数据集上使用
less
或head
来找出数据类型。 - 通过这些调整,我们可以在不到 2 分钟内读取 125,497,040 行数据。
- Python 本身并不快,但几乎我们在 Python 中进行数据科学时想要做的一切都已经为我们用 C 或更常见的 Cython 编写好了,Cython 是一种类似 Python 的语言,可以编译成 C。在 Pandas 中,很多代码是用汇编语言编写的,这些代码经过了大量优化。在幕后,很多代码都是调用基于 Fortran 的线性代数库。
问题:指定int64
与int
是否有任何性能考虑[18:33]?这里的关键性能是使用尽可能少的位数来完全表示列。如果我们对item_nbr
使用int8
,最大的item_nbr
大于 255,将无法容纳。另一方面,如果我们对store_nbr
使用int64
,它使用的位数比必要的多。鉴于这里的整个目的是避免耗尽 RAM,我们不希望使用比必要多 8 倍的内存。当您处理大型数据集时,很多时候最慢的部分是读取和写入 RAM,而不是 CPU 操作。另外,作为一个经验法则,较小的数据类型通常会运行得更快,特别是如果您可以使用单指令多数据(SIMD)矢量化代码,它可以将更多数字打包到一个单独的矢量中一次运行。
问题:我们不再需要对数据进行洗牌了吗[20:11]?尽管在这里我已经读取了整个数据,但当我开始时,我从不会一开始就读取整个数据。通过使用 UNIX 命令shuf
,您可以在命令提示符下获取数据的随机样本,然后您可以直接读取该样本。这是一个很好的方法,例如,找出要使用的数据类型 — 读取一个随机样本,让 Pandas 为您找出。通常情况下,我会尽可能多地在样本上工作,直到我确信我理解了样本后才会继续。
要使用shuf
从文件中随机选择一行,请使用-n
选项。这将限制输出为指定的数量。您还可以指定输出文件:
shuf -n 5 -o sample_training.csv train.csv
'onpromotion': ‘object'
[21:28]— object
是一个通用的 Python 数据类型,速度慢且占用内存。原因是它是一个布尔值,还有缺失值,所以我们需要在将其转换为布尔值之前处理它,如下所示:
df_all.onpromotion.fillna(False, inplace=True) df_all.onpromotion = df_all.onpromotion.map({ 'False': False, 'True': True }) df_all.onpromotion = df_all.onpromotion.astype(bool) %time df_all.to_feather('tmp/raw_groceries')
fillna(False)
: 我们不会在没有先检查的情况下这样做,但一些探索性数据分析显示这可能是一个合适的做法(即缺失值表示 false)。map({‘False’: False, ‘True’: True})
:object
通常读取为字符串,所以用实际的布尔值替换字符串‘True’
和‘False’
。astype(bool)
:最后将其转换为布尔类型。- 拥有超过 1.25 亿条记录的 feather 文件占用了不到 2.5GB 的内存。
- 现在它以一个很好的快速格式,我们可以在不到 5 秒的时间内将它保存为 feather 格式。
Pandas 通常很快,所以你可以在 20 秒内总结所有 1.25 亿条记录的每一列:
%time df_all.describe(include='all')
- 首先要看的是日期。日期很重要,因为你在实践中放入的任何模型,都会在比你训练的日期晚的某个日期放入。所以如果世界上的任何事情发生变化,你需要知道你的预测准确性也会如何变化。所以对于 Kaggle 或你自己的项目,你应该始终确保你的日期不重叠。
- 在这种情况下,训练集从 2013 年到 2017 年 8 月。
df_test = pd.read_csv( f'{PATH}test.csv', parse_dates = ['date'], dtype=types, infer_datetime_format=True ) df_test.onpromotion.fillna(False, inplace=True) df_test.onpromotion = df_test.onpromotion.map({ 'False': False, 'True': True }) df_test.onpromotion = df_test.onpromotion.astype(bool) df_test.describe(include='all')
- 在我们的测试集中,它们从第二天开始直到月底。
- 这是一个关键的事情 —— 除非你理解这个基本的部分,否则你无法真正做出任何有用的机器学习。你有四年的数据,你正在尝试预测接下来的两周。在你能够做好这个工作之前,这是你需要理解的基本事情。
- 如果你想使用一个较小的数据集,我们应该使用最近的 —— 而不是随机的集合。
问题:四年前大约在同一时间段重要吗(例如在圣诞节左右)?确实。并不是说四年前没有有用的信息,所以我们不想完全抛弃它。但作为第一步,如果你要提交平均值,你不会提交 2012 年销售额的平均值,而可能想要提交上个月销售额的平均值。之后,我们可能希望更高权重最近的日期,因为它们可能更相关。但我们应该进行大量的探索性数据分析来检查。
df_all.tail()
这是数据底部的样子。
df_all.unit_sales = np.log1p(np.clip(df_all.unit_sales, 0, None))
- 我们必须对销售额取对数,因为我们正在尝试预测根据比率变化的某些东西,而他们告诉我们,在这个比赛中,均方根对数误差是他们关心的事情。
np.clip(df_all.unit_sales, 0, None)
: 有一些代表退货的负销售额,组织者告诉我们在这个比赛中将它们视为零。clip
截断到指定的最小值和最大值。np.log1p
:值加 1 的对数。比赛细节告诉你他们将使用均方根对数加 1 误差,因为 log(0)没有意义。
%time add_datepart(df_all, 'date') ''' CPU times: user 1min 35s, sys: 16.1 s, total: 1min 51s Wall time: 1min 53s '''
我们可以像往常一样添加日期部分。这需要几分钟,所以我们应该先在样本上运行所有这些,以确保它有效。一旦你知道一切都是合理的,然后回去在整个集合上运行。
n_valid = len(df_test) n_trn = len(df_all) - n_valid train, valid = split_vals(df_all, n_trn) train.shape, valid.shape ''' ((122126576, 18), (3370464, 18)) '''
这些代码行与我们在推土机比赛中看到的代码行是相同的。我们不需要运行train_cats
或apply_cats
,因为所有的数据类型已经是数字的了(记住apply_cats
将相同的分类代码应用于验证集和训练集)。
%%time trn, y, nas = proc_df(train, 'unit_sales') val, y_val, nas = proc_df(valid, 'unit_sales', nas)
调用proc_df
来检查缺失值等。
def rmse(x,y): return math.sqrt(((x-y)**2).mean()) def print_score(m): res = [ rmse(m.predict(X_train), y_train), rmse(m.predict(X_valid), y_valid), m.score(X_train, y_train), m.score(X_valid, y_valid) ] if hasattr(m, 'oob_score_'): res.append(m.oob_score_) print(res)
这些代码行再次是相同的。然后有两个变化:
set_rf_samples(1_000_000) %time x = np.array(trn, dtype=np.float32) ''' CPU times: user 1min 17s, sys: 18.9 s, total: 1min 36s Wall time: 1min 37s ''' m = RandomForestRegressor( n_estimators=20, min_samples_leaf=100, n_jobs=8 ) %time m.fit(x, y)
我们上周学习了set_rf_samples
。我们可能不想从 1.25 亿条记录中创建一棵树(不确定需要多长时间)。你可以从 10k 或 100k 开始,然后找出你可以运行多少。数据集的大小与构建随机森林所需时间之间没有关系,关系在于估计器数量乘以样本大小。
问题: n_job
是什么?过去,它总是-1
[29:42]。作业数是要使用的核心数。我在一台大约有 60 个核心的计算机上运行这个,如果你尝试使用所有核心,它会花费很多时间来启动作业,速度会变慢。如果你的计算机有很多核心,有时你想要更少(-1
表示使用每个核心)。
另一个变化是x = np.array(trn, dtype=np.float32)
。这将数据框转换为浮点数组,然后我们在其上进行拟合。在随机森林代码内部,他们无论如何都会这样做。鉴于我们想要运行几个不同的随机森林,使用几种不同的超参数,自己做一次可以节省 1 分 37 秒。
分析器:%prun
[30:40]
如果你运行一行需要很长时间的代码,你可以在前面加上%prun
。
%prun m.fit(x, y)
- 这将运行一个分析器,并告诉你哪些代码行花费了最多的时间。这里是 scikit-learn 中将数据框转换为 numpy 数组的代码行。
- 查看哪些事情占用了时间被称为“分析”,在软件工程中是最重要的工具之一。但数据科学家往往低估了它。
- 有趣的是,尝试不时运行
%prun
在需要 10-20 秒的代码上,看看你是否能学会解释和使用分析器输出。 - Jeremy 在分析器中注意到的另一件事是,当我们使用
set_rf_samples
时,我们不能使用 OOB 分数,因为如果这样做,它将使用其他 124 百万行来计算 OOB 分数。此外,我们希望使用最近日期的验证集,而不是随机的。
print_score(m) ''' [0.7726754289860, 0.7658818632043, 0.23234198105350, 0.2193243264] '''
所以这让我们得到了 0.76 的验证均方根对数误差。
m = RandomForestRegressor( n_estimators=20, min_samples_leaf=10, n_jobs=8 ) %time m.fit(x, y)
这使我们降到了 0.71,尽管花了更长的时间。
m = RandomForestRegressor( n_estimators=20, min_samples_leaf=3, n_jobs=8 ) %time m.fit(x, y)
这将错误降低到 0.70。min_samples_leaf=1
并没有真正帮助。所以我们在这里有一个“合理”的随机森林。但是这在排行榜上并没有取得好的结果[33:42]。为什么?让我们回头看看数据:
这些是我们必须用来预测的列(以及由add_datepart
添加的内容)。关于明天预计销售多少的大部分见解可能都包含在关于商店位置、商店通常销售的物品种类以及给定物品的类别是什么的细节中。随机森林除了在诸如星期几、商店编号、物品编号等方面创建二元分割之外,没有其他能力。它不知道物品类型或商店位置。由于它理解正在发生的事情的能力有限,我们可能需要使用整整 4 年的数据才能得到一些有用的见解。但是一旦我们开始使用整整 4 年的数据,我们使用的数据中有很多是非常陈旧的。有一个 Kaggle 内核指出,你可以做的是[35:54]:
- 看看最后两周。
- 按商店编号、物品编号、促销情况的平均销售额,然后跨日期取平均。
- 只需提交,你就能排在第 30 名左右🎉
我们将在下一堂课上讨论这个问题,但如果你能找出如何从那个模型开始并使其变得更好一点,你将排在第 30 名以上。
问题:您能否尝试通过创建新列来捕捉季节性和趋势效应,比如 8 月份的平均销售额?这是一个很好的主意。要解决的问题是如何做到这一点,因为有一些细节需要正确,这些细节很困难-不是在智力上困难,而是以一种让你在凌晨 2 点撞头的方式困难。
为机器学习编码是非常令人沮丧和非常困难的。如果你弄错了一个细节,很多时候它不会给你一个异常,它只会默默地比原来稍微差一点。如果你在 Kaggle 上,你会知道你的表现不如其他人。但否则,你没有什么可以比较的。你不会知道你的公司模型是否只有它可能的一半好,因为你犯了一个小错误。这就是为什么现在在 Kaggle 上练习是很好的。
你将练习找到所有可能令人恼火地搞砸事情的方法,你会感到惊讶。
即使对于 Jeremy 来说,这些验证集也是非常丰富的。当你开始了解它们是什么时,你将开始知道如何在进行时检查它们。你应该假设你按下的每个按钮都会按错按钮。只要你有一种找出来的方法就可以。
不幸的是,没有一套你应该总是做的具体事情,你只需要考虑一下我即将做的事情的结果。这里有一个非常简单的例子。如果你创建了一个基本的条目,其中你按日期、店铺编号、促销状态取平均值,然后提交了它,并得到了一个合理的分数。然后你认为你有一些稍微好一点的东西,你为此做了预测。你可以创建一个散点图,显示你的平均模型预测在一个轴上,与你的新模型预测在另一个轴上。你应该看到它们几乎形成一条直线。如果不是,那么这非常明显地表明你搞砸了什么。
问题:您多久从其他来源获取数据来补充您已有的数据集?非常频繁。星型模式的整个重点是你有一个中心表,你有其他表与之相连,提供关于它的元数据。在 Kaggle 上,大多数比赛的规则是你可以使用外部数据,只要在论坛上发布并且是公开可用的(双重检查规则!)。在 Kaggle 之外,你应该始终寻找可能利用的外部数据。
问题:如何添加厄瓜多尔的假期来补充数据?这个信息实际上是提供的。一种解决这种问题的一般方法是创建许多新列,其中包含假期销售平均数量,一月和二月之间销售平均百分比变化等。在德国的一个杂货连锁店曾经有一场先前的比赛,几乎是一样的。获胜者是一个领域专家,擅长做物流预测。他根据自己的经验创建了许多列,这些列通常对于做预测是有用的。所以这是一个可以奏效的方法。然而,第三名获奖者几乎没有进行特征工程,而且他们也有一个大的疏忽,这可能导致他们失去第一名。随着比赛的进行,我们将学到更多关于如何赢得这场比赛以及类似比赛的知识。
好的验证集的重要性
如果你没有一个好的验证集,要创建一个好的模型是困难的,甚至是不可能的。如果你试图预测下个月的销售额,并建立模型。如果你无法知道你建立的模型是否擅长提前一个月预测销售额,那么当你将模型投入生产或在测试集上使用时,你就无法知道它是否真的会很好。你需要一个可靠的验证集,告诉你你的模型是否有可能在投入生产或在测试集上使用时表现良好。
通常情况下,你不应该对测试集做任何其他操作,除非在比赛结束时或项目结束时使用它来查看你的表现。但是有一件事你可以在测试集中使用 —— 那就是校准你的验证集[46:02]。
Terrance 在这里做的是建立了四种不同的模型,并将这四种模型分别提交到 Kaggle 上,以找出它们的得分。X 轴是 Kaggle 在排行榜上告诉我们的得分,y 轴是他在一个特定的验证集上绘制的得分,他试图看看这个验证集是否会很好。如果你的验证集很好,那么排行榜得分(即测试集得分)之间的关系应该是一条直线。理想情况下,它将位于y = x
线上,但老实说,这并不太重要,只要相对来说告诉你哪些模型比哪些模型更好,那么你就知道哪个模型是最好的。在这种情况下,Terrance 设法找到了一个看起来能够很好地预测 Kaggle 排行榜得分的验证集。这真的很酷,因为他可以尝试一百种不同类型的模型、特征工程、加权、调整、超参数等等,看看它们在验证集上的表现,而不必提交到 Kaggle。因此,你将得到更多的迭代,更多的反馈。这不仅适用于 Kaggle,而且适用于你做的每一个机器学习项目。一般来说,如果你的验证集没有显示出良好的拟合线,你需要仔细思考[48:02]。测试集是如何构建的?我的验证集有什么不同?你将不得不绘制很多图表等等来找出。
**问题:**如何构建一个与测试集尽可能接近的验证集[48:23]?以下是 Terrance 的一些建议:
- 日期接近(即最近的)
- 首先看一下测试集的日期范围(16 天),然后看一下描述如何在排行榜上获得 0.58 分的内核的日期范围(14 天)。
- 测试集从发薪日的第二天开始,到下一个发薪日结束。
- 绘制很多图片。即使你不知道今天是发薪日,你也想绘制时间序列图,希望看到每两周有一个高峰,并确保验证集中有与测试集相同数量的高峰。
解释机器学习模型[50:38 / 笔记本]
PATH = "data/bulldozers/" df_raw = pd.read_feather('tmp/raw') df_trn, y_trn, nas = proc_df(df_raw, 'SalePrice')
我们首先读取蓝色书籍对推土机比赛的 feather 文件。提醒:我们已经读取了 CSV 文件,将其处理为类别,并保存为 feather 格式。接下来我们调用proc_df
将类别转换为整数,处理缺失值,并提取出因变量。然后创建一个像上周一样的验证集:
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)
绕道到第 1 课笔记本[51:59]
上周,在proc_df
中有一个 bug,当传入subset
时会打乱数据框,导致验证集不是最新的 12000 条记录。这个问题已经修复。
## From lesson1-rf.ipynb df_trn, y_trn, nas = proc_df( df_raw, 'SalePrice', subset=30000, na_dict=nas ) X_train, _ = split_vals(df_trn, 20000) y_train, _ = split_vals(y_trn, 20000)
问题:为什么nas
既是该函数的输入又是输出[53:03]?proc_df
返回一个告诉您哪些列丢失以及每个丢失列的中位数的字典。
- 当您在较大的数据集上调用
proc_df
时,不需要传入nas
,但您希望保留该返回值。 - 稍后,当您想要创建一个子集(通过传入
subset
)时,您希望使用相同的丢失列和中位数,因此您传入nas
。 - 如果发现子集来自完全不同的数据集并且具有不同的丢失列,它将使用附加键值更新字典。
- 它会跟踪您在传递给
proc_df
的任何内容中遇到的任何丢失列。
fast.ai 机器学习笔记(一)(4)https://developer.aliyun.com/article/1482641