推薦系統實踐 0x0f AutoRec

NoMornings發表於2020-12-14

從這一篇開始,我們開始學習深度學習推薦模型,與傳統的機器學習相比,深度學習模型的表達能力更強,並且更能夠挖掘出資料中潛藏的模式。另外。深度學習模型結構也非常靈活,能夠根據業務場景和資料結構進行調整。還是原來的樣子,我會按照原理以及程式碼實現,再就是一些優缺點進行逐一介紹。

AutoRec

AutoRec可以說是最小的深度學習推薦系統了,它是一種單隱層神經網路推薦模型,將自編碼器與協同過濾相結合。那麼什麼是自編碼器呢?自編碼器可以看做是一種壓縮維度的工具,無論是影像、音訊、還是文字,都能夠通過自編碼器轉換成向量形式進行表達,假設我們的輸入(無論是影像、音訊等等)的資料向量是\(r\),那麼希望通過自編碼器的輸出向量儘可能接近原來的資料輸入\(r\)

以下是論文原文

Our aim in this work is to design an item-based (user-based) autoencoder which can take as input each partially observed, project it into a low-dimensional latent (hidden) space, and then reconstruct in the output space to predict missing ratings for purposes of recommendation.

假設自編碼器的重建函式是\(h(r;\theta)\),那麼自編碼器的目標函式是:

\[\min_{\theta}\sum_{r\in S}||r-h(r;\theta)||_2^2 \]

其中的\(S\)就是所有資料輸入的向量結合。

一般來說,重建函式\(h(r;\theta)\)的引數量遠遠小於輸入向量的維度,所以自編碼器相當於完成了資料壓縮和降維的工作。並且,通過自編碼器生成的輸出向量,使得自編碼器的編碼過程有一定的泛化能力,可以預測丟失的維度資訊,這也是自編碼器能夠用於推薦系統的原因。

模型結構

在之前的文章中我們介紹了協同過濾的關鍵——共現矩陣。就是因為由\(m\)個使用者以及\(n\)的物品形成的\(m\times n\)的共現矩陣維度太高,所以我們需要使用一個重建函式對共現矩陣裡面的評分向量進行壓縮,然後經過評分預估以及排序之後形成最終的排序列表。AutoRec使用了單隱層神經網路結構來實現自編碼器的功能。如下圖所示。

藍色神經元代表模型的\(k\)維單隱層,也就是壓縮之後的向量,\(V\)以及\(W\)代表從輸入到隱層、從隱層到輸出層的引數矩陣。那麼寫成重建函式的形式就是

\[h(r;\theta)=f(W\cdot g(Vr+\mu)+b) \]

\(f(\cdot)\)以及\(g(\cdot)\)為輸出層和隱層神經元的啟用函式。為了防止重構函式(單隱層神經網路、或者說三層神經網路)的過擬合,再加上\(L2\)正則化項,那麼AutoRec的目標函式就是

\[\min_{\theta}\sum_{r=1}^{n}||r^{(i)}-h(r;\theta)||_O^2+\frac{\lambda}{2}(||W||_{F}^{2}+|V||_{F}^2) \]

\(||\cdot||_F\)為Frobenius範數.

推薦過程

當輸入物品\(i\)的評分向量\(r^{(i)}\)時,得到的模型輸出向量\(h(r;\theta)\)就是所有使用者對物品\(i\)的評分預測。其中第\(u\)維就是使用者\(u\)對物品\(i\)的預測評分\(\hat{R}_{ui}\)。那麼再遍歷一遍物品向量就可以得到該使用者對所有物品的評分預測,然後進行排序就可以得到推薦列表。這種以物品評分向量作為輸入的被稱為I-AutoRec(Item based AutoRec),另外一種就是以使用者評分向量作為輸入的就是U-AutoRec(User based AutoRec)。U-Auto相比較於I-Auto優勢是僅輸入一次目標使用者的使用者向量就可以重建使用者對所有物品的評分向量,也就是說僅需一次推斷就可以得到使用者的推薦列表,但是使用者向量的稀疏性可能會影響模型推薦效果。

侷限性

無法進行特徵交叉,表達能力相對於後面更復雜的深度學習模型還是表達能力不足。由於AutoRec的簡單明瞭,作為入門的深度學習推薦模型再合適不過了。

程式碼

## 模型部分
class Autorec(nn.Module):
    def __init__(self,args, num_items):
        super(Autorec, self).__init__()
        self.args = args
        #self.num_users = num_users
        self.num_items = num_items
        self.hidden_units = args.hidden_units
        self.lambda_value = args.lambda_value
        self.encoder = nn.Sequential(
            nn.Linear(self.num_items, self.hidden_units),
            nn.Sigmoid()
        )
        self.decoder = nn.Sequential(
            nn.Linear(self.hidden_units, self.num_items),
        )
    def forward(self,torch_input):
        encoder = self.encoder(torch_input)
        decoder = self.decoder(encoder)
        return decoder
## 損失函式部分
def loss(self, decoder, input, optimizer, mask_input):
    cost = 0
    temp2 = 0
    cost += ((decoder - input) * mask_input).pow(2).sum()
    rmse = cost
    for i in optimizer.param_groups:
        for j in i['params']:
            # print(type(j.data), j.shape,j.data.dim())
            if j.data.dim() == 2:
                temp2 += torch.t(j.data).pow(2).sum()
    cost += temp2 * self.lambda_value * 0.5
    return cost, rmse

參考

AutoRec: Autoencoders Meet Collaborative Filtering
Github:NeWnIx5991/AutoRec-for-CF

相關文章