特征工程对于模型的执行非常重要,即使是具有强大功能的简单模型也可以胜过复杂的算法。实际上,特征工程被认为是决定预测模型成功或失败的最重要因素。特征工程真正归结为机器学习中的人为因素。通过人类的直觉和创造力,您对数据的了解程度可以带来不同。
那么什么是特征工程?对于不同的问题,它可能意味着许多事情,但在泰坦尼克号的竞争中,它可能意味着砍伐,并结合我们在Kaggle的优秀人员给予的不同属性来从中榨取更多的价值。通常,机器学习算法可以更容易地从工程学习算法中消化和制定规则,而不是从其导出的变量。
获得更多机器学习魔力的最初嫌疑人是我们上次从未发送到决策树的三个文本字段。票号,舱位和名称都是每位乘客独有的; 也许可以提取这些文本字符串的一部分以构建新的预测属性。让我们从名称字段开始。如果我们看一下第一位乘客的名字,我们会看到以下内容:
> train$Name[1] [1] Braund, Mr. Owen Harris 891 Levels: Abbing, Mr. Anthony Abbott, Mr. Rossmore Edward ... Zimmerman, Mr. Leo
以前我们只通过子集化访问乘客组,现在我们通过使用行号1作为索引来访问个人。好吧,船上没有其他人有这个名字,这几乎可以肯定,但他们还有什么共享?好吧,我确信船上有很多先生。也许人物头衔可能会给我们更多的洞察力。
如果我们滚动数据集,我们会看到更多的标题,包括Miss,Mrs,Master,甚至是Countess!标题“大师”现在有点过时,但在这些日子里,它被保留给未婚男孩。此外,像我们伯爵夫人这样的贵族也可能对低级无产阶级采取不同的行动。在这方面似乎有很少的模式可能性比我们之前看过的年龄,性别等组合更深入。
为了提取这些标题以创建新变量,我们需要在训练集和测试集上执行相同的操作,以便这些功能可用于增长我们的决策树,并对看不见的测试数据进行预测。在两个数据集上同时执行相同过程的简单方法是合并它们。在R中我们可以使用rbind,它代表行绑定,只要两个数据帧具有彼此相同的列。由于我们在测试集中显然缺少Survived列,让我们创建一个完整的缺失值(NAs),然后将两个数据集行绑定在一起:
> test$Survived <- NA > combi <- rbind(train, test)
现在我们有了一个名为“combi”的新数据框,其中包含与原始两个数据集完全相同的行,按照我们指定的顺序堆叠:先训练,然后测试第二。
如果你回顾一下我们对Owen的调查结果,他的名字仍然被编码为一个因素。正如我们在教程系列前面提到的那样,字符串会自动导入R中的因子,即使它没有意义。所以我们需要将此列转换回文本字符串。要做到这一点,我们使用as.character。让我们这样做,然后再看看欧文:
> combi$Name <- as.character(combi$Name) > combi$Name[1] [1] "Braund, Mr. Owen Harris"
为了分解字符串,我们需要一些钩子来告诉程序要查找。很好,我们看到人名后面有一个逗号,并且在他们的头衔之后有一个句号。我们可以很容易地使用函数strsplit(代表字符串拆分)来区分这两个符号的原始名称。让我们试试布朗德先生:
> strsplit(combi$Name[1], split='[,.]') [[1]] [1] "Braund" " Mr" " Owen Harris"
好的。在这里,我们发送strsplit了感兴趣的单元格,并在分割字符串时为其选择了一些符号,可以是逗号或句点。方括号中的那些符号称为正则表达式,虽然这是一个非常简单的符号,如果您打算使用大量文本,我肯定会建议习惯使用它们!
我们看到标题已经单独打破了,虽然在它开始之前有一个奇怪的空间,因为逗号发生在姓氏的末尾。但是,我们如何获得这个标题并清除其他我们不想要的东西呢?[[1]]在文本部分之前打印索引。让我们尝试通过将所有方括号附加到原始命令来深入研究这种新类型的容器:
> strsplit(combi$Name[1], split='[,.]')[[1]] [1] "Braund" " Mr" " Owen Harris"
字符串拆分使用双重堆叠矩阵,因为它永远不能确定给定的正则表达式将具有相同数量的块。如果名称中有更多逗号或句点,则会创建更多段,因此它会将它们隐藏得更深,以维护我们习惯使用的矩形类型的容器,例如电子表格或现在的数据帧!让我们深入了解索引混乱并提取标题。这是这个嵌套列表中的第二个项目,所以让我们深入研究这个新容器的索引号2
> strsplit(combi$Name[1], split='[,.]')[[1]][2] [1] " Mr"
由于我们不得不深入研究这个容器以获得标题,只需尝试combi$Title <- strsplit(combi$Name, split='[,.]')[[1]][2]遍历整个名称向量就会导致我们所有的行都具有相同的Mr.,所以我们需要更加努力。不出所料,将函数应用于数据帧或向量中的大量单元格会使用applyR的函数套件:
> combi$Title <- sapply(combi$Name, FUN=function(x) {strsplit(x, split='[,.]')[[1]][2]})
R的应用功能都以稍微不同的方式sapply工作,但在这里工作得很好。我们提供sapply了我们刚刚提出的名称向量和函数。它遍历名称向量的行,并将每个名称发送到函数。所有这些字符串拆分的结果都被组合成一个向量作为sapply函数的输出,然后我们将其存储到原始数据帧中的一个新列,称为Title。
最后,我们可能希望从标题的开头剥离这些空格。在这里,我们可以用任何东西替换第一次出现的空格。我们可以使用sub这个:
> combi$Title <- sub(' ', '', combi$Title)
我们现在有一个很好的新标题列:
> table(combi$Title) Capt Col Don Dona Dr Jonkheer Lady 1 4 1 1 8 1 1 Major Master Miss Mlle Mme Mr Mrs 2 61 260 2 1 757 197 Ms Rev Sir the Countess 2 8 1 1
嗯,这里有一些非常罕见的标题,不会给我们的模型很多,所以让我们结合一些最不寻常的。让我们将它们组合成一个类别:
> combi$Title[combi$Title %in% c('Mme', 'Mlle')] <- 'Mlle'
我们在这做了什么?该%in%运营商检查是否值是我们比较它与载体的一部分。所以在这里我们将两个标题“Mme”和“Mlle”组合成一个新的临时向量,使用c()运算符并查看整个Title列中的任何现有标题是否与它们中的任何一个匹配。然后我们用“Mlle”替换任何一场比赛。
我们一直在寻找冗余。对于我们这里的集合来说,非常富有似乎是一个问题。对于这些男人来说,我们有一些只有一两个被祝福的头衔:船长,少校和先生。所有这些都是军事头衔,或者是出生时拥有大片土地的富裕家伙。
对于女士们,我们有Dona,Lady,Jonkheer(*见下面的评论),当然还有我们的伯爵夫人。所有这些人都是富人,由于他们的高贵出生,他们的行为可能有些相似。让我们将这两个组合在一起,并将因子级别的数量减少到决策树可能理解的范围:
< combi$Title[combi$Title %in% c('Dona', 'Lady', 'the Countess', 'Jonkheer')] <- 'Lady'
我们的最后一步是将变量类型更改回一个因子,因为这些基本上是我们创建的类别:
> combi$Title <- factor(combi$Title)
好的。我们现在已经完成了乘客的头衔。我们还能想到什么呢?那么,有两个变量SibSb和Parch表明乘客随行的家庭成员人数。似乎有理由认为一个大家庭可能无法追踪小约翰尼,因为他们都争先恐后地下沉沉船,所以让我们将这两个变量合并为一个新的,FamilySize:
> combi$FamilySize <- combi$SibSp + combi$Parch + 1
很简单!我们只是添加乘客与他们在一起的兄弟姐妹,配偶,父母和孩子的数量,当然还有一个用于他们自己的存在,并且有一个新的变量表明他们旅行的家庭的大小。
更多的东西?好吧,我们只是想到一个大家庭一起遇到救生艇的问题,但也许特定的家庭比其他家庭更麻烦?我们可以尝试提取乘客的姓氏并将他们分组以寻找家人,但像约翰逊这样的常见姓氏可能会在船上增加一些非相关人员。事实上,在一个3岁的家庭中有三个约翰逊,另外三个可能无关的约翰逊都是独自旅行。
将姓氏与家庭大小相结合可以解决这个问题。没有两个家族 - 约翰逊应该在如此小的船上拥有相同的FamilySize变量。让我们首先提取乘客的姓氏。这应该是我们之前运行的标题提取代码的一个非常简单的变化,现在我们只想要strsplit输出的第一部分:
> combi$Surname <- sapply(combi$Name, FUN=function(x) {strsplit(x, split='[,.]')[[1]][1]})
然后我们想要将FamilySize变量附加到它的前面,但正如我们所看到的那样,字符串操作需要字符串。因此,让我们将FamilySize变量临时转换为字符串,并将其与Surname结合使用以获取新的FamilyID变量:
combi$FamilyID <- paste(as.character(combi$FamilySize), combi$Surname, sep="")
我们使用该函数paste将两个字符串组合在一起,并告诉它通过sep参数将它们分开。这被存储到一个名为FamilyID的新列中。但是那三个单身的约翰逊人都拥有相同的家庭ID。鉴于我们最初假设大家庭可能难以在恐慌中坚持到一起,让我们将任何两个或更少的家庭大小淘汰,称之为“小”家庭。这也将解决约翰逊问题。
> combi$FamilyID[combi$FamilySize <= 2] <- 'Small'
让我们看看我们如何识别这些家庭群体:
> table(combi$FamilyID) 11Sage 3Abbott 3Appleton 3Beckwith 3Boulos 11 3 1 2 3 3Bourke 3Brown 3Caldwell 3Christy 3Collyer 3 4 3 2 3 3Compton 3Cornell 3Coutts 3Crosby 3Danbom 3 1 3 3 3 . . .
嗯,有几个似乎已经从这里的裂缝中滑落。有很多FamilyID只有一两个成员,即使我们只想要3或更多的家庭成员。也许有些家庭有不同的姓氏,但无论如何,所有这些一两个人群体都是我们试图避免的三个人的截止。让我们开始清理它:
> famIDs <- data.frame(table(combi$FamilyID))
现在我们将上面的表存储到数据帧中。是的,如果您愿意,可以将大多数表存储到数据框中,所以让我们通过在资源管理器中单击它来查看它:
在这里,我们再次看到所有那些与我们的假设不能很好地合作的顽皮家庭,所以让我们将这个数据框的子集只显示那些意外小的FamilyID组。
famIDs <- famIDs[famIDs$Freq <= 2,]
然后,我们需要在数据集中覆盖未正确识别的组中的任何族ID,并最终将其转换为因子:
我们现在准备将测试和训练集分解回原始状态,用它们带来我们新奇的工程变量。我们刚刚做的最好的部分是如何在R中处理因子。在幕后,因子基本上存储为整数,但是用它们的文本名称掩盖以供我们查看。如果在单独的测试和训练集上创建上述因子,则无法保证两组中都存在两个组。
例如,先前讨论的“3Johnson”族在测试集中不存在。我们知道他们三个都从训练集数据中幸存下来。如果我们孤立地建立了我们的因素,那么测试集就没有因素“3Johnson”。这会扰乱任何机器学习模型,因为用于构建模型的训练集与要求它预测的测试集之间的因素不一致。即。如果你尝试,R会向你抛出错误。
因为我们在单个数据帧上构建了因子,然后在构建它们之后将它们拆分,R将为所有新数据帧提供所有因子级别,即使该因子不存在于一个数据帧中也是如此。它仍然具有因子水平,但在集合中没有实际观察。整洁的把戏对吗?我向您保证,手动更新因子水平是一件痛苦的事。
因此,让我们将它们分开并对我们新的花哨工程变量做一些预测:
这里我们介绍R中的另一种子集方法; 有很多取决于您希望如何切割数据。我们已根据原始列车和测试集的大小隔离了组合数据集的某些行范围。之后的逗号后面没有数字表示我们想要使用此子集获取所有列并将其存储到指定的数据帧。这为我们提供了原始行数,以及所有新变量,包括一致的因子水平。
是时候做我们的预测了!我们有一堆新变量,所以让我们将它们发送到一个新的决策树。上次默认的复杂性非常好,所以让我们用香草控件生成一棵树,看看它能做什么:
有趣的是,我们的新变量基本上管理着我们的树。这是我上次没有提到的决策树的另一个缺点:它们偏向于支持多层次的因素。看看我们的61级FamilyID因素在这里是如此突出,并且树挑出了所有比其他家庭更偏向的家庭。这样,决策节点可以将数据切割并改变为以下节点的纯度的最佳可能组合。
但除此之外,您应该知道如何从决策树创建提交,所以让我们看看它是如何执行的!
通过从我们已经拥有的东西中榨取更多的价值。这只是您可以在此数据集中找到的示例。
继续尝试创建更多工程变量!和以前一样,我也非常鼓励你玩复杂性参数,也许可以尝试修剪一些更深的树,看它是否有助于或阻碍你的等级。您甚至可以考虑从树中排除一些变量,看看它是否也发生了变化。
但在大多数情况下,由于决策树的贪婪性,标题或性别变量将决定第一个决策。对于多层次因素的偏见也不会消失,如果没有实际提交意见书,过度拟合问题很难衡量,但良好的判断力可能会有所帮助。
有问题欢迎下方留言!