作者:沈克強 來源:人工智慧學習圈
I 研究背景
推薦系統(Recommender System)是向用戶建議有用物品的軟體工具和技術,它運用數據分析、數據挖掘等技術,實現對用戶瀏覽信息或商品進行智能推薦,是機器學習,尤其是深度學習算法的重要應用場景。
個性化推薦系統是高級的、智能的信息過濾系統(Information Filtering System),它的應用範圍很廣,信息流推薦,電子商務平臺、音樂網站的「猜你喜歡」功能都是個性化推薦系統的實際應用案例。推薦系統通過對用戶行為和商品屬性進行分析、挖掘,發現用戶的個性化需求與興趣特點,將用戶可能感興趣的信息或商品推薦給用戶。推薦系統不同於搜尋引擎根據用戶需求被動返回信息的運行過程,它根據用戶歷史行為主動為用戶提供精準的推薦信息。
II 傳統推薦方法
傳統的推薦方法包括基於內容的推薦(Content-based Recommendation)、協同過濾推薦(Collaborative Filtering Recommendation)和混合推薦(Hybrid Recommendation)三種,接下來我們分別分析各種推薦方法。
基於內容的推薦(Content-based Recommendation)
基於內容的推薦系統通過分析一系列用戶之前已評分物品的文檔和(或)描述,從而基於用戶已評分對象的特徵建立模型或個人信息。個人信息是用戶興趣的結構化描述,並且被應用在推薦新的感興趣的物品中。推薦的主要處理過程是將用戶個人信息的特徵和內容對象的特徵相匹配,結果就是用戶對某個對象感興趣程度的評價。
目標函數的優化使用線性回歸模型,對何:個用戶而言,該線性回歸模型的成本函數為預測誤差(預測評分和真實評分的差值)的平方和,再加上正則化項:
構建一個推薦系統,需要預測所有用戶對不同電影的喜好,因此推薦系統需要學習優化所有用戶的電影類型喜好參數 向量,推薦系統模型的全局成本函$等於毎個用戶對應的線性回歸模型$成$函數之和:
2.選梯度下降算法迭代優化H標函數。因為當/c = 0時,參數向量對應特徵向量中的偏置項, 該項是人為設定的固定值,不耑要正則化,所以根據fc是否等於0分別列出迭代公式如下:
協同過濾推薦(Collaborative Filtering Recommendation)
協同過濾推薦方法基於用戶對商品的評分或其他行為(如購買)模式來為用戶提供個性化的推薦,而不需要了解用戶或者商品的大量信息。這種方法是找到與用戶有相同品味的用戶,然後將相似用戶過去喜歡的物品推薦給用戶。
協同過濾推薦是應用最廣泛的推薦方法之一,它可以分為多個子類:
基於用戶 (User-Based)的推薦
基於物品(Item-Based)的推薦
基於社交網絡關係(Social-Based)的推薦
基於模型(Model-based)的推薦……
在上一節基於內容的推薦方法中,推薦系統根據每部電影的類型特徵值學習了表徵用戶對不同類型電影喜好的參數向量,預測了用戶對於電影的評分並依此進行推薦。但是在現實應用中,得到推薦系統電影資料庫中所有電影的類型特徵值是很困難的,人為標定這些特徵值費時費力,也容易摻雜主觀因素。如果每部電影的特徵值是未知的,但用戶對不同類型電影喜好的參數向量是已知的,是否可以學習得出每部電影的類型特徵值呢?
1.根據上一節的內容,類比兩種情況的求解思路,可以得出上述問題的目標函數:
由此可知,對於一個電影推薦系統,初始化用戶參數向量0,然後可以迭代求出所有的電影特徵值X 和用戶參數向量0,這就是初始的協同過濾方法的基本思路。如果將己知0求X和己知求0的兩個目標函數進行改進,就可以得到同時學習0和X的0標函數。
2.目標函數
對比己知0求和己知X求0的兩個目標函數,不難發現兩個函數預測誤差平方和項相同,求和限制條件不同,正則化項不同。改進後的聯合學習目標函數使用相同的預測誤差平方和項,改變求和的限制條件,並保留不同的正則化項,融合了兩個目標函數中所有的成本項,具體如下:
3.訓練優化
優化上述目標函數時,首先將X ( 1 )、X(2).........在較小範圍進行隨機初始化,然後應用梯度下降算法迭代優化目標函數。迭代公式如下:
待目標函數收斂時,迭代完成,(0⑴即為推薦系統的期望輸出(用戶)給電影的評分)。 在上述兩種推薦方法中通過學習得到的特徵矩陣^包含了電影的重要數據信息,有時這些信息隱含著某些不易被人讀 懂的屬性和關係,但是依然需要把特徵矩陣^作為重要的電影推薦依據。例如,如果一位用戶正在觀看電影,推薦 模型可以依據給定的相似性度量方法(例如比較向量之間的歐氏距離),找到相似的電影推薦給該用戶。
混合推薦(Hybrid Recommendation)
混合推薦系統是一種將多個算法或推薦系統單元組合在一起的技術。推薦系統的各個組成部分可以以流水線方式串行連接,也可以並行運行一同輸出結果。在實際工程應用中,多採用組合推薦方法。
組合策略選取的最重要原則就是結合不同算法和模型的優點,並克服它們的缺陷和問題。
加權(Weight):
對多種推薦預測結果加權求和作為最終推薦預測結果。
變換(Switch):
根據不同的問題背景和實際情況,變換選擇不同的推薦方法。
交叉混合(Mixed):
同時採用多種推薦方法給出多種推薦預測結果供用戶參考決策。
特徵組合(Feature combination):
對來自不同推薦數據源的特徵進行組合,再應用到另一種推薦方法中。
層疊(Cascade):
先選用一種推薦方法產生粗糙的推薦預測結果,再在此推薦結果的基礎上使用第二種推薦方法產生更精確的推薦預測結果。
特徵擴充(Feature augmentation):
一種推薦方法產生的特徵信息補充到另一種推薦方法的特徵輸入中。
元級別(Meta-level):
一種推薦方法產生的模型作為另一種推薦方法的輸入。
III YouTube的深度神經網絡推薦系統
YouTube是世界上最大的視頻上傳、分享和發現網站。
YouTube推薦系統為超過10億用戶從不斷增長的視頻庫中推薦個性化的內容,系統由兩個神經網絡組成:
候選生成網絡:從百萬量級的視頻庫中生成上百個候選
排序網絡:對候選進行打分排序,輸出排名最高的數十個結果
候選生成網絡(Candidate Generation Network)
候選生成網絡的核心思想是將推薦問題建模為一個類別數極大的多分類問題。
以YouTube視頻推薦系統為例,對於一個YouTube用戶,可以選用的分類類別包括以下兩類:
歷史行為信息
用戶觀看歷史(視頻ID)
搜索詞記錄(search tokens)
……
用戶屬性信息
人口學信息(如地理位置、用戶登錄設備)
二值特徵(如性別、是否登錄
連續特徵(如用戶年齡)
……
通過上述分類類別,推薦系統對視頻庫中所有視頻分別進行分類,得到每一類別的分類結果(即每一個視頻的推薦概率),最終輸出概率較高的幾百個視頻。
候選生成網絡運行過程
-用戶歷史行為信息映射為向量後取平均值得到固定長度的表示
-輸入用戶屬性信息中的人口學信息,並將二值特徵和連續特徵進行歸一化處理,用以優化新用戶的推薦效果
-將所有特徵表示拼接為一個特徵向量,並輸入給非線形多層感知器(MLP)處理
- MLP的輸出分別流向訓練和預測兩個模塊:在訓練模塊,MLP的輸出流向softmax分類層,與所有視頻特徵一同做分類;在預測模塊,計算MLP的輸出(用戶的綜合特徵)與所有視頻的相似度,取相似度最高的/c個輸出視頻作為 候選生成網絡的預測結果
排序網絡(Ranking Network)
排序網絡的結構類似於候選生成網絡,但是它的優勢是對候選預測結果進行了更細緻的得分計算和結果排序。類比於傳統廣告排序方法中的特徵提取,排序網絡也構造了大量用於視頻排序的相關特徵(如視頻 ID、上次觀看時間等)。
特徵處理與候選生成網絡的不同之處在於排序網絡的輸出端是一個加權邏輯回歸模型,它計算所有候選視頻的預測得分,按分值大小排序後將分值較高的一些視頻推薦給用戶。
IIII 融合推薦系統
融合推薦系統是一個應用深度神經網絡結構的個性化電影推薦系統,它融合了用戶和電影的多項特徵,通過深度神經網絡處理後進行用戶喜愛電影的預測和推薦。
詞向量(word embedding)
詞向量技術是是推薦系統、搜尋引擎、廣告系統等網際網路服務必不可少的基礎技術,多用於這些服務的自然語言處理過程中。
在這些網際網路服務裡,一個常見任務是要比較兩個詞或者兩段文本之間的相關性,要完成這個任務就需要把詞或文字表示成計算機能識別和處理的「語言」,如果在機器學習領域裡選擇問題的解決方法,通常會選擇詞向量模型。
通過詞向量模型可將一個詞語映射為一個維度較低的實數向量(embedding vector),例如:
embedding(情人節)=[0.3,4.2,1.5,…]
embedding(玫瑰花)=[0.2,5.6,2.3,…]
在這組映射得到的實數向量表示中,兩個語義(或用法)上相似的詞對應的詞向量應該「更像」,所以「情人節」和「玫瑰花」這兩個表面看來不相關的詞因其語境的相關性而有較高的相似度。
文本卷積神經網絡(Text Convolutional Neural Networks)
卷積神經網絡可以有效地提取、抽象得到高級的特徵表示。
實踐表明,卷積神經網絡能高效地處理圖像問題和文本問題
卷積神經網絡主要由卷積層(convolution layer)和池化層(pooling layer)組成
其應用及組合方式靈活多變,種類繁多。
在融合推薦模型中,選擇文本卷積神經網絡用於學習電影名稱的表示,網絡結構如圖所示。
系統模型概覽
IIIII PaddlePaddle實現
數據準備
PaddlePaddle在API中提供了自動加載數據的模塊,數據模塊為paddle.dataset.movielens。在原始數據中包含用戶的特徵數據,電影的特徵數據,和用戶對電影的評分。
初始化
import paddle.v2 as paddle
with_gpu = os.getenv('WITH_GPU', '0') != '0'
# 初始化,設置為不使用GPU
paddle.init(use_gpu=with_gpu)
列印某條電影特徵數據
movie_info = paddle.dataset.movielens.movie_info()
print movie_info.values()[0]
顯示列印出的某條電影特徵數據:
<movieinfo id(1),="" title(toy="" story="" ),="" categories(['animation',="" "children's",=""'comedy'])="">模型配置
2.用戶特徵配置
# 讀取用戶編號信息(user_id)
uid = paddle.layer.data(name='user_id', type=paddle.data_type.integer_value(paddle.dataset.movielens.max_user_id() + 1))
# 將用戶編號變換為對應詞向量
usr_emb = paddle.layer.embedding(input=uid, size=32)
# 將用戶編號對應詞向量輸入到全連接層
usr_fc = paddle.layer.fc(input=usr_emb, size=32)
# 讀取用戶性別類別編號信息(gender_id)並做處理(同上)
usr_gender_id = paddle.layer.data(name='gender_id', type=paddle.data_type.integer_value(2))
usr_gender_emb = paddle.layer.embedding(input=usr_gender_id, size=16)
usr_gender_fc = paddle.layer.fc(input=usr_gender_emb, size=16)
# 讀取用戶年齡類別編號信息(age_id)並做處理(同上)
usr_age_id = paddle.layer.data(name='age_id',type=paddle.data_type.integer_value(
len(paddle.dataset.movielens.age_table)))
usr_age_emb = paddle.layer.embedding(input=usr_age_id, size=16)
usr_age_fc = paddle.layer.fc(input=usr_age_emb, size=16)
# 讀取用戶職業類別編號信息(job_id)並做處理(同上)
usr_job_id = paddle.layer.data(
name='job_id',
type=paddle.data_type.integer_value(
paddle.dataset.movielens.max_job_id() + 1))
usr_job_emb = paddle.layer.embedding(input=usr_job_id, size=16)
usr_job_fc = paddle.layer.fc(input=usr_job_emb, size=16)
用戶融合特徵配置
# 所有的用戶特徵再輸入到一個全連接層中,完成特徵融合
usr_combined_features = paddle.layer.fc(input=[usr_fc, usr_gender_fc, usr_age_fc, usr_job_fc],
size=200,act=paddle.activation.Tanh())
電影融合特徵配置
# 讀取電影編號信息(movie_id)
mov_id = paddle.layer.data(
name='movie_id',
type=paddle.data_type.integer_value(
paddle.dataset.movielens.max_movie_id() + 1))
# 將電影編號變換為對應詞向量
mov_emb = paddle.layer.embedding(input=mov_id, size=32)
# 將電影編號對應詞向量輸入到全連接層
mov_fc = paddle.layer.fc(input=mov_emb, size=32)
# 讀取電影類別編號信息(category_id)
mov_categories = paddle.layer.data(
name='category_id',
type=paddle.data_type.sparse_binary_vector(
len(paddle.dataset.movielens.movie_categories())))
# 將電影編號信息輸入到全連接層
mov_categories_hidden = paddle.layer.fc(input=mov_categories, size=32)
# 讀取電影名信息(movie_title)
mov_title_id = paddle.layer.data(name='movie_title',
type=paddle.data_type.integer_value_sequence(len(movie_title_dict)))
# 將電影名變換為對應詞向量
mov_title_emb = paddle.layer.embedding(input=mov_title_id, size=32)
# 將電影名對應詞向量輸入到卷積網絡生成電影名時序特徵
mov_title_conv = paddle.networks.sequence_conv_pool(
input=mov_title_emb, hidden_size=32, context_len=3)
# 所有的電影特徵再輸入到一個全連接層中,完成特徵融合
mov_combined_features = paddle.layer.fc(input=[mov_fc, mov_categories_hidden, mov_title_conv],
size=200,act=paddle.activation.Tanh())
確定cost
使用餘弦相似度計算用戶特徵與電影特徵的相似性,並將這個相似性擬合(回歸)到用戶評分上。cost即為本系統的優化目標,也可認為是本網絡模型的拓撲結構。
# 計算用戶融合特徵和電影融合特徵的餘弦相似度
inference = paddle.layer.cos_sim(
a=usr_combined_features, b=mov_combined_features, size=1, scale=5)
# 定義成本函數為均方誤差函數
cost = paddle.layer.square_error_cost(
input=inference,
label=paddle.layer.data(
name='score', type=paddle.data_type.dense_vector(1)))
參數定義
parameters是模型的所有參數集合,它是一個python的dict數據,存儲這個網絡中的所有參數名稱。
# 利用cost創建parameters
parameters = paddle.parameters.create(cost)
feeding定義
# 數據層和數組索引映射,用於trainer訓練時讀取數據
feeding = {
'user_id': 0,
'gender_id': 1,
'age_id': 2,
'job_id': 3,
'movie_id': 4,
'category_id': 5,
'movie_title': 6,
'score': 7
}
event_handler定義
def event_handler(event):
"""
事件處理器,可以根據訓練過程的信息作相應操作
Args:
event -- 事件對象,包含event.pass_id, event.batch_id, event.cost等信息
Return:
"""
global step
if isinstance(event, paddle.event.EndIteration):
# 每100個batch輸出一條記錄,分別是當前的迭代次數編號,batch編號和對應成本值
if event.batch_id % 100 == 0:
print "Pass %d Batch %d Cost %.2f" % (
event.pass_id, event.batch_id, event.cost)
# 添加訓練數據的cost繪圖數據
train_costs.append(event.cost)
train_step.append(step)
step += 1
if isinstance(event, paddle.event.EndPass):
# 保存參數至文件
with open('params_pass_%d.tar' % event.pass_id, 'w') as f:
trainer.save_parameter_to_tar(f)
# 利用測試數據進行測試
result = trainer.test(reader=paddle.batch(
paddle.dataset.movielens.test(), batch_size=128))
print "Test with Pass %d, Cost %f" % (
event.pass_id, result.cost)
# 添加測試數據的cost繪圖數據
test_costs.append(result.cost)
test_step.append(step)
繪製曲線圖
def plot_costs(train_costs, train_step, test_costs, test_step):
train_costs = np.squeeze(train_costs)
test_costs = np.squeeze(test_costs)
plt.figure()
plt.plot(train_step,train_costs,label="Train Cost")
plt.plot(test_step,test_costs,label="Test Cost")
plt.ylabel('cost')
plt.xlabel('iterations (step)')
plt.title("train-test-cost")
plt.legend()
plt.show()
plt.savefig('train_test_cost.png')
訓練過程定義
trainer.train(
reader=paddle.batch(paddle.reader.shuffle(paddle.dataset.movielens.train(), buf_size=8192),
batch_size=256), event_handler=event_handler_plot, feeding=feeding, num_passes=10)
評分預測
import copy
global PARAMETERS
# 讀取模型參數,'params_pass_9.tar'為第10次迭代訓練出的模型參數
if not os.path.exists('params_pass_9.tar'):
print("Params file doesn't exists.")
return
with open('params_pass_9.tar', 'r') as f:
PARAMETERS = paddle.parameters.Parameters.from_tar(f)
# 定義用戶編號值和電影編號值
user_id = 234
movie_id = 345
# 根據已定義的用戶、電影編號值從movielens數據集中讀取數據信息
user = paddle.dataset.movielens.user_info()[user_id]
movie = paddle.dataset.movielens.movie_info()[movie_id]
# 存儲用戶特徵和電影特徵
feature = user.value() + movie.value()
# 複製feeding值,並刪除序列中的得分項
infer_dict = copy.copy(feeding)
del infer_dict['score']
# 預測指定用戶對指定電影的喜好得分值
prediction = paddle.infer(
output_layer=inference,
parameters= PARAMETERS,
input=[feature],
feeding=infer_dict)
score = (prediction[0][0] + 5.0) / 2
print "[Predict] User %d Rating Movie %d With Score %.2f"%(user_id, movie_id, score)
列印出的預測結果:
[Predict] User 234 Rating Movie 345 With Score 4.46