我正在尝试构建一个深度学习预测器,它将一组词向量(在欧几里得空间中)作为输入并输出 Poincaré 嵌入。到目前为止,我运气不佳,因为模型预测的是 n 维实空间中的任意点,而不是双曲空间。这会导致距离,从而导致损失函数未定义。因此我需要以某种方式限制模型的输出。我已经尝试了几件事。
首先是定义最小化双曲距离的损失函数(在庞加莱超圆盘上):
def distance_loss(u, v):
max_norm = 1 - K.epsilon()
sq_u_norm = K.clip(K.sum(K.pow(u, 2), axis=-1), 0, max_norm)
sq_v_norm = K.clip(K.sum(K.pow(v, 2), axis=-1), 0, max_norm)
sq_dist = K.sum(K.pow(u - v, 2), axis=-1)
poincare_dist = tf.acosh(1 + (sq_dist / ((1 - sq_u_norm) * (1 - sq_v_norm))) * 2)
neg_exp_dist = K.exp(-poincare_dist)
return -K.log(neg_exp_dist)
但是,这似乎并不能单独正常工作。接下来是将优化器更改为我从有关该主题的笔记本中提取的内容,以及一些幻灯片 (PDF)。请注意,我使用的是带有 Tensorflow 的 Keras 2.1.6,因此我必须进行一些更改。
def get_normalization(p):
p_norm = K.sum(K.square(p), -1, keepdims=True)
mp = K.square(1 - p_norm)/4.0
return mp, K.sqrt(p_norm)
def project(p, p_norm):
p_norm_clip = K.maximum(p_norm, 1.0)
p_norm_cond = K.cast(p_norm > 1.0, dtype='float') * K.epsilon()
return p/p_norm_clip - p_norm_cond
class AdamPoincare(Adam):
@interfaces.legacy_get_updates_support
def get_updates(self,loss,params):
grads = self.get_gradients(loss, params)
self.updates = [K.update_add(self.iterations, 1)]
lr = self.lr
if self.initial_decay > 0:
lr = lr * (1. / (1. + self.decay * K.cast(self.iterations,
K.dtype(self.decay))))
t = K.cast(self.iterations, K.floatx()) + 1
lr_t = lr * (K.sqrt(1. - K.pow(self.beta_2, t)) /
(1. - K.pow(self.beta_1, t)))
ms = [K.zeros(K.int_shape(p), dtype=K.dtype(p)) for p in params]
vs = [K.zeros(K.int_shape(p), dtype=K.dtype(p)) for p in params]
self.weights = [self.iterations] + ms + vs
for p, g, m, v in zip(params, grads, ms, vs):
normalization, p_norm = get_normalization(p)
g = normalization * g
m_t = (self.beta_1 * m) + (1. - self.beta_1) * g
v_t = (self.beta_2 * v) + (1. - self.beta_2) * K.square(g)
p_t = p - lr_t * m_t / (K.sqrt(v_t) + self.epsilon)
self.updates.append(K.update(m, m_t))
self.updates.append(K.update(v, v_t))
new_p = project(p_t, p_norm)
# Apply constraints.
if getattr(p, 'constraint', None) is not None:
new_p = p.constraint(new_p)
self.updates.append(K.update(p, new_p))
return self.updates
那仍然没有达到我想要的效果,所以最后我尝试添加一个 lambda 层,在前向传递中投射点(尽管我不知道这是否正确)。目标输出已经是双曲空间中的坐标(因此在反向传递中,这应该是无操作的)。
def poincare_project(x, axis=-1):
square_sum = K.tf.reduce_sum(
K.tf.square(x), axis, keepdims=True)
x_inv_norm = K.tf.rsqrt(square_sum)
x_inv_norm = K.tf.minimum((1. - K.epsilon()) * x_inv_norm, 1.)
outputs = K.tf.multiply(x, x_inv_norm)
return outputs
x_dense = Dense(int(params["semantic_dense"]))(x_activation)
x_activation = activation(x_dense)
x_output = Dense(params["semantic_dim"], activation="tanh")(x_activation)
x_project = Lambda(poincare_project)(x_output)
但它仍然会产生垃圾结果(不会最小化距离,或在后续评估中导致 NaN/Inf)。现在,这些实现中的任何一个都可能存在错误,或者整个想法都是无效的。我现在真的说不出来。具体目标是一种有监督的实体链接形式,其中输入是上下文中的目标词(使用预训练的快速文本向量甚至 BERT 嵌入),输出是 Poincare 嵌入中的一个点,表示结构化本体(经过预训练使用 gensim 实现)。
我确实找到了一篇论文(pdf)试图通过重新参数化模型来做到这一点,但我无法从他们的描述中判断如何实现这一点。它确实巧妙地描述了这个问题。