如何使用Python在Kaggle竞赛中成为Top15

如何使用Python在Kaggle竞赛中成为Top15

Kaggle比赛是一个学习数据科学和投资时间的非常的方式,我自己通过Kaggle学习到了很多数据科学的概念和思想,在我学习编程之后的几个月就开始了Kaggle比赛,最近还赢得了几个比赛。
要在Kaggle比赛中取得好成绩不仅仅是要求知道一些机器学习算法,而且要有一个准确的思维模式,好学,花大量的时间探索数据。虽然,在很多方面通常都不强调在开始Kaggle比赛的时候使用教程(tutorials),但是在这里,我将告诉大家如何开始 Kaggle Expedia hotel recommendations competition这个比赛,包括建立准确的思维模式,设置测试基础,探索数据,构造新特征,做预测。
最后,我们会使用这篇文章提到的技术生成一个提交文件。(submission:Kaggle竞赛的标准提交文件一般是submission.csv)提交之后排名将会top15。

Expedia Kaggle比赛简介

Expedia比赛的挑战是你基于在Expedia提供的用户的搜索数据中的一些属性来预测他们会预定哪一个酒店。在我们编程之前需要花时间先理解问题和数据。

快速查看

第一步就是看一下数据集的列描述,点这里,你可以看到每一列的数据的描述,通过浏览,我们可以了解到一些Expedia的用户是搜索信息,和数据一起提供的test.csv/train.csv包含有顾客最终预定了哪个酒店的聚合信息。destinations.csv包括了地区用户搜索酒店的信息。 我们现在仍不担心我们要预测什么,只需要专注的理解数据列的信息。

Expedia

因为比赛是由用户在Expedia预定酒店的数据组成,所以我们需要花些时间来Expedia网站。浏览预定流程可以帮助我们置身于数据描述的情景中更好的理解Expedia数据。
网站的Going To对应数据中的srch_destination_type_idhotel_continenthotel_countryhotel_market
网站的Check-in对应数据中的srch_ciCheck-out对应数据中的srch_co
网站的Guests对应数据中的srch_adults_cntsrch_children_cntsrch_rm_cnt
网站的Add a Flight对应数据中的is_package
site_name是你访问的网站名字,是Expedia.com或者其他。
user_location_country,user_location_region,user_location_city,is_mobile,channel,is_booking,cnt是由用户在哪里、他们的设备是什么或者他们在Expedia网站的session(网站服务器记录用户的会话密匙)决定的属性,就在一个页屏幕上我们能立即看到所有的变量。浏览屏幕,填充数据,体验整个预定流程可以帮我们更好的置身于情景中。

用Python探索数据

现在我们将用一个高级的方式来处理数据,可以更深层次的探索数据。你可以在Kaggle下载数据。数据集有点大,所以你需要一个较大的磁盘空间(训练数据达到了3.975GB,普通的编辑器无法打开),你需要将.gz文件解压成.csv

用pandas来处理数据

给你的系统腾出大量的内存,不然有可能无法将所有数据都读进内存(内存足够大的忽略~内存8G无法读取)。如果无法做到,你可以考虑用EC2云主机或者DigitalOcean来处理数据。这是用云主机来处理的教程~
一旦我们下载完数据,我们就可以用pandas将其读取:

import pandas

destinations = pd.read_csv("destinations.csv")
test = pd.read_csv("test.csv")
train = pd.read_csv("train.csv")

观察各个数据集的大小:

in[1]:train.shape
out[1]:(37670293, 24)
in[2]:test.shape
out[2]:(2528243, 22)

我们大概有3700千万行训练数据和200万行的测试数据,这会使这个问题的工作有一点挑战。我们可以探索数据的开始几行:

