Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Produtoschevron-right
Atlaschevron-right

Comparação de técnicas de NLP para Atlas Search de produtos escaláveis

como
VH
NC
MM
Adit Shrimal, Varun Hande, Neil Chitre, Manav Middha8 min read • Published Sep 23, 2024 • Updated Sep 23, 2024
IAPythonAtlas
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
No setor de varejo online em rápida expansão, os dados estão sendo gerados em uma escala sem precedentes. Para fornecer uma experiência de compra perfeita aos clientes, é crucial criar uma arquitetura eficiente e escalável do Atlas Search para armazenar e processar dados. Neste artigo, compararemos quatro técnicas populares de processamento de linguagem natural (NLP) - BM25, TF-IDF, Word2Vec e BERT - para encontrar empiricamente a solução mais ideal para recuperar os resultados mais relevantes para uma query do Atlas Search a partir de um grande corpus de produtos.

Onde está o código?

O código completo deste projeto pode ser encontrado aqui.

Processamento de dados

Nossos dados são obtidos a partir das APIs de dados de produtos em tempo real da Asos (um revendedor on-line de roupas, calçados, acessórios etc.). Utilizamos Apache Spark, GCP Storage (GSS) e MongoDB para armazenar e processar dados orquestrados pelo Apache Airflow.
Nosso pipeline de processamento de dados consiste em três partes principais:

1. Buscar dados de APIs e escrever no GCS

Primeiro buscamos as categorias de produtos. Em seguida, obtemos os produtos em cada categoria e armazenamos esses dados no GCP Storage. O trecho do Python abaixo ilustra como buscamos os dados usando o RapidAPI e armazenamos os dados no GCS:
1import json
2import requests
3
4url = "https://asos2.p.rapidapi.com/categories/list"
5headers = {
6 "x-rapidapi-key": "YOUR_RAPIDAPI_KEY",
7 "x-rapidapi-host": "asos2.p.rapidapi.com",
8}
9response = requests.request("GET", url, headers=headers)
10categories = json.loads(response.text)
11for category in categories:a
12 url = f"https://asos2.p.rapidapi.com/products/v2/list/{category['id']}"
13 response = requests.request("GET", url, headers=headers)
14 products = json.loads(response.text)
15 # Store products in GCS
16 bucket = os.environ.get('GS_BUCKET_NAME')
17 product_filename = f"products_{datetime.now().strftime('%H%m%S')}.json"
18 write(bucket, str(date.today()) + "/" + product_filename, products)

2. Pré-processamento

A próxima etapa é o pré-processamento. Como os nomes dos produtos geralmente não são limpos, tokenizamos os dados e removemos as palavras vazias. Para isso, usamos o RegexTokenizer e StopWordsRemover do SparkML.
1from pyspark.ml.feature import RegexTokenizer, StopWordsRemover
2
3# Instantiate and set input and output column parameters
4regexTokenizer = RegexTokenizer(
5 inputCol="product_name", outputCol="words", pattern="\\W"
6)
7remover = StopWordsRemover(inputCol="words", outputCol="filtered")
8
9# Transform the data
10regexTokenized = regexTokenizer.transform(df)
11removedStopWords = remover.transform(regexTokenized)

3. Gravar dados transformados no MongoDB

A última etapa é armazenar os documentos transformados em uma coleção MongoDB. MongoDBO do document model, a capacidade de lidar com eficiência com grandes volumes de dados, a funcionalidade de texto completo do Atlas Search e a fácil integração com ferramentas de big data, como o PySpark, fazem dele uma opção adequada para nossa pipeline de processamento de dados. O seguinte trecho de código mostra como escrever dados no MongoDB:
1import json
2
3from pymongo import MongoClient
4
5client = MongoClient(
6 "mongodb://<username>:<password>@cluster0.mongodb.net/test?retryWrites=true&w=majority"
7)
8db = client.get_database("product_db")
9products = db.products
10
11# Fetch data from GCS
12for product in gcs_data:
13 products.insert_one(product)

Experimentação de diferentes técnicas de NLP

Nosso objetivo principal era encontrar os produtos mais semelhantes para uma determinada query do Atlas Search. Experimentamos várias metodologias:

1. TF-IDF + similaridade do cosseno

TF-IDF, abreviação de Term Frequency – Inverse Document Frequency , é uma medida da importância de uma palavra para um documento em um corpus . É um método amplamente usado para codificar texto. Dada uma query de usuário, primeiro a codificamos usando TF-IDF e, em seguida, usamos a similaridade de cosseno para encontrar os produtos mais semelhantes em nosso catálogo.
1from pyspark.ml.feature import IDF, HashingTF
2
3hashingTF = HashingTF(inputCol="filtered", outputCol="rawFeatures", numFeatures=20)
4featurizedData = hashingTF.transform(removedStopWords)
5
6idf = IDF(inputCol="rawFeatures", outputCol="features")
7idfModel = idf.fit(featurizedData)
8rescaledData = idfModel.transform(featurizedData)

2. Palavra2Vec + similaridade de cosseno

Palavra2Vec é uma família de arquiteturas de modelo para aprender incorporações de palavras a partir de grandes conjuntos de dados. Aqui, usamos a implementação do Word2Vec no SparkML para incorporar a consulta do usuário e, em seguida, usamos a similaridade do cosseno para encontrar os produtos mais semelhantes em nosso catálogo.
1from pyspark.ml.feature import Word2Vec
2
3word2Vec = Word2Vec(
4 vectorSize=300, minCount=0, inputCol="filtered", outputCol="features"
5)
6model = word2Vec.fit(removedStopWords)
7result = model.transform(removedStopWords)

3. BM25

BM25, abreviação de "Best Match 25, ", é uma função de classificação usada em sistemas de recuperação de informações para classificar documentos com base em sua relevância para uma determinada query do Atlas Search. É um modelo de classificação probabilístico e é conhecido por sua capacidade de equilibrar a frequência de termo (TF) e a frequência inversa de documentos (IDF), tornando-o uma versão mais sofisticada do TF-IDF.
Em nosso projeto, implementamos o BM25 do zero no PySpark devido à falta de uma implementação direta na biblioteca. Aqui está uma visão mais detalhada de nossa abordagem e código:
Primeiro, calculamos os valores de IDF para cada termo, que é uma medida da quantidade de informações que a palavra fornece, ou seja, se é comum ou rara em todos os documentos.
Em segundo lugar, calculamos a frequência do termo em cada documento. A frequência do termo é simplesmente o número de vezes que uma palavra aparece em um documento.
Com essas duas métricas, poderíamos calcular a pontuação BM25 para cada documento em relação a uma query. Abaixo está um pseudocódigo simplificado para o processo:
1# Compute IDF values for each term
2# Compute term frequency in each document for each term
3# For each query term, do:
4# For each document containing the term do:
5# Compute BM25 Score
6# Sort documents by score

Construindo um índice invertido

Para aumentar a eficiência de tempo do cálculo de TF e IDF todas as vezes para um produto, criamos um índice invertido. Esse índice invertido salva esses valores importantes antecipadamente para melhorar o tempo de recuperação quando alguém pesquisa um produto.
A primeira parte do pré-processamento envolve a derivação, que é o processo de reduzir palavras flexionadas (ou às vezes derivadas) à sua forma de derivação, base ou raiz. É feito para chegar à parte básica de uma palavra que carrega o significado. Por exemplo, a palavra matriz de “running” é “run.. Neste trabalho, usamos o algoritmo dePorter Stemming, um algoritmo de derivação popular e robusto.
1def stemming(words):
2 ps = PorterStemmer()
3 result = []
4
5 for w in words:
6 root_word = ps.stem(w)
7 result.append(root_word)
8
9 return result
10
11stemming_udf = udf(lambda x: stemming(x), ArrayType(elementType=StringType()))
12preprocessed_data = preprocessed_data.withColumn(
13"tokens", stemming_udf("filtered_words")
14)
Em seguida, nivelamos a lista de tokens em palavras individuais e contamos o número total de palavras únicas e palavras totais.
1words = preprocessed_data.select(explode("tokens").alias("word"))
2total_unique_words = words.select("word").distinct().count()
3total_words = words.count()
Quando tivermos uma lista de palavras, começaremos a construir o índice invertido. Para cada palavra em nosso conjunto de dados, criamos uma entrada em nosso índice, onde cada entrada aponta para uma lista de documentos que contêm a palavra, juntamente com a frequência da palavra em cada documento.
1id_str = concat_ws("", col("_id").cast(StringType()))
2
3word_count_df = (
4 preprocessed_data.select("_id", explode("tokens").alias("word"))
5 .groupBy("word", "_id")
6 .agg(size(collect_list("word")).alias("occurrence"))
7 .groupBy("word")
8 .agg(
9 map_from_entries(collect_list(struct(id_str.alias("id"), "occurrence"))).alias(
10 "documents"
11 )
12 )
13 .withColumnRenamed("word", "_id")
14).rdd.map(lambda row: (row["_id"], row["documents"]))
15
16inverted_index = word_count_df.collectAsMap()
Por fim, também calculamos o comprimento de todos os documentos e a contagem exclusiva de palavras para cada documento. Esses valores são usados posteriormente na fórmula BM25 .
1doc_length_df = (
2 preprocessed_data.select("_id", size("tokens").alias("length"))
3 .groupBy("_id")
4 .agg(sum(col("length")).alias("doc_length"))
5 .withColumn("_id", concat_ws("", col("_id").cast(StringType())))
6 .rdd.map(lambda row: (row["_id"], row["doc_length"]))
7)
8doc_length = doc_length_df.collectAsMap()
9
10doc_length["word_counter"] = total_unique_words
11doc_length["doc_counter"] = total_words
12
13unique_word_count_df = preprocessed_data.select(
14 "_id",
15 size(array_distinct("tokens")).alias("num_unique_words"),
16).withColumn("_id", concat_ws("", col("_id").cast(StringType()))).rdd.map(lambda row: (row["_id"], row["num_unique_words"]))
17unique_word = unique_word_count_df.collectAsMap()
Isso conclui o pré-processamento e a etapa de construção de índice invertido. O índice invertido é um componente essencial da função de classificação do BM25 , pois permite a recuperação rápida de documentos relevantes para uma determinada query.
Agora, vamos mergulhar de cabeça na implementação do algoritmo BM25 .

