Creazione di un Generatore di Playlist con Sentence Transformers

Generatore di Playlist con Sentence Transformers.

Poco tempo fa ho pubblicato un generatore di playlist che ho costruito utilizzando Sentence Transformers e Gradio, e ho seguito questo con una riflessione su come cerco di utilizzare i miei progetti come esperienze di apprendimento efficaci. Ma come ho effettivamente costruito il generatore di playlist? In questo post analizzeremo quel progetto e esamineremo due dettagli tecnici: come sono state generate le embeddings e come è stato costruito il demo multi-step di Gradio.

Come abbiamo esplorato in precedenti post sul blog di Hugging Face, Sentence Transformers (ST) è una libreria che ci fornisce strumenti per generare embeddings di frasi, che hanno una varietà di utilizzi. Poiché avevo accesso a un dataset di testi di canzoni, ho deciso di sfruttare la funzionalità di ricerca semantica di ST per generare playlist da un prompt di testo dato. In particolare, l’obiettivo era creare un’embedding dal prompt, utilizzare quell’embedding per una ricerca semantica su un insieme di embeddings pre-generati dei testi delle canzoni per generare un insieme rilevante di canzoni. Tutto questo sarebbe stato racchiuso in un’app Gradio utilizzando la nuova API Blocks, ospitata su Hugging Face Spaces.

Studiaremo un utilizzo leggermente avanzato di Gradio, quindi se sei un principiante nella libreria ti consiglio di leggere l’Introduzione a Blocks prima di affrontare le parti specifiche di Gradio di questo post. Inoltre, nota che sebbene non rilascerò il dataset dei testi delle canzoni, le embeddings dei testi delle canzoni sono disponibili su Hugging Face Hub per giocarci. Iniziamo! 🪂

Gli embeddings sono fondamentali in Sentence Transformers! Abbiamo imparato cosa sono gli embeddings e come generarli in un articolo precedente, e consiglio di dare un’occhiata a quello prima di continuare con questo post.

Sentence Transformers offre una vasta collezione di modelli di embedding pre-addestrati! Include persino tutorial per il fine-tuning di quei modelli con i nostri dati di allenamento, ma per molti casi d’uso (come la ricerca semantica su un corpus di testi di canzoni) i modelli pre-addestrati funzioneranno in modo eccellente già dal principio. Tuttavia, con così tanti modelli di embedding disponibili, come sappiamo quale scegliere?

La documentazione di ST evidenzia molte delle scelte, insieme alle loro metriche di valutazione e alcune descrizioni dei casi d’uso previsti. I modelli MS MARCO sono addestrati su query del motore di ricerca di Bing, ma poiché funzionano bene anche in altri domini ho deciso che uno qualsiasi di questi potrebbe essere una buona scelta per questo progetto. Tutto ciò di cui abbiamo bisogno per il generatore di playlist è trovare canzoni che abbiano una certa similarità semantica, e poiché non mi interessa particolarmente raggiungere una metrica di prestazione specifica, ho scelto in modo arbitrario sentence-transformers/msmarco-MiniLM-L-6-v3.

Ogni modello in ST ha una lunghezza di sequenza di input configurabile (fino a un massimo), dopo la quale i tuoi input verranno troncati. Il modello che ho scelto aveva una lunghezza massima della sequenza di 512 pezzi di parole, che, come ho scoperto, spesso non è sufficiente per incorporare intere canzoni. Fortunatamente, c’è un modo facile per suddividere i testi delle canzoni in blocchi più piccoli che il modello può elaborare: i versi! Una volta suddivise le canzoni in versi ed incorporato ciascun verso, vedremo che la ricerca funziona molto meglio.

Le canzoni vengono suddivise in versi e poi ciascun verso viene incorporato.

Per generare effettivamente gli embeddings, puoi chiamare il metodo .encode() del modello Sentence Transformers e passargli una lista di stringhe. Poi puoi salvare gli embeddings come preferisci, in questo caso ho scelto di salvarli in formato Pickle.

from sentence_transformers import SentenceTransformer
import pickle

embedder = SentenceTransformer('msmarco-MiniLM-L-6-v3')
verses = [...] # Carica le tue stringhe in una lista
corpus_embeddings = embedder.encode(verses, show_progress_bar=True)

with open('verse-embeddings.pkl', "wb") as fOut:
    pickle.dump(corpus_embeddings, fOut)

Per poter condividere gli embeddings con gli altri, puoi addirittura caricare il file Pickle in un dataset di Hugging Face. Leggi questo tutorial per saperne di più, o visita la documentazione di Datasets per provarlo tu stesso! In breve, una volta creato un nuovo Dataset su Hub, puoi semplicemente caricare manualmente il tuo file Pickle cliccando il pulsante “Aggiungi file”, mostrato di seguito.

Puoi caricare manualmente i file del dataset su Hub.

L’ultima cosa che dobbiamo fare ora è utilizzare effettivamente gli embeddings per la ricerca semantica! Il codice seguente carica gli embeddings, genera un nuovo embedding per una stringa data ed esegue una ricerca semantica sugli embeddings dei testi delle canzoni per trovare i risultati più vicini. Per rendere più facile lavorare con i risultati, mi piace anche inserirli in un DataFrame di Pandas.

from sentence_transformers import util
import pandas as pd

prompt_embedding = embedder.encode(prompt, convert_to_tensor=True)
hits = util.semantic_search(prompt_embedding, corpus_embeddings, top_k=20)
hits = pd.DataFrame(hits[0], columns=['corpus_id', 'score'])
# Nota che "corpus_id" è l'indice del verso per quell'embedding
# Puoi usare "corpus_id" per cercare la canzone originale

Dato che stiamo cercando qualsiasi verso che corrisponda alla frase di testo, c’è una buona possibilità che la ricerca semantica trovi più versi dalla stessa canzone. Quando eliminiamo i duplicati, potremmo finire solo con poche canzoni distinte. Se aumentiamo il numero di embedding di versi che util.semantic_search recupera con il parametro top_k, possiamo aumentare il numero di canzoni che troveremo. Sperimentalmente, ho scoperto che quando imposto top_k=20, quasi sempre ottengo almeno 9 canzoni distinte.

Creazione di un’app Gradio a più step

Per la demo, volevo che gli utenti inserissero una frase di testo (o scegliessero tra alcuni esempi) e conducessero una ricerca semantica per trovare le 9 canzoni più rilevanti. Successivamente, gli utenti dovrebbero essere in grado di selezionare tra le canzoni risultanti per visualizzare i testi, che potrebbero fornire loro alcune informazioni su perché sono state scelte quelle particolari canzoni. Ecco come possiamo farlo!

All’inizio della demo Gradio carichiamo gli embedding, le associazioni e i testi delle canzoni dai dataset di Hugging Face quando l’app viene avviata.

from sentence_transformers import SentenceTransformer, util
from huggingface_hub import hf_hub_download
import os
import pickle
import pandas as pd

corpus_embeddings = pickle.load(open(hf_hub_download("NimaBoscarino/playlist-generator", repo_type="dataset", filename="verse-embeddings.pkl"), "rb"))
songs = pd.read_csv(hf_hub_download("NimaBoscarino/playlist-generator", repo_type="dataset", filename="songs_new.csv"))
verses = pd.read_csv(hf_hub_download("NimaBoscarino/playlist-generator", repo_type="dataset", filename="verses.csv"))

# Sto caricando i testi delle canzoni dal mio dataset privato, con il mio proprio token API
auth_token = os.environ.get("TOKEN_FROM_SECRET") 
lyrics = pd.read_csv(hf_hub_download("NimaBoscarino/playlist-generator-private", repo_type="dataset", filename="lyrics_new.csv", use_auth_token=auth_token))

La API Gradio Blocks ti permette di costruire interfacce a più step, il che significa che sei libero di creare sequenze abbastanza complesse per le tue demo. Daremo un’occhiata a alcuni frammenti di codice di esempio qui, ma dai un’occhiata al codice del progetto per vederlo tutto in azione. Per questo progetto, vogliamo che gli utenti scelgano una frase di testo e quindi, dopo che la ricerca semantica è completa, gli utenti dovrebbero avere la possibilità di scegliere una canzone dai risultati per ispezionare i testi. Con Gradio, questo può essere costruito iterativamente iniziando con la definizione dei componenti di input iniziali e quindi registrando un evento click sul pulsante. C’è anche un componente Radio, che verrà aggiornato per mostrare i nomi delle canzoni per la playlist.

import gradio as gr

song_prompt = gr.TextArea(
    value="Running wild and free",
    placeholder="Inserisci una frase di testo o scegli un esempio"
)

fetch_songs = gr.Button(value="Genera la tua playlist!")

song_option = gr.Radio()

fetch_songs.click(
    fn=generate_playlist,
    inputs=[song_prompt],
    outputs=[song_option],
)

In questo modo, quando si fa clic sul pulsante, Gradio prende il valore corrente del TextArea e lo passa a una funzione, mostrata di seguito:

def generate_playlist(prompt):
    prompt_embedding = embedder.encode(prompt, convert_to_tensor=True)
    hits = util.semantic_search(prompt_embedding, corpus_embeddings, top_k=20)
    hits = pd.DataFrame(hits[0], columns=['corpus_id', 'score'])
    # ... codice per mappare dagli ID dei versi ai nomi delle canzoni
    song_names = ... # ad esempio ["Thank U, Next", "Freebird", "La Cucaracha"]
    return (
        gr.Radio.update(label="Canzoni", interactive=True, choices=song_names)
    )

In quella funzione, utilizziamo la frase di testo per condurre la ricerca semantica. Come si può vedere sopra, per inviare aggiornamenti ai componenti Gradio nell’app, la funzione deve solo restituire componenti creati con il metodo .update(). Poiché abbiamo collegato il componente Radio song_option a fetch_songs.click con il suo parametro output, generate_playlist può controllare le scelte per il componente Radio!

Puoi persino fare qualcosa di simile al componente Radio per consentire agli utenti di scegliere quale testo delle canzoni visualizzare. Visita il codice su Hugging Face Spaces per vederlo nel dettaglio!

Alcuni pensieri

Sentence Transformers e Gradio sono ottime scelte per questo tipo di progetto! ST ha le funzioni di utilità di cui abbiamo bisogno per generare rapidamente gli embedding, così come per eseguire la ricerca semantica con un codice minimo. Avere accesso a una vasta collezione di modelli pre-addestrati è anche estremamente utile, poiché non abbiamo bisogno di creare e addestrare i nostri modelli per questo tipo di cose. Costruire la nostra demo in Gradio significa che dobbiamo solo concentrarci sulla programmazione in Python, e il deploy dei progetti Gradio su Hugging Face Spaces è anche super semplice!

Ci sono un sacco di altre cose che avrei voluto avere il tempo di integrare in questo progetto, come queste idee che potrei esplorare in futuro:

  • Integrare con Spotify per generare automaticamente una playlist, e magari utilizzare il player incorporato di Spotify per consentire agli utenti di ascoltare immediatamente le canzoni.
  • Utilizzare il componente **HighlightedText** di Gradio per identificare il verso specifico trovato dalla ricerca semantica.
  • Creare alcune visualizzazioni dello spazio di embedding, come in questo Space di Radamés Ajna.

Anche se i testi delle canzoni non vengono rilasciati, ho pubblicato gli embedding dei versi insieme alle corrispondenze di ciascuna canzone, quindi sei libero di divertirti e lasciarti ispirare!

Ricorda di passare su Discord per fare domande e condividere il tuo lavoro! Sono entusiasta di vedere cosa farai con gli embedding di Sentence Transformers 🤗

Risorse extra

  • Iniziare con gli Embedding di Omar Espejel
    • O come thread di Twitter di Omar Sanseviero
  • Documentazione di Hugging Face + Sentence Transformers
  • Gradio Blocks party – Visualizza alcuni incredibili progetti della comunità che mostrano Gradio Blocks!