這篇整理 TransD、TransA、TranSparse,也都是之前看過的,比較熟悉。
TransD
paper: Knowledge Graph Embedding via Dynamic Mapping Matrix
論文
本文是自動化所趙軍、劉康老師團隊發表在 ACL 2015 上的工作,D 代表 Dynamic,是針對 TransR 參數量太大做的改進。主要思想是:實體和關系共同構建投影矩陣。具體地,每個實體和關系由兩個向量(meaning vector 和 projection vector)表示,一個表示本身的 embedding,另一個用於構建投影矩陣。每個“實體-關系”對使用的投影矩陣是不同的,頭尾實體單獨投影。
構建投影矩陣
投影矩陣:
投影操作:
三元組打分函數:
歸一化約束和之前的模型一樣,都是 \(\lVert \cdot \rVert_2 \leq 1\)。
Loss:
訓練優化器使用 ADADELTA SGD,並使用 TransE 的訓練結果初始化。
與 TransE、TransH 和 TransR 的關系
- 與 TransE 的關系
當實體、關系維度相同且所有的投影向量設為 0 時,退化為 TransE
- 與 TransH 的關系
當實體、關系維度相同時,TransD 的投影操作可展開為:

而 TransH 的投影操作為:

區別是,TransH 的投影向量僅由關系決定,而 TransD 的投影向量由實體和關系共同決定。
- 與 TransR 的關系
TransR 為每個關系訓練一個投影矩陣 \(M_r\),TransD 通過投影向量動態地構建投影矩陣,沒有“矩陣-向量”相乘的操作,大大減少了參數量和計算量。據原文稱,TransD 比 TransR 快約三倍。
實驗
文章進行了三元組分類和鏈接預測兩個實驗,並通過 case study 說明投影向量的屬性。



TransD 仍然是對打分函數的改進,然后賦予一個合理的物理意義,沒什么新意。感覺 TransE 后續的這些系列模型全是中國人提出來的,總感覺是在蹭熱度。用最新的東西、最新的噱頭,因為跟進的人很少,所以不太可能會有很多人提出質疑,學到了。
TransH、TransR、TransD 都是在投影操作上下文章的,后面的就不是了。
代碼
還是貼 \(Pykg2vec\) 的代碼:
class TransD(PairwiseModel):
def __init__(self, **kwargs):
super(TransD, self).__init__(self.__class__.__name__.lower())
param_list = ["tot_entity", "tot_relation", "rel_hidden_size", "ent_hidden_size", "l1_flag"]
param_dict = self.load_params(param_list, kwargs)
self.__dict__.update(param_dict)
self.ent_embeddings = NamedEmbedding("ent_embedding", self.tot_entity, self.ent_hidden_size)
self.rel_embeddings = NamedEmbedding("rel_embedding", self.tot_relation, self.rel_hidden_size)
self.ent_mappings = NamedEmbedding("ent_mappings", self.tot_entity, self.ent_hidden_size)
self.rel_mappings = NamedEmbedding("rel_mappings", self.tot_relation, self.rel_hidden_size)
nn.init.xavier_uniform_(self.ent_embeddings.weight)
nn.init.xavier_uniform_(self.rel_embeddings.weight)
nn.init.xavier_uniform_(self.ent_mappings.weight)
nn.init.xavier_uniform_(self.rel_mappings.weight)
self.parameter_list = [
self.ent_embeddings,
self.rel_embeddings,
self.ent_mappings,
self.rel_mappings,
]
self.loss = Criterion.pairwise_hinge
def embed(self, h, r, t):
"""Function to get the embedding value.
Args:
h (Tensor): Head entities ids.
r (Tensor): Relation ids of the triple.
t (Tensor): Tail entity ids of the triple.
Returns:
Tensors: Returns head, relation and tail embedding Tensors.
"""
emb_h = self.ent_embeddings(h)
emb_r = self.rel_embeddings(r)
emb_t = self.ent_embeddings(t)
h_m = self.ent_mappings(h)
r_m = self.rel_mappings(r)
t_m = self.ent_mappings(t)
emb_h = self._projection(emb_h, h_m, r_m)
emb_t = self._projection(emb_t, t_m, r_m)
return emb_h, emb_r, emb_t
def forward(self, h, r, t):
"""Function to get the embedding value.
Args:
h (Tensor): Head entities ids.
r (Tensor): Relation ids.
t (Tensor): Tail entity ids.
Returns:
Tensors: the scores of evaluationReturns head, relation and tail embedding Tensors.
"""
h_e, r_e, t_e = self.embed(h, r, t)
norm_h_e = F.normalize(h_e, p=2, dim=-1)
norm_r_e = F.normalize(r_e, p=2, dim=-1)
norm_t_e = F.normalize(t_e, p=2, dim=-1)
if self.l1_flag:
return torch.norm(norm_h_e + norm_r_e - norm_t_e, p=1, dim=-1)
return torch.norm(norm_h_e + norm_r_e - norm_t_e, p=2, dim=-1)
@staticmethod
def _projection(emb_e, emb_m, proj_vec):
# [b, k] + sigma ([b, k] * [b, k]) * [b, k]
return emb_e + torch.sum(emb_e * emb_m, axis=-1, keepdims=True) * proj_vec
TransA
paper: TransA: An Adaptive Approach for Knowledge Graph Embedding
論文
該文是清華大學朱小燕黃民烈老師團隊發表在 AAAI 2015 上的文章, A 代表 Adaptive。主要創新只有一點:將三元組打分函數中的歐氏距離改為馬氏(Mahalanobis)距離(即加權的歐式距離)。論文提出的方法倒沒有什么,倒是其他地方有很多值得學習的地方。
Adaptive Metric 打分函數

