機器學習專案|Kaggle — Airbnb New User Prediction(Top 24%)

學.誌|Chris Kang
13 min readFeb 9, 2021
Photo by Karsten Winegeart on Unsplash

這篇文章主要紀錄 Airbnb 之前在 Kaggle 上的預測競賽,Airbnb New User Bookings 。題目的目的,主要在預測根據已經有的使用者 Log 與基礎資料,來預測未來註冊的新用戶可能會預定那個地區的房子,並藉此在用戶剛註冊時,就以演算法來推薦用戶選項。

其中,評分的方式則是以 nDCG 的計算方式來評估推薦排序,藉以計算該演算法距離理想還有多少的差異,若想知道如何計算可以參考本篇文章

Why do I want to choose this competition?

當初會想要以這個題目為主,是因為在疫情其間其實很想要出國去走走放輕鬆;但當時無奈只能選擇待在台灣。因此希望能把原本的動力,結合自己對機器學習的理解來嘗試解決業界的實際問題。

這個問題其實也和我之前所寫的文章 深入淺出常用推薦系統演算法 的目的相近,都是以使用者的歷史資料來進行推薦。這也是當平台開始累積用戶資料後,能夠提升使用者體驗(User Experience)的一大環節。

本篇文章將會透過多次的探索性分析(Exploratory Data Analysis)與特徵工程進行資料處理,並藉由極限梯度提升樹(XGBoost)作為訓練的演算法。最終使用 18 個翠取特徵,並達到比賽中的 Top24%(360/1458)。因為系統運算限制,並未採取更多層的模型來進行計算。

The result is as below:

My score
Kaggle Leaderboard

使用的語言和工具則以 PythonPandasnumpySklearn、XGboost、matplotlib 等套件,基本上沒有太多過於困難的工具與模型。具體流程可以直接參考下面兩篇文章,皆有更完整的解說方式:

Content

1. Data & Environment Setting

  • 平台使用與資料處理。
  • 第一次探索性分析(EDA)
  • Baseline Model

2. Feature Engineering

  • 處理 training data
  • 處理 session data

3. Model Building

  • 建立模型與預測
  • 評估模型

Data & Environment Setting

平台使用與資料處理

讀者可以直接在 Data Source 的地方下載到所有要分析的數據,整體的資料其實並不算多,再加上我本身使用的 Macbook Air 也是五年前的電腦,因此決定直接使用 Kaggle 提供的 Kernel 來運算。

Kaggle Kernel

第一次探索性分析(EDA)

本文的重點一樣會放在特徵工程與模型表現上,而這次的參賽隊伍其實有 1458 隊參與競賽;但可惜的是在 Kernel 的幾乎都僅有 Baseline 可以參考。

Importing and loading data

將資料與必要的套件讀取進來,並檢視整個資料集的 Shape。

Glance at train data table and details

接著快速檢視一下 train & test data 的 missing value,並列印出來。

OUTPUT

我們可以發現 date_first_bookingtest data 100% 完全沒有資料,換句話說這筆資料對預測完全沒有幫助,因此需要刪除;而 age 則也各有 40% 左右的資料有遺失值,等等需要考慮以 KNN 填補。

接著,大概瞥一眼整體的資料狀況,以及個別的資料擁有哪一些類別。

df_total.head()

從前面的五筆,我們就可以發現雖然 gender 並沒有任何缺失值;但實際上仍然有 -unknown-的值。因此等等需要想辦法處理缺失值。

其他的變數則分別有:

train_data的資料中,我們可以發現一些有趣且可以利用的資料。舉個例子來說:例如第一次的啟動時間、第一次的申請時間、裝置差異等資料,可以計算啟動與申請時間的差異,或是進行 EDA 瞭解使用不同的裝置是否有行為上的差異。

再來便是參考裡面很多人參考的 EDA Kernel 進行資料的探索性資料分析,為了避免文章過常,這裡僅附上部分的 plot code 來說明 EDA 的內容,有興趣的讀者可以直接參考文末的 Github Code

Feature: Gender

可以發現 Unknown 的比例佔比為整體最高,因此可能需要考慮統一以隱含特徵來處理未知的資料。

Feature: Year & Month

接著我們以年份和 account_created 的數量來繪圖,可以發現在 Training data 當中,不同的年份確實有很明顯的分部差異;且我們可以看到月份確實會影響用戶的註冊意願。

進一步的分析,還可以帶到不同年份的用戶組成分佈是否不同?哪一部份的用戶崛起的最快等等問題。以下我們透過 Pivot_table 來進一步檢視資料的分佈狀況:

Year-AccountCreated Distribution
Month-AccountCreated Distribution
Month-Gender Distribution
Month-Age Distribution

上面可以看到很有趣的趨勢是,15~30 歲的年輕人在之後隨著時間明顯上升,而在 7~10 月平穩一陣子後又明顯提升。進一步瞭解後面的 2014~2015 年後的數據,發現正是該族群的用戶明顯提升,讓第一張圖表的每年使用者大幅提昇。

Glance at session data in details

session_data.head()

我們可以觀察到 action_typedetail 的缺失,實際上和 action 有直接相關,凡是 actionlookuptypedetail 皆為 NaN,因此等等可以直接以 Simple Imputer 插值來將 NaN 填補成 lookup

進一步觀察,有一些 secs_elapsed 是隨機缺失的,因此預期一樣考慮以均值或 KNN 來進行填補。

接著,在 action 的項目我發現總共高達 360 個項目,因此我好奇這些項目的佔比為何,是否需要以分箱法來減少特徵量,避免稍後 one-hot encoding 有維度爆炸的危險。

'Ratio contains: 89.70893805376394'