in[1]:train.head(5)
out[1]:
date_time	site_name	posa_continent	user_location_country	user_location_region	user_location_city	orig_destination_distance	user_id	is_mobile	is_package	...	srch_children_cnt	srch_rm_cnt	srch_destination_id	srch_destination_type_id	is_booking	cnt	hotel_continent	hotel_country	hotel_market	hotel_cluster
0	2014-08-11 07:46:59	2	3	66	348	48862	2234.2641	12	0	1	...	0	1	8250	1	0	3	2	50	628	1
1	2014-08-11 08:22:12	2	3	66	348	48862	2234.2641	12	0	1	...	0	1	8250	1	1	1	2	50	628	1
2	2014-08-11 08:24:33	2	3	66	348	48862	2234.2641	12	0	0	...	0	1	8250	1	0	1	2	50	628	1
3	2014-08-09 18:05:16	2	3	66	442	35390	913.1932	93	0	0	...	0	1	14984	1	0	1	2	50	1457	80
4	2014-08-09 18:08:18	2	3	66	442	35390	913.6259	93	0	0	...	0	1	14984	1	0	1	2	50	1457	21

我们很快就能发现几个事情:

  • data_time应该对我们的预测很有用,所以我们需要转换它。
  • 大多数列数值都是整型和浮点型,所以我们不能做太多的特征工程。例如,user_location_country不是国家的名字,是一个整型数值。这使构造新特征变得困难,因为我们不知道那个数值真正代表的是什么意思。
in[1]:test.head(5)
out[1]:
id	date_time	site_name	posa_continent	user_location_country	user_location_region	user_location_city	orig_destination_distance	user_id	is_mobile	...	srch_ci	srch_co	srch_adults_cnt	srch_children_cnt	srch_rm_cnt	srch_destination_id	srch_destination_type_id	hotel_continent	hotel_country	hotel_market
0	0	2015-09-03 17:09:54	2	3	66	174	37449	5539.0567	1	1	...	2016-05-19	2016-05-23	2	0	1	12243	6	6	204	27
1	1	2015-09-24 17:38:35	2	3	66	174	37449	5873.2923	1	1	...	2016-05-12	2016-05-15	2	0	1	14474	7	6	204	1540
2	2	2015-06-07 15:53:02	2	3	66	142	17440	3975.9776	20	0	...	2015-07-26	2015-07-27	4	0	1	11353	1	2	50	699
3	3	2015-09-14 14:49:10	2	3	66	258	34156	1508.5975	28	0	...	2015-09-14	2015-09-16	2	0	1	8250	1	2	50	628
4	4	2015-07-17 09:32:04	2	3	66	467	36345	66.7913	50	0	...	2015-07-22	2015-07-23	2	0	1	11812	1	2	50	538

通过观察test.csv我们可以发现几件事:

  • test.csv的日期比train.csv要更晚,数据页也证实了这一点,测试数据集的日期是从2015开始的,而训练数据集的日期是从2013到2014的。
  • 看起来test.csv的用户ids好像是train.csv用户ids的一个子集,是一个交叉的整型树范围。我们之后会证实这个。
  • test.csv中的is_booking列的数值总是1,数据页可以证实这一点。

找出我们需要预测什么

我们将根据给定的一个用户的搜索数据预测哪个hotel_cluster会被预定。根据描述总共大概有100个集群。
计分方法评价页面说计分方法将会采用Mean Average Precision@5,那就是说我们需要在每行做出五个预测集群,然后根据正确的预测是否出现在我们的列表中来打分,正确的预测越靠前,我们得到的分数越多。举个例子,如果正确的集群是3,而我们的预测是4,43,60,3,20,那我们的分数将低于预测3,4,43,60,20的分数。
探索酒店集群:现在我们知道了我们要预测什么,是时候来钻研和探索hotel_cluster了。我们可以使用value_counts方法在序列中来完成:

in[1]:train["hotel_cluster"].value_counts()

out[1]:
91    1043720
41     772743
48     754033
64     704734
65     670960
5      620194
       ...
53     134812
88     107784
27     105040
74      48355

上面的结果是截断了的,但是也可以看出每个集群中的酒店数目也是相当的均匀分布,这并没有显示出集群号码和项目数量之间有任何的联系。
探索训练和测试数据的用户ids(user_id):最后,我们将验证我们的 假设——所有的测试用户id都能在训练数据框中找到。我们可以通过找到训练数据集中所有的用户id(user_id)唯一值来看他们是否存在于训练数据集。在下面的代码中,我们将:

  • 创建一个包含了所有测试数据中用户id唯一值的集合。
  • 创建一个包含了所有训练数据中用户id唯一值的集合。
  • 计算出有多少测试数据中的用户id在训练数据中用户id中。
  • 看看匹配的数目是否和测试数据中用户id总数一样。
test_ids = set(test.user_id.unique())
train_ids = set(train.user_id.unique())
intersection_count = len(test_ids & train_ids)
intersection_count == len(test_ids)

out:
True

看来我们的假设是准确的,这应该会让我们在这个数据上工作起来更容易。
在我们的Kaggle数据上采样:完整的训练数据包含了3700万行数据,这会使我们在不同方法中实验变得困难。理想情况下,我们想要一个足够小的数据集可以让我们能够非常快的迭代不同的方法而且仍然能够代表整个训练数据集。我们可以通过从我们的数据中随机取样行,然后从train.csv中选取新的训练数据集和测试数据集,通过从train.csv选取所有的数据集,我们每一行可以得到真正的hotel_cluster标签,并且我们可以和我们测试方法一样能够计算我们的准确率。
添加日期和时间:第一步是将monthyear添加到train,因为train和test数据的区分就是日期,我们需要添加日期字段来使我们可以用一样的方法将我们的数据分割成两个数据集。如果我们添加了yearmonth字段,我们可以将我们的数据分割成测试数据集合训练数据集来使用它们。下面的代码将:

  • data_time列从object转换成datatime值,这能使它比日期工作起来简单许多。
  • yearmonthdata_time提取出来并赋值到它们的列。
train["date_time"] = pd.to_datetime(train["date_time"])
train["year"] = train["date_time"].dt.year
train["month"] = train["date_time"].dt.month

选出10000个用户:因为测试的用户id是训练用户id的一个子集,所以我们需要通过随机取样的一种方式保存每一个用户的全部数据。我们可以通过选取一个确定用户随机的数字然后从train选取user_id在我们的用户id随机取样集合中的行来实现。

import random

unique_users = train.user_id.unique()

sel_user_ids = [unique_users[i] for i in sorted(random.sample(range(len(unique_users)), 10000)) ]
sel_train = train[train.user_id.isin(sel_user_ids)]

上面的代码创建了一个叫做sel_train的数据框只包含10000个用户的数据。
选取新的训练和测试数据集:我们现在需要从sel_train选取新的训练和测试数据集,我们将其命名为t1和t2.

t1 = sel_train[((sel_train.year == 2013) | ((sel_train.year == 2014) & (sel_train.month < 8)))]
t2 = sel_train[((sel_train.year == 2014) & (sel_train.month >= 8))]

原始的test数据框包含的数据是从2015年开始,train包含的数据是从2013到2014,所以我们将新数据集中2014年7月之后的数据分给t2,之前的数据分给t1。这会使我们以更小的训练和测试数据集得到和原始的测试训练数据集相似的特性。
移除点击事件:如果is_booking的值是0,表明是一次点击,值是1表明是一个预定。测试数据集值包含预定事件,所以我们也需要将t2简化成只包含预定。

t2 = t2[t2.is_booking == True]

一个简单的算法:我们可以在这个数据集试验最简单的方法找到最常见的集群,然后使用它们来做预测。在这里我们可以再次使用value_counts方法来帮助我们完成:

most_common_clusters = list(train.hotel_cluster.value_counts().head().index)

上面的代码将为我们展示训练数据集中一个包含五个最常见的集群的列表,这是因为head()方法默认返回前五行,在value_counts方法运行之后index属性将会返回数据框中酒店集群的索引。
生成预测:我们将用同样的方法为每一行都做预测并将most_common_clusters保存到一个列表中。

predictions = [most_common_clusters for i in range(t2.shape[0])]

这会创建一个元素和t2的行数一样多的列表,每个元素等同于most_common_clusters

评估误差

为了评估误差我们首先要搞清如何计算Mean Average Precision,不过,Ben Hamner已经实现了这个方法点这里去到github看源码并安装,可以作为ml_metrics包的一部分来安装,你可以点这里找到怎么安装它安装指南。我们可以用ml_metricsmapk方法计算我们的误差度量:

import ml_metrics as metrics
target = [[l] for l in t2["hotel_cluster"]]
metrics.mapk(target, predictions, k=5)

out:
0.058020770920711007

我们的目标是需要用列表嵌套列表的格式来使mapk工作,所以我们要把t2的hotel_cluster列转换成一个嵌套列表,然后根据我们的目标,我们的预测,我们想要评估(5)的预测数量调用mapk方法。
我们的结果并不是太好,但是我们才生成我们的第一个预测数据集,和估计误差。我们已经建好的框架可以帮助我们快速的测试其他一系列的方法而且能计算分数,我们顺利的构建了一个在排行榜中性能很棒的解决方案。
找出相关性:在我们去创造一个更好的算法之前,让我们找找是否有和hotel_cluster非常相关的元素,这会让我们知道我们是否要花更多的时间来研究某一列,我们可以用corr方法从训练数据集中算出线性相关:

train.corr()["hotel_cluster"]

out:
site_name                   -0.022408
posa_continent               0.014938
user_location_country       -0.010477
user_location_region         0.007453
user_location_city           0.000831
orig_destination_distance    0.007260
user_id                      0.001052
is_mobile                    0.008412
is_package                   0.038733
channel                      0.000707

这告诉我们没有哪一列是和hotel_cluster线性相关的,这是有道理的,因为没有线性预定hotel_cluster,举个例子:有一个更高的集群号码和有一个更高的srch_destination_id没有关联。 很遗憾,这就意味着像线性回归和逻辑回归等方法都不能在我们的数据上起到好的效果,因为他们依靠的就是预测和目标之间线性相关性。

为我们的Kaggle提交创建更好的预测

这个比赛的这些数据集用机器学习来做预测非常难的原因有这些:

  • 有上千万行,会增加算法的运行时间和内存。
  • 有100个不同的集群,但是根据比赛管理员描述其边界却相当的模糊,所以这会让做预测变得困难。随着集群的增加,分类器的准确率通常会降低。
  • 没有和目标(hotel_cluster)是线性相关的,意味着我们不能用很快的像线性回归这样的机器学习方法。

由于这些原因,机器学习可能在我们的数据上不会有很好的效果,但是我们可以尝试找出一个算法。

生成特征

第一步是应用机器学习来生成特征,我们可以在训练数据集和destinations数据集中用可用的数据来生成特征,我们至今还没有看过destinations,所以我们就先快速的看一眼。
从destinations中生成特征destinations包含了和srch_destination_id相关联的一个id,还有destinations149列隐藏信息,这是个样例:
srch_destination_id d1 d2 d3 d4 d5 d6 d7 d8 d9 ... d140 d141 d142 d143 d144 d145 d146 d147 d148 d149
0 0 -2.198657 -2.198657 -2.198657 -2.198657 -2.198657 -1.897627 -2.198657 -2.198657 -1.897627 ... -2.198657 -2.198657 -2.198657 -2.198657 -2.198657 -2.198657 -2.198657 -2.198657 -2.198657 -2.198657
1 1 -2.181690 -2.181690 -2.181690 -2.082564 -2.181690 -2.165028 -2.181690 -2.181690 -2.031597 ... -2.165028 -2.181690 -2.165028 -2.181690 -2.181690 -2.165028 -2.181690 -2.181690 -2.181690 -2.181690
2 2 -2.183490 -2.224164 -2.224164 -2.189562 -2.105819 -2.075407 -2.224164 -2.118483 -2.140393 ... -2.224164 -2.224164 -2.196379 -2.224164 -2.192009 -2.224164 -2.224164 -2.224164 -2.224164 -2.057548
3 3 -2.177409 -2.177409 -2.177409 -2.177409 -2.177409 -2.115485 -2.177409 -2.177409 -2.177409 ... -2.161081 -2.177409 -2.177409 -2.177409 -2.177409 -2.177409 -2.177409 -2.177409 -2.177409 -2.177409
4 4 -2.189562 -2.187783 -2.194008 -2.171153 -2.152303 -2.056618 -2.194008 -2.194008 -2.145911 ... -2.187356 -2.194008 -2.191779 -2.194008 -2.194008 -2.185161 -2.194008 -2.194008 -2.194008 -2.188037

