1. 程式人生 > >一文帶你搭建簡單的酒店推薦系統

一文帶你搭建簡單的酒店推薦系統

1. 資料

資料是匿名使用者的,並且所有欄位都是數字格式。資料可以在Kaggle中下載,train.csv中記錄使用者的行為,destinations.csv包含了使用者的酒店資訊。

import datetime
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import make_pipeline
from sklearn import preprocessing
from sklearn.preprocessing import StandardScaler
from sklearn import svm

為了能夠在本地執行,我們隨機選取了1%的資料,但仍然有24179條資料。

df = pd.read_csv('train.csv.gz', sep=',').dropna()
dest = pd.read_csv('destinations.csv.gz')
df = df.sample(frac=0.01, random_state=99)
df.shape

輸出:(241179, 24)

2. 探索性分析

該系統的目的是要根據使用者的搜尋資訊,預測使用者將會預定哪種旅館。總共有100種。換言之,我們是要處理一個100分類問題。

plt.figure(figsize=(12, 6))
sns.distplot(df['hotel_cluster'])

可以看出每個類的分佈很均勻。

3. 特徵工程

Checkin和checkout列的資料是時間格式的資料,不能直接使用。我們將從中提取出年份和月份。通過定義一個函式取抽取,並將他們合併到destination.csv中。

from datetime import datetime
def get_year(x):
    if x is not None and type(x) is not float:
        try:
            return datetime.strptime(x, '%Y-%m-%d').year
        except ValueError:
            return datetime.strptime(x, '%Y-%m-%d %H:%M:%S').year
    else:
        return 2013
    pass
def get_month(x):
    if x is not None and type(x) is not float:
        try:
            return datetime.strptime(x, '%Y-%m-%d').month
        except:
            return datetime.strptime(x, '%Y-%m-%d %H:%M:%S').month
    else:
        return 1
    pass

def left_merge_dataset(left_dframe, right_dframe, merge_column):
    return pd.merge(left_dframe, right_dframe, on=merge_column, how='left')

處理時間格式的列:

df['date_time_year'] = pd.Series(df.date_time, index = df.index)
df['date_time_month'] = pd.Series(df.date_time, index = df.index)
from datetime import datetime
df.date_time_year = df.date_time_year.apply(lambda x: get_year(x))
df.date_time_month = df.date_time_month.apply(lambda x: get_month(x))
del df['date_time']

處理srch_ci列:

df['srch_ci_year'] = pd.Series(df.srch_ci, index=df.index)
df['srch_ci_month'] = pd.Series(df.srch_ci, index=df.index)


# convert year & months to int
df.srch_ci_year = df.srch_ci_year.apply(lambda x: get_year(x))
df.srch_ci_month = df.srch_ci_month.apply(lambda x: get_month(x))
# remove the srch_ci column
del df['srch_ci']

處理srch_co列:

df['srch_co_year'] = pd.Series(df.srch_co, index=df.index)
df['srch_co_month'] = pd.Series(df.srch_co, index=df.index)
# convert year & months to int
df.srch_co_year = df.srch_co_year.apply(lambda x: get_year(x))
df.srch_co_month = df.srch_co_month.apply(lambda x: get_month(x))
# remove the srch_co column
del df['srch_co']

4. 初步分析

在建立了一些新列和去除一些無用的列後,我們想要知道每一列跟類標是否有線性關係。這可以讓我們更加關注一些特定的特徵。

df.corr()["hotel_cluster"].sort_values()

可以看出所有的列都基本跟類標沒什麼線性關係。這意味著剛才的那些方法對這個問題並不合適。

5. 策略

在快速的進行谷歌搜尋之後,我們不難發現將目的地、旅館國家、旅館超市結合起來能夠更加準確的幫助我們找到對應的類標。

pieces = [df.groupby(['srch_destination_id','hotel_country','hotel_market','hotel_cluster'])['is_booking'].agg(['sum','count'])]
agg = pd.concat(pieces).groupby(level=[0,1,2,3]).sum()
agg.dropna(inplace=True)
agg.head()

agg['sum_and_cnt'] = 0.85*agg['sum'] + 0.15*agg['count']
agg = agg.groupby(level=[0,1,2]).apply(lambda x: x.astype(float)/x.sum())
agg.reset_index(inplace=True)
agg.head()

agg_pivot = agg.pivot_table(index=['srch_destination_id','hotel_country','hotel_market'], columns='hotel_cluster', values='sum_and_cnt').reset_index()
agg_pivot.head()

df = pd.merge(df, dest, how='left', on='srch_destination_id')
df = pd.merge(df, agg_pivot, how='left', on=['srch_destination_id','hotel_country','hotel_market'])
df.fillna(0, inplace=True)
df.shape

輸出:(241179, 276)

6. 實現演算法

我們只對預定的樣本有興趣:

df = df.loc[df['is_booking'] == 1]

得到特徵和類標:

X = df.drop(['user_id', 'hotel_cluster', 'is_booking'], axis=1)
y = df.hotel_cluster

樸素貝葉斯:

from sklearn.naive_bayes import GaussianNB
clf = make_pipeline(preprocessing.StandardScaler(), GaussianNB(priors=None))
np.mean(cross_val_score(clf, X, y, cv=10))

0.10347912437041926

KNN:

from sklearn.neighbors import KNeighborsClassifier
clf = make_pipeline(preprocessing.StandardScaler(), KNeighborsClassifier(n_neighbors=5))
np.mean(cross_val_score(clf, X, y, cv=10, scoring='accuracy'))

0.25631461834732266

隨機森林:

clf = make_pipeline(preprocessing.StandardScaler(), RandomForestClassifier(n_estimators=273,max_depth=10,random_state=0))
np.mean(cross_val_score(clf, X, y, cv=10))

0.24865023372782996

多分類邏輯迴歸:

from sklearn.linear_model import LogisticRegression
clf = make_pipeline(preprocessing.StandardScaler(), LogisticRegression(multi_class='ovr'))
np.mean(cross_val_score(clf, X, y, cv=10))

0.30445543572367767
支援向量機:很耗時,但是效果更好。

from sklearn import svm
clf = make_pipeline(preprocessing.StandardScaler(), svm.SVC(decision_function_shape='ovo'))
np.mean(cross_val_score(clf, X, y, cv=10))

0.3228727137315005

看起來我們需要做更多的特徵工程去優化結果。接下來將會進一步調優。

 

轉自 :磐創AI