上圖為對 TransA 的說明,大意就是,如果按照圖(a)按照歐氏距離的遠近匹配尾實體的話,會得到三個錯誤的答案,因為正確的尾實體按照歐氏距離計算的話會比較遠,只有在比較 x 軸的距離時候,才能推斷出正確的答案。文中說這種情況應該增大 y 軸 loss 的權重,減小 x 軸 loss 的權重,但我覺得反了,想了很久也沒有想明白,可能按照之前模型的打分函數加負號的情況,就能說得通了。總之 point 就是給 embedding 的各個維度賦予不同的權重。
文章的核心貢獻就是這個式子——打分函數:
Loss:

還有一點值得一提的是,為了減少參數量,達到與 TransE 相當的參數量,Wr 由基於三元組的 embedding 計算得出:

實驗



讀完這篇論文,我的感覺就是:這是一篇靠寫出來的論文,文章本身的創新點很小,但是居然能發到頂會上,可能有偶然和外在因素的影響,但就文章本身而言,需要學習的有如下幾點:
(1)論文結構可以模仿。結構是中規中矩、簡潔清晰的“引言-相關工作-方法-實驗-結論”五段式結構。引言部分,針對自己方法,詳細說明了傳統歐氏距離方法的缺陷,令我腦海中冒出一句話:“世上本沒有問題,造也要造出一個問題。”;相關工作將之前的工作分為“Translation-based”及其他兩類,還是很嚴謹的;方法部分,因為對於指標做的小改進非常單薄,所以作者加上了“Perspective from Equipotential Surfaces”和“Perspective from Feature Weighting”兩個角度的解釋,使其看起來有理論支撐;實驗部分,也是做了經典的鏈接預測和三元組分類兩個實驗,沒有其他,但是在結果分析的時候分條解釋的操作看起來很整齊;最后是結論。
(2)有一些比較“聰明”的表述值得學習。比如由示意圖解釋 TransA 可以處理“一對多”關系,因為 TransA 的指標是對稱的,所以也可以處理“多對一”關系,又,因為“多對多”關系可以被視為多個“一對一”關系,因此推斷 TransA 也可以處理“多對多”關系,實驗也證明了這一點。再比如,對於 TransA 在 WN18 的 Mean Rank 沒有達到最優,也給出了合理的解釋,是因為測試集中某些個別的被排在最后的樣本影響了整體的平均值。
雖然文章的貢獻干貨並不大,但是整體讀起來很清爽很舒服,或許這就是能夠被頂會錄用的原因吧。
代碼
TransA 的話,\(Pykg2vec\) 和 \(OpenKE\) 都沒有實現,之前參考的一個大神實現的幾個 KGE 模型上倒是有。
https://github.com/LYuhang/Trans-Implementation/blob/master/code/models/TransA.py
class TransA(nn.Module):
def __init__(self, entityNum, relationNum, embeddingDim, margin=1.0, L=2, lamb=0.01, C=0.2):
super(TransA, self).__init__()
assert (L==1 or L==2)
self.model = "TransE"
self.entnum = entityNum
self.relnum = relationNum
self.enbdim = embeddingDim
self.margin = margin
self.L = L
self.lamb = lamb
self.C = C
self.entityEmbedding = nn.Embedding(num_embeddings=entityNum,
embedding_dim=embeddingDim)
self.relationEmbedding = nn.Embedding(num_embeddings=relationNum,
embedding_dim=embeddingDim)
self.distfn = nn.PairwiseDistance(L)
'''
Normalize embedding
'''
def normalizeEmbedding(self):
pass
'''
Reset Wr to zero
'''
def resetWr(self, usegpu, index):
if usegpu:
self.Wr = torch.zeros((self.relnum, self.enbdim, self.enbdim)).cuda(index)
else:
self.Wr = torch.zeros((self.relnum, self.enbdim, self.enbdim))
def retEvalWeights(self):
return {"entityEmbed": self.entityEmbedding.weight.detach().cpu().numpy(),
"relationEmbed": self.relationEmbedding.weight.detach().cpu().numpy(),
"Wr": self.Wr.detach().cpu().numpy()}
'''
Calculate the Mahalanobis distance weights
'''
def calculateWr(self, posX, negX):
size = posX.size()[0]
posHead, posRel, posTail = torch.chunk(input=posX,
chunks=3,
dim=1)
negHead, negRel, negTail = torch.chunk(input=negX,
chunks=3,
dim=1)
posHeadM, posRelM, posTailM = self.entityEmbedding(posHead), \
self.relationEmbedding(posRel), \
self.entityEmbedding(posTail)
negHeadM, negRelM, negTailM = self.entityEmbedding(negHead), \
self.relationEmbedding(negRel), \
self.entityEmbedding(negTail)
errorPos = torch.abs(posHeadM + posRelM - posTailM)
errorNeg = torch.abs(negHeadM + negRelM - negTailM)
del posHeadM, posRelM, posTailM, negHeadM, negRelM, negTailM
self.Wr[posRel] += torch.sum(torch.matmul(errorNeg.permute((0, 2, 1)), errorNeg), dim=0) - \
torch.sum(torch.matmul(errorPos.permute((0, 2, 1)), errorPos), dim=0)
'''
This function is used to calculate score, steps follows:
Step1: Split input as head, relation and tail index array
Step2: Transform index array to embedding vector
Step3: Calculate Mahalanobis distance weights
Step4: Calculate distance as final score
'''
def scoreOp(self, inputTriples):
head, relation, tail = torch.chunk(input=inputTriples,
chunks=3,
dim=1)
relWr = self.Wr[relation]
head = torch.squeeze(self.entityEmbedding(head), dim=1)
relation = torch.squeeze(self.relationEmbedding(relation), dim=1)
tail = torch.squeeze(self.entityEmbedding(tail), dim=1)
# (B, E) -> (B, 1, E) * (B, E, E) * (B, E, 1) -> (B, 1, 1) -> (B, )
error = torch.unsqueeze(torch.abs(head+relation-tail), dim=1)
error = torch.matmul(torch.matmul(error, torch.unsqueeze(relWr, dim=0)), error.permute((0, 2, 1)))
return torch.squeeze(error)
def forward(self, posX, negX):
size = posX.size()[0]
self.calculateWr(posX, negX)
# Calculate score
posScore = self.scoreOp(posX)
negScore = self.scoreOp(negX)
# Calculate loss
marginLoss = 1 / size * torch.sum(F.relu(input=posScore-negScore+self.margin))
WrLoss = 1 / size * torch.norm(input=self.Wr, p=self.L)
weightLoss = ( 1 / self.entnum * torch.norm(input=self.entityEmbedding.weight, p=2) + \
1 / self.relnum * torch.norm(input=self.relationEmbedding.weight, p=2))
return marginLoss + self.lamb * WrLoss + self.C * weightLoss
TranSparse
paper: Knowledge Graph Completion with Adaptive Sparse Transfer Matrix
論文
這篇文章是自動化所趙軍、劉康老師團隊發表在 AAAI 2016 上的工作, 和 TransD 的作者是一個人。主要思想是:引入稀疏度解決知識庫的異質性和不平衡性問題。TransD 和 TranSparse 都是從減少參數量出發的。
兩個模型
知識圖譜的異質性(heterogeneity)指某些關系與大量的實體連接,某些關系可能僅與少量實體連接;不平衡性(imbalance)指某些關系的頭實體和尾實體的數量可能差別巨大。TranSparse(share)用於解決異質性問題,TranSparse(separate)用於解決不平衡性問題。
- TranSparse(share)
關系 r 的投影矩陣 \(M_r\) 的稀疏度定義為:

\(N_r\) 指關系 r 連接的實體對的數量,如果越多,則投影矩陣的稀疏度越小,矩陣越密集(即非 0 元素個數越多)。\(N_r\) 表示連接實體對數最多的值。
投影操作:

這里,頭尾實體共享一個投影矩陣。
- TranSparse(separate)
在這種模式下,頭尾實體使用不同的投影矩陣,兩個投影矩陣的稀疏度由下式決定:

其中,\(N_r^l\) 表示 “the number of entities linked by relation r at location l ”(不是很明白=.=),帶 * 表示最大值。
投影操作:

TranSparse(share, separate) 的三元組評分函數都是:
Loss:
據原文表述,TranSparse 的時間和空間復雜度與 TransH、TransD 相當。
實驗
文章進行了三元組分類和鏈接預測實驗。



實驗中對比的 baseline 並不多,TranSparse 自己的模型就占了四行,很多論文是這樣,自己提出的模型有很多個小 trick,可以視為幾個子模型,與 baseline 一齊比較,即使其中任意一個達到 state-of-the-art 的效果,都是成功,看起來會比較好看,在 baseline 比較少的情況下不會使實驗部分看起來很單薄。
代碼
\(Pykg2vec\) 和 \(OpenKE\) 都沒有實現 TranSparse,github 上也沒有找到 PyTorch 版本的實現,只有一個 Tensorflow 版本的,因為對 TensorFlow 不太熟悉,所以沒有細讀代碼,傳送門放在這里,備需:https://github.com/FrankWork/transparse/blob/master/transparse/transparse_model.py
最近看論文比較能看進去了,看的這幾篇經典老論文,發覺比較 easy 了,有如下幾個原因吧:一是因為 TransE 后面的這些模型都是中國人提出來的,看這些人寫的英文可能相對容易一些;二是自己能靜下心來了,這是很重要的一個原因吧,不怕慢,只是去做;還有一個原因就是,自己寫過一篇文章,再去看,會有一些些代入感,可能會有幾分站在作者的角度去思考了,會關注作者怎樣構建這篇文章,關注他/她想要表達什么意思。這很好,只要心靜下來,就能走得很遠很遠。