Implementação do BM25

A fórmula matemática para BM25 é a seguinte:
BM25 fórmula matemática
Onde:
  • f(q_i, D): frequência do termo de query q_i no documento D
  • |D|: contagem de palavras do documento D
  • avgdl: contagem média de palavras em todos os documentos
  • k_1 e b: Parâmetros de ajuste, geralmente definidos como k_1 em [1.2, 2.0] e b=0.75
  • IDF(q_i): peso do termo de query q_i com base em sua raiz em todos os documentos
Veja como implementamos o algoritmo acima no Python:
1def bm25(doc_length,
2 inverted_index,
3 unique_word,
4 word_counter,
5 doc_counter,
6 query):
7 k1 = 1.2
8 b = 0.75
9 k2 = 100
10 N = len(doc_length) - 2
11 avgdl = doc_length["doc_counter"] / N
12
13 score = defaultdict(int)
14 for word in query:
15 if word in inverted_index:
16 n = len(inverted_index[word])
17 idf = math.log((N - n + 0.5) / (n + 0.5))
18 for doc, freq in inverted_index[word].items():
19 f = freq
20 qf = query.count(word)
21 dl = doc_length[doc]
22 K = k1 * ((1 - b) + b * (float(dl) / float(avgdl)))
23 R1 = idf * ((f * (k1 + 1)) / (f + K))
24 R2 = (qf * (k2 + 1)) / (qf + k2)
25 R = R1 * R2
26 score[doc] += R
27 else:
28 Continue
29
30 return score
Essa função usa o dicionário de comprimento do documento, o índice invertido, o dicionário de contagem exclusiva de palavras, o contador total de palavras, o contador de documentos e a query do Atlas Search. Ele calcula a pontuação BM25 para cada documento em relação à query e retorna um dicionário em que as chaves são os IDs do documento e os valores são as pontuações BM25 correspondentes. Os documentos com as pontuações BM25 mais altas são os mais relevantes para a query. Neste trabalho, escolhemos valores para k1, b e k2 com base em outros estudos existentes.

4. BERT

O BERT, abreviação de Bidirecional Encoder Representations from Transformers, foi um dos primeiros exemplos de modelos de linguagem grandes (LLMs) e usa uma arquitetura de codificador de transformação para representar texto como vetores. Usamos um modelo BERT pré-treinado para mapear a query do usuário em um espaço vetorial de alta dimensão e, em seguida, usar a similaridade do cosseno para encontrar os produtos mais semelhantes em nosso catálogo.
1from sentence_transformers import SentenceTransformer
2
3model = SentenceTransformer('bert-base-nli-mean-tokens')
4sentence_embeddings = model.encode(data['product_name'])

Resultados

