今天帶來的是對阿里雲天池&Datawhale零基礎入門新聞推薦算法賽事的學習打卡筆記第一部分—賽題理解+baseline。
本文大致介紹了推薦算法的相關背景知識,以及賽事的基本分析。
本文素材來自網絡及datawhale,糾錯指正、深入探討,咱們評論區見。
推薦算法是什麼
推薦算法是算法工程師通過一些數學算法邏輯,基於用戶數據推測出用戶可能喜歡的東西的一種工具,目前應用推薦算法比較好的地方主要是網絡應用。
個性化推薦概念的首次出現是在1995年3月的美國人工智慧協會上,由卡耐基梅隆大學的 Robert Armstrong等提出了個性化導航系統 Web Watcher。同時,史丹福大學的Marko balabanovic等也推出了LIRA——一個個性化推薦系統。自此之後,個性化推薦的研究開始蓬勃發展。【1】
推薦系統的類別
基於內容
基於內容的推薦(Content-based Recommendation)是信息過濾技術的延續與發展,它是建立在項目的內容信息上做出推薦的,而不需要依據用戶對項目的評價意見,更多地需要用機器學習的方法從關於內容的特徵描述的事例中得到用戶的興趣資料。
在基於內容的推薦系統中,項目或對象是通過相關特徵的屬性來定義的,系統基於用戶評價對象的特徵、學習用戶的興趣,考察用戶資料與待預測項目的匹配程度。用戶的資料模型取決於所用的學習方法,常用的有決策樹、神經網絡和基於向量的表示方法等。基於內容的用戶資料需要有用戶的歷史數據,用戶資料模型可能隨著用戶的偏好改變而發生變化。
基於內容的推薦與基於人口統計學的推薦有類似的地方,只不過系統評估的中心轉到了物品本身,使用物品本身的相似度而不是用戶的相似度來進行推薦。
基於內容的推薦算法的優勢在於:對用戶興趣可以很好地建模,並通過對物品屬性維度的增加,獲得更好的推薦精度。而不足之處就在於:物品的屬性有限,很難有效得到更多數據;物品相似度的衡量標準只考慮到了物品本身,有一定的片面性;需要用戶的物品的大量歷史數據。
如何用python實現可參考俺之前分享的文章----分類預測
基於協同
基於協同過濾的推薦算法( Collaborative Filtering Recommendation)技術是推薦系統中應用最早和最為成功的技術之一。它一般採用最近鄰技術,利用用戶的歷史喜好信息計算用戶之間的距離,然後利用目標用戶的最近相鄰用戶對商品評價的加權評價值來預測目標用戶對特定商品的喜好程度,從而根據這一喜好程度來對目標用戶進行推薦。
基於協同過濾的推薦算法最大優點是對推薦對象沒有特殊的要求,能處理非結構化的複雜對象,如音樂、電影。
基於協同過濾的推薦算法是基於這樣的假設:為一用戶找到他真正感興趣的內容的好方法是首先找到與此用戶有相似興趣的其他用戶,然後將他們感興趣的內容推薦給此用戶。其基本思想非常易於理解,在日常生活中,人們往往會利用好朋友的推薦來進行一些選擇。基於協同過濾的推薦算法正是把這一思想運用到電子商務推薦系統中來,基於其他用戶對某一內容的評價來向目標用戶進行推薦。
基於協同過濾的推薦系統可以說是從用戶的角度來進行相應推薦的,而且是自動的,即用戶獲得的推薦是系統從購買模式或瀏覽行為等隱式獲得的,不需要用戶努力地找到適合自己興趣的推薦信息,如填寫一些調查表格等。
基於協同過濾的推薦算法具有如下優點:
1)能夠過濾難以進行機器自動內容分析的信息,如藝術品、音樂等。
2)共享其他人的經驗,避免了內容分析的不完全和不精確,並且能夠基於一些複雜的,難以表述的概念(如信息質量、個人品位)進行過濾。
3)有推薦新信息的能力。可以發現內容上完全不相似的信息,用戶對推薦信息的內容事先是預料不到的。這也是基於協同過濾的推薦算法和基於內容的推薦一個較大的差別,基於內容的推薦很多都是用戶本來就熟悉的內容,而基於協同過濾的推薦可以發現用戶潛在的但自己尚未發現的興趣偏好。
4)能夠有效地使用其他相似用戶的反饋信息,減少用戶的反饋量,加快個性化學習的速度。
如何用python實現可參考俺之前分享的文章----基於親和度的推薦模型
基於關聯規則
基於關聯規則的推薦(Association Rule-based Recommendation)是以關聯規則為基礎,把已購商品作為規則頭,規則體為推薦對象。關聯規則挖掘可以發現不同商品在銷售過程中的相關性,在零售業中已經得到了成功的應用。
關聯規則就是在一個交易資料庫中統計購買了商品集X的交易中有多大比例的交易同時購買了商品集y,其直觀的意義就是用戶在購買某些商品的時候有多大傾向去購買另外一些商品。比如購買牛奶的同時很多人會購買麵包。
算法的第一步關聯規則的發現最為關鍵且最耗時,是算法的瓶頸,但可以離線進行。其次,商品名稱的同義性問題也是關聯規則的一個難點。
如何用python實現可參考俺之前分享的文章----商品推薦原理
基於效用
基於效用的推薦(Utility-based Recommendation)是建立在對用戶使用項目的效用情況上計算的,其核心問題是怎樣為每一個用戶去創建一個效用函數,因此,用戶資料模型很大程度上是由系統所採用的效用函數決定的。
基於效用推薦的好處是它能把非產品的屬性,如提供商的可靠性( Vendor Reliability)和產品的可得性( Product Availability)等考慮到效用計算中。
基於知識
基於知識的推薦( Knowledge-based Recommendation)在某種程度是可以看成是一種推理(Inference)技術,它不是建立在用戶需要和偏好基礎上推薦的。
基於知識的方法因它們所用的功能知識不同而有明顯區別。效用知識
(FunctionalKnowledge)是一種關於一個項目如何滿足某一特定用戶的知識,因此能解釋需要和推薦的關係,所以用戶資料可以是任何能支持推理的知識結構,它可以是用戶已經規範化的查詢,也可以是一個更詳細用戶需要的表示。
組合推薦
由於各種推薦方法都有優缺點,所以在實際中,組合推薦( Hybrid Recommendation)經常被採用。研究和應用最多的是內容推薦和協同過濾推薦的組合。
最簡單的做法就是分別用基於內容的方法和協同過濾推薦方法去產生一個推薦預測結果,然後用某方法組合其結果。儘管從理論上有很多種推薦組合方法,但在某一具體問題中並不見得都有效,組合推薦的一個最重要原則就是通過組合來避免或彌補各自推薦技術的弱點。
在組合方式上,有研究人員提出了七種組合思路。
1)加權(Weight):加權多種推薦技術結果。
2)變換(Switch);根據問題背景和實際情況或要求決定變換採用不同的推薦技術。
3)混合( Mixed):同時採用多種推薦技術給出多種推薦結果,為用戶提供參考。
4)特徵組合(Feature Combination):組合來自不同推薦數據源的特徵被另一種推薦算法所採用。
5)層疊(Cascade):先用一種推薦技術產生一種粗糙的推薦結果,第二種推薦技術在此推薦結果的基礎上進一步做出更精確的推薦。
6)特徵擴充( Feature Augmentation):將一種技術產生附加的特徵信息嵌入另一種推薦技術的特徵輸入中。
7)元級別(Meta-Ievel):用一種推薦方法產生的模型作為另一種推薦方法的輸入。【1】
各類別優缺點比較
比賽相關及Datawhale講義
賽事連結:
https://tianchi.aliyun.com/s/dc6103f06c18ae9e48e9d6cce9785953
講義連結:
http://datawhale.club/t/topic/196
講義內容
賽題理解是切入一道賽題的基礎,會影響後續特徵工程和模型構建等各種工作,也影響著後續發展工作的方向,正確了解賽題背後的思想以及賽題業務邏輯的清晰,有利於花費更少時間構建更為有效的特徵模型, 在各種比賽中, 賽題理解都是極其重要且必須走好的第一步, 今天我們就從賽題的理解出發, 首先了解一下這次賽題的概況和數據,從中分析賽題以及大致的處理方式, 其次我們了解模型評測的指標,最後對賽題的理解整理一些經驗。
賽題簡介
此次比賽是新聞推薦場景下的用戶行為預測挑戰賽, 該賽題是以新聞APP中的新聞推薦為背景, 目的是要求我們根據用戶歷史瀏覽點擊新聞文章的數據信息預測用戶未來的點擊行為, 即用戶的最後一次點擊的新聞文章, 這道賽題的設計初衷是引導大家了解推薦系統中的一些業務背景, 解決實際問題。
數據概況
該數據來自某新聞APP平臺的用戶交互數據,包括30萬用戶,近300萬次點擊,共36萬多篇不同的新聞文章,同時每篇新聞文章有對應的embedding向量表示。為了保證比賽的公平性,從中抽取20萬用戶的點擊日誌數據作為訓練集,5萬用戶的點擊日誌數據作為測試集A,5萬用戶的點擊日誌數據作為測試集B。具體數據表和參數, 大家可以參考賽題說明。下面說一下拿到這樣的數據如何進行理解, 來有效的開展下一步的工作。
評價方式理解
理解評價方式, 我們需要結合著最後的提交文件來看, 根據sample.submit.csv, 我們最後提交的格式是針對每個用戶, 我們都會給出五篇文章的推薦結果,按照點擊概率從前往後排序。而真實的每個用戶最後一次點擊的文章只會有一篇的真實答案, 所以我們就看我們推薦的這五篇裡面是否有命中真實答案的。比如對於user1來說, 我們的提交會是:
user1, article1, article2, article3, article4, article5.
評價指標的公式如下:
假如article1就是真實的用戶點擊文章,也就是article1命中, 則s(user1,1)=1, s(user1,2-4)都是0, 如果article2是用戶點擊的文章, 則s(user,2)=1/2,s(user,1,3,4,5)都是0。也就是score(user)=命中第幾條的倒數。如果都沒中, 則score(user1)=0。這個是合理的, 因為我們希望的就是命中的結果儘量靠前, 而此時分數正好比較高。
賽題理解
根據賽題簡介,我們首先要明確我們此次比賽的目標:根據用戶歷史瀏覽點擊新聞的數據信息預測用戶最後一次點擊的新聞文章。從這個目標上看, 會發現此次比賽和我們之前遇到的普通的結構化比賽不太一樣, 主要有兩點:
首先是目標上, 要預測最後一次點擊的新聞文章,也就是我們給用戶推薦的是新聞文章, 並不是像之前那種預測一個數或者預測數據哪一類那樣的問題數據上, 通過給出的數據我們會發現, 這種數據也不是我們之前遇到的那種特徵+標籤的數據,而是基於了真實的業務場景, 拿到的用戶的點擊日誌所以拿到這個題目,我們的思考方向就是結合我們的目標,把該預測問題轉成一個監督學習的問題(特徵+標籤),然後我們才能進行ML,DL等建模預測。那麼我們自然而然的就應該在心裡會有這麼幾個問題:如何轉成一個監督學習問題呢?轉成一個什麼樣的監督學習問題呢?我們能利用的特徵又有哪些呢?又有哪些模型可以嘗試呢?此次面對數萬級別的文章推薦,我們又有哪些策略呢?
當然這些問題不會在我們剛看到賽題之後就一下出來答案, 但是只要有了問題之後, 我們就能想辦法解決問題了, 比如上面的第二個問題,轉成一個什麼樣的監督學習問題?由於我們是預測用戶最後一次點擊的新聞文章,從36萬篇文章中預測某一篇的話我們首先可能會想到這可能是一個多分類的問題(36萬類裡面選1), 但是如此龐大的分類問題, 我們做起來可能比較困難, 那麼能不能轉化一下?既然是要預測最後一次點擊的文章, 那麼如果我們能預測出某個用戶最後一次對於某一篇文章會進行點擊的概率, 是不是就間接性的解決了這個問題呢?概率最大的那篇文章不就是用戶最後一次可能點擊的新聞文章嗎?這樣就把原問題變成了一個點擊率預測的問題(用戶, 文章) --> 點擊的概率(軟分類), 而這個問題, 就是我們所熟悉的監督學習領域分類問題了, 這樣我們後面建模的時候, 對於模型的選擇就基本上有大致方向了,比如最簡單的邏輯回歸模型。
這樣, 我們對於該賽題的解決方案應該有了一個大致的解決思路,要先轉成一個分類問題來做, 而分類的標籤就是用戶是否會點擊某篇文章,分類問題的特徵中會有用戶和文章,我們要訓練一個分類模型, 對某用戶最後一次點擊某篇文章的概率進行預測。那麼又會有幾個問題:如何轉成監督學習問題?訓練集和測試集怎麼製作?我們又能利用哪些特徵?我們又可以嘗試哪些模型?面對36萬篇文章, 20多萬用戶的推薦, 我們又有哪些策略來縮減問題的規模?如何進行最後的預測?
Baseline
Baseline使用的是簡單的協同過濾,這裡直接寫的代碼,詳細內容可以參考上一期組隊學習推薦系統基礎部分的協同過濾,對應的連結已經放在下面了。
推薦系統組隊學習之協同過濾
導包
# import packagesimport time, math, osfrom tqdm import tqdmimport gcimport pickleimport randomfrom datetime import datetimefrom operator import itemgetterimport numpy as npimport pandas as pdimport warningsimport collectionsfrom collections import defaultdictwarnings.filterwarnings('ignore')
data_path = './data_raw/'save_path = './tmp_results/'
df節省內存函數
# 節約內存的一個標配函數defreduce_mem(df): starttime = time.time() numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64'] start_mem = df.memory_usage().sum() / 1024**2for col in df.columns: col_type = df[col].dtypesif col_type in numerics: c_min = df[col].min() c_max = df[col].max()if pd.isnull(c_min) or pd.isnull(c_max):continueif str(col_type)[:3] == 'int':if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max: df[col] = df[col].astype(np.int8)elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max: df[col] = df[col].astype(np.int16)elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max: df[col] = df[col].astype(np.int32)elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max: df[col] = df[col].astype(np.int64)else:if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max: df[col] = df[col].astype(np.float16)elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max: df[col] = df[col].astype(np.float32)else: df[col] = df[col].astype(np.float64) end_mem = df.memory_usage().sum() / 1024**2 print('-- Mem. usage decreased to {:5.2f} Mb ({:.1f}% reduction),time spend:{:2.2f} min'.format(end_mem, 100*(start_mem-end_mem)/start_mem, (time.time()-starttime)/60))return df
讀取採樣或全量數據
# debug模式:從訓練集中劃出一部分數據來調試代碼defget_all_click_sample(data_path, sample_nums=10000): """ 訓練集中採樣一部分數據調試 data_path: 原數據的存儲路徑 sample_nums: 採樣數目(這裡由於機器的內存限制,可以採樣用戶做) """ all_click = pd.read_csv(data_path + 'train_click_log.csv') all_user_ids = all_click.user_id.unique() sample_user_ids = np.random.choice(all_user_ids, size=sample_nums, replace=False) all_click = all_click[all_click['user_id'].isin(sample_user_ids)] all_click = all_click.drop_duplicates((['user_id', 'click_article_id', 'click_timestamp']))return all_click# 讀取點擊數據,這裡分成線上和線下,如果是為了獲取線上提交結果應該講測試集中的點擊數據合併到總的數據中# 如果是為了線下驗證模型的有效性或者特徵的有效性,可以只使用訓練集defget_all_click_df(data_path='./data_raw/', offline=True):if offline: all_click = pd.read_csv(data_path + 'train_click_log.csv')else: trn_click = pd.read_csv(data_path + 'train_click_log.csv') tst_click = pd.read_csv(data_path + 'testA_click_log.csv') all_click = trn_click.append(tst_click) all_click = all_click.drop_duplicates((['user_id', 'click_article_id', 'click_timestamp']))return all_click
# 全量訓練集all_click_df = get_all_click_df(offline=False)
獲取 用戶 - 文章 - 點擊時間字典
# 根據點擊時間獲取用戶的點擊文章序列 {user1: [(item1, time1), (item2, time2)..]...}defget_user_item_time(click_df): click_df = click_df.sort_values('click_timestamp')defmake_item_time_pair(df):return list(zip(df['click_article_id'], df['click_timestamp'])) user_item_time_df = click_df.groupby('user_id')['click_article_id', 'click_timestamp'].apply(lambda x: make_item_time_pair(x))\ .reset_index().rename(columns={0: 'item_time_list'}) user_item_time_dict = dict(zip(user_item_time_df['user_id'], user_item_time_df['item_time_list']))return user_item_time_dict
獲取點擊最多的Topk個文章
# 獲取近期點擊最多的文章defget_item_topk_click(click_df, k): topk_click = click_df['click_article_id'].value_counts().index[:k]return topk_click
itemCF的物品相似度計算
defitemcf_sim(df): """ 文章與文章之間的相似性矩陣計算 :param df: 數據表 :item_created_time_dict: 文章創建時間的字典 return : 文章與文章的相似性矩陣 思路: 基於物品的協同過濾(詳細請參考上一期推薦系統基礎的組隊學習), 在多路召回部分會加上關聯規則的召回策略 """ user_item_time_dict = get_user_item_time(df)# 計算物品相似度 i2i_sim = {} item_cnt = defaultdict(int)for user, item_time_list in tqdm(user_item_time_dict.items()):# 在基於商品的協同過濾優化的時候可以考慮時間因素for i, i_click_time in item_time_list: item_cnt[i] += 1 i2i_sim.setdefault(i, {})for j, j_click_time in item_time_list:if(i == j):continue i2i_sim[i].setdefault(j, 0) i2i_sim[i][j] += 1 / math.log(len(item_time_list) + 1) i2i_sim_ = i2i_sim.copy()for i, related_items in i2i_sim.items():for j, wij in related_items.items(): i2i_sim_[i][j] = wij / math.sqrt(item_cnt[i] * item_cnt[j])# 將得到的相似性矩陣保存到本地 pickle.dump(i2i_sim_, open(save_path + 'itemcf_i2i_sim.pkl', 'wb'))return i2i_sim_
i2i_sim = itemcf_sim(all_click_df)