В этом посте мы рассмотрим еще один способ создания гиперболических вложений. Этот подход хорош, когда у вас есть набор точек данных, и для каждой точки данных у вас есть соответствующие «положительные» точки данных и «отрицательные» точки данных. Положительные точки данных — это точки данных, которые вы хотите расположить близко друг к другу в пространстве встраивания, а отрицательные точки данных — это точки данных, которые вы хотите отдалить. Этот алгоритм отлично подходит для иерархических данных и описан в книге «Вложения Пуанкаре для изучения иерархических представлений» Никеля и Кейлы [1]. Он особенно хорошо работает для встраивания в небольшие измерения, поэтому может значительно увеличить время обучения и подходит для данных со скрытой иерархией.

Этот набор данных точек данных, которые имеют «положительную» связь, также можно рассматривать как график, независимо от того, являются ли узлы точками данных, а ребра соединяют узлы, которые положительно связаны.

Это означает, что с помощью этого подхода можно встроить широкий спектр данных, включая, помимо прочего, слова, предложения, геномы, сети социальных сетей и многое другое.

Теперь вопрос в том, как мне встроить свои данные с помощью этого алгоритма? Мы покажем вам, как это сделать, на примере wordnet. Сначала мы помещаем наши данные в разделение X и Y. X — это множество положительных примеров формы 6000x2, так как у нас есть 6000 положительных примеров и два слова на положительный пример. Y имеет форму 6000x15, так как у нас есть 6000 записей и 10 пар отрицательных примеров. Давайте задействуем это, сначала давайте получим набор положительных примеров:

noun_closure = pd.read_csv("data/mammal_closure.csv")
noun_closure.head()

Теперь давайте создадим функцию, которая создает и добавляет отрицательные примеры в набор данных:

def load_wordnet_data(file, negatives=10):
    # Load dataset of positive examples
    noun_closure = pd.read_csv(file)
    noun_closure_np = noun_closure[["id1","id2"]].values
    edges = set()
    for i, j in noun_closure_np:
        edges.add((i,j))
    # Get all unique nouns in the dataset
    unique_nouns = list(set(
        noun_closure["id1"].tolist()+noun_closure["id2"].tolist()
    ))
    # For each positive example lets get 10 negative examples
    neg_examples = {}
    for noun in unique_nouns:
        neg_list = []
        while len(neg_list) < negatives:
            neg_noun = choice(unique_nouns)
            if neg_noun != noun \
            and neg_noun not in neg_list \
            and (noun, neg_noun) not in edges:
                neg_list.append(neg_noun)
        neg_examples[noun] = neg_list
    # Lets add the negative examples to our dataset
    noun_closure["neg_pairs"] = noun_closure["id1"].apply(lambda x:    neg_examples[x])
    return noun_closure, unique_nouns

А теперь вызовите функцию и поместите вывод в набор данных Tensorflow.

# Make training dataset
noun_closure, unique_nouns = load_wordnet_data("data/mammal_closure.csv", negatives=15)
noun_closure_dataset = noun_closure[["id1","id2"]].values
batch_size = 16
train_dataset = tf.data.Dataset.from_tensor_slices((noun_closure_dataset, noun_closure["neg_pairs"].tolist()))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)

Пришло время создать экземпляр нашей модели Hierarchical Embeddings и обучить ее на нашем наборе данных:

from hyperlib.model.pehr import HierarchicalEmbeddings
# Create model
model = HierarchicalEmbeddings(embedding_length=len(unique_nouns), embedding_dim=5, c=1.0, clip_value=0.95)
sgd = keras.optimizers.SGD(learning_rate=5e-3)
# Run custom training loop
model.fit(
    train_dataset,
    epochs=20,
    optimizer=sgd
)

Мы обучали его всего 10 эпох, но давайте посмотрим сейчас, давайте посмотрим на наши встраивания и посмотрим, насколько они хороши. Давайте посмотрим на эмбеддинги, близкие к dog:

vocab = model.get_vocabulary()
embs = model.get_embeddings()
mammal = M.expmap0(model(tf.constant('dog.n.01')), c=1)
dists = M.dist(mammal, embs, c=1.0)
top = tf.math.top_k(-dists[:,0], k=20)
for i in top.indices:
    print(vocab[i])

Хорошо, как насчет летучей мыши:

mammal = M.expmap0(model(tf.constant('bat.n.01')), c=1)
dists = M.dist(mammal, embs, c=1.0)
top = tf.math.top_k(-dists[:,0], k=20)
for i in top.indices:
    print(vocab[i])

Выглядит неплохо, я в восторге! Только в 5 измерениях и 10 эпохах мы могли сгенерировать эти вложения. Если у вас есть набор данных со скрытой иерархией, это может дать вам прирост производительности!

Рекомендации

[1] Вложения Пуанкаре для изучения иерархических представлений. Никель и Кейла