Costruzione di Sistemi di Raccomandazione Potenti con Deep Learning

Powerful Recommendation Systems with Deep Learning

Illustrazione dell'autore

Una Implementazione Passo-passo Utilizzando la Libreria PyTorch TorchRec

Raccomandare il prodotto giusto ai clienti nel momento giusto è una sfida diffusa in diversi settori. Ad esempio, i banchieri sono costantemente alla ricerca di suggerire servizi altamente pertinenti ai loro clienti esistenti o potenziali. I rivenditori si sforzano di raccomandare prodotti interessanti che soddisfino i gusti dei clienti. Allo stesso modo, i social network mirano a costruire feed accattivanti per favorire l’adozione da parte degli utenti.

Nonostante sia un caso d’uso ampiamente esplorato, raggiungere risultati soddisfacenti rimane difficile a causa della natura unica del problema. Le ragioni principali includono la presenza di abbondanti dati categorici, che spesso portano a problemi di scarsità, e l’aspetto computazionale del caso d’uso, che pone problemi di scalabilità. Solo di recente i modelli di raccomandazione hanno sfruttato le reti neurali.

In questo contesto, Meta ha sviluppato e reso disponibile pubblicamente un modello di raccomandazione di deep learning (DRLM). Il modello è particolarmente notevole per la combinazione dei principi di filtraggio collaborativo e analisi predittiva e per essere adatto alla produzione su larga scala.

Obiettivo

Lo scopo di questo articolo è guidarti attraverso un’implementazione passo-passo utilizzando la libreria PyTorch TorchRec, consentendoti di affrontare efficacemente il tuo caso d’uso di raccomandazione.

Dopo aver letto questo articolo, capirai:

  1. Come funziona il modello DLRM?
  2. Cosa distingue i modelli DLRM e cosa li rende potenti e scalabili?
  3. Come puoi implementare il tuo sistema di raccomandazione dall’inizio alla fine?

L’articolo richiede una conoscenza generale del problema del sistema di raccomandazione e familiarità con la libreria PyTorch. Le sperimentazioni descritte nell’articolo sono state effettuate utilizzando le librerie TorchRec e PyTorch. Puoi trovare il codice qui su GitHub.

GitHub – linafaik08/recommender_systems_dlrm

Contribuisci allo sviluppo di linafaik08/recommender_systems_dlrm creando un account su GitHub.

github.co

1. Decodifica del Modello DLRM

Iniziamo con l’esplorazione delle complessità del modello DLRM e delle sue principali caratteristiche e meccanismi.

1.1. Una Panoramica del Design del Modello

Per fornire un’illustrazione più tangibile, consideriamo lo scenario di un rivenditore online che desidera creare un feed personalizzato per ogni cliente che visita il proprio sito web.

Per raggiungere questo obiettivo, il rivenditore può addestrare un modello che prevede la probabilità che un cliente acquisti un determinato prodotto. Questo modello assegna un punteggio a ciascun prodotto per ciascun cliente, basandosi su vari fattori. Il feed viene costruito classificando i punteggi.

In questo caso, il modello può imparare dai dati storici che comprendono una serie di informazioni per ogni cliente e prodotto. Queste informazioni includono variabili numeriche come l’età del cliente e il prezzo del prodotto, così come caratteristiche categoriche come il tipo di prodotto, il colore e altro ancora.

Ecco dove il modello DLRM eccelle: possiede la notevole capacità di sfruttare sia variabili numeriche che categoriche, anche quando si tratta di un grande numero di categorie uniche. Ciò consente al modello di analizzare in modo completo e comprendere le relazioni complesse tra le caratteristiche. Per capire il motivo, diamo un’occhiata all’architettura del modello nella Figura 1.

Figura 1 - Architettura del modello DLRM, illustrazione dell'autore, ispirata da [5]

Caratteristiche categoriche

DLRM apprende una tabella di embedding per ogni caratteristica categorica e le utilizza per mappare queste variabili in rappresentazioni dense. Pertanto, ogni caratteristica categorica è rappresentata come un vettore della stessa lunghezza.

Caratteristiche numeriche

DLRM elabora le caratteristiche numeriche tramite un MLP, chiamato bottom MLP. L’output di questo MLP ha la stessa dimensione dei vettori di embedding precedenti.

Interazione in coppia

DLRM calcola il prodotto scalare tra tutte le coppie di vettori di embedding e le caratteristiche numeriche elaborate. Ciò consente al modello di includere l’interazione delle caratteristiche di secondo ordine.

Concatenazione e output finale

DLRM concatena questi prodotti scalari con le caratteristiche numeriche elaborate e utilizza i risultati per alimentare un altro MLP, chiamato top MLP. La probabilità finale viene ottenuta passando l’output di questo MLP a una funzione sigmoide.

1.2. Implementazione del modello

Sebbene il potenziale del modello sembri promettente nella teoria, la sua implementazione pratica presenta una sfida computazionale.

Solitamente, i casi d’uso di raccomandazione coinvolgono la gestione di volumi enormi di dati. L’utilizzo di modelli DLRM, in particolare, introduce un numero molto elevato di parametri, superiore rispetto ai modelli di deep learning comuni. Di conseguenza, ciò amplifica le richieste computazionali associate alla loro implementazione.

  1. La maggior parte dei parametri in DLRM può essere attribuita agli embedding in quanto consistono in tabelle multiple, ognuna delle quali richiede una grande quantità di memoria. Ciò rende DLRM computazionalmente impegnativo, sia in termini di capacità di memoria che di larghezza di banda.
  2. Anche se l’occupazione di memoria dei parametri MLP è inferiore, richiedono comunque risorse computazionali consistenti.

Per mitigare il collo di bottiglia della memoria, DLRM si affida a una combinazione unica di parallelismo del modello per gli embedding e parallelismo dei dati per gli MLP.

2. Dal concetto all’implementazione: una guida passo-passo per la creazione del tuo sistema di raccomandazione personalizzato

Questa sezione fornisce una guida dettagliata passo-passo su come implementare il proprio sistema di raccomandazione dall’inizio alla fine.

2.1. Trasformazione dei dati e costruzione dei batch

Il primo passo consiste nella conversione dei dati in tensori e nell’organizzazione degli stessi in batch per l’input nel modello.

Per illustrare questo processo, prendiamo in considerazione questo dataframe come esempio.

Per le caratteristiche sparse, è necessario concatenare i valori in un singolo vettore e calcolare le lunghezze. Ciò può essere realizzato utilizzando la funzione KeyedJaggedTensor.from_lengths_sync, che prende entrambi gli elementi in input. Ecco un esempio dello script Python:

values = sample[cols_sparse].sum(axis=0).sum(axis=0)values = torch.tensor(values).to(device)# values = tensor([1, 0, 2, 0, 2, 2, 0, 2, 0, 1, 0, 1, 2, 0], device='cuda:0')lengths = torch.tensor(    pd.concat([sample[feat].apply(lambda x: len(x)) for feat in cols_sparse],              axis=0).values,    dtype=torch.int32).to(self.device)# lengths = tensor([1, 1, 1, 1, 1, 2, 3, 2, 2, 0], device='cuda:0', dtype=torch.int32)sparse_features = KeyedJaggedTensor.from_lengths_sync(  keys=cols_sparse,  values=values,  lengths=lengths)

Per le caratteristiche dense e le etichette, il processo è più semplice. Ecco un esempio dello script Python:

dense_features = torch.tensor(sample[cols_dense].values, dtype=torch.float32).to(device)labels = torch.tensor(sample[col_label].values, dtype=torch.int32).to(device)

Utilizzando gli output dei passaggi precedenti, diventa possibile costruire un batch. Ecco un esempio dello script Python:

batch = Batch(  dense_features=dense_features,  sparse_features=sparse_features,  labels=labels,).to(device)

Per un’implementazione più completa, puoi fare riferimento al file batch.py nel repository GitHub corrispondente.

2.2. Inizializzazione del modello e configurazione dell’ottimizzazione

Il passo successivo consiste nell’inizializzare il modello, come dimostrato nel seguente codice Python:

# Inizializza il modello e configura l'ottimizzazione# Definisci la dimensionalità degli embedding utilizzati nel modellodem_embedding = 10# Calcola il numero di embedding per ogni caratteristicanum_embedding_per_feature = {c: len(v) for c, v in map_sparse.items()}# Definisci le dimensioni dei layer per l'architettura densadense_arch_layer_sizes = [512, 256, embedding_dim]# Definisci le dimensioni dei layer per l'architettura complessivaover_arch_layer_sizes = [512, 512, 256, 1]# Specifica se utilizzare l'ottimizzatore Adagrad o l'ottimizzatore SGDadagrad = False# Imposta il valore epsilon per l'ottimizzazione Adagrads = 1e-8# Imposta il tasso di apprendimento per l'ottimizzazionelearning_rate = 0.01# Crea una lista di oggetti EmbeddingBagConfig per ogni caratteristica sparsaeb_configs = [    EmbeddingBagConfig(        name=f"t_{feature_name}",        embedding_dim=embedding_dim,        num_embeddings=num_embeddings_per_feature[feature_name + '_enc'],        feature_names=[feature_name + '_enc'],    )    for feature_idx, feature_name in enumerate(cols_sparse)]# Inizializza il modello DLRM con la raccolta di embedding bag e le specifiche dell'architetturadlrm_model = DLRM(    embedding_bag_collection=EmbeddingBagCollection(        tables=eb_configs, device=device    ),    dense_in_features=len(cols_dense),    dense_arch_layer_sizes=dense_arch_layer_sizes,    over_arch_layer_sizes=over_arch_layer_sizes,    dense_device=device,)# Crea un'istanza DLRMTrain per gestire l'operazione di addestramentotrain_model = DLRMTrain(dlrm_model).to(device)# Scegli la classe di ottimizzatore appropriata per i parametri di embeddingembedding_optimizer = torch.optim.Adagrad if adagrad else torch.optim.SGD# Imposta gli argomenti delle parole chiave per l'ottimizzatoreif adagrad:    optimizer_kwargs["eps"] = eps# Applica l'ottimizzatore ai parametri dell'architettura sparsa nel backwardapply_optimizer_in_backward(    optimizer_class=embedding_optimizer,    params=train_model.model.sparse_arch.parameters(),    optimizer_kwargs=optimizer_kwargs,)# Inizializza l'ottimizzatore denso con i parametri appropriatidense_optimizer = KeyedOptimizerWrapper(    dict(in_backward_optimizer_filter(train_model.named_parameters())),    optimizer_with_params(adagrad, learning_rate, eps),)# Crea un'istanza CombinedOptimizer per gestire l'ottimizzazioneoptimizer = CombinedOptimizer([dense_optimizer])

Il modello può quindi essere addestrato e valutato utilizzando il seguente codice:

loss, (loss2, logits, labels) = train_model(batch)

Per un’implementazione più completa, puoi fare riferimento al file model.py nel repository GitHub corrispondente.

Punti chiave

✔ Il modello DLRM presenta un approccio convincente per combinare in modo efficace caratteristiche numeriche e categoriche utilizzando embedding, consentendo al modello di catturare modelli e relazioni complesse.

✔ Sebbene la sua architettura richieda risorse computazionali considerevoli, la sua implementazione incorpora una combinazione unica di parallelismo del modello e parallelismo dei dati, rendendo il modello scalabile per la produzione.

✔ Tuttavia, a causa della disponibilità limitata dei dati, le prestazioni del modello non sono state ampiamente testate su dataset reali diversi. Ciò solleva incertezze sulla sua efficacia in scenari pratici.

✔ Inoltre, il modello richiede il tuning di un numero considerevole di parametri, complicando ulteriormente il processo.

✔ Tenendo conto di ciò, modelli più semplici come LGBM possono offrire prestazioni comparabili con un’implementazione, un tuning e una manutenzione più semplici, senza lo stesso overhead computazionale.

Riferimenti

[1] M Naumov e altri, Deep Learning Recommendation Model for Personalization and Recommendation Systems, maggio 2019

[2] Repository GitHub dell’implementazione iniziale del modello DLRM del team di Facebook disponibile come open source

[3] DLRM: Un modello di raccomandazione di apprendimento profondo avanzato e open source, Meta AI Blog, luglio 2019

[4] Libreria PyTorch per sistemi di raccomandazione di produzione moderni, torchec

[5] Vinh Nguyen, Tomasz Grel e Mengdi Huang, Ottimizzazione del modello di raccomandazione di apprendimento profondo su GPU NVIDIA, giugno 2020