比赛没有告诉我们每一个隐藏特征真正的意义,但是可以假设那些是和目的特性的某些组成,像名字,描述,或者其他。这些隐藏特征将会被转换从数值,所以他们将变成匿名的。
我们可以将目标信息当成特征应用在机器学习算法中,但是我们也需要先将列的数量压缩来减少运行时间。我们可以用PCA,PCA会在一个矩阵中减少列的数量同时会尽量保留每一行相同的变量。理想情况下,PCA会把包含的所有列的全部信息都压缩得更少,但是现实是会丢失一些信息。在下面的代码中,我们将:

  • scikit-learn来初始化PCA模型;
  • 确定我们想保留数据集中的3列数据;
  • 将d1-d149列转换成3列。
from sklearn.decomposition import PCA

pca = PCA(n_components=3)
dest_small = pca.fit_transform(destinations[["d{0}".format(i + 1) for i in range(149)]])
dest_small = pd.DataFrame(dest_small)
dest_small["srch_destination_id"] = destinations["srch_destination_id"]

上面的代码实现了将destinations中149列的数据压缩成了3列,并创建了一个叫dest_small的数据框。在我们完成这个的时候保留了destinations中大多数的变量,所以我们没有丢失太多信息,但是却减少了机器学习算法的大量运行时间。

生成特征

现在我们已经完成了预处理工作了,我们可以生成我们的特征了,我们将完成以下内容:

  • 生成新的像data_time的数据特征列。
  • 删除像data_time的非数据列。
  • dest_small添加特征。
  • 用-1替换所有的缺失值。
def calc_fast_features(df):
    df["date_time"] = pd.to_datetime(df["date_time"])
    df["srch_ci"] = pd.to_datetime(df["srch_ci"], format='%Y-%m-%d', errors="coerce")
    df["srch_co"] = pd.to_datetime(df["srch_co"], format='%Y-%m-%d', errors="coerce")

    props = {}
    for prop in ["month", "day", "hour", "minute", "dayofweek", "quarter"]:
        props[prop] = getattr(df["date_time"].dt, prop)

    carryover = [p for p in df.columns if p not in ["date_time", "srch_ci", "srch_co"]]
    for prop in carryover:
        props[prop] = df[prop]

    date_props = ["month", "day", "dayofweek", "quarter"]
    for prop in date_props:
        props["ci_{0}".format(prop)] = getattr(df["srch_ci"].dt, prop)
        props["co_{0}".format(prop)] = getattr(df["srch_co"].dt, prop)
    props["stay_span"] = (df["srch_co"] - df["srch_ci"]).astype('timedelta64[h]')

    ret = pd.DataFrame(props)

    ret = ret.join(dest_small, on="srch_destination_id", how='left', rsuffix="dest")
    ret = ret.drop("srch_destination_iddest", axis=1)
    return ret

df = calc_fast_features(t1)
df.fillna(-1, inplace=True)

上面的代码将会计算像停留时长,入住日期,退房月这样的特征,这些特征稍后会帮我们训练一个机器学习算法。

机器学习

现在我们有了训练数据的特征,我们可以尝试机器学习了。我们将使用3折交叉验证使用训练数据集来生成一个可靠的误差估计。交叉验证将训练数据集分成三部分,然后用其中每一部分分别预测hotel_cluster,用其他的所有部分来训练。
我们将用随机森林算法来生成预测,随机森林构造树,可以拟合出数据的非线性趋势,即使没有任何一列是线性相关的,这个算法也会让我们能够做出预测。我们将先初始化模型然后计算交叉验证的分数:

predictors = [c for c in df.columns if c not in ["hotel_cluster"]]
from sklearn import cross_validation
from sklearn.ensemble import RandomForestClassifier

clf = RandomForestClassifier(n_estimators=10, min_weight_fraction_leaf=0.1)
scores = cross_validation.cross_val_score(clf, df[predictors], df['hotel_cluster'], cv=3)

out:
array([ 0.06203556,  0.06233452,  0.06392277])

