推薦系統(tǒng)(四):基于稀疏自編碼的推薦方法

一楔绞、基本原理

1. 自編碼結(jié)構(gòu)

矩陣分解本質(zhì)上只通過一次分解來對原矩陣進(jìn)行逼近结闸,特征挖掘?qū)哟尾粔蛏钊氪蕉遥矝]有運用到物品本身的特征。隨著神經(jīng)網(wǎng)絡(luò)的興起桦锄,多層感知機(jī)可以得到更加深度的特征表示扎附,并對內(nèi)容分類特征加以利用。該方法試圖直接學(xué)習(xí)數(shù)據(jù)的特征集结耀,利用與此特征集相應(yīng)的基向量留夜,將學(xué)習(xí)得到的特征集從特征空間轉(zhuǎn)換到樣本數(shù)據(jù)空間,這樣我們就可以用學(xué)習(xí)得到的特征集重構(gòu)樣本數(shù)據(jù)图甜。其根本是一種數(shù)據(jù)降維的方法碍粥。
大型推薦系統(tǒng),物品的數(shù)量級為千萬黑毅,用戶的數(shù)量級為億嚼摩。所以用戶對物品的打分基本不可能靠離線計算完成,只能依靠在線計算。而在線計算能更快地響應(yīng)最近的事件和用戶交互枕面,但必須實時完成愿卒。這又會限制使用算法的復(fù)雜性和處理的數(shù)據(jù)量。所以個性化推薦實時架構(gòu)的關(guān)鍵問題潮秘,就是如何以無縫方式結(jié)合琼开、管理在線和離線計算過程。使用稀疏編碼進(jìn)行數(shù)據(jù)降維后枕荞,用戶或者物品均可用一組低維基向量表征柜候,便于存儲計算,可供在線層實時調(diào)用买猖。
稀疏自編碼神經(jīng)網(wǎng)絡(luò)是一種無監(jiān)督學(xué)習(xí)算法改橘。假設(shè)我們只有一個沒有帶類別標(biāo)簽的訓(xùn)練樣本集合:\{x^{1},x^2,x^3,\dots\},其中x^i \in R^n玉控,稀疏編碼使用反向傳播算法飞主,并讓目標(biāo)值等于輸入值y^{(i)}=x^{(i)},同時中間層維度遠(yuǎn)低于輸入層和輸出層高诺,如下圖所示碌识,這樣就得到了第一層特征壓縮。

自編碼神經(jīng)網(wǎng)絡(luò)模型

簡言之虱而,自編碼神經(jīng)網(wǎng)絡(luò)嘗試學(xué)習(xí)一個恒等函數(shù)

如果網(wǎng)絡(luò)的輸入數(shù)據(jù)是完全隨機(jī)的筏餐,比如每一個輸入都是一個跟其它特征完全無關(guān)的獨立同分布高斯隨機(jī)變量,那么這一壓縮表示將會非常難學(xué)習(xí)牡拇。但是如果輸入數(shù)據(jù)中隱含著一些特定的結(jié)構(gòu)魁瞪,比如某些輸入特征是彼此相關(guān)的,那么這一算法就可以發(fā)現(xiàn)輸入數(shù)據(jù)中的這些相關(guān)性惠呼。

以上是自編碼最基礎(chǔ)的結(jié)構(gòu)导俘,另外我們也可以用深度學(xué)習(xí)的一些思想,學(xué)習(xí)到高層抽象特征剔蹋。其中一種方法是棧式自編碼旅薄,其采用逐層貪婪訓(xùn)練法進(jìn)行訓(xùn)練。即先利用原始輸入來訓(xùn)練網(wǎng)絡(luò)的第一層泣崩,得到其參數(shù)
W^{(1,1)},W^{(1,2)},b^{(1,1)},b^{(1,2)}

然后網(wǎng)絡(luò)第一層將原始輸入轉(zhuǎn)化成為由隱藏單元激活值組成的向量少梁,接著將其作為第二層的輸入,繼續(xù)訓(xùn)練得到第二層的參數(shù)
W^{(2,1)},W^{(2,2)},b^{(2,1)},b^{(2,2)}

