En este artículo, aprenderá a crear un canal de agrupación de texto combinando incrustaciones de modelos de lenguaje grandes con HDBSCAN, un algoritmo de agrupación basado en densidad, para descubrir automáticamente temas en datos de texto sin etiquetar.
Los temas que cubriremos incluyen:
Cómo generar incrustaciones de texto para documentos sin formato utilizando un modelo de transformadores de oraciones previamente entrenado. Cómo reducir la dimensionalidad de esas incorporaciones con UMAP para prepararlas para la agrupación. Cómo aplicar HDBSCAN para descubrir automáticamente grupos de temas y visualizar los resultados.
Agrupación de texto no estructurado con incrustaciones LLM y HDBSCAN
Introducción
La era actual de la IA generativa parece centrarse principalmente en las interfaces y mensajes de chat, pero la gama de aplicaciones de los grandes modelos de lenguaje, o LLM para abreviar, no se limita solo a eso. De hecho, una de sus capacidades posteriores más poderosas consiste en convertir texto crudo, desordenado y desestructurado en representaciones matemáticas semánticamente ricas llamadas incrustaciones. Una vez hecho esto, podemos usar estas representaciones de texto para una variedad de casos de uso de aprendizaje automático, y la agrupación en clústeres no es una excepción.
En particular, las incrustaciones se pueden combinar con técnicas avanzadas de agrupamiento basadas en densidad como HDBSCAN, lo que permite como resultado el descubrimiento de temas, patrones o categorías ocultos en su colección de documentos de texto: todo sin la necesidad de un etiquetado previo.
Este artículo muestra cómo construir una canalización de agrupación en clústeres basada en texto desde cero. Usaremos un conjunto de datos disponible gratuitamente que contiene instancias de texto, así como un LLM de código abierto que ha sido capacitado para generar incrustaciones, es decir, el llamado modelo de incrustación. La guinda del pastel: utilizaremos bibliotecas de Python modernas, prácticas y gratuitas que proporcionan implementaciones de algoritmos de agrupación en clústeres como HDBSCAN.
Tutorial paso a paso
Primero, comencemos instalando las bibliotecas clave de Python que necesitaremos:
Transformadores de oraciones, para cargar un LLM previamente entrenado para la generación integrada desde Hugging Face; necesitará una clave API de Hugging Face, también llamada token de acceso, para poder cargar el modelo. Umap-learn, para aplicar un algoritmo para reducir la dimensionalidad de las incrustaciones.
Del mismo modo, si está trabajando en un IDE local en lugar de un entorno de portátil en la nube y no tiene scikit-learn y pandas, es posible que también deba instalarlos.
!pip install sentence-transformers umap-learn
! pip install sentence – transformers umap – learn
Ahora comenzamos la parte de codificación obteniendo algunos datos nuevos. La función fetch_20newsgroups, que recupera un conjunto de datos que contiene textos de artículos de noticias categorizados, será suficiente. Tenga en cuenta que, aunque el conjunto de datos contiene etiquetas, las omitiremos, ya que pretendemos no conocer esta información con el fin de agrupar estas instancias de datos en grupos según la similitud. Además, tomamos una muestra del conjunto de datos a 150 instancias, lo que será lo suficientemente representativo para nuestro ejemplo.
import pandas as pd
from sklearn.datasets import fetch_20newsgroups
# Fetching a highly targeted subset of data (~150-200 docs)
categories = [‘sci.space’, ‘sci.med’, ‘rec.autos’]
newsgroups = fetch_20newsgroups(subset=”train”, categories=categories, remove=(‘headers’, ‘footers’, ‘quotes’))
# Sampling down into a representative, illustrative subset
df = pd.DataFrame({‘text’: newsgroups.data, ‘true_label’: newsgroups.target})
df = df[df[‘text’].str.strip().str.len() > 100].sample(150, random_state=42).reset_index(drop=True)
print(f”Loaded {len(df)} text documents.”)
print(“nSample document:”)
print(df[‘text’].iloc[0][:150] + "…")
importar pandas como PD
de sklearn . importación de conjuntos de datos y búsqueda _ 20newsgroups
# Obteniendo un subconjunto de datos altamente específico (~150-200 documentos)
categorias = [ 'ciencia.espacio' , 'ciencia.med' , 'rec.autos' ]
grupos de noticias = fetch_20newsgroups ( subconjunto = 'tren' , categorías = categorías , eliminar = ( 'encabezados' , 'pies de página' , 'citas' ) )
# Muestreo en un subconjunto representativo e ilustrativo
df = PD . Marco de datos ( { 'texto' : grupos de noticias . datos , 'etiqueta_verdadera' : grupos de noticias . objetivo } )
df = df [ df [ 'texto' ] . cadena . banda ( ) . cadena . len ( ) > 100 ] . muestra ( 150 , estado_aleatorio = 42 ) . reset_index ( soltar = Verdadero )
print ( f "Documentos de texto {len(df)} cargados." )
imprimir ( "nDocumento de muestra: " )
imprimir ( df [ 'texto' ] . iloc [ 0 ] [ : 150 ] + "…" )
Producción:
Loaded 150 text documents.
Sample document:
Okay Mr. Dyer, we’re properly impressed with your philosophical skills and
ability to insult people. You’re a wonderful speaker and an adept politic…
Loaded 150 text documents .
Sample document :
Okay Mr . Dyer , we ‘re properly impressed with your philosophical skills and
ability to insult people. You’ re a wonderful speaker and an adept politic . . .
El siguiente paso es obtener las incrustaciones a partir de textos sin formato. Para hacer esto, cargamos all-MiniLM-L6-v2 de la biblioteca de transformadores de oraciones de Hugging Face. Se trata de un modelo ligero pero eficaz para obtener incrustaciones rápidamente.
from sentence_transformers import SentenceTransformer
# Loading the free, open-source model
model = SentenceTransformer(‘all-MiniLM-L6-v2’)
# Encoding text documents into dense vector embeddings
print(“Generating embeddings…”)
embeddings = model.encode(df[‘text’].tolist(), show_progress_bar=True)
print(f”Embedding matrix shape: {embeddings.shape}”)
from sentence_transformers import SentenceTransformer
# Loading the free, open-source model
model = SentenceTransformer ( ‘all-MiniLM-L6-v2’ )
# Encoding text documents into dense vector embeddings
print ( “Generating embeddings…” )
embeddings = model . encode ( df [ ‘text’ ] . tolist ( ) , show_progress_bar = True )
print ( f “Embedding matrix shape: {embeddings.shape}” )
Dado que la dimensión de incrustación es originalmente demasiado alta para fines de agrupación, ahora aplicamos una técnica de reducción de dimensionalidad utilizando el algoritmo UMAP de la biblioteca homónima instalada anteriormente:
import umap
# Reducing embedding dimensions to 5, to retain enough density information for clustering
reducer = umap.UMAP(n_neighbors=15, n_components=5, min_dist=0.0, random_state=42)
reduced_embeddings = reducer.fit_transform(embeddings)
print(f”Reduced matrix shape: {reduced_embeddings.shape}”)
import umap
# Reducing embedding dimensions to 5, to retain enough density information for clustering
reducer = umap . UMAP ( n_neighbors = 15 , n_components = 5 , min_dist = 0.0 , random_state = 42 )
reduced_embeddings = reducer . fit_transform ( embeddings )
print ( f “Reduced matrix shape: {reduced_embeddings.shape}” )
Ahora nuestros vectores de incrustación numéricos asociados con artículos de noticias constan únicamente de cinco dimensiones (atributos). Veamos si esta representación compacta es lo suficientemente significativa como para obtener una agrupación detallada aplicando el algoritmo HDBSCAN, que es un enfoque de agrupación basado en densidad:
from sklearn.cluster import HDBSCAN
# Initializing HDBSCAN
# min_cluster_size=8: we specified that each cluster must have at least 8 documents
clusterer = HDBSCAN(min_cluster_size=8, min_samples=3, store_centers=”centroid”)
df[‘cluster’] = clusterer.fit_predict(reduced_embeddings)
# Counting instances per cluster
cluster_counts = df[‘cluster’].value_counts()
print(“nCluster Distribution:”)
print(cluster_counts)
from sklearn . cluster import HDBSCAN
# Initializing HDBSCAN
# min_cluster_size=8: we specified that each cluster must have at least 8 documents
agrupador = HDBSCAN ( min_cluster_size = 8 , min_muestras = 3 , store_centers = 'centroide' )
df [ 'grupo' ] = agrupador . fit_predict ( incrustaciones_reducidas )
# Contando instancias por cluster
cluster_counts = df [ 'clúster' ] . valor_cuenta ( )
imprimir ( "nDistribución del clúster: " )
imprimir ( cluster_counts )
Importante: los resultados de la agrupación están parcialmente influenciados por la configuración de hiperparámetros que definimos para HDBSCAN. Le recomiendo que pruebe otras configuraciones para el tamaño mínimo del clúster y otros hiperparámetros para explorar cómo esto afecta los resultados.
Resultado:
Cluster Distribution:
cluster
0 101
1 49
Name: count, dtype: int64
Cluster Distribution :
cluster
0 101
1 49
Name : count , dtype : int64
Parece que HDBSCAN detectó dos grupos asociados con regiones de alta densidad en el espacio de datos. ¿Habría también puntos ruidosos que no estuvieran asignados a ninguno de estos dos grupos? Comprobemos:
for cluster_id in sorted(df[‘cluster’].unique()):
if cluster_id == -1:
print(“n=== CLUSTER: NOISE / UNCLASSIFIED ===”)
else:
print(f”n=== CLUSTER: Discovered Topic #{cluster_id} ===”)
# Getting up to 3 sample texts from this cluster
samples = df[df[‘cluster’] == cluster_id][‘text’].head(3).tolist()
for i, sample in enumerate(samples, 1):
clean_sample = ” “.join(sample.split())[:120]
print(f” {i}. {clean_sample}…”)
for cluster_id in sorted ( df [ ‘cluster’ ] . unique ( ) ) :
if cluster_id == – 1 :
print ( “n=== CLUSTER: NOISE / UNCLASSIFIED ===” )
else :
print ( f “n=== CLUSTER: Discovered Topic #{cluster_id} ===” )
# Getting up to 3 sample texts from this cluster
samples = df [ df [ ‘cluster’ ] == cluster_id ] [ ‘text’ ] . head ( 3 ) . tolist ( )
for i , sample in enumerate ( samples , 1 ) :
clean_sample = ” “ . join ( sample . split ( ) ) [ : 120 ]
print ( f ” {i}. {clean_sample}…” )
Producción:
=== CLUSTER: Discovered Topic #0 ===
1. Okay Mr. Dyer, we’re properly impressed with your philosophical skills and ability to insult people. You’re a wonderful …
2. I was at an interesting seminar at work (UK’s R.A.L. Space Science Dept.) on this subject, specifically on a small-scale…
3. This is the second post which seems to be blurring the distinction between real disease caused by Candida albicans and t…
=== CLUSTER: Discovered Topic #1 ===
1. It’s great that all these other cars can out-handle, out-corner, and out- accelerate an Integra. But, you’ve got to ask …
2. l diamond star cars (Talon/Eclipse/Laser) put out 190 hp in the turbo models, and 195 hp in the AWD turbo models, These …
3. Sorry for the mis-spelling, but I forgot how to spell it after my series of exams and NO-on hand reference here. Is it s…
=== GRUPO : Tema descubierto #0 ===
1. Está bien Sr. tintorero , Estamos realmente impresionados con sus habilidades filosóficas y su capacidad para insultar a la gente. Estás a maravilloso . . .
2. I estaba en un seminario interesante en el trabajo ( Departamento de Ciencias Espaciales de la RAL del Reino Unido ) sobre este tema, específicamente a pequeña escala…
3. Esta es la segunda publicación que parece desdibujar la distinción entre la enfermedad real causada por Candida albicans y la…
=== CLÚSTER: Tema descubierto #1 ===
1. es Es fantástico que todos estos otros coches puedan superarlo en manejo . fuera de la esquina , y afuera – Acelerar un Integra . Pero , tienes que preguntar . . .
2. yo coches estrella de diamante ( Talón / Eclipse / Láser ) apagar 190 caballos de fuerza en los modelos turbo , y 195 caballos de fuerza en los modelos turbo AWD , Estos . . .
3. Lo siento por el error ortográfico , pero I olvidé cómo deletréalo después de mi serie de exámenes y NO hay referencia disponible aquí . Es él s . . .
Parece que todos los puntos de datos de la muestra de 150 se asignaron a cualquiera de los dos grupos identificados, lo que da a entender que los artículos de noticias podrían separarse fácilmente según el tema.
Para obtener información adicional, podemos mostrar algunas visualizaciones de clústeres con la ayuda del código complementario que se proporciona a continuación, que muestra un diagrama de dispersión para cada combinación por pares de los cinco componentes existentes que describen cada punto de datos:
import matplotlib.pyplot as plt
import seaborn as sns
import itertools
# Creating a DataFrame for the 5 reduced embeddings and cluster labels
reduced_df = pd.DataFrame(reduced_embeddings, columns=[f’UMAP_D{i+1}’ for i in range(reduced_embeddings.shape[1])]) reduce_df['cluster'] = df['cluster'] # Obteniendo todas las combinaciones únicas por pares de las 5 dimensiones dim_pairs = list(itertools.combinations(reduced_df.columns[:-1], 2)) num_plots = len(dim_pairs) num_cols = 3 num_rows = (num_plots + num_cols – 1) // num_cols plt.figure(figsize=(num_cols * 5, num_rows * 4)) para i, (dim1, dim2) en enumerate(dim_pairs): plt.subplot(num_rows, num_cols, i + 1) sns.scatterplot( x=dim1, y=dim2, hue="cluster", data=reduced_df, paleta="viridis", s=70, alpha=0.7, legend='full' ) plt.title(f'{dim1} vs {dim2}') plt.xlabel(dim1) plt.ylabel(dim2) plt.grid(True, linestyle="–", alpha=0.6) plt.tight_layout() plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
importar matplotlib . trazado de datos como pl
importar productos del mar como redes sociales
importar itertools
# Creando un DataFrame para las 5 incrustaciones reducidas y etiquetas de cluster
reducido_df = PD . Marco de datos ( incrustaciones_reducidas , columnas = [ f 'UMAP_D{i+1}' para i en rango ( incrustaciones_reducidas . forma [ 1 ] ) ] )
df_reducido [ 'clúster' ] = df [ 'grupo' ]
# Obtener todas las combinaciones únicas por pares de las 5 dimensiones
pares_tenues = lista ( itertools . combinaciones ( reducido_df . columnas [ : – 1 ] , 2 ) )
num_plots = len ( pares_dim )
núm_cols = 3
numero_filas = ( núm_tramas + núm_cols – 1 ) // núm_cols
pl . figura ( tamaño de figura = ( num_cols * 5 , núm_filas * 4 ) )
para i , ( tenue1 , tenue2 ) en enumerar ( dim_pairs ) :
pl . subtrama ( num_rows , núm_cols , i + 1 )
sns . diagrama de dispersión (
x = tenue1 ,
y = tenue2 ,
tono = 'grupo' ,
datos = df_reducido ,
paleta = 'viridis' ,
s = 70 ,
alfa = 0,7 ,
leyenda = 'lleno'
)
pl . título ( f '{dim1} vs {dim2}' )
pl . xlabel ( tenue1 )
pl . ylabel ( dim2 )
pl . cuadrícula ( verdadero , estilo de línea = '–' , alfa = 0,6 )
pl . diseño_apretado ( )
pl . espectáculo ( )
Resultado:
Al probar diferentes configuraciones para HDBSCAN, es posible que encuentre resultados en los que la cantidad de clústeres identificados podría ser diferente de dos. ¡Pruébalo!
Concluyendo
Una vez que hayamos pasado por el proceso de construcción del proceso de agrupación en clústeres basado en texto, vale la pena concluir señalando las razones clave por las que vale la pena integrar incrustaciones de LLM con HDBSCAN. Estos incluyen la capacidad de retener y capturar, hasta cierto punto, el verdadero significado semántico y los matices lingüísticos del texto original, gracias a las propiedades inherentes a las incrustaciones obtenidas mediante transformadores de oraciones. Además, HDBSCAN determina automáticamente una cantidad óptima de grupos y es capaz de detectar puntos periféricos que podrían ser ruido o valores atípicos que distorsionarían las estadísticas a nivel de grupo.