TransE如何进行向量更新?

目录

算法伪代码

SGD中的向量更新

代码实现

关于TransE,博客上各种博文漫天飞,对于原理我就不做重复性劳动,只多说一句,TransE是知识表示算法翻译算法系列中的最基础算法,此处还有TransH、TransD等等;个人觉得翻译算法的叫法是不太合适的,translating,叫做平移或者变换算法可能更加符合作者的原本意图,利用向量的平移不变性去做链路预测。了解原理个人觉得以下两篇足够了:

TransE Embedding

TransE论文知识总结

算法伪代码

《TransE如何进行向量更新?》

合页损失函数

《TransE如何进行向量更新?》

SGD中的向量更新

前辈们的博文中对原理已经说的比较清楚了,但是对于SGD(stochastic gradient descent)随机梯度下降中的向量更新,都几乎没有过多讲解,简单的说一下我的理解。

随机梯度下降我们首先可以简化成梯度下降,随机是体现在sample时的。根据吴恩达老师机器学习的课程梯度下降可知,每一次对θ的更新如下(α是学习率):

《TransE如何进行向量更新?》

线性回归的损失函数如下:

《TransE如何进行向量更新?》

损失函数可以推广到更一般的形式,在TransE中我们的损失函数如下:

《TransE如何进行向量更新?》

对于一般的数学表达式,不管是导数和偏导数我们都比较熟悉。以导数为例

对于一般函数,例如《TransE如何进行向量更新?》,则《TransE如何进行向量更新?》

对于TransE中的损失函数,首先进行简化,去掉前面的两个sigma求和,将Relu神经元用R函数代替,简化成如下形式。

《TransE如何进行向量更新?》

走到这一步我们发现,和上面的线性回归的损失函数已经对应起来了,只不过吴恩达老师课程里的变量名是《TransE如何进行向量更新?》,在这里的变量名是《TransE如何进行向量更新?》,而且这里的变量实际上是一个dim维向量,这个dim是TransE算法中的一个超参数。ok,那么类比于这一个公式《TransE如何进行向量更新?》,我们来试着推导TransE的梯度下降:

《TransE如何进行向量更新?》

以其中第一个公式为例进行推导:

《TransE如何进行向量更新?》

《TransE如何进行向量更新?》

 

首先来看公式7里面的《TransE如何进行向量更新?》,这里的d函数实际上是第一范数,d的取值一定会大于等于0。对于Relu函数,具备如下的函数图像,可以认为在TransE中,《TransE如何进行向量更新?》=1:

《TransE如何进行向量更新?》

再来看《TransE如何进行向量更新?》,因为d和h相关的式子是《TransE如何进行向量更新?》,所以这里《TransE如何进行向量更新?》。这里的的d函数是第一范数L1,一般情况下我们使用第二范数L2(L1范数:向量中各个元素绝对值之和;L2范数:向量中各个元素平方和的开二次方根;Lp范数:向量中各个元素绝对值的p次方和的开p次方根)。这里L2范数是可导的,L1范数并不是处处可导的,因为L1范数简单的来说就是f(x)=|x|,在x=0处是不可导的。但是存在次梯度,拥有次微分,这里的次微分就是我们的《TransE如何进行向量更新?》,详细可见常用的范数求导

《TransE如何进行向量更新?》

  《TransE如何进行向量更新?》《TransE如何进行向量更新?》

所以我们由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_rate
    temp_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 - t
	d_negative = h2 + r - t2
	temp_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_rate

h -= der_h_times_lr
t -= der_t_times_lr
r -= der_r_times_lr
h2 -= der_h2_times_lr
t2 -= der_t2_times_lr

看梯度更新也是下降的:

《TransE如何进行向量更新?》

代码实现

https://github.com/haidfs/TransE

代码简要分析

case1:TrainTransESimple

先实现基本的功能,各项模型内参数与超参如截图所示,可见单进程单线程一次训练一个batch_size为10000的batch,速度非常慢,接近11s(这是在内存128G的Linux服务器上,个人PC会更慢),

《TransE如何进行向量更新?》

case2:TrainTransEMpManager

11s一轮实在说不上快。。在不考虑物理外挂(gpu)的情况下,先考虑使用多进程,最开始不太理解多进程的使用方法,最初的思路是多进程共享变量,将TransE类的变量在多个子进程间传递,于是有了TrainTransEMpManager.py(不建议在个人PC上运行,会非常卡),在这里面将TransE类的实例通过Manager共享。这个速度相比于之前的for循环存在一定的提升,但是不如multiprocessing.Queue()带来的性能提升大。个人理解:如果类比于多线程,每次线程的切入切出总是需要记录上下文信息,大量的线程会造成线程颠簸,带来不必要的开销;Python的多进程应该也是类似,当进程间共享的类的对象存在很多属性,即占用很大的内存空间时,切入和切出同样会带来很大的开销。这样的多进程反而降低了性能,多进程,应该尽可能精简共享的内存大小。在Linux服务器上,同样的batch_size,manger多进程一轮的时间为4.7s,如下:

《TransE如何进行向量更新?》

 

case3:TrainTransEMpQueue

再看多进程实现同样的参数配置,通过multiprocessing的Queue(),每次仅仅共享[batch_size,dim]维大小的向量,和同样的case1相比:速度还是提升了不少,每一轮仅耗时4.3s。运行到1000轮之后,每轮运行时间接近2s。

《TransE如何进行向量更新?》

 

初步训练与测试结果:

可以发现初步结果与论文结果较接近,但是还有一定的差距,等待后续调参再训练。

 FB15k
epochs:2000MeanRankHits@10
rawfilterrawfilter
head320.743192.15229.741.2
tail236.984153.43136.146.2
average 278.863 172.79232.943.7
paper24312534.947.1

《TransE如何进行向量更新?》

 

疑问:

进行测试时由于单个测试例需要利用整个测试集的所有测试例替换头尾实体,在代码里面写了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

    原文作者:暗焰之珩
    原文地址: https://blog.csdn.net/weixin_42348333/article/details/89598144
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