来自预训练嵌入的 Keras 语义相似度模型

Keras Semantic Similarity model from pre-trained embeddings

我想实现一个 Keras 模型来预测词嵌入中两个句子之间的相似性,如下所示(我在最后包含了我的完整脚本):

  1. 加载词嵌入模型,例如 Word2Vec 和 fastText。
  2. 通过计算句子中所有单词的平均单词向量来生成样本(X1X2)。如果使用两个或多个模型,则计算所有嵌入的算术平均值(Frustratingly Easy Meta-Embedding -- Computing Meta-Embeddings by Averaging Source Word Embeddings)。
  3. X1X2 连接成一个数组,然后再将它们提供给网络。
  4. 编译(并评估)Keras 模型。

整个脚本如下:

import numpy as np
from gensim.models import Word2Vec
from keras.layers import Dense
from keras.models import Sequential
from sklearn.model_selection import train_test_split


def encoder_vector(v: str, model: Word2Vec) -> np.array:
    wv_dim = model.vector_size
    if v in model.wv:
        return model.wv[v]
    else:
        return np.zeros(wv_dim)


def encoder_words_avg(words: list[str], model: Word2Vec) -> np.array:
    dim = model.vector_size
    words = [word for word in words if word in model.wv]
    if len(words) >= 1:
        return np.mean(model.wv[words], axis=0)
    else:
        return np.zeros(dim)


def load_samples(mappings, w2v_model, fast_model):
    dim = w2v_model.vector_size
    num = len(mappings)

    X1 = np.zeros((num, dim))
    X2 = np.zeros((num, dim))
    y = np.zeros((num, 1))

    for i in range(num):
        mapping = mappings[i].split("|")
        sentence_1, sentence_2 = mapping[1:]

        e = np.zeros((2, dim))

        # Compute meta-embedding by averaging all embeddings.
        e[0, :] = encoder_words_avg(words=sentence_1.split(), model=w2v_model)
        e[1, :] = encoder_words_avg(words=sentence_1.split(), model=fast_model)
        X1[i] = e.mean(axis=0)

        e[0, :] = encoder_words_avg(words=sentence_2.split(), model=w2v_model)
        e[1, :] = encoder_words_avg(words=sentence_2.split(), model=fast_model)
        X2[i] = e.mean(axis=0)

        y[i] = 0.0 if mapping[0].startswith("-") else 1.0

    return X1, X2, y


def baseline_model(X_train, X_test, y_train, y_test):
    model = Sequential()
    model.add(
        Dense(
            200,
            input_shape=(X_train.shape[1],),
            activation="relu",
            kernel_initializer="he_uniform",
        )
    )
    model.add(Dense(1, activation="sigmoid"))
    model.compile(optimizer="sgd", loss="binary_crossentropy", metrics=["accuracy"])
    model.fit(X_train, y_train, batch_size=8, epochs=14)

    # Evaluate the trained model, using the train and test data
    _, train_acc = model.evaluate(X_train, y_train, verbose=0)
    _, test_acc = model.evaluate(X_test, y_test, verbose=0)

    print("Train: %.3f, Test: %.3f\n" % (train_acc, test_acc))

    return model


def main():
    w2v_model = Word2Vec.load("")
    fast_model = Word2Vec.load("")

    mappings = [
        "1|boiled chicken egg|hen egg whole boiled",
        "2|tomato|tomato substance",
        "3|sweet potatoes|potato chip",
        "-1|watering plants|cornsalad plant",
        "-2|butter|butane",
        "-3|olive plant|black olives",
    ]

    X1, X2, y = load_samples(mappings, w2v_model=w2v_model, fast_model=fast_model)

    # Concatenate both arrays into one before feeding to the network.
    X = np.concatenate([X1, X2], axis=1)

    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42, stratify=y
    )

    model = baseline_model(X_train, X_test, y_train, y_test)

    model.summary()

上面的脚本似乎有效,但即使仅使用 Word2Vec 时预测结果也很差(这让我觉得 Keras 模型可能存在问题...)。关于如何改善结果的任何想法?我做错了什么吗?

谢谢。

不清楚您打算预测什么。

您是否希望您的 Keras NN 报告 与精确 cosine-similarity 计算相同的 值,在两个文本摘要向量之间,会报告?如果是这样,为什么不只是...做计算?这不是我一定期望 neural-architecture 更好地近似的东西。

或者,如果您的微型 6 对数据集是目标:

  1. 您现有的 'gold standard' 答案对我来说显然不正确。从表面上看,'olive plant' 和 'black olives' 与 'tomato' 和 'tomato substance' 几乎一样 'similar'。同样,'watering plants' & 'cornsalad plant' about-as-similar 为 'sweet potatoes' & 'potato chip'.

  2. 仅 6 个示例(train/test 拆分后可能有 5 个?)都不足以有效地训练更大的神经分类器,分类器可能很容易训练(实际上 'overfit')到 5 个训练示例,它不一定学到任何可推广到一个 hold-out 示例的东西(它使用的向量与训练文本相去甚远). (由于训练数据如此匮乏,并且使用可能与训练数据任意不同的输入进行测试,预计性能会“非常差”。神经网络需要大量不同的训练示例!)

最后,创建 combined-embeddings-by-averaging 的策略,正如您的链接论文所调查的那样,是另一种对我来说似乎可疑的非典型做法。即使它可以提供一些好处,也没有理由将这种非典型的、有点 non-intuitive 的额外练习混合到你的实验中,甚至在使用更典型和更简单的基线方法进行比较之前,为了确保额外的 'meta'/平均是值得的复杂化。

这篇论文本身并没有真正显示出比串联有任何优势,串联比平均具有更强的理论基础(保留每个模型的完整独立空间),除了在 1-of-6 测试中的一小部分。此外,GLoVe 和 CBOW 的平均值在 6 项评估中的 3 项表现 与单独 GLoVe 相同或更差——并且在其他 3 项评估中略好。对我来说,这意味着出色的表现可能主要是额外步骤引入的随机抖动,而平均 - 充其量 - 是一个廉价的选择,可以考虑作为微小的提升,而不是 generally-better 方法。

该论文还留下了许多未解决的自然相关问题:

  • 平均比仅选择每个模型维度的随机一半进行串联更好吗?那会更便宜!
  • 一些任务中的一些轻微提升可能不是由于平均,而是他们应用的其他转换 – l2 归一化应用于每个源模型,或应用于 GLoVE 模型的整个每个维度? (尚不清楚此 model-postprocessing 是否仅在 dual-model 平均之前应用,或者在其单独评估中也应用于 GLoVe。)

还有其他工作表明 post-training word-vector 空间的转换可能会提高下游任务的性能——参见示例 'All But The Top'——所以哪些步骤,确切地说,获得哪些优势对区分。