基於Embedding的推薦系統召回策略
推薦系統主要試圖預測user對item的評分或是偏好,通過評分的高低進行鍼對性的推薦。縱觀各網際網路大公司,幾乎都會採用使用到推薦服務,比如:新聞推薦、廣告推薦、商品推薦、書籍推薦等等。本文主要介紹如何使用keras訓練embedding weights進而進行推薦。
前言
關於推薦系統,值得一提的是,推薦系統存在的“冷啟動”問題,關於“冷啟動”,主要分以下三類:
- 使用者冷啟動:即新來一個使用者,如何做個性化推薦;
- 物品冷啟動:即新的物品如何推薦給可能對它感興趣的使用者。
- 系統冷啟動:即如何在一個新開發的網站(沒有使用者,沒有使用者行為,只有部分物品資訊)上設計個性化推薦系統,從而在專案剛釋出時就讓使用者體會到個性化推薦。
一個推薦系統可分為兩個階段:第一、召回階段: 因為單個召回演算法得到的結果一般都很難滿足業務需求,所以通常都採取多路召回方式,如熱門推薦、協同過濾、主題模型、內容召回以及模型召回等;第二、排序階段:對多個召回方法的結果進行統一打分並排序,選出最優Top K。然而本文介紹的方法並不能解決冷啟動問題,但是也算是一種召回策略。關於“冷啟動”,後續再進行深入探討。使用神經網路訓練Embedding的幾個步驟要點:
- 收集資料:神經網路需要大量的訓練樣本;
- 資料處理:根據具體問題將資料按照embedding的場景標準進行處理;
- 訓練weights:建立embedding模型訓練weights;
- 使用weights:使用Embedding weight進行recommendation和visualizations.
下面根據這幾個步驟進行詳細探討,最終將給出一個推薦結果。
資料收集
假設我們獲取的已知資料如下:
- doc的相關資訊,包括:title、content(部分正文)
- user對doc的點選事件。
目的:給使用者推薦 Top N
個沒有閱讀過的doc。
資料處理
Item2vec中把使用者瀏覽的item集合等價於word2vec中的word的序列,即句子(忽略了商品序列空間資訊spatial information) 。出現在同一個集合的 商品 對視為 positive
本文主要介紹如何給已有的使用者進行推薦。使用者點選過一篇doc,說明使用者對doc產生了一定的興趣,當我們把每個doc用實體詞標籤標記之後,就相當於使用者對這些實體詞感興趣[user_id, keywords],其中keywords是用“|”分隔的詞的集合。當我們將使用者與多篇doc關聯起來之後,就可以得到使用者與實體詞的興趣。最後可以使用[user_id, keyword, score]進行標記。
user_keywords.csv檔案內容如下,GitHub連結: user_keywords.csv :
user_idkeywords 113新聞推薦|資訊推薦|內容推薦|文字分類|人工分類|自然語言處理|聚類|分類|冷啟動 143網路|睡眠|精神衰弱|聲音|人工分類 123新年願望|夢想|2018|辭舊迎新 234父母|肩頭|餃子|蔬菜塊|青春叛逆期|聲音 117新聞推薦|內容推薦|文字分類|人工分類|自然語言處理|聚類|分類|冷啟動 119新聞推薦|資訊推薦|人工分類|自然語言處理|聚類|分類|冷啟動 12新聞推薦|資訊推薦|內容推薦|文字分類|聚類|分類|冷啟動 122機器學習|新聞推薦|夢想|人工分類|自然語言處理
首先,我們通過pandas載入資料集
# -*- coding: utf-8 -*- """ Created on Sat Jan5 15:34:34 2019 @author: liudiwei """ import pandas as pd import numpy as np import random from keras.layers import Input, Embedding, Dot, Reshape, Dense from keras.models import Model random.seed(100) #load dataset user_keywords = pd.read_csv("user_keywords.csv")
通過資料處理之後可以得到user_keywords, user_index, keyword_index:
def date_process(user_item): """user_item is a DataFrame, column=[user_id, keywords] 1. user_item: user and item information, user_id, keywords, keyword_index 2. user_index: user to index 3. item_index:item to index """ user_item["keywords"] = user_item["keywords"].apply(lambda x: x.split("|")) keyword_list = [] for i in user_item["keywords"]: keyword_list.extend(i) #word count item_count = pd.DataFrame(pd.Series(keyword_list).value_counts()) # add index to word_count item_count['id'] = list(range(0, len(item_count))) #將word的id對應起來 map_index = lambda x: list(item_count['id'][x]) user_item['keyword_index'] = user_item['keywords'].apply(map_index) #速度太慢 #create user_index, item_index user_index = { v:k for k,v in user_item["user_id"].to_dict().items()} item_index = item_count["id"].to_dict() return user_item, user_index, item_index user_keywords, user_index, keyword_index = date_process(user_keywords)
接下來,需要模擬一個分類或者回歸的場景,用於學習embedding的weights。這裡,我們模擬一個分類場景!
訓練分類模型
注意,這裡主要是模擬場景,並非做真的分類模型。訓練模型的預測結果不是我們的最終目的,我們的目的是得到模型的parameters,即weights,所以,我們不需要真正的care模型結果是否精確。
對於Embedding的weights,主要有三個作用:
top K
這裡注重分析第1點和第3點。下面,先來構建正負樣本。
模擬正負樣本
通過資料構造,我們得到了user與keyword的關係,知道每個user對keyword的喜好程度。為了構建分類模型,這裡我們將已有的user與keyword的對應關係作為正樣本,負樣本通過人工的從user集合與keyword集合進行構造,當出現的user與keyword對不在已有的資料內,我們就將其作為負樣本。
def create_pairs(user_keywords, user_index): """ generate user, keyword pair list """ pairs = [] def doc2tag(pairs, x): for index in x["keyword_index"]: pairs.append((user_index[x["user_id"]], index)) user_keywords.apply(lambda x: doc2tag(pairs, x), axis=1) #速度太慢 return pairs pairs = create_pairs(user_keywords, user_index)
通過create_pairs方法,可以得到user與keyword的pairs。
Embedding模型
Embedding model的五個layer:
- Input: user與keyword同時作為輸入;
- Embedding: 每個user和keyword使用同樣的embedding size;
- Dot: 使用dot product合併embedding;
- Reshape: 將點積Reshape為一個值;
- Dense: 使用sigmoid啟用函式處理output。
def build_embedding_model(embedding_size = 50, classification = False): """訓練embedding模型""" # 輸入為1-D user = Input(name = 'user', shape = [1]) keyword = Input(name = 'keyword', shape = [1]) # user Embedding 設定:(None, 1, 50) user_embedding = Embedding(name = 'user_embedding', input_dim = len(user_index), output_dim = embedding_size)(user) # keyword Embedding設定: (None, 1, 50) keyword_embedding = Embedding(name = 'keyword_embedding', input_dim = len(keyword_index), output_dim = embedding_size)(keyword) # 使用dot進行合併 # (shape will be (None, 1, 1)) merged = Dot(name = 'dot_product', normalize = True, axes = 2)([user_embedding, keyword_embedding]) # Reshape 為1個數 (shape 大小為 (None, 1)) merged = Reshape(target_shape = [1])(merged) # 分類輸出 out = Dense(1, activation = 'sigmoid')(merged) model = Model(inputs = [user, keyword], outputs = out) # 優化方法和loss model.compile(optimizer = 'Adam', loss = 'binary_crossentropy', metrics = ['accuracy']) #print(model.summary()) return model model = build_embedding_model(embedding_size = 20, classification = False)
batch訓練:
def generate_batch(pairs, n_positive = 50, negative_ratio = 1.0): batch_size = n_positive * (1 + negative_ratio) batch = np.zeros((batch_size, 3)) while True: # Randomly choose positive examples for idx, (user_id, keyword_id) in enumerate(random.sample(pairs, n_positive)): batch[idx, :] = (user_id, keyword_id, 1) idx += 1 # Add negative examples until reach batch size while idx < batch_size: # Random selection random_user = random.randrange(len(user_index)) random_keyword = random.randrange(len(keyword_index)) #print(random_user, random_keyword) # Check to make sure this is not a positive example if (random_user, random_keyword) not in pairs: # Add to batch and increment index batch[idx, :] = (random_user, random_keyword, 0) idx += 1 # Make sure to shuffle order np.random.shuffle(batch) yield {'user': batch[:, 0], 'keyword': batch[:, 1]}, batch[:, 2] n_positive = len(pairs) gen = generate_batch(pairs, n_positive, negative_ratio = 1) # Train h = model.fit_generator(gen, epochs = 100, steps_per_epoch = len(pairs) // n_positive)
提取user weight
當模型訓練完之後,我們可以通過下面的方法提取user的weight,提取方法如下:
# Extract embeddings user_layer = model.get_layer('user_embedding') user_weights = user_layer.get_weights()[0]
user_weights是使用者權重集合,每一行表示一個使用者,相當於通過embedding訓練user的向量表示,接下來可以進一步對使用者進行視覺化分析,同時可以通過計算相似度得到每個使用者最相似的K個使用者。
Visualization
對上面訓練得到的weights,通過PCA視覺化,我們可以看到幾個user之間的空間關係,程式碼如下:
from sklearn.decomposition import PCA import seaborn as sns #PCA視覺化 def pca_show(): pca = PCA(n_components=2) pca_result = pca.fit_transform(user_weights) sns.jointplot(x=pca_result[:,0], y=pca_result[:,1]) pca_show()
視覺化效果:
PCA結果:相同的幾個樣本點有一定的聚合性
我們可以看到,有著相同keyword喜好的user呈現出一定的聚合性。
Top K推薦
上面我們假設了每篇doc的keywords就是user對應的keywords,因此,我們可以直接通過計算weights 的cosine相似度進行推薦。
#calculate cosine similarity from sklearn.metrics.pairwise import cosine_similarity cos = cosine_similarity(user_weights[5:6], user_weights) recommendations = cos[0].argsort()[-4:][::-1]
結果為: [5、0、4、6]
,去掉第一個為自己本身,我們可以得到推薦下標為 0
、 4
、 6
的三篇使用者。然後我們可以從這幾個使用者的user behavior裡面,篩選出最近點選的或者最喜歡的doc給使用者119。
下一步
上面的方法主要是找到user的向量表示,類似於user-cf,通過表示向量我們可以計算出使用者最相似的其它使用者,進而進行推薦。下一步的推薦策略還可以進一步擴充套件:
- 如何準確對使用者推薦doc:可以採用user_similarity_score * user_doc_score,然後取top N;
- 訓練基於doc的embedding,對每篇doc進行推薦;
完整的Demo程式碼: recommend.py
References
- neural network embeddings explained
- machine learning cosin similarity for vector space models
- Google Tensorflows Embedding Projector
- Personalized Top-N Sequential Recommendation via Convolutional Sequence Embedding
- Item2vec: Neural Item Embedding for Collaborative Filtering
- Building a Recommendation System Using Neural Network Embeddings