TransE如何进行向量更新?
目錄
算法偽代碼
SGD中的向量更新
代碼實現
關于TransE,博客上各種博文漫天飛,對于原理我就不做重復性勞動,只多說一句,TransE是知識表示算法翻譯算法系列中的最基礎算法,此處還有TransH、TransD等等;個人覺得翻譯算法的叫法是不太合適的,translating,叫做平移或者變換算法可能更加符合作者的原本意圖,利用向量的平移不變性去做鏈路預測。了解原理個人覺得以下兩篇足夠了:
TransE Embedding
TransE論文知識總結
算法偽代碼
合頁損失函數
SGD中的向量更新
前輩們的博文中對原理已經說的比較清楚了,但是對于SGD(stochastic gradient descent)隨機梯度下降中的向量更新,都幾乎沒有過多講解,簡單的說一下我的理解。
隨機梯度下降我們首先可以簡化成梯度下降,隨機是體現在sample時的。根據吳恩達老師機器學習的課程梯度下降可知,每一次對θ的更新如下(α是學習率):
線性回歸的損失函數如下:
損失函數可以推廣到更一般的形式,在TransE中我們的損失函數如下:
對于一般的數學表達式,不管是導數和偏導數我們都比較熟悉。以導數為例
對于一般函數,例如,則
對于TransE中的損失函數,首先進行簡化,去掉前面的兩個sigma求和,將Relu神經元用R函數代替,簡化成如下形式。
走到這一步我們發現,和上面的線性回歸的損失函數已經對應起來了,只不過吳恩達老師課程里的變量名是,在這里的變量名是',而且這里的變量實際上是一個dim維向量,這個dim是TransE算法中的一個超參數。ok,那么類比于這一個公式,我們來試著推導TransE的梯度下降:
以其中第一個公式為例進行推導:
?
首先來看公式7里面的,這里的d函數實際上是第一范數,d的取值一定會大于等于0。對于Relu函數,具備如下的函數圖像,可以認為在TransE中,=1:
再來看,因為d和h相關的式子是,所以這里。這里的的d函數是第一范數L1,一般情況下我們使用第二范數L2(L1范數:向量中各個元素絕對值之和;L2范數:向量中各個元素平方和的開二次方根;Lp范數:向量中各個元素絕對值的p次方和的開p次方根)。這里L2范數是可導的,L1范數并不是處處可導的,因為L1范數簡單的來說就是f(x)=|x|,在x=0處是不可導的。但是存在次梯度,擁有次微分,這里的次微分就是我們的,詳細可見常用的范數求導。
??
所以我們由789 10四個公式得到,最后的偏導數向量要等于類似[1,1,1,-1,1,-1,...,-1]這樣的一個dim維向量。特別說一下,更新的時候一定要乘上學習率,否則很有可能會不收斂,形成z字形震蕩。
wuxiyu前輩的梯度更新代碼如下所示:
self.loss += eg temp_positive = 2 * self.learning_rate * (t - h - r) temp_negative = 2 * self.learning_rate * (t2 - h2 - r) if self.normal_form == "L1":temp_positive_L1 = [1 if temp_positive[i] >= 0 else -1 for i in range(self.dim)]temp_negative_L1 = [1 if temp_negative[i] >= 0 else -1 for i in range(self.dim)]temp_positive = np.array(temp_positive_L1) * self.learning_ratetemp_negative = np.array(temp_negative_L1) * self.learning_rate# 對損失函數的5個參數進行梯度下降, 隨機體現在sample函數上 h += temp_positive t -= temp_positive r = r + temp_positive - temp_negative h2 -= temp_negative t2 += temp_negative他的代碼是先推L2的導數,根據L2來推L1,也可以寫成這樣的形式,更好理解一些:
if self.normal_form == "L2":temp_positive = 2 * (h + r - t)temp_negative = 2 * (h2 + r - t2) else: # 此處表示使用L1范數d_positive = h + r - td_negative = h2 + r - t2temp_positive = [1 if d_positive[i] >= 0 else -1 for i in range(self.dim)]temp_negative = [1 if d_negative[i] >= 0 else -1 for i in range(self.dim)]# 對損失函數的5個參數進行梯度下降, 隨機體現在sample函數上 # 命名表示嚴格的偏導乘上學習率 der_h_times_lr = np.array(temp_positive) * self.learning_rate der_t_times_lr = -1 * np.array(temp_positive) * self.learning_rate der_r_times_lr = (np.array(temp_positive) - np.array(temp_negative)) * self.learning_rate der_h2_times_lr = -1 * np.array(temp_negative) * self.learning_rate der_t2_times_lr = np.array(temp_negative) * self.learning_rateh -= der_h_times_lr t -= der_t_times_lr r -= der_r_times_lr h2 -= der_h2_times_lr t2 -= der_t2_times_lr看梯度更新也是下降的:
代碼實現
https://github.com/haidfs/TransE
代碼簡要分析
case1:TrainTransESimple
先實現基本的功能,各項模型內參數與超參如截圖所示,可見單進程單線程一次訓練一個batch_size為10000的batch,速度非常慢,接近11s(這是在內存128G的Linux服務器上,個人PC會更慢),
case2:TrainTransEMpManager
11s一輪實在說不上快。。在不考慮物理外掛(gpu)的情況下,先考慮使用多進程,最開始不太理解多進程的使用方法,最初的思路是多進程共享變量,將TransE類的變量在多個子進程間傳遞,于是有了TrainTransEMpManager.py(不建議在個人PC上運行,會非常卡),在這里面將TransE類的實例通過Manager共享。這個速度相比于之前的for循環存在一定的提升,但是不如multiprocessing.Queue()帶來的性能提升大。個人理解:如果類比于多線程,每次線程的切入切出總是需要記錄上下文信息,大量的線程會造成線程顛簸,帶來不必要的開銷;Python的多進程應該也是類似,當進程間共享的類的對象存在很多屬性,即占用很大的內存空間時,切入和切出同樣會帶來很大的開銷。這樣的多進程反而降低了性能,多進程,應該盡可能精簡共享的內存大小。在Linux服務器上,同樣的batch_size,manger多進程一輪的時間為4.7s,如下:
?
case3:TrainTransEMpQueue
再看多進程實現同樣的參數配置,通過multiprocessing的Queue(),每次僅僅共享[batch_size,dim]維大小的向量,和同樣的case1相比:速度還是提升了不少,每一輪僅耗時4.3s。運行到1000輪之后,每輪運行時間接近2s。
?
初步訓練與測試結果:
可以發現初步結果與論文結果較接近,但是還有一定的差距,等待后續調參再訓練。
| MeanRank | Hits@10 | ||
| raw | filter | raw | filter |
| 320.743 | 192.152 | 29.7 | 41.2 |
| 236.984 | 153.431 | 36.1 | 46.2 |
| ?278.863 | ?172.792 | 32.9 | 43.7 |
| 243 | 125 | 34.9 | 47.1 |
?
疑問:
進行測試時由于單個測試例需要利用整個測試集的所有測試例替換頭尾實體,在代碼里面寫了TestTransEMpQueue和TestMainTF兩個版本的測試代碼,但是使用Queue()的多進程效果并不理想,幾乎沒有提升,單個測試例0.4s左右,接近5w個測試例約為5.5小時,速度實在太慢。。。但是不明確為什么,希望有明白的大神可以多多指教。
TestMainTF進行一次測試的耗時為420s左右,約7min。
參考:
https://blog.csdn.net/oBrightLamp/article/details/84326978
https://blog.csdn.net/raby_gyl/article/details/53635459
總結
以上是生活随笔為你收集整理的TransE如何进行向量更新?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TransE,知识图谱嵌入(KGE)论文
- 下一篇: 知识图谱嵌入:TransE算法原理及代码