推薦系統公平性論文閱讀(二)

lonelyprince7發表於2021-10-07

接下來我花一天時間精讀了論文《Learning Fair Representations for Recommendation: A Graph-based Perspective》[1],將論文的結構和核心思想進行了詳細地梳理,之後準備使用Pytorch框架對該論文進行復現。

論文創新點

該論文有兩個要點,其一個是使用生成對抗網路(GAN)訓練的濾波器對原始的使用者-物品embeddings向量進行轉換,以除去使用者的敏感資訊(該論文假定原始嵌入演算法不可修改,只能在已經生成的embeddings向量上做轉換);其二是在GAN的優化目標函式(被稱為價值函式)中加入使用者-物品二分圖的資訊,以充分利用使用者和物品的關係。除此之外,該論文通篇都散發著表示學習(representation learning)[2]思想的光輝。

背景知識儲備

因為該論文使用了GAN網路,我先查閱了Goodfellow的論文《Generative Adversarial Nets》\cite{gan}對GAN網路進行了複習。
GAN的主要思想如下:給定樣例資料\(\textbf{x}\),我們想學習得到資料的生成分佈\(p_{data}(\textbf{x})\)。我們定義一個先驗噪聲變數\(p_{\textbf{z}}(\textbf{z})\),再定義生成器\(G(\textbf{z};\theta_{g})\)根據噪聲資料\(z\)產生“偽造”樣本,這裡\(G\)可以是一個由多層感知機(MLP)表示的可微函式,引數為\(\theta_{g}\)。我們訓練判別器\(D(x; \theta_{d})\)指示樣本\(x\)是真實訓練樣本而不是“偽造”樣本的概率。
我們訓練判別器\(D\)極大化\(\log D(x)\)\(log(1-D(G(\textbf{z})))\),以期求“存真去偽”;同時訓練生成器\(G\)來極小化\(\log(1-D(G(\textbf{z})))\),以期求“以假亂真”。也就是說,\(D\)\(G\)進行雙人的極小極大(minimax)博弈,價值函式為:

\[\underset{G}{\mathrm{min}} \quad \underset{D}{\mathrm{max}}V(D, G)= \mathbb{E}_{\textbf{x}\sim p_{data}(\textbf{x})}[\log D(\textbf{x})] + \mathbb{E}_{\textbf{z}\sim p_{\textbf{z}(\textbf{z})}}[\log (1-D(G(\textbf{z})))] \]

接下來是訓練部分,我們設定\(k\)輪迭代。設生成器產生的樣本分佈為\(p_{g}\),我們假定模型會逐漸收斂到全域性最優點,即\(p_{g}=p_{data}\)。每輪迭代我們從噪聲先驗\(p_{\textbf{z}}(\textbf{z})\)\(m\)個噪聲樣本{ \(\textbf{z}^{(1)},\ldots \textbf{z}^{(m)}\) },從資料生成分佈\(p_{data}(\textbf{x})\)中採\(m\)個小批量(minibatch)樣例{\(\textbf{x}_{(1)},\ldots \textbf{x}_{(m)}\)}。
然後我們按照隨機梯度上升的方式更新判別器引數\(\theta_{d}\)

\[ \nabla_{\theta_{d}} \frac{1}{m} \sum_{i=1}^{m}[\log D(\textbf{x}^{(i)})+\log(1-D(G(\textbf{z}^{(i)})))] \]

然後再次從噪聲先驗\(p_{\textbf{z}}(\textbf{z})\)採m個小批量噪聲樣本{ \(\textbf{z}^{(1)},\ldots \textbf{z}^{(m)}\) },並按照隨機梯度下降的方式更新生成器的引數\(\theta_{f}\)

\[ \nabla_{\theta_{g}} \frac{1}{m} \sum_{i=1}^{m} \log (1-D(G(\textbf{z}^{(i)}))) \]

具體的基於梯度的引數更新法則不限,可以使用任何標準的基於梯度的學習法則,原始論文中用的momentum優化演算法(接下來我復現的時候用的Adam優化演算法)。

論文理解與復現

接下來我們開始編寫程式碼並進行實驗。
我們採用的是Lastfm-360K資料集,該資料集包含使用者-物品圖結構的鄰接矩陣。因為我們的演算法主要使用者和物品的嵌入向量上進行轉換,故我們從網上直接下載經過圖卷積網路得到的使用者和物品的embeddings向量。使用者和物品的embeddings向量都以.npy矩陣格式儲存,大小分別為1.5MB和1MB。
接下來我們定義屬性濾波器(做為生成器)和判別器的細節。屬性過濾器我們直接採用多層感知機(MLP)進行實現。該MLP的輸入維度和輸出維度都是使用者的embeddings向量的維度。其輸出可以理解為屬性濾波器力求“偽造”的不含敏感屬性的使用者embeddings,以“騙過”判別器。我們定義了兩個隱藏層,每個隱藏層的啟用函式採用LeakyReLU啟用函式。LeakyReLU實在ReLU啟用函式之上的改進,它在負輸入值段的函式梯度\(\lambda\)是一個取自連續性均勻分佈\(U(l, u)\)概率模型的隨機變數,即

