Os aplicativos modernos geralmente incorporam soluções de pesquisa para permitir que os usuários acessem rapidamente o conteúdo existente sob demanda. É difícil imaginar qualquer outra funcionalidade que possa recuperar essas informações com eficiência, tornando a pesquisa um recurso essencial na maioria dos aplicativos.
Simultaneamente, mesmo que a necessidade de consultar e pesquisar seja muito comum, diferentes aplicativos adotam abordagens drasticamente diferentes.
Na maioria dos casos, as empresas permanecem em um nível muito básico de pesquisa simplesmente consultando bancos de dados OLTP diretamente. As solicitações podem ter esta aparência: SELECT id, title FROM, entities WHERE, description LIKE '%bow%
.
No entanto, com mais frequência, eles são representados em estruturas complexas de junção de tabelas de vários níveis que são impossíveis de ler, lentas e primitivas. Eles são incapazes de compreender o contexto, exigem inúmeras personalizações e são muito difíceis de implementar adequadamente.
Embora seja possível melhorar o tempo de execução da consulta por meio de exibições materializadas, cache de consulta e outras técnicas, a complexidade adicional resulta em um atraso considerável entre atualizações primárias e resultados de pesquisa consistentes.
Alternativas mais eficientes para soluções primitivas de pesquisa baseadas em banco de dados podem constituir mecanismos de pesquisa de código aberto, como Apache Lucene, Apache Solr, Elasticsearch, Sphinx, MeiliSearch, Typesense, etc.
Eles tendem a ser comparativamente mais rápidos e muito melhores para lidar com consultas complexas e trabalhar com filtros. Mas, uma vez que esses mecanismos de pesquisa são comparados a equivalentes como o Google Search ou o DuckDuckGo, fica claro que as soluções de código aberto falham na criação de contexto de pesquisa e modalidades de consulta adequados - eles são incapazes de entender uma consulta se o usuário fornecer uma solicitação de pesquisa vaga.
Imagine que você simplesmente não consegue se lembrar como se chama aquela fruta cítrica amarela com sabor azedo! Mas você deseja pesquisar no aplicativo um artigo sobre como cultivar essa fruta misteriosa. Como você faz essa busca?
Sua consulta pode ser: “Como cultivar frutas cítricas amarelas dentro de casa”. Qualquer um dos mecanismos de pesquisa de código aberto mencionados acima pode ter dificuldades significativas para retornar resultados relevantes para essa consulta, mesmo que o banco de dados contenha artigos sobre o cultivo de “limões”.
Isso ocorre porque a extração de significado de uma consulta é uma tarefa de linguagem natural e é improvável que seja resolvida sem componentes de IA. O GPT-3 é bom nessa tarefa.
Ofertas OpenAI
A conversão do texto do documento em um vetor representativo pode ocorrer em segundo plano, enquanto a vetorização da consulta de pesquisa deve ocorrer em tempo de execução. Existem várias famílias de modelos GPT-3 que o OpenAI oferece:
text-search-ada-doc-001: 1024 text-search-babbage-doc-001: 2048 text-search-curie-doc-001: 4096 text-search-davinci-doc-001: 12288
Dimensões vetoriais mais altas levam a mais informações incorporadas e, portanto, também a custos mais altos e pesquisas mais lentas.
Os documentos geralmente são longos e as consultas geralmente são curtas e incompletas. Portanto, a vetorização de qualquer documento difere significativamente da vetorização de qualquer consulta considerando a densidade e o tamanho do conteúdo. A OpenAI sabe disso e, portanto, oferece dois modelos emparelhados, -doc
e -query
:
text-search-ada-query-001: 1024 text-search-babbage-query-001: 2048 text-search-curie-queryc-001: 4096 text-search-davinci-query-001: 12288
É importante observar que a consulta e o documento devem utilizar a mesma família de modelos e ter o mesmo comprimento do vetor de saída.
Pode ser mais fácil observar e entender o poder dessa solução de pesquisa por meio de exemplos. Para este exemplo, vamos nos basear no
O conjunto de dados contém muitas colunas, mas nosso processo de vetorização será construído em torno apenas das colunas de título e visão geral.
Title: Harry Potter and the Half-Blood Prince Overview: As Harry begins his sixth year at Hogwarts, he discovers an old book marked as 'Property of the Half-Blood Prince', and begins to learn more about Lord Voldemort's dark past.
Vamos mapear o conjunto de dados em texto pronto para indexação:
datafile_path = "./tmdb_5000_movies.csv" df = pd.read_csv(datafile_path) def combined_info(row): columns = ['title', 'overview'] columns_to_join = [f"{column.capitalize()}: {row[column]}" for column in columns] return '\n'.join(columns_to_join) df['combined_info'] = df.apply(lambda row: combined_info(row), axis=1)
O processo de incorporação é simples:
def get_embedding(text, model="text-search-babbage-doc-001"): text = text.replace("\n", " ") return openai.Embedding.create(input = [text], model=model)['data'][0]['embedding'] get_embedding(df['combined_info'][0])
Este bloco de código gera uma lista cujo tamanho é igual aos parâmetros com os quais o modelo está operando, que no caso de text-search-babbage-doc-001
é 2048.
Um processo de incorporação semelhante deve ser aplicado a todos os documentos nos quais desejamos pesquisar:
df['combined_info_search'] = df['combined_info'].apply(lambda x: get_embedding(x, model='text-search-babbage-doc-001')) df.to_csv('./tmdb_5000_movies_search.csv', index=False)
A coluna combined_info_search
manterá uma representação vetorial do combined_text.
E, surpreendentemente, já é isso! Por fim, estamos prontos para executar uma consulta de pesquisa de exemplo:
from openai.embeddings_utils import get_embedding, cosine_similarity def search_movies(df, query, n=3, pprint=True): embedding = get_embedding( query, engine="text-search-babbage-query-001" ) df["similarities"] = df.combined_info_search.apply(lambda x: cosine_similarity(x, embedding)) res = ( df.sort_values("similarities", ascending=False) .head(n) .combined_info ) if pprint: for r in res: print(r[:200]) print() return res res = search_movies(df, "movie about the wizardry school", n=3)
Title: Harry Potter and the Philosopher's StoneOverview: Harry Potter has lived under the stairs at his aunt and uncle's house his whole life. But on his 11th birthday, he learns he's a powerful wizard — with a place waiting for him at the Hogwarts School of Witchcraft and Wizardry. As he learns to harness his newfound powers with the help of the school's kindly headmaster, Harry uncovers the truth about his parents' deaths — and about the villain who's to blame. Title: Harry Potter and the Goblet of FireOverview: Harry starts his fourth year at Hogwarts, competes in the treacherous Triwizard Tournament and faces the evil Lord Voldemort. Ron and Hermione help Harry manage the pressure — but Voldemort lurks, awaiting his chance to destroy Harry and all that he stands for. Title: Harry Potter and the Prisoner of AzkabanOverview: Harry, Ron and Hermione return to Hogwarts for another magic-filled year. Harry comes face to face with danger yet again, this time in the form of an escaped convict, Sirius Black — and turns to sympathetic Professor Lupin for help.
A visão geral de ' Harry Potter e a Pedra Filosofal ' contém as palavras 'wizardry' e 'school' e vem primeiro na saída da pesquisa. O segundo resultado não contém mais a palavra 'escola', mas ainda retém palavras próximas a 'bruxo', 'Tribruxo'. O terceiro resultado contém apenas um sinônimo de 'bruxaria' — magia.
Há, é claro, uma infinidade de outros filmes neste banco de dados que apresentam escolas ou bruxos (ou ambos), mas os acima foram os únicos que retornaram para nós. Esta é uma prova clara de que a solução de pesquisa funciona e realmente entendeu o contexto de nossa consulta.
Usamos o modelo de Babbage com apenas 2.048 parâmetros. O Davinci possui seis vezes mais (12.288) parâmetros e pode, assim, ter um desempenho significativamente melhor em consultas de alta complexidade.
A solução de pesquisa pode ocasionalmente falhar em produzir resultados relevantes para algumas consultas. Por exemplo, a consulta 'filmes sobre bruxos na escola' produz:
Title: Harry Potter and the Philosopher's StoneOverview: Harry Potter has lived under the stairs at his aunt and uncle's house his whole life. But on his 11th birthday, he learns he's a powerful wizard — with a place waiting for him at the Hogwarts School of Witchcraft and Wizardry. As he learns to harness his newfound powers with the help of the school's kindly headmaster, Harry uncovers the truth about his parents' deaths — and about the villain who's to blame. Title: Dumb and Dumberer: When Harry Met LloydOverview: This wacky prequel to the 1994 blockbuster goes back to the lame-brained Harry and Lloyd's days as classmates at a Rhode Island high school, where the unprincipled principal puts the pair in remedial courses as part of a scheme to fleece the school. Title: Harry Potter and the Prisoner of AzkabanOverview: Harry, Ron and Hermione return to Hogwarts for another magic-filled year. Harry comes face to face with danger yet again, this time in the form of an escaped convict, Sirius Black — and turns to sympathetic Professor Lupin for help.
O que 'Dumb and Dumberer: When Harry Met Lloyd' está fazendo aqui, você pode se perguntar? Felizmente, esse problema não foi reproduzido em parâmetros com mais parâmetros.
A saída da pesquisa deve consistir em documentos classificados em ordem decrescente por relevância. Para conseguir isso, devemos estar cientes da distância entre a consulta atual e cada documento. Quanto menor o comprimento, comparativamente mais relevante é a saída. Então, após um alcance máximo definido, devemos parar de considerar a relevância dos demais documentos.
No exemplo acima, usamos
Algoritmos de cálculo de distância tendem a representar essa semelhança (diferença) entre uma consulta e um documento com um único número. No entanto, não podemos confiar no
Se desejar, você pode verificar o repositório resultante aqui:
Como alternativa, você pode brincar com ele no Google Colab aqui .
Usamos a abordagem de força bruta para classificar os documentos. Vamos determinar:
● n: número de pontos no conjunto de dados de treinamento
● d: dimensionalidade dos dados
A complexidade do tempo de busca para soluções de força bruta é O(n * d * n * log(n)) . O parâmetro d depende do modelo (no caso de Babbage, é igual a 2048) enquanto temos o bloco O(nlog(n)) devido à etapa de ordenação.
É fundamental lembrar-nos nesta fase que os modelos menores são mais rápidos e baratos. Por exemplo, na etapa de cálculo da similaridade do caso de busca, o modelo Ada é duas vezes mais rápido, enquanto o modelo Davinci é seis vezes mais lento.
Os cálculos de similaridade de cosseno entre a consulta e 4.803 documentos de 2.048 dimensões levaram 1.260 ms no meu M1 Pro. Na implementação atual, o tempo necessário para calcular cresceria linearmente em relação ao número total de documentos. Simultaneamente, essa abordagem oferece suporte à paralelização de computação.
Em soluções de pesquisa, as consultas devem ser concluídas o mais rápido possível. E esse preço geralmente é pago ao lado do treinamento e do tempo de pré-caching. Podemos usar estruturas de dados como uma árvore kd, r-tree ou ball tree. Considere o artigo de
As árvores Kd, ball tree e r-trees constituem estruturas de dados que são usadas para armazenar e buscar pontos no espaço N-dimensional com eficiência, como nossos vetores de significado.
As árvores Kd e ball são estruturas de dados baseadas em árvore que usam um esquema de particionamento binário iterativo para dividir o espaço em regiões, com cada nó na árvore representando uma sub-região. As árvores Kd são particularmente eficientes na busca de pontos dentro de um intervalo específico ou na localização do vizinho mais próximo de um determinado ponto.
Da mesma forma, as árvores r também são usadas para armazenar pontos no espaço N-dimensional, no entanto, elas são muito mais eficientes na busca de pontos dentro de uma região específica ou na localização de todos os pontos dentro de uma certa distância de um determinado ponto. É importante ressaltar que as árvores r usam um esquema de particionamento diferente das árvores kd e das bolas; eles dividem o espaço em 'retângulos' em vez de partições binárias.
As implementações de árvore estão fora do escopo deste artigo e diferentes implementações levarão a diferentes saídas de pesquisa.
Talvez a desvantagem mais significativa da solução de pesquisa atual seja que devemos chamar uma API OpenAI externa para recuperar o vetor de incorporação de consulta. Não importa a rapidez com que nosso algoritmo seja capaz de encontrar os vizinhos mais próximos, uma etapa de bloqueio sequencial será necessária.
Text-search-babbage-query-001 Number of dimensions: 2048 Number of queries: 100 Average duration: 225ms Median duration: 207ms Max duration: 1301ms Min duration: 176ms
Text-search-ada-query-002 Number of dimensions: 1536 Number of queries: 100 Average duration: 264ms Median duration: 250ms Max duration: 859ms Min duration: 215ms
Text-search-davinci-query-001 Number of dimensions: 12288 Number of queries: 100 Average duration: 379ms Median duration: 364ms Max duration: 1161ms Min duration: 271ms
Se tomarmos a mediana como ponto de referência, podemos ver que ada-002 é +28% mais lento e davinci-001 é +76% mais lento.
Referindo-se a
Além disso, os custos de treinamento são relativamente altos com o OpenAI.
Alternativamente, você pode considerar tentar
O Elasticsearch 8.0 oferece suporte à pesquisa de vizinho mais próximo (ANN) aproximada e eficiente, que pode ser usada para resolver nossas magnitudes de problemas mais rapidamente do que qualquer KNN linear. O Elasticsearch 8.0 utiliza um algoritmo ANN chamado Hierarchical Navigable Small World graphs (HNSW) para organizar vetores em gráficos com base na similaridade. Testado em um conjunto de dados de 10 milhões de vetores incorporados, alcançamos um desempenho impressionante de 200 consultas por segundo com ANN em uma única máquina com foco em computação, enquanto apenas 2 consultas por segundo usando KNN. Ambos foram fornecidos pelo Elasticsearch.
De acordo com o ElasticSearch
Como você deve ter visto, GPT-3 Embeddings não é a solução perfeita para todo e qualquer problema de busca devido à sua complexidade de indexação, custo, bem como a alta complexidade computacional da operação de busca, mesmo de aproximações. No entanto, GPT-3 Embeddings continua sendo uma excelente escolha para quem procura um backbone poderoso para uma solução de pesquisa com consultas pouco frequentes e requisitos de indexação modestos.
Além disso, vale acrescentar que a Microsoft recentemente __ anunciou __que o buscador Bing agora utiliza a nova versão atualizada do GPT 3.5, chamada 'Prometheus' e desenvolvida inicialmente para buscas. De acordo com o anúncio, o novo modelo de linguagem Prometheus permite que o Bing aumente a relevância, anote trechos com mais precisão, forneça resultados mais atualizados, entenda a geolocalização e melhore a segurança. Isso pode abrir novas possibilidades para o uso do modelo de linguagem autorregressiva para soluções de busca, que definitivamente ficaremos de olho daqui para frente.
Referências:
Também publicado aqui.