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


接下來我花一天時間精讀了論文《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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM