Estratégias de chunking para aplicativos LLM
No contexto da construção de aplicativos relacionados aos LLMs, o chunking é o processo de dividir grandes partes de texto em segmentos menores. É uma técnica essencial que ajuda a otimizar a relevância do conteúdo que recebemos de um banco de dados vetorial quando usamos o LLM para incorporar emdeb o conteúdo. Nesta postagem do blog, exploraremos se e como isso ajuda a melhorar a eficiência e a precisão em aplicativos relacionados aos LLMs.
Como sabemos, qualquer conteúdo que indexamos num banco de dados vetorial precisa ser incorporado embedding primeiro. A principal razão para o chunking é garantir que estamos incorporando um conteúdo com o mínimo de ruído possível que ainda seja semanticamente relevante.
Por exemplo, na busca semântica, indexamos um corpus de documentos, com cada documento contendo informações valiosas sobre um tópico específico. Ao aplicar uma estratégia eficaz de chunking, podemos garantir que nossos resultados de pesquisa capturem com precisão a essência da consulta do usuário. Se nossos blocos, ou chunks, forem muito pequenos ou muito grandes, isso pode levar a resultados de pesquisa imprecisos ou oportunidades perdidas de exibir conteúdo relevante. Como regra geral, se o chunk faz sentido sem o contexto circundante para um ser humano, também fará sentido para o modelo de linguagem. Portanto, encontrar o tamanho ideal do chunk para os documentos no corpus é crucial para garantir que os resultados da pesquisa sejam precisos e relevantes.
Outro exemplo são os agentes de conversação (que abordamos antes de entrar no código). Usamos os chunks incorporados embedding para construir o contexto para o agente conversacional baseado em uma base de conhecimento que fundamenta o agente em informações confiáveis. Nessa situação, é importante fazer a escolha certa sobre nossa estratégia de chunking por dois motivos:
1º - isso determinará se o contexto é realmente relevante para nosso prompt.
2º - isso determinará se seremos ou não capazes de encaixar o texto recuperado no contexto antes de enviá-lo para um provedor de modelo externo (por exemplo, OpenAI), dadas as limitações do número de tokens que podemos enviar para cada solicitação.
Em alguns casos, como ao usar GPT-4 com uma janela de contexto de 32k, ajustar os chunks pode não ser um problema. Ainda assim, precisamos estar atentos quando estivermos usando chunks muito grandes, pois isso pode afetar negativamente a relevância dos resultados que serão enviados ao banco vetorial.
Nesta postagem, exploraremos vários métodos de chunking e discutiremos as compensações que você deve considerar ao escolher um tamanho e um método de chunking. Por fim, daremos algumas recomendações para determinar o melhor tamanho e método de chunking que serão apropriados para sua aplicação.
Incorporando embedding conteúdo curto e longo
Quando realizamos o embedding do nosso conteúdo, podemos antecipar comportamentos distintos, dependendo se o conteúdo é curto (como frases) ou longo (como parágrafos ou documentos inteiros).
Quando uma frase é incorporada embedding, o vetor resultante se concentra no significado específico da frase. A comparação seria feita naturalmente nesse nível quando comparada a outras incorporações embeddings de sentenças. Isso também implica que a incorporação embeddings pode perder informações contextuais mais amplas encontradas em um parágrafo ou documento.
Quando um parágrafo ou documento completo é incorporado embedding, o processo de incorporação embeddings considera o contexto geral e as relações entre as sentenças e frases dentro do texto. Isso pode resultar em uma representação vetorial mais abrangente que captura o significado e os temas mais amplos do texto. Tamanhos de texto de entrada maiores, por outro lado, podem introduzir ruído ou diluir o significado de sentenças ou frases individuais, tornando mais difícil encontrar correspondências precisas ao consultar o índice.
O comprimento da consulta também influencia como as incorporações embeddings se relacionam umas com as outras. Uma consulta mais curta, como uma única frase ou uma sentença, se concentrará em detalhes e pode ser mais adequada para correspondência com incorporações embeddings em nível de frase. Uma consulta mais longa que abrange mais de uma frase ou um parágrafo pode estar mais em sintonia com as incorporações embeddings no nível do parágrafo ou do documento porque provavelmente está procurando um contexto ou temas mais amplos.
O índice/index também pode ser não homogêneo e conter embeddings para blocos de tamanhos variados. Isso pode representar desafios em termos de relevância do resultado da consulta, mas também pode ter algumas consequências positivas. Por um lado, a relevância do resultado da consulta pode flutuar devido a discrepâncias entre as representações semânticas de conteúdo longo e curto. Por outro lado, um índice/index não homogêneo poderia capturar uma gama mais ampla de contexto e informações, pois diferentes tamanhos de chunks representam diferentes níveis de granularidade no texto. Isso poderia acomodar diferentes tipos de consultas com mais flexibilidade.
Considerações do chunking
Várias variáveis desempenham um papel na determinação da melhor estratégia de chunking e essas variáveis variam dependendo do caso de uso. Aqui estão alguns aspectos-chave a ter em mente:
Qual é a natureza do conteúdo que está sendo indexado? Você está trabalhando com documentos longos, como artigos ou livros, ou com conteúdo mais curto, como tweets ou mensagens instantâneas? A resposta ditaria qual modelo seria mais adequado para o seu objetivo e, consequentemente, qual estratégia de chunking aplicar.
Qual modelo de incorporação/embedding você está usando e em quais tamanhos de chunk ele funciona de maneira ideal? Por exemplo, modelos de sentence-transformer funcionam bem em sentenças individuais, mas um modelo como text-embedding-ada-002 funciona melhor em chunks contendo 256 ou 512 tokens.
Quais são suas expectativas em relação à duração e à complexidade das consultas dos usuários? Serão curtos e específicos ou longos e complexos? Isso também pode informar a maneira como você escolhe segmentar seu conteúdo, para que haja uma correlação mais próxima entre a consulta incorporada e os fragmentos/chunks incorporados
embeddings.Como os resultados recuperados serão utilizados em seu aplicativo específico? Por exemplo, eles serão usados para pesquisa semântica, resposta a perguntas, resumo ou outros propósitos? Por exemplo, se seus resultados precisarem ser alimentados em outro LLM com um limite de token, você terá que levar isso em consideração e limitar o tamanho dos chunks com base no número de chunks que gostaria de encaixar na solicitação para o LLM.
Responder a essas perguntas permitirá que você desenvolva uma estratégia de chunking que equilibre desempenho e precisão e, por sua vez, garantirá que os resultados da consulta sejam mais relevantes.
Métodos de Chunking
Existem diferentes métodos para agrupamento, e cada um deles pode ser apropriado para diferentes situações. Ao examinar os pontos fortes e fracos de cada método, nosso objetivo é identificar o cenário certo para aplicá-los.
Chunking de tamanho fixo/Fixed-size chunking
Essa é a abordagem mais comum e direta para o chunking: simplesmente decidimos o número de tokens em nosso chunk e, opcionalmente, se deve haver alguma sobreposição entre eles. Em geral, queremos manter alguma sobreposição entre os blocos para garantir que o contexto semântico não se perca entre os chunks. A fragmentação de tamanho fixo será o melhor caminho na maioria dos casos comuns. Comparado a outras formas de chunking, o chunking de tamanho fixo é computacionalmente barato e simples de usar, pois não requer o uso de nenhuma biblioteca NLP.
Aqui está um exemplo para realizar chunking de tamanho fixo com LangChain:
COPY
text = "..." # your text
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
    separator = "\n\n",
    chunk_size = 256,
    chunk_overlap  = 20
)
docs = text_splitter.create_documents([text])
Chunking “consciente de conteúdo”/“Content-aware” Chunking
Este é um conjunto de métodos para aproveitar a natureza do conteúdo que estamos agrupando e aplicar chunkings mais sofisticados a ele. Aqui estão alguns exemplos:
Divisão de sentença/Sentence splitting
Como mencionamos anteriormente, muitos modelos são otimizados para incorporar embedding conteúdo em nível de frase. Naturalmente, usaríamos divisão de sentenças, e existem várias abordagens e ferramentas disponíveis para fazer isso, incluindo:
Naive splitting: A abordagem mais ingênua seria dividir as sentenças por pontos (“.”) e novas linhas. Embora isso possa ser rápido e simples, essa abordagem não levaria em consideração todos os possíveis casos extremos. Aqui está um exemplo muito simples:
COPY
  text = "..." # your text
  docs = text.split(".")
NLTK: O Natural Language Toolkit (NLTK) é uma biblioteca Python popular para trabalhar com dados de linguagem humana. Ele fornece um tokenizer de sentença que pode dividir o texto em sentenças, ajudando a criar chunks mais significativos. Por exemplo, para usar NLTK com LangChain, você pode fazer o seguinte:
COPY
  text = "..." # your text
  from langchain.text_splitter import NLTKTextSplitter
  text_splitter = NLTKTextSplitter()
  docs = text_splitter.split_text(text)
spaCy: spaCy é outra poderosa biblioteca Python para tarefas de NLP. Ele oferece um sofisticado recurso de segmentação de sentenças que pode dividir o texto de forma eficiente em sentenças separadas, permitindo uma melhor preservação do contexto nos chunks resultantes. Por exemplo, para usar spaCy com LangChain, você pode fazer o seguinte:
COPY
  text = "..." # your text
  from langchain.text_splitter import SpacyTextSplitter
  text_splitter = SpaCyTextSplitter()
  docs = text_splitter.split_text(text)
Chunking Recursivo/Recursive Chunking
O chunking recursiva divide o texto de entrada em partes menores de maneira hierárquica e iterativa usando um conjunto de separadores. Se a tentativa inicial de dividir o texto não produzir chunks do tamanho ou estrutura desejados, o método chama a si mesmo recursivamente nos chunks resultantes com um separador ou critério diferente até que o tamanho ou estrutura do chunk desejado seja alcançado. Isso significa que, embora os pedaços não tenham exatamente o mesmo tamanho, eles ainda “aspirarão” a ter um tamanho semelhante.
Aqui está um exemplo de como usar fragmentação recursiva com LangChain:
COPY
text = "..." # your text
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
    # Set a really small chunk size, just to show.
    chunk_size = 256,
    chunk_overlap  = 20
)
docs = text_splitter.create_documents([text])
Fragmentação especializada/Specialized chunking
Markdown e LaTeX são dois exemplos de conteúdo estruturado e formatado que você pode encontrar. Nesses casos, você pode usar métodos de chunking especializados para preservar a estrutura original do conteúdo durante o processo de chunking.
Markdown: Markdown é uma linguagem de marcação leve comumente usada para formatar texto. Ao reconhecer a sintaxe Markdown (por exemplo, cabeçalhos, listas e blocos de código), você pode dividir o conteúdo de forma inteligente com base em sua estrutura e hierarquia, resultando em chunks semanticamente mais coerentes. Por exemplo:
COPY
  from langchain.text_splitter import MarkdownTextSplitter
  markdown_text = "..."
  markdown_splitter = MarkdownTextSplitter(chunk_size=100, chunk_overlap=0)
  docs = markdown_splitter.create_documents([markdown_text])