我們發現僅是前面 30 個變數,就已經佔了接近 90% 的佔比。因此後來決定以前面 10 個變數各為一類,剩下的歸納成一類。

Baseline Model

一開始的 Baseline,僅將所有在 train data 的特徵進行資料轉換,並將所有的缺失值以 -1 補上。其 nDCG 獲得 0.851 的分數。因為多數的 Destination 都是 NDF(即沒有預定),因此很容易就獲得很不錯的預測,但其實在這個分數下,排名已經接近在後段的 80% 了。

我們也可以從下列的 train data 分類可以看到 NDF 和 US 就佔了 87%,因此 Baseline 基本都可以達到不錯的分數;但 nDCG 便是透過和理想排名之間的差異,來避免掉評估前面五名時,都只是把 NDF 和 US 放在前面,就因此難以評估差異了。

Building Hypothesis and Tasks

在我們檢視完資料後,就能以觀察到的資料,來預先設計我的特徵工程任務清單,以下是我具體做過的特徵清單。

Training data

  • 看到所有的 Date 類別的資料,我預期 year、month 應該會對於預測有影響,因此將 timestamp_first_active 個別切成一項特徵。
  • 看到所有的 Date 類別的資料,我預期 year、month 應該會對於預測有影響,因此將 date_account_created 個別切成一項特徵。
  • 看到 age 的資料有些型態為 198X,猜測為將年紀誤填寫成西元年。因此將超出 1000 的資料減掉 2015 年作為新的年齡資料。
  • 看到 age 有些資料低於 15 歲很明顯不合法規(因為線上刷卡),因此將低於 15 歲的資料刪除,並填補成 NaN。

Testing data

  • Filled NaN with ‘-1’ as a missing value
  • Calculate each user by: number of actions taken
  • Calculate each user by: number of unique action_type, action_details, device
  • Calculate each user by: sum of seconds of elaspe

Feature Engineering

將上述的這些假設與代辦清單整理好後,我們就可以參考上述的資料來進行特徵工程(Feature Engineering)。

Copy data set & separate timestamp

這裡特別提一下,在進行特徵工程時,盡量養成將資料 copy 一份副本後,以該份副本來編輯與處理。原因是如果以原始的資料來處理,一旦在處理的過程中失敗,就必須回到源頭重新將資料重新讀取與處理一次,會導致資料處理的速度變的很慢。

依據前面的清單與 EDA 的結果,我們可以知道 Year 和 Month 應該是具有影響力的特徵,因此特別將該欄位分出來。這裡我們個別處理 date_account_createdfirst_active 兩個 timestamp,並將處理過後的原是使資料丟棄。

Age fill missing value & transform

在這個階段,我簡單地以潛在特徵 -1 對遺失值進行填充,原因是缺失的值已經佔資料的 48%,如果單純以平均值來填補會造成分佈明顯不均,這很明顯不符合真實資料。再來並不使用 KNN 來進行差補,則是因為資料量較大,將導致計算緩慢,且在高維度的情況下很有可能因維度詛咒而是結果不佳。

未來會考慮使用 ANN(Approximate Nearest Neighbor),據論文結果與 KNN 相似度達 99% 以上;但速度快超過 300 倍。

接著將資料以 5 歲為間格,進行分箱以減少資料的維度差異。

Session Preprocessing

一樣將缺失值以 -1 轉換為潛在特徵,在 XGBoost 處理的時候,就會直接將該類別分成一類,以減少缺失值對預測的影響。

接著,分別計算 session data 裡面的動作次數、比例、動作的細節與類別等,並新增為新的特徵欄位。

在將上述特徵計算出來後,就來看個別資料的分佈狀態。從分佈可以看到所有的分佈都是右偏為主,因此對所有右偏的特徵進行一次的 log transform。

在經過 Log transform 之後,可以發現所有的資料都變成接近常態分佈。

最後將資料合併回原本的資料 df_total 來進行下一步的 one-hot encoding。

Model Building

在開始真的訓練模型之前,必須要先將之前的資料轉換成 One-hot Encoding,才能讓 XGBoost Classifier 運算。

接著將資料放進 XGBoost 裡面訓練模型,好了之後就可以獲得預測結果。

最後將資料進行轉換,把所有的資料轉換成 Kaggle 所需的檔案形式。

經過多次的調參和特徵工程之後,這是筆者目前依賴線上的 Kaggle Kernel能夠做到接近盡力的結果;之前曾經將 XGBoost 的步數稍微拉大一些到 100,Kernel 就曾經跑爆過兩次。之後應該就會開始考慮使用 GPU 或是 Colab 來試著運算看看,是否能在調參上更進一步。

Complete Code:

GitHub Source Code

Summary

這是我第一次在學習機器學習後,實際以 Kaggle 的比賽來練習的專案。原本預期自己要在三個禮拜內推進到前 30 名,沒想到一個半禮拜就達到了,而且這也是第一次沒有可以直接參考的資料科學專案,而是看討論區、Baseline 還有各種 Sklearn 的官方文件嘗試自己實作。

對於踏入機器學習一小陣子,是一個非常棒的練習。而且在實作的過程,真的覺得比起去 DataCamp 的專案做好幾個,都不如直接到 Kaggle 挑一個專案實作來的有效。

我會繼續紀錄我在機器學習、產品管理的所見所學,如果喜歡的讀者歡迎大膽地按下你的追蹤,讓筆者繼續提供更多有趣有詳細的文章吧!

--

--

學.誌|Chris Kang

嗨!我是 Chris,一位擁有技術背景的獵頭,熱愛解決生活與職涯上的挑戰。專注於產品管理/資料科學/前端開發 / 人生成長,在這條路上,歡迎你找我一起聊聊。歡迎來信合作和交流: chriskang0917@gmail.com