最后矫付,對后面的各層同樣采用的策略凯沪,即將前層的輸出作為下一層輸入的方式依次訓(xùn)練。
假設(shè)我們用原始輸入x^{(k)} ,訓(xùn)練第一個自編碼器买优,它能夠?qū)W習(xí)得到原始輸入的一階特征表示 h^{(1)(k)}妨马,然后再用這些一階特征作為另一個稀疏自編碼器的輸入樟遣,使用它們來學(xué)習(xí)二階特征h^{(2)(k)},接下來身笤,可以把這些二階特征作為softmax分類器的輸入豹悬,訓(xùn)練得到一個能將二階特征映射到數(shù)字標(biāo)簽的模型。最終液荸,可以將這三層結(jié)合起來構(gòu)建一個包含兩個隱藏層和一個最終softmax分類器層的棧式自編碼網(wǎng)絡(luò)瞻佛。

完整的棧式自編碼網(wǎng)絡(luò)

2. 推薦系統(tǒng)中的應(yīng)用

在推薦系統(tǒng)中,主要使用稀疏編碼的方法娇钱,輸入用戶點擊/收藏/購買數(shù)據(jù)伤柄,訓(xùn)練出物品及用戶的特征向量,具體構(gòu)造自編碼網(wǎng)絡(luò)的方法如下:
輸入層文搂,每首物品的輸入向量為(u_1,u_2,u_3,\dots)适刀,其中u_i表示用戶i是否點擊/收藏/購買該物品,輸入矩陣(m+1)\times n(包含一個截距項)煤蹭,m為用戶數(shù)量笔喉,n為物品數(shù)量污淋。
輸出層浩螺,指定為和輸出層一致(無截距項)。
隱藏層源哩,強制指定神經(jīng)元的數(shù)量為k+1個稽物,此時隱藏層其實就是物品的低維特征向量奄毡,矩陣為(k+1)\times nk+1為特征維數(shù)(包含一個截距項1贝或,之所以保留吼过,是為了可以重構(gòu)出輸出層),n為物品數(shù)量咪奖。
隱藏層到輸出層的連接盗忱。一般的神經(jīng)網(wǎng)絡(luò)中,往往會忽略隱藏層到輸出層的連接權(quán)重
W^{(1,1)},W^{(1,2)},b^{(1,1)},b^{(1,2)}

的意義赡艰,只是將其作為一個輸出預(yù)測的分類器售淡;但在自編碼網(wǎng)絡(luò)中斤葱,連接層是有實際意義的慷垮。這些權(quán)重作用是將物品特征向量映射到用戶是否聽過/喜歡該物品,其實可就是用戶的低維特征揍堕,所以該稀疏網(wǎng)絡(luò)同樣可以學(xué)習(xí)到用戶的特征矩陣m\times(k+1)料身。值得注意的是,當(dāng)網(wǎng)絡(luò)結(jié)構(gòu)為3層時衩茸,其目標(biāo)函數(shù)與svd基本一致芹血,算法上是相通的。

二、算法實現(xiàn)

采用GroupLens提供的MovieLens數(shù)據(jù)集幔烛,https://grouplens.org/datasets/movielens/ 啃擦,實現(xiàn)上述算法。

1.數(shù)據(jù)加載

import numpy as np
import pandas as pd

import torch
import torch.nn as nn
import torch.nn.parallel
import torch.optim as optim
import torch.utils.data
from torch.autograd import Variable

training_set = pd.read_csv('ml-100k/u1.base', delimiter='\t', header=None)
test_set = pd.read_csv('ml-100k/u1.test', delimiter='\t', header=None)
training_set = np.array(training_set,dtype='int')
test_set = np.array(test_set,dtype='int')

n_users = int(max(max(training_set[:,0]),max(test_set[:,0])))
n_movies = int(max(max(training_set[:,1]),max(test_set[:,1])))
print('Number of users:{}, number of movies:{}'.format(n_users,n_movies))

training_set饿悬,test_set數(shù)據(jù)的基本格式為userId令蛉,movieId,rating狡恬,timestamp珠叔,下圖為部分?jǐn)?shù)據(jù)舉例。


training_set

test_set

據(jù)統(tǒng)計弟劲,訓(xùn)練集共80000條記錄祷安,測試集共20000條記錄,其中用戶總數(shù)為943兔乞,電影總數(shù)為1682.

Number of users:943, number of movies:1682

2. 數(shù)據(jù)轉(zhuǎn)換