上面的代码并不会得到很好的准确率,并且验证了我们之前的怀疑——对于这个问题机器学习不是一个好的方法。然而,当有一个高聚合的时候分类器很容易得到一个低准确率的结果。我们可以尝试训练100个二分类器,每一个分类都只决定某一行是否在集群中,这将需要为hotel_cluster中每一个标签都训练一个分类器。
二类分类器:我们将再次训练随机森林,但是每一个森林都将只预测一个单独的酒店集群,为了速度我们将使用2折交叉验证,而且每个标签只训练10棵树。在下面的袋中,我们将:

  • 循环遍历每一个唯一的hotel_cluster
    • 使用2折交叉验证训练一个随机森林分类器。
    • 使用分类器提取出某行在唯一hotel_cluster中的概率。
  • 合并所有的概率。
  • 对于每一行,找出5个最大的概率并且将hotel_cluster的值赋值给预测。
  • 使用mapk计算准确率。
from  sklearn.ensemble  import  RandomForestClassifier
from  sklearn.cross_validation  import  KFold
from  itertools  import  chain

all_probs  =  []
unique_clusters  =  df [ "hotel_cluster" ] . unique ()
for  cluster  in  unique_clusters :
    df [ "target" ]  =  1
    df [ "target" ][ df [ "hotel_cluster" ]  !=  cluster ]  =  0
    predictors  =  [ col  for  col  in  df  if  col  not  in  [ 'hotel_cluster' ,  "target" ]]
    probs  =  []
    cv  =  KFold ( len ( df [ "target " ]),  n_folds = 2 )
    clf  =  RandomForestClassifier ( n_estimators = 10 ,  min_weight_fraction_leaf = 0.1 )
    for  i ,  ( tr ,  te )  in  enumerate ( cv ):
        clf . fit ( df [ predictors ] . iloc [ tr ],  df [ "target" ] . iloc [ tr ])
        preds  =  clf . predict_proba ( df [ predictors ] . iloc [ te ])
        probs . append ([ p [ 1 ]  for  p  in  preds ])
    full_probs  =  chain . from_iterable ( probs )
    all_probs . append ( list ( full_probs ))

prediction_frame  =  pd . DataFrame ( all_probs ) . T
prediction_frame . columns  =  unique_clusters
def  find_top_5 ( row ):
    return  list ( row . nlargest ( 5 ) . index )

preds  =  []
for  index ,  row  in  prediction_frame . iterrows ():
    preds . append ( find_top_5 ( row ))

metrics . mapk ([[ l ]  for  l  in  t2 . iloc [ "hotel_cluster" ]],  preds ,  k = 5 )

out:
0.041083333333333326

我们现在的准确率还不如之前,排行榜的其他人比这个准确率的分数高出很多,为了完成比赛我们必须抛弃机器学习砖头其他方法的怀抱,机器学习是一个非常强大的方法,但是并不是对于每一个问题都总是好的方法。

基于hotel_cluster的顶级集群:很少有比赛的kaggle-scriptsorig_destination_distance或者srch_destination_idhotel_cluster联系起来,聚合orig_destination_distance会利用一个比赛的数据泄露,并且会试图匹配相同的用户聚集到一起。聚合orig_destination_distance会为每一个目标找到最受欢迎的酒店集群,然后我们就能够预测某个用户搜索的目标是最受欢迎的酒店集群中的一个。想想这个我们早先使用过的最常见的聚合方法的颗粒版本。
我们可以先生成每一个srch_destination_id中的每一个hotel_cluster的分数,我们将预定的权重高于点击,这是因为尝试数据集全都是预定数据,并且这也是我们想要预测的,我们想包括点击信息,但是用低权重来反映,接下来我们将:

  • 通过srch_destination_idhotel_cluster将t1分组。
  • 迭代每一个分组,并且:
    • 赋值1表明每一个酒店集群的is_booking为真;
    • 赋值.15表明每一个酒店集群的is_booking为假;
    • 在字典中将分数赋值给srch_destination_id/hotel_cluster组合。

下面的代码将完成上面的步骤:

def make_key(items):
    return "_".join([str(i) for i in items])

match_cols = ["srch_destination_id"]
cluster_cols = match_cols + ['hotel_cluster']
groups = t1.groupby(cluster_cols)
top_clusters = {}
for name, group in groups:
    clicks = len(group.is_booking[group.is_booking == False])
    bookings = len(group.is_booking[group.is_booking == True])

    score = bookings + .15 * clicks

    clus_name = make_key(name[:len(match_cols)])
    if clus_name not in top_clusters:
        top_clusters[clus_name] = {}
    top_clusters[clus_name][name[-1]] = score

最后,我们有一个每个键是一个srch_destination_id的字典,字典中的每一个值都会是另外一个字典——酒店集群是键,分数是值,这是一个样例:
{'39331': {20: 1.15, 30: 0.15, 81: 0.3},
'511': {17: 0.15, 34: 0.15, 55: 0.15, 70: 0.15}}

我们接下来将变换这个字典来找到每个srch_destination_id的前五个酒店集群,为了完成这个任务,我们将:

  • 遍历top_clusters的每一个键;
  • 找到键中的最高的5个集群;
  • 将最高的5个集群赋值给一个新的字典cluster_dict

代码:

import operator

cluster_dict = {}
for n in top_clusters:
    tc = top_clusters[n]
    top = [l[0] for l in sorted(tc.items(), key=operator.itemgetter(1), reverse=True)[:5]]
    cluster_dict[n] = top

基于用户目标来做预测

一旦我们知道了每一个srch_destination_id的前几个集群,我们就可以很快开始做预测了,为了做预测,我们必须完成这些:

  • 迭代t2中的每一行:
    • 提取每一行的srch_destination_id
    • 为目标id找到前几的集群;
    • 将前几集群添加到preds

代码:

preds = []
for index, row in t2.iterrows():
    key = make_key([row[m] for m in match_cols])
    if key in cluster_dict:
        preds.append(cluster_dict[key])
    else:
        preds.append([])

循环结束后,preds将会变成一个包含了我们预测的嵌套列表,长成这样:

[
  [2, 25, 28, 10, 64],
  [25, 78, 64, 90, 60],
  ...
]

计算误差

一旦我们得到了我们的预测,我们就可以早点使用mapk函数来计算我们的准确率了:

metrics.mapk([[l] for l in t2["hotel_cluster"]], preds, k=5)

out:
0.22388136288998359

这个结果已经做得相当好了!我们比之前最好的机器学习方法提升了4倍多,而且我们的方法简单和快得多。你可能也注意到这个值比排行榜的准确率低了一些,本地的测试结果比提交之后的准确率要低,所以这个方法实际上在排行榜上回表现得非常好,排行榜和本地的分数差异是由于一些因素:

  • 排行榜计分的的隐藏数据和本地的计算数据是不同的,举个例子:我们计算误差用的是训练数据集的一个样例,但是排行榜使用的是测试数据集来计算分数的。
  • 方法有越多的训练数据得到的准确率越高,我们只使用了训练数据集的一个较小的子集,当我们使用全部训练数据集时它可能会有更高的准确率;
  • 不同的随机化。确定的算法,包含随机的数字,但是我们不会使用这些。

为你的Kaggle提交生成更好的预测

Kaggle的论坛非常的重要,你会集成找到非常棒的信息并且会帮助你提升你的分数,Expedia比赛也不会例外,这个帖子讨论了一个从测试数据集中使用一个包含了user_location_countryuser_location_region列的数据集来在训练数据集中匹配匹配用户的数据泄露。我们将使用这个帖子的信息通过用测试数据集匹配训练数据集的用户来提升我们的分数,基于论坛的思路,这样做是OK的,毕竟比赛是不会因为数据泄露而更新数据。
找出匹配用户:第一步就是从训练数据集中到测试数据集中匹配的用户,为了完成这个任务,我们将:

  • 将训练数据集以匹配的列切分分组;
  • 遍历测试数据;
  • 为匹配列创建索引;
  • 用分组得到所有测试数据和训练数据集相匹配的用户。

代码;

match_cols = ['user_location_country', 'user_location_region', 'user_location_city', 'hotel_market', 'orig_destination_distance']

groups = t1.groupby(match_cols)

def generate_exact_matches(row, match_cols):
    index = tuple([row[t] for t in match_cols])
    try:
        group = groups.get_group(index)
    except Exception:
        return []
    clus = list(set(group.hotel_cluster))
    return clus

