O crescente volume de publicações de pesquisa requer métodos eficientes para estruturar o conhecimento acadêmico. Essa tarefa normalmente envolve o desenvolvimento de um esquema subjacente supervisionado de classes e alocação de publicações para a classe mais relevante. Neste artigo, implementamos uma solução automatizada de ponta a ponta usando quantização de incorporação e um amplo Modelo de Idioma (LLM). Nosso estudo de caso começa com um conjunto de dados de 25.000 publicações da ARXIV da Computational Linguistics (CS.CL), publicada antes de julho de 2024, que organizamos sob um novo esquema de classes.
Nossa abordagem se concentra em três tarefas principais: (i) agrupamento não supervisionado do conjunto de dados ARXIV em coleções relacionadas, (ii) descobrindo as estruturas temáticas latentes em cada cluster e (iii) criando um esquema de taxonomia candidato com base nas estruturas temáticas.
Na sua essência, a tarefa de cluster requer a identificação de um número suficiente de exemplos semelhantes em um conjunto de dados não marcado . Esta é uma tarefa natural para incorporação, pois capturam relacionamentos semânticos em um corpus e podem ser fornecidos como recursos de entrada para um algoritmo de agrupamento para estabelecer links de similaridade entre os exemplos. Começamos transformando os pares ( título : resumo ) do nosso conjunto de dados em uma representação de incorporação usando Jina-Jobeddings-V2, um modelo de atenção baseado em Bert-Alibi. E aplicar quantização escalar usando os transformadores de frases e uma implementação personalizada.
Para agrupamentos, executamos o HDBSCAN em um espaço dimensional reduzido, comparando os resultados usando os métodos de agrupamento eom e leaf . Além disso, examinamos se o uso de (u)int8 INCEDDINGS REMAÇÃO Em vez de representações float32 afeta esse processo.
Para descobrir tópicos latentes dentro de cada aglomerado de publicações ARXIV, combinamos Langchain e Pydantic com Mistral-7B-Instrut-V0.3 (e GPT-4O, incluídos para comparação) em uma linha de LLM. A saída é então incorporada a um modelo de prompt refinado que orienta o Claude Sonnet 3.5 na geração de uma taxonomia hierárquica.
Os resultados sugerem 35 tópicos de pesquisa emergentes, em que cada tópico compreende pelo menos 100 publicações. Estes são organizados dentro de 7 classes pais no campo da linguística computacional (CS.CL). Essa abordagem pode servir como uma linha de base para gerar automaticamente esquemas de candidatos hierárquicos em categorias de ARXIV de alto nível e concluir eficientemente as taxonomias, abordando o desafio representado pelo crescente volume de literatura acadêmica.
Taxonomia Conclusão da literatura acadêmica com quantização de incorporação e uma linha de linha LLM
As incorporações são representações numéricas de objetos do mundo real, como texto, imagens e áudio que encapsulam informações semânticas dos dados que eles representam. Eles são usados por modelos de IA para entender domínios complexos de conhecimento em aplicativos a jusante, como agrupamentos, recuperação de informações e tarefas semânticas de entendimento, entre outras.
Mapearemos ( título : resumo ) pares das publicações do ARXIV para um espaço 768-dimensional usando Jina-Jobeddings-V2 [1], um modelo de incorporação de texto de código aberto capaz de acomodar até 8192 tokens. Isso fornece um comprimento de sequência suficientemente grande para títulos, resumos e outras seções de documentos que podem ser relevantes. Para superar o limite convencional de 512-token presente em outros modelos, Jina-Jobdingdings-V2 incorpora álibi bidirecional [2] na estrutura de Bert. O álibi (atenção com vieses lineares) permite a extrapolação do comprimento da entrada (isto é, sequências que excedam 2048 tokens), codificando informações posicionais diretamente dentro da camada de auto-ativação, em vez de introduzir incorporações posicionais. Na prática, influencia os escores de atenção da chave de consulta com uma penalidade proporcional à sua distância, favorecendo a atenção mútua mais forte entre os tokens próximos.
O primeiro passo para usar o modelo Jina-Jobddings-V2 é carregá-lo através de transformadores de sentenças, uma estrutura para acessar modelos de ponta que estão disponíveis no Hubging Face Hub:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer ( 'jinaai/jina-embeddings-v2-base-en' , trust_remote_code = True ) Agora codificamos ( título : resumo ) pares do nosso conjunto de dados usando batch_size = 64 . Isso permite a computação paralela em aceleradores de hardware, como as GPUs (embora ao custo de exigir mais memória):
from datasets import load_dataset
ds = load_dataset ( "dcarpintero/arxiv.cs.CL.25k" , split = "train" )
corpus = [ title + ':' + abstract for title , abstract in zip ( ds [ 'title' ], ds [ 'abstract' ])]
f32_embeddings = model . encode ( corpus ,
batch_size = 64 ,
show_progress_bar = True )A similaridade semântica entre corpora agora pode ser trivialmente calculada como o produto interno das incorporações. No mapa de calor a seguir, cada entrada [x, y] é colorida com base no referido produto de incorporação para frases de ' título ' exemplares [x] e [y].
Similário semântico em cs.cl arxivtles usando incorporação
A ampliação de incorporações pode ser desafiadora. Atualmente, os modelos de última geração representam cada incorporação como float32 , que requer 4 bytes de memória. Dado que Jina-Embetbeddings-V2 mapeia o texto para um espaço 768-dimensional, os requisitos de memória para o nosso conjunto de dados seriam em torno de 73 MB, sem índices e outros metadados relacionados aos registros de publicação:
25 , 000 embeddings * 768 dimensions / embedding * 4 bytes / dimension = 76 , 800 , 000 bytes
76 , 800 , 000 bytes / ( 1024 ^ 2 ) ≈ 73.24 MBNo entanto, trabalhar com um conjunto de dados maior pode aumentar significativamente os requisitos de memória e os custos associados:
| Incorporação Dimensão | Incorporação Modelo | 2,5m Abstracts Arxiv | 60,9m Páginas da Wikipedia | 100m Incorporação |
|---|---|---|---|---|
| 384 | Minilm-L12-V2 | 3,57 GB | 85,26 GB | 142.88 GB |
| 768 | All-MPNET-BASE-V2 | 7.15 GB | 170,52 GB | 285.76 GB |
| 768 | Jina-Embetbeddings-V2 | 7.15 GB | 170,52 GB | 285.76 GB |
| 1536 | OpenAi-Text-Embeting-3-Small | 14,31 GB | 341.04 GB | 571,53 GB |
| 3072 | OpenI-Text-Embeting-3-Large | 28.61 GB | 682.08 GB | 1.143 TB |
Uma técnica usada para obter economia de memória é quantização . A intuição por trás dessa abordagem é que podemos discretizar os valores do ponto flutuante mapeando seu alcance [ f_max , f_min ] em uma faixa menor de números de ponto fixo [ q_max , q_min ] e distribuindo linearmente todos os valores entre esses intervalos. Na prática, isso geralmente reduz a precisão de um ponto flutuante de 32 bits a larguras de bits, como 8 bits (quantização escalar) ou valores de 1 bit (quantização binária).
Quantização de incorporação escalar - de float32 a (u) int8
Ao plotar a distribuição de frequência das incorporações geradas por Jina , observamos que os valores estão realmente concentrados em torno de uma faixa relativamente estreita [-2,0, +2,0]. Isso significa que podemos efetivamente mapear os valores float32 para 256 (u)int8 sem perda significativa de informações:
import matplotlib . pyplot as plt
plt . hist ( f32_embeddings . flatten (), bins = 250 , edgecolor = 'C0' )
plt . xlabel ( 'float-32 jina-embeddings-v2' )
plt . title ( 'distribution' )
plt . show ()Distribuição Float32 Jina-Jina-Jina-Embeddings-V2
Podemos calcular os valores exatos [min, max] da distribuição:
> >> np . min ( f32_embeddings ), np . max ( f32_embeddings )
( - 2.0162134 , 2.074683 ) A primeira etapa para a implementação da quantização escalar é definir um conjunto de calibração de incorporação. Um ponto de partida típico é um subconjunto de incorporação de 10k, que no nosso caso cobriria quase 99,98% dos valores originais de incorporação float32 . O uso da calibração destina -se a obter valores representativos f_min e f_max ao longo de cada dimensão para reduzir a sobrecarga computacional e os possíveis problemas causados por discrepantes que podem aparecer em conjuntos de dados maiores.
def calibration_accuracy ( embeddings : np . ndarray , k : int = 10000 ) -> float :
calibration_embeddings = embeddings [: k ]
f_min = np . min ( calibration_embeddings , axis = 0 )
f_max = np . max ( calibration_embeddings , axis = 0 )
# Calculate percentage in range for each dimension
size = embeddings . shape [ 0 ]
avg = []
for i in range ( embeddings . shape [ 1 ]):
in_range = np . sum (( embeddings [:, i ] >= f_min [ i ]) & ( embeddings [:, i ] <= f_max [ i ]))
dim_percentage = ( in_range / size ) * 100
avg . append ( dim_percentage )
return np . mean ( avg )
acc = calibration_accuracy ( f32_embeddings , k = 10000 )
print ( f"Average percentage of embeddings within [f_min, f_max] calibration: { acc :.5f } %" )
> >> Average percentage of embeddings within [ f_min , f_max ] calibration : 99.98636 % A segunda e a terceira etapas da quantização escalar - escalas de computação e ponto zero e codificação - podem ser facilmente aplicadas com transformadores de frases, resultando em uma economia de memória 4x em comparação com a representação float32 original. Além disso, também nos beneficiaremos de operações aritméticas mais rápidas, pois a multiplicação da matriz pode ser realizada mais rapidamente com aritmética inteira.
from sentence_transformers . quantization import quantize_embeddings
# quantization is applied in a post-processing step
int8_embeddings = quantize_embeddings (
np . array ( f32_embeddings ),
precision = "int8" ,
calibration_embeddings = np . array ( f32_embeddings [: 10000 ]),
) f32_embeddings . dtype , f32_embeddings . shape , f32_embeddings . nbytes
>> > ( dtype ( 'float32' ), ( 25107 , 768 ), 77128704 ) # 73.5 MB
int8_embeddings . dtype , int8_embeddings . shape , int8_embeddings . nbytes
>> > ( dtype ( 'int8' ), ( 25107 , 768 ), 19282176 ) # 18.3 MB
# calculate compression
( f32_embeddings . nbytes - int8_embeddings . nbytes ) / f32_embeddings . nbytes * 100
>> > 75.0Para completude, implementamos um método de quantização escalar para ilustrar essas três etapas:
def scalar_quantize_embeddings ( embeddings : np . ndarray ,
calibration_embeddings : np . ndarray ) -> np . ndarray :
# Step 1: Calculate [f_min, f_max] per dimension from the calibration set
f_min = np . min ( calibration_embeddings , axis = 0 )
f_max = np . max ( calibration_embeddings , axis = 0 )
# Step 2: Map [f_min, f_max] to [q_min, q_max] => (scaling factors, zero point)
q_min = 0
q_max = 255
scales = ( f_max - f_min ) / ( q_max - q_min )
zero_point = 0 # uint8 quantization maps inherently min_values to zero
# Step 3: encode (scale, round)
quantized_embeddings = (( embeddings - f_min ) / scales ). astype ( np . uint8 )
return quantized_embeddings calibration_embeddings = f32_embeddings [: 10000 ]
beta_uint8_embeddings = scalar_quantize_embeddings ( f32_embeddings , calibration_embeddings ) beta_uint8_embeddings [ 5000 ][ 64 : 128 ]. reshape ( 8 , 8 )
array ([[ 187 , 111 , 96 , 128 , 116 , 129 , 130 , 122 ],
[ 132 , 153 , 72 , 136 , 94 , 120 , 112 , 93 ],
[ 143 , 121 , 137 , 143 , 195 , 159 , 90 , 93 ],
[ 178 , 189 , 143 , 99 , 99 , 151 , 93 , 102 ],
[ 179 , 104 , 146 , 150 , 176 , 94 , 148 , 118 ],
[ 161 , 138 , 90 , 122 , 93 , 146 , 140 , 129 ],
[ 121 , 115 , 153 , 118 , 107 , 45 , 70 , 171 ],
[ 207 , 53 , 67 , 115 , 223 , 105 , 124 , 158 ]], dtype = uint8 )Continuaremos com a versão das incorporações que foram quantizadas usando transformadores de sentenças (nossa implementação personalizada também está incluída na análise de resultados):
# `f32_embeddings` => if you prefer to not use quantization
# `beta_uint8_embeddings` => to check our custom implemention
embeddings = int8_embeddings Nesta seção, realizamos uma projeção de dois estágios de ( título : resumo ), incorporando pares de seu espaço original de alta dimensão (768) para as dimensões mais baixas, a saber::
5 dimensions para reduzir a complexidade computacional durante o agrupamento e2 dimensions para permitir a representação visual nas coordenadas (x, y) .Para ambas as projeções, empregamos a UMAP [3], uma técnica popular de redução de dimensionalidade conhecida por sua eficácia na preservação das estruturas de dados locais e globais. Na prática, isso o torna uma escolha preferida para lidar com conjuntos de dados complexos com incorporações de alta dimensão:
import umap
embedding_5d = umap . UMAP ( n_neighbors = 100 , # consider 100 nearest neighbors for each point
n_components = 5 , # reduce embedding space from 768 to 5 dimensions
min_dist = 0.1 , # maintain local and global balance
metric = 'cosine' ). fit_transform ( embeddings )
embedding_2d = umap . UMAP ( n_neighbors = 100 ,
n_components = 2 ,
min_dist = 0.1 ,
metric = 'cosine' ). fit_transform ( embeddings ) Observe que, quando aplicarmos o agrupamento HDBSCAN na próxima etapa, os clusters encontrados serão influenciados pela maneira como a UMAP preservou as estruturas locais. Um valor menor n_neighbors significa que a UMAP se concentrará mais nas estruturas locais, enquanto um valor maior permite capturar mais representações globais, o que pode ser benéfico para entender os padrões gerais nos dados.
As incorporações reduzidas ( título : resumo ) agora podem ser usadas como recursos de entrada de um algoritmo de cluster, permitindo a identificação de categorias relacionadas com base na incorporação de distâncias.
Optamos por HDBSCAN (agrupamento espacial baseado em densidade hierárquica de aplicações com ruído) [4], um algoritmo avançado de cluster que se estende a DBSCAN, adaptando-se a agrupamentos variados de densidade. Ao contrário do K-Means, que requer pré-especificar o número de clusters, o HDBScan possui apenas um hiperparâmetro importante, n , que estabelece o número mínimo de exemplos a serem incluídos em um cluster.
O HDBSCAN funciona primeiro transformando o espaço de dados de acordo com a densidade dos pontos de dados, tornando as regiões mais densas (áreas onde os pontos de dados estão juntos em números altos) mais atraentes para a formação de cluster. O algoritmo constrói uma hierarquia de clusters com base no tamanho mínimo do cluster estabelecido pelo hiperparâmetro n . Isso permite distinguir entre ruído (áreas esparsas) e regiões densas (agrupamentos em potencial). Finalmente, o HDBSCAN condensa essa hierarquia a derivar os grupos mais persistentes, identificando grupos de diferentes densidades e formas. Como método baseado em densidade, ele também pode detectar outliers.
import hdbscan
hdbs = hdbscan . HDBSCAN ( min_cluster_size = 100 , # conservative clusters' size
metric = 'euclidean' , # points distance metric
cluster_selection_method = 'leaf' ) # favour fine grained clustering
clusters = hdbs . fit_predict ( embedding_5d ) # apply HDBSCAN on reduced UMAP O cluster_selection_method determina como o hdbscan seleciona clusters planos da hierarquia da árvore. No nosso caso, o uso do método de seleção de cluster eom (excesso de massa) em combinação com a quantização de incorporação tendeu a criar alguns clusters maiores e menos específicos. Esses clusters exigiriam um processo adicional de recluster para extrair tópicos latentes significativos. Em vez disso, mudando para o método de seleção leaf , guiamos o algoritmo a selecionar nós da folha da hierarquia de cluster, que produziu um agrupamento mais refinado em comparação com o excesso do método de massa:
Comparação do método de cluster de folhas HDBSCAN EOM usando a interrupção INT8-EMBEDDING
Depois de realizar a etapa de cluster, agora ilustramos como inferir o tópico latente de cada cluster combinando um LLM como a instrução Mistral-7B [5] com pydantic e Langchain para criar um pipeline LLM que gera a saída em um formato estruturado composável.
Modelos pydantic são classes que derivam de pydantic.BaseModel , definindo campos como atributos de anotamento do tipo. Eles são semelhantes aos Python dataclasses. No entanto, eles foram projetados com diferenças sutis, mas significativas, que otimizam várias operações, como validação, serialização e geração de esquema JSON . Nossa classe Topic define um campo chamado label . Isso gerará saída LLM em um formato estruturado, em vez de um bloco de texto de forma livre, facilitando o processamento e a análise mais fáceis.
from pydantic import BaseModel , Field
class Topic ( BaseModel ):
"""
Pydantic Model to generate an structured Topic Model
"""
label : str = Field (..., description = "Identified topic" )Os modelos de prompt de Langchain são receitas predefinidas para traduzir a entrada e os parâmetros do usuário em instruções para um modelo de idioma. Definimos aqui o prompt de nossa tarefa pretendida:
from langchain_core . prompts import PromptTemplate
topic_prompt = """
You are a helpful research assistant. Your task is to analyze a set of research paper
titles related to Natural Language Processing, and determine the overarching topic.
INSTRUCTIONS:
1. Based on the titles provided, identify the most relevant topic:
- Ensure the topic is concise and clear.
2. Format Respose:
- Ensure the title response is in JSON as in the 'OUTPUT OUTPUT' section below.
- No follow up questions are needed.
OUTPUT FORMAT:
{{"label": "Topic Name"}}
TITLES:
{titles}
""" Vamos agora compor um pipeline de modelagem de tópicos usando a linguagem de expressão de Langchain (LCEL) para renderizar nosso modelo de prompt na entrada LLM e analisar a saída de inferência como JSON :
from langchain . chains import LLMChain
from langchain_huggingface import HuggingFaceEndpoint
from langchain_core . output_parsers import PydanticOutputParser
from typing import List
def TopicModeling ( titles : List [ str ]) -> str :
"""
Infer the common topic of the given titles w/ LangChain, Pydantic, OpenAI
"""
repo_id = "mistralai/Mistral-7B-Instruct-v0.3"
llm = HuggingFaceEndpoint (
repo_id = repo_id ,
temperature = 0.2 ,
huggingfacehub_api_token = os . environ [ "HUGGINGFACEHUB_API_TOKEN" ]
)
prompt = PromptTemplate . from_template ( topic_prompt )
parser = PydanticOutputParser ( pydantic_object = Topic )
topic_chain = prompt | llm | parser
return topic_chain . invoke ({ "titles" : titles })Para permitir que o modelo inferir o tópico de cada cluster, incluímos um subconjunto de 25 títulos em papel de cada cluster como parte da entrada LLM:
topics = []
for i , cluster in df . groupby ( 'cluster' ):
titles = cluster [ 'title' ]. sample ( 25 ). tolist ()
topic = TopicModeling ( titles )
topics . append ( topic . label )Vamos atribuir cada publicação ARXIV ao seu cluster correspondente:
n_clusters = len ( df [ 'cluster' ]. unique ())
topic_map = dict ( zip ( range ( n_clusters ), topics ))
df [ 'topic' ] = df [ 'cluster' ]. map ( topic_map )Para criar uma taxonomia hierárquica, criamos um aviso para orientar o Claude Sonnet 3.5 na organização dos tópicos de pesquisa identificados correspondentes a cada cluster em um esquema hierárquico:
from langchain_core . prompts import PromptTemplate
taxonomy_prompt = """
Create a comprehensive and well-structured taxonomy
for the ArXiv cs.CL (Computational Linguistics) category.
This taxonomy should organize subtopics in a logical manner.
INSTRUCTIONS:
1. Review and Refine Subtopics:
- Examine the provided list of subtopics in computational linguistics.
- Ensure each subtopic is clearly defined and distinct from others.
2. Create Definitions:
- For each subtopic, provide a concise definition (1-2 sentences).
3. Develop a Hierarchical Structure:
- Group related subtopics into broader categories.
- Create a multi-level hierarchy, with top-level categories and nested subcategories.
- Ensure that the structure is logical and intuitive for researchers in the field.
4. Validate and Refine:
- Review the entire taxonomy for consistency, completeness, and clarity.
OUTPUT FORMAT:
- Present the final taxonomy in a clear, hierarchical format, with:
. Main categories
.. Subcategories
... Individual topics with their definitions
SUBTOPICS:
{taxonomy_subtopics}
""" Vamos criar um gráfico de dispersão interativa:
chart = alt . Chart ( df ). mark_circle ( size = 5 ). encode (
x = 'x' ,
y = 'y' ,
color = 'topic:N' ,
tooltip = [ 'title' , 'topic' ]
). interactive (). properties (
title = 'Clustering and Topic Modeling | 25k arXiv cs.CL publications)' ,
width = 600 ,
height = 400 ,
)
chart . display () E compare os resultados do cluster usando as representações de incorporação float32 e os transformadores de sentenças int8 : Quantização:
HDBSCAN FLUSERING usando o Float32 e o Quantized-Int8 incorporados (frase-transformadores-quantização)
Agora realizamos a mesma comparação com nossa implementação de quantização personalizada:
HDBSCAN FLUSERing usando o float32 e as incorporações quantizadas-uint8 (personalização personalizada)
Os resultados do cluster usando float32 e (u)int8 mostram um layout geral semelhante de aglomerados bem definidos, indicando que (i) o algoritmo de agrupamento HDBSCAN foi eficaz em ambos os casos e (ii) as relações centrais nos dados foram mantidas após a quantização (usando transformadores de sentença e nossa implementação personalizada).
Notavelmente, pode -se observar que o uso de quantização de incorporação resultou em ambos os casos em agrupamentos um pouco mais granulares (35 aglomerados versus 31) que parecem ser semanticamente coerentes. Nossa hipótese provisória para essa diferença é que a quantização escalar pode guiar paradoxicamente o algoritmo de agrupamento HDBSCAN para separar pontos que foram agrupados anteriormente.
Isso pode ser devido ao (i) ruído (a quantização pode criar pequenas variações barulhentas nos dados, o que pode ter um tipo de efeito de regularização e levar a decisões de cluster mais sensíveis), ou devido a (ii) a diferença na precisão numérica e na alteração dos cálculos de distância (isso pode ampliar certas diferenças entre os pontos que foram menos pronunciados na representação float32 . Uma investigação mais aprofundada seria necessária para entender completamente as implicações da quantização no agrupamento.
Todo o esquema está disponível em cs.cl.taxonomia. Essa abordagem pode servir como uma linha de base para identificar automaticamente os esquemas de candidatos de classes em categorias ARXIV de alto nível:
. Foundations of Language Models
.. Model Architectures and Mechanisms
... Transformer Models and Attention Mechanisms
... Large Language Models (LLMs)
.. Model Optimization and Efficiency
... Compression and Quantization
... Parameter-Efficient Fine-Tuning
... Knowledge Distillation
.. Learning Paradigms
... In-Context Learning
... Instruction Tuning
. AI Ethics, Safety, and Societal Impact
.. Ethical Considerations
... Bias and Fairness in Models
... Alignment and Preference Optimization
.. Safety and Security
... Hallucination in LLMs
... Adversarial Attacks and Robustness
... Detection of AI-Generated Text
.. Social Impact
... Hate Speech and Offensive Language Detection
... Fake News Detection
[...]
@article{carpintero2024
author = { Diego Carpintero},
title = {Taxonomy Completion with Embedding Quantization and an LLM-Pipeline: A Case Study in Computational Linguistics},
journal = {Hugging Face Blog},
year = {2024},
note = {https://huggingface.co/blog/dcarpintero/taxonomy-completion},
}