將原始數(shù)據(jù)轉(zhuǎn)化為矩陣形式汇鞭,用戶為行,電影為列庸追,每一行為用戶對其看過的電影的評分虱咧,列為每個電影不同用戶的評分,矩陣大小為943\times 1682锚国。

def convert(data):
    new_data = []
    for id_user in range(1,n_users+1):
        id_movie = data[:,1][data[:,0]==id_user]
        id_rating = data[:,2][data[:,0]==id_user]
        ratings = np.zeros(n_movies)
        ratings[id_movie-1] = id_rating
        new_data.append(list(ratings))
    return new_data

training_set = convert(training_set)
test_set = convert(test_set)
training_set = torch.FloatTensor(training_set)
test_set = torch.FloatTensor(test_set)

進(jìn)而將其轉(zhuǎn)化為Pytorch的FloatTensor的數(shù)據(jù)格式腕巡,結(jié)果如下:

In [3]: training_set
Out[3]: 
tensor([[5., 3., 4.,  ..., 0., 0., 0.],
        [4., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        ...,
        [5., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 5., 0.,  ..., 0., 0., 0.]])
In [4]: test_set
Out[4]: 
tensor([[0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        ...,
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.]])

3. 自編碼網(wǎng)絡(luò)的搭建與實例化

本文的自編碼結(jié)構(gòu)圖如下所示,損失函數(shù)采用MSE血筑,優(yōu)化器采用RMSprop绘沉。


自編碼結(jié)構(gòu)圖
class SAE(nn.Module):
    def __init__(self):
        super(SAE,self).__init__()
        self.fc1 = nn.Linear(n_movies,20)
        self.fc2 = nn.Linear(20,10)
        self.fc3 = nn.Linear(10,20)
        self.fc4 = nn.Linear(20,n_movies)
        
    def forward(self,x):
        x = nn.Sigmoid()(self.fc1(x))
        x = nn.Sigmoid()(self.fc2(x))
        x = nn.Sigmoid()(self.fc3(x))
        x = self.fc4(x)
        return x
    
sae = SAE()
criterion = nn.MSELoss()
optimizer = optim.RMSprop(sae.parameters(),lr=0.01,weight_decay=0.5)

4. 訓(xùn)練及測試過程

代碼的具體含義如注釋所示:

epochs = 200
for epoch in range(1,epochs+1):
    train_loss = 0
    s = 0
    for id_user in range(n_users):  #對于每個用戶
        input = Variable(training_set[id_user]).unsqueeze(0)  # 輸入其電影評分
        target = input.clone()   #目標(biāo)與輸入相同
        if torch.sum(target.data>0)>0:  # 至少有一個評分
            output = sae(input)  #調(diào)用自編碼網(wǎng)絡(luò)
            target.require_grad = False  #目標(biāo)不允許梯度
            output[target==0] = 0   #未評分輸出仍為0 
            loss = criterion(output,target)  #損失函數(shù)
            mean_corrector = n_movies/float(torch.sum(target.data>0) + 1e-10)  #均分
            loss.backward()  #誤差反向傳播
            train_loss += np.sqrt(loss.data*mean_corrector)
            s += 1
            optimizer.step()
    print('epoch:'+str(epoch)+' training loss:'+str(train_loss/s))
    
test_loss = 0
s = 0
for id_user in range(n_users):
    input = Variable(training_set[id_user]).unsqueeze(0)
    target = Variable(test_set[id_user]).unsqueeze(0)
    if torch.sum(target.data>0)>0:
        output = sae(input)
        target.require_grad = False
        output[target==0] = 0
        loss = criterion(output,target)
        mean_corrector = n_movies/float(torch.sum(target.data>0) + 1e-10)
        test_loss += np.sqrt(loss.data*mean_corrector)
        s += 1
print('test loss:'+str(test_loss/s))

訓(xùn)練過程如下:

epoch:1 training loss:tensor(1.7715)
epoch:2 training loss:tensor(1.0967)
epoch:3 training loss:tensor(1.0535)
epoch:4 training loss:tensor(1.0383)
epoch:5 training loss:tensor(1.0310)
epoch:6 training loss:tensor(1.0268)
epoch:7 training loss:tensor(1.0241)
epoch:8 training loss:tensor(1.0219)
epoch:9 training loss:tensor(1.0209)
epoch:10 training loss:tensor(1.0200)
...
epoch:191 training loss:tensor(0.9185)
epoch:192 training loss:tensor(0.9186)
epoch:193 training loss:tensor(0.9181)
epoch:194 training loss:tensor(0.9186)
epoch:195 training loss:tensor(0.9176)
epoch:196 training loss:tensor(0.9184)
epoch:197 training loss:tensor(0.9177)
epoch:198 training loss:tensor(0.9179)
epoch:199 training loss:tensor(0.9170)
epoch:200 training loss:tensor(0.9176)
test loss:tensor(0.9560)

5. 效果預(yù)測

首先找到電影及其對應(yīng)的原本的名稱


movie_title 舉例
movies = pd.read_csv('ml-100k/u.item', sep = '|', engine = 'python', encoding = 'latin-1', header = None)
movie_title = movies.iloc[:n_movies, 1:2]
user_id = 10
user_rating = training_set.data.numpy()[user_id - 1, :].reshape(-1,1)
user_target = test_set.data.numpy()[user_id, :].reshape(-1,1)
user_input = Variable(training_set[user_id]).unsqueeze(0)
predicted = sae(user_input)
predicted = predicted.data.numpy().reshape(-1,1)
result_array = np.hstack([movie_title, user_target, predicted])
result_array = result_array[result_array[:, 1] > 0]
result_df = pd.DataFrame(data=result_array, columns=['Movie', 'Target Rating', 'Predicted'])

進(jìn)一步對第10號用戶,進(jìn)行預(yù)測并只保留非0項


電影名稱豺总、評分及預(yù)測值

參考資料

[1]. 推薦系統(tǒng)與深度學(xué)習(xí). 黃昕等. 清華大學(xué)出版社. 2019.
[2]. 美團(tuán)機(jī)器學(xué)習(xí)實踐. 美團(tuán)算法團(tuán)隊. 人民郵電出版社. 2018.
[3]. 推薦系統(tǒng)算法實踐. 黃美靈. 電子工業(yè)出版社. 2019.
[4]. 推薦系統(tǒng)算法. 項亮. 人民郵電出版社. 2012.
[5]. https://github.com/fredkron/SAE_Recommendation_System
[6]. https://github.com/devalindey/Recommendation-System-with-SAE-using-Pytorch
[7]. https://zhuanlan.zhihu.com/p/33801415

惟此獨立之精神车伞,自由之思想,歷千萬祀喻喳,與天壤而同久另玖,共三光而永光”砺祝——陳寅恪 題王國維碑

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谦去,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蹦哼,更是在濱河造成了極大的恐慌鳄哭,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纲熏,死亡現(xiàn)場離奇詭異妆丘,居然都是意外死亡锄俄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門勺拣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奶赠,“玉大人,你說我怎么就攤上這事药有〕的” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵塑猖,是天一觀的道長竹祷。 經(jīng)常有香客問我,道長羊苟,這世上最難降的妖魔是什么塑陵? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蜡励,結(jié)果婚禮上令花,老公的妹妹穿的比我還像新娘。我一直安慰自己凉倚,他們只是感情好兼都,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著稽寒,像睡著了一般扮碧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杏糙,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天慎王,我揣著相機(jī)與錄音,去河邊找鬼宏侍。 笑死赖淤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的谅河。 我是一名探鬼主播咱旱,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绷耍!你這毒婦竟也來了吐限?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤锨天,失蹤者是張志新(化名)和其女友劉穎毯盈,沒想到半個月后剃毒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體病袄,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡搂赋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了益缠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脑奠。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖幅慌,靈堂內(nèi)的尸體忽然破棺而出宋欺,到底是詐尸還是另有隱情,我是刑警寧澤胰伍,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布齿诞,位于F島的核電站,受9級特大地震影響骂租,放射性物質(zhì)發(fā)生泄漏祷杈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一渗饮、第九天 我趴在偏房一處隱蔽的房頂上張望但汞。 院中可真熱鬧,春花似錦互站、人聲如沸私蕾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽踩叭。三九已至,卻和暖如春翠胰,著一層夾襖步出監(jiān)牢的瞬間懊纳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工亡容, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留嗤疯,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓闺兢,卻偏偏與公主長得像茂缚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子屋谭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容