LaTex: LaTeX é um sistema de preparação de documentos e linguagem de marcação frequentemente usado para trabalhos acadêmicos e documentos técnicos. Ao analisar os comandos e ambientes do LaTeX, você pode criar chunks que respeitam a organização lógica do conteúdo (por exemplo, seções, subseções e equações), levando a resultados mais precisos e contextualmente relevantes. Por exemplo:
COPY
  from langchain.text_splitter import LatexTextSplitter
  latex_text = "..."
  latex_splitter = LatexTextSplitter(chunk_size=100, chunk_overlap=0)
  docs = latex_splitter.create_documents([latex_text])
Descobrindo o melhor tamanho de bloco para sua aplicação
Aqui estão algumas dicas para ajudá-lo a encontrar um tamanho de fragmento ideal se as abordagens comuns de chunking, como chunking fixa, não se aplicarem facilmente ao seu caso de uso.
Pré-processando seus dados - Você precisa primeiro pré-processar seus dados para garantir a qualidade antes de determinar o melhor tamanho de bloco para seu aplicativo. Por exemplo, se seus dados foram recuperados da web, talvez seja necessário remover tags HTML ou elementos específicos que apenas adicionam ruído.
Selecionando uma variedade de tamanhos de chunks - Uma vez que seus dados são pré-processados, a próxima etapa é escolher uma variedade de tamanhos de chunks potenciais para testar. Conforme mencionado anteriormente, a escolha deve levar em consideração a natureza do conteúdo (por exemplo, mensagens curtas ou documentos longos), o modelo de incorporação
embeddingsque você usará e seus recursos (por exemplo, limites de token). O objetivo é encontrar um equilíbrio entre preservar o contexto e manter a precisão. Comece explorando uma variedade de tamanhos de chunks, incluindo chunks menores (por exemplo, 128 ou 256 tokens) para capturar informações semânticas mais granulares e chunks maiores (por exemplo, 512 ou 1024 tokens) para reter mais contexto.Avaliando o desempenho de cada tamanho de chunk - Para testar vários tamanhos de chunks, você pode usar vários índices ou um único índice com vários namespaces*. Com um conjunto de dados representativo, crie as incorporações
embeddingspara os tamanhos de chunks que deseja testar e salve-os em seu índice (ou índices). Em seguida, você pode executar uma série de consultas para avaliar a qualidade e comparar o desempenho dos vários tamanhos de chunks. É mais provável que seja um processo iterativo, no qual você testa diferentes tamanhos de chunks em diferentes consultas até poder determinar o tamanho de chunk com melhor desempenho para seu conteúdo e consultas esperadas.
Conclusão
Dividir seu conteúdo é bastante simples na maioria dos casos - mas pode apresentar alguns desafios quando você começa a se desviar do caminho comum. Não existe uma solução única para o chunking, então o que funciona para um caso de uso pode não funcionar para outro. Esperançosamente, esta postagem ajudará você a obter uma intuição melhor sobre como abordar o agrupamento em seu aplicativo.
Obrigado por ler. Comente abaixo para compartilhar seus pensamentos.

