首页 > 技术文章 > kaggle数据挖掘竞赛初步--Titanic<派生属性&维归约>

north-north 2015-03-22 21:34 原文

完整代码: https://github.com/cindycindyhi/kaggle-Titanic

特征工程系列:

Titanic系列之原始数据分析和数据处理

Titanic系列之数据变换

Titanic系列之派生属性&维归约

为什么有的机器学习项目成功了有的却失败了呢?毕竟算法是有限的改进也是有限的,最主要的因素就是特征的选择了。如果我们有一些与类别非常相关同时又相互独立的特征,学习起来是很容易的,相反就不一定了。通常情况下,并不是直接把原始数据作为特征,而是从中构建一些特征。这是机器学习中的主要工作。在这一步骤中,通常直觉、创造性、魔法和技术一样重要。

当然,机器学习的一个终极目标就是将特征工程过程越来越多地自动化。现在经常采用的一种方式是先自动产生大量的候选特征,然后根据它们与分类类别的信息增益等方法来选取最好的特征。但是,运行包含大量特征的学习器来寻找有用的特征组合太耗时,也容易导致过拟合。还是需要人为的介入特征的选择中。

什么是派生属性呢?派生属性就是从原始数据中得到的一些属性,比如上一节从Age属性经过Factorize得到的Age_bin属性就是一个派生属性,当然这种派生只是非常简单的派生。为什么要对这些属性做各种各样的统计和处理呢,这其实是特征工程的一部分,先构建足够多可能会对结果有意义的属性,然后再从这些候选集中选择我们想要的特征。特征工程非常繁琐,但是对数据挖掘非常重要,一般来说,做一个数据挖掘项目,百分之八十的努力要用在特征工程上。除了基本的转换和interaction属性,我们也要创造性的从原始属性中发现新属性。比如电话号码,可以从中提取出来国家和区域特征。

一 研究业务逻辑提取特征

Titanic的数据集相对较为简单,但是对于一些字符串类型的属性,比如Name我们可以从中提取出来一些可以揭示其社会地位的称号。

名字的长度也会代表一个人的社会地位,社会地位高的人可能会更容易得到救生船。

1 df['Names'] = df['Name'].map(lambda x: len(re.split(' ',x)))

对于名字中间的爵位,可以看到称号有Mr Mrs Master等,经过统计可以看到有以下几种称号:

这些称号有法语还有英语,需要依据当时的文化环境将其归类,如何将其归类可以参考这篇文章trevorstephens.com/post/73461351896/titanic-getting-started-with-r-part-4-feature

1     df['Title'] = df['Name'].map(lambda x: re.compile(",(.*?)\.").findall(x)[0])
2     df['Title'][df.Title=='Jonkheer'] = 'Master'
3     df['Title'][df.Title.isin(['Ms','Mlle'])] = 'Miss'
4     df['Title'][df.Title == 'Mme'] = 'Mrs'
5     df['Title'][df.Title.isin(['Capt', 'Don', 'Major', 'Col', 'Sir'])] = 'Sir'
6     df['Title'][df.Title.isin(['Dona', 'Lady', 'the Countess'])] = 'Lady'
7     df['Title_id'] = pd.factorize(df.Title)[0]+1

对于Ticket属性也需要处理,可以看到Ticket字段有的全是数字有的是字母和数字的集合,进一步对数据分析发现约25%的数据有前缀,前缀共有45种,如果把 . 和 / 去掉的话还剩29种,数字部分也有一定的规律:以1开头的一般是一等舱2开头的是二等舱3开头的是三等舱,4-9开头的大都是三等舱。以上这些数据告诉我们处理Ticket是有意义的,能够发现其内部蕴涵的信息。

 1 def processTicket():
 2     global df
 3     df['TicketPrefix'] = df['Ticket'].map(lambda x: getTicketPrefix(x.upper()))
 4     df['TicketPrefix'] = df['TicketPrefix'].map(lambda x: re.sub\
 5                             ('[\.?\/?]','',x))
 6     df['TicketPrefix'] = df['TicketPrefix'].map(lambda x:re.sub\
 7                                                 ('STON','SOTON',x))
 8     df['TicketPrefix'] = pd.factorize(df['TicketPrefix'])[0]
 9     df['TicketNumber'] = df['Ticket'].map(lambda x: getTicketNumber(x) )
10     df['TicketNumberLength'] = df['TicketNumber'].map(lambda x: len(x)).\
11                                astype(int)
12     df['TicketNumberStart'] = df['TicketNumber'].map(lambda x: x[0:1]).\
13                               astype(int)
14     df['TicketNumber'] = df['TicketNumber'].astype(int)
15 def getTicketPrefix(ticket):
16     match = re.compile("([a-zA-Z\.\/]+)").search(ticket)
17     if match:
18         return match.group()
19     else:
20         return 'U'
21 def getTicketNumber(ticket):
22     match = re.compile("([0-9]+$)").search(ticket)
23     if match:
24         return match.group()
25     else:
26         return '0'