\[f(x)=\left\{ \begin{aligned} x, \quad x>0\\ \lambda x, \quad x \leqslant 0 \end{aligned} \right . \]

其中\(\lambda\sim U(l, u)\)\(l<u\),且\(l,u \in [0,1)\)。這樣可以保證啟用函式在負輸入段也有梯度,可以有效避免梯度消失問題。屬性濾波器的核心網路架構如下:

    self.net = nn.Sequential(
        nn.Linear(self.embed_dim, int(self.embed_dim*2), bias=True),
        nn.LeakyReLU(0.2,inplace=True),
        nn.Linear(int(self.embed_dim*2), self.embed_dim, bias=True), 
        nn.LeakyReLU(0.2,inplace=True),
        nn.Linear(self.embed_dim, self.embed_dim , bias=True),
    )

在判別器方面,因為我們需要訓練出三個不同的屬性濾波器,分別對使用者的性別(gender)、年齡(age)、職業(occupation)這三種敏感屬性進行濾波,所以我們分別定義性別判別器、年齡判別器、職業判別器三種網路,以訓練三種不同的屬性濾波器。判別器也採用MLP實現,其輸入維度維度為使用者embeddings向量的維度,輸出維度是一個softmax概率分佈,指示樣本屬於屬性空間中各類別的概率,據此來判定屬性濾波器所生成濾波後樣本的質量。
我們設輸入的使用者或物品的embeddins向量為\(\textbf{e}_{i}\),生成器為\(\mathcal{F}\),判別器為\(\mathcal{D}\),這樣我們可以定義地\(k\)個濾波器在第\(i\)個使用者向量的輸出是\(\mathcal{F}(\textbf{e}_{i})\)\(\mathcal{F}(\textbf{e}_{i})\)由呼叫濾波器類的forward方法返回。判別器的核心網路架構如下,以使用者性別屬性判別器為例:(注:softmax函式在forward方法中呼叫)

    self.net = nn.Sequential(
        nn.Linear(self.embed_dim, int(self.embed_dim/4), bias=True),
        nn.LeakyReLU(0.2,inplace=True),
        nn.Linear(int(self.embed_dim/4), int(self.embed_dim/8), bias=True), 
        nn.LeakyReLU(0.2,inplace=True),
        nn.Linear(int(self.embed_dim /8), self.out_dim , bias=True),
        nn.LeakyReLU(0.2,inplace=True),
        nn.Linear(self.out_dim, self.out_dim , bias=True)
    )

我們定義InforMax類做為我們的GAN模型的總體架構,該類的主要目的是計算屬性濾波器和判別器的價值函式。InforMax類中包含我們做為屬性濾波器的三個Filter(對於使用者的性別、年齡、性格)和對應的三個做為判別器的Discriminator。最終對於嵌入向量\(e_{i}\)我們計算其濾波後的向量為\(f_{i}=\sum_{k=1}^{K}\mathcal{F}^{k}(e_{i})/K\)。然後我們再考慮到使用者-物品圖結構的資訊,可以定義價值函式\(V_{R}\)表示為訓練樣本資料中rating(使用者的評分等級)分佈的對數似然,價值函式\(V_{G}\)表示成我們預測的屬性分佈的對數似然,如下所示:

\[ V_{R}=\mathbb{E}_{(u,v,r,x_u)\sim p(\textbf{E}, \textbf{R}, \textbf{X})}[\ln q_{\mathcal{R}}(r|(\textbf{f}_{u}, \textbf{f}_{v})=\mathcal{F}(\textbf{e}_u, \textbf{e}_v))] \]

\[ V_{G}=\mathbb{E}_{(u,v,r,x_u)\sim p(\textbf{E}, \textbf{R}, \textbf{X})}[\ln q_{\mathcal{D}}(x|(\textbf{f}_{u}, \textbf{p}_{u})=\mathcal{F}(\textbf{e}_u, \textbf{e}_v))] \]

這個類的forward方法中同時返回GAN中的價值函式\(V_{G}\)和價值函式\(V_{R}\)。核心部分程式碼如下:

    w_f=[1,2,1]
    d_loss = (
        d_mask[0]*d_loss1*w_f[0]+ d_mask[1]*d_loss2*w_f[1]
        + d_mask[2]*d_loss3*w_f[2])
    d_loss_local = (
        d_mask[0]*d_loss1_local*w_f[0]+ d_mask[1]*d_loss2_local*w_f[1]
            + d_mask[2]*d_loss3_local*w_f[2])

    #L_R preference prediction loss.
    user_person_f = user_f2_tmp
    item_person_f = item_f2_tmp

    user_b = F.embedding(user_batch,user_person_f)
    item_b = F.embedding(item_batch,item_person_f)
    prediction = (user_b * item_b).sum(dim=-1)
    loss_part = self.mse_loss(prediction,rating_batch)
    l2_regulization = 0.01*(user_b**2+item_b**2).sum(dim=-1)
    loss_p_square=loss_part+l2_regulization.mean()

    d_loss_all= 1*(d_loss+1*d_loss_local)
    g_loss_all= 10*loss_p_square 
    g_d_loss_all = - 1*d_loss_all
    #the loss needs to be returned.
    d_g_loss = [d_loss_all,g_loss_all,g_d_loss_all]

最後我們定義模型訓練模組。我們定義兩個Adam優化器f_optimizer和d_optimizer分別對最後我們定義模型訓練(優化)模組。在原始論文中,作者定義了生成器\(\mathcal{F}\)和判別器\(\mathcal{D}\)對價值函式\(V(\mathcal{F}, \mathcal{D})\)的極大極小化:

\[\underset{\mathcal{F} }{\mathrm{argmax}} \quad \underset{\mathcal{D}}{\mathrm{argmin}}V(\mathcal{F}, \mathcal{D}) = V_{R} - \lambda V_{G} \]

這裡\(\lambda\)是一個平衡引數來平衡\(V_{R}\)\(V_{G}\)這兩個價值函式。如果\(\lambda\)等於0,那麼我們公平性的需求就消失了。
這裡我們採用兩次遍歷訓練集的方式來實現:
第一次遍歷的每一輪迭代,呼叫InforMax模型的forward方法取得屬性濾波器需要優化的價值函式部分,並呼叫backward方法方向傳播計算梯度後,用Adam優化器分別對屬性濾波器進行優化。
第二次遍歷的每一輪迭代,呼叫Informax模型的forward方法取得判別器需要優化的價值函式部分,並呼叫backward方法反向傳播計算梯度後,呼叫Adam優化器對判別器進行優化。
就這樣,我們採取了兩個優化器分別往正梯度和負梯度方向對判別器和屬性濾波器進行優化。核心部分程式碼如下:

    loss_current = [[],[],[],[]]
    for user_batch, rating_batch, item_batch in train_loader: 
        user_batch = user_batch.cuda()
        rating_batch = rating_batch.cuda()
        item_batch = item_batch.cuda()
        d_g_l_get =  model(copy.deepcopy(pos_adj),user_batch,rating_batch,item_batch)
        d_l,f_l,_ = d_g_l_get
        loss_current[2].append(d_l.item()) 
        d_optimizer.zero_grad()
        d_l.backward()
        d_optimizer.step()
    for user_batch, rating_batch, item_batch in train_loader: 
        user_batch = user_batch.cuda()
        rating_batch = rating_batch.cuda()
        item_batch = item_batch.cuda()
        d_g_l_get = model(copy.deepcopy(pos_adj),user_batch,rating_batch,item_batch)
        _,f_l,d_l = d_g_l_get 
        loss_current[0].append(f_l.item()) 
        f_optimizer.zero_grad()
        f_l.backward()
        f_optimizer.step()

總結

因為該篇論文的模型較為複雜,實現的工程量比較大。目前我只是初步完成了論文核心部分的程式碼編寫,有一些細節還沒有完成,程式碼也還沒有經過除錯。我準備在未來兩天完成程式碼的編寫與除錯。
目前遇到的最大的難點主要在於兩點:
一是理清論文的邏輯思路和各模型組分之間的從屬、先後關係,這要求我們充分把握整個模型的架構,然後再進行模組化程式設計;
二是搞清楚論文中每一個變數的含義。模型的關鍵部分,也就是價值函式部分,原始論文寫得十分含糊,我也是反覆來回咀嚼了幾次原始論文,並在網上查詢了其他人對論文的解讀(這裡又得感謝一下人大趙鑫老師AIBox實驗室的部落格文章),然後才充分理解了論文想表達的價值函式的定義。不過我相信我在這個地方花費的功夫是值得的,因為如果一個深度學習模型的優化函式都不對,那麼顯然它就無法完成我們的任務需求了,甚至南轅北轍。

參考文獻

  • [1] Wu L, Chen L, Shao P, et al. Learning Fair Representations for Recommendation: A Graph-based Perspective[C]//Proceedings of the Web Conference 2021. 2021: 2198-2208.
  • [2] Goodfellow I, Bengio Y, Courville A. Deep learning[M]. MIT press, 2016.
  • [3] Goodfellow I, Pouget-Abadie J, Mirza M, et al. Generative adversarial nets[J]. Advances in neural information processing systems, 2014, 27.

相關文章