一楔绞、基本原理
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)練樣本集合:,其中
玉控,稀疏編碼使用反向傳播算法飞主,并讓目標(biāo)值等于輸入值
,同時中間層維度遠(yuǎn)低于輸入層和輸出層高诺,如下圖所示碌识,這樣就得到了第一層特征壓縮。
簡言之虱而,自編碼神經(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ǎng)絡(luò)第一層將原始輸入轉(zhuǎn)化成為由隱藏單元激活值組成的向量少梁,接著將其作為第二層的輸入,繼續(xù)訓(xùn)練得到第二層的參數(shù)
最后矫付,對后面的各層同樣采用的策略凯沪,即將前層的輸出作為下一層輸入的方式依次訓(xùn)練。
假設(shè)我們用原始輸入 ,訓(xùn)練第一個自編碼器买优,它能夠?qū)W習(xí)得到原始輸入的一階特征表示
妨马,然后再用這些一階特征作為另一個稀疏自編碼器的輸入樟遣,使用它們來學(xué)習(xí)二階特征
,接下來身笤,可以把這些二階特征作為softmax分類器的輸入豹悬,訓(xùn)練得到一個能將二階特征映射到數(shù)字標(biāo)簽的模型。最終液荸,可以將這三層結(jié)合起來構(gòu)建一個包含兩個隱藏層和一個最終softmax分類器層的棧式自編碼網(wǎng)絡(luò)瞻佛。
2. 推薦系統(tǒng)中的應(yīng)用
在推薦系統(tǒng)中,主要使用稀疏編碼的方法娇钱,輸入用戶點擊/收藏/購買數(shù)據(jù)伤柄,訓(xùn)練出物品及用戶的特征向量,具體構(gòu)造自編碼網(wǎng)絡(luò)的方法如下:
輸入層文搂,每首物品的輸入向量為适刀,其中
表示用戶
是否點擊/收藏/購買該物品,輸入矩陣
(包含一個截距項)煤蹭,
為用戶數(shù)量笔喉,
為物品數(shù)量污淋。
輸出層浩螺,指定為和輸出層一致(無截距項)。
隱藏層源哩,強制指定神經(jīng)元的數(shù)量為個稽物,此時隱藏層其實就是物品的低維特征向量奄毡,矩陣為
,
為特征維數(shù)(包含一個截距項1贝或,之所以保留吼过,是為了可以重構(gòu)出輸出層),
為物品數(shù)量咪奖。
隱藏層到輸出層的連接盗忱。一般的神經(jīng)網(wǎng)絡(luò)中,往往會忽略隱藏層到輸出層的連接權(quán)重
的意義赡艰,只是將其作為一個輸出預(yù)測的分類器售淡;但在自編碼網(wǎng)絡(luò)中斤葱,連接層是有實際意義的慷垮。這些權(quán)重作用是將物品特征向量映射到用戶是否聽過/喜歡該物品,其實可就是用戶的低維特征揍堕,所以該稀疏網(wǎng)絡(luò)同樣可以學(xué)習(xí)到用戶的特征矩陣料身。值得注意的是,當(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ù)舉例。
據(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)化為矩陣形式汇鞭,用戶為行,電影為列庸追,每一行為用戶對其看過的電影的評分虱咧,列為每個電影不同用戶的評分,矩陣大小為锚国。
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绘沉。
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)的原本的名稱
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項
參考資料
[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
惟此獨立之精神车伞,自由之思想,歷千萬祀喻喳,與天壤而同久另玖,共三光而永光”砺祝——陳寅恪 題王國維碑