二 简单组合属性提取特征

一些属性可以从它本身的数据里提取一些信息,有些属性则需要和其他属性组合来产生信息。比如对淘宝上的一个商品来说,购买数/点击率可以反应商品的转化率,也是商品的一个非常重要的特征。

对于Titanic来说,我们用Age*Pclass组合产生一个属性,虽然没有一个名词来解释它,但是从结果数据上来看,我们增大了年纪大的人的权重也提高了高等舱的权重,从最后幸存的结果上看,这个组合还是有意义的。除了这两个属性之外,我们还可以对其他数值属性进行数学运算,以得到更大的候选特征集。

 1     numerics = df.loc[:, ['Age_scaled', 'Fare_scaled', 'Pclass_scaled', 'Parch_scaled', 'SibSp_scaled', 
 2                           'Names_scaled', 'CabinNumber_scaled', 'Age_bin_id_scaled', 'Fare_bin_id_scaled']]
 3     print "\nFeatures used for automated feature generation:\n", numerics.head(10)
 4     
 5     new_fields_count = 0
 6     for i in range(0, numerics.columns.size-1):
 7         for j in range(0, numerics.columns.size-1):
 8             if i <= j:
 9                 name = str(numerics.columns.values[i]) + "*" + str(numerics.columns.values[j])
10                 df = pd.concat([df, pd.Series(numerics.iloc[:,i] * numerics.iloc[:,j], name=name)], axis=1)
11                 new_fields_count += 1
12             if i < j:
13                 name = str(numerics.columns.values[i]) + "+" + str(numerics.columns.values[j])
14                 df = pd.concat([df, pd.Series(numerics.iloc[:,i] + numerics.iloc[:,j], name=name)], axis=1)
15                 new_fields_count += 1
16             if not i == j:
17                 name = str(numerics.columns.values[i]) + "/" + str(numerics.columns.values[j])
18                 df = pd.concat([df, pd.Series(numerics.iloc[:,i] / numerics.iloc[:,j], name=name)], axis=1)
19                 name = str(numerics.columns.values[i]) + "-" + str(numerics.columns.values[j])
20                 df = pd.concat([df, pd.Series(numerics.iloc[:,i] - numerics.iloc[:,j], name=name)], axis=1)
21                 new_fields_count += 2
22       
23     print "\n", new_fields_count, "new features generated"

这个过程自动产生大量的特征,这里用了9个特征产生了176个特征,可能这些特征有些过于多了,但是它只是一个候选集,我们可以通过一些处理筛选掉一些特征。当然有些模型也很适合大量特征的训练集,比如随机森林(有论文验证,随机森林是分类算法中表现最好的模型)。

产生的这些特征可能高度相关于原始特征,线性模型处理这类特征时会产生multicollinearity问题,可以用计算这些特征的皮尔逊相关系数,筛选相关性特征。如果用随机森林模型训练的话,可以不需要这个步骤。

三 用PCA进行维归约

通过上面三个部分的处理,我们得到了具有大量特征的维度很高的数据集,特征较多不能直接用来作为模型输入,一是因为这些特征间具有多重共线性,可能 会导致空间的不稳定;二是因为高维空间本身具有稀疏性,一维正态分布有68%的值落于正负标准差之间,而在十维空间上只有0.02%;三是由于过多的属性 会使挖掘需要很长时间。对于一些模型来说,比如使用L1(Lasso),当有大量属性时效果很好,因为它可以有效忽略掉噪声变量。而一些模型则容易过拟 合。

数据归约技术可以用来得到数据集的规约表示,它小得多,但仍接近于保持原始数据的完整性。也就是说,在归约后的数据集上进行数据挖掘将更加有效,仍然产生几乎相同的数据分析结果。

PCA(主成份分析)是一种维归约的方法,它搜索k个最能代表数据的n维正交向量,将原始数据投影到一个小的多的空间上,导致维归约。PCA通过创建一个替换的较小的变量集组合属性的基本要素。具体原理及python的实现过程可以参考这篇blog Implementing a Principal Component Analysis (PCA) in Python step by step

我们可以直接使用scikit-learn的PCA函数进行维规约

1     X = df.values[:, 1::]
2     y = df.values[:, 0]   
3     variance_pct = .99
4     # Create PCA object
5     pca = PCA(n_components=variance_pct)    
6     # Transform the initial features
7     X_transformed = pca.fit_transform(X,y)    
8     # Create a data frame from the PCA'd data
9     pcaDataFrame = pd.DataFrame(X_transformed)

实验发现,PCA对于线性模型(不使用Lasso)非常有效,但是对于随机森林模型没有提高。

推荐阅读