KKBOX Data Game - 17.06 1st place Solution

Kaishen Tseng
8 min readJun 29, 2017

--

KKBOX Data Game 17.06KKBOX 子集團 - KKV (KKStream & KKTV) 在 Kaggle 上第一次對外舉辦的資料分析競賽。經過兩個禮拜的努力,在這個比賽拿了第一名。以下想跟大家分享這次的競賽過程。

比賽首頁有詳細解說這次比賽的資料跟題目,此處僅作簡單描述。比賽的目的是想透過每個用戶 (匿名處理) 前四個月歷史觀看各個劇 (匿名處理) 的資料,來預測接下來一個月觀看最久的是哪一部劇。

一開始,我用些時間理解跟熟悉這份資料,可以幫助判斷該用何種方式解題比較合適。而探索資料的過程有很多面向,以這次來說,我做的是不斷猜想跟驗證的循環。

資料探索

首先拿到資料,我先查看一下基本的統計量,像是各劇的佔比、用戶平均看多少劇、花多少時間……等等,雖然有點抽象,但在跟資料越多次的來回交流,會更加深對這份資料輪廓的認識。另外我比較了資料在訓練集跟測試集的分布狀況是否一致,這關係到我們是否能相信利用訓練資料來做驗證。就結果而言,我覺得這份資料對於訓練集和測試集的切割,十分一致,使我接下來在做交叉驗證的過程中,更具信心。

在訓練集資料探索的過程,可以發現以下幾件事情:

  1. 27% 的用戶在 labels_train(標準答案) 的 title_id 是最後一部看的劇
  2. 37% 的用戶在 labels_train 的 title_id 出現在他過去看過的劇
  3. 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)來提供給模型學習。特徵分別如下:

  1. 看過幾部不同的劇 (number of titles)
  2. 總資料列數 (total session number)
  3. 在各個月份觀看的次數 (session number per month)
  4. 最後一部看什麼 (last title id)
  5. 每一部花觀看的總時間 (session length per title)
  6. 每一部劇看幾次 (session number per title)

總共特徵數在處理完(one-hot-encoding)後,大約為 1000 個。

此處所使用的分類模型是 XGBoost,由於時間因素,並沒有對於參數做過多的調整,只單純用過去使用的經驗設定一組參數來訓練模型。整個訓練的過程大致如下:

  1. 透過分層抽樣後的交叉驗證 ( cross validation ),決定 nround 參數。並將 out-of-fold cv prediction 存起來(以利後續使用)。
  2. 透過所有訓練資料訓練模型。
  3. 對測試資料預測,並取出前三名預測機率值最高的劇。

結合轉移矩陣和分類模型結果 (PB Leaderboard: 0.28562)

由於在設計成分類問題時,有一類為「其他」,因此若預測結果為「其他」的那些樣本,想透過轉移矩陣的答案來補強。以下分別介紹步驟跟舉例:

  1. 資料格式:每個用戶會有四個預測值,分別是「轉移矩陣預測的劇」、「分類模型預測第一名的劇」、「分類模型預測第二名的劇」和「分類模型預測第三名的劇」,其中透過分類模型預測出的劇有機率值。
  2. 在「預測機率值」跟「轉移矩陣」結果中取得一個平衡,因為整體來說,除了第一名的機率值外,第二和第三機率值的分布差異很大,有些機率值很高,代表信心很高,但有些機率值都偏低,代表信心不足。因此要針對第二到第三名的機率值分布,各自找出各自的機率門檻值 ( threshold )。而因為第一名的機率值普遍較高,因此只要第一名的預測結果不是「其他」類別,就將預測第一名的劇作為預測答案。
資料示意圖

在實作方面,透過先前的 out-of-fold prediction 來作為訓練資料的預測結果,並同樣利用轉移矩陣來取出每個訓練資料樣本中的預測值,並針對由模型選出的第二名跟第三名,各自從 0 ~ 1 的區間設定一組數值當作門檻值,使得整體的預測命中率最高(各自選出的數值為 0.11, 0.095)。範例如下:

  1. 先做出兩組向量,皆為取介於 0 ~ 1 之間的數。例:thld_vec_1 = thld_vec_2 = (0, 0.05, 0.1, 0.15, …, 0.90, 0.95, 1)。
  2. 對第 i 個用戶,我們會有「轉移矩陣的預測結果」、「模型選出機率最高的劇」、「模型選出機率次高的劇」以及「模型選出機率第三高的劇」。
  3. 先設定一組數值,分別為 thld_1 跟 thld_2。 其中 thld_1 和 thld_2 為從 thld_vec_1 和 thld_vec_2 取出的一組組合。
  4. 若模型推出的「機率最高的劇」不為「其他」,則推選該劇為答案。若為其他,則往下看「機率次高的劇」。
  5. 若「機率次高的劇」不是「其他」,且「機率次高的劇」的機率值大於 thld_1,則取其為答案;若否,則再往下看「機率第三高的劇」。
  6. 若「機率第三高的劇」不是「其他」,且「機率第三高的劇」的機率值大於 thld_2,則取其為答案;若否,則取「轉移矩陣」的結果。
  7. 目標為找到一組 thld_1 和 thld_2 的組合,使得整體的準確度最高。

透過結合轉移矩陣跟分類模型,在 PB Leaderboard 上取得 0.28562,PV Leaderboard: 28811

心得

以上敘述為針對這次比賽的過程分享。從分數的進展發現,做了多方調整後,增加了約1% (0.27 to 0.28) 的準確度,雖然進步幅度不大,在時間成本上很不划算,但透過模型的方式,確實找到了一些人工比較沒辦法找到的規則,再加上因為時間有限,所以這次並沒有在調整參數上花很多心力,而這部分對於模型的成效是有很大的影響力。另外因為這次提供的資料只有瀏覽紀錄,若再加入像是劇的相關資訊 (meta data),用人工的方式找規則就會更加困難,而這些都是可以透過模型的方式來找出關聯性。

分析資料的方式有很多種,歡迎大家有任何想法都可以跟我一起討論,進而找出資料中更多還沒被發掘的資訊:)

補充

Model Structure

--

--