Cada um dos modelos tinha seus pontos fortes e fracos. Embora o TF-IDF seja o mais fácil de interpretar, ele tem dificuldade em capturar o significado semântica dos dados. A palavra2Vec estava limitada a lidar com queries presentes no corpus de treinamento. O BM25 foi muito rápido, mas faltava compreensão semântica. Entre os algoritmos que testamos, o melhor modelo foi o BERT, pois ele poderia incorporar o significado semântica dos dados e lidar com queries em vários idiomas.
Para nosso projeto, usamos uma instância de computação acelerada por CPU no Amazon Web Services. Abaixo estão os detalhes das especificações que usamos:
  • Versão do Databricks Runtime: 12.1 ML (inclui Apache Spark 3.3.1, CPU, Scala 2.12)
  • Tipos de trabalhador/driver: g4dn.xlarge
  • Número de trabalhadores: mínimo 2, máximo 5
  • Recursos por trabalhador: 32–80 GB de memória, 8–20 Núcleos
  • Recursos do driver: 16 GB de memória, 4 núcleos
Comparamos o desempenho dos quatro algoritmos do Atlas Search (BM25, TF-IDF, palavra2vec e BERT) em três queries distintas: "sneakers", "t-camiseta" e "chappals" (que significa sandlias/chinelas em Hindu). Os resultados variaram em termos de velocidade e relevância para cada combinação de query e algoritmo. Um resumo dos resultados é o seguinte:
  • Query do Sneakers: o BM25 foi o mais rápido a retornar resultados relevantes. Tanto o TF-IDF quanto o Word2Vec foram mais lentos em comparação com o BM25, enquanto o BERT oferece um equilíbrio entre velocidade e relevância.
  • Consulte25 Palavra2Vec teve um desempenho ruim em termos de velocidade e relevância, semelhante ao TF-IDF. O BERT foi moderadamente lento, mas forneceu resultados altamente relevantes.
  • Querydo Appals : Appall é uma palavra em Hindu para sandalias de cabeda. Mais uma vez, BM25 se superou em termos de velocidade, mas forneceu resultados irrelevantes devido a limitações de linguagem no corpus. A palavra2Vec e o TF-IDF tiveram métricas de desempenho semelhantes às da query "t-camiseta". O BERT demonstrado novamente sua eficácia ao fornecer resultados altamente relevantes, mesmo com a complexidade de lidar com vários idiomas.
Resultados TD-IDF na query "chappals "
Os resultados do TF-IDF para a query "chappals " mostram a luta do algoritmo com queries de vários idiomas.
 Resultados da palavra2Vec na query "chappals "
Os resultados do Word2Vec demonstram suas limitações ao lidar com diversos idiomas.
BM25 resultados na query "chappals "
BM25 responde rapidamente, mas produz resultados irrelevantes devido a limitações de linguagem no corpus.
Resultados BERT na query "chappals "
O BERT recupera com sucesso resultados relevantes, mostrando sua força no tratamento de queries em vários idiomas.

Conclusão

Neste artigo, comparamos quatro técnicas diferentes de processamento de linguagem natural (NLP) - BM25, TF-IDF, Word2Vec e BERT - para encontrar empiricamente a solução mais ideal para recuperar os resultados mais relevantes para um Query do Atlas Search de um grande corpus de produtos.
Nossos experimentos primários mostram que o algoritmo BM25 é o mais eficiente em termos de tempo, enquanto o BERT fornece a melhor compensação entre velocidade e relevância. Essa observação está alinhada com o que vemos na prática no mundo real, com o BM25 sendo um algoritmo popular para a Atlas Search lexical, e a Atlas Search vetorial sendo a escolha preferida para a Atlas Search semântica. A Atlas Search híbrida, uma técnica que combina os pontos fortes da palavra-chave e da semântica Atlas Search, está se tornando cada vez mais comum.
O MongoDB Atlas oferece suporte ao Atlas Search lexical, vetorial e híbrida. Veja alguns recursos para começar:
Em caso de dúvidas, entre em contato conosco nos fóruns da comunidade!
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.
Iniciar a conversa

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Como criar um sistema RAG usando o Claude 3 Opus e o MongoDB


Aug 28, 2024 | 15 min read
exemplo de código

EHRS-Peru


Sep 11, 2024 | 3 min read
Artigo

Como criar aplicativos sem servidor com SST e MongoDB Atlas


Aug 29, 2024 | 3 min read
Tutorial

Transmissão de dados do MongoDB para o BigQuery usando conectores Confluent


Jul 11, 2023 | 4 min read
Sumário