exact_matches = []
for i in range(t2.shape[0]):
    exact_matches.append(generate_exact_matches(t2.iloc[i], match_cols))

循环结束时,我们会得到一个包含了所有准确匹配测试和训练数据集的嵌套列表,然而,并没有太多的匹配,为了了估计误差更精准,我们将这些预测合并到我们之前的预测中,否则,我们会得到一个低准确率的值,因为预测的很多行都是空列表。

合并预测:我们可以合并预测的不同列表来提高准确率,做了这些也能帮助我们看出我们的匹配策略有多好,为了完成这个任务,我们必须要:

  • 合并exact_matches,preds,most_common_clusters
  • 从这里使用f5函数只选取唯一的预测,按顺序排列;
  • 确保我们有了测试数据集中每一行的最大的5个预测。

代码:

def f5(seq, idfun=None):
    if idfun is None:
        def idfun(x): return x
    seen = {}
    result = []
    for item in seq:
        marker = idfun(item)
        if marker in seen: continue
        seen[marker] = 1
        result.append(item)
    return result

full_preds = [f5(exact_matches[p] + preds[p] + most_common_clusters)[:5] for p in range(len(preds))]
mapk([[l] for l in t2["hotel_cluster"]], full_preds, k=5)

out:
0.28400041050903119

根据误差这看起来已经相当不错了——我们比之前明显提高了!我们将继续前进并且做出更多小的实现,但是我们可能打算现在提交了。

完成一个Kaggle提交文件

幸好,因为我们写代码的方式,所有我们为了提交必须完成的工作就是将train赋值给变量t1,将测试数据集赋给t2.然后,我只需要重新运行代码来做预测,重新运行训练和测试数据集应该花费不到一个小时,一旦我们得到了预测,我们只需要将他们写进一个文件即可:

write_p = [" ".join([str(l) for l in p]) for p in full_preds]
write_frame = ["{0},{1}".format(t2["id"][i], write_p[i]) for i in range(len(full_preds))]
write_frame = ["id,hotel_clusters"] + write_frame
with open("predictions.csv", "w+") as f:
    f.write("\n".join(write_frame))

我们将得到一个正确格式的提交文件,随着文件写完,提交这个文件你就会成为top15。

总结

我们在这个帖子上取得了很大的进展!我们一路从只是观察数据到创建提交并且进入排行榜,顺便,我们再回顾一下一些关键步骤:

  • 研究数据并且理解问题;
  • 建立一种快速迭代不同方法的方式;
  • 构造一种计算本地准确率的方式;
  • 通过非常亲密的阅读论坛、原稿和竞赛的描述来更好的理解数据的结构;
  • 尝试一系列的方法并且不要害怕不使用机器学习。

这些步骤可以保证你在任何的Kaggle比赛中取得较好的成绩。

进一步提高

为了快速的迭代探测方法,速度很关键,但是对于这个比赛却非常难,但是有一些策略可以去尝试:

  • 使随机取样的数据集样本更小;
  • 使用多核心并行执行;
  • 使用Spark或其他工具来使任务能够在分布式系统上并行运行;
  • 探索更多的方法来写代码和标记来找出更有效的方法;
  • 避免迭代所有的训练数据集和测试数据集,使用分组数据来替代。

写出快速、高效率的代码对于这个比赛会有很大的优势。

尝试特征方法

一旦你有一个坚实的基础来运行你的代码,这有几个途径来探索按照提升准确率的方法:

  • 找到用户之间的相似点,然后基于相似点来调整酒店集群分数;
  • 利用相似点将所有目标分成多个目标组;
  • 在数据集的子集上应用机器学习算法;
  • 以一个参数的方式合并不同的预测策略;
  • 探索酒店集群和地区之间更多的数据泄露;

我希望你们能在比赛开心!我也希望听到你们的任何反馈,如果当你在研究比赛时想要学习更多内容,欢迎选择我们的课程dataquest来学习关于数据处理,统计学,机器学习,如何使用Spark工作等等。

posted @ 2016-07-04 23:33  cutd  阅读(3434)  评论(0编辑  收藏  举报