KKBOX Data Game - 17.06 1st place Solution
KKBOX Data Game 17.06 是 KKBOX 子集團 - KKV (KKStream & KKTV) 在 Kaggle 上第一次對外舉辦的資料分析競賽。經過兩個禮拜的努力,在這個比賽拿了第一名。以下想跟大家分享這次的競賽過程。
比賽首頁有詳細解說這次比賽的資料跟題目,此處僅作簡單描述。比賽的目的是想透過每個用戶 (匿名處理) 前四個月歷史觀看各個劇 (匿名處理) 的資料,來預測接下來一個月觀看最久的是哪一部劇。
一開始,我用些時間理解跟熟悉這份資料,可以幫助判斷該用何種方式解題比較合適。而探索資料的過程有很多面向,以這次來說,我做的是不斷猜想跟驗證的循環。
資料探索
首先拿到資料,我先查看一下基本的統計量,像是各劇的佔比、用戶平均看多少劇、花多少時間……等等,雖然有點抽象,但在跟資料越多次的來回交流,會更加深對這份資料輪廓的認識。另外我比較了資料在訓練集跟測試集的分布狀況是否一致,這關係到我們是否能相信利用訓練資料來做驗證。就結果而言,我覺得這份資料對於訓練集和測試集的切割,十分一致,使我接下來在做交叉驗證的過程中,更具信心。
在訓練集資料探索的過程,可以發現以下幾件事情:
- 27% 的用戶在 labels_train(標準答案) 的 title_id 是最後一部看的劇
- 37% 的用戶在 labels_train 的 title_id 出現在他過去看過的劇
- 18% 的用戶在 labels_train 的 title_id 是訓練資料沒出現過的劇
有 ⅓ 左右的用戶,labesl_train 是看過去看過的劇。因此若是用協同過濾(CF)等方法,針對沒看過的劇進行推薦,那就等於會丟失這些資訊。
最後一部劇推薦 (PB Leaderboard: 0.27276)
將每個用戶的最後一部劇當作答案遞交,在 PB Leaderboard 的分數是 0.27276,因此希望後續的改進,都可以基於這個基準(baseline)往上提升
轉移矩陣 (Transition Matrix) (PB Leaderboard: 0.27421)
把資料中各個用戶觀看的歷史紀錄依據時序排列,序列中每一個元素為當下觀看的劇 (title id)。則任一用戶,可以取得一序列,如 (t1, t1, t2, t3, …)。而透過任兩部連續的元素觀察,可以發現連續觀看同一部劇的序列出現次數很高,說明用戶可能正在追劇。因此建立出來的轉移矩陣,反映的結果會類似直接取最後一部來做推薦。這方法呼應前面我們不想違背「最後一部劇推薦」 (.27) 的想法,並且也能更有系統性地去統整每一部劇轉移的關聯性。
此外,使用轉移矩陣的概念,再納入 labels_train,我們等於也窺見「未來才會出現的劇」(換言之,在訓練資料集中沒有的 title id)來提升猜中「答案是沒出現過的劇」的狀況。
而透過轉移矩陣所得出的答案,在 PB Leaderboard 的分數是 0.27421。
而可惜的是,無論是最後一部推薦,或是轉移矩陣,都有點像是「規則式」的方法來找答案,我暫且想不到辦法進一步提升準確率的方法。( 或許轉移矩陣可以透過考慮更長的序列來達到不同的預測效果,不過我當時沒有進一步嘗試)。並且以直覺來說,用戶的歷史觀看行為,像是看幾種劇、各部劇看過幾次……等資訊,應該是會和推薦結果有相關的,但上述的方法都沒有使用到。
重新定義問題
由於上述的探索過程,將本來的推薦問題嘗試更動為多類別分類問題。藉由用戶的歷史行為,預測用戶會看哪一部劇。
總共的劇大約有接近 500 部,因此若視為一個 500 類的分類問題,在模型的建立與訓練會非常耗時。因此實作上,只考慮前 n 個熱門的劇作為分類,剩下的則歸類為其他。此處 n 取 40,並沒有特別意義,只是在訓練時間跟群數上取得一個平衡。
因此最終將問題轉換為一個多類別分類問題,總類別數為 41 ( 40 部劇與其他 )。
分類模型
既然將題目定義為分類問題,因此就需要製作特徵(Feature)來提供給模型學習。特徵分別如下:
- 看過幾部不同的劇 (number of titles)
- 總資料列數 (total session number)
- 在各個月份觀看的次數 (session number per month)
- 最後一部看什麼 (last title id)
- 每一部花觀看的總時間 (session length per title)
- 每一部劇看幾次 (session number per title)
總共特徵數在處理完(one-hot-encoding)後,大約為 1000 個。
此處所使用的分類模型是 XGBoost,由於時間因素,並沒有對於參數做過多的調整,只單純用過去使用的經驗設定一組參數來訓練模型。整個訓練的過程大致如下:
- 透過分層抽樣後的交叉驗證 ( cross validation ),決定 nround 參數。並將 out-of-fold cv prediction 存起來(以利後續使用)。
- 透過所有訓練資料訓練模型。
- 對測試資料預測,並取出前三名預測機率值最高的劇。
結合轉移矩陣和分類模型結果 (PB Leaderboard: 0.28562)
由於在設計成分類問題時,有一類為「其他」,因此若預測結果為「其他」的那些樣本,想透過轉移矩陣的答案來補強。以下分別介紹步驟跟舉例:
- 資料格式:每個用戶會有四個預測值,分別是「轉移矩陣預測的劇」、「分類模型預測第一名的劇」、「分類模型預測第二名的劇」和「分類模型預測第三名的劇」,其中透過分類模型預測出的劇有機率值。
- 在「預測機率值」跟「轉移矩陣」結果中取得一個平衡,因為整體來說,除了第一名的機率值外,第二和第三機率值的分布差異很大,有些機率值很高,代表信心很高,但有些機率值都偏低,代表信心不足。因此要針對第二到第三名的機率值分布,各自找出各自的機率門檻值 ( threshold )。而因為第一名的機率值普遍較高,因此只要第一名的預測結果不是「其他」類別,就將預測第一名的劇作為預測答案。
在實作方面,透過先前的 out-of-fold prediction 來作為訓練資料的預測結果,並同樣利用轉移矩陣來取出每個訓練資料樣本中的預測值,並針對由模型選出的第二名跟第三名,各自從 0 ~ 1 的區間設定一組數值當作門檻值,使得整體的預測命中率最高(各自選出的數值為 0.11, 0.095)。範例如下:
- 先做出兩組向量,皆為取介於 0 ~ 1 之間的數。例:thld_vec_1 = thld_vec_2 = (0, 0.05, 0.1, 0.15, …, 0.90, 0.95, 1)。
- 對第 i 個用戶,我們會有「轉移矩陣的預測結果」、「模型選出機率最高的劇」、「模型選出機率次高的劇」以及「模型選出機率第三高的劇」。
- 先設定一組數值,分別為 thld_1 跟 thld_2。 其中 thld_1 和 thld_2 為從 thld_vec_1 和 thld_vec_2 取出的一組組合。
- 若模型推出的「機率最高的劇」不為「其他」,則推選該劇為答案。若為其他,則往下看「機率次高的劇」。
- 若「機率次高的劇」不是「其他」,且「機率次高的劇」的機率值大於 thld_1,則取其為答案;若否,則再往下看「機率第三高的劇」。
- 若「機率第三高的劇」不是「其他」,且「機率第三高的劇」的機率值大於 thld_2,則取其為答案;若否,則取「轉移矩陣」的結果。
- 目標為找到一組 thld_1 和 thld_2 的組合,使得整體的準確度最高。
透過結合轉移矩陣跟分類模型,在 PB Leaderboard 上取得 0.28562,PV Leaderboard: 28811。
心得
以上敘述為針對這次比賽的過程分享。從分數的進展發現,做了多方調整後,增加了約1% (0.27 to 0.28) 的準確度,雖然進步幅度不大,在時間成本上很不划算,但透過模型的方式,確實找到了一些人工比較沒辦法找到的規則,再加上因為時間有限,所以這次並沒有在調整參數上花很多心力,而這部分對於模型的成效是有很大的影響力。另外因為這次提供的資料只有瀏覽紀錄,若再加入像是劇的相關資訊 (meta data),用人工的方式找規則就會更加困難,而這些都是可以透過模型的方式來找出關聯性。
分析資料的方式有很多種,歡迎大家有任何想法都可以跟我一起討論,進而找出資料中更多還沒被發掘的資訊:)
補充
- 程式碼:1st place solution on github
- 模型架構圖: