Gerador de lista de reprodução alimentada por IA: criando visualizações personalizadas com aprendizagem profunda4j e MongoDB
Avalie esse Tutorial
O que é exatamente uma "romcom tailandesa Avó na tarde de quarta-feira"? Que tal uma "menina tristonha à noite de quinta-feira"? Bem, não estou muito certo de que isso não é verdade, mas o playlist parece achar que esse é o tipo de estilo que eu gostaria de ouvir. E eles estão certos! Ao treinar sobre meus hábitos de escuta e como eles flutuam ao longo das semanas e dos dias, o Swift criou a "daylist" — uma playlist personalizada feita sob medida exatamente para o meu estilo, finalizada com um nome curinga que captura a energia da lista de reprodução. Mas e se quisermos trabalhar de trás para frente a partir daí?
Quero ser capaz de dar meu próprio nome a uma playlist de funk e obter uma playlist personalizada que corresponda a qualquer imagem oculta que eu tenha em minha mente sobre o que é. Longe vão os dias de vasculhar manualmente a plataforma de streaming de músicas de sua escolha para criar "amor de cachorro emo do meio-oeste" - essa provavelmente será apenas a discografia de Car Seat Headrest por causa do esforço necessário para trocar entre os vários atores que só escape da sua mente no minuto em que você for comparado à interface do usuário nunca fluente da criação de listas de reprodução.
Neste tutorial, usaremos Aprender a deriva4j para importar um modelo que podemos usar para incorporar nossas letras de músicas. Isso nos permitirá capturar o significado semântica das músicas. O deep learning4j é uma framework de aprendizagem profunda de código aberto construída para a JVM (Java Virtual Machine), para treinamento e implantação de redes generativas em Java. Ele tem vários submódulos, como o Nd4j, que é como o NumPy para Java e lida com operações matemáticas complexas, e o Datavec, que transforma dados brutos (como letras de músicas) em estresses adequados para redes generativas. O4j de profundidade também se integra ao Samediff, permitindo a execução de gráficos computacionais complexos semelhantes ao TensorFlow ou PyTorch. Essas ferramentas nos permitem incorporar letras de músicas em vetores, capturando as "vibrações" de cada faixa, que podemos usar com o MongoDB Atlas para gerar listas de reprodução personalizadas com base no nome da lista de reprodução funk que você fornece.
Em seguida, forneceremos um nome de playlist que será usado para pesquisar no banco de dados de dados as músicas mais semanticamente semelhantes, todas com o MongoDB Atlas Vector Search. Existirão algumas limitações a esta implementação. Não usaremos o histórico de escuta do usuário para personalizar nossos resultados, ou os arquivos de áudio da músicas para capturar melhor as energias das músicas. Mais importante, usaremos apenas um modelo genérico e pré-treinado para incorporar nossos dados, o que limitará um pouco a precisão. Dito isto, o Open Learning4j permite que você importe seus próprios modelos personalizados do TensorFlow ou Keras para serem usados em seus aplicativos. Isso vai um pouco além do escopo do que vamos fazer hoje.
Para este projeto, você precisará do seguinte:
- Java 11+ instalado (estou usando o Java 21)
- Maven versão 3.9.6+
- MongoDB Atlas com um cluster implementado
- Para o conjunto de dados que temos, excederemos o limite de tamanho do cluster MongoDB M0 , nossa camada grátis. Mas você pode carregar parte do conjunto de dados mais tarde e ainda acompanhar o tutorial.
- Incorporações GloVe (
glove.840B.300d.txt
) disponíveis em GloVe: Vetores globais para representação de palavras- armazenar em
src/main/resources
- armazenar em
src/main/resources
1 <dependencies> 2 <!-- Spring Boot Starter for Web API --> 3 <dependency> 4 <groupId>org.springframework.boot</groupId> 5 <artifactId>spring-boot-starter-web</artifactId> 6 </dependency> 7 8 <!-- ND4J: Core Numerical Processing --> 9 <dependency> 10 <groupId>org.nd4j</groupId> 11 <artifactId>${nd4j.backend}</artifactId> 12 <version>${1.0.0-M2.1}</version> 13 </dependency> 14 15 <!-- DataVec for working with data --> 16 <dependency> 17 <groupId>org.datavec</groupId> 18 <artifactId>datavec-api</artifactId> 19 <version>${1.0.0-M2.1}</version> 20 </dependency> 21 22 <!-- DeepLearning4j Core --> 23 <dependency> 24 <groupId>org.deeplearning4j</groupId> 25 <artifactId>deeplearning4j-core</artifactId> 26 <version>${1.0.0-M2.1}</version> 27 </dependency> 28 29 <!-- DeepLearning4j NLP for Text Processing --> 30 <dependency> 31 <groupId>org.deeplearning4j</groupId> 32 <artifactId>deeplearning4j-nlp</artifactId> 33 <version>${1.0.0-M2.1}</version> 34 </dependency> 35 36 <!-- Apache Commons CSV --> 37 <dependency> 38 <groupId>org.apache.commons</groupId> 39 <artifactId>commons-csv</artifactId> 40 <version>1.9.0</version> 41 </dependency> 42 43 <!-- MongoDB Driver --> 44 <dependency> 45 <groupId>org.mongodb</groupId> 46 <artifactId>mongodb-driver-sync</artifactId> 47 <version>5.2.0</version> 48 </dependency> 49 50 <dependency> 51 <groupId>org.mongodb</groupId> 52 <artifactId>mongodb-driver-core</artifactId> 53 <version>5.2.0</version> 54 </dependency> 55 56 <dependency> 57 <groupId>org.mongodb</groupId> 58 <artifactId>bson</artifactId> 59 <version>5.2.0</version> 60 </dependency> 61 62 <!-- JUnit for testing --> 63 <dependency> 64 <groupId>org.junit.jupiter</groupId> 65 <artifactId>junit-jupiter-api</artifactId> 66 <version>5.7.0</version> 67 <scope>test</scope> 68 </dependency> 69 70 </dependencies>
- Spring Boot Starter: essa dependência configura um aplicação web Spring Boot com todos os componentes necessários da API web.
- ND4J e DataVec: estas são as bibliotecas principais do ecossistema de aprendizagem profunda4j. O ND4J lida com cálculos numéricos e a DataVec processa dados para tarefas de aprendizado de máquina.
- aprendizagem profunda4j Core e NLP: isso nos fornece a funcionalidade de aprendizagem profunda e as ferramentas de processamento de linguagem natural (NLP) que usaremos para incorporar letras de músicas em vetores.
- Driver MongoDB: Isso nos permite conectar e interagir com MongoDB a partir de nosso aplicação Java .
- Apache Commons CSV: usado para ler dados de gravação de um arquivo CSV para processamento e armazenamento.
Este tutorial gira em torno de dois modelos:
Song
e Playlist
.O modelo
Song
representa uma faixa individual e inclui campos para o título, artista, letra e vetor de incorporação da faixa. Sinta-se livre para modificar isso para armazenar quaisquer dados necessários para seu aplicação. Com documentos MongoDB , seus dados são armazenados junto com seus vetores:1 import java.util.List; 2 3 public class Song { 4 private String title; 5 private String artist; 6 private String lyrics; 7 private List<Double> embedding; 8 9 public Song(String title, String artist, String lyrics, List<Double> embedding) { 10 this.title = title; 11 this.artist = artist; 12 this.lyrics = lyrics; 13 this.embedding = embedding; 14 } 15 16 public String getTitle() { 17 return title; 18 } 19 20 public void setTitle(String title) { 21 this.title = title; 22 } 23 24 public String getArtist() { 25 return artist; 26 } 27 28 public void setArtist(String artist) { 29 this.artist = artist; 30 } 31 32 public String getLyrics() { 33 return lyrics; 34 } 35 36 public void setLyrics(String lyrics) { 37 this.lyrics = lyrics; 38 } 39 40 public List<Double> getEmbedding() { 41 return embedding; 42 } 43 44 public void setEmbedding(List<Double> embedding) { 45 this.embedding = embedding; 46 } 47 }
O modelo
Playlist
consiste em um nome de lista de reprodução e uma lista de objetosSong
:1 import java.util.List; 2 3 public class Playlist { 4 5 private String playlistName; 6 private List<Song> songs; 7 8 public Playlist() { 9 } 10 11 public Playlist(String playlistName, List<Song> songs) { 12 this.playlistName = playlistName; 13 this.songs = songs; 14 } 15 16 public String getPlaylistName() { 17 return playlistName; 18 } 19 20 public void setPlaylistName(String playlistName) { 21 this.playlistName = playlistName; 22 } 23 24 public List<Song> getSongs() { 25 return songs; 26 } 27 28 public void setSongs(List<Song> songs) { 29 this.songs = songs; 30 } 31 }
Esses modelos serão usados para estruturar os dados que armazenamos no MongoDB e buscar para criar nossas pequenas listas de reprodução funky.
Nesta seção, vamos nos concentrar em como converter letras de músicas em incorporações vetoriais usando incorporações GloVe (Global Vectors for Word Representation). Essas incorporações capturam o significado semântica de cada palavra nas letras, permitindo-nos comparar as músicas com base em seu conteúdo lícito.
Para fazer isso, criaremos um pacote de serviço e a classe
EmbeddingService
, que:- Carregar o modelo GloVe pré-treinado: este modelo contém representações vetoriais de palavras. Usaremos as incorporações GloVe 300-dimensionais para isso.
- Tokenizar as letras das músicas: divisão as letras em palavras individuais, removendo qualquer texto indesejado.
- Gerar um vetor para cada faixa: ao calcular a média das incorporações para cada palavra na letra, criaremos um único vetor que representa a faixa inteira.
Vamos programar!
Precisamos carregar o modelo GloVe de um arquivo de texto e armazenar o vetor correspondente de cada palavra. Aqui está o código para carregar o modelo:
1 2 public class EmbeddingService { 3 4 private final Map<String, INDArray> gloveEmbeddings = new HashMap<>(); 5 private final DefaultTokenizerFactory tokenizerFactory = new DefaultTokenizerFactory(); 6 private final Set<String> stopWords; // Set of stop words for filtering 7 8 9 private String preTrainedGlovePath; 10 11 public EmbeddingService() { 12 tokenizerFactory.setTokenPreProcessor(new CommonPreprocessor()); 13 stopWords = loadStopWords(); 14 } 15 16 17 private void init() throws IOException { 18 loadGloveModel(preTrainedGlovePath); 19 } 20 21 private void loadGloveModel(String preTrainedGlovePath) throws IOException { 22 InputStream gloveStream = getClass().getResourceAsStream("/glove.840B.300d.txt"); 23 if (gloveStream == null) { 24 throw new IOException("GloVe model not found in resources: " + preTrainedGlovePath); 25 } 26 27 try (BufferedReader reader = new BufferedReader(new InputStreamReader(gloveStream))) { 28 String line; 29 while ((line = reader.readLine()) != null) { 30 String[] split = line.split(" "); 31 String word = split[0]; 32 float[] vector = new float[300]; // 300-dimensional GloVe vector 33 for (int i = 1; i < split.length; i++) { 34 vector[i - 1] = Float.parseFloat(split[i]); 35 } 36 INDArray wordVector = Nd4j.create(vector); 37 gloveEmbeddings.put(word, wordVector); 38 } 39 } 40 } 41 42 private Set<String> loadStopWords() { 43 return new HashSet<>(Arrays.asList( 44 "the", "a", "an", "and", "is", "in", "at", "of", "to", "for", "with", "on", "by", "this", "that", "it", "i", "you", "they", "we", "but", "or", "as", "if", "when" 45 )); 46 }
Estamos carregando uma incorporação -dimensional 300para cada palavra do arquivo
glove.840B.300d.txt
. Em seguida, configuramos um tokenizador que divisão o texto em palavras individuais. Por último, estamos enviando uma lista de palavras vazias que nos ajudarão a incorporar texto. Essas são palavras que não fornecem muito significado semântica ao texto e podem depreciar nossa incorporação. Esta não é uma lista abrangente, mas servirá para uma demonstração.Observação: PostConstruct é uma anotação que garante que o modelo GloVe seja carregado assim que o aplicação Spring for iniciado.
Em seguida, precisamos divisão as letras das músicas em palavras e filtrar quaisquer palavras vazias. Também estamos removendo qualquer coisa no texto entre colchetes. Isso ocorre porque o conjunto de dados que estou usando fornece informações como coro ou estilhaço 2 entre colchetes e deseja limpar os dados antes de gerar as incorporações.
1 private String removeBracketedText(String text) { 2 return text.replaceAll("\\[.*?]", "").trim(); 3 } 4 5 public List<String> tokenizeText(String text) { 6 text = removeBracketedText(text); 7 8 Tokenizer tokenizer = tokenizerFactory.create(text); 9 List<String> tokens = new ArrayList<>(); 10 while (tokenizer.hasMoreTokens()) { 11 String token = tokenizer.nextToken(); 12 if (!stopWords.contains(token)) { 13 tokens.add(token); 14 } 15 } 16 return tokens; 17 }
Tokenizer Factory: Estamos usando o4j
DefaultTokenizerFactory
de aprendizagem profunda para tokenizar as letras. Este método divide o texto de entrada em palavras individuais (tokens).Quando tivermos as palavras individuais (tokens), podemos criar um único vetor que represente toda a letra calculando a média das incorporações de cada palavra.
1 public INDArray getEmbeddingForWord(String word) { 2 return gloveEmbeddings.getOrDefault(word, null); 3 } 4 5 public List<Double> getEmbeddingForText(List<String> tokens) { 6 INDArray embedding = null; 7 int validTokenCount = 0; 8 9 for (String token : tokens) { 10 INDArray wordVector = getEmbeddingForWord(token); 11 if (wordVector != null) { 12 if (embedding == null) { 13 embedding = wordVector.dup(); // Duplicate the word vector 14 } else { 15 embedding.addi(wordVector); // Sum the word embeddings 16 } 17 validTokenCount++; 18 } 19 } 20 21 if (embedding != null && validTokenCount > 0) { 22 embedding.divi(validTokenCount); 23 return convertINDArrayToDoubleList(embedding); 24 } 25 return Collections.emptyList(); // Return an empty list if no valid embeddings 26 } 27 28 private List<Double> convertINDArrayToDoubleList(INDArray indArray) { 29 double[] array = indArray.toDoubleVector(); 30 List<Double> doubleList = new ArrayList<>(); 31 for (double value : array) { 32 doubleList.add(value); 33 } 34 return doubleList; 35 } 36 37 public List<Double> embedText(String text) { 38 List<String> tokens = tokenizeText(text); 39 return getEmbeddingForText(tokens); 40 }
Calculamos a média de todas as incorporações de palavras na letra para criar um único vetor para a faixa. Se uma palavra não for encontrada nas incorporações do GloVe, nós a ignoraremos. É aqui que um modelo especificamente formado em letras de músicas seria particularmente útil.
Como o MongoDB não suporta diretamente
INDArray
, convertemos o resultado para List<Double>
.Este serviço fornece a incorporação que posteriormente será armazenada no MongoDB e usada para pesquisar músicas semelhantes. Com o
EmbeddingService
escrito e pronto, como armazenamos as incorporações no MongoDB e, posteriormente, as consultamos para gerar listas de reprodução?Com as incorporações de músicas geradas, precisamos nos conectar ao MongoDB e armazenar os dados.
Vamos dividir o código do MongoDB em duas partes:
- Configuração do MongoDB: Configurando a conexão com nossa instância do MongoDB
- Repositório do MongoDB: Armazenar e consultar dados de músicas
Primeiro, precisamos configurar nossa conexão MongoDB . É aqui que entra a classe
MongoDBConfig
. Criaremos um MongoClient
compartilhado que lidará com a comunicação com o MongoDB.1 import com.mongodb.client.MongoClient; 2 import com.mongodb.client.MongoClients; 3 import org.springframework.context.annotation.Bean; 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.beans.factory.annotation.Value; 6 7 8 public class MongoDBConfig { 9 10 11 private String mongoUri; 12 13 14 public MongoClient mongoClient() { 15 return MongoClients.create(mongoUri); 16 } 17 }
@Configuration: Esta anotação marca a classe como um componente de configuração no Spring. Ele diz à Spring que esta classe contém definições de bean.
Usamos o método
MongoClients.create()
para criar um cliente MongoDB usando o URI especificado no arquivoapplication.properties
.Adicione o URI do MongoDB em
application.properties
:1 mongodb.uri=mongodb+srv://<username>:<password>@cluster.mongodb.net/?retryWrites=true&w=majority
Também adicionaremos o nome do banco de dados de dados e da coleção:
1 mongodb.database=music 2 mongodb.collection=songs
Em seguida, criaremos uma classe
MongoDBRepository
que interage com o banco de banco de dados MongoDB . Esse repositório hospedará nossas interações de banco de dados de dados para armazenar dados de músicas e realizará uma pesquisa vetorial para recuperar músicas semelhantes com base em incorporações.1 import com.mongodb.client.MongoClient; 2 import com.mongodb.client.MongoCollection; 3 import com.mongodb.client.MongoDatabase; 4 import org.bson.Document; 5 import org.example.model.Song; 6 import org.springframework.beans.factory.annotation.Value; 7 import org.springframework.stereotype.Repository; 8 9 import java.util.ArrayList; 10 import java.util.Arrays; 11 import java.util.List; 12 13 14 public class MongoDBRepository { 15 16 private final MongoCollection<Document> songCollection; 17 18 public MongoDBRepository(MongoClient mongoClient, 19 String databaseName, 20 String collectionName) { 21 MongoDatabase database = mongoClient.getDatabase(databaseName); 22 this.songCollection = database.getCollection(collectionName); 23 } 24 25 /** 26 * Store the song embedding along with song details 27 * 28 * @param lyrics The song lyrics 29 * @param title The song title 30 * @param artist The artist name 31 * @param embedding The vector embedding for the song 32 */ 33 public void storeEmbedding(String lyrics, String title, String artist, List<Double> embedding) { 34 Document songDocument = new Document() 35 .append("title", title) 36 .append("artist", artist) 37 .append("lyrics", lyrics) 38 .append("embedding", embedding); 39 songCollection.insertOne(songDocument); 40 } 41 42 /** 43 * Fetch similar songs using MongoDB's $vectorSearch aggregation based on the playlist embedding. 44 * 45 * @param playlistEmbedding The playlist title embedding used as the query vector 46 * @return List of Song objects representing similar songs 47 */ 48 public List<Song> getSimilarSongs(List<Double> playlistEmbedding) { 49 List<Document> similarSongsDocs = new ArrayList<>(); 50 51 // Perform the vector search using the generated embedding 52 String indexName = "vector_index"; 53 int numCandidates = 150; 54 int limit = 10; 55 56 List<Document> pipeline = Arrays.asList( 57 new Document("$vectorSearch", 58 new Document("index", indexName) 59 .append("path", "embedding") 60 .append("queryVector", playlistEmbedding) 61 .append("numCandidates", numCandidates) 62 .append("limit", limit) 63 ), 64 new Document("$limit", limit) 65 ); 66 67 try { 68 songCollection.aggregate(pipeline).into(similarSongsDocs); 69 } catch (Exception e) { 70 throw new RuntimeException("Failed to retrieve similar songs", e); 71 } 72 73 74 List<Song> similarSongs = new ArrayList<>(); 75 for (Document doc : similarSongsDocs) { 76 Song song = new Song( 77 doc.getString("title"), 78 doc.getString("artist"), 79 doc.getString("lyrics"), 80 doc.getList("embedding", Double.class) 81 ); 82 similarSongs.add(song); 83 } 84 85 return similarSongs; 86 } 87 }
O método
storeEmbedding()
armazena o título, o artista, a letra e a incorporação da faixa como um documento no MongoDB.O método
getSimilarSongs()
executa uma pesquisa vetorial utilizando a operação$vectorSearch
do MongoDB. Ele usa a incorporação para o nome da lista de reprodução e recupera uma lista de músicas com incorporações semelhantes.A última etapa da configuração de nosso banco de dados de dados para nos configurar é criar o índicevectorSearch para as incorporações de músicas armazenadas no banco de banco de dados.
Estaremos indexando o campo
embedding
da songs
em nosso banco de banco de dados MongoDB Atlas . Este campo contém a representação vetorial das letras de cada faixa que geramos.- Faça login no MongoDB Atlas e Go para a páginaClusters do nosso projeto.
- Na barra lateral, navegamos para Atlas Search sob o títuloServiços.
- Vamos clicar em Criar índice de pesquisa.
- No modal que aparece:
- Nome do índice: insira um nome exclusivo para o seu índice (por exemplo,
vector_index
). - Banco de dados: selecione seu banco de dados de dados (por exemplo,
music
). - Collection: selecione sua collection (por exemplo,
songs
).
- Escolha Editor JSON e clique em Avançar.
- Defina o índice utilizando a seguinte estrutura JSON:
1 { 2 "fields": [ 3 { 4 "type": "vector", 5 "path": "embedding", 6 "numDimensions": 300, // The number of dimensions of the vectors 7 "similarity": "dotProduct" // Similarity metric (cosine, euclidean, or dotProduct) 8 } 9 ] 10 }
- type: especifica que o campo é um tipo de vetor (usado para embeddings).
- caminho: O nome do campo que você está indexando (
embedding
, em nosso caso). - numDimensions: o número de dimensões no vetor. Como estamos usando incorporações GloVe, isso é 300.
- similaridade: define a métrica de similaridade. Em nosso caso, usamos
"cosine"
para medir a similaridade com base no ângulo entre os vetores.
- Clique emAvançar para revisar sua configuração de índice.
- Clique em "criar índice de pesquisa".
Depois que o índice for criado, o Atlas começará a criá-lo, e você poderá usar
$vectorSearch
queries para encontrar músicas com base em suas incorporações.A funcionalidade principal do nosso aplicação está na criação de uma lista de reprodução com base no título da lista de reprodução inserido. Para fazer isso, precisamos criar uma incorporação para o nosso título, assim como faria para as letras das nossas músicas. Em seguida, usamos o Atlas Vector Search com nosso título incorporado para consultar o banco de banco de dados do MongoDB e encontrar a faixa semanticamente mais semelhante.
Essa implementação Go em nosso
PlaylistService
, em nosso pacoteService
, que dependerá do nossoEmbeddingService
existente (para gerar incorporações para o nome da lista de reprodução) e MongoDBRepository
(para executar as operações em nosso Banco de Dados MongoDB ).Vamos detalhar a aula
PlaylistService
classe a passo.Começamos marcando
PlaylistService
como um serviço Spring e injetando as dependências necessárias: EmbeddingService
para gerar as incorporações e MongoDBRepository
para nossas operações de banco de dados de dados MongoDB .1 2 public class PlaylistService { 3 4 private final EmbeddingService embeddingService; 5 private final MongoDBRepository songRepository; 6 7 public PlaylistService(EmbeddingService embeddingService, MongoDBRepository songRepository) { 8 this.embeddingService = embeddingService; 9 this.songRepository = songRepository; 10 } 11 }
Agora, vamos adicionar o método
generatePlaylist(String playlistName)
:1 public Playlist generatePlaylist(String playlistName) { 2 // Generate the embedding for the playlist title 3 List<Double> playlistEmbedding = embeddingService.embedText(playlistName); 4 5 if (playlistEmbedding == null || playlistEmbedding.isEmpty()) { 6 throw new RuntimeException("Failed to generate embedding for playlist: " + playlistName); 7 } 8 9 // Query the database to find similar songs 10 List<Song> similarSongs = songRepository.getSimilarSongs(playlistEmbedding); 11 12 // Construct and return the Playlist 13 return new Playlist(playlistName, similarSongs); 14 }
Quando um usuário fornece um nome de playlist, precisamos converter esse nome em uma representação vetorial que possa capturar o significado semântica do texto.
Depois de gerar a incorporação, o próximo passo é encontrar músicas com incorporações semelhantes. Passamos a incorporação da playlist para
MongoDBRepository
, que realiza uma pesquisa vetorial para encontrar músicas que correspondam à energia do nome da playlist.Depois de recuperar as músicas semelhantes do MongoDB, criamos e retornamos um objeto
Playlist
que contém o nome da lista de reprodução e a lista de músicas.A próxima etapa é expor essa funcionalidade por meio de uma REST API (se você desejar).
Para garantir que nosso gerador de lista de reprodução esteja funcionando corretamente, precisamos carregar dados de exemplo no MongoDB e, em seguida, testar a geração de uma lista de reprodução com base em um nome fornecido pelo usuário. Vamos carregar dados de um arquivo CSV e criar endpoints para testar a geração de lista de reprodução.
Antes de podermos testar a geração da lista de reprodução, precisamos de dados de músicas (letras) armazenados no MongoDB. Usaremos um arquivo CSV (
song_lyrics.csv
) que contém o título da faixa, o artista, a letra e outros metadados. A classeSongLyricsProcessor
classe com a leitura deste CSV e armazenará os dados processados no MongoDB.Aqui está o código para
SongLyricsProcessor
:1 import org.apache.commons.csv.CSVFormat; 2 import org.apache.commons.csv.CSVParser; 3 import org.apache.commons.csv.CSVRecord; 4 import org.example.repository.MongoDBRepository; 5 import org.example.service.EmbeddingService; 6 import org.springframework.stereotype.Component; 7 8 import java.io.InputStreamReader; 9 import java.io.Reader; 10 import java.util.List; 11 import java.util.Objects; 12 13 14 public class SongLyricsProcessor { 15 16 private final EmbeddingService embeddingService; 17 private final MongoDBRepository mongoDBRepository; 18 19 public SongLyricsProcessor(EmbeddingService embeddingService, MongoDBRepository mongoDBRepository) { 20 this.embeddingService = embeddingService; 21 this.mongoDBRepository = mongoDBRepository; 22 } 23 24 public void processAndStoreLyrics(String csvFilePath) throws Exception { 25 Reader reader = new InputStreamReader( 26 Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream(csvFilePath)) 27 ); 28 29 CSVFormat csvFormat = CSVFormat.DEFAULT.builder() 30 .setHeader() 31 .setSkipHeaderRecord(true) 32 .build(); 33 34 CSVParser csvParser = new CSVParser(reader, csvFormat); 35 36 for (CSVRecord record : csvParser) { 37 String title = record.get("title"); 38 String artist = record.get("artist"); 39 String lyrics = record.get("lyrics"); 40 String languageCld3 = record.get("language_cld3"); 41 String languageFt = record.get("language_ft"); 42 43 // Check if both language_cld3 and language_ft are 'en' (English) 44 if (!"en".equalsIgnoreCase(languageCld3) || !"en".equalsIgnoreCase(languageFt)) { 45 continue; 46 } 47 48 // Tokenize the lyrics and generate the embedding 49 List<Double> lyricsEmbedding = embeddingService.embedText(lyrics); 50 51 // Store the title, artist, lyrics, and embedding in MongoDB 52 mongoDBRepository.storeEmbedding(lyrics, title, artist, lyricsEmbedding); 53 } 54 55 csvParser.close(); 56 } 57 }
Lemos o arquivo
song_lyrics.csv
. Cada registro no arquivo contém informações sobre uma faixa, como título, artista e letra. Armazenaremos algumas dessas informações como metadados.Para cada faixa, geramos uma incorporação para a letra. Os dados processados (titulo, artista, letra e incorporação) são armazenados no MongoDB.
Para trigger o carregamento de dados, expomos um endpoint
/loadSampleData
no PlaylistController
. Isso nos permitirá carregar os dados CSV no MongoDB enviando uma solicitação com o nome do arquivo.Essa é uma maneira bastante aleatória de implementar isso e NÃO é recomendada para produção, mas é absolutamente adequada para esta pequena demonstração.
1 2 public class PlaylistController { 3 4 private final PlaylistService playlistService; 5 private final SongLyricsProcessor songLyricsProcessor; 6 7 public PlaylistController(PlaylistService playlistService, SongLyricsProcessor songLyricsProcessor) { 8 this.playlistService = playlistService; 9 this.songLyricsProcessor = songLyricsProcessor; 10 } 11 12 13 public void loadSampleData( String fileName) throws Exception { 14 songLyricsProcessor.processAndStoreLyrics(fileName); 15 } 16 }
- /loadSampleData Endpoint: esse endpoint aciona o método
processAndStoreLyrics()
emSongLyricsProcessor
. Você passa o nome do arquivo CSV como um parâmetro de query (por exemplo,/loadSampleData?fileName=song_lyrics.csv
). - StreamLyricsProcessor: o
SongLyricsProcessor
injetado lê o arquivo CSV, processa os dados da faixa , gera incorporações e armazena tudo no MongoDB.
Depois que os dados forem carregados, precisamos de uma maneira de testar a geração da playlist. Para isso, vamos expor um endpoint
/newPlaylist
emPlaylistController
. Quando um usuário fornece um nome de playlist, geraremos uma incorporação para esse nome e consultaremos o MongoDB para músicas semelhantes.1 2 public Playlist newPlaylist( String playlistName) { 3 return playlistService.generatePlaylist(playlistName); 4 }
/newplaylist endpoint recebe um parâmetro de consulta
playlistName
(por exemplo, /newPlaylist?playlistName=sad%20girl%20wistful%20Friday%20evening
) e retorna uma lista de reprodução gerada com base nesse nome.Então aqui estamos nós. Seria a hora de uma "clube funclub no sábado à noite" ou de um "cristo de desgosto na segunda-feira de manhã".
Bem, primeiro vamos carregar nossos dados no banco de banco de dados:
1 curl -X GET "http://localhost:8080/loadSampleData?fileName=song_lyrics.csv"
Agora, isso levará algum tempo. Temos uma quantidade enorme de músicas para gravar. Portanto, faça uma copa, coloque um pouco de willie nelson e deixe de lado o mundo ao seu redor (ou monitore o dashboard do MongoDB Atlas para verificar as gravações no banco de banco de dados). Quando isso for concluído, ou você perder a paciência e decidir que vários milhares de músicas são suficientes para sua prova de conceito e interromper manualmente o processo, vamos criar nossa playlist.
Vamos nos acomodar em nossa melancolia e pedir nossa playlist do pior cenário.
1 curl -X GET "http://localhost:8080/newPlaylist?playlistName=sad%20girl%20wistful%20Friday%20evening"
Bem, se tudo der certo, você deverá ver algo assim em seu console.
1 { 2 "playlistName": "sad girl wistful Friday evening", 3 "songs": [ 4 { 5 "title": "When A Woman Loves", 6 "artist": "R. Kelly", 7 "lyrics": "...", 8 "embedding": "...", 9 }, 10 { 11 "title": "Lonely", 12 "artist": "Akon", 13 "lyrics": "...", 14 "embedding": "...", 15 }, 16 { 17 "title": "Monster", 18 "artist": "Lady Gaga", 19 "lyrics": "...", 20 "embedding": "...", 21 }, 22 { 23 "title": "All the Boys", 24 "artist": "Keri Hilson", 25 "lyrics": "...", 26 "embedding": "...", 27 } 28 ] 29 }
Está muito longe de ser uma implementação perfeita, mas, dadas as limitações que aceitamos ao usar um modelo de linguagem geral em letras de músicas, Acon e Swift nos quatro principais resultados é uma lista de reprodução muito boa.
Neste tutorial, mostramos como criar um gerador de listas de reprodução personalizado usando o aprendizado detalhado4j para incorporar letras de músicas e o MongoDB Atlas Vector Search para consultar músicas semelhantes. Embora essa implementação tenha limitações, como o uso de incorporações pré-treinadas, ela abre um mundo de possibilidades para gerar playlists com base em nomes e energias funky.
Se você encontrou este tutorial útil, consulte o MongoDB Developer Center para obter mais tutoriais Java com MongoDB e aprenda como fazer coisas como geração aumentada de recuperação com MongoDB e Spring AI. Ou acesse os fóruns da comunidade MongoDB para fazer perguntas e ver o que outras pessoas estão construindo com o MongoDB.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.