Explicación y práctica de la red convolucional de gráficos ligeros LightGCN

Reimpreso de: deephub

Los sistemas de recomendación son las tareas de ML más influyentes en la industria actual. Desde Taobao hasta Douyin, las empresas tecnológicas intentan constantemente crear mejores sistemas de recomendación para sus aplicaciones específicas. Y la tarea no se pone más fácil, porque cada día queremos ver más opciones para elegir. Por lo tanto, nuestro modelo no solo debe hacer recomendaciones óptimas, sino también hacer recomendaciones de manera eficiente. El modelo presentado hoy se llama: Light Graph Convolution Network o LightGCN¹.

2836641543047b5b14f5c2f0085eba58.png

Imaginemos a los usuarios y los elementos como nodos en un gráfico bipartito, donde los usuarios están conectados a los elementos que han sido seleccionados. Entonces, el problema de encontrar el mejor elemento de recomendación se convierte en un problema de predicción de enlaces.

conjunto de datos de ejemplo

Como ejemplo práctico, estamos hablando de usuarios que escuchan música y buscan artistas musicales ("elementos"). El conjunto de datos original está disponible en ³.

e063a157afeda7ef248d6bb6c2049d29.png

El conjunto de datos contiene 1824 usuarios, 6854 artistas y 20 664 etiquetas. Un artista común está asociado con aproximadamente 3 usuarios, y un usuario común está asociado con aproximadamente 11 artistas, porque en este conjunto de datos en particular, hay sustancialmente más artistas que usuarios. Una característica de este conjunto de datos es que podemos ver el tiempo de creación de cada nueva conexión, lo cual es muy importante para nosotros porque los datos se pueden dividir en conjunto de entrenamiento (tiempo más temprano) y conjunto de prueba (tiempo más reciente) por tiempo de conexión³. Nuestro objetivo es crear un modelo de sistema de recomendación para predecir nuevas etiquetas/conexiones formadas en el futuro.

Modelos basados ​​en incrustaciones

LightGCN es un modelo basado en incrustaciones, lo que significa que intenta encontrar las mejores incrustaciones (vectores) para usuarios y elementos. Además de esto, también busca la función de puntuación óptima f, que puntúa nuevos elementos de usuario y recomienda aquellos con puntuaciones más altas.

40e0fcb844fe57ed5c4433782afe7ee3.png

Para vectores incrustados, los usuarios con preferencias similares tendrán incrustaciones similares, y los usuarios con preferencias diferentes tendrán más incrustaciones diferentes.

Antes de continuar con el estudio de lightGCN, primero presente brevemente el modelo basado en incrustación, el método de factorización matricial se ha utilizado en los sistemas de recomendación tradicionales durante muchos años y el efecto ha sido muy bueno, por lo que se utilizará como nuestro modelo de referencia:

807356245b90eef0ea1df704347f5851.png

La figura anterior es la relación entre el proceso de factorización matricial (MF) y el gráfico original y la matriz incrustada².

Aquí comparamos el modelo de factorización matricial con el modelo LightGCN como referencia. La función de puntuación f aquí es solo el producto escalar de dos incrustaciones y el modelo entrenado minimizando la norma de Frobenious de la matriz (R - HW), donde la matriz R es la matriz de adyacencia del elemento de usuario y la matriz H contiene incrustaciones de usuario y W contiene elementos Embedded². La función de puntuación f es la misma en el caso de lightGCN, pero para comprender el modelo de manera intuitiva, primero considere cuál es el objetivo de optimización del rendimiento del modelo lightGCN.

Pero, ¿cómo medir el rendimiento?

Una medida de rendimiento popular es tomar todas las ventajas reales de los nuevos usuarios del conjunto de prueba y calcular las K predicciones principales (es decir, con la puntuación más alta f (usuario, elemento)) teniendo en cuenta el modelo. Este puntaje se calcula para cada usuario y luego se promedian los puntajes de todos los usuarios para obtener un puntaje final, llamado Recall@K².

341ab96b71f3d3617258f3e413729c5f.png

Pero la métrica Recall@K no es diferenciable, lo que significa que se debe diseñar una función de pérdida diferenciable para que el entrenamiento del modelo lightGCN pueda usar el gradiente para encontrar el valor óptimo.

El objetivo principal de diseñar esta función de pérdida es que la función de puntuación para el borde positivo futuro resulte en un número mayor, y la función de puntuación del borde negativo futuro resulte en un número más pequeño². Entonces, una mejor manera de combinar estos dos problemas es esperar que la diferencia entre un futuro borde positivo dado para el usuario u y un futuro borde negativo dado para el usuario u sea un número grande:

7084a56930a8029773a31c9db554ab8b.png

Después de usar la función sigmoidea para asignar la diferencia de las dos puntuaciones al intervalo [0, 1], las puntuaciones pueden tratarse como probabilidades. Entonces, para un usuario u dado, las puntuaciones de todos los pares dados de aristas positivas y negativas pueden combinarse e introducirse en la función de pérdida. Estas pérdidas² luego se promedian entre todos los usuarios para obtener la pérdida final, que se denomina pérdida BPR de clasificación personalizada bayesiana:

b773ef28f1b583e869b67fdee9d5db28.png

LuzGCN

Ahora, para llegar al punto de este documento, mientras que los métodos de factorización de matrices solo capturan la estructura de primer orden conectada por los bordes del gráfico (solo la información de los vecinos inmediatos de un nodo dado), queremos que el modelo capture estructura gráfica. Entonces use LightGCN para hacer esto, que comienza a entrenar con incrustaciones de nodos inicializadas con factorización de matriz:

e5b158e7a25af4b662160ad6df7846ca.png

Después de inicializar la incrustación, LightGCN usa 3 capas para completar el entrenamiento de la incrustación, en cada capa, cada nodo obtiene una nueva incrustación al combinar las incrustaciones de sus vecinos. Esto se puede considerar como una especie de convolución de gráfico (consulte a continuación una comparación con la convolución de imagen):

b935be44147f923d5aa1ebcc4afd1cab.gif

La convolución de imágenes (izquierda) puede verse como un caso especial de convolución de gráficos (derecha). La convolución de gráficos es una operación invariable de permutación de nodos.

Como se muestra en la figura anterior, apilar más capas de manera similar a las capas convolucionales significa que la información de un nodo determinado puede obtener información de nodos más alejados de ese nodo, lo que puede capturar la estructura gráfica de orden superior según sea necesario. Pero, ¿cómo se combinan exactamente las incorporaciones en una nueva incorporación en cada iteración k? Aquí hay dos ejemplos:

1f5e57d82338163a7d28e021f6f86d62.png

El gráfico anterior muestra el efecto de las incrustaciones de elementos adyacentes en la incrustación del usuario en la siguiente capa y viceversa¹. El impacto de la incrustación inicial disminuye con cada iteración, ya que puede llegar a más nodos más alejados del origen. Esto es lo que se dice que son incrustaciones de difusión, esta forma particular de difusión también vectoriza y acelera el proceso al construir una matriz de difusión:

296d4d31b365983d1be1d6cfcc350e15.png

Construya una matriz de difusión a partir de una matriz de grados y una matriz de adyacencia².

Dado que la matriz de difusión se calcula a partir de la matriz de grados D y la matriz de adyacencia A, la matriz de difusión solo debe calcularse una vez y no contiene parámetros que se puedan aprender. El único parámetro que se puede aprender en el modelo es la incrustación en el nodo de entrada poco profundo, que se multiplica K veces por la matriz de difusión para obtener (K ​​+ 1) incrustaciones, que luego se promedian para obtener la incrustación final:

15057cf43415002891ea366da6b1ecf2.png

Ahora que entendemos cómo el modelo propaga las incrustaciones de entrada hacia adelante, podemos codificar el modelo usando PyTorch Geometric y luego usar la pérdida de BPR mencionada anteriormente para optimizar las incrustaciones para elementos y usuarios. PyG (PyTorch Geometric) es una biblioteca basada en PyTorch que nos ayuda a escribir y entrenar redes neuronales gráficas (GNN).

import torch

import torch.nn as nn
import torch.nn.functional as F
import torch_scatter
from torch_geometric.nn.conv import MessagePassing


class LightGCNStack(torch.nn.Module):
    def __init__(self, latent_dim, args):
        super(LightGCNStack, self).__init__()
        conv_model = LightGCN
        self.convs = nn.ModuleList()
        self.convs.append(conv_model(latent_dim))
        assert (args.num_layers >= 1), 'Number of layers is not >=1'
        for l in range(args.num_layers-1):
            self.convs.append(conv_model(latent_dim))

        self.latent_dim = latent_dim
        self.num_layers = args.num_layers
        self.dataset = None
        self.embeddings_users = None
        self.embeddings_artists = None

    def reset_parameters(self):
        self.embeddings.reset_parameters()

    def init_data(self, dataset):
        self.dataset = dataset
        self.embeddings_users = torch.nn.Embedding(num_embeddings=dataset.num_users, embedding_dim=self.latent_dim).to('cuda')
        self.embeddings_artists = torch.nn.Embedding(num_embeddings=dataset.num_artists, embedding_dim=self.latent_dim).to('cuda')

    def forward(self):
        x_users, x_artists, batch = self.embeddings_users.weight, self.embeddings_artists.weight, \
                                                self.dataset.batch

        final_embeddings_users = torch.zeros(size=x_users.size(), device='cuda')
        final_embeddings_artists = torch.zeros(size=x_artists.size(), device='cuda')
        final_embeddings_users = final_embeddings_users + x_users/(self.num_layers + 1)
        final_embeddings_artists = final_embeddings_artists + x_artists/(self.num_layers+1)
        for i in range(self.num_layers):
            x_users = self.convs[i]((x_artists, x_users), self.dataset.edge_index_a2u, size=(self.dataset.num_artists, self.dataset.num_users))
            x_artists = self.convs[i]((x_users, x_artists), self.dataset.edge_index_u2a, size=(self.dataset.num_users, self.dataset.num_artists))
            final_embeddings_users = final_embeddings_users + x_users/(self.num_layers+1)
            final_embeddings_artists = final_embeddings_artists + x_artists/(self.num_layers + 1)

        return final_embeddings_users, final_embeddings_artists

    def decode(self, z1, z2, pos_edge_index, neg_edge_index):  # only pos and neg edges
        edge_index = torch.cat([pos_edge_index, neg_edge_index], dim=-1)  # concatenate pos and neg edges
        logits = (z1[edge_index[0]] * z2[edge_index[1]]).sum(dim=-1)  # dot product
        return logits

    def decode_all(self, z_users, z_artists):
        prob_adj = z_users @ z_artists.t()  # get adj NxN
        #return (prob_adj > 0).nonzero(as_tuple=False).t()  # get predicted edge_list
        return prob_adj

    def BPRLoss(self, prob_adj, real_adj, edge_index):
        loss = 0
        pos_scores = prob_adj[edge_index.cpu().numpy()]
        for pos_score, node_index in zip(pos_scores, edge_index[0]):
            neg_scores = prob_adj[node_index, real_adj[node_index] == 0]
            loss = loss - torch.sum(torch.log(torch.sigmoid(pos_score.repeat(neg_scores.size()[0]) - neg_scores))) / \
                   neg_scores.size()[0]

        return loss / edge_index.size()[1]

    def topN(self, user_id, n):
        z_users, z_artists = self.forward()
        scores = torch.squeeze(z_users[user_id] @ z_artists.t())
        return torch.topk(scores, k=n)


class LightGCN(MessagePassing):
    def __init__(self, latent_dim, **kwargs):
        super(LightGCN, self).__init__(node_dim=0, **kwargs)
        self.latent_dim = latent_dim

    def forward(self, x, edge_index, size=None):
        return self.propagate(edge_index=edge_index, x=(x[0], x[1]), size=size)

    def message(self, x_j):
        return x_j

    def aggregate(self, inputs, index, dim_size=None):
        return torch_scatter.scatter(src=inputs, index=index, dim=0, dim_size=dim_size, reduce='mean')

Predicción con LightGCN

PyTorch Geometric también proporciona funciones de entrenamiento para ayudarnos a simplificar el proceso de entrenamiento. Después del entrenamiento, la representación incrustada ahora puede representar que es probable que a los usuarios les gusten elementos similares y tengan preferencias similares. Por lo tanto, las puntuaciones de los nuevos elementos se pueden calcular a partir de las incrustaciones finales devueltas por el modelo para predecir la preferencia de cada usuario por los elementos que aún no han visto. Para cada usuario recomiende los K elementos con mayor puntuación (nuevos para el usuario). Al igual que la factorización de matrices, la función de puntuación f es solo un producto escalar de incrustaciones, calculado de manera eficiente mediante la multiplicación de matrices:

e3bd6de1141caa8a01b4f83353028363.png

El conjunto de prueba también contiene nuevos usuarios que no aparecieron en el conjunto de entrenamiento. Entonces, en este caso, solo recomendamos los elementos K principales que son populares entre todos los usuarios combinados presentes en el conjunto de entrenamiento.

from functools import partial

import get_pyg_data
from model import LightGCNStack
import torch

from src.data_preprocessing import TrainTestGenerator
from src.evaluator import Evaluator
from train_test import train, test
from torch_geometric.utils import train_test_split_edges
import time

import pandas as pd


class objectview(object):
    def __init__(self, *args, **kwargs):
        d = dict(*args, **kwargs)
        self.__dict__ = d


# Wrapper for evaluation
class LightGCN_recommender:
    def __init__(self, args):
        self.args = objectview(args)
        self.model = LightGCNStack(latent_dim=64, args=self.args).to('cuda')
        self.a_rev_dict = None
        self.u_rev_dict = None
        self.a_dict = None
        self.u_dict = None

    def fit(self, data: pd.DataFrame):
        # Default rankings when userID is not in training set
        self.default_recommendation = data["artistID"].value_counts().index.tolist()

        # LightGCN
        data, self.u_rev_dict, self.a_rev_dict, self.u_dict, self.a_dict = get_pyg_data.load_data(data)
        data = data.to("cuda")
        self.model.init_data(data)
        self.optimizer = torch.optim.Adam(params=self.model.parameters(), lr=0.001)

        best_val_perf = test_perf = 0

        for epoch in range(1, self.args.epochs+1):
            start = time.time()
            train_loss = train(self.model, data, self.optimizer)
            val_perf, tmp_test_perf = test(self.model, (data, data))
            if val_perf > best_val_perf:
                best_val_perf = val_perf
                test_perf = tmp_test_perf
            log = 'Epoch: {:03d}, Loss: {:.4f}, Val: {:.4f}, Test: {:.4f}, Elapsed time: {:.2f}'
            print(log.format(epoch, train_loss, best_val_perf, test_perf, time.time()-start))

    def recommend(self, user_id, n):
        try:
            recommendations = self.model.topN(self.u_dict[str(user_id)], n=n)
        except KeyError:

            recommendations = self.default_recommendation
        else:
            recommendations = recommendations.indices.cpu().tolist()
            recommendations = list(map(lambda x: self.a_rev_dict[x], recommendations))
        return recommendations


def evaluate(args):
    data_dir = "../data/"
    data_generator = TrainTestGenerator(data_dir)

    evaluator = Evaluator(partial(LightGCN_recommender, args), data_generator)
    evaluator.evaluate()

    evaluator.save_results('../results/lightgcn.csv', '../results/lightgcn_time.csv')
    print('Recall:')
    print(evaluator.get_recalls())
    print('MRR:')
    print(evaluator.get_mrr())


if __name__=='__main__':
    # best_val_perf = test_perf = 0
    # data = get_pyg_data.load_data()
    #data = train_test_split_edges(data)

    args = {'model_type': 'LightGCN', 'num_layers': 3, 'batch_size': 32, 'hidden_dim': 32,
         'dropout': 0, 'epochs': 1000, 'opt': 'adam', 'opt_scheduler': 'none', 'opt_restart': 0, 'weight_decay': 5e-3,
         'lr': 0.1, 'lambda_reg': 1e-4}

    evaluate(args)

Resultados comparativos

El modelo se ejecutó en tres conjuntos de prueba durante tres años: 2008, 2009 y 2010. Para un conjunto de prueba determinado, los datos de entrenamiento consisten en todas las conexiones realizadas en años anteriores, por ejemplo, un modelo probado en el conjunto de prueba en 2010 se ejecutó en el conjunto de entrenamiento de todos los años anteriores (incluidos 2008 y 2009) entrenados. Pero el modelo probado en el conjunto de pruebas de 2008 solo se entrenó con datos de 2007 y anteriores.

Una vez que el modelo ha producido predicciones, se evalúa utilizando Recall@K presentado anteriormente. La primera tabla a continuación muestra los resultados con la factorización matricial como línea de base, mientras que la segunda tabla a continuación muestra los resultados obtenidos con LightGCN:

2047982387760f5e4528967d9e305702.png

Puntuación de Recall@K por factorización matricial

f70f104c73e47b6d552b5f2ff4485912.png

Puntuación Recall@K de LightGCN

Como era de esperar, la recuperación @K aumenta con K, y el modelo parece funcionar mejor en el conjunto de prueba en 2010, probablemente porque el conjunto de entrenamiento tiene la mayor cantidad de datos en este caso. Las tablas anteriores muestran claramente que LightGCN supera a los modelos de referencia de factorización de matriz en Recall@K. El siguiente gráfico muestra el valor promedio de Recall@K durante tres años.

95674f501854031b24e28d26530c4bd9.png

Otra métrica que se puede utilizar es el MRR de rango recíproco medio. Esta métrica intenta ilustrar mejor la certeza del modelo sobre la predicción de conexiones. Lo hace considerando todas las nuevas conexiones Q que son realmente correctas. Para cada conexión, verifica cuántas conexiones predichas incorrectamente (falsos positivos) hay para obtener un rango para esa conexión (el rango más pequeño posible es 1, ya que también contamos las conexiones correctas). Los recíprocos de estas clasificaciones se promedian para obtener el MRR:

c36848d8e73db1c24c5fefc724681480.png

Con respecto a MRR, nuevamente podemos ver claramente que el modelo LightGCN funciona mejor que el modelo de factorización matricial, como se muestra en la siguiente tabla:

03cb9205c135891b5c985624c9f4595a.png

Pero el modelo LightGCN tarda mucho más en entrenarse que el modelo de factorización matricial utilizado para inicializar sus incrustaciones. Pero por el nombre, se puede ver que LightGCN es muy liviano en comparación con otras redes neuronales convolucionales de gráficos, esto se debe a que LightGCN no tiene ningún parámetro de aprendizaje aparte de la incrustación de entrada, lo que hace que el entrenamiento sea más rápido que otros utilizados para los sistemas de recomendación GCN. Los modelos basados ​​en son mucho más rápidos.

eea3b79d7ea2de775c1f20dd7772e25d.png

Para el momento de las predicciones, ambos modelos toman milisegundos para generar predicciones, la brecha es básicamente insignificante

cita

  1. Xiangnan He, Kuan Deng, Xiang Wang, Yan Li, Yongdong Zhang y Meng Wang. Lightgcn: Simplificando y potenciando la red de convolución de gráficos para recomendación. En Actas de la 43.ª conferencia internacional ACM SIGIR sobre investigación y desarrollo en recuperación de información, páginas 639–648, 2020. arXiv:2002.02126  

  2. Visualizaciones tomadas de la conferencia impartida por Jure Leskovec, disponible en http://web.stanford.edu/class/cs224w/slides/13-recsys.pdf

  3. Iván Cantador, Peter Brusilovsky y Tsvi Kuflik. 2º taller sobre heterogeneidad y fusión de información en sistemas de recomendación (hetrec 2011). En Actas de la 5.ª conferencia ACM sobre sistemas de recomendación, RecSys 2011, Nueva York, NY, EE. UU., 2011. ACM.

  4. 代码 代码 https://github.com/tm1897/mlg_cs224w_project/tree/main(Autores: Ermin Omeragic, Tomaz Marticic, Jurij Nastran)

Autor: jn2279

Lectura recomendada:

Mi intercambio de reclutamiento escolar por Internet de 2022

Mi Resumen 2021

Hablando de la diferencia entre la publicación de algoritmos y la publicación de desarrollo

Resumen de salarios de investigación y desarrollo de reclutamiento de escuelas de Internet

Para series de tiempo, todo lo que puedes hacer.

¿Qué es el problema de la secuencia espacio-temporal? ¿Qué modelos se utilizan principalmente para tales problemas? ¿Cuáles son las principales aplicaciones?

Número público: coche caracol AI

Mantente humilde, mantente disciplinado, mantente progresista

24f9dff84a6267b3cb36457053e6f6d1.png

Envíe [Snail] para obtener una copia del "Proyecto práctico de IA" (AI Snail Car)

Envíe [1222] para obtener una buena nota de cepillado de leetcode

Envíe [AI Four Classics] para obtener cuatro libros electrónicos clásicos de AI

Supongo que te gusta

Origin blog.csdn.net/qq_33431368/article/details/123515948
Recomendado
Clasificación