El volumen cada vez mayor de publicaciones de investigación requiere métodos eficientes para estructurar el conocimiento académico. Esta tarea generalmente implica desarrollar un esquema subyacente supervisado de clases y asignar publicaciones a la clase más relevante. En este artículo, implementamos una solución automatizada de extremo a extremo utilizando cuantización de incrustación y una tubería de modelo de lenguaje grande (LLM). Nuestro estudio de caso comienza con un conjunto de datos de 25,000 publicaciones ARXIV de la lingüística computacional (cs.Cl), publicado antes de julio de 2024, que organizamos bajo un nuevo esquema de clases.
Nuestro enfoque se centra en tres tareas clave: (i) la agrupación no supervisada del conjunto de datos ARXIV en colecciones relacionadas, (ii) descubriendo las estructuras temáticas latentes dentro de cada clúster y (iii) creando un esquema de taxonomía candidata basado en dichas estructuras temáticas.
En esencia, la tarea de agrupación requiere identificar un número suficiente de ejemplos similares dentro de un conjunto de datos no etiquetado . Esta es una tarea natural para los incrustaciones, ya que capturan relaciones semánticas en un corpus y pueden proporcionarse como características de entrada a un algoritmo de agrupación para establecer enlaces de similitud entre ejemplos. Comenzamos transformando los pares ( Título : Resumen ) de nuestro conjunto de datos en una representación de incrustaciones utilizando Jina-Embeddings-V2, un modelo de atención basado en Bert-Alibi. Y aplicar cuantificación escalar utilizando tanto transformadores de oraciones como una implementación personalizada.
Para la agrupación, ejecutamos HDBSCAN en un espacio dimensional reducido, comparando los resultados utilizando métodos de agrupación eom y leaf . Además, examinamos si el uso de (u)int8 Incrustaciones cuantifica en lugar de representaciones float32 afecta este proceso.
Para descubrir temas latentes dentro de cada grupo de publicaciones de ARXIV, combinamos Langchain y Pydantic con Mistral-7B-Instructo-V0.3 (y GPT-4O, incluidos para comparar) en un Pipeline LLM. La salida se incorpora luego en una plantilla de inmediato refinada que guía el soneto de Claude 3.5 en la generación de una taxonomía jerárquica.
Los resultados insinúan 35 temas de investigación emergentes, en los que cada tema comprende al menos 100 publicaciones. Estos se organizan dentro de 7 clases de padres en el campo de la lingüística computacional (CS.Cl). Este enfoque puede servir como línea de base para generar automáticamente esquemas de candidatos jerárquicos en categorías ARXIV de alto nivel y completar eficientemente las taxonomías, abordando el desafío planteado por el creciente volumen de literatura académica.
Taxonomy Finalización de la literatura académica con cuantización de incrustación y un Pipeline LLM
Los incrustaciones son representaciones numéricas de objetos del mundo real como texto, imágenes y audio que encapsulan la información semántica de los datos que representan. Son utilizados por modelos de IA para comprender dominios de conocimiento complejos en aplicaciones aguas abajo, como la agrupación, la recuperación de información y las tareas de comprensión semántica, entre otras.
Mapearemos ( Título : Resumen ) Pares de publicaciones ARXIV a un espacio de 768 dimensiones utilizando Jina-Embeddings-V2 [1], un modelo de incrustación de texto de código abierto capaz de acomodar hasta 8192 tokens. Esto proporciona una longitud de secuencia suficientemente grande para títulos, resúmenes y otras secciones de documentos que podrían ser relevantes. Para superar el límite convencional de 512-token presente en otros modelos, Jina-Embeddings-V2 incorpora la coartada bidireccional [2] en el marco Bert. La alibi (atención con sesgos lineales) permite la extrapolación de la longitud de entrada (es decir, secuencias superiores a 2048 tokens) codificando información posicional directamente dentro de la capa de autoatimiento, en lugar de introducir incrustaciones posicionales. En la práctica, sesga los puntajes de atención de consulta con una penalización que es proporcional a su distancia, favoreciendo una atención mutua más fuerte entre los tokens próximos.
El primer paso para usar el modelo Jina-Embeddings-V2 es cargarlo a través de los transformadores de oraciones, un marco para acceder a modelos de última generación que están disponibles en el Hub Face Hub:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer ( 'jinaai/jina-embeddings-v2-base-en' , trust_remote_code = True ) Ahora codificamos ( Título : Resumen ) Pares de nuestro conjunto de datos usando batch_size = 64 . Esto permite el cálculo paralelo en aceleradores de hardware como GPU (aunque a costa de requerir más memoria):
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 )La similitud semántica entre los corpus ahora se puede calcular trivialmente como el producto interno de las integridades. En el siguiente mapa de calor, cada entrada [x, y] se colorea en función de dicho producto de incrustaciones para oraciones ejemplares de ' título ' [x] y [y].
Similar semántico en Cs.Cl arxiv-titles usando incrustaciones
La ampliación de los incrustaciones puede ser un desafío. Actualmente, los modelos de vanguardia representan cada incrustación como float32 , que requiere 4 bytes de memoria. Dado que Jina-Embeddings-V2 mapea el texto a un espacio de 768 dimensiones, los requisitos de memoria para nuestro conjunto de datos serían alrededor de 73 MB, sin índices y otros metadatos relacionados con los registros de publicación:
25 , 000 embeddings * 768 dimensions / embedding * 4 bytes / dimension = 76 , 800 , 000 bytes
76 , 800 , 000 bytes / ( 1024 ^ 2 ) ≈ 73.24 MBSin embargo, trabajar con un conjunto de datos más grande podría aumentar significativamente los requisitos de memoria y los costos asociados:
| Incrustación Dimensión | Incrustación Modelo | 2.5m Resúmenes arxiv | 60.9m Páginas de Wikipedia | 100m Incrustaciones |
|---|---|---|---|---|
| 384 | All-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-Embeddings-V2 | 7.15 GB | 170.52 GB | 285.76 GB |
| 1536 | Operai-Text-Embedding-3-Small | 14.31 GB | 341.04 GB | 571.53 GB |
| 3072 | OpenAI-Text-Embeding-3-Large | 28.61 GB | 682.08 GB | 1.143 TB |
Una técnica utilizada para lograr el ahorro de memoria es la cuantización . La intuición detrás de este enfoque es que podemos discretizar los valores de punto flotante mapeando su rango [ f_max , f_min ] en un rango más pequeño de números de punto fijo [ q_max , q_min ], y distribuyendo linealmente todos los valores entre estos rangos. En la práctica, esto generalmente reduce la precisión de un punto flotante de 32 bits a anchos de bits inferiores como 8 bits (cuantización escalar) o valores de 1 bits (cuantización binaria).
Cuantización de incrustación escalar: desde Float32 a (U) INT8
Al trazar la distribución de frecuencia de los incrustaciones generadas por Jina , observamos que los valores se concentran en un rango relativamente estrecho [-2.0, +2.0]. Esto significa que podemos asignar efectivamente los valores float32 a 256 (u)int8 Buckets sin pérdida significativa de información:
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 ()Float32 original Jina-Embeddings-V2 Distribución
Podemos calcular los valores exactos [min, max] de la distribución:
> >> np . min ( f32_embeddings ), np . max ( f32_embeddings )
( - 2.0162134 , 2.074683 ) El primer paso para implementar la cuantización escalar es definir un conjunto de incrustaciones de calibración. Un punto de partida típico es un subconjunto de 10k incrustaciones, que en nuestro caso cubriría casi el 99.98% de los valores de incrustación float32 originales. El uso de la calibración está destinado a obtener valores representativos f_min y f_max a lo largo de cada dimensión para reducir la sobrecarga computacional y los posibles problemas causados por valores atípicos que pueden aparecer en conjuntos de datos más grandes.
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 % El segundo y tercer paso de la cuantización escalar ( escalas de cálculo y el punto cero , y la codificación ) pueden aplicarse fácilmente con transformadores de oraciones, lo que resulta en un ahorro de memoria 4X en comparación con la representación float32 original. Además, también nos beneficiaremos de operaciones aritméticas más rápidas, ya que la multiplicación de matriz se puede realizar más rápidamente con aritmética entera.
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 completar, implementamos un método de cuantización escalar para ilustrar esos tres pasos:
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 con la versión de los incrustaciones que se han cuantificado utilizando transformadores de oraciones (nuestra implementación personalizada también se incluye en el análisis de resultados):
# `f32_embeddings` => if you prefer to not use quantization
# `beta_uint8_embeddings` => to check our custom implemention
embeddings = int8_embeddings En esta sección, realizamos una proyección de dos etapas de ( Título : Resumen ) de incrustación de pares de su espacio original de alta dimensión (768) a dimensiones más bajas, a saber:
5 dimensions para reducir la complejidad computacional durante la agrupación, y2 dimensions para habilitar la representación visual en coordenadas (x, y) .Para ambas proyecciones, empleamos UMAP [3], una técnica de reducción de dimensionalidad popular conocida por su efectividad en la preservación de las estructuras de datos locales y globales. En la práctica, esto lo convierte en una opción preferida para manejar conjuntos de datos complejos con incrustaciones de alta dimensión:
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 ) Tenga en cuenta que cuando aplicamos la agrupación HDBSCAN en el siguiente paso, los grupos encontrados estarán influenciados por cómo UMAP conservó las estructuras locales. Un valor más pequeño n_neighbors significa que UMAP se centrará más en las estructuras locales, mientras que un valor mayor permite capturar más representaciones globales, lo que podría ser beneficioso para comprender los patrones generales en los datos.
Las incrustaciones reducidas ( Título : Resumen ) ahora se pueden usar como características de entrada de un algoritmo de agrupación, lo que permite la identificación de categorías relacionadas basadas en distancias de incrustación.
Hemos optado por HDBSCAN (agrupación espacial basada en densidad jerárquica de aplicaciones con ruido) [4], un algoritmo de agrupación avanzado que extiende DBSCAN al adaptarse a variables grupos de densidad. A diferencia de K-means, que requiere especificar previamente el número de grupos, HDBSCAN tiene solo un hiperparámetro importante, n , que establece el número mínimo de ejemplos para incluir en un clúster.
HDBSCAN funciona al transformar primero el espacio de datos de acuerdo con la densidad de los puntos de datos, lo que hace que las regiones más densas (áreas donde los puntos de datos estén unidos en números altos) más atractivas para la formación de clúster. El algoritmo luego construye una jerarquía de grupos basados en el tamaño mínimo del clúster establecido por el hiperparámetro n . Esto le permite distinguir entre ruido (áreas dispersas) y regiones densas (grupos potenciales). Finalmente, Hdbscan condensa esta jerarquía a derivar los grupos más persistentes, identificando grupos de diferentes densidades y formas. Como método basado en densidad, también puede detectar valores atípicos.
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 El cluster_selection_method determina cómo HDBSCAN selecciona grupos planos de la jerarquía de árboles. En nuestro caso, el uso del método de selección de clúster eom (exceso de masa) en combinación con la cuantización de incrustación tendió a crear algunos grupos más grandes y menos específicos. Estos grupos habrían requerido un proceso de reclusación adicional para extraer temas latentes significativos. En cambio, al cambiar al método de selección leaf , guiamos el algoritmo para seleccionar nodos de hoja de la jerarquía del clúster, que produjo una agrupación de grano más fino en comparación con el exceso de método de masa:
Comparación del método de agrupación HDBSCAN EOM y Leaf utilizando la quantización de INT8-Embedding
Después de haber realizado el paso de agrupación, ahora ilustramos cómo inferir el tema latente de cada clúster combinando una LLM como el instructo Mistral-7B [5] con Pydantic y Langchain para crear una tubería LLM que genera salida en un formato estructurado compuesto.
Los modelos Pydantic son clases que derivan de pydantic.BaseModel , definiendo los campos como atributos anotados por tipo. Son similares a las dataclases Python . Sin embargo, han sido diseñados con diferencias sutiles pero significativas que optimizan diversas operaciones, como la validación, la serialización y la generación de esquemas JSON . Nuestra clase Topic define un campo llamado label . Esto generará la salida LLM en un formato estructurado, en lugar de un bloque de texto de forma libre, facilitando un procesamiento y análisis más fácil.
from pydantic import BaseModel , Field
class Topic ( BaseModel ):
"""
Pydantic Model to generate an structured Topic Model
"""
label : str = Field (..., description = "Identified topic" )Las plantillas de solicitud de Langchain son recetas predefinidas para traducir la entrada y los parámetros del usuario en instrucciones para un modelo de idioma. Definimos aquí el mensaje para nuestra tarea prevista:
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}
""" Ahora componamos una tubería de modelado de temas utilizando el lenguaje de expresión de Langchain (LCEL) para convertir nuestra plantilla de inmediato en la entrada LLM y analizar la salida de inferencia 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 el modelo infiera el tema de cada clúster, incluimos un subconjunto de 25 títulos de papel de cada clúster como parte de la entrada LLM:
topics = []
for i , cluster in df . groupby ( 'cluster' ):
titles = cluster [ 'title' ]. sample ( 25 ). tolist ()
topic = TopicModeling ( titles )
topics . append ( topic . label )Asignemos cada publicación ARXIV a su clúster correspondiente:
n_clusters = len ( df [ 'cluster' ]. unique ())
topic_map = dict ( zip ( range ( n_clusters ), topics ))
df [ 'topic' ] = df [ 'cluster' ]. map ( topic_map )Para crear una taxonomía jerárquica, elaboramos un aviso para guiar a Claude Sonnet 3.5 al organizar los temas de investigación identificados correspondientes a cada clúster en un esquema jerá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}
""" Creemos una trama de dispersión interactiva:
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 () Y compare los resultados de agrupación utilizando representaciones de incrustación float32 y cuantificación de transformadores de oraciones int8 :
La agrupación de hojas HDBSCAN utilizando Float32 e Incremedios cuantizados-INT8 (Sentence-Transformers-Cuantización)
Ahora realizamos la misma comparación con nuestra implementación de cuantificación personalizada:
La agrupación de hojas HDBSCAN utilizando incrustaciones Float32 y Quantized-Uint8 (implementación de la cuantización personalizada)
Los resultados de la agrupación utilizando float32 e (u)int8 Incrustaciones cuantificadas muestran un diseño general similar de grupos bien definidos, lo que indica que (i) el algoritmo de agrupación HDBSCAN fue efectivo en ambos casos, y (ii) las relaciones centrales en los datos se mantuvieron después de la cuantificación (utilizando transformadores de oraciones y nuestra implementación personalizada).
En particular, se puede observar que el uso de la cuantización de incrustación dio como resultado ambos casos en agrupación ligeramente más granular (35 grupos versus 31) que parecen ser semánticamente coherentes. Nuestra hipótesis tentativa para esta diferencia es que la cuantización escalar podría guiar paradójicamente el algoritmo de agrupación HDBSCAN para separar puntos que se agruparon previamente.
Esto podría deberse al ruido (i) (la cuantización puede crear pequeñas variaciones ruidosas en los datos, lo que podría tener una especie de efecto de regularización y conducir a decisiones de agrupamiento más sensibles), o debido a (ii) la diferencia en la precisión numérica y la alteración de los cálculos de distancia (esto podría amplificar ciertas diferencias entre los puntos que se pronuncian menos pronunciados en la representación float32 ). Sería necesaria una mayor investigación para comprender completamente las implicaciones de la cuantización en la agrupación.
Todo el esquema está disponible en cs.Cl.Taxonomy. Este enfoque puede servir como línea de base para identificar automáticamente esquemas de clases candidatos en categorías ARXIV de alto nivel